/**
 * Curated, human-facing examples for the generated Tool Reference.
 *
 * WHY THIS FILE EXISTS. Every page under docs/tools/ is generated from the live
 * zod schemas by scripts/gen-tool-docs.ts, and for a long time every single one
 * of them ended in "Example coming soon — the call below is a generated
 * skeleton" followed by `{"filename": "<filename>"}`. That skeleton is derived
 * mechanically from the required fields, so it is never WRONG — it is just never
 * useful either. A reader learns the parameter exists, which the parameter table
 * already told them, and nothing about what a real value looks like.
 *
 * Hand-editing the .mdx is not an option: the next `npm run docs:gen` overwrites
 * it, and CI runs docs:gen and fails if the tree changes. So the examples live
 * HERE, as data, and the generator renders them.
 *
 * THE RULE FOR ADDING ONE: every `args` object is validated against the tool's
 * real zod schema at generation time (see validateExamples in gen-tool-docs.ts).
 * A field that does not exist, or has the wrong type, fails the build rather
 * than shipping. Do not disable that check to make an example fit — if the
 * example does not typecheck, the example is wrong. A confidently wrong example
 * is worse than the skeleton it replaced, because a reader will copy it.
 *
 * WHAT MAKES A GOOD ENTRY:
 *  - `ask` is what a PERSON says out loud, not a restatement of the tool name.
 *    Nobody says "call the library listing"; they say "what have I got saved?".
 *  - `args` uses plausible real values — a real filename, a real prompt, a real
 *    HuggingFace URL — not `<placeholder>` echoes of the field name.
 *  - `returns` says what lands back in the conversation, in a sentence.
 *  - `caution` is REQUIRED wherever the call deletes, overwrites, costs money,
 *    or interrupts work someone else may be waiting on.
 *
 * WHAT THIS FILE MUST NOT DO: rewrite the tool DESCRIPTIONS. Those are tuned for
 * model dispatch and a readability edit there is a regression in tool-choice
 * accuracy, not an improvement (#557/#654). When a description reads badly to a
 * human, add a `gloss` here — it renders ALONGSIDE the description, leaving the
 * model-facing text untouched.
 *
 * COVERAGE IS DELIBERATELY PARTIAL. The beginner path is covered end to end;
 * expert surfaces (training, comfy-cli's 26 actions) are left on the skeleton
 * until someone writes examples worth reading. A missing example is an honest
 * gap; a made-up one is a bug. RunPod left that list in 0.50.0, and node
 * authoring left it in the same release — see the notes above their entries for
 * why a skeleton stopped being adequate for a tool whose only required field is
 * `action`.
 */

export interface ToolDocExample {
  /** What a person actually says to the agent, in plain English. */
  ask: string;
  /**
   * The call the agent then makes. Validated against the tool's real zod schema
   * at generation time — an unknown or mistyped field fails `npm run docs:gen`.
   */
  args: Record<string, unknown>;
  /** What comes back, in a sentence or two. */
  returns: string;
  /**
   * Rendered under the JSON when the arguments are shown in shortened form —
   * used for the graph-shaped parameters, where pasting a complete 40-node
   * workflow would bury the point. Say plainly what was left out.
   */
  argsNote?: string;
  /** Rendered as a warning. Required when the call destroys or costs something. */
  caution?: string;
}

export interface ToolDocEntry {
  /**
   * A plain-language gloss shown ALONGSIDE (never instead of) the model-facing
   * description. For tools whose description is written in terms a human reader
   * has no reason to care about — context budgets, dispatch disambiguation.
   */
  gloss?: string;
  examples: ToolDocExample[];
}

/**
 * An abbreviated but structurally REAL API-format graph, for the tools that take
 * a whole workflow. Two genuine nodes rather than a fake one-liner: a reader
 * should come away knowing that API format is an object keyed by node id, each
 * value having `class_type` and `inputs`, and that a link is `[nodeId, slot]`.
 * Every entry that uses it also carries an `argsNote` saying it is shortened.
 */
const WORKFLOW_FRAGMENT = {
  "4": {
    class_type: "CheckpointLoaderSimple",
    inputs: { ckpt_name: "sd_xl_base_1.0.safetensors" },
  },
  "9": {
    class_type: "SaveImage",
    inputs: { filename_prefix: "portrait", images: ["8", 0] },
  },
} as const;

const FRAGMENT_NOTE =
  "Shortened for readability — two nodes of a real API-format graph are shown. " +
  "In practice the agent passes the whole thing: the graph it just loaded with " +
  "get_workflow, or the one it built for you.";

export const TOOL_DOC_EXAMPLES: Readonly<Record<string, ToolDocEntry>> = {
  kitchen: {
    gloss:
      "comfy-kitchen is ComfyUI's kernel library (fp8 / NVFP4 / MXFP8 / INT8 " +
      "attention). This tool reports what the connected GPU can run, walks a " +
      "graph for the faster path, and applies a recommendation. A failed probe " +
      "is unknown, never a no — a recommendation fires only when every fact it " +
      "needs is known. For the open canvas use panel_kitchen.",
    examples: [
      {
        ask: "Is kitchen even installed on this GPU, and can it do fp8?",
        args: { action: "status" },
        returns:
          "Kitchen version (or unknown), which backends loaded, whether INT8 " +
          "attention is on, GPU fp8/NVFP4/MXFP8, and the launch flags in argv. " +
          "Remote sessions skip the import probe and model.quant.",
      },
      {
        ask: "Where is this workflow leaving kitchen speed on the table?",
        args: {
          action: "assess",
          workflow: {
            "12": {
              class_type: "UNETLoader",
              inputs: { unet_name: "flux1-dev.safetensors", weight_dtype: "default" },
            },
          },
        },
        returns:
          "Per-loader facts plus recommendations that fired. A default " +
          "UNETLoader on a bf16 file, with kitchen present and an fp8 GPU, " +
          "yields fp8_e4m3fn_fast. Missing facts stay unknown; they do not " +
          "invent a rec.",
      },
      {
        ask: "Apply that fp8 widget change.",
        args: { action: "apply", recommendation_id: "fp8_unet_fast:12" },
        returns:
          "The widget edit on the supplied workflow, or a needs_confirm " +
          "refusal for a flag that would restart. Flag apply names the launch " +
          "flag; restart_comfyui replays argv and does not inject it.",
        caution:
          "A flag or download recommendation needs confirm:true. Proof of a " +
          "faster non-black after-run is panel_kitchen apply.",
      },
    ],
  },
  // -------------------------------------------------------------------------
  // Image & Audio Generation
  // -------------------------------------------------------------------------
  generate_image: {
    gloss:
      "The one-line way to get a picture — and, through `action`, the same " +
      "one-line way to get audio, a video clip, a 3D model, a re-run of an " +
      "earlier image, an upscale, or a background removed. You do not need a " +
      "workflow open, or any workflow at all: describe what you want and this " +
      "builds and runs a sensible graph for you. Everything except `action` " +
      "and the one or two fields that action needs is optional and falls back " +
      "to your saved defaults.",
    examples: [
      {
        ask: "Make me a picture of a red fox in the snow.",
        args: { action: "image", prompt: "a red fox in deep snow, golden hour, sharp focus" },
        returns:
          "The finished image, inline in the conversation, plus the seed and " +
          "settings that produced it so you can ask for the same thing again.",
      },
      {
        ask: "Same fox, but widescreen, more detail, and keep it repeatable.",
        args: {
          action: "image",
          prompt: "a red fox in deep snow, golden hour, sharp focus",
          negative_prompt: "blurry, watermark, text",
          width: 1344,
          height: 768,
          steps: 30,
          cfg: 4.5,
          seed: 12345,
        },
        returns:
          "The same fields plus the image. Because `seed` was pinned, running " +
          "this again with the same settings gives the same picture — that is " +
          "how you iterate on one image instead of rolling a new one each time.",
      },
      {
        ask: "Turn that fox picture into a short clip of it walking.",
        args: {
          action: "video",
          prompt: "a red fox walking through deep snow, camera slowly pushing in",
          seconds: 5,
          resolution: "832x480",
          fps: 16,
        },
        returns:
          "A path to the rendered video file plus the settings used. Video takes " +
          "far longer than a still — minutes, not seconds — so the agent will " +
          "usually tell you it has started and then report back.",
      },
      {
        ask: "That one was nearly right — run it again with more steps.",
        args: {
          action: "regenerate",
          asset_id: "a3f9c1",
          overrides: { steps: 40 },
        },
        returns:
          "A new render from the EXACT graph that produced that asset, with " +
          "only the fields you named changed. The seed is re-rolled unless you " +
          "pass one, so this gives you a fresh take rather than the same image.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Workflow Execution
  // -------------------------------------------------------------------------
  get_system_stats: {
    gloss:
      'Three read-only views of the connected ComfyUI: action:"stats" for what ' +
      'it is running on, action:"logs" for what it has said, and ' +
      'action:"health" for whether it is fit to dispatch work to. Nothing here ' +
      "changes anything — freeing VRAM is clear_vram, and filing a bug is " +
      "report_issue.",
    examples: [
      {
        ask: "How much VRAM have I got left?",
        args: { action: "stats" },
        returns:
          "The GPU, its total and free VRAM, system RAM, and the ComfyUI and " +
          "Python versions.",
      },
      {
        ask: "Is everything working?",
        args: { action: "health" },
        returns:
          "Whether the server is reachable, what it is running on, and whether " +
          "the model folders have anything in them.",
      },
      {
        ask: "Check the setup, and tell me if any recent runs blew up.",
        args: { action: "health", model_categories: ["checkpoints", "loras"], recent_errors: 5 },
        returns:
          "The same report, narrowed to the two model folders you asked about, " +
          "with the last five errors from history attached.",
      },
      {
        ask: "Show me the last errors from the server log.",
        args: { action: "logs", keyword: "error", max_lines: 50 },
        returns: "The last fifty log lines that mention 'error', ANSI codes stripped.",
      },
    ],
  },
  enqueue_workflow: {
    gloss:
      "Starts a render. `action` says where the graph comes from: one the " +
      "agent is holding in the conversation, an earlier run, a URL someone " +
      "shared, or a bundled template. If you want to run the graph you are " +
      "LOOKING at in ComfyUI, that is the panel's run tool instead, and if you " +
      "just want a picture, use generate_image.",
    examples: [
      {
        ask: "Run it.",
        args: { action: "enqueue", workflow: WORKFLOW_FRAGMENT },
        argsNote: FRAGMENT_NOTE,
        returns:
          "A prompt_id straight away, then the finished images once the render " +
          "completes.",
      },
      {
        ask: "What can I change about the anima text-to-image pack?",
        args: { action: "template_schema", template: "anima-txt2img" },
        returns:
          "The knobs that template exposes — prompt, size, steps, which model " +
          "file — with their current values, and the exact keys to pass as " +
          "`overrides` when you run it. Read-only.",
      },
      {
        ask: "Run the anima pack with my prompt and wait for it.",
        args: {
          action: "run_template",
          template: "anima-txt2img",
          overrides: { "45.text": "a lighthouse in a storm, dramatic sky" },
          wait: true,
        },
        argsNote:
          "Override keys are `\"<nodeId>.<widget_name>\"`, not bare parameter names — " +
          "a plain `\"prompt\"` is rejected. 45 is the positive CLIPTextEncode in THIS " +
          "pack; every template numbers its nodes differently, so read the keys off " +
          "the schema call above rather than copying this one.",
        returns:
          "With `wait: true`, the finished result in one go. Leave it off and " +
          "you get a prompt_id immediately and check back later.",
      },
      {
        ask: "Someone posted this workflow — what is in it?",
        args: {
          action: "run_url",
          url: "https://raw.githubusercontent.com/comfyanonymous/ComfyUI_examples/master/flux/flux_dev_example.json",
        },
        returns:
          "A summary of the graph and a validation report, WITHOUT running it. " +
          "Add `run: true` to enqueue it once you have read what it does.",
        caution:
          "`run: true` executes a graph you did not write on your own GPU. " +
          "Read the summary first — a shared workflow can pull in models and " +
          "custom nodes you do not have, and will fail late if so.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Workflow Library
  // -------------------------------------------------------------------------
  get_workflow: {
    gloss:
      "Everything you can READ about a saved workflow FILE, chosen with " +
      "`action` — the library listing, the raw JSON, a plain-English summary, a " +
      "targeted query. Never the graph currently open on your canvas. If you " +
      "only want to know what a workflow does, `action: \"analyze\"` is a far " +
      "shorter answer than the full JSON.",
    examples: [
      {
        ask: "What workflows have I got saved?",
        args: { action: "list" },
        returns:
          "A numbered list of names, each relative to the library root — one filed " +
          "in a folder shows as VIDEO/MiniMaxH3/clip.json. Pick one and hand the " +
          "whole name back as `filename`.",
      },
      {
        ask: "Open my portrait workflow so we can change the prompt.",
        args: { action: "get", filename: "portrait-flux.json" },
        returns:
          "The full workflow as runnable API-format JSON. This is a big " +
          "response — it is meant for the agent to work on, not for you to read.",
      },
      {
        ask: "Give me the raw file exactly as ComfyUI saved it.",
        args: { action: "get", filename: "portrait-flux.json", format: "ui" },
        returns:
          "The on-disk UI format, including node positions and colours — what " +
          "you want if the file is going to be loaded back into the canvas " +
          "rather than executed.",
      },
      {
        ask: "What does my portrait workflow actually do?",
        args: { action: "analyze", filename: "portrait-flux.json" },
        returns:
          "A short summary: the model it loads, the prompts, the sampler " +
          "settings, and what it saves.",
      },
      {
        ask: "Is that workflow going to run, or is something missing?",
        args: { action: "analyze", filename: "portrait-flux.json", view: "health" },
        returns:
          "Problems only — missing models, unconnected inputs, nodes whose " +
          "packs are not installed.",
      },
      {
        ask: "Which samplers in that graph are running above cfg 7?",
        args: {
          action: "query",
          filename: "portrait-flux.json",
          types: ["KSampler"],
          where: ["cfg>7"],
        },
        returns:
          "One line per matching node instead of the whole file — the only " +
          "context-safe way to ask a question of a 100-node graph.",
      },
      {
        ask: "How was this picture made?",
        args: {
          action: "from_image",
          image_path: "C:/ComfyUI/output/ComfyUI_00042_.png",
        },
        returns:
          "The workflow ComfyUI embedded in the PNG when it saved it, in both " +
          "API and UI form — the way to reverse-engineer someone else's image.",
      },
      {
        ask: "This graph is a mess of Get/Set nodes. What is actually wired to what?",
        args: { action: "strip", path: "C:/ComfyUI/user/default/workflows/expert.json" },
        returns:
          "The same graph with the virtual wiring resolved into real " +
          "connections, plus a note wherever the stripped form DIFFERS from the " +
          "source. Read those notes before running it.",
      },
    ],
  },
  save_workflow: {
    gloss:
      "The WRITE half of the library: store a workflow, or record/check the " +
      "exact models and node-pack commits it ran against so it can be " +
      "reproduced later.",
    examples: [
      {
        ask: "Save that as a new file so I don't lose the original.",
        args: {
          action: "save",
          filename: "portrait-flux-v2.json",
          workflow: WORKFLOW_FRAGMENT,
        },
        argsNote: FRAGMENT_NOTE,
        returns: "Confirmation and the path it was written to.",
        caution:
          "Writing to a filename that already exists replaces that file. Ask " +
          "for a new name — as above — when you want to keep the original.",
      },
      {
        ask: "Pin this one down so it renders the same in six months.",
        args: { action: "lock", filename: "portrait-flux-v2.json" },
        returns:
          "A lock file written alongside the workflow, recording a SHA-256 for " +
          "every model it loads and the git commit of every custom node pack it " +
          "uses. Needs a local install — hashing reads the model files.",
        caution:
          "This writes a second file (<filename>.lock.json) and hashes every " +
          "referenced model, which on a large install is minutes of disk work.",
      },
      {
        ask: "Has anything changed since I locked that workflow?",
        args: { action: "verify_lock", filename: "portrait-flux-v2.json" },
        returns:
          "A drift report: which models now hash differently, which node packs " +
          "moved to a new commit, whether ComfyUI itself was updated. Empty " +
          "everywhere means it will behave exactly as it did. Read-only.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Workflow Authoring
  // -------------------------------------------------------------------------
  create_workflow: {
    gloss:
      "Building a graph, and the schema lookup you make while building it — " +
      "start one from a template, patch an existing one, check it before you " +
      "queue it, or ask what a node's inputs are. Chosen with `action`.",
    examples: [
      {
        ask: "Give me a basic text-to-image workflow to start from.",
        args: {
          action: "create",
          template: "txt2img",
          params: { positive_prompt: "a snow leopard on a rooftop at dusk", steps: 25 },
        },
        returns:
          "A complete, runnable API-format workflow. Anything you left out uses " +
          "the template default, so check the checkpoint it picked actually " +
          "exists on your server before running it.",
      },
      {
        ask: "Turn the steps up to 40 on node 3.",
        args: {
          action: "modify",
          workflow: WORKFLOW_FRAGMENT,
          operations: [{ op: "set_input", node_id: "3", input_name: "steps", value: 40 }],
        },
        argsNote: FRAGMENT_NOTE,
        returns:
          "The modified workflow plus the ids of any nodes the operations added. " +
          "Nothing is written to disk and nothing is queued.",
      },
      {
        ask: "Will this run?",
        args: { action: "validate", workflow: WORKFLOW_FRAGMENT },
        argsNote: FRAGMENT_NOTE,
        returns:
          "Either a clean bill of health or the specific problems, without " +
          "queuing anything.",
      },
      {
        ask: "What settings does the KSampler node have?",
        args: { action: "node_info", node_type: "KSampler" },
        returns:
          "That node's inputs and outputs with their types. Dropdown options are " +
          "summarised as a count by default, because a model-loader dropdown can " +
          "be hundreds of kilobytes on its own.",
      },
    ],
  },
  visualize_workflow: {
    gloss:
      "Turn a workflow you PASS IN into something readable — a Mermaid diagram " +
      "or the compact DSL — and back again. It never looks at the graph open on " +
      "your canvas.",
    examples: [
      {
        ask: "Draw me a diagram of this workflow.",
        args: { action: "render", workflow: WORKFLOW_FRAGMENT },
        argsNote: FRAGMENT_NOTE,
        returns:
          "Mermaid flowchart syntax, nodes grouped by role and every connection " +
          "labelled with the data type flowing along it.",
      },
      {
        ask: "That chart is unreadable, it's a 60-node graph.",
        args: { action: "render_hierarchical", workflow: WORKFLOW_FRAGMENT, view: "overview" },
        argsNote: FRAGMENT_NOTE,
        returns:
          "The same graph collapsed to one box per section with the data flow " +
          "between them. `view: \"list\"` names the sections; `view: \"detail\"` " +
          "with a `section` opens one of them up.",
      },
      {
        ask: "Show me this workflow in a form I can actually edit by hand.",
        args: { action: "to_dsl", workflow: WORKFLOW_FRAGMENT },
        argsNote: FRAGMENT_NOTE,
        returns:
          "The compact DSL: one block per node, connections as " +
          "`key <- nodeId.outputIndex`. `action: \"from_dsl\"` converts it back " +
          "to runnable JSON when you are done editing.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Assets & Images
  // -------------------------------------------------------------------------
  // The three read-side image tools are ONE tool since 0.50.0 slice 15, chosen
  // with `action`. The skeleton the generator would otherwise emit is
  // `{"action": "get"}` — a call that returns the handler's missing-`filename`
  // error — so, as with runpod below, real calls are worth more than a skeleton
  // here. Every `args` object is validated against the live zod schema at
  // generation time.
  get_image: {
    gloss:
      "Everything the agent does to LOOK at what ComfyUI made: browse what has " +
      "been written to disk, fetch one file, put a picture in front of the " +
      "agent, re-encode it, or measure its colour. Worth knowing: video nodes " +
      "often finish without registering in ComfyUI's history, so " +
      "`action: \"list_outputs\"` is the reliable way to confirm a video really " +
      "rendered.",
    examples: [
      {
        ask: "Show me the last few things I generated.",
        args: { action: "list_outputs", limit: 10 },
        returns:
          "The ten newest files, newest first, each with whether it is an image " +
          "or a video, its folder, size and time. Not the pictures themselves — " +
          "ask to see one and the agent fetches it.",
      },
      {
        ask: "Did that fox render ever come out?",
        args: { action: "list_outputs", pattern: "fox", limit: 5 },
        returns: "Only files whose names contain \"fox\".",
      },
      {
        ask: "Show me that one.",
        args: { action: "view", asset_id: "asset_01HQ8Z3K7V" },
        returns:
          "The image inline, visible to both of you. The asset id comes from " +
          "the completion message of the run that made it. Images only — for " +
          "video and audio use `action: \"get\"`, which saves to disk.",
      },
      {
        ask: "Save that render onto my desktop.",
        args: {
          action: "get",
          filename: "portrait_00042_.png",
          type: "output",
          save_dir: "C:/Users/me/Desktop",
        },
        returns:
          "The file copied to that folder, and the path it landed at. Use this " +
          "rather than `action: \"view\"` for video and audio, which cannot be " +
          "shown inline.",
      },
      {
        ask: "Why does this render look so washed out?",
        args: { action: "analyze_color", filename: "portrait_00042_.png" },
        returns:
          "Measured numbers rather than an opinion: black and white points, " +
          "contrast, saturation, per-channel colour cast and clipping, plus " +
          "flags like washedOut/liftedBlacks and a one-line verdict. Read-only.",
      },
      {
        ask: "Make me a smaller JPEG of that so I can email it.",
        args: {
          action: "convert",
          path: "portrait_00042_.png",
          format: "jpeg",
          quality: 85,
        },
        returns:
          "The re-encoded image inline, with the source and output sizes and how " +
          "many bytes were saved.",
        caution:
          "Passing `out_path` also WRITES the converted file under the ComfyUI " +
          "output directory, overwriting whatever is already at that path.",
      },
      {
        ask: "What have I made recently?",
        args: { action: "list_assets", limit: 5 },
        returns:
          "The five newest registered assets with their asset ids, prompt ids, " +
          "filenames and when they were made — including renders this session " +
          "never watched, which are reconciled from ComfyUI's history on the " +
          "way. Feed an asset id to `action: \"view\"` to actually see one.",
      },
      {
        ask: "What settings produced that image?",
        args: { action: "asset_metadata", asset_id: "asset_01HQ8Z3K7V" },
        returns:
          "Full provenance for the asset, including the entire workflow that " +
          "produced it — the prompt, sampler, steps, seed and so on. Read this " +
          'before generate_image (action:"regenerate") with overrides.',
      },
    ],
  },

  upload_image: {
    gloss:
      "The other direction: putting a file where ComfyUI can read it. A local " +
      "file goes into ComfyUI's input/ folder (`image`/`video`/`audio`), an " +
      "existing render is re-registered as an input without touching the disk " +
      "(`stage` — the correct way to chain one stage into the next), and " +
      "`output` is the only action that sends anything OFF this machine.",
    examples: [
      {
        ask: "Use this photo as the starting image.",
        args: { action: "image", source_path: "C:/Users/me/Pictures/cat.png" },
        returns:
          "The filename it was stored under in ComfyUI's input/ directory — drop " +
          "that into a LoadImage node's `image` widget.",
      },
      {
        ask: "Now feed that render into the video stage.",
        args: { action: "stage", filename: "portrait_00042_.png" },
        returns:
          "The same file registered as an INPUT, with the filename to put in the " +
          "next stage's loader. Goes through the server API, so it works even " +
          "when ComfyUI was launched with custom input/output directories — " +
          "never guess a filesystem `input/` path.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Models
  // -------------------------------------------------------------------------
  download_model: {
    gloss:
      "Everything to do with GETTING a model: searching HuggingFace or CivitAI, " +
      "fetching the file, and watching or stopping the transfer. " +
      'The `action:"resolve_missing"` one is the answer to "this workflow says a ' +
      'model is missing" — it works out what is actually absent and finds ' +
      "downloadable candidates, including smaller quantised versions when the " +
      "full file will not fit your GPU.",
    examples: [
      {
        ask: "Find me a Flux model I can actually run.",
        args: { action: "search", query: "flux schnell", limit: 5 },
        returns:
          "Matching models with their download URLs and file sizes. Nothing is " +
          "downloaded — this is a search.",
      },
      {
        ask: "Get that one.",
        args: {
          action: "download",
          url: "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/flux1-schnell.safetensors",
          target_subfolder: "checkpoints",
        },
        returns:
          "Live progress in the panel's download tray, and the path the file " +
          "landed at. `target_subfolder` decides which ComfyUI dropdown it shows " +
          "up in — checkpoints, loras, vae and so on.",
        caution:
          "Model files are large — many are 5-25 GB. Check you have the disk " +
          "space before agreeing to a few of these.",
      },
      {
        ask: "Is that download still going?",
        args: { action: "status" },
        returns:
          "Every tracked download with its state and byte progress. Pass an " +
          "`id` to ask about just one.",
      },
      {
        ask: "This workflow won't run, it says something's missing. Find it for me.",
        args: { action: "resolve_missing", workflow: WORKFLOW_FRAGMENT },
        argsNote: FRAGMENT_NOTE,
        returns:
          "Each missing file with candidate downloads, their sizes and " +
          "precision, and whether each one fits your VRAM. It downloads nothing " +
          "— you pick, then it fetches.",
      },
    ],
  },
  list_local_models: {
    gloss:
      "Everything to do with what you ALREADY have: the installed model files, " +
      "the embeddings, and the extra folders ComfyUI searches. It also has the " +
      'one delete: `action:"remove"` unlinks a model file.',
    examples: [
      {
        ask: "Which checkpoints do I already have?",
        args: { action: "list", model_type: "checkpoints" },
        returns:
          "The model filenames in that folder — the exact strings a workflow " +
          "needs. Omit `model_type` to see every folder at once.",
      },
      {
        ask: "Delete that old LoRA, I'm out of disk space.",
        args: { action: "remove", path: "loras/old-character-v1.safetensors" },
        returns: "Confirmation, and how much space came back.",
        caution:
          "This deletes the file. There is no undo and no recycle bin — you " +
          "would have to download it again. Check the path is the one you mean. " +
          'Note that `action:"remove_path"` is a different thing entirely: it ' +
          "edits the search-path config and deletes nothing.",
      },
      {
        ask: "Also look for models on my E: drive.",
        args: { action: "add_path", category: "checkpoints", path: "E:/Models/checkpoints" },
        returns:
          "The updated extra-search-path config. ComfyUI needs a restart before " +
          "it picks the folder up.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Custom Nodes
  // -------------------------------------------------------------------------
  search_custom_nodes: {
    gloss:
      "Finding a node pack in the public registry, before you install anything. " +
      "Read-only and network-only — it does not need a running ComfyUI. " +
      "Installing what you find is a different tool: `install_custom_node`.",
    examples: [
      {
        ask: "Is there a node pack for face detailing?",
        args: { action: "search", query: "face detailer", limit: 5 },
        returns:
          "Matching packs from the registry with their ids, authors and " +
          "descriptions. The id is what install_custom_node wants.",
      },
      {
        ask: "Tell me more about that Impact Pack before I install it.",
        args: { action: "details", id: "comfyui-impact-pack" },
        returns:
          "The pack's description, author, license, repository, install count, " +
          "the node types it provides, and its recent version changelogs.",
      },
    ],
  },
  install_custom_node: {
    // Same reason RunPod stopped being adequate on the skeleton (see the header):
    // 0.50.0 slice 12 made `action` the only REQUIRED field, so the generated
    // skeleton collapses to {"action": "install"} and the documented call
    // deterministically hits the handler's missing-`id` error. For a surface that
    // runs third-party code and can REMOVE an installed pack, "the documented
    // call does not work" is not an acceptable gap.
    gloss:
      "Everything you do to someone ELSE'S node pack once you have found it: " +
      "install it, keep it working, turn it off, remove it. One tool for the " +
      "whole pack lifecycle, chosen with `action`. FINDING a pack is " +
      "`search_custom_nodes`; writing your own is `node_pack`.",
    examples: [
      {
        ask: "Install the Impact Pack.",
        args: { action: "install", id: "comfyui-impact-pack" },
        returns:
          "Progress, then confirmation. New nodes do not appear until ComfyUI " +
          "restarts — the agent will normally offer to do that for you.",
        caution:
          "A custom node pack is third-party code that runs inside your " +
          "ComfyUI. Install packs you have reason to trust, the same as any " +
          "other plugin.",
      },
      {
        ask: "What node packs have I got installed?",
        args: { action: "list" },
        returns:
          "Every installed pack with its version and whether it is enabled or " +
          "disabled. Read-only.",
      },
      {
        ask: "That pack has been broken since the last update — repair it.",
        args: { action: "fix", id: "comfyui-impact-pack" },
        returns:
          "What the repair did to the pack's install and Python dependencies. A " +
          "restart is usually needed before the result is visible.",
        caution:
          "Repairing re-runs the pack's own install step, which is third-party " +
          "code. Pass id \"all\" only if you mean every installed pack.",
      },
      {
        ask: "Turn that pack off but don't delete it — I want to see if it's the culprit.",
        args: { action: "disable", id: "comfyui-impact-pack" },
        returns:
          "Confirmation, re-read from the installed-pack list so a Manager no-op " +
          "is reported as NOT disabled rather than as success. Restart ComfyUI " +
          "for it to take effect; action \"enable\" puts it back.",
      },
      {
        ask: "Get rid of that pack for good.",
        args: { action: "uninstall", id: "comfyui-impact-pack" },
        returns:
          "Confirmation, but only after the installed-pack list is re-read and " +
          "the pack is provably GONE. An id that resolves nowhere is refused " +
          "before anything is queued.",
        caution:
          "This REMOVES the pack and is irreversible through this tool — any " +
          "workflow using its nodes will go red. For a cleanup audit use action " +
          "\"disable\" first, which is reversible.",
      },
    ],
  },
  node_pack: {
    // Same reasoning as install_custom_node above: `action` is the only required
    // field, so the skeleton documents a call that cannot work. This tool also
    // WRITES files and runs git, so the examples carry the cautions.
    gloss:
      "The loop for authoring YOUR OWN node pack: scaffold it, read and edit its " +
      "source, check it actually loads, commit it, publish it. Everything is " +
      "local and jailed to `custom_nodes/`. Installing someone else's pack is " +
      "`install_custom_node`.",
    examples: [
      {
        ask: "Start me a new node pack called my-cool-nodes.",
        args: {
          action: "scaffold",
          name: "my-cool-nodes",
          display_name: "My Cool Nodes",
        },
        returns:
          "The files it wrote under custom_nodes/my-cool-nodes/ — pyproject.toml, " +
          "__init__.py and a runnable sample node. Restart ComfyUI to load it.",
      },
      {
        ask: "Where is that pack's sampler class defined?",
        args: { action: "search", query: "class .*Sampler", glob: "**/*.py" },
        returns:
          "File/line/text matches under custom_nodes/, capped so a broad regex " +
          "cannot flood the reply. Read-only.",
      },
      {
        ask: "Show me the top of that file.",
        args: { action: "read", path: "my-cool-nodes/src/nodes.py", line_count: 60 },
        returns:
          "The requested line range, with a truncation notice if it was clipped.",
      },
      {
        ask: "Replace that file with the fixed version.",
        args: {
          action: "write",
          path: "my-cool-nodes/src/nodes.py",
          content: "# ...the full new contents of the file...",
          overwrite: true,
        },
        argsNote:
          "`content` is the COMPLETE new file, shortened to one line here. For a " +
          "small edit prefer action \"patch\", which applies a unified diff.",
        returns: "The path written and its byte count.",
        caution:
          "With overwrite true this replaces the whole file. There is no undo " +
          "here — commit first with action \"git\" if the pack is a repo.",
      },
      {
        ask: "Does my pack actually load?",
        args: { action: "verify", name: "my-cool-nodes" },
        returns:
          "Whether each of the pack's node classes appeared in /object_info. A " +
          "class that is missing failed to import — that is your bug.",
        caution:
          "By default this RESTARTS your local ComfyUI, which aborts anything " +
          "rendering. Pass restart false to check the live server as-is.",
      },
      {
        ask: "Commit what I changed in that pack.",
        args: {
          action: "git",
          pack: "my-cool-nodes",
          git_action: "commit",
          message: "fix: sampler returns the right latent",
        },
        returns:
          "The git output, capped. status/diff/log always work; commit and push " +
          "need COMFYUI_MCP_ALLOW_GIT_WRITES=1 and otherwise return a structured " +
          "refusal naming that flag.",
      },
      {
        ask: "Publish it to the registry.",
        args: { action: "publish", name: "my-cool-nodes" },
        returns:
          "What comfy-cli published, after pyproject.toml is validated for a real " +
          "name, version and PublisherId.",
        caution:
          "IRREVERSIBLE and PUBLIC: this creates or updates a version on " +
          "registry.comfy.org that this tool cannot take back. Needs comfy-cli " +
          "and REGISTRY_ACCESS_TOKEN.",
      },
    ],
  },
  node_snapshot: {
    gloss:
      "A restore point for your installed node packs. Take one before a round " +
      "of installing, and you have a way back if something breaks.",
    examples: [
      {
        ask: "Save where my nodes are at before I start installing things.",
        args: { action: "save", name: "before-impact-pack" },
        returns: "Confirmation that the snapshot was recorded.",
      },
      {
        ask: "That broke everything. Put it back how it was.",
        args: { action: "restore", name: "before-impact-pack" },
        returns: "What it added, removed or re-pinned to match the snapshot.",
        caution:
          "Restoring rewrites your installed node packs to match the snapshot — " +
          "anything installed since is uninstalled or downgraded.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Install & Environment
  // -------------------------------------------------------------------------
  workspace: {
    gloss:
      "Which ComfyUI installation everything else is talking about. One tool, " +
      "three jobs, chosen with `action` — this is the tool the " +
      "[consolidation note](/using-tools#one-tool-several-jobs) uses as its " +
      "worked example.",
    examples: [
      {
        ask: "Which ComfyUI am I actually using?",
        args: { action: "get" },
        returns:
          "The active install's path and how it was chosen — a flag, an " +
          "environment variable, or the saved default.",
      },
      {
        ask: "Always use the one on my big drive from now on.",
        args: { action: "set_default", path: "D:/AI/ComfyUI" },
        returns:
          "Confirmation, and it sticks across restarts. Later sessions target " +
          "this install unless something overrides it.",
      },
    ],
  },
  install_comfyui: {
    gloss:
      "One tool for everything that INSTALLS or UPDATES: ComfyUI itself, every " +
      "custom node pack, the Agent sidebar panel, this MCP server, and " +
      "ComfyUI-Manager's own settings. `action` picks which — and nothing here " +
      'is read-only except action:"environment" and the two "status" ' +
      "sub-actions.",
    examples: [
      {
        ask: "Tell me about my setup — useful when I'm reporting a bug.",
        args: { action: "environment" },
        returns:
          "Where ComfyUI is installed, the Python and torch versions, the GPU, " +
          "and which settings are in force. The first thing to paste into an " +
          "issue.",
      },
      {
        ask: "What version of the panel have I got?",
        args: { action: "panel", panel_action: "status" },
        returns:
          "The installed panel version, where it lives, and whether it is " +
          "pinned. This changes nothing.",
      },
      {
        ask: "It says my panel is too old — update it.",
        args: { action: "panel", panel_action: "update" },
        returns:
          "The update result. Two more steps are yours: restart ComfyUI, then " +
          "hard-refresh the ComfyUI browser tab (Ctrl+Shift+R). Without that " +
          "refresh the tab keeps running the old cached panel code and the same " +
          "refusal comes back.",
      },
      {
        ask: "Am I on the latest comfyui-mcp?",
        args: { action: "self_update", self_update_action: "status" },
        returns: "Your version, the latest published version, and whether they differ.",
      },
      {
        ask: "Update comfyui-mcp itself.",
        args: { action: "self_update", self_update_action: "update" },
        returns:
          "The upgrade result. Your MCP client has to be restarted afterwards " +
          "to pick up the new server.",
        caution:
          "This updates THIS server, not ComfyUI and not the sidebar panel — " +
          'those are action:"update" and action:"panel".',
      },
      {
        ask: "Update every custom node I have installed.",
        args: { action: "update_all" },
        returns:
          "Confirmation that the bulk update was queued with ComfyUI-Manager. " +
          "It runs asynchronously and ComfyUI usually needs a restart after.",
        caution:
          "This moves EVERY installed pack, not just the one that is broken. " +
          "It is refused while the sidebar panel is version-pinned.",
      },
    ],
  },
  report_issue: {
    examples: [
      {
        ask: "This keeps crashing. File a bug about it.",
        args: {
          title: "Video render finishes but no file appears in outputs",
          body:
            "Running the WAN template completes and history shows success, but " +
            "output/video/ is empty. Happens every time on 0.49.3.",
        },
        returns:
          "A GitHub issue on the project, with your environment details " +
          "attached automatically, and a link to it.",
        caution:
          "This posts publicly. The environment block it attaches includes " +
          "paths and versions from your machine — glance at what it drafted " +
          "before agreeing to send it.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Process Control
  // -------------------------------------------------------------------------
  restart_comfyui: {
    gloss:
      "Starting, stopping and restarting ComfyUI, chosen with `action`. " +
      "Restarting is the standard next step after installing a node pack — new " +
      "nodes are not loaded until ComfyUI starts again.",
    examples: [
      {
        ask: "Restart it so the new nodes load.",
        args: { action: "restart" },
        returns:
          "The server stops and comes back up, and you are told when it is " +
          "answering again.",
        caution:
          "Anything queued or rendering is lost. Check the queue is empty first " +
          "if a long job is in flight.",
      },
      {
        ask: "Shut ComfyUI down, I need the VRAM.",
        args: { action: "stop" },
        returns: "Confirmation that the process stopped.",
        caution: "Any running or queued render is lost.",
      },
      {
        ask: "Start ComfyUI again.",
        args: { action: "start" },
        returns:
          "It launches the server and waits until it is answering, then tells " +
          "you the URL. Only works for a ComfyUI on this machine.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Defaults
  // -------------------------------------------------------------------------
  get_defaults: {
    gloss:
      "Two separate sets of settings behind one tool, told apart by `action`. " +
      "`get`/`set` are the values the agent falls back to when you do not say " +
      "one — stop repeating yourself and set them once. `get_ui`/`set_ui` are " +
      "ComfyUI's OWN interface settings, the ones its Settings panel writes; " +
      "changing those changes the ComfyUI web UI, not what the agent renders.",
    examples: [
      {
        ask: "What settings do you use when I don't say?",
        args: { action: "get" },
        returns:
          "The current fallback size, steps, cfg, sampler and checkpoint, and " +
          "where each came from.",
      },
      {
        ask: "Always use 1024 by 1024 and 30 steps, permanently.",
        args: { action: "set", values: { width: 1024, height: 1024, steps: 30 }, persist: true },
        returns:
          "Confirmation of the new defaults. `persist: true` writes them to your " +
          "config file so they survive a restart; without it they last only for " +
          "this session.",
      },
      {
        ask: "ComfyUI keeps refusing to open this workflow — loosen its validation.",
        args: { action: "set_ui", id: "Comfy.Validation.Workflows", value: false },
        returns:
          "The old and new value of that ComfyUI interface setting, so you can " +
          "put it back. Reload the ComfyUI tab for it to take effect — an " +
          "already-open tab keeps the old value.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // Apps (micro-apps)
  // -------------------------------------------------------------------------
  apps: {
    gloss:
      "Micro-apps are workflows someone has already wired up and reduced to a " +
      "few boxes to fill in — the closest thing here to a normal app. Handy on " +
      "mobile, where there is no canvas to edit. One tool, several jobs, chosen " +
      "with `action`.",
    examples: [
      {
        ask: "What one-click apps do I have?",
        args: { action: "list" },
        returns:
          "Every installed app with its id, name and description. Read-only.",
      },
      {
        ask: "What do I need to fill in for the portrait one?",
        args: { action: "get", app_id: "b1f4c2de-90a7-4c3e-9d21-5c8e2f7a13bb" },
        returns:
          "That app's input form — each box with its label, type and default, " +
          "and the key you set it by.",
      },
      {
        ask: "Run it with \"a snow leopard on a rooftop\".",
        args: {
          action: "run",
          app_id: "b1f4c2de-90a7-4c3e-9d21-5c8e2f7a13bb",
          values: { "6.text": "a snow leopard on a rooftop at dusk" },
        },
        returns:
          "A prompt_id immediately; the app renders in the background. Check on " +
          "it with `action: \"run_status\"` and the same app_id plus that " +
          "prompt_id. The odd-looking `\"6.text\"` key is nodeId.widget — the " +
          "form from `action: \"get\"` tells you which keys exist.",
      },
    ],
  },

  // -------------------------------------------------------------------------
  // RunPod (cloud GPU)
  //
  // The header above calls RunPod an expert surface left on the skeleton. It is
  // no longer left there, and the reason is specific to the 0.50.0 fold: while
  // each pod action was its own tool, `pod_id` was schema-REQUIRED, so the
  // generated skeleton included it and copying the skeleton at least addressed a
  // pod. Folding eleven tools into two makes `action` the only required field,
  // so the skeleton collapses to `{"action": "watch"}` — a call that
  // deterministically returns the handler's missing-field error. For a surface
  // that rents billed hardware, "the documented call does not work" is not an
  // acceptable gap, so these are real calls. Every `args` object below is
  // validated against the live zod schema at generation time.
  // -------------------------------------------------------------------------
  runpod: {
    gloss:
      "Rent a GPU in the cloud and render on it when your own card cannot fit " +
      "the job, then switch back. One tool for the whole pod lifecycle, chosen " +
      "with `action`. Pods bill by the hour from the moment they boot, so the " +
      "two actions that start one — `create` and `start` — are the ones to be " +
      "deliberate about, and `stop` is how the meter stops.",
    examples: [
      {
        ask: "What pods have I got out there?",
        args: { action: "list" },
        returns:
          "Every pod on your RunPod account with its id, name, state, GPU and " +
          "hourly cost — including any you forgot was running. Read-only.",
      },
      {
        ask: "My 8 GB card can't do this video. Rent me a 4090.",
        args: { action: "create", gpu_type: "NVIDIA GeForce RTX 4090" },
        returns:
          "A new pod deploying from this project's template, with its id and " +
          "hourly rate. It takes 1-3 minutes to boot; connect to it after that.",
        caution:
          "This spends real money and starts billing as soon as the pod boots. " +
          "Stop it with `action: \"stop\"` when you are done — a forgotten pod " +
          "bills all night.",
      },
      {
        ask: "Point everything at that pod.",
        args: { action: "connect", pod_id: "qk29vt4mzx8lb3" },
        returns:
          "Confirmation that the pod's ComfyUI answered and that this session " +
          "now renders there — every other tool follows it until you switch back.",
      },
      {
        ask: "I'm done with the cloud, go back to my own machine.",
        args: { action: "use_local" },
        returns:
          "Confirmation that rendering is back on the local ComfyUI. If a pod " +
          "was in use it says so — it is still running, and still billing.",
        caution:
          "This does NOT stop the pod. Switching back to local leaves the pod " +
          "running and charging; use `action: \"stop\"` to end billing.",
      },
      {
        ask: "Shut the pod down, I don't want to pay for it overnight.",
        args: { action: "stop", pod_id: "qk29vt4mzx8lb3" },
        returns:
          "Confirmation that the GPU was released and GPU-time billing ended. The " +
          "pod and its disk are KEPT so you can start it again later — which is " +
          "the point, but note that retained storage can still be charged for. " +
          "Deleting the pod outright is a RunPod console action, not this tool.",
        caution:
          "Anything rendering on that pod dies with it. Check nothing is in " +
          "flight first — `action: \"status\"` shows what the pod is doing.",
      },
    ],
  },

  runpod_watch: {
    gloss:
      "The live readout for a rented pod — what it is doing, what it is costing " +
      "— plus the \"why won't it connect?\" check. These do not deploy or resume " +
      "a pod; that is the `runpod` tool. Watching a pod DOES arm its idle " +
      "auto-stop, which is a cost guard, not a surprise: an idle watched pod " +
      "stops itself rather than billing for nothing.",
    examples: [
      {
        ask: "Keep an eye on that pod while it boots.",
        args: { action: "watch", pod_id: "qk29vt4mzx8lb3" },
        returns:
          "Live status starts appearing in the control panel — state, GPU and " +
          "VRAM use, uptime, hourly cost — refreshed every ~15 seconds.",
        caution:
          "Watching also arms the idle auto-stop: if that pod's ComfyUI sits " +
          "idle past the configured timeout it is stopped to save money.",
      },
      {
        ask: "It says the pod is running but nothing connects. Why?",
        args: { action: "troubleshoot", pod_id: "qk29vt4mzx8lb3" },
        returns:
          "The specific blocker and what to do about it, in the order it checks: " +
          "no such pod, pod not RUNNING, still booting (no runtime yet — GPU " +
          "attaching / container starting), ComfyUI's port not exposed, or the " +
          "port exposed but ComfyUI not answering. Read-only.",
      },
      {
        ask: "Stop showing me that pod, it's cluttering the panel.",
        args: { action: "unwatch" },
        returns:
          "Confirmation that the broadcast stopped. Unwatching never stops the " +
          "pod, so whatever state it was in, it stays in.",
        caution:
          "This turns the idle auto-stop OFF as well: if the pod is still " +
          "running it now bills until you stop it yourself, with nothing " +
          "watching for it going idle.",
      },
    ],
  },
};
