@serkanalgur/opencode-nexus 2.6.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 +3950 -174
- package/dist/tui.js +173 -47
- package/package.json +3 -4
package/dist/tui.js
CHANGED
|
@@ -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,6 +320,7 @@ 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() {
|
|
@@ -528,6 +541,140 @@ function mergeSidebarAgents(agents, polled, childIDs) {
|
|
|
528
541
|
}
|
|
529
542
|
return next.filter((a) => a.sessionID && childIDs.includes(a.sessionID) || a.status === "completed" || a.status === "failed");
|
|
530
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
|
+
}
|
|
531
678
|
var tui_default = define({
|
|
532
679
|
id: "nexus.cli",
|
|
533
680
|
setup(context) {
|
|
@@ -619,10 +766,17 @@ var tui_default = define({
|
|
|
619
766
|
variant: "success"
|
|
620
767
|
});
|
|
621
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
|
+
};
|
|
622
776
|
const handleDashboard = async () => {
|
|
623
777
|
const config = configManager.getConfig();
|
|
624
778
|
const lines = [
|
|
625
|
-
"\u26A1 Nexus
|
|
779
|
+
"\u26A1 Nexus Overview",
|
|
626
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",
|
|
627
781
|
"",
|
|
628
782
|
"\uD83E\uDD16 Agent Models:"
|
|
@@ -646,6 +800,7 @@ var tui_default = define({
|
|
|
646
800
|
lines.push(" /nexus status - Show config summary");
|
|
647
801
|
lines.push(" /nexus config - Configure models & budget");
|
|
648
802
|
lines.push(" /nexus model - Select model for role");
|
|
803
|
+
lines.push(" /nexus web - Open the web dashboard, if one is running");
|
|
649
804
|
lines.push(" /nexus reset - Reset to defaults");
|
|
650
805
|
lines.push("");
|
|
651
806
|
lines.push("\uD83D\uDD27 Tools (use in agent prompt):");
|
|
@@ -653,8 +808,14 @@ var tui_default = define({
|
|
|
653
808
|
lines.push(" nexus.agents - List spawned agents");
|
|
654
809
|
lines.push(" nexus.costs - Cost report");
|
|
655
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.");
|
|
656
817
|
context.ui.toast.show({
|
|
657
|
-
title: "Nexus
|
|
818
|
+
title: "Nexus Overview",
|
|
658
819
|
message: lines.join(`
|
|
659
820
|
`),
|
|
660
821
|
variant: "info",
|
|
@@ -701,30 +862,7 @@ var tui_default = define({
|
|
|
701
862
|
break;
|
|
702
863
|
case "web":
|
|
703
864
|
case "w":
|
|
704
|
-
|
|
705
|
-
const port = parts[1] ? parseInt(parts[1]) : 4747;
|
|
706
|
-
const host = parts[2] || "127.0.0.1";
|
|
707
|
-
context.ui.toast.show({
|
|
708
|
-
title: "\u26A1 Nexus Web Dashboard",
|
|
709
|
-
message: [
|
|
710
|
-
`Starting dashboard on port ${port}...`,
|
|
711
|
-
"",
|
|
712
|
-
`Ask the agent to run: nexus.dashboard.start(port=${port})`,
|
|
713
|
-
"",
|
|
714
|
-
`Or type: nexus.dashboard.start with port=${port} in your next message`,
|
|
715
|
-
"",
|
|
716
|
-
`Then open: http://${host}:${port}`
|
|
717
|
-
].join(`
|
|
718
|
-
`),
|
|
719
|
-
variant: "success",
|
|
720
|
-
duration: 8000
|
|
721
|
-
});
|
|
722
|
-
try {
|
|
723
|
-
const { exec } = await import("child_process");
|
|
724
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
725
|
-
exec(`${cmd} http://${host}:${port}`);
|
|
726
|
-
} catch {}
|
|
727
|
-
}
|
|
865
|
+
await runWebDashboard(parts.slice(1).join(" "));
|
|
728
866
|
break;
|
|
729
867
|
case "model":
|
|
730
868
|
case "m":
|
|
@@ -785,7 +923,7 @@ var tui_default = define({
|
|
|
785
923
|
},
|
|
786
924
|
{
|
|
787
925
|
id: "nexus.dashboard",
|
|
788
|
-
title: "Nexus
|
|
926
|
+
title: "Nexus Overview (config, budget, dashboard status)",
|
|
789
927
|
group: "Nexus",
|
|
790
928
|
palette: true,
|
|
791
929
|
slash: { name: "nexus-dashboard", aliases: ["nd"], arguments: true },
|
|
@@ -797,31 +935,15 @@ var tui_default = define({
|
|
|
797
935
|
},
|
|
798
936
|
{
|
|
799
937
|
id: "nexus.web",
|
|
800
|
-
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",
|
|
801
940
|
group: "Nexus",
|
|
802
941
|
palette: true,
|
|
803
942
|
slash: { name: "nexus-web", aliases: ["nw"], arguments: true },
|
|
804
943
|
enabled: () => true,
|
|
805
944
|
suggested: true,
|
|
806
945
|
run: async (input) => {
|
|
807
|
-
|
|
808
|
-
const host = "127.0.0.1";
|
|
809
|
-
context.ui.toast.show({
|
|
810
|
-
title: "\u26A1 Nexus Web Dashboard",
|
|
811
|
-
message: [
|
|
812
|
-
`Port: ${port} Host: ${host}`,
|
|
813
|
-
"",
|
|
814
|
-
"To start the dashboard, ask the agent:",
|
|
815
|
-
` nexus.dashboard.start(port=${port}, host="${host}")`,
|
|
816
|
-
"",
|
|
817
|
-
`Then open: http://${host}:${port}`,
|
|
818
|
-
"",
|
|
819
|
-
"Tip: The dashboard shows live agent status, costs, and history."
|
|
820
|
-
].join(`
|
|
821
|
-
`),
|
|
822
|
-
variant: "info",
|
|
823
|
-
duration: 12000
|
|
824
|
-
});
|
|
946
|
+
await runWebDashboard(input);
|
|
825
947
|
}
|
|
826
948
|
},
|
|
827
949
|
{
|
|
@@ -1066,7 +1188,11 @@ var tui_default = define({
|
|
|
1066
1188
|
export {
|
|
1067
1189
|
collectSidebarAgents,
|
|
1068
1190
|
tui_default as default,
|
|
1191
|
+
handleWebDashboard,
|
|
1069
1192
|
mergeSidebarAgents,
|
|
1193
|
+
openInBrowser,
|
|
1194
|
+
parseWebDashboardTarget,
|
|
1195
|
+
probeDashboard,
|
|
1070
1196
|
sidebarAgentFor,
|
|
1071
1197
|
sidebarChildSessionIDs
|
|
1072
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"
|