create-snipara 2.0.17 → 2.1.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 +63 -13
- package/dist/index.js +407 -102
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-snipara
|
|
2
2
|
|
|
3
|
-
**One-command onboarding for Snipara
|
|
3
|
+
**One-command onboarding for Snipara Cloud or self-hosted Snipara Server.**
|
|
4
4
|
|
|
5
5
|
`create-snipara` connects a local project to Snipara so AI agents can start
|
|
6
6
|
with project-owned context, reviewed memory, shared guidance, and optional code
|
|
@@ -10,10 +10,30 @@ impact tooling instead of starting cold every session.
|
|
|
10
10
|
npx create-snipara
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
For a local or private server, use the same activation flow:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export SNIPARA_LOCAL_API_KEY="your-server-operator-key"
|
|
17
|
+
npx create-snipara@latest --self-hosted \
|
|
18
|
+
--server-url http://localhost:8000/mcp/local \
|
|
19
|
+
--api-key "$SNIPARA_LOCAL_API_KEY" \
|
|
20
|
+
--client claude-code
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Both modes aim to finish with the same visible proof:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
✓ Endpoint connected
|
|
27
|
+
✓ Repository README/docs seeded
|
|
28
|
+
✓ First project-grounded answer verified
|
|
29
|
+
✓ AI client configuration written
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`create-snipara` is the single activation engine for the agent path. It detects
|
|
33
|
+
or asks where Snipara should run, writes the right MCP config, seeds a small
|
|
34
|
+
README/docs corpus, and automatically verifies a first project-grounded answer.
|
|
35
|
+
The default path is Cloud because it is the fastest first experience; the same
|
|
36
|
+
flow works with an existing self-hosted Snipara Server.
|
|
17
37
|
It is also the canonical activation engine behind editor setup docs: VS Code can
|
|
18
38
|
surface activation as a native command, Cursor can pair OpenVSX UI with Hosted
|
|
19
39
|
MCP, but both paths should land on the same project context.
|
|
@@ -25,7 +45,7 @@ behind the scenes.
|
|
|
25
45
|
|
|
26
46
|
Use it for:
|
|
27
47
|
|
|
28
|
-
-
|
|
48
|
+
- Snipara Cloud or self-hosted MCP onboarding for Claude Code, Cursor, Kimi Code CLI, Codex, Gemini, Mistral, VS Code/Copilot, Continue, ChatGPT Desktop, Orca-style MCP clients, LM Studio-backed local stacks, and similar clients
|
|
29
49
|
- free account signup with reviewed Context + Memory
|
|
30
50
|
- local project wiring, `doctor`, `repair`, and `upgrade`
|
|
31
51
|
- local source activation without GitHub, plus optional GitHub repository sync and PR Answer Packs approval when the current folder is a GitHub repo
|
|
@@ -40,22 +60,22 @@ normalizes that setup.
|
|
|
40
60
|
```mermaid
|
|
41
61
|
flowchart LR
|
|
42
62
|
Project["Local project"] --> CLI["npx create-snipara"]
|
|
43
|
-
CLI --> Auth["
|
|
44
|
-
CLI --> MCP["
|
|
63
|
+
CLI --> Auth["Cloud auth or self-hosted operator key"]
|
|
64
|
+
CLI --> MCP["Configured MCP endpoint"]
|
|
45
65
|
CLI --> Templates["AGENTS.md, CLAUDE.md, Cursor, Codex, MCP references"]
|
|
46
66
|
MCP --> Agents["Claude Code, Cursor, Codex, Gemini, Mistral, VS Code, ChatGPT, Orca, LM Studio stacks"]
|
|
47
67
|
Templates --> Agents
|
|
48
68
|
Agents --> Context["Snipara context and memory"]
|
|
49
69
|
```
|
|
50
70
|
|
|
51
|
-
The CLI does not become the runtime brain. After setup, the agent talks to
|
|
52
|
-
|
|
71
|
+
The CLI does not become the runtime brain. After setup, the agent talks to the
|
|
72
|
+
configured MCP endpoint and keeps using its own LLM.
|
|
53
73
|
|
|
54
74
|
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`.
|
|
55
75
|
|
|
56
76
|
The product surfaces remain:
|
|
57
77
|
|
|
58
|
-
-
|
|
78
|
+
- Snipara Cloud or self-hosted Snipara Server for LLM agents, context, and reviewed memory
|
|
59
79
|
- HTTP API / SDK for apps, integrators, backends, and pipelines
|
|
60
80
|
|
|
61
81
|
Advanced flags can wire local helpers around those surfaces:
|
|
@@ -67,6 +87,24 @@ Advanced flags can wire local helpers around those surfaces:
|
|
|
67
87
|
- local config, env files, hook scripts, and a project companion pack
|
|
68
88
|
- `snipara-mcp` only for local stdio compatibility, development, or testing
|
|
69
89
|
|
|
90
|
+
## Deployment Modes
|
|
91
|
+
|
|
92
|
+
The deployment mode and install profile are independent:
|
|
93
|
+
|
|
94
|
+
| Mode | Command | Data boundary | First proof |
|
|
95
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------ |
|
|
96
|
+
| Snipara Cloud | `npx create-snipara@latest` | Snipara-managed hosted project | Cloud MCP connection, README/docs seed, grounded answer |
|
|
97
|
+
| Self-hosted Snipara Server | `npx create-snipara@latest --self-hosted --server-url http://localhost:8000/mcp/local --api-key "$SNIPARA_LOCAL_API_KEY"` | Your server and storage | Server contract, MCP connection, README/docs seed, grounded answer |
|
|
98
|
+
|
|
99
|
+
Self-hosted runs use the server's `X-API-Key` operator key, the public
|
|
100
|
+
`snipara-server-oss-v2` contract, and the server's `rlm_*` compatibility tools.
|
|
101
|
+
They never open Cloud signup or call the Cloud dashboard automation API.
|
|
102
|
+
|
|
103
|
+
Every successful live setup writes `.snipara/activation/activation-manifest.json`
|
|
104
|
+
with the selected endpoint, seed status, proof tool, and a non-sensitive proof
|
|
105
|
+
receipt such as `answer_pack_chars=...`. Raw API keys are never included in the
|
|
106
|
+
activation report or JSON summary.
|
|
107
|
+
|
|
70
108
|
## Install Profiles
|
|
71
109
|
|
|
72
110
|
The CLI now works around explicit install profiles:
|
|
@@ -199,11 +237,13 @@ npx create-snipara
|
|
|
199
237
|
|
|
200
238
|
The interactive flow now asks for:
|
|
201
239
|
|
|
240
|
+
- deployment mode when `--advanced` is used
|
|
241
|
+
- a self-hosted MCP endpoint when self-hosted mode is selected
|
|
202
242
|
- project slug
|
|
203
|
-
- API key path:
|
|
243
|
+
- API key path: Cloud key/signup, self-hosted operator key, or add key later
|
|
204
244
|
- AI client
|
|
205
245
|
|
|
206
|
-
By default, this uses the minimal `hosted-only` setup:
|
|
246
|
+
By default, this uses the minimal `hosted-only` setup: configured MCP config and a
|
|
207
247
|
doctor report, with no local Companion install, hooks, GitHub approval prompt, or
|
|
208
248
|
sandbox prompt. Add `--advanced` when you want profile selection, project id
|
|
209
249
|
entry, hook prompts, GitHub automation prompts, or sandbox provider prompts.
|
|
@@ -414,9 +454,19 @@ npx create-snipara --runtime-only
|
|
|
414
454
|
# Safer non-interactive setup with an existing API key
|
|
415
455
|
SNIPARA_API_KEY=snp-xxx npx create-snipara -y --slug my-project
|
|
416
456
|
|
|
457
|
+
# Self-hosted non-interactive setup; the same command works with any MCP client
|
|
458
|
+
SNIPARA_LOCAL_API_KEY=local-operator-key npx create-snipara -y \
|
|
459
|
+
--self-hosted --server-url http://localhost:8000/mcp/local \
|
|
460
|
+
--api-key "$SNIPARA_LOCAL_API_KEY" --slug my-project
|
|
461
|
+
|
|
417
462
|
# Compatibility path; prints a warning because CLI args can leak
|
|
418
463
|
npx create-snipara -y --api-key snp-xxx --slug my-project
|
|
419
464
|
|
|
465
|
+
# Explicitly choose the deployment mode
|
|
466
|
+
npx create-snipara -y --deployment hosted --api-key snp-xxx --slug my-project
|
|
467
|
+
npx create-snipara -y --deployment self-hosted \
|
|
468
|
+
--server-url http://localhost:8000/mcp/local --api-key local-operator-key
|
|
469
|
+
|
|
420
470
|
# Open GitHub automation after setup when a GitHub remote is detected
|
|
421
471
|
npx create-snipara --github
|
|
422
472
|
npx create-snipara --github-path docs/
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command, Option } from "commander";
|
|
|
5
5
|
import prompts from "prompts";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import ora from "ora";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
8
9
|
import fs from "fs";
|
|
9
10
|
import path from "path";
|
|
10
11
|
import { execSync, spawn } from "child_process";
|
|
@@ -13,8 +14,8 @@ import { homedir } from "os";
|
|
|
13
14
|
// package.json
|
|
14
15
|
var package_default = {
|
|
15
16
|
name: "create-snipara",
|
|
16
|
-
version: "2.0
|
|
17
|
-
description: "Onboard Snipara
|
|
17
|
+
version: "2.1.0",
|
|
18
|
+
description: "Onboard Snipara Cloud or self-hosted MCP with an automatic project-grounded proof of value",
|
|
18
19
|
main: "dist/index.js",
|
|
19
20
|
type: "module",
|
|
20
21
|
bin: {
|
|
@@ -52,7 +53,11 @@ var package_default = {
|
|
|
52
53
|
"lm-studio",
|
|
53
54
|
"vscode",
|
|
54
55
|
"continue",
|
|
55
|
-
"claude-desktop"
|
|
56
|
+
"claude-desktop",
|
|
57
|
+
"self-hosted",
|
|
58
|
+
"snipara-server",
|
|
59
|
+
"project-intelligence",
|
|
60
|
+
"persistent-memory"
|
|
56
61
|
],
|
|
57
62
|
author: "Snipara",
|
|
58
63
|
license: "MIT",
|
|
@@ -134,7 +139,7 @@ var DECISION_COMMIT_TERMS = [
|
|
|
134
139
|
"switch"
|
|
135
140
|
];
|
|
136
141
|
function buildActivationId() {
|
|
137
|
-
return `act_${Date.now().toString(36)}_${
|
|
142
|
+
return `act_${Date.now().toString(36)}_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
138
143
|
}
|
|
139
144
|
function getActivationDir() {
|
|
140
145
|
return path.join(process.cwd(), ".snipara", "activation");
|
|
@@ -1188,7 +1193,7 @@ function createInitialFirstWorkBrief(answers, detection, scan) {
|
|
|
1188
1193
|
}
|
|
1189
1194
|
function createActivationManifest(answers, detection, mode = "default", scan) {
|
|
1190
1195
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1191
|
-
const
|
|
1196
|
+
const hasMcpConfig = answers.profile !== "runtime-only";
|
|
1192
1197
|
const hasCompanion = answers.installCompanion;
|
|
1193
1198
|
const localScan = scan ?? detectLocalActivationScan(detection);
|
|
1194
1199
|
const dayZeroQuery = "What is this project, what is currently in flight, and what should an agent know before making the first edit?";
|
|
@@ -1196,6 +1201,8 @@ function createActivationManifest(answers, detection, mode = "default", scan) {
|
|
|
1196
1201
|
return {
|
|
1197
1202
|
activationId: buildActivationId(),
|
|
1198
1203
|
mode,
|
|
1204
|
+
deployment: answers.deployment,
|
|
1205
|
+
serverUrl: answers.serverUrl,
|
|
1199
1206
|
surfaceContract,
|
|
1200
1207
|
consumerContract: buildEditorActivationConsumerContract(answers.aiClient),
|
|
1201
1208
|
activationMetric: {
|
|
@@ -1204,15 +1211,22 @@ function createActivationManifest(answers, detection, mode = "default", scan) {
|
|
|
1204
1211
|
proof: "A correct, non-obvious statement about this repository from the first answer pack."
|
|
1205
1212
|
},
|
|
1206
1213
|
dayZeroQuery: {
|
|
1207
|
-
tool: "snipara_context_query",
|
|
1214
|
+
tool: answers.deployment === "self-hosted" ? "rlm_context_query" : "snipara_context_query",
|
|
1208
1215
|
query: dayZeroQuery,
|
|
1209
1216
|
expectedOutcome: "First own-repo answer pack before the user reads product docs."
|
|
1210
1217
|
},
|
|
1211
1218
|
dayZeroCorpus: createDayZeroCorpusReport(
|
|
1212
1219
|
"queued",
|
|
1213
1220
|
collectDayZeroCorpusDocuments(localScan),
|
|
1214
|
-
|
|
1221
|
+
`README and docs Markdown will be submitted to ${getDeploymentLabel(answers.deployment)} before the first day-zero query when live checks are enabled.`
|
|
1215
1222
|
),
|
|
1223
|
+
valueProof: {
|
|
1224
|
+
status: "skipped",
|
|
1225
|
+
deployment: answers.deployment,
|
|
1226
|
+
query: dayZeroQuery,
|
|
1227
|
+
detail: "Waiting for the configured MCP connection and source seeding.",
|
|
1228
|
+
observedAt: now
|
|
1229
|
+
},
|
|
1216
1230
|
agentReadiness: buildAgentReadinessReport(localScan),
|
|
1217
1231
|
contextEconomy: buildContextEconomyReport(localScan, dayZeroQuery),
|
|
1218
1232
|
projectSlug: answers.projectSlug,
|
|
@@ -1228,20 +1242,20 @@ function createActivationManifest(answers, detection, mode = "default", scan) {
|
|
|
1228
1242
|
localScan,
|
|
1229
1243
|
lanes: {
|
|
1230
1244
|
connection: createActivationLane(
|
|
1231
|
-
|
|
1232
|
-
|
|
1245
|
+
hasMcpConfig ? "ready" : "skipped",
|
|
1246
|
+
hasMcpConfig ? `${getDeploymentLabel(answers.deployment)} MCP config written.` : "Runtime-only profile skipped MCP config."
|
|
1233
1247
|
),
|
|
1234
1248
|
clientConfig: createActivationLane(
|
|
1235
|
-
|
|
1236
|
-
|
|
1249
|
+
hasMcpConfig ? "ready" : "skipped",
|
|
1250
|
+
hasMcpConfig ? `${getClientDisplayName(answers.aiClient)} project files generated.` : "No AI client config generated for runtime-only profile."
|
|
1237
1251
|
),
|
|
1238
1252
|
localSnapshot: createActivationLane(
|
|
1239
1253
|
hasCompanion ? "queued" : "skipped",
|
|
1240
1254
|
hasCompanion ? "Local source activation will create a repository snapshot." : "Install companion later to activate local source context."
|
|
1241
1255
|
),
|
|
1242
1256
|
contextIndex: createActivationLane(
|
|
1243
|
-
|
|
1244
|
-
hasCompanion ? "Context indexing starts from local docs and instruction files." :
|
|
1257
|
+
hasMcpConfig || hasCompanion ? "queued" : "skipped",
|
|
1258
|
+
hasCompanion ? "Context indexing starts from local docs and instruction files." : hasMcpConfig ? `Day-zero README/docs sync will seed ${getDeploymentLabel(answers.deployment)} when live checks run.` : "Context indexing requires companion or hosted provider sync."
|
|
1245
1259
|
),
|
|
1246
1260
|
codeGraph: createActivationLane(
|
|
1247
1261
|
hasCompanion ? "queued" : "skipped",
|
|
@@ -1360,7 +1374,7 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
|
1360
1374
|
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
1361
1375
|
"skipped",
|
|
1362
1376
|
[],
|
|
1363
|
-
"Runtime-only profile skipped
|
|
1377
|
+
"Runtime-only profile skipped MCP config and document sync."
|
|
1364
1378
|
);
|
|
1365
1379
|
updateActivationManifest(manifest);
|
|
1366
1380
|
return;
|
|
@@ -1370,7 +1384,7 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
|
1370
1384
|
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
1371
1385
|
"skipped",
|
|
1372
1386
|
documents2,
|
|
1373
|
-
|
|
1387
|
+
`Skipped because ${getDeploymentLabel(answers.deployment)} validation was disabled with --skip-test.`
|
|
1374
1388
|
);
|
|
1375
1389
|
updateActivationManifest(manifest);
|
|
1376
1390
|
return;
|
|
@@ -1379,7 +1393,7 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
|
1379
1393
|
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
1380
1394
|
"skipped",
|
|
1381
1395
|
[],
|
|
1382
|
-
|
|
1396
|
+
`Skipped because no live ${getDeploymentLabel(answers.deployment)} API key was available.`
|
|
1383
1397
|
);
|
|
1384
1398
|
updateActivationManifest(manifest);
|
|
1385
1399
|
return;
|
|
@@ -1389,18 +1403,18 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
|
1389
1403
|
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
1390
1404
|
"skipped",
|
|
1391
1405
|
[],
|
|
1392
|
-
|
|
1406
|
+
`No README or docs Markdown files were found to seed ${getDeploymentLabel(answers.deployment)}.`
|
|
1393
1407
|
);
|
|
1394
1408
|
if (manifest.lanes.contextIndex.status === "queued") {
|
|
1395
1409
|
manifest.lanes.contextIndex = createActivationLane(
|
|
1396
1410
|
"skipped",
|
|
1397
|
-
|
|
1411
|
+
`No README or docs Markdown files were found to seed ${getDeploymentLabel(answers.deployment)}.`
|
|
1398
1412
|
);
|
|
1399
1413
|
}
|
|
1400
1414
|
updateActivationManifest(manifest);
|
|
1401
1415
|
return;
|
|
1402
1416
|
}
|
|
1403
|
-
const syncTool = "snipara_sync_documents";
|
|
1417
|
+
const syncTool = answers.deployment === "self-hosted" ? "rlm_sync_documents" : "snipara_sync_documents";
|
|
1404
1418
|
const syncResponse = await callHostedMcp(answers, "tools/call", {
|
|
1405
1419
|
name: syncTool,
|
|
1406
1420
|
arguments: {
|
|
@@ -1412,18 +1426,18 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
|
1412
1426
|
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
1413
1427
|
"submitted",
|
|
1414
1428
|
documents,
|
|
1415
|
-
`Submitted ${documents.length} README/docs files to
|
|
1429
|
+
`Submitted ${documents.length} README/docs files to ${getDeploymentLabel(answers.deployment)} for the first day-zero answer pack.`,
|
|
1416
1430
|
{ tool: syncTool }
|
|
1417
1431
|
);
|
|
1418
1432
|
manifest.lanes.contextIndex = createActivationLane(
|
|
1419
1433
|
"indexing",
|
|
1420
|
-
|
|
1434
|
+
`Day-zero README/docs corpus submitted to ${getDeploymentLabel(answers.deployment)} indexing.`,
|
|
1421
1435
|
syncTool
|
|
1422
1436
|
);
|
|
1423
1437
|
updateActivationManifest(manifest);
|
|
1424
1438
|
return;
|
|
1425
1439
|
}
|
|
1426
|
-
const uploadTool = "snipara_upload_document";
|
|
1440
|
+
const uploadTool = answers.deployment === "self-hosted" ? "rlm_upload_document" : "snipara_upload_document";
|
|
1427
1441
|
const uploadErrors = [];
|
|
1428
1442
|
for (const document of documents) {
|
|
1429
1443
|
const uploadResponse = await callHostedMcp(answers, "tools/call", {
|
|
@@ -1441,12 +1455,12 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
|
1441
1455
|
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
1442
1456
|
"submitted",
|
|
1443
1457
|
documents,
|
|
1444
|
-
`Submitted ${documents.length} README/docs files to
|
|
1458
|
+
`Submitted ${documents.length} README/docs files to ${getDeploymentLabel(answers.deployment)} for the first day-zero answer pack.`,
|
|
1445
1459
|
{ tool: uploadTool }
|
|
1446
1460
|
);
|
|
1447
1461
|
manifest.lanes.contextIndex = createActivationLane(
|
|
1448
1462
|
"indexing",
|
|
1449
|
-
|
|
1463
|
+
`Day-zero README/docs corpus submitted to ${getDeploymentLabel(answers.deployment)} indexing.`,
|
|
1450
1464
|
uploadTool
|
|
1451
1465
|
);
|
|
1452
1466
|
} else {
|
|
@@ -1457,13 +1471,96 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
|
1457
1471
|
error: detail
|
|
1458
1472
|
});
|
|
1459
1473
|
manifest.lanes.contextIndex = failActivationLane(
|
|
1460
|
-
|
|
1474
|
+
`${getDeploymentLabel(answers.deployment)} day-zero README/docs sync failed; the first answer may need GitHub, companion, or a later sync.`,
|
|
1461
1475
|
syncTool,
|
|
1462
1476
|
new Error(detail)
|
|
1463
1477
|
);
|
|
1464
1478
|
}
|
|
1465
1479
|
updateActivationManifest(manifest);
|
|
1466
1480
|
}
|
|
1481
|
+
async function runDayZeroValueProof(answers, manifest, enabled) {
|
|
1482
|
+
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1483
|
+
if (!enabled || answers.profile === "runtime-only") {
|
|
1484
|
+
manifest.valueProof = {
|
|
1485
|
+
status: "skipped",
|
|
1486
|
+
deployment: answers.deployment,
|
|
1487
|
+
query: manifest.dayZeroQuery.query,
|
|
1488
|
+
detail: "Skipped because live MCP validation was disabled or the profile is runtime-only.",
|
|
1489
|
+
observedAt
|
|
1490
|
+
};
|
|
1491
|
+
updateActivationManifest(manifest);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (answers.apiKeyType === "none" || answers.apiKey.includes("YOUR_")) {
|
|
1495
|
+
manifest.valueProof = {
|
|
1496
|
+
status: "skipped",
|
|
1497
|
+
deployment: answers.deployment,
|
|
1498
|
+
query: manifest.dayZeroQuery.query,
|
|
1499
|
+
detail: "Skipped because no live API key was available.",
|
|
1500
|
+
observedAt
|
|
1501
|
+
};
|
|
1502
|
+
updateActivationManifest(manifest);
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
const queryTool = answers.deployment === "self-hosted" ? "rlm_context_query" : "snipara_context_query";
|
|
1506
|
+
const spinner = ora(`Running ${getDeploymentLabel(answers.deployment)} value proof...`).start();
|
|
1507
|
+
try {
|
|
1508
|
+
const response = await callHostedMcp(answers, "tools/call", {
|
|
1509
|
+
name: queryTool,
|
|
1510
|
+
arguments: {
|
|
1511
|
+
query: manifest.dayZeroQuery.query,
|
|
1512
|
+
max_tokens: 1200,
|
|
1513
|
+
search_mode: "keyword",
|
|
1514
|
+
return_references: true,
|
|
1515
|
+
auto_decompose: false
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
if (!response.ok || response.data.error) {
|
|
1519
|
+
const detail = response.ok ? describeJsonRpcError(response.data) : describeHostedHttpFailure(response.status, response.bodyText);
|
|
1520
|
+
throw new Error(detail);
|
|
1521
|
+
}
|
|
1522
|
+
const evidenceChars = extractMcpText(response.data).length;
|
|
1523
|
+
if (evidenceChars === 0) {
|
|
1524
|
+
throw new Error("The MCP query returned no answer content");
|
|
1525
|
+
}
|
|
1526
|
+
manifest.valueProof = {
|
|
1527
|
+
status: "ready",
|
|
1528
|
+
deployment: answers.deployment,
|
|
1529
|
+
query: manifest.dayZeroQuery.query,
|
|
1530
|
+
tool: queryTool,
|
|
1531
|
+
detail: "A non-empty project answer pack was returned from the configured MCP endpoint.",
|
|
1532
|
+
evidence: `answer_pack_chars=${evidenceChars}`,
|
|
1533
|
+
observedAt
|
|
1534
|
+
};
|
|
1535
|
+
spinner.succeed("Value proof ready: Snipara returned a project-grounded answer.");
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
const detail = error instanceof Error ? error.message : "unknown MCP value-proof error";
|
|
1538
|
+
manifest.valueProof = {
|
|
1539
|
+
status: "failed",
|
|
1540
|
+
deployment: answers.deployment,
|
|
1541
|
+
query: manifest.dayZeroQuery.query,
|
|
1542
|
+
tool: queryTool,
|
|
1543
|
+
detail: "The connection succeeded, but the first project answer could not be verified.",
|
|
1544
|
+
observedAt,
|
|
1545
|
+
error: detail.slice(0, 300)
|
|
1546
|
+
};
|
|
1547
|
+
spinner.warn(`Value proof needs attention: ${detail}`);
|
|
1548
|
+
}
|
|
1549
|
+
updateActivationManifest(manifest);
|
|
1550
|
+
}
|
|
1551
|
+
function extractMcpText(data) {
|
|
1552
|
+
const content = data.result?.content;
|
|
1553
|
+
if (!Array.isArray(content)) {
|
|
1554
|
+
return "";
|
|
1555
|
+
}
|
|
1556
|
+
return content.map((item) => {
|
|
1557
|
+
if (!item || typeof item !== "object") {
|
|
1558
|
+
return "";
|
|
1559
|
+
}
|
|
1560
|
+
const record = item;
|
|
1561
|
+
return typeof record.text === "string" ? record.text : "";
|
|
1562
|
+
}).filter(Boolean).join("\n");
|
|
1563
|
+
}
|
|
1467
1564
|
function createActivationArtifacts(answers, manifest) {
|
|
1468
1565
|
const projectBootstrap = buildProjectBootstrap(manifest.localScan);
|
|
1469
1566
|
writeProjectBootstrap(projectBootstrap);
|
|
@@ -1587,14 +1684,14 @@ function shouldInstallDefaultHooks(aiClient) {
|
|
|
1587
1684
|
}
|
|
1588
1685
|
function buildProgram() {
|
|
1589
1686
|
const program = new Command();
|
|
1590
|
-
program.name("create-snipara").description("
|
|
1687
|
+
program.name("create-snipara").description("Connect Snipara Cloud or a self-hosted Snipara Server with a fast proof of value").version(VERSION);
|
|
1591
1688
|
program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
1592
1689
|
"--client <client>",
|
|
1593
1690
|
"AI client: claude-code, cursor, kimi, codex, gemini, mistral, chatgpt, vscode, continue, claude-desktop, other"
|
|
1594
1691
|
).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
|
|
1595
1692
|
"--profile <profile>",
|
|
1596
1693
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
1597
|
-
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip
|
|
1694
|
+
).option("--deployment <deployment>", "Deployment: hosted or self-hosted").option("--self-hosted", "Use an existing self-hosted Snipara MCP server").option("--server-url <url>", "Self-hosted MCP endpoint URL").option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip MCP connection and value-proof checks").option("--with-orchestrator", "Install optional snipara-orchestrator package").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("--starter", "Mark this run as a starter activation and print dashboard-first guidance").option("--json", "Print a machine-readable activation summary after setup").option("--runtime-only", "Install only Snipara Sandbox").addOption(new Option("--full", "Legacy alias for --profile full-stack").hideHelp()).option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
|
|
1598
1695
|
printBanner();
|
|
1599
1696
|
await runInit(options);
|
|
1600
1697
|
});
|
|
@@ -1605,7 +1702,7 @@ function buildProgram() {
|
|
|
1605
1702
|
printBanner();
|
|
1606
1703
|
await runDoctor(options);
|
|
1607
1704
|
});
|
|
1608
|
-
program.command("repair").description("Rebuild local Snipara config and re-run validation").option("--api-key <key>", "Snipara API key override").option("--slug <slug>", "Project slug override").option("--project-id <id>", "Project ID override").option("--profile <profile>", "Target install profile").option("--with-hooks", "Force hook generation").option("--with-runtime", "Force Snipara Sandbox server/config").option("--with-mcp", "Force MCP config").option("--with-companion", "Force companion pack generation").option("--with-orchestrator", "Install optional snipara-orchestrator package").action(async (options) => {
|
|
1705
|
+
program.command("repair").description("Rebuild local Snipara config and re-run validation").option("--api-key <key>", "Snipara API key override").option("--slug <slug>", "Project slug override").option("--project-id <id>", "Project ID override").option("--profile <profile>", "Target install profile").option("--deployment <deployment>", "Deployment: hosted or self-hosted").option("--self-hosted", "Use an existing self-hosted Snipara MCP server").option("--server-url <url>", "Self-hosted MCP endpoint URL").option("--with-hooks", "Force hook generation").option("--with-runtime", "Force Snipara Sandbox server/config").option("--with-mcp", "Force MCP config").option("--with-companion", "Force companion pack generation").option("--with-orchestrator", "Install optional snipara-orchestrator package").action(async (options) => {
|
|
1609
1706
|
printBanner();
|
|
1610
1707
|
await runRepair(options);
|
|
1611
1708
|
});
|
|
@@ -1635,7 +1732,7 @@ function addSharedRootOptions(program) {
|
|
|
1635
1732
|
).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
|
|
1636
1733
|
"--profile <profile>",
|
|
1637
1734
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
1638
|
-
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip
|
|
1735
|
+
).option("--deployment <deployment>", "Deployment: hosted or self-hosted").option("--self-hosted", "Use an existing self-hosted Snipara MCP server").option("--server-url <url>", "Self-hosted MCP endpoint URL").option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip MCP connection and value-proof checks").option("--with-orchestrator", "Install optional snipara-orchestrator package").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("--starter", "Mark this run as a starter activation and print dashboard-first guidance").option("--json", "Print a machine-readable activation summary after setup").option("--runtime-only", "Install only Snipara Sandbox").addOption(new Option("--full", "Legacy alias for --profile full-stack").hideHelp()).option("-y, --yes", "Accept defaults without prompting");
|
|
1639
1736
|
}
|
|
1640
1737
|
async function main() {
|
|
1641
1738
|
const program = buildProgram();
|
|
@@ -1719,6 +1816,8 @@ function parseInitOptionsFromArgv(args) {
|
|
|
1719
1816
|
"--project-id": "projectId",
|
|
1720
1817
|
"--client": "client",
|
|
1721
1818
|
"--profile": "profile",
|
|
1819
|
+
"--deployment": "deployment",
|
|
1820
|
+
"--server-url": "serverUrl",
|
|
1722
1821
|
"--github-path": "githubPath"
|
|
1723
1822
|
};
|
|
1724
1823
|
const booleanFlags = {
|
|
@@ -1732,6 +1831,7 @@ function parseInitOptionsFromArgv(args) {
|
|
|
1732
1831
|
"--runtime-only": "runtimeOnly",
|
|
1733
1832
|
"--full": "full",
|
|
1734
1833
|
"--Full": "full",
|
|
1834
|
+
"--self-hosted": "selfHosted",
|
|
1735
1835
|
"--github": "github",
|
|
1736
1836
|
"--skip-github": "skipGithub",
|
|
1737
1837
|
"--starter": "starter",
|
|
@@ -1797,6 +1897,7 @@ async function runInit(options) {
|
|
|
1797
1897
|
activationScan
|
|
1798
1898
|
);
|
|
1799
1899
|
await runHostedDayZeroCorpusSync(answers, activationManifest, !normalizedOptions.skipTest);
|
|
1900
|
+
await runDayZeroValueProof(answers, activationManifest, !normalizedOptions.skipTest);
|
|
1800
1901
|
runLocalSourceActivation(answers, activationManifest);
|
|
1801
1902
|
createActivationArtifacts(answers, activationManifest);
|
|
1802
1903
|
printSuccess(answers, validationReport, activationManifest);
|
|
@@ -1816,14 +1917,17 @@ function buildActivationJsonSummary(manifest, validation) {
|
|
|
1816
1917
|
const laneEntries = Object.entries(manifest.lanes);
|
|
1817
1918
|
const failedLanes = laneEntries.filter(([, lane]) => lane.status === "failed").map(([name]) => name);
|
|
1818
1919
|
const pendingLanes = laneEntries.filter(([, lane]) => lane.status === "queued" || lane.status === "indexing").map(([name]) => name);
|
|
1819
|
-
const activationStatus = failedLanes.length > 0 ? "partial" : pendingLanes.length > 0 ? "partial" : "complete";
|
|
1920
|
+
const activationStatus = failedLanes.length > 0 || manifest.valueProof.status === "failed" ? "partial" : pendingLanes.length > 0 ? "partial" : "complete";
|
|
1820
1921
|
return {
|
|
1821
1922
|
ok: true,
|
|
1822
1923
|
activationStatus,
|
|
1823
1924
|
activationId: manifest.activationId,
|
|
1824
1925
|
projectSlug: manifest.projectSlug,
|
|
1926
|
+
deployment: manifest.deployment,
|
|
1927
|
+
serverUrl: manifest.serverUrl,
|
|
1825
1928
|
aiClient: manifest.aiClient,
|
|
1826
1929
|
profile: validation.profile,
|
|
1930
|
+
valueProof: manifest.valueProof,
|
|
1827
1931
|
artifacts: manifest.artifacts,
|
|
1828
1932
|
lanes: Object.fromEntries(
|
|
1829
1933
|
laneEntries.map(([name, lane]) => [
|
|
@@ -1981,8 +2085,8 @@ function printBanner() {
|
|
|
1981
2085
|
\u2551 \u2551
|
|
1982
2086
|
\u2551 ${chalk.bold("Snipara")} - Context + Memory onboarding \u2551
|
|
1983
2087
|
\u2551 \u2551
|
|
1984
|
-
\u2551 \u2022
|
|
1985
|
-
\u2551 \u2022
|
|
2088
|
+
\u2551 \u2022 Cloud or self-hosted MCP for LLM agents \u2551
|
|
2089
|
+
\u2551 \u2022 First project-grounded answer in minutes \u2551
|
|
1986
2090
|
\u2551 \u2022 First grounded answer before local workflow setup \u2551
|
|
1987
2091
|
\u2551 \u2022 API / SDK for product integrations \u2551
|
|
1988
2092
|
\u2551 \u2022 Optional advanced companion, hooks, and Snipara Sandbox helpers \u2551
|
|
@@ -2014,17 +2118,26 @@ async function gatherConfiguration(options) {
|
|
|
2014
2118
|
detectedProjectId = existingConfig.projectId || "";
|
|
2015
2119
|
}
|
|
2016
2120
|
const initialProfile = resolveRequestedProfile(options);
|
|
2017
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2121
|
+
const initialDeployment = resolveRequestedDeployment(options);
|
|
2122
|
+
const initialServerUrl = normalizeMcpEndpoint(
|
|
2123
|
+
options.serverUrl || process.env.SNIPARA_MCP_URL || getExistingConfig()?.serverUrl || "http://localhost:8000/mcp/local"
|
|
2124
|
+
);
|
|
2125
|
+
const defaultProjectSlug = options.slug || detectedSlug || (initialDeployment === "self-hosted" ? extractSlugFromMcpEndpoint(initialServerUrl) || "local" : "");
|
|
2126
|
+
const optionApiKey = options.apiKey?.trim();
|
|
2127
|
+
const environmentApiKey = options.yes ? initialDeployment === "self-hosted" ? process.env.SNIPARA_LOCAL_API_KEY?.trim() || process.env.SNIPARA_API_KEY?.trim() : process.env.SNIPARA_API_KEY?.trim() : void 0;
|
|
2128
|
+
const providedApiKey = optionApiKey || environmentApiKey;
|
|
2129
|
+
if (providedApiKey && !isValidApiKey(providedApiKey, initialDeployment)) {
|
|
2130
|
+
throw new Error(manualApiKeyValidationMessage(initialDeployment));
|
|
2131
|
+
}
|
|
2132
|
+
if (optionApiKey) {
|
|
2022
2133
|
warnApiKeyCliExposure();
|
|
2023
2134
|
}
|
|
2024
2135
|
if (options.yes) {
|
|
2025
2136
|
return buildAnswersFromProfile({
|
|
2026
|
-
projectSlug:
|
|
2137
|
+
projectSlug: defaultProjectSlug || "my-project",
|
|
2027
2138
|
projectId: options.projectId || detectedProjectId,
|
|
2139
|
+
deployment: initialDeployment,
|
|
2140
|
+
serverUrl: initialServerUrl,
|
|
2028
2141
|
apiKey: providedApiKey || "YOUR_API_KEY_HERE",
|
|
2029
2142
|
apiKeyType: providedApiKey ? "api" : "none",
|
|
2030
2143
|
profile: initialProfile,
|
|
@@ -2035,13 +2148,45 @@ async function gatherConfiguration(options) {
|
|
|
2035
2148
|
withOrchestrator: !!options.withOrchestrator,
|
|
2036
2149
|
skipHooks: !!options.skipHooks,
|
|
2037
2150
|
configureGitHub: Boolean(
|
|
2038
|
-
(options.github || options.githubPath) && detectedGitRepo && !options.skipGithub
|
|
2151
|
+
initialDeployment === "hosted" && (options.github || options.githubPath) && detectedGitRepo && !options.skipGithub
|
|
2039
2152
|
),
|
|
2040
2153
|
githubRepo: detectedGitRepo || void 0,
|
|
2041
2154
|
githubPath: options.githubPath
|
|
2042
2155
|
});
|
|
2043
2156
|
}
|
|
2044
2157
|
const response = await prompts([
|
|
2158
|
+
{
|
|
2159
|
+
type: showAdvancedPrompts ? "select" : null,
|
|
2160
|
+
name: "deployment",
|
|
2161
|
+
message: "Where should this project connect?",
|
|
2162
|
+
initial: initialDeployment === "self-hosted" ? 1 : 0,
|
|
2163
|
+
choices: [
|
|
2164
|
+
{
|
|
2165
|
+
title: "Snipara Cloud (fastest first experience)",
|
|
2166
|
+
value: "hosted",
|
|
2167
|
+
description: "Hosted MCP, account signup, and automatic day-zero proof"
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
title: "Existing self-hosted Snipara Server",
|
|
2171
|
+
value: "self-hosted",
|
|
2172
|
+
description: "Your MCP endpoint, operator API key, and local data boundary"
|
|
2173
|
+
}
|
|
2174
|
+
]
|
|
2175
|
+
},
|
|
2176
|
+
{
|
|
2177
|
+
type: (_, values) => initialDeployment === "self-hosted" || values.deployment === "self-hosted" ? "text" : null,
|
|
2178
|
+
name: "serverUrl",
|
|
2179
|
+
message: "Self-hosted MCP endpoint URL:",
|
|
2180
|
+
initial: options.serverUrl || process.env.SNIPARA_MCP_URL || initialServerUrl,
|
|
2181
|
+
validate: (value) => {
|
|
2182
|
+
try {
|
|
2183
|
+
normalizeMcpEndpoint(value);
|
|
2184
|
+
return true;
|
|
2185
|
+
} catch {
|
|
2186
|
+
return "Enter a valid http:// or https:// MCP endpoint URL";
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
},
|
|
2045
2190
|
{
|
|
2046
2191
|
type: showAdvancedPrompts ? "select" : null,
|
|
2047
2192
|
name: "profile",
|
|
@@ -2053,7 +2198,7 @@ async function gatherConfiguration(options) {
|
|
|
2053
2198
|
type: "text",
|
|
2054
2199
|
name: "projectSlug",
|
|
2055
2200
|
message: "Project slug:",
|
|
2056
|
-
initial:
|
|
2201
|
+
initial: defaultProjectSlug,
|
|
2057
2202
|
validate: (value) => value.length > 0 ? true : "Project slug is required (for example my-project)"
|
|
2058
2203
|
},
|
|
2059
2204
|
{
|
|
@@ -2065,8 +2210,18 @@ async function gatherConfiguration(options) {
|
|
|
2065
2210
|
{
|
|
2066
2211
|
type: (_, values) => values.profile === "runtime-only" || providedApiKey ? null : "select",
|
|
2067
2212
|
name: "apiKeyType",
|
|
2068
|
-
message: "How do you want to add your Snipara API key?",
|
|
2069
|
-
choices: [
|
|
2213
|
+
message: (_, values) => values.deployment === "self-hosted" || initialDeployment === "self-hosted" ? "How do you want to add the self-hosted operator API key?" : "How do you want to add your Snipara API key?",
|
|
2214
|
+
choices: (_, values) => values.deployment === "self-hosted" || initialDeployment === "self-hosted" ? [
|
|
2215
|
+
{
|
|
2216
|
+
title: "I have an operator API key",
|
|
2217
|
+
value: "api",
|
|
2218
|
+
description: "Use the X-API-Key configured for your server"
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
title: "I'll add the API key later",
|
|
2222
|
+
value: "skip"
|
|
2223
|
+
}
|
|
2224
|
+
] : [
|
|
2070
2225
|
{
|
|
2071
2226
|
title: "I have an API key",
|
|
2072
2227
|
value: "api",
|
|
@@ -2087,7 +2242,10 @@ async function gatherConfiguration(options) {
|
|
|
2087
2242
|
type: (_, values) => values.profile === "runtime-only" ? null : values.apiKeyType === "api" ? "text" : null,
|
|
2088
2243
|
name: "apiKey",
|
|
2089
2244
|
message: (_, values) => values.apiKeyType === "api" ? "Enter your Snipara API key:" : "Enter your API key:",
|
|
2090
|
-
validate: (value) =>
|
|
2245
|
+
validate: (value, values) => {
|
|
2246
|
+
const deployment = normalizeDeployment(values.deployment) || initialDeployment;
|
|
2247
|
+
return isValidApiKey(value, deployment) ? true : manualApiKeyValidationMessage(deployment);
|
|
2248
|
+
}
|
|
2091
2249
|
},
|
|
2092
2250
|
{
|
|
2093
2251
|
type: "select",
|
|
@@ -2126,11 +2284,15 @@ async function gatherConfiguration(options) {
|
|
|
2126
2284
|
initial: options.githubPath || ""
|
|
2127
2285
|
}
|
|
2128
2286
|
]);
|
|
2287
|
+
const selectedDeployment = normalizeDeployment(response.deployment) || initialDeployment;
|
|
2288
|
+
const selectedServerUrl = normalizeMcpEndpoint(
|
|
2289
|
+
options.serverUrl || response.serverUrl || initialServerUrl
|
|
2290
|
+
);
|
|
2129
2291
|
let apiKey = providedApiKey;
|
|
2130
2292
|
let apiKeyType = response.profile === "runtime-only" ? "none" : providedApiKey ? "api" : "none";
|
|
2131
2293
|
let signupResult = null;
|
|
2132
2294
|
if (!apiKey && response.profile !== "runtime-only") {
|
|
2133
|
-
if (response.apiKeyType === "signup") {
|
|
2295
|
+
if (response.apiKeyType === "signup" && selectedDeployment === "hosted") {
|
|
2134
2296
|
signupResult = await handleSignup({
|
|
2135
2297
|
projectHint: response.projectSlug,
|
|
2136
2298
|
repoHint: detectedGitRepo?.fullName
|
|
@@ -2148,6 +2310,8 @@ async function gatherConfiguration(options) {
|
|
|
2148
2310
|
const answers = buildAnswersFromProfile({
|
|
2149
2311
|
projectSlug: signupResult?.projectSlug || response.projectSlug,
|
|
2150
2312
|
projectId: signupResult?.projectId || response.projectId || options.projectId || "",
|
|
2313
|
+
deployment: selectedDeployment,
|
|
2314
|
+
serverUrl: selectedServerUrl,
|
|
2151
2315
|
apiKey: apiKey || "",
|
|
2152
2316
|
apiKeyType,
|
|
2153
2317
|
profile: response.profile || initialProfile,
|
|
@@ -2158,7 +2322,7 @@ async function gatherConfiguration(options) {
|
|
|
2158
2322
|
withOrchestrator: Boolean(options.withOrchestrator || response.installOrchestrator),
|
|
2159
2323
|
skipHooks: !!options.skipHooks || !(typeof response.installHooks === "boolean" ? response.installHooks : shouldInstallDefaultHooks(requestedClient || response.aiClient || detectedClient)),
|
|
2160
2324
|
configureGitHub: Boolean(
|
|
2161
|
-
(response.configureGitHub || options.github || options.githubPath) && detectedGitRepo && !options.skipGithub
|
|
2325
|
+
selectedDeployment === "hosted" && (response.configureGitHub || options.github || options.githubPath) && detectedGitRepo && !options.skipGithub
|
|
2162
2326
|
),
|
|
2163
2327
|
githubRepo: detectedGitRepo || void 0,
|
|
2164
2328
|
githubPath: response.githubPath || options.githubPath
|
|
@@ -2226,6 +2390,33 @@ function resolveRequestedProfile(options) {
|
|
|
2226
2390
|
}
|
|
2227
2391
|
return "hosted-only";
|
|
2228
2392
|
}
|
|
2393
|
+
function normalizeDeployment(value) {
|
|
2394
|
+
if (value === "hosted" || value === "self-hosted") {
|
|
2395
|
+
return value;
|
|
2396
|
+
}
|
|
2397
|
+
return null;
|
|
2398
|
+
}
|
|
2399
|
+
function resolveRequestedDeployment(options) {
|
|
2400
|
+
if (options.selfHosted || options.serverUrl) {
|
|
2401
|
+
return "self-hosted";
|
|
2402
|
+
}
|
|
2403
|
+
if (normalizeDeployment(options.deployment)) {
|
|
2404
|
+
return normalizeDeployment(options.deployment);
|
|
2405
|
+
}
|
|
2406
|
+
const existingEndpoint = getExistingConfig()?.serverUrl;
|
|
2407
|
+
return existingEndpoint && !existingEndpoint.startsWith(API_URL) ? "self-hosted" : "hosted";
|
|
2408
|
+
}
|
|
2409
|
+
function normalizeMcpEndpoint(value) {
|
|
2410
|
+
const normalized = value.trim().replace(/\/+$/, "");
|
|
2411
|
+
const parsed = new URL(normalized);
|
|
2412
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2413
|
+
throw new Error("MCP endpoint must use http:// or https://");
|
|
2414
|
+
}
|
|
2415
|
+
return normalized;
|
|
2416
|
+
}
|
|
2417
|
+
function getDeploymentLabel(deployment) {
|
|
2418
|
+
return deployment === "self-hosted" ? "self-hosted Snipara Server" : "Snipara Cloud";
|
|
2419
|
+
}
|
|
2229
2420
|
function isInstallProfile(value) {
|
|
2230
2421
|
return ["hosted-only", "hosted-companion", "full-stack", "runtime-only"].includes(value);
|
|
2231
2422
|
}
|
|
@@ -2321,6 +2512,8 @@ function buildAnswersFromProfile(input) {
|
|
|
2321
2512
|
return {
|
|
2322
2513
|
projectSlug: input.projectSlug,
|
|
2323
2514
|
projectId: input.projectId,
|
|
2515
|
+
deployment: input.deployment,
|
|
2516
|
+
serverUrl: input.deployment === "hosted" ? `${API_URL}/mcp/${encodeURIComponent(input.projectSlug)}` : normalizeMcpEndpoint(input.serverUrl),
|
|
2324
2517
|
apiKey: input.apiKey || "YOUR_API_KEY_HERE",
|
|
2325
2518
|
apiKeyType: input.profile === "runtime-only" ? "none" : input.apiKeyType,
|
|
2326
2519
|
profile: input.profile,
|
|
@@ -2495,17 +2688,20 @@ function runDemo(options) {
|
|
|
2495
2688
|
function sleep(ms) {
|
|
2496
2689
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2497
2690
|
}
|
|
2498
|
-
function
|
|
2691
|
+
function isValidApiKey(value, deployment) {
|
|
2499
2692
|
const trimmed = value.trim();
|
|
2693
|
+
if (deployment === "self-hosted") {
|
|
2694
|
+
return trimmed.length >= 8 && !/\s/.test(trimmed);
|
|
2695
|
+
}
|
|
2500
2696
|
return trimmed.startsWith("snp-");
|
|
2501
2697
|
}
|
|
2502
|
-
function manualApiKeyValidationMessage() {
|
|
2503
|
-
return "API key should start with snp-";
|
|
2698
|
+
function manualApiKeyValidationMessage(deployment = "hosted") {
|
|
2699
|
+
return deployment === "self-hosted" ? "Self-hosted API key should be at least 8 characters and contain no spaces" : "API key should start with snp-";
|
|
2504
2700
|
}
|
|
2505
2701
|
function warnApiKeyCliExposure() {
|
|
2506
2702
|
console.log(
|
|
2507
2703
|
chalk.yellow(
|
|
2508
|
-
" \u26A0\uFE0F --api-key can be exposed in shell history or process lists. Prefer SNIPARA_API_KEY or the interactive prompt for future runs."
|
|
2704
|
+
" \u26A0\uFE0F --api-key can be exposed in shell history or process lists. Prefer SNIPARA_API_KEY, SNIPARA_LOCAL_API_KEY, or the interactive prompt for future runs."
|
|
2509
2705
|
)
|
|
2510
2706
|
);
|
|
2511
2707
|
}
|
|
@@ -2788,7 +2984,7 @@ function readExistingCorrelationSessionId() {
|
|
|
2788
2984
|
}
|
|
2789
2985
|
function getCorrelationSessionId(answers) {
|
|
2790
2986
|
const existing = normalizeCorrelationSessionId(answers.correlationSessionId) || readExistingCorrelationSessionId();
|
|
2791
|
-
const sessionId = existing || `sess_${Date.now()}_${
|
|
2987
|
+
const sessionId = existing || `sess_${Date.now()}_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
2792
2988
|
answers.correlationSessionId = sessionId;
|
|
2793
2989
|
return sessionId;
|
|
2794
2990
|
}
|
|
@@ -2800,7 +2996,11 @@ async function createClaudeCodeConfig(answers) {
|
|
|
2800
2996
|
return;
|
|
2801
2997
|
}
|
|
2802
2998
|
console.log(chalk.bold("\n\u{1F916} Claude Code MCP\n"));
|
|
2803
|
-
console.log(
|
|
2999
|
+
console.log(
|
|
3000
|
+
chalk.green(
|
|
3001
|
+
` \u2713 Project .mcp.json contains the ${getDeploymentLabel(answers.deployment)} MCP server`
|
|
3002
|
+
)
|
|
3003
|
+
);
|
|
2804
3004
|
console.log(chalk.dim(" Claude Code loads project-scoped MCP servers from .mcp.json."));
|
|
2805
3005
|
console.log(
|
|
2806
3006
|
chalk.dim(
|
|
@@ -2921,8 +3121,14 @@ function writeIfMissing(filePath, content, label) {
|
|
|
2921
3121
|
fs.writeFileSync(filePath, content);
|
|
2922
3122
|
console.log(chalk.green(` Created ${label}`));
|
|
2923
3123
|
}
|
|
3124
|
+
function buildMcpEndpoint(answers) {
|
|
3125
|
+
if (answers.deployment === "self-hosted") {
|
|
3126
|
+
return answers.serverUrl;
|
|
3127
|
+
}
|
|
3128
|
+
return `${API_URL}/mcp/${encodeURIComponent(answers.projectSlug)}`;
|
|
3129
|
+
}
|
|
2924
3130
|
function buildHostedMcpEndpoint(answers) {
|
|
2925
|
-
return
|
|
3131
|
+
return buildMcpEndpoint(answers);
|
|
2926
3132
|
}
|
|
2927
3133
|
function writeAgentSkillPackTemplates(skillsDir, answers) {
|
|
2928
3134
|
const skills = buildSniparaSkillPackTemplates(answers);
|
|
@@ -3874,15 +4080,19 @@ async function updateEnvFiles(answers) {
|
|
|
3874
4080
|
const envPath = path.join(process.cwd(), ".env");
|
|
3875
4081
|
let envVars = "";
|
|
3876
4082
|
let envExampleVars = "";
|
|
3877
|
-
if (answers.
|
|
4083
|
+
if (answers.profile !== "runtime-only") {
|
|
3878
4084
|
envVars += `
|
|
3879
4085
|
# Snipara Configuration
|
|
4086
|
+
SNIPARA_DEPLOYMENT=${answers.deployment}
|
|
4087
|
+
SNIPARA_MCP_URL=${answers.serverUrl}
|
|
3880
4088
|
SNIPARA_API_KEY=${answers.apiKey.includes("YOUR_") ? "your_api_key_here" : answers.apiKey}
|
|
3881
4089
|
SNIPARA_PROJECT_SLUG=${answers.projectSlug}
|
|
3882
4090
|
${answers.projectId ? `SNIPARA_PROJECT_ID=${answers.projectId}` : ""}
|
|
3883
4091
|
`.trim();
|
|
3884
4092
|
envExampleVars += `
|
|
3885
4093
|
# Snipara Configuration
|
|
4094
|
+
SNIPARA_DEPLOYMENT=${answers.deployment}
|
|
4095
|
+
SNIPARA_MCP_URL=${answers.serverUrl}
|
|
3886
4096
|
SNIPARA_API_KEY=your_api_key_here
|
|
3887
4097
|
SNIPARA_PROJECT_SLUG=${answers.projectSlug || "your_project_slug"}
|
|
3888
4098
|
${answers.projectId ? `SNIPARA_PROJECT_ID=${answers.projectId}` : "SNIPARA_PROJECT_ID=your_project_id"}
|
|
@@ -3907,10 +4117,14 @@ ${llmVarName}=your_${answers.llmProvider}_api_key_here`;
|
|
|
3907
4117
|
}
|
|
3908
4118
|
if (envExampleVars && fs.existsSync(envExamplePath)) {
|
|
3909
4119
|
const content = fs.readFileSync(envExamplePath, "utf-8");
|
|
3910
|
-
|
|
4120
|
+
const missingLines = envExampleVars.split("\n").filter((line) => {
|
|
4121
|
+
const match = line.match(/^([A-Z0-9_]+)=/);
|
|
4122
|
+
return Boolean(match && !content.includes(`${match[1]}=`));
|
|
4123
|
+
});
|
|
4124
|
+
if (missingLines.length > 0) {
|
|
3911
4125
|
fs.appendFileSync(envExamplePath, `
|
|
3912
4126
|
|
|
3913
|
-
${
|
|
4127
|
+
${missingLines.join("\n")}
|
|
3914
4128
|
`);
|
|
3915
4129
|
console.log(chalk.green(" \u2713 Updated .env.example"));
|
|
3916
4130
|
}
|
|
@@ -3921,16 +4135,26 @@ ${envExampleVars}
|
|
|
3921
4135
|
const targetEnvPath = fs.existsSync(envLocalPath) ? envLocalPath : envPath;
|
|
3922
4136
|
if (fs.existsSync(targetEnvPath)) {
|
|
3923
4137
|
const content = fs.readFileSync(targetEnvPath, "utf-8");
|
|
3924
|
-
|
|
4138
|
+
const missingLines = envVars.split("\n").filter((line) => {
|
|
4139
|
+
const match = line.match(/^([A-Z0-9_]+)=/);
|
|
4140
|
+
if (!match) {
|
|
4141
|
+
return false;
|
|
4142
|
+
}
|
|
4143
|
+
if (match[1] === "SNIPARA_API_KEY") {
|
|
4144
|
+
return !content.includes("SNIPARA_API_KEY=") && !answers.apiKey.includes("YOUR_");
|
|
4145
|
+
}
|
|
4146
|
+
return !content.includes(`${match[1]}=`);
|
|
4147
|
+
});
|
|
4148
|
+
if (missingLines.length > 0) {
|
|
3925
4149
|
fs.appendFileSync(targetEnvPath, `
|
|
3926
4150
|
|
|
3927
|
-
${
|
|
4151
|
+
${missingLines.join("\n")}
|
|
3928
4152
|
`);
|
|
3929
4153
|
console.log(chalk.green(` \u2713 Updated ${path.basename(targetEnvPath)}`));
|
|
3930
4154
|
}
|
|
3931
4155
|
return;
|
|
3932
4156
|
}
|
|
3933
|
-
if (!answers.apiKey.includes("YOUR_") || answers.llmApiKey) {
|
|
4157
|
+
if (!answers.apiKey.includes("YOUR_") && answers.profile !== "runtime-only" || answers.llmApiKey) {
|
|
3934
4158
|
fs.writeFileSync(targetEnvPath, envVars + "\n");
|
|
3935
4159
|
console.log(chalk.green(` \u2713 Created ${path.basename(targetEnvPath)}`));
|
|
3936
4160
|
}
|
|
@@ -3946,7 +4170,7 @@ async function createCompanionConfig(answers) {
|
|
|
3946
4170
|
const workspaceConfig = {
|
|
3947
4171
|
...existingWorkspaceConfig,
|
|
3948
4172
|
apiKey: answers.apiKey,
|
|
3949
|
-
apiUrl: API_URL,
|
|
4173
|
+
apiUrl: answers.deployment === "hosted" ? API_URL : answers.serverUrl,
|
|
3950
4174
|
projectId: answers.projectId || answers.projectSlug,
|
|
3951
4175
|
sessionId: getCorrelationSessionId(answers),
|
|
3952
4176
|
client: getAutomationClientId(answers.aiClient)
|
|
@@ -3991,7 +4215,7 @@ async function installLocalCodeOverlayHooks(answers) {
|
|
|
3991
4215
|
}
|
|
3992
4216
|
}
|
|
3993
4217
|
async function syncRemoteAutomationClient(answers, options) {
|
|
3994
|
-
if (!options.enabled || answers.apiKeyType === "none" || !answers.apiKey || answers.apiKey.includes("YOUR_") || !answers.projectSlug) {
|
|
4218
|
+
if (answers.deployment !== "hosted" || !options.enabled || answers.apiKeyType === "none" || !answers.apiKey || answers.apiKey.includes("YOUR_") || !answers.projectSlug) {
|
|
3995
4219
|
return;
|
|
3996
4220
|
}
|
|
3997
4221
|
const automationClient = getAutomationClientId(answers.aiClient);
|
|
@@ -4035,7 +4259,7 @@ function buildActivationSurfaceContract(client) {
|
|
|
4035
4259
|
const shared = {
|
|
4036
4260
|
doctrine: "one_activation_engine_multiple_entry_points",
|
|
4037
4261
|
engine: "create-snipara",
|
|
4038
|
-
firstSuccessState: "
|
|
4262
|
+
firstSuccessState: "The configured Snipara endpoint connected, local workspace context was seeded, and a First Work Brief is ready for the next agent session.",
|
|
4039
4263
|
mustNotDuplicate: [
|
|
4040
4264
|
"Local source scan heuristics",
|
|
4041
4265
|
"First Work Brief generation",
|
|
@@ -4266,6 +4490,11 @@ function getCompanionCommands(answers) {
|
|
|
4266
4490
|
title: "Refresh local source context",
|
|
4267
4491
|
command: `${COMPANION_NPX_COMMAND} source sync --json`,
|
|
4268
4492
|
purpose: "Refresh the local snapshot, document dry-run, and code overlay cache after files change."
|
|
4493
|
+
},
|
|
4494
|
+
{
|
|
4495
|
+
title: "Generate a reviewable Project Brief",
|
|
4496
|
+
command: `${COMPANION_NPX_COMMAND} docs bootstrap --preview`,
|
|
4497
|
+
purpose: "Preview a safe, evidence-linked project brief when the repository has little or no documentation; apply it only after review."
|
|
4269
4498
|
}
|
|
4270
4499
|
] : [];
|
|
4271
4500
|
const businessOnboardingCommands = answers.installCompanion ? [
|
|
@@ -4472,6 +4701,8 @@ function getClientAutomationCommand(client) {
|
|
|
4472
4701
|
function buildClientPackMarkdown(answers, commands, guidance) {
|
|
4473
4702
|
const clientName = getClientDisplayName(answers.aiClient);
|
|
4474
4703
|
const automationCommand = getClientAutomationCommand(answers.aiClient);
|
|
4704
|
+
const deploymentLabel = getDeploymentLabel(answers.deployment);
|
|
4705
|
+
const toolNamespace = answers.deployment === "self-hosted" ? "rlm_*" : "snipara_*";
|
|
4475
4706
|
const bootstrapCommand = commands.find(
|
|
4476
4707
|
(command) => command.title === "Bootstrap local companion"
|
|
4477
4708
|
);
|
|
@@ -4491,9 +4722,9 @@ Use \`AGENTS.md\` as the shared source of agent behavior, then apply the client-
|
|
|
4491
4722
|
- Next surface action: ${surfaceContract.nextSurfaceAction}
|
|
4492
4723
|
|
|
4493
4724
|
The client-specific files in this pack should not reimplement local source scanning,
|
|
4494
|
-
First Work Brief generation, memory-candidate extraction, or
|
|
4725
|
+
First Work Brief generation, memory-candidate extraction, or MCP config semantics.
|
|
4495
4726
|
|
|
4496
|
-
##
|
|
4727
|
+
## ${deploymentLabel}
|
|
4497
4728
|
|
|
4498
4729
|
- Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
|
|
4499
4730
|
- Auth: \`SNIPARA_API_KEY\` from the environment or local client secret store
|
|
@@ -4506,7 +4737,7 @@ ${getClientConfigArtifacts(answers.aiClient).map((item) => `- \`${item}\``).join
|
|
|
4506
4737
|
## Recommended Setup
|
|
4507
4738
|
|
|
4508
4739
|
${bootstrapCommand ? `1. Bootstrap companion: \`${bootstrapCommand.command}\`` : `1. Bootstrap companion with \`${COMPANION_NPX_COMMAND} init\`.`}
|
|
4509
|
-
2. Confirm the client can list or call the \`
|
|
4740
|
+
2. Confirm the client can list or call the \`${toolNamespace}\` MCP tools.
|
|
4510
4741
|
3. Keep exact edits, tests, and dirty working-tree checks local; use Snipara for memory, source truth, and indexed code graph context.
|
|
4511
4742
|
${automationCommand ? `4. For generated local automations, run \`${automationCommand}\` when that client exposes usable automation files.` : "4. This client is treated as MCP-first; no local hook surface is assumed."}
|
|
4512
4743
|
|
|
@@ -4537,10 +4768,11 @@ async function createCompanionPack(answers) {
|
|
|
4537
4768
|
const guidance = [UNIVERSAL_CLIENT_GUIDANCE, ...getClientGuidance(answers.aiClient)];
|
|
4538
4769
|
const detection = detectEnvironment();
|
|
4539
4770
|
const clientPackFilename = getClientPackFilename(answers.aiClient);
|
|
4771
|
+
const deploymentLabel = getDeploymentLabel(answers.deployment);
|
|
4540
4772
|
fs.mkdirSync(packDir, { recursive: true });
|
|
4541
4773
|
const readme = `# Snipara Companion Pack
|
|
4542
4774
|
|
|
4543
|
-
This project uses
|
|
4775
|
+
This project uses ${deploymentLabel} as a project intelligence layer with source-backed context, reviewed memory, and local companion workflow helpers.
|
|
4544
4776
|
The activation doctrine is: one shared activation engine, multiple entry points.
|
|
4545
4777
|
\`create-snipara\` is the canonical bootstrap; editor extensions and plugins should provide UI, rules, or handoff affordances without forking the activation engine.
|
|
4546
4778
|
|
|
@@ -4552,7 +4784,7 @@ The activation doctrine is: one shared activation engine, multiple entry points.
|
|
|
4552
4784
|
- AI client: \`${answers.aiClient}\`
|
|
4553
4785
|
- Client pack: \`${clientPackFilename}\`
|
|
4554
4786
|
- Installed local companion: \`${answers.installCompanion ? "yes" : "no"}\`
|
|
4555
|
-
- Generated
|
|
4787
|
+
- Generated ${deploymentLabel} MCP config: \`${answers.profile !== "runtime-only" ? "yes" : "no"}\`
|
|
4556
4788
|
- Installed local snipara-mcp package: \`${answers.installMcp ? "yes" : "no"}\`
|
|
4557
4789
|
- Installed Snipara Sandbox: \`${answers.installRuntime ? "yes" : "no"}\`
|
|
4558
4790
|
- Installed Snipara Orchestrator: \`${answers.installOrchestrator ? "yes" : "no"}\`
|
|
@@ -4612,15 +4844,25 @@ Refresh after file changes:
|
|
|
4612
4844
|
|
|
4613
4845
|
\`${COMPANION_NPX_COMMAND} source sync --json\`
|
|
4614
4846
|
|
|
4847
|
+
If the folder has little or no documentation, preview a Project Brief before writing it:
|
|
4848
|
+
|
|
4849
|
+
\`${COMPANION_NPX_COMMAND} docs bootstrap --preview\`
|
|
4850
|
+
|
|
4851
|
+
After reviewing the generated brief, apply and index it:
|
|
4852
|
+
|
|
4853
|
+
\`${COMPANION_NPX_COMMAND} docs bootstrap --apply\`
|
|
4854
|
+
|
|
4855
|
+
\`${COMPANION_NPX_COMMAND} source sync --apply --reindex\`
|
|
4856
|
+
|
|
4615
4857
|
Inspect local drift:
|
|
4616
4858
|
|
|
4617
4859
|
\`${COMPANION_NPX_COMMAND} source status --json\`` : `This setup did not install snipara-companion. Enable it before local source activation:
|
|
4618
4860
|
|
|
4619
4861
|
\`${CREATE_SNIPARA_NPX_COMMAND} repair --with-companion\``}
|
|
4620
4862
|
|
|
4621
|
-
GitHub automation remains
|
|
4622
|
-
sync and PR Answer Packs.
|
|
4623
|
-
|
|
4863
|
+
GitHub automation remains a Cloud-only option after first value: it gives Snipara a canonical
|
|
4864
|
+
hosted repository sync and PR Answer Packs. In self-hosted mode, keep source activation local and
|
|
4865
|
+
do not send repository data to the Cloud unless the user explicitly starts a separate Cloud setup.
|
|
4624
4866
|
|
|
4625
4867
|
## GitHub PR Answer Packs
|
|
4626
4868
|
|
|
@@ -4962,10 +5204,10 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
|
|
|
4962
5204
|
detail: detection.files.sniparaSkillPack ? ".snipara/templates/skills contains the Snipara skill pack" : "Snipara skill pack missing from .snipara/templates/skills"
|
|
4963
5205
|
});
|
|
4964
5206
|
if (targetClient === "codex") {
|
|
4965
|
-
checks.push(getCodexConfigCheck());
|
|
5207
|
+
checks.push(getCodexConfigCheck(effectiveAnswers));
|
|
4966
5208
|
}
|
|
4967
5209
|
if (targetClient === "mistral") {
|
|
4968
|
-
checks.push(getMistralConfigCheck());
|
|
5210
|
+
checks.push(getMistralConfigCheck(effectiveAnswers));
|
|
4969
5211
|
}
|
|
4970
5212
|
checks.push({
|
|
4971
5213
|
name: "env_files",
|
|
@@ -4995,7 +5237,7 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
|
|
|
4995
5237
|
checks.push({
|
|
4996
5238
|
name: "hosted_connection",
|
|
4997
5239
|
status: "warn",
|
|
4998
|
-
detail:
|
|
5240
|
+
detail: `Skipped ${getDeploymentLabel(answers.deployment)} connectivity test because no live API key was available`
|
|
4999
5241
|
});
|
|
5000
5242
|
}
|
|
5001
5243
|
for (const check of checks) {
|
|
@@ -5014,7 +5256,7 @@ function writeDoctorReport(report) {
|
|
|
5014
5256
|
fs.writeFileSync(path.join(reportDir, "doctor.json"), JSON.stringify(report, null, 2) + "\n");
|
|
5015
5257
|
console.log(chalk.green(" \u2713 Wrote .snipara/companion/doctor.json"));
|
|
5016
5258
|
}
|
|
5017
|
-
function getCodexConfigCheck() {
|
|
5259
|
+
function getCodexConfigCheck(answers) {
|
|
5018
5260
|
const candidates = [
|
|
5019
5261
|
{
|
|
5020
5262
|
label: ".codex/config.toml",
|
|
@@ -5054,8 +5296,8 @@ function getCodexConfigCheck() {
|
|
|
5054
5296
|
const nonSniparaBearerEnvVars = configuredBearerEnvVars.filter(
|
|
5055
5297
|
(candidate) => candidate.value !== "SNIPARA_API_KEY"
|
|
5056
5298
|
);
|
|
5057
|
-
const
|
|
5058
|
-
(candidate) => candidate.content.includes(
|
|
5299
|
+
const hasConfiguredEndpoint = present.some(
|
|
5300
|
+
(candidate) => candidate.content.includes(buildMcpEndpoint(answers))
|
|
5059
5301
|
);
|
|
5060
5302
|
const labels = present.map((candidate) => candidate.label).join(", ");
|
|
5061
5303
|
if (nonSniparaBearerEnvVars.length > 0) {
|
|
@@ -5066,20 +5308,20 @@ function getCodexConfigCheck() {
|
|
|
5066
5308
|
detail: `Codex config uses a non-Snipara bearer env var (${details}); use SNIPARA_API_KEY and restart Codex`
|
|
5067
5309
|
};
|
|
5068
5310
|
}
|
|
5069
|
-
if (!usesSniparaEnv || !
|
|
5311
|
+
if (!usesSniparaEnv || !hasConfiguredEndpoint) {
|
|
5070
5312
|
return {
|
|
5071
5313
|
name: "codex_config",
|
|
5072
5314
|
status: "warn",
|
|
5073
|
-
detail: `Codex config found in ${labels}, but expected
|
|
5315
|
+
detail: `Codex config found in ${labels}, but expected the configured ${getDeploymentLabel(answers.deployment)} endpoint plus bearer_token_env_var = "SNIPARA_API_KEY"`
|
|
5074
5316
|
};
|
|
5075
5317
|
}
|
|
5076
5318
|
return {
|
|
5077
5319
|
name: "codex_config",
|
|
5078
5320
|
status: "pass",
|
|
5079
|
-
detail: `Codex config uses
|
|
5321
|
+
detail: `Codex config uses ${getDeploymentLabel(answers.deployment)} and SNIPARA_API_KEY (${labels}); restart Codex after config changes`
|
|
5080
5322
|
};
|
|
5081
5323
|
}
|
|
5082
|
-
function getMistralConfigCheck() {
|
|
5324
|
+
function getMistralConfigCheck(answers) {
|
|
5083
5325
|
const candidates = [
|
|
5084
5326
|
{
|
|
5085
5327
|
label: ".vibe/config.toml",
|
|
@@ -5105,24 +5347,24 @@ function getMistralConfigCheck() {
|
|
|
5105
5347
|
detail: "No Mistral Vibe config found; use .snipara/templates/mistral-vibe-config.toml or add Snipara as a Le Chat Custom MCP Connector"
|
|
5106
5348
|
};
|
|
5107
5349
|
}
|
|
5108
|
-
const
|
|
5109
|
-
(candidate) => candidate.content.includes(
|
|
5350
|
+
const hasConfiguredEndpoint = present.some(
|
|
5351
|
+
(candidate) => candidate.content.includes(buildMcpEndpoint(answers))
|
|
5110
5352
|
);
|
|
5111
5353
|
const hasSniparaEnv = present.some(
|
|
5112
5354
|
(candidate) => candidate.content.includes('api_key_env = "SNIPARA_API_KEY"')
|
|
5113
5355
|
);
|
|
5114
5356
|
const labels = present.map((candidate) => candidate.label).join(", ");
|
|
5115
|
-
if (!
|
|
5357
|
+
if (!hasConfiguredEndpoint || !hasSniparaEnv) {
|
|
5116
5358
|
return {
|
|
5117
5359
|
name: "mistral_config",
|
|
5118
5360
|
status: "warn",
|
|
5119
|
-
detail: `Mistral config found in ${labels}, but expected
|
|
5361
|
+
detail: `Mistral config found in ${labels}, but expected the configured ${getDeploymentLabel(answers.deployment)} endpoint plus api_key_env = "SNIPARA_API_KEY"`
|
|
5120
5362
|
};
|
|
5121
5363
|
}
|
|
5122
5364
|
return {
|
|
5123
5365
|
name: "mistral_config",
|
|
5124
5366
|
status: "pass",
|
|
5125
|
-
detail: `Mistral Vibe config uses
|
|
5367
|
+
detail: `Mistral Vibe config uses ${getDeploymentLabel(answers.deployment)} and SNIPARA_API_KEY (${labels}); restart Vibe after config changes`
|
|
5126
5368
|
};
|
|
5127
5369
|
}
|
|
5128
5370
|
function printDoctorReport(detection, report) {
|
|
@@ -5144,7 +5386,8 @@ function printDoctorReport(detection, report) {
|
|
|
5144
5386
|
console.log("");
|
|
5145
5387
|
}
|
|
5146
5388
|
async function testHostedConnection(answers) {
|
|
5147
|
-
const
|
|
5389
|
+
const deploymentLabel = getDeploymentLabel(answers.deployment);
|
|
5390
|
+
const spinner = ora(`Connecting to ${deploymentLabel}...`).start();
|
|
5148
5391
|
try {
|
|
5149
5392
|
const toolsResponse = await callHostedMcp(answers, "tools/list", {});
|
|
5150
5393
|
if (!toolsResponse.ok) {
|
|
@@ -5167,7 +5410,9 @@ async function testHostedConnection(answers) {
|
|
|
5167
5410
|
}
|
|
5168
5411
|
];
|
|
5169
5412
|
}
|
|
5170
|
-
spinner.succeed(
|
|
5413
|
+
spinner.succeed(
|
|
5414
|
+
answers.deployment === "hosted" ? "Connected to Snipara Hosted MCP." : `Connected to ${deploymentLabel}.`
|
|
5415
|
+
);
|
|
5171
5416
|
const toolNames = extractMcpToolNames(toolsResponse.data);
|
|
5172
5417
|
const hasSniparaTools = toolNames.some((name) => name.startsWith("snipara_"));
|
|
5173
5418
|
const advertisesLegacyTools = toolNames.some((name) => name.startsWith("rlm_"));
|
|
@@ -5179,29 +5424,32 @@ async function testHostedConnection(answers) {
|
|
|
5179
5424
|
},
|
|
5180
5425
|
{
|
|
5181
5426
|
name: "hosted_snipara_tools",
|
|
5182
|
-
status: hasSniparaTools ? "pass" : "warn",
|
|
5183
|
-
detail: hasSniparaTools ? "snipara_
|
|
5427
|
+
status: hasSniparaTools || advertisesLegacyTools ? "pass" : "warn",
|
|
5428
|
+
detail: hasSniparaTools || advertisesLegacyTools ? `${toolNames.filter((name) => name.startsWith("snipara_") || name.startsWith("rlm_")).length} Snipara MCP tools are advertised` : "tools/list succeeded, but no Snipara tool namespace was detected"
|
|
5184
5429
|
},
|
|
5185
5430
|
{
|
|
5186
5431
|
name: "hosted_legacy_tools_hidden",
|
|
5187
|
-
status: advertisesLegacyTools ? "warn" : "pass",
|
|
5188
|
-
detail: advertisesLegacyTools ? "
|
|
5432
|
+
status: answers.deployment === "hosted" && advertisesLegacyTools ? "warn" : "pass",
|
|
5433
|
+
detail: answers.deployment === "hosted" && advertisesLegacyTools ? "Hosted MCP still advertises legacy rlm_* tools; update or redeploy the hosted surface" : answers.deployment === "self-hosted" ? "Self-hosted deployment may expose the public rlm_* compatibility namespace" : "Hosted MCP does not advertise legacy rlm_* duplicates"
|
|
5189
5434
|
}
|
|
5190
5435
|
];
|
|
5191
|
-
|
|
5436
|
+
if (answers.deployment === "self-hosted") {
|
|
5437
|
+
checks.push(...await testSelfHostedCapabilities(answers));
|
|
5438
|
+
}
|
|
5439
|
+
const settingsTool = answers.deployment === "self-hosted" ? "rlm_settings" : "snipara_settings";
|
|
5192
5440
|
const settingsResponse = await callHostedMcp(answers, "tools/call", {
|
|
5193
5441
|
name: settingsTool,
|
|
5194
5442
|
arguments: {}
|
|
5195
5443
|
});
|
|
5196
5444
|
if (settingsResponse.ok && !settingsResponse.data.error) {
|
|
5197
5445
|
checks.push({
|
|
5198
|
-
name: "
|
|
5446
|
+
name: "deployment_settings",
|
|
5199
5447
|
status: "pass",
|
|
5200
5448
|
detail: `${settingsTool} tool call succeeded`
|
|
5201
5449
|
});
|
|
5202
5450
|
} else {
|
|
5203
5451
|
checks.push({
|
|
5204
|
-
name: "
|
|
5452
|
+
name: "deployment_settings",
|
|
5205
5453
|
status: "warn",
|
|
5206
5454
|
detail: settingsResponse.ok ? describeJsonRpcError(settingsResponse.data) : describeHostedHttpFailure(settingsResponse.status, settingsResponse.bodyText)
|
|
5207
5455
|
});
|
|
@@ -5213,7 +5461,40 @@ async function testHostedConnection(answers) {
|
|
|
5213
5461
|
{
|
|
5214
5462
|
name: "hosted_connection",
|
|
5215
5463
|
status: "warn",
|
|
5216
|
-
detail:
|
|
5464
|
+
detail: `Could not reach ${deploymentLabel} from this machine`
|
|
5465
|
+
}
|
|
5466
|
+
];
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
async function testSelfHostedCapabilities(answers) {
|
|
5470
|
+
try {
|
|
5471
|
+
const endpoint = new URL(answers.serverUrl);
|
|
5472
|
+
const response = await fetch(`${endpoint.origin}/capabilities`, {
|
|
5473
|
+
headers: { "X-API-Key": answers.apiKey }
|
|
5474
|
+
});
|
|
5475
|
+
const body = await response.json();
|
|
5476
|
+
if (!response.ok) {
|
|
5477
|
+
return [
|
|
5478
|
+
{
|
|
5479
|
+
name: "self_hosted_contract",
|
|
5480
|
+
status: "warn",
|
|
5481
|
+
detail: `Could not verify /capabilities (HTTP ${response.status})`
|
|
5482
|
+
}
|
|
5483
|
+
];
|
|
5484
|
+
}
|
|
5485
|
+
return [
|
|
5486
|
+
{
|
|
5487
|
+
name: "self_hosted_contract",
|
|
5488
|
+
status: body.contract === "snipara-server-oss-v2" ? "pass" : "warn",
|
|
5489
|
+
detail: body.contract === "snipara-server-oss-v2" ? "snipara-server-oss-v2 contract verified" : `Unexpected self-hosted contract${body.contract ? `: ${body.contract}` : ""}`
|
|
5490
|
+
}
|
|
5491
|
+
];
|
|
5492
|
+
} catch {
|
|
5493
|
+
return [
|
|
5494
|
+
{
|
|
5495
|
+
name: "self_hosted_contract",
|
|
5496
|
+
status: "warn",
|
|
5497
|
+
detail: "Self-hosted /capabilities endpoint could not be verified"
|
|
5217
5498
|
}
|
|
5218
5499
|
];
|
|
5219
5500
|
}
|
|
@@ -5228,7 +5509,7 @@ function extractMcpToolNames(data) {
|
|
|
5228
5509
|
).filter(Boolean);
|
|
5229
5510
|
}
|
|
5230
5511
|
async function callHostedMcp(answers, method, params) {
|
|
5231
|
-
const response = await fetch(
|
|
5512
|
+
const response = await fetch(buildMcpEndpoint(answers), {
|
|
5232
5513
|
method: "POST",
|
|
5233
5514
|
headers: {
|
|
5234
5515
|
"Content-Type": "application/json",
|
|
@@ -5300,8 +5581,24 @@ function printActivationSummary(manifest) {
|
|
|
5300
5581
|
` ${formatActivationStatus(manifest.lanes.connection.status)} Connected - ${manifest.lanes.connection.detail}`
|
|
5301
5582
|
);
|
|
5302
5583
|
console.log(` ${formatActivationStatus(contextLane.status)} Context - ${contextLane.detail}`);
|
|
5303
|
-
|
|
5304
|
-
|
|
5584
|
+
if (manifest.valueProof.status === "ready") {
|
|
5585
|
+
console.log(
|
|
5586
|
+
` ${chalk.green("\u2713 ready")} Value proof - ${manifest.valueProof.detail} (${manifest.valueProof.evidence || "evidence recorded"})`
|
|
5587
|
+
);
|
|
5588
|
+
console.log(chalk.green("\n Activation ready: First grounded answer verified."));
|
|
5589
|
+
} else if (manifest.valueProof.status === "failed") {
|
|
5590
|
+
console.log(
|
|
5591
|
+
` ${chalk.red("\u2717 failed")} Value proof - ${manifest.valueProof.error || manifest.valueProof.detail}`
|
|
5592
|
+
);
|
|
5593
|
+
console.log(
|
|
5594
|
+
chalk.yellow(
|
|
5595
|
+
"\n Activation needs attention: run create-snipara doctor after checking the MCP endpoint."
|
|
5596
|
+
)
|
|
5597
|
+
);
|
|
5598
|
+
} else {
|
|
5599
|
+
console.log(` ${chalk.dim("\u25CB pending")} First grounded answer`);
|
|
5600
|
+
console.log(chalk.yellow("\n Activation pending: first grounded answer needed."));
|
|
5601
|
+
}
|
|
5305
5602
|
console.log(
|
|
5306
5603
|
chalk.dim(
|
|
5307
5604
|
`
|
|
@@ -5425,6 +5722,7 @@ function detectEnvironment() {
|
|
|
5425
5722
|
existingConfig: {
|
|
5426
5723
|
slug: existingConfig?.slug,
|
|
5427
5724
|
projectId: existingConfig?.projectId,
|
|
5725
|
+
serverUrl: existingConfig?.serverUrl,
|
|
5428
5726
|
hasSniparaServer: existingConfig?.hasSniparaServer ?? false,
|
|
5429
5727
|
hasRuntimeServer: existingConfig?.hasRuntimeServer ?? false
|
|
5430
5728
|
}
|
|
@@ -5433,19 +5731,22 @@ function detectEnvironment() {
|
|
|
5433
5731
|
function loadExistingAnswers(options) {
|
|
5434
5732
|
const detection = detectEnvironment();
|
|
5435
5733
|
const effectiveProfile = resolveMaintenanceProfile(detection, options.profile);
|
|
5734
|
+
const deployment = resolveRequestedDeployment(options);
|
|
5436
5735
|
const providedApiKey = options.apiKey?.trim();
|
|
5437
|
-
if (providedApiKey && !
|
|
5438
|
-
throw new Error(manualApiKeyValidationMessage());
|
|
5736
|
+
if (providedApiKey && !isValidApiKey(providedApiKey, deployment)) {
|
|
5737
|
+
throw new Error(manualApiKeyValidationMessage(deployment));
|
|
5439
5738
|
}
|
|
5440
5739
|
if (providedApiKey) {
|
|
5441
5740
|
warnApiKeyCliExposure();
|
|
5442
5741
|
}
|
|
5443
|
-
const envApiKey = readApiKeyFromEnvFiles();
|
|
5742
|
+
const envApiKey = readApiKeyFromEnvFiles(deployment);
|
|
5444
5743
|
const apiKey = providedApiKey || envApiKey || "YOUR_API_KEY_HERE";
|
|
5445
5744
|
const apiKeyType = effectiveProfile === "runtime-only" ? "none" : providedApiKey || envApiKey ? "api" : "none";
|
|
5446
5745
|
return buildAnswersFromProfile({
|
|
5447
5746
|
projectSlug: options.slug || detection.projectSlug || "my-project",
|
|
5448
5747
|
projectId: options.projectId || detection.existingConfig.projectId || "",
|
|
5748
|
+
deployment,
|
|
5749
|
+
serverUrl: options.serverUrl || process.env.SNIPARA_MCP_URL || getExistingConfig()?.serverUrl || "http://localhost:8000/mcp/local",
|
|
5449
5750
|
apiKey,
|
|
5450
5751
|
apiKeyType,
|
|
5451
5752
|
profile: effectiveProfile,
|
|
@@ -5472,10 +5773,13 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
|
|
|
5472
5773
|
}
|
|
5473
5774
|
return "hosted-only";
|
|
5474
5775
|
}
|
|
5475
|
-
function readApiKeyFromEnvFiles() {
|
|
5776
|
+
function readApiKeyFromEnvFiles(deployment = "hosted") {
|
|
5476
5777
|
const candidates = [".env.local", ".env"];
|
|
5477
5778
|
for (const file of candidates) {
|
|
5478
|
-
const value = readEnvValueFromFile(
|
|
5779
|
+
const value = readEnvValueFromFile(
|
|
5780
|
+
file,
|
|
5781
|
+
deployment === "self-hosted" ? ["SNIPARA_LOCAL_API_KEY", "SNIPARA_API_KEY"] : ["SNIPARA_API_KEY"]
|
|
5782
|
+
);
|
|
5479
5783
|
if (value) {
|
|
5480
5784
|
return value;
|
|
5481
5785
|
}
|
|
@@ -5782,6 +6086,7 @@ function getExistingConfig() {
|
|
|
5782
6086
|
return {
|
|
5783
6087
|
slug: extractSlugFromMcpEndpoint(sniparaServer?.url) || void 0,
|
|
5784
6088
|
projectId: readExistingCompanionProjectId(),
|
|
6089
|
+
serverUrl: typeof sniparaServer?.url === "string" ? normalizeMcpEndpoint(sniparaServer.url) : void 0,
|
|
5785
6090
|
hasSniparaServer: Boolean(sniparaServer),
|
|
5786
6091
|
hasRuntimeServer: Boolean(runtimeServer)
|
|
5787
6092
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-snipara",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Onboard Snipara
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Onboard Snipara Cloud or self-hosted MCP with an automatic project-grounded proof of value",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -39,7 +39,11 @@
|
|
|
39
39
|
"lm-studio",
|
|
40
40
|
"vscode",
|
|
41
41
|
"continue",
|
|
42
|
-
"claude-desktop"
|
|
42
|
+
"claude-desktop",
|
|
43
|
+
"self-hosted",
|
|
44
|
+
"snipara-server",
|
|
45
|
+
"project-intelligence",
|
|
46
|
+
"persistent-memory"
|
|
43
47
|
],
|
|
44
48
|
"author": "Snipara",
|
|
45
49
|
"license": "MIT",
|