create-snipara 1.3.24 → 1.3.26

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 +0 -1
  2. package/dist/index.js +193 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -282,7 +282,6 @@ That keeps business truth review separate from agent onboarding.
282
282
  npx create-snipara --profile hosted-companion
283
283
  npx create-snipara --profile hosted-only
284
284
  npx create-snipara --profile full-stack
285
- npx create-snipara --full
286
285
  npx create-snipara --advanced
287
286
 
288
287
  # Sandbox-only local execution
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command } from "commander";
4
+ import { Command, Option } from "commander";
5
5
  import prompts from "prompts";
6
6
  import chalk from "chalk";
7
7
  import ora from "ora";
@@ -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.24",
16
+ version: "1.3.26",
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",
@@ -170,7 +170,7 @@ function buildProgram() {
170
170
  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("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
171
171
  "--profile <profile>",
172
172
  "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
173
- ).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").option("--full", "Alias for --profile full-stack").option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
173
+ ).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) => {
174
174
  printBanner();
175
175
  await runInit(options);
176
176
  });
@@ -185,7 +185,7 @@ function buildProgram() {
185
185
  printBanner();
186
186
  await runRepair(options);
187
187
  });
188
- program.command("upgrade").description("Upgrade installed Snipara local components").option("--with-hooks", "Refresh hooks after upgrade").option("--with-runtime", "Upgrade Snipara Sandbox even if not detected").option("--with-mcp", "Upgrade snipara-mcp even if not detected").option("--with-companion", "Upgrade companion even if not detected").option("--with-orchestrator", "Upgrade snipara-orchestrator even if not detected").action(async (options) => {
188
+ program.command("upgrade").description("Upgrade installed Snipara local components").option("--slug <slug>", "Project slug override").option("--with-hooks", "Refresh hooks after upgrade").option("--with-runtime", "Upgrade Snipara Sandbox even if not detected").option("--with-mcp", "Upgrade snipara-mcp even if not detected").option("--with-companion", "Upgrade companion even if not detected").option("--with-orchestrator", "Upgrade snipara-orchestrator even if not detected").action(async (options) => {
189
189
  printBanner();
190
190
  await runUpgrade(options);
191
191
  });
@@ -204,7 +204,7 @@ function addSharedRootOptions(program) {
204
204
  program.option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
205
205
  "--profile <profile>",
206
206
  "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
207
- ).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").option("--full", "Alias for --profile full-stack").option("-y, --yes", "Accept defaults without prompting");
207
+ ).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");
208
208
  }
209
209
  async function main() {
210
210
  const program = buildProgram();
@@ -385,7 +385,7 @@ async function runPrintConfig(options) {
385
385
  repository: githubRepo,
386
386
  answerPacks: githubAnswerPacks
387
387
  },
388
- inferredSetup: answers
388
+ inferredSetup: redactSetupAnswers(answers)
389
389
  },
390
390
  null,
391
391
  2
@@ -395,6 +395,7 @@ async function runPrintConfig(options) {
395
395
  }
396
396
  console.log(chalk.bold("\n\u{1F4E6} Current Setup\n"));
397
397
  console.log(` Project slug: ${answers.projectSlug}`);
398
+ console.log(` Project slug source: ${detection.projectSlugSource || "not detected"}`);
398
399
  console.log(` Project id: ${answers.projectId || "not-set"}`);
399
400
  console.log(` Profile: ${answers.profile}`);
400
401
  console.log(` AI client: ${answers.aiClient}`);
@@ -407,11 +408,21 @@ async function runPrintConfig(options) {
407
408
  console.log(` Snipara Orchestrator: ${answers.installOrchestrator ? "enabled" : "disabled"}`);
408
409
  console.log(` Hooks: ${answers.installHooks ? "enabled" : "disabled"}`);
409
410
  console.log(` GitHub repo: ${githubRepo ? githubRepo.fullName : "not detected"}`);
411
+ if (detection.detectedRepoSlug && detection.detectedRepoSlug !== answers.projectSlug) {
412
+ console.log(` Git repo slug: ${detection.detectedRepoSlug}`);
413
+ }
410
414
  console.log(` ${GITHUB_ANSWER_PACKS_LABEL}: ${githubAnswerPacks}`);
411
415
  console.log(` Existing .mcp.json: ${detection.files.mcpConfig ? "yes" : "no"}`);
412
416
  console.log(` Companion pack: ${detection.files.sniparaCompanionPack ? "yes" : "no"}`);
413
417
  console.log("");
414
418
  }
419
+ function redactSetupAnswers(answers) {
420
+ return {
421
+ ...answers,
422
+ apiKey: answers.apiKey.includes("YOUR_") ? answers.apiKey : "[redacted]",
423
+ llmApiKey: answers.llmApiKey ? "[redacted]" : void 0
424
+ };
425
+ }
415
426
  function printBanner() {
416
427
  console.log(
417
428
  chalk.cyan(`
@@ -431,10 +442,11 @@ function printBanner() {
431
442
  }
432
443
  async function gatherConfiguration(options) {
433
444
  console.log(chalk.bold("\n\u{1F4CB} Project Configuration\n"));
434
- let detectedSlug = detectProjectSlug();
435
- let detectedProjectId = "";
436
445
  const detectedClient = detectAiClient();
437
446
  const existingConfig = getExistingConfig();
447
+ const projectSlugResolution = resolveProjectSlug(existingConfig);
448
+ let detectedSlug = projectSlugResolution.slug;
449
+ let detectedProjectId = "";
438
450
  const detectedGitRepo = detectGitRepository();
439
451
  const showAdvancedPrompts = Boolean(options.advanced);
440
452
  if (options.github && !detectedGitRepo) {
@@ -446,7 +458,6 @@ async function gatherConfiguration(options) {
446
458
  }
447
459
  if (existingConfig) {
448
460
  console.log(chalk.dim(" Found existing .mcp.json configuration"));
449
- detectedSlug = existingConfig.slug || detectedSlug;
450
461
  detectedProjectId = existingConfig.projectId || "";
451
462
  }
452
463
  const initialProfile = resolveRequestedProfile(options);
@@ -2230,6 +2241,16 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
2230
2241
  status: answers.projectSlug ? "pass" : "fail",
2231
2242
  detail: answers.projectSlug || "Project slug missing"
2232
2243
  });
2244
+ const configuredSlugSignals = collectConfiguredProjectSlugSignals(detection.existingConfig);
2245
+ const presentSlugSignals = configuredSlugSignals.filter((signal) => signal.slug);
2246
+ const uniqueConfiguredSlugs = new Set(presentSlugSignals.map((signal) => signal.slug));
2247
+ if (uniqueConfiguredSlugs.size > 1) {
2248
+ checks.push({
2249
+ name: "project_slug_consistency",
2250
+ status: "warn",
2251
+ detail: `Configured slug mismatch: ${presentSlugSignals.map((signal) => `${signal.source}=${signal.slug}`).join(", ")}`
2252
+ });
2253
+ }
2233
2254
  checks.push({
2234
2255
  name: "target_client",
2235
2256
  status: "pass",
@@ -2315,9 +2336,11 @@ function writeDoctorReport(report) {
2315
2336
  function printDoctorReport(detection, report) {
2316
2337
  const targetClient = report.checks.find((check) => check.name === "target_client")?.detail || detection.aiClient;
2317
2338
  console.log(chalk.bold("\n\u{1FA7A} Snipara Doctor\n"));
2318
- console.log(
2319
- ` Project slug: ${detection.existingConfig.slug || detection.projectSlug || "unknown"}`
2320
- );
2339
+ console.log(` Project slug: ${detection.projectSlug || "unknown"}`);
2340
+ console.log(` Project slug source: ${detection.projectSlugSource || "not detected"}`);
2341
+ if (detection.detectedRepoSlug && detection.detectedRepoSlug !== detection.projectSlug) {
2342
+ console.log(` Git repo slug: ${detection.detectedRepoSlug}`);
2343
+ }
2321
2344
  console.log(` AI client: ${targetClient}`);
2322
2345
  console.log(` In git repo: ${detection.inGitRepo ? "yes" : "no"}`);
2323
2346
  console.log(` Profile: ${report.profile}`);
@@ -2629,10 +2652,13 @@ function getClaudeConfigPaths() {
2629
2652
  }
2630
2653
  function detectEnvironment() {
2631
2654
  const existingConfig = getExistingConfig();
2655
+ const projectSlugResolution = resolveProjectSlug(existingConfig);
2632
2656
  const claudePaths = getClaudeConfigPaths();
2633
2657
  return {
2634
2658
  cwd: process.cwd(),
2635
- projectSlug: detectProjectSlug(),
2659
+ projectSlug: projectSlugResolution.slug,
2660
+ projectSlugSource: projectSlugResolution.source,
2661
+ detectedRepoSlug: detectRepoProjectSlug(),
2636
2662
  aiClient: detectAiClient(),
2637
2663
  inGitRepo: isGitRepo(),
2638
2664
  files: {
@@ -2644,6 +2670,7 @@ function detectEnvironment() {
2644
2670
  claudeMd: fs.existsSync(path.join(process.cwd(), "CLAUDE.md")),
2645
2671
  codexConfig: fs.existsSync(path.join(process.cwd(), ".codex", "config.toml")),
2646
2672
  cursorRule: fs.existsSync(path.join(process.cwd(), ".cursor", "rules", "snipara.mdc")),
2673
+ sniparaProject: fs.existsSync(path.join(process.cwd(), ".snipara", "project")),
2647
2674
  claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
2648
2675
  claudeSettings: fs.existsSync(claudePaths.codeSettingsPath),
2649
2676
  claudeDesktopConfig: fs.existsSync(claudePaths.desktopConfigPath),
@@ -2683,7 +2710,7 @@ function loadExistingAnswers(options) {
2683
2710
  const apiKey = providedApiKey || envApiKey || "YOUR_API_KEY_HERE";
2684
2711
  const apiKeyType = effectiveProfile === "runtime-only" ? "none" : providedApiKey || envApiKey ? "api" : "none";
2685
2712
  return buildAnswersFromProfile({
2686
- projectSlug: options.slug || detection.existingConfig.slug || detection.projectSlug || "my-project",
2713
+ projectSlug: options.slug || detection.projectSlug || "my-project",
2687
2714
  projectId: options.projectId || detection.existingConfig.projectId || "",
2688
2715
  apiKey,
2689
2716
  apiKeyType,
@@ -2714,17 +2741,157 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
2714
2741
  function readApiKeyFromEnvFiles() {
2715
2742
  const candidates = [".env.local", ".env"];
2716
2743
  for (const file of candidates) {
2717
- const filePath = path.join(process.cwd(), file);
2718
- if (!fs.existsSync(filePath)) continue;
2744
+ const value = readEnvValueFromFile(file, ["SNIPARA_API_KEY", "RLM_API_KEY"]);
2745
+ if (value) {
2746
+ return value;
2747
+ }
2748
+ }
2749
+ return null;
2750
+ }
2751
+ function resolveProjectSlug(existingConfig = getExistingConfig()) {
2752
+ const configuredSignals = collectConfiguredProjectSlugSignals(existingConfig);
2753
+ const signals = [
2754
+ ...configuredSignals,
2755
+ { source: "git remote", slug: detectRepoProjectSlug() },
2756
+ { source: "working directory", slug: detectDirectoryProjectSlug() }
2757
+ ];
2758
+ const selected = signals.find((signal) => signal.slug);
2759
+ return {
2760
+ slug: selected?.slug || null,
2761
+ source: selected?.source || null,
2762
+ signals
2763
+ };
2764
+ }
2765
+ function collectConfiguredProjectSlugSignals(existingConfig = getExistingConfig()) {
2766
+ return [
2767
+ { source: ".mcp.json", slug: normalizeSlugCandidate(existingConfig?.slug) },
2768
+ { source: ".snipara/project", slug: readProjectSlugFromProjectBinding() },
2769
+ {
2770
+ source: ".codex/config.toml",
2771
+ slug: readProjectSlugFromTextFile(path.join(process.cwd(), ".codex", "config.toml"))
2772
+ },
2773
+ {
2774
+ source: ".snipara/templates/codex-config.toml",
2775
+ slug: readProjectSlugFromTextFile(
2776
+ path.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
2777
+ )
2778
+ },
2779
+ { source: ".env.local", slug: readProjectSlugFromEnvFile(".env.local") },
2780
+ { source: ".env", slug: readProjectSlugFromEnvFile(".env") }
2781
+ ];
2782
+ }
2783
+ function normalizeSlugCandidate(value) {
2784
+ if (!value) {
2785
+ return null;
2786
+ }
2787
+ const trimmed = value.trim().replace(/^["']|["']$/g, "");
2788
+ if (!trimmed || /^your_/i.test(trimmed) || /^YOUR_/.test(trimmed) || trimmed.includes("<") || trimmed.includes(">")) {
2789
+ return null;
2790
+ }
2791
+ return trimmed;
2792
+ }
2793
+ function slugifyFallback(value) {
2794
+ const normalized = normalizeSlugCandidate(value);
2795
+ if (!normalized) {
2796
+ return null;
2797
+ }
2798
+ const slug = normalized.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2799
+ return slug || null;
2800
+ }
2801
+ function extractSlugFromMcpEndpoint(value) {
2802
+ const endpoint = normalizeSlugCandidate(value);
2803
+ if (!endpoint) {
2804
+ return null;
2805
+ }
2806
+ const match = endpoint.match(/\/mcp\/([^/?#\s"']+)/);
2807
+ return normalizeSlugCandidate(match?.[1] ? decodeURIComponent(match[1]) : null);
2808
+ }
2809
+ function extractSlugFromText(content) {
2810
+ const match = content.match(/\/mcp\/([^/?#\s"'`]+)/);
2811
+ return normalizeSlugCandidate(match?.[1] ? decodeURIComponent(match[1]) : null);
2812
+ }
2813
+ function readProjectSlugFromTextFile(filePath) {
2814
+ if (!fs.existsSync(filePath)) {
2815
+ return null;
2816
+ }
2817
+ try {
2818
+ return extractSlugFromText(fs.readFileSync(filePath, "utf-8"));
2819
+ } catch {
2820
+ return null;
2821
+ }
2822
+ }
2823
+ function readProjectSlugFromProjectBinding() {
2824
+ const filePath = path.join(process.cwd(), ".snipara", "project");
2825
+ if (!fs.existsSync(filePath)) {
2826
+ return null;
2827
+ }
2828
+ try {
2829
+ const content = fs.readFileSync(filePath, "utf-8").trim();
2830
+ if (!content) {
2831
+ return null;
2832
+ }
2833
+ if (content.startsWith("{")) {
2834
+ const parsed = JSON.parse(content);
2835
+ return normalizeSlugCandidate(parsed.slug || parsed.projectSlug || parsed.project_slug);
2836
+ }
2837
+ for (const line of content.split(/\r?\n/)) {
2838
+ const trimmed = line.trim();
2839
+ if (!trimmed || trimmed.startsWith("#")) {
2840
+ continue;
2841
+ }
2842
+ const keyed = trimmed.match(/^(?:slug|project|projectSlug|project_slug)\s*[:=]\s*(.+)$/i);
2843
+ if (keyed?.[1]) {
2844
+ return normalizeSlugCandidate(stripInlineEnvComment(keyed[1]));
2845
+ }
2846
+ }
2847
+ const firstValue = content.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !line.startsWith("#"));
2848
+ return normalizeSlugCandidate(firstValue);
2849
+ } catch {
2850
+ return null;
2851
+ }
2852
+ }
2853
+ function readProjectSlugFromEnvFile(file) {
2854
+ const explicitSlug = readEnvValueFromFile(file, ["SNIPARA_PROJECT_SLUG", "RLM_PROJECT_SLUG"]);
2855
+ if (explicitSlug) {
2856
+ return explicitSlug;
2857
+ }
2858
+ const legacySlug = readEnvValueFromFile(file, ["SNIPARA_PROJECT_ID", "RLM_PROJECT_ID"]);
2859
+ if (legacySlug && /^[a-z0-9][a-z0-9-]*$/i.test(legacySlug)) {
2860
+ return legacySlug;
2861
+ }
2862
+ return null;
2863
+ }
2864
+ function readEnvValueFromFile(file, names) {
2865
+ const filePath = path.join(process.cwd(), file);
2866
+ if (!fs.existsSync(filePath)) {
2867
+ return null;
2868
+ }
2869
+ try {
2719
2870
  const content = fs.readFileSync(filePath, "utf-8");
2720
- const match = content.match(/^(?:SNIPARA_API_KEY|RLM_API_KEY)=(.+)$/m);
2721
- if (match?.[1]) {
2722
- return match[1].trim();
2871
+ for (const line of content.split(/\r?\n/)) {
2872
+ const match = line.trim().match(/^(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)$/);
2873
+ if (!match || !names.includes(match[1])) {
2874
+ continue;
2875
+ }
2876
+ const value = normalizeSlugCandidate(stripInlineEnvComment(match[2]));
2877
+ if (value) {
2878
+ return value;
2879
+ }
2723
2880
  }
2881
+ } catch {
2882
+ return null;
2724
2883
  }
2725
2884
  return null;
2726
2885
  }
2727
- function detectProjectSlug() {
2886
+ function stripInlineEnvComment(value) {
2887
+ const trimmed = value.trim();
2888
+ const quoted = trimmed.match(/^(['"])(.*)\1$/);
2889
+ if (quoted) {
2890
+ return quoted[2].trim();
2891
+ }
2892
+ return trimmed.replace(/\s+#.*$/, "").trim();
2893
+ }
2894
+ function detectRepoProjectSlug() {
2728
2895
  try {
2729
2896
  const remote = execSync("git remote get-url origin", {
2730
2897
  encoding: "utf-8",
@@ -2732,11 +2899,14 @@ function detectProjectSlug() {
2732
2899
  }).trim();
2733
2900
  const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
2734
2901
  if (match) {
2735
- return match[1].toLowerCase().replace(/[^a-z0-9-]/g, "-");
2902
+ return slugifyFallback(match[1]);
2736
2903
  }
2737
2904
  } catch {
2738
2905
  }
2739
- return path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
2906
+ return null;
2907
+ }
2908
+ function detectDirectoryProjectSlug() {
2909
+ return slugifyFallback(path.basename(process.cwd()));
2740
2910
  }
2741
2911
  function detectGitRepository() {
2742
2912
  try {
@@ -2854,9 +3024,8 @@ function getExistingConfig() {
2854
3024
  const config = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
2855
3025
  const sniparaServer = config.mcpServers?.snipara;
2856
3026
  const runtimeServer = config.mcpServers?.[SANDBOX_SERVER_NAME] || config.mcpServers?.[LEGACY_RUNTIME_SERVER_NAME];
2857
- const match = sniparaServer?.url?.match(/\/mcp\/([^/]+)$/);
2858
3027
  return {
2859
- slug: match?.[1],
3028
+ slug: extractSlugFromMcpEndpoint(sniparaServer?.url) || void 0,
2860
3029
  projectId: "",
2861
3030
  hasSniparaServer: Boolean(sniparaServer),
2862
3031
  hasRuntimeServer: Boolean(runtimeServer)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-snipara",
3
- "version": "1.3.24",
3
+ "version": "1.3.26",
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",