plugin-updater 1.0.36 → 1.0.38
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/dist/index.d.ts +2 -0
- package/dist/index.js +80 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -18,5 +18,7 @@ export declare function uninstallNpmPlugin(name: string, configDir: string): str
|
|
|
18
18
|
export declare function updateNpmPlugin(name: string, configDir: string, updateInterval?: number): string;
|
|
19
19
|
export declare function updatePluginPublic(pluginName: string, gitUrl: string, branch?: string, commitHash?: string): Promise<void | object>;
|
|
20
20
|
export declare function earlyLaunch(configDir: string, plugins: Plugin[]): Promise<void | object>;
|
|
21
|
+
export declare function getPluginsPath(configDir: string): string;
|
|
22
|
+
export declare function getPlugins(configDir: string): Plugin[];
|
|
21
23
|
export declare function activate(opencodeHookInput?: unknown): Promise<void | object>;
|
|
22
24
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -416,6 +416,7 @@ async function deployToExecutionDir(pluginName, executionPath, changed, configDi
|
|
|
416
416
|
writeLog(`Copy failed for ${pluginName}: ${err.message}`, true);
|
|
417
417
|
}
|
|
418
418
|
applyClaudeManifest(sourceDir, configDir, pluginName);
|
|
419
|
+
await startDeclaredDaemon(sourceDir, configDir, pluginName);
|
|
419
420
|
// Claude Code never imports deployed plugin files, so under claude the
|
|
420
421
|
// updater is the runtime and invokes the plugin's activate() itself
|
|
421
422
|
if (getAppName() === "claude") {
|
|
@@ -464,14 +465,62 @@ function applyClaudeManifest(sourceDir, configDir, pluginName) {
|
|
|
464
465
|
settings.env = env;
|
|
465
466
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
466
467
|
}
|
|
467
|
-
if (manifest.daemon?.script) {
|
|
468
|
-
writeLog(`${pluginName} defines a daemon (${manifest.daemon.script}) which the updater does not manage yet`, true);
|
|
469
|
-
}
|
|
470
468
|
}
|
|
471
469
|
catch (e) {
|
|
472
470
|
writeLog(`claudeHub manifest handling failed for ${pluginName}: ${e.message}`, true);
|
|
473
471
|
}
|
|
474
472
|
}
|
|
473
|
+
async function isDaemonHealthy(url) {
|
|
474
|
+
try {
|
|
475
|
+
const controller = new AbortController();
|
|
476
|
+
const timer = setTimeout(() => controller.abort(), 1500);
|
|
477
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
478
|
+
clearTimeout(timer);
|
|
479
|
+
return res.ok;
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// idempotent: health-check the declared endpoint, spawn detached only if down.
|
|
486
|
+
// the daemon outlives this process so the proxy persists across the session.
|
|
487
|
+
async function startDeclaredDaemon(sourceDir, configDir, pluginName) {
|
|
488
|
+
try {
|
|
489
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(sourceDir, "package.json"), "utf8"));
|
|
490
|
+
const daemon = pkg.claudeHub?.daemon;
|
|
491
|
+
if (!daemon?.script)
|
|
492
|
+
return;
|
|
493
|
+
const healthUrl = daemon.healthCheckUrl
|
|
494
|
+
|| (daemon.port ? `http://127.0.0.1:${daemon.port}/health` : "");
|
|
495
|
+
if (healthUrl && (await isDaemonHealthy(healthUrl))) {
|
|
496
|
+
writeLog(`Daemon for ${pluginName} already running`);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const scriptPath = path.join(sourceDir, daemon.script);
|
|
500
|
+
if (!fs.existsSync(scriptPath)) {
|
|
501
|
+
writeLog(`Daemon script missing for ${pluginName}: ${scriptPath}`, true);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const runtime = daemon.runtime || "node";
|
|
505
|
+
const { spawn } = await import("child_process");
|
|
506
|
+
const child = spawn(runtime, [scriptPath], {
|
|
507
|
+
cwd: sourceDir,
|
|
508
|
+
detached: true,
|
|
509
|
+
stdio: "ignore",
|
|
510
|
+
env: {
|
|
511
|
+
...process.env,
|
|
512
|
+
HUB_CONFIG_DIR: configDir,
|
|
513
|
+
HUB_APP_NAME: getAppName() === "claude" ? "Claude Code" : "OpenCode",
|
|
514
|
+
...(daemon.port ? { HUB_PROXY_PORT: String(daemon.port) } : {}),
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
child.unref();
|
|
518
|
+
writeLog(`Started daemon for ${pluginName} (${runtime} ${daemon.script})`);
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
writeLog(`Daemon start failed for ${pluginName}: ${e.message}`, true);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
475
524
|
async function pluginUpdaterEntry(input) {
|
|
476
525
|
const appName = getAppName();
|
|
477
526
|
const configDir = getAppConfigDir(appName);
|
|
@@ -550,6 +599,32 @@ export async function earlyLaunch(configDir, plugins) {
|
|
|
550
599
|
}
|
|
551
600
|
}
|
|
552
601
|
}
|
|
602
|
+
export function getPluginsPath(configDir) {
|
|
603
|
+
if (isOpencodeHookInvocation(configDir))
|
|
604
|
+
return "";
|
|
605
|
+
const preferred = path.join(configDir, "config", "plugins.json");
|
|
606
|
+
const fallback = path.join(configDir, "plugins.json");
|
|
607
|
+
if (fs.existsSync(preferred))
|
|
608
|
+
return preferred;
|
|
609
|
+
if (fs.existsSync(fallback))
|
|
610
|
+
return fallback;
|
|
611
|
+
return preferred;
|
|
612
|
+
}
|
|
613
|
+
// single source of truth for the git-plugin list; consumers (loaders, TUI)
|
|
614
|
+
// must read through this rather than touching plugins.json directly
|
|
615
|
+
export function getPlugins(configDir) {
|
|
616
|
+
if (isOpencodeHookInvocation(configDir))
|
|
617
|
+
return [];
|
|
618
|
+
const file = getPluginsPath(configDir);
|
|
619
|
+
try {
|
|
620
|
+
if (fs.existsSync(file))
|
|
621
|
+
return JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
622
|
+
}
|
|
623
|
+
catch (e) {
|
|
624
|
+
writeLog(`Failed to parse ${file}: ${e.message}`, true);
|
|
625
|
+
}
|
|
626
|
+
return [];
|
|
627
|
+
}
|
|
553
628
|
export async function activate(opencodeHookInput) {
|
|
554
629
|
// module load below calls activate() with no argument; opencode passes a
|
|
555
630
|
// context object when re-invoking the export — return an inert plugin instance
|
|
@@ -558,17 +633,8 @@ export async function activate(opencodeHookInput) {
|
|
|
558
633
|
const appName = getAppName();
|
|
559
634
|
const configDir = getAppConfigDir(appName);
|
|
560
635
|
writeLog(`Plugin updater activating for ${appName}`);
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
if (fs.existsSync(pluginsJsonPath)) {
|
|
564
|
-
try {
|
|
565
|
-
gitPlugins = JSON.parse(fs.readFileSync(pluginsJsonPath, "utf-8"));
|
|
566
|
-
writeLog(`Found ${gitPlugins.length} git plugins in plugins.json`);
|
|
567
|
-
}
|
|
568
|
-
catch (e) {
|
|
569
|
-
writeLog(`Failed to parse plugins.json: ${e.message}`, true);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
636
|
+
const gitPlugins = getPlugins(configDir);
|
|
637
|
+
writeLog(`Found ${gitPlugins.length} git plugins in plugins.json`);
|
|
572
638
|
await earlyLaunch(configDir, gitPlugins);
|
|
573
639
|
}
|
|
574
640
|
// consumers like the loader TUI import this module for its API only — running
|