@openwop/cli 1.2.0 → 1.2.1

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
@@ -98,7 +98,7 @@ What else the CLI does as a v2 client (audited against `spec/v2/core/*` at corpu
98
98
  | Discovery (`capabilities.md`) | `openwop capabilities` renders the v2 root (records by status, `minClientVersion`, `eventLogSchemaVersion`, `extensions`) from the negotiation read. |
99
99
  | Run list (`runs.md` §List) | `runs list --cursor` / `--workflow-id`; the `nextCursor` hint is printed. |
100
100
  | Stream origin (host extension; openwop-app ADR 0761) | Some front doors buffer event streams entirely — `https://app.openwop.dev/api` delivered **0 bytes of an event stream in 25 s** (measured 2026-09-27), and `runs.md` keeps `eventsUrl` on the request's origin, so the protocol cannot redirect. Every stream (`runs watch`, `chat`, `notifications stream`, `kanban watch`, `present`) is therefore read from the **stream origin**: `--stream-base-url` > `OPENWOP_STREAM_BASE_URL` > config `host.streamBaseUrl` > a host-advertised `extensions.*.streamBase` (accepted only if `https:` with no credentials/query/fragment, read from an `https:` host; loopback `http:` only for a loopback base) > `--base-url`. Non-stream requests always use `--base-url`. To keep streams on `--base-url` too (e.g. an egress proxy), set `--stream-base-url` to the same URL; `openwop doctor`'s **stream origin** row shows which origin is in use and why. |
101
- | Stalled streams | A run stream that delivers **no bytes** (keep-alives included) for `--idle-timeout-ms` (default 45000; 0 disables) is aborted and resumed with `Last-Event-ID` — a half-open connection no longer hangs the command. `scripts/live-sse-resume.mjs` checks resume against a real run (`--mode drop|stall`). |
101
+ | Stalled streams | A run stream that delivers **no bytes** (keep-alives included) for `--idle-timeout-ms` (default 45000; 0 disables) is aborted and resumed with `Last-Event-ID` — a half-open connection no longer hangs the command. A stream whose response headers do not arrive within 10 s (a front door that buffers streams) is followed by polling instead, with one stderr line naming `--stream-base-url`; `--verbose` says whether each reconnect was a drop or a stall. `scripts/live-sse-resume.mjs` checks resume against a real run (`--mode drop|stall`). |
102
102
 
103
103
  When the host's `minClientVersion` is above the CLI, every command prints one warning (from the discovery read it already made); refusing is left to the host (`426`). Not exposed: the `bufferMs` batching hint (batched frames from a host that batches anyway are consumed).
104
104
 
package/dist/cli.js CHANGED
@@ -578,7 +578,7 @@ function errText(err) {
578
578
  // package.json
579
579
  var package_default = {
580
580
  name: "@openwop/cli",
581
- version: "1.2.0",
581
+ version: "1.2.1",
582
582
  description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
583
583
  license: "Apache-2.0",
584
584
  type: "module",
@@ -1236,11 +1236,15 @@ function isLoopback(url) {
1236
1236
  }
1237
1237
  function explicitStreamBase(raw) {
1238
1238
  if (typeof raw !== "string" || raw.trim() === "") return void 0;
1239
+ let url;
1239
1240
  try {
1240
- new URL(raw.trim());
1241
+ url = new URL(raw.trim());
1241
1242
  } catch {
1242
1243
  throw new CliError(`--stream-base-url / OPENWOP_STREAM_BASE_URL is not a URL: ${raw}`);
1243
1244
  }
1245
+ if (url.username !== "" || url.password !== "") {
1246
+ throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
1247
+ }
1244
1248
  return raw.trim().replace(/\/+$/, "");
1245
1249
  }
1246
1250
  function streamBaseFrom(extensions, baseUrl) {
@@ -1481,6 +1485,10 @@ async function streamRunEvents(ctx, runId, opts = {}) {
1481
1485
  } catch (err) {
1482
1486
  if (err instanceof HttpError && err.status >= 400 && err.status < 500 && (opts.streamMode || opts.lastEventId !== void 0 || opts.afterSequence !== void 0)) throw err;
1483
1487
  if (err instanceof SseResumeExhausted) throw err.reason instanceof HttpError ? err.reason : err;
1488
+ if (err instanceof StreamSilent && err.beforeHeaders && !ctx.quiet) {
1489
+ ctx.io.stderr.write(`openwop: the event stream sent nothing for ${err.ms >= 1e3 ? `${Math.round(err.ms / 1e3)} s` : `${err.ms} ms`} \u2014 the host's front door may be buffering streams. Following by polling instead; for live events pass --stream-base-url <a direct origin> (see \`openwop doctor\`).
1490
+ `);
1491
+ }
1484
1492
  }
1485
1493
  }
1486
1494
  if (opts.streamMode && opts.streamMode !== "updates" && !ctx.quiet) {
@@ -1530,8 +1538,10 @@ async function streamViaSse(ctx, runId, deliver, opts) {
1530
1538
  try {
1531
1539
  const { url, headers } = await resolveStreamRequest(ctx, `/v1/runs/${encodeURIComponent(runId)}/events${query}`, {});
1532
1540
  if (lastEventId !== void 0 && lastEventId !== "") headers["last-event-id"] = lastEventId;
1533
- idle = idleWatchdog(opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
1541
+ const idleMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
1542
+ idle = idleWatchdog(idleMs, opts.headersTimeoutMs ?? (idleMs > 0 ? Math.min(idleMs, DEFAULT_HEADERS_TIMEOUT_MS) : 0));
1534
1543
  const res = await ctx.fetchImpl(url, { method: "GET", headers, signal: idle.signal });
1544
+ idle.headersArrived();
1535
1545
  if (!res.ok) {
1536
1546
  const text = await res.text().catch(() => "");
1537
1547
  let body = null;
@@ -1575,12 +1585,15 @@ async function streamViaSse(ctx, runId, deliver, opts) {
1575
1585
  if (await runIsTerminal(ctx, runId)) return true;
1576
1586
  failures = progressed ? 1 : failures + 1;
1577
1587
  if (failures > maxReconnects) {
1578
- throw new SseResumeExhausted(`events stream for ${runId} dropped ${failures} times without progress${dropError instanceof Error ? `: ${dropError.message}` : ""}`, dropError);
1588
+ throw new SseResumeExhausted(`events stream for ${runId} dropped ${failures} times without progress${dropError instanceof Error ? `: ${dropError.message}` : ""}. Re-run with --since <last sequence printed> to continue, or --no-stream to follow by polling.`, dropError);
1579
1589
  }
1580
1590
  const delayMs = Math.min(retryMs * 2 ** (failures - 1), 3e4);
1581
1591
  opts.onReconnect?.({ attempt: failures, lastEventId, delayMs });
1582
- if (ctx.verbose) ctx.io.stderr.write(`openwop: events stream dropped; reconnecting in ${delayMs}ms with Last-Event-ID ${lastEventId ?? "(none)"}
1592
+ if (ctx.verbose) {
1593
+ const why = dropError instanceof StreamSilent ? `stalled (${dropError.message})` : "dropped";
1594
+ ctx.io.stderr.write(`openwop: events stream ${why}; reconnecting in ${delayMs}ms with Last-Event-ID ${lastEventId ?? "(none)"}
1583
1595
  `);
1596
+ }
1584
1597
  await sleep(delayMs);
1585
1598
  }
1586
1599
  }
@@ -1779,29 +1792,44 @@ function defaultReadTurn(ctx) {
1779
1792
  });
1780
1793
  }
1781
1794
  var DEFAULT_IDLE_TIMEOUT_MS = 45e3;
1782
- function idleWatchdog(ms2) {
1795
+ var DEFAULT_HEADERS_TIMEOUT_MS = 1e4;
1796
+ var StreamSilent = class extends Error {
1797
+ constructor(ms2, beforeHeaders) {
1798
+ super(beforeHeaders ? `no response headers within ${ms2}ms` : `no bytes for ${ms2}ms`);
1799
+ this.ms = ms2;
1800
+ this.beforeHeaders = beforeHeaders;
1801
+ this.name = "StreamSilent";
1802
+ }
1803
+ };
1804
+ function idleWatchdog(ms2, headersMs = ms2) {
1783
1805
  const controller = new AbortController();
1784
1806
  let timer;
1785
1807
  let onExpire;
1786
- const expire = () => {
1787
- controller.abort(new Error(`no bytes for ${ms2}ms`));
1808
+ let beforeHeaders = true;
1809
+ const expire = (after) => {
1810
+ controller.abort(new StreamSilent(after, beforeHeaders));
1788
1811
  onExpire?.();
1789
1812
  };
1790
- const arm = () => {
1791
- if (!(ms2 > 0)) return;
1813
+ const arm = (after = ms2) => {
1814
+ if (!(after > 0)) return;
1792
1815
  if (timer !== void 0) clearTimeout(timer);
1793
- timer = setTimeout(expire, ms2);
1816
+ timer = setTimeout(() => expire(after), after);
1794
1817
  };
1795
- arm();
1818
+ arm(headersMs);
1796
1819
  return {
1797
1820
  signal: controller.signal,
1821
+ /** The response headers arrived: from here on only the idle timeout applies. */
1822
+ headersArrived() {
1823
+ beforeHeaders = false;
1824
+ arm();
1825
+ },
1798
1826
  watch(body) {
1799
1827
  if (!(ms2 > 0) || typeof body?.getReader !== "function") return body;
1800
1828
  const source = body.getReader();
1801
1829
  return new ReadableStream({
1802
1830
  start(out) {
1803
1831
  onExpire = () => {
1804
- out.error(new Error(`events stream idle for ${ms2}ms`));
1832
+ out.error(new StreamSilent(ms2, false));
1805
1833
  source.cancel().catch(() => {
1806
1834
  });
1807
1835
  };
@@ -2650,7 +2678,8 @@ async function runInterrupts(ctx, argv) {
2650
2678
  if (b.key) writeLine(ctx.io.stdout, `key: ${b.key}`);
2651
2679
  writeLine(ctx.io.stdout, `resolved: ${b.resolved ? "yes" : "no"}`);
2652
2680
  if (b.expiresAt) writeLine(ctx.io.stdout, `expiresAt: ${b.expiresAt}`);
2653
- if (b.data && typeof b.data === "object" && typeof b.data.prompt === "string") writeLine(ctx.io.stdout, `prompt: ${b.data.prompt}`);
2681
+ const prompt = b.data && typeof b.data === "object" ? b.data.prompt : void 0;
2682
+ if (typeof prompt === "string") writeLine(ctx.io.stdout, `prompt: ${prompt}`);
2654
2683
  if (b.resumeSchema !== void 0) writeLine(ctx.io.stdout, `resumeSchema: ${JSON.stringify(b.resumeSchema)}`);
2655
2684
  return 0;
2656
2685
  }
@@ -12417,7 +12446,9 @@ honouring the server's \`retry:\` field), and no event is printed twice.
12417
12446
  (\`values\` never combines). A host that does not serve
12418
12447
  the mode answers 400 unsupported_stream_mode.
12419
12448
  --idle-timeout-ms N a connection that delivers no bytes for N ms (default
12420
- 45000; 0 disables) is treated as dropped and resumed.
12449
+ 45000; 0 disables) is treated as dropped and resumed. A
12450
+ stream whose headers do not arrive within 10 s is given
12451
+ up on and followed by polling, with a hint on stderr.
12421
12452
  Streams are read from the stream origin: --stream-base-url (global), else the
12422
12453
  host's advertised \`streamBase\` (https only), else --base-url \u2014 some front
12423
12454
  doors buffer event streams entirely (e.g. a CDN rewrite).
@@ -17404,6 +17435,24 @@ async function runPromptsGet(ctx, argv) {
17404
17435
  return 0;
17405
17436
  }
17406
17437
 
17438
+ // src/redact.ts
17439
+ var BASE_SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
17440
+ function redactSecrets(value, rules = {}) {
17441
+ const secret = rules.secret ?? BASE_SECRET_KEY;
17442
+ const walk = (v) => {
17443
+ if (Array.isArray(v)) return v.map(walk);
17444
+ if (v && typeof v === "object") {
17445
+ const out = {};
17446
+ for (const [k, inner] of Object.entries(v)) {
17447
+ out[k] = secret.test(k) && !(rules.keep?.test(k) ?? false) ? "[redacted]" : walk(inner);
17448
+ }
17449
+ return out;
17450
+ }
17451
+ return v;
17452
+ };
17453
+ return walk(value);
17454
+ }
17455
+
17407
17456
  // src/cli/connections.ts
17408
17457
  var CONN = "/v1/host/openwop-app/connections/:id";
17409
17458
  var CONNECTIONS_ROUTES = [
@@ -17479,15 +17528,7 @@ Examples:
17479
17528
  `;
17480
17529
  var SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
17481
17530
  function redact(value) {
17482
- if (Array.isArray(value)) return value.map(redact);
17483
- if (value && typeof value === "object") {
17484
- const out = {};
17485
- for (const [k, v] of Object.entries(value)) {
17486
- out[k] = SECRET_KEY.test(k) ? "[redacted]" : redact(v);
17487
- }
17488
- return out;
17489
- }
17490
- return value;
17531
+ return redactSecrets(value, { secret: SECRET_KEY });
17491
17532
  }
17492
17533
  var TOP = ["list", "get", "test", "authorize", "oauth-clients", "oauth-client"];
17493
17534
  async function runConnections(ctx, argv) {
@@ -17691,7 +17732,8 @@ Run \`openwop connections --help\` for usage.`);
17691
17732
  } catch (err) {
17692
17733
  gate4042(err);
17693
17734
  }
17694
- const clients = (Array.isArray(res.body?.clients) ? res.body.clients : []).map(redact);
17735
+ const rawClients = Array.isArray(res.body?.clients) ? res.body.clients : [];
17736
+ const clients = rawClients.map(redact);
17695
17737
  if (action === "get") {
17696
17738
  if (positionals.length !== 1) {
17697
17739
  write(ctx.io.stdout, "Usage: openwop connections oauth-clients get <provider> [--json]\n");
@@ -19207,15 +19249,7 @@ Examples:
19207
19249
  var BASE11 = "/v1/host/openwop-app/auth";
19208
19250
  var SECRET_KEY2 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential|bearer|assertion|certificate|cert|pem/i;
19209
19251
  function redact2(value) {
19210
- if (Array.isArray(value)) return value.map(redact2);
19211
- if (value && typeof value === "object") {
19212
- const out = {};
19213
- for (const [k, v] of Object.entries(value)) {
19214
- out[k] = SECRET_KEY2.test(k) ? "[redacted]" : redact2(v);
19215
- }
19216
- return out;
19217
- }
19218
- return value;
19252
+ return redactSecrets(value, { secret: SECRET_KEY2 });
19219
19253
  }
19220
19254
  async function authRequest(ctx, path, options, notConfiguredMsg) {
19221
19255
  try {
@@ -20327,15 +20361,7 @@ Examples:
20327
20361
  var SECRET_KEY3 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
20328
20362
  var REF_ONLY_KEY = /^secretsToRebind$/;
20329
20363
  function redact3(value) {
20330
- if (Array.isArray(value)) return value.map(redact3);
20331
- if (value && typeof value === "object") {
20332
- const out = {};
20333
- for (const [k, v] of Object.entries(value)) {
20334
- out[k] = SECRET_KEY3.test(k) && !REF_ONLY_KEY.test(k) ? "[redacted]" : redact3(v);
20335
- }
20336
- return out;
20337
- }
20338
- return value;
20364
+ return redactSecrets(value, { secret: SECRET_KEY3, keep: REF_ONLY_KEY });
20339
20365
  }
20340
20366
  async function ensureAdvertised5(ctx) {
20341
20367
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
@@ -21991,7 +22017,7 @@ async function startJob(ctx, path, body) {
21991
22017
  const res = await requestJson(ctx, path, { method: "POST", body });
21992
22018
  return renderDone(ctx, res.body, `Video job ${res.body?.jobId ?? ""} ${res.body?.status ?? "started"}.`);
21993
22019
  } catch (err) {
21994
- const b = err instanceof HttpError ? err.body : void 0;
22020
+ const b = err instanceof HttpError && err.body && typeof err.body === "object" ? err.body : void 0;
21995
22021
  if (err instanceof HttpError && b && b.status === "failed") {
21996
22022
  if (ctx.json) writeJson(ctx.io.stdout, b);
21997
22023
  else writeLine(ctx.io.stderr, `openwop: video generation failed (HTTP ${err.status}): ${b.error ?? "unknown error"}${b.error === "no_connection" ? " \u2014 connect the video provider under Connections first" : ""}`);
@@ -30443,6 +30469,7 @@ Examples:
30443
30469
  openwop packs install community.openwop-team.demo --version 0.1.0
30444
30470
  `;
30445
30471
  export {
30472
+ BASE_SECRET_KEY,
30446
30473
  CLI_PROTOCOL_VERSION_BY_MAJOR,
30447
30474
  COMMAND_NAMES,
30448
30475
  DEFAULT_BASE_URL,
@@ -30488,6 +30515,7 @@ export {
30488
30515
  projectTenantBoundId,
30489
30516
  readConfigSafe,
30490
30517
  readDaemonRecord,
30518
+ redactSecrets,
30491
30519
  renderEvent,
30492
30520
  requestNormativeOrHost,
30493
30521
  resolveStreamRequest,
package/dist/openwop.js CHANGED
@@ -579,7 +579,7 @@ function errText(err) {
579
579
  // package.json
580
580
  var package_default = {
581
581
  name: "@openwop/cli",
582
- version: "1.2.0",
582
+ version: "1.2.1",
583
583
  description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
584
584
  license: "Apache-2.0",
585
585
  type: "module",
@@ -1236,11 +1236,15 @@ function isLoopback(url) {
1236
1236
  }
1237
1237
  function explicitStreamBase(raw) {
1238
1238
  if (typeof raw !== "string" || raw.trim() === "") return void 0;
1239
+ let url;
1239
1240
  try {
1240
- new URL(raw.trim());
1241
+ url = new URL(raw.trim());
1241
1242
  } catch {
1242
1243
  throw new CliError(`--stream-base-url / OPENWOP_STREAM_BASE_URL is not a URL: ${raw}`);
1243
1244
  }
1245
+ if (url.username !== "" || url.password !== "") {
1246
+ throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
1247
+ }
1244
1248
  return raw.trim().replace(/\/+$/, "");
1245
1249
  }
1246
1250
  function streamBaseFrom(extensions, baseUrl) {
@@ -1481,6 +1485,10 @@ async function streamRunEvents(ctx, runId, opts = {}) {
1481
1485
  } catch (err) {
1482
1486
  if (err instanceof HttpError && err.status >= 400 && err.status < 500 && (opts.streamMode || opts.lastEventId !== void 0 || opts.afterSequence !== void 0)) throw err;
1483
1487
  if (err instanceof SseResumeExhausted) throw err.reason instanceof HttpError ? err.reason : err;
1488
+ if (err instanceof StreamSilent && err.beforeHeaders && !ctx.quiet) {
1489
+ ctx.io.stderr.write(`openwop: the event stream sent nothing for ${err.ms >= 1e3 ? `${Math.round(err.ms / 1e3)} s` : `${err.ms} ms`} \u2014 the host's front door may be buffering streams. Following by polling instead; for live events pass --stream-base-url <a direct origin> (see \`openwop doctor\`).
1490
+ `);
1491
+ }
1484
1492
  }
1485
1493
  }
1486
1494
  if (opts.streamMode && opts.streamMode !== "updates" && !ctx.quiet) {
@@ -1530,8 +1538,10 @@ async function streamViaSse(ctx, runId, deliver, opts) {
1530
1538
  try {
1531
1539
  const { url, headers } = await resolveStreamRequest(ctx, `/v1/runs/${encodeURIComponent(runId)}/events${query}`, {});
1532
1540
  if (lastEventId !== void 0 && lastEventId !== "") headers["last-event-id"] = lastEventId;
1533
- idle = idleWatchdog(opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
1541
+ const idleMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
1542
+ idle = idleWatchdog(idleMs, opts.headersTimeoutMs ?? (idleMs > 0 ? Math.min(idleMs, DEFAULT_HEADERS_TIMEOUT_MS) : 0));
1534
1543
  const res = await ctx.fetchImpl(url, { method: "GET", headers, signal: idle.signal });
1544
+ idle.headersArrived();
1535
1545
  if (!res.ok) {
1536
1546
  const text = await res.text().catch(() => "");
1537
1547
  let body = null;
@@ -1575,12 +1585,15 @@ async function streamViaSse(ctx, runId, deliver, opts) {
1575
1585
  if (await runIsTerminal(ctx, runId)) return true;
1576
1586
  failures = progressed ? 1 : failures + 1;
1577
1587
  if (failures > maxReconnects) {
1578
- throw new SseResumeExhausted(`events stream for ${runId} dropped ${failures} times without progress${dropError instanceof Error ? `: ${dropError.message}` : ""}`, dropError);
1588
+ throw new SseResumeExhausted(`events stream for ${runId} dropped ${failures} times without progress${dropError instanceof Error ? `: ${dropError.message}` : ""}. Re-run with --since <last sequence printed> to continue, or --no-stream to follow by polling.`, dropError);
1579
1589
  }
1580
1590
  const delayMs = Math.min(retryMs * 2 ** (failures - 1), 3e4);
1581
1591
  opts.onReconnect?.({ attempt: failures, lastEventId, delayMs });
1582
- if (ctx.verbose) ctx.io.stderr.write(`openwop: events stream dropped; reconnecting in ${delayMs}ms with Last-Event-ID ${lastEventId ?? "(none)"}
1592
+ if (ctx.verbose) {
1593
+ const why = dropError instanceof StreamSilent ? `stalled (${dropError.message})` : "dropped";
1594
+ ctx.io.stderr.write(`openwop: events stream ${why}; reconnecting in ${delayMs}ms with Last-Event-ID ${lastEventId ?? "(none)"}
1583
1595
  `);
1596
+ }
1584
1597
  await sleep(delayMs);
1585
1598
  }
1586
1599
  }
@@ -1779,29 +1792,44 @@ function defaultReadTurn(ctx) {
1779
1792
  });
1780
1793
  }
1781
1794
  var DEFAULT_IDLE_TIMEOUT_MS = 45e3;
1782
- function idleWatchdog(ms2) {
1795
+ var DEFAULT_HEADERS_TIMEOUT_MS = 1e4;
1796
+ var StreamSilent = class extends Error {
1797
+ constructor(ms2, beforeHeaders) {
1798
+ super(beforeHeaders ? `no response headers within ${ms2}ms` : `no bytes for ${ms2}ms`);
1799
+ this.ms = ms2;
1800
+ this.beforeHeaders = beforeHeaders;
1801
+ this.name = "StreamSilent";
1802
+ }
1803
+ };
1804
+ function idleWatchdog(ms2, headersMs = ms2) {
1783
1805
  const controller = new AbortController();
1784
1806
  let timer;
1785
1807
  let onExpire;
1786
- const expire = () => {
1787
- controller.abort(new Error(`no bytes for ${ms2}ms`));
1808
+ let beforeHeaders = true;
1809
+ const expire = (after) => {
1810
+ controller.abort(new StreamSilent(after, beforeHeaders));
1788
1811
  onExpire?.();
1789
1812
  };
1790
- const arm = () => {
1791
- if (!(ms2 > 0)) return;
1813
+ const arm = (after = ms2) => {
1814
+ if (!(after > 0)) return;
1792
1815
  if (timer !== void 0) clearTimeout(timer);
1793
- timer = setTimeout(expire, ms2);
1816
+ timer = setTimeout(() => expire(after), after);
1794
1817
  };
1795
- arm();
1818
+ arm(headersMs);
1796
1819
  return {
1797
1820
  signal: controller.signal,
1821
+ /** The response headers arrived: from here on only the idle timeout applies. */
1822
+ headersArrived() {
1823
+ beforeHeaders = false;
1824
+ arm();
1825
+ },
1798
1826
  watch(body) {
1799
1827
  if (!(ms2 > 0) || typeof body?.getReader !== "function") return body;
1800
1828
  const source = body.getReader();
1801
1829
  return new ReadableStream({
1802
1830
  start(out) {
1803
1831
  onExpire = () => {
1804
- out.error(new Error(`events stream idle for ${ms2}ms`));
1832
+ out.error(new StreamSilent(ms2, false));
1805
1833
  source.cancel().catch(() => {
1806
1834
  });
1807
1835
  };
@@ -2650,7 +2678,8 @@ async function runInterrupts(ctx, argv) {
2650
2678
  if (b.key) writeLine(ctx.io.stdout, `key: ${b.key}`);
2651
2679
  writeLine(ctx.io.stdout, `resolved: ${b.resolved ? "yes" : "no"}`);
2652
2680
  if (b.expiresAt) writeLine(ctx.io.stdout, `expiresAt: ${b.expiresAt}`);
2653
- if (b.data && typeof b.data === "object" && typeof b.data.prompt === "string") writeLine(ctx.io.stdout, `prompt: ${b.data.prompt}`);
2681
+ const prompt = b.data && typeof b.data === "object" ? b.data.prompt : void 0;
2682
+ if (typeof prompt === "string") writeLine(ctx.io.stdout, `prompt: ${prompt}`);
2654
2683
  if (b.resumeSchema !== void 0) writeLine(ctx.io.stdout, `resumeSchema: ${JSON.stringify(b.resumeSchema)}`);
2655
2684
  return 0;
2656
2685
  }
@@ -12417,7 +12446,9 @@ honouring the server's \`retry:\` field), and no event is printed twice.
12417
12446
  (\`values\` never combines). A host that does not serve
12418
12447
  the mode answers 400 unsupported_stream_mode.
12419
12448
  --idle-timeout-ms N a connection that delivers no bytes for N ms (default
12420
- 45000; 0 disables) is treated as dropped and resumed.
12449
+ 45000; 0 disables) is treated as dropped and resumed. A
12450
+ stream whose headers do not arrive within 10 s is given
12451
+ up on and followed by polling, with a hint on stderr.
12421
12452
  Streams are read from the stream origin: --stream-base-url (global), else the
12422
12453
  host's advertised \`streamBase\` (https only), else --base-url \u2014 some front
12423
12454
  doors buffer event streams entirely (e.g. a CDN rewrite).
@@ -17404,6 +17435,24 @@ async function runPromptsGet(ctx, argv) {
17404
17435
  return 0;
17405
17436
  }
17406
17437
 
17438
+ // src/redact.ts
17439
+ var BASE_SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
17440
+ function redactSecrets(value, rules = {}) {
17441
+ const secret = rules.secret ?? BASE_SECRET_KEY;
17442
+ const walk = (v) => {
17443
+ if (Array.isArray(v)) return v.map(walk);
17444
+ if (v && typeof v === "object") {
17445
+ const out = {};
17446
+ for (const [k, inner] of Object.entries(v)) {
17447
+ out[k] = secret.test(k) && !(rules.keep?.test(k) ?? false) ? "[redacted]" : walk(inner);
17448
+ }
17449
+ return out;
17450
+ }
17451
+ return v;
17452
+ };
17453
+ return walk(value);
17454
+ }
17455
+
17407
17456
  // src/cli/connections.ts
17408
17457
  var CONN = "/v1/host/openwop-app/connections/:id";
17409
17458
  var CONNECTIONS_ROUTES = [
@@ -17479,15 +17528,7 @@ Examples:
17479
17528
  `;
17480
17529
  var SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
17481
17530
  function redact(value) {
17482
- if (Array.isArray(value)) return value.map(redact);
17483
- if (value && typeof value === "object") {
17484
- const out = {};
17485
- for (const [k, v] of Object.entries(value)) {
17486
- out[k] = SECRET_KEY.test(k) ? "[redacted]" : redact(v);
17487
- }
17488
- return out;
17489
- }
17490
- return value;
17531
+ return redactSecrets(value, { secret: SECRET_KEY });
17491
17532
  }
17492
17533
  var TOP = ["list", "get", "test", "authorize", "oauth-clients", "oauth-client"];
17493
17534
  async function runConnections(ctx, argv) {
@@ -17691,7 +17732,8 @@ Run \`openwop connections --help\` for usage.`);
17691
17732
  } catch (err) {
17692
17733
  gate4042(err);
17693
17734
  }
17694
- const clients = (Array.isArray(res.body?.clients) ? res.body.clients : []).map(redact);
17735
+ const rawClients = Array.isArray(res.body?.clients) ? res.body.clients : [];
17736
+ const clients = rawClients.map(redact);
17695
17737
  if (action === "get") {
17696
17738
  if (positionals.length !== 1) {
17697
17739
  write(ctx.io.stdout, "Usage: openwop connections oauth-clients get <provider> [--json]\n");
@@ -19207,15 +19249,7 @@ Examples:
19207
19249
  var BASE11 = "/v1/host/openwop-app/auth";
19208
19250
  var SECRET_KEY2 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential|bearer|assertion|certificate|cert|pem/i;
19209
19251
  function redact2(value) {
19210
- if (Array.isArray(value)) return value.map(redact2);
19211
- if (value && typeof value === "object") {
19212
- const out = {};
19213
- for (const [k, v] of Object.entries(value)) {
19214
- out[k] = SECRET_KEY2.test(k) ? "[redacted]" : redact2(v);
19215
- }
19216
- return out;
19217
- }
19218
- return value;
19252
+ return redactSecrets(value, { secret: SECRET_KEY2 });
19219
19253
  }
19220
19254
  async function authRequest(ctx, path, options, notConfiguredMsg) {
19221
19255
  try {
@@ -20327,15 +20361,7 @@ Examples:
20327
20361
  var SECRET_KEY3 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
20328
20362
  var REF_ONLY_KEY = /^secretsToRebind$/;
20329
20363
  function redact3(value) {
20330
- if (Array.isArray(value)) return value.map(redact3);
20331
- if (value && typeof value === "object") {
20332
- const out = {};
20333
- for (const [k, v] of Object.entries(value)) {
20334
- out[k] = SECRET_KEY3.test(k) && !REF_ONLY_KEY.test(k) ? "[redacted]" : redact3(v);
20335
- }
20336
- return out;
20337
- }
20338
- return value;
20364
+ return redactSecrets(value, { secret: SECRET_KEY3, keep: REF_ONLY_KEY });
20339
20365
  }
20340
20366
  async function ensureAdvertised5(ctx) {
20341
20367
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
@@ -21991,7 +22017,7 @@ async function startJob(ctx, path, body) {
21991
22017
  const res = await requestJson(ctx, path, { method: "POST", body });
21992
22018
  return renderDone(ctx, res.body, `Video job ${res.body?.jobId ?? ""} ${res.body?.status ?? "started"}.`);
21993
22019
  } catch (err) {
21994
- const b = err instanceof HttpError ? err.body : void 0;
22020
+ const b = err instanceof HttpError && err.body && typeof err.body === "object" ? err.body : void 0;
21995
22021
  if (err instanceof HttpError && b && b.status === "failed") {
21996
22022
  if (ctx.json) writeJson(ctx.io.stdout, b);
21997
22023
  else writeLine(ctx.io.stderr, `openwop: video generation failed (HTTP ${err.status}): ${b.error ?? "unknown error"}${b.error === "no_connection" ? " \u2014 connect the video provider under Connections first" : ""}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",