@openwop/cli 1.2.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/dist/cli.js +800 -339
- package/dist/openwop.js +801 -340
- package/package.json +1 -1
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.
|
|
582
|
+
version: "1.4.0",
|
|
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",
|
|
@@ -1079,7 +1079,7 @@ import { createInterface as createInterface2 } from "node:readline";
|
|
|
1079
1079
|
|
|
1080
1080
|
// src/ids.ts
|
|
1081
1081
|
function projectTenantBoundId(id) {
|
|
1082
|
-
if (id.includes("~")) return id;
|
|
1082
|
+
if (id.includes("~") && !id.includes("/")) return id;
|
|
1083
1083
|
let out = "";
|
|
1084
1084
|
for (const byte of new TextEncoder().encode(id)) {
|
|
1085
1085
|
const ch = String.fromCharCode(byte);
|
|
@@ -1153,6 +1153,17 @@ var V2_PATH_TEMPLATES = [
|
|
|
1153
1153
|
"/webhooks/{webhookId}/rotate-secret",
|
|
1154
1154
|
"/workflows/{workflowId}"
|
|
1155
1155
|
];
|
|
1156
|
+
var V2_RENAMED = { "/v1/openapi.json": "/openapi.json" };
|
|
1157
|
+
var V2_SEAMS_PROFILE = "openwop-conformance-seams-v2";
|
|
1158
|
+
var V2_SEAM_PREFIXES = {
|
|
1159
|
+
"/v1/host/workspace/files": "/conformance/seams/workspace/files"
|
|
1160
|
+
};
|
|
1161
|
+
function v2SeamTwin(path) {
|
|
1162
|
+
for (const [v1, v2] of Object.entries(V2_SEAM_PREFIXES)) {
|
|
1163
|
+
if (path === v1 || path.startsWith(`${v1}/`) || path.startsWith(`${v1}?`)) return `${v2}${path.slice(v1.length)}`;
|
|
1164
|
+
}
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1156
1167
|
var V2_MATCHERS = V2_PATH_TEMPLATES.map(
|
|
1157
1168
|
(t) => new RegExp(`^${t.replace(/[.:]/g, "\\$&").replace(/\{[A-Za-z]+\}/g, "[^/]+")}$`)
|
|
1158
1169
|
);
|
|
@@ -1161,6 +1172,7 @@ function v2Twin(path) {
|
|
|
1161
1172
|
const bare = q === -1 ? path : path.slice(0, q);
|
|
1162
1173
|
const query = q === -1 ? "" : path.slice(q);
|
|
1163
1174
|
if (!bare.startsWith("/v1/")) return null;
|
|
1175
|
+
if (V2_RENAMED[bare] !== void 0) return `${V2_RENAMED[bare]}${query}`;
|
|
1164
1176
|
const unversioned = bare.slice("/v1".length);
|
|
1165
1177
|
return V2_MATCHERS.some((m) => m.test(unversioned)) ? `${unversioned}${query}` : null;
|
|
1166
1178
|
}
|
|
@@ -1204,6 +1216,7 @@ async function negotiateMajor(ctx) {
|
|
|
1204
1216
|
}
|
|
1205
1217
|
let major = 1;
|
|
1206
1218
|
let roots = {};
|
|
1219
|
+
let seams = false;
|
|
1207
1220
|
try {
|
|
1208
1221
|
const url = new URL(".well-known/openwop", ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
|
|
1209
1222
|
const res = await ctx.fetchImpl(url, { method: "GET", headers: { accept: "application/json", "openwop-version": "2" } });
|
|
@@ -1212,7 +1225,10 @@ async function negotiateMajor(ctx) {
|
|
|
1212
1225
|
ctx.discovery = { doc, servedVersion: res.headers?.get?.("openwop-version") ?? void 0 };
|
|
1213
1226
|
ctx.discoveredStreamBase = streamBaseFrom(doc?.extensions, ctx.baseUrl);
|
|
1214
1227
|
major = selectMajor(doc?.protocolVersions);
|
|
1215
|
-
if (major === 2)
|
|
1228
|
+
if (major === 2) {
|
|
1229
|
+
roots = hostRootsFrom(doc?.extensions);
|
|
1230
|
+
seams = doc?.conformance?.seamsProfile === V2_SEAMS_PROFILE;
|
|
1231
|
+
}
|
|
1216
1232
|
} else if (res.status === 406) {
|
|
1217
1233
|
major = selectMajor(doc?.details?.protocolVersions);
|
|
1218
1234
|
}
|
|
@@ -1220,6 +1236,7 @@ async function negotiateMajor(ctx) {
|
|
|
1220
1236
|
}
|
|
1221
1237
|
ctx.protocolMajor = major;
|
|
1222
1238
|
ctx.hostRoots = roots;
|
|
1239
|
+
ctx.seamsV2 = seams;
|
|
1223
1240
|
return major;
|
|
1224
1241
|
}
|
|
1225
1242
|
async function resolveRequest(ctx, path, headers) {
|
|
@@ -1227,6 +1244,8 @@ async function resolveRequest(ctx, path, headers) {
|
|
|
1227
1244
|
if (major !== 2) return { path, headers };
|
|
1228
1245
|
const twin = v2Twin(path);
|
|
1229
1246
|
if (twin !== null) return { path: projectRunIdsInPath(twin), headers: { ...headers, "openwop-version": "2.0" } };
|
|
1247
|
+
const seam = ctx.seamsV2 ? v2SeamTwin(path) : null;
|
|
1248
|
+
if (seam !== null) return { path: seam, headers: { ...headers, "openwop-version": "2.0" } };
|
|
1230
1249
|
const rooted = hostRootFor(path, ctx.hostRoots ?? {});
|
|
1231
1250
|
if (rooted !== null) return { path: rooted, headers };
|
|
1232
1251
|
return { path, headers };
|
|
@@ -2613,7 +2632,19 @@ async function runInterrupts(ctx, argv) {
|
|
|
2613
2632
|
write(ctx.io.stdout, "Usage: openwop interrupts list <runId> [--json]\n");
|
|
2614
2633
|
return 2;
|
|
2615
2634
|
}
|
|
2616
|
-
|
|
2635
|
+
let res;
|
|
2636
|
+
try {
|
|
2637
|
+
res = await requestJson(ctx, `/v1/host/openwop-app/runs/${encodeURIComponent(rest[0])}/interrupts`);
|
|
2638
|
+
} catch (err) {
|
|
2639
|
+
const env = err instanceof HttpError ? errorEnvelope(err.body) : {};
|
|
2640
|
+
if (err instanceof HttpError && err.status === 404 && /^no operation at /i.test(env.message ?? "")) {
|
|
2641
|
+
throw new CliError(
|
|
2642
|
+
"interrupts: `list` reads the openwop-app host extension (GET /v1/host/openwop-app/runs/{runId}/interrupts), which this host does not serve. On any host an open interrupt appears as interrupt.requested in the run's events; answer it with `openwop interrupts respond <runId> <nodeId>`.",
|
|
2643
|
+
1
|
|
2644
|
+
);
|
|
2645
|
+
}
|
|
2646
|
+
throw err;
|
|
2647
|
+
}
|
|
2617
2648
|
if (ctx.json) {
|
|
2618
2649
|
writeJson(ctx.io.stdout, res.body);
|
|
2619
2650
|
return 0;
|
|
@@ -3580,6 +3611,24 @@ async function readDiscovery(ctx) {
|
|
|
3580
3611
|
ctx.discovery = { doc: res.body, servedVersion };
|
|
3581
3612
|
return { doc: res.body, servedVersion, major };
|
|
3582
3613
|
}
|
|
3614
|
+
async function advertisedRecord(ctx, pick) {
|
|
3615
|
+
const doc = (d) => d && typeof d === "object" ? d : {};
|
|
3616
|
+
let readAny = false;
|
|
3617
|
+
const v1 = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
3618
|
+
if (v1.ok) {
|
|
3619
|
+
readAny = true;
|
|
3620
|
+
const r = pick(doc(v1.body));
|
|
3621
|
+
if (r) return r;
|
|
3622
|
+
}
|
|
3623
|
+
try {
|
|
3624
|
+
const { doc: negotiated } = await readDiscovery(ctx);
|
|
3625
|
+
readAny = true;
|
|
3626
|
+
const r = pick(doc(negotiated));
|
|
3627
|
+
if (r) return r;
|
|
3628
|
+
} catch {
|
|
3629
|
+
}
|
|
3630
|
+
return readAny ? null : void 0;
|
|
3631
|
+
}
|
|
3583
3632
|
function isV2Discovery(caps, servedVersion) {
|
|
3584
3633
|
if (typeof servedVersion === "string" && servedVersion.startsWith("2.")) return true;
|
|
3585
3634
|
if (!caps || typeof caps !== "object" || "capabilities" in caps || "supportedTransports" in caps) return false;
|
|
@@ -4389,6 +4438,10 @@ registry (\`openwop packs\` operates packs.openwop.dev):
|
|
|
4389
4438
|
get GET /v1/packs/{packName} \u2014 one pack's node types (reverse-DNS name)
|
|
4390
4439
|
export GET /v1/packs/export \u2014 the installed agent manifests (RFC 0003
|
|
4391
4440
|
round-trip), re-installable elsewhere; --out writes them to a file.
|
|
4441
|
+
These four are v1 operations: protocol v2 names no installed-packs operation
|
|
4442
|
+
(spec/v2/path-manifest.json, api/v2/openapi.yaml), so they are sent as the v1
|
|
4443
|
+
paths whichever major the host speaks. To read the published catalog, use
|
|
4444
|
+
\`openwop packs\`, which resolves the registry's v2 tree.
|
|
4392
4445
|
|
|
4393
4446
|
\`tools\` reads the portable tool catalog (RFC 0078 \xA7B) \u2014 GET /v1/tools, or
|
|
4394
4447
|
GET /v1/tools/<toolId> for one descriptor. This is the tools an agent/workflow
|
|
@@ -11851,8 +11904,12 @@ ${routesHelp("workspace", WORKSPACE_ROUTES)}
|
|
|
11851
11904
|
|
|
11852
11905
|
Agent workspace (RFC 0059 \xA7C). A tenant-scoped file area an agent reads and
|
|
11853
11906
|
writes during a run, with optimistic concurrency via ETag / If-Match (WCT-1
|
|
11854
|
-
cross-owner isolation is enforced host-side). Drives
|
|
11855
|
-
|
|
11907
|
+
cross-owner isolation is enforced host-side). Drives GET/PUT/DELETE
|
|
11908
|
+
/v1/host/workspace/files. v2 names no canonical workspace operation: its only
|
|
11909
|
+
v2 home is the conformance seams profile, so on a host that speaks major 2 AND
|
|
11910
|
+
advertises conformance.seamsProfile "openwop-conformance-seams-v2" the same
|
|
11911
|
+
calls go to /conformance/seams/workspace/files (OpenWOP-Version: 2.0);
|
|
11912
|
+
otherwise the v1 path is used. (The /v1/host/openwop-app/workspace/op
|
|
11856
11913
|
cross-owner seam is a conformance-only test seam \u2014 reachable here as
|
|
11857
11914
|
\`workspace op\`, and only on a host started with OPENWOP_TEST_SEAM_ENABLED=true.)
|
|
11858
11915
|
|
|
@@ -14833,6 +14890,153 @@ async function runOnboard(ctx, argv) {
|
|
|
14833
14890
|
return 0;
|
|
14834
14891
|
}
|
|
14835
14892
|
|
|
14893
|
+
// src/cli/login.ts
|
|
14894
|
+
import { hostname } from "node:os";
|
|
14895
|
+
var BASE10 = "/v1/host/openwop-app/cli-login";
|
|
14896
|
+
var LOGIN_HELP = `Usage:
|
|
14897
|
+
openwop login [--label <name>] [--timeout <seconds>] [--profile <name>] [--json]
|
|
14898
|
+
openwop logout [--profile <name>] [--json]
|
|
14899
|
+
|
|
14900
|
+
Sign this terminal in to an openwop-app host without copying an API key
|
|
14901
|
+
(host extension under ${BASE10}).
|
|
14902
|
+
|
|
14903
|
+
login Shows a short code. Open the host's Access \u2192 API keys page, type the
|
|
14904
|
+
code under "Sign in the OpenWOP CLI", and approve. The host then issues
|
|
14905
|
+
this CLI a key (30 days by default), saved to your config file.
|
|
14906
|
+
logout Revokes that key on the host and removes it from your config file.
|
|
14907
|
+
|
|
14908
|
+
The key is never printed. It is listed in the app as "CLI: <label>", where you can
|
|
14909
|
+
revoke it. Sign in again to renew.
|
|
14910
|
+
|
|
14911
|
+
--label <name> What to call this terminal in the app (default: this machine's hostname).
|
|
14912
|
+
--timeout <seconds> Give up waiting for approval after this long (default: the host's own limit).
|
|
14913
|
+
--profile <name> Use the named config profile.
|
|
14914
|
+
|
|
14915
|
+
Approve a code ONLY if you just ran \`openwop login\` yourself.
|
|
14916
|
+
|
|
14917
|
+
Exit codes: 0 signed in / signed out \xB7 1 denied, expired or not available on this host \xB7 2 usage.
|
|
14918
|
+
|
|
14919
|
+
Examples:
|
|
14920
|
+
export OPENWOP_BASE_URL=https://app.openwop.dev/api
|
|
14921
|
+
openwop login
|
|
14922
|
+
openwop login --label "work laptop"
|
|
14923
|
+
openwop logout
|
|
14924
|
+
`;
|
|
14925
|
+
var sleep2 = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
14926
|
+
function approvalUrl(baseUrl, path) {
|
|
14927
|
+
const p = typeof path === "string" && path.startsWith("/") ? path : "/access?tab=api-keys";
|
|
14928
|
+
try {
|
|
14929
|
+
return `${new URL(baseUrl).origin}${p}`;
|
|
14930
|
+
} catch {
|
|
14931
|
+
return p;
|
|
14932
|
+
}
|
|
14933
|
+
}
|
|
14934
|
+
function notAvailable(ctx) {
|
|
14935
|
+
return new CliError(
|
|
14936
|
+
`${ctx.baseUrl} does not serve the CLI sign-in (${BASE10}/start is not there). Create an API key on the host and set OPENWOP_API_KEY instead.`,
|
|
14937
|
+
1
|
|
14938
|
+
);
|
|
14939
|
+
}
|
|
14940
|
+
async function runLogin(ctx, argv) {
|
|
14941
|
+
const { options } = parseOptions(argv, { bool: ["--help"], value: ["--label", "--timeout", "--profile"] });
|
|
14942
|
+
if (options.help) {
|
|
14943
|
+
write(ctx.io.stdout, LOGIN_HELP);
|
|
14944
|
+
return 0;
|
|
14945
|
+
}
|
|
14946
|
+
const timeoutS = options.timeout === void 0 ? void 0 : Number(options.timeout);
|
|
14947
|
+
if (timeoutS !== void 0 && !(Number.isFinite(timeoutS) && timeoutS > 0)) {
|
|
14948
|
+
writeLine(ctx.io.stderr, "--timeout must be a positive number of seconds.");
|
|
14949
|
+
return 2;
|
|
14950
|
+
}
|
|
14951
|
+
const label = String(options.label ?? hostname() ?? "this machine").slice(0, 60);
|
|
14952
|
+
const anon = { ...ctx, apiKey: void 0 };
|
|
14953
|
+
let started;
|
|
14954
|
+
try {
|
|
14955
|
+
started = (await requestJson(anon, `${BASE10}/start`, { method: "POST", body: { label } })).body;
|
|
14956
|
+
} catch (err) {
|
|
14957
|
+
if (err instanceof HttpError && err.status === 404) throw notAvailable(ctx);
|
|
14958
|
+
throw err;
|
|
14959
|
+
}
|
|
14960
|
+
const deviceCode = started?.deviceCode;
|
|
14961
|
+
const userCode = started?.userCode;
|
|
14962
|
+
if (typeof deviceCode !== "string" || typeof userCode !== "string") throw notAvailable(ctx);
|
|
14963
|
+
writeLine(ctx.io.stderr, "");
|
|
14964
|
+
writeLine(ctx.io.stderr, ` 1. Open ${approvalUrl(ctx.baseUrl, started.verificationPath)}`);
|
|
14965
|
+
writeLine(ctx.io.stderr, ` 2. Enter ${userCode} under "Sign in the OpenWOP CLI", and approve.`);
|
|
14966
|
+
writeLine(ctx.io.stderr, "");
|
|
14967
|
+
writeLine(ctx.io.stderr, "Waiting for approval\u2026");
|
|
14968
|
+
const hostLimitS = Number.isFinite(Number(started.expiresIn)) ? Number(started.expiresIn) : 600;
|
|
14969
|
+
const deadline = Date.now() + Math.min(timeoutS ?? hostLimitS, hostLimitS) * 1e3;
|
|
14970
|
+
let intervalS = Number.isFinite(Number(started.interval)) ? Number(started.interval) : 3;
|
|
14971
|
+
for (; ; ) {
|
|
14972
|
+
if (Date.now() >= deadline) throw new CliError("Timed out waiting for approval. Run `openwop login` again for a new code.", 1);
|
|
14973
|
+
await sleep2(Math.max(0, intervalS) * 1e3);
|
|
14974
|
+
let res;
|
|
14975
|
+
try {
|
|
14976
|
+
res = (await requestJson(anon, `${BASE10}/poll`, { method: "POST", body: { deviceCode } })).body;
|
|
14977
|
+
} catch (err) {
|
|
14978
|
+
if (err instanceof HttpError && err.status === 404) {
|
|
14979
|
+
throw new CliError("That sign-in expired or was already used. Run `openwop login` again for a new code.", 1);
|
|
14980
|
+
}
|
|
14981
|
+
throw err;
|
|
14982
|
+
}
|
|
14983
|
+
if (res?.status === "pending") {
|
|
14984
|
+
if (Number.isFinite(Number(res.interval))) intervalS = Number(res.interval);
|
|
14985
|
+
continue;
|
|
14986
|
+
}
|
|
14987
|
+
if (res?.status === "denied") throw new CliError("The sign-in was denied in the app.", 1);
|
|
14988
|
+
if (res?.status === "failed") throw new CliError(`The host could not issue a key: ${String(res.message ?? "unknown reason")}`, 1);
|
|
14989
|
+
if (res?.status === "approved" && typeof res.token === "string") {
|
|
14990
|
+
const configPath = configPathFor(options.profile, ctx.env);
|
|
14991
|
+
saveConfig(configPath, mergeConfig(readConfigSafe(configPath), { host: { baseUrl: ctx.baseUrl, apiKey: res.token } }));
|
|
14992
|
+
const key = res.key ?? {};
|
|
14993
|
+
if (ctx.json) {
|
|
14994
|
+
writeJson(ctx.io.stdout, { status: "signed-in", keyId: key.keyId ?? null, name: key.name ?? null, expiresAt: key.expiresAt ?? null, configPath });
|
|
14995
|
+
return 0;
|
|
14996
|
+
}
|
|
14997
|
+
writeLine(ctx.io.stdout, `Signed in to ${ctx.baseUrl} as \u201C${key.name ?? `CLI: ${label}`}\u201D.`);
|
|
14998
|
+
if (key.expiresAt) writeLine(ctx.io.stdout, `The key expires ${String(key.expiresAt).slice(0, 10)}; run \`openwop login\` again to renew.`);
|
|
14999
|
+
writeLine(ctx.io.stdout, `Saved to ${configPath}. Revoke it any time with \`openwop logout\` or in the app.`);
|
|
15000
|
+
return 0;
|
|
15001
|
+
}
|
|
15002
|
+
throw new CliError("The host answered the sign-in in a shape this CLI does not understand.", 1);
|
|
15003
|
+
}
|
|
15004
|
+
}
|
|
15005
|
+
async function runLogout(ctx, argv) {
|
|
15006
|
+
const { options } = parseOptions(argv, { bool: ["--help"], value: ["--profile"] });
|
|
15007
|
+
if (options.help) {
|
|
15008
|
+
write(ctx.io.stdout, LOGIN_HELP);
|
|
15009
|
+
return 0;
|
|
15010
|
+
}
|
|
15011
|
+
const configPath = configPathFor(options.profile, ctx.env);
|
|
15012
|
+
const config = readConfigSafe(configPath);
|
|
15013
|
+
const saved = config?.host?.apiKey;
|
|
15014
|
+
if (typeof saved !== "string" || saved.length === 0) {
|
|
15015
|
+
if (ctx.json) writeJson(ctx.io.stdout, { status: "not-signed-in" });
|
|
15016
|
+
else writeLine(ctx.io.stdout, "Not signed in: no key is saved in your config.");
|
|
15017
|
+
return 0;
|
|
15018
|
+
}
|
|
15019
|
+
let revoked = false;
|
|
15020
|
+
let note = "";
|
|
15021
|
+
try {
|
|
15022
|
+
await requestJson({ ...ctx, apiKey: saved }, `${BASE10}/logout`, { method: "POST", body: {} });
|
|
15023
|
+
revoked = true;
|
|
15024
|
+
} catch (err) {
|
|
15025
|
+
if (err instanceof HttpError && err.status === 401) note = "The host no longer recognised the key (already revoked or expired).";
|
|
15026
|
+
else if (err instanceof HttpError && err.status === 404) note = "This host has no sign-out route; the key was NOT revoked there \u2014 revoke it in the app.";
|
|
15027
|
+
else if (err instanceof HttpError && err.status === 400) note = "The saved key is not one a sign-in issued; it was NOT revoked on the host \u2014 revoke it in the app.";
|
|
15028
|
+
else throw err;
|
|
15029
|
+
}
|
|
15030
|
+
unsetByPath(config, "host.apiKey");
|
|
15031
|
+
saveConfig(configPath, config);
|
|
15032
|
+
if (ctx.json) {
|
|
15033
|
+
writeJson(ctx.io.stdout, { status: "signed-out", revokedOnHost: revoked, ...note ? { note } : {} });
|
|
15034
|
+
return 0;
|
|
15035
|
+
}
|
|
15036
|
+
writeLine(ctx.io.stdout, revoked ? "Signed out. The key was revoked on the host and removed from your config." : `Removed the key from your config. ${note}`);
|
|
15037
|
+
return 0;
|
|
15038
|
+
}
|
|
15039
|
+
|
|
14836
15040
|
// src/cli/providers.ts
|
|
14837
15041
|
var PROVIDERS_HELP = `Usage:
|
|
14838
15042
|
openwop providers list [--json]
|
|
@@ -14972,30 +15176,49 @@ import { existsSync as existsSync6, readFileSync as readFileSync15, writeFileSyn
|
|
|
14972
15176
|
import { join as join6, resolve as resolvePath8 } from "node:path";
|
|
14973
15177
|
import { homedir as homedir3 } from "node:os";
|
|
14974
15178
|
var PACKS_HELP = `Usage:
|
|
14975
|
-
openwop packs search [query] [--registry-url url] [--limit n] [--json]
|
|
14976
|
-
openwop packs info <name> [--version v] [--registry-url url] [--json]
|
|
14977
|
-
openwop packs install <name>[@version] [--version v] [--dir path] [--no-verify] [--registry-url url] [--json]
|
|
14978
|
-
openwop packs publish <dir> [--key ed25519.pem] [--key-id id] [--out dir] [--json]
|
|
14979
|
-
openwop packs yank <name>[@version] [--version v] [--undo] [--json]
|
|
15179
|
+
openwop packs search [query] [--registry-url url] [--tree v1|v2] [--limit n] [--json]
|
|
15180
|
+
openwop packs info <name> [--version v] [--registry-url url] [--tree v1|v2] [--json]
|
|
15181
|
+
openwop packs install <name>[@version|@range] [--version v] [--dir path] [--no-verify] [--registry-url url] [--tree v1|v2] [--json]
|
|
15182
|
+
openwop packs publish <dir> [--key ed25519.pem] [--key-id id] [--out dir] [--tree v1|v2] [--json]
|
|
15183
|
+
openwop packs yank <name>[@version] [--version v] [--undo] [--tree v1|v2] [--json]
|
|
14980
15184
|
|
|
14981
15185
|
Operates the signed node-pack registry (default: https://packs.openwop.dev,
|
|
14982
15186
|
override with --registry-url or OPENWOP_REGISTRY_URL). The registry is a
|
|
14983
15187
|
separate surface from the host --base-url.
|
|
14984
15188
|
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
15189
|
+
Every registry path is resolved through the registry's
|
|
15190
|
+
/.well-known/openwop-registry.json \`endpoints\` map (spec/v2/core/packs.md
|
|
15191
|
+
\xA7"The registry tree"), preferring the v2 tree (\`endpoints.v2\`). The v1 tree
|
|
15192
|
+
is used only when the registry names no v2 tree (or publishes no discovery
|
|
15193
|
+
document), or when you pass --tree v1.
|
|
15194
|
+
|
|
15195
|
+
search Reads the registry index (endpoints.registryIndex, /v2/index.json on
|
|
15196
|
+
packs.openwop.dev) and filters the catalog client-side.
|
|
15197
|
+
info Reads the pack index (endpoints.packMetadata) + the version manifest
|
|
15198
|
+
with --version.
|
|
15199
|
+
install Resolves the version, downloads the tarball, checks its sha256
|
|
15200
|
+
integrity against the manifest, and verifies the signature (skip
|
|
15201
|
+
with --no-verify). v2: the detached Ed25519 .sig over the in-tarball
|
|
15202
|
+
pack.json (RFC 8785 canonical JSON), under the registry key named by
|
|
15203
|
+
signing.keyId, whose permittedNamespaces must cover the pack name.
|
|
15204
|
+
Places the tarball + manifest under ~/.openwop/packs/{name}/{version}/
|
|
14991
15205
|
(override with --dir).
|
|
15206
|
+
Version: none = the pack's latest unyanked version; a range (^1.2,
|
|
15207
|
+
~1.2.0, 1.x, >=1.0.0 <2.0.0) = the highest matching unyanked version;
|
|
15208
|
+
an exact version is a pin and MAY install a yanked version (with a
|
|
15209
|
+
warning). A deprecated version installs with a warning.
|
|
14992
15210
|
publish The reference registry has NO write API (publish is PR-based). This
|
|
14993
15211
|
packages + Ed25519-signs a local pack dir into a signed tarball +
|
|
14994
|
-
sidecars
|
|
14995
|
-
|
|
15212
|
+
sidecars, ready to commit + open a PR against openwop-registry. The
|
|
15213
|
+
default (v2) signing block is { keyId, scheme: "ed25519-canonical-json" };
|
|
15214
|
+
--tree v1 writes the legacy v1 block. Private key: --key, else
|
|
15215
|
+
~/.openwop-keys/{keyId}.private.pem.
|
|
14996
15216
|
yank Edits a local registry checkout \u2014 flips "yanked": true in the version
|
|
14997
|
-
manifest (--
|
|
15217
|
+
manifest under registry/v2/ (--tree v1 for the frozen v1 tree;
|
|
15218
|
+
--undo reverses). The published change lands via PR + a
|
|
14998
15219
|
build-index.mjs rerun. Run from inside the repo.
|
|
15220
|
+
|
|
15221
|
+
Exit codes: 0 ok \xB7 1 verification / integrity / resolution failure \xB7 2 usage.
|
|
14999
15222
|
`;
|
|
15000
15223
|
async function runPacks(ctx, argv) {
|
|
15001
15224
|
const sub = argv[0];
|
|
@@ -15023,6 +15246,71 @@ Run \`openwop packs --help\` for usage.`);
|
|
|
15023
15246
|
function registryUrlFor(options, env) {
|
|
15024
15247
|
return normalizeBaseUrl(options.registryUrl ?? env.OPENWOP_REGISTRY_URL ?? DEFAULT_REGISTRY_URL);
|
|
15025
15248
|
}
|
|
15249
|
+
var REQUIRED_ENDPOINTS = ["registryIndex", "packMetadata", "versionManifest", "versionTarball", "versionSignature"];
|
|
15250
|
+
var LEGACY_V1_ENDPOINTS = {
|
|
15251
|
+
registryIndex: "/v1/index.json",
|
|
15252
|
+
packMetadata: "/v1/packs/{name}/index.json",
|
|
15253
|
+
versionManifest: "/v1/packs/{name}/-/{version}.json",
|
|
15254
|
+
versionTarball: "/v1/packs/{name}/-/{version}.tgz",
|
|
15255
|
+
versionSignature: "/v1/packs/{name}/-/{version}.sig",
|
|
15256
|
+
publicKey: "/keys/{keyId}.pub"
|
|
15257
|
+
};
|
|
15258
|
+
function treeOption(options) {
|
|
15259
|
+
if (options.tree === void 0) return void 0;
|
|
15260
|
+
if (options.tree !== "v1" && options.tree !== "v2") throw new CliError(`--tree must be v1 or v2 (got "${options.tree}").`);
|
|
15261
|
+
return options.tree;
|
|
15262
|
+
}
|
|
15263
|
+
function stringTemplates(value) {
|
|
15264
|
+
const out = {};
|
|
15265
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return out;
|
|
15266
|
+
for (const [k, v] of Object.entries(value)) if (typeof v === "string" && !k.startsWith("_")) out[k] = v;
|
|
15267
|
+
return out;
|
|
15268
|
+
}
|
|
15269
|
+
async function resolveRegistry(ctx, options) {
|
|
15270
|
+
const url = registryUrlFor(options, ctx.env);
|
|
15271
|
+
const forced = treeOption(options);
|
|
15272
|
+
let doc = null;
|
|
15273
|
+
try {
|
|
15274
|
+
doc = await registryJson(ctx, url, "/.well-known/openwop-registry.json");
|
|
15275
|
+
} catch (err) {
|
|
15276
|
+
if (!(err instanceof HttpError) || err.status !== 404) throw err;
|
|
15277
|
+
}
|
|
15278
|
+
const endpoints = doc && typeof doc.endpoints === "object" ? doc.endpoints : null;
|
|
15279
|
+
if (!endpoints) {
|
|
15280
|
+
if (forced === "v2") throw new CliError(`${url} publishes no /.well-known/openwop-registry.json endpoints map, so no v2 tree can be resolved.`, 1);
|
|
15281
|
+
return { url, tree: "v1", endpoints: { ...LEGACY_V1_ENDPOINTS }, signingKeys: null, source: "legacy-v1" };
|
|
15282
|
+
}
|
|
15283
|
+
const flat = stringTemplates(endpoints);
|
|
15284
|
+
const publicKey = flat.publicKey ?? LEGACY_V1_ENDPOINTS.publicKey;
|
|
15285
|
+
const v2 = stringTemplates(endpoints.v2);
|
|
15286
|
+
const hasV2 = REQUIRED_ENDPOINTS.every((k) => typeof v2[k] === "string");
|
|
15287
|
+
let tree;
|
|
15288
|
+
let map;
|
|
15289
|
+
let source;
|
|
15290
|
+
if (forced === "v2" || forced === void 0 && hasV2) {
|
|
15291
|
+
if (!hasV2) throw new CliError(`${url} names no v2 tree in its discovery document (endpoints.v2); retry with --tree v1.`, 1);
|
|
15292
|
+
tree = "v2";
|
|
15293
|
+
map = v2;
|
|
15294
|
+
source = "endpoints.v2";
|
|
15295
|
+
} else {
|
|
15296
|
+
const v1 = stringTemplates(endpoints.v1);
|
|
15297
|
+
tree = "v1";
|
|
15298
|
+
map = { ...flat, ...v1 };
|
|
15299
|
+
source = "endpoints.v1";
|
|
15300
|
+
}
|
|
15301
|
+
for (const k of REQUIRED_ENDPOINTS) {
|
|
15302
|
+
if (typeof map[k] !== "string") throw new CliError(`${url}'s discovery document names no ${tree} "${k}" endpoint.`, 1);
|
|
15303
|
+
}
|
|
15304
|
+
return { url, tree, endpoints: { ...map, publicKey }, signingKeys: Array.isArray(doc.signingKeys) ? doc.signingKeys : null, source };
|
|
15305
|
+
}
|
|
15306
|
+
function registryPath(view, key, params = {}) {
|
|
15307
|
+
const template = view.endpoints[key];
|
|
15308
|
+
if (typeof template !== "string") throw new CliError(`${view.url}'s discovery document names no "${key}" endpoint.`, 1);
|
|
15309
|
+
return template.replace(/\{([A-Za-z]+)\}/g, (_m, p) => {
|
|
15310
|
+
if (params[p] === void 0) throw new CliError(`Registry endpoint ${key} needs {${p}}.`, 1);
|
|
15311
|
+
return encodeURIComponent(params[p]);
|
|
15312
|
+
});
|
|
15313
|
+
}
|
|
15026
15314
|
async function registryJson(ctx, registryUrl, path) {
|
|
15027
15315
|
const url = new URL(path, registryUrl + "/");
|
|
15028
15316
|
const res = await ctx.fetchImpl(url, { method: "GET", headers: { accept: "application/json" } });
|
|
@@ -15043,15 +15331,15 @@ async function registryBytes(ctx, registryUrl, path) {
|
|
|
15043
15331
|
async function runPacksSearch(ctx, argv) {
|
|
15044
15332
|
const { options, positionals } = parseOptions(argv, {
|
|
15045
15333
|
bool: ["--help"],
|
|
15046
|
-
value: ["--registry-url", "--limit"]
|
|
15334
|
+
value: ["--registry-url", "--limit", "--tree"]
|
|
15047
15335
|
});
|
|
15048
15336
|
if (options.help) {
|
|
15049
15337
|
write(ctx.io.stdout, PACKS_HELP);
|
|
15050
15338
|
return 0;
|
|
15051
15339
|
}
|
|
15052
15340
|
const query = String(positionals[0] ?? "").toLowerCase();
|
|
15053
|
-
const
|
|
15054
|
-
const index = await registryJson(ctx,
|
|
15341
|
+
const registry = await resolveRegistry(ctx, options);
|
|
15342
|
+
const index = await registryJson(ctx, registry.url, registryPath(registry, "registryIndex"));
|
|
15055
15343
|
const packs = Array.isArray(index?.packs) ? index.packs : [];
|
|
15056
15344
|
const matched = packs.filter((p) => {
|
|
15057
15345
|
if (!query) return true;
|
|
@@ -15060,7 +15348,7 @@ async function runPacksSearch(ctx, argv) {
|
|
|
15060
15348
|
});
|
|
15061
15349
|
const limit = Number(options.limit ?? 30);
|
|
15062
15350
|
if (ctx.json) {
|
|
15063
|
-
writeJson(ctx.io.stdout, { query: positionals[0] ?? "", total: matched.length, packs: matched });
|
|
15351
|
+
writeJson(ctx.io.stdout, { query: positionals[0] ?? "", tree: registry.tree, total: matched.length, packs: matched });
|
|
15064
15352
|
return 0;
|
|
15065
15353
|
}
|
|
15066
15354
|
if (matched.length === 0) {
|
|
@@ -15083,7 +15371,7 @@ async function runPacksSearch(ctx, argv) {
|
|
|
15083
15371
|
async function runPacksInfo(ctx, argv) {
|
|
15084
15372
|
const { options, positionals } = parseOptions(argv, {
|
|
15085
15373
|
bool: ["--help"],
|
|
15086
|
-
value: ["--registry-url", "--version"]
|
|
15374
|
+
value: ["--registry-url", "--version", "--tree"]
|
|
15087
15375
|
});
|
|
15088
15376
|
if (options.help) {
|
|
15089
15377
|
write(ctx.io.stdout, PACKS_HELP);
|
|
@@ -15091,15 +15379,11 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15091
15379
|
}
|
|
15092
15380
|
const name = positionals[0];
|
|
15093
15381
|
if (!name) throw new CliError("packs info requires a pack name.\nUsage: openwop packs info <name> [--version v]");
|
|
15094
|
-
const
|
|
15095
|
-
const pack = await
|
|
15382
|
+
const registry = await resolveRegistry(ctx, options);
|
|
15383
|
+
const pack = await packMetadata(ctx, registry, name);
|
|
15096
15384
|
let versionManifest = null;
|
|
15097
15385
|
if (options.version) {
|
|
15098
|
-
versionManifest = await registryJson(
|
|
15099
|
-
ctx,
|
|
15100
|
-
registryUrl,
|
|
15101
|
-
`/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(options.version)}.json`
|
|
15102
|
-
);
|
|
15386
|
+
versionManifest = await registryJson(ctx, registry.url, registryPath(registry, "versionManifest", { name, version: options.version }));
|
|
15103
15387
|
}
|
|
15104
15388
|
if (ctx.json) {
|
|
15105
15389
|
writeJson(ctx.io.stdout, versionManifest ? { ...pack, requestedVersion: versionManifest } : pack);
|
|
@@ -15111,7 +15395,8 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15111
15395
|
`Latest: ${pack.latest ?? "(none)"}`,
|
|
15112
15396
|
`License: ${pack.license || "\u2014"}`,
|
|
15113
15397
|
`Author: ${pack.author || "\u2014"}`,
|
|
15114
|
-
`Description: ${pack.description || "\u2014"}
|
|
15398
|
+
`Description: ${pack.description || "\u2014"}`,
|
|
15399
|
+
`Tree: ${registry.tree}`
|
|
15115
15400
|
];
|
|
15116
15401
|
if (pack.homepage) lines.push(`Homepage: ${pack.homepage}`);
|
|
15117
15402
|
lines.push("");
|
|
@@ -15121,7 +15406,7 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15121
15406
|
const rows = versions.map((v) => ({
|
|
15122
15407
|
version: v.version,
|
|
15123
15408
|
keyId: v.signingKeyId ?? "",
|
|
15124
|
-
flags: v
|
|
15409
|
+
flags: versionFlags(v),
|
|
15125
15410
|
integrity: typeof v.integrity === "string" ? v.integrity.slice(0, 24) + "\u2026" : ""
|
|
15126
15411
|
}));
|
|
15127
15412
|
writeLine(ctx.io.stdout, formatTable(rows, ["version", "keyId", "flags", "integrity"]));
|
|
@@ -15130,38 +15415,52 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15130
15415
|
}
|
|
15131
15416
|
return 0;
|
|
15132
15417
|
}
|
|
15418
|
+
async function packMetadata(ctx, registry, name) {
|
|
15419
|
+
try {
|
|
15420
|
+
return await registryJson(ctx, registry.url, registryPath(registry, "packMetadata", { name }));
|
|
15421
|
+
} catch (err) {
|
|
15422
|
+
if (err instanceof HttpError && err.status === 404) throw new CliError(`No pack named ${name} on ${registry.url} (${registry.tree} tree). Try \`openwop packs search\`.`, 2);
|
|
15423
|
+
throw err;
|
|
15424
|
+
}
|
|
15425
|
+
}
|
|
15426
|
+
function versionFlags(v) {
|
|
15427
|
+
return v?.yanked ? "yanked" : v?.versionDeprecated || v?.deprecated ? "deprecated" : "";
|
|
15428
|
+
}
|
|
15133
15429
|
async function runPacksInstall(ctx, argv) {
|
|
15134
15430
|
const { options, positionals } = parseOptions(argv, {
|
|
15135
15431
|
bool: ["--help", "--no-verify"],
|
|
15136
|
-
value: ["--registry-url", "--version", "--dir"]
|
|
15432
|
+
value: ["--registry-url", "--version", "--dir", "--tree"]
|
|
15137
15433
|
});
|
|
15138
15434
|
if (options.help) {
|
|
15139
15435
|
write(ctx.io.stdout, PACKS_HELP);
|
|
15140
15436
|
return 0;
|
|
15141
15437
|
}
|
|
15142
15438
|
let name = positionals[0];
|
|
15143
|
-
let
|
|
15439
|
+
let requested = options.version;
|
|
15144
15440
|
if (name && name.includes("@")) {
|
|
15145
15441
|
const at = name.lastIndexOf("@");
|
|
15146
|
-
|
|
15442
|
+
requested = requested ?? name.slice(at + 1);
|
|
15147
15443
|
name = name.slice(0, at);
|
|
15148
15444
|
}
|
|
15149
15445
|
if (!name) throw new CliError("packs install requires a pack name.\nUsage: openwop packs install <name>[@version] [--version v]");
|
|
15150
|
-
const
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15446
|
+
const registry = await resolveRegistry(ctx, options);
|
|
15447
|
+
const warnings = [];
|
|
15448
|
+
let version;
|
|
15449
|
+
if (requested && isExactVersion(requested)) {
|
|
15450
|
+
version = requested;
|
|
15451
|
+
} else {
|
|
15452
|
+
const pack = await packMetadata(ctx, registry, name);
|
|
15453
|
+
version = resolveVersion(name, pack, requested);
|
|
15155
15454
|
}
|
|
15156
|
-
const manifest = await registryJson(
|
|
15157
|
-
ctx,
|
|
15158
|
-
registryUrl,
|
|
15159
|
-
`/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.json`
|
|
15160
|
-
);
|
|
15455
|
+
const manifest = await registryJson(ctx, registry.url, registryPath(registry, "versionManifest", { name, version }));
|
|
15161
15456
|
if (manifest?.yanked) {
|
|
15162
|
-
|
|
15457
|
+
warnings.push(`${name}@${version} is yanked${manifest.yankedReason ? ` (${manifest.yankedReason})` : ""}; installing it because it was pinned exactly.`);
|
|
15163
15458
|
}
|
|
15164
|
-
|
|
15459
|
+
if (manifest?.versionDeprecated || manifest?.deprecated) {
|
|
15460
|
+
const reason = manifest.deprecationReason ?? (typeof manifest.deprecated === "string" ? manifest.deprecated : "");
|
|
15461
|
+
warnings.push(`${name}@${version} is deprecated${reason ? ` (${reason})` : ""}${manifest.supersededBy ? `; superseded by ${manifest.supersededBy}` : ""}.`);
|
|
15462
|
+
}
|
|
15463
|
+
const tgz = await registryBytes(ctx, registry.url, registryPath(registry, "versionTarball", { name, version }));
|
|
15165
15464
|
const integrity = "sha256-" + createHash("sha256").update(tgz).digest("base64");
|
|
15166
15465
|
if (manifest?.integrity && manifest.integrity !== integrity) {
|
|
15167
15466
|
throw new CliError(
|
|
@@ -15171,19 +15470,7 @@ async function runPacksInstall(ctx, argv) {
|
|
|
15171
15470
|
}
|
|
15172
15471
|
let verifyResult = "skipped";
|
|
15173
15472
|
if (!options.noVerify) {
|
|
15174
|
-
|
|
15175
|
-
if (!keyId) throw new CliError(`${name}@${version} manifest has no signing key reference; re-run with --no-verify to bypass.`, 1);
|
|
15176
|
-
const sig = await registryBytes(ctx, registryUrl, `/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.sig`);
|
|
15177
|
-
if (sig.length !== 64) throw new CliError(`Signature for ${name}@${version} is ${sig.length} bytes; expected 64 for Ed25519.`, 1);
|
|
15178
|
-
const pubPem = (await registryBytes(ctx, registryUrl, `/keys/${encodeURIComponent(keyId)}.pub`)).toString("utf8");
|
|
15179
|
-
const publicKey = createPublicKey(pubPem);
|
|
15180
|
-
const method = manifest?.signing?.method ?? "ed25519";
|
|
15181
|
-
const signedBytes = method === "manual" ? extractPackJsonBytes(tgz) : tgz;
|
|
15182
|
-
const valid = ed25519Verify(null, signedBytes, publicKey, sig);
|
|
15183
|
-
if (!valid) {
|
|
15184
|
-
throw new CliError(`Signature verification FAILED for ${name}@${version} (keyId=${keyId}, method=${method}).`, 1);
|
|
15185
|
-
}
|
|
15186
|
-
verifyResult = `verified (keyId=${keyId}, method=${method})`;
|
|
15473
|
+
verifyResult = registry.tree === "v2" ? await verifyV2(ctx, registry, name, version, manifest, tgz) : await verifyV1(ctx, registry, name, version, manifest, tgz);
|
|
15187
15474
|
}
|
|
15188
15475
|
const baseDir = options.dir ? resolvePath8(ctx.cwd, options.dir) : join6(configHomeDir(ctx.env), "packs");
|
|
15189
15476
|
const destDir = join6(baseDir, name, version);
|
|
@@ -15192,27 +15479,182 @@ async function runPacksInstall(ctx, argv) {
|
|
|
15192
15479
|
const manifestPath = join6(destDir, `${version}.json`);
|
|
15193
15480
|
writeFileSync10(tgzPath, tgz);
|
|
15194
15481
|
writeFileSync10(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
15482
|
+
for (const w of warnings) writeLine(ctx.io.stderr, `warning: ${w}`);
|
|
15195
15483
|
if (ctx.json) {
|
|
15196
15484
|
writeJson(ctx.io.stdout, {
|
|
15197
15485
|
name,
|
|
15198
15486
|
version,
|
|
15487
|
+
tree: registry.tree,
|
|
15199
15488
|
integrity,
|
|
15200
15489
|
signature: verifyResult,
|
|
15490
|
+
yanked: Boolean(manifest?.yanked),
|
|
15491
|
+
deprecated: Boolean(manifest?.versionDeprecated || manifest?.deprecated),
|
|
15201
15492
|
tarball: tgzPath,
|
|
15202
15493
|
manifest: manifestPath
|
|
15203
15494
|
});
|
|
15204
15495
|
return 0;
|
|
15205
15496
|
}
|
|
15206
|
-
writeLine(ctx.io.stdout, `Installed ${name}@${version}`);
|
|
15497
|
+
writeLine(ctx.io.stdout, `Installed ${name}@${version} (${registry.tree} tree)`);
|
|
15207
15498
|
writeLine(ctx.io.stdout, ` signature: ${verifyResult}`);
|
|
15208
15499
|
writeLine(ctx.io.stdout, ` integrity: ${integrity}`);
|
|
15209
15500
|
writeLine(ctx.io.stdout, ` tarball: ${tgzPath}`);
|
|
15210
15501
|
return 0;
|
|
15211
15502
|
}
|
|
15503
|
+
async function verifyV1(ctx, registry, name, version, manifest, tgz) {
|
|
15504
|
+
const keyId = manifest?.signing?.keyId ?? manifest?.signing?.publicKeyRef;
|
|
15505
|
+
if (!keyId) throw new CliError(`${name}@${version} manifest has no signing key reference; re-run with --no-verify to bypass.`, 1);
|
|
15506
|
+
const sig = await registryBytes(ctx, registry.url, registryPath(registry, "versionSignature", { name, version }));
|
|
15507
|
+
if (sig.length !== 64) throw new CliError(`Signature for ${name}@${version} is ${sig.length} bytes; expected 64 for Ed25519.`, 1);
|
|
15508
|
+
const pubPem = (await registryBytes(ctx, registry.url, registryPath(registry, "publicKey", { keyId }))).toString("utf8");
|
|
15509
|
+
const method = manifest?.signing?.method ?? "ed25519";
|
|
15510
|
+
const signedBytes = method === "manual" ? extractPackJsonBytes(tgz) : tgz;
|
|
15511
|
+
if (!ed25519Verify(null, signedBytes, createPublicKey(pubPem), sig)) {
|
|
15512
|
+
throw new CliError(`Signature verification FAILED for ${name}@${version} (keyId=${keyId}, method=${method}).`, 1);
|
|
15513
|
+
}
|
|
15514
|
+
return `verified (keyId=${keyId}, method=${method})`;
|
|
15515
|
+
}
|
|
15516
|
+
var V2_SCHEME = "ed25519-canonical-json";
|
|
15517
|
+
async function verifyV2(ctx, registry, name, version, manifest, tgz) {
|
|
15518
|
+
const fail3 = (why) => new CliError(`Signature verification FAILED for ${name}@${version} (pack_signature_invalid): ${why}`, 1);
|
|
15519
|
+
const signing = manifest?.signing;
|
|
15520
|
+
if (!signing || typeof signing !== "object") throw fail3("the version manifest has no signing block; re-run with --no-verify to bypass.");
|
|
15521
|
+
const legacy = ["method", "publicKeyRef", "signatureRef"].filter((k) => k in signing);
|
|
15522
|
+
if (legacy.length > 0) throw fail3(`a v2 signing block carries ${legacy.join(", ")} (v1 fields; a v2 block is { keyId, scheme }).`);
|
|
15523
|
+
if (signing.scheme !== V2_SCHEME) throw fail3(`signing.scheme is ${JSON.stringify(signing.scheme)}; the only v2 scheme is ${V2_SCHEME}.`);
|
|
15524
|
+
const keyId = signing.keyId;
|
|
15525
|
+
if (typeof keyId !== "string" || keyId === "") throw fail3("signing.keyId is missing.");
|
|
15526
|
+
if (!registry.signingKeys) throw fail3(`${registry.url} publishes no signingKeys[], so the namespace check cannot run.`);
|
|
15527
|
+
const key = registry.signingKeys.find((k) => k && k.keyId === keyId);
|
|
15528
|
+
if (!key) throw fail3(`keyId ${keyId} is not listed in the registry's signingKeys[].`);
|
|
15529
|
+
const permitted = Array.isArray(key.permittedNamespaces) ? key.permittedNamespaces : [];
|
|
15530
|
+
if (!permitted.some((pattern) => namespaceCovers(pattern, name))) {
|
|
15531
|
+
throw fail3(`key ${keyId} is not permitted to sign ${name} (permittedNamespaces: ${permitted.join(", ") || "none"}).`);
|
|
15532
|
+
}
|
|
15533
|
+
const packJson = extractPackJsonBytes(tgz);
|
|
15534
|
+
let embedded;
|
|
15535
|
+
try {
|
|
15536
|
+
embedded = JSON.parse(packJson.toString("utf8"));
|
|
15537
|
+
} catch {
|
|
15538
|
+
throw fail3("the in-tarball pack.json is not JSON.");
|
|
15539
|
+
}
|
|
15540
|
+
if (embedded?.name !== name || embedded?.version !== version) {
|
|
15541
|
+
throw fail3(`the in-tarball pack.json is ${embedded?.name}@${embedded?.version}, not ${name}@${version}.`);
|
|
15542
|
+
}
|
|
15543
|
+
if (embedded?.signing?.keyId !== void 0 && embedded.signing.keyId !== keyId) {
|
|
15544
|
+
throw fail3(`the in-tarball pack.json names signer ${embedded.signing.keyId}, the version manifest ${keyId}.`);
|
|
15545
|
+
}
|
|
15546
|
+
if (canonicalJsonStringify(embedded) !== packJson.toString("utf8")) {
|
|
15547
|
+
throw fail3("the in-tarball pack.json is not in canonical (RFC 8785) form, so its bytes are not what ed25519-canonical-json signs.");
|
|
15548
|
+
}
|
|
15549
|
+
const sig = await registryBytes(ctx, registry.url, registryPath(registry, "versionSignature", { name, version }));
|
|
15550
|
+
if (sig.length !== 64) throw fail3(`the signature is ${sig.length} bytes; expected 64 for Ed25519.`);
|
|
15551
|
+
const keyPath = typeof key.publicKeyUrl === "string" && key.publicKeyUrl !== "" ? key.publicKeyUrl : registryPath(registry, "publicKey", { keyId });
|
|
15552
|
+
const pubPem = (await registryBytes(ctx, registry.url, keyPath)).toString("utf8");
|
|
15553
|
+
if (!ed25519Verify(null, packJson, createPublicKey(pubPem), sig)) throw fail3(`the signature does not verify under ${keyId}.`);
|
|
15554
|
+
const status = typeof key.status === "string" ? key.status : "active";
|
|
15555
|
+
return `verified (keyId=${keyId}, scheme=${V2_SCHEME}${status !== "active" ? `, key status ${status}` : ""})`;
|
|
15556
|
+
}
|
|
15557
|
+
function namespaceCovers(pattern, name) {
|
|
15558
|
+
if (typeof pattern !== "string") return false;
|
|
15559
|
+
if (pattern.endsWith(".*")) return name.startsWith(pattern.slice(0, -1));
|
|
15560
|
+
return name === pattern;
|
|
15561
|
+
}
|
|
15562
|
+
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
15563
|
+
function isExactVersion(v) {
|
|
15564
|
+
return SEMVER_RE.test(v.trim());
|
|
15565
|
+
}
|
|
15566
|
+
function parseSemver(v) {
|
|
15567
|
+
const m = SEMVER_RE.exec(String(v).trim());
|
|
15568
|
+
if (!m) return null;
|
|
15569
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: m[4] ? m[4].split(".") : [] };
|
|
15570
|
+
}
|
|
15571
|
+
function compareSemver(a, b) {
|
|
15572
|
+
const x = parseSemver(a);
|
|
15573
|
+
const y = parseSemver(b);
|
|
15574
|
+
if (!x || !y) return String(a).localeCompare(String(b));
|
|
15575
|
+
for (const k of ["major", "minor", "patch"]) if (x[k] !== y[k]) return x[k] - y[k];
|
|
15576
|
+
if (x.pre.length === 0 || y.pre.length === 0) return y.pre.length - x.pre.length;
|
|
15577
|
+
for (let i = 0; i < Math.max(x.pre.length, y.pre.length); i++) {
|
|
15578
|
+
const p = x.pre[i];
|
|
15579
|
+
const q = y.pre[i];
|
|
15580
|
+
if (p === void 0) return -1;
|
|
15581
|
+
if (q === void 0) return 1;
|
|
15582
|
+
const pn = /^\d+$/.test(p);
|
|
15583
|
+
const qn = /^\d+$/.test(q);
|
|
15584
|
+
if (pn && qn && Number(p) !== Number(q)) return Number(p) - Number(q);
|
|
15585
|
+
if (pn !== qn) return pn ? -1 : 1;
|
|
15586
|
+
if (p !== q) return p < q ? -1 : 1;
|
|
15587
|
+
}
|
|
15588
|
+
return 0;
|
|
15589
|
+
}
|
|
15590
|
+
function comparator(raw) {
|
|
15591
|
+
const m = /^(\^|~|>=|<=|>|<|=)?\s*v?(\d+|[xX*])(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(-[0-9A-Za-z.-]+)?$/.exec(raw);
|
|
15592
|
+
if (!m) throw new CliError(`Unsupported version range "${raw}". Use an exact version, ^x.y.z, ~x.y.z, x.y.x, or comparators like ">=1.0.0 <2.0.0".`);
|
|
15593
|
+
const op = m[1] ?? "";
|
|
15594
|
+
const wild = (p) => p === void 0 || /^[xX*]$/.test(p);
|
|
15595
|
+
const [maj, min, pat] = [m[2], m[3], m[4]];
|
|
15596
|
+
const n = (p) => wild(p) ? 0 : Number(p);
|
|
15597
|
+
const floor = `${n(maj)}.${n(min)}.${n(pat)}${m[5] ?? ""}`;
|
|
15598
|
+
const cmp = (v) => compareSemver(v, floor);
|
|
15599
|
+
let ceil = null;
|
|
15600
|
+
if (op === "^") {
|
|
15601
|
+
ceil = n(maj) > 0 || wild(min) ? `${n(maj) + 1}.0.0` : n(min) > 0 || wild(pat) ? `0.${n(min) + 1}.0` : `0.0.${n(pat) + 1}`;
|
|
15602
|
+
} else if (op === "~") {
|
|
15603
|
+
ceil = wild(min) ? `${n(maj) + 1}.0.0` : `${n(maj)}.${n(min) + 1}.0`;
|
|
15604
|
+
} else if (op === "" || op === "=") {
|
|
15605
|
+
if (wild(maj)) return () => true;
|
|
15606
|
+
if (wild(min)) ceil = `${n(maj) + 1}.0.0`;
|
|
15607
|
+
else if (wild(pat)) ceil = `${n(maj)}.${n(min) + 1}.0`;
|
|
15608
|
+
else return (v) => cmp(v) === 0;
|
|
15609
|
+
}
|
|
15610
|
+
if (ceil !== null) {
|
|
15611
|
+
const c = ceil;
|
|
15612
|
+
return (v) => cmp(v) >= 0 && compareSemver(v, `${c}-0`) < 0;
|
|
15613
|
+
}
|
|
15614
|
+
switch (op) {
|
|
15615
|
+
case ">=":
|
|
15616
|
+
return (v) => cmp(v) >= 0;
|
|
15617
|
+
case ">":
|
|
15618
|
+
return (v) => cmp(v) > 0;
|
|
15619
|
+
case "<=":
|
|
15620
|
+
return (v) => cmp(v) <= 0;
|
|
15621
|
+
default:
|
|
15622
|
+
return (v) => cmp(v) < 0;
|
|
15623
|
+
}
|
|
15624
|
+
}
|
|
15625
|
+
function satisfiesRange(version, range) {
|
|
15626
|
+
const parsed = parseSemver(version);
|
|
15627
|
+
if (!parsed) return false;
|
|
15628
|
+
return range.split("||").some((set) => {
|
|
15629
|
+
const parts = set.trim().replace(/(>=|<=|>|<|=|\^|~)\s+/g, "$1").split(/\s+/).filter(Boolean);
|
|
15630
|
+
if (parts.length === 0) return true;
|
|
15631
|
+
if (parsed.pre.length > 0 && !parts.some((p) => p.includes("-"))) return false;
|
|
15632
|
+
return parts.every((p) => comparator(p)(version));
|
|
15633
|
+
});
|
|
15634
|
+
}
|
|
15635
|
+
function resolveVersion(name, pack, range) {
|
|
15636
|
+
const versions = Array.isArray(pack?.versions) ? pack.versions : [];
|
|
15637
|
+
const unyanked = versions.filter((v) => v && typeof v.version === "string" && !v.yanked).map((v) => v.version);
|
|
15638
|
+
if (!range || range === "latest") {
|
|
15639
|
+
const latest = typeof pack?.latest === "string" ? pack.latest : void 0;
|
|
15640
|
+
const latestEntry = versions.find((v) => v?.version === latest);
|
|
15641
|
+
if (latest && versions.length === 0) return latest;
|
|
15642
|
+
if (latest && latestEntry && !latestEntry.yanked) return latest;
|
|
15643
|
+
const best = unyanked.filter((v) => (parseSemver(v)?.pre.length ?? 1) === 0).sort(compareSemver).pop() ?? unyanked.sort(compareSemver).pop();
|
|
15644
|
+
if (!best) throw new CliError(`${name} has no installable (unyanked) version; pin an exact version to install a yanked one.`, 1);
|
|
15645
|
+
return best;
|
|
15646
|
+
}
|
|
15647
|
+
const match = unyanked.filter((v) => satisfiesRange(v, range)).sort(compareSemver).pop();
|
|
15648
|
+
if (!match) {
|
|
15649
|
+
const yankedHit = versions.some((v) => v?.yanked && typeof v.version === "string" && satisfiesRange(v.version, range));
|
|
15650
|
+
throw new CliError(`No unyanked version of ${name} satisfies "${range}"${yankedHit ? " (a yanked version does; pin it exactly to install it)" : ""}.`, 1);
|
|
15651
|
+
}
|
|
15652
|
+
return match;
|
|
15653
|
+
}
|
|
15212
15654
|
async function runPacksPublish(ctx, argv) {
|
|
15213
15655
|
const { options, positionals } = parseOptions(argv, {
|
|
15214
15656
|
bool: ["--help"],
|
|
15215
|
-
value: ["--key", "--key-id", "--out"]
|
|
15657
|
+
value: ["--key", "--key-id", "--out", "--tree"]
|
|
15216
15658
|
});
|
|
15217
15659
|
if (options.help) {
|
|
15218
15660
|
write(ctx.io.stdout, PACKS_HELP);
|
|
@@ -15230,9 +15672,10 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15230
15672
|
const version = manifest.version;
|
|
15231
15673
|
if (!name || !version) throw new CliError('pack.json must declare both "name" and "version".');
|
|
15232
15674
|
const keyId = options.keyId ?? "openwop-team-1";
|
|
15675
|
+
const tree = treeOption(options) ?? "v2";
|
|
15233
15676
|
const signedManifest = {
|
|
15234
15677
|
...manifest,
|
|
15235
|
-
signing: { method: "manual", publicKeyRef: keyId, signatureRef: "keys/pack.json.sig" }
|
|
15678
|
+
signing: tree === "v2" ? { keyId, scheme: V2_SCHEME } : { method: "manual", publicKeyRef: keyId, signatureRef: "keys/pack.json.sig" }
|
|
15236
15679
|
};
|
|
15237
15680
|
const canonical = canonicalJsonStringify(signedManifest);
|
|
15238
15681
|
let privateKey;
|
|
@@ -15269,6 +15712,7 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15269
15712
|
name,
|
|
15270
15713
|
version,
|
|
15271
15714
|
keyId,
|
|
15715
|
+
tree,
|
|
15272
15716
|
integrity: `sha256:${sha}`,
|
|
15273
15717
|
tarball: tgzPath,
|
|
15274
15718
|
signature: sigPath,
|
|
@@ -15279,7 +15723,7 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15279
15723
|
});
|
|
15280
15724
|
return 0;
|
|
15281
15725
|
}
|
|
15282
|
-
writeLine(ctx.io.stdout, `Packaged + signed ${name}@${version} (keyId=${keyId})`);
|
|
15726
|
+
writeLine(ctx.io.stdout, `Packaged + signed ${name}@${version} for the ${tree} tree (keyId=${keyId})`);
|
|
15283
15727
|
writeLine(ctx.io.stdout, ` tarball: ${tgzPath}`);
|
|
15284
15728
|
writeLine(ctx.io.stdout, ` signature: ${sigPath}`);
|
|
15285
15729
|
writeLine(ctx.io.stdout, ` manifest: ${manifestOut}`);
|
|
@@ -15291,15 +15735,15 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15291
15735
|
}
|
|
15292
15736
|
writeLine(ctx.io.stdout, "");
|
|
15293
15737
|
writeLine(ctx.io.stdout, "The reference registry has no write API. To publish:");
|
|
15294
|
-
writeLine(ctx.io.stdout, ` 1. Copy the artifacts into registry/
|
|
15295
|
-
writeLine(ctx.io.stdout,
|
|
15738
|
+
writeLine(ctx.io.stdout, ` 1. Copy the artifacts into registry/${tree}/packs/${name}/-/ of an openwop-registry checkout`);
|
|
15739
|
+
writeLine(ctx.io.stdout, ` 2. Run \`node registry/scripts/build-index.mjs --tree ${tree}\` to refresh the index.`);
|
|
15296
15740
|
writeLine(ctx.io.stdout, " 3. Open a pull request (publishMethod: github-pull-request).");
|
|
15297
15741
|
return 0;
|
|
15298
15742
|
}
|
|
15299
15743
|
async function runPacksYank(ctx, argv) {
|
|
15300
15744
|
const { options, positionals } = parseOptions(argv, {
|
|
15301
15745
|
bool: ["--help", "--undo"],
|
|
15302
|
-
value: ["--version"]
|
|
15746
|
+
value: ["--version", "--tree"]
|
|
15303
15747
|
});
|
|
15304
15748
|
if (options.help) {
|
|
15305
15749
|
write(ctx.io.stdout, PACKS_HELP);
|
|
@@ -15316,7 +15760,8 @@ async function runPacksYank(ctx, argv) {
|
|
|
15316
15760
|
throw new CliError("packs yank requires <name>@<version> (or <name> --version v).");
|
|
15317
15761
|
}
|
|
15318
15762
|
const root = requireRepoRoot(ctx);
|
|
15319
|
-
const
|
|
15763
|
+
const tree = treeOption(options) ?? "v2";
|
|
15764
|
+
const manifestPath = join6(root, "registry", tree, "packs", name, "-", `${version}.json`);
|
|
15320
15765
|
if (!existsSync6(manifestPath)) {
|
|
15321
15766
|
throw new CliError(`Version manifest not found: ${manifestPath}
|
|
15322
15767
|
(packs yank edits a local registry checkout; the published change lands via PR.)`);
|
|
@@ -15330,12 +15775,12 @@ async function runPacksYank(ctx, argv) {
|
|
|
15330
15775
|
manifest.yanked = newValue;
|
|
15331
15776
|
writeFileSync10(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
15332
15777
|
if (ctx.json) {
|
|
15333
|
-
writeJson(ctx.io.stdout, { name, version, yanked: newValue, manifest: manifestPath, publishMethod: "github-pull-request" });
|
|
15778
|
+
writeJson(ctx.io.stdout, { name, version, tree, yanked: newValue, manifest: manifestPath, publishMethod: "github-pull-request" });
|
|
15334
15779
|
return 0;
|
|
15335
15780
|
}
|
|
15336
15781
|
writeLine(ctx.io.stdout, `${newValue ? "Yanked" : "Un-yanked"} ${name}@${version}`);
|
|
15337
15782
|
writeLine(ctx.io.stdout, ` edited: ${manifestPath}`);
|
|
15338
|
-
writeLine(ctx.io.stdout,
|
|
15783
|
+
writeLine(ctx.io.stdout, ` Next: run \`node registry/scripts/build-index.mjs --tree ${tree}\`, commit, and open a PR.`);
|
|
15339
15784
|
return 0;
|
|
15340
15785
|
}
|
|
15341
15786
|
function canonicalJsonStringify(value) {
|
|
@@ -17818,7 +18263,7 @@ Examples:
|
|
|
17818
18263
|
openwop profiles pin r_123
|
|
17819
18264
|
openwop profiles activity --limit 10 --status failed
|
|
17820
18265
|
`;
|
|
17821
|
-
var
|
|
18266
|
+
var BASE11 = "/v1/host/openwop-app/profiles";
|
|
17822
18267
|
async function profilesRequest(ctx, path, options, notFoundMsg) {
|
|
17823
18268
|
try {
|
|
17824
18269
|
return await requestJson(ctx, path, options);
|
|
@@ -17915,7 +18360,7 @@ async function runList2(ctx, argv) {
|
|
|
17915
18360
|
write(ctx.io.stdout, "Usage: openwop profiles list [--json]\n");
|
|
17916
18361
|
return 0;
|
|
17917
18362
|
}
|
|
17918
|
-
const res = await profilesRequest(ctx,
|
|
18363
|
+
const res = await profilesRequest(ctx, BASE11);
|
|
17919
18364
|
if (ctx.json) {
|
|
17920
18365
|
writeJson(ctx.io.stdout, res.body);
|
|
17921
18366
|
return 0;
|
|
@@ -17941,7 +18386,7 @@ async function runGet3(ctx, argv) {
|
|
|
17941
18386
|
write(ctx.io.stdout, "Usage: openwop profiles get [<userId>] [--json]\n");
|
|
17942
18387
|
return options.help ? 0 : 2;
|
|
17943
18388
|
}
|
|
17944
|
-
const path = positionals.length === 1 ? `${
|
|
18389
|
+
const path = positionals.length === 1 ? `${BASE11}/${encodeURIComponent(positionals[0])}` : `${BASE11}/me`;
|
|
17945
18390
|
const res = await profilesRequest(ctx, path, void 0, `Profile not found: ${positionals[0] ?? "me"}`);
|
|
17946
18391
|
if (ctx.json) {
|
|
17947
18392
|
writeJson(ctx.io.stdout, res.body);
|
|
@@ -17960,7 +18405,7 @@ async function runActivity(ctx, argv) {
|
|
|
17960
18405
|
if (options.limit !== void 0) qs2.set("limit", String(options.limit));
|
|
17961
18406
|
if (options.status !== void 0) qs2.set("status", String(options.status));
|
|
17962
18407
|
const q = qs2.toString();
|
|
17963
|
-
const res = await profilesRequest(ctx, `${
|
|
18408
|
+
const res = await profilesRequest(ctx, `${BASE11}/me/activity${q ? `?${q}` : ""}`);
|
|
17964
18409
|
if (ctx.json) {
|
|
17965
18410
|
writeJson(ctx.io.stdout, res.body);
|
|
17966
18411
|
return 0;
|
|
@@ -17999,7 +18444,7 @@ async function runEdit(ctx, argv) {
|
|
|
17999
18444
|
if (Array.isArray(options.interests)) body.interests = options.interests;
|
|
18000
18445
|
if (Array.isArray(options.growthInterests)) body.growthInterests = options.growthInterests;
|
|
18001
18446
|
if (options.location !== void 0 || Array.isArray(options.link)) {
|
|
18002
|
-
const current = (await profilesRequest(ctx, `${
|
|
18447
|
+
const current = (await profilesRequest(ctx, `${BASE11}/me`)).body?.contact ?? {};
|
|
18003
18448
|
const contact = { ...current };
|
|
18004
18449
|
if (options.location !== void 0) contact.location = options.location;
|
|
18005
18450
|
if (Array.isArray(options.link)) {
|
|
@@ -18026,7 +18471,7 @@ async function runEdit(ctx, argv) {
|
|
|
18026
18471
|
}
|
|
18027
18472
|
if (Object.keys(avail).length) body.availability = avail;
|
|
18028
18473
|
if (Object.keys(body).length === 0) throw new CliError("Nothing to edit \u2014 pass at least one field.", 2);
|
|
18029
|
-
const res = await profilesRequest(ctx, `${
|
|
18474
|
+
const res = await profilesRequest(ctx, `${BASE11}/me`, { method: "PATCH", body });
|
|
18030
18475
|
if (ctx.json) {
|
|
18031
18476
|
writeJson(ctx.io.stdout, res.body);
|
|
18032
18477
|
return 0;
|
|
@@ -18052,7 +18497,7 @@ async function runSkillsSet(ctx, argv) {
|
|
|
18052
18497
|
if (!Number.isFinite(prof2) || prof2 < 1 || prof2 > 5) throw new CliError(`--skill proficiency must be 1..5 (got '${e}')`, 2);
|
|
18053
18498
|
return { name, proficiency: prof2 };
|
|
18054
18499
|
});
|
|
18055
|
-
const res = await profilesRequest(ctx, `${
|
|
18500
|
+
const res = await profilesRequest(ctx, `${BASE11}/me/skills`, { method: "PUT", body: { skills } });
|
|
18056
18501
|
if (ctx.json) {
|
|
18057
18502
|
writeJson(ctx.io.stdout, res.body);
|
|
18058
18503
|
return 0;
|
|
@@ -18067,7 +18512,7 @@ async function runPortfolioAdd(ctx, argv) {
|
|
|
18067
18512
|
write(ctx.io.stdout, "Usage: openwop profiles portfolio add --token <mediaToken> [--json]\n");
|
|
18068
18513
|
return options.help ? 0 : 2;
|
|
18069
18514
|
}
|
|
18070
|
-
const res = await profilesRequest(ctx, `${
|
|
18515
|
+
const res = await profilesRequest(ctx, `${BASE11}/me/portfolio`, { method: "POST", body: { token: options.token } });
|
|
18071
18516
|
if (ctx.json) {
|
|
18072
18517
|
writeJson(ctx.io.stdout, res.body);
|
|
18073
18518
|
return 0;
|
|
@@ -18081,7 +18526,7 @@ async function runPortfolioRemove(ctx, argv) {
|
|
|
18081
18526
|
write(ctx.io.stdout, "Usage: openwop profiles portfolio remove <mediaToken> [--json]\n");
|
|
18082
18527
|
return options.help ? 0 : 2;
|
|
18083
18528
|
}
|
|
18084
|
-
const res = await profilesRequest(ctx, `${
|
|
18529
|
+
const res = await profilesRequest(ctx, `${BASE11}/me/portfolio/${encodeURIComponent(positionals[0])}`, { method: "DELETE" }, `Portfolio asset not found: ${positionals[0]}`);
|
|
18085
18530
|
if (ctx.json) {
|
|
18086
18531
|
writeJson(ctx.io.stdout, res.body);
|
|
18087
18532
|
return 0;
|
|
@@ -18096,7 +18541,7 @@ async function runPin(ctx, argv, pinned) {
|
|
|
18096
18541
|
`);
|
|
18097
18542
|
return options.help ? 0 : 2;
|
|
18098
18543
|
}
|
|
18099
|
-
const path = `${
|
|
18544
|
+
const path = `${BASE11}/me/pinned-agents/${encodeURIComponent(positionals[0])}`;
|
|
18100
18545
|
const res = await profilesRequest(ctx, path, { method: pinned ? "PUT" : "DELETE" }, `Agent not found: ${positionals[0]}`);
|
|
18101
18546
|
if (ctx.json) {
|
|
18102
18547
|
writeJson(ctx.io.stdout, res.body);
|
|
@@ -18113,7 +18558,7 @@ async function runEndorse(ctx, argv, add) {
|
|
|
18113
18558
|
return options.help ? 0 : 2;
|
|
18114
18559
|
}
|
|
18115
18560
|
const [userId, skill] = positionals;
|
|
18116
|
-
const path = `${
|
|
18561
|
+
const path = `${BASE11}/${encodeURIComponent(userId)}/skills/${encodeURIComponent(skill)}/endorse`;
|
|
18117
18562
|
const res = await profilesRequest(ctx, path, { method: add ? "POST" : "DELETE" }, `Profile or skill not found: ${userId} / ${skill}`);
|
|
18118
18563
|
if (ctx.json) {
|
|
18119
18564
|
writeJson(ctx.io.stdout, res.body);
|
|
@@ -19208,7 +19653,7 @@ Examples:
|
|
|
19208
19653
|
openwop auth saml validate --idp-url http://localhost:9100/idp --variant valid
|
|
19209
19654
|
openwop auth scim provision --op create-user --user-name jo@acme.com --email jo@acme.com
|
|
19210
19655
|
`;
|
|
19211
|
-
var
|
|
19656
|
+
var BASE12 = "/v1/host/openwop-app/auth";
|
|
19212
19657
|
var SECRET_KEY2 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential|bearer|assertion|certificate|cert|pem/i;
|
|
19213
19658
|
function redact2(value) {
|
|
19214
19659
|
return redactSecrets(value, { secret: SECRET_KEY2 });
|
|
@@ -19259,7 +19704,7 @@ async function runStatus(ctx, argv) {
|
|
|
19259
19704
|
const profiles = disc.ok && Array.isArray(disc.body?.capabilities?.auth?.profiles) ? disc.body.capabilities.auth.profiles : [];
|
|
19260
19705
|
const samlAdvertised = profiles.includes("openwop-auth-saml");
|
|
19261
19706
|
const scimAdvertised = profiles.includes("openwop-auth-scim");
|
|
19262
|
-
const metaReachable = samlAdvertised ? (await probeEndpoint(ctx, `${
|
|
19707
|
+
const metaReachable = samlAdvertised ? (await probeEndpoint(ctx, `${BASE12}/saml/sso/metadata`)).ok : false;
|
|
19263
19708
|
if (ctx.json) {
|
|
19264
19709
|
writeJson(ctx.io.stdout, {
|
|
19265
19710
|
authProfilesAdvertised: profiles,
|
|
@@ -19284,7 +19729,7 @@ async function runSamlMetadata(ctx, argv) {
|
|
|
19284
19729
|
write(ctx.io.stdout, "Usage: openwop auth saml metadata [--json]\n");
|
|
19285
19730
|
return 0;
|
|
19286
19731
|
}
|
|
19287
|
-
const res = await authRequest(ctx, `${
|
|
19732
|
+
const res = await authRequest(ctx, `${BASE12}/saml/sso/metadata`, { auth: false }, "SAML SSO is not configured on this host (set OPENWOP_SAML_*).");
|
|
19288
19733
|
const xml = typeof res.body?.raw === "string" ? res.body.raw : typeof res.body === "string" ? res.body : JSON.stringify(res.body);
|
|
19289
19734
|
if (ctx.json) {
|
|
19290
19735
|
writeJson(ctx.io.stdout, { metadata: xml });
|
|
@@ -19302,7 +19747,7 @@ async function runSamlLoginUrl(ctx, argv) {
|
|
|
19302
19747
|
}
|
|
19303
19748
|
const returnTo = options.returnTo ?? "/";
|
|
19304
19749
|
const base16 = ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`;
|
|
19305
|
-
const url = new URL(`${
|
|
19750
|
+
const url = new URL(`${BASE12}/saml/sso/login?returnTo=${encodeURIComponent(returnTo)}`.replace(/^\//, ""), base16);
|
|
19306
19751
|
const res = await ctx.fetchImpl(url, { method: "GET", redirect: "manual" });
|
|
19307
19752
|
if (res.status === 404) {
|
|
19308
19753
|
throw new CliError("SAML SSO is not configured on this host (set OPENWOP_SAML_*).", 1);
|
|
@@ -19327,7 +19772,7 @@ async function runSamlValidate(ctx, argv) {
|
|
|
19327
19772
|
const body = { idpUrl: options.idpUrl, variant: options.variant };
|
|
19328
19773
|
let res;
|
|
19329
19774
|
try {
|
|
19330
|
-
res = await requestJson(ctx, `${
|
|
19775
|
+
res = await requestJson(ctx, `${BASE12}/saml/validate`, { method: "POST", body });
|
|
19331
19776
|
} catch (err) {
|
|
19332
19777
|
if (err instanceof HttpError && err.status === 404) {
|
|
19333
19778
|
throw new CliError("SAML validation seam not configured on this host (set OPENWOP_TEST_SAML_IDP_URL).", 1);
|
|
@@ -19370,7 +19815,7 @@ async function runScimProvision(ctx, argv) {
|
|
|
19370
19815
|
if (options.displayName !== void 0) user.displayName = options.displayName;
|
|
19371
19816
|
const body = { op: options.op, user };
|
|
19372
19817
|
if (options.group !== void 0) body.group = options.group;
|
|
19373
|
-
const res = await authRequest(ctx, `${
|
|
19818
|
+
const res = await authRequest(ctx, `${BASE12}/scim/provision`, { method: "POST", body }, "SCIM provisioning seam not configured on this host (set OPENWOP_TEST_SCIM_URL).");
|
|
19374
19819
|
const safe = redact2(res.body ?? {});
|
|
19375
19820
|
if (ctx.json) {
|
|
19376
19821
|
writeJson(ctx.io.stdout, safe);
|
|
@@ -19394,7 +19839,7 @@ async function runBreakGlass(ctx, argv) {
|
|
|
19394
19839
|
const token = await readSecretInput(ctx, options.tokenFile !== void 0 ? { valueFile: options.tokenFile } : {}, "Break-glass token");
|
|
19395
19840
|
let res;
|
|
19396
19841
|
try {
|
|
19397
|
-
res = await requestJson(ctx, `${
|
|
19842
|
+
res = await requestJson(ctx, `${BASE12}/break-glass`, { method: "POST", body: { token } });
|
|
19398
19843
|
} catch (err) {
|
|
19399
19844
|
if (err instanceof HttpError && err.status === 404) throw new CliError("Break-glass login is not enabled on this host (OPENWOP_BREAKGLASS_ENABLED is not true).", 1);
|
|
19400
19845
|
if (err instanceof HttpError && err.status === 401) throw new CliError("Break-glass login refused (wrong, short, or already-used token, or the host is misconfigured). The attempt was audited.", 4);
|
|
@@ -19723,10 +20168,9 @@ function exitForState(state) {
|
|
|
19723
20168
|
return 1;
|
|
19724
20169
|
}
|
|
19725
20170
|
async function ensureAdvertised3(ctx) {
|
|
19726
|
-
const
|
|
19727
|
-
if (
|
|
19728
|
-
const
|
|
19729
|
-
const advertised = !!(body.capabilities?.agents?.proposals ?? body.agents?.proposals);
|
|
20171
|
+
const record = await advertisedRecord(ctx, (b) => b.capabilities?.agents?.proposals ?? b.agents?.proposals);
|
|
20172
|
+
if (record === void 0) return;
|
|
20173
|
+
const advertised = !!record;
|
|
19730
20174
|
if (!advertised) {
|
|
19731
20175
|
throw new CliError(
|
|
19732
20176
|
"proposals: this host does not advertise the reviewable-learning proposal capability (capabilities.agents.proposals is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -20055,10 +20499,9 @@ function exitForState2(state) {
|
|
|
20055
20499
|
return 1;
|
|
20056
20500
|
}
|
|
20057
20501
|
async function ensureAdvertised4(ctx) {
|
|
20058
|
-
const
|
|
20059
|
-
if (
|
|
20060
|
-
const
|
|
20061
|
-
const advertised = !!(body.capabilities?.agents?.goals ?? body.agents?.goals);
|
|
20502
|
+
const record = await advertisedRecord(ctx, (b) => b.capabilities?.agents?.goals ?? b.agents?.goals);
|
|
20503
|
+
if (record === void 0) return;
|
|
20504
|
+
const advertised = !!record;
|
|
20062
20505
|
if (!advertised) {
|
|
20063
20506
|
throw new CliError(
|
|
20064
20507
|
"goals: this host does not advertise the standing-goals capability (capabilities.agents.goals is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -20326,10 +20769,9 @@ function redact3(value) {
|
|
|
20326
20769
|
return redactSecrets(value, { secret: SECRET_KEY3, keep: REF_ONLY_KEY });
|
|
20327
20770
|
}
|
|
20328
20771
|
async function ensureAdvertised5(ctx) {
|
|
20329
|
-
const
|
|
20330
|
-
if (
|
|
20331
|
-
const
|
|
20332
|
-
const advertised = !!(body.capabilities?.portability ?? body.portability);
|
|
20772
|
+
const record = await advertisedRecord(ctx, (b) => b.capabilities?.portability ?? b.portability);
|
|
20773
|
+
if (record === void 0) return;
|
|
20774
|
+
const advertised = !!record;
|
|
20333
20775
|
if (!advertised) {
|
|
20334
20776
|
throw new CliError(
|
|
20335
20777
|
"portability: this host does not advertise the export/import capability (capabilities.portability is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -20576,10 +21018,7 @@ function exitForState3(state) {
|
|
|
20576
21018
|
return 1;
|
|
20577
21019
|
}
|
|
20578
21020
|
async function triggerBridgeCaps(ctx) {
|
|
20579
|
-
|
|
20580
|
-
if (!wk.ok) return void 0;
|
|
20581
|
-
const body = wk.body && typeof wk.body === "object" ? wk.body : {};
|
|
20582
|
-
return body.capabilities?.triggerBridge ?? body.triggerBridge ?? null;
|
|
21021
|
+
return advertisedRecord(ctx, (b) => b.capabilities?.triggerBridge ?? b.triggerBridge);
|
|
20583
21022
|
}
|
|
20584
21023
|
async function ensureAdvertised6(ctx) {
|
|
20585
21024
|
const tb = await triggerBridgeCaps(ctx);
|
|
@@ -20839,10 +21278,7 @@ function exitForState4(state) {
|
|
|
20839
21278
|
return 1;
|
|
20840
21279
|
}
|
|
20841
21280
|
async function a2aCaps(ctx) {
|
|
20842
|
-
|
|
20843
|
-
if (!wk.ok) return void 0;
|
|
20844
|
-
const body = wk.body && typeof wk.body === "object" ? wk.body : {};
|
|
20845
|
-
return body.capabilities?.a2a ?? body.a2a ?? null;
|
|
21281
|
+
return advertisedRecord(ctx, (b) => b.capabilities?.a2a ?? b.a2a);
|
|
20846
21282
|
}
|
|
20847
21283
|
async function runStatus2(ctx, argv) {
|
|
20848
21284
|
const { options } = parseOptions(argv, { bool: ["--help"] });
|
|
@@ -20859,7 +21295,7 @@ async function runStatus2(ctx, argv) {
|
|
|
20859
21295
|
writeLine(ctx.io.stdout, "This host does not advertise A2A (capabilities.a2a absent).");
|
|
20860
21296
|
return 1;
|
|
20861
21297
|
}
|
|
20862
|
-
writeLine(ctx.io.stdout, `a2a.supported: ${a2a.supported ? "yes" : "no"}`);
|
|
21298
|
+
writeLine(ctx.io.stdout, `a2a.supported: ${a2a.supported ?? true ? "yes" : "no"}`);
|
|
20863
21299
|
if (a2a.agentCardUrl) writeLine(ctx.io.stdout, `agentCardUrl: ${a2a.agentCardUrl}`);
|
|
20864
21300
|
writeLine(ctx.io.stdout, `streaming: ${a2a.streaming ? "yes" : "no"}`);
|
|
20865
21301
|
writeLine(ctx.io.stdout, `pushNotifications: ${a2a.pushNotifications ? "yes" : "no"}`);
|
|
@@ -21048,7 +21484,7 @@ Run \`openwop docs --help\` for usage.`);
|
|
|
21048
21484
|
}
|
|
21049
21485
|
|
|
21050
21486
|
// src/cli/knowledgeSync.ts
|
|
21051
|
-
var
|
|
21487
|
+
var BASE13 = "/v1/host/openwop-app/knowledge-sync";
|
|
21052
21488
|
var KNOWLEDGE_SYNC_HELP = `Usage:
|
|
21053
21489
|
openwop knowledge-sync list --org <orgId> [--json]
|
|
21054
21490
|
openwop knowledge-sync get <sourceId> [--json]
|
|
@@ -21108,12 +21544,12 @@ async function runKnowledgeSync(ctx, argv) {
|
|
|
21108
21544
|
switch (sub) {
|
|
21109
21545
|
case "list": {
|
|
21110
21546
|
if (!options.org) return usage4(ctx, "list --org <orgId>");
|
|
21111
|
-
const res = await requestJson(ctx, withQuery(
|
|
21547
|
+
const res = await requestJson(ctx, withQuery(BASE13, { orgId: String(options.org) }));
|
|
21112
21548
|
return renderList(ctx, res.body, pickArray(res.body, "sources"), ["id", "provider", "collectionId", "cadence", "status", "lastSyncedAt"], "No sync sources.");
|
|
21113
21549
|
}
|
|
21114
21550
|
case "get": {
|
|
21115
21551
|
if (!id) return usage4(ctx, "get <sourceId>");
|
|
21116
|
-
writeJson(ctx.io.stdout, (await requestJson(ctx, `${
|
|
21552
|
+
writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE13}/${enc2(id)}`)).body);
|
|
21117
21553
|
return 0;
|
|
21118
21554
|
}
|
|
21119
21555
|
case "create": {
|
|
@@ -21129,12 +21565,12 @@ async function runKnowledgeSync(ctx, argv) {
|
|
|
21129
21565
|
if (options.cadence) body.cadence = String(options.cadence);
|
|
21130
21566
|
if (options.provider) body.provider = String(options.provider);
|
|
21131
21567
|
if (options.noMedia) body.includeMedia = false;
|
|
21132
|
-
const res = await requestJson(ctx,
|
|
21568
|
+
const res = await requestJson(ctx, BASE13, { method: "POST", body });
|
|
21133
21569
|
return renderDone(ctx, res.body, `Created sync source ${res.body?.source?.id ?? ""}.`);
|
|
21134
21570
|
}
|
|
21135
21571
|
case "browse": {
|
|
21136
21572
|
if (!options.org || !options.connection) return usage4(ctx, "browse --org <orgId> --connection <connectionId> [--folder id]");
|
|
21137
|
-
const res = await requestJson(ctx, withQuery(`${
|
|
21573
|
+
const res = await requestJson(ctx, withQuery(`${BASE13}/browse`, {
|
|
21138
21574
|
orgId: String(options.org),
|
|
21139
21575
|
connectionId: String(options.connection),
|
|
21140
21576
|
folderId: options.folder
|
|
@@ -21143,18 +21579,18 @@ async function runKnowledgeSync(ctx, argv) {
|
|
|
21143
21579
|
}
|
|
21144
21580
|
case "update": {
|
|
21145
21581
|
if (!id || !options.includeMedia && !options.noMedia) return usage4(ctx, "update <sourceId> (--include-media | --no-media)");
|
|
21146
|
-
const res = await requestJson(ctx, `${
|
|
21582
|
+
const res = await requestJson(ctx, `${BASE13}/${enc2(id)}`, { method: "PATCH", body: { includeMedia: !options.noMedia } });
|
|
21147
21583
|
return renderDone(ctx, res.body, `Updated sync source ${id} (media ${options.noMedia ? "excluded" : "included"}).`);
|
|
21148
21584
|
}
|
|
21149
21585
|
case "pause":
|
|
21150
21586
|
case "resume": {
|
|
21151
21587
|
if (!id) return usage4(ctx, `${sub} <sourceId>`);
|
|
21152
|
-
const res = await requestJson(ctx, `${
|
|
21588
|
+
const res = await requestJson(ctx, `${BASE13}/${enc2(id)}/${sub}`, { method: "POST", body: {} });
|
|
21153
21589
|
return renderDone(ctx, res.body, `${sub === "pause" ? "Paused" : "Resumed"} sync source ${id}.`);
|
|
21154
21590
|
}
|
|
21155
21591
|
case "sync": {
|
|
21156
21592
|
if (!id) return usage4(ctx, "sync <sourceId>");
|
|
21157
|
-
const res = await requestJson(ctx, `${
|
|
21593
|
+
const res = await requestJson(ctx, `${BASE13}/${enc2(id)}/sync`, { method: "POST", body: {} });
|
|
21158
21594
|
const r = res.body?.result ?? {};
|
|
21159
21595
|
const counts = Object.entries(r).filter(([, v]) => typeof v === "number").map(([k, v]) => `${k}=${v}`).join(" ");
|
|
21160
21596
|
return renderDone(ctx, res.body, `Synced source ${id}${counts ? `: ${counts}` : "."}`);
|
|
@@ -21162,7 +21598,7 @@ async function runKnowledgeSync(ctx, argv) {
|
|
|
21162
21598
|
case "delete": {
|
|
21163
21599
|
if (!id) return usage4(ctx, "delete <sourceId> [--yes]");
|
|
21164
21600
|
if (!options.yes) throw new CliError(`Refusing to delete sync source ${id} without --yes.`, 2);
|
|
21165
|
-
const res = await requestJson(ctx, `${
|
|
21601
|
+
const res = await requestJson(ctx, `${BASE13}/${enc2(id)}`, { method: "DELETE" });
|
|
21166
21602
|
return renderDone(ctx, res.body, `Deleted sync source ${id}.`);
|
|
21167
21603
|
}
|
|
21168
21604
|
default:
|
|
@@ -21174,7 +21610,7 @@ Run \`openwop knowledge-sync --help\` for usage.`);
|
|
|
21174
21610
|
// src/cli/entities.ts
|
|
21175
21611
|
import { readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "node:fs";
|
|
21176
21612
|
import { resolve as resolvePath9 } from "node:path";
|
|
21177
|
-
var
|
|
21613
|
+
var BASE14 = "/v1/host/openwop-app/entities";
|
|
21178
21614
|
var PUBLIC_BASE2 = "/v1/host/openwop-app/public-entities";
|
|
21179
21615
|
var ENTITIES_HELP = `Usage:
|
|
21180
21616
|
openwop entities locale-context [--json]
|
|
@@ -21296,7 +21732,7 @@ async function runEntities(ctx, argv) {
|
|
|
21296
21732
|
const q = (path, extra = {}) => withQuery(path, { projectId: project4, ...extra });
|
|
21297
21733
|
switch (family) {
|
|
21298
21734
|
case "locale-context": {
|
|
21299
|
-
const res = await requestJson(ctx, q(`${
|
|
21735
|
+
const res = await requestJson(ctx, q(`${BASE14}/locale-context`));
|
|
21300
21736
|
if (ctx.json) {
|
|
21301
21737
|
writeJson(ctx.io.stdout, res.body);
|
|
21302
21738
|
return 0;
|
|
@@ -21328,10 +21764,10 @@ Run \`openwop entities --help\` for usage.`);
|
|
|
21328
21764
|
}
|
|
21329
21765
|
async function types(ctx, sub, pos, o, project4, q) {
|
|
21330
21766
|
const name = pos[0];
|
|
21331
|
-
const typePath = (n) => `${
|
|
21767
|
+
const typePath = (n) => `${BASE14}/types/${enc2(n)}`;
|
|
21332
21768
|
switch (sub) {
|
|
21333
21769
|
case "list": {
|
|
21334
|
-
const res = await requestJson(ctx, q(`${
|
|
21770
|
+
const res = await requestJson(ctx, q(`${BASE14}/types`));
|
|
21335
21771
|
return renderList(
|
|
21336
21772
|
ctx,
|
|
21337
21773
|
res.body,
|
|
@@ -21355,7 +21791,7 @@ async function types(ctx, sub, pos, o, project4, q) {
|
|
|
21355
21791
|
projectId: project4
|
|
21356
21792
|
});
|
|
21357
21793
|
if (!body.name) return usage5(ctx, "types create --name <n> [--fields <json>]");
|
|
21358
|
-
const res = await requestJson(ctx, `${
|
|
21794
|
+
const res = await requestJson(ctx, `${BASE14}/types`, { method: "POST", body });
|
|
21359
21795
|
return renderDone(ctx, res.body, `Created entity type ${res.body?.name ?? body.name}.`);
|
|
21360
21796
|
}
|
|
21361
21797
|
case "update": {
|
|
@@ -21430,7 +21866,7 @@ async function types(ctx, sub, pos, o, project4, q) {
|
|
|
21430
21866
|
async function records(ctx, sub, pos, o, _project, q) {
|
|
21431
21867
|
const [type, id] = pos;
|
|
21432
21868
|
if (!type) return usage5(ctx, `${sub} <type>${sub === "list" || sub === "create" ? "" : " <entityId>"}`);
|
|
21433
|
-
const coll = `${
|
|
21869
|
+
const coll = `${BASE14}/types/${enc2(type)}/entities`;
|
|
21434
21870
|
const recordBody = () => mergeBody(ctx, o, {
|
|
21435
21871
|
values: o.values !== void 0 ? parseJsonFlag("--values", o.values) : void 0,
|
|
21436
21872
|
termIds: csv(o.termIds),
|
|
@@ -21475,7 +21911,7 @@ async function records(ctx, sub, pos, o, _project, q) {
|
|
|
21475
21911
|
async function taxonomies(ctx, sub, pos, o, project4, q) {
|
|
21476
21912
|
switch (sub) {
|
|
21477
21913
|
case "list": {
|
|
21478
|
-
const res = await requestJson(ctx, q(`${
|
|
21914
|
+
const res = await requestJson(ctx, q(`${BASE14}/taxonomies`));
|
|
21479
21915
|
return renderList(ctx, res.body, pickArray(res.body, "taxonomies"), ["name", "displayName"], "No taxonomies.");
|
|
21480
21916
|
}
|
|
21481
21917
|
case "create": {
|
|
@@ -21485,14 +21921,14 @@ async function taxonomies(ctx, sub, pos, o, project4, q) {
|
|
|
21485
21921
|
projectId: project4
|
|
21486
21922
|
});
|
|
21487
21923
|
if (!body.name) return usage5(ctx, "taxonomies create --name <n> [--display-name d]");
|
|
21488
|
-
const res = await requestJson(ctx, `${
|
|
21924
|
+
const res = await requestJson(ctx, `${BASE14}/taxonomies`, { method: "POST", body });
|
|
21489
21925
|
return renderDone(ctx, res.body, `Created taxonomy ${res.body?.name ?? body.name}.`);
|
|
21490
21926
|
}
|
|
21491
21927
|
case "delete": {
|
|
21492
21928
|
const name = pos[0];
|
|
21493
21929
|
if (!name) return usage5(ctx, "taxonomies delete <taxonomy> --yes");
|
|
21494
21930
|
confirm(o, `taxonomy ${name}`);
|
|
21495
|
-
await requestJson(ctx, q(`${
|
|
21931
|
+
await requestJson(ctx, q(`${BASE14}/taxonomies/${enc2(name)}`), { method: "DELETE" });
|
|
21496
21932
|
writeLine(ctx.io.stdout, `Deleted taxonomy ${name}.`);
|
|
21497
21933
|
return 0;
|
|
21498
21934
|
}
|
|
@@ -21503,7 +21939,7 @@ async function taxonomies(ctx, sub, pos, o, project4, q) {
|
|
|
21503
21939
|
async function terms(ctx, sub, pos, o, project4, q) {
|
|
21504
21940
|
const [tax, slug] = pos;
|
|
21505
21941
|
if (!tax) return usage5(ctx, `terms ${sub} <taxonomy>`);
|
|
21506
|
-
const coll = `${
|
|
21942
|
+
const coll = `${BASE14}/taxonomies/${enc2(tax)}/terms`;
|
|
21507
21943
|
switch (sub) {
|
|
21508
21944
|
case "list": {
|
|
21509
21945
|
const res = await requestJson(ctx, q(coll));
|
|
@@ -21555,7 +21991,7 @@ async function terms(ctx, sub, pos, o, project4, q) {
|
|
|
21555
21991
|
async function relationships(ctx, sub, pos, o, project4, q) {
|
|
21556
21992
|
switch (sub) {
|
|
21557
21993
|
case "list": {
|
|
21558
|
-
const res = await requestJson(ctx, q(`${
|
|
21994
|
+
const res = await requestJson(ctx, q(`${BASE14}/relationships`));
|
|
21559
21995
|
return renderList(ctx, res.body, pickArray(res.body, "relationships"), ["fromTypeName", "toTypeName", "cardinality", "onDelete"], "No relationships.");
|
|
21560
21996
|
}
|
|
21561
21997
|
case "create": {
|
|
@@ -21567,14 +22003,14 @@ async function relationships(ctx, sub, pos, o, project4, q) {
|
|
|
21567
22003
|
projectId: project4
|
|
21568
22004
|
});
|
|
21569
22005
|
if (!body.fromTypeName || !body.toTypeName) return usage5(ctx, "relationships create --from <type> --to <type>");
|
|
21570
|
-
const res = await requestJson(ctx, `${
|
|
22006
|
+
const res = await requestJson(ctx, `${BASE14}/relationships`, { method: "POST", body });
|
|
21571
22007
|
return renderDone(ctx, res.body, `Created relationship ${body.fromTypeName} \u2192 ${body.toTypeName}.`);
|
|
21572
22008
|
}
|
|
21573
22009
|
case "delete": {
|
|
21574
22010
|
const [from, to] = pos;
|
|
21575
22011
|
if (!from || !to) return usage5(ctx, "relationships delete <fromType> <toType> --yes");
|
|
21576
22012
|
confirm(o, `relationship ${from} \u2192 ${to}`);
|
|
21577
|
-
await requestJson(ctx, q(`${
|
|
22013
|
+
await requestJson(ctx, q(`${BASE14}/relationships/${enc2(from)}/${enc2(to)}`), { method: "DELETE" });
|
|
21578
22014
|
writeLine(ctx.io.stdout, `Deleted relationship ${from} \u2192 ${to}.`);
|
|
21579
22015
|
return 0;
|
|
21580
22016
|
}
|
|
@@ -22162,7 +22598,7 @@ Run \`openwop production --help\` for usage.`);
|
|
|
22162
22598
|
}
|
|
22163
22599
|
|
|
22164
22600
|
// src/cli/tutorials.ts
|
|
22165
|
-
var
|
|
22601
|
+
var BASE15 = "/v1/host/openwop-app/tutorials";
|
|
22166
22602
|
var TUTORIALS_HELP = `Usage:
|
|
22167
22603
|
openwop tutorials list [--json]
|
|
22168
22604
|
openwop tutorials get <slug> [--json]
|
|
@@ -22170,9 +22606,9 @@ var TUTORIALS_HELP = `Usage:
|
|
|
22170
22606
|
openwop tutorials progress set <tutorialId> --steps <id,id,...> [--json]
|
|
22171
22607
|
openwop tutorials progress clear <tutorialId> [--json]
|
|
22172
22608
|
|
|
22173
|
-
The tutorial library (GET ${
|
|
22174
|
-
\`progress\` reads YOUR saved steps (GET ${
|
|
22175
|
-
tutorial's completed steps and \`progress clear\` resets them (POST ${
|
|
22609
|
+
The tutorial library (GET ${BASE15}) and one tutorial in full (GET ${BASE15}/<slug>).
|
|
22610
|
+
\`progress\` reads YOUR saved steps (GET ${BASE15}/progress); \`progress set\` replaces a
|
|
22611
|
+
tutorial's completed steps and \`progress clear\` resets them (POST ${BASE15}/progress,
|
|
22176
22612
|
body { tutorialId, completedStepIds, mode? }). The server stamps the user \u2014 you can
|
|
22177
22613
|
only write your own progress; anonymous sessions cannot save.
|
|
22178
22614
|
|
|
@@ -22196,7 +22632,7 @@ async function runTutorials(ctx, argv) {
|
|
|
22196
22632
|
}
|
|
22197
22633
|
switch (sub) {
|
|
22198
22634
|
case "list": {
|
|
22199
|
-
const res = await requestJson(ctx,
|
|
22635
|
+
const res = await requestJson(ctx, BASE15);
|
|
22200
22636
|
const items = Array.isArray(res.body?.tutorials) ? res.body.tutorials : [];
|
|
22201
22637
|
const code2 = renderList(ctx, res.body, items, ["id", "category", "title", "source"], "No tutorials.");
|
|
22202
22638
|
if (!ctx.json && res.body?.degraded) writeLine(ctx.io.stdout, "(shipped copy \u2014 the tenant library was unreachable)");
|
|
@@ -22208,13 +22644,13 @@ async function runTutorials(ctx, argv) {
|
|
|
22208
22644
|
write(ctx.io.stderr, "Usage: openwop tutorials get <slug>\n");
|
|
22209
22645
|
return 2;
|
|
22210
22646
|
}
|
|
22211
|
-
writeJson(ctx.io.stdout, (await requestJson(ctx, `${
|
|
22647
|
+
writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE15}/${enc2(slug)}`)).body);
|
|
22212
22648
|
return 0;
|
|
22213
22649
|
}
|
|
22214
22650
|
case "progress": {
|
|
22215
22651
|
const action = positionals[0];
|
|
22216
22652
|
if (!action) {
|
|
22217
|
-
const res2 = await requestJson(ctx, `${
|
|
22653
|
+
const res2 = await requestJson(ctx, `${BASE15}/progress`);
|
|
22218
22654
|
const items = Array.isArray(res2.body?.progress) ? res2.body.progress : [];
|
|
22219
22655
|
const code2 = renderList(
|
|
22220
22656
|
ctx,
|
|
@@ -22237,7 +22673,7 @@ async function runTutorials(ctx, argv) {
|
|
|
22237
22673
|
return 2;
|
|
22238
22674
|
}
|
|
22239
22675
|
const body = action === "set" ? { tutorialId, completedStepIds: csv(options.steps) ?? [] } : { tutorialId, completedStepIds: [], mode: "clear" };
|
|
22240
|
-
const res = await requestJson(ctx, `${
|
|
22676
|
+
const res = await requestJson(ctx, `${BASE15}/progress`, { method: "POST", body });
|
|
22241
22677
|
return renderDone(ctx, res.body, action === "set" ? `Saved ${Array.isArray(res.body?.completedStepIds) ? res.body.completedStepIds.length : body.completedStepIds.length} completed step(s) for ${tutorialId}.` : `Cleared progress for ${tutorialId}.`);
|
|
22242
22678
|
}
|
|
22243
22679
|
default:
|
|
@@ -22247,16 +22683,16 @@ Run \`openwop tutorials --help\` for usage.`);
|
|
|
22247
22683
|
}
|
|
22248
22684
|
|
|
22249
22685
|
// src/cli/walkthroughs.ts
|
|
22250
|
-
var
|
|
22686
|
+
var BASE16 = "/v1/host/openwop-app/walkthroughs";
|
|
22251
22687
|
var WALKTHROUGHS_HELP = `Usage:
|
|
22252
22688
|
openwop walkthroughs progress [--json]
|
|
22253
22689
|
openwop walkthroughs progress set <walkthroughId> --status started|completed --run <runId> [--json]
|
|
22254
22690
|
openwop walkthroughs funnel <walkthroughId> [--json]
|
|
22255
22691
|
|
|
22256
|
-
\`progress\` reads your walkthrough progress (GET ${
|
|
22257
|
-
records one (POST ${
|
|
22692
|
+
\`progress\` reads your walkthrough progress (GET ${BASE16}/progress); \`progress set\`
|
|
22693
|
+
records one (POST ${BASE16}/progress, body { walkthroughId, status, runId }).
|
|
22258
22694
|
\`funnel\` shows run-status counts + where runs stalled for one walkthrough
|
|
22259
|
-
(GET ${
|
|
22695
|
+
(GET ${BASE16}/funnel?walkthroughId=\u2026) \u2014 a recent-window view over the newest runs.
|
|
22260
22696
|
|
|
22261
22697
|
Exit codes: 0 ok \xB7 2 usage / validation / feature off \xB7 4 forbidden.
|
|
22262
22698
|
|
|
@@ -22284,10 +22720,10 @@ async function runWalkthroughs(ctx, argv) {
|
|
|
22284
22720
|
write(ctx.io.stderr, "Usage: openwop walkthroughs progress set <walkthroughId> --status started|completed --run <runId>\n");
|
|
22285
22721
|
return 2;
|
|
22286
22722
|
}
|
|
22287
|
-
const res2 = await requestJson(ctx, `${
|
|
22723
|
+
const res2 = await requestJson(ctx, `${BASE16}/progress`, { method: "POST", body: { walkthroughId, status: String(options.status), runId: String(options.run) } });
|
|
22288
22724
|
return renderDone(ctx, res2.body, `Recorded ${walkthroughId} as ${String(options.status)}.`);
|
|
22289
22725
|
}
|
|
22290
|
-
const res = await requestJson(ctx, `${
|
|
22726
|
+
const res = await requestJson(ctx, `${BASE16}/progress`);
|
|
22291
22727
|
const items = Array.isArray(res.body?.progress) ? res.body.progress : [];
|
|
22292
22728
|
return renderList(ctx, res.body, items, ["walkthroughId", "status", "runId", "updatedAt"], "No walkthrough progress.");
|
|
22293
22729
|
}
|
|
@@ -22297,7 +22733,7 @@ async function runWalkthroughs(ctx, argv) {
|
|
|
22297
22733
|
write(ctx.io.stderr, "Usage: openwop walkthroughs funnel <walkthroughId>\n");
|
|
22298
22734
|
return 2;
|
|
22299
22735
|
}
|
|
22300
|
-
const res = await requestJson(ctx, withQuery(`${
|
|
22736
|
+
const res = await requestJson(ctx, withQuery(`${BASE16}/funnel`, { walkthroughId }));
|
|
22301
22737
|
if (ctx.json) {
|
|
22302
22738
|
writeJson(ctx.io.stdout, res.body);
|
|
22303
22739
|
return 0;
|
|
@@ -22318,7 +22754,7 @@ Run \`openwop walkthroughs --help\` for usage.`);
|
|
|
22318
22754
|
}
|
|
22319
22755
|
|
|
22320
22756
|
// src/cli/widgets.ts
|
|
22321
|
-
var
|
|
22757
|
+
var BASE17 = "/v1/host/openwop-app/widgets";
|
|
22322
22758
|
var WIDGETS_HELP = `Usage:
|
|
22323
22759
|
openwop widgets list [--json]
|
|
22324
22760
|
openwop widgets summary [--json]
|
|
@@ -22326,8 +22762,8 @@ var WIDGETS_HELP = `Usage:
|
|
|
22326
22762
|
openwop widgets archive <widgetId> [--json]
|
|
22327
22763
|
openwop widgets seed [--json]
|
|
22328
22764
|
|
|
22329
|
-
The reference example domain (GET/POST ${
|
|
22330
|
-
POST ${
|
|
22765
|
+
The reference example domain (GET/POST ${BASE17}, GET ${BASE17}/summary,
|
|
22766
|
+
POST ${BASE17}/<id>/archive, POST ${BASE17}/seed). Mounted only when the host sets
|
|
22331
22767
|
OPENWOP_EXAMPLE_WIDGETS_ENABLED=true. \`seed\` is idempotent (\`seeded: false\` on a
|
|
22332
22768
|
re-run). Archiving an already-archived widget is a conflict (exit 2), never a
|
|
22333
22769
|
silent success.
|
|
@@ -22353,12 +22789,12 @@ async function runWidgets(ctx, argv) {
|
|
|
22353
22789
|
try {
|
|
22354
22790
|
switch (sub) {
|
|
22355
22791
|
case "list": {
|
|
22356
|
-
const res = await requestJson(ctx,
|
|
22792
|
+
const res = await requestJson(ctx, BASE17);
|
|
22357
22793
|
const items = Array.isArray(res.body?.widgets) ? res.body.widgets : [];
|
|
22358
22794
|
return renderList(ctx, res.body, items, ["id", "name", "status"], "No widgets.");
|
|
22359
22795
|
}
|
|
22360
22796
|
case "summary": {
|
|
22361
|
-
const res = await requestJson(ctx, `${
|
|
22797
|
+
const res = await requestJson(ctx, `${BASE17}/summary`);
|
|
22362
22798
|
if (ctx.json) {
|
|
22363
22799
|
writeJson(ctx.io.stdout, res.body);
|
|
22364
22800
|
return 0;
|
|
@@ -22372,7 +22808,7 @@ async function runWidgets(ctx, argv) {
|
|
|
22372
22808
|
write(ctx.io.stderr, "widgets create needs --name.\n");
|
|
22373
22809
|
return 2;
|
|
22374
22810
|
}
|
|
22375
|
-
const res = await requestJson(ctx,
|
|
22811
|
+
const res = await requestJson(ctx, BASE17, { method: "POST", body: { name: String(options.name) } });
|
|
22376
22812
|
return renderDone(ctx, res.body, `Created widget ${res.body?.id ?? ""} (${String(options.name)}).`);
|
|
22377
22813
|
}
|
|
22378
22814
|
case "archive": {
|
|
@@ -22381,11 +22817,11 @@ async function runWidgets(ctx, argv) {
|
|
|
22381
22817
|
write(ctx.io.stderr, "Usage: openwop widgets archive <widgetId>\n");
|
|
22382
22818
|
return 2;
|
|
22383
22819
|
}
|
|
22384
|
-
const res = await requestJson(ctx, `${
|
|
22820
|
+
const res = await requestJson(ctx, `${BASE17}/${enc2(id)}/archive`, { method: "POST", body: {} });
|
|
22385
22821
|
return renderDone(ctx, res.body, `Archived widget ${id}.`);
|
|
22386
22822
|
}
|
|
22387
22823
|
case "seed": {
|
|
22388
|
-
const res = await requestJson(ctx, `${
|
|
22824
|
+
const res = await requestJson(ctx, `${BASE17}/seed`, { method: "POST", body: {} });
|
|
22389
22825
|
return renderDone(ctx, res.body, res.body?.seeded === false ? "Example widgets already seeded (no-op)." : "Seeded example widgets.");
|
|
22390
22826
|
}
|
|
22391
22827
|
default:
|
|
@@ -22405,17 +22841,17 @@ Run \`openwop widgets --help\` for usage.`);
|
|
|
22405
22841
|
}
|
|
22406
22842
|
|
|
22407
22843
|
// src/cli/uiState.ts
|
|
22408
|
-
var
|
|
22844
|
+
var BASE18 = "/v1/host/openwop-app/ui-state";
|
|
22409
22845
|
var UI_STATE_HELP = `Usage:
|
|
22410
22846
|
openwop ui-state list [--resource-type <t>] [--resource-id <id>] [--json]
|
|
22411
22847
|
openwop ui-state get <resourceType> <resourceId> <key> [--json]
|
|
22412
22848
|
openwop ui-state set <resourceType> <resourceId> <key> --value <json> [--json]
|
|
22413
22849
|
openwop ui-state delete <resourceType> <resourceId> <key>
|
|
22414
22850
|
|
|
22415
|
-
\`list\` reads your rows (GET ${
|
|
22416
|
-
key from that list. \`set\` writes one key (PUT ${
|
|
22851
|
+
\`list\` reads your rows (GET ${BASE18}?resourceType=&resourceId=); \`get\` filters one
|
|
22852
|
+
key from that list. \`set\` writes one key (PUT ${BASE18}, body { resourceType,
|
|
22417
22853
|
resourceId, key, value }); --value is parsed as JSON, falling back to a string.
|
|
22418
|
-
\`delete\` removes one key (DELETE ${
|
|
22854
|
+
\`delete\` removes one key (DELETE ${BASE18}/<type>/<id>/<key>).
|
|
22419
22855
|
|
|
22420
22856
|
Exit codes: 0 ok \xB7 2 usage / not found \xB7 4 forbidden.
|
|
22421
22857
|
|
|
@@ -22438,7 +22874,7 @@ async function runUiState(ctx, argv) {
|
|
|
22438
22874
|
const [resourceType, resourceId, key] = positionals;
|
|
22439
22875
|
switch (sub) {
|
|
22440
22876
|
case "list": {
|
|
22441
|
-
const res = await requestJson(ctx, withQuery(
|
|
22877
|
+
const res = await requestJson(ctx, withQuery(BASE18, { resourceType: options.resourceType, resourceId: options.resourceId }));
|
|
22442
22878
|
const items = Array.isArray(res.body?.items) ? res.body.items : [];
|
|
22443
22879
|
return renderList(ctx, res.body, items, ["resourceType", "resourceId", "key", "value"], "No saved UI state.");
|
|
22444
22880
|
}
|
|
@@ -22447,7 +22883,7 @@ async function runUiState(ctx, argv) {
|
|
|
22447
22883
|
write(ctx.io.stderr, "Usage: openwop ui-state get <resourceType> <resourceId> <key>\n");
|
|
22448
22884
|
return 2;
|
|
22449
22885
|
}
|
|
22450
|
-
const res = await requestJson(ctx, withQuery(
|
|
22886
|
+
const res = await requestJson(ctx, withQuery(BASE18, { resourceType, resourceId }));
|
|
22451
22887
|
const items = Array.isArray(res.body?.items) ? res.body.items : [];
|
|
22452
22888
|
const hit = items.find((i) => i?.key === key);
|
|
22453
22889
|
if (!hit) throw new CliError(`No ui-state for ${resourceType}/${resourceId}/${key}.`, 2);
|
|
@@ -22466,7 +22902,7 @@ async function runUiState(ctx, argv) {
|
|
|
22466
22902
|
} catch {
|
|
22467
22903
|
value = String(options.value);
|
|
22468
22904
|
}
|
|
22469
|
-
const res = await requestJson(ctx,
|
|
22905
|
+
const res = await requestJson(ctx, BASE18, { method: "PUT", body: { resourceType, resourceId, key, value } });
|
|
22470
22906
|
return renderDone(ctx, res.body, `Saved ${resourceType}/${resourceId}/${key}.`);
|
|
22471
22907
|
}
|
|
22472
22908
|
case "delete": {
|
|
@@ -22474,7 +22910,7 @@ async function runUiState(ctx, argv) {
|
|
|
22474
22910
|
write(ctx.io.stderr, "Usage: openwop ui-state delete <resourceType> <resourceId> <key>\n");
|
|
22475
22911
|
return 2;
|
|
22476
22912
|
}
|
|
22477
|
-
await requestJson(ctx, `${
|
|
22913
|
+
await requestJson(ctx, `${BASE18}/${enc2(resourceType)}/${enc2(resourceId)}/${enc2(key)}`, { method: "DELETE" });
|
|
22478
22914
|
writeLine(ctx.io.stdout, `Deleted ${resourceType}/${resourceId}/${key}.`);
|
|
22479
22915
|
return 0;
|
|
22480
22916
|
}
|
|
@@ -22487,8 +22923,8 @@ Run \`openwop ui-state --help\` for usage.`);
|
|
|
22487
22923
|
// src/cli/uiPlugin.ts
|
|
22488
22924
|
import { writeFileSync as writeFileSync16 } from "node:fs";
|
|
22489
22925
|
import { resolve as resolvePath11 } from "node:path";
|
|
22490
|
-
var
|
|
22491
|
-
var RPC_PRODUCT = `${
|
|
22926
|
+
var BASE19 = "/v1/host/openwop-app/ui-plugin";
|
|
22927
|
+
var RPC_PRODUCT = `${BASE19}/rpc`;
|
|
22492
22928
|
var RPC_CONFORMANCE = "/v1/host/sample/ui-plugin/rpc";
|
|
22493
22929
|
var UI_PLUGIN_HELP = `Usage:
|
|
22494
22930
|
openwop ui-plugin packs [--json]
|
|
@@ -22499,15 +22935,18 @@ var UI_PLUGIN_HELP = `Usage:
|
|
|
22499
22935
|
openwop ui-plugin rpc --body <json> | --body-file <path>
|
|
22500
22936
|
|
|
22501
22937
|
\`packs\` lists served plugins + the host's isolation mechanism + each plugin's tier
|
|
22502
|
-
(GET ${
|
|
22503
|
-
(GET ${
|
|
22504
|
-
pack's main-frame ES module (GET ${
|
|
22938
|
+
(GET ${BASE19}/packs). \`entry\` fetches a plugin's sandboxed entry bundle
|
|
22939
|
+
(GET ${BASE19}/packs/<name>/plugins/<id>/entry); \`trusted-entry\` fetches a signed
|
|
22940
|
+
pack's main-frame ES module (GET ${BASE19}/trusted/<name>/plugins/<id>/entry.mjs \u2014
|
|
22505
22941
|
404 unless the trusted-plugins toggle is on and the signature verifies).
|
|
22506
|
-
\`demo-artifact\` ensures the idempotent demo artifact (POST ${
|
|
22942
|
+
\`demo-artifact\` ensures the idempotent demo artifact (POST ${BASE19}/demo-artifact).
|
|
22507
22943
|
\`rpc\` sends one ui-plugin/1 request (POST ${RPC_PRODUCT}, body { message }); the
|
|
22508
22944
|
message is { openwop: "ui-plugin/1", type: "request", id (integer,
|
|
22509
22945
|
default 1), method, params } unless you pass a
|
|
22510
|
-
whole body with --body/--body-file.
|
|
22946
|
+
whole body with --body/--body-file. --conformance-alias posts to the test-seam
|
|
22947
|
+
alias ${RPC_CONFORMANCE} instead; that seam has no v2 equivalent (the v2
|
|
22948
|
+
seams profile, api/seams-v2.yaml, defines no ui-plugin operation), so it is
|
|
22949
|
+
always sent as the v1 path, on either protocol major.
|
|
22511
22950
|
|
|
22512
22951
|
Exit codes: 0 ok \xB7 2 usage / not found / rejected request \xB7 4 forbidden.
|
|
22513
22952
|
|
|
@@ -22541,7 +22980,7 @@ async function runUiPlugin(ctx, argv) {
|
|
|
22541
22980
|
}
|
|
22542
22981
|
switch (sub) {
|
|
22543
22982
|
case "packs": {
|
|
22544
|
-
const res = await requestJson(ctx, `${
|
|
22983
|
+
const res = await requestJson(ctx, `${BASE19}/packs`);
|
|
22545
22984
|
const items = Array.isArray(res.body?.plugins) ? res.body.plugins : [];
|
|
22546
22985
|
const code2 = renderList(ctx, res.body, items, ["packName", "pluginId", "tier"], "No UI plugins served.");
|
|
22547
22986
|
if (!ctx.json && res.body?.isolation !== void 0) writeLine(ctx.io.stdout, `isolation: ${typeof res.body.isolation === "string" ? res.body.isolation : JSON.stringify(res.body.isolation)}`);
|
|
@@ -22555,7 +22994,7 @@ async function runUiPlugin(ctx, argv) {
|
|
|
22555
22994
|
`);
|
|
22556
22995
|
return 2;
|
|
22557
22996
|
}
|
|
22558
|
-
const path = sub === "entry" ? `${
|
|
22997
|
+
const path = sub === "entry" ? `${BASE19}/packs/${enc2(name)}/plugins/${enc2(pluginId)}/entry` : `${BASE19}/trusted/${enc2(name)}/plugins/${enc2(pluginId)}/entry.mjs`;
|
|
22559
22998
|
const res = await fetchRawText(ctx, path);
|
|
22560
22999
|
if (options.output) {
|
|
22561
23000
|
writeFileSync16(resolvePath11(ctx.cwd, String(options.output)), res.text);
|
|
@@ -22567,7 +23006,7 @@ async function runUiPlugin(ctx, argv) {
|
|
|
22567
23006
|
return 0;
|
|
22568
23007
|
}
|
|
22569
23008
|
case "demo-artifact": {
|
|
22570
|
-
const res = await requestJson(ctx, `${
|
|
23009
|
+
const res = await requestJson(ctx, `${BASE19}/demo-artifact`, { method: "POST", body: {} });
|
|
22571
23010
|
return renderDone(ctx, res.body, `Demo artifact ${res.body?.artifactId ?? ""} (version ${res.body?.version ?? ""}).`);
|
|
22572
23011
|
}
|
|
22573
23012
|
case "rpc": {
|
|
@@ -22597,18 +23036,18 @@ Run \`openwop ui-plugin --help\` for usage.`);
|
|
|
22597
23036
|
}
|
|
22598
23037
|
|
|
22599
23038
|
// src/cli/canvasCollab.ts
|
|
22600
|
-
var
|
|
23039
|
+
var BASE20 = "/v1/host/openwop-app/canvas-collab";
|
|
22601
23040
|
var CANVAS_COLLAB_HELP = `Usage:
|
|
22602
23041
|
openwop canvas-collab ticket <canvasId> [--json]
|
|
22603
23042
|
openwop canvas-collab claim-seed <canvasId> [--json]
|
|
22604
23043
|
openwop canvas-collab debug [--json]
|
|
22605
23044
|
|
|
22606
23045
|
\`ticket\` mints a short-lived collaboration ticket for one canvas
|
|
22607
|
-
(POST ${
|
|
23046
|
+
(POST ${BASE20}/<canvasId>/ticket) \u2014 a credential for the collaboration socket's
|
|
22608
23047
|
?ticket= parameter; it is printed once and never stored. \`claim-seed\` runs the
|
|
22609
|
-
seeder election (POST ${
|
|
23048
|
+
seeder election (POST ${BASE20}/<canvasId>/claim-seed) \u2192 { seed: true } for the one
|
|
22610
23049
|
caller that should seed a fresh room. \`debug\` lists this server instance's live
|
|
22611
|
-
rooms (GET ${
|
|
23050
|
+
rooms (GET ${BASE20}/_debug; super-admin only; lifecycle metadata, never content).
|
|
22612
23051
|
|
|
22613
23052
|
Exit codes: 0 ok \xB7 2 usage / not found (uniform) \xB7 4 forbidden (debug without super-admin).
|
|
22614
23053
|
|
|
@@ -22642,7 +23081,7 @@ async function runCanvasCollab(ctx, argv) {
|
|
|
22642
23081
|
write(ctx.io.stderr, "Usage: openwop canvas-collab ticket <canvasId>\n");
|
|
22643
23082
|
return 2;
|
|
22644
23083
|
}
|
|
22645
|
-
const res = await requestJson(ctx, `${
|
|
23084
|
+
const res = await requestJson(ctx, `${BASE20}/${enc2(id)}/ticket`, { method: "POST", body: {} });
|
|
22646
23085
|
return printTicketOnce(ctx, res.body, `canvas ${id}`);
|
|
22647
23086
|
}
|
|
22648
23087
|
case "claim-seed": {
|
|
@@ -22650,13 +23089,13 @@ async function runCanvasCollab(ctx, argv) {
|
|
|
22650
23089
|
write(ctx.io.stderr, "Usage: openwop canvas-collab claim-seed <canvasId>\n");
|
|
22651
23090
|
return 2;
|
|
22652
23091
|
}
|
|
22653
|
-
const res = await requestJson(ctx, `${
|
|
23092
|
+
const res = await requestJson(ctx, `${BASE20}/${enc2(id)}/claim-seed`, { method: "POST", body: {} });
|
|
22654
23093
|
if (ctx.json) writeJson(ctx.io.stdout, res.body);
|
|
22655
23094
|
else writeLine(ctx.io.stdout, res.body?.seed ? `You won the seed election for ${id} \u2014 seed the room.` : `Another client seeds ${id} (seed: false).`);
|
|
22656
23095
|
return 0;
|
|
22657
23096
|
}
|
|
22658
23097
|
case "debug": {
|
|
22659
|
-
const res = await requestJson(ctx, `${
|
|
23098
|
+
const res = await requestJson(ctx, `${BASE20}/_debug`);
|
|
22660
23099
|
const items = Array.isArray(res.body?.instanceRooms) ? res.body.instanceRooms : [];
|
|
22661
23100
|
return renderList(ctx, res.body, items, ["roomId", "tenantId", "clients", "openedAt"], "No live collaboration rooms on this instance.");
|
|
22662
23101
|
}
|
|
@@ -22667,14 +23106,14 @@ Run \`openwop canvas-collab --help\` for usage.`);
|
|
|
22667
23106
|
}
|
|
22668
23107
|
|
|
22669
23108
|
// src/cli/workflowCollab.ts
|
|
22670
|
-
var
|
|
23109
|
+
var BASE21 = "/v1/host/openwop-app/workflow-collab";
|
|
22671
23110
|
var WORKFLOW_COLLAB_HELP = `Usage:
|
|
22672
23111
|
openwop workflow-collab ticket <workflowId> [--json]
|
|
22673
23112
|
openwop workflow-collab claim-seed <workflowId> [--json]
|
|
22674
23113
|
|
|
22675
23114
|
\`ticket\` mints a short-lived collaboration ticket for one workflow draft
|
|
22676
|
-
(POST ${
|
|
22677
|
-
the seeder election (POST ${
|
|
23115
|
+
(POST ${BASE21}/<workflowId>/ticket) \u2014 shown once, never stored. \`claim-seed\` runs
|
|
23116
|
+
the seeder election (POST ${BASE21}/<workflowId>/claim-seed) \u2192 { seed }.
|
|
22678
23117
|
|
|
22679
23118
|
Exit codes: 0 ok \xB7 2 usage / not found / feature off \xB7 4 forbidden.
|
|
22680
23119
|
|
|
@@ -22701,11 +23140,11 @@ async function runWorkflowCollab(ctx, argv) {
|
|
|
22701
23140
|
}
|
|
22702
23141
|
switch (sub) {
|
|
22703
23142
|
case "ticket": {
|
|
22704
|
-
const res = await requestJson(ctx, `${
|
|
23143
|
+
const res = await requestJson(ctx, `${BASE21}/${enc2(id)}/ticket`, { method: "POST", body: {} });
|
|
22705
23144
|
return printTicketOnce(ctx, res.body, `workflow ${id}`);
|
|
22706
23145
|
}
|
|
22707
23146
|
case "claim-seed": {
|
|
22708
|
-
const res = await requestJson(ctx, `${
|
|
23147
|
+
const res = await requestJson(ctx, `${BASE21}/${enc2(id)}/claim-seed`, { method: "POST", body: {} });
|
|
22709
23148
|
if (ctx.json) writeJson(ctx.io.stdout, res.body);
|
|
22710
23149
|
else writeLine(ctx.io.stdout, res.body?.seed ? `You won the seed election for ${id} \u2014 seed the room.` : `Another client seeds ${id} (seed: false).`);
|
|
22711
23150
|
return 0;
|
|
@@ -22749,7 +23188,7 @@ Run \`openwop canvas-packs --help\` for usage.`);
|
|
|
22749
23188
|
}
|
|
22750
23189
|
|
|
22751
23190
|
// src/cli/present.ts
|
|
22752
|
-
var
|
|
23191
|
+
var BASE22 = "/v1/host/openwop-app/present";
|
|
22753
23192
|
var ACTIONS = ["next", "prev", "goto", "blank"];
|
|
22754
23193
|
var PRESENT_HELP = `Usage:
|
|
22755
23194
|
openwop present outline <token> [--json]
|
|
@@ -22759,11 +23198,11 @@ var PRESENT_HELP = `Usage:
|
|
|
22759
23198
|
openwop present events <token> [--max <n>] [--timeout <seconds>] [--json]
|
|
22760
23199
|
|
|
22761
23200
|
Drive a live presentation with its remote token (no API key \u2014 the token is the
|
|
22762
|
-
credential). \`outline\` reads { title, frames, current? } (GET ${
|
|
22763
|
-
\`command\` sends a navigation command (POST ${
|
|
23201
|
+
credential). \`outline\` reads { title, frames, current? } (GET ${BASE22}/<token>/outline).
|
|
23202
|
+
\`command\` sends a navigation command (POST ${BASE22}/<token>/command, body
|
|
22764
23203
|
{ action, index? }). \`state\` reports the presenter's current frame
|
|
22765
|
-
(POST ${
|
|
22766
|
-
feed (GET ${
|
|
23204
|
+
(POST ${BASE22}/<token>/state, body { current }). \`events\` follows the navigation
|
|
23205
|
+
feed (GET ${BASE22}/<token>/events, server-sent events) \u2014 one line per event, or one
|
|
22767
23206
|
JSON object per line with --json; stops after --max events or --timeout seconds.
|
|
22768
23207
|
|
|
22769
23208
|
Exit codes: 0 ok \xB7 2 usage / unknown or expired session.
|
|
@@ -22790,7 +23229,7 @@ async function runPresent(ctx, argv) {
|
|
|
22790
23229
|
`);
|
|
22791
23230
|
return 2;
|
|
22792
23231
|
}
|
|
22793
|
-
const base16 = `${
|
|
23232
|
+
const base16 = `${BASE22}/${enc2(token)}`;
|
|
22794
23233
|
switch (sub) {
|
|
22795
23234
|
case "outline": {
|
|
22796
23235
|
const res = await requestJson(ctx, `${base16}/outline`, { auth: false });
|
|
@@ -23798,8 +24237,8 @@ async function runCampaignBrief(ctx, argv) {
|
|
|
23798
24237
|
}
|
|
23799
24238
|
|
|
23800
24239
|
// src/cli/campaignConnectors.ts
|
|
23801
|
-
var
|
|
23802
|
-
var orgBase2 = (org2) => `${
|
|
24240
|
+
var BASE23 = `${APP}/campaign-connectors`;
|
|
24241
|
+
var orgBase2 = (org2) => `${BASE23}/orgs/${enc2(org2)}`;
|
|
23803
24242
|
var CAMPAIGN_CONNECTORS_HELP = `Usage:
|
|
23804
24243
|
openwop campaign-connectors platforms [--json]
|
|
23805
24244
|
openwop campaign-connectors sync --org <orgId> --platform meta|google [--json]
|
|
@@ -23848,7 +24287,7 @@ var TABLE2 = {
|
|
|
23848
24287
|
usage: "platforms [--json]",
|
|
23849
24288
|
args: 0,
|
|
23850
24289
|
run: async (ctx) => {
|
|
23851
|
-
const body = (await requestJson(ctx, `${
|
|
24290
|
+
const body = (await requestJson(ctx, `${BASE23}/platforms`)).body;
|
|
23852
24291
|
if (ctx.json) {
|
|
23853
24292
|
writeJson(ctx.io.stdout, body);
|
|
23854
24293
|
return 0;
|
|
@@ -23865,7 +24304,7 @@ var TABLE2 = {
|
|
|
23865
24304
|
requires: ["platform"],
|
|
23866
24305
|
run: async (ctx, a) => {
|
|
23867
24306
|
const req2 = assign({ ...a.body }, { orgId: orgOf(a), platform: a.options.platform });
|
|
23868
|
-
const body = (await requestJson(ctx, `${
|
|
24307
|
+
const body = (await requestJson(ctx, `${BASE23}/sync`, { method: "POST", body: req2 })).body;
|
|
23869
24308
|
return done(ctx, body, `Sync ${body?.outcome ?? "done"}${body?.imported !== void 0 ? ` (${body.imported} imported)` : ""}.`);
|
|
23870
24309
|
}
|
|
23871
24310
|
},
|
|
@@ -23878,7 +24317,7 @@ var TABLE2 = {
|
|
|
23878
24317
|
run: async (ctx, a) => {
|
|
23879
24318
|
const o = a.options;
|
|
23880
24319
|
const req2 = assign({ ...a.body }, { orgId: orgOf(a), segmentId: o.segment, adAccountId: o.adAccount, platform: o.platform, audienceName: o.audienceName });
|
|
23881
|
-
const body = (await requestJson(ctx, `${
|
|
24320
|
+
const body = (await requestJson(ctx, `${BASE23}/audience-sync`, { method: "POST", body: req2 })).body;
|
|
23882
24321
|
return done(ctx, body, `Audience sync: ${body?.outcome ?? "done"}.`);
|
|
23883
24322
|
}
|
|
23884
24323
|
},
|
|
@@ -23901,7 +24340,7 @@ var TABLE2 = {
|
|
|
23901
24340
|
writeLine(ctx.io.stderr, "openwop: missing --csv-file");
|
|
23902
24341
|
return 2;
|
|
23903
24342
|
}
|
|
23904
|
-
const body = (await requestJson(ctx, `${
|
|
24343
|
+
const body = (await requestJson(ctx, `${BASE23}/import`, { method: "POST", body: req2 })).body;
|
|
23905
24344
|
return done(ctx, body, `Imported ${body?.imported ?? 0} rows (${body?.deduped ?? 0} deduped, ${Array.isArray(body?.invalid) ? body.invalid.length : body?.invalid ?? 0} invalid).`);
|
|
23906
24345
|
}
|
|
23907
24346
|
},
|
|
@@ -23911,7 +24350,7 @@ var TABLE2 = {
|
|
|
23911
24350
|
value: ["--org", "--campaign"],
|
|
23912
24351
|
run: async (ctx, a) => listOut(
|
|
23913
24352
|
ctx,
|
|
23914
|
-
(await requestJson(ctx, `${
|
|
24353
|
+
(await requestJson(ctx, `${BASE23}/records${qs({ orgId: orgOf(a), campaignId: a.options.campaign })}`)).body,
|
|
23915
24354
|
"records",
|
|
23916
24355
|
["date", "platform", "campaignId", "impressions", "clicks", "spend", "conversions"],
|
|
23917
24356
|
"No performance records."
|
|
@@ -23921,7 +24360,7 @@ var TABLE2 = {
|
|
|
23921
24360
|
usage: "kpi --org <orgId> [--campaign <id>] [--json]",
|
|
23922
24361
|
args: 0,
|
|
23923
24362
|
value: ["--org", "--campaign"],
|
|
23924
|
-
run: async (ctx, a) => detail(ctx, (await requestJson(ctx, `${
|
|
24363
|
+
run: async (ctx, a) => detail(ctx, (await requestJson(ctx, `${BASE23}/kpi${qs({ orgId: orgOf(a), campaignId: a.options.campaign })}`)).body)
|
|
23925
24364
|
},
|
|
23926
24365
|
"sync-status": {
|
|
23927
24366
|
usage: "sync-status --org <orgId> [--json]",
|
|
@@ -24043,7 +24482,7 @@ async function runCampaignConnectors(ctx, argv) {
|
|
|
24043
24482
|
}
|
|
24044
24483
|
|
|
24045
24484
|
// src/cli/campaignIntel.ts
|
|
24046
|
-
var
|
|
24485
|
+
var BASE24 = `${APP}/campaign-intel`;
|
|
24047
24486
|
var CAMPAIGN_INTEL_HELP = `Usage:
|
|
24048
24487
|
openwop campaign-intel budget --org <orgId> [--campaign <id>] [--json]
|
|
24049
24488
|
openwop campaign-intel forecast --org <orgId> [--campaign <id>] [--json]
|
|
@@ -24082,7 +24521,7 @@ var read = (name, withCampaign) => ({
|
|
|
24082
24521
|
args: 0,
|
|
24083
24522
|
value: ["--org", ...withCampaign ? ["--campaign"] : []],
|
|
24084
24523
|
run: async (ctx, a) => {
|
|
24085
|
-
const body = (await requestJson(ctx, `${
|
|
24524
|
+
const body = (await requestJson(ctx, `${BASE24}/${name}${qs({ orgId: orgOf2(a), campaignId: withCampaign ? a.options.campaign : void 0 })}`)).body;
|
|
24086
24525
|
if (name === "anomalies") return listOut(ctx, body, "anomalies", ["date", "platform", "campaignId", "metric", "severity", "message"], "No anomalies.");
|
|
24087
24526
|
if (name === "forecast") return listOut(ctx, body, "forecasts", ["campaignId", "platform", "horizonDays", "projectedSpend", "projectedConversions"], "No forecasts (no performance records).");
|
|
24088
24527
|
return detail(ctx, body);
|
|
@@ -24117,7 +24556,7 @@ var TABLE3 = {
|
|
|
24117
24556
|
return 2;
|
|
24118
24557
|
}
|
|
24119
24558
|
}
|
|
24120
|
-
return detail(ctx, (await requestJson(ctx, `${
|
|
24559
|
+
return detail(ctx, (await requestJson(ctx, `${BASE24}/plan-budget`, { method: "POST", body: req2 })).body);
|
|
24121
24560
|
}
|
|
24122
24561
|
},
|
|
24123
24562
|
apply: {
|
|
@@ -24142,7 +24581,7 @@ var TABLE3 = {
|
|
|
24142
24581
|
dailyBudgetMinor: o.dailyBudgetMinor !== void 0 ? num(o.dailyBudgetMinor, "--daily-budget-minor") : void 0,
|
|
24143
24582
|
dryRun: dryRun ? true : void 0
|
|
24144
24583
|
});
|
|
24145
|
-
const body = (await requestJson(ctx, `${
|
|
24584
|
+
const body = (await requestJson(ctx, `${BASE24}/recommendations/apply`, { method: "POST", body: req2 })).body;
|
|
24146
24585
|
return done(ctx, body, `Budget ${dryRun ? "preview" : "update"}: ${body?.outcome ?? "done"}.`);
|
|
24147
24586
|
}
|
|
24148
24587
|
}
|
|
@@ -24202,7 +24641,7 @@ async function runCampaignJourneys(ctx, argv) {
|
|
|
24202
24641
|
}
|
|
24203
24642
|
|
|
24204
24643
|
// src/cli/cdp.ts
|
|
24205
|
-
var
|
|
24644
|
+
var BASE25 = `${APP}/cdp`;
|
|
24206
24645
|
var CDP_HELP = `Usage:
|
|
24207
24646
|
openwop cdp resolve --type <identifierType> --value <v> [--json]
|
|
24208
24647
|
openwop cdp schemas list [--json]
|
|
@@ -24249,7 +24688,7 @@ var limited = (path, key, columns, empty) => ({
|
|
|
24249
24688
|
usage: `${path === "governance-decisions" ? "governance" : path === "collected-events" ? "events" : path} [--limit <n>] [--json]`,
|
|
24250
24689
|
args: 0,
|
|
24251
24690
|
value: ["--limit"],
|
|
24252
|
-
run: async (ctx, a) => listOut(ctx, (await requestJson(ctx, `${
|
|
24691
|
+
run: async (ctx, a) => listOut(ctx, (await requestJson(ctx, `${BASE25}/${path}${qs({ limit: a.options.limit })}`)).body, key, columns, empty)
|
|
24253
24692
|
});
|
|
24254
24693
|
var TABLE5 = {
|
|
24255
24694
|
resolve: {
|
|
@@ -24257,7 +24696,7 @@ var TABLE5 = {
|
|
|
24257
24696
|
args: 0,
|
|
24258
24697
|
value: ["--type", "--value"],
|
|
24259
24698
|
requires: ["type", "value"],
|
|
24260
|
-
run: async (ctx, a) => detail(ctx, (await requestJson(ctx, `${
|
|
24699
|
+
run: async (ctx, a) => detail(ctx, (await requestJson(ctx, `${BASE25}/identity/resolve${qs({ type: a.options.type, value: a.options.value })}`)).body)
|
|
24261
24700
|
},
|
|
24262
24701
|
collect: {
|
|
24263
24702
|
usage: "collect --event-type <t> [--payload-json '{...}'] [--dedupe-key <k>] [--json]",
|
|
@@ -24272,7 +24711,7 @@ var TABLE5 = {
|
|
|
24272
24711
|
payload: o.payloadJson !== void 0 ? parseJsonFlag2(o.payloadJson, "--payload-json") : void 0,
|
|
24273
24712
|
dedupeKey: o.dedupeKey
|
|
24274
24713
|
});
|
|
24275
|
-
const body = (await requestJson(ctx, `${
|
|
24714
|
+
const body = (await requestJson(ctx, `${BASE25}/collect`, { method: "POST", body: req2 })).body;
|
|
24276
24715
|
return done(ctx, body, `Collected ${req2.eventType} event ${body?.eventId ?? body?.event?.eventId ?? ""}${body?.deduped ? " (deduplicated)" : ""}.`);
|
|
24277
24716
|
}
|
|
24278
24717
|
},
|
|
@@ -24287,7 +24726,7 @@ var TABLE5 = {
|
|
|
24287
24726
|
writeLine(ctx.io.stderr, "openwop: missing --events-json or --events-file");
|
|
24288
24727
|
return 2;
|
|
24289
24728
|
}
|
|
24290
|
-
return detail(ctx, (await requestJson(ctx, `${
|
|
24729
|
+
return detail(ctx, (await requestJson(ctx, `${BASE25}/collect/batch`, { method: "POST", body: req2 })).body);
|
|
24291
24730
|
}
|
|
24292
24731
|
},
|
|
24293
24732
|
import: {
|
|
@@ -24307,7 +24746,7 @@ var TABLE5 = {
|
|
|
24307
24746
|
writeLine(ctx.io.stderr, "openwop: missing --csv-file");
|
|
24308
24747
|
return 2;
|
|
24309
24748
|
}
|
|
24310
|
-
return detail(ctx, (await requestJson(ctx, `${
|
|
24749
|
+
return detail(ctx, (await requestJson(ctx, `${BASE25}/collect/import`, { method: "POST", body: req2 })).body);
|
|
24311
24750
|
}
|
|
24312
24751
|
},
|
|
24313
24752
|
events: limited("collected-events", "events", ["eventId", "eventType", ["at", (e) => e.at ?? e.receivedAt ?? e.createdAt], "dedupeKey"], "No collected events."),
|
|
@@ -24317,7 +24756,7 @@ var TABLE5 = {
|
|
|
24317
24756
|
usage: "audit-verify [--json]",
|
|
24318
24757
|
args: 0,
|
|
24319
24758
|
run: async (ctx) => {
|
|
24320
|
-
const body = (await requestJson(ctx, `${
|
|
24759
|
+
const body = (await requestJson(ctx, `${BASE25}/audit-chain/verify`)).body;
|
|
24321
24760
|
if (ctx.json) {
|
|
24322
24761
|
writeJson(ctx.io.stdout, body);
|
|
24323
24762
|
return body?.valid === false || body?.ok === false ? 1 : 0;
|
|
@@ -24335,7 +24774,7 @@ var SCHEMAS = {
|
|
|
24335
24774
|
args: 0,
|
|
24336
24775
|
run: async (ctx) => listOut(
|
|
24337
24776
|
ctx,
|
|
24338
|
-
(await requestJson(ctx, `${
|
|
24777
|
+
(await requestJson(ctx, `${BASE25}/event-schemas`)).body,
|
|
24339
24778
|
"schemas",
|
|
24340
24779
|
["eventType", "version", ["updatedAt", (s) => s.updatedAt ?? s.registeredAt ?? s.createdAt]],
|
|
24341
24780
|
"No event schemas registered."
|
|
@@ -24356,7 +24795,7 @@ var SCHEMAS = {
|
|
|
24356
24795
|
writeLine(ctx.io.stderr, "openwop: missing --schema-json or --schema-file");
|
|
24357
24796
|
return 2;
|
|
24358
24797
|
}
|
|
24359
|
-
const body = (await requestJson(ctx, `${
|
|
24798
|
+
const body = (await requestJson(ctx, `${BASE25}/event-schemas`, { method: "POST", body: req2 })).body;
|
|
24360
24799
|
return done(ctx, body, `Registered the ${req2.eventType} event schema${body?.version !== void 0 ? ` (version ${body.version})` : ""}.`);
|
|
24361
24800
|
}
|
|
24362
24801
|
},
|
|
@@ -24369,7 +24808,7 @@ var SCHEMAS = {
|
|
|
24369
24808
|
const req2 = assign({ ...a.body }, { payload: jsonFrom(ctx, a.options.payloadJson, a.options.payloadFile, "--payload-json", "--payload-file") });
|
|
24370
24809
|
let body;
|
|
24371
24810
|
try {
|
|
24372
|
-
body = (await requestJson(ctx, `${
|
|
24811
|
+
body = (await requestJson(ctx, `${BASE25}/event-schemas/${enc2(a.positionals[0])}/validate`, { method: "POST", body: req2 })).body;
|
|
24373
24812
|
} catch (err) {
|
|
24374
24813
|
if (!(err instanceof HttpError) || err.status !== 422) throw err;
|
|
24375
24814
|
body = err.body;
|
|
@@ -24393,7 +24832,7 @@ async function runCdp(ctx, argv) {
|
|
|
24393
24832
|
}
|
|
24394
24833
|
|
|
24395
24834
|
// src/cli/destinationSync.ts
|
|
24396
|
-
var
|
|
24835
|
+
var BASE26 = `${APP}/destination-sync/syncs`;
|
|
24397
24836
|
var DESTINATION_SYNC_HELP = `Usage:
|
|
24398
24837
|
openwop destination-sync list [--json]
|
|
24399
24838
|
openwop destination-sync get <syncId> [--json]
|
|
@@ -24427,14 +24866,14 @@ function jsonFrom2(ctx, inline, file, inlineFlag, fileFlag) {
|
|
|
24427
24866
|
if (file !== void 0) return parseJsonFlag2(readText(ctx, String(file), fileFlag), fileFlag);
|
|
24428
24867
|
return void 0;
|
|
24429
24868
|
}
|
|
24430
|
-
var item = (id) => `${
|
|
24869
|
+
var item = (id) => `${BASE26}/${enc2(id)}`;
|
|
24431
24870
|
var TABLE6 = {
|
|
24432
24871
|
list: {
|
|
24433
24872
|
usage: "list [--json]",
|
|
24434
24873
|
args: 0,
|
|
24435
24874
|
run: async (ctx) => listOut(
|
|
24436
24875
|
ctx,
|
|
24437
|
-
(await requestJson(ctx,
|
|
24876
|
+
(await requestJson(ctx, BASE26)).body,
|
|
24438
24877
|
"syncs",
|
|
24439
24878
|
[["id", (s) => s.id ?? s.syncId], "name", "destinationKind", "sourceObject", "syncMode", "cursor"],
|
|
24440
24879
|
"No destination syncs."
|
|
@@ -24462,7 +24901,7 @@ var TABLE6 = {
|
|
|
24462
24901
|
cursorField: o.cursorField,
|
|
24463
24902
|
connectionId: o.connection
|
|
24464
24903
|
});
|
|
24465
|
-
const body = (await requestJson(ctx,
|
|
24904
|
+
const body = (await requestJson(ctx, BASE26, { method: "POST", body: req2 })).body;
|
|
24466
24905
|
return done(ctx, body, `Created destination sync ${body?.id ?? body?.syncId ?? ""} (${req2.name}).`);
|
|
24467
24906
|
}
|
|
24468
24907
|
},
|
|
@@ -25292,7 +25731,7 @@ async function runPublic(ctx, argv) {
|
|
|
25292
25731
|
}
|
|
25293
25732
|
|
|
25294
25733
|
// src/cli/vault.ts
|
|
25295
|
-
var
|
|
25734
|
+
var BASE27 = "/v1/host/openwop-app/vault";
|
|
25296
25735
|
var SURFACE2 = "The secrets vault";
|
|
25297
25736
|
var VAULT_HELP = `Usage:
|
|
25298
25737
|
openwop vault list [--json]
|
|
@@ -25304,10 +25743,10 @@ The super-admin secrets vault (host extension). Every route is SUPER-ADMIN gated
|
|
|
25304
25743
|
without a super-admin principal the command fails closed with exit 4 and says how to
|
|
25305
25744
|
get one (OPENWOP_SUPERADMIN_TENANTS on the server).
|
|
25306
25745
|
|
|
25307
|
-
list GET ${
|
|
25308
|
-
set POST ${
|
|
25309
|
-
rotate POST ${
|
|
25310
|
-
delete DELETE ${
|
|
25746
|
+
list GET ${BASE27} inventory of refs (never values)
|
|
25747
|
+
set POST ${BASE27}/secrets store a new secret under a ref
|
|
25748
|
+
rotate POST ${BASE27}/secrets/:credentialRef/rotate overwrite an EXISTING secret
|
|
25749
|
+
delete DELETE ${BASE27}/secrets/:credentialRef remove (refused while the ref has live consumers unless --force)
|
|
25311
25750
|
|
|
25312
25751
|
SECRETS ARE REFS, NEVER VALUES. The value for set/rotate is read from --value-file or a
|
|
25313
25752
|
no-echo prompt (a piped line also works) \u2014 never from a command-line flag. The host's
|
|
@@ -25363,7 +25802,7 @@ async function vaultList(ctx, argv) {
|
|
|
25363
25802
|
write(ctx.io.stdout, VAULT_HELP);
|
|
25364
25803
|
return 0;
|
|
25365
25804
|
}
|
|
25366
|
-
const res = await gatedRequest(ctx,
|
|
25805
|
+
const res = await gatedRequest(ctx, BASE27, void 0, SURFACE2, "superadmin");
|
|
25367
25806
|
if (ctx.json) {
|
|
25368
25807
|
writeJson(ctx.io.stdout, res.body);
|
|
25369
25808
|
return 0;
|
|
@@ -25394,7 +25833,7 @@ async function vaultWrite(ctx, argv, verb) {
|
|
|
25394
25833
|
const scope = scopeOf(options);
|
|
25395
25834
|
const value = await readSecretInput(ctx, options, `Secret value for "${ref}"`);
|
|
25396
25835
|
const body = { value, ...scope === "host" ? { scope: "host" } : {} };
|
|
25397
|
-
const path = verb === "set" ? `${
|
|
25836
|
+
const path = verb === "set" ? `${BASE27}/secrets` : `${BASE27}/secrets/${encodeURIComponent(ref)}/rotate`;
|
|
25398
25837
|
if (verb === "set") body.credentialRef = ref;
|
|
25399
25838
|
const res = await gatedRequest(ctx, path, { method: "POST", body }, SURFACE2, "superadmin");
|
|
25400
25839
|
if (ctx.json) {
|
|
@@ -25418,7 +25857,7 @@ async function vaultDelete(ctx, argv) {
|
|
|
25418
25857
|
if (scopeOf(options) === "host") qs2.set("scope", "host");
|
|
25419
25858
|
if (options.force) qs2.set("force", "true");
|
|
25420
25859
|
const q = qs2.toString() ? `?${qs2}` : "";
|
|
25421
|
-
const res = await gatedRequest(ctx, `${
|
|
25860
|
+
const res = await gatedRequest(ctx, `${BASE27}/secrets/${encodeURIComponent(positionals[0])}${q}`, { method: "DELETE" }, SURFACE2, "superadmin");
|
|
25422
25861
|
if (ctx.json) {
|
|
25423
25862
|
writeJson(ctx.io.stdout, res.body);
|
|
25424
25863
|
return 0;
|
|
@@ -25428,27 +25867,40 @@ async function vaultDelete(ctx, argv) {
|
|
|
25428
25867
|
}
|
|
25429
25868
|
|
|
25430
25869
|
// src/cli/developerKeys.ts
|
|
25431
|
-
var
|
|
25870
|
+
var BASE28 = "/v1/host/openwop-app/developer-keys";
|
|
25432
25871
|
var SURFACE3 = "Managing developer API keys";
|
|
25433
25872
|
var DEVELOPER_KEYS_HELP = `Usage:
|
|
25434
25873
|
openwop developer-keys list [--json]
|
|
25435
25874
|
openwop developer-keys create --name <n> [--scope <s>]... [--expires-at <iso>] [--json]
|
|
25436
25875
|
openwop developer-keys revoke <keyId> --yes [--json]
|
|
25437
25876
|
|
|
25438
|
-
Developer API keys for this workspace (host extension under ${
|
|
25877
|
+
Developer API keys for this workspace (host extension under ${BASE28}). Requires an
|
|
25439
25878
|
authenticated principal (not an anonymous session); an admin or owner sees and can
|
|
25440
25879
|
revoke every key in the workspace, anyone else only their own.
|
|
25441
25880
|
|
|
25442
|
-
list GET ${
|
|
25443
|
-
create POST ${
|
|
25444
|
-
revoke DELETE ${
|
|
25881
|
+
list GET ${BASE28} keys (public fields only \u2014 never the token or its hash)
|
|
25882
|
+
create POST ${BASE28} issue a key; the token is shown ONCE
|
|
25883
|
+
revoke DELETE ${BASE28}/:keyId revoke (404 when the key is not yours / not in this workspace)
|
|
25445
25884
|
|
|
25446
25885
|
ONE-TIME SECRET: \`create\` prints the new token exactly once, on stdout, with a warning.
|
|
25447
25886
|
Store it immediately \u2014 the host keeps only a hash and cannot show it again. The CLI does
|
|
25448
25887
|
not save it anywhere.
|
|
25449
25888
|
|
|
25889
|
+
A KEY CANNOT MANAGE KEYS: openwop-app refuses \`list\`, \`create\` and \`revoke\` (403)
|
|
25890
|
+
when the CLI is itself signed in with a developer key (owk_\u2026). Create and revoke keys
|
|
25891
|
+
in the app (Access \u2192 API keys), or sign in with an operator key.
|
|
25892
|
+
|
|
25893
|
+
SCOPES ARE REQUIRED AND NARROW: openwop-app refuses a key with no --scope, and a key is
|
|
25894
|
+
limited to exactly the scopes it declares. To start a run and read it back a key needs
|
|
25895
|
+
runs:create AND runs:read; reaching the rest of the workspace (agents, memory, BYOK)
|
|
25896
|
+
needs workspace:read / workspace:write. \`--scope '*'\` is a key with your full authority.
|
|
25897
|
+
A scope the host does not know is refused by name.
|
|
25898
|
+
|
|
25899
|
+
EXPIRY: openwop-app gives a key 90 days unless --expires-at says otherwise, and refuses
|
|
25900
|
+
an expiry in the past or more than its maximum (366 days by default) away.
|
|
25901
|
+
|
|
25450
25902
|
--name <n> (create) Display name. Required.
|
|
25451
|
-
--scope <s> (create) Scope granted to the key (repeatable).
|
|
25903
|
+
--scope <s> (create) Scope granted to the key (repeatable; at least one).
|
|
25452
25904
|
--expires-at <iso> (create) Expiry timestamp.
|
|
25453
25905
|
--yes (revoke) Required confirmation.
|
|
25454
25906
|
|
|
@@ -25456,7 +25908,7 @@ Exit codes: 0 ok \xB7 2 usage / not found \xB7 4 not signed in.
|
|
|
25456
25908
|
|
|
25457
25909
|
Examples:
|
|
25458
25910
|
openwop developer-keys list
|
|
25459
|
-
openwop developer-keys create --name "CI deploy" --scope runs:
|
|
25911
|
+
openwop developer-keys create --name "CI deploy" --scope runs:create --scope runs:read
|
|
25460
25912
|
openwop developer-keys revoke dk:0123abcd --yes
|
|
25461
25913
|
`;
|
|
25462
25914
|
async function runDeveloperKeys(ctx, argv) {
|
|
@@ -25485,7 +25937,7 @@ async function keysList(ctx, argv) {
|
|
|
25485
25937
|
write(ctx.io.stdout, DEVELOPER_KEYS_HELP);
|
|
25486
25938
|
return 0;
|
|
25487
25939
|
}
|
|
25488
|
-
const res = await gatedRequest(ctx,
|
|
25940
|
+
const res = await gatedRequest(ctx, BASE28, void 0, SURFACE3, "signed-in");
|
|
25489
25941
|
if (ctx.json) {
|
|
25490
25942
|
writeJson(ctx.io.stdout, res.body);
|
|
25491
25943
|
return 0;
|
|
@@ -25514,7 +25966,7 @@ async function keysCreate(ctx, argv) {
|
|
|
25514
25966
|
const body = { name: options.name };
|
|
25515
25967
|
if (Array.isArray(options.scope) && options.scope.length) body.scopes = options.scope;
|
|
25516
25968
|
if (options.expiresAt) body.expiresAt = options.expiresAt;
|
|
25517
|
-
const res = await gatedRequest(ctx,
|
|
25969
|
+
const res = await gatedRequest(ctx, BASE28, { method: "POST", body }, SURFACE3, "signed-in");
|
|
25518
25970
|
writeLine(ctx.io.stderr, "WARNING: this token is shown ONCE. Store it now \u2014 it cannot be retrieved again.");
|
|
25519
25971
|
if (ctx.json) {
|
|
25520
25972
|
writeJson(ctx.io.stdout, res.body);
|
|
@@ -25535,7 +25987,7 @@ async function keysRevoke(ctx, argv) {
|
|
|
25535
25987
|
writeLine(ctx.io.stderr, `Refusing to revoke ${positionals[0]} without --yes.`);
|
|
25536
25988
|
return 2;
|
|
25537
25989
|
}
|
|
25538
|
-
await gatedRequest(ctx, `${
|
|
25990
|
+
await gatedRequest(ctx, `${BASE28}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" }, SURFACE3, "signed-in");
|
|
25539
25991
|
if (ctx.json) {
|
|
25540
25992
|
writeJson(ctx.io.stdout, { keyId: positionals[0], revoked: true });
|
|
25541
25993
|
return 0;
|
|
@@ -25604,44 +26056,44 @@ Run \`openwop custom-domains --help\` for usage.`);
|
|
|
25604
26056
|
})), ["hostname", "status", "verifiedAt", "lastError"]));
|
|
25605
26057
|
return 0;
|
|
25606
26058
|
}
|
|
25607
|
-
const
|
|
25608
|
-
if (!
|
|
26059
|
+
const hostname2 = positionals[0];
|
|
26060
|
+
if (!hostname2) throw new CliError(`Usage: openwop custom-domains ${sub} <hostname> --org <orgId>`, 2);
|
|
25609
26061
|
if (sub === "add") {
|
|
25610
|
-
const res2 = await requestJson(ctx, base14(orgId), { method: "POST", body: { hostname } });
|
|
26062
|
+
const res2 = await requestJson(ctx, base14(orgId), { method: "POST", body: { hostname: hostname2 } });
|
|
25611
26063
|
if (ctx.json) {
|
|
25612
26064
|
writeJson(ctx.io.stdout, res2.body);
|
|
25613
26065
|
return 0;
|
|
25614
26066
|
}
|
|
25615
26067
|
const d = res2.body?.domain ?? {};
|
|
25616
|
-
writeLine(ctx.io.stdout, `Added ${d.hostname ??
|
|
25617
|
-
writeLine(ctx.io.stdout, `Publish TXT _openwop-verify.${d.hostname ??
|
|
26068
|
+
writeLine(ctx.io.stdout, `Added ${d.hostname ?? hostname2} (status ${d.status ?? "pending"}).`);
|
|
26069
|
+
writeLine(ctx.io.stdout, `Publish TXT _openwop-verify.${d.hostname ?? hostname2} = ${d.verificationToken ?? "(see --json)"} then run \`openwop custom-domains verify ${d.hostname ?? hostname2} --org ${orgId}\`.`);
|
|
25618
26070
|
return 0;
|
|
25619
26071
|
}
|
|
25620
26072
|
if (sub === "verify") {
|
|
25621
|
-
const res2 = await requestJson(ctx, `${base14(orgId)}/${encodeURIComponent(
|
|
26073
|
+
const res2 = await requestJson(ctx, `${base14(orgId)}/${encodeURIComponent(hostname2)}/verify`, { method: "POST", body: {} });
|
|
25622
26074
|
if (ctx.json) {
|
|
25623
26075
|
writeJson(ctx.io.stdout, res2.body);
|
|
25624
26076
|
return 0;
|
|
25625
26077
|
}
|
|
25626
26078
|
const d = res2.body?.domain ?? {};
|
|
25627
|
-
writeLine(ctx.io.stdout, `${d.hostname ??
|
|
26079
|
+
writeLine(ctx.io.stdout, `${d.hostname ?? hostname2}: ${d.status ?? "?"}${d.lastError ? ` \u2014 ${d.lastError}` : ""}`);
|
|
25628
26080
|
return d.status === "live" ? 0 : 3;
|
|
25629
26081
|
}
|
|
25630
26082
|
if (!options.yes) {
|
|
25631
|
-
writeLine(ctx.io.stderr, `Refusing to remove ${
|
|
26083
|
+
writeLine(ctx.io.stderr, `Refusing to remove ${hostname2} without --yes.`);
|
|
25632
26084
|
return 2;
|
|
25633
26085
|
}
|
|
25634
|
-
const res = await requestJson(ctx, `${base14(orgId)}/${encodeURIComponent(
|
|
26086
|
+
const res = await requestJson(ctx, `${base14(orgId)}/${encodeURIComponent(hostname2)}`, { method: "DELETE" });
|
|
25635
26087
|
if (ctx.json) {
|
|
25636
26088
|
writeJson(ctx.io.stdout, res.body);
|
|
25637
26089
|
return 0;
|
|
25638
26090
|
}
|
|
25639
|
-
writeLine(ctx.io.stdout, `Removed ${
|
|
26091
|
+
writeLine(ctx.io.stdout, `Removed ${hostname2}.`);
|
|
25640
26092
|
return 0;
|
|
25641
26093
|
}
|
|
25642
26094
|
|
|
25643
26095
|
// src/cli/environments.ts
|
|
25644
|
-
var
|
|
26096
|
+
var BASE29 = "/v1/host/openwop-app/environments";
|
|
25645
26097
|
var PROTECTION = ["open", "protected", "locked"];
|
|
25646
26098
|
var ENVIRONMENTS_HELP = `Usage:
|
|
25647
26099
|
openwop environments list [--drift] [--json]
|
|
@@ -25658,21 +26110,21 @@ var ENVIRONMENTS_HELP = `Usage:
|
|
|
25658
26110
|
openwop environments apply --snapshot <hash> [--json]
|
|
25659
26111
|
openwop environments promotions [--json]
|
|
25660
26112
|
|
|
25661
|
-
Config environments + promotion (host extension under ${
|
|
26113
|
+
Config environments + promotion (host extension under ${BASE29}; toggle 'environments').
|
|
25662
26114
|
Reads need workspace:read; every write needs host:members:manage in the workspace.
|
|
25663
26115
|
|
|
25664
|
-
list GET ${
|
|
25665
|
-
create POST ${
|
|
25666
|
-
ensure-chain POST ${
|
|
25667
|
-
protect PATCH ${
|
|
25668
|
-
settings GET/PATCH ${
|
|
25669
|
-
snapshots GET ${
|
|
25670
|
-
snapshot POST ${
|
|
25671
|
-
preview POST ${
|
|
25672
|
-
promote POST ${
|
|
25673
|
-
rollback POST ${
|
|
25674
|
-
apply POST ${
|
|
25675
|
-
promotions GET ${
|
|
26116
|
+
list GET ${BASE29}[?drift=1] environments, config domains, app version
|
|
26117
|
+
create POST ${BASE29} add an environment to the chain
|
|
26118
|
+
ensure-chain POST ${BASE29}/ensure-chain create the default dev \u2192 staging \u2192 prod chain
|
|
26119
|
+
protect PATCH ${BASE29}/:name/protection set open | protected | locked
|
|
26120
|
+
settings GET/PATCH ${BASE29}/settings the promotion-approval gate
|
|
26121
|
+
snapshots GET ${BASE29}/snapshots stored config snapshots
|
|
26122
|
+
snapshot POST ${BASE29}/snapshots snapshot the current live config
|
|
26123
|
+
preview POST ${BASE29}/preview diff a snapshot against an environment (no change)
|
|
26124
|
+
promote POST ${BASE29}/promote promote an env's snapshot to the next (or --to) env
|
|
26125
|
+
rollback POST ${BASE29}/rollback point an env back at a historical snapshot
|
|
26126
|
+
apply POST ${BASE29}/apply apply a snapshot to the live config
|
|
26127
|
+
promotions GET ${BASE29}/promotions the promotion ledger
|
|
25676
26128
|
|
|
25677
26129
|
Exit codes: 0 ok \xB7 3 queued for approval (202 pending_approval) \xB7 2 usage / not found /
|
|
25678
26130
|
feature off \xB7 4 permission denied.
|
|
@@ -25705,7 +26157,7 @@ async function runEnvironments(ctx, argv) {
|
|
|
25705
26157
|
};
|
|
25706
26158
|
switch (sub) {
|
|
25707
26159
|
case "list": {
|
|
25708
|
-
const res = await requestJson(ctx, `${
|
|
26160
|
+
const res = await requestJson(ctx, `${BASE29}${options.drift ? "?drift=1" : ""}`);
|
|
25709
26161
|
emit2(res.body, () => {
|
|
25710
26162
|
const envs = arrayOf(res.body, "environments");
|
|
25711
26163
|
if (envs.length === 0) {
|
|
@@ -25732,35 +26184,35 @@ async function runEnvironments(ctx, argv) {
|
|
|
25732
26184
|
body.order = n;
|
|
25733
26185
|
}
|
|
25734
26186
|
if (options.protection !== void 0) body.protection = protectionOf(options.protection);
|
|
25735
|
-
const res = await requestJson(ctx,
|
|
26187
|
+
const res = await requestJson(ctx, BASE29, { method: "POST", body });
|
|
25736
26188
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Created environment ${res.body?.name ?? name} (order ${res.body?.order ?? "?"}, ${res.body?.protection ?? "?"}).`));
|
|
25737
26189
|
return 0;
|
|
25738
26190
|
}
|
|
25739
26191
|
case "ensure-chain": {
|
|
25740
|
-
const res = await requestJson(ctx, `${
|
|
26192
|
+
const res = await requestJson(ctx, `${BASE29}/ensure-chain`, { method: "POST", body: {} });
|
|
25741
26193
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Chain: ${arrayOf(res.body, "environments").map((e) => e.name).join(" \u2192 ")}`));
|
|
25742
26194
|
return 0;
|
|
25743
26195
|
}
|
|
25744
26196
|
case "protect": {
|
|
25745
26197
|
const name = positionals[0];
|
|
25746
26198
|
if (!name || options.protection === void 0) throw new CliError("Usage: openwop environments protect <name> --protection open|protected|locked", 2);
|
|
25747
|
-
const res = await requestJson(ctx, `${
|
|
26199
|
+
const res = await requestJson(ctx, `${BASE29}/${encodeURIComponent(name)}/protection`, { method: "PATCH", body: { protection: protectionOf(options.protection) } });
|
|
25748
26200
|
emit2(res.body, () => writeLine(ctx.io.stdout, `${name} is now ${res.body?.protection ?? options.protection}.`));
|
|
25749
26201
|
return 0;
|
|
25750
26202
|
}
|
|
25751
26203
|
case "settings": {
|
|
25752
26204
|
if (positionals[0] === "set") {
|
|
25753
26205
|
if (options.requireApproval === void 0) throw new CliError("Usage: openwop environments settings set --require-approval true|false", 2);
|
|
25754
|
-
const res2 = await requestJson(ctx, `${
|
|
26206
|
+
const res2 = await requestJson(ctx, `${BASE29}/settings`, { method: "PATCH", body: { requireApprovalForPromotion: parseBool("--require-approval", options.requireApproval) } });
|
|
25755
26207
|
emit2(res2.body, () => writeLine(ctx.io.stdout, `requireApprovalForPromotion: ${res2.body?.requireApprovalForPromotion}`));
|
|
25756
26208
|
return 0;
|
|
25757
26209
|
}
|
|
25758
|
-
const res = await requestJson(ctx, `${
|
|
26210
|
+
const res = await requestJson(ctx, `${BASE29}/settings`);
|
|
25759
26211
|
emit2(res.body, () => writeLine(ctx.io.stdout, `requireApprovalForPromotion: ${res.body?.requireApprovalForPromotion ?? false}`));
|
|
25760
26212
|
return 0;
|
|
25761
26213
|
}
|
|
25762
26214
|
case "snapshots": {
|
|
25763
|
-
const res = await requestJson(ctx, `${
|
|
26215
|
+
const res = await requestJson(ctx, `${BASE29}/snapshots`);
|
|
25764
26216
|
emit2(res.body, () => {
|
|
25765
26217
|
const snaps = arrayOf(res.body, "snapshots");
|
|
25766
26218
|
if (snaps.length === 0) {
|
|
@@ -25780,13 +26232,13 @@ async function runEnvironments(ctx, argv) {
|
|
|
25780
26232
|
case "snapshot": {
|
|
25781
26233
|
const body = {};
|
|
25782
26234
|
if (options.sourceEnv) body.sourceEnv = options.sourceEnv;
|
|
25783
|
-
const res = await requestJson(ctx, `${
|
|
26235
|
+
const res = await requestJson(ctx, `${BASE29}/snapshots`, { method: "POST", body });
|
|
25784
26236
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Snapshot ${res.body?.hash ?? "?"}${res.body?.sourceEnv ? ` (from ${res.body.sourceEnv})` : ""}.`));
|
|
25785
26237
|
return 0;
|
|
25786
26238
|
}
|
|
25787
26239
|
case "preview": {
|
|
25788
26240
|
if (!options.to || !options.snapshot) throw new CliError("Usage: openwop environments preview --to <env> --snapshot <hash>", 2);
|
|
25789
|
-
const res = await requestJson(ctx, `${
|
|
26241
|
+
const res = await requestJson(ctx, `${BASE29}/preview`, { method: "POST", body: { toEnv: options.to, snapshotHash: options.snapshot } });
|
|
25790
26242
|
writeJson(ctx.io.stdout, res.body);
|
|
25791
26243
|
return 0;
|
|
25792
26244
|
}
|
|
@@ -25794,18 +26246,18 @@ async function runEnvironments(ctx, argv) {
|
|
|
25794
26246
|
if (!options.from) throw new CliError("Usage: openwop environments promote --from <env> [--to <env>]", 2);
|
|
25795
26247
|
const body = { fromEnv: options.from };
|
|
25796
26248
|
if (options.to) body.toEnv = options.to;
|
|
25797
|
-
return movement(ctx, await requestJson(ctx, `${
|
|
26249
|
+
return movement(ctx, await requestJson(ctx, `${BASE29}/promote`, { method: "POST", body }), "Promoted");
|
|
25798
26250
|
}
|
|
25799
26251
|
case "rollback": {
|
|
25800
26252
|
if (!options.env || !options.snapshot) throw new CliError("Usage: openwop environments rollback --env <env> --snapshot <hash>", 2);
|
|
25801
|
-
return movement(ctx, await requestJson(ctx, `${
|
|
26253
|
+
return movement(ctx, await requestJson(ctx, `${BASE29}/rollback`, { method: "POST", body: { env: options.env, snapshotHash: options.snapshot } }), "Rolled back");
|
|
25802
26254
|
}
|
|
25803
26255
|
case "apply": {
|
|
25804
26256
|
if (!options.snapshot) throw new CliError("Usage: openwop environments apply --snapshot <hash>", 2);
|
|
25805
|
-
return movement(ctx, await requestJson(ctx, `${
|
|
26257
|
+
return movement(ctx, await requestJson(ctx, `${BASE29}/apply`, { method: "POST", body: { snapshotHash: options.snapshot } }), "Applied");
|
|
25806
26258
|
}
|
|
25807
26259
|
case "promotions": {
|
|
25808
|
-
const res = await requestJson(ctx, `${
|
|
26260
|
+
const res = await requestJson(ctx, `${BASE29}/promotions`);
|
|
25809
26261
|
emit2(res.body, () => {
|
|
25810
26262
|
const rows = arrayOf(res.body, "promotions");
|
|
25811
26263
|
if (rows.length === 0) {
|
|
@@ -25850,7 +26302,7 @@ function movement(ctx, res, verb) {
|
|
|
25850
26302
|
}
|
|
25851
26303
|
|
|
25852
26304
|
// src/cli/billing.ts
|
|
25853
|
-
var
|
|
26305
|
+
var BASE30 = "/v1/host/openwop-app/billing";
|
|
25854
26306
|
var BILLING_HELP = `Usage:
|
|
25855
26307
|
openwop billing subscription [--json]
|
|
25856
26308
|
openwop billing balance [--json]
|
|
@@ -25868,7 +26320,7 @@ var BILLING_HELP = `Usage:
|
|
|
25868
26320
|
openwop billing sync-seats [--json]
|
|
25869
26321
|
openwop billing import (--body <json> | --body-file <path>) [--json]
|
|
25870
26322
|
|
|
25871
|
-
Workspace billing (host extension under ${
|
|
26323
|
+
Workspace billing (host extension under ${BASE30}; toggle 'billing').
|
|
25872
26324
|
subscription GET /subscription plan tier, status, seats, period
|
|
25873
26325
|
balance GET /balance purchased token balance
|
|
25874
26326
|
entitlements GET /entitlements the plan's allowed features + limits
|
|
@@ -25915,7 +26367,7 @@ async function runBilling(ctx, argv) {
|
|
|
25915
26367
|
const superadmin = (path, body, surface) => gatedRequest(ctx, path, { method: "POST", body }, surface, "superadmin");
|
|
25916
26368
|
switch (sub) {
|
|
25917
26369
|
case "subscription": {
|
|
25918
|
-
const res = await requestJson(ctx, `${
|
|
26370
|
+
const res = await requestJson(ctx, `${BASE30}/subscription`);
|
|
25919
26371
|
emit2(res.body, () => {
|
|
25920
26372
|
const s = res.body ?? {};
|
|
25921
26373
|
writeLine(ctx.io.stdout, `plan: ${s.planTier ?? "?"} (${s.status ?? "?"})`);
|
|
@@ -25926,12 +26378,12 @@ async function runBilling(ctx, argv) {
|
|
|
25926
26378
|
return 0;
|
|
25927
26379
|
}
|
|
25928
26380
|
case "balance": {
|
|
25929
|
-
const res = await requestJson(ctx, `${
|
|
26381
|
+
const res = await requestJson(ctx, `${BASE30}/balance`);
|
|
25930
26382
|
emit2(res.body, () => writeLine(ctx.io.stdout, `tokens available: ${res.body?.totalAvailable ?? 0} (purchased total ${res.body?.purchasedTokensTotal ?? 0})`));
|
|
25931
26383
|
return 0;
|
|
25932
26384
|
}
|
|
25933
26385
|
case "entitlements": {
|
|
25934
|
-
const res = await requestJson(ctx, `${
|
|
26386
|
+
const res = await requestJson(ctx, `${BASE30}/entitlements`);
|
|
25935
26387
|
emit2(res.body, () => {
|
|
25936
26388
|
const e = res.body ?? {};
|
|
25937
26389
|
writeLine(ctx.io.stdout, `plan: ${e.plan ?? "?"}`);
|
|
@@ -25942,7 +26394,7 @@ async function runBilling(ctx, argv) {
|
|
|
25942
26394
|
return 0;
|
|
25943
26395
|
}
|
|
25944
26396
|
case "bundles": {
|
|
25945
|
-
const res = await requestJson(ctx, `${
|
|
26397
|
+
const res = await requestJson(ctx, `${BASE30}/bundles`);
|
|
25946
26398
|
emit2(res.body, () => {
|
|
25947
26399
|
const rows = arrayOf(res.body, "bundles");
|
|
25948
26400
|
if (rows.length === 0) {
|
|
@@ -25962,19 +26414,19 @@ async function runBilling(ctx, argv) {
|
|
|
25962
26414
|
const verb = positionals[0] ?? "list";
|
|
25963
26415
|
if (verb === "get") {
|
|
25964
26416
|
if (!positionals[1]) throw new CliError("Usage: openwop billing invoices get <invoiceId>", 2);
|
|
25965
|
-
const res2 = await requestJson(ctx, `${
|
|
26417
|
+
const res2 = await requestJson(ctx, `${BASE30}/invoices/${encodeURIComponent(positionals[1])}`);
|
|
25966
26418
|
emit2(res2.body, () => writeLine(ctx.io.stdout, res2.body?.markdown ?? JSON.stringify(res2.body, null, 2)));
|
|
25967
26419
|
return 0;
|
|
25968
26420
|
}
|
|
25969
26421
|
if (verb === "create") {
|
|
25970
26422
|
const amount = Number(options.amount);
|
|
25971
26423
|
if (options.amount === void 0 || !Number.isFinite(amount)) throw new CliError("Usage: openwop billing invoices create --amount <n> [--currency <c>]", 2);
|
|
25972
|
-
const res2 = await superadmin(`${
|
|
26424
|
+
const res2 = await superadmin(`${BASE30}/invoices`, { amount, ...options.currency ? { currency: options.currency } : {} }, "Generating an invoice");
|
|
25973
26425
|
emit2(res2.body, () => writeLine(ctx.io.stdout, `Created invoice ${res2.body?.invoiceId ?? "?"} (${res2.body?.amount} ${res2.body?.currency}).`));
|
|
25974
26426
|
return 0;
|
|
25975
26427
|
}
|
|
25976
26428
|
if (verb !== "list") throw new CliError(`Unknown invoices verb: ${verb}`, 2);
|
|
25977
|
-
const res = await requestJson(ctx, `${
|
|
26429
|
+
const res = await requestJson(ctx, `${BASE30}/invoices`);
|
|
25978
26430
|
emit2(res.body, () => {
|
|
25979
26431
|
const rows = arrayOf(res.body, "invoices");
|
|
25980
26432
|
if (rows.length === 0) {
|
|
@@ -25992,18 +26444,18 @@ async function runBilling(ctx, argv) {
|
|
|
25992
26444
|
}
|
|
25993
26445
|
case "checkout": {
|
|
25994
26446
|
if (!options.price) throw new CliError("Usage: openwop billing checkout --price <priceId>", 2);
|
|
25995
|
-
const res = await requestJson(ctx, `${
|
|
26447
|
+
const res = await requestJson(ctx, `${BASE30}/checkout`, { method: "POST", body: { priceId: options.price } });
|
|
25996
26448
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Open to complete checkout (${res.body?.mode ?? "?"}): ${res.body?.url ?? "?"}`));
|
|
25997
26449
|
return 0;
|
|
25998
26450
|
}
|
|
25999
26451
|
case "bundle-checkout": {
|
|
26000
26452
|
if (!positionals[0]) throw new CliError("Usage: openwop billing bundle-checkout <bundleId>", 2);
|
|
26001
|
-
const res = await requestJson(ctx, `${
|
|
26453
|
+
const res = await requestJson(ctx, `${BASE30}/bundles/${encodeURIComponent(positionals[0])}/checkout`, { method: "POST", body: {} });
|
|
26002
26454
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Open to complete checkout (${res.body?.mode ?? "?"}): ${res.body?.url ?? "?"}`));
|
|
26003
26455
|
return 0;
|
|
26004
26456
|
}
|
|
26005
26457
|
case "portal": {
|
|
26006
|
-
const res = await requestJson(ctx, `${
|
|
26458
|
+
const res = await requestJson(ctx, `${BASE30}/portal`, { method: "POST", body: {} });
|
|
26007
26459
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Billing portal (${res.body?.mode ?? "?"}): ${res.body?.url ?? "?"}`));
|
|
26008
26460
|
return 0;
|
|
26009
26461
|
}
|
|
@@ -26016,19 +26468,19 @@ async function runBilling(ctx, argv) {
|
|
|
26016
26468
|
if (!Number.isFinite(v)) throw new CliError("--value must be a number.", 2);
|
|
26017
26469
|
body.value = v;
|
|
26018
26470
|
}
|
|
26019
|
-
const res = await superadmin(`${
|
|
26471
|
+
const res = await superadmin(`${BASE30}/coupons`, body, "Creating a billing coupon");
|
|
26020
26472
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Created coupon ${res.body?.code ?? options.code} (${res.body?.type} ${res.body?.value}).`));
|
|
26021
26473
|
return 0;
|
|
26022
26474
|
}
|
|
26023
26475
|
case "sync-seats": {
|
|
26024
|
-
const res = await superadmin(`${
|
|
26476
|
+
const res = await superadmin(`${BASE30}/sync-seats`, {}, "Billing seat sync");
|
|
26025
26477
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Seats synced: ${res.body?.quantity ?? "?"}.`));
|
|
26026
26478
|
return 0;
|
|
26027
26479
|
}
|
|
26028
26480
|
case "import": {
|
|
26029
26481
|
const body = readBodyOption(ctx, options);
|
|
26030
26482
|
if (!body) throw new CliError('Usage: openwop billing import (--body <json> | --body-file <path>) \u2014 {"subscriptions":[...],"balances":[...]}', 2);
|
|
26031
|
-
const res = await superadmin(`${
|
|
26483
|
+
const res = await superadmin(`${BASE30}/import`, body, "Billing import");
|
|
26032
26484
|
emit2(res.body, () => writeLine(ctx.io.stdout, `Imported ${res.body?.subscriptions ?? 0} subscription(s), ${res.body?.balances ?? 0} balance(s).`));
|
|
26033
26485
|
return 0;
|
|
26034
26486
|
}
|
|
@@ -26099,15 +26551,15 @@ Run \`openwop site-config --help\` for usage.`);
|
|
|
26099
26551
|
}
|
|
26100
26552
|
|
|
26101
26553
|
// src/cli/runtimePosture.ts
|
|
26102
|
-
var
|
|
26554
|
+
var BASE31 = "/v1/host/openwop-app/runtime-posture";
|
|
26103
26555
|
var SURFACE5 = "Runtime posture";
|
|
26104
26556
|
var RUNTIME_POSTURE_HELP = `Usage:
|
|
26105
26557
|
openwop runtime-posture [get] [--json]
|
|
26106
26558
|
openwop runtime-posture request --warm|--cold [--json]
|
|
26107
26559
|
|
|
26108
26560
|
The serving service's runtime posture (host extension, SUPER-ADMIN gated).
|
|
26109
|
-
get GET ${
|
|
26110
|
-
request POST ${
|
|
26561
|
+
get GET ${BASE31} live posture (serving revision, warm/cold, rollout)
|
|
26562
|
+
request POST ${BASE31}/change-requests ask for a warm/cold change; returns the commands to run
|
|
26111
26563
|
|
|
26112
26564
|
A change request APPLIES NOTHING: the host records it in the audit log and returns the
|
|
26113
26565
|
exact commands for an operator holding deploy rights. Without a super-admin principal
|
|
@@ -26131,7 +26583,7 @@ async function runRuntimePosture(ctx, argv) {
|
|
|
26131
26583
|
return 0;
|
|
26132
26584
|
}
|
|
26133
26585
|
if (sub === "get") {
|
|
26134
|
-
const res2 = await gatedRequest(ctx,
|
|
26586
|
+
const res2 = await gatedRequest(ctx, BASE31, void 0, SURFACE5, "superadmin");
|
|
26135
26587
|
if (ctx.json) {
|
|
26136
26588
|
writeJson(ctx.io.stdout, res2.body);
|
|
26137
26589
|
return 0;
|
|
@@ -26150,7 +26602,7 @@ async function runRuntimePosture(ctx, argv) {
|
|
|
26150
26602
|
if (sub !== "request") throw new CliError(`Unknown runtime-posture command: ${sub}
|
|
26151
26603
|
Run \`openwop runtime-posture --help\` for usage.`);
|
|
26152
26604
|
if (Boolean(options.warm) === Boolean(options.cold)) throw new CliError("Pass exactly one of --warm or --cold.", 2);
|
|
26153
|
-
const res = await gatedRequest(ctx, `${
|
|
26605
|
+
const res = await gatedRequest(ctx, `${BASE31}/change-requests`, { method: "POST", body: { warm: Boolean(options.warm) } }, SURFACE5, "superadmin");
|
|
26154
26606
|
if (ctx.json) {
|
|
26155
26607
|
writeJson(ctx.io.stdout, res.body);
|
|
26156
26608
|
return 0;
|
|
@@ -26205,16 +26657,16 @@ Run \`openwop maintenance --help\` for usage.`);
|
|
|
26205
26657
|
}
|
|
26206
26658
|
|
|
26207
26659
|
// src/cli/menuConfig.ts
|
|
26208
|
-
var
|
|
26660
|
+
var BASE32 = "/v1/host/openwop-app/menu-config";
|
|
26209
26661
|
var MENU_CONFIG_HELP = `Usage:
|
|
26210
26662
|
openwop menu-config [get] [--json]
|
|
26211
26663
|
openwop menu-config set me (--body <json> | --body-file <path>) [--json]
|
|
26212
26664
|
openwop menu-config set tenant (--body <json> | --body-file <path>) [--json]
|
|
26213
26665
|
|
|
26214
26666
|
Navigation menu layout (host extension).
|
|
26215
|
-
get GET ${
|
|
26216
|
-
set me PUT ${
|
|
26217
|
-
set tenant PUT ${
|
|
26667
|
+
get GET ${BASE32} {tenant, user} layers for the caller
|
|
26668
|
+
set me PUT ${BASE32}/me replace YOUR personal layer (signed-in user)
|
|
26669
|
+
set tenant PUT ${BASE32}/tenant [super-admin] replace the workspace default layer
|
|
26218
26670
|
|
|
26219
26671
|
The body is the menu config object ({"items": {...}, "headers": [...]}) or the full
|
|
26220
26672
|
request ({"config": {...}}). A write REPLACES that layer. \`set tenant\` reads the current
|
|
@@ -26240,7 +26692,7 @@ async function runMenuConfig(ctx, argv) {
|
|
|
26240
26692
|
return 0;
|
|
26241
26693
|
}
|
|
26242
26694
|
if (sub === "get") {
|
|
26243
|
-
const res2 = await requestJson(ctx,
|
|
26695
|
+
const res2 = await requestJson(ctx, BASE32);
|
|
26244
26696
|
writeJson(ctx.io.stdout, res2.body);
|
|
26245
26697
|
return 0;
|
|
26246
26698
|
}
|
|
@@ -26254,13 +26706,13 @@ Run \`openwop menu-config --help\` for usage.`);
|
|
|
26254
26706
|
const body = "config" in raw ? raw : { config: raw };
|
|
26255
26707
|
let res;
|
|
26256
26708
|
if (layer === "me") {
|
|
26257
|
-
res = await gatedRequest(ctx, `${
|
|
26709
|
+
res = await gatedRequest(ctx, `${BASE32}/me`, { method: "PUT", body }, "Editing your menu layout", "signed-in");
|
|
26258
26710
|
} else {
|
|
26259
|
-
const current = await requestJson(ctx,
|
|
26711
|
+
const current = await requestJson(ctx, BASE32);
|
|
26260
26712
|
const etag = current.headers.get("etag");
|
|
26261
26713
|
res = await gatedRequest(
|
|
26262
26714
|
ctx,
|
|
26263
|
-
`${
|
|
26715
|
+
`${BASE32}/tenant`,
|
|
26264
26716
|
{ method: "PUT", body, ...etag ? { headers: { "if-match": etag } } : {} },
|
|
26265
26717
|
"Editing the workspace default menu layout",
|
|
26266
26718
|
"superadmin"
|
|
@@ -26650,7 +27102,7 @@ function renderKnowledge(ctx, agentId, k) {
|
|
|
26650
27102
|
|
|
26651
27103
|
// src/cli/compatEndpoints.ts
|
|
26652
27104
|
import { readFileSync as readFileSync19 } from "node:fs";
|
|
26653
|
-
var
|
|
27105
|
+
var BASE33 = "/v1/host/openwop-app/compat-endpoints";
|
|
26654
27106
|
var COMPAT_ENDPOINTS_HELP = `Usage:
|
|
26655
27107
|
openwop compat-endpoints list --org <orgId> [--json]
|
|
26656
27108
|
openwop compat-endpoints create --org <orgId> --label <l> --base-url-endpoint <https://\u2026> [--api-key-file <path> | --api-key-env <VAR>] [--model <m>]... [--vision] [--tools] [--long-context] [--json]
|
|
@@ -26700,7 +27152,7 @@ async function runCompatEndpoints(ctx, argv) {
|
|
|
26700
27152
|
switch (sub) {
|
|
26701
27153
|
case "list": {
|
|
26702
27154
|
const org2 = requireOrg(options.org);
|
|
26703
|
-
const res = await requestJson(ctx, `${
|
|
27155
|
+
const res = await requestJson(ctx, `${BASE33}?orgId=${encodeURIComponent(org2)}`);
|
|
26704
27156
|
if (ctx.json) {
|
|
26705
27157
|
writeJson(ctx.io.stdout, res.body);
|
|
26706
27158
|
return 0;
|
|
@@ -26733,7 +27185,7 @@ async function runCompatEndpoints(ctx, argv) {
|
|
|
26733
27185
|
const apiKey = readApiKey(ctx, options);
|
|
26734
27186
|
if (apiKey) body.apiKey = apiKey;
|
|
26735
27187
|
if (Array.isArray(options.model) && options.model.length) body.models = options.model;
|
|
26736
|
-
const res = await requestJson(ctx,
|
|
27188
|
+
const res = await requestJson(ctx, BASE33, { method: "POST", body });
|
|
26737
27189
|
if (ctx.json) {
|
|
26738
27190
|
writeJson(ctx.io.stdout, res.body);
|
|
26739
27191
|
return 0;
|
|
@@ -26747,7 +27199,7 @@ async function runCompatEndpoints(ctx, argv) {
|
|
|
26747
27199
|
writeLine(ctx.io.stderr, `Refusing to delete ${positionals[0]} (and its stored key) without --yes.`);
|
|
26748
27200
|
return 2;
|
|
26749
27201
|
}
|
|
26750
|
-
await requestJson(ctx, `${
|
|
27202
|
+
await requestJson(ctx, `${BASE33}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
|
|
26751
27203
|
writeLine(ctx.io.stdout, `Deleted compat endpoint ${positionals[0]}.`);
|
|
26752
27204
|
return 0;
|
|
26753
27205
|
}
|
|
@@ -26777,7 +27229,7 @@ function readApiKey(ctx, options) {
|
|
|
26777
27229
|
}
|
|
26778
27230
|
|
|
26779
27231
|
// src/cli/hostEvents.ts
|
|
26780
|
-
var
|
|
27232
|
+
var BASE34 = "/v1/host/openwop-app/host-events/bindings";
|
|
26781
27233
|
var HOST_EVENTS_HELP = `Usage:
|
|
26782
27234
|
openwop host-events list [--json]
|
|
26783
27235
|
openwop host-events bind --event <host.\u2026> --workflow <workflowId> [--json]
|
|
@@ -26820,7 +27272,7 @@ async function runHostEvents(ctx, argv) {
|
|
|
26820
27272
|
}
|
|
26821
27273
|
switch (sub) {
|
|
26822
27274
|
case "list": {
|
|
26823
|
-
const res = await requestJson(ctx,
|
|
27275
|
+
const res = await requestJson(ctx, BASE34);
|
|
26824
27276
|
if (ctx.json) {
|
|
26825
27277
|
writeJson(ctx.io.stdout, res.body);
|
|
26826
27278
|
return 0;
|
|
@@ -26841,7 +27293,7 @@ async function runHostEvents(ctx, argv) {
|
|
|
26841
27293
|
case "bind": {
|
|
26842
27294
|
if (!options.event || !options.workflow) throw new CliError("bind needs --event <host.\u2026> and --workflow <workflowId>.", 2);
|
|
26843
27295
|
if (!String(options.event).startsWith("host.")) throw new CliError('--event must be a host-extension event name starting with "host.".', 2);
|
|
26844
|
-
const res = await requestJson(ctx,
|
|
27296
|
+
const res = await requestJson(ctx, BASE34, { method: "POST", body: { eventType: options.event, workflowId: options.workflow } });
|
|
26845
27297
|
if (ctx.json) {
|
|
26846
27298
|
writeJson(ctx.io.stdout, res.body);
|
|
26847
27299
|
return 0;
|
|
@@ -26852,7 +27304,7 @@ async function runHostEvents(ctx, argv) {
|
|
|
26852
27304
|
case "enable":
|
|
26853
27305
|
case "disable": {
|
|
26854
27306
|
if (positionals.length !== 1) throw new CliError(`Usage: openwop host-events ${sub} <bindingId> [--json]`, 2);
|
|
26855
|
-
const res = await requestJson(ctx, `${
|
|
27307
|
+
const res = await requestJson(ctx, `${BASE34}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body: { enabled: sub === "enable" } });
|
|
26856
27308
|
if (ctx.json) {
|
|
26857
27309
|
writeJson(ctx.io.stdout, res.body);
|
|
26858
27310
|
return 0;
|
|
@@ -26866,7 +27318,7 @@ async function runHostEvents(ctx, argv) {
|
|
|
26866
27318
|
writeLine(ctx.io.stderr, `Refusing to delete binding ${positionals[0]} without --yes.`);
|
|
26867
27319
|
return 2;
|
|
26868
27320
|
}
|
|
26869
|
-
await requestJson(ctx, `${
|
|
27321
|
+
await requestJson(ctx, `${BASE34}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
|
|
26870
27322
|
writeLine(ctx.io.stdout, `Deleted binding ${positionals[0]}.`);
|
|
26871
27323
|
return 0;
|
|
26872
27324
|
}
|
|
@@ -27010,8 +27462,10 @@ var OPENAPI_HELP = `Usage:
|
|
|
27010
27462
|
openwop openapi [--out <file>] [--json]
|
|
27011
27463
|
openwop openapi paths [--filter <text>] [--json]
|
|
27012
27464
|
|
|
27013
|
-
Fetch the host's OpenAPI document
|
|
27014
|
-
host
|
|
27465
|
+
Fetch the host's OpenAPI document \u2014 the document the host serves, which lists
|
|
27466
|
+
the operations it implements. A host that speaks protocol major 2 is asked for
|
|
27467
|
+
GET /openapi.json with OpenWOP-Version: 2.0 (its v2 document); a v1-only host
|
|
27468
|
+
for GET /v1/openapi.json.
|
|
27015
27469
|
|
|
27016
27470
|
(default) Prints the document's title, version and operation count; --json
|
|
27017
27471
|
prints the whole document; --out <file> saves it.
|
|
@@ -27063,7 +27517,7 @@ Run \`openwop openapi --help\` for usage.`);
|
|
|
27063
27517
|
}
|
|
27064
27518
|
|
|
27065
27519
|
// src/cli/assistant.ts
|
|
27066
|
-
var
|
|
27520
|
+
var BASE35 = "/v1/host/openwop-app/assistant";
|
|
27067
27521
|
var ASSISTANT_HELP = `Usage:
|
|
27068
27522
|
openwop assistant workspace [--json]
|
|
27069
27523
|
openwop assistant briefing [--json]
|
|
@@ -27126,7 +27580,7 @@ async function runAssistant(ctx, argv) {
|
|
|
27126
27580
|
return await runWorkspace2(ctx, rest);
|
|
27127
27581
|
case "briefing":
|
|
27128
27582
|
case "brief":
|
|
27129
|
-
return await simpleRead(ctx, rest, `${
|
|
27583
|
+
return await simpleRead(ctx, rest, `${BASE35}/briefing`, (b) => writeLine(ctx.io.stdout, JSON.stringify(b?.brief ?? b, null, 2)));
|
|
27130
27584
|
case "projects":
|
|
27131
27585
|
case "project":
|
|
27132
27586
|
return await runProjects2(ctx, rest);
|
|
@@ -27139,7 +27593,7 @@ async function runAssistant(ctx, argv) {
|
|
|
27139
27593
|
case "meeting":
|
|
27140
27594
|
return await runMeetings(ctx, rest);
|
|
27141
27595
|
case "stakeholders":
|
|
27142
|
-
return await simpleRead(ctx, rest, `${
|
|
27596
|
+
return await simpleRead(ctx, rest, `${BASE35}/stakeholders`, (b) => table2(ctx, b?.stakeholders, (s) => ({
|
|
27143
27597
|
stakeholderId: s.stakeholderId,
|
|
27144
27598
|
name: s.person?.name ?? s.person?.email ?? "",
|
|
27145
27599
|
importance: String(s.importance ?? ""),
|
|
@@ -27152,7 +27606,7 @@ async function runAssistant(ctx, argv) {
|
|
|
27152
27606
|
case "loop":
|
|
27153
27607
|
return await runLoops(ctx, rest);
|
|
27154
27608
|
case "health":
|
|
27155
|
-
return await simpleRead(ctx, rest, `${
|
|
27609
|
+
return await simpleRead(ctx, rest, `${BASE35}/health`, (b) => writeLine(ctx.io.stdout, JSON.stringify(b?.health ?? b, null, 2)));
|
|
27156
27610
|
default:
|
|
27157
27611
|
throw new CliError(`Unknown assistant command: ${sub}
|
|
27158
27612
|
Run \`openwop assistant --help\` for usage.`, 2);
|
|
@@ -27182,7 +27636,7 @@ async function runWorkspace2(ctx, argv) {
|
|
|
27182
27636
|
write(ctx.io.stdout, ASSISTANT_HELP);
|
|
27183
27637
|
return 0;
|
|
27184
27638
|
}
|
|
27185
|
-
const res = await requestJson(ctx, `${
|
|
27639
|
+
const res = await requestJson(ctx, `${BASE35}/workspace-conversation`, { method: "POST", body: {} });
|
|
27186
27640
|
return emit(ctx, res.body, () => {
|
|
27187
27641
|
writeLine(ctx.io.stdout, `${res.status === 201 ? "Opened" : "Resumed"} workspace conversation ${res.body?.sessionId}.`);
|
|
27188
27642
|
writeLine(ctx.io.stdout, `Talk to the assistant: openwop chat messages send ${res.body?.sessionId} --content "\u2026"`);
|
|
@@ -27208,7 +27662,7 @@ async function runProjects2(ctx, argv) {
|
|
|
27208
27662
|
const id = positionals[0];
|
|
27209
27663
|
const needId = () => {
|
|
27210
27664
|
if (!id) throw new CliError(`assistant projects ${sub} requires <projectId>.`, 2);
|
|
27211
|
-
return `${
|
|
27665
|
+
return `${BASE35}/projects/${enc(id)}`;
|
|
27212
27666
|
};
|
|
27213
27667
|
const fields = () => {
|
|
27214
27668
|
const body = {};
|
|
@@ -27221,7 +27675,7 @@ async function runProjects2(ctx, argv) {
|
|
|
27221
27675
|
};
|
|
27222
27676
|
switch (sub) {
|
|
27223
27677
|
case "list": {
|
|
27224
|
-
const res = await requestJson(ctx, `${
|
|
27678
|
+
const res = await requestJson(ctx, `${BASE35}/projects`);
|
|
27225
27679
|
return emit(ctx, res.body, () => table2(ctx, res.body?.projects, projectRow, "No assistant projects. Create one with `openwop assistant projects create --name <n>`."));
|
|
27226
27680
|
}
|
|
27227
27681
|
case "get": {
|
|
@@ -27242,7 +27696,7 @@ async function runProjects2(ctx, argv) {
|
|
|
27242
27696
|
}
|
|
27243
27697
|
case "create": {
|
|
27244
27698
|
if (!options.name) throw new CliError("assistant projects create requires --name <n>.", 2);
|
|
27245
|
-
const res = await requestJson(ctx, `${
|
|
27699
|
+
const res = await requestJson(ctx, `${BASE35}/projects`, { method: "POST", body: fields() });
|
|
27246
27700
|
return emit(ctx, res.body, () => writeLine(ctx.io.stdout, `Created project ${res.body?.projectId} (${res.body?.name ?? options.name}).`));
|
|
27247
27701
|
}
|
|
27248
27702
|
case "update": {
|
|
@@ -27289,14 +27743,14 @@ async function runCommitments(ctx, argv) {
|
|
|
27289
27743
|
const id = positionals[0];
|
|
27290
27744
|
const needId = () => {
|
|
27291
27745
|
if (!id) throw new CliError(`assistant commitments ${sub} requires <commitmentId>.`, 2);
|
|
27292
|
-
return `${
|
|
27746
|
+
return `${BASE35}/commitments/${enc(id)}`;
|
|
27293
27747
|
};
|
|
27294
27748
|
switch (sub) {
|
|
27295
27749
|
case "list": {
|
|
27296
27750
|
const q = new URLSearchParams();
|
|
27297
27751
|
if (options.status) q.set("status", options.status);
|
|
27298
27752
|
if (options.project) q.set("projectId", options.project);
|
|
27299
|
-
const res = await requestJson(ctx, `${
|
|
27753
|
+
const res = await requestJson(ctx, `${BASE35}/commitments${q.size ? `?${q}` : ""}`);
|
|
27300
27754
|
return emit(ctx, res.body, () => table2(ctx, res.body?.commitments, commitmentRow, "No commitments."));
|
|
27301
27755
|
}
|
|
27302
27756
|
case "get": {
|
|
@@ -27346,7 +27800,7 @@ async function runDecisions(ctx, argv) {
|
|
|
27346
27800
|
write(ctx.io.stdout, ASSISTANT_HELP);
|
|
27347
27801
|
return 0;
|
|
27348
27802
|
}
|
|
27349
|
-
const res = await requestJson(ctx, `${
|
|
27803
|
+
const res = await requestJson(ctx, `${BASE35}/decisions${options.project ? `?projectId=${enc(options.project)}` : ""}`);
|
|
27350
27804
|
return emit(ctx, res.body, () => table2(ctx, res.body?.decisions, (d) => ({
|
|
27351
27805
|
decisionId: d.decisionId,
|
|
27352
27806
|
decidedAt: d.decidedAt ?? "",
|
|
@@ -27363,7 +27817,7 @@ async function runMeetings(ctx, argv) {
|
|
|
27363
27817
|
return 0;
|
|
27364
27818
|
}
|
|
27365
27819
|
if (sub === "list") {
|
|
27366
|
-
const res = await requestJson(ctx, `${
|
|
27820
|
+
const res = await requestJson(ctx, `${BASE35}/meetings`);
|
|
27367
27821
|
return emit(ctx, res.body, () => table2(ctx, res.body?.meetings, (m) => ({
|
|
27368
27822
|
meetingId: m.meetingId,
|
|
27369
27823
|
startAt: m.startAt ?? "",
|
|
@@ -27373,7 +27827,7 @@ async function runMeetings(ctx, argv) {
|
|
|
27373
27827
|
}
|
|
27374
27828
|
if (sub === "get") {
|
|
27375
27829
|
if (!positionals[0]) throw new CliError("assistant meetings get requires <meetingId>.", 2);
|
|
27376
|
-
const res = await requestJson(ctx, `${
|
|
27830
|
+
const res = await requestJson(ctx, `${BASE35}/meetings/${enc(positionals[0])}`);
|
|
27377
27831
|
const m = res.body ?? {};
|
|
27378
27832
|
return emit(ctx, res.body, () => writeFields(ctx, [
|
|
27379
27833
|
["meetingId", m.meetingId],
|
|
@@ -27402,11 +27856,11 @@ async function runPending(ctx, argv) {
|
|
|
27402
27856
|
const id = positionals[0];
|
|
27403
27857
|
const needId = () => {
|
|
27404
27858
|
if (!id) throw new CliError(`assistant pending ${sub} requires <actionId>.`, 2);
|
|
27405
|
-
return `${
|
|
27859
|
+
return `${BASE35}/pending-actions/${enc(id)}`;
|
|
27406
27860
|
};
|
|
27407
27861
|
switch (sub) {
|
|
27408
27862
|
case "list": {
|
|
27409
|
-
const res = await requestJson(ctx, `${
|
|
27863
|
+
const res = await requestJson(ctx, `${BASE35}/pending-actions${options.status ? `?status=${enc(options.status)}` : ""}`);
|
|
27410
27864
|
return emit(ctx, res.body, () => table2(ctx, res.body?.pendingActions, (a) => ({
|
|
27411
27865
|
actionId: a.actionId,
|
|
27412
27866
|
kind: a.kind ?? "",
|
|
@@ -27450,7 +27904,7 @@ async function runLoops(ctx, argv) {
|
|
|
27450
27904
|
return 0;
|
|
27451
27905
|
}
|
|
27452
27906
|
if (sub === "list") {
|
|
27453
|
-
const res = await requestJson(ctx, `${
|
|
27907
|
+
const res = await requestJson(ctx, `${BASE35}/loops`);
|
|
27454
27908
|
return emit(ctx, res.body, () => table2(ctx, res.body?.loops, (l) => ({
|
|
27455
27909
|
loopId: l.loopId,
|
|
27456
27910
|
label: l.label ?? "",
|
|
@@ -27465,7 +27919,7 @@ async function runLoops(ctx, argv) {
|
|
|
27465
27919
|
if (!loopId) throw new CliError(`assistant loops ${sub} requires <loopId>.`, 2);
|
|
27466
27920
|
const body = {};
|
|
27467
27921
|
if (sub === "enable" && options.cron) body.cronExpr = options.cron;
|
|
27468
|
-
const res = await requestJson(ctx, `${
|
|
27922
|
+
const res = await requestJson(ctx, `${BASE35}/loops/${enc(loopId)}/${sub}`, { method: "POST", body });
|
|
27469
27923
|
return emit(ctx, res.body, () => writeLine(ctx.io.stdout, `Loop ${loopId}: ${res.body?.enabled ? "enabled" : "disabled"} (job ${res.body?.jobId ?? ""}).`));
|
|
27470
27924
|
}
|
|
27471
27925
|
throw new CliError(`Unknown assistant loops command: ${sub}
|
|
@@ -27473,7 +27927,7 @@ Run \`openwop assistant --help\` for usage.`, 2);
|
|
|
27473
27927
|
}
|
|
27474
27928
|
|
|
27475
27929
|
// src/cli/channels.ts
|
|
27476
|
-
var
|
|
27930
|
+
var BASE36 = "/v1/host/openwop-app/channels";
|
|
27477
27931
|
var CHANNELS_HELP = `Usage:
|
|
27478
27932
|
openwop channels list [--json]
|
|
27479
27933
|
openwop channels create --name <n> [--description d] [--private] [--member <userId>]... [--agent <agentId>]... [--json]
|
|
@@ -27572,7 +28026,7 @@ function oneId(argv, usage6, spec = {}) {
|
|
|
27572
28026
|
const { options, positionals } = parseOptions(argv, { ...spec, bool: ["--help", ...spec.bool ?? []] });
|
|
27573
28027
|
if (options.help) return { help: true, options, positionals };
|
|
27574
28028
|
if (!positionals[0]) throw new CliError(`Usage: openwop channels ${usage6}`, 2);
|
|
27575
|
-
return { help: false, options, positionals, path: `${
|
|
28029
|
+
return { help: false, options, positionals, path: `${BASE36}/${enc(positionals[0])}` };
|
|
27576
28030
|
}
|
|
27577
28031
|
function channelSummary(res) {
|
|
27578
28032
|
const c = res?.channel ?? {};
|
|
@@ -27585,7 +28039,7 @@ async function runList4(ctx, argv) {
|
|
|
27585
28039
|
write(ctx.io.stdout, CHANNELS_HELP);
|
|
27586
28040
|
return 0;
|
|
27587
28041
|
}
|
|
27588
|
-
const res = await requestJson(ctx,
|
|
28042
|
+
const res = await requestJson(ctx, BASE36);
|
|
27589
28043
|
return emit(ctx, res.body, () => {
|
|
27590
28044
|
const rows = Array.isArray(res.body?.channels) ? res.body.channels : [];
|
|
27591
28045
|
if (!rows.length) {
|
|
@@ -27618,7 +28072,7 @@ async function runCreate(ctx, argv) {
|
|
|
27618
28072
|
if (options.description) body.description = options.description;
|
|
27619
28073
|
if (Array.isArray(options.member) && options.member.length) body.memberUserIds = options.member;
|
|
27620
28074
|
if (Array.isArray(options.agent) && options.agent.length) body.agentIds = options.agent;
|
|
27621
|
-
const res = await requestJson(ctx,
|
|
28075
|
+
const res = await requestJson(ctx, BASE36, { method: "POST", body });
|
|
27622
28076
|
return emit(ctx, res.body, () => writeLine(ctx.io.stdout, `Created channel ${channelSummary(res.body)}.`));
|
|
27623
28077
|
}
|
|
27624
28078
|
async function runGet5(ctx, argv) {
|
|
@@ -28588,7 +29042,7 @@ Run \`openwop agent-author --help\` for usage.`, 2);
|
|
|
28588
29042
|
}
|
|
28589
29043
|
|
|
28590
29044
|
// src/cli/workflowAuthor.ts
|
|
28591
|
-
var
|
|
29045
|
+
var BASE37 = "/v1/host/openwop-app/workflow-author";
|
|
28592
29046
|
var WORKFLOW_AUTHOR_HELP = `Usage:
|
|
28593
29047
|
openwop workflow-author catalog [--json]
|
|
28594
29048
|
openwop workflow-author draft --intent <text> [--provider p] [--model m] [--max-attempts n]
|
|
@@ -28624,7 +29078,7 @@ async function runWorkflowAuthor(ctx, argv) {
|
|
|
28624
29078
|
return 0;
|
|
28625
29079
|
}
|
|
28626
29080
|
if (sub === "catalog") {
|
|
28627
|
-
const res = await requestJson(ctx, `${
|
|
29081
|
+
const res = await requestJson(ctx, `${BASE37}/catalog`);
|
|
28628
29082
|
return emit(ctx, res.body, () => {
|
|
28629
29083
|
const nodes = Array.isArray(res.body?.nodes) ? res.body.nodes : [];
|
|
28630
29084
|
if (!nodes.length) writeLine(ctx.io.stdout, "No authorable nodes in this workspace.");
|
|
@@ -28640,7 +29094,7 @@ async function runWorkflowAuthor(ctx, argv) {
|
|
|
28640
29094
|
if (options.model) body.model = options.model;
|
|
28641
29095
|
const maxAttempts = intFlag(options.maxAttempts, "--max-attempts");
|
|
28642
29096
|
if (maxAttempts) body.maxAttempts = maxAttempts;
|
|
28643
|
-
const res = await requestJson(ctx, `${
|
|
29097
|
+
const res = await requestJson(ctx, `${BASE37}/draft`, { method: "POST", body, headers: idempotencyHeaders(options.idempotencyKey) });
|
|
28644
29098
|
const runId = res.body?.runId;
|
|
28645
29099
|
if (!options.follow) {
|
|
28646
29100
|
return emit(ctx, res.body, () => {
|
|
@@ -28667,7 +29121,7 @@ Run \`openwop workflow-author --help\` for usage.`, 2);
|
|
|
28667
29121
|
}
|
|
28668
29122
|
|
|
28669
29123
|
// src/cli/workflowProposals.ts
|
|
28670
|
-
var
|
|
29124
|
+
var BASE38 = "/v1/host/openwop-app/workflow-proposals/admin/policies";
|
|
28671
29125
|
var WORKFLOW_PROPOSALS_HELP = `Usage:
|
|
28672
29126
|
openwop workflow-proposals policies --tenant <tenantId> [--json]
|
|
28673
29127
|
openwop workflow-proposals enable --tenant <tenantId> --agent-profile <agentProfileId> [--json]
|
|
@@ -28698,7 +29152,7 @@ async function runWorkflowProposals(ctx, argv) {
|
|
|
28698
29152
|
}
|
|
28699
29153
|
if (!options.tenant) throw new CliError("workflow-proposals requires --tenant <tenantId>.", 2);
|
|
28700
29154
|
if (sub === "policies" || sub === "list") {
|
|
28701
|
-
const res = await requestJson(ctx, `${
|
|
29155
|
+
const res = await requestJson(ctx, `${BASE38}?tenantId=${enc(options.tenant)}`);
|
|
28702
29156
|
return emit(ctx, res.body, () => {
|
|
28703
29157
|
const items = Array.isArray(res.body?.items) ? res.body.items : [];
|
|
28704
29158
|
if (!items.length) {
|
|
@@ -28710,7 +29164,7 @@ async function runWorkflowProposals(ctx, argv) {
|
|
|
28710
29164
|
}
|
|
28711
29165
|
if (sub === "enable" || sub === "disable") {
|
|
28712
29166
|
if (!options.agentProfile) throw new CliError(`workflow-proposals ${sub} requires --agent-profile <agentProfileId>.`, 2);
|
|
28713
|
-
const path = `${
|
|
29167
|
+
const path = `${BASE38}/${enc(options.tenant)}/${enc(options.agentProfile)}`;
|
|
28714
29168
|
const res = await requestJson(ctx, path, { method: sub === "enable" ? "PUT" : "DELETE" });
|
|
28715
29169
|
return emit(ctx, res.body, () => writeLine(ctx.io.stdout, sub === "enable" ? `Auto-approval enabled for ${options.agentProfile} in ${options.tenant}.` : res.body?.removed ? `Auto-approval disabled for ${options.agentProfile} in ${options.tenant}.` : `No policy was set for ${options.agentProfile} in ${options.tenant}.`));
|
|
28716
29170
|
}
|
|
@@ -29656,6 +30110,10 @@ async function runCli(argv, options = {}) {
|
|
|
29656
30110
|
return await runConformance(ctx, commandArgs);
|
|
29657
30111
|
case "onboard":
|
|
29658
30112
|
return await runOnboard(ctx, commandArgs);
|
|
30113
|
+
case "login":
|
|
30114
|
+
return await runLogin(ctx, commandArgs);
|
|
30115
|
+
case "logout":
|
|
30116
|
+
return await runLogout(ctx, commandArgs);
|
|
29659
30117
|
case "providers":
|
|
29660
30118
|
case "provider":
|
|
29661
30119
|
return await runProviders(ctx, commandArgs);
|
|
@@ -29984,6 +30442,8 @@ var HELP_MAP = {
|
|
|
29984
30442
|
packs: PACKS_HELP,
|
|
29985
30443
|
pack: PACKS_HELP,
|
|
29986
30444
|
onboard: ONBOARD_HELP,
|
|
30445
|
+
login: LOGIN_HELP,
|
|
30446
|
+
logout: LOGIN_HELP,
|
|
29987
30447
|
providers: PROVIDERS_HELP,
|
|
29988
30448
|
provider: PROVIDERS_HELP,
|
|
29989
30449
|
agents: AGENTS_HELP,
|
|
@@ -30182,6 +30642,7 @@ Exit codes:
|
|
|
30182
30642
|
|
|
30183
30643
|
Commands:
|
|
30184
30644
|
onboard Guided first-run setup (host + provider + model + BYOK key)
|
|
30645
|
+
login | logout Sign this terminal in to an openwop-app host by a code (no key to copy)
|
|
30185
30646
|
providers list List stored BYOK credential refs (never values)
|
|
30186
30647
|
providers add Store a credential ref against the configured host
|
|
30187
30648
|
providers remove Remove a credential ref
|
|
@@ -30434,4 +30895,4 @@ Examples:
|
|
|
30434
30895
|
|
|
30435
30896
|
// src/openwop.ts
|
|
30436
30897
|
var code = await runCli(process.argv.slice(2));
|
|
30437
|
-
process.exit(code);
|
|
30898
|
+
process.stdout.write("", () => process.exit(code));
|