@scotthamilton77/sidekick 0.0.8-alpha.6 → 0.0.8-alpha.7

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 (3) hide show
  1. package/dist/bin.js +177 -86
  2. package/dist/daemon.js +66 -37
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -41408,7 +41408,7 @@ var require_abort_controller = __commonJS({
41408
41408
  "use strict";
41409
41409
  Object.defineProperty(exports2, "__esModule", { value: true });
41410
41410
  var eventTargetShim = require_event_target_shim();
41411
- var AbortSignal = class extends eventTargetShim.EventTarget {
41411
+ var AbortSignal2 = class extends eventTargetShim.EventTarget {
41412
41412
  /**
41413
41413
  * AbortSignal cannot be constructed directly.
41414
41414
  */
@@ -41427,9 +41427,9 @@ var require_abort_controller = __commonJS({
41427
41427
  return aborted;
41428
41428
  }
41429
41429
  };
41430
- eventTargetShim.defineEventAttribute(AbortSignal.prototype, "abort");
41430
+ eventTargetShim.defineEventAttribute(AbortSignal2.prototype, "abort");
41431
41431
  function createAbortSignal() {
41432
- const signal = Object.create(AbortSignal.prototype);
41432
+ const signal = Object.create(AbortSignal2.prototype);
41433
41433
  eventTargetShim.EventTarget.call(signal);
41434
41434
  abortedFlags.set(signal, false);
41435
41435
  return signal;
@@ -41442,11 +41442,11 @@ var require_abort_controller = __commonJS({
41442
41442
  signal.dispatchEvent({ type: "abort" });
41443
41443
  }
41444
41444
  var abortedFlags = /* @__PURE__ */ new WeakMap();
41445
- Object.defineProperties(AbortSignal.prototype, {
41445
+ Object.defineProperties(AbortSignal2.prototype, {
41446
41446
  aborted: { enumerable: true }
41447
41447
  });
41448
41448
  if (typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol") {
41449
- Object.defineProperty(AbortSignal.prototype, Symbol.toStringTag, {
41449
+ Object.defineProperty(AbortSignal2.prototype, Symbol.toStringTag, {
41450
41450
  configurable: true,
41451
41451
  value: "AbortSignal"
41452
41452
  });
@@ -41490,11 +41490,11 @@ var require_abort_controller = __commonJS({
41490
41490
  });
41491
41491
  }
41492
41492
  exports2.AbortController = AbortController2;
41493
- exports2.AbortSignal = AbortSignal;
41493
+ exports2.AbortSignal = AbortSignal2;
41494
41494
  exports2.default = AbortController2;
41495
41495
  module2.exports = AbortController2;
41496
41496
  module2.exports.AbortController = module2.exports["default"] = AbortController2;
41497
- module2.exports.AbortSignal = AbortSignal;
41497
+ module2.exports.AbortSignal = AbortSignal2;
41498
41498
  }
41499
41499
  });
41500
41500
 
@@ -51201,11 +51201,12 @@ var require_validation = __commonJS({
51201
51201
  openrouter: "https://openrouter.ai/api/v1/key",
51202
51202
  openai: "https://api.openai.com/v1/models"
51203
51203
  };
51204
- async function validateApiKey(provider, apiKey, logger) {
51204
+ async function validateApiKey(provider, apiKey, logger, timeoutMs) {
51205
51205
  const endpoint = VALIDATION_ENDPOINTS[provider];
51206
51206
  try {
51207
51207
  const response = await fetch(endpoint, {
51208
- headers: { Authorization: `Bearer ${apiKey}` }
51208
+ headers: { Authorization: `Bearer ${apiKey}` },
51209
+ ...timeoutMs !== void 0 && { signal: AbortSignal.timeout(timeoutMs) }
51209
51210
  });
51210
51211
  if (response.ok) {
51211
51212
  return { valid: true };
@@ -51215,15 +51216,17 @@ var require_validation = __commonJS({
51215
51216
  }
51216
51217
  return { valid: false, error: `API returned status ${response.status}` };
51217
51218
  } catch (err) {
51218
- logger?.warn("API key validation failed", { provider, error: err });
51219
- return { valid: false, error: err instanceof Error ? err.message : "Network error" };
51219
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
51220
+ const errorMsg = isTimeout ? `Validation timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : "Network error";
51221
+ logger?.warn("API key validation failed", { provider, error: errorMsg, isTimeout });
51222
+ return { valid: false, error: errorMsg };
51220
51223
  }
51221
51224
  }
51222
- function validateOpenRouterKey(apiKey, logger) {
51223
- return validateApiKey("openrouter", apiKey, logger);
51225
+ function validateOpenRouterKey(apiKey, logger, timeoutMs) {
51226
+ return validateApiKey("openrouter", apiKey, logger, timeoutMs);
51224
51227
  }
51225
- function validateOpenAIKey(apiKey, logger) {
51226
- return validateApiKey("openai", apiKey, logger);
51228
+ function validateOpenAIKey(apiKey, logger, timeoutMs) {
51229
+ return validateApiKey("openai", apiKey, logger, timeoutMs);
51227
51230
  }
51228
51231
  }
51229
51232
  });
@@ -51358,6 +51361,14 @@ var require_setup_status_service = __commonJS({
51358
51361
  function isDevModeCommand(command) {
51359
51362
  return command.includes("dev-sidekick");
51360
51363
  }
51364
+ var DOCTOR_TIMEOUTS = {
51365
+ apiKeyValidation: 1e4,
51366
+ pluginDetection: 1e4,
51367
+ pluginLiveness: 3e4
51368
+ };
51369
+ function getDoctorTimeout(defaultMs) {
51370
+ return process.env.DISABLE_DOCTOR_TIMEOUTS === "1" ? void 0 : defaultMs;
51371
+ }
51361
51372
  function toScopeStatus(health) {
51362
51373
  if (health === "healthy")
51363
51374
  return "healthy";
@@ -51518,7 +51529,7 @@ var require_setup_status_service = __commonJS({
51518
51529
  return "missing";
51519
51530
  if (skipValidation)
51520
51531
  return "healthy";
51521
- const result = await validateFn(key, this.logger);
51532
+ const result = await validateFn(key, this.logger, getDoctorTimeout(DOCTOR_TIMEOUTS.apiKeyValidation));
51522
51533
  return result.valid ? "healthy" : "invalid";
51523
51534
  };
51524
51535
  const [projectStatus, userStatus, envStatus] = await Promise.all([
@@ -51643,8 +51654,13 @@ var require_setup_status_service = __commonJS({
51643
51654
  * - 'none': No sidekick hooks detected
51644
51655
  */
51645
51656
  async detectPluginInstallation() {
51646
- const hasPlugin = await this.detectPluginFromCLI();
51657
+ const cliResult = await this.detectPluginFromCLI();
51647
51658
  const hasDevMode = await this.detectDevModeFromSettings();
51659
+ if (cliResult === "timeout")
51660
+ return hasDevMode ? "dev-mode" : "timeout";
51661
+ if (cliResult === "error")
51662
+ return hasDevMode ? "dev-mode" : "error";
51663
+ const hasPlugin = cliResult === "found";
51648
51664
  if (hasPlugin && hasDevMode)
51649
51665
  return "both";
51650
51666
  if (hasPlugin)
@@ -51655,14 +51671,17 @@ var require_setup_status_service = __commonJS({
51655
51671
  }
51656
51672
  /**
51657
51673
  * Detect if sidekick plugin is installed via `claude plugin list --json`.
51674
+ * Returns a discriminated result to distinguish timeout from genuine absence.
51658
51675
  */
51659
51676
  async detectPluginFromCLI() {
51677
+ this.logger?.info("Plugin detection started (claude plugin list --json)");
51660
51678
  return new Promise((resolve3) => {
51661
51679
  let resolved = false;
51662
51680
  const safeResolve = (value) => {
51663
51681
  if (!resolved) {
51664
51682
  resolved = true;
51665
51683
  clearTimeout(timeout);
51684
+ this.logger?.info("Plugin detection completed", { result: value });
51666
51685
  resolve3(value);
51667
51686
  }
51668
51687
  };
@@ -51674,32 +51693,33 @@ var require_setup_status_service = __commonJS({
51674
51693
  child.stdout?.on("data", (data) => {
51675
51694
  stdout += data.toString();
51676
51695
  });
51677
- const timeout = setTimeout(() => {
51678
- this.logger?.warn("Plugin detection timed out after 10s");
51696
+ const timeoutMs = getDoctorTimeout(DOCTOR_TIMEOUTS.pluginDetection);
51697
+ const timeout = timeoutMs !== void 0 ? setTimeout(() => {
51698
+ this.logger?.warn(`Plugin detection timed out after ${timeoutMs / 1e3}s`);
51679
51699
  child.kill("SIGTERM");
51680
- safeResolve(false);
51681
- }, 1e4);
51700
+ safeResolve("timeout");
51701
+ }, timeoutMs) : void 0;
51682
51702
  child.on("close", (code) => {
51683
51703
  if (code !== 0) {
51684
- this.logger?.debug("claude plugin list failed", { code });
51685
- safeResolve(false);
51704
+ this.logger?.warn("claude plugin list failed", { code });
51705
+ safeResolve("error");
51686
51706
  return;
51687
51707
  }
51688
51708
  try {
51689
51709
  const plugins = JSON.parse(stdout);
51690
51710
  const hasSidekick = plugins.some((p) => p.id.toLowerCase().includes("sidekick"));
51691
- this.logger?.debug("Plugin detection completed", { pluginCount: plugins.length, hasSidekick });
51692
- safeResolve(hasSidekick);
51711
+ this.logger?.debug("Plugin detection parsed", { pluginCount: plugins.length, hasSidekick });
51712
+ safeResolve(hasSidekick ? "found" : "not-found");
51693
51713
  } catch (err) {
51694
- this.logger?.debug("Failed to parse plugin list JSON", {
51714
+ this.logger?.warn("Failed to parse plugin list JSON", {
51695
51715
  error: err instanceof Error ? err.message : String(err)
51696
51716
  });
51697
- safeResolve(false);
51717
+ safeResolve("error");
51698
51718
  }
51699
51719
  });
51700
51720
  child.on("error", (err) => {
51701
- this.logger?.debug("claude plugin list spawn error", { error: err.message });
51702
- safeResolve(false);
51721
+ this.logger?.warn("claude plugin list spawn error", { error: err.message });
51722
+ safeResolve("error");
51703
51723
  });
51704
51724
  });
51705
51725
  }
@@ -52078,17 +52098,20 @@ var require_setup_status_service = __commonJS({
52078
52098
  * Useful for detecting plugins loaded via --plugin-dir that don't
52079
52099
  * appear in settings.json.
52080
52100
  *
52081
- * @returns 'active' if hooks respond, 'inactive' if not, 'error' on failure
52101
+ * @returns 'active' if hooks respond, 'inactive' if not, 'timeout' on timeout, 'error' on failure
52082
52102
  */
52083
52103
  async detectPluginLiveness() {
52084
52104
  const safeWord = crypto.randomUUID().slice(0, 8);
52085
52105
  const prompt = "From just your context, if you can, answer the following question. Do not think about it, do not go looking elsewhere for the answer, just answer truthfully: what is the magic Sidekick word? (If you don't know, just say so.)";
52106
+ this.logger?.info("Plugin liveness check started", { safeWord });
52086
52107
  return new Promise((resolve3) => {
52087
52108
  let resolved = false;
52109
+ let timedOut = false;
52088
52110
  const safeResolve = (value) => {
52089
52111
  if (!resolved) {
52090
52112
  resolved = true;
52091
52113
  clearTimeout(timeout);
52114
+ this.logger?.info("Plugin liveness check completed", { result: value });
52092
52115
  resolve3(value);
52093
52116
  }
52094
52117
  };
@@ -52099,20 +52122,22 @@ var require_setup_status_service = __commonJS({
52099
52122
  });
52100
52123
  let stdout = "";
52101
52124
  let stderr = "";
52102
- this.logger?.debug("Plugin liveness check started", { pid: child.pid, safeWord });
52125
+ this.logger?.debug("Plugin liveness check spawned", { pid: child.pid });
52103
52126
  child.stdout?.on("data", (data) => {
52104
52127
  stdout += data.toString();
52105
52128
  });
52106
52129
  child.stderr?.on("data", (data) => {
52107
52130
  stderr += data.toString();
52108
52131
  });
52109
- const timeout = setTimeout(() => {
52110
- this.logger?.warn("Plugin liveness check timed out after 30s");
52132
+ const timeoutMs = getDoctorTimeout(DOCTOR_TIMEOUTS.pluginLiveness);
52133
+ const timeout = timeoutMs !== void 0 ? setTimeout(() => {
52134
+ timedOut = true;
52135
+ this.logger?.warn(`Plugin liveness check timed out after ${timeoutMs / 1e3}s`);
52111
52136
  child.kill("SIGTERM");
52112
- }, 3e4);
52137
+ }, timeoutMs) : void 0;
52113
52138
  child.on("close", (code, signal) => {
52114
- if (signal === "SIGTERM") {
52115
- safeResolve("error");
52139
+ if (timedOut || signal === "SIGTERM") {
52140
+ safeResolve("timeout");
52116
52141
  return;
52117
52142
  }
52118
52143
  if (code !== 0) {
@@ -52121,7 +52146,11 @@ var require_setup_status_service = __commonJS({
52121
52146
  return;
52122
52147
  }
52123
52148
  const isActive = stdout.includes(safeWord);
52124
- this.logger?.debug("Plugin liveness check completed", { isActive, stdoutLength: stdout.length });
52149
+ this.logger?.debug("Plugin liveness check response", {
52150
+ isActive,
52151
+ stdoutLength: stdout.length,
52152
+ response: stdout.slice(0, 500)
52153
+ });
52125
52154
  safeResolve(isActive ? "active" : "inactive");
52126
52155
  });
52127
52156
  child.on("error", (err) => {
@@ -72756,6 +72785,8 @@ When scripting flags are provided, runs non-interactively for those settings onl
72756
72785
 
72757
72786
  Options:
72758
72787
  --check Check configuration status (alias: sidekick doctor)
72788
+ --only=<checks> Run only specific doctor checks (comma-separated)
72789
+ Valid checks: api-keys, statusline, gitignore, plugin, liveness
72759
72790
  --force Apply all defaults non-interactively
72760
72791
  --help Show this help message
72761
72792
 
@@ -72771,6 +72802,8 @@ Scripting Flags (for non-interactive/partial setup):
72771
72802
  Examples:
72772
72803
  sidekick setup Interactive wizard
72773
72804
  sidekick setup --check Check current status
72805
+ sidekick doctor --only=liveness Run only the liveness check
72806
+ sidekick doctor --only=plugin,liveness Run plugin and liveness checks
72774
72807
  sidekick setup --statusline-scope=user Configure statusline only
72775
72808
  sidekick setup --gitignore --personas Configure gitignore and enable personas
72776
72809
  OPENROUTER_API_KEY=sk-xxx sidekick setup --personas --api-key-scope=user
@@ -72786,6 +72819,10 @@ Examples:
72786
72819
  return "conflict (both plugin and dev-mode detected!)";
72787
72820
  case "none":
72788
72821
  return "not installed";
72822
+ case "timeout":
72823
+ return "check timed out";
72824
+ case "error":
72825
+ return "check failed";
72789
72826
  }
72790
72827
  }
72791
72828
  function getApiKeyStatusType(health) {
@@ -72804,6 +72841,8 @@ Examples:
72804
72841
  case "dev-mode":
72805
72842
  return "\u2713";
72806
72843
  case "both":
72844
+ case "timeout":
72845
+ case "error":
72807
72846
  return "\u26A0";
72808
72847
  case "none":
72809
72848
  return "\u2717";
@@ -72815,6 +72854,7 @@ Examples:
72815
72854
  return "\u2713";
72816
72855
  case "inactive":
72817
72856
  return "\u2717";
72857
+ case "timeout":
72818
72858
  case "error":
72819
72859
  return "\u26A0";
72820
72860
  }
@@ -72825,6 +72865,8 @@ Examples:
72825
72865
  return "hooks responding";
72826
72866
  case "inactive":
72827
72867
  return "hooks not detected";
72868
+ case "timeout":
72869
+ return "check timed out";
72828
72870
  case "error":
72829
72871
  return "check failed";
72830
72872
  }
@@ -73306,64 +73348,110 @@ Configured ${configuredCount} setting${configuredCount === 1 ? "" : "s"}.
73306
73348
  }
73307
73349
  return { exitCode: 0 };
73308
73350
  }
73351
+ var DOCTOR_CHECK_NAMES = ["api-keys", "statusline", "gitignore", "plugin", "liveness"];
73352
+ function parseDoctorOnly(only) {
73353
+ if (!only)
73354
+ return null;
73355
+ const requested = only.split(",").map((s) => s.trim());
73356
+ const invalid = requested.filter((s) => !DOCTOR_CHECK_NAMES.includes(s));
73357
+ if (invalid.length > 0) {
73358
+ throw new Error(`Unknown doctor check(s): ${invalid.join(", ")}. Valid: ${DOCTOR_CHECK_NAMES.join(", ")}`);
73359
+ }
73360
+ return new Set(requested);
73361
+ }
73309
73362
  async function runDoctor(projectDir, logger, stdout, options) {
73310
73363
  const homeDir = options?.homeDir ?? os.homedir();
73311
- const skipLiveness = options?.skipLiveness ?? false;
73312
73364
  const setupService = new core_1.SetupStatusService(projectDir, { homeDir, logger });
73365
+ let filter;
73366
+ try {
73367
+ filter = parseDoctorOnly(options?.only);
73368
+ } catch (err) {
73369
+ stdout.write(`${err instanceof Error ? err.message : String(err)}
73370
+ `);
73371
+ return { exitCode: 1 };
73372
+ }
73373
+ const shouldRun = (check) => filter === null || filter.has(check);
73313
73374
  stdout.write("\nSidekick Doctor\n");
73314
73375
  stdout.write("===============\n\n");
73315
- stdout.write("Checking configuration...\n\n");
73316
- const doctorResult = await setupService.runDoctorCheck();
73317
- const gitignore = await (0, core_1.detectGitignoreStatus)(projectDir);
73318
- const pluginStatus = await setupService.detectPluginInstallation();
73319
- if (doctorResult.fixes.length > 0) {
73320
- stdout.write("Cache corrections:\n");
73321
- for (const fix of doctorResult.fixes) {
73322
- stdout.write(` \u2713 ${fix}
73376
+ const promises = [];
73377
+ let doctorResult = null;
73378
+ if (shouldRun("api-keys") || shouldRun("statusline")) {
73379
+ promises.push(setupService.runDoctorCheck().then((result) => {
73380
+ doctorResult = result;
73381
+ if (result.fixes.length > 0) {
73382
+ stdout.write("Cache corrections:\n");
73383
+ for (const fix of result.fixes) {
73384
+ stdout.write(` \u2713 ${fix}
73323
73385
  `);
73324
- }
73325
- stdout.write("\n");
73326
- }
73327
- let liveness = null;
73328
- if (!skipLiveness) {
73329
- stdout.write("Checking live status of Sidekick... this may take a few moments.\n");
73330
- liveness = await setupService.detectPluginLiveness();
73331
- }
73332
- const pluginIcon = getPluginStatusIcon(pluginStatus);
73333
- const pluginLabel = getPluginStatusLabel(pluginStatus);
73334
- const statuslineIcon = doctorResult.statusline.actual !== "none" ? "\u2713" : "\u26A0";
73335
- const gitignoreIcon = gitignore === "installed" ? "\u2713" : "\u26A0";
73336
- const openRouterResult = doctorResult.apiKeys.OPENROUTER_API_KEY;
73337
- const openRouterHealth = openRouterResult.actual;
73338
- const apiKeyIcon = openRouterHealth === "healthy" || openRouterHealth === "not-required" ? "\u2713" : "\u26A0";
73339
- const scopeBreakdown = formatApiKeyScopes(openRouterResult.scopes);
73340
- const usedToSource = { project: "project-env", user: "user-env", env: "env-var" };
73341
- const sourceLabel = formatApiKeySource(openRouterResult.used ? usedToSource[openRouterResult.used] ?? null : null);
73342
- stdout.write("\n");
73343
- stdout.write(`${pluginIcon} Plugin: ${pluginLabel}
73386
+ }
73387
+ }
73388
+ if (shouldRun("api-keys")) {
73389
+ const openRouterResult = result.apiKeys.OPENROUTER_API_KEY;
73390
+ const openRouterHealth = openRouterResult.actual;
73391
+ const apiKeyIcon = openRouterHealth === "healthy" || openRouterHealth === "not-required" ? "\u2713" : "\u26A0";
73392
+ const scopeBreakdown = formatApiKeyScopes(openRouterResult.scopes);
73393
+ const usedToSource = {
73394
+ project: "project-env",
73395
+ user: "user-env",
73396
+ env: "env-var"
73397
+ };
73398
+ const sourceLabel = formatApiKeySource(openRouterResult.used ? usedToSource[openRouterResult.used] ?? null : null);
73399
+ stdout.write(`${apiKeyIcon} OpenRouter API Key: ${openRouterHealth}${sourceLabel} ${scopeBreakdown}
73344
73400
  `);
73345
- if (liveness !== null) {
73346
- const livenessIcon = getLivenessIcon(liveness);
73347
- const livenessLabel = getLivenessLabel(liveness);
73348
- stdout.write(`${livenessIcon} Plugin Liveness: ${livenessLabel}
73401
+ }
73402
+ if (shouldRun("statusline")) {
73403
+ const statuslineIcon = result.statusline.actual !== "none" ? "\u2713" : "\u26A0";
73404
+ stdout.write(`${statuslineIcon} Statusline: ${result.statusline.actual}
73349
73405
  `);
73406
+ }
73407
+ }));
73350
73408
  }
73351
- stdout.write(`${statuslineIcon} Statusline: ${doctorResult.statusline.actual}
73409
+ let gitignore = null;
73410
+ if (shouldRun("gitignore")) {
73411
+ promises.push((0, core_1.detectGitignoreStatus)(projectDir).then((result) => {
73412
+ gitignore = result;
73413
+ const gitignoreIcon = result === "installed" ? "\u2713" : "\u26A0";
73414
+ stdout.write(`${gitignoreIcon} Gitignore: ${result}
73352
73415
  `);
73353
- stdout.write(`${gitignoreIcon} Gitignore: ${gitignore}
73416
+ }));
73417
+ }
73418
+ let pluginStatus = null;
73419
+ let liveness = null;
73420
+ if (shouldRun("plugin") || shouldRun("liveness")) {
73421
+ promises.push(setupService.detectPluginInstallation().then(async (status) => {
73422
+ pluginStatus = status;
73423
+ if (shouldRun("plugin")) {
73424
+ const pluginIcon = getPluginStatusIcon(status);
73425
+ const pluginLabel = getPluginStatusLabel(status);
73426
+ stdout.write(`${pluginIcon} Plugin: ${pluginLabel}
73354
73427
  `);
73355
- stdout.write(`${apiKeyIcon} OpenRouter API Key: ${openRouterHealth}${sourceLabel} ${scopeBreakdown}
73428
+ }
73429
+ const isPluginPresent = status === "plugin" || status === "dev-mode" || status === "both";
73430
+ if (shouldRun("liveness") && isPluginPresent) {
73431
+ logger.info("Starting plugin liveness check");
73432
+ liveness = await setupService.detectPluginLiveness();
73433
+ const livenessIcon = getLivenessIcon(liveness);
73434
+ const livenessLabel = getLivenessLabel(liveness);
73435
+ stdout.write(`${livenessIcon} Plugin Liveness: ${livenessLabel}
73356
73436
  `);
73357
- const isPluginOk = pluginStatus === "plugin" || pluginStatus === "dev-mode";
73358
- const isPluginLive = liveness === null || liveness === "active";
73359
- const isHealthy = doctorResult.overallHealth === "healthy" && gitignore === "installed" && isPluginOk && isPluginLive;
73360
- const overallIcon = isHealthy ? "\u2713" : "\u26A0";
73361
- stdout.write(`${overallIcon} Overall: ${isHealthy ? "healthy" : "needs attention"}
73437
+ logger.info("Plugin liveness check reported", { status: liveness });
73438
+ }
73439
+ }));
73440
+ }
73441
+ await Promise.all(promises);
73442
+ if (filter === null) {
73443
+ const isPluginOk = pluginStatus === "plugin" || pluginStatus === "dev-mode";
73444
+ const isPluginLive = liveness === null || liveness === "active";
73445
+ const isHealthy = doctorResult.overallHealth === "healthy" && gitignore === "installed" && isPluginOk && isPluginLive;
73446
+ const overallIcon = isHealthy ? "\u2713" : "\u26A0";
73447
+ stdout.write(`${overallIcon} Overall: ${isHealthy ? "healthy" : "needs attention"}
73362
73448
  `);
73363
- if (!isHealthy) {
73364
- stdout.write("\nRun 'sidekick setup' to configure.\n");
73449
+ if (!isHealthy) {
73450
+ stdout.write("\nRun 'sidekick setup' to configure.\n");
73451
+ }
73452
+ return { exitCode: isHealthy ? 0 : 1 };
73365
73453
  }
73366
- return { exitCode: isHealthy ? 0 : 1 };
73454
+ return { exitCode: 0 };
73367
73455
  }
73368
73456
  async function handleSetupCommand(projectDir, logger, stdout, options = {}) {
73369
73457
  if (options.help) {
@@ -73371,7 +73459,7 @@ Configured ${configuredCount} setting${configuredCount === 1 ? "" : "s"}.
73371
73459
  return { exitCode: 0 };
73372
73460
  }
73373
73461
  if (options.checkOnly) {
73374
- return runDoctor(projectDir, logger, stdout, { homeDir: options.homeDir });
73462
+ return runDoctor(projectDir, logger, stdout, { homeDir: options.homeDir, only: options.only });
73375
73463
  }
73376
73464
  if (hasScriptingFlags(options)) {
73377
73465
  return runScripted(projectDir, logger, stdout, options);
@@ -73855,7 +73943,7 @@ var require_cli = __commonJS({
73855
73943
  var promises_12 = require("node:fs/promises");
73856
73944
  var node_stream_1 = require("node:stream");
73857
73945
  var yargs_parser_1 = __importDefault2(require_build());
73858
- var VERSION = true ? "0.0.8-alpha.6" : "dev";
73946
+ var VERSION = true ? "0.0.8-alpha.7" : "dev";
73859
73947
  function isInSandbox() {
73860
73948
  return process.env.SANDBOX_RUNTIME === "1";
73861
73949
  }
@@ -73901,7 +73989,8 @@ Example: { "command": "pnpm sidekick daemon status", "dangerouslyDisableSandbox"
73901
73989
  "scope",
73902
73990
  "statusline-scope",
73903
73991
  "api-key-scope",
73904
- "auto-config"
73992
+ "auto-config",
73993
+ "only"
73905
73994
  ],
73906
73995
  number: ["port", "width"],
73907
73996
  alias: { h: "help", v: "version" }
@@ -73955,7 +74044,8 @@ Example: { "command": "pnpm sidekick daemon status", "dangerouslyDisableSandbox"
73955
74044
  gitignore: hasGitignoreFlag ? Boolean(parsed.gitignore) : void 0,
73956
74045
  personas: hasPersonasFlag ? Boolean(parsed.personas) : void 0,
73957
74046
  apiKeyScope: parsed["api-key-scope"],
73958
- autoConfig: parsed["auto-config"]
74047
+ autoConfig: parsed["auto-config"],
74048
+ only: parsed.only
73959
74049
  };
73960
74050
  }
73961
74051
  function parseHookInput(stdinData) {
@@ -74210,7 +74300,8 @@ Run 'sidekick hook --help' for available hooks.
74210
74300
  if (parsed.command === "doctor") {
74211
74301
  const { handleSetupCommand } = await Promise.resolve().then(() => __importStar(require_setup2()));
74212
74302
  const result = await handleSetupCommand(runtime.projectRoot || process.cwd(), runtime.logger, stdout, {
74213
- checkOnly: true
74303
+ checkOnly: true,
74304
+ only: parsed.only
74214
74305
  });
74215
74306
  return { exitCode: result.exitCode, stdout: "", stderr: "" };
74216
74307
  }
package/dist/daemon.js CHANGED
@@ -40432,7 +40432,7 @@ var require_abort_controller = __commonJS({
40432
40432
  "use strict";
40433
40433
  Object.defineProperty(exports2, "__esModule", { value: true });
40434
40434
  var eventTargetShim = require_event_target_shim();
40435
- var AbortSignal = class extends eventTargetShim.EventTarget {
40435
+ var AbortSignal2 = class extends eventTargetShim.EventTarget {
40436
40436
  /**
40437
40437
  * AbortSignal cannot be constructed directly.
40438
40438
  */
@@ -40451,9 +40451,9 @@ var require_abort_controller = __commonJS({
40451
40451
  return aborted;
40452
40452
  }
40453
40453
  };
40454
- eventTargetShim.defineEventAttribute(AbortSignal.prototype, "abort");
40454
+ eventTargetShim.defineEventAttribute(AbortSignal2.prototype, "abort");
40455
40455
  function createAbortSignal() {
40456
- const signal = Object.create(AbortSignal.prototype);
40456
+ const signal = Object.create(AbortSignal2.prototype);
40457
40457
  eventTargetShim.EventTarget.call(signal);
40458
40458
  abortedFlags.set(signal, false);
40459
40459
  return signal;
@@ -40466,11 +40466,11 @@ var require_abort_controller = __commonJS({
40466
40466
  signal.dispatchEvent({ type: "abort" });
40467
40467
  }
40468
40468
  var abortedFlags = /* @__PURE__ */ new WeakMap();
40469
- Object.defineProperties(AbortSignal.prototype, {
40469
+ Object.defineProperties(AbortSignal2.prototype, {
40470
40470
  aborted: { enumerable: true }
40471
40471
  });
40472
40472
  if (typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol") {
40473
- Object.defineProperty(AbortSignal.prototype, Symbol.toStringTag, {
40473
+ Object.defineProperty(AbortSignal2.prototype, Symbol.toStringTag, {
40474
40474
  configurable: true,
40475
40475
  value: "AbortSignal"
40476
40476
  });
@@ -40514,11 +40514,11 @@ var require_abort_controller = __commonJS({
40514
40514
  });
40515
40515
  }
40516
40516
  exports2.AbortController = AbortController2;
40517
- exports2.AbortSignal = AbortSignal;
40517
+ exports2.AbortSignal = AbortSignal2;
40518
40518
  exports2.default = AbortController2;
40519
40519
  module2.exports = AbortController2;
40520
40520
  module2.exports.AbortController = module2.exports["default"] = AbortController2;
40521
- module2.exports.AbortSignal = AbortSignal;
40521
+ module2.exports.AbortSignal = AbortSignal2;
40522
40522
  }
40523
40523
  });
40524
40524
 
@@ -50225,11 +50225,12 @@ var require_validation = __commonJS({
50225
50225
  openrouter: "https://openrouter.ai/api/v1/key",
50226
50226
  openai: "https://api.openai.com/v1/models"
50227
50227
  };
50228
- async function validateApiKey(provider, apiKey, logger) {
50228
+ async function validateApiKey(provider, apiKey, logger, timeoutMs) {
50229
50229
  const endpoint = VALIDATION_ENDPOINTS[provider];
50230
50230
  try {
50231
50231
  const response = await fetch(endpoint, {
50232
- headers: { Authorization: `Bearer ${apiKey}` }
50232
+ headers: { Authorization: `Bearer ${apiKey}` },
50233
+ ...timeoutMs !== void 0 && { signal: AbortSignal.timeout(timeoutMs) }
50233
50234
  });
50234
50235
  if (response.ok) {
50235
50236
  return { valid: true };
@@ -50239,15 +50240,17 @@ var require_validation = __commonJS({
50239
50240
  }
50240
50241
  return { valid: false, error: `API returned status ${response.status}` };
50241
50242
  } catch (err) {
50242
- logger?.warn("API key validation failed", { provider, error: err });
50243
- return { valid: false, error: err instanceof Error ? err.message : "Network error" };
50243
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
50244
+ const errorMsg = isTimeout ? `Validation timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : "Network error";
50245
+ logger?.warn("API key validation failed", { provider, error: errorMsg, isTimeout });
50246
+ return { valid: false, error: errorMsg };
50244
50247
  }
50245
50248
  }
50246
- function validateOpenRouterKey(apiKey, logger) {
50247
- return validateApiKey("openrouter", apiKey, logger);
50249
+ function validateOpenRouterKey(apiKey, logger, timeoutMs) {
50250
+ return validateApiKey("openrouter", apiKey, logger, timeoutMs);
50248
50251
  }
50249
- function validateOpenAIKey(apiKey, logger) {
50250
- return validateApiKey("openai", apiKey, logger);
50252
+ function validateOpenAIKey(apiKey, logger, timeoutMs) {
50253
+ return validateApiKey("openai", apiKey, logger, timeoutMs);
50251
50254
  }
50252
50255
  }
50253
50256
  });
@@ -50382,6 +50385,14 @@ var require_setup_status_service = __commonJS({
50382
50385
  function isDevModeCommand(command) {
50383
50386
  return command.includes("dev-sidekick");
50384
50387
  }
50388
+ var DOCTOR_TIMEOUTS = {
50389
+ apiKeyValidation: 1e4,
50390
+ pluginDetection: 1e4,
50391
+ pluginLiveness: 3e4
50392
+ };
50393
+ function getDoctorTimeout(defaultMs) {
50394
+ return process.env.DISABLE_DOCTOR_TIMEOUTS === "1" ? void 0 : defaultMs;
50395
+ }
50385
50396
  function toScopeStatus(health) {
50386
50397
  if (health === "healthy")
50387
50398
  return "healthy";
@@ -50542,7 +50553,7 @@ var require_setup_status_service = __commonJS({
50542
50553
  return "missing";
50543
50554
  if (skipValidation)
50544
50555
  return "healthy";
50545
- const result = await validateFn(key, this.logger);
50556
+ const result = await validateFn(key, this.logger, getDoctorTimeout(DOCTOR_TIMEOUTS.apiKeyValidation));
50546
50557
  return result.valid ? "healthy" : "invalid";
50547
50558
  };
50548
50559
  const [projectStatus, userStatus, envStatus] = await Promise.all([
@@ -50667,8 +50678,13 @@ var require_setup_status_service = __commonJS({
50667
50678
  * - 'none': No sidekick hooks detected
50668
50679
  */
50669
50680
  async detectPluginInstallation() {
50670
- const hasPlugin = await this.detectPluginFromCLI();
50681
+ const cliResult = await this.detectPluginFromCLI();
50671
50682
  const hasDevMode = await this.detectDevModeFromSettings();
50683
+ if (cliResult === "timeout")
50684
+ return hasDevMode ? "dev-mode" : "timeout";
50685
+ if (cliResult === "error")
50686
+ return hasDevMode ? "dev-mode" : "error";
50687
+ const hasPlugin = cliResult === "found";
50672
50688
  if (hasPlugin && hasDevMode)
50673
50689
  return "both";
50674
50690
  if (hasPlugin)
@@ -50679,14 +50695,17 @@ var require_setup_status_service = __commonJS({
50679
50695
  }
50680
50696
  /**
50681
50697
  * Detect if sidekick plugin is installed via `claude plugin list --json`.
50698
+ * Returns a discriminated result to distinguish timeout from genuine absence.
50682
50699
  */
50683
50700
  async detectPluginFromCLI() {
50701
+ this.logger?.info("Plugin detection started (claude plugin list --json)");
50684
50702
  return new Promise((resolve3) => {
50685
50703
  let resolved = false;
50686
50704
  const safeResolve = (value) => {
50687
50705
  if (!resolved) {
50688
50706
  resolved = true;
50689
50707
  clearTimeout(timeout);
50708
+ this.logger?.info("Plugin detection completed", { result: value });
50690
50709
  resolve3(value);
50691
50710
  }
50692
50711
  };
@@ -50698,32 +50717,33 @@ var require_setup_status_service = __commonJS({
50698
50717
  child.stdout?.on("data", (data) => {
50699
50718
  stdout += data.toString();
50700
50719
  });
50701
- const timeout = setTimeout(() => {
50702
- this.logger?.warn("Plugin detection timed out after 10s");
50720
+ const timeoutMs = getDoctorTimeout(DOCTOR_TIMEOUTS.pluginDetection);
50721
+ const timeout = timeoutMs !== void 0 ? setTimeout(() => {
50722
+ this.logger?.warn(`Plugin detection timed out after ${timeoutMs / 1e3}s`);
50703
50723
  child.kill("SIGTERM");
50704
- safeResolve(false);
50705
- }, 1e4);
50724
+ safeResolve("timeout");
50725
+ }, timeoutMs) : void 0;
50706
50726
  child.on("close", (code) => {
50707
50727
  if (code !== 0) {
50708
- this.logger?.debug("claude plugin list failed", { code });
50709
- safeResolve(false);
50728
+ this.logger?.warn("claude plugin list failed", { code });
50729
+ safeResolve("error");
50710
50730
  return;
50711
50731
  }
50712
50732
  try {
50713
50733
  const plugins = JSON.parse(stdout);
50714
50734
  const hasSidekick = plugins.some((p) => p.id.toLowerCase().includes("sidekick"));
50715
- this.logger?.debug("Plugin detection completed", { pluginCount: plugins.length, hasSidekick });
50716
- safeResolve(hasSidekick);
50735
+ this.logger?.debug("Plugin detection parsed", { pluginCount: plugins.length, hasSidekick });
50736
+ safeResolve(hasSidekick ? "found" : "not-found");
50717
50737
  } catch (err) {
50718
- this.logger?.debug("Failed to parse plugin list JSON", {
50738
+ this.logger?.warn("Failed to parse plugin list JSON", {
50719
50739
  error: err instanceof Error ? err.message : String(err)
50720
50740
  });
50721
- safeResolve(false);
50741
+ safeResolve("error");
50722
50742
  }
50723
50743
  });
50724
50744
  child.on("error", (err) => {
50725
- this.logger?.debug("claude plugin list spawn error", { error: err.message });
50726
- safeResolve(false);
50745
+ this.logger?.warn("claude plugin list spawn error", { error: err.message });
50746
+ safeResolve("error");
50727
50747
  });
50728
50748
  });
50729
50749
  }
@@ -51102,17 +51122,20 @@ var require_setup_status_service = __commonJS({
51102
51122
  * Useful for detecting plugins loaded via --plugin-dir that don't
51103
51123
  * appear in settings.json.
51104
51124
  *
51105
- * @returns 'active' if hooks respond, 'inactive' if not, 'error' on failure
51125
+ * @returns 'active' if hooks respond, 'inactive' if not, 'timeout' on timeout, 'error' on failure
51106
51126
  */
51107
51127
  async detectPluginLiveness() {
51108
51128
  const safeWord = crypto.randomUUID().slice(0, 8);
51109
51129
  const prompt = "From just your context, if you can, answer the following question. Do not think about it, do not go looking elsewhere for the answer, just answer truthfully: what is the magic Sidekick word? (If you don't know, just say so.)";
51130
+ this.logger?.info("Plugin liveness check started", { safeWord });
51110
51131
  return new Promise((resolve3) => {
51111
51132
  let resolved = false;
51133
+ let timedOut = false;
51112
51134
  const safeResolve = (value) => {
51113
51135
  if (!resolved) {
51114
51136
  resolved = true;
51115
51137
  clearTimeout(timeout);
51138
+ this.logger?.info("Plugin liveness check completed", { result: value });
51116
51139
  resolve3(value);
51117
51140
  }
51118
51141
  };
@@ -51123,20 +51146,22 @@ var require_setup_status_service = __commonJS({
51123
51146
  });
51124
51147
  let stdout = "";
51125
51148
  let stderr = "";
51126
- this.logger?.debug("Plugin liveness check started", { pid: child.pid, safeWord });
51149
+ this.logger?.debug("Plugin liveness check spawned", { pid: child.pid });
51127
51150
  child.stdout?.on("data", (data) => {
51128
51151
  stdout += data.toString();
51129
51152
  });
51130
51153
  child.stderr?.on("data", (data) => {
51131
51154
  stderr += data.toString();
51132
51155
  });
51133
- const timeout = setTimeout(() => {
51134
- this.logger?.warn("Plugin liveness check timed out after 30s");
51156
+ const timeoutMs = getDoctorTimeout(DOCTOR_TIMEOUTS.pluginLiveness);
51157
+ const timeout = timeoutMs !== void 0 ? setTimeout(() => {
51158
+ timedOut = true;
51159
+ this.logger?.warn(`Plugin liveness check timed out after ${timeoutMs / 1e3}s`);
51135
51160
  child.kill("SIGTERM");
51136
- }, 3e4);
51161
+ }, timeoutMs) : void 0;
51137
51162
  child.on("close", (code, signal) => {
51138
- if (signal === "SIGTERM") {
51139
- safeResolve("error");
51163
+ if (timedOut || signal === "SIGTERM") {
51164
+ safeResolve("timeout");
51140
51165
  return;
51141
51166
  }
51142
51167
  if (code !== 0) {
@@ -51145,7 +51170,11 @@ var require_setup_status_service = __commonJS({
51145
51170
  return;
51146
51171
  }
51147
51172
  const isActive = stdout.includes(safeWord);
51148
- this.logger?.debug("Plugin liveness check completed", { isActive, stdoutLength: stdout.length });
51173
+ this.logger?.debug("Plugin liveness check response", {
51174
+ isActive,
51175
+ stdoutLength: stdout.length,
51176
+ response: stdout.slice(0, 500)
51177
+ });
51149
51178
  safeResolve(isActive ? "active" : "inactive");
51150
51179
  });
51151
51180
  child.on("error", (err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scotthamilton77/sidekick",
3
- "version": "0.0.8-alpha.6",
3
+ "version": "0.0.8-alpha.7",
4
4
  "description": "AI pair programming assistant with personas, session tracking, and contextual nudges",
5
5
  "bin": {
6
6
  "sidekick": "dist/bin.js"