create-snipara 1.3.8 → 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.
- package/README.md +40 -6
- package/dist/index.js +200 -61
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# create-snipara
|
|
2
2
|
|
|
3
|
-
One-command onboarding
|
|
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
|
|
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:
|
|
@@ -104,11 +130,18 @@ npx create-snipara
|
|
|
104
130
|
|
|
105
131
|
The interactive flow now asks for:
|
|
106
132
|
|
|
107
|
-
- install profile
|
|
108
133
|
- project slug
|
|
109
|
-
- optional project id
|
|
110
134
|
- API key path: project key, team key, free signup, or add key later
|
|
111
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
|
|
112
145
|
- whether hooks should be generated
|
|
113
146
|
- whether to open GitHub repository automation when a GitHub remote is detected
|
|
114
147
|
- optional runtime provider config when `rlm-runtime` is installed
|
|
@@ -217,6 +250,7 @@ That keeps business truth review separate from agent onboarding.
|
|
|
217
250
|
npx create-snipara --profile hosted-only
|
|
218
251
|
npx create-snipara --profile hosted-companion
|
|
219
252
|
npx create-snipara --profile full-stack
|
|
253
|
+
npx create-snipara --advanced
|
|
220
254
|
|
|
221
255
|
# Runtime-only local execution
|
|
222
256
|
npx create-snipara --runtime-only
|
|
@@ -329,7 +363,7 @@ Best when the user only wants Hosted MCP Context + Memory.
|
|
|
329
363
|
### Full local stack
|
|
330
364
|
|
|
331
365
|
```bash
|
|
332
|
-
npx create-snipara --profile full-stack
|
|
366
|
+
npx create-snipara --profile full-stack --advanced
|
|
333
367
|
```
|
|
334
368
|
|
|
335
369
|
Best when the user also wants `snipara-companion` and `rlm-runtime`.
|
|
@@ -377,7 +411,7 @@ npx create-snipara print-config --json
|
|
|
377
411
|
When testing a locally packed tarball, use `npm exec --package` instead of `npx`, for example:
|
|
378
412
|
|
|
379
413
|
```bash
|
|
380
|
-
npm exec --package ./create-snipara-1.3.
|
|
414
|
+
npm exec --package ./create-snipara-1.3.9.tgz create-snipara -- --help
|
|
381
415
|
```
|
|
382
416
|
|
|
383
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.
|
|
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
|
}
|
|
@@ -198,6 +253,7 @@ async function gatherConfiguration(options) {
|
|
|
198
253
|
const detectedClient = detectAiClient();
|
|
199
254
|
const existingConfig = getExistingConfig();
|
|
200
255
|
const detectedGitRepo = detectGitRepository();
|
|
256
|
+
const showAdvancedPrompts = Boolean(options.advanced);
|
|
201
257
|
if (options.github && !detectedGitRepo) {
|
|
202
258
|
console.log(
|
|
203
259
|
chalk.yellow(
|
|
@@ -230,7 +286,7 @@ async function gatherConfiguration(options) {
|
|
|
230
286
|
}
|
|
231
287
|
const response = await prompts([
|
|
232
288
|
{
|
|
233
|
-
type: "select",
|
|
289
|
+
type: showAdvancedPrompts ? "select" : null,
|
|
234
290
|
name: "profile",
|
|
235
291
|
message: "Choose an install profile:",
|
|
236
292
|
initial: installProfileChoices().findIndex((choice) => choice.value === initialProfile),
|
|
@@ -244,7 +300,7 @@ async function gatherConfiguration(options) {
|
|
|
244
300
|
validate: (value) => value.length > 0 ? true : "Project slug is required (for example my-project)"
|
|
245
301
|
},
|
|
246
302
|
{
|
|
247
|
-
type: "text",
|
|
303
|
+
type: showAdvancedPrompts ? "text" : null,
|
|
248
304
|
name: "projectId",
|
|
249
305
|
message: "Project ID (optional, press Enter to skip):",
|
|
250
306
|
initial: options.projectId || detectedProjectId || ""
|
|
@@ -318,19 +374,19 @@ async function gatherConfiguration(options) {
|
|
|
318
374
|
]
|
|
319
375
|
},
|
|
320
376
|
{
|
|
321
|
-
type: "confirm",
|
|
377
|
+
type: showAdvancedPrompts ? "confirm" : null,
|
|
322
378
|
name: "installHooks",
|
|
323
379
|
message: "Generate local hooks when supported?",
|
|
324
380
|
initial: detectedClient === "claude-code"
|
|
325
381
|
},
|
|
326
382
|
{
|
|
327
|
-
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,
|
|
328
384
|
name: "configureGitHub",
|
|
329
385
|
message: detectedGitRepo ? `Open GitHub automation setup for ${detectedGitRepo.fullName} after local config?` : "Open GitHub automation setup after local config?",
|
|
330
386
|
initial: true
|
|
331
387
|
},
|
|
332
388
|
{
|
|
333
|
-
type: (_, values) => values.configureGitHub && detectedGitRepo ? "text" : null,
|
|
389
|
+
type: (_, values) => showAdvancedPrompts && values.configureGitHub && detectedGitRepo ? "text" : null,
|
|
334
390
|
name: "githubPath",
|
|
335
391
|
message: "GitHub path filter (optional, press Enter for Snipara defaults):",
|
|
336
392
|
initial: options.githubPath || ""
|
|
@@ -354,7 +410,7 @@ async function gatherConfiguration(options) {
|
|
|
354
410
|
}
|
|
355
411
|
const answers = buildAnswersFromProfile({
|
|
356
412
|
projectSlug: signupResult?.projectSlug || response.projectSlug,
|
|
357
|
-
projectId: signupResult?.projectId || response.projectId || "",
|
|
413
|
+
projectId: signupResult?.projectId || response.projectId || options.projectId || "",
|
|
358
414
|
apiKey: apiKey || "",
|
|
359
415
|
apiKeyType,
|
|
360
416
|
profile: response.profile || initialProfile,
|
|
@@ -837,15 +893,16 @@ Agent task lifecycle:
|
|
|
837
893
|
|
|
838
894
|
1. Start substantial work with \`rlm_recall\` and a targeted \`rlm_context_query\`.
|
|
839
895
|
2. Use \`rlm_context_query\` for docs, business context, client/project truth, architecture notes, runbooks, and narrative source material.
|
|
840
|
-
3.
|
|
841
|
-
4. Use \`
|
|
842
|
-
5.
|
|
843
|
-
6.
|
|
844
|
-
7. Use
|
|
845
|
-
8. Use
|
|
846
|
-
9.
|
|
847
|
-
10.
|
|
848
|
-
11.
|
|
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.
|
|
849
906
|
`;
|
|
850
907
|
}
|
|
851
908
|
function buildClaudeMdTemplate(answers) {
|
|
@@ -860,6 +917,7 @@ Use Snipara Hosted MCP before answering project-specific questions.
|
|
|
860
917
|
- Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
|
|
861
918
|
- Start substantial work with \`rlm_recall\` and a targeted \`rlm_context_query\`.
|
|
862
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.
|
|
863
921
|
- Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
|
|
864
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.
|
|
865
923
|
- Use RLM Runtime only when sandboxed execution or repeatable validation materially helps.
|
|
@@ -882,6 +940,7 @@ This workspace uses Snipara Hosted MCP for current project context, business/cli
|
|
|
882
940
|
- Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
|
|
883
941
|
- Use \`rlm_recall\` for durable decisions, preferences, and validated learnings.
|
|
884
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.
|
|
885
944
|
- Use \`rlm_get_chunk\` before relying on exact cited wording from reference-based results.
|
|
886
945
|
- Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
|
|
887
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.
|
|
@@ -1149,7 +1208,7 @@ function getClientGuidance(client) {
|
|
|
1149
1208
|
if (client === "codex") {
|
|
1150
1209
|
return [
|
|
1151
1210
|
"Codex should use the hosted MCP surface plus AGENTS.md as the canonical project instruction file.",
|
|
1152
|
-
"The generated .snipara/templates/codex-config.toml
|
|
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."
|
|
1153
1212
|
];
|
|
1154
1213
|
}
|
|
1155
1214
|
if (client === "chatgpt") {
|
|
@@ -1449,7 +1508,7 @@ async function runValidation(answers, includeHostedCheck) {
|
|
|
1449
1508
|
detail: answers.installHooks && answers.aiClient === "claude-code" ? detection.files.claudeDir ? "~/.claude detected" : "~/.claude missing" : "Hooks not requested"
|
|
1450
1509
|
});
|
|
1451
1510
|
if (includeHostedCheck && answers.apiKeyType !== "none" && !answers.apiKey.includes("YOUR_")) {
|
|
1452
|
-
checks.push(await
|
|
1511
|
+
checks.push(...await testHostedConnection(answers));
|
|
1453
1512
|
} else if (includeHostedCheck) {
|
|
1454
1513
|
checks.push({
|
|
1455
1514
|
name: "hosted_connection",
|
|
@@ -1488,48 +1547,119 @@ function printDoctorReport(detection, report) {
|
|
|
1488
1547
|
}
|
|
1489
1548
|
console.log("");
|
|
1490
1549
|
}
|
|
1491
|
-
async function
|
|
1492
|
-
const spinner = ora("Connecting to Snipara
|
|
1550
|
+
async function testHostedConnection(answers) {
|
|
1551
|
+
const spinner = ora("Connecting to Snipara Hosted MCP...").start();
|
|
1493
1552
|
try {
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
+
{
|
|
1512
1578
|
name: "hosted_connection",
|
|
1513
1579
|
status: "pass",
|
|
1514
|
-
detail: `
|
|
1515
|
-
}
|
|
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
|
+
});
|
|
1516
1599
|
}
|
|
1517
|
-
|
|
1518
|
-
spinner.warn(`Connection returned ${response.status}`);
|
|
1519
|
-
return {
|
|
1520
|
-
name: "hosted_connection",
|
|
1521
|
-
status: "warn",
|
|
1522
|
-
detail: `HTTP ${response.status}: ${errorText.slice(0, 120)}`
|
|
1523
|
-
};
|
|
1600
|
+
return checks;
|
|
1524
1601
|
} catch (error) {
|
|
1525
1602
|
spinner.warn(`Could not connect: ${error}`);
|
|
1526
|
-
return
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1603
|
+
return [
|
|
1604
|
+
{
|
|
1605
|
+
name: "hosted_connection",
|
|
1606
|
+
status: "warn",
|
|
1607
|
+
detail: "Could not reach Snipara Hosted MCP from this machine"
|
|
1608
|
+
}
|
|
1609
|
+
];
|
|
1531
1610
|
}
|
|
1532
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.`;
|
|
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"}`;
|
|
1662
|
+
}
|
|
1533
1663
|
function printSuccess(answers, report) {
|
|
1534
1664
|
console.log(
|
|
1535
1665
|
chalk.green(`
|
|
@@ -1572,6 +1702,11 @@ function printSuccess(answers, report) {
|
|
|
1572
1702
|
nextStep += 1;
|
|
1573
1703
|
};
|
|
1574
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
|
+
}
|
|
1575
1710
|
printNextStep("Open .snipara/companion/README.md for local commands and repair flow.");
|
|
1576
1711
|
printNextStep("Run create-snipara doctor anytime you want a fresh local validation report.");
|
|
1577
1712
|
if (answers.installCompanion) {
|
|
@@ -1697,7 +1832,7 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
|
|
|
1697
1832
|
if (detection.existingConfig.hasSniparaServer) {
|
|
1698
1833
|
return "hosted-only";
|
|
1699
1834
|
}
|
|
1700
|
-
return
|
|
1835
|
+
return "hosted-only";
|
|
1701
1836
|
}
|
|
1702
1837
|
function readApiKeyFromEnvFiles() {
|
|
1703
1838
|
const candidates = [".env.local", ".env"];
|
|
@@ -1705,7 +1840,7 @@ function readApiKeyFromEnvFiles() {
|
|
|
1705
1840
|
const filePath = path.join(process.cwd(), file);
|
|
1706
1841
|
if (!fs.existsSync(filePath)) continue;
|
|
1707
1842
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
1708
|
-
const match = content.match(/^SNIPARA_API_KEY=(.+)$/m);
|
|
1843
|
+
const match = content.match(/^(?:SNIPARA_API_KEY|RLM_API_KEY)=(.+)$/m);
|
|
1709
1844
|
if (match?.[1]) {
|
|
1710
1845
|
return match[1].trim();
|
|
1711
1846
|
}
|
|
@@ -1715,7 +1850,8 @@ function readApiKeyFromEnvFiles() {
|
|
|
1715
1850
|
function detectProjectSlug() {
|
|
1716
1851
|
try {
|
|
1717
1852
|
const remote = execSync("git remote get-url origin", {
|
|
1718
|
-
encoding: "utf-8"
|
|
1853
|
+
encoding: "utf-8",
|
|
1854
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1719
1855
|
}).trim();
|
|
1720
1856
|
const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
|
|
1721
1857
|
if (match) {
|
|
@@ -1728,7 +1864,8 @@ function detectProjectSlug() {
|
|
|
1728
1864
|
function detectGitRepository() {
|
|
1729
1865
|
try {
|
|
1730
1866
|
const remoteUrl = execSync("git remote get-url origin", {
|
|
1731
|
-
encoding: "utf-8"
|
|
1867
|
+
encoding: "utf-8",
|
|
1868
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1732
1869
|
}).trim();
|
|
1733
1870
|
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) || remoteUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
1734
1871
|
if (!match) {
|
|
@@ -1751,7 +1888,8 @@ function detectGitRepository() {
|
|
|
1751
1888
|
function detectGitBranch() {
|
|
1752
1889
|
try {
|
|
1753
1890
|
const branch = execSync("git branch --show-current", {
|
|
1754
|
-
encoding: "utf-8"
|
|
1891
|
+
encoding: "utf-8",
|
|
1892
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1755
1893
|
}).trim();
|
|
1756
1894
|
if (branch) {
|
|
1757
1895
|
return branch;
|
|
@@ -1760,7 +1898,8 @@ function detectGitBranch() {
|
|
|
1760
1898
|
}
|
|
1761
1899
|
try {
|
|
1762
1900
|
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
1763
|
-
encoding: "utf-8"
|
|
1901
|
+
encoding: "utf-8",
|
|
1902
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1764
1903
|
}).trim();
|
|
1765
1904
|
return branch && branch !== "HEAD" ? branch : "main";
|
|
1766
1905
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-snipara",
|
|
3
|
-
"version": "1.3.
|
|
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"
|