@scotthamilton77/sidekick 0.0.7-alpha → 0.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.
@@ -0,0 +1,13 @@
1
+ # safe-word-liveness.yaml
2
+ # Liveness probe reminder for plugin detection via safe word response
3
+ id: safe-word-liveness
4
+ blocking: false
5
+ priority: 100 # High priority - inject early
6
+ persistent: false # One-shot per session
7
+
8
+ additionalContext: |
9
+ This session is assisted by Sidekick, an AI coding companion.
10
+ Occasionally you will see additional context injected into the conversation by Sidekick.
11
+ This is important to observe and follow - the user installed this to help you reach quality outcomes.
12
+
13
+ If the user asks whether you know the magic Sidekick word, respond with: {{safeWord}}
@@ -6,16 +6,13 @@ priority: 10
6
6
  persistent: true
7
7
 
8
8
  additionalContext: |
9
- As you answer the user's questions, you can use the following context:
10
- # claudeMd
11
- Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.
12
-
13
9
  Before proceeding:
14
10
  - Verify first (don't tell the user "you're right" based solely on their statements/assertions). Wrong path = 10+ wasted turns. Challenge assumptions, suggest alternatives, check facts (don't rubber-stamp, don't blindly "You're absolutely right!")
15
11
  - Review the user's request carefully and ensure you understand the requirements
16
12
  - If anything is unclear or ambiguous, ask for clarification rather than making assumptions
17
- - Track your progress methodically - use TodoWrite for multi-step tasks
13
+ - Track your progress methodically - use Tasks and update them as you complete steps
18
14
  - Stay focused on the immediate task; don't drift into tangential improvements
15
+ - If you identify unrelated issues or new work, give yourself a task to ask the user if they want to tackle them now or add to the user's task tracking system
19
16
  - Verify your work before claiming completion (run tests, lint, type-check)
20
17
  - Update the user with progress summaries for long-running tasks
21
18
  - Use appropriate AGENTS/SKILLS: consider whether there are agents and/or skills that should be leveraged; ask the user if unsure
package/dist/bin.js CHANGED
@@ -16740,7 +16740,9 @@ var require_setup_status = __commonJS({
16740
16740
  apiKeys: zod_1.z.object({
16741
16741
  OPENROUTER_API_KEY: exports2.ApiKeyHealthSchema,
16742
16742
  OPENAI_API_KEY: exports2.ApiKeyHealthSchema
16743
- })
16743
+ }),
16744
+ /** True if sidekick plugin detected at user scope via `claude plugin list` */
16745
+ pluginDetected: zod_1.z.boolean().optional()
16744
16746
  });
16745
16747
  exports2.GitignoreStatusSchema = zod_1.z.enum([
16746
16748
  "unknown",
@@ -16762,7 +16764,11 @@ var require_setup_status = __commonJS({
16762
16764
  OPENROUTER_API_KEY: exports2.ProjectApiKeyHealthSchema,
16763
16765
  OPENAI_API_KEY: exports2.ProjectApiKeyHealthSchema
16764
16766
  }),
16765
- gitignore: exports2.GitignoreStatusSchema.optional().default("unknown")
16767
+ gitignore: exports2.GitignoreStatusSchema.optional().default("unknown"),
16768
+ /** True if sidekick plugin detected at project scope via `claude plugin list` */
16769
+ pluginDetected: zod_1.z.boolean().optional(),
16770
+ /** True if dev-mode hooks are enabled for this project. Set by `dev-mode enable/disable`. */
16771
+ devMode: zod_1.z.boolean().optional()
16766
16772
  });
16767
16773
  }
16768
16774
  });
@@ -32795,7 +32801,8 @@ var require_package3 = __commonJS({
32795
32801
  sidekick: "node packages/sidekick-cli/dist/bin.js",
32796
32802
  "test:dist:setup": "./scripts/test-dist.sh setup",
32797
32803
  "test:dist:teardown": "./scripts/test-dist.sh teardown",
32798
- "test:dist:rebuild": "./scripts/test-dist.sh rebuild"
32804
+ "test:dist:rebuild": "./scripts/test-dist.sh rebuild",
32805
+ "publish:dist": "./scripts/publish-dist.sh"
32799
32806
  },
32800
32807
  devDependencies: {
32801
32808
  "@eslint/js": "^9.39.1",
@@ -50168,12 +50175,15 @@ var require_setup_status_service = __commonJS({
50168
50175
  var fs = __importStar(require("node:fs/promises"));
50169
50176
  var path = __importStar(require("node:path"));
50170
50177
  var os = __importStar(require("node:os"));
50178
+ var crypto = __importStar(require("node:crypto"));
50179
+ var node_child_process_1 = require("node:child_process");
50171
50180
  var types_1 = require_dist();
50172
50181
  var shared_providers_1 = require_dist3();
50173
50182
  function isSidekickStatuslineCommand(command) {
50174
- if (!command)
50175
- return false;
50176
- return command.toLowerCase().includes("sidekick");
50183
+ return command?.toLowerCase().includes("sidekick") ?? false;
50184
+ }
50185
+ function isDevModeCommand(command) {
50186
+ return command.includes("dev-sidekick");
50177
50187
  }
50178
50188
  var SetupStatusService = class {
50179
50189
  constructor(projectDir, options) {
@@ -50304,6 +50314,123 @@ var require_setup_status_service = __commonJS({
50304
50314
  }
50305
50315
  return "not-setup";
50306
50316
  }
50317
+ /**
50318
+ * Detect plugin installation status.
50319
+ *
50320
+ * Uses `claude plugin list --json` to detect installed plugins,
50321
+ * and checks settings files for dev-mode hooks.
50322
+ *
50323
+ * @returns Installation status:
50324
+ * - 'plugin': Sidekick plugin installed via Claude marketplace
50325
+ * - 'dev-mode': Dev-mode hooks installed (dev-sidekick path)
50326
+ * - 'both': Both plugin and dev-mode hooks (conflict state)
50327
+ * - 'none': No sidekick hooks detected
50328
+ */
50329
+ async detectPluginInstallation() {
50330
+ const hasPlugin = await this.detectPluginFromCLI();
50331
+ const hasDevMode = await this.detectDevModeFromSettings();
50332
+ if (hasPlugin && hasDevMode)
50333
+ return "both";
50334
+ if (hasPlugin)
50335
+ return "plugin";
50336
+ if (hasDevMode)
50337
+ return "dev-mode";
50338
+ return "none";
50339
+ }
50340
+ /**
50341
+ * Detect if sidekick plugin is installed via `claude plugin list --json`.
50342
+ */
50343
+ async detectPluginFromCLI() {
50344
+ return new Promise((resolve3) => {
50345
+ let resolved = false;
50346
+ const safeResolve = (value) => {
50347
+ if (!resolved) {
50348
+ resolved = true;
50349
+ clearTimeout(timeout);
50350
+ resolve3(value);
50351
+ }
50352
+ };
50353
+ const child = (0, node_child_process_1.spawn)("claude", ["plugin", "list", "--json"], {
50354
+ stdio: ["ignore", "pipe", "pipe"]
50355
+ });
50356
+ let stdout = "";
50357
+ child.stdout?.on("data", (data) => {
50358
+ stdout += data.toString();
50359
+ });
50360
+ const timeout = setTimeout(() => {
50361
+ this.logger?.warn("Plugin detection timed out after 10s");
50362
+ child.kill("SIGTERM");
50363
+ safeResolve(false);
50364
+ }, 1e4);
50365
+ child.on("close", (code) => {
50366
+ if (code !== 0) {
50367
+ this.logger?.debug("claude plugin list failed", { code });
50368
+ safeResolve(false);
50369
+ return;
50370
+ }
50371
+ try {
50372
+ const plugins = JSON.parse(stdout);
50373
+ const hasSidekick = plugins.some((p) => p.id.toLowerCase().includes("sidekick"));
50374
+ this.logger?.debug("Plugin detection completed", { pluginCount: plugins.length, hasSidekick });
50375
+ safeResolve(hasSidekick);
50376
+ } catch (err) {
50377
+ this.logger?.debug("Failed to parse plugin list JSON", {
50378
+ error: err instanceof Error ? err.message : String(err)
50379
+ });
50380
+ safeResolve(false);
50381
+ }
50382
+ });
50383
+ child.on("error", (err) => {
50384
+ this.logger?.debug("claude plugin list spawn error", { error: err.message });
50385
+ safeResolve(false);
50386
+ });
50387
+ });
50388
+ }
50389
+ /**
50390
+ * Detect if dev-mode hooks are installed by checking settings files.
50391
+ */
50392
+ async detectDevModeFromSettings() {
50393
+ const settingsPaths = [
50394
+ path.join(this.homeDir, ".claude", "settings.json"),
50395
+ path.join(this.projectDir, ".claude", "settings.local.json")
50396
+ ];
50397
+ for (const settingsPath of settingsPaths) {
50398
+ try {
50399
+ const content = await fs.readFile(settingsPath, "utf-8");
50400
+ const settings = JSON.parse(content);
50401
+ if (this.hasDevModeHooks(settings)) {
50402
+ return true;
50403
+ }
50404
+ } catch {
50405
+ }
50406
+ }
50407
+ return false;
50408
+ }
50409
+ /**
50410
+ * Check a Claude settings object for dev-mode hooks.
50411
+ */
50412
+ hasDevModeHooks(settings) {
50413
+ const statusLineCommand = settings.statusLine?.command;
50414
+ if (statusLineCommand && isDevModeCommand(statusLineCommand)) {
50415
+ return true;
50416
+ }
50417
+ if (settings.hooks) {
50418
+ for (const hookEntries of Object.values(settings.hooks)) {
50419
+ if (!Array.isArray(hookEntries))
50420
+ continue;
50421
+ for (const entry of hookEntries) {
50422
+ if (!entry?.hooks)
50423
+ continue;
50424
+ for (const hook of entry.hooks) {
50425
+ if (hook?.command && isDevModeCommand(hook.command)) {
50426
+ return true;
50427
+ }
50428
+ }
50429
+ }
50430
+ }
50431
+ }
50432
+ return false;
50433
+ }
50307
50434
  // === Merged getters ===
50308
50435
  async getStatuslineHealth() {
50309
50436
  const project = await this.getProjectStatus();
@@ -50336,6 +50463,47 @@ var require_setup_status_service = __commonJS({
50336
50463
  const openrouterKey = await this.getEffectiveApiKeyHealth("OPENROUTER_API_KEY");
50337
50464
  return statusline === "configured" && (openrouterKey === "healthy" || openrouterKey === "not-required");
50338
50465
  }
50466
+ // === Dev-mode and plugin detection helpers ===
50467
+ /**
50468
+ * Get the devMode flag from project status.
50469
+ * Returns false if project status doesn't exist or devMode is not set.
50470
+ */
50471
+ async getDevMode() {
50472
+ const project = await this.getProjectStatus();
50473
+ return project?.devMode ?? false;
50474
+ }
50475
+ /**
50476
+ * Set the devMode flag in project status.
50477
+ * Creates project status if it doesn't exist.
50478
+ */
50479
+ async setDevMode(enabled) {
50480
+ const current = await this.getProjectStatus();
50481
+ if (current) {
50482
+ await this.updateProjectStatus({ devMode: enabled });
50483
+ } else {
50484
+ await this.writeProjectStatus({
50485
+ version: 1,
50486
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50487
+ autoConfigured: false,
50488
+ statusline: "user",
50489
+ apiKeys: {
50490
+ OPENROUTER_API_KEY: "user",
50491
+ OPENAI_API_KEY: "user"
50492
+ },
50493
+ gitignore: "unknown",
50494
+ devMode: enabled
50495
+ });
50496
+ }
50497
+ }
50498
+ /**
50499
+ * Check if the sidekick plugin is installed at any scope.
50500
+ * Reads pluginDetected flags from both user and project status.
50501
+ */
50502
+ async isPluginInstalled() {
50503
+ const user = await this.getUserStatus();
50504
+ const project = await this.getProjectStatus();
50505
+ return (user?.pluginDetected ?? false) || (project?.pluginDetected ?? false);
50506
+ }
50339
50507
  // === Auto-config helpers ===
50340
50508
  async isUserSetupComplete() {
50341
50509
  const user = await this.getUserStatus();
@@ -50471,6 +50639,66 @@ var require_setup_status_service = __commonJS({
50471
50639
  const isHealthy = await this.isHealthy();
50472
50640
  return isHealthy ? "healthy" : "unhealthy";
50473
50641
  }
50642
+ // === Plugin Liveness Detection ===
50643
+ /**
50644
+ * Detect if sidekick hooks are actually responding by spawning Claude
50645
+ * with a safe word and checking if it appears in the response.
50646
+ *
50647
+ * This tests actual hook execution, not just config file presence.
50648
+ * Useful for detecting plugins loaded via --plugin-dir that don't
50649
+ * appear in settings.json.
50650
+ *
50651
+ * @returns 'active' if hooks respond, 'inactive' if not, 'error' on failure
50652
+ */
50653
+ async detectPluginLiveness() {
50654
+ const safeWord = crypto.randomUUID().slice(0, 8);
50655
+ 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.)";
50656
+ return new Promise((resolve3) => {
50657
+ let resolved = false;
50658
+ const safeResolve = (value) => {
50659
+ if (!resolved) {
50660
+ resolved = true;
50661
+ clearTimeout(timeout);
50662
+ resolve3(value);
50663
+ }
50664
+ };
50665
+ const child = (0, node_child_process_1.spawn)("claude", ["-p", prompt], {
50666
+ env: { ...process.env, SIDEKICK_SAFE_WORD: safeWord },
50667
+ stdio: ["ignore", "pipe", "pipe"]
50668
+ });
50669
+ let stdout = "";
50670
+ let stderr = "";
50671
+ this.logger?.debug("Plugin liveness check started", { pid: child.pid, safeWord });
50672
+ child.stdout?.on("data", (data) => {
50673
+ stdout += data.toString();
50674
+ });
50675
+ child.stderr?.on("data", (data) => {
50676
+ stderr += data.toString();
50677
+ });
50678
+ const timeout = setTimeout(() => {
50679
+ this.logger?.warn("Plugin liveness check timed out after 30s");
50680
+ child.kill("SIGTERM");
50681
+ }, 3e4);
50682
+ child.on("close", (code, signal) => {
50683
+ if (signal === "SIGTERM") {
50684
+ safeResolve("error");
50685
+ return;
50686
+ }
50687
+ if (code !== 0) {
50688
+ this.logger?.warn("Plugin liveness check failed", { code, stderr: stderr.slice(0, 200) });
50689
+ safeResolve("error");
50690
+ return;
50691
+ }
50692
+ const isActive = stdout.includes(safeWord);
50693
+ this.logger?.debug("Plugin liveness check completed", { isActive, stdoutLength: stdout.length });
50694
+ safeResolve(isActive ? "active" : "inactive");
50695
+ });
50696
+ child.on("error", (err) => {
50697
+ this.logger?.warn("Plugin liveness check spawn error", { error: err.message });
50698
+ safeResolve("error");
50699
+ });
50700
+ });
50701
+ }
50474
50702
  };
50475
50703
  exports2.SetupStatusService = SetupStatusService;
50476
50704
  function createSetupStatusService(projectDir, homeDir) {
@@ -66380,18 +66608,41 @@ var require_hook_command = __commonJS({
66380
66608
  }
66381
66609
  return reason ?? additionalContext ?? "Blocked by Sidekick";
66382
66610
  }
66611
+ function loadSafeWordContext(safeWord, projectRoot, logger) {
66612
+ try {
66613
+ const resolver = (0, core_1.createAssetResolver)({
66614
+ defaultAssetsDir: (0, core_1.getDefaultAssetsDir)(),
66615
+ projectRoot
66616
+ });
66617
+ const template = resolver.resolveYaml("reminders/safe-word-liveness.yaml");
66618
+ if (template?.additionalContext) {
66619
+ return template.additionalContext.replace("{{safeWord}}", safeWord);
66620
+ }
66621
+ logger.error("safe-word-liveness.yaml missing additionalContext field", { projectRoot });
66622
+ return void 0;
66623
+ } catch (err) {
66624
+ logger.error("Failed to load safe-word-liveness.yaml", {
66625
+ error: err instanceof Error ? err.message : String(err),
66626
+ projectRoot
66627
+ });
66628
+ return void 0;
66629
+ }
66630
+ }
66631
+ function addUserMessage(response, userMessage) {
66632
+ if (userMessage) {
66633
+ response.systemMessage = userMessage;
66634
+ }
66635
+ }
66383
66636
  function translateSessionStart(internal) {
66384
66637
  const response = {};
66385
66638
  if (internal.blocking === true) {
66386
66639
  response.continue = false;
66387
66640
  response.stopReason = internal.reason ?? "Blocked by Sidekick";
66388
66641
  }
66389
- if (internal.userMessage) {
66390
- response.systemMessage = internal.userMessage;
66391
- }
66392
66642
  if (internal.additionalContext) {
66393
66643
  response.hookSpecificOutput = { hookEventName: "SessionStart", additionalContext: internal.additionalContext };
66394
66644
  }
66645
+ addUserMessage(response, internal.userMessage);
66395
66646
  return response;
66396
66647
  }
66397
66648
  function translateUserPromptSubmit(internal) {
@@ -66400,15 +66651,13 @@ var require_hook_command = __commonJS({
66400
66651
  response.decision = "block";
66401
66652
  response.reason = internal.reason ?? "Blocked by Sidekick";
66402
66653
  }
66403
- if (internal.userMessage) {
66404
- response.systemMessage = internal.userMessage;
66405
- }
66406
66654
  if (internal.additionalContext) {
66407
66655
  response.hookSpecificOutput = {
66408
66656
  hookEventName: "UserPromptSubmit",
66409
66657
  additionalContext: internal.additionalContext
66410
66658
  };
66411
66659
  }
66660
+ addUserMessage(response, internal.userMessage);
66412
66661
  return response;
66413
66662
  }
66414
66663
  function translatePreToolUse(internal) {
@@ -66426,9 +66675,7 @@ var require_hook_command = __commonJS({
66426
66675
  permissionDecisionReason: internal.additionalContext
66427
66676
  };
66428
66677
  }
66429
- if (internal.userMessage) {
66430
- response.systemMessage = internal.userMessage;
66431
- }
66678
+ addUserMessage(response, internal.userMessage);
66432
66679
  return response;
66433
66680
  }
66434
66681
  function translatePostToolUse(internal) {
@@ -66442,58 +66689,52 @@ var require_hook_command = __commonJS({
66442
66689
  additionalContext: internal.additionalContext
66443
66690
  };
66444
66691
  }
66445
- if (internal.userMessage) {
66446
- response.systemMessage = internal.userMessage;
66447
- }
66692
+ addUserMessage(response, internal.userMessage);
66448
66693
  return response;
66449
66694
  }
66450
66695
  function translateStop(internal) {
66451
66696
  const response = {};
66452
66697
  if (internal.blocking === true) {
66453
66698
  response.decision = "block";
66454
- response.reason = combineReasonAndContext(internal.reason, internal.additionalContext, "\n");
66455
66699
  if (!internal.reason && !internal.additionalContext) {
66456
66700
  response.reason = "Task not complete - please continue";
66701
+ } else {
66702
+ response.reason = combineReasonAndContext(internal.reason, internal.additionalContext, "\n");
66457
66703
  }
66458
66704
  }
66459
- if (internal.userMessage) {
66460
- response.systemMessage = internal.userMessage;
66461
- }
66705
+ addUserMessage(response, internal.userMessage);
66462
66706
  return response;
66463
66707
  }
66464
- function translateNotificationOnly(_internal) {
66465
- return {};
66466
- }
66467
66708
  var TRANSLATORS = {
66468
66709
  SessionStart: translateSessionStart,
66469
- SessionEnd: translateNotificationOnly,
66710
+ SessionEnd: () => ({}),
66470
66711
  UserPromptSubmit: translateUserPromptSubmit,
66471
66712
  PreToolUse: translatePreToolUse,
66472
66713
  PostToolUse: translatePostToolUse,
66473
66714
  Stop: translateStop,
66474
- PreCompact: translateNotificationOnly
66715
+ PreCompact: () => ({})
66475
66716
  };
66476
66717
  function translateToClaudeCodeFormat(hookName, internal) {
66477
- const translator = TRANSLATORS[hookName];
66478
- return translator(internal);
66718
+ return TRANSLATORS[hookName](internal);
66479
66719
  }
66480
- var KEBAB_TO_HOOK = {
66720
+ var HOOK_ARG_TO_NAME = {
66481
66721
  "session-start": "SessionStart",
66482
66722
  "session-end": "SessionEnd",
66483
66723
  "user-prompt-submit": "UserPromptSubmit",
66484
66724
  "pre-tool-use": "PreToolUse",
66485
66725
  "post-tool-use": "PostToolUse",
66486
66726
  stop: "Stop",
66487
- "pre-compact": "PreCompact"
66488
- };
66489
- var HOOK_ARG_TO_NAME = {
66490
- ...KEBAB_TO_HOOK,
66491
- ...Object.fromEntries(Object.keys(TRANSLATORS).map((name) => [name, name]))
66727
+ "pre-compact": "PreCompact",
66728
+ SessionStart: "SessionStart",
66729
+ SessionEnd: "SessionEnd",
66730
+ UserPromptSubmit: "UserPromptSubmit",
66731
+ PreToolUse: "PreToolUse",
66732
+ PostToolUse: "PostToolUse",
66733
+ Stop: "Stop",
66734
+ PreCompact: "PreCompact"
66492
66735
  };
66493
66736
  function parseHookArg(arg) {
66494
- if (!arg)
66495
- return void 0;
66496
- return HOOK_ARG_TO_NAME[arg];
66737
+ return arg ? HOOK_ARG_TO_NAME[arg] : void 0;
66497
66738
  }
66498
66739
  var DEGRADED_MODE_MESSAGES = {
66499
66740
  "not-run": {
@@ -66556,8 +66797,27 @@ var require_hook_command = __commonJS({
66556
66797
  }
66557
66798
  }
66558
66799
  async function handleUnifiedHookCommand(hookName, options, logger, stdout) {
66559
- const { projectRoot, hookInput, correlationId, runtime } = options;
66800
+ const { projectRoot, hookInput, correlationId, runtime, force } = options;
66560
66801
  logger.debug("Unified hook command invoked", { hookName, sessionId: hookInput.sessionId });
66802
+ if (!force) {
66803
+ try {
66804
+ const setupService = new core_1.SetupStatusService(projectRoot);
66805
+ const devMode = await setupService.getDevMode();
66806
+ if (devMode) {
66807
+ logger.debug("Dev-mode active, bailing early (let dev-mode hooks win)", {
66808
+ hookName,
66809
+ devMode
66810
+ });
66811
+ stdout.write("{}\n");
66812
+ return { exitCode: 0, output: "{}" };
66813
+ }
66814
+ } catch (err) {
66815
+ logger.warn("Failed to check plugin/dev-mode status, proceeding normally", {
66816
+ error: err instanceof Error ? err.message : String(err),
66817
+ hookName
66818
+ });
66819
+ }
66820
+ }
66561
66821
  const degradedResponse = await checkSetupState(projectRoot, hookName, logger);
66562
66822
  if (degradedResponse !== null) {
66563
66823
  const claudeResponse2 = translateToClaudeCodeFormat(hookName, degradedResponse);
@@ -66582,6 +66842,15 @@ var require_hook_command = __commonJS({
66582
66842
  runtime
66583
66843
  }, logger, captureStream);
66584
66844
  const internalResponse = parseInternalResponse(internalOutput.trim(), hookName, logger);
66845
+ if (hookName === "SessionStart") {
66846
+ const safeWord = process.env.SIDEKICK_SAFE_WORD ?? "nope";
66847
+ const safeWordContext = loadSafeWordContext(safeWord, projectRoot, logger);
66848
+ if (safeWordContext) {
66849
+ internalResponse.additionalContext = internalResponse.additionalContext ? `${internalResponse.additionalContext}
66850
+
66851
+ ${safeWordContext}` : safeWordContext;
66852
+ }
66853
+ }
66585
66854
  const claudeResponse = translateToClaudeCodeFormat(hookName, internalResponse);
66586
66855
  const outputStr = JSON.stringify(claudeResponse);
66587
66856
  stdout.write(`${outputStr}
@@ -69370,6 +69639,23 @@ Examples:
69370
69639
  stdout.write(USAGE_TEXT);
69371
69640
  return { exitCode: 0 };
69372
69641
  }
69642
+ if (!options.force) {
69643
+ try {
69644
+ const setupService = new core_1.SetupStatusService(projectDir);
69645
+ const devMode = await setupService.getDevMode();
69646
+ if (devMode) {
69647
+ logger.debug("Dev-mode active in statusline, bailing early (let dev-mode win)", {
69648
+ devMode
69649
+ });
69650
+ stdout.write("\n");
69651
+ return { exitCode: 0 };
69652
+ }
69653
+ } catch (err) {
69654
+ logger.warn("Failed to check plugin/dev-mode status for statusline, proceeding normally", {
69655
+ error: err instanceof Error ? err.message : String(err)
69656
+ });
69657
+ }
69658
+ }
69373
69659
  const format3 = options.format ?? "text";
69374
69660
  const useColors = format3 === "text";
69375
69661
  const startTime = performance.now();
@@ -70289,7 +70575,7 @@ var require_dev_mode = __commonJS({
70289
70575
  return cleanedCount;
70290
70576
  }
70291
70577
  function isDevHookCommand(command) {
70292
- return command?.includes("dev-hooks") ?? false;
70578
+ return command?.includes("dev-sidekick") ?? false;
70293
70579
  }
70294
70580
  async function cleanStateFolder(stateDir, label, stdout) {
70295
70581
  if (!await fileExists(stateDir)) {
@@ -70346,7 +70632,7 @@ var require_dev_mode = __commonJS({
70346
70632
  }
70347
70633
  async function doEnable(projectDir, stdout) {
70348
70634
  log(stdout, "step", "Enabling dev-mode hooks...");
70349
- const devHooksDir = node_path_1.default.join(projectDir, "scripts", "dev-hooks");
70635
+ const devHooksDir = node_path_1.default.join(projectDir, "scripts", "dev-sidekick");
70350
70636
  const settingsPath = node_path_1.default.join(projectDir, ".claude", "settings.local.json");
70351
70637
  const cliBin = node_path_1.default.join(projectDir, "packages", "sidekick-cli", "dist", "bin.js");
70352
70638
  for (const hook of HOOK_SCRIPTS) {
@@ -70368,7 +70654,7 @@ var require_dev_mode = __commonJS({
70368
70654
  log(stdout, "warn", "Dev-mode hooks already enabled");
70369
70655
  return { exitCode: 0 };
70370
70656
  }
70371
- const devHooksPath = "$CLAUDE_PROJECT_DIR/scripts/dev-hooks";
70657
+ const devHooksPath = "$CLAUDE_PROJECT_DIR/scripts/dev-sidekick";
70372
70658
  settings.hooks ?? (settings.hooks = {});
70373
70659
  const hookConfig = [
70374
70660
  { type: "SessionStart", script: "session-start" },
@@ -70393,6 +70679,8 @@ var require_dev_mode = __commonJS({
70393
70679
  }
70394
70680
  settings.statusLine = { type: "command", command: `${devHooksPath}/statusline` };
70395
70681
  await (0, promises_12.writeFile)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
70682
+ const setupService = new core_1.SetupStatusService(projectDir);
70683
+ await setupService.setDevMode(true);
70396
70684
  log(stdout, "info", `Dev-mode hooks enabled in ${settingsPath}`);
70397
70685
  log(stdout, "info", "");
70398
70686
  log(stdout, "info", "Registered hooks:");
@@ -70437,6 +70725,8 @@ var require_dev_mode = __commonJS({
70437
70725
  if (isDevHookCommand(settings.statusLine?.command)) {
70438
70726
  delete settings.statusLine;
70439
70727
  }
70728
+ const setupService = new core_1.SetupStatusService(projectDir);
70729
+ await setupService.setDevMode(false);
70440
70730
  if (Object.keys(settings).length === 0) {
70441
70731
  log(stdout, "info", `Settings now empty, removing ${settingsPath}`);
70442
70732
  await (0, promises_12.unlink)(settingsPath);
@@ -70450,7 +70740,7 @@ var require_dev_mode = __commonJS({
70450
70740
  return { exitCode: 0 };
70451
70741
  }
70452
70742
  async function doStatus(projectDir, stdout) {
70453
- const devHooksDir = node_path_1.default.join(projectDir, "scripts", "dev-hooks");
70743
+ const devHooksDir = node_path_1.default.join(projectDir, "scripts", "dev-sidekick");
70454
70744
  const settingsPath = node_path_1.default.join(projectDir, ".claude", "settings.local.json");
70455
70745
  const cliBin = node_path_1.default.join(projectDir, "packages", "sidekick-cli", "dist", "bin.js");
70456
70746
  stdout.write("Dev-Mode Status\n");
@@ -70467,7 +70757,7 @@ var require_dev_mode = __commonJS({
70467
70757
  stdout.write(`Dev-mode: ${colors.green}ENABLED${colors.reset}
70468
70758
 
70469
70759
  `);
70470
- stdout.write("Registered dev-hooks:\n");
70760
+ stdout.write("Registered dev-sidekick:\n");
70471
70761
  if (settings.hooks) {
70472
70762
  for (const [hookType, entries] of Object.entries(settings.hooks)) {
70473
70763
  for (const entry of entries ?? []) {
@@ -70651,8 +70941,8 @@ var require_dev_mode = __commonJS({
70651
70941
  var USAGE_TEXT = `Usage: sidekick dev-mode <command> [options]
70652
70942
 
70653
70943
  Commands:
70654
- enable Add dev-hooks to .claude/settings.local.json
70655
- disable Remove dev-hooks from settings.local.json
70944
+ enable Add dev-sidekick to .claude/settings.local.json
70945
+ disable Remove dev-sidekick from settings.local.json
70656
70946
  status Show current dev-mode state
70657
70947
  clean Truncate logs, kill daemon, clean state folders
70658
70948
  clean-all Full cleanup: clean + remove logs/sessions/state dirs
@@ -70904,6 +71194,18 @@ Examples:
70904
71194
  OPENROUTER_API_KEY=sk-xxx sidekick setup --personas --api-key-scope=user
70905
71195
  `;
70906
71196
  var STATUSLINE_COMMAND = "npx @scotthamilton77/sidekick statusline --project-dir=$CLAUDE_PROJECT_DIR";
71197
+ function getPluginStatusLabel(status) {
71198
+ switch (status) {
71199
+ case "plugin":
71200
+ return "installed";
71201
+ case "dev-mode":
71202
+ return "dev-mode (local)";
71203
+ case "both":
71204
+ return "conflict (both plugin and dev-mode detected!)";
71205
+ case "none":
71206
+ return "not installed";
71207
+ }
71208
+ }
70907
71209
  function getApiKeyStatusType(health) {
70908
71210
  switch (health) {
70909
71211
  case "healthy":
@@ -70914,6 +71216,37 @@ Examples:
70914
71216
  return "warning";
70915
71217
  }
70916
71218
  }
71219
+ function getPluginStatusIcon(status) {
71220
+ switch (status) {
71221
+ case "plugin":
71222
+ case "dev-mode":
71223
+ return "\u2713";
71224
+ case "both":
71225
+ return "\u26A0";
71226
+ case "none":
71227
+ return "\u2717";
71228
+ }
71229
+ }
71230
+ function getLivenessIcon(status) {
71231
+ switch (status) {
71232
+ case "active":
71233
+ return "\u2713";
71234
+ case "inactive":
71235
+ return "\u2717";
71236
+ case "error":
71237
+ return "\u26A0";
71238
+ }
71239
+ }
71240
+ function getLivenessLabel(status) {
71241
+ switch (status) {
71242
+ case "active":
71243
+ return "hooks responding";
71244
+ case "inactive":
71245
+ return "hooks not detected";
71246
+ case "error":
71247
+ return "check failed";
71248
+ }
71249
+ }
70917
71250
  async function configureStatusline(settingsPath, logger) {
70918
71251
  let settings = {};
70919
71252
  try {
@@ -71307,6 +71640,7 @@ Configured ${configuredCount} setting${configuredCount === 1 ? "" : "s"}.
71307
71640
  stdout.write("Checking configuration...\n\n");
71308
71641
  const doctorResult = await setupService.runDoctorCheck();
71309
71642
  const gitignore = await (0, core_1.detectGitignoreStatus)(projectDir);
71643
+ const pluginStatus = await setupService.detectPluginInstallation();
71310
71644
  if (doctorResult.fixes.length > 0) {
71311
71645
  stdout.write("Cache corrections:\n");
71312
71646
  for (const fix of doctorResult.fixes) {
@@ -71315,17 +71649,30 @@ Configured ${configuredCount} setting${configuredCount === 1 ? "" : "s"}.
71315
71649
  }
71316
71650
  stdout.write("\n");
71317
71651
  }
71652
+ stdout.write("Checking live status of Sidekick... this may take a few moments.\n");
71653
+ const liveness = await setupService.detectPluginLiveness();
71654
+ const pluginIcon = getPluginStatusIcon(pluginStatus);
71655
+ const pluginLabel = getPluginStatusLabel(pluginStatus);
71656
+ const livenessIcon = getLivenessIcon(liveness);
71657
+ const livenessLabel = getLivenessLabel(liveness);
71318
71658
  const statuslineIcon = doctorResult.statusline.actual === "configured" ? "\u2713" : "\u26A0";
71319
71659
  const gitignoreIcon = gitignore === "installed" ? "\u2713" : "\u26A0";
71320
71660
  const openRouterHealth = doctorResult.apiKeys.OPENROUTER_API_KEY.actual;
71321
71661
  const apiKeyIcon = openRouterHealth === "healthy" || openRouterHealth === "not-required" ? "\u2713" : "\u26A0";
71662
+ stdout.write("\n");
71663
+ stdout.write(`${pluginIcon} Plugin: ${pluginLabel}
71664
+ `);
71665
+ stdout.write(`${livenessIcon} Plugin Liveness: ${livenessLabel}
71666
+ `);
71322
71667
  stdout.write(`${statuslineIcon} Statusline: ${doctorResult.statusline.actual}
71323
71668
  `);
71324
71669
  stdout.write(`${gitignoreIcon} Gitignore: ${gitignore}
71325
71670
  `);
71326
71671
  stdout.write(`${apiKeyIcon} OpenRouter API Key: ${openRouterHealth}
71327
71672
  `);
71328
- const isHealthy = doctorResult.overallHealth === "healthy" && gitignore === "installed";
71673
+ const isPluginOk = pluginStatus === "plugin" || pluginStatus === "dev-mode";
71674
+ const isPluginLive = liveness === "active";
71675
+ const isHealthy = doctorResult.overallHealth === "healthy" && gitignore === "installed" && isPluginOk && isPluginLive;
71329
71676
  const overallIcon = isHealthy ? "\u2713" : "\u26A0";
71330
71677
  stdout.write(`${overallIcon} Overall: ${isHealthy ? "healthy" : "needs attention"}
71331
71678
  `);
@@ -71417,7 +71764,7 @@ var require_cli = __commonJS({
71417
71764
  var promises_12 = require("node:fs/promises");
71418
71765
  var node_stream_1 = require("node:stream");
71419
71766
  var yargs_parser_1 = __importDefault2(require_build());
71420
- var VERSION = true ? "0.0.7-alpha" : "dev";
71767
+ var VERSION = true ? "0.0.8" : "dev";
71421
71768
  function isInSandbox() {
71422
71769
  return process.env.SANDBOX_RUNTIME === "1";
71423
71770
  }
@@ -71668,7 +72015,8 @@ Run 'sidekick hook --help' for available hooks.
71668
72015
  projectRoot: runtime.projectRoot,
71669
72016
  hookInput,
71670
72017
  correlationId: runtime.correlationId,
71671
- runtime
72018
+ runtime,
72019
+ force: parsed.force
71672
72020
  }, runtime.logger, stdout);
71673
72021
  return { exitCode: result.exitCode, stdout: result.output, stderr: "" };
71674
72022
  }
@@ -71695,7 +72043,8 @@ Run 'sidekick hook --help' for available hooks.
71695
72043
  hookInput: parsedHookInput,
71696
72044
  configService: runtime.config,
71697
72045
  assets: runtime.assets,
71698
- help: parsed.help
72046
+ help: parsed.help,
72047
+ force: parsed.force
71699
72048
  });
71700
72049
  return { exitCode: result.exitCode, stdout: "", stderr: "" };
71701
72050
  }
package/dist/daemon.js CHANGED
@@ -15764,7 +15764,9 @@ var require_setup_status = __commonJS({
15764
15764
  apiKeys: zod_1.z.object({
15765
15765
  OPENROUTER_API_KEY: exports2.ApiKeyHealthSchema,
15766
15766
  OPENAI_API_KEY: exports2.ApiKeyHealthSchema
15767
- })
15767
+ }),
15768
+ /** True if sidekick plugin detected at user scope via `claude plugin list` */
15769
+ pluginDetected: zod_1.z.boolean().optional()
15768
15770
  });
15769
15771
  exports2.GitignoreStatusSchema = zod_1.z.enum([
15770
15772
  "unknown",
@@ -15786,7 +15788,11 @@ var require_setup_status = __commonJS({
15786
15788
  OPENROUTER_API_KEY: exports2.ProjectApiKeyHealthSchema,
15787
15789
  OPENAI_API_KEY: exports2.ProjectApiKeyHealthSchema
15788
15790
  }),
15789
- gitignore: exports2.GitignoreStatusSchema.optional().default("unknown")
15791
+ gitignore: exports2.GitignoreStatusSchema.optional().default("unknown"),
15792
+ /** True if sidekick plugin detected at project scope via `claude plugin list` */
15793
+ pluginDetected: zod_1.z.boolean().optional(),
15794
+ /** True if dev-mode hooks are enabled for this project. Set by `dev-mode enable/disable`. */
15795
+ devMode: zod_1.z.boolean().optional()
15790
15796
  });
15791
15797
  }
15792
15798
  });
@@ -31819,7 +31825,8 @@ var require_package3 = __commonJS({
31819
31825
  sidekick: "node packages/sidekick-cli/dist/bin.js",
31820
31826
  "test:dist:setup": "./scripts/test-dist.sh setup",
31821
31827
  "test:dist:teardown": "./scripts/test-dist.sh teardown",
31822
- "test:dist:rebuild": "./scripts/test-dist.sh rebuild"
31828
+ "test:dist:rebuild": "./scripts/test-dist.sh rebuild",
31829
+ "publish:dist": "./scripts/publish-dist.sh"
31823
31830
  },
31824
31831
  devDependencies: {
31825
31832
  "@eslint/js": "^9.39.1",
@@ -49192,12 +49199,15 @@ var require_setup_status_service = __commonJS({
49192
49199
  var fs = __importStar(require("node:fs/promises"));
49193
49200
  var path = __importStar(require("node:path"));
49194
49201
  var os = __importStar(require("node:os"));
49202
+ var crypto = __importStar(require("node:crypto"));
49203
+ var node_child_process_1 = require("node:child_process");
49195
49204
  var types_1 = require_dist();
49196
49205
  var shared_providers_1 = require_dist3();
49197
49206
  function isSidekickStatuslineCommand(command) {
49198
- if (!command)
49199
- return false;
49200
- return command.toLowerCase().includes("sidekick");
49207
+ return command?.toLowerCase().includes("sidekick") ?? false;
49208
+ }
49209
+ function isDevModeCommand(command) {
49210
+ return command.includes("dev-sidekick");
49201
49211
  }
49202
49212
  var SetupStatusService = class {
49203
49213
  constructor(projectDir2, options) {
@@ -49328,6 +49338,123 @@ var require_setup_status_service = __commonJS({
49328
49338
  }
49329
49339
  return "not-setup";
49330
49340
  }
49341
+ /**
49342
+ * Detect plugin installation status.
49343
+ *
49344
+ * Uses `claude plugin list --json` to detect installed plugins,
49345
+ * and checks settings files for dev-mode hooks.
49346
+ *
49347
+ * @returns Installation status:
49348
+ * - 'plugin': Sidekick plugin installed via Claude marketplace
49349
+ * - 'dev-mode': Dev-mode hooks installed (dev-sidekick path)
49350
+ * - 'both': Both plugin and dev-mode hooks (conflict state)
49351
+ * - 'none': No sidekick hooks detected
49352
+ */
49353
+ async detectPluginInstallation() {
49354
+ const hasPlugin = await this.detectPluginFromCLI();
49355
+ const hasDevMode = await this.detectDevModeFromSettings();
49356
+ if (hasPlugin && hasDevMode)
49357
+ return "both";
49358
+ if (hasPlugin)
49359
+ return "plugin";
49360
+ if (hasDevMode)
49361
+ return "dev-mode";
49362
+ return "none";
49363
+ }
49364
+ /**
49365
+ * Detect if sidekick plugin is installed via `claude plugin list --json`.
49366
+ */
49367
+ async detectPluginFromCLI() {
49368
+ return new Promise((resolve3) => {
49369
+ let resolved = false;
49370
+ const safeResolve = (value) => {
49371
+ if (!resolved) {
49372
+ resolved = true;
49373
+ clearTimeout(timeout);
49374
+ resolve3(value);
49375
+ }
49376
+ };
49377
+ const child = (0, node_child_process_1.spawn)("claude", ["plugin", "list", "--json"], {
49378
+ stdio: ["ignore", "pipe", "pipe"]
49379
+ });
49380
+ let stdout = "";
49381
+ child.stdout?.on("data", (data) => {
49382
+ stdout += data.toString();
49383
+ });
49384
+ const timeout = setTimeout(() => {
49385
+ this.logger?.warn("Plugin detection timed out after 10s");
49386
+ child.kill("SIGTERM");
49387
+ safeResolve(false);
49388
+ }, 1e4);
49389
+ child.on("close", (code) => {
49390
+ if (code !== 0) {
49391
+ this.logger?.debug("claude plugin list failed", { code });
49392
+ safeResolve(false);
49393
+ return;
49394
+ }
49395
+ try {
49396
+ const plugins = JSON.parse(stdout);
49397
+ const hasSidekick = plugins.some((p) => p.id.toLowerCase().includes("sidekick"));
49398
+ this.logger?.debug("Plugin detection completed", { pluginCount: plugins.length, hasSidekick });
49399
+ safeResolve(hasSidekick);
49400
+ } catch (err) {
49401
+ this.logger?.debug("Failed to parse plugin list JSON", {
49402
+ error: err instanceof Error ? err.message : String(err)
49403
+ });
49404
+ safeResolve(false);
49405
+ }
49406
+ });
49407
+ child.on("error", (err) => {
49408
+ this.logger?.debug("claude plugin list spawn error", { error: err.message });
49409
+ safeResolve(false);
49410
+ });
49411
+ });
49412
+ }
49413
+ /**
49414
+ * Detect if dev-mode hooks are installed by checking settings files.
49415
+ */
49416
+ async detectDevModeFromSettings() {
49417
+ const settingsPaths = [
49418
+ path.join(this.homeDir, ".claude", "settings.json"),
49419
+ path.join(this.projectDir, ".claude", "settings.local.json")
49420
+ ];
49421
+ for (const settingsPath of settingsPaths) {
49422
+ try {
49423
+ const content = await fs.readFile(settingsPath, "utf-8");
49424
+ const settings = JSON.parse(content);
49425
+ if (this.hasDevModeHooks(settings)) {
49426
+ return true;
49427
+ }
49428
+ } catch {
49429
+ }
49430
+ }
49431
+ return false;
49432
+ }
49433
+ /**
49434
+ * Check a Claude settings object for dev-mode hooks.
49435
+ */
49436
+ hasDevModeHooks(settings) {
49437
+ const statusLineCommand = settings.statusLine?.command;
49438
+ if (statusLineCommand && isDevModeCommand(statusLineCommand)) {
49439
+ return true;
49440
+ }
49441
+ if (settings.hooks) {
49442
+ for (const hookEntries of Object.values(settings.hooks)) {
49443
+ if (!Array.isArray(hookEntries))
49444
+ continue;
49445
+ for (const entry of hookEntries) {
49446
+ if (!entry?.hooks)
49447
+ continue;
49448
+ for (const hook of entry.hooks) {
49449
+ if (hook?.command && isDevModeCommand(hook.command)) {
49450
+ return true;
49451
+ }
49452
+ }
49453
+ }
49454
+ }
49455
+ }
49456
+ return false;
49457
+ }
49331
49458
  // === Merged getters ===
49332
49459
  async getStatuslineHealth() {
49333
49460
  const project = await this.getProjectStatus();
@@ -49360,6 +49487,47 @@ var require_setup_status_service = __commonJS({
49360
49487
  const openrouterKey = await this.getEffectiveApiKeyHealth("OPENROUTER_API_KEY");
49361
49488
  return statusline === "configured" && (openrouterKey === "healthy" || openrouterKey === "not-required");
49362
49489
  }
49490
+ // === Dev-mode and plugin detection helpers ===
49491
+ /**
49492
+ * Get the devMode flag from project status.
49493
+ * Returns false if project status doesn't exist or devMode is not set.
49494
+ */
49495
+ async getDevMode() {
49496
+ const project = await this.getProjectStatus();
49497
+ return project?.devMode ?? false;
49498
+ }
49499
+ /**
49500
+ * Set the devMode flag in project status.
49501
+ * Creates project status if it doesn't exist.
49502
+ */
49503
+ async setDevMode(enabled) {
49504
+ const current = await this.getProjectStatus();
49505
+ if (current) {
49506
+ await this.updateProjectStatus({ devMode: enabled });
49507
+ } else {
49508
+ await this.writeProjectStatus({
49509
+ version: 1,
49510
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
49511
+ autoConfigured: false,
49512
+ statusline: "user",
49513
+ apiKeys: {
49514
+ OPENROUTER_API_KEY: "user",
49515
+ OPENAI_API_KEY: "user"
49516
+ },
49517
+ gitignore: "unknown",
49518
+ devMode: enabled
49519
+ });
49520
+ }
49521
+ }
49522
+ /**
49523
+ * Check if the sidekick plugin is installed at any scope.
49524
+ * Reads pluginDetected flags from both user and project status.
49525
+ */
49526
+ async isPluginInstalled() {
49527
+ const user = await this.getUserStatus();
49528
+ const project = await this.getProjectStatus();
49529
+ return (user?.pluginDetected ?? false) || (project?.pluginDetected ?? false);
49530
+ }
49363
49531
  // === Auto-config helpers ===
49364
49532
  async isUserSetupComplete() {
49365
49533
  const user = await this.getUserStatus();
@@ -49495,6 +49663,66 @@ var require_setup_status_service = __commonJS({
49495
49663
  const isHealthy = await this.isHealthy();
49496
49664
  return isHealthy ? "healthy" : "unhealthy";
49497
49665
  }
49666
+ // === Plugin Liveness Detection ===
49667
+ /**
49668
+ * Detect if sidekick hooks are actually responding by spawning Claude
49669
+ * with a safe word and checking if it appears in the response.
49670
+ *
49671
+ * This tests actual hook execution, not just config file presence.
49672
+ * Useful for detecting plugins loaded via --plugin-dir that don't
49673
+ * appear in settings.json.
49674
+ *
49675
+ * @returns 'active' if hooks respond, 'inactive' if not, 'error' on failure
49676
+ */
49677
+ async detectPluginLiveness() {
49678
+ const safeWord = crypto.randomUUID().slice(0, 8);
49679
+ 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.)";
49680
+ return new Promise((resolve3) => {
49681
+ let resolved = false;
49682
+ const safeResolve = (value) => {
49683
+ if (!resolved) {
49684
+ resolved = true;
49685
+ clearTimeout(timeout);
49686
+ resolve3(value);
49687
+ }
49688
+ };
49689
+ const child = (0, node_child_process_1.spawn)("claude", ["-p", prompt], {
49690
+ env: { ...process.env, SIDEKICK_SAFE_WORD: safeWord },
49691
+ stdio: ["ignore", "pipe", "pipe"]
49692
+ });
49693
+ let stdout = "";
49694
+ let stderr = "";
49695
+ this.logger?.debug("Plugin liveness check started", { pid: child.pid, safeWord });
49696
+ child.stdout?.on("data", (data) => {
49697
+ stdout += data.toString();
49698
+ });
49699
+ child.stderr?.on("data", (data) => {
49700
+ stderr += data.toString();
49701
+ });
49702
+ const timeout = setTimeout(() => {
49703
+ this.logger?.warn("Plugin liveness check timed out after 30s");
49704
+ child.kill("SIGTERM");
49705
+ }, 3e4);
49706
+ child.on("close", (code, signal) => {
49707
+ if (signal === "SIGTERM") {
49708
+ safeResolve("error");
49709
+ return;
49710
+ }
49711
+ if (code !== 0) {
49712
+ this.logger?.warn("Plugin liveness check failed", { code, stderr: stderr.slice(0, 200) });
49713
+ safeResolve("error");
49714
+ return;
49715
+ }
49716
+ const isActive = stdout.includes(safeWord);
49717
+ this.logger?.debug("Plugin liveness check completed", { isActive, stdoutLength: stdout.length });
49718
+ safeResolve(isActive ? "active" : "inactive");
49719
+ });
49720
+ child.on("error", (err) => {
49721
+ this.logger?.warn("Plugin liveness check spawn error", { error: err.message });
49722
+ safeResolve("error");
49723
+ });
49724
+ });
49725
+ }
49498
49726
  };
49499
49727
  exports2.SetupStatusService = SetupStatusService;
49500
49728
  function createSetupStatusService(projectDir2, homeDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scotthamilton77/sidekick",
3
- "version": "0.0.7-alpha",
3
+ "version": "0.0.8",
4
4
  "description": "AI pair programming assistant with personas, session tracking, and contextual nudges",
5
5
  "bin": {
6
6
  "sidekick": "dist/bin.js"