create-snipara 1.3.7 → 1.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -7
- package/dist/index.js +388 -53
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# create-snipara
|
|
2
2
|
|
|
3
|
-
One-command onboarding
|
|
3
|
+
**One-command onboarding for Snipara Hosted MCP Context + Memory.**
|
|
4
|
+
|
|
5
|
+
`create-snipara` connects a local project to Snipara so AI agents can start
|
|
6
|
+
with project-owned context, reviewed memory, shared guidance, and optional code
|
|
7
|
+
impact tooling instead of starting cold every session.
|
|
4
8
|
|
|
5
9
|
```bash
|
|
6
10
|
npx create-snipara
|
|
7
11
|
```
|
|
8
12
|
|
|
9
|
-
`create-snipara` is the simple onboarding package for the agent path. It
|
|
13
|
+
`create-snipara` is the simple onboarding package for the agent path. It
|
|
14
|
+
creates or reuses auth, writes Hosted MCP config, and prepares the local project
|
|
15
|
+
so an LLM client can use Snipara context and reviewed memory.
|
|
10
16
|
|
|
11
17
|
Use it for:
|
|
12
18
|
|
|
@@ -15,6 +21,26 @@ Use it for:
|
|
|
15
21
|
- local project wiring, `doctor`, `repair`, and `upgrade`
|
|
16
22
|
- GitHub repository sync approval when the current folder is a GitHub repo
|
|
17
23
|
|
|
24
|
+
## Why It Exists
|
|
25
|
+
|
|
26
|
+
AI clients all have slightly different config formats, but the project need is
|
|
27
|
+
the same: connect the agent to the same durable project memory. `create-snipara`
|
|
28
|
+
normalizes that setup.
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
flowchart LR
|
|
32
|
+
Project["Local project"] --> CLI["npx create-snipara"]
|
|
33
|
+
CLI --> Auth["Snipara auth and project key"]
|
|
34
|
+
CLI --> MCP["Hosted MCP config"]
|
|
35
|
+
CLI --> Templates["AGENTS.md, CLAUDE.md, Cursor, Codex snippets"]
|
|
36
|
+
MCP --> Agents["Codex, Claude Code, Cursor, ChatGPT Desktop"]
|
|
37
|
+
Templates --> Agents
|
|
38
|
+
Agents --> Context["Snipara context and memory"]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The CLI does not become the runtime brain. After setup, the agent talks to
|
|
42
|
+
Hosted MCP and keeps using its own LLM.
|
|
43
|
+
|
|
18
44
|
Do not use it to choose business documents one by one. For local folders, mounted Drive/SharePoint exports, old offers, or PowerPoints, use `snipara-business`.
|
|
19
45
|
|
|
20
46
|
The product surfaces remain:
|
|
@@ -46,7 +72,7 @@ Default profile: `hosted-only`
|
|
|
46
72
|
|
|
47
73
|
Free onboarding is now **Context + Memory**, not Context-only:
|
|
48
74
|
|
|
49
|
-
- Context Free:
|
|
75
|
+
- Context Free: three projects and 1,000 context queries/month
|
|
50
76
|
- Memory Free: reviewed hosted memories for continuity across sessions
|
|
51
77
|
- Hosted MCP: direct connection for Claude Code, Codex, Cursor, ChatGPT Desktop, and other MCP clients
|
|
52
78
|
|
|
@@ -76,6 +102,8 @@ That distinction matters:
|
|
|
76
102
|
- installs `snipara-companion` when the selected profile needs local helper workflows
|
|
77
103
|
- installs optional `rlm-runtime`
|
|
78
104
|
- configures `.mcp.json`
|
|
105
|
+
- generates merge-ready AI agent templates in `.snipara/templates/`, including Pro+ symbol-card and code-impact guidance for paid Context plans
|
|
106
|
+
- creates `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/snipara.mdc`, and `.codex/config.toml` when those files do not already exist
|
|
79
107
|
- updates `.env`, `.env.local`, and `.env.example`
|
|
80
108
|
- generates `.snipara/companion/README.md`
|
|
81
109
|
- generates `.snipara/companion/commands.json`
|
|
@@ -102,15 +130,30 @@ npx create-snipara
|
|
|
102
130
|
|
|
103
131
|
The interactive flow now asks for:
|
|
104
132
|
|
|
105
|
-
- install profile
|
|
106
133
|
- project slug
|
|
107
|
-
- optional project id
|
|
108
134
|
- API key path: project key, team key, free signup, or add key later
|
|
109
135
|
- AI client
|
|
136
|
+
|
|
137
|
+
By default, this stays on the recommended `hosted-only` setup. Add
|
|
138
|
+
`--advanced` when you want profile selection, project id entry, hook prompts,
|
|
139
|
+
GitHub automation prompts, or runtime provider prompts.
|
|
140
|
+
|
|
141
|
+
The advanced flow asks for:
|
|
142
|
+
|
|
143
|
+
- install profile
|
|
144
|
+
- optional project id
|
|
110
145
|
- whether hooks should be generated
|
|
111
146
|
- whether to open GitHub repository automation when a GitHub remote is detected
|
|
112
147
|
- optional runtime provider config when `rlm-runtime` is installed
|
|
113
148
|
|
|
149
|
+
The generated agent templates cover:
|
|
150
|
+
|
|
151
|
+
- Codex `AGENTS.md`
|
|
152
|
+
- Claude Code `CLAUDE.md`
|
|
153
|
+
- Cursor `.cursor/rules/snipara.mdc`
|
|
154
|
+
- Codex `.codex/config.toml` snippet
|
|
155
|
+
- ChatGPT/OpenAI HTTP MCP snippet
|
|
156
|
+
|
|
114
157
|
## Main Commands
|
|
115
158
|
|
|
116
159
|
### Install or initialize
|
|
@@ -207,6 +250,7 @@ That keeps business truth review separate from agent onboarding.
|
|
|
207
250
|
npx create-snipara --profile hosted-only
|
|
208
251
|
npx create-snipara --profile hosted-companion
|
|
209
252
|
npx create-snipara --profile full-stack
|
|
253
|
+
npx create-snipara --advanced
|
|
210
254
|
|
|
211
255
|
# Runtime-only local execution
|
|
212
256
|
npx create-snipara --runtime-only
|
|
@@ -319,7 +363,7 @@ Best when the user only wants Hosted MCP Context + Memory.
|
|
|
319
363
|
### Full local stack
|
|
320
364
|
|
|
321
365
|
```bash
|
|
322
|
-
npx create-snipara --profile full-stack
|
|
366
|
+
npx create-snipara --profile full-stack --advanced
|
|
323
367
|
```
|
|
324
368
|
|
|
325
369
|
Best when the user also wants `snipara-companion` and `rlm-runtime`.
|
|
@@ -367,7 +411,7 @@ npx create-snipara print-config --json
|
|
|
367
411
|
When testing a locally packed tarball, use `npm exec --package` instead of `npx`, for example:
|
|
368
412
|
|
|
369
413
|
```bash
|
|
370
|
-
npm exec --package ./create-snipara-1.3.
|
|
414
|
+
npm exec --package ./create-snipara-1.3.9.tgz create-snipara -- --help
|
|
371
415
|
```
|
|
372
416
|
|
|
373
417
|
This avoids `npx` resolution issues with unpublished tarballs.
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import fs from "fs";
|
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { execSync, spawn } from "child_process";
|
|
11
11
|
import { homedir } from "os";
|
|
12
|
-
var VERSION = "1.3.
|
|
12
|
+
var VERSION = "1.3.9";
|
|
13
13
|
var BASE_URL = "https://www.snipara.com";
|
|
14
14
|
var API_URL = "https://api.snipara.com";
|
|
15
15
|
var COMPANION_PACKAGE_NAME = "snipara-companion";
|
|
@@ -27,7 +27,7 @@ function getCompanionInitClientFlag(aiClient) {
|
|
|
27
27
|
function buildProgram() {
|
|
28
28
|
const program = new Command();
|
|
29
29
|
program.name("create-snipara").description("Scaffold hosted Snipara MCP config plus optional local helper workflows").version(VERSION);
|
|
30
|
-
program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
30
|
+
program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option("--advanced", "Show advanced profile, project id, hooks, GitHub, and runtime prompts").option(
|
|
31
31
|
"--profile <profile>",
|
|
32
32
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
33
33
|
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--runtime-only", "Install only RLM-Runtime").option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
|
|
@@ -58,7 +58,7 @@ function buildProgram() {
|
|
|
58
58
|
return program;
|
|
59
59
|
}
|
|
60
60
|
function addSharedRootOptions(program) {
|
|
61
|
-
program.option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
|
|
61
|
+
program.option("--api-key <key>", "Snipara project API key").option("--team-key <key>", "Snipara team API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option("--advanced", "Show advanced profile, project id, hooks, GitHub, and runtime prompts").option(
|
|
62
62
|
"--profile <profile>",
|
|
63
63
|
"Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
|
|
64
64
|
).option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip rlm-runtime installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip hosted connection test").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--runtime-only", "Install only RLM-Runtime").option("-y, --yes", "Accept defaults without prompting");
|
|
@@ -67,8 +67,63 @@ async function main() {
|
|
|
67
67
|
const program = buildProgram();
|
|
68
68
|
await program.parseAsync(process.argv);
|
|
69
69
|
}
|
|
70
|
+
function normalizeInitOptions(options) {
|
|
71
|
+
const parsed = parseInitOptionsFromArgv(process.argv.slice(2));
|
|
72
|
+
const normalized = { ...parsed };
|
|
73
|
+
for (const [key, value] of Object.entries(options)) {
|
|
74
|
+
if (value !== void 0) {
|
|
75
|
+
normalized[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return normalized;
|
|
79
|
+
}
|
|
80
|
+
function parseInitOptionsFromArgv(args) {
|
|
81
|
+
const parsed = {};
|
|
82
|
+
const valueFlags = {
|
|
83
|
+
"--api-key": "apiKey",
|
|
84
|
+
"--team-key": "teamKey",
|
|
85
|
+
"--slug": "slug",
|
|
86
|
+
"--project-id": "projectId",
|
|
87
|
+
"--profile": "profile",
|
|
88
|
+
"--github-path": "githubPath"
|
|
89
|
+
};
|
|
90
|
+
const booleanFlags = {
|
|
91
|
+
"--advanced": "advanced",
|
|
92
|
+
"--skip-companion": "skipCompanion",
|
|
93
|
+
"--skip-mcp": "skipMcp",
|
|
94
|
+
"--skip-runtime": "skipRuntime",
|
|
95
|
+
"--skip-hooks": "skipHooks",
|
|
96
|
+
"--skip-test": "skipTest",
|
|
97
|
+
"--runtime-only": "runtimeOnly",
|
|
98
|
+
"--github": "github",
|
|
99
|
+
"--skip-github": "skipGithub",
|
|
100
|
+
"--yes": "yes",
|
|
101
|
+
"-y": "yes"
|
|
102
|
+
};
|
|
103
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
104
|
+
const arg = args[index];
|
|
105
|
+
if (arg === "init") {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const [flag, inlineValue] = arg.split("=", 2);
|
|
109
|
+
if (flag in valueFlags) {
|
|
110
|
+
const value = inlineValue ?? args[index + 1];
|
|
111
|
+
if (value && !value.startsWith("-")) {
|
|
112
|
+
parsed[valueFlags[flag]] = value;
|
|
113
|
+
if (inlineValue === void 0) {
|
|
114
|
+
index += 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (arg in booleanFlags) {
|
|
120
|
+
parsed[booleanFlags[arg]] = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return parsed;
|
|
124
|
+
}
|
|
70
125
|
async function runInit(options) {
|
|
71
|
-
const answers = await gatherConfiguration(options);
|
|
126
|
+
const answers = await gatherConfiguration(normalizeInitOptions(options));
|
|
72
127
|
if (answers.installMcp || answers.installRuntime) {
|
|
73
128
|
await installPythonPackages(answers, false);
|
|
74
129
|
}
|
|
@@ -78,6 +133,7 @@ async function runInit(options) {
|
|
|
78
133
|
if (answers.profile !== "runtime-only" || answers.installRuntime) {
|
|
79
134
|
await createMcpConfig(answers);
|
|
80
135
|
}
|
|
136
|
+
await createAgentTemplates(answers);
|
|
81
137
|
if (answers.installHooks && answers.aiClient === "claude-code") {
|
|
82
138
|
await installClaudeCodeHooks(answers);
|
|
83
139
|
}
|
|
@@ -113,6 +169,7 @@ async function runRepair(options) {
|
|
|
113
169
|
await installNodeCompanion(true);
|
|
114
170
|
}
|
|
115
171
|
await createMcpConfig(answers);
|
|
172
|
+
await createAgentTemplates(answers);
|
|
116
173
|
await updateEnvFiles(answers);
|
|
117
174
|
await createCompanionPack(answers);
|
|
118
175
|
if (answers.installHooks && answers.aiClient === "claude-code") {
|
|
@@ -140,6 +197,7 @@ async function runUpgrade(options) {
|
|
|
140
197
|
if (options.withMcp || options.withRuntime || detection.files.mcpConfig) {
|
|
141
198
|
await createMcpConfig(upgradedAnswers);
|
|
142
199
|
}
|
|
200
|
+
await createAgentTemplates(upgradedAnswers);
|
|
143
201
|
if (options.withHooks || answers.installHooks) {
|
|
144
202
|
await installClaudeCodeHooks({ ...answers, installHooks: true });
|
|
145
203
|
}
|
|
@@ -195,6 +253,7 @@ async function gatherConfiguration(options) {
|
|
|
195
253
|
const detectedClient = detectAiClient();
|
|
196
254
|
const existingConfig = getExistingConfig();
|
|
197
255
|
const detectedGitRepo = detectGitRepository();
|
|
256
|
+
const showAdvancedPrompts = Boolean(options.advanced);
|
|
198
257
|
if (options.github && !detectedGitRepo) {
|
|
199
258
|
console.log(
|
|
200
259
|
chalk.yellow(
|
|
@@ -227,7 +286,7 @@ async function gatherConfiguration(options) {
|
|
|
227
286
|
}
|
|
228
287
|
const response = await prompts([
|
|
229
288
|
{
|
|
230
|
-
type: "select",
|
|
289
|
+
type: showAdvancedPrompts ? "select" : null,
|
|
231
290
|
name: "profile",
|
|
232
291
|
message: "Choose an install profile:",
|
|
233
292
|
initial: installProfileChoices().findIndex((choice) => choice.value === initialProfile),
|
|
@@ -241,7 +300,7 @@ async function gatherConfiguration(options) {
|
|
|
241
300
|
validate: (value) => value.length > 0 ? true : "Project slug is required (for example my-project)"
|
|
242
301
|
},
|
|
243
302
|
{
|
|
244
|
-
type: "text",
|
|
303
|
+
type: showAdvancedPrompts ? "text" : null,
|
|
245
304
|
name: "projectId",
|
|
246
305
|
message: "Project ID (optional, press Enter to skip):",
|
|
247
306
|
initial: options.projectId || detectedProjectId || ""
|
|
@@ -282,7 +341,9 @@ async function gatherConfiguration(options) {
|
|
|
282
341
|
type: "select",
|
|
283
342
|
name: "aiClient",
|
|
284
343
|
message: "Which AI client are you using?",
|
|
285
|
-
initial: ["claude-code", "cursor", "claude-desktop", "other"].indexOf(
|
|
344
|
+
initial: ["claude-code", "cursor", "codex", "chatgpt", "claude-desktop", "other"].indexOf(
|
|
345
|
+
detectedClient
|
|
346
|
+
),
|
|
286
347
|
choices: [
|
|
287
348
|
{
|
|
288
349
|
title: "Claude Code (CLI/VS Code extension)",
|
|
@@ -294,6 +355,16 @@ async function gatherConfiguration(options) {
|
|
|
294
355
|
value: "cursor",
|
|
295
356
|
description: "MCP via .mcp.json plus companion terminal workflows"
|
|
296
357
|
},
|
|
358
|
+
{
|
|
359
|
+
title: "OpenAI Codex",
|
|
360
|
+
value: "codex",
|
|
361
|
+
description: "Hosted MCP plus AGENTS.md"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
title: "ChatGPT / OpenAI MCP",
|
|
365
|
+
value: "chatgpt",
|
|
366
|
+
description: "Hosted MCP for ChatGPT Desktop or OpenAI-compatible clients"
|
|
367
|
+
},
|
|
297
368
|
{
|
|
298
369
|
title: "Claude Desktop",
|
|
299
370
|
value: "claude-desktop",
|
|
@@ -303,19 +374,19 @@ async function gatherConfiguration(options) {
|
|
|
303
374
|
]
|
|
304
375
|
},
|
|
305
376
|
{
|
|
306
|
-
type: "confirm",
|
|
377
|
+
type: showAdvancedPrompts ? "confirm" : null,
|
|
307
378
|
name: "installHooks",
|
|
308
379
|
message: "Generate local hooks when supported?",
|
|
309
380
|
initial: detectedClient === "claude-code"
|
|
310
381
|
},
|
|
311
382
|
{
|
|
312
|
-
type: (_, values) => detectedGitRepo && !options.skipGithub && values.profile !== "runtime-only" ? "confirm" : null,
|
|
383
|
+
type: (_, values) => detectedGitRepo && showAdvancedPrompts && !options.skipGithub && (values.profile || initialProfile) !== "runtime-only" ? "confirm" : null,
|
|
313
384
|
name: "configureGitHub",
|
|
314
385
|
message: detectedGitRepo ? `Open GitHub automation setup for ${detectedGitRepo.fullName} after local config?` : "Open GitHub automation setup after local config?",
|
|
315
386
|
initial: true
|
|
316
387
|
},
|
|
317
388
|
{
|
|
318
|
-
type: (_, values) => values.configureGitHub && detectedGitRepo ? "text" : null,
|
|
389
|
+
type: (_, values) => showAdvancedPrompts && values.configureGitHub && detectedGitRepo ? "text" : null,
|
|
319
390
|
name: "githubPath",
|
|
320
391
|
message: "GitHub path filter (optional, press Enter for Snipara defaults):",
|
|
321
392
|
initial: options.githubPath || ""
|
|
@@ -339,7 +410,7 @@ async function gatherConfiguration(options) {
|
|
|
339
410
|
}
|
|
340
411
|
const answers = buildAnswersFromProfile({
|
|
341
412
|
projectSlug: signupResult?.projectSlug || response.projectSlug,
|
|
342
|
-
projectId: signupResult?.projectId || response.projectId || "",
|
|
413
|
+
projectId: signupResult?.projectId || response.projectId || options.projectId || "",
|
|
343
414
|
apiKey: apiKey || "",
|
|
344
415
|
apiKeyType,
|
|
345
416
|
profile: response.profile || initialProfile,
|
|
@@ -744,6 +815,162 @@ async function createMcpConfig(answers) {
|
|
|
744
815
|
await createClaudeDesktopConfig(answers);
|
|
745
816
|
}
|
|
746
817
|
}
|
|
818
|
+
async function createAgentTemplates(answers) {
|
|
819
|
+
if (answers.profile === "runtime-only") {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
console.log(chalk.bold("\nGenerating Agent Templates\n"));
|
|
823
|
+
const templatesDir = path.join(process.cwd(), ".snipara", "templates");
|
|
824
|
+
fs.mkdirSync(templatesDir, { recursive: true });
|
|
825
|
+
const templates = [
|
|
826
|
+
{ path: path.join(templatesDir, "AGENTS.md"), content: buildAgentsMdTemplate(answers) },
|
|
827
|
+
{ path: path.join(templatesDir, "CLAUDE.md"), content: buildClaudeMdTemplate(answers) },
|
|
828
|
+
{
|
|
829
|
+
path: path.join(templatesDir, "cursor-snipara.mdc"),
|
|
830
|
+
content: buildCursorRuleTemplate(answers)
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
path: path.join(templatesDir, "codex-config.toml"),
|
|
834
|
+
content: buildCodexConfigTemplate(answers)
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
path: path.join(templatesDir, "chatgpt-openai-mcp.json"),
|
|
838
|
+
content: buildChatGptMcpTemplate(answers)
|
|
839
|
+
}
|
|
840
|
+
];
|
|
841
|
+
for (const template of templates) {
|
|
842
|
+
fs.writeFileSync(template.path, template.content);
|
|
843
|
+
}
|
|
844
|
+
console.log(chalk.green(" Created .snipara/templates agent snippets"));
|
|
845
|
+
writeIfMissing(
|
|
846
|
+
path.join(process.cwd(), "AGENTS.md"),
|
|
847
|
+
buildAgentsMdTemplate(answers),
|
|
848
|
+
"AGENTS.md"
|
|
849
|
+
);
|
|
850
|
+
writeIfMissing(
|
|
851
|
+
path.join(process.cwd(), "CLAUDE.md"),
|
|
852
|
+
buildClaudeMdTemplate(answers),
|
|
853
|
+
"CLAUDE.md"
|
|
854
|
+
);
|
|
855
|
+
const cursorRulesDir = path.join(process.cwd(), ".cursor", "rules");
|
|
856
|
+
fs.mkdirSync(cursorRulesDir, { recursive: true });
|
|
857
|
+
writeIfMissing(
|
|
858
|
+
path.join(cursorRulesDir, "snipara.mdc"),
|
|
859
|
+
buildCursorRuleTemplate(answers),
|
|
860
|
+
".cursor/rules/snipara.mdc"
|
|
861
|
+
);
|
|
862
|
+
const codexDir = path.join(process.cwd(), ".codex");
|
|
863
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
864
|
+
writeIfMissing(
|
|
865
|
+
path.join(codexDir, "config.toml"),
|
|
866
|
+
buildCodexConfigTemplate(answers),
|
|
867
|
+
".codex/config.toml"
|
|
868
|
+
);
|
|
869
|
+
ensureGitignoreEntry(".snipara/companion/doctor.json", "# Snipara generated validation output");
|
|
870
|
+
}
|
|
871
|
+
function writeIfMissing(filePath, content, label) {
|
|
872
|
+
if (fs.existsSync(filePath)) {
|
|
873
|
+
console.log(chalk.dim(` ${label} already exists; merge from .snipara/templates if needed`));
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
fs.writeFileSync(filePath, content);
|
|
877
|
+
console.log(chalk.green(` Created ${label}`));
|
|
878
|
+
}
|
|
879
|
+
function buildHostedMcpEndpoint(answers) {
|
|
880
|
+
return `${API_URL}/mcp/${answers.projectSlug}`;
|
|
881
|
+
}
|
|
882
|
+
function buildAgentsMdTemplate(answers) {
|
|
883
|
+
return `# AGENTS.md
|
|
884
|
+
|
|
885
|
+
## Snipara Context Workflow
|
|
886
|
+
|
|
887
|
+
This project uses Snipara Hosted MCP for project context and reviewed memory.
|
|
888
|
+
|
|
889
|
+
- Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
|
|
890
|
+
- Auth: use \`SNIPARA_API_KEY\` or \`RLM_API_KEY\` from the environment. Never commit keys.
|
|
891
|
+
|
|
892
|
+
Agent task lifecycle:
|
|
893
|
+
|
|
894
|
+
1. Start substantial work with \`rlm_recall\` and a targeted \`rlm_context_query\`.
|
|
895
|
+
2. Use \`rlm_context_query\` for docs, business context, client/project truth, architecture notes, runbooks, and narrative source material.
|
|
896
|
+
3. When \`rlm_context_query\` returns \`answer_pack\`, treat it as the first-pass response plan: use its source facts, caveats, source map, and verification checklist before drafting claims.
|
|
897
|
+
4. Use \`rlm_get_chunk\` to load cited source sections returned by reference-based queries before relying on precise wording.
|
|
898
|
+
5. Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
|
|
899
|
+
6. On paid Context plans, use \`rlm_code_symbol_card\` for agent-ready symbol context and \`rlm_code_impact\` before risky multi-file changes or PR reviews.
|
|
900
|
+
7. Use \`rlm_plan\` or \`rlm_decompose\` only for complex work and only when the hosted server exposes them.
|
|
901
|
+
8. Use local file reads, \`rg\`, git commands, and test commands for exact edits, current working-tree state, and verification.
|
|
902
|
+
9. Use RLM Runtime only when sandboxed execution, repeatable validation, or isolated transformations materially help.
|
|
903
|
+
10. If a broad query is slow, retry once with a narrow keyword query before falling back to local search.
|
|
904
|
+
11. End substantial work with \`rlm_end_of_task_commit\` when available. Use \`rlm_remember_if_novel\` for one reusable memory while avoiding duplicates, and use \`rlm_remember\` for explicit direct memory writes.
|
|
905
|
+
12. Do not store secrets, tokens, passwords, private keys, raw logs, transient command output, or unreviewed guesses in memory.
|
|
906
|
+
`;
|
|
907
|
+
}
|
|
908
|
+
function buildClaudeMdTemplate(answers) {
|
|
909
|
+
return `# CLAUDE.md
|
|
910
|
+
|
|
911
|
+
Canonical project context is in \`AGENTS.md\`. This file exists for Claude Code compatibility.
|
|
912
|
+
|
|
913
|
+
## Snipara
|
|
914
|
+
|
|
915
|
+
Use Snipara Hosted MCP before answering project-specific questions.
|
|
916
|
+
|
|
917
|
+
- Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
|
|
918
|
+
- Start substantial work with \`rlm_recall\` and a targeted \`rlm_context_query\`.
|
|
919
|
+
- Use \`rlm_context_query\` for source truth and \`rlm_get_chunk\` for exact cited sections.
|
|
920
|
+
- When \`rlm_context_query\` returns \`answer_pack\`, use its facts, caveats, source map, and verification checklist before drafting claims.
|
|
921
|
+
- Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
|
|
922
|
+
- On paid Context plans, use \`rlm_code_symbol_card\` before editing important symbols and \`rlm_code_impact\` before risky multi-file changes or PR reviews.
|
|
923
|
+
- Use RLM Runtime only when sandboxed execution or repeatable validation materially helps.
|
|
924
|
+
- End substantial work with \`rlm_end_of_task_commit\` when available; use \`rlm_remember_if_novel\` or \`rlm_remember\` only for narrow durable memories.
|
|
925
|
+
- Do not store secrets, one-off command output, raw logs, or unreviewed guesses in memory.
|
|
926
|
+
- Use the Snipara Claude Code plugin only when slash commands, skills, or hooks are helpful; Hosted MCP remains the normal agent path.
|
|
927
|
+
`;
|
|
928
|
+
}
|
|
929
|
+
function buildCursorRuleTemplate(answers) {
|
|
930
|
+
return `---
|
|
931
|
+
description: Use Snipara Hosted MCP for project context and reviewed memory.
|
|
932
|
+
alwaysApply: true
|
|
933
|
+
---
|
|
934
|
+
|
|
935
|
+
# Snipara Agent Workflow
|
|
936
|
+
|
|
937
|
+
This workspace uses Snipara Hosted MCP for current project context, business/client context, and reviewed memory.
|
|
938
|
+
|
|
939
|
+
- Check the configured Snipara MCP server before answering project-specific questions.
|
|
940
|
+
- Endpoint: \`${buildHostedMcpEndpoint(answers)}\`
|
|
941
|
+
- Use \`rlm_recall\` for durable decisions, preferences, and validated learnings.
|
|
942
|
+
- Use \`rlm_context_query\` for docs, business context, current client/project truth, and narrative source material.
|
|
943
|
+
- When \`rlm_context_query\` returns \`answer_pack\`, use its facts, caveats, source map, and verification checklist before drafting claims.
|
|
944
|
+
- Use \`rlm_get_chunk\` before relying on exact cited wording from reference-based results.
|
|
945
|
+
- Use \`rlm_code_callers\`, \`rlm_code_imports\`, \`rlm_code_neighbors\`, or \`rlm_code_shortest_path\` for structural code questions.
|
|
946
|
+
- On paid Context plans, use \`rlm_code_symbol_card\` before editing important symbols and \`rlm_code_impact\` before risky multi-file changes or PR reviews.
|
|
947
|
+
- Use \`rlm_plan\` or \`rlm_decompose\` only for complex work and only when available.
|
|
948
|
+
- Use local file reads and tests for exact code edits and current working-tree state.
|
|
949
|
+
- Use RLM Runtime only when sandboxed execution or repeatable validation materially helps.
|
|
950
|
+
- End substantial work with \`rlm_end_of_task_commit\` when available; use \`rlm_remember_if_novel\` or \`rlm_remember\` only for narrow durable memories. Never store secrets.
|
|
951
|
+
`;
|
|
952
|
+
}
|
|
953
|
+
function buildCodexConfigTemplate(answers) {
|
|
954
|
+
return `[mcp_servers.snipara]
|
|
955
|
+
type = "streamable_http"
|
|
956
|
+
url = "${buildHostedMcpEndpoint(answers)}"
|
|
957
|
+
bearer_token_env_var = "SNIPARA_API_KEY"
|
|
958
|
+
`;
|
|
959
|
+
}
|
|
960
|
+
function buildChatGptMcpTemplate(answers) {
|
|
961
|
+
return `${JSON.stringify(
|
|
962
|
+
{
|
|
963
|
+
name: "snipara",
|
|
964
|
+
url: buildHostedMcpEndpoint(answers),
|
|
965
|
+
headers: {
|
|
966
|
+
"X-API-Key": "YOUR_SNIPARA_PROJECT_API_KEY"
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
null,
|
|
970
|
+
2
|
|
971
|
+
)}
|
|
972
|
+
`;
|
|
973
|
+
}
|
|
747
974
|
function ensureGitignoreEntry(entry, comment) {
|
|
748
975
|
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
749
976
|
if (fs.existsSync(gitignorePath)) {
|
|
@@ -978,6 +1205,18 @@ function getClientGuidance(client) {
|
|
|
978
1205
|
"Use the companion CLI only for explicit upload, planning, chunk lookup, doctor, and task commit workflows outside the editor UI."
|
|
979
1206
|
];
|
|
980
1207
|
}
|
|
1208
|
+
if (client === "codex") {
|
|
1209
|
+
return [
|
|
1210
|
+
"Codex should use the hosted MCP surface plus AGENTS.md as the canonical project instruction file.",
|
|
1211
|
+
"The generated .snipara/templates/codex-config.toml is a merge-ready snippet for ~/.codex/config.toml if your Codex app does not read project .codex/config.toml."
|
|
1212
|
+
];
|
|
1213
|
+
}
|
|
1214
|
+
if (client === "chatgpt") {
|
|
1215
|
+
return [
|
|
1216
|
+
"ChatGPT and OpenAI-compatible MCP clients should use the direct hosted MCP endpoint.",
|
|
1217
|
+
"The generated chatgpt-openai-mcp.json is a copy-ready HTTP MCP reference with placeholder auth."
|
|
1218
|
+
];
|
|
1219
|
+
}
|
|
981
1220
|
if (client === "claude-desktop") {
|
|
982
1221
|
return [
|
|
983
1222
|
"Claude Desktop uses the generated desktop MCP config plus the same hosted Snipara tool contract.",
|
|
@@ -1253,6 +1492,11 @@ async function runValidation(answers, includeHostedCheck) {
|
|
|
1253
1492
|
status: detection.files.sniparaCompanionPack ? "pass" : "warn",
|
|
1254
1493
|
detail: detection.files.sniparaCompanionPack ? ".snipara/companion present" : ".snipara/companion missing"
|
|
1255
1494
|
});
|
|
1495
|
+
checks.push({
|
|
1496
|
+
name: "agent_templates",
|
|
1497
|
+
status: detection.files.sniparaTemplates && detection.files.cursorRule && detection.files.codexConfig ? "pass" : "warn",
|
|
1498
|
+
detail: detection.files.sniparaTemplates && detection.files.cursorRule && detection.files.codexConfig ? ".snipara/templates, .cursor/rules/snipara.mdc, and .codex/config.toml present" : "Agent templates, Cursor rule, or Codex config missing"
|
|
1499
|
+
});
|
|
1256
1500
|
checks.push({
|
|
1257
1501
|
name: "env_files",
|
|
1258
1502
|
status: detection.files.env || detection.files.envLocal || detection.files.envExample ? "pass" : "warn",
|
|
@@ -1264,7 +1508,7 @@ async function runValidation(answers, includeHostedCheck) {
|
|
|
1264
1508
|
detail: answers.installHooks && answers.aiClient === "claude-code" ? detection.files.claudeDir ? "~/.claude detected" : "~/.claude missing" : "Hooks not requested"
|
|
1265
1509
|
});
|
|
1266
1510
|
if (includeHostedCheck && answers.apiKeyType !== "none" && !answers.apiKey.includes("YOUR_")) {
|
|
1267
|
-
checks.push(await
|
|
1511
|
+
checks.push(...await testHostedConnection(answers));
|
|
1268
1512
|
} else if (includeHostedCheck) {
|
|
1269
1513
|
checks.push({
|
|
1270
1514
|
name: "hosted_connection",
|
|
@@ -1303,47 +1547,118 @@ function printDoctorReport(detection, report) {
|
|
|
1303
1547
|
}
|
|
1304
1548
|
console.log("");
|
|
1305
1549
|
}
|
|
1306
|
-
async function
|
|
1307
|
-
const spinner = ora("Connecting to Snipara
|
|
1550
|
+
async function testHostedConnection(answers) {
|
|
1551
|
+
const spinner = ora("Connecting to Snipara Hosted MCP...").start();
|
|
1308
1552
|
try {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1553
|
+
const toolsResponse = await callHostedMcp(answers, "tools/list", {});
|
|
1554
|
+
if (!toolsResponse.ok) {
|
|
1555
|
+
spinner.warn(`Hosted MCP returned HTTP ${toolsResponse.status}`);
|
|
1556
|
+
return [
|
|
1557
|
+
{
|
|
1558
|
+
name: "hosted_connection",
|
|
1559
|
+
status: toolsResponse.status === 401 || toolsResponse.status === 403 ? "fail" : "warn",
|
|
1560
|
+
detail: describeHostedHttpFailure(toolsResponse.status, toolsResponse.bodyText)
|
|
1561
|
+
}
|
|
1562
|
+
];
|
|
1563
|
+
}
|
|
1564
|
+
if (toolsResponse.data.error) {
|
|
1565
|
+
spinner.warn("Hosted MCP tools/list returned a JSON-RPC error");
|
|
1566
|
+
return [
|
|
1567
|
+
{
|
|
1568
|
+
name: "hosted_connection",
|
|
1569
|
+
status: "warn",
|
|
1570
|
+
detail: describeJsonRpcError(toolsResponse.data)
|
|
1571
|
+
}
|
|
1572
|
+
];
|
|
1573
|
+
}
|
|
1574
|
+
const toolCount = toolsResponse.data.result?.tools?.length || 0;
|
|
1575
|
+
spinner.succeed(`Connected! ${toolCount} MCP tools available.`);
|
|
1576
|
+
const checks = [
|
|
1577
|
+
{
|
|
1327
1578
|
name: "hosted_connection",
|
|
1328
1579
|
status: "pass",
|
|
1329
|
-
detail: `
|
|
1330
|
-
}
|
|
1580
|
+
detail: `tools/list succeeded (${toolCount} tools reported)`
|
|
1581
|
+
}
|
|
1582
|
+
];
|
|
1583
|
+
const settingsResponse = await callHostedMcp(answers, "tools/call", {
|
|
1584
|
+
name: "rlm_settings",
|
|
1585
|
+
arguments: {}
|
|
1586
|
+
});
|
|
1587
|
+
if (settingsResponse.ok && !settingsResponse.data.error) {
|
|
1588
|
+
checks.push({
|
|
1589
|
+
name: "hosted_settings",
|
|
1590
|
+
status: "pass",
|
|
1591
|
+
detail: "rlm_settings tool call succeeded"
|
|
1592
|
+
});
|
|
1593
|
+
} else {
|
|
1594
|
+
checks.push({
|
|
1595
|
+
name: "hosted_settings",
|
|
1596
|
+
status: "warn",
|
|
1597
|
+
detail: settingsResponse.ok ? describeJsonRpcError(settingsResponse.data) : describeHostedHttpFailure(settingsResponse.status, settingsResponse.bodyText)
|
|
1598
|
+
});
|
|
1331
1599
|
}
|
|
1332
|
-
|
|
1333
|
-
spinner.warn(`Connection returned ${response.status}`);
|
|
1334
|
-
return {
|
|
1335
|
-
name: "hosted_connection",
|
|
1336
|
-
status: "warn",
|
|
1337
|
-
detail: `HTTP ${response.status}: ${errorText.slice(0, 120)}`
|
|
1338
|
-
};
|
|
1600
|
+
return checks;
|
|
1339
1601
|
} catch (error) {
|
|
1340
1602
|
spinner.warn(`Could not connect: ${error}`);
|
|
1341
|
-
return
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1603
|
+
return [
|
|
1604
|
+
{
|
|
1605
|
+
name: "hosted_connection",
|
|
1606
|
+
status: "warn",
|
|
1607
|
+
detail: "Could not reach Snipara Hosted MCP from this machine"
|
|
1608
|
+
}
|
|
1609
|
+
];
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
async function callHostedMcp(answers, method, params) {
|
|
1613
|
+
const response = await fetch(`${API_URL}/mcp/${answers.projectSlug}`, {
|
|
1614
|
+
method: "POST",
|
|
1615
|
+
headers: {
|
|
1616
|
+
"Content-Type": "application/json",
|
|
1617
|
+
"X-API-Key": answers.apiKey
|
|
1618
|
+
},
|
|
1619
|
+
body: JSON.stringify({
|
|
1620
|
+
jsonrpc: "2.0",
|
|
1621
|
+
id: 1,
|
|
1622
|
+
method,
|
|
1623
|
+
params
|
|
1624
|
+
})
|
|
1625
|
+
});
|
|
1626
|
+
const bodyText = await response.text();
|
|
1627
|
+
let data = {};
|
|
1628
|
+
try {
|
|
1629
|
+
data = bodyText ? JSON.parse(bodyText) : {};
|
|
1630
|
+
} catch {
|
|
1631
|
+
data = {};
|
|
1632
|
+
}
|
|
1633
|
+
return {
|
|
1634
|
+
ok: response.ok,
|
|
1635
|
+
status: response.status,
|
|
1636
|
+
data,
|
|
1637
|
+
bodyText
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function describeHostedHttpFailure(status, bodyText) {
|
|
1641
|
+
const detail = bodyText.trim().slice(0, 120);
|
|
1642
|
+
if (status === 401 || status === 403) {
|
|
1643
|
+
return `Auth failed (${status}). Check the project/team API key and project slug.`;
|
|
1644
|
+
}
|
|
1645
|
+
if (status === 404) {
|
|
1646
|
+
return "Endpoint or project slug was not found. Check the hosted MCP URL.";
|
|
1647
|
+
}
|
|
1648
|
+
if (status === 429) {
|
|
1649
|
+
return "Rate limit reached. Retry later or check plan limits.";
|
|
1650
|
+
}
|
|
1651
|
+
if (status >= 500) {
|
|
1652
|
+
return `Hosted MCP server error (${status}). Retry later or check Snipara status.`;
|
|
1346
1653
|
}
|
|
1654
|
+
return detail ? `HTTP ${status}: ${detail}` : `HTTP ${status}`;
|
|
1655
|
+
}
|
|
1656
|
+
function describeJsonRpcError(data) {
|
|
1657
|
+
if (!data.error) {
|
|
1658
|
+
return "Unexpected hosted MCP response";
|
|
1659
|
+
}
|
|
1660
|
+
const code = data.error.code !== void 0 ? ` ${data.error.code}` : "";
|
|
1661
|
+
return `JSON-RPC error${code}: ${data.error.message || "unknown error"}`;
|
|
1347
1662
|
}
|
|
1348
1663
|
function printSuccess(answers, report) {
|
|
1349
1664
|
console.log(
|
|
@@ -1387,6 +1702,11 @@ function printSuccess(answers, report) {
|
|
|
1387
1702
|
nextStep += 1;
|
|
1388
1703
|
};
|
|
1389
1704
|
printNextStep("Restart your AI client if you created or changed hosted MCP config.");
|
|
1705
|
+
if (answers.aiClient === "codex") {
|
|
1706
|
+
printNextStep(
|
|
1707
|
+
"For Codex, merge .snipara/templates/codex-config.toml into ~/.codex/config.toml if the app does not read project .codex/config.toml."
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1390
1710
|
printNextStep("Open .snipara/companion/README.md for local commands and repair flow.");
|
|
1391
1711
|
printNextStep("Run create-snipara doctor anytime you want a fresh local validation report.");
|
|
1392
1712
|
if (answers.installCompanion) {
|
|
@@ -1452,9 +1772,14 @@ function detectEnvironment() {
|
|
|
1452
1772
|
env: fs.existsSync(path.join(process.cwd(), ".env")),
|
|
1453
1773
|
envLocal: fs.existsSync(path.join(process.cwd(), ".env.local")),
|
|
1454
1774
|
envExample: fs.existsSync(path.join(process.cwd(), ".env.example")),
|
|
1775
|
+
agentsMd: fs.existsSync(path.join(process.cwd(), "AGENTS.md")),
|
|
1776
|
+
claudeMd: fs.existsSync(path.join(process.cwd(), "CLAUDE.md")),
|
|
1777
|
+
codexConfig: fs.existsSync(path.join(process.cwd(), ".codex", "config.toml")),
|
|
1778
|
+
cursorRule: fs.existsSync(path.join(process.cwd(), ".cursor", "rules", "snipara.mdc")),
|
|
1455
1779
|
claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
|
|
1456
1780
|
cursorDir: fs.existsSync(path.join(homedir(), ".cursor")),
|
|
1457
|
-
sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion"))
|
|
1781
|
+
sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion")),
|
|
1782
|
+
sniparaTemplates: fs.existsSync(path.join(process.cwd(), ".snipara", "templates"))
|
|
1458
1783
|
},
|
|
1459
1784
|
tooling: {
|
|
1460
1785
|
node: commandExists("node"),
|
|
@@ -1507,7 +1832,7 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
|
|
|
1507
1832
|
if (detection.existingConfig.hasSniparaServer) {
|
|
1508
1833
|
return "hosted-only";
|
|
1509
1834
|
}
|
|
1510
|
-
return
|
|
1835
|
+
return "hosted-only";
|
|
1511
1836
|
}
|
|
1512
1837
|
function readApiKeyFromEnvFiles() {
|
|
1513
1838
|
const candidates = [".env.local", ".env"];
|
|
@@ -1515,7 +1840,7 @@ function readApiKeyFromEnvFiles() {
|
|
|
1515
1840
|
const filePath = path.join(process.cwd(), file);
|
|
1516
1841
|
if (!fs.existsSync(filePath)) continue;
|
|
1517
1842
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
1518
|
-
const match = content.match(/^SNIPARA_API_KEY=(.+)$/m);
|
|
1843
|
+
const match = content.match(/^(?:SNIPARA_API_KEY|RLM_API_KEY)=(.+)$/m);
|
|
1519
1844
|
if (match?.[1]) {
|
|
1520
1845
|
return match[1].trim();
|
|
1521
1846
|
}
|
|
@@ -1525,7 +1850,8 @@ function readApiKeyFromEnvFiles() {
|
|
|
1525
1850
|
function detectProjectSlug() {
|
|
1526
1851
|
try {
|
|
1527
1852
|
const remote = execSync("git remote get-url origin", {
|
|
1528
|
-
encoding: "utf-8"
|
|
1853
|
+
encoding: "utf-8",
|
|
1854
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1529
1855
|
}).trim();
|
|
1530
1856
|
const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
|
|
1531
1857
|
if (match) {
|
|
@@ -1538,7 +1864,8 @@ function detectProjectSlug() {
|
|
|
1538
1864
|
function detectGitRepository() {
|
|
1539
1865
|
try {
|
|
1540
1866
|
const remoteUrl = execSync("git remote get-url origin", {
|
|
1541
|
-
encoding: "utf-8"
|
|
1867
|
+
encoding: "utf-8",
|
|
1868
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1542
1869
|
}).trim();
|
|
1543
1870
|
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) || remoteUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
1544
1871
|
if (!match) {
|
|
@@ -1561,7 +1888,8 @@ function detectGitRepository() {
|
|
|
1561
1888
|
function detectGitBranch() {
|
|
1562
1889
|
try {
|
|
1563
1890
|
const branch = execSync("git branch --show-current", {
|
|
1564
|
-
encoding: "utf-8"
|
|
1891
|
+
encoding: "utf-8",
|
|
1892
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1565
1893
|
}).trim();
|
|
1566
1894
|
if (branch) {
|
|
1567
1895
|
return branch;
|
|
@@ -1570,7 +1898,8 @@ function detectGitBranch() {
|
|
|
1570
1898
|
}
|
|
1571
1899
|
try {
|
|
1572
1900
|
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
1573
|
-
encoding: "utf-8"
|
|
1901
|
+
encoding: "utf-8",
|
|
1902
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1574
1903
|
}).trim();
|
|
1575
1904
|
return branch && branch !== "HEAD" ? branch : "main";
|
|
1576
1905
|
} catch {
|
|
@@ -1588,6 +1917,9 @@ function isGitRepo() {
|
|
|
1588
1917
|
}
|
|
1589
1918
|
}
|
|
1590
1919
|
function detectAiClient() {
|
|
1920
|
+
if (fs.existsSync(path.join(process.cwd(), ".codex"))) {
|
|
1921
|
+
return "codex";
|
|
1922
|
+
}
|
|
1591
1923
|
if (fs.existsSync(path.join(homedir(), ".claude")) || process.env.CLAUDE_CODE) {
|
|
1592
1924
|
return "claude-code";
|
|
1593
1925
|
}
|
|
@@ -1598,6 +1930,9 @@ function detectAiClient() {
|
|
|
1598
1930
|
if (fs.existsSync(claudeDesktopPath)) {
|
|
1599
1931
|
return "claude-desktop";
|
|
1600
1932
|
}
|
|
1933
|
+
if (fs.existsSync(path.join(homedir(), ".codex"))) {
|
|
1934
|
+
return "codex";
|
|
1935
|
+
}
|
|
1601
1936
|
return "other";
|
|
1602
1937
|
}
|
|
1603
1938
|
function detectPipCommand() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-snipara",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"description": "Onboard Snipara Hosted MCP Context + Memory with auth plus optional local helper and runtime workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
12
12
|
"dev": "tsup src/index.ts --format esm --watch",
|
|
13
13
|
"prepublishOnly": "npm run build",
|
|
14
|
+
"pretest": "pnpm build",
|
|
15
|
+
"test": "node --test test/*.test.cjs",
|
|
14
16
|
"pack:smoke": "bash ../../scripts/smoke-node-package.sh packages/create-snipara create-snipara create-snipara --help",
|
|
15
17
|
"type-check": "tsc --noEmit",
|
|
16
18
|
"lint": "eslint src --ext .ts"
|
|
@@ -26,6 +28,9 @@
|
|
|
26
28
|
"multi-agent",
|
|
27
29
|
"memory",
|
|
28
30
|
"claude-code",
|
|
31
|
+
"codex",
|
|
32
|
+
"chatgpt",
|
|
33
|
+
"openai",
|
|
29
34
|
"cursor",
|
|
30
35
|
"claude-desktop"
|
|
31
36
|
],
|