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.
- package/README.md +56 -27
- package/bin/commands/annotate.js +137 -0
- package/bin/commands/annotate.ts +118 -0
- package/bin/commands/cancel.js +37 -33
- package/bin/commands/cancel.ts +45 -0
- package/bin/commands/canvas-versions.js +91 -0
- package/bin/commands/canvas-versions.ts +80 -0
- package/bin/commands/cardnews.js +293 -0
- package/bin/commands/cardnews.ts +248 -0
- package/bin/commands/comfy.js +63 -0
- package/bin/commands/comfy.ts +54 -0
- package/bin/commands/config.js +270 -0
- package/bin/commands/config.ts +265 -0
- package/bin/commands/edit.js +97 -72
- package/bin/commands/edit.ts +116 -0
- package/bin/commands/gen.js +140 -118
- package/bin/commands/gen.ts +176 -0
- package/bin/commands/history.js +164 -0
- package/bin/commands/history.ts +145 -0
- package/bin/commands/ls.js +60 -42
- package/bin/commands/ls.ts +60 -0
- package/bin/commands/metadata.js +45 -0
- package/bin/commands/metadata.ts +36 -0
- package/bin/commands/multimode.js +159 -0
- package/bin/commands/multimode.ts +146 -0
- package/bin/commands/node.js +176 -0
- package/bin/commands/node.ts +157 -0
- package/bin/commands/observability.js +201 -0
- package/bin/commands/observability.ts +176 -0
- package/bin/commands/ping.js +26 -20
- package/bin/commands/ping.ts +29 -0
- package/bin/commands/prompt.js +506 -0
- package/bin/commands/prompt.ts +421 -0
- package/bin/commands/ps.js +78 -71
- package/bin/commands/ps.ts +78 -0
- package/bin/commands/session.js +308 -0
- package/bin/commands/session.ts +265 -0
- package/bin/commands/show.js +75 -40
- package/bin/commands/show.ts +69 -0
- package/bin/ima2.js +324 -310
- package/bin/ima2.ts +444 -0
- package/bin/lib/args.js +75 -66
- package/bin/lib/args.ts +73 -0
- package/bin/lib/browser-id.js +15 -0
- package/bin/lib/browser-id.ts +16 -0
- package/bin/lib/client.js +91 -83
- package/bin/lib/client.ts +109 -0
- package/bin/lib/error-hints.js +14 -17
- package/bin/lib/error-hints.ts +23 -0
- package/bin/lib/files.js +26 -28
- package/bin/lib/files.ts +39 -0
- package/bin/lib/output.js +44 -42
- package/bin/lib/output.ts +58 -0
- package/bin/lib/platform.js +60 -56
- package/bin/lib/platform.ts +97 -0
- package/bin/lib/sse.js +73 -0
- package/bin/lib/sse.ts +73 -0
- package/bin/lib/star-prompt.js +69 -76
- package/bin/lib/star-prompt.ts +97 -0
- package/bin/lib/storage-doctor.js +34 -35
- package/bin/lib/storage-doctor.ts +38 -0
- package/config.js +147 -190
- package/config.ts +331 -0
- package/docs/API.md +48 -8
- package/docs/CLI.md +190 -0
- package/docs/FAQ.ko.md +5 -5
- package/docs/FAQ.md +5 -5
- package/docs/README.ja.md +71 -25
- package/docs/README.ko.md +61 -24
- package/docs/README.zh-CN.md +73 -27
- package/lib/assetLifecycle.js +130 -130
- package/lib/assetLifecycle.ts +142 -0
- package/lib/canvasVersionStore.js +135 -153
- package/lib/canvasVersionStore.ts +181 -0
- package/lib/cardNewsGenerator.js +127 -142
- package/lib/cardNewsGenerator.ts +162 -0
- package/lib/cardNewsJobStore.js +78 -84
- package/lib/cardNewsJobStore.ts +107 -0
- package/lib/cardNewsManifestStore.js +88 -93
- package/lib/cardNewsManifestStore.ts +112 -0
- package/lib/cardNewsPlanner.js +157 -152
- package/lib/cardNewsPlanner.ts +180 -0
- package/lib/cardNewsPlannerClient.js +101 -98
- package/lib/cardNewsPlannerClient.ts +114 -0
- package/lib/cardNewsPlannerPrompt.js +56 -56
- package/lib/cardNewsPlannerPrompt.ts +60 -0
- package/lib/cardNewsPlannerSchema.js +231 -223
- package/lib/cardNewsPlannerSchema.ts +259 -0
- package/lib/cardNewsRoleTemplateStore.js +39 -41
- package/lib/cardNewsRoleTemplateStore.ts +47 -0
- package/lib/cardNewsTemplateStore.js +171 -175
- package/lib/cardNewsTemplateStore.ts +210 -0
- package/lib/codexDetect.js +44 -47
- package/lib/codexDetect.ts +69 -0
- package/lib/comfyBridge.js +164 -184
- package/lib/comfyBridge.ts +214 -0
- package/lib/db.js +41 -51
- package/lib/db.ts +166 -0
- package/lib/errorClassify.js +62 -78
- package/lib/errorClassify.ts +100 -0
- package/lib/generationErrors.js +140 -103
- package/lib/generationErrors.ts +125 -0
- package/lib/historyList.js +149 -147
- package/lib/historyList.ts +164 -0
- package/lib/imageMetadata.js +86 -89
- package/lib/imageMetadata.ts +111 -0
- package/lib/imageMetadataStore.js +46 -51
- package/lib/imageMetadataStore.ts +67 -0
- package/lib/imageModels.js +38 -45
- package/lib/imageModels.ts +52 -0
- package/lib/inflight.js +131 -150
- package/lib/inflight.ts +204 -0
- package/lib/localImportStore.js +105 -0
- package/lib/localImportStore.ts +111 -0
- package/lib/logger.js +105 -112
- package/lib/logger.ts +150 -0
- package/lib/nodeStore.js +65 -64
- package/lib/nodeStore.ts +81 -0
- package/lib/oauthLauncher.js +61 -59
- package/lib/oauthLauncher.ts +64 -0
- package/lib/oauthNormalize.js +15 -19
- package/lib/oauthNormalize.ts +30 -0
- package/lib/oauthProxy.js +834 -832
- package/lib/oauthProxy.ts +995 -0
- package/lib/openDirectory.js +41 -40
- package/lib/openDirectory.ts +45 -0
- package/lib/pngInfo.js +18 -20
- package/lib/pngInfo.ts +26 -0
- package/lib/promptImport/curatedSources.js +135 -0
- package/lib/promptImport/curatedSources.ts +139 -0
- package/lib/promptImport/discoveryRegistry.js +218 -0
- package/lib/promptImport/discoveryRegistry.ts +236 -0
- package/lib/promptImport/errors.js +10 -10
- package/lib/promptImport/errors.ts +18 -0
- package/lib/promptImport/githubDiscovery.js +238 -0
- package/lib/promptImport/githubDiscovery.ts +248 -0
- package/lib/promptImport/githubFolder.js +302 -0
- package/lib/promptImport/githubFolder.ts +308 -0
- package/lib/promptImport/githubSource.js +194 -171
- package/lib/promptImport/githubSource.ts +239 -0
- package/lib/promptImport/gptImageHints.js +61 -0
- package/lib/promptImport/gptImageHints.ts +68 -0
- package/lib/promptImport/parsePromptCandidates.js +110 -112
- package/lib/promptImport/parsePromptCandidates.ts +153 -0
- package/lib/promptImport/promptIndex.js +230 -0
- package/lib/promptImport/promptIndex.ts +248 -0
- package/lib/promptImport/rankPromptCandidates.js +52 -0
- package/lib/promptImport/rankPromptCandidates.ts +49 -0
- package/lib/providerOptions.js +31 -0
- package/lib/providerOptions.ts +41 -0
- package/lib/referenceImageCompress.js +51 -62
- package/lib/referenceImageCompress.ts +75 -0
- package/lib/refs.js +93 -81
- package/lib/refs.ts +117 -0
- package/lib/requestLogger.js +32 -38
- package/lib/requestLogger.ts +48 -0
- package/lib/responsesImageAdapter.js +351 -0
- package/lib/responsesImageAdapter.ts +352 -0
- package/lib/runtimePorts.js +71 -73
- package/lib/runtimePorts.ts +93 -0
- package/lib/sessionStore.js +179 -230
- package/lib/sessionStore.ts +272 -0
- package/lib/storageMigration.js +247 -245
- package/lib/storageMigration.ts +284 -0
- package/lib/styleSheet.js +86 -90
- package/lib/styleSheet.ts +128 -0
- package/lib/systemTrash.js +18 -0
- package/lib/systemTrash.ts +20 -0
- package/package.json +26 -10
- package/routes/annotations.js +76 -79
- package/routes/annotations.ts +95 -0
- package/routes/canvasVersions.js +50 -54
- package/routes/canvasVersions.ts +64 -0
- package/routes/cardNews.js +158 -171
- package/routes/cardNews.ts +183 -0
- package/routes/comfy.js +23 -31
- package/routes/comfy.ts +39 -0
- package/routes/edit.js +183 -214
- package/routes/edit.ts +230 -0
- package/routes/generate.js +269 -291
- package/routes/generate.ts +309 -0
- package/routes/health.js +102 -107
- package/routes/health.ts +114 -0
- package/routes/history.js +136 -144
- package/routes/history.ts +153 -0
- package/routes/imageImport.js +33 -0
- package/routes/imageImport.ts +33 -0
- package/routes/index.js +18 -16
- package/routes/index.ts +35 -0
- package/routes/metadata.js +60 -64
- package/routes/metadata.ts +71 -0
- package/routes/multimode.js +228 -263
- package/routes/multimode.ts +280 -0
- package/routes/nodes.js +378 -424
- package/routes/nodes.ts +455 -0
- package/routes/promptImport.js +291 -152
- package/routes/promptImport.ts +354 -0
- package/routes/prompts.js +333 -360
- package/routes/prompts.ts +379 -0
- package/routes/sessions.js +277 -285
- package/routes/sessions.ts +292 -0
- package/routes/storage.js +29 -31
- package/routes/storage.ts +39 -0
- package/server.js +189 -196
- package/server.ts +235 -0
- package/ui/dist/.vite/manifest.json +101 -0
- package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
- package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
- package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
- package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
- package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
- package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
- package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
- package/ui/dist/assets/index-C9cXwiWE.js +25 -0
- package/ui/dist/assets/index-CGMIkZXn.css +1 -0
- package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
- package/ui/dist/index.html +6 -3
- package/assets/screenshot.png +0 -0
- package/assets/screenshots/classic-generate-light.png +0 -0
- package/assets/screenshots/node-graph-branching.png +0 -0
- package/assets/screenshots/settings-oauth-generation.png +0 -0
- package/assets/screenshots/settings-workspace.png +0 -0
- package/assets/screenshots/style-sheet-editor.png +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
- package/ui/dist/assets/index-DARPdT4Q.css +0 -1
- package/ui/dist/assets/index-ht80GMq4.js +0 -31
- package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
package/lib/codexDetect.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
}
|
package/lib/comfyBridge.js
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
20
|
+
return new ComfyBridgeError(code, message, status);
|
|
25
21
|
}
|
|
26
|
-
|
|
27
22
|
export function isComfyBridgeError(error) {
|
|
28
|
-
|
|
23
|
+
return error instanceof ComfyBridgeError;
|
|
29
24
|
}
|
|
30
|
-
|
|
31
25
|
export function normalizeComfyOrigin(rawUrl) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
97
|
-
|
|
85
|
+
const rel = relative(parent, candidate);
|
|
86
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
98
87
|
}
|
|
99
|
-
|
|
100
88
|
function sniffImage(buffer) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
buffer
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
}
|