sp-rag 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,71 +1,129 @@
1
- # `sp-rag`
2
-
3
- CLI cho setup nhanh SP-RAG theo hướng dev-friendly:
4
-
5
- - lưu cấu hình mặc định để dev không phải nhớ lại URL, client, alias
6
- - kiểm tra nhanh health observability của stack
7
- - gọi sync codegraph/GitNexus theo branch hoặc `commit_sha`
8
- - đọc docs đã render
9
- - cài MCP config cho client
10
- - cài skill để agent ưu tiên dùng RAG nội bộ
11
- - chạy evaluation/regression suite từ file JSON
12
-
13
- ## Yêu cầu
14
-
15
- - Node.js `>= 20`
16
-
1
+ # `sp-rag`
2
+
3
+ CLI để setup nhanh SP-RAG theo hướng dev-friendly:
4
+
5
+ - lưu cấu hình mặc định để dev không phải nhớ lại URL, client, alias
6
+ - cài MCP config đúng format cho từng client
7
+ - cài native skill / rule / custom agent cho từng IDE khi có convention ổn định
8
+ - kiểm tra nhanh health và observability của stack
9
+ - gọi sync codegraph/GitNexus theo branch hoặc `commit_sha`
10
+ - đọc docs đã render
11
+ - chạy evaluation/regression suite từ file JSON
12
+
13
+ ## Yêu cầu
14
+
15
+ - Node.js `>= 20`
16
+
17
+ ## Trạng thái package
18
+
19
+ - package npm public: `sp-rag`
20
+ - version đang publish: `0.5.0`
21
+ - binary public: `sp-rag`
22
+
17
23
  ## Cài từ source trong monorepo
18
24
 
19
25
  ```bash
20
26
  cd apps/sp-rag-cli
21
27
  npm install
28
+ npm test -- --run
22
29
  npm run build
23
30
  node dist/index.js doctor
24
31
  ```
25
32
 
26
33
  ## Cài nhanh qua `npx`
27
34
 
28
- Nếu package đã được publish, luồng ngắn nhất là:
29
-
30
35
  ```bash
31
- npx sp-rag@latest install --client codex --mcp-token <token>
32
- npx sp-rag@latest token add --token <token> --client codex
36
+ npx sp-rag@latest install --client codex --mcp-token <token truy cập MCP> --doctor
37
+ npx sp-rag@latest install --client cursor --scope project --cwd D:/Webs/seo-booster --mcp-token <token truy cập MCP>
38
+ npx sp-rag@latest install --client vscode --scope project --cwd D:/Webs/seo-booster --mcp-token <token truy cập MCP>
39
+ npx sp-rag@latest token add --token <token truy cập MCP> --client codex
40
+ npx sp-rag@latest mcp add antigravity --mcp-token <token truy cập MCP>
41
+ npx sp-rag@latest mcp add opencode --scope project --cwd D:/Webs/seo-booster --mcp-token <token truy cập MCP>
42
+ npx sp-rag@latest skill install --skill-client cursor --scope project --cwd D:/Webs/seo-booster
43
+ npx sp-rag@latest skill install --skill-client vscode --scope project --cwd D:/Webs/seo-booster
33
44
  ```
34
45
 
35
46
  Tương đương bằng `npm`:
36
47
 
37
48
  ```bash
38
- npm exec --yes sp-rag@latest install -- --client codex --mcp-token <token>
39
- npm exec --yes sp-rag@latest token add -- --token <token> --client codex
49
+ npm exec --yes sp-rag@latest install -- --client codex --mcp-token <token truy cập MCP> --doctor
50
+ npm exec --yes sp-rag@latest token add -- --token <token truy cập MCP> --client codex
40
51
  ```
41
52
 
53
+ ## MCP client được hỗ trợ
54
+
55
+ - `codex`
56
+ - `cursor`
57
+ - `claude-code`
58
+ - `antigravity`
59
+ - `vscode`
60
+ - `opencode`
61
+
62
+ ## Skill client được hỗ trợ
63
+
64
+ - `codex` -> `SKILL.md`
65
+ - `claude-code` -> `SKILL.md`
66
+ - `antigravity` -> `SKILL.md`
67
+ - `opencode` -> `SKILL.md`
68
+ - `cursor` -> `.cursor/rules/sp-rag.mdc`
69
+ - `vscode` -> `.github/agents/sp-rag.agent.md` hoặc `~/.copilot/agents/sp-rag.agent.md`
70
+
42
71
  Ghi chú:
43
72
 
44
- - `install` lệnh onboarding một bước: lưu config, cài MCP config, cài skill, và có thể chạy `doctor`
45
- - `token add` dùng khi dev đã cài xong nhưng chưa gắn bearer token vào MCP client
73
+ - generated skill luôn được render bằng tiếng Anh
74
+ - `cursor` hiện nên dùng `scope project` cho rule `.mdc`
75
+ - `vscode` hỗ trợ cả `scope project` lẫn `scope global`
46
76
  - nếu không muốn lưu token literal vào file config client, dùng `sp-rag mcp add --auth-env-var SP_RAG_MCP_TOKEN`
47
77
 
78
+ ## Path mặc định quan trọng
79
+
80
+ ### MCP
81
+
82
+ - `codex`: `~/.codex/config.toml`
83
+ - `cursor` project: `.cursor/mcp.json`
84
+ - `cursor` global: `~/.cursor/mcp.json`
85
+ - `claude-code`: `.mcp.json`
86
+ - `antigravity`: `~/.gemini/antigravity/mcp_config.json`
87
+ - `vscode` project: `.vscode/mcp.json`
88
+ - `vscode` global: file `mcp.json` trong user profile của VS Code
89
+ - `opencode` project: `opencode.json`
90
+ - `opencode` global: `~/.config/opencode/opencode.json`
91
+
92
+ ### Skill / rule / custom agent
93
+
94
+ - `codex`: `~/.codex/skills/sp-rag/SKILL.md`
95
+ - `claude-code`: `~/.claude/skills/sp-rag/SKILL.md`
96
+ - `antigravity`: `~/.gemini/antigravity/skills/sp-rag/SKILL.md`
97
+ - `opencode`: `~/.config/opencode/skills/sp-rag/SKILL.md`
98
+ - `cursor` project: `.cursor/rules/sp-rag.mdc`
99
+ - `vscode` project: `.github/agents/sp-rag.agent.md`
100
+ - `vscode` global: `~/.copilot/agents/sp-rag.agent.md`
101
+
48
102
  ## Luồng khuyên dùng cho dev mới
49
103
 
50
104
  ```bash
51
- sp-rag install --client codex --mcp-token <token> --doctor
52
- sp-rag token add --token <token> --client codex
105
+ sp-rag install --client codex --mcp-token <token truy cập MCP> --doctor
106
+ sp-rag token add --token <token truy cập MCP> --client codex
53
107
  sp-rag config show
54
108
  sp-rag codegraph status
55
109
  sp-rag codegraph watch --interval-ms 2000
56
110
  sp-rag codegraph runs --limit 5
57
111
  sp-rag codegraph metrics
58
112
  sp-rag codegraph recover --reason "Ops dọn stale run sau crash"
59
- sp-rag mcp add codex --mcp-token <token>
113
+ sp-rag mcp add antigravity --mcp-token <token truy cập MCP>
114
+ sp-rag mcp add vscode --scope project --cwd D:/Webs/seo-booster --mcp-token <token truy cập MCP>
115
+ sp-rag mcp add opencode --scope project --cwd D:/Webs/seo-booster --mcp-token <token truy cập MCP>
60
116
  sp-rag skill install
117
+ sp-rag skill install --skill-client cursor --scope project --cwd D:/Webs/seo-booster
118
+ sp-rag skill install --skill-client vscode --scope project --cwd D:/Webs/seo-booster
61
119
  sp-rag eval run --file ./examples/eval-suite.sample.json
62
- ```
63
-
64
- ## Lệnh chính
65
-
120
+ ```
121
+
122
+ ## Lệnh chính
123
+
66
124
  ```bash
67
- sp-rag install --client codex --mcp-token <token> --doctor
68
- sp-rag token add --token <token> --client codex
125
+ sp-rag install --client codex --mcp-token <token truy cập MCP> --doctor
126
+ sp-rag token add --token <token truy cập MCP> --client codex
69
127
  sp-rag config show
70
128
  sp-rag doctor
71
129
  sp-rag codegraph status
@@ -73,16 +131,21 @@ sp-rag codegraph watch --interval-ms 2000
73
131
  sp-rag codegraph runs --limit 10
74
132
  sp-rag codegraph metrics
75
133
  sp-rag codegraph recover --reason "Ops dọn stale run sau crash"
76
- sp-rag codegraph sync --branch master --commit-sha <sha> --webhook-token <token> --gitlab-job-token <ci-job-token>
134
+ sp-rag codegraph sync --branch master --commit-sha <sha> --webhook-token <token webhook codegraph> --gitlab-job-token <ci-job-token>
77
135
  sp-rag docs get public --format md
78
- sp-rag mcp add codex --mcp-token <token>
136
+ sp-rag mcp add codex --mcp-token <token truy cập MCP>
137
+ sp-rag mcp add antigravity --mcp-token <token truy cập MCP>
138
+ sp-rag mcp add vscode --scope project --cwd D:/Webs/seo-booster --mcp-token <token truy cập MCP>
139
+ sp-rag mcp add opencode --scope project --cwd D:/Webs/seo-booster --mcp-token <token truy cập MCP>
79
140
  sp-rag skill install
141
+ sp-rag skill install --skill-client cursor --scope project --cwd D:/Webs/seo-booster
142
+ sp-rag skill install --skill-client vscode --scope project --cwd D:/Webs/seo-booster
80
143
  sp-rag eval run --file ./examples/eval-suite.sample.json
81
- sp-rag update setup --client codex
82
- ```
83
-
84
- ## Cấu hình mặc định
85
-
144
+ sp-rag update setup --client codex
145
+ ```
146
+
147
+ ## Cấu hình mặc định
148
+
86
149
  CLI lưu cấu hình tại:
87
150
 
88
151
  - `~/.sp-rag/config.json`
@@ -91,48 +154,22 @@ CLI lưu cấu hình tại:
91
154
  CLI có thể lưu thêm:
92
155
 
93
156
  - `mcpToken` cho flow cài nhanh
94
- - hoặc `authEnvVar` nếu muốn client đọc token từ biến môi trường thay vì lưu literal
95
-
96
- Giá trị mặc định:
97
-
98
- - base URL: `https://sp-rag.secomapp.com`
99
- - MCP URL: `https://sp-rag.secomapp.com/mcp`
100
- - alias MCP: `sp-rag`
101
-
102
- Override nhanh bằng env:
103
-
104
- ```bash
105
- SP_RAG_BASE_URL=https://sp-rag.secomapp.com
106
- SP_RAG_MCP_URL=https://sp-rag.secomapp.com/mcp
107
- SP_RAG_HOME_DIR=D:/Temp/sp-rag-home
108
- ```
109
-
110
- ## Evaluation mẫu
111
-
112
- - file mẫu: [`examples/eval-suite.sample.json`](./examples/eval-suite.sample.json)
113
-
114
- ## Recovery khi sync bị treo
115
-
116
- Nếu worker crash giữa chừng, `codegraph-ts` sẽ tự đổi run `running` cũ sang `failed` ở lần truy cập đầu tiên sau khi service lên lại. Khi ops muốn dọn tay hoặc gắn lý do vận hành rõ ràng hơn, dùng:
157
+ - `authEnvVar` nếu muốn client đọc token từ biến môi trường thay vì lưu literal
158
+ - `defaultClient`, `defaultScope`, `skillClient`
117
159
 
118
- ```bash
119
- sp-rag codegraph recover --reason "Ops dọn stale run sau crash"
120
- ```
160
+ Giá trị mặc định:
121
161
 
122
- CLI này gọi `POST /codegraph/sync-recover-stale` và trả lại danh sách run vừa được chuyển sang `failed`.
162
+ - base URL: `https://sp-rag.secomapp.com`
163
+ - MCP URL: `https://sp-rag.secomapp.com/mcp`
164
+ - alias MCP: `sp-rag`
123
165
 
124
- ## Theo dõi active
125
-
126
- Khi một sync đang chạy và ops muốn biết phase hiện tại theo thời gian thực, dùng:
166
+ ## Evaluation mẫu
127
167
 
128
- ```bash
129
- sp-rag codegraph watch --interval-ms 2000
130
- ```
168
+ - file mẫu: [`examples/eval-suite.sample.json`](./examples/eval-suite.sample.json)
131
169
 
132
- CLI sẽ in liên tục `lastStatus`, `activity.currentPhase`, `progressPercentHint`, `elapsed` và `message`.
133
-
134
170
  ## Tài liệu thêm
135
171
 
136
172
  - [Hướng dẫn dev sử dụng SP-RAG](../../docs/runbooks/dev-usage-guide.md)
137
173
  - [Runbook CLI `sp-rag`](../../docs/runbooks/sp-rag-cli.md)
174
+ - [Runbook phát hành CLI `sp-rag`](../../docs/runbooks/sp-rag-cli-release.md)
138
175
  - [Runbook MCP Public](../../docs/runbooks/mcp-public-clients.md)
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { loadCliConfig, saveCliConfig, } from './lib/config-store.js';
3
3
  import { runEvaluationSuite } from './lib/eval.js';
4
4
  import { defaultBaseUrl, defaultMcpServerAlias, defaultMcpUrl, installMcpConfig, } from './lib/mcp-config.js';
5
5
  import { fetchJson, fetchText, runDoctor } from './lib/http.js';
6
- import { installCodexSkill } from './lib/skill.js';
6
+ import { installSkill, } from './lib/skill.js';
7
7
  function parseArgv(argv) {
8
8
  const positionals = [];
9
9
  const options = {};
@@ -42,10 +42,47 @@ function supportedClient(value) {
42
42
  if (!value) {
43
43
  return undefined;
44
44
  }
45
- if (value === 'codex' || value === 'cursor' || value === 'claude-code') {
45
+ if (value === 'codex' ||
46
+ value === 'cursor' ||
47
+ value === 'claude-code' ||
48
+ value === 'antigravity' ||
49
+ value === 'vscode' ||
50
+ value === 'opencode') {
46
51
  return value;
47
52
  }
48
- throw new Error('Client phải là codex, cursor hoặc claude-code.');
53
+ throw new Error('Client phải là codex, cursor, claude-code, antigravity, vscode hoặc opencode.');
54
+ }
55
+ function supportedSkillClient(value) {
56
+ if (!value) {
57
+ return undefined;
58
+ }
59
+ if (value === 'codex' ||
60
+ value === 'cursor' ||
61
+ value === 'claude-code' ||
62
+ value === 'antigravity' ||
63
+ value === 'vscode' ||
64
+ value === 'opencode') {
65
+ return value;
66
+ }
67
+ throw new Error('Skill client phải là codex, cursor, claude-code, antigravity, vscode hoặc opencode.');
68
+ }
69
+ function defaultSkillClientForMcpClient(client) {
70
+ switch (client) {
71
+ case 'codex':
72
+ return 'codex';
73
+ case 'cursor':
74
+ return 'cursor';
75
+ case 'claude-code':
76
+ return 'claude-code';
77
+ case 'antigravity':
78
+ return 'antigravity';
79
+ case 'vscode':
80
+ return 'vscode';
81
+ case 'opencode':
82
+ return 'opencode';
83
+ default:
84
+ return undefined;
85
+ }
49
86
  }
50
87
  function supportedScope(value) {
51
88
  if (!value) {
@@ -90,6 +127,9 @@ async function loadRuntimeDefaults(parsed) {
90
127
  optionString(parsed, 'token') ??
91
128
  process.env['SP_RAG_MCP_TOKEN']?.trim() ??
92
129
  config?.mcpToken,
130
+ skillClient: supportedSkillClient(optionString(parsed, 'skill-client')) ??
131
+ config?.skillClient ??
132
+ defaultSkillClientForMcpClient(defaultClient),
93
133
  skillTargetDir: optionString(parsed, 'target-dir') ?? config?.skillTargetDir,
94
134
  };
95
135
  }
@@ -97,9 +137,9 @@ function helpText() {
97
137
  return `sp-rag - CLI cho setup, MCP, codegraph, eval và skill của SP-RAG
98
138
 
99
139
  Lệnh chính:
100
- sp-rag install [--base-url URL] [--mcp-url URL] [--client codex|cursor|claude-code] [--scope global|project] [--mcp-token TOKEN] [--auth-env-var ENV_VAR] [--target-dir PATH]
101
- sp-rag init [--base-url URL] [--mcp-url URL] [--client codex|cursor|claude-code] [--scope global|project] [--auth-env-var ENV_VAR] [--target-dir PATH]
102
- sp-rag token add --token TOKEN [--client codex|cursor|claude-code] [--scope global|project] [--cwd PATH]
140
+ sp-rag install [--base-url URL] [--mcp-url URL] [--client codex|cursor|claude-code|antigravity|vscode|opencode] [--skill-client codex|cursor|claude-code|antigravity|vscode|opencode] [--scope global|project] [--mcp-token TOKEN] [--auth-env-var ENV_VAR] [--target-dir PATH]
141
+ sp-rag init [--base-url URL] [--mcp-url URL] [--client codex|cursor|claude-code|antigravity|vscode|opencode] [--skill-client codex|cursor|claude-code|antigravity|vscode|opencode] [--scope global|project] [--auth-env-var ENV_VAR] [--target-dir PATH]
142
+ sp-rag token add --token TOKEN [--client codex|cursor|claude-code|antigravity|vscode|opencode] [--scope global|project] [--cwd PATH]
103
143
  sp-rag config show
104
144
  sp-rag doctor [--base-url URL]
105
145
  sp-rag codegraph status [--base-url URL]
@@ -109,10 +149,10 @@ Lệnh chính:
109
149
  sp-rag codegraph recover [--base-url URL] [--reason TEXT]
110
150
  sp-rag codegraph sync [--base-url URL] [--branch BRANCH] [--commit-sha SHA] [--force] [--webhook-token TOKEN] [--gitlab-job-token TOKEN]
111
151
  sp-rag docs get <public|function|dev> [--base-url URL] [--format md|json|html]
112
- sp-rag mcp add <codex|cursor|claude-code> [--url URL] [--scope global|project] [--auth-env-var ENV_VAR] [--mcp-token TOKEN]
113
- sp-rag skill install [--target-dir PATH] [--mcp-url URL] [--docs-url URL]
152
+ sp-rag mcp add <codex|cursor|claude-code|antigravity|vscode|opencode> [--url URL] [--scope global|project] [--auth-env-var ENV_VAR] [--mcp-token TOKEN]
153
+ sp-rag skill install [--skill-client codex|cursor|claude-code|antigravity|vscode|opencode] [--scope global|project] [--cwd PATH] [--target-dir PATH] [--mcp-url URL] [--docs-url URL]
114
154
  sp-rag eval run --file eval-suite.json [--base-url URL]
115
- sp-rag update setup [--client codex|cursor|claude-code] [--scope global|project] [--url URL] [--auth-env-var ENV_VAR] [--target-dir PATH]
155
+ sp-rag update setup [--client codex|cursor|claude-code|antigravity|vscode|opencode] [--skill-client codex|cursor|claude-code|antigravity|vscode|opencode] [--scope global|project] [--url URL] [--auth-env-var ENV_VAR] [--target-dir PATH]
116
156
  `;
117
157
  }
118
158
  function buildCliConfig(defaults) {
@@ -124,6 +164,7 @@ function buildCliConfig(defaults) {
124
164
  defaultScope: defaults.defaultScope,
125
165
  authEnvVar: defaults.authEnvVar,
126
166
  mcpToken: defaults.mcpToken,
167
+ skillClient: defaults.skillClient,
127
168
  skillTargetDir: defaults.skillTargetDir,
128
169
  docsUrl: defaults.docsUrl,
129
170
  };
@@ -256,7 +297,7 @@ async function runDocsGet(parsed) {
256
297
  async function runMcpAdd(parsed, defaults, explicitClient) {
257
298
  const client = supportedClient(explicitClient ?? parsed.positionals[2] ?? defaults.defaultClient);
258
299
  if (!client) {
259
- throw new Error('Thiếu client. Dùng codex, cursor hoặc claude-code.');
300
+ throw new Error('Thiếu client. Dùng codex, cursor, claude-code, antigravity, vscode hoặc opencode.');
260
301
  }
261
302
  const result = await installMcpConfig({
262
303
  client,
@@ -270,13 +311,19 @@ async function runMcpAdd(parsed, defaults, explicitClient) {
270
311
  process.stdout.write(`Đã cập nhật cấu hình MCP cho ${result.client} tại ${result.path} (${result.scope}).\n`);
271
312
  }
272
313
  async function runSkillInstall(parsed, defaults) {
273
- const result = await installCodexSkill({
314
+ const client = supportedSkillClient(optionString(parsed, 'skill-client')) ??
315
+ defaults.skillClient ??
316
+ 'codex';
317
+ const result = await installSkill({
318
+ client,
319
+ cwd: optionString(parsed, 'cwd'),
320
+ scope: supportedScope(optionString(parsed, 'scope')) ?? defaults.defaultScope,
274
321
  targetDir: optionString(parsed, 'target-dir') ?? defaults.skillTargetDir,
275
322
  serverAlias: optionString(parsed, 'server-alias') ?? defaults.serverAlias,
276
323
  mcpUrl: optionString(parsed, 'mcp-url') ?? defaults.mcpUrl,
277
324
  docsUrl: optionString(parsed, 'docs-url') ?? defaults.docsUrl,
278
325
  });
279
- process.stdout.write(`Đã cài Codex skill tại ${result.path}\n`);
326
+ process.stdout.write(`Đã cài skill cho ${result.client} tại ${result.path}\n`);
280
327
  }
281
328
  async function runInit(parsed) {
282
329
  const defaults = await loadRuntimeDefaults(parsed);
@@ -286,7 +333,7 @@ async function runInit(parsed) {
286
333
  if (!optionFlag(parsed, 'skip-mcp') && defaults.defaultClient) {
287
334
  await runMcpAdd(parsed, defaults, defaults.defaultClient);
288
335
  }
289
- if (!optionFlag(parsed, 'skip-skill')) {
336
+ if (!optionFlag(parsed, 'skip-skill') && defaults.skillClient) {
290
337
  await runSkillInstall(parsed, defaults);
291
338
  }
292
339
  if (optionFlag(parsed, 'doctor')) {
@@ -356,7 +403,7 @@ async function runUpdateSetup(parsed) {
356
403
  positionals: ['mcp', 'add', client],
357
404
  }, defaults, client);
358
405
  }
359
- if (!optionFlag(parsed, 'skip-skill')) {
406
+ if (!optionFlag(parsed, 'skip-skill') && defaults.skillClient) {
360
407
  await runSkillInstall(parsed, defaults);
361
408
  }
362
409
  }
@@ -444,7 +491,7 @@ export async function runCli(argv) {
444
491
  return 0;
445
492
  }
446
493
  if (group === 'version') {
447
- process.stdout.write('sp-rag 0.3.0\n');
494
+ process.stdout.write('sp-rag 0.5.0\n');
448
495
  return 0;
449
496
  }
450
497
  process.stdout.write(helpText());
@@ -7,6 +7,64 @@ function escapeRegex(value) {
7
7
  function quotedTomlKey(value) {
8
8
  return `"${value.replace(/"/g, '\\"')}"`;
9
9
  }
10
+ function parseJsonObject(existing) {
11
+ return existing?.trim() ? JSON.parse(existing) : {};
12
+ }
13
+ function withHeaders(target, headers) {
14
+ if (!headers) {
15
+ return target;
16
+ }
17
+ return {
18
+ ...target,
19
+ headers,
20
+ };
21
+ }
22
+ function bearerHeader(authToken, authEnvVar, envStyle = 'shell') {
23
+ const trimmedToken = authToken?.trim();
24
+ if (trimmedToken) {
25
+ return {
26
+ Authorization: `Bearer ${trimmedToken}`,
27
+ };
28
+ }
29
+ const trimmedEnvVar = authEnvVar?.trim();
30
+ if (!trimmedEnvVar) {
31
+ return undefined;
32
+ }
33
+ switch (envStyle) {
34
+ case 'vscode':
35
+ return {
36
+ Authorization: `Bearer \${env:${trimmedEnvVar}}`,
37
+ };
38
+ case 'opencode':
39
+ return {
40
+ Authorization: `Bearer {env:${trimmedEnvVar}}`,
41
+ };
42
+ default:
43
+ return {
44
+ Authorization: `Bearer \${${trimmedEnvVar}}`,
45
+ };
46
+ }
47
+ }
48
+ function upsertObjectEntry(base, sectionKey, entryKey, entryValue) {
49
+ const section = base[sectionKey] && typeof base[sectionKey] === 'object'
50
+ ? { ...base[sectionKey] }
51
+ : {};
52
+ section[entryKey] = entryValue;
53
+ return {
54
+ ...base,
55
+ [sectionKey]: section,
56
+ };
57
+ }
58
+ function vscodeGlobalConfigPath(home) {
59
+ if (process.platform === 'win32') {
60
+ const appData = process.env['APPDATA']?.trim() || path.join(home, 'AppData', 'Roaming');
61
+ return path.join(appData, 'Code', 'User', 'mcp.json');
62
+ }
63
+ if (process.platform === 'darwin') {
64
+ return path.join(home, 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
65
+ }
66
+ return path.join(home, '.config', 'Code', 'User', 'mcp.json');
67
+ }
10
68
  export function defaultMcpServerAlias() {
11
69
  return 'sp-rag';
12
70
  }
@@ -27,54 +85,60 @@ export function upsertCodexConfig(existing, options) {
27
85
  `[mcp_servers.${quotedTomlKey(alias)}]`,
28
86
  `url = "${options.url}"`,
29
87
  ];
30
- const authToken = options.authToken?.trim();
31
- const authEnvVar = options.authEnvVar?.trim();
32
- if (authToken) {
33
- lines.push('', `[mcp_servers.${quotedTomlKey(alias)}.headers]`);
34
- lines.push(`Authorization = "Bearer ${authToken.replace(/"/g, '\\"')}"`);
35
- }
36
- else if (authEnvVar) {
88
+ const headers = bearerHeader(options.authToken, options.authEnvVar, 'shell');
89
+ if (headers?.Authorization) {
37
90
  lines.push('', `[mcp_servers.${quotedTomlKey(alias)}.headers]`);
38
- lines.push(`Authorization = "Bearer \${${authEnvVar}}"`);
91
+ lines.push(`Authorization = "${headers.Authorization.replace(/"/g, '\\"')}"`);
39
92
  }
40
93
  return `${cleaned ? `${cleaned}\n\n` : ''}${lines.join('\n')}\n`;
41
94
  }
42
95
  export function upsertJsonMcpConfig(existing, options) {
43
- const base = existing?.trim()
44
- ? JSON.parse(existing)
45
- : {};
46
- const mcpServers = base['mcpServers'] && typeof base['mcpServers'] === 'object'
47
- ? { ...base['mcpServers'] }
48
- : {};
49
- const serverConfig = {
96
+ const base = parseJsonObject(existing);
97
+ const serverConfig = withHeaders({
98
+ ...(options.includeTypeHttp ? { type: 'http' } : {}),
50
99
  url: options.url,
51
- };
52
- if (options.includeTypeHttp) {
53
- serverConfig['type'] = 'http';
54
- }
55
- const authToken = options.authToken?.trim();
56
- const authEnvVar = options.authEnvVar?.trim();
57
- if (authToken) {
58
- serverConfig['headers'] = {
59
- Authorization: `Bearer ${authToken}`,
60
- };
61
- }
62
- else if (authEnvVar) {
63
- serverConfig['headers'] = {
64
- Authorization: `Bearer \${${authEnvVar}}`,
100
+ }, bearerHeader(options.authToken, options.authEnvVar, 'shell'));
101
+ return `${JSON.stringify(upsertObjectEntry(base, 'mcpServers', options.serverAlias, serverConfig), null, 2)}\n`;
102
+ }
103
+ export function upsertAntigravityConfig(existing, options) {
104
+ const base = parseJsonObject(existing);
105
+ const serverConfig = withHeaders({
106
+ serverUrl: options.url,
107
+ }, bearerHeader(options.authToken, options.authEnvVar, 'shell'));
108
+ return `${JSON.stringify(upsertObjectEntry(base, 'mcpServers', options.serverAlias, serverConfig), null, 2)}\n`;
109
+ }
110
+ export function upsertVsCodeConfig(existing, options) {
111
+ const base = parseJsonObject(existing);
112
+ const serverConfig = withHeaders({
113
+ type: 'http',
114
+ url: options.url,
115
+ }, bearerHeader(options.authToken, options.authEnvVar, 'vscode'));
116
+ return `${JSON.stringify(upsertObjectEntry(base, 'servers', options.serverAlias, serverConfig), null, 2)}\n`;
117
+ }
118
+ export function upsertOpenCodeConfig(existing, options) {
119
+ const base = parseJsonObject(existing);
120
+ const headers = bearerHeader(options.authToken, options.authEnvVar, 'opencode');
121
+ const nextBase = typeof base['$schema'] === 'string'
122
+ ? base
123
+ : {
124
+ ...base,
125
+ $schema: 'https://opencode.ai/config.json',
65
126
  };
66
- }
67
- mcpServers[options.serverAlias] = serverConfig;
68
- return `${JSON.stringify({
69
- ...base,
70
- mcpServers,
71
- }, null, 2)}\n`;
127
+ const serverConfig = withHeaders({
128
+ type: 'remote',
129
+ url: options.url,
130
+ enabled: true,
131
+ ...(headers ? { oauth: false } : {}),
132
+ }, headers);
133
+ return `${JSON.stringify(upsertObjectEntry(nextBase, 'mcp', options.serverAlias, serverConfig), null, 2)}\n`;
72
134
  }
73
135
  export function resolveMcpConfigPath(options) {
74
- const scope = options.scope ??
75
- (options.client === 'codex' ? 'global' : 'project');
76
- if (options.client === 'codex' && scope !== 'global') {
77
- throw new Error('Codex hiện chỉ hỗ trợ scope global trong CLI này.');
136
+ const inferredScope = options.client === 'codex' || options.client === 'antigravity'
137
+ ? 'global'
138
+ : 'project';
139
+ const scope = options.scope ?? inferredScope;
140
+ if ((options.client === 'codex' || options.client === 'antigravity') && scope !== 'global') {
141
+ throw new Error(`${options.client} hiện chỉ hỗ trợ scope global trong CLI này.`);
78
142
  }
79
143
  if (options.client === 'claude-code' && scope !== 'project') {
80
144
  throw new Error('Claude Code hiện chỉ hỗ trợ scope project trong CLI này.');
@@ -102,6 +166,26 @@ export function resolveMcpConfigPath(options) {
102
166
  scope,
103
167
  path: path.join(cwd, '.mcp.json'),
104
168
  };
169
+ case 'antigravity':
170
+ return {
171
+ client: options.client,
172
+ scope,
173
+ path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),
174
+ };
175
+ case 'vscode':
176
+ return {
177
+ client: options.client,
178
+ scope,
179
+ path: scope === 'global' ? vscodeGlobalConfigPath(home) : path.join(cwd, '.vscode', 'mcp.json'),
180
+ };
181
+ case 'opencode':
182
+ return {
183
+ client: options.client,
184
+ scope,
185
+ path: scope === 'global'
186
+ ? path.join(home, '.config', 'opencode', 'opencode.json')
187
+ : path.join(cwd, 'opencode.json'),
188
+ };
105
189
  }
106
190
  }
107
191
  export async function installMcpConfig(options) {
@@ -109,20 +193,58 @@ export async function installMcpConfig(options) {
109
193
  const existing = await readFile(resolved.path, 'utf8').catch(() => '');
110
194
  const alias = options.serverAlias?.trim() || defaultMcpServerAlias();
111
195
  const url = options.url.trim();
112
- const content = resolved.client === 'codex'
113
- ? upsertCodexConfig(existing, {
114
- serverAlias: alias,
115
- url,
116
- authEnvVar: options.authEnvVar,
117
- authToken: options.authToken,
118
- })
119
- : upsertJsonMcpConfig(existing, {
120
- serverAlias: alias,
121
- url,
122
- authEnvVar: options.authEnvVar,
123
- authToken: options.authToken,
124
- includeTypeHttp: resolved.client === 'claude-code',
125
- });
196
+ let content;
197
+ switch (resolved.client) {
198
+ case 'codex':
199
+ content = upsertCodexConfig(existing, {
200
+ serverAlias: alias,
201
+ url,
202
+ authEnvVar: options.authEnvVar,
203
+ authToken: options.authToken,
204
+ });
205
+ break;
206
+ case 'cursor':
207
+ content = upsertJsonMcpConfig(existing, {
208
+ serverAlias: alias,
209
+ url,
210
+ authEnvVar: options.authEnvVar,
211
+ authToken: options.authToken,
212
+ });
213
+ break;
214
+ case 'claude-code':
215
+ content = upsertJsonMcpConfig(existing, {
216
+ serverAlias: alias,
217
+ url,
218
+ authEnvVar: options.authEnvVar,
219
+ authToken: options.authToken,
220
+ includeTypeHttp: true,
221
+ });
222
+ break;
223
+ case 'antigravity':
224
+ content = upsertAntigravityConfig(existing, {
225
+ serverAlias: alias,
226
+ url,
227
+ authEnvVar: options.authEnvVar,
228
+ authToken: options.authToken,
229
+ });
230
+ break;
231
+ case 'vscode':
232
+ content = upsertVsCodeConfig(existing, {
233
+ serverAlias: alias,
234
+ url,
235
+ authEnvVar: options.authEnvVar,
236
+ authToken: options.authToken,
237
+ });
238
+ break;
239
+ case 'opencode':
240
+ content = upsertOpenCodeConfig(existing, {
241
+ serverAlias: alias,
242
+ url,
243
+ authEnvVar: options.authEnvVar,
244
+ authToken: options.authToken,
245
+ });
246
+ break;
247
+ }
126
248
  await mkdir(path.dirname(resolved.path), { recursive: true });
127
249
  await writeFile(resolved.path, content, 'utf8');
128
250
  return resolved;
package/dist/lib/skill.js CHANGED
@@ -1,51 +1,191 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import { mkdir, writeFile } from 'node:fs/promises';
4
- export function defaultSkillDir() {
5
- return path.join(os.homedir(), '.codex', 'skills', 'sp-rag');
4
+ function clientLabel(client) {
5
+ switch (client) {
6
+ case 'codex':
7
+ return 'Codex';
8
+ case 'claude-code':
9
+ return 'Claude Code';
10
+ case 'antigravity':
11
+ return 'Antigravity';
12
+ case 'opencode':
13
+ return 'OpenCode';
14
+ case 'cursor':
15
+ return 'Cursor';
16
+ case 'vscode':
17
+ return 'VS Code';
18
+ }
6
19
  }
7
- export function renderCodexSkill(options) {
8
- return `---
9
- name: sp-rag
10
- description: Dùng RAG nội bộ qua MCP để trả lời câu hỏi về codebase, domain, docs và vận hành trước khi dựa vào trí nhớ.
11
- ---
12
-
13
- # SP-RAG
14
-
15
- MCP server alias mặc định: \`${options.serverAlias}\`
16
- MCP URL: \`${options.mcpUrl}\`
17
- Docs URL: \`${options.docsUrl}\`
18
-
19
- ## Khi nào dùng
20
-
21
- - Khi câu hỏi liên quan đến codebase nội bộ, domain nghiệp vụ, docs đã render, trạng thái sync hoặc inventory import.
22
- - Khi cần câu trả lời evidence thay vì suy đoán theo trí nhớ.
23
-
24
- ## Luồng dùng khuyến nghị
25
-
26
- 1. Gọi \`healthz\` nếu nghi server đang lỗi hoặc vừa kết nối lần đầu.
27
- 2. Dùng \`query_context\` cho câu hỏi về kiến trúc, domain, flow nghiệp vụ, entities, relations.
28
- 3. Dùng \`get_rendered_docs\` cho public docs, function docs hoặc dev docs đã render sẵn.
29
- 4. Dùng \`get_sync_status\`, \`get_sync_runs\` hoặc \`get_sync_metrics\` khi cần kiểm tra code graph đang ở commit nào, sync có lỗi không, hay muốn đọc lịch sử và metrics vận hành.
30
- 5. Chỉ dùng \`trigger_code_graph_sync\` khi user yêu cầu làm mới index hoặc code graph.
31
-
32
- ## Nguyên tắc trả lời
33
-
34
- - Ưu tiên câu trả lời grounded từ MCP/RAG trước.
35
- - Nếu evidence có dấu hiệu stale, nói rõ dữ liệu có thể chưa được sync mới nhất.
36
- - Không tự kích hoạt sync/index nếu user chưa yêu cầu hoặc chưa thật sự cần.
37
- - Khi docs đã đủ, ưu tiên trích từ docs đã render thay vì tự diễn giải dài dòng.
20
+ function normalizeScope(client, scope) {
21
+ if (scope) {
22
+ return scope;
23
+ }
24
+ switch (client) {
25
+ case 'cursor':
26
+ return 'project';
27
+ case 'vscode':
28
+ return 'project';
29
+ default:
30
+ return 'global';
31
+ }
32
+ }
33
+ function requireProjectCwd(client, cwd) {
34
+ if (!cwd?.trim()) {
35
+ throw new Error(`${client} cần --cwd hoặc --target-dir để cài skill theo project.`);
36
+ }
37
+ return path.resolve(cwd);
38
+ }
39
+ export function defaultSkillDir(client = 'codex', cwd, scope) {
40
+ const normalizedScope = normalizeScope(client, scope);
41
+ switch (client) {
42
+ case 'codex':
43
+ return path.join(os.homedir(), '.codex', 'skills', 'sp-rag');
44
+ case 'claude-code':
45
+ return path.join(os.homedir(), '.claude', 'skills', 'sp-rag');
46
+ case 'antigravity':
47
+ return path.join(os.homedir(), '.gemini', 'antigravity', 'skills', 'sp-rag');
48
+ case 'opencode':
49
+ return path.join(os.homedir(), '.config', 'opencode', 'skills', 'sp-rag');
50
+ case 'cursor':
51
+ if (normalizedScope !== 'project') {
52
+ throw new Error('Cursor rule hiện chỉ nên cài ở scope project qua .cursor/rules.');
53
+ }
54
+ return path.join(requireProjectCwd(client, cwd), '.cursor', 'rules');
55
+ case 'vscode':
56
+ return normalizedScope === 'project'
57
+ ? path.join(requireProjectCwd(client, cwd), '.github', 'agents')
58
+ : path.join(os.homedir(), '.copilot', 'agents');
59
+ }
60
+ }
61
+ function renderSkillMarkdown(context) {
62
+ return `---
63
+ name: sp-rag
64
+ description: Use SP-RAG whenever the user asks about this codebase, internal business domain, rendered docs, import inventory, or codegraph sync status. Prefer MCP-backed evidence before answering from memory.
65
+ ---
66
+
67
+ # SP-RAG
68
+
69
+ Target client: ${clientLabel(context.client)}
70
+ Default MCP server alias: \`${context.serverAlias}\`
71
+ MCP URL: \`${context.mcpUrl}\`
72
+ Docs URL: \`${context.docsUrl}\`
73
+
74
+ ## When To Use This Skill
75
+
76
+ - Use this skill whenever the request is about the internal codebase, business workflows, rendered docs, import inventory, or sync state.
77
+ - Use this skill when grounded evidence matters more than memory or intuition.
78
+ - Use this skill when the answer should come from MCP tools or rendered docs before freeform reasoning.
79
+
80
+ ## Recommended Workflow
81
+
82
+ 1. Call \`healthz\` if the MCP server might be unavailable or the connection is brand new.
83
+ 2. Use \`query_context\` for architecture, domain, entities, relations, and business flow questions.
84
+ 3. Use \`get_rendered_docs\` for public, function, or dev docs that were already rendered from the latest graph.
85
+ 4. Use \`get_sync_status\`, \`get_sync_runs\`, or \`get_sync_metrics\` when you need to verify commit freshness, investigate failures, or inspect operational history.
86
+ 5. Only call \`trigger_code_graph_sync\` when the user explicitly asks to refresh the graph or when a stale graph is the confirmed blocker.
87
+
88
+ ## Guardrails
89
+
90
+ - Prefer MCP-grounded answers before relying on memory.
91
+ - If the evidence may be stale, say so clearly and mention that the graph or docs may need a refresh.
92
+ - Do not trigger sync or import actions unless the user asked for it or the workflow truly requires it.
93
+ - When rendered docs already answer the question, cite or summarize those docs instead of rewriting everything from scratch.
38
94
  `;
39
95
  }
40
- export async function installCodexSkill(options = {}) {
41
- const targetDir = path.resolve(options.targetDir ?? defaultSkillDir());
42
- const filePath = path.join(targetDir, 'SKILL.md');
43
- const content = renderCodexSkill({
96
+ function renderCursorRule(context) {
97
+ return `---
98
+ description: Use SP-RAG when the request is about this codebase, internal business workflows, rendered docs, import inventory, or codegraph sync status.
99
+ globs:
100
+ alwaysApply: false
101
+ ---
102
+
103
+ # SP-RAG
104
+
105
+ - Prefer the \`${context.serverAlias}\` MCP server at \`${context.mcpUrl}\` before answering from memory.
106
+ - Use rendered docs from \`${context.docsUrl}\` when documentation already answers the question.
107
+ - For architecture, domain, entities, relations, and business workflow questions, query MCP first and only then synthesize the answer.
108
+ - For freshness or operational questions, check sync status, recent runs, and metrics before assuming the graph is current.
109
+ - Only trigger codegraph sync when the user explicitly asks for it or stale evidence is the confirmed blocker.
110
+ - If the evidence may be stale, say so clearly.
111
+ `;
112
+ }
113
+ function renderVsCodeAgent(context) {
114
+ return `---
115
+ name: SP-RAG
116
+ description: Use this custom agent whenever the request is about this codebase, internal business workflows, rendered docs, import inventory, or codegraph sync status.
117
+ tools: ['${context.serverAlias}/*']
118
+ ---
119
+
120
+ You are the SP-RAG custom agent for this workspace.
121
+
122
+ Use the \`${context.serverAlias}\` MCP server at \`${context.mcpUrl}\` and the rendered docs URL \`${context.docsUrl}\` as your grounded source of truth.
123
+
124
+ ## Recommended Workflow
125
+
126
+ 1. Start with \`healthz\` if connectivity or freshness is uncertain.
127
+ 2. Use \`query_context\` for architecture, domain, entities, relations, and workflow questions.
128
+ 3. Use \`get_rendered_docs\` when public, function, or dev docs may already answer the question.
129
+ 4. Use \`get_sync_status\`, \`get_sync_runs\`, or \`get_sync_metrics\` for freshness, incident review, or operational debugging.
130
+ 5. Trigger codegraph sync only when the user explicitly requests a refresh or stale evidence is the confirmed blocker.
131
+
132
+ ## Guardrails
133
+
134
+ - Prefer MCP-grounded evidence before answering from memory.
135
+ - When evidence may be stale, say so clearly and mention that a refresh might be needed.
136
+ - Do not trigger sync or import actions unless the workflow truly requires it.
137
+ - When rendered docs already answer the question, summarize those docs instead of rewriting everything from scratch.
138
+ `;
139
+ }
140
+ function renderSkillArtifact(context) {
141
+ switch (context.client) {
142
+ case 'cursor':
143
+ return {
144
+ fileName: 'sp-rag.mdc',
145
+ content: renderCursorRule(context),
146
+ };
147
+ case 'vscode':
148
+ return {
149
+ fileName: 'sp-rag.agent.md',
150
+ content: renderVsCodeAgent(context),
151
+ };
152
+ default:
153
+ return {
154
+ fileName: 'SKILL.md',
155
+ content: renderSkillMarkdown(context),
156
+ };
157
+ }
158
+ }
159
+ export function renderSkill(options) {
160
+ return renderSkillArtifact(options).content;
161
+ }
162
+ export function renderCodexSkill(options) {
163
+ return renderSkill({
164
+ client: 'codex',
165
+ ...options,
166
+ });
167
+ }
168
+ export async function installSkill(options = {}) {
169
+ const client = options.client ?? 'codex';
170
+ const scope = normalizeScope(client, options.scope);
171
+ const targetDir = path.resolve(options.targetDir ?? defaultSkillDir(client, options.cwd, scope));
172
+ const artifact = renderSkillArtifact({
173
+ client,
44
174
  serverAlias: options.serverAlias?.trim() || 'sp-rag',
45
175
  mcpUrl: options.mcpUrl?.trim() || 'https://sp-rag.secomapp.com/mcp',
46
176
  docsUrl: options.docsUrl?.trim() || 'https://sp-rag.secomapp.com/codegraph/docs/public?format=md',
47
177
  });
178
+ const filePath = path.join(targetDir, artifact.fileName);
48
179
  await mkdir(targetDir, { recursive: true });
49
- await writeFile(filePath, content, 'utf8');
50
- return { path: filePath };
180
+ await writeFile(filePath, artifact.content, 'utf8');
181
+ return {
182
+ client,
183
+ path: filePath,
184
+ };
185
+ }
186
+ export async function installCodexSkill(options = {}) {
187
+ return installSkill({
188
+ ...options,
189
+ client: 'codex',
190
+ });
51
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp-rag",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI cho setup MCP, codegraph GitNexus và skill của SP-RAG",
5
5
  "type": "module",
6
6
  "files": [