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.
- package/CHANGELOG.md +151 -0
- package/README.md +10 -1
- package/bin/commands/backfillThumbs.js +6 -0
- package/bin/commands/gen.js +13 -5
- package/bin/commands/multimode.js +7 -5
- package/bin/commands/node.js +2 -1
- package/bin/ima2.js +14 -10
- package/config.js +8 -1
- package/docs/API.md +133 -9
- package/docs/CLI.md +6 -5
- package/docs/FAQ.ko.md +16 -0
- package/docs/FAQ.md +30 -0
- package/docs/README.ko.md +7 -3
- package/docs/migration/runtime-test-inventory.md +25 -1
- package/lib/agentCommandParser.js +10 -6
- package/lib/agentGenerationPlanner.js +96 -19
- package/lib/agentImageVideoGen.js +281 -0
- package/lib/agentPlannerModel.js +172 -0
- package/lib/agentQuestionResponder.js +9 -3
- package/lib/agentQueueStore.js +42 -0
- package/lib/agentQueueWorker.js +57 -7
- package/lib/agentRuntime.js +103 -274
- package/lib/agentSettings.js +9 -6
- package/lib/agentToolManifest.js +90 -0
- package/lib/agentTypes.js +1 -0
- package/lib/agyCli.js +31 -0
- package/lib/agyImageAdapter.js +104 -12
- package/lib/capabilities.js +7 -5
- package/lib/configKeys.js +6 -0
- package/lib/errorClassify.js +8 -7
- package/lib/eventBus.js +71 -0
- package/lib/geminiApiImageAdapter.js +24 -21
- package/lib/generationErrors.js +3 -1
- package/lib/generationRequestLog.js +25 -0
- package/lib/grokImageAdapter.js +76 -144
- package/lib/grokImageCore.js +153 -0
- package/lib/grokMultimodeAdapter.js +7 -4
- package/lib/grokVideoAdapter.js +3 -2
- package/lib/grokVideoCanvas.js +13 -0
- package/lib/grokVideoPlannerPrompt.js +52 -12
- package/lib/historyList.js +1 -0
- package/lib/inflight.js +54 -17
- package/lib/multimodeHelpers.js +11 -0
- package/lib/nodeHelpers.js +59 -0
- package/lib/oauthProxy/generators.js +6 -5
- package/lib/oauthProxy/prompts.js +60 -41
- package/lib/oauthProxy/streams.js +1 -1
- package/lib/promptBuilder/systemPrompt.js +2 -5
- package/lib/promptSafetyPolicy.js +1 -5
- package/lib/responsesDoctor.js +3 -3
- package/lib/responsesFallback.js +29 -15
- package/lib/responsesImageAdapter.js +11 -7
- package/lib/routeHelpers.js +44 -0
- package/lib/ssePublish.js +12 -0
- package/lib/storyboardPrefix.js +28 -0
- package/lib/thumbBackfill.js +16 -5
- package/node_modules/qs/CHANGELOG.md +10 -0
- package/node_modules/qs/README.md +1 -1
- package/node_modules/qs/dist/qs.js +15 -15
- package/node_modules/qs/eslint.config.mjs +1 -0
- package/node_modules/qs/lib/parse.js +52 -22
- package/node_modules/qs/lib/stringify.js +11 -4
- package/node_modules/qs/package.json +2 -2
- package/node_modules/qs/test/parse.js +49 -0
- package/node_modules/qs/test/stringify.js +129 -0
- package/package.json +4 -1
- package/routes/agent.js +19 -2
- package/routes/agy.js +48 -0
- package/routes/auth.js +6 -2
- package/routes/edit.js +7 -1
- package/routes/events.js +78 -0
- package/routes/generate.js +111 -128
- package/routes/generationRequestLog.js +16 -0
- package/routes/index.js +6 -0
- package/routes/keys.js +22 -2
- package/routes/multimode.js +100 -57
- package/routes/nodes.js +64 -108
- package/routes/video.js +100 -17
- package/skills/ima2/SKILL.md +98 -21
- package/ui/dist/.vite/manifest.json +22 -12
- package/ui/dist/assets/AgentWorkspace-qEMrATBP.js +3 -0
- package/ui/dist/assets/{CardNewsWorkspace-Dqyc1WZ1.js → CardNewsWorkspace-DoJnVHmn.js} +1 -1
- package/ui/dist/assets/GenerationRequestLogPanel-BDWRRLNV.js +1 -0
- package/ui/dist/assets/{NodeCanvas-ChEXzQbb.js → NodeCanvas-DIMo45sp.js} +3 -3
- package/ui/dist/assets/{PromptBuilderPanel-B95ZufnR.js → PromptBuilderPanel-BF3lcSJD.js} +2 -2
- package/ui/dist/assets/{PromptImportDialog-DGOwFQET.js → PromptImportDialog-Cy6ZymKG.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-CgvdnR49.js → PromptImportDiscoverySection-CzuW-8P2.js} +1 -1
- package/ui/dist/assets/PromptImportFolderSection-DP5ywnsv.js +1 -0
- package/ui/dist/assets/{PromptLibraryPanel-B9kndPw1.js → PromptLibraryPanel-CAsKr7CV.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-_PdPVsxi.js +1 -0
- package/ui/dist/assets/index-BUinlX2n.js +4 -0
- package/ui/dist/assets/index-CX3fge8X.css +1 -0
- package/ui/dist/assets/index-ygo6nfqx.js +23 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AgentWorkspace-CYv84Rus.js +0 -3
- package/ui/dist/assets/PromptImportFolderSection-CfUye9J8.js +0 -1
- package/ui/dist/assets/SettingsWorkspace-B3tgLrmF.js +0 -1
- package/ui/dist/assets/index-BhcvL0g-.js +0 -1
- package/ui/dist/assets/index-BtK3YhJc.js +0 -39
- 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. **
|
|
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
|

|
|
@@ -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
|
}
|
package/bin/commands/gen.js
CHANGED
|
@@ -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
|
|
46
|
-
--ref <file> Attach reference image (repeatable, max
|
|
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 >
|
|
86
|
-
die(2,
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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 >
|
|
89
|
-
die(2,
|
|
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;
|
package/bin/commands/node.js
CHANGED
|
@@ -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(
|
|
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)
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
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;
|