plugin-updater 1.0.35 → 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 +61 -4
  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") {
@@ -423,7 +424,15 @@ async function deployToExecutionDir(pluginName, executionPath, changed, configDi
423
424
  const deployed = await import(pluginExecutionFile);
424
425
  if (typeof deployed.activate === "function") {
425
426
  writeLog(`Activating ${pluginName}`);
426
- await deployed.activate();
427
+ // tells the plugin the updater is the caller, so it must not start
428
+ // another earlyLaunch and recurse back into the updater
429
+ process.env.PLUGIN_UPDATER_ACTIVATION = "1";
430
+ try {
431
+ await deployed.activate();
432
+ }
433
+ finally {
434
+ delete process.env.PLUGIN_UPDATER_ACTIVATION;
435
+ }
427
436
  writeLog(`Activated ${pluginName}`);
428
437
  }
429
438
  }
@@ -456,14 +465,62 @@ function applyClaudeManifest(sourceDir, configDir, pluginName) {
456
465
  settings.env = env;
457
466
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
458
467
  }
459
- if (manifest.daemon?.script) {
460
- writeLog(`${pluginName} defines a daemon (${manifest.daemon.script}) which the updater does not manage yet`, true);
461
- }
462
468
  }
463
469
  catch (e) {
464
470
  writeLog(`claudeHub manifest handling failed for ${pluginName}: ${e.message}`, true);
465
471
  }
466
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
+ }
467
524
  async function pluginUpdaterEntry(input) {
468
525
  const appName = getAppName();
469
526
  const configDir = getAppConfigDir(appName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-updater",
3
- "version": "1.0.35",
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",