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 +140 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +645 -0
- package/package.json +59 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|