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
package/.env.example
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
# ── Server ─────────────────────────────────────────────────────────────
|
|
6
6
|
# IMA2_PORT=3333 # also accepted: PORT
|
|
7
7
|
# IMA2_HOST=127.0.0.1
|
|
8
|
+
# Non-loopback access requires your own strong, private token (never commit it).
|
|
9
|
+
# IMA2_LAN_TOKEN=
|
|
10
|
+
# Exact serving origins for a TLS proxy, LAN DNS name or changed Docker port:
|
|
11
|
+
# IMA2_PUBLIC_ORIGINS=["https://studio.example"]
|
|
8
12
|
# IMA2_BODY_LIMIT=50mb
|
|
9
13
|
|
|
10
14
|
# ── OAuth proxy (openai-oauth subprocess) ──────────────────────────────
|
|
@@ -50,6 +54,12 @@
|
|
|
50
54
|
# ── Logging ────────────────────────────────────────────────────────────
|
|
51
55
|
# IMA2_LOG_LEVEL=info # trace|debug|info|warn|error|fatal
|
|
52
56
|
|
|
57
|
+
# Explicit doctor checks only; defaults in milliseconds, maximum 30000.
|
|
58
|
+
# --verify-keys checks remote authentication without generating images.
|
|
59
|
+
# IMA2_DIAGNOSTIC_KEY_TIMEOUT_MS=5000
|
|
60
|
+
# --runtime requires an explicit loopback server origin.
|
|
61
|
+
# IMA2_DIAGNOSTIC_RUNTIME_TIMEOUT_MS=1500
|
|
62
|
+
|
|
53
63
|
# ── Secrets (not merged from config.json, env only) ────────────────────
|
|
54
64
|
# OPENAI_API_KEY=sk-...
|
|
55
65
|
# XAI_API_KEY=xai-...
|
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
28
28
|
|
|
29
29
|
## [Unreleased]
|
|
30
30
|
|
|
31
|
+
### Production readiness
|
|
32
|
+
|
|
33
|
+
- Correct NovelAI positive/negative prompt pane sizing, scrolling and toolbar spacing across sidebar, bottom, home and mobile composers; retain drafts when changing providers.
|
|
34
|
+
- Keep provider/model labels, capabilities and selection consistent across the UI, CLI and generation routes, including unavailable or malformed provider catalogs.
|
|
35
|
+
- Preserve accepted-job tracking through disconnects, cancellation, restart and LAN reauthentication without automatically submitting the job again.
|
|
36
|
+
- Bound image/video artifact reads and downloads, reject escaped artifact paths, and keep cancellation from persisting an incomplete result.
|
|
37
|
+
- Add LAN sign-in with origin-bound, HttpOnly session cookies; protect generated media, SSE, Range and HEAD requests. Default loopback use remains token-free, and explicit header/query API credentials remain supported.
|
|
38
|
+
- Share failed-token cooldown across API, media, status and browser bootstrap; retain existing cookie sessions during a bearer-token cooldown.
|
|
39
|
+
- Bind CLI LAN credentials only to an explicitly selected server, reject redirects and credential-bearing URLs, and preserve provider authentication errors separately from LAN access failures.
|
|
40
|
+
- Add offline `doctor --installation --json`, safe diagnostic reports and metadata-derived installation/runtime documentation; improve Windows lock handling without terminating unrelated processes.
|
|
41
|
+
- Use a patched, development-only Node SDK downloader for source builds; keep runtime dependencies and install-script approvals unchanged.
|
|
42
|
+
|
|
43
|
+
LAN HTTP is intended for trusted networks and does not encrypt tokens or cookies; use an operator-configured HTTPS proxy or VPN on untrusted networks. Verification uses isolated synthetic providers, not a claim that live provider credentials, billing or every remote deployment have been exercised.
|
|
44
|
+
|
|
31
45
|
### Added
|
|
32
46
|
|
|
33
47
|
- **Current model defaults** — Grok search, image/video planning, and frame analysis now default to `grok-4.5` while retaining `grok-4.3` as an explicit compatibility override. GPT image generation, stylesheet analysis, Card News planning, and Prompt Builder default to `gpt-5.6-luna`; Grok video model discovery projects `grok-imagine-video-1.5` from runtime config.
|
package/README.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# ima2-gen
|
|
2
2
|
|
|
3
|
+
<!-- runtime-install:generated:start -->
|
|
4
|
+
| Contract | Value |
|
|
5
|
+
|---|---|
|
|
6
|
+
| Node engine | `>=22` |
|
|
7
|
+
| npm toolchain | `npm@11.18.0` |
|
|
8
|
+
| Release Node | `24.17.0` |
|
|
9
|
+
| CLI entry | `bin/ima2.js` |
|
|
10
|
+
| OpenAI SDK | `^7.4.0` |
|
|
11
|
+
| Express | `^5.1.0` |
|
|
12
|
+
<!-- runtime-install:generated:end -->
|
|
13
|
+
|
|
14
|
+
The installer derives its Node.js floor from package metadata and runs an offline installation check before launching the server.
|
|
15
|
+
|
|
3
16
|
<p align="center">
|
|
4
17
|
<img src="assets/logo.png" alt="ima2-gen logo" width="240">
|
|
5
18
|
</p>
|
|
@@ -75,7 +88,7 @@ irm https://lidge-jun.github.io/ima2-gen/install-windows.ps1 | iex
|
|
|
75
88
|
curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
|
|
76
89
|
```
|
|
77
90
|
|
|
78
|
-
Each script checks
|
|
91
|
+
Each script checks the package-derived Node.js floor, installs Node LTS through the best available method, installs once, and runs the offline installation check before launching `ima2 serve`. It does not stop unrelated processes or clear global locks.
|
|
79
92
|
|
|
80
93
|
### Setup
|
|
81
94
|
|
|
@@ -97,7 +110,7 @@ Stop the running server with Ctrl+C, then:
|
|
|
97
110
|
npm install -g ima2-gen@latest
|
|
98
111
|
```
|
|
99
112
|
|
|
100
|
-
Ctrl+C now performs a clean shutdown — closing the database, stopping child processes, and releasing file locks.
|
|
113
|
+
Ctrl+C now performs a clean shutdown — closing the database, stopping child processes, and releasing file locks. If an installation fails, inspect the reported npm permissions or stop the specific `ima2` process yourself; the installer does not perform broad process cleanup.
|
|
101
114
|
|
|
102
115
|
## What It Does
|
|
103
116
|
|
|
@@ -5,7 +5,7 @@ import { config } from "../../config.js";
|
|
|
5
5
|
import { buildIma2Capabilities } from "../../lib/capabilities.js";
|
|
6
6
|
import { parseArgs } from "../lib/args.js";
|
|
7
7
|
import { resolveServer, request } from "../lib/client.js";
|
|
8
|
-
import { color,
|
|
8
|
+
import { color, exitCodeForError, fail, json, out } from "../lib/output.js";
|
|
9
9
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
10
10
|
const PACKAGE_PATH = join(ROOT, "package.json");
|
|
11
11
|
const HELP = `
|
|
@@ -47,7 +47,8 @@ async function readCapabilities(args) {
|
|
|
47
47
|
return await request(server.base, "/api/capabilities", { timeoutMs: 5000 });
|
|
48
48
|
}
|
|
49
49
|
catch (error) {
|
|
50
|
-
if (args.server || args["require-server"]
|
|
50
|
+
if (args.server !== undefined || process.env.IMA2_SERVER !== undefined || args["require-server"]
|
|
51
|
+
|| error?.code !== "SERVER_UNREACHABLE")
|
|
51
52
|
throw error;
|
|
52
53
|
return localCapabilities();
|
|
53
54
|
}
|
|
@@ -131,6 +132,7 @@ export default async function capabilitiesCmd(argv) {
|
|
|
131
132
|
printText(capabilities);
|
|
132
133
|
}
|
|
133
134
|
catch (error) {
|
|
134
|
-
|
|
135
|
+
fail({ json: Boolean(args.json), code: error?.code ?? "SERVER_REQUEST_FAILED",
|
|
136
|
+
message: error?.message || "server request failed", exitCode: exitCodeForError(error) });
|
|
135
137
|
}
|
|
136
138
|
}
|
package/bin/commands/config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createInterface } from "readline/promises";
|
|
|
2
2
|
import { parseArgs } from "../lib/args.js";
|
|
3
3
|
import { confirmDestructiveAction } from "../lib/destructive-confirm.js";
|
|
4
4
|
import { out, die, color, json, exitFlushed } from "../lib/output.js";
|
|
5
|
+
import { parsePublicOrigins } from "../../lib/localAccessPolicy.js";
|
|
5
6
|
import { CONFIG_FILE, KEY_TO_ENV, WRITABLE_CONFIG_KEYS, buildEffectiveConfig, deleteNestedKey, displayPath, envOverrideForKey, getNestedKey, isAuthConfigKey, isSensitiveConfigKey, isWritableConfigKey, loadFileCfg, parseConfigValue, redactValue, restartNotice, saveFileCfg, setNestedKey, } from "../lib/config-store.js";
|
|
6
7
|
const HELP = `
|
|
7
8
|
ima2 config <subcommand> [options]
|
|
@@ -33,7 +34,7 @@ async function pathSub(_argv) {
|
|
|
33
34
|
async function lsSub(argv) {
|
|
34
35
|
const args = parseArgs(argv, { flags: FLAGS });
|
|
35
36
|
if (args.effective) {
|
|
36
|
-
const eff = buildEffectiveConfig();
|
|
37
|
+
const eff = redactValue("", buildEffectiveConfig());
|
|
37
38
|
if (args.json) {
|
|
38
39
|
json(eff);
|
|
39
40
|
return;
|
|
@@ -41,7 +42,7 @@ async function lsSub(argv) {
|
|
|
41
42
|
out(JSON.stringify(eff, null, 2));
|
|
42
43
|
}
|
|
43
44
|
else {
|
|
44
|
-
const fileCfg = loadFileCfg();
|
|
45
|
+
const fileCfg = redactValue("", loadFileCfg());
|
|
45
46
|
if (args.json) {
|
|
46
47
|
json(fileCfg);
|
|
47
48
|
return;
|
|
@@ -93,11 +94,21 @@ async function setSub(argv) {
|
|
|
93
94
|
if (!isWritableConfigKey(key)) {
|
|
94
95
|
die(2, `unknown config key: "${key}". Run 'ima2 config ls --effective' to see the config structure.`);
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
+
let value = parseConfigValue(rawValue);
|
|
98
|
+
if (key === "server.publicOrigins") {
|
|
99
|
+
try {
|
|
100
|
+
value = parsePublicOrigins(value);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
die(2, "INVALID_PUBLIC_ORIGINS: expected an array of exact HTTP(S) origins without credentials, paths, query or fragments.");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
97
106
|
// Warn if env var is overriding this key
|
|
98
107
|
const override = envOverrideForKey(key);
|
|
99
108
|
if (override) {
|
|
100
|
-
out(color.yellow(
|
|
109
|
+
out(color.yellow(key === "server.publicOrigins"
|
|
110
|
+
? `warning: env ${override.envVar} is currently overriding this value.`
|
|
111
|
+
: `warning: env ${override.envVar}=${override.value} is currently overriding this value.`));
|
|
101
112
|
out(`The file change will only apply after unsetting the env var and restarting the server.`);
|
|
102
113
|
}
|
|
103
114
|
// Confirm if writing a sensitive key
|
package/bin/commands/defaults.js
CHANGED
|
@@ -2,7 +2,7 @@ import { config } from "../../config.js";
|
|
|
2
2
|
import { parseArgs } from "../lib/args.js";
|
|
3
3
|
import { resolveServer, request } from "../lib/client.js";
|
|
4
4
|
import { buildEffectiveConfig, deleteNestedKey, displayPath, envOverrideForKey, getNestedKey, loadCliDefaults, loadFileCfg, restartNotice, saveFileCfg, setNestedKey, CONFIG_FILE, } from "../lib/config-store.js";
|
|
5
|
-
import { color, die,
|
|
5
|
+
import { color, die, exitCodeForError, fail, json, out } from "../lib/output.js";
|
|
6
6
|
const MODEL_KEYS = ["imageModels.default", "apiProvider.defaultImageModel"];
|
|
7
7
|
const REASONING_KEYS = ["imageModels.reasoningEffort", "apiProvider.defaultReasoningEffort"];
|
|
8
8
|
const HELP = `
|
|
@@ -67,7 +67,8 @@ async function readDefaults(args) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
if (args.server
|
|
70
|
+
if (args.server !== undefined || process.env.IMA2_SERVER !== undefined
|
|
71
|
+
|| error?.code !== "SERVER_UNREACHABLE")
|
|
71
72
|
throw error;
|
|
72
73
|
return localDefaults();
|
|
73
74
|
}
|
|
@@ -143,8 +144,8 @@ async function fetchModelCatalog(args) {
|
|
|
143
144
|
}
|
|
144
145
|
catch (error) {
|
|
145
146
|
fail({
|
|
146
|
-
json: Boolean(args.json), code: "
|
|
147
|
-
message: error?.message || "server
|
|
147
|
+
json: Boolean(args.json), code: error?.code ?? "SERVER_REQUEST_FAILED",
|
|
148
|
+
message: error?.message || "server request failed", exitCode: exitCodeForError(error),
|
|
148
149
|
});
|
|
149
150
|
}
|
|
150
151
|
}
|
|
@@ -197,7 +198,8 @@ async function listSub(argv) {
|
|
|
197
198
|
printDefaults(payload);
|
|
198
199
|
}
|
|
199
200
|
catch (error) {
|
|
200
|
-
|
|
201
|
+
fail({ json: Boolean(args.json), code: error?.code ?? "SERVER_REQUEST_FAILED",
|
|
202
|
+
message: error?.message || "server request failed", exitCode: exitCodeForError(error) });
|
|
201
203
|
}
|
|
202
204
|
}
|
|
203
205
|
async function setSub(argv) {
|
package/bin/commands/doctor.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import { createRequire } from "module";
|
|
2
1
|
import { existsSync, readFileSync } from "fs";
|
|
3
2
|
import { dirname, join } from "path";
|
|
4
3
|
import { fileURLToPath } from "url";
|
|
5
4
|
import { buildHardeningDoctorLines } from "../lib/doctor-checks.js";
|
|
6
5
|
import { buildStorageDoctorLines } from "../lib/storage-doctor.js";
|
|
7
6
|
import { detectCodexAuth } from "../../lib/codexDetect.js";
|
|
8
|
-
import { resolvePackageBin } from "../../lib/packageCli.js";
|
|
9
7
|
import { runImageDoctorProbe } from "../../lib/responsesDoctor.js";
|
|
10
8
|
import { config as runtimeConfig } from "../../config.js";
|
|
11
9
|
import { exitFlushed } from "../lib/output.js";
|
|
12
10
|
import { buildProviderDoctorLines, verifyConfiguredKeys } from "../lib/doctor-providers.js";
|
|
13
11
|
import { buildMediaDoctorLines } from "../lib/doctor-media.js";
|
|
14
12
|
import { buildDoctorBundle } from "../lib/doctor-bundle.js";
|
|
13
|
+
import { buildDoctorReport, renderDoctorReport } from "../lib/doctor-report.js";
|
|
14
|
+
import { buildInstallationDoctorLines, checkNodeEngine, missingRuntimeDeps, probeDoctorRuntime } from "../lib/doctor-runtime.js";
|
|
15
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
16
|
const ROOT = join(__dirname, "../..");
|
|
17
|
-
const requireFromRoot = createRequire(join(ROOT, "package.json"));
|
|
18
17
|
const CONFIG_FILE = runtimeConfig.storage.configFile;
|
|
19
18
|
const LEGACY_CONFIG_FILE = join(ROOT, ".ima2", "config.json");
|
|
20
19
|
let pkg = { version: "?", name: "ima2-gen" };
|
|
21
20
|
try {
|
|
22
|
-
|
|
21
|
+
const metadata = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
|
|
22
|
+
if (metadata && typeof metadata.version === "string" && typeof metadata.name === "string")
|
|
23
|
+
pkg = metadata;
|
|
23
24
|
}
|
|
24
25
|
catch { }
|
|
25
26
|
function loadConfig() {
|
|
@@ -34,32 +35,6 @@ function loadConfig() {
|
|
|
34
35
|
}
|
|
35
36
|
return {};
|
|
36
37
|
}
|
|
37
|
-
function missingRuntimeDeps() {
|
|
38
|
-
const deps = ["express", "better-sqlite3", "openai", "openai-oauth", "progrok/package.json", "@openai/codex/package.json", "zod"];
|
|
39
|
-
const missing = deps.filter((dep) => {
|
|
40
|
-
try {
|
|
41
|
-
requireFromRoot.resolve(dep);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
}).map((dep) => dep.endsWith("/package.json") ? dep.slice(0, -"/package.json".length) : dep);
|
|
48
|
-
for (const pair of [["openai-oauth", "openai-oauth"], ["@openai/codex", "codex"]]) {
|
|
49
|
-
const packageName = pair[0];
|
|
50
|
-
const binName = pair[1];
|
|
51
|
-
if (!packageName || !binName)
|
|
52
|
-
continue;
|
|
53
|
-
try {
|
|
54
|
-
resolvePackageBin(packageName, binName);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
if (!missing.includes(packageName))
|
|
58
|
-
missing.push(packageName);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return missing;
|
|
62
|
-
}
|
|
63
38
|
function valueAfter(args, name) {
|
|
64
39
|
const index = args.indexOf(name);
|
|
65
40
|
if (index === -1)
|
|
@@ -92,6 +67,13 @@ export function showDoctorHelp() {
|
|
|
92
67
|
Usage: ima2 doctor [image-probe] [options]
|
|
93
68
|
|
|
94
69
|
Diagnose environment, storage, dependencies, and authentication.
|
|
70
|
+
--json Emit one machine-readable diagnostic report
|
|
71
|
+
--bundle Emit a safe compatibility bundle (--json for JSON only)
|
|
72
|
+
--installation Offline installation checks; no account/config reads
|
|
73
|
+
--verify-keys Explicit non-generating remote authentication checks
|
|
74
|
+
--runtime <origin> Explicit loopback health/version check; no credentials
|
|
75
|
+
Installation mode cannot combine with bundle, verify-keys or runtime.
|
|
76
|
+
image-probe performs live billed generation; ordinary doctor never runs it.
|
|
95
77
|
Run 'ima2 doctor image-probe --help' for live image probe options.
|
|
96
78
|
`);
|
|
97
79
|
}
|
|
@@ -136,16 +118,12 @@ async function standardDoctor(args = []) {
|
|
|
136
118
|
console.log(`\n ${pkg.name} v${pkg.version} — Doctor\n`);
|
|
137
119
|
let ok = 0;
|
|
138
120
|
let fail = 0;
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
console.log(` ✓ Node.js ${nodeVersion} (>= 20)`);
|
|
121
|
+
const node = checkNodeEngine(process.version, pkg.engines?.node);
|
|
122
|
+
console.log(` ${node.kind === "pass" ? "✓" : "✗"} ${node.text}`);
|
|
123
|
+
if (node.kind === "pass")
|
|
143
124
|
ok++;
|
|
144
|
-
|
|
145
|
-
else {
|
|
146
|
-
console.log(` ✗ Node.js ${nodeVersion} (requires >= 20)`);
|
|
125
|
+
else
|
|
147
126
|
fail++;
|
|
148
|
-
}
|
|
149
127
|
if (existsSync(join(ROOT, "package.json"))) {
|
|
150
128
|
console.log(" ✓ package.json found");
|
|
151
129
|
ok++;
|
|
@@ -154,7 +132,7 @@ async function standardDoctor(args = []) {
|
|
|
154
132
|
console.log(" ✗ package.json missing");
|
|
155
133
|
fail++;
|
|
156
134
|
}
|
|
157
|
-
const missingDeps = missingRuntimeDeps();
|
|
135
|
+
const missingDeps = missingRuntimeDeps(ROOT);
|
|
158
136
|
if (missingDeps.length === 0) {
|
|
159
137
|
console.log(" ✓ runtime dependencies resolvable");
|
|
160
138
|
ok++;
|
|
@@ -276,5 +254,76 @@ export async function doctor(args = []) {
|
|
|
276
254
|
await imageProbe(args.slice(1));
|
|
277
255
|
return;
|
|
278
256
|
}
|
|
257
|
+
if (!validDoctorArguments(args)) {
|
|
258
|
+
console.error("Invalid doctor options; use ima2 doctor --help.");
|
|
259
|
+
exitFlushed(2);
|
|
260
|
+
}
|
|
261
|
+
if (args.includes("--json") || args.includes("--bundle") || args.includes("--runtime") || args.includes("--installation")) {
|
|
262
|
+
await machineDoctor(args);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
279
265
|
await standardDoctor(args);
|
|
280
266
|
}
|
|
267
|
+
function validDoctorArguments(args) {
|
|
268
|
+
const seen = new Set();
|
|
269
|
+
for (let i = 0; i < args.length; i++) {
|
|
270
|
+
const arg = args[i];
|
|
271
|
+
if (seen.has(arg) || !["--json", "--bundle", "--installation", "--verify-keys", "--runtime"].includes(arg))
|
|
272
|
+
return false;
|
|
273
|
+
seen.add(arg);
|
|
274
|
+
if (arg === "--runtime" && (!args[++i] || args[i].startsWith("--")))
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
return !seen.has("--installation") || !["--bundle", "--verify-keys", "--runtime"].some((flag) => seen.has(flag));
|
|
278
|
+
}
|
|
279
|
+
function diagnosticConfig(lines) {
|
|
280
|
+
try {
|
|
281
|
+
const path = existsSync(CONFIG_FILE) ? CONFIG_FILE : existsSync(LEGACY_CONFIG_FILE) ? LEGACY_CONFIG_FILE : null;
|
|
282
|
+
const value = path ? JSON.parse(readFileSync(path, "utf8")) : {};
|
|
283
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
284
|
+
throw Error("CONFIG_INVALID");
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
lines.push({ code: "CONFIG_INVALID", kind: "fail", text: "Configuration is not a JSON object" });
|
|
289
|
+
return {};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function machineDoctor(args) {
|
|
293
|
+
const installation = args.includes("--installation"), lines = buildInstallationDoctorLines(ROOT);
|
|
294
|
+
let providerLines = [];
|
|
295
|
+
try {
|
|
296
|
+
if (!installation) {
|
|
297
|
+
const fileConfig = diagnosticConfig(lines);
|
|
298
|
+
lines.push({ code: fileConfig.provider ? "CONFIG_PRESENT" : "CONFIG_MISSING", kind: fileConfig.provider ? "info" : "warn", text: "Configuration selection" });
|
|
299
|
+
try {
|
|
300
|
+
if (existsSync(runtimeConfig.storage.advertiseFile)) {
|
|
301
|
+
const value = JSON.parse(readFileSync(runtimeConfig.storage.advertiseFile, "utf8"));
|
|
302
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
303
|
+
throw Error("invalid");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
lines.push({ code: "ADVERTISEMENT_INVALID", kind: "warn", text: "Invalid server advertisement" });
|
|
308
|
+
}
|
|
309
|
+
lines.push(...await buildHardeningDoctorLines({ root: ROOT, configFile: CONFIG_FILE, fileConfig, includeInstallationChecks: false }));
|
|
310
|
+
providerLines = buildProviderDoctorLines(fileConfig);
|
|
311
|
+
lines.push(...providerLines);
|
|
312
|
+
if (args.includes("--verify-keys"))
|
|
313
|
+
lines.push(...await verifyConfiguredKeys(fileConfig));
|
|
314
|
+
lines.push(...await buildMediaDoctorLines());
|
|
315
|
+
const url = valueAfter(args, "--runtime");
|
|
316
|
+
if (url)
|
|
317
|
+
lines.push(...await probeDoctorRuntime({ url, expectedVersion: pkg.version, timeoutMs: runtimeConfig.diagnostics.runtimeTimeoutMs }));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
lines.push({ code: "DIAGNOSTIC_UNKNOWN", kind: "fail", text: "A diagnostic could not complete" });
|
|
322
|
+
}
|
|
323
|
+
const report = buildDoctorReport({ version: pkg.version, mode: installation ? "installation" : "standard", lines });
|
|
324
|
+
if (args.includes("--json"))
|
|
325
|
+
console.log(JSON.stringify(args.includes("--bundle") ? buildDoctorBundle({ version: pkg.version, providerLines, report }) : report));
|
|
326
|
+
else
|
|
327
|
+
console.log(renderDoctorReport(report));
|
|
328
|
+
exitFlushed(report.summary.exitCode);
|
|
329
|
+
}
|
package/bin/commands/gen.js
CHANGED
|
@@ -6,13 +6,13 @@ import { sizeDrifted } from "../../lib/sizeNudge.js";
|
|
|
6
6
|
import { errInfo } from "../../lib/errInfo.js";
|
|
7
7
|
import { parseArgs } from "../lib/args.js";
|
|
8
8
|
import { wasFlagPassed } from "../lib/argsExplicit.js";
|
|
9
|
-
import { resolveServer, request, normalizeGenerate } from "../lib/client.js";
|
|
9
|
+
import { resolveServer, request, normalizeGenerate, fetchServer } from "../lib/client.js";
|
|
10
10
|
import { fileToDataUri, dataUriToFile, defaultOutName, readStdin } from "../lib/files.js";
|
|
11
11
|
import { loadCliDefaults } from "../lib/config-store.js";
|
|
12
12
|
import { runMcpJob } from "../lib/mcpJob.js";
|
|
13
13
|
import { characterElementIdForMcp } from "../lib/characterResolve.js";
|
|
14
14
|
import { resolveTarget } from "../lib/modelResolver.js";
|
|
15
|
-
import { out, die, dieWithError, color, err, fail, json } from "../lib/output.js";
|
|
15
|
+
import { out, die, dieWithError, color, err, exitCodeForError, fail, json } from "../lib/output.js";
|
|
16
16
|
import { createCliRequestId, recoverGeneratedOutputs, formatRecoveryHint } from "../lib/recover-output.js";
|
|
17
17
|
import { deriveProviderIds } from "../../lib/providers/derive.js";
|
|
18
18
|
import { listProviders } from "../../lib/mcp/providerRegistry.js";
|
|
@@ -140,7 +140,10 @@ function savedLine(path, actual, requested) {
|
|
|
140
140
|
return [head, color.dim(` ! requested ${String(requested)}; the provider returned a different size`)];
|
|
141
141
|
}
|
|
142
142
|
function failServer(jsonMode, error) {
|
|
143
|
+
const code = error?.code ?? "SERVER_REQUEST_FAILED";
|
|
143
144
|
const message = error?.message || "server unreachable";
|
|
145
|
+
if (code !== "SERVER_UNREACHABLE")
|
|
146
|
+
fail({ json: jsonMode, code, message, exitCode: exitCodeForError(error) });
|
|
144
147
|
if (jsonMode)
|
|
145
148
|
err("Hint: start the server with `ima2 serve`.");
|
|
146
149
|
fail({ json: jsonMode, code: "SERVER_UNREACHABLE", message: `${message}\nHint: run ima2 serve`, exitCode: 3 });
|
|
@@ -231,7 +234,7 @@ function validateMcpRefs(refs, context, roles, jsonMode) {
|
|
|
231
234
|
return parsed;
|
|
232
235
|
}
|
|
233
236
|
async function downloadMcpResult(serverBase, url, target) {
|
|
234
|
-
const response = await
|
|
237
|
+
const response = await fetchServer(serverBase, url);
|
|
235
238
|
if (!response.ok)
|
|
236
239
|
die(1, `failed to download image: HTTP ${response.status}`);
|
|
237
240
|
await mkdir(dirname(target), { recursive: true });
|
|
@@ -267,7 +270,8 @@ async function runMcpImage(argv, args, context) {
|
|
|
267
270
|
}
|
|
268
271
|
catch (error) {
|
|
269
272
|
const typed = error;
|
|
270
|
-
fail({ json: Boolean(args.json), code: typed.code ?? "MCP_GENERATION_FAILED", message: typed.message,
|
|
273
|
+
fail({ json: Boolean(args.json), code: typed.code ?? "MCP_GENERATION_FAILED", message: typed.message,
|
|
274
|
+
exitCode: exitCodeForError(error) === 4 ? 4 : 1 });
|
|
271
275
|
}
|
|
272
276
|
}
|
|
273
277
|
function validateCoreFlags(args) {
|
|
@@ -384,11 +388,24 @@ async function runCoreImage(args, context) {
|
|
|
384
388
|
}
|
|
385
389
|
}
|
|
386
390
|
export default async function genCmd(argv) {
|
|
391
|
+
try {
|
|
392
|
+
await generate(argv);
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
if (!(error instanceof Error))
|
|
396
|
+
throw error; // Preserve the output owner's exit marker.
|
|
397
|
+
fail({ json: argv.includes("--json"), code: error.code ?? "GENERATION_FAILED",
|
|
398
|
+
message: error.message, exitCode: exitCodeForError(error) });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function generate(argv) {
|
|
387
402
|
const args = parseArgs(argv, SPEC);
|
|
388
403
|
if (args.help) {
|
|
389
404
|
out(HELP);
|
|
390
405
|
return;
|
|
391
406
|
}
|
|
407
|
+
if (args._unknown.length)
|
|
408
|
+
die(2, `unknown option: ${args._unknown[0]}`);
|
|
392
409
|
const naiPreflight = unwrapNaiCliResult(parseNaiCliOptions(args, "allow-unknown"), Boolean(args.json));
|
|
393
410
|
let prompt = args.positional.join(" ");
|
|
394
411
|
if (!prompt && !args.stdin)
|
package/bin/commands/models.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "../lib/args.js";
|
|
2
2
|
import { request, resolveServer } from "../lib/client.js";
|
|
3
|
-
import { fail, json, out, table } from "../lib/output.js";
|
|
3
|
+
import { exitCodeForError, fail, json, out, table } from "../lib/output.js";
|
|
4
4
|
const SPEC = {
|
|
5
5
|
flags: {
|
|
6
6
|
kind: { type: "string" },
|
|
@@ -61,7 +61,7 @@ async function fetchCatalog(serverFlag, isJson) {
|
|
|
61
61
|
}
|
|
62
62
|
catch (error) {
|
|
63
63
|
const message = error?.message || "server unreachable";
|
|
64
|
-
fail({ json: isJson, code: "
|
|
64
|
+
fail({ json: isJson, code: error?.code ?? "SERVER_REQUEST_FAILED", message, exitCode: exitCodeForError(error) });
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
export default async function modelsCmd(argv) {
|
package/bin/commands/ping.js
CHANGED
|
@@ -28,7 +28,7 @@ export default async function pingCmd(argv) {
|
|
|
28
28
|
catch (e) {
|
|
29
29
|
const err = errInfo(e);
|
|
30
30
|
if (args.json) {
|
|
31
|
-
json({ ok: false, error: err.message });
|
|
31
|
+
json({ ok: false, error: err.message, code: err.code });
|
|
32
32
|
exitFlushed(exitCodeForError(e));
|
|
33
33
|
}
|
|
34
34
|
die(exitCodeForError(e), err.message);
|
package/bin/commands/prompt.js
CHANGED
|
@@ -356,9 +356,11 @@ async function importSources(argv) {
|
|
|
356
356
|
const args = parseArgs(argv, { flags: COMMON_FLAGS });
|
|
357
357
|
const server = await getServer(args);
|
|
358
358
|
const [curated, discovery] = await Promise.all([
|
|
359
|
-
request(server.base, "/api/prompts/import/curated-sources").catch(() => (
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
request(server.base, "/api/prompts/import/curated-sources").catch((error) => { if (exitCodeForError(error) === 4)
|
|
360
|
+
throw error; return {}; }),
|
|
361
|
+
request(server.base, "/api/prompts/import/discovery").catch((error) => { if (exitCodeForError(error) === 4)
|
|
362
|
+
throw error; return {}; }),
|
|
363
|
+
]).catch(handle);
|
|
362
364
|
if (args.json) {
|
|
363
365
|
json({ curated, discovery });
|
|
364
366
|
return;
|
package/bin/commands/service.js
CHANGED
|
@@ -3,6 +3,8 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { clearServerBinding, fetchServerUrl, resolveServer } from "../lib/client.js";
|
|
7
|
+
import { dieWithError } from "../lib/output.js";
|
|
6
8
|
import { LAUNCHD_LABEL, SYSTEMD_UNIT, launchctlOutputIndicatesFailure, renderLaunchdPlist, renderSystemdUnit, serviceStateStale, } from "../lib/serviceTemplates.js";
|
|
7
9
|
import { corroborateByStartTime, escalateKill, gracefulStop, isProcessAlive, verifyServerIdentity, waitForExit, } from "../../lib/processControl.js";
|
|
8
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -82,19 +84,35 @@ function readAdvertise() {
|
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
async function waitForHealth(timeoutMs) {
|
|
87
|
+
clearServerBinding();
|
|
88
|
+
const explicitTarget = process.env.IMA2_SERVER;
|
|
89
|
+
let lastSelectionError;
|
|
85
90
|
const deadline = Date.now() + timeoutMs;
|
|
86
91
|
while (Date.now() < deadline) {
|
|
87
92
|
const entry = readAdvertise();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
93
|
+
try {
|
|
94
|
+
if (explicitTarget !== undefined) {
|
|
95
|
+
const selected = await resolveServer({ serverFlag: explicitTarget });
|
|
96
|
+
// Only a matching advertisement can describe the selected service.
|
|
97
|
+
return { ok: true, entry: entry?.url?.replace(/\/$/, "") === selected.base ? entry : { url: selected.base } };
|
|
98
|
+
}
|
|
99
|
+
if (entry?.url) {
|
|
100
|
+
const r = await fetchServerUrl(`${String(entry.url).replace(/\/$/, "")}/api/health`, { headers: { connection: "close" } });
|
|
91
101
|
if (r.ok)
|
|
92
102
|
return { ok: true, entry };
|
|
93
103
|
}
|
|
94
|
-
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const code = error?.code;
|
|
107
|
+
if (code !== "NETWORK_FAILED" && code !== "SERVER_UNREACHABLE")
|
|
108
|
+
throw error;
|
|
109
|
+
if (explicitTarget !== undefined)
|
|
110
|
+
lastSelectionError = error;
|
|
95
111
|
}
|
|
96
112
|
await new Promise((r) => setTimeout(r, 500));
|
|
97
113
|
}
|
|
114
|
+
if (lastSelectionError)
|
|
115
|
+
throw lastSelectionError;
|
|
98
116
|
return { ok: false, entry: readAdvertise() };
|
|
99
117
|
}
|
|
100
118
|
function reportProviderLiveness(entry) {
|
|
@@ -415,6 +433,16 @@ const HELP = `
|
|
|
415
433
|
repair Re-render artifacts after node/npm path moves
|
|
416
434
|
`;
|
|
417
435
|
export async function service(args = []) {
|
|
436
|
+
try {
|
|
437
|
+
await dispatchService(args);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
if (!(error instanceof Error))
|
|
441
|
+
throw error;
|
|
442
|
+
dieWithError(error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function dispatchService(args) {
|
|
418
446
|
const sub = args[0];
|
|
419
447
|
switch (sub) {
|
|
420
448
|
case "install":
|
package/bin/commands/tools.js
CHANGED
|
@@ -11,7 +11,7 @@ import { buildToolShow, buildToolsList, catalogVersion, errorEnvelope, execution
|
|
|
11
11
|
import { loadAllBundledSnapshots, readLocalSnapshot } from "../../lib/mcp/snapshotStore.js";
|
|
12
12
|
import { parseArgs } from "../lib/args.js";
|
|
13
13
|
import { resolveServer, request } from "../lib/client.js";
|
|
14
|
-
import { json, out } from "../lib/output.js";
|
|
14
|
+
import { json, out, exitCodeForError } from "../lib/output.js";
|
|
15
15
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
16
16
|
const HELP = `
|
|
17
17
|
ima2 tools <list|show|schema|call> [id] [--json] [--offline] [--server <url>] [--input '<json>']
|
|
@@ -65,7 +65,7 @@ async function fromServer(args, path) {
|
|
|
65
65
|
return await request(server.base, path, { timeoutMs: 8000 });
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
|
-
if (error.code === "SERVER_UNREACHABLE" && !args.server)
|
|
68
|
+
if (error.code === "SERVER_UNREACHABLE" && !args.server && !process.env.IMA2_SERVER)
|
|
69
69
|
return null; // offline fallback
|
|
70
70
|
throw error;
|
|
71
71
|
}
|
|
@@ -77,7 +77,7 @@ function emit(payload, asJson) {
|
|
|
77
77
|
}
|
|
78
78
|
json(payload); // machine surface: JSON is the primary output either way
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
async function runToolsCommand(argv) {
|
|
81
81
|
const args = parseArgs(argv, { flags: FLAGS });
|
|
82
82
|
if (args.help) {
|
|
83
83
|
out(HELP);
|
|
@@ -124,6 +124,22 @@ export default async function toolsCommand(argv) {
|
|
|
124
124
|
out(HELP);
|
|
125
125
|
process.exitCode = sub ? 1 : 0;
|
|
126
126
|
}
|
|
127
|
+
export default async function toolsCommand(argv) {
|
|
128
|
+
try {
|
|
129
|
+
await runToolsCommand(argv);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (!(error instanceof Error))
|
|
133
|
+
throw error;
|
|
134
|
+
const code = error.code;
|
|
135
|
+
const exit = exitCodeForError(error);
|
|
136
|
+
if (exit !== 4 && code !== "SERVER_UNREACHABLE")
|
|
137
|
+
throw error;
|
|
138
|
+
const envelope = errorEnvelope("server_error", error.message, { catalogVersion: "unavailable", cliVersion: cliVersion() });
|
|
139
|
+
json({ ...envelope, error: { ...envelope.error, code: code ?? "SERVER_ACCESS_DENIED" } });
|
|
140
|
+
process.exitCode = exit;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
127
143
|
async function callTool(args, id, asJson) {
|
|
128
144
|
const entries = localEntries();
|
|
129
145
|
const meta = localMeta(entries);
|