@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 +25 -5
- package/dist/cli.js +107 -6
- package/dist/openwop.js +107 -6
- package/package.json +1 -1
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)
|
|
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
|
-
>
|
|
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,
|
|
972
|
-
const
|
|
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
|
|
1049
|
-
const
|
|
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,
|
|
973
|
-
const
|
|
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
|
|
1050
|
-
const
|
|
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;
|