@mnemom/mnemom 0.8.0 → 0.9.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.
@@ -6,15 +6,16 @@ export async function integrityCommand(agentName) {
6
6
  console.log("\nFetching integrity score...\n");
7
7
  try {
8
8
  const integrity = await getIntegrity(agentId);
9
- const scorePercent = (integrity.score * 100).toFixed(1);
10
- const scoreBar = generateScoreBar(integrity.score);
9
+ // Field names match the docs.mnemom.ai canonical IntegrityScore schema:
10
+ // integrity_score (in [0,1]), total_traces, verified_traces, violation_count.
11
+ const scorePercent = (integrity.integrity_score * 100).toFixed(1);
12
+ const scoreBar = generateScoreBar(integrity.integrity_score);
11
13
  console.log(fmt.header("Integrity Score"));
12
14
  console.log(` ${fmt.label("Score: ", `${scorePercent}% ${scoreBar}`)}`);
13
15
  console.log(` ${fmt.label("Total: ", `${integrity.total_traces} traces`)}`);
14
- console.log(` ${fmt.label("Verified: ", `${integrity.verified}`)} ${fmt.success("")}`);
15
- console.log(` ${fmt.label("Violations:", ` ${integrity.violations}`)} ${fmt.error("")}`);
16
- console.log(` ${fmt.label("Updated: ", integrity.last_updated)}`);
17
- if (integrity.violations > 0) {
16
+ console.log(` ${fmt.label("Verified: ", `${integrity.verified_traces}`)}`);
17
+ console.log(` ${fmt.label("Violations:", ` ${integrity.violation_count}`)}`);
18
+ if (integrity.violation_count > 0) {
18
19
  console.log("\n" + fmt.warn("You have integrity violations. Run `mnemom logs` to investigate.") + "\n");
19
20
  }
20
21
  else if (integrity.total_traces === 0) {
@@ -4,11 +4,19 @@ export interface ValidationCheck {
4
4
  message: string;
5
5
  }
6
6
  /**
7
- * Validate a protection card object.
8
- * Schema: mode, thresholds, screen_surfaces, trusted_sources
7
+ * Validate a protection card against ADR-037 canonical form.
8
+ *
9
+ * Required: card_version, agent_id, mode (off|observe|nudge|enforce).
10
+ * Optional: thresholds (warn ≤ quarantine ≤ block, all in [0,1]),
11
+ * screen_surfaces (object of bools with the four named keys),
12
+ * trusted_sources (object of typed buckets, per-bucket deny-lists).
9
13
  */
10
14
  export declare function validateProtectionCard(card: Record<string, unknown>): ValidationCheck[];
11
15
  export declare function protectionShowCommand(agentName?: string): Promise<void>;
12
- export declare function protectionPublishCommand(file: string, agentName?: string): Promise<void>;
16
+ export declare function protectionPublishCommand(file: string, agentName?: string, options?: {
17
+ idempotencyKey?: string;
18
+ }): Promise<void>;
13
19
  export declare function protectionValidateCommand(file: string): Promise<void>;
14
- export declare function protectionEditCommand(agentName?: string): Promise<void>;
20
+ export declare function protectionEditCommand(agentName?: string, options?: {
21
+ idempotencyKey?: string;
22
+ }): Promise<void>;
@@ -3,76 +3,283 @@ import * as path from "node:path";
3
3
  import * as os from "node:os";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import yaml from "js-yaml";
6
- import { getProtectionCard, putProtectionCard, resolveAgentId, } from "../lib/api.js";
6
+ import { PROTECTION_CARD_MAX_BYTES, getProtectionCard, putProtectionCard, resolveAgentId, } from "../lib/api.js";
7
7
  import { requireAuth } from "../lib/auth.js";
8
8
  import { fmt } from "../lib/format.js";
9
9
  import { askYesNo, isInteractive } from "../lib/prompt.js";
10
- const VALID_MODES = new Set(["observe", "warn", "block"]);
10
+ const PROTECTION_MODES = ["off", "observe", "nudge", "enforce"];
11
+ const SURFACE_KEYS = ["incoming", "outgoing", "tool_calls", "tool_responses"];
12
+ // Per ADR-037 Decision 4: deny public LLM endpoints + public DNS providers,
13
+ // and the any-host CIDRs, at write time.
14
+ const DENY_DOMAINS = new Set([
15
+ "api.openai.com",
16
+ "api.anthropic.com",
17
+ "generativelanguage.googleapis.com",
18
+ "api.cohere.ai",
19
+ "api.mistral.ai",
20
+ "api.groq.com",
21
+ "cloud.google.com",
22
+ "dns.google",
23
+ "cloudflare-dns.com",
24
+ "one.one.one.one",
25
+ "dns.quad9.net",
26
+ ]);
27
+ const DENY_IP_PREFIXES = [
28
+ "0.0.0.0/0",
29
+ "::/0",
30
+ "8.8.8.0/24",
31
+ "8.8.4.0/24",
32
+ "1.1.1.0/24",
33
+ "1.0.0.0/24",
34
+ "9.9.9.0/24",
35
+ ];
36
+ const DOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+(:\d{1,5})?$/i;
37
+ const AGENT_ID_RE = /^mnm-[a-z0-9-]{4,}$/i;
38
+ const CIDR_RE = /^([0-9]{1,3}(\.[0-9]{1,3}){3})\/([0-9]|[12][0-9]|3[0-2])$|^([0-9a-f:]+)\/(\d{1,3})$/i;
39
+ function isObject(v) {
40
+ return typeof v === "object" && v !== null && !Array.isArray(v);
41
+ }
42
+ function validateDomain(d) {
43
+ const lower = d.toLowerCase();
44
+ if (DENY_DOMAINS.has(lower.split(":")[0])) {
45
+ return "domain is on the static deny-list (public LLM/DNS endpoint)";
46
+ }
47
+ if (!DOMAIN_RE.test(lower))
48
+ return "not a valid DNS name (or host:port)";
49
+ return null;
50
+ }
51
+ function validateAgentId(a) {
52
+ if (!AGENT_ID_RE.test(a))
53
+ return "must match Mnemom agent ID format (mnm-*)";
54
+ return null;
55
+ }
56
+ function validateCidr(c) {
57
+ if (DENY_IP_PREFIXES.includes(c)) {
58
+ return "CIDR is on the static deny-list (public DNS / 0.0.0.0/0 / ::/0)";
59
+ }
60
+ if (!CIDR_RE.test(c))
61
+ return "not a valid CIDR notation";
62
+ return null;
63
+ }
64
+ function validateTrustedBucket(name, bucket, checks, perEntry) {
65
+ if (bucket === undefined)
66
+ return;
67
+ if (!Array.isArray(bucket)) {
68
+ checks.push({
69
+ name: `trusted_sources.${name}`,
70
+ passed: false,
71
+ message: `Must be an array of strings (got ${typeof bucket}). Per ADR-037 trusted_sources is an object of typed buckets — not an array of objects.`,
72
+ });
73
+ return;
74
+ }
75
+ let bad = 0;
76
+ bucket.forEach((entry, i) => {
77
+ if (typeof entry !== "string") {
78
+ checks.push({
79
+ name: `trusted_sources.${name}[${i}]`,
80
+ passed: false,
81
+ message: "Must be a string",
82
+ });
83
+ bad++;
84
+ return;
85
+ }
86
+ const err = perEntry(entry);
87
+ if (err) {
88
+ checks.push({
89
+ name: `trusted_sources.${name}[${i}]`,
90
+ passed: false,
91
+ message: `${entry}: ${err}`,
92
+ });
93
+ bad++;
94
+ }
95
+ });
96
+ if (bad === 0) {
97
+ checks.push({
98
+ name: `trusted_sources.${name}`,
99
+ passed: true,
100
+ message: `${bucket.length} entr${bucket.length === 1 ? "y" : "ies"}`,
101
+ });
102
+ }
103
+ }
11
104
  /**
12
- * Validate a protection card object.
13
- * Schema: mode, thresholds, screen_surfaces, trusted_sources
105
+ * Validate a protection card against ADR-037 canonical form.
106
+ *
107
+ * Required: card_version, agent_id, mode (off|observe|nudge|enforce).
108
+ * Optional: thresholds (warn ≤ quarantine ≤ block, all in [0,1]),
109
+ * screen_surfaces (object of bools with the four named keys),
110
+ * trusted_sources (object of typed buckets, per-bucket deny-lists).
14
111
  */
15
112
  export function validateProtectionCard(card) {
16
113
  const checks = [];
17
- // mode: required, must be observe | warn | block
18
- const mode = card.mode;
19
- if (typeof mode === "string" && VALID_MODES.has(mode)) {
20
- checks.push({ name: "mode", passed: true, message: mode });
114
+ // ── card_version (required) ──
115
+ if (typeof card.card_version !== "string" || card.card_version.length === 0) {
116
+ checks.push({
117
+ name: "card_version",
118
+ passed: false,
119
+ message: 'Required (string, e.g. "protection/2026-04-26"). See ADR-037.',
120
+ });
121
+ }
122
+ else {
123
+ checks.push({ name: "card_version", passed: true, message: card.card_version });
124
+ }
125
+ // ── agent_id (required) ──
126
+ if (typeof card.agent_id !== "string" || card.agent_id.length === 0) {
127
+ checks.push({
128
+ name: "agent_id",
129
+ passed: false,
130
+ message: "Required (string).",
131
+ });
132
+ }
133
+ else {
134
+ checks.push({ name: "agent_id", passed: true, message: card.agent_id });
21
135
  }
22
- else if (mode === undefined) {
23
- checks.push({ name: "mode", passed: false, message: "Required (observe | warn | block)" });
136
+ // ── mode (required, ADR-037 canonical enum) ──
137
+ const mode = card.mode;
138
+ if (typeof mode !== "string") {
139
+ checks.push({
140
+ name: "mode",
141
+ passed: false,
142
+ message: `Required. Must be one of: ${PROTECTION_MODES.join(" | ")}. Per ADR-037 the legacy "block"/"warn" values are no longer accepted.`,
143
+ });
144
+ }
145
+ else if (!PROTECTION_MODES.includes(mode)) {
146
+ checks.push({
147
+ name: "mode",
148
+ passed: false,
149
+ message: `Invalid: "${mode}". Must be one of: ${PROTECTION_MODES.join(" | ")}. (Per ADR-037 the legacy "block"/"warn" values are no longer accepted; use "enforce"/"nudge".)`,
150
+ });
24
151
  }
25
152
  else {
26
- checks.push({ name: "mode", passed: false, message: `Invalid: "${mode}". Must be observe | warn | block` });
153
+ checks.push({ name: "mode", passed: true, message: mode });
27
154
  }
28
- // thresholds: optional, if present must have warn/quarantine/block in ascending order (0-1)
29
- const thresholds = card.thresholds;
30
- if (thresholds !== undefined) {
31
- if (typeof thresholds !== "object" || thresholds === null) {
32
- checks.push({ name: "thresholds", passed: false, message: "Must be an object" });
155
+ // ── thresholds (optional) ──
156
+ if (card.thresholds !== undefined) {
157
+ if (!isObject(card.thresholds)) {
158
+ checks.push({ name: "thresholds", passed: false, message: "Must be an object if present" });
33
159
  }
34
160
  else {
35
- const w = thresholds.warn;
36
- const q = thresholds.quarantine;
37
- const b = thresholds.block;
38
- if (w === undefined || q === undefined || b === undefined) {
39
- checks.push({ name: "thresholds", passed: false, message: "Must have warn, quarantine, and block fields" });
161
+ const t = card.thresholds;
162
+ const nums = {};
163
+ let bad = false;
164
+ for (const key of ["warn", "quarantine", "block"]) {
165
+ const v = t[key];
166
+ if (v === undefined) {
167
+ checks.push({
168
+ name: `thresholds.${key}`,
169
+ passed: false,
170
+ message: "Required when thresholds is present",
171
+ });
172
+ bad = true;
173
+ continue;
174
+ }
175
+ if (typeof v !== "number" || v < 0 || v > 1 || Number.isNaN(v)) {
176
+ checks.push({
177
+ name: `thresholds.${key}`,
178
+ passed: false,
179
+ message: "Must be a number in [0, 1]",
180
+ });
181
+ bad = true;
182
+ continue;
183
+ }
184
+ nums[key] = v;
40
185
  }
41
- else if (typeof w !== "number" || typeof q !== "number" || typeof b !== "number") {
42
- checks.push({ name: "thresholds", passed: false, message: "Values must be numbers" });
186
+ if (nums.warn !== undefined && nums.quarantine !== undefined && nums.warn > nums.quarantine) {
187
+ checks.push({
188
+ name: "thresholds.warn",
189
+ passed: false,
190
+ message: `Must be ≤ thresholds.quarantine (warn=${nums.warn} > quarantine=${nums.quarantine})`,
191
+ });
192
+ bad = true;
43
193
  }
44
- else if (w < 0 || w > 1 || q < 0 || q > 1 || b < 0 || b > 1) {
45
- checks.push({ name: "thresholds", passed: false, message: "Values must be between 0 and 1" });
194
+ if (nums.quarantine !== undefined && nums.block !== undefined && nums.quarantine > nums.block) {
195
+ checks.push({
196
+ name: "thresholds.quarantine",
197
+ passed: false,
198
+ message: `Must be ≤ thresholds.block (quarantine=${nums.quarantine} > block=${nums.block})`,
199
+ });
200
+ bad = true;
46
201
  }
47
- else if (!(w <= q && q <= b)) {
48
- checks.push({ name: "thresholds", passed: false, message: `Must be in ascending order (warn=${w} <= quarantine=${q} <= block=${b})` });
49
- }
50
- else {
51
- checks.push({ name: "thresholds", passed: true, message: `warn=${w}, quarantine=${q}, block=${b}` });
202
+ if (!bad) {
203
+ checks.push({
204
+ name: "thresholds",
205
+ passed: true,
206
+ message: `warn=${nums.warn}, quarantine=${nums.quarantine}, block=${nums.block}`,
207
+ });
52
208
  }
53
209
  }
54
210
  }
55
- // screen_surfaces: optional, array of strings
56
- const surfaces = card.screen_surfaces;
57
- if (surfaces !== undefined) {
58
- if (!Array.isArray(surfaces)) {
59
- checks.push({ name: "screen_surfaces", passed: false, message: "Must be an array" });
211
+ // ── screen_surfaces (optional, object of bools) ──
212
+ if (card.screen_surfaces !== undefined) {
213
+ if (Array.isArray(card.screen_surfaces)) {
214
+ checks.push({
215
+ name: "screen_surfaces",
216
+ passed: false,
217
+ message: 'Must be an object of booleans, not an array. Per ADR-037 use { incoming: true, outgoing: true, tool_calls: true, tool_responses: true }.',
218
+ });
60
219
  }
61
- else if (surfaces.some((s) => typeof s !== "string")) {
62
- checks.push({ name: "screen_surfaces", passed: false, message: "All entries must be strings" });
220
+ else if (!isObject(card.screen_surfaces)) {
221
+ checks.push({ name: "screen_surfaces", passed: false, message: "Must be an object if present" });
63
222
  }
64
223
  else {
65
- checks.push({ name: "screen_surfaces", passed: true, message: `${surfaces.length} surface(s)` });
224
+ const s = card.screen_surfaces;
225
+ let bad = false;
226
+ for (const key of SURFACE_KEYS) {
227
+ const v = s[key];
228
+ if (v !== undefined && typeof v !== "boolean") {
229
+ checks.push({
230
+ name: `screen_surfaces.${key}`,
231
+ passed: false,
232
+ message: "Must be a boolean",
233
+ });
234
+ bad = true;
235
+ }
236
+ }
237
+ for (const key of Object.keys(s)) {
238
+ if (!SURFACE_KEYS.includes(key)) {
239
+ checks.push({
240
+ name: `screen_surfaces.${key}`,
241
+ passed: false,
242
+ message: `Unknown surface (allowed: ${SURFACE_KEYS.join(", ")})`,
243
+ });
244
+ bad = true;
245
+ }
246
+ }
247
+ if (!bad) {
248
+ const enabled = SURFACE_KEYS.filter(k => s[k] === true).length;
249
+ checks.push({
250
+ name: "screen_surfaces",
251
+ passed: true,
252
+ message: `${enabled}/${SURFACE_KEYS.length} surfaces enabled`,
253
+ });
254
+ }
66
255
  }
67
256
  }
68
- // trusted_sources: optional, array of objects
69
- const trusted = card.trusted_sources;
70
- if (trusted !== undefined) {
71
- if (!Array.isArray(trusted)) {
72
- checks.push({ name: "trusted_sources", passed: false, message: "Must be an array" });
257
+ // ── trusted_sources (optional, typed buckets) ──
258
+ if (card.trusted_sources !== undefined) {
259
+ if (Array.isArray(card.trusted_sources)) {
260
+ checks.push({
261
+ name: "trusted_sources",
262
+ passed: false,
263
+ message: 'Must be an object of typed buckets, not an array. Per ADR-037 use { domains: [...], agent_ids: [...], ip_ranges: [...] } — the legacy [{pattern, ...}] shape is no longer accepted.',
264
+ });
265
+ }
266
+ else if (!isObject(card.trusted_sources)) {
267
+ checks.push({ name: "trusted_sources", passed: false, message: "Must be an object if present" });
73
268
  }
74
269
  else {
75
- checks.push({ name: "trusted_sources", passed: true, message: `${trusted.length} source(s)` });
270
+ const ts = card.trusted_sources;
271
+ validateTrustedBucket("domains", ts.domains, checks, validateDomain);
272
+ validateTrustedBucket("agent_ids", ts.agent_ids, checks, validateAgentId);
273
+ validateTrustedBucket("ip_ranges", ts.ip_ranges, checks, validateCidr);
274
+ for (const key of Object.keys(ts)) {
275
+ if (!["domains", "agent_ids", "ip_ranges"].includes(key)) {
276
+ checks.push({
277
+ name: `trusted_sources.${key}`,
278
+ passed: false,
279
+ message: "Unknown bucket (allowed: domains, agent_ids, ip_ranges)",
280
+ });
281
+ }
282
+ }
76
283
  }
77
284
  }
78
285
  return checks;
@@ -125,7 +332,7 @@ export async function protectionShowCommand(agentName) {
125
332
  process.exit(1);
126
333
  }
127
334
  }
128
- export async function protectionPublishCommand(file, agentName) {
335
+ export async function protectionPublishCommand(file, agentName, options = {}) {
129
336
  const agentId = await resolveAgentId(agentName);
130
337
  const filePath = path.resolve(file);
131
338
  if (!fs.existsSync(filePath)) {
@@ -172,11 +379,19 @@ export async function protectionPublishCommand(file, agentName) {
172
379
  console.log("\nPublishing protection card...");
173
380
  const contentType = parsed.format === "yaml" ? "text/yaml" : "application/json";
174
381
  const body = parsed.format === "yaml" ? parsed.raw : JSON.stringify(parsed.parsed);
175
- const result = await putProtectionCard(agentId, body, contentType);
382
+ const bodyBytes = Buffer.byteLength(body, "utf-8");
383
+ if (bodyBytes > PROTECTION_CARD_MAX_BYTES) {
384
+ console.log("\n" +
385
+ fmt.error(`Protection card is ${bodyBytes} bytes; limit is ${PROTECTION_CARD_MAX_BYTES} bytes (64 KB). The API will return 413.`) +
386
+ "\n");
387
+ process.exit(1);
388
+ }
389
+ const result = await putProtectionCard(agentId, body, contentType, {
390
+ idempotencyKey: options.idempotencyKey,
391
+ });
176
392
  console.log(fmt.success("Protection card published!"));
177
- console.log(fmt.label(" Card ID:", ` ${result.card_id}`));
178
- if (result.composed) {
179
- console.log(fmt.success("Canonical protection card recomposed"));
393
+ if (typeof result.card_id === "string" && result.card_id.length > 0) {
394
+ console.log(fmt.label(" Card ID:", ` ${result.card_id}`));
180
395
  }
181
396
  console.log();
182
397
  }
@@ -227,7 +442,7 @@ export async function protectionValidateCommand(file) {
227
442
  process.exit(1);
228
443
  }
229
444
  }
230
- export async function protectionEditCommand(agentName) {
445
+ export async function protectionEditCommand(agentName, options = {}) {
231
446
  const agentId = await resolveAgentId(agentName);
232
447
  await requireAuth();
233
448
  console.log("\nFetching current protection card...\n");
@@ -236,10 +451,17 @@ export async function protectionEditCommand(agentName) {
236
451
  console.log(fmt.warn("No protection card found. Creating a template..."));
237
452
  }
238
453
  const cardYaml = original || yaml.dump({
454
+ card_version: "protection/2026-04-26",
455
+ agent_id: agentId,
239
456
  mode: "observe",
240
457
  thresholds: { warn: 0.3, quarantine: 0.6, block: 0.9 },
241
- screen_surfaces: ["system_prompt", "tool_input", "tool_output"],
242
- trusted_sources: [],
458
+ screen_surfaces: {
459
+ incoming: true,
460
+ outgoing: true,
461
+ tool_calls: true,
462
+ tool_responses: true,
463
+ },
464
+ trusted_sources: { domains: [], agent_ids: [], ip_ranges: [] },
243
465
  }, { lineWidth: 120, noRefs: true });
244
466
  const tmpDir = os.tmpdir();
245
467
  const tmpFile = path.join(tmpDir, `mnemom-protection-${agentId}.yaml`);
@@ -296,9 +518,21 @@ export async function protectionEditCommand(agentName) {
296
518
  }
297
519
  try {
298
520
  console.log("\nPublishing protection card...");
299
- const putResult = await putProtectionCard(agentId, edited, "text/yaml");
521
+ const editedBytes = Buffer.byteLength(edited, "utf-8");
522
+ if (editedBytes > PROTECTION_CARD_MAX_BYTES) {
523
+ console.log("\n" +
524
+ fmt.error(`Protection card is ${editedBytes} bytes; limit is ${PROTECTION_CARD_MAX_BYTES} bytes (64 KB). The API will return 413.`) +
525
+ "\n");
526
+ process.exit(1);
527
+ }
528
+ const putResult = await putProtectionCard(agentId, edited, "text/yaml", {
529
+ idempotencyKey: options.idempotencyKey,
530
+ });
300
531
  console.log(fmt.success("Protection card published!"));
301
- console.log(fmt.label(" Card ID:", ` ${putResult.card_id}`) + "\n");
532
+ if (typeof putResult.card_id === "string" && putResult.card_id.length > 0) {
533
+ console.log(fmt.label(" Card ID:", ` ${putResult.card_id}`));
534
+ }
535
+ console.log();
302
536
  }
303
537
  catch (error) {
304
538
  const message = error instanceof Error ? error.message : String(error);
@@ -331,12 +331,14 @@ async function showTraceSummary(agentId) {
331
331
  console.log();
332
332
  if (integrityResult.status === "fulfilled") {
333
333
  const integrity = integrityResult.value;
334
- const score = (integrity.score * 100).toFixed(1);
334
+ // Field names per docs.mnemom.ai IntegrityScore: integrity_score
335
+ // (in [0,1]), verified_traces, violation_count.
336
+ const score = (integrity.integrity_score * 100).toFixed(1);
335
337
  console.log(`Integrity Score: ${score}%`);
336
338
  console.log(`Total Traces: ${integrity.total_traces}`);
337
- console.log(`Verified: ${integrity.verified}`);
338
- if (integrity.violations > 0) {
339
- console.log(`Violations: ${integrity.violations}`);
339
+ console.log(`Verified: ${integrity.verified_traces}`);
340
+ if (integrity.violation_count > 0) {
341
+ console.log(`Violations: ${integrity.violation_count}`);
340
342
  }
341
343
  }
342
344
  else {
package/dist/index.js CHANGED
@@ -116,10 +116,11 @@ cardCmd
116
116
  cardCmd
117
117
  .command("edit")
118
118
  .description("Edit alignment card in $EDITOR")
119
- .action(async () => {
119
+ .option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
120
+ .action(async (subOpts) => {
120
121
  try {
121
122
  const opts = program.opts();
122
- await cardEditCommand(opts.agent);
123
+ await cardEditCommand(opts.agent, { idempotencyKey: subOpts.idempotencyKey });
123
124
  }
124
125
  catch (error) {
125
126
  console.error("Error:", error instanceof Error ? error.message : error);
@@ -130,10 +131,11 @@ cardCmd
130
131
  .command("publish")
131
132
  .argument("<file>", "Path to alignment card file (YAML or JSON)")
132
133
  .description("Publish alignment card")
133
- .action(async (file) => {
134
+ .option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
135
+ .action(async (file, subOpts) => {
134
136
  try {
135
137
  const opts = program.opts();
136
- await cardPublishCommand(file, opts.agent);
138
+ await cardPublishCommand(file, opts.agent, { idempotencyKey: subOpts.idempotencyKey });
137
139
  }
138
140
  catch (error) {
139
141
  console.error("Error:", error instanceof Error ? error.message : error);
@@ -191,10 +193,11 @@ protectionCmd
191
193
  protectionCmd
192
194
  .command("edit")
193
195
  .description("Edit protection card in $EDITOR")
194
- .action(async () => {
196
+ .option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
197
+ .action(async (subOpts) => {
195
198
  try {
196
199
  const opts = program.opts();
197
- await protectionEditCommand(opts.agent);
200
+ await protectionEditCommand(opts.agent, { idempotencyKey: subOpts.idempotencyKey });
198
201
  }
199
202
  catch (error) {
200
203
  console.error("Error:", error instanceof Error ? error.message : error);
@@ -205,10 +208,11 @@ protectionCmd
205
208
  .command("publish")
206
209
  .argument("<file>", "Path to protection card file (YAML or JSON)")
207
210
  .description("Publish protection card")
208
- .action(async (file) => {
211
+ .option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
212
+ .action(async (file, subOpts) => {
209
213
  try {
210
214
  const opts = program.opts();
211
- await protectionPublishCommand(file, opts.agent);
215
+ await protectionPublishCommand(file, opts.agent, { idempotencyKey: subOpts.idempotencyKey });
212
216
  }
213
217
  catch (error) {
214
218
  console.error("Error:", error instanceof Error ? error.message : error);