create-snipara 1.3.22 → 1.3.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +260 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -197,6 +197,19 @@ Writes `.snipara/companion/doctor.json` and validates:
197
197
  - Snipara Sandbox CLI presence
198
198
  - environment files
199
199
  - hosted MCP reachability when a live API key is available
200
+ - exposed Hosted MCP tool aliases (`snipara_*`, with `rlm_*` reported as legacy)
201
+ - client-specific loading gaps that the terminal cannot prove
202
+
203
+ For Claude-specific support, run:
204
+
205
+ ```bash
206
+ npx create-snipara doctor --client claude
207
+ ```
208
+
209
+ This separates "Snipara config was written" from "Claude actually loaded the MCP
210
+ server." If Claude uses its cloud/UI MCP configuration instead of local files,
211
+ the doctor output points the user to Claude **Settings -> MCP** instead of
212
+ repeating local file edits.
200
213
 
201
214
  ### Repair local setup
202
215
 
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.22",
16
+ version: "1.3.23",
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",
@@ -174,7 +174,10 @@ function buildProgram() {
174
174
  printBanner();
175
175
  await runInit(options);
176
176
  });
177
- program.command("doctor").description("Inspect the current project and write a validation report").option("--json", "Print the report as JSON").action(async (options) => {
177
+ program.command("doctor").description("Inspect the current project and write a validation report").option(
178
+ "--client <client>",
179
+ "Run client-specific diagnostics: claude, claude-code, claude-desktop, cursor, codex, etc."
180
+ ).option("--skip-npm-check", "Skip npm latest-version freshness check").option("--json", "Print the report as JSON").action(async (options) => {
178
181
  printBanner();
179
182
  await runDoctor(options);
180
183
  });
@@ -291,8 +294,16 @@ async function runInit(options) {
291
294
  }
292
295
  }
293
296
  async function runDoctor(options) {
294
- const answers = loadExistingAnswers(options);
295
- const report = await runValidation(answers, true);
297
+ const requestedClient = normalizeClientOption(options.client);
298
+ const existingAnswers = loadExistingAnswers(options);
299
+ const answers = {
300
+ ...existingAnswers,
301
+ aiClient: requestedClient || existingAnswers.aiClient
302
+ };
303
+ const report = await runValidation(answers, true, {
304
+ client: requestedClient || answers.aiClient,
305
+ includeNpmFreshness: !options.skipNpmCheck
306
+ });
296
307
  writeDoctorReport(report);
297
308
  if (options.json) {
298
309
  console.log(JSON.stringify({ detection: detectEnvironment(), validation: report }, null, 2));
@@ -631,6 +642,34 @@ function resolveRequestedProfile(options) {
631
642
  function isInstallProfile(value) {
632
643
  return ["hosted-only", "hosted-companion", "full-stack", "runtime-only"].includes(value);
633
644
  }
645
+ function normalizeClientOption(value) {
646
+ if (!value) {
647
+ return null;
648
+ }
649
+ const normalized = value.trim().toLowerCase();
650
+ const aliases = {
651
+ claude: "claude-code",
652
+ "claude-code": "claude-code",
653
+ claude_code: "claude-code",
654
+ claudecli: "claude-code",
655
+ "claude-cli": "claude-code",
656
+ "claude-desktop": "claude-desktop",
657
+ claude_desktop: "claude-desktop",
658
+ cursor: "cursor",
659
+ windsurf: "windsurf",
660
+ codex: "codex",
661
+ gemini: "gemini",
662
+ chatgpt: "chatgpt",
663
+ vscode: "vscode",
664
+ continue: "continue",
665
+ other: "other"
666
+ };
667
+ const client = aliases[normalized];
668
+ if (!client) {
669
+ throw new Error(`Unknown client '${value}'. Use claude, claude-code, cursor, codex, or other.`);
670
+ }
671
+ return client;
672
+ }
634
673
  function installProfileChoices() {
635
674
  return [
636
675
  {
@@ -2044,15 +2083,143 @@ function buildGitHubAutomationUrl(answers) {
2044
2083
  }
2045
2084
  return url.toString();
2046
2085
  }
2047
- async function runValidation(answers, includeHostedCheck) {
2086
+ function getClaudeClientChecks(answers, detection) {
2087
+ const checks = [];
2088
+ const claudePaths = getClaudeConfigPaths();
2089
+ const projectConfig = readJsonFile(path.join(process.cwd(), ".mcp.json"));
2090
+ const codeSettings = readJsonFile(claudePaths.codeSettingsPath);
2091
+ const desktopConfig = readJsonFile(claudePaths.desktopConfigPath);
2092
+ const projectHasSnipara = containsSniparaMcpReference(projectConfig);
2093
+ const codeSettingsHasSnipara = containsSniparaMcpReference(codeSettings);
2094
+ const desktopHasSnipara = containsSniparaMcpReference(desktopConfig);
2095
+ checks.push({
2096
+ name: "claude_project_config",
2097
+ status: projectHasSnipara ? "pass" : "warn",
2098
+ detail: projectHasSnipara ? "Project .mcp.json contains a Snipara Hosted MCP server" : "Project .mcp.json does not contain a Snipara Hosted MCP server"
2099
+ });
2100
+ if (answers.aiClient === "claude-code") {
2101
+ checks.push({
2102
+ name: "claude_code_settings",
2103
+ status: detection.files.claudeSettings ? codeSettingsHasSnipara ? "pass" : "warn" : "warn",
2104
+ detail: detection.files.claudeSettings ? codeSettingsHasSnipara ? "~/.claude/settings.json references Snipara" : "~/.claude/settings.json exists but no Snipara MCP server was found" : "~/.claude/settings.json not found; Claude may require Settings -> MCP UI setup"
2105
+ });
2106
+ }
2107
+ if (answers.aiClient === "claude-desktop") {
2108
+ checks.push({
2109
+ name: "claude_desktop_config",
2110
+ status: detection.files.claudeDesktopConfig ? desktopHasSnipara ? "pass" : "warn" : "warn",
2111
+ detail: detection.files.claudeDesktopConfig ? desktopHasSnipara ? "Claude Desktop config references Snipara" : "Claude Desktop config exists but no Snipara MCP server was found" : "Claude Desktop config not found"
2112
+ });
2113
+ }
2114
+ checks.push({
2115
+ name: "claude_tools_visible",
2116
+ status: "warn",
2117
+ detail: "This terminal cannot prove Claude loaded the MCP server; confirm Snipara tools in Claude Settings -> MCP or ToolSearch after restart"
2118
+ });
2119
+ return checks;
2120
+ }
2121
+ function readJsonFile(filePath) {
2122
+ if (!fs.existsSync(filePath)) {
2123
+ return null;
2124
+ }
2125
+ try {
2126
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
2127
+ } catch {
2128
+ return null;
2129
+ }
2130
+ }
2131
+ function containsSniparaMcpReference(value) {
2132
+ if (value === null || value === void 0) {
2133
+ return false;
2134
+ }
2135
+ if (typeof value === "string") {
2136
+ return value.includes("api.snipara.com/mcp") || value.includes("snipara-mcp");
2137
+ }
2138
+ if (Array.isArray(value)) {
2139
+ return value.some((item) => containsSniparaMcpReference(item));
2140
+ }
2141
+ if (typeof value === "object") {
2142
+ const record = value;
2143
+ const mcpServers = record.mcpServers;
2144
+ if (mcpServers && typeof mcpServers === "object" && "snipara" in mcpServers) {
2145
+ return true;
2146
+ }
2147
+ return Object.values(record).some((nested) => containsSniparaMcpReference(nested));
2148
+ }
2149
+ return false;
2150
+ }
2151
+ function getCreateSniparaPackageFreshnessCheck() {
2152
+ if (!commandExists("npm")) {
2153
+ return {
2154
+ name: "create_snipara_npm_version",
2155
+ status: "warn",
2156
+ detail: "npm not found; could not compare local create-snipara with npm latest"
2157
+ };
2158
+ }
2159
+ try {
2160
+ const raw = execSync("npm view create-snipara version --json", {
2161
+ encoding: "utf-8",
2162
+ stdio: ["ignore", "pipe", "ignore"],
2163
+ shell: "/bin/sh",
2164
+ timeout: 4e3
2165
+ }).trim();
2166
+ const latest = raw.replace(/^"|"$/g, "");
2167
+ const comparison = compareSemver(VERSION, latest);
2168
+ if (comparison === 0) {
2169
+ return {
2170
+ name: "create_snipara_npm_version",
2171
+ status: "pass",
2172
+ detail: `Local package version ${VERSION} matches npm latest`
2173
+ };
2174
+ }
2175
+ if (comparison > 0) {
2176
+ return {
2177
+ name: "create_snipara_npm_version",
2178
+ status: "warn",
2179
+ detail: `Local package version ${VERSION} is newer than npm latest ${latest}; publish may be pending`
2180
+ };
2181
+ }
2182
+ return {
2183
+ name: "create_snipara_npm_version",
2184
+ status: "warn",
2185
+ detail: `npm latest is ${latest}; rerun with npx create-snipara@latest if this local copy is older`
2186
+ };
2187
+ } catch {
2188
+ return {
2189
+ name: "create_snipara_npm_version",
2190
+ status: "warn",
2191
+ detail: "Could not reach npm to compare create-snipara latest version"
2192
+ };
2193
+ }
2194
+ }
2195
+ function compareSemver(left, right) {
2196
+ const leftParts = left.split(".").map((part) => Number.parseInt(part, 10) || 0);
2197
+ const rightParts = right.split(".").map((part) => Number.parseInt(part, 10) || 0);
2198
+ const length = Math.max(leftParts.length, rightParts.length);
2199
+ for (let index = 0; index < length; index += 1) {
2200
+ const diff = (leftParts[index] || 0) - (rightParts[index] || 0);
2201
+ if (diff !== 0) {
2202
+ return diff > 0 ? 1 : -1;
2203
+ }
2204
+ }
2205
+ return 0;
2206
+ }
2207
+ async function runValidation(answers, includeHostedCheck, options = {}) {
2048
2208
  console.log(chalk.bold("\n\u{1FA7A} Running Validation\n"));
2049
2209
  const checks = [];
2050
2210
  const detection = detectEnvironment();
2211
+ const targetClient = options.client || answers.aiClient;
2212
+ const effectiveAnswers = { ...answers, aiClient: targetClient };
2051
2213
  checks.push({
2052
2214
  name: "project_slug",
2053
2215
  status: answers.projectSlug ? "pass" : "fail",
2054
2216
  detail: answers.projectSlug || "Project slug missing"
2055
2217
  });
2218
+ checks.push({
2219
+ name: "target_client",
2220
+ status: "pass",
2221
+ detail: getClientDisplayName(targetClient)
2222
+ });
2056
2223
  checks.push({
2057
2224
  name: "mcp_config",
2058
2225
  status: detection.files.mcpConfig ? "pass" : "warn",
@@ -2090,9 +2257,15 @@ async function runValidation(answers, includeHostedCheck) {
2090
2257
  });
2091
2258
  checks.push({
2092
2259
  name: "hooks",
2093
- status: answers.installHooks && answers.aiClient === "claude-code" ? detection.files.claudeDir ? "pass" : "warn" : "pass",
2094
- detail: answers.installHooks && answers.aiClient === "claude-code" ? detection.files.claudeDir ? "~/.claude detected" : "~/.claude missing" : "Hooks not requested"
2260
+ status: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeDir ? "pass" : "warn" : "pass",
2261
+ detail: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeDir ? "~/.claude detected" : "~/.claude missing" : "Hooks not requested"
2095
2262
  });
2263
+ if (targetClient === "claude-code" || targetClient === "claude-desktop") {
2264
+ checks.push(...getClaudeClientChecks(effectiveAnswers, detection));
2265
+ }
2266
+ if (options.includeNpmFreshness) {
2267
+ checks.push(getCreateSniparaPackageFreshnessCheck());
2268
+ }
2096
2269
  const githubRepo = answers.githubRepo || detectGitRepository();
2097
2270
  checks.push({
2098
2271
  name: "github_answer_packs",
@@ -2125,11 +2298,12 @@ function writeDoctorReport(report) {
2125
2298
  console.log(chalk.green(" \u2713 Wrote .snipara/companion/doctor.json"));
2126
2299
  }
2127
2300
  function printDoctorReport(detection, report) {
2301
+ const targetClient = report.checks.find((check) => check.name === "target_client")?.detail || detection.aiClient;
2128
2302
  console.log(chalk.bold("\n\u{1FA7A} Snipara Doctor\n"));
2129
2303
  console.log(
2130
2304
  ` Project slug: ${detection.existingConfig.slug || detection.projectSlug || "unknown"}`
2131
2305
  );
2132
- console.log(` AI client: ${detection.aiClient}`);
2306
+ console.log(` AI client: ${targetClient}`);
2133
2307
  console.log(` In git repo: ${detection.inGitRepo ? "yes" : "no"}`);
2134
2308
  console.log(` Profile: ${report.profile}`);
2135
2309
  console.log("");
@@ -2164,22 +2338,31 @@ async function testHostedConnection(answers) {
2164
2338
  ];
2165
2339
  }
2166
2340
  spinner.succeed("Connected to Snipara Hosted MCP.");
2341
+ const toolNames = extractMcpToolNames(toolsResponse.data);
2342
+ const hasSniparaTools = toolNames.some((name) => name.startsWith("snipara_"));
2343
+ const hasLegacyTools = toolNames.some((name) => name.startsWith("rlm_"));
2167
2344
  const checks = [
2168
2345
  {
2169
2346
  name: "hosted_connection",
2170
2347
  status: "pass",
2171
2348
  detail: "tools/list succeeded"
2349
+ },
2350
+ {
2351
+ name: "hosted_tool_aliases",
2352
+ status: hasSniparaTools ? "pass" : hasLegacyTools ? "warn" : "warn",
2353
+ detail: hasSniparaTools ? "snipara_* tools are exposed by Hosted MCP" : hasLegacyTools ? "Only legacy rlm_* tool aliases were detected; Hosted MCP is usable but naming is stale" : "tools/list succeeded, but expected snipara_* tools were not detected"
2172
2354
  }
2173
2355
  ];
2356
+ const settingsTool = hasSniparaTools ? "snipara_settings" : hasLegacyTools ? "rlm_settings" : "snipara_settings";
2174
2357
  const settingsResponse = await callHostedMcp(answers, "tools/call", {
2175
- name: "snipara_settings",
2358
+ name: settingsTool,
2176
2359
  arguments: {}
2177
2360
  });
2178
2361
  if (settingsResponse.ok && !settingsResponse.data.error) {
2179
2362
  checks.push({
2180
2363
  name: "hosted_settings",
2181
2364
  status: "pass",
2182
- detail: "snipara_settings tool call succeeded"
2365
+ detail: `${settingsTool} tool call succeeded`
2183
2366
  });
2184
2367
  } else {
2185
2368
  checks.push({
@@ -2200,6 +2383,15 @@ async function testHostedConnection(answers) {
2200
2383
  ];
2201
2384
  }
2202
2385
  }
2386
+ function extractMcpToolNames(data) {
2387
+ const tools = data.result?.tools;
2388
+ if (!Array.isArray(tools)) {
2389
+ return [];
2390
+ }
2391
+ return tools.map(
2392
+ (tool) => tool && typeof tool === "object" && "name" in tool ? String(tool.name || "") : ""
2393
+ ).filter(Boolean);
2394
+ }
2203
2395
  async function callHostedMcp(answers, method, params) {
2204
2396
  const response = await fetch(`${API_URL}/mcp/${answers.projectSlug}`, {
2205
2397
  method: "POST",
@@ -2251,6 +2443,39 @@ function describeJsonRpcError(data) {
2251
2443
  const code = data.error.code !== void 0 ? ` ${data.error.code}` : "";
2252
2444
  return `JSON-RPC error${code}: ${data.error.message || "unknown error"}`;
2253
2445
  }
2446
+ function getCheck(report, name) {
2447
+ return report.checks.find((check) => check.name === name);
2448
+ }
2449
+ function formatCheckStatus(check) {
2450
+ if (!check) {
2451
+ return "not checked";
2452
+ }
2453
+ if (check.status === "pass") {
2454
+ return `OK - ${check.detail}`;
2455
+ }
2456
+ if (check.status === "fail") {
2457
+ return `FAILED - ${check.detail}`;
2458
+ }
2459
+ return `NEEDS ATTENTION - ${check.detail}`;
2460
+ }
2461
+ function printSetupVerdict(answers, report) {
2462
+ const hostedConnection = getCheck(report, "hosted_connection");
2463
+ const toolAliases = getCheck(report, "hosted_tool_aliases");
2464
+ const claudeTools = getCheck(report, "claude_tools_visible");
2465
+ console.log(chalk.bold("\nSetup verdict:\n"));
2466
+ console.log(` Config written: ${answers.profile !== "runtime-only" ? "OK" : "not requested"}`);
2467
+ console.log(` Hosted MCP: ${formatCheckStatus(hostedConnection)}`);
2468
+ if (toolAliases) {
2469
+ console.log(` Tool aliases: ${formatCheckStatus(toolAliases)}`);
2470
+ }
2471
+ if (answers.aiClient === "claude-code" || answers.aiClient === "claude-desktop") {
2472
+ console.log(
2473
+ ` Claude loaded MCP: ${claudeTools ? formatCheckStatus(claudeTools) : "not verified"}`
2474
+ );
2475
+ } else {
2476
+ console.log(" AI client loaded MCP: not verifiable from this terminal");
2477
+ }
2478
+ }
2254
2479
  function printSuccess(answers, report) {
2255
2480
  console.log(
2256
2481
  chalk.green(`
@@ -2263,6 +2488,7 @@ function printSuccess(answers, report) {
2263
2488
  );
2264
2489
  console.log(chalk.bold("Installed profile:\n"));
2265
2490
  console.log(` ${chalk.green("\u2713")} ${answers.profile}`);
2491
+ printSetupVerdict(answers, report);
2266
2492
  console.log(chalk.bold("\nWhat was installed:\n"));
2267
2493
  if (answers.installMcp) {
2268
2494
  console.log(
@@ -2300,6 +2526,20 @@ function printSuccess(answers, report) {
2300
2526
  nextStep += 1;
2301
2527
  };
2302
2528
  printNextStep("Restart your AI client if you created or changed hosted MCP config.");
2529
+ if (answers.aiClient === "claude-code") {
2530
+ printNextStep(
2531
+ "For Claude Code, confirm Snipara appears in Settings -> MCP or ToolSearch; local files alone do not prove Claude loaded the server."
2532
+ );
2533
+ printNextStep("If Snipara tools are missing, run: npx create-snipara doctor --client claude");
2534
+ }
2535
+ if (answers.aiClient === "claude-desktop") {
2536
+ printNextStep(
2537
+ "For Claude Desktop, confirm the Snipara server appears in the desktop MCP settings after restart."
2538
+ );
2539
+ printNextStep(
2540
+ "If Snipara tools are missing, run: npx create-snipara doctor --client claude-desktop"
2541
+ );
2542
+ }
2303
2543
  if (answers.aiClient === "codex") {
2304
2544
  printNextStep(
2305
2545
  "For Codex, merge .snipara/templates/codex-config.toml into ~/.codex/config.toml if the app does not read project .codex/config.toml."
@@ -2365,8 +2605,16 @@ function commandExists(command) {
2365
2605
  return false;
2366
2606
  }
2367
2607
  }
2608
+ function getClaudeConfigPaths() {
2609
+ const desktopDir = process.platform === "darwin" ? path.join(homedir(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path.join(homedir(), "AppData", "Roaming", "Claude") : path.join(homedir(), ".config", "claude");
2610
+ return {
2611
+ codeSettingsPath: path.join(homedir(), ".claude", "settings.json"),
2612
+ desktopConfigPath: path.join(desktopDir, "claude_desktop_config.json")
2613
+ };
2614
+ }
2368
2615
  function detectEnvironment() {
2369
2616
  const existingConfig = getExistingConfig();
2617
+ const claudePaths = getClaudeConfigPaths();
2370
2618
  return {
2371
2619
  cwd: process.cwd(),
2372
2620
  projectSlug: detectProjectSlug(),
@@ -2382,6 +2630,8 @@ function detectEnvironment() {
2382
2630
  codexConfig: fs.existsSync(path.join(process.cwd(), ".codex", "config.toml")),
2383
2631
  cursorRule: fs.existsSync(path.join(process.cwd(), ".cursor", "rules", "snipara.mdc")),
2384
2632
  claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
2633
+ claudeSettings: fs.existsSync(claudePaths.codeSettingsPath),
2634
+ claudeDesktopConfig: fs.existsSync(claudePaths.desktopConfigPath),
2385
2635
  cursorDir: fs.existsSync(path.join(homedir(), ".cursor")),
2386
2636
  sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion")),
2387
2637
  sniparaTemplates: fs.existsSync(path.join(process.cwd(), ".snipara", "templates"))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-snipara",
3
- "version": "1.3.22",
3
+ "version": "1.3.23",
4
4
  "description": "Onboard Snipara Hosted MCP Context + Memory with auth, companion workflows, and optional Snipara Sandbox",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",