sp-rag 0.6.8 → 0.6.10

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/dist/cli.js CHANGED
@@ -5,7 +5,18 @@ import { defaultBaseUrl, defaultMcpServerAlias, defaultMcpUrl, installMcpConfig,
5
5
  import { diagnoseMcpConfig, formatMcpDoctorResult } from './lib/doctor.js';
6
6
  import { fetchJson, fetchText, runDoctor } from './lib/http.js';
7
7
  import { installSkill, resolveSkillInstallTarget, } from './lib/skill.js';
8
- const cliVersion = '0.6.7';
8
+ let cliVersionPromise;
9
+ async function resolveCliVersion() {
10
+ if (!cliVersionPromise) {
11
+ cliVersionPromise = readFile(new URL('../package.json', import.meta.url), 'utf8')
12
+ .then((raw) => {
13
+ const packageJson = JSON.parse(raw);
14
+ return packageJson.version?.trim() || '0.0.0';
15
+ })
16
+ .catch(() => '0.0.0');
17
+ }
18
+ return cliVersionPromise;
19
+ }
9
20
  function parseArgv(argv) {
10
21
  const positionals = [];
11
22
  const options = {};
@@ -166,8 +177,21 @@ function deriveDefaultsForClient(parsed, defaults, client) {
166
177
  skillClient: nextSkillClient,
167
178
  };
168
179
  }
180
+ function validateMcpToken(token) {
181
+ const normalized = token?.trim();
182
+ if (!normalized) {
183
+ return undefined;
184
+ }
185
+ if (normalized.startsWith('pat_')) {
186
+ throw new Error('Token dạng pat_ không dùng được cho MCP. Đây có vẻ là token_id, không phải generated_token bí mật. Hãy dùng token bắt đầu bằng grc_pat_.');
187
+ }
188
+ return normalized;
189
+ }
190
+ function validateStoredToken(defaults) {
191
+ validateMcpToken(defaults.mcpToken);
192
+ }
169
193
  function resolveAuthForMcp(parsed, defaults) {
170
- const authToken = optionString(parsed, 'mcp-token') ?? defaults.mcpToken;
194
+ const authToken = validateMcpToken(optionString(parsed, 'mcp-token') ?? defaults.mcpToken);
171
195
  const authEnvVar = optionString(parsed, 'auth-env-var') ?? defaults.authEnvVar;
172
196
  if (!authToken?.trim() && !authEnvVar?.trim()) {
173
197
  throw new Error('Chưa có token MCP trong config. Hãy chạy sp-rag install --client <client> --mcp-token <token> hoặc sp-rag token add --token <token>.');
@@ -400,6 +424,7 @@ async function emitDoctorReport(parsed, defaults, explicitClient, checks) {
400
424
  }
401
425
  async function runClientSetup(parsed, defaults, client) {
402
426
  const nextDefaults = deriveDefaultsForClient(parsed, defaults, client);
427
+ validateStoredToken(nextDefaults);
403
428
  const configPath = await saveResolvedConfig(nextDefaults);
404
429
  process.stdout.write(`Đã lưu config CLI tại ${configPath}\n`);
405
430
  if (!optionFlag(parsed, 'skip-mcp')) {
@@ -416,6 +441,7 @@ async function runInit(parsed) {
416
441
  const defaults = await loadRuntimeDefaults(parsed);
417
442
  const client = resolveSelectedClient(parsed, defaults);
418
443
  const nextDefaults = deriveDefaultsForClient(parsed, defaults, client);
444
+ validateStoredToken(nextDefaults);
419
445
  const configPath = await saveResolvedConfig(nextDefaults);
420
446
  process.stdout.write(`Đã lưu config CLI tại ${configPath}\n`);
421
447
  if (!optionFlag(parsed, 'skip-mcp') && client) {
@@ -446,7 +472,7 @@ async function runAdd(parsed) {
446
472
  }
447
473
  async function runTokenAdd(parsed) {
448
474
  const defaults = await loadRuntimeDefaults(parsed);
449
- const token = optionString(parsed, 'token') ?? optionString(parsed, 'mcp-token') ?? defaults.mcpToken;
475
+ const token = validateMcpToken(optionString(parsed, 'token') ?? optionString(parsed, 'mcp-token') ?? defaults.mcpToken);
450
476
  if (!token?.trim()) {
451
477
  throw new Error('Thiếu token. Dùng --token <token>.');
452
478
  }
@@ -460,7 +486,7 @@ async function runTokenAdd(parsed) {
460
486
  }
461
487
  async function runTokenVerify(parsed) {
462
488
  const defaults = await loadRuntimeDefaults(parsed);
463
- const token = optionString(parsed, 'token') ?? optionString(parsed, 'mcp-token') ?? defaults.mcpToken;
489
+ const token = validateMcpToken(optionString(parsed, 'token') ?? optionString(parsed, 'mcp-token') ?? defaults.mcpToken);
464
490
  if (!token?.trim()) {
465
491
  throw new Error('Thiếu token. Dùng --token <token>.');
466
492
  }
@@ -577,7 +603,7 @@ export async function runCli(argv) {
577
603
  const [group, action] = parsed.positionals;
578
604
  try {
579
605
  if (optionFlag(parsed, 'version')) {
580
- process.stdout.write(`sp-rag ${cliVersion}\n`);
606
+ process.stdout.write(`sp-rag ${await resolveCliVersion()}\n`);
581
607
  return 0;
582
608
  }
583
609
  if (!group || group === 'help' || group === '--help') {
@@ -675,7 +701,7 @@ export async function runCli(argv) {
675
701
  return 0;
676
702
  }
677
703
  if (group === 'version') {
678
- process.stdout.write(`sp-rag ${cliVersion}\n`);
704
+ process.stdout.write(`sp-rag ${await resolveCliVersion()}\n`);
679
705
  return 0;
680
706
  }
681
707
  process.stdout.write(helpText());
@@ -7,6 +7,18 @@ function normalizeEndpoint(value) {
7
7
  const trimmed = value.trim();
8
8
  return trimmed ? trimmed.replace(/\/+$/, '') : undefined;
9
9
  }
10
+ function parseStringHeaders(value) {
11
+ if (!value || typeof value !== 'object') {
12
+ return undefined;
13
+ }
14
+ const headers = {};
15
+ for (const [key, headerValue] of Object.entries(value)) {
16
+ if (typeof headerValue === 'string') {
17
+ headers[key] = headerValue;
18
+ }
19
+ }
20
+ return Object.keys(headers).length > 0 ? headers : undefined;
21
+ }
10
22
  function parseJsonEntries(raw, client) {
11
23
  const parsed = JSON.parse(raw);
12
24
  let sectionKey = 'mcpServers';
@@ -28,29 +40,16 @@ function parseJsonEntries(raw, client) {
28
40
  return [];
29
41
  }
30
42
  const entry = value;
31
- const headers = parseStringHeaders(entry.headers);
32
43
  return [
33
44
  {
34
45
  alias,
35
46
  endpoint: normalizeEndpoint(entry[endpointKey]),
36
- headers,
47
+ headers: parseStringHeaders(entry.headers),
37
48
  oauth: entry.oauth,
38
49
  },
39
50
  ];
40
51
  });
41
52
  }
42
- function parseStringHeaders(value) {
43
- if (!value || typeof value !== 'object') {
44
- return undefined;
45
- }
46
- const headers = {};
47
- for (const [key, headerValue] of Object.entries(value)) {
48
- if (typeof headerValue === 'string') {
49
- headers[key] = headerValue;
50
- }
51
- }
52
- return Object.keys(headers).length > 0 ? headers : undefined;
53
- }
54
53
  function parseTomlEntries(raw) {
55
54
  const entries = new Map();
56
55
  let currentAlias;
@@ -141,14 +140,41 @@ function analyzeAuthorization(headerValue) {
141
140
  }
142
141
  const envVar = match[1];
143
142
  const envValue = process.env[envVar]?.trim();
143
+ if (!envValue) {
144
+ return {
145
+ authorizationState: 'env-missing',
146
+ authorizationSource: 'env',
147
+ authorizationEnvVar: envVar,
148
+ likelyOAuthLogin: true,
149
+ };
150
+ }
151
+ if (envValue.startsWith('pat_')) {
152
+ return {
153
+ authorizationState: 'invalid',
154
+ authorizationSource: 'env',
155
+ authorizationEnvVar: envVar,
156
+ authorizationInvalidReason: 'pat_token',
157
+ likelyOAuthLogin: true,
158
+ };
159
+ }
144
160
  return {
145
- authorizationState: envValue ? 'env-present' : 'env-missing',
161
+ authorizationState: 'env-present',
146
162
  authorizationSource: 'env',
147
163
  authorizationEnvVar: envVar,
148
- likelyOAuthLogin: !envValue,
164
+ likelyOAuthLogin: false,
149
165
  };
150
166
  }
151
- if (/^Bearer\s+\S+/i.test(trimmed)) {
167
+ const bearerMatch = trimmed.match(/^Bearer\s+(\S+)$/i);
168
+ if (bearerMatch) {
169
+ const token = bearerMatch[1];
170
+ if (token.startsWith('pat_')) {
171
+ return {
172
+ authorizationState: 'invalid',
173
+ authorizationSource: 'literal',
174
+ authorizationInvalidReason: 'pat_token',
175
+ likelyOAuthLogin: true,
176
+ };
177
+ }
152
178
  return {
153
179
  authorizationState: 'present',
154
180
  authorizationSource: 'literal',
@@ -216,12 +242,19 @@ export async function diagnoseMcpConfig(options) {
216
242
  issues.push(`Authorization đang dùng biến môi trường ${authorization.authorizationEnvVar} nhưng biến này chưa có giá trị trong phiên hiện tại.`);
217
243
  }
218
244
  else if (authorization.authorizationState === 'invalid') {
219
- issues.push('Authorization header có định dạng lạ, IDE có thể bỏ qua và bật OAuth.');
245
+ if (authorization.authorizationInvalidReason === 'pat_token') {
246
+ issues.push('Authorization đang dùng token dạng pat_. Đây có vẻ là token_id hoặc mã hiển thị, không phải generated_token bí mật cho MCP. Hãy dùng token bắt đầu bằng grc_pat_.');
247
+ }
248
+ else {
249
+ issues.push('Authorization header có định dạng lạ, IDE có thể bỏ qua và bật OAuth.');
250
+ }
220
251
  }
221
252
  const oauthDisabled = options.client === 'opencode' && typeof entry.oauth === 'boolean'
222
253
  ? entry.oauth === false
223
254
  : undefined;
224
- if (options.client === 'opencode' && authorization.authorizationState !== 'missing' && oauthDisabled !== true) {
255
+ if (options.client === 'opencode' &&
256
+ authorization.authorizationState !== 'missing' &&
257
+ oauthDisabled !== true) {
225
258
  issues.push('OpenCode chưa có oauth=false dù đã cấu hình Authorization header.');
226
259
  }
227
260
  return {
@@ -241,13 +274,13 @@ export function formatMcpDoctorResult(result) {
241
274
  `MCP config cho ${result.client}: ${result.path} (${result.scope})`,
242
275
  ];
243
276
  if (!result.exists) {
244
- lines.push(`- Trạng thái file: thiếu hoặc không đọc được`);
277
+ lines.push('- Trạng thái file: thiếu hoặc không đọc được');
245
278
  for (const issue of result.issues) {
246
279
  lines.push(`- Cảnh báo: ${issue}`);
247
280
  }
248
281
  return lines;
249
282
  }
250
- lines.push(`- Trạng thái file: đã đọc được`);
283
+ lines.push('- Trạng thái file: đã đọc được');
251
284
  lines.push(`- Server entry: ${result.entryFound
252
285
  ? `${result.matchedAlias} (${result.matchedBy === 'endpoint' ? 'khớp theo endpoint' : 'khớp theo alias'})`
253
286
  : 'không tìm thấy'}`);
@@ -261,7 +294,9 @@ export function formatMcpDoctorResult(result) {
261
294
  : result.authorizationState === 'env-missing'
262
295
  ? `dùng biến môi trường ${result.authorizationEnvVar} nhưng hiện chưa có giá trị`
263
296
  : result.authorizationState === 'invalid'
264
- ? 'có header nhưng định dạng không hợp lệ'
297
+ ? result.authorizationInvalidReason === 'pat_token'
298
+ ? 'token hiện tại là pat_ nên không hợp lệ cho MCP'
299
+ : 'có header nhưng định dạng không hợp lệ'
265
300
  : 'thiếu hoàn toàn';
266
301
  lines.push(`- Authorization: ${authText}`);
267
302
  if (typeof result.oauthDisabled === 'boolean') {
package/dist/lib/skill.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import { mkdir, writeFile } from 'node:fs/promises';
4
+ const seoBoosterRemoteUrl = 'https://gitlab.com/secomapp/development/seo-booster.git';
5
+ const seoBoosterProjectGuard = [
6
+ '- SP-RAG chỉ dành cho repo seo-booster.',
7
+ '- Trước tiên, nếu basename là `seo-booster` hoặc `sb` thì được phép gọi SP-RAG ngay.',
8
+ `- Nếu basename không phải \`seo-booster\` hoặc \`sb\`, hãy kiểm tra \`git remote get-url origin\` và chỉ gọi SP-RAG khi remote hiện tại là \`${seoBoosterRemoteUrl}\`.`,
9
+ '- Nếu project hiện tại không khớp hai điều kiện trên, không được gọi SP-RAG và phải nói rõ MCP này chỉ dành cho seo-booster.',
10
+ ].join('\n');
4
11
  function clientLabel(client) {
5
12
  switch (client) {
6
13
  case 'codex':
@@ -89,6 +96,7 @@ Docs URL: \`${context.docsUrl}\`
89
96
 
90
97
  ## Guardrails
91
98
 
99
+ ${seoBoosterProjectGuard}
92
100
  - You must call SP-RAG MCP tools first for codebase or domain questions before using local workspace search, grep, or file reads.
93
101
  - Prefer MCP-grounded answers before relying on memory.
94
102
  - Treat \`answer_brief\` as a hint only. Prefer the richer evidence in \`matched_passages\`, \`top_entities\`, \`top_relations\`, and \`citations\` when writing the final answer.
@@ -107,6 +115,7 @@ alwaysApply: true
107
115
 
108
116
  # SP-RAG
109
117
 
118
+ ${seoBoosterProjectGuard}
110
119
  - You must call the \`${context.serverAlias}\` MCP server first before using local workspace search, grep, or file reads for codebase and domain questions.
111
120
  - Use rendered docs from \`${context.docsUrl}\` when documentation already answers the question.
112
121
  - For architecture, domain, entities, relations, and business workflow questions, query MCP first and only then synthesize the answer.
@@ -141,6 +150,7 @@ You must call an SP-RAG MCP tool before using local workspace search, grep, or f
141
150
 
142
151
  ## Guardrails
143
152
 
153
+ ${seoBoosterProjectGuard}
144
154
  - Prefer MCP-grounded evidence before answering from memory.
145
155
  - Treat \`answer_brief\` as a hint. Prefer the richer evidence in \`matched_passages\`, \`top_entities\`, \`top_relations\`, and \`citations\` when writing the final answer.
146
156
  - Do not use local workspace search, grep, or file reads until SP-RAG MCP has been tried first for the current question.
@@ -153,6 +163,7 @@ You must call an SP-RAG MCP tool before using local workspace search, grep, or f
153
163
  function renderVsCodeAlwaysOnInstructions(context) {
154
164
  return `# SP-RAG workspace instructions
155
165
 
166
+ ${seoBoosterProjectGuard}
156
167
  - Always use the \`${context.serverAlias}\` MCP server first for codebase, domain, architecture, workflow, and operational questions before local workspace search, grep, or file reads.
157
168
  - Start with \`healthz\` only when connectivity is uncertain or the session is new.
158
169
  - Use \`query_context\` first for feature, entity, relation, and business-flow questions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp-rag",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "CLI cho setup MCP, codegraph GitNexus và skill của SP-RAG",
5
5
  "type": "module",
6
6
  "files": [
@@ -9,12 +9,12 @@
9
9
  "bin": {
10
10
  "sp-rag": "dist/index.js"
11
11
  },
12
- "scripts": {
13
- "build": "tsc -p tsconfig.json",
14
- "prepack": "npm run build",
15
- "start": "node dist/index.js",
16
- "test": "vitest run"
17
- },
12
+ "scripts": {
13
+ "build": "tsc -p tsconfig.json",
14
+ "prepack": "npm run build",
15
+ "start": "node dist/index.js",
16
+ "test": "vitest run"
17
+ },
18
18
  "engines": {
19
19
  "node": ">=20"
20
20
  },