@openwop/cli 0.3.0 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +9 -4
  2. package/dist/cli.js +3595 -163
  3. package/dist/openwop.js +3594 -163
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -150,8 +150,8 @@ function signalAvailable(env) {
150
150
  if (probe.status === 0) return { channel: "signal", available: true, detail: (probe.stdout || "").trim() || "signal-cli present" };
151
151
  return { channel: "signal", available: false, detail: "signal-cli not found on PATH \u2014 install from https://github.com/AsamK/signal-cli (or set OPENWOP_SIGNAL_DAEMON_URL)" };
152
152
  }
153
- function joinUrl(base, path) {
154
- return new URL(path.replace(/^\//, ""), base.endsWith("/") ? base : `${base}/`);
153
+ function joinUrl(base8, path) {
154
+ return new URL(path.replace(/^\//, ""), base8.endsWith("/") ? base8 : `${base8}/`);
155
155
  }
156
156
  async function signalDaemonRpc(baseUrl, method, params) {
157
157
  const res = await fetch(joinUrl(baseUrl, "api/v1/rpc"), {
@@ -522,7 +522,7 @@ function errText(err) {
522
522
  }
523
523
 
524
524
  // src/constants.ts
525
- var VERSION = "0.3.0";
525
+ var VERSION = "0.18.2";
526
526
  var DEFAULT_BASE_URL = "http://localhost:8080";
527
527
  var DEFAULT_REGISTRY_URL = "https://packs.openwop.dev";
528
528
  var DEFAULT_API_KEY = "sample-token";
@@ -606,6 +606,7 @@ function extractGlobalOptions(argv, env = process.env) {
606
606
  const globals = {
607
607
  baseUrl: void 0,
608
608
  apiKey: void 0,
609
+ profile: void 0,
609
610
  json: false,
610
611
  quiet: false,
611
612
  verbose: false,
@@ -625,6 +626,10 @@ function extractGlobalOptions(argv, env = process.env) {
625
626
  globals.apiKey = value ?? takeValue(argv, ++i, "--api-key");
626
627
  continue;
627
628
  }
629
+ if (flag === "--profile") {
630
+ globals.profile = value ?? takeValue(argv, ++i, "--profile");
631
+ continue;
632
+ }
628
633
  if (arg === "--json") {
629
634
  globals.json = true;
630
635
  continue;
@@ -728,11 +733,11 @@ function saveConfig(path, config) {
728
733
  }
729
734
  }
730
735
  function mergeConfig(existing, next) {
731
- const base = existing ?? {};
736
+ const base8 = existing ?? {};
732
737
  return {
733
- ...base,
738
+ ...base8,
734
739
  ...next,
735
- host: { ...base.host ?? {}, ...next.host ?? {} }
740
+ host: { ...base8.host ?? {}, ...next.host ?? {} }
736
741
  };
737
742
  }
738
743
  function getByPath(obj, path) {
@@ -1235,7 +1240,7 @@ async function runNotifications(ctx, argv) {
1235
1240
  write(ctx.io.stdout, NOTIFICATIONS_HELP);
1236
1241
  return 0;
1237
1242
  }
1238
- const base = "/v1/host/sample/notifications";
1243
+ const base8 = "/v1/host/sample/notifications";
1239
1244
  const rest = argv.slice(1);
1240
1245
  switch (sub) {
1241
1246
  case "list": {
@@ -1244,7 +1249,7 @@ async function runNotifications(ctx, argv) {
1244
1249
  if (options.status) q.set("status", options.status);
1245
1250
  if (options.archived) q.set("includeArchived", "true");
1246
1251
  if (options.limit) q.set("limit", options.limit);
1247
- const res = await requestJson(ctx, `${base}${q.toString() ? `?${q}` : ""}`);
1252
+ const res = await requestJson(ctx, `${base8}${q.toString() ? `?${q}` : ""}`);
1248
1253
  if (ctx.json) {
1249
1254
  writeJson(ctx.io.stdout, res.body);
1250
1255
  return 0;
@@ -1268,7 +1273,7 @@ async function runNotifications(ctx, argv) {
1268
1273
  `);
1269
1274
  return 2;
1270
1275
  }
1271
- const res = await requestJson(ctx, `${base}/${encodeURIComponent(rest[0])}/${sub}`, { method: "POST", body: {} });
1276
+ const res = await requestJson(ctx, `${base8}/${encodeURIComponent(rest[0])}/${sub}`, { method: "POST", body: {} });
1272
1277
  if (ctx.json) {
1273
1278
  writeJson(ctx.io.stdout, res.body);
1274
1279
  return 0;
@@ -1277,7 +1282,7 @@ async function runNotifications(ctx, argv) {
1277
1282
  return 0;
1278
1283
  }
1279
1284
  case "mark-all-read": {
1280
- const res = await requestJson(ctx, `${base}:mark-all-read`, { method: "POST", body: {} });
1285
+ const res = await requestJson(ctx, `${base8}:mark-all-read`, { method: "POST", body: {} });
1281
1286
  if (ctx.json) {
1282
1287
  writeJson(ctx.io.stdout, res.body);
1283
1288
  return 0;
@@ -1291,7 +1296,7 @@ async function runNotifications(ctx, argv) {
1291
1296
  write(ctx.io.stdout, "Usage: openwop notifications delete <id> [--json]\n");
1292
1297
  return 2;
1293
1298
  }
1294
- const res = await requestJson(ctx, `${base}/${encodeURIComponent(rest[0])}`, { method: "DELETE" });
1299
+ const res = await requestJson(ctx, `${base8}/${encodeURIComponent(rest[0])}`, { method: "DELETE" });
1295
1300
  if (ctx.json) {
1296
1301
  writeJson(ctx.io.stdout, res.body);
1297
1302
  return 0;
@@ -1376,9 +1381,14 @@ var PROMPTS_HELP = `Usage:
1376
1381
  openwop prompts list [--kind k] [--tag t] [--limit n] [--json]
1377
1382
  openwop prompts get <templateId> [--json]
1378
1383
  openwop prompts render <ref> [--variables-json '{...}'] [--json]
1379
-
1380
- Browse + render the host's prompt library (RFC 0029, /v1/prompts). \`render\`
1381
- resolves a PromptRef (templateId[@version]) against the supplied variables.
1384
+ openwop prompts create --template-id <id> --version <v> --kind <k> --text <t> [--json]
1385
+ openwop prompts update <templateId> [--version v] [--kind k] [--text t] [--json]
1386
+ openwop prompts delete <templateId> [--yes]
1387
+
1388
+ Browse, render, and manage the host's prompt library (RFC 0029, /v1/prompts).
1389
+ \`render\` resolves a PromptRef (templateId[@version]) against the supplied
1390
+ variables. \`create\` posts a new PromptTemplate; \`update\` PUTs the fields you
1391
+ pass to an existing template; \`delete\` removes one.
1382
1392
  `;
1383
1393
  async function runPrompts(ctx, argv) {
1384
1394
  const sub = argv[0] ?? "list";
@@ -1437,6 +1447,44 @@ async function runPrompts(ctx, argv) {
1437
1447
  writeJson(ctx.io.stdout, res.body);
1438
1448
  return 0;
1439
1449
  }
1450
+ case "create": {
1451
+ const { options } = parseOptions(rest, { value: ["--template-id", "--version", "--kind", "--text"] });
1452
+ if (!options.templateId || !options.version || !options.kind || !options.text) {
1453
+ write(ctx.io.stderr, "Usage: openwop prompts create --template-id <id> --version <v> --kind <k> --text <t> [--json]\n");
1454
+ return 2;
1455
+ }
1456
+ const body = { templateId: String(options.templateId), version: String(options.version), kind: String(options.kind), text: String(options.text) };
1457
+ const res = await requestJson(ctx, "/v1/prompts", { method: "POST", body });
1458
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
1459
+ else writeLine(ctx.io.stdout, `Created prompt ${body.templateId}@${body.version}`);
1460
+ return 0;
1461
+ }
1462
+ case "update": {
1463
+ const { options, positionals } = parseOptions(rest, { value: ["--version", "--kind", "--text"] });
1464
+ if (positionals.length !== 1) {
1465
+ write(ctx.io.stdout, "Usage: openwop prompts update <templateId> [--version v] [--kind k] [--text t] [--json]\n");
1466
+ return 2;
1467
+ }
1468
+ const body = {};
1469
+ if (options.version) body.version = String(options.version);
1470
+ if (options.kind) body.kind = String(options.kind);
1471
+ if (options.text) body.text = String(options.text);
1472
+ const res = await requestJson(ctx, `/v1/prompts/${encodeURIComponent(positionals[0])}`, { method: "PUT", body });
1473
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
1474
+ else writeLine(ctx.io.stdout, `Updated prompt ${positionals[0]}`);
1475
+ return 0;
1476
+ }
1477
+ case "delete": {
1478
+ const { options, positionals } = parseOptions(rest, { bool: ["--yes"] });
1479
+ if (positionals.length !== 1) {
1480
+ write(ctx.io.stdout, "Usage: openwop prompts delete <templateId> [--yes]\n");
1481
+ return 2;
1482
+ }
1483
+ if (!options.yes) throw new CliError(`Refusing to delete prompt ${positionals[0]} without --yes.`, 2);
1484
+ await requestJson(ctx, `/v1/prompts/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
1485
+ writeLine(ctx.io.stdout, `Deleted prompt ${positionals[0]}`);
1486
+ return 0;
1487
+ }
1440
1488
  default:
1441
1489
  throw new CliError(`Unknown prompts command: ${sub}
1442
1490
  Run \`openwop prompts --help\` for usage.`);
@@ -2073,6 +2121,10 @@ async function downloadAsset(ctx, assetUrl, outPath) {
2073
2121
  }
2074
2122
 
2075
2123
  // src/cli/shared.ts
2124
+ function requireOrg(org) {
2125
+ if (!org) throw new CliError("This command is org-scoped \u2014 pass --org <orgId>.", 2);
2126
+ return String(org);
2127
+ }
2076
2128
  function buildInputs(options) {
2077
2129
  const fromJson = options.inputsJson ? JSON.parse(options.inputsJson) : {};
2078
2130
  if (fromJson === null || typeof fromJson !== "object" || Array.isArray(fromJson)) {
@@ -2194,10 +2246,15 @@ async function runConfig(ctx, argv) {
2194
2246
  var CATALOG_HELP = `Usage:
2195
2247
  openwop catalog nodes [--search text] [--limit n] [--json]
2196
2248
  openwop catalog packs [--json]
2249
+ openwop catalog tools [<toolId>] [--json]
2250
+
2251
+ \`tools\` reads the portable tool catalog (RFC 0078 \xA7B) \u2014 GET /v1/tools, or
2252
+ GET /v1/tools/<toolId> for one descriptor. This is the tools an agent/workflow
2253
+ may call, distinct from the node catalog + installed packs.
2197
2254
  `;
2198
2255
  async function runCatalog(ctx, argv) {
2199
2256
  const sub = argv[0] ?? "nodes";
2200
- const args = argv.slice(sub === "nodes" || sub === "packs" ? 1 : 0);
2257
+ const args = argv.slice(sub === "nodes" || sub === "packs" || sub === "tools" ? 1 : 0);
2201
2258
  if (sub === "--help" || sub === "-h") {
2202
2259
  write(ctx.io.stdout, CATALOG_HELP);
2203
2260
  return 0;
@@ -2207,10 +2264,35 @@ async function runCatalog(ctx, argv) {
2207
2264
  return runCatalogNodes(ctx, args);
2208
2265
  case "packs":
2209
2266
  return runCatalogPacks(ctx, args);
2267
+ case "tools":
2268
+ return runCatalogTools(ctx, args);
2210
2269
  default:
2211
2270
  throw new CliError(`Unknown catalog command: ${sub}`);
2212
2271
  }
2213
2272
  }
2273
+ async function runCatalogTools(ctx, argv) {
2274
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
2275
+ if (options.help) {
2276
+ write(ctx.io.stdout, CATALOG_HELP);
2277
+ return 0;
2278
+ }
2279
+ const path = positionals.length === 1 ? `/v1/tools/${encodeURIComponent(positionals[0])}` : "/v1/tools";
2280
+ const res = await requestJson(ctx, path);
2281
+ if (ctx.json || positionals.length === 1) {
2282
+ writeJson(ctx.io.stdout, res.body);
2283
+ return 0;
2284
+ }
2285
+ const tools = Array.isArray(res.body?.tools) ? res.body.tools : Array.isArray(res.body) ? res.body : [];
2286
+ if (tools.length === 0) {
2287
+ writeLine(ctx.io.stdout, "No tools advertised.");
2288
+ return 0;
2289
+ }
2290
+ writeLine(ctx.io.stdout, formatTable(
2291
+ tools.map((t) => ({ toolId: t.toolId ?? t.id ?? "", title: t.title ?? t.name ?? "", effect: t.dataEffect ?? t.effect ?? "" })),
2292
+ ["toolId", "title", "effect"]
2293
+ ));
2294
+ return 0;
2295
+ }
2214
2296
  async function runCatalogNodes(ctx, argv) {
2215
2297
  const { options } = parseOptions(argv, {
2216
2298
  bool: ["--help"],
@@ -2265,10 +2347,17 @@ var WORKFLOWS_HELP = `Usage:
2265
2347
  openwop workflows get <workflowId> [--json]
2266
2348
  openwop workflows register <workflow.json> [--json]
2267
2349
  openwop workflows delete <workflowId> [--json]
2350
+ openwop workflows chains [--json]
2351
+ openwop workflows from-chain <chainId> [--params-json '{...}'] [--json]
2352
+ openwop workflows chain-pack-install --name <pack> [--version <v>] [--json]
2353
+
2354
+ \`chains\` lists the host's workflow-chain templates (ADR 0163 / RFC 0013);
2355
+ \`from-chain\` expands one into a registered workflow you can run; \`chain-pack-install\`
2356
+ installs a signed workflow-chain pack.
2268
2357
  `;
2269
2358
  async function runWorkflows(ctx, argv) {
2270
2359
  const sub = argv[0] ?? "list";
2271
- const args = argv.slice(["list", "get", "register", "delete", "rm"].includes(sub) ? 1 : 0);
2360
+ const args = argv.slice(["list", "get", "register", "delete", "rm", "chains", "from-chain", "chain-pack-install"].includes(sub) ? 1 : 0);
2272
2361
  if (sub === "--help" || sub === "-h") {
2273
2362
  write(ctx.io.stdout, WORKFLOWS_HELP);
2274
2363
  return 0;
@@ -2283,10 +2372,71 @@ async function runWorkflows(ctx, argv) {
2283
2372
  case "delete":
2284
2373
  case "rm":
2285
2374
  return runWorkflowsDelete(ctx, args);
2375
+ case "chains":
2376
+ return runWorkflowsChains(ctx, args);
2377
+ case "from-chain":
2378
+ return runWorkflowsFromChain(ctx, args);
2379
+ case "chain-pack-install":
2380
+ return runWorkflowsChainPackInstall(ctx, args);
2286
2381
  default:
2287
2382
  throw new CliError(`Unknown workflows command: ${sub}`);
2288
2383
  }
2289
2384
  }
2385
+ async function runWorkflowsChains(ctx, argv) {
2386
+ const { options } = parseOptions(argv, { bool: ["--help"] });
2387
+ if (options.help) {
2388
+ write(ctx.io.stdout, WORKFLOWS_HELP);
2389
+ return 0;
2390
+ }
2391
+ const res = await requestJson(ctx, "/v1/host/sample/workflow-chains");
2392
+ if (ctx.json) {
2393
+ writeJson(ctx.io.stdout, res.body);
2394
+ return 0;
2395
+ }
2396
+ const chains = Array.isArray(res.body?.chains) ? res.body.chains : [];
2397
+ if (chains.length === 0) {
2398
+ writeLine(ctx.io.stdout, "No workflow chains.");
2399
+ return 0;
2400
+ }
2401
+ writeLine(ctx.io.stdout, formatTable(
2402
+ chains.map((c) => ({ chainId: c.chainId ?? "", name: c.name ?? "", steps: Array.isArray(c.steps) ? c.steps.length : c.stepCount ?? "" })),
2403
+ ["chainId", "name", "steps"]
2404
+ ));
2405
+ return 0;
2406
+ }
2407
+ async function runWorkflowsFromChain(ctx, argv) {
2408
+ const { options, positionals } = parseOptions(argv, { value: ["--chain", "--params-json"] });
2409
+ const chainId = options.chain ?? positionals[0];
2410
+ if (!chainId) {
2411
+ write(ctx.io.stderr, "Usage: openwop workflows from-chain <chainId> [--params-json '{...}'] [--json]\n");
2412
+ return 2;
2413
+ }
2414
+ let params = {};
2415
+ if (options.paramsJson) {
2416
+ try {
2417
+ params = JSON.parse(String(options.paramsJson));
2418
+ } catch {
2419
+ throw new CliError("--params-json must be valid JSON.", 2);
2420
+ }
2421
+ }
2422
+ const res = await requestJson(ctx, "/v1/host/sample/workflows/from-chain", { method: "POST", body: { chainId, params } });
2423
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
2424
+ else writeLine(ctx.io.stdout, `Registered workflow ${res.body?.workflowId ?? ""} from chain ${chainId}.`);
2425
+ return 0;
2426
+ }
2427
+ async function runWorkflowsChainPackInstall(ctx, argv) {
2428
+ const { options } = parseOptions(argv, { value: ["--name", "--version"] });
2429
+ if (!options.name) {
2430
+ write(ctx.io.stderr, "Usage: openwop workflows chain-pack-install --name <pack> [--version <v>] [--json]\n");
2431
+ return 2;
2432
+ }
2433
+ const body = { name: String(options.name) };
2434
+ if (options.version) body.version = String(options.version);
2435
+ const res = await requestJson(ctx, "/v1/host/sample/workflow-chain-packs/install", { method: "POST", body });
2436
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
2437
+ else writeLine(ctx.io.stdout, `Installed workflow-chain pack ${options.name}.`);
2438
+ return 0;
2439
+ }
2290
2440
  async function runWorkflowsList(ctx, argv) {
2291
2441
  const { options } = parseOptions(argv, { bool: ["--help"] });
2292
2442
  if (options.help) {
@@ -2899,6 +3049,10 @@ var KANBAN_HELP = `Usage:
2899
3049
  openwop kanban card-move <cardId> --column <columnId> [--json]
2900
3050
  openwop kanban card-update <cardId> [--title <text>] [--description <text>] [--column <columnId>] [--workflow <id>] [--json]
2901
3051
  openwop kanban card-delete <cardId> [--yes]
3052
+ openwop kanban card-assign <cardId> --assignee <subject> [--role <r>] [--no-notify] [--json]
3053
+ openwop kanban card-claim <cardId> [--json]
3054
+ openwop kanban boards-personal [--json]
3055
+ openwop kanban assigned [--json]
2902
3056
  openwop kanban watch <boardId>
2903
3057
 
2904
3058
  Kanban boards (host-extension under /v1/host/sample/kanban). A board is a lane
@@ -2924,7 +3078,7 @@ async function runKanban(ctx, argv) {
2924
3078
  write(ctx.io.stdout, KANBAN_HELP);
2925
3079
  return 0;
2926
3080
  }
2927
- const known = ["boards", "board", "board-create", "board-delete", "card-add", "card-move", "card-update", "card-delete", "watch"];
3081
+ const known = ["boards", "board", "board-create", "board-delete", "card-add", "card-move", "card-update", "card-delete", "card-assign", "card-claim", "boards-personal", "assigned", "watch"];
2928
3082
  const args = argv.slice(known.includes(sub) ? 1 : 0);
2929
3083
  switch (sub) {
2930
3084
  case "boards":
@@ -2943,6 +3097,14 @@ async function runKanban(ctx, argv) {
2943
3097
  return await cardPatch(ctx, args, false);
2944
3098
  case "card-delete":
2945
3099
  return await cardDelete(ctx, args);
3100
+ case "card-assign":
3101
+ return await cardAssign(ctx, args);
3102
+ case "card-claim":
3103
+ return await cardClaim(ctx, args);
3104
+ case "boards-personal":
3105
+ return await boardsPersonal(ctx, args);
3106
+ case "assigned":
3107
+ return await assignedList(ctx, args);
2946
3108
  case "watch":
2947
3109
  return await boardWatch(ctx, args);
2948
3110
  default:
@@ -3078,52 +3240,2913 @@ async function cardPatch(ctx, argv, moveOnly) {
3078
3240
  if (options.description) body.description = options.description;
3079
3241
  if (options.workflow) body.workflowId = options.workflow;
3080
3242
  }
3081
- if (Object.keys(body).length === 0) throw new CliError("Nothing to update \u2014 pass at least one field.", 2);
3082
- const res = await requestJson(ctx, `/v1/host/sample/kanban/cards/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body });
3083
- if (ctx.json) {
3084
- writeJson(ctx.io.stdout, res.body);
3243
+ if (Object.keys(body).length === 0) throw new CliError("Nothing to update \u2014 pass at least one field.", 2);
3244
+ const res = await requestJson(ctx, `/v1/host/sample/kanban/cards/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body });
3245
+ if (ctx.json) {
3246
+ writeJson(ctx.io.stdout, res.body);
3247
+ return 0;
3248
+ }
3249
+ writeLine(ctx.io.stdout, `Updated card ${positionals[0]}.`);
3250
+ return 0;
3251
+ }
3252
+ async function cardDelete(ctx, argv) {
3253
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"] });
3254
+ if (options.help || positionals.length !== 1) {
3255
+ write(ctx.io.stdout, "Usage: openwop kanban card-delete <cardId> [--yes]\n");
3256
+ return options.help ? 0 : 2;
3257
+ }
3258
+ if (!options.yes) {
3259
+ writeLine(ctx.io.stderr, `Refusing to delete card ${positionals[0]} without --yes.`);
3260
+ return 2;
3261
+ }
3262
+ await requestJson(ctx, `/v1/host/sample/kanban/cards/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
3263
+ writeLine(ctx.io.stdout, `Deleted card ${positionals[0]}.`);
3264
+ return 0;
3265
+ }
3266
+ async function boardWatch(ctx, argv) {
3267
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
3268
+ if (options.help || positionals.length !== 1) {
3269
+ write(ctx.io.stdout, "Usage: openwop kanban watch <boardId>\n");
3270
+ return options.help ? 0 : 2;
3271
+ }
3272
+ const url = new URL(`v1/host/sample/kanban/boards/${encodeURIComponent(positionals[0])}/events`, ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
3273
+ const headers = { accept: "text/event-stream" };
3274
+ if (ctx.apiKey) headers.authorization = `Bearer ${ctx.apiKey}`;
3275
+ const res = await ctx.fetchImpl(url, { headers });
3276
+ if (!res.ok || !res.body) {
3277
+ throw new CliError(`Could not open the board event stream (HTTP ${res.status}).`, 1);
3278
+ }
3279
+ writeLine(ctx.io.stdout, `Watching board ${positionals[0]} (Ctrl-C to stop)\u2026`);
3280
+ await consumeSse(res.body, (frame) => {
3281
+ if (ctx.json) {
3282
+ writeLine(ctx.io.stdout, JSON.stringify(frame));
3283
+ return;
3284
+ }
3285
+ const type = frame.event ?? "message";
3286
+ writeLine(ctx.io.stdout, `\xB7 ${type}${frame.data ? `: ${frame.data}` : ""}`);
3287
+ });
3288
+ return 0;
3289
+ }
3290
+ async function cardAssign(ctx, argv) {
3291
+ const { options, positionals } = parseOptions(argv, { value: ["--assignee", "--role"], bool: ["--no-notify", "--help"] });
3292
+ if (options.help || positionals.length !== 1 || !options.assignee) {
3293
+ write(ctx.io.stdout, "Usage: openwop kanban card-assign <cardId> --assignee <subject> [--role r] [--no-notify] [--json]\n");
3294
+ return options.help ? 0 : 2;
3295
+ }
3296
+ const body = { assigneeId: options.assignee };
3297
+ if (options.role) body.assigneeRole = options.role;
3298
+ if (options.noNotify) body.notifyAssignee = false;
3299
+ const res = await requestJson(ctx, `/v1/host/sample/kanban/cards/${encodeURIComponent(positionals[0])}/assign`, { method: "POST", body });
3300
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3301
+ else writeLine(ctx.io.stdout, `Assigned card ${positionals[0]} to ${options.assignee}.`);
3302
+ return 0;
3303
+ }
3304
+ async function cardClaim(ctx, argv) {
3305
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
3306
+ if (options.help || positionals.length !== 1) {
3307
+ write(ctx.io.stdout, "Usage: openwop kanban card-claim <cardId> [--json]\n");
3308
+ return options.help ? 0 : 2;
3309
+ }
3310
+ const res = await requestJson(ctx, `/v1/host/sample/kanban/cards/${encodeURIComponent(positionals[0])}/claim`, { method: "POST", body: {} });
3311
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3312
+ else writeLine(ctx.io.stdout, `Claimed card ${positionals[0]}.`);
3313
+ return 0;
3314
+ }
3315
+ async function boardsPersonal(ctx, argv) {
3316
+ const { options } = parseOptions(argv, { bool: ["--help"] });
3317
+ if (options.help) {
3318
+ write(ctx.io.stdout, KANBAN_HELP);
3319
+ return 0;
3320
+ }
3321
+ const res = await requestJson(ctx, "/v1/host/sample/kanban/boards/personal");
3322
+ if (ctx.json) {
3323
+ writeJson(ctx.io.stdout, res.body);
3324
+ return 0;
3325
+ }
3326
+ const boards = Array.isArray(res.body?.boards) ? res.body.boards : [];
3327
+ if (boards.length === 0) {
3328
+ writeLine(ctx.io.stdout, "No personal boards.");
3329
+ return 0;
3330
+ }
3331
+ writeLine(ctx.io.stdout, formatTable(
3332
+ boards.map((b) => ({ boardId: b.boardId ?? b.id ?? "", name: b.name ?? "", cards: b.cardCount ?? "" })),
3333
+ ["boardId", "name", "cards"]
3334
+ ));
3335
+ return 0;
3336
+ }
3337
+ async function assignedList(ctx, argv) {
3338
+ const { options } = parseOptions(argv, { bool: ["--help"] });
3339
+ if (options.help) {
3340
+ write(ctx.io.stdout, KANBAN_HELP);
3341
+ return 0;
3342
+ }
3343
+ const res = await requestJson(ctx, "/v1/host/sample/kanban/assigned");
3344
+ if (ctx.json) {
3345
+ writeJson(ctx.io.stdout, res.body);
3346
+ return 0;
3347
+ }
3348
+ const cards = Array.isArray(res.body?.cards) ? res.body.cards : [];
3349
+ if (cards.length === 0) {
3350
+ writeLine(ctx.io.stdout, "No cards assigned to you.");
3351
+ return 0;
3352
+ }
3353
+ writeLine(ctx.io.stdout, formatTable(
3354
+ cards.map((c) => ({ cardId: c.id ?? c.cardId ?? "", title: c.title ?? "", board: c.boardId ?? "", column: c.columnId ?? "" })),
3355
+ ["cardId", "title", "board", "column"]
3356
+ ));
3357
+ return 0;
3358
+ }
3359
+
3360
+ // src/cli/brand.ts
3361
+ var PUBLIC_PATH = "/v1/host/openwop-app/public-brand";
3362
+ var APP_PATH = "/v1/host/openwop-app/app-brand";
3363
+ var CONTRAST = ["standard", "medium", "high"];
3364
+ var RADIUS = ["sm", "md", "lg"];
3365
+ var MODE = ["system", "light", "dark"];
3366
+ var BRAND_HELP = `Usage:
3367
+ openwop brand public [--json]
3368
+ openwop brand get [--json]
3369
+ openwop brand set [--name <n>] [--product-name <n>] [--accent <color>] [--neutral <color>]
3370
+ [--contrast standard|medium|high] [--radius sm|md|lg] [--default-mode system|light|dark]
3371
+ [--logo <url>] [--favicon <url>] [--identity-json <json>] [--json]
3372
+
3373
+ White-label app brand for the OpenWOP demo host (ADR 0170 / 0171). ONE brand owns the
3374
+ app's own runtime identity \u2014 logo, colors, fonts, name, and a generative theme \u2014 applied
3375
+ to the running app with no rebuild. Drives the host-extension surface
3376
+ GET/PUT /v1/host/openwop-app/{public-brand,app-brand}:
3377
+ \u2022 public \u2014 the applied identity every visitor sees (anonymous; no auth needed).
3378
+ \u2022 get / set \u2014 read + edit the reserved app brand; requires SUPER-ADMIN
3379
+ (OPENWOP_SUPERADMIN_TENANTS; authenticate as that tenant).
3380
+
3381
+ 'set' is read-modify-write: it fetches the current brand, applies your flags, and PUTs
3382
+ the merged result \u2014 so setting --accent won't wipe the logo. The accent/neutral/contrast/
3383
+ radius flags feed the ADR 0171 theme generator, which derives a full, accessible light +
3384
+ dark theme from the seed (the accent is kept exact; text shades are solved for WCAG-AA).
3385
+ --identity-json REPLACES the whole identity facet (advanced \u2014 mirrors the editor's JSON tier).
3386
+
3387
+ --name <n> The brand's internal name.
3388
+ --product-name <n> The app's product name (shown in the wordmark / document title).
3389
+ --accent <color> Brand accent seed \u2014 any CSS color (oklch / #hex / rgb). Generates the ramp.
3390
+ --neutral <color> Optional surface/background tint seed.
3391
+ --contrast <lvl> Contrast target: standard (AA) | medium | high.
3392
+ --radius <size> Corner radius: sm | md | lg.
3393
+ --default-mode <m> Default theme mode: system | light | dark.
3394
+ --logo <url> Logo / mark URL (https, root-relative, or data:image).
3395
+ --favicon <url> Favicon URL.
3396
+ --identity-json <j> Full identity facet as JSON (REPLACES every identity field).
3397
+ --json Machine-readable output.
3398
+
3399
+ Examples:
3400
+ openwop brand public --json
3401
+ openwop brand set --accent 'oklch(58% 0.13 250)' --product-name 'Acme Ops'
3402
+ openwop brand set --identity-json '{"productName":"Acme","theme":{"accentSeed":"#0a84ff"}}'
3403
+ `;
3404
+ async function runBrand(ctx, argv) {
3405
+ const sub = argv[0] ?? "public";
3406
+ if (sub === "--help" || sub === "-h") {
3407
+ write(ctx.io.stdout, BRAND_HELP);
3408
+ return 0;
3409
+ }
3410
+ const args = argv.slice(["public", "get", "set"].includes(sub) ? 1 : 0);
3411
+ switch (sub) {
3412
+ case "public":
3413
+ return await brandPublic(ctx, args);
3414
+ case "get":
3415
+ return await brandGet(ctx, args);
3416
+ case "set":
3417
+ return await brandSet(ctx, args);
3418
+ default:
3419
+ throw new CliError(`Unknown brand command: ${sub}
3420
+ Run \`openwop brand --help\` for usage.`);
3421
+ }
3422
+ }
3423
+ async function appRequest(ctx, path, options, action) {
3424
+ try {
3425
+ return await requestJson(ctx, path, options);
3426
+ } catch (err) {
3427
+ if (err instanceof HttpError && (err.status === 401 || err.status === 403)) {
3428
+ throw new CliError(
3429
+ `${action} requires super-admin on this host. Set OPENWOP_SUPERADMIN_TENANTS on the server and authenticate the CLI as that tenant (openwop onboard / --api-key).`,
3430
+ 4
3431
+ );
3432
+ }
3433
+ throw err;
3434
+ }
3435
+ }
3436
+ function showIdentity(ctx, identity) {
3437
+ const id = identity ?? {};
3438
+ const theme = id.theme ?? {};
3439
+ const logo = id.logo ?? {};
3440
+ writeLine(ctx.io.stdout, `productName: ${id.productName ?? "(default)"}`);
3441
+ writeLine(ctx.io.stdout, `accent: ${theme.accentSeed ?? id.colors?.accent ?? "(stock)"}`);
3442
+ if (theme.neutralSeed) writeLine(ctx.io.stdout, `neutral: ${theme.neutralSeed}`);
3443
+ writeLine(ctx.io.stdout, `contrast: ${theme.contrastLevel ?? "standard"}`);
3444
+ if (theme.radius) writeLine(ctx.io.stdout, `radius: ${theme.radius}`);
3445
+ writeLine(ctx.io.stdout, `defaultMode: ${theme.defaultMode ?? "system"}`);
3446
+ writeLine(ctx.io.stdout, `logo: ${logo.markSrc ?? "(none)"}`);
3447
+ writeLine(ctx.io.stdout, `favicon: ${logo.faviconSrc ?? "(none)"}`);
3448
+ if (theme.override && (theme.override.light || theme.override.dark)) {
3449
+ writeLine(ctx.io.stdout, `override: (advanced token overrides set)`);
3450
+ }
3451
+ }
3452
+ async function brandPublic(ctx, argv) {
3453
+ const { options } = parseOptions(argv, { bool: ["--help"] });
3454
+ if (options.help) {
3455
+ write(ctx.io.stdout, "Usage: openwop brand public [--json]\n");
3456
+ return 0;
3457
+ }
3458
+ const res = await requestJson(ctx, PUBLIC_PATH, { auth: false });
3459
+ if (ctx.json) {
3460
+ writeJson(ctx.io.stdout, res.body);
3461
+ return 0;
3462
+ }
3463
+ showIdentity(ctx, res.body?.identity);
3464
+ return 0;
3465
+ }
3466
+ async function brandGet(ctx, argv) {
3467
+ const { options } = parseOptions(argv, { bool: ["--help"] });
3468
+ if (options.help) {
3469
+ write(ctx.io.stdout, "Usage: openwop brand get [--json]\n");
3470
+ return 0;
3471
+ }
3472
+ const res = await appRequest(ctx, APP_PATH, {}, "Reading the app brand");
3473
+ if (ctx.json) {
3474
+ writeJson(ctx.io.stdout, res.body);
3475
+ return 0;
3476
+ }
3477
+ const brand = res.body?.brand ?? {};
3478
+ writeLine(ctx.io.stdout, `name: ${brand.name ?? "(unnamed)"}`);
3479
+ showIdentity(ctx, brand.identity);
3480
+ return 0;
3481
+ }
3482
+ async function brandSet(ctx, argv) {
3483
+ const { options } = parseOptions(argv, {
3484
+ bool: ["--help"],
3485
+ value: [
3486
+ "--name",
3487
+ "--product-name",
3488
+ "--accent",
3489
+ "--neutral",
3490
+ "--contrast",
3491
+ "--radius",
3492
+ "--default-mode",
3493
+ "--logo",
3494
+ "--favicon",
3495
+ "--identity-json"
3496
+ ]
3497
+ });
3498
+ if (options.help) {
3499
+ write(ctx.io.stdout, BRAND_HELP);
3500
+ return 0;
3501
+ }
3502
+ const enumErr = (flag, val, allowed) => {
3503
+ if (val !== void 0 && !allowed.includes(String(val))) {
3504
+ throw new CliError(`--${flag} must be one of: ${allowed.join(" | ")} (got: ${String(val)})`, 2);
3505
+ }
3506
+ };
3507
+ enumErr("contrast", options.contrast, CONTRAST);
3508
+ enumErr("radius", options.radius, RADIUS);
3509
+ enumErr("default-mode", options.defaultMode, MODE);
3510
+ const cur = await appRequest(ctx, APP_PATH, {}, "Reading the app brand");
3511
+ const current = cur.body?.brand ?? {};
3512
+ let identity;
3513
+ if (options.identityJson) {
3514
+ try {
3515
+ identity = JSON.parse(String(options.identityJson));
3516
+ } catch (err) {
3517
+ throw new CliError(`--identity-json is not valid JSON: ${err.message}`, 2);
3518
+ }
3519
+ if (!identity || typeof identity !== "object") throw new CliError("--identity-json must be a JSON object", 2);
3520
+ } else {
3521
+ identity = { ...current.identity ?? {} };
3522
+ if (options.productName) identity.productName = String(options.productName);
3523
+ if (options.logo || options.favicon) {
3524
+ identity.logo = { ...identity.logo ?? {} };
3525
+ if (options.logo) identity.logo.markSrc = String(options.logo);
3526
+ if (options.favicon) identity.logo.faviconSrc = String(options.favicon);
3527
+ }
3528
+ const theme = { ...identity.theme ?? {} };
3529
+ if (options.accent) theme.accentSeed = String(options.accent);
3530
+ if (options.neutral) theme.neutralSeed = String(options.neutral);
3531
+ if (options.contrast) theme.contrastLevel = String(options.contrast);
3532
+ if (options.radius) theme.radius = String(options.radius);
3533
+ if (options.defaultMode) theme.defaultMode = String(options.defaultMode);
3534
+ if (Object.keys(theme).length) identity.theme = theme;
3535
+ }
3536
+ const body = { identity };
3537
+ if (options.name) body.name = String(options.name);
3538
+ const res = await appRequest(ctx, APP_PATH, { method: "PUT", body }, "Updating the app brand");
3539
+ if (ctx.json) {
3540
+ writeJson(ctx.io.stdout, res.body);
3541
+ return 0;
3542
+ }
3543
+ writeLine(ctx.io.stdout, "App brand updated. Applied live to the running app.");
3544
+ showIdentity(ctx, res.body?.brand?.identity);
3545
+ return 0;
3546
+ }
3547
+
3548
+ // src/cli/crm.ts
3549
+ var BASE = "/v1/host/sample/crm/contacts";
3550
+ var CRM_HELP = `Usage:
3551
+ openwop crm list [--stage <s>] [--json]
3552
+ openwop crm get <contactId> [--json]
3553
+ openwop crm create --name <name> [--email <e>] [--company <c>] [--stage <s>] [--json]
3554
+ openwop crm update <contactId> [--name n] [--email e] [--company c] [--stage s] [--json]
3555
+ openwop crm delete <contactId> [--yes]
3556
+ openwop crm triage <contactId> [--json]
3557
+
3558
+ CRM contacts (host-extension ${BASE}). A contact has a name, optional email /
3559
+ company, and a pipeline stage. \`triage\` runs the host's triage workflow over a
3560
+ contact and returns the run. The host is the authority; the CLI mirrors + relays.
3561
+ `;
3562
+ async function runCrm(ctx, argv) {
3563
+ const sub = argv[0] ?? "list";
3564
+ if (sub === "--help" || sub === "-h") {
3565
+ write(ctx.io.stdout, CRM_HELP);
3566
+ return 0;
3567
+ }
3568
+ const args = argv.slice(["list", "get", "create", "update", "delete", "triage"].includes(sub) ? 1 : 0);
3569
+ switch (sub) {
3570
+ case "list":
3571
+ return crmList(ctx, args);
3572
+ case "get":
3573
+ return crmGet(ctx, args);
3574
+ case "create":
3575
+ return crmCreate(ctx, args);
3576
+ case "update":
3577
+ return crmUpdate(ctx, args);
3578
+ case "delete":
3579
+ return crmDelete(ctx, args);
3580
+ case "triage":
3581
+ return crmTriage(ctx, args);
3582
+ default:
3583
+ throw new CliError(`Unknown crm command: ${sub}
3584
+ Run \`openwop crm --help\` for usage.`);
3585
+ }
3586
+ }
3587
+ async function crmList(ctx, argv) {
3588
+ const { options } = parseOptions(argv, { bool: ["--help"], value: ["--stage"] });
3589
+ if (options.help) {
3590
+ write(ctx.io.stdout, CRM_HELP);
3591
+ return 0;
3592
+ }
3593
+ const q = options.stage ? `?stage=${encodeURIComponent(String(options.stage))}` : "";
3594
+ const res = await requestJson(ctx, `${BASE}${q}`);
3595
+ if (ctx.json) {
3596
+ writeJson(ctx.io.stdout, res.body);
3597
+ return 0;
3598
+ }
3599
+ const items = Array.isArray(res.body?.contacts) ? res.body.contacts : Array.isArray(res.body) ? res.body : [];
3600
+ if (items.length === 0) {
3601
+ writeLine(ctx.io.stdout, "No contacts.");
3602
+ return 0;
3603
+ }
3604
+ writeLine(ctx.io.stdout, formatTable(
3605
+ items.map((c) => ({ id: c.id ?? "", name: c.name ?? "", stage: c.stage ?? "", email: c.email ?? "", company: c.company ?? "" })),
3606
+ ["id", "name", "stage", "email", "company"]
3607
+ ));
3608
+ return 0;
3609
+ }
3610
+ async function crmGet(ctx, argv) {
3611
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
3612
+ if (options.help || positionals.length !== 1) {
3613
+ write(ctx.io.stdout, "Usage: openwop crm get <contactId> [--json]\n");
3614
+ return options.help ? 0 : 2;
3615
+ }
3616
+ const res = await requestJson(ctx, `${BASE}/${encodeURIComponent(positionals[0])}`);
3617
+ writeJson(ctx.io.stdout, res.body);
3618
+ return 0;
3619
+ }
3620
+ async function crmCreate(ctx, argv) {
3621
+ const { options } = parseOptions(argv, { value: ["--name", "--email", "--company", "--stage"] });
3622
+ if (!options.name) {
3623
+ write(ctx.io.stderr, "Usage: openwop crm create --name <name> [--email e] [--company c] [--stage s] [--json]\n");
3624
+ return 2;
3625
+ }
3626
+ const body = { name: String(options.name) };
3627
+ for (const k of ["email", "company", "stage"]) if (options[k]) body[k] = String(options[k]);
3628
+ const res = await requestJson(ctx, BASE, { method: "POST", body });
3629
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3630
+ else writeLine(ctx.io.stdout, `Created contact ${res.body?.id ?? ""} (${body.name}).`);
3631
+ return 0;
3632
+ }
3633
+ async function crmUpdate(ctx, argv) {
3634
+ const { options, positionals } = parseOptions(argv, { value: ["--name", "--email", "--company", "--stage"] });
3635
+ if (positionals.length !== 1) {
3636
+ write(ctx.io.stderr, "Usage: openwop crm update <contactId> [--name n] [--email e] [--company c] [--stage s] [--json]\n");
3637
+ return 2;
3638
+ }
3639
+ const body = {};
3640
+ for (const k of ["name", "email", "company", "stage"]) if (options[k]) body[k] = String(options[k]);
3641
+ const res = await requestJson(ctx, `${BASE}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body });
3642
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3643
+ else writeLine(ctx.io.stdout, `Updated contact ${positionals[0]}.`);
3644
+ return 0;
3645
+ }
3646
+ async function crmDelete(ctx, argv) {
3647
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"] });
3648
+ if (options.help || positionals.length !== 1) {
3649
+ write(ctx.io.stdout, "Usage: openwop crm delete <contactId> [--yes]\n");
3650
+ return options.help ? 0 : 2;
3651
+ }
3652
+ if (!options.yes) throw new CliError(`Refusing to delete contact ${positionals[0]} without --yes.`, 2);
3653
+ await requestJson(ctx, `${BASE}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
3654
+ writeLine(ctx.io.stdout, `Deleted contact ${positionals[0]}.`);
3655
+ return 0;
3656
+ }
3657
+ async function crmTriage(ctx, argv) {
3658
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
3659
+ if (options.help || positionals.length !== 1) {
3660
+ write(ctx.io.stdout, "Usage: openwop crm triage <contactId> [--json]\n");
3661
+ return options.help ? 0 : 2;
3662
+ }
3663
+ const res = await requestJson(ctx, `${BASE}/${encodeURIComponent(positionals[0])}/triage`, { method: "POST", body: {} });
3664
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3665
+ else writeLine(ctx.io.stdout, `Triaged contact ${positionals[0]} \u2192 run ${res.body?.runId ?? "(see --json)"}.`);
3666
+ return 0;
3667
+ }
3668
+
3669
+ // src/cli/csm.ts
3670
+ var BASE2 = "/v1/host/sample/csm/accounts";
3671
+ var CSM_HELP = `Usage:
3672
+ openwop csm list [--json]
3673
+ openwop csm get <accountId> [--json]
3674
+ openwop csm create --name <name> [--health-score <0-100>] [--json]
3675
+ openwop csm update <accountId> [--name n] [--health-score <0-100>] [--json]
3676
+ openwop csm delete <accountId> [--yes]
3677
+
3678
+ Customer-Success accounts (host-extension ${BASE2}). An account has a name and a
3679
+ health score. The host is the authority; the CLI mirrors + relays.
3680
+ `;
3681
+ async function runCsm(ctx, argv) {
3682
+ const sub = argv[0] ?? "list";
3683
+ if (sub === "--help" || sub === "-h") {
3684
+ write(ctx.io.stdout, CSM_HELP);
3685
+ return 0;
3686
+ }
3687
+ const args = argv.slice(["list", "get", "create", "update", "delete"].includes(sub) ? 1 : 0);
3688
+ switch (sub) {
3689
+ case "list":
3690
+ return csmList(ctx, args);
3691
+ case "get":
3692
+ return csmGet(ctx, args);
3693
+ case "create":
3694
+ return csmCreate(ctx, args);
3695
+ case "update":
3696
+ return csmUpdate(ctx, args);
3697
+ case "delete":
3698
+ return csmDelete(ctx, args);
3699
+ default:
3700
+ throw new CliError(`Unknown csm command: ${sub}
3701
+ Run \`openwop csm --help\` for usage.`);
3702
+ }
3703
+ }
3704
+ async function csmList(ctx, argv) {
3705
+ const { options } = parseOptions(argv, { bool: ["--help"] });
3706
+ if (options.help) {
3707
+ write(ctx.io.stdout, CSM_HELP);
3708
+ return 0;
3709
+ }
3710
+ const res = await requestJson(ctx, BASE2);
3711
+ if (ctx.json) {
3712
+ writeJson(ctx.io.stdout, res.body);
3713
+ return 0;
3714
+ }
3715
+ const items = Array.isArray(res.body?.accounts) ? res.body.accounts : Array.isArray(res.body) ? res.body : [];
3716
+ if (items.length === 0) {
3717
+ writeLine(ctx.io.stdout, "No accounts.");
3718
+ return 0;
3719
+ }
3720
+ writeLine(ctx.io.stdout, formatTable(
3721
+ items.map((a) => ({ id: a.id ?? "", name: a.name ?? "", healthScore: a.healthScore ?? "" })),
3722
+ ["id", "name", "healthScore"]
3723
+ ));
3724
+ return 0;
3725
+ }
3726
+ async function csmGet(ctx, argv) {
3727
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
3728
+ if (options.help || positionals.length !== 1) {
3729
+ write(ctx.io.stdout, "Usage: openwop csm get <accountId> [--json]\n");
3730
+ return options.help ? 0 : 2;
3731
+ }
3732
+ const res = await requestJson(ctx, `${BASE2}/${encodeURIComponent(positionals[0])}`);
3733
+ writeJson(ctx.io.stdout, res.body);
3734
+ return 0;
3735
+ }
3736
+ async function csmCreate(ctx, argv) {
3737
+ const { options } = parseOptions(argv, { value: ["--name", "--health-score"] });
3738
+ if (!options.name) {
3739
+ write(ctx.io.stderr, "Usage: openwop csm create --name <name> [--health-score <0-100>] [--json]\n");
3740
+ return 2;
3741
+ }
3742
+ const body = { name: String(options.name) };
3743
+ if (options.healthScore !== void 0) body.healthScore = Number(options.healthScore);
3744
+ const res = await requestJson(ctx, BASE2, { method: "POST", body });
3745
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3746
+ else writeLine(ctx.io.stdout, `Created account ${res.body?.id ?? ""} (${String(options.name)}).`);
3747
+ return 0;
3748
+ }
3749
+ async function csmUpdate(ctx, argv) {
3750
+ const { options, positionals } = parseOptions(argv, { value: ["--name", "--health-score"] });
3751
+ if (positionals.length !== 1) {
3752
+ write(ctx.io.stderr, "Usage: openwop csm update <accountId> [--name n] [--health-score <0-100>] [--json]\n");
3753
+ return 2;
3754
+ }
3755
+ const body = {};
3756
+ if (options.name) body.name = String(options.name);
3757
+ if (options.healthScore !== void 0) body.healthScore = Number(options.healthScore);
3758
+ const res = await requestJson(ctx, `${BASE2}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body });
3759
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3760
+ else writeLine(ctx.io.stdout, `Updated account ${positionals[0]}.`);
3761
+ return 0;
3762
+ }
3763
+ async function csmDelete(ctx, argv) {
3764
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"] });
3765
+ if (options.help || positionals.length !== 1) {
3766
+ write(ctx.io.stdout, "Usage: openwop csm delete <accountId> [--yes]\n");
3767
+ return options.help ? 0 : 2;
3768
+ }
3769
+ if (!options.yes) throw new CliError(`Refusing to delete account ${positionals[0]} without --yes.`, 2);
3770
+ await requestJson(ctx, `${BASE2}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
3771
+ writeLine(ctx.io.stdout, `Deleted account ${positionals[0]}.`);
3772
+ return 0;
3773
+ }
3774
+
3775
+ // src/cli/comments.ts
3776
+ var base = (org) => `/v1/host/sample/comments/orgs/${encodeURIComponent(org)}/comments`;
3777
+ var COMMENTS_HELP = `Usage:
3778
+ openwop comments list --org <orgId> --resource-type <t> --resource-id <id> [--json]
3779
+ openwop comments create --org <orgId> --resource-type <t> --resource-id <id> --body <text> [--parent <commentId>] [--json]
3780
+ openwop comments update <commentId> --org <orgId> --body <text> [--json]
3781
+ openwop comments delete <commentId> --org <orgId> [--yes]
3782
+
3783
+ Threaded comments on a (resourceType, resourceId) target (host-extension, org-scoped,
3784
+ ADR 0021). Every command needs --org. The host validates the resource in-org and emits
3785
+ a notification on add/reply; the CLI mirrors + relays.
3786
+ `;
3787
+ async function runComments(ctx, argv) {
3788
+ const sub = argv[0] ?? "list";
3789
+ if (sub === "--help" || sub === "-h") {
3790
+ write(ctx.io.stdout, COMMENTS_HELP);
3791
+ return 0;
3792
+ }
3793
+ const args = argv.slice(["list", "create", "update", "delete"].includes(sub) ? 1 : 0);
3794
+ switch (sub) {
3795
+ case "list":
3796
+ return commentsList(ctx, args);
3797
+ case "create":
3798
+ return commentsCreate(ctx, args);
3799
+ case "update":
3800
+ return commentsUpdate(ctx, args);
3801
+ case "delete":
3802
+ return commentsDelete(ctx, args);
3803
+ default:
3804
+ throw new CliError(`Unknown comments command: ${sub}
3805
+ Run \`openwop comments --help\` for usage.`);
3806
+ }
3807
+ }
3808
+ async function commentsList(ctx, argv) {
3809
+ const { options } = parseOptions(argv, { bool: ["--help"], value: ["--org", "--resource-type", "--resource-id"] });
3810
+ if (options.help) {
3811
+ write(ctx.io.stdout, COMMENTS_HELP);
3812
+ return 0;
3813
+ }
3814
+ const org = requireOrg(options.org);
3815
+ if (!options.resourceType || !options.resourceId) {
3816
+ write(ctx.io.stderr, "comments list needs --resource-type and --resource-id.\n");
3817
+ return 2;
3818
+ }
3819
+ const q = `?resourceType=${encodeURIComponent(String(options.resourceType))}&resourceId=${encodeURIComponent(String(options.resourceId))}`;
3820
+ const res = await requestJson(ctx, `${base(org)}${q}`);
3821
+ if (ctx.json) {
3822
+ writeJson(ctx.io.stdout, res.body);
3823
+ return 0;
3824
+ }
3825
+ const items = Array.isArray(res.body?.comments) ? res.body.comments : [];
3826
+ if (items.length === 0) {
3827
+ writeLine(ctx.io.stdout, "No comments.");
3828
+ return 0;
3829
+ }
3830
+ writeLine(ctx.io.stdout, formatTable(
3831
+ items.map((c) => ({ id: c.id ?? "", author: c.authorId ?? "", parent: c.parentId ?? "", body: (c.body ?? "").slice(0, 60) })),
3832
+ ["id", "author", "parent", "body"]
3833
+ ));
3834
+ return 0;
3835
+ }
3836
+ async function commentsCreate(ctx, argv) {
3837
+ const { options } = parseOptions(argv, { value: ["--org", "--resource-type", "--resource-id", "--body", "--parent"] });
3838
+ const org = requireOrg(options.org);
3839
+ if (!options.resourceType || !options.resourceId || !options.body) {
3840
+ write(ctx.io.stderr, "comments create needs --resource-type, --resource-id, and --body.\n");
3841
+ return 2;
3842
+ }
3843
+ const body = { resourceType: String(options.resourceType), resourceId: String(options.resourceId), body: String(options.body) };
3844
+ if (options.parent) body.parentId = String(options.parent);
3845
+ const res = await requestJson(ctx, base(org), { method: "POST", body });
3846
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3847
+ else writeLine(ctx.io.stdout, `Added comment ${res.body?.id ?? ""}.`);
3848
+ return 0;
3849
+ }
3850
+ async function commentsUpdate(ctx, argv) {
3851
+ const { options, positionals } = parseOptions(argv, { value: ["--org", "--body"] });
3852
+ const org = requireOrg(options.org);
3853
+ if (positionals.length !== 1 || !options.body) {
3854
+ write(ctx.io.stderr, "Usage: openwop comments update <commentId> --org <orgId> --body <text> [--json]\n");
3855
+ return 2;
3856
+ }
3857
+ const res = await requestJson(ctx, `${base(org)}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body: { body: String(options.body) } });
3858
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3859
+ else writeLine(ctx.io.stdout, `Updated comment ${positionals[0]}.`);
3860
+ return 0;
3861
+ }
3862
+ async function commentsDelete(ctx, argv) {
3863
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"], value: ["--org"] });
3864
+ const org = requireOrg(options.org);
3865
+ if (options.help || positionals.length !== 1) {
3866
+ write(ctx.io.stdout, "Usage: openwop comments delete <commentId> --org <orgId> [--yes]\n");
3867
+ return options.help ? 0 : 2;
3868
+ }
3869
+ if (!options.yes) throw new CliError(`Refusing to delete comment ${positionals[0]} without --yes.`, 2);
3870
+ await requestJson(ctx, `${base(org)}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
3871
+ writeLine(ctx.io.stdout, `Deleted comment ${positionals[0]}.`);
3872
+ return 0;
3873
+ }
3874
+
3875
+ // src/cli/sharing.ts
3876
+ var links = (org) => `/v1/host/sample/sharing/orgs/${encodeURIComponent(org)}/links`;
3877
+ var SHARING_HELP = `Usage:
3878
+ openwop sharing list --org <orgId> [--json]
3879
+ openwop sharing create --org <orgId> --resource-type <t> --resource-id <id> [--json]
3880
+ openwop sharing revoke <token> --org <orgId> [--yes]
3881
+ openwop sharing resolve <token> [--json]
3882
+
3883
+ Shareable links to a resource (host-extension, ADR 0013). \`list\`/\`create\`/\`revoke\`
3884
+ are org-scoped (need --org, RBAC-gated). \`resolve\` reads a public /shared/<token>
3885
+ link WITHOUT auth \u2014 the unguessable token IS the credential.
3886
+ `;
3887
+ async function runSharing(ctx, argv) {
3888
+ const sub = argv[0] ?? "list";
3889
+ if (sub === "--help" || sub === "-h") {
3890
+ write(ctx.io.stdout, SHARING_HELP);
3891
+ return 0;
3892
+ }
3893
+ const args = argv.slice(["list", "create", "revoke", "resolve"].includes(sub) ? 1 : 0);
3894
+ switch (sub) {
3895
+ case "list":
3896
+ return sharingList(ctx, args);
3897
+ case "create":
3898
+ return sharingCreate(ctx, args);
3899
+ case "revoke":
3900
+ return sharingRevoke(ctx, args);
3901
+ case "resolve":
3902
+ return sharingResolve(ctx, args);
3903
+ default:
3904
+ throw new CliError(`Unknown sharing command: ${sub}
3905
+ Run \`openwop sharing --help\` for usage.`);
3906
+ }
3907
+ }
3908
+ async function sharingList(ctx, argv) {
3909
+ const { options } = parseOptions(argv, { bool: ["--help"], value: ["--org"] });
3910
+ if (options.help) {
3911
+ write(ctx.io.stdout, SHARING_HELP);
3912
+ return 0;
3913
+ }
3914
+ const org = requireOrg(options.org);
3915
+ const res = await requestJson(ctx, links(org));
3916
+ if (ctx.json) {
3917
+ writeJson(ctx.io.stdout, res.body);
3918
+ return 0;
3919
+ }
3920
+ const items = Array.isArray(res.body?.links) ? res.body.links : [];
3921
+ if (items.length === 0) {
3922
+ writeLine(ctx.io.stdout, "No share links.");
3923
+ return 0;
3924
+ }
3925
+ writeLine(ctx.io.stdout, formatTable(
3926
+ items.map((l) => ({ token: l.token ?? "", resource: `${l.resourceType ?? ""}:${l.resourceId ?? ""}`, createdAt: l.createdAt ?? "" })),
3927
+ ["token", "resource", "createdAt"]
3928
+ ));
3929
+ return 0;
3930
+ }
3931
+ async function sharingCreate(ctx, argv) {
3932
+ const { options } = parseOptions(argv, { value: ["--org", "--resource-type", "--resource-id"] });
3933
+ const org = requireOrg(options.org);
3934
+ if (!options.resourceType || !options.resourceId) {
3935
+ write(ctx.io.stderr, "sharing create needs --resource-type and --resource-id.\n");
3936
+ return 2;
3937
+ }
3938
+ const body = { resourceType: String(options.resourceType), resourceId: String(options.resourceId) };
3939
+ const res = await requestJson(ctx, links(org), { method: "POST", body });
3940
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
3941
+ else writeLine(ctx.io.stdout, `Created share link ${res.body?.token ?? ""}${res.body?.url ? ` (${res.body.url})` : ""}.`);
3942
+ return 0;
3943
+ }
3944
+ async function sharingRevoke(ctx, argv) {
3945
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"], value: ["--org"] });
3946
+ const org = requireOrg(options.org);
3947
+ if (options.help || positionals.length !== 1) {
3948
+ write(ctx.io.stdout, "Usage: openwop sharing revoke <token> --org <orgId> [--yes]\n");
3949
+ return options.help ? 0 : 2;
3950
+ }
3951
+ if (!options.yes) throw new CliError(`Refusing to revoke share link ${positionals[0]} without --yes.`, 2);
3952
+ await requestJson(ctx, `${links(org)}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
3953
+ writeLine(ctx.io.stdout, `Revoked share link ${positionals[0]}.`);
3954
+ return 0;
3955
+ }
3956
+ async function sharingResolve(ctx, argv) {
3957
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
3958
+ if (options.help || positionals.length !== 1) {
3959
+ write(ctx.io.stdout, "Usage: openwop sharing resolve <token> [--json]\n");
3960
+ return options.help ? 0 : 2;
3961
+ }
3962
+ const res = await requestJson(ctx, `/v1/host/sample/shared/${encodeURIComponent(positionals[0])}`, { auth: false });
3963
+ writeJson(ctx.io.stdout, res.body);
3964
+ return 0;
3965
+ }
3966
+
3967
+ // src/cli/forms.ts
3968
+ var base2 = (org) => `/v1/host/sample/forms/orgs/${encodeURIComponent(org)}/forms`;
3969
+ var FORMS_HELP = `Usage:
3970
+ openwop forms list --org <orgId> [--json]
3971
+ openwop forms get <formId> --org <orgId> [--json]
3972
+ openwop forms create --org <orgId> --title <t> [--fields-json '[...]'] [--json]
3973
+ openwop forms update <formId> --org <orgId> [--title <t>] [--fields-json '[...]'] [--json]
3974
+ openwop forms status <formId> --org <orgId> --status <draft|published|closed> [--json]
3975
+ openwop forms delete <formId> --org <orgId> [--yes]
3976
+ openwop forms submissions <formId> --org <orgId> [--json]
3977
+
3978
+ Form builder + intake (host-extension, org-scoped). Every command needs --org.
3979
+ A form has a title + fields[]; \`status\` publishes/closes it; \`submissions\` reads
3980
+ the collected responses. The host is the authority; the CLI mirrors + relays.
3981
+ `;
3982
+ function parseFields(raw) {
3983
+ if (raw === void 0) return void 0;
3984
+ try {
3985
+ return JSON.parse(String(raw));
3986
+ } catch {
3987
+ throw new CliError("--fields-json must be valid JSON.", 2);
3988
+ }
3989
+ }
3990
+ async function runForms(ctx, argv) {
3991
+ const sub = argv[0] ?? "list";
3992
+ if (sub === "--help" || sub === "-h") {
3993
+ write(ctx.io.stdout, FORMS_HELP);
3994
+ return 0;
3995
+ }
3996
+ const args = argv.slice(["list", "get", "create", "update", "status", "delete", "submissions"].includes(sub) ? 1 : 0);
3997
+ switch (sub) {
3998
+ case "list":
3999
+ return formsList(ctx, args);
4000
+ case "get":
4001
+ return formsGet(ctx, args);
4002
+ case "create":
4003
+ return formsCreate(ctx, args);
4004
+ case "update":
4005
+ return formsUpdate(ctx, args);
4006
+ case "status":
4007
+ return formsStatus(ctx, args);
4008
+ case "delete":
4009
+ return formsDelete(ctx, args);
4010
+ case "submissions":
4011
+ return formsSubmissions(ctx, args);
4012
+ default:
4013
+ throw new CliError(`Unknown forms command: ${sub}
4014
+ Run \`openwop forms --help\` for usage.`);
4015
+ }
4016
+ }
4017
+ async function formsList(ctx, argv) {
4018
+ const { options } = parseOptions(argv, { bool: ["--help"], value: ["--org"] });
4019
+ if (options.help) {
4020
+ write(ctx.io.stdout, FORMS_HELP);
4021
+ return 0;
4022
+ }
4023
+ const org = requireOrg(options.org);
4024
+ const res = await requestJson(ctx, base2(org));
4025
+ if (ctx.json) {
4026
+ writeJson(ctx.io.stdout, res.body);
4027
+ return 0;
4028
+ }
4029
+ const items = Array.isArray(res.body?.forms) ? res.body.forms : [];
4030
+ if (items.length === 0) {
4031
+ writeLine(ctx.io.stdout, "No forms.");
4032
+ return 0;
4033
+ }
4034
+ writeLine(ctx.io.stdout, formatTable(
4035
+ items.map((f) => ({ id: f.id ?? "", title: f.title ?? "", status: f.status ?? "", fields: Array.isArray(f.fields) ? f.fields.length : "" })),
4036
+ ["id", "title", "status", "fields"]
4037
+ ));
4038
+ return 0;
4039
+ }
4040
+ async function formsGet(ctx, argv) {
4041
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--org"] });
4042
+ const org = requireOrg(options.org);
4043
+ if (options.help || positionals.length !== 1) {
4044
+ write(ctx.io.stdout, "Usage: openwop forms get <formId> --org <orgId> [--json]\n");
4045
+ return options.help ? 0 : 2;
4046
+ }
4047
+ const res = await requestJson(ctx, `${base2(org)}/${encodeURIComponent(positionals[0])}`);
4048
+ writeJson(ctx.io.stdout, res.body);
4049
+ return 0;
4050
+ }
4051
+ async function formsCreate(ctx, argv) {
4052
+ const { options } = parseOptions(argv, { value: ["--org", "--title", "--fields-json"] });
4053
+ const org = requireOrg(options.org);
4054
+ if (!options.title) {
4055
+ write(ctx.io.stderr, "Usage: openwop forms create --org <orgId> --title <t> [--fields-json '[...]'] [--json]\n");
4056
+ return 2;
4057
+ }
4058
+ const body = { title: String(options.title) };
4059
+ const fields = parseFields(options.fieldsJson);
4060
+ if (fields !== void 0) body.fields = fields;
4061
+ const res = await requestJson(ctx, base2(org), { method: "POST", body });
4062
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4063
+ else writeLine(ctx.io.stdout, `Created form ${res.body?.id ?? ""} (${String(options.title)}).`);
4064
+ return 0;
4065
+ }
4066
+ async function formsUpdate(ctx, argv) {
4067
+ const { options, positionals } = parseOptions(argv, { value: ["--org", "--title", "--fields-json"] });
4068
+ const org = requireOrg(options.org);
4069
+ if (positionals.length !== 1) {
4070
+ write(ctx.io.stderr, "Usage: openwop forms update <formId> --org <orgId> [--title <t>] [--fields-json ...] [--json]\n");
4071
+ return 2;
4072
+ }
4073
+ const body = {};
4074
+ if (options.title) body.title = String(options.title);
4075
+ const fields = parseFields(options.fieldsJson);
4076
+ if (fields !== void 0) body.fields = fields;
4077
+ const res = await requestJson(ctx, `${base2(org)}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body });
4078
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4079
+ else writeLine(ctx.io.stdout, `Updated form ${positionals[0]}.`);
4080
+ return 0;
4081
+ }
4082
+ async function formsStatus(ctx, argv) {
4083
+ const { options, positionals } = parseOptions(argv, { value: ["--org", "--status"] });
4084
+ const org = requireOrg(options.org);
4085
+ if (positionals.length !== 1 || !options.status) {
4086
+ write(ctx.io.stderr, "Usage: openwop forms status <formId> --org <orgId> --status <draft|published|closed> [--json]\n");
4087
+ return 2;
4088
+ }
4089
+ const res = await requestJson(ctx, `${base2(org)}/${encodeURIComponent(positionals[0])}/status`, { method: "POST", body: { status: String(options.status) } });
4090
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4091
+ else writeLine(ctx.io.stdout, `Form ${positionals[0]} \u2192 status ${options.status}.`);
4092
+ return 0;
4093
+ }
4094
+ async function formsDelete(ctx, argv) {
4095
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"], value: ["--org"] });
4096
+ const org = requireOrg(options.org);
4097
+ if (options.help || positionals.length !== 1) {
4098
+ write(ctx.io.stdout, "Usage: openwop forms delete <formId> --org <orgId> [--yes]\n");
4099
+ return options.help ? 0 : 2;
4100
+ }
4101
+ if (!options.yes) throw new CliError(`Refusing to delete form ${positionals[0]} without --yes.`, 2);
4102
+ await requestJson(ctx, `${base2(org)}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
4103
+ writeLine(ctx.io.stdout, `Deleted form ${positionals[0]}.`);
4104
+ return 0;
4105
+ }
4106
+ async function formsSubmissions(ctx, argv) {
4107
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--org"] });
4108
+ const org = requireOrg(options.org);
4109
+ if (options.help || positionals.length !== 1) {
4110
+ write(ctx.io.stdout, "Usage: openwop forms submissions <formId> --org <orgId> [--json]\n");
4111
+ return options.help ? 0 : 2;
4112
+ }
4113
+ const res = await requestJson(ctx, `${base2(org)}/${encodeURIComponent(positionals[0])}/submissions`);
4114
+ if (ctx.json) {
4115
+ writeJson(ctx.io.stdout, res.body);
4116
+ return 0;
4117
+ }
4118
+ const subs = Array.isArray(res.body?.submissions) ? res.body.submissions : [];
4119
+ writeLine(ctx.io.stdout, `${subs.length} submission(s).`);
4120
+ if (subs.length) writeJson(ctx.io.stdout, subs);
4121
+ return 0;
4122
+ }
4123
+
4124
+ // src/cli/email.ts
4125
+ var base3 = (org) => `/v1/host/sample/email/orgs/${encodeURIComponent(org)}`;
4126
+ var EMAIL_HELP = `Usage:
4127
+ openwop email templates list --org <orgId> [--json]
4128
+ openwop email templates get <templateId> --org <orgId> [--json]
4129
+ openwop email templates create --org <orgId> --name <n> --subject <s> --body <b> [--json]
4130
+ openwop email templates update <templateId> --org <orgId> [--name n] [--subject s] [--body b] [--json]
4131
+ openwop email templates delete <templateId> --org <orgId> [--yes]
4132
+ openwop email campaigns list --org <orgId> [--json]
4133
+ openwop email campaigns get <campaignId> --org <orgId> [--json]
4134
+ openwop email campaigns create --org <orgId> --template <templateId> [--name <n>] [--json]
4135
+ openwop email campaigns delete <campaignId> --org <orgId> [--yes]
4136
+ openwop email campaigns send <campaignId> --org <orgId> [--yes] [--json]
4137
+ openwop email campaigns sends <campaignId> --org <orgId> [--json]
4138
+
4139
+ Outbound email (host-extension, org-scoped). Templates hold a name/subject/body;
4140
+ a campaign binds a template + audience; \`send\` dispatches it and \`sends\` reads the
4141
+ delivery log. Every command needs --org. The host is the authority; the CLI relays.
4142
+ `;
4143
+ async function runEmail(ctx, argv) {
4144
+ const group = argv[0];
4145
+ if (group === "--help" || group === "-h" || group === void 0) {
4146
+ write(ctx.io.stdout, EMAIL_HELP);
4147
+ return group === void 0 ? 2 : 0;
4148
+ }
4149
+ const rest = argv.slice(1);
4150
+ if (group === "templates") return emailTemplates(ctx, rest);
4151
+ if (group === "campaigns") return emailCampaigns(ctx, rest);
4152
+ throw new CliError(`Unknown email command: ${group}. Use 'templates' or 'campaigns'.`);
4153
+ }
4154
+ async function emailTemplates(ctx, argv) {
4155
+ const sub = argv[0] ?? "list";
4156
+ const args = argv.slice(["list", "get", "create", "update", "delete"].includes(sub) ? 1 : 0);
4157
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name", "--subject", "--body"] });
4158
+ if (options.help) {
4159
+ write(ctx.io.stdout, EMAIL_HELP);
4160
+ return 0;
4161
+ }
4162
+ const org = requireOrg(options.org);
4163
+ const url = `${base3(org)}/templates`;
4164
+ switch (sub) {
4165
+ case "list": {
4166
+ const res = await requestJson(ctx, url);
4167
+ if (ctx.json) {
4168
+ writeJson(ctx.io.stdout, res.body);
4169
+ return 0;
4170
+ }
4171
+ const items = Array.isArray(res.body?.templates) ? res.body.templates : [];
4172
+ if (items.length === 0) {
4173
+ writeLine(ctx.io.stdout, "No templates.");
4174
+ return 0;
4175
+ }
4176
+ writeLine(ctx.io.stdout, formatTable(items.map((t) => ({ id: t.id ?? "", name: t.name ?? "", subject: t.subject ?? "" })), ["id", "name", "subject"]));
4177
+ return 0;
4178
+ }
4179
+ case "get": {
4180
+ if (positionals.length !== 1) {
4181
+ write(ctx.io.stderr, "Usage: openwop email templates get <templateId> --org <orgId>\n");
4182
+ return 2;
4183
+ }
4184
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`);
4185
+ writeJson(ctx.io.stdout, res.body);
4186
+ return 0;
4187
+ }
4188
+ case "create": {
4189
+ if (!options.name || !options.subject || !options.body) {
4190
+ write(ctx.io.stderr, "email templates create needs --name, --subject, --body.\n");
4191
+ return 2;
4192
+ }
4193
+ const res = await requestJson(ctx, url, { method: "POST", body: { name: String(options.name), subject: String(options.subject), body: String(options.body) } });
4194
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4195
+ else writeLine(ctx.io.stdout, `Created template ${res.body?.id ?? ""} (${String(options.name)}).`);
4196
+ return 0;
4197
+ }
4198
+ case "update": {
4199
+ if (positionals.length !== 1) {
4200
+ write(ctx.io.stderr, "Usage: openwop email templates update <templateId> --org <orgId> [--name n] [--subject s] [--body b]\n");
4201
+ return 2;
4202
+ }
4203
+ const patch = {};
4204
+ for (const k of ["name", "subject", "body"]) if (options[k]) patch[k] = String(options[k]);
4205
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body: patch });
4206
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4207
+ else writeLine(ctx.io.stdout, `Updated template ${positionals[0]}.`);
4208
+ return 0;
4209
+ }
4210
+ case "delete": {
4211
+ if (positionals.length !== 1) {
4212
+ write(ctx.io.stderr, "Usage: openwop email templates delete <templateId> --org <orgId> [--yes]\n");
4213
+ return 2;
4214
+ }
4215
+ if (!options.yes) throw new CliError(`Refusing to delete template ${positionals[0]} without --yes.`, 2);
4216
+ await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
4217
+ writeLine(ctx.io.stdout, `Deleted template ${positionals[0]}.`);
4218
+ return 0;
4219
+ }
4220
+ default:
4221
+ throw new CliError(`Unknown email templates command: ${sub}`);
4222
+ }
4223
+ }
4224
+ async function emailCampaigns(ctx, argv) {
4225
+ const sub = argv[0] ?? "list";
4226
+ const args = argv.slice(["list", "get", "create", "delete", "send", "sends"].includes(sub) ? 1 : 0);
4227
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--template", "--name"] });
4228
+ if (options.help) {
4229
+ write(ctx.io.stdout, EMAIL_HELP);
4230
+ return 0;
4231
+ }
4232
+ const org = requireOrg(options.org);
4233
+ const url = `${base3(org)}/campaigns`;
4234
+ switch (sub) {
4235
+ case "list": {
4236
+ const res = await requestJson(ctx, url);
4237
+ if (ctx.json) {
4238
+ writeJson(ctx.io.stdout, res.body);
4239
+ return 0;
4240
+ }
4241
+ const items = Array.isArray(res.body?.campaigns) ? res.body.campaigns : [];
4242
+ if (items.length === 0) {
4243
+ writeLine(ctx.io.stdout, "No campaigns.");
4244
+ return 0;
4245
+ }
4246
+ writeLine(ctx.io.stdout, formatTable(items.map((c) => ({ id: c.id ?? "", name: c.name ?? "", status: c.status ?? "", template: c.templateId ?? "" })), ["id", "name", "status", "template"]));
4247
+ return 0;
4248
+ }
4249
+ case "get": {
4250
+ if (positionals.length !== 1) {
4251
+ write(ctx.io.stderr, "Usage: openwop email campaigns get <campaignId> --org <orgId>\n");
4252
+ return 2;
4253
+ }
4254
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`);
4255
+ writeJson(ctx.io.stdout, res.body);
4256
+ return 0;
4257
+ }
4258
+ case "create": {
4259
+ if (!options.template) {
4260
+ write(ctx.io.stderr, "email campaigns create needs --template <templateId>.\n");
4261
+ return 2;
4262
+ }
4263
+ const body = { templateId: String(options.template) };
4264
+ if (options.name) body.name = String(options.name);
4265
+ const res = await requestJson(ctx, url, { method: "POST", body });
4266
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4267
+ else writeLine(ctx.io.stdout, `Created campaign ${res.body?.id ?? ""}.`);
4268
+ return 0;
4269
+ }
4270
+ case "delete": {
4271
+ if (positionals.length !== 1) {
4272
+ write(ctx.io.stderr, "Usage: openwop email campaigns delete <campaignId> --org <orgId> [--yes]\n");
4273
+ return 2;
4274
+ }
4275
+ if (!options.yes) throw new CliError(`Refusing to delete campaign ${positionals[0]} without --yes.`, 2);
4276
+ await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
4277
+ writeLine(ctx.io.stdout, `Deleted campaign ${positionals[0]}.`);
4278
+ return 0;
4279
+ }
4280
+ case "send": {
4281
+ if (positionals.length !== 1) {
4282
+ write(ctx.io.stderr, "Usage: openwop email campaigns send <campaignId> --org <orgId> [--yes]\n");
4283
+ return 2;
4284
+ }
4285
+ if (!options.yes) throw new CliError(`Refusing to SEND campaign ${positionals[0]} without --yes (this dispatches real email).`, 2);
4286
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}/send`, { method: "POST", body: {} });
4287
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4288
+ else writeLine(ctx.io.stdout, `Sent campaign ${positionals[0]}.`);
4289
+ return 0;
4290
+ }
4291
+ case "sends": {
4292
+ if (positionals.length !== 1) {
4293
+ write(ctx.io.stderr, "Usage: openwop email campaigns sends <campaignId> --org <orgId>\n");
4294
+ return 2;
4295
+ }
4296
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}/sends`);
4297
+ writeJson(ctx.io.stdout, res.body);
4298
+ return 0;
4299
+ }
4300
+ default:
4301
+ throw new CliError(`Unknown email campaigns command: ${sub}`);
4302
+ }
4303
+ }
4304
+
4305
+ // src/cli/chatWidget.ts
4306
+ var base4 = (org) => `/v1/host/sample/chat-widget/orgs/${encodeURIComponent(org)}/widgets`;
4307
+ var CHAT_WIDGET_HELP = `Usage:
4308
+ openwop chat-widget list --org <orgId> [--json]
4309
+ openwop chat-widget get <widgetId> --org <orgId> [--json]
4310
+ openwop chat-widget create --org <orgId> [--name <n>] [--json]
4311
+ openwop chat-widget update <widgetId> --org <orgId> [--name <n>] [--json]
4312
+ openwop chat-widget delete <widgetId> --org <orgId> [--yes]
4313
+ openwop chat-widget rotate-token <widgetId> --org <orgId> [--json]
4314
+
4315
+ Embeddable chat widgets (host-extension, org-scoped). Each widget carries an embed
4316
+ token; \`rotate-token\` invalidates the old one. Every command needs --org.
4317
+ `;
4318
+ async function runChatWidget(ctx, argv) {
4319
+ const sub = argv[0] ?? "list";
4320
+ if (sub === "--help" || sub === "-h") {
4321
+ write(ctx.io.stdout, CHAT_WIDGET_HELP);
4322
+ return 0;
4323
+ }
4324
+ const args = argv.slice(["list", "get", "create", "update", "delete", "rotate-token"].includes(sub) ? 1 : 0);
4325
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name"] });
4326
+ if (options.help) {
4327
+ write(ctx.io.stdout, CHAT_WIDGET_HELP);
4328
+ return 0;
4329
+ }
4330
+ const org = requireOrg(options.org);
4331
+ switch (sub) {
4332
+ case "list": {
4333
+ const res = await requestJson(ctx, base4(org));
4334
+ if (ctx.json) {
4335
+ writeJson(ctx.io.stdout, res.body);
4336
+ return 0;
4337
+ }
4338
+ const items = Array.isArray(res.body?.widgets) ? res.body.widgets : [];
4339
+ if (items.length === 0) {
4340
+ writeLine(ctx.io.stdout, "No widgets.");
4341
+ return 0;
4342
+ }
4343
+ writeLine(ctx.io.stdout, formatTable(items.map((w) => ({ id: w.id ?? w.widgetId ?? "", name: w.name ?? "", createdAt: w.createdAt ?? "" })), ["id", "name", "createdAt"]));
4344
+ return 0;
4345
+ }
4346
+ case "get": {
4347
+ if (positionals.length !== 1) {
4348
+ write(ctx.io.stderr, "Usage: openwop chat-widget get <widgetId> --org <orgId>\n");
4349
+ return 2;
4350
+ }
4351
+ const res = await requestJson(ctx, `${base4(org)}/${encodeURIComponent(positionals[0])}`);
4352
+ writeJson(ctx.io.stdout, res.body);
4353
+ return 0;
4354
+ }
4355
+ case "create": {
4356
+ const body = {};
4357
+ if (options.name) body.name = String(options.name);
4358
+ const res = await requestJson(ctx, base4(org), { method: "POST", body });
4359
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4360
+ else writeLine(ctx.io.stdout, `Created widget ${res.body?.widget?.id ?? res.body?.id ?? ""}.`);
4361
+ return 0;
4362
+ }
4363
+ case "update": {
4364
+ if (positionals.length !== 1) {
4365
+ write(ctx.io.stderr, "Usage: openwop chat-widget update <widgetId> --org <orgId> [--name n]\n");
4366
+ return 2;
4367
+ }
4368
+ const patch = {};
4369
+ if (options.name) patch.name = String(options.name);
4370
+ const res = await requestJson(ctx, `${base4(org)}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body: patch });
4371
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4372
+ else writeLine(ctx.io.stdout, `Updated widget ${positionals[0]}.`);
4373
+ return 0;
4374
+ }
4375
+ case "delete": {
4376
+ if (positionals.length !== 1) {
4377
+ write(ctx.io.stderr, "Usage: openwop chat-widget delete <widgetId> --org <orgId> [--yes]\n");
4378
+ return 2;
4379
+ }
4380
+ if (!options.yes) throw new CliError(`Refusing to delete widget ${positionals[0]} without --yes.`, 2);
4381
+ await requestJson(ctx, `${base4(org)}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
4382
+ writeLine(ctx.io.stdout, `Deleted widget ${positionals[0]}.`);
4383
+ return 0;
4384
+ }
4385
+ case "rotate-token": {
4386
+ if (positionals.length !== 1) {
4387
+ write(ctx.io.stderr, "Usage: openwop chat-widget rotate-token <widgetId> --org <orgId>\n");
4388
+ return 2;
4389
+ }
4390
+ const res = await requestJson(ctx, `${base4(org)}/${encodeURIComponent(positionals[0])}/rotate-token`, { method: "POST", body: {} });
4391
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4392
+ else writeLine(ctx.io.stdout, `Rotated token for widget ${positionals[0]}.`);
4393
+ return 0;
4394
+ }
4395
+ default:
4396
+ throw new CliError(`Unknown chat-widget command: ${sub}
4397
+ Run \`openwop chat-widget --help\` for usage.`);
4398
+ }
4399
+ }
4400
+
4401
+ // src/cli/marketplace.ts
4402
+ var MP = "/v1/host/sample/marketplace";
4403
+ var reviews = (org, pack) => `${MP}/orgs/${encodeURIComponent(org)}/listings/${encodeURIComponent(pack)}/reviews`;
4404
+ var MARKETPLACE_HELP = `Usage:
4405
+ openwop marketplace listings [--json]
4406
+ openwop marketplace install --pack <name> --version <v> [--json]
4407
+ openwop marketplace reviews <packName> --org <orgId> [--json]
4408
+ openwop marketplace review <packName> --org <orgId> --rating <1-5> [--comment <t>] [--json]
4409
+ openwop marketplace unreview <packName> --org <orgId> [--yes]
4410
+
4411
+ The pack marketplace (host-extension). \`listings\`/\`install\` browse + install shared
4412
+ packs; \`reviews\`/\`review\`/\`unreview\` are org-scoped (need --org). Distinct from the
4413
+ signed node-pack \`packs\` registry. The host is the authority; the CLI mirrors + relays.
4414
+ `;
4415
+ async function runMarketplace(ctx, argv) {
4416
+ const sub = argv[0] ?? "listings";
4417
+ if (sub === "--help" || sub === "-h") {
4418
+ write(ctx.io.stdout, MARKETPLACE_HELP);
4419
+ return 0;
4420
+ }
4421
+ const args = argv.slice(["listings", "install", "reviews", "review", "unreview"].includes(sub) ? 1 : 0);
4422
+ switch (sub) {
4423
+ case "listings":
4424
+ return mpListings(ctx, args);
4425
+ case "install":
4426
+ return mpInstall(ctx, args);
4427
+ case "reviews":
4428
+ return mpReviews(ctx, args);
4429
+ case "review":
4430
+ return mpReview(ctx, args);
4431
+ case "unreview":
4432
+ return mpUnreview(ctx, args);
4433
+ default:
4434
+ throw new CliError(`Unknown marketplace command: ${sub}
4435
+ Run \`openwop marketplace --help\` for usage.`);
4436
+ }
4437
+ }
4438
+ async function mpListings(ctx, argv) {
4439
+ const { options } = parseOptions(argv, { bool: ["--help"] });
4440
+ if (options.help) {
4441
+ write(ctx.io.stdout, MARKETPLACE_HELP);
4442
+ return 0;
4443
+ }
4444
+ const res = await requestJson(ctx, `${MP}/listings`);
4445
+ if (ctx.json) {
4446
+ writeJson(ctx.io.stdout, res.body);
4447
+ return 0;
4448
+ }
4449
+ const items = Array.isArray(res.body?.listings) ? res.body.listings : [];
4450
+ if (items.length === 0) {
4451
+ writeLine(ctx.io.stdout, "No listings.");
4452
+ return 0;
4453
+ }
4454
+ writeLine(ctx.io.stdout, formatTable(
4455
+ items.map((l) => ({ pack: l.packName ?? l.name ?? "", version: l.version ?? l.latestVersion ?? "", rating: l.rating ?? "", installs: l.installs ?? "" })),
4456
+ ["pack", "version", "rating", "installs"]
4457
+ ));
4458
+ return 0;
4459
+ }
4460
+ async function mpInstall(ctx, argv) {
4461
+ const { options } = parseOptions(argv, { value: ["--pack", "--version"] });
4462
+ if (!options.pack || !options.version) {
4463
+ write(ctx.io.stderr, "Usage: openwop marketplace install --pack <name> --version <v> [--json]\n");
4464
+ return 2;
4465
+ }
4466
+ const res = await requestJson(ctx, `${MP}/install`, { method: "POST", body: { packName: String(options.pack), version: String(options.version) } });
4467
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4468
+ else writeLine(ctx.io.stdout, `Installed ${String(options.pack)}@${String(options.version)}.`);
4469
+ return 0;
4470
+ }
4471
+ async function mpReviews(ctx, argv) {
4472
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--org"] });
4473
+ const org = requireOrg(options.org);
4474
+ if (options.help || positionals.length !== 1) {
4475
+ write(ctx.io.stdout, "Usage: openwop marketplace reviews <packName> --org <orgId> [--json]\n");
4476
+ return options.help ? 0 : 2;
4477
+ }
4478
+ const res = await requestJson(ctx, reviews(org, positionals[0]));
4479
+ if (ctx.json) {
4480
+ writeJson(ctx.io.stdout, res.body);
4481
+ return 0;
4482
+ }
4483
+ const items = Array.isArray(res.body?.reviews) ? res.body.reviews : [];
4484
+ if (items.length === 0) {
4485
+ writeLine(ctx.io.stdout, "No reviews.");
4486
+ return 0;
4487
+ }
4488
+ writeLine(ctx.io.stdout, formatTable(items.map((r) => ({ author: r.authorId ?? "", rating: r.rating ?? "", comment: (r.comment ?? "").slice(0, 60) })), ["author", "rating", "comment"]));
4489
+ return 0;
4490
+ }
4491
+ async function mpReview(ctx, argv) {
4492
+ const { options, positionals } = parseOptions(argv, { value: ["--org", "--rating", "--comment"] });
4493
+ const org = requireOrg(options.org);
4494
+ if (positionals.length !== 1 || options.rating === void 0) {
4495
+ write(ctx.io.stderr, "Usage: openwop marketplace review <packName> --org <orgId> --rating <1-5> [--comment <t>] [--json]\n");
4496
+ return 2;
4497
+ }
4498
+ const body = { rating: Number(options.rating) };
4499
+ if (options.comment) body.comment = String(options.comment);
4500
+ const res = await requestJson(ctx, reviews(org, positionals[0]), { method: "POST", body });
4501
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4502
+ else writeLine(ctx.io.stdout, `Reviewed ${positionals[0]} (${Number(options.rating)}\u2605).`);
4503
+ return 0;
4504
+ }
4505
+ async function mpUnreview(ctx, argv) {
4506
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"], value: ["--org"] });
4507
+ const org = requireOrg(options.org);
4508
+ if (options.help || positionals.length !== 1) {
4509
+ write(ctx.io.stdout, "Usage: openwop marketplace unreview <packName> --org <orgId> [--yes]\n");
4510
+ return options.help ? 0 : 2;
4511
+ }
4512
+ if (!options.yes) throw new CliError(`Refusing to remove your review of ${positionals[0]} without --yes.`, 2);
4513
+ await requestJson(ctx, reviews(org, positionals[0]), { method: "DELETE" });
4514
+ writeLine(ctx.io.stdout, `Removed review of ${positionals[0]}.`);
4515
+ return 0;
4516
+ }
4517
+
4518
+ // src/cli/kb.ts
4519
+ var base5 = (org) => `/v1/host/sample/kb/orgs/${encodeURIComponent(org)}`;
4520
+ var cols = (org) => `${base5(org)}/collections`;
4521
+ var KB_HELP = `Usage:
4522
+ openwop kb collections list --org <orgId> [--json]
4523
+ openwop kb collections get <collectionId> --org <orgId> [--json]
4524
+ openwop kb collections create --org <orgId> --name <n> [--json]
4525
+ openwop kb collections delete <collectionId> --org <orgId> [--yes]
4526
+ openwop kb docs list <collectionId> --org <orgId> [--json]
4527
+ openwop kb docs get <collectionId> <documentId> --org <orgId> [--json]
4528
+ openwop kb docs add <collectionId> --org <orgId> --title <t> --text <text> [--json]
4529
+ openwop kb docs delete <collectionId> <documentId> --org <orgId> [--yes]
4530
+ openwop kb search <collectionId> --org <orgId> --query <q> [--top-k <n>] [--json]
4531
+ openwop kb rag <collectionId> --org <orgId> --query <q> [--top-k <n>] [--json]
4532
+
4533
+ Knowledge base (host-extension, org-scoped). Collections hold documents; \`search\` runs
4534
+ retrieval and \`rag\` a retrieve-then-generate query. Every command needs --org. The host
4535
+ is the authority; the CLI mirrors + relays.
4536
+ `;
4537
+ async function runKb(ctx, argv) {
4538
+ const group = argv[0] ?? "collections";
4539
+ if (group === "--help" || group === "-h") {
4540
+ write(ctx.io.stdout, KB_HELP);
4541
+ return 0;
4542
+ }
4543
+ const rest = argv.slice(1);
4544
+ switch (group) {
4545
+ case "collections":
4546
+ return kbCollections(ctx, rest);
4547
+ case "docs":
4548
+ return kbDocs(ctx, rest);
4549
+ case "search":
4550
+ return kbSearch(ctx, rest, "search");
4551
+ case "rag":
4552
+ return kbSearch(ctx, rest, "rag");
4553
+ default:
4554
+ throw new CliError(`Unknown kb command: ${group}. Use collections|docs|search|rag.`);
4555
+ }
4556
+ }
4557
+ async function kbCollections(ctx, argv) {
4558
+ const sub = argv[0] ?? "list";
4559
+ const args = argv.slice(["list", "get", "create", "delete"].includes(sub) ? 1 : 0);
4560
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name"] });
4561
+ if (options.help) {
4562
+ write(ctx.io.stdout, KB_HELP);
4563
+ return 0;
4564
+ }
4565
+ const org = requireOrg(options.org);
4566
+ switch (sub) {
4567
+ case "list": {
4568
+ const res = await requestJson(ctx, cols(org));
4569
+ if (ctx.json) {
4570
+ writeJson(ctx.io.stdout, res.body);
4571
+ return 0;
4572
+ }
4573
+ const items = Array.isArray(res.body?.collections) ? res.body.collections : [];
4574
+ if (items.length === 0) {
4575
+ writeLine(ctx.io.stdout, "No collections.");
4576
+ return 0;
4577
+ }
4578
+ writeLine(ctx.io.stdout, formatTable(items.map((c) => ({ id: c.id ?? c.collectionId ?? "", name: c.name ?? "", docs: c.documentCount ?? "" })), ["id", "name", "docs"]));
4579
+ return 0;
4580
+ }
4581
+ case "get": {
4582
+ if (positionals.length !== 1) {
4583
+ write(ctx.io.stderr, "Usage: openwop kb collections get <collectionId> --org <orgId>\n");
4584
+ return 2;
4585
+ }
4586
+ const res = await requestJson(ctx, `${cols(org)}/${encodeURIComponent(positionals[0])}`);
4587
+ writeJson(ctx.io.stdout, res.body);
4588
+ return 0;
4589
+ }
4590
+ case "create": {
4591
+ if (!options.name) {
4592
+ write(ctx.io.stderr, "kb collections create needs --name.\n");
4593
+ return 2;
4594
+ }
4595
+ const res = await requestJson(ctx, cols(org), { method: "POST", body: { name: String(options.name) } });
4596
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4597
+ else writeLine(ctx.io.stdout, `Created collection ${res.body?.id ?? res.body?.collectionId ?? ""} (${String(options.name)}).`);
4598
+ return 0;
4599
+ }
4600
+ case "delete": {
4601
+ if (positionals.length !== 1) {
4602
+ write(ctx.io.stderr, "Usage: openwop kb collections delete <collectionId> --org <orgId> [--yes]\n");
4603
+ return 2;
4604
+ }
4605
+ if (!options.yes) throw new CliError(`Refusing to delete collection ${positionals[0]} without --yes.`, 2);
4606
+ await requestJson(ctx, `${cols(org)}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
4607
+ writeLine(ctx.io.stdout, `Deleted collection ${positionals[0]}.`);
4608
+ return 0;
4609
+ }
4610
+ default:
4611
+ throw new CliError(`Unknown kb collections command: ${sub}`);
4612
+ }
4613
+ }
4614
+ async function kbDocs(ctx, argv) {
4615
+ const sub = argv[0] ?? "list";
4616
+ const args = argv.slice(["list", "get", "add", "delete"].includes(sub) ? 1 : 0);
4617
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--title", "--text"] });
4618
+ if (options.help) {
4619
+ write(ctx.io.stdout, KB_HELP);
4620
+ return 0;
4621
+ }
4622
+ const org = requireOrg(options.org);
4623
+ const collectionId = positionals[0];
4624
+ if (!collectionId) {
4625
+ write(ctx.io.stderr, "kb docs commands need a <collectionId>.\n");
4626
+ return 2;
4627
+ }
4628
+ const docsUrl = `${cols(org)}/${encodeURIComponent(collectionId)}/documents`;
4629
+ switch (sub) {
4630
+ case "list": {
4631
+ const res = await requestJson(ctx, docsUrl);
4632
+ if (ctx.json) {
4633
+ writeJson(ctx.io.stdout, res.body);
4634
+ return 0;
4635
+ }
4636
+ const items = Array.isArray(res.body?.documents) ? res.body.documents : [];
4637
+ if (items.length === 0) {
4638
+ writeLine(ctx.io.stdout, "No documents.");
4639
+ return 0;
4640
+ }
4641
+ writeLine(ctx.io.stdout, formatTable(items.map((d) => ({ id: d.id ?? d.documentId ?? "", title: d.title ?? "" })), ["id", "title"]));
4642
+ return 0;
4643
+ }
4644
+ case "get": {
4645
+ if (positionals.length !== 2) {
4646
+ write(ctx.io.stderr, "Usage: openwop kb docs get <collectionId> <documentId> --org <orgId>\n");
4647
+ return 2;
4648
+ }
4649
+ const res = await requestJson(ctx, `${docsUrl}/${encodeURIComponent(positionals[1])}`);
4650
+ writeJson(ctx.io.stdout, res.body);
4651
+ return 0;
4652
+ }
4653
+ case "add": {
4654
+ if (!options.title || !options.text) {
4655
+ write(ctx.io.stderr, "kb docs add needs --title and --text.\n");
4656
+ return 2;
4657
+ }
4658
+ const res = await requestJson(ctx, docsUrl, { method: "POST", body: { title: String(options.title), text: String(options.text) } });
4659
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4660
+ else writeLine(ctx.io.stdout, `Added document ${res.body?.id ?? ""} to ${collectionId}.`);
4661
+ return 0;
4662
+ }
4663
+ case "delete": {
4664
+ if (positionals.length !== 2) {
4665
+ write(ctx.io.stderr, "Usage: openwop kb docs delete <collectionId> <documentId> --org <orgId> [--yes]\n");
4666
+ return 2;
4667
+ }
4668
+ if (!options.yes) throw new CliError(`Refusing to delete document ${positionals[1]} without --yes.`, 2);
4669
+ await requestJson(ctx, `${docsUrl}/${encodeURIComponent(positionals[1])}`, { method: "DELETE" });
4670
+ writeLine(ctx.io.stdout, `Deleted document ${positionals[1]}.`);
4671
+ return 0;
4672
+ }
4673
+ default:
4674
+ throw new CliError(`Unknown kb docs command: ${sub}`);
4675
+ }
4676
+ }
4677
+ async function kbSearch(ctx, argv, kind) {
4678
+ const { options, positionals } = parseOptions(argv, { value: ["--org", "--query", "--top-k"] });
4679
+ const org = requireOrg(options.org);
4680
+ if (positionals.length !== 1 || !options.query) {
4681
+ write(ctx.io.stderr, `Usage: openwop kb ${kind} <collectionId> --org <orgId> --query <q> [--top-k <n>] [--json]
4682
+ `);
4683
+ return 2;
4684
+ }
4685
+ const body = { query: String(options.query) };
4686
+ if (options.topK !== void 0) body.topK = Number(options.topK);
4687
+ const res = await requestJson(ctx, `${cols(org)}/${encodeURIComponent(positionals[0])}/${kind}`, { method: "POST", body });
4688
+ writeJson(ctx.io.stdout, res.body);
4689
+ return 0;
4690
+ }
4691
+
4692
+ // src/cli/cms.ts
4693
+ var pages = (org) => `/v1/host/sample/cms/orgs/${encodeURIComponent(org)}/pages`;
4694
+ var LIFECYCLE = ["submit", "approve", "reject", "publish", "unpublish", "archive"];
4695
+ var CMS_HELP = `Usage:
4696
+ openwop cms pages list --org <orgId> [--json]
4697
+ openwop cms pages get <pageId> --org <orgId> [--json]
4698
+ openwop cms pages by-slug <slug> --org <orgId> [--json]
4699
+ openwop cms pages create --org <orgId> --title <t> [--slug <s>] [--json]
4700
+ openwop cms pages update <pageId> --org <orgId> [--title <t>] [--slug <s>] [--json]
4701
+ openwop cms pages delete <pageId> --org <orgId> [--yes]
4702
+ openwop cms pages versions <pageId> --org <orgId> [--json]
4703
+ openwop cms <submit|approve|reject|publish|unpublish|archive> <pageId> --org <orgId> [--json]
4704
+
4705
+ CMS pages + the authoring lifecycle (host-extension, org-scoped, ADR 0027). A page
4706
+ moves draft \u2192 submit \u2192 approve \u2192 publish; \`versions\` lists its history. Every command
4707
+ needs --org. The host is the authority; the CLI mirrors + relays.
4708
+ `;
4709
+ async function runCms(ctx, argv) {
4710
+ const sub = argv[0] ?? "pages";
4711
+ if (sub === "--help" || sub === "-h") {
4712
+ write(ctx.io.stdout, CMS_HELP);
4713
+ return 0;
4714
+ }
4715
+ if (sub === "pages") return cmsPages(ctx, argv.slice(1));
4716
+ if (LIFECYCLE.includes(sub)) return cmsLifecycle(ctx, sub, argv.slice(1));
4717
+ throw new CliError(`Unknown cms command: ${sub}
4718
+ Run \`openwop cms --help\` for usage.`);
4719
+ }
4720
+ async function cmsPages(ctx, argv) {
4721
+ const sub = argv[0] ?? "list";
4722
+ const args = argv.slice(["list", "get", "by-slug", "create", "update", "delete", "versions"].includes(sub) ? 1 : 0);
4723
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--title", "--slug"] });
4724
+ if (options.help) {
4725
+ write(ctx.io.stdout, CMS_HELP);
4726
+ return 0;
4727
+ }
4728
+ const org = requireOrg(options.org);
4729
+ const url = pages(org);
4730
+ switch (sub) {
4731
+ case "list": {
4732
+ const res = await requestJson(ctx, url);
4733
+ if (ctx.json) {
4734
+ writeJson(ctx.io.stdout, res.body);
4735
+ return 0;
4736
+ }
4737
+ const items = Array.isArray(res.body?.pages) ? res.body.pages : [];
4738
+ if (items.length === 0) {
4739
+ writeLine(ctx.io.stdout, "No pages.");
4740
+ return 0;
4741
+ }
4742
+ writeLine(ctx.io.stdout, formatTable(items.map((p) => ({ id: p.id ?? p.pageId ?? "", title: p.title ?? "", slug: p.slug ?? "", status: p.status ?? "" })), ["id", "title", "slug", "status"]));
4743
+ return 0;
4744
+ }
4745
+ case "get": {
4746
+ if (positionals.length !== 1) {
4747
+ write(ctx.io.stderr, "Usage: openwop cms pages get <pageId> --org <orgId>\n");
4748
+ return 2;
4749
+ }
4750
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`);
4751
+ writeJson(ctx.io.stdout, res.body);
4752
+ return 0;
4753
+ }
4754
+ case "by-slug": {
4755
+ if (positionals.length !== 1) {
4756
+ write(ctx.io.stderr, "Usage: openwop cms pages by-slug <slug> --org <orgId>\n");
4757
+ return 2;
4758
+ }
4759
+ const res = await requestJson(ctx, `${url}/by-slug/${encodeURIComponent(positionals[0])}`);
4760
+ writeJson(ctx.io.stdout, res.body);
4761
+ return 0;
4762
+ }
4763
+ case "create": {
4764
+ if (!options.title) {
4765
+ write(ctx.io.stderr, "cms pages create needs --title.\n");
4766
+ return 2;
4767
+ }
4768
+ const body = { title: String(options.title) };
4769
+ if (options.slug) body.slug = String(options.slug);
4770
+ const res = await requestJson(ctx, url, { method: "POST", body });
4771
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4772
+ else writeLine(ctx.io.stdout, `Created page ${res.body?.id ?? ""} (${String(options.title)}).`);
4773
+ return 0;
4774
+ }
4775
+ case "update": {
4776
+ if (positionals.length !== 1) {
4777
+ write(ctx.io.stderr, "Usage: openwop cms pages update <pageId> --org <orgId> [--title t] [--slug s]\n");
4778
+ return 2;
4779
+ }
4780
+ const patch = {};
4781
+ if (options.title) patch.title = String(options.title);
4782
+ if (options.slug) patch.slug = String(options.slug);
4783
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body: patch });
4784
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4785
+ else writeLine(ctx.io.stdout, `Updated page ${positionals[0]}.`);
4786
+ return 0;
4787
+ }
4788
+ case "delete": {
4789
+ if (positionals.length !== 1) {
4790
+ write(ctx.io.stderr, "Usage: openwop cms pages delete <pageId> --org <orgId> [--yes]\n");
4791
+ return 2;
4792
+ }
4793
+ if (!options.yes) throw new CliError(`Refusing to delete page ${positionals[0]} without --yes.`, 2);
4794
+ await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
4795
+ writeLine(ctx.io.stdout, `Deleted page ${positionals[0]}.`);
4796
+ return 0;
4797
+ }
4798
+ case "versions": {
4799
+ if (positionals.length !== 1) {
4800
+ write(ctx.io.stderr, "Usage: openwop cms pages versions <pageId> --org <orgId>\n");
4801
+ return 2;
4802
+ }
4803
+ const res = await requestJson(ctx, `${url}/${encodeURIComponent(positionals[0])}/versions`);
4804
+ writeJson(ctx.io.stdout, res.body);
4805
+ return 0;
4806
+ }
4807
+ default:
4808
+ throw new CliError(`Unknown cms pages command: ${sub}`);
4809
+ }
4810
+ }
4811
+ async function cmsLifecycle(ctx, action, argv) {
4812
+ const { options, positionals } = parseOptions(argv, { value: ["--org"] });
4813
+ const org = requireOrg(options.org);
4814
+ if (positionals.length !== 1) {
4815
+ write(ctx.io.stderr, `Usage: openwop cms ${action} <pageId> --org <orgId> [--json]
4816
+ `);
4817
+ return 2;
4818
+ }
4819
+ const res = await requestJson(ctx, `${pages(org)}/${encodeURIComponent(positionals[0])}/${action}`, { method: "POST", body: {} });
4820
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4821
+ else writeLine(ctx.io.stdout, `Page ${positionals[0]} \u2192 ${action}.`);
4822
+ return 0;
4823
+ }
4824
+
4825
+ // src/cli/documents.ts
4826
+ var base6 = (org) => `/v1/host/sample/documents/orgs/${encodeURIComponent(org)}`;
4827
+ var DOCUMENTS_HELP = `Usage:
4828
+ openwop documents list --org <orgId> [--json]
4829
+ openwop documents get <documentId> --org <orgId> [--json]
4830
+ openwop documents create --org <orgId> --title <t> --kind <k> [--format <f>] [--json]
4831
+ openwop documents update <documentId> --org <orgId> [--title <t>] [--json]
4832
+ openwop documents delete <documentId> --org <orgId> [--yes]
4833
+ openwop documents versions <documentId> --org <orgId> [--json]
4834
+ openwop documents render <documentId> --org <orgId> [--json]
4835
+ openwop documents templates list --org <orgId> [--json]
4836
+ openwop documents templates get <templateId> --org <orgId> [--json]
4837
+ openwop documents templates create --org <orgId> --name <n> --kind <k> [--output-format <f>] [--json]
4838
+ openwop documents templates delete <templateId> --org <orgId> [--yes]
4839
+
4840
+ Document generation + templates (host-extension, org-scoped). A document has a
4841
+ title/kind/format and versioned content; \`render\` produces its output; templates
4842
+ drive assembly. Every command needs --org. The host is the authority; the CLI relays.
4843
+ `;
4844
+ async function runDocuments(ctx, argv) {
4845
+ const sub = argv[0] ?? "list";
4846
+ if (sub === "--help" || sub === "-h") {
4847
+ write(ctx.io.stdout, DOCUMENTS_HELP);
4848
+ return 0;
4849
+ }
4850
+ if (sub === "templates") return docTemplates(ctx, argv.slice(1));
4851
+ const args = argv.slice(["list", "get", "create", "update", "delete", "versions", "render"].includes(sub) ? 1 : 0);
4852
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--title", "--kind", "--format"] });
4853
+ if (options.help) {
4854
+ write(ctx.io.stdout, DOCUMENTS_HELP);
4855
+ return 0;
4856
+ }
4857
+ const org = requireOrg(options.org);
4858
+ const docs = `${base6(org)}/documents`;
4859
+ const id = positionals[0];
4860
+ switch (sub) {
4861
+ case "list": {
4862
+ const res = await requestJson(ctx, docs);
4863
+ if (ctx.json) {
4864
+ writeJson(ctx.io.stdout, res.body);
4865
+ return 0;
4866
+ }
4867
+ const items = Array.isArray(res.body?.documents) ? res.body.documents : [];
4868
+ if (items.length === 0) {
4869
+ writeLine(ctx.io.stdout, "No documents.");
4870
+ return 0;
4871
+ }
4872
+ writeLine(ctx.io.stdout, formatTable(items.map((d) => ({ id: d.id ?? "", title: d.title ?? "", kind: d.kind ?? "", format: d.format ?? "" })), ["id", "title", "kind", "format"]));
4873
+ return 0;
4874
+ }
4875
+ case "get": {
4876
+ if (!id) {
4877
+ write(ctx.io.stderr, "Usage: openwop documents get <documentId> --org <orgId>\n");
4878
+ return 2;
4879
+ }
4880
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${docs}/${encodeURIComponent(id)}`)).body);
4881
+ return 0;
4882
+ }
4883
+ case "create": {
4884
+ if (!options.title || !options.kind) {
4885
+ write(ctx.io.stderr, "documents create needs --title and --kind.\n");
4886
+ return 2;
4887
+ }
4888
+ const body = { title: String(options.title), kind: String(options.kind) };
4889
+ if (options.format) body.format = String(options.format);
4890
+ const res = await requestJson(ctx, docs, { method: "POST", body });
4891
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4892
+ else writeLine(ctx.io.stdout, `Created document ${res.body?.id ?? ""} (${String(options.title)}).`);
4893
+ return 0;
4894
+ }
4895
+ case "update": {
4896
+ if (!id) {
4897
+ write(ctx.io.stderr, "Usage: openwop documents update <documentId> --org <orgId> [--title t]\n");
4898
+ return 2;
4899
+ }
4900
+ const patch = {};
4901
+ if (options.title) patch.title = String(options.title);
4902
+ const res = await requestJson(ctx, `${docs}/${encodeURIComponent(id)}`, { method: "PATCH", body: patch });
4903
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4904
+ else writeLine(ctx.io.stdout, `Updated document ${id}.`);
4905
+ return 0;
4906
+ }
4907
+ case "delete": {
4908
+ if (!id) {
4909
+ write(ctx.io.stderr, "Usage: openwop documents delete <documentId> --org <orgId> [--yes]\n");
4910
+ return 2;
4911
+ }
4912
+ if (!options.yes) throw new CliError(`Refusing to delete document ${id} without --yes.`, 2);
4913
+ await requestJson(ctx, `${docs}/${encodeURIComponent(id)}`, { method: "DELETE" });
4914
+ writeLine(ctx.io.stdout, `Deleted document ${id}.`);
4915
+ return 0;
4916
+ }
4917
+ case "versions": {
4918
+ if (!id) {
4919
+ write(ctx.io.stderr, "Usage: openwop documents versions <documentId> --org <orgId>\n");
4920
+ return 2;
4921
+ }
4922
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${docs}/${encodeURIComponent(id)}/versions`)).body);
4923
+ return 0;
4924
+ }
4925
+ case "render": {
4926
+ if (!id) {
4927
+ write(ctx.io.stderr, "Usage: openwop documents render <documentId> --org <orgId>\n");
4928
+ return 2;
4929
+ }
4930
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${docs}/${encodeURIComponent(id)}/render`, { method: "POST", body: {} })).body);
4931
+ return 0;
4932
+ }
4933
+ default:
4934
+ throw new CliError(`Unknown documents command: ${sub}`);
4935
+ }
4936
+ }
4937
+ async function docTemplates(ctx, argv) {
4938
+ const sub = argv[0] ?? "list";
4939
+ const args = argv.slice(["list", "get", "create", "delete"].includes(sub) ? 1 : 0);
4940
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name", "--kind", "--output-format"] });
4941
+ if (options.help) {
4942
+ write(ctx.io.stdout, DOCUMENTS_HELP);
4943
+ return 0;
4944
+ }
4945
+ const org = requireOrg(options.org);
4946
+ const url = `${base6(org)}/templates`;
4947
+ const id = positionals[0];
4948
+ switch (sub) {
4949
+ case "list": {
4950
+ const res = await requestJson(ctx, url);
4951
+ if (ctx.json) {
4952
+ writeJson(ctx.io.stdout, res.body);
4953
+ return 0;
4954
+ }
4955
+ const items = Array.isArray(res.body?.templates) ? res.body.templates : [];
4956
+ if (items.length === 0) {
4957
+ writeLine(ctx.io.stdout, "No templates.");
4958
+ return 0;
4959
+ }
4960
+ writeLine(ctx.io.stdout, formatTable(items.map((t) => ({ id: t.id ?? "", name: t.name ?? "", kind: t.kind ?? "" })), ["id", "name", "kind"]));
4961
+ return 0;
4962
+ }
4963
+ case "get": {
4964
+ if (!id) {
4965
+ write(ctx.io.stderr, "Usage: openwop documents templates get <templateId> --org <orgId>\n");
4966
+ return 2;
4967
+ }
4968
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${url}/${encodeURIComponent(id)}`)).body);
4969
+ return 0;
4970
+ }
4971
+ case "create": {
4972
+ if (!options.name || !options.kind) {
4973
+ write(ctx.io.stderr, "documents templates create needs --name and --kind.\n");
4974
+ return 2;
4975
+ }
4976
+ const body = { name: String(options.name), kind: String(options.kind) };
4977
+ if (options.outputFormat) body.outputFormat = String(options.outputFormat);
4978
+ const res = await requestJson(ctx, url, { method: "POST", body });
4979
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
4980
+ else writeLine(ctx.io.stdout, `Created template ${res.body?.id ?? ""} (${String(options.name)}).`);
4981
+ return 0;
4982
+ }
4983
+ case "delete": {
4984
+ if (!id) {
4985
+ write(ctx.io.stderr, "Usage: openwop documents templates delete <templateId> --org <orgId> [--yes]\n");
4986
+ return 2;
4987
+ }
4988
+ if (!options.yes) throw new CliError(`Refusing to delete template ${id} without --yes.`, 2);
4989
+ await requestJson(ctx, `${url}/${encodeURIComponent(id)}`, { method: "DELETE" });
4990
+ writeLine(ctx.io.stdout, `Deleted template ${id}.`);
4991
+ return 0;
4992
+ }
4993
+ default:
4994
+ throw new CliError(`Unknown documents templates command: ${sub}`);
4995
+ }
4996
+ }
4997
+
4998
+ // src/cli/projects.ts
4999
+ var BASE3 = "/v1/host/sample/projects";
5000
+ var PROJECTS_HELP = `Usage:
5001
+ openwop projects list [--json]
5002
+ openwop projects get <projectId> [--json]
5003
+ openwop projects create --org <orgId> --name <n> [--json]
5004
+ openwop projects update <projectId> [--name <n>] [--json]
5005
+ openwop projects delete <projectId> [--yes]
5006
+ openwop projects members list <projectId> [--json]
5007
+ openwop projects members add <projectId> --ref <subjectRef> [--json]
5008
+ openwop projects members remove <projectId> <ref> [--yes]
5009
+
5010
+ Project workspaces (host-extension). A project scopes members + knowledge + schedules;
5011
+ \`create\` needs the owning --org. The host is the authority; the CLI mirrors + relays.
5012
+ `;
5013
+ async function runProjects(ctx, argv) {
5014
+ const sub = argv[0] ?? "list";
5015
+ if (sub === "--help" || sub === "-h") {
5016
+ write(ctx.io.stdout, PROJECTS_HELP);
5017
+ return 0;
5018
+ }
5019
+ if (sub === "members") return projectMembers(ctx, argv.slice(1));
5020
+ const args = argv.slice(["list", "get", "create", "update", "delete"].includes(sub) ? 1 : 0);
5021
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name"] });
5022
+ if (options.help) {
5023
+ write(ctx.io.stdout, PROJECTS_HELP);
5024
+ return 0;
5025
+ }
5026
+ const id = positionals[0];
5027
+ switch (sub) {
5028
+ case "list": {
5029
+ const res = await requestJson(ctx, BASE3);
5030
+ if (ctx.json) {
5031
+ writeJson(ctx.io.stdout, res.body);
5032
+ return 0;
5033
+ }
5034
+ const items = Array.isArray(res.body?.projects) ? res.body.projects : [];
5035
+ if (items.length === 0) {
5036
+ writeLine(ctx.io.stdout, "No projects.");
5037
+ return 0;
5038
+ }
5039
+ writeLine(ctx.io.stdout, formatTable(items.map((p) => ({ id: p.id ?? "", name: p.name ?? "", org: p.orgId ?? "" })), ["id", "name", "org"]));
5040
+ return 0;
5041
+ }
5042
+ case "get": {
5043
+ if (!id) {
5044
+ write(ctx.io.stderr, "Usage: openwop projects get <projectId>\n");
5045
+ return 2;
5046
+ }
5047
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE3}/${encodeURIComponent(id)}`)).body);
5048
+ return 0;
5049
+ }
5050
+ case "create": {
5051
+ if (!options.org || !options.name) {
5052
+ write(ctx.io.stderr, "projects create needs --org and --name.\n");
5053
+ return 2;
5054
+ }
5055
+ const res = await requestJson(ctx, BASE3, { method: "POST", body: { orgId: String(options.org), name: String(options.name) } });
5056
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5057
+ else writeLine(ctx.io.stdout, `Created project ${res.body?.id ?? ""} (${String(options.name)}).`);
5058
+ return 0;
5059
+ }
5060
+ case "update": {
5061
+ if (!id) {
5062
+ write(ctx.io.stderr, "Usage: openwop projects update <projectId> [--name n]\n");
5063
+ return 2;
5064
+ }
5065
+ const patch = {};
5066
+ if (options.name) patch.name = String(options.name);
5067
+ const res = await requestJson(ctx, `${BASE3}/${encodeURIComponent(id)}`, { method: "PATCH", body: patch });
5068
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5069
+ else writeLine(ctx.io.stdout, `Updated project ${id}.`);
5070
+ return 0;
5071
+ }
5072
+ case "delete": {
5073
+ if (!id) {
5074
+ write(ctx.io.stderr, "Usage: openwop projects delete <projectId> [--yes]\n");
5075
+ return 2;
5076
+ }
5077
+ if (!options.yes) throw new CliError(`Refusing to delete project ${id} without --yes.`, 2);
5078
+ await requestJson(ctx, `${BASE3}/${encodeURIComponent(id)}`, { method: "DELETE" });
5079
+ writeLine(ctx.io.stdout, `Deleted project ${id}.`);
5080
+ return 0;
5081
+ }
5082
+ default:
5083
+ throw new CliError(`Unknown projects command: ${sub}`);
5084
+ }
5085
+ }
5086
+ async function projectMembers(ctx, argv) {
5087
+ const sub = argv[0] ?? "list";
5088
+ const args = argv.slice(["list", "add", "remove"].includes(sub) ? 1 : 0);
5089
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--ref"] });
5090
+ if (options.help) {
5091
+ write(ctx.io.stdout, PROJECTS_HELP);
5092
+ return 0;
5093
+ }
5094
+ const projectId = positionals[0];
5095
+ if (!projectId) {
5096
+ write(ctx.io.stderr, "projects members commands need a <projectId>.\n");
5097
+ return 2;
5098
+ }
5099
+ const url = `${BASE3}/${encodeURIComponent(projectId)}/members`;
5100
+ switch (sub) {
5101
+ case "list": {
5102
+ const res = await requestJson(ctx, url);
5103
+ if (ctx.json) {
5104
+ writeJson(ctx.io.stdout, res.body);
5105
+ return 0;
5106
+ }
5107
+ const items = Array.isArray(res.body?.members) ? res.body.members : [];
5108
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((m) => ({ ref: m.ref ?? m.subject ?? "", role: m.role ?? "" })), ["ref", "role"]) : "No members.");
5109
+ return 0;
5110
+ }
5111
+ case "add": {
5112
+ if (!options.ref) {
5113
+ write(ctx.io.stderr, "projects members add needs --ref <subjectRef>.\n");
5114
+ return 2;
5115
+ }
5116
+ const res = await requestJson(ctx, url, { method: "POST", body: { ref: String(options.ref) } });
5117
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5118
+ else writeLine(ctx.io.stdout, `Added member ${String(options.ref)} to ${projectId}.`);
5119
+ return 0;
5120
+ }
5121
+ case "remove": {
5122
+ if (positionals.length !== 2) {
5123
+ write(ctx.io.stderr, "Usage: openwop projects members remove <projectId> <ref> [--yes]\n");
5124
+ return 2;
5125
+ }
5126
+ if (!options.yes) throw new CliError(`Refusing to remove member ${positionals[1]} without --yes.`, 2);
5127
+ await requestJson(ctx, `${url}/${encodeURIComponent(positionals[1])}`, { method: "DELETE" });
5128
+ writeLine(ctx.io.stdout, `Removed member ${positionals[1]}.`);
5129
+ return 0;
5130
+ }
5131
+ default:
5132
+ throw new CliError(`Unknown projects members command: ${sub}`);
5133
+ }
5134
+ }
5135
+
5136
+ // src/cli/reviews.ts
5137
+ var BASE4 = "/v1/host/sample/reviews";
5138
+ var REVIEWS_HELP = `Usage:
5139
+ openwop reviews list [--status <s>] [--json]
5140
+ openwop reviews get <reviewId> [--json]
5141
+ openwop reviews action <reviewId> <action> [--note <t>] [--json]
5142
+
5143
+ The unified review inbox (ADR 0068 / RFC 0070). Reviewers act on a review via a named
5144
+ <action> (e.g. approve | reject | comment) that the host defines + quorums. The host is
5145
+ the authority; the CLI renders its resolved view and relays the action. Exit: 0 resolved
5146
+ \xB7 3 pending \xB7 1 rejected/error.`;
5147
+ async function runReviews(ctx, argv) {
5148
+ const sub = argv[0] ?? "list";
5149
+ if (sub === "--help" || sub === "-h") {
5150
+ write(ctx.io.stdout, REVIEWS_HELP);
5151
+ return 0;
5152
+ }
5153
+ const args = argv.slice(["list", "get", "action"].includes(sub) ? 1 : 0);
5154
+ const { options, positionals } = parseOptions(args, { bool: ["--help"], value: ["--status", "--note"] });
5155
+ if (options.help) {
5156
+ write(ctx.io.stdout, REVIEWS_HELP);
5157
+ return 0;
5158
+ }
5159
+ switch (sub) {
5160
+ case "list": {
5161
+ const q = options.status ? `?status=${encodeURIComponent(String(options.status))}` : "";
5162
+ const res = await requestJson(ctx, `${BASE4}${q}`);
5163
+ if (ctx.json) {
5164
+ writeJson(ctx.io.stdout, res.body);
5165
+ return 0;
5166
+ }
5167
+ const items = Array.isArray(res.body?.reviews) ? res.body.reviews : [];
5168
+ if (items.length === 0) {
5169
+ writeLine(ctx.io.stdout, "No reviews.");
5170
+ return 0;
5171
+ }
5172
+ writeLine(ctx.io.stdout, formatTable(items.map((r) => ({ id: r.reviewId ?? r.id ?? "", status: r.status ?? "", kind: r.kind ?? "", title: (r.title ?? "").slice(0, 50) })), ["id", "status", "kind", "title"]));
5173
+ return 0;
5174
+ }
5175
+ case "get": {
5176
+ if (positionals.length !== 1) {
5177
+ write(ctx.io.stderr, "Usage: openwop reviews get <reviewId>\n");
5178
+ return 2;
5179
+ }
5180
+ const res = await requestJson(ctx, `${BASE4}/${encodeURIComponent(positionals[0])}`);
5181
+ if (ctx.json) {
5182
+ writeJson(ctx.io.stdout, res.body);
5183
+ return 0;
5184
+ }
5185
+ writeJson(ctx.io.stdout, res.body);
5186
+ const st = res.body?.status;
5187
+ return st === "approved" || st === "resolved" ? 0 : st === "rejected" ? 1 : 3;
5188
+ }
5189
+ case "action": {
5190
+ if (positionals.length !== 2) {
5191
+ write(ctx.io.stderr, "Usage: openwop reviews action <reviewId> <action> [--note <t>] [--json]\n");
5192
+ return 2;
5193
+ }
5194
+ const body = options.note ? { note: String(options.note) } : {};
5195
+ const res = await requestJson(ctx, `${BASE4}/${encodeURIComponent(positionals[0])}/actions/${encodeURIComponent(positionals[1])}`, { method: "POST", body });
5196
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5197
+ else writeLine(ctx.io.stdout, `Applied action '${positionals[1]}' to review ${positionals[0]}.`);
5198
+ return 0;
5199
+ }
5200
+ default:
5201
+ throw new CliError(`Unknown reviews command: ${sub}
5202
+ Run \`openwop reviews --help\` for usage.`);
5203
+ }
5204
+ }
5205
+
5206
+ // src/cli/workspacesTenancy.ts
5207
+ var WORKSPACES_HELP = `Usage:
5208
+ openwop workspaces list [--json]
5209
+ openwop workspaces create --name <n> [--json]
5210
+ openwop workspaces switch <workspaceId> [--json]
5211
+
5212
+ Your B2B workspaces (each a tenant, ADR 0015). \`switch\` changes the active workspace.
5213
+ Distinct from \`workspace\` (singular) \u2014 the per-tenant agent FILE store.`;
5214
+ async function runWorkspaces(ctx, argv) {
5215
+ const sub = argv[0] ?? "list";
5216
+ if (sub === "--help" || sub === "-h") {
5217
+ write(ctx.io.stdout, WORKSPACES_HELP);
5218
+ return 0;
5219
+ }
5220
+ const args = argv.slice(["list", "create", "switch"].includes(sub) ? 1 : 0);
5221
+ const { options, positionals } = parseOptions(args, { bool: ["--help"], value: ["--name"] });
5222
+ if (options.help) {
5223
+ write(ctx.io.stdout, WORKSPACES_HELP);
5224
+ return 0;
5225
+ }
5226
+ switch (sub) {
5227
+ case "list": {
5228
+ const res = await requestJson(ctx, "/v1/host/sample/me/workspaces");
5229
+ if (ctx.json) {
5230
+ writeJson(ctx.io.stdout, res.body);
5231
+ return 0;
5232
+ }
5233
+ const items = Array.isArray(res.body?.workspaces) ? res.body.workspaces : [];
5234
+ if (items.length === 0) {
5235
+ writeLine(ctx.io.stdout, "No workspaces.");
5236
+ return 0;
5237
+ }
5238
+ writeLine(ctx.io.stdout, formatTable(items.map((w) => ({ id: w.id ?? w.workspaceId ?? "", name: w.name ?? "", active: w.active ? "yes" : "" })), ["id", "name", "active"]));
5239
+ return 0;
5240
+ }
5241
+ case "create": {
5242
+ if (!options.name) {
5243
+ write(ctx.io.stderr, "Usage: openwop workspaces create --name <n> [--json]\n");
5244
+ return 2;
5245
+ }
5246
+ const res = await requestJson(ctx, "/v1/host/sample/workspaces", { method: "POST", body: { name: String(options.name) } });
5247
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5248
+ else writeLine(ctx.io.stdout, `Created workspace ${res.body?.id ?? ""} (${String(options.name)}).`);
5249
+ return 0;
5250
+ }
5251
+ case "switch": {
5252
+ if (positionals.length !== 1) {
5253
+ write(ctx.io.stderr, "Usage: openwop workspaces switch <workspaceId> [--json]\n");
5254
+ return 2;
5255
+ }
5256
+ const res = await requestJson(ctx, `/v1/host/sample/workspaces/${encodeURIComponent(positionals[0])}/switch`, { method: "POST", body: {} });
5257
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5258
+ else writeLine(ctx.io.stdout, `Switched to workspace ${positionals[0]}.`);
5259
+ return 0;
5260
+ }
5261
+ default:
5262
+ throw new CliError(`Unknown workspaces command: ${sub}
5263
+ Run \`openwop workspaces --help\` for usage.`);
5264
+ }
5265
+ }
5266
+
5267
+ // src/cli/agentProfile.ts
5268
+ var prof = (id) => `/v1/host/sample/agents/${encodeURIComponent(id)}/profile`;
5269
+ var AGENT_PROFILE_HELP = `Usage:
5270
+ openwop agent-profile get <agentId> [--json]
5271
+ openwop agent-profile set <agentId> --profile-json '{...}' [--json]
5272
+ openwop agent-profile readiness <agentId> [--json]
5273
+
5274
+ An agent's rich profile + its connector readiness (ADR 0031). \`set\` PUTs the full
5275
+ profile object; \`readiness\` reports whether the connectors the agent needs are wired.`;
5276
+ async function runAgentProfile(ctx, argv) {
5277
+ const sub = argv[0] ?? "get";
5278
+ if (sub === "--help" || sub === "-h") {
5279
+ write(ctx.io.stdout, AGENT_PROFILE_HELP);
5280
+ return 0;
5281
+ }
5282
+ const args = argv.slice(["get", "set", "readiness"].includes(sub) ? 1 : 0);
5283
+ const { options, positionals } = parseOptions(args, { bool: ["--help"], value: ["--profile-json"] });
5284
+ if (options.help) {
5285
+ write(ctx.io.stdout, AGENT_PROFILE_HELP);
5286
+ return 0;
5287
+ }
5288
+ if (positionals.length !== 1) {
5289
+ write(ctx.io.stderr, `Usage: openwop agent-profile ${sub} <agentId>
5290
+ `);
5291
+ return 2;
5292
+ }
5293
+ const agentId = positionals[0];
5294
+ switch (sub) {
5295
+ case "get":
5296
+ writeJson(ctx.io.stdout, (await requestJson(ctx, prof(agentId))).body);
5297
+ return 0;
5298
+ case "readiness":
5299
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `/v1/host/sample/agents/${encodeURIComponent(agentId)}/connection-readiness`)).body);
5300
+ return 0;
5301
+ case "set": {
5302
+ if (!options.profileJson) {
5303
+ write(ctx.io.stderr, "agent-profile set needs --profile-json '{...}'.\n");
5304
+ return 2;
5305
+ }
5306
+ let profile;
5307
+ try {
5308
+ profile = JSON.parse(String(options.profileJson));
5309
+ } catch {
5310
+ throw new CliError("--profile-json must be valid JSON.", 2);
5311
+ }
5312
+ const res = await requestJson(ctx, prof(agentId), { method: "PUT", body: profile });
5313
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5314
+ else writeLine(ctx.io.stdout, `Updated profile for agent ${agentId}.`);
5315
+ return 0;
5316
+ }
5317
+ default:
5318
+ throw new CliError(`Unknown agent-profile command: ${sub}`);
5319
+ }
5320
+ }
5321
+
5322
+ // src/cli/agentAllowlists.ts
5323
+ var BASE5 = "/v1/host/sample/agent-allowlists/admin/agents";
5324
+ var AGENT_ALLOWLISTS_HELP = `Usage:
5325
+ openwop agent-allowlists list [--json]
5326
+ openwop agent-allowlists get <agentId> [--json]
5327
+ openwop agent-allowlists set <agentId> --allowlist-json '[...]' [--json]
5328
+ openwop agent-allowlists clear <agentId> [--yes]
5329
+
5330
+ Super-admin per-agent tool-allowlist overrides (ADR 0104). \`set\` PUTs the override
5331
+ (an array of tool ids); \`clear\` removes it. Requires super-admin (OPENWOP_SUPERADMIN_TENANTS).`;
5332
+ async function req(ctx, path, opts, action) {
5333
+ try {
5334
+ return await requestJson(ctx, path, opts);
5335
+ } catch (err) {
5336
+ if (err instanceof HttpError && (err.status === 401 || err.status === 403)) {
5337
+ throw new CliError(`${action} requires super-admin on this host (OPENWOP_SUPERADMIN_TENANTS).`, 4);
5338
+ }
5339
+ throw err;
5340
+ }
5341
+ }
5342
+ async function runAgentAllowlists(ctx, argv) {
5343
+ const sub = argv[0] ?? "list";
5344
+ if (sub === "--help" || sub === "-h") {
5345
+ write(ctx.io.stdout, AGENT_ALLOWLISTS_HELP);
5346
+ return 0;
5347
+ }
5348
+ const args = argv.slice(["list", "get", "set", "clear"].includes(sub) ? 1 : 0);
5349
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--allowlist-json"] });
5350
+ if (options.help) {
5351
+ write(ctx.io.stdout, AGENT_ALLOWLISTS_HELP);
5352
+ return 0;
5353
+ }
5354
+ switch (sub) {
5355
+ case "list": {
5356
+ const res = await req(ctx, BASE5, {}, "Reading agent allowlists");
5357
+ if (ctx.json) {
5358
+ writeJson(ctx.io.stdout, res.body);
5359
+ return 0;
5360
+ }
5361
+ const items = Array.isArray(res.body?.agents) ? res.body.agents : [];
5362
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((a) => ({ agentId: a.agentId ?? a.id ?? "", tools: Array.isArray(a.allowlist) ? a.allowlist.length : "" })), ["agentId", "tools"]) : "No allowlist overrides.");
5363
+ return 0;
5364
+ }
5365
+ case "get": {
5366
+ if (positionals.length !== 1) {
5367
+ write(ctx.io.stderr, "Usage: openwop agent-allowlists get <agentId>\n");
5368
+ return 2;
5369
+ }
5370
+ writeJson(ctx.io.stdout, (await req(ctx, `${BASE5}/${encodeURIComponent(positionals[0])}`, {}, "Reading the agent allowlist")).body);
5371
+ return 0;
5372
+ }
5373
+ case "set": {
5374
+ if (positionals.length !== 1 || !options.allowlistJson) {
5375
+ write(ctx.io.stderr, "Usage: openwop agent-allowlists set <agentId> --allowlist-json '[...]'\n");
5376
+ return 2;
5377
+ }
5378
+ let allowlist;
5379
+ try {
5380
+ allowlist = JSON.parse(String(options.allowlistJson));
5381
+ } catch {
5382
+ throw new CliError("--allowlist-json must be valid JSON.", 2);
5383
+ }
5384
+ const res = await req(ctx, `${BASE5}/${encodeURIComponent(positionals[0])}`, { method: "PUT", body: { allowlist } }, "Setting the agent allowlist");
5385
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5386
+ else writeLine(ctx.io.stdout, `Set allowlist override for ${positionals[0]}.`);
5387
+ return 0;
5388
+ }
5389
+ case "clear": {
5390
+ if (positionals.length !== 1) {
5391
+ write(ctx.io.stderr, "Usage: openwop agent-allowlists clear <agentId> [--yes]\n");
5392
+ return 2;
5393
+ }
5394
+ if (!options.yes) throw new CliError(`Refusing to clear the allowlist override for ${positionals[0]} without --yes.`, 2);
5395
+ await req(ctx, `${BASE5}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" }, "Clearing the agent allowlist");
5396
+ writeLine(ctx.io.stdout, `Cleared allowlist override for ${positionals[0]}.`);
5397
+ return 0;
5398
+ }
5399
+ default:
5400
+ throw new CliError(`Unknown agent-allowlists command: ${sub}`);
5401
+ }
5402
+ }
5403
+
5404
+ // src/cli/agentPacks.ts
5405
+ var BASE6 = "/v1/host/sample/registry/agent-packs";
5406
+ var AGENT_PACKS_HELP = `Usage:
5407
+ openwop agent-packs list [--json]
5408
+ openwop agent-packs install --name <core.openwop.agents.*> [--version <v>] [--json]
5409
+
5410
+ The agent (persona) pack registry. \`install\` provisions a signed agent pack (its name
5411
+ MUST start with 'core.openwop.agents.'). Distinct from the node-pack 'packs' registry.`;
5412
+ async function runAgentPacks(ctx, argv) {
5413
+ const sub = argv[0] ?? "list";
5414
+ if (sub === "--help" || sub === "-h") {
5415
+ write(ctx.io.stdout, AGENT_PACKS_HELP);
5416
+ return 0;
5417
+ }
5418
+ const args = argv.slice(["list", "install"].includes(sub) ? 1 : 0);
5419
+ const { options } = parseOptions(args, { bool: ["--help"], value: ["--name", "--version"] });
5420
+ if (options.help) {
5421
+ write(ctx.io.stdout, AGENT_PACKS_HELP);
5422
+ return 0;
5423
+ }
5424
+ switch (sub) {
5425
+ case "list": {
5426
+ const res = await requestJson(ctx, BASE6);
5427
+ if (ctx.json) {
5428
+ writeJson(ctx.io.stdout, res.body);
5429
+ return 0;
5430
+ }
5431
+ const items = Array.isArray(res.body?.packs) ? res.body.packs : Array.isArray(res.body) ? res.body : [];
5432
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((p) => ({ name: p.name ?? "", version: p.version ?? p.latestVersion ?? "", agents: Array.isArray(p.agents) ? p.agents.length : "" })), ["name", "version", "agents"]) : "No agent packs.");
5433
+ return 0;
5434
+ }
5435
+ case "install": {
5436
+ if (!options.name) {
5437
+ write(ctx.io.stderr, "Usage: openwop agent-packs install --name <core.openwop.agents.*> [--version <v>]\n");
5438
+ return 2;
5439
+ }
5440
+ const body = { name: String(options.name) };
5441
+ if (options.version) body.version = String(options.version);
5442
+ const res = await requestJson(ctx, `${BASE6}/install`, { method: "POST", body });
5443
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5444
+ else writeLine(ctx.io.stdout, `Installed agent pack ${String(options.name)}.`);
5445
+ return 0;
5446
+ }
5447
+ default:
5448
+ throw new CliError(`Unknown agent-packs command: ${sub}`);
5449
+ }
5450
+ }
5451
+
5452
+ // src/cli/agentOps.ts
5453
+ var H = "/v1/host/sample";
5454
+ var AGENT_OPS_HELP = `Usage:
5455
+ openwop agent-ops seed [--heal] [--json]
5456
+ openwop agent-ops status [--json]
5457
+ openwop agent-ops run [--step <id>]... [--dry-run] [--json]
5458
+ openwop agent-ops clear [--yes]
5459
+ openwop agent-ops roster-check <rosterId> [--json]
5460
+ openwop agent-ops roster-activity <rosterId> [--json]
5461
+ openwop agent-ops fleet-activity [--json]
5462
+
5463
+ Demo/operations helpers: seed/run/clear the example dataset + read roster & fleet
5464
+ activity. \`seed --heal\` repairs a partial seed; \`run --dry-run\` previews without writing.`;
5465
+ async function runAgentOps(ctx, argv) {
5466
+ const sub = argv[0] ?? "status";
5467
+ if (sub === "--help" || sub === "-h") {
5468
+ write(ctx.io.stdout, AGENT_OPS_HELP);
5469
+ return 0;
5470
+ }
5471
+ const args = argv.slice(["seed", "status", "run", "clear", "roster-check", "roster-activity", "fleet-activity"].includes(sub) ? 1 : 0);
5472
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--heal", "--dry-run", "--yes"], multi: ["--step"] });
5473
+ if (options.help) {
5474
+ write(ctx.io.stdout, AGENT_OPS_HELP);
5475
+ return 0;
5476
+ }
5477
+ const rid = positionals[0];
5478
+ switch (sub) {
5479
+ case "seed": {
5480
+ const res = await requestJson(ctx, `${H}/example-data/seed`, { method: "POST", body: options.heal ? { heal: true } : {} });
5481
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5482
+ else writeLine(ctx.io.stdout, "Seeded example data.");
5483
+ return 0;
5484
+ }
5485
+ case "status":
5486
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${H}/example-data/status`)).body);
5487
+ return 0;
5488
+ case "run": {
5489
+ const body = {};
5490
+ if (Array.isArray(options.step) && options.step.length) body.steps = options.step;
5491
+ if (options.dryRun) body.dryRun = true;
5492
+ const res = await requestJson(ctx, `${H}/example-data/run`, { method: "POST", body });
5493
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5494
+ else writeLine(ctx.io.stdout, options.dryRun ? "Dry-run complete." : "Ran example-data steps.");
5495
+ return 0;
5496
+ }
5497
+ case "clear": {
5498
+ if (!options.yes) throw new CliError("Refusing to clear example data without --yes.", 2);
5499
+ await requestJson(ctx, `${H}/example-data/clear`, { method: "POST", body: {} });
5500
+ writeLine(ctx.io.stdout, "Cleared example data.");
5501
+ return 0;
5502
+ }
5503
+ case "roster-check": {
5504
+ if (!rid) {
5505
+ write(ctx.io.stderr, "Usage: openwop agent-ops roster-check <rosterId>\n");
5506
+ return 2;
5507
+ }
5508
+ const res = await requestJson(ctx, `${H}/roster/${encodeURIComponent(rid)}/check`, { method: "POST", body: {} });
5509
+ writeJson(ctx.io.stdout, res.body);
5510
+ return 0;
5511
+ }
5512
+ case "roster-activity": {
5513
+ if (!rid) {
5514
+ write(ctx.io.stderr, "Usage: openwop agent-ops roster-activity <rosterId>\n");
5515
+ return 2;
5516
+ }
5517
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${H}/roster/${encodeURIComponent(rid)}/activity`)).body);
5518
+ return 0;
5519
+ }
5520
+ case "fleet-activity":
5521
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${H}/fleet/activity`)).body);
5522
+ return 0;
5523
+ default:
5524
+ throw new CliError(`Unknown agent-ops command: ${sub}`);
5525
+ }
5526
+ }
5527
+
5528
+ // src/cli/evals.ts
5529
+ var base7 = (org) => `/v1/host/sample/evals/orgs/${encodeURIComponent(org)}`;
5530
+ var EVALS_HELP = `Usage:
5531
+ openwop evals leaderboard --org <orgId> [--json]
5532
+ openwop evals rating --org <orgId> [--json]
5533
+ openwop evals match --org <orgId> --model-a <a> --model-b <b> --winner <a|b|tie> [--json]
5534
+
5535
+ Model evals (host-extension, org-scoped). \`leaderboard\`/\`rating\` read the standings;
5536
+ \`match\` records an arena head-to-head outcome. Every command needs --org.`;
5537
+ async function runEvals(ctx, argv) {
5538
+ const sub = argv[0] ?? "leaderboard";
5539
+ if (sub === "--help" || sub === "-h") {
5540
+ write(ctx.io.stdout, EVALS_HELP);
5541
+ return 0;
5542
+ }
5543
+ const args = argv.slice(["leaderboard", "rating", "match"].includes(sub) ? 1 : 0);
5544
+ const { options } = parseOptions(args, { bool: ["--help"], value: ["--org", "--model-a", "--model-b", "--winner"] });
5545
+ if (options.help) {
5546
+ write(ctx.io.stdout, EVALS_HELP);
5547
+ return 0;
5548
+ }
5549
+ const org = requireOrg(options.org);
5550
+ switch (sub) {
5551
+ case "leaderboard":
5552
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${base7(org)}/leaderboard`)).body);
5553
+ return 0;
5554
+ case "rating":
5555
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${base7(org)}/arena/rating`)).body);
5556
+ return 0;
5557
+ case "match": {
5558
+ if (!options.modelA || !options.modelB || !options.winner) {
5559
+ write(ctx.io.stderr, "evals match needs --model-a, --model-b, --winner.\n");
5560
+ return 2;
5561
+ }
5562
+ const res = await requestJson(ctx, `${base7(org)}/arena/match`, { method: "POST", body: { modelA: String(options.modelA), modelB: String(options.modelB), winner: String(options.winner) } });
5563
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5564
+ else writeLine(ctx.io.stdout, `Recorded match (${String(options.modelA)} vs ${String(options.modelB)} \u2192 ${String(options.winner)}).`);
5565
+ return 0;
5566
+ }
5567
+ default:
5568
+ throw new CliError(`Unknown evals command: ${sub}`);
5569
+ }
5570
+ }
5571
+
5572
+ // src/cli/twin.ts
5573
+ var twinPath = (id) => `/v1/host/sample/agents/${encodeURIComponent(id)}/twin`;
5574
+ var GRANTS = "/v1/host/sample/profiles/me/twin-grants";
5575
+ var TWIN_HELP = `Usage:
5576
+ openwop twin get <agentId> [--json]
5577
+ openwop twin set <agentId> --scopes <a,b,...> [--json]
5578
+ openwop twin clear <agentId> [--yes]
5579
+ openwop twin grants [--json]
5580
+ openwop twin grant --agent <agentId> --scopes <a,b,...> [--json]
5581
+ openwop twin revoke <agentId> [--yes]
5582
+
5583
+ An agent's digital-twin config + your twin GRANTS (which agents may act as your twin,
5584
+ with which scopes). \`set\` configures an agent's twin; \`grant\`/\`revoke\` manage grants.`;
5585
+ async function runTwin(ctx, argv) {
5586
+ const sub = argv[0] ?? "get";
5587
+ if (sub === "--help" || sub === "-h") {
5588
+ write(ctx.io.stdout, TWIN_HELP);
5589
+ return 0;
5590
+ }
5591
+ const args = argv.slice(["get", "set", "clear", "grants", "grant", "revoke"].includes(sub) ? 1 : 0);
5592
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--scopes", "--agent"] });
5593
+ if (options.help) {
5594
+ write(ctx.io.stdout, TWIN_HELP);
5595
+ return 0;
5596
+ }
5597
+ const scopes = options.scopes ? String(options.scopes).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
5598
+ switch (sub) {
5599
+ case "get": {
5600
+ if (!positionals[0]) {
5601
+ write(ctx.io.stderr, "Usage: openwop twin get <agentId>\n");
5602
+ return 2;
5603
+ }
5604
+ writeJson(ctx.io.stdout, (await requestJson(ctx, twinPath(positionals[0]))).body);
5605
+ return 0;
5606
+ }
5607
+ case "set": {
5608
+ if (!positionals[0] || !scopes) {
5609
+ write(ctx.io.stderr, "Usage: openwop twin set <agentId> --scopes <a,b,...>\n");
5610
+ return 2;
5611
+ }
5612
+ const res = await requestJson(ctx, twinPath(positionals[0]), { method: "PUT", body: { scopes } });
5613
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5614
+ else writeLine(ctx.io.stdout, `Configured twin for agent ${positionals[0]}.`);
5615
+ return 0;
5616
+ }
5617
+ case "clear": {
5618
+ if (!positionals[0]) {
5619
+ write(ctx.io.stderr, "Usage: openwop twin clear <agentId> [--yes]\n");
5620
+ return 2;
5621
+ }
5622
+ if (!options.yes) throw new CliError(`Refusing to clear the twin for ${positionals[0]} without --yes.`, 2);
5623
+ await requestJson(ctx, twinPath(positionals[0]), { method: "DELETE" });
5624
+ writeLine(ctx.io.stdout, `Cleared twin for ${positionals[0]}.`);
5625
+ return 0;
5626
+ }
5627
+ case "grants": {
5628
+ const res = await requestJson(ctx, GRANTS);
5629
+ if (ctx.json) {
5630
+ writeJson(ctx.io.stdout, res.body);
5631
+ return 0;
5632
+ }
5633
+ const items = Array.isArray(res.body?.grants) ? res.body.grants : [];
5634
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((g) => ({ agentId: g.agentId ?? "", scopes: Array.isArray(g.scopes) ? g.scopes.join(",") : "" })), ["agentId", "scopes"]) : "No twin grants.");
5635
+ return 0;
5636
+ }
5637
+ case "grant": {
5638
+ if (!options.agent || !scopes) {
5639
+ write(ctx.io.stderr, "Usage: openwop twin grant --agent <agentId> --scopes <a,b,...>\n");
5640
+ return 2;
5641
+ }
5642
+ const res = await requestJson(ctx, GRANTS, { method: "POST", body: { agentId: String(options.agent), scopes } });
5643
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5644
+ else writeLine(ctx.io.stdout, `Granted twin scope to ${String(options.agent)}.`);
5645
+ return 0;
5646
+ }
5647
+ case "revoke": {
5648
+ if (!positionals[0]) {
5649
+ write(ctx.io.stderr, "Usage: openwop twin revoke <agentId> [--yes]\n");
5650
+ return 2;
5651
+ }
5652
+ if (!options.yes) throw new CliError(`Refusing to revoke the twin grant for ${positionals[0]} without --yes.`, 2);
5653
+ await requestJson(ctx, `${GRANTS}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
5654
+ writeLine(ctx.io.stdout, `Revoked twin grant for ${positionals[0]}.`);
5655
+ return 0;
5656
+ }
5657
+ default:
5658
+ throw new CliError(`Unknown twin command: ${sub}`);
5659
+ }
5660
+ }
5661
+
5662
+ // src/cli/strategy.ts
5663
+ var BASE7 = "/v1/host/sample/strategy";
5664
+ var STRATEGY_HELP = `Usage:
5665
+ openwop strategy list [--json]
5666
+ openwop strategy get <strategyId> [--json]
5667
+ openwop strategy create --org <orgId> --title <t> [--json]
5668
+ openwop strategy update <strategyId> [--title <t>] [--json]
5669
+ openwop strategy delete <strategyId> [--yes]
5670
+ openwop strategy context [--json]
5671
+ openwop strategy health [--json]
5672
+
5673
+ Strategy documents (host-extension). \`context\`/\`health\` read the strategy context +
5674
+ its health. \`create\` needs the owning --org. The host is the authority; the CLI relays.`;
5675
+ async function runStrategy(ctx, argv) {
5676
+ const sub = argv[0] ?? "list";
5677
+ if (sub === "--help" || sub === "-h") {
5678
+ write(ctx.io.stdout, STRATEGY_HELP);
5679
+ return 0;
5680
+ }
5681
+ const args = argv.slice(["list", "get", "create", "update", "delete", "context", "health"].includes(sub) ? 1 : 0);
5682
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--title"] });
5683
+ if (options.help) {
5684
+ write(ctx.io.stdout, STRATEGY_HELP);
5685
+ return 0;
5686
+ }
5687
+ const id = positionals[0];
5688
+ switch (sub) {
5689
+ case "list": {
5690
+ const res = await requestJson(ctx, BASE7);
5691
+ if (ctx.json) {
5692
+ writeJson(ctx.io.stdout, res.body);
5693
+ return 0;
5694
+ }
5695
+ const items = Array.isArray(res.body?.strategies) ? res.body.strategies : [];
5696
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((s) => ({ id: s.id ?? "", title: s.title ?? "", org: s.orgId ?? "" })), ["id", "title", "org"]) : "No strategies.");
5697
+ return 0;
5698
+ }
5699
+ case "get": {
5700
+ if (!id) {
5701
+ write(ctx.io.stderr, "Usage: openwop strategy get <strategyId>\n");
5702
+ return 2;
5703
+ }
5704
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE7}/${encodeURIComponent(id)}`)).body);
5705
+ return 0;
5706
+ }
5707
+ case "context":
5708
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE7}/context`)).body);
5709
+ return 0;
5710
+ case "health":
5711
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE7}/health`)).body);
5712
+ return 0;
5713
+ case "create": {
5714
+ if (!options.org || !options.title) {
5715
+ write(ctx.io.stderr, "strategy create needs --org and --title.\n");
5716
+ return 2;
5717
+ }
5718
+ const res = await requestJson(ctx, BASE7, { method: "POST", body: { orgId: String(options.org), title: String(options.title) } });
5719
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5720
+ else writeLine(ctx.io.stdout, `Created strategy ${res.body?.id ?? ""} (${String(options.title)}).`);
5721
+ return 0;
5722
+ }
5723
+ case "update": {
5724
+ if (!id) {
5725
+ write(ctx.io.stderr, "Usage: openwop strategy update <strategyId> [--title t]\n");
5726
+ return 2;
5727
+ }
5728
+ const patch = {};
5729
+ if (options.title) patch.title = String(options.title);
5730
+ const res = await requestJson(ctx, `${BASE7}/${encodeURIComponent(id)}`, { method: "PATCH", body: patch });
5731
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5732
+ else writeLine(ctx.io.stdout, `Updated strategy ${id}.`);
5733
+ return 0;
5734
+ }
5735
+ case "delete": {
5736
+ if (!id) {
5737
+ write(ctx.io.stderr, "Usage: openwop strategy delete <strategyId> [--yes]\n");
5738
+ return 2;
5739
+ }
5740
+ if (!options.yes) throw new CliError(`Refusing to delete strategy ${id} without --yes.`, 2);
5741
+ await requestJson(ctx, `${BASE7}/${encodeURIComponent(id)}`, { method: "DELETE" });
5742
+ writeLine(ctx.io.stdout, `Deleted strategy ${id}.`);
5743
+ return 0;
5744
+ }
5745
+ default:
5746
+ throw new CliError(`Unknown strategy command: ${sub}`);
5747
+ }
5748
+ }
5749
+
5750
+ // src/cli/advisoryBoard.ts
5751
+ var BOARDS = "/v1/host/sample/advisors/boards";
5752
+ var ADVISORS_HELP = `Usage:
5753
+ openwop advisors list [--json]
5754
+ openwop advisors get <boardId> [--json]
5755
+ openwop advisors by-handle <handle> [--json]
5756
+ openwop advisors create --org <orgId> --name <n> [--handle <h>] [--json]
5757
+ openwop advisors delete <boardId> [--yes]
5758
+
5759
+ Advisory boards (host-extension) \u2014 a panel of advisor agents. \`create\` needs the
5760
+ owning --org. The host is the authority; the CLI mirrors + relays.`;
5761
+ async function runAdvisors(ctx, argv) {
5762
+ const sub = argv[0] ?? "list";
5763
+ if (sub === "--help" || sub === "-h") {
5764
+ write(ctx.io.stdout, ADVISORS_HELP);
5765
+ return 0;
5766
+ }
5767
+ const args = argv.slice(["list", "get", "by-handle", "create", "delete"].includes(sub) ? 1 : 0);
5768
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name", "--handle"] });
5769
+ if (options.help) {
5770
+ write(ctx.io.stdout, ADVISORS_HELP);
5771
+ return 0;
5772
+ }
5773
+ const id = positionals[0];
5774
+ switch (sub) {
5775
+ case "list": {
5776
+ const res = await requestJson(ctx, BOARDS);
5777
+ if (ctx.json) {
5778
+ writeJson(ctx.io.stdout, res.body);
5779
+ return 0;
5780
+ }
5781
+ const items = Array.isArray(res.body?.boards) ? res.body.boards : [];
5782
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((b) => ({ id: b.id ?? b.boardId ?? "", name: b.name ?? "", handle: b.handle ?? "" })), ["id", "name", "handle"]) : "No advisory boards.");
5783
+ return 0;
5784
+ }
5785
+ case "get": {
5786
+ if (!id) {
5787
+ write(ctx.io.stderr, "Usage: openwop advisors get <boardId>\n");
5788
+ return 2;
5789
+ }
5790
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BOARDS}/${encodeURIComponent(id)}`)).body);
5791
+ return 0;
5792
+ }
5793
+ case "by-handle": {
5794
+ if (!id) {
5795
+ write(ctx.io.stderr, "Usage: openwop advisors by-handle <handle>\n");
5796
+ return 2;
5797
+ }
5798
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BOARDS}/by-handle/${encodeURIComponent(id)}`)).body);
5799
+ return 0;
5800
+ }
5801
+ case "create": {
5802
+ if (!options.org || !options.name) {
5803
+ write(ctx.io.stderr, "advisors create needs --org and --name.\n");
5804
+ return 2;
5805
+ }
5806
+ const body = { orgId: String(options.org), name: String(options.name) };
5807
+ if (options.handle) body.handle = String(options.handle);
5808
+ const res = await requestJson(ctx, BOARDS, { method: "POST", body });
5809
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5810
+ else writeLine(ctx.io.stdout, `Created advisory board ${res.body?.id ?? ""} (${String(options.name)}).`);
5811
+ return 0;
5812
+ }
5813
+ case "delete": {
5814
+ if (!id) {
5815
+ write(ctx.io.stderr, "Usage: openwop advisors delete <boardId> [--yes]\n");
5816
+ return 2;
5817
+ }
5818
+ if (!options.yes) throw new CliError(`Refusing to delete advisory board ${id} without --yes.`, 2);
5819
+ await requestJson(ctx, `${BOARDS}/${encodeURIComponent(id)}`, { method: "DELETE" });
5820
+ writeLine(ctx.io.stdout, `Deleted advisory board ${id}.`);
5821
+ return 0;
5822
+ }
5823
+ default:
5824
+ throw new CliError(`Unknown advisors command: ${sub}`);
5825
+ }
5826
+ }
5827
+
5828
+ // src/cli/campaignOrchestration.ts
5829
+ var BASE8 = "/v1/host/sample/campaign-orchestration/campaigns";
5830
+ var CAMPAIGNS_ORCH_HELP = `Usage:
5831
+ openwop campaigns-orchestration list [--json]
5832
+ openwop campaigns-orchestration get <campaignId> [--json]
5833
+ openwop campaigns-orchestration create --name <n> [--json]
5834
+ openwop campaigns-orchestration update <campaignId> [--name <n>] [--json]
5835
+ openwop campaigns-orchestration delete <campaignId> [--yes]
5836
+ openwop campaigns-orchestration finalize <campaignId> [--yes] [--json]
5837
+
5838
+ Campaign orchestration (host-extension). \`finalize\` locks a campaign for execution.
5839
+ The host is the authority; the CLI mirrors + relays.`;
5840
+ async function runCampaignsOrch(ctx, argv) {
5841
+ const sub = argv[0] ?? "list";
5842
+ if (sub === "--help" || sub === "-h") {
5843
+ write(ctx.io.stdout, CAMPAIGNS_ORCH_HELP);
5844
+ return 0;
5845
+ }
5846
+ const args = argv.slice(["list", "get", "create", "update", "delete", "finalize"].includes(sub) ? 1 : 0);
5847
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--name"] });
5848
+ if (options.help) {
5849
+ write(ctx.io.stdout, CAMPAIGNS_ORCH_HELP);
5850
+ return 0;
5851
+ }
5852
+ const id = positionals[0];
5853
+ switch (sub) {
5854
+ case "list": {
5855
+ const res = await requestJson(ctx, BASE8);
5856
+ if (ctx.json) {
5857
+ writeJson(ctx.io.stdout, res.body);
5858
+ return 0;
5859
+ }
5860
+ const items = Array.isArray(res.body?.campaigns) ? res.body.campaigns : [];
5861
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((c) => ({ id: c.id ?? "", name: c.name ?? "", status: c.status ?? "" })), ["id", "name", "status"]) : "No campaigns.");
5862
+ return 0;
5863
+ }
5864
+ case "get": {
5865
+ if (!id) {
5866
+ write(ctx.io.stderr, "Usage: openwop campaigns-orchestration get <campaignId>\n");
5867
+ return 2;
5868
+ }
5869
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE8}/${encodeURIComponent(id)}`)).body);
5870
+ return 0;
5871
+ }
5872
+ case "create": {
5873
+ if (!options.name) {
5874
+ write(ctx.io.stderr, "campaigns-orchestration create needs --name.\n");
5875
+ return 2;
5876
+ }
5877
+ const res = await requestJson(ctx, BASE8, { method: "POST", body: { name: String(options.name) } });
5878
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5879
+ else writeLine(ctx.io.stdout, `Created campaign ${res.body?.id ?? ""} (${String(options.name)}).`);
5880
+ return 0;
5881
+ }
5882
+ case "update": {
5883
+ if (!id) {
5884
+ write(ctx.io.stderr, "Usage: openwop campaigns-orchestration update <campaignId> [--name n]\n");
5885
+ return 2;
5886
+ }
5887
+ const patch = {};
5888
+ if (options.name) patch.name = String(options.name);
5889
+ const res = await requestJson(ctx, `${BASE8}/${encodeURIComponent(id)}`, { method: "PATCH", body: patch });
5890
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5891
+ else writeLine(ctx.io.stdout, `Updated campaign ${id}.`);
5892
+ return 0;
5893
+ }
5894
+ case "delete": {
5895
+ if (!id) {
5896
+ write(ctx.io.stderr, "Usage: openwop campaigns-orchestration delete <campaignId> [--yes]\n");
5897
+ return 2;
5898
+ }
5899
+ if (!options.yes) throw new CliError(`Refusing to delete campaign ${id} without --yes.`, 2);
5900
+ await requestJson(ctx, `${BASE8}/${encodeURIComponent(id)}`, { method: "DELETE" });
5901
+ writeLine(ctx.io.stdout, `Deleted campaign ${id}.`);
5902
+ return 0;
5903
+ }
5904
+ case "finalize": {
5905
+ if (!id) {
5906
+ write(ctx.io.stderr, "Usage: openwop campaigns-orchestration finalize <campaignId> [--yes]\n");
5907
+ return 2;
5908
+ }
5909
+ if (!options.yes) throw new CliError(`Refusing to finalize campaign ${id} without --yes.`, 2);
5910
+ const res = await requestJson(ctx, `/v1/host/sample/campaign-orchestration/finalize`, { method: "POST", body: { campaignId: id } });
5911
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5912
+ else writeLine(ctx.io.stdout, `Finalized campaign ${id}.`);
5913
+ return 0;
5914
+ }
5915
+ default:
5916
+ throw new CliError(`Unknown campaigns-orchestration command: ${sub}`);
5917
+ }
5918
+ }
5919
+
5920
+ // src/cli/notebooks.ts
5921
+ var BASE9 = "/v1/host/sample/notebooks";
5922
+ var NOTEBOOKS_HELP = `Usage:
5923
+ openwop notebooks list [--json]
5924
+ openwop notebooks get <notebookId> [--json]
5925
+ openwop notebooks create --org <orgId> --name <n> [--json]
5926
+ openwop notebooks delete <notebookId> [--yes]
5927
+ openwop notebooks notes <notebookId> [--json]
5928
+
5929
+ NotebookLM-style notebooks (host-extension). A notebook holds sources + notes + a chat.
5930
+ \`create\` needs the owning --org. The host is the authority; the CLI mirrors + relays.`;
5931
+ async function runNotebooks(ctx, argv) {
5932
+ const sub = argv[0] ?? "list";
5933
+ if (sub === "--help" || sub === "-h") {
5934
+ write(ctx.io.stdout, NOTEBOOKS_HELP);
5935
+ return 0;
5936
+ }
5937
+ const args = argv.slice(["list", "get", "create", "delete", "notes"].includes(sub) ? 1 : 0);
5938
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name"] });
5939
+ if (options.help) {
5940
+ write(ctx.io.stdout, NOTEBOOKS_HELP);
3085
5941
  return 0;
3086
5942
  }
3087
- writeLine(ctx.io.stdout, `Updated card ${positionals[0]}.`);
3088
- return 0;
5943
+ const id = positionals[0];
5944
+ switch (sub) {
5945
+ case "list": {
5946
+ const res = await requestJson(ctx, BASE9);
5947
+ if (ctx.json) {
5948
+ writeJson(ctx.io.stdout, res.body);
5949
+ return 0;
5950
+ }
5951
+ const items = Array.isArray(res.body?.notebooks) ? res.body.notebooks : [];
5952
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((n) => ({ id: n.id ?? "", title: n.title ?? "", sources: n.sourceCount ?? "" })), ["id", "title", "sources"]) : "No notebooks.");
5953
+ return 0;
5954
+ }
5955
+ case "get": {
5956
+ if (!id) {
5957
+ write(ctx.io.stderr, "Usage: openwop notebooks get <notebookId>\n");
5958
+ return 2;
5959
+ }
5960
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE9}/${encodeURIComponent(id)}`)).body);
5961
+ return 0;
5962
+ }
5963
+ case "notes": {
5964
+ if (!id) {
5965
+ write(ctx.io.stderr, "Usage: openwop notebooks notes <notebookId>\n");
5966
+ return 2;
5967
+ }
5968
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${BASE9}/${encodeURIComponent(id)}/notes`)).body);
5969
+ return 0;
5970
+ }
5971
+ case "create": {
5972
+ if (!options.org || !options.name) {
5973
+ write(ctx.io.stderr, "notebooks create needs --org and --name.\n");
5974
+ return 2;
5975
+ }
5976
+ const res = await requestJson(ctx, BASE9, { method: "POST", body: { orgId: String(options.org), name: String(options.name) } });
5977
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
5978
+ else writeLine(ctx.io.stdout, `Created notebook ${res.body?.id ?? ""} (${String(options.name)}).`);
5979
+ return 0;
5980
+ }
5981
+ case "delete": {
5982
+ if (!id) {
5983
+ write(ctx.io.stderr, "Usage: openwop notebooks delete <notebookId> [--yes]\n");
5984
+ return 2;
5985
+ }
5986
+ if (!options.yes) throw new CliError(`Refusing to delete notebook ${id} without --yes.`, 2);
5987
+ await requestJson(ctx, `${BASE9}/${encodeURIComponent(id)}`, { method: "DELETE" });
5988
+ writeLine(ctx.io.stdout, `Deleted notebook ${id}.`);
5989
+ return 0;
5990
+ }
5991
+ default:
5992
+ throw new CliError(`Unknown notebooks command: ${sub}`);
5993
+ }
3089
5994
  }
3090
- async function cardDelete(ctx, argv) {
3091
- const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"] });
3092
- if (options.help || positionals.length !== 1) {
3093
- write(ctx.io.stdout, "Usage: openwop kanban card-delete <cardId> [--yes]\n");
3094
- return options.help ? 0 : 2;
5995
+
5996
+ // src/cli/podcasts.ts
5997
+ var EP = "/v1/host/sample/podcasts/episodes";
5998
+ var PODCASTS_HELP = `Usage:
5999
+ openwop podcasts list [--json]
6000
+ openwop podcasts get <episodeId> [--json]
6001
+ openwop podcasts create --org <orgId> --notebook <notebookId> --episode-profile <profileId> [--title <t>] [--json]
6002
+ openwop podcasts delete <episodeId> [--yes]
6003
+ openwop podcasts retry <episodeId> [--json]
6004
+
6005
+ Podcast episodes (host-extension). \`create\` starts an episode; \`retry\` re-runs a
6006
+ failed generation. The host is the authority; the CLI mirrors + relays.`;
6007
+ async function runPodcasts(ctx, argv) {
6008
+ const sub = argv[0] ?? "list";
6009
+ if (sub === "--help" || sub === "-h") {
6010
+ write(ctx.io.stdout, PODCASTS_HELP);
6011
+ return 0;
3095
6012
  }
3096
- if (!options.yes) {
3097
- writeLine(ctx.io.stderr, `Refusing to delete card ${positionals[0]} without --yes.`);
3098
- return 2;
6013
+ const args = argv.slice(["list", "get", "create", "delete", "retry"].includes(sub) ? 1 : 0);
6014
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--title", "--org", "--notebook", "--episode-profile"] });
6015
+ if (options.help) {
6016
+ write(ctx.io.stdout, PODCASTS_HELP);
6017
+ return 0;
6018
+ }
6019
+ const id = positionals[0];
6020
+ switch (sub) {
6021
+ case "list": {
6022
+ const res = await requestJson(ctx, EP);
6023
+ if (ctx.json) {
6024
+ writeJson(ctx.io.stdout, res.body);
6025
+ return 0;
6026
+ }
6027
+ const items = Array.isArray(res.body?.episodes) ? res.body.episodes : [];
6028
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((e) => ({ id: e.id ?? "", title: e.title ?? "", status: e.status ?? "" })), ["id", "title", "status"]) : "No episodes.");
6029
+ return 0;
6030
+ }
6031
+ case "get": {
6032
+ if (!id) {
6033
+ write(ctx.io.stderr, "Usage: openwop podcasts get <episodeId>\n");
6034
+ return 2;
6035
+ }
6036
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${EP}/${encodeURIComponent(id)}`)).body);
6037
+ return 0;
6038
+ }
6039
+ case "create": {
6040
+ if (!options.org || !options.notebook || !options.episodeProfile) {
6041
+ write(ctx.io.stderr, "podcasts create needs --org, --notebook, and --episode-profile.\n");
6042
+ return 2;
6043
+ }
6044
+ const body = { orgId: String(options.org), notebookId: String(options.notebook), episodeProfileId: String(options.episodeProfile) };
6045
+ if (options.title) body.title = String(options.title);
6046
+ const res = await requestJson(ctx, EP, { method: "POST", body });
6047
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
6048
+ else writeLine(ctx.io.stdout, `Created episode ${res.body?.id ?? ""}.`);
6049
+ return 0;
6050
+ }
6051
+ case "delete": {
6052
+ if (!id) {
6053
+ write(ctx.io.stderr, "Usage: openwop podcasts delete <episodeId> [--yes]\n");
6054
+ return 2;
6055
+ }
6056
+ if (!options.yes) throw new CliError(`Refusing to delete episode ${id} without --yes.`, 2);
6057
+ await requestJson(ctx, `${EP}/${encodeURIComponent(id)}`, { method: "DELETE" });
6058
+ writeLine(ctx.io.stdout, `Deleted episode ${id}.`);
6059
+ return 0;
6060
+ }
6061
+ case "retry": {
6062
+ if (!id) {
6063
+ write(ctx.io.stderr, "Usage: openwop podcasts retry <episodeId>\n");
6064
+ return 2;
6065
+ }
6066
+ const res = await requestJson(ctx, `${EP}/${encodeURIComponent(id)}/retry`, { method: "POST", body: {} });
6067
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
6068
+ else writeLine(ctx.io.stdout, `Retrying episode ${id}.`);
6069
+ return 0;
6070
+ }
6071
+ default:
6072
+ throw new CliError(`Unknown podcasts command: ${sub}`);
3099
6073
  }
3100
- await requestJson(ctx, `/v1/host/sample/kanban/cards/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
3101
- writeLine(ctx.io.stdout, `Deleted card ${positionals[0]}.`);
3102
- return 0;
3103
6074
  }
3104
- async function boardWatch(ctx, argv) {
3105
- const { options, positionals } = parseOptions(argv, { bool: ["--help"] });
3106
- if (options.help || positionals.length !== 1) {
3107
- write(ctx.io.stdout, "Usage: openwop kanban watch <boardId>\n");
3108
- return options.help ? 0 : 2;
6075
+
6076
+ // src/cli/priorityMatrix.ts
6077
+ var LISTS = "/v1/host/sample/priority-matrix/lists";
6078
+ var PRIORITY_MATRIX_HELP = `Usage:
6079
+ openwop priority-matrix lists [--json]
6080
+ openwop priority-matrix get <listId> [--json]
6081
+ openwop priority-matrix create --org <orgId> --name <n> [--json]
6082
+ openwop priority-matrix delete <listId> [--yes]
6083
+ openwop priority-matrix ideas <listId> [--json]
6084
+
6085
+ Prioritization boards (host-extension). A list holds scored/voted ideas; \`ideas\` reads
6086
+ a list's ideas. \`create\` needs the owning --org. The host is the authority.`;
6087
+ async function runPriorityMatrix(ctx, argv) {
6088
+ const sub = argv[0] ?? "lists";
6089
+ if (sub === "--help" || sub === "-h") {
6090
+ write(ctx.io.stdout, PRIORITY_MATRIX_HELP);
6091
+ return 0;
3109
6092
  }
3110
- const url = new URL(`v1/host/sample/kanban/boards/${encodeURIComponent(positionals[0])}/events`, ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
3111
- const headers = { accept: "text/event-stream" };
3112
- if (ctx.apiKey) headers.authorization = `Bearer ${ctx.apiKey}`;
3113
- const res = await ctx.fetchImpl(url, { headers });
3114
- if (!res.ok || !res.body) {
3115
- throw new CliError(`Could not open the board event stream (HTTP ${res.status}).`, 1);
6093
+ const args = argv.slice(["lists", "get", "create", "delete", "ideas"].includes(sub) ? 1 : 0);
6094
+ const { options, positionals } = parseOptions(args, { bool: ["--help", "--yes"], value: ["--org", "--name"] });
6095
+ if (options.help) {
6096
+ write(ctx.io.stdout, PRIORITY_MATRIX_HELP);
6097
+ return 0;
3116
6098
  }
3117
- writeLine(ctx.io.stdout, `Watching board ${positionals[0]} (Ctrl-C to stop)\u2026`);
3118
- await consumeSse(res.body, (frame) => {
3119
- if (ctx.json) {
3120
- writeLine(ctx.io.stdout, JSON.stringify(frame));
3121
- return;
6099
+ const id = positionals[0];
6100
+ switch (sub) {
6101
+ case "lists": {
6102
+ const res = await requestJson(ctx, LISTS);
6103
+ if (ctx.json) {
6104
+ writeJson(ctx.io.stdout, res.body);
6105
+ return 0;
6106
+ }
6107
+ const items = Array.isArray(res.body?.lists) ? res.body.lists : [];
6108
+ writeLine(ctx.io.stdout, items.length ? formatTable(items.map((l) => ({ id: l.id ?? l.listId ?? "", name: l.name ?? "", ideas: l.ideaCount ?? "" })), ["id", "name", "ideas"]) : "No lists.");
6109
+ return 0;
3122
6110
  }
3123
- const type = frame.event ?? "message";
3124
- writeLine(ctx.io.stdout, `\xB7 ${type}${frame.data ? `: ${frame.data}` : ""}`);
3125
- });
3126
- return 0;
6111
+ case "get": {
6112
+ if (!id) {
6113
+ write(ctx.io.stderr, "Usage: openwop priority-matrix get <listId>\n");
6114
+ return 2;
6115
+ }
6116
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${LISTS}/${encodeURIComponent(id)}`)).body);
6117
+ return 0;
6118
+ }
6119
+ case "ideas": {
6120
+ if (!id) {
6121
+ write(ctx.io.stderr, "Usage: openwop priority-matrix ideas <listId>\n");
6122
+ return 2;
6123
+ }
6124
+ writeJson(ctx.io.stdout, (await requestJson(ctx, `${LISTS}/${encodeURIComponent(id)}/ideas`)).body);
6125
+ return 0;
6126
+ }
6127
+ case "create": {
6128
+ if (!options.org || !options.name) {
6129
+ write(ctx.io.stderr, "priority-matrix create needs --org and --name.\n");
6130
+ return 2;
6131
+ }
6132
+ const res = await requestJson(ctx, LISTS, { method: "POST", body: { orgId: String(options.org), name: String(options.name) } });
6133
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
6134
+ else writeLine(ctx.io.stdout, `Created list ${res.body?.id ?? ""} (${String(options.name)}).`);
6135
+ return 0;
6136
+ }
6137
+ case "delete": {
6138
+ if (!id) {
6139
+ write(ctx.io.stderr, "Usage: openwop priority-matrix delete <listId> [--yes]\n");
6140
+ return 2;
6141
+ }
6142
+ if (!options.yes) throw new CliError(`Refusing to delete list ${id} without --yes.`, 2);
6143
+ await requestJson(ctx, `${LISTS}/${encodeURIComponent(id)}`, { method: "DELETE" });
6144
+ writeLine(ctx.io.stdout, `Deleted list ${id}.`);
6145
+ return 0;
6146
+ }
6147
+ default:
6148
+ throw new CliError(`Unknown priority-matrix command: ${sub}`);
6149
+ }
3127
6150
  }
3128
6151
 
3129
6152
  // src/cli/accessControl.ts
@@ -3312,10 +6335,10 @@ async function runEntity(ctx, entity, argv) {
3312
6335
  return orgId ? 0 : 2;
3313
6336
  }
3314
6337
  const rest = argv.slice(2);
3315
- const base = `/v1/host/sample/orgs/${encodeURIComponent(orgId)}/${entity}`;
6338
+ const base8 = `/v1/host/sample/orgs/${encodeURIComponent(orgId)}/${entity}`;
3316
6339
  switch (verb) {
3317
6340
  case "list": {
3318
- const res = await requestJson(ctx, base);
6341
+ const res = await requestJson(ctx, base8);
3319
6342
  if (ctx.json) {
3320
6343
  writeJson(ctx.io.stdout, res.body);
3321
6344
  return 0;
@@ -3331,7 +6354,7 @@ async function runEntity(ctx, entity, argv) {
3331
6354
  case "create": {
3332
6355
  const { body, ok: ok3 } = entityBody(ctx, entity, rest, true);
3333
6356
  if (!ok3) return 2;
3334
- const res = await requestJson(ctx, base, { method: "POST", body });
6357
+ const res = await requestJson(ctx, base8, { method: "POST", body });
3335
6358
  if (ctx.json) {
3336
6359
  writeJson(ctx.io.stdout, res.body);
3337
6360
  return 0;
@@ -3347,7 +6370,7 @@ async function runEntity(ctx, entity, argv) {
3347
6370
  }
3348
6371
  const { body, ok: ok3 } = entityBody(ctx, entity, rest, false);
3349
6372
  if (!ok3) return 2;
3350
- const res = await requestJson(ctx, `${base}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body });
6373
+ const res = await requestJson(ctx, `${base8}/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body });
3351
6374
  if (ctx.json) {
3352
6375
  writeJson(ctx.io.stdout, res.body);
3353
6376
  return 0;
@@ -3365,7 +6388,7 @@ async function runEntity(ctx, entity, argv) {
3365
6388
  writeLine(ctx.io.stderr, `Refusing to delete ${singular(entity)} ${positionals[0]} without --yes.`);
3366
6389
  return 2;
3367
6390
  }
3368
- await requestJson(ctx, `${base}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
6391
+ await requestJson(ctx, `${base8}/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
3369
6392
  writeLine(ctx.io.stdout, `Deleted ${singular(entity)} ${positionals[0]}.`);
3370
6393
  return 0;
3371
6394
  }
@@ -3672,6 +6695,9 @@ var BYOK_HELP = `Usage:
3672
6695
  openwop byok list [--json]
3673
6696
  openwop byok set --ref <credentialRef> [--value <secret>] [--json]
3674
6697
  openwop byok delete <credentialRef> [--yes]
6698
+ openwop byok ai-default [get] [--json]
6699
+ openwop byok ai-default set <credentialRef> [--json]
6700
+ openwop byok ai-default clear [--yes]
3675
6701
 
3676
6702
  Bring-your-own-key secret store (host-side). The host holds the secret; the
3677
6703
  credential-payload-redaction invariant (RFC 0046) means a value is NEVER
@@ -3695,7 +6721,7 @@ async function runByok(ctx, argv) {
3695
6721
  write(ctx.io.stdout, BYOK_HELP);
3696
6722
  return 0;
3697
6723
  }
3698
- const args = argv.slice(["list", "set", "delete"].includes(sub) ? 1 : 0);
6724
+ const args = argv.slice(["list", "set", "delete", "ai-default"].includes(sub) ? 1 : 0);
3699
6725
  switch (sub) {
3700
6726
  case "list":
3701
6727
  return await byokList(ctx, args);
@@ -3703,6 +6729,8 @@ async function runByok(ctx, argv) {
3703
6729
  return await byokSet(ctx, args);
3704
6730
  case "delete":
3705
6731
  return await byokDelete(ctx, args);
6732
+ case "ai-default":
6733
+ return await byokAiDefault(ctx, args);
3706
6734
  default:
3707
6735
  throw new CliError(`Unknown byok command: ${sub}
3708
6736
  Run \`openwop byok --help\` for usage.`);
@@ -3765,6 +6793,39 @@ async function byokDelete(ctx, argv) {
3765
6793
  writeLine(ctx.io.stdout, `Deleted secret ref ${positionals[0]}.`);
3766
6794
  return 0;
3767
6795
  }
6796
+ async function byokAiDefault(ctx, argv) {
6797
+ const action = argv[0] && !argv[0].startsWith("-") ? argv[0] : "get";
6798
+ const rest = ["get", "set", "clear"].includes(action) ? argv.slice(1) : argv;
6799
+ const { options, positionals } = parseOptions(rest, { bool: ["--help", "--yes"] });
6800
+ if (options.help) {
6801
+ write(ctx.io.stdout, BYOK_HELP);
6802
+ return 0;
6803
+ }
6804
+ const path = "/v1/host/sample/byok/ai-default";
6805
+ if (action === "set") {
6806
+ if (positionals.length !== 1) {
6807
+ write(ctx.io.stderr, "Usage: openwop byok ai-default set <credentialRef> [--json]\n");
6808
+ return 2;
6809
+ }
6810
+ const res2 = await requestJson(ctx, path, { method: "PUT", body: { credentialRef: positionals[0] } });
6811
+ if (ctx.json) writeJson(ctx.io.stdout, res2.body);
6812
+ else writeLine(ctx.io.stdout, `AI-default credential set to ${positionals[0]}.`);
6813
+ return 0;
6814
+ }
6815
+ if (action === "clear") {
6816
+ if (!options.yes) {
6817
+ writeLine(ctx.io.stderr, "Refusing to clear the AI-default binding without --yes.");
6818
+ return 2;
6819
+ }
6820
+ await requestJson(ctx, path, { method: "DELETE" });
6821
+ writeLine(ctx.io.stdout, "AI-default credential cleared.");
6822
+ return 0;
6823
+ }
6824
+ const res = await requestJson(ctx, path);
6825
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
6826
+ else writeLine(ctx.io.stdout, `AI-default credentialRef: ${res.body?.credentialRef ?? "(none)"}`);
6827
+ return 0;
6828
+ }
3768
6829
 
3769
6830
  // src/cli/conformance.ts
3770
6831
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -3892,6 +6953,10 @@ var RUNS_HELP = `Usage:
3892
6953
  openwop runs annotations <runId> [--json]
3893
6954
  openwop runs annotate <runId> (--rating 1-5 | --label t | --correction t | --flag) [--note t] [--event-id id] [--node-id id]
3894
6955
  openwop runs debug-bundle <runId> [--max-events n] [--out file] [--json]
6956
+ openwop runs fork <runId> [--mode replay|branch] [--from-sequence n] [--json]
6957
+ openwop runs diff <runId> --against <otherRunId> [--json]
6958
+ openwop runs delete <runId> [--yes]
6959
+ openwop runs bulk-cancel <runId...> [--reason text] [--json]
3895
6960
 
3896
6961
  \`runs events\` polls GET /v1/runs/{runId}/events/poll (JSON, not SSE); --since N
3897
6962
  returns events with sequence > N. \`runs annotate\` posts a review signal
@@ -3916,7 +6981,7 @@ Input parsing for \`runs create\`:
3916
6981
  `;
3917
6982
  async function runRuns(ctx, argv) {
3918
6983
  const sub = argv[0] ?? "list";
3919
- const args = argv.slice(["list", "create", "get", "cancel", "ancestry", "events", "annotations", "annotate", "debug-bundle"].includes(sub) ? 1 : 0);
6984
+ const args = argv.slice(["list", "create", "get", "cancel", "ancestry", "events", "annotations", "annotate", "debug-bundle", "fork", "diff", "delete", "bulk-cancel"].includes(sub) ? 1 : 0);
3920
6985
  if (sub === "--help" || sub === "-h") {
3921
6986
  write(ctx.io.stdout, RUNS_HELP);
3922
6987
  return 0;
@@ -3940,6 +7005,14 @@ async function runRuns(ctx, argv) {
3940
7005
  return runRunsAnnotate(ctx, args);
3941
7006
  case "debug-bundle":
3942
7007
  return runRunsDebugBundle(ctx, args);
7008
+ case "fork":
7009
+ return runRunsFork(ctx, args);
7010
+ case "diff":
7011
+ return runRunsDiff(ctx, args);
7012
+ case "delete":
7013
+ return runRunsDelete(ctx, args);
7014
+ case "bulk-cancel":
7015
+ return runRunsBulkCancel(ctx, args);
3943
7016
  default:
3944
7017
  throw new CliError(`Unknown runs command: ${sub}`);
3945
7018
  }
@@ -4032,6 +7105,57 @@ async function runRunsCancel(ctx, argv) {
4032
7105
  else writeLine(ctx.io.stdout, `Cancelled ${positionals[0]}`);
4033
7106
  return 0;
4034
7107
  }
7108
+ async function runRunsFork(ctx, argv) {
7109
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--mode", "--from-sequence"] });
7110
+ if (options.help || positionals.length !== 1) {
7111
+ write(ctx.io.stdout, "Usage: openwop runs fork <runId> [--mode replay|branch] [--from-sequence n] [--json]\n");
7112
+ return options.help ? 0 : 2;
7113
+ }
7114
+ const body = {};
7115
+ if (options.mode) body.mode = String(options.mode);
7116
+ if (options.fromSequence !== void 0) body.fromSeq = Number(options.fromSequence);
7117
+ const res = await requestJson(ctx, `/v1/runs/${encodeURIComponent(positionals[0])}:fork`, { method: "POST", body });
7118
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
7119
+ else writeLine(ctx.io.stdout, `Forked ${positionals[0]} \u2192 ${res.body?.runId ?? "(see --json)"}`);
7120
+ return 0;
7121
+ }
7122
+ async function runRunsDiff(ctx, argv) {
7123
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--against"] });
7124
+ if (options.help || positionals.length !== 1 || !options.against) {
7125
+ write(ctx.io.stdout, "Usage: openwop runs diff <runId> --against <otherRunId> [--json]\n");
7126
+ return options.help ? 0 : 2;
7127
+ }
7128
+ const res = await requestJson(
7129
+ ctx,
7130
+ `/v1/runs/${encodeURIComponent(positionals[0])}:diff?against=${encodeURIComponent(String(options.against))}`
7131
+ );
7132
+ writeJson(ctx.io.stdout, res.body);
7133
+ return 0;
7134
+ }
7135
+ async function runRunsDelete(ctx, argv) {
7136
+ const { options, positionals } = parseOptions(argv, { bool: ["--help", "--yes"] });
7137
+ if (options.help || positionals.length !== 1) {
7138
+ write(ctx.io.stdout, "Usage: openwop runs delete <runId> [--yes]\n");
7139
+ return options.help ? 0 : 2;
7140
+ }
7141
+ if (!options.yes) throw new CliError(`Refusing to delete run ${positionals[0]} without --yes (this removes the run and its event log).`, 2);
7142
+ await requestJson(ctx, `/v1/runs/${encodeURIComponent(positionals[0])}`, { method: "DELETE" });
7143
+ writeLine(ctx.io.stdout, `Deleted ${positionals[0]}`);
7144
+ return 0;
7145
+ }
7146
+ async function runRunsBulkCancel(ctx, argv) {
7147
+ const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--reason"] });
7148
+ if (options.help || positionals.length === 0) {
7149
+ write(ctx.io.stdout, "Usage: openwop runs bulk-cancel <runId...> [--reason text] [--json]\n");
7150
+ return options.help ? 0 : 2;
7151
+ }
7152
+ const body = { runIds: positionals };
7153
+ if (options.reason) body.reason = options.reason;
7154
+ const res = await requestJson(ctx, "/v1/runs:bulk-cancel", { method: "POST", body });
7155
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
7156
+ else writeLine(ctx.io.stdout, `Requested cancel of ${positionals.length} run(s)`);
7157
+ return 0;
7158
+ }
4035
7159
  async function runRunsEvents(ctx, argv) {
4036
7160
  const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--since", "--limit"] });
4037
7161
  if (options.help || positionals.length !== 1) {
@@ -5950,10 +9074,10 @@ async function runPacksPublish(ctx, argv) {
5950
9074
  const sha = createHash("sha256").update(tgz).digest("hex");
5951
9075
  const outDir = options.out ? resolvePath4(ctx.cwd, options.out) : join6(ctx.cwd, "dist", "packs");
5952
9076
  mkdirSync5(outDir, { recursive: true });
5953
- const base = `${name}-${version}`;
5954
- const tgzPath = join6(outDir, `${base}.tgz`);
5955
- const sigPath = join6(outDir, `${base}.sig`);
5956
- const manifestOut = join6(outDir, `${base}.manifest.json`);
9077
+ const base8 = `${name}-${version}`;
9078
+ const tgzPath = join6(outDir, `${base8}.tgz`);
9079
+ const sigPath = join6(outDir, `${base8}.sig`);
9080
+ const manifestOut = join6(outDir, `${base8}.manifest.json`);
5957
9081
  writeFileSync7(tgzPath, tgz);
5958
9082
  writeFileSync7(sigPath, sig);
5959
9083
  writeFileSync7(manifestOut, JSON.stringify(signedManifest, null, 2) + "\n", "utf8");
@@ -6038,8 +9162,8 @@ function canonicalJsonStringify(value) {
6038
9162
  return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalJsonStringify(value[k])).join(",") + "}";
6039
9163
  }
6040
9164
  function configHomeDir(env = process.env) {
6041
- const base = env.OPENWOP_CONFIG_HOME ? env.OPENWOP_CONFIG_HOME : homedir3();
6042
- return join6(base, ".openwop");
9165
+ const base8 = env.OPENWOP_CONFIG_HOME ? env.OPENWOP_CONFIG_HOME : homedir3();
9166
+ return join6(base8, ".openwop");
6043
9167
  }
6044
9168
  function extractPackJsonBytes(tarballBytes) {
6045
9169
  const decompressed = gunzipSync(tarballBytes);
@@ -6220,6 +9344,8 @@ var GOVERNANCE_HELP = `Usage:
6220
9344
  openwop governance policy [get] [--json]
6221
9345
  openwop governance policy set [--provider-allowlist <a,b,...>] [--action <kind=policy>]... [--retention-graph-days <n>] [--retention-source-days <n>] [--json]
6222
9346
  openwop governance audit [--prefix <p>] [--limit <n>] [--since <iso>] [--json]
9347
+ openwop governance media-budget [get] [--json]
9348
+ openwop governance media-budget set [--tts-chars <n>] [--stt-bytes <n>] [--json]
6223
9349
 
6224
9350
  Tenant governance administration (ADR 0028, host extension \u2014 superadmin-gated).
6225
9351
  Endpoints under ${GOV_BASE}:
@@ -6278,6 +9404,8 @@ async function runGovernance(ctx, argv) {
6278
9404
  return await runGovernancePolicy(ctx, argv.slice(1));
6279
9405
  case "audit":
6280
9406
  return await runGovernanceAudit(ctx, argv.slice(1));
9407
+ case "media-budget":
9408
+ return await runGovernanceMediaBudget(ctx, argv.slice(1));
6281
9409
  // `get`/`set` at the top level are policy ops — lets the `policy` group alias
6282
9410
  // read naturally (`openwop policy set ...`) without a redundant `policy policy`.
6283
9411
  case "get":
@@ -6427,6 +9555,30 @@ async function runGovernanceAudit(ctx, argv) {
6427
9555
  writeLine(ctx.io.stdout, formatTable(rows, ["timestamp", "principal", "action", "resource", "outcome"]));
6428
9556
  return 0;
6429
9557
  }
9558
+ async function runGovernanceMediaBudget(ctx, argv) {
9559
+ const action = argv[0] === "set" ? "set" : "get";
9560
+ const rest = argv[0] === "get" || argv[0] === "set" ? argv.slice(1) : argv;
9561
+ const path = `${GOV_BASE}/media-budget`;
9562
+ if (action === "set") {
9563
+ const { options } = parseOptions(rest, { value: ["--tts-chars", "--stt-bytes"] });
9564
+ const body = {};
9565
+ if (options.ttsChars !== void 0) body.ttsChars = Number(options.ttsChars);
9566
+ if (options.sttBytes !== void 0) body.sttBytes = Number(options.sttBytes);
9567
+ const res2 = await requestJson(ctx, path, { method: "PUT", body });
9568
+ if (ctx.json) writeJson(ctx.io.stdout, res2.body);
9569
+ else writeLine(ctx.io.stdout, "Media budget updated.");
9570
+ return 0;
9571
+ }
9572
+ const res = await requestJson(ctx, path);
9573
+ if (ctx.json) {
9574
+ writeJson(ctx.io.stdout, res.body);
9575
+ return 0;
9576
+ }
9577
+ const b = res.body ?? {};
9578
+ writeLine(ctx.io.stdout, `override: ${b.override ? JSON.stringify(b.override) : "(none \u2014 using host defaults)"}`);
9579
+ writeLine(ctx.io.stdout, `effective: tts ${b.budgets?.ttsChars ?? "?"} chars/day, stt ${b.budgets?.sttBytes ?? "?"} bytes/day`);
9580
+ return 0;
9581
+ }
6430
9582
 
6431
9583
  // src/cli/demo.ts
6432
9584
  import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
@@ -8000,7 +11152,7 @@ Examples:
8000
11152
  openwop profiles pin r_123
8001
11153
  openwop profiles activity --limit 10 --status failed
8002
11154
  `;
8003
- var BASE = "/v1/host/sample/profiles";
11155
+ var BASE10 = "/v1/host/sample/profiles";
8004
11156
  async function profilesRequest(ctx, path, options, notFoundMsg) {
8005
11157
  try {
8006
11158
  return await requestJson(ctx, path, options);
@@ -8095,7 +11247,7 @@ async function runList2(ctx, argv) {
8095
11247
  write(ctx.io.stdout, "Usage: openwop profiles list [--json]\n");
8096
11248
  return 0;
8097
11249
  }
8098
- const res = await profilesRequest(ctx, BASE);
11250
+ const res = await profilesRequest(ctx, BASE10);
8099
11251
  if (ctx.json) {
8100
11252
  writeJson(ctx.io.stdout, res.body);
8101
11253
  return 0;
@@ -8121,7 +11273,7 @@ async function runGet3(ctx, argv) {
8121
11273
  write(ctx.io.stdout, "Usage: openwop profiles get [<userId>] [--json]\n");
8122
11274
  return options.help ? 0 : 2;
8123
11275
  }
8124
- const path = positionals.length === 1 ? `${BASE}/${encodeURIComponent(positionals[0])}` : `${BASE}/me`;
11276
+ const path = positionals.length === 1 ? `${BASE10}/${encodeURIComponent(positionals[0])}` : `${BASE10}/me`;
8125
11277
  const res = await profilesRequest(ctx, path, void 0, `Profile not found: ${positionals[0] ?? "me"}`);
8126
11278
  if (ctx.json) {
8127
11279
  writeJson(ctx.io.stdout, res.body);
@@ -8140,7 +11292,7 @@ async function runActivity(ctx, argv) {
8140
11292
  if (options.limit !== void 0) qs.set("limit", String(options.limit));
8141
11293
  if (options.status !== void 0) qs.set("status", String(options.status));
8142
11294
  const q = qs.toString();
8143
- const res = await profilesRequest(ctx, `${BASE}/me/activity${q ? `?${q}` : ""}`);
11295
+ const res = await profilesRequest(ctx, `${BASE10}/me/activity${q ? `?${q}` : ""}`);
8144
11296
  if (ctx.json) {
8145
11297
  writeJson(ctx.io.stdout, res.body);
8146
11298
  return 0;
@@ -8192,7 +11344,7 @@ async function runEdit(ctx, argv) {
8192
11344
  }
8193
11345
  if (Object.keys(avail).length) body.availability = avail;
8194
11346
  if (Object.keys(body).length === 0) throw new CliError("Nothing to edit \u2014 pass at least one field.", 2);
8195
- const res = await profilesRequest(ctx, `${BASE}/me`, { method: "PATCH", body });
11347
+ const res = await profilesRequest(ctx, `${BASE10}/me`, { method: "PATCH", body });
8196
11348
  if (ctx.json) {
8197
11349
  writeJson(ctx.io.stdout, res.body);
8198
11350
  return 0;
@@ -8213,12 +11365,12 @@ async function runSkillsSet(ctx, argv) {
8213
11365
  const eq = e.lastIndexOf("=");
8214
11366
  if (eq < 0) throw new CliError(`--skill must be name=proficiency (got '${e}')`, 2);
8215
11367
  const name = e.slice(0, eq).trim();
8216
- const prof = Number(e.slice(eq + 1));
11368
+ const prof2 = Number(e.slice(eq + 1));
8217
11369
  if (!name) throw new CliError(`--skill name is required (got '${e}')`, 2);
8218
- if (!Number.isFinite(prof) || prof < 1 || prof > 5) throw new CliError(`--skill proficiency must be 1..5 (got '${e}')`, 2);
8219
- return { name, proficiency: prof };
11370
+ if (!Number.isFinite(prof2) || prof2 < 1 || prof2 > 5) throw new CliError(`--skill proficiency must be 1..5 (got '${e}')`, 2);
11371
+ return { name, proficiency: prof2 };
8220
11372
  });
8221
- const res = await profilesRequest(ctx, `${BASE}/me/skills`, { method: "PUT", body: { skills } });
11373
+ const res = await profilesRequest(ctx, `${BASE10}/me/skills`, { method: "PUT", body: { skills } });
8222
11374
  if (ctx.json) {
8223
11375
  writeJson(ctx.io.stdout, res.body);
8224
11376
  return 0;
@@ -8233,7 +11385,7 @@ async function runPortfolioAdd(ctx, argv) {
8233
11385
  write(ctx.io.stdout, "Usage: openwop profiles portfolio add --token <mediaToken> [--json]\n");
8234
11386
  return options.help ? 0 : 2;
8235
11387
  }
8236
- const res = await profilesRequest(ctx, `${BASE}/me/portfolio`, { method: "POST", body: { token: options.token } });
11388
+ const res = await profilesRequest(ctx, `${BASE10}/me/portfolio`, { method: "POST", body: { token: options.token } });
8237
11389
  if (ctx.json) {
8238
11390
  writeJson(ctx.io.stdout, res.body);
8239
11391
  return 0;
@@ -8247,7 +11399,7 @@ async function runPortfolioRemove(ctx, argv) {
8247
11399
  write(ctx.io.stdout, "Usage: openwop profiles portfolio remove <mediaToken> [--json]\n");
8248
11400
  return options.help ? 0 : 2;
8249
11401
  }
8250
- const res = await profilesRequest(ctx, `${BASE}/me/portfolio/${encodeURIComponent(positionals[0])}`, { method: "DELETE" }, `Portfolio asset not found: ${positionals[0]}`);
11402
+ const res = await profilesRequest(ctx, `${BASE10}/me/portfolio/${encodeURIComponent(positionals[0])}`, { method: "DELETE" }, `Portfolio asset not found: ${positionals[0]}`);
8251
11403
  if (ctx.json) {
8252
11404
  writeJson(ctx.io.stdout, res.body);
8253
11405
  return 0;
@@ -8262,7 +11414,7 @@ async function runPin(ctx, argv, pinned) {
8262
11414
  `);
8263
11415
  return options.help ? 0 : 2;
8264
11416
  }
8265
- const path = `${BASE}/me/pinned-agents/${encodeURIComponent(positionals[0])}`;
11417
+ const path = `${BASE10}/me/pinned-agents/${encodeURIComponent(positionals[0])}`;
8266
11418
  const res = await profilesRequest(ctx, path, { method: pinned ? "PUT" : "DELETE" }, `Agent not found: ${positionals[0]}`);
8267
11419
  if (ctx.json) {
8268
11420
  writeJson(ctx.io.stdout, res.body);
@@ -8279,7 +11431,7 @@ async function runEndorse(ctx, argv, add) {
8279
11431
  return options.help ? 0 : 2;
8280
11432
  }
8281
11433
  const [userId, skill] = positionals;
8282
- const path = `${BASE}/${encodeURIComponent(userId)}/skills/${encodeURIComponent(skill)}/endorse`;
11434
+ const path = `${BASE10}/${encodeURIComponent(userId)}/skills/${encodeURIComponent(skill)}/endorse`;
8283
11435
  const res = await profilesRequest(ctx, path, { method: add ? "POST" : "DELETE" }, `Profile or skill not found: ${userId} / ${skill}`);
8284
11436
  if (ctx.json) {
8285
11437
  writeJson(ctx.io.stdout, res.body);
@@ -8289,6 +11441,115 @@ async function runEndorse(ctx, argv, add) {
8289
11441
  return 0;
8290
11442
  }
8291
11443
 
11444
+ // src/cli/completion.ts
11445
+ var COMPLETION_HELP = `Usage:
11446
+ openwop completion <bash|zsh|fish>
11447
+
11448
+ Emit a shell-completion script for openwop's top-level commands. Install it:
11449
+ bash: openwop completion bash > \${BASH_COMPLETION_USER_DIR:-~/.local/share/bash-completion}/completions/openwop
11450
+ zsh: openwop completion zsh > "\${fpath[1]}/_openwop" # then: autoload -U compinit && compinit
11451
+ fish: openwop completion fish > ~/.config/fish/completions/openwop.fish
11452
+ `;
11453
+ function bashScript(cmds) {
11454
+ return `# openwop bash completion \u2014 source this or drop it in your bash-completion dir.
11455
+ _openwop() {
11456
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
11457
+ if [ "\${COMP_CWORD}" -eq 1 ]; then
11458
+ COMPREPLY=( $(compgen -W "${cmds}" -- "\${cur}") )
11459
+ return 0
11460
+ fi
11461
+ }
11462
+ complete -F _openwop openwop
11463
+ `;
11464
+ }
11465
+ function zshScript(cmds) {
11466
+ return `#compdef openwop
11467
+ # openwop zsh completion \u2014 install as _openwop on your fpath.
11468
+ _openwop() {
11469
+ local -a _cmds
11470
+ _cmds=(${cmds})
11471
+ _arguments '1: :(\${_cmds})' '*::arg:->args'
11472
+ }
11473
+ _openwop "$@"
11474
+ `;
11475
+ }
11476
+ function fishScript(commands) {
11477
+ const lines = commands.map((c) => `complete -c openwop -n __fish_use_subcommand -a ${c}`);
11478
+ return `# openwop fish completion \u2014 install as ~/.config/fish/completions/openwop.fish
11479
+ ${lines.join("\n")}
11480
+ `;
11481
+ }
11482
+ function runCompletion(ctx, argv, commands) {
11483
+ const shell = argv[0];
11484
+ if (shell === "--help" || shell === "-h") {
11485
+ write(ctx.io.stdout, COMPLETION_HELP);
11486
+ return 0;
11487
+ }
11488
+ if (!shell) {
11489
+ write(ctx.io.stderr, COMPLETION_HELP);
11490
+ return 2;
11491
+ }
11492
+ const cmds = commands.join(" ");
11493
+ switch (shell) {
11494
+ case "bash":
11495
+ write(ctx.io.stdout, bashScript(cmds));
11496
+ return 0;
11497
+ case "zsh":
11498
+ write(ctx.io.stdout, zshScript(cmds));
11499
+ return 0;
11500
+ case "fish":
11501
+ write(ctx.io.stdout, fishScript(commands));
11502
+ return 0;
11503
+ default:
11504
+ throw new CliError(`Unsupported shell: ${shell}. Use one of: bash | zsh | fish.`, 2);
11505
+ }
11506
+ }
11507
+
11508
+ // src/cli/upgrade.ts
11509
+ var REGISTRY = "https://registry.npmjs.org/@openwop/cli/latest";
11510
+ var UPGRADE_HELP = `Usage:
11511
+ openwop upgrade [--json]
11512
+
11513
+ Check npm for a newer @openwop/cli and print how to update. Report-only \u2014 it never
11514
+ installs. Update the global CLI yourself with:
11515
+ npm install -g @openwop/cli@latest
11516
+ `;
11517
+ function semverGt(a, b) {
11518
+ const pa = a.split(".").map((n) => Number(n) || 0);
11519
+ const pb = b.split(".").map((n) => Number(n) || 0);
11520
+ for (let i = 0; i < 3; i++) {
11521
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
11522
+ if (d !== 0) return d > 0;
11523
+ }
11524
+ return false;
11525
+ }
11526
+ async function runUpgrade(ctx, argv) {
11527
+ if (argv[0] === "--help" || argv[0] === "-h") {
11528
+ write(ctx.io.stdout, UPGRADE_HELP);
11529
+ return 0;
11530
+ }
11531
+ let latest = "";
11532
+ try {
11533
+ const res = await ctx.fetchImpl(REGISTRY, { headers: { accept: "application/json" } });
11534
+ if (!res.ok) throw new Error(`registry HTTP ${res.status}`);
11535
+ latest = (await res.json()).version ?? "";
11536
+ } catch (err) {
11537
+ throw new CliError(`Could not reach the npm registry to check for updates: ${err.message}`, 1);
11538
+ }
11539
+ const behind = latest !== "" && semverGt(latest, VERSION);
11540
+ if (ctx.json) {
11541
+ writeJson(ctx.io.stdout, { current: VERSION, latest, upToDate: !behind });
11542
+ return 0;
11543
+ }
11544
+ if (!behind) {
11545
+ writeLine(ctx.io.stdout, `@openwop/cli is up to date (${VERSION}).`);
11546
+ return 0;
11547
+ }
11548
+ writeLine(ctx.io.stdout, `A newer @openwop/cli is available: ${VERSION} \u2192 ${latest}`);
11549
+ writeLine(ctx.io.stdout, "Update with: npm install -g @openwop/cli@latest");
11550
+ return 0;
11551
+ }
11552
+
8292
11553
  // src/cli/toggles.ts
8293
11554
  var TOGGLES_BASE = "/v1/host/sample/feature-toggles";
8294
11555
  var TOGGLES_HELP = `Usage:
@@ -8996,7 +12257,7 @@ Examples:
8996
12257
  openwop auth saml validate --idp-url http://localhost:9100/idp --variant valid
8997
12258
  openwop auth scim provision --op create-user --user-name jo@acme.com --email jo@acme.com
8998
12259
  `;
8999
- var BASE2 = "/v1/host/sample/auth";
12260
+ var BASE11 = "/v1/host/sample/auth";
9000
12261
  var SECRET_KEY2 = /secret|token|password|private[-_]?key|client[-_]?secret|api[-_]?key|credential|bearer|assertion|certificate|cert|pem/i;
9001
12262
  function redact2(value) {
9002
12263
  if (Array.isArray(value)) return value.map(redact2);
@@ -9054,7 +12315,7 @@ async function runStatus(ctx, argv) {
9054
12315
  const profiles = disc.ok && Array.isArray(disc.body?.capabilities?.auth?.profiles) ? disc.body.capabilities.auth.profiles : [];
9055
12316
  const samlAdvertised = profiles.includes("openwop-auth-saml");
9056
12317
  const scimAdvertised = profiles.includes("openwop-auth-scim");
9057
- const metaReachable = samlAdvertised ? (await probeEndpoint(ctx, `${BASE2}/saml/sso/metadata`)).ok : false;
12318
+ const metaReachable = samlAdvertised ? (await probeEndpoint(ctx, `${BASE11}/saml/sso/metadata`)).ok : false;
9058
12319
  if (ctx.json) {
9059
12320
  writeJson(ctx.io.stdout, {
9060
12321
  authProfilesAdvertised: profiles,
@@ -9079,7 +12340,7 @@ async function runSamlMetadata(ctx, argv) {
9079
12340
  write(ctx.io.stdout, "Usage: openwop auth saml metadata [--json]\n");
9080
12341
  return 0;
9081
12342
  }
9082
- const res = await authRequest(ctx, `${BASE2}/saml/sso/metadata`, { auth: false }, "SAML SSO is not configured on this host (set OPENWOP_SAML_*).");
12343
+ const res = await authRequest(ctx, `${BASE11}/saml/sso/metadata`, { auth: false }, "SAML SSO is not configured on this host (set OPENWOP_SAML_*).");
9083
12344
  const xml = typeof res.body?.raw === "string" ? res.body.raw : typeof res.body === "string" ? res.body : JSON.stringify(res.body);
9084
12345
  if (ctx.json) {
9085
12346
  writeJson(ctx.io.stdout, { metadata: xml });
@@ -9096,8 +12357,8 @@ async function runSamlLoginUrl(ctx, argv) {
9096
12357
  return 0;
9097
12358
  }
9098
12359
  const returnTo = options.returnTo ?? "/";
9099
- const base = ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`;
9100
- const url = new URL(`${BASE2}/saml/sso/login?returnTo=${encodeURIComponent(returnTo)}`.replace(/^\//, ""), base);
12360
+ const base8 = ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`;
12361
+ const url = new URL(`${BASE11}/saml/sso/login?returnTo=${encodeURIComponent(returnTo)}`.replace(/^\//, ""), base8);
9101
12362
  const res = await ctx.fetchImpl(url, { method: "GET", redirect: "manual" });
9102
12363
  if (res.status === 404) {
9103
12364
  throw new CliError("SAML SSO is not configured on this host (set OPENWOP_SAML_*).", 1);
@@ -9122,7 +12383,7 @@ async function runSamlValidate(ctx, argv) {
9122
12383
  const body = { idpUrl: options.idpUrl, variant: options.variant };
9123
12384
  let res;
9124
12385
  try {
9125
- res = await requestJson(ctx, `${BASE2}/saml/validate`, { method: "POST", body });
12386
+ res = await requestJson(ctx, `${BASE11}/saml/validate`, { method: "POST", body });
9126
12387
  } catch (err) {
9127
12388
  if (err instanceof HttpError && err.status === 404) {
9128
12389
  throw new CliError("SAML validation seam not configured on this host (set OPENWOP_TEST_SAML_IDP_URL).", 1);
@@ -9165,7 +12426,7 @@ async function runScimProvision(ctx, argv) {
9165
12426
  if (options.displayName !== void 0) user.displayName = options.displayName;
9166
12427
  const body = { op: options.op, user };
9167
12428
  if (options.group !== void 0) body.group = options.group;
9168
- const res = await authRequest(ctx, `${BASE2}/scim/provision`, { method: "POST", body }, "SCIM provisioning seam not configured on this host (set OPENWOP_TEST_SCIM_URL).");
12429
+ const res = await authRequest(ctx, `${BASE11}/scim/provision`, { method: "POST", body }, "SCIM provisioning seam not configured on this host (set OPENWOP_TEST_SCIM_URL).");
9169
12430
  const safe = redact2(res.body ?? {});
9170
12431
  if (ctx.json) {
9171
12432
  writeJson(ctx.io.stdout, safe);
@@ -10170,6 +13431,8 @@ var TRIGGERS_HELP = `Usage:
10170
13431
  openwop triggers register --source <webhook|email|form> --workflow <id> [--dedup] [--verification <mode>] [--json]
10171
13432
  openwop triggers list [--state <s>] [--source <s>] [--json]
10172
13433
  openwop triggers get <subscriptionId> [--json]
13434
+ openwop triggers update <subscriptionId> --state <active|paused> [--json]
13435
+ openwop triggers ingest <subscriptionId> [--event-json '{...}'] [--json]
10173
13436
 
10174
13437
  External-event trigger subscriptions (RFC 0099). A subscription binds an external
10175
13438
  SOURCE (webhook / email / form) to a WORKFLOW the caller can start; a verified
@@ -10208,7 +13471,7 @@ Examples:
10208
13471
  openwop triggers list --state active
10209
13472
  openwop triggers get sub_123 --json
10210
13473
  `;
10211
- var SUBCOMMANDS5 = ["register", "list", "get"];
13474
+ var SUBCOMMANDS5 = ["register", "list", "get", "update", "ingest"];
10212
13475
  async function runTriggers(ctx, argv) {
10213
13476
  const sub = argv[0] ?? "list";
10214
13477
  if (sub === "--help" || sub === "-h") {
@@ -10223,11 +13486,45 @@ async function runTriggers(ctx, argv) {
10223
13486
  return await runList3(ctx, args);
10224
13487
  case "get":
10225
13488
  return await runGet4(ctx, args);
13489
+ case "update":
13490
+ return await runTriggersUpdate(ctx, args);
13491
+ case "ingest":
13492
+ return await runTriggersIngest(ctx, args);
10226
13493
  default:
10227
13494
  throw new CliError(`Unknown triggers command: ${sub}
10228
13495
  Run \`openwop triggers --help\` for usage.`);
10229
13496
  }
10230
13497
  }
13498
+ async function runTriggersUpdate(ctx, argv) {
13499
+ const { options, positionals } = parseOptions(argv, { value: ["--state"] });
13500
+ if (positionals.length !== 1 || !options.state) {
13501
+ write(ctx.io.stderr, "Usage: openwop triggers update <subscriptionId> --state <active|paused> [--json]\n");
13502
+ return 2;
13503
+ }
13504
+ const res = await requestJson(ctx, `/v1/trigger-subscriptions/${encodeURIComponent(positionals[0])}`, { method: "PATCH", body: { state: options.state } });
13505
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
13506
+ else writeLine(ctx.io.stdout, `Updated ${positionals[0]} \u2192 state ${options.state}.`);
13507
+ return 0;
13508
+ }
13509
+ async function runTriggersIngest(ctx, argv) {
13510
+ const { options, positionals } = parseOptions(argv, { value: ["--event-json"] });
13511
+ if (positionals.length !== 1) {
13512
+ write(ctx.io.stderr, "Usage: openwop triggers ingest <subscriptionId> [--event-json '{...}'] [--json]\n");
13513
+ return 2;
13514
+ }
13515
+ let event = {};
13516
+ if (options.eventJson) {
13517
+ try {
13518
+ event = JSON.parse(String(options.eventJson));
13519
+ } catch {
13520
+ throw new CliError("--event-json must be valid JSON.", 2);
13521
+ }
13522
+ }
13523
+ const res = await requestJson(ctx, `/v1/trigger-subscriptions/${encodeURIComponent(positionals[0])}/ingest`, { method: "POST", body: event });
13524
+ if (ctx.json) writeJson(ctx.io.stdout, res.body);
13525
+ else writeLine(ctx.io.stdout, `Ingested event for ${positionals[0]}.`);
13526
+ return 0;
13527
+ }
10231
13528
  function exitForState3(state) {
10232
13529
  if (state === "active") return 0;
10233
13530
  if (state === "paused") return 3;
@@ -10548,13 +13845,18 @@ async function runCli(argv, options = {}) {
10548
13845
  throw new CliError("This CLI requires a Node runtime with global fetch (Node 20+).");
10549
13846
  }
10550
13847
  const parsed = extractGlobalOptions(argv, env);
13848
+ const profile = parsed.globals.profile ?? env.OPENWOP_PROFILE;
13849
+ const savedConfig = readConfigSafe(configPathFor(profile, env)) ?? {};
10551
13850
  const ctx = {
10552
13851
  cwd,
10553
13852
  env,
10554
13853
  io,
10555
13854
  fetchImpl,
10556
- baseUrl: normalizeBaseUrl(parsed.globals.baseUrl ?? env.OPENWOP_BASE_URL ?? DEFAULT_BASE_URL),
10557
- apiKey: parsed.globals.apiKey ?? env.OPENWOP_API_KEY,
13855
+ profile,
13856
+ baseUrl: normalizeBaseUrl(
13857
+ parsed.globals.baseUrl ?? env.OPENWOP_BASE_URL ?? savedConfig?.host?.baseUrl ?? DEFAULT_BASE_URL
13858
+ ),
13859
+ apiKey: parsed.globals.apiKey ?? env.OPENWOP_API_KEY ?? savedConfig?.host?.apiKey,
10558
13860
  json: parsed.globals.json,
10559
13861
  quiet: parsed.globals.quiet,
10560
13862
  verbose: parsed.globals.verbose,
@@ -10630,6 +13932,60 @@ async function runCli(argv, options = {}) {
10630
13932
  return await runOrgs(ctx, commandArgs);
10631
13933
  case "workspace":
10632
13934
  return await runWorkspace(ctx, commandArgs);
13935
+ case "brand":
13936
+ return await runBrand(ctx, commandArgs);
13937
+ case "crm":
13938
+ return await runCrm(ctx, commandArgs);
13939
+ case "csm":
13940
+ return await runCsm(ctx, commandArgs);
13941
+ case "comments":
13942
+ return await runComments(ctx, commandArgs);
13943
+ case "sharing":
13944
+ return await runSharing(ctx, commandArgs);
13945
+ case "forms":
13946
+ return await runForms(ctx, commandArgs);
13947
+ case "email":
13948
+ return await runEmail(ctx, commandArgs);
13949
+ case "chat-widget":
13950
+ return await runChatWidget(ctx, commandArgs);
13951
+ case "marketplace":
13952
+ return await runMarketplace(ctx, commandArgs);
13953
+ case "kb":
13954
+ return await runKb(ctx, commandArgs);
13955
+ case "cms":
13956
+ return await runCms(ctx, commandArgs);
13957
+ case "documents":
13958
+ return await runDocuments(ctx, commandArgs);
13959
+ case "projects":
13960
+ return await runProjects(ctx, commandArgs);
13961
+ case "reviews":
13962
+ return await runReviews(ctx, commandArgs);
13963
+ case "workspaces":
13964
+ return await runWorkspaces(ctx, commandArgs);
13965
+ case "agent-profile":
13966
+ return await runAgentProfile(ctx, commandArgs);
13967
+ case "agent-allowlists":
13968
+ return await runAgentAllowlists(ctx, commandArgs);
13969
+ case "agent-packs":
13970
+ return await runAgentPacks(ctx, commandArgs);
13971
+ case "agent-ops":
13972
+ return await runAgentOps(ctx, commandArgs);
13973
+ case "evals":
13974
+ return await runEvals(ctx, commandArgs);
13975
+ case "twin":
13976
+ return await runTwin(ctx, commandArgs);
13977
+ case "strategy":
13978
+ return await runStrategy(ctx, commandArgs);
13979
+ case "advisors":
13980
+ return await runAdvisors(ctx, commandArgs);
13981
+ case "campaigns-orchestration":
13982
+ return await runCampaignsOrch(ctx, commandArgs);
13983
+ case "notebooks":
13984
+ return await runNotebooks(ctx, commandArgs);
13985
+ case "podcasts":
13986
+ return await runPodcasts(ctx, commandArgs);
13987
+ case "priority-matrix":
13988
+ return await runPriorityMatrix(ctx, commandArgs);
10633
13989
  case "byok":
10634
13990
  return await runByok(ctx, commandArgs);
10635
13991
  case "config":
@@ -10698,6 +14054,10 @@ async function runCli(argv, options = {}) {
10698
14054
  return await runTriggers(ctx, commandArgs);
10699
14055
  case "a2a":
10700
14056
  return await runA2a(ctx, commandArgs);
14057
+ case "completion":
14058
+ return runCompletion(ctx, commandArgs, COMMAND_NAMES);
14059
+ case "upgrade":
14060
+ return await runUpgrade(ctx, commandArgs);
10701
14061
  default:
10702
14062
  throw new CliError(`Unknown command: ${command}
10703
14063
  Run \`openwop --help\` for usage.`);
@@ -10711,6 +14071,7 @@ Run \`openwop --help\` for usage.`);
10711
14071
  const bodyMessage = err.body && typeof err.body === "object" && typeof err.body.message === "string" ? `: ${err.body.message}` : "";
10712
14072
  writeLine(io.stderr, `openwop: HTTP ${err.status}${bodyMessage}`);
10713
14073
  if (options.debugErrors) writeLine(io.stderr, String(err.stack ?? err));
14074
+ if (err.status === 401 || err.status === 403) return 4;
10714
14075
  return err.status >= 500 ? 1 : 2;
10715
14076
  }
10716
14077
  writeLine(io.stderr, `openwop: ${err instanceof Error ? err.message : String(err)}`);
@@ -10718,78 +14079,108 @@ Run \`openwop --help\` for usage.`);
10718
14079
  return 1;
10719
14080
  }
10720
14081
  }
14082
+ var HELP_MAP = {
14083
+ demo: DEMO_HELP,
14084
+ runs: RUNS_HELP,
14085
+ run: RUNS_HELP,
14086
+ chat: CHAT_HELP,
14087
+ workflows: WORKFLOWS_HELP,
14088
+ workflow: WORKFLOWS_HELP,
14089
+ catalog: CATALOG_HELP,
14090
+ packs: PACKS_HELP,
14091
+ pack: PACKS_HELP,
14092
+ onboard: ONBOARD_HELP,
14093
+ providers: PROVIDERS_HELP,
14094
+ provider: PROVIDERS_HELP,
14095
+ agents: AGENTS_HELP,
14096
+ agent: AGENTS_HELP,
14097
+ roster: ROSTER_HELP,
14098
+ "org-chart": ORG_CHART_HELP,
14099
+ orgchart: ORG_CHART_HELP,
14100
+ kanban: KANBAN_HELP,
14101
+ boards: KANBAN_HELP,
14102
+ orgs: ORGS_HELP,
14103
+ org: ORGS_HELP,
14104
+ workspace: WORKSPACE_HELP,
14105
+ byok: BYOK_HELP,
14106
+ brand: BRAND_HELP,
14107
+ crm: CRM_HELP,
14108
+ csm: CSM_HELP,
14109
+ comments: COMMENTS_HELP,
14110
+ sharing: SHARING_HELP,
14111
+ forms: FORMS_HELP,
14112
+ email: EMAIL_HELP,
14113
+ "chat-widget": CHAT_WIDGET_HELP,
14114
+ marketplace: MARKETPLACE_HELP,
14115
+ kb: KB_HELP,
14116
+ cms: CMS_HELP,
14117
+ documents: DOCUMENTS_HELP,
14118
+ projects: PROJECTS_HELP,
14119
+ reviews: REVIEWS_HELP,
14120
+ workspaces: WORKSPACES_HELP,
14121
+ "agent-profile": AGENT_PROFILE_HELP,
14122
+ "agent-allowlists": AGENT_ALLOWLISTS_HELP,
14123
+ "agent-packs": AGENT_PACKS_HELP,
14124
+ "agent-ops": AGENT_OPS_HELP,
14125
+ evals: EVALS_HELP,
14126
+ twin: TWIN_HELP,
14127
+ strategy: STRATEGY_HELP,
14128
+ advisors: ADVISORS_HELP,
14129
+ "campaigns-orchestration": CAMPAIGNS_ORCH_HELP,
14130
+ notebooks: NOTEBOOKS_HELP,
14131
+ podcasts: PODCASTS_HELP,
14132
+ "priority-matrix": PRIORITY_MATRIX_HELP,
14133
+ config: CONFIG_HELP,
14134
+ doctor: DOCTOR_HELP,
14135
+ health: HEALTH_HELP,
14136
+ capabilities: CAPABILITIES_HELP,
14137
+ caps: CAPABILITIES_HELP,
14138
+ media: MEDIA_HELP,
14139
+ conformance: CONFORMANCE_HELP,
14140
+ memory: MEMORY_HELP,
14141
+ webhooks: WEBHOOKS_HELP,
14142
+ webhook: WEBHOOKS_HELP,
14143
+ cron: CRON_HELP,
14144
+ messaging: MESSAGING_HELP,
14145
+ relay: RELAY_HELP,
14146
+ notifications: NOTIFICATIONS_HELP,
14147
+ notification: NOTIFICATIONS_HELP,
14148
+ interrupts: INTERRUPTS_HELP,
14149
+ interrupt: INTERRUPTS_HELP,
14150
+ prompts: PROMPTS_HELP,
14151
+ prompt: PROMPTS_HELP,
14152
+ notify: NOTIFY_HELP,
14153
+ account: ACCOUNT_HELP,
14154
+ admin: ADMIN_HELP,
14155
+ governance: GOVERNANCE_HELP,
14156
+ policy: GOVERNANCE_HELP,
14157
+ approvals: APPROVALS_HELP,
14158
+ approval: APPROVALS_HELP,
14159
+ consent: CONSENT_HELP,
14160
+ mcp: MCP_HELP,
14161
+ connections: CONNECTIONS_HELP,
14162
+ conn: CONNECTIONS_HELP,
14163
+ profiles: PROFILES_HELP,
14164
+ completion: COMPLETION_HELP,
14165
+ upgrade: UPGRADE_HELP,
14166
+ toggles: TOGGLES_HELP,
14167
+ users: USERS_HELP,
14168
+ workforces: WORKFORCES_HELP,
14169
+ fleet: WORKFORCES_HELP,
14170
+ auth: AUTH_HELP,
14171
+ sso: AUTH_HELP,
14172
+ analytics: ANALYTICS_HELP,
14173
+ usage: ANALYTICS_HELP,
14174
+ proposals: PROPOSALS_HELP,
14175
+ goals: GOALS_HELP,
14176
+ export: EXPORT_HELP,
14177
+ import: IMPORT_HELP,
14178
+ triggers: TRIGGERS_HELP,
14179
+ a2a: A2A_HELP
14180
+ };
14181
+ var COMMAND_NAMES = Object.keys(HELP_MAP);
10721
14182
  function showHelp(io, command) {
10722
- const map = {
10723
- demo: DEMO_HELP,
10724
- runs: RUNS_HELP,
10725
- run: RUNS_HELP,
10726
- chat: CHAT_HELP,
10727
- workflows: WORKFLOWS_HELP,
10728
- workflow: WORKFLOWS_HELP,
10729
- catalog: CATALOG_HELP,
10730
- packs: PACKS_HELP,
10731
- pack: PACKS_HELP,
10732
- onboard: ONBOARD_HELP,
10733
- providers: PROVIDERS_HELP,
10734
- provider: PROVIDERS_HELP,
10735
- agents: AGENTS_HELP,
10736
- agent: AGENTS_HELP,
10737
- roster: ROSTER_HELP,
10738
- "org-chart": ORG_CHART_HELP,
10739
- orgchart: ORG_CHART_HELP,
10740
- kanban: KANBAN_HELP,
10741
- boards: KANBAN_HELP,
10742
- orgs: ORGS_HELP,
10743
- org: ORGS_HELP,
10744
- workspace: WORKSPACE_HELP,
10745
- byok: BYOK_HELP,
10746
- config: CONFIG_HELP,
10747
- doctor: DOCTOR_HELP,
10748
- health: HEALTH_HELP,
10749
- capabilities: CAPABILITIES_HELP,
10750
- caps: CAPABILITIES_HELP,
10751
- media: MEDIA_HELP,
10752
- conformance: CONFORMANCE_HELP,
10753
- memory: MEMORY_HELP,
10754
- webhooks: WEBHOOKS_HELP,
10755
- webhook: WEBHOOKS_HELP,
10756
- cron: CRON_HELP,
10757
- messaging: MESSAGING_HELP,
10758
- relay: RELAY_HELP,
10759
- notifications: NOTIFICATIONS_HELP,
10760
- notification: NOTIFICATIONS_HELP,
10761
- interrupts: INTERRUPTS_HELP,
10762
- interrupt: INTERRUPTS_HELP,
10763
- prompts: PROMPTS_HELP,
10764
- prompt: PROMPTS_HELP,
10765
- notify: NOTIFY_HELP,
10766
- account: ACCOUNT_HELP,
10767
- admin: ADMIN_HELP,
10768
- governance: GOVERNANCE_HELP,
10769
- policy: GOVERNANCE_HELP,
10770
- approvals: APPROVALS_HELP,
10771
- approval: APPROVALS_HELP,
10772
- consent: CONSENT_HELP,
10773
- mcp: MCP_HELP,
10774
- connections: CONNECTIONS_HELP,
10775
- conn: CONNECTIONS_HELP,
10776
- profiles: PROFILES_HELP,
10777
- toggles: TOGGLES_HELP,
10778
- users: USERS_HELP,
10779
- workforces: WORKFORCES_HELP,
10780
- fleet: WORKFORCES_HELP,
10781
- auth: AUTH_HELP,
10782
- sso: AUTH_HELP,
10783
- analytics: ANALYTICS_HELP,
10784
- usage: ANALYTICS_HELP,
10785
- proposals: PROPOSALS_HELP,
10786
- goals: GOALS_HELP,
10787
- export: EXPORT_HELP,
10788
- import: IMPORT_HELP,
10789
- triggers: TRIGGERS_HELP,
10790
- a2a: A2A_HELP
10791
- };
10792
- write(io.stdout, map[command] ?? ROOT_HELP);
14183
+ write(io.stdout, HELP_MAP[command] ?? ROOT_HELP);
10793
14184
  return 0;
10794
14185
  }
10795
14186
  var ROOT_HELP = `openwop - operate OpenWOP hosts and the workflow-engine demo app
@@ -10798,14 +14189,24 @@ Usage:
10798
14189
  openwop [global options] <command> [options]
10799
14190
 
10800
14191
  Global options:
10801
- --base-url <url> Host base URL (default: OPENWOP_BASE_URL or http://localhost:8080)
10802
- --api-key <key> Bearer API key (default: OPENWOP_API_KEY, or sample-token for localhost)
14192
+ --base-url <url> Host base URL (default: --base-url > OPENWOP_BASE_URL > saved config > http://localhost:8080)
14193
+ --api-key <key> Bearer API key (default: OPENWOP_API_KEY > saved config > sample-token for localhost)
14194
+ --profile <name> Use a named config profile (~/.openwop-<name>); also OPENWOP_PROFILE
10803
14195
  --json Print machine-readable JSON where supported
10804
14196
  --quiet Suppress non-essential output
10805
14197
  --verbose Print extra diagnostics
10806
14198
  --version Print CLI version
10807
14199
  --help, -h Show help
10808
14200
 
14201
+ Exit codes:
14202
+ 0 success (a read succeeded; a run/goal completed; a check passed)
14203
+ 1 runtime/host error (HTTP >=500, unreachable host, an operation failed)
14204
+ 2 usage error (bad flags/args) or a client/host 4xx (e.g. not found, validation)
14205
+ 3 attention needed \u2014 a non-failure "open" state: a run escalated/paused, a
14206
+ proposal is pending, a goal is still open (set by run/goal/proposal/approval-style
14207
+ commands so scripts can branch on 0 vs 3 vs 1)
14208
+ 4 auth/permission denied (e.g. a super-admin surface without a super-admin principal)
14209
+
10809
14210
  Commands:
10810
14211
  onboard Guided first-run setup (host + provider + model + BYOK key)
10811
14212
  providers list List stored BYOK credential refs (never values)
@@ -10814,6 +14215,8 @@ Commands:
10814
14215
  providers test Verify the credential ref is reachable
10815
14216
  config file Print the local config file path
10816
14217
  config get|set|unset Read or modify ~/.openwop/config.json
14218
+ completion <shell> Emit a bash/zsh/fish completion script
14219
+ upgrade Check npm for a newer @openwop/cli
10817
14220
  doctor Check local prerequisites and demo reachability
10818
14221
  demo status Inspect the workflow-engine demo app
10819
14222
  demo start Start the demo backend and frontend (--detach to background)
@@ -10870,6 +14273,34 @@ Commands:
10870
14273
  byok list List BYOK credential refs (never values)
10871
14274
  byok set Store a BYOK secret (prompts if --value omitted)
10872
14275
  byok delete Remove a BYOK secret ref
14276
+ brand public Show the applied white-label app identity (ADR 0170/0171)
14277
+ brand get|set Read / edit the app brand + generative theme (super-admin)
14278
+ crm list|create Manage CRM contacts (+ get/update/delete/triage)
14279
+ csm list|create Manage Customer-Success accounts (+ get/update/delete)
14280
+ comments list Threaded comments on a resource (--org; +create/update/delete)
14281
+ sharing list Shareable resource links (--org; +create/revoke/resolve)
14282
+ forms list Form builder + intake (--org; +create/update/status/submissions)
14283
+ email templates Email templates + campaigns (--org; create/send/sends)
14284
+ chat-widget list Embeddable chat widgets (--org; +create/rotate-token)
14285
+ marketplace listings Browse/install packs + reviews (install; review --org)
14286
+ kb collections Knowledge base (--org; collections/docs/search/rag)
14287
+ cms pages CMS pages + authoring lifecycle (--org; submit/approve/publish)
14288
+ documents list Document generation + templates (--org; versions/render)
14289
+ projects list Project workspaces (create --org; +members)
14290
+ reviews list The unified review inbox (get; action <id> <action>)
14291
+ workspaces list Your B2B workspaces / tenants (create/switch; vs the file 'workspace')
14292
+ agent-profile get Rich agent profile + connector readiness (set/readiness)
14293
+ agent-allowlists Super-admin per-agent tool allowlists (list/get/set/clear)
14294
+ agent-packs list Agent (persona) pack registry (install)
14295
+ agent-ops status Demo example-data + roster/fleet activity (seed/run/clear)
14296
+ evals leaderboard Model eval leaderboard + arena (--org; match)
14297
+ twin get Agent digital-twin config + grants (set/grant/revoke)
14298
+ strategy list Strategy documents (create --org; context/health)
14299
+ advisors list Advisory boards (create --org; by-handle)
14300
+ campaigns-orchestration Orchestrated campaigns (create/finalize)
14301
+ notebooks list NotebookLM-style notebooks (create --org; notes)
14302
+ podcasts list Podcast episodes (create/retry)
14303
+ priority-matrix lists Prioritization boards (create --org; ideas)
10873
14304
  webhooks list List webhook subscriptions
10874
14305
  webhooks add Register a webhook subscription
10875
14306
  webhooks remove Delete a webhook subscription
@@ -10942,6 +14373,7 @@ Examples:
10942
14373
  openwop packs install community.openwop-team.demo --version 0.1.0
10943
14374
  `;
10944
14375
  export {
14376
+ COMMAND_NAMES,
10945
14377
  DEFAULT_BASE_URL,
10946
14378
  DEFAULT_REGISTRY_URL,
10947
14379
  HOST_PRESETS,