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
@@ -1,38 +1,37 @@
1
1
  import { inspectGeneratedStorage } from "../../lib/storageMigration.js";
2
-
3
2
  export async function buildStorageDoctorLines(ctx) {
4
- const status = await inspectGeneratedStorage(ctx);
5
- const lines = [
6
- " Storage",
7
- ` Current gallery: ${status.generatedDirLabel}`,
8
- ` Source: ${status.overrides.generatedDir ? "IMA2_GENERATED_DIR" : "default"}`,
9
- ` Images in current gallery: ${status.targetFileCount}`,
10
- ` Legacy folders scanned: ${status.legacyCandidatesScanned}`,
11
- ` Legacy folders found: ${status.legacySourcesFound}`,
12
- ];
13
-
14
- for (const source of status.legacySources.slice(0, 5)) {
15
- lines.push(` - ${source.path} (${source.fileCount} files)`);
16
- }
17
- if (status.legacySources.length > 5) {
18
- lines.push(` ...and ${status.legacySources.length - 5} more`);
19
- }
20
-
21
- lines.push("");
22
- lines.push(" Next step");
23
- if (status.state === "recoverable") {
24
- lines.push(" Old images may still be recoverable. Restart ima2 or copy them manually.");
25
- } else if (status.state === "not_found") {
26
- lines.push(" No previous generated folder was found on this machine.");
27
- lines.push(" If the old global install folder was replaced during update, backups may be required.");
28
- } else if (status.state === "unknown") {
29
- lines.push(" Storage status could not be fully checked.");
30
- } else {
31
- lines.push(" Current gallery storage looks available.");
32
- }
33
- lines.push(` See: ${status.recoveryDocsPath}`);
34
- lines.push(" macOS/Linux: cp -n \"/old/ima2-gen/generated/\"* ~/.ima2/generated/");
35
- lines.push(" Windows: Copy old generated files into %USERPROFILE%\\.ima2\\generated");
36
-
37
- return lines;
3
+ const status = await inspectGeneratedStorage(ctx);
4
+ const lines = [
5
+ " Storage",
6
+ ` Current gallery: ${status.generatedDirLabel}`,
7
+ ` Source: ${status.overrides.generatedDir ? "IMA2_GENERATED_DIR" : "default"}`,
8
+ ` Images in current gallery: ${status.targetFileCount}`,
9
+ ` Legacy folders scanned: ${status.legacyCandidatesScanned}`,
10
+ ` Legacy folders found: ${status.legacySourcesFound}`,
11
+ ];
12
+ for (const source of status.legacySources.slice(0, 5)) {
13
+ lines.push(` - ${source.path} (${source.fileCount} files)`);
14
+ }
15
+ if (status.legacySources.length > 5) {
16
+ lines.push(` ...and ${status.legacySources.length - 5} more`);
17
+ }
18
+ lines.push("");
19
+ lines.push(" Next step");
20
+ if (status.state === "recoverable") {
21
+ lines.push(" Old images may still be recoverable. Restart ima2 or copy them manually.");
22
+ }
23
+ else if (status.state === "not_found") {
24
+ lines.push(" No previous generated folder was found on this machine.");
25
+ lines.push(" If the old global install folder was replaced during update, backups may be required.");
26
+ }
27
+ else if (status.state === "unknown") {
28
+ lines.push(" Storage status could not be fully checked.");
29
+ }
30
+ else {
31
+ lines.push(" Current gallery storage looks available.");
32
+ }
33
+ lines.push(` See: ${status.recoveryDocsPath}`);
34
+ lines.push(" macOS/Linux: cp -n \"/old/ima2-gen/generated/\"* ~/.ima2/generated/");
35
+ lines.push(" Windows: Copy old generated files into %USERPROFILE%\\.ima2\\generated");
36
+ return lines;
38
37
  }
@@ -0,0 +1,38 @@
1
+ import { inspectGeneratedStorage } from "../../lib/storageMigration.js";
2
+
3
+ export async function buildStorageDoctorLines(ctx) {
4
+ const status = await inspectGeneratedStorage(ctx);
5
+ const lines = [
6
+ " Storage",
7
+ ` Current gallery: ${status.generatedDirLabel}`,
8
+ ` Source: ${status.overrides.generatedDir ? "IMA2_GENERATED_DIR" : "default"}`,
9
+ ` Images in current gallery: ${status.targetFileCount}`,
10
+ ` Legacy folders scanned: ${status.legacyCandidatesScanned}`,
11
+ ` Legacy folders found: ${status.legacySourcesFound}`,
12
+ ];
13
+
14
+ for (const source of status.legacySources.slice(0, 5)) {
15
+ lines.push(` - ${source.path} (${source.fileCount} files)`);
16
+ }
17
+ if (status.legacySources.length > 5) {
18
+ lines.push(` ...and ${status.legacySources.length - 5} more`);
19
+ }
20
+
21
+ lines.push("");
22
+ lines.push(" Next step");
23
+ if (status.state === "recoverable") {
24
+ lines.push(" Old images may still be recoverable. Restart ima2 or copy them manually.");
25
+ } else if (status.state === "not_found") {
26
+ lines.push(" No previous generated folder was found on this machine.");
27
+ lines.push(" If the old global install folder was replaced during update, backups may be required.");
28
+ } else if (status.state === "unknown") {
29
+ lines.push(" Storage status could not be fully checked.");
30
+ } else {
31
+ lines.push(" Current gallery storage looks available.");
32
+ }
33
+ lines.push(` See: ${status.recoveryDocsPath}`);
34
+ lines.push(" macOS/Linux: cp -n \"/old/ima2-gen/generated/\"* ~/.ima2/generated/");
35
+ lines.push(" Windows: Copy old generated files into %USERPROFILE%\\.ima2\\generated");
36
+
37
+ return lines;
38
+ }
package/config.js CHANGED
@@ -10,225 +10,182 @@
10
10
  //
11
11
  // Keep this module dependency-free aside from node:* built-ins to avoid
12
12
  // circular imports with lib/*.
13
-
14
13
  import { homedir } from "node:os";
15
14
  import { join, dirname } from "node:path";
16
15
  import { fileURLToPath } from "node:url";
17
16
  import { readFileSync, existsSync } from "node:fs";
18
-
19
17
  const env = process.env;
20
18
  const packageRoot = dirname(fileURLToPath(import.meta.url));
21
19
  const configDir = env.IMA2_CONFIG_DIR || join(homedir(), ".ima2");
22
-
23
20
  // ── Optional config.json layer ─────────────────────────────────────────
24
21
  // Users can drop `${configDir}/config.json` to override defaults without
25
22
  // setting env vars. Shape: same as the `config` object below (partial).
26
23
  function loadConfigJson() {
27
- const candidates = [
28
- join(configDir, "config.json"),
29
- join(packageRoot, ".ima2", "config.json"),
30
- ];
31
- for (const p of candidates) {
32
- if (!existsSync(p)) continue;
33
- try {
34
- const raw = readFileSync(p, "utf-8");
35
- return JSON.parse(raw);
36
- } catch {
37
- // ignore malformed config.json; env+defaults still apply
24
+ const candidates = [
25
+ join(configDir, "config.json"),
26
+ join(packageRoot, ".ima2", "config.json"),
27
+ ];
28
+ for (const p of candidates) {
29
+ if (!existsSync(p))
30
+ continue;
31
+ try {
32
+ const raw = readFileSync(p, "utf-8");
33
+ return JSON.parse(raw);
34
+ }
35
+ catch {
36
+ // ignore malformed config.json; env+defaults still apply
37
+ }
38
38
  }
39
- }
40
- return {};
39
+ return {};
41
40
  }
42
41
  const fileCfg = loadConfigJson();
43
-
44
42
  function firstDefined(...vals) {
45
- return vals.find((v) => v !== undefined && v !== "");
43
+ return vals.find((v) => v !== undefined && v !== "");
46
44
  }
47
45
  function pickInt(envVal, fileVal, fallback) {
48
- const candidate = firstDefined(envVal, fileVal);
49
- if (candidate === undefined) return fallback;
50
- const n = Number(candidate);
51
- return Number.isFinite(n) ? n : fallback;
46
+ const candidate = firstDefined(envVal, fileVal);
47
+ if (candidate === undefined)
48
+ return fallback;
49
+ const n = Number(candidate);
50
+ return Number.isFinite(n) ? n : fallback;
52
51
  }
53
52
  function pickPositiveInt(envVal, fileVal, fallback) {
54
- const n = pickInt(envVal, fileVal, fallback);
55
- return Number.isFinite(n) && n > 0 ? n : fallback;
53
+ const n = pickInt(envVal, fileVal, fallback);
54
+ return Number.isFinite(n) && n > 0 ? n : fallback;
56
55
  }
57
56
  function pickStr(envVal, fileVal, fallback) {
58
- return firstDefined(envVal, fileVal) ?? fallback;
57
+ return firstDefined(envVal, fileVal) ?? fallback;
59
58
  }
60
59
  function pickBool(envVal, fileVal, fallback) {
61
- const v = firstDefined(envVal, fileVal);
62
- if (v === undefined) return fallback;
63
- if (typeof v === "boolean") return v;
64
- const s = String(v).toLowerCase();
65
- return s === "1" || s === "true" || s === "yes";
60
+ const v = firstDefined(envVal, fileVal);
61
+ if (v === undefined)
62
+ return fallback;
63
+ if (typeof v === "boolean")
64
+ return v;
65
+ const s = String(v).toLowerCase();
66
+ return s === "1" || s === "true" || s === "yes";
66
67
  }
67
-
68
68
  export function defaultLogLevelForEnv(runtimeEnv = env) {
69
- return runtimeEnv.IMA2_DEV === "1" ? "debug" : "info";
69
+ return runtimeEnv.IMA2_DEV === "1" ? "debug" : "info";
70
70
  }
71
-
72
71
  export const config = {
73
- server: {
74
- // Accept both IMA2_PORT and legacy PORT.
75
- port: pickInt(firstDefined(env.IMA2_PORT, env.PORT), fileCfg.server?.port, 3333),
76
- host: pickStr(env.IMA2_HOST, fileCfg.server?.host, "127.0.0.1"),
77
- bodyLimit: pickStr(env.IMA2_BODY_LIMIT, fileCfg.server?.bodyLimit, "50mb"),
78
- },
79
- limits: {
80
- maxRefB64Bytes: pickInt(env.IMA2_MAX_REF_B64_BYTES, fileCfg.limits?.maxRefB64Bytes, 7 * 1024 * 1024),
81
- maxMetadataReadB64Bytes: pickInt(
82
- env.IMA2_MAX_METADATA_READ_B64_BYTES,
83
- fileCfg.limits?.maxMetadataReadB64Bytes,
84
- 12 * 1024 * 1024,
85
- ),
86
- maxRefCount: pickInt(env.IMA2_MAX_REF_COUNT, fileCfg.limits?.maxRefCount, 5),
87
- maxParallel: pickInt(env.IMA2_MAX_PARALLEL, fileCfg.limits?.maxParallel, 8),
88
- graphMaxNodes: pickInt(env.IMA2_GRAPH_MAX_NODES, fileCfg.limits?.graphMaxNodes, 500),
89
- graphMaxEdges: pickInt(env.IMA2_GRAPH_MAX_EDGES, fileCfg.limits?.graphMaxEdges, 1000),
90
- promptImportMaxFileBytes: pickInt(
91
- env.IMA2_PROMPT_IMPORT_MAX_FILE_BYTES,
92
- fileCfg.limits?.promptImportMaxFileBytes,
93
- 512 * 1024,
94
- ),
95
- promptImportMaxCandidatesPerFile: pickInt(
96
- env.IMA2_PROMPT_IMPORT_MAX_CANDIDATES_PER_FILE,
97
- fileCfg.limits?.promptImportMaxCandidatesPerFile,
98
- 100,
99
- ),
100
- promptImportMaxCandidatesPerImport: pickInt(
101
- env.IMA2_PROMPT_IMPORT_MAX_CANDIDATES_PER_IMPORT,
102
- fileCfg.limits?.promptImportMaxCandidatesPerImport,
103
- 100,
104
- ),
105
- promptImportFetchTimeoutMs: pickInt(
106
- env.IMA2_PROMPT_IMPORT_FETCH_TIMEOUT_MS,
107
- fileCfg.limits?.promptImportFetchTimeoutMs,
108
- 8000,
109
- ),
110
- promptImportMaxCandidateChars: pickInt(
111
- env.IMA2_PROMPT_IMPORT_MAX_CANDIDATE_CHARS,
112
- fileCfg.limits?.promptImportMaxCandidateChars,
113
- 12000,
114
- ),
115
- promptImportMinCandidateChars: pickInt(
116
- env.IMA2_PROMPT_IMPORT_MIN_CANDIDATE_CHARS,
117
- fileCfg.limits?.promptImportMinCandidateChars,
118
- 40,
119
- ),
120
- promptImportMaxSourceCharsScanned: pickInt(
121
- env.IMA2_PROMPT_IMPORT_MAX_SOURCE_CHARS_SCANNED,
122
- fileCfg.limits?.promptImportMaxSourceCharsScanned,
123
- 512 * 1024,
124
- ),
125
- },
126
- history: {
127
- defaultPageSize: pickInt(
128
- env.IMA2_HISTORY_PAGE_SIZE,
129
- fileCfg.history?.defaultPageSize ?? fileCfg.limits?.historyDefaultPageSize,
130
- 50,
131
- ),
132
- maxPageCap: pickInt(
133
- env.IMA2_HISTORY_MAX_PAGE,
134
- fileCfg.history?.maxPageCap ?? fileCfg.limits?.historyMaxPageCap,
135
- 500,
136
- ),
137
- },
138
- oauth: {
139
- // Accept both IMA2_OAUTH_PROXY_PORT and legacy OAUTH_PORT.
140
- proxyPort: pickInt(firstDefined(env.IMA2_OAUTH_PROXY_PORT, env.OAUTH_PORT), fileCfg.oauth?.proxyPort, 10531),
141
- // IMA2_NO_OAUTH_PROXY=1 disables auto-start; default is auto-start enabled.
142
- autoStart: !pickBool(env.IMA2_NO_OAUTH_PROXY, fileCfg.oauth?.disableAutoStart, false),
143
- statusTimeoutMs: pickInt(env.IMA2_OAUTH_STATUS_TIMEOUT_MS, fileCfg.oauth?.statusTimeoutMs, 3000),
144
- restartDelayMs: pickInt(env.IMA2_OAUTH_RESTART_DELAY_MS, fileCfg.oauth?.restartDelayMs, 5000),
145
- researchSuffix: pickStr(
146
- env.IMA2_RESEARCH_SUFFIX,
147
- fileCfg.oauth?.researchSuffix,
148
- "\n\nIf the subject matter requires factual accuracy (faces, products, places, recent events), search the web for accurate visual references first, then generate.",
149
- ),
150
- validModeration: new Set(
151
- Array.isArray(fileCfg.oauth?.validModeration) && fileCfg.oauth.validModeration.length
152
- ? fileCfg.oauth.validModeration
153
- : ["auto", "low"],
154
- ),
155
- },
156
- storage: {
157
- configDir,
158
- packageRoot,
159
- generatedDir: pickStr(env.IMA2_GENERATED_DIR, fileCfg.storage?.generatedDir, join(configDir, "generated")),
160
- trashDir: pickStr(env.IMA2_TRASH_DIR, fileCfg.storage?.trashDir, join(configDir, "generated", ".trash")),
161
- generatedDirName: pickStr(env.IMA2_GENERATED_DIRNAME, fileCfg.storage?.generatedDirName, "generated"),
162
- trashDirName: pickStr(env.IMA2_TRASH_DIRNAME, fileCfg.storage?.trashDirName, ".trash"),
163
- dbPath: pickStr(env.IMA2_DB_PATH, fileCfg.storage?.dbPath, join(configDir, "sessions.db")),
164
- configFile: join(configDir, "config.json"),
165
- advertiseFile: pickStr(env.IMA2_ADVERTISE_FILE, fileCfg.storage?.advertiseFile, join(configDir, "server.json")),
166
- staticMaxAge: pickStr(env.IMA2_STATIC_MAX_AGE, fileCfg.storage?.staticMaxAge, "1y"),
167
- },
168
- ids: {
169
- generatedHexBytes: pickInt(env.IMA2_GENERATED_HEX_BYTES, fileCfg.ids?.generatedHexBytes, 4),
170
- nodeHexBytes: pickInt(env.IMA2_NODE_HEX_BYTES, fileCfg.ids?.nodeHexBytes, 5),
171
- },
172
- inflight: {
173
- ttlMs: pickInt(env.IMA2_INFLIGHT_TTL_MS, fileCfg.inflight?.ttlMs, 10 * 60 * 1000),
174
- reapMs: pickInt(env.IMA2_INFLIGHT_REAP_MS, fileCfg.inflight?.reapMs, 60 * 1000),
175
- terminalTtlMs: pickInt(env.IMA2_INFLIGHT_TERMINAL_TTL_MS, fileCfg.inflight?.terminalTtlMs, 30 * 1000),
176
- },
177
- trash: {
178
- ttlMs: pickInt(env.IMA2_TRASH_TTL_MS, fileCfg.trash?.ttlMs, 10_000),
179
- },
180
- styleSheet: {
181
- maxPrefix: pickInt(env.IMA2_STYLE_SHEET_MAX_PREFIX, fileCfg.styleSheet?.maxPrefix, 4000),
182
- model: pickStr(env.IMA2_STYLE_MODEL, fileCfg.styleSheet?.model, "gpt-5.4-mini"),
183
- },
184
- imageModels: {
185
- default: pickStr(env.IMA2_IMAGE_MODEL_DEFAULT, fileCfg.imageModels?.default, "gpt-5.4-mini"),
186
- valid: new Set(["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"]),
187
- unsupported: new Set(["gpt-5.3-codex-spark"]),
188
- reasoningEffort: pickStr(
189
- env.IMA2_REASONING_EFFORT,
190
- fileCfg.imageModels?.reasoningEffort,
191
- "medium",
192
- ),
193
- validReasoningEfforts: new Set(["low", "medium", "high", "xhigh"]),
194
- },
195
- log: {
196
- level: pickStr(env.IMA2_LOG_LEVEL, fileCfg.log?.level, defaultLogLevelForEnv(env)),
197
- pretty: env.NODE_ENV !== "production",
198
- },
199
- features: {
200
- cardNews: pickBool(env.IMA2_CARD_NEWS, fileCfg.features?.cardNews, env.IMA2_DEV === "1"),
201
- },
202
- cardNewsPlanner: {
203
- enabled: pickBool(env.IMA2_CARD_NEWS_PLANNER, fileCfg.cardNewsPlanner?.enabled, true),
204
- model: pickStr(env.IMA2_CARD_NEWS_PLANNER_MODEL, fileCfg.cardNewsPlanner?.model, "gpt-5.4-mini"),
205
- timeoutMs: pickInt(env.IMA2_CARD_NEWS_PLANNER_TIMEOUT_MS, fileCfg.cardNewsPlanner?.timeoutMs, 60_000),
206
- deterministicFallback: pickBool(
207
- env.IMA2_CARD_NEWS_PLANNER_FALLBACK,
208
- fileCfg.cardNewsPlanner?.deterministicFallback,
209
- false,
210
- ),
211
- },
212
- comfy: {
213
- defaultUrl: pickStr(env.IMA2_COMFY_URL, fileCfg.comfy?.defaultUrl, "http://127.0.0.1:8188"),
214
- uploadTimeoutMs: pickPositiveInt(
215
- env.IMA2_COMFY_UPLOAD_TIMEOUT_MS,
216
- fileCfg.comfy?.uploadTimeoutMs,
217
- 30_000,
218
- ),
219
- maxUploadBytes: pickPositiveInt(
220
- env.IMA2_COMFY_MAX_UPLOAD_BYTES,
221
- fileCfg.comfy?.maxUploadBytes,
222
- 50 * 1024 * 1024,
223
- ),
224
- },
225
- dev: {
226
- viteDevMode: pickBool(env.VITE_IMA2_DEV, fileCfg.dev?.viteDevMode, false),
227
- },
72
+ server: {
73
+ // Accept both IMA2_PORT and legacy PORT.
74
+ port: pickInt(firstDefined(env.IMA2_PORT, env.PORT), fileCfg.server?.port, 3333),
75
+ host: pickStr(env.IMA2_HOST, fileCfg.server?.host, "127.0.0.1"),
76
+ bodyLimit: pickStr(env.IMA2_BODY_LIMIT, fileCfg.server?.bodyLimit, "50mb"),
77
+ },
78
+ limits: {
79
+ maxRefB64Bytes: pickInt(env.IMA2_MAX_REF_B64_BYTES, fileCfg.limits?.maxRefB64Bytes, 7 * 1024 * 1024),
80
+ maxMetadataReadB64Bytes: pickInt(env.IMA2_MAX_METADATA_READ_B64_BYTES, fileCfg.limits?.maxMetadataReadB64Bytes, 12 * 1024 * 1024),
81
+ maxRefCount: pickInt(env.IMA2_MAX_REF_COUNT, fileCfg.limits?.maxRefCount, 5),
82
+ maxParallel: pickInt(env.IMA2_MAX_PARALLEL, fileCfg.limits?.maxParallel, 8),
83
+ graphMaxNodes: pickInt(env.IMA2_GRAPH_MAX_NODES, fileCfg.limits?.graphMaxNodes, 500),
84
+ graphMaxEdges: pickInt(env.IMA2_GRAPH_MAX_EDGES, fileCfg.limits?.graphMaxEdges, 1000),
85
+ promptImportMaxFileBytes: pickInt(env.IMA2_PROMPT_IMPORT_MAX_FILE_BYTES, fileCfg.limits?.promptImportMaxFileBytes, 512 * 1024),
86
+ promptImportMaxCandidatesPerFile: pickInt(env.IMA2_PROMPT_IMPORT_MAX_CANDIDATES_PER_FILE, fileCfg.limits?.promptImportMaxCandidatesPerFile, 100),
87
+ promptImportMaxCandidatesPerImport: pickInt(env.IMA2_PROMPT_IMPORT_MAX_CANDIDATES_PER_IMPORT, fileCfg.limits?.promptImportMaxCandidatesPerImport, 100),
88
+ promptImportFetchTimeoutMs: pickInt(env.IMA2_PROMPT_IMPORT_FETCH_TIMEOUT_MS, fileCfg.limits?.promptImportFetchTimeoutMs, 8000),
89
+ promptImportMaxCandidateChars: pickInt(env.IMA2_PROMPT_IMPORT_MAX_CANDIDATE_CHARS, fileCfg.limits?.promptImportMaxCandidateChars, 12000),
90
+ promptImportMinCandidateChars: pickInt(env.IMA2_PROMPT_IMPORT_MIN_CANDIDATE_CHARS, fileCfg.limits?.promptImportMinCandidateChars, 40),
91
+ promptImportMaxSourceCharsScanned: pickInt(env.IMA2_PROMPT_IMPORT_MAX_SOURCE_CHARS_SCANNED, fileCfg.limits?.promptImportMaxSourceCharsScanned, 512 * 1024),
92
+ promptImportMaxRepoIndexFiles: pickInt(env.IMA2_PROMPT_IMPORT_MAX_REPO_INDEX_FILES, fileCfg.limits?.promptImportMaxRepoIndexFiles, 500),
93
+ promptImportCuratedSearchLimit: pickInt(env.IMA2_PROMPT_IMPORT_CURATED_SEARCH_LIMIT, fileCfg.limits?.promptImportCuratedSearchLimit, 50),
94
+ promptImportIndexCacheTtlMs: pickInt(env.IMA2_PROMPT_IMPORT_INDEX_CACHE_TTL_MS, fileCfg.limits?.promptImportIndexCacheTtlMs, 24 * 60 * 60 * 1000),
95
+ promptImportMaxFolderFiles: pickInt(env.IMA2_PROMPT_IMPORT_MAX_FOLDER_FILES, fileCfg.limits?.promptImportMaxFolderFiles, 100),
96
+ promptImportMaxFolderPreviewFiles: pickInt(env.IMA2_PROMPT_IMPORT_MAX_FOLDER_PREVIEW_FILES, fileCfg.limits?.promptImportMaxFolderPreviewFiles, 20),
97
+ promptImportDiscoverySearchLimit: pickInt(env.IMA2_PROMPT_IMPORT_DISCOVERY_SEARCH_LIMIT, fileCfg.limits?.promptImportDiscoverySearchLimit, 20),
98
+ promptImportDiscoveryCacheTtlMs: pickInt(env.IMA2_PROMPT_IMPORT_DISCOVERY_CACHE_TTL_MS, fileCfg.limits?.promptImportDiscoveryCacheTtlMs, 60 * 60 * 1000),
99
+ promptImportDiscoveryMaxQueries: pickInt(env.IMA2_PROMPT_IMPORT_DISCOVERY_MAX_QUERIES, fileCfg.limits?.promptImportDiscoveryMaxQueries, 5),
100
+ },
101
+ history: {
102
+ defaultPageSize: pickInt(env.IMA2_HISTORY_PAGE_SIZE, fileCfg.history?.defaultPageSize ?? fileCfg.limits?.historyDefaultPageSize, 50),
103
+ maxPageCap: pickInt(env.IMA2_HISTORY_MAX_PAGE, fileCfg.history?.maxPageCap ?? fileCfg.limits?.historyMaxPageCap, 500),
104
+ },
105
+ oauth: {
106
+ // Accept both IMA2_OAUTH_PROXY_PORT and legacy OAUTH_PORT.
107
+ proxyPort: pickInt(firstDefined(env.IMA2_OAUTH_PROXY_PORT, env.OAUTH_PORT), fileCfg.oauth?.proxyPort, 10531),
108
+ // IMA2_NO_OAUTH_PROXY=1 disables auto-start; default is auto-start enabled.
109
+ autoStart: !pickBool(env.IMA2_NO_OAUTH_PROXY, fileCfg.oauth?.disableAutoStart, false),
110
+ statusTimeoutMs: pickInt(env.IMA2_OAUTH_STATUS_TIMEOUT_MS, fileCfg.oauth?.statusTimeoutMs, 3000),
111
+ generationTimeoutMs: pickInt(env.IMA2_OAUTH_GENERATION_TIMEOUT_MS, fileCfg.oauth?.generationTimeoutMs, 400 * 1000),
112
+ restartDelayMs: pickInt(env.IMA2_OAUTH_RESTART_DELAY_MS, fileCfg.oauth?.restartDelayMs, 5000),
113
+ // Provider-backed masked edit is off until upstream STEP-0 verification is recorded.
114
+ maskedEditEnabled: pickBool(env.IMA2_OAUTH_MASKED_EDIT_ENABLED, fileCfg.oauth?.maskedEditEnabled, false),
115
+ researchSuffix: pickStr(env.IMA2_RESEARCH_SUFFIX, fileCfg.oauth?.researchSuffix, "\n\nIf factual visual accuracy is required and the user's prompt or attached visual context is not already sufficient, use at least one concise web_search call for references before generating. If the user's prompt is already visually sufficient, do not search, rewrite, translate, summarize, or add clarifiers; pass the user's prompt through as the image_generation prompt argument."),
116
+ validModeration: new Set(Array.isArray(fileCfg.oauth?.validModeration) && fileCfg.oauth.validModeration.length
117
+ ? fileCfg.oauth.validModeration
118
+ : ["auto", "low"]),
119
+ },
120
+ github: {
121
+ token: pickStr(env.IMA2_GITHUB_TOKEN, fileCfg.github?.token, ""),
122
+ },
123
+ storage: {
124
+ configDir,
125
+ packageRoot,
126
+ generatedDir: pickStr(env.IMA2_GENERATED_DIR, fileCfg.storage?.generatedDir, join(configDir, "generated")),
127
+ trashDir: pickStr(env.IMA2_TRASH_DIR, fileCfg.storage?.trashDir, join(configDir, "generated", ".trash")),
128
+ generatedDirName: pickStr(env.IMA2_GENERATED_DIRNAME, fileCfg.storage?.generatedDirName, "generated"),
129
+ trashDirName: pickStr(env.IMA2_TRASH_DIRNAME, fileCfg.storage?.trashDirName, ".trash"),
130
+ dbPath: pickStr(env.IMA2_DB_PATH, fileCfg.storage?.dbPath, join(configDir, "sessions.db")),
131
+ promptImportIndexCacheFile: pickStr(env.IMA2_PROMPT_IMPORT_INDEX_CACHE_FILE, fileCfg.storage?.promptImportIndexCacheFile, join(configDir, "prompt-import-index.json")),
132
+ promptImportDiscoveryRegistryFile: pickStr(env.IMA2_PROMPT_IMPORT_DISCOVERY_REGISTRY_FILE, fileCfg.storage?.promptImportDiscoveryRegistryFile, join(configDir, "prompt-import-discovery.json")),
133
+ configFile: join(configDir, "config.json"),
134
+ advertiseFile: pickStr(env.IMA2_ADVERTISE_FILE, fileCfg.storage?.advertiseFile, join(configDir, "server.json")),
135
+ staticMaxAge: pickStr(env.IMA2_STATIC_MAX_AGE, fileCfg.storage?.staticMaxAge, "1y"),
136
+ },
137
+ ids: {
138
+ generatedHexBytes: pickInt(env.IMA2_GENERATED_HEX_BYTES, fileCfg.ids?.generatedHexBytes, 4),
139
+ nodeHexBytes: pickInt(env.IMA2_NODE_HEX_BYTES, fileCfg.ids?.nodeHexBytes, 5),
140
+ },
141
+ inflight: {
142
+ ttlMs: pickInt(env.IMA2_INFLIGHT_TTL_MS, fileCfg.inflight?.ttlMs, 10 * 60 * 1000),
143
+ reapMs: pickInt(env.IMA2_INFLIGHT_REAP_MS, fileCfg.inflight?.reapMs, 60 * 1000),
144
+ terminalTtlMs: pickInt(env.IMA2_INFLIGHT_TERMINAL_TTL_MS, fileCfg.inflight?.terminalTtlMs, 5 * 60 * 1000),
145
+ },
146
+ trash: {
147
+ ttlMs: pickInt(env.IMA2_TRASH_TTL_MS, fileCfg.trash?.ttlMs, 10_000),
148
+ },
149
+ styleSheet: {
150
+ maxPrefix: pickInt(env.IMA2_STYLE_SHEET_MAX_PREFIX, fileCfg.styleSheet?.maxPrefix, 4000),
151
+ model: pickStr(env.IMA2_STYLE_MODEL, fileCfg.styleSheet?.model, "gpt-5.4-mini"),
152
+ },
153
+ imageModels: {
154
+ default: pickStr(env.IMA2_IMAGE_MODEL_DEFAULT, fileCfg.imageModels?.default, "gpt-5.4-mini"),
155
+ valid: new Set(["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"]),
156
+ unsupported: new Set(["gpt-5.3-codex-spark"]),
157
+ reasoningEffort: pickStr(env.IMA2_REASONING_EFFORT, fileCfg.imageModels?.reasoningEffort, "medium"),
158
+ validReasoningEfforts: new Set(["none", "low", "medium", "high", "xhigh"]),
159
+ },
160
+ apiProvider: {
161
+ defaultImageModel: pickStr(env.IMA2_API_IMAGE_MODEL_DEFAULT, fileCfg.apiProvider?.defaultImageModel, "gpt-5.4-mini"),
162
+ defaultReasoningEffort: pickStr(env.IMA2_API_REASONING_EFFORT, fileCfg.apiProvider?.defaultReasoningEffort, "low"),
163
+ defaultSize: pickStr(env.IMA2_API_IMAGE_SIZE, fileCfg.apiProvider?.defaultSize, "1024x1024"),
164
+ allowWebSearch: pickBool(env.IMA2_API_ALLOW_WEB_SEARCH, fileCfg.apiProvider?.allowWebSearch, true),
165
+ },
166
+ log: {
167
+ level: pickStr(env.IMA2_LOG_LEVEL, fileCfg.log?.level, defaultLogLevelForEnv(env)),
168
+ pretty: env.NODE_ENV !== "production",
169
+ },
170
+ features: {
171
+ cardNews: pickBool(env.IMA2_CARD_NEWS, fileCfg.features?.cardNews, env.IMA2_DEV === "1"),
172
+ },
173
+ cardNewsPlanner: {
174
+ enabled: pickBool(env.IMA2_CARD_NEWS_PLANNER, fileCfg.cardNewsPlanner?.enabled, true),
175
+ model: pickStr(env.IMA2_CARD_NEWS_PLANNER_MODEL, fileCfg.cardNewsPlanner?.model, "gpt-5.4-mini"),
176
+ timeoutMs: pickInt(env.IMA2_CARD_NEWS_PLANNER_TIMEOUT_MS, fileCfg.cardNewsPlanner?.timeoutMs, 60_000),
177
+ deterministicFallback: pickBool(env.IMA2_CARD_NEWS_PLANNER_FALLBACK, fileCfg.cardNewsPlanner?.deterministicFallback, false),
178
+ },
179
+ comfy: {
180
+ defaultUrl: pickStr(env.IMA2_COMFY_URL, fileCfg.comfy?.defaultUrl, "http://127.0.0.1:8188"),
181
+ uploadTimeoutMs: pickPositiveInt(env.IMA2_COMFY_UPLOAD_TIMEOUT_MS, fileCfg.comfy?.uploadTimeoutMs, 30_000),
182
+ maxUploadBytes: pickPositiveInt(env.IMA2_COMFY_MAX_UPLOAD_BYTES, fileCfg.comfy?.maxUploadBytes, 50 * 1024 * 1024),
183
+ },
184
+ dev: {
185
+ viteDevMode: pickBool(env.VITE_IMA2_DEV, fileCfg.dev?.viteDevMode, false),
186
+ },
228
187
  };
229
-
230
188
  export default config;
231
-
232
189
  // ── Backward-compatible flat re-exports (used by lib/inflight.js & earlier
233
190
  // call sites). Prefer `import { config } from "./config.js"` going forward.
234
191
  export const PORT = config.server.port;