@scotthamilton77/sidekick 0.0.8-alpha.0 → 0.0.8-alpha.1

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/bin.js CHANGED
@@ -16704,7 +16704,7 @@ var require_setup_status = __commonJS({
16704
16704
  "../types/dist/setup-status.js"(exports2) {
16705
16705
  "use strict";
16706
16706
  Object.defineProperty(exports2, "__esModule", { value: true });
16707
- exports2.ProjectSetupStatusSchema = exports2.GitignoreStatusSchema = exports2.UserSetupStatusSchema = exports2.ProjectApiKeyHealthSchema = exports2.ApiKeyHealthSchema = void 0;
16707
+ exports2.ProjectSetupStatusSchema = exports2.ProjectApiKeyValueSchema = exports2.GitignoreStatusSchema = exports2.UserSetupStatusSchema = exports2.UserApiKeyValueSchema = exports2.StatuslineStatusSchema = exports2.ProjectApiKeyStatusSchema = exports2.UserApiKeyStatusSchema = exports2.ApiKeyScopeSchema = exports2.ScopeStatusSchema = exports2.ProjectApiKeyHealthSchema = exports2.ApiKeyHealthSchema = void 0;
16708
16708
  var zod_1 = require_zod();
16709
16709
  exports2.ApiKeyHealthSchema = zod_1.z.enum([
16710
16710
  "missing",
@@ -16727,6 +16727,42 @@ var require_setup_status = __commonJS({
16727
16727
  "user"
16728
16728
  // Deferring to user-level
16729
16729
  ]);
16730
+ exports2.ScopeStatusSchema = zod_1.z.enum(["healthy", "invalid", "missing"]);
16731
+ exports2.ApiKeyScopeSchema = zod_1.z.enum(["project", "user", "env"]);
16732
+ exports2.UserApiKeyStatusSchema = zod_1.z.object({
16733
+ /** Which scope's key is being used (first valid in priority order) */
16734
+ used: zod_1.z.enum(["user", "env"]).nullable(),
16735
+ /** Overall status based on the used key */
16736
+ status: exports2.ScopeStatusSchema,
16737
+ /** Per-scope breakdown */
16738
+ scopes: zod_1.z.object({
16739
+ user: exports2.ScopeStatusSchema,
16740
+ env: exports2.ScopeStatusSchema
16741
+ })
16742
+ });
16743
+ exports2.ProjectApiKeyStatusSchema = zod_1.z.object({
16744
+ /** Which scope's key is being used (first valid in priority order) */
16745
+ used: zod_1.z.enum(["project", "user", "env"]).nullable(),
16746
+ /** Overall status based on the used key */
16747
+ status: exports2.ScopeStatusSchema,
16748
+ /** Per-scope breakdown */
16749
+ scopes: zod_1.z.object({
16750
+ project: exports2.ScopeStatusSchema,
16751
+ user: exports2.ScopeStatusSchema,
16752
+ env: exports2.ScopeStatusSchema
16753
+ })
16754
+ });
16755
+ exports2.StatuslineStatusSchema = zod_1.z.enum([
16756
+ "user",
16757
+ // Configured in ~/.claude/settings.json
16758
+ "project",
16759
+ // Configured in .claude/settings.local.json
16760
+ "both",
16761
+ // Configured in both (project overrides user)
16762
+ "none"
16763
+ // Not configured anywhere
16764
+ ]);
16765
+ exports2.UserApiKeyValueSchema = zod_1.z.union([exports2.ApiKeyHealthSchema, exports2.UserApiKeyStatusSchema]);
16730
16766
  exports2.UserSetupStatusSchema = zod_1.z.object({
16731
16767
  version: zod_1.z.literal(1),
16732
16768
  lastUpdatedAt: zod_1.z.string(),
@@ -16736,10 +16772,10 @@ var require_setup_status = __commonJS({
16736
16772
  defaultStatuslineScope: zod_1.z.enum(["user", "project"]),
16737
16773
  defaultApiKeyScope: zod_1.z.enum(["user", "project", "skip"])
16738
16774
  }),
16739
- statusline: zod_1.z.enum(["configured", "skipped"]),
16775
+ statusline: exports2.StatuslineStatusSchema,
16740
16776
  apiKeys: zod_1.z.object({
16741
- OPENROUTER_API_KEY: exports2.ApiKeyHealthSchema,
16742
- OPENAI_API_KEY: exports2.ApiKeyHealthSchema
16777
+ OPENROUTER_API_KEY: exports2.UserApiKeyValueSchema,
16778
+ OPENAI_API_KEY: exports2.UserApiKeyValueSchema
16743
16779
  }),
16744
16780
  /** True if sidekick plugin detected at user scope via `claude plugin list` */
16745
16781
  pluginDetected: zod_1.z.boolean().optional()
@@ -16754,15 +16790,16 @@ var require_setup_status = __commonJS({
16754
16790
  "installed"
16755
16791
  // Sidekick section present with all required entries
16756
16792
  ]);
16793
+ exports2.ProjectApiKeyValueSchema = zod_1.z.union([exports2.ProjectApiKeyHealthSchema, exports2.ProjectApiKeyStatusSchema]);
16757
16794
  exports2.ProjectSetupStatusSchema = zod_1.z.object({
16758
16795
  version: zod_1.z.literal(1),
16759
16796
  lastUpdatedAt: zod_1.z.string(),
16760
16797
  // ISO timestamp
16761
16798
  autoConfigured: zod_1.z.boolean(),
16762
- statusline: zod_1.z.enum(["configured", "skipped", "user"]),
16799
+ statusline: exports2.StatuslineStatusSchema,
16763
16800
  apiKeys: zod_1.z.object({
16764
- OPENROUTER_API_KEY: exports2.ProjectApiKeyHealthSchema,
16765
- OPENAI_API_KEY: exports2.ProjectApiKeyHealthSchema
16801
+ OPENROUTER_API_KEY: exports2.ProjectApiKeyValueSchema,
16802
+ OPENAI_API_KEY: exports2.ProjectApiKeyValueSchema
16766
16803
  }),
16767
16804
  gitignore: exports2.GitignoreStatusSchema.optional().default("unknown"),
16768
16805
  /** True if sidekick plugin detected at project scope via `claude plugin list` */
@@ -50025,7 +50062,7 @@ var require_validation = __commonJS({
50025
50062
  exports2.validateOpenRouterKey = validateOpenRouterKey;
50026
50063
  exports2.validateOpenAIKey = validateOpenAIKey;
50027
50064
  var VALIDATION_ENDPOINTS = {
50028
- openrouter: "https://openrouter.ai/api/v1/models",
50065
+ openrouter: "https://openrouter.ai/api/v1/key",
50029
50066
  openai: "https://api.openai.com/v1/models"
50030
50067
  };
50031
50068
  async function validateApiKey(provider, apiKey, logger) {
@@ -50185,6 +50222,25 @@ var require_setup_status_service = __commonJS({
50185
50222
  function isDevModeCommand(command) {
50186
50223
  return command.includes("dev-sidekick");
50187
50224
  }
50225
+ function toScopeStatus(health) {
50226
+ if (health === "healthy")
50227
+ return "healthy";
50228
+ if (health === "invalid")
50229
+ return "invalid";
50230
+ return "missing";
50231
+ }
50232
+ function determineOverallStatus(used, anyKeyFound) {
50233
+ if (used)
50234
+ return "healthy";
50235
+ if (anyKeyFound)
50236
+ return "invalid";
50237
+ return "missing";
50238
+ }
50239
+ var SCOPE_TO_SOURCE = {
50240
+ project: "project-env",
50241
+ user: "user-env",
50242
+ env: "env-var"
50243
+ };
50188
50244
  var SetupStatusService = class {
50189
50245
  constructor(projectDir, options) {
50190
50246
  this.projectDir = projectDir;
@@ -50272,47 +50328,152 @@ var require_setup_status_service = __commonJS({
50272
50328
  // === Actual config detection ===
50273
50329
  /**
50274
50330
  * Detect actual API key by reading .env files.
50275
- * Checks environment variable first, then project .env, then user .env.
50276
- * Returns the key value if found, null otherwise.
50331
+ * Priority: project .env user .env → environment variable.
50332
+ * Returns both the key value and where it was found.
50277
50333
  */
50278
50334
  async detectActualApiKey(keyName) {
50335
+ const projectKey = await this.readKeyFromEnvFile(path.join(this.projectDir, ".sidekick", ".env"), keyName);
50336
+ if (projectKey) {
50337
+ return { key: projectKey, source: "project-env" };
50338
+ }
50339
+ const userKey = await this.readKeyFromEnvFile(path.join(this.homeDir, ".sidekick", ".env"), keyName);
50340
+ if (userKey) {
50341
+ return { key: userKey, source: "user-env" };
50342
+ }
50279
50343
  if (process.env[keyName]) {
50280
- return process.env[keyName];
50344
+ return { key: process.env[keyName] ?? null, source: "env-var" };
50281
50345
  }
50282
- const envPaths = [path.join(this.projectDir, ".sidekick", ".env"), path.join(this.homeDir, ".sidekick", ".env")];
50283
- for (const envPath of envPaths) {
50284
- try {
50285
- const content = await fs.readFile(envPath, "utf-8");
50286
- const match = content.match(new RegExp(`^${keyName}=(.+)$`, "m"));
50287
- if (match) {
50288
- return match[1];
50289
- }
50290
- } catch {
50291
- }
50346
+ return { key: null, source: null };
50347
+ }
50348
+ /**
50349
+ * Read API key from a specific .env file.
50350
+ * @returns The key value if found, null otherwise.
50351
+ */
50352
+ async readKeyFromEnvFile(envPath, keyName) {
50353
+ try {
50354
+ const content = await fs.readFile(envPath, "utf-8");
50355
+ const match = content.match(new RegExp(`^${keyName}=(.+)$`, "m"));
50356
+ return match ? match[1] : null;
50357
+ } catch {
50358
+ return null;
50292
50359
  }
50293
- return null;
50360
+ }
50361
+ /**
50362
+ * Detect API key across ALL scopes and optionally validate each.
50363
+ * New priority order: project → user → env
50364
+ *
50365
+ * @param keyName - The API key name (OPENROUTER_API_KEY or OPENAI_API_KEY)
50366
+ * @param skipValidation - If true, found keys are marked 'healthy' without validation
50367
+ * @returns Detection results for all three scopes
50368
+ */
50369
+ async detectAllApiKeys(keyName, skipValidation = false) {
50370
+ const validateFn = keyName === "OPENROUTER_API_KEY" ? shared_providers_1.validateOpenRouterKey : shared_providers_1.validateOpenAIKey;
50371
+ const projectEnvPath = path.join(this.projectDir, ".sidekick", ".env");
50372
+ const userEnvPath = path.join(this.homeDir, ".sidekick", ".env");
50373
+ const projectKey = await this.readKeyFromEnvFile(projectEnvPath, keyName);
50374
+ const userKey = await this.readKeyFromEnvFile(userEnvPath, keyName);
50375
+ const rawEnvKey = process.env[keyName] ?? null;
50376
+ const envKey = rawEnvKey && rawEnvKey !== projectKey && rawEnvKey !== userKey ? rawEnvKey : null;
50377
+ const getStatus = async (key) => {
50378
+ if (!key)
50379
+ return "missing";
50380
+ if (skipValidation)
50381
+ return "healthy";
50382
+ const result = await validateFn(key, this.logger);
50383
+ return result.valid ? "healthy" : "invalid";
50384
+ };
50385
+ const [projectStatus, userStatus, envStatus] = await Promise.all([
50386
+ getStatus(projectKey),
50387
+ getStatus(userKey),
50388
+ getStatus(envKey)
50389
+ ]);
50390
+ return {
50391
+ project: { found: projectKey !== null, key: projectKey, status: projectStatus },
50392
+ user: { found: userKey !== null, key: userKey, status: userStatus },
50393
+ env: { found: envKey !== null, key: envKey, status: envStatus }
50394
+ };
50395
+ }
50396
+ /**
50397
+ * Build UserApiKeyStatus from detection results.
50398
+ * User-level status only includes 'user' and 'env' scopes.
50399
+ * Priority for 'used': user → env (first valid)
50400
+ */
50401
+ buildUserApiKeyStatus(detection) {
50402
+ let used = null;
50403
+ if (detection.user.status === "healthy") {
50404
+ used = "user";
50405
+ } else if (detection.env.status === "healthy") {
50406
+ used = "env";
50407
+ }
50408
+ const status = determineOverallStatus(used, detection.user.found || detection.env.found);
50409
+ return {
50410
+ used,
50411
+ status,
50412
+ scopes: {
50413
+ user: detection.user.status,
50414
+ env: detection.env.status
50415
+ }
50416
+ };
50417
+ }
50418
+ /**
50419
+ * Build ProjectApiKeyStatus from detection results.
50420
+ * Project-level status includes all three scopes.
50421
+ * Priority for 'used': project → user → env (first valid)
50422
+ */
50423
+ buildProjectApiKeyStatus(detection) {
50424
+ let used = null;
50425
+ if (detection.project.status === "healthy") {
50426
+ used = "project";
50427
+ } else if (detection.user.status === "healthy") {
50428
+ used = "user";
50429
+ } else if (detection.env.status === "healthy") {
50430
+ used = "env";
50431
+ }
50432
+ const status = determineOverallStatus(used, detection.project.found || detection.user.found || detection.env.found);
50433
+ return {
50434
+ used,
50435
+ status,
50436
+ scopes: {
50437
+ project: detection.project.status,
50438
+ user: detection.user.status,
50439
+ env: detection.env.status
50440
+ }
50441
+ };
50294
50442
  }
50295
50443
  /**
50296
50444
  * Detect actual statusline configuration by reading Claude settings files.
50297
- * Checks both user (~/.claude/settings.json) and project (.claude/settings.local.json).
50445
+ * Returns WHERE the statusline is configured, matching PluginInstallationStatus pattern.
50298
50446
  */
50299
50447
  async detectActualStatusline() {
50300
- const settingsPaths = [
50301
- path.join(this.homeDir, ".claude", "settings.json"),
50302
- path.join(this.projectDir, ".claude", "settings.local.json")
50303
- ];
50304
- for (const settingsPath of settingsPaths) {
50305
- try {
50306
- const content = await fs.readFile(settingsPath, "utf-8");
50307
- const settings = JSON.parse(content);
50308
- const statusLine = settings.statusLine;
50309
- if (isSidekickStatuslineCommand(statusLine?.command)) {
50310
- return "configured";
50311
- }
50312
- } catch {
50448
+ const userSettingsPath = path.join(this.homeDir, ".claude", "settings.json");
50449
+ const projectSettingsPath = path.join(this.projectDir, ".claude", "settings.local.json");
50450
+ let inUser = false;
50451
+ let inProject = false;
50452
+ try {
50453
+ const content = await fs.readFile(userSettingsPath, "utf-8");
50454
+ const settings = JSON.parse(content);
50455
+ const statusLine = settings.statusLine;
50456
+ if (isSidekickStatuslineCommand(statusLine?.command)) {
50457
+ inUser = true;
50313
50458
  }
50459
+ } catch {
50314
50460
  }
50315
- return "not-setup";
50461
+ try {
50462
+ const content = await fs.readFile(projectSettingsPath, "utf-8");
50463
+ const settings = JSON.parse(content);
50464
+ const statusLine = settings.statusLine;
50465
+ if (isSidekickStatuslineCommand(statusLine?.command)) {
50466
+ inProject = true;
50467
+ }
50468
+ } catch {
50469
+ }
50470
+ if (inUser && inProject)
50471
+ return "both";
50472
+ if (inProject)
50473
+ return "project";
50474
+ if (inUser)
50475
+ return "user";
50476
+ return "none";
50316
50477
  }
50317
50478
  /**
50318
50479
  * Detect plugin installation status.
@@ -50432,36 +50593,52 @@ var require_setup_status_service = __commonJS({
50432
50593
  return false;
50433
50594
  }
50434
50595
  // === Merged getters ===
50435
- async getStatuslineHealth() {
50596
+ /**
50597
+ * Get cached statusline status. Project cache takes precedence over user cache.
50598
+ * Returns 'none' if no cache exists.
50599
+ */
50600
+ async getStatuslineStatus() {
50436
50601
  const project = await this.getProjectStatus();
50437
- if (project?.statusline === "configured")
50438
- return "configured";
50439
- if (project?.statusline === "skipped")
50440
- return "skipped";
50441
- if (project?.statusline === "user") {
50442
- const user2 = await this.getUserStatus();
50443
- return user2?.statusline ?? "not-setup";
50444
- }
50602
+ if (project?.statusline)
50603
+ return project.statusline;
50445
50604
  const user = await this.getUserStatus();
50446
- return user?.statusline ?? "not-setup";
50605
+ if (user?.statusline)
50606
+ return user.statusline;
50607
+ return "none";
50608
+ }
50609
+ /**
50610
+ * @deprecated Use getStatuslineStatus() instead
50611
+ */
50612
+ async getStatuslineHealth() {
50613
+ return this.getStatuslineStatus();
50447
50614
  }
50448
50615
  async getApiKeyHealth(key) {
50449
50616
  const project = await this.getProjectStatus();
50450
50617
  const projectHealth = project?.apiKeys[key];
50451
50618
  if (projectHealth && projectHealth !== "user") {
50619
+ if (typeof projectHealth === "object") {
50620
+ return toScopeStatus(projectHealth.status);
50621
+ }
50452
50622
  return projectHealth;
50453
50623
  }
50454
50624
  const user = await this.getUserStatus();
50455
- return user?.apiKeys[key] ?? "missing";
50625
+ const userHealth = user?.apiKeys[key];
50626
+ if (!userHealth)
50627
+ return "missing";
50628
+ if (typeof userHealth === "object") {
50629
+ return toScopeStatus(userHealth.status);
50630
+ }
50631
+ return userHealth;
50456
50632
  }
50457
50633
  async getEffectiveApiKeyHealth(key) {
50458
50634
  const health = await this.getApiKeyHealth(key);
50459
50635
  return health === "user" ? "missing" : health;
50460
50636
  }
50461
50637
  async isHealthy() {
50462
- const statusline = await this.getStatuslineHealth();
50638
+ const statusline = await this.getStatuslineStatus();
50463
50639
  const openrouterKey = await this.getEffectiveApiKeyHealth("OPENROUTER_API_KEY");
50464
- return statusline === "configured" && (openrouterKey === "healthy" || openrouterKey === "not-required");
50640
+ const statuslineOk = statusline !== "none";
50641
+ return statuslineOk && (openrouterKey === "healthy" || openrouterKey === "not-required");
50465
50642
  }
50466
50643
  // === Dev-mode and plugin detection helpers ===
50467
50644
  /**
@@ -50474,21 +50651,38 @@ var require_setup_status_service = __commonJS({
50474
50651
  }
50475
50652
  /**
50476
50653
  * Set the devMode flag in project status.
50477
- * Creates project status if it doesn't exist.
50654
+ * Creates project status if it doesn't exist, detecting actual configuration state.
50478
50655
  */
50479
50656
  async setDevMode(enabled) {
50480
50657
  const current = await this.getProjectStatus();
50481
50658
  if (current) {
50482
50659
  await this.updateProjectStatus({ devMode: enabled });
50483
50660
  } else {
50661
+ const statusline = await this.detectActualStatusline();
50662
+ const openRouterResult = await this.detectActualApiKey("OPENROUTER_API_KEY");
50663
+ const openAIResult = await this.detectActualApiKey("OPENAI_API_KEY");
50664
+ const getApiKeyHealth = async (result) => {
50665
+ if (result.source === "project-env") {
50666
+ return "pending-validation";
50667
+ }
50668
+ if (result.source === "user-env" || result.source === "env-var") {
50669
+ return "user";
50670
+ }
50671
+ const userStatus = await this.getUserStatus();
50672
+ if (userStatus) {
50673
+ return "user";
50674
+ }
50675
+ return "missing";
50676
+ };
50484
50677
  await this.writeProjectStatus({
50485
50678
  version: 1,
50486
50679
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50487
50680
  autoConfigured: false,
50488
- statusline: "user",
50681
+ statusline,
50682
+ // Now directly uses 'user' | 'project' | 'both' | 'none'
50489
50683
  apiKeys: {
50490
- OPENROUTER_API_KEY: "user",
50491
- OPENAI_API_KEY: "user"
50684
+ OPENROUTER_API_KEY: await getApiKeyHealth(openRouterResult),
50685
+ OPENAI_API_KEY: await getApiKeyHealth(openAIResult)
50492
50686
  },
50493
50687
  gitignore: "unknown",
50494
50688
  devMode: enabled
@@ -50520,6 +50714,41 @@ var require_setup_status_service = __commonJS({
50520
50714
  }
50521
50715
  return !await this.isProjectConfigured();
50522
50716
  }
50717
+ /**
50718
+ * Auto-configure the project using user's default preferences.
50719
+ * Creates project status that inherits from user-level settings.
50720
+ * @returns true if auto-configured, false if skipped (preference disabled or already configured)
50721
+ */
50722
+ async autoConfigureProject() {
50723
+ const user = await this.getUserStatus();
50724
+ if (!user?.preferences.autoConfigureProjects) {
50725
+ this.logger?.debug("Skipping auto-configure: user preference disabled");
50726
+ return false;
50727
+ }
50728
+ if (await this.isProjectConfigured()) {
50729
+ this.logger?.debug("Skipping auto-configure: project already configured");
50730
+ return false;
50731
+ }
50732
+ this.logger?.info("Auto-configuring project with user defaults", {
50733
+ projectDir: this.projectDir
50734
+ });
50735
+ const projectStatus = {
50736
+ version: 1,
50737
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50738
+ autoConfigured: true,
50739
+ statusline: "user",
50740
+ apiKeys: {
50741
+ OPENROUTER_API_KEY: "user",
50742
+ OPENAI_API_KEY: "user"
50743
+ },
50744
+ gitignore: "unknown"
50745
+ };
50746
+ await this.writeProjectStatus(projectStatus);
50747
+ this.logger?.info("Project auto-configured successfully", {
50748
+ projectDir: this.projectDir
50749
+ });
50750
+ return true;
50751
+ }
50523
50752
  async setApiKeyHealth(key, health, scope) {
50524
50753
  if (scope === "user") {
50525
50754
  const current = await this.getUserStatus();
@@ -50547,62 +50776,110 @@ var require_setup_status_service = __commonJS({
50547
50776
  async runDoctorCheck(options = {}) {
50548
50777
  const fixes = [];
50549
50778
  const actualStatusline = await this.detectActualStatusline();
50550
- const cachedStatusline = await this.getStatuslineHealth();
50551
- const statuslineFixed = actualStatusline !== cachedStatusline && actualStatusline === "configured";
50779
+ const cachedStatusline = await this.getStatuslineStatus();
50780
+ const statuslineFixed = actualStatusline !== cachedStatusline && actualStatusline !== "none";
50552
50781
  if (statuslineFixed) {
50553
- fixes.push("Statusline was actually configured (updated cache)");
50554
- const userStatus = await this.getUserStatus();
50555
- if (userStatus) {
50556
- await this.updateUserStatus({ statusline: "configured" });
50782
+ fixes.push(`Statusline was actually configured at ${actualStatusline} level (updated cache)`);
50783
+ if (actualStatusline === "project" || actualStatusline === "both") {
50784
+ const projectStatus = await this.getProjectStatus();
50785
+ if (projectStatus) {
50786
+ await this.updateProjectStatus({ statusline: actualStatusline });
50787
+ } else {
50788
+ await this.writeProjectStatus({
50789
+ version: 1,
50790
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50791
+ autoConfigured: false,
50792
+ statusline: actualStatusline,
50793
+ apiKeys: {
50794
+ OPENROUTER_API_KEY: "user",
50795
+ OPENAI_API_KEY: "user"
50796
+ },
50797
+ gitignore: "unknown"
50798
+ });
50799
+ }
50557
50800
  } else {
50558
- await this.writeUserStatus({
50559
- version: 1,
50560
- lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50561
- preferences: {
50562
- autoConfigureProjects: false,
50563
- defaultStatuslineScope: "user",
50564
- defaultApiKeyScope: "user"
50565
- },
50566
- statusline: "configured",
50567
- apiKeys: {
50568
- OPENROUTER_API_KEY: "missing",
50569
- OPENAI_API_KEY: "not-required"
50570
- }
50571
- });
50801
+ const userStatus = await this.getUserStatus();
50802
+ if (userStatus) {
50803
+ await this.updateUserStatus({ statusline: actualStatusline });
50804
+ } else {
50805
+ await this.writeUserStatus({
50806
+ version: 1,
50807
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50808
+ preferences: {
50809
+ autoConfigureProjects: false,
50810
+ defaultStatuslineScope: "user",
50811
+ defaultApiKeyScope: "user"
50812
+ },
50813
+ statusline: actualStatusline,
50814
+ apiKeys: {
50815
+ OPENROUTER_API_KEY: "missing",
50816
+ OPENAI_API_KEY: "not-required"
50817
+ }
50818
+ });
50819
+ }
50572
50820
  }
50573
50821
  }
50574
50822
  const apiKeyResults = {};
50575
50823
  const keysToCheck = ["OPENROUTER_API_KEY", "OPENAI_API_KEY"];
50576
50824
  for (const keyName of keysToCheck) {
50577
- const actualKey = await this.detectActualApiKey(keyName);
50825
+ const detection = await this.detectAllApiKeys(keyName, options.skipValidation);
50826
+ const projectApiKeyStatus = this.buildProjectApiKeyStatus(detection);
50578
50827
  const cachedHealth = await this.getEffectiveApiKeyHealth(keyName);
50579
- let actualHealth;
50580
- if (!actualKey) {
50581
- actualHealth = "missing";
50582
- } else if (options.skipValidation) {
50583
- actualHealth = "pending-validation";
50584
- } else {
50585
- const validateFn = keyName === "OPENROUTER_API_KEY" ? shared_providers_1.validateOpenRouterKey : shared_providers_1.validateOpenAIKey;
50586
- const validationResult = await validateFn(actualKey, this.logger);
50587
- actualHealth = validationResult.valid ? "healthy" : "invalid";
50588
- }
50589
- const keyFixed = actualKey !== null && cachedHealth === "missing";
50828
+ const actualHealth = toScopeStatus(projectApiKeyStatus.status);
50829
+ const source = projectApiKeyStatus.used ? SCOPE_TO_SOURCE[projectApiKeyStatus.used] ?? null : null;
50830
+ const anyKeyPresent = detection.project.found || detection.user.found || detection.env.found;
50831
+ const cacheNeedsUpdate = anyKeyPresent && cachedHealth === "missing" || !anyKeyPresent && cachedHealth !== "missing" && cachedHealth !== "not-required";
50832
+ const keyFixed = cacheNeedsUpdate;
50590
50833
  if (keyFixed) {
50591
- fixes.push(`${keyName} was actually present (updated cache)`);
50592
- const userStatus = await this.getUserStatus();
50593
- if (userStatus) {
50594
- await this.updateUserStatus({
50595
- apiKeys: { ...userStatus.apiKeys, [keyName]: actualHealth }
50834
+ const state = anyKeyPresent ? "present" : "missing";
50835
+ fixes.push(`${keyName} was actually ${state} (updated cache)`);
50836
+ const projectStatus = await this.getProjectStatus();
50837
+ if (projectStatus) {
50838
+ await this.updateProjectStatus({
50839
+ apiKeys: { ...projectStatus.apiKeys, [keyName]: projectApiKeyStatus }
50596
50840
  });
50841
+ } else {
50842
+ const userApiKeyStatus = this.buildUserApiKeyStatus(detection);
50843
+ await this.writeProjectStatus({
50844
+ version: 1,
50845
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50846
+ autoConfigured: false,
50847
+ statusline: actualStatusline,
50848
+ apiKeys: {
50849
+ OPENROUTER_API_KEY: keyName === "OPENROUTER_API_KEY" ? projectApiKeyStatus : "user",
50850
+ OPENAI_API_KEY: keyName === "OPENAI_API_KEY" ? projectApiKeyStatus : "user"
50851
+ },
50852
+ gitignore: "unknown"
50853
+ });
50854
+ const userStatus = await this.getUserStatus();
50855
+ if (!userStatus) {
50856
+ await this.writeUserStatus({
50857
+ version: 1,
50858
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
50859
+ preferences: {
50860
+ autoConfigureProjects: false,
50861
+ defaultStatuslineScope: "user",
50862
+ defaultApiKeyScope: "user"
50863
+ },
50864
+ statusline: actualStatusline,
50865
+ apiKeys: {
50866
+ OPENROUTER_API_KEY: keyName === "OPENROUTER_API_KEY" ? userApiKeyStatus : "missing",
50867
+ OPENAI_API_KEY: keyName === "OPENAI_API_KEY" ? userApiKeyStatus : "not-required"
50868
+ }
50869
+ });
50870
+ }
50597
50871
  }
50598
50872
  }
50599
50873
  apiKeyResults[keyName] = {
50600
50874
  actual: actualHealth,
50601
50875
  cached: cachedHealth,
50602
- fixed: keyFixed
50876
+ fixed: keyFixed,
50877
+ source,
50878
+ used: projectApiKeyStatus.used,
50879
+ scopes: projectApiKeyStatus.scopes
50603
50880
  };
50604
50881
  }
50605
- const isStatuslineHealthy = actualStatusline === "configured";
50882
+ const isStatuslineHealthy = actualStatusline !== "none";
50606
50883
  const openRouterActual = apiKeyResults.OPENROUTER_API_KEY.actual;
50607
50884
  const openRouterCached = apiKeyResults.OPENROUTER_API_KEY.cached;
50608
50885
  const isApiKeyHealthy = openRouterActual !== "missing" || openRouterCached === "not-required";
@@ -66751,6 +67028,25 @@ var require_hook_command = __commonJS({
66751
67028
  }
66752
67029
  };
66753
67030
  var VERBOSE_DEGRADED_HOOKS = /* @__PURE__ */ new Set(["SessionStart", "UserPromptSubmit"]);
67031
+ async function maybeAutoConfigureProject(projectRoot, logger) {
67032
+ try {
67033
+ const setupService = new core_1.SetupStatusService(projectRoot, { logger });
67034
+ const shouldAuto = await setupService.shouldAutoConfigureProject();
67035
+ if (!shouldAuto)
67036
+ return false;
67037
+ const configured = await setupService.autoConfigureProject();
67038
+ if (configured) {
67039
+ logger.info("Project auto-configured on SessionStart", { projectRoot });
67040
+ }
67041
+ return configured;
67042
+ } catch (err) {
67043
+ logger.warn("Failed to auto-configure project", {
67044
+ error: err instanceof Error ? err.message : String(err),
67045
+ projectRoot
67046
+ });
67047
+ return false;
67048
+ }
67049
+ }
66754
67050
  async function checkSetupState(projectRoot, hookName, logger) {
66755
67051
  let state;
66756
67052
  try {
@@ -66818,6 +67114,9 @@ var require_hook_command = __commonJS({
66818
67114
  });
66819
67115
  }
66820
67116
  }
67117
+ if (hookName === "SessionStart") {
67118
+ await maybeAutoConfigureProject(projectRoot, logger);
67119
+ }
66821
67120
  const degradedResponse = await checkSetupState(projectRoot, hookName, logger);
66822
67121
  if (degradedResponse !== null) {
66823
67122
  const claudeResponse2 = translateToClaudeCodeFormat(hookName, degradedResponse);
@@ -67223,7 +67522,9 @@ var require_types4 = __commonJS({
67223
67522
  includeAssistantThinking: false,
67224
67523
  keepHistory: false,
67225
67524
  maxTitleWords: 8,
67226
- maxIntentWords: 12,
67525
+ maxIntentWords: 15,
67526
+ maxSnarkyWords: 20,
67527
+ maxResumeWords: 20,
67227
67528
  snarkyMessages: true,
67228
67529
  countdown: {
67229
67530
  lowConfidence: 5,
@@ -67603,7 +67904,7 @@ var require_update_summary = __commonJS({
67603
67904
  await summaryState.summaryCountdown.write(sessionId, state);
67604
67905
  }
67605
67906
  function interpolatePrompt(template, context) {
67606
- return template.replace(/\{\{transcript\}\}/g, context.transcript).replace(/\{\{previousConfidence\}\}/g, String(context.previousConfidence)).replace(/\{\{previousAnalysis\}\}/g, context.previousAnalysis);
67907
+ return template.replace(/\{\{transcript\}\}/g, context.transcript).replace(/\{\{previousConfidence\}\}/g, String(context.previousConfidence)).replace(/\{\{previousAnalysis\}\}/g, context.previousAnalysis).replace(/\{\{maxTitleWords\}\}/g, String(context.maxTitleWords)).replace(/\{\{maxIntentWords\}\}/g, String(context.maxIntentWords));
67607
67908
  }
67608
67909
  function parseResponse(content) {
67609
67910
  try {
@@ -67645,7 +67946,9 @@ var require_update_summary = __commonJS({
67645
67946
  const prompt = interpolatePrompt(promptTemplate, {
67646
67947
  transcript,
67647
67948
  previousConfidence,
67648
- previousAnalysis
67949
+ previousAnalysis,
67950
+ maxTitleWords: config.maxTitleWords,
67951
+ maxIntentWords: config.maxIntentWords
67649
67952
  });
67650
67953
  const schemaContent = ctx.assets.resolve(SESSION_SUMMARY_SCHEMA_FILE);
67651
67954
  const jsonSchema = schemaContent ? {
@@ -67777,7 +68080,8 @@ var require_update_summary = __commonJS({
67777
68080
  latest_intent: summary.latest_intent,
67778
68081
  turn_count: ctx.transcript.getMetrics().turnCount,
67779
68082
  tool_count: ctx.transcript.getMetrics().toolCount,
67780
- sessionSummary: JSON.stringify(summary, null, 2)
68083
+ sessionSummary: JSON.stringify(summary, null, 2),
68084
+ maxSnarkyWords: config.maxSnarkyWords
67781
68085
  });
67782
68086
  const llmConfig = config.llm?.snarkyComment ?? types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG.llm.snarkyComment;
67783
68087
  const provider = ctx.profileFactory.createForProfile(llmConfig.profile, llmConfig.fallbackProfile);
@@ -67843,7 +68147,8 @@ var require_update_summary = __commonJS({
67843
68147
  confidence: summary.session_title_confidence,
67844
68148
  latestIntent: summary.latest_intent,
67845
68149
  keyPhrases,
67846
- transcript: transcriptExcerpt
68150
+ transcript: transcriptExcerpt,
68151
+ maxResumeWords: config.maxResumeWords
67847
68152
  });
67848
68153
  const llmConfig = config.llm?.resumeMessage ?? types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG.llm.resumeMessage;
67849
68154
  const provider = ctx.profileFactory.createForProfile(llmConfig.profile, llmConfig.fallbackProfile);
@@ -67943,16 +68248,17 @@ var require_on_demand_generation = __commonJS({
67943
68248
  };
67944
68249
  }
67945
68250
  const personaContext = (0, persona_utils_js_1.buildPersonaContext)(persona);
68251
+ const featureConfig = ctx.config.getFeature("session-summary");
68252
+ const config = { ...types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG, ...featureConfig.settings };
67946
68253
  const prompt = (0, update_summary_js_1.interpolateTemplate)(promptTemplate, {
67947
68254
  ...personaContext,
67948
68255
  session_title: summary.session_title,
67949
68256
  latest_intent: summary.latest_intent,
67950
68257
  turn_count: ctx.transcript.getMetrics().turnCount,
67951
68258
  tool_count: ctx.transcript.getMetrics().toolCount,
67952
- sessionSummary: JSON.stringify(summary, null, 2)
68259
+ sessionSummary: JSON.stringify(summary, null, 2),
68260
+ maxSnarkyWords: config.maxSnarkyWords
67953
68261
  });
67954
- const featureConfig = ctx.config.getFeature("session-summary");
67955
- const config = { ...types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG, ...featureConfig.settings };
67956
68262
  const llmConfig = config.llm?.snarkyComment ?? types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG.llm.snarkyComment;
67957
68263
  const provider = ctx.profileFactory.createForProfile(llmConfig.profile, llmConfig.fallbackProfile);
67958
68264
  try {
@@ -68015,6 +68321,8 @@ var require_on_demand_generation = __commonJS({
68015
68321
  };
68016
68322
  }
68017
68323
  const personaContext = (0, persona_utils_js_1.buildPersonaContext)(persona);
68324
+ const featureConfig = ctx.config.getFeature("session-summary");
68325
+ const config = { ...types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG, ...featureConfig.settings };
68018
68326
  const excerpt = ctx.transcript.getExcerpt({
68019
68327
  maxLines: 50,
68020
68328
  includeToolMessages: true,
@@ -68028,10 +68336,9 @@ var require_on_demand_generation = __commonJS({
68028
68336
  confidence: summary.session_title_confidence,
68029
68337
  latestIntent: summary.latest_intent,
68030
68338
  keyPhrases,
68031
- transcript: excerpt.content
68339
+ transcript: excerpt.content,
68340
+ maxResumeWords: config.maxResumeWords
68032
68341
  });
68033
- const featureConfig = ctx.config.getFeature("session-summary");
68034
- const config = { ...types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG, ...featureConfig.settings };
68035
68342
  const llmConfig = config.llm?.resumeMessage ?? types_js_1.DEFAULT_SESSION_SUMMARY_CONFIG.llm.resumeMessage;
68036
68343
  const provider = ctx.profileFactory.createForProfile(llmConfig.profile, llmConfig.fallbackProfile);
68037
68344
  try {
@@ -70922,6 +71229,11 @@ var require_dev_mode = __commonJS({
70922
71229
  log(stdout, "info", "No sessions directory found");
70923
71230
  }
70924
71231
  await removeDirectory(node_path_1.default.join(sidekickDir, "state"), "state", stdout);
71232
+ const setupStatusPath = node_path_1.default.join(sidekickDir, "setup-status.json");
71233
+ if (await fileExists(setupStatusPath)) {
71234
+ await (0, promises_12.unlink)(setupStatusPath);
71235
+ log(stdout, "info", `Removed ${setupStatusPath}`);
71236
+ }
70925
71237
  const tmpDir = node_os_1.default.tmpdir();
70926
71238
  try {
70927
71239
  const files = await (0, promises_12.readdir)(tmpDir);
@@ -71051,7 +71363,7 @@ var require_prompts = __commonJS({
71051
71363
  ctx.stdout.write(`${colorMap[type]}${icons[type]}${colors.reset} ${message}
71052
71364
  `);
71053
71365
  }
71054
- async function promptSelect(ctx, question, options) {
71366
+ async function promptSelect(ctx, question, options, defaultIndex = 0) {
71055
71367
  ctx.stdout.write(`${question}
71056
71368
 
71057
71369
  `);
@@ -71069,17 +71381,30 @@ var require_prompts = __commonJS({
71069
71381
  output: ctx.stdout,
71070
71382
  terminal: false
71071
71383
  });
71384
+ const defaultNum = defaultIndex + 1;
71385
+ const prompt = `Enter choice (1-${options.length}) [${defaultNum}]: `;
71072
71386
  return new Promise((resolve3) => {
71073
- ctx.stdout.write(`Enter choice (1-${options.length}): `);
71074
- rl.once("line", (answer) => {
71075
- rl.close();
71076
- const num = parseInt(answer.trim(), 10);
71077
- if (num >= 1 && num <= options.length) {
71078
- resolve3(options[num - 1].value);
71079
- } else {
71080
- resolve3(options[0].value);
71081
- }
71082
- });
71387
+ const ask = () => {
71388
+ ctx.stdout.write(prompt);
71389
+ rl.once("line", (answer) => {
71390
+ const trimmed = answer.trim();
71391
+ if (trimmed === "") {
71392
+ rl.close();
71393
+ resolve3(options[defaultIndex].value);
71394
+ } else {
71395
+ const num = parseInt(trimmed, 10);
71396
+ if (num >= 1 && num <= options.length) {
71397
+ rl.close();
71398
+ resolve3(options[num - 1].value);
71399
+ } else {
71400
+ ctx.stdout.write(`${colors.yellow}Invalid choice. Enter a number between 1 and ${options.length}.${colors.reset}
71401
+ `);
71402
+ ask();
71403
+ }
71404
+ }
71405
+ });
71406
+ };
71407
+ ask();
71083
71408
  });
71084
71409
  }
71085
71410
  async function promptConfirm(ctx, question, defaultYes = true) {
@@ -71089,17 +71414,29 @@ var require_prompts = __commonJS({
71089
71414
  output: ctx.stdout,
71090
71415
  terminal: false
71091
71416
  });
71417
+ const prompt = `${question} ${hint} `;
71092
71418
  return new Promise((resolve3) => {
71093
- ctx.stdout.write(`${question} ${hint} `);
71094
- rl.once("line", (answer) => {
71095
- rl.close();
71096
- const normalized = answer.trim().toLowerCase();
71097
- if (normalized === "") {
71098
- resolve3(defaultYes);
71099
- } else {
71100
- resolve3(normalized === "y" || normalized === "yes");
71101
- }
71102
- });
71419
+ const ask = () => {
71420
+ ctx.stdout.write(prompt);
71421
+ rl.once("line", (answer) => {
71422
+ const normalized = answer.trim().toLowerCase();
71423
+ if (normalized === "") {
71424
+ rl.close();
71425
+ resolve3(defaultYes);
71426
+ } else if (normalized === "y" || normalized === "yes") {
71427
+ rl.close();
71428
+ resolve3(true);
71429
+ } else if (normalized === "n" || normalized === "no") {
71430
+ rl.close();
71431
+ resolve3(false);
71432
+ } else {
71433
+ ctx.stdout.write(`${colors.yellow}Please enter y or n.${colors.reset}
71434
+ `);
71435
+ ask();
71436
+ }
71437
+ });
71438
+ };
71439
+ ask();
71103
71440
  });
71104
71441
  }
71105
71442
  async function promptInput(ctx, question) {
@@ -71247,6 +71584,12 @@ Examples:
71247
71584
  return "check failed";
71248
71585
  }
71249
71586
  }
71587
+ function getScopeIcon(status) {
71588
+ return status === "healthy" ? "\u2713" : "\u2717";
71589
+ }
71590
+ function formatApiKeyScopes(scopes) {
71591
+ return `[project ${getScopeIcon(scopes.project)} user ${getScopeIcon(scopes.user)} env ${getScopeIcon(scopes.env)}]`;
71592
+ }
71250
71593
  async function configureStatusline(settingsPath, logger) {
71251
71594
  let settings = {};
71252
71595
  try {
@@ -71286,27 +71629,6 @@ Examples:
71286
71629
  }
71287
71630
  await fs.writeFile(envPath, content);
71288
71631
  }
71289
- async function findExistingApiKey(keyName, homeDir, projectDir) {
71290
- if (process.env[keyName]) {
71291
- return process.env[keyName];
71292
- }
71293
- const envPaths = [
71294
- path.join(homeDir, ".sidekick", ".env"),
71295
- path.join(projectDir, ".sidekick", ".env"),
71296
- path.join(projectDir, ".sidekick", ".env.local")
71297
- ];
71298
- for (const envPath of envPaths) {
71299
- try {
71300
- const content = await fs.readFile(envPath, "utf-8");
71301
- const match = content.match(new RegExp(`^${keyName}=(.+)$`, "m"));
71302
- if (match) {
71303
- return match[1];
71304
- }
71305
- } catch {
71306
- }
71307
- }
71308
- return null;
71309
- }
71310
71632
  async function writePersonaConfig(_projectDir, homeDir, enabled) {
71311
71633
  const configDir = path.join(homeDir, ".sidekick");
71312
71634
  const featuresPath = path.join(configDir, "features.yaml");
@@ -71389,39 +71711,52 @@ Examples:
71389
71711
  stdout.write("These require an OpenRouter API key (small cost per message).\n\n");
71390
71712
  const wantPersonas = await (0, prompts_js_1.promptConfirm)(ctx, "Enable persona features?", true);
71391
71713
  let apiKeyHealth = "not-required";
71714
+ let apiKeyDetection = null;
71392
71715
  if (!wantPersonas) {
71393
71716
  await writePersonaConfig(projectDir, homeDir, false);
71394
71717
  (0, prompts_js_1.printStatus)(ctx, "info", "Personas disabled");
71395
71718
  } else {
71396
71719
  await writePersonaConfig(projectDir, homeDir, true);
71397
- apiKeyHealth = await configureApiKey(wctx);
71720
+ const result = await configureApiKey(wctx);
71721
+ apiKeyHealth = result.health;
71722
+ apiKeyDetection = result.detection;
71398
71723
  }
71399
- return { wantPersonas, apiKeyHealth };
71724
+ return { wantPersonas, apiKeyHealth, apiKeyDetection };
71400
71725
  }
71401
71726
  async function configureApiKey(wctx) {
71402
- const { ctx, homeDir, projectDir, logger } = wctx;
71727
+ const { ctx, homeDir, projectDir, logger, setupService } = wctx;
71403
71728
  const stdout = ctx.stdout;
71404
- const existingKey = await findExistingApiKey("OPENROUTER_API_KEY", homeDir, projectDir);
71405
- if (existingKey) {
71406
- (0, prompts_js_1.printStatus)(ctx, "success", "OPENROUTER_API_KEY found");
71407
- stdout.write("Validating... ");
71408
- const result2 = await (0, core_1.validateOpenRouterKey)(existingKey, logger);
71409
- if (result2.valid) {
71410
- stdout.write("valid!\n");
71411
- return "healthy";
71412
- } else {
71413
- stdout.write(`invalid (${result2.error})
71729
+ stdout.write("\nChecking API keys...\n");
71730
+ const detection = await setupService.detectAllApiKeys("OPENROUTER_API_KEY");
71731
+ const scopeLabels = {
71732
+ project: "project (.sidekick/.env)",
71733
+ user: "user (~/.sidekick/.env)",
71734
+ env: "env (OPENROUTER_API_KEY)"
71735
+ };
71736
+ for (const scope of ["project", "user", "env"]) {
71737
+ const r = detection[scope];
71738
+ const icon = r.found ? r.status === "healthy" ? "\u2713" : "\u2717" : "-";
71739
+ const label = r.found ? r.status : "not found";
71740
+ stdout.write(` ${icon} ${scopeLabels[scope]}: ${label}
71414
71741
  `);
71415
- return "invalid";
71416
- }
71417
71742
  }
71418
- (0, prompts_js_1.printStatus)(ctx, "warning", "OPENROUTER_API_KEY not found");
71419
- const configureNow = await (0, prompts_js_1.promptConfirm)(ctx, "Configure API key now?", true);
71743
+ const healthyScope = ["project", "user", "env"].find((s) => detection[s].status === "healthy");
71744
+ if (healthyScope) {
71745
+ (0, prompts_js_1.printStatus)(ctx, "success", `OpenRouter API Key: healthy (using ${healthyScope})`);
71746
+ return { health: "healthy", detection };
71747
+ }
71748
+ const invalidScope = ["project", "user", "env"].find((s) => detection[s].found);
71749
+ if (invalidScope) {
71750
+ (0, prompts_js_1.printStatus)(ctx, "warning", "API key found but validation failed");
71751
+ } else {
71752
+ (0, prompts_js_1.printStatus)(ctx, "warning", "OPENROUTER_API_KEY not found");
71753
+ }
71754
+ const configureNow = await (0, prompts_js_1.promptConfirm)(ctx, invalidScope ? "Replace API key?" : "Configure API key now?", true);
71420
71755
  if (!configureNow) {
71421
71756
  stdout.write("\n");
71422
71757
  (0, prompts_js_1.printStatus)(ctx, "warning", "Persona features will show warnings in the statusline until an API key is configured.");
71423
71758
  stdout.write("Run 'sidekick setup' again or ask Claude to help configure API keys using /sidekick-config.\n");
71424
- return "missing";
71759
+ return { health: "missing", detection };
71425
71760
  }
71426
71761
  const keyScope = await (0, prompts_js_1.promptSelect)(ctx, "Where should the API key be stored?", [
71427
71762
  { value: "user", label: "User-level (~/.sidekick/.env)", description: "Works in all projects" },
@@ -71435,14 +71770,14 @@ Examples:
71435
71770
  stdout.write("valid!\n");
71436
71771
  await writeApiKeyToEnv(envPath, "OPENROUTER_API_KEY", apiKey);
71437
71772
  (0, prompts_js_1.printStatus)(ctx, "success", `API key saved to ${envPath}`);
71438
- return "healthy";
71439
71773
  } else {
71440
71774
  stdout.write(`invalid (${result.error})
71441
71775
  `);
71442
71776
  (0, prompts_js_1.printStatus)(ctx, "warning", "API key validation failed, saving anyway");
71443
71777
  await writeApiKeyToEnv(envPath, "OPENROUTER_API_KEY", apiKey);
71444
- return "invalid";
71445
71778
  }
71779
+ const postDetection = await setupService.detectAllApiKeys("OPENROUTER_API_KEY");
71780
+ return { health: result.valid ? "healthy" : "invalid", detection: postDetection };
71446
71781
  }
71447
71782
  async function runStep4AutoConfig(wctx) {
71448
71783
  const { ctx } = wctx;
@@ -71456,7 +71791,7 @@ Examples:
71456
71791
  }
71457
71792
  async function writeStatusFiles(wctx, state) {
71458
71793
  const { setupService } = wctx;
71459
- const { statuslineScope, gitignoreStatus, wantPersonas, apiKeyHealth, autoConfig } = state;
71794
+ const { statuslineScope, gitignoreStatus, wantPersonas, apiKeyHealth, apiKeyDetection, autoConfig } = state;
71460
71795
  const userStatus = {
71461
71796
  version: 1,
71462
71797
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -71465,9 +71800,9 @@ Examples:
71465
71800
  defaultStatuslineScope: statuslineScope,
71466
71801
  defaultApiKeyScope: wantPersonas ? "user" : "skip"
71467
71802
  },
71468
- statusline: "configured",
71803
+ statusline: statuslineScope,
71469
71804
  apiKeys: {
71470
- OPENROUTER_API_KEY: apiKeyHealth,
71805
+ OPENROUTER_API_KEY: apiKeyDetection ? setupService.buildUserApiKeyStatus(apiKeyDetection) : apiKeyHealth,
71471
71806
  OPENAI_API_KEY: "not-required"
71472
71807
  }
71473
71808
  };
@@ -71476,9 +71811,9 @@ Examples:
71476
71811
  version: 1,
71477
71812
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
71478
71813
  autoConfigured: false,
71479
- statusline: statuslineScope === "project" ? "configured" : "user",
71814
+ statusline: statuslineScope,
71480
71815
  apiKeys: {
71481
- OPENROUTER_API_KEY: apiKeyHealth === "healthy" ? "user" : apiKeyHealth,
71816
+ OPENROUTER_API_KEY: apiKeyDetection ? setupService.buildProjectApiKeyStatus(apiKeyDetection) : apiKeyHealth === "healthy" ? "user" : apiKeyHealth,
71482
71817
  OPENAI_API_KEY: "not-required"
71483
71818
  },
71484
71819
  gitignore: gitignoreStatus
@@ -71497,6 +71832,15 @@ Examples:
71497
71832
  (0, prompts_js_1.printStatus)(ctx, wantPersonas ? "success" : "info", `Personas: ${wantPersonas ? "Enabled" : "Disabled"}`);
71498
71833
  const apiKeyStatusType = getApiKeyStatusType(apiKeyHealth);
71499
71834
  (0, prompts_js_1.printStatus)(ctx, apiKeyStatusType, `API Key: ${apiKeyHealth}`);
71835
+ if (state.apiKeyDetection) {
71836
+ const scopes = {
71837
+ project: state.apiKeyDetection.project.status,
71838
+ user: state.apiKeyDetection.user.status,
71839
+ env: state.apiKeyDetection.env.status
71840
+ };
71841
+ stdout.write(` ${formatApiKeyScopes(scopes)}
71842
+ `);
71843
+ }
71500
71844
  (0, prompts_js_1.printStatus)(ctx, "success", `Auto-configure: ${autoConfig === "auto" ? "Enabled" : "Disabled"}`);
71501
71845
  stdout.write("\n");
71502
71846
  stdout.write("Restart Claude Code to see your statusline: claude --continue\n");
@@ -71519,13 +71863,20 @@ Examples:
71519
71863
  }
71520
71864
  const statuslineScope = force ? "user" : await runStep1Statusline(wctx);
71521
71865
  const gitignoreStatus = await runStep2Gitignore(wctx, force);
71522
- const { wantPersonas, apiKeyHealth } = force ? { wantPersonas: true, apiKeyHealth: "not-required" } : await runStep3Personas(wctx);
71866
+ const { wantPersonas, apiKeyHealth, apiKeyDetection } = force ? { wantPersonas: true, apiKeyHealth: "not-required", apiKeyDetection: null } : await runStep3Personas(wctx);
71523
71867
  const autoConfig = force ? "auto" : await runStep4AutoConfig(wctx);
71524
71868
  if (force) {
71525
71869
  const statuslinePath = path.join(homeDir, ".claude", "settings.json");
71526
71870
  await configureStatusline(statuslinePath, logger);
71527
71871
  }
71528
- const state = { statuslineScope, gitignoreStatus, wantPersonas, apiKeyHealth, autoConfig };
71872
+ const state = {
71873
+ statuslineScope,
71874
+ gitignoreStatus,
71875
+ wantPersonas,
71876
+ apiKeyHealth,
71877
+ apiKeyDetection,
71878
+ autoConfig
71879
+ };
71529
71880
  await writeStatusFiles(wctx, state);
71530
71881
  if (!force) {
71531
71882
  printSummary(wctx, state);
@@ -71609,8 +71960,8 @@ Examples:
71609
71960
  defaultStatuslineScope: "user",
71610
71961
  defaultApiKeyScope: "user"
71611
71962
  },
71612
- statusline: "skipped",
71613
- // Default to skipped if no prior setup
71963
+ statusline: "none",
71964
+ // Default to none if no prior setup
71614
71965
  apiKeys: {
71615
71966
  OPENROUTER_API_KEY: "not-required",
71616
71967
  OPENAI_API_KEY: "not-required"
@@ -71655,10 +72006,12 @@ Configured ${configuredCount} setting${configuredCount === 1 ? "" : "s"}.
71655
72006
  const pluginLabel = getPluginStatusLabel(pluginStatus);
71656
72007
  const livenessIcon = getLivenessIcon(liveness);
71657
72008
  const livenessLabel = getLivenessLabel(liveness);
71658
- const statuslineIcon = doctorResult.statusline.actual === "configured" ? "\u2713" : "\u26A0";
72009
+ const statuslineIcon = doctorResult.statusline.actual !== "none" ? "\u2713" : "\u26A0";
71659
72010
  const gitignoreIcon = gitignore === "installed" ? "\u2713" : "\u26A0";
71660
- const openRouterHealth = doctorResult.apiKeys.OPENROUTER_API_KEY.actual;
72011
+ const openRouterResult = doctorResult.apiKeys.OPENROUTER_API_KEY;
72012
+ const openRouterHealth = openRouterResult.actual;
71661
72013
  const apiKeyIcon = openRouterHealth === "healthy" || openRouterHealth === "not-required" ? "\u2713" : "\u26A0";
72014
+ const scopeBreakdown = formatApiKeyScopes(openRouterResult.scopes);
71662
72015
  stdout.write("\n");
71663
72016
  stdout.write(`${pluginIcon} Plugin: ${pluginLabel}
71664
72017
  `);
@@ -71668,7 +72021,7 @@ Configured ${configuredCount} setting${configuredCount === 1 ? "" : "s"}.
71668
72021
  `);
71669
72022
  stdout.write(`${gitignoreIcon} Gitignore: ${gitignore}
71670
72023
  `);
71671
- stdout.write(`${apiKeyIcon} OpenRouter API Key: ${openRouterHealth}
72024
+ stdout.write(`${apiKeyIcon} OpenRouter API Key: ${openRouterHealth} ${scopeBreakdown}
71672
72025
  `);
71673
72026
  const isPluginOk = pluginStatus === "plugin" || pluginStatus === "dev-mode";
71674
72027
  const isPluginLive = liveness === "active";
@@ -71764,7 +72117,7 @@ var require_cli = __commonJS({
71764
72117
  var promises_12 = require("node:fs/promises");
71765
72118
  var node_stream_1 = require("node:stream");
71766
72119
  var yargs_parser_1 = __importDefault2(require_build());
71767
- var VERSION = true ? "0.0.8-alpha.0" : "dev";
72120
+ var VERSION = true ? "0.0.8-alpha.1" : "dev";
71768
72121
  function isInSandbox() {
71769
72122
  return process.env.SANDBOX_RUNTIME === "1";
71770
72123
  }