create-snipara 2.0.18 → 2.1.1
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 +67 -13
- package/dist/index.js +396 -107
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
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
|
|
7
7
|
impact tooling instead of starting cold every session.
|
|
8
8
|
|
|
9
|
+
The shortest path is three moments: connect the folder your agent already uses,
|
|
10
|
+
let Snipara index its project context, then ask one question that only the
|
|
11
|
+
project can answer.
|
|
12
|
+
|
|
9
13
|
```bash
|
|
10
14
|
npx create-snipara
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
For a local or private server, use the same activation flow:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export SNIPARA_LOCAL_API_KEY="your-server-operator-key"
|
|
21
|
+
npx create-snipara@latest --self-hosted \
|
|
22
|
+
--server-url http://localhost:8000/mcp/local \
|
|
23
|
+
--api-key "$SNIPARA_LOCAL_API_KEY" \
|
|
24
|
+
--client claude-code
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Both modes aim to finish with the same visible proof:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
✓ Endpoint connected
|
|
31
|
+
✓ Repository README/docs seeded
|
|
32
|
+
✓ First project-grounded answer verified
|
|
33
|
+
✓ AI client configuration written
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`create-snipara` is the single activation engine for the agent path. It detects
|
|
37
|
+
or asks where Snipara should run, writes the right MCP config, seeds a small
|
|
38
|
+
README/docs corpus, and automatically verifies a first project-grounded answer.
|
|
39
|
+
The default path is Cloud because it is the fastest first experience; the same
|
|
40
|
+
flow works with an existing self-hosted Snipara Server.
|
|
17
41
|
It is also the canonical activation engine behind editor setup docs: VS Code can
|
|
18
42
|
surface activation as a native command, Cursor can pair OpenVSX UI with Hosted
|
|
19
43
|
MCP, but both paths should land on the same project context.
|
|
@@ -25,7 +49,7 @@ behind the scenes.
|
|
|
25
49
|
|
|
26
50
|
Use it for:
|
|
27
51
|
|
|
28
|
-
-
|
|
52
|
+
- 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
53
|
- free account signup with reviewed Context + Memory
|
|
30
54
|
- local project wiring, `doctor`, `repair`, and `upgrade`
|
|
31
55
|
- 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 +64,22 @@ normalizes that setup.
|
|
|
40
64
|
```mermaid
|
|
41
65
|
flowchart LR
|
|
42
66
|
Project["Local project"] --> CLI["npx create-snipara"]
|
|
43
|
-
CLI --> Auth["
|
|
44
|
-
CLI --> MCP["
|
|
67
|
+
CLI --> Auth["Cloud auth or self-hosted operator key"]
|
|
68
|
+
CLI --> MCP["Configured MCP endpoint"]
|
|
45
69
|
CLI --> Templates["AGENTS.md, CLAUDE.md, Cursor, Codex, MCP references"]
|
|
46
70
|
MCP --> Agents["Claude Code, Cursor, Codex, Gemini, Mistral, VS Code, ChatGPT, Orca, LM Studio stacks"]
|
|
47
71
|
Templates --> Agents
|
|
48
72
|
Agents --> Context["Snipara context and memory"]
|
|
49
73
|
```
|
|
50
74
|
|
|
51
|
-
The CLI does not become the runtime brain. After setup, the agent talks to
|
|
52
|
-
|
|
75
|
+
The CLI does not become the runtime brain. After setup, the agent talks to the
|
|
76
|
+
configured MCP endpoint and keeps using its own LLM.
|
|
53
77
|
|
|
54
78
|
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
79
|
|
|
56
80
|
The product surfaces remain:
|
|
57
81
|
|
|
58
|
-
-
|
|
82
|
+
- Snipara Cloud or self-hosted Snipara Server for LLM agents, context, and reviewed memory
|
|
59
83
|
- HTTP API / SDK for apps, integrators, backends, and pipelines
|
|
60
84
|
|
|
61
85
|
Advanced flags can wire local helpers around those surfaces:
|
|
@@ -67,6 +91,24 @@ Advanced flags can wire local helpers around those surfaces:
|
|
|
67
91
|
- local config, env files, hook scripts, and a project companion pack
|
|
68
92
|
- `snipara-mcp` only for local stdio compatibility, development, or testing
|
|
69
93
|
|
|
94
|
+
## Deployment Modes
|
|
95
|
+
|
|
96
|
+
The deployment mode and install profile are independent:
|
|
97
|
+
|
|
98
|
+
| Mode | Command | Data boundary | First proof |
|
|
99
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------ |
|
|
100
|
+
| Snipara Cloud | `npx create-snipara@latest` | Snipara-managed hosted project | Cloud MCP connection, README/docs seed, grounded answer |
|
|
101
|
+
| 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 |
|
|
102
|
+
|
|
103
|
+
Self-hosted runs use the server's `X-API-Key` operator key, the public
|
|
104
|
+
`snipara-server-oss-v2` contract, and the server's `rlm_*` compatibility tools.
|
|
105
|
+
They never open Cloud signup or call the Cloud dashboard automation API.
|
|
106
|
+
|
|
107
|
+
Every successful live setup writes `.snipara/activation/activation-manifest.json`
|
|
108
|
+
with the selected endpoint, seed status, proof tool, and a non-sensitive proof
|
|
109
|
+
receipt such as `answer_pack_chars=...`. Raw API keys are never included in the
|
|
110
|
+
activation report or JSON summary.
|
|
111
|
+
|
|
70
112
|
## Install Profiles
|
|
71
113
|
|
|
72
114
|
The CLI now works around explicit install profiles:
|
|
@@ -199,11 +241,13 @@ npx create-snipara
|
|
|
199
241
|
|
|
200
242
|
The interactive flow now asks for:
|
|
201
243
|
|
|
244
|
+
- deployment mode when `--advanced` is used
|
|
245
|
+
- a self-hosted MCP endpoint when self-hosted mode is selected
|
|
202
246
|
- project slug
|
|
203
|
-
- API key path:
|
|
247
|
+
- API key path: Cloud key/signup, self-hosted operator key, or add key later
|
|
204
248
|
- AI client
|
|
205
249
|
|
|
206
|
-
By default, this uses the minimal `hosted-only` setup:
|
|
250
|
+
By default, this uses the minimal `hosted-only` setup: configured MCP config and a
|
|
207
251
|
doctor report, with no local Companion install, hooks, GitHub approval prompt, or
|
|
208
252
|
sandbox prompt. Add `--advanced` when you want profile selection, project id
|
|
209
253
|
entry, hook prompts, GitHub automation prompts, or sandbox provider prompts.
|
|
@@ -414,9 +458,19 @@ npx create-snipara --runtime-only
|
|
|
414
458
|
# Safer non-interactive setup with an existing API key
|
|
415
459
|
SNIPARA_API_KEY=snp-xxx npx create-snipara -y --slug my-project
|
|
416
460
|
|
|
461
|
+
# Self-hosted non-interactive setup; the same command works with any MCP client
|
|
462
|
+
SNIPARA_LOCAL_API_KEY=local-operator-key npx create-snipara -y \
|
|
463
|
+
--self-hosted --server-url http://localhost:8000/mcp/local \
|
|
464
|
+
--api-key "$SNIPARA_LOCAL_API_KEY" --slug my-project
|
|
465
|
+
|
|
417
466
|
# Compatibility path; prints a warning because CLI args can leak
|
|
418
467
|
npx create-snipara -y --api-key snp-xxx --slug my-project
|
|
419
468
|
|
|
469
|
+
# Explicitly choose the deployment mode
|
|
470
|
+
npx create-snipara -y --deployment hosted --api-key snp-xxx --slug my-project
|
|
471
|
+
npx create-snipara -y --deployment self-hosted \
|
|
472
|
+
--server-url http://localhost:8000/mcp/local --api-key local-operator-key
|
|
473
|
+
|
|
420
474
|
# Open GitHub automation after setup when a GitHub remote is detected
|
|
421
475
|
npx create-snipara --github
|
|
422
476
|
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.
|
|
17
|
-
description: "Onboard Snipara
|
|
17
|
+
version: "2.1.1",
|
|
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",
|
|
@@ -93,7 +98,7 @@ var COMPANION_PACKAGE_NAME = "snipara-companion";
|
|
|
93
98
|
var COMPANION_COMMAND_NAME = "snipara-companion";
|
|
94
99
|
var COMPANION_NPX_COMMAND = "npx -y snipara-companion@latest";
|
|
95
100
|
var CREATE_SNIPARA_NPX_COMMAND = "npx -y create-snipara@latest";
|
|
96
|
-
var MIN_COMPANION_VERSION = "3.
|
|
101
|
+
var MIN_COMPANION_VERSION = "3.8.0";
|
|
97
102
|
var GITHUB_ANSWER_PACKS_LABEL = "GitHub PR Answer Packs";
|
|
98
103
|
var MCP_PACKAGE_NAME = "snipara-mcp";
|
|
99
104
|
var SANDBOX_PACKAGE_NAME = "snipara-sandbox[all]";
|
|
@@ -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);
|
|
@@ -3304,8 +3510,7 @@ Agent task lifecycle:
|
|
|
3304
3510
|
commit SHA, indexing time, included file sample, coverage, freshness warnings,
|
|
3305
3511
|
and sync guidance before relying on it. If \`git status --short\` is dirty,
|
|
3306
3512
|
warn that the indexed graph may not include local edits.
|
|
3307
|
-
12.
|
|
3308
|
-
isolated transformations materially help.
|
|
3513
|
+
12. Before executable repository tests, probe the required Snipara Sandbox capabilities using \`validation gate\` with an explicit request and workflow/lot scope. If absent, inaccessible or incompatible, ask the user to choose another test environment, install/configure Sandbox, or continue without Sandbox (local tests versus no tests). Never fall back silently or treat no response as approval; preserve the scoped choice and proof limits across resume. Static reads and documentation do not require a container.
|
|
3309
3514
|
13. If a broad query is slow, retry once with a narrow keyword query before
|
|
3310
3515
|
falling back to local search.
|
|
3311
3516
|
14. When generated hooks are enabled, \`snipara-companion pre-tool\`, \`post-tool\`,
|
|
@@ -3356,7 +3561,7 @@ ${EXPERIENCE_GOAL_SECTION}
|
|
|
3356
3561
|
- If a broad query is slow, retry once with a narrow keyword query before falling back to local search.
|
|
3357
3562
|
- When the plan is visible and \`snipara-companion\` is installed, keep the machine plan in JSON, use \`snipara-companion workflow start\`, \`phase-start\`, and \`phase-commit\` per phase, and after \`workflow resume\` rerun \`workflow phase-start\` before editing again.
|
|
3358
3563
|
- If generated hooks are enabled, \`snipara-companion pre-tool\`, \`post-tool\`, and \`stuck-guard status\` provide runtime Rescue Pack checks when the agent loops on failures or empty searches.
|
|
3359
|
-
-
|
|
3564
|
+
- Before executable repository tests, run \`validation gate\` with an explicit request and workflow/lot scope. If unavailable or incompatible, ask the user to choose another environment, install/configure Sandbox, or continue without Sandbox (local tests versus no tests). Never fall back silently or infer approval. Preserve the scoped choice and proof requirements across resume. For runtime-bound phases, capture compact rehydratable state with \`workflow runtime-checkpoint <phase_id> --summary "<state>" --rehydrate-file <state.json>\`. Then \`workflow resume\` restores workflow and memory continuity plus the recorded Sandbox binding and prints a reattach or rehydrate plan. It does not snapshot or exactly restore a live Snipara Sandbox or REPL process.
|
|
3360
3565
|
- End substantial work with \`snipara_end_of_task_commit\` when available; for managed workflows, end every phase with \`snipara-companion workflow phase-commit\` and the task with \`snipara-companion final-commit\`.
|
|
3361
3566
|
- Use \`snipara_remember_if_novel\` or \`snipara_remember\` only for narrow durable memories.
|
|
3362
3567
|
- When remembered knowledge is obsolete or replaced and \`snipara-companion\` is installed, use \`snipara-companion memory invalidate <memory-id>\` or \`snipara-companion memory supersede <old-memory-id> <new-memory-id>\`; otherwise use hosted \`snipara_memory_invalidate\` or \`snipara_memory_supersede\`.
|
|
@@ -3397,7 +3602,7 @@ ${EXPERIENCE_GOAL_SECTION}
|
|
|
3397
3602
|
- If a broad query is slow, retry once with a narrow keyword query before falling back to local search.
|
|
3398
3603
|
- When \`snipara-companion\` is installed and the LLM has a visible multi-phase plan, keep the machine plan in JSON, use \`snipara-companion workflow start\`, \`phase-start\`, and \`phase-commit\` per phase, and after \`workflow resume\` rerun \`workflow phase-start\` before editing again.
|
|
3399
3604
|
- Generated hooks can call \`snipara-companion pre-tool\`, \`post-tool\`, and \`stuck-guard status\` so Rescue Packs are runtime-triggered rather than relying only on instructions.
|
|
3400
|
-
-
|
|
3605
|
+
- Before executable repository tests, run \`validation gate\` with an explicit request and workflow/lot scope. If unavailable or incompatible, ask the user to choose another environment, install/configure Sandbox, or continue without Sandbox (local tests versus no tests). Never fall back silently or infer approval. Preserve the scoped choice and proof requirements across resume. For runtime-bound phases, capture compact rehydratable state with \`workflow runtime-checkpoint <phase_id> --summary "<state>" --rehydrate-file <state.json>\`. Then \`workflow resume\` restores workflow and memory continuity plus the recorded Sandbox binding and prints a reattach or rehydrate plan. It does not snapshot or exactly restore a live Snipara Sandbox or REPL process.
|
|
3401
3606
|
- End substantial work with \`snipara_end_of_task_commit\` when available; for managed workflows, end every phase with \`snipara-companion workflow phase-commit\` and the task with \`snipara-companion final-commit\`. Use \`snipara_remember_if_novel\` or \`snipara_remember\` only for narrow durable memories. When remembered knowledge is obsolete or replaced, use \`snipara-companion memory invalidate <memory-id>\` or \`snipara-companion memory supersede <old-memory-id> <new-memory-id>\` when companion is installed; otherwise use hosted \`snipara_memory_invalidate\` or \`snipara_memory_supersede\`. Never store secrets.
|
|
3402
3607
|
`;
|
|
3403
3608
|
}
|
|
@@ -3874,15 +4079,19 @@ async function updateEnvFiles(answers) {
|
|
|
3874
4079
|
const envPath = path.join(process.cwd(), ".env");
|
|
3875
4080
|
let envVars = "";
|
|
3876
4081
|
let envExampleVars = "";
|
|
3877
|
-
if (answers.
|
|
4082
|
+
if (answers.profile !== "runtime-only") {
|
|
3878
4083
|
envVars += `
|
|
3879
4084
|
# Snipara Configuration
|
|
4085
|
+
SNIPARA_DEPLOYMENT=${answers.deployment}
|
|
4086
|
+
SNIPARA_MCP_URL=${answers.serverUrl}
|
|
3880
4087
|
SNIPARA_API_KEY=${answers.apiKey.includes("YOUR_") ? "your_api_key_here" : answers.apiKey}
|
|
3881
4088
|
SNIPARA_PROJECT_SLUG=${answers.projectSlug}
|
|
3882
4089
|
${answers.projectId ? `SNIPARA_PROJECT_ID=${answers.projectId}` : ""}
|
|
3883
4090
|
`.trim();
|
|
3884
4091
|
envExampleVars += `
|
|
3885
4092
|
# Snipara Configuration
|
|
4093
|
+
SNIPARA_DEPLOYMENT=${answers.deployment}
|
|
4094
|
+
SNIPARA_MCP_URL=${answers.serverUrl}
|
|
3886
4095
|
SNIPARA_API_KEY=your_api_key_here
|
|
3887
4096
|
SNIPARA_PROJECT_SLUG=${answers.projectSlug || "your_project_slug"}
|
|
3888
4097
|
${answers.projectId ? `SNIPARA_PROJECT_ID=${answers.projectId}` : "SNIPARA_PROJECT_ID=your_project_id"}
|
|
@@ -3907,10 +4116,14 @@ ${llmVarName}=your_${answers.llmProvider}_api_key_here`;
|
|
|
3907
4116
|
}
|
|
3908
4117
|
if (envExampleVars && fs.existsSync(envExamplePath)) {
|
|
3909
4118
|
const content = fs.readFileSync(envExamplePath, "utf-8");
|
|
3910
|
-
|
|
4119
|
+
const missingLines = envExampleVars.split("\n").filter((line) => {
|
|
4120
|
+
const match = line.match(/^([A-Z0-9_]+)=/);
|
|
4121
|
+
return Boolean(match && !content.includes(`${match[1]}=`));
|
|
4122
|
+
});
|
|
4123
|
+
if (missingLines.length > 0) {
|
|
3911
4124
|
fs.appendFileSync(envExamplePath, `
|
|
3912
4125
|
|
|
3913
|
-
${
|
|
4126
|
+
${missingLines.join("\n")}
|
|
3914
4127
|
`);
|
|
3915
4128
|
console.log(chalk.green(" \u2713 Updated .env.example"));
|
|
3916
4129
|
}
|
|
@@ -3921,16 +4134,26 @@ ${envExampleVars}
|
|
|
3921
4134
|
const targetEnvPath = fs.existsSync(envLocalPath) ? envLocalPath : envPath;
|
|
3922
4135
|
if (fs.existsSync(targetEnvPath)) {
|
|
3923
4136
|
const content = fs.readFileSync(targetEnvPath, "utf-8");
|
|
3924
|
-
|
|
4137
|
+
const missingLines = envVars.split("\n").filter((line) => {
|
|
4138
|
+
const match = line.match(/^([A-Z0-9_]+)=/);
|
|
4139
|
+
if (!match) {
|
|
4140
|
+
return false;
|
|
4141
|
+
}
|
|
4142
|
+
if (match[1] === "SNIPARA_API_KEY") {
|
|
4143
|
+
return !content.includes("SNIPARA_API_KEY=") && !answers.apiKey.includes("YOUR_");
|
|
4144
|
+
}
|
|
4145
|
+
return !content.includes(`${match[1]}=`);
|
|
4146
|
+
});
|
|
4147
|
+
if (missingLines.length > 0) {
|
|
3925
4148
|
fs.appendFileSync(targetEnvPath, `
|
|
3926
4149
|
|
|
3927
|
-
${
|
|
4150
|
+
${missingLines.join("\n")}
|
|
3928
4151
|
`);
|
|
3929
4152
|
console.log(chalk.green(` \u2713 Updated ${path.basename(targetEnvPath)}`));
|
|
3930
4153
|
}
|
|
3931
4154
|
return;
|
|
3932
4155
|
}
|
|
3933
|
-
if (!answers.apiKey.includes("YOUR_") || answers.llmApiKey) {
|
|
4156
|
+
if (!answers.apiKey.includes("YOUR_") && answers.profile !== "runtime-only" || answers.llmApiKey) {
|
|
3934
4157
|
fs.writeFileSync(targetEnvPath, envVars + "\n");
|
|
3935
4158
|
console.log(chalk.green(` \u2713 Created ${path.basename(targetEnvPath)}`));
|
|
3936
4159
|
}
|
|
@@ -3946,7 +4169,7 @@ async function createCompanionConfig(answers) {
|
|
|
3946
4169
|
const workspaceConfig = {
|
|
3947
4170
|
...existingWorkspaceConfig,
|
|
3948
4171
|
apiKey: answers.apiKey,
|
|
3949
|
-
apiUrl: API_URL,
|
|
4172
|
+
apiUrl: answers.deployment === "hosted" ? API_URL : answers.serverUrl,
|
|
3950
4173
|
projectId: answers.projectId || answers.projectSlug,
|
|
3951
4174
|
sessionId: getCorrelationSessionId(answers),
|
|
3952
4175
|
client: getAutomationClientId(answers.aiClient)
|
|
@@ -3991,7 +4214,7 @@ async function installLocalCodeOverlayHooks(answers) {
|
|
|
3991
4214
|
}
|
|
3992
4215
|
}
|
|
3993
4216
|
async function syncRemoteAutomationClient(answers, options) {
|
|
3994
|
-
if (!options.enabled || answers.apiKeyType === "none" || !answers.apiKey || answers.apiKey.includes("YOUR_") || !answers.projectSlug) {
|
|
4217
|
+
if (answers.deployment !== "hosted" || !options.enabled || answers.apiKeyType === "none" || !answers.apiKey || answers.apiKey.includes("YOUR_") || !answers.projectSlug) {
|
|
3995
4218
|
return;
|
|
3996
4219
|
}
|
|
3997
4220
|
const automationClient = getAutomationClientId(answers.aiClient);
|
|
@@ -4035,7 +4258,7 @@ function buildActivationSurfaceContract(client) {
|
|
|
4035
4258
|
const shared = {
|
|
4036
4259
|
doctrine: "one_activation_engine_multiple_entry_points",
|
|
4037
4260
|
engine: "create-snipara",
|
|
4038
|
-
firstSuccessState: "
|
|
4261
|
+
firstSuccessState: "The configured Snipara endpoint connected, local workspace context was seeded, and a First Work Brief is ready for the next agent session.",
|
|
4039
4262
|
mustNotDuplicate: [
|
|
4040
4263
|
"Local source scan heuristics",
|
|
4041
4264
|
"First Work Brief generation",
|
|
@@ -4477,6 +4700,8 @@ function getClientAutomationCommand(client) {
|
|
|
4477
4700
|
function buildClientPackMarkdown(answers, commands, guidance) {
|
|
4478
4701
|
const clientName = getClientDisplayName(answers.aiClient);
|
|
4479
4702
|
const automationCommand = getClientAutomationCommand(answers.aiClient);
|
|
4703
|
+
const deploymentLabel = getDeploymentLabel(answers.deployment);
|
|
4704
|
+
const toolNamespace = answers.deployment === "self-hosted" ? "rlm_*" : "snipara_*";
|
|
4480
4705
|
const bootstrapCommand = commands.find(
|
|
4481
4706
|
(command) => command.title === "Bootstrap local companion"
|
|
4482
4707
|
);
|
|
@@ -4496,9 +4721,9 @@ Use \`AGENTS.md\` as the shared source of agent behavior, then apply the client-
|
|
|
4496
4721
|
- Next surface action: ${surfaceContract.nextSurfaceAction}
|
|
4497
4722
|
|
|
4498
4723
|
The client-specific files in this pack should not reimplement local source scanning,
|
|
4499
|
-
First Work Brief generation, memory-candidate extraction, or
|
|
4724
|
+
First Work Brief generation, memory-candidate extraction, or MCP config semantics.
|
|
4500
4725
|
|
|
4501
|
-
##
|
|
4726
|
+
## ${deploymentLabel}
|
|
4502
4727
|
|
|
4503
4728
|
- Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
|
|
4504
4729
|
- Auth: \`SNIPARA_API_KEY\` from the environment or local client secret store
|
|
@@ -4511,7 +4736,7 @@ ${getClientConfigArtifacts(answers.aiClient).map((item) => `- \`${item}\``).join
|
|
|
4511
4736
|
## Recommended Setup
|
|
4512
4737
|
|
|
4513
4738
|
${bootstrapCommand ? `1. Bootstrap companion: \`${bootstrapCommand.command}\`` : `1. Bootstrap companion with \`${COMPANION_NPX_COMMAND} init\`.`}
|
|
4514
|
-
2. Confirm the client can list or call the \`
|
|
4739
|
+
2. Confirm the client can list or call the \`${toolNamespace}\` MCP tools.
|
|
4515
4740
|
3. Keep exact edits, tests, and dirty working-tree checks local; use Snipara for memory, source truth, and indexed code graph context.
|
|
4516
4741
|
${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."}
|
|
4517
4742
|
|
|
@@ -4542,10 +4767,11 @@ async function createCompanionPack(answers) {
|
|
|
4542
4767
|
const guidance = [UNIVERSAL_CLIENT_GUIDANCE, ...getClientGuidance(answers.aiClient)];
|
|
4543
4768
|
const detection = detectEnvironment();
|
|
4544
4769
|
const clientPackFilename = getClientPackFilename(answers.aiClient);
|
|
4770
|
+
const deploymentLabel = getDeploymentLabel(answers.deployment);
|
|
4545
4771
|
fs.mkdirSync(packDir, { recursive: true });
|
|
4546
4772
|
const readme = `# Snipara Companion Pack
|
|
4547
4773
|
|
|
4548
|
-
This project uses
|
|
4774
|
+
This project uses ${deploymentLabel} as a project intelligence layer with source-backed context, reviewed memory, and local companion workflow helpers.
|
|
4549
4775
|
The activation doctrine is: one shared activation engine, multiple entry points.
|
|
4550
4776
|
\`create-snipara\` is the canonical bootstrap; editor extensions and plugins should provide UI, rules, or handoff affordances without forking the activation engine.
|
|
4551
4777
|
|
|
@@ -4557,7 +4783,7 @@ The activation doctrine is: one shared activation engine, multiple entry points.
|
|
|
4557
4783
|
- AI client: \`${answers.aiClient}\`
|
|
4558
4784
|
- Client pack: \`${clientPackFilename}\`
|
|
4559
4785
|
- Installed local companion: \`${answers.installCompanion ? "yes" : "no"}\`
|
|
4560
|
-
- Generated
|
|
4786
|
+
- Generated ${deploymentLabel} MCP config: \`${answers.profile !== "runtime-only" ? "yes" : "no"}\`
|
|
4561
4787
|
- Installed local snipara-mcp package: \`${answers.installMcp ? "yes" : "no"}\`
|
|
4562
4788
|
- Installed Snipara Sandbox: \`${answers.installRuntime ? "yes" : "no"}\`
|
|
4563
4789
|
- Installed Snipara Orchestrator: \`${answers.installOrchestrator ? "yes" : "no"}\`
|
|
@@ -4633,9 +4859,9 @@ Inspect local drift:
|
|
|
4633
4859
|
|
|
4634
4860
|
\`${CREATE_SNIPARA_NPX_COMMAND} repair --with-companion\``}
|
|
4635
4861
|
|
|
4636
|
-
GitHub automation remains
|
|
4637
|
-
sync and PR Answer Packs.
|
|
4638
|
-
|
|
4862
|
+
GitHub automation remains a Cloud-only option after first value: it gives Snipara a canonical
|
|
4863
|
+
hosted repository sync and PR Answer Packs. In self-hosted mode, keep source activation local and
|
|
4864
|
+
do not send repository data to the Cloud unless the user explicitly starts a separate Cloud setup.
|
|
4639
4865
|
|
|
4640
4866
|
## GitHub PR Answer Packs
|
|
4641
4867
|
|
|
@@ -4977,10 +5203,10 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
|
|
|
4977
5203
|
detail: detection.files.sniparaSkillPack ? ".snipara/templates/skills contains the Snipara skill pack" : "Snipara skill pack missing from .snipara/templates/skills"
|
|
4978
5204
|
});
|
|
4979
5205
|
if (targetClient === "codex") {
|
|
4980
|
-
checks.push(getCodexConfigCheck());
|
|
5206
|
+
checks.push(getCodexConfigCheck(effectiveAnswers));
|
|
4981
5207
|
}
|
|
4982
5208
|
if (targetClient === "mistral") {
|
|
4983
|
-
checks.push(getMistralConfigCheck());
|
|
5209
|
+
checks.push(getMistralConfigCheck(effectiveAnswers));
|
|
4984
5210
|
}
|
|
4985
5211
|
checks.push({
|
|
4986
5212
|
name: "env_files",
|
|
@@ -5010,7 +5236,7 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
|
|
|
5010
5236
|
checks.push({
|
|
5011
5237
|
name: "hosted_connection",
|
|
5012
5238
|
status: "warn",
|
|
5013
|
-
detail:
|
|
5239
|
+
detail: `Skipped ${getDeploymentLabel(answers.deployment)} connectivity test because no live API key was available`
|
|
5014
5240
|
});
|
|
5015
5241
|
}
|
|
5016
5242
|
for (const check of checks) {
|
|
@@ -5029,7 +5255,7 @@ function writeDoctorReport(report) {
|
|
|
5029
5255
|
fs.writeFileSync(path.join(reportDir, "doctor.json"), JSON.stringify(report, null, 2) + "\n");
|
|
5030
5256
|
console.log(chalk.green(" \u2713 Wrote .snipara/companion/doctor.json"));
|
|
5031
5257
|
}
|
|
5032
|
-
function getCodexConfigCheck() {
|
|
5258
|
+
function getCodexConfigCheck(answers) {
|
|
5033
5259
|
const candidates = [
|
|
5034
5260
|
{
|
|
5035
5261
|
label: ".codex/config.toml",
|
|
@@ -5069,8 +5295,8 @@ function getCodexConfigCheck() {
|
|
|
5069
5295
|
const nonSniparaBearerEnvVars = configuredBearerEnvVars.filter(
|
|
5070
5296
|
(candidate) => candidate.value !== "SNIPARA_API_KEY"
|
|
5071
5297
|
);
|
|
5072
|
-
const
|
|
5073
|
-
(candidate) => candidate.content.includes(
|
|
5298
|
+
const hasConfiguredEndpoint = present.some(
|
|
5299
|
+
(candidate) => candidate.content.includes(buildMcpEndpoint(answers))
|
|
5074
5300
|
);
|
|
5075
5301
|
const labels = present.map((candidate) => candidate.label).join(", ");
|
|
5076
5302
|
if (nonSniparaBearerEnvVars.length > 0) {
|
|
@@ -5081,20 +5307,20 @@ function getCodexConfigCheck() {
|
|
|
5081
5307
|
detail: `Codex config uses a non-Snipara bearer env var (${details}); use SNIPARA_API_KEY and restart Codex`
|
|
5082
5308
|
};
|
|
5083
5309
|
}
|
|
5084
|
-
if (!usesSniparaEnv || !
|
|
5310
|
+
if (!usesSniparaEnv || !hasConfiguredEndpoint) {
|
|
5085
5311
|
return {
|
|
5086
5312
|
name: "codex_config",
|
|
5087
5313
|
status: "warn",
|
|
5088
|
-
detail: `Codex config found in ${labels}, but expected
|
|
5314
|
+
detail: `Codex config found in ${labels}, but expected the configured ${getDeploymentLabel(answers.deployment)} endpoint plus bearer_token_env_var = "SNIPARA_API_KEY"`
|
|
5089
5315
|
};
|
|
5090
5316
|
}
|
|
5091
5317
|
return {
|
|
5092
5318
|
name: "codex_config",
|
|
5093
5319
|
status: "pass",
|
|
5094
|
-
detail: `Codex config uses
|
|
5320
|
+
detail: `Codex config uses ${getDeploymentLabel(answers.deployment)} and SNIPARA_API_KEY (${labels}); restart Codex after config changes`
|
|
5095
5321
|
};
|
|
5096
5322
|
}
|
|
5097
|
-
function getMistralConfigCheck() {
|
|
5323
|
+
function getMistralConfigCheck(answers) {
|
|
5098
5324
|
const candidates = [
|
|
5099
5325
|
{
|
|
5100
5326
|
label: ".vibe/config.toml",
|
|
@@ -5120,24 +5346,24 @@ function getMistralConfigCheck() {
|
|
|
5120
5346
|
detail: "No Mistral Vibe config found; use .snipara/templates/mistral-vibe-config.toml or add Snipara as a Le Chat Custom MCP Connector"
|
|
5121
5347
|
};
|
|
5122
5348
|
}
|
|
5123
|
-
const
|
|
5124
|
-
(candidate) => candidate.content.includes(
|
|
5349
|
+
const hasConfiguredEndpoint = present.some(
|
|
5350
|
+
(candidate) => candidate.content.includes(buildMcpEndpoint(answers))
|
|
5125
5351
|
);
|
|
5126
5352
|
const hasSniparaEnv = present.some(
|
|
5127
5353
|
(candidate) => candidate.content.includes('api_key_env = "SNIPARA_API_KEY"')
|
|
5128
5354
|
);
|
|
5129
5355
|
const labels = present.map((candidate) => candidate.label).join(", ");
|
|
5130
|
-
if (!
|
|
5356
|
+
if (!hasConfiguredEndpoint || !hasSniparaEnv) {
|
|
5131
5357
|
return {
|
|
5132
5358
|
name: "mistral_config",
|
|
5133
5359
|
status: "warn",
|
|
5134
|
-
detail: `Mistral config found in ${labels}, but expected
|
|
5360
|
+
detail: `Mistral config found in ${labels}, but expected the configured ${getDeploymentLabel(answers.deployment)} endpoint plus api_key_env = "SNIPARA_API_KEY"`
|
|
5135
5361
|
};
|
|
5136
5362
|
}
|
|
5137
5363
|
return {
|
|
5138
5364
|
name: "mistral_config",
|
|
5139
5365
|
status: "pass",
|
|
5140
|
-
detail: `Mistral Vibe config uses
|
|
5366
|
+
detail: `Mistral Vibe config uses ${getDeploymentLabel(answers.deployment)} and SNIPARA_API_KEY (${labels}); restart Vibe after config changes`
|
|
5141
5367
|
};
|
|
5142
5368
|
}
|
|
5143
5369
|
function printDoctorReport(detection, report) {
|
|
@@ -5159,7 +5385,8 @@ function printDoctorReport(detection, report) {
|
|
|
5159
5385
|
console.log("");
|
|
5160
5386
|
}
|
|
5161
5387
|
async function testHostedConnection(answers) {
|
|
5162
|
-
const
|
|
5388
|
+
const deploymentLabel = getDeploymentLabel(answers.deployment);
|
|
5389
|
+
const spinner = ora(`Connecting to ${deploymentLabel}...`).start();
|
|
5163
5390
|
try {
|
|
5164
5391
|
const toolsResponse = await callHostedMcp(answers, "tools/list", {});
|
|
5165
5392
|
if (!toolsResponse.ok) {
|
|
@@ -5182,7 +5409,9 @@ async function testHostedConnection(answers) {
|
|
|
5182
5409
|
}
|
|
5183
5410
|
];
|
|
5184
5411
|
}
|
|
5185
|
-
spinner.succeed(
|
|
5412
|
+
spinner.succeed(
|
|
5413
|
+
answers.deployment === "hosted" ? "Connected to Snipara Hosted MCP." : `Connected to ${deploymentLabel}.`
|
|
5414
|
+
);
|
|
5186
5415
|
const toolNames = extractMcpToolNames(toolsResponse.data);
|
|
5187
5416
|
const hasSniparaTools = toolNames.some((name) => name.startsWith("snipara_"));
|
|
5188
5417
|
const advertisesLegacyTools = toolNames.some((name) => name.startsWith("rlm_"));
|
|
@@ -5194,29 +5423,32 @@ async function testHostedConnection(answers) {
|
|
|
5194
5423
|
},
|
|
5195
5424
|
{
|
|
5196
5425
|
name: "hosted_snipara_tools",
|
|
5197
|
-
status: hasSniparaTools ? "pass" : "warn",
|
|
5198
|
-
detail: hasSniparaTools ? "snipara_
|
|
5426
|
+
status: hasSniparaTools || advertisesLegacyTools ? "pass" : "warn",
|
|
5427
|
+
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"
|
|
5199
5428
|
},
|
|
5200
5429
|
{
|
|
5201
5430
|
name: "hosted_legacy_tools_hidden",
|
|
5202
|
-
status: advertisesLegacyTools ? "warn" : "pass",
|
|
5203
|
-
detail: advertisesLegacyTools ? "
|
|
5431
|
+
status: answers.deployment === "hosted" && advertisesLegacyTools ? "warn" : "pass",
|
|
5432
|
+
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"
|
|
5204
5433
|
}
|
|
5205
5434
|
];
|
|
5206
|
-
|
|
5435
|
+
if (answers.deployment === "self-hosted") {
|
|
5436
|
+
checks.push(...await testSelfHostedCapabilities(answers));
|
|
5437
|
+
}
|
|
5438
|
+
const settingsTool = answers.deployment === "self-hosted" ? "rlm_settings" : "snipara_settings";
|
|
5207
5439
|
const settingsResponse = await callHostedMcp(answers, "tools/call", {
|
|
5208
5440
|
name: settingsTool,
|
|
5209
5441
|
arguments: {}
|
|
5210
5442
|
});
|
|
5211
5443
|
if (settingsResponse.ok && !settingsResponse.data.error) {
|
|
5212
5444
|
checks.push({
|
|
5213
|
-
name: "
|
|
5445
|
+
name: "deployment_settings",
|
|
5214
5446
|
status: "pass",
|
|
5215
5447
|
detail: `${settingsTool} tool call succeeded`
|
|
5216
5448
|
});
|
|
5217
5449
|
} else {
|
|
5218
5450
|
checks.push({
|
|
5219
|
-
name: "
|
|
5451
|
+
name: "deployment_settings",
|
|
5220
5452
|
status: "warn",
|
|
5221
5453
|
detail: settingsResponse.ok ? describeJsonRpcError(settingsResponse.data) : describeHostedHttpFailure(settingsResponse.status, settingsResponse.bodyText)
|
|
5222
5454
|
});
|
|
@@ -5228,7 +5460,40 @@ async function testHostedConnection(answers) {
|
|
|
5228
5460
|
{
|
|
5229
5461
|
name: "hosted_connection",
|
|
5230
5462
|
status: "warn",
|
|
5231
|
-
detail:
|
|
5463
|
+
detail: `Could not reach ${deploymentLabel} from this machine`
|
|
5464
|
+
}
|
|
5465
|
+
];
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
async function testSelfHostedCapabilities(answers) {
|
|
5469
|
+
try {
|
|
5470
|
+
const endpoint = new URL(answers.serverUrl);
|
|
5471
|
+
const response = await fetch(`${endpoint.origin}/capabilities`, {
|
|
5472
|
+
headers: { "X-API-Key": answers.apiKey }
|
|
5473
|
+
});
|
|
5474
|
+
const body = await response.json();
|
|
5475
|
+
if (!response.ok) {
|
|
5476
|
+
return [
|
|
5477
|
+
{
|
|
5478
|
+
name: "self_hosted_contract",
|
|
5479
|
+
status: "warn",
|
|
5480
|
+
detail: `Could not verify /capabilities (HTTP ${response.status})`
|
|
5481
|
+
}
|
|
5482
|
+
];
|
|
5483
|
+
}
|
|
5484
|
+
return [
|
|
5485
|
+
{
|
|
5486
|
+
name: "self_hosted_contract",
|
|
5487
|
+
status: body.contract === "snipara-server-oss-v2" ? "pass" : "warn",
|
|
5488
|
+
detail: body.contract === "snipara-server-oss-v2" ? "snipara-server-oss-v2 contract verified" : `Unexpected self-hosted contract${body.contract ? `: ${body.contract}` : ""}`
|
|
5489
|
+
}
|
|
5490
|
+
];
|
|
5491
|
+
} catch {
|
|
5492
|
+
return [
|
|
5493
|
+
{
|
|
5494
|
+
name: "self_hosted_contract",
|
|
5495
|
+
status: "warn",
|
|
5496
|
+
detail: "Self-hosted /capabilities endpoint could not be verified"
|
|
5232
5497
|
}
|
|
5233
5498
|
];
|
|
5234
5499
|
}
|
|
@@ -5243,7 +5508,7 @@ function extractMcpToolNames(data) {
|
|
|
5243
5508
|
).filter(Boolean);
|
|
5244
5509
|
}
|
|
5245
5510
|
async function callHostedMcp(answers, method, params) {
|
|
5246
|
-
const response = await fetch(
|
|
5511
|
+
const response = await fetch(buildMcpEndpoint(answers), {
|
|
5247
5512
|
method: "POST",
|
|
5248
5513
|
headers: {
|
|
5249
5514
|
"Content-Type": "application/json",
|
|
@@ -5315,8 +5580,24 @@ function printActivationSummary(manifest) {
|
|
|
5315
5580
|
` ${formatActivationStatus(manifest.lanes.connection.status)} Connected - ${manifest.lanes.connection.detail}`
|
|
5316
5581
|
);
|
|
5317
5582
|
console.log(` ${formatActivationStatus(contextLane.status)} Context - ${contextLane.detail}`);
|
|
5318
|
-
|
|
5319
|
-
|
|
5583
|
+
if (manifest.valueProof.status === "ready") {
|
|
5584
|
+
console.log(
|
|
5585
|
+
` ${chalk.green("\u2713 ready")} Value proof - ${manifest.valueProof.detail} (${manifest.valueProof.evidence || "evidence recorded"})`
|
|
5586
|
+
);
|
|
5587
|
+
console.log(chalk.green("\n Activation ready: First grounded answer verified."));
|
|
5588
|
+
} else if (manifest.valueProof.status === "failed") {
|
|
5589
|
+
console.log(
|
|
5590
|
+
` ${chalk.red("\u2717 failed")} Value proof - ${manifest.valueProof.error || manifest.valueProof.detail}`
|
|
5591
|
+
);
|
|
5592
|
+
console.log(
|
|
5593
|
+
chalk.yellow(
|
|
5594
|
+
"\n Activation needs attention: run create-snipara doctor after checking the MCP endpoint."
|
|
5595
|
+
)
|
|
5596
|
+
);
|
|
5597
|
+
} else {
|
|
5598
|
+
console.log(` ${chalk.dim("\u25CB pending")} First grounded answer`);
|
|
5599
|
+
console.log(chalk.yellow("\n Activation pending: first grounded answer needed."));
|
|
5600
|
+
}
|
|
5320
5601
|
console.log(
|
|
5321
5602
|
chalk.dim(
|
|
5322
5603
|
`
|
|
@@ -5440,6 +5721,7 @@ function detectEnvironment() {
|
|
|
5440
5721
|
existingConfig: {
|
|
5441
5722
|
slug: existingConfig?.slug,
|
|
5442
5723
|
projectId: existingConfig?.projectId,
|
|
5724
|
+
serverUrl: existingConfig?.serverUrl,
|
|
5443
5725
|
hasSniparaServer: existingConfig?.hasSniparaServer ?? false,
|
|
5444
5726
|
hasRuntimeServer: existingConfig?.hasRuntimeServer ?? false
|
|
5445
5727
|
}
|
|
@@ -5448,19 +5730,22 @@ function detectEnvironment() {
|
|
|
5448
5730
|
function loadExistingAnswers(options) {
|
|
5449
5731
|
const detection = detectEnvironment();
|
|
5450
5732
|
const effectiveProfile = resolveMaintenanceProfile(detection, options.profile);
|
|
5733
|
+
const deployment = resolveRequestedDeployment(options);
|
|
5451
5734
|
const providedApiKey = options.apiKey?.trim();
|
|
5452
|
-
if (providedApiKey && !
|
|
5453
|
-
throw new Error(manualApiKeyValidationMessage());
|
|
5735
|
+
if (providedApiKey && !isValidApiKey(providedApiKey, deployment)) {
|
|
5736
|
+
throw new Error(manualApiKeyValidationMessage(deployment));
|
|
5454
5737
|
}
|
|
5455
5738
|
if (providedApiKey) {
|
|
5456
5739
|
warnApiKeyCliExposure();
|
|
5457
5740
|
}
|
|
5458
|
-
const envApiKey = readApiKeyFromEnvFiles();
|
|
5741
|
+
const envApiKey = readApiKeyFromEnvFiles(deployment);
|
|
5459
5742
|
const apiKey = providedApiKey || envApiKey || "YOUR_API_KEY_HERE";
|
|
5460
5743
|
const apiKeyType = effectiveProfile === "runtime-only" ? "none" : providedApiKey || envApiKey ? "api" : "none";
|
|
5461
5744
|
return buildAnswersFromProfile({
|
|
5462
5745
|
projectSlug: options.slug || detection.projectSlug || "my-project",
|
|
5463
5746
|
projectId: options.projectId || detection.existingConfig.projectId || "",
|
|
5747
|
+
deployment,
|
|
5748
|
+
serverUrl: options.serverUrl || process.env.SNIPARA_MCP_URL || getExistingConfig()?.serverUrl || "http://localhost:8000/mcp/local",
|
|
5464
5749
|
apiKey,
|
|
5465
5750
|
apiKeyType,
|
|
5466
5751
|
profile: effectiveProfile,
|
|
@@ -5487,10 +5772,13 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
|
|
|
5487
5772
|
}
|
|
5488
5773
|
return "hosted-only";
|
|
5489
5774
|
}
|
|
5490
|
-
function readApiKeyFromEnvFiles() {
|
|
5775
|
+
function readApiKeyFromEnvFiles(deployment = "hosted") {
|
|
5491
5776
|
const candidates = [".env.local", ".env"];
|
|
5492
5777
|
for (const file of candidates) {
|
|
5493
|
-
const value = readEnvValueFromFile(
|
|
5778
|
+
const value = readEnvValueFromFile(
|
|
5779
|
+
file,
|
|
5780
|
+
deployment === "self-hosted" ? ["SNIPARA_LOCAL_API_KEY", "SNIPARA_API_KEY"] : ["SNIPARA_API_KEY"]
|
|
5781
|
+
);
|
|
5494
5782
|
if (value) {
|
|
5495
5783
|
return value;
|
|
5496
5784
|
}
|
|
@@ -5797,6 +6085,7 @@ function getExistingConfig() {
|
|
|
5797
6085
|
return {
|
|
5798
6086
|
slug: extractSlugFromMcpEndpoint(sniparaServer?.url) || void 0,
|
|
5799
6087
|
projectId: readExistingCompanionProjectId(),
|
|
6088
|
+
serverUrl: typeof sniparaServer?.url === "string" ? normalizeMcpEndpoint(sniparaServer.url) : void 0,
|
|
5800
6089
|
hasSniparaServer: Boolean(sniparaServer),
|
|
5801
6090
|
hasRuntimeServer: Boolean(runtimeServer)
|
|
5802
6091
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-snipara",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Onboard Snipara
|
|
3
|
+
"version": "2.1.1",
|
|
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",
|