@iicp/client 0.7.76 → 0.7.77

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/README.md CHANGED
@@ -39,7 +39,7 @@ What good looks like:
39
39
  ```bash
40
40
  iicp-node --help # shows query, serve, proxy, mcp-gateway, credits, ...
41
41
  which iicp-node # points to your Node/npm environment
42
- iicp-node --version # prints iicp-node 0.7.76 or newer
42
+ iicp-node --version # prints iicp-node 0.7.77 or newer
43
43
  ```
44
44
 
45
45
  The query command contacts the public directory, discovers a matching live node,
@@ -88,7 +88,7 @@ base URL. Full guide: <https://iicp.network/docs/proxy>
88
88
 
89
89
  ## Provider upgrade note
90
90
 
91
- > **Upgrade note (0.7.76)** — upgrade provider nodes so Quick Tunnel endpoints
91
+ > **Upgrade note (0.7.77)** — upgrade provider nodes so Quick Tunnel endpoints
92
92
  > recover safely after sleep, idle, Cloudflare edge drops, and local DNS
93
93
  > propagation lag on freshly-created `trycloudflare.com` URLs. Tunnel
94
94
  > twilight/recovery still heartbeats as unavailable and only re-registers once
@@ -112,7 +112,7 @@ If a node is older than 0.7.67, perform one manual upgrade/restart first,
112
112
  especially for Dockerized Python or TypeScript providers: early updater wiring
113
113
  did not reliably cover every normal `serve` path. For Docker, use a Compose
114
114
  `restart: unless-stopped` policy (or `docker run --restart unless-stopped`) so
115
- 0.7.76 can intentionally exit from a confirmed tunnel-dead state and let Docker
115
+ 0.7.77 can intentionally exit from a confirmed tunnel-dead state and let Docker
116
116
  bring it back cleanly.
117
117
 
118
118
  > **Upgrade note (0.5.3)** — if you operate a node and use the native IICP
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA2CA,OAAO,EAeL,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAGvB,KAAK,aAAa,GAAG,cAAc,cAAc,CAAC,CAAC;AAEnD,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,yBAA8B,GAAG,MAAM,IAAI,CA+C3F;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,qGAAqG;IACrG,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iGAAiG;IACjG,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoBD,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE/D,wBAAgB,uBAAuB,IAAI,gBAAgB,CAM1D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,OAAO,GAAG,kBAAkB,CAGpG;AA6UD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,OAAO,GACrB,KAAK,CAAC,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC,CAGtC;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWlE;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,EAEhB,OAAO,CAAC,EAAE,GAAG,GACZ,MAAM,GAAG,IAAI,CAUf;AAiFD,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,SAAS,CAsB9E;AAo0BD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA+D5D;AAqfD,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBlF"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAiDA,OAAO,EAeL,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAGvB,KAAK,aAAa,GAAG,cAAc,cAAc,CAAC,CAAC;AAEnD,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,yBAA8B,GAAG,MAAM,IAAI,CA+C3F;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,qGAAqG;IACrG,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iGAAiG;IACjG,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoBD,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE/D,wBAAgB,uBAAuB,IAAI,gBAAgB,CAM1D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,OAAO,GAAG,kBAAkB,CAGpG;AAwZD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,OAAO,GACrB,KAAK,CAAC,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC,CAGtC;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWlE;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,EAEhB,OAAO,CAAC,EAAE,GAAG,GACZ,MAAM,GAAG,IAAI,CAUf;AAiFD,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,SAAS,CAsB9E;AAo0BD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA+D5D;AAmoBD,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBlF"}
package/dist/cli.js CHANGED
@@ -46,6 +46,7 @@ const cip_policy_js_1 = require("./cip_policy.js");
46
46
  const index_js_1 = require("./proxy/index.js");
47
47
  const instance_lock_js_1 = require("./instance_lock.js");
48
48
  const index_js_2 = require("./backends/index.js");
49
+ const recovery_js_1 = require("./recovery.js");
49
50
  const identity_js_1 = require("./identity.js");
50
51
  const delegation_js_1 = require("./delegation.js");
51
52
  /**
@@ -138,11 +139,13 @@ function printHelp() {
138
139
  ` init Interactive wizard — set up operator + first node config\n` +
139
140
  ` list List node configs saved under ~/.iicp/nodes/\n` +
140
141
  ` serve Register and serve a node\n` +
142
+ ` doctor Check local health, directory presence, and recovery action\n` +
141
143
  ` query <prompt> Discover mesh nodes and submit a chat task\n` +
142
144
  ` credits Show your operator wallet plus this node's credit ledger\n` +
143
145
  ` proxy Run the local OpenAI/Ollama/Anthropic-compat gateway (loopback; no registration)\n` +
144
146
  ` mcp-gateway Bridge a local MCP server as an IICP provider node (registers + serves)\n` +
145
147
  ` service Generate/install OS supervisor units for unattended node serving\n` +
148
+ ` update Check whether a newer @iicp/client release is available\n` +
146
149
  ` operator rename <name> Change your public display_name (signed by your operator key)\n` +
147
150
  ` operator encrypt Password-encrypt the operator secret at rest ($IICP_OPERATOR_PASSPHRASE)\n` +
148
151
  ` operator decrypt Remove at-rest encryption of the operator secret\n\n` +
@@ -187,6 +190,58 @@ function printHelp() {
187
190
  ` --max-tokens N Limit response length\n` +
188
191
  ` --timeout-ms N Request timeout (default 60000)\n`);
189
192
  }
193
+ function argsHaveHelp(argv) {
194
+ return argv.includes("--help") || argv.includes("-h");
195
+ }
196
+ function printListHelp() {
197
+ process.stdout.write(`usage: iicp-node list\n\n` +
198
+ `List saved node configs under ~/.iicp/nodes/ without contacting the directory.\n\n` +
199
+ `Options:\n` +
200
+ ` -h, --help Show this help and exit without listing nodes\n`);
201
+ }
202
+ function printUpdateHelp() {
203
+ process.stdout.write(`usage: iicp-node update\n\n` +
204
+ `Check whether a newer @iicp/client release is available. Read-only: this command never installs or restarts anything.\n\n` +
205
+ `Exit codes:\n` +
206
+ ` 0 current, unreachable registry, or help\n` +
207
+ ` 10 newer release available\n\n` +
208
+ `Options:\n` +
209
+ ` -h, --help Show this help and exit without contacting npm\n`);
210
+ }
211
+ function printServeHelp() {
212
+ process.stdout.write(`usage: iicp-node serve [options]\n\n` +
213
+ `Register and serve an IICP provider node backed by an OpenAI-compatible backend.\n` +
214
+ `Use \`iicp-node init\` first for the lowest-friction saved-node path, then run:\n` +
215
+ ` iicp-node serve --node <NAME>\n\n` +
216
+ `Required (flag/env/saved node):\n` +
217
+ ` --model NAME IICP_BACKEND_MODEL — model name (e.g. qwen2.5:0.5b)\n` +
218
+ ` --node NAME load ~/.iicp/nodes/<NAME>.json from \`iicp-node init\`\n\n` +
219
+ `Core options:\n` +
220
+ ` --backend-url URL IICP_BACKEND_URL — Ollama / vLLM / LM Studio (default http://localhost:11434; anthropic → https://api.anthropic.com)\n` +
221
+ ` --backend-type TYPE IICP_BACKEND_TYPE — openai_compat | vllm | llamacpp | anthropic\n` +
222
+ ` --backend-api-key KEY IICP_BACKEND_API_KEY — Bearer key for auth'd backends\n` +
223
+ ` --public-endpoint URL IICP_PUBLIC_ENDPOINT — externally reachable URL of this node\n` +
224
+ ` --directory-url URL IICP_DIRECTORY_URL (default https://iicp.network/api)\n` +
225
+ ` --region REGION IICP_REGION (e.g. eu-central)\n` +
226
+ ` --intent URN IICP_INTENT (default urn:iicp:intent:llm:chat:v1)\n` +
227
+ ` --max-concurrent N IICP_MAX_CONCURRENT (default 4)\n` +
228
+ ` --node-id ID IICP_NODE_ID (auto-generated if absent)\n` +
229
+ ` --port N IICP_PORT (default 9484)\n` +
230
+ ` --host HOST IICP_HOST (default :: — dual-stack IPv4+IPv6)\n` +
231
+ ` --skip-registration IICP_SKIP_REGISTRATION — register-free dev mode\n` +
232
+ ` --force IICP_FORCE — take over the single-instance lock for this node_id\n\n` +
233
+ `Reachability and resilience:\n` +
234
+ ` --auto-detect-nat IICP_AUTO_DETECT_NAT — run NAT detection at startup (default on)\n` +
235
+ ` --no-auto-detect-nat disable NAT detection at startup\n` +
236
+ ` --external-ip-probe-url U IICP_EXTERNAL_IP_PROBE_URL — fallback IPv4 probe\n` +
237
+ ` --tunnel / --no-tunnel IICP_TUNNEL — auto Cloudflare Quick Tunnel fallback when direct reachability fails\n` +
238
+ ` --relay-worker-endpoint H IICP_RELAY_WORKER_ENDPOINT — <host>:<port> of a relay node\n` +
239
+ ` --relay-capable IICP_RELAY_CAPABLE — advertise as relay server\n` +
240
+ ` --relay-accept-port N IICP_RELAY_ACCEPT_PORT — relay accept TCP port (default 9485)\n` +
241
+ ` --log-dir DIR IICP_LOG_DIR — directory for persistent log files\n` +
242
+ ` --with-proxy IICP_WITH_PROXY — also run the loopback compat proxy (127.0.0.1:9483)\n` +
243
+ ` -h, --help Show this focused serve help\n`);
244
+ }
190
245
  /**
191
246
  * Thrown by safeParseArgs / port parsing to signal a clean, user-facing CLI error.
192
247
  * main() catches it and prints a one-line `ERROR:` (exit 2) — never a raw stack trace.
@@ -427,7 +482,18 @@ async function runInit() {
427
482
  rl.close();
428
483
  }
429
484
  }
430
- function runList() {
485
+ function runList(argv = []) {
486
+ const { values } = safeParseArgs({
487
+ args: argv,
488
+ options: {
489
+ help: { type: "boolean", short: "h" },
490
+ },
491
+ allowPositionals: false,
492
+ });
493
+ if (values.help) {
494
+ printListHelp();
495
+ return 0;
496
+ }
431
497
  const nodes = (0, identity_js_1.listNodes)();
432
498
  if (nodes.length === 0) {
433
499
  process.stdout.write(`No saved node configs. Run \`iicp-node init\` first.\n`);
@@ -1646,6 +1712,103 @@ async function fetchAndDisplayCredits(directoryUrl, nodeId, token, label, asJson
1646
1712
  }
1647
1713
  return 0;
1648
1714
  }
1715
+ function printDoctorHelp() {
1716
+ process.stdout.write(`usage: iicp-node doctor [options]\n\n` +
1717
+ `Check local health, directory presence, and deterministic recovery action.\n\n` +
1718
+ `options:\n` +
1719
+ ` --node NAME Load saved node config (~/.iicp/nodes/<NAME>.json)\n` +
1720
+ ` --directory-url URL Override the saved IICP directory base URL\n` +
1721
+ ` --json Print machine-readable recovery state\n` +
1722
+ ` -h, --help Show this help and exit\n`);
1723
+ }
1724
+ function doctorLoopbackHost(host) {
1725
+ const h = (host || "").trim();
1726
+ if (h === "" || h === "::" || h === "0.0.0.0")
1727
+ return "127.0.0.1";
1728
+ return h;
1729
+ }
1730
+ function doctorUrl(host, port) {
1731
+ const h = doctorLoopbackHost(host);
1732
+ return h.includes(":") && !h.startsWith("[")
1733
+ ? `http://[${h}]:${port}/iicp/health`
1734
+ : `http://${h}:${port}/iicp/health`;
1735
+ }
1736
+ async function runDoctor(argv) {
1737
+ const { values } = safeParseArgs({
1738
+ args: argv,
1739
+ options: {
1740
+ node: { type: "string" },
1741
+ "directory-url": { type: "string" },
1742
+ json: { type: "boolean" },
1743
+ help: { type: "boolean", short: "h" },
1744
+ },
1745
+ allowPositionals: false,
1746
+ });
1747
+ if (values.help) {
1748
+ printDoctorHelp();
1749
+ return 0;
1750
+ }
1751
+ const nodeName = values.node ?? process.env.IICP_NODE_NAME ?? "default";
1752
+ const saved = (0, identity_js_1.loadNode)(nodeName);
1753
+ if (!saved) {
1754
+ process.stderr.write(`ERROR: no saved config at ~/.iicp/nodes/${nodeName}.json — run \`iicp-node init\` first.\n`);
1755
+ return 1;
1756
+ }
1757
+ const directoryUrl = values["directory-url"] ?? saved.directory_url ?? process.env.IICP_DIRECTORY_URL ?? "https://iicp.network/api";
1758
+ const localHealthUrl = doctorUrl(saved.host ?? "0.0.0.0", saved.port ?? 8020);
1759
+ let localHealthOk = false;
1760
+ let health = null;
1761
+ let healthError = null;
1762
+ try {
1763
+ const resp = await fetch(localHealthUrl, { signal: AbortSignal.timeout(2_000) });
1764
+ if (!resp.ok)
1765
+ throw new Error(`HTTP ${resp.status}`);
1766
+ health = (await resp.json());
1767
+ localHealthOk = true;
1768
+ }
1769
+ catch (err) {
1770
+ healthError = err instanceof Error ? err.message : String(err);
1771
+ }
1772
+ const presence = await (0, recovery_js_1.registryNodePresence)(directoryUrl, saved.node_id, 5_000);
1773
+ const stability = (health?.["backend_stability"] ?? {});
1774
+ const backendAttention = stability["backend_state"] === "draining";
1775
+ const failures = !localHealthOk || presence === "absent" ? 1 : 0;
1776
+ const { state, action } = (0, recovery_js_1.classifyRecovery)({
1777
+ localHealthOk,
1778
+ publicAvailable: localHealthOk,
1779
+ directoryPresence: presence,
1780
+ consecutiveFailures: failures,
1781
+ graceChecks: (0, recovery_js_1.envGraceChecks)(),
1782
+ backendAttention,
1783
+ });
1784
+ const prefix = (0, recovery_js_1.nodeRegistryPrefix)(saved.node_id);
1785
+ if (values.json) {
1786
+ process.stdout.write(JSON.stringify({
1787
+ node: saved.name,
1788
+ node_id: saved.node_id,
1789
+ prefix,
1790
+ directory_url: directoryUrl,
1791
+ local_health_url: localHealthUrl,
1792
+ local_health_ok: localHealthOk,
1793
+ local_health_error: healthError,
1794
+ directory_presence: presence,
1795
+ recovery_state: state,
1796
+ recommended_action: action,
1797
+ health,
1798
+ }, null, 2) + "\n");
1799
+ return 0;
1800
+ }
1801
+ process.stdout.write(`IICP node doctor — ${saved.name}\n`);
1802
+ process.stdout.write(` Local health ${localHealthOk ? "ok" : "failed"} (${localHealthUrl})\n`);
1803
+ if (healthError)
1804
+ process.stdout.write(` Local health detail ${healthError}\n`);
1805
+ process.stdout.write(` Directory prefix ${prefix}\n`);
1806
+ process.stdout.write(` Directory presence ${presence}\n`);
1807
+ process.stdout.write(` Recovery state ${state}\n`);
1808
+ process.stdout.write(` Recommended action ${action}\n`);
1809
+ process.stdout.write(" Note restart is automatic only when supervised services set IICP_SUPERVISED=1\n");
1810
+ return 0;
1811
+ }
1649
1812
  /**
1650
1813
  * Resolve a passphrase: $IICP_OPERATOR_PASSPHRASE if set (headless/CI), else an interactive
1651
1814
  * readline prompt (this command is operator-run, so a prompt is fine here — only `serve` must
@@ -1738,16 +1901,46 @@ function printOperatorHelp() {
1738
1901
  ` --directory-url URL IICP directory base URL (defaults to env / iicp.network)\n` +
1739
1902
  ` -h, --help Show this help and exit\n`);
1740
1903
  }
1904
+ function printOperatorEncryptHelp() {
1905
+ process.stdout.write(`usage: iicp-node operator encrypt\n\n` +
1906
+ `Password-encrypt the operator secret at rest. The command prompts for a passphrase\n` +
1907
+ `unless $IICP_OPERATOR_PASSPHRASE is set for headless use.\n\n` +
1908
+ `Options:\n` +
1909
+ ` -h, --help Show this help and exit without prompting\n`);
1910
+ }
1911
+ function printOperatorDecryptHelp() {
1912
+ process.stdout.write(`usage: iicp-node operator decrypt\n\n` +
1913
+ `Remove at-rest encryption and store the operator secret in plaintext again. The\n` +
1914
+ `command prompts for the passphrase unless $IICP_OPERATOR_PASSPHRASE is set.\n\n` +
1915
+ `Options:\n` +
1916
+ ` -h, --help Show this help and exit without prompting\n`);
1917
+ }
1741
1918
  async function runOperator(argv) {
1742
1919
  const sub = argv[0];
1743
1920
  if (sub === undefined || sub === "--help" || sub === "-h") {
1744
1921
  printOperatorHelp();
1745
1922
  return sub === undefined ? 2 : 0;
1746
1923
  }
1747
- if (sub === "encrypt")
1924
+ if (sub === "encrypt") {
1925
+ const rest = argv.slice(1);
1926
+ if (argsHaveHelp(rest)) {
1927
+ printOperatorEncryptHelp();
1928
+ return 0;
1929
+ }
1930
+ if (rest.length > 0)
1931
+ throw new CliError(`unknown operator encrypt option '${rest[0]}'`);
1748
1932
  return runOperatorEncrypt();
1749
- if (sub === "decrypt")
1933
+ }
1934
+ if (sub === "decrypt") {
1935
+ const rest = argv.slice(1);
1936
+ if (argsHaveHelp(rest)) {
1937
+ printOperatorDecryptHelp();
1938
+ return 0;
1939
+ }
1940
+ if (rest.length > 0)
1941
+ throw new CliError(`unknown operator decrypt option '${rest[0]}'`);
1750
1942
  return runOperatorDecrypt();
1943
+ }
1751
1944
  if (sub !== "rename") {
1752
1945
  process.stderr.write(`unknown operator subcommand: ${sub}\n`);
1753
1946
  printOperatorHelp();
@@ -2073,7 +2266,18 @@ async function runMcpGateway(argv) {
2073
2266
  /** Command dispatch — separated so main() can wrap parse failures as clean CliError output. */
2074
2267
  // #521 P1 — read-only version check. Exit 10 when a newer release exists
2075
2268
  // (so cron/scripts can act), 0 when current/unreachable. Never installs.
2076
- async function runUpdate() {
2269
+ async function runUpdate(argv = []) {
2270
+ const { values } = safeParseArgs({
2271
+ args: argv,
2272
+ options: {
2273
+ help: { type: "boolean", short: "h" },
2274
+ },
2275
+ allowPositionals: false,
2276
+ });
2277
+ if (values.help) {
2278
+ printUpdateHelp();
2279
+ return 0;
2280
+ }
2077
2281
  const { checkUpdate, latestNpmVersion } = await import("./updater.js");
2078
2282
  const latest = await latestNpmVersion();
2079
2283
  const v = checkUpdate(SDK_VERSION, latest);
@@ -2150,11 +2354,13 @@ async function dispatch(argv) {
2150
2354
  if (cmd === "init")
2151
2355
  return runInit();
2152
2356
  if (cmd === "list")
2153
- return runList();
2357
+ return runList(argv.slice(1));
2154
2358
  if (cmd === "query")
2155
2359
  return runQuery(argv.slice(1));
2156
2360
  if (cmd === "credits")
2157
2361
  return runCredits(argv.slice(1));
2362
+ if (cmd === "doctor")
2363
+ return runDoctor(argv.slice(1));
2158
2364
  if (cmd === "operator")
2159
2365
  return runOperator(argv.slice(1));
2160
2366
  if (cmd === "proxy")
@@ -2162,7 +2368,7 @@ async function dispatch(argv) {
2162
2368
  if (cmd === "mcp-gateway")
2163
2369
  return runMcpGateway(argv.slice(1));
2164
2370
  if (cmd === "update")
2165
- return runUpdate();
2371
+ return runUpdate(argv.slice(1));
2166
2372
  if (cmd === "service")
2167
2373
  return runService(argv.slice(1));
2168
2374
  if (cmd !== "serve") {
@@ -2205,7 +2411,7 @@ async function dispatch(argv) {
2205
2411
  allowPositionals: false,
2206
2412
  });
2207
2413
  if (values.help) {
2208
- printHelp();
2414
+ printServeHelp();
2209
2415
  return 0;
2210
2416
  }
2211
2417
  const opts = {