@thinkingai/ae-cli 1.0.27 → 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-H3GJD55A.js +457 -0
- package/dist/{auth-TXFJHPXU.js → auth-U25QJU5D.js} +3 -3
- package/dist/{chunk-B4UL2VIJ.js → chunk-CLJF7MQA.js} +1 -1
- package/dist/{chunk-6T7245YE.js → chunk-EGEIXA2Z.js} +90 -18
- package/dist/{chunk-2NGWYMLB.js → chunk-GLNZSDKO.js} +88 -50
- package/dist/chunk-GSJGPNKK.js +49 -0
- package/dist/{chunk-VT6VC4PA.js → chunk-I42JGQ2J.js} +26 -11
- package/dist/chunk-JM34JPCO.js +239 -0
- package/dist/chunk-NOO24N7W.js +11 -0
- package/dist/chunk-SF3KTPIC.js +98 -0
- package/dist/chunk-SRJIAOBN.js +244 -0
- package/dist/chunk-U6TKB3IV.js +171 -0
- package/dist/{chunk-TG64AQV4.js → chunk-VJFUB3H3.js} +99 -56
- package/dist/{client-IU2E5IOL.js → client-IN42F223.js} +4 -4
- package/dist/config-64G36TJL.js +130 -0
- package/dist/index.js +52 -25
- package/dist/{model-CC7U5XOJ.js → model-VZCYNIOG.js} +11 -10
- package/dist/{raw-TUHZQ4UB.js → raw-WJIWC6YP.js} +13 -7
- package/dist/{sync-EVEDS7YE.js → sync-NVASAISS.js} +146 -47
- package/dist/{te-agent-FLTDYV3L.js → te-agent-YV4DXEXK.js} +24 -23
- package/dist/{te-analysis-CMXADIJM.js → te-analysis-CB3DA5I5.js} +61 -13
- package/dist/{te-audience-LPZFFSXM.js → te-audience-W7SCQFC7.js} +7 -5
- package/dist/{te-common-BXQFKTB3.js → te-common-FBXVWBSI.js} +21 -9
- package/dist/{te-community-K5S3VOZY.js → te-community-LNVDZSGW.js} +6 -4
- package/dist/{te-dataops-567IMCQG.js → te-dataops-IPCSSWYH.js} +29 -27
- package/dist/{te-engage-TBUTGW5F.js → te-engage-KARARMIR.js} +6 -4
- package/dist/{te-kb-XHVBWP5E.js → te-kb-2Z2CARN3.js} +7 -5
- package/dist/{te-meta-NQ4U3VJW.js → te-meta-PDKLMWXN.js} +6 -4
- package/dist/{te-team-556APRIU.js → te-team-SW5B7DHO.js} +17 -12
- package/package.json +6 -2
- package/skills/ae-agent/SKILL.md +2 -1
- package/skills/ae-analysis/SKILL.md +1 -1
- package/skills/ae-analysis/references/list_clusters.md +4 -2
- package/skills/ae-analysis-global/SKILL.md +51 -0
- package/skills/ae-analysis-global/references/list_query_clusters.md +67 -0
- package/skills/ae-community/SKILL.md +2 -3
- package/skills/ae-dataops/SKILL.md +1 -2
- package/skills/ae-engage/SKILL.md +1 -0
- package/skills/ae-engage/references/add-approver.md +0 -1
- package/skills/ae-engage/references/add-channel.md +0 -1
- package/skills/ae-engage/references/approver-list.md +0 -1
- package/skills/ae-engage/references/cancel-query-by-request-id.md +0 -1
- package/skills/ae-engage/references/channel-detail.md +0 -1
- package/skills/ae-engage/references/channel-list.md +0 -1
- package/skills/ae-engage/references/config-channel-detail.md +0 -1
- package/skills/ae-engage/references/config-channel-list.md +0 -1
- package/skills/ae-engage/references/config-item-analysis-report.md +0 -1
- package/skills/ae-engage/references/config-item-detail.md +0 -1
- package/skills/ae-engage/references/config-item-list.md +0 -1
- package/skills/ae-engage/references/config-item-strategy-comparison.md +0 -1
- package/skills/ae-engage/references/config-item-trigger-report.md +0 -1
- package/skills/ae-engage/references/copy-config-template.md +0 -1
- package/skills/ae-engage/references/delete-channel.md +0 -1
- package/skills/ae-engage/references/delete-config-channel.md +0 -1
- package/skills/ae-engage/references/delete-config-item.md +0 -1
- package/skills/ae-engage/references/delete-flow.md +0 -1
- package/skills/ae-engage/references/flow-ab-split-node-report.md +0 -1
- package/skills/ae-engage/references/flow-detail.md +0 -1
- package/skills/ae-engage/references/flow-list.md +0 -1
- package/skills/ae-engage/references/flow-node-config-schema.md +0 -1
- package/skills/ae-engage/references/flow-node-detail-report.md +0 -1
- package/skills/ae-engage/references/flow-node-overview-report.md +0 -1
- package/skills/ae-engage/references/flow-process-report.md +0 -1
- package/skills/ae-engage/references/manage-flow.md +0 -1
- package/skills/ae-engage/references/manage-strategy.md +0 -1
- package/skills/ae-engage/references/manage-task.md +0 -1
- package/skills/ae-engage/references/modify-flow-base-info.md +0 -1
- package/skills/ae-engage/references/strategy-detail.md +0 -1
- package/skills/ae-engage/references/strategy-list.md +0 -1
- package/skills/ae-engage/references/task-data-detail.md +0 -1
- package/skills/ae-engage/references/task-data-overview.md +0 -1
- package/skills/ae-engage/references/task-detail.md +0 -1
- package/skills/ae-engage/references/task-experiment-report.md +0 -1
- package/skills/ae-engage/references/task-list.md +0 -1
- package/skills/ae-engage/references/task-metric-detail.md +0 -1
- package/skills/ae-engage/references/task-stats.md +0 -1
- package/skills/ae-engage/references/update-channel-status.md +0 -1
- package/skills/ae-engage/references/update-config-channel-status.md +0 -1
- package/skills/ae-engage/references/validate-flow-node-config.md +0 -1
- package/skills/ae-engage/references/whitelist-list.md +0 -1
- package/skills/ae-kb/SKILL.md +1 -0
- package/skills/ae-team/SKILL.md +1 -1
- package/dist/auth-W2JZ3DKA.js +0 -170
- package/dist/chunk-BI5ZVD6T.js +0 -186
- package/dist/chunk-MNHE6SSI.js +0 -79
- package/dist/chunk-MWWYGZ76.js +0 -130
- package/dist/chunk-SRRUNQIM.js +0 -312
- package/dist/config-WH3R3IRY.js +0 -258
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/core/multiselect.ts
|
|
2
2
|
var MultiselectCancelled = class extends Error {
|
|
3
3
|
constructor() {
|
|
4
|
-
super("
|
|
4
|
+
super("Selection cancelled by user");
|
|
5
5
|
this.name = "MultiselectCancelled";
|
|
6
6
|
}
|
|
7
7
|
};
|
|
@@ -27,7 +27,7 @@ function promptMultiselect(opts) {
|
|
|
27
27
|
const stdin = process.stdin;
|
|
28
28
|
if (!stdin.isTTY) {
|
|
29
29
|
return Promise.reject(
|
|
30
|
-
new Error("ae-cli sync
|
|
30
|
+
new Error("ae-cli sync requires a TTY for multi-select; please run in an interactive terminal")
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
const rows = buildRows(items);
|
|
@@ -48,7 +48,7 @@ function promptMultiselect(opts) {
|
|
|
48
48
|
if (renderCount > 0) {
|
|
49
49
|
stderr.write(`\x1B[${totalLines()}A`);
|
|
50
50
|
}
|
|
51
|
-
stderr.write(`${title} (space
|
|
51
|
+
stderr.write(`${title} (space: select \xB7 a: select all/none \xB7 enter: confirm \xB7 q: cancel)\x1B[K
|
|
52
52
|
`);
|
|
53
53
|
stderr.write(`\x1B[K
|
|
54
54
|
`);
|
|
@@ -128,10 +128,10 @@ function promptMultiselect(opts) {
|
|
|
128
128
|
}
|
|
129
129
|
function promptSingleCheckboxSelect(opts) {
|
|
130
130
|
const { title, items } = opts;
|
|
131
|
-
if (items.length === 0) return Promise.reject(new Error("
|
|
131
|
+
if (items.length === 0) return Promise.reject(new Error("No items to select"));
|
|
132
132
|
const stderr = process.stderr;
|
|
133
133
|
const stdin = process.stdin;
|
|
134
|
-
if (!stdin.isTTY) return Promise.reject(new Error("
|
|
134
|
+
if (!stdin.isTTY) return Promise.reject(new Error("A TTY is required for single-select"));
|
|
135
135
|
const preselectedIndex = items.findIndex((item) => item.preselected);
|
|
136
136
|
let cursor = preselectedIndex >= 0 ? preselectedIndex : 0;
|
|
137
137
|
let selected = preselectedIndex >= 0 ? preselectedIndex : null;
|
|
@@ -139,7 +139,7 @@ function promptSingleCheckboxSelect(opts) {
|
|
|
139
139
|
const lines = items.length + 2;
|
|
140
140
|
function render() {
|
|
141
141
|
if (renderCount > 0) stderr.write(`\x1B[${lines}A`);
|
|
142
|
-
stderr.write(`${title} (space
|
|
142
|
+
stderr.write(`${title} (space: select \xB7 enter: confirm \xB7 q: cancel)\x1B[K
|
|
143
143
|
`);
|
|
144
144
|
stderr.write(`\x1B[K
|
|
145
145
|
`);
|
|
@@ -193,10 +193,10 @@ function promptSingleCheckboxSelect(opts) {
|
|
|
193
193
|
}
|
|
194
194
|
function promptSingleSelect(opts) {
|
|
195
195
|
const { title, items } = opts;
|
|
196
|
-
if (items.length === 0) return Promise.reject(new Error("
|
|
196
|
+
if (items.length === 0) return Promise.reject(new Error("No items to select"));
|
|
197
197
|
const stderr = process.stderr;
|
|
198
198
|
const stdin = process.stdin;
|
|
199
|
-
if (!stdin.isTTY) return Promise.reject(new Error("
|
|
199
|
+
if (!stdin.isTTY) return Promise.reject(new Error("A TTY is required for single-select"));
|
|
200
200
|
let cursor = 0;
|
|
201
201
|
let renderCount = 0;
|
|
202
202
|
const lines = items.length + 1;
|
|
@@ -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
|
};
|
|
@@ -1,20 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAnalysisMappingPathForClusterMode
|
|
3
|
+
} from "./chunk-GSJGPNKK.js";
|
|
4
|
+
import {
|
|
5
|
+
PermissionError
|
|
6
|
+
} from "./chunk-NOO24N7W.js";
|
|
1
7
|
import {
|
|
2
8
|
getToken
|
|
3
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-U6TKB3IV.js";
|
|
4
10
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
getConfigDir,
|
|
8
|
-
safeJsonParse,
|
|
9
|
-
safeReadJsonFile
|
|
10
|
-
} from "./chunk-BI5ZVD6T.js";
|
|
11
|
+
loadMcpToken
|
|
12
|
+
} from "./chunk-JM34JPCO.js";
|
|
11
13
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
getActiveHost,
|
|
15
|
+
getFallbackMcpToken,
|
|
16
|
+
logger,
|
|
17
|
+
safeJsonParse
|
|
18
|
+
} from "./chunk-SRJIAOBN.js";
|
|
14
19
|
|
|
15
20
|
// src/core/mcp.ts
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
}
|
|
18
31
|
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
19
32
|
var JSONRPC_VERSION = "2.0";
|
|
20
33
|
var serviceMappingMap = /* @__PURE__ */ new Map();
|
|
@@ -39,6 +52,9 @@ function getMcpMapping(serviceName) {
|
|
|
39
52
|
if (!mapping) {
|
|
40
53
|
throw new Error(`MCP mapping not found for service: '${serviceName}'. Please register it using registerMcpMapping() first.`);
|
|
41
54
|
}
|
|
55
|
+
if (serviceName === "analysis") {
|
|
56
|
+
return { ...mapping, mappingPath: getAnalysisMappingPathForClusterMode() };
|
|
57
|
+
}
|
|
42
58
|
return mapping;
|
|
43
59
|
}
|
|
44
60
|
function buildMcpUrl(host, serviceName) {
|
|
@@ -56,22 +72,8 @@ var requestIdCounter = 1;
|
|
|
56
72
|
function genRequestId() {
|
|
57
73
|
return requestIdCounter++;
|
|
58
74
|
}
|
|
59
|
-
var MCP_TOKEN_FILE = path.join(getConfigDir(), "mcp-tokens.json");
|
|
60
75
|
var MCP_TOKEN_GENERATE_PATH = "/v1/ta/mcp/token/generate";
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
if (fs.existsSync(MCP_TOKEN_FILE)) {
|
|
64
|
-
return safeReadJsonFile(MCP_TOKEN_FILE);
|
|
65
|
-
}
|
|
66
|
-
} catch {
|
|
67
|
-
}
|
|
68
|
-
return {};
|
|
69
|
-
}
|
|
70
|
-
function saveMcpTokenStore(store) {
|
|
71
|
-
const dir = getConfigDir();
|
|
72
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
73
|
-
fs.writeFileSync(MCP_TOKEN_FILE, JSON.stringify(store, null, 2));
|
|
74
|
-
}
|
|
76
|
+
var _mcpTokenCache = /* @__PURE__ */ new Map();
|
|
75
77
|
async function generateMcpToken(hostUrl) {
|
|
76
78
|
const teToken = await getToken(hostUrl);
|
|
77
79
|
const base = hostUrl.replace(/\/+$/, "");
|
|
@@ -97,33 +99,38 @@ async function generateMcpToken(hostUrl) {
|
|
|
97
99
|
}
|
|
98
100
|
async function getMcpToken(hostOverride) {
|
|
99
101
|
const hostUrl = hostOverride || getActiveHost();
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
return
|
|
102
|
+
const cached = _mcpTokenCache.get(hostUrl);
|
|
103
|
+
if (cached) {
|
|
104
|
+
return cached;
|
|
105
|
+
}
|
|
106
|
+
const storedToken = loadMcpToken(hostUrl);
|
|
107
|
+
if (storedToken) {
|
|
108
|
+
_mcpTokenCache.set(hostUrl, storedToken);
|
|
109
|
+
logger.info(`Using persisted MCP token (secure-store) for ${hostUrl}`);
|
|
110
|
+
return storedToken;
|
|
111
|
+
}
|
|
112
|
+
const fallbackToken = getFallbackMcpToken(hostUrl);
|
|
113
|
+
if (fallbackToken) {
|
|
114
|
+
_mcpTokenCache.set(hostUrl, fallbackToken);
|
|
115
|
+
logger.info(`Using sandbox-provisioned MCP token (fallback file) for ${hostUrl}`);
|
|
116
|
+
return fallbackToken;
|
|
103
117
|
}
|
|
104
118
|
logger.info(`Generating MCP token for ${hostUrl}`);
|
|
105
119
|
const mcpToken = await generateMcpToken(hostUrl);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
logger.info(`MCP token generated and cached for ${hostUrl}`);
|
|
120
|
+
_mcpTokenCache.set(hostUrl, mcpToken);
|
|
121
|
+
logger.info(`MCP token generated and in-process cached for ${hostUrl}`);
|
|
109
122
|
return mcpToken;
|
|
110
123
|
}
|
|
111
124
|
function clearMcpToken(hostUrl) {
|
|
112
|
-
const store = loadMcpTokenStore();
|
|
113
125
|
if (hostUrl) {
|
|
114
|
-
delete
|
|
126
|
+
_mcpTokenCache.delete(hostUrl);
|
|
115
127
|
} else {
|
|
116
|
-
|
|
117
|
-
delete store[key];
|
|
118
|
-
}
|
|
128
|
+
_mcpTokenCache.clear();
|
|
119
129
|
}
|
|
120
|
-
saveMcpTokenStore(store);
|
|
121
130
|
}
|
|
122
131
|
function setMcpTokenManual(token, hostUrl) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
saveMcpTokenStore(store);
|
|
126
|
-
logger.info(`MCP token manually set for ${hostUrl}`);
|
|
132
|
+
_mcpTokenCache.set(hostUrl, token);
|
|
133
|
+
logger.info(`MCP token manually set (in-process) for ${hostUrl}`);
|
|
127
134
|
}
|
|
128
135
|
async function validateMcpToken(token, hostUrl) {
|
|
129
136
|
const mapping = getMcpMapping("analysis");
|
|
@@ -186,18 +193,27 @@ async function mcpRequest(url, method, params = {}, hostOverride) {
|
|
|
186
193
|
body: JSON.stringify(body)
|
|
187
194
|
});
|
|
188
195
|
if (!resp.ok) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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`);
|
|
201
|
+
_mcpTokenCache.delete(hostUrl);
|
|
202
|
+
const fallbackToken = getFallbackMcpToken(hostUrl);
|
|
203
|
+
const newToken = fallbackToken ?? await generateMcpToken(hostUrl);
|
|
204
|
+
_mcpTokenCache.set(hostUrl, newToken);
|
|
205
|
+
logger.info(`MCP token re-minted for ${hostUrl}`);
|
|
206
|
+
process.stderr.write(`[ae-cli] MCP token re-minted for ${hostUrl}
|
|
194
207
|
`);
|
|
195
|
-
headers["mcp-token"] =
|
|
208
|
+
headers["mcp-token"] = newToken;
|
|
196
209
|
const retryResp = await fetch(url, {
|
|
197
210
|
method: "POST",
|
|
198
211
|
headers,
|
|
199
212
|
body: JSON.stringify(body)
|
|
200
213
|
});
|
|
214
|
+
if (retryResp.status === 403) {
|
|
215
|
+
throw new PermissionError(await permissionMessage(retryResp));
|
|
216
|
+
}
|
|
201
217
|
if (!retryResp.ok) {
|
|
202
218
|
throw new Error(`MCP HTTP error: ${retryResp.status} ${retryResp.statusText}`);
|
|
203
219
|
}
|
|
@@ -241,19 +257,41 @@ async function callMcpTool(url, toolName, args = {}, hostOverride) {
|
|
|
241
257
|
function parseMcpResult(result) {
|
|
242
258
|
if (result.content.length > 0 && result.content[0].type === "text") {
|
|
243
259
|
const text = result.content[0].text || "";
|
|
260
|
+
let parsed;
|
|
244
261
|
try {
|
|
245
|
-
|
|
262
|
+
parsed = safeJsonParse(text);
|
|
246
263
|
} catch {
|
|
247
264
|
return text;
|
|
248
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;
|
|
249
274
|
}
|
|
250
275
|
return result;
|
|
251
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
|
+
}
|
|
252
290
|
|
|
253
291
|
export {
|
|
254
292
|
registerMcpMappings,
|
|
255
293
|
resolveMcpUrl,
|
|
256
|
-
|
|
294
|
+
getMcpToken,
|
|
257
295
|
clearMcpToken,
|
|
258
296
|
setMcpTokenManual,
|
|
259
297
|
validateMcpToken,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSandboxClusterInfoFilePath,
|
|
3
|
+
logger,
|
|
4
|
+
safeReadJsonFile
|
|
5
|
+
} from "./chunk-SRJIAOBN.js";
|
|
6
|
+
|
|
7
|
+
// src/core/cluster-info.ts
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
var GLOBAL_QUERY_CONFIG_KEY = "sw_cfg_enable_global_query";
|
|
11
|
+
var CLUSTER_INFO_FILE_ENV = "AE_CLUSTER_INFO_FILE";
|
|
12
|
+
function getClusterInfoFilePath() {
|
|
13
|
+
return process.env[CLUSTER_INFO_FILE_ENV] || getSandboxClusterInfoFilePath();
|
|
14
|
+
}
|
|
15
|
+
function loadClusterInfo() {
|
|
16
|
+
const file = getClusterInfoFilePath();
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(file)) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
const raw = safeReadJsonFile(file);
|
|
22
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
23
|
+
} catch (err) {
|
|
24
|
+
logger.warn(`Failed to load cluster info from ${file}: ${err.message}`);
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isGlobalQueryModeEnabled() {
|
|
29
|
+
return loadClusterInfo()[GLOBAL_QUERY_CONFIG_KEY] === true;
|
|
30
|
+
}
|
|
31
|
+
function setGlobalQueryModeEnabled(enabled) {
|
|
32
|
+
const file = getClusterInfoFilePath();
|
|
33
|
+
const dir = path.dirname(file);
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
35
|
+
const next = { [GLOBAL_QUERY_CONFIG_KEY]: enabled };
|
|
36
|
+
fs.writeFileSync(file, JSON.stringify(next, null, 2));
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
function getAnalysisMappingPathForClusterMode(enabled = isGlobalQueryModeEnabled()) {
|
|
40
|
+
return enabled ? "analysis-global" : "analysis";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
GLOBAL_QUERY_CONFIG_KEY,
|
|
45
|
+
getClusterInfoFilePath,
|
|
46
|
+
isGlobalQueryModeEnabled,
|
|
47
|
+
setGlobalQueryModeEnabled,
|
|
48
|
+
getAnalysisMappingPathForClusterMode
|
|
49
|
+
};
|
|
@@ -2,13 +2,14 @@ import {
|
|
|
2
2
|
clearToken,
|
|
3
3
|
getToken,
|
|
4
4
|
resolveHost
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-U6TKB3IV.js";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
SecureStoreAuthError
|
|
8
|
+
} from "./chunk-JM34JPCO.js";
|
|
9
9
|
import {
|
|
10
|
-
logger
|
|
11
|
-
|
|
10
|
+
logger,
|
|
11
|
+
safeJsonParse
|
|
12
|
+
} from "./chunk-SRJIAOBN.js";
|
|
12
13
|
|
|
13
14
|
// src/core/client.ts
|
|
14
15
|
import WebSocket from "ws";
|
|
@@ -45,9 +46,16 @@ async function request(method, modulePath, params = {}, body = null, retry = tru
|
|
|
45
46
|
const text = await resp.text();
|
|
46
47
|
const data = safeJsonParse(text);
|
|
47
48
|
logger.api(method, url, resp.status, body, data);
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
if (resp.status === 401) {
|
|
50
|
+
if (retry) {
|
|
51
|
+
clearToken(resolvedHost);
|
|
52
|
+
return request(method, modulePath, params, body, false, resolvedHost);
|
|
53
|
+
}
|
|
54
|
+
throw new SecureStoreAuthError(`Session expired or unauthorized for ${resolvedHost}. Please run: ae-cli auth login`);
|
|
55
|
+
}
|
|
56
|
+
if (resp.status === 403) {
|
|
57
|
+
const m = data && typeof data === "object" && typeof data.error === "string" ? data.error : "Permission denied";
|
|
58
|
+
throw new Error(m);
|
|
51
59
|
}
|
|
52
60
|
if (data.return_code === -1001 && retry) {
|
|
53
61
|
clearToken(resolvedHost);
|
|
@@ -81,9 +89,16 @@ async function uploadRequest(modulePath, form, params = {}, retry = true, hostUr
|
|
|
81
89
|
const text = await resp.text();
|
|
82
90
|
const data = safeJsonParse(text);
|
|
83
91
|
logger.api("POST", url, resp.status, { upload: true }, data);
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
if (resp.status === 401) {
|
|
93
|
+
if (retry) {
|
|
94
|
+
clearToken(resolvedHost);
|
|
95
|
+
return uploadRequest(modulePath, form, params, false, resolvedHost);
|
|
96
|
+
}
|
|
97
|
+
throw new SecureStoreAuthError(`Session expired or unauthorized for ${resolvedHost}. Please run: ae-cli auth login`);
|
|
98
|
+
}
|
|
99
|
+
if (resp.status === 403) {
|
|
100
|
+
const m = data && typeof data === "object" && typeof data.error === "string" ? data.error : "Permission denied";
|
|
101
|
+
throw new Error(m);
|
|
87
102
|
}
|
|
88
103
|
if (data.return_code === -1001 && retry) {
|
|
89
104
|
clearToken(resolvedHost);
|