@openwop/cli 1.5.0 → 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/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.5.0",
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",
@@ -1209,6 +1209,14 @@ function hostRootFor(path, roots) {
1209
1209
  }
1210
1210
  return best === null ? null : `${roots[best]}${path.slice(best.length)}`;
1211
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
+ }
1212
1220
  async function negotiateMajor(ctx) {
1213
1221
  if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
1214
1222
  const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
@@ -16898,8 +16906,8 @@ var APPROVALS_ROUTES = [
16898
16906
  var APPROVALS_HELP = `Usage:
16899
16907
  openwop approvals list [--status pending|approved|rejected] [--json]
16900
16908
  openwop approvals get <approvalId> [--json]
16901
- openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
16902
- 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]
16903
16911
  openwop approvals sla-policy [--json]
16904
16912
  openwop approvals sla-policy set (--enabled | --disabled) [--remind-after-ms n] [--escalate-after-ms n] [--expire-after-ms n] [--json]
16905
16913
  openwop approvals email-pref [--json]
@@ -16937,6 +16945,11 @@ Endpoints:
16937
16945
  --acted-for <s> (claim/reject) As a delegate covering several people, whose approval this is.
16938
16946
  --expected-hash <h> (claim/reject) The definition hash you reviewed (composed-workflow proposals;
16939
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).
16940
16953
  --status <s> (list) Filter the queue: pending | approved | rejected (default: all).
16941
16954
  --note <text> (claim/reject) Optional human note recorded with the decision.
16942
16955
  --json Print the raw host response instead of the rendered view.
@@ -16953,6 +16966,8 @@ Examples:
16953
16966
  openwop approvals list --status pending
16954
16967
  openwop approvals get appr_123 --json
16955
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
16956
16971
  openwop approvals reject appr_123 --note "out of policy"
16957
16972
 
16958
16973
  Teams approval delivery:
@@ -16999,8 +17014,7 @@ function exitForStatus(status) {
16999
17014
  async function ensureApprovalsAdvertised(ctx) {
17000
17015
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
17001
17016
  if (!wk.ok) return;
17002
- const paths = wk.body && typeof wk.body === "object" ? wk.body.paths : void 0;
17003
- 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");
17004
17018
  if (!advertised) {
17005
17019
  throw new CliError(
17006
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.",
@@ -17085,12 +17099,31 @@ async function runApprovalsGet(ctx, argv) {
17085
17099
  if (approval.resolvedAt) writeLine(ctx.io.stdout, `resolvedAt: ${approval.resolvedAt}`);
17086
17100
  if (approval.runId) writeLine(ctx.io.stdout, `runId: ${approval.runId}`);
17087
17101
  if (approval.note) writeLine(ctx.io.stdout, `note: ${approval.note}`);
17102
+ if (approval.action && typeof approval.action === "object") renderActionCard(ctx, approval.action);
17088
17103
  return exitForStatus(approval.status);
17089
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
+ }
17090
17123
  async function runApprovalsResolve(ctx, argv, verb) {
17091
- 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"] });
17092
17125
  if (options.help || positionals.length !== 1) {
17093
- 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]
17094
17127
  `);
17095
17128
  return options.help ? 0 : 2;
17096
17129
  }
@@ -17100,11 +17133,25 @@ async function runApprovalsResolve(ctx, argv, verb) {
17100
17133
  if (options.note !== void 0) decision.note = options.note;
17101
17134
  if (options.actedFor !== void 0) decision.actedFor = options.actedFor;
17102
17135
  if (options.expectedHash !== void 0) decision.expectedDefinitionHash = options.expectedHash;
17136
+ if (options.contentHash !== void 0) decision.expectedContentHash = options.contentHash;
17103
17137
  const body = Object.keys(decision).length ? decision : void 0;
17104
17138
  let res;
17105
17139
  try {
17106
17140
  res = await requestJson(ctx, path, { method: "POST", ...body !== void 0 ? { body } : {} });
17107
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
+ }
17108
17155
  if (err instanceof HttpError && err.status === 409) {
17109
17156
  const status = err.body?.status;
17110
17157
  throw new CliError(`approvals: ${positionals[0]} is already ${status ?? "resolved"} \u2014 the host rejected a second decision.`, 1);
@@ -17986,8 +18033,7 @@ Run \`openwop connections --help\` for usage.`);
17986
18033
  async function ensureAdvertised(ctx) {
17987
18034
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
17988
18035
  if (!wk.ok) return;
17989
- const paths = wk.body && typeof wk.body === "object" ? wk.body.paths : void 0;
17990
- 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");
17991
18037
  if (!advertised) {
17992
18038
  throw new CliError(
17993
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.",
@@ -19396,8 +19442,7 @@ Run \`openwop workforces --help\` for usage.`);
19396
19442
  async function ensureAdvertised2(ctx) {
19397
19443
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
19398
19444
  if (!wk.ok) return;
19399
- const paths = wk.body && typeof wk.body === "object" ? wk.body.paths : void 0;
19400
- 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");
19401
19446
  if (!advertised) {
19402
19447
  throw new CliError(
19403
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.",
@@ -30944,6 +30989,7 @@ export {
30944
30989
  getChannelPlugin,
30945
30990
  hostRootFor,
30946
30991
  hostRootsFrom,
30992
+ hostSurfaceAdvertised,
30947
30993
  idempotencyHeaders,
30948
30994
  isNoCredentialChallenge,
30949
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.5.0",
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",
@@ -1209,6 +1209,14 @@ function hostRootFor(path, roots) {
1209
1209
  }
1210
1210
  return best === null ? null : `${roots[best]}${path.slice(best.length)}`;
1211
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
+ }
1212
1220
  async function negotiateMajor(ctx) {
1213
1221
  if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
1214
1222
  const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
@@ -16898,8 +16906,8 @@ var APPROVALS_ROUTES = [
16898
16906
  var APPROVALS_HELP = `Usage:
16899
16907
  openwop approvals list [--status pending|approved|rejected] [--json]
16900
16908
  openwop approvals get <approvalId> [--json]
16901
- openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
16902
- 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]
16903
16911
  openwop approvals sla-policy [--json]
16904
16912
  openwop approvals sla-policy set (--enabled | --disabled) [--remind-after-ms n] [--escalate-after-ms n] [--expire-after-ms n] [--json]
16905
16913
  openwop approvals email-pref [--json]
@@ -16937,6 +16945,11 @@ Endpoints:
16937
16945
  --acted-for <s> (claim/reject) As a delegate covering several people, whose approval this is.
16938
16946
  --expected-hash <h> (claim/reject) The definition hash you reviewed (composed-workflow proposals;
16939
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).
16940
16953
  --status <s> (list) Filter the queue: pending | approved | rejected (default: all).
16941
16954
  --note <text> (claim/reject) Optional human note recorded with the decision.
16942
16955
  --json Print the raw host response instead of the rendered view.
@@ -16953,6 +16966,8 @@ Examples:
16953
16966
  openwop approvals list --status pending
16954
16967
  openwop approvals get appr_123 --json
16955
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
16956
16971
  openwop approvals reject appr_123 --note "out of policy"
16957
16972
 
16958
16973
  Teams approval delivery:
@@ -16999,8 +17014,7 @@ function exitForStatus(status) {
16999
17014
  async function ensureApprovalsAdvertised(ctx) {
17000
17015
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
17001
17016
  if (!wk.ok) return;
17002
- const paths = wk.body && typeof wk.body === "object" ? wk.body.paths : void 0;
17003
- 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");
17004
17018
  if (!advertised) {
17005
17019
  throw new CliError(
17006
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.",
@@ -17085,12 +17099,31 @@ async function runApprovalsGet(ctx, argv) {
17085
17099
  if (approval.resolvedAt) writeLine(ctx.io.stdout, `resolvedAt: ${approval.resolvedAt}`);
17086
17100
  if (approval.runId) writeLine(ctx.io.stdout, `runId: ${approval.runId}`);
17087
17101
  if (approval.note) writeLine(ctx.io.stdout, `note: ${approval.note}`);
17102
+ if (approval.action && typeof approval.action === "object") renderActionCard(ctx, approval.action);
17088
17103
  return exitForStatus(approval.status);
17089
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
+ }
17090
17123
  async function runApprovalsResolve(ctx, argv, verb) {
17091
- 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"] });
17092
17125
  if (options.help || positionals.length !== 1) {
17093
- 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]
17094
17127
  `);
17095
17128
  return options.help ? 0 : 2;
17096
17129
  }
@@ -17100,11 +17133,25 @@ async function runApprovalsResolve(ctx, argv, verb) {
17100
17133
  if (options.note !== void 0) decision.note = options.note;
17101
17134
  if (options.actedFor !== void 0) decision.actedFor = options.actedFor;
17102
17135
  if (options.expectedHash !== void 0) decision.expectedDefinitionHash = options.expectedHash;
17136
+ if (options.contentHash !== void 0) decision.expectedContentHash = options.contentHash;
17103
17137
  const body = Object.keys(decision).length ? decision : void 0;
17104
17138
  let res;
17105
17139
  try {
17106
17140
  res = await requestJson(ctx, path, { method: "POST", ...body !== void 0 ? { body } : {} });
17107
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
+ }
17108
17155
  if (err instanceof HttpError && err.status === 409) {
17109
17156
  const status = err.body?.status;
17110
17157
  throw new CliError(`approvals: ${positionals[0]} is already ${status ?? "resolved"} \u2014 the host rejected a second decision.`, 1);
@@ -17986,8 +18033,7 @@ Run \`openwop connections --help\` for usage.`);
17986
18033
  async function ensureAdvertised(ctx) {
17987
18034
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
17988
18035
  if (!wk.ok) return;
17989
- const paths = wk.body && typeof wk.body === "object" ? wk.body.paths : void 0;
17990
- 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");
17991
18037
  if (!advertised) {
17992
18038
  throw new CliError(
17993
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.",
@@ -19396,8 +19442,7 @@ Run \`openwop workforces --help\` for usage.`);
19396
19442
  async function ensureAdvertised2(ctx) {
19397
19443
  const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
19398
19444
  if (!wk.ok) return;
19399
- const paths = wk.body && typeof wk.body === "object" ? wk.body.paths : void 0;
19400
- 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");
19401
19446
  if (!advertised) {
19402
19447
  throw new CliError(
19403
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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.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",