llm-cli-gateway 2.3.0 → 2.5.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.
@@ -10,6 +10,34 @@ const PERMISSION_MODES = [
10
10
  "bypassPermissions",
11
11
  ];
12
12
  const EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
13
+ function scFlag(name, arity = "optional") {
14
+ return [
15
+ name,
16
+ {
17
+ arity,
18
+ description: `Subcommand-local ${name} option tracked for help-surface drift only`,
19
+ },
20
+ ];
21
+ }
22
+ function scFlags(flags, arityOverrides = {}) {
23
+ return Object.fromEntries(flags.map(flag => scFlag(flag, arityOverrides[flag] ?? "optional")));
24
+ }
25
+ function subcommand(commandPath, summary, risk, flags = [], options = {}) {
26
+ return {
27
+ commandPath,
28
+ helpArgs: [["--help"]],
29
+ flags: scFlags(flags, options.flagArities),
30
+ maxPositionals: options.maxPositionals ?? 0,
31
+ aliases: options.aliases ?? [],
32
+ children: options.children ?? {},
33
+ risk,
34
+ exposure: options.exposure ?? "tracked_only",
35
+ tier: options.tier ?? "catalog",
36
+ tokenCost: options.tokenCost ?? "small",
37
+ summary,
38
+ conformanceFixtures: options.fixtures ?? [],
39
+ };
40
+ }
13
41
  export const UPSTREAM_CLI_CONTRACTS = {
14
42
  claude: {
15
43
  cli: "claude",
@@ -23,6 +51,57 @@ export const UPSTREAM_CLI_CONTRACTS = {
23
51
  watchCategories: ["flags", "output-formats", "permission-modes", "session-resume", "models"],
24
52
  },
25
53
  helpArgs: [["--help"]],
54
+ subcommands: {
55
+ doctor: subcommand(["doctor"], "Run Claude Code diagnostic checks.", "read_only", [], {
56
+ tier: "diagnostic",
57
+ }),
58
+ mcp: subcommand(["mcp"], "Manage Claude MCP server configuration.", "writes_local_config"),
59
+ plugin: subcommand(["plugin"], "Manage Claude plugins.", "writes_local_config", [], {
60
+ aliases: ["plugins"],
61
+ }),
62
+ plugins: subcommand(["plugins"], "Alias for Claude plugin management.", "writes_local_config", [], {
63
+ aliases: ["plugin"],
64
+ }),
65
+ agents: subcommand(["agents"], "Inspect and manage Claude agent definitions.", "writes_local_config", [
66
+ "--add-dir",
67
+ "--agent",
68
+ "--allow-dangerously-skip-permissions",
69
+ "--cwd",
70
+ "--dangerously-skip-permissions",
71
+ "--effort",
72
+ "--json",
73
+ "--mcp-config",
74
+ "--model",
75
+ "--permission-mode",
76
+ "--plugin-dir",
77
+ "--setting-sources",
78
+ "--settings",
79
+ "--strict-mcp-config",
80
+ ], { tier: "inspect", flagArities: { "--json": "none", "--strict-mcp-config": "none" } }),
81
+ auth: subcommand(["auth"], "Manage Claude authentication state.", "auth", [], {
82
+ exposure: "not_exposed",
83
+ }),
84
+ project: subcommand(["project"], "Manage Claude project configuration.", "writes_local_config"),
85
+ update: subcommand(["update"], "Update the Claude Code binary.", "updates_binary", [], {
86
+ exposure: "not_exposed",
87
+ }),
88
+ upgrade: subcommand(["upgrade"], "Alias for updating the Claude Code binary.", "updates_binary", [], {
89
+ exposure: "not_exposed",
90
+ }),
91
+ install: subcommand(["install"], "Install Claude Code shell integrations.", "writes_local_config", ["--force"], {
92
+ exposure: "not_exposed",
93
+ flagArities: { "--force": "none" },
94
+ }),
95
+ "auto-mode": subcommand(["auto-mode"], "Configure Claude auto-mode behavior.", "writes_local_config"),
96
+ ultrareview: subcommand(["ultrareview"], "Run Claude ultrareview diagnostics.", "executes_agent", ["--json", "--timeout"], {
97
+ tier: "diagnostic",
98
+ tokenCost: "medium",
99
+ flagArities: { "--json": "none" },
100
+ }),
101
+ "setup-token": subcommand(["setup-token"], "Configure Claude setup token authentication.", "auth", [], {
102
+ exposure: "not_exposed",
103
+ }),
104
+ },
26
105
  maxPositionals: 0,
27
106
  mcpTools: ["claude_request", "claude_request_async"],
28
107
  mcpParameters: [
@@ -282,6 +361,216 @@ export const UPSTREAM_CLI_CONTRACTS = {
282
361
  ["exec", "--help"],
283
362
  ["exec", "resume", "--help"],
284
363
  ],
364
+ subcommands: {
365
+ exec: subcommand(["exec"], "Run Codex in non-interactive execution mode.", "executes_agent", [
366
+ "--add-dir",
367
+ "--cd",
368
+ "--color",
369
+ "--config",
370
+ "--dangerously-bypass-approvals-and-sandbox",
371
+ "--dangerously-bypass-hook-trust",
372
+ "--disable",
373
+ "--enable",
374
+ "--ephemeral",
375
+ "--ignore-rules",
376
+ "--ignore-user-config",
377
+ "--image",
378
+ "--json",
379
+ "--local-provider",
380
+ "--model",
381
+ "--oss",
382
+ "--output-last-message",
383
+ "--output-schema",
384
+ "--profile",
385
+ "--sandbox",
386
+ "--skip-git-repo-check",
387
+ "--strict-config",
388
+ "--version",
389
+ ], {
390
+ children: {
391
+ resume: subcommand(["exec", "resume"], "Resume Codex sessions from the interactive CLI.", "executes_agent", [
392
+ "--add-dir",
393
+ "--all",
394
+ "--cd",
395
+ "--config",
396
+ "--dangerously-bypass-approvals-and-sandbox",
397
+ "--dangerously-bypass-hook-trust",
398
+ "--disable",
399
+ "--enable",
400
+ "--image",
401
+ "--include-non-interactive",
402
+ "--last",
403
+ "--local-provider",
404
+ "--model",
405
+ "--no-alt-screen",
406
+ "--oss",
407
+ "--profile",
408
+ "--remote",
409
+ "--remote-auth-token-env",
410
+ "--sandbox",
411
+ "--strict-config",
412
+ ]),
413
+ review: subcommand(["exec", "review"], "Run Codex code review workflows.", "executes_agent", [
414
+ "--base",
415
+ "--commit",
416
+ "--config",
417
+ "--disable",
418
+ "--enable",
419
+ "--strict-config",
420
+ "--title",
421
+ "--uncommitted",
422
+ ]),
423
+ },
424
+ }),
425
+ review: subcommand(["review"], "Run Codex code review workflows.", "executes_agent", [
426
+ "--base",
427
+ "--commit",
428
+ "--config",
429
+ "--disable",
430
+ "--enable",
431
+ "--strict-config",
432
+ "--title",
433
+ "--uncommitted",
434
+ ]),
435
+ login: subcommand(["login"], "Authenticate Codex CLI.", "auth", [
436
+ "--config",
437
+ "--device-auth",
438
+ "--disable",
439
+ "--enable",
440
+ "--with-access-token",
441
+ "--with-api-key",
442
+ ], { exposure: "not_exposed" }),
443
+ logout: subcommand(["logout"], "Clear Codex authentication state.", "auth", ["--config", "--disable", "--enable"], { exposure: "not_exposed" }),
444
+ mcp: subcommand(["mcp"], "Manage Codex MCP configuration.", "writes_local_config", [
445
+ "--config",
446
+ "--disable",
447
+ "--enable",
448
+ ]),
449
+ plugin: subcommand(["plugin"], "Manage Codex plugins.", "writes_local_config", [
450
+ "--config",
451
+ "--disable",
452
+ "--enable",
453
+ ]),
454
+ "mcp-server": subcommand(["mcp-server"], "Start Codex MCP server mode.", "starts_server", ["--config", "--disable", "--enable", "--strict-config"], { exposure: "not_exposed" }),
455
+ "app-server": subcommand(["app-server"], "Start Codex app server mode.", "starts_server", [
456
+ "--analytics-default-enabled",
457
+ "--config",
458
+ "--disable",
459
+ "--enable",
460
+ "--listen",
461
+ "--stdio",
462
+ "--strict-config",
463
+ "--ws-audience",
464
+ "--ws-auth",
465
+ "--ws-issuer",
466
+ "--ws-max-clock-skew-seconds",
467
+ "--ws-shared-secret-file",
468
+ "--ws-token-file",
469
+ "--ws-token-sha256",
470
+ ], { exposure: "not_exposed" }),
471
+ "remote-control": subcommand(["remote-control"], "Inspect or manage Codex remote control state.", "network", ["--config", "--disable", "--enable", "--json"]),
472
+ completion: subcommand(["completion"], "Generate Codex shell completions.", "read_only", ["--config", "--disable", "--enable"], { tier: "inspect" }),
473
+ update: subcommand(["update"], "Update the Codex CLI binary.", "updates_binary", ["--config", "--disable", "--enable"], { exposure: "not_exposed" }),
474
+ doctor: subcommand(["doctor"], "Run Codex diagnostic checks.", "read_only", [
475
+ "--all",
476
+ "--ascii",
477
+ "--config",
478
+ "--disable",
479
+ "--enable",
480
+ "--json",
481
+ "--no-color",
482
+ "--summary",
483
+ ], { tier: "diagnostic" }),
484
+ sandbox: subcommand(["sandbox"], "Run or inspect Codex sandbox behavior.", "executes_agent", [
485
+ "--cd",
486
+ "--config",
487
+ "--disable",
488
+ "--enable",
489
+ "--include-managed-config",
490
+ "--permissions-profile",
491
+ "--profile",
492
+ ], { exposure: "not_exposed" }),
493
+ debug: subcommand(["debug"], "Run Codex debugging utilities.", "read_only", ["--config", "--disable", "--enable"], { tier: "diagnostic" }),
494
+ apply: subcommand(["apply"], "Apply a Codex patch to the workspace.", "destructive", ["--config", "--disable", "--enable"], { exposure: "not_exposed" }),
495
+ archive: subcommand(["archive"], "Archive Codex session state.", "writes_local_config", [
496
+ "--add-dir",
497
+ "--cd",
498
+ "--config",
499
+ "--dangerously-bypass-approvals-and-sandbox",
500
+ "--dangerously-bypass-hook-trust",
501
+ "--disable",
502
+ "--enable",
503
+ "--image",
504
+ "--local-provider",
505
+ "--model",
506
+ "--oss",
507
+ "--profile",
508
+ "--remote",
509
+ "--remote-auth-token-env",
510
+ "--sandbox",
511
+ "--strict-config",
512
+ ], { exposure: "not_exposed" }),
513
+ unarchive: subcommand(["unarchive"], "Restore archived Codex session state.", "writes_local_config", [
514
+ "--add-dir",
515
+ "--cd",
516
+ "--config",
517
+ "--dangerously-bypass-approvals-and-sandbox",
518
+ "--dangerously-bypass-hook-trust",
519
+ "--disable",
520
+ "--enable",
521
+ "--image",
522
+ "--local-provider",
523
+ "--model",
524
+ "--oss",
525
+ "--profile",
526
+ "--remote",
527
+ "--remote-auth-token-env",
528
+ "--sandbox",
529
+ "--strict-config",
530
+ ], { exposure: "not_exposed" }),
531
+ fork: subcommand(["fork"], "Fork a Codex session.", "executes_agent", [
532
+ "--add-dir",
533
+ "--all",
534
+ "--ask-for-approval",
535
+ "--cd",
536
+ "--config",
537
+ "--dangerously-bypass-approvals-and-sandbox",
538
+ "--dangerously-bypass-hook-trust",
539
+ "--disable",
540
+ "--enable",
541
+ "--image",
542
+ "--last",
543
+ "--local-provider",
544
+ "--model",
545
+ "--no-alt-screen",
546
+ "--oss",
547
+ "--profile",
548
+ "--remote",
549
+ "--remote-auth-token-env",
550
+ "--sandbox",
551
+ "--search",
552
+ "--strict-config",
553
+ "--version",
554
+ ]),
555
+ cloud: subcommand(["cloud"], "Inspect or manage Codex cloud features.", "network", [
556
+ "--config",
557
+ "--disable",
558
+ "--enable",
559
+ "--version",
560
+ ]),
561
+ "exec-server": subcommand(["exec-server"], "Start Codex exec server mode.", "starts_server", [
562
+ "--config",
563
+ "--disable",
564
+ "--enable",
565
+ "--environment-id",
566
+ "--listen",
567
+ "--name",
568
+ "--remote",
569
+ "--strict-config",
570
+ "--use-agent-identity-auth",
571
+ ], { exposure: "not_exposed" }),
572
+ features: subcommand(["features"], "Inspect or configure Codex feature flags.", "writes_local_config", ["--config", "--disable", "--enable"]),
573
+ },
285
574
  command: { requiredFirstArg: "exec", optionalSecondArg: "resume" },
286
575
  maxPositionals: 1,
287
576
  resumeMaxPositionals: 2,
@@ -504,6 +793,38 @@ export const UPSTREAM_CLI_CONTRACTS = {
504
793
  watchCategories: ["flags", "approval-modes", "output-formats", "session-resume"],
505
794
  },
506
795
  helpArgs: [["--help"]],
796
+ subcommands: {
797
+ mcp: subcommand(["mcp"], "Manage Gemini MCP server configuration.", "writes_local_config", ["--debug"], {
798
+ flagArities: { "--debug": "none" },
799
+ }),
800
+ extensions: subcommand(["extensions"], "Manage Gemini extensions.", "writes_local_config", ["--debug"], {
801
+ aliases: ["extension"],
802
+ flagArities: { "--debug": "none" },
803
+ }),
804
+ extension: subcommand(["extension"], "Alias for Gemini extension management.", "writes_local_config", ["--debug"], {
805
+ aliases: ["extensions"],
806
+ flagArities: { "--debug": "none" },
807
+ }),
808
+ skills: subcommand(["skills"], "Manage Gemini skills.", "writes_local_config", ["--debug"], {
809
+ aliases: ["skill"],
810
+ flagArities: { "--debug": "none" },
811
+ }),
812
+ skill: subcommand(["skill"], "Alias for Gemini skill management.", "writes_local_config", ["--debug"], {
813
+ aliases: ["skills"],
814
+ flagArities: { "--debug": "none" },
815
+ }),
816
+ hooks: subcommand(["hooks"], "Manage Gemini hooks.", "writes_local_config", ["--debug"], {
817
+ aliases: ["hook"],
818
+ flagArities: { "--debug": "none" },
819
+ }),
820
+ hook: subcommand(["hook"], "Alias for Gemini hook management.", "writes_local_config", ["--debug"], {
821
+ aliases: ["hooks"],
822
+ flagArities: { "--debug": "none" },
823
+ }),
824
+ gemma: subcommand(["gemma"], "Run Gemini Gemma local-model helper surfaces.", "executes_agent", ["--debug"], {
825
+ flagArities: { "--debug": "none" },
826
+ }),
827
+ },
507
828
  maxPositionals: 0,
508
829
  mcpTools: ["gemini_request", "gemini_request_async"],
509
830
  mcpParameters: [
@@ -625,6 +946,88 @@ export const UPSTREAM_CLI_CONTRACTS = {
625
946
  watchCategories: ["flags", "permission-modes", "session-resume", "sandbox", "output-formats"],
626
947
  },
627
948
  helpArgs: [["--help"]],
949
+ subcommands: {
950
+ agent: subcommand(["agent"], "Run Grok agent service helpers.", "executes_agent", [
951
+ "--agent-profile",
952
+ "--always-approve",
953
+ "--cli-chat-proxy-base-url",
954
+ "--grok-ws-origin",
955
+ "--grok-ws-url",
956
+ "--leader",
957
+ "--leader-socket",
958
+ "--model",
959
+ "--no-leader",
960
+ "--reasoning-effort",
961
+ "--reauth",
962
+ "--xai-api-base-url",
963
+ ], {
964
+ children: {
965
+ stdio: subcommand(["agent", "stdio"], "Run Grok agent stdio mode.", "starts_server", ["--leader-socket"], { exposure: "not_exposed" }),
966
+ headless: subcommand(["agent", "headless"], "Run Grok headless agent mode.", "executes_agent", ["--grok-ws-origin", "--grok-ws-url", "--leader-socket"]),
967
+ serve: subcommand(["agent", "serve"], "Start Grok agent server mode.", "starts_server", [
968
+ "--bind",
969
+ "--grok-ws-origin",
970
+ "--grok-ws-url",
971
+ "--leader-socket",
972
+ "--remote",
973
+ "--secret",
974
+ ], { exposure: "not_exposed" }),
975
+ leader: subcommand(["agent", "leader"], "Start Grok agent leader mode.", "starts_server", [
976
+ "--grok-ws-origin",
977
+ "--grok-ws-url",
978
+ "--leader-socket",
979
+ "--no-auto-update",
980
+ "--no-exit-on-disconnect",
981
+ ], { exposure: "not_exposed" }),
982
+ },
983
+ }),
984
+ completions: subcommand(["completions"], "Generate Grok shell completions.", "read_only", ["--leader-socket"], { tier: "inspect" }),
985
+ export: subcommand(["export"], "Export Grok session data.", "read_only", ["--clipboard", "--leader-socket"], { tier: "inspect" }),
986
+ import: subcommand(["import"], "Import Grok session data.", "writes_local_config", [
987
+ "--json",
988
+ "--leader-socket",
989
+ "--list",
990
+ ]),
991
+ inspect: subcommand(["inspect"], "Inspect Grok local state.", "read_only", ["--json", "--leader-socket"], { tier: "inspect" }),
992
+ leader: subcommand(["leader"], "Manage Grok leader process.", "starts_server", ["--leader-socket"], {
993
+ exposure: "not_exposed",
994
+ }),
995
+ login: subcommand(["login"], "Authenticate Grok CLI.", "auth", ["--device-auth", "--leader-socket", "--oauth"], { exposure: "not_exposed" }),
996
+ logout: subcommand(["logout"], "Clear Grok authentication state.", "auth", ["--leader-socket"], {
997
+ exposure: "not_exposed",
998
+ }),
999
+ mcp: subcommand(["mcp"], "Manage Grok MCP configuration.", "writes_local_config", [
1000
+ "--leader-socket",
1001
+ ]),
1002
+ memory: subcommand(["memory"], "Manage Grok memory state.", "writes_local_config", [
1003
+ "--leader-socket",
1004
+ ]),
1005
+ models: subcommand(["models"], "Inspect Grok model catalog.", "network", ["--leader-socket"], {
1006
+ tier: "diagnostic",
1007
+ }),
1008
+ plugin: subcommand(["plugin"], "Manage Grok plugins.", "writes_local_config", [
1009
+ "--leader-socket",
1010
+ ]),
1011
+ sessions: subcommand(["sessions"], "Inspect Grok sessions.", "read_only", ["--leader-socket"], {
1012
+ tier: "inspect",
1013
+ }),
1014
+ setup: subcommand(["setup"], "Configure Grok CLI local setup.", "writes_local_config", ["--leader-socket"], { exposure: "not_exposed" }),
1015
+ ssh: subcommand(["ssh"], "Manage Grok SSH integration.", "network", ["--leader-socket"]),
1016
+ trace: subcommand(["trace"], "Inspect Grok trace data.", "read_only", ["--json", "--leader-socket", "--local", "--output"], { tier: "diagnostic" }),
1017
+ update: subcommand(["update"], "Update the Grok CLI binary.", "updates_binary", [
1018
+ "--alpha",
1019
+ "--check",
1020
+ "--force-reinstall",
1021
+ "--json",
1022
+ "--leader-socket",
1023
+ "--stable",
1024
+ "--version",
1025
+ ], { exposure: "not_exposed" }),
1026
+ version: subcommand(["version"], "Print Grok version information.", "read_only", ["--json", "--leader-socket"], { tier: "diagnostic" }),
1027
+ worktree: subcommand(["worktree"], "Manage Grok worktree sessions.", "writes_local_config", [
1028
+ "--leader-socket",
1029
+ ]),
1030
+ },
628
1031
  maxPositionals: 0,
629
1032
  mcpTools: ["grok_request", "grok_request_async"],
630
1033
  mcpParameters: [
@@ -860,12 +1263,18 @@ export const UPSTREAM_CLI_CONTRACTS = {
860
1263
  "--prompt-json",
861
1264
  "[]",
862
1265
  "--restore-code",
1266
+ "--leader-socket",
1267
+ "/tmp/leader.sock",
863
1268
  "--single",
864
1269
  "single prompt",
865
1270
  "--todo-gate",
866
1271
  "--verbatim",
867
1272
  "--version",
868
1273
  "--worktree",
1274
+ "--compaction-mode",
1275
+ "summary",
1276
+ "--compaction-detail",
1277
+ "balanced",
869
1278
  ],
870
1279
  expect: "pass",
871
1280
  },
@@ -931,6 +1340,7 @@ export const UPSTREAM_CLI_CONTRACTS = {
931
1340
  watchCategories: ["flags", "agent-modes", "session-logging", "output-formats", "env-model"],
932
1341
  },
933
1342
  helpArgs: [["--help"]],
1343
+ subcommands: {},
934
1344
  maxPositionals: 0,
935
1345
  mcpTools: ["mistral_request", "mistral_request_async"],
936
1346
  mcpParameters: [
@@ -1232,6 +1642,210 @@ export function assertUpstreamCliArgs(cli, args) {
1232
1642
  throw new Error(`Upstream ${cli} CLI contract violation: ${details}`);
1233
1643
  }
1234
1644
  }
1645
+ function subcommandKey(commandPath) {
1646
+ return commandPath.join(" ");
1647
+ }
1648
+ function subcommandResourceUri(cli, commandPath) {
1649
+ return `provider-subcommands://${cli}/${commandPath.map(encodeURIComponent).join("/")}`;
1650
+ }
1651
+ export function flattenCliSubcommands(subcommands) {
1652
+ const flattened = [];
1653
+ const visit = (node) => {
1654
+ flattened.push(node);
1655
+ for (const child of Object.values(node.children ?? {}))
1656
+ visit(child);
1657
+ };
1658
+ for (const node of Object.values(subcommands ?? {}))
1659
+ visit(node);
1660
+ return flattened.sort((a, b) => subcommandKey(a.commandPath).localeCompare(subcommandKey(b.commandPath)));
1661
+ }
1662
+ export function getCliSubcommandContract(cli, commandPath) {
1663
+ const wanted = subcommandKey(commandPath);
1664
+ return (flattenCliSubcommands(UPSTREAM_CLI_CONTRACTS[cli].subcommands).find(contract => subcommandKey(contract.commandPath) === wanted) ?? null);
1665
+ }
1666
+ function serializeFlagContract(flag) {
1667
+ return {
1668
+ arity: flag.arity,
1669
+ values: flag.values ?? null,
1670
+ pattern: flag.pattern?.source ?? null,
1671
+ description: flag.description,
1672
+ hiddenFromHelp: flag.hiddenFromHelp ?? false,
1673
+ };
1674
+ }
1675
+ export function serializeCliSubcommandContract(cli, contract) {
1676
+ return {
1677
+ provider: cli,
1678
+ commandPath: contract.commandPath,
1679
+ helpArgs: contract.helpArgs,
1680
+ flags: Object.fromEntries(Object.entries(contract.flags).map(([name, flag]) => [name, serializeFlagContract(flag)])),
1681
+ maxPositionals: contract.maxPositionals,
1682
+ aliases: contract.aliases ?? [],
1683
+ children: Object.values(contract.children ?? {}).map(child => ({
1684
+ commandPath: child.commandPath,
1685
+ summary: child.summary,
1686
+ resourceUri: subcommandResourceUri(cli, child.commandPath),
1687
+ })),
1688
+ risk: contract.risk,
1689
+ exposure: contract.exposure,
1690
+ tier: contract.tier,
1691
+ tokenCost: contract.tokenCost,
1692
+ summary: contract.summary,
1693
+ conformanceFixtures: contract.conformanceFixtures.map(fixture => ({
1694
+ id: fixture.id,
1695
+ description: fixture.description,
1696
+ expect: fixture.expect,
1697
+ })),
1698
+ resourceUri: subcommandResourceUri(cli, contract.commandPath),
1699
+ };
1700
+ }
1701
+ export function listProviderSubcommands(options = {}) {
1702
+ const providers = options.provider
1703
+ ? [options.provider]
1704
+ : Object.keys(UPSTREAM_CLI_CONTRACTS);
1705
+ const prefix = options.commandPathPrefix ?? [];
1706
+ const rows = [];
1707
+ for (const provider of providers) {
1708
+ for (const contract of flattenCliSubcommands(UPSTREAM_CLI_CONTRACTS[provider].subcommands)) {
1709
+ if (options.tier && contract.tier !== options.tier)
1710
+ continue;
1711
+ if (options.risk && contract.risk !== options.risk)
1712
+ continue;
1713
+ if (options.exposure && contract.exposure !== options.exposure)
1714
+ continue;
1715
+ if (prefix.length > 0 &&
1716
+ !prefix.every((part, index) => contract.commandPath[index] === part)) {
1717
+ continue;
1718
+ }
1719
+ rows.push({
1720
+ provider,
1721
+ commandPath: contract.commandPath,
1722
+ aliases: contract.aliases ?? [],
1723
+ tier: contract.tier,
1724
+ risk: contract.risk,
1725
+ exposure: contract.exposure,
1726
+ tokenCost: contract.tokenCost,
1727
+ summary: contract.summary.length > 48
1728
+ ? `${contract.summary.slice(0, 45).trimEnd()}...`
1729
+ : contract.summary,
1730
+ driftStatus: "unknown",
1731
+ resourceUri: subcommandResourceUri(provider, contract.commandPath),
1732
+ });
1733
+ }
1734
+ }
1735
+ return rows.sort((a, b) => `${a.provider}:${subcommandKey(a.commandPath)}`.localeCompare(`${b.provider}:${subcommandKey(b.commandPath)}`));
1736
+ }
1737
+ export function buildProviderSubcommandsCompactCatalog(options = {}) {
1738
+ return {
1739
+ schemaVersion: "provider-subcommands-catalog.v1",
1740
+ columns: [
1741
+ "provider",
1742
+ "commandPath",
1743
+ "aliases",
1744
+ "tier",
1745
+ "risk",
1746
+ "exposure",
1747
+ "tokenCost",
1748
+ "summary",
1749
+ "driftStatus",
1750
+ "resourceUri",
1751
+ ],
1752
+ rows: listProviderSubcommands(options).map(row => [
1753
+ row.provider,
1754
+ row.commandPath.join(" "),
1755
+ row.aliases.join(","),
1756
+ row.tier,
1757
+ row.risk,
1758
+ row.exposure,
1759
+ row.tokenCost,
1760
+ row.summary,
1761
+ row.driftStatus,
1762
+ row.resourceUri,
1763
+ ]),
1764
+ };
1765
+ }
1766
+ export function validateUpstreamCliSubcommandArgs(cli, commandPath, args) {
1767
+ const contract = getCliSubcommandContract(cli, commandPath);
1768
+ const violations = [];
1769
+ if (!contract) {
1770
+ violations.push({
1771
+ cli,
1772
+ message: `${cli} subcommand "${subcommandKey(commandPath)}" is not declared in the upstream subcommand contract`,
1773
+ });
1774
+ return { ok: false, violations, commandPath };
1775
+ }
1776
+ const positionals = [];
1777
+ for (let i = 0; i < args.length; i++) {
1778
+ const arg = args[i];
1779
+ const flag = contract.flags[arg];
1780
+ if (!flag) {
1781
+ if (arg.startsWith("-")) {
1782
+ violations.push({
1783
+ cli,
1784
+ arg,
1785
+ index: i,
1786
+ message: `Unsupported ${cli} subcommand flag "${arg}" for ${subcommandKey(commandPath)}`,
1787
+ });
1788
+ }
1789
+ else {
1790
+ positionals.push(arg);
1791
+ }
1792
+ continue;
1793
+ }
1794
+ if (flag.arity === "none")
1795
+ continue;
1796
+ if (flag.arity === "one") {
1797
+ const value = args[i + 1];
1798
+ if (value === undefined) {
1799
+ violations.push({
1800
+ cli,
1801
+ arg,
1802
+ index: i,
1803
+ message: `${cli} subcommand flag "${arg}" requires one value`,
1804
+ });
1805
+ continue;
1806
+ }
1807
+ validateFlagValue(cli, arg, flag, value, i + 1, violations);
1808
+ i += 1;
1809
+ continue;
1810
+ }
1811
+ if (flag.arity === "optional") {
1812
+ const value = args[i + 1];
1813
+ if (value !== undefined && !value.startsWith("-")) {
1814
+ validateFlagValue(cli, arg, flag, value, i + 1, violations);
1815
+ i += 1;
1816
+ }
1817
+ continue;
1818
+ }
1819
+ let consumed = 0;
1820
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
1821
+ validateFlagValue(cli, arg, flag, args[i + 1], i + 1, violations);
1822
+ i += 1;
1823
+ consumed += 1;
1824
+ }
1825
+ if (consumed === 0) {
1826
+ violations.push({
1827
+ cli,
1828
+ arg,
1829
+ index: i,
1830
+ message: `${cli} subcommand flag "${arg}" requires at least one value`,
1831
+ });
1832
+ }
1833
+ }
1834
+ if (positionals.length > contract.maxPositionals) {
1835
+ violations.push({
1836
+ cli,
1837
+ message: `${cli} subcommand "${subcommandKey(commandPath)}" has ${positionals.length} positional values; upstream subcommand contract allows ${contract.maxPositionals}`,
1838
+ });
1839
+ }
1840
+ return {
1841
+ ok: violations.length === 0,
1842
+ violations,
1843
+ commandPath,
1844
+ risk: contract.risk,
1845
+ exposure: contract.exposure,
1846
+ tier: contract.tier,
1847
+ };
1848
+ }
1235
1849
  export function validateUpstreamCliEnv(cli, env) {
1236
1850
  if (!env || Object.keys(env).length === 0)
1237
1851
  return { ok: true, violations: [] };
@@ -1329,6 +1943,42 @@ export function computeFlagDrift(contract, helpText, discoveredFlags) {
1329
1943
  }
1330
1944
  return { missingFlags, extraFlags, acknowledgedExtraFlags, warnings };
1331
1945
  }
1946
+ export function computeSubcommandFlagDrift(contract, executable, helpText, discoveredFlags) {
1947
+ const warnings = [];
1948
+ const missingFlags = [];
1949
+ for (const [flag, spec] of Object.entries(contract.flags)) {
1950
+ const inHelp = helpText.includes(flag);
1951
+ if (spec.hiddenFromHelp) {
1952
+ if (inHelp) {
1953
+ warnings.push(`${subcommandKey(contract.commandPath)} ${flag} is marked hiddenFromHelp but now appears in ${executable} help output; remove the hiddenFromHelp marker from the subcommand contract`);
1954
+ }
1955
+ continue;
1956
+ }
1957
+ if (!inHelp)
1958
+ missingFlags.push(flag);
1959
+ }
1960
+ const contractFlagSet = new Set(Object.keys(contract.flags));
1961
+ const acknowledged = new Set(contract.acknowledgedUpstreamFlags ?? []);
1962
+ const extraFlags = [];
1963
+ const acknowledgedExtraFlags = [];
1964
+ for (const flag of discoveredFlags) {
1965
+ if (contractFlagSet.has(flag))
1966
+ continue;
1967
+ if (acknowledged.has(flag)) {
1968
+ acknowledgedExtraFlags.push(flag);
1969
+ }
1970
+ else {
1971
+ extraFlags.push(flag);
1972
+ }
1973
+ }
1974
+ const discoveredSet = new Set(discoveredFlags);
1975
+ for (const flag of acknowledged) {
1976
+ if (!discoveredSet.has(flag)) {
1977
+ warnings.push(`acknowledged upstream subcommand flag ${flag} no longer appears in ${executable} ${subcommandKey(contract.commandPath)} help output; remove it from acknowledgedUpstreamFlags`);
1978
+ }
1979
+ }
1980
+ return { missingFlags, extraFlags, acknowledgedExtraFlags, warnings };
1981
+ }
1332
1982
  export function probeInstalledCliContract(cli, timeoutMs = 5_000) {
1333
1983
  const contract = UPSTREAM_CLI_CONTRACTS[cli];
1334
1984
  const outputs = [];
@@ -1365,6 +2015,7 @@ export function probeInstalledCliContract(cli, timeoutMs = 5_000) {
1365
2015
  discoveredFlags: [],
1366
2016
  helpHash: undefined,
1367
2017
  versionHint: undefined,
2018
+ subcommands: {},
1368
2019
  probedAt: new Date().toISOString(),
1369
2020
  warnings: [result.error.message],
1370
2021
  };
@@ -1381,6 +2032,7 @@ export function probeInstalledCliContract(cli, timeoutMs = 5_000) {
1381
2032
  const versionMatch = helpText.match(/^\s*(?:[A-Za-z][\w .-]+)?v?\d+\.\d+\S*/m);
1382
2033
  const versionHint = versionMatch ? versionMatch[0].trim().slice(0, 80) : undefined;
1383
2034
  const helpHash = createHash("sha256").update(helpText).digest("hex");
2035
+ const subcommands = probeInstalledCliSubcommands(cli, timeoutMs);
1384
2036
  return {
1385
2037
  cli,
1386
2038
  executable: contract.executable,
@@ -1394,10 +2046,69 @@ export function probeInstalledCliContract(cli, timeoutMs = 5_000) {
1394
2046
  discoveredFlags,
1395
2047
  helpHash,
1396
2048
  versionHint,
2049
+ subcommands,
1397
2050
  probedAt: new Date().toISOString(),
1398
2051
  warnings,
1399
2052
  };
1400
2053
  }
2054
+ function probeInstalledCliSubcommands(cli, timeoutMs) {
2055
+ const contract = UPSTREAM_CLI_CONTRACTS[cli];
2056
+ const probes = {};
2057
+ for (const sub of flattenCliSubcommands(contract.subcommands)) {
2058
+ const outputs = [];
2059
+ const warnings = [];
2060
+ let available = true;
2061
+ const checkedHelpCommands = sub.helpArgs.map(helpArgs => [...sub.commandPath, ...helpArgs]);
2062
+ for (const helpArgs of sub.helpArgs) {
2063
+ const args = [...sub.commandPath, ...helpArgs];
2064
+ const extendedPath = getExtendedPath();
2065
+ const env = envWithExtendedPath(process.env, extendedPath);
2066
+ const resolved = resolveCommandForSpawn(contract.executable, args, {
2067
+ envPath: extendedPath,
2068
+ });
2069
+ const result = spawnSync(resolved.command, resolved.args, {
2070
+ encoding: "utf8",
2071
+ timeout: timeoutMs,
2072
+ maxBuffer: 1024 * 1024,
2073
+ env,
2074
+ windowsHide: true,
2075
+ windowsVerbatimArguments: resolved.windowsVerbatimArguments,
2076
+ });
2077
+ if (result.error) {
2078
+ available = false;
2079
+ warnings.push(result.error.message);
2080
+ break;
2081
+ }
2082
+ outputs.push(`${result.stdout ?? ""}\n${result.stderr ?? ""}`);
2083
+ if (result.status !== 0) {
2084
+ warnings.push(`${contract.executable} ${args.join(" ")} exited with status ${result.status}`);
2085
+ }
2086
+ }
2087
+ const helpText = outputs.join("\n");
2088
+ const discoveredFlags = available ? extractDiscoveredFlags(helpText) : [];
2089
+ const drift = available
2090
+ ? computeSubcommandFlagDrift(sub, contract.executable, helpText, discoveredFlags)
2091
+ : { missingFlags: [], extraFlags: [], acknowledgedExtraFlags: [], warnings: [] };
2092
+ warnings.push(...drift.warnings);
2093
+ probes[subcommandKey(sub.commandPath)] = {
2094
+ commandPath: sub.commandPath,
2095
+ checkedHelpCommands,
2096
+ available,
2097
+ missingFlags: drift.missingFlags,
2098
+ extraFlags: drift.extraFlags,
2099
+ acknowledgedExtraFlags: drift.acknowledgedExtraFlags,
2100
+ discoveredFlags,
2101
+ helpHash: available ? createHash("sha256").update(helpText).digest("hex") : undefined,
2102
+ probedAt: new Date().toISOString(),
2103
+ warnings,
2104
+ risk: sub.risk,
2105
+ exposure: sub.exposure,
2106
+ tier: sub.tier,
2107
+ summary: sub.summary,
2108
+ };
2109
+ }
2110
+ return probes;
2111
+ }
1401
2112
  export function buildUpstreamContractReport(options = {}) {
1402
2113
  const selected = options.cli ? [options.cli] : Object.keys(UPSTREAM_CLI_CONTRACTS);
1403
2114
  const contracts = Object.fromEntries(selected.map(cli => {
@@ -1423,13 +2134,10 @@ export function buildUpstreamContractReport(options = {}) {
1423
2134
  mcpParameters: contract.mcpParameters,
1424
2135
  flags: Object.fromEntries(Object.entries(contract.flags).map(([name, flag]) => [
1425
2136
  name,
1426
- {
1427
- arity: flag.arity,
1428
- values: flag.values ?? null,
1429
- pattern: flag.pattern?.source ?? null,
1430
- description: flag.description,
1431
- },
2137
+ serializeFlagContract(flag),
1432
2138
  ])),
2139
+ subcommandCount: flattenCliSubcommands(contract.subcommands).length,
2140
+ subcommandsCatalog: buildProviderSubcommandsCompactCatalog({ provider: cli }),
1433
2141
  env: Object.fromEntries(Object.entries(contract.env ?? {}).map(([name, envContract]) => [
1434
2142
  name,
1435
2143
  {