ima2-gen 3.13.1 → 3.14.0
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/.env.example +10 -0
- package/CHANGELOG.md +14 -0
- package/README.md +15 -2
- package/bin/commands/capabilities.js +5 -3
- package/bin/commands/config.js +15 -4
- package/bin/commands/defaults.js +7 -5
- package/bin/commands/doctor.js +88 -39
- package/bin/commands/gen.js +21 -4
- package/bin/commands/models.js +2 -2
- package/bin/commands/ping.js +1 -1
- package/bin/commands/prompt.js +5 -3
- package/bin/commands/service.js +32 -4
- package/bin/commands/tools.js +19 -3
- package/bin/commands/upscale.js +18 -4
- package/bin/commands/video.js +37 -50
- package/bin/ima2.js +28 -9
- package/bin/lib/characterResolve.js +2 -1
- package/bin/lib/client.js +155 -20
- package/bin/lib/config-store.js +40 -7
- package/bin/lib/doctor-bundle.js +6 -2
- package/bin/lib/doctor-checks.js +13 -29
- package/bin/lib/doctor-media.js +3 -3
- package/bin/lib/doctor-providers.js +48 -35
- package/bin/lib/doctor-report.js +78 -0
- package/bin/lib/doctor-runtime.js +173 -0
- package/bin/lib/mcpJob.js +19 -7
- package/bin/lib/output.js +3 -0
- package/bin/lib/platform.js +12 -9
- package/bin/lib/recover-output.js +9 -2
- package/bin/lib/sse.js +9 -1
- package/bin/lib/videoMcp.js +16 -8
- package/config.js +41 -5
- package/docs/API.md +154 -16
- package/docs/CLI.md +65 -1
- package/docs/DOCKER.md +19 -4
- package/docs/DOCKER.zh-CN.md +11 -4
- package/docs/DOCKER.zh-TW.md +11 -4
- package/docs/FAQ.md +13 -0
- package/docs/NPX_QUICKSTART.md +6 -0
- package/docs/README.ja.md +15 -2
- package/docs/README.ko.md +15 -2
- package/docs/README.zh-CN.md +15 -2
- package/docs/README.zh-TW.md +15 -2
- package/docs/migration/runtime-test-inventory.md +79 -8
- package/lib/agyArtifact.js +82 -0
- package/lib/agyArtifactRead.js +249 -0
- package/lib/agyImageAdapter.js +2 -329
- package/lib/agyProcess.js +122 -0
- package/lib/apiRequestBudget.js +48 -0
- package/lib/assetLifecycle.js +42 -9
- package/lib/canvasVersionStore.js +46 -14
- package/lib/capabilities.js +7 -1
- package/lib/configKeys.js +2 -0
- package/lib/errors/providerMap.js +26 -0
- package/lib/eventBus.js +10 -6
- package/lib/eventsPolicy.js +9 -0
- package/lib/geminiApiImageAdapter.js +1 -214
- package/lib/generatePipeline.js +39 -165
- package/lib/generatedMediaAccess.js +74 -0
- package/lib/generationErrors.js +3 -22
- package/lib/grokImageAdapter.js +2 -353
- package/lib/grokImageCore.js +1 -57
- package/lib/grokImageDownload.js +159 -0
- package/lib/grokImageDownloadPolicy.js +124 -0
- package/lib/grokImagePlanner.js +301 -0
- package/lib/grokMultimodeAdapter.js +1 -75
- package/lib/grokVideoDownload.js +137 -34
- package/lib/inflight.js +112 -112
- package/lib/jobs/terminalStore.js +45 -0
- package/lib/lanSessionStore.js +131 -0
- package/lib/localAccessPolicy.js +180 -0
- package/lib/localLanAccess.js +281 -0
- package/lib/logger.js +22 -13
- package/lib/mcp/downloadMediaResult.js +72 -78
- package/lib/multimodePipeline.js +39 -126
- package/lib/nodeGeneration.js +44 -101
- package/lib/nodeStore.js +25 -11
- package/lib/nodeValidation.js +5 -1
- package/lib/pinnedHttpGet.js +174 -0
- package/lib/promptImport/githubFolder.js +32 -18
- package/lib/promptImport/githubSource.js +14 -10
- package/lib/promptImport/parsePromptCandidates.js +32 -2
- package/lib/providers/adapters/agyOperations.js +179 -0
- package/lib/providers/adapters/comfy.js +1 -0
- package/lib/providers/adapters/geminiOperations.js +214 -0
- package/lib/providers/adapters/googleExecution.js +97 -0
- package/lib/providers/adapters/grokExecution.js +137 -0
- package/lib/providers/adapters/grokMultimodeOperations.js +83 -0
- package/lib/providers/adapters/grokOperations.js +63 -0
- package/lib/providers/adapters/openaiExecution.js +118 -0
- package/lib/providers/adapters/openaiOperations.js +201 -0
- package/lib/providers/adapters/openaiTypes.js +1 -0
- package/lib/providers/derive.js +4 -0
- package/lib/providers/deriveCore.js +2 -7
- package/lib/providers/execution/admission.js +25 -0
- package/lib/providers/execution/index.js +34 -0
- package/lib/providers/execution/legacy.js +17 -0
- package/lib/providers/execution/legacyClassic.js +66 -0
- package/lib/providers/execution/legacyEdit.js +30 -0
- package/lib/providers/execution/legacyMultimode.js +34 -0
- package/lib/providers/execution/legacyNode.js +41 -0
- package/lib/providers/execution/types.js +1 -0
- package/lib/providers/registry.js +19 -14
- package/lib/providers/surfaceSupport.js +22 -0
- package/lib/responsesImageAdapter.js +1 -389
- package/lib/responsesTransport.js +190 -0
- package/lib/spriteJobEvents.js +20 -2
- package/lib/ssePublish.js +5 -4
- package/lib/videoExtendI2vOperation.js +76 -0
- package/package.json +5 -2
- package/routes/edit.js +31 -123
- package/routes/events.js +140 -63
- package/routes/mcpMedia.js +10 -2
- package/routes/models.js +28 -25
- package/routes/videoExtended.js +9 -68
- package/server.js +24 -46
- package/ui/dist/.vite/manifest.json +90 -63
- package/ui/dist/assets/{AgentWorkspace-PbBytTeV.js → AgentWorkspace-CL3MLv3l.js} +2 -2
- package/ui/dist/assets/App-B9cEM6dJ.js +9 -0
- package/ui/dist/assets/AssetGenWorkspace-swWPvXrx.js +2 -0
- package/ui/dist/assets/AssetsWorkspace-CRVeYU2F.js +1 -0
- package/ui/dist/assets/{CardNewsWorkspace-BS3jKOja.js → CardNewsWorkspace-BAOxWLKf.js} +2 -2
- package/ui/dist/assets/GenerationRequestLogPanel-DZtYZm-D.js +1 -0
- package/ui/dist/assets/HomeWorkspace-BeZ1s0Jm.js +1 -0
- package/ui/dist/assets/KeyingPanel-ZyBSXioh.js +1 -0
- package/ui/dist/assets/NodeCanvas-BvvcCErf.js +7 -0
- package/ui/dist/assets/{PromptBuilderPanel-D97A4pwB.js → PromptBuilderPanel-DBycmShA.js} +2 -2
- package/ui/dist/assets/PromptImportDialog-ChvMv7ek.js +2 -0
- package/ui/dist/assets/PromptImportDiscoverySection-60py7whH.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-CCRh_-_7.js +1 -0
- package/ui/dist/assets/PromptLibraryPanel-BFWQO8OT.js +2 -0
- package/ui/dist/assets/SettingsWorkspace-B5Ghsgdj.js +1 -0
- package/ui/dist/assets/SpriteRecipeWorkspace-DRWdkmwU.js +1 -0
- package/ui/dist/assets/index-7ngEOVxt.js +5 -0
- package/ui/dist/assets/index-B5jy7ePg.css +1 -0
- package/ui/dist/assets/index-gxh_02DQ.js +22 -0
- package/ui/dist/assets/{pptxgen.es-D4pedbwM.js → pptxgen.es-OUNhQLEV.js} +1 -1
- package/ui/dist/assets/promptBuilderStore-BTUUzbBT.js +1 -0
- package/ui/dist/assets/useAgentDialogFocus-DSc2qY8p.js +1 -0
- package/ui/dist/index.html +3 -2
- package/ui/dist/assets/AssetGenWorkspace-B-TGlvVb.js +0 -2
- package/ui/dist/assets/AssetsWorkspace-LnGaW151.js +0 -1
- package/ui/dist/assets/GenerationRequestLogPanel-DuDWD_ic.js +0 -1
- package/ui/dist/assets/HomeWorkspace-DVP7UG6O.js +0 -1
- package/ui/dist/assets/KeyingPanel-5bRUFQaq.js +0 -1
- package/ui/dist/assets/NodeCanvas-BBEpERTb.js +0 -7
- package/ui/dist/assets/PromptImportDialog-C89MN5JY.js +0 -2
- package/ui/dist/assets/PromptImportDiscoverySection-BgRfDOs2.js +0 -1
- package/ui/dist/assets/PromptImportFolderSection-zTMyriT1.js +0 -1
- package/ui/dist/assets/PromptLibraryPanel-BD96qZu_.js +0 -2
- package/ui/dist/assets/SettingsWorkspace-BAOk3X_8.js +0 -1
- package/ui/dist/assets/SpriteRecipeWorkspace-ClIM5QRo.js +0 -1
- package/ui/dist/assets/index-0fpfY8vu.css +0 -1
- package/ui/dist/assets/index-BFciPGTn.js +0 -5
- package/ui/dist/assets/index-BbykeXR9.js +0 -30
- package/ui/dist/assets/promptBuilderStore-Do7M8g7J.js +0 -1
- package/ui/dist/assets/useAgentDialogFocus-B6-QgEVI.js +0 -1
|
@@ -2,6 +2,7 @@ import { copyFile, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { join, dirname, basename, isAbsolute } from "node:path";
|
|
3
3
|
import { config } from "../../config.js";
|
|
4
4
|
import { request } from "./client.js";
|
|
5
|
+
import { exitCodeForError } from "./output.js";
|
|
5
6
|
export function createCliRequestId(prefix = "req_cli") {
|
|
6
7
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
7
8
|
}
|
|
@@ -91,13 +92,19 @@ export async function recoverGeneratedOutputs(base, requestId, target) {
|
|
|
91
92
|
if (result)
|
|
92
93
|
return result;
|
|
93
94
|
}
|
|
94
|
-
catch {
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (exitCodeForError(error) === 4)
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
95
99
|
try {
|
|
96
100
|
const result = await tryHistoryRecovery(base, requestId, target);
|
|
97
101
|
if (result)
|
|
98
102
|
return result;
|
|
99
103
|
}
|
|
100
|
-
catch {
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (exitCodeForError(error) === 4)
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
101
108
|
return { recovered: false, paths: [], requestId, source: "none" };
|
|
102
109
|
}
|
|
103
110
|
export function formatRecoveryHint(result) {
|
package/bin/lib/sse.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// SSE consumer for CLI streaming endpoints. Plain fetch + line-based parser, no external libs.
|
|
2
|
+
import { parseEventCursor } from "../../lib/eventsPolicy.js";
|
|
3
|
+
import { fetchServerUrl } from "./client.js";
|
|
2
4
|
let CLI_VERSION = "0.0.0";
|
|
3
5
|
export function setCliVersion(v) { CLI_VERSION = v; }
|
|
4
6
|
function requestHeaders(init) {
|
|
@@ -82,7 +84,7 @@ async function openWithMethod(url, init, defaultMethod) {
|
|
|
82
84
|
if (init.signal?.aborted)
|
|
83
85
|
close();
|
|
84
86
|
try {
|
|
85
|
-
const res = await
|
|
87
|
+
const res = await fetchServerUrl(url, {
|
|
86
88
|
method: init.method || defaultMethod,
|
|
87
89
|
headers: requestHeaders(init),
|
|
88
90
|
...(init.body !== undefined ? { body: JSON.stringify(init.body) } : {}),
|
|
@@ -92,9 +94,15 @@ async function openWithMethod(url, init, defaultMethod) {
|
|
|
92
94
|
close();
|
|
93
95
|
throw await errorFromResponse(res);
|
|
94
96
|
}
|
|
97
|
+
const cursorHeader = res.headers.get("x-ima2-event-cursor");
|
|
98
|
+
if (cursorHeader !== null && parseEventCursor(cursorHeader) === null) {
|
|
99
|
+
close();
|
|
100
|
+
throw new Error("SSE_INVALID_EVENT_CURSOR");
|
|
101
|
+
}
|
|
95
102
|
const events = parseBody(res.body, () => closed);
|
|
96
103
|
return {
|
|
97
104
|
events,
|
|
105
|
+
...(cursorHeader !== null ? { initialEventId: cursorHeader } : {}),
|
|
98
106
|
close: () => {
|
|
99
107
|
init.signal?.removeEventListener("abort", forwardAbort);
|
|
100
108
|
close();
|
package/bin/lib/videoMcp.js
CHANGED
|
@@ -4,7 +4,7 @@ import { config } from "../../config.js";
|
|
|
4
4
|
import { GROK_VIDEO_MODEL_15, GROK_VIDEO_MODEL_15_PREVIEW_ALIAS, validateVideoResolutionForRequest, } from "../../lib/imageModels.js";
|
|
5
5
|
import { VIDEO_CLIENT_TIMEOUT_MS } from "../../lib/videoClientTimeouts.js";
|
|
6
6
|
import { wasFlagPassed } from "./argsExplicit.js";
|
|
7
|
-
import { resolveHistoryReference, resolveServer, request } from "./client.js";
|
|
7
|
+
import { resolveHistoryReference, resolveServer, request, fetchServer } from "./client.js";
|
|
8
8
|
import { loadCliDefaults } from "./config-store.js";
|
|
9
9
|
import { runMcpJob } from "./mcpJob.js";
|
|
10
10
|
import { characterElementIdForMcp } from "./characterResolve.js";
|
|
@@ -41,7 +41,10 @@ async function writeBuffer(path, buffer) {
|
|
|
41
41
|
await writeFile(path, buffer);
|
|
42
42
|
}
|
|
43
43
|
function failServer(jsonMode, error) {
|
|
44
|
+
const code = error?.code ?? "SERVER_REQUEST_FAILED";
|
|
44
45
|
const message = error?.message || "server unreachable";
|
|
46
|
+
if (code !== "SERVER_UNREACHABLE")
|
|
47
|
+
fail({ json: jsonMode, code, message, exitCode: exitCodeForError(error) });
|
|
45
48
|
if (jsonMode)
|
|
46
49
|
err("Hint: start the server with `ima2 serve`.");
|
|
47
50
|
fail({ json: jsonMode, code: "SERVER_UNREACHABLE", message: `${message}\nHint: run ima2 serve`, exitCode: 3 });
|
|
@@ -110,7 +113,7 @@ function validateCoreOptions(args, refs, model, lane) {
|
|
|
110
113
|
die(2, check.error);
|
|
111
114
|
return { duration, resolution, aspectRatio };
|
|
112
115
|
}
|
|
113
|
-
async function coreReferences(serverBase, refs) {
|
|
116
|
+
async function coreReferences(serverBase, refs, jsonMode) {
|
|
114
117
|
let latestPromise;
|
|
115
118
|
return Promise.all(refs.map(async (path) => {
|
|
116
119
|
if (path === "@last")
|
|
@@ -121,7 +124,8 @@ async function coreReferences(serverBase, refs) {
|
|
|
121
124
|
return (await readFile(resolved)).toString("base64");
|
|
122
125
|
})).catch((error) => {
|
|
123
126
|
const typed = error;
|
|
124
|
-
return
|
|
127
|
+
return fail({ json: jsonMode, code: typed.code ?? "REFERENCE_FAILED", message: typed.message || String(error),
|
|
128
|
+
exitCode: typed.code === "HISTORY_EMPTY" ? 5 : exitCodeForError(error) });
|
|
125
129
|
});
|
|
126
130
|
}
|
|
127
131
|
function coreBody(args, context, options, references, requestId) {
|
|
@@ -195,7 +199,10 @@ async function consumeCoreSse(url, body, args, requestId) {
|
|
|
195
199
|
return null;
|
|
196
200
|
if (!args.json && lastProgress >= 0)
|
|
197
201
|
process.stdout.write("\n");
|
|
198
|
-
|
|
202
|
+
if (!(error instanceof Error))
|
|
203
|
+
throw error;
|
|
204
|
+
fail({ json: Boolean(args.json), code: error.code ?? "VIDEO_FAILED",
|
|
205
|
+
message: error.message, exitCode: exitCodeForError(error) });
|
|
199
206
|
}
|
|
200
207
|
finally {
|
|
201
208
|
clearTimeout(timer);
|
|
@@ -207,14 +214,14 @@ async function consumeCoreSse(url, body, args, requestId) {
|
|
|
207
214
|
async function runCoreVideo(args, context) {
|
|
208
215
|
const refs = (Array.isArray(args.ref) ? args.ref : []);
|
|
209
216
|
const options = validateCoreOptions(args, refs, context.target.model, context.target.lane);
|
|
210
|
-
const references = await coreReferences(context.server.base, refs);
|
|
217
|
+
const references = await coreReferences(context.server.base, refs, Boolean(args.json));
|
|
211
218
|
const requestId = createCliRequestId("req_cli_video");
|
|
212
219
|
const done = await consumeCoreSse(`${context.server.base}/api/video/generate`, coreBody(args, context, options, references, requestId), args, requestId);
|
|
213
220
|
if (!done?.filename)
|
|
214
221
|
die(1, "server did not return a video filename");
|
|
215
222
|
const filename = String(done.filename);
|
|
216
223
|
const target = args.out ? String(args.out) : args["out-dir"] ? join(String(args["out-dir"]), filename) : join(config.storage.generatedDir, filename);
|
|
217
|
-
const response = await
|
|
224
|
+
const response = await fetchServer(context.server.base, String(done.url || `/generated/${encodeURIComponent(filename)}`), { signal: AbortSignal.timeout(parseInteger(args.timeout, MCP_VIDEO_TIMEOUT_MS / 1000, "--timeout") * 1000) });
|
|
218
225
|
if (!response.ok)
|
|
219
226
|
die(1, `failed to download video: HTTP ${response.status}`);
|
|
220
227
|
await writeBuffer(target, Buffer.from(await response.arrayBuffer()));
|
|
@@ -357,7 +364,7 @@ function mcpMediaInputs(args, context, roles) {
|
|
|
357
364
|
};
|
|
358
365
|
}
|
|
359
366
|
async function downloadMcpVideo(serverBase, url, target) {
|
|
360
|
-
const response = await
|
|
367
|
+
const response = await fetchServer(serverBase, url);
|
|
361
368
|
if (!response.ok)
|
|
362
369
|
die(1, `failed to download video: HTTP ${response.status}`);
|
|
363
370
|
await writeBuffer(target, Buffer.from(await response.arrayBuffer()));
|
|
@@ -391,7 +398,8 @@ async function runMcpVideo(argv, args, context) {
|
|
|
391
398
|
}
|
|
392
399
|
catch (error) {
|
|
393
400
|
const typed = error;
|
|
394
|
-
fail({ json: Boolean(args.json), code: typed.code ?? "MCP_GENERATION_FAILED", message: typed.message,
|
|
401
|
+
fail({ json: Boolean(args.json), code: typed.code ?? "MCP_GENERATION_FAILED", message: typed.message,
|
|
402
|
+
exitCode: exitCodeForError(error) === 4 ? 4 : 1 });
|
|
395
403
|
}
|
|
396
404
|
}
|
|
397
405
|
export async function runVideoGenerate(argv, args, prompt) {
|
package/config.js
CHANGED
|
@@ -8,17 +8,29 @@
|
|
|
8
8
|
// `config.json` is loaded once at module import. Mutating the file at runtime
|
|
9
9
|
// requires a server restart (same as env vars).
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
// circular imports with lib/*.
|
|
11
|
+
// Import only pure policy helpers from lib/*; never runtime owners that import config.
|
|
13
12
|
import { homedir } from "node:os";
|
|
14
13
|
import { join, dirname } from "node:path";
|
|
15
14
|
import { fileURLToPath } from "node:url";
|
|
16
15
|
import { readFileSync, existsSync } from "node:fs";
|
|
16
|
+
import { parsePublicOrigins } from "./lib/localAccessPolicy.js";
|
|
17
|
+
export { SSE_STREAM_POLICY } from "./lib/eventsPolicy.js";
|
|
17
18
|
import { deriveSupportedImageModels, deriveUnsupportedImageModels } from "./lib/providers/derive.js";
|
|
18
19
|
import { DEFAULT_PROMPT_BUILDER_MODELS, PROMPT_BUILDER_BACKENDS, PROMPT_BUILDER_MODELS, } from "./lib/promptBuilder/constants.js";
|
|
19
20
|
// 4.6 rewrites prompts in ways that read worse than 4.3 for this planner's job, which
|
|
20
21
|
// is judged by the result rather than a benchmark. 4.6 stays selectable below.
|
|
21
22
|
export const DEFAULT_GROK_PLANNER_MODEL = "grok-4.3";
|
|
23
|
+
export const AGY_PROCESS_POLICY = Object.freeze({ timeoutMs: 360_000, terminateGraceMs: 1000, maxOutputBytes: 1_048_576 });
|
|
24
|
+
export const AGY_ARTIFACT_POLICY = Object.freeze({ maxBytes: 52_428_800, chunkBytes: 65_536 });
|
|
25
|
+
/** Per-process API admission; streaming frames do not consume request slots. */
|
|
26
|
+
export const API_REQUEST_POLICY = Object.freeze({
|
|
27
|
+
windowMs: 60_000, requests: 600, mutations: 120, maxPeers: 4096,
|
|
28
|
+
});
|
|
29
|
+
export const LAN_SECURITY_POLICY = Object.freeze({
|
|
30
|
+
lanSessionTtlMs: 28_800_000, lanMaxSessions: 256,
|
|
31
|
+
lanAuthWindowMs: 60_000, lanAuthMaxFailures: 10,
|
|
32
|
+
lanAuthMaxBuckets: 4096, lanTokenMaxBytes: 4096,
|
|
33
|
+
});
|
|
22
34
|
export const GROK_PLANNER_MODELS = [
|
|
23
35
|
DEFAULT_GROK_PLANNER_MODEL,
|
|
24
36
|
"grok-4.6",
|
|
@@ -46,15 +58,21 @@ function loadConfigJson() {
|
|
|
46
58
|
continue;
|
|
47
59
|
try {
|
|
48
60
|
const raw = readFileSync(p, "utf-8");
|
|
49
|
-
|
|
61
|
+
const parsed = JSON.parse(raw);
|
|
62
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
63
|
+
return { values: parsed, source: p };
|
|
50
64
|
}
|
|
51
65
|
catch {
|
|
52
66
|
// ignore malformed config.json; env+defaults still apply
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
|
-
return {};
|
|
69
|
+
return { values: {}, source: null };
|
|
56
70
|
}
|
|
57
|
-
const
|
|
71
|
+
const loadedFileConfig = loadConfigJson();
|
|
72
|
+
const fileCfg = loadedFileConfig.values;
|
|
73
|
+
export const CONFIG_SOURCE_FILE = loadedFileConfig.source;
|
|
74
|
+
const publicOriginsEnv = env.IMA2_PUBLIC_ORIGINS;
|
|
75
|
+
const publicOriginsFile = fileCfg.server?.publicOrigins;
|
|
58
76
|
function firstDefined(...vals) {
|
|
59
77
|
return vals.find((v) => v !== undefined && v !== "");
|
|
60
78
|
}
|
|
@@ -92,11 +110,29 @@ export function defaultLogLevelForEnv(runtimeEnv = env) {
|
|
|
92
110
|
return runtimeEnv.IMA2_DEV === "1" ? "debug" : "info";
|
|
93
111
|
}
|
|
94
112
|
export const config = {
|
|
113
|
+
security: LAN_SECURITY_POLICY,
|
|
114
|
+
diagnostics: {
|
|
115
|
+
keyTimeoutMs: Math.min(30000, pickPositiveInt(env.IMA2_DIAGNOSTIC_KEY_TIMEOUT_MS, fileCfg.diagnostics?.keyTimeoutMs, 5000)),
|
|
116
|
+
runtimeTimeoutMs: Math.min(30000, pickPositiveInt(env.IMA2_DIAGNOSTIC_RUNTIME_TIMEOUT_MS, fileCfg.diagnostics?.runtimeTimeoutMs, 1500)),
|
|
117
|
+
},
|
|
95
118
|
server: {
|
|
96
119
|
// Accept both IMA2_PORT and legacy PORT.
|
|
97
120
|
port: pickInt(firstDefined(env.IMA2_PORT, env.PORT), fileCfg.server?.port, 3333),
|
|
98
121
|
host: pickStr(env.IMA2_HOST, fileCfg.server?.host, "127.0.0.1"),
|
|
99
122
|
lanToken: env.IMA2_LAN_TOKEN || "",
|
|
123
|
+
// Lazy validation permits config rm to repair a bad file-layer value.
|
|
124
|
+
// Access policy snapshots this before listening; inputs stay import-time fixed.
|
|
125
|
+
get publicOrigins() {
|
|
126
|
+
try {
|
|
127
|
+
return parsePublicOrigins(publicOriginsEnv === undefined
|
|
128
|
+
? (publicOriginsFile === undefined ? [] : publicOriginsFile) : JSON.parse(publicOriginsEnv));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw Object.assign(new Error("IMA2_PUBLIC_ORIGINS must be an array of exact HTTP(S) origins."), {
|
|
132
|
+
code: "INVALID_PUBLIC_ORIGINS", status: 400,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
100
136
|
bodyLimit: pickStr(env.IMA2_BODY_LIMIT, fileCfg.server?.bodyLimit, "50mb"),
|
|
101
137
|
},
|
|
102
138
|
limits: {
|
package/docs/API.md
CHANGED
|
@@ -8,15 +8,63 @@ Base URL:
|
|
|
8
8
|
http://localhost:3333
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Local and LAN access
|
|
12
|
+
|
|
13
|
+
The configured bind determines access mode. Loopback keeps single-user no-token
|
|
14
|
+
use; a non-loopback bind requires `IMA2_LAN_TOKEN`, including requests arriving
|
|
15
|
+
from a loopback proxy. LAN protects both `/api` and `/generated` before file,
|
|
16
|
+
Range or conditional-response processing. UI/static assets remain public.
|
|
17
|
+
|
|
18
|
+
| Endpoint | Contract |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `GET /api/auth/lan/session` | No-store `{mode, authenticated, expiresAt}`; no token/cookie value returned |
|
|
21
|
+
| `POST /api/auth/lan/session` | Same-origin `Origin`, JSON `{}`, valid `x-ima2-token`; 204 and an opaque HttpOnly/SameSite=Strict cookie in LAN mode |
|
|
22
|
+
| `DELETE /api/auth/lan/session` | Same-origin `Origin` and presented cookie; revoke it and return 204 |
|
|
23
|
+
|
|
24
|
+
Sessions are in memory, origin-bound and expire after an absolute eight hours.
|
|
25
|
+
Restart/rotation invalidates them. Settings → General → Studio session signs out;
|
|
26
|
+
sign-out/expiry closes admitted streams but does not cancel accepted generation.
|
|
27
|
+
Reauthentication does not automatically submit a waiting generation again.
|
|
28
|
+
The browser removes a supplied `?token=...` before loading App and never stores
|
|
29
|
+
the token in local/sessionStorage; entering it in the sign-in form avoids putting
|
|
30
|
+
it in initial URL/access logs. API clients can use `x-ima2-token`; legacy query
|
|
31
|
+
tokens remain accepted. Header → query → cookie precedence applies. Malformed
|
|
32
|
+
duplicates are rejected; an explicit invalid credential cannot fall back to a cookie.
|
|
33
|
+
|
|
34
|
+
Serving Host/Origin must match the actual listener or exact configured
|
|
35
|
+
`server.publicOrigins` / `IMA2_PUBLIC_ORIGINS` JSON array (maximum 16). No wildcards,
|
|
36
|
+
userinfo, paths, query or fragments. This is a serving-origin list, not CORS
|
|
37
|
+
permission: cross-origin and same-site sibling-origin requests remain rejected.
|
|
38
|
+
Forwarded headers do not establish trust. The case-compatible exact GET MCP OAuth
|
|
39
|
+
callback retains its existing state/PKCE check; admin stop still needs its nonce.
|
|
40
|
+
|
|
41
|
+
For Vite development, use `http://127.0.0.1:5173` and explicitly include that origin
|
|
42
|
+
on the backend (`IMA2_PUBLIC_ORIGINS='["http://127.0.0.1:5173"]'`). The dev proxy
|
|
43
|
+
preserves Host. If opening `localhost` instead, list that exact origin too; local
|
|
44
|
+
mode canonicalizes it to `127.0.0.1`, while LAN mode preserves the entered origin.
|
|
45
|
+
|
|
46
|
+
LAN media uses `private, no-store, max-age=0`, no ETag/Last-Modified, and
|
|
47
|
+
same-origin resource policy. JSON sidecars and outside-root links are not served;
|
|
48
|
+
SVG retains its restrictive CSP. Previously cached/downloaded copies cannot be
|
|
49
|
+
revoked: purge operator-controlled proxy caches during upgrade. HTTP LAN is for
|
|
50
|
+
trusted networks only; use TLS/VPN on untrusted networks. HTTPS sessions use Secure
|
|
51
|
+
cookies. A malicious process on the operator host is outside this boundary.
|
|
52
|
+
|
|
53
|
+
Wrong/missing LAN credentials return `401 LAN_TOKEN_REQUIRED`; invalid Host/Origin
|
|
54
|
+
returns 403. Bootstrap is limited to ten failed attempts per socket peer per minute
|
|
55
|
+
and shares the app API request budget. Respect `Retry-After` on 429; a full
|
|
56
|
+
256-session store returns 503 instead of evicting users. Tokens are bounded to
|
|
57
|
+
4096 UTF-8 bytes. Access errors contain fixed messages, never supplied secrets.
|
|
58
|
+
|
|
11
59
|
## Provider Policy
|
|
12
60
|
|
|
13
61
|
Image generation supports OAuth, API-key, Grok, and Gemini (`agy` and `gemini-api`) providers.
|
|
14
62
|
|
|
15
63
|
- `provider: "oauth"` uses the local Codex OAuth proxy.
|
|
16
64
|
- `provider: "api"` uses the OpenAI Responses API with the hosted `image_generation` tool.
|
|
17
|
-
- `provider: "grok"` uses the bundled progrok xAI proxy. Classic, Node
|
|
65
|
+
- `provider: "grok"` uses the bundled progrok xAI proxy. Classic, Node and multimode image generation honor `webSearchEnabled: false` (Node also honors `searchMode: "off"`): this skips `/v1/responses` search, not the configured planner's forced `generate_image` call. Planner default is `grok-4.3`; `grok-4.6` and `grok-4.5` remain selectable overrides. References select `/v1/images/edits` instead of `/v1/images/generations`. Agent's omitted-option search default is unchanged.
|
|
18
66
|
- `provider: "agy"` spawns the Antigravity CLI (`agy -p`) to generate images via Google Gemini's `default_api:generate_image` tool. Model is `nano-banana-2`. Output is fixed at 1024×1024 JPEG. Max 3 reference images (i2i). No web search, quality, size, or mask controls. Multimode returns a single image. Video is unsupported (`AGY_VIDEO_UNSUPPORTED`).
|
|
19
|
-
- `provider: "grok-api"` uses a direct xAI API key
|
|
67
|
+
- `provider: "grok-api"` uses a direct xAI API key with the same image search/planner/image semantics. Missing or blank image credentials are refused with `GROK_API_KEY_MISSING` before admission, never changed to the proxy lane. Video remains a separate path. Configure the key in Settings or `XAI_API_KEY`.
|
|
20
68
|
- `provider: "gemini-api"` calls the Google Generative Language API directly (or Vertex AI with a service account JSON). Supports models `nano-banana-2` (Gemini 3.1 Flash Image) and `nano-banana-pro` (Gemini 3 Pro Image). Supports variable aspect ratios (1:1 through 21:9) and four resolution tiers (512px, 1K, 2K, 4K) on both auth paths — the direct API path sends `generation_config.response_format.image` (snake_case) while the Vertex AI endpoint (`aiplatform.googleapis.com`) sends `generationConfig.imageConfig` (camelCase). With `size: "auto"` the image config is omitted entirely and the model decides ratio/size. Auth: `GEMINI_API_KEY` env var, web UI key management (`/api/keys/gemini`), or a Vertex AI service account JSON (`VERTEX_SERVICE_ACCOUNT_JSON` or `/api/keys/vertex`). When both Vertex credentials and an API key are configured, Vertex takes priority. The chosen auth mode (`apikey` or `vertex`) persists to `~/.ima2/config.json` as `geminiAuthMode` and is restored on server startup. Per-model cost: `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.
|
|
21
69
|
- `provider: "nai"` calls NovelAI text-to-image generation with one of `nai-diffusion-5-full`, `nai-diffusion-5-curated`, `nai-diffusion-4-5-full`, or `nai-diffusion-4-5-curated`. It accepts the provider-native request fields documented below and returns a ZIP that ima2 decodes to PNG. References, edits, and masks are explicitly refused.
|
|
22
70
|
- API-key generation covers classic generate, edit, mask-guided edit, multimode, and node generation.
|
|
@@ -27,8 +75,77 @@ Image generation supports OAuth, API-key, Grok, and Gemini (`agy` and `gemini-ap
|
|
|
27
75
|
Grok video generation uses `POST /api/video/generate` (SSE). See the Video
|
|
28
76
|
Generation section below for the full endpoint specification.
|
|
29
77
|
|
|
78
|
+
### Grok image artifact retrieval
|
|
79
|
+
|
|
80
|
+
Returned image URLs require HTTPS and a conservative numeric-address policy.
|
|
81
|
+
Private/link-local/shared/loopback/documentation/transition destinations are
|
|
82
|
+
rejected by default; every DNS answer and redirect hop is validated, and the
|
|
83
|
+
connection uses only those validated addresses. No default re-resolution,
|
|
84
|
+
connection pooling, API credential, cookie or referrer forwarding occurs.
|
|
85
|
+
|
|
86
|
+
The exact server-configured proxy origin is a local exception only when it is
|
|
87
|
+
the initial artifact origin. Leaving it permanently removes that exception for
|
|
88
|
+
the chain: public→private-proxy and proxy→public→private-proxy cannot regain trust.
|
|
89
|
+
At most five redirects and50MiB are accepted; the single30s default deadline
|
|
90
|
+
covers DNS, retries, redirects and streamed bytes. An omitted/false Content-Length
|
|
91
|
+
does not bypass the streamed cap. HTTP GET may retry under the existing policy;
|
|
92
|
+
billable image POST is not automatically retried.
|
|
93
|
+
|
|
94
|
+
The downloader reports `GROK_IMAGE_DOWNLOAD_FAILED`/502 for policy/body failures,
|
|
95
|
+
`GROK_IMAGE_TIMEOUT`/504 for deadline, and `GENERATION_CANCELED`/499 for caller
|
|
96
|
+
abort; existing surface error normalization still applies. Errors do not expose
|
|
97
|
+
raw returned URL/query credentials. This restricted policy is not an exhaustive
|
|
98
|
+
public-address/routability guarantee and does not change video or MCP URL policy.
|
|
99
|
+
|
|
100
|
+
### Agy artifact reads and cleanup
|
|
101
|
+
|
|
102
|
+
Reported and fallback artifact paths must be absolute regular files within the
|
|
103
|
+
canonical `.gemini`, `.cache`, or system temporary roots. Intentionally relocated
|
|
104
|
+
root directories remain supported; missing roots never broaden the boundary.
|
|
105
|
+
Leaf symlinks, paths escaping those roots, unusable file identities and detected
|
|
106
|
+
path/file replacement are rejected. The same checks apply to parsed result paths
|
|
107
|
+
and fallback discovery; filename matching alone is not acceptance.
|
|
108
|
+
|
|
109
|
+
The reader enforces an inclusive 50MiB limit before and during descriptor reads,
|
|
110
|
+
using bounded blocks even when individual reads are short. A successful receipt
|
|
111
|
+
authorizes cleanup only for that same checked file; replacements and unrelated
|
|
112
|
+
siblings are left alone. Direct reader/operation cancellation returns
|
|
113
|
+
`GENERATION_CANCELED`/499 after pending file I/O and descriptor close settle;
|
|
114
|
+
existing node normalization can expose `INVALID_REQUEST`/499 instead.
|
|
115
|
+
Policy-rejected artifacts are not deleted. The reader and Agy operation add no
|
|
116
|
+
retries, but caller policies remain unchanged: reference-free node requests can
|
|
117
|
+
retry once after retryable artifact errors, starting another generation attempt.
|
|
118
|
+
|
|
119
|
+
`AGY_PATH_REJECTED`/502 covers the path/identity policy; `AGY_ARTIFACT_TOO_LARGE`/502
|
|
120
|
+
covers overflow, and `AGY_ARTIFACT_NOT_FOUND`/502 covers missing reported files.
|
|
121
|
+
Existing generation normalization may expose `code: UNKNOWN` while retaining
|
|
122
|
+
the original `rawCode` and `errorClass: INTERNAL_STATE_ERROR`.
|
|
123
|
+
Windows uses explicit path/identity checks without POSIX no-follow open flags.
|
|
124
|
+
This is not an atomic filesystem sandbox against another same-user process, a
|
|
125
|
+
hardlink-provenance guarantee, or a process-wide memory ceiling.
|
|
126
|
+
|
|
30
127
|
## Health And Status
|
|
31
128
|
|
|
129
|
+
### Core provider surface metadata
|
|
130
|
+
|
|
131
|
+
`GET /api/capabilities` includes `providerSurfaces[coreProviderId][surface]`.
|
|
132
|
+
`GET /api/models` includes the same `surfaces` on each core lane; MCP lanes
|
|
133
|
+
retain their existing model capabilities. Surfaces are `generate`, `edit`,
|
|
134
|
+
`multimode`, `node`, and `video`.
|
|
135
|
+
|
|
136
|
+
Each record contains `supported`, `references`, `mask`, `streaming`, and
|
|
137
|
+
`catalogAccess` (`static` or `runtime`). These are application capability facts,
|
|
138
|
+
not credential/liveness or entitlement checks. `references` means the surface
|
|
139
|
+
can accept image input; a Comfy workflow still needs the appropriate binding.
|
|
140
|
+
`streaming` describes partial-image delivery on Node/Multimode, not lifecycle SSE.
|
|
141
|
+
|
|
142
|
+
NovelAI's generate/node/multimode entries are supported without references;
|
|
143
|
+
edit/video are unsupported. Its models explicitly declare generation support,
|
|
144
|
+
not fictitious edit support. Comfy is runtime-catalog backed: empty static
|
|
145
|
+
model IDs do not disable its generate/edit/video surfaces, while node/multimode
|
|
146
|
+
remain unsupported. Local capabilities expose the structural map without
|
|
147
|
+
inventing a runtime `lanes` availability result.
|
|
148
|
+
|
|
32
149
|
| Method | Path | Notes |
|
|
33
150
|
|---|---|---|
|
|
34
151
|
| `GET` | `/api/health` | Server health, version, paths, provider policy |
|
|
@@ -230,23 +347,23 @@ enabled alpha are V5-only. The four exact image models are
|
|
|
230
347
|
text-to-image only: `NAI_REF_UNSUPPORTED`, `NAI_EDIT_UNSUPPORTED`, and
|
|
231
348
|
`NAI_MASK_UNSUPPORTED` fail closed rather than discarding input.
|
|
232
349
|
|
|
233
|
-
When `provider` is `"grok"`, supported models
|
|
234
|
-
`grok-imagine-image-quality`. The server uses `grok-4.
|
|
235
|
-
|
|
350
|
+
When `provider` is `"grok"`, supported models include `grok-imagine-image-2.0`,
|
|
351
|
+
`grok-imagine-image` and `grok-imagine-image-quality`. The server uses `grok-4.3`
|
|
352
|
+
as the configured search/planner default (`IMA2_GROK_PLANNER_MODEL`) and times search and
|
|
236
353
|
planner steps separately from the image call (`IMA2_GROK_PLANNER_TIMEOUT_MS`).
|
|
237
354
|
For `n > 1`, search and planning run once and the planned prompt is reused for
|
|
238
|
-
the image requests.
|
|
239
|
-
|
|
355
|
+
the image requests. Search-disabled classic generations report zero search calls;
|
|
356
|
+
otherwise the shared plan reports one logical search call.
|
|
240
357
|
|
|
241
358
|
If `references` are present on a Grok classic request, ima2 still performs the
|
|
242
|
-
|
|
359
|
+
configured planning phase, with search only when enabled. The planner receives the
|
|
243
360
|
reference images as multimodal `image_url` inputs, and its forced
|
|
244
361
|
`generate_image.prompt` argument is instructed to be English-only except for
|
|
245
362
|
exact visible text requested by the user. The final image call then uses xAI
|
|
246
363
|
`/v1/images/edits` with the same reference images instead of
|
|
247
364
|
`/v1/images/generations`. This keeps image-to-image/reference context alive
|
|
248
|
-
through the
|
|
249
|
-
|
|
365
|
+
through the pipeline. The server permits up to three source images for these
|
|
366
|
+
Grok classic requests; more than three
|
|
250
367
|
references return `GROK_REF_TOO_MANY`.
|
|
251
368
|
|
|
252
369
|
Grok size mapping:
|
|
@@ -280,9 +397,11 @@ With `provider: "grok"`, edit requests are sent to xAI `/v1/images/edits`
|
|
|
280
397
|
through the bundled progrok proxy. Masked Grok edits are rejected before
|
|
281
398
|
upstream with `GROK_MASK_UNSUPPORTED`.
|
|
282
399
|
|
|
283
|
-
Grok multimode
|
|
284
|
-
|
|
285
|
-
|
|
400
|
+
Grok multimode plans each attempted image in order and performs search only when
|
|
401
|
+
enabled. Sparse successes retain their original attempt index: a failed first
|
|
402
|
+
attempt cannot make the final sweep persist the second image twice. Identical
|
|
403
|
+
bytes at different successful indices remain separate outputs. The internal
|
|
404
|
+
originalIndexes array is not added to request/SSE/history/sidecar schemas.
|
|
286
405
|
|
|
287
406
|
### `POST /api/node/generate`
|
|
288
407
|
|
|
@@ -310,7 +429,7 @@ Body fields:
|
|
|
310
429
|
|
|
311
430
|
When `parentNodeId` is present, the server loads the stored parent node image and uses the edit path. Node-local references are allowed on both root and child/edit nodes; for child/edit nodes the parent image is sent first, then references, then the text prompt.
|
|
312
431
|
|
|
313
|
-
|
|
432
|
+
Grok Node Mode uses the configured planner and Images API, with search suppressed by `searchMode: "off"` or `webSearchEnabled: false`. A parent node image, `externalSrc`, or extra references are passed to the planner and then to `/v1/images/edits`; otherwise the final call uses `/v1/images/generations`. The server caps total input images at three, counting parent/current image plus references, and returns `GROK_REF_TOO_MANY` before upstream when exceeded. Existing quality-model resolution is unchanged.
|
|
314
433
|
|
|
315
434
|
The route can stream Server-Sent Events when the client sends `Accept: text/event-stream`. Possible events include `phase`, `partial`, `done`, and `error`. Alternatively, send `{ "async": true, "requestId": "req_xxx" }` in the body to receive `202 { requestId }` immediately and follow progress on `GET /api/events` (see Events section).
|
|
316
435
|
|
|
@@ -489,7 +608,7 @@ Grok prompt surfaces used by video APIs:
|
|
|
489
608
|
|
|
490
609
|
| Code | Meaning |
|
|
491
610
|
|---|---|
|
|
492
|
-
| `VIDEO_PROVIDER_UNSUPPORTED` | Provider is not `"grok"` |
|
|
611
|
+
| `VIDEO_PROVIDER_UNSUPPORTED` | Provider is not `"grok"` or `"grok-api"` |
|
|
493
612
|
| `PROMPT_REQUIRED` | Empty or missing prompt |
|
|
494
613
|
| `INVALID_GROK_VIDEO_MODEL` | Model not in valid set |
|
|
495
614
|
| `INVALID_VIDEO_RESOLUTION` | Resolution is not 480p/720p/1080p, or 1080p was requested outside `grok-imagine-video-1.5` prompt-only T2V / I2V |
|
|
@@ -498,6 +617,24 @@ Grok prompt surfaces used by video APIs:
|
|
|
498
617
|
| `GROK_VIDEO_REF_TOO_MANY` | More than 7 reference images |
|
|
499
618
|
| `GROK_VIDEO_FAILED` | Upstream xAI video generation failed |
|
|
500
619
|
| `GROK_VIDEO_FRAME_FAILED` | Server could not extract the parent video's last frame |
|
|
620
|
+
| `GROK_VIDEO_DOWNLOAD_FAILED` | Download-stage HTTP, size, MIME, empty-body, MP4-prefix or read failure (502) |
|
|
621
|
+
| `GROK_VIDEO_TIMEOUT` | The download's own non-resetting timeout expired (504) |
|
|
622
|
+
| `GENERATION_CANCELED` | Caller cancellation observed while downloading, including custom reasons (499) |
|
|
623
|
+
|
|
624
|
+
Completed Grok video artifacts are consumed incrementally with an inclusive
|
|
625
|
+
**100 MiB** byte cap. Declared size and actual streamed bytes are checked before
|
|
626
|
+
retaining an overflowing chunk; invalid/empty/aborted bodies do not reach caller
|
|
627
|
+
persistence. Accepted MIME is MP4 or octet-stream (missing MIME defaults to MP4),
|
|
628
|
+
and the existing minimum-length/`ftyp` prefix check is retained. This is not full
|
|
629
|
+
MP4 decoding or a100MiB process-memory ceiling: fetch buffers, chunk overhead and
|
|
630
|
+
downstream copies are separate. Safe GET retry ends at response headers; body
|
|
631
|
+
failure never starts a new billed generation.
|
|
632
|
+
|
|
633
|
+
The video destination policy is unchanged: initial HTTPS or the existing literal
|
|
634
|
+
HTTP-loopback allowlist, with fetch's default redirect behavior. It does not gain
|
|
635
|
+
the image downloader's DNS/private-address/per-hop pinning policy. Do not treat
|
|
636
|
+
HTTPS alone or these body bounds as an SSRF/security guarantee. Download error
|
|
637
|
+
status appears in the existing JSON or SSE/terminal envelope for each caller.
|
|
501
638
|
|
|
502
639
|
### `POST /api/video/edit`
|
|
503
640
|
|
|
@@ -751,7 +888,8 @@ Registered only when `config.features.cardNews` is true (`routes/cardNews.ts`).
|
|
|
751
888
|
| `AGY_QUOTA_EXHAUSTED` | Gemini API quota exhausted (rate limit) |
|
|
752
889
|
| `AGY_PARSE_FAILED` | Could not parse artifact path from agy output |
|
|
753
890
|
| `AGY_ARTIFACT_NOT_FOUND` | Agy reported an artifact path that does not exist |
|
|
754
|
-
| `AGY_PATH_REJECTED` | Agy artifact path
|
|
891
|
+
| `AGY_PATH_REJECTED` | Agy artifact path, file type, or identity failed the allowed-root policy |
|
|
892
|
+
| `AGY_ARTIFACT_TOO_LARGE` | Agy artifact exceeded the inclusive 50MiB read limit |
|
|
755
893
|
| `AGY_VIDEO_UNSUPPORTED` | Video generation is not supported by the Gemini (agy) provider |
|
|
756
894
|
| `AGY_MASK_UNSUPPORTED` | Mask-based editing is not supported by the Gemini (agy) provider |
|
|
757
895
|
| `AGY_REF_TOO_MANY` | Too many reference images for agy (max 3) |
|
package/docs/CLI.md
CHANGED
|
@@ -12,6 +12,7 @@ For a quick start, see the [main README](../README.md). For endpoint mapping, se
|
|
|
12
12
|
| `ima2 setup` / `ima2 login` | Reconfigure saved auth (interactive) |
|
|
13
13
|
| `ima2 status` | Show config and OAuth status |
|
|
14
14
|
| `ima2 doctor` | Diagnose Node, package, config, and auth |
|
|
15
|
+
| `ima2 doctor --installation --json` | Offline package checks without config, account or network access |
|
|
15
16
|
| `ima2 doctor image-probe [--json]` | Run live sanitized Responses image probes for `EMPTY_RESPONSE` support |
|
|
16
17
|
| `ima2 open` | Open the web UI in a browser |
|
|
17
18
|
| `ima2 grok login/status/models/proxy` | Manage the bundled progrok runtime used by the Grok provider |
|
|
@@ -24,10 +25,40 @@ These work on most client commands:
|
|
|
24
25
|
|
|
25
26
|
| Flag | Meaning |
|
|
26
27
|
|---|---|
|
|
27
|
-
| `--server <url>` |
|
|
28
|
+
| `--server <url>` | Explicit server origin; overrides `IMA2_SERVER`, then credential-free advertise/default discovery |
|
|
28
29
|
| `--json` | Emit machine-readable JSON instead of human-formatted output |
|
|
29
30
|
| `-h`, `--help` | Show subcommand help |
|
|
30
31
|
|
|
32
|
+
## LAN server authentication
|
|
33
|
+
|
|
34
|
+
Export your private `IMA2_LAN_TOKEN`, then select the known server explicitly:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
IMA2_SERVER=http://192.168.1.20:3333 ima2 ping --json
|
|
38
|
+
ima2 models --server https://studio.example --json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The token goes only to the selected normalized HTTP(S) origin, never to arbitrary
|
|
42
|
+
advertise entries, redirects or external returned media. Do not put credentials,
|
|
43
|
+
query strings or non-root paths in `--server` / `IMA2_SERVER`. No token flag or
|
|
44
|
+
cookie jar is used. Auth/forbidden failures exit 4 with a safe code; an unreachable
|
|
45
|
+
explicit server exits 3. Explicit targets never silently fall back to local data.
|
|
46
|
+
`--local`/`--offline` options keep their documented local behavior.
|
|
47
|
+
|
|
48
|
+
For a proxy or changed published port, configure exact origins and restart:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
ima2 config set server.publicOrigins '["https://studio.example"]' --yes
|
|
52
|
+
ima2 config rm server.publicOrigins --yes
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Invalid origin values are rejected without echoing them. Removal can recover an
|
|
56
|
+
invalid primary or legacy file-layer value while preserving other settings; the
|
|
57
|
+
edited full object is written privately to the primary path and the legacy file
|
|
58
|
+
is retained. An invalid environment override must be unset or corrected separately.
|
|
59
|
+
Config listings/get redact nested credentials; `config path` identifies the file.
|
|
60
|
+
See [LAN HTTP/session and transport limits](API.md#local-and-lan-access).
|
|
61
|
+
|
|
31
62
|
## Agent discovery
|
|
32
63
|
|
|
33
64
|
Agents should start from the packaged skill and capability commands instead of guessing from scattered help text.
|
|
@@ -278,6 +309,39 @@ download fields such as `ok`, `path`, and `filename`. `ima2 video continue
|
|
|
278
309
|
|
|
279
310
|
## Diagnostics
|
|
280
311
|
|
|
312
|
+
`ima2 doctor --json` prints one JSON document with `schemaVersion: 1`, `mode`,
|
|
313
|
+
`checks` and `summary`. Each check has a stable code, severity, evidence category
|
|
314
|
+
and a fixed message/action. Exit0 means no failed checks; warnings alone do not
|
|
315
|
+
fail. Exit1 means a prerequisite failed. Invalid flag combinations exit2 with
|
|
316
|
+
stderr only. Standard mode retains provider-auth checks; missing OAuth can fail
|
|
317
|
+
standard doctor even if a different provider is selected.
|
|
318
|
+
|
|
319
|
+
Use `ima2 doctor --installation --json` to check only the package Node engine,
|
|
320
|
+
runtime dependencies/CLI entries, in-memory SQLite binding, packaged skills and UI
|
|
321
|
+
build. It does not load account/config files, repair anything, build the UI or make
|
|
322
|
+
network requests. Missing optional provider credentials are not installation errors.
|
|
323
|
+
It cannot combine with `--bundle`, `--verify-keys` or `--runtime`.
|
|
324
|
+
|
|
325
|
+
`ima2 doctor --bundle --json` retains the legacy version/node/platform/hostnameHash/
|
|
326
|
+
lanes fields and adds safe checks/summary. The compatibility hostname hash is not
|
|
327
|
+
an anonymity guarantee. Machine reports omit arbitrary diagnostic text, raw paths,
|
|
328
|
+
URLs, response bodies and credential values.
|
|
329
|
+
|
|
330
|
+
`--verify-keys` explicitly checks registry-declared non-generating authentication
|
|
331
|
+
endpoints. Configured/present is not verified, and successful authentication does
|
|
332
|
+
not prove generation capability or balance. Authentication rejection, rate limit,
|
|
333
|
+
upstream failure, network failure and timeout have different codes.
|
|
334
|
+
|
|
335
|
+
`--runtime http://127.0.0.1:3333` explicitly checks only that loopback origin's
|
|
336
|
+
`/api/health`; localhost and IPv6 loopback are also accepted. No credentials,
|
|
337
|
+
redirects, arbitrary paths, query strings or server discovery are used. A version
|
|
338
|
+
match proves a local response, not provider success. Authorization-required health
|
|
339
|
+
responses direct the operator to the configured authorized client.
|
|
340
|
+
|
|
341
|
+
Timeouts default to5000ms for key checks and1500ms for runtime health. Optional
|
|
342
|
+
`IMA2_DIAGNOSTIC_KEY_TIMEOUT_MS` and `IMA2_DIAGNOSTIC_RUNTIME_TIMEOUT_MS` override
|
|
343
|
+
them, capped at30000ms. Response cleanup remains inside the deadline.
|
|
344
|
+
|
|
281
345
|
`ima2 doctor image-probe` runs live Responses probes that help classify image
|
|
282
346
|
generation failures such as `EMPTY_RESPONSE`. It is intended for support
|
|
283
347
|
bundles, especially when OAuth is green but a simple prompt produces no image.
|
package/docs/DOCKER.md
CHANGED
|
@@ -5,11 +5,13 @@ web UI and server from source and runs the Express server directly.
|
|
|
5
5
|
|
|
6
6
|
## Quick start
|
|
7
7
|
|
|
8
|
+
First export your own strong, private `IMA2_LAN_TOKEN`. Do not deploy a sample token.
|
|
9
|
+
|
|
8
10
|
```bash
|
|
9
11
|
docker build -t ima2-gen .
|
|
10
12
|
docker run -d --name ima2 \
|
|
11
13
|
-p 3333:3333 \
|
|
12
|
-
-e IMA2_LAN_TOKEN
|
|
14
|
+
-e IMA2_LAN_TOKEN \
|
|
13
15
|
-v ima2-data:/data \
|
|
14
16
|
ima2-gen
|
|
15
17
|
```
|
|
@@ -17,11 +19,12 @@ docker run -d --name ima2 \
|
|
|
17
19
|
Or with compose:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
|
|
22
|
+
docker compose up -d
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
Then open `http://localhost:3333
|
|
24
|
-
|
|
25
|
+
Then open `http://localhost:3333/` and enter the token in the studio sign-in form.
|
|
26
|
+
The browser uses a short-lived HttpOnly session for APIs, images, videos and SSE.
|
|
27
|
+
CLI clients use the token environment variable with an explicit server origin.
|
|
25
28
|
|
|
26
29
|
## Why `IMA2_LAN_TOKEN` is required
|
|
27
30
|
|
|
@@ -38,6 +41,7 @@ container exits at startup with a clear error — that is by design, not a bug.
|
|
|
38
41
|
| `IMA2_LAN_TOKEN` | — (required) | API access token for non-loopback bind |
|
|
39
42
|
| `IMA2_PORT` | `3333` | Server port |
|
|
40
43
|
| `IMA2_HOST` | `0.0.0.0` | Bind host |
|
|
44
|
+
| `IMA2_PUBLIC_ORIGINS` | `[]` | JSON array of exact browser/proxy origins when address or published port differs |
|
|
41
45
|
| `IMA2_CONFIG_DIR` | `/data` | Config + generated output + credentials |
|
|
42
46
|
| `OPENAI_API_KEY` | — | Optional: API-key image provider |
|
|
43
47
|
| `GEMINI_API_KEY` | — | Optional: Gemini API provider |
|
|
@@ -54,6 +58,17 @@ in `/data`.
|
|
|
54
58
|
|
|
55
59
|
## Limitations
|
|
56
60
|
|
|
61
|
+
For `-p 8080:3333`, also pass
|
|
62
|
+
`-e IMA2_PUBLIC_ORIGINS='["http://localhost:8080"]'`. Add the exact LAN IP/DNS URL
|
|
63
|
+
used by remote browsers as needed. A TLS proxy must preserve external Host and
|
|
64
|
+
Origin, forward Set-Cookie and avoid caching or logging token queries. Do not use
|
|
65
|
+
a loopback bind behind an external proxy as a substitute for LAN authentication.
|
|
66
|
+
HTTP does not encrypt credentials; use trusted networks or TLS/VPN.
|
|
67
|
+
|
|
68
|
+
LAN generated media is now private/no-store. Old proxy/browser cached copies may
|
|
69
|
+
outlive an upgrade or sign-out; purge caches you control. Restart invalidates browser
|
|
70
|
+
sessions without deleting files. [Full access contract](API.md#local-and-lan-access).
|
|
71
|
+
|
|
57
72
|
- The image runs the server only. CLI workflows (`ima2 gen`, etc.) can exec
|
|
58
73
|
into the container (`docker exec -it ima2 node bin/ima2.js …`) but are
|
|
59
74
|
primarily designed for host installs.
|