@thinkingai/ae-cli 1.0.28 → 1.0.30
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 +1 -16
- package/README.zh.md +1 -16
- package/dist/{auth-5NSDQFQB.js → auth-H3GJD55A.js} +8 -5
- package/dist/{auth-H2376DEF.js → auth-U25QJU5D.js} +2 -1
- package/dist/{chunk-IMBKVXKY.js → chunk-EGEIXA2Z.js} +82 -10
- package/dist/{chunk-WCHI7725.js → chunk-GLNZSDKO.js} +48 -5
- package/dist/{chunk-LU7SXK4Q.js → chunk-I42JGQ2J.js} +4 -2
- package/dist/{chunk-CAQYQA4R.js → chunk-JM34JPCO.js} +3 -163
- package/dist/chunk-NOO24N7W.js +11 -0
- package/dist/chunk-SF3KTPIC.js +98 -0
- package/dist/chunk-U6TKB3IV.js +171 -0
- package/dist/{chunk-AD5V3ZPJ.js → chunk-VJFUB3H3.js} +97 -9
- package/dist/{client-FCMK3XEK.js → client-IN42F223.js} +3 -2
- package/dist/index.js +42 -20
- package/dist/{model-EZ6K7FXI.js → model-VZCYNIOG.js} +4 -6
- package/dist/{raw-VEF3D6UD.js → raw-WJIWC6YP.js} +5 -3
- package/dist/{sync-NRODEVNH.js → sync-NVASAISS.js} +118 -21
- package/dist/{te-agent-7U7JLJKU.js → te-agent-YV4DXEXK.js} +1 -2
- package/dist/{te-analysis-A2OSGHQ4.js → te-analysis-CB3DA5I5.js} +4 -2
- package/dist/{te-audience-LI67PPGO.js → te-audience-W7SCQFC7.js} +4 -2
- package/dist/{te-common-35WVU3C5.js → te-common-FBXVWBSI.js} +4 -2
- package/dist/{te-community-LEAP6PWT.js → te-community-LNVDZSGW.js} +4 -2
- package/dist/{te-dataops-N2LGZBOX.js → te-dataops-IPCSSWYH.js} +27 -25
- package/dist/{te-engage-H7C72Z46.js → te-engage-KARARMIR.js} +4 -2
- package/dist/{te-kb-I2OKTBAI.js → te-kb-2Z2CARN3.js} +5 -3
- package/dist/{te-meta-HBTIEDRO.js → te-meta-PDKLMWXN.js} +4 -2
- package/dist/{te-team-EM5OWRQL.js → te-team-SW5B7DHO.js} +14 -9
- package/package.json +1 -1
- package/skills/ae-agent/SKILL.md +1 -1
- package/dist/chunk-KAEZTSXN.js +0 -99
- package/dist/chunk-OJDNO5QY.js +0 -63
package/README.md
CHANGED
|
@@ -31,24 +31,9 @@ npx skills add ThinkingAIAgenticEngine/ae-cli -g -y
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
|
|
35
|
-
ae-cli config
|
|
34
|
+
ae-cli auth login --host xxxxx
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
The `config` command opens an interactive terminal UI:
|
|
39
|
-
- First run: prompts you to add a AE host URL and label, then auto-authenticates
|
|
40
|
-
- Subsequent runs: shows all configured hosts, lets you switch, edit, delete, or add new ones
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
AE Host Manager (↑↓ select · Enter switch · e edit label · d delete · a add · q quit)
|
|
44
|
-
|
|
45
|
-
❯ ● Production https://ta.thinkingdata.cn ✓
|
|
46
|
-
○ Staging https://ta-staging.example.com:8080 ✗
|
|
47
|
-
+ Add new host...
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
After selecting a host, ae-cli automatically checks if the token is valid. If not, it triggers `auth login` for you.
|
|
51
|
-
|
|
52
37
|
## Usage
|
|
53
38
|
|
|
54
39
|
```bash
|
package/README.zh.md
CHANGED
|
@@ -31,24 +31,9 @@ npx skills add ThinkingAIAgenticEngine/ae-cli -g -y
|
|
|
31
31
|
## 快速开始
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
|
|
35
|
-
ae-cli config
|
|
34
|
+
ae-cli auth login --host xxxxx
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
`config` 命令会打开一个交互式终端界面:
|
|
39
|
-
- 首次运行:提示您添加 AE 主机 URL 和标签,然后自动进行身份验证
|
|
40
|
-
- 后续运行:显示所有已配置的主机,可以切换、编辑、删除或添加新主机
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
AE Host Manager (↑↓ 选择 · Enter 切换 · e 编辑标签 · d 删除 · a 添加 · q 退出)
|
|
44
|
-
|
|
45
|
-
❯ ● Production https://ta.thinkingdata.cn ✓
|
|
46
|
-
○ Staging https://ta-staging.example.com:8080 ✗
|
|
47
|
-
+ Add new host...
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
选择主机后,ae-cli 会自动检查 token 是否有效。如果无效,会自动触发 `auth login`。
|
|
51
|
-
|
|
52
37
|
## 使用方法
|
|
53
38
|
|
|
54
39
|
```bash
|
|
@@ -6,18 +6,21 @@ import {
|
|
|
6
6
|
clearMcpToken,
|
|
7
7
|
setMcpTokenManual,
|
|
8
8
|
validateMcpToken
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GLNZSDKO.js";
|
|
10
10
|
import "./chunk-GSJGPNKK.js";
|
|
11
|
+
import "./chunk-NOO24N7W.js";
|
|
11
12
|
import {
|
|
12
|
-
clear,
|
|
13
13
|
clearToken,
|
|
14
14
|
getAuthStatus,
|
|
15
|
-
load,
|
|
16
15
|
resolveHost,
|
|
17
|
-
save,
|
|
18
16
|
setTokenManual,
|
|
19
17
|
validateToken
|
|
20
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-U6TKB3IV.js";
|
|
19
|
+
import {
|
|
20
|
+
clear,
|
|
21
|
+
load,
|
|
22
|
+
save
|
|
23
|
+
} from "./chunk-JM34JPCO.js";
|
|
21
24
|
import {
|
|
22
25
|
loadConfig,
|
|
23
26
|
logger,
|
|
@@ -293,8 +293,33 @@ function writeSkillManifestEntries(manifestPath, entries) {
|
|
|
293
293
|
);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
// src/commands/sync/validation.ts
|
|
297
|
+
var SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
298
|
+
var MCP_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
299
|
+
function isValidSkillSlug(slug) {
|
|
300
|
+
return slug.length > 0 && slug.length <= 48 && SKILL_SLUG_RE.test(slug);
|
|
301
|
+
}
|
|
302
|
+
function assertValidSkillSlug(slug) {
|
|
303
|
+
if (!isValidSkillSlug(slug)) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`Invalid Skill slug: ${slug}. Skill slugs may only contain lowercase letters, digits, and non-consecutive hyphens (-), must be at most 48 characters, and must not start or end with a hyphen`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function isValidMcpName(name) {
|
|
310
|
+
return name.length >= 2 && name.length <= 64 && MCP_NAME_RE.test(name);
|
|
311
|
+
}
|
|
312
|
+
function assertValidMcpName(name) {
|
|
313
|
+
if (!isValidMcpName(name)) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Invalid MCP name: ${name}. MCP names may only contain letters, digits, underscores, and hyphens, must be 2-64 characters, and must start with a letter`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
296
320
|
// src/commands/sync/scanners.ts
|
|
297
321
|
var SECRET_PATTERN = /TOKEN|SECRET|KEY|PASSWORD/i;
|
|
322
|
+
var MCP_MANIFEST_FILE = ".mcp-manifest.json";
|
|
298
323
|
function safeLstat(p) {
|
|
299
324
|
try {
|
|
300
325
|
return lstatSync(p);
|
|
@@ -360,6 +385,7 @@ function scanSkillsInDir(dir, source, workspacePath) {
|
|
|
360
385
|
if (!isDir) continue;
|
|
361
386
|
const filePath = join(slugDir, "SKILL.md");
|
|
362
387
|
if (!existsSync2(filePath)) continue;
|
|
388
|
+
assertValidSkillSlug(slug);
|
|
363
389
|
const file = readSkillFile(filePath);
|
|
364
390
|
if (!file) continue;
|
|
365
391
|
out.push({
|
|
@@ -389,23 +415,42 @@ function readJsonSafe(p) {
|
|
|
389
415
|
return null;
|
|
390
416
|
}
|
|
391
417
|
}
|
|
418
|
+
function isMcpManifestScope(value) {
|
|
419
|
+
return value === "personal" || value === "company" || value === "system";
|
|
420
|
+
}
|
|
421
|
+
function readMcpManifestScopes(workspaceDir) {
|
|
422
|
+
const parsed = readJsonSafe(join(workspaceDir, MCP_MANIFEST_FILE));
|
|
423
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
424
|
+
if (!Array.isArray(parsed)) return scopes;
|
|
425
|
+
for (const item of parsed) {
|
|
426
|
+
if (!item || typeof item !== "object") continue;
|
|
427
|
+
const entry = item;
|
|
428
|
+
if (typeof entry.name !== "string" || !isMcpManifestScope(entry.scope)) continue;
|
|
429
|
+
if (scopes.has(entry.name)) continue;
|
|
430
|
+
scopes.set(entry.name, entry.scope);
|
|
431
|
+
}
|
|
432
|
+
return scopes;
|
|
433
|
+
}
|
|
392
434
|
function detectSecrets(env) {
|
|
393
435
|
if (!env) return false;
|
|
394
436
|
return Object.keys(env).some((k) => SECRET_PATTERN.test(k));
|
|
395
437
|
}
|
|
396
|
-
function parseMcpServers(servers, source, workspacePath, options = {
|
|
438
|
+
function parseMcpServers(servers, source, workspacePath, workspaceDir, options = {}) {
|
|
397
439
|
if (!servers || typeof servers !== "object") return [];
|
|
398
440
|
const out = [];
|
|
399
441
|
for (const [slug, raw] of Object.entries(servers)) {
|
|
400
442
|
if (!raw || typeof raw !== "object") continue;
|
|
401
|
-
if (options.
|
|
402
|
-
|
|
443
|
+
if (options.excludedNames?.has(slug)) continue;
|
|
444
|
+
assertValidMcpName(slug);
|
|
445
|
+
const rawTransport = raw.transport ?? raw.type;
|
|
446
|
+
const transport = rawTransport === "sse" ? "sse" : rawTransport === "http" || typeof raw.url === "string" ? "http" : "stdio";
|
|
403
447
|
const env = raw.env && typeof raw.env === "object" ? raw.env : void 0;
|
|
404
448
|
const headers = raw.headers && typeof raw.headers === "object" ? raw.headers : void 0;
|
|
405
449
|
out.push({
|
|
406
450
|
slug,
|
|
407
451
|
source,
|
|
408
452
|
workspacePath,
|
|
453
|
+
workspaceDir,
|
|
409
454
|
transport,
|
|
410
455
|
url: typeof raw.url === "string" ? raw.url : void 0,
|
|
411
456
|
command: typeof raw.command === "string" ? raw.command : void 0,
|
|
@@ -444,21 +489,45 @@ function scanMcps() {
|
|
|
444
489
|
if (!workspace) return [];
|
|
445
490
|
const out = [];
|
|
446
491
|
const scopedMcpJson = readJsonSafe(join(workspace.dir, ".mcp.json"));
|
|
447
|
-
|
|
492
|
+
const projectManifestScopes = readMcpManifestScopes(workspace.dir);
|
|
493
|
+
const excludedProjectMcps = new Set(
|
|
494
|
+
[...projectManifestScopes.entries()].filter(([, scope]) => scope === "system" || scope === "company").map(([name]) => name)
|
|
495
|
+
);
|
|
496
|
+
out.push(
|
|
497
|
+
...parseMcpServers(
|
|
498
|
+
scopedMcpJson?.mcpServers,
|
|
499
|
+
"project",
|
|
500
|
+
workspace.name,
|
|
501
|
+
workspace.dir,
|
|
502
|
+
{ excludedNames: excludedProjectMcps }
|
|
503
|
+
)
|
|
504
|
+
);
|
|
448
505
|
const workspaceClaudeJson = readJsonSafe(join(workspace.dir, ".claude", ".claude.json"));
|
|
449
506
|
out.push(
|
|
450
|
-
...parseMcpServers(
|
|
451
|
-
|
|
452
|
-
|
|
507
|
+
...parseMcpServers(
|
|
508
|
+
projectMcpServers(workspaceClaudeJson, workspace.dir),
|
|
509
|
+
"local",
|
|
510
|
+
workspace.name,
|
|
511
|
+
void 0
|
|
512
|
+
)
|
|
453
513
|
);
|
|
454
514
|
const globalClaudeJson = readJsonSafe(join(home, ".claude.json"));
|
|
455
515
|
out.push(
|
|
456
|
-
...parseMcpServers(
|
|
457
|
-
|
|
458
|
-
|
|
516
|
+
...parseMcpServers(
|
|
517
|
+
projectMcpServers(globalClaudeJson, workspace.dir),
|
|
518
|
+
"user",
|
|
519
|
+
void 0,
|
|
520
|
+
void 0
|
|
521
|
+
)
|
|
459
522
|
);
|
|
460
523
|
return out;
|
|
461
524
|
}
|
|
525
|
+
function splitPushableMcps(candidates) {
|
|
526
|
+
return {
|
|
527
|
+
supported: candidates.filter((mcp) => mcp.transport === "http" || mcp.transport === "sse"),
|
|
528
|
+
unsupportedStdio: candidates.filter((mcp) => mcp.transport === "stdio")
|
|
529
|
+
};
|
|
530
|
+
}
|
|
462
531
|
function describeSource(_c) {
|
|
463
532
|
return void 0;
|
|
464
533
|
}
|
|
@@ -471,8 +540,11 @@ export {
|
|
|
471
540
|
SKILL_MANIFEST_FILE,
|
|
472
541
|
readSkillManifestEntries,
|
|
473
542
|
writeSkillManifestEntries,
|
|
543
|
+
assertValidSkillSlug,
|
|
544
|
+
assertValidMcpName,
|
|
474
545
|
getCurrentWorkspace,
|
|
475
546
|
scanSkills,
|
|
476
547
|
scanMcps,
|
|
548
|
+
splitPushableMcps,
|
|
477
549
|
describeSource
|
|
478
550
|
};
|
|
@@ -2,9 +2,14 @@ import {
|
|
|
2
2
|
getAnalysisMappingPathForClusterMode
|
|
3
3
|
} from "./chunk-GSJGPNKK.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
PermissionError
|
|
6
|
+
} from "./chunk-NOO24N7W.js";
|
|
7
|
+
import {
|
|
8
|
+
getToken
|
|
9
|
+
} from "./chunk-U6TKB3IV.js";
|
|
10
|
+
import {
|
|
6
11
|
loadMcpToken
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JM34JPCO.js";
|
|
8
13
|
import {
|
|
9
14
|
getActiveHost,
|
|
10
15
|
getFallbackMcpToken,
|
|
@@ -13,6 +18,16 @@ import {
|
|
|
13
18
|
} from "./chunk-SRJIAOBN.js";
|
|
14
19
|
|
|
15
20
|
// src/core/mcp.ts
|
|
21
|
+
async function permissionMessage(resp) {
|
|
22
|
+
const text = await resp.text().catch(() => "");
|
|
23
|
+
try {
|
|
24
|
+
const d = safeJsonParse(text);
|
|
25
|
+
const msg = d && (d.error?.message || (typeof d.error === "string" ? d.error : void 0) || d.message);
|
|
26
|
+
if (msg && typeof msg === "string") return msg;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return "Permission denied for this resource (HTTP 403)";
|
|
30
|
+
}
|
|
16
31
|
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
17
32
|
var JSONRPC_VERSION = "2.0";
|
|
18
33
|
var serviceMappingMap = /* @__PURE__ */ new Map();
|
|
@@ -178,8 +193,11 @@ async function mcpRequest(url, method, params = {}, hostOverride) {
|
|
|
178
193
|
body: JSON.stringify(body)
|
|
179
194
|
});
|
|
180
195
|
if (!resp.ok) {
|
|
181
|
-
|
|
182
|
-
|
|
196
|
+
if (resp.status === 403) {
|
|
197
|
+
throw new PermissionError(await permissionMessage(resp));
|
|
198
|
+
}
|
|
199
|
+
if (resp.status === 401) {
|
|
200
|
+
logger.warn(`MCP request failed (HTTP 401) for ${hostUrl}, re-minting token`);
|
|
183
201
|
_mcpTokenCache.delete(hostUrl);
|
|
184
202
|
const fallbackToken = getFallbackMcpToken(hostUrl);
|
|
185
203
|
const newToken = fallbackToken ?? await generateMcpToken(hostUrl);
|
|
@@ -193,6 +211,9 @@ async function mcpRequest(url, method, params = {}, hostOverride) {
|
|
|
193
211
|
headers,
|
|
194
212
|
body: JSON.stringify(body)
|
|
195
213
|
});
|
|
214
|
+
if (retryResp.status === 403) {
|
|
215
|
+
throw new PermissionError(await permissionMessage(retryResp));
|
|
216
|
+
}
|
|
196
217
|
if (!retryResp.ok) {
|
|
197
218
|
throw new Error(`MCP HTTP error: ${retryResp.status} ${retryResp.statusText}`);
|
|
198
219
|
}
|
|
@@ -236,14 +257,36 @@ async function callMcpTool(url, toolName, args = {}, hostOverride) {
|
|
|
236
257
|
function parseMcpResult(result) {
|
|
237
258
|
if (result.content.length > 0 && result.content[0].type === "text") {
|
|
238
259
|
const text = result.content[0].text || "";
|
|
260
|
+
let parsed;
|
|
239
261
|
try {
|
|
240
|
-
|
|
262
|
+
parsed = safeJsonParse(text);
|
|
241
263
|
} catch {
|
|
242
264
|
return text;
|
|
243
265
|
}
|
|
266
|
+
const errMsg = mcpResultErrorMessage(parsed);
|
|
267
|
+
if (errMsg !== null) {
|
|
268
|
+
if (/no_auth|auth_error|permission|forbidden|unauthor|\u65e0\u6743/i.test(errMsg)) {
|
|
269
|
+
throw new PermissionError(errMsg);
|
|
270
|
+
}
|
|
271
|
+
throw new Error(errMsg);
|
|
272
|
+
}
|
|
273
|
+
return parsed;
|
|
244
274
|
}
|
|
245
275
|
return result;
|
|
246
276
|
}
|
|
277
|
+
function mcpResultErrorMessage(parsed) {
|
|
278
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
279
|
+
if (parsed.success === true || parsed.ok === true) return null;
|
|
280
|
+
const e = parsed.error;
|
|
281
|
+
if (typeof e === "string" && e.trim()) return e;
|
|
282
|
+
if (e && typeof e === "object") {
|
|
283
|
+
const msg = typeof e.message === "string" && e.message || void 0;
|
|
284
|
+
const code = typeof e.code === "string" && e.code || void 0;
|
|
285
|
+
if (msg && code) return `${code}: ${msg}`;
|
|
286
|
+
if (msg || code) return msg || code;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
247
290
|
|
|
248
291
|
export {
|
|
249
292
|
registerMcpMappings,
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getActiveHost,
|
|
3
2
|
getConfigDir,
|
|
4
3
|
logger,
|
|
5
|
-
safeJsonParse
|
|
6
|
-
safeReadJsonFile
|
|
4
|
+
safeJsonParse
|
|
7
5
|
} from "./chunk-SRJIAOBN.js";
|
|
8
6
|
|
|
9
|
-
// src/core/auth.ts
|
|
10
|
-
import fs2 from "fs";
|
|
11
|
-
import path2 from "path";
|
|
12
|
-
|
|
13
7
|
// src/core/secure-store.ts
|
|
14
8
|
import crypto from "crypto";
|
|
15
9
|
import fs from "fs";
|
|
@@ -235,165 +229,11 @@ var SecureStoreAuthError = class extends Error {
|
|
|
235
229
|
}
|
|
236
230
|
};
|
|
237
231
|
|
|
238
|
-
// src/core/auth.ts
|
|
239
|
-
var TOKENS_FILE = path2.join(getConfigDir(), "tokens.json");
|
|
240
|
-
var LEGACY_MCP_TOKENS_FILE = path2.join(getConfigDir(), "mcp-tokens.json");
|
|
241
|
-
var LEGACY_DIR = path2.join(process.env.HOME || "", ".te-mcp");
|
|
242
|
-
(function removeLegacyMcpTokens() {
|
|
243
|
-
try {
|
|
244
|
-
if (fs2.existsSync(LEGACY_MCP_TOKENS_FILE)) {
|
|
245
|
-
fs2.rmSync(LEGACY_MCP_TOKENS_FILE);
|
|
246
|
-
logger.info("auth: removed legacy mcp-tokens.json (plaintext MCP token file)");
|
|
247
|
-
}
|
|
248
|
-
} catch {
|
|
249
|
-
}
|
|
250
|
-
})();
|
|
251
|
-
function ensureDir() {
|
|
252
|
-
const dir = getConfigDir();
|
|
253
|
-
if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
|
|
254
|
-
}
|
|
255
|
-
function resolveHost(hostOverride) {
|
|
256
|
-
if (hostOverride) return hostOverride;
|
|
257
|
-
return getActiveHost();
|
|
258
|
-
}
|
|
259
|
-
async function validateToken(token, hostUrl) {
|
|
260
|
-
const url = `${hostUrl}/v1/oauth/checkToken`;
|
|
261
|
-
try {
|
|
262
|
-
const resp = await fetch(url, {
|
|
263
|
-
method: "POST",
|
|
264
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
265
|
-
body: new URLSearchParams({ accessToken: token }).toString()
|
|
266
|
-
});
|
|
267
|
-
const respJson = safeJsonParse(await resp.text());
|
|
268
|
-
return respJson?.return_code === 0;
|
|
269
|
-
} catch {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function loadAllTokens() {
|
|
274
|
-
try {
|
|
275
|
-
const legacyTokens = path2.join(LEGACY_DIR, "tokens.json");
|
|
276
|
-
if (fs2.existsSync(legacyTokens) && !fs2.existsSync(TOKENS_FILE)) {
|
|
277
|
-
const data = safeReadJsonFile(legacyTokens);
|
|
278
|
-
const migrated = {};
|
|
279
|
-
for (const [key, val] of Object.entries(data)) {
|
|
280
|
-
const url = key.startsWith("http") ? key : `https://${key}`;
|
|
281
|
-
migrated[url] = val;
|
|
282
|
-
}
|
|
283
|
-
ensureDir();
|
|
284
|
-
fs2.writeFileSync(TOKENS_FILE, JSON.stringify(migrated, null, 2));
|
|
285
|
-
return migrated;
|
|
286
|
-
}
|
|
287
|
-
if (fs2.existsSync(TOKENS_FILE)) {
|
|
288
|
-
const data = safeReadJsonFile(TOKENS_FILE);
|
|
289
|
-
let needsMigration = false;
|
|
290
|
-
const migrated = {};
|
|
291
|
-
for (const [key, val] of Object.entries(data)) {
|
|
292
|
-
if (!key.startsWith("http")) {
|
|
293
|
-
migrated[`https://${key}`] = val;
|
|
294
|
-
needsMigration = true;
|
|
295
|
-
} else {
|
|
296
|
-
migrated[key] = val;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (needsMigration) {
|
|
300
|
-
fs2.writeFileSync(TOKENS_FILE, JSON.stringify(migrated, null, 2));
|
|
301
|
-
return migrated;
|
|
302
|
-
}
|
|
303
|
-
return data;
|
|
304
|
-
}
|
|
305
|
-
} catch {
|
|
306
|
-
}
|
|
307
|
-
return {};
|
|
308
|
-
}
|
|
309
|
-
function saveAllTokens(tokens) {
|
|
310
|
-
ensureDir();
|
|
311
|
-
fs2.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2));
|
|
312
|
-
try {
|
|
313
|
-
fs2.chmodSync(TOKENS_FILE, 384);
|
|
314
|
-
} catch {
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
function loadToken(hostUrl) {
|
|
318
|
-
const tokens = loadAllTokens();
|
|
319
|
-
const entry = tokens[hostUrl];
|
|
320
|
-
if (!entry || !entry.token) return null;
|
|
321
|
-
return { host: hostUrl, token: entry.token, updatedAt: entry.updatedAt };
|
|
322
|
-
}
|
|
323
|
-
function saveToken(token, hostUrl) {
|
|
324
|
-
const tokens = loadAllTokens();
|
|
325
|
-
tokens[hostUrl] = { token, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
326
|
-
saveAllTokens(tokens);
|
|
327
|
-
logger.info(`Token saved for ${hostUrl}`);
|
|
328
|
-
}
|
|
329
|
-
function clearToken(hostUrl) {
|
|
330
|
-
const tokens = loadAllTokens();
|
|
331
|
-
delete tokens[hostUrl];
|
|
332
|
-
saveAllTokens(tokens);
|
|
333
|
-
}
|
|
334
|
-
function setTokenManual(token, hostUrl) {
|
|
335
|
-
saveToken(token, hostUrl);
|
|
336
|
-
}
|
|
337
|
-
function getAuthStatus(hostUrl) {
|
|
338
|
-
if (process.env.TE_TOKEN) {
|
|
339
|
-
return { authenticated: true, host: hostUrl, source: "env:TE_TOKEN" };
|
|
340
|
-
}
|
|
341
|
-
const cached = loadToken(hostUrl);
|
|
342
|
-
if (cached) {
|
|
343
|
-
const ageMs = Date.now() - new Date(cached.updatedAt).getTime();
|
|
344
|
-
const hours = Math.round(ageMs / 36e5);
|
|
345
|
-
return { authenticated: true, host: hostUrl, tokenAge: `${hours}h ago`, source: "cache" };
|
|
346
|
-
}
|
|
347
|
-
return { authenticated: false, host: hostUrl };
|
|
348
|
-
}
|
|
349
|
-
async function getToken(hostUrl) {
|
|
350
|
-
if (!hostUrl) {
|
|
351
|
-
throw new Error(
|
|
352
|
-
`No AE host configured.
|
|
353
|
-
Run: ae-cli config set-host <url>`
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
if (process.env.TE_TOKEN) {
|
|
357
|
-
logger.info("Using token from env:TE_TOKEN");
|
|
358
|
-
return process.env.TE_TOKEN;
|
|
359
|
-
}
|
|
360
|
-
const cached = loadToken(hostUrl);
|
|
361
|
-
if (cached && cached.token) {
|
|
362
|
-
logger.info(`Using cached token for ${hostUrl}`);
|
|
363
|
-
return cached.token;
|
|
364
|
-
}
|
|
365
|
-
try {
|
|
366
|
-
const secureToken = await getValidAccessToken(hostUrl);
|
|
367
|
-
logger.info(`Using secure-store token for ${hostUrl}`);
|
|
368
|
-
return secureToken;
|
|
369
|
-
} catch (e) {
|
|
370
|
-
if (e instanceof SecureStoreAuthError) {
|
|
371
|
-
logger.info(`secure-store: ${e.message}`);
|
|
372
|
-
} else {
|
|
373
|
-
logger.warn(`secure-store unexpected error: ${e.message}`);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
throw new Error(
|
|
377
|
-
`Cannot obtain token for ${hostUrl}.
|
|
378
|
-
Options:
|
|
379
|
-
1. ae-cli auth login (device code flow, cross-platform)
|
|
380
|
-
2. ae-cli auth set-token <token>
|
|
381
|
-
3. export TE_TOKEN=<token> (CI/headless)`
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
232
|
export {
|
|
386
233
|
save,
|
|
387
234
|
load,
|
|
388
235
|
clear,
|
|
236
|
+
getValidAccessToken,
|
|
389
237
|
loadMcpToken,
|
|
390
|
-
SecureStoreAuthError
|
|
391
|
-
resolveHost,
|
|
392
|
-
validateToken,
|
|
393
|
-
loadToken,
|
|
394
|
-
saveToken,
|
|
395
|
-
clearToken,
|
|
396
|
-
setTokenManual,
|
|
397
|
-
getAuthStatus,
|
|
398
|
-
getToken
|
|
238
|
+
SecureStoreAuthError
|
|
399
239
|
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMcpToken
|
|
3
|
+
} from "./chunk-GLNZSDKO.js";
|
|
4
|
+
import {
|
|
5
|
+
PermissionError
|
|
6
|
+
} from "./chunk-NOO24N7W.js";
|
|
7
|
+
import {
|
|
8
|
+
safeJsonParse
|
|
9
|
+
} from "./chunk-SRJIAOBN.js";
|
|
10
|
+
|
|
11
|
+
// src/core/mcp-access.ts
|
|
12
|
+
function buildUrl(host, path, params = {}) {
|
|
13
|
+
const base = host.replace(/\/$/, "");
|
|
14
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
15
|
+
const url = new URL(`${base}${p}`);
|
|
16
|
+
for (const [key, value] of Object.entries(params)) {
|
|
17
|
+
if (value !== void 0 && value !== null) {
|
|
18
|
+
url.searchParams.set(key, String(value));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return url.toString();
|
|
22
|
+
}
|
|
23
|
+
function parseKbResponse(resp, text) {
|
|
24
|
+
if (resp.status === 403) {
|
|
25
|
+
let msg = "Permission denied for this resource (HTTP 403)";
|
|
26
|
+
try {
|
|
27
|
+
const d = safeJsonParse(text);
|
|
28
|
+
const m = d && (typeof d.error === "string" ? d.error : d.error?.message || d.message);
|
|
29
|
+
if (m && typeof m === "string") msg = m;
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
throw new PermissionError(msg);
|
|
33
|
+
}
|
|
34
|
+
if (resp.status === 401) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`KB MCP token auth failed: HTTP ${resp.status} ${resp.statusText}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const data = safeJsonParse(text);
|
|
40
|
+
if (data.return_code === -1001) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`KB MCP token auth failed: ${data.return_message || "unauthorized"} (code: ${data.return_code})`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (data.return_code !== 0 && data.return_code !== void 0) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`AE API error: ${data.return_message || "unknown"} (code: ${data.return_code})`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (data && typeof data === "object" && typeof data.error === "string" && data.error.trim() && data.return_code === void 0 && data.success === void 0) {
|
|
51
|
+
throw new Error(data.error);
|
|
52
|
+
}
|
|
53
|
+
return data.data !== void 0 ? data.data : data;
|
|
54
|
+
}
|
|
55
|
+
async function fetchWithMcpToken(input, init, mcpToken) {
|
|
56
|
+
const headers = new Headers(init.headers);
|
|
57
|
+
headers.set("mcp-token", mcpToken);
|
|
58
|
+
const resp = await fetch(input, { ...init, headers });
|
|
59
|
+
return parseKbResponse(resp, await resp.text());
|
|
60
|
+
}
|
|
61
|
+
async function getAuthHeaders(ctx) {
|
|
62
|
+
const mcpToken = await getMcpToken(ctx.host());
|
|
63
|
+
return { "mcp-token": mcpToken };
|
|
64
|
+
}
|
|
65
|
+
async function kbApi(ctx, method, path, params = {}, body) {
|
|
66
|
+
const host = ctx.host();
|
|
67
|
+
const mcpToken = await getMcpToken(host);
|
|
68
|
+
const upperMethod = method.toUpperCase();
|
|
69
|
+
return fetchWithMcpToken(
|
|
70
|
+
buildUrl(host, path, params),
|
|
71
|
+
{
|
|
72
|
+
method: upperMethod,
|
|
73
|
+
headers: {
|
|
74
|
+
"Content-Type": "application/json"
|
|
75
|
+
},
|
|
76
|
+
body: upperMethod === "GET" ? void 0 : JSON.stringify(body ?? {})
|
|
77
|
+
},
|
|
78
|
+
mcpToken
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
async function kbUpload(ctx, path, form, params = {}) {
|
|
82
|
+
const host = ctx.host();
|
|
83
|
+
const mcpToken = await getMcpToken(host);
|
|
84
|
+
return fetchWithMcpToken(
|
|
85
|
+
buildUrl(host, path, params),
|
|
86
|
+
{
|
|
87
|
+
method: "POST",
|
|
88
|
+
body: form
|
|
89
|
+
},
|
|
90
|
+
mcpToken
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
getAuthHeaders,
|
|
96
|
+
kbApi,
|
|
97
|
+
kbUpload
|
|
98
|
+
};
|