@openwop/cli 1.1.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/dist/cli.js CHANGED
@@ -565,6 +565,12 @@ function httpErrorHint(err, env) {
565
565
  }
566
566
  return void 0;
567
567
  }
568
+ function isNoCredentialChallenge(err) {
569
+ if (!(err instanceof HttpError) || err.status !== 401) return false;
570
+ const challenge = err.headers?.get?.("www-authenticate");
571
+ if (!challenge || !/^\s*Bearer\b/i.test(challenge)) return false;
572
+ return !/\berror\s*=/i.test(challenge);
573
+ }
568
574
  function errText(err) {
569
575
  return err instanceof Error ? err.message : String(err);
570
576
  }
@@ -572,7 +578,7 @@ function errText(err) {
572
578
  // package.json
573
579
  var package_default = {
574
580
  name: "@openwop/cli",
575
- version: "1.1.0",
581
+ version: "1.2.1",
576
582
  description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
577
583
  license: "Apache-2.0",
578
584
  type: "module",
@@ -703,6 +709,7 @@ function prefixChunk(stream, label, chunk) {
703
709
  function extractGlobalOptions(argv, env = process.env) {
704
710
  const globals = {
705
711
  baseUrl: void 0,
712
+ streamBaseUrl: void 0,
706
713
  apiKey: void 0,
707
714
  profile: void 0,
708
715
  json: false,
@@ -720,6 +727,10 @@ function extractGlobalOptions(argv, env = process.env) {
720
727
  globals.baseUrl = value ?? takeValue(argv, ++i, "--base-url");
721
728
  continue;
722
729
  }
730
+ if (flag === "--stream-base-url") {
731
+ globals.streamBaseUrl = value ?? takeValue(argv, ++i, "--stream-base-url");
732
+ continue;
733
+ }
723
734
  if (flag === "--api-key") {
724
735
  globals.apiKey = value ?? takeValue(argv, ++i, "--api-key");
725
736
  continue;
@@ -1199,6 +1210,7 @@ async function negotiateMajor(ctx) {
1199
1210
  const doc = JSON.parse(await res.text());
1200
1211
  if (res.ok) {
1201
1212
  ctx.discovery = { doc, servedVersion: res.headers?.get?.("openwop-version") ?? void 0 };
1213
+ ctx.discoveredStreamBase = streamBaseFrom(doc?.extensions, ctx.baseUrl);
1202
1214
  major = selectMajor(doc?.protocolVersions);
1203
1215
  if (major === 2) roots = hostRootsFrom(doc?.extensions);
1204
1216
  } else if (res.status === 406) {
@@ -1219,6 +1231,67 @@ async function resolveRequest(ctx, path, headers) {
1219
1231
  if (rooted !== null) return { path: rooted, headers };
1220
1232
  return { path, headers };
1221
1233
  }
1234
+ function isLoopback(url) {
1235
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1";
1236
+ }
1237
+ function explicitStreamBase(raw) {
1238
+ if (typeof raw !== "string" || raw.trim() === "") return void 0;
1239
+ let url;
1240
+ try {
1241
+ url = new URL(raw.trim());
1242
+ } catch {
1243
+ throw new CliError(`--stream-base-url / OPENWOP_STREAM_BASE_URL is not a URL: ${raw}`);
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
+ }
1248
+ return raw.trim().replace(/\/+$/, "");
1249
+ }
1250
+ function streamBaseFrom(extensions, baseUrl) {
1251
+ if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) return void 0;
1252
+ let base16;
1253
+ try {
1254
+ base16 = new URL(baseUrl);
1255
+ } catch {
1256
+ return void 0;
1257
+ }
1258
+ for (const value of Object.values(extensions)) {
1259
+ const raw = value && typeof value === "object" ? value.streamBase : void 0;
1260
+ if (typeof raw !== "string") continue;
1261
+ let url;
1262
+ try {
1263
+ url = new URL(raw);
1264
+ } catch {
1265
+ continue;
1266
+ }
1267
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") continue;
1268
+ const secure = url.protocol === "https:" && (base16.protocol === "https:" || isLoopback(base16));
1269
+ const localDev = url.protocol === "http:" && isLoopback(url) && isLoopback(base16);
1270
+ if (!secure && !localDev) continue;
1271
+ return raw.replace(/\/+$/, "");
1272
+ }
1273
+ return void 0;
1274
+ }
1275
+ async function resolveStreamRequest(ctx, requestedPath, headers) {
1276
+ const resolved = await resolveRequest(ctx, requestedPath, { accept: "text/event-stream", ...headers });
1277
+ const { origin, source } = streamOrigin(ctx);
1278
+ if (ctx.verbose && source !== "base") ctx.io.stderr.write(`openwop: reading the event stream from ${origin} (${STREAM_SOURCE_LABEL[source]})
1279
+ `);
1280
+ const url = new URL(resolved.path.replace(/^\//, ""), origin.endsWith("/") ? origin : `${origin}/`);
1281
+ const out = { ...resolved.headers };
1282
+ if (ctx.apiKey) out.authorization = `Bearer ${ctx.apiKey}`;
1283
+ return { url, headers: out };
1284
+ }
1285
+ var STREAM_SOURCE_LABEL = {
1286
+ setting: "your --stream-base-url / OPENWOP_STREAM_BASE_URL / config setting",
1287
+ advertised: "the host's advertised streamBase",
1288
+ base: "--base-url"
1289
+ };
1290
+ function streamOrigin(ctx) {
1291
+ if (ctx.streamBaseUrl) return { origin: ctx.streamBaseUrl, source: "setting" };
1292
+ if (ctx.discoveredStreamBase) return { origin: ctx.discoveredStreamBase, source: "advertised" };
1293
+ return { origin: ctx.baseUrl, source: "base" };
1294
+ }
1222
1295
 
1223
1296
  // src/wire.ts
1224
1297
  import { randomUUID } from "node:crypto";
@@ -1412,6 +1485,10 @@ async function streamRunEvents(ctx, runId, opts = {}) {
1412
1485
  } catch (err) {
1413
1486
  if (err instanceof HttpError && err.status >= 400 && err.status < 500 && (opts.streamMode || opts.lastEventId !== void 0 || opts.afterSequence !== void 0)) throw err;
1414
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
+ }
1415
1492
  }
1416
1493
  }
1417
1494
  if (opts.streamMode && opts.streamMode !== "updates" && !ctx.quiet) {
@@ -1457,12 +1534,14 @@ async function streamViaSse(ctx, runId, deliver, opts) {
1457
1534
  for (; ; ) {
1458
1535
  let progressed = false;
1459
1536
  let dropError;
1537
+ let idle;
1460
1538
  try {
1461
- const { path, headers } = await resolveRequest(ctx, `/v1/runs/${encodeURIComponent(runId)}/events${query}`, { accept: "text/event-stream" });
1462
- const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
1463
- if (ctx.apiKey) headers.authorization = `Bearer ${ctx.apiKey}`;
1539
+ const { url, headers } = await resolveStreamRequest(ctx, `/v1/runs/${encodeURIComponent(runId)}/events${query}`, {});
1464
1540
  if (lastEventId !== void 0 && lastEventId !== "") headers["last-event-id"] = lastEventId;
1465
- const res = await ctx.fetchImpl(url, { method: "GET", headers });
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));
1543
+ const res = await ctx.fetchImpl(url, { method: "GET", headers, signal: idle.signal });
1544
+ idle.headersArrived();
1466
1545
  if (!res.ok) {
1467
1546
  const text = await res.text().catch(() => "");
1468
1547
  let body = null;
@@ -1479,7 +1558,7 @@ async function streamViaSse(ctx, runId, deliver, opts) {
1479
1558
  throw new HttpError("resumed events stream is not text/event-stream", res.status, null, res.headers);
1480
1559
  }
1481
1560
  opened = true;
1482
- await consumeSse(res.body, (frame) => {
1561
+ await consumeSse(idle.watch(res.body), (frame) => {
1483
1562
  if (frame.retry !== void 0) retryMs = frame.retry;
1484
1563
  if (frame.id !== void 0) lastEventId = frame.id;
1485
1564
  if (frame.data === void 0) return;
@@ -1498,18 +1577,23 @@ async function streamViaSse(ctx, runId, deliver, opts) {
1498
1577
  if (!opened) throw err;
1499
1578
  if (err instanceof HttpError && err.status !== 429 && err.status < 500) throw new SseResumeExhausted(err.message, err);
1500
1579
  dropError = err;
1580
+ } finally {
1581
+ idle?.stop();
1501
1582
  }
1502
1583
  if (terminal) return true;
1503
1584
  if (lastEventId === void 0 && deliver.highest() >= 0) lastEventId = String(deliver.highest());
1504
1585
  if (await runIsTerminal(ctx, runId)) return true;
1505
1586
  failures = progressed ? 1 : failures + 1;
1506
1587
  if (failures > maxReconnects) {
1507
- 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);
1508
1589
  }
1509
1590
  const delayMs = Math.min(retryMs * 2 ** (failures - 1), 3e4);
1510
1591
  opts.onReconnect?.({ attempt: failures, lastEventId, delayMs });
1511
- 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)"}
1512
1595
  `);
1596
+ }
1513
1597
  await sleep(delayMs);
1514
1598
  }
1515
1599
  }
@@ -1707,6 +1791,74 @@ function defaultReadTurn(ctx) {
1707
1791
  rl.once("close", onClose);
1708
1792
  });
1709
1793
  }
1794
+ var DEFAULT_IDLE_TIMEOUT_MS = 45e3;
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) {
1805
+ const controller = new AbortController();
1806
+ let timer;
1807
+ let onExpire;
1808
+ let beforeHeaders = true;
1809
+ const expire = (after) => {
1810
+ controller.abort(new StreamSilent(after, beforeHeaders));
1811
+ onExpire?.();
1812
+ };
1813
+ const arm = (after = ms2) => {
1814
+ if (!(after > 0)) return;
1815
+ if (timer !== void 0) clearTimeout(timer);
1816
+ timer = setTimeout(() => expire(after), after);
1817
+ };
1818
+ arm(headersMs);
1819
+ return {
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
+ },
1826
+ watch(body) {
1827
+ if (!(ms2 > 0) || typeof body?.getReader !== "function") return body;
1828
+ const source = body.getReader();
1829
+ return new ReadableStream({
1830
+ start(out) {
1831
+ onExpire = () => {
1832
+ out.error(new StreamSilent(ms2, false));
1833
+ source.cancel().catch(() => {
1834
+ });
1835
+ };
1836
+ },
1837
+ async pull(out) {
1838
+ try {
1839
+ const { value, done: done2 } = await source.read();
1840
+ if (done2) {
1841
+ out.close();
1842
+ return;
1843
+ }
1844
+ arm();
1845
+ out.enqueue(value);
1846
+ } catch (err) {
1847
+ out.error(err);
1848
+ }
1849
+ },
1850
+ cancel(reason) {
1851
+ return source.cancel(reason);
1852
+ }
1853
+ });
1854
+ },
1855
+ stop() {
1856
+ if (timer !== void 0) clearTimeout(timer);
1857
+ timer = void 0;
1858
+ onExpire = void 0;
1859
+ }
1860
+ };
1861
+ }
1710
1862
 
1711
1863
  // src/cli/chatShared.ts
1712
1864
  var enc = (value) => encodeURIComponent(value);
@@ -1731,9 +1883,7 @@ function writeFields(ctx, fields) {
1731
1883
  }
1732
1884
  }
1733
1885
  async function streamHostSse(ctx, requestedPath, opts) {
1734
- const { path, headers } = await resolveRequest(ctx, requestedPath, { accept: "text/event-stream" });
1735
- const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
1736
- if (ctx.apiKey) headers.authorization = `Bearer ${ctx.apiKey}`;
1886
+ const { url, headers } = await resolveStreamRequest(ctx, requestedPath, {});
1737
1887
  const controller = new AbortController();
1738
1888
  let timer;
1739
1889
  if (opts.timeoutMs && opts.timeoutMs > 0) timer = setTimeout(() => controller.abort(), opts.timeoutMs);
@@ -1790,6 +1940,82 @@ function renderFrame(frame) {
1790
1940
 
1791
1941
  // src/cli/routeKit.ts
1792
1942
  import { readFileSync as readFileSync4 } from "node:fs";
1943
+
1944
+ // src/cli/shared.ts
1945
+ function requireOrg(org2) {
1946
+ if (!org2) throw new CliError("This command is org-scoped \u2014 pass --org <orgId>.", 2);
1947
+ return String(org2);
1948
+ }
1949
+ function buildInputs(options) {
1950
+ const fromJson = options.inputsJson ? JSON.parse(options.inputsJson) : {};
1951
+ if (fromJson === null || typeof fromJson !== "object" || Array.isArray(fromJson)) {
1952
+ throw new CliError("--inputs-json must be a JSON object");
1953
+ }
1954
+ const inputs = { ...fromJson };
1955
+ for (const pair of options.input ?? []) {
1956
+ const eq = pair.indexOf("=");
1957
+ if (eq <= 0) throw new CliError(`--input must be key=value, got: ${pair}`);
1958
+ inputs[pair.slice(0, eq)] = parseInputValue(pair.slice(eq + 1));
1959
+ }
1960
+ return inputs;
1961
+ }
1962
+ function parseInputValue(value) {
1963
+ try {
1964
+ return JSON.parse(value);
1965
+ } catch {
1966
+ return value;
1967
+ }
1968
+ }
1969
+ function parseNodeVersion(version) {
1970
+ const [major, minor, patch] = version.split(".").map((v) => Number(v));
1971
+ return { major: major || 0, minor: minor || 0, patch: patch || 0 };
1972
+ }
1973
+ function defaultApiKeyFor(baseUrl) {
1974
+ try {
1975
+ const host = new URL(baseUrl).hostname;
1976
+ if (host === "localhost" || host === "127.0.0.1" || host === "::1") return DEFAULT_API_KEY;
1977
+ } catch {
1978
+ return void 0;
1979
+ }
1980
+ return void 0;
1981
+ }
1982
+ function normalizeBaseUrl(value) {
1983
+ if (!value) return DEFAULT_BASE_URL;
1984
+ return value.endsWith("/") ? value.slice(0, -1) : value;
1985
+ }
1986
+ function npmCommand() {
1987
+ return process.platform === "win32" ? "npm.cmd" : "npm";
1988
+ }
1989
+ function ok(name, message) {
1990
+ return { status: "ok", name, message };
1991
+ }
1992
+ function warn(name, message) {
1993
+ return { status: "warn", name, message };
1994
+ }
1995
+ function fail(name, message) {
1996
+ return { status: "fail", name, message };
1997
+ }
1998
+ function formatCheckTable(checks) {
1999
+ return formatTable(
2000
+ checks.map((c) => ({ status: c.status.toUpperCase(), check: c.name, message: c.message })),
2001
+ ["status", "check", "message"]
2002
+ );
2003
+ }
2004
+
2005
+ // src/cli/routeKit.ts
2006
+ var errText2 = (err) => err instanceof Error ? err.message : String(err);
2007
+ var DEFAULT_MESSAGES = {
2008
+ required: (flag, usage6) => `${usage6}
2009
+ ${flag} is required.`,
2010
+ refusal: (words, positionals) => `Refusing to ${words.join(" ")} ${positionals.join(" ")} without --yes.`.replace(/\s+/g, " ").trim(),
2011
+ invalidNumber: (flag, raw) => `${flag} must be a number (got "${raw}").`,
2012
+ invalidBool: (flag, raw) => `${flag} must be true or false (got "${raw}").`,
2013
+ invalidJson: (flag) => `${flag} must be valid JSON.`,
2014
+ unreadable: (flag, path, err) => `Could not read ${path} for ${flag}: ${errText2(err)}`,
2015
+ unreadableBody: (path, err) => `Could not read ${path}: ${errText2(err)}`,
2016
+ invalidBodyJson: "--body / --body-file must be a JSON object.",
2017
+ bodyNotObject: "--body / --body-file must be a JSON object."
2018
+ };
1793
2019
  var COMMON_BOOL = ["--help", "--yes"];
1794
2020
  function templates(cmd) {
1795
2021
  return Array.isArray(cmd.path) ? cmd.path : [cmd.path];
@@ -1797,18 +2023,31 @@ function templates(cmd) {
1797
2023
  function paramNames(path) {
1798
2024
  return [...path.matchAll(/:([A-Za-z][A-Za-z0-9_]*)/g)].map((m) => m[1]);
1799
2025
  }
2026
+ function positionalNames(cmd, path) {
2027
+ return paramNames(path).filter((p) => !(cmd.orgFlag && p === "org"));
2028
+ }
2029
+ function bindsOrg(cmd) {
2030
+ return Boolean(cmd.orgFlag) && templates(cmd).some((p) => paramNames(p).includes("org"));
2031
+ }
2032
+ function fillPath(cmd, template, positionals, org2) {
2033
+ let i = 0;
2034
+ return template.replace(/:([A-Za-z][A-Za-z0-9_]*)/g, (_m, name) => {
2035
+ if (cmd.orgFlag && name === "org") return encodeURIComponent(requireOrg(org2));
2036
+ return encodeURIComponent(positionals[i++] ?? "");
2037
+ });
2038
+ }
1800
2039
  function flagHint(f) {
1801
2040
  const t = f.type ?? "string";
1802
- const v = t === "boolean" ? "" : t === "file" || t === "file64" || t === "json-file" ? " <path>" : t === "map" ? " <key=value>..." : t === "list" ? ` <${f.key}>...` : t === "json" ? ` <json>` : ` <${f.key.split(".").pop()}>`;
2041
+ const v = t === "boolean" ? "" : t === "bool" ? " <true|false>" : t === "file" || t === "file64" || t === "json-file" ? " <path>" : t === "map" ? " <key=value>..." : t === "list" ? ` <${f.key}>...` : t === "json" ? ` <json>` : ` <${f.key.split(".").pop()}>`;
1803
2042
  return f.required ? `${f.flag}${v}` : `[${f.flag}${v}]`;
1804
2043
  }
1805
2044
  function usageLine(group, cmd) {
1806
2045
  const paths = templates(cmd);
1807
- const longest = paths.reduce((a, b) => paramNames(b).length > paramNames(a).length ? b : a);
1808
- const shortest = paths.reduce((a, b) => paramNames(b).length < paramNames(a).length ? b : a);
1809
- const req2 = paramNames(shortest);
1810
- const params = paramNames(longest).map((p, i) => i < req2.length ? `<${p}>` : `[<${p}>]`);
1811
- const flags = [...cmd.query ?? [], ...cmd.body ?? []].map(flagHint);
2046
+ const longest = paths.reduce((a, b) => positionalNames(cmd, b).length > positionalNames(cmd, a).length ? b : a);
2047
+ const shortest = paths.reduce((a, b) => positionalNames(cmd, b).length < positionalNames(cmd, a).length ? b : a);
2048
+ const req2 = positionalNames(cmd, shortest);
2049
+ const params = positionalNames(cmd, longest).map((p, i) => i < req2.length ? `<${p}>` : `[<${p}>]`);
2050
+ const flags = [...bindsOrg(cmd) ? ["--org <orgId>"] : [], ...[...cmd.query ?? [], ...cmd.body ?? []].map(flagHint)];
1812
2051
  if (cmd.body?.some((f) => f.type === "json")) flags.push("[--body <json>|--body-file <path>]");
1813
2052
  if (cmd.confirm ?? cmd.method === "DELETE") flags.push("--yes");
1814
2053
  if (cmd.method === "GET") flags.push("[--json]");
@@ -1844,9 +2083,15 @@ function setPath(target, key, value) {
1844
2083
  }
1845
2084
  node[parts[parts.length - 1]] = value;
1846
2085
  }
1847
- function coerce(f, raw) {
2086
+ function coerce(f, raw, cmd, msg) {
1848
2087
  const t = f.type ?? "string";
1849
2088
  if (t === "boolean") return raw;
2089
+ if (t === "bool") {
2090
+ const s2 = String(raw);
2091
+ if (s2 === "true" || s2 === "yes" || s2 === "1") return true;
2092
+ if (s2 === "false" || s2 === "no" || s2 === "0") return false;
2093
+ throw new CliError(msg.invalidBool(f.flag, s2), 2);
2094
+ }
1850
2095
  if (t === "list") return Array.isArray(raw) ? raw.map(String) : [String(raw)];
1851
2096
  if (t === "map") {
1852
2097
  const out = {};
@@ -1862,7 +2107,7 @@ function coerce(f, raw) {
1862
2107
  const s = String(raw);
1863
2108
  if (t === "number") {
1864
2109
  const n = Number(s);
1865
- if (!Number.isFinite(n)) throw new CliError(`${f.flag} must be a number (got "${s}").`, 2);
2110
+ if (!Number.isFinite(n) || cmd.strictNumbers && s.trim() === "") throw new CliError(msg.invalidNumber(f.flag, s), 2);
1866
2111
  return n;
1867
2112
  }
1868
2113
  if (t === "json-file") {
@@ -1870,7 +2115,7 @@ function coerce(f, raw) {
1870
2115
  try {
1871
2116
  text = readFileSync4(s, "utf8");
1872
2117
  } catch (err) {
1873
- throw new CliError(`Could not read ${s} for ${f.flag}: ${err instanceof Error ? err.message : String(err)}`, 2);
2118
+ throw new CliError(msg.unreadable(f.flag, s, err), 2);
1874
2119
  }
1875
2120
  try {
1876
2121
  return JSON.parse(text);
@@ -1882,14 +2127,14 @@ function coerce(f, raw) {
1882
2127
  try {
1883
2128
  return readFileSync4(s).toString("base64");
1884
2129
  } catch (err) {
1885
- throw new CliError(`Could not read ${s} for ${f.flag}: ${err instanceof Error ? err.message : String(err)}`, 2);
2130
+ throw new CliError(msg.unreadable(f.flag, s, err), 2);
1886
2131
  }
1887
2132
  }
1888
2133
  if (t === "file") {
1889
2134
  try {
1890
2135
  return readFileSync4(s, "utf8").replace(/\r?\n$/, "");
1891
2136
  } catch (err) {
1892
- throw new CliError(`Could not read ${s} for ${f.flag}: ${err instanceof Error ? err.message : String(err)}`, 2);
2137
+ throw new CliError(msg.unreadable(f.flag, s, err), 2);
1893
2138
  }
1894
2139
  }
1895
2140
  if (t === "csv") return s.split(",").map((x) => x.trim()).filter(Boolean);
@@ -1897,18 +2142,19 @@ function coerce(f, raw) {
1897
2142
  try {
1898
2143
  return JSON.parse(s);
1899
2144
  } catch {
1900
- throw new CliError(`${f.flag} must be valid JSON.`, 2);
2145
+ throw new CliError(msg.invalidJson(f.flag, s), 2);
1901
2146
  }
1902
2147
  }
1903
2148
  return s;
1904
2149
  }
1905
- function readBodyOptions(options) {
2150
+ function readBodyOptions(options, msg) {
1906
2151
  let raw;
1907
2152
  if (options.bodyFile !== void 0) {
2153
+ if (String(options.bodyFile).trim() === "") throw new CliError("--body-file needs a path (got an empty value).", 2);
1908
2154
  try {
1909
2155
  raw = readFileSync4(String(options.bodyFile), "utf8");
1910
2156
  } catch (err) {
1911
- throw new CliError(`Could not read ${String(options.bodyFile)}: ${err instanceof Error ? err.message : String(err)}`, 2);
2157
+ throw new CliError(msg.unreadableBody(String(options.bodyFile), err), 2);
1912
2158
  }
1913
2159
  } else if (options.body !== void 0) {
1914
2160
  raw = String(options.body);
@@ -1918,9 +2164,9 @@ function readBodyOptions(options) {
1918
2164
  try {
1919
2165
  parsed = JSON.parse(raw);
1920
2166
  } catch {
1921
- throw new CliError("--body / --body-file must be a JSON object.", 2);
2167
+ throw new CliError(msg.invalidBodyJson, 2);
1922
2168
  }
1923
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError("--body / --body-file must be a JSON object.", 2);
2169
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError(msg.bodyNotObject, 2);
1924
2170
  return parsed;
1925
2171
  }
1926
2172
  function hostError(err) {
@@ -1931,30 +2177,34 @@ function hostError(err) {
1931
2177
  ${hint}` : line, code);
1932
2178
  }
1933
2179
  async function runRoute(ctx, group, cmd, argv) {
2180
+ const msg = { ...DEFAULT_MESSAGES, ...cmd.messages ?? {} };
1934
2181
  const rest = argv.slice(cmd.words.length);
1935
2182
  const fields = [...cmd.query ?? [], ...cmd.body ?? []];
1936
2183
  const bool = [...COMMON_BOOL, ...fields.filter((f) => f.type === "boolean").flatMap((f) => [f.flag, f.flag.replace(/^--/, "--no-")])];
1937
2184
  const isMulti = (f) => f.type === "list" || f.type === "map";
1938
- const value = ["--body", "--body-file", ...fields.filter((f) => f.type !== "boolean" && !isMulti(f)).map((f) => f.flag)];
2185
+ const value = [
2186
+ ...cmd.bodyFlags !== false ? ["--body", "--body-file"] : [],
2187
+ ...bindsOrg(cmd) ? ["--org"] : [],
2188
+ ...fields.filter((f) => f.type !== "boolean" && !isMulti(f)).map((f) => f.flag)
2189
+ ];
1939
2190
  const multi = fields.filter(isMulti).map((f) => f.flag);
1940
2191
  const { options, positionals } = parseOptions(rest, { bool, value, multi });
1941
2192
  if (options.help) {
1942
- writeLine(ctx.io.stdout, `Usage: ${usageLine(group, cmd)}
2193
+ if (cmd.usageText !== void 0) write(ctx.io.stdout, cmd.usageText);
2194
+ else writeLine(ctx.io.stdout, `Usage: ${usageLine(group, cmd)}
1943
2195
 
1944
2196
  ${cmd.summary}
1945
2197
  Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
1946
2198
  return 0;
1947
2199
  }
1948
- const template = templates(cmd).find((p) => paramNames(p).length === positionals.length);
2200
+ const template = templates(cmd).find((p) => positionalNames(cmd, p).length === positionals.length);
1949
2201
  if (!template) {
1950
- write(ctx.io.stderr, `Usage: ${usageLine(group, cmd)}
2202
+ write(ctx.io.stderr, cmd.usageText ?? `Usage: ${usageLine(group, cmd)}
1951
2203
  `);
1952
2204
  return 2;
1953
2205
  }
1954
- let path = template;
1955
- paramNames(template).forEach((name, i) => {
1956
- path = path.replace(`:${name}`, encodeURIComponent(positionals[i]));
1957
- });
2206
+ const path = fillPath(cmd, template, positionals, options.org);
2207
+ const requiredError = (f) => new CliError(msg.required(f.flag, cmd.usageText ?? usageLine(group, cmd)), 2);
1958
2208
  const optionOf = (f) => {
1959
2209
  const name = f.flag.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1960
2210
  if (f.type === "boolean") {
@@ -1969,34 +2219,39 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
1969
2219
  for (const f of cmd.query ?? []) {
1970
2220
  const v = optionOf(f);
1971
2221
  if (v === void 0) {
1972
- if (f.required) throw new CliError(`${usageLine(group, cmd)}
1973
- ${f.flag} is required.`, 2);
2222
+ if (f.required) throw requiredError(f);
1974
2223
  continue;
1975
2224
  }
1976
- const c = coerce(f, v);
1977
- if (Array.isArray(c)) c.forEach((x) => qs2.append(f.key, String(x)));
1978
- else qs2.set(f.key, String(c));
2225
+ const c = coerce(f, v, cmd, msg);
2226
+ if (f.type === "csv") qs2.set(f.key, c.join(","));
2227
+ else if (Array.isArray(c)) c.forEach((x) => qs2.append(f.key, String(x)));
2228
+ else qs2.set(f.key, c !== null && typeof c === "object" ? JSON.stringify(c) : String(c));
1979
2229
  }
1980
2230
  const url = qs2.size ? `${path}${path.includes("?") ? "&" : "?"}${qs2.toString()}` : path;
1981
2231
  const confirm2 = cmd.confirm ?? cmd.method === "DELETE";
1982
- if (confirm2 && !options.yes) throw new CliError(`Refusing to ${cmd.words.join(" ")} ${positionals.join(" ")} without --yes.`.replace(/\s+/g, " ").trim(), 2);
1983
- let body;
2232
+ const gate = () => {
2233
+ if (confirm2 && !options.yes) throw new CliError(msg.refusal(cmd.words, positionals), 2);
2234
+ };
2235
+ if (!cmd.validateFirst) gate();
1984
2236
  const isWrite = cmd.method !== "GET" && cmd.method !== "DELETE";
1985
- if (isWrite || cmd.body?.length) {
1986
- const explicit = readBodyOptions(options);
1987
- const overlay = {};
2237
+ const takesBody = isWrite || Boolean(cmd.body?.length) || cmd.bodyFlags === true;
2238
+ let explicit = {};
2239
+ const overlay = {};
2240
+ if (takesBody) {
2241
+ explicit = readBodyOptions(options, msg);
1988
2242
  for (const f of cmd.body ?? []) {
1989
2243
  const v = optionOf(f);
1990
- if (v === void 0) continue;
1991
- setPath(overlay, f.key, coerce(f, v));
2244
+ if (v !== void 0) setPath(overlay, f.key, coerce(f, v, cmd, msg));
2245
+ else if (cmd.validateFirst && f.required && getPath(explicit, f.key) === void 0) throw requiredError(f);
1992
2246
  }
2247
+ }
2248
+ if (cmd.validateFirst) gate();
2249
+ let body;
2250
+ if (takesBody) {
1993
2251
  let base16 = {};
1994
2252
  if (cmd.rmw) {
1995
- let from = cmd.rmw.from ?? template;
1996
- paramNames(from).forEach((name, i) => {
1997
- from = from.replace(`:${name}`, encodeURIComponent(positionals[i]));
1998
- });
1999
- const current = await send(ctx, from, { method: "GET" }, cmd.anonymous);
2253
+ const from = fillPath(cmd, cmd.rmw.from ?? template, positionals, options.org);
2254
+ const current = await send(ctx, cmd, from, { method: "GET" });
2000
2255
  const picked = cmd.rmw.pick ? cmd.rmw.pick(current.body) : current.body;
2001
2256
  base16 = picked && typeof picked === "object" && !Array.isArray(picked) ? { ...picked } : {};
2002
2257
  }
@@ -2005,13 +2260,15 @@ ${f.flag} is required.`, 2);
2005
2260
  const v = getPath(overlay, f.key);
2006
2261
  if (v !== void 0) setPath(body, f.key, v);
2007
2262
  }
2008
- for (const f of cmd.body ?? []) {
2009
- if (f.required && getPath(body, f.key) === void 0) throw new CliError(`${usageLine(group, cmd)}
2010
- ${f.flag} is required.`, 2);
2263
+ if (!cmd.validateFirst) {
2264
+ for (const f of cmd.body ?? []) {
2265
+ if (f.required && getPath(body, f.key) === void 0) throw requiredError(f);
2266
+ }
2011
2267
  }
2012
2268
  if (!isWrite && Object.keys(body).length === 0) body = void 0;
2013
2269
  if (isWrite && body && Object.keys(body).length === 0 && cmd.emptyBody === false) body = void 0;
2014
2270
  }
2271
+ if (cmd.notice !== void 0) writeLine(ctx.io.stderr, cmd.notice);
2015
2272
  try {
2016
2273
  const res = await requestJson(ctx, url, { method: cmd.method, ...body !== void 0 ? { body } : {}, ...cmd.anonymous ? { auth: false } : {} });
2017
2274
  render(ctx, cmd, positionals, res.status, res.body);
@@ -2021,25 +2278,42 @@ ${f.flag} is required.`, 2);
2021
2278
  render(ctx, { ...cmd, method: "GET" }, positionals, err.status, err.body);
2022
2279
  return 1;
2023
2280
  }
2024
- if (err instanceof HttpError) throw hostError(err);
2281
+ if (err instanceof HttpError && cmd.hostErrors !== false) throw hostError(err);
2025
2282
  throw err;
2026
2283
  }
2027
2284
  }
2028
2285
  function getPath(obj, key) {
2029
2286
  return key.split(".").reduce((node, part) => node && typeof node === "object" ? node[part] : void 0, obj);
2030
2287
  }
2031
- async function send(ctx, url, init, anonymous) {
2288
+ async function send(ctx, cmd, url, init) {
2032
2289
  try {
2033
- return await requestJson(ctx, url, { method: init.method, ...init.body !== void 0 ? { body: init.body } : {}, ...anonymous ? { auth: false } : {} });
2290
+ return await requestJson(ctx, url, { method: init.method, ...init.body !== void 0 ? { body: init.body } : {}, ...cmd.anonymous ? { auth: false } : {} });
2034
2291
  } catch (err) {
2035
- if (err instanceof HttpError) throw hostError(err);
2292
+ if (err instanceof HttpError && cmd.hostErrors !== false) throw hostError(err);
2036
2293
  throw err;
2037
2294
  }
2038
2295
  }
2296
+ function tableRows(table3, body) {
2297
+ if (!table3.anyArray) return table3.key ? getPath(body ?? {}, table3.key) : body;
2298
+ if (Array.isArray(body)) return body;
2299
+ const keyed = table3.key ? getPath(body ?? {}, table3.key) : void 0;
2300
+ if (Array.isArray(keyed)) return keyed;
2301
+ return Object.values(body ?? {}).find((v) => Array.isArray(v)) ?? [];
2302
+ }
2039
2303
  function render(ctx, cmd, positionals, status, rawBody) {
2040
2304
  const body = cmd.transform ? cmd.transform(rawBody) : rawBody;
2305
+ const noContent = body === null || body === void 0 ? cmd.noContent : void 0;
2041
2306
  if (ctx.json) {
2042
- writeJson(ctx.io.stdout, body);
2307
+ writeJson(ctx.io.stdout, noContent !== void 0 ? { ok: true, status } : body);
2308
+ return;
2309
+ }
2310
+ if (cmd.rawText && body && typeof body === "object" && typeof body.raw === "string" && Object.keys(body).length === 1) {
2311
+ write(ctx.io.stdout, body.raw.endsWith("\n") ? body.raw : `${body.raw}
2312
+ `);
2313
+ return;
2314
+ }
2315
+ if (noContent !== void 0) {
2316
+ writeLine(ctx.io.stdout, noContent.replace("{status}", String(status)));
2043
2317
  return;
2044
2318
  }
2045
2319
  if (cmd.human) {
@@ -2047,7 +2321,7 @@ function render(ctx, cmd, positionals, status, rawBody) {
2047
2321
  return;
2048
2322
  }
2049
2323
  if (cmd.table) {
2050
- const rows = cmd.table.key ? getPath(body ?? {}, cmd.table.key) : body;
2324
+ const rows = tableRows(cmd.table, body);
2051
2325
  if (rows === null || rows === void 0) {
2052
2326
  writeLine(ctx.io.stdout, cmd.table.empty ?? "None.");
2053
2327
  return;
@@ -2065,7 +2339,7 @@ function render(ctx, cmd, positionals, status, rawBody) {
2065
2339
  return;
2066
2340
  }
2067
2341
  }
2068
- if (cmd.method === "GET") {
2342
+ if (cmd.method === "GET" || cmd.writeOutput === "json") {
2069
2343
  writeJson(ctx.io.stdout, body);
2070
2344
  return;
2071
2345
  }
@@ -2404,7 +2678,8 @@ async function runInterrupts(ctx, argv) {
2404
2678
  if (b.key) writeLine(ctx.io.stdout, `key: ${b.key}`);
2405
2679
  writeLine(ctx.io.stdout, `resolved: ${b.resolved ? "yes" : "no"}`);
2406
2680
  if (b.expiresAt) writeLine(ctx.io.stdout, `expiresAt: ${b.expiresAt}`);
2407
- 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}`);
2408
2683
  if (b.resumeSchema !== void 0) writeLine(ctx.io.stdout, `resumeSchema: ${JSON.stringify(b.resumeSchema)}`);
2409
2684
  return 0;
2410
2685
  }
@@ -2460,67 +2735,6 @@ function lifecycleError(err) {
2460
2735
  throw err;
2461
2736
  }
2462
2737
 
2463
- // src/cli/shared.ts
2464
- function requireOrg(org2) {
2465
- if (!org2) throw new CliError("This command is org-scoped \u2014 pass --org <orgId>.", 2);
2466
- return String(org2);
2467
- }
2468
- function buildInputs(options) {
2469
- const fromJson = options.inputsJson ? JSON.parse(options.inputsJson) : {};
2470
- if (fromJson === null || typeof fromJson !== "object" || Array.isArray(fromJson)) {
2471
- throw new CliError("--inputs-json must be a JSON object");
2472
- }
2473
- const inputs = { ...fromJson };
2474
- for (const pair of options.input ?? []) {
2475
- const eq = pair.indexOf("=");
2476
- if (eq <= 0) throw new CliError(`--input must be key=value, got: ${pair}`);
2477
- inputs[pair.slice(0, eq)] = parseInputValue(pair.slice(eq + 1));
2478
- }
2479
- return inputs;
2480
- }
2481
- function parseInputValue(value) {
2482
- try {
2483
- return JSON.parse(value);
2484
- } catch {
2485
- return value;
2486
- }
2487
- }
2488
- function parseNodeVersion(version) {
2489
- const [major, minor, patch] = version.split(".").map((v) => Number(v));
2490
- return { major: major || 0, minor: minor || 0, patch: patch || 0 };
2491
- }
2492
- function defaultApiKeyFor(baseUrl) {
2493
- try {
2494
- const host = new URL(baseUrl).hostname;
2495
- if (host === "localhost" || host === "127.0.0.1" || host === "::1") return DEFAULT_API_KEY;
2496
- } catch {
2497
- return void 0;
2498
- }
2499
- return void 0;
2500
- }
2501
- function normalizeBaseUrl(value) {
2502
- if (!value) return DEFAULT_BASE_URL;
2503
- return value.endsWith("/") ? value.slice(0, -1) : value;
2504
- }
2505
- function npmCommand() {
2506
- return process.platform === "win32" ? "npm.cmd" : "npm";
2507
- }
2508
- function ok(name, message) {
2509
- return { status: "ok", name, message };
2510
- }
2511
- function warn(name, message) {
2512
- return { status: "warn", name, message };
2513
- }
2514
- function fail(name, message) {
2515
- return { status: "fail", name, message };
2516
- }
2517
- function formatCheckTable(checks) {
2518
- return formatTable(
2519
- checks.map((c) => ({ status: c.status.toUpperCase(), check: c.name, message: c.message })),
2520
- ["status", "check", "message"]
2521
- );
2522
- }
2523
-
2524
2738
  // src/cli/contentHelpers.ts
2525
2739
  import { readFileSync as readFileSync5 } from "node:fs";
2526
2740
  import { resolve as resolvePath2 } from "node:path";
@@ -2873,19 +3087,37 @@ import { readFileSync as readFileSync6 } from "node:fs";
2873
3087
 
2874
3088
  // src/cli/requestHelpers.ts
2875
3089
  var NOT_SERVED = /* @__PURE__ */ new Set([404, 405, 501]);
3090
+ function presentsCredential(ctx, req2) {
3091
+ if (req2.auth !== false && ctx.apiKey) return true;
3092
+ return Object.keys(req2.headers ?? {}).some((h) => /^(authorization|cookie)$/i.test(h));
3093
+ }
2876
3094
  async function requestNormativeOrHost(ctx, normativePath, hostPath, options = {}) {
2877
3095
  const { forceHost, ...req2 } = options;
3096
+ let anonymousRetryOf;
2878
3097
  if (!forceHost) {
2879
3098
  try {
2880
3099
  const res2 = await requestJson(ctx, normativePath, req2);
2881
3100
  if (ctx.verbose) writeLine(ctx.io.stderr, `openwop: served by the normative path ${normativePath}`);
2882
3101
  return { ...res2, path: normativePath, via: "normative" };
2883
3102
  } catch (err) {
2884
- if (!(err instanceof HttpError) || !NOT_SERVED.has(err.status)) throw err;
3103
+ if (!(err instanceof HttpError)) throw err;
3104
+ if (isNoCredentialChallenge(err) && !presentsCredential(ctx, req2)) anonymousRetryOf = err;
3105
+ else if (!NOT_SERVED.has(err.status)) throw err;
2885
3106
  }
2886
3107
  }
2887
- const res = await requestJson(ctx, hostPath, req2);
2888
- if (ctx.verbose) writeLine(ctx.io.stderr, `openwop: served by the host-extension path ${hostPath}`);
3108
+ let res;
3109
+ try {
3110
+ res = await requestJson(ctx, hostPath, req2);
3111
+ } catch (err) {
3112
+ if (anonymousRetryOf && err instanceof HttpError && (NOT_SERVED.has(err.status) || err.status === 401)) throw anonymousRetryOf;
3113
+ throw err;
3114
+ }
3115
+ if (anonymousRetryOf) {
3116
+ const sent = (await resolveRequest(ctx, hostPath, {})).path;
3117
+ writeLine(ctx.io.stderr, `openwop: no credential sent \u2014 showing this host's anonymous view (${sent}), not your workspace. Pass --api-key, or run \`openwop onboard\` to save one.`);
3118
+ } else if (ctx.verbose) {
3119
+ writeLine(ctx.io.stderr, `openwop: served by the host-extension path ${hostPath}`);
3120
+ }
2889
3121
  return { ...res, path: hostPath, via: "host" };
2890
3122
  }
2891
3123
  function failClosedOn404(err, surface) {
@@ -4847,6 +5079,8 @@ GET /v1/agents and GET /v1/agents/{agentId}. When the host does not serve the
4847
5079
  normative read (404/405/501) they fall back to the host-extension alias
4848
5080
  GET /v1/host/openwop-app/agents[/{agentId}]; --host forces the alias (which also
4849
5081
  reports the host's runtime posture); --verbose names the path that answered.
5082
+ With no credential, a v2 host refuses the normative read (401); the command
5083
+ then shows the host's anonymous view from the alias and says so on stderr.
4850
5084
  'run' dispatches one agent turn via
4851
5085
  POST /v1/host/openwop-app/agents/{agentId}/dispatch \u2014 the tool surface is filtered to
4852
5086
  the agent's toolAllowlist (RFC 0002 \xA7A14), task/return payloads are validated
@@ -5170,6 +5404,8 @@ GET /v1/agents/roster. The normative view carries only protocol-shaped entries
5170
5404
  (the host may omit host-only entries such as advisors); when the host does not
5171
5405
  serve it (404/405/501), or with --host, 'list' reads the host-extension
5172
5406
  GET /v1/host/openwop-app/roster instead (--verbose names the path that answered).
5407
+ With no credential, a v2 host refuses the normative read (401); 'list' then
5408
+ shows the host's anonymous view from the alias and says so on stderr.
5173
5409
  'get'/'create'/'update'/'delete' drive the host-extension surface
5174
5410
  GET/POST/PATCH/DELETE /v1/host/openwop-app/roster[/{rosterId}].
5175
5411
 
@@ -5352,7 +5588,9 @@ field).
5352
5588
  GET /v1/agents/org-chart/{departmentId}. When the host does not serve it
5353
5589
  (404/405/501 on 'get'), or with --host, they read the host-extension
5354
5590
  GET /v1/host/openwop-app/org-chart[/{departmentId}] (--verbose names the path
5355
- that answered). 'set'/'clear' drive the host-extension PUT/DELETE
5591
+ that answered). With no credential, a v2 host refuses the normative read (401);
5592
+ 'get'/'dept' then show the host's anonymous view from the alias and say so on
5593
+ stderr. 'set'/'clear' drive the host-extension PUT/DELETE
5356
5594
  /v1/host/openwop-app/org-chart. 'set' replaces the whole chart from a JSON file
5357
5595
  with { "departments": [...], "members": [...] }.
5358
5596
 
@@ -5734,9 +5972,7 @@ async function boardWatch(ctx, argv) {
5734
5972
  write(ctx.io.stdout, "Usage: openwop kanban watch <boardId>\n");
5735
5973
  return options.help ? 0 : 2;
5736
5974
  }
5737
- const { path, headers } = await resolveRequest(ctx, `/v1/host/openwop-app/kanban/boards/${encodeURIComponent(positionals[0])}/events`, { accept: "text/event-stream" });
5738
- const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
5739
- if (ctx.apiKey) headers.authorization = `Bearer ${ctx.apiKey}`;
5975
+ const { url, headers } = await resolveStreamRequest(ctx, `/v1/host/openwop-app/kanban/boards/${encodeURIComponent(positionals[0])}/events`, {});
5740
5976
  const res = await ctx.fetchImpl(url, { headers });
5741
5977
  if (!res.ok || !res.body) {
5742
5978
  throw new CliError(`Could not open the board event stream (HTTP ${res.status}).`, 1);
@@ -6061,7 +6297,6 @@ async function brandAsset(ctx, argv) {
6061
6297
  }
6062
6298
 
6063
6299
  // src/cli/resourceCommands.ts
6064
- import { readFileSync as readFileSync9 } from "node:fs";
6065
6300
  function parseField(spec) {
6066
6301
  let s = spec;
6067
6302
  let flagOverride;
@@ -6076,35 +6311,6 @@ function parseField(spec) {
6076
6311
  const flag = `--${flagOverride ?? key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`).replace(/_/g, "-")}`;
6077
6312
  return { key, flag, type, required };
6078
6313
  }
6079
- function coerce2(field, raw) {
6080
- switch (field.type) {
6081
- case "number": {
6082
- const n = Number(raw);
6083
- if (raw.trim() === "" || !Number.isFinite(n)) throw new CliError(`${field.flag} must be a number, got: ${raw}`);
6084
- return n;
6085
- }
6086
- case "bool":
6087
- if (raw === "true" || raw === "yes" || raw === "1") return true;
6088
- if (raw === "false" || raw === "no" || raw === "0") return false;
6089
- throw new CliError(`${field.flag} must be true or false, got: ${raw}`);
6090
- case "json":
6091
- try {
6092
- return JSON.parse(raw);
6093
- } catch {
6094
- throw new CliError(`${field.flag} must be valid JSON`);
6095
- }
6096
- case "list":
6097
- return raw.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
6098
- case "file":
6099
- try {
6100
- return readFileSync9(raw, "utf8").replace(/\r?\n$/, "");
6101
- } catch {
6102
- throw new CliError(`Cannot read ${field.flag} ${raw}`);
6103
- }
6104
- default:
6105
- return raw;
6106
- }
6107
- }
6108
6314
  function hasOrg(route) {
6109
6315
  return /:org(?![A-Za-z0-9_])/.test(route);
6110
6316
  }
@@ -6147,142 +6353,62 @@ ${footer ? `
6147
6353
  ${footer.trim()}
6148
6354
  ` : ""}`;
6149
6355
  }
6150
- function matchSpec(specs, argv) {
6151
- let best = null;
6152
- for (const spec of specs) {
6153
- if (spec.cmd.length > argv.length) continue;
6154
- if (spec.cmd.every((w, i) => argv[i] === w) && (!best || spec.cmd.length > best.spec.cmd.length)) {
6155
- best = { spec, rest: argv.slice(spec.cmd.length) };
6156
- }
6157
- }
6158
- return best;
6159
- }
6160
- function readBodyOption2(options) {
6161
- let raw;
6162
- if (options.bodyFile) {
6163
- try {
6164
- raw = readFileSync9(String(options.bodyFile), "utf8");
6165
- } catch {
6166
- throw new CliError(`Cannot read --body-file ${options.bodyFile}`);
6167
- }
6168
- } else if (options.body !== void 0) {
6169
- raw = String(options.body);
6170
- }
6171
- if (raw === void 0) return {};
6172
- let parsed;
6173
- try {
6174
- parsed = JSON.parse(raw);
6175
- } catch {
6176
- throw new CliError("--body/--body-file must be valid JSON");
6177
- }
6178
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError("--body/--body-file must be a JSON object");
6179
- return parsed;
6356
+ var TYPES = { string: "string", number: "number", bool: "bool", json: "json", list: "csv", file: "file" };
6357
+ function toField(spec) {
6358
+ const f = parseField(spec);
6359
+ return { flag: f.flag, key: f.key, type: TYPES[f.type] ?? "string", required: f.required };
6180
6360
  }
6181
- async function runSpec(ctx, group, spec, argv) {
6182
- const queryFields = (spec.query ?? []).map(parseField);
6183
- const bodyFields = (spec.body ?? []).map(parseField);
6184
- const allowBody = Boolean(spec.body || spec.rawBody);
6185
- const valueFlags = [...queryFields, ...bodyFields].map((f) => f.flag);
6186
- if (hasOrg(spec.route)) valueFlags.push("--org");
6187
- if (allowBody) valueFlags.push("--body", "--body-file");
6188
- const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"], value: valueFlags });
6361
+ function toRouteCmd(group, spec) {
6189
6362
  const usage6 = `Usage: ${usageLine2(group, spec)}
6190
6363
  ${spec.method} ${spec.route} \u2014 ${spec.summary}
6191
6364
  `;
6192
- if (options.help) {
6193
- write(ctx.io.stdout, usage6);
6194
- return 0;
6195
- }
6196
- const params = paramsOf(spec.route);
6197
- if (positionals.length !== params.length) {
6198
- write(ctx.io.stderr, usage6);
6199
- return 2;
6200
- }
6201
- const optionValue = (f) => options[f.flag.slice(2).replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase())];
6202
- const fill = (route) => {
6203
- let i = 0;
6204
- return route.replace(/:[A-Za-z][A-Za-z0-9_]*/g, (m) => {
6205
- if (m === ":org") return encodeURIComponent(requireOrg(options.org));
6206
- return encodeURIComponent(positionals[i++] ?? "");
6207
- });
6208
- };
6209
- let path = fill(spec.route);
6210
- const qs2 = new URLSearchParams();
6211
- for (const f of queryFields) {
6212
- const v = optionValue(f);
6213
- if (v === void 0) {
6214
- if (f.required) throw new CliError(`${f.flag} is required.
6215
- ${usage6}`);
6216
- continue;
6217
- }
6218
- const c = coerce2(f, String(v));
6219
- qs2.set(f.key, Array.isArray(c) ? c.join(",") : typeof c === "object" ? JSON.stringify(c) : String(c));
6220
- }
6221
- const q = qs2.toString();
6222
- if (q) path += `${path.includes("?") ? "&" : "?"}${q}`;
6223
- let body;
6224
- if (allowBody || spec.method === "POST" || spec.method === "PUT" || spec.method === "PATCH") {
6225
- body = allowBody ? readBodyOption2(options) : {};
6226
- for (const f of bodyFields) {
6227
- const v = optionValue(f);
6228
- if (v !== void 0) body[f.key] = coerce2(f, String(v));
6229
- else if (f.required && body[f.key] === void 0) throw new CliError(`${f.flag} is required.
6230
- ${usage6}`);
6231
- }
6232
- if (spec.method === "DELETE" && Object.keys(body).length === 0) body = void 0;
6233
- }
6234
- if (spec.confirm && !options.yes) {
6235
- throw new CliError(`Refusing to ${spec.summary.charAt(0).toLowerCase()}${spec.summary.slice(1).replace(/\.$/, "")} without --yes.`, 2);
6236
- }
6237
- if (spec.rmw) {
6238
- const current = await requestJson(ctx, fill(spec.rmw), { auth: spec.auth });
6239
- const base16 = spec.rmwKey ? current.body?.[spec.rmwKey] : current.body;
6240
- body = { ...base16 && typeof base16 === "object" && !Array.isArray(base16) ? base16 : {}, ...body ?? {} };
6241
- }
6242
- if (spec.notice) writeLine(ctx.io.stderr, spec.notice);
6243
- const res = await requestJson(ctx, path, {
6365
+ const rmwKey = spec.rmwKey;
6366
+ return {
6367
+ words: spec.cmd,
6244
6368
  method: spec.method,
6245
- ...body !== void 0 ? { body } : {},
6246
- ...spec.auth === false ? { auth: false } : {}
6247
- });
6248
- return render2(ctx, spec, res.status, res.body);
6249
- }
6250
- function render2(ctx, spec, status, body) {
6251
- if (ctx.json) {
6252
- writeJson(ctx.io.stdout, body ?? { ok: true, status });
6253
- return 0;
6254
- }
6255
- if (body && typeof body === "object" && typeof body.raw === "string" && Object.keys(body).length === 1) {
6256
- write(ctx.io.stdout, body.raw.endsWith("\n") ? body.raw : `${body.raw}
6257
- `);
6258
- return 0;
6259
- }
6260
- if (body === null || body === void 0) {
6261
- writeLine(ctx.io.stdout, spec.method === "DELETE" ? "Deleted." : `Done (HTTP ${status}).`);
6262
- return 0;
6263
- }
6264
- if (spec.list) {
6265
- const items = Array.isArray(body) ? body : spec.list.key && Array.isArray(body?.[spec.list.key]) ? body[spec.list.key] : Object.values(body ?? {}).find((v) => Array.isArray(v)) ?? [];
6266
- if (items.length === 0) {
6267
- writeLine(ctx.io.stdout, spec.list.empty ?? "None.");
6268
- return 0;
6369
+ path: spec.route,
6370
+ summary: spec.summary,
6371
+ query: (spec.query ?? []).map(toField),
6372
+ body: (spec.body ?? []).map(toField),
6373
+ orgFlag: true,
6374
+ bodyFlags: Boolean(spec.body || spec.rawBody),
6375
+ emptyBody: true,
6376
+ confirm: Boolean(spec.confirm),
6377
+ anonymous: spec.auth === false,
6378
+ ...spec.list ? { table: { key: spec.list.key, columns: spec.list.columns, empty: spec.list.empty ?? "None.", anyArray: true } } : {},
6379
+ ...spec.rmw ? { rmw: { from: spec.rmw, pick: (body) => rmwKey ? body?.[rmwKey] : body } } : {},
6380
+ ...spec.notice !== void 0 ? { notice: spec.notice } : {},
6381
+ rawText: true,
6382
+ // every spec, as before — `text` only documents the route
6383
+ noContent: spec.method === "DELETE" ? "Deleted." : "Done (HTTP {status}).",
6384
+ writeOutput: "json",
6385
+ hostErrors: false,
6386
+ validateFirst: true,
6387
+ strictNumbers: true,
6388
+ usageText: usage6,
6389
+ messages: {
6390
+ required: (flag, usageText) => `${flag} is required.
6391
+ ${usageText}`,
6392
+ refusal: () => `Refusing to ${spec.summary.charAt(0).toLowerCase()}${spec.summary.slice(1).replace(/\.$/, "")} without --yes.`,
6393
+ invalidNumber: (flag, raw) => `${flag} must be a number, got: ${raw}`,
6394
+ invalidBool: (flag, raw) => `${flag} must be true or false, got: ${raw}`,
6395
+ invalidJson: (flag) => `${flag} must be valid JSON`,
6396
+ unreadable: (flag, path) => `Cannot read ${flag} ${path}`,
6397
+ unreadableBody: (path) => `Cannot read --body-file ${path}`,
6398
+ invalidBodyJson: "--body/--body-file must be valid JSON",
6399
+ bodyNotObject: "--body/--body-file must be a JSON object"
6269
6400
  }
6270
- writeLine(ctx.io.stdout, formatTable(items.map((row2) => {
6271
- const out = {};
6272
- for (const col of spec.list.columns) {
6273
- const v = row2?.[col];
6274
- out[col] = v === void 0 || v === null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
6275
- }
6276
- return out;
6277
- }), spec.list.columns));
6278
- return 0;
6279
- }
6280
- writeJson(ctx.io.stdout, body);
6281
- return 0;
6401
+ };
6402
+ }
6403
+ function matchSpec(specs, argv) {
6404
+ return matchRoute(specs.map((spec) => ({ words: spec.cmd, spec })), argv)?.spec;
6405
+ }
6406
+ async function runSpec(ctx, group, spec, argv) {
6407
+ return runRoute(ctx, group, toRouteCmd(group, spec), [...spec.cmd, ...argv]);
6282
6408
  }
6283
6409
  async function dispatchSpecs(ctx, group, specs, argv) {
6284
- const match = matchSpec(specs, argv);
6285
- return match ? runSpec(ctx, group, match.spec, match.rest) : void 0;
6410
+ const spec = matchSpec(specs, argv);
6411
+ return spec ? runSpec(ctx, group, spec, argv.slice(spec.cmd.length)) : void 0;
6286
6412
  }
6287
6413
  function specsUsage(group, specs) {
6288
6414
  return specs.map((s) => ` ${usageLine2(group, s)}
@@ -6293,12 +6419,12 @@ async function runResourceGroup(ctx, group, help, specs, argv) {
6293
6419
  write(ctx.io.stdout, help);
6294
6420
  return 0;
6295
6421
  }
6296
- const match = matchSpec(specs, argv);
6297
- if (!match) {
6422
+ const spec = matchSpec(specs, argv);
6423
+ if (!spec) {
6298
6424
  throw new CliError(`Unknown ${group} command: ${argv.filter((a) => !a.startsWith("-")).slice(0, 2).join(" ")}
6299
6425
  Run \`openwop ${group} --help\` for usage.`);
6300
6426
  }
6301
- return runSpec(ctx, group, match.spec, match.rest);
6427
+ return runSpec(ctx, group, spec, argv.slice(spec.cmd.length));
6302
6428
  }
6303
6429
 
6304
6430
  // src/cli/crmOrg.ts
@@ -6975,7 +7101,7 @@ async function sharingFrameViews(ctx, argv) {
6975
7101
  }
6976
7102
 
6977
7103
  // src/cli/marketingShared.ts
6978
- import { readFileSync as readFileSync10 } from "node:fs";
7104
+ import { readFileSync as readFileSync9 } from "node:fs";
6979
7105
  import { resolve as resolvePath5 } from "node:path";
6980
7106
  var APP = "/v1/host/openwop-app";
6981
7107
  async function dispatchTable(ctx, group, help, table3, argv, defaultSub) {
@@ -7033,14 +7159,14 @@ function jsonObject(text, flag) {
7033
7159
  }
7034
7160
  function readText(ctx, path, flag) {
7035
7161
  try {
7036
- return readFileSync10(resolvePath5(ctx.cwd, path), "utf8");
7162
+ return readFileSync9(resolvePath5(ctx.cwd, path), "utf8");
7037
7163
  } catch (err) {
7038
7164
  throw new CliError(`Cannot read ${flag} ${path}: ${err instanceof Error ? err.message : String(err)}`, 2);
7039
7165
  }
7040
7166
  }
7041
7167
  function readBase64(ctx, path, flag) {
7042
7168
  try {
7043
- return readFileSync10(resolvePath5(ctx.cwd, path)).toString("base64");
7169
+ return readFileSync9(resolvePath5(ctx.cwd, path)).toString("base64");
7044
7170
  } catch (err) {
7045
7171
  throw new CliError(`Cannot read ${flag} ${path}: ${err instanceof Error ? err.message : String(err)}`, 2);
7046
7172
  }
@@ -8652,10 +8778,10 @@ Run \`openwop documents --help\` for usage.`);
8652
8778
  if (!id) return usage(ctx, "add-version <documentId> --org <orgId> (--content <text> | --content-file <path>)");
8653
8779
  let content = options.content !== void 0 ? String(options.content) : void 0;
8654
8780
  if (options.contentFile) {
8655
- const { readFileSync: readFileSync23 } = await import("node:fs");
8781
+ const { readFileSync: readFileSync22 } = await import("node:fs");
8656
8782
  const { resolve: resolve5 } = await import("node:path");
8657
8783
  try {
8658
- content = readFileSync23(resolve5(ctx.cwd, String(options.contentFile)), "utf8");
8784
+ content = readFileSync22(resolve5(ctx.cwd, String(options.contentFile)), "utf8");
8659
8785
  } catch (err) {
8660
8786
  throw new CliError(`Cannot read --content-file ${String(options.contentFile)}: ${err instanceof Error ? err.message : String(err)}`);
8661
8787
  }
@@ -9242,7 +9368,7 @@ Run \`openwop reviews --help\` for usage.`);
9242
9368
  }
9243
9369
 
9244
9370
  // src/cli/adminShared.ts
9245
- import { readFileSync as readFileSync11 } from "node:fs";
9371
+ import { readFileSync as readFileSync10 } from "node:fs";
9246
9372
  import { resolve as resolve2 } from "node:path";
9247
9373
 
9248
9374
  // src/prompt.ts
@@ -9361,7 +9487,7 @@ async function readSecretInput(ctx, options, label) {
9361
9487
  let value;
9362
9488
  if (options.valueFile !== void 0) {
9363
9489
  try {
9364
- value = readFileSync11(resolve2(ctx.cwd, String(options.valueFile)), "utf8").replace(/\r?\n$/, "");
9490
+ value = readFileSync10(resolve2(ctx.cwd, String(options.valueFile)), "utf8").replace(/\r?\n$/, "");
9365
9491
  } catch (err) {
9366
9492
  throw new CliError(`Cannot read --value-file ${options.valueFile}: ${err instanceof Error ? err.message : String(err)}`, 2);
9367
9493
  }
@@ -10315,7 +10441,7 @@ async function runCampaignsOrch(ctx, argv) {
10315
10441
  }
10316
10442
 
10317
10443
  // src/cli/notebooks.ts
10318
- import { readFileSync as readFileSync12 } from "node:fs";
10444
+ import { readFileSync as readFileSync11 } from "node:fs";
10319
10445
  import { extname as extname3, resolve as resolvePath7 } from "node:path";
10320
10446
  var BASE9 = "/v1/host/openwop-app/notebooks";
10321
10447
  var NOTEBOOKS_HELP = `Usage:
@@ -10390,7 +10516,7 @@ var MIME = {
10390
10516
  function readFileBase64(ctx, file, contentType) {
10391
10517
  let buf;
10392
10518
  try {
10393
- buf = readFileSync12(resolvePath7(ctx.cwd, file));
10519
+ buf = readFileSync11(resolvePath7(ctx.cwd, file));
10394
10520
  } catch (err) {
10395
10521
  throw new CliError(`Cannot read file ${file}: ${err instanceof Error ? err.message : String(err)}`);
10396
10522
  }
@@ -12319,6 +12445,13 @@ honouring the server's \`retry:\` field), and no event is printed twice.
12319
12445
  debug, or a comma list of updates/messages/debug
12320
12446
  (\`values\` never combines). A host that does not serve
12321
12447
  the mode answers 400 unsupported_stream_mode.
12448
+ --idle-timeout-ms N a connection that delivers no bytes for N ms (default
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.
12452
+ Streams are read from the stream origin: --stream-base-url (global), else the
12453
+ host's advertised \`streamBase\` (https only), else --base-url \u2014 some front
12454
+ doors buffer event streams entirely (e.g. a CDN rewrite).
12322
12455
  Exit 0 when the run completes, 1 when it fails or is cancelled. \`runs list\` pages with --cursor: pass the
12323
12456
  \`nextCursor\` the previous page printed.
12324
12457
 
@@ -12541,7 +12674,7 @@ async function runRunsBulkCancel(ctx, argv) {
12541
12674
  return 0;
12542
12675
  }
12543
12676
  async function runRunsEvents(ctx, argv) {
12544
- const { options, positionals } = parseOptions(argv, { bool: ["--help", "--follow", "--no-stream"], value: ["--since", "--limit", "--last-event-id", "--stream-mode", "--timeout-ms"] });
12677
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--follow", "--no-stream"], value: ["--since", "--limit", "--last-event-id", "--stream-mode", "--timeout-ms", "--idle-timeout-ms"] });
12545
12678
  if (options.help || positionals.length !== 1) {
12546
12679
  write(ctx.io.stdout, "Usage: openwop runs events <runId> [--since <sequence>] [--limit <n>] [--json]\n openwop runs events <runId> --follow [--since <sequence> | --last-event-id <id>] [--stream-mode <mode>] [--no-stream] [--json]\n");
12547
12680
  return options.help ? 0 : 2;
@@ -12579,6 +12712,7 @@ async function followRunEvents(ctx, runId, options) {
12579
12712
  ...start,
12580
12713
  useStream: !options.noStream,
12581
12714
  timeoutMs,
12715
+ ...options.idleTimeoutMs !== void 0 ? { idleTimeoutMs: nonNegativeInt(options.idleTimeoutMs, "--idle-timeout-ms") } : {},
12582
12716
  onEvent: (ev) => {
12583
12717
  if (isTerminalRunEvent(ev)) terminalType = canonicalEventType(ev.type);
12584
12718
  if (ctx.json) {
@@ -12818,10 +12952,15 @@ async function runRunsRedrive(ctx, argv) {
12818
12952
  })), ["runId", "redriveRunId", "error"]));
12819
12953
  return anyFailed ? 1 : 0;
12820
12954
  }
12955
+ function nonNegativeInt(value, flag) {
12956
+ const n = Number(value);
12957
+ if (!Number.isInteger(n) || n < 0) throw new CliError(`${flag} must be a non-negative integer (got ${String(value)})`);
12958
+ return n;
12959
+ }
12821
12960
 
12822
12961
  // src/cli/chat.ts
12823
12962
  import { randomUUID as randomUUID2 } from "node:crypto";
12824
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
12963
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
12825
12964
  var CHAT_BASE = "/v1/host/openwop-app/chat";
12826
12965
  var CHAT_SUBCOMMANDS = ["repl", "sessions", "session", "messages", "message", "participants", "open", "feedback", "models", "search", "export", "import", "tools"];
12827
12966
  var CHAT_HELP = `Usage:
@@ -13411,7 +13550,7 @@ Requires a signed-in account.
13411
13550
  }
13412
13551
  let data;
13413
13552
  try {
13414
- data = JSON.parse(readFileSync13(String(options.file), "utf8"));
13553
+ data = JSON.parse(readFileSync12(String(options.file), "utf8"));
13415
13554
  } catch (err) {
13416
13555
  throw new CliError(`--file: cannot read JSON from ${options.file} (${err instanceof Error ? err.message : String(err)})`, 2);
13417
13556
  }
@@ -13505,7 +13644,7 @@ Run \`openwop chat tools --help\` for usage.`, 2);
13505
13644
  }
13506
13645
 
13507
13646
  // src/cli/relayShared.ts
13508
- import { existsSync as existsSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync3, rmSync as rmSync2, chmodSync as chmodSync3 } from "node:fs";
13647
+ import { existsSync as existsSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync3, rmSync as rmSync2, chmodSync as chmodSync3 } from "node:fs";
13509
13648
  import { dirname as dirname4, join as join5 } from "node:path";
13510
13649
  var MESSAGING_BASE = "/v1/host/openwop-app/messaging";
13511
13650
  var DEVICE_TOKEN_HEADER = "x-openwop-device-token";
@@ -13516,7 +13655,7 @@ function relayCredsPath(env) {
13516
13655
  function loadRelayConfig(ctx) {
13517
13656
  try {
13518
13657
  const p = relayCredsPath(ctx.env);
13519
- if (existsSync4(p)) return JSON.parse(readFileSync14(p, "utf8"));
13658
+ if (existsSync4(p)) return JSON.parse(readFileSync13(p, "utf8"));
13520
13659
  } catch {
13521
13660
  }
13522
13661
  return {};
@@ -13552,7 +13691,7 @@ function relayLogPath(env) {
13552
13691
  function readRelayRecord(env) {
13553
13692
  try {
13554
13693
  const p = relayPidPath(env);
13555
- return existsSync4(p) ? JSON.parse(readFileSync14(p, "utf8")) : null;
13694
+ return existsSync4(p) ? JSON.parse(readFileSync13(p, "utf8")) : null;
13556
13695
  } catch {
13557
13696
  return null;
13558
13697
  }
@@ -14147,7 +14286,7 @@ Usage: openwop notify <email|sms> --to <addr> --text <msg> [--subject s]`);
14147
14286
 
14148
14287
  // src/cli/relay.ts
14149
14288
  import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
14150
- import { existsSync as existsSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, rmSync as rmSync3 } from "node:fs";
14289
+ import { existsSync as existsSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, rmSync as rmSync3 } from "node:fs";
14151
14290
  import { dirname as dirname5 } from "node:path";
14152
14291
  import { fileURLToPath } from "node:url";
14153
14292
  var RELAY_HELP = `Usage:
@@ -14498,7 +14637,7 @@ async function runRelayLogs(ctx, argv) {
14498
14637
  const r = spawnSync3("tail", ["-f", logPath], { stdio: "inherit" });
14499
14638
  if (r.status === 0 || r.signal) return 0;
14500
14639
  }
14501
- write(ctx.io.stdout, readFileSync15(logPath, "utf8"));
14640
+ write(ctx.io.stdout, readFileSync14(logPath, "utf8"));
14502
14641
  return 0;
14503
14642
  }
14504
14643
 
@@ -14867,7 +15006,7 @@ async function runProvidersTest(ctx, argv) {
14867
15006
  // src/cli/packs.ts
14868
15007
  import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign as ed25519Sign, verify as ed25519Verify } from "node:crypto";
14869
15008
  import { gunzipSync, gzipSync } from "node:zlib";
14870
- import { existsSync as existsSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync5, readdirSync, statSync } from "node:fs";
15009
+ import { existsSync as existsSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync5, readdirSync, statSync } from "node:fs";
14871
15010
  import { join as join6, resolve as resolvePath8 } from "node:path";
14872
15011
  import { homedir as homedir3 } from "node:os";
14873
15012
  var PACKS_HELP = `Usage:
@@ -15124,7 +15263,7 @@ async function runPacksPublish(ctx, argv) {
15124
15263
  if (!existsSync6(manifestPath)) {
15125
15264
  throw new CliError(`No pack.json found in ${absPackDir}.`);
15126
15265
  }
15127
- const manifest = JSON.parse(readFileSync16(manifestPath, "utf8"));
15266
+ const manifest = JSON.parse(readFileSync15(manifestPath, "utf8"));
15128
15267
  const name = manifest.name;
15129
15268
  const version = manifest.version;
15130
15269
  if (!name || !version) throw new CliError('pack.json must declare both "name" and "version".');
@@ -15137,11 +15276,11 @@ async function runPacksPublish(ctx, argv) {
15137
15276
  let privateKey;
15138
15277
  let ephemeralPublicB64 = null;
15139
15278
  if (options.key) {
15140
- privateKey = createPrivateKey({ key: readFileSync16(resolvePath8(ctx.cwd, options.key), "utf8"), format: "pem" });
15279
+ privateKey = createPrivateKey({ key: readFileSync15(resolvePath8(ctx.cwd, options.key), "utf8"), format: "pem" });
15141
15280
  } else {
15142
15281
  const conventional = join6(homedir3(), ".openwop-keys", `${keyId}.private.pem`);
15143
15282
  if (existsSync6(conventional)) {
15144
- privateKey = createPrivateKey({ key: readFileSync16(conventional, "utf8"), format: "pem" });
15283
+ privateKey = createPrivateKey({ key: readFileSync15(conventional, "utf8"), format: "pem" });
15145
15284
  } else {
15146
15285
  const kp = generateKeyPairSync("ed25519");
15147
15286
  privateKey = kp.privateKey;
@@ -15220,7 +15359,7 @@ async function runPacksYank(ctx, argv) {
15220
15359
  throw new CliError(`Version manifest not found: ${manifestPath}
15221
15360
  (packs yank edits a local registry checkout; the published change lands via PR.)`);
15222
15361
  }
15223
- const manifest = JSON.parse(readFileSync16(manifestPath, "utf8"));
15362
+ const manifest = JSON.parse(readFileSync15(manifestPath, "utf8"));
15224
15363
  const newValue = !options.undo;
15225
15364
  if (Boolean(manifest.yanked) === newValue) {
15226
15365
  writeLine(ctx.io.stdout, `${name}@${version} is already ${newValue ? "yanked" : "un-yanked"}; no change.`);
@@ -15277,11 +15416,11 @@ function walkPackDir(packDir) {
15277
15416
  const st = statSync(full);
15278
15417
  if (st.isFile()) {
15279
15418
  if (!ALLOWED_TOPS.has(entry)) continue;
15280
- entries2.push({ name: entry, content: readFileSync16(full) });
15419
+ entries2.push({ name: entry, content: readFileSync15(full) });
15281
15420
  } else if (st.isDirectory() && ALLOWED_DIRS.has(entry)) {
15282
15421
  for (const f of readdirSync(full).sort()) {
15283
15422
  if (!f.endsWith(".json") && !f.endsWith(".pem") && !f.endsWith(".sig")) continue;
15284
- entries2.push({ name: `${entry}/${f}`, content: readFileSync16(join6(full, f)) });
15423
+ entries2.push({ name: `${entry}/${f}`, content: readFileSync15(join6(full, f)) });
15285
15424
  }
15286
15425
  }
15287
15426
  }
@@ -15450,6 +15589,8 @@ async function protocolChecks(ctx) {
15450
15589
  } else if (floor.required !== void 0) {
15451
15590
  rows.push(warn("min client", `host minClientVersion ${floor.required} is not a <major>.<minor> version`));
15452
15591
  }
15592
+ const stream = streamOrigin(ctx);
15593
+ rows.push(stream.source === "base" ? ok("stream origin", `event streams use --base-url (${stream.origin})`) : ok("stream origin", `event streams use ${stream.origin} \u2014 ${STREAM_SOURCE_LABEL[stream.source]}; set --stream-base-url to your --base-url to keep them there`));
15453
15594
  return rows;
15454
15595
  }
15455
15596
 
@@ -15841,7 +15982,7 @@ async function runGovernanceEgress(ctx, argv) {
15841
15982
 
15842
15983
  // src/cli/demo.ts
15843
15984
  import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
15844
- import { existsSync as existsSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync12, createReadStream, openSync as openSync2, statSync as statSync2, mkdirSync as mkdirSync6, rmSync as rmSync4, chmodSync as chmodSync4, watch } from "node:fs";
15985
+ import { existsSync as existsSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync12, createReadStream, openSync as openSync2, statSync as statSync2, mkdirSync as mkdirSync6, rmSync as rmSync4, chmodSync as chmodSync4, watch } from "node:fs";
15845
15986
  import { join as join7, dirname as dirname7, relative as relative2 } from "node:path";
15846
15987
  var DEMO_HELP = `Usage:
15847
15988
  openwop demo status [--json]
@@ -16208,7 +16349,7 @@ async function runDemoLogs(ctx, argv) {
16208
16349
  return 2;
16209
16350
  }
16210
16351
  const lineCount = Number(options.lines ?? 50);
16211
- const existing = readFileSync17(logPath, "utf8");
16352
+ const existing = readFileSync16(logPath, "utf8");
16212
16353
  const lines = existing.split("\n");
16213
16354
  const tail = lines.slice(Math.max(0, lines.length - lineCount - 1));
16214
16355
  write(ctx.io.stdout, tail.join("\n"));
@@ -17294,6 +17435,24 @@ async function runPromptsGet(ctx, argv) {
17294
17435
  return 0;
17295
17436
  }
17296
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
+
17297
17456
  // src/cli/connections.ts
17298
17457
  var CONN = "/v1/host/openwop-app/connections/:id";
17299
17458
  var CONNECTIONS_ROUTES = [
@@ -17369,15 +17528,7 @@ Examples:
17369
17528
  `;
17370
17529
  var SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
17371
17530
  function redact(value) {
17372
- if (Array.isArray(value)) return value.map(redact);
17373
- if (value && typeof value === "object") {
17374
- const out = {};
17375
- for (const [k, v] of Object.entries(value)) {
17376
- out[k] = SECRET_KEY.test(k) ? "[redacted]" : redact(v);
17377
- }
17378
- return out;
17379
- }
17380
- return value;
17531
+ return redactSecrets(value, { secret: SECRET_KEY });
17381
17532
  }
17382
17533
  var TOP = ["list", "get", "test", "authorize", "oauth-clients", "oauth-client"];
17383
17534
  async function runConnections(ctx, argv) {
@@ -17581,7 +17732,8 @@ Run \`openwop connections --help\` for usage.`);
17581
17732
  } catch (err) {
17582
17733
  gate4042(err);
17583
17734
  }
17584
- 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);
17585
17737
  if (action === "get") {
17586
17738
  if (positionals.length !== 1) {
17587
17739
  write(ctx.io.stdout, "Usage: openwop connections oauth-clients get <provider> [--json]\n");
@@ -18873,7 +19025,7 @@ async function runWfList(ctx, argv) {
18873
19025
  writeLine(ctx.io.stdout, formatTable(rows, ["workforceId", "name", "businessFunction", "status", "autonomy", "agents"]));
18874
19026
  return 0;
18875
19027
  }
18876
- async function runWfReadOne(ctx, argv, suffix, render3) {
19028
+ async function runWfReadOne(ctx, argv, suffix, render2) {
18877
19029
  const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
18878
19030
  if (options.help || positionals.length !== 1) {
18879
19031
  write(ctx.io.stdout, WORKFORCES_HELP);
@@ -18890,7 +19042,7 @@ async function runWfReadOne(ctx, argv, suffix, render3) {
18890
19042
  writeJson(ctx.io.stdout, res.body);
18891
19043
  return 0;
18892
19044
  }
18893
- render3(ctx, res.body ?? {}, positionals[0]);
19045
+ render2(ctx, res.body ?? {}, positionals[0]);
18894
19046
  return 0;
18895
19047
  }
18896
19048
  function renderWorkforce(ctx, w, id) {
@@ -19097,15 +19249,7 @@ Examples:
19097
19249
  var BASE11 = "/v1/host/openwop-app/auth";
19098
19250
  var SECRET_KEY2 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential|bearer|assertion|certificate|cert|pem/i;
19099
19251
  function redact2(value) {
19100
- if (Array.isArray(value)) return value.map(redact2);
19101
- if (value && typeof value === "object") {
19102
- const out = {};
19103
- for (const [k, v] of Object.entries(value)) {
19104
- out[k] = SECRET_KEY2.test(k) ? "[redacted]" : redact2(v);
19105
- }
19106
- return out;
19107
- }
19108
- return value;
19252
+ return redactSecrets(value, { secret: SECRET_KEY2 });
19109
19253
  }
19110
19254
  async function authRequest(ctx, path, options, notConfiguredMsg) {
19111
19255
  try {
@@ -20164,7 +20308,7 @@ function truncate4(s, n) {
20164
20308
  }
20165
20309
 
20166
20310
  // src/cli/portability.ts
20167
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "node:fs";
20311
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "node:fs";
20168
20312
  import { resolve as resolve4 } from "node:path";
20169
20313
  var EXPORT_HELP = `Usage:
20170
20314
  openwop export [--kinds <kind>]... [--out <file>] [--json]
@@ -20217,15 +20361,7 @@ Examples:
20217
20361
  var SECRET_KEY3 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
20218
20362
  var REF_ONLY_KEY = /^secretsToRebind$/;
20219
20363
  function redact3(value) {
20220
- if (Array.isArray(value)) return value.map(redact3);
20221
- if (value && typeof value === "object") {
20222
- const out = {};
20223
- for (const [k, v] of Object.entries(value)) {
20224
- out[k] = SECRET_KEY3.test(k) && !REF_ONLY_KEY.test(k) ? "[redacted]" : redact3(v);
20225
- }
20226
- return out;
20227
- }
20228
- return value;
20364
+ return redactSecrets(value, { secret: SECRET_KEY3, keep: REF_ONLY_KEY });
20229
20365
  }
20230
20366
  async function ensureAdvertised5(ctx) {
20231
20367
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
@@ -20302,7 +20438,7 @@ async function runImport(ctx, argv) {
20302
20438
  }
20303
20439
  let bundle;
20304
20440
  try {
20305
- const raw = readFileSync18(resolve4(ctx.cwd, positionals[0]), "utf8");
20441
+ const raw = readFileSync17(resolve4(ctx.cwd, positionals[0]), "utf8");
20306
20442
  bundle = JSON.parse(raw);
20307
20443
  } catch (err) {
20308
20444
  throw new CliError(`import: could not read bundle file ${positionals[0]} \u2014 ${err instanceof Error ? err.message : "invalid JSON"}`, 2);
@@ -21074,7 +21210,7 @@ Run \`openwop knowledge-sync --help\` for usage.`);
21074
21210
  }
21075
21211
 
21076
21212
  // src/cli/entities.ts
21077
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync14 } from "node:fs";
21213
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "node:fs";
21078
21214
  import { resolve as resolvePath9 } from "node:path";
21079
21215
  var BASE13 = "/v1/host/openwop-app/entities";
21080
21216
  var PUBLIC_BASE2 = "/v1/host/openwop-app/public-entities";
@@ -21310,7 +21446,7 @@ async function types(ctx, sub, pos, o, project4, q) {
21310
21446
  if (!name || !o.file) return usage5(ctx, "types import <type> --file <file.ndjson|file.json>");
21311
21447
  let raw;
21312
21448
  try {
21313
- raw = readFileSync19(resolvePath9(ctx.cwd, String(o.file)), "utf8");
21449
+ raw = readFileSync18(resolvePath9(ctx.cwd, String(o.file)), "utf8");
21314
21450
  } catch (err) {
21315
21451
  throw new CliError(`Cannot read --file ${String(o.file)}: ${err instanceof Error ? err.message : String(err)}`);
21316
21452
  }
@@ -21881,7 +22017,7 @@ async function startJob(ctx, path, body) {
21881
22017
  const res = await requestJson(ctx, path, { method: "POST", body });
21882
22018
  return renderDone(ctx, res.body, `Video job ${res.body?.jobId ?? ""} ${res.body?.status ?? "started"}.`);
21883
22019
  } catch (err) {
21884
- 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;
21885
22021
  if (err instanceof HttpError && b && b.status === "failed") {
21886
22022
  if (ctx.json) writeJson(ctx.io.stdout, b);
21887
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" : ""}`);
@@ -22744,8 +22880,7 @@ async function followEvents(ctx, requestedPath, options) {
22744
22880
  const max = options.max !== void 0 ? Number(options.max) : Infinity;
22745
22881
  const timeoutMs = options.timeout !== void 0 ? Number(options.timeout) * 1e3 : Infinity;
22746
22882
  if (Number.isNaN(max) || Number.isNaN(timeoutMs)) throw new CliError("--max and --timeout must be numbers.");
22747
- const { path, headers } = await resolveRequest(ctx, requestedPath, { accept: "text/event-stream" });
22748
- const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
22883
+ const { url, headers } = await resolveStreamRequest(ctx, requestedPath, {});
22749
22884
  const controller = new AbortController();
22750
22885
  const timer = Number.isFinite(timeoutMs) ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
22751
22886
  let seen = 0;
@@ -26552,7 +26687,7 @@ function renderKnowledge(ctx, agentId, k) {
26552
26687
  }
26553
26688
 
26554
26689
  // src/cli/compatEndpoints.ts
26555
- import { readFileSync as readFileSync20 } from "node:fs";
26690
+ import { readFileSync as readFileSync19 } from "node:fs";
26556
26691
  var BASE32 = "/v1/host/openwop-app/compat-endpoints";
26557
26692
  var COMPAT_ENDPOINTS_HELP = `Usage:
26558
26693
  openwop compat-endpoints list --org <orgId> [--json]
@@ -26666,7 +26801,7 @@ function readApiKey(ctx, options) {
26666
26801
  if (options.apiKeyFile && options.apiKeyEnv) throw new CliError("Pass only one of --api-key-file / --api-key-env.", 2);
26667
26802
  if (options.apiKeyFile) {
26668
26803
  try {
26669
- return readFileSync20(String(options.apiKeyFile), "utf8").trim() || void 0;
26804
+ return readFileSync19(String(options.apiKeyFile), "utf8").trim() || void 0;
26670
26805
  } catch (err) {
26671
26806
  throw new CliError(`Could not read ${options.apiKeyFile}: ${err instanceof Error ? err.message : String(err)}`, 2);
26672
26807
  }
@@ -27868,7 +28003,7 @@ Run \`openwop scheduled-chats --help\` for usage.`, 2);
27868
28003
  }
27869
28004
 
27870
28005
  // src/cli/voice.ts
27871
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync19 } from "node:fs";
28006
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync19 } from "node:fs";
27872
28007
  var SESSION = "/v1/host/openwop-app/voice/session";
27873
28008
  var REALTIME = "/v1/host/openwop-app/voice/realtime";
27874
28009
  var AUDIO_CHUNK_BYTES = 192 * 1024;
@@ -27973,7 +28108,7 @@ Run \`openwop voice --help\` for usage.`, 2);
27973
28108
  if (options.file) {
27974
28109
  let bytes;
27975
28110
  try {
27976
- bytes = readFileSync21(String(options.file));
28111
+ bytes = readFileSync20(String(options.file));
27977
28112
  } catch (err) {
27978
28113
  throw new CliError(`--file: cannot read ${options.file} (${err instanceof Error ? err.message : String(err)})`, 2);
27979
28114
  }
@@ -28115,7 +28250,7 @@ async function runRealtime(ctx, argv) {
28115
28250
  if (!options.sdpFile) throw new CliError("voice realtime connect requires --sdp-file <offer.sdp>.", 2);
28116
28251
  let sdp;
28117
28252
  try {
28118
- sdp = readFileSync21(String(options.sdpFile), "utf8");
28253
+ sdp = readFileSync20(String(options.sdpFile), "utf8");
28119
28254
  } catch (err) {
28120
28255
  throw new CliError(`--sdp-file: cannot read ${options.sdpFile} (${err instanceof Error ? err.message : String(err)})`, 2);
28121
28256
  }
@@ -28170,10 +28305,10 @@ async function runRealtimeConfig(ctx, argv) {
28170
28305
  write(ctx.io.stdout, VOICE_HELP);
28171
28306
  return 0;
28172
28307
  }
28173
- const render3 = (b) => writeFields(ctx, [["provider", b?.provider], ["credentialRef", b?.credentialRef], ["model", b?.model]]);
28308
+ const render2 = (b) => writeFields(ctx, [["provider", b?.provider], ["credentialRef", b?.credentialRef], ["model", b?.model]]);
28174
28309
  if (sub === "get") {
28175
28310
  const res = await requestJson(ctx, `${REALTIME}/config`);
28176
- return emit(ctx, res.body, () => render3(res.body));
28311
+ return emit(ctx, res.body, () => render2(res.body));
28177
28312
  }
28178
28313
  if (sub === "set") {
28179
28314
  if (!options.provider && options.credentialRef === void 0 && options.model === void 0) {
@@ -28188,7 +28323,7 @@ async function runRealtimeConfig(ctx, argv) {
28188
28323
  const res = await requestJson(ctx, `${REALTIME}/config`, { method: "PUT", body });
28189
28324
  return emit(ctx, res.body, () => {
28190
28325
  writeLine(ctx.io.stdout, "Updated realtime voice config.");
28191
- render3(res.body);
28326
+ render2(res.body);
28192
28327
  });
28193
28328
  }
28194
28329
  throw new CliError(`Unknown voice realtime config command: ${sub}
@@ -28196,7 +28331,7 @@ Run \`openwop voice --help\` for usage.`, 2);
28196
28331
  }
28197
28332
 
28198
28333
  // src/cli/ai.ts
28199
- import { readFileSync as readFileSync22 } from "node:fs";
28334
+ import { readFileSync as readFileSync21 } from "node:fs";
28200
28335
  var AI = "/v1/host/openwop-app/ai";
28201
28336
  var AI_HELP = `Usage:
28202
28337
  openwop ai call (--message <text> | --messages-json <json>) [--provider p] [--model m] [--json]
@@ -28294,7 +28429,7 @@ async function runAi(ctx, argv) {
28294
28429
  let value;
28295
28430
  if (options.valueFile) {
28296
28431
  try {
28297
- value = readFileSync22(String(options.valueFile), "utf8").trim();
28432
+ value = readFileSync21(String(options.valueFile), "utf8").trim();
28298
28433
  } catch (err) {
28299
28434
  throw new CliError(`--value-file: cannot read ${options.valueFile} (${err instanceof Error ? err.message : String(err)})`, 2);
28300
28435
  }
@@ -29501,6 +29636,7 @@ async function runCli(argv, options = {}) {
29501
29636
  parsed.globals.baseUrl ?? env.OPENWOP_BASE_URL ?? savedConfig?.host?.baseUrl ?? DEFAULT_BASE_URL
29502
29637
  ),
29503
29638
  apiKey: parsed.globals.apiKey ?? env.OPENWOP_API_KEY ?? savedConfig?.host?.apiKey,
29639
+ streamBaseUrl: explicitStreamBase(parsed.globals.streamBaseUrl ?? env.OPENWOP_STREAM_BASE_URL ?? savedConfig?.host?.streamBaseUrl),
29504
29640
  json: parsed.globals.json,
29505
29641
  quiet: parsed.globals.quiet,
29506
29642
  verbose: parsed.globals.verbose,
@@ -30061,6 +30197,9 @@ Usage:
30061
30197
 
30062
30198
  Global options:
30063
30199
  --base-url <url> Host base URL (default: --base-url > OPENWOP_BASE_URL > saved config > http://localhost:8080)
30200
+ --stream-base-url <url>
30201
+ Where to read event streams (default: OPENWOP_STREAM_BASE_URL >
30202
+ saved config > the host's advertised https streamBase > --base-url)
30064
30203
  --api-key <key> Bearer API key (default: OPENWOP_API_KEY > saved config > sample-token for localhost)
30065
30204
  --profile <name> Use a named config profile (~/.openwop-<name>); also OPENWOP_PROFILE
30066
30205
  --json Print machine-readable JSON where supported
@@ -30330,6 +30469,7 @@ Examples:
30330
30469
  openwop packs install community.openwop-team.demo --version 0.1.0
30331
30470
  `;
30332
30471
  export {
30472
+ BASE_SECRET_KEY,
30333
30473
  CLI_PROTOCOL_VERSION_BY_MAJOR,
30334
30474
  COMMAND_NAMES,
30335
30475
  DEFAULT_BASE_URL,
@@ -30353,6 +30493,7 @@ export {
30353
30493
  describeHttpError,
30354
30494
  detectChannelAvailability,
30355
30495
  errorEnvelope,
30496
+ explicitStreamBase,
30356
30497
  extractAssistantText,
30357
30498
  extractGlobalOptions,
30358
30499
  findRepoRoot,
@@ -30361,6 +30502,7 @@ export {
30361
30502
  hostRootFor,
30362
30503
  hostRootsFrom,
30363
30504
  idempotencyHeaders,
30505
+ isNoCredentialChallenge,
30364
30506
  isTerminalRunEvent,
30365
30507
  openwopHomeDir,
30366
30508
  parseDiscordMessage,
@@ -30373,10 +30515,14 @@ export {
30373
30515
  projectTenantBoundId,
30374
30516
  readConfigSafe,
30375
30517
  readDaemonRecord,
30518
+ redactSecrets,
30376
30519
  renderEvent,
30520
+ requestNormativeOrHost,
30521
+ resolveStreamRequest,
30377
30522
  runCli,
30378
30523
  saveConfig,
30379
30524
  startInboundReceive,
30525
+ streamBaseFrom,
30380
30526
  streamRunEvents,
30381
30527
  submitTurn,
30382
30528
  summarizeCapabilities