@openwop/cli 1.4.1 → 1.6.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 +1 -1
- package/dist/cli.js +72 -15
- package/dist/openwop.js +71 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ openwop packs search ads # browse the signed pack registry
|
|
|
134
134
|
|
|
135
135
|
The `onboard` wizard walks you through:
|
|
136
136
|
|
|
137
|
-
1. **Host URL** — `https://app.openwop.dev/api`
|
|
137
|
+
1. **Host URL** — `https://api.openwop.dev` (shared demo; `https://app.openwop.dev/api` also works), `http://localhost:8080` (local), or a custom URL.
|
|
138
138
|
2. **AI provider** — `anthropic`, `openai`, `google`, or `minimax` (matches what the demo backend dispatches to).
|
|
139
139
|
3. **API key** — auto-detects `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `MINIMAX_API_KEY`, or hidden-input via raw-mode stdin. The key is POSTed to `/v1/host/openwop-app/byok/secrets` on the configured host. **The key is never written to your local config file** — only a credential ref pointer is stored.
|
|
140
140
|
4. **Model** — provider-specific recommended defaults plus a custom option.
|
package/dist/cli.js
CHANGED
|
@@ -578,7 +578,7 @@ function errText(err) {
|
|
|
578
578
|
// package.json
|
|
579
579
|
var package_default = {
|
|
580
580
|
name: "@openwop/cli",
|
|
581
|
-
version: "1.
|
|
581
|
+
version: "1.6.0",
|
|
582
582
|
description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
|
|
583
583
|
license: "Apache-2.0",
|
|
584
584
|
type: "module",
|
|
@@ -667,7 +667,10 @@ var PROVIDER_CATALOG = {
|
|
|
667
667
|
}
|
|
668
668
|
};
|
|
669
669
|
var HOST_PRESETS = [
|
|
670
|
-
|
|
670
|
+
// openwop-app ADR 0827: the protocol origin, served straight from Cloud Run with
|
|
671
|
+
// no CDN in front (`https://app.openwop.dev/api` still works; its edge drops
|
|
672
|
+
// conditional requests). `openwop login` opens the host-named web page.
|
|
673
|
+
{ key: "shared", label: "Shared demo at https://api.openwop.dev (recommended for trying things out)", url: "https://api.openwop.dev" },
|
|
671
674
|
{ key: "local", label: "Local demo at http://localhost:8080 (run `openwop demo start` to launch)", url: "http://localhost:8080" }
|
|
672
675
|
];
|
|
673
676
|
|
|
@@ -1206,6 +1209,14 @@ function hostRootFor(path, roots) {
|
|
|
1206
1209
|
}
|
|
1207
1210
|
return best === null ? null : `${roots[best]}${path.slice(best.length)}`;
|
|
1208
1211
|
}
|
|
1212
|
+
function hostSurfaceAdvertised(discovery, v1Prefix) {
|
|
1213
|
+
if (!discovery || typeof discovery !== "object") return false;
|
|
1214
|
+
const { paths, extensions } = discovery;
|
|
1215
|
+
if (paths !== null && typeof paths === "object") {
|
|
1216
|
+
return Object.keys(paths).some((p) => p.startsWith(v1Prefix));
|
|
1217
|
+
}
|
|
1218
|
+
return hostRootFor(v1Prefix, hostRootsFrom(extensions)) !== null;
|
|
1219
|
+
}
|
|
1209
1220
|
async function negotiateMajor(ctx) {
|
|
1210
1221
|
if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
|
|
1211
1222
|
const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
|
|
@@ -14925,13 +14936,21 @@ Approve a code ONLY if you just ran \`openwop login\` yourself.
|
|
|
14925
14936
|
Exit codes: 0 signed in / signed out \xB7 1 denied, expired or not available on this host \xB7 2 usage.
|
|
14926
14937
|
|
|
14927
14938
|
Examples:
|
|
14928
|
-
export OPENWOP_BASE_URL=https://
|
|
14939
|
+
export OPENWOP_BASE_URL=https://api.openwop.dev
|
|
14929
14940
|
openwop login
|
|
14930
14941
|
openwop login --label "work laptop"
|
|
14931
14942
|
openwop logout
|
|
14932
14943
|
`;
|
|
14933
14944
|
var sleep2 = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
14934
|
-
function approvalUrl(baseUrl, path) {
|
|
14945
|
+
function approvalUrl(baseUrl, path, uri) {
|
|
14946
|
+
if (typeof uri === "string") {
|
|
14947
|
+
try {
|
|
14948
|
+
const u = new URL(uri);
|
|
14949
|
+
const loopback = u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]";
|
|
14950
|
+
if (u.protocol === "https:" || u.protocol === "http:" && loopback) return u.toString();
|
|
14951
|
+
} catch {
|
|
14952
|
+
}
|
|
14953
|
+
}
|
|
14935
14954
|
const p = typeof path === "string" && path.startsWith("/") ? path : "/access?tab=api-keys";
|
|
14936
14955
|
try {
|
|
14937
14956
|
return `${new URL(baseUrl).origin}${p}`;
|
|
@@ -14969,7 +14988,7 @@ async function runLogin(ctx, argv) {
|
|
|
14969
14988
|
const userCode = started?.userCode;
|
|
14970
14989
|
if (typeof deviceCode !== "string" || typeof userCode !== "string") throw notAvailable(ctx);
|
|
14971
14990
|
writeLine(ctx.io.stderr, "");
|
|
14972
|
-
writeLine(ctx.io.stderr, ` 1. Open ${approvalUrl(ctx.baseUrl, started.verificationPath)}`);
|
|
14991
|
+
writeLine(ctx.io.stderr, ` 1. Open ${approvalUrl(ctx.baseUrl, started.verificationPath, started.verificationUri)}`);
|
|
14973
14992
|
writeLine(ctx.io.stderr, ` 2. Enter ${userCode} under "Sign in the OpenWOP CLI", and approve.`);
|
|
14974
14993
|
writeLine(ctx.io.stderr, "");
|
|
14975
14994
|
writeLine(ctx.io.stderr, "Waiting for approval\u2026");
|
|
@@ -16887,8 +16906,8 @@ var APPROVALS_ROUTES = [
|
|
|
16887
16906
|
var APPROVALS_HELP = `Usage:
|
|
16888
16907
|
openwop approvals list [--status pending|approved|rejected] [--json]
|
|
16889
16908
|
openwop approvals get <approvalId> [--json]
|
|
16890
|
-
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16891
|
-
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16909
|
+
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16910
|
+
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16892
16911
|
openwop approvals sla-policy [--json]
|
|
16893
16912
|
openwop approvals sla-policy set (--enabled | --disabled) [--remind-after-ms n] [--escalate-after-ms n] [--expire-after-ms n] [--json]
|
|
16894
16913
|
openwop approvals email-pref [--json]
|
|
@@ -16926,6 +16945,11 @@ Endpoints:
|
|
|
16926
16945
|
--acted-for <s> (claim/reject) As a delegate covering several people, whose approval this is.
|
|
16927
16946
|
--expected-hash <h> (claim/reject) The definition hash you reviewed (composed-workflow proposals;
|
|
16928
16947
|
the host refuses the decision if the proposal changed since).
|
|
16948
|
+
--content-hash <h> (claim/reject) The contentHash of the assistant-action card you reviewed
|
|
16949
|
+
(\`approvals get\` prints it under the card). REQUIRED to claim an
|
|
16950
|
+
assistant action (ADR 0862): the host refuses a claim without it (exit 2),
|
|
16951
|
+
and refuses one whose action changed since you read it (exit 1; re-read
|
|
16952
|
+
it with \`approvals get\` and review the new card).
|
|
16929
16953
|
--status <s> (list) Filter the queue: pending | approved | rejected (default: all).
|
|
16930
16954
|
--note <text> (claim/reject) Optional human note recorded with the decision.
|
|
16931
16955
|
--json Print the raw host response instead of the rendered view.
|
|
@@ -16942,6 +16966,8 @@ Examples:
|
|
|
16942
16966
|
openwop approvals list --status pending
|
|
16943
16967
|
openwop approvals get appr_123 --json
|
|
16944
16968
|
openwop approvals claim appr_123 --note "LGTM, ship it"
|
|
16969
|
+
openwop approvals get appr_456 # an assistant action: read the card
|
|
16970
|
+
openwop approvals claim appr_456 --content-hash 9f2c\u2026 # \u2026then approve exactly what you read
|
|
16945
16971
|
openwop approvals reject appr_123 --note "out of policy"
|
|
16946
16972
|
|
|
16947
16973
|
Teams approval delivery:
|
|
@@ -16988,8 +17014,7 @@ function exitForStatus(status) {
|
|
|
16988
17014
|
async function ensureApprovalsAdvertised(ctx) {
|
|
16989
17015
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
16990
17016
|
if (!wk.ok) return;
|
|
16991
|
-
const
|
|
16992
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/approvals"));
|
|
17017
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/approvals");
|
|
16993
17018
|
if (!advertised) {
|
|
16994
17019
|
throw new CliError(
|
|
16995
17020
|
"approvals: this host does not advertise the approval-inbox surface (/v1/host/openwop-app/approvals is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -17074,12 +17099,31 @@ async function runApprovalsGet(ctx, argv) {
|
|
|
17074
17099
|
if (approval.resolvedAt) writeLine(ctx.io.stdout, `resolvedAt: ${approval.resolvedAt}`);
|
|
17075
17100
|
if (approval.runId) writeLine(ctx.io.stdout, `runId: ${approval.runId}`);
|
|
17076
17101
|
if (approval.note) writeLine(ctx.io.stdout, `note: ${approval.note}`);
|
|
17102
|
+
if (approval.action && typeof approval.action === "object") renderActionCard(ctx, approval.action);
|
|
17077
17103
|
return exitForStatus(approval.status);
|
|
17078
17104
|
}
|
|
17105
|
+
function renderActionCard(ctx, action) {
|
|
17106
|
+
writeLine(ctx.io.stdout, "action:");
|
|
17107
|
+
if (action.actionId) writeLine(ctx.io.stdout, ` actionId: ${action.actionId}`);
|
|
17108
|
+
if (action.kind) writeLine(ctx.io.stdout, ` kind: ${action.kind}`);
|
|
17109
|
+
if (action.riskLevel) writeLine(ctx.io.stdout, ` riskLevel: ${action.riskLevel}`);
|
|
17110
|
+
if (action.reason) writeLine(ctx.io.stdout, ` reason: ${action.reason}`);
|
|
17111
|
+
if (typeof action.draft === "string" && action.draft.length) writeLine(ctx.io.stdout, ` draft: ${action.draft}`);
|
|
17112
|
+
if (action.payload && typeof action.payload === "object") {
|
|
17113
|
+
for (const [k, v] of Object.entries(action.payload)) {
|
|
17114
|
+
writeLine(ctx.io.stdout, ` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
17115
|
+
}
|
|
17116
|
+
}
|
|
17117
|
+
if (action.contentHash) writeLine(ctx.io.stdout, ` contentHash: ${action.contentHash} (claim with --content-hash ${action.contentHash})`);
|
|
17118
|
+
}
|
|
17119
|
+
function detailsOf(body) {
|
|
17120
|
+
const d = body && typeof body === "object" ? body.details : void 0;
|
|
17121
|
+
return d && typeof d === "object" ? d : {};
|
|
17122
|
+
}
|
|
17079
17123
|
async function runApprovalsResolve(ctx, argv, verb) {
|
|
17080
|
-
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash"] });
|
|
17124
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash", "--content-hash"] });
|
|
17081
17125
|
if (options.help || positionals.length !== 1) {
|
|
17082
|
-
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
17126
|
+
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
17083
17127
|
`);
|
|
17084
17128
|
return options.help ? 0 : 2;
|
|
17085
17129
|
}
|
|
@@ -17089,11 +17133,25 @@ async function runApprovalsResolve(ctx, argv, verb) {
|
|
|
17089
17133
|
if (options.note !== void 0) decision.note = options.note;
|
|
17090
17134
|
if (options.actedFor !== void 0) decision.actedFor = options.actedFor;
|
|
17091
17135
|
if (options.expectedHash !== void 0) decision.expectedDefinitionHash = options.expectedHash;
|
|
17136
|
+
if (options.contentHash !== void 0) decision.expectedContentHash = options.contentHash;
|
|
17092
17137
|
const body = Object.keys(decision).length ? decision : void 0;
|
|
17093
17138
|
let res;
|
|
17094
17139
|
try {
|
|
17095
17140
|
res = await requestJson(ctx, path, { method: "POST", ...body !== void 0 ? { body } : {} });
|
|
17096
17141
|
} catch (err) {
|
|
17142
|
+
if (err instanceof HttpError && err.status === 409 && detailsOf(err.body).reason === "action_changed") {
|
|
17143
|
+
const now = detailsOf(err.body).contentHash;
|
|
17144
|
+
throw new CliError(
|
|
17145
|
+
`approvals: the action behind ${positionals[0]} changed since you reviewed it \u2014 nothing was ${verb === "claim" ? "approved" : "rejected"}. Re-read it with \`openwop approvals get ${positionals[0]}\`${now ? ` (its card now hashes to ${now})` : ""} and decide on what it says now.`,
|
|
17146
|
+
1
|
|
17147
|
+
);
|
|
17148
|
+
}
|
|
17149
|
+
if (err instanceof HttpError && err.status === 400 && detailsOf(err.body).reason === "content_hash_required") {
|
|
17150
|
+
throw new CliError(
|
|
17151
|
+
`approvals: ${positionals[0]} is an assistant action \u2014 read its card with \`openwop approvals get ${positionals[0]}\`, then pass the contentHash it shows: --content-hash <hash>.`,
|
|
17152
|
+
2
|
|
17153
|
+
);
|
|
17154
|
+
}
|
|
17097
17155
|
if (err instanceof HttpError && err.status === 409) {
|
|
17098
17156
|
const status = err.body?.status;
|
|
17099
17157
|
throw new CliError(`approvals: ${positionals[0]} is already ${status ?? "resolved"} \u2014 the host rejected a second decision.`, 1);
|
|
@@ -17975,8 +18033,7 @@ Run \`openwop connections --help\` for usage.`);
|
|
|
17975
18033
|
async function ensureAdvertised(ctx) {
|
|
17976
18034
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
17977
18035
|
if (!wk.ok) return;
|
|
17978
|
-
const
|
|
17979
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/connections"));
|
|
18036
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/connections");
|
|
17980
18037
|
if (!advertised) {
|
|
17981
18038
|
throw new CliError(
|
|
17982
18039
|
"connections: this host does not advertise the connections surface (/v1/host/openwop-app/connections is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -19385,8 +19442,7 @@ Run \`openwop workforces --help\` for usage.`);
|
|
|
19385
19442
|
async function ensureAdvertised2(ctx) {
|
|
19386
19443
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
19387
19444
|
if (!wk.ok) return;
|
|
19388
|
-
const
|
|
19389
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/workforces"));
|
|
19445
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/workforces");
|
|
19390
19446
|
if (!advertised) {
|
|
19391
19447
|
throw new CliError(
|
|
19392
19448
|
"workforces: this host does not advertise the workforces surface (/v1/host/openwop-app/workforces is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -30933,6 +30989,7 @@ export {
|
|
|
30933
30989
|
getChannelPlugin,
|
|
30934
30990
|
hostRootFor,
|
|
30935
30991
|
hostRootsFrom,
|
|
30992
|
+
hostSurfaceAdvertised,
|
|
30936
30993
|
idempotencyHeaders,
|
|
30937
30994
|
isNoCredentialChallenge,
|
|
30938
30995
|
isTerminalRunEvent,
|
package/dist/openwop.js
CHANGED
|
@@ -579,7 +579,7 @@ function errText(err) {
|
|
|
579
579
|
// package.json
|
|
580
580
|
var package_default = {
|
|
581
581
|
name: "@openwop/cli",
|
|
582
|
-
version: "1.
|
|
582
|
+
version: "1.6.0",
|
|
583
583
|
description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
|
|
584
584
|
license: "Apache-2.0",
|
|
585
585
|
type: "module",
|
|
@@ -668,7 +668,10 @@ var PROVIDER_CATALOG = {
|
|
|
668
668
|
}
|
|
669
669
|
};
|
|
670
670
|
var HOST_PRESETS = [
|
|
671
|
-
|
|
671
|
+
// openwop-app ADR 0827: the protocol origin, served straight from Cloud Run with
|
|
672
|
+
// no CDN in front (`https://app.openwop.dev/api` still works; its edge drops
|
|
673
|
+
// conditional requests). `openwop login` opens the host-named web page.
|
|
674
|
+
{ key: "shared", label: "Shared demo at https://api.openwop.dev (recommended for trying things out)", url: "https://api.openwop.dev" },
|
|
672
675
|
{ key: "local", label: "Local demo at http://localhost:8080 (run `openwop demo start` to launch)", url: "http://localhost:8080" }
|
|
673
676
|
];
|
|
674
677
|
|
|
@@ -1206,6 +1209,14 @@ function hostRootFor(path, roots) {
|
|
|
1206
1209
|
}
|
|
1207
1210
|
return best === null ? null : `${roots[best]}${path.slice(best.length)}`;
|
|
1208
1211
|
}
|
|
1212
|
+
function hostSurfaceAdvertised(discovery, v1Prefix) {
|
|
1213
|
+
if (!discovery || typeof discovery !== "object") return false;
|
|
1214
|
+
const { paths, extensions } = discovery;
|
|
1215
|
+
if (paths !== null && typeof paths === "object") {
|
|
1216
|
+
return Object.keys(paths).some((p) => p.startsWith(v1Prefix));
|
|
1217
|
+
}
|
|
1218
|
+
return hostRootFor(v1Prefix, hostRootsFrom(extensions)) !== null;
|
|
1219
|
+
}
|
|
1209
1220
|
async function negotiateMajor(ctx) {
|
|
1210
1221
|
if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
|
|
1211
1222
|
const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
|
|
@@ -14925,13 +14936,21 @@ Approve a code ONLY if you just ran \`openwop login\` yourself.
|
|
|
14925
14936
|
Exit codes: 0 signed in / signed out \xB7 1 denied, expired or not available on this host \xB7 2 usage.
|
|
14926
14937
|
|
|
14927
14938
|
Examples:
|
|
14928
|
-
export OPENWOP_BASE_URL=https://
|
|
14939
|
+
export OPENWOP_BASE_URL=https://api.openwop.dev
|
|
14929
14940
|
openwop login
|
|
14930
14941
|
openwop login --label "work laptop"
|
|
14931
14942
|
openwop logout
|
|
14932
14943
|
`;
|
|
14933
14944
|
var sleep2 = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
14934
|
-
function approvalUrl(baseUrl, path) {
|
|
14945
|
+
function approvalUrl(baseUrl, path, uri) {
|
|
14946
|
+
if (typeof uri === "string") {
|
|
14947
|
+
try {
|
|
14948
|
+
const u = new URL(uri);
|
|
14949
|
+
const loopback = u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]";
|
|
14950
|
+
if (u.protocol === "https:" || u.protocol === "http:" && loopback) return u.toString();
|
|
14951
|
+
} catch {
|
|
14952
|
+
}
|
|
14953
|
+
}
|
|
14935
14954
|
const p = typeof path === "string" && path.startsWith("/") ? path : "/access?tab=api-keys";
|
|
14936
14955
|
try {
|
|
14937
14956
|
return `${new URL(baseUrl).origin}${p}`;
|
|
@@ -14969,7 +14988,7 @@ async function runLogin(ctx, argv) {
|
|
|
14969
14988
|
const userCode = started?.userCode;
|
|
14970
14989
|
if (typeof deviceCode !== "string" || typeof userCode !== "string") throw notAvailable(ctx);
|
|
14971
14990
|
writeLine(ctx.io.stderr, "");
|
|
14972
|
-
writeLine(ctx.io.stderr, ` 1. Open ${approvalUrl(ctx.baseUrl, started.verificationPath)}`);
|
|
14991
|
+
writeLine(ctx.io.stderr, ` 1. Open ${approvalUrl(ctx.baseUrl, started.verificationPath, started.verificationUri)}`);
|
|
14973
14992
|
writeLine(ctx.io.stderr, ` 2. Enter ${userCode} under "Sign in the OpenWOP CLI", and approve.`);
|
|
14974
14993
|
writeLine(ctx.io.stderr, "");
|
|
14975
14994
|
writeLine(ctx.io.stderr, "Waiting for approval\u2026");
|
|
@@ -16887,8 +16906,8 @@ var APPROVALS_ROUTES = [
|
|
|
16887
16906
|
var APPROVALS_HELP = `Usage:
|
|
16888
16907
|
openwop approvals list [--status pending|approved|rejected] [--json]
|
|
16889
16908
|
openwop approvals get <approvalId> [--json]
|
|
16890
|
-
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16891
|
-
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16909
|
+
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16910
|
+
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16892
16911
|
openwop approvals sla-policy [--json]
|
|
16893
16912
|
openwop approvals sla-policy set (--enabled | --disabled) [--remind-after-ms n] [--escalate-after-ms n] [--expire-after-ms n] [--json]
|
|
16894
16913
|
openwop approvals email-pref [--json]
|
|
@@ -16926,6 +16945,11 @@ Endpoints:
|
|
|
16926
16945
|
--acted-for <s> (claim/reject) As a delegate covering several people, whose approval this is.
|
|
16927
16946
|
--expected-hash <h> (claim/reject) The definition hash you reviewed (composed-workflow proposals;
|
|
16928
16947
|
the host refuses the decision if the proposal changed since).
|
|
16948
|
+
--content-hash <h> (claim/reject) The contentHash of the assistant-action card you reviewed
|
|
16949
|
+
(\`approvals get\` prints it under the card). REQUIRED to claim an
|
|
16950
|
+
assistant action (ADR 0862): the host refuses a claim without it (exit 2),
|
|
16951
|
+
and refuses one whose action changed since you read it (exit 1; re-read
|
|
16952
|
+
it with \`approvals get\` and review the new card).
|
|
16929
16953
|
--status <s> (list) Filter the queue: pending | approved | rejected (default: all).
|
|
16930
16954
|
--note <text> (claim/reject) Optional human note recorded with the decision.
|
|
16931
16955
|
--json Print the raw host response instead of the rendered view.
|
|
@@ -16942,6 +16966,8 @@ Examples:
|
|
|
16942
16966
|
openwop approvals list --status pending
|
|
16943
16967
|
openwop approvals get appr_123 --json
|
|
16944
16968
|
openwop approvals claim appr_123 --note "LGTM, ship it"
|
|
16969
|
+
openwop approvals get appr_456 # an assistant action: read the card
|
|
16970
|
+
openwop approvals claim appr_456 --content-hash 9f2c\u2026 # \u2026then approve exactly what you read
|
|
16945
16971
|
openwop approvals reject appr_123 --note "out of policy"
|
|
16946
16972
|
|
|
16947
16973
|
Teams approval delivery:
|
|
@@ -16988,8 +17014,7 @@ function exitForStatus(status) {
|
|
|
16988
17014
|
async function ensureApprovalsAdvertised(ctx) {
|
|
16989
17015
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
16990
17016
|
if (!wk.ok) return;
|
|
16991
|
-
const
|
|
16992
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/approvals"));
|
|
17017
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/approvals");
|
|
16993
17018
|
if (!advertised) {
|
|
16994
17019
|
throw new CliError(
|
|
16995
17020
|
"approvals: this host does not advertise the approval-inbox surface (/v1/host/openwop-app/approvals is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -17074,12 +17099,31 @@ async function runApprovalsGet(ctx, argv) {
|
|
|
17074
17099
|
if (approval.resolvedAt) writeLine(ctx.io.stdout, `resolvedAt: ${approval.resolvedAt}`);
|
|
17075
17100
|
if (approval.runId) writeLine(ctx.io.stdout, `runId: ${approval.runId}`);
|
|
17076
17101
|
if (approval.note) writeLine(ctx.io.stdout, `note: ${approval.note}`);
|
|
17102
|
+
if (approval.action && typeof approval.action === "object") renderActionCard(ctx, approval.action);
|
|
17077
17103
|
return exitForStatus(approval.status);
|
|
17078
17104
|
}
|
|
17105
|
+
function renderActionCard(ctx, action) {
|
|
17106
|
+
writeLine(ctx.io.stdout, "action:");
|
|
17107
|
+
if (action.actionId) writeLine(ctx.io.stdout, ` actionId: ${action.actionId}`);
|
|
17108
|
+
if (action.kind) writeLine(ctx.io.stdout, ` kind: ${action.kind}`);
|
|
17109
|
+
if (action.riskLevel) writeLine(ctx.io.stdout, ` riskLevel: ${action.riskLevel}`);
|
|
17110
|
+
if (action.reason) writeLine(ctx.io.stdout, ` reason: ${action.reason}`);
|
|
17111
|
+
if (typeof action.draft === "string" && action.draft.length) writeLine(ctx.io.stdout, ` draft: ${action.draft}`);
|
|
17112
|
+
if (action.payload && typeof action.payload === "object") {
|
|
17113
|
+
for (const [k, v] of Object.entries(action.payload)) {
|
|
17114
|
+
writeLine(ctx.io.stdout, ` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
17115
|
+
}
|
|
17116
|
+
}
|
|
17117
|
+
if (action.contentHash) writeLine(ctx.io.stdout, ` contentHash: ${action.contentHash} (claim with --content-hash ${action.contentHash})`);
|
|
17118
|
+
}
|
|
17119
|
+
function detailsOf(body) {
|
|
17120
|
+
const d = body && typeof body === "object" ? body.details : void 0;
|
|
17121
|
+
return d && typeof d === "object" ? d : {};
|
|
17122
|
+
}
|
|
17079
17123
|
async function runApprovalsResolve(ctx, argv, verb) {
|
|
17080
|
-
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash"] });
|
|
17124
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash", "--content-hash"] });
|
|
17081
17125
|
if (options.help || positionals.length !== 1) {
|
|
17082
|
-
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
17126
|
+
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
17083
17127
|
`);
|
|
17084
17128
|
return options.help ? 0 : 2;
|
|
17085
17129
|
}
|
|
@@ -17089,11 +17133,25 @@ async function runApprovalsResolve(ctx, argv, verb) {
|
|
|
17089
17133
|
if (options.note !== void 0) decision.note = options.note;
|
|
17090
17134
|
if (options.actedFor !== void 0) decision.actedFor = options.actedFor;
|
|
17091
17135
|
if (options.expectedHash !== void 0) decision.expectedDefinitionHash = options.expectedHash;
|
|
17136
|
+
if (options.contentHash !== void 0) decision.expectedContentHash = options.contentHash;
|
|
17092
17137
|
const body = Object.keys(decision).length ? decision : void 0;
|
|
17093
17138
|
let res;
|
|
17094
17139
|
try {
|
|
17095
17140
|
res = await requestJson(ctx, path, { method: "POST", ...body !== void 0 ? { body } : {} });
|
|
17096
17141
|
} catch (err) {
|
|
17142
|
+
if (err instanceof HttpError && err.status === 409 && detailsOf(err.body).reason === "action_changed") {
|
|
17143
|
+
const now = detailsOf(err.body).contentHash;
|
|
17144
|
+
throw new CliError(
|
|
17145
|
+
`approvals: the action behind ${positionals[0]} changed since you reviewed it \u2014 nothing was ${verb === "claim" ? "approved" : "rejected"}. Re-read it with \`openwop approvals get ${positionals[0]}\`${now ? ` (its card now hashes to ${now})` : ""} and decide on what it says now.`,
|
|
17146
|
+
1
|
|
17147
|
+
);
|
|
17148
|
+
}
|
|
17149
|
+
if (err instanceof HttpError && err.status === 400 && detailsOf(err.body).reason === "content_hash_required") {
|
|
17150
|
+
throw new CliError(
|
|
17151
|
+
`approvals: ${positionals[0]} is an assistant action \u2014 read its card with \`openwop approvals get ${positionals[0]}\`, then pass the contentHash it shows: --content-hash <hash>.`,
|
|
17152
|
+
2
|
|
17153
|
+
);
|
|
17154
|
+
}
|
|
17097
17155
|
if (err instanceof HttpError && err.status === 409) {
|
|
17098
17156
|
const status = err.body?.status;
|
|
17099
17157
|
throw new CliError(`approvals: ${positionals[0]} is already ${status ?? "resolved"} \u2014 the host rejected a second decision.`, 1);
|
|
@@ -17975,8 +18033,7 @@ Run \`openwop connections --help\` for usage.`);
|
|
|
17975
18033
|
async function ensureAdvertised(ctx) {
|
|
17976
18034
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
17977
18035
|
if (!wk.ok) return;
|
|
17978
|
-
const
|
|
17979
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/connections"));
|
|
18036
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/connections");
|
|
17980
18037
|
if (!advertised) {
|
|
17981
18038
|
throw new CliError(
|
|
17982
18039
|
"connections: this host does not advertise the connections surface (/v1/host/openwop-app/connections is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -19385,8 +19442,7 @@ Run \`openwop workforces --help\` for usage.`);
|
|
|
19385
19442
|
async function ensureAdvertised2(ctx) {
|
|
19386
19443
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
19387
19444
|
if (!wk.ok) return;
|
|
19388
|
-
const
|
|
19389
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/workforces"));
|
|
19445
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/workforces");
|
|
19390
19446
|
if (!advertised) {
|
|
19391
19447
|
throw new CliError(
|
|
19392
19448
|
"workforces: this host does not advertise the workforces surface (/v1/host/openwop-app/workforces is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|