@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.
Files changed (4) hide show
  1. package/README.md +15 -8
  2. package/dist/cli.js +800 -339
  3. package/dist/openwop.js +801 -340
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -578,7 +578,7 @@ function errText(err) {
578
578
  // package.json
579
579
  var package_default = {
580
580
  name: "@openwop/cli",
581
- version: "1.2.3",
581
+ version: "1.4.0",
582
582
  description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
583
583
  license: "Apache-2.0",
584
584
  type: "module",
@@ -1078,7 +1078,7 @@ import { createInterface as createInterface2 } from "node:readline";
1078
1078
 
1079
1079
  // src/ids.ts
1080
1080
  function projectTenantBoundId(id) {
1081
- if (id.includes("~")) return id;
1081
+ if (id.includes("~") && !id.includes("/")) return id;
1082
1082
  let out = "";
1083
1083
  for (const byte of new TextEncoder().encode(id)) {
1084
1084
  const ch = String.fromCharCode(byte);
@@ -1153,6 +1153,17 @@ var V2_PATH_TEMPLATES = [
1153
1153
  "/workflows/{workflowId}"
1154
1154
  ];
1155
1155
  var V2_MANIFEST_OMITTED = ["/.well-known/openwop", "/openapi.json"];
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) roots = hostRootsFrom(doc?.extensions);
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
- const res = await requestJson(ctx, `/v1/host/openwop-app/runs/${encodeURIComponent(rest[0])}/interrupts`);
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 the real CRUD surface
11855
- GET/PUT/DELETE /v1/host/workspace/files. (The /v1/host/openwop-app/workspace/op
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
- search Reads /v1/index.json and filters the catalog client-side.
14986
- info Reads /v1/packs/{name}/index.json (+ the version manifest with --version).
14987
- install Downloads /v1/packs/{name}/-/{version}.tgz, checks sha256 integrity
14988
- against the manifest, and verifies the detached Ed25519 .sig against
14989
- the publisher key at /keys/{keyId}.pub (skip with --no-verify). Places
14990
- the tarball + manifest under ~/.openwop/packs/{name}/{version}/
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 (mirrors scripts/build-pack-tarball.mjs --signed), ready to
14995
- commit + open a PR. Private key: --key, else ~/.openwop-keys/{keyId}.private.pem.
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 (--undo reverses). The published change lands via PR + a
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 registryUrl = registryUrlFor(options, ctx.env);
15054
- const index = await registryJson(ctx, registryUrl, "/v1/index.json");
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 registryUrl = registryUrlFor(options, ctx.env);
15095
- const pack = await registryJson(ctx, registryUrl, `/v1/packs/${encodeURIComponent(name)}/index.json`);
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.yanked ? "yanked" : v.deprecated ? "deprecated" : "",
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 version = options.version;
15439
+ let requested = options.version;
15144
15440
  if (name && name.includes("@")) {
15145
15441
  const at = name.lastIndexOf("@");
15146
- version = version ?? name.slice(at + 1);
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 registryUrl = registryUrlFor(options, ctx.env);
15151
- if (!version) {
15152
- const pack = await registryJson(ctx, registryUrl, `/v1/packs/${encodeURIComponent(name)}/index.json`);
15153
- version = pack?.latest;
15154
- if (!version) throw new CliError(`Could not resolve a version for ${name}; pass --version.`);
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
- throw new CliError(`${name}@${version} has been yanked and cannot be installed.`, 1);
15457
+ warnings.push(`${name}@${version} is yanked${manifest.yankedReason ? ` (${manifest.yankedReason})` : ""}; installing it because it was pinned exactly.`);
15163
15458
  }
15164
- const tgz = await registryBytes(ctx, registryUrl, `/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.tgz`);
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
- const keyId = manifest?.signing?.keyId ?? manifest?.signing?.publicKeyRef;
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/v1/packs/${name}/-/`);
15295
- writeLine(ctx.io.stdout, " 2. Run `node registry/scripts/build-index.mjs` to refresh the index.");
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 manifestPath = join6(root, "registry", "v1", "packs", name, "-", `${version}.json`);
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, " Next: run `node registry/scripts/build-index.mjs`, commit, and open a PR.");
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 BASE10 = "/v1/host/openwop-app/profiles";
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, BASE10);
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 ? `${BASE10}/${encodeURIComponent(positionals[0])}` : `${BASE10}/me`;
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, `${BASE10}/me/activity${q ? `?${q}` : ""}`);
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, `${BASE10}/me`)).body?.contact ?? {};
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, `${BASE10}/me`, { method: "PATCH", body });
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, `${BASE10}/me/skills`, { method: "PUT", body: { skills } });
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, `${BASE10}/me/portfolio`, { method: "POST", body: { token: options.token } });
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, `${BASE10}/me/portfolio/${encodeURIComponent(positionals[0])}`, { method: "DELETE" }, `Portfolio asset not found: ${positionals[0]}`);
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 = `${BASE10}/me/pinned-agents/${encodeURIComponent(positionals[0])}`;
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 = `${BASE10}/${encodeURIComponent(userId)}/skills/${encodeURIComponent(skill)}/endorse`;
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 BASE11 = "/v1/host/openwop-app/auth";
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, `${BASE11}/saml/sso/metadata`)).ok : false;
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, `${BASE11}/saml/sso/metadata`, { auth: false }, "SAML SSO is not configured on this host (set OPENWOP_SAML_*).");
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(`${BASE11}/saml/sso/login?returnTo=${encodeURIComponent(returnTo)}`.replace(/^\//, ""), base16);
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, `${BASE11}/saml/validate`, { method: "POST", body });
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, `${BASE11}/scim/provision`, { method: "POST", body }, "SCIM provisioning seam not configured on this host (set OPENWOP_TEST_SCIM_URL).");
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, `${BASE11}/break-glass`, { method: "POST", body: { token } });
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 wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
19727
- if (!wk.ok) return;
19728
- const body = wk.body && typeof wk.body === "object" ? wk.body : {};
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 wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
20059
- if (!wk.ok) return;
20060
- const body = wk.body && typeof wk.body === "object" ? wk.body : {};
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 wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
20330
- if (!wk.ok) return;
20331
- const body = wk.body && typeof wk.body === "object" ? wk.body : {};
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
- const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
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
- const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
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 BASE12 = "/v1/host/openwop-app/knowledge-sync";
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(BASE12, { orgId: String(options.org) }));
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, `${BASE12}/${enc2(id)}`)).body);
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, BASE12, { method: "POST", body });
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(`${BASE12}/browse`, {
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, `${BASE12}/${enc2(id)}`, { method: "PATCH", body: { includeMedia: !options.noMedia } });
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, `${BASE12}/${enc2(id)}/${sub}`, { method: "POST", body: {} });
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, `${BASE12}/${enc2(id)}/sync`, { method: "POST", body: {} });
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, `${BASE12}/${enc2(id)}`, { method: "DELETE" });
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 BASE13 = "/v1/host/openwop-app/entities";
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(`${BASE13}/locale-context`));
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) => `${BASE13}/types/${enc2(n)}`;
21767
+ const typePath = (n) => `${BASE14}/types/${enc2(n)}`;
21332
21768
  switch (sub) {
21333
21769
  case "list": {
21334
- const res = await requestJson(ctx, q(`${BASE13}/types`));
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, `${BASE13}/types`, { method: "POST", body });
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 = `${BASE13}/types/${enc2(type)}/entities`;
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(`${BASE13}/taxonomies`));
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, `${BASE13}/taxonomies`, { method: "POST", body });
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(`${BASE13}/taxonomies/${enc2(name)}`), { method: "DELETE" });
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 = `${BASE13}/taxonomies/${enc2(tax)}/terms`;
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(`${BASE13}/relationships`));
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, `${BASE13}/relationships`, { method: "POST", body });
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(`${BASE13}/relationships/${enc2(from)}/${enc2(to)}`), { method: "DELETE" });
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 BASE14 = "/v1/host/openwop-app/tutorials";
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 ${BASE14}) and one tutorial in full (GET ${BASE14}/<slug>).
22174
- \`progress\` reads YOUR saved steps (GET ${BASE14}/progress); \`progress set\` replaces a
22175
- tutorial's completed steps and \`progress clear\` resets them (POST ${BASE14}/progress,
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, BASE14);
22635
+ const res = await requestJson(ctx, BASE15);
22200
22636
  const items = Array.isArray(res.body?.tutorials) ? res.body.tutorials : [];
22201
22637
  const code = 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, `${BASE14}/${enc2(slug)}`)).body);
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, `${BASE14}/progress`);
22653
+ const res2 = await requestJson(ctx, `${BASE15}/progress`);
22218
22654
  const items = Array.isArray(res2.body?.progress) ? res2.body.progress : [];
22219
22655
  const code = 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, `${BASE14}/progress`, { method: "POST", body });
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 BASE15 = "/v1/host/openwop-app/walkthroughs";
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 ${BASE15}/progress); \`progress set\`
22257
- records one (POST ${BASE15}/progress, body { walkthroughId, status, runId }).
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 ${BASE15}/funnel?walkthroughId=\u2026) \u2014 a recent-window view over the newest runs.
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, `${BASE15}/progress`, { method: "POST", body: { walkthroughId, status: String(options.status), runId: String(options.run) } });
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, `${BASE15}/progress`);
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(`${BASE15}/funnel`, { walkthroughId }));
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 BASE16 = "/v1/host/openwop-app/widgets";
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 ${BASE16}, GET ${BASE16}/summary,
22330
- POST ${BASE16}/<id>/archive, POST ${BASE16}/seed). Mounted only when the host sets
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, BASE16);
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, `${BASE16}/summary`);
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, BASE16, { method: "POST", body: { name: String(options.name) } });
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, `${BASE16}/${enc2(id)}/archive`, { method: "POST", body: {} });
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, `${BASE16}/seed`, { method: "POST", body: {} });
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 BASE17 = "/v1/host/openwop-app/ui-state";
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 ${BASE17}?resourceType=&resourceId=); \`get\` filters one
22416
- key from that list. \`set\` writes one key (PUT ${BASE17}, body { resourceType,
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 ${BASE17}/<type>/<id>/<key>).
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(BASE17, { resourceType: options.resourceType, resourceId: options.resourceId }));
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(BASE17, { resourceType, resourceId }));
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, BASE17, { method: "PUT", body: { resourceType, resourceId, key, value } });
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, `${BASE17}/${enc2(resourceType)}/${enc2(resourceId)}/${enc2(key)}`, { method: "DELETE" });
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 BASE18 = "/v1/host/openwop-app/ui-plugin";
22491
- var RPC_PRODUCT = `${BASE18}/rpc`;
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 ${BASE18}/packs). \`entry\` fetches a plugin's sandboxed entry bundle
22503
- (GET ${BASE18}/packs/<name>/plugins/<id>/entry); \`trusted-entry\` fetches a signed
22504
- pack's main-frame ES module (GET ${BASE18}/trusted/<name>/plugins/<id>/entry.mjs \u2014
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 ${BASE18}/demo-artifact).
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, `${BASE18}/packs`);
22983
+ const res = await requestJson(ctx, `${BASE19}/packs`);
22545
22984
  const items = Array.isArray(res.body?.plugins) ? res.body.plugins : [];
22546
22985
  const code = 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" ? `${BASE18}/packs/${enc2(name)}/plugins/${enc2(pluginId)}/entry` : `${BASE18}/trusted/${enc2(name)}/plugins/${enc2(pluginId)}/entry.mjs`;
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, `${BASE18}/demo-artifact`, { method: "POST", body: {} });
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 BASE19 = "/v1/host/openwop-app/canvas-collab";
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 ${BASE19}/<canvasId>/ticket) \u2014 a credential for the collaboration socket's
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 ${BASE19}/<canvasId>/claim-seed) \u2192 { seed: true } for the one
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 ${BASE19}/_debug; super-admin only; lifecycle metadata, never content).
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, `${BASE19}/${enc2(id)}/ticket`, { method: "POST", body: {} });
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, `${BASE19}/${enc2(id)}/claim-seed`, { method: "POST", body: {} });
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, `${BASE19}/_debug`);
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 BASE20 = "/v1/host/openwop-app/workflow-collab";
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 ${BASE20}/<workflowId>/ticket) \u2014 shown once, never stored. \`claim-seed\` runs
22677
- the seeder election (POST ${BASE20}/<workflowId>/claim-seed) \u2192 { seed }.
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, `${BASE20}/${enc2(id)}/ticket`, { method: "POST", body: {} });
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, `${BASE20}/${enc2(id)}/claim-seed`, { method: "POST", body: {} });
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 BASE21 = "/v1/host/openwop-app/present";
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 ${BASE21}/<token>/outline).
22763
- \`command\` sends a navigation command (POST ${BASE21}/<token>/command, body
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 ${BASE21}/<token>/state, body { current }). \`events\` follows the navigation
22766
- feed (GET ${BASE21}/<token>/events, server-sent events) \u2014 one line per event, or one
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 = `${BASE21}/${enc2(token)}`;
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 BASE22 = `${APP}/campaign-connectors`;
23802
- var orgBase2 = (org2) => `${BASE22}/orgs/${enc2(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, `${BASE22}/platforms`)).body;
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, `${BASE22}/sync`, { method: "POST", body: req2 })).body;
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, `${BASE22}/audience-sync`, { method: "POST", body: req2 })).body;
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, `${BASE22}/import`, { method: "POST", body: req2 })).body;
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, `${BASE22}/records${qs({ orgId: orgOf(a), campaignId: a.options.campaign })}`)).body,
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, `${BASE22}/kpi${qs({ orgId: orgOf(a), campaignId: a.options.campaign })}`)).body)
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 BASE23 = `${APP}/campaign-intel`;
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, `${BASE23}/${name}${qs({ orgId: orgOf2(a), campaignId: withCampaign ? a.options.campaign : void 0 })}`)).body;
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, `${BASE23}/plan-budget`, { method: "POST", body: req2 })).body);
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, `${BASE23}/recommendations/apply`, { method: "POST", body: req2 })).body;
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 BASE24 = `${APP}/cdp`;
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, `${BASE24}/${path}${qs({ limit: a.options.limit })}`)).body, key, columns, empty)
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, `${BASE24}/identity/resolve${qs({ type: a.options.type, value: a.options.value })}`)).body)
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, `${BASE24}/collect`, { method: "POST", body: req2 })).body;
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, `${BASE24}/collect/batch`, { method: "POST", body: req2 })).body);
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, `${BASE24}/collect/import`, { method: "POST", body: req2 })).body);
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, `${BASE24}/audit-chain/verify`)).body;
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, `${BASE24}/event-schemas`)).body,
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, `${BASE24}/event-schemas`, { method: "POST", body: req2 })).body;
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, `${BASE24}/event-schemas/${enc2(a.positionals[0])}/validate`, { method: "POST", body: req2 })).body;
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 BASE25 = `${APP}/destination-sync/syncs`;
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) => `${BASE25}/${enc2(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, BASE25)).body,
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, BASE25, { method: "POST", body: req2 })).body;
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 BASE26 = "/v1/host/openwop-app/vault";
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 ${BASE26} inventory of refs (never values)
25308
- set POST ${BASE26}/secrets store a new secret under a ref
25309
- rotate POST ${BASE26}/secrets/:credentialRef/rotate overwrite an EXISTING secret
25310
- delete DELETE ${BASE26}/secrets/:credentialRef remove (refused while the ref has live consumers unless --force)
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, BASE26, void 0, SURFACE2, "superadmin");
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" ? `${BASE26}/secrets` : `${BASE26}/secrets/${encodeURIComponent(ref)}/rotate`;
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, `${BASE26}/secrets/${encodeURIComponent(positionals[0])}${q}`, { method: "DELETE" }, SURFACE2, "superadmin");
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 BASE27 = "/v1/host/openwop-app/developer-keys";
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 ${BASE27}). Requires an
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 ${BASE27} keys (public fields only \u2014 never the token or its hash)
25443
- create POST ${BASE27} issue a key; the token is shown ONCE
25444
- revoke DELETE ${BASE27}/:keyId revoke (404 when the key is not yours / not in this workspace)
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:write
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, BASE27, void 0, SURFACE3, "signed-in");
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, BASE27, { method: "POST", body }, SURFACE3, "signed-in");
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, `${BASE27}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" }, SURFACE3, "signed-in");
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 hostname = positionals[0];
25608
- if (!hostname) throw new CliError(`Usage: openwop custom-domains ${sub} <hostname> --org <orgId>`, 2);
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 ?? hostname} (status ${d.status ?? "pending"}).`);
25617
- writeLine(ctx.io.stdout, `Publish TXT _openwop-verify.${d.hostname ?? hostname} = ${d.verificationToken ?? "(see --json)"} then run \`openwop custom-domains verify ${d.hostname ?? hostname} --org ${orgId}\`.`);
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(hostname)}/verify`, { method: "POST", body: {} });
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 ?? hostname}: ${d.status ?? "?"}${d.lastError ? ` \u2014 ${d.lastError}` : ""}`);
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 ${hostname} without --yes.`);
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(hostname)}`, { method: "DELETE" });
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 ${hostname}.`);
26091
+ writeLine(ctx.io.stdout, `Removed ${hostname2}.`);
25640
26092
  return 0;
25641
26093
  }
25642
26094
 
25643
26095
  // src/cli/environments.ts
25644
- var BASE28 = "/v1/host/openwop-app/environments";
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 ${BASE28}; toggle 'environments').
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 ${BASE28}[?drift=1] environments, config domains, app version
25665
- create POST ${BASE28} add an environment to the chain
25666
- ensure-chain POST ${BASE28}/ensure-chain create the default dev \u2192 staging \u2192 prod chain
25667
- protect PATCH ${BASE28}/:name/protection set open | protected | locked
25668
- settings GET/PATCH ${BASE28}/settings the promotion-approval gate
25669
- snapshots GET ${BASE28}/snapshots stored config snapshots
25670
- snapshot POST ${BASE28}/snapshots snapshot the current live config
25671
- preview POST ${BASE28}/preview diff a snapshot against an environment (no change)
25672
- promote POST ${BASE28}/promote promote an env's snapshot to the next (or --to) env
25673
- rollback POST ${BASE28}/rollback point an env back at a historical snapshot
25674
- apply POST ${BASE28}/apply apply a snapshot to the live config
25675
- promotions GET ${BASE28}/promotions the promotion ledger
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, `${BASE28}${options.drift ? "?drift=1" : ""}`);
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, BASE28, { method: "POST", body });
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, `${BASE28}/ensure-chain`, { method: "POST", body: {} });
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, `${BASE28}/${encodeURIComponent(name)}/protection`, { method: "PATCH", body: { protection: protectionOf(options.protection) } });
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, `${BASE28}/settings`, { method: "PATCH", body: { requireApprovalForPromotion: parseBool("--require-approval", options.requireApproval) } });
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, `${BASE28}/settings`);
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, `${BASE28}/snapshots`);
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, `${BASE28}/snapshots`, { method: "POST", body });
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, `${BASE28}/preview`, { method: "POST", body: { toEnv: options.to, snapshotHash: options.snapshot } });
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, `${BASE28}/promote`, { method: "POST", body }), "Promoted");
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, `${BASE28}/rollback`, { method: "POST", body: { env: options.env, snapshotHash: options.snapshot } }), "Rolled back");
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, `${BASE28}/apply`, { method: "POST", body: { snapshotHash: options.snapshot } }), "Applied");
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, `${BASE28}/promotions`);
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 BASE29 = "/v1/host/openwop-app/billing";
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 ${BASE29}; toggle 'billing').
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, `${BASE29}/subscription`);
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, `${BASE29}/balance`);
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, `${BASE29}/entitlements`);
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, `${BASE29}/bundles`);
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, `${BASE29}/invoices/${encodeURIComponent(positionals[1])}`);
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(`${BASE29}/invoices`, { amount, ...options.currency ? { currency: options.currency } : {} }, "Generating an invoice");
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, `${BASE29}/invoices`);
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, `${BASE29}/checkout`, { method: "POST", body: { priceId: options.price } });
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, `${BASE29}/bundles/${encodeURIComponent(positionals[0])}/checkout`, { method: "POST", body: {} });
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, `${BASE29}/portal`, { method: "POST", body: {} });
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(`${BASE29}/coupons`, body, "Creating a billing coupon");
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(`${BASE29}/sync-seats`, {}, "Billing seat sync");
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(`${BASE29}/import`, body, "Billing import");
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 BASE30 = "/v1/host/openwop-app/runtime-posture";
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 ${BASE30} live posture (serving revision, warm/cold, rollout)
26110
- request POST ${BASE30}/change-requests ask for a warm/cold change; returns the commands to run
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, BASE30, void 0, SURFACE5, "superadmin");
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, `${BASE30}/change-requests`, { method: "POST", body: { warm: Boolean(options.warm) } }, SURFACE5, "superadmin");
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 BASE31 = "/v1/host/openwop-app/menu-config";
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 ${BASE31} {tenant, user} layers for the caller
26216
- set me PUT ${BASE31}/me replace YOUR personal layer (signed-in user)
26217
- set tenant PUT ${BASE31}/tenant [super-admin] replace the workspace default layer
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, BASE31);
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, `${BASE31}/me`, { method: "PUT", body }, "Editing your menu layout", "signed-in");
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, BASE31);
26711
+ const current = await requestJson(ctx, BASE32);
26260
26712
  const etag = current.headers.get("etag");
26261
26713
  res = await gatedRequest(
26262
26714
  ctx,
26263
- `${BASE31}/tenant`,
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 BASE32 = "/v1/host/openwop-app/compat-endpoints";
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, `${BASE32}?orgId=${encodeURIComponent(org2)}`);
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, BASE32, { method: "POST", body });
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, `${BASE32}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
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 BASE33 = "/v1/host/openwop-app/host-events/bindings";
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, BASE33);
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, BASE33, { method: "POST", body: { eventType: options.event, workflowId: options.workflow } });
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, `${BASE33}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body: { enabled: sub === "enable" } });
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, `${BASE33}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
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 (GET /v1/openapi.json \u2014 the document the
27014
- host serves, which lists the operations it implements).
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 BASE34 = "/v1/host/openwop-app/assistant";
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, `${BASE34}/briefing`, (b) => writeLine(ctx.io.stdout, JSON.stringify(b?.brief ?? b, null, 2)));
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, `${BASE34}/stakeholders`, (b) => table2(ctx, b?.stakeholders, (s) => ({
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, `${BASE34}/health`, (b) => writeLine(ctx.io.stdout, JSON.stringify(b?.health ?? b, null, 2)));
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, `${BASE34}/workspace-conversation`, { method: "POST", body: {} });
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 `${BASE34}/projects/${enc(id)}`;
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, `${BASE34}/projects`);
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, `${BASE34}/projects`, { method: "POST", body: fields() });
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 `${BASE34}/commitments/${enc(id)}`;
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, `${BASE34}/commitments${q.size ? `?${q}` : ""}`);
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, `${BASE34}/decisions${options.project ? `?projectId=${enc(options.project)}` : ""}`);
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, `${BASE34}/meetings`);
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, `${BASE34}/meetings/${enc(positionals[0])}`);
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 `${BASE34}/pending-actions/${enc(id)}`;
27859
+ return `${BASE35}/pending-actions/${enc(id)}`;
27406
27860
  };
27407
27861
  switch (sub) {
27408
27862
  case "list": {
27409
- const res = await requestJson(ctx, `${BASE34}/pending-actions${options.status ? `?status=${enc(options.status)}` : ""}`);
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, `${BASE34}/loops`);
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, `${BASE34}/loops/${enc(loopId)}/${sub}`, { method: "POST", body });
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 BASE35 = "/v1/host/openwop-app/channels";
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: `${BASE35}/${enc(positionals[0])}` };
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, BASE35);
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, BASE35, { method: "POST", body });
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 BASE36 = "/v1/host/openwop-app/workflow-author";
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, `${BASE36}/catalog`);
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, `${BASE36}/draft`, { method: "POST", body, headers: idempotencyHeaders(options.idempotencyKey) });
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 BASE37 = "/v1/host/openwop-app/workflow-proposals/admin/policies";
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, `${BASE37}?tenantId=${enc(options.tenant)}`);
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 = `${BASE37}/${enc(options.tenant)}/${enc(options.agentProfile)}`;
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