@serkanalgur/opencode-nexus 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -40
- package/dist/index.js +4454 -242
- package/dist/tui.js +176 -48
- package/package.json +3 -4
package/dist/tui.js
CHANGED
|
@@ -94,7 +94,7 @@ function describeFile(file) {
|
|
|
94
94
|
}
|
|
95
95
|
function formatConfigLoadLog(info) {
|
|
96
96
|
const models = Object.entries(info.models).map(([role, model]) => `${role}=${model}`).join(" ");
|
|
97
|
-
const override = info.sessionOverride ?
|
|
97
|
+
const override = info.sessionOverride ? ' (+session override: storage; disk edits to models are IGNORED while a preset is set \u2014 call the preset tool with mode "clear", or reset in the TUI, to hand control back to disk)' : "";
|
|
98
98
|
return `[nexus] config loaded (#${info.loadCount} trigger=${info.trigger} at=${info.loadedAt}) ` + `project=${info.project.path} [${describeFile(info.project)}] ` + `global=${info.global.path} [${describeFile(info.global)}] ` + `models:${override} ${models}`;
|
|
99
99
|
}
|
|
100
100
|
var DEFAULT_CONFIG = {
|
|
@@ -116,6 +116,11 @@ var DEFAULT_CONFIG = {
|
|
|
116
116
|
enabled: true,
|
|
117
117
|
maxRetries: 3,
|
|
118
118
|
contextTransfer: true
|
|
119
|
+
},
|
|
120
|
+
dashboard: {
|
|
121
|
+
enabled: true,
|
|
122
|
+
port: 4747,
|
|
123
|
+
host: "127.0.0.1"
|
|
119
124
|
}
|
|
120
125
|
};
|
|
121
126
|
|
|
@@ -124,9 +129,11 @@ class NexusConfigManager {
|
|
|
124
129
|
globalConfig = null;
|
|
125
130
|
storageConfig = null;
|
|
126
131
|
loadInfo = null;
|
|
127
|
-
|
|
132
|
+
dashboardBase;
|
|
133
|
+
constructor(dashboardBase) {
|
|
128
134
|
this.projectConfig = null;
|
|
129
135
|
this.globalConfig = null;
|
|
136
|
+
this.dashboardBase = { ...DEFAULT_CONFIG.dashboard, ...dashboardBase };
|
|
130
137
|
}
|
|
131
138
|
loadConfigs(basePath) {
|
|
132
139
|
if (this.loadInfo === null)
|
|
@@ -188,6 +195,11 @@ class NexusConfigManager {
|
|
|
188
195
|
...this.globalConfig?.selfHealing,
|
|
189
196
|
...this.projectConfig?.selfHealing,
|
|
190
197
|
...this.storageConfig?.selfHealing
|
|
198
|
+
},
|
|
199
|
+
dashboard: {
|
|
200
|
+
enabled: this.storageConfig?.dashboard?.enabled ?? this.projectConfig?.dashboard?.enabled ?? this.globalConfig?.dashboard?.enabled ?? this.dashboardBase.enabled,
|
|
201
|
+
port: this.storageConfig?.dashboard?.port ?? this.projectConfig?.dashboard?.port ?? this.globalConfig?.dashboard?.port ?? this.dashboardBase.port,
|
|
202
|
+
host: this.storageConfig?.dashboard?.host ?? this.projectConfig?.dashboard?.host ?? this.globalConfig?.dashboard?.host ?? this.dashboardBase.host
|
|
191
203
|
}
|
|
192
204
|
};
|
|
193
205
|
}
|
|
@@ -308,10 +320,13 @@ class NexusConfigManager {
|
|
|
308
320
|
result.models = { ...current.models };
|
|
309
321
|
result.budget = { ...current.budget };
|
|
310
322
|
result.selfHealing = { ...current.selfHealing };
|
|
323
|
+
result.dashboard = { ...current.dashboard };
|
|
311
324
|
return result;
|
|
312
325
|
}
|
|
313
326
|
resetToDefaults() {
|
|
327
|
+
const hadOverride = this.storageConfig !== null;
|
|
314
328
|
this.storageConfig = null;
|
|
329
|
+
return hadOverride;
|
|
315
330
|
}
|
|
316
331
|
applyPreset(name) {
|
|
317
332
|
const preset = PRESETS[name];
|
|
@@ -526,6 +541,140 @@ function mergeSidebarAgents(agents, polled, childIDs) {
|
|
|
526
541
|
}
|
|
527
542
|
return next.filter((a) => a.sessionID && childIDs.includes(a.sessionID) || a.status === "completed" || a.status === "failed");
|
|
528
543
|
}
|
|
544
|
+
async function probeDashboard(host, port, fetchImpl = fetch) {
|
|
545
|
+
const url = `http://${host}:${port}/api/health`;
|
|
546
|
+
let response;
|
|
547
|
+
try {
|
|
548
|
+
response = await fetchImpl(url);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
551
|
+
const refused = /ECONNREFUSED|fetch failed|Failed to fetch|NetworkError|ECONNRESET/i.test(detail);
|
|
552
|
+
return {
|
|
553
|
+
listening: false,
|
|
554
|
+
isNexusDashboard: false,
|
|
555
|
+
detail: refused ? `nothing is listening on ${host}:${port}` : `could not reach ${host}:${port} (${detail})`
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
let body;
|
|
559
|
+
try {
|
|
560
|
+
body = await response.json();
|
|
561
|
+
} catch {
|
|
562
|
+
return {
|
|
563
|
+
listening: true,
|
|
564
|
+
isNexusDashboard: false,
|
|
565
|
+
detail: `${host}:${port} answered ${response.status} but not with dashboard JSON`
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
569
|
+
const record = body;
|
|
570
|
+
if (record.ok === true && typeof record.uptime === "number") {
|
|
571
|
+
return {
|
|
572
|
+
listening: true,
|
|
573
|
+
isNexusDashboard: true,
|
|
574
|
+
detail: `a nexus dashboard is serving ${host}:${port}`
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
listening: true,
|
|
580
|
+
isNexusDashboard: false,
|
|
581
|
+
detail: `something else is listening on ${host}:${port} and it is not a nexus dashboard`
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function parseWebDashboardTarget(input, fallback) {
|
|
585
|
+
const parts = (input ?? "").trim().split(/\s+/).filter(Boolean);
|
|
586
|
+
let port = fallback.port;
|
|
587
|
+
let host = fallback.host;
|
|
588
|
+
if (parts.length > 0) {
|
|
589
|
+
const parsed = Number(parts[0]);
|
|
590
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
591
|
+
return { error: `"${parts[0]}" is not a port number. Give a port between 1 and 65535.` };
|
|
592
|
+
}
|
|
593
|
+
port = parsed;
|
|
594
|
+
}
|
|
595
|
+
if (parts.length > 1) {
|
|
596
|
+
host = parts[1];
|
|
597
|
+
}
|
|
598
|
+
return { target: { port, host } };
|
|
599
|
+
}
|
|
600
|
+
async function handleWebDashboard(input, deps) {
|
|
601
|
+
const parsed = parseWebDashboardTarget(input, {
|
|
602
|
+
port: deps.dashboard.port,
|
|
603
|
+
host: deps.dashboard.host
|
|
604
|
+
});
|
|
605
|
+
if ("error" in parsed) {
|
|
606
|
+
deps.showToast({ title: "Nexus Web Dashboard", message: parsed.error, variant: "error" });
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const { port, host } = parsed.target;
|
|
610
|
+
const url = `http://${host}:${port}`;
|
|
611
|
+
if (!deps.dashboard.enabled) {
|
|
612
|
+
deps.showToast({
|
|
613
|
+
title: "Nexus Web Dashboard \u2014 disabled",
|
|
614
|
+
message: [
|
|
615
|
+
`The dashboard is switched off: \`dashboard.enabled\` is false in your nexus.jsonc,`,
|
|
616
|
+
"so nothing will listen on any port and no browser was opened.",
|
|
617
|
+
"",
|
|
618
|
+
"Set it to true (or delete the `dashboard` block, which defaults to enabled),",
|
|
619
|
+
"then run /nexus web again."
|
|
620
|
+
].join(`
|
|
621
|
+
`),
|
|
622
|
+
variant: "error",
|
|
623
|
+
duration: 12000
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const probe = await probeDashboard(host, port, deps.fetchImpl);
|
|
628
|
+
if (probe.isNexusDashboard) {
|
|
629
|
+
await deps.openBrowser(url);
|
|
630
|
+
deps.showToast({
|
|
631
|
+
title: "\u26A1 Nexus Web Dashboard",
|
|
632
|
+
message: `Opened ${url} \u2014 a dashboard is already running there.`,
|
|
633
|
+
variant: "success",
|
|
634
|
+
duration: 6000
|
|
635
|
+
});
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (probe.listening) {
|
|
639
|
+
deps.showToast({
|
|
640
|
+
title: "Nexus Web Dashboard \u2014 port in use",
|
|
641
|
+
message: [
|
|
642
|
+
`${probe.detail}.`,
|
|
643
|
+
"",
|
|
644
|
+
"No browser was opened: that address is not the dashboard.",
|
|
645
|
+
"Run /nexus web with a different port, e.g. /nexus web 4748."
|
|
646
|
+
].join(`
|
|
647
|
+
`),
|
|
648
|
+
variant: "error",
|
|
649
|
+
duration: 12000
|
|
650
|
+
});
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
deps.showToast({
|
|
654
|
+
title: "\u26A1 Nexus Web Dashboard",
|
|
655
|
+
message: [
|
|
656
|
+
`No dashboard is running on ${url} (${probe.detail}), and no browser was opened.`,
|
|
657
|
+
"",
|
|
658
|
+
"The dashboard server cannot be started from the TUI: it runs in the OpenCode",
|
|
659
|
+
"server process, next to the orchestrator that feeds it, and the TUI has no",
|
|
660
|
+
"handle on either. So start it from the agent \u2014 this is the one call:",
|
|
661
|
+
"",
|
|
662
|
+
` nexus.dashboard.start(port=${port}, host="${host}")`,
|
|
663
|
+
"",
|
|
664
|
+
`Then re-run /nexus web and it will open ${url} for you.`
|
|
665
|
+
].join(`
|
|
666
|
+
`),
|
|
667
|
+
variant: "info",
|
|
668
|
+
duration: 15000
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
async function openInBrowser(url) {
|
|
672
|
+
try {
|
|
673
|
+
const { exec } = await import("child_process");
|
|
674
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
675
|
+
exec(`${command} ${url}`);
|
|
676
|
+
} catch {}
|
|
677
|
+
}
|
|
529
678
|
var tui_default = define({
|
|
530
679
|
id: "nexus.cli",
|
|
531
680
|
setup(context) {
|
|
@@ -617,10 +766,17 @@ var tui_default = define({
|
|
|
617
766
|
variant: "success"
|
|
618
767
|
});
|
|
619
768
|
};
|
|
769
|
+
const runWebDashboard = async (input) => {
|
|
770
|
+
await handleWebDashboard(input, {
|
|
771
|
+
dashboard: configManager.getConfig().dashboard,
|
|
772
|
+
showToast: (options) => context.ui.toast.show(options),
|
|
773
|
+
openBrowser: openInBrowser
|
|
774
|
+
});
|
|
775
|
+
};
|
|
620
776
|
const handleDashboard = async () => {
|
|
621
777
|
const config = configManager.getConfig();
|
|
622
778
|
const lines = [
|
|
623
|
-
"\u26A1 Nexus
|
|
779
|
+
"\u26A1 Nexus Overview",
|
|
624
780
|
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
625
781
|
"",
|
|
626
782
|
"\uD83E\uDD16 Agent Models:"
|
|
@@ -644,6 +800,7 @@ var tui_default = define({
|
|
|
644
800
|
lines.push(" /nexus status - Show config summary");
|
|
645
801
|
lines.push(" /nexus config - Configure models & budget");
|
|
646
802
|
lines.push(" /nexus model - Select model for role");
|
|
803
|
+
lines.push(" /nexus web - Open the web dashboard, if one is running");
|
|
647
804
|
lines.push(" /nexus reset - Reset to defaults");
|
|
648
805
|
lines.push("");
|
|
649
806
|
lines.push("\uD83D\uDD27 Tools (use in agent prompt):");
|
|
@@ -651,8 +808,14 @@ var tui_default = define({
|
|
|
651
808
|
lines.push(" nexus.agents - List spawned agents");
|
|
652
809
|
lines.push(" nexus.costs - Cost report");
|
|
653
810
|
lines.push(" nexus.spawn - Spawn a sub-agent");
|
|
811
|
+
lines.push(" nexus.dashboard.start - Start the web dashboard server");
|
|
812
|
+
lines.push("");
|
|
813
|
+
lines.push("\uD83D\uDCCA Web Dashboard:");
|
|
814
|
+
lines.push(` Enabled: ${config.dashboard.enabled ? "\u2705" : "\u274C"}`);
|
|
815
|
+
lines.push(` Address: http://${config.dashboard.host}:${config.dashboard.port} (when running)`);
|
|
816
|
+
lines.push(" Not running? Ask the agent for nexus.dashboard.start, then /nexus web.");
|
|
654
817
|
context.ui.toast.show({
|
|
655
|
-
title: "Nexus
|
|
818
|
+
title: "Nexus Overview",
|
|
656
819
|
message: lines.join(`
|
|
657
820
|
`),
|
|
658
821
|
variant: "info",
|
|
@@ -699,30 +862,7 @@ var tui_default = define({
|
|
|
699
862
|
break;
|
|
700
863
|
case "web":
|
|
701
864
|
case "w":
|
|
702
|
-
|
|
703
|
-
const port = parts[1] ? parseInt(parts[1]) : 4747;
|
|
704
|
-
const host = parts[2] || "127.0.0.1";
|
|
705
|
-
context.ui.toast.show({
|
|
706
|
-
title: "\u26A1 Nexus Web Dashboard",
|
|
707
|
-
message: [
|
|
708
|
-
`Starting dashboard on port ${port}...`,
|
|
709
|
-
"",
|
|
710
|
-
`Ask the agent to run: nexus.dashboard.start(port=${port})`,
|
|
711
|
-
"",
|
|
712
|
-
`Or type: nexus.dashboard.start with port=${port} in your next message`,
|
|
713
|
-
"",
|
|
714
|
-
`Then open: http://${host}:${port}`
|
|
715
|
-
].join(`
|
|
716
|
-
`),
|
|
717
|
-
variant: "success",
|
|
718
|
-
duration: 8000
|
|
719
|
-
});
|
|
720
|
-
try {
|
|
721
|
-
const { exec } = await import("child_process");
|
|
722
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
723
|
-
exec(`${cmd} http://${host}:${port}`);
|
|
724
|
-
} catch {}
|
|
725
|
-
}
|
|
865
|
+
await runWebDashboard(parts.slice(1).join(" "));
|
|
726
866
|
break;
|
|
727
867
|
case "model":
|
|
728
868
|
case "m":
|
|
@@ -783,7 +923,7 @@ var tui_default = define({
|
|
|
783
923
|
},
|
|
784
924
|
{
|
|
785
925
|
id: "nexus.dashboard",
|
|
786
|
-
title: "Nexus
|
|
926
|
+
title: "Nexus Overview (config, budget, dashboard status)",
|
|
787
927
|
group: "Nexus",
|
|
788
928
|
palette: true,
|
|
789
929
|
slash: { name: "nexus-dashboard", aliases: ["nd"], arguments: true },
|
|
@@ -795,31 +935,15 @@ var tui_default = define({
|
|
|
795
935
|
},
|
|
796
936
|
{
|
|
797
937
|
id: "nexus.web",
|
|
798
|
-
title: "
|
|
938
|
+
title: "Open the Nexus Web Dashboard",
|
|
939
|
+
description: "Open the dashboard in your browser if one is already serving; otherwise say how to start it",
|
|
799
940
|
group: "Nexus",
|
|
800
941
|
palette: true,
|
|
801
942
|
slash: { name: "nexus-web", aliases: ["nw"], arguments: true },
|
|
802
943
|
enabled: () => true,
|
|
803
944
|
suggested: true,
|
|
804
945
|
run: async (input) => {
|
|
805
|
-
|
|
806
|
-
const host = "127.0.0.1";
|
|
807
|
-
context.ui.toast.show({
|
|
808
|
-
title: "\u26A1 Nexus Web Dashboard",
|
|
809
|
-
message: [
|
|
810
|
-
`Port: ${port} Host: ${host}`,
|
|
811
|
-
"",
|
|
812
|
-
"To start the dashboard, ask the agent:",
|
|
813
|
-
` nexus.dashboard.start(port=${port}, host="${host}")`,
|
|
814
|
-
"",
|
|
815
|
-
`Then open: http://${host}:${port}`,
|
|
816
|
-
"",
|
|
817
|
-
"Tip: The dashboard shows live agent status, costs, and history."
|
|
818
|
-
].join(`
|
|
819
|
-
`),
|
|
820
|
-
variant: "info",
|
|
821
|
-
duration: 12000
|
|
822
|
-
});
|
|
946
|
+
await runWebDashboard(input);
|
|
823
947
|
}
|
|
824
948
|
},
|
|
825
949
|
{
|
|
@@ -1064,7 +1188,11 @@ var tui_default = define({
|
|
|
1064
1188
|
export {
|
|
1065
1189
|
collectSidebarAgents,
|
|
1066
1190
|
tui_default as default,
|
|
1191
|
+
handleWebDashboard,
|
|
1067
1192
|
mergeSidebarAgents,
|
|
1193
|
+
openInBrowser,
|
|
1194
|
+
parseWebDashboardTarget,
|
|
1195
|
+
probeDashboard,
|
|
1068
1196
|
sidebarAgentFor,
|
|
1069
1197
|
sidebarChildSessionIDs
|
|
1070
1198
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serkanalgur/opencode-nexus",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Adaptive Multi-Agent Orchestration with Cost Intelligence for OpenCode V2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -44,18 +44,17 @@
|
|
|
44
44
|
"dev": "bun run --watch src/index.ts",
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
46
46
|
"test": "bun test",
|
|
47
|
-
"lint": "biome check src/",
|
|
48
|
-
"format": "biome format --write src/",
|
|
47
|
+
"lint": "biome check src/ test/ && tsc --noEmit && bun -e 'const page = \"dashboard/index.html\"; const html = await Bun.file(page).text(); const inline = [...html.matchAll(/<script(?![^>]*\\bsrc=)[^>]*>([\\s\\S]*?)<\\/script>/g)]; if (inline.length === 0) throw new Error(page + \" has no inline <script> to check\"); inline.forEach(m => { new Function(m[1]); }); console.log(\"lint: \" + page + \" - \" + inline.length + \" inline script(s) parse clean\");'",
|
|
49
48
|
"prepublishOnly": "bun run build"
|
|
50
49
|
},
|
|
51
50
|
"dependencies": {
|
|
52
51
|
"@opencode/plugin": "latest"
|
|
53
52
|
},
|
|
54
53
|
"devDependencies": {
|
|
54
|
+
"@biomejs/biome": "latest",
|
|
55
55
|
"@opentui/core": "^0.5.8",
|
|
56
56
|
"@opentui/solid": "^0.5.8",
|
|
57
57
|
"@types/bun": "latest",
|
|
58
|
-
"biome": "latest",
|
|
59
58
|
"bun-types": "^1.4.2",
|
|
60
59
|
"solid-js": "^1.9.0",
|
|
61
60
|
"typescript": "^5.5.0"
|