create-snipara 1.3.7 → 1.3.9

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 +51 -7
  2. package/dist/index.js +388 -53
  3. package/package.json +6 -1
package/README.md CHANGED
@@ -1,12 +1,18 @@
1
1
  # create-snipara
2
2
 
3
- One-command onboarding and maintenance CLI for **Snipara Hosted MCP Context + Memory** with optional `snipara-companion` and optional RLM-Runtime.
3
+ **One-command onboarding for Snipara Hosted MCP Context + Memory.**
4
+
5
+ `create-snipara` connects a local project to Snipara so AI agents can start
6
+ with project-owned context, reviewed memory, shared guidance, and optional code
7
+ impact tooling instead of starting cold every session.
4
8
 
5
9
  ```bash
6
10
  npx create-snipara
7
11
  ```
8
12
 
9
- `create-snipara` is the simple onboarding package for the agent path. It creates or reuses auth, writes Hosted MCP config, and prepares the local project so an LLM client can use Snipara context and reviewed memory.
13
+ `create-snipara` is the simple onboarding package for the agent path. It
14
+ creates or reuses auth, writes Hosted MCP config, and prepares the local project
15
+ so an LLM client can use Snipara context and reviewed memory.
10
16
 
11
17
  Use it for:
12
18
 
@@ -15,6 +21,26 @@ Use it for:
15
21
  - local project wiring, `doctor`, `repair`, and `upgrade`
16
22
  - GitHub repository sync approval when the current folder is a GitHub repo
17
23
 
24
+ ## Why It Exists
25
+
26
+ AI clients all have slightly different config formats, but the project need is
27
+ the same: connect the agent to the same durable project memory. `create-snipara`
28
+ normalizes that setup.
29
+
30
+ ```mermaid
31
+ flowchart LR
32
+ Project["Local project"] --> CLI["npx create-snipara"]
33
+ CLI --> Auth["Snipara auth and project key"]
34
+ CLI --> MCP["Hosted MCP config"]
35
+ CLI --> Templates["AGENTS.md, CLAUDE.md, Cursor, Codex snippets"]
36
+ MCP --> Agents["Codex, Claude Code, Cursor, ChatGPT Desktop"]
37
+ Templates --> Agents
38
+ Agents --> Context["Snipara context and memory"]
39
+ ```
40
+
41
+ The CLI does not become the runtime brain. After setup, the agent talks to
42
+ Hosted MCP and keeps using its own LLM.
43
+
18
44
  Do not use it to choose business documents one by one. For local folders, mounted Drive/SharePoint exports, old offers, or PowerPoints, use `snipara-business`.
19
45
 
20
46
  The product surfaces remain:
@@ -46,7 +72,7 @@ Default profile: `hosted-only`
46
72
 
47
73
  Free onboarding is now **Context + Memory**, not Context-only:
48
74
 
49
- - Context Free: one project and 100 context queries/month
75
+ - Context Free: three projects and 1,000 context queries/month
50
76
  - Memory Free: reviewed hosted memories for continuity across sessions
51
77
  - Hosted MCP: direct connection for Claude Code, Codex, Cursor, ChatGPT Desktop, and other MCP clients
52
78
 
@@ -76,6 +102,8 @@ That distinction matters:
76
102
  - installs `snipara-companion` when the selected profile needs local helper workflows
77
103
  - installs optional `rlm-runtime`
78
104
  - configures `.mcp.json`
105
+ - generates merge-ready AI agent templates in `.snipara/templates/`, including Pro+ symbol-card and code-impact guidance for paid Context plans
106
+ - creates `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/snipara.mdc`, and `.codex/config.toml` when those files do not already exist
79
107
  - updates `.env`, `.env.local`, and `.env.example`
80
108
  - generates `.snipara/companion/README.md`
81
109
  - generates `.snipara/companion/commands.json`
@@ -102,15 +130,30 @@ npx create-snipara
102
130
 
103
131
  The interactive flow now asks for:
104
132
 
105
- - install profile
106
133
  - project slug
107
- - optional project id
108
134
  - API key path: project key, team key, free signup, or add key later
109
135
  - AI client
136
+
137
+ By default, this stays on the recommended `hosted-only` setup. Add
138
+ `--advanced` when you want profile selection, project id entry, hook prompts,
139
+ GitHub automation prompts, or runtime provider prompts.
140
+
141
+ The advanced flow asks for:
142
+
143
+ - install profile
144
+ - optional project id
110
145
  - whether hooks should be generated
111
146
  - whether to open GitHub repository automation when a GitHub remote is detected
112
147
  - optional runtime provider config when `rlm-runtime` is installed
113
148
 
149
+ The generated agent templates cover:
150
+
151
+ - Codex `AGENTS.md`
152
+ - Claude Code `CLAUDE.md`
153
+ - Cursor `.cursor/rules/snipara.mdc`
154
+ - Codex `.codex/config.toml` snippet
155
+ - ChatGPT/OpenAI HTTP MCP snippet
156
+
114
157
  ## Main Commands
115
158
 
116
159
  ### Install or initialize
@@ -207,6 +250,7 @@ That keeps business truth review separate from agent onboarding.
207
250
  npx create-snipara --profile hosted-only
208
251
  npx create-snipara --profile hosted-companion
209
252
  npx create-snipara --profile full-stack
253
+ npx create-snipara --advanced
210
254
 
211
255
  # Runtime-only local execution
212
256
  npx create-snipara --runtime-only
@@ -319,7 +363,7 @@ Best when the user only wants Hosted MCP Context + Memory.
319
363
  ### Full local stack
320
364
 
321
365
  ```bash
322
- npx create-snipara --profile full-stack
366
+ npx create-snipara --profile full-stack --advanced
323
367
  ```
324
368
 
325
369
  Best when the user also wants `snipara-companion` and `rlm-runtime`.
@@ -367,7 +411,7 @@ npx create-snipara print-config --json
367
411
  When testing a locally packed tarball, use `npm exec --package` instead of `npx`, for example:
368
412
 
369
413
  ```bash
370
- npm exec --package ./create-snipara-1.3.7.tgz create-snipara -- --help
414
+ npm exec --package ./create-snipara-1.3.9.tgz create-snipara -- --help
371
415
  ```
372
416
 
373
417
  This avoids `npx` resolution issues with unpublished tarballs.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import fs from "fs";
9
9
  import path from "path";
10
10
  import { execSync, spawn } from "child_process";
11
11
  import { homedir } from "os";
12
- var VERSION = "1.3.7";
12
+ var VERSION = "1.3.9";
13
13
  var BASE_URL = "https://www.snipara.com";
14
14
  var API_URL = "https://api.snipara.com";
15
15
  var COMPANION_PACKAGE_NAME = "snipara-companion";
@@ -27,7 +27,7 @@ function getCompanionInitClientFlag(aiClient) {
27
27
  function buildProgram() {
28
28
  const program = new Command();
29
29
  program.name("create-snipara").description("Scaffold hosted Snipara MCP config plus optional local helper workflows").version(VERSION);
30
- program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
30
+ program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option("--advanced", "Show advanced profile, project id, hooks, GitHub, and runtime prompts").option(
31
31
  "--profile <profile>",
32
32
  "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
33
33
  ).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").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 RLM-Runtime").option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
@@ -58,7 +58,7 @@ function buildProgram() {
58
58
  return program;
59
59
  }
60
60
  function addSharedRootOptions(program) {
61
- program.option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
61
+ program.option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option("--advanced", "Show advanced profile, project id, hooks, GitHub, and runtime prompts").option(
62
62
  "--profile <profile>",
63
63
  "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
64
64
  ).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").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 RLM-Runtime").option("-y, --yes", "Accept defaults without prompting");
@@ -67,8 +67,63 @@ async function main() {
67
67
  const program = buildProgram();
68
68
  await program.parseAsync(process.argv);
69
69
  }
70
+ function normalizeInitOptions(options) {
71
+ const parsed = parseInitOptionsFromArgv(process.argv.slice(2));
72
+ const normalized = { ...parsed };
73
+ for (const [key, value] of Object.entries(options)) {
74
+ if (value !== void 0) {
75
+ normalized[key] = value;
76
+ }
77
+ }
78
+ return normalized;
79
+ }
80
+ function parseInitOptionsFromArgv(args) {
81
+ const parsed = {};
82
+ const valueFlags = {
83
+ "--api-key": "apiKey",
84
+ "--team-key": "teamKey",
85
+ "--slug": "slug",
86
+ "--project-id": "projectId",
87
+ "--profile": "profile",
88
+ "--github-path": "githubPath"
89
+ };
90
+ const booleanFlags = {
91
+ "--advanced": "advanced",
92
+ "--skip-companion": "skipCompanion",
93
+ "--skip-mcp": "skipMcp",
94
+ "--skip-runtime": "skipRuntime",
95
+ "--skip-hooks": "skipHooks",
96
+ "--skip-test": "skipTest",
97
+ "--runtime-only": "runtimeOnly",
98
+ "--github": "github",
99
+ "--skip-github": "skipGithub",
100
+ "--yes": "yes",
101
+ "-y": "yes"
102
+ };
103
+ for (let index = 0; index < args.length; index += 1) {
104
+ const arg = args[index];
105
+ if (arg === "init") {
106
+ continue;
107
+ }
108
+ const [flag, inlineValue] = arg.split("=", 2);
109
+ if (flag in valueFlags) {
110
+ const value = inlineValue ?? args[index + 1];
111
+ if (value && !value.startsWith("-")) {
112
+ parsed[valueFlags[flag]] = value;
113
+ if (inlineValue === void 0) {
114
+ index += 1;
115
+ }
116
+ }
117
+ continue;
118
+ }
119
+ if (arg in booleanFlags) {
120
+ parsed[booleanFlags[arg]] = true;
121
+ }
122
+ }
123
+ return parsed;
124
+ }
70
125
  async function runInit(options) {
71
- const answers = await gatherConfiguration(options);
126
+ const answers = await gatherConfiguration(normalizeInitOptions(options));
72
127
  if (answers.installMcp || answers.installRuntime) {
73
128
  await installPythonPackages(answers, false);
74
129
  }
@@ -78,6 +133,7 @@ async function runInit(options) {
78
133
  if (answers.profile !== "runtime-only" || answers.installRuntime) {
79
134
  await createMcpConfig(answers);
80
135
  }
136
+ await createAgentTemplates(answers);
81
137
  if (answers.installHooks && answers.aiClient === "claude-code") {
82
138
  await installClaudeCodeHooks(answers);
83
139
  }
@@ -113,6 +169,7 @@ async function runRepair(options) {
113
169
  await installNodeCompanion(true);
114
170
  }
115
171
  await createMcpConfig(answers);
172
+ await createAgentTemplates(answers);
116
173
  await updateEnvFiles(answers);
117
174
  await createCompanionPack(answers);
118
175
  if (answers.installHooks && answers.aiClient === "claude-code") {
@@ -140,6 +197,7 @@ async function runUpgrade(options) {
140
197
  if (options.withMcp || options.withRuntime || detection.files.mcpConfig) {
141
198
  await createMcpConfig(upgradedAnswers);
142
199
  }
200
+ await createAgentTemplates(upgradedAnswers);
143
201
  if (options.withHooks || answers.installHooks) {
144
202
  await installClaudeCodeHooks({ ...answers, installHooks: true });
145
203
  }
@@ -195,6 +253,7 @@ async function gatherConfiguration(options) {
195
253
  const detectedClient = detectAiClient();
196
254
  const existingConfig = getExistingConfig();
197
255
  const detectedGitRepo = detectGitRepository();
256
+ const showAdvancedPrompts = Boolean(options.advanced);
198
257
  if (options.github && !detectedGitRepo) {
199
258
  console.log(
200
259
  chalk.yellow(
@@ -227,7 +286,7 @@ async function gatherConfiguration(options) {
227
286
  }
228
287
  const response = await prompts([
229
288
  {
230
- type: "select",
289
+ type: showAdvancedPrompts ? "select" : null,
231
290
  name: "profile",
232
291
  message: "Choose an install profile:",
233
292
  initial: installProfileChoices().findIndex((choice) => choice.value === initialProfile),
@@ -241,7 +300,7 @@ async function gatherConfiguration(options) {
241
300
  validate: (value) => value.length > 0 ? true : "Project slug is required (for example my-project)"
242
301
  },
243
302
  {
244
- type: "text",
303
+ type: showAdvancedPrompts ? "text" : null,
245
304
  name: "projectId",
246
305
  message: "Project ID (optional, press Enter to skip):",
247
306
  initial: options.projectId || detectedProjectId || ""
@@ -282,7 +341,9 @@ async function gatherConfiguration(options) {
282
341
  type: "select",
283
342
  name: "aiClient",
284
343
  message: "Which AI client are you using?",
285
- initial: ["claude-code", "cursor", "claude-desktop", "other"].indexOf(detectedClient),
344
+ initial: ["claude-code", "cursor", "codex", "chatgpt", "claude-desktop", "other"].indexOf(
345
+ detectedClient
346
+ ),
286
347
  choices: [
287
348
  {
288
349
  title: "Claude Code (CLI/VS Code extension)",
@@ -294,6 +355,16 @@ async function gatherConfiguration(options) {
294
355
  value: "cursor",
295
356
  description: "MCP via .mcp.json plus companion terminal workflows"
296
357
  },
358
+ {
359
+ title: "OpenAI Codex",
360
+ value: "codex",
361
+ description: "Hosted MCP plus AGENTS.md"
362
+ },
363
+ {
364
+ title: "ChatGPT / OpenAI MCP",
365
+ value: "chatgpt",
366
+ description: "Hosted MCP for ChatGPT Desktop or OpenAI-compatible clients"
367
+ },
297
368
  {
298
369
  title: "Claude Desktop",
299
370
  value: "claude-desktop",
@@ -303,19 +374,19 @@ async function gatherConfiguration(options) {
303
374
  ]
304
375
  },
305
376
  {
306
- type: "confirm",
377
+ type: showAdvancedPrompts ? "confirm" : null,
307
378
  name: "installHooks",
308
379
  message: "Generate local hooks when supported?",
309
380
  initial: detectedClient === "claude-code"
310
381
  },
311
382
  {
312
- type: (_, values) => detectedGitRepo && !options.skipGithub && values.profile !== "runtime-only" ? "confirm" : null,
383
+ type: (_, values) => detectedGitRepo && showAdvancedPrompts && !options.skipGithub && (values.profile || initialProfile) !== "runtime-only" ? "confirm" : null,
313
384
  name: "configureGitHub",
314
385
  message: detectedGitRepo ? `Open GitHub automation setup for ${detectedGitRepo.fullName} after local config?` : "Open GitHub automation setup after local config?",
315
386
  initial: true
316
387
  },
317
388
  {
318
- type: (_, values) => values.configureGitHub && detectedGitRepo ? "text" : null,
389
+ type: (_, values) => showAdvancedPrompts && values.configureGitHub && detectedGitRepo ? "text" : null,
319
390
  name: "githubPath",
320
391
  message: "GitHub path filter (optional, press Enter for Snipara defaults):",
321
392
  initial: options.githubPath || ""
@@ -339,7 +410,7 @@ async function gatherConfiguration(options) {
339
410
  }
340
411
  const answers = buildAnswersFromProfile({
341
412
  projectSlug: signupResult?.projectSlug || response.projectSlug,
342
- projectId: signupResult?.projectId || response.projectId || "",
413
+ projectId: signupResult?.projectId || response.projectId || options.projectId || "",
343
414
  apiKey: apiKey || "",
344
415
  apiKeyType,
345
416
  profile: response.profile || initialProfile,
@@ -744,6 +815,162 @@ async function createMcpConfig(answers) {
744
815
  await createClaudeDesktopConfig(answers);
745
816
  }
746
817
  }
818
+ async function createAgentTemplates(answers) {
819
+ if (answers.profile === "runtime-only") {
820
+ return;
821
+ }
822
+ console.log(chalk.bold("\nGenerating Agent Templates\n"));
823
+ const templatesDir = path.join(process.cwd(), ".snipara", "templates");
824
+ fs.mkdirSync(templatesDir, { recursive: true });
825
+ const templates = [
826
+ { path: path.join(templatesDir, "AGENTS.md"), content: buildAgentsMdTemplate(answers) },
827
+ { path: path.join(templatesDir, "CLAUDE.md"), content: buildClaudeMdTemplate(answers) },
828
+ {
829
+ path: path.join(templatesDir, "cursor-snipara.mdc"),
830
+ content: buildCursorRuleTemplate(answers)
831
+ },
832
+ {
833
+ path: path.join(templatesDir, "codex-config.toml"),
834
+ content: buildCodexConfigTemplate(answers)
835
+ },
836
+ {
837
+ path: path.join(templatesDir, "chatgpt-openai-mcp.json"),
838
+ content: buildChatGptMcpTemplate(answers)
839
+ }
840
+ ];
841
+ for (const template of templates) {
842
+ fs.writeFileSync(template.path, template.content);
843
+ }
844
+ console.log(chalk.green(" Created .snipara/templates agent snippets"));
845
+ writeIfMissing(
846
+ path.join(process.cwd(), "AGENTS.md"),
847
+ buildAgentsMdTemplate(answers),
848
+ "AGENTS.md"
849
+ );
850
+ writeIfMissing(
851
+ path.join(process.cwd(), "CLAUDE.md"),
852
+ buildClaudeMdTemplate(answers),
853
+ "CLAUDE.md"
854
+ );
855
+ const cursorRulesDir = path.join(process.cwd(), ".cursor", "rules");
856
+ fs.mkdirSync(cursorRulesDir, { recursive: true });
857
+ writeIfMissing(
858
+ path.join(cursorRulesDir, "snipara.mdc"),
859
+ buildCursorRuleTemplate(answers),
860
+ ".cursor/rules/snipara.mdc"
861
+ );
862
+ const codexDir = path.join(process.cwd(), ".codex");
863
+ fs.mkdirSync(codexDir, { recursive: true });
864
+ writeIfMissing(
865
+ path.join(codexDir, "config.toml"),
866
+ buildCodexConfigTemplate(answers),
867
+ ".codex/config.toml"
868
+ );
869
+ ensureGitignoreEntry(".snipara/companion/doctor.json", "# Snipara generated validation output");
870
+ }
871
+ function writeIfMissing(filePath, content, label) {
872
+ if (fs.existsSync(filePath)) {
873
+ console.log(chalk.dim(` ${label} already exists; merge from .snipara/templates if needed`));
874
+ return;
875
+ }
876
+ fs.writeFileSync(filePath, content);
877
+ console.log(chalk.green(` Created ${label}`));
878
+ }
879
+ function buildHostedMcpEndpoint(answers) {
880
+ return `${API_URL}/mcp/${answers.projectSlug}`;
881
+ }
882
+ function buildAgentsMdTemplate(answers) {
883
+ return `# AGENTS.md
884
+
885
+ ## Snipara Context Workflow
886
+
887
+ This project uses Snipara Hosted MCP for project context and reviewed memory.
888
+
889
+ - Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
890
+ - Auth: use \`SNIPARA_API_KEY\` or \`RLM_API_KEY\` from the environment. Never commit keys.
891
+
892
+ Agent task lifecycle:
893
+
894
+ 1. Start substantial work with \`rlm_recall\` and a targeted \`rlm_context_query\`.
895
+ 2. Use \`rlm_context_query\` for docs, business context, client/project truth, architecture notes, runbooks, and narrative source material.
896
+ 3. When \`rlm_context_query\` returns \`answer_pack\`, treat it as the first-pass response plan: use its source facts, caveats, source map, and verification checklist before drafting claims.
897
+ 4. Use \`rlm_get_chunk\` to load cited source sections returned by reference-based queries before relying on precise wording.
898
+ 5. Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
899
+ 6. On paid Context plans, use \`rlm_code_symbol_card\` for agent-ready symbol context and \`rlm_code_impact\` before risky multi-file changes or PR reviews.
900
+ 7. Use \`rlm_plan\` or \`rlm_decompose\` only for complex work and only when the hosted server exposes them.
901
+ 8. Use local file reads, \`rg\`, git commands, and test commands for exact edits, current working-tree state, and verification.
902
+ 9. Use RLM Runtime only when sandboxed execution, repeatable validation, or isolated transformations materially help.
903
+ 10. If a broad query is slow, retry once with a narrow keyword query before falling back to local search.
904
+ 11. End substantial work with \`rlm_end_of_task_commit\` when available. Use \`rlm_remember_if_novel\` for one reusable memory while avoiding duplicates, and use \`rlm_remember\` for explicit direct memory writes.
905
+ 12. Do not store secrets, tokens, passwords, private keys, raw logs, transient command output, or unreviewed guesses in memory.
906
+ `;
907
+ }
908
+ function buildClaudeMdTemplate(answers) {
909
+ return `# CLAUDE.md
910
+
911
+ Canonical project context is in \`AGENTS.md\`. This file exists for Claude Code compatibility.
912
+
913
+ ## Snipara
914
+
915
+ Use Snipara Hosted MCP before answering project-specific questions.
916
+
917
+ - Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
918
+ - Start substantial work with \`rlm_recall\` and a targeted \`rlm_context_query\`.
919
+ - Use \`rlm_context_query\` for source truth and \`rlm_get_chunk\` for exact cited sections.
920
+ - When \`rlm_context_query\` returns \`answer_pack\`, use its facts, caveats, source map, and verification checklist before drafting claims.
921
+ - Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
922
+ - On paid Context plans, use \`rlm_code_symbol_card\` before editing important symbols and \`rlm_code_impact\` before risky multi-file changes or PR reviews.
923
+ - Use RLM Runtime only when sandboxed execution or repeatable validation materially helps.
924
+ - End substantial work with \`rlm_end_of_task_commit\` when available; use \`rlm_remember_if_novel\` or \`rlm_remember\` only for narrow durable memories.
925
+ - Do not store secrets, one-off command output, raw logs, or unreviewed guesses in memory.
926
+ - Use the Snipara Claude Code plugin only when slash commands, skills, or hooks are helpful; Hosted MCP remains the normal agent path.
927
+ `;
928
+ }
929
+ function buildCursorRuleTemplate(answers) {
930
+ return `---
931
+ description: Use Snipara Hosted MCP for project context and reviewed memory.
932
+ alwaysApply: true
933
+ ---
934
+
935
+ # Snipara Agent Workflow
936
+
937
+ This workspace uses Snipara Hosted MCP for current project context, business/client context, and reviewed memory.
938
+
939
+ - Check the configured Snipara MCP server before answering project-specific questions.
940
+ - Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
941
+ - Use \`rlm_recall\` for durable decisions, preferences, and validated learnings.
942
+ - Use \`rlm_context_query\` for docs, business context, current client/project truth, and narrative source material.
943
+ - When \`rlm_context_query\` returns \`answer_pack\`, use its facts, caveats, source map, and verification checklist before drafting claims.
944
+ - Use \`rlm_get_chunk\` before relying on exact cited wording from reference-based results.
945
+ - Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
946
+ - On paid Context plans, use \`rlm_code_symbol_card\` before editing important symbols and \`rlm_code_impact\` before risky multi-file changes or PR reviews.
947
+ - Use \`rlm_plan\` or \`rlm_decompose\` only for complex work and only when available.
948
+ - Use local file reads and tests for exact code edits and current working-tree state.
949
+ - Use RLM Runtime only when sandboxed execution or repeatable validation materially helps.
950
+ - End substantial work with \`rlm_end_of_task_commit\` when available; use \`rlm_remember_if_novel\` or \`rlm_remember\` only for narrow durable memories. Never store secrets.
951
+ `;
952
+ }
953
+ function buildCodexConfigTemplate(answers) {
954
+ return `[mcp_servers.snipara]
955
+ type = "streamable_http"
956
+ url = "${buildHostedMcpEndpoint(answers)}"
957
+ bearer_token_env_var = "SNIPARA_API_KEY"
958
+ `;
959
+ }
960
+ function buildChatGptMcpTemplate(answers) {
961
+ return `${JSON.stringify(
962
+ {
963
+ name: "snipara",
964
+ url: buildHostedMcpEndpoint(answers),
965
+ headers: {
966
+ "X-API-Key": "YOUR_SNIPARA_PROJECT_API_KEY"
967
+ }
968
+ },
969
+ null,
970
+ 2
971
+ )}
972
+ `;
973
+ }
747
974
  function ensureGitignoreEntry(entry, comment) {
748
975
  const gitignorePath = path.join(process.cwd(), ".gitignore");
749
976
  if (fs.existsSync(gitignorePath)) {
@@ -978,6 +1205,18 @@ function getClientGuidance(client) {
978
1205
  "Use the companion CLI only for explicit upload, planning, chunk lookup, doctor, and task commit workflows outside the editor UI."
979
1206
  ];
980
1207
  }
1208
+ if (client === "codex") {
1209
+ return [
1210
+ "Codex should use the hosted MCP surface plus AGENTS.md as the canonical project instruction file.",
1211
+ "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."
1212
+ ];
1213
+ }
1214
+ if (client === "chatgpt") {
1215
+ return [
1216
+ "ChatGPT and OpenAI-compatible MCP clients should use the direct hosted MCP endpoint.",
1217
+ "The generated chatgpt-openai-mcp.json is a copy-ready HTTP MCP reference with placeholder auth."
1218
+ ];
1219
+ }
981
1220
  if (client === "claude-desktop") {
982
1221
  return [
983
1222
  "Claude Desktop uses the generated desktop MCP config plus the same hosted Snipara tool contract.",
@@ -1253,6 +1492,11 @@ async function runValidation(answers, includeHostedCheck) {
1253
1492
  status: detection.files.sniparaCompanionPack ? "pass" : "warn",
1254
1493
  detail: detection.files.sniparaCompanionPack ? ".snipara/companion present" : ".snipara/companion missing"
1255
1494
  });
1495
+ checks.push({
1496
+ name: "agent_templates",
1497
+ status: detection.files.sniparaTemplates && detection.files.cursorRule && detection.files.codexConfig ? "pass" : "warn",
1498
+ 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"
1499
+ });
1256
1500
  checks.push({
1257
1501
  name: "env_files",
1258
1502
  status: detection.files.env || detection.files.envLocal || detection.files.envExample ? "pass" : "warn",
@@ -1264,7 +1508,7 @@ async function runValidation(answers, includeHostedCheck) {
1264
1508
  detail: answers.installHooks && answers.aiClient === "claude-code" ? detection.files.claudeDir ? "~/.claude detected" : "~/.claude missing" : "Hooks not requested"
1265
1509
  });
1266
1510
  if (includeHostedCheck && answers.apiKeyType !== "none" && !answers.apiKey.includes("YOUR_")) {
1267
- checks.push(await testConnection(answers));
1511
+ checks.push(...await testHostedConnection(answers));
1268
1512
  } else if (includeHostedCheck) {
1269
1513
  checks.push({
1270
1514
  name: "hosted_connection",
@@ -1303,47 +1547,118 @@ function printDoctorReport(detection, report) {
1303
1547
  }
1304
1548
  console.log("");
1305
1549
  }
1306
- async function testConnection(answers) {
1307
- const spinner = ora("Connecting to Snipara API...").start();
1550
+ async function testHostedConnection(answers) {
1551
+ const spinner = ora("Connecting to Snipara Hosted MCP...").start();
1308
1552
  try {
1309
- const response = await fetch(`${API_URL}/mcp/${answers.projectSlug}`, {
1310
- method: "POST",
1311
- headers: {
1312
- "Content-Type": "application/json",
1313
- "X-API-Key": answers.apiKey
1314
- },
1315
- body: JSON.stringify({
1316
- jsonrpc: "2.0",
1317
- id: 1,
1318
- method: "tools/list",
1319
- params: {}
1320
- })
1321
- });
1322
- if (response.ok) {
1323
- const data = await response.json();
1324
- const toolCount = data.result?.tools?.length || 0;
1325
- spinner.succeed(`Connected! ${toolCount} MCP tools available.`);
1326
- return {
1553
+ const toolsResponse = await callHostedMcp(answers, "tools/list", {});
1554
+ if (!toolsResponse.ok) {
1555
+ spinner.warn(`Hosted MCP returned HTTP ${toolsResponse.status}`);
1556
+ return [
1557
+ {
1558
+ name: "hosted_connection",
1559
+ status: toolsResponse.status === 401 || toolsResponse.status === 403 ? "fail" : "warn",
1560
+ detail: describeHostedHttpFailure(toolsResponse.status, toolsResponse.bodyText)
1561
+ }
1562
+ ];
1563
+ }
1564
+ if (toolsResponse.data.error) {
1565
+ spinner.warn("Hosted MCP tools/list returned a JSON-RPC error");
1566
+ return [
1567
+ {
1568
+ name: "hosted_connection",
1569
+ status: "warn",
1570
+ detail: describeJsonRpcError(toolsResponse.data)
1571
+ }
1572
+ ];
1573
+ }
1574
+ const toolCount = toolsResponse.data.result?.tools?.length || 0;
1575
+ spinner.succeed(`Connected! ${toolCount} MCP tools available.`);
1576
+ const checks = [
1577
+ {
1327
1578
  name: "hosted_connection",
1328
1579
  status: "pass",
1329
- detail: `Connected successfully (${toolCount} tools reported)`
1330
- };
1580
+ detail: `tools/list succeeded (${toolCount} tools reported)`
1581
+ }
1582
+ ];
1583
+ const settingsResponse = await callHostedMcp(answers, "tools/call", {
1584
+ name: "rlm_settings",
1585
+ arguments: {}
1586
+ });
1587
+ if (settingsResponse.ok && !settingsResponse.data.error) {
1588
+ checks.push({
1589
+ name: "hosted_settings",
1590
+ status: "pass",
1591
+ detail: "rlm_settings tool call succeeded"
1592
+ });
1593
+ } else {
1594
+ checks.push({
1595
+ name: "hosted_settings",
1596
+ status: "warn",
1597
+ detail: settingsResponse.ok ? describeJsonRpcError(settingsResponse.data) : describeHostedHttpFailure(settingsResponse.status, settingsResponse.bodyText)
1598
+ });
1331
1599
  }
1332
- const errorText = await response.text();
1333
- spinner.warn(`Connection returned ${response.status}`);
1334
- return {
1335
- name: "hosted_connection",
1336
- status: "warn",
1337
- detail: `HTTP ${response.status}: ${errorText.slice(0, 120)}`
1338
- };
1600
+ return checks;
1339
1601
  } catch (error) {
1340
1602
  spinner.warn(`Could not connect: ${error}`);
1341
- return {
1342
- name: "hosted_connection",
1343
- status: "warn",
1344
- detail: "Could not reach Snipara API from this machine"
1345
- };
1603
+ return [
1604
+ {
1605
+ name: "hosted_connection",
1606
+ status: "warn",
1607
+ detail: "Could not reach Snipara Hosted MCP from this machine"
1608
+ }
1609
+ ];
1610
+ }
1611
+ }
1612
+ async function callHostedMcp(answers, method, params) {
1613
+ const response = await fetch(`${API_URL}/mcp/${answers.projectSlug}`, {
1614
+ method: "POST",
1615
+ headers: {
1616
+ "Content-Type": "application/json",
1617
+ "X-API-Key": answers.apiKey
1618
+ },
1619
+ body: JSON.stringify({
1620
+ jsonrpc: "2.0",
1621
+ id: 1,
1622
+ method,
1623
+ params
1624
+ })
1625
+ });
1626
+ const bodyText = await response.text();
1627
+ let data = {};
1628
+ try {
1629
+ data = bodyText ? JSON.parse(bodyText) : {};
1630
+ } catch {
1631
+ data = {};
1632
+ }
1633
+ return {
1634
+ ok: response.ok,
1635
+ status: response.status,
1636
+ data,
1637
+ bodyText
1638
+ };
1639
+ }
1640
+ function describeHostedHttpFailure(status, bodyText) {
1641
+ const detail = bodyText.trim().slice(0, 120);
1642
+ if (status === 401 || status === 403) {
1643
+ return `Auth failed (${status}). Check the project/team API key and project slug.`;
1644
+ }
1645
+ if (status === 404) {
1646
+ return "Endpoint or project slug was not found. Check the hosted MCP URL.";
1647
+ }
1648
+ if (status === 429) {
1649
+ return "Rate limit reached. Retry later or check plan limits.";
1650
+ }
1651
+ if (status >= 500) {
1652
+ return `Hosted MCP server error (${status}). Retry later or check Snipara status.`;
1346
1653
  }
1654
+ return detail ? `HTTP ${status}: ${detail}` : `HTTP ${status}`;
1655
+ }
1656
+ function describeJsonRpcError(data) {
1657
+ if (!data.error) {
1658
+ return "Unexpected hosted MCP response";
1659
+ }
1660
+ const code = data.error.code !== void 0 ? ` ${data.error.code}` : "";
1661
+ return `JSON-RPC error${code}: ${data.error.message || "unknown error"}`;
1347
1662
  }
1348
1663
  function printSuccess(answers, report) {
1349
1664
  console.log(
@@ -1387,6 +1702,11 @@ function printSuccess(answers, report) {
1387
1702
  nextStep += 1;
1388
1703
  };
1389
1704
  printNextStep("Restart your AI client if you created or changed hosted MCP config.");
1705
+ if (answers.aiClient === "codex") {
1706
+ printNextStep(
1707
+ "For Codex, merge .snipara/templates/codex-config.toml into ~/.codex/config.toml if the app does not read project .codex/config.toml."
1708
+ );
1709
+ }
1390
1710
  printNextStep("Open .snipara/companion/README.md for local commands and repair flow.");
1391
1711
  printNextStep("Run create-snipara doctor anytime you want a fresh local validation report.");
1392
1712
  if (answers.installCompanion) {
@@ -1452,9 +1772,14 @@ function detectEnvironment() {
1452
1772
  env: fs.existsSync(path.join(process.cwd(), ".env")),
1453
1773
  envLocal: fs.existsSync(path.join(process.cwd(), ".env.local")),
1454
1774
  envExample: fs.existsSync(path.join(process.cwd(), ".env.example")),
1775
+ agentsMd: fs.existsSync(path.join(process.cwd(), "AGENTS.md")),
1776
+ claudeMd: fs.existsSync(path.join(process.cwd(), "CLAUDE.md")),
1777
+ codexConfig: fs.existsSync(path.join(process.cwd(), ".codex", "config.toml")),
1778
+ cursorRule: fs.existsSync(path.join(process.cwd(), ".cursor", "rules", "snipara.mdc")),
1455
1779
  claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
1456
1780
  cursorDir: fs.existsSync(path.join(homedir(), ".cursor")),
1457
- sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion"))
1781
+ sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion")),
1782
+ sniparaTemplates: fs.existsSync(path.join(process.cwd(), ".snipara", "templates"))
1458
1783
  },
1459
1784
  tooling: {
1460
1785
  node: commandExists("node"),
@@ -1507,7 +1832,7 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
1507
1832
  if (detection.existingConfig.hasSniparaServer) {
1508
1833
  return "hosted-only";
1509
1834
  }
1510
- return detection.tooling.runtimeCli ? "runtime-only" : "hosted-only";
1835
+ return "hosted-only";
1511
1836
  }
1512
1837
  function readApiKeyFromEnvFiles() {
1513
1838
  const candidates = [".env.local", ".env"];
@@ -1515,7 +1840,7 @@ function readApiKeyFromEnvFiles() {
1515
1840
  const filePath = path.join(process.cwd(), file);
1516
1841
  if (!fs.existsSync(filePath)) continue;
1517
1842
  const content = fs.readFileSync(filePath, "utf-8");
1518
- const match = content.match(/^SNIPARA_API_KEY=(.+)$/m);
1843
+ const match = content.match(/^(?:SNIPARA_API_KEY|RLM_API_KEY)=(.+)$/m);
1519
1844
  if (match?.[1]) {
1520
1845
  return match[1].trim();
1521
1846
  }
@@ -1525,7 +1850,8 @@ function readApiKeyFromEnvFiles() {
1525
1850
  function detectProjectSlug() {
1526
1851
  try {
1527
1852
  const remote = execSync("git remote get-url origin", {
1528
- encoding: "utf-8"
1853
+ encoding: "utf-8",
1854
+ stdio: ["ignore", "pipe", "ignore"]
1529
1855
  }).trim();
1530
1856
  const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
1531
1857
  if (match) {
@@ -1538,7 +1864,8 @@ function detectProjectSlug() {
1538
1864
  function detectGitRepository() {
1539
1865
  try {
1540
1866
  const remoteUrl = execSync("git remote get-url origin", {
1541
- encoding: "utf-8"
1867
+ encoding: "utf-8",
1868
+ stdio: ["ignore", "pipe", "ignore"]
1542
1869
  }).trim();
1543
1870
  const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) || remoteUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
1544
1871
  if (!match) {
@@ -1561,7 +1888,8 @@ function detectGitRepository() {
1561
1888
  function detectGitBranch() {
1562
1889
  try {
1563
1890
  const branch = execSync("git branch --show-current", {
1564
- encoding: "utf-8"
1891
+ encoding: "utf-8",
1892
+ stdio: ["ignore", "pipe", "ignore"]
1565
1893
  }).trim();
1566
1894
  if (branch) {
1567
1895
  return branch;
@@ -1570,7 +1898,8 @@ function detectGitBranch() {
1570
1898
  }
1571
1899
  try {
1572
1900
  const branch = execSync("git rev-parse --abbrev-ref HEAD", {
1573
- encoding: "utf-8"
1901
+ encoding: "utf-8",
1902
+ stdio: ["ignore", "pipe", "ignore"]
1574
1903
  }).trim();
1575
1904
  return branch && branch !== "HEAD" ? branch : "main";
1576
1905
  } catch {
@@ -1588,6 +1917,9 @@ function isGitRepo() {
1588
1917
  }
1589
1918
  }
1590
1919
  function detectAiClient() {
1920
+ if (fs.existsSync(path.join(process.cwd(), ".codex"))) {
1921
+ return "codex";
1922
+ }
1591
1923
  if (fs.existsSync(path.join(homedir(), ".claude")) || process.env.CLAUDE_CODE) {
1592
1924
  return "claude-code";
1593
1925
  }
@@ -1598,6 +1930,9 @@ function detectAiClient() {
1598
1930
  if (fs.existsSync(claudeDesktopPath)) {
1599
1931
  return "claude-desktop";
1600
1932
  }
1933
+ if (fs.existsSync(path.join(homedir(), ".codex"))) {
1934
+ return "codex";
1935
+ }
1601
1936
  return "other";
1602
1937
  }
1603
1938
  function detectPipCommand() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-snipara",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Onboard Snipara Hosted MCP Context + Memory with auth plus optional local helper and runtime workflows",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -11,6 +11,8 @@
11
11
  "build": "tsup src/index.ts --format esm --dts --clean",
12
12
  "dev": "tsup src/index.ts --format esm --watch",
13
13
  "prepublishOnly": "npm run build",
14
+ "pretest": "pnpm build",
15
+ "test": "node --test test/*.test.cjs",
14
16
  "pack:smoke": "bash ../../scripts/smoke-node-package.sh packages/create-snipara create-snipara create-snipara --help",
15
17
  "type-check": "tsc --noEmit",
16
18
  "lint": "eslint src --ext .ts"
@@ -26,6 +28,9 @@
26
28
  "multi-agent",
27
29
  "memory",
28
30
  "claude-code",
31
+ "codex",
32
+ "chatgpt",
33
+ "openai",
29
34
  "cursor",
30
35
  "claude-desktop"
31
36
  ],