ima2-gen 1.1.7 → 1.1.9

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.
Files changed (229) hide show
  1. package/README.md +56 -27
  2. package/bin/commands/annotate.js +137 -0
  3. package/bin/commands/annotate.ts +118 -0
  4. package/bin/commands/cancel.js +37 -33
  5. package/bin/commands/cancel.ts +45 -0
  6. package/bin/commands/canvas-versions.js +91 -0
  7. package/bin/commands/canvas-versions.ts +80 -0
  8. package/bin/commands/cardnews.js +293 -0
  9. package/bin/commands/cardnews.ts +248 -0
  10. package/bin/commands/comfy.js +63 -0
  11. package/bin/commands/comfy.ts +54 -0
  12. package/bin/commands/config.js +270 -0
  13. package/bin/commands/config.ts +265 -0
  14. package/bin/commands/edit.js +97 -72
  15. package/bin/commands/edit.ts +116 -0
  16. package/bin/commands/gen.js +140 -118
  17. package/bin/commands/gen.ts +176 -0
  18. package/bin/commands/history.js +164 -0
  19. package/bin/commands/history.ts +145 -0
  20. package/bin/commands/ls.js +60 -42
  21. package/bin/commands/ls.ts +60 -0
  22. package/bin/commands/metadata.js +45 -0
  23. package/bin/commands/metadata.ts +36 -0
  24. package/bin/commands/multimode.js +159 -0
  25. package/bin/commands/multimode.ts +146 -0
  26. package/bin/commands/node.js +176 -0
  27. package/bin/commands/node.ts +157 -0
  28. package/bin/commands/observability.js +201 -0
  29. package/bin/commands/observability.ts +176 -0
  30. package/bin/commands/ping.js +26 -20
  31. package/bin/commands/ping.ts +29 -0
  32. package/bin/commands/prompt.js +506 -0
  33. package/bin/commands/prompt.ts +421 -0
  34. package/bin/commands/ps.js +78 -71
  35. package/bin/commands/ps.ts +78 -0
  36. package/bin/commands/session.js +308 -0
  37. package/bin/commands/session.ts +265 -0
  38. package/bin/commands/show.js +75 -40
  39. package/bin/commands/show.ts +69 -0
  40. package/bin/ima2.js +324 -310
  41. package/bin/ima2.ts +444 -0
  42. package/bin/lib/args.js +75 -66
  43. package/bin/lib/args.ts +73 -0
  44. package/bin/lib/browser-id.js +15 -0
  45. package/bin/lib/browser-id.ts +16 -0
  46. package/bin/lib/client.js +91 -83
  47. package/bin/lib/client.ts +109 -0
  48. package/bin/lib/error-hints.js +14 -17
  49. package/bin/lib/error-hints.ts +23 -0
  50. package/bin/lib/files.js +26 -28
  51. package/bin/lib/files.ts +39 -0
  52. package/bin/lib/output.js +44 -42
  53. package/bin/lib/output.ts +58 -0
  54. package/bin/lib/platform.js +60 -56
  55. package/bin/lib/platform.ts +97 -0
  56. package/bin/lib/sse.js +73 -0
  57. package/bin/lib/sse.ts +73 -0
  58. package/bin/lib/star-prompt.js +69 -76
  59. package/bin/lib/star-prompt.ts +97 -0
  60. package/bin/lib/storage-doctor.js +34 -35
  61. package/bin/lib/storage-doctor.ts +38 -0
  62. package/config.js +147 -190
  63. package/config.ts +331 -0
  64. package/docs/API.md +48 -8
  65. package/docs/CLI.md +190 -0
  66. package/docs/FAQ.ko.md +5 -5
  67. package/docs/FAQ.md +5 -5
  68. package/docs/README.ja.md +71 -25
  69. package/docs/README.ko.md +61 -24
  70. package/docs/README.zh-CN.md +73 -27
  71. package/lib/assetLifecycle.js +130 -130
  72. package/lib/assetLifecycle.ts +142 -0
  73. package/lib/canvasVersionStore.js +135 -153
  74. package/lib/canvasVersionStore.ts +181 -0
  75. package/lib/cardNewsGenerator.js +127 -142
  76. package/lib/cardNewsGenerator.ts +162 -0
  77. package/lib/cardNewsJobStore.js +78 -84
  78. package/lib/cardNewsJobStore.ts +107 -0
  79. package/lib/cardNewsManifestStore.js +88 -93
  80. package/lib/cardNewsManifestStore.ts +112 -0
  81. package/lib/cardNewsPlanner.js +157 -152
  82. package/lib/cardNewsPlanner.ts +180 -0
  83. package/lib/cardNewsPlannerClient.js +101 -98
  84. package/lib/cardNewsPlannerClient.ts +114 -0
  85. package/lib/cardNewsPlannerPrompt.js +56 -56
  86. package/lib/cardNewsPlannerPrompt.ts +60 -0
  87. package/lib/cardNewsPlannerSchema.js +231 -223
  88. package/lib/cardNewsPlannerSchema.ts +259 -0
  89. package/lib/cardNewsRoleTemplateStore.js +39 -41
  90. package/lib/cardNewsRoleTemplateStore.ts +47 -0
  91. package/lib/cardNewsTemplateStore.js +171 -175
  92. package/lib/cardNewsTemplateStore.ts +210 -0
  93. package/lib/codexDetect.js +44 -47
  94. package/lib/codexDetect.ts +69 -0
  95. package/lib/comfyBridge.js +164 -184
  96. package/lib/comfyBridge.ts +214 -0
  97. package/lib/db.js +41 -51
  98. package/lib/db.ts +166 -0
  99. package/lib/errorClassify.js +62 -78
  100. package/lib/errorClassify.ts +100 -0
  101. package/lib/generationErrors.js +140 -103
  102. package/lib/generationErrors.ts +125 -0
  103. package/lib/historyList.js +149 -147
  104. package/lib/historyList.ts +164 -0
  105. package/lib/imageMetadata.js +86 -89
  106. package/lib/imageMetadata.ts +111 -0
  107. package/lib/imageMetadataStore.js +46 -51
  108. package/lib/imageMetadataStore.ts +67 -0
  109. package/lib/imageModels.js +38 -45
  110. package/lib/imageModels.ts +52 -0
  111. package/lib/inflight.js +131 -150
  112. package/lib/inflight.ts +204 -0
  113. package/lib/localImportStore.js +105 -0
  114. package/lib/localImportStore.ts +111 -0
  115. package/lib/logger.js +105 -112
  116. package/lib/logger.ts +150 -0
  117. package/lib/nodeStore.js +65 -64
  118. package/lib/nodeStore.ts +81 -0
  119. package/lib/oauthLauncher.js +61 -59
  120. package/lib/oauthLauncher.ts +64 -0
  121. package/lib/oauthNormalize.js +15 -19
  122. package/lib/oauthNormalize.ts +30 -0
  123. package/lib/oauthProxy.js +834 -832
  124. package/lib/oauthProxy.ts +995 -0
  125. package/lib/openDirectory.js +41 -40
  126. package/lib/openDirectory.ts +45 -0
  127. package/lib/pngInfo.js +18 -20
  128. package/lib/pngInfo.ts +26 -0
  129. package/lib/promptImport/curatedSources.js +135 -0
  130. package/lib/promptImport/curatedSources.ts +139 -0
  131. package/lib/promptImport/discoveryRegistry.js +218 -0
  132. package/lib/promptImport/discoveryRegistry.ts +236 -0
  133. package/lib/promptImport/errors.js +10 -10
  134. package/lib/promptImport/errors.ts +18 -0
  135. package/lib/promptImport/githubDiscovery.js +238 -0
  136. package/lib/promptImport/githubDiscovery.ts +248 -0
  137. package/lib/promptImport/githubFolder.js +302 -0
  138. package/lib/promptImport/githubFolder.ts +308 -0
  139. package/lib/promptImport/githubSource.js +194 -171
  140. package/lib/promptImport/githubSource.ts +239 -0
  141. package/lib/promptImport/gptImageHints.js +61 -0
  142. package/lib/promptImport/gptImageHints.ts +68 -0
  143. package/lib/promptImport/parsePromptCandidates.js +110 -112
  144. package/lib/promptImport/parsePromptCandidates.ts +153 -0
  145. package/lib/promptImport/promptIndex.js +230 -0
  146. package/lib/promptImport/promptIndex.ts +248 -0
  147. package/lib/promptImport/rankPromptCandidates.js +52 -0
  148. package/lib/promptImport/rankPromptCandidates.ts +49 -0
  149. package/lib/providerOptions.js +31 -0
  150. package/lib/providerOptions.ts +41 -0
  151. package/lib/referenceImageCompress.js +51 -62
  152. package/lib/referenceImageCompress.ts +75 -0
  153. package/lib/refs.js +93 -81
  154. package/lib/refs.ts +117 -0
  155. package/lib/requestLogger.js +32 -38
  156. package/lib/requestLogger.ts +48 -0
  157. package/lib/responsesImageAdapter.js +351 -0
  158. package/lib/responsesImageAdapter.ts +352 -0
  159. package/lib/runtimePorts.js +71 -73
  160. package/lib/runtimePorts.ts +93 -0
  161. package/lib/sessionStore.js +179 -230
  162. package/lib/sessionStore.ts +272 -0
  163. package/lib/storageMigration.js +247 -245
  164. package/lib/storageMigration.ts +284 -0
  165. package/lib/styleSheet.js +86 -90
  166. package/lib/styleSheet.ts +128 -0
  167. package/lib/systemTrash.js +18 -0
  168. package/lib/systemTrash.ts +20 -0
  169. package/package.json +26 -10
  170. package/routes/annotations.js +76 -79
  171. package/routes/annotations.ts +95 -0
  172. package/routes/canvasVersions.js +50 -54
  173. package/routes/canvasVersions.ts +64 -0
  174. package/routes/cardNews.js +158 -171
  175. package/routes/cardNews.ts +183 -0
  176. package/routes/comfy.js +23 -31
  177. package/routes/comfy.ts +39 -0
  178. package/routes/edit.js +183 -214
  179. package/routes/edit.ts +230 -0
  180. package/routes/generate.js +269 -291
  181. package/routes/generate.ts +309 -0
  182. package/routes/health.js +102 -107
  183. package/routes/health.ts +114 -0
  184. package/routes/history.js +136 -144
  185. package/routes/history.ts +153 -0
  186. package/routes/imageImport.js +33 -0
  187. package/routes/imageImport.ts +33 -0
  188. package/routes/index.js +18 -16
  189. package/routes/index.ts +35 -0
  190. package/routes/metadata.js +60 -64
  191. package/routes/metadata.ts +71 -0
  192. package/routes/multimode.js +228 -263
  193. package/routes/multimode.ts +280 -0
  194. package/routes/nodes.js +378 -424
  195. package/routes/nodes.ts +455 -0
  196. package/routes/promptImport.js +291 -152
  197. package/routes/promptImport.ts +354 -0
  198. package/routes/prompts.js +333 -360
  199. package/routes/prompts.ts +379 -0
  200. package/routes/sessions.js +277 -285
  201. package/routes/sessions.ts +292 -0
  202. package/routes/storage.js +29 -31
  203. package/routes/storage.ts +39 -0
  204. package/server.js +189 -196
  205. package/server.ts +235 -0
  206. package/ui/dist/.vite/manifest.json +101 -0
  207. package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
  208. package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
  209. package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
  210. package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
  211. package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
  212. package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
  213. package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
  214. package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
  215. package/ui/dist/assets/index-C9cXwiWE.js +25 -0
  216. package/ui/dist/assets/index-CGMIkZXn.css +1 -0
  217. package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
  218. package/ui/dist/index.html +6 -3
  219. package/assets/screenshot.png +0 -0
  220. package/assets/screenshots/classic-generate-light.png +0 -0
  221. package/assets/screenshots/node-graph-branching.png +0 -0
  222. package/assets/screenshots/settings-oauth-generation.png +0 -0
  223. package/assets/screenshots/settings-workspace.png +0 -0
  224. package/assets/screenshots/style-sheet-editor.png +0 -0
  225. package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  226. package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
  227. package/ui/dist/assets/index-DARPdT4Q.css +0 -1
  228. package/ui/dist/assets/index-ht80GMq4.js +0 -31
  229. package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
@@ -8,62 +8,59 @@ import { existsSync } from "node:fs";
8
8
  import { execFileSync } from "node:child_process";
9
9
  import { homedir } from "node:os";
10
10
  import { join } from "node:path";
11
-
12
11
  const HOME = homedir();
13
-
14
12
  export function codexAuthPaths() {
15
- const codexHome = process.env.CODEX_HOME || join(HOME, ".codex");
16
- return {
17
- codex: join(codexHome, "auth.json"),
18
- chatgpt: join(HOME, ".chatgpt-local", "auth.json"),
19
- xdgCodex: join(HOME, ".config", "codex", "auth.json"),
20
- };
13
+ const codexHome = process.env.CODEX_HOME || join(HOME, ".codex");
14
+ return {
15
+ codex: join(codexHome, "auth.json"),
16
+ chatgpt: join(HOME, ".chatgpt-local", "auth.json"),
17
+ xdgCodex: join(HOME, ".config", "codex", "auth.json"),
18
+ };
21
19
  }
22
-
23
20
  export function hasAuthFile() {
24
- const p = codexAuthPaths();
25
- return existsSync(p.codex) || existsSync(p.chatgpt) || existsSync(p.xdgCodex);
21
+ const p = codexAuthPaths();
22
+ return existsSync(p.codex) || existsSync(p.chatgpt) || existsSync(p.xdgCodex);
26
23
  }
27
-
28
24
  // Non-invasive probe: `codex login status` returns 0 when authed (file OR keyring).
29
25
  // Returns: "authed" | "unauthed" | "missing" (codex binary not found)
30
26
  export function codexLoginStatus(timeoutMs = 2000) {
31
- const candidates =
32
- process.platform === "win32"
33
- ? ["codex.cmd", "codex.exe", "codex"]
34
- : ["codex"];
35
- for (const bin of candidates) {
36
- try {
37
- execFileSync(bin, ["login", "status"], {
38
- stdio: "ignore",
39
- timeout: timeoutMs,
40
- windowsHide: true,
41
- });
42
- return "authed";
43
- } catch (err) {
44
- if (err && err.code === "ENOENT") continue;
45
- // non-zero exit = binary exists but not authed
46
- if (err && typeof err.status === "number") return "unauthed";
27
+ const candidates = process.platform === "win32"
28
+ ? ["codex.cmd", "codex.exe", "codex"]
29
+ : ["codex"];
30
+ for (const bin of candidates) {
31
+ try {
32
+ execFileSync(bin, ["login", "status"], {
33
+ stdio: "ignore",
34
+ timeout: timeoutMs,
35
+ windowsHide: true,
36
+ });
37
+ return "authed";
38
+ }
39
+ catch (err) {
40
+ if (err && err.code === "ENOENT")
41
+ continue;
42
+ // non-zero exit = binary exists but not authed
43
+ if (err && typeof err.status === "number")
44
+ return "unauthed";
45
+ }
47
46
  }
48
- }
49
- return "missing";
47
+ return "missing";
50
48
  }
51
-
52
49
  export function detectCodexAuth() {
53
- const files = codexAuthPaths();
54
- const fileHits = {
55
- codex: existsSync(files.codex),
56
- chatgpt: existsSync(files.chatgpt),
57
- xdgCodex: existsSync(files.xdgCodex),
58
- };
59
- const probe = codexLoginStatus();
60
- const authed = probe === "authed" || fileHits.codex || fileHits.chatgpt || fileHits.xdgCodex;
61
- return {
62
- authed,
63
- probe,
64
- files,
65
- fileHits,
66
- platform: process.platform,
67
- wslHint: process.platform === "win32",
68
- };
50
+ const files = codexAuthPaths();
51
+ const fileHits = {
52
+ codex: existsSync(files.codex),
53
+ chatgpt: existsSync(files.chatgpt),
54
+ xdgCodex: existsSync(files.xdgCodex),
55
+ };
56
+ const probe = codexLoginStatus();
57
+ const authed = probe === "authed" || fileHits.codex || fileHits.chatgpt || fileHits.xdgCodex;
58
+ return {
59
+ authed,
60
+ probe,
61
+ files,
62
+ fileHits,
63
+ platform: process.platform,
64
+ wslHint: process.platform === "win32",
65
+ };
69
66
  }
@@ -0,0 +1,69 @@
1
+ // Codex CLI / OAuth auth detection across platforms.
2
+ // References:
3
+ // - OpenAI Codex stores auth under CODEX_HOME (default ~/.codex/auth.json).
4
+ // - Legacy chatgpt-local stores auth under ~/.chatgpt-local/auth.json.
5
+ // - Auth may live in OS keyring instead of a file (file absence ≠ unauth).
6
+ // - Windows has no documented native install path; WSL is the supported path.
7
+ import { existsSync } from "node:fs";
8
+ import { execFileSync } from "node:child_process";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+
12
+ const HOME = homedir();
13
+
14
+ export function codexAuthPaths() {
15
+ const codexHome = process.env.CODEX_HOME || join(HOME, ".codex");
16
+ return {
17
+ codex: join(codexHome, "auth.json"),
18
+ chatgpt: join(HOME, ".chatgpt-local", "auth.json"),
19
+ xdgCodex: join(HOME, ".config", "codex", "auth.json"),
20
+ };
21
+ }
22
+
23
+ export function hasAuthFile() {
24
+ const p = codexAuthPaths();
25
+ return existsSync(p.codex) || existsSync(p.chatgpt) || existsSync(p.xdgCodex);
26
+ }
27
+
28
+ // Non-invasive probe: `codex login status` returns 0 when authed (file OR keyring).
29
+ // Returns: "authed" | "unauthed" | "missing" (codex binary not found)
30
+ export function codexLoginStatus(timeoutMs = 2000) {
31
+ const candidates =
32
+ process.platform === "win32"
33
+ ? ["codex.cmd", "codex.exe", "codex"]
34
+ : ["codex"];
35
+ for (const bin of candidates) {
36
+ try {
37
+ execFileSync(bin, ["login", "status"], {
38
+ stdio: "ignore",
39
+ timeout: timeoutMs,
40
+ windowsHide: true,
41
+ });
42
+ return "authed";
43
+ } catch (err) {
44
+ if (err && err.code === "ENOENT") continue;
45
+ // non-zero exit = binary exists but not authed
46
+ if (err && typeof err.status === "number") return "unauthed";
47
+ }
48
+ }
49
+ return "missing";
50
+ }
51
+
52
+ export function detectCodexAuth() {
53
+ const files = codexAuthPaths();
54
+ const fileHits = {
55
+ codex: existsSync(files.codex),
56
+ chatgpt: existsSync(files.chatgpt),
57
+ xdgCodex: existsSync(files.xdgCodex),
58
+ };
59
+ const probe = codexLoginStatus();
60
+ const authed = probe === "authed" || fileHits.codex || fileHits.chatgpt || fileHits.xdgCodex;
61
+ return {
62
+ authed,
63
+ probe,
64
+ files,
65
+ fileHits,
66
+ platform: process.platform,
67
+ wslHint: process.platform === "win32",
68
+ };
69
+ }
@@ -1,214 +1,194 @@
1
1
  import { constants as fsConstants } from "node:fs";
2
2
  import { access, readFile, realpath, stat } from "node:fs/promises";
3
3
  import { basename, extname, isAbsolute, join, relative } from "node:path";
4
-
5
4
  export const COMFY_ERROR = {
6
- URL_NOT_LOCAL: "COMFY_URL_NOT_LOCAL",
7
- IMAGE_INVALID: "COMFY_IMAGE_INVALID",
8
- IMAGE_NOT_FOUND: "COMFY_IMAGE_NOT_FOUND",
9
- UPLOAD_FAILED: "COMFY_UPLOAD_FAILED",
5
+ URL_NOT_LOCAL: "COMFY_URL_NOT_LOCAL",
6
+ IMAGE_INVALID: "COMFY_IMAGE_INVALID",
7
+ IMAGE_NOT_FOUND: "COMFY_IMAGE_NOT_FOUND",
8
+ UPLOAD_FAILED: "COMFY_UPLOAD_FAILED",
10
9
  };
11
-
12
10
  const LOCAL_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]);
13
-
14
11
  class ComfyBridgeError extends Error {
15
- constructor(code, message, status) {
16
- super(message);
17
- this.name = "ComfyBridgeError";
18
- this.code = code;
19
- this.status = status;
20
- }
12
+ constructor(code, message, status) {
13
+ super(message);
14
+ this.name = "ComfyBridgeError";
15
+ this.code = code;
16
+ this.status = status;
17
+ }
21
18
  }
22
-
23
19
  function bridgeError(code, message, status) {
24
- return new ComfyBridgeError(code, message, status);
20
+ return new ComfyBridgeError(code, message, status);
25
21
  }
26
-
27
22
  export function isComfyBridgeError(error) {
28
- return error instanceof ComfyBridgeError;
23
+ return error instanceof ComfyBridgeError;
29
24
  }
30
-
31
25
  export function normalizeComfyOrigin(rawUrl) {
32
- if (typeof rawUrl !== "string" || !rawUrl.trim()) {
33
- throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not configured.", 400);
34
- }
35
- const trimmed = rawUrl.trim();
36
- const rawHost = trimmed.match(/^http:\/\/(?:[^@/]+@)?(\[[^\]]+\]|[^/:?#]+)/i)?.[1] ?? "";
37
- if (
38
- rawHost === "localhost." ||
39
- /^\d+$/.test(rawHost) ||
40
- /^0x/i.test(rawHost) ||
41
- /^0[0-9]+/.test(rawHost) ||
42
- (/^[0-9.]+$/.test(rawHost) && rawHost.split(".").length !== 4) ||
43
- rawHost.split(".").some((part) => part.length > 1 && part.startsWith("0"))
44
- ) {
45
- throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not local.", 400);
46
- }
47
- let url;
48
- try {
49
- url = new URL(trimmed);
50
- } catch {
51
- throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is invalid.", 400);
52
- }
53
- if (url.protocol !== "http:") {
54
- throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL must use HTTP.", 400);
55
- }
56
- if (url.username || url.password || !url.port) {
57
- throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not local.", 400);
58
- }
59
- if (!LOCAL_HOSTS.has(url.hostname)) {
60
- throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not local.", 400);
61
- }
62
- if (url.pathname !== "/" || url.search || url.hash) {
63
- throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL must be an origin.", 400);
64
- }
65
- return url.origin;
26
+ if (typeof rawUrl !== "string" || !rawUrl.trim()) {
27
+ throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not configured.", 400);
28
+ }
29
+ const trimmed = rawUrl.trim();
30
+ const rawHost = trimmed.match(/^http:\/\/(?:[^@/]+@)?(\[[^\]]+\]|[^/:?#]+)/i)?.[1] ?? "";
31
+ if (rawHost === "localhost." ||
32
+ /^\d+$/.test(rawHost) ||
33
+ /^0x/i.test(rawHost) ||
34
+ /^0[0-9]+/.test(rawHost) ||
35
+ (/^[0-9.]+$/.test(rawHost) && rawHost.split(".").length !== 4) ||
36
+ rawHost.split(".").some((part) => part.length > 1 && part.startsWith("0"))) {
37
+ throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not local.", 400);
38
+ }
39
+ let url;
40
+ try {
41
+ url = new URL(trimmed);
42
+ }
43
+ catch {
44
+ throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is invalid.", 400);
45
+ }
46
+ if (url.protocol !== "http:") {
47
+ throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL must use HTTP.", 400);
48
+ }
49
+ if (url.username || url.password || !url.port) {
50
+ throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not local.", 400);
51
+ }
52
+ if (!LOCAL_HOSTS.has(url.hostname)) {
53
+ throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL is not local.", 400);
54
+ }
55
+ if (url.pathname !== "/" || url.search || url.hash) {
56
+ throw bridgeError(COMFY_ERROR.URL_NOT_LOCAL, "ComfyUI URL must be an origin.", 400);
57
+ }
58
+ return url.origin;
66
59
  }
67
-
68
60
  function hasEncodedSeparator(filename) {
69
- try {
70
- const decoded = decodeURIComponent(filename);
71
- return decoded.includes("/") || decoded.includes("\\");
72
- } catch {
73
- return true;
74
- }
61
+ try {
62
+ const decoded = decodeURIComponent(filename);
63
+ return decoded.includes("/") || decoded.includes("\\");
64
+ }
65
+ catch {
66
+ return true;
67
+ }
75
68
  }
76
-
77
69
  function validateFilename(filename) {
78
- if (typeof filename !== "string" || !filename.trim()) {
79
- throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "A generated filename is required.", 400);
80
- }
81
- if (
82
- isAbsolute(filename) ||
83
- filename !== basename(filename) ||
84
- filename.includes("/") ||
85
- filename.includes("\\") ||
86
- filename.includes("..") ||
87
- /^[a-z][a-z0-9+.-]*:/i.test(filename) ||
88
- hasEncodedSeparator(filename)
89
- ) {
90
- throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated filename is invalid.", 400);
91
- }
92
- return filename;
70
+ if (typeof filename !== "string" || !filename.trim()) {
71
+ throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "A generated filename is required.", 400);
72
+ }
73
+ if (isAbsolute(filename) ||
74
+ filename !== basename(filename) ||
75
+ filename.includes("/") ||
76
+ filename.includes("\\") ||
77
+ filename.includes("..") ||
78
+ /^[a-z][a-z0-9+.-]*:/i.test(filename) ||
79
+ hasEncodedSeparator(filename)) {
80
+ throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated filename is invalid.", 400);
81
+ }
82
+ return filename;
93
83
  }
94
-
95
84
  function isInsideDirectory(parent, candidate) {
96
- const rel = relative(parent, candidate);
97
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
85
+ const rel = relative(parent, candidate);
86
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
98
87
  }
99
-
100
88
  function sniffImage(buffer) {
101
- if (
102
- buffer.length >= 8 &&
103
- buffer[0] === 0x89 &&
104
- buffer[1] === 0x50 &&
105
- buffer[2] === 0x4e &&
106
- buffer[3] === 0x47 &&
107
- buffer[4] === 0x0d &&
108
- buffer[5] === 0x0a &&
109
- buffer[6] === 0x1a &&
110
- buffer[7] === 0x0a
111
- ) {
112
- return { ext: "png", mime: "image/png" };
113
- }
114
- if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
115
- return { ext: "jpg", mime: "image/jpeg" };
116
- }
117
- if (
118
- buffer.length >= 12 &&
119
- buffer.subarray(0, 4).toString("ascii") === "RIFF" &&
120
- buffer.subarray(8, 12).toString("ascii") === "WEBP"
121
- ) {
122
- return { ext: "webp", mime: "image/webp" };
123
- }
124
- throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated file is not a supported image.", 400);
89
+ if (buffer.length >= 8 &&
90
+ buffer[0] === 0x89 &&
91
+ buffer[1] === 0x50 &&
92
+ buffer[2] === 0x4e &&
93
+ buffer[3] === 0x47 &&
94
+ buffer[4] === 0x0d &&
95
+ buffer[5] === 0x0a &&
96
+ buffer[6] === 0x1a &&
97
+ buffer[7] === 0x0a) {
98
+ return { ext: "png", mime: "image/png" };
99
+ }
100
+ if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
101
+ return { ext: "jpg", mime: "image/jpeg" };
102
+ }
103
+ if (buffer.length >= 12 &&
104
+ buffer.subarray(0, 4).toString("ascii") === "RIFF" &&
105
+ buffer.subarray(8, 12).toString("ascii") === "WEBP") {
106
+ return { ext: "webp", mime: "image/webp" };
107
+ }
108
+ throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated file is not a supported image.", 400);
125
109
  }
126
-
127
110
  function sanitizeBaseName(filename) {
128
- const raw = basename(filename, extname(filename));
129
- const safe = raw.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
130
- return safe || "image";
111
+ const raw = basename(filename, extname(filename));
112
+ const safe = raw.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
113
+ return safe || "image";
131
114
  }
132
-
133
115
  async function readGeneratedImage(ctx, filename) {
134
- const safeFilename = validateFilename(filename);
135
- const generatedDir = await realpath(ctx.config.storage.generatedDir);
136
- const candidatePath = join(ctx.config.storage.generatedDir, safeFilename);
137
- try {
138
- await access(candidatePath, fsConstants.F_OK);
139
- } catch {
140
- throw bridgeError(COMFY_ERROR.IMAGE_NOT_FOUND, "Generated image was not found.", 404);
141
- }
142
- let candidateReal;
143
- try {
144
- candidateReal = await realpath(candidatePath);
145
- } catch {
146
- throw bridgeError(COMFY_ERROR.IMAGE_NOT_FOUND, "Generated image was not found.", 404);
147
- }
148
- if (!isInsideDirectory(generatedDir, candidateReal)) {
149
- throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated filename is invalid.", 400);
150
- }
151
- const info = await stat(candidateReal);
152
- if (!info.isFile()) {
153
- throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated filename is invalid.", 400);
154
- }
155
- if (info.size > ctx.config.comfy.maxUploadBytes) {
156
- throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated image is too large.", 400);
157
- }
158
- const buffer = await readFile(candidateReal);
159
- const imageType = sniffImage(buffer);
160
- return {
161
- buffer,
162
- imageType,
163
- sourceFilename: safeFilename,
164
- uploadFilename: `ima2_${Date.now()}_${sanitizeBaseName(safeFilename)}.${imageType.ext}`,
165
- };
116
+ const safeFilename = validateFilename(filename);
117
+ const generatedDir = await realpath(ctx.config.storage.generatedDir);
118
+ const candidatePath = join(ctx.config.storage.generatedDir, safeFilename);
119
+ try {
120
+ await access(candidatePath, fsConstants.F_OK);
121
+ }
122
+ catch {
123
+ throw bridgeError(COMFY_ERROR.IMAGE_NOT_FOUND, "Generated image was not found.", 404);
124
+ }
125
+ let candidateReal;
126
+ try {
127
+ candidateReal = await realpath(candidatePath);
128
+ }
129
+ catch {
130
+ throw bridgeError(COMFY_ERROR.IMAGE_NOT_FOUND, "Generated image was not found.", 404);
131
+ }
132
+ if (!isInsideDirectory(generatedDir, candidateReal)) {
133
+ throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated filename is invalid.", 400);
134
+ }
135
+ const info = await stat(candidateReal);
136
+ if (!info.isFile()) {
137
+ throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated filename is invalid.", 400);
138
+ }
139
+ if (info.size > ctx.config.comfy.maxUploadBytes) {
140
+ throw bridgeError(COMFY_ERROR.IMAGE_INVALID, "Generated image is too large.", 400);
141
+ }
142
+ const buffer = await readFile(candidateReal);
143
+ const imageType = sniffImage(buffer);
144
+ return {
145
+ buffer,
146
+ imageType,
147
+ sourceFilename: safeFilename,
148
+ uploadFilename: `ima2_${Date.now()}_${sanitizeBaseName(safeFilename)}.${imageType.ext}`,
149
+ };
166
150
  }
167
-
168
151
  async function postToComfy(origin, image, timeoutMs, fetchImpl = fetch) {
169
- const controller = new AbortController();
170
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
171
- try {
172
- const form = new FormData();
173
- form.append("image", new Blob([image.buffer], { type: image.imageType.mime }), image.uploadFilename);
174
- form.append("type", "input");
175
- const res = await fetchImpl(`${origin}/upload/image`, {
176
- method: "POST",
177
- body: form,
178
- redirect: "manual",
179
- signal: controller.signal,
180
- });
181
- if (res.status >= 300 && res.status < 400) {
182
- throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
183
- }
184
- if (!res.ok) {
185
- throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
186
- }
187
- const data = await res.json().catch(() => null);
188
- if (!data || typeof data.name !== "string" || !data.name.trim()) {
189
- throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
190
- }
191
- return data.name;
192
- } catch (error) {
193
- if (isComfyBridgeError(error)) throw error;
194
- throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
195
- } finally {
196
- clearTimeout(timeout);
197
- }
152
+ const controller = new AbortController();
153
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
154
+ try {
155
+ const form = new FormData();
156
+ form.append("image", new Blob([image.buffer], { type: image.imageType.mime }), image.uploadFilename);
157
+ form.append("type", "input");
158
+ const res = await fetchImpl(`${origin}/upload/image`, {
159
+ method: "POST",
160
+ body: form,
161
+ redirect: "manual",
162
+ signal: controller.signal,
163
+ });
164
+ if (res.status >= 300 && res.status < 400) {
165
+ throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
166
+ }
167
+ if (!res.ok) {
168
+ throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
169
+ }
170
+ const data = await res.json().catch(() => null);
171
+ if (!data || typeof data.name !== "string" || !data.name.trim()) {
172
+ throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
173
+ }
174
+ return data.name;
175
+ }
176
+ catch (error) {
177
+ if (isComfyBridgeError(error))
178
+ throw error;
179
+ throw bridgeError(COMFY_ERROR.UPLOAD_FAILED, "Could not upload image to ComfyUI.", 502);
180
+ }
181
+ finally {
182
+ clearTimeout(timeout);
183
+ }
198
184
  }
199
-
200
185
  export async function exportImageToComfy(ctx, input, options = {}) {
201
- const origin = normalizeComfyOrigin(options.comfyUrl ?? ctx.config.comfy.defaultUrl);
202
- const image = await readGeneratedImage(ctx, input.filename);
203
- const uploadedFilename = await postToComfy(
204
- origin,
205
- image,
206
- ctx.config.comfy.uploadTimeoutMs,
207
- options.fetchImpl,
208
- );
209
- return {
210
- ok: true,
211
- sourceFilename: image.sourceFilename,
212
- uploadedFilename,
213
- };
186
+ const origin = normalizeComfyOrigin(options.comfyUrl ?? ctx.config.comfy.defaultUrl);
187
+ const image = await readGeneratedImage(ctx, input.filename);
188
+ const uploadedFilename = await postToComfy(origin, image, ctx.config.comfy.uploadTimeoutMs, options.fetchImpl);
189
+ return {
190
+ ok: true,
191
+ sourceFilename: image.sourceFilename,
192
+ uploadedFilename,
193
+ };
214
194
  }