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
@@ -0,0 +1,45 @@
1
+ import { parseArgs } from "../lib/args.js";
2
+ import { resolveServer, request } from "../lib/client.js";
3
+ import { fileToDataUri } from "../lib/files.js";
4
+ import { out, die, json, exitCodeForError } from "../lib/output.js";
5
+ const SPEC = {
6
+ flags: {
7
+ json: { type: "boolean" },
8
+ server: { type: "string" },
9
+ help: { short: "h", type: "boolean" },
10
+ },
11
+ };
12
+ const HELP = `
13
+ ima2 metadata <imagefile> [--json]
14
+
15
+ Read embedded metadata from any local image file.
16
+ POSTs { dataUrl } to /api/metadata/read.
17
+ `;
18
+ export default async function metadataCmd(argv) {
19
+ const args = parseArgs(argv, SPEC);
20
+ if (args.help) {
21
+ out(HELP);
22
+ return;
23
+ }
24
+ const file = args.positional[0];
25
+ if (!file)
26
+ die(2, "image file required");
27
+ const dataUrl = await fileToDataUri(file);
28
+ let server;
29
+ try {
30
+ server = await resolveServer({ serverFlag: args.server });
31
+ }
32
+ catch (e) {
33
+ die(exitCodeForError(e), e.message);
34
+ throw e;
35
+ }
36
+ const resp = await request(server.base, "/api/metadata/read", {
37
+ method: "POST",
38
+ body: { dataUrl },
39
+ }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
40
+ if (args.json) {
41
+ json(resp);
42
+ }
43
+ else
44
+ out(JSON.stringify(resp, null, 2));
45
+ }
@@ -0,0 +1,36 @@
1
+ import { parseArgs } from "../lib/args.js";
2
+ import { resolveServer, request } from "../lib/client.js";
3
+ import { fileToDataUri } from "../lib/files.js";
4
+ import { out, die, json, exitCodeForError } from "../lib/output.js";
5
+
6
+ const SPEC = {
7
+ flags: {
8
+ json: { type: "boolean" },
9
+ server: { type: "string" },
10
+ help: { short: "h", type: "boolean" },
11
+ },
12
+ };
13
+
14
+ const HELP = `
15
+ ima2 metadata <imagefile> [--json]
16
+
17
+ Read embedded metadata from any local image file.
18
+ POSTs { dataUrl } to /api/metadata/read.
19
+ `;
20
+
21
+ export default async function metadataCmd(argv) {
22
+ const args = parseArgs(argv, SPEC);
23
+ if (args.help) { out(HELP); return; }
24
+ const file = args.positional[0];
25
+ if (!file) die(2, "image file required");
26
+ const dataUrl = await fileToDataUri(file);
27
+ let server;
28
+ try { server = await resolveServer({ serverFlag: args.server }); }
29
+ catch (e: any) { die(exitCodeForError(e), e.message); throw e; }
30
+ const resp = await request(server.base, "/api/metadata/read", {
31
+ method: "POST",
32
+ body: { dataUrl },
33
+ }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
34
+ if (args.json) { json(resp); }
35
+ else out(JSON.stringify(resp, null, 2));
36
+ }
@@ -0,0 +1,159 @@
1
+ import { parseArgs } from "../lib/args.js";
2
+ import { resolveServer } from "../lib/client.js";
3
+ import { streamSse } from "../lib/sse.js";
4
+ import { dataUriToFile, defaultOutName } from "../lib/files.js";
5
+ import { out, die, color, json, exitCodeForError } from "../lib/output.js";
6
+ import { config } from "../../config.js";
7
+ const SPEC = {
8
+ flags: {
9
+ quality: { short: "q", type: "string", default: "low" },
10
+ size: { short: "s", type: "string", default: "1024x1024" },
11
+ "max-images": { type: "string", default: "4" },
12
+ out: { short: "o", type: "string" },
13
+ "out-dir": { short: "d", type: "string" },
14
+ json: { type: "boolean" },
15
+ timeout: { type: "string", default: "600" },
16
+ server: { type: "string" },
17
+ model: { type: "string" },
18
+ "reasoning-effort": { type: "string" },
19
+ "web-search": { type: "boolean" },
20
+ "no-web-search": { type: "boolean" },
21
+ moderation: { type: "string", default: "low" },
22
+ session: { type: "string" },
23
+ "show-partial": { type: "boolean" },
24
+ help: { short: "h", type: "boolean" },
25
+ },
26
+ };
27
+ const HELP = `
28
+ ima2 multimode <prompt...> [options]
29
+
30
+ Stream multi-image generation via SSE (phase / partial / image / done / error).
31
+
32
+ Options:
33
+ -q, --quality <low|medium|high> Default: low
34
+ -s, --size <WxH> Default: 1024x1024
35
+ --max-images <1..8> Default: 4
36
+ -o, --out <file> First image (implies --max-images 1)
37
+ -d, --out-dir <dir> Output dir for multiple images
38
+ --json
39
+ --model <gpt-5.5|gpt-5.4|gpt-5.4-mini>
40
+ --reasoning-effort <none|low|medium|high|xhigh>
41
+ --web-search / --no-web-search
42
+ --moderation <auto|low>
43
+ --session <id>
44
+ --show-partial Print [partial #N received] notices
45
+ --timeout <sec> Default: 600
46
+ `;
47
+ export default async function multimodeCmd(argv) {
48
+ const args = parseArgs(argv, SPEC);
49
+ if (args.help) {
50
+ out(HELP);
51
+ return;
52
+ }
53
+ const prompt = args.positional.join(" ");
54
+ if (!prompt)
55
+ die(2, "prompt required");
56
+ const VALID_REASONING = new Set(["none", "low", "medium", "high", "xhigh"]);
57
+ if (args["reasoning-effort"] && !VALID_REASONING.has(args["reasoning-effort"])) {
58
+ die(2, "--reasoning-effort must be one of: none, low, medium, high, xhigh");
59
+ }
60
+ if (args["web-search"] && args["no-web-search"]) {
61
+ die(2, "--web-search and --no-web-search are mutually exclusive");
62
+ }
63
+ let server;
64
+ try {
65
+ server = await resolveServer({ serverFlag: args.server });
66
+ }
67
+ catch (e) {
68
+ die(exitCodeForError(e), e.message);
69
+ throw e;
70
+ }
71
+ const maxImages = Math.max(1, Math.min(8, parseInt(args["max-images"]) || 4));
72
+ const body = {
73
+ prompt,
74
+ quality: args.quality,
75
+ size: args.size,
76
+ maxImages,
77
+ moderation: args.moderation,
78
+ sessionId: args.session,
79
+ };
80
+ if (args.model)
81
+ body.model = args.model;
82
+ if (args["reasoning-effort"])
83
+ body.reasoningEffort = args["reasoning-effort"];
84
+ if (args["no-web-search"])
85
+ body.webSearchEnabled = false;
86
+ else if (args["web-search"])
87
+ body.webSearchEnabled = true;
88
+ const ac = new AbortController();
89
+ const onSig = () => { ac.abort(); process.exit(130); };
90
+ process.once("SIGINT", onSig);
91
+ process.once("SIGTERM", onSig);
92
+ const url = `${server.base}/api/generate/multimode`;
93
+ const images = [];
94
+ let doneInfo = null;
95
+ try {
96
+ for await (const ev of streamSse(url, { body, signal: ac.signal })) {
97
+ switch (ev.event) {
98
+ case "phase":
99
+ if (!args.json)
100
+ out(color.dim(`[phase] ${ev.data.phase} (max ${ev.data.maxImages ?? maxImages})`));
101
+ break;
102
+ case "partial":
103
+ if (args["show-partial"] && !args.json) {
104
+ const len = (ev.data.image || "").length;
105
+ out(color.dim(`[partial #${ev.data.index}] (${len}B preview)`));
106
+ }
107
+ break;
108
+ case "image":
109
+ images.push(ev.data);
110
+ if (!args.json)
111
+ out(color.green(`✓ image ${images.length}`));
112
+ break;
113
+ case "done":
114
+ doneInfo = ev.data;
115
+ break;
116
+ case "error":
117
+ die(1, `multimode error: ${ev.data.error || ev.data}${ev.data.code ? ` (${ev.data.code})` : ""}`);
118
+ }
119
+ }
120
+ }
121
+ catch (e) {
122
+ if (e.name === "AbortError")
123
+ return;
124
+ die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`);
125
+ }
126
+ // Save images
127
+ const outDir = args["out-dir"] || null;
128
+ const explicitOut = args.out || null;
129
+ if (explicitOut && images.length > 1) {
130
+ if (!args.json)
131
+ out(color.yellow(`(received ${images.length} images, --out only saves first)`));
132
+ }
133
+ const savedPaths = [];
134
+ for (let i = 0; i < images.length; i++) {
135
+ const im = images[i];
136
+ if (!im.image)
137
+ continue;
138
+ let target;
139
+ if (explicitOut && i === 0)
140
+ target = explicitOut;
141
+ else if (outDir)
142
+ target = `${outDir}/${defaultOutName(i, images.length)}`;
143
+ else
144
+ target = `${config.storage.generatedDir}/${defaultOutName(i, images.length)}`;
145
+ if (target) {
146
+ await dataUriToFile(im.image, target);
147
+ savedPaths.push(target);
148
+ }
149
+ }
150
+ if (args.json) {
151
+ json({ ok: true, requestId: doneInfo?.requestId, returned: doneInfo?.returned, paths: savedPaths });
152
+ }
153
+ else {
154
+ for (const p of savedPaths)
155
+ out(color.green("✓ ") + p);
156
+ if (doneInfo)
157
+ out(color.dim(`done: ${doneInfo.returned}/${doneInfo.requested ?? maxImages}`));
158
+ }
159
+ }
@@ -0,0 +1,146 @@
1
+ import { writeFile } from "fs/promises";
2
+ import { parseArgs } from "../lib/args.js";
3
+ import { resolveServer } from "../lib/client.js";
4
+ import { streamSse } from "../lib/sse.js";
5
+ import { dataUriToFile, defaultOutName } from "../lib/files.js";
6
+ import { out, die, color, json, exitCodeForError } from "../lib/output.js";
7
+ import { config } from "../../config.js";
8
+
9
+ const SPEC = {
10
+ flags: {
11
+ quality: { short: "q", type: "string", default: "low" },
12
+ size: { short: "s", type: "string", default: "1024x1024" },
13
+ "max-images": { type: "string", default: "4" },
14
+ out: { short: "o", type: "string" },
15
+ "out-dir": { short: "d", type: "string" },
16
+ json: { type: "boolean" },
17
+ timeout: { type: "string", default: "600" },
18
+ server: { type: "string" },
19
+ model: { type: "string" },
20
+ "reasoning-effort": { type: "string" },
21
+ "web-search": { type: "boolean" },
22
+ "no-web-search": { type: "boolean" },
23
+ moderation: { type: "string", default: "low" },
24
+ session: { type: "string" },
25
+ "show-partial": { type: "boolean" },
26
+ help: { short: "h", type: "boolean" },
27
+ },
28
+ };
29
+
30
+ const HELP = `
31
+ ima2 multimode <prompt...> [options]
32
+
33
+ Stream multi-image generation via SSE (phase / partial / image / done / error).
34
+
35
+ Options:
36
+ -q, --quality <low|medium|high> Default: low
37
+ -s, --size <WxH> Default: 1024x1024
38
+ --max-images <1..8> Default: 4
39
+ -o, --out <file> First image (implies --max-images 1)
40
+ -d, --out-dir <dir> Output dir for multiple images
41
+ --json
42
+ --model <gpt-5.5|gpt-5.4|gpt-5.4-mini>
43
+ --reasoning-effort <none|low|medium|high|xhigh>
44
+ --web-search / --no-web-search
45
+ --moderation <auto|low>
46
+ --session <id>
47
+ --show-partial Print [partial #N received] notices
48
+ --timeout <sec> Default: 600
49
+ `;
50
+
51
+ export default async function multimodeCmd(argv) {
52
+ const args = parseArgs(argv, SPEC);
53
+ if (args.help) { out(HELP); return; }
54
+ const prompt = args.positional.join(" ");
55
+ if (!prompt) die(2, "prompt required");
56
+
57
+ const VALID_REASONING = new Set(["none", "low", "medium", "high", "xhigh"]);
58
+ if (args["reasoning-effort"] && !VALID_REASONING.has(args["reasoning-effort"])) {
59
+ die(2, "--reasoning-effort must be one of: none, low, medium, high, xhigh");
60
+ }
61
+ if (args["web-search"] && args["no-web-search"]) {
62
+ die(2, "--web-search and --no-web-search are mutually exclusive");
63
+ }
64
+
65
+ let server;
66
+ try { server = await resolveServer({ serverFlag: args.server }); }
67
+ catch (e: any) { die(exitCodeForError(e), e.message); throw e; }
68
+
69
+ const maxImages = Math.max(1, Math.min(8, parseInt(args["max-images"]) || 4));
70
+ const body: any = {
71
+ prompt,
72
+ quality: args.quality,
73
+ size: args.size,
74
+ maxImages,
75
+ moderation: args.moderation,
76
+ sessionId: args.session,
77
+ };
78
+ if (args.model) body.model = args.model;
79
+ if (args["reasoning-effort"]) body.reasoningEffort = args["reasoning-effort"];
80
+ if (args["no-web-search"]) body.webSearchEnabled = false;
81
+ else if (args["web-search"]) body.webSearchEnabled = true;
82
+
83
+ const ac = new AbortController();
84
+ const onSig = () => { ac.abort(); process.exit(130); };
85
+ process.once("SIGINT", onSig);
86
+ process.once("SIGTERM", onSig);
87
+
88
+ const url = `${server.base}/api/generate/multimode`;
89
+ const images: any[] = [];
90
+ let doneInfo: any = null;
91
+ try {
92
+ for await (const ev of streamSse(url, { body, signal: ac.signal })) {
93
+ switch (ev.event) {
94
+ case "phase":
95
+ if (!args.json) out(color.dim(`[phase] ${ev.data.phase} (max ${ev.data.maxImages ?? maxImages})`));
96
+ break;
97
+ case "partial":
98
+ if (args["show-partial"] && !args.json) {
99
+ const len = (ev.data.image || "").length;
100
+ out(color.dim(`[partial #${ev.data.index}] (${len}B preview)`));
101
+ }
102
+ break;
103
+ case "image":
104
+ images.push(ev.data);
105
+ if (!args.json) out(color.green(`✓ image ${images.length}`));
106
+ break;
107
+ case "done":
108
+ doneInfo = ev.data;
109
+ break;
110
+ case "error":
111
+ die(1, `multimode error: ${ev.data.error || ev.data}${ev.data.code ? ` (${ev.data.code})` : ""}`);
112
+ }
113
+ }
114
+ } catch (e: any) {
115
+ if (e.name === "AbortError") return;
116
+ die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`);
117
+ }
118
+
119
+ // Save images
120
+ const outDir = args["out-dir"] || null;
121
+ const explicitOut = args.out || null;
122
+ if (explicitOut && images.length > 1) {
123
+ if (!args.json) out(color.yellow(`(received ${images.length} images, --out only saves first)`));
124
+ }
125
+
126
+ const savedPaths: string[] = [];
127
+ for (let i = 0; i < images.length; i++) {
128
+ const im = images[i];
129
+ if (!im.image) continue;
130
+ let target;
131
+ if (explicitOut && i === 0) target = explicitOut;
132
+ else if (outDir) target = `${outDir}/${defaultOutName(i, images.length)}`;
133
+ else target = `${config.storage.generatedDir}/${defaultOutName(i, images.length)}`;
134
+ if (target) {
135
+ await dataUriToFile(im.image, target);
136
+ savedPaths.push(target);
137
+ }
138
+ }
139
+
140
+ if (args.json) {
141
+ json({ ok: true, requestId: doneInfo?.requestId, returned: doneInfo?.returned, paths: savedPaths });
142
+ } else {
143
+ for (const p of savedPaths) out(color.green("✓ ") + p);
144
+ if (doneInfo) out(color.dim(`done: ${doneInfo.returned}/${doneInfo.requested ?? maxImages}`));
145
+ }
146
+ }
@@ -0,0 +1,176 @@
1
+ import { parseArgs } from "../lib/args.js";
2
+ import { resolveServer, request } from "../lib/client.js";
3
+ import { streamSse } from "../lib/sse.js";
4
+ import { fileToDataUri, dataUriToFile, defaultOutName } from "../lib/files.js";
5
+ import { out, die, color, json, exitCodeForError } from "../lib/output.js";
6
+ import { config } from "../../config.js";
7
+ const HELP = `
8
+ ima2 node <subcommand> [options]
9
+
10
+ Subcommands:
11
+ generate <prompt...> [--parent <nodeId>] [--ref <file>...] [--no-stream] [...gen-style flags]
12
+ show <nodeId> [--json]
13
+ `;
14
+ const GEN_FLAGS = {
15
+ quality: { short: "q", type: "string", default: "low" },
16
+ size: { short: "s", type: "string", default: "1024x1024" },
17
+ count: { short: "n", type: "string", default: "1" },
18
+ ref: { type: "string", repeatable: true },
19
+ out: { short: "o", type: "string" },
20
+ json: { type: "boolean" },
21
+ timeout: { type: "string", default: "600" },
22
+ server: { type: "string" },
23
+ model: { type: "string" },
24
+ parent: { type: "string" },
25
+ "reasoning-effort": { type: "string" },
26
+ "web-search": { type: "boolean" },
27
+ "no-web-search": { type: "boolean" },
28
+ moderation: { type: "string", default: "low" },
29
+ session: { type: "string" },
30
+ "no-stream": { type: "boolean" },
31
+ help: { short: "h", type: "boolean" },
32
+ };
33
+ const SHOW_FLAGS = {
34
+ json: { type: "boolean" },
35
+ server: { type: "string" },
36
+ help: { short: "h", type: "boolean" },
37
+ };
38
+ async function getServer(args) {
39
+ try {
40
+ return await resolveServer({ serverFlag: args.server });
41
+ }
42
+ catch (e) {
43
+ die(exitCodeForError(e), e.message);
44
+ throw e;
45
+ }
46
+ }
47
+ async function generateSub(argv) {
48
+ const args = parseArgs(argv, { flags: GEN_FLAGS });
49
+ if (args.help) {
50
+ out(HELP);
51
+ return;
52
+ }
53
+ const prompt = args.positional.join(" ");
54
+ if (!prompt)
55
+ die(2, "prompt required");
56
+ const refs = args.ref || [];
57
+ const VALID_REASONING = new Set(["none", "low", "medium", "high", "xhigh"]);
58
+ if (args["reasoning-effort"] && !VALID_REASONING.has(args["reasoning-effort"])) {
59
+ die(2, "--reasoning-effort must be one of: none, low, medium, high, xhigh");
60
+ }
61
+ if (args["web-search"] && args["no-web-search"]) {
62
+ die(2, "--web-search and --no-web-search are mutually exclusive");
63
+ }
64
+ const references = await Promise.all(refs.map((p) => fileToDataUri(p)));
65
+ const server = await getServer(args);
66
+ const body = {
67
+ prompt,
68
+ quality: args.quality,
69
+ size: args.size,
70
+ n: Math.max(1, Math.min(8, parseInt(args.count) || 1)),
71
+ references,
72
+ moderation: args.moderation,
73
+ sessionId: args.session,
74
+ };
75
+ if (args.model)
76
+ body.model = args.model;
77
+ if (args.parent)
78
+ body.parentNodeId = args.parent;
79
+ if (args["reasoning-effort"])
80
+ body.reasoningEffort = args["reasoning-effort"];
81
+ if (args["no-web-search"])
82
+ body.webSearchEnabled = false;
83
+ else if (args["web-search"])
84
+ body.webSearchEnabled = true;
85
+ if (args["no-stream"]) {
86
+ const resp = await request(server.base, "/api/node/generate", {
87
+ method: "POST",
88
+ body,
89
+ timeoutMs: (parseInt(args.timeout) || 600) * 1000,
90
+ }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
91
+ if (args.json) {
92
+ json(resp);
93
+ return;
94
+ }
95
+ out(color.green("✓ node ") + (resp?.node?.id || "(no id)"));
96
+ return;
97
+ }
98
+ const ac = new AbortController();
99
+ const onSig = () => { ac.abort(); process.exit(130); };
100
+ process.once("SIGINT", onSig);
101
+ process.once("SIGTERM", onSig);
102
+ const url = `${server.base}/api/node/generate`;
103
+ const images = [];
104
+ let doneInfo = null;
105
+ try {
106
+ for await (const ev of streamSse(url, { body, signal: ac.signal })) {
107
+ switch (ev.event) {
108
+ case "phase":
109
+ if (!args.json)
110
+ out(color.dim(`[phase] ${ev.data.phase || ev.data}`));
111
+ break;
112
+ case "partial":
113
+ if (!args.json)
114
+ out(color.dim(`[partial #${ev.data.index ?? "?"}]`));
115
+ break;
116
+ case "image":
117
+ images.push(ev.data);
118
+ if (!args.json)
119
+ out(color.green(`✓ image ${images.length}`));
120
+ break;
121
+ case "done":
122
+ doneInfo = ev.data;
123
+ break;
124
+ case "error":
125
+ die(1, `node generate error: ${ev.data.error || ev.data}${ev.data.code ? ` (${ev.data.code})` : ""}`);
126
+ }
127
+ }
128
+ }
129
+ catch (e) {
130
+ if (e.name === "AbortError")
131
+ return;
132
+ die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`);
133
+ }
134
+ const savedPaths = [];
135
+ for (let i = 0; i < images.length; i++) {
136
+ const im = images[i];
137
+ if (!im.image)
138
+ continue;
139
+ const target = args.out && i === 0
140
+ ? args.out
141
+ : `${config.storage.generatedDir}/${defaultOutName(i, images.length)}`;
142
+ await dataUriToFile(im.image, target);
143
+ savedPaths.push(target);
144
+ }
145
+ if (args.json) {
146
+ json({ ok: true, paths: savedPaths, doneInfo });
147
+ }
148
+ else {
149
+ for (const p of savedPaths)
150
+ out(color.green("✓ ") + p);
151
+ }
152
+ }
153
+ async function showSub(argv) {
154
+ const args = parseArgs(argv, { flags: SHOW_FLAGS });
155
+ const id = args.positional[0];
156
+ if (!id)
157
+ die(2, "nodeId required");
158
+ const server = await getServer(args);
159
+ const resp = await request(server.base, `/api/node/${encodeURIComponent(id)}`).catch((e) => die(exitCodeForError(e), e.message));
160
+ json(resp);
161
+ }
162
+ const SUB = {
163
+ generate: generateSub,
164
+ show: showSub,
165
+ };
166
+ export default async function nodeCmd(argv) {
167
+ const sub = argv[0];
168
+ if (!sub || sub === "--help" || sub === "-h") {
169
+ out(HELP);
170
+ return;
171
+ }
172
+ const handler = SUB[sub];
173
+ if (!handler)
174
+ die(2, `unknown subcommand: ${sub}\n${HELP}`);
175
+ return handler(argv.slice(1));
176
+ }