ima2-gen 2.0.1 → 2.0.3

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 (100) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +10 -1
  3. package/bin/commands/backfillThumbs.js +6 -0
  4. package/bin/commands/gen.js +13 -5
  5. package/bin/commands/multimode.js +7 -5
  6. package/bin/commands/node.js +2 -1
  7. package/bin/ima2.js +14 -10
  8. package/config.js +8 -1
  9. package/docs/API.md +133 -9
  10. package/docs/CLI.md +6 -5
  11. package/docs/FAQ.ko.md +16 -0
  12. package/docs/FAQ.md +30 -0
  13. package/docs/README.ko.md +7 -3
  14. package/docs/migration/runtime-test-inventory.md +25 -1
  15. package/lib/agentCommandParser.js +10 -6
  16. package/lib/agentGenerationPlanner.js +96 -19
  17. package/lib/agentImageVideoGen.js +281 -0
  18. package/lib/agentPlannerModel.js +172 -0
  19. package/lib/agentQuestionResponder.js +9 -3
  20. package/lib/agentQueueStore.js +42 -0
  21. package/lib/agentQueueWorker.js +57 -7
  22. package/lib/agentRuntime.js +103 -274
  23. package/lib/agentSettings.js +9 -6
  24. package/lib/agentToolManifest.js +90 -0
  25. package/lib/agentTypes.js +1 -0
  26. package/lib/agyCli.js +31 -0
  27. package/lib/agyImageAdapter.js +104 -12
  28. package/lib/capabilities.js +7 -5
  29. package/lib/configKeys.js +6 -0
  30. package/lib/errorClassify.js +8 -7
  31. package/lib/eventBus.js +71 -0
  32. package/lib/geminiApiImageAdapter.js +24 -21
  33. package/lib/generationErrors.js +3 -1
  34. package/lib/generationRequestLog.js +25 -0
  35. package/lib/grokImageAdapter.js +76 -144
  36. package/lib/grokImageCore.js +153 -0
  37. package/lib/grokMultimodeAdapter.js +7 -4
  38. package/lib/grokVideoAdapter.js +3 -2
  39. package/lib/grokVideoCanvas.js +13 -0
  40. package/lib/grokVideoPlannerPrompt.js +52 -12
  41. package/lib/historyList.js +1 -0
  42. package/lib/inflight.js +54 -17
  43. package/lib/multimodeHelpers.js +11 -0
  44. package/lib/nodeHelpers.js +59 -0
  45. package/lib/oauthProxy/generators.js +6 -5
  46. package/lib/oauthProxy/prompts.js +60 -41
  47. package/lib/oauthProxy/streams.js +1 -1
  48. package/lib/promptBuilder/systemPrompt.js +2 -5
  49. package/lib/promptSafetyPolicy.js +1 -5
  50. package/lib/responsesDoctor.js +3 -3
  51. package/lib/responsesFallback.js +29 -15
  52. package/lib/responsesImageAdapter.js +11 -7
  53. package/lib/routeHelpers.js +44 -0
  54. package/lib/ssePublish.js +12 -0
  55. package/lib/storyboardPrefix.js +28 -0
  56. package/lib/thumbBackfill.js +16 -5
  57. package/node_modules/qs/CHANGELOG.md +10 -0
  58. package/node_modules/qs/README.md +1 -1
  59. package/node_modules/qs/dist/qs.js +15 -15
  60. package/node_modules/qs/eslint.config.mjs +1 -0
  61. package/node_modules/qs/lib/parse.js +52 -22
  62. package/node_modules/qs/lib/stringify.js +11 -4
  63. package/node_modules/qs/package.json +2 -2
  64. package/node_modules/qs/test/parse.js +49 -0
  65. package/node_modules/qs/test/stringify.js +129 -0
  66. package/package.json +4 -1
  67. package/routes/agent.js +19 -2
  68. package/routes/agy.js +48 -0
  69. package/routes/auth.js +6 -2
  70. package/routes/edit.js +7 -1
  71. package/routes/events.js +78 -0
  72. package/routes/generate.js +111 -128
  73. package/routes/generationRequestLog.js +16 -0
  74. package/routes/index.js +6 -0
  75. package/routes/keys.js +22 -2
  76. package/routes/multimode.js +100 -57
  77. package/routes/nodes.js +64 -108
  78. package/routes/video.js +100 -17
  79. package/skills/ima2/SKILL.md +98 -21
  80. package/ui/dist/.vite/manifest.json +22 -12
  81. package/ui/dist/assets/AgentWorkspace-qEMrATBP.js +3 -0
  82. package/ui/dist/assets/{CardNewsWorkspace-Dqyc1WZ1.js → CardNewsWorkspace-DoJnVHmn.js} +1 -1
  83. package/ui/dist/assets/GenerationRequestLogPanel-BDWRRLNV.js +1 -0
  84. package/ui/dist/assets/{NodeCanvas-ChEXzQbb.js → NodeCanvas-DIMo45sp.js} +3 -3
  85. package/ui/dist/assets/{PromptBuilderPanel-B95ZufnR.js → PromptBuilderPanel-BF3lcSJD.js} +2 -2
  86. package/ui/dist/assets/{PromptImportDialog-DGOwFQET.js → PromptImportDialog-Cy6ZymKG.js} +2 -2
  87. package/ui/dist/assets/{PromptImportDiscoverySection-CgvdnR49.js → PromptImportDiscoverySection-CzuW-8P2.js} +1 -1
  88. package/ui/dist/assets/PromptImportFolderSection-DP5ywnsv.js +1 -0
  89. package/ui/dist/assets/{PromptLibraryPanel-B9kndPw1.js → PromptLibraryPanel-CAsKr7CV.js} +2 -2
  90. package/ui/dist/assets/SettingsWorkspace-_PdPVsxi.js +1 -0
  91. package/ui/dist/assets/index-BUinlX2n.js +4 -0
  92. package/ui/dist/assets/index-CX3fge8X.css +1 -0
  93. package/ui/dist/assets/index-ygo6nfqx.js +23 -0
  94. package/ui/dist/index.html +2 -2
  95. package/ui/dist/assets/AgentWorkspace-CYv84Rus.js +0 -3
  96. package/ui/dist/assets/PromptImportFolderSection-CfUye9J8.js +0 -1
  97. package/ui/dist/assets/SettingsWorkspace-B3tgLrmF.js +0 -1
  98. package/ui/dist/assets/index-BhcvL0g-.js +0 -1
  99. package/ui/dist/assets/index-BtK3YhJc.js +0 -39
  100. package/ui/dist/assets/index-ClOLOjnA.css +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,151 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - **SSE multiplexing** — shared `GET /api/events` endpoint with ring-buffer replay and `Last-Event-ID` reconnect support (`lib/eventBus.ts`, `routes/events.ts`).
13
+ - **Async POST generation mode** — multimode, node, and video routes accept async POST and dual-emit progress on both per-request SSE and the shared event bus.
14
+ - **Frontend event channel** — singleton `EventSource` client (`ui/src/lib/eventChannel.ts`) replaces per-request SSE streams for UI generation flows.
15
+ - **Subscribe-before-fetch contract** — `tests/async-stream-subscribe-order.test.js` locks the race where ultra-fast server publish could arrive before client handler registration.
16
+ - Store modularization — split monolithic `useAppStore` into focused impl modules (`storeGenImpl`, `storeNodeGenImpl`, `storeVideoImpl`, `storeInflightImpl`, etc.).
17
+ - Frontend/API barrel splits — `ui/src/lib/api.ts` and `ui/src/index.css` decomposed into ≤500-line modules.
18
+ - Storyboard workflow — 9-panel grid with black Panel 1 lead-in for image and video generation.
19
+ - Gallery hang fix — video decoder/connection exhaustion on focus change (RCA 01).
20
+
21
+ ### Changed
22
+
23
+ - UI clients migrated from per-request SSE to `eventChannel` + async POST for multimode, node, and video generation.
24
+ - Multimode concurrency tracking uses `activeFlightIds` Set instead of `multimodeAbortControllers`.
25
+ - Test suite grew to **968** cases across **186** files (65 runtime-importing, 121 contract-only).
26
+
27
+ ### Fixed
28
+
29
+ - SSE multiplexing hardening — inflight cancel/done race guards, replay-gap handling, subscribe/timeout/requestId races, and frontend reconnect/error parsing (`sseStreamError.ts`).
30
+ - Node route validation order — `startJob`/202 response moved after request validation.
31
+ - CI typecheck — unused imports in card-news tests and store split type mismatches.
32
+ - Thumbnail backfill failure reporting (#94).
33
+ - AGY provider detection now finds user-local `agy` installs such as `~/.local/bin/agy` and supports `IMA2_AGY_BIN` for explicit binary paths.
34
+ - AGY Windows pipe handling, Gemini API aspect ratio string values, multimode same-prompt batching.
35
+ - Moderation over-filtering — removed safety tags and added error enrichment.
36
+
37
+ ## [2.0.1] - 2026-06-03
38
+
39
+ ### Added
40
+
41
+ - **Gemini API provider** (`provider: "gemini-api"`) — direct Generative Language API and Vertex AI paths with `nano-banana-2` / `nano-banana-pro` model picker, aspect ratio, and resolution controls.
42
+ - **Grok billing quota bar** — `$used/$limit` on QuotaCard via `GET /api/quota`.
43
+ - **Switch Account** — device-code OAuth re-auth for Grok and Codex without leaving the app.
44
+ - **Grok video model picker** — V / V1.5 selection in video controls.
45
+ - Image/video thumbnails and history sidebar cards.
46
+ - Centralized recursive thumbnail backfill.
47
+ - Gemini/Vertex API key management routes and web UI.
48
+
49
+ ### Changed
50
+
51
+ - Provider plumbing and CLI parity for gemini-api, grok-api, and vertex paths.
52
+ - Grok model/size pickers and adapter updates.
53
+ - Pages and developer docs reorganized to be feature-centric.
54
+
55
+ ### Fixed
56
+
57
+ - Preserve video metadata in sequence history and thumbnail fallbacks in history UI.
58
+ - Vertex AI integration — auth mode persistence, skip unsupported `response_format`, prefer Vertex over API key when both configured.
59
+ - Gemini image cost corrected to official pricing; aspect ratio/resolution UI layout polish.
60
+ - Skip GPT pixel-limit size confirm for Grok/Gemini providers.
61
+ - Reap orphaned codex device-auth child on abandoned Switch Account flow.
62
+ - Document Gemini providers in CLI help; harden provider paths and CLI metadata.
63
+
64
+ ### Security
65
+
66
+ - Atomic `config.json` writes in keys routes; atomic token write with codex env scrubbing.
67
+ - Cap sharp input pixels to prevent decompression bombs.
68
+ - Audit fixes — crypto session IDs, session cap, double-click guard, API key in header only.
69
+
70
+ ## [2.0.0] - 2026-06-02
71
+
72
+ ### Added
73
+
74
+ - Major version bump packaging the Gemini API, Grok API key, Vertex AI, and expanded provider surface shipped in the 1.1.x preview line.
75
+
76
+ ## [1.1.23] - 2026-06-02
77
+
78
+ ### Added
79
+
80
+ - Gallery skeleton shimmer and F5 refresh fix (#93).
81
+ - Hero one-click install scripts on the documentation site.
82
+
83
+ ## [1.1.22] - 2026-06-02
84
+
85
+ ### Fixed
86
+
87
+ - Graceful shutdown releases file handles on Windows (EBUSY fix).
88
+ - Ctrl+C clean shutdown — database close, child process stop, file lock release.
89
+
90
+ ## [1.1.21] - 2026-05-31
91
+
92
+ ### Changed
93
+
94
+ - Bump bundled progrok 0.1.1 → 0.2.0 (video edit + extend commands).
95
+
96
+ ## [1.1.15] - 2026-05-31
97
+
98
+ ### Added
99
+
100
+ - **Agent Mode** — conversational image workspace with sessions, turns, durable queue, slash commands (`/api/agent/*`).
101
+ - **Grok provider** — bundled progrok, Classic/Node/Agent through search + planner + xAI Images API.
102
+ - **Video generation** — text/image/reference-to-video via Grok, edit/extend/frame/analyze routes, branch-local last-frame continuation.
103
+ - `GET /api/capabilities` discovery endpoint (#62).
104
+ - `POST /api/prompt-builder/chat` assistant and `ima2 prompt build` CLI wrapper.
105
+ - Grok model/size pickers, billing API, and `ima2 grok` helpers.
106
+
107
+ ### Fixed
108
+
109
+ - Prompt Studio regression (#75), long-prompt preview (#77), prompt autofill perf (#78).
110
+ - Per-image metadata persistence (#79), batch comparison matrix (#80).
111
+
112
+ ## [1.1.10] - 2026-05-06
113
+
114
+ ### Added
115
+
116
+ - API-key provider Responses parity for generate/edit/multimode/node (#49).
117
+ - Masked-edit feature flag groundwork (`IMA2_OAUTH_MASKED_EDIT_ENABLED`, #31).
118
+ - Gallery default-to-current-session with All Images toggle (#42).
119
+ - Centralized `persistenceRegistry` for `ima2.*` localStorage keys (#43).
120
+ - `typecheck:tests` and `test:inventory` quality gates.
121
+
122
+ ### Changed
123
+
124
+ - Split `lib/oauthProxy.ts` into `lib/oauthProxy/*` subtree.
125
+ - Added `lib/runtimeContext.ts`, `lib/responsesImageAdapter.ts`, `lib/providerOptions.ts`, `lib/errInfo.ts`, `lib/promptSafetyPolicy.ts`.
126
+
127
+ ## [1.1.0] - 2026-04-25
128
+
129
+ ### Added
130
+
131
+ - TypeScript migration complete — route, lib, server, config, and bin sources are `*.ts` with committed build artifacts.
132
+ - CLI feature parity with server API (#45).
133
+ - Canvas Mode workspace split and dual-mask cleanup.
134
+ - OS-trash soft-delete for history.
135
+
136
+ ## [1.0.3] - 2026-04-23
137
+
138
+ ### Added
139
+
140
+ - Initial npm publish of `ima2-gen` — local OAuth image generation studio with Classic mode, Node mode, Canvas Mode, and CLI.
141
+
142
+ [Unreleased]: https://github.com/lidge-jun/ima2-gen/compare/v2.0.1...HEAD
143
+ [2.0.1]: https://github.com/lidge-jun/ima2-gen/compare/v2.0.0...v2.0.1
144
+ [2.0.0]: https://github.com/lidge-jun/ima2-gen/compare/v1.1.23...v2.0.0
145
+ [1.1.23]: https://github.com/lidge-jun/ima2-gen/compare/v1.1.22...v1.1.23
146
+ [1.1.22]: https://github.com/lidge-jun/ima2-gen/compare/v1.1.21...v1.1.22
147
+ [1.1.21]: https://github.com/lidge-jun/ima2-gen/compare/v1.1.20...v1.1.21
148
+ [1.1.15]: https://github.com/lidge-jun/ima2-gen/compare/v1.1.14...v1.1.15
149
+ [1.1.10]: https://github.com/lidge-jun/ima2-gen/compare/v1.1.9...v1.1.10
150
+ [1.1.0]: https://github.com/lidge-jun/ima2-gen/compare/v1.0.11...v1.1.0
151
+ [1.0.3]: https://github.com/lidge-jun/ima2-gen/releases/tag/v1.0.3
package/README.md CHANGED
@@ -69,7 +69,7 @@ Each script checks for nvm/fnm/brew/winget, installs Node LTS through the best a
69
69
  1. **GPT OAuth** — login with ChatGPT account (free, images only)
70
70
  2. **Grok OAuth** — login with xAI/Grok account (images + video)
71
71
  3. **Both** — GPT OAuth + Grok OAuth (full feature access)
72
- 4. **API Key** — paste your OpenAI API key (paid)
72
+ 4. **Web setup** — configure everything in the web UI
73
73
 
74
74
  Video generation requires Grok OAuth (option 2 or 3). Run `ima2 grok login` separately if you already have GPT OAuth configured and want to add video support; it defaults to the manual-paste flow.
75
75
 
@@ -97,6 +97,10 @@ Ctrl+C now performs a clean shutdown — closing the database, stopping child pr
97
97
  - **Mobile shell**: use the app bar, compose sheet, and compact settings toggle on smaller screens.
98
98
  - **Observable jobs**: active and recent jobs are tracked with safe logs and request IDs.
99
99
 
100
+ ### SSE Multiplexing
101
+
102
+ The web UI uses a single `GET /api/events` Server-Sent Events connection for all generation progress. Multimode, node, and video requests are submitted as async POST (`202 { requestId }`) and progress events are multiplexed through a shared event bus. This eliminates the browser 6-connection limit that previously caused gallery hangs during concurrent generation. CLI clients that do not send `async: true` still receive per-request SSE streams for backward compatibility.
103
+
100
104
  ## Provider Paths
101
105
 
102
106
  Image generation can run through the local Codex/ChatGPT OAuth path, a configured OpenAI API key, the bundled Grok provider, or the Gemini provider via Antigravity CLI.
@@ -105,11 +109,14 @@ Image generation can run through the local Codex/ChatGPT OAuth path, a configure
105
109
  - `provider: "api"` calls the OpenAI Responses API with the hosted `image_generation` tool.
106
110
  - `provider: "grok"` starts bundled `progrok` on `127.0.0.1:18645`, runs mandatory xAI Web Search plus a planner pass (default: `grok-4.3`, configurable in settings or via `--planner-model`), then calls xAI Images API through the local proxy.
107
111
  - `provider: "agy"` spawns the Antigravity CLI (`agy -p`) to generate images via Google Gemini's `default_api:generate_image` tool (model: `nano-banana-2`). Output is fixed at 1024×1024 JPEG, max 3 reference images. No web search, quality, or size controls.
112
+ - `provider: "gemini-api"` calls the Google Generative Language API directly. Supports two models: `nano-banana-2` (Gemini 3.1 Flash Image) and `nano-banana-pro` (Gemini 3 Pro Image). Auth is via `GEMINI_API_KEY` env var, web UI key management, or a Vertex AI service account JSON (`VERTEX_SERVICE_ACCOUNT_JSON`). When both an API key and Vertex credentials are configured, Vertex takes priority. Supports variable aspect ratios (1:1 through 21:9) and four resolution tiers (512px, 1K, 2K, 4K); these controls are only honored on the direct API path — the Vertex AI endpoint ignores aspect/size because it does not accept the `response_format` field. Per-model cost differs: `nano-banana-2` (Flash): 512=$0.001, 1K=$0.003, 2K=$0.004, 4K=$0.006; `nano-banana-pro`: 1K=$0.007, 2K=$0.007, 4K=$0.013. No web search or mask controls.
108
113
  - API-key generation supports classic generate, edit, mask-guided edit, multimode, and node generation.
109
114
  - Grok generation supports Classic, Node, and Agent flows. If a Classic reference, Node parent image, or Agent current image is present, ima2 switches the final Grok call to xAI image edit so image-to-image context is preserved.
110
115
 
111
116
  If no provider is specified, the app keeps the current GPT OAuth/default behavior. API-key generation defaults to `gpt-5.4-mini`, `low` reasoning, and `1024x1024` unless the request passes validated model, reasoning, size, or web-search options. Grok defaults to `grok-imagine-image`; `quality: "high"` promotes the final image call to `grok-imagine-image-quality`.
112
117
 
118
+ Grok image generation exposes a model picker (`grok-imagine-image` / `grok-imagine-image-quality`) and a size picker (aspect ratio + 1k/2k resolution). The Settings page shows a billing/quota bar with `$used/$limit` drawn from the Grok billing API, and a **Switch Account** button that starts a device-code OAuth flow (`POST /api/auth/switch`) for re-authenticating without leaving the app.
119
+
113
120
  Grok video generation uses `grok-imagine-video` (default) or `grok-imagine-video-1.5-preview`. Three modes are auto-detected from reference count: text-to-video (0 refs), image-to-video (1 ref), and reference-to-video (2–7 refs, max 10s duration). `grok-imagine-video-1.5-preview` supports image-to-video but not `reference_images` Ref2V, so 2+ refs use `grok-imagine-video` as the effective model. Video edit and extension are also base-model only. Video controls include duration (1–15s), resolution (480p, 720p), and aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, auto).
114
121
 
115
122
  ![Settings workspace showing GPT OAuth active and API key provider available.](assets/screenshots/settings-oauth-generation.png)
@@ -260,6 +267,8 @@ environment variables > ~/.ima2/config.json > built-in defaults
260
267
  | `IMA2_GROK_IMAGE_MODEL_DEFAULT` | `grok-imagine-image` | Default final Grok image model |
261
268
  | `IMA2_GROK_GENERATION_TIMEOUT_MS` | `120000` | Timeout for the final Grok Images API call |
262
269
  | `IMA2_OAUTH_MASKED_EDIT_ENABLED` | `false` | Opt-in feature flag for masked-edit requests on the OAuth path (#31, groundwork only) |
270
+ | `GEMINI_API_KEY` | — | API key for `provider: "gemini-api"` direct Generative Language API path |
271
+ | `VERTEX_SERVICE_ACCOUNT_JSON` | — | Google service account JSON for Vertex AI auth with `provider: "gemini-api"`; takes priority over `GEMINI_API_KEY` when both are set |
263
272
 
264
273
  ### Logging modes
265
274
 
@@ -15,4 +15,10 @@ export async function backfillThumbs() {
15
15
  if (r.created > 0)
16
16
  invalidateHistoryIndex();
17
17
  console.log(`[thumbs] Done: ${r.created} created, ${r.skipped} skipped (already exist), ${r.failed} failed out of ${r.total} media files.`);
18
+ if (r.failures.length > 0) {
19
+ console.log(`[thumbs] Showing ${r.failures.length} thumbnail failure(s):`);
20
+ for (const failure of r.failures) {
21
+ console.log(` - ${failure.kind}: ${failure.file} (${failure.reason})`);
22
+ }
23
+ }
18
24
  }
@@ -9,6 +9,8 @@ const VALID_MODES = new Set(["auto", "direct"]);
9
9
  const VALID_MODERATION = new Set(["auto", "low"]);
10
10
  const VALID_PROVIDERS = new Set(["auto", "oauth", "api", "grok", "grok-api", "agy", "gemini-api"]);
11
11
  const KNOWN_IMAGE_MODELS = new Set(["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark", "grok-imagine-image", "grok-imagine-image-quality", "nano-banana-2", "nano-banana-pro"]);
12
+ const MAX_GENERATION_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxGeneratedImages) || 24));
13
+ const MAX_REFERENCE_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxRefCount) || 5));
12
14
  const SPEC = {
13
15
  flags: {
14
16
  quality: { short: "q", type: "string", default: "low" },
@@ -39,11 +41,16 @@ const HELP = `
39
41
 
40
42
  Generate image(s) via the running ima2 server.
41
43
 
44
+ Batch/async note:
45
+ Use -n <N> for multiple candidates in one request. Independent CLI
46
+ commands can run concurrently against the server; monitor active requestIds
47
+ with 'ima2 ps --json' and stop one with 'ima2 cancel <requestId>'.
48
+
42
49
  Options:
43
50
  -q, --quality <low|medium|high> Default: low
44
51
  -s, --size <WxH | auto> Default: 1024x1024
45
- -n, --count <1..8> Default: 1
46
- --ref <file> Attach reference image (repeatable, max 5)
52
+ -n, --count <1..${MAX_GENERATION_COUNT}> Default: 1
53
+ --ref <file> Attach reference image (repeatable, max ${MAX_REFERENCE_COUNT})
47
54
  -o, --out <file> Single-image output path (implies -n 1)
48
55
  -d, --out-dir <dir> Output dir for multiple images
49
56
  --json Print JSON result to stdout
@@ -63,6 +70,7 @@ const HELP = `
63
70
 
64
71
  Examples:
65
72
  ima2 gen "a shiba in space"
73
+ ima2 gen "a shiba in space" -n 4 -d ./out
66
74
  ima2 gen "poster" --model gpt-5.4 --mode direct --moderation low
67
75
  ima2 gen "merge" --ref a.png --ref b.png -q high -o out.png
68
76
  cat prompt.txt | ima2 gen --stdin -n 2 -d ./out
@@ -82,8 +90,8 @@ export default async function genCmd(argv) {
82
90
  if (!prompt)
83
91
  die(2, "prompt is required (positional or via --stdin)");
84
92
  const refs = (Array.isArray(args.ref) ? args.ref : []);
85
- if (refs.length > 5)
86
- die(2, "max 5 --ref attachments");
93
+ if (refs.length > MAX_REFERENCE_COUNT)
94
+ die(2, `max ${MAX_REFERENCE_COUNT} --ref attachments`);
87
95
  if (!VALID_MODES.has(String(args.mode)))
88
96
  die(2, "--mode must be one of: auto, direct");
89
97
  if (!VALID_MODERATION.has(String(args.moderation)))
@@ -101,7 +109,7 @@ export default async function genCmd(argv) {
101
109
  if (args["web-search"] && args["no-web-search"]) {
102
110
  die(2, "--web-search and --no-web-search are mutually exclusive");
103
111
  }
104
- const n = Math.max(1, Math.min(8, parseInt(String(args.count)) || 1));
112
+ const n = Math.max(1, Math.min(MAX_GENERATION_COUNT, parseInt(String(args.count)) || 1));
105
113
  const timeoutMs = (parseInt(String(args.timeout)) || 180) * 1000;
106
114
  let server;
107
115
  try {
@@ -5,6 +5,8 @@ import { dataUriToFile, defaultOutName, fileToDataUri } from "../lib/files.js";
5
5
  import { out, die, color, json, exitCodeForError } from "../lib/output.js";
6
6
  import { config } from "../../config.js";
7
7
  import { createCliRequestId, recoverGeneratedOutputs, formatRecoveryHint } from "../lib/recover-output.js";
8
+ const MAX_GENERATION_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxGeneratedImages) || 24));
9
+ const MAX_REFERENCE_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxRefCount) || 5));
8
10
  const SPEC = {
9
11
  flags: {
10
12
  quality: { short: "q", type: "string", default: "low" },
@@ -36,7 +38,7 @@ const HELP = `
36
38
  Options:
37
39
  -q, --quality <low|medium|high> Default: low
38
40
  -s, --size <WxH> Default: 1024x1024
39
- --max-images <1..8> Default: 4
41
+ --max-images <1..${MAX_GENERATION_COUNT}> Default: 4
40
42
  -o, --out <file> First image (implies --max-images 1)
41
43
  -d, --out-dir <dir> Output dir for multiple images
42
44
  --json
@@ -44,7 +46,7 @@ const HELP = `
44
46
  --provider <auto|oauth|api|grok|grok-api|agy|gemini-api>
45
47
  Provider (oauth = GPT OAuth; grok = xAI Grok; agy/gemini-api = Gemini)
46
48
  --mode <auto|direct> Prompt handling mode. Default: auto
47
- --ref <file> Attach reference image (repeatable, max 5)
49
+ --ref <file> Attach reference image (repeatable, max ${MAX_REFERENCE_COUNT})
48
50
  --reasoning-effort <none|low|medium|high|xhigh>
49
51
  --web-search / --no-web-search
50
52
  --moderation <auto|low>
@@ -83,10 +85,10 @@ export default async function multimodeCmd(argv) {
83
85
  die(exitCodeForError(e), e.message);
84
86
  throw e;
85
87
  }
86
- const maxImages = Math.max(1, Math.min(8, parseInt(String(args["max-images"])) || 4));
88
+ const maxImages = Math.max(1, Math.min(MAX_GENERATION_COUNT, parseInt(String(args["max-images"])) || 4));
87
89
  const refs = (Array.isArray(args.ref) ? args.ref : []);
88
- if (refs.length > 5)
89
- die(2, "max 5 --ref attachments");
90
+ if (refs.length > MAX_REFERENCE_COUNT)
91
+ die(2, `max ${MAX_REFERENCE_COUNT} --ref attachments`);
90
92
  const references = await Promise.all(refs.map((p) => fileToDataUri(p)));
91
93
  const outDir = args["out-dir"] ? String(args["out-dir"]) : null;
92
94
  const explicitOut = args.out ? String(args.out) : null;
@@ -4,6 +4,7 @@ import { streamSse } from "../lib/sse.js";
4
4
  import { fileToDataUri, dataUriToFile, defaultOutName } from "../lib/files.js";
5
5
  import { out, die, color, json, exitCodeForError } from "../lib/output.js";
6
6
  import { config } from "../../config.js";
7
+ const MAX_GENERATION_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxGeneratedImages) || 24));
7
8
  const HELP = `
8
9
  ima2 node <subcommand> [options]
9
10
 
@@ -75,7 +76,7 @@ async function generateSub(argv) {
75
76
  prompt,
76
77
  quality: args.quality,
77
78
  size: args.size,
78
- n: Math.max(1, Math.min(8, parseInt(String(args.count)) || 1)),
79
+ n: Math.max(1, Math.min(MAX_GENERATION_COUNT, parseInt(String(args.count)) || 1)),
79
80
  references,
80
81
  moderation: args.moderation,
81
82
  sessionId: args.session,
package/bin/ima2.js CHANGED
@@ -65,20 +65,15 @@ async function setup() {
65
65
  console.log(" 1) GPT OAuth — login with ChatGPT account (free, images only)");
66
66
  console.log(" 2) Grok OAuth — login with xAI/Grok account (images + video)");
67
67
  console.log(" 3) Both — GPT OAuth + Grok OAuth");
68
- console.log(" 4) API Key paste your OpenAI API key (paid)\n");
68
+ console.log(" 4) Web setup configure everything in the web UI\n");
69
69
  const choice = await rl.question(" Enter 1-4: ");
70
70
  const config = loadConfig();
71
71
  if (choice.trim() === "4") {
72
- const key = await rl.question(" OpenAI API Key: ");
73
- if (!key.startsWith("sk-")) {
74
- console.log(" Invalid API key format. Expected sk-...");
75
- rl.close();
76
- process.exit(1);
77
- }
78
- config.provider = "api";
79
- config.apiKey = key.trim();
72
+ config.provider = "oauth";
73
+ delete config.apiKey;
80
74
  saveConfig(config);
81
- console.log("\n API key saved. Starting server...\n");
75
+ console.log("\n You can set up everything from the web UI.");
76
+ console.log(" Run 'ima2 serve', then open Settings in the browser to sign in or add API keys.\n");
82
77
  }
83
78
  else if (choice.trim() === "2") {
84
79
  config.provider = "grok";
@@ -260,6 +255,12 @@ function showHelp() {
260
255
 
261
256
  Usage: ima2 <command> [options]
262
257
 
258
+ Generation workflow:
259
+ Image/video jobs run on the server. For multiple candidates, prefer
260
+ 'ima2 gen -n <N>' or 'ima2 multimode <prompt>' instead of repeating
261
+ one-image prompts. Start independent CLI jobs concurrently when needed;
262
+ use 'ima2 ps --json' to monitor requestIds and 'ima2 cancel <id>' to stop.
263
+
263
264
  Server commands:
264
265
  serve [--dev] Start the image generation server
265
266
  setup, login Configure API key or GPT OAuth (interactive)
@@ -316,6 +317,9 @@ function showHelp() {
316
317
  ima2 serve Start server
317
318
  ima2 serve --dev Start with verbose server diagnostics
318
319
  ima2 gen "a shiba in space" Generate from CLI
320
+ ima2 gen "a shiba in space" -n 4 -d ./out
321
+ Generate 4 candidates in one request
322
+ ima2 ps --json Watch active async generation jobs
319
323
  ima2 gen "merge" --ref a.png --ref b.png -q high -o out.png
320
324
  ima2 video "a cat playing piano" --duration 10
321
325
  ima2 ls -n 10 Last 10 generations
package/config.js CHANGED
@@ -79,7 +79,8 @@ export const config = {
79
79
  maxRefB64Bytes: pickInt(env.IMA2_MAX_REF_B64_BYTES, fileCfg.limits?.maxRefB64Bytes, 7 * 1024 * 1024),
80
80
  maxMetadataReadB64Bytes: pickInt(env.IMA2_MAX_METADATA_READ_B64_BYTES, fileCfg.limits?.maxMetadataReadB64Bytes, 12 * 1024 * 1024),
81
81
  maxRefCount: pickInt(env.IMA2_MAX_REF_COUNT, fileCfg.limits?.maxRefCount, 5),
82
- maxParallel: pickInt(env.IMA2_MAX_PARALLEL, fileCfg.limits?.maxParallel, 8),
82
+ maxGeneratedImages: pickInt(env.IMA2_MAX_GENERATED_IMAGES, fileCfg.limits?.maxGeneratedImages, 24),
83
+ maxParallel: pickInt(env.IMA2_MAX_PARALLEL, fileCfg.limits?.maxParallel, 24),
83
84
  graphMaxNodes: pickInt(env.IMA2_GRAPH_MAX_NODES, fileCfg.limits?.graphMaxNodes, 500),
84
85
  graphMaxEdges: pickInt(env.IMA2_GRAPH_MAX_EDGES, fileCfg.limits?.graphMaxEdges, 1000),
85
86
  promptImportMaxFileBytes: pickInt(env.IMA2_PROMPT_IMPORT_MAX_FILE_BYTES, fileCfg.limits?.promptImportMaxFileBytes, 512 * 1024),
@@ -133,6 +134,7 @@ export const config = {
133
134
  promptImportDiscoveryRegistryFile: pickStr(env.IMA2_PROMPT_IMPORT_DISCOVERY_REGISTRY_FILE, fileCfg.storage?.promptImportDiscoveryRegistryFile, join(configDir, "prompt-import-discovery.json")),
134
135
  configFile: join(configDir, "config.json"),
135
136
  advertiseFile: pickStr(env.IMA2_ADVERTISE_FILE, fileCfg.storage?.advertiseFile, join(configDir, "server.json")),
137
+ generationRequestLogFile: pickStr(env.IMA2_GENERATION_REQUEST_LOG_FILE, fileCfg.storage?.generationRequestLogFile, join(configDir, "generation-request-log.json")),
136
138
  staticMaxAge: pickStr(env.IMA2_STATIC_MAX_AGE, fileCfg.storage?.staticMaxAge, "1y"),
137
139
  },
138
140
  ids: {
@@ -193,6 +195,10 @@ export const config = {
193
195
  timeoutMs: pickInt(env.IMA2_CARD_NEWS_PLANNER_TIMEOUT_MS, fileCfg.cardNewsPlanner?.timeoutMs, 60_000),
194
196
  deterministicFallback: pickBool(env.IMA2_CARD_NEWS_PLANNER_FALLBACK, fileCfg.cardNewsPlanner?.deterministicFallback, false),
195
197
  },
198
+ agentPlanner: {
199
+ enabled: pickBool(env.IMA2_AGENT_PLANNER_ENABLED, fileCfg.agentPlanner?.enabled, true),
200
+ timeoutMs: pickInt(env.IMA2_AGENT_PLANNER_TIMEOUT_MS, fileCfg.agentPlanner?.timeoutMs, 30_000),
201
+ },
196
202
  comfy: {
197
203
  defaultUrl: pickStr(env.IMA2_COMFY_URL, fileCfg.comfy?.defaultUrl, "http://127.0.0.1:8188"),
198
204
  uploadTimeoutMs: pickPositiveInt(env.IMA2_COMFY_UPLOAD_TIMEOUT_MS, fileCfg.comfy?.uploadTimeoutMs, 30_000),
@@ -216,6 +222,7 @@ export const GENERATED_DIR = config.storage.generatedDir;
216
222
  export const BODY_LIMIT = config.server.bodyLimit;
217
223
  export const MAX_REF_B64_BYTES = config.limits.maxRefB64Bytes;
218
224
  export const MAX_REFS = config.limits.maxRefCount;
225
+ export const MAX_GENERATED_IMAGES = config.limits.maxGeneratedImages;
219
226
  export const MAX_N = config.limits.maxParallel;
220
227
  export const INFLIGHT_TTL_MS = config.inflight.ttlMs;
221
228
  export const INFLIGHT_REAP_MS = config.inflight.reapMs;