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
package/bin/ima2.ts ADDED
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from "readline/promises";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
+ import { join, dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { createRequire } from "module";
7
+ import { spawn, execSync } from "child_process";
8
+ import { networkInterfaces, homedir } from "os";
9
+ import { openUrl, resolveBin } from "./lib/platform.js";
10
+ import { maybePromptGithubStar } from "./lib/star-prompt.js";
11
+ import { buildStorageDoctorLines } from "./lib/storage-doctor.js";
12
+ import { detectCodexAuth } from "../lib/codexDetect.js";
13
+ import { config as runtimeConfig } from "../config.js";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const ROOT = join(__dirname, "..");
17
+ const HOME = homedir();
18
+ const requireFromRoot = createRequire(join(ROOT, "package.json"));
19
+ // Config lives under runtimeConfig.storage.configDir (honors IMA2_CONFIG_DIR).
20
+ // Legacy installs that stored config at <packageRoot>/.ima2/config.json will be
21
+ // migrated on first write.
22
+ const CONFIG_DIR = runtimeConfig.storage.configDir;
23
+ const CONFIG_FILE = runtimeConfig.storage.configFile;
24
+ const LEGACY_CONFIG_FILE = join(ROOT, ".ima2", "config.json");
25
+
26
+ // Load package.json for version
27
+ let pkg = { version: "?", name: "ima2-gen" };
28
+ try {
29
+ pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
30
+ } catch {}
31
+
32
+ function loadConfig() {
33
+ if (existsSync(CONFIG_FILE)) {
34
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
35
+ }
36
+ // One-time read from legacy location so users who set up on <1.0.4 don't lose auth.
37
+ if (existsSync(LEGACY_CONFIG_FILE)) {
38
+ try { return JSON.parse(readFileSync(LEGACY_CONFIG_FILE, "utf-8")); } catch {}
39
+ }
40
+ return {};
41
+ }
42
+
43
+ function saveConfig(config) {
44
+ mkdirSync(CONFIG_DIR, { recursive: true });
45
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
46
+ }
47
+
48
+ function loadAdvertisement() {
49
+ const p = runtimeConfig.storage.advertiseFile;
50
+ if (!existsSync(p)) return null;
51
+ try {
52
+ return JSON.parse(readFileSync(p, "utf-8"));
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function advertisedServerUrl() {
59
+ const adv = loadAdvertisement();
60
+ return adv?.backend?.url || adv?.url || (adv?.port ? `http://localhost:${adv.port}` : null);
61
+ }
62
+
63
+ function missingRuntimeDeps() {
64
+ const deps = ["express", "better-sqlite3", "openai", "openai-oauth"];
65
+ return deps.filter((dep) => {
66
+ try {
67
+ requireFromRoot.resolve(dep);
68
+ return false;
69
+ } catch {
70
+ return true;
71
+ }
72
+ });
73
+ }
74
+
75
+ async function setup() {
76
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
77
+
78
+ console.log("\n ima2-gen — GPT Image 2 Generator\n");
79
+ console.log(" Choose authentication method:\n");
80
+ console.log(" 1) API Key — paste your OpenAI API key (paid)");
81
+ console.log(" 2) OAuth — login with ChatGPT account (free)\n");
82
+
83
+ const choice = await rl.question(" Enter 1 or 2: ");
84
+ const config = loadConfig();
85
+
86
+ if (choice.trim() === "1") {
87
+ const key = await rl.question(" OpenAI API Key: ");
88
+ if (!key.startsWith("sk-")) {
89
+ console.log(" Invalid API key format. Expected sk-...");
90
+ rl.close();
91
+ process.exit(1);
92
+ }
93
+ config.provider = "api";
94
+ config.apiKey = key.trim();
95
+ saveConfig(config);
96
+ console.log("\n API key saved. Starting server...\n");
97
+ } else {
98
+ config.provider = "oauth";
99
+ delete config.apiKey;
100
+ saveConfig(config);
101
+ console.log("\n Starting OAuth login...\n");
102
+
103
+ // Check if codex auth exists (file OR keyring via `codex login status`)
104
+ const auth = detectCodexAuth();
105
+ const hasAuth = auth.authed;
106
+
107
+ if (!hasAuth) {
108
+ if (auth.platform === "win32") {
109
+ console.log(
110
+ " Windows note: OpenAI Codex has no documented native installer. Use WSL2 for best results.\n",
111
+ );
112
+ }
113
+ console.log(" Running 'codex login' — follow the browser prompt.\n");
114
+ try {
115
+ execSync(`${resolveBin("npx")} @openai/codex login`, { stdio: "inherit" });
116
+ } catch {
117
+ console.log("\n Login failed or cancelled. You can retry with 'ima2 serve'.\n");
118
+ rl.close();
119
+ process.exit(1);
120
+ }
121
+ } else {
122
+ const how = auth.probe === "authed" ? "codex CLI" : "auth file";
123
+ console.log(` Existing OAuth session found (${how}).\n`);
124
+ }
125
+
126
+ saveConfig(config);
127
+ console.log(" OAuth configured. Starting server...\n");
128
+ }
129
+
130
+ rl.close();
131
+ return config;
132
+ }
133
+
134
+ async function serve(serveArgs = []) {
135
+ try {
136
+ await maybePromptGithubStar();
137
+ } catch (err) {
138
+ console.error(`[ima2] Star prompt skipped: ${err?.message || err}`);
139
+ }
140
+
141
+ let config = loadConfig();
142
+
143
+ if (!config.provider) {
144
+ config = await setup();
145
+ }
146
+
147
+ // Ensure ui/dist exists — if missing, auto-build (dev) or error (installed pkg)
148
+ const distIndex = join(ROOT, "ui", "dist", "index.html");
149
+ if (!existsSync(distIndex)) {
150
+ const hasUiSrc = existsSync(join(ROOT, "ui", "package.json"));
151
+ if (hasUiSrc) {
152
+ console.log("\n ui/dist missing — running 'npm run build' first...\n");
153
+ try {
154
+ execSync(`${resolveBin("npm")} run build`, { stdio: "inherit", cwd: ROOT });
155
+ } catch {
156
+ console.log("\n Build failed. Try: cd ui && npm install && npm run build\n");
157
+ process.exit(1);
158
+ }
159
+ } else {
160
+ console.log("\n ui/dist not found and ui/ source is missing.");
161
+ console.log(" This installation appears broken. Reinstall: npm i -g ima2-gen\n");
162
+ process.exit(1);
163
+ }
164
+ }
165
+
166
+ const env = { ...process.env };
167
+ const serveDev = serveArgs.includes("--dev");
168
+ if (serveDev) {
169
+ env.IMA2_DEV = "1";
170
+ env.IMA2_LOG_LEVEL = env.IMA2_LOG_LEVEL || "debug";
171
+ }
172
+
173
+ if (config.provider === "api" && config.apiKey) {
174
+ env.OPENAI_API_KEY = config.apiKey;
175
+ }
176
+
177
+ const serverPath = join(ROOT, "server.js");
178
+ const child = spawn("node", [serverPath], {
179
+ stdio: "inherit",
180
+ env,
181
+ cwd: ROOT,
182
+ });
183
+
184
+ child.on("exit", (code) => process.exit(code));
185
+
186
+ process.on("SIGINT", () => child.kill("SIGINT"));
187
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
188
+ }
189
+
190
+ async function showStatus() {
191
+ const config = loadConfig();
192
+ console.log(`\n ${pkg.name} v${pkg.version}\n`);
193
+ console.log(` Config file: ${CONFIG_FILE}`);
194
+ console.log(` Exists: ${existsSync(CONFIG_FILE) ? "yes" : "no"}\n`);
195
+
196
+ if (config.provider) {
197
+ console.log(` Provider: ${config.provider}`);
198
+ if (config.provider === "api") {
199
+ const key = config.apiKey || "";
200
+ console.log(` API Key: ${key ? key.slice(0, 8) + "..." + key.slice(-4) : "not set"}`);
201
+ }
202
+ console.log("");
203
+ } else {
204
+ console.log(" Status: not configured");
205
+ console.log(" Run 'ima2 setup' to configure.\n");
206
+ }
207
+
208
+ // Check OAuth auth files + codex CLI probe
209
+ const auth = detectCodexAuth();
210
+ console.log(` OAuth sessions:`);
211
+ console.log(` ${auth.files.codex} ${auth.fileHits.codex ? "✓" : "✗"}`);
212
+ console.log(` ${auth.files.chatgpt} ${auth.fileHits.chatgpt ? "✓" : "✗"}`);
213
+ if (auth.fileHits.xdgCodex) {
214
+ console.log(` ${auth.files.xdgCodex} ✓`);
215
+ }
216
+ const probeLabel =
217
+ auth.probe === "authed" ? "✓ authed"
218
+ : auth.probe === "unauthed" ? "✗ not logged in"
219
+ : "– codex CLI not found";
220
+ console.log(` codex login status ${probeLabel}`);
221
+ if (auth.platform === "win32") {
222
+ console.log(" (Windows: no native codex installer — use WSL2)");
223
+ }
224
+ console.log("");
225
+ }
226
+
227
+ async function doctor() {
228
+ console.log(`\n ${pkg.name} v${pkg.version} — Doctor\n`);
229
+
230
+ let ok = 0;
231
+ let fail = 0;
232
+
233
+ // Node version
234
+ const nodeVersion = process.version;
235
+ const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0]);
236
+ if (nodeMajor >= 20) {
237
+ console.log(` ✓ Node.js ${nodeVersion} (>= 20)`);
238
+ ok++;
239
+ } else {
240
+ console.log(` ✗ Node.js ${nodeVersion} (requires >= 20)`);
241
+ fail++;
242
+ }
243
+
244
+ // package.json exists
245
+ if (existsSync(join(ROOT, "package.json"))) {
246
+ console.log(" ✓ package.json found");
247
+ ok++;
248
+ } else {
249
+ console.log(" ✗ package.json missing");
250
+ fail++;
251
+ }
252
+
253
+ // Runtime dependencies may be hoisted by npm, pnpm, or Yarn. Resolve the
254
+ // packages instead of requiring a package-local node_modules folder.
255
+ const missingDeps = missingRuntimeDeps();
256
+ if (missingDeps.length === 0) {
257
+ console.log(" ✓ runtime dependencies resolvable");
258
+ ok++;
259
+ } else {
260
+ console.log(` ✗ missing runtime dependencies: ${missingDeps.join(", ")}`);
261
+ fail++;
262
+ }
263
+
264
+ // .env
265
+ if (existsSync(join(ROOT, ".env"))) {
266
+ console.log(" ✓ .env file exists");
267
+ ok++;
268
+ } else {
269
+ console.log(" ⚠ .env file not found (optional — copy from .env.example)");
270
+ }
271
+
272
+ // Config
273
+ const config = loadConfig();
274
+ if (config.provider) {
275
+ console.log(` ✓ Configured: ${config.provider}`);
276
+ ok++;
277
+ } else {
278
+ console.log(" ⚠ Not configured — run 'ima2 setup'");
279
+ }
280
+
281
+ // Port availability (simple check)
282
+ const adv = loadAdvertisement();
283
+ console.log(` ℹ Preferred backend port: ${runtimeConfig.server.port}`);
284
+ if (adv?.backend || adv?.port) {
285
+ console.log(` ℹ Backend actual URL: ${adv?.backend?.url || adv?.url || `http://localhost:${adv.port}`}`);
286
+ if (adv?.oauth) {
287
+ console.log(` ℹ OAuth actual URL: ${adv.oauth.url} (${adv.oauth.status || "unknown"})`);
288
+ }
289
+ }
290
+
291
+ const storageLines = await buildStorageDoctorLines({
292
+ rootDir: ROOT,
293
+ config: runtimeConfig,
294
+ });
295
+ console.log("");
296
+ for (const line of storageLines) console.log(line);
297
+
298
+ console.log(`\n ${ok} passed, ${fail} failed\n`);
299
+ process.exit(fail > 0 ? 1 : 0);
300
+ }
301
+
302
+ function openBrowser() {
303
+ const url = advertisedServerUrl() || `http://localhost:${runtimeConfig.server.port}`;
304
+ const res = openUrl(url);
305
+ if (res.ok) {
306
+ console.log(`\n Opening ${url} ...\n`);
307
+ } else {
308
+ console.log(`\n Could not open browser. Visit: ${url}\n`);
309
+ }
310
+ }
311
+
312
+ function showHelp() {
313
+ console.log(`
314
+ ${pkg.name} v${pkg.version} — GPT Image 2 Generator
315
+
316
+ Usage: ima2 <command> [options]
317
+
318
+ Server commands:
319
+ serve [--dev] Start the image generation server
320
+ setup, login Configure API key or OAuth (interactive)
321
+ status Show current configuration status
322
+ doctor Diagnose environment and setup
323
+ open Open web UI in browser
324
+ reset Reset configuration
325
+
326
+ Client commands (require a running 'ima2 serve'):
327
+ gen <prompt> Generate image(s) from prompt (ima2 gen --help)
328
+ edit <file> Edit an existing image (ima2 edit --help)
329
+ ls List recent history (ima2 ls --help)
330
+ show <name> Show one history item (ima2 show --help)
331
+ session <sub> Session/graph CRUD (ima2 session --help)
332
+ history <sub> History write-ops (ima2 history --help)
333
+ prompt <sub> Prompt library + folders + import (ima2 prompt --help)
334
+ multimode <prompt> Multi-image SSE generation (ima2 multimode --help)
335
+ node <sub> Node-mode generate/show (ima2 node --help)
336
+ annotate <sub> Image annotations CRUD (ima2 annotate --help)
337
+ canvas-versions <sub> Canvas version save/update (ima2 canvas-versions --help)
338
+ metadata <file> Read embedded metadata
339
+ comfy <sub> ComfyUI workflow export (ima2 comfy --help)
340
+ cardnews <sub> Card News templates/jobs/export (ima2 cardnews --help)
341
+ ps List active jobs (ima2 ps --help)
342
+ cancel <id> Mark an in-flight job canceled (ima2 cancel --help)
343
+ inflight <sub> Inflight jobs (ls / rm) (ima2 inflight --help)
344
+ storage <sub> Storage status / open-dir (ima2 storage --help)
345
+ billing API usage / quota
346
+ providers Configured providers
347
+ oauth <sub> OAuth proxy status (ima2 oauth --help)
348
+ config <sub> Config get/set/ls/path/rm (ima2 config --help)
349
+ ping Ping running server / check health
350
+
351
+ Options:
352
+ -v, --version Show version
353
+ -h, --help Show help
354
+
355
+ Examples:
356
+ ima2 serve Start server
357
+ ima2 serve --dev Start with verbose server diagnostics
358
+ ima2 gen "a shiba in space" Generate from CLI
359
+ ima2 gen "merge" --ref a.png --ref b.png -q high -o out.png
360
+ ima2 ls -n 10 Last 10 generations
361
+ ima2 ping Health check
362
+ `);
363
+ }
364
+
365
+ // ── CLI ──
366
+ const args = process.argv.slice(2);
367
+ const command = args[0];
368
+
369
+ if (args.includes("-v") || args.includes("--version")) {
370
+ console.log(pkg.version);
371
+ process.exit(0);
372
+ }
373
+
374
+ if ((!command || args.includes("-h") || args.includes("--help"))
375
+ && !["gen", "edit", "ls", "show", "ps", "cancel", "session", "history", "prompt", "multimode", "node", "annotate", "canvas-versions", "metadata", "comfy", "cardnews", "inflight", "storage", "billing", "providers", "oauth", "config", "ping"].includes(command)) {
376
+ showHelp();
377
+ process.exit(command ? 0 : 1);
378
+ }
379
+
380
+ switch (command) {
381
+ case "serve":
382
+ serve(args.slice(1));
383
+ break;
384
+ case "setup":
385
+ case "login":
386
+ setup().then(() => console.log(" Done. Run 'ima2 serve' to start."));
387
+ break;
388
+ case "status":
389
+ showStatus();
390
+ break;
391
+ case "doctor":
392
+ await doctor();
393
+ break;
394
+ case "open":
395
+ openBrowser();
396
+ break;
397
+ case "reset":
398
+ if (existsSync(CONFIG_FILE)) {
399
+ writeFileSync(CONFIG_FILE, "{}");
400
+ console.log(" Config reset. Run 'ima2 serve' to reconfigure.");
401
+ } else {
402
+ console.log(" No config to reset.");
403
+ }
404
+ break;
405
+ case "gen":
406
+ case "edit":
407
+ case "ls":
408
+ case "show":
409
+ case "ps":
410
+ case "cancel":
411
+ case "session":
412
+ case "history":
413
+ case "prompt":
414
+ case "multimode":
415
+ case "node":
416
+ case "annotate":
417
+ case "canvas-versions":
418
+ case "metadata":
419
+ case "comfy":
420
+ case "cardnews":
421
+ case "config":
422
+ case "ping": {
423
+ const { setCliVersion } = await import("./lib/client.js");
424
+ setCliVersion(pkg.version);
425
+ const mod = await import(`./commands/${command}.js`);
426
+ await mod.default(args.slice(1));
427
+ break;
428
+ }
429
+ case "storage":
430
+ case "billing":
431
+ case "providers":
432
+ case "oauth":
433
+ case "inflight": {
434
+ const { setCliVersion } = await import("./lib/client.js");
435
+ setCliVersion(pkg.version);
436
+ const mod = await import("./commands/observability.js");
437
+ await mod.default([command, ...args.slice(1)]);
438
+ break;
439
+ }
440
+ default:
441
+ console.log(` Unknown command: "${command}"`);
442
+ console.log(" Run 'ima2 --help' for usage.\n");
443
+ process.exit(1);
444
+ }
package/bin/lib/args.js CHANGED
@@ -1,73 +1,82 @@
1
1
  // Tiny argv parser — no dependencies.
2
2
  // Supports: --long, --long=val, --long val, -s, -s val, repeatable flags, positional, --.
3
-
4
3
  export function parseArgs(argv, spec = {}) {
5
- const shortMap = {};
6
- for (const [name, def] of Object.entries(spec.flags || {})) {
7
- if (def.short) shortMap[def.short] = name;
8
- }
9
-
10
- const out = { positional: [], _unknown: [] };
11
- for (const [name, def] of Object.entries(spec.flags || {})) {
12
- if (def.repeatable) out[name] = [];
13
- else if ("default" in def) out[name] = def.default;
14
- }
15
-
16
- let i = 0;
17
- let doubleDashSeen = false;
18
- while (i < argv.length) {
19
- const a = argv[i];
20
- if (doubleDashSeen) {
21
- out.positional.push(a);
22
- i++;
23
- continue;
4
+ const shortMap = {};
5
+ for (const [name, def] of Object.entries(spec.flags || {})) {
6
+ if (def.short)
7
+ shortMap[def.short] = name;
24
8
  }
25
- if (a === "--") {
26
- doubleDashSeen = true;
27
- i++;
28
- continue;
9
+ const out = { positional: [], _unknown: [] };
10
+ for (const [name, def] of Object.entries(spec.flags || {})) {
11
+ if (def.repeatable)
12
+ out[name] = [];
13
+ else if ("default" in def)
14
+ out[name] = def.default;
29
15
  }
30
- if (a.startsWith("--")) {
31
- const eq = a.indexOf("=");
32
- const name = eq > -1 ? a.slice(2, eq) : a.slice(2);
33
- const def = (spec.flags || {})[name];
34
- if (!def) {
35
- out._unknown.push(a);
36
- i++;
37
- continue;
38
- }
39
- if (def.type === "boolean") {
40
- out[name] = true;
41
- i++;
42
- } else {
43
- const val = eq > -1 ? a.slice(eq + 1) : argv[i + 1];
44
- if (eq === -1) i++;
45
- if (def.repeatable) out[name].push(val);
46
- else out[name] = val;
47
- i++;
48
- }
49
- } else if (a.startsWith("-") && a.length > 1 && !/^-\d/.test(a)) {
50
- const short = a.slice(1);
51
- const name = shortMap[short];
52
- if (!name) {
53
- out._unknown.push(a);
54
- i++;
55
- continue;
56
- }
57
- const def = spec.flags[name];
58
- if (def.type === "boolean") {
59
- out[name] = true;
60
- i++;
61
- } else {
62
- const val = argv[i + 1];
63
- if (def.repeatable) out[name].push(val);
64
- else out[name] = val;
65
- i += 2;
66
- }
67
- } else {
68
- out.positional.push(a);
69
- i++;
16
+ let i = 0;
17
+ let doubleDashSeen = false;
18
+ while (i < argv.length) {
19
+ const a = argv[i];
20
+ if (doubleDashSeen) {
21
+ out.positional.push(a);
22
+ i++;
23
+ continue;
24
+ }
25
+ if (a === "--") {
26
+ doubleDashSeen = true;
27
+ i++;
28
+ continue;
29
+ }
30
+ if (a.startsWith("--")) {
31
+ const eq = a.indexOf("=");
32
+ const name = eq > -1 ? a.slice(2, eq) : a.slice(2);
33
+ const def = (spec.flags || {})[name];
34
+ if (!def) {
35
+ out._unknown.push(a);
36
+ i++;
37
+ continue;
38
+ }
39
+ if (def.type === "boolean") {
40
+ out[name] = true;
41
+ i++;
42
+ }
43
+ else {
44
+ const val = eq > -1 ? a.slice(eq + 1) : argv[i + 1];
45
+ if (eq === -1)
46
+ i++;
47
+ if (def.repeatable)
48
+ out[name].push(val);
49
+ else
50
+ out[name] = val;
51
+ i++;
52
+ }
53
+ }
54
+ else if (a.startsWith("-") && a.length > 1 && !/^-\d/.test(a)) {
55
+ const short = a.slice(1);
56
+ const name = shortMap[short];
57
+ if (!name) {
58
+ out._unknown.push(a);
59
+ i++;
60
+ continue;
61
+ }
62
+ const def = spec.flags[name];
63
+ if (def.type === "boolean") {
64
+ out[name] = true;
65
+ i++;
66
+ }
67
+ else {
68
+ const val = argv[i + 1];
69
+ if (def.repeatable)
70
+ out[name].push(val);
71
+ else
72
+ out[name] = val;
73
+ i += 2;
74
+ }
75
+ }
76
+ else {
77
+ out.positional.push(a);
78
+ i++;
79
+ }
70
80
  }
71
- }
72
- return out;
81
+ return out;
73
82
  }
@@ -0,0 +1,73 @@
1
+ // Tiny argv parser — no dependencies.
2
+ // Supports: --long, --long=val, --long val, -s, -s val, repeatable flags, positional, --.
3
+
4
+ export function parseArgs(argv, spec: any = {}): any {
5
+ const shortMap: any = {};
6
+ for (const [name, def] of Object.entries<any>(spec.flags || {})) {
7
+ if (def.short) shortMap[def.short] = name;
8
+ }
9
+
10
+ const out: any = { positional: [], _unknown: [] };
11
+ for (const [name, def] of Object.entries<any>(spec.flags || {})) {
12
+ if (def.repeatable) out[name] = [];
13
+ else if ("default" in def) out[name] = def.default;
14
+ }
15
+
16
+ let i = 0;
17
+ let doubleDashSeen = false;
18
+ while (i < argv.length) {
19
+ const a = argv[i];
20
+ if (doubleDashSeen) {
21
+ out.positional.push(a);
22
+ i++;
23
+ continue;
24
+ }
25
+ if (a === "--") {
26
+ doubleDashSeen = true;
27
+ i++;
28
+ continue;
29
+ }
30
+ if (a.startsWith("--")) {
31
+ const eq = a.indexOf("=");
32
+ const name = eq > -1 ? a.slice(2, eq) : a.slice(2);
33
+ const def = (spec.flags || {})[name];
34
+ if (!def) {
35
+ out._unknown.push(a);
36
+ i++;
37
+ continue;
38
+ }
39
+ if (def.type === "boolean") {
40
+ out[name] = true;
41
+ i++;
42
+ } else {
43
+ const val = eq > -1 ? a.slice(eq + 1) : argv[i + 1];
44
+ if (eq === -1) i++;
45
+ if (def.repeatable) out[name].push(val);
46
+ else out[name] = val;
47
+ i++;
48
+ }
49
+ } else if (a.startsWith("-") && a.length > 1 && !/^-\d/.test(a)) {
50
+ const short = a.slice(1);
51
+ const name = shortMap[short];
52
+ if (!name) {
53
+ out._unknown.push(a);
54
+ i++;
55
+ continue;
56
+ }
57
+ const def = spec.flags[name];
58
+ if (def.type === "boolean") {
59
+ out[name] = true;
60
+ i++;
61
+ } else {
62
+ const val = argv[i + 1];
63
+ if (def.repeatable) out[name].push(val);
64
+ else out[name] = val;
65
+ i += 2;
66
+ }
67
+ } else {
68
+ out.positional.push(a);
69
+ i++;
70
+ }
71
+ }
72
+ return out;
73
+ }
@@ -0,0 +1,15 @@
1
+ import { createHash } from "crypto";
2
+ import { config } from "../../config.js";
3
+ let cached = null;
4
+ /**
5
+ * Stable per-machine browser-id for CLI flows that share storage with the UI
6
+ * but do not have a real browser. Derived from the config dir so two CLIs on
7
+ * the same install share favorites/annotations, but different installs (e.g.
8
+ * different IMA2_CONFIG_DIR) stay isolated.
9
+ */
10
+ export function getCliBrowserId() {
11
+ if (cached)
12
+ return cached;
13
+ cached = "cli-" + createHash("sha1").update(config.storage.configDir).digest("hex").slice(0, 16);
14
+ return cached;
15
+ }