ima2-gen 1.1.9 → 1.1.11

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 (234) hide show
  1. package/README.md +10 -1
  2. package/bin/commands/annotate.js +5 -3
  3. package/bin/commands/annotate.ts +13 -12
  4. package/bin/commands/cancel.js +5 -2
  5. package/bin/commands/cancel.ts +6 -3
  6. package/bin/commands/canvas-versions.js +4 -4
  7. package/bin/commands/canvas-versions.ts +10 -10
  8. package/bin/commands/capabilities.js +90 -0
  9. package/bin/commands/capabilities.ts +93 -0
  10. package/bin/commands/cardnews.js +3 -1
  11. package/bin/commands/cardnews.ts +18 -17
  12. package/bin/commands/comfy.js +2 -2
  13. package/bin/commands/comfy.ts +4 -4
  14. package/bin/commands/config.js +11 -143
  15. package/bin/commands/config.ts +33 -147
  16. package/bin/commands/defaults.js +180 -0
  17. package/bin/commands/defaults.ts +192 -0
  18. package/bin/commands/edit.js +45 -9
  19. package/bin/commands/edit.ts +44 -10
  20. package/bin/commands/gen.js +55 -14
  21. package/bin/commands/gen.ts +56 -18
  22. package/bin/commands/history.js +2 -1
  23. package/bin/commands/history.ts +11 -10
  24. package/bin/commands/ls.js +10 -5
  25. package/bin/commands/ls.ts +15 -11
  26. package/bin/commands/metadata.js +4 -1
  27. package/bin/commands/metadata.ts +5 -2
  28. package/bin/commands/multimode.js +64 -7
  29. package/bin/commands/multimode.ts +59 -9
  30. package/bin/commands/node.js +18 -8
  31. package/bin/commands/node.ts +24 -15
  32. package/bin/commands/observability.js +7 -7
  33. package/bin/commands/observability.ts +21 -21
  34. package/bin/commands/ping.js +4 -2
  35. package/bin/commands/ping.ts +5 -3
  36. package/bin/commands/prompt.js +14 -11
  37. package/bin/commands/prompt.ts +40 -38
  38. package/bin/commands/ps.js +10 -7
  39. package/bin/commands/ps.ts +15 -12
  40. package/bin/commands/session.js +4 -3
  41. package/bin/commands/session.ts +23 -22
  42. package/bin/commands/show.js +5 -2
  43. package/bin/commands/show.ts +8 -5
  44. package/bin/commands/skill.js +62 -0
  45. package/bin/commands/skill.ts +70 -0
  46. package/bin/ima2.js +14 -5
  47. package/bin/ima2.ts +16 -7
  48. package/bin/lib/args.ts +20 -1
  49. package/bin/lib/client.ts +20 -7
  50. package/bin/lib/config-store.js +156 -0
  51. package/bin/lib/config-store.ts +163 -0
  52. package/bin/lib/error-hints.ts +3 -3
  53. package/bin/lib/files.ts +8 -8
  54. package/bin/lib/output.js +19 -17
  55. package/bin/lib/output.ts +38 -23
  56. package/bin/lib/platform.js +3 -1
  57. package/bin/lib/platform.ts +9 -7
  58. package/bin/lib/recover-output.js +104 -0
  59. package/bin/lib/recover-output.ts +139 -0
  60. package/bin/lib/star-prompt.ts +1 -1
  61. package/bin/lib/storage-doctor.ts +3 -2
  62. package/config.js +1 -1
  63. package/config.ts +8 -6
  64. package/docs/CLI.md +64 -8
  65. package/docs/migration/runtime-test-inventory.md +146 -0
  66. package/lib/assetLifecycle.ts +8 -8
  67. package/lib/canvasVersionStore.js +2 -0
  68. package/lib/canvasVersionStore.ts +54 -12
  69. package/lib/capabilities.js +70 -0
  70. package/lib/capabilities.ts +84 -0
  71. package/lib/cardNewsGenerator.js +13 -3
  72. package/lib/cardNewsGenerator.ts +123 -14
  73. package/lib/cardNewsJobStore.ts +47 -12
  74. package/lib/cardNewsManifestStore.js +13 -6
  75. package/lib/cardNewsManifestStore.ts +56 -14
  76. package/lib/cardNewsPlanner.js +11 -9
  77. package/lib/cardNewsPlanner.ts +86 -30
  78. package/lib/cardNewsPlannerClient.js +23 -10
  79. package/lib/cardNewsPlannerClient.ts +58 -17
  80. package/lib/cardNewsPlannerPrompt.js +2 -0
  81. package/lib/cardNewsPlannerPrompt.ts +2 -0
  82. package/lib/cardNewsPlannerSchema.js +43 -36
  83. package/lib/cardNewsPlannerSchema.ts +120 -58
  84. package/lib/cardNewsTemplateStore.js +20 -35
  85. package/lib/cardNewsTemplateStore.ts +100 -58
  86. package/lib/codexDetect.js +5 -3
  87. package/lib/codexDetect.ts +5 -3
  88. package/lib/comfyBridge.js +3 -1
  89. package/lib/comfyBridge.ts +37 -16
  90. package/lib/db.ts +5 -5
  91. package/lib/errInfo.js +32 -0
  92. package/lib/errInfo.ts +43 -0
  93. package/lib/errorClassify.ts +2 -2
  94. package/lib/generationCancel.js +18 -0
  95. package/lib/generationCancel.ts +28 -0
  96. package/lib/generationErrors.ts +37 -11
  97. package/lib/historyIndex.js +34 -0
  98. package/lib/historyIndex.ts +51 -0
  99. package/lib/historyList.js +10 -6
  100. package/lib/historyList.ts +17 -13
  101. package/lib/imageMetadata.js +1 -1
  102. package/lib/imageMetadata.ts +8 -8
  103. package/lib/imageMetadataStore.ts +6 -6
  104. package/lib/imageModels.ts +6 -4
  105. package/lib/inflight.js +32 -8
  106. package/lib/inflight.ts +93 -16
  107. package/lib/localImportStore.js +2 -0
  108. package/lib/localImportStore.ts +8 -5
  109. package/lib/logger.js +7 -5
  110. package/lib/logger.ts +34 -23
  111. package/lib/nodeStore.js +2 -0
  112. package/lib/nodeStore.ts +20 -10
  113. package/lib/oauthLauncher.js +2 -2
  114. package/lib/oauthLauncher.ts +7 -6
  115. package/lib/oauthNormalize.ts +1 -1
  116. package/lib/oauthProxy/errors.js +93 -0
  117. package/lib/oauthProxy/errors.ts +128 -0
  118. package/lib/oauthProxy/generators.js +426 -0
  119. package/lib/oauthProxy/generators.ts +494 -0
  120. package/lib/oauthProxy/index.js +8 -0
  121. package/lib/oauthProxy/index.ts +28 -0
  122. package/lib/oauthProxy/prompts.js +99 -0
  123. package/lib/oauthProxy/prompts.ts +123 -0
  124. package/lib/oauthProxy/references.js +32 -0
  125. package/lib/oauthProxy/references.ts +45 -0
  126. package/lib/oauthProxy/runtime.js +101 -0
  127. package/lib/oauthProxy/runtime.ts +115 -0
  128. package/lib/oauthProxy/streams.js +211 -0
  129. package/lib/oauthProxy/streams.ts +232 -0
  130. package/lib/oauthProxy/types.js +6 -0
  131. package/lib/oauthProxy/types.ts +9 -0
  132. package/lib/oauthProxy.js +3 -911
  133. package/lib/oauthProxy.ts +3 -995
  134. package/lib/openDirectory.js +5 -3
  135. package/lib/openDirectory.ts +9 -7
  136. package/lib/pngInfo.ts +2 -2
  137. package/lib/promptImport/curatedSources.ts +4 -2
  138. package/lib/promptImport/discoveryRegistry.js +17 -9
  139. package/lib/promptImport/discoveryRegistry.ts +121 -28
  140. package/lib/promptImport/errors.ts +6 -6
  141. package/lib/promptImport/githubDiscovery.js +13 -7
  142. package/lib/promptImport/githubDiscovery.ts +84 -23
  143. package/lib/promptImport/githubFolder.js +22 -14
  144. package/lib/promptImport/githubFolder.ts +130 -41
  145. package/lib/promptImport/githubSource.js +3 -1
  146. package/lib/promptImport/githubSource.ts +32 -14
  147. package/lib/promptImport/gptImageHints.ts +10 -8
  148. package/lib/promptImport/parsePromptCandidates.js +2 -0
  149. package/lib/promptImport/parsePromptCandidates.ts +43 -17
  150. package/lib/promptImport/promptIndex.js +33 -23
  151. package/lib/promptImport/promptIndex.ts +124 -46
  152. package/lib/promptImport/rankPromptCandidates.js +2 -2
  153. package/lib/promptImport/rankPromptCandidates.ts +22 -6
  154. package/lib/promptImport/types.js +1 -0
  155. package/lib/promptImport/types.ts +103 -0
  156. package/lib/promptSafetyPolicy.js +5 -0
  157. package/lib/promptSafetyPolicy.ts +5 -0
  158. package/lib/providerOptions.ts +3 -2
  159. package/lib/referenceImageCompress.ts +15 -6
  160. package/lib/refs.js +2 -0
  161. package/lib/refs.ts +27 -11
  162. package/lib/requestLogger.ts +4 -3
  163. package/lib/responsesImageAdapter.js +54 -17
  164. package/lib/responsesImageAdapter.ts +169 -36
  165. package/lib/runtimeContext.js +100 -0
  166. package/lib/runtimeContext.ts +131 -0
  167. package/lib/runtimePorts.js +2 -1
  168. package/lib/runtimePorts.ts +28 -16
  169. package/lib/sessionStore.js +7 -5
  170. package/lib/sessionStore.ts +73 -37
  171. package/lib/storageMigration.js +18 -11
  172. package/lib/storageMigration.ts +63 -37
  173. package/lib/styleSheet.js +7 -6
  174. package/lib/styleSheet.ts +34 -23
  175. package/lib/visibleTextLanguagePolicy.js +7 -0
  176. package/lib/visibleTextLanguagePolicy.ts +7 -0
  177. package/package.json +6 -3
  178. package/routes/annotations.js +9 -4
  179. package/routes/annotations.ts +35 -12
  180. package/routes/canvasVersions.js +8 -3
  181. package/routes/canvasVersions.ts +14 -9
  182. package/routes/capabilities.js +13 -0
  183. package/routes/capabilities.ts +18 -0
  184. package/routes/cardNews.js +31 -18
  185. package/routes/cardNews.ts +66 -38
  186. package/routes/comfy.js +6 -3
  187. package/routes/comfy.ts +10 -6
  188. package/routes/edit.js +38 -7
  189. package/routes/edit.ts +63 -12
  190. package/routes/generate.js +71 -23
  191. package/routes/generate.ts +85 -31
  192. package/routes/health.js +9 -6
  193. package/routes/health.ts +17 -13
  194. package/routes/history.js +85 -36
  195. package/routes/history.ts +112 -44
  196. package/routes/imageImport.js +6 -2
  197. package/routes/imageImport.ts +9 -5
  198. package/routes/index.js +5 -1
  199. package/routes/index.ts +6 -1
  200. package/routes/metadata.js +9 -4
  201. package/routes/metadata.ts +13 -7
  202. package/routes/multimode.js +161 -38
  203. package/routes/multimode.ts +216 -58
  204. package/routes/nodes.js +55 -26
  205. package/routes/nodes.ts +108 -40
  206. package/routes/promptImport.js +42 -36
  207. package/routes/promptImport.ts +89 -64
  208. package/routes/prompts.js +62 -39
  209. package/routes/prompts.ts +120 -71
  210. package/routes/sessions.js +46 -24
  211. package/routes/sessions.ts +60 -35
  212. package/routes/storage.js +4 -2
  213. package/routes/storage.ts +13 -5
  214. package/server.js +25 -21
  215. package/server.ts +57 -37
  216. package/skills/ima2/SKILL.md +206 -0
  217. package/ui/dist/.vite/manifest.json +11 -10
  218. package/ui/dist/assets/{CardNewsWorkspace-BJOCey7Z.js → CardNewsWorkspace-j4ULtNdk.js} +1 -1
  219. package/ui/dist/assets/NodeCanvas-Bc7BUViM.js +7 -0
  220. package/ui/dist/assets/{PromptImportDialog-Dqu1VpUh.js → PromptImportDialog-DBKprBEo.js} +2 -2
  221. package/ui/dist/assets/{PromptImportDiscoverySection-Dg8T9X0L.js → PromptImportDiscoverySection-m5v55Zsy.js} +1 -1
  222. package/ui/dist/assets/PromptImportFolderSection-DnPvJkfJ.js +1 -0
  223. package/ui/dist/assets/PromptLibraryPanel-BMSqfK9C.js +2 -0
  224. package/ui/dist/assets/SettingsWorkspace-Cj3LD0uu.js +1 -0
  225. package/ui/dist/assets/{index-Cvld7dUZ.js → index-9aOJKFI-.js} +1 -1
  226. package/ui/dist/assets/index-De-AWE6B.css +1 -0
  227. package/ui/dist/assets/index-tQhOLR-C.js +28 -0
  228. package/ui/dist/index.html +2 -2
  229. package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +0 -7
  230. package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +0 -1
  231. package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +0 -2
  232. package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +0 -1
  233. package/ui/dist/assets/index-C9cXwiWE.js +0 -25
  234. package/ui/dist/assets/index-CGMIkZXn.css +0 -1
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # ima2-gen
2
2
 
3
+ <p align="center">
4
+ <img src="assets/logo.png" alt="ima2-gen logo" width="240">
5
+ </p>
6
+
3
7
  [![npm version](https://img.shields.io/npm/v/ima2-gen)](https://www.npmjs.com/package/ima2-gen)
4
8
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
5
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
@@ -49,7 +53,7 @@ persists, reboot and run the update before starting ima2 again.
49
53
  - **Node mode**: branch a good image into multiple directions without losing the original.
50
54
  - **Multimode batches**: launch several Classic outputs from one prompt, watch slot-by-slot progress, and continue from the best result.
51
55
  - **Canvas Mode**: zoom, pan, annotate, erase, clean backgrounds, keep transparent previews, and export either alpha or matte-backed versions.
52
- - **Local gallery**: keep generated assets on your machine with session-aware history.
56
+ - **Local gallery**: keep generated assets on your machine with session-aware history. By default the gallery shows the current session and an All Images toggle reveals the full history; the default scope is sticky across sessions.
53
57
  - **Reference images**: drag, drop, paste, and attach up to 5 references; large images are compressed before upload.
54
58
  - **Prompt library imports**: import local prompt packs, GitHub folders, and curated GPT-image prompt hints into the built-in prompt library.
55
59
  - **Mobile shell**: use the app bar, compose sheet, and compact settings toggle on smaller screens.
@@ -192,6 +196,11 @@ environment variables > ~/.ima2/config.json > built-in defaults
192
196
  | `IMA2_LOG_LEVEL` | `warn` | Normal serve defaults to `warn`; dev mode defaults to `debug`; supports `debug`, `info`, `warn`, `error`, or `silent` |
193
197
  | `IMA2_INFLIGHT_TERMINAL_TTL_MS` | `30000` | Recent terminal job retention for debug views |
194
198
  | `OPENAI_API_KEY` | — | API key for the `provider: "api"` Responses API image path and auxiliary API-key features |
199
+ | `IMA2_API_IMAGE_MODEL_DEFAULT` | `gpt-5.4-mini` | Default image model for `provider: "api"` |
200
+ | `IMA2_API_REASONING_EFFORT` | `low` | Default reasoning effort for `provider: "api"` |
201
+ | `IMA2_API_IMAGE_SIZE` | `1024x1024` | Default size for `provider: "api"` |
202
+ | `IMA2_API_ALLOW_WEB_SEARCH` | `true` | Toggle web search for `provider: "api"` |
203
+ | `IMA2_OAUTH_MASKED_EDIT_ENABLED` | `false` | Opt-in feature flag for masked-edit requests on the OAuth path (#31, groundwork only) |
195
204
 
196
205
  ### Logging modes
197
206
 
@@ -31,6 +31,8 @@ async function getServer(args) {
31
31
  async function resolveBody(value) {
32
32
  if (!value)
33
33
  return null;
34
+ if (typeof value !== "string")
35
+ return null;
34
36
  let text;
35
37
  if (value === "-")
36
38
  text = await readStdin();
@@ -72,7 +74,7 @@ async function getSub(argv) {
72
74
  const browserId = getCliBrowserId();
73
75
  const resp = await request(server.base, `/api/annotations/${encodeURIComponent(filename)}`, {
74
76
  headers: { "X-Ima2-Browser-Id": browserId },
75
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
77
+ }).catch((e) => { const err = e; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
76
78
  json(resp);
77
79
  }
78
80
  async function setSub(argv) {
@@ -89,7 +91,7 @@ async function setSub(argv) {
89
91
  method: "PUT",
90
92
  body,
91
93
  headers: { "X-Ima2-Browser-Id": browserId },
92
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
94
+ }).catch((e) => { const err = e; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
93
95
  if (args.json) {
94
96
  json(resp);
95
97
  return;
@@ -116,7 +118,7 @@ async function rmSub(argv) {
116
118
  await request(server.base, `/api/annotations/${encodeURIComponent(filename)}`, {
117
119
  method: "DELETE",
118
120
  headers: { "X-Ima2-Browser-Id": browserId },
119
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
121
+ }).catch((e) => { const err = e; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
120
122
  out(color.green("✓ deleted"));
121
123
  }
122
124
  const SUB = {
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "fs/promises";
2
- import { parseArgs } from "../lib/args.js";
2
+ import { parseArgs, type ParsedArgs } from "../lib/args.js";
3
3
  import { resolveServer, request } from "../lib/client.js";
4
4
  import { readStdin } from "../lib/files.js";
5
5
  import { out, die, color, json, exitCodeForError } from "../lib/output.js";
@@ -22,14 +22,15 @@ const FLAGS = {
22
22
  help: { short: "h", type: "boolean" },
23
23
  };
24
24
 
25
- async function getServer(args) {
25
+ async function getServer(args: ParsedArgs) {
26
26
  try { return await resolveServer({ serverFlag: args.server }); }
27
27
  catch (e: any) { die(exitCodeForError(e), e.message); throw e; }
28
28
  }
29
29
 
30
- async function resolveBody(value): Promise<any> {
30
+ async function resolveBody(value: unknown): Promise<any> {
31
31
  if (!value) return null;
32
- let text;
32
+ if (typeof value !== "string") return null;
33
+ let text: string;
33
34
  if (value === "-") text = await readStdin();
34
35
  else if (value.startsWith("@")) text = await readFile(value.slice(1), "utf-8");
35
36
  else text = value;
@@ -41,7 +42,7 @@ async function readLine(): Promise<string> {
41
42
  return new Promise((resolve) => {
42
43
  let buf = "";
43
44
  process.stdin.setEncoding("utf-8");
44
- const onData = (chunk) => {
45
+ const onData = (chunk: Buffer | string) => {
45
46
  buf += chunk;
46
47
  const nl = buf.indexOf("\n");
47
48
  if (nl !== -1) {
@@ -55,7 +56,7 @@ async function readLine(): Promise<string> {
55
56
  });
56
57
  }
57
58
 
58
- async function getSub(argv) {
59
+ async function getSub(argv: string[]) {
59
60
  const args = parseArgs(argv, { flags: FLAGS });
60
61
  const filename = args.positional[0];
61
62
  if (!filename) die(2, "filename required");
@@ -63,11 +64,11 @@ async function getSub(argv) {
63
64
  const browserId = getCliBrowserId();
64
65
  const resp = await request(server.base, `/api/annotations/${encodeURIComponent(filename)}`, {
65
66
  headers: { "X-Ima2-Browser-Id": browserId },
66
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
67
+ }).catch((e: unknown) => { const err = e as { message?: string; code?: string }; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
67
68
  json(resp);
68
69
  }
69
70
 
70
- async function setSub(argv) {
71
+ async function setSub(argv: string[]) {
71
72
  const args = parseArgs(argv, { flags: FLAGS });
72
73
  const filename = args.positional[0];
73
74
  if (!filename) die(2, "filename required");
@@ -79,12 +80,12 @@ async function setSub(argv) {
79
80
  method: "PUT",
80
81
  body,
81
82
  headers: { "X-Ima2-Browser-Id": browserId },
82
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
83
+ }).catch((e: unknown) => { const err = e as { message?: string; code?: string }; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
83
84
  if (args.json) { json(resp); return; }
84
85
  out(color.green("✓ annotation saved"));
85
86
  }
86
87
 
87
- async function rmSub(argv) {
88
+ async function rmSub(argv: string[]) {
88
89
  const args = parseArgs(argv, { flags: FLAGS });
89
90
  const filename = args.positional[0];
90
91
  if (!filename) die(2, "filename required");
@@ -99,7 +100,7 @@ async function rmSub(argv) {
99
100
  await request(server.base, `/api/annotations/${encodeURIComponent(filename)}`, {
100
101
  method: "DELETE",
101
102
  headers: { "X-Ima2-Browser-Id": browserId },
102
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
103
+ }).catch((e: unknown) => { const err = e as { message?: string; code?: string }; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
103
104
  out(color.green("✓ deleted"));
104
105
  }
105
106
 
@@ -109,7 +110,7 @@ const SUB: Record<string, (argv: any[]) => Promise<void>> = {
109
110
  rm: rmSub,
110
111
  };
111
112
 
112
- export default async function annotateCmd(argv) {
113
+ export default async function annotateCmd(argv: string[]) {
113
114
  const sub = argv[0];
114
115
  if (!sub || sub === "--help" || sub === "-h") { out(HELP); return; }
115
116
  const handler = SUB[sub];
@@ -1,6 +1,7 @@
1
1
  import { parseArgs } from "../lib/args.js";
2
2
  import { resolveServer, request } from "../lib/client.js";
3
3
  import { out, die, dieWithError, color, json } from "../lib/output.js";
4
+ import { errInfo } from "../../lib/errInfo.js";
4
5
  const SPEC = {
5
6
  flags: {
6
7
  json: { type: "boolean" },
@@ -27,8 +28,9 @@ export default async function cancelCmd(argv) {
27
28
  server = await resolveServer({ serverFlag: args.server });
28
29
  }
29
30
  catch (e) {
31
+ const err = errInfo(e);
30
32
  if (args.json)
31
- json({ ok: false, requestId, error: e.message, code: e.code, status: e.status });
33
+ json({ ok: false, requestId, error: err.message, code: err.code, status: err.status });
32
34
  dieWithError(e);
33
35
  }
34
36
  try {
@@ -38,8 +40,9 @@ export default async function cancelCmd(argv) {
38
40
  });
39
41
  }
40
42
  catch (e) {
43
+ const err = errInfo(e);
41
44
  if (args.json)
42
- json({ ok: false, requestId, error: e.message, code: e.code, status: e.status });
45
+ json({ ok: false, requestId, error: err.message, code: err.code, status: err.status });
43
46
  dieWithError(e);
44
47
  }
45
48
  if (args.json)
@@ -2,6 +2,7 @@ import { parseArgs } from "../lib/args.js";
2
2
  import { resolveServer, request } from "../lib/client.js";
3
3
  import { out, die, dieWithError, color, json } from "../lib/output.js";
4
4
 
5
+ import { errInfo } from "../../lib/errInfo.js";
5
6
  const SPEC = {
6
7
  flags: {
7
8
  json: { type: "boolean" },
@@ -16,7 +17,7 @@ const HELP = `
16
17
  Mark an in-flight job as canceled in the local ima2 server registry.
17
18
  `;
18
19
 
19
- export default async function cancelCmd(argv) {
20
+ export default async function cancelCmd(argv: string[]) {
20
21
  const args = parseArgs(argv, SPEC);
21
22
  if (args.help) { out(HELP); return; }
22
23
 
@@ -26,7 +27,8 @@ export default async function cancelCmd(argv) {
26
27
  let server;
27
28
  try { server = await resolveServer({ serverFlag: args.server }); }
28
29
  catch (e) {
29
- if (args.json) json({ ok: false, requestId, error: e.message, code: e.code, status: e.status });
30
+ const err = errInfo(e);
31
+ if (args.json) json({ ok: false, requestId, error: err.message, code: err.code, status: err.status });
30
32
  dieWithError(e);
31
33
  }
32
34
 
@@ -36,7 +38,8 @@ export default async function cancelCmd(argv) {
36
38
  timeoutMs: 30_000,
37
39
  });
38
40
  } catch (e) {
39
- if (args.json) json({ ok: false, requestId, error: e.message, code: e.code, status: e.status });
41
+ const err = errInfo(e);
42
+ if (args.json) json({ ok: false, requestId, error: err.message, code: err.code, status: err.status });
40
43
  dieWithError(e);
41
44
  }
42
45
 
@@ -31,9 +31,9 @@ async function getServer(args) {
31
31
  function buildHeaders(args) {
32
32
  const h = { "Content-Type": "image/png" };
33
33
  if (args.source)
34
- h["X-Ima2-Canvas-Source-Filename"] = args.source;
34
+ h["X-Ima2-Canvas-Source-Filename"] = String(args.source);
35
35
  if (args.prompt)
36
- h["X-Ima2-Canvas-Prompt"] = args.prompt;
36
+ h["X-Ima2-Canvas-Prompt"] = String(args.prompt);
37
37
  return h;
38
38
  }
39
39
  async function saveSub(argv) {
@@ -48,7 +48,7 @@ async function saveSub(argv) {
48
48
  body: buf,
49
49
  raw: true,
50
50
  headers: buildHeaders(args),
51
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
51
+ }).catch((e) => { const err = e; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
52
52
  if (args.json) {
53
53
  json(resp);
54
54
  return;
@@ -67,7 +67,7 @@ async function updateSub(argv) {
67
67
  body: buf,
68
68
  raw: true,
69
69
  headers: buildHeaders(args),
70
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
70
+ }).catch((e) => { const err = e; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
71
71
  if (args.json) {
72
72
  json(resp);
73
73
  return;
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "fs/promises";
2
- import { parseArgs } from "../lib/args.js";
2
+ import { parseArgs, type ParsedArgs } from "../lib/args.js";
3
3
  import { resolveServer, request } from "../lib/client.js";
4
4
  import { out, die, color, json, exitCodeForError } from "../lib/output.js";
5
5
 
@@ -22,19 +22,19 @@ const FLAGS = {
22
22
  help: { short: "h", type: "boolean" },
23
23
  };
24
24
 
25
- async function getServer(args) {
25
+ async function getServer(args: ParsedArgs) {
26
26
  try { return await resolveServer({ serverFlag: args.server }); }
27
27
  catch (e: any) { die(exitCodeForError(e), e.message); throw e; }
28
28
  }
29
29
 
30
- function buildHeaders(args) {
30
+ function buildHeaders(args: ParsedArgs) {
31
31
  const h: Record<string, string> = { "Content-Type": "image/png" };
32
- if (args.source) h["X-Ima2-Canvas-Source-Filename"] = args.source;
33
- if (args.prompt) h["X-Ima2-Canvas-Prompt"] = args.prompt;
32
+ if (args.source) h["X-Ima2-Canvas-Source-Filename"] = String(args.source);
33
+ if (args.prompt) h["X-Ima2-Canvas-Prompt"] = String(args.prompt);
34
34
  return h;
35
35
  }
36
36
 
37
- async function saveSub(argv) {
37
+ async function saveSub(argv: string[]) {
38
38
  const args = parseArgs(argv, { flags: FLAGS });
39
39
  const file = args.positional[0];
40
40
  if (!file) die(2, "image file required");
@@ -45,12 +45,12 @@ async function saveSub(argv) {
45
45
  body: buf,
46
46
  raw: true,
47
47
  headers: buildHeaders(args),
48
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
48
+ }).catch((e: unknown) => { const err = e as { message?: string; code?: string }; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
49
49
  if (args.json) { json(resp); return; }
50
50
  out(color.green("✓ saved canvas version"));
51
51
  }
52
52
 
53
- async function updateSub(argv) {
53
+ async function updateSub(argv: string[]) {
54
54
  const args = parseArgs(argv, { flags: FLAGS });
55
55
  const [filename, file] = args.positional;
56
56
  if (!filename || !file) die(2, "usage: canvas-versions update <filename> <imagefile>");
@@ -61,7 +61,7 @@ async function updateSub(argv) {
61
61
  body: buf,
62
62
  raw: true,
63
63
  headers: buildHeaders(args),
64
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
64
+ }).catch((e: unknown) => { const err = e as { message?: string; code?: string }; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
65
65
  if (args.json) { json(resp); return; }
66
66
  out(color.green("✓ updated"));
67
67
  }
@@ -71,7 +71,7 @@ const SUB: Record<string, (argv: any[]) => Promise<void>> = {
71
71
  update: updateSub,
72
72
  };
73
73
 
74
- export default async function canvasVersionsCmd(argv) {
74
+ export default async function canvasVersionsCmd(argv: string[]) {
75
75
  const sub = argv[0];
76
76
  if (!sub || sub === "--help" || sub === "-h") { out(HELP); return; }
77
77
  const handler = SUB[sub];
@@ -0,0 +1,90 @@
1
+ import { readFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { config } from "../../config.js";
5
+ import { buildIma2Capabilities } from "../../lib/capabilities.js";
6
+ import { parseArgs } from "../lib/args.js";
7
+ import { resolveServer, request } from "../lib/client.js";
8
+ import { color, dieWithError, json, out } from "../lib/output.js";
9
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
10
+ const PACKAGE_PATH = join(ROOT, "package.json");
11
+ const HELP = `
12
+ ima2 capabilities [--json] [--server <url>] [--require-server]
13
+
14
+ Print agent-friendly capability metadata.
15
+
16
+ Options:
17
+ --json Print JSON
18
+ --server <url> Query a specific running server
19
+ --require-server Fail instead of falling back to local package metadata
20
+ `;
21
+ const FLAGS = {
22
+ json: { type: "boolean" },
23
+ server: { type: "string" },
24
+ "require-server": { type: "boolean" },
25
+ help: { short: "h", type: "boolean" },
26
+ };
27
+ function packageVersion() {
28
+ try {
29
+ const pkg = JSON.parse(readFileSync(PACKAGE_PATH, "utf-8"));
30
+ return pkg.version || "?";
31
+ }
32
+ catch {
33
+ return "?";
34
+ }
35
+ }
36
+ function localCapabilities() {
37
+ return buildIma2Capabilities({
38
+ appConfig: config,
39
+ packageVersion: packageVersion(),
40
+ source: "local",
41
+ server: null,
42
+ });
43
+ }
44
+ async function readCapabilities(args) {
45
+ try {
46
+ const server = await resolveServer({ serverFlag: args.server });
47
+ return await request(server.base, "/api/capabilities", { timeoutMs: 5000 });
48
+ }
49
+ catch (error) {
50
+ if (args.server || args["require-server"])
51
+ throw error;
52
+ return localCapabilities();
53
+ }
54
+ }
55
+ function printText(capabilities) {
56
+ out(`ima2 capabilities (${capabilities.source})`);
57
+ out(`version: ${capabilities.version}`);
58
+ out(`server: ${capabilities.server || "none"}`);
59
+ out("");
60
+ out("defaults:");
61
+ out(` oauth model: ${capabilities.defaults?.oauth?.model}`);
62
+ out(` oauth reasoning: ${capabilities.defaults?.oauth?.reasoningEffort}`);
63
+ out(` api model: ${capabilities.defaults?.api?.model}`);
64
+ out(` api reasoning: ${capabilities.defaults?.api?.reasoningEffort}`);
65
+ out("");
66
+ out("valid:");
67
+ out(` models: ${capabilities.valid?.imageModels?.supported?.join(", ")}`);
68
+ out(` reasoning: ${capabilities.valid?.reasoningEfforts?.join(", ")}`);
69
+ out(` quality: ${capabilities.valid?.quality?.join(", ")}`);
70
+ out("");
71
+ out(`limits: refs=${capabilities.limits?.maxRefCount}, images=${capabilities.limits?.maxGeneratedImages}`);
72
+ out(color.dim(`maxParallel: ${capabilities.limits?.maxParallel?.value} (${capabilities.limits?.maxParallel?.note})`));
73
+ }
74
+ export default async function capabilitiesCmd(argv) {
75
+ const args = parseArgs(argv, { flags: FLAGS });
76
+ if (args.help) {
77
+ out(HELP);
78
+ return;
79
+ }
80
+ try {
81
+ const capabilities = await readCapabilities(args);
82
+ if (args.json)
83
+ json(capabilities);
84
+ else
85
+ printText(capabilities);
86
+ }
87
+ catch (error) {
88
+ dieWithError(error);
89
+ }
90
+ }
@@ -0,0 +1,93 @@
1
+ import { readFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { config } from "../../config.js";
5
+ import { buildIma2Capabilities } from "../../lib/capabilities.js";
6
+ import { parseArgs } from "../lib/args.js";
7
+ import { resolveServer, request } from "../lib/client.js";
8
+ import { color, dieWithError, json, out } from "../lib/output.js";
9
+
10
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
11
+ const PACKAGE_PATH = join(ROOT, "package.json");
12
+
13
+ const HELP = `
14
+ ima2 capabilities [--json] [--server <url>] [--require-server]
15
+
16
+ Print agent-friendly capability metadata.
17
+
18
+ Options:
19
+ --json Print JSON
20
+ --server <url> Query a specific running server
21
+ --require-server Fail instead of falling back to local package metadata
22
+ `;
23
+
24
+ const FLAGS = {
25
+ json: { type: "boolean" },
26
+ server: { type: "string" },
27
+ "require-server": { type: "boolean" },
28
+ help: { short: "h", type: "boolean" },
29
+ };
30
+
31
+ function packageVersion(): string {
32
+ try {
33
+ const pkg = JSON.parse(readFileSync(PACKAGE_PATH, "utf-8")) as { version?: string };
34
+ return pkg.version || "?";
35
+ } catch {
36
+ return "?";
37
+ }
38
+ }
39
+
40
+ function localCapabilities() {
41
+ return buildIma2Capabilities({
42
+ appConfig: config,
43
+ packageVersion: packageVersion(),
44
+ source: "local",
45
+ server: null,
46
+ });
47
+ }
48
+
49
+ async function readCapabilities(args: ReturnType<typeof parseArgs>) {
50
+ try {
51
+ const server = await resolveServer({ serverFlag: args.server });
52
+ return await request(server.base, "/api/capabilities", { timeoutMs: 5000 });
53
+ } catch (error) {
54
+ if (args.server || args["require-server"]) throw error;
55
+ return localCapabilities();
56
+ }
57
+ }
58
+
59
+ function printText(capabilities: any): void {
60
+ out(`ima2 capabilities (${capabilities.source})`);
61
+ out(`version: ${capabilities.version}`);
62
+ out(`server: ${capabilities.server || "none"}`);
63
+ out("");
64
+ out("defaults:");
65
+ out(` oauth model: ${capabilities.defaults?.oauth?.model}`);
66
+ out(` oauth reasoning: ${capabilities.defaults?.oauth?.reasoningEffort}`);
67
+ out(` api model: ${capabilities.defaults?.api?.model}`);
68
+ out(` api reasoning: ${capabilities.defaults?.api?.reasoningEffort}`);
69
+ out("");
70
+ out("valid:");
71
+ out(` models: ${capabilities.valid?.imageModels?.supported?.join(", ")}`);
72
+ out(` reasoning: ${capabilities.valid?.reasoningEfforts?.join(", ")}`);
73
+ out(` quality: ${capabilities.valid?.quality?.join(", ")}`);
74
+ out("");
75
+ out(`limits: refs=${capabilities.limits?.maxRefCount}, images=${capabilities.limits?.maxGeneratedImages}`);
76
+ out(color.dim(`maxParallel: ${capabilities.limits?.maxParallel?.value} (${capabilities.limits?.maxParallel?.note})`));
77
+ }
78
+
79
+ export default async function capabilitiesCmd(argv: string[]) {
80
+ const args = parseArgs(argv, { flags: FLAGS });
81
+ if (args.help) {
82
+ out(HELP);
83
+ return;
84
+ }
85
+
86
+ try {
87
+ const capabilities = await readCapabilities(args);
88
+ if (args.json) json(capabilities);
89
+ else printText(capabilities);
90
+ } catch (error) {
91
+ dieWithError(error);
92
+ }
93
+ }
@@ -51,6 +51,8 @@ async function getServer(args) {
51
51
  function parseData(raw) {
52
52
  if (!raw)
53
53
  return {};
54
+ if (typeof raw !== "string")
55
+ return {};
54
56
  try {
55
57
  return JSON.parse(raw);
56
58
  }
@@ -201,7 +203,7 @@ async function jobRetrySub(argv) {
201
203
  const server = await getServer(args);
202
204
  const body = {};
203
205
  if (args.cards)
204
- body.cardIds = args.cards.split(",").map((s) => s.trim()).filter(Boolean);
206
+ body.cardIds = String(args.cards).split(",").map((s) => s.trim()).filter(Boolean);
205
207
  const resp = await request(server.base, `/api/cardnews/jobs/${encodeURIComponent(jobId)}/retry`, { method: "POST", body })
206
208
  .catch((e) => die(exitCodeForError(e), e.message));
207
209
  if (args.json) {
@@ -1,4 +1,4 @@
1
- import { parseArgs } from "../lib/args.js";
1
+ import { parseArgs, type ParsedArgs } from "../lib/args.js";
2
2
  import { resolveServer, request } from "../lib/client.js";
3
3
  import { out, die, color, json, exitCodeForError } from "../lib/output.js";
4
4
  import { config as runtimeConfig } from "../../config.js";
@@ -43,18 +43,19 @@ function ensureCardNewsEnabled() {
43
43
  }
44
44
  }
45
45
 
46
- async function getServer(args) {
46
+ async function getServer(args: ParsedArgs) {
47
47
  try { return await resolveServer({ serverFlag: args.server }); }
48
48
  catch (e: any) { die(exitCodeForError(e), e.message); throw e; }
49
49
  }
50
50
 
51
- function parseData(raw: string | undefined): any {
51
+ function parseData(raw: unknown): any {
52
52
  if (!raw) return {};
53
+ if (typeof raw !== "string") return {};
53
54
  try { return JSON.parse(raw); }
54
55
  catch { die(2, `--data must be valid JSON`); }
55
56
  }
56
57
 
57
- async function templatesSub(argv) {
58
+ async function templatesSub(argv: string[]) {
58
59
  const args = parseArgs(argv, { flags: FLAGS });
59
60
  if (args.help) { out(HELP); return; }
60
61
  ensureCardNewsEnabled();
@@ -72,7 +73,7 @@ async function templatesSub(argv) {
72
73
  out(JSON.stringify(roleTemplates, null, 2));
73
74
  }
74
75
 
75
- async function templatePreviewSub(argv) {
76
+ async function templatePreviewSub(argv: string[]) {
76
77
  const args = parseArgs(argv, { flags: FLAGS });
77
78
  const templateId = args.positional[0];
78
79
  if (!templateId) die(2, "templateId required");
@@ -84,7 +85,7 @@ async function templatePreviewSub(argv) {
84
85
  out(JSON.stringify(resp, null, 2));
85
86
  }
86
87
 
87
- async function setsSub(argv) {
88
+ async function setsSub(argv: string[]) {
88
89
  const args = parseArgs(argv, { flags: FLAGS });
89
90
  ensureCardNewsEnabled();
90
91
  const server = await getServer(args);
@@ -94,7 +95,7 @@ async function setsSub(argv) {
94
95
  out(JSON.stringify(resp, null, 2));
95
96
  }
96
97
 
97
- async function setShowSub(argv) {
98
+ async function setShowSub(argv: string[]) {
98
99
  const args = parseArgs(argv, { flags: FLAGS });
99
100
  const setId = args.positional[0];
100
101
  if (!setId) die(2, "setId required");
@@ -106,7 +107,7 @@ async function setShowSub(argv) {
106
107
  out(JSON.stringify(resp, null, 2));
107
108
  }
108
109
 
109
- async function setManifestSub(argv) {
110
+ async function setManifestSub(argv: string[]) {
110
111
  const args = parseArgs(argv, { flags: FLAGS });
111
112
  const setId = args.positional[0];
112
113
  if (!setId) die(2, "setId required");
@@ -118,7 +119,7 @@ async function setManifestSub(argv) {
118
119
  out(JSON.stringify(resp, null, 2));
119
120
  }
120
121
 
121
- async function draftSub(argv) {
122
+ async function draftSub(argv: string[]) {
122
123
  const args = parseArgs(argv, { flags: FLAGS });
123
124
  ensureCardNewsEnabled();
124
125
  const server = await getServer(args);
@@ -129,7 +130,7 @@ async function draftSub(argv) {
129
130
  out(JSON.stringify(resp, null, 2));
130
131
  }
131
132
 
132
- async function generateSub(argv) {
133
+ async function generateSub(argv: string[]) {
133
134
  const args = parseArgs(argv, { flags: FLAGS });
134
135
  ensureCardNewsEnabled();
135
136
  const server = await getServer(args);
@@ -140,7 +141,7 @@ async function generateSub(argv) {
140
141
  out(JSON.stringify(resp, null, 2));
141
142
  }
142
143
 
143
- async function jobCreateSub(argv) {
144
+ async function jobCreateSub(argv: string[]) {
144
145
  const args = parseArgs(argv, { flags: FLAGS });
145
146
  ensureCardNewsEnabled();
146
147
  const server = await getServer(args);
@@ -151,7 +152,7 @@ async function jobCreateSub(argv) {
151
152
  out(JSON.stringify(resp, null, 2));
152
153
  }
153
154
 
154
- async function jobShowSub(argv) {
155
+ async function jobShowSub(argv: string[]) {
155
156
  const args = parseArgs(argv, { flags: FLAGS });
156
157
  const jobId = args.positional[0];
157
158
  if (!jobId) die(2, "jobId required");
@@ -163,14 +164,14 @@ async function jobShowSub(argv) {
163
164
  out(JSON.stringify(resp, null, 2));
164
165
  }
165
166
 
166
- async function jobRetrySub(argv) {
167
+ async function jobRetrySub(argv: string[]) {
167
168
  const args = parseArgs(argv, { flags: FLAGS });
168
169
  const jobId = args.positional[0];
169
170
  if (!jobId) die(2, "jobId required");
170
171
  ensureCardNewsEnabled();
171
172
  const server = await getServer(args);
172
173
  const body: any = {};
173
- if (args.cards) body.cardIds = args.cards.split(",").map((s) => s.trim()).filter(Boolean);
174
+ if (args.cards) body.cardIds = String(args.cards).split(",").map((s: string) => s.trim()).filter(Boolean);
174
175
  const resp = await request(server.base, `/api/cardnews/jobs/${encodeURIComponent(jobId)}/retry`, { method: "POST", body })
175
176
  .catch((e) => die(exitCodeForError(e), e.message));
176
177
  if (args.json) { json(resp); return; }
@@ -178,7 +179,7 @@ async function jobRetrySub(argv) {
178
179
  if (resp && typeof resp === "object" && Object.keys(resp).length > 0) out(JSON.stringify(resp, null, 2));
179
180
  }
180
181
 
181
- async function cardRegenerateSub(argv) {
182
+ async function cardRegenerateSub(argv: string[]) {
182
183
  const args = parseArgs(argv, { flags: FLAGS });
183
184
  const cardId = args.positional[0];
184
185
  if (!cardId) die(2, "cardId required");
@@ -192,7 +193,7 @@ async function cardRegenerateSub(argv) {
192
193
  if (resp && typeof resp === "object" && Object.keys(resp).length > 0) out(JSON.stringify(resp, null, 2));
193
194
  }
194
195
 
195
- async function exportSub(argv) {
196
+ async function exportSub(argv: string[]) {
196
197
  const args = parseArgs(argv, { flags: FLAGS });
197
198
  ensureCardNewsEnabled();
198
199
  const server = await getServer(args);
@@ -206,7 +207,7 @@ async function exportSub(argv) {
206
207
 
207
208
  type Sub = (argv: any[]) => Promise<void>;
208
209
 
209
- export default async function cardnewsCmd(argv) {
210
+ export default async function cardnewsCmd(argv: string[]) {
210
211
  const sub = argv[0];
211
212
  if (!sub || sub === "--help" || sub === "-h") { out(HELP); return; }
212
213
 
@@ -31,8 +31,8 @@ async function exportSub(argv) {
31
31
  const resp = await request(server.base, "/api/comfy/export-image", {
32
32
  method: "POST",
33
33
  body: { filename },
34
- }).catch((e) => die(exitCodeForError(e), `${e.message}${e.code ? ` (${e.code})` : ""}`));
35
- const target = args.out || `${filename}.workflow.json`;
34
+ }).catch((e) => { const err = e; die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`); });
35
+ const target = String(args.out || `${filename}.workflow.json`);
36
36
  if (!args.force) {
37
37
  try {
38
38
  await access(target);