@mnemom/mnemom 0.9.1 → 0.11.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/index.js CHANGED
@@ -3,12 +3,18 @@ import { program } from "commander";
3
3
  import { statusCommand } from "./commands/status.js";
4
4
  import { integrityCommand } from "./commands/integrity.js";
5
5
  import { logsCommand } from "./commands/logs.js";
6
- import { licenseActivateCommand, licenseStatusCommand, licenseDeactivateCommand } from "./commands/license.js";
7
- import { cardShowCommand, cardPublishCommand, cardValidateCommand, cardEditCommand, cardEvaluateCommand } from "./commands/card.js";
6
+ import { licenseActivateCommand, licenseStatusCommand, licenseDeactivateCommand, } from "./commands/license.js";
7
+ import { cardShowCommand, cardPublishCommand, cardValidateCommand, cardEditCommand, cardEvaluateCommand, } from "./commands/card.js";
8
8
  import { policyInitCommand, policyValidateCommand, policyPublishCommand, policyListCommand, policyTestCommand, policyEvaluateCommand, } from "./commands/policy.js";
9
- import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand } from "./commands/protection.js";
9
+ import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, } from "./commands/protection.js";
10
10
  import { agentsListCommand } from "./commands/agents.js";
11
+ import { orgListCommand, orgShowCommand } from "./commands/org.js";
12
+ import { teamListCommand, teamShowCommand, teamTemplateCommand, teamPreviewComposeCommand, teamAdminGrantCommand, teamAdminRevokeCommand, teamAdminListCommand, teamCoverageCommand, } from "./commands/team.js";
13
+ import { advisoriesListCommand, advisoriesShowCommand, } from "./commands/advisories.js";
14
+ import { postureListCommand, postureShowCommand, postureCreateCommand, postureUpdateCommand, postureCloneCommand, postureRevisionsCommand, postureDiffCommand, postureAssignCommand, postureUnassignCommand, posturePreviewComposeCommand, postureDeleteCommand, } from "./commands/posture.js";
11
15
  import { loginCommand, logoutCommand, whoamiCommand } from "./commands/auth.js";
16
+ import { validateSafeHouseCommand } from "./commands/validate.js";
17
+ import { apiKeyListCommand, apiKeyCreateCommand, apiKeyRotateCommand, apiKeyRevokeCommand, } from "./commands/api-key.js";
12
18
  program
13
19
  .name("mnemom")
14
20
  .description("Transparent AI agent tracing")
@@ -55,9 +61,7 @@ program
55
61
  process.exit(1);
56
62
  }
57
63
  });
58
- const license = program
59
- .command("license")
60
- .description("Enterprise license management");
64
+ const license = program.command("license").description("Enterprise license management");
61
65
  license
62
66
  .command("activate <jwt>")
63
67
  .description("Activate an enterprise license")
@@ -97,9 +101,7 @@ license
97
101
  // ============================================================================
98
102
  // Card commands (alignment card)
99
103
  // ============================================================================
100
- const cardCmd = program
101
- .command("card")
102
- .description("Manage alignment card");
104
+ const cardCmd = program.command("card").description("Manage alignment card");
103
105
  cardCmd
104
106
  .command("show")
105
107
  .description("Display alignment card (YAML)")
@@ -174,9 +176,7 @@ cardCmd
174
176
  // ============================================================================
175
177
  // Protection card commands
176
178
  // ============================================================================
177
- const protectionCmd = program
178
- .command("protection")
179
- .description("Manage protection card");
179
+ const protectionCmd = program.command("protection").description("Manage protection card");
180
180
  protectionCmd
181
181
  .command("show")
182
182
  .description("Display protection card (YAML)")
@@ -241,12 +241,16 @@ const policyCmd = program
241
241
  policyCmd
242
242
  .command("init")
243
243
  .description("(removed) Use `mnemom card validate`")
244
- .action(async () => { await policyInitCommand(); });
244
+ .action(async () => {
245
+ await policyInitCommand();
246
+ });
245
247
  policyCmd
246
248
  .command("validate")
247
249
  .argument("<file>", "Path to policy file")
248
250
  .description("(removed) Use `mnemom card validate`")
249
- .action(async (file) => { await policyValidateCommand(file); });
251
+ .action(async (file) => {
252
+ await policyValidateCommand(file);
253
+ });
250
254
  policyCmd
251
255
  .command("publish")
252
256
  .argument("<file>", "Path to policy file")
@@ -297,6 +301,607 @@ program
297
301
  }
298
302
  });
299
303
  // ============================================================================
304
+ // Organization management (ADR-044, Piece 1 of T1-3.1)
305
+ // ============================================================================
306
+ const orgCmd = program
307
+ .command("org")
308
+ .description("Manage your organizations (personal + multi-user)");
309
+ orgCmd
310
+ .command("list")
311
+ .description("List every org you are a member of, including your personal org")
312
+ .option("--json", "Output JSON instead of a human-readable table")
313
+ .action(async (options) => {
314
+ try {
315
+ await orgListCommand({ json: options.json });
316
+ }
317
+ catch (error) {
318
+ console.error("Error:", error instanceof Error ? error.message : error);
319
+ process.exit(1);
320
+ }
321
+ });
322
+ orgCmd
323
+ .command("show [org_id]")
324
+ .description("Show details of an org (default: your personal org if --personal, else single membership)")
325
+ .option("--personal", "Show your personal-org-of-one (per ADR-044)")
326
+ .option("--json", "Output JSON instead of human-readable text")
327
+ .action(async (orgId, options) => {
328
+ try {
329
+ await orgShowCommand(orgId, { personal: options.personal, json: options.json });
330
+ }
331
+ catch (error) {
332
+ console.error("Error:", error instanceof Error ? error.message : error);
333
+ process.exit(1);
334
+ }
335
+ });
336
+ // ============================================================================
337
+ // Team management (ADR-044 amended, Piece 2 of T1-3.1)
338
+ //
339
+ // Per ADR-044 + Charter §I11: team membership is OPTIONAL. Solo agents
340
+ // (zero teams) compose under Platform → Org → Agent. Backend RBAC for
341
+ // team-template endpoints is purely org-level (requireOrgRole on the
342
+ // team's parent org); Team Admin role lands in Piece 5.
343
+ // ============================================================================
344
+ const teamCmd = program
345
+ .command("team")
346
+ .description("Manage teams and their scope-cascade templates");
347
+ teamCmd
348
+ .command("list")
349
+ .description("List every team across all orgs you are a member of")
350
+ .option("--json", "Output JSON instead of a human-readable table")
351
+ .action(async (options) => {
352
+ try {
353
+ await teamListCommand({ json: options.json });
354
+ }
355
+ catch (error) {
356
+ console.error("Error:", error instanceof Error ? error.message : error);
357
+ process.exit(1);
358
+ }
359
+ });
360
+ teamCmd
361
+ .command("show <team_id>")
362
+ .description("Show details of a single team")
363
+ .option("--json", "Output JSON instead of human-readable text")
364
+ .action(async (teamId, options) => {
365
+ try {
366
+ await teamShowCommand(teamId, { json: options.json });
367
+ }
368
+ catch (error) {
369
+ console.error("Error:", error instanceof Error ? error.message : error);
370
+ process.exit(1);
371
+ }
372
+ });
373
+ teamCmd
374
+ .command("alignment-template <team_id>")
375
+ .description("Read or write the team's alignment template")
376
+ .option("--set <file>", "Write the template from a YAML or JSON file")
377
+ .option("--clear", "Clear the template")
378
+ .option("--json", "Output JSON instead of human-readable text")
379
+ .action(async (teamId, options) => {
380
+ try {
381
+ await teamTemplateCommand("alignment", teamId, options);
382
+ }
383
+ catch (error) {
384
+ console.error("Error:", error instanceof Error ? error.message : error);
385
+ process.exit(1);
386
+ }
387
+ });
388
+ teamCmd
389
+ .command("protection-template <team_id>")
390
+ .description("Read or write the team's protection template")
391
+ .option("--set <file>", "Write the template from a YAML or JSON file")
392
+ .option("--clear", "Clear the template")
393
+ .option("--json", "Output JSON instead of human-readable text")
394
+ .action(async (teamId, options) => {
395
+ try {
396
+ await teamTemplateCommand("protection", teamId, options);
397
+ }
398
+ catch (error) {
399
+ console.error("Error:", error instanceof Error ? error.message : error);
400
+ process.exit(1);
401
+ }
402
+ });
403
+ teamCmd
404
+ .command("preview-compose <team_id>")
405
+ .description("Dry-run the composer with a draft template (alignment by default)")
406
+ .option("--protection", "Preview protection template (default: alignment)")
407
+ .option("--from <file>", "Read draft template from a file (default: stdin)")
408
+ .option("--json", "Output JSON instead of human-readable text")
409
+ .action(async (teamId, options) => {
410
+ try {
411
+ await teamPreviewComposeCommand(teamId, options);
412
+ }
413
+ catch (error) {
414
+ console.error("Error:", error instanceof Error ? error.message : error);
415
+ process.exit(1);
416
+ }
417
+ });
418
+ // `mnemom team admin {grant,revoke,list}` — Piece 5 of T1-3.1 (ADR-046).
419
+ const teamAdminCmd = teamCmd
420
+ .command("admin")
421
+ .description("Manage team_admin grants (Piece 5 of T1-3.1, ADR-046)");
422
+ teamAdminCmd
423
+ .command("grant <team_id>")
424
+ .description("Grant team_admin role to a user on this team")
425
+ .requiredOption("--user <user_id>", "User ID to grant team_admin to")
426
+ .option("--json", "Output JSON instead of human-readable text")
427
+ .action(async (teamId, options) => {
428
+ try {
429
+ await teamAdminGrantCommand(teamId, options);
430
+ }
431
+ catch (error) {
432
+ console.error("Error:", error instanceof Error ? error.message : error);
433
+ process.exit(1);
434
+ }
435
+ });
436
+ teamAdminCmd
437
+ .command("revoke <team_id>")
438
+ .description("Revoke a team_admin grant (admin or self)")
439
+ .requiredOption("--user <user_id>", "User ID to revoke (use your own ID for self-revoke)")
440
+ .option("--json", "Output JSON instead of human-readable text")
441
+ .action(async (teamId, options) => {
442
+ try {
443
+ await teamAdminRevokeCommand(teamId, options);
444
+ }
445
+ catch (error) {
446
+ console.error("Error:", error instanceof Error ? error.message : error);
447
+ process.exit(1);
448
+ }
449
+ });
450
+ teamAdminCmd
451
+ .command("list <team_id>")
452
+ .description("List active team_admin grants on a team")
453
+ .option("--json", "Output JSON instead of human-readable text")
454
+ .action(async (teamId, options) => {
455
+ try {
456
+ await teamAdminListCommand(teamId, options);
457
+ }
458
+ catch (error) {
459
+ console.error("Error:", error instanceof Error ? error.message : error);
460
+ process.exit(1);
461
+ }
462
+ });
463
+ // `mnemom team coverage <team_id>` — Piece 6 of T1-3.1 (ADR-040 + ADR-045 +
464
+ // ADR-047). Compliance-grade proof-of-coverage for a team's sideband
465
+ // detector sweeps; per-axis 30-day summary + per-day rows.
466
+ teamCmd
467
+ .command("coverage <team_id>")
468
+ .description("Show last-30-day sideband sweep coverage for a team")
469
+ .option("--rows", "Include per-day rows in addition to per-axis summary")
470
+ .option("--json", "Output JSON instead of human-readable text")
471
+ .action(async (teamId, options) => {
472
+ try {
473
+ await teamCoverageCommand(teamId, options);
474
+ }
475
+ catch (error) {
476
+ console.error("Error:", error instanceof Error ? error.message : error);
477
+ process.exit(1);
478
+ }
479
+ });
480
+ // ============================================================================
481
+ // Sideband advisories (Piece 6 of T1-3.1, ADR-040 + ADR-045 + ADR-047)
482
+ //
483
+ // Surface the `pending_advisories` rows where source LIKE 'sideband.%' as a
484
+ // CLI-readable cross-turn carryover view. Detector firings (coherence,
485
+ // fault-line, fleet, drift) land here; the gateway injects them on the
486
+ // agent's next turn.
487
+ // ============================================================================
488
+ const advisoriesCmd = program
489
+ .command("advisories")
490
+ .description("List + inspect sideband advisories (pending_advisories rows)");
491
+ advisoriesCmd
492
+ .command("list")
493
+ .description("List recent sideband advisories scoped to one agent or one team")
494
+ .option("--agent <id>", "Scope to a single agent")
495
+ .option("--team <id>", "Scope to all agents in a team")
496
+ .option("--source <source>", "Filter to one source (e.g. sideband.coherence)")
497
+ .option("--limit <n>", "Page size (default 50 for agent, 100 for team)")
498
+ .option("--since <iso>", "Only advisories created at or after this ISO timestamp")
499
+ .option("--json", "Output JSON")
500
+ .action(async (options) => {
501
+ try {
502
+ await advisoriesListCommand(options);
503
+ }
504
+ catch (error) {
505
+ console.error("Error:", error instanceof Error ? error.message : error);
506
+ process.exit(1);
507
+ }
508
+ });
509
+ advisoriesCmd
510
+ .command("show <advisory_id>")
511
+ .description("Show one advisory's full content (nudge_content + source_ref)")
512
+ .option("--agent <id>", "Scope lookup to this agent")
513
+ .option("--team <id>", "Scope lookup to this team")
514
+ .option("--json", "Output JSON")
515
+ .action(async (advisoryId, options) => {
516
+ try {
517
+ await advisoriesShowCommand(advisoryId, options);
518
+ }
519
+ catch (error) {
520
+ console.error("Error:", error instanceof Error ? error.message : error);
521
+ process.exit(1);
522
+ }
523
+ });
524
+ // ============================================================================
525
+ // Governance signals (ADR-048) — operator-actionable observation surface
526
+ //
527
+ // Distinct from `mnemom advisories list` (which now narrows to runtime.* /
528
+ // manual.* per ADR-048 §1). Surfaces sideband.* + future protection.* /
529
+ // posture.* signals from governance_signals: ack/resolve/dismiss workflow,
530
+ // notification destinations (slack/email/pagerduty/webhook), escalation
531
+ // rules.
532
+ // ============================================================================
533
+ import { governanceSignalsListCommand, governanceSignalsShowCommand, governanceSignalsAckCommand, governanceSignalsResolveCommand, governanceSignalsDismissCommand, governanceDestinationsListCommand, governanceDestinationsAddCommand, governanceDestinationsRemoveCommand, governanceDestinationsTestCommand, governanceRulesListCommand, governanceRulesAddCommand, governanceRulesRemoveCommand, } from "./commands/governance.js";
534
+ const governanceCmd = program
535
+ .command("governance")
536
+ .description("Operator workflow for governance_signals (ADR-048)");
537
+ const govSignalsCmd = governanceCmd
538
+ .command("signals")
539
+ .description("List, inspect, and transition governance_signals");
540
+ govSignalsCmd
541
+ .command("list")
542
+ .description("List governance signals at platform/org/team/agent scope")
543
+ .option("--org <id>", "Org scope")
544
+ .option("--team <id>", "Team scope")
545
+ .option("--agent <id>", "Per-agent slice (signals affecting this agent)")
546
+ .option("--source <s>", "Filter by source (sideband.{drift,coherence,fault_line,fleet})")
547
+ .option("--severity <s>", "Filter by severity (info|warn|high|critical)")
548
+ .option("--status <s>", "Filter by status (open|acknowledged|resolved|dismissed|expired)")
549
+ .option("--scope <s>", "Filter by scope (platform|org|team|agent)")
550
+ .option("--pattern-type <s>", "Filter by pattern_type")
551
+ .option("--since <iso>", "Detected since (ISO timestamp)")
552
+ .option("--limit <n>", "Max rows (default 100, max 500)")
553
+ .option("--json", "Output JSON")
554
+ .action(async (options) => {
555
+ try {
556
+ await governanceSignalsListCommand(options);
557
+ }
558
+ catch (error) {
559
+ console.error("Error:", error instanceof Error ? error.message : error);
560
+ process.exit(1);
561
+ }
562
+ });
563
+ govSignalsCmd
564
+ .command("show <signal_id>")
565
+ .description("Show one signal in detail")
566
+ .option("--json", "Output JSON")
567
+ .action(async (signalId, options) => {
568
+ try {
569
+ await governanceSignalsShowCommand(signalId, options);
570
+ }
571
+ catch (error) {
572
+ console.error("Error:", error instanceof Error ? error.message : error);
573
+ process.exit(1);
574
+ }
575
+ });
576
+ govSignalsCmd
577
+ .command("ack <signal_id>")
578
+ .description("Acknowledge an open signal (org_admin / org_owner)")
579
+ .option("--action <text>", "Operator-authored action note")
580
+ .option("--json", "Output JSON")
581
+ .action(async (signalId, options) => {
582
+ try {
583
+ await governanceSignalsAckCommand(signalId, options);
584
+ }
585
+ catch (error) {
586
+ console.error("Error:", error instanceof Error ? error.message : error);
587
+ process.exit(1);
588
+ }
589
+ });
590
+ govSignalsCmd
591
+ .command("resolve <signal_id>")
592
+ .description("Resolve a signal with a resolution status")
593
+ .requiredOption("--status <s>", "Resolution status (action_taken|wont_fix|duplicate|false_positive|self_resolved)")
594
+ .option("--action <text>", "Operator-authored action note")
595
+ .option("--json", "Output JSON")
596
+ .action(async (signalId, options) => {
597
+ try {
598
+ await governanceSignalsResolveCommand(signalId, options);
599
+ }
600
+ catch (error) {
601
+ console.error("Error:", error instanceof Error ? error.message : error);
602
+ process.exit(1);
603
+ }
604
+ });
605
+ govSignalsCmd
606
+ .command("dismiss <signal_id>")
607
+ .description("Dismiss a signal as not actionable")
608
+ .option("--reason <text>", "Operator-authored dismissal reason")
609
+ .option("--json", "Output JSON")
610
+ .action(async (signalId, options) => {
611
+ try {
612
+ await governanceSignalsDismissCommand(signalId, options);
613
+ }
614
+ catch (error) {
615
+ console.error("Error:", error instanceof Error ? error.message : error);
616
+ process.exit(1);
617
+ }
618
+ });
619
+ const govDestCmd = governanceCmd
620
+ .command("destinations")
621
+ .description("Manage notification destinations (slack/email/pagerduty/webhook)");
622
+ govDestCmd
623
+ .command("list")
624
+ .description("List destinations for an org")
625
+ .requiredOption("--org <id>", "Org ID")
626
+ .option("--json", "Output JSON")
627
+ .action(async (options) => {
628
+ try {
629
+ await governanceDestinationsListCommand(options);
630
+ }
631
+ catch (error) {
632
+ console.error("Error:", error instanceof Error ? error.message : error);
633
+ process.exit(1);
634
+ }
635
+ });
636
+ govDestCmd
637
+ .command("add")
638
+ .description("Add a destination (channel + JSON config)")
639
+ .requiredOption("--org <id>", "Org ID")
640
+ .requiredOption("--channel <c>", "webhook|slack|email|pagerduty")
641
+ .requiredOption("--config <json>", "Channel config (e.g., '{\"url\":\"...\",\"signing_secret\":\"...\"}')")
642
+ .option("--name <n>", "Display name")
643
+ .option("--filter <json>", "Filter narrowing (sources/severities/etc)")
644
+ .option("--json", "Output JSON")
645
+ .action(async (options) => {
646
+ try {
647
+ await governanceDestinationsAddCommand(options);
648
+ }
649
+ catch (error) {
650
+ console.error("Error:", error instanceof Error ? error.message : error);
651
+ process.exit(1);
652
+ }
653
+ });
654
+ govDestCmd
655
+ .command("remove <destination_id>")
656
+ .description("Remove a destination")
657
+ .requiredOption("--org <id>", "Org ID")
658
+ .option("--json", "Output JSON")
659
+ .action(async (id, options) => {
660
+ try {
661
+ await governanceDestinationsRemoveCommand(id, options);
662
+ }
663
+ catch (error) {
664
+ console.error("Error:", error instanceof Error ? error.message : error);
665
+ process.exit(1);
666
+ }
667
+ });
668
+ govDestCmd
669
+ .command("test <destination_id>")
670
+ .description("Send a synthetic test signal through this destination")
671
+ .requiredOption("--org <id>", "Org ID")
672
+ .option("--json", "Output JSON")
673
+ .action(async (id, options) => {
674
+ try {
675
+ await governanceDestinationsTestCommand(id, options);
676
+ }
677
+ catch (error) {
678
+ console.error("Error:", error instanceof Error ? error.message : error);
679
+ process.exit(1);
680
+ }
681
+ });
682
+ const govRulesCmd = governanceCmd
683
+ .command("rules")
684
+ .description("Manage escalation rules (predicate → destinations)");
685
+ govRulesCmd
686
+ .command("list")
687
+ .description("List escalation rules for an org")
688
+ .requiredOption("--org <id>", "Org ID")
689
+ .option("--json", "Output JSON")
690
+ .action(async (options) => {
691
+ try {
692
+ await governanceRulesListCommand(options);
693
+ }
694
+ catch (error) {
695
+ console.error("Error:", error instanceof Error ? error.message : error);
696
+ process.exit(1);
697
+ }
698
+ });
699
+ govRulesCmd
700
+ .command("add")
701
+ .description("Add an escalation rule (predicate JSON + destination IDs)")
702
+ .requiredOption("--org <id>", "Org ID")
703
+ .requiredOption("--name <n>", "Rule name")
704
+ .requiredOption("--predicate <json>", "Predicate JSON (e.g., '{\"source\":\"sideband.fleet\",\"severity_min\":\"high\"}')")
705
+ .requiredOption("--destinations <ids>", "Comma-separated destination IDs")
706
+ .option("--json", "Output JSON")
707
+ .action(async (options) => {
708
+ try {
709
+ await governanceRulesAddCommand(options);
710
+ }
711
+ catch (error) {
712
+ console.error("Error:", error instanceof Error ? error.message : error);
713
+ process.exit(1);
714
+ }
715
+ });
716
+ govRulesCmd
717
+ .command("remove <rule_id>")
718
+ .description("Remove an escalation rule")
719
+ .requiredOption("--org <id>", "Org ID")
720
+ .option("--json", "Output JSON")
721
+ .action(async (id, options) => {
722
+ try {
723
+ await governanceRulesRemoveCommand(id, options);
724
+ }
725
+ catch (error) {
726
+ console.error("Error:", error instanceof Error ? error.message : error);
727
+ process.exit(1);
728
+ }
729
+ });
730
+ // ============================================================================
731
+ // Trust Posture (Piece 3 of T1-3.1, ADR-045)
732
+ //
733
+ // Postures are team-scoped policy input that drives the observer's sideband
734
+ // sweep. Cards remain agent-scoped runtime output; postures are a parallel
735
+ // artifact type. Mnemom ships three default postures (Standard / High-
736
+ // Compliance / Low-Latency) seeded via migration; orgs use them as-is or
737
+ // clone-and-customize.
738
+ // ============================================================================
739
+ const postureCmd = program
740
+ .command("posture")
741
+ .description("Manage Trust Postures (team-scoped oversight policy)");
742
+ postureCmd
743
+ .command("list")
744
+ .description("List visible postures (Mnemom defaults + org-owned)")
745
+ .option("--org <id>", "Filter to postures owned by this org")
746
+ .option("--no-include-platform", "Hide Mnemom-shipped platform defaults")
747
+ .option("--json", "Output JSON")
748
+ .action(async (options) => {
749
+ try {
750
+ await postureListCommand(options);
751
+ }
752
+ catch (error) {
753
+ console.error("Error:", error instanceof Error ? error.message : error);
754
+ process.exit(1);
755
+ }
756
+ });
757
+ postureCmd
758
+ .command("show <posture_id>")
759
+ .description("Show one posture's metadata + body summary")
760
+ .option("--json", "Output JSON")
761
+ .action(async (postureId, options) => {
762
+ try {
763
+ await postureShowCommand(postureId, options);
764
+ }
765
+ catch (error) {
766
+ console.error("Error:", error instanceof Error ? error.message : error);
767
+ process.exit(1);
768
+ }
769
+ });
770
+ postureCmd
771
+ .command("create")
772
+ .description("Create a new org-scope posture (body from a JSON file)")
773
+ .requiredOption("--org <id>", "Target org ID")
774
+ .requiredOption("--slug <slug>", "Kebab-case slug, unique within the org")
775
+ .requiredOption("--name <name>", "Display name")
776
+ .requiredOption("--from <file>", "Path to a JSON file with the posture body")
777
+ .option("--description <text>", "Optional description")
778
+ .option("--summary <text>", "Change summary for the initial revision")
779
+ .option("--json", "Output JSON")
780
+ .action(async (options) => {
781
+ try {
782
+ await postureCreateCommand(options);
783
+ }
784
+ catch (error) {
785
+ console.error("Error:", error instanceof Error ? error.message : error);
786
+ process.exit(1);
787
+ }
788
+ });
789
+ postureCmd
790
+ .command("update <posture_id>")
791
+ .description("Write a new revision (forward-only — old revisions stay queryable)")
792
+ .requiredOption("--from <file>", "Path to a JSON file with the new posture body")
793
+ .option("--summary <text>", "Change summary for this revision")
794
+ .option("--name <text>", "Update the posture's display name")
795
+ .option("--description <text>", "Update the description")
796
+ .option("--json", "Output JSON")
797
+ .action(async (postureId, options) => {
798
+ try {
799
+ await postureUpdateCommand(postureId, options);
800
+ }
801
+ catch (error) {
802
+ console.error("Error:", error instanceof Error ? error.message : error);
803
+ process.exit(1);
804
+ }
805
+ });
806
+ postureCmd
807
+ .command("clone <posture_id>")
808
+ .description("Clone any visible posture into an org as the customization starting point")
809
+ .requiredOption("--org <id>", "Target org ID")
810
+ .option("--slug <slug>", "Slug for the new posture (default: <source-slug>-clone)")
811
+ .option("--name <name>", "Display name for the new posture")
812
+ .option("--description <text>", "Description override")
813
+ .option("--json", "Output JSON")
814
+ .action(async (postureId, options) => {
815
+ try {
816
+ await postureCloneCommand(postureId, options);
817
+ }
818
+ catch (error) {
819
+ console.error("Error:", error instanceof Error ? error.message : error);
820
+ process.exit(1);
821
+ }
822
+ });
823
+ postureCmd
824
+ .command("revisions <posture_id>")
825
+ .description("List the posture's revision history (newest first)")
826
+ .option("--json", "Output JSON")
827
+ .action(async (postureId, options) => {
828
+ try {
829
+ await postureRevisionsCommand(postureId, options);
830
+ }
831
+ catch (error) {
832
+ console.error("Error:", error instanceof Error ? error.message : error);
833
+ process.exit(1);
834
+ }
835
+ });
836
+ postureCmd
837
+ .command("diff <posture_id>")
838
+ .description("Structural diff between two revisions of a posture")
839
+ .requiredOption("--from <N>", "Revision number to diff from")
840
+ .requiredOption("--to <M>", "Revision number to diff to")
841
+ .option("--json", "Output JSON")
842
+ .action(async (postureId, options) => {
843
+ try {
844
+ await postureDiffCommand(postureId, options);
845
+ }
846
+ catch (error) {
847
+ console.error("Error:", error instanceof Error ? error.message : error);
848
+ process.exit(1);
849
+ }
850
+ });
851
+ postureCmd
852
+ .command("assign <posture_id>")
853
+ .description("Assign a posture to a team (replaces any prior assignment for that team)")
854
+ .requiredOption("--team <team_id>", "Team UUID")
855
+ .option("--pin-revision <N>", "Pin to a specific revision number (default: float against current)")
856
+ .action(async (postureId, options) => {
857
+ try {
858
+ await postureAssignCommand(postureId, options);
859
+ }
860
+ catch (error) {
861
+ console.error("Error:", error instanceof Error ? error.message : error);
862
+ process.exit(1);
863
+ }
864
+ });
865
+ postureCmd
866
+ .command("unassign <posture_id>")
867
+ .description("Remove a posture's assignment from a team")
868
+ .requiredOption("--team <team_id>", "Team UUID")
869
+ .action(async (postureId, options) => {
870
+ try {
871
+ await postureUnassignCommand(postureId, options);
872
+ }
873
+ catch (error) {
874
+ console.error("Error:", error instanceof Error ? error.message : error);
875
+ process.exit(1);
876
+ }
877
+ });
878
+ postureCmd
879
+ .command("preview-compose <posture_id>")
880
+ .description("Preview the effective composed posture for a team if assigned")
881
+ .requiredOption("--team <team_id>", "Team UUID")
882
+ .option("--json", "Output JSON")
883
+ .action(async (postureId, options) => {
884
+ try {
885
+ await posturePreviewComposeCommand(postureId, options);
886
+ }
887
+ catch (error) {
888
+ console.error("Error:", error instanceof Error ? error.message : error);
889
+ process.exit(1);
890
+ }
891
+ });
892
+ postureCmd
893
+ .command("delete <posture_id>")
894
+ .description("Soft-delete an org-scope posture (refuses if currently assigned to any team)")
895
+ .action(async (postureId) => {
896
+ try {
897
+ await postureDeleteCommand(postureId);
898
+ }
899
+ catch (error) {
900
+ console.error("Error:", error instanceof Error ? error.message : error);
901
+ process.exit(1);
902
+ }
903
+ });
904
+ // ============================================================================
300
905
  // Auth
301
906
  // ============================================================================
302
907
  program
@@ -336,4 +941,98 @@ program
336
941
  process.exit(1);
337
942
  }
338
943
  });
944
+ // ── validate (Safe House Hardening T1-4) ─────────────────────────────────
945
+ const validateCmd = program
946
+ .command("validate")
947
+ .description("Validate platform state against quality gates");
948
+ validateCmd
949
+ .command("safe-house")
950
+ .description("Render Safe House harness state per lane (full/fast) — promotion-readiness signal")
951
+ .option("--against <env>", "Target environment ('staging' or 'production'; informational)", "staging")
952
+ .option("--max-age <duration>", "Reject runs older than this (e.g. 30m, 2h, 24h, 1d)", "24h")
953
+ .option("--lane <lane>", "Show only one lane ('full', 'fast', or 'both')", "both")
954
+ .option("--strict", "Exit non-zero if any selected lane is stale or red")
955
+ .option("--json", "Emit raw JSON state instead of rendered output")
956
+ .action(async (opts) => {
957
+ try {
958
+ const lane = opts.lane === "full" || opts.lane === "fast" || opts.lane === "both"
959
+ ? opts.lane
960
+ : "both";
961
+ await validateSafeHouseCommand({
962
+ against: opts.against,
963
+ maxAge: opts.maxAge,
964
+ lane,
965
+ strict: opts.strict,
966
+ json: opts.json,
967
+ });
968
+ }
969
+ catch (error) {
970
+ console.error("Error:", error instanceof Error ? error.message : error);
971
+ process.exit(1);
972
+ }
973
+ });
974
+ // ── api-key (ADR-049 capability-based scopes) ────────────────────────────
975
+ const apiKeyCmd = program
976
+ .command("api-key")
977
+ .description("Manage personal API keys (capability-based scopes — ADR-049)");
978
+ apiKeyCmd
979
+ .command("list")
980
+ .description("List your active personal API keys with their scope sets")
981
+ .option("--json", "Emit raw JSON instead of rendered output")
982
+ .action(async (options) => {
983
+ try {
984
+ await apiKeyListCommand({ json: options.json });
985
+ }
986
+ catch (error) {
987
+ console.error("Error:", error instanceof Error ? error.message : error);
988
+ process.exit(1);
989
+ }
990
+ });
991
+ apiKeyCmd
992
+ .command("create")
993
+ .description("Mint a new personal API key. Default scopes: gateway, api:read, api:write. " +
994
+ "Admin scopes (admin:org, admin:platform) are role-gated at mint time.")
995
+ .requiredOption("--name <name>", "Friendly name for the key (e.g. 'ci-prod')")
996
+ .option("--scopes <list>", "Comma-separated capability scopes. Valid: " +
997
+ "gateway, api:read, api:write, admin:org, admin:platform. " +
998
+ "Default: gateway,api:read,api:write")
999
+ .option("--json", "Emit raw JSON instead of rendered output")
1000
+ .action(async (options) => {
1001
+ try {
1002
+ await apiKeyCreateCommand({
1003
+ name: options.name,
1004
+ scopes: options.scopes,
1005
+ json: options.json,
1006
+ });
1007
+ }
1008
+ catch (error) {
1009
+ console.error("Error:", error instanceof Error ? error.message : error);
1010
+ process.exit(1);
1011
+ }
1012
+ });
1013
+ apiKeyCmd
1014
+ .command("rotate <key_id>")
1015
+ .description("Atomic mint-new + revoke-old. Returns the new cleartext once.")
1016
+ .option("--json", "Emit raw JSON instead of rendered output")
1017
+ .action(async (keyId, options) => {
1018
+ try {
1019
+ await apiKeyRotateCommand(keyId, { json: options.json });
1020
+ }
1021
+ catch (error) {
1022
+ console.error("Error:", error instanceof Error ? error.message : error);
1023
+ process.exit(1);
1024
+ }
1025
+ });
1026
+ apiKeyCmd
1027
+ .command("revoke <key_id>")
1028
+ .description("Soft-revoke an active key. Action is immediate and irreversible.")
1029
+ .action(async (keyId) => {
1030
+ try {
1031
+ await apiKeyRevokeCommand(keyId);
1032
+ }
1033
+ catch (error) {
1034
+ console.error("Error:", error instanceof Error ? error.message : error);
1035
+ process.exit(1);
1036
+ }
1037
+ });
339
1038
  program.parse();