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 +112 -75
- package/dist/cli.js +62 -15
- package/dist/lib/mcp-config.js +175 -53
- package/dist/lib/skill.js +179 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,71 +1,129 @@
|
|
|
1
|
-
# `sp-rag`
|
|
2
|
-
|
|
3
|
-
CLI
|
|
4
|
-
|
|
5
|
-
- lưu cấu hình mặc định để dev không phải nhớ lại URL, client, alias
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
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
|
|
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
|
-
-
|
|
45
|
-
- `
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
119
|
-
sp-rag codegraph recover --reason "Ops dọn stale run sau crash"
|
|
120
|
-
```
|
|
160
|
+
Giá trị mặc định:
|
|
121
161
|
|
|
122
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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 {
|
|
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' ||
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
494
|
+
process.stdout.write('sp-rag 0.5.0\n');
|
|
448
495
|
return 0;
|
|
449
496
|
}
|
|
450
497
|
process.stdout.write(helpText());
|
package/dist/lib/mcp-config.js
CHANGED
|
@@ -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
|
|
31
|
-
|
|
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 = "
|
|
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
|
|
44
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
},
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 {
|
|
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
|
}
|