codedeck 0.3.0 → 0.4.1

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 (80) hide show
  1. package/dist/cli/charts/bar-chart.js +104 -0
  2. package/dist/cli/charts/bar-chart.js.map +1 -0
  3. package/dist/cli/charts/sparkline.js +18 -0
  4. package/dist/cli/charts/sparkline.js.map +1 -0
  5. package/dist/cli/charts/stacked-bar.js +59 -0
  6. package/dist/cli/charts/stacked-bar.js.map +1 -0
  7. package/dist/cli/commands/done.js +35 -0
  8. package/dist/cli/commands/done.js.map +1 -0
  9. package/dist/cli/commands/models.js +4 -4
  10. package/dist/cli/commands/models.js.map +1 -1
  11. package/dist/cli/commands/open.js +218 -57
  12. package/dist/cli/commands/open.js.map +1 -1
  13. package/dist/cli/commands/ps.js +1 -35
  14. package/dist/cli/commands/ps.js.map +1 -1
  15. package/dist/cli/commands/run.js +4 -3
  16. package/dist/cli/commands/run.js.map +1 -1
  17. package/dist/cli/commands/send.js +2 -0
  18. package/dist/cli/commands/send.js.map +1 -1
  19. package/dist/cli/commands/setup.js +604 -13
  20. package/dist/cli/commands/setup.js.map +1 -1
  21. package/dist/cli/commands/usage.js +171 -16
  22. package/dist/cli/commands/usage.js.map +1 -1
  23. package/dist/cli/commands/web.js +307 -0
  24. package/dist/cli/commands/web.js.map +1 -0
  25. package/dist/cli/index.js +5 -0
  26. package/dist/cli/index.js.map +1 -1
  27. package/dist/cli/ui.js +34 -0
  28. package/dist/cli/ui.js.map +1 -1
  29. package/dist/cli/usage/dashboard.js +330 -0
  30. package/dist/cli/usage/dashboard.js.map +1 -0
  31. package/dist/cli/usage/snapshot.js +112 -0
  32. package/dist/cli/usage/snapshot.js.map +1 -0
  33. package/dist/config/config.js +287 -5
  34. package/dist/config/config.js.map +1 -1
  35. package/dist/config/paths.js +20 -9
  36. package/dist/config/paths.js.map +1 -1
  37. package/dist/core/autocompact.js +77 -0
  38. package/dist/core/autocompact.js.map +1 -0
  39. package/dist/core/driver.js.map +1 -1
  40. package/dist/core/events.js.map +1 -1
  41. package/dist/core/models.js +166 -3
  42. package/dist/core/models.js.map +1 -1
  43. package/dist/core/pricing.js +18 -0
  44. package/dist/core/pricing.js.map +1 -1
  45. package/dist/core/session.js +14 -1
  46. package/dist/core/session.js.map +1 -1
  47. package/dist/daemon/daemon.js +483 -96
  48. package/dist/daemon/daemon.js.map +1 -1
  49. package/dist/drivers/antigravity/driver.js +172 -0
  50. package/dist/drivers/antigravity/driver.js.map +1 -0
  51. package/dist/drivers/antigravity/parser.js +168 -0
  52. package/dist/drivers/antigravity/parser.js.map +1 -0
  53. package/dist/drivers/claude/driver.js +13 -3
  54. package/dist/drivers/claude/driver.js.map +1 -1
  55. package/dist/drivers/registry.js +2 -0
  56. package/dist/drivers/registry.js.map +1 -1
  57. package/dist/drivers/session-driver.js +6 -4
  58. package/dist/drivers/session-driver.js.map +1 -1
  59. package/dist/drivers/session-runtime.js +1 -1
  60. package/dist/drivers/session-runtime.js.map +1 -1
  61. package/dist/open/contract.js +16 -4
  62. package/dist/open/contract.js.map +1 -1
  63. package/dist/open/injection.js +1 -0
  64. package/dist/open/injection.js.map +1 -1
  65. package/dist/open/launchers/claude.js +4 -1
  66. package/dist/open/launchers/claude.js.map +1 -1
  67. package/dist/open/launchers/opencode.js +37 -2
  68. package/dist/open/launchers/opencode.js.map +1 -1
  69. package/dist/open/runtime.js +43 -8
  70. package/dist/open/runtime.js.map +1 -1
  71. package/dist/plugin/agents/orchestrator-edit.md +1 -3
  72. package/dist/plugin/agents/orchestrator-read.md +1 -3
  73. package/dist/plugin/agents/orchestrator.md +2 -4
  74. package/dist/store/database.js +40 -15
  75. package/dist/store/database.js.map +1 -1
  76. package/dist/store/sessions.js +225 -3
  77. package/dist/store/sessions.js.map +1 -1
  78. package/dist/web/canvas-page.js +1069 -0
  79. package/dist/web/canvas-page.js.map +1 -0
  80. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"send.js","sourceRoot":"","sources":["../../../src/cli/commands/send.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,qFAAqF,CAAC;SAClG,QAAQ,CAAC,MAAM,EAAE,wCAAwC,CAAC;SAC1D,QAAQ,CAAC,WAAW,EAAE,sCAAsC,CAAC;SAC7D,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC;SAC/B,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,OAAe,EAAE,IAAS,EAAE,EAAE;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC;YAAC,MAAM,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACpD,IAAI,MAAW,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;;YACvD,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../../src/cli/commands/send.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,qFAAqF,CAAC;SAClG,QAAQ,CAAC,MAAM,EAAE,wCAAwC,CAAC;SAC1D,QAAQ,CAAC,WAAW,EAAE,sCAAsC,CAAC;SAC7D,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC;SAC/B,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,OAAe,EAAE,IAAS,EAAE,EAAE;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC;YAAC,MAAM,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACpD,IAAI,MAAW,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;aACvD,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,qCAAqC,CAAC,CAAC;;YACxG,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1,11 +1,14 @@
1
- import { getCachedOrDiscoverModels, } from "../../core/models.js";
1
+ import { AUTOCOMPACT_DEFAULT_CAP, AUTOCOMPACT_DEFAULT_PERCENT, } from "../../core/autocompact.js";
2
+ import { getBatchModels, getCachedOrDiscoverModels, } from "../../core/models.js";
3
+ import { isAgentId } from "../../core/session.js";
2
4
  import { itemKey } from "../picker-state.js";
3
5
  import { runScreens } from "../picker.js";
4
6
  import { colors, readDimensions } from "../ui.js";
5
7
  import { getCliName } from "../cli-name.js";
6
8
  import { getRegistry } from "../../drivers/registry.js";
7
9
  import { ROLES } from "../../core/roles.js";
8
- import { BALANCED_PRESET, DISPATCHER_PRESET, EXPLORER_PRESET, ORCHESTRATOR_PRESETS, isOrchestratorMode, loadConfig, orchestratorModeLabel, resolveOrchestratorMode, saveConfig, } from "../../config/config.js";
10
+ import { getPaths } from "../../config/paths.js";
11
+ import { BALANCED_PRESET, DISPATCHER_PRESET, EXPLORER_PRESET, ORCHESTRATOR_PRESETS, createSetupConfigStore, DEFAULT_CONFIG, isOrchestratorMode, loadConfig, orchestratorModeLabel, resolveOrchestratorMode, saveConfig, serializeConfig, } from "../../config/config.js";
9
12
  const ORCHESTRATOR_SCREEN_ROLE = "orchestrator-mode";
10
13
  const ORCHESTRATOR_PICKER_GROUP = "orchestrator";
11
14
  const PARALLELISM_NONE = "none";
@@ -13,6 +16,11 @@ const SANDBOX_SCREEN_ROLE = "sandbox";
13
16
  const SANDBOX_PICKER_GROUP = "sandbox";
14
17
  const SANDBOX_OFF = "workspace-write";
15
18
  const SANDBOX_ON = "danger-full-access";
19
+ const AUTOCOMPACT_SCREEN_ROLE = "autocompact";
20
+ const AUTOCOMPACT_PICKER_GROUP = "autocompact";
21
+ const AUTOCOMPACT_OFF = "off";
22
+ const AUTOCOMPACT_ON = "on";
23
+ const AUTOCOMPACT_DESCRIPTION = `Native autocompact applies to Claude and OpenCode sessions. Claude's default window is min(${AUTOCOMPACT_DEFAULT_CAP / 1000}k tokens, ${AUTOCOMPACT_DEFAULT_PERCENT * 100}% of the context window); OpenCode uses its internal context trigger.`;
16
24
  const ORCHESTRATOR_PARALLELISM_NOTE_LINES = [
17
25
  "parallelism is advisory. Version 1 puts the requested cap in the",
18
26
  "orchestrator prompt for review verification and does not enforce the",
@@ -144,6 +152,35 @@ export function buildSandboxScreen(config = {}, index = 0, total = 1) {
144
152
  next: () => [],
145
153
  };
146
154
  }
155
+ export function buildAutocompactScreen(config = {}, index = 0, total = 1) {
156
+ // A hand-tuned block without `enabled` still resolves as active in the
157
+ // Claude driver (only `enabled: false` disables), so show it as ON rather
158
+ // than let an accepted OFF silently switch off a working setup.
159
+ const selected = config.autocompact === undefined || config.autocompact.enabled === false
160
+ ? AUTOCOMPACT_OFF
161
+ : AUTOCOMPACT_ON;
162
+ const values = selected === AUTOCOMPACT_ON
163
+ ? [AUTOCOMPACT_ON, AUTOCOMPACT_OFF]
164
+ : [AUTOCOMPACT_OFF, AUTOCOMPACT_ON];
165
+ const items = values.map((value) => ({
166
+ id: value,
167
+ label: value.toUpperCase(),
168
+ group: AUTOCOMPACT_PICKER_GROUP,
169
+ harness: AUTOCOMPACT_PICKER_GROUP,
170
+ ...(value === selected ? { note: "atual" } : {}),
171
+ }));
172
+ return {
173
+ role: AUTOCOMPACT_SCREEN_ROLE,
174
+ title: "Claude native autocompact",
175
+ counter: `configuração ${index + 1} de ${total}`,
176
+ description: [AUTOCOMPACT_DESCRIPTION],
177
+ items,
178
+ pinned: true,
179
+ known: new Set(items.map((item) => itemKey(item.harness, item.id))),
180
+ harnesses: new Set([AUTOCOMPACT_PICKER_GROUP]),
181
+ next: () => [],
182
+ };
183
+ }
147
184
  function modeAfterParameter(mode, result) {
148
185
  const parameters = collectOrchestratorParameters([result], mode);
149
186
  return orchestratorConfigFromSelection("custom", parameters) ?? mode;
@@ -431,7 +468,10 @@ export async function runModelSetupWizard(options = {}) {
431
468
  ...roleScreens,
432
469
  buildOrchestratorScreen(config, roleScreens.length, roleScreens.length + 1),
433
470
  ];
434
- const results = await runScreens([...screens, buildSandboxScreen(config, screens.length, screens.length + 1)], { input, output, onResize: watchResize }, paint);
471
+ const total = screens.length + 2;
472
+ const sandbox = buildSandboxScreen(config, screens.length, total);
473
+ const autocompact = buildAutocompactScreen(config, screens.length + 1, total);
474
+ const results = await runScreens([...screens, sandbox, autocompact], { input, output, onResize: watchResize }, paint);
435
475
  if (results.some((result) => result.kind === "aborted")) {
436
476
  console.error("Warning: Model setup was interrupted; nothing was saved.");
437
477
  return config;
@@ -473,12 +513,25 @@ export async function runModelSetupWizard(options = {}) {
473
513
  updatedConfig.defaultSandbox = sandboxSelection.id;
474
514
  }
475
515
  }
516
+ const autocompactSelection = answeredResults.find((result) => result.role === AUTOCOMPACT_SCREEN_ROLE);
517
+ if (autocompactSelection?.kind === "picked") {
518
+ if (autocompactSelection.id === AUTOCOMPACT_ON) {
519
+ updatedConfig.autocompact = { ...config.autocompact, enabled: true };
520
+ }
521
+ else if (autocompactSelection.id === AUTOCOMPACT_OFF && config.autocompact !== undefined) {
522
+ updatedConfig.autocompact = { ...config.autocompact, enabled: false };
523
+ }
524
+ }
525
+ let saved = false;
476
526
  try {
477
527
  (options.save ?? saveConfig)(updatedConfig);
528
+ saved = true;
478
529
  }
479
530
  catch (error) {
480
531
  console.error(`Warning: Could not save config: ${error instanceof Error ? error.message : String(error)}`);
481
532
  }
533
+ if (!saved)
534
+ return updatedConfig;
482
535
  const summary = roleScreens
483
536
  .map((screen) => {
484
537
  const binding = agents[screen.role];
@@ -489,20 +542,558 @@ export async function runModelSetupWizard(options = {}) {
489
542
  output.write(`\n saved: ${summary}${modeSummary}\n\n`);
490
543
  return updatedConfig;
491
544
  }
492
- export function registerSetupCommand(program) {
545
+ export class SetupUsageError extends Error {
546
+ constructor(message) {
547
+ super(message);
548
+ this.name = "SetupUsageError";
549
+ }
550
+ }
551
+ function invalidBindMessage(value) {
552
+ return `Invalid --bind "${value}": expected role=harness:model (role: general|orchestrator|reviewer|auditor; harness: claude|codex|opencode|omp; model: non-empty and without whitespace, control characters or '=')`;
553
+ }
554
+ export function parseBind(value) {
555
+ const equals = value.indexOf("=");
556
+ const roleValue = equals < 0 ? "" : value.slice(0, equals);
557
+ const right = equals < 0 ? "" : value.slice(equals + 1);
558
+ const colon = right.indexOf(":");
559
+ const harnessValue = colon < 0 ? "" : right.slice(0, colon);
560
+ const model = colon < 0 ? "" : right.slice(colon + 1);
561
+ const role = ROLES.includes(roleValue) ? roleValue : undefined;
562
+ const harness = isAgentId(harnessValue) ? harnessValue : undefined;
563
+ const modelPattern = /^[^\p{White_Space}\p{Cc}\p{Cf}=]+$/u;
564
+ if (!role || !harness || !modelPattern.test(model))
565
+ throw new SetupUsageError(invalidBindMessage(value));
566
+ return { role, binding: { harness, model } };
567
+ }
568
+ export const parseSetupBind = parseBind;
569
+ function parseError(message, json) {
570
+ return { ok: false, message, json };
571
+ }
572
+ function isSetupFlag(arg) {
573
+ return arg.startsWith("-");
574
+ }
575
+ export function parseSetupArgs(args) {
576
+ const json = args.some((arg) => arg === "--json" || arg.startsWith("--json="));
577
+ const binds = [];
578
+ let refresh = false;
579
+ let nonInteractive = false;
580
+ let dryRun = false;
581
+ for (let index = 0; index < args.length; index += 1) {
582
+ const arg = args[index];
583
+ if (arg === "--refresh") {
584
+ refresh = true;
585
+ continue;
586
+ }
587
+ if (arg === "--non-interactive") {
588
+ nonInteractive = true;
589
+ continue;
590
+ }
591
+ if (arg === "--dry-run") {
592
+ dryRun = true;
593
+ continue;
594
+ }
595
+ if (arg === "--json") {
596
+ continue;
597
+ }
598
+ if (arg === "--bind") {
599
+ const value = args[index + 1];
600
+ if (value === undefined || isSetupFlag(value)) {
601
+ return parseError('Option "--bind" expects role=harness:model.', json);
602
+ }
603
+ index += 1;
604
+ try {
605
+ binds.push(parseBind(value));
606
+ }
607
+ catch (error) {
608
+ return parseError(error instanceof Error ? error.message : String(error), json);
609
+ }
610
+ continue;
611
+ }
612
+ if (arg.startsWith("--bind=")) {
613
+ try {
614
+ binds.push(parseBind(arg.slice("--bind=".length)));
615
+ }
616
+ catch (error) {
617
+ return parseError(error instanceof Error ? error.message : String(error), json);
618
+ }
619
+ continue;
620
+ }
621
+ if (arg.startsWith("-"))
622
+ return parseError(`Unknown option "${arg}".`, json);
623
+ return parseError(`Unexpected argument "${arg}".`, json);
624
+ }
625
+ return {
626
+ ok: true,
627
+ options: {
628
+ refresh,
629
+ nonInteractive,
630
+ json,
631
+ dryRun,
632
+ binds,
633
+ batch: nonInteractive || json || dryRun || binds.length > 0,
634
+ },
635
+ };
636
+ }
637
+ function writeLine(stream, message) {
638
+ if (stream) {
639
+ stream.write(`${message}\n`);
640
+ }
641
+ else {
642
+ console.error(message);
643
+ }
644
+ }
645
+ function configValidation(read) {
646
+ return {
647
+ status: read.status,
648
+ source: read.source,
649
+ path: read.path,
650
+ message: read.status === "invalid" ? read.message : null,
651
+ };
652
+ }
653
+ function notNeededCatalog() {
654
+ return { status: "not-needed", source: "none", ageMs: null, message: null };
655
+ }
656
+ function emptyEnvelope(message, code, path) {
657
+ return {
658
+ proposta: null,
659
+ validacoes: {
660
+ config: { status: "not-run", source: "none", path, message: "not-run" },
661
+ catalogo: { status: "not-needed", source: "none", ageMs: null, message: "not-run" },
662
+ bindings: [],
663
+ },
664
+ mudancas: [],
665
+ resultado: { status: "error", code, saved: false, message },
666
+ };
667
+ }
668
+ function jsonObject(value) {
669
+ return typeof value === "object" && value !== null && !Array.isArray(value);
670
+ }
671
+ function jsonValue(value) {
672
+ if (value === undefined)
673
+ return null;
674
+ if (Array.isArray(value))
675
+ return value.map(jsonValue);
676
+ if (jsonObject(value)) {
677
+ return Object.fromEntries(Object.keys(value).sort((left, right) => left.localeCompare(right)).map((key) => [key, jsonValue(value[key])]));
678
+ }
679
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
680
+ return value;
681
+ }
682
+ return null;
683
+ }
684
+ function jsonEqual(left, right) {
685
+ return JSON.stringify(jsonValue(left)) === JSON.stringify(jsonValue(right));
686
+ }
687
+ function pointerPart(value) {
688
+ return value.replaceAll("~", "~0").replaceAll("/", "~1");
689
+ }
690
+ function diffAt(output, pathValue, beforePresent, before, afterPresent, after, expandObjectChildren = false) {
691
+ const beforeObject = beforePresent && jsonObject(before) ? before : undefined;
692
+ const afterObject = afterPresent && jsonObject(after) ? after : undefined;
693
+ if (beforeObject !== undefined && afterObject !== undefined) {
694
+ const keys = [...new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)])].sort((left, right) => left.localeCompare(right));
695
+ if (keys.length === 0)
696
+ return;
697
+ for (const key of keys) {
698
+ diffAt(output, `${pathValue}/${pointerPart(key)}`, Object.hasOwn(beforeObject, key), beforeObject[key], Object.hasOwn(afterObject, key), afterObject[key]);
699
+ }
700
+ return;
701
+ }
702
+ if (!beforePresent && afterObject !== undefined) {
703
+ if (expandObjectChildren) {
704
+ const keys = Object.keys(afterObject).sort((left, right) => left.localeCompare(right));
705
+ if (keys.length === 0) {
706
+ output.push({
707
+ path: pathValue,
708
+ beforePresent: false,
709
+ before: null,
710
+ afterPresent: true,
711
+ after: jsonValue(afterObject),
712
+ });
713
+ return;
714
+ }
715
+ for (const key of keys) {
716
+ const value = afterObject[key];
717
+ const childPath = `${pathValue}/${pointerPart(key)}`;
718
+ if (jsonObject(value)) {
719
+ output.push({
720
+ path: childPath,
721
+ beforePresent: false,
722
+ before: null,
723
+ afterPresent: true,
724
+ after: jsonValue(value),
725
+ });
726
+ }
727
+ else {
728
+ diffAt(output, childPath, false, undefined, true, value);
729
+ }
730
+ }
731
+ return;
732
+ }
733
+ output.push({
734
+ path: pathValue,
735
+ beforePresent: false,
736
+ before: null,
737
+ afterPresent: true,
738
+ after: jsonValue(afterObject),
739
+ });
740
+ return;
741
+ }
742
+ if (beforeObject !== undefined && !afterPresent) {
743
+ if (expandObjectChildren) {
744
+ const keys = Object.keys(beforeObject).sort((left, right) => left.localeCompare(right));
745
+ if (keys.length === 0) {
746
+ output.push({
747
+ path: pathValue,
748
+ beforePresent: true,
749
+ before: jsonValue(beforeObject),
750
+ afterPresent: false,
751
+ after: null,
752
+ });
753
+ return;
754
+ }
755
+ for (const key of keys) {
756
+ const value = beforeObject[key];
757
+ const childPath = `${pathValue}/${pointerPart(key)}`;
758
+ if (jsonObject(value)) {
759
+ output.push({
760
+ path: childPath,
761
+ beforePresent: true,
762
+ before: jsonValue(value),
763
+ afterPresent: false,
764
+ after: null,
765
+ });
766
+ }
767
+ else {
768
+ diffAt(output, childPath, true, value, false, undefined);
769
+ }
770
+ }
771
+ return;
772
+ }
773
+ output.push({
774
+ path: pathValue,
775
+ beforePresent: true,
776
+ before: jsonValue(beforeObject),
777
+ afterPresent: false,
778
+ after: null,
779
+ });
780
+ return;
781
+ }
782
+ if (beforePresent === afterPresent && (!beforePresent || jsonEqual(before, after)))
783
+ return;
784
+ output.push({
785
+ path: pathValue,
786
+ beforePresent,
787
+ before: jsonValue(before),
788
+ afterPresent,
789
+ after: jsonValue(after),
790
+ });
791
+ }
792
+ export function diffConfig(before, after) {
793
+ const output = [];
794
+ const beforeObject = jsonObject(before) ? before : {};
795
+ const afterObject = jsonObject(after) ? after : {};
796
+ const keys = [...new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)])].sort((left, right) => left.localeCompare(right));
797
+ for (const key of keys) {
798
+ diffAt(output, `/${pointerPart(key)}`, Object.hasOwn(beforeObject, key), beforeObject[key], Object.hasOwn(afterObject, key), afterObject[key], true);
799
+ }
800
+ return output.sort((left, right) => left.path.localeCompare(right.path));
801
+ }
802
+ function catalogContains(catalog, model) {
803
+ return catalog.providers.some((provider) => provider.models.some((candidate) => candidate.id === model || (candidate.aliases !== undefined && candidate.aliases.some((alias) => alias === model))));
804
+ }
805
+ function validateBindings(bindings, catalog) {
806
+ const byAgent = new Map(catalog.models.map((item) => [item.agent, item]));
807
+ const entries = bindings.map(({ role, binding }) => {
808
+ const found = byAgent.get(binding.harness);
809
+ if (found?.available === false) {
810
+ return {
811
+ role,
812
+ harness: binding.harness,
813
+ model: binding.model,
814
+ status: "harness-unavailable",
815
+ message: `Cannot apply binding for role "${role}": harness "${binding.harness}" is unavailable.`,
816
+ };
817
+ }
818
+ if (catalog.status === "unavailable" || found === undefined) {
819
+ return {
820
+ role,
821
+ harness: binding.harness,
822
+ model: binding.model,
823
+ status: "unverified",
824
+ message: `Cannot validate model "${binding.model}" for harness "${binding.harness}": catalog unavailable.`,
825
+ };
826
+ }
827
+ if (catalog.status === "offline" && !catalogContains(found, binding.model)) {
828
+ return {
829
+ role,
830
+ harness: binding.harness,
831
+ model: binding.model,
832
+ status: "unverified",
833
+ message: `Cannot validate model "${binding.model}" for harness "${binding.harness}": the catalog is stale. Re-run with --refresh.`,
834
+ };
835
+ }
836
+ if (!catalogContains(found, binding.model)) {
837
+ return {
838
+ role,
839
+ harness: binding.harness,
840
+ model: binding.model,
841
+ status: "unknown-model",
842
+ message: `Model "${binding.model}" is not in the ${binding.harness} catalog for role "${role}".`,
843
+ };
844
+ }
845
+ return { role, harness: binding.harness, model: binding.model, status: "accepted", message: "" };
846
+ });
847
+ const failed = entries.find((entry) => entry.status !== "accepted");
848
+ const code = failed === undefined
849
+ ? undefined
850
+ : failed.status === "harness-unavailable"
851
+ ? 11
852
+ : failed.status === "unknown-model"
853
+ ? 12
854
+ : 13;
855
+ let message = null;
856
+ if (catalog.status === "offline") {
857
+ message = failed?.status === "unverified" ? failed.message : "Catalog is stale; using offline cache.";
858
+ }
859
+ else if (catalog.status === "unavailable") {
860
+ message = failed?.message ?? "Catalog unavailable.";
861
+ }
862
+ return { entries, code, message };
863
+ }
864
+ function catalogValidation(catalog, message) {
865
+ if (catalog === undefined)
866
+ return notNeededCatalog();
867
+ return {
868
+ status: catalog.status,
869
+ source: catalog.source,
870
+ ageMs: catalog.ageMs,
871
+ message,
872
+ };
873
+ }
874
+ function configSummary(config) {
875
+ const agents = config.agents ?? {};
876
+ return ROLES.map((role) => {
877
+ const binding = agents[role];
878
+ return `${role} ${binding ? `${binding.harness}:${binding.model}` : "unset"}`;
879
+ }).join(" · ");
880
+ }
881
+ function errorResult(read, proposal, catalog, bindings, changes, code, message) {
882
+ return {
883
+ code,
884
+ envelope: {
885
+ proposta: proposal,
886
+ validacoes: { config: configValidation(read), catalogo: catalog, bindings },
887
+ mudancas: changes,
888
+ resultado: { status: "error", code, saved: false, message },
889
+ },
890
+ };
891
+ }
892
+ export async function runSetupBatch(options, dependencies = {}) {
893
+ const stderr = dependencies.stderr;
894
+ const store = dependencies.configStore ?? createSetupConfigStore();
895
+ let read;
896
+ try {
897
+ read = store.read();
898
+ }
899
+ catch (error) {
900
+ const path = getPaths().configFile;
901
+ const message = `Cannot save config "${path}": ${error instanceof Error ? error.message : String(error)}.`;
902
+ writeLine(stderr, message);
903
+ return {
904
+ code: 15,
905
+ envelope: emptyEnvelope(message, 15, path),
906
+ };
907
+ }
908
+ if (read.status === "invalid") {
909
+ const message = read.readError
910
+ ? `Cannot save config "${getPaths().configFile}": ${read.message}.`
911
+ : read.message ?? `Config file "${read.path}" contains invalid JSON; no changes were written. Repair or move it and retry.`;
912
+ writeLine(stderr, message);
913
+ return errorResult(read, null, notNeededCatalog(), [], [], read.readError ? 15 : 14, message);
914
+ }
915
+ const current = { ...DEFAULT_CONFIG, ...(read.config ?? {}) };
916
+ const lastByRole = new Map();
917
+ options.binds.forEach((binding, index) => lastByRole.set(binding.role, index));
918
+ const winning = options.binds.filter((binding, index) => lastByRole.get(binding.role) === index);
919
+ const existingAgents = jsonObject(current.agents) ? current.agents : {};
920
+ const proposed = options.binds.length === 0
921
+ ? { ...current }
922
+ : {
923
+ ...current,
924
+ agents: {
925
+ ...existingAgents,
926
+ ...Object.fromEntries(winning.map(({ role, binding }) => [role, binding])),
927
+ },
928
+ };
929
+ const changes = diffConfig(current, proposed);
930
+ let catalog;
931
+ let bindingValidations = [];
932
+ let catalogMessage = null;
933
+ if (winning.length > 0 || options.refresh) {
934
+ const agents = winning.length > 0
935
+ ? [...new Set(winning.map(({ binding }) => binding.harness))]
936
+ : undefined;
937
+ catalog = await getBatchModels(dependencies.registry ?? getRegistry(), {
938
+ agents,
939
+ refresh: options.refresh,
940
+ allowNetwork: !options.dryRun || options.refresh,
941
+ now: dependencies.now,
942
+ timeoutMs: dependencies.timeoutMs,
943
+ discover: dependencies.discoverModels,
944
+ saveCache: dependencies.saveCache,
945
+ onDiscoveryStart: () => writeLine(stderr, "discovering models..."),
946
+ });
947
+ if (catalog.discoveryError !== undefined) {
948
+ writeLine(stderr, `Warning: ${catalog.discoveryError}.`);
949
+ }
950
+ if (catalog.cacheWriteFailed)
951
+ writeLine(stderr, "Warning: Could not update models cache.");
952
+ if (winning.length > 0) {
953
+ const validation = validateBindings(winning, catalog);
954
+ bindingValidations = validation.entries;
955
+ catalogMessage = validation.message;
956
+ if (validation.code !== undefined) {
957
+ for (const entry of bindingValidations) {
958
+ if (entry.status !== "accepted")
959
+ writeLine(stderr, entry.message);
960
+ }
961
+ return errorResult(read, proposed, catalogValidation(catalog, catalogMessage), bindingValidations, changes, validation.code, bindingValidations.find((entry) => entry.status !== "accepted")?.message ?? "Setup validation failed.");
962
+ }
963
+ }
964
+ else if (catalog.status === "unavailable") {
965
+ catalogMessage = "Catalog unavailable.";
966
+ const message = "Model catalog unavailable.";
967
+ writeLine(stderr, message);
968
+ return errorResult(read, proposed, catalogValidation(catalog, catalogMessage), [], changes, 13, message);
969
+ }
970
+ else if (catalog.status === "offline") {
971
+ catalogMessage = "Catalog is stale; using offline cache.";
972
+ writeLine(stderr, catalogMessage);
973
+ }
974
+ }
975
+ const validations = {
976
+ config: configValidation(read),
977
+ catalogo: catalogValidation(catalog, catalogMessage),
978
+ bindings: bindingValidations,
979
+ };
980
+ if (winning.length === 0) {
981
+ const message = "Configuration unchanged.";
982
+ if (!options.json)
983
+ writeLine(stderr, message);
984
+ return {
985
+ code: 0,
986
+ envelope: { proposta: proposed, validacoes: validations, mudancas: changes, resultado: { status: "unchanged", code: 0, saved: false, message } },
987
+ };
988
+ }
989
+ if (options.dryRun) {
990
+ const message = "Dry run; configuration not written.";
991
+ if (!options.json)
992
+ writeLine(stderr, message);
993
+ return {
994
+ code: 0,
995
+ envelope: { proposta: proposed, validacoes: validations, mudancas: changes, resultado: { status: "dry-run", code: 0, saved: false, message } },
996
+ };
997
+ }
998
+ const serialized = serializeConfig(proposed);
999
+ let saved = false;
1000
+ try {
1001
+ const canonicalRaw = read.source === "canonical" ? read.raw : null;
1002
+ if (canonicalRaw !== null && canonicalRaw === serialized) {
1003
+ saved = false;
1004
+ }
1005
+ else {
1006
+ saved = store.save(proposed) !== false;
1007
+ }
1008
+ }
1009
+ catch (error) {
1010
+ const message = `Cannot save config "${getPaths().configFile}": ${error instanceof Error ? error.message : String(error)}.`;
1011
+ writeLine(stderr, message);
1012
+ return errorResult(read, proposed, catalogValidation(catalog, catalogMessage), bindingValidations, changes, 15, message);
1013
+ }
1014
+ if (!saved) {
1015
+ const message = "Configuration unchanged.";
1016
+ if (!options.json)
1017
+ writeLine(stderr, message);
1018
+ return {
1019
+ code: 0,
1020
+ envelope: { proposta: proposed, validacoes: validations, mudancas: [], resultado: { status: "unchanged", code: 0, saved: false, message } },
1021
+ };
1022
+ }
1023
+ const message = "Configuration saved.";
1024
+ if (!options.json)
1025
+ writeLine(stderr, `saved: ${configSummary(proposed)}`);
1026
+ return {
1027
+ code: 0,
1028
+ envelope: { proposta: proposed, validacoes: validations, mudancas: changes, resultado: { status: "applied", code: 0, saved: true, message } },
1029
+ };
1030
+ }
1031
+ function commandTokens(opts, command) {
1032
+ const tokens = [];
1033
+ if (opts.refresh)
1034
+ tokens.push("--refresh");
1035
+ if (opts.nonInteractive)
1036
+ tokens.push("--non-interactive");
1037
+ if (opts.json)
1038
+ tokens.push("--json");
1039
+ if (opts.dryRun)
1040
+ tokens.push("--dry-run");
1041
+ const binds = Array.isArray(opts.bind) ? opts.bind : opts.bind === undefined ? [] : [opts.bind];
1042
+ for (const bind of binds) {
1043
+ tokens.push("--bind");
1044
+ if (bind !== true)
1045
+ tokens.push(String(bind));
1046
+ }
1047
+ return tokens.concat(command.args.map(String));
1048
+ }
1049
+ export async function executeSetupAction(args, dependencies = {}) {
1050
+ const parsed = parseSetupArgs(args);
1051
+ const stdout = dependencies.stdout ?? process.stdout;
1052
+ const input = dependencies.input ?? process.stdin;
1053
+ const writeError = (message) => writeLine(dependencies.stderr, message);
1054
+ if (!parsed.ok) {
1055
+ if (parsed.json)
1056
+ stdout.write(`${JSON.stringify(emptyEnvelope(parsed.message, 2, getPaths().configFile))}\n`);
1057
+ writeError(parsed.message);
1058
+ return { code: 2, envelope: parsed.json ? emptyEnvelope(parsed.message, 2, getPaths().configFile) : undefined };
1059
+ }
1060
+ const tty = dependencies.isTTY ?? Boolean(input.isTTY && stdout.isTTY);
1061
+ if (!parsed.options.batch) {
1062
+ if (!tty) {
1063
+ const message = `${getCliName()} setup needs a terminal on both stdin and stdout.`;
1064
+ writeError(message);
1065
+ return { code: 1 };
1066
+ }
1067
+ await runModelSetupWizard({
1068
+ registry: dependencies.registry,
1069
+ input: dependencies.input,
1070
+ output: dependencies.stdout,
1071
+ refresh: parsed.options.refresh,
1072
+ discoverModels: dependencies.wizardDiscoverModels,
1073
+ save: dependencies.saveConfig,
1074
+ isTTY: tty,
1075
+ });
1076
+ return { code: 0 };
1077
+ }
1078
+ const result = await runSetupBatch(parsed.options, dependencies);
1079
+ if (parsed.options.json)
1080
+ stdout.write(`${JSON.stringify(result.envelope)}\n`);
1081
+ return result;
1082
+ }
1083
+ export function registerSetupCommand(program, dependencies = {}) {
493
1084
  program
494
1085
  .command("setup")
495
1086
  .description("Choose the harness and model each agent should run on")
1087
+ .allowUnknownOption(true)
1088
+ .allowExcessArguments(true)
496
1089
  .option("--refresh", "ignore the cached catalog and rediscover")
497
- .action(async (opts) => {
498
- // The message lives here rather than in the wizard, because `open` calls
499
- // the same function and has to stay quiet when it cannot prompt.
500
- if (!isInteractiveTerminal()) {
501
- console.error(`${getCliName()} setup needs a terminal on both stdin and stdout.`);
502
- process.exitCode = 1;
503
- return;
504
- }
505
- await runModelSetupWizard({ refresh: opts.refresh });
1090
+ .option("--non-interactive", "run setup without the picker")
1091
+ .option("--json", "output one machine-readable envelope")
1092
+ .option("--dry-run", "show the proposed config without writing it")
1093
+ .option("--bind [binding]", "bind a role to a harness and model", (value, previous = []) => [...previous, value], [])
1094
+ .action(async (opts, command) => {
1095
+ const result = await executeSetupAction(commandTokens(opts, command), dependencies);
1096
+ process.exitCode = result.code;
506
1097
  });
507
1098
  }
508
1099
  //# sourceMappingURL=setup.js.map