@openwop/cli 1.6.0 → 1.7.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.
Files changed (3) hide show
  1. package/dist/cli.js +108 -18
  2. package/dist/openwop.js +106 -17
  3. package/package.json +1 -1
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.6.0",
581
+ version: "1.7.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",
@@ -855,21 +855,31 @@ function mergeConfig(existing, next) {
855
855
  function getByPath(obj, path) {
856
856
  return path.split(".").reduce((acc, key) => acc == null ? acc : acc[key], obj);
857
857
  }
858
- function setByPath(obj, path, value) {
858
+ var UNSAFE_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
859
+ function safePathParts(path) {
859
860
  const parts = path.split(".");
861
+ for (const part of parts) {
862
+ if (UNSAFE_PATH_SEGMENTS.has(part)) throw new CliError(`Config key segment '${part}' is not allowed: ${path}`);
863
+ }
864
+ return parts;
865
+ }
866
+ function setByPath(obj, path, value) {
867
+ const parts = safePathParts(path);
860
868
  let cur = obj;
861
869
  for (let i = 0; i < parts.length - 1; i++) {
862
- if (typeof cur[parts[i]] !== "object" || cur[parts[i]] === null) cur[parts[i]] = {};
863
- cur = cur[parts[i]];
870
+ const key = parts[i];
871
+ if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
872
+ cur = cur[key];
864
873
  }
865
874
  cur[parts[parts.length - 1]] = value;
866
875
  }
867
876
  function unsetByPath(obj, path) {
868
- const parts = path.split(".");
877
+ const parts = safePathParts(path);
869
878
  let cur = obj;
870
879
  for (let i = 0; i < parts.length - 1; i++) {
871
- if (typeof cur[parts[i]] !== "object" || cur[parts[i]] === null) return;
872
- cur = cur[parts[i]];
880
+ const key = parts[i];
881
+ if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) return;
882
+ cur = cur[key];
873
883
  }
874
884
  delete cur[parts[parts.length - 1]];
875
885
  }
@@ -1096,12 +1106,22 @@ function decodeSegment(segment) {
1096
1106
  return segment;
1097
1107
  }
1098
1108
  }
1109
+ function splitRunSegment(bare) {
1110
+ const PREFIX = "/runs/";
1111
+ if (!bare.startsWith(PREFIX)) return null;
1112
+ let end = PREFIX.length;
1113
+ while (end < bare.length && bare[end] !== "/" && bare[end] !== ":") end++;
1114
+ if (end === PREFIX.length) return null;
1115
+ const rest = bare.slice(end);
1116
+ if (/[\n\r\u2028\u2029]/.test(rest)) return null;
1117
+ return [bare.slice(PREFIX.length, end), rest];
1118
+ }
1099
1119
  function projectRunIdsInPath(path) {
1100
1120
  const q = path.indexOf("?");
1101
1121
  const bare = q === -1 ? path : path.slice(0, q);
1102
1122
  let query = q === -1 ? "" : path.slice(q);
1103
- const m = /^\/runs\/([^/:]+)(.*)$/.exec(bare);
1104
- const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[1]))}${m[2]}` : bare;
1123
+ const m = splitRunSegment(bare);
1124
+ const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[0]))}${m[1]}` : bare;
1105
1125
  if (m && query) {
1106
1126
  query = query.replace(/([?&]against=)([^&]*)/, (_all, key, value) => `${key}${projectTenantBoundId(decodeSegment(value))}`);
1107
1127
  }
@@ -1167,9 +1187,11 @@ function v2SeamTwin(path) {
1167
1187
  }
1168
1188
  return null;
1169
1189
  }
1170
- var V2_MATCHERS = V2_PATH_TEMPLATES.map(
1171
- (t) => new RegExp(`^${t.replace(/[.:]/g, "\\$&").replace(/\{[A-Za-z]+\}/g, "[^/]+")}$`)
1172
- );
1190
+ function v2TemplatePattern(template) {
1191
+ const literals = template.split(/\{[A-Za-z]+\}/);
1192
+ return `^${literals.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+")}$`;
1193
+ }
1194
+ var V2_MATCHERS = V2_PATH_TEMPLATES.map((t) => new RegExp(v2TemplatePattern(t)));
1173
1195
  function v2Twin(path) {
1174
1196
  const q = path.indexOf("?");
1175
1197
  const bare = q === -1 ? path : path.slice(0, q);
@@ -1275,7 +1297,10 @@ function explicitStreamBase(raw) {
1275
1297
  if (url.username !== "" || url.password !== "") {
1276
1298
  throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
1277
1299
  }
1278
- return raw.trim().replace(/\/+$/, "");
1300
+ const base16 = raw.trim();
1301
+ let end = base16.length;
1302
+ while (end > 0 && base16[end - 1] === "/") end--;
1303
+ return base16.slice(0, end);
1279
1304
  }
1280
1305
  function streamBaseFrom(extensions, baseUrl) {
1281
1306
  if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) return void 0;
@@ -29786,7 +29811,7 @@ var INTENT_LEDGER_ROUTES = [
29786
29811
  words: ["draft"],
29787
29812
  method: "POST",
29788
29813
  path: `${C3}/draft`,
29789
- summary: "Draft (or REPLACE) the ledger: a new draft the user then approves.",
29814
+ summary: "Draft the ledger (replaces a draft; an approved mission must be rejected first): a new draft the user then approves.",
29790
29815
  body: [
29791
29816
  { flag: "--goal", key: "goal", required: true },
29792
29817
  { flag: "--allowed", key: "allowed", type: "csv" },
@@ -29796,12 +29821,16 @@ var INTENT_LEDGER_ROUTES = [
29796
29821
  { flag: "--expires-at-rel-ms", key: "expiresAtRelMs", type: "number" }
29797
29822
  ]
29798
29823
  },
29799
- { words: ["approve"], method: "POST", path: `${C3}/approve`, summary: "Approve the drafted ledger (you become approvedBy)." },
29800
- { words: ["reject"], method: "POST", path: `${C3}/reject`, summary: "Reject the drafted ledger." },
29801
29824
  { words: ["reckoning"], method: "GET", path: `${C3}/reckoning`, summary: "What runs did vs. the mandate: used / blocked tools, within-mandate verdict (null before any stamped run)." }
29802
29825
  ];
29803
29826
  var INTENT_LEDGER_HELP = `Usage:
29804
29827
  ${routesHelp("intent-ledger", INTENT_LEDGER_ROUTES)}
29828
+ Decisions (read, review, then decide exactly what you reviewed \u2014 openwop-app ADR 0896):
29829
+ openwop intent-ledger approve <conversationId> [--yes --digest <digest>] [--json]
29830
+ openwop intent-ledger reject <conversationId> [--yes --digest <digest>] [--json]
29831
+ Prints the contract; with --yes and the printed --digest, approves (or
29832
+ rejects/revokes) exactly that version. POST ${C3}/approve|reject
29833
+
29805
29834
  Intent ledger (ADR 0136). The host enforces the ledger at tool-call time; the CLI
29806
29835
  only records your intent and reads the host's reckoning.
29807
29836
 
@@ -29809,10 +29838,70 @@ Exit codes: 0 ok \xB7 1 server error \xB7 2 usage / not found \xB7 4 not signed
29809
29838
 
29810
29839
  Examples:
29811
29840
  openwop intent-ledger draft conv_1 --goal "Book travel" --allowed travel.search,travel.book --require-approval travel.book
29812
- openwop intent-ledger approve conv_1
29841
+ openwop intent-ledger approve conv_1 # prints the contract + its digest
29842
+ openwop intent-ledger approve conv_1 --yes --digest sha256:\u2026
29813
29843
  openwop intent-ledger reckoning conv_1 --json
29814
29844
  `;
29845
+ function contractLines(l) {
29846
+ const list = (xs) => xs && xs.length ? xs.join(", ") : "(none)";
29847
+ return [
29848
+ `Mission ${l.ledgerId ?? "?"} \u2014 ${l.status ?? "?"}${l.revision !== void 0 ? ` (revision ${l.revision})` : ""}`,
29849
+ ` goal: ${l.goal ?? ""}`,
29850
+ ` allowed: ${l.allowed && l.allowed.length ? l.allowed.join(", ") : "(none \u2014 the agent may talk but not act)"}`,
29851
+ ` forbidden: ${list(l.forbidden)}`,
29852
+ ` needs approval: ${list(l.requireApproval)}`,
29853
+ ` success criteria: ${list(l.successCriteria)}`,
29854
+ ...l.expiresAtRelMs !== void 0 ? [` ends: ${Math.round(l.expiresAtRelMs / 6e4)} min after the agent starts working under it`] : [],
29855
+ ...l.digest ? [` digest: ${l.digest}`] : []
29856
+ ];
29857
+ }
29858
+ async function decide2(ctx, verb, argv) {
29859
+ const { options, positionals } = parseOptions(argv, { bool: ["--yes", "-y", "--json", "--help", "-h"], value: ["--digest"] });
29860
+ if (options.help || options.h) {
29861
+ write(ctx.io.stdout, INTENT_LEDGER_HELP);
29862
+ return 0;
29863
+ }
29864
+ const conversationId = positionals[0];
29865
+ if (!conversationId || positionals.length > 1) throw new CliError(`Usage: openwop intent-ledger ${verb} <conversationId> [--yes --digest <digest>]`, 2);
29866
+ const path = C3.replace(":conversationId", encodeURIComponent(conversationId));
29867
+ try {
29868
+ const got = await requestJson(ctx, path);
29869
+ const ledger = got.body?.ledger ?? null;
29870
+ if (!ledger) throw new CliError(`No mission ledger on conversation ${conversationId}.`, 2);
29871
+ if (!options.yes && !options.y) {
29872
+ if (options.json) writeJson(ctx.io.stdout, got.body);
29873
+ else for (const line of contractLines(ledger)) writeLine(ctx.io.stdout, line);
29874
+ writeLine(ctx.io.stderr, `Review the contract above, then re-run: openwop intent-ledger ${verb} ${conversationId} --yes${ledger.digest ? ` --digest ${ledger.digest}` : ""}`);
29875
+ return 2;
29876
+ }
29877
+ if (ledger.digest) {
29878
+ if (!options.digest) {
29879
+ for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
29880
+ throw new CliError(`Pass --digest ${ledger.digest} to ${verb} exactly this contract.`, 2);
29881
+ }
29882
+ if (options.digest !== ledger.digest) {
29883
+ for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
29884
+ throw new CliError(`The mission changed since you reviewed it (digest ${options.digest} \u2192 ${ledger.digest}). Review the version above and re-run with its digest.`, 2);
29885
+ }
29886
+ }
29887
+ const body = {};
29888
+ if (ledger.ledgerId !== void 0) body.ledgerId = ledger.ledgerId;
29889
+ if (ledger.revision !== void 0) body.revision = ledger.revision;
29890
+ if (ledger.digest !== void 0) body.digest = ledger.digest;
29891
+ const res = await requestJson(ctx, `${path}/${verb}`, { method: "POST", body });
29892
+ if (options.json) writeJson(ctx.io.stdout, res.body);
29893
+ else {
29894
+ const out = res.body?.ledger;
29895
+ writeLine(ctx.io.stdout, `Mission ${out?.ledgerId ?? ledger.ledgerId ?? ""} ${out?.status ?? (verb === "approve" ? "approved" : "rejected")}.`);
29896
+ }
29897
+ return 0;
29898
+ } catch (err) {
29899
+ if (err instanceof HttpError) throw hostError(err);
29900
+ throw err;
29901
+ }
29902
+ }
29815
29903
  async function runIntentLedger(ctx, argv) {
29904
+ if (argv[0] === "approve" || argv[0] === "reject") return decide2(ctx, argv[0], argv.slice(1));
29816
29905
  return runRouteGroup(ctx, "intent-ledger", INTENT_LEDGER_HELP, INTENT_LEDGER_ROUTES, argv);
29817
29906
  }
29818
29907
 
@@ -31014,5 +31103,6 @@ export {
31014
31103
  streamBaseFrom,
31015
31104
  streamRunEvents,
31016
31105
  submitTurn,
31017
- summarizeCapabilities
31106
+ summarizeCapabilities,
31107
+ v2TemplatePattern
31018
31108
  };
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.6.0",
582
+ version: "1.7.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",
@@ -856,21 +856,31 @@ function mergeConfig(existing, next) {
856
856
  function getByPath(obj, path) {
857
857
  return path.split(".").reduce((acc, key) => acc == null ? acc : acc[key], obj);
858
858
  }
859
- function setByPath(obj, path, value) {
859
+ var UNSAFE_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
860
+ function safePathParts(path) {
860
861
  const parts = path.split(".");
862
+ for (const part of parts) {
863
+ if (UNSAFE_PATH_SEGMENTS.has(part)) throw new CliError(`Config key segment '${part}' is not allowed: ${path}`);
864
+ }
865
+ return parts;
866
+ }
867
+ function setByPath(obj, path, value) {
868
+ const parts = safePathParts(path);
861
869
  let cur = obj;
862
870
  for (let i = 0; i < parts.length - 1; i++) {
863
- if (typeof cur[parts[i]] !== "object" || cur[parts[i]] === null) cur[parts[i]] = {};
864
- cur = cur[parts[i]];
871
+ const key = parts[i];
872
+ if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
873
+ cur = cur[key];
865
874
  }
866
875
  cur[parts[parts.length - 1]] = value;
867
876
  }
868
877
  function unsetByPath(obj, path) {
869
- const parts = path.split(".");
878
+ const parts = safePathParts(path);
870
879
  let cur = obj;
871
880
  for (let i = 0; i < parts.length - 1; i++) {
872
- if (typeof cur[parts[i]] !== "object" || cur[parts[i]] === null) return;
873
- cur = cur[parts[i]];
881
+ const key = parts[i];
882
+ if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) return;
883
+ cur = cur[key];
874
884
  }
875
885
  delete cur[parts[parts.length - 1]];
876
886
  }
@@ -1097,12 +1107,22 @@ function decodeSegment(segment) {
1097
1107
  return segment;
1098
1108
  }
1099
1109
  }
1110
+ function splitRunSegment(bare) {
1111
+ const PREFIX = "/runs/";
1112
+ if (!bare.startsWith(PREFIX)) return null;
1113
+ let end = PREFIX.length;
1114
+ while (end < bare.length && bare[end] !== "/" && bare[end] !== ":") end++;
1115
+ if (end === PREFIX.length) return null;
1116
+ const rest = bare.slice(end);
1117
+ if (/[\n\r\u2028\u2029]/.test(rest)) return null;
1118
+ return [bare.slice(PREFIX.length, end), rest];
1119
+ }
1100
1120
  function projectRunIdsInPath(path) {
1101
1121
  const q = path.indexOf("?");
1102
1122
  const bare = q === -1 ? path : path.slice(0, q);
1103
1123
  let query = q === -1 ? "" : path.slice(q);
1104
- const m = /^\/runs\/([^/:]+)(.*)$/.exec(bare);
1105
- const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[1]))}${m[2]}` : bare;
1124
+ const m = splitRunSegment(bare);
1125
+ const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[0]))}${m[1]}` : bare;
1106
1126
  if (m && query) {
1107
1127
  query = query.replace(/([?&]against=)([^&]*)/, (_all, key, value) => `${key}${projectTenantBoundId(decodeSegment(value))}`);
1108
1128
  }
@@ -1167,9 +1187,11 @@ function v2SeamTwin(path) {
1167
1187
  }
1168
1188
  return null;
1169
1189
  }
1170
- var V2_MATCHERS = V2_PATH_TEMPLATES.map(
1171
- (t) => new RegExp(`^${t.replace(/[.:]/g, "\\$&").replace(/\{[A-Za-z]+\}/g, "[^/]+")}$`)
1172
- );
1190
+ function v2TemplatePattern(template) {
1191
+ const literals = template.split(/\{[A-Za-z]+\}/);
1192
+ return `^${literals.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+")}$`;
1193
+ }
1194
+ var V2_MATCHERS = V2_PATH_TEMPLATES.map((t) => new RegExp(v2TemplatePattern(t)));
1173
1195
  function v2Twin(path) {
1174
1196
  const q = path.indexOf("?");
1175
1197
  const bare = q === -1 ? path : path.slice(0, q);
@@ -1275,7 +1297,10 @@ function explicitStreamBase(raw) {
1275
1297
  if (url.username !== "" || url.password !== "") {
1276
1298
  throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
1277
1299
  }
1278
- return raw.trim().replace(/\/+$/, "");
1300
+ const base16 = raw.trim();
1301
+ let end = base16.length;
1302
+ while (end > 0 && base16[end - 1] === "/") end--;
1303
+ return base16.slice(0, end);
1279
1304
  }
1280
1305
  function streamBaseFrom(extensions, baseUrl) {
1281
1306
  if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) return void 0;
@@ -29786,7 +29811,7 @@ var INTENT_LEDGER_ROUTES = [
29786
29811
  words: ["draft"],
29787
29812
  method: "POST",
29788
29813
  path: `${C3}/draft`,
29789
- summary: "Draft (or REPLACE) the ledger: a new draft the user then approves.",
29814
+ summary: "Draft the ledger (replaces a draft; an approved mission must be rejected first): a new draft the user then approves.",
29790
29815
  body: [
29791
29816
  { flag: "--goal", key: "goal", required: true },
29792
29817
  { flag: "--allowed", key: "allowed", type: "csv" },
@@ -29796,12 +29821,16 @@ var INTENT_LEDGER_ROUTES = [
29796
29821
  { flag: "--expires-at-rel-ms", key: "expiresAtRelMs", type: "number" }
29797
29822
  ]
29798
29823
  },
29799
- { words: ["approve"], method: "POST", path: `${C3}/approve`, summary: "Approve the drafted ledger (you become approvedBy)." },
29800
- { words: ["reject"], method: "POST", path: `${C3}/reject`, summary: "Reject the drafted ledger." },
29801
29824
  { words: ["reckoning"], method: "GET", path: `${C3}/reckoning`, summary: "What runs did vs. the mandate: used / blocked tools, within-mandate verdict (null before any stamped run)." }
29802
29825
  ];
29803
29826
  var INTENT_LEDGER_HELP = `Usage:
29804
29827
  ${routesHelp("intent-ledger", INTENT_LEDGER_ROUTES)}
29828
+ Decisions (read, review, then decide exactly what you reviewed \u2014 openwop-app ADR 0896):
29829
+ openwop intent-ledger approve <conversationId> [--yes --digest <digest>] [--json]
29830
+ openwop intent-ledger reject <conversationId> [--yes --digest <digest>] [--json]
29831
+ Prints the contract; with --yes and the printed --digest, approves (or
29832
+ rejects/revokes) exactly that version. POST ${C3}/approve|reject
29833
+
29805
29834
  Intent ledger (ADR 0136). The host enforces the ledger at tool-call time; the CLI
29806
29835
  only records your intent and reads the host's reckoning.
29807
29836
 
@@ -29809,10 +29838,70 @@ Exit codes: 0 ok \xB7 1 server error \xB7 2 usage / not found \xB7 4 not signed
29809
29838
 
29810
29839
  Examples:
29811
29840
  openwop intent-ledger draft conv_1 --goal "Book travel" --allowed travel.search,travel.book --require-approval travel.book
29812
- openwop intent-ledger approve conv_1
29841
+ openwop intent-ledger approve conv_1 # prints the contract + its digest
29842
+ openwop intent-ledger approve conv_1 --yes --digest sha256:\u2026
29813
29843
  openwop intent-ledger reckoning conv_1 --json
29814
29844
  `;
29845
+ function contractLines(l) {
29846
+ const list = (xs) => xs && xs.length ? xs.join(", ") : "(none)";
29847
+ return [
29848
+ `Mission ${l.ledgerId ?? "?"} \u2014 ${l.status ?? "?"}${l.revision !== void 0 ? ` (revision ${l.revision})` : ""}`,
29849
+ ` goal: ${l.goal ?? ""}`,
29850
+ ` allowed: ${l.allowed && l.allowed.length ? l.allowed.join(", ") : "(none \u2014 the agent may talk but not act)"}`,
29851
+ ` forbidden: ${list(l.forbidden)}`,
29852
+ ` needs approval: ${list(l.requireApproval)}`,
29853
+ ` success criteria: ${list(l.successCriteria)}`,
29854
+ ...l.expiresAtRelMs !== void 0 ? [` ends: ${Math.round(l.expiresAtRelMs / 6e4)} min after the agent starts working under it`] : [],
29855
+ ...l.digest ? [` digest: ${l.digest}`] : []
29856
+ ];
29857
+ }
29858
+ async function decide2(ctx, verb, argv) {
29859
+ const { options, positionals } = parseOptions(argv, { bool: ["--yes", "-y", "--json", "--help", "-h"], value: ["--digest"] });
29860
+ if (options.help || options.h) {
29861
+ write(ctx.io.stdout, INTENT_LEDGER_HELP);
29862
+ return 0;
29863
+ }
29864
+ const conversationId = positionals[0];
29865
+ if (!conversationId || positionals.length > 1) throw new CliError(`Usage: openwop intent-ledger ${verb} <conversationId> [--yes --digest <digest>]`, 2);
29866
+ const path = C3.replace(":conversationId", encodeURIComponent(conversationId));
29867
+ try {
29868
+ const got = await requestJson(ctx, path);
29869
+ const ledger = got.body?.ledger ?? null;
29870
+ if (!ledger) throw new CliError(`No mission ledger on conversation ${conversationId}.`, 2);
29871
+ if (!options.yes && !options.y) {
29872
+ if (options.json) writeJson(ctx.io.stdout, got.body);
29873
+ else for (const line of contractLines(ledger)) writeLine(ctx.io.stdout, line);
29874
+ writeLine(ctx.io.stderr, `Review the contract above, then re-run: openwop intent-ledger ${verb} ${conversationId} --yes${ledger.digest ? ` --digest ${ledger.digest}` : ""}`);
29875
+ return 2;
29876
+ }
29877
+ if (ledger.digest) {
29878
+ if (!options.digest) {
29879
+ for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
29880
+ throw new CliError(`Pass --digest ${ledger.digest} to ${verb} exactly this contract.`, 2);
29881
+ }
29882
+ if (options.digest !== ledger.digest) {
29883
+ for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
29884
+ throw new CliError(`The mission changed since you reviewed it (digest ${options.digest} \u2192 ${ledger.digest}). Review the version above and re-run with its digest.`, 2);
29885
+ }
29886
+ }
29887
+ const body = {};
29888
+ if (ledger.ledgerId !== void 0) body.ledgerId = ledger.ledgerId;
29889
+ if (ledger.revision !== void 0) body.revision = ledger.revision;
29890
+ if (ledger.digest !== void 0) body.digest = ledger.digest;
29891
+ const res = await requestJson(ctx, `${path}/${verb}`, { method: "POST", body });
29892
+ if (options.json) writeJson(ctx.io.stdout, res.body);
29893
+ else {
29894
+ const out = res.body?.ledger;
29895
+ writeLine(ctx.io.stdout, `Mission ${out?.ledgerId ?? ledger.ledgerId ?? ""} ${out?.status ?? (verb === "approve" ? "approved" : "rejected")}.`);
29896
+ }
29897
+ return 0;
29898
+ } catch (err) {
29899
+ if (err instanceof HttpError) throw hostError(err);
29900
+ throw err;
29901
+ }
29902
+ }
29815
29903
  async function runIntentLedger(ctx, argv) {
29904
+ if (argv[0] === "approve" || argv[0] === "reject") return decide2(ctx, argv[0], argv.slice(1));
29816
29905
  return runRouteGroup(ctx, "intent-ledger", INTENT_LEDGER_HELP, INTENT_LEDGER_ROUTES, argv);
29817
29906
  }
29818
29907
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/cli",
3
- "version": "1.6.0",
3
+ "version": "1.7.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",