create-snipara 1.3.39 → 1.3.41

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 +18 -4
  2. package/dist/index.js +407 -20
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -16,7 +16,7 @@ so an LLM client can use Snipara context and reviewed memory.
16
16
 
17
17
  Use it for:
18
18
 
19
- - Hosted MCP onboarding for Claude Code, Cursor, Windsurf, Codex, Gemini, VS Code/Copilot, Continue, ChatGPT Desktop, and similar clients
19
+ - Hosted MCP onboarding for Claude Code, Cursor, Windsurf, Codex, Gemini, Mistral, VS Code/Copilot, Continue, ChatGPT Desktop, and similar clients
20
20
  - free account signup with reviewed Context + Memory
21
21
  - local project wiring, `doctor`, `repair`, and `upgrade`
22
22
  - GitHub repository sync and PR Answer Packs approval when the current folder is a GitHub repo
@@ -33,7 +33,7 @@ flowchart LR
33
33
  CLI --> Auth["Snipara auth and project key"]
34
34
  CLI --> MCP["Hosted MCP config"]
35
35
  CLI --> Templates["AGENTS.md, CLAUDE.md, Cursor, Codex, MCP references"]
36
- MCP --> Agents["Claude Code, Cursor, Codex, Gemini, VS Code, ChatGPT"]
36
+ MCP --> Agents["Claude Code, Cursor, Codex, Gemini, Mistral, VS Code, ChatGPT"]
37
37
  Templates --> Agents
38
38
  Agents --> Context["Snipara context and memory"]
39
39
  ```
@@ -75,7 +75,7 @@ Free onboarding is now **Context + Memory**, not Context-only:
75
75
 
76
76
  - Context Free: three projects and 1,000 context queries/month
77
77
  - Memory Free: reviewed hosted memories for continuity across sessions
78
- - Hosted MCP: direct connection for Claude Code, Cursor, Windsurf, Codex, Gemini, VS Code/Copilot, Continue, ChatGPT Desktop, and other MCP clients
78
+ - Hosted MCP: direct connection for Claude Code, Cursor, Windsurf, Codex, Gemini, Mistral, VS Code/Copilot, Continue, ChatGPT Desktop, and other MCP clients
79
79
 
80
80
  The CLI opens a browser device-flow login when the user does not already have a key, auto-provisions the free Context + Memory account when needed, then writes the returned API key into the local MCP config.
81
81
 
@@ -108,6 +108,9 @@ That distinction matters:
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
109
  - creates `AGENTS.md` and `CLAUDE.md` when missing, or appends a marked Snipara section when those files already exist
110
110
  - creates `.cursor/rules/snipara.mdc` and `.codex/config.toml` when those files do not already exist
111
+ - validates Codex config against Hosted MCP plus `bearer_token_env_var = "SNIPARA_API_KEY"` and tells users to restart Codex after MCP config changes
112
+ - runs a real hosted MCP `tools/list` check and warns if the server still advertises legacy `rlm_*` duplicate tools
113
+ - generates Mistral Le Chat, Mistral Vibe, and LangChain `ChatMistralAI.bindTools` references without embedding secrets
111
114
  - writes client-specific companion packs such as `.snipara/companion/CODEX.md` or `.snipara/companion/GEMINI.md`
112
115
  - updates `.env`, `.env.local`, and `.env.example`
113
116
  - generates `.snipara/companion/README.md`
@@ -161,8 +164,16 @@ The generated agent templates cover:
161
164
  - Cursor `.cursor/rules/snipara.mdc`
162
165
  - Codex `.codex/config.toml` snippet
163
166
  - ChatGPT/OpenAI HTTP MCP snippet
167
+ - Mistral Le Chat connector, Vibe config, and LangChain `ChatMistralAI.bindTools` snippets
164
168
  - Gemini, VS Code, Continue, and custom HTTP MCP reference snippets
165
169
 
170
+ For Codex, `SNIPARA_API_KEY` is the only generated bearer token environment
171
+ variable. If Codex lazily exposes only a minimal Snipara MCP surface after
172
+ restart, force exact discovery for `snipara_recall`, `snipara_context_query`,
173
+ and `snipara_settings`. Use `snipara-companion recall`, `snipara-companion
174
+ query`, and `snipara-companion task-commit` as the reliable local fallback while
175
+ the Codex session reloads or rediscovers tools.
176
+
166
177
  Existing root `AGENTS.md` and `CLAUDE.md` files are not replaced. The installer
167
178
  adds or refreshes a marked Snipara section so project-specific instructions stay
168
179
  intact.
@@ -344,8 +355,11 @@ companion add workflow ergonomics:
344
355
 
345
356
  Claude Code, Cursor, and Windsurf can receive generated automation files when
346
357
  that client has a useful local hook surface. Codex, Gemini, VS Code, Continue,
347
- ChatGPT, and custom clients are treated as MCP-first: they get the hosted MCP
358
+ Mistral, ChatGPT, and custom clients are treated as MCP-first: they get the hosted MCP
348
359
  configuration/reference and agent instructions, not invented local hooks.
360
+ For Mistral, request hooks from LangChain are documented as `ChatMistralAI`
361
+ request lifecycle hooks (`beforeRequestHooks`, `requestErrorHooks`, and
362
+ `responseHooks`), not as local agent lifecycle hooks.
349
363
 
350
364
  OpenClaw is the specialized exception: use `snipara-openclaw-install` for a
351
365
  single OpenClaw-focused setup command, or use `create-snipara` plus
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.39",
16
+ version: "1.3.41",
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",
@@ -47,6 +47,7 @@ var package_default = {
47
47
  "cursor",
48
48
  "windsurf",
49
49
  "gemini",
50
+ "mistral",
50
51
  "vscode",
51
52
  "continue",
52
53
  "claude-desktop"
@@ -128,6 +129,11 @@ var AI_CLIENT_CHOICES = [
128
129
  value: "gemini",
129
130
  description: "Hosted MCP reference plus agent instructions"
130
131
  },
132
+ {
133
+ title: "Mistral Le Chat / Vibe",
134
+ value: "mistral",
135
+ description: "Le Chat custom MCP connector plus Mistral Vibe config"
136
+ },
131
137
  {
132
138
  title: "VS Code / Copilot",
133
139
  value: "vscode",
@@ -157,6 +163,7 @@ function getCompanionInitClientFlag(aiClient) {
157
163
  windsurf: "windsurf",
158
164
  codex: "codex",
159
165
  gemini: "gemini",
166
+ mistral: "mistral",
160
167
  chatgpt: "chatgpt",
161
168
  vscode: "vscode",
162
169
  continue: "continue",
@@ -176,7 +183,7 @@ function buildProgram() {
176
183
  program.name("create-snipara").description("Scaffold hosted Snipara MCP config plus companion workflow helpers").version(VERSION);
177
184
  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(
178
185
  "--client <client>",
179
- "AI client: claude-code, cursor, windsurf, codex, gemini, chatgpt, vscode, continue, claude-desktop, other"
186
+ "AI client: claude-code, cursor, windsurf, codex, gemini, mistral, chatgpt, vscode, continue, claude-desktop, other"
180
187
  ).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
181
188
  "--profile <profile>",
182
189
  "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
@@ -213,7 +220,7 @@ function buildProgram() {
213
220
  function addSharedRootOptions(program) {
214
221
  program.option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
215
222
  "--client <client>",
216
- "AI client: claude-code, cursor, windsurf, codex, gemini, chatgpt, vscode, continue, claude-desktop, other"
223
+ "AI client: claude-code, cursor, windsurf, codex, gemini, mistral, chatgpt, vscode, continue, claude-desktop, other"
217
224
  ).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
218
225
  "--profile <profile>",
219
226
  "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
@@ -607,7 +614,10 @@ async function gatherConfiguration(options) {
607
614
  let signupResult = null;
608
615
  if (!apiKey && response.profile !== "runtime-only") {
609
616
  if (response.apiKeyType === "signup") {
610
- signupResult = await handleSignup();
617
+ signupResult = await handleSignup({
618
+ projectHint: response.projectSlug,
619
+ repoHint: detectedGitRepo?.fullName
620
+ });
611
621
  apiKey = signupResult.apiKey;
612
622
  apiKeyType = "api";
613
623
  } else if (response.apiKeyType === "api") {
@@ -719,6 +729,12 @@ function normalizeClientOption(value) {
719
729
  windsurf: "windsurf",
720
730
  codex: "codex",
721
731
  gemini: "gemini",
732
+ mistral: "mistral",
733
+ "le-chat": "mistral",
734
+ lechat: "mistral",
735
+ vibe: "mistral",
736
+ "mistral-vibe": "mistral",
737
+ mistral_vibe: "mistral",
722
738
  chatgpt: "chatgpt",
723
739
  vscode: "vscode",
724
740
  continue: "continue",
@@ -727,7 +743,9 @@ function normalizeClientOption(value) {
727
743
  };
728
744
  const client = aliases[normalized];
729
745
  if (!client) {
730
- throw new Error(`Unknown client '${value}'. Use claude, claude-code, cursor, codex, or other.`);
746
+ throw new Error(
747
+ `Unknown client '${value}'. Use claude, claude-code, cursor, codex, mistral, or other.`
748
+ );
731
749
  }
732
750
  return client;
733
751
  }
@@ -800,10 +818,10 @@ function buildAnswersFromProfile(input) {
800
818
  githubPath: input.githubPath
801
819
  };
802
820
  }
803
- async function handleSignup() {
821
+ async function handleSignup(hints = {}) {
804
822
  console.log(chalk.bold("\n\u{1F510} Browser Account Authorization\n"));
805
823
  try {
806
- return await runDeviceAuthorization();
824
+ return await runDeviceAuthorization(hints);
807
825
  } catch (error) {
808
826
  if (error instanceof DeviceAuthorizationAborted) {
809
827
  throw error;
@@ -831,7 +849,27 @@ async function handleManualSignup() {
831
849
  });
832
850
  return { apiKey: response.apiKey };
833
851
  }
834
- async function runDeviceAuthorization() {
852
+ function normalizeDeviceFlowHint(value) {
853
+ const normalized = value?.trim();
854
+ return normalized && !normalized.includes("<") && !normalized.includes(">") ? normalized : null;
855
+ }
856
+ function appendDeviceFlowHints(url, hints) {
857
+ try {
858
+ const parsed = new URL(url);
859
+ const projectHint = normalizeDeviceFlowHint(hints.projectHint);
860
+ const repoHint = normalizeDeviceFlowHint(hints.repoHint);
861
+ if (projectHint) {
862
+ parsed.searchParams.set("project_hint", projectHint);
863
+ }
864
+ if (repoHint) {
865
+ parsed.searchParams.set("repo_hint", repoHint);
866
+ }
867
+ return parsed.toString();
868
+ } catch {
869
+ return url;
870
+ }
871
+ }
872
+ async function runDeviceAuthorization(hints = {}) {
835
873
  const codeResponse = await postJson(`${BASE_URL}/api/oauth/device/code`, {
836
874
  client_id: DEVICE_CLIENT_ID,
837
875
  scope: "mcp:read mcp:write",
@@ -840,11 +878,10 @@ async function runDeviceAuthorization() {
840
878
  if (codeResponse.error) {
841
879
  throw new Error(codeResponse.error_description || codeResponse.error);
842
880
  }
843
- console.log(
844
- chalk.cyan(` Opening ${codeResponse.verification_uri_complete} in your browser...`)
845
- );
881
+ const verificationUrl = appendDeviceFlowHints(codeResponse.verification_uri_complete, hints);
882
+ console.log(chalk.cyan(` Opening ${verificationUrl} in your browser...`));
846
883
  console.log(chalk.dim(` Device code: ${codeResponse.user_code}`));
847
- openBrowser(codeResponse.verification_uri_complete);
884
+ openBrowser(verificationUrl);
848
885
  const spinner = ora("Waiting for Snipara authorization in the browser...").start();
849
886
  const startedAt = Date.now();
850
887
  let intervalMs = Math.max(1, codeResponse.interval || 5) * 1e3;
@@ -1212,6 +1249,18 @@ async function createAgentTemplates(answers) {
1212
1249
  path: path.join(templatesDir, "gemini-mcp-reference.json"),
1213
1250
  content: buildGenericMcpReferenceTemplate(answers, "gemini")
1214
1251
  },
1252
+ {
1253
+ path: path.join(templatesDir, "mistral-vibe-config.toml"),
1254
+ content: buildMistralVibeConfigTemplate(answers)
1255
+ },
1256
+ {
1257
+ path: path.join(templatesDir, "mistral-le-chat-mcp.json"),
1258
+ content: buildMistralLeChatTemplate(answers)
1259
+ },
1260
+ {
1261
+ path: path.join(templatesDir, "mistral-langchain-tools.ts"),
1262
+ content: buildMistralLangChainToolsTemplate(answers)
1263
+ },
1215
1264
  {
1216
1265
  path: path.join(templatesDir, "vscode-mcp-reference.json"),
1217
1266
  content: buildGenericMcpReferenceTemplate(answers, "vscode")
@@ -1253,6 +1302,15 @@ async function createAgentTemplates(answers) {
1253
1302
  buildCodexConfigTemplate(answers),
1254
1303
  ".codex/config.toml"
1255
1304
  );
1305
+ if (answers.aiClient === "mistral") {
1306
+ const vibeDir = path.join(process.cwd(), ".vibe");
1307
+ fs.mkdirSync(vibeDir, { recursive: true });
1308
+ writeIfMissing(
1309
+ path.join(vibeDir, "config.toml"),
1310
+ buildMistralVibeConfigTemplate(answers),
1311
+ ".vibe/config.toml"
1312
+ );
1313
+ }
1256
1314
  ensureGitignoreEntry(".snipara/companion/doctor.json", "# Snipara generated validation output");
1257
1315
  }
1258
1316
  function writeIfMissing(filePath, content, label) {
@@ -1281,10 +1339,17 @@ This project uses Snipara Hosted MCP for project context and reviewed memory.
1281
1339
  call such as \`snipara_settings\`, \`tools/list\`, or a lightweight
1282
1340
  \`snipara_context_query\`. Do not treat empty MCP resources/templates as an
1283
1341
  outage because Snipara may be tool-only.
1342
+ - In Codex, if only a minimal Snipara tool subset appears, force precise tool
1343
+ discovery for \`snipara_recall\`, \`snipara_context_query\`, and
1344
+ \`snipara_settings\` before concluding the MCP server is incomplete.
1284
1345
  - If a session appears to expose only a subset of expected Snipara tools, call
1285
1346
  \`snipara_help(list_all=true)\` and compare exact tool names before concluding
1286
1347
  that a tool is unavailable. Do not infer absence from one broad discovery
1287
1348
  pass or a ranked tool-search result.
1349
+ - If Codex still exposes only a partial surface after exact discovery, use
1350
+ \`snipara-companion recall\`, \`snipara-companion query\`, and
1351
+ \`snipara-companion task-commit\` as the reliable local fallback, then restart
1352
+ or reload Codex after MCP config changes.
1288
1353
 
1289
1354
  Agent task lifecycle:
1290
1355
 
@@ -1408,6 +1473,138 @@ function buildChatGptMcpTemplate(answers) {
1408
1473
  )}
1409
1474
  `;
1410
1475
  }
1476
+ function buildMistralVibeConfigTemplate(answers) {
1477
+ return `[[mcp_servers]]
1478
+ name = "snipara"
1479
+ transport = "streamable-http"
1480
+ url = "${buildHostedMcpEndpoint(answers)}"
1481
+ api_key_env = "SNIPARA_API_KEY"
1482
+ api_key_header = "X-API-Key"
1483
+ api_key_format = "{token}"
1484
+ `;
1485
+ }
1486
+ function buildMistralLeChatTemplate(answers) {
1487
+ return `${JSON.stringify(
1488
+ {
1489
+ connector_name: "snipara",
1490
+ server_url: buildHostedMcpEndpoint(answers),
1491
+ auth: {
1492
+ method: "HTTP Bearer Token or Basic/API key prompt",
1493
+ secret: "Use SNIPARA_API_KEY from your environment or secret store"
1494
+ },
1495
+ notes: [
1496
+ "In Le Chat, add a Custom MCP Connector with this Server URL.",
1497
+ "Le Chat auto-detects the authentication flow; do not commit the API key.",
1498
+ "Enable the connector tools in each conversation or pre-authorize read functions deliberately."
1499
+ ]
1500
+ },
1501
+ null,
1502
+ 2
1503
+ )}
1504
+ `;
1505
+ }
1506
+ function buildMistralLangChainToolsTemplate(answers) {
1507
+ return `import { ChatMistralAI } from "@langchain/mistralai";
1508
+ import { tool } from "@langchain/core/tools";
1509
+ import { z } from "zod";
1510
+
1511
+ const SNIPARA_MCP_URL = process.env.SNIPARA_MCP_URL || "${buildHostedMcpEndpoint(answers)}";
1512
+ const SNIPARA_API_KEY = process.env.SNIPARA_API_KEY;
1513
+
1514
+ if (!SNIPARA_API_KEY) {
1515
+ throw new Error("Missing SNIPARA_API_KEY");
1516
+ }
1517
+
1518
+ async function callSniparaTool(toolName: string, args: Record<string, unknown>): Promise<string> {
1519
+ const response = await fetch(SNIPARA_MCP_URL, {
1520
+ method: "POST",
1521
+ headers: {
1522
+ "Content-Type": "application/json",
1523
+ "X-API-Key": SNIPARA_API_KEY,
1524
+ },
1525
+ body: JSON.stringify({
1526
+ jsonrpc: "2.0",
1527
+ id: Date.now(),
1528
+ method: "tools/call",
1529
+ params: {
1530
+ name: toolName,
1531
+ arguments: args,
1532
+ },
1533
+ }),
1534
+ });
1535
+
1536
+ const payload = (await response.json()) as { error?: unknown; result?: unknown };
1537
+ if (!response.ok || payload.error) {
1538
+ throw new Error("Snipara MCP call failed: " + JSON.stringify(payload.error || payload));
1539
+ }
1540
+
1541
+ return JSON.stringify(payload.result || {});
1542
+ }
1543
+
1544
+ export const sniparaRecallTool = tool(
1545
+ async ({ query, type }) =>
1546
+ callSniparaTool("snipara_recall", {
1547
+ query,
1548
+ ...(type ? { type } : {}),
1549
+ }),
1550
+ {
1551
+ name: "snipara_recall",
1552
+ description: "Recall durable Snipara decisions, preferences, and validated learnings.",
1553
+ schema: z.object({
1554
+ query: z.string().describe("Focused memory query."),
1555
+ type: z.enum(["decision", "learning", "context", "preference"]).optional(),
1556
+ }),
1557
+ }
1558
+ );
1559
+
1560
+ export const sniparaContextQueryTool = tool(
1561
+ async ({ query, maxTokens }) =>
1562
+ callSniparaTool("snipara_context_query", {
1563
+ query,
1564
+ max_tokens: maxTokens,
1565
+ return_references: true,
1566
+ }),
1567
+ {
1568
+ name: "snipara_context_query",
1569
+ description: "Retrieve optimized source context, docs, runbooks, and source-truth references.",
1570
+ schema: z.object({
1571
+ query: z.string().describe("Question or task needing project context."),
1572
+ maxTokens: z.number().int().positive().max(12000).default(4000),
1573
+ }),
1574
+ }
1575
+ );
1576
+
1577
+ export const sniparaSettingsTool = tool(() => callSniparaTool("snipara_settings", {}), {
1578
+ name: "snipara_settings",
1579
+ description: "Validate the hosted Snipara MCP server and project settings.",
1580
+ schema: z.object({}),
1581
+ });
1582
+
1583
+ const beforeRequestHook = (req: Request): Request => {
1584
+ const headers = new Headers(req.headers);
1585
+ headers.set("X-Snipara-Client", "mistral-langchain");
1586
+ return new Request(req, { headers });
1587
+ };
1588
+
1589
+ const requestErrorHook = async (err: unknown, req: Request): Promise<void> => {
1590
+ console.warn("Mistral request failed", { url: req.url, err });
1591
+ };
1592
+
1593
+ const responseHook = async (res: Response, req: Request): Promise<void> => {
1594
+ void req;
1595
+ void res;
1596
+ };
1597
+
1598
+ export const mistralWithSniparaTools = new ChatMistralAI({
1599
+ model: process.env.MISTRAL_MODEL || "mistral-large-latest",
1600
+ temperature: 0,
1601
+ maxRetries: 2,
1602
+ beforeRequestHooks: [beforeRequestHook],
1603
+ requestErrorHooks: [requestErrorHook],
1604
+ responseHooks: [responseHook],
1605
+ }).bindTools([sniparaRecallTool, sniparaContextQueryTool, sniparaSettingsTool]);
1606
+ `;
1607
+ }
1411
1608
  function buildGenericMcpReferenceTemplate(answers, client) {
1412
1609
  return `${JSON.stringify(
1413
1610
  {
@@ -1829,7 +2026,9 @@ function getClientGuidance(client) {
1829
2026
  if (client === "codex") {
1830
2027
  return [
1831
2028
  "Codex should use the hosted MCP surface plus AGENTS.md as the canonical project instruction file.",
1832
- "The generated .snipara/templates/codex-config.toml is a merge-ready snippet for ~/.codex/config.toml if your Codex app does not read project .codex/config.toml."
2029
+ "The generated .snipara/templates/codex-config.toml is a merge-ready snippet for ~/.codex/config.toml if your Codex app does not read project .codex/config.toml.",
2030
+ "Use SNIPARA_API_KEY as the only Codex bearer_token_env_var; restart Codex after changing MCP config.",
2031
+ "If Codex lazily exposes only a minimal tool set, force discovery for snipara_recall, snipara_context_query, and snipara_settings; use snipara-companion as the fallback until Codex reloads."
1833
2032
  ];
1834
2033
  }
1835
2034
  if (client === "gemini") {
@@ -1838,6 +2037,14 @@ function getClientGuidance(client) {
1838
2037
  "Use .snipara/templates/gemini-mcp-reference.json as a field reference for the client's MCP settings."
1839
2038
  ];
1840
2039
  }
2040
+ if (client === "mistral") {
2041
+ return [
2042
+ "Mistral Le Chat should use a Custom MCP Connector pointed at the hosted Snipara MCP endpoint.",
2043
+ "Mistral Vibe should use .vibe/config.toml or ~/.vibe/config.toml with the generated streamable-http server block and AGENTS.md at the workspace root.",
2044
+ "LangChain JavaScript can bind Snipara tools to ChatMistralAI with .snipara/templates/mistral-langchain-tools.ts.",
2045
+ "Native lifecycle hooks are not claimed for Mistral; use snipara-companion for explicit recall, query, workflow, and task-commit flows."
2046
+ ];
2047
+ }
1841
2048
  if (client === "vscode") {
1842
2049
  return [
1843
2050
  "VS Code and Copilot-style clients should use Hosted MCP plus AGENTS.md for project workflow rules.",
@@ -2004,6 +2211,7 @@ function getClientDisplayName(client) {
2004
2211
  windsurf: "Windsurf",
2005
2212
  codex: "OpenAI Codex",
2006
2213
  gemini: "Gemini",
2214
+ mistral: "Mistral Le Chat / Vibe",
2007
2215
  chatgpt: "ChatGPT / OpenAI MCP",
2008
2216
  vscode: "VS Code",
2009
2217
  continue: "Continue",
@@ -2019,6 +2227,7 @@ function getClientPackFilename(client) {
2019
2227
  windsurf: "WINDSURF.md",
2020
2228
  codex: "CODEX.md",
2021
2229
  gemini: "GEMINI.md",
2230
+ mistral: "MISTRAL.md",
2022
2231
  chatgpt: "CHATGPT.md",
2023
2232
  vscode: "VSCODE.md",
2024
2233
  continue: "CONTINUE.md",
@@ -2043,6 +2252,15 @@ function getClientConfigArtifacts(client) {
2043
2252
  if (client === "gemini") {
2044
2253
  return ["AGENTS.md", ".snipara/templates/gemini-mcp-reference.json"];
2045
2254
  }
2255
+ if (client === "mistral") {
2256
+ return [
2257
+ "AGENTS.md",
2258
+ ".vibe/config.toml",
2259
+ ".snipara/templates/mistral-vibe-config.toml",
2260
+ ".snipara/templates/mistral-le-chat-mcp.json",
2261
+ ".snipara/templates/mistral-langchain-tools.ts"
2262
+ ];
2263
+ }
2046
2264
  if (client === "vscode") {
2047
2265
  return ["AGENTS.md", ".snipara/templates/vscode-mcp-reference.json"];
2048
2266
  }
@@ -2480,6 +2698,12 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
2480
2698
  status: detection.files.sniparaTemplates && detection.files.cursorRule && detection.files.codexConfig ? "pass" : "warn",
2481
2699
  detail: detection.files.sniparaTemplates && detection.files.cursorRule && detection.files.codexConfig ? ".snipara/templates, .cursor/rules/snipara.mdc, and .codex/config.toml present" : "Agent templates, Cursor rule, or Codex config missing"
2482
2700
  });
2701
+ if (targetClient === "codex") {
2702
+ checks.push(getCodexConfigCheck());
2703
+ }
2704
+ if (targetClient === "mistral") {
2705
+ checks.push(getMistralConfigCheck());
2706
+ }
2483
2707
  checks.push({
2484
2708
  name: "env_files",
2485
2709
  status: detection.files.env || detection.files.envLocal || detection.files.envExample ? "pass" : "warn",
@@ -2527,6 +2751,117 @@ function writeDoctorReport(report) {
2527
2751
  fs.writeFileSync(path.join(reportDir, "doctor.json"), JSON.stringify(report, null, 2) + "\n");
2528
2752
  console.log(chalk.green(" \u2713 Wrote .snipara/companion/doctor.json"));
2529
2753
  }
2754
+ function getCodexConfigCheck() {
2755
+ const candidates = [
2756
+ {
2757
+ label: ".codex/config.toml",
2758
+ filePath: path.join(process.cwd(), ".codex", "config.toml")
2759
+ },
2760
+ {
2761
+ label: ".snipara/templates/codex-config.toml",
2762
+ filePath: path.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
2763
+ },
2764
+ {
2765
+ label: "~/.codex/config.toml",
2766
+ filePath: path.join(homedir(), ".codex", "config.toml")
2767
+ }
2768
+ ];
2769
+ const present = candidates.filter((candidate) => fs.existsSync(candidate.filePath)).map((candidate) => ({
2770
+ label: candidate.label,
2771
+ content: fs.readFileSync(candidate.filePath, "utf-8")
2772
+ }));
2773
+ if (present.length === 0) {
2774
+ return {
2775
+ name: "codex_config",
2776
+ status: "warn",
2777
+ detail: "No Codex config found; merge .snipara/templates/codex-config.toml into ~/.codex/config.toml and restart Codex"
2778
+ };
2779
+ }
2780
+ const configuredBearerEnvVars = present.flatMap(
2781
+ (candidate) => Array.from(candidate.content.matchAll(/bearer_token_env_var\s*=\s*["']([^"']+)["']/g)).map(
2782
+ (match) => ({
2783
+ label: candidate.label,
2784
+ value: match[1]
2785
+ })
2786
+ )
2787
+ );
2788
+ const usesSniparaEnv = configuredBearerEnvVars.some(
2789
+ (candidate) => candidate.value === "SNIPARA_API_KEY"
2790
+ );
2791
+ const nonSniparaBearerEnvVars = configuredBearerEnvVars.filter(
2792
+ (candidate) => candidate.value !== "SNIPARA_API_KEY"
2793
+ );
2794
+ const hasHostedEndpoint = present.some(
2795
+ (candidate) => candidate.content.includes(`${API_URL}/mcp/`)
2796
+ );
2797
+ const labels = present.map((candidate) => candidate.label).join(", ");
2798
+ if (nonSniparaBearerEnvVars.length > 0) {
2799
+ const details = nonSniparaBearerEnvVars.map((candidate) => `${candidate.label} uses ${candidate.value}`).join("; ");
2800
+ return {
2801
+ name: "codex_config",
2802
+ status: "warn",
2803
+ detail: `Codex config uses a non-Snipara bearer env var (${details}); use SNIPARA_API_KEY and restart Codex`
2804
+ };
2805
+ }
2806
+ if (!usesSniparaEnv || !hasHostedEndpoint) {
2807
+ return {
2808
+ name: "codex_config",
2809
+ status: "warn",
2810
+ detail: `Codex config found in ${labels}, but expected hosted MCP endpoint plus bearer_token_env_var = "SNIPARA_API_KEY"`
2811
+ };
2812
+ }
2813
+ return {
2814
+ name: "codex_config",
2815
+ status: "pass",
2816
+ detail: `Codex config uses hosted MCP and SNIPARA_API_KEY (${labels}); restart Codex after config changes`
2817
+ };
2818
+ }
2819
+ function getMistralConfigCheck() {
2820
+ const candidates = [
2821
+ {
2822
+ label: ".vibe/config.toml",
2823
+ filePath: path.join(process.cwd(), ".vibe", "config.toml")
2824
+ },
2825
+ {
2826
+ label: ".snipara/templates/mistral-vibe-config.toml",
2827
+ filePath: path.join(process.cwd(), ".snipara", "templates", "mistral-vibe-config.toml")
2828
+ },
2829
+ {
2830
+ label: "~/.vibe/config.toml",
2831
+ filePath: path.join(homedir(), ".vibe", "config.toml")
2832
+ }
2833
+ ];
2834
+ const present = candidates.filter((candidate) => fs.existsSync(candidate.filePath)).map((candidate) => ({
2835
+ label: candidate.label,
2836
+ content: fs.readFileSync(candidate.filePath, "utf-8")
2837
+ }));
2838
+ if (present.length === 0) {
2839
+ return {
2840
+ name: "mistral_config",
2841
+ status: "warn",
2842
+ detail: "No Mistral Vibe config found; use .snipara/templates/mistral-vibe-config.toml or add Snipara as a Le Chat Custom MCP Connector"
2843
+ };
2844
+ }
2845
+ const hasHostedEndpoint = present.some(
2846
+ (candidate) => candidate.content.includes(`${API_URL}/mcp/`)
2847
+ );
2848
+ const hasSniparaEnv = present.some(
2849
+ (candidate) => candidate.content.includes('api_key_env = "SNIPARA_API_KEY"')
2850
+ );
2851
+ const labels = present.map((candidate) => candidate.label).join(", ");
2852
+ if (!hasHostedEndpoint || !hasSniparaEnv) {
2853
+ return {
2854
+ name: "mistral_config",
2855
+ status: "warn",
2856
+ detail: `Mistral config found in ${labels}, but expected hosted MCP endpoint plus api_key_env = "SNIPARA_API_KEY"`
2857
+ };
2858
+ }
2859
+ return {
2860
+ name: "mistral_config",
2861
+ status: "pass",
2862
+ detail: `Mistral Vibe config uses hosted MCP and SNIPARA_API_KEY (${labels}); restart Vibe after config changes`
2863
+ };
2864
+ }
2530
2865
  function printDoctorReport(detection, report) {
2531
2866
  const targetClient = report.checks.find((check) => check.name === "target_client")?.detail || detection.aiClient;
2532
2867
  console.log(chalk.bold("\n\u{1FA7A} Snipara Doctor\n"));
@@ -2572,16 +2907,22 @@ async function testHostedConnection(answers) {
2572
2907
  spinner.succeed("Connected to Snipara Hosted MCP.");
2573
2908
  const toolNames = extractMcpToolNames(toolsResponse.data);
2574
2909
  const hasSniparaTools = toolNames.some((name) => name.startsWith("snipara_"));
2910
+ const advertisesLegacyTools = toolNames.some((name) => name.startsWith("rlm_"));
2575
2911
  const checks = [
2576
2912
  {
2577
2913
  name: "hosted_connection",
2578
2914
  status: "pass",
2579
- detail: "tools/list succeeded"
2915
+ detail: "real tools/list succeeded"
2580
2916
  },
2581
2917
  {
2582
- name: "hosted_tool_aliases",
2918
+ name: "hosted_snipara_tools",
2583
2919
  status: hasSniparaTools ? "pass" : "warn",
2584
- detail: hasSniparaTools ? "snipara_* tools are exposed by Hosted MCP" : "tools/list succeeded, but expected snipara_* tools were not detected"
2920
+ detail: hasSniparaTools ? "snipara_* tools are advertised by Hosted MCP" : "tools/list succeeded, but expected snipara_* tools were not detected"
2921
+ },
2922
+ {
2923
+ name: "hosted_legacy_tools_hidden",
2924
+ status: advertisesLegacyTools ? "warn" : "pass",
2925
+ detail: advertisesLegacyTools ? "tools/list still advertises legacy rlm_* tools; update or redeploy the hosted MCP surface" : "tools/list does not advertise legacy rlm_* duplicates"
2585
2926
  }
2586
2927
  ];
2587
2928
  const settingsTool = "snipara_settings";
@@ -2691,13 +3032,25 @@ function formatCheckStatus(check) {
2691
3032
  }
2692
3033
  function printSetupVerdict(answers, report) {
2693
3034
  const hostedConnection = getCheck(report, "hosted_connection");
2694
- const toolAliases = getCheck(report, "hosted_tool_aliases");
3035
+ const sniparaTools = getCheck(report, "hosted_snipara_tools");
3036
+ const legacyTools = getCheck(report, "hosted_legacy_tools_hidden");
3037
+ const codexConfig = getCheck(report, "codex_config");
3038
+ const mistralConfig = getCheck(report, "mistral_config");
2695
3039
  const claudeTools = getCheck(report, "claude_tools_visible");
2696
3040
  console.log(chalk.bold("\nSetup verdict:\n"));
2697
3041
  console.log(` Config written: ${answers.profile !== "runtime-only" ? "OK" : "not requested"}`);
2698
3042
  console.log(` Hosted MCP: ${formatCheckStatus(hostedConnection)}`);
2699
- if (toolAliases) {
2700
- console.log(` Tool aliases: ${formatCheckStatus(toolAliases)}`);
3043
+ if (sniparaTools) {
3044
+ console.log(` Snipara tools: ${formatCheckStatus(sniparaTools)}`);
3045
+ }
3046
+ if (legacyTools) {
3047
+ console.log(` Legacy tool duplicates: ${formatCheckStatus(legacyTools)}`);
3048
+ }
3049
+ if (codexConfig) {
3050
+ console.log(` Codex config: ${formatCheckStatus(codexConfig)}`);
3051
+ }
3052
+ if (mistralConfig) {
3053
+ console.log(` Mistral config: ${formatCheckStatus(mistralConfig)}`);
2701
3054
  }
2702
3055
  if (answers.aiClient === "claude-code" || answers.aiClient === "claude-desktop") {
2703
3056
  console.log(
@@ -2782,6 +3135,23 @@ function printSuccess(answers, report) {
2782
3135
  printNextStep(
2783
3136
  "For Codex, merge .snipara/templates/codex-config.toml into ~/.codex/config.toml if the app does not read project .codex/config.toml."
2784
3137
  );
3138
+ printNextStep(
3139
+ "Restart Codex after changing ~/.codex/config.toml so it rediscovers the hosted Snipara MCP tools."
3140
+ );
3141
+ printNextStep(
3142
+ "If Codex shows only a minimal Snipara tool set, force discovery for snipara_recall, snipara_context_query, and snipara_settings."
3143
+ );
3144
+ }
3145
+ if (answers.aiClient === "mistral") {
3146
+ printNextStep(
3147
+ "For Mistral Le Chat, add .snipara/templates/mistral-le-chat-mcp.json as a Custom MCP Connector reference in Connectors."
3148
+ );
3149
+ printNextStep(
3150
+ "For Mistral Vibe, keep .vibe/config.toml or ~/.vibe/config.toml on the generated SNIPARA_API_KEY-backed streamable-http server block, then restart Vibe."
3151
+ );
3152
+ printNextStep(
3153
+ "For LangChain JavaScript, adapt .snipara/templates/mistral-langchain-tools.ts to bind Snipara recall, context_query, and settings tools to ChatMistralAI."
3154
+ );
2785
3155
  }
2786
3156
  printNextStep("Open .snipara/companion/README.md for local commands and repair flow.");
2787
3157
  printNextStep("Run create-snipara doctor anytime you want a fresh local validation report.");
@@ -2790,7 +3160,7 @@ function printSuccess(answers, report) {
2790
3160
  `${COMPANION_COMMAND_NAME} ${MIN_COMPANION_VERSION}+ is installed for low-latency local hooks.`
2791
3161
  );
2792
3162
  printNextStep(
2793
- `Use ${COMPANION_NPX_COMMAND} query / plan / upload / task-commit for explicit local workflows.`
3163
+ `Use ${COMPANION_NPX_COMMAND} recall / query / plan / upload / task-commit for explicit local workflows and as a fallback when an AI client exposes only a partial MCP surface.`
2794
3164
  );
2795
3165
  printNextStep(
2796
3166
  `For business folders, preview with: ${COMPANION_NPX_COMMAND} onboard-folder ./client-export --source-provider local_folder --write-manifest ./snipara-onboard.json`
@@ -2877,6 +3247,7 @@ function detectEnvironment() {
2877
3247
  claudeProjectSettings: fs.existsSync(path.join(process.cwd(), ".claude", "settings.json")),
2878
3248
  claudeDesktopConfig: fs.existsSync(claudePaths.desktopConfigPath),
2879
3249
  cursorDir: fs.existsSync(path.join(homedir(), ".cursor")),
3250
+ mistralVibeConfig: fs.existsSync(path.join(process.cwd(), ".vibe", "config.toml")) || fs.existsSync(path.join(homedir(), ".vibe", "config.toml")),
2880
3251
  sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion")),
2881
3252
  sniparaTemplates: fs.existsSync(path.join(process.cwd(), ".snipara", "templates"))
2882
3253
  },
@@ -2981,6 +3352,16 @@ function collectConfiguredProjectSlugSignals(existingConfig = getExistingConfig(
2981
3352
  path.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
2982
3353
  )
2983
3354
  },
3355
+ {
3356
+ source: ".vibe/config.toml",
3357
+ slug: readProjectSlugFromTextFile(path.join(process.cwd(), ".vibe", "config.toml"))
3358
+ },
3359
+ {
3360
+ source: ".snipara/templates/mistral-vibe-config.toml",
3361
+ slug: readProjectSlugFromTextFile(
3362
+ path.join(process.cwd(), ".snipara", "templates", "mistral-vibe-config.toml")
3363
+ )
3364
+ },
2984
3365
  { source: ".env.local", slug: readProjectSlugFromEnvFile(".env.local") },
2985
3366
  { source: ".env", slug: readProjectSlugFromEnvFile(".env") }
2986
3367
  ];
@@ -3186,6 +3567,9 @@ function detectAiClient() {
3186
3567
  if (fs.existsSync(path.join(process.cwd(), ".gemini"))) {
3187
3568
  return "gemini";
3188
3569
  }
3570
+ if (fs.existsSync(path.join(process.cwd(), ".vibe"))) {
3571
+ return "mistral";
3572
+ }
3189
3573
  if (fs.existsSync(path.join(process.cwd(), ".continue"))) {
3190
3574
  return "continue";
3191
3575
  }
@@ -3204,6 +3588,9 @@ function detectAiClient() {
3204
3588
  if (fs.existsSync(path.join(homedir(), ".gemini")) || process.env.GEMINI_CLI) {
3205
3589
  return "gemini";
3206
3590
  }
3591
+ if (fs.existsSync(path.join(homedir(), ".vibe")) || process.env.MISTRAL_VIBE) {
3592
+ return "mistral";
3593
+ }
3207
3594
  if (fs.existsSync(path.join(homedir(), ".continue")) || process.env.CONTINUE_GLOBAL_DIR) {
3208
3595
  return "continue";
3209
3596
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-snipara",
3
- "version": "1.3.39",
3
+ "version": "1.3.41",
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",
@@ -34,6 +34,7 @@
34
34
  "cursor",
35
35
  "windsurf",
36
36
  "gemini",
37
+ "mistral",
37
38
  "vscode",
38
39
  "continue",
39
40
  "claude-desktop"