#!/usr/bin/env node

// src/cli/agent.ts
import { pathToFileURL } from "url";
import { parseArgs } from "util";
import { z as z21 } from "zod";

// src/knowledge/index.ts
import { existsSync as existsSync2, readdirSync, readFileSync } from "fs";
import { join } from "path";

// src/utils/logger.ts
var LEVELS = {
  debug: 10,
  info: 20,
  warn: 30,
  error: 40,
  silent: 99
};
function createLogger(level = "info") {
  const threshold = LEVELS[level];
  const emit = (lvl, message, meta) => {
    if (LEVELS[lvl] < threshold) return;
    const stamp = (/* @__PURE__ */ new Date()).toISOString();
    const suffix = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
    process.stderr.write(`[tdmcp] ${stamp} ${lvl.toUpperCase()} ${message}${suffix}
`);
  };
  return {
    debug: (message, meta) => emit("debug", message, meta),
    info: (message, meta) => emit("info", message, meta),
    warn: (message, meta) => emit("warn", message, meta),
    error: (message, meta) => emit("error", message, meta)
  };
}
var silentLogger = {
  debug: () => {
  },
  info: () => {
  },
  warn: () => {
  },
  error: () => {
  }
};

// src/utils/paths.ts
import { existsSync } from "fs";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";
var moduleDir = dirname(fileURLToPath(import.meta.url));
function firstExisting(candidates, fallback) {
  for (const candidate of candidates) {
    if (existsSync(candidate)) return candidate;
  }
  return fallback;
}
function knowledgeDataDir() {
  return firstExisting(
    [resolve(moduleDir, "../knowledge/data"), resolve(moduleDir, "knowledge/data")],
    resolve(moduleDir, "../knowledge/data")
  );
}
function recipesDir() {
  return firstExisting(
    [resolve(moduleDir, "../../recipes"), resolve(moduleDir, "recipes")],
    resolve(moduleDir, "../../recipes")
  );
}
function bottobotPackageDir() {
  const candidates = [
    resolve(moduleDir, "../../node_modules/@bottobot/td-mcp"),
    resolve(moduleDir, "../node_modules/@bottobot/td-mcp"),
    resolve(process.cwd(), "node_modules/@bottobot/td-mcp")
  ];
  for (const candidate of candidates) {
    if (existsSync(resolve(candidate, "wiki/data/processed"))) return candidate;
  }
  return void 0;
}

// src/knowledge/normalize.ts
function slugify(value) {
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
}
function compactKey(value) {
  return value.toLowerCase().replace(/[^a-z0-9]/g, "");
}
function toOperatorSummary(slug, doc) {
  return {
    slug,
    name: doc.name,
    displayName: doc.displayName ?? doc.name,
    category: doc.category ?? "Unknown",
    subcategory: doc.subcategory ?? "",
    summary: doc.summary ?? doc.description ?? "",
    keywords: doc.keywords ?? []
  };
}
function toPythonSummary(cls) {
  return {
    className: cls.className,
    displayName: cls.displayName ?? cls.className,
    category: cls.category ?? "Unknown",
    methodCount: cls.methods?.length ?? 0,
    memberCount: cls.members?.length ?? 0
  };
}
function toTutorialSummary(tut) {
  return {
    id: tut.id,
    name: tut.name,
    category: tut.category ?? "Unknown",
    summary: tut.summary ?? tut.description ?? ""
  };
}
function normalizePatterns(raw) {
  let list = [];
  if (Array.isArray(raw)) {
    list = raw;
  } else if (raw && typeof raw === "object") {
    const obj = raw;
    if (Array.isArray(obj.patterns)) {
      list = obj.patterns;
    } else {
      list = Object.values(obj).filter((v) => !!v && typeof v === "object");
    }
  }
  return list.filter((p) => typeof p.name === "string").map((p) => ({
    id: slugify(p.name),
    name: p.name,
    description: p.description,
    category: p.category,
    workflow: Array.isArray(p.workflow) ? p.workflow : void 0,
    use_case: p.use_case
  }));
}
function normalizeGlsl(raw) {
  const file = raw ?? {};
  const techniques = Array.isArray(file.techniques) ? file.techniques : [];
  return techniques.filter((t) => !!t && typeof t === "object").map((t) => {
    const name = typeof t.name === "string" ? t.name : "Untitled";
    const id = typeof t.id === "string" && t.id.length > 0 ? t.id : slugify(name);
    return {
      id,
      name,
      subcategory: typeof t.subcategory === "string" ? t.subcategory : void 0,
      description: typeof t.description === "string" ? t.description : void 0,
      difficulty: typeof t.difficulty === "string" ? t.difficulty : void 0,
      operators: Array.isArray(t.operators) ? t.operators : void 0,
      tags: Array.isArray(t.tags) ? t.tags : void 0,
      notes: typeof t.notes === "string" ? t.notes : void 0,
      code: t.code ?? void 0,
      setup: typeof t.setup === "string" ? t.setup : void 0
    };
  });
}

// src/knowledge/index.ts
var EMPTY_SOURCE = {
  kind: "empty",
  operatorsDir: "",
  pythonDir: "",
  tutorialsDir: "",
  patternsFile: "",
  glslFile: ""
};
var KnowledgeBase = class _KnowledgeBase {
  logger;
  source;
  opIndexCache;
  opLookupCache;
  opDocCache = /* @__PURE__ */ new Map();
  pyIndexCache;
  pyLookupCache;
  pyDocCache = /* @__PURE__ */ new Map();
  tutIndexCache;
  tutLookupCache;
  tutDocCache = /* @__PURE__ */ new Map();
  patternsCache;
  glslCache;
  constructor(options = {}) {
    this.logger = options.logger ?? silentLogger;
    this.source = _KnowledgeBase.resolveSource(options.dataDir ?? knowledgeDataDir());
    this.logger.debug("knowledge source resolved", { kind: this.source.kind });
  }
  static resolveSource(localDir) {
    if (existsSync2(join(localDir, "operators"))) {
      return {
        kind: "local",
        operatorsDir: join(localDir, "operators"),
        operatorsIndex: join(localDir, "operators", "index.json"),
        pythonDir: join(localDir, "python-api"),
        tutorialsDir: join(localDir, "tutorials"),
        patternsFile: join(localDir, "patterns.json"),
        glslFile: join(localDir, "glsl.json")
      };
    }
    const bb = bottobotPackageDir();
    if (bb) {
      return {
        kind: "bottobot",
        operatorsDir: join(bb, "wiki/data/processed"),
        pythonDir: join(bb, "wiki/data/python-api"),
        tutorialsDir: join(bb, "wiki/data/tutorials"),
        patternsFile: join(bb, "data/patterns.json"),
        glslFile: join(bb, "wiki/data/experimental/glsl.json")
      };
    }
    return EMPTY_SOURCE;
  }
  readJson(path) {
    try {
      return JSON.parse(readFileSync(path, "utf8"));
    } catch (err) {
      this.logger.debug("knowledge readJson failed", { path, error: String(err) });
      return void 0;
    }
  }
  listJsonFiles(dir) {
    if (!dir || !existsSync2(dir)) return [];
    try {
      return readdirSync(dir).filter((f) => f.endsWith(".json") && f !== "index.json");
    } catch {
      return [];
    }
  }
  // ---- Operators -----------------------------------------------------------
  operatorIndex() {
    if (this.opIndexCache) return this.opIndexCache;
    if (this.source.kind === "empty") {
      this.opIndexCache = [];
      return this.opIndexCache;
    }
    if (this.source.operatorsIndex && existsSync2(this.source.operatorsIndex)) {
      const data = this.readJson(this.source.operatorsIndex);
      if (Array.isArray(data)) {
        this.opIndexCache = data;
        return this.opIndexCache;
      }
    }
    const summaries = [];
    for (const file of this.listJsonFiles(this.source.operatorsDir)) {
      const doc = this.readJson(join(this.source.operatorsDir, file));
      if (doc?.name) summaries.push(toOperatorSummary(file.replace(/\.json$/, ""), doc));
    }
    this.opIndexCache = summaries;
    return this.opIndexCache;
  }
  operatorLookup() {
    if (this.opLookupCache) return this.opLookupCache;
    const map = /* @__PURE__ */ new Map();
    for (const summary of this.operatorIndex()) {
      map.set(compactKey(summary.slug), summary.slug);
      map.set(compactKey(summary.name), summary.slug);
      map.set(compactKey(summary.displayName), summary.slug);
    }
    this.opLookupCache = map;
    return map;
  }
  listOperatorCategories() {
    const set = /* @__PURE__ */ new Set();
    for (const summary of this.operatorIndex()) set.add(summary.category);
    return [...set].sort();
  }
  listOperators(category) {
    const all = this.operatorIndex();
    if (!category) return all;
    const wanted = compactKey(category);
    return all.filter((s) => compactKey(s.category) === wanted);
  }
  getOperator(nameOrSlug) {
    if (this.source.kind === "empty") return void 0;
    const slug = this.operatorLookup().get(compactKey(nameOrSlug)) ?? slugify(nameOrSlug);
    const cached = this.opDocCache.get(slug);
    if (cached !== void 0) return cached ?? void 0;
    const file = join(this.source.operatorsDir, `${slug}.json`);
    const doc = existsSync2(file) ? this.readJson(file) : void 0;
    this.opDocCache.set(slug, doc ?? null);
    return doc;
  }
  /** Soft existence check (by operator type, display name, or slug). */
  operatorExists(typeOrName) {
    return this.operatorLookup().has(compactKey(typeOrName));
  }
  searchOperators(query, limit = 25) {
    const q = query.trim().toLowerCase();
    if (!q) return [];
    const terms = q.split(/\s+/);
    const scored = [];
    for (const summary of this.operatorIndex()) {
      const haystack = `${summary.name} ${summary.displayName} ${summary.summary} ${summary.keywords.join(" ")}`.toLowerCase();
      let score = 0;
      for (const term of terms) {
        if (haystack.includes(term)) score += 1;
        if (summary.name.toLowerCase().includes(term)) score += 1;
      }
      if (score > 0) scored.push({ summary, score });
    }
    scored.sort((a, b) => b.score - a.score);
    return scored.slice(0, limit).map((s) => s.summary);
  }
  // ---- Python API ----------------------------------------------------------
  pythonIndex() {
    if (this.pyIndexCache) return this.pyIndexCache;
    if (this.source.kind === "empty") {
      this.pyIndexCache = [];
      return this.pyIndexCache;
    }
    const indexPath = join(this.source.pythonDir, "index.json");
    if (existsSync2(indexPath)) {
      const data = this.readJson(indexPath);
      if (Array.isArray(data)) {
        this.pyIndexCache = data;
        return this.pyIndexCache;
      }
    }
    const summaries = [];
    for (const file of this.listJsonFiles(this.source.pythonDir)) {
      const cls = this.readJson(join(this.source.pythonDir, file));
      if (cls?.className) summaries.push(toPythonSummary(cls));
    }
    this.pyIndexCache = summaries;
    return this.pyIndexCache;
  }
  pythonLookup() {
    if (this.pyLookupCache) return this.pyLookupCache;
    const map = /* @__PURE__ */ new Map();
    for (const summary of this.pythonIndex()) {
      map.set(compactKey(summary.className), summary.className);
      map.set(compactKey(summary.displayName), summary.className);
    }
    this.pyLookupCache = map;
    return map;
  }
  listPythonClasses() {
    return this.pythonIndex();
  }
  getPythonClass(name) {
    if (this.source.kind === "empty") return void 0;
    const className = this.pythonLookup().get(compactKey(name)) ?? name;
    const cached = this.pyDocCache.get(className);
    if (cached !== void 0) return cached ?? void 0;
    const file = join(this.source.pythonDir, `${className}.json`);
    const cls = existsSync2(file) ? this.readJson(file) : void 0;
    this.pyDocCache.set(className, cls ?? null);
    return cls;
  }
  // ---- Patterns ------------------------------------------------------------
  patterns() {
    if (this.patternsCache) return this.patternsCache;
    if (this.source.kind === "empty") {
      this.patternsCache = [];
      return this.patternsCache;
    }
    const data = this.readJson(this.source.patternsFile);
    if (Array.isArray(data) && data.length > 0 && typeof data[0].id === "string") {
      this.patternsCache = data;
    } else {
      this.patternsCache = normalizePatterns(data);
    }
    return this.patternsCache;
  }
  listPatterns() {
    return this.patterns().map((p) => ({
      id: p.id,
      name: p.name,
      category: p.category ?? "Unknown",
      description: p.description ?? ""
    }));
  }
  getPattern(name) {
    const key = compactKey(name);
    return this.patterns().find((p) => compactKey(p.id) === key || compactKey(p.name) === key);
  }
  // ---- GLSL ----------------------------------------------------------------
  glsl() {
    if (this.glslCache) return this.glslCache;
    if (this.source.kind === "empty") {
      this.glslCache = [];
      return this.glslCache;
    }
    const data = this.readJson(this.source.glslFile);
    this.glslCache = Array.isArray(data) ? data : normalizeGlsl(data);
    return this.glslCache;
  }
  listGlslPatterns() {
    return this.glsl().map((g) => ({
      id: g.id,
      name: g.name,
      description: g.description ?? "",
      difficulty: g.difficulty ?? "unknown"
    }));
  }
  getGlslPattern(name) {
    const key = compactKey(name);
    return this.glsl().find((g) => compactKey(g.id) === key || compactKey(g.name) === key);
  }
  // ---- Tutorials -----------------------------------------------------------
  tutorialIndex() {
    if (this.tutIndexCache) return this.tutIndexCache;
    if (this.source.kind === "empty") {
      this.tutIndexCache = [];
      return this.tutIndexCache;
    }
    const indexPath = join(this.source.tutorialsDir, "index.json");
    if (existsSync2(indexPath)) {
      const data = this.readJson(indexPath);
      if (Array.isArray(data)) {
        this.tutIndexCache = data;
        return this.tutIndexCache;
      }
    }
    const summaries = [];
    for (const file of this.listJsonFiles(this.source.tutorialsDir)) {
      const tut = this.readJson(join(this.source.tutorialsDir, file));
      if (tut?.id || tut?.name) {
        const normalized = {
          ...tut,
          id: tut.id ?? file.replace(/\.json$/, ""),
          name: tut.name ?? file
        };
        summaries.push(toTutorialSummary(normalized));
      }
    }
    this.tutIndexCache = summaries;
    return this.tutIndexCache;
  }
  tutorialLookup() {
    if (this.tutLookupCache) return this.tutLookupCache;
    const map = /* @__PURE__ */ new Map();
    for (const file of this.listJsonFiles(this.source.tutorialsDir)) {
      const slug = file.replace(/\.json$/, "");
      map.set(compactKey(slug), slug);
    }
    for (const summary of this.tutorialIndex()) {
      map.set(compactKey(summary.id), summary.id);
      map.set(compactKey(summary.name), summary.id);
    }
    this.tutLookupCache = map;
    return map;
  }
  listTutorials() {
    return this.tutorialIndex();
  }
  getTutorial(name) {
    if (this.source.kind === "empty") return void 0;
    const slug = this.tutorialLookup().get(compactKey(name)) ?? slugify(name);
    const cached = this.tutDocCache.get(slug);
    if (cached !== void 0) return cached ?? void 0;
    const file = join(this.source.tutorialsDir, `${slug}.json`);
    const tut = existsSync2(file) ? this.readJson(file) : void 0;
    this.tutDocCache.set(slug, tut ?? null);
    return tut;
  }
  // ---- Meta ----------------------------------------------------------------
  get sourceKind() {
    return this.source.kind;
  }
  stats() {
    return {
      source: this.source.kind,
      operators: this.operatorIndex().length,
      pythonClasses: this.pythonIndex().length,
      patterns: this.patterns().length,
      glsl: this.glsl().length,
      tutorials: this.tutorialIndex().length
    };
  }
};

// src/recipes/loader.ts
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
import { join as join2 } from "path";

// src/recipes/schema.ts
import { z } from "zod";
var RecipeNodeSchema = z.object({
  name: z.string().describe("Unique node name within the recipe (used for wiring)."),
  type: z.string().describe("Operator type, e.g. 'noiseTOP'."),
  parameters: z.record(z.string(), z.unknown()).default({}),
  parent: z.string().optional().describe(
    "Name of another recipe node (a COMP, e.g. a geometryCOMP) to nest this node inside of. The parent must appear earlier in `nodes`. Used to place SOPs inside a Geometry COMP."
  ),
  render: z.boolean().optional().describe(
    "For a SOP nested in a geometryCOMP: make this the rendered geometry. Sets the render/display flags on it and clears its siblings, so the COMP renders this instead of its default torus."
  ),
  comment: z.string().optional()
});
var RecipeConnectionSchema = z.object({
  from: z.string().describe("Source node name."),
  to: z.string().describe("Target node name."),
  from_output: z.number().int().nonnegative().default(0),
  to_input: z.number().int().nonnegative().default(0)
});
var RecipeParameterSchema = z.object({
  name: z.string().describe("Friendly name of the exposed control."),
  node: z.string().describe("Recipe node name the parameter belongs to."),
  param: z.string().describe("TD parameter name on that node."),
  value: z.unknown().optional(),
  label: z.string().optional(),
  min: z.number().optional(),
  max: z.number().optional(),
  description: z.string().optional()
});
var RecipeGlslUniformSchema = z.object({
  node: z.string().describe("Recipe node name of the GLSL TOP that declares the uniform."),
  name: z.string().describe("Uniform name as referenced in the shader, e.g. 'uFeed'."),
  kind: z.enum(["float", "vec", "color"]).default("float").describe(
    "Uniform kind: float (uniform float), vec (uniform vec2/3/4), color (rgba). float/vec use the Vectors page; color uses the Colors page."
  ),
  value: z.union([z.number(), z.array(z.number())]).optional().describe("Initial value: a number for float, or an array of components for vec/color."),
  label: z.string().optional(),
  min: z.number().optional(),
  max: z.number().optional(),
  description: z.string().optional()
});
var RecipeSchema = z.object({
  id: z.string(),
  name: z.string(),
  description: z.string().default(""),
  tags: z.array(z.string()).default([]),
  difficulty: z.enum(["beginner", "intermediate", "advanced"]).default("intermediate"),
  td_version_min: z.string().default("2023"),
  nodes: z.array(RecipeNodeSchema).min(1),
  connections: z.array(RecipeConnectionSchema).default([]),
  parameters: z.array(RecipeParameterSchema).default([]),
  glsl_uniforms: z.array(RecipeGlslUniformSchema).default([]),
  glsl_code: z.record(z.string(), z.string()).optional(),
  python_code: z.record(z.string(), z.string()).optional(),
  preview_description: z.string().default("")
});

// src/recipes/loader.ts
var RecipeLibrary = class {
  dir;
  logger;
  cache;
  constructor(options = {}) {
    this.dir = options.dir ?? recipesDir();
    this.logger = options.logger ?? silentLogger;
  }
  load() {
    if (this.cache) return this.cache;
    const recipes = [];
    if (existsSync3(this.dir)) {
      for (const file of readdirSync2(this.dir)) {
        if (!file.endsWith(".json")) continue;
        try {
          const raw = JSON.parse(readFileSync2(join2(this.dir, file), "utf8"));
          const parsed = RecipeSchema.safeParse(raw);
          if (parsed.success) {
            recipes.push(parsed.data);
          } else {
            this.logger.warn(`Invalid recipe ${file}`, { issues: parsed.error.issues.length });
          }
        } catch (err) {
          this.logger.warn(`Failed to read recipe ${file}`, { error: String(err) });
        }
      }
    }
    this.cache = recipes;
    return recipes;
  }
  all() {
    return this.load();
  }
  list() {
    return this.load().map((r2) => ({
      id: r2.id,
      name: r2.name,
      description: r2.description,
      tags: r2.tags,
      difficulty: r2.difficulty
    }));
  }
  get(id) {
    const key = compactKey(id);
    return this.load().find((r2) => compactKey(r2.id) === key || compactKey(r2.name) === key);
  }
  /** Finds the best recipe whose tags/name/description match any of the given terms. */
  findByTags(terms) {
    const wanted = terms.map((t) => t.toLowerCase()).filter(Boolean);
    if (wanted.length === 0) return void 0;
    let best;
    for (const recipe of this.load()) {
      const haystack = `${recipe.name} ${recipe.description} ${recipe.tags.join(" ")}`.toLowerCase();
      let score = 0;
      for (const term of wanted) {
        if (recipe.tags.some((tag) => tag.toLowerCase() === term)) score += 2;
        else if (haystack.includes(term)) score += 1;
      }
      if (score > 0 && (!best || score > best.score)) best = { recipe, score };
    }
    return best?.recipe;
  }
};

// src/td-client/types.ts
var TdError = class extends Error {
  code;
  constructor(message, code, options) {
    super(message, options);
    this.name = "TdError";
    this.code = code;
  }
};
var TdConnectionError = class extends TdError {
  constructor(message, options) {
    super(message, "TD_CONNECTION", options);
    this.name = "TdConnectionError";
  }
};
var TdTimeoutError = class extends TdError {
  constructor(message, options) {
    super(message, "TD_TIMEOUT", options);
    this.name = "TdTimeoutError";
  }
};
var TdApiError = class extends TdError {
  status;
  apiCode;
  constructor(message, options) {
    super(message, "TD_API", options);
    this.name = "TdApiError";
    this.status = options?.status;
    this.apiCode = options?.apiCode;
  }
};
function friendlyTdError(err) {
  if (err instanceof TdError) return err.message;
  if (err instanceof Error) return err.message;
  return String(err);
}

// src/td-client/validators.ts
import { z as z2 } from "zod";
var ApiEnvelopeSchema = z2.object({
  ok: z2.boolean(),
  data: z2.unknown().optional(),
  error: z2.object({ code: z2.string().optional(), message: z2.string() }).optional()
});
var NodeRefSchema = z2.object({
  path: z2.string(),
  type: z2.string().default(""),
  name: z2.string().default("")
});
var NodeDetailSchema = NodeRefSchema.extend({
  parameters: z2.record(z2.string(), z2.unknown()).default({}),
  inputs: z2.array(z2.string()).optional(),
  outputs: z2.array(z2.string()).optional(),
  family: z2.string().optional(),
  errors: z2.array(z2.string()).optional()
});
var NodeListSchema = z2.object({ nodes: z2.array(NodeRefSchema).default([]) });
var InfoSchema = z2.object({
  td_version: z2.string().optional(),
  python_version: z2.string().optional(),
  build: z2.string().optional(),
  bridge_version: z2.string().optional(),
  project: z2.string().optional()
});
var NodeErrorSchema = z2.object({
  path: z2.string(),
  message: z2.string(),
  type: z2.string().optional()
});
var NodeErrorsSchema = z2.object({ errors: z2.array(NodeErrorSchema).default([]) });
var PreviewSchema = z2.object({
  path: z2.string(),
  width: z2.number().int().positive(),
  height: z2.number().int().positive(),
  format: z2.string().default("png"),
  base64: z2.string()
});
var ExecResultSchema = z2.object({
  result: z2.unknown().optional(),
  stdout: z2.string().optional(),
  printed: z2.array(z2.string()).optional()
});
var MethodResultSchema = z2.object({ result: z2.unknown() });
var DeleteResultSchema = z2.object({ deleted: z2.string() });
var ConnectionSchema = z2.object({
  source_path: z2.string(),
  source_output: z2.number().int().default(0),
  target_path: z2.string(),
  target_input: z2.number().int().default(0)
});
var TopologySchema = z2.object({
  nodes: z2.array(NodeRefSchema).default([]),
  connections: z2.array(ConnectionSchema).default([])
});
var PerformanceSchema = z2.object({
  nodes: z2.array(
    z2.object({
      path: z2.string(),
      cook_time_ms: z2.number().default(0),
      cook_count: z2.number().optional()
    })
  ).default([]),
  total_cook_time_ms: z2.number().optional(),
  gpu_memory_mb: z2.number().optional()
});
var BatchOpResultSchema = z2.object({
  action: z2.string(),
  ok: z2.boolean(),
  path: z2.string().optional(),
  data: z2.unknown().optional(),
  error: z2.string().optional()
});
var BatchResultSchema = z2.object({ results: z2.array(BatchOpResultSchema).default([]) });
var CreateNodeInputSchema = z2.object({
  parent_path: z2.string(),
  type: z2.string(),
  name: z2.string().optional(),
  parameters: z2.record(z2.string(), z2.unknown()).optional()
});
var BatchOperationSchema = z2.discriminatedUnion("action", [
  z2.object({
    action: z2.literal("create"),
    parent_path: z2.string(),
    type: z2.string(),
    name: z2.string().optional(),
    parameters: z2.record(z2.string(), z2.unknown()).optional()
  }),
  z2.object({
    action: z2.literal("update"),
    path: z2.string(),
    parameters: z2.record(z2.string(), z2.unknown())
  }),
  z2.object({ action: z2.literal("delete"), path: z2.string() }),
  z2.object({
    action: z2.literal("connect"),
    source_path: z2.string(),
    target_path: z2.string(),
    source_output: z2.number().int().default(0),
    target_input: z2.number().int().default(0)
  })
]);

// src/td-client/touchDesignerClient.ts
function extractErrorMessage(json) {
  if (json && typeof json === "object") {
    const obj = json;
    const error = obj.error;
    if (error && typeof error === "object" && typeof error.message === "string") {
      return error.message;
    }
    if (typeof obj.message === "string") return obj.message;
  }
  return void 0;
}
function segment(path) {
  return encodeURIComponent(path);
}
var TouchDesignerClient = class {
  baseUrl;
  timeoutMs;
  logger;
  token;
  fetchImpl;
  constructor(options) {
    this.baseUrl = options.baseUrl.replace(/\/+$/, "");
    this.timeoutMs = options.timeoutMs ?? 1e4;
    this.logger = options.logger ?? silentLogger;
    this.token = options.token;
    this.fetchImpl = options.fetchImpl ?? fetch;
  }
  get endpoint() {
    return this.baseUrl;
  }
  async request(method, path, schema, body, query) {
    const url = new URL(`${this.baseUrl}${path}`);
    if (query) {
      for (const [key, value] of Object.entries(query)) {
        if (value !== void 0) url.searchParams.set(key, String(value));
      }
    }
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), this.timeoutMs);
    let response;
    try {
      this.logger.debug(`TD ${method} ${path}`);
      const headers = {};
      if (body !== void 0) headers["content-type"] = "application/json";
      if (this.token) headers.authorization = `Bearer ${this.token}`;
      response = await this.fetchImpl(url, {
        method,
        signal: controller.signal,
        headers: Object.keys(headers).length > 0 ? headers : void 0,
        body: body !== void 0 ? JSON.stringify(body) : void 0
      });
    } catch (err) {
      if (err instanceof Error && err.name === "AbortError") {
        throw new TdTimeoutError(
          `TouchDesigner request timed out after ${this.timeoutMs}ms (${method} ${path}).`,
          { cause: err }
        );
      }
      throw new TdConnectionError(
        `Cannot reach TouchDesigner at ${this.baseUrl}. Make sure TD is running with the tdmcp bridge (WebServer DAT) installed and listening on that port.`,
        { cause: err }
      );
    } finally {
      clearTimeout(timer);
    }
    const text = await response.text();
    let json;
    if (text) {
      try {
        json = JSON.parse(text);
      } catch {
        json = void 0;
      }
    }
    if (!response.ok) {
      const message = extractErrorMessage(json) ?? `TouchDesigner bridge returned HTTP ${response.status} for ${method} ${path}.`;
      throw new TdApiError(message, { status: response.status });
    }
    const envelope = ApiEnvelopeSchema.safeParse(json);
    if (!envelope.success) {
      throw new TdApiError(`Malformed response from TouchDesigner bridge for ${method} ${path}.`, {
        status: response.status
      });
    }
    if (!envelope.data.ok) {
      throw new TdApiError(
        envelope.data.error?.message ?? `TouchDesigner reported an error for ${method} ${path}.`,
        { status: response.status, apiCode: envelope.data.error?.code }
      );
    }
    const parsed = schema.safeParse(envelope.data.data);
    if (!parsed.success) {
      throw new TdApiError(
        `Unexpected data shape from TouchDesigner bridge for ${method} ${path}: ${parsed.error.message}`,
        { status: response.status }
      );
    }
    return parsed.data;
  }
  getInfo() {
    return this.request("GET", "/api/info", InfoSchema);
  }
  createNode(input) {
    return this.request("POST", "/api/nodes", NodeRefSchema, CreateNodeInputSchema.parse(input));
  }
  deleteNode(path) {
    return this.request("DELETE", `/api/nodes/${segment(path)}`, DeleteResultSchema);
  }
  getNodes(parentPath) {
    return this.request("GET", "/api/nodes", NodeListSchema, void 0, { parent: parentPath });
  }
  getNode(path) {
    return this.request("GET", `/api/nodes/${segment(path)}`, NodeDetailSchema);
  }
  updateNodeParameters(path, parameters) {
    return this.request("PATCH", `/api/nodes/${segment(path)}`, NodeDetailSchema, { parameters });
  }
  executePythonScript(script, returnOutput = true) {
    return this.request("POST", "/api/exec", ExecResultSchema, {
      script,
      return_output: returnOutput
    });
  }
  execNodeMethod(path, method, args = [], kwargs = {}) {
    return this.request("POST", `/api/nodes/${segment(path)}/method`, MethodResultSchema, {
      method,
      args,
      kwargs
    });
  }
  getNodeErrors(path) {
    return this.request("GET", `/api/nodes/${segment(path)}/errors`, NodeErrorsSchema);
  }
  getPreview(path, width = 640, height = 360) {
    return this.request("GET", `/api/preview/${segment(path)}`, PreviewSchema, void 0, {
      width,
      height
    });
  }
  batch(operations) {
    return this.request("POST", "/api/batch", BatchResultSchema, { operations });
  }
  getNetworkErrors(path) {
    return this.request("GET", `/api/network/${segment(path)}/errors`, NodeErrorsSchema);
  }
  getNetworkTopology(path, recursive = false) {
    return this.request(
      "GET",
      `/api/network/${segment(path)}/topology`,
      TopologySchema,
      void 0,
      recursive ? { recursive: true } : void 0
    );
  }
  getNetworkPerformance(path) {
    return this.request("GET", `/api/network/${segment(path)}/performance`, PerformanceSchema);
  }
};

// src/utils/config.ts
import { z as z3 } from "zod";
var ConfigSchema = z3.object({
  /** TouchDesigner bridge host. */
  tdHost: z3.string().min(1).default("127.0.0.1"),
  /** TouchDesigner bridge port (WebServer DAT). */
  tdPort: z3.coerce.number().int().positive().max(65535).default(9980),
  /** MCP transport: `stdio` (default, for local clients) or `http` (Streamable HTTP, loopback-only). */
  transport: z3.enum(["stdio", "http"]).default("stdio"),
  /** Log verbosity (written to stderr). */
  logLevel: z3.enum(["debug", "info", "warn", "error", "silent"]).default("info"),
  /** Per-request timeout against the TD bridge, in milliseconds. */
  requestTimeoutMs: z3.coerce.number().int().positive().default(1e4),
  /** HTTP transport port (only used when transport=http). */
  httpPort: z3.coerce.number().int().positive().max(65535).default(3939),
  /** Subscribe to TD WebSocket events and forward them as MCP logging notifications. */
  events: z3.enum(["on", "off"]).default("on"),
  /**
   * Raw Python escape-hatch tools (`execute_python_script`, `exec_node_method`).
   * Set to "off" to lock them out for restricted setups; on by default.
   */
  rawPython: z3.enum(["on", "off"]).default("on"),
  /**
   * Optional shared bearer token for the TD bridge. When set, the server sends it
   * as `Authorization: Bearer <token>` and the bridge requires a match. Leave unset
   * (default) for the zero-config local flow. Set the SAME value in TouchDesigner's
   * environment (`TDMCP_BRIDGE_TOKEN`) to turn enforcement on.
   */
  bridgeToken: z3.string().min(1).optional()
});
function loadConfig(env = process.env) {
  return ConfigSchema.parse({
    tdHost: env.TDMCP_TD_HOST,
    tdPort: env.TDMCP_TD_PORT,
    transport: env.TDMCP_TRANSPORT,
    logLevel: env.TDMCP_LOG_LEVEL,
    requestTimeoutMs: env.TDMCP_REQUEST_TIMEOUT_MS,
    httpPort: env.TDMCP_HTTP_PORT,
    events: env.TDMCP_EVENTS,
    rawPython: env.TDMCP_RAW_PYTHON,
    bridgeToken: env.TDMCP_BRIDGE_TOKEN || void 0
  });
}
function tdBaseUrl(config) {
  return `http://${config.tdHost}:${config.tdPort}`;
}

// src/server/connectionManager.ts
var ConnectionManager = class {
  client;
  constructor(config, logger) {
    this.client = new TouchDesignerClient({
      baseUrl: tdBaseUrl(config),
      timeoutMs: config.requestTimeoutMs,
      token: config.bridgeToken,
      logger
    });
  }
  async isReachable() {
    try {
      await this.client.getInfo();
      return true;
    } catch {
      return false;
    }
  }
};

// src/server/context.ts
function buildToolContext(config, overrides = {}) {
  const logger = overrides.logger ?? createLogger(config.logLevel);
  const connection = overrides.connection ?? new ConnectionManager(config, logger);
  const knowledge = overrides.knowledge ?? new KnowledgeBase({ logger });
  const recipes = overrides.recipes ?? new RecipeLibrary({ logger });
  return {
    client: connection.client,
    knowledge,
    recipes,
    logger,
    allowRawPython: config.rawPython !== "off"
  };
}

// src/tools/layer3/compareTdNodes.ts
import { z as z4 } from "zod";

// src/tools/result.ts
function textResult(text) {
  return { content: [{ type: "text", text }] };
}
function errorResult(message) {
  return { isError: true, content: [{ type: "text", text: message }] };
}
function jsonResult(summary, data) {
  const text = `${summary}

\`\`\`json
${JSON.stringify(data, null, 2)}
\`\`\``;
  return { content: [{ type: "text", text }] };
}
function structuredResult(summary, data) {
  return {
    content: [{ type: "text", text: summary }],
    structuredContent: data
  };
}
async function guardTd(fn, onOk) {
  try {
    return onOk(await fn());
  } catch (err) {
    return errorResult(friendlyTdError(err));
  }
}

// src/tools/layer3/compareTdNodes.ts
var compareTdNodesSchema = z4.object({
  path_a: z4.string().describe("First node path."),
  path_b: z4.string().describe("Second node path."),
  only_diff: z4.boolean().default(true).describe("Return only the parameters that differ (true) or also list the identical ones.")
});
var compareTdNodesOutputSchema = z4.object({
  a: z4.string(),
  b: z4.string(),
  type_a: z4.string(),
  type_b: z4.string(),
  type_match: z4.boolean(),
  differing_count: z4.number(),
  same_count: z4.number(),
  differing: z4.array(z4.object({ param: z4.string(), a: z4.unknown(), b: z4.unknown() })),
  identical: z4.array(z4.string()).optional()
});
async function compareTdNodesImpl(ctx, args) {
  return guardTd(
    async () => {
      const [a, b] = await Promise.all([
        ctx.client.getNode(args.path_a),
        ctx.client.getNode(args.path_b)
      ]);
      return { a, b };
    },
    ({ a, b }) => {
      const keys = /* @__PURE__ */ new Set([...Object.keys(a.parameters), ...Object.keys(b.parameters)]);
      const differing = [];
      const identical = [];
      for (const key of [...keys].sort()) {
        const va = a.parameters[key];
        const vb = b.parameters[key];
        if (JSON.stringify(va) === JSON.stringify(vb)) identical.push(key);
        else differing.push({ param: key, a: va, b: vb });
      }
      const data = {
        a: a.path,
        b: b.path,
        type_a: a.type,
        type_b: b.type,
        type_match: a.type === b.type,
        differing_count: differing.length,
        same_count: identical.length,
        differing
      };
      if (!args.only_diff) data.identical = identical;
      return structuredResult(
        `${differing.length} differing parameter(s) between ${a.path} and ${b.path}${a.type === b.type ? "" : ` (types differ: ${a.type} vs ${b.type})`}.`,
        data
      );
    }
  );
}

// src/tools/layer3/createTdNode.ts
import { z as z5 } from "zod";
var createTdNodeSchema = z5.object({
  parent_path: z5.string().default("/project1").describe("Parent COMP path to create the node inside."),
  type: z5.string().describe("Operator type string, e.g. 'noiseTOP', 'feedbackTOP', 'nullTOP', 'constantCHOP'."),
  name: z5.string().optional().describe("Optional node name (auto-generated if omitted)."),
  parameters: z5.record(z5.string(), z5.unknown()).optional().describe("Optional initial parameter overrides as key\u2192value pairs.")
});
async function createTdNodeImpl(ctx, args) {
  const warnings = [];
  if (!ctx.knowledge.operatorExists(args.type)) {
    const suggestions = ctx.knowledge.searchOperators(args.type, 3).map((s) => s.name);
    warnings.push(
      `Operator type "${args.type}" was not found in the knowledge base.${suggestions.length ? ` Did you mean: ${suggestions.join(", ")}?` : ""}`
    );
  }
  return guardTd(
    () => ctx.client.createNode({
      parent_path: args.parent_path,
      type: args.type,
      name: args.name,
      parameters: args.parameters
    }),
    (node) => jsonResult(`Created ${node.type || args.type} at ${node.path}.`, { node, warnings })
  );
}

// src/tools/layer3/deleteTdNode.ts
import { z as z6 } from "zod";
var deleteTdNodeSchema = z6.object({
  path: z6.string().describe("Full path of the node to delete, e.g. '/project1/noise1'.")
});
async function deleteTdNodeImpl(ctx, args) {
  return guardTd(
    () => ctx.client.deleteNode(args.path),
    (result) => jsonResult(`Deleted ${result.deleted}.`, result)
  );
}

// src/tools/layer3/execNodeMethod.ts
import { z as z7 } from "zod";
var execNodeMethodSchema = z7.object({
  path: z7.string().describe("Full path of the node to call the method on."),
  method: z7.string().describe("Method name to call, e.g. 'cook', 'par', 'destroy', 'copy'."),
  args: z7.array(z7.unknown()).default([]).describe("Positional arguments."),
  kwargs: z7.record(z7.string(), z7.unknown()).default({}).describe("Keyword arguments.")
});
async function execNodeMethodImpl(ctx, args) {
  return guardTd(
    () => ctx.client.execNodeMethod(args.path, args.method, args.args, args.kwargs),
    (result) => jsonResult(`Called ${args.path}.${args.method}().`, result)
  );
}

// src/tools/layer3/executePythonScript.ts
import { z as z8 } from "zod";
var executePythonScriptSchema = z8.object({
  script: z8.string().min(1).describe(
    "Python source to execute inside TouchDesigner (runs in the TD process, not locally)."
  ),
  return_output: z8.boolean().default(true).describe("Capture stdout / the value of the last expression and return it.")
});
async function executePythonScriptImpl(ctx, args) {
  return guardTd(
    () => ctx.client.executePythonScript(args.script, args.return_output),
    (result) => jsonResult("Python executed in TouchDesigner.", result)
  );
}

// src/tools/layer3/findTdNodes.ts
import { z as z9 } from "zod";

// src/tools/layer3/nodeMatch.ts
function globToRegExp(pattern) {
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
  return new RegExp(escaped, "i");
}
function parentOf(path) {
  const idx = path.lastIndexOf("/");
  return idx <= 0 ? "/" : path.slice(0, idx);
}

// src/tools/layer3/findTdNodes.ts
var findTdNodesSchema = z9.object({
  parent_path: z9.string().default("/project1").describe("Where to search from."),
  pattern: z9.string().optional().describe("Case-insensitive name/path filter with '*' wildcards (e.g. 'text*', '*noise*')."),
  type: z9.string().optional().describe("Case-insensitive operator-type substring (e.g. 'TOP', 'noise')."),
  recursive: z9.boolean().default(true).describe("Search the whole sub-network (true) or only direct children (false)."),
  path_only: z9.boolean().default(false).describe("Return only matching paths."),
  limit: z9.number().int().positive().default(50).describe("Max matches to return.")
});
var findTdNodesOutputSchema = z9.object({
  parent_path: z9.string(),
  recursive: z9.boolean(),
  count: z9.number(),
  truncated: z9.boolean(),
  paths: z9.array(z9.string()).optional(),
  matches: z9.array(NodeRefSchema).optional()
});
async function findTdNodesImpl(ctx, args) {
  const fetch2 = args.recursive ? async () => (await ctx.client.getNetworkTopology(args.parent_path, true)).nodes : async () => (await ctx.client.getNodes(args.parent_path)).nodes;
  return guardTd(fetch2, (allNodes) => {
    let nodes = allNodes;
    if (args.pattern) {
      const re = globToRegExp(args.pattern);
      nodes = nodes.filter((n) => re.test(n.name) || re.test(n.path));
    }
    if (args.type) {
      const t = args.type.toLowerCase();
      nodes = nodes.filter((n) => n.type.toLowerCase().includes(t));
    }
    const count = nodes.length;
    const truncated = count > args.limit;
    nodes = nodes.slice(0, args.limit);
    const summary = `${count} match(es) under ${args.parent_path}${truncated ? ` (showing ${args.limit})` : ""}.`;
    const base = { parent_path: args.parent_path, recursive: args.recursive, count, truncated };
    return args.path_only ? structuredResult(summary, { ...base, paths: nodes.map((n) => n.path) }) : structuredResult(summary, { ...base, matches: nodes });
  });
}

// src/tools/layer3/getModuleHelp.ts
import { z as z10 } from "zod";
var getModuleHelpSchema = z10.object({
  name: z10.string().describe("Class or module name to get help for, e.g. 'OP', 'App', 'Project'.")
});
function getModuleHelpImpl(ctx, args) {
  const cls = ctx.knowledge.getPythonClass(args.name);
  if (!cls) {
    const suggestions = ctx.knowledge.listPythonClasses().filter((c) => c.className.toLowerCase().includes(args.name.toLowerCase())).slice(0, 5).map((c) => c.className);
    return jsonResult(`No help found for "${args.name}".`, { found: false, suggestions });
  }
  const lines = [`# ${cls.displayName || cls.className}`, ""];
  if (cls.description) lines.push(cls.description, "");
  if (cls.members && cls.members.length > 0) {
    lines.push("## Members");
    for (const m of cls.members) {
      const ro = m.readOnly ? " (read-only)" : "";
      lines.push(`- ${m.name}${m.returnType ? `: ${m.returnType}` : ""}${ro}`);
    }
    lines.push("");
  }
  if (cls.methods && cls.methods.length > 0) {
    lines.push("## Methods");
    for (const method of cls.methods) {
      const sig = method.signature || method.name || "";
      lines.push(`- ${sig}${method.returns ? ` -> ${method.returns}` : ""}`);
    }
  }
  return textResult(lines.join("\n"));
}

// src/tools/layer3/getTdClassDetails.ts
import { z as z11 } from "zod";
var getTdClassDetailsSchema = z11.object({
  class_name: z11.string().describe("Python class name, e.g. 'OP', 'TOP', 'App', 'CHOP'.")
});
function getTdClassDetailsImpl(ctx, args) {
  const cls = ctx.knowledge.getPythonClass(args.class_name);
  if (!cls) {
    const suggestions = ctx.knowledge.listPythonClasses().filter((c) => c.className.toLowerCase().includes(args.class_name.toLowerCase())).slice(0, 5).map((c) => c.className);
    return structuredResult(`Python class "${args.class_name}" not found.`, {
      found: false,
      suggestions
    });
  }
  return structuredResult(
    `${cls.className} \u2014 ${cls.methods?.length ?? 0} methods, ${cls.members?.length ?? 0} members.`,
    cls
  );
}

// src/tools/layer3/getTdClasses.ts
import { z as z12 } from "zod";
var getTdClassesSchema = z12.object({
  filter: z12.string().optional().describe("Optional case-insensitive substring to filter class names by.")
});
function getTdClassesImpl(ctx, args) {
  let classes = ctx.knowledge.listPythonClasses();
  if (args.filter) {
    const needle = args.filter.toLowerCase();
    classes = classes.filter(
      (c) => c.className.toLowerCase().includes(needle) || c.displayName.toLowerCase().includes(needle)
    );
  }
  return structuredResult(`Found ${classes.length} TouchDesigner Python API class(es).`, {
    classes
  });
}

// src/tools/layer3/getTdInfo.ts
async function getTdInfoImpl(ctx) {
  const knowledge = ctx.knowledge.stats();
  try {
    const info = await ctx.client.getInfo();
    return jsonResult("TouchDesigner is connected.", {
      connected: true,
      endpoint: ctx.client.endpoint,
      touchdesigner: info,
      knowledge
    });
  } catch (err) {
    return jsonResult("TouchDesigner is not reachable (the server is still running).", {
      connected: false,
      endpoint: ctx.client.endpoint,
      reason: friendlyTdError(err),
      knowledge
    });
  }
}

// src/tools/layer3/getTdNodeErrors.ts
import { z as z13 } from "zod";
var getTdNodeErrorsSchema = z13.object({
  path: z13.string().describe("Full path of the node (or network root) to check for errors."),
  recursive: z13.boolean().default(false).describe("If true, check the whole network under `path`; otherwise just that node."),
  summary: z13.boolean().default(false).describe("Return only counts grouped by error type instead of the full error list.")
});
var getTdNodeErrorsOutputSchema = z13.object({
  path: z13.string(),
  total: z13.number(),
  errors: z13.array(NodeErrorSchema).optional(),
  by_type: z13.record(z13.string(), z13.number()).optional()
});
async function getTdNodeErrorsImpl(ctx, args) {
  return guardTd(
    () => args.recursive ? ctx.client.getNetworkErrors(args.path) : ctx.client.getNodeErrors(args.path),
    (result) => {
      const errors = result.errors;
      const total = errors.length;
      const none = total === 0;
      if (args.summary) {
        const byType = {};
        for (const e of errors) {
          const key = e.type || "error";
          byType[key] = (byType[key] ?? 0) + 1;
        }
        return structuredResult(
          none ? `No errors found at ${args.path}.` : `${total} error(s) at ${args.path}.`,
          { path: args.path, total, by_type: byType }
        );
      }
      return structuredResult(
        none ? `No errors found at ${args.path}.` : `Found ${total} error(s) at ${args.path}.`,
        { path: args.path, total, errors }
      );
    }
  );
}

// src/tools/layer3/getTdNodeParameters.ts
import { z as z14 } from "zod";
var getTdNodeParametersSchema = z14.object({
  path: z14.string().describe("Full path of the node to inspect."),
  keys: z14.array(z14.string()).optional().describe("Only return these parameter names (case-sensitive). Omit to return all parameters."),
  omit_io: z14.boolean().default(false).describe("Drop the inputs/outputs lists from the result to save context.")
});
async function getTdNodeParametersImpl(ctx, args) {
  return guardTd(
    () => ctx.client.getNode(args.path),
    (node) => {
      let parameters = node.parameters;
      if (args.keys && args.keys.length > 0) {
        const wanted = new Set(args.keys);
        parameters = Object.fromEntries(
          Object.entries(node.parameters).filter(([k]) => wanted.has(k))
        );
      }
      const data = {
        path: node.path,
        type: node.type,
        name: node.name,
        parameters
      };
      if (!args.omit_io) {
        data.inputs = node.inputs;
        data.outputs = node.outputs;
      }
      const count = Object.keys(parameters).length;
      return structuredResult(`${count} parameter(s) for ${node.path} (${node.type}).`, data);
    }
  );
}

// src/tools/layer3/getTdNodes.ts
import { z as z15 } from "zod";
var getTdNodesSchema = z15.object({
  parent_path: z15.string().default("/project1").describe("Parent COMP whose direct children should be listed."),
  pattern: z15.string().optional().describe(
    "Case-insensitive filter on node name/path. Supports '*' wildcards (e.g. 'text*', '*noise*')."
  ),
  path_only: z15.boolean().default(false).describe("Return only the list of node paths, dropping type/name."),
  limit: z15.number().int().positive().optional().describe("Cap the number of nodes returned."),
  detail_level: z15.enum(["summary", "full"]).default("summary").describe(
    "'summary' (default) returns a count, a type breakdown and the first few paths; 'full' returns every node. Use 'full' (or path_only) when you need the complete list."
  )
});
var SAMPLE_SIZE = 10;
var getTdNodesOutputSchema = z15.object({
  parent_path: z15.string(),
  count: z15.number(),
  detail_level: z15.enum(["summary", "full"]),
  truncated: z15.boolean(),
  by_type: z15.record(z15.string(), z15.number()).optional(),
  sample: z15.array(z15.string()).optional(),
  paths: z15.array(z15.string()).optional(),
  nodes: z15.array(NodeRefSchema).optional(),
  hint: z15.string().optional()
});
async function getTdNodesImpl(ctx, args) {
  return guardTd(
    () => ctx.client.getNodes(args.parent_path),
    (list) => {
      let nodes = list.nodes;
      if (args.pattern) {
        const re = globToRegExp(args.pattern);
        nodes = nodes.filter((n) => re.test(n.name) || re.test(n.path));
      }
      const matched = nodes.length;
      const truncated = args.limit !== void 0 && matched > args.limit;
      if (args.limit !== void 0) nodes = nodes.slice(0, args.limit);
      const where = args.pattern ? ` matching "${args.pattern}"` : "";
      const base = {
        parent_path: args.parent_path,
        count: matched,
        detail_level: args.detail_level,
        truncated
      };
      if (args.path_only) {
        return structuredResult(`${matched} node(s) under ${args.parent_path}${where}.`, {
          ...base,
          paths: nodes.map((n) => n.path)
        });
      }
      if (args.detail_level === "summary") {
        const byType = {};
        for (const n of nodes) {
          const key = n.type || "unknown";
          byType[key] = (byType[key] ?? 0) + 1;
        }
        const sample = nodes.slice(0, SAMPLE_SIZE).map((n) => n.path);
        return structuredResult(
          `${matched} node(s) under ${args.parent_path}${where}. Summary only \u2014 pass detail_level:"full" or path_only:true for the full list.`,
          {
            ...base,
            by_type: byType,
            sample,
            ...matched > sample.length ? {
              hint: `Showing first ${sample.length} of ${matched}. Use detail_level:"full" for all.`
            } : {}
          }
        );
      }
      return structuredResult(`${matched} node(s) under ${args.parent_path}${where}.`, {
        ...base,
        nodes
      });
    }
  );
}

// src/tools/layer3/getTdPerformance.ts
import { z as z16 } from "zod";

// src/feedback/performanceMonitor.ts
async function checkPerformance(client, path, targetFps = 60) {
  const perf = await client.getNetworkPerformance(path);
  const frameBudgetMs = 1e3 / targetFps;
  const warnings = [];
  for (const node of perf.nodes) {
    if (node.cook_time_ms > frameBudgetMs) {
      warnings.push(
        `${node.path} cooks in ${node.cook_time_ms.toFixed(2)}ms, exceeding the ${frameBudgetMs.toFixed(2)}ms budget at ${targetFps}fps.`
      );
    }
  }
  const totalCookMs = perf.total_cook_time_ms ?? perf.nodes.reduce((sum, node) => sum + node.cook_time_ms, 0);
  if (totalCookMs > frameBudgetMs) {
    warnings.push(
      `Total cook time ${totalCookMs.toFixed(2)}ms exceeds the ${frameBudgetMs.toFixed(2)}ms budget at ${targetFps}fps.`
    );
  }
  return { path, targetFps, frameBudgetMs, totalCookMs, nodes: perf.nodes, warnings };
}

// src/tools/layer3/getTdPerformance.ts
var getTdPerformanceSchema = z16.object({
  root_path: z16.string().default("/project1").describe("Network root to measure cook times under."),
  target_fps: z16.number().positive().default(60).describe("Frame-rate target used to flag slow nodes.")
});
var getTdPerformanceOutputSchema = z16.object({
  path: z16.string(),
  targetFps: z16.number(),
  frameBudgetMs: z16.number(),
  totalCookMs: z16.number(),
  nodes: z16.array(
    z16.object({
      path: z16.string(),
      cook_time_ms: z16.number(),
      cook_count: z16.number().optional()
    })
  ),
  warnings: z16.array(z16.string())
});
async function getTdPerformanceImpl(ctx, args) {
  return guardTd(
    () => checkPerformance(ctx.client, args.root_path, args.target_fps),
    (report) => structuredResult(
      report.warnings.length === 0 ? `Within budget: ${report.totalCookMs.toFixed(2)}ms total under ${args.root_path} (${args.target_fps}fps).` : `${report.warnings.length} performance warning(s) under ${args.root_path}.`,
      report
    )
  );
}

// src/tools/layer3/getTdTopology.ts
import { z as z17 } from "zod";

// src/feedback/networkVerifier.ts
async function verifyNetwork(client, path) {
  const topology = await client.getNetworkTopology(path);
  const issues = [];
  if (topology.nodes.length === 0) {
    issues.push(`No nodes were found under ${path}.`);
  } else if (topology.nodes.length > 1 && topology.connections.length === 0) {
    issues.push("Multiple nodes exist but none are connected.");
  }
  return {
    path,
    nodeCount: topology.nodes.length,
    connectionCount: topology.connections.length,
    issues,
    topology
  };
}

// src/tools/layer3/getTdTopology.ts
var getTdTopologySchema = z17.object({
  root_path: z17.string().default("/project1").describe("Network root to map.")
});
var getTdTopologyOutputSchema = z17.object({
  path: z17.string(),
  nodeCount: z17.number(),
  connectionCount: z17.number(),
  issues: z17.array(z17.string()),
  topology: TopologySchema
});
async function getTdTopologyImpl(ctx, args) {
  return guardTd(
    () => verifyNetwork(ctx.client, args.root_path),
    (report) => structuredResult(
      `${report.nodeCount} node(s), ${report.connectionCount} connection(s) under ${args.root_path}.`,
      report
    )
  );
}

// src/tools/layer3/snapshotTdGraph.ts
import { z as z18 } from "zod";
var MAX_PARAM_NODES = 60;
var snapshotTdGraphSchema = z18.object({
  path: z18.string().default("/project1").describe("Network root to snapshot."),
  include_params: z18.boolean().default(false).describe("Also fetch each node's parameters (one request per node; capped for large graphs).")
});
var snapshotTdGraphOutputSchema = z18.object({
  path: z18.string(),
  nodeCount: z18.number(),
  connectionCount: z18.number(),
  issues: z18.array(z18.string()),
  params_truncated: z18.boolean(),
  nodes: z18.array(
    z18.object({
      path: z18.string(),
      type: z18.string(),
      name: z18.string(),
      parameters: z18.record(z18.string(), z18.unknown()).optional()
    })
  ),
  connections: z18.array(ConnectionSchema)
});
async function snapshotTdGraphImpl(ctx, args) {
  return guardTd(
    async () => {
      const report = await verifyNetwork(ctx.client, args.path);
      const refs = report.topology.nodes;
      let paramsTruncated = false;
      const params = /* @__PURE__ */ new Map();
      if (args.include_params) {
        const targets = refs.slice(0, MAX_PARAM_NODES);
        paramsTruncated = refs.length > MAX_PARAM_NODES;
        const details = await Promise.allSettled(targets.map((n) => ctx.client.getNode(n.path)));
        for (const detail of details) {
          if (detail.status === "fulfilled") params.set(detail.value.path, detail.value.parameters);
        }
      }
      const nodes = refs.map((n) => ({
        path: n.path,
        type: n.type,
        name: n.name,
        ...args.include_params ? { parameters: params.get(n.path) ?? {} } : {}
      }));
      return { report, nodes, paramsTruncated };
    },
    ({ report, nodes, paramsTruncated }) => structuredResult(
      `Snapshot of ${args.path}: ${report.nodeCount} node(s), ${report.connectionCount} connection(s)${report.issues.length ? `, ${report.issues.length} issue(s)` : ""}.`,
      {
        path: report.path,
        nodeCount: report.nodeCount,
        connectionCount: report.connectionCount,
        issues: report.issues,
        params_truncated: paramsTruncated,
        nodes,
        connections: report.topology.connections
      }
    )
  );
}

// src/tools/layer3/summarizeTdErrors.ts
import { z as z19 } from "zod";
var summarizeTdErrorsSchema = z19.object({
  path: z19.string().default("/project1").describe("Network root to collect errors under."),
  group_by: z19.enum(["message", "type", "parent"]).default("message").describe(
    "How to cluster errors: by exact message, by error type, or by parent container (to find a common upstream cause)."
  )
});
var summarizeTdErrorsOutputSchema = z19.object({
  path: z19.string(),
  total: z19.number(),
  group_by: z19.enum(["message", "type", "parent"]),
  groups: z19.array(
    z19.object({
      key: z19.string(),
      count: z19.number(),
      sample: z19.object({ path: z19.string(), message: z19.string() })
    })
  ),
  suggestions: z19.array(z19.string())
});
async function summarizeTdErrorsImpl(ctx, args) {
  return guardTd(
    () => ctx.client.getNetworkErrors(args.path),
    (result) => {
      const errors = result.errors;
      const total = errors.length;
      if (total === 0) {
        return structuredResult(`No errors found under ${args.path}.`, {
          path: args.path,
          total: 0,
          group_by: args.group_by,
          groups: [],
          suggestions: []
        });
      }
      const keyOf = (e) => args.group_by === "message" ? e.message : args.group_by === "type" ? e.type || "error" : parentOf(e.path);
      const grouped = /* @__PURE__ */ new Map();
      const byPath = /* @__PURE__ */ new Map();
      for (const e of errors) {
        const key = keyOf(e);
        const g = grouped.get(key);
        if (g) g.count += 1;
        else grouped.set(key, { count: 1, sample: { path: e.path, message: e.message } });
        byPath.set(e.path, (byPath.get(e.path) ?? 0) + 1);
      }
      const groups = [...grouped.entries()].map(([key, g]) => ({ key, count: g.count, sample: g.sample })).sort((a, b) => b.count - a.count);
      const worstNodes = [...byPath.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([p, c]) => `${p} (${c} error${c === 1 ? "" : "s"})`);
      const suggestions = [];
      if (groups[0] && groups[0].count > 1) {
        suggestions.push(
          `${groups[0].count} errors share ${args.group_by} "${groups[0].key}" \u2014 fixing the common cause clears them at once.`
        );
      }
      if (worstNodes.length > 0) {
        suggestions.push(`Check first: ${worstNodes.join(", ")}.`);
      }
      return structuredResult(
        `${total} error(s) under ${args.path} in ${groups.length} ${args.group_by} group(s).`,
        { path: args.path, total, group_by: args.group_by, groups, suggestions }
      );
    }
  );
}

// src/tools/layer3/updateTdNodeParameters.ts
import { z as z20 } from "zod";
var updateTdNodeParametersSchema = z20.object({
  path: z20.string().describe("Full path of the node whose parameters to update."),
  parameters: z20.record(z20.string(), z20.unknown()).describe("Parameter overrides as key\u2192value pairs, e.g. { period: 4, amplitude: 0.5 }.")
});
async function updateTdNodeParametersImpl(ctx, args) {
  return guardTd(
    () => ctx.client.updateNodeParameters(args.path, args.parameters),
    (node) => jsonResult(`Updated ${Object.keys(args.parameters).length} parameter(s) on ${node.path}.`, {
      node
    })
  );
}

// src/cli/agent.ts
var r = (schema, run, summary, opts = {}) => ({ schema, run, summary, mutates: !!opts.mutates, unsafe: !!opts.unsafe });
var COMMANDS = {
  info: r(z21.object({}), (ctx) => getTdInfoImpl(ctx), "Health check + TD/bridge info."),
  "nodes list": r(
    getTdNodesSchema,
    getTdNodesImpl,
    "List a COMP's child nodes (summary by default)."
  ),
  "nodes find": r(findTdNodesSchema, findTdNodesImpl, "Search nodes by name pattern and/or type."),
  "nodes get": r(getTdNodeParametersSchema, getTdNodeParametersImpl, "Read a node's parameters."),
  "nodes errors": r(getTdNodeErrorsSchema, getTdNodeErrorsImpl, "Check a node/network for errors."),
  "nodes compare": r(compareTdNodesSchema, compareTdNodesImpl, "Diff two nodes' parameters."),
  "nodes snapshot": r(snapshotTdGraphSchema, snapshotTdGraphImpl, "Capture a network snapshot."),
  "nodes topology": r(getTdTopologySchema, getTdTopologyImpl, "Map nodes + connections."),
  "nodes performance": r(getTdPerformanceSchema, getTdPerformanceImpl, "Report cook times."),
  "nodes update": r(
    updateTdNodeParametersSchema,
    updateTdNodeParametersImpl,
    "Set node parameters.",
    { mutates: true }
  ),
  "nodes create": r(createTdNodeSchema, createTdNodeImpl, "Create an operator.", { mutates: true }),
  "nodes delete": r(deleteTdNodeSchema, deleteTdNodeImpl, "Delete a node.", { mutates: true }),
  "errors summarize": r(
    summarizeTdErrorsSchema,
    summarizeTdErrorsImpl,
    "Cluster network errors by cause."
  ),
  "classes list": r(getTdClassesSchema, getTdClassesImpl, "List TD Python API classes (offline)."),
  "classes get": r(
    getTdClassDetailsSchema,
    getTdClassDetailsImpl,
    "Get one Python class (offline)."
  ),
  "module help": r(
    getModuleHelpSchema,
    getModuleHelpImpl,
    "Human-readable help for a class (offline)."
  ),
  "exec python": r(
    executePythonScriptSchema,
    executePythonScriptImpl,
    "Escape hatch: run arbitrary Python in TD.",
    { mutates: true, unsafe: true }
  ),
  "exec node-method": r(
    execNodeMethodSchema,
    execNodeMethodImpl,
    "Escape hatch: call a Python method on a node.",
    { mutates: true, unsafe: true }
  )
};
function textOf(result) {
  return result.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
}
function extractData(result) {
  if (result.structuredContent !== void 0) return result.structuredContent;
  const text = textOf(result);
  const fence = text.match(/```json\n([\s\S]*?)\n```/);
  if (fence) {
    try {
      return JSON.parse(fence[1]);
    } catch {
    }
  }
  return { message: text };
}
function firstArray(data) {
  if (Array.isArray(data)) return data;
  if (data && typeof data === "object") {
    for (const value of Object.values(data)) if (Array.isArray(value)) return value;
  }
  return null;
}
function resolveCommand(positionals) {
  const key2 = positionals.slice(0, 2).join(" ");
  if (COMMANDS[key2]) return { key: key2, cmd: COMMANDS[key2] };
  const key1 = positionals[0] ?? "";
  if (COMMANDS[key1]) return { key: key1, cmd: COMMANDS[key1] };
  return void 0;
}
function usage() {
  const lines = ["tdmcp-agent \u2014 drive TouchDesigner from a shell (machine-readable output).", ""];
  lines.push("Usage: tdmcp-agent <command> [--params '<json>'] [--json '<json>'] [flags]", "");
  lines.push("Flags:");
  lines.push(
    "  --params <json>   Arguments object (validated against the command's input schema)."
  );
  lines.push("  --json <json>     Merged into --params (e.g. for request bodies).");
  lines.push("  --output <fmt>    json (default) | ndjson | text.");
  lines.push("  --dry-run         Validate and print the intended call without executing.");
  lines.push("  --allow-unsafe    Required for `exec` escape-hatch commands.");
  lines.push("  -h, --help        Show this help.", "");
  lines.push("Commands:");
  for (const [key, cmd] of Object.entries(COMMANDS)) {
    const tags = [cmd.mutates ? "mutates" : "", cmd.unsafe ? "unsafe" : ""].filter(Boolean).join(",");
    lines.push(`  ${key.padEnd(20)} ${cmd.summary}${tags ? `  [${tags}]` : ""}`);
  }
  lines.push("  schema <command>     Print a command's JSON Schema and metadata.");
  return lines.join("\n");
}
function parseCliArgs(argv) {
  return parseArgs({
    args: argv,
    allowPositionals: true,
    options: {
      params: { type: "string" },
      json: { type: "string" },
      output: { type: "string", default: "json" },
      "dry-run": { type: "boolean", default: false },
      "allow-unsafe": { type: "boolean", default: false },
      help: { type: "boolean", short: "h", default: false }
    }
  });
}
async function runCli(argv, opts = {}) {
  let parsed;
  try {
    parsed = parseCliArgs(argv);
  } catch (err) {
    return { stdout: "", stderr: `${err.message}
`, code: 2 };
  }
  const { values, positionals } = parsed;
  if (values.help || positionals.length === 0) {
    return { stdout: `${usage()}
`, stderr: "", code: 0 };
  }
  if (positionals[0] === "schema") {
    const target = positionals.slice(1).join(" ");
    const cmd2 = COMMANDS[target];
    if (!cmd2) return { stdout: "", stderr: `Unknown command for schema: "${target}".
`, code: 2 };
    const doc = {
      command: target,
      summary: cmd2.summary,
      mutates: cmd2.mutates,
      unsafe: cmd2.unsafe,
      input: z21.toJSONSchema(cmd2.schema)
    };
    return { stdout: `${JSON.stringify(doc, null, 2)}
`, stderr: "", code: 0 };
  }
  const resolved = resolveCommand(positionals);
  if (!resolved) {
    return {
      stdout: "",
      stderr: `Unknown command: "${positionals.join(" ")}". Run with --help.
`,
      code: 2
    };
  }
  const { key, cmd } = resolved;
  const raw = {};
  try {
    if (typeof values.params === "string") Object.assign(raw, JSON.parse(values.params));
    if (typeof values.json === "string") Object.assign(raw, JSON.parse(values.json));
  } catch (err) {
    return {
      stdout: "",
      stderr: `Invalid JSON in --params/--json: ${err.message}
`,
      code: 2
    };
  }
  const args = cmd.schema.safeParse(raw);
  if (!args.success) {
    return {
      stdout: "",
      stderr: `Invalid arguments for "${key}": ${args.error.message}
`,
      code: 2
    };
  }
  if (values["dry-run"]) {
    const doc = {
      dryRun: true,
      command: key,
      mutates: cmd.mutates,
      unsafe: cmd.unsafe,
      args: args.data
    };
    return { stdout: `${JSON.stringify(doc, null, 2)}
`, stderr: "", code: 0 };
  }
  const ctx = opts.makeCtx ? opts.makeCtx() : buildToolContext(loadConfig(), { logger: silentLogger });
  if (cmd.unsafe) {
    if (ctx.allowRawPython === false) {
      return { stdout: "", stderr: `"${key}" is disabled (TDMCP_RAW_PYTHON=off).
`, code: 2 };
    }
    if (!values["allow-unsafe"]) {
      return {
        stdout: "",
        stderr: `"${key}" is an escape hatch. Re-run with --allow-unsafe to execute.
`,
        code: 2
      };
    }
  }
  const result = await cmd.run(ctx, args.data);
  const summary = textOf(result).split("\n")[0] ?? "";
  if (result.isError) return { stdout: "", stderr: `${textOf(result)}
`, code: 1 };
  const output = String(values.output);
  const data = extractData(result);
  if (output === "text") return { stdout: `${textOf(result)}
`, stderr: "", code: 0 };
  if (output === "ndjson") {
    const arr = firstArray(data);
    const body = arr ? arr.map((item) => JSON.stringify(item)).join("\n") : JSON.stringify(data);
    return { stdout: `${body}
`, stderr: summary ? `${summary}
` : "", code: 0 };
  }
  return {
    stdout: `${JSON.stringify(data, null, 2)}
`,
    stderr: summary ? `${summary}
` : "",
    code: 0
  };
}
async function main() {
  const result = await runCli(process.argv.slice(2));
  if (result.stdout) process.stdout.write(result.stdout);
  if (result.stderr) process.stderr.write(result.stderr);
  process.exitCode = result.code;
}
var invokedDirectly = process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href;
if (invokedDirectly) void main();
export {
  runCli
};
//# sourceMappingURL=agent.js.map