plugin-updater 1.0.36 → 1.0.37

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/dist/index.js +52 -3
  2. package/package.json +1 -1
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-updater",
3
- "version": "1.0.36",
3
+ "version": "1.0.37",
4
4
  "description": "Plugin lifecycle manager for OpenCode and Claude Code launchers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",