create-snipara 1.3.3 → 1.3.6
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 +157 -13
- package/dist/index.js +254 -31
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
# create-snipara
|
|
2
2
|
|
|
3
|
-
One-command
|
|
3
|
+
One-command onboarding and maintenance CLI for **Snipara Hosted MCP Context + Memory** with optional `snipara-companion` and optional RLM-Runtime.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx create-snipara
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
`create-snipara` is onboarding
|
|
9
|
+
`create-snipara` is the simple onboarding package for the agent path. It creates or reuses auth, writes Hosted MCP config, and prepares the local project so an LLM client can use Snipara context and reviewed memory.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Use it for:
|
|
12
|
+
|
|
13
|
+
- Hosted MCP onboarding for Codex, Claude Code, Cursor, ChatGPT Desktop, and similar clients
|
|
14
|
+
- free account signup with reviewed Context + Memory
|
|
15
|
+
- local project wiring, `doctor`, `repair`, and `upgrade`
|
|
16
|
+
- GitHub repository sync approval when the current folder is a GitHub repo
|
|
17
|
+
|
|
18
|
+
Do not use it to choose business documents one by one. For local folders, mounted Drive/SharePoint exports, old offers, or PowerPoints, use `snipara-business`.
|
|
19
|
+
|
|
20
|
+
The product surfaces remain:
|
|
21
|
+
|
|
22
|
+
- Hosted MCP for LLM agents, context, and reviewed memory
|
|
12
23
|
- HTTP API / SDK for apps, integrators, backends, and pipelines
|
|
13
24
|
|
|
14
25
|
The CLI can wire local helpers around those surfaces:
|
|
@@ -31,10 +42,37 @@ The CLI now works around explicit install profiles:
|
|
|
31
42
|
|
|
32
43
|
Default profile: `hosted-only`
|
|
33
44
|
|
|
45
|
+
## Free Account Fit
|
|
46
|
+
|
|
47
|
+
Free onboarding is now **Context + Memory**, not Context-only:
|
|
48
|
+
|
|
49
|
+
- Context Free: one project and 100 context queries/month
|
|
50
|
+
- Memory Free: reviewed hosted memories for continuity across sessions
|
|
51
|
+
- Hosted MCP: direct connection for Claude Code, Codex, Cursor, ChatGPT Desktop, and other MCP clients
|
|
52
|
+
|
|
53
|
+
The CLI opens a browser device-flow login when the user does not already have a key, auto-provisions the free Context + Memory account when needed, then writes the returned project key into the local MCP config.
|
|
54
|
+
|
|
55
|
+
## The Two Approval Paths
|
|
56
|
+
|
|
57
|
+
`create-snipara` deliberately splits setup into two user-friendly surfaces:
|
|
58
|
+
|
|
59
|
+
1. **Terminal**
|
|
60
|
+
Connects the agent, writes local config, and prepares the workstation.
|
|
61
|
+
2. **Browser**
|
|
62
|
+
Handles account login and GitHub approval because those source permissions must be chosen explicitly by the user.
|
|
63
|
+
|
|
64
|
+
That distinction matters:
|
|
65
|
+
|
|
66
|
+
- the terminal is for local setup
|
|
67
|
+
- the browser is for identity and source approval
|
|
68
|
+
- the MCP is for agent usage after setup, not for initial GitHub permission selection
|
|
69
|
+
|
|
34
70
|
## What It Does
|
|
35
71
|
|
|
36
72
|
- detects local environment and project state
|
|
73
|
+
- opens the browser device-flow free Context + Memory account flow when needed
|
|
37
74
|
- writes Hosted MCP configuration
|
|
75
|
+
- detects a GitHub remote and can open the Snipara GitHub App automation path
|
|
38
76
|
- installs `snipara-companion` when the selected profile needs local helper workflows
|
|
39
77
|
- installs optional `rlm-runtime`
|
|
40
78
|
- configures `.mcp.json`
|
|
@@ -45,6 +83,17 @@ Default profile: `hosted-only`
|
|
|
45
83
|
- optionally installs Claude Code hooks
|
|
46
84
|
- supports `doctor`, `repair`, `upgrade`, and `print-config`
|
|
47
85
|
|
|
86
|
+
## Product Boundary
|
|
87
|
+
|
|
88
|
+
Keep the package roles simple:
|
|
89
|
+
|
|
90
|
+
- `create-snipara` = connect the user to Hosted MCP Context + Memory
|
|
91
|
+
- `create-snipara --github` = connect the current GitHub repository through the browser approval flow
|
|
92
|
+
- `snipara-business` = prepare business context from local or mounted folders
|
|
93
|
+
- Hosted MCP = the runtime surface the LLM actually uses after setup
|
|
94
|
+
|
|
95
|
+
This avoids mixing repo sync, business document review, and agent runtime into one unclear workflow.
|
|
96
|
+
|
|
48
97
|
## Quick Start
|
|
49
98
|
|
|
50
99
|
```bash
|
|
@@ -56,9 +105,10 @@ The interactive flow now asks for:
|
|
|
56
105
|
- install profile
|
|
57
106
|
- project slug
|
|
58
107
|
- optional project id
|
|
59
|
-
- API key
|
|
108
|
+
- API key path: project key, team key, free signup, or add key later
|
|
60
109
|
- AI client
|
|
61
110
|
- whether hooks should be generated
|
|
111
|
+
- whether to open GitHub repository automation when a GitHub remote is detected
|
|
62
112
|
- optional runtime provider config when `rlm-runtime` is installed
|
|
63
113
|
|
|
64
114
|
## Main Commands
|
|
@@ -70,6 +120,8 @@ npx create-snipara
|
|
|
70
120
|
npx create-snipara init
|
|
71
121
|
```
|
|
72
122
|
|
|
123
|
+
Use this when the goal is "connect my LLM client to Snipara."
|
|
124
|
+
|
|
73
125
|
### Inspect local setup
|
|
74
126
|
|
|
75
127
|
```bash
|
|
@@ -112,6 +164,42 @@ npx create-snipara print-config
|
|
|
112
164
|
npx create-snipara print-config --json
|
|
113
165
|
```
|
|
114
166
|
|
|
167
|
+
## Source Selection Rules
|
|
168
|
+
|
|
169
|
+
### GitHub repositories
|
|
170
|
+
|
|
171
|
+
For source-code repositories, let the user approve access in the browser:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npx create-snipara --github
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The CLI detects the current repo and can prefill:
|
|
178
|
+
|
|
179
|
+
- `owner/repo`
|
|
180
|
+
- current branch
|
|
181
|
+
- optional path filter such as `docs/` or `README.md`
|
|
182
|
+
|
|
183
|
+
The browser flow is where the user:
|
|
184
|
+
|
|
185
|
+
- signs in to Snipara if needed
|
|
186
|
+
- approves the Snipara GitHub App
|
|
187
|
+
- chooses the GitHub account or organization
|
|
188
|
+
- chooses which repositories Snipara may read
|
|
189
|
+
|
|
190
|
+
This is the correct product boundary. Repository access is explicit, auditable, and revocable.
|
|
191
|
+
|
|
192
|
+
### Local business folders
|
|
193
|
+
|
|
194
|
+
For business documents, do not ask the user to pick files through the MCP setup flow.
|
|
195
|
+
Instead:
|
|
196
|
+
|
|
197
|
+
1. connect Hosted MCP with `create-snipara`
|
|
198
|
+
2. materialize the relevant local or mounted folder
|
|
199
|
+
3. run `snipara-business` to build a reviewable manifest
|
|
200
|
+
|
|
201
|
+
That keeps business truth review separate from agent onboarding.
|
|
202
|
+
|
|
115
203
|
## Common Flags
|
|
116
204
|
|
|
117
205
|
```bash
|
|
@@ -126,11 +214,16 @@ npx create-snipara --runtime-only
|
|
|
126
214
|
# Non-interactive setup
|
|
127
215
|
npx create-snipara -y --api-key rlm_xxx --slug my-project
|
|
128
216
|
|
|
217
|
+
# Open GitHub automation after setup when a GitHub remote is detected
|
|
218
|
+
npx create-snipara --github
|
|
219
|
+
npx create-snipara --github-path docs/
|
|
220
|
+
|
|
129
221
|
# Skip selected layers
|
|
130
222
|
npx create-snipara --skip-companion
|
|
131
223
|
npx create-snipara --skip-mcp # Skip local snipara-mcp package if explicitly requested
|
|
132
224
|
npx create-snipara --skip-runtime
|
|
133
225
|
npx create-snipara --skip-hooks
|
|
226
|
+
npx create-snipara --skip-github
|
|
134
227
|
|
|
135
228
|
# Force extra layers during maintenance
|
|
136
229
|
npx create-snipara repair --with-hooks
|
|
@@ -141,7 +234,7 @@ npx create-snipara upgrade --with-runtime
|
|
|
141
234
|
|
|
142
235
|
The intended split is:
|
|
143
236
|
|
|
144
|
-
- `create-snipara`:
|
|
237
|
+
- `create-snipara`: Context + Memory onboarding, auth, config, inspect, repair, and upgrade
|
|
145
238
|
- `snipara-companion`: optional ongoing local runtime workflows through `rlm-hook`
|
|
146
239
|
- `snipara-mcp`: local stdio bridge, client-package development, compatibility, and testing
|
|
147
240
|
- `rlm-runtime`: optional local execution engine
|
|
@@ -181,15 +274,37 @@ Use this for dashboardless business-context imports after an LLM client has
|
|
|
181
274
|
materialized Drive, Notion, Gmail, or local files into a folder. For source-code
|
|
182
275
|
repositories, use the GitHub OAuth/code onboarding path instead.
|
|
183
276
|
|
|
277
|
+
## GitHub Automation
|
|
278
|
+
|
|
279
|
+
When run inside a GitHub-backed repository, `create-snipara` can open the Snipara GitHub App flow with the detected `owner/repo` and branch already carried through:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npx create-snipara --github
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The browser flow lets the user:
|
|
286
|
+
|
|
287
|
+
- install or update the Snipara GitHub App
|
|
288
|
+
- pick the detected repo
|
|
289
|
+
- create or reuse the matching Snipara project
|
|
290
|
+
- enable push sync
|
|
291
|
+
- start the first documentation sync
|
|
292
|
+
|
|
293
|
+
Hosted memory stays attached to the same Snipara account.
|
|
294
|
+
|
|
295
|
+
Recommended explanation to users:
|
|
296
|
+
|
|
297
|
+
> `create-snipara` connects your LLM from the terminal. GitHub sync is approved in the browser because GitHub requires the user to choose which account, organization, and repositories Snipara can read.
|
|
298
|
+
|
|
184
299
|
## API Key Requirements
|
|
185
300
|
|
|
186
|
-
| Tooling | Snipara API key
|
|
187
|
-
| ------------------------------------ |
|
|
188
|
-
| Hosted MCP queries | Required
|
|
189
|
-
| Memory / planning tools | Required
|
|
190
|
-
| `snipara-companion` hosted workflows | Optional
|
|
191
|
-
| `rlm-runtime` `execute_python` MCP | Not needed
|
|
192
|
-
| `rlm run` / `rlm agent` CLI | Optional for context
|
|
301
|
+
| Tooling | Snipara API key | LLM API key |
|
|
302
|
+
| ------------------------------------ | -------------------------- | ----------- |
|
|
303
|
+
| Hosted MCP queries | Required | Not needed |
|
|
304
|
+
| Memory / planning tools | Required with memory scope | Not needed |
|
|
305
|
+
| `snipara-companion` hosted workflows | Optional | Not needed |
|
|
306
|
+
| `rlm-runtime` `execute_python` MCP | Not needed | Not needed |
|
|
307
|
+
| `rlm run` / `rlm agent` CLI | Optional for context | Required |
|
|
193
308
|
|
|
194
309
|
## Example Flows
|
|
195
310
|
|
|
@@ -199,12 +314,41 @@ repositories, use the GitHub OAuth/code onboarding path instead.
|
|
|
199
314
|
npx create-snipara --profile hosted-only
|
|
200
315
|
```
|
|
201
316
|
|
|
317
|
+
Best when the user only wants Hosted MCP Context + Memory.
|
|
318
|
+
|
|
202
319
|
### Full local stack
|
|
203
320
|
|
|
204
321
|
```bash
|
|
205
322
|
npx create-snipara --profile full-stack
|
|
206
323
|
```
|
|
207
324
|
|
|
325
|
+
Best when the user also wants `snipara-companion` and `rlm-runtime`.
|
|
326
|
+
|
|
327
|
+
### Connect the current repository
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
npx create-snipara --github
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Best when the current folder is a GitHub repository and the user wants code or docs sync through the GitHub App.
|
|
334
|
+
|
|
335
|
+
### Connect the current repository with a path filter
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
npx create-snipara --github --github-path docs/
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Best when the user wants a focused first sync instead of the whole repository.
|
|
342
|
+
|
|
343
|
+
### Continue with business documents
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
npx create-snipara
|
|
347
|
+
npx snipara-business init
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Best when the agent connection is done and the next step is business context onboarding from local or mounted folders.
|
|
351
|
+
|
|
208
352
|
### Repair after a broken workstation or new shell
|
|
209
353
|
|
|
210
354
|
```bash
|
|
@@ -223,7 +367,7 @@ npx create-snipara print-config --json
|
|
|
223
367
|
When testing a locally packed tarball, use `npm exec --package` instead of `npx`, for example:
|
|
224
368
|
|
|
225
369
|
```bash
|
|
226
|
-
npm exec --package ./create-snipara-1.3.
|
|
370
|
+
npm exec --package ./create-snipara-1.3.6.tgz create-snipara -- --help
|
|
227
371
|
```
|
|
228
372
|
|
|
229
373
|
This avoids `npx` resolution issues with unpublished tarballs.
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,15 @@ import fs from "fs";
|
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { execSync, spawn } from "child_process";
|
|
11
11
|
import { homedir } from "os";
|
|
12
|
-
var VERSION = "1.3.
|
|
12
|
+
var VERSION = "1.3.6";
|
|
13
13
|
var BASE_URL = "https://www.snipara.com";
|
|
14
14
|
var API_URL = "https://api.snipara.com";
|
|
15
15
|
var COMPANION_PACKAGE_NAME = "snipara-companion";
|
|
16
16
|
var MCP_PACKAGE_NAME = "snipara-mcp";
|
|
17
17
|
var RUNTIME_PACKAGE_NAME = "rlm-runtime[all]";
|
|
18
|
+
var DEVICE_CLIENT_ID = "create-snipara";
|
|
19
|
+
var DeviceAuthorizationAborted = class extends Error {
|
|
20
|
+
};
|
|
18
21
|
function getCompanionInitClientFlag(aiClient) {
|
|
19
22
|
if (aiClient === "cursor") {
|
|
20
23
|
return " --client cursor";
|
|
@@ -27,7 +30,7 @@ function buildProgram() {
|
|
|
27
30
|
program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
28
31
|
"--profile <profile>",
|
|
29
32
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
30
|
-
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--runtime-only", "Install only RLM-Runtime").option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
|
|
33
|
+
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--runtime-only", "Install only RLM-Runtime").option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
|
|
31
34
|
printBanner();
|
|
32
35
|
await runInit(options);
|
|
33
36
|
});
|
|
@@ -58,7 +61,7 @@ function addSharedRootOptions(program) {
|
|
|
58
61
|
program.option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
59
62
|
"--profile <profile>",
|
|
60
63
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
61
|
-
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--runtime-only", "Install only RLM-Runtime").option("-y, --yes", "Accept defaults without prompting");
|
|
64
|
+
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--runtime-only", "Install only RLM-Runtime").option("-y, --yes", "Accept defaults without prompting");
|
|
62
65
|
}
|
|
63
66
|
async function main() {
|
|
64
67
|
const program = buildProgram();
|
|
@@ -83,6 +86,9 @@ async function runInit(options) {
|
|
|
83
86
|
const validationReport = await runValidation(answers, !options.skipTest);
|
|
84
87
|
writeDoctorReport(validationReport);
|
|
85
88
|
printSuccess(answers, validationReport);
|
|
89
|
+
if (answers.configureGitHub && answers.githubRepo) {
|
|
90
|
+
await openGitHubAutomation(answers);
|
|
91
|
+
}
|
|
86
92
|
if (answers.runRlmInit && answers.installRuntime) {
|
|
87
93
|
await runRlmInit();
|
|
88
94
|
}
|
|
@@ -119,20 +125,21 @@ async function runRepair(options) {
|
|
|
119
125
|
async function runUpgrade(options) {
|
|
120
126
|
const detection = detectEnvironment();
|
|
121
127
|
const answers = loadExistingAnswers(options);
|
|
128
|
+
const upgradedAnswers = {
|
|
129
|
+
...answers,
|
|
130
|
+
installMcp: options.withMcp || answers.installMcp,
|
|
131
|
+
installRuntime: options.withRuntime || answers.installRuntime
|
|
132
|
+
};
|
|
122
133
|
console.log(chalk.bold("\n\u2B06\uFE0F Upgrading Local Components\n"));
|
|
123
134
|
if (options.withMcp || options.withRuntime || detection.existingConfig.hasRuntimeServer) {
|
|
124
|
-
await installPythonPackages(
|
|
125
|
-
{
|
|
126
|
-
...answers,
|
|
127
|
-
installMcp: options.withMcp || answers.installMcp,
|
|
128
|
-
installRuntime: options.withRuntime || answers.installRuntime
|
|
129
|
-
},
|
|
130
|
-
true
|
|
131
|
-
);
|
|
135
|
+
await installPythonPackages(upgradedAnswers, true);
|
|
132
136
|
}
|
|
133
137
|
if (options.withCompanion || detection.tooling.companionCli || answers.installCompanion) {
|
|
134
138
|
await installNodeCompanion(true);
|
|
135
139
|
}
|
|
140
|
+
if (options.withMcp || options.withRuntime || detection.files.mcpConfig) {
|
|
141
|
+
await createMcpConfig(upgradedAnswers);
|
|
142
|
+
}
|
|
136
143
|
if (options.withHooks || answers.installHooks) {
|
|
137
144
|
await installClaudeCodeHooks({ ...answers, installHooks: true });
|
|
138
145
|
}
|
|
@@ -169,12 +176,13 @@ function printBanner() {
|
|
|
169
176
|
chalk.cyan(`
|
|
170
177
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
171
178
|
\u2551 \u2551
|
|
172
|
-
\u2551 ${chalk.bold("Snipara")} -
|
|
179
|
+
\u2551 ${chalk.bold("Snipara")} - Context + Memory onboarding \u2551
|
|
173
180
|
\u2551 \u2551
|
|
174
181
|
\u2551 \u2022 Hosted MCP for LLM agents \u2551
|
|
182
|
+
\u2551 \u2022 Free account path with hosted context and reviewed memory \u2551
|
|
175
183
|
\u2551 \u2022 API / SDK for product integrations \u2551
|
|
176
184
|
\u2551 \u2022 Optional local companion and runtime helpers \u2551
|
|
177
|
-
\u2551 \u2022 One CLI for
|
|
185
|
+
\u2551 \u2022 One CLI for auth, config, doctor, repair, and upgrade \u2551
|
|
178
186
|
\u2551 \u2551
|
|
179
187
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
180
188
|
`)
|
|
@@ -186,6 +194,14 @@ async function gatherConfiguration(options) {
|
|
|
186
194
|
let detectedProjectId = "";
|
|
187
195
|
const detectedClient = detectAiClient();
|
|
188
196
|
const existingConfig = getExistingConfig();
|
|
197
|
+
const detectedGitRepo = detectGitRepository();
|
|
198
|
+
if (options.github && !detectedGitRepo) {
|
|
199
|
+
console.log(
|
|
200
|
+
chalk.yellow(
|
|
201
|
+
" \u26A0\uFE0F --github was requested, but no GitHub origin remote was detected in this folder."
|
|
202
|
+
)
|
|
203
|
+
);
|
|
204
|
+
}
|
|
189
205
|
if (existingConfig) {
|
|
190
206
|
console.log(chalk.dim(" Found existing .mcp.json configuration"));
|
|
191
207
|
detectedSlug = existingConfig.slug || detectedSlug;
|
|
@@ -203,7 +219,10 @@ async function gatherConfiguration(options) {
|
|
|
203
219
|
skipCompanion: !!options.skipCompanion,
|
|
204
220
|
skipMcp: !!options.skipMcp,
|
|
205
221
|
skipRuntime: !!options.skipRuntime,
|
|
206
|
-
skipHooks: !!options.skipHooks
|
|
222
|
+
skipHooks: !!options.skipHooks,
|
|
223
|
+
configureGitHub: Boolean(options.github && detectedGitRepo && !options.skipGithub),
|
|
224
|
+
githubRepo: detectedGitRepo || void 0,
|
|
225
|
+
githubPath: options.githubPath
|
|
207
226
|
});
|
|
208
227
|
}
|
|
209
228
|
const response = await prompts([
|
|
@@ -243,8 +262,9 @@ async function gatherConfiguration(options) {
|
|
|
243
262
|
description: "Access to all projects in your team"
|
|
244
263
|
},
|
|
245
264
|
{
|
|
246
|
-
title: "
|
|
247
|
-
value: "signup"
|
|
265
|
+
title: "Create free Context + Memory account",
|
|
266
|
+
value: "signup",
|
|
267
|
+
description: "Hosted MCP with Context Free and reviewed Memory Free"
|
|
248
268
|
},
|
|
249
269
|
{
|
|
250
270
|
title: "I'll add the API key later",
|
|
@@ -287,13 +307,27 @@ async function gatherConfiguration(options) {
|
|
|
287
307
|
name: "installHooks",
|
|
288
308
|
message: "Generate local hooks when supported?",
|
|
289
309
|
initial: detectedClient === "claude-code"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: (_, values) => detectedGitRepo && !options.skipGithub && values.profile !== "runtime-only" ? "confirm" : null,
|
|
313
|
+
name: "configureGitHub",
|
|
314
|
+
message: detectedGitRepo ? `Open GitHub automation setup for ${detectedGitRepo.fullName} after local config?` : "Open GitHub automation setup after local config?",
|
|
315
|
+
initial: true
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
type: (_, values) => values.configureGitHub && detectedGitRepo ? "text" : null,
|
|
319
|
+
name: "githubPath",
|
|
320
|
+
message: "GitHub path filter (optional, press Enter for Snipara defaults):",
|
|
321
|
+
initial: options.githubPath || ""
|
|
290
322
|
}
|
|
291
323
|
]);
|
|
292
324
|
let apiKey = options.teamKey || options.apiKey;
|
|
293
325
|
let apiKeyType = response.profile === "runtime-only" ? "none" : options.teamKey ? "team" : options.apiKey ? "project" : "none";
|
|
326
|
+
let signupResult = null;
|
|
294
327
|
if (!apiKey && response.profile !== "runtime-only") {
|
|
295
328
|
if (response.apiKeyType === "signup") {
|
|
296
|
-
|
|
329
|
+
signupResult = await handleSignup();
|
|
330
|
+
apiKey = signupResult.apiKey;
|
|
297
331
|
apiKeyType = "project";
|
|
298
332
|
} else if (response.apiKeyType === "project" || response.apiKeyType === "team") {
|
|
299
333
|
apiKey = response.apiKey;
|
|
@@ -304,8 +338,8 @@ async function gatherConfiguration(options) {
|
|
|
304
338
|
}
|
|
305
339
|
}
|
|
306
340
|
const answers = buildAnswersFromProfile({
|
|
307
|
-
projectSlug: response.projectSlug,
|
|
308
|
-
projectId: response.projectId || "",
|
|
341
|
+
projectSlug: signupResult?.projectSlug || response.projectSlug,
|
|
342
|
+
projectId: signupResult?.projectId || response.projectId || "",
|
|
309
343
|
apiKey: apiKey || "",
|
|
310
344
|
apiKeyType,
|
|
311
345
|
profile: response.profile || initialProfile,
|
|
@@ -313,7 +347,12 @@ async function gatherConfiguration(options) {
|
|
|
313
347
|
skipCompanion: !!options.skipCompanion,
|
|
314
348
|
skipMcp: !!options.skipMcp,
|
|
315
349
|
skipRuntime: !!options.skipRuntime,
|
|
316
|
-
skipHooks: !!options.skipHooks || !response.installHooks
|
|
350
|
+
skipHooks: !!options.skipHooks || !response.installHooks,
|
|
351
|
+
configureGitHub: Boolean(
|
|
352
|
+
(response.configureGitHub || options.github) && detectedGitRepo && !options.skipGithub
|
|
353
|
+
),
|
|
354
|
+
githubRepo: detectedGitRepo || void 0,
|
|
355
|
+
githubPath: response.githubPath || options.githubPath
|
|
317
356
|
});
|
|
318
357
|
if (answers.installRuntime) {
|
|
319
358
|
const runtimeResponse = await prompts([
|
|
@@ -440,29 +479,124 @@ function buildAnswersFromProfile(input) {
|
|
|
440
479
|
installMcp: selected.installMcp && !input.skipMcp,
|
|
441
480
|
installRuntime: selected.installRuntime && !input.skipRuntime,
|
|
442
481
|
installHooks: selected.installHooks && !input.skipHooks,
|
|
443
|
-
aiClient: input.aiClient
|
|
482
|
+
aiClient: input.aiClient,
|
|
483
|
+
configureGitHub: Boolean(input.configureGitHub && input.githubRepo),
|
|
484
|
+
githubRepo: input.githubRepo,
|
|
485
|
+
githubPath: input.githubPath
|
|
444
486
|
};
|
|
445
487
|
}
|
|
446
488
|
async function handleSignup() {
|
|
447
|
-
|
|
489
|
+
console.log(chalk.bold("\n\u{1F510} Browser Account Authorization\n"));
|
|
490
|
+
try {
|
|
491
|
+
return await runDeviceAuthorization();
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (error instanceof DeviceAuthorizationAborted) {
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
console.log(
|
|
497
|
+
chalk.yellow(
|
|
498
|
+
` \u26A0\uFE0F Browser authorization did not complete: ${error instanceof Error ? error.message : "unknown error"}`
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
console.log(chalk.dim(" Falling back to manual key capture.\n"));
|
|
502
|
+
return handleManualSignup();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async function handleManualSignup() {
|
|
506
|
+
const signupUrl = `${BASE_URL}/register?plan=free&source=create-snipara`;
|
|
448
507
|
console.log(chalk.cyan(`
|
|
449
508
|
Opening ${signupUrl} in your browser...
|
|
450
509
|
`));
|
|
451
|
-
|
|
452
|
-
const platform = process.platform;
|
|
453
|
-
const openCommand = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
454
|
-
execSync(`${openCommand} "${signupUrl}"`, { stdio: "ignore", shell: "/bin/sh" });
|
|
455
|
-
} catch {
|
|
456
|
-
console.log(chalk.dim(` Please open ${signupUrl} manually.
|
|
457
|
-
`));
|
|
458
|
-
}
|
|
510
|
+
openBrowser(signupUrl);
|
|
459
511
|
const response = await prompts({
|
|
460
512
|
type: "text",
|
|
461
513
|
name: "apiKey",
|
|
462
514
|
message: "Paste your Project API key when ready:",
|
|
463
515
|
validate: (value) => value.startsWith("rlm_") ? true : "API key should start with rlm_"
|
|
464
516
|
});
|
|
465
|
-
return response.apiKey;
|
|
517
|
+
return { apiKey: response.apiKey };
|
|
518
|
+
}
|
|
519
|
+
async function runDeviceAuthorization() {
|
|
520
|
+
const codeResponse = await postJson(`${BASE_URL}/api/oauth/device/code`, {
|
|
521
|
+
client_id: DEVICE_CLIENT_ID,
|
|
522
|
+
scope: "mcp:read mcp:write",
|
|
523
|
+
auto_provision: true
|
|
524
|
+
});
|
|
525
|
+
if (codeResponse.error) {
|
|
526
|
+
throw new Error(codeResponse.error_description || codeResponse.error);
|
|
527
|
+
}
|
|
528
|
+
console.log(
|
|
529
|
+
chalk.cyan(` Opening ${codeResponse.verification_uri_complete} in your browser...`)
|
|
530
|
+
);
|
|
531
|
+
console.log(chalk.dim(` Device code: ${codeResponse.user_code}`));
|
|
532
|
+
openBrowser(codeResponse.verification_uri_complete);
|
|
533
|
+
const spinner = ora("Waiting for Snipara authorization in the browser...").start();
|
|
534
|
+
const startedAt = Date.now();
|
|
535
|
+
let intervalMs = Math.max(1, codeResponse.interval || 5) * 1e3;
|
|
536
|
+
const expiresAt = startedAt + Math.max(60, codeResponse.expires_in || 900) * 1e3;
|
|
537
|
+
while (Date.now() < expiresAt) {
|
|
538
|
+
await sleep(intervalMs);
|
|
539
|
+
const tokenResponse = await postJson(
|
|
540
|
+
`${BASE_URL}/api/oauth/device/token`,
|
|
541
|
+
{
|
|
542
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
543
|
+
device_code: codeResponse.device_code,
|
|
544
|
+
client_id: DEVICE_CLIENT_ID
|
|
545
|
+
},
|
|
546
|
+
true
|
|
547
|
+
);
|
|
548
|
+
if (!tokenResponse.error) {
|
|
549
|
+
if (!tokenResponse.api_key) {
|
|
550
|
+
throw new Error("Device flow completed but no project API key was returned");
|
|
551
|
+
}
|
|
552
|
+
spinner.succeed(
|
|
553
|
+
tokenResponse.project_name ? `Authorized ${tokenResponse.project_name}` : "Authorized Snipara account"
|
|
554
|
+
);
|
|
555
|
+
return {
|
|
556
|
+
apiKey: tokenResponse.api_key,
|
|
557
|
+
projectId: tokenResponse.project_id,
|
|
558
|
+
projectSlug: tokenResponse.project_slug
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
if (tokenResponse.error === "authorization_pending") {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (tokenResponse.error === "slow_down") {
|
|
565
|
+
intervalMs += 5e3;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (tokenResponse.error === "access_denied") {
|
|
569
|
+
throw new DeviceAuthorizationAborted("Authorization denied in browser");
|
|
570
|
+
}
|
|
571
|
+
throw new Error(tokenResponse.error_description || tokenResponse.error);
|
|
572
|
+
}
|
|
573
|
+
spinner.fail("Authorization expired");
|
|
574
|
+
throw new Error("device code expired");
|
|
575
|
+
}
|
|
576
|
+
async function postJson(url, body, allowErrorResponse = false) {
|
|
577
|
+
const response = await fetch(url, {
|
|
578
|
+
method: "POST",
|
|
579
|
+
headers: { "Content-Type": "application/json" },
|
|
580
|
+
body: JSON.stringify(body)
|
|
581
|
+
});
|
|
582
|
+
const data = await response.json().catch(() => ({}));
|
|
583
|
+
if (!response.ok && !(allowErrorResponse && data.error)) {
|
|
584
|
+
throw new Error(data.error_description || data.error || `HTTP ${response.status}`);
|
|
585
|
+
}
|
|
586
|
+
return data;
|
|
587
|
+
}
|
|
588
|
+
function openBrowser(url) {
|
|
589
|
+
try {
|
|
590
|
+
const platform = process.platform;
|
|
591
|
+
const openCommand = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
592
|
+
execSync(`${openCommand} "${url}"`, { stdio: "ignore", shell: "/bin/sh" });
|
|
593
|
+
} catch {
|
|
594
|
+
console.log(chalk.dim(` Please open ${url} manually.
|
|
595
|
+
`));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function sleep(ms) {
|
|
599
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
466
600
|
}
|
|
467
601
|
async function installPythonPackages(answers, upgrade) {
|
|
468
602
|
console.log(chalk.bold(`
|
|
@@ -987,6 +1121,16 @@ Apply only after reviewing the manifest:
|
|
|
987
1121
|
|
|
988
1122
|
For source-code repositories, use the GitHub OAuth/code onboarding path instead of \`onboard-folder\`.
|
|
989
1123
|
|
|
1124
|
+
## GitHub Repository Automation
|
|
1125
|
+
|
|
1126
|
+
${answers.githubRepo ? `Detected repository: \`${answers.githubRepo.fullName}\` on branch \`${answers.githubRepo.branch}\`.
|
|
1127
|
+
|
|
1128
|
+
Open the Snipara GitHub App setup and connect this repository:
|
|
1129
|
+
|
|
1130
|
+
\`${buildGitHubAutomationUrl(answers)}\`
|
|
1131
|
+
|
|
1132
|
+
After connection, Snipara creates or reuses the repository project, enables push sync, starts the first documentation sync, and keeps hosted memory available through the same account.` : `No GitHub repository was detected for this folder. Run \`npx create-snipara --github\` from a GitHub-backed git repository to open the repo automation path.`}
|
|
1133
|
+
|
|
990
1134
|
## Suggested Commands
|
|
991
1135
|
|
|
992
1136
|
${commands.map(
|
|
@@ -1011,6 +1155,8 @@ ${item.purpose}`
|
|
|
1011
1155
|
ai_client: answers.aiClient,
|
|
1012
1156
|
profile: answers.profile,
|
|
1013
1157
|
llm_provider: answers.llmProvider || null,
|
|
1158
|
+
github_repository: answers.githubRepo || null,
|
|
1159
|
+
github_automation_url: answers.githubRepo ? buildGitHubAutomationUrl(answers) : null,
|
|
1014
1160
|
commands,
|
|
1015
1161
|
guidance,
|
|
1016
1162
|
detection
|
|
@@ -1048,6 +1194,36 @@ async function runRlmInit() {
|
|
|
1048
1194
|
});
|
|
1049
1195
|
});
|
|
1050
1196
|
}
|
|
1197
|
+
async function openGitHubAutomation(answers) {
|
|
1198
|
+
if (!answers.githubRepo) {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const url = buildGitHubAutomationUrl(answers);
|
|
1202
|
+
console.log(chalk.bold("\n\u{1F419} GitHub Automation\n"));
|
|
1203
|
+
console.log(
|
|
1204
|
+
` Opening Snipara GitHub setup for ${chalk.cyan(answers.githubRepo.fullName)} on branch ${chalk.cyan(
|
|
1205
|
+
answers.githubRepo.branch
|
|
1206
|
+
)}.`
|
|
1207
|
+
);
|
|
1208
|
+
console.log(chalk.dim(` ${url}
|
|
1209
|
+
`));
|
|
1210
|
+
openBrowser(url);
|
|
1211
|
+
}
|
|
1212
|
+
function buildGitHubAutomationUrl(answers) {
|
|
1213
|
+
const url = new URL("/dashboard/projects/connect", BASE_URL);
|
|
1214
|
+
url.searchParams.set("source", "create-snipara");
|
|
1215
|
+
if (answers.githubRepo) {
|
|
1216
|
+
url.searchParams.set("repo", answers.githubRepo.fullName);
|
|
1217
|
+
url.searchParams.set("branch", answers.githubRepo.branch);
|
|
1218
|
+
}
|
|
1219
|
+
if (answers.githubPath) {
|
|
1220
|
+
url.searchParams.set("path", answers.githubPath);
|
|
1221
|
+
}
|
|
1222
|
+
if (answers.projectSlug) {
|
|
1223
|
+
url.searchParams.set("project_slug", answers.projectSlug);
|
|
1224
|
+
}
|
|
1225
|
+
return url.toString();
|
|
1226
|
+
}
|
|
1051
1227
|
async function runValidation(answers, includeHostedCheck) {
|
|
1052
1228
|
console.log(chalk.bold("\n\u{1FA7A} Running Validation\n"));
|
|
1053
1229
|
const checks = [];
|
|
@@ -1229,6 +1405,11 @@ function printSuccess(answers, report) {
|
|
|
1229
1405
|
if (answers.installRuntime && !answers.runRlmInit) {
|
|
1230
1406
|
printNextStep("Run rlm init when you want to choose sandbox/docker/local execution.");
|
|
1231
1407
|
}
|
|
1408
|
+
if (answers.githubRepo) {
|
|
1409
|
+
printNextStep(
|
|
1410
|
+
`Connect GitHub automation for ${answers.githubRepo.fullName} from the generated Snipara link.`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1232
1413
|
if (failedChecks.length > 0 || warnedChecks.length > 0) {
|
|
1233
1414
|
console.log(chalk.bold("\nValidation summary:\n"));
|
|
1234
1415
|
for (const check of [...failedChecks, ...warnedChecks]) {
|
|
@@ -1354,6 +1535,48 @@ function detectProjectSlug() {
|
|
|
1354
1535
|
}
|
|
1355
1536
|
return path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
1356
1537
|
}
|
|
1538
|
+
function detectGitRepository() {
|
|
1539
|
+
try {
|
|
1540
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
1541
|
+
encoding: "utf-8"
|
|
1542
|
+
}).trim();
|
|
1543
|
+
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) || remoteUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
1544
|
+
if (!match) {
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
const owner = match[1];
|
|
1548
|
+
const repo = match[2];
|
|
1549
|
+
const branch = detectGitBranch();
|
|
1550
|
+
return {
|
|
1551
|
+
owner,
|
|
1552
|
+
repo,
|
|
1553
|
+
fullName: `${owner}/${repo}`,
|
|
1554
|
+
branch,
|
|
1555
|
+
remoteUrl
|
|
1556
|
+
};
|
|
1557
|
+
} catch {
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function detectGitBranch() {
|
|
1562
|
+
try {
|
|
1563
|
+
const branch = execSync("git branch --show-current", {
|
|
1564
|
+
encoding: "utf-8"
|
|
1565
|
+
}).trim();
|
|
1566
|
+
if (branch) {
|
|
1567
|
+
return branch;
|
|
1568
|
+
}
|
|
1569
|
+
} catch {
|
|
1570
|
+
}
|
|
1571
|
+
try {
|
|
1572
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
1573
|
+
encoding: "utf-8"
|
|
1574
|
+
}).trim();
|
|
1575
|
+
return branch && branch !== "HEAD" ? branch : "main";
|
|
1576
|
+
} catch {
|
|
1577
|
+
return "main";
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1357
1580
|
function isGitRepo() {
|
|
1358
1581
|
try {
|
|
1359
1582
|
execSync("git rev-parse --is-inside-work-tree", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-snipara",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.6",
|
|
4
|
+
"description": "Onboard Snipara Hosted MCP Context + Memory with auth plus optional local helper and runtime workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"type": "git",
|
|
36
36
|
"url": "git+https://github.com/Snipara/create-snipara.git"
|
|
37
37
|
},
|
|
38
|
-
"homepage": "https://snipara.com/docs/
|
|
38
|
+
"homepage": "https://snipara.com/docs/integration/create-snipara",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"chalk": "^5.3.0",
|
|
41
41
|
"commander": "^12.1.0",
|