@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
|
|
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
|
-
|
|
50175
|
-
|
|
50176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
66710
|
+
SessionEnd: () => ({}),
|
|
66470
66711
|
UserPromptSubmit: translateUserPromptSubmit,
|
|
66471
66712
|
PreToolUse: translatePreToolUse,
|
|
66472
66713
|
PostToolUse: translatePostToolUse,
|
|
66473
66714
|
Stop: translateStop,
|
|
66474
|
-
PreCompact:
|
|
66715
|
+
PreCompact: () => ({})
|
|
66475
66716
|
};
|
|
66476
66717
|
function translateToClaudeCodeFormat(hookName, internal) {
|
|
66477
|
-
|
|
66478
|
-
return translator(internal);
|
|
66718
|
+
return TRANSLATORS[hookName](internal);
|
|
66479
66719
|
}
|
|
66480
|
-
var
|
|
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
|
-
|
|
66490
|
-
|
|
66491
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
70655
|
-
disable Remove dev-
|
|
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
|
|
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.
|
|
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
|
-
|
|
49199
|
-
|
|
49200
|
-
|
|
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) {
|