create-snipara 1.3.36 → 1.3.38
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/README.md +6 -1
- package/dist/index.js +176 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,8 @@ That distinction matters:
|
|
|
106
106
|
- installs optional `snipara-orchestrator` for proof gates, drift checks, and htask workflows when requested
|
|
107
107
|
- configures `.mcp.json`
|
|
108
108
|
- generates merge-ready AI agent templates in `.snipara/templates/`, including Pro+ symbol-card, code-impact, and managed workflow guidance for paid Context plans
|
|
109
|
-
- creates `AGENTS.md
|
|
109
|
+
- creates `AGENTS.md` and `CLAUDE.md` when missing, or appends a marked Snipara section when those files already exist
|
|
110
|
+
- creates `.cursor/rules/snipara.mdc` and `.codex/config.toml` when those files do not already exist
|
|
110
111
|
- writes client-specific companion packs such as `.snipara/companion/CODEX.md` or `.snipara/companion/GEMINI.md`
|
|
111
112
|
- updates `.env`, `.env.local`, and `.env.example`
|
|
112
113
|
- generates `.snipara/companion/README.md`
|
|
@@ -162,6 +163,10 @@ The generated agent templates cover:
|
|
|
162
163
|
- ChatGPT/OpenAI HTTP MCP snippet
|
|
163
164
|
- Gemini, VS Code, Continue, and custom HTTP MCP reference snippets
|
|
164
165
|
|
|
166
|
+
Existing root `AGENTS.md` and `CLAUDE.md` files are not replaced. The installer
|
|
167
|
+
adds or refreshes a marked Snipara section so project-specific instructions stay
|
|
168
|
+
intact.
|
|
169
|
+
|
|
165
170
|
When `snipara-companion` is installed, the companion pack also includes the compaction-safe managed workflow:
|
|
166
171
|
|
|
167
172
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { homedir } from "os";
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "create-snipara",
|
|
16
|
-
version: "1.3.
|
|
16
|
+
version: "1.3.38",
|
|
17
17
|
description: "Onboard Snipara Hosted MCP Context + Memory with auth, companion workflows, and optional Snipara Sandbox",
|
|
18
18
|
main: "dist/index.js",
|
|
19
19
|
type: "module",
|
|
@@ -163,10 +163,19 @@ function getCompanionInitClientFlag(aiClient) {
|
|
|
163
163
|
const companionClient = supportedClientMap[aiClient];
|
|
164
164
|
return companionClient ? ` --client ${companionClient}` : "";
|
|
165
165
|
}
|
|
166
|
+
function getAutomationClientId(aiClient) {
|
|
167
|
+
if (aiClient === "other" || aiClient === "claude-desktop") {
|
|
168
|
+
return "custom";
|
|
169
|
+
}
|
|
170
|
+
return aiClient;
|
|
171
|
+
}
|
|
166
172
|
function buildProgram() {
|
|
167
173
|
const program = new Command();
|
|
168
174
|
program.name("create-snipara").description("Scaffold hosted Snipara MCP config plus companion workflow helpers").version(VERSION);
|
|
169
|
-
program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
175
|
+
program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
176
|
+
"--client <client>",
|
|
177
|
+
"AI client: claude-code, cursor, windsurf, codex, gemini, chatgpt, vscode, continue, claude-desktop, other"
|
|
178
|
+
).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
|
|
170
179
|
"--profile <profile>",
|
|
171
180
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
172
181
|
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--with-orchestrator", "Install optional snipara-orchestrator package").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--runtime-only", "Install only Snipara Sandbox").addOption(new Option("--full", "Legacy alias for --profile full-stack").hideHelp()).option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
|
|
@@ -200,7 +209,10 @@ function buildProgram() {
|
|
|
200
209
|
return program;
|
|
201
210
|
}
|
|
202
211
|
function addSharedRootOptions(program) {
|
|
203
|
-
program.option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
212
|
+
program.option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
213
|
+
"--client <client>",
|
|
214
|
+
"AI client: claude-code, cursor, windsurf, codex, gemini, chatgpt, vscode, continue, claude-desktop, other"
|
|
215
|
+
).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
|
|
204
216
|
"--profile <profile>",
|
|
205
217
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
206
218
|
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--with-orchestrator", "Install optional snipara-orchestrator package").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--runtime-only", "Install only Snipara Sandbox").addOption(new Option("--full", "Legacy alias for --profile full-stack").hideHelp()).option("-y, --yes", "Accept defaults without prompting");
|
|
@@ -233,6 +245,7 @@ function parseInitOptionsFromArgv(args) {
|
|
|
233
245
|
"--api-key": "apiKey",
|
|
234
246
|
"--slug": "slug",
|
|
235
247
|
"--project-id": "projectId",
|
|
248
|
+
"--client": "client",
|
|
236
249
|
"--profile": "profile",
|
|
237
250
|
"--github-path": "githubPath"
|
|
238
251
|
};
|
|
@@ -275,7 +288,8 @@ function parseInitOptionsFromArgv(args) {
|
|
|
275
288
|
return parsed;
|
|
276
289
|
}
|
|
277
290
|
async function runInit(options) {
|
|
278
|
-
const
|
|
291
|
+
const normalizedOptions = normalizeInitOptions(options);
|
|
292
|
+
const answers = await gatherConfiguration(normalizedOptions);
|
|
279
293
|
if (answers.installMcp || answers.installRuntime || answers.installOrchestrator) {
|
|
280
294
|
await installPythonPackages(answers, false);
|
|
281
295
|
}
|
|
@@ -295,8 +309,9 @@ async function runInit(options) {
|
|
|
295
309
|
}
|
|
296
310
|
await updateEnvFiles(answers);
|
|
297
311
|
await createCompanionConfig(answers);
|
|
312
|
+
await syncRemoteAutomationClient(answers, { enabled: !normalizedOptions.skipTest });
|
|
298
313
|
await createCompanionPack(answers);
|
|
299
|
-
const validationReport = await runValidation(answers, !
|
|
314
|
+
const validationReport = await runValidation(answers, !normalizedOptions.skipTest);
|
|
300
315
|
writeDoctorReport(validationReport);
|
|
301
316
|
printSuccess(answers, validationReport);
|
|
302
317
|
if (answers.configureGitHub && answers.githubRepo) {
|
|
@@ -454,6 +469,7 @@ function printBanner() {
|
|
|
454
469
|
async function gatherConfiguration(options) {
|
|
455
470
|
console.log(chalk.bold("\n\u{1F4CB} Project Configuration\n"));
|
|
456
471
|
const detectedClient = detectAiClient();
|
|
472
|
+
const requestedClient = normalizeClientOption(options.client);
|
|
457
473
|
const existingConfig = getExistingConfig();
|
|
458
474
|
const projectSlugResolution = resolveProjectSlug(existingConfig);
|
|
459
475
|
let detectedSlug = projectSlugResolution.slug;
|
|
@@ -486,7 +502,7 @@ async function gatherConfiguration(options) {
|
|
|
486
502
|
apiKey: providedApiKey || "YOUR_API_KEY_HERE",
|
|
487
503
|
apiKeyType: providedApiKey ? "api" : "none",
|
|
488
504
|
profile: initialProfile,
|
|
489
|
-
aiClient: detectedClient,
|
|
505
|
+
aiClient: requestedClient || detectedClient,
|
|
490
506
|
skipCompanion: !!options.skipCompanion,
|
|
491
507
|
skipMcp: !!options.skipMcp,
|
|
492
508
|
skipRuntime: !!options.skipRuntime,
|
|
@@ -553,7 +569,9 @@ async function gatherConfiguration(options) {
|
|
|
553
569
|
message: "Which AI client are you using?",
|
|
554
570
|
initial: Math.max(
|
|
555
571
|
0,
|
|
556
|
-
AI_CLIENT_CHOICES.findIndex(
|
|
572
|
+
AI_CLIENT_CHOICES.findIndex(
|
|
573
|
+
(choice) => choice.value === (requestedClient || detectedClient)
|
|
574
|
+
)
|
|
557
575
|
),
|
|
558
576
|
choices: AI_CLIENT_CHOICES
|
|
559
577
|
},
|
|
@@ -604,7 +622,7 @@ async function gatherConfiguration(options) {
|
|
|
604
622
|
apiKey: apiKey || "",
|
|
605
623
|
apiKeyType,
|
|
606
624
|
profile: response.profile || initialProfile,
|
|
607
|
-
aiClient: response.aiClient || detectedClient,
|
|
625
|
+
aiClient: requestedClient || response.aiClient || detectedClient,
|
|
608
626
|
skipCompanion: !!options.skipCompanion,
|
|
609
627
|
skipMcp: !!options.skipMcp,
|
|
610
628
|
skipRuntime: !!options.skipRuntime,
|
|
@@ -702,7 +720,8 @@ function normalizeClientOption(value) {
|
|
|
702
720
|
chatgpt: "chatgpt",
|
|
703
721
|
vscode: "vscode",
|
|
704
722
|
continue: "continue",
|
|
705
|
-
other: "other"
|
|
723
|
+
other: "other",
|
|
724
|
+
custom: "other"
|
|
706
725
|
};
|
|
707
726
|
const client = aliases[normalized];
|
|
708
727
|
if (!client) {
|
|
@@ -870,10 +889,14 @@ async function runDeviceAuthorization() {
|
|
|
870
889
|
spinner.fail("Authorization expired");
|
|
871
890
|
throw new Error("device code expired");
|
|
872
891
|
}
|
|
873
|
-
async function postJson(url, body, allowErrorResponse = false) {
|
|
892
|
+
async function postJson(url, body, allowErrorResponse = false, init = {}) {
|
|
893
|
+
const headers = {
|
|
894
|
+
...init.headers ?? {},
|
|
895
|
+
"Content-Type": "application/json"
|
|
896
|
+
};
|
|
874
897
|
const response = await fetch(url, {
|
|
875
|
-
method: "POST",
|
|
876
|
-
headers
|
|
898
|
+
method: init.method ?? "POST",
|
|
899
|
+
headers,
|
|
877
900
|
body: JSON.stringify(body)
|
|
878
901
|
});
|
|
879
902
|
const data = await response.json().catch(() => ({}));
|
|
@@ -1115,31 +1138,10 @@ async function createClaudeCodeConfig(answers) {
|
|
|
1115
1138
|
if (answers.profile === "runtime-only") {
|
|
1116
1139
|
return;
|
|
1117
1140
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
console.log(chalk.
|
|
1121
|
-
|
|
1122
|
-
fs.mkdirSync(claudeDir, { recursive: true });
|
|
1123
|
-
let settings = {};
|
|
1124
|
-
if (fs.existsSync(settingsPath)) {
|
|
1125
|
-
settings = asObjectRecord(JSON.parse(fs.readFileSync(settingsPath, "utf-8")));
|
|
1126
|
-
}
|
|
1127
|
-
const mcpServers = asObjectRecord(settings.mcpServers);
|
|
1128
|
-
settings.mcpServers = {
|
|
1129
|
-
...mcpServers,
|
|
1130
|
-
snipara: buildHostedMcpServer(answers)
|
|
1131
|
-
};
|
|
1132
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", { mode: 384 });
|
|
1133
|
-
try {
|
|
1134
|
-
fs.chmodSync(settingsPath, 384);
|
|
1135
|
-
} catch {
|
|
1136
|
-
}
|
|
1137
|
-
console.log(chalk.green(" \u2713 Updated ~/.claude/settings.json with Snipara MCP"));
|
|
1138
|
-
} catch (error) {
|
|
1139
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1140
|
-
console.log(chalk.yellow(` \u26A0\uFE0F Could not update Claude Code MCP config: ${detail}`));
|
|
1141
|
-
console.log(chalk.dim(" Run create-snipara doctor --client claude after manual setup."));
|
|
1142
|
-
}
|
|
1141
|
+
console.log(chalk.bold("\n\u{1F916} Claude Code MCP\n"));
|
|
1142
|
+
console.log(chalk.green(" \u2713 Project .mcp.json contains the Snipara Hosted MCP server"));
|
|
1143
|
+
console.log(chalk.dim(" Claude Code loads project-scoped MCP servers from .mcp.json."));
|
|
1144
|
+
console.log(chalk.dim(` Verify with: claude mcp list && claude mcp test snipara`));
|
|
1143
1145
|
}
|
|
1144
1146
|
async function createProjectBinding(answers) {
|
|
1145
1147
|
if (answers.profile === "runtime-only") {
|
|
@@ -1447,14 +1449,8 @@ async function createClaudeDesktopConfig(answers) {
|
|
|
1447
1449
|
}
|
|
1448
1450
|
async function installClaudeCodeHooks(answers) {
|
|
1449
1451
|
console.log(chalk.bold("\n\u{1FA9D} Installing Claude Code Hooks\n"));
|
|
1450
|
-
const claudeDir = path.join(
|
|
1452
|
+
const claudeDir = path.join(process.cwd(), ".claude");
|
|
1451
1453
|
const hooksDir = path.join(claudeDir, "hooks");
|
|
1452
|
-
if (!fs.existsSync(claudeDir)) {
|
|
1453
|
-
console.log(
|
|
1454
|
-
chalk.yellow(" \u26A0\uFE0F ~/.claude not found. Install Claude Code first, then rerun repair.\n")
|
|
1455
|
-
);
|
|
1456
|
-
return;
|
|
1457
|
-
}
|
|
1458
1454
|
if (!fs.existsSync(hooksDir)) {
|
|
1459
1455
|
fs.mkdirSync(hooksDir, { recursive: true });
|
|
1460
1456
|
}
|
|
@@ -1491,41 +1487,88 @@ async function installClaudeCodeHooks(answers) {
|
|
|
1491
1487
|
settings = {};
|
|
1492
1488
|
}
|
|
1493
1489
|
}
|
|
1494
|
-
settings.hooks = settings.hooks
|
|
1490
|
+
settings.hooks = asObjectRecord(settings.hooks);
|
|
1495
1491
|
const hooksConfig = settings.hooks;
|
|
1496
1492
|
const hookEntries = [
|
|
1497
1493
|
{
|
|
1498
1494
|
event: "PreToolUse",
|
|
1499
|
-
|
|
1500
|
-
|
|
1495
|
+
entry: {
|
|
1496
|
+
matcher: "Bash|Read|Grep|Glob|Edit|MultiEdit|Write",
|
|
1497
|
+
hooks: [
|
|
1498
|
+
{
|
|
1499
|
+
type: "command",
|
|
1500
|
+
command: createClaudeProjectHookCommand("snipara-stuck-guard.sh"),
|
|
1501
|
+
timeout: 10
|
|
1502
|
+
}
|
|
1503
|
+
]
|
|
1504
|
+
}
|
|
1501
1505
|
},
|
|
1502
1506
|
{
|
|
1503
|
-
event: "
|
|
1504
|
-
|
|
1505
|
-
|
|
1507
|
+
event: "SessionStart",
|
|
1508
|
+
entry: {
|
|
1509
|
+
matcher: "startup|resume|compact|clear",
|
|
1510
|
+
hooks: [
|
|
1511
|
+
{
|
|
1512
|
+
type: "command",
|
|
1513
|
+
command: createClaudeProjectHookCommand("snipara-startup.sh"),
|
|
1514
|
+
timeout: 30
|
|
1515
|
+
}
|
|
1516
|
+
]
|
|
1517
|
+
}
|
|
1506
1518
|
},
|
|
1507
1519
|
{
|
|
1508
1520
|
event: "PostToolUse",
|
|
1509
|
-
|
|
1510
|
-
|
|
1521
|
+
entry: {
|
|
1522
|
+
matcher: "Bash|Read|Grep|Glob|Edit|MultiEdit|Write",
|
|
1523
|
+
hooks: [
|
|
1524
|
+
{
|
|
1525
|
+
type: "command",
|
|
1526
|
+
command: createClaudeProjectHookCommand("snipara-session.sh"),
|
|
1527
|
+
timeout: 10
|
|
1528
|
+
}
|
|
1529
|
+
]
|
|
1530
|
+
}
|
|
1511
1531
|
},
|
|
1512
1532
|
{
|
|
1513
1533
|
event: "PreCompact",
|
|
1514
|
-
|
|
1534
|
+
entry: {
|
|
1535
|
+
matcher: "manual|auto",
|
|
1536
|
+
hooks: [
|
|
1537
|
+
{
|
|
1538
|
+
type: "command",
|
|
1539
|
+
command: createClaudeProjectHookCommand("snipara-compact.sh"),
|
|
1540
|
+
timeout: 30
|
|
1541
|
+
}
|
|
1542
|
+
]
|
|
1543
|
+
}
|
|
1515
1544
|
}
|
|
1516
1545
|
];
|
|
1517
|
-
for (const entry of hookEntries) {
|
|
1518
|
-
const eventHooks = hooksConfig[
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
);
|
|
1546
|
+
for (const { event, entry } of hookEntries) {
|
|
1547
|
+
const eventHooks = Array.isArray(hooksConfig[event]) ? hooksConfig[event] : [];
|
|
1548
|
+
const expectedCommand = entry.hooks[0].command;
|
|
1549
|
+
const exists = eventHooks.some((hook) => hookConfigEntryHasCommand(hook, expectedCommand));
|
|
1522
1550
|
if (!exists) {
|
|
1523
1551
|
eventHooks.push(entry);
|
|
1524
|
-
hooksConfig[
|
|
1552
|
+
hooksConfig[event] = eventHooks;
|
|
1525
1553
|
}
|
|
1526
1554
|
}
|
|
1527
1555
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1528
|
-
console.log(chalk.green(" \u2713 Updated
|
|
1556
|
+
console.log(chalk.green(" \u2713 Updated .claude/settings.json hooks"));
|
|
1557
|
+
}
|
|
1558
|
+
function createClaudeProjectHookCommand(scriptName) {
|
|
1559
|
+
return `bash -lc 'cd "\${CLAUDE_PROJECT_DIR:-$PWD}" && ./.claude/hooks/${scriptName}'`;
|
|
1560
|
+
}
|
|
1561
|
+
function hookConfigEntryHasCommand(entry, expectedCommand) {
|
|
1562
|
+
if (!entry || typeof entry !== "object") {
|
|
1563
|
+
return false;
|
|
1564
|
+
}
|
|
1565
|
+
const hooks = entry.hooks;
|
|
1566
|
+
if (!Array.isArray(hooks)) {
|
|
1567
|
+
return false;
|
|
1568
|
+
}
|
|
1569
|
+
return hooks.some(
|
|
1570
|
+
(hook) => hook && typeof hook === "object" && hook.command === expectedCommand
|
|
1571
|
+
);
|
|
1529
1572
|
}
|
|
1530
1573
|
function createStuckGuardHook(answers) {
|
|
1531
1574
|
return `#!/usr/bin/env bash
|
|
@@ -1679,31 +1722,49 @@ async function createCompanionConfig(answers) {
|
|
|
1679
1722
|
}
|
|
1680
1723
|
const configDir = path.join(process.cwd(), ".snipara", "companion");
|
|
1681
1724
|
const configPath = path.join(configDir, "config.json");
|
|
1682
|
-
const globalConfigPath = getGlobalCompanionConfigPath();
|
|
1683
1725
|
fs.mkdirSync(configDir, { recursive: true });
|
|
1684
|
-
fs.mkdirSync(path.dirname(globalConfigPath), { recursive: true });
|
|
1685
|
-
const existingGlobalConfig = readCompanionConfigFile(globalConfigPath);
|
|
1686
1726
|
const existingWorkspaceConfig = readCompanionConfigFile(configPath);
|
|
1687
|
-
const globalConfig = {
|
|
1688
|
-
...existingGlobalConfig,
|
|
1689
|
-
apiKey: answers.apiKey,
|
|
1690
|
-
apiUrl: API_URL
|
|
1691
|
-
};
|
|
1692
1727
|
const workspaceConfig = {
|
|
1693
|
-
|
|
1694
|
-
|
|
1728
|
+
...existingWorkspaceConfig,
|
|
1729
|
+
apiKey: answers.apiKey,
|
|
1730
|
+
apiUrl: API_URL,
|
|
1731
|
+
projectId: answers.projectId || answers.projectSlug,
|
|
1732
|
+
sessionId: typeof existingWorkspaceConfig.sessionId === "string" && existingWorkspaceConfig.sessionId ? existingWorkspaceConfig.sessionId : `sess_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
1733
|
+
client: getAutomationClientId(answers.aiClient)
|
|
1695
1734
|
};
|
|
1696
|
-
fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 2) + "\n");
|
|
1697
1735
|
fs.writeFileSync(configPath, JSON.stringify(workspaceConfig, null, 2) + "\n");
|
|
1698
|
-
ensureGitignoreEntry(
|
|
1699
|
-
|
|
1700
|
-
"# Snipara local companion workspace config"
|
|
1701
|
-
);
|
|
1702
|
-
console.log(chalk.green(" \u2713 Configured global companion auth"));
|
|
1703
|
-
console.log(chalk.green(" \u2713 Configured local companion workspace"));
|
|
1736
|
+
ensureGitignoreEntry(".snipara/companion/config.json", "# Snipara local companion config");
|
|
1737
|
+
console.log(chalk.green(" \u2713 Configured local companion config"));
|
|
1704
1738
|
}
|
|
1705
|
-
function
|
|
1706
|
-
|
|
1739
|
+
async function syncRemoteAutomationClient(answers, options) {
|
|
1740
|
+
if (!options.enabled || answers.apiKeyType === "none" || !answers.apiKey || answers.apiKey.includes("YOUR_") || !answers.projectSlug) {
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
const automationClient = getAutomationClientId(answers.aiClient);
|
|
1744
|
+
const url = `${BASE_URL}/api/cli/projects/${encodeURIComponent(
|
|
1745
|
+
answers.projectSlug
|
|
1746
|
+
)}/automation-client`;
|
|
1747
|
+
try {
|
|
1748
|
+
const response = await postJson(
|
|
1749
|
+
url,
|
|
1750
|
+
{ automationClient },
|
|
1751
|
+
false,
|
|
1752
|
+
{
|
|
1753
|
+
method: "PATCH",
|
|
1754
|
+
headers: { "X-API-Key": answers.apiKey }
|
|
1755
|
+
}
|
|
1756
|
+
);
|
|
1757
|
+
if (response.success === false) {
|
|
1758
|
+
throw new Error(response.message || "dashboard rejected automation client update");
|
|
1759
|
+
}
|
|
1760
|
+
console.log(chalk.green(` \u2713 Synced dashboard automation client: ${automationClient}`));
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
console.log(
|
|
1763
|
+
chalk.yellow(
|
|
1764
|
+
` \u26A0\uFE0F Could not sync dashboard automation client: ${error instanceof Error ? error.message : "unknown error"}`
|
|
1765
|
+
)
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1707
1768
|
}
|
|
1708
1769
|
function readCompanionConfigFile(filePath) {
|
|
1709
1770
|
if (!fs.existsSync(filePath)) {
|
|
@@ -1938,7 +1999,7 @@ function getClientPackFilename(client) {
|
|
|
1938
1999
|
}
|
|
1939
2000
|
function getClientConfigArtifacts(client) {
|
|
1940
2001
|
if (client === "claude-code") {
|
|
1941
|
-
return ["AGENTS.md", "CLAUDE.md", "
|
|
2002
|
+
return ["AGENTS.md", "CLAUDE.md", ".mcp.json MCP server", ".claude/settings.json hooks"];
|
|
1942
2003
|
}
|
|
1943
2004
|
if (client === "cursor") {
|
|
1944
2005
|
return [".mcp.json", ".cursor/rules/snipara.mdc", "dashboard automation bundle when installed"];
|
|
@@ -2197,11 +2258,15 @@ function getClaudeClientChecks(answers, detection) {
|
|
|
2197
2258
|
const checks = [];
|
|
2198
2259
|
const claudePaths = getClaudeConfigPaths();
|
|
2199
2260
|
const projectConfig = readJsonFile(path.join(process.cwd(), ".mcp.json"));
|
|
2261
|
+
const projectSettings = readJsonFile(path.join(process.cwd(), ".claude", "settings.json"));
|
|
2200
2262
|
const codeSettings = readJsonFile(claudePaths.codeSettingsPath);
|
|
2201
2263
|
const desktopConfig = readJsonFile(claudePaths.desktopConfigPath);
|
|
2202
2264
|
const projectHasSnipara = containsSniparaMcpReference(projectConfig);
|
|
2265
|
+
const projectSettingsHasSnipara = containsSniparaMcpReference(projectSettings);
|
|
2203
2266
|
const codeSettingsHasSnipara = containsSniparaMcpReference(codeSettings);
|
|
2267
|
+
const hasLegacyCodeSettingsMcp = projectSettingsHasSnipara || codeSettingsHasSnipara;
|
|
2204
2268
|
const desktopHasSnipara = containsSniparaMcpReference(desktopConfig);
|
|
2269
|
+
const projectHooksConfigured = containsClaudeHooksReference(projectSettings);
|
|
2205
2270
|
checks.push({
|
|
2206
2271
|
name: "claude_project_config",
|
|
2207
2272
|
status: projectHasSnipara ? "pass" : "warn",
|
|
@@ -2210,8 +2275,13 @@ function getClaudeClientChecks(answers, detection) {
|
|
|
2210
2275
|
if (answers.aiClient === "claude-code") {
|
|
2211
2276
|
checks.push({
|
|
2212
2277
|
name: "claude_code_settings",
|
|
2213
|
-
status:
|
|
2214
|
-
detail:
|
|
2278
|
+
status: hasLegacyCodeSettingsMcp ? "warn" : "pass",
|
|
2279
|
+
detail: hasLegacyCodeSettingsMcp ? "Legacy Claude settings contain an MCP server entry; Claude Code project MCP should live in .mcp.json or be managed by claude mcp add --scope project" : "Claude settings are not being used as the MCP destination; .claude/settings.json is hooks-only"
|
|
2280
|
+
});
|
|
2281
|
+
checks.push({
|
|
2282
|
+
name: "claude_code_hooks",
|
|
2283
|
+
status: !answers.installHooks || projectHooksConfigured ? "pass" : "warn",
|
|
2284
|
+
detail: answers.installHooks ? projectHooksConfigured ? "Project .claude/settings.json contains Snipara hook commands" : "Project .claude/settings.json hooks missing; run create-snipara repair --with-hooks" : "Claude hooks not requested"
|
|
2215
2285
|
});
|
|
2216
2286
|
}
|
|
2217
2287
|
if (answers.aiClient === "claude-desktop") {
|
|
@@ -2258,6 +2328,15 @@ function containsSniparaMcpReference(value) {
|
|
|
2258
2328
|
}
|
|
2259
2329
|
return false;
|
|
2260
2330
|
}
|
|
2331
|
+
function containsClaudeHooksReference(value) {
|
|
2332
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2333
|
+
return false;
|
|
2334
|
+
}
|
|
2335
|
+
const hooks = value.hooks;
|
|
2336
|
+
return Boolean(
|
|
2337
|
+
hooks && typeof hooks === "object" && JSON.stringify(hooks).includes(COMPANION_COMMAND_NAME)
|
|
2338
|
+
);
|
|
2339
|
+
}
|
|
2261
2340
|
function getCreateSniparaPackageFreshnessCheck() {
|
|
2262
2341
|
if (!commandExists("npm")) {
|
|
2263
2342
|
return {
|
|
@@ -2377,8 +2456,8 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
|
|
|
2377
2456
|
});
|
|
2378
2457
|
checks.push({
|
|
2379
2458
|
name: "hooks",
|
|
2380
|
-
status: answers.installHooks && targetClient === "claude-code" ? detection.files.
|
|
2381
|
-
detail: answers.installHooks && targetClient === "claude-code" ? detection.files.
|
|
2459
|
+
status: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeProjectSettings ? "pass" : "warn" : "pass",
|
|
2460
|
+
detail: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeProjectSettings ? "Project .claude/settings.json hooks present" : "Project .claude/settings.json hooks missing" : "Hooks not requested"
|
|
2382
2461
|
});
|
|
2383
2462
|
if (targetClient === "claude-code" || targetClient === "claude-desktop") {
|
|
2384
2463
|
checks.push(...getClaudeClientChecks(effectiveAnswers, detection));
|
|
@@ -2639,7 +2718,7 @@ function printSuccess(answers, report) {
|
|
|
2639
2718
|
}
|
|
2640
2719
|
if (answers.aiClient === "claude-code" && answers.profile !== "runtime-only") {
|
|
2641
2720
|
console.log(
|
|
2642
|
-
chalk.green(" \u2713
|
|
2721
|
+
chalk.green(" \u2713 Claude Code") + chalk.dim(" - .mcp.json MCP config, .claude/settings.json hooks when enabled")
|
|
2643
2722
|
);
|
|
2644
2723
|
}
|
|
2645
2724
|
console.log(chalk.green(" \u2713 .snipara/companion") + chalk.dim(" - Local pack + doctor report"));
|
|
@@ -2759,6 +2838,8 @@ function detectEnvironment() {
|
|
|
2759
2838
|
sniparaProject: fs.existsSync(path.join(process.cwd(), ".snipara", "project")),
|
|
2760
2839
|
claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
|
|
2761
2840
|
claudeSettings: fs.existsSync(claudePaths.codeSettingsPath),
|
|
2841
|
+
claudeProjectDir: fs.existsSync(path.join(process.cwd(), ".claude")),
|
|
2842
|
+
claudeProjectSettings: fs.existsSync(path.join(process.cwd(), ".claude", "settings.json")),
|
|
2762
2843
|
claudeDesktopConfig: fs.existsSync(claudePaths.desktopConfigPath),
|
|
2763
2844
|
cursorDir: fs.existsSync(path.join(homedir(), ".cursor")),
|
|
2764
2845
|
sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion")),
|
|
@@ -2984,7 +3065,8 @@ function detectRepoProjectSlug() {
|
|
|
2984
3065
|
try {
|
|
2985
3066
|
const remote = execSync("git remote get-url origin", {
|
|
2986
3067
|
encoding: "utf-8",
|
|
2987
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3068
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3069
|
+
timeout: 500
|
|
2988
3070
|
}).trim();
|
|
2989
3071
|
const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
|
|
2990
3072
|
if (match) {
|
|
@@ -3001,7 +3083,8 @@ function detectGitRepository() {
|
|
|
3001
3083
|
try {
|
|
3002
3084
|
const remoteUrl = execSync("git remote get-url origin", {
|
|
3003
3085
|
encoding: "utf-8",
|
|
3004
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3086
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3087
|
+
timeout: 500
|
|
3005
3088
|
}).trim();
|
|
3006
3089
|
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) || remoteUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
3007
3090
|
if (!match) {
|
|
@@ -3025,7 +3108,8 @@ function detectGitBranch() {
|
|
|
3025
3108
|
try {
|
|
3026
3109
|
const branch = execSync("git branch --show-current", {
|
|
3027
3110
|
encoding: "utf-8",
|
|
3028
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3111
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3112
|
+
timeout: 500
|
|
3029
3113
|
}).trim();
|
|
3030
3114
|
if (branch) {
|
|
3031
3115
|
return branch;
|
|
@@ -3035,7 +3119,8 @@ function detectGitBranch() {
|
|
|
3035
3119
|
try {
|
|
3036
3120
|
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
3037
3121
|
encoding: "utf-8",
|
|
3038
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3122
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3123
|
+
timeout: 500
|
|
3039
3124
|
}).trim();
|
|
3040
3125
|
return branch && branch !== "HEAD" ? branch : "main";
|
|
3041
3126
|
} catch {
|
|
@@ -3045,7 +3130,8 @@ function detectGitBranch() {
|
|
|
3045
3130
|
function isGitRepo() {
|
|
3046
3131
|
try {
|
|
3047
3132
|
execSync("git rev-parse --is-inside-work-tree", {
|
|
3048
|
-
stdio: "ignore"
|
|
3133
|
+
stdio: "ignore",
|
|
3134
|
+
timeout: 500
|
|
3049
3135
|
});
|
|
3050
3136
|
return true;
|
|
3051
3137
|
} catch {
|
package/package.json
CHANGED