opencode-hub 1.0.7 → 1.0.8
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/oc-tui.js +93 -28
- package/package.json +1 -1
package/oc-tui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { Database } from "bun:sqlite";
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, unlinkSync } from "fs";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { join, dirname } from "path";
|
|
6
6
|
import { homedir } from "os";
|
|
@@ -20,6 +20,30 @@ var PLUGINS_DIR = join(CONFIG_DIR, "plugin");
|
|
|
20
20
|
// Folder name helper: <creator>/<repo-name> to avoid collisions
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
|
|
23
|
+
function loadNpmPlugins() {
|
|
24
|
+
var ocPath = join(CONFIG_DIR, "opencode.json");
|
|
25
|
+
if (!existsSync(ocPath)) return [];
|
|
26
|
+
try {
|
|
27
|
+
var raw = readFileSync(ocPath, "utf-8");
|
|
28
|
+
var stripped = raw.replace(/\/\/[^\n]*/g, "");
|
|
29
|
+
var oc = JSON.parse(stripped);
|
|
30
|
+
var plugins = oc.plugin || [];
|
|
31
|
+
return plugins
|
|
32
|
+
.filter(function(p) { return typeof p === "string"; })
|
|
33
|
+
.map(function(p) {
|
|
34
|
+
var name = p.replace(/@[^@\/]+$/, "") || p;
|
|
35
|
+
var version = "";
|
|
36
|
+
try {
|
|
37
|
+
var pkgPath = join(CONFIG_DIR, "node_modules", name, "package.json");
|
|
38
|
+
if (existsSync(pkgPath)) {
|
|
39
|
+
version = JSON.parse(readFileSync(pkgPath, "utf-8")).version || "";
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
return { name: name, version: version, raw: p };
|
|
43
|
+
});
|
|
44
|
+
} catch { return []; }
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
function getFolderName(plugin) {
|
|
24
48
|
var match = (plugin.url || "").match(/github\.com\/([^\/]+)\/([^\/\.]+)/);
|
|
25
49
|
if (match) return match[1] + "/" + plugin.name;
|
|
@@ -74,20 +98,6 @@ function checkForUpdates() {
|
|
|
74
98
|
execSync("npm install -g opencode-ai@latest", { stdio: "inherit", timeout: 120000 });
|
|
75
99
|
process.stderr.write("\x1b[32m > Updated to " + latest + "\x1b[0m\n\n");
|
|
76
100
|
} catch (e) {}
|
|
77
|
-
|
|
78
|
-
// Auto-update NPM plugins
|
|
79
|
-
try {
|
|
80
|
-
var ocPath = join(CONFIG_DIR, "opencode.json");
|
|
81
|
-
if (existsSync(ocPath)) {
|
|
82
|
-
var oc = JSON.parse(readFileSync(ocPath, "utf-8"));
|
|
83
|
-
var pluginsArr = oc.plugin || oc.plugins || [];
|
|
84
|
-
var npmPlugs = pluginsArr.filter(function(p) { return typeof p === "string" && p.includes("@latest") && !p.startsWith("."); });
|
|
85
|
-
if (npmPlugs.length > 0) {
|
|
86
|
-
process.stderr.write("\x1b[33m > Auto-updating NPM plugins: " + npmPlugs.join(", ") + "\x1b[0m\n");
|
|
87
|
-
execSync("npm install --no-save " + npmPlugs.join(" "), { cwd: CONFIG_DIR, stdio: "ignore", timeout: 60000 });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} catch (e) {}
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
checkForUpdates();
|
|
@@ -224,10 +234,13 @@ function buildPluginList() {
|
|
|
224
234
|
var remoteHead = "";
|
|
225
235
|
var subject = "";
|
|
226
236
|
var updateAvail = false;
|
|
237
|
+
var latestTag = "";
|
|
238
|
+
var enabled = p.enabled !== false;
|
|
227
239
|
|
|
228
240
|
if (installed) {
|
|
229
241
|
localHead = gitText(["git", "rev-parse", "HEAD"], dir);
|
|
230
242
|
subject = gitText(["git", "log", "-1", "--format=%s"], dir);
|
|
243
|
+
latestTag = gitText(["git", "describe", "--tags", "--abbrev=0"], dir);
|
|
231
244
|
}
|
|
232
245
|
|
|
233
246
|
list.push({
|
|
@@ -235,10 +248,12 @@ function buildPluginList() {
|
|
|
235
248
|
folderName: folderName,
|
|
236
249
|
url: p.url,
|
|
237
250
|
autoUpdate: p.autoUpdate !== false,
|
|
251
|
+
enabled: enabled,
|
|
238
252
|
installed: installed,
|
|
239
253
|
deployed: deployed,
|
|
240
254
|
localHead: localHead,
|
|
241
255
|
remoteHead: remoteHead,
|
|
256
|
+
latestTag: latestTag,
|
|
242
257
|
subject: subject,
|
|
243
258
|
updateAvail: updateAvail,
|
|
244
259
|
hasBuild: !!(p.build || p.bundle),
|
|
@@ -293,6 +308,10 @@ function runPluginUpdate(pluginItem) {
|
|
|
293
308
|
try { execSync(repo.install.join(" "), { cwd: dir, timeout: 120000, stdio: "ignore" }); }
|
|
294
309
|
catch (e) { return "Install failed"; }
|
|
295
310
|
}
|
|
311
|
+
if (repo.postInstall) {
|
|
312
|
+
try { execSync(repo.postInstall.join(" "), { cwd: dir, timeout: 120000, stdio: "ignore" }); }
|
|
313
|
+
catch (e) { return "Post-install failed"; }
|
|
314
|
+
}
|
|
296
315
|
if (repo.build) {
|
|
297
316
|
try { execSync(repo.build.join(" "), { cwd: dir, timeout: 120000, stdio: "ignore" }); }
|
|
298
317
|
catch (e) { return "Build failed"; }
|
|
@@ -348,6 +367,7 @@ function showCur() { process.stderr.write(E + "?25h"); }
|
|
|
348
367
|
|
|
349
368
|
var items = buildList();
|
|
350
369
|
var pluginItems = buildPluginList();
|
|
370
|
+
var npmPluginItems = loadNpmPlugins();
|
|
351
371
|
var cursor = 0;
|
|
352
372
|
var pcursor = 0; // plugin page cursor
|
|
353
373
|
var mode = "list"; // "list" | "actions" | "input" | "pactions"
|
|
@@ -394,16 +414,22 @@ function getActions(item) {
|
|
|
394
414
|
|
|
395
415
|
function getPluginActions(pitem) {
|
|
396
416
|
var a = [];
|
|
417
|
+
if (!pitem.enabled) {
|
|
418
|
+
a.push({ key: "enable-plugin", label: "Enable plugin" });
|
|
419
|
+
a.push({ key: "cancel", label: "Cancel" });
|
|
420
|
+
return a;
|
|
421
|
+
}
|
|
397
422
|
if (pitem.updateAvail || !pitem.deployed) {
|
|
398
423
|
a.push({ key: "update", label: "Update now" });
|
|
399
424
|
}
|
|
400
425
|
if (pitem.autoUpdate) {
|
|
401
|
-
a.push({ key: "disable-auto", label: "
|
|
426
|
+
a.push({ key: "disable-auto", label: "Set to manual update" });
|
|
402
427
|
} else {
|
|
403
428
|
a.push({ key: "enable-auto", label: "Enable auto-update" });
|
|
404
429
|
}
|
|
405
430
|
a.push({ key: "force-update", label: "Force rebuild & deploy" });
|
|
406
431
|
a.push({ key: "commits", label: "Select specific commit (Downgrade)" });
|
|
432
|
+
a.push({ key: "disable-plugin", label: "Disable plugin" });
|
|
407
433
|
a.push({ key: "cancel", label: "Cancel" });
|
|
408
434
|
return a;
|
|
409
435
|
}
|
|
@@ -627,23 +653,29 @@ function buildPluginItem(pushBody, i, pitem, nameW, cols, isSelected) {
|
|
|
627
653
|
var nameStyle = sel ? (BOLD + WHITE) : DIM;
|
|
628
654
|
|
|
629
655
|
var statusParts = [];
|
|
630
|
-
if (pitem.
|
|
656
|
+
if (!pitem.enabled) {
|
|
657
|
+
statusParts.push(RED + "disabled" + RST);
|
|
658
|
+
} else if (pitem.autoUpdate) {
|
|
631
659
|
statusParts.push(GREEN + "auto" + RST);
|
|
632
660
|
} else {
|
|
633
661
|
statusParts.push(YELLOW + "manual" + RST);
|
|
634
662
|
}
|
|
635
|
-
if (pitem.
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
663
|
+
if (pitem.enabled) {
|
|
664
|
+
if (pitem.updateAvail) {
|
|
665
|
+
statusParts.push(CYAN + "UPDATE" + RST);
|
|
666
|
+
} else if (pitem.deployed) {
|
|
667
|
+
statusParts.push(GRAY + "ok" + RST);
|
|
668
|
+
} else {
|
|
669
|
+
statusParts.push(RED + "missing" + RST);
|
|
670
|
+
}
|
|
641
671
|
}
|
|
642
672
|
|
|
643
673
|
var statusStr = statusParts.join(GRAY + " | " + RST);
|
|
644
|
-
var
|
|
674
|
+
var versionStr = pitem.latestTag
|
|
675
|
+
? (GRAY + pitem.latestTag + RST)
|
|
676
|
+
: (pitem.localHead ? (GRAY + pitem.localHead.substring(0, 7) + RST) : (GRAY + "---" + RST));
|
|
645
677
|
|
|
646
|
-
pushBody(" " + bg + arrow + nameStyle + pad(trunc(pitem.name, nameW), nameW) + RST + bg + " " + statusStr + " " +
|
|
678
|
+
pushBody(" " + bg + arrow + nameStyle + pad(trunc(pitem.name, nameW), nameW) + RST + bg + " " + statusStr + " " + versionStr + RST, isSelected);
|
|
647
679
|
|
|
648
680
|
if (sel) {
|
|
649
681
|
var subInfo = GRAY + " " + trunc(pitem.subject || pitem.url, cols - 10) + RST;
|
|
@@ -701,15 +733,18 @@ function buildPlugins(pushBody, pushFoot, cols, barW) {
|
|
|
701
733
|
return;
|
|
702
734
|
}
|
|
703
735
|
|
|
704
|
-
var autoCount = 0, manualCount = 0, updateCount = 0;
|
|
736
|
+
var autoCount = 0, manualCount = 0, updateCount = 0, disabledCount = 0;
|
|
705
737
|
for (var p of pluginItems) {
|
|
706
|
-
if (p.
|
|
738
|
+
if (!p.enabled) disabledCount++;
|
|
739
|
+
else if (p.autoUpdate) autoCount++; else manualCount++;
|
|
707
740
|
if (p.updateAvail) updateCount++;
|
|
708
741
|
}
|
|
709
742
|
|
|
710
743
|
pushBody(" " + MAGENTA + "#" + GRAY + " Plugins " +
|
|
711
744
|
DIM + "(" + autoCount + " auto, " + manualCount + " manual" +
|
|
745
|
+
(disabledCount > 0 ? ", " + RED + disabledCount + " disabled" + DIM : "") +
|
|
712
746
|
(updateCount > 0 ? ", " + CYAN + updateCount + " updates" + DIM : "") +
|
|
747
|
+
(npmPluginItems.length > 0 ? ", " + GRAY + npmPluginItems.length + " npm" + DIM : "") +
|
|
713
748
|
")" + RST, false);
|
|
714
749
|
|
|
715
750
|
if (!pluginFetched) {
|
|
@@ -720,6 +755,16 @@ function buildPlugins(pushBody, pushFoot, cols, barW) {
|
|
|
720
755
|
buildPluginItem(pushBody, i, pluginItems[i], nameW, cols, i === pcursor);
|
|
721
756
|
}
|
|
722
757
|
|
|
758
|
+
if (npmPluginItems.length > 0) {
|
|
759
|
+
pushBody("", false);
|
|
760
|
+
pushBody(" " + MAGENTA + "#" + GRAY + " npm plugins" + RST, false);
|
|
761
|
+
for (var ni = 0; ni < npmPluginItems.length; ni++) {
|
|
762
|
+
var np = npmPluginItems[ni];
|
|
763
|
+
var nvstr = np.version ? (GRAY + "v" + np.version + RST) : (GRAY + "not installed" + RST);
|
|
764
|
+
pushBody(" " + DIM + np.name + RST + " " + nvstr, false);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
723
768
|
pushBody("", false);
|
|
724
769
|
|
|
725
770
|
if (message) {
|
|
@@ -768,7 +813,7 @@ function render() {
|
|
|
768
813
|
pushHead("");
|
|
769
814
|
pushHead(" " + BOLD + CYAN + " OpenCode" + RST + GRAY + " Launcher" + RST);
|
|
770
815
|
pushHead(" " + GRAY + "-".repeat(barW) + RST);
|
|
771
|
-
var showPluginsTab = pluginItems.length > 0;
|
|
816
|
+
var showPluginsTab = pluginItems.length > 0 || npmPluginItems.length > 0;
|
|
772
817
|
var projTab = page === "projects" ? (BOLD + WHITE + BG_SEL + " Projects " + RST) : (GRAY + " Projects " + RST);
|
|
773
818
|
var plugTab = showPluginsTab ? (page === "plugins" ? (BOLD + WHITE + BG_SEL + " Plugins " + RST) : (GRAY + " Plugins " + RST)) : "";
|
|
774
819
|
pushHead(" " + projTab + (showPluginsTab ? " " + plugTab + " " + DIM + "<- ->" + RST : ""));
|
|
@@ -951,6 +996,26 @@ function handlePluginKey(key) {
|
|
|
951
996
|
flash(pitem.name + ": auto-update " + (newVal ? "ON" : "OFF"));
|
|
952
997
|
mode = "list";
|
|
953
998
|
}
|
|
999
|
+
else if (action === "disable-plugin") {
|
|
1000
|
+
var plugins = loadPlugins();
|
|
1001
|
+
var match = plugins.find(function(r) { return r.name === pitem.name; });
|
|
1002
|
+
if (match) { match.enabled = false; savePlugins(plugins); }
|
|
1003
|
+
var deployedPath = join(PLUGINS_DIR, pitem.pluginFile);
|
|
1004
|
+
if (existsSync(deployedPath)) { try { unlinkSync(deployedPath); } catch {} }
|
|
1005
|
+
pluginItems = buildPluginList();
|
|
1006
|
+
if (pcursor >= pluginItems.length) pcursor = Math.max(0, pluginItems.length - 1);
|
|
1007
|
+
flash(pitem.name + " disabled. Restart OpenCode to unload.");
|
|
1008
|
+
mode = "list";
|
|
1009
|
+
}
|
|
1010
|
+
else if (action === "enable-plugin") {
|
|
1011
|
+
var plugins = loadPlugins();
|
|
1012
|
+
var match = plugins.find(function(r) { return r.name === pitem.name; });
|
|
1013
|
+
if (match) { delete match.enabled; savePlugins(plugins); }
|
|
1014
|
+
pluginItems = buildPluginList();
|
|
1015
|
+
if (pcursor >= pluginItems.length) pcursor = Math.max(0, pluginItems.length - 1);
|
|
1016
|
+
flash(pitem.name + " enabled. Use Update to deploy.");
|
|
1017
|
+
mode = "list";
|
|
1018
|
+
}
|
|
954
1019
|
else if (action === "commits") {
|
|
955
1020
|
var dir = join(REPOS_DIR, pitem.folderName);
|
|
956
1021
|
if (!existsSync(dir)) { flash("Not installed locally yet"); mode = "list"; return; }
|