@openwop/cli 0.18.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenWOP CLI
2
2
 
3
- The OpenWOP CLI is the local control plane for the [OpenWOP](https://github.com/openwop/openwop) demo app (`apps/workflow-engine`, live at [app.openwop.dev](https://app.openwop.dev)) and a lightweight client for any OpenWOP-compatible host.
3
+ The OpenWOP CLI is the local control plane for the [OpenWOP](https://github.com/openwop/openwop) reference app ([`openwop/openwop-app`](https://github.com/openwop/openwop-app), live at [app.openwop.dev](https://app.openwop.dev)) and a lightweight client for any OpenWOP-compatible host.
4
4
 
5
5
  This is the standalone home of the `@openwop/cli` package; it ships independently of the [`openwop/openwop`](https://github.com/openwop/openwop) spec corpus.
6
6
 
@@ -12,16 +12,36 @@ The CLI is a host-agnostic **control plane**: every group drives one protocol su
12
12
  |---|---|
13
13
  | **Setup & health** | `onboard` · `doctor` · `demo` · `health` · `capabilities` · `config` · `completion` · `upgrade` |
14
14
  | **Run lifecycle** | `runs` · `chat` · `interrupts` · `workflows` · `catalog` · `media` |
15
- | **Agents & orchestration** | `agents` · `roster` · `org-chart` · `kanban` · `workforces` (`fleet`) |
15
+ | **Agents & orchestration** | `agents` · `roster` · `org-chart` · `kanban` (`boards`) · `workforces` (`fleet`) · `agent-profile` · `agent-packs` · `agent-allowlists` · `agent-ops` · `evals` · `twin` · `reviews` |
16
16
  | **Governance & safety** | `approvals` · `governance` (`policy`) · `consent` · `toggles` |
17
- | **Identity & access** | `orgs` · `users` · `profiles` · `auth` (`sso`) |
17
+ | **Identity & access** | `orgs` (`org`) · `workspaces` · `users` · `profiles` · `auth` (`sso`) |
18
18
  | **Extensibility & connections** | `packs` · `mcp` · `connections` (`conn`) · `providers` · `byok` |
19
19
  | **Memory & workspace** | `memory` · `workspace` · `prompts` |
20
20
  | **Automation & messaging** | `cron` · `webhooks` · `messaging` · `relay` · `notifications` · `notify` |
21
21
  | **Observability & admin** | `analytics` (`usage`) · `account` · `admin` · `brand` · `conformance` |
22
- | **Product surfaces** | `crm` · `csm` · `comments` · `sharing` · `forms` · `email` · `chat-widget` · `marketplace` · `kb` · `cms` · `documents` · `projects` |
22
+ | **Product surfaces** | `crm` · `csm` · `comments` · `sharing` · `forms` · `email` · `chat-widget` · `marketplace` · `kb` · `cms` · `documents` · `projects` · `strategy` · `advisors` · `campaigns-orchestration` · `notebooks` · `podcasts` · `priority-matrix` |
23
+ | **Protocol surfaces (RFC)** | `a2a` · `triggers` · `goals` · `proposals` · `export` / `import` |
23
24
 
24
- > **Recent sprint (`[Unreleased]`, slated for `0.3.0`):** 11 new groups + a `cron enable/disable` extension surfaced the host's governance/safety, identity, extensibility, orchestration, and observability surfaces — `approvals`, `governance`, `consent`, `toggles`, `users`, `profiles`, `auth`, `mcp`, `connections`, `workforces`, `analytics`. See [`CHANGELOG.md`](./CHANGELOG.md) for the per-group detail.
25
+ > **`0.18.x`:** the catalog now covers the full app feature surface — the agent
26
+ > platform (`agent-profile`, `agent-packs`, `agent-allowlists`, `agent-ops`,
27
+ > `evals`, `twin`, `reviews`), workspace tenancy (`workspaces`), and the product
28
+ > surfaces (`strategy`, `advisors`, `campaigns-orchestration`, `notebooks`,
29
+ > `podcasts`, `priority-matrix`) — alongside the protocol RFC surfaces (`a2a`,
30
+ > `triggers`, `goals`, `proposals`, `export`/`import`). See [`CHANGELOG.md`](./CHANGELOG.md)
31
+ > for the per-group detail.
32
+
33
+ ## Protocol version support (v2 and v1)
34
+
35
+ The CLI speaks the **OpenWOP wire directly** — no SDK dependency — and negotiates the protocol major once per invocation (`src/protocol.ts`, per `spec/v2/core/versioning.md` §1.5):
36
+
37
+ - It reads `/.well-known/openwop` and selects the **highest major it implements that the host advertises** in `protocolVersions[]` — 2 when the host lists any `2.x`, otherwise 1.
38
+ - Under major 2, every request for an operation named in `spec/v2/path-manifest.json` goes to the **unversioned path with `OpenWOP-Version: 2.0`** (`/v1/runs` → `/runs`). Host-proprietary routes the manifest does not name — the demo backend's `/v1/host/sample/*` — are sent exactly as written; they have no v2 home yet (`versioning.md` §5).
39
+ - Under major 1 nothing changes: `/v1/…` paths, header-less, the v1 default the overlap guarantees.
40
+ - `OPENWOP_PROTOCOL_MAJOR=1` or `=2` pins the major without probing discovery.
41
+
42
+ `openwop doctor`'s `protocol` row reports `protocolVersions`, `preferredVersion`, and the major this process selected; it fails only when the host advertises neither major the CLI implements.
43
+
44
+ The pre-1.0 `0.18.x` line was frozen v1-only; that decision was reversed with 1.0.0 (CHANGELOG). The frozen line stays on branch `cli-v1-frozen` for anyone who needs a client that never sends `OpenWOP-Version`.
25
45
 
26
46
  ## Install
27
47
 
package/dist/cli.js CHANGED
@@ -967,14 +967,105 @@ function project(root, name, relativeDir) {
967
967
  // src/sse.ts
968
968
  import { createInterface as createInterface2 } from "node:readline";
969
969
 
970
+ // src/protocol.ts
971
+ var CLI_PROTOCOL_MAJORS = [2, 1];
972
+ var V2_PATH_TEMPLATES = [
973
+ "/agents",
974
+ "/agents/org-chart",
975
+ "/agents/org-chart/{departmentId}",
976
+ "/agents/roster",
977
+ "/agents/roster/{rosterId}",
978
+ "/agents/{agentId}",
979
+ "/agents/{agentId}/deployments",
980
+ "/audit/verify",
981
+ "/content/pages",
982
+ "/content/pages/{pageId}/sections/{sectionId}",
983
+ "/content/pages/{slug}",
984
+ "/content/settings",
985
+ "/host/effect-seams",
986
+ "/host/events",
987
+ "/interrupts/{token}",
988
+ "/prompts",
989
+ "/prompts/{templateId}",
990
+ "/prompts:render",
991
+ "/runs",
992
+ "/runs/{runId}",
993
+ "/runs/{runId}/ancestry",
994
+ "/runs/{runId}/annotations",
995
+ "/runs/{runId}/artifacts/{artifactId}",
996
+ "/runs/{runId}/cancel",
997
+ "/runs/{runId}/compensation",
998
+ "/runs/{runId}/effects",
999
+ "/runs/{runId}/eval-summary",
1000
+ "/runs/{runId}/events",
1001
+ "/runs/{runId}/events/poll",
1002
+ "/runs/{runId}/interrupts/{nodeId}",
1003
+ "/runs/{runId}:diff",
1004
+ "/runs/{runId}:fork",
1005
+ "/runs/{runId}:pause",
1006
+ "/runs/{runId}:resume",
1007
+ "/runs:bulk-cancel",
1008
+ "/tools",
1009
+ "/tools/{toolId}",
1010
+ "/trigger-subscriptions",
1011
+ "/webhooks",
1012
+ "/webhooks/{webhookId}",
1013
+ "/workflows/{workflowId}"
1014
+ ];
1015
+ var V2_MATCHERS = V2_PATH_TEMPLATES.map(
1016
+ (t) => new RegExp(`^${t.replace(/[.:]/g, "\\$&").replace(/\{[A-Za-z]+\}/g, "[^/]+")}$`)
1017
+ );
1018
+ function v2Twin(path) {
1019
+ const q = path.indexOf("?");
1020
+ const bare = q === -1 ? path : path.slice(0, q);
1021
+ const query = q === -1 ? "" : path.slice(q);
1022
+ if (!bare.startsWith("/v1/")) return null;
1023
+ const unversioned = bare.slice("/v1".length);
1024
+ return V2_MATCHERS.some((m) => m.test(unversioned)) ? `${unversioned}${query}` : null;
1025
+ }
1026
+ function selectMajor(protocolVersions) {
1027
+ const advertised = Array.isArray(protocolVersions) ? protocolVersions.filter((v) => typeof v === "string") : [];
1028
+ for (const major of CLI_PROTOCOL_MAJORS) {
1029
+ if (advertised.some((v) => v.startsWith(`${major}.`))) return major;
1030
+ }
1031
+ return 1;
1032
+ }
1033
+ async function negotiateMajor(ctx) {
1034
+ if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
1035
+ const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
1036
+ if (pinned === "1" || pinned === "2") {
1037
+ ctx.protocolMajor = Number(pinned);
1038
+ return ctx.protocolMajor;
1039
+ }
1040
+ let major = 1;
1041
+ try {
1042
+ const url = new URL(".well-known/openwop", ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
1043
+ const res = await ctx.fetchImpl(url, { method: "GET", headers: { accept: "application/json" } });
1044
+ if (res.ok) {
1045
+ const doc = JSON.parse(await res.text());
1046
+ major = selectMajor(doc?.protocolVersions);
1047
+ }
1048
+ } catch {
1049
+ }
1050
+ ctx.protocolMajor = major;
1051
+ return major;
1052
+ }
1053
+ async function resolveRequest(ctx, path, headers) {
1054
+ const major = await negotiateMajor(ctx);
1055
+ if (major !== 2) return { path, headers };
1056
+ const twin = v2Twin(path);
1057
+ if (twin === null) return { path, headers };
1058
+ return { path: twin, headers: { ...headers, "openwop-version": "2.0" } };
1059
+ }
1060
+
970
1061
  // src/api.ts
971
- async function requestJson(ctx, path, options = {}) {
972
- const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
973
- const headers = {
1062
+ async function requestJson(ctx, requestedPath, options = {}) {
1063
+ const { path, headers } = await resolveRequest(ctx, requestedPath, {
974
1064
  accept: "application/json",
975
1065
  ...options.body !== void 0 ? { "content-type": "application/json" } : {},
976
1066
  ...options.headers ?? {}
977
- };
1067
+ });
1068
+ const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
978
1069
  if (options.auth !== false && ctx.apiKey) {
979
1070
  headers.authorization = `Bearer ${ctx.apiKey}`;
980
1071
  }
@@ -1045,8 +1136,8 @@ async function streamRunEvents(ctx, runId, { onEvent, useStream = true, timeoutM
1045
1136
  await streamViaPoll(ctx, runId, onEvent, timeoutMs);
1046
1137
  }
1047
1138
  async function streamViaSse(ctx, runId, onEvent) {
1048
- const url = new URL(`/v1/runs/${encodeURIComponent(runId)}/events`, ctx.baseUrl);
1049
- const headers = { accept: "text/event-stream" };
1139
+ const { path, headers } = await resolveRequest(ctx, `/v1/runs/${encodeURIComponent(runId)}/events`, { accept: "text/event-stream" });
1140
+ const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
1050
1141
  if (ctx.apiKey) headers.authorization = `Bearer ${ctx.apiKey}`;
1051
1142
  const res = await ctx.fetchImpl(url, { method: "GET", headers });
1052
1143
  if (!res.ok) throw new HttpError(`HTTP ${res.status}`, res.status, null);
@@ -9291,6 +9382,16 @@ async function runDoctor(ctx, argv) {
9291
9382
  const health = await probeEndpoint(ctx, "/health");
9292
9383
  if (health.ok) checks.push(ok("demo health", `${ctx.baseUrl}/health responded`));
9293
9384
  else checks.push(warn("demo health", `demo is not reachable at ${ctx.baseUrl} (${health.message})`));
9385
+ const discovery = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
9386
+ const versions = discovery?.ok ? discovery.body?.protocolVersions : void 0;
9387
+ if (Array.isArray(versions) && versions.length > 0) {
9388
+ const advertised = versions.filter((v) => typeof v === "string");
9389
+ const preferred = typeof discovery.body?.preferredVersion === "string" ? discovery.body.preferredVersion : "?";
9390
+ const selected = await negotiateMajor(ctx);
9391
+ const detail = `protocolVersions ${advertised.join(", ")}; preferredVersion ${preferred}; CLI speaks major ${selected}`;
9392
+ if (advertised.some((v) => v.startsWith(`${selected}.`))) checks.push(ok("protocol", detail));
9393
+ else checks.push(fail("protocol", `${detail} \u2014 host advertises neither major this CLI implements; see README \xA7"Protocol version support"`));
9394
+ }
9294
9395
  const daemon = await safeRequest(ctx, "/v1/host/sample/daemon-status");
9295
9396
  if (daemon.ok && daemon.body) {
9296
9397
  const b = daemon.body;
package/dist/openwop.js CHANGED
@@ -968,14 +968,105 @@ function project(root, name, relativeDir) {
968
968
  // src/sse.ts
969
969
  import { createInterface as createInterface2 } from "node:readline";
970
970
 
971
+ // src/protocol.ts
972
+ var CLI_PROTOCOL_MAJORS = [2, 1];
973
+ var V2_PATH_TEMPLATES = [
974
+ "/agents",
975
+ "/agents/org-chart",
976
+ "/agents/org-chart/{departmentId}",
977
+ "/agents/roster",
978
+ "/agents/roster/{rosterId}",
979
+ "/agents/{agentId}",
980
+ "/agents/{agentId}/deployments",
981
+ "/audit/verify",
982
+ "/content/pages",
983
+ "/content/pages/{pageId}/sections/{sectionId}",
984
+ "/content/pages/{slug}",
985
+ "/content/settings",
986
+ "/host/effect-seams",
987
+ "/host/events",
988
+ "/interrupts/{token}",
989
+ "/prompts",
990
+ "/prompts/{templateId}",
991
+ "/prompts:render",
992
+ "/runs",
993
+ "/runs/{runId}",
994
+ "/runs/{runId}/ancestry",
995
+ "/runs/{runId}/annotations",
996
+ "/runs/{runId}/artifacts/{artifactId}",
997
+ "/runs/{runId}/cancel",
998
+ "/runs/{runId}/compensation",
999
+ "/runs/{runId}/effects",
1000
+ "/runs/{runId}/eval-summary",
1001
+ "/runs/{runId}/events",
1002
+ "/runs/{runId}/events/poll",
1003
+ "/runs/{runId}/interrupts/{nodeId}",
1004
+ "/runs/{runId}:diff",
1005
+ "/runs/{runId}:fork",
1006
+ "/runs/{runId}:pause",
1007
+ "/runs/{runId}:resume",
1008
+ "/runs:bulk-cancel",
1009
+ "/tools",
1010
+ "/tools/{toolId}",
1011
+ "/trigger-subscriptions",
1012
+ "/webhooks",
1013
+ "/webhooks/{webhookId}",
1014
+ "/workflows/{workflowId}"
1015
+ ];
1016
+ var V2_MATCHERS = V2_PATH_TEMPLATES.map(
1017
+ (t) => new RegExp(`^${t.replace(/[.:]/g, "\\$&").replace(/\{[A-Za-z]+\}/g, "[^/]+")}$`)
1018
+ );
1019
+ function v2Twin(path) {
1020
+ const q = path.indexOf("?");
1021
+ const bare = q === -1 ? path : path.slice(0, q);
1022
+ const query = q === -1 ? "" : path.slice(q);
1023
+ if (!bare.startsWith("/v1/")) return null;
1024
+ const unversioned = bare.slice("/v1".length);
1025
+ return V2_MATCHERS.some((m) => m.test(unversioned)) ? `${unversioned}${query}` : null;
1026
+ }
1027
+ function selectMajor(protocolVersions) {
1028
+ const advertised = Array.isArray(protocolVersions) ? protocolVersions.filter((v) => typeof v === "string") : [];
1029
+ for (const major of CLI_PROTOCOL_MAJORS) {
1030
+ if (advertised.some((v) => v.startsWith(`${major}.`))) return major;
1031
+ }
1032
+ return 1;
1033
+ }
1034
+ async function negotiateMajor(ctx) {
1035
+ if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
1036
+ const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
1037
+ if (pinned === "1" || pinned === "2") {
1038
+ ctx.protocolMajor = Number(pinned);
1039
+ return ctx.protocolMajor;
1040
+ }
1041
+ let major = 1;
1042
+ try {
1043
+ const url = new URL(".well-known/openwop", ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
1044
+ const res = await ctx.fetchImpl(url, { method: "GET", headers: { accept: "application/json" } });
1045
+ if (res.ok) {
1046
+ const doc = JSON.parse(await res.text());
1047
+ major = selectMajor(doc?.protocolVersions);
1048
+ }
1049
+ } catch {
1050
+ }
1051
+ ctx.protocolMajor = major;
1052
+ return major;
1053
+ }
1054
+ async function resolveRequest(ctx, path, headers) {
1055
+ const major = await negotiateMajor(ctx);
1056
+ if (major !== 2) return { path, headers };
1057
+ const twin = v2Twin(path);
1058
+ if (twin === null) return { path, headers };
1059
+ return { path: twin, headers: { ...headers, "openwop-version": "2.0" } };
1060
+ }
1061
+
971
1062
  // src/api.ts
972
- async function requestJson(ctx, path, options = {}) {
973
- const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
974
- const headers = {
1063
+ async function requestJson(ctx, requestedPath, options = {}) {
1064
+ const { path, headers } = await resolveRequest(ctx, requestedPath, {
975
1065
  accept: "application/json",
976
1066
  ...options.body !== void 0 ? { "content-type": "application/json" } : {},
977
1067
  ...options.headers ?? {}
978
- };
1068
+ });
1069
+ const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
979
1070
  if (options.auth !== false && ctx.apiKey) {
980
1071
  headers.authorization = `Bearer ${ctx.apiKey}`;
981
1072
  }
@@ -1046,8 +1137,8 @@ async function streamRunEvents(ctx, runId, { onEvent, useStream = true, timeoutM
1046
1137
  await streamViaPoll(ctx, runId, onEvent, timeoutMs);
1047
1138
  }
1048
1139
  async function streamViaSse(ctx, runId, onEvent) {
1049
- const url = new URL(`/v1/runs/${encodeURIComponent(runId)}/events`, ctx.baseUrl);
1050
- const headers = { accept: "text/event-stream" };
1140
+ const { path, headers } = await resolveRequest(ctx, `/v1/runs/${encodeURIComponent(runId)}/events`, { accept: "text/event-stream" });
1141
+ const url = new URL(path.replace(/^\//, ""), ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
1051
1142
  if (ctx.apiKey) headers.authorization = `Bearer ${ctx.apiKey}`;
1052
1143
  const res = await ctx.fetchImpl(url, { method: "GET", headers });
1053
1144
  if (!res.ok) throw new HttpError(`HTTP ${res.status}`, res.status, null);
@@ -9292,6 +9383,16 @@ async function runDoctor(ctx, argv) {
9292
9383
  const health = await probeEndpoint(ctx, "/health");
9293
9384
  if (health.ok) checks.push(ok("demo health", `${ctx.baseUrl}/health responded`));
9294
9385
  else checks.push(warn("demo health", `demo is not reachable at ${ctx.baseUrl} (${health.message})`));
9386
+ const discovery = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
9387
+ const versions = discovery?.ok ? discovery.body?.protocolVersions : void 0;
9388
+ if (Array.isArray(versions) && versions.length > 0) {
9389
+ const advertised = versions.filter((v) => typeof v === "string");
9390
+ const preferred = typeof discovery.body?.preferredVersion === "string" ? discovery.body.preferredVersion : "?";
9391
+ const selected = await negotiateMajor(ctx);
9392
+ const detail = `protocolVersions ${advertised.join(", ")}; preferredVersion ${preferred}; CLI speaks major ${selected}`;
9393
+ if (advertised.some((v) => v.startsWith(`${selected}.`))) checks.push(ok("protocol", detail));
9394
+ else checks.push(fail("protocol", `${detail} \u2014 host advertises neither major this CLI implements; see README \xA7"Protocol version support"`));
9395
+ }
9295
9396
  const daemon = await safeRequest(ctx, "/v1/host/sample/daemon-status");
9296
9397
  if (daemon.ok && daemon.body) {
9297
9398
  const b = daemon.body;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/cli",
3
- "version": "0.18.2",
3
+ "version": "1.0.0",
4
4
  "description": "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",