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.
Files changed (2) hide show
  1. package/oc-tui.js +93 -28
  2. 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: "Disable auto-update" });
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.autoUpdate) {
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.updateAvail) {
636
- statusParts.push(CYAN + "UPDATE" + RST);
637
- } else if (pitem.deployed) {
638
- statusParts.push(GRAY + "ok" + RST);
639
- } else {
640
- statusParts.push(RED + "missing" + RST);
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 commitStr = pitem.localHead ? (GRAY + pitem.localHead.substring(0, 7) + RST) : (GRAY + "---" + RST);
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 + " " + commitStr + RST, isSelected);
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.autoUpdate) autoCount++; else manualCount++;
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; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-hub",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "TUI launcher for OpenCode - project switcher and plugin manager with oc command",
5
5
  "main": "plugin.js",
6
6
  "type": "module",