create-snipara 1.0.0

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.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # create-snipara
2
+
3
+ One-command setup for **Snipara MCP + RLM-Runtime** - context optimization, semantic memory, and safe code execution for AI agents.
4
+
5
+ ```bash
6
+ npx create-snipara
7
+ ```
8
+
9
+ ## What It Does
10
+
11
+ - **Installs snipara-mcp** - MCP server for context-optimized documentation queries
12
+ - **Installs rlm-runtime** - Safe code execution with Docker isolation
13
+ - **Configures .mcp.json** - Ready for Claude Code, Cursor, Claude Desktop
14
+ - **Sets up hooks** - Session memory automation (Claude Code)
15
+ - **Updates environment files** - Adds API key configuration
16
+
17
+ ## Interactive Setup
18
+
19
+ ```bash
20
+ npx create-snipara
21
+ ```
22
+
23
+ You'll be prompted for:
24
+
25
+ | Prompt | Description |
26
+ |--------|-------------|
27
+ | **Project slug** | Auto-detected from git remote or directory name |
28
+ | **Project ID** | Optional, for advanced use cases |
29
+ | **API key** | Your Snipara API key (or sign up for free) |
30
+ | **AI client** | Claude Code, Cursor, Claude Desktop, or other |
31
+ | **Packages** | Choose what to install |
32
+
33
+ ## Command Line Options
34
+
35
+ ```bash
36
+ # Use existing API key
37
+ npx create-snipara --api-key rlm_your_key_here
38
+
39
+ # Specify project slug
40
+ npx create-snipara --slug my-project
41
+
42
+ # Skip specific installations
43
+ npx create-snipara --skip-mcp # Skip snipara-mcp
44
+ npx create-snipara --skip-runtime # Skip rlm-runtime
45
+ npx create-snipara --skip-hooks # Skip Claude Code hooks
46
+
47
+ # Accept all defaults
48
+ npx create-snipara -y
49
+
50
+ # Full example
51
+ npx create-snipara --api-key rlm_xxx --slug my-project --skip-hooks
52
+ ```
53
+
54
+ ## What Gets Created
55
+
56
+ ### .mcp.json
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "snipara": {
62
+ "type": "http",
63
+ "url": "https://api.snipara.com/mcp/your-project",
64
+ "headers": {
65
+ "X-API-Key": "rlm_your_key"
66
+ }
67
+ },
68
+ "rlm-runtime": {
69
+ "type": "http",
70
+ "url": "http://localhost:8765/mcp",
71
+ "headers": {}
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### Claude Code Hooks (if selected)
78
+
79
+ - `snipara-startup.sh` - Restores session context
80
+ - `snipara-session.sh` - Auto-remembers commits
81
+ - `snipara-compact.sh` - Saves context before compaction
82
+
83
+ ### Environment Files
84
+
85
+ Updates `.env.example` and `.env.local` with:
86
+
87
+ ```bash
88
+ SNIPARA_API_KEY=your_api_key
89
+ SNIPARA_PROJECT_SLUG=your-project
90
+ ```
91
+
92
+ ## After Installation
93
+
94
+ ### For Claude Code / Cursor
95
+
96
+ 1. Restart your AI client
97
+ 2. MCP tools are automatically available
98
+
99
+ ### For Claude Desktop
100
+
101
+ 1. Restart Claude Desktop
102
+ 2. Config is at `~/Library/Application Support/Claude/claude_desktop_config.json`
103
+
104
+ ### RLM-Runtime Commands
105
+
106
+ ```bash
107
+ rlm init # Initialize configuration
108
+ rlm run --env docker # Run with Docker isolation
109
+ rlm serve # Start MCP server
110
+ rlm visualize # Launch trajectory dashboard
111
+ ```
112
+
113
+ ## Available MCP Tools
114
+
115
+ After setup, you have access to 43+ MCP tools:
116
+
117
+ | Category | Tools |
118
+ |----------|-------|
119
+ | **Context** | `rlm_context_query`, `rlm_ask`, `rlm_search`, `rlm_sections` |
120
+ | **Planning** | `rlm_plan`, `rlm_decompose`, `rlm_multi_query` |
121
+ | **Memory** | `rlm_remember`, `rlm_recall`, `rlm_memories`, `rlm_forget` |
122
+ | **Execution** | `execute_python`, `rlm_agent_run` (via RLM-Runtime) |
123
+ | **Swarms** | `rlm_swarm_create`, `rlm_claim`, `rlm_task_create` |
124
+
125
+ ## Requirements
126
+
127
+ - Node.js 18+
128
+ - Python 3.10+ (for snipara-mcp and rlm-runtime)
129
+ - Docker (optional, for RLM-Runtime isolation)
130
+
131
+ ## Links
132
+
133
+ - **Website**: https://snipara.com
134
+ - **Documentation**: https://snipara.com/docs
135
+ - **Dashboard**: https://snipara.com/dashboard
136
+ - **Support**: hello@snipara.com
137
+
138
+ ## License
139
+
140
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,645 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { program } from "commander";
5
+ import prompts from "prompts";
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { execSync } from "child_process";
11
+ import { homedir } from "os";
12
+ var VERSION = "1.0.0";
13
+ var BASE_URL = "https://www.snipara.com";
14
+ var API_URL = "https://api.snipara.com";
15
+ async function main() {
16
+ program.name("create-snipara").description("Initialize Snipara MCP + RLM-Runtime with a single command").version(VERSION).option("--api-key <key>", "Snipara API key (project or team)").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID (optional, for advanced use)").option("--skip-mcp", "Skip snipara-mcp installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hooks installation").option("--skip-test", "Skip connection test").option("-y, --yes", "Accept defaults without prompting").parse();
17
+ const options = program.opts();
18
+ printBanner();
19
+ const answers = await gatherConfiguration(options);
20
+ if (answers.installMcp || answers.installRuntime) {
21
+ await installPythonPackages(answers);
22
+ }
23
+ await createMcpConfig(answers);
24
+ if (answers.installHooks && answers.aiClient === "claude-code") {
25
+ await installClaudeCodeHooks(answers);
26
+ }
27
+ await updateEnvFiles(answers);
28
+ if (!options.skipTest && answers.apiKey && !answers.apiKey.includes("YOUR_")) {
29
+ await testConnection(answers);
30
+ }
31
+ printSuccess(answers);
32
+ }
33
+ function printBanner() {
34
+ console.log(
35
+ chalk.cyan(`
36
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
37
+ \u2551 \u2551
38
+ \u2551 ${chalk.bold("Snipara")} - Context Optimization + RLM-Runtime \u2551
39
+ \u2551 \u2551
40
+ \u2551 \u2022 90% context reduction for LLM queries \u2551
41
+ \u2551 \u2022 Semantic memory across sessions \u2551
42
+ \u2551 \u2022 Safe code execution with Docker isolation \u2551
43
+ \u2551 \u2022 Multi-agent coordination \u2551
44
+ \u2551 \u2551
45
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
46
+ `)
47
+ );
48
+ }
49
+ async function gatherConfiguration(options) {
50
+ console.log(chalk.bold("\n\u{1F4CB} Project Configuration\n"));
51
+ let detectedSlug = detectProjectSlug();
52
+ let detectedProjectId = "";
53
+ const detectedClient = detectAiClient();
54
+ const existingConfig = getExistingConfig();
55
+ if (existingConfig) {
56
+ console.log(chalk.dim(" Found existing .mcp.json configuration"));
57
+ detectedSlug = existingConfig.slug || detectedSlug;
58
+ detectedProjectId = existingConfig.projectId || "";
59
+ }
60
+ if (options.yes) {
61
+ return {
62
+ projectSlug: options.slug || detectedSlug || "my-project",
63
+ projectId: options.projectId || detectedProjectId,
64
+ apiKey: options.apiKey || "YOUR_API_KEY_HERE",
65
+ installMcp: !options.skipMcp,
66
+ installRuntime: !options.skipRuntime,
67
+ installHooks: !options.skipHooks && detectedClient === "claude-code",
68
+ aiClient: detectedClient
69
+ };
70
+ }
71
+ const response = await prompts([
72
+ {
73
+ type: "text",
74
+ name: "projectSlug",
75
+ message: "Project slug:",
76
+ initial: options.slug || detectedSlug || "",
77
+ validate: (v) => v.length > 0 ? true : "Project slug is required (e.g., my-project)"
78
+ },
79
+ {
80
+ type: "text",
81
+ name: "projectId",
82
+ message: "Project ID (optional, press Enter to skip):",
83
+ initial: options.projectId || detectedProjectId || ""
84
+ },
85
+ {
86
+ type: options.apiKey ? null : "select",
87
+ name: "authMethod",
88
+ message: "How would you like to authenticate?",
89
+ choices: [
90
+ { title: "I have a Snipara API key", value: "existing" },
91
+ { title: "Sign up for free (100 queries/mo)", value: "signup" },
92
+ { title: "I'll add the API key later", value: "skip" }
93
+ ]
94
+ },
95
+ {
96
+ type: (prev, values) => values.authMethod === "existing" ? "text" : null,
97
+ name: "apiKey",
98
+ message: "Enter your API key:",
99
+ validate: (v) => v.startsWith("rlm_") ? true : "API key should start with rlm_"
100
+ },
101
+ {
102
+ type: "select",
103
+ name: "aiClient",
104
+ message: "Which AI client are you using?",
105
+ initial: ["claude-code", "cursor", "claude-desktop", "other"].indexOf(
106
+ detectedClient
107
+ ),
108
+ choices: [
109
+ {
110
+ title: "Claude Code (CLI/VS Code extension)",
111
+ value: "claude-code",
112
+ description: "Full hooks support"
113
+ },
114
+ {
115
+ title: "Cursor",
116
+ value: "cursor",
117
+ description: "MCP support via .mcp.json"
118
+ },
119
+ {
120
+ title: "Claude Desktop",
121
+ value: "claude-desktop",
122
+ description: "MCP support via config file"
123
+ },
124
+ { title: "Other", value: "other" }
125
+ ]
126
+ },
127
+ {
128
+ type: "multiselect",
129
+ name: "packages",
130
+ message: "What would you like to install?",
131
+ choices: [
132
+ {
133
+ title: "snipara-mcp",
134
+ value: "mcp",
135
+ selected: !options.skipMcp,
136
+ description: "MCP server for context queries"
137
+ },
138
+ {
139
+ title: "rlm-runtime",
140
+ value: "runtime",
141
+ selected: !options.skipRuntime,
142
+ description: "Safe code execution with Docker"
143
+ },
144
+ {
145
+ title: "Claude Code hooks",
146
+ value: "hooks",
147
+ selected: !options.skipHooks,
148
+ description: "Session memory & automation (Claude Code only)"
149
+ }
150
+ ],
151
+ hint: "Space to toggle, Enter to confirm"
152
+ }
153
+ ]);
154
+ let apiKey = options.apiKey;
155
+ if (response.authMethod === "signup") {
156
+ apiKey = await handleSignup();
157
+ } else if (response.authMethod === "existing") {
158
+ apiKey = response.apiKey;
159
+ } else if (response.authMethod === "skip") {
160
+ apiKey = "YOUR_API_KEY_HERE";
161
+ }
162
+ const packages = response.packages || [];
163
+ return {
164
+ projectSlug: response.projectSlug,
165
+ projectId: response.projectId || "",
166
+ apiKey: apiKey || "YOUR_API_KEY_HERE",
167
+ installMcp: packages.includes("mcp"),
168
+ installRuntime: packages.includes("runtime"),
169
+ installHooks: packages.includes("hooks"),
170
+ aiClient: response.aiClient || detectedClient
171
+ };
172
+ }
173
+ async function handleSignup() {
174
+ const signupUrl = `${BASE_URL}/signup`;
175
+ console.log(chalk.cyan(`
176
+ Opening ${signupUrl} in your browser...
177
+ `));
178
+ try {
179
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
180
+ execSync(`${openCmd} "${signupUrl}"`, { stdio: "ignore" });
181
+ } catch {
182
+ console.log(
183
+ chalk.yellow(` Could not open browser. Please visit:
184
+ ${signupUrl}
185
+ `)
186
+ );
187
+ }
188
+ console.log(
189
+ chalk.dim(
190
+ " After signing up, create a project and copy your API key.\n"
191
+ )
192
+ );
193
+ const response = await prompts({
194
+ type: "text",
195
+ name: "key",
196
+ message: "Paste your API key here:",
197
+ validate: (v) => v.startsWith("rlm_") ? true : "API key should start with rlm_"
198
+ });
199
+ return response.key;
200
+ }
201
+ async function installPythonPackages(answers) {
202
+ console.log(chalk.bold("\n\u{1F4E6} Installing Python Packages\n"));
203
+ const pipCmd = detectPipCommand();
204
+ if (!pipCmd) {
205
+ console.log(
206
+ chalk.yellow(
207
+ " \u26A0\uFE0F Python pip not found. Please install packages manually:\n"
208
+ )
209
+ );
210
+ if (answers.installMcp) {
211
+ console.log(chalk.gray(" pip install snipara-mcp"));
212
+ }
213
+ if (answers.installRuntime) {
214
+ console.log(chalk.gray(" pip install rlm-runtime[all]"));
215
+ }
216
+ console.log("");
217
+ return;
218
+ }
219
+ const packages = [];
220
+ if (answers.installMcp) {
221
+ packages.push("snipara-mcp");
222
+ }
223
+ if (answers.installRuntime) {
224
+ packages.push("rlm-runtime[all]");
225
+ }
226
+ if (packages.length === 0) return;
227
+ const spinner = ora(`Installing ${packages.join(", ")}...`).start();
228
+ try {
229
+ const installCmd = pipCmd === "uv" ? `uv pip install ${packages.join(" ")}` : `${pipCmd} install ${packages.join(" ")}`;
230
+ execSync(installCmd, {
231
+ stdio: "pipe",
232
+ encoding: "utf-8"
233
+ });
234
+ spinner.succeed(`Installed ${packages.join(", ")}`);
235
+ } catch (error) {
236
+ spinner.fail(`Failed to install packages`);
237
+ console.log(chalk.yellow("\n Install manually with:"));
238
+ console.log(chalk.gray(` pip install ${packages.join(" ")}
239
+ `));
240
+ }
241
+ }
242
+ async function createMcpConfig(answers) {
243
+ console.log(chalk.bold("\n\u2699\uFE0F Creating Configuration\n"));
244
+ const mcpConfigPath = path.join(process.cwd(), ".mcp.json");
245
+ let mcpConfig = { mcpServers: {} };
246
+ if (fs.existsSync(mcpConfigPath)) {
247
+ try {
248
+ mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
249
+ console.log(chalk.dim(" Found existing .mcp.json, merging..."));
250
+ } catch {
251
+ console.log(
252
+ chalk.yellow(" Could not parse existing .mcp.json, creating new...")
253
+ );
254
+ }
255
+ }
256
+ mcpConfig.mcpServers["snipara"] = {
257
+ type: "http",
258
+ url: `${API_URL}/mcp/${answers.projectSlug}`,
259
+ headers: {
260
+ "X-API-Key": answers.apiKey
261
+ }
262
+ };
263
+ if (answers.installRuntime) {
264
+ mcpConfig.mcpServers["rlm-runtime"] = {
265
+ type: "http",
266
+ url: "http://localhost:8765/mcp",
267
+ headers: {}
268
+ };
269
+ }
270
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
271
+ console.log(chalk.green(" \u2713 Created .mcp.json"));
272
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
273
+ if (fs.existsSync(gitignorePath)) {
274
+ const gitignore = fs.readFileSync(gitignorePath, "utf-8");
275
+ if (!gitignore.includes(".mcp.json")) {
276
+ fs.appendFileSync(
277
+ gitignorePath,
278
+ "\n# Snipara MCP config (contains API key)\n.mcp.json\n"
279
+ );
280
+ console.log(chalk.green(" \u2713 Added .mcp.json to .gitignore"));
281
+ }
282
+ } else {
283
+ fs.writeFileSync(
284
+ gitignorePath,
285
+ "# Snipara MCP config (contains API key)\n.mcp.json\n"
286
+ );
287
+ console.log(chalk.green(" \u2713 Created .gitignore with .mcp.json"));
288
+ }
289
+ if (answers.aiClient === "claude-desktop") {
290
+ await createClaudeDesktopConfig(answers);
291
+ }
292
+ }
293
+ async function createClaudeDesktopConfig(answers) {
294
+ const configDir = process.platform === "darwin" ? path.join(homedir(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path.join(homedir(), "AppData", "Roaming", "Claude") : path.join(homedir(), ".config", "claude");
295
+ const configPath = path.join(configDir, "claude_desktop_config.json");
296
+ console.log(chalk.dim(`
297
+ Claude Desktop config: ${configPath}`));
298
+ try {
299
+ if (!fs.existsSync(configDir)) {
300
+ fs.mkdirSync(configDir, { recursive: true });
301
+ }
302
+ let config = {};
303
+ if (fs.existsSync(configPath)) {
304
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
305
+ }
306
+ config.mcpServers = config.mcpServers || {};
307
+ config.mcpServers["snipara"] = {
308
+ command: "uvx",
309
+ args: ["snipara-mcp"],
310
+ env: {
311
+ SNIPARA_API_KEY: answers.apiKey,
312
+ SNIPARA_PROJECT_SLUG: answers.projectSlug
313
+ }
314
+ };
315
+ if (answers.installRuntime) {
316
+ config.mcpServers["rlm-runtime"] = {
317
+ command: "rlm",
318
+ args: ["serve"],
319
+ env: {
320
+ SNIPARA_API_KEY: answers.apiKey,
321
+ SNIPARA_PROJECT_SLUG: answers.projectSlug
322
+ }
323
+ };
324
+ }
325
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
326
+ console.log(chalk.green(" \u2713 Updated Claude Desktop config"));
327
+ } catch (error) {
328
+ console.log(
329
+ chalk.yellow(` \u26A0\uFE0F Could not update Claude Desktop config: ${error}`)
330
+ );
331
+ }
332
+ }
333
+ async function installClaudeCodeHooks(answers) {
334
+ console.log(chalk.bold("\n\u{1FA9D} Installing Claude Code Hooks\n"));
335
+ const claudeDir = path.join(homedir(), ".claude");
336
+ const hooksDir = path.join(claudeDir, "hooks");
337
+ if (!fs.existsSync(claudeDir)) {
338
+ console.log(
339
+ chalk.yellow(
340
+ " \u26A0\uFE0F ~/.claude not found. Install Claude Code first, then run:\n"
341
+ )
342
+ );
343
+ console.log(chalk.gray(" create-snipara --skip-mcp --skip-runtime\n"));
344
+ return;
345
+ }
346
+ if (!fs.existsSync(hooksDir)) {
347
+ fs.mkdirSync(hooksDir, { recursive: true });
348
+ }
349
+ const hooks = [
350
+ {
351
+ name: "snipara-startup.sh",
352
+ event: "PreToolUse",
353
+ content: createStartupHook(answers)
354
+ },
355
+ {
356
+ name: "snipara-session.sh",
357
+ event: "PostToolUse",
358
+ content: createSessionHook(answers)
359
+ },
360
+ {
361
+ name: "snipara-compact.sh",
362
+ event: "PreCompact",
363
+ content: createCompactHook(answers)
364
+ }
365
+ ];
366
+ for (const hook of hooks) {
367
+ const hookPath = path.join(hooksDir, hook.name);
368
+ fs.writeFileSync(hookPath, hook.content);
369
+ fs.chmodSync(hookPath, "755");
370
+ console.log(chalk.green(` \u2713 Created ${hook.name}`));
371
+ }
372
+ const settingsPath = path.join(claudeDir, "settings.json");
373
+ let settings = {};
374
+ if (fs.existsSync(settingsPath)) {
375
+ try {
376
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
377
+ } catch {
378
+ }
379
+ }
380
+ settings.hooks = settings.hooks || {};
381
+ const hooksConfig = settings.hooks;
382
+ const hookEntries = [
383
+ {
384
+ event: "PreToolUse",
385
+ script: path.join(hooksDir, "snipara-startup.sh"),
386
+ matcher: { tool_name: "TodoWrite" }
387
+ },
388
+ {
389
+ event: "PostToolUse",
390
+ script: path.join(hooksDir, "snipara-session.sh"),
391
+ matcher: { tool_name: "Bash" }
392
+ },
393
+ {
394
+ event: "PreCompact",
395
+ script: path.join(hooksDir, "snipara-compact.sh")
396
+ }
397
+ ];
398
+ for (const entry of hookEntries) {
399
+ const eventHooks = hooksConfig[entry.event] || [];
400
+ const exists = eventHooks.some(
401
+ (h) => typeof h === "object" && h !== null && "script" in h && h.script === entry.script
402
+ );
403
+ if (!exists) {
404
+ eventHooks.push(entry);
405
+ hooksConfig[entry.event] = eventHooks;
406
+ }
407
+ }
408
+ settings.hooks = hooksConfig;
409
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
410
+ console.log(chalk.green(" \u2713 Updated ~/.claude/settings.json"));
411
+ }
412
+ function createStartupHook(answers) {
413
+ return `#!/bin/bash
414
+ # Snipara Startup Hook - Restores session context
415
+ # Event: PreToolUse (TodoWrite)
416
+
417
+ export SNIPARA_API_KEY="${answers.apiKey}"
418
+ export SNIPARA_PROJECT_SLUG="${answers.projectSlug}"
419
+
420
+ # Recall recent context on first TodoWrite
421
+ if [ -z "$SNIPARA_SESSION_STARTED" ]; then
422
+ export SNIPARA_SESSION_STARTED=1
423
+
424
+ # Use snipara-mcp to recall context
425
+ if command -v snipara-mcp &> /dev/null; then
426
+ snipara-mcp recall --query "session progress recent work" --limit 3 2>/dev/null || true
427
+ fi
428
+ fi
429
+
430
+ exit 0
431
+ `;
432
+ }
433
+ function createSessionHook(answers) {
434
+ return `#!/bin/bash
435
+ # Snipara Session Hook - Auto-remembers significant commits
436
+ # Event: PostToolUse (Bash)
437
+
438
+ export SNIPARA_API_KEY="${answers.apiKey}"
439
+ export SNIPARA_PROJECT_SLUG="${answers.projectSlug}"
440
+
441
+ # Check if this was a git commit
442
+ if echo "$TOOL_INPUT" | grep -q "git commit"; then
443
+ COMMIT_MSG=$(git log -1 --pretty=%B 2>/dev/null || echo "")
444
+
445
+ if [ -n "$COMMIT_MSG" ]; then
446
+ if command -v snipara-mcp &> /dev/null; then
447
+ snipara-mcp remember --type "context" --content "Committed: $COMMIT_MSG" --ttl 7 2>/dev/null || true
448
+ fi
449
+ fi
450
+ fi
451
+
452
+ exit 0
453
+ `;
454
+ }
455
+ function createCompactHook(answers) {
456
+ return `#!/bin/bash
457
+ # Snipara Compact Hook - Saves session context before compaction
458
+ # Event: PreCompact
459
+
460
+ export SNIPARA_API_KEY="${answers.apiKey}"
461
+ export SNIPARA_PROJECT_SLUG="${answers.projectSlug}"
462
+
463
+ # Save session context before compaction
464
+ if command -v snipara-mcp &> /dev/null; then
465
+ # Extract key context from conversation (simplified)
466
+ snipara-mcp remember --type "context" --content "Session compacted. Resume point saved." --ttl 7 2>/dev/null || true
467
+ fi
468
+
469
+ exit 0
470
+ `;
471
+ }
472
+ async function updateEnvFiles(answers) {
473
+ console.log(chalk.bold("\n\u{1F4DD} Updating Environment Files\n"));
474
+ const envExamplePath = path.join(process.cwd(), ".env.example");
475
+ const envLocalPath = path.join(process.cwd(), ".env.local");
476
+ const envPath = path.join(process.cwd(), ".env");
477
+ const envVars = `
478
+ # Snipara Configuration
479
+ SNIPARA_API_KEY=${answers.apiKey.includes("YOUR_") ? "your_api_key_here" : answers.apiKey}
480
+ SNIPARA_PROJECT_SLUG=${answers.projectSlug}
481
+ ${answers.projectId ? `SNIPARA_PROJECT_ID=${answers.projectId}` : ""}
482
+ `.trim();
483
+ if (fs.existsSync(envExamplePath)) {
484
+ const content = fs.readFileSync(envExamplePath, "utf-8");
485
+ if (!content.includes("SNIPARA_API_KEY")) {
486
+ fs.appendFileSync(envExamplePath, `
487
+
488
+ ${envVars}
489
+ `);
490
+ console.log(chalk.green(" \u2713 Updated .env.example"));
491
+ }
492
+ } else {
493
+ fs.writeFileSync(envExamplePath, envVars + "\n");
494
+ console.log(chalk.green(" \u2713 Created .env.example"));
495
+ }
496
+ const targetEnvPath = fs.existsSync(envLocalPath) ? envLocalPath : envPath;
497
+ if (fs.existsSync(targetEnvPath)) {
498
+ const content = fs.readFileSync(targetEnvPath, "utf-8");
499
+ if (!content.includes("SNIPARA_API_KEY") && !answers.apiKey.includes("YOUR_")) {
500
+ fs.appendFileSync(targetEnvPath, `
501
+
502
+ ${envVars}
503
+ `);
504
+ console.log(chalk.green(` \u2713 Updated ${path.basename(targetEnvPath)}`));
505
+ }
506
+ }
507
+ }
508
+ async function testConnection(answers) {
509
+ console.log(chalk.bold("\n\u{1F50C} Testing Connection\n"));
510
+ const spinner = ora("Connecting to Snipara API...").start();
511
+ try {
512
+ const response = await fetch(`${API_URL}/mcp/${answers.projectSlug}`, {
513
+ method: "POST",
514
+ headers: {
515
+ "Content-Type": "application/json",
516
+ "X-API-Key": answers.apiKey
517
+ },
518
+ body: JSON.stringify({
519
+ jsonrpc: "2.0",
520
+ id: 1,
521
+ method: "tools/list",
522
+ params: {}
523
+ })
524
+ });
525
+ if (response.ok) {
526
+ const data = await response.json();
527
+ const toolCount = data.result?.tools?.length || 0;
528
+ spinner.succeed(`Connected! ${toolCount} MCP tools available.`);
529
+ } else {
530
+ const errorText = await response.text();
531
+ spinner.warn(`Connection returned ${response.status}: ${errorText}`);
532
+ }
533
+ } catch (error) {
534
+ spinner.warn(`Could not connect: ${error}`);
535
+ console.log(
536
+ chalk.dim(
537
+ "\n This is normal if you haven't created the project yet."
538
+ )
539
+ );
540
+ console.log(chalk.dim(` Create your project at: ${BASE_URL}/dashboard
541
+ `));
542
+ }
543
+ }
544
+ function printSuccess(answers) {
545
+ console.log(
546
+ chalk.green(`
547
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
548
+ \u2551 \u2551
549
+ \u2551 ${chalk.bold("\u2713 Setup Complete!")} \u2551
550
+ \u2551 \u2551
551
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
552
+ `)
553
+ );
554
+ console.log(chalk.bold("What was installed:\n"));
555
+ if (answers.installMcp) {
556
+ console.log(chalk.green(" \u2713 snipara-mcp") + chalk.dim(" - MCP server for context queries"));
557
+ }
558
+ if (answers.installRuntime) {
559
+ console.log(chalk.green(" \u2713 rlm-runtime") + chalk.dim(" - Safe code execution with Docker"));
560
+ }
561
+ if (answers.installHooks) {
562
+ console.log(chalk.green(" \u2713 Claude Code hooks") + chalk.dim(" - Session memory automation"));
563
+ }
564
+ console.log(chalk.green(" \u2713 .mcp.json") + chalk.dim(" - MCP server configuration"));
565
+ console.log(chalk.bold("\nNext steps:\n"));
566
+ if (answers.apiKey.includes("YOUR_")) {
567
+ console.log(` 1. Get your API key at ${chalk.cyan(`${BASE_URL}/signup`)}`);
568
+ console.log(" 2. Update SNIPARA_API_KEY in .mcp.json");
569
+ console.log(" 3. Restart your AI client");
570
+ } else {
571
+ console.log(" 1. Restart your AI client (Claude Code, Cursor, etc.)");
572
+ console.log(` 2. Upload docs at ${chalk.cyan(`${BASE_URL}/dashboard`)}`);
573
+ console.log(" 3. Start using MCP tools like rlm_context_query");
574
+ }
575
+ if (answers.installRuntime) {
576
+ console.log(chalk.bold("\nRLM-Runtime commands:\n"));
577
+ console.log(chalk.dim(" rlm init # Initialize configuration"));
578
+ console.log(chalk.dim(" rlm run --env docker # Run with Docker isolation"));
579
+ console.log(chalk.dim(" rlm serve # Start MCP server"));
580
+ console.log(chalk.dim(" rlm visualize # Launch trajectory dashboard"));
581
+ }
582
+ console.log(chalk.dim(`
583
+ Docs: ${BASE_URL}/docs`));
584
+ console.log(chalk.dim(" Support: hello@snipara.com\n"));
585
+ }
586
+ function detectProjectSlug() {
587
+ try {
588
+ const remote = execSync("git remote get-url origin 2>/dev/null", {
589
+ encoding: "utf-8"
590
+ }).trim();
591
+ const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
592
+ if (match) {
593
+ return match[1].toLowerCase().replace(/[^a-z0-9-]/g, "-");
594
+ }
595
+ } catch {
596
+ }
597
+ return path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
598
+ }
599
+ function detectAiClient() {
600
+ if (fs.existsSync(path.join(homedir(), ".claude")) || process.env.CLAUDE_CODE) {
601
+ return "claude-code";
602
+ }
603
+ if (fs.existsSync(path.join(homedir(), ".cursor")) || process.env.CURSOR_EDITOR) {
604
+ return "cursor";
605
+ }
606
+ const claudeDesktopPath = process.platform === "darwin" ? path.join(homedir(), "Library", "Application Support", "Claude") : path.join(homedir(), "AppData", "Roaming", "Claude");
607
+ if (fs.existsSync(claudeDesktopPath)) {
608
+ return "claude-desktop";
609
+ }
610
+ return "other";
611
+ }
612
+ function detectPipCommand() {
613
+ const commands = ["uv", "pip3", "pip"];
614
+ for (const cmd of commands) {
615
+ try {
616
+ execSync(`${cmd} --version`, { stdio: "pipe" });
617
+ return cmd;
618
+ } catch {
619
+ }
620
+ }
621
+ return null;
622
+ }
623
+ function getExistingConfig() {
624
+ const mcpConfigPath = path.join(process.cwd(), ".mcp.json");
625
+ if (fs.existsSync(mcpConfigPath)) {
626
+ try {
627
+ const config = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
628
+ const sniparaServer = config.mcpServers?.snipara;
629
+ if (sniparaServer?.url) {
630
+ const match = sniparaServer.url.match(/\/mcp\/([^/]+)$/);
631
+ return {
632
+ slug: match?.[1],
633
+ projectId: ""
634
+ // Could extract from headers if stored
635
+ };
636
+ }
637
+ } catch {
638
+ }
639
+ }
640
+ return null;
641
+ }
642
+ main().catch((error) => {
643
+ console.error(chalk.red("Error:"), error.message);
644
+ process.exit(1);
645
+ });
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "create-snipara",
3
+ "version": "1.0.0",
4
+ "description": "Initialize Snipara MCP + RLM-Runtime with a single command - context optimization, semantic memory, and safe code execution",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-snipara": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm --dts --clean",
12
+ "dev": "tsup src/index.ts --format esm --watch",
13
+ "prepublishOnly": "npm run build",
14
+ "type-check": "tsc --noEmit",
15
+ "lint": "eslint src --ext .ts"
16
+ },
17
+ "keywords": [
18
+ "snipara",
19
+ "create-snipara",
20
+ "mcp",
21
+ "rlm-runtime",
22
+ "context-optimization",
23
+ "llm",
24
+ "ai-agents",
25
+ "multi-agent",
26
+ "memory",
27
+ "claude-code",
28
+ "cursor",
29
+ "claude-desktop"
30
+ ],
31
+ "author": "Snipara",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Snipara/create-snipara.git"
36
+ },
37
+ "homepage": "https://snipara.com/docs/getting-started",
38
+ "dependencies": {
39
+ "chalk": "^5.3.0",
40
+ "commander": "^12.1.0",
41
+ "ora": "^8.0.1",
42
+ "prompts": "^2.4.2"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.17.12",
46
+ "@types/prompts": "^2.4.9",
47
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
48
+ "@typescript-eslint/parser": "^7.18.0",
49
+ "eslint": "^8.57.1",
50
+ "tsup": "^8.3.5",
51
+ "typescript": "^5.7.2"
52
+ },
53
+ "engines": {
54
+ "node": ">=18"
55
+ },
56
+ "files": [
57
+ "dist"
58
+ ]
59
+ }