create-snipara 1.3.25 → 1.3.27

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 (2) hide show
  1. package/dist/index.js +250 -29
  2. package/package.json +1 -1
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.25",
16
+ version: "1.3.27",
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",
@@ -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
  });
@@ -286,6 +286,7 @@ async function runInit(options) {
286
286
  if (answers.profile !== "runtime-only" || answers.installRuntime) {
287
287
  await createMcpConfig(answers);
288
288
  }
289
+ await createProjectBinding(answers);
289
290
  await createAgentTemplates(answers);
290
291
  if (answers.installHooks && answers.aiClient === "claude-code") {
291
292
  await installClaudeCodeHooks(answers);
@@ -331,6 +332,7 @@ async function runRepair(options) {
331
332
  await installNodeCompanion(true);
332
333
  }
333
334
  await createMcpConfig(answers);
335
+ await createProjectBinding(answers);
334
336
  await createAgentTemplates(answers);
335
337
  await updateEnvFiles(answers);
336
338
  await createCompanionConfig(answers);
@@ -361,6 +363,7 @@ async function runUpgrade(options) {
361
363
  if (options.withMcp || options.withRuntime || detection.files.mcpConfig) {
362
364
  await createMcpConfig(upgradedAnswers);
363
365
  }
366
+ await createProjectBinding(upgradedAnswers);
364
367
  await createAgentTemplates(upgradedAnswers);
365
368
  await createCompanionConfig(upgradedAnswers);
366
369
  if (options.withHooks || answers.installHooks) {
@@ -385,7 +388,7 @@ async function runPrintConfig(options) {
385
388
  repository: githubRepo,
386
389
  answerPacks: githubAnswerPacks
387
390
  },
388
- inferredSetup: answers
391
+ inferredSetup: redactSetupAnswers(answers)
389
392
  },
390
393
  null,
391
394
  2
@@ -395,6 +398,7 @@ async function runPrintConfig(options) {
395
398
  }
396
399
  console.log(chalk.bold("\n\u{1F4E6} Current Setup\n"));
397
400
  console.log(` Project slug: ${answers.projectSlug}`);
401
+ console.log(` Project slug source: ${detection.projectSlugSource || "not detected"}`);
398
402
  console.log(` Project id: ${answers.projectId || "not-set"}`);
399
403
  console.log(` Profile: ${answers.profile}`);
400
404
  console.log(` AI client: ${answers.aiClient}`);
@@ -407,11 +411,21 @@ async function runPrintConfig(options) {
407
411
  console.log(` Snipara Orchestrator: ${answers.installOrchestrator ? "enabled" : "disabled"}`);
408
412
  console.log(` Hooks: ${answers.installHooks ? "enabled" : "disabled"}`);
409
413
  console.log(` GitHub repo: ${githubRepo ? githubRepo.fullName : "not detected"}`);
414
+ if (detection.detectedRepoSlug && detection.detectedRepoSlug !== answers.projectSlug) {
415
+ console.log(` Git repo slug: ${detection.detectedRepoSlug}`);
416
+ }
410
417
  console.log(` ${GITHUB_ANSWER_PACKS_LABEL}: ${githubAnswerPacks}`);
411
418
  console.log(` Existing .mcp.json: ${detection.files.mcpConfig ? "yes" : "no"}`);
412
419
  console.log(` Companion pack: ${detection.files.sniparaCompanionPack ? "yes" : "no"}`);
413
420
  console.log("");
414
421
  }
422
+ function redactSetupAnswers(answers) {
423
+ return {
424
+ ...answers,
425
+ apiKey: answers.apiKey.includes("YOUR_") ? answers.apiKey : "[redacted]",
426
+ llmApiKey: answers.llmApiKey ? "[redacted]" : void 0
427
+ };
428
+ }
415
429
  function printBanner() {
416
430
  console.log(
417
431
  chalk.cyan(`
@@ -431,10 +445,11 @@ function printBanner() {
431
445
  }
432
446
  async function gatherConfiguration(options) {
433
447
  console.log(chalk.bold("\n\u{1F4CB} Project Configuration\n"));
434
- let detectedSlug = detectProjectSlug();
435
- let detectedProjectId = "";
436
448
  const detectedClient = detectAiClient();
437
449
  const existingConfig = getExistingConfig();
450
+ const projectSlugResolution = resolveProjectSlug(existingConfig);
451
+ let detectedSlug = projectSlugResolution.slug;
452
+ let detectedProjectId = "";
438
453
  const detectedGitRepo = detectGitRepository();
439
454
  const showAdvancedPrompts = Boolean(options.advanced);
440
455
  if (options.github && !detectedGitRepo) {
@@ -446,7 +461,6 @@ async function gatherConfiguration(options) {
446
461
  }
447
462
  if (existingConfig) {
448
463
  console.log(chalk.dim(" Found existing .mcp.json configuration"));
449
- detectedSlug = existingConfig.slug || detectedSlug;
450
464
  detectedProjectId = existingConfig.projectId || "";
451
465
  }
452
466
  const initialProfile = resolveRequestedProfile(options);
@@ -1072,6 +1086,19 @@ async function createMcpConfig(answers) {
1072
1086
  await createClaudeDesktopConfig(answers);
1073
1087
  }
1074
1088
  }
1089
+ async function createProjectBinding(answers) {
1090
+ if (answers.profile === "runtime-only") {
1091
+ return;
1092
+ }
1093
+ const sniparaDir = path.join(process.cwd(), ".snipara");
1094
+ const projectBindingPath = path.join(sniparaDir, "project");
1095
+ const projectBinding = `${answers.projectSlug}
1096
+ `;
1097
+ const action = fs.existsSync(projectBindingPath) ? "Updated" : "Created";
1098
+ fs.mkdirSync(sniparaDir, { recursive: true });
1099
+ fs.writeFileSync(projectBindingPath, projectBinding);
1100
+ console.log(chalk.green(` \u2713 ${action} .snipara/project`));
1101
+ }
1075
1102
  async function createAgentTemplates(answers) {
1076
1103
  if (answers.profile === "runtime-only") {
1077
1104
  return;
@@ -1607,20 +1634,44 @@ async function createCompanionConfig(answers) {
1607
1634
  }
1608
1635
  const configDir = path.join(process.cwd(), ".snipara", "companion");
1609
1636
  const configPath = path.join(configDir, "config.json");
1637
+ const globalConfigPath = getGlobalCompanionConfigPath();
1610
1638
  fs.mkdirSync(configDir, { recursive: true });
1611
- const config = {
1639
+ fs.mkdirSync(path.dirname(globalConfigPath), { recursive: true });
1640
+ const existingGlobalConfig = readCompanionConfigFile(globalConfigPath);
1641
+ const existingWorkspaceConfig = readCompanionConfigFile(configPath);
1642
+ const globalConfig = {
1643
+ ...existingGlobalConfig,
1612
1644
  apiKey: answers.apiKey,
1613
- projectId: answers.projectId || answers.projectSlug,
1614
- apiUrl: API_URL,
1615
- sessionId: `sess_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
1645
+ apiUrl: API_URL
1646
+ };
1647
+ const workspaceConfig = {
1648
+ ...existingWorkspaceConfig,
1649
+ projectId: answers.projectId || (typeof existingWorkspaceConfig.projectId === "string" && existingWorkspaceConfig.projectId ? existingWorkspaceConfig.projectId : answers.projectSlug),
1650
+ sessionId: typeof existingWorkspaceConfig.sessionId === "string" && existingWorkspaceConfig.sessionId ? existingWorkspaceConfig.sessionId : `sess_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
1616
1651
  };
1617
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1652
+ fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 2) + "\n");
1653
+ fs.writeFileSync(configPath, JSON.stringify(workspaceConfig, null, 2) + "\n");
1618
1654
  ensureGitignoreEntry(
1619
1655
  ".snipara/companion/config.json",
1620
- "# Snipara local companion config (contains API key)"
1656
+ "# Snipara local companion workspace config"
1621
1657
  );
1658
+ console.log(chalk.green(" \u2713 Configured global companion auth"));
1622
1659
  console.log(chalk.green(" \u2713 Configured local companion workspace"));
1623
1660
  }
1661
+ function getGlobalCompanionConfigPath() {
1662
+ return path.join(homedir(), ".rlmsaas", "config.json");
1663
+ }
1664
+ function readCompanionConfigFile(filePath) {
1665
+ if (!fs.existsSync(filePath)) {
1666
+ return {};
1667
+ }
1668
+ try {
1669
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
1670
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
1671
+ } catch {
1672
+ return {};
1673
+ }
1674
+ }
1624
1675
  function getClientGuidance(client) {
1625
1676
  if (client === "claude-code") {
1626
1677
  return [
@@ -2230,6 +2281,16 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
2230
2281
  status: answers.projectSlug ? "pass" : "fail",
2231
2282
  detail: answers.projectSlug || "Project slug missing"
2232
2283
  });
2284
+ const configuredSlugSignals = collectConfiguredProjectSlugSignals(detection.existingConfig);
2285
+ const presentSlugSignals = configuredSlugSignals.filter((signal) => signal.slug);
2286
+ const uniqueConfiguredSlugs = new Set(presentSlugSignals.map((signal) => signal.slug));
2287
+ if (uniqueConfiguredSlugs.size > 1) {
2288
+ checks.push({
2289
+ name: "project_slug_consistency",
2290
+ status: "warn",
2291
+ detail: `Configured slug mismatch: ${presentSlugSignals.map((signal) => `${signal.source}=${signal.slug}`).join(", ")}`
2292
+ });
2293
+ }
2233
2294
  checks.push({
2234
2295
  name: "target_client",
2235
2296
  status: "pass",
@@ -2315,9 +2376,11 @@ function writeDoctorReport(report) {
2315
2376
  function printDoctorReport(detection, report) {
2316
2377
  const targetClient = report.checks.find((check) => check.name === "target_client")?.detail || detection.aiClient;
2317
2378
  console.log(chalk.bold("\n\u{1FA7A} Snipara Doctor\n"));
2318
- console.log(
2319
- ` Project slug: ${detection.existingConfig.slug || detection.projectSlug || "unknown"}`
2320
- );
2379
+ console.log(` Project slug: ${detection.projectSlug || "unknown"}`);
2380
+ console.log(` Project slug source: ${detection.projectSlugSource || "not detected"}`);
2381
+ if (detection.detectedRepoSlug && detection.detectedRepoSlug !== detection.projectSlug) {
2382
+ console.log(` Git repo slug: ${detection.detectedRepoSlug}`);
2383
+ }
2321
2384
  console.log(` AI client: ${targetClient}`);
2322
2385
  console.log(` In git repo: ${detection.inGitRepo ? "yes" : "no"}`);
2323
2386
  console.log(` Profile: ${report.profile}`);
@@ -2629,10 +2692,13 @@ function getClaudeConfigPaths() {
2629
2692
  }
2630
2693
  function detectEnvironment() {
2631
2694
  const existingConfig = getExistingConfig();
2695
+ const projectSlugResolution = resolveProjectSlug(existingConfig);
2632
2696
  const claudePaths = getClaudeConfigPaths();
2633
2697
  return {
2634
2698
  cwd: process.cwd(),
2635
- projectSlug: detectProjectSlug(),
2699
+ projectSlug: projectSlugResolution.slug,
2700
+ projectSlugSource: projectSlugResolution.source,
2701
+ detectedRepoSlug: detectRepoProjectSlug(),
2636
2702
  aiClient: detectAiClient(),
2637
2703
  inGitRepo: isGitRepo(),
2638
2704
  files: {
@@ -2644,6 +2710,7 @@ function detectEnvironment() {
2644
2710
  claudeMd: fs.existsSync(path.join(process.cwd(), "CLAUDE.md")),
2645
2711
  codexConfig: fs.existsSync(path.join(process.cwd(), ".codex", "config.toml")),
2646
2712
  cursorRule: fs.existsSync(path.join(process.cwd(), ".cursor", "rules", "snipara.mdc")),
2713
+ sniparaProject: fs.existsSync(path.join(process.cwd(), ".snipara", "project")),
2647
2714
  claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
2648
2715
  claudeSettings: fs.existsSync(claudePaths.codeSettingsPath),
2649
2716
  claudeDesktopConfig: fs.existsSync(claudePaths.desktopConfigPath),
@@ -2683,7 +2750,7 @@ function loadExistingAnswers(options) {
2683
2750
  const apiKey = providedApiKey || envApiKey || "YOUR_API_KEY_HERE";
2684
2751
  const apiKeyType = effectiveProfile === "runtime-only" ? "none" : providedApiKey || envApiKey ? "api" : "none";
2685
2752
  return buildAnswersFromProfile({
2686
- projectSlug: options.slug || detection.existingConfig.slug || detection.projectSlug || "my-project",
2753
+ projectSlug: options.slug || detection.projectSlug || "my-project",
2687
2754
  projectId: options.projectId || detection.existingConfig.projectId || "",
2688
2755
  apiKey,
2689
2756
  apiKeyType,
@@ -2714,17 +2781,157 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
2714
2781
  function readApiKeyFromEnvFiles() {
2715
2782
  const candidates = [".env.local", ".env"];
2716
2783
  for (const file of candidates) {
2717
- const filePath = path.join(process.cwd(), file);
2718
- if (!fs.existsSync(filePath)) continue;
2784
+ const value = readEnvValueFromFile(file, ["SNIPARA_API_KEY", "RLM_API_KEY"]);
2785
+ if (value) {
2786
+ return value;
2787
+ }
2788
+ }
2789
+ return null;
2790
+ }
2791
+ function resolveProjectSlug(existingConfig = getExistingConfig()) {
2792
+ const configuredSignals = collectConfiguredProjectSlugSignals(existingConfig);
2793
+ const signals = [
2794
+ ...configuredSignals,
2795
+ { source: "git remote", slug: detectRepoProjectSlug() },
2796
+ { source: "working directory", slug: detectDirectoryProjectSlug() }
2797
+ ];
2798
+ const selected = signals.find((signal) => signal.slug);
2799
+ return {
2800
+ slug: selected?.slug || null,
2801
+ source: selected?.source || null,
2802
+ signals
2803
+ };
2804
+ }
2805
+ function collectConfiguredProjectSlugSignals(existingConfig = getExistingConfig()) {
2806
+ return [
2807
+ { source: ".mcp.json", slug: normalizeSlugCandidate(existingConfig?.slug) },
2808
+ { source: ".snipara/project", slug: readProjectSlugFromProjectBinding() },
2809
+ {
2810
+ source: ".codex/config.toml",
2811
+ slug: readProjectSlugFromTextFile(path.join(process.cwd(), ".codex", "config.toml"))
2812
+ },
2813
+ {
2814
+ source: ".snipara/templates/codex-config.toml",
2815
+ slug: readProjectSlugFromTextFile(
2816
+ path.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
2817
+ )
2818
+ },
2819
+ { source: ".env.local", slug: readProjectSlugFromEnvFile(".env.local") },
2820
+ { source: ".env", slug: readProjectSlugFromEnvFile(".env") }
2821
+ ];
2822
+ }
2823
+ function normalizeSlugCandidate(value) {
2824
+ if (!value) {
2825
+ return null;
2826
+ }
2827
+ const trimmed = value.trim().replace(/^["']|["']$/g, "");
2828
+ if (!trimmed || /^your_/i.test(trimmed) || /^YOUR_/.test(trimmed) || trimmed.includes("<") || trimmed.includes(">")) {
2829
+ return null;
2830
+ }
2831
+ return trimmed;
2832
+ }
2833
+ function slugifyFallback(value) {
2834
+ const normalized = normalizeSlugCandidate(value);
2835
+ if (!normalized) {
2836
+ return null;
2837
+ }
2838
+ const slug = normalized.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2839
+ return slug || null;
2840
+ }
2841
+ function extractSlugFromMcpEndpoint(value) {
2842
+ const endpoint = normalizeSlugCandidate(value);
2843
+ if (!endpoint) {
2844
+ return null;
2845
+ }
2846
+ const match = endpoint.match(/\/mcp\/([^/?#\s"']+)/);
2847
+ return normalizeSlugCandidate(match?.[1] ? decodeURIComponent(match[1]) : null);
2848
+ }
2849
+ function extractSlugFromText(content) {
2850
+ const match = content.match(/\/mcp\/([^/?#\s"'`]+)/);
2851
+ return normalizeSlugCandidate(match?.[1] ? decodeURIComponent(match[1]) : null);
2852
+ }
2853
+ function readProjectSlugFromTextFile(filePath) {
2854
+ if (!fs.existsSync(filePath)) {
2855
+ return null;
2856
+ }
2857
+ try {
2858
+ return extractSlugFromText(fs.readFileSync(filePath, "utf-8"));
2859
+ } catch {
2860
+ return null;
2861
+ }
2862
+ }
2863
+ function readProjectSlugFromProjectBinding() {
2864
+ const filePath = path.join(process.cwd(), ".snipara", "project");
2865
+ if (!fs.existsSync(filePath)) {
2866
+ return null;
2867
+ }
2868
+ try {
2869
+ const content = fs.readFileSync(filePath, "utf-8").trim();
2870
+ if (!content) {
2871
+ return null;
2872
+ }
2873
+ if (content.startsWith("{")) {
2874
+ const parsed = JSON.parse(content);
2875
+ return normalizeSlugCandidate(parsed.slug || parsed.projectSlug || parsed.project_slug);
2876
+ }
2877
+ for (const line of content.split(/\r?\n/)) {
2878
+ const trimmed = line.trim();
2879
+ if (!trimmed || trimmed.startsWith("#")) {
2880
+ continue;
2881
+ }
2882
+ const keyed = trimmed.match(/^(?:slug|project|projectSlug|project_slug)\s*[:=]\s*(.+)$/i);
2883
+ if (keyed?.[1]) {
2884
+ return normalizeSlugCandidate(stripInlineEnvComment(keyed[1]));
2885
+ }
2886
+ }
2887
+ const firstValue = content.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !line.startsWith("#"));
2888
+ return normalizeSlugCandidate(firstValue);
2889
+ } catch {
2890
+ return null;
2891
+ }
2892
+ }
2893
+ function readProjectSlugFromEnvFile(file) {
2894
+ const explicitSlug = readEnvValueFromFile(file, ["SNIPARA_PROJECT_SLUG", "RLM_PROJECT_SLUG"]);
2895
+ if (explicitSlug) {
2896
+ return explicitSlug;
2897
+ }
2898
+ const legacySlug = readEnvValueFromFile(file, ["SNIPARA_PROJECT_ID", "RLM_PROJECT_ID"]);
2899
+ if (legacySlug && /^[a-z0-9][a-z0-9-]*$/i.test(legacySlug)) {
2900
+ return legacySlug;
2901
+ }
2902
+ return null;
2903
+ }
2904
+ function readEnvValueFromFile(file, names) {
2905
+ const filePath = path.join(process.cwd(), file);
2906
+ if (!fs.existsSync(filePath)) {
2907
+ return null;
2908
+ }
2909
+ try {
2719
2910
  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();
2911
+ for (const line of content.split(/\r?\n/)) {
2912
+ const match = line.trim().match(/^(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)$/);
2913
+ if (!match || !names.includes(match[1])) {
2914
+ continue;
2915
+ }
2916
+ const value = normalizeSlugCandidate(stripInlineEnvComment(match[2]));
2917
+ if (value) {
2918
+ return value;
2919
+ }
2723
2920
  }
2921
+ } catch {
2922
+ return null;
2724
2923
  }
2725
2924
  return null;
2726
2925
  }
2727
- function detectProjectSlug() {
2926
+ function stripInlineEnvComment(value) {
2927
+ const trimmed = value.trim();
2928
+ const quoted = trimmed.match(/^(['"])(.*)\1$/);
2929
+ if (quoted) {
2930
+ return quoted[2].trim();
2931
+ }
2932
+ return trimmed.replace(/\s+#.*$/, "").trim();
2933
+ }
2934
+ function detectRepoProjectSlug() {
2728
2935
  try {
2729
2936
  const remote = execSync("git remote get-url origin", {
2730
2937
  encoding: "utf-8",
@@ -2732,11 +2939,14 @@ function detectProjectSlug() {
2732
2939
  }).trim();
2733
2940
  const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
2734
2941
  if (match) {
2735
- return match[1].toLowerCase().replace(/[^a-z0-9-]/g, "-");
2942
+ return slugifyFallback(match[1]);
2736
2943
  }
2737
2944
  } catch {
2738
2945
  }
2739
- return path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
2946
+ return null;
2947
+ }
2948
+ function detectDirectoryProjectSlug() {
2949
+ return slugifyFallback(path.basename(process.cwd()));
2740
2950
  }
2741
2951
  function detectGitRepository() {
2742
2952
  try {
@@ -2854,22 +3064,33 @@ function getExistingConfig() {
2854
3064
  const config = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
2855
3065
  const sniparaServer = config.mcpServers?.snipara;
2856
3066
  const runtimeServer = config.mcpServers?.[SANDBOX_SERVER_NAME] || config.mcpServers?.[LEGACY_RUNTIME_SERVER_NAME];
2857
- const match = sniparaServer?.url?.match(/\/mcp\/([^/]+)$/);
2858
3067
  return {
2859
- slug: match?.[1],
2860
- projectId: "",
3068
+ slug: extractSlugFromMcpEndpoint(sniparaServer?.url) || void 0,
3069
+ projectId: readExistingCompanionProjectId(),
2861
3070
  hasSniparaServer: Boolean(sniparaServer),
2862
3071
  hasRuntimeServer: Boolean(runtimeServer)
2863
3072
  };
2864
3073
  } catch {
2865
3074
  return {
2866
3075
  slug: void 0,
2867
- projectId: "",
3076
+ projectId: readExistingCompanionProjectId(),
2868
3077
  hasSniparaServer: false,
2869
3078
  hasRuntimeServer: false
2870
3079
  };
2871
3080
  }
2872
3081
  }
3082
+ function readExistingCompanionProjectId() {
3083
+ const configPath = path.join(process.cwd(), ".snipara", "companion", "config.json");
3084
+ if (!fs.existsSync(configPath)) {
3085
+ return void 0;
3086
+ }
3087
+ try {
3088
+ const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
3089
+ return typeof parsed.projectId === "string" && parsed.projectId ? parsed.projectId : void 0;
3090
+ } catch {
3091
+ return void 0;
3092
+ }
3093
+ }
2873
3094
  main().catch((error) => {
2874
3095
  console.error(chalk.red("Error:"), error.message);
2875
3096
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-snipara",
3
- "version": "1.3.25",
3
+ "version": "1.3.27",
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",