@openwop/cli 0.3.0 → 0.18.1

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