comfyui-mcp 0.51.18 → 0.51.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,209 @@
1
+ /**
2
+ * The SHIPPED catalogs, rendered through the real runtime.
3
+ *
4
+ * i18n-check.mjs already compares the files to each other: same keys, same placeholders, right
5
+ * plural categories. What it cannot see is whether the running process can find and use them —
6
+ * a catalog can be perfect JSON in a directory `catalogFor` never looks in, under a code
7
+ * `resolveLocale` maps somewhere else, and the only symptom is that every language quietly
8
+ * renders English. That failure looks identical to "translation not started yet" and is exactly
9
+ * what this repo shipped before these files existed.
10
+ *
11
+ * So every assertion here goes through `trFor` rather than through the JSON, and the load-
12
+ * bearing one is the sentinel: a fallback string no catalog contains, which comes back
13
+ * unchanged if and only if the lookup missed.
14
+ */
15
+ import { describe, it, expect, afterEach } from "vitest";
16
+ import { execFileSync } from "node:child_process";
17
+ import { readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { trFor, LOCALES, __resetI18nForTest } from "./index.js";
20
+ import { displayWidth, RULE_MAX } from "./terminal-layout.js";
21
+ afterEach(() => __resetI18nForTest());
22
+ const ROOT = join(import.meta.dirname, "..", "..");
23
+ const TRANSLATED = LOCALES.filter((l) => l !== "en");
24
+ function flatten(node, prefix = "", out = {}) {
25
+ for (const [k, v] of Object.entries((node ?? {}))) {
26
+ const key = prefix ? `${prefix}.${k}` : k;
27
+ if (v && typeof v === "object")
28
+ flatten(v, key, out);
29
+ else if (typeof v === "string")
30
+ out[key] = v;
31
+ }
32
+ return out;
33
+ }
34
+ const english = flatten(JSON.parse(readFileSync(join(ROOT, "locales", "en", "main.json"), "utf8")));
35
+ const KEYS = Object.keys(english).sort();
36
+ /** Placeholder names a value declares, e.g. `{url}` -> "url". */
37
+ const holes = (s) => (s.match(/\{(\w+)\}/g) ?? []).map((h) => h.slice(1, -1));
38
+ /**
39
+ * Substitution values chosen to be findable in the output. `count` stays a NUMBER because
40
+ * `trFor` only takes the plural path when it is one — passing a string everywhere would leave
41
+ * the counted strings' branch untested.
42
+ */
43
+ function varsFor(text) {
44
+ const vars = {};
45
+ for (const name of holes(text))
46
+ vars[name] = name === "count" ? 7 : `«${name}»`;
47
+ return vars;
48
+ }
49
+ /**
50
+ * Status markers are NOT decoration: they are what a reader scans before reading a word, and
51
+ * `ready-banner.ts` states outright that every translation must reproduce them. A catalog that
52
+ * drops the 🟢 changes what the line asserts; one that keeps a ⚠️ the frame re-adds prints two.
53
+ */
54
+ const MARKERS = ["🟢", "⚠️", "✓", "✅", "🔒", "⏸", "🕶️", "👁️", "ℹ️"];
55
+ describe("English is generated, not written", () => {
56
+ it("locales/en/main.json still matches what the call sites declare", () => {
57
+ // Same contract as the docs:gen gate — a committed generated file that no longer matches
58
+ // its source is a silent lie, and here the lie is "these keys are the ones we render".
59
+ const fresh = execFileSync(process.execPath, [join(ROOT, "scripts", "i18n-build-en.mjs"), "--print"], {
60
+ cwd: ROOT,
61
+ encoding: "utf8",
62
+ maxBuffer: 1 << 26,
63
+ });
64
+ expect(fresh).toBe(readFileSync(join(ROOT, "locales", "en", "main.json"), "utf8"));
65
+ });
66
+ it("declares a catalog worth translating", () => {
67
+ expect(KEYS.length).toBeGreaterThan(100);
68
+ });
69
+ });
70
+ describe.each(TRANSLATED)("%s renders through the real runtime", (locale) => {
71
+ it("answers EVERY key — a sentinel fallback never survives", () => {
72
+ // The whole point. `«no catalog»` appears in no file, so getting it back means the lookup
73
+ // missed: wrong directory, wrong locale code, a nesting level `flatten` did not descend.
74
+ const missed = KEYS.filter((key) => trFor(locale, key, "«no catalog»") === "«no catalog»");
75
+ expect(missed).toEqual([]);
76
+ });
77
+ it("never renders a key as its own name", () => {
78
+ const leaked = KEYS.filter((key) => {
79
+ const out = trFor(locale, key, english[key], varsFor(english[key]));
80
+ return out === key || out.includes(key);
81
+ });
82
+ expect(leaked).toEqual([]);
83
+ });
84
+ it("substitutes every placeholder and leaves none behind", () => {
85
+ const broken = [];
86
+ for (const key of KEYS) {
87
+ const vars = varsFor(english[key]);
88
+ const out = trFor(locale, key, english[key], vars);
89
+ for (const [name, value] of Object.entries(vars)) {
90
+ if (!out.includes(String(value)))
91
+ broken.push(`${key}: {${name}} did not substitute`);
92
+ }
93
+ // A `{leftover}` means the translation renamed or invented a placeholder: the runtime
94
+ // leaves an unknown hole verbatim, so the reader sees braces instead of the answer.
95
+ const leftover = out.match(/\{\w+\}/g);
96
+ if (leftover)
97
+ broken.push(`${key}: unresolved ${leftover.join(", ")}`);
98
+ }
99
+ expect(broken).toEqual([]);
100
+ });
101
+ it("renders no empty value", () => {
102
+ const empty = KEYS.filter((key) => !trFor(locale, key, english[key], varsFor(english[key])).trim());
103
+ expect(empty).toEqual([]);
104
+ });
105
+ it("keeps the status marker the English line opens with", () => {
106
+ const dropped = [];
107
+ for (const key of KEYS) {
108
+ const marker = MARKERS.find((m) => english[key].startsWith(m));
109
+ if (!marker)
110
+ continue;
111
+ const out = trFor(locale, key, english[key], varsFor(english[key]));
112
+ if (!out.startsWith(marker))
113
+ dropped.push(`${key}: expected to start with ${marker}`);
114
+ }
115
+ expect(dropped).toEqual([]);
116
+ });
117
+ it("keeps every backticked command and every ENV_VAR verbatim", () => {
118
+ // These are things the reader TYPES. A translated `codex login` or a localised
119
+ // OPENROUTER_API_KEY is an instruction that cannot work, and no shape check sees it.
120
+ const lost = [];
121
+ for (const key of KEYS) {
122
+ const out = trFor(locale, key, english[key], varsFor(english[key]));
123
+ for (const cmd of english[key].match(/`[^`]+`/g) ?? []) {
124
+ if (!out.includes(cmd))
125
+ lost.push(`${key}: lost ${cmd}`);
126
+ }
127
+ for (const env of english[key].match(/\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+){2,}\b/g) ?? []) {
128
+ if (!out.includes(env))
129
+ lost.push(`${key}: lost ${env}`);
130
+ }
131
+ }
132
+ expect(lost).toEqual([]);
133
+ });
134
+ });
135
+ /**
136
+ * Strings that land INSIDE one of the two `════` boxes the CLI draws.
137
+ *
138
+ * Enumerated rather than matched on a `cli.connect_`/`cli.tunnel_` prefix: `connect_url_invalid`
139
+ * and `cannot_start` share those prefixes and are free-flowing fatal lines with no box around
140
+ * them, so a keyword rule would hold them to a width they have no reason to respect — and the
141
+ * repo has been bitten before by a guard whose over-broad direction is invisible in its own
142
+ * output. A key added to a banner has to be added here too; the render in the PR is the
143
+ * backstop that notices if it is not.
144
+ */
145
+ const BOXED_KEYS = [
146
+ "connect_title",
147
+ "connect_bridge_secure",
148
+ "connect_label_bridge",
149
+ "connect_label_driving",
150
+ "connect_label_secure",
151
+ "connect_secure_note",
152
+ "connect_next_steps",
153
+ "connect_step_open_comfyui",
154
+ "connect_step_enable_external_orchestrator",
155
+ "connect_step_click_connect",
156
+ "connect_quiet_note",
157
+ "tunnel_title",
158
+ "tunnel_label_public_url",
159
+ "tunnel_label_token",
160
+ "tunnel_desktop_connector_path",
161
+ "tunnel_field_header",
162
+ "tunnel_field_name",
163
+ "tunnel_field_url",
164
+ "tunnel_header_alt",
165
+ "tunnel_headless_snippet",
166
+ "tunnel_keep_open",
167
+ ];
168
+ describe("box-drawn banners survive translation", () => {
169
+ it.each(LOCALES)("%s keeps every boxed line inside the rule", (locale) => {
170
+ // CJK is TWO cells per character and the box caps its rule at RULE_MAX, so a sentence that
171
+ // is a comfortable 70 characters in English can be a 140-cell line that walks straight out
172
+ // of the frame. This is a NECESSARY condition, not a sufficient one — every boxed line also
173
+ // carries a prefix (a label column, a step number) that this cannot know — which is why the
174
+ // PR pastes real renders. What it does catch, cheaply and forever, is the gross case.
175
+ const budget = RULE_MAX - 1; // every free-standing banner line is written as ` ${text}`
176
+ const tooWide = [];
177
+ for (const key of BOXED_KEYS) {
178
+ const full = `cli.${key}`;
179
+ expect(english[full], `${full} is not in the English catalog — was a banner key renamed?`).toBeTypeOf("string");
180
+ const rendered = trFor(locale, full, english[full], varsFor(english[full]));
181
+ for (const line of rendered.split("\n")) {
182
+ const w = displayWidth(line);
183
+ if (w > budget)
184
+ tooWide.push(`${full}: ${w} cells > ${budget} — ${line}`);
185
+ }
186
+ }
187
+ expect(tooWide).toEqual([]);
188
+ });
189
+ });
190
+ describe("the catalogs are actually being read", () => {
191
+ it("translates every key that is prose rather than an acronym", () => {
192
+ // Guards the case a sentinel cannot: a file that loads but is a copy of English. The
193
+ // exemption is i18n-check's own rule — a value with no run of four lowercase letters is
194
+ // "API", "URL", "PANEL / COMFYUI", which are the same word in every language and would
195
+ // be WRONG to change. Using the same rule here keeps the two gates from disagreeing.
196
+ const isProse = (s) => /[a-z]{4}/.test(s);
197
+ for (const locale of TRANSLATED) {
198
+ const same = KEYS.filter((key) => isProse(english[key]) && trFor(locale, key, english[key]) === english[key]);
199
+ expect(`${locale}: ${same.join(", ") || "none"}`).toBe(`${locale}: none`);
200
+ }
201
+ });
202
+ it("resolves the regional codes the way the files are named", () => {
203
+ // `pt-BR` and `zh-TW` are the two codes where a directory name and a resolved locale could
204
+ // disagree; if they did, those two languages would silently render English forever.
205
+ expect(trFor("pt_BR.UTF-8", "console.credentials.save", "Save")).not.toBe("Save");
206
+ expect(trFor("zh-HK", "console.credentials.save", "Save")).not.toBe("Save");
207
+ });
208
+ });
209
+ //# sourceMappingURL=catalogs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogs.test.js","sourceRoot":"","sources":["../../src/i18n/catalogs.test.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,14 @@ describe("locale resolution", () => {
43
43
  it("--lang beats the environment, and explicit env beats the POSIX chain", () => {
44
44
  const prev = { ...process.env };
45
45
  try {
46
+ // Own the WHOLE chain, not just the link under test. The suite now pins
47
+ // COMFYUI_MCP_LANG=en (see helpers/isolated-test-home.ts), and this case asserts that
48
+ // LANG is consulted — which is only observable when the higher-precedence variables are
49
+ // absent. Leaving them to the ambient environment made the test assert "en beats fr" on
50
+ // a pinned machine and "ja beats fr" for anyone with LANG=ja_JP, i.e. it measured the
51
+ // developer rather than the code.
52
+ for (const k of ["COMFYUI_MCP_LANG", "LC_ALL", "LC_MESSAGES"])
53
+ delete process.env[k];
46
54
  process.env.LANG = "fr_FR.UTF-8";
47
55
  expect(processLocale(["node", "cli", "--lang", "ko"])).toBe("ko");
48
56
  expect(processLocale(["node", "cli", "--lang=ja"])).toBe("ja");
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.test.js","sourceRoot":"","sources":["../../src/i18n/i18n.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"i18n.test.js","sourceRoot":"","sources":["../../src/i18n/i18n.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * #1371 — a model was written into a DIFFERENT local ComfyUI installation than the one
3
+ * serving the session.
4
+ *
5
+ * Connected to ComfyUI on `:8190` (`…\ComfyUI_H3`), the download streamed into
6
+ * `…\ComfyUI\models` — a stale `COMFYUI_PATH` from another install — while visibility
7
+ * was checked against the connected server.
8
+ *
9
+ * ## Why the existing guard did not catch it
10
+ *
11
+ * `model-resolver.ts` has refused divergent destinations since v0.49.4, and the reporter
12
+ * was on 0.50.107 — newer — so the guard was present and did not fire. It proves
13
+ * divergence from CONTENT: files on disk the running server does not list. With a sparse
14
+ * or empty destination category (`vae`, here) there is nothing to compare.
15
+ *
16
+ * ## The evidence used, and one that was REFUTED first
17
+ *
18
+ * A first attempt refused when the destination fell outside the install root the server
19
+ * names for itself. Review knocked that down, correctly: being outside the install root
20
+ * does not mean the server cannot read it. `extra_model_paths.yaml` registers roots
21
+ * elsewhere by design, `<install>/models` is routinely a junction to another drive, and
22
+ * a container-mounted path and its host destination describe one directory through two
23
+ * unrelated strings. #1474 — fixed the same day — is a Comfy Desktop install serving
24
+ * models from a SHARED root outside its install directory; that guard would have refused
25
+ * it outright.
26
+ *
27
+ * The right question is not "is this inside the install" but "does the connected server
28
+ * read this tree", and `isUnderLiveModelRoots` already answers exactly that: it checks
29
+ * the live primary models root AND live-registered extra roots, canonicalizes junctions
30
+ * and symlinks through `realpath`, is category-scoped, and returns `undefined` for
31
+ * UNKNOWN rather than folding it into "no".
32
+ *
33
+ * So this refuses on `inRoots === false` — demonstrably not read — and never on
34
+ * `undefined`. That is the distinction the whole file exists to hold: the surrounding
35
+ * code's own note records that every time "the server did not vouch for it" was treated
36
+ * as "the server cannot read it", it produced another false refusal.
37
+ */
38
+ /**
39
+ * The refusal for a destination the connected server demonstrably does not read.
40
+ *
41
+ * `inRoots` comes from `isUnderLiveModelRoots`: `false` means demonstrably outside every
42
+ * tree the live server reads; `undefined` means only local configuration could answer,
43
+ * which proves nothing and must NOT refuse.
44
+ */
45
+ export function divergentInstallRefusal(opts) {
46
+ const { targetDir, inRoots, liveRoot, serverUrl } = opts;
47
+ if (inRoots !== false)
48
+ return null; // true = fine; undefined = unknown, never refuse
49
+ if (!targetDir)
50
+ return null;
51
+ const where = liveRoot ? ` It reads models from "${liveRoot}" (and any extra roots it has registered).` : "";
52
+ return (`NOT downloaded: the connected ComfyUI${serverUrl ? ` (${serverUrl})` : ""} does not ` +
53
+ `read from "${targetDir}", so a model placed there would never be visible to it.` +
54
+ `${where} This destination came from LOCAL CONFIGURATION — COMFYUI_PATH or the saved ` +
55
+ `default workspace — not from the running server, and a COMFYUI_PATH left over from a ` +
56
+ `different installation is the usual cause (#1371). Nothing was written.\n\n` +
57
+ `This is a demonstrated mismatch, not an unverified one: the destination was checked ` +
58
+ `against the roots the live server actually reads, including any registered through ` +
59
+ `extra_model_paths.yaml, and with junctions and symlinks resolved. Point COMFYUI_PATH ` +
60
+ `at the ComfyUI that is actually running, or launch that server with an absolute ` +
61
+ `--base-directory, then retry. If you MEANT another install, download it there — ` +
62
+ `writing here would report success for a model the connected server can never load.`);
63
+ }
64
+ //# sourceMappingURL=download-root-correspondence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-root-correspondence.js","sourceRoot":"","sources":["../../src/services/download-root-correspondence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAMvC;IACC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IACzD,IAAI,OAAO,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC,CAAC,iDAAiD;IACrF,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,0BAA0B,QAAQ,4CAA4C,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7G,OAAO,CACL,wCAAwC,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,YAAY;QACtF,cAAc,SAAS,0DAA0D;QACjF,GAAG,KAAK,8EAA8E;QACtF,uFAAuF;QACvF,6EAA6E;QAC7E,sFAAsF;QACtF,qFAAqF;QACrF,uFAAuF;QACvF,kFAAkF;QAClF,kFAAkF;QAClF,oFAAoF,CACrF,CAAC;AACJ,CAAC"}
@@ -6,13 +6,14 @@ import { dirname, join, basename, normalize, resolve, relative, sep, isAbsolute,
6
6
  import { config, getComfyUIBaseUrl, isRemoteMode } from "../config.js";
7
7
  import { getClient, getLogs, getSystemStats, comfyApiFetch } from "../comfyui/client.js";
8
8
  import { getExtraModelRoots, getLiveExtraModelRoots } from "./extra-paths.js";
9
- import { resolveEffectiveComfyUIBase, resolveLiveServerRoot } from "./workspace-env.js";
9
+ import { resolveEffectiveComfyUIBase, resolveLiveServerRoot, } from "./workspace-env.js";
10
10
  import { installModelViaManager } from "./node-management.js";
11
11
  import { ModelError, ValidationError, unreachableHostMessage } from "../utils/errors.js";
12
12
  import { logger } from "../utils/logger.js";
13
13
  import { downloadWithCache, probeRemoteModelPayload } from "./download-cache.js";
14
14
  import { reportDownloadProgress } from "./download-progress.js";
15
15
  import { modelNotFoundMessage } from "./model-root-scope.js";
16
+ import { divergentInstallRefusal } from "./download-root-correspondence.js";
16
17
  import { resolveModelsDir, resolveModelsDirWithBases, parseModelsDirFromArgv, hasUnresolvableRelativeModelDirFlag, isLiveAuthoritativeModelsDir, } from "./output-dir.js";
17
18
  import { applyDownloadAuth, redactUrlForLogs, } from "./download-auth.js";
18
19
  export const MODEL_SUBDIRS = [
@@ -1787,6 +1788,12 @@ liveSnapshot = { reachable: false }) {
1787
1788
  cursor = parent;
1788
1789
  }
1789
1790
  }
1791
+ /** The models CATEGORY a target subfolder belongs to — its first segment. Live extra
1792
+ * roots are category-scoped, so a `checkpoints` root must not vouch for a LoRA. */
1793
+ function normalizedSubfolderFor(targetSubfolder) {
1794
+ const first = (targetSubfolder ?? "").trim().replace(/\\/g, "/").split("/")[0];
1795
+ return first || undefined;
1796
+ }
1790
1797
  /**
1791
1798
  * The SINGLE source of truth for a model download's final on-disk destination,
1792
1799
  * shared by downloadModel (the writer) AND the background job registry (which
@@ -1816,6 +1823,37 @@ export async function resolveDownloadTarget(url, targetSubfolder, filename) {
1816
1823
  if (!resolve(targetPath).startsWith(resolve(targetDir) + sep)) {
1817
1824
  throw new ModelError("Refusing to write outside the target model directory.", { filename: rawFilename });
1818
1825
  }
1826
+ // #1371 — a stale COMFYUI_PATH wrote a model into a DIFFERENT local install than the
1827
+ // one serving the session. The destination is rooted at the server's own models dir
1828
+ // when it can be, but falls back to <COMFYUI_PATH>/models when the server was not
1829
+ // launched with --base-directory — and nothing then checked that the fallback belongs
1830
+ // to THAT server.
1831
+ //
1832
+ // A server can still name its install root through its main.py argv. When it does and
1833
+ // the destination is outside it, the installs are PROVABLY different. Positive
1834
+ // evidence, so this does not reintroduce the false-refusal class the content check's
1835
+ // own note warns about — and it is skipped entirely when the destination came from
1836
+ // the live server (nothing to disagree with) or when no root can be derived.
1837
+ if (!liveRootAtResolve) {
1838
+ const membership = await isUnderLiveModelRoots(targetDir, normalizedSubfolderFor(targetSubfolder));
1839
+ const refusal = divergentInstallRefusal({
1840
+ targetDir,
1841
+ inRoots: membership.inRoots,
1842
+ liveRoot: membership.liveRoot,
1843
+ // Defensive: decoration on a refusal, and an existing suite mocks config.js
1844
+ // without this export — a message detail must never fail a download.
1845
+ serverUrl: (() => {
1846
+ try {
1847
+ return getComfyUIBaseUrl();
1848
+ }
1849
+ catch {
1850
+ return undefined;
1851
+ }
1852
+ })(),
1853
+ });
1854
+ if (refusal)
1855
+ throw new ModelError(refusal, { path: targetDir });
1856
+ }
1819
1857
  return { targetDir, filename: resolvedFilename, targetPath, liveRootAtResolve };
1820
1858
  }
1821
1859
  /**