@openwop/cli 1.2.0 → 1.2.2
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 +1 -1
- package/dist/cli.js +101 -110
- package/dist/openwop.js +99 -110
- package/package.json +1 -1
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.
|
|
581
|
+
version: "1.2.2",
|
|
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
|
-
|
|
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}` : ""}
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
1787
|
-
|
|
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 (!(
|
|
1813
|
+
const arm = (after = ms2) => {
|
|
1814
|
+
if (!(after > 0)) return;
|
|
1792
1815
|
if (timer !== void 0) clearTimeout(timer);
|
|
1793
|
-
timer = setTimeout(expire,
|
|
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
|
|
1832
|
+
out.error(new StreamSilent(ms2, false));
|
|
1805
1833
|
source.cancel().catch(() => {
|
|
1806
1834
|
});
|
|
1807
1835
|
};
|
|
@@ -1976,7 +2004,7 @@ function formatCheckTable(checks) {
|
|
|
1976
2004
|
|
|
1977
2005
|
// src/cli/routeKit.ts
|
|
1978
2006
|
var errText2 = (err) => err instanceof Error ? err.message : String(err);
|
|
1979
|
-
var
|
|
2007
|
+
var MSG = {
|
|
1980
2008
|
required: (flag, usage6) => `${usage6}
|
|
1981
2009
|
${flag} is required.`,
|
|
1982
2010
|
refusal: (words, positionals) => `Refusing to ${words.join(" ")} ${positionals.join(" ")} without --yes.`.replace(/\s+/g, " ").trim(),
|
|
@@ -2055,14 +2083,14 @@ function setPath(target, key, value) {
|
|
|
2055
2083
|
}
|
|
2056
2084
|
node[parts[parts.length - 1]] = value;
|
|
2057
2085
|
}
|
|
2058
|
-
function coerce(f, raw
|
|
2086
|
+
function coerce(f, raw) {
|
|
2059
2087
|
const t = f.type ?? "string";
|
|
2060
2088
|
if (t === "boolean") return raw;
|
|
2061
2089
|
if (t === "bool") {
|
|
2062
2090
|
const s2 = String(raw);
|
|
2063
2091
|
if (s2 === "true" || s2 === "yes" || s2 === "1") return true;
|
|
2064
2092
|
if (s2 === "false" || s2 === "no" || s2 === "0") return false;
|
|
2065
|
-
throw new CliError(
|
|
2093
|
+
throw new CliError(MSG.invalidBool(f.flag, s2), 2);
|
|
2066
2094
|
}
|
|
2067
2095
|
if (t === "list") return Array.isArray(raw) ? raw.map(String) : [String(raw)];
|
|
2068
2096
|
if (t === "map") {
|
|
@@ -2079,7 +2107,7 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2079
2107
|
const s = String(raw);
|
|
2080
2108
|
if (t === "number") {
|
|
2081
2109
|
const n = Number(s);
|
|
2082
|
-
if (
|
|
2110
|
+
if (s.trim() === "" || !Number.isFinite(n)) throw new CliError(MSG.invalidNumber(f.flag, s), 2);
|
|
2083
2111
|
return n;
|
|
2084
2112
|
}
|
|
2085
2113
|
if (t === "json-file") {
|
|
@@ -2087,7 +2115,7 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2087
2115
|
try {
|
|
2088
2116
|
text = readFileSync4(s, "utf8");
|
|
2089
2117
|
} catch (err) {
|
|
2090
|
-
throw new CliError(
|
|
2118
|
+
throw new CliError(MSG.unreadable(f.flag, s, err), 2);
|
|
2091
2119
|
}
|
|
2092
2120
|
try {
|
|
2093
2121
|
return JSON.parse(text);
|
|
@@ -2099,14 +2127,14 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2099
2127
|
try {
|
|
2100
2128
|
return readFileSync4(s).toString("base64");
|
|
2101
2129
|
} catch (err) {
|
|
2102
|
-
throw new CliError(
|
|
2130
|
+
throw new CliError(MSG.unreadable(f.flag, s, err), 2);
|
|
2103
2131
|
}
|
|
2104
2132
|
}
|
|
2105
2133
|
if (t === "file") {
|
|
2106
2134
|
try {
|
|
2107
2135
|
return readFileSync4(s, "utf8").replace(/\r?\n$/, "");
|
|
2108
2136
|
} catch (err) {
|
|
2109
|
-
throw new CliError(
|
|
2137
|
+
throw new CliError(MSG.unreadable(f.flag, s, err), 2);
|
|
2110
2138
|
}
|
|
2111
2139
|
}
|
|
2112
2140
|
if (t === "csv") return s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
@@ -2114,19 +2142,19 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2114
2142
|
try {
|
|
2115
2143
|
return JSON.parse(s);
|
|
2116
2144
|
} catch {
|
|
2117
|
-
throw new CliError(
|
|
2145
|
+
throw new CliError(MSG.invalidJson(f.flag), 2);
|
|
2118
2146
|
}
|
|
2119
2147
|
}
|
|
2120
2148
|
return s;
|
|
2121
2149
|
}
|
|
2122
|
-
function readBodyOptions(options
|
|
2150
|
+
function readBodyOptions(options) {
|
|
2123
2151
|
let raw;
|
|
2124
2152
|
if (options.bodyFile !== void 0) {
|
|
2125
2153
|
if (String(options.bodyFile).trim() === "") throw new CliError("--body-file needs a path (got an empty value).", 2);
|
|
2126
2154
|
try {
|
|
2127
2155
|
raw = readFileSync4(String(options.bodyFile), "utf8");
|
|
2128
2156
|
} catch (err) {
|
|
2129
|
-
throw new CliError(
|
|
2157
|
+
throw new CliError(MSG.unreadableBody(String(options.bodyFile), err), 2);
|
|
2130
2158
|
}
|
|
2131
2159
|
} else if (options.body !== void 0) {
|
|
2132
2160
|
raw = String(options.body);
|
|
@@ -2136,9 +2164,9 @@ function readBodyOptions(options, msg) {
|
|
|
2136
2164
|
try {
|
|
2137
2165
|
parsed = JSON.parse(raw);
|
|
2138
2166
|
} catch {
|
|
2139
|
-
throw new CliError(
|
|
2167
|
+
throw new CliError(MSG.invalidBodyJson, 2);
|
|
2140
2168
|
}
|
|
2141
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError(
|
|
2169
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError(MSG.bodyNotObject, 2);
|
|
2142
2170
|
return parsed;
|
|
2143
2171
|
}
|
|
2144
2172
|
function hostError(err) {
|
|
@@ -2149,7 +2177,6 @@ function hostError(err) {
|
|
|
2149
2177
|
${hint}` : line, code);
|
|
2150
2178
|
}
|
|
2151
2179
|
async function runRoute(ctx, group, cmd, argv) {
|
|
2152
|
-
const msg = { ...DEFAULT_MESSAGES, ...cmd.messages ?? {} };
|
|
2153
2180
|
const rest = argv.slice(cmd.words.length);
|
|
2154
2181
|
const fields = [...cmd.query ?? [], ...cmd.body ?? []];
|
|
2155
2182
|
const bool = [...COMMON_BOOL, ...fields.filter((f) => f.type === "boolean").flatMap((f) => [f.flag, f.flag.replace(/^--/, "--no-")])];
|
|
@@ -2162,8 +2189,7 @@ async function runRoute(ctx, group, cmd, argv) {
|
|
|
2162
2189
|
const multi = fields.filter(isMulti).map((f) => f.flag);
|
|
2163
2190
|
const { options, positionals } = parseOptions(rest, { bool, value, multi });
|
|
2164
2191
|
if (options.help) {
|
|
2165
|
-
|
|
2166
|
-
else writeLine(ctx.io.stdout, `Usage: ${usageLine(group, cmd)}
|
|
2192
|
+
writeLine(ctx.io.stdout, `Usage: ${usageLine(group, cmd)}
|
|
2167
2193
|
|
|
2168
2194
|
${cmd.summary}
|
|
2169
2195
|
Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
@@ -2171,12 +2197,12 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2171
2197
|
}
|
|
2172
2198
|
const template = templates(cmd).find((p) => positionalNames(cmd, p).length === positionals.length);
|
|
2173
2199
|
if (!template) {
|
|
2174
|
-
write(ctx.io.stderr,
|
|
2200
|
+
write(ctx.io.stderr, `Usage: ${usageLine(group, cmd)}
|
|
2175
2201
|
`);
|
|
2176
2202
|
return 2;
|
|
2177
2203
|
}
|
|
2178
2204
|
const path = fillPath(cmd, template, positionals, options.org);
|
|
2179
|
-
const requiredError = (f) => new CliError(
|
|
2205
|
+
const requiredError = (f) => new CliError(MSG.required(f.flag, usageLine(group, cmd)), 2);
|
|
2180
2206
|
const optionOf = (f) => {
|
|
2181
2207
|
const name = f.flag.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2182
2208
|
if (f.type === "boolean") {
|
|
@@ -2194,7 +2220,7 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2194
2220
|
if (f.required) throw requiredError(f);
|
|
2195
2221
|
continue;
|
|
2196
2222
|
}
|
|
2197
|
-
const c = coerce(f, v
|
|
2223
|
+
const c = coerce(f, v);
|
|
2198
2224
|
if (f.type === "csv") qs2.set(f.key, c.join(","));
|
|
2199
2225
|
else if (Array.isArray(c)) c.forEach((x) => qs2.append(f.key, String(x)));
|
|
2200
2226
|
else qs2.set(f.key, c !== null && typeof c === "object" ? JSON.stringify(c) : String(c));
|
|
@@ -2202,7 +2228,7 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2202
2228
|
const url = qs2.size ? `${path}${path.includes("?") ? "&" : "?"}${qs2.toString()}` : path;
|
|
2203
2229
|
const confirm2 = cmd.confirm ?? cmd.method === "DELETE";
|
|
2204
2230
|
const gate = () => {
|
|
2205
|
-
if (confirm2 && !options.yes) throw new CliError(
|
|
2231
|
+
if (confirm2 && !options.yes) throw new CliError(MSG.refusal(cmd.words, positionals), 2);
|
|
2206
2232
|
};
|
|
2207
2233
|
if (!cmd.validateFirst) gate();
|
|
2208
2234
|
const isWrite = cmd.method !== "GET" && cmd.method !== "DELETE";
|
|
@@ -2210,10 +2236,10 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2210
2236
|
let explicit = {};
|
|
2211
2237
|
const overlay = {};
|
|
2212
2238
|
if (takesBody) {
|
|
2213
|
-
explicit = readBodyOptions(options
|
|
2239
|
+
explicit = readBodyOptions(options);
|
|
2214
2240
|
for (const f of cmd.body ?? []) {
|
|
2215
2241
|
const v = optionOf(f);
|
|
2216
|
-
if (v !== void 0) setPath(overlay, f.key, coerce(f, v
|
|
2242
|
+
if (v !== void 0) setPath(overlay, f.key, coerce(f, v));
|
|
2217
2243
|
else if (cmd.validateFirst && f.required && getPath(explicit, f.key) === void 0) throw requiredError(f);
|
|
2218
2244
|
}
|
|
2219
2245
|
}
|
|
@@ -2250,7 +2276,7 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2250
2276
|
render(ctx, { ...cmd, method: "GET" }, positionals, err.status, err.body);
|
|
2251
2277
|
return 1;
|
|
2252
2278
|
}
|
|
2253
|
-
if (err instanceof HttpError
|
|
2279
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
2254
2280
|
throw err;
|
|
2255
2281
|
}
|
|
2256
2282
|
}
|
|
@@ -2261,7 +2287,7 @@ async function send(ctx, cmd, url, init) {
|
|
|
2261
2287
|
try {
|
|
2262
2288
|
return await requestJson(ctx, url, { method: init.method, ...init.body !== void 0 ? { body: init.body } : {}, ...cmd.anonymous ? { auth: false } : {} });
|
|
2263
2289
|
} catch (err) {
|
|
2264
|
-
if (err instanceof HttpError
|
|
2290
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
2265
2291
|
throw err;
|
|
2266
2292
|
}
|
|
2267
2293
|
}
|
|
@@ -2311,7 +2337,7 @@ function render(ctx, cmd, positionals, status, rawBody) {
|
|
|
2311
2337
|
return;
|
|
2312
2338
|
}
|
|
2313
2339
|
}
|
|
2314
|
-
if (cmd.method === "GET"
|
|
2340
|
+
if (cmd.method === "GET") {
|
|
2315
2341
|
writeJson(ctx.io.stdout, body);
|
|
2316
2342
|
return;
|
|
2317
2343
|
}
|
|
@@ -2650,7 +2676,8 @@ async function runInterrupts(ctx, argv) {
|
|
|
2650
2676
|
if (b.key) writeLine(ctx.io.stdout, `key: ${b.key}`);
|
|
2651
2677
|
writeLine(ctx.io.stdout, `resolved: ${b.resolved ? "yes" : "no"}`);
|
|
2652
2678
|
if (b.expiresAt) writeLine(ctx.io.stdout, `expiresAt: ${b.expiresAt}`);
|
|
2653
|
-
|
|
2679
|
+
const prompt = b.data && typeof b.data === "object" ? b.data.prompt : void 0;
|
|
2680
|
+
if (typeof prompt === "string") writeLine(ctx.io.stdout, `prompt: ${prompt}`);
|
|
2654
2681
|
if (b.resumeSchema !== void 0) writeLine(ctx.io.stdout, `resumeSchema: ${JSON.stringify(b.resumeSchema)}`);
|
|
2655
2682
|
return 0;
|
|
2656
2683
|
}
|
|
@@ -6282,25 +6309,8 @@ function parseField(spec) {
|
|
|
6282
6309
|
const flag = `--${flagOverride ?? key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`).replace(/_/g, "-")}`;
|
|
6283
6310
|
return { key, flag, type, required };
|
|
6284
6311
|
}
|
|
6285
|
-
function hasOrg(route) {
|
|
6286
|
-
return /:org(?![A-Za-z0-9_])/.test(route);
|
|
6287
|
-
}
|
|
6288
|
-
function paramsOf(route) {
|
|
6289
|
-
return (route.match(/:[A-Za-z][A-Za-z0-9_]*/g) ?? []).map((p) => p.slice(1)).filter((p) => p !== "org");
|
|
6290
|
-
}
|
|
6291
6312
|
function usageLine2(group, spec) {
|
|
6292
|
-
|
|
6293
|
-
if (hasOrg(spec.route)) parts.push("--org <orgId>");
|
|
6294
|
-
const fmt = (f) => {
|
|
6295
|
-
const v = f.type === "bool" ? "true|false" : f.type === "json" ? "json" : f.type === "list" ? "a,b" : f.type === "number" ? "n" : f.type === "file" ? "path" : "v";
|
|
6296
|
-
return f.required ? `${f.flag} <${v}>` : `[${f.flag} <${v}>]`;
|
|
6297
|
-
};
|
|
6298
|
-
for (const f of (spec.query ?? []).map(parseField)) parts.push(fmt(f));
|
|
6299
|
-
for (const f of (spec.body ?? []).map(parseField)) parts.push(fmt(f));
|
|
6300
|
-
if (spec.body || spec.rawBody) parts.push("[--body <json> | --body-file <path>]");
|
|
6301
|
-
if (spec.confirm) parts.push("--yes");
|
|
6302
|
-
parts.push("[--json]");
|
|
6303
|
-
return parts.join(" ");
|
|
6313
|
+
return usageLine(group, toRouteCmd(spec));
|
|
6304
6314
|
}
|
|
6305
6315
|
function buildGroupHelp(group, intro, specs, footer = "") {
|
|
6306
6316
|
const lines = ["Usage:"];
|
|
@@ -6329,10 +6339,7 @@ function toField(spec) {
|
|
|
6329
6339
|
const f = parseField(spec);
|
|
6330
6340
|
return { flag: f.flag, key: f.key, type: TYPES[f.type] ?? "string", required: f.required };
|
|
6331
6341
|
}
|
|
6332
|
-
function toRouteCmd(
|
|
6333
|
-
const usage6 = `Usage: ${usageLine2(group, spec)}
|
|
6334
|
-
${spec.method} ${spec.route} \u2014 ${spec.summary}
|
|
6335
|
-
`;
|
|
6342
|
+
function toRouteCmd(spec) {
|
|
6336
6343
|
const rmwKey = spec.rmwKey;
|
|
6337
6344
|
return {
|
|
6338
6345
|
words: spec.cmd,
|
|
@@ -6352,30 +6359,14 @@ function toRouteCmd(group, spec) {
|
|
|
6352
6359
|
rawText: true,
|
|
6353
6360
|
// every spec, as before — `text` only documents the route
|
|
6354
6361
|
noContent: spec.method === "DELETE" ? "Deleted." : "Done (HTTP {status}).",
|
|
6355
|
-
|
|
6356
|
-
hostErrors: false,
|
|
6357
|
-
validateFirst: true,
|
|
6358
|
-
strictNumbers: true,
|
|
6359
|
-
usageText: usage6,
|
|
6360
|
-
messages: {
|
|
6361
|
-
required: (flag, usageText) => `${flag} is required.
|
|
6362
|
-
${usageText}`,
|
|
6363
|
-
refusal: () => `Refusing to ${spec.summary.charAt(0).toLowerCase()}${spec.summary.slice(1).replace(/\.$/, "")} without --yes.`,
|
|
6364
|
-
invalidNumber: (flag, raw) => `${flag} must be a number, got: ${raw}`,
|
|
6365
|
-
invalidBool: (flag, raw) => `${flag} must be true or false, got: ${raw}`,
|
|
6366
|
-
invalidJson: (flag) => `${flag} must be valid JSON`,
|
|
6367
|
-
unreadable: (flag, path) => `Cannot read ${flag} ${path}`,
|
|
6368
|
-
unreadableBody: (path) => `Cannot read --body-file ${path}`,
|
|
6369
|
-
invalidBodyJson: "--body/--body-file must be valid JSON",
|
|
6370
|
-
bodyNotObject: "--body/--body-file must be a JSON object"
|
|
6371
|
-
}
|
|
6362
|
+
validateFirst: true
|
|
6372
6363
|
};
|
|
6373
6364
|
}
|
|
6374
6365
|
function matchSpec(specs, argv) {
|
|
6375
6366
|
return matchRoute(specs.map((spec) => ({ words: spec.cmd, spec })), argv)?.spec;
|
|
6376
6367
|
}
|
|
6377
6368
|
async function runSpec(ctx, group, spec, argv) {
|
|
6378
|
-
return runRoute(ctx, group, toRouteCmd(
|
|
6369
|
+
return runRoute(ctx, group, toRouteCmd(spec), [...spec.cmd, ...argv]);
|
|
6379
6370
|
}
|
|
6380
6371
|
async function dispatchSpecs(ctx, group, specs, argv) {
|
|
6381
6372
|
const spec = matchSpec(specs, argv);
|
|
@@ -12417,7 +12408,9 @@ honouring the server's \`retry:\` field), and no event is printed twice.
|
|
|
12417
12408
|
(\`values\` never combines). A host that does not serve
|
|
12418
12409
|
the mode answers 400 unsupported_stream_mode.
|
|
12419
12410
|
--idle-timeout-ms N a connection that delivers no bytes for N ms (default
|
|
12420
|
-
45000; 0 disables) is treated as dropped and resumed.
|
|
12411
|
+
45000; 0 disables) is treated as dropped and resumed. A
|
|
12412
|
+
stream whose headers do not arrive within 10 s is given
|
|
12413
|
+
up on and followed by polling, with a hint on stderr.
|
|
12421
12414
|
Streams are read from the stream origin: --stream-base-url (global), else the
|
|
12422
12415
|
host's advertised \`streamBase\` (https only), else --base-url \u2014 some front
|
|
12423
12416
|
doors buffer event streams entirely (e.g. a CDN rewrite).
|
|
@@ -17404,6 +17397,24 @@ async function runPromptsGet(ctx, argv) {
|
|
|
17404
17397
|
return 0;
|
|
17405
17398
|
}
|
|
17406
17399
|
|
|
17400
|
+
// src/redact.ts
|
|
17401
|
+
var BASE_SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
|
|
17402
|
+
function redactSecrets(value, rules = {}) {
|
|
17403
|
+
const secret = rules.secret ?? BASE_SECRET_KEY;
|
|
17404
|
+
const walk = (v) => {
|
|
17405
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
17406
|
+
if (v && typeof v === "object") {
|
|
17407
|
+
const out = {};
|
|
17408
|
+
for (const [k, inner] of Object.entries(v)) {
|
|
17409
|
+
out[k] = secret.test(k) && !(rules.keep?.test(k) ?? false) ? "[redacted]" : walk(inner);
|
|
17410
|
+
}
|
|
17411
|
+
return out;
|
|
17412
|
+
}
|
|
17413
|
+
return v;
|
|
17414
|
+
};
|
|
17415
|
+
return walk(value);
|
|
17416
|
+
}
|
|
17417
|
+
|
|
17407
17418
|
// src/cli/connections.ts
|
|
17408
17419
|
var CONN = "/v1/host/openwop-app/connections/:id";
|
|
17409
17420
|
var CONNECTIONS_ROUTES = [
|
|
@@ -17479,15 +17490,7 @@ Examples:
|
|
|
17479
17490
|
`;
|
|
17480
17491
|
var SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
|
|
17481
17492
|
function redact(value) {
|
|
17482
|
-
|
|
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;
|
|
17493
|
+
return redactSecrets(value, { secret: SECRET_KEY });
|
|
17491
17494
|
}
|
|
17492
17495
|
var TOP = ["list", "get", "test", "authorize", "oauth-clients", "oauth-client"];
|
|
17493
17496
|
async function runConnections(ctx, argv) {
|
|
@@ -17691,7 +17694,8 @@ Run \`openwop connections --help\` for usage.`);
|
|
|
17691
17694
|
} catch (err) {
|
|
17692
17695
|
gate4042(err);
|
|
17693
17696
|
}
|
|
17694
|
-
const
|
|
17697
|
+
const rawClients = Array.isArray(res.body?.clients) ? res.body.clients : [];
|
|
17698
|
+
const clients = rawClients.map(redact);
|
|
17695
17699
|
if (action === "get") {
|
|
17696
17700
|
if (positionals.length !== 1) {
|
|
17697
17701
|
write(ctx.io.stdout, "Usage: openwop connections oauth-clients get <provider> [--json]\n");
|
|
@@ -19207,15 +19211,7 @@ Examples:
|
|
|
19207
19211
|
var BASE11 = "/v1/host/openwop-app/auth";
|
|
19208
19212
|
var SECRET_KEY2 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential|bearer|assertion|certificate|cert|pem/i;
|
|
19209
19213
|
function redact2(value) {
|
|
19210
|
-
|
|
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;
|
|
19214
|
+
return redactSecrets(value, { secret: SECRET_KEY2 });
|
|
19219
19215
|
}
|
|
19220
19216
|
async function authRequest(ctx, path, options, notConfiguredMsg) {
|
|
19221
19217
|
try {
|
|
@@ -20327,15 +20323,7 @@ Examples:
|
|
|
20327
20323
|
var SECRET_KEY3 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
|
|
20328
20324
|
var REF_ONLY_KEY = /^secretsToRebind$/;
|
|
20329
20325
|
function redact3(value) {
|
|
20330
|
-
|
|
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;
|
|
20326
|
+
return redactSecrets(value, { secret: SECRET_KEY3, keep: REF_ONLY_KEY });
|
|
20339
20327
|
}
|
|
20340
20328
|
async function ensureAdvertised5(ctx) {
|
|
20341
20329
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
@@ -21991,7 +21979,7 @@ async function startJob(ctx, path, body) {
|
|
|
21991
21979
|
const res = await requestJson(ctx, path, { method: "POST", body });
|
|
21992
21980
|
return renderDone(ctx, res.body, `Video job ${res.body?.jobId ?? ""} ${res.body?.status ?? "started"}.`);
|
|
21993
21981
|
} catch (err) {
|
|
21994
|
-
const b = err instanceof HttpError ? err.body : void 0;
|
|
21982
|
+
const b = err instanceof HttpError && err.body && typeof err.body === "object" ? err.body : void 0;
|
|
21995
21983
|
if (err instanceof HttpError && b && b.status === "failed") {
|
|
21996
21984
|
if (ctx.json) writeJson(ctx.io.stdout, b);
|
|
21997
21985
|
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" : ""}`);
|
|
@@ -29495,8 +29483,9 @@ async function runCapabilityFirewall(ctx, argv) {
|
|
|
29495
29483
|
try {
|
|
29496
29484
|
return await runResourceGroup(ctx, "capability-firewall", CAPABILITY_FIREWALL_HELP, CAPABILITY_FIREWALL_SPECS, argv);
|
|
29497
29485
|
} catch (err) {
|
|
29498
|
-
if (argv[0] === "platform" && err instanceof
|
|
29499
|
-
throw new CliError(gateAdvice("superadmin", "The capability-firewall platform baseline")
|
|
29486
|
+
if (argv[0] === "platform" && err instanceof CliError && err.code === 4) {
|
|
29487
|
+
throw new CliError(`${gateAdvice("superadmin", "The capability-firewall platform baseline")}
|
|
29488
|
+
${err.message}`, 4);
|
|
29500
29489
|
}
|
|
29501
29490
|
throw err;
|
|
29502
29491
|
}
|
|
@@ -30443,6 +30432,7 @@ Examples:
|
|
|
30443
30432
|
openwop packs install community.openwop-team.demo --version 0.1.0
|
|
30444
30433
|
`;
|
|
30445
30434
|
export {
|
|
30435
|
+
BASE_SECRET_KEY,
|
|
30446
30436
|
CLI_PROTOCOL_VERSION_BY_MAJOR,
|
|
30447
30437
|
COMMAND_NAMES,
|
|
30448
30438
|
DEFAULT_BASE_URL,
|
|
@@ -30488,6 +30478,7 @@ export {
|
|
|
30488
30478
|
projectTenantBoundId,
|
|
30489
30479
|
readConfigSafe,
|
|
30490
30480
|
readDaemonRecord,
|
|
30481
|
+
redactSecrets,
|
|
30491
30482
|
renderEvent,
|
|
30492
30483
|
requestNormativeOrHost,
|
|
30493
30484
|
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.
|
|
582
|
+
version: "1.2.2",
|
|
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
|
-
|
|
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}` : ""}
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
1787
|
-
|
|
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 (!(
|
|
1813
|
+
const arm = (after = ms2) => {
|
|
1814
|
+
if (!(after > 0)) return;
|
|
1792
1815
|
if (timer !== void 0) clearTimeout(timer);
|
|
1793
|
-
timer = setTimeout(expire,
|
|
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
|
|
1832
|
+
out.error(new StreamSilent(ms2, false));
|
|
1805
1833
|
source.cancel().catch(() => {
|
|
1806
1834
|
});
|
|
1807
1835
|
};
|
|
@@ -1976,7 +2004,7 @@ function formatCheckTable(checks) {
|
|
|
1976
2004
|
|
|
1977
2005
|
// src/cli/routeKit.ts
|
|
1978
2006
|
var errText2 = (err) => err instanceof Error ? err.message : String(err);
|
|
1979
|
-
var
|
|
2007
|
+
var MSG = {
|
|
1980
2008
|
required: (flag, usage6) => `${usage6}
|
|
1981
2009
|
${flag} is required.`,
|
|
1982
2010
|
refusal: (words, positionals) => `Refusing to ${words.join(" ")} ${positionals.join(" ")} without --yes.`.replace(/\s+/g, " ").trim(),
|
|
@@ -2055,14 +2083,14 @@ function setPath(target, key, value) {
|
|
|
2055
2083
|
}
|
|
2056
2084
|
node[parts[parts.length - 1]] = value;
|
|
2057
2085
|
}
|
|
2058
|
-
function coerce(f, raw
|
|
2086
|
+
function coerce(f, raw) {
|
|
2059
2087
|
const t = f.type ?? "string";
|
|
2060
2088
|
if (t === "boolean") return raw;
|
|
2061
2089
|
if (t === "bool") {
|
|
2062
2090
|
const s2 = String(raw);
|
|
2063
2091
|
if (s2 === "true" || s2 === "yes" || s2 === "1") return true;
|
|
2064
2092
|
if (s2 === "false" || s2 === "no" || s2 === "0") return false;
|
|
2065
|
-
throw new CliError(
|
|
2093
|
+
throw new CliError(MSG.invalidBool(f.flag, s2), 2);
|
|
2066
2094
|
}
|
|
2067
2095
|
if (t === "list") return Array.isArray(raw) ? raw.map(String) : [String(raw)];
|
|
2068
2096
|
if (t === "map") {
|
|
@@ -2079,7 +2107,7 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2079
2107
|
const s = String(raw);
|
|
2080
2108
|
if (t === "number") {
|
|
2081
2109
|
const n = Number(s);
|
|
2082
|
-
if (
|
|
2110
|
+
if (s.trim() === "" || !Number.isFinite(n)) throw new CliError(MSG.invalidNumber(f.flag, s), 2);
|
|
2083
2111
|
return n;
|
|
2084
2112
|
}
|
|
2085
2113
|
if (t === "json-file") {
|
|
@@ -2087,7 +2115,7 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2087
2115
|
try {
|
|
2088
2116
|
text = readFileSync4(s, "utf8");
|
|
2089
2117
|
} catch (err) {
|
|
2090
|
-
throw new CliError(
|
|
2118
|
+
throw new CliError(MSG.unreadable(f.flag, s, err), 2);
|
|
2091
2119
|
}
|
|
2092
2120
|
try {
|
|
2093
2121
|
return JSON.parse(text);
|
|
@@ -2099,14 +2127,14 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2099
2127
|
try {
|
|
2100
2128
|
return readFileSync4(s).toString("base64");
|
|
2101
2129
|
} catch (err) {
|
|
2102
|
-
throw new CliError(
|
|
2130
|
+
throw new CliError(MSG.unreadable(f.flag, s, err), 2);
|
|
2103
2131
|
}
|
|
2104
2132
|
}
|
|
2105
2133
|
if (t === "file") {
|
|
2106
2134
|
try {
|
|
2107
2135
|
return readFileSync4(s, "utf8").replace(/\r?\n$/, "");
|
|
2108
2136
|
} catch (err) {
|
|
2109
|
-
throw new CliError(
|
|
2137
|
+
throw new CliError(MSG.unreadable(f.flag, s, err), 2);
|
|
2110
2138
|
}
|
|
2111
2139
|
}
|
|
2112
2140
|
if (t === "csv") return s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
@@ -2114,19 +2142,19 @@ function coerce(f, raw, cmd, msg) {
|
|
|
2114
2142
|
try {
|
|
2115
2143
|
return JSON.parse(s);
|
|
2116
2144
|
} catch {
|
|
2117
|
-
throw new CliError(
|
|
2145
|
+
throw new CliError(MSG.invalidJson(f.flag), 2);
|
|
2118
2146
|
}
|
|
2119
2147
|
}
|
|
2120
2148
|
return s;
|
|
2121
2149
|
}
|
|
2122
|
-
function readBodyOptions(options
|
|
2150
|
+
function readBodyOptions(options) {
|
|
2123
2151
|
let raw;
|
|
2124
2152
|
if (options.bodyFile !== void 0) {
|
|
2125
2153
|
if (String(options.bodyFile).trim() === "") throw new CliError("--body-file needs a path (got an empty value).", 2);
|
|
2126
2154
|
try {
|
|
2127
2155
|
raw = readFileSync4(String(options.bodyFile), "utf8");
|
|
2128
2156
|
} catch (err) {
|
|
2129
|
-
throw new CliError(
|
|
2157
|
+
throw new CliError(MSG.unreadableBody(String(options.bodyFile), err), 2);
|
|
2130
2158
|
}
|
|
2131
2159
|
} else if (options.body !== void 0) {
|
|
2132
2160
|
raw = String(options.body);
|
|
@@ -2136,9 +2164,9 @@ function readBodyOptions(options, msg) {
|
|
|
2136
2164
|
try {
|
|
2137
2165
|
parsed = JSON.parse(raw);
|
|
2138
2166
|
} catch {
|
|
2139
|
-
throw new CliError(
|
|
2167
|
+
throw new CliError(MSG.invalidBodyJson, 2);
|
|
2140
2168
|
}
|
|
2141
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError(
|
|
2169
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError(MSG.bodyNotObject, 2);
|
|
2142
2170
|
return parsed;
|
|
2143
2171
|
}
|
|
2144
2172
|
function hostError(err) {
|
|
@@ -2149,7 +2177,6 @@ function hostError(err) {
|
|
|
2149
2177
|
${hint}` : line, code2);
|
|
2150
2178
|
}
|
|
2151
2179
|
async function runRoute(ctx, group, cmd, argv) {
|
|
2152
|
-
const msg = { ...DEFAULT_MESSAGES, ...cmd.messages ?? {} };
|
|
2153
2180
|
const rest = argv.slice(cmd.words.length);
|
|
2154
2181
|
const fields = [...cmd.query ?? [], ...cmd.body ?? []];
|
|
2155
2182
|
const bool = [...COMMON_BOOL, ...fields.filter((f) => f.type === "boolean").flatMap((f) => [f.flag, f.flag.replace(/^--/, "--no-")])];
|
|
@@ -2162,8 +2189,7 @@ async function runRoute(ctx, group, cmd, argv) {
|
|
|
2162
2189
|
const multi = fields.filter(isMulti).map((f) => f.flag);
|
|
2163
2190
|
const { options, positionals } = parseOptions(rest, { bool, value, multi });
|
|
2164
2191
|
if (options.help) {
|
|
2165
|
-
|
|
2166
|
-
else writeLine(ctx.io.stdout, `Usage: ${usageLine(group, cmd)}
|
|
2192
|
+
writeLine(ctx.io.stdout, `Usage: ${usageLine(group, cmd)}
|
|
2167
2193
|
|
|
2168
2194
|
${cmd.summary}
|
|
2169
2195
|
Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
@@ -2171,12 +2197,12 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2171
2197
|
}
|
|
2172
2198
|
const template = templates(cmd).find((p) => positionalNames(cmd, p).length === positionals.length);
|
|
2173
2199
|
if (!template) {
|
|
2174
|
-
write(ctx.io.stderr,
|
|
2200
|
+
write(ctx.io.stderr, `Usage: ${usageLine(group, cmd)}
|
|
2175
2201
|
`);
|
|
2176
2202
|
return 2;
|
|
2177
2203
|
}
|
|
2178
2204
|
const path = fillPath(cmd, template, positionals, options.org);
|
|
2179
|
-
const requiredError = (f) => new CliError(
|
|
2205
|
+
const requiredError = (f) => new CliError(MSG.required(f.flag, usageLine(group, cmd)), 2);
|
|
2180
2206
|
const optionOf = (f) => {
|
|
2181
2207
|
const name = f.flag.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2182
2208
|
if (f.type === "boolean") {
|
|
@@ -2194,7 +2220,7 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2194
2220
|
if (f.required) throw requiredError(f);
|
|
2195
2221
|
continue;
|
|
2196
2222
|
}
|
|
2197
|
-
const c = coerce(f, v
|
|
2223
|
+
const c = coerce(f, v);
|
|
2198
2224
|
if (f.type === "csv") qs2.set(f.key, c.join(","));
|
|
2199
2225
|
else if (Array.isArray(c)) c.forEach((x) => qs2.append(f.key, String(x)));
|
|
2200
2226
|
else qs2.set(f.key, c !== null && typeof c === "object" ? JSON.stringify(c) : String(c));
|
|
@@ -2202,7 +2228,7 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2202
2228
|
const url = qs2.size ? `${path}${path.includes("?") ? "&" : "?"}${qs2.toString()}` : path;
|
|
2203
2229
|
const confirm2 = cmd.confirm ?? cmd.method === "DELETE";
|
|
2204
2230
|
const gate = () => {
|
|
2205
|
-
if (confirm2 && !options.yes) throw new CliError(
|
|
2231
|
+
if (confirm2 && !options.yes) throw new CliError(MSG.refusal(cmd.words, positionals), 2);
|
|
2206
2232
|
};
|
|
2207
2233
|
if (!cmd.validateFirst) gate();
|
|
2208
2234
|
const isWrite = cmd.method !== "GET" && cmd.method !== "DELETE";
|
|
@@ -2210,10 +2236,10 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2210
2236
|
let explicit = {};
|
|
2211
2237
|
const overlay = {};
|
|
2212
2238
|
if (takesBody) {
|
|
2213
|
-
explicit = readBodyOptions(options
|
|
2239
|
+
explicit = readBodyOptions(options);
|
|
2214
2240
|
for (const f of cmd.body ?? []) {
|
|
2215
2241
|
const v = optionOf(f);
|
|
2216
|
-
if (v !== void 0) setPath(overlay, f.key, coerce(f, v
|
|
2242
|
+
if (v !== void 0) setPath(overlay, f.key, coerce(f, v));
|
|
2217
2243
|
else if (cmd.validateFirst && f.required && getPath(explicit, f.key) === void 0) throw requiredError(f);
|
|
2218
2244
|
}
|
|
2219
2245
|
}
|
|
@@ -2250,7 +2276,7 @@ Endpoint: ${cmd.method} ${templates(cmd).join(" | ")}`);
|
|
|
2250
2276
|
render(ctx, { ...cmd, method: "GET" }, positionals, err.status, err.body);
|
|
2251
2277
|
return 1;
|
|
2252
2278
|
}
|
|
2253
|
-
if (err instanceof HttpError
|
|
2279
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
2254
2280
|
throw err;
|
|
2255
2281
|
}
|
|
2256
2282
|
}
|
|
@@ -2261,7 +2287,7 @@ async function send(ctx, cmd, url, init) {
|
|
|
2261
2287
|
try {
|
|
2262
2288
|
return await requestJson(ctx, url, { method: init.method, ...init.body !== void 0 ? { body: init.body } : {}, ...cmd.anonymous ? { auth: false } : {} });
|
|
2263
2289
|
} catch (err) {
|
|
2264
|
-
if (err instanceof HttpError
|
|
2290
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
2265
2291
|
throw err;
|
|
2266
2292
|
}
|
|
2267
2293
|
}
|
|
@@ -2311,7 +2337,7 @@ function render(ctx, cmd, positionals, status, rawBody) {
|
|
|
2311
2337
|
return;
|
|
2312
2338
|
}
|
|
2313
2339
|
}
|
|
2314
|
-
if (cmd.method === "GET"
|
|
2340
|
+
if (cmd.method === "GET") {
|
|
2315
2341
|
writeJson(ctx.io.stdout, body);
|
|
2316
2342
|
return;
|
|
2317
2343
|
}
|
|
@@ -2650,7 +2676,8 @@ async function runInterrupts(ctx, argv) {
|
|
|
2650
2676
|
if (b.key) writeLine(ctx.io.stdout, `key: ${b.key}`);
|
|
2651
2677
|
writeLine(ctx.io.stdout, `resolved: ${b.resolved ? "yes" : "no"}`);
|
|
2652
2678
|
if (b.expiresAt) writeLine(ctx.io.stdout, `expiresAt: ${b.expiresAt}`);
|
|
2653
|
-
|
|
2679
|
+
const prompt = b.data && typeof b.data === "object" ? b.data.prompt : void 0;
|
|
2680
|
+
if (typeof prompt === "string") writeLine(ctx.io.stdout, `prompt: ${prompt}`);
|
|
2654
2681
|
if (b.resumeSchema !== void 0) writeLine(ctx.io.stdout, `resumeSchema: ${JSON.stringify(b.resumeSchema)}`);
|
|
2655
2682
|
return 0;
|
|
2656
2683
|
}
|
|
@@ -6282,25 +6309,8 @@ function parseField(spec) {
|
|
|
6282
6309
|
const flag = `--${flagOverride ?? key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`).replace(/_/g, "-")}`;
|
|
6283
6310
|
return { key, flag, type, required };
|
|
6284
6311
|
}
|
|
6285
|
-
function hasOrg(route) {
|
|
6286
|
-
return /:org(?![A-Za-z0-9_])/.test(route);
|
|
6287
|
-
}
|
|
6288
|
-
function paramsOf(route) {
|
|
6289
|
-
return (route.match(/:[A-Za-z][A-Za-z0-9_]*/g) ?? []).map((p) => p.slice(1)).filter((p) => p !== "org");
|
|
6290
|
-
}
|
|
6291
6312
|
function usageLine2(group, spec) {
|
|
6292
|
-
|
|
6293
|
-
if (hasOrg(spec.route)) parts.push("--org <orgId>");
|
|
6294
|
-
const fmt = (f) => {
|
|
6295
|
-
const v = f.type === "bool" ? "true|false" : f.type === "json" ? "json" : f.type === "list" ? "a,b" : f.type === "number" ? "n" : f.type === "file" ? "path" : "v";
|
|
6296
|
-
return f.required ? `${f.flag} <${v}>` : `[${f.flag} <${v}>]`;
|
|
6297
|
-
};
|
|
6298
|
-
for (const f of (spec.query ?? []).map(parseField)) parts.push(fmt(f));
|
|
6299
|
-
for (const f of (spec.body ?? []).map(parseField)) parts.push(fmt(f));
|
|
6300
|
-
if (spec.body || spec.rawBody) parts.push("[--body <json> | --body-file <path>]");
|
|
6301
|
-
if (spec.confirm) parts.push("--yes");
|
|
6302
|
-
parts.push("[--json]");
|
|
6303
|
-
return parts.join(" ");
|
|
6313
|
+
return usageLine(group, toRouteCmd(spec));
|
|
6304
6314
|
}
|
|
6305
6315
|
function buildGroupHelp(group, intro, specs, footer = "") {
|
|
6306
6316
|
const lines = ["Usage:"];
|
|
@@ -6329,10 +6339,7 @@ function toField(spec) {
|
|
|
6329
6339
|
const f = parseField(spec);
|
|
6330
6340
|
return { flag: f.flag, key: f.key, type: TYPES[f.type] ?? "string", required: f.required };
|
|
6331
6341
|
}
|
|
6332
|
-
function toRouteCmd(
|
|
6333
|
-
const usage6 = `Usage: ${usageLine2(group, spec)}
|
|
6334
|
-
${spec.method} ${spec.route} \u2014 ${spec.summary}
|
|
6335
|
-
`;
|
|
6342
|
+
function toRouteCmd(spec) {
|
|
6336
6343
|
const rmwKey = spec.rmwKey;
|
|
6337
6344
|
return {
|
|
6338
6345
|
words: spec.cmd,
|
|
@@ -6352,30 +6359,14 @@ function toRouteCmd(group, spec) {
|
|
|
6352
6359
|
rawText: true,
|
|
6353
6360
|
// every spec, as before — `text` only documents the route
|
|
6354
6361
|
noContent: spec.method === "DELETE" ? "Deleted." : "Done (HTTP {status}).",
|
|
6355
|
-
|
|
6356
|
-
hostErrors: false,
|
|
6357
|
-
validateFirst: true,
|
|
6358
|
-
strictNumbers: true,
|
|
6359
|
-
usageText: usage6,
|
|
6360
|
-
messages: {
|
|
6361
|
-
required: (flag, usageText) => `${flag} is required.
|
|
6362
|
-
${usageText}`,
|
|
6363
|
-
refusal: () => `Refusing to ${spec.summary.charAt(0).toLowerCase()}${spec.summary.slice(1).replace(/\.$/, "")} without --yes.`,
|
|
6364
|
-
invalidNumber: (flag, raw) => `${flag} must be a number, got: ${raw}`,
|
|
6365
|
-
invalidBool: (flag, raw) => `${flag} must be true or false, got: ${raw}`,
|
|
6366
|
-
invalidJson: (flag) => `${flag} must be valid JSON`,
|
|
6367
|
-
unreadable: (flag, path) => `Cannot read ${flag} ${path}`,
|
|
6368
|
-
unreadableBody: (path) => `Cannot read --body-file ${path}`,
|
|
6369
|
-
invalidBodyJson: "--body/--body-file must be valid JSON",
|
|
6370
|
-
bodyNotObject: "--body/--body-file must be a JSON object"
|
|
6371
|
-
}
|
|
6362
|
+
validateFirst: true
|
|
6372
6363
|
};
|
|
6373
6364
|
}
|
|
6374
6365
|
function matchSpec(specs, argv) {
|
|
6375
6366
|
return matchRoute(specs.map((spec) => ({ words: spec.cmd, spec })), argv)?.spec;
|
|
6376
6367
|
}
|
|
6377
6368
|
async function runSpec(ctx, group, spec, argv) {
|
|
6378
|
-
return runRoute(ctx, group, toRouteCmd(
|
|
6369
|
+
return runRoute(ctx, group, toRouteCmd(spec), [...spec.cmd, ...argv]);
|
|
6379
6370
|
}
|
|
6380
6371
|
async function dispatchSpecs(ctx, group, specs, argv) {
|
|
6381
6372
|
const spec = matchSpec(specs, argv);
|
|
@@ -12417,7 +12408,9 @@ honouring the server's \`retry:\` field), and no event is printed twice.
|
|
|
12417
12408
|
(\`values\` never combines). A host that does not serve
|
|
12418
12409
|
the mode answers 400 unsupported_stream_mode.
|
|
12419
12410
|
--idle-timeout-ms N a connection that delivers no bytes for N ms (default
|
|
12420
|
-
45000; 0 disables) is treated as dropped and resumed.
|
|
12411
|
+
45000; 0 disables) is treated as dropped and resumed. A
|
|
12412
|
+
stream whose headers do not arrive within 10 s is given
|
|
12413
|
+
up on and followed by polling, with a hint on stderr.
|
|
12421
12414
|
Streams are read from the stream origin: --stream-base-url (global), else the
|
|
12422
12415
|
host's advertised \`streamBase\` (https only), else --base-url \u2014 some front
|
|
12423
12416
|
doors buffer event streams entirely (e.g. a CDN rewrite).
|
|
@@ -17404,6 +17397,24 @@ async function runPromptsGet(ctx, argv) {
|
|
|
17404
17397
|
return 0;
|
|
17405
17398
|
}
|
|
17406
17399
|
|
|
17400
|
+
// src/redact.ts
|
|
17401
|
+
var BASE_SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
|
|
17402
|
+
function redactSecrets(value, rules = {}) {
|
|
17403
|
+
const secret = rules.secret ?? BASE_SECRET_KEY;
|
|
17404
|
+
const walk = (v) => {
|
|
17405
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
17406
|
+
if (v && typeof v === "object") {
|
|
17407
|
+
const out = {};
|
|
17408
|
+
for (const [k, inner] of Object.entries(v)) {
|
|
17409
|
+
out[k] = secret.test(k) && !(rules.keep?.test(k) ?? false) ? "[redacted]" : walk(inner);
|
|
17410
|
+
}
|
|
17411
|
+
return out;
|
|
17412
|
+
}
|
|
17413
|
+
return v;
|
|
17414
|
+
};
|
|
17415
|
+
return walk(value);
|
|
17416
|
+
}
|
|
17417
|
+
|
|
17407
17418
|
// src/cli/connections.ts
|
|
17408
17419
|
var CONN = "/v1/host/openwop-app/connections/:id";
|
|
17409
17420
|
var CONNECTIONS_ROUTES = [
|
|
@@ -17479,15 +17490,7 @@ Examples:
|
|
|
17479
17490
|
`;
|
|
17480
17491
|
var SECRET_KEY = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
|
|
17481
17492
|
function redact(value) {
|
|
17482
|
-
|
|
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;
|
|
17493
|
+
return redactSecrets(value, { secret: SECRET_KEY });
|
|
17491
17494
|
}
|
|
17492
17495
|
var TOP = ["list", "get", "test", "authorize", "oauth-clients", "oauth-client"];
|
|
17493
17496
|
async function runConnections(ctx, argv) {
|
|
@@ -17691,7 +17694,8 @@ Run \`openwop connections --help\` for usage.`);
|
|
|
17691
17694
|
} catch (err) {
|
|
17692
17695
|
gate4042(err);
|
|
17693
17696
|
}
|
|
17694
|
-
const
|
|
17697
|
+
const rawClients = Array.isArray(res.body?.clients) ? res.body.clients : [];
|
|
17698
|
+
const clients = rawClients.map(redact);
|
|
17695
17699
|
if (action === "get") {
|
|
17696
17700
|
if (positionals.length !== 1) {
|
|
17697
17701
|
write(ctx.io.stdout, "Usage: openwop connections oauth-clients get <provider> [--json]\n");
|
|
@@ -19207,15 +19211,7 @@ Examples:
|
|
|
19207
19211
|
var BASE11 = "/v1/host/openwop-app/auth";
|
|
19208
19212
|
var SECRET_KEY2 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential|bearer|assertion|certificate|cert|pem/i;
|
|
19209
19213
|
function redact2(value) {
|
|
19210
|
-
|
|
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;
|
|
19214
|
+
return redactSecrets(value, { secret: SECRET_KEY2 });
|
|
19219
19215
|
}
|
|
19220
19216
|
async function authRequest(ctx, path, options, notConfiguredMsg) {
|
|
19221
19217
|
try {
|
|
@@ -20327,15 +20323,7 @@ Examples:
|
|
|
20327
20323
|
var SECRET_KEY3 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential/i;
|
|
20328
20324
|
var REF_ONLY_KEY = /^secretsToRebind$/;
|
|
20329
20325
|
function redact3(value) {
|
|
20330
|
-
|
|
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;
|
|
20326
|
+
return redactSecrets(value, { secret: SECRET_KEY3, keep: REF_ONLY_KEY });
|
|
20339
20327
|
}
|
|
20340
20328
|
async function ensureAdvertised5(ctx) {
|
|
20341
20329
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
@@ -21991,7 +21979,7 @@ async function startJob(ctx, path, body) {
|
|
|
21991
21979
|
const res = await requestJson(ctx, path, { method: "POST", body });
|
|
21992
21980
|
return renderDone(ctx, res.body, `Video job ${res.body?.jobId ?? ""} ${res.body?.status ?? "started"}.`);
|
|
21993
21981
|
} catch (err) {
|
|
21994
|
-
const b = err instanceof HttpError ? err.body : void 0;
|
|
21982
|
+
const b = err instanceof HttpError && err.body && typeof err.body === "object" ? err.body : void 0;
|
|
21995
21983
|
if (err instanceof HttpError && b && b.status === "failed") {
|
|
21996
21984
|
if (ctx.json) writeJson(ctx.io.stdout, b);
|
|
21997
21985
|
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" : ""}`);
|
|
@@ -29495,8 +29483,9 @@ async function runCapabilityFirewall(ctx, argv) {
|
|
|
29495
29483
|
try {
|
|
29496
29484
|
return await runResourceGroup(ctx, "capability-firewall", CAPABILITY_FIREWALL_HELP, CAPABILITY_FIREWALL_SPECS, argv);
|
|
29497
29485
|
} catch (err) {
|
|
29498
|
-
if (argv[0] === "platform" && err instanceof
|
|
29499
|
-
throw new CliError(gateAdvice("superadmin", "The capability-firewall platform baseline")
|
|
29486
|
+
if (argv[0] === "platform" && err instanceof CliError && err.code === 4) {
|
|
29487
|
+
throw new CliError(`${gateAdvice("superadmin", "The capability-firewall platform baseline")}
|
|
29488
|
+
${err.message}`, 4);
|
|
29500
29489
|
}
|
|
29501
29490
|
throw err;
|
|
29502
29491
|
}
|