codedeck 0.6.0 → 0.7.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.
- package/README.md +50 -35
- package/dist/cli/commands/claims.js +3 -2
- package/dist/cli/commands/claims.js.map +1 -1
- package/dist/cli/commands/doctor.js +8 -40
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/logs.js +2 -2
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/open.js +102 -11
- package/dist/cli/commands/open.js.map +1 -1
- package/dist/cli/commands/ps.js +6 -6
- package/dist/cli/commands/ps.js.map +1 -1
- package/dist/cli/commands/review.js +36 -58
- package/dist/cli/commands/review.js.map +1 -1
- package/dist/cli/commands/run.js +27 -27
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/setup.js +96 -314
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/show.js +2 -1
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/ui.js +61 -0
- package/dist/cli/commands/ui.js.map +1 -0
- package/dist/cli/commands/usage-backfill.js +149 -0
- package/dist/cli/commands/usage-backfill.js.map +1 -0
- package/dist/cli/commands/usage.js +113 -45
- package/dist/cli/commands/usage.js.map +1 -1
- package/dist/cli/commands/wait.js +3 -2
- package/dist/cli/commands/wait.js.map +1 -1
- package/dist/cli/index.js +55 -39
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/web-launch.js +126 -0
- package/dist/cli/web-launch.js.map +1 -0
- package/dist/config/config.js +0 -70
- package/dist/config/config.js.map +1 -1
- package/dist/config/paths.js +1 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/setup.js +248 -0
- package/dist/config/setup.js.map +1 -0
- package/dist/config/web-host.js +31 -0
- package/dist/config/web-host.js.map +1 -0
- package/dist/config/web-port.js +24 -0
- package/dist/config/web-port.js.map +1 -0
- package/dist/core/claude-transcript.js +226 -0
- package/dist/core/claude-transcript.js.map +1 -0
- package/dist/core/errors.js +17 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/models.js +4 -1
- package/dist/core/models.js.map +1 -1
- package/dist/core/pricing.js +87 -55
- package/dist/core/pricing.js.map +1 -1
- package/dist/core/roles.js +6 -5
- package/dist/core/roles.js.map +1 -1
- package/dist/core/run-skills.js +114 -0
- package/dist/core/run-skills.js.map +1 -0
- package/dist/core/run-usage.js +56 -10
- package/dist/core/run-usage.js.map +1 -1
- package/dist/core/session.js +6 -16
- package/dist/core/session.js.map +1 -1
- package/dist/core/usage-query.js +41 -0
- package/dist/core/usage-query.js.map +1 -0
- package/dist/core/usage-source.js +17 -0
- package/dist/core/usage-source.js.map +1 -0
- package/dist/daemon/build-id.js +30 -0
- package/dist/daemon/build-id.js.map +1 -0
- package/dist/daemon/daemon.js +1257 -700
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/instance-lock.js +48 -0
- package/dist/daemon/instance-lock.js.map +1 -0
- package/dist/daemon/ipc.js +4 -3
- package/dist/daemon/ipc.js.map +1 -1
- package/dist/daemon/web-supervisor.js +251 -0
- package/dist/daemon/web-supervisor.js.map +1 -0
- package/dist/drivers/claude/driver.js +1 -0
- package/dist/drivers/claude/driver.js.map +1 -1
- package/dist/drivers/codex/driver.js +14 -3
- package/dist/drivers/codex/driver.js.map +1 -1
- package/dist/drivers/codex/host-skills.js +57 -0
- package/dist/drivers/codex/host-skills.js.map +1 -0
- package/dist/drivers/opencode/driver.js +6 -2
- package/dist/drivers/opencode/driver.js.map +1 -1
- package/dist/drivers/session-driver.js +7 -1
- package/dist/drivers/session-driver.js.map +1 -1
- package/dist/drivers/session-runtime.js +2 -0
- package/dist/drivers/session-runtime.js.map +1 -1
- package/dist/drivers/terminal.js +15 -0
- package/dist/drivers/terminal.js.map +1 -1
- package/dist/git/diff.js +29 -4
- package/dist/git/diff.js.map +1 -1
- package/dist/open/contract.js.map +1 -1
- package/dist/open/launchers/claude.js +78 -2
- package/dist/open/launchers/claude.js.map +1 -1
- package/dist/open/link-watcher.js +44 -0
- package/dist/open/link-watcher.js.map +1 -0
- package/dist/open/pty.js +330 -5
- package/dist/open/pty.js.map +1 -1
- package/dist/open/runtime.js +15 -12
- package/dist/open/runtime.js.map +1 -1
- package/dist/plugin/agents/general.md +33 -6
- package/dist/plugin/hooks/pane-ticker.ts +102 -0
- package/dist/plugin/hooks/register.tsx +39 -18
- package/dist/plugin/hooks/session-id.sh +1 -1
- package/dist/plugin/mods/agents/pane.ts +285 -156
- package/dist/plugin/mods/agents/types.ts +23 -3
- package/dist/plugin/statusline.sh +57 -32
- package/dist/store/database.js +49 -1
- package/dist/store/database.js.map +1 -1
- package/dist/store/native-links.js +80 -0
- package/dist/store/native-links.js.map +1 -0
- package/dist/store/sessions.js +166 -51
- package/dist/store/sessions.js.map +1 -1
- package/dist/store/usage-ledger.js +153 -0
- package/dist/store/usage-ledger.js.map +1 -0
- package/dist/utils/process.js +2 -16
- package/dist/utils/process.js.map +1 -1
- package/dist/web/brand.js +59 -0
- package/dist/web/brand.js.map +1 -0
- package/dist/web/child.js +67 -0
- package/dist/web/child.js.map +1 -0
- package/dist/web/home-page.js +48 -0
- package/dist/web/home-page.js.map +1 -0
- package/dist/web/review-page.js +12 -5
- package/dist/web/review-page.js.map +1 -1
- package/dist/web/security.js +170 -0
- package/dist/web/security.js.map +1 -0
- package/dist/web/server.js +198 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/setup-icons.js +37 -0
- package/dist/web/setup-icons.js.map +1 -0
- package/dist/web/setup-page.js +1795 -0
- package/dist/web/setup-page.js.map +1 -0
- package/dist/web/setup-routes.js +475 -0
- package/dist/web/setup-routes.js.map +1 -0
- package/dist/web/usage-page.js +609 -0
- package/dist/web/usage-page.js.map +1 -0
- package/dist/web/usage-routes.js +88 -0
- package/dist/web/usage-routes.js.map +1 -0
- package/dist/web/web-token.js +56 -0
- package/dist/web/web-token.js.map +1 -0
- package/package.json +1 -1
- package/dist/plugin/prompts/_partials/commit.md +0 -145
- package/dist/plugin/prompts/_partials/pr-writer.md +0 -186
|
@@ -9,7 +9,11 @@ import { getCliName } from "../cli-name.js";
|
|
|
9
9
|
import { getRegistry } from "../../drivers/registry.js";
|
|
10
10
|
import { ROLES } from "../../core/roles.js";
|
|
11
11
|
import { getPaths } from "../../config/paths.js";
|
|
12
|
-
import {
|
|
12
|
+
import { buildSetupPlan, diffConfig, SetupUsageError, validateBindings, } from "../../config/setup.js";
|
|
13
|
+
import { BALANCED_PRESET, DISPATCHER_PRESET, EXPLORER_PRESET, ORCHESTRATOR_PRESETS, createSetupConfigStore, DEFAULT_CONFIG, isOrchestratorMode, loadConfig, orchestratorModeLabel, resolveOrchestratorMode, saveConfig, serializeConfig, } from "../../config/config.js";
|
|
14
|
+
import { parseOptionalWebPort } from "../../web/server.js";
|
|
15
|
+
import { launchWebPage } from "../web-launch.js";
|
|
16
|
+
export { diffConfig, SetupUsageError };
|
|
13
17
|
const ORCHESTRATOR_SCREEN_ROLE = "orchestrator-mode";
|
|
14
18
|
const ORCHESTRATOR_PICKER_GROUP = "orchestrator";
|
|
15
19
|
const PARALLELISM_NONE = "none";
|
|
@@ -320,16 +324,8 @@ export function needsModelSetup(config, isTTY = isInteractiveTerminal()) {
|
|
|
320
324
|
return false;
|
|
321
325
|
// `agents` is what setup writes now. A config carrying only the older
|
|
322
326
|
// per-harness `models` has never answered the per-role question, so it still
|
|
323
|
-
// counts as unset.
|
|
324
|
-
|
|
325
|
-
if (config == null)
|
|
326
|
-
return true;
|
|
327
|
-
try {
|
|
328
|
-
return resolveEffectiveConfig(config).agents == null;
|
|
329
|
-
}
|
|
330
|
-
catch {
|
|
331
|
-
return config.agents == null;
|
|
332
|
-
}
|
|
327
|
+
// counts as unset.
|
|
328
|
+
return config?.agents == null;
|
|
333
329
|
}
|
|
334
330
|
/**
|
|
335
331
|
* The catalog of every installed harness, flattened into one list.
|
|
@@ -476,7 +472,7 @@ function watchResize(listener) {
|
|
|
476
472
|
}
|
|
477
473
|
export async function runModelSetupWizard(options = {}) {
|
|
478
474
|
const loaded = options.config ?? loadConfig();
|
|
479
|
-
const
|
|
475
|
+
const config = loaded;
|
|
480
476
|
if (!(options.isTTY ?? isInteractiveTerminal()))
|
|
481
477
|
return config;
|
|
482
478
|
const output = options.output ?? process.stdout;
|
|
@@ -571,22 +567,22 @@ export async function runModelSetupWizard(options = {}) {
|
|
|
571
567
|
return config;
|
|
572
568
|
}
|
|
573
569
|
}
|
|
574
|
-
const
|
|
570
|
+
const selections = { agents };
|
|
575
571
|
if (orchestrator !== undefined)
|
|
576
|
-
|
|
572
|
+
selections.orchestrator = orchestrator;
|
|
577
573
|
const sandboxSelection = answeredResults.find((result) => result.role === SANDBOX_SCREEN_ROLE);
|
|
578
574
|
if (sandboxSelection?.kind === "picked") {
|
|
579
575
|
if (sandboxSelection.id === SANDBOX_OFF || sandboxSelection.id === SANDBOX_ON) {
|
|
580
|
-
|
|
576
|
+
selections.sandbox = sandboxSelection.id;
|
|
581
577
|
}
|
|
582
578
|
}
|
|
583
579
|
const autocompactSelection = answeredResults.find((result) => result.role === AUTOCOMPACT_SCREEN_ROLE);
|
|
584
580
|
if (autocompactSelection?.kind === "picked") {
|
|
585
581
|
if (autocompactSelection.id === AUTOCOMPACT_ON) {
|
|
586
|
-
|
|
582
|
+
selections.autocompact = { enabled: true };
|
|
587
583
|
}
|
|
588
584
|
else if (autocompactSelection.id === AUTOCOMPACT_OFF && config.autocompact !== undefined) {
|
|
589
|
-
|
|
585
|
+
selections.autocompact = { enabled: false };
|
|
590
586
|
}
|
|
591
587
|
}
|
|
592
588
|
// Per-role effort answers arrive as followups of their role screen, in the
|
|
@@ -601,16 +597,10 @@ export async function runModelSetupWizard(options = {}) {
|
|
|
601
597
|
agents[target] = { ...agents[target], effort: result.id };
|
|
602
598
|
}
|
|
603
599
|
}
|
|
604
|
-
updatedConfig
|
|
605
|
-
const toSave = profile === undefined
|
|
606
|
-
? updatedConfig
|
|
607
|
-
: {
|
|
608
|
-
...loaded,
|
|
609
|
-
profiles: { ...loaded.profiles, [profile]: extractProfileSnapshot(updatedConfig) },
|
|
610
|
-
};
|
|
600
|
+
const { proposedConfig: updatedConfig } = buildSetupPlan(loaded, selections);
|
|
611
601
|
let saved = false;
|
|
612
602
|
try {
|
|
613
|
-
(options.save ?? saveConfig)(
|
|
603
|
+
(options.save ?? saveConfig)(updatedConfig);
|
|
614
604
|
saved = true;
|
|
615
605
|
}
|
|
616
606
|
catch (error) {
|
|
@@ -631,32 +621,6 @@ export async function runModelSetupWizard(options = {}) {
|
|
|
631
621
|
output.write(`\n saved: ${summary}${modeSummary}\n\n`);
|
|
632
622
|
return updatedConfig;
|
|
633
623
|
}
|
|
634
|
-
export class SetupUsageError extends Error {
|
|
635
|
-
constructor(message) {
|
|
636
|
-
super(message);
|
|
637
|
-
this.name = "SetupUsageError";
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
function resolveSetupTarget(loaded, explicitProfile) {
|
|
641
|
-
const isExplicit = explicitProfile !== undefined;
|
|
642
|
-
const profile = isExplicit
|
|
643
|
-
? parseProfileName(explicitProfile)
|
|
644
|
-
: loaded.activeProfile === undefined || loaded.activeProfile.trim() === ""
|
|
645
|
-
? undefined
|
|
646
|
-
: parseProfileName(loaded.activeProfile);
|
|
647
|
-
const snapshot = profile === undefined ? undefined : getProfileSnapshot(loaded, profile);
|
|
648
|
-
if (!isExplicit && profile !== undefined && snapshot === undefined) {
|
|
649
|
-
throw new SetupUsageError(`Active profile "${profile}" does not exist. Choose an existing profile with "codedeck profile use <name>" or pass --profile <name>.`);
|
|
650
|
-
}
|
|
651
|
-
return {
|
|
652
|
-
...(profile === undefined ? {} : { profile }),
|
|
653
|
-
config: profile === undefined
|
|
654
|
-
? loaded
|
|
655
|
-
: snapshot === undefined
|
|
656
|
-
? { ...baseConfig(loaded), agents: {} }
|
|
657
|
-
: { ...baseConfig(loaded), ...snapshot },
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
624
|
function invalidBindMessage(value) {
|
|
661
625
|
return `Invalid --bind "${value}": expected role=harness:model[:effort] (role: general|orchestrator|reviewer|auditor; harness: claude|codex|opencode|omp; model: non-empty and without whitespace, control characters or '='; effort: ${REASONING_EFFORTS.join("|")})`;
|
|
662
626
|
}
|
|
@@ -703,34 +667,34 @@ export function parseSetupArgs(args) {
|
|
|
703
667
|
let refresh = false;
|
|
704
668
|
let nonInteractive = false;
|
|
705
669
|
let dryRun = false;
|
|
706
|
-
let
|
|
670
|
+
let tui = false;
|
|
671
|
+
let noOpen = false;
|
|
672
|
+
let port;
|
|
707
673
|
for (let index = 0; index < args.length; index += 1) {
|
|
708
674
|
const arg = args[index];
|
|
709
675
|
if (arg === "--refresh") {
|
|
710
676
|
refresh = true;
|
|
711
677
|
continue;
|
|
712
678
|
}
|
|
713
|
-
if (arg === "--
|
|
679
|
+
if (arg === "--tui") {
|
|
680
|
+
tui = true;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (arg === "--no-open") {
|
|
684
|
+
noOpen = true;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (arg === "--port") {
|
|
714
688
|
const value = args[index + 1];
|
|
715
689
|
if (value === undefined || isSetupFlag(value)) {
|
|
716
|
-
return parseError('Option "--
|
|
690
|
+
return parseError('Option "--port" expects a port.', json);
|
|
717
691
|
}
|
|
692
|
+
port = value;
|
|
718
693
|
index += 1;
|
|
719
|
-
try {
|
|
720
|
-
profile = parseProfileName(value);
|
|
721
|
-
}
|
|
722
|
-
catch (error) {
|
|
723
|
-
return parseError(error instanceof Error ? error.message : String(error), json);
|
|
724
|
-
}
|
|
725
694
|
continue;
|
|
726
695
|
}
|
|
727
|
-
if (arg.startsWith("--
|
|
728
|
-
|
|
729
|
-
profile = parseProfileName(arg.slice("--profile=".length));
|
|
730
|
-
}
|
|
731
|
-
catch (error) {
|
|
732
|
-
return parseError(error instanceof Error ? error.message : String(error), json);
|
|
733
|
-
}
|
|
696
|
+
if (arg.startsWith("--port=")) {
|
|
697
|
+
port = arg.slice("--port=".length);
|
|
734
698
|
continue;
|
|
735
699
|
}
|
|
736
700
|
if (arg === "--non-interactive") {
|
|
@@ -771,6 +735,12 @@ export function parseSetupArgs(args) {
|
|
|
771
735
|
return parseError(`Unknown option "${arg}".`, json);
|
|
772
736
|
return parseError(`Unexpected argument "${arg}".`, json);
|
|
773
737
|
}
|
|
738
|
+
const batch = nonInteractive || json || dryRun || binds.length > 0;
|
|
739
|
+
if (tui && batch)
|
|
740
|
+
return parseError('Option "--tui" cannot be used with batch setup flags.', json);
|
|
741
|
+
if (port !== undefined && (json || dryRun || nonInteractive)) {
|
|
742
|
+
return parseError('Option "--port" cannot be used with --json, --dry-run, or --non-interactive.', json);
|
|
743
|
+
}
|
|
774
744
|
return {
|
|
775
745
|
ok: true,
|
|
776
746
|
options: {
|
|
@@ -778,9 +748,11 @@ export function parseSetupArgs(args) {
|
|
|
778
748
|
nonInteractive,
|
|
779
749
|
json,
|
|
780
750
|
dryRun,
|
|
751
|
+
tui,
|
|
752
|
+
noOpen,
|
|
753
|
+
...(port === undefined ? {} : { port }),
|
|
781
754
|
binds,
|
|
782
|
-
batch
|
|
783
|
-
...(profile === undefined ? {} : { profile }),
|
|
755
|
+
batch,
|
|
784
756
|
},
|
|
785
757
|
};
|
|
786
758
|
}
|
|
@@ -815,202 +787,6 @@ function emptyEnvelope(message, code, path) {
|
|
|
815
787
|
resultado: { status: "error", code, saved: false, message },
|
|
816
788
|
};
|
|
817
789
|
}
|
|
818
|
-
function jsonObject(value) {
|
|
819
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
820
|
-
}
|
|
821
|
-
function jsonValue(value) {
|
|
822
|
-
if (value === undefined)
|
|
823
|
-
return null;
|
|
824
|
-
if (Array.isArray(value))
|
|
825
|
-
return value.map(jsonValue);
|
|
826
|
-
if (jsonObject(value)) {
|
|
827
|
-
return Object.fromEntries(Object.keys(value).sort((left, right) => left.localeCompare(right)).map((key) => [key, jsonValue(value[key])]));
|
|
828
|
-
}
|
|
829
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
830
|
-
return value;
|
|
831
|
-
}
|
|
832
|
-
return null;
|
|
833
|
-
}
|
|
834
|
-
function jsonEqual(left, right) {
|
|
835
|
-
return JSON.stringify(jsonValue(left)) === JSON.stringify(jsonValue(right));
|
|
836
|
-
}
|
|
837
|
-
function pointerPart(value) {
|
|
838
|
-
return value.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
839
|
-
}
|
|
840
|
-
function diffAt(output, pathValue, beforePresent, before, afterPresent, after, expandObjectChildren = false) {
|
|
841
|
-
const beforeObject = beforePresent && jsonObject(before) ? before : undefined;
|
|
842
|
-
const afterObject = afterPresent && jsonObject(after) ? after : undefined;
|
|
843
|
-
if (beforeObject !== undefined && afterObject !== undefined) {
|
|
844
|
-
const keys = [...new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)])].sort((left, right) => left.localeCompare(right));
|
|
845
|
-
if (keys.length === 0)
|
|
846
|
-
return;
|
|
847
|
-
for (const key of keys) {
|
|
848
|
-
diffAt(output, `${pathValue}/${pointerPart(key)}`, Object.hasOwn(beforeObject, key), beforeObject[key], Object.hasOwn(afterObject, key), afterObject[key]);
|
|
849
|
-
}
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
if (!beforePresent && afterObject !== undefined) {
|
|
853
|
-
if (expandObjectChildren) {
|
|
854
|
-
const keys = Object.keys(afterObject).sort((left, right) => left.localeCompare(right));
|
|
855
|
-
if (keys.length === 0) {
|
|
856
|
-
output.push({
|
|
857
|
-
path: pathValue,
|
|
858
|
-
beforePresent: false,
|
|
859
|
-
before: null,
|
|
860
|
-
afterPresent: true,
|
|
861
|
-
after: jsonValue(afterObject),
|
|
862
|
-
});
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
for (const key of keys) {
|
|
866
|
-
const value = afterObject[key];
|
|
867
|
-
const childPath = `${pathValue}/${pointerPart(key)}`;
|
|
868
|
-
if (jsonObject(value)) {
|
|
869
|
-
output.push({
|
|
870
|
-
path: childPath,
|
|
871
|
-
beforePresent: false,
|
|
872
|
-
before: null,
|
|
873
|
-
afterPresent: true,
|
|
874
|
-
after: jsonValue(value),
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
else {
|
|
878
|
-
diffAt(output, childPath, false, undefined, true, value);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
output.push({
|
|
884
|
-
path: pathValue,
|
|
885
|
-
beforePresent: false,
|
|
886
|
-
before: null,
|
|
887
|
-
afterPresent: true,
|
|
888
|
-
after: jsonValue(afterObject),
|
|
889
|
-
});
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
if (beforeObject !== undefined && !afterPresent) {
|
|
893
|
-
if (expandObjectChildren) {
|
|
894
|
-
const keys = Object.keys(beforeObject).sort((left, right) => left.localeCompare(right));
|
|
895
|
-
if (keys.length === 0) {
|
|
896
|
-
output.push({
|
|
897
|
-
path: pathValue,
|
|
898
|
-
beforePresent: true,
|
|
899
|
-
before: jsonValue(beforeObject),
|
|
900
|
-
afterPresent: false,
|
|
901
|
-
after: null,
|
|
902
|
-
});
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
for (const key of keys) {
|
|
906
|
-
const value = beforeObject[key];
|
|
907
|
-
const childPath = `${pathValue}/${pointerPart(key)}`;
|
|
908
|
-
if (jsonObject(value)) {
|
|
909
|
-
output.push({
|
|
910
|
-
path: childPath,
|
|
911
|
-
beforePresent: true,
|
|
912
|
-
before: jsonValue(value),
|
|
913
|
-
afterPresent: false,
|
|
914
|
-
after: null,
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
else {
|
|
918
|
-
diffAt(output, childPath, true, value, false, undefined);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
923
|
-
output.push({
|
|
924
|
-
path: pathValue,
|
|
925
|
-
beforePresent: true,
|
|
926
|
-
before: jsonValue(beforeObject),
|
|
927
|
-
afterPresent: false,
|
|
928
|
-
after: null,
|
|
929
|
-
});
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
if (beforePresent === afterPresent && (!beforePresent || jsonEqual(before, after)))
|
|
933
|
-
return;
|
|
934
|
-
output.push({
|
|
935
|
-
path: pathValue,
|
|
936
|
-
beforePresent,
|
|
937
|
-
before: jsonValue(before),
|
|
938
|
-
afterPresent,
|
|
939
|
-
after: jsonValue(after),
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
export function diffConfig(before, after) {
|
|
943
|
-
const output = [];
|
|
944
|
-
const beforeObject = jsonObject(before) ? before : {};
|
|
945
|
-
const afterObject = jsonObject(after) ? after : {};
|
|
946
|
-
const keys = [...new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)])].sort((left, right) => left.localeCompare(right));
|
|
947
|
-
for (const key of keys) {
|
|
948
|
-
diffAt(output, `/${pointerPart(key)}`, Object.hasOwn(beforeObject, key), beforeObject[key], Object.hasOwn(afterObject, key), afterObject[key], true);
|
|
949
|
-
}
|
|
950
|
-
return output.sort((left, right) => left.path.localeCompare(right.path));
|
|
951
|
-
}
|
|
952
|
-
function catalogContains(catalog, model) {
|
|
953
|
-
return catalog.providers.some((provider) => provider.models.some((candidate) => candidate.id === model || (candidate.aliases !== undefined && candidate.aliases.some((alias) => alias === model))));
|
|
954
|
-
}
|
|
955
|
-
function validateBindings(bindings, catalog) {
|
|
956
|
-
const byAgent = new Map(catalog.models.map((item) => [item.agent, item]));
|
|
957
|
-
const entries = bindings.map(({ role, binding }) => {
|
|
958
|
-
const found = byAgent.get(binding.harness);
|
|
959
|
-
if (found?.available === false) {
|
|
960
|
-
return {
|
|
961
|
-
role,
|
|
962
|
-
harness: binding.harness,
|
|
963
|
-
model: binding.model,
|
|
964
|
-
status: "harness-unavailable",
|
|
965
|
-
message: `Cannot apply binding for role "${role}": harness "${binding.harness}" is unavailable.`,
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
if (catalog.status === "unavailable" || found === undefined) {
|
|
969
|
-
return {
|
|
970
|
-
role,
|
|
971
|
-
harness: binding.harness,
|
|
972
|
-
model: binding.model,
|
|
973
|
-
status: "unverified",
|
|
974
|
-
message: `Cannot validate model "${binding.model}" for harness "${binding.harness}": catalog unavailable.`,
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
if (catalog.status === "offline" && !catalogContains(found, binding.model)) {
|
|
978
|
-
return {
|
|
979
|
-
role,
|
|
980
|
-
harness: binding.harness,
|
|
981
|
-
model: binding.model,
|
|
982
|
-
status: "unverified",
|
|
983
|
-
message: `Cannot validate model "${binding.model}" for harness "${binding.harness}": the catalog is stale. Re-run with --refresh.`,
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
if (!catalogContains(found, binding.model)) {
|
|
987
|
-
return {
|
|
988
|
-
role,
|
|
989
|
-
harness: binding.harness,
|
|
990
|
-
model: binding.model,
|
|
991
|
-
status: "unknown-model",
|
|
992
|
-
message: `Model "${binding.model}" is not in the ${binding.harness} catalog for role "${role}".`,
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
return { role, harness: binding.harness, model: binding.model, status: "accepted", message: "" };
|
|
996
|
-
});
|
|
997
|
-
const failed = entries.find((entry) => entry.status !== "accepted");
|
|
998
|
-
const code = failed === undefined
|
|
999
|
-
? undefined
|
|
1000
|
-
: failed.status === "harness-unavailable"
|
|
1001
|
-
? 11
|
|
1002
|
-
: failed.status === "unknown-model"
|
|
1003
|
-
? 12
|
|
1004
|
-
: 13;
|
|
1005
|
-
let message = null;
|
|
1006
|
-
if (catalog.status === "offline") {
|
|
1007
|
-
message = failed?.status === "unverified" ? failed.message : "Catalog is stale; using offline cache.";
|
|
1008
|
-
}
|
|
1009
|
-
else if (catalog.status === "unavailable") {
|
|
1010
|
-
message = failed?.message ?? "Catalog unavailable.";
|
|
1011
|
-
}
|
|
1012
|
-
return { entries, code, message };
|
|
1013
|
-
}
|
|
1014
790
|
function catalogValidation(catalog, message) {
|
|
1015
791
|
if (catalog === undefined)
|
|
1016
792
|
return notNeededCatalog();
|
|
@@ -1063,38 +839,23 @@ export async function runSetupBatch(options, dependencies = {}) {
|
|
|
1063
839
|
return errorResult(read, null, notNeededCatalog(), [], [], read.readError ? 15 : 14, message);
|
|
1064
840
|
}
|
|
1065
841
|
const current = { ...DEFAULT_CONFIG, ...(read.config ?? {}) };
|
|
1066
|
-
let profile;
|
|
1067
|
-
let target;
|
|
1068
|
-
try {
|
|
1069
|
-
const resolved = resolveSetupTarget(current, options.profile);
|
|
1070
|
-
profile = resolved.profile;
|
|
1071
|
-
target = resolved.config;
|
|
1072
|
-
}
|
|
1073
|
-
catch (error) {
|
|
1074
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1075
|
-
writeLine(stderr, message);
|
|
1076
|
-
return errorResult(read, null, notNeededCatalog(), [], [], 14, message);
|
|
1077
|
-
}
|
|
1078
842
|
const lastByRole = new Map();
|
|
1079
843
|
options.binds.forEach((binding, index) => lastByRole.set(binding.role, index));
|
|
1080
844
|
const winning = options.binds.filter((binding, index) => lastByRole.get(binding.role) === index);
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
},
|
|
1096
|
-
};
|
|
1097
|
-
const changes = diffConfig(current, proposed);
|
|
845
|
+
let proposed;
|
|
846
|
+
let changes;
|
|
847
|
+
if (winning.length === 0) {
|
|
848
|
+
proposed = { ...current };
|
|
849
|
+
changes = diffConfig(current, proposed);
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
const agents = {};
|
|
853
|
+
for (const { role, binding } of winning)
|
|
854
|
+
agents[role] = binding;
|
|
855
|
+
const plan = buildSetupPlan(current, { agents });
|
|
856
|
+
proposed = plan.proposedConfig;
|
|
857
|
+
changes = plan.diff;
|
|
858
|
+
}
|
|
1098
859
|
let catalog;
|
|
1099
860
|
let bindingValidations = [];
|
|
1100
861
|
let catalogMessage = null;
|
|
@@ -1189,9 +950,8 @@ export async function runSetupBatch(options, dependencies = {}) {
|
|
|
1189
950
|
};
|
|
1190
951
|
}
|
|
1191
952
|
const message = "Configuration saved.";
|
|
1192
|
-
const summarized = profile === undefined ? proposed : (proposed.profiles?.[profile] ?? proposed);
|
|
1193
953
|
if (!options.json) {
|
|
1194
|
-
writeLine(stderr,
|
|
954
|
+
writeLine(stderr, `saved: ${configSummary(proposed)}`);
|
|
1195
955
|
}
|
|
1196
956
|
return {
|
|
1197
957
|
code: 0,
|
|
@@ -1202,14 +962,18 @@ function commandTokens(opts, command) {
|
|
|
1202
962
|
const tokens = [];
|
|
1203
963
|
if (opts.refresh)
|
|
1204
964
|
tokens.push("--refresh");
|
|
965
|
+
if (opts.tui)
|
|
966
|
+
tokens.push("--tui");
|
|
967
|
+
if (opts.open === false)
|
|
968
|
+
tokens.push("--no-open");
|
|
969
|
+
if (typeof opts.port === "string")
|
|
970
|
+
tokens.push("--port", opts.port);
|
|
1205
971
|
if (opts.nonInteractive)
|
|
1206
972
|
tokens.push("--non-interactive");
|
|
1207
973
|
if (opts.json)
|
|
1208
974
|
tokens.push("--json");
|
|
1209
975
|
if (opts.dryRun)
|
|
1210
976
|
tokens.push("--dry-run");
|
|
1211
|
-
if (typeof opts.profile === "string")
|
|
1212
|
-
tokens.push("--profile", opts.profile);
|
|
1213
977
|
const binds = Array.isArray(opts.bind) ? opts.bind : opts.bind === undefined ? [] : [opts.bind];
|
|
1214
978
|
for (const bind of binds) {
|
|
1215
979
|
tokens.push("--bind");
|
|
@@ -1231,28 +995,44 @@ export async function executeSetupAction(args, dependencies = {}) {
|
|
|
1231
995
|
}
|
|
1232
996
|
const tty = dependencies.isTTY ?? Boolean(input.isTTY && stdout.isTTY);
|
|
1233
997
|
if (!parsed.options.batch) {
|
|
1234
|
-
if (
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
998
|
+
if (parsed.options.tui) {
|
|
999
|
+
if (!tty) {
|
|
1000
|
+
writeError(`${getCliName()} setup needs a terminal on both stdin and stdout.`);
|
|
1001
|
+
return { code: 1 };
|
|
1002
|
+
}
|
|
1003
|
+
try {
|
|
1004
|
+
await (dependencies.runWizard ?? runModelSetupWizard)({
|
|
1005
|
+
registry: dependencies.registry,
|
|
1006
|
+
input: dependencies.input,
|
|
1007
|
+
output: dependencies.stdout,
|
|
1008
|
+
refresh: parsed.options.refresh,
|
|
1009
|
+
discoverModels: dependencies.wizardDiscoverModels,
|
|
1010
|
+
save: dependencies.saveConfig,
|
|
1011
|
+
isTTY: tty,
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
writeError(error instanceof Error ? error.message : String(error));
|
|
1016
|
+
return { code: 1 };
|
|
1017
|
+
}
|
|
1018
|
+
return { code: 0 };
|
|
1238
1019
|
}
|
|
1020
|
+
let port;
|
|
1239
1021
|
try {
|
|
1240
|
-
|
|
1241
|
-
registry: dependencies.registry,
|
|
1242
|
-
input: dependencies.input,
|
|
1243
|
-
output: dependencies.stdout,
|
|
1244
|
-
refresh: parsed.options.refresh,
|
|
1245
|
-
discoverModels: dependencies.wizardDiscoverModels,
|
|
1246
|
-
save: dependencies.saveConfig,
|
|
1247
|
-
isTTY: tty,
|
|
1248
|
-
...(parsed.options.profile === undefined ? {} : { profile: parsed.options.profile }),
|
|
1249
|
-
});
|
|
1022
|
+
port = parseOptionalWebPort(parsed.options.port);
|
|
1250
1023
|
}
|
|
1251
1024
|
catch (error) {
|
|
1252
1025
|
writeError(error instanceof Error ? error.message : String(error));
|
|
1253
1026
|
return { code: 1 };
|
|
1254
1027
|
}
|
|
1255
|
-
|
|
1028
|
+
const code = await (dependencies.launch ?? launchWebPage)({
|
|
1029
|
+
path: "/setup",
|
|
1030
|
+
query: parsed.options.refresh ? { refresh: "1" } : {},
|
|
1031
|
+
title: "CodeDeck setup",
|
|
1032
|
+
port,
|
|
1033
|
+
open: !parsed.options.noOpen,
|
|
1034
|
+
}, { error: writeError });
|
|
1035
|
+
return { code: code === 0 ? 0 : 1 };
|
|
1256
1036
|
}
|
|
1257
1037
|
const result = await runSetupBatch(parsed.options, dependencies);
|
|
1258
1038
|
if (parsed.options.json)
|
|
@@ -1266,10 +1046,12 @@ export function registerSetupCommand(program, dependencies = {}) {
|
|
|
1266
1046
|
.allowUnknownOption(true)
|
|
1267
1047
|
.allowExcessArguments(true)
|
|
1268
1048
|
.option("--refresh", "ignore the cached catalog and rediscover")
|
|
1049
|
+
.option("--tui", "use the frozen terminal setup wizard")
|
|
1050
|
+
.option("--port <n>", "port for a new console (default: web.port from config, else 7777)")
|
|
1051
|
+
.option("--no-open", "print the setup URL without opening a browser")
|
|
1269
1052
|
.option("--non-interactive", "run setup without the picker")
|
|
1270
1053
|
.option("--json", "output one machine-readable envelope")
|
|
1271
1054
|
.option("--dry-run", "show the proposed config without writing it")
|
|
1272
|
-
.option("--profile <name>", "edit a saved profile instead of the active setup")
|
|
1273
1055
|
.option("--bind [binding]", "bind a role to a harness and model", (value, previous = []) => [...previous, value], [])
|
|
1274
1056
|
.action(async (opts, command) => {
|
|
1275
1057
|
const result = await executeSetupAction(commandTokens(opts, command), dependencies);
|