@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.
Files changed (90) hide show
  1. package/README.md +1 -16
  2. package/README.zh.md +1 -16
  3. package/dist/auth-H3GJD55A.js +457 -0
  4. package/dist/{auth-TXFJHPXU.js → auth-U25QJU5D.js} +3 -3
  5. package/dist/{chunk-B4UL2VIJ.js → chunk-CLJF7MQA.js} +1 -1
  6. package/dist/{chunk-6T7245YE.js → chunk-EGEIXA2Z.js} +90 -18
  7. package/dist/{chunk-2NGWYMLB.js → chunk-GLNZSDKO.js} +88 -50
  8. package/dist/chunk-GSJGPNKK.js +49 -0
  9. package/dist/{chunk-VT6VC4PA.js → chunk-I42JGQ2J.js} +26 -11
  10. package/dist/chunk-JM34JPCO.js +239 -0
  11. package/dist/chunk-NOO24N7W.js +11 -0
  12. package/dist/chunk-SF3KTPIC.js +98 -0
  13. package/dist/chunk-SRJIAOBN.js +244 -0
  14. package/dist/chunk-U6TKB3IV.js +171 -0
  15. package/dist/{chunk-TG64AQV4.js → chunk-VJFUB3H3.js} +99 -56
  16. package/dist/{client-IU2E5IOL.js → client-IN42F223.js} +4 -4
  17. package/dist/config-64G36TJL.js +130 -0
  18. package/dist/index.js +52 -25
  19. package/dist/{model-CC7U5XOJ.js → model-VZCYNIOG.js} +11 -10
  20. package/dist/{raw-TUHZQ4UB.js → raw-WJIWC6YP.js} +13 -7
  21. package/dist/{sync-EVEDS7YE.js → sync-NVASAISS.js} +146 -47
  22. package/dist/{te-agent-FLTDYV3L.js → te-agent-YV4DXEXK.js} +24 -23
  23. package/dist/{te-analysis-CMXADIJM.js → te-analysis-CB3DA5I5.js} +61 -13
  24. package/dist/{te-audience-LPZFFSXM.js → te-audience-W7SCQFC7.js} +7 -5
  25. package/dist/{te-common-BXQFKTB3.js → te-common-FBXVWBSI.js} +21 -9
  26. package/dist/{te-community-K5S3VOZY.js → te-community-LNVDZSGW.js} +6 -4
  27. package/dist/{te-dataops-567IMCQG.js → te-dataops-IPCSSWYH.js} +29 -27
  28. package/dist/{te-engage-TBUTGW5F.js → te-engage-KARARMIR.js} +6 -4
  29. package/dist/{te-kb-XHVBWP5E.js → te-kb-2Z2CARN3.js} +7 -5
  30. package/dist/{te-meta-NQ4U3VJW.js → te-meta-PDKLMWXN.js} +6 -4
  31. package/dist/{te-team-556APRIU.js → te-team-SW5B7DHO.js} +17 -12
  32. package/package.json +6 -2
  33. package/skills/ae-agent/SKILL.md +2 -1
  34. package/skills/ae-analysis/SKILL.md +1 -1
  35. package/skills/ae-analysis/references/list_clusters.md +4 -2
  36. package/skills/ae-analysis-global/SKILL.md +51 -0
  37. package/skills/ae-analysis-global/references/list_query_clusters.md +67 -0
  38. package/skills/ae-community/SKILL.md +2 -3
  39. package/skills/ae-dataops/SKILL.md +1 -2
  40. package/skills/ae-engage/SKILL.md +1 -0
  41. package/skills/ae-engage/references/add-approver.md +0 -1
  42. package/skills/ae-engage/references/add-channel.md +0 -1
  43. package/skills/ae-engage/references/approver-list.md +0 -1
  44. package/skills/ae-engage/references/cancel-query-by-request-id.md +0 -1
  45. package/skills/ae-engage/references/channel-detail.md +0 -1
  46. package/skills/ae-engage/references/channel-list.md +0 -1
  47. package/skills/ae-engage/references/config-channel-detail.md +0 -1
  48. package/skills/ae-engage/references/config-channel-list.md +0 -1
  49. package/skills/ae-engage/references/config-item-analysis-report.md +0 -1
  50. package/skills/ae-engage/references/config-item-detail.md +0 -1
  51. package/skills/ae-engage/references/config-item-list.md +0 -1
  52. package/skills/ae-engage/references/config-item-strategy-comparison.md +0 -1
  53. package/skills/ae-engage/references/config-item-trigger-report.md +0 -1
  54. package/skills/ae-engage/references/copy-config-template.md +0 -1
  55. package/skills/ae-engage/references/delete-channel.md +0 -1
  56. package/skills/ae-engage/references/delete-config-channel.md +0 -1
  57. package/skills/ae-engage/references/delete-config-item.md +0 -1
  58. package/skills/ae-engage/references/delete-flow.md +0 -1
  59. package/skills/ae-engage/references/flow-ab-split-node-report.md +0 -1
  60. package/skills/ae-engage/references/flow-detail.md +0 -1
  61. package/skills/ae-engage/references/flow-list.md +0 -1
  62. package/skills/ae-engage/references/flow-node-config-schema.md +0 -1
  63. package/skills/ae-engage/references/flow-node-detail-report.md +0 -1
  64. package/skills/ae-engage/references/flow-node-overview-report.md +0 -1
  65. package/skills/ae-engage/references/flow-process-report.md +0 -1
  66. package/skills/ae-engage/references/manage-flow.md +0 -1
  67. package/skills/ae-engage/references/manage-strategy.md +0 -1
  68. package/skills/ae-engage/references/manage-task.md +0 -1
  69. package/skills/ae-engage/references/modify-flow-base-info.md +0 -1
  70. package/skills/ae-engage/references/strategy-detail.md +0 -1
  71. package/skills/ae-engage/references/strategy-list.md +0 -1
  72. package/skills/ae-engage/references/task-data-detail.md +0 -1
  73. package/skills/ae-engage/references/task-data-overview.md +0 -1
  74. package/skills/ae-engage/references/task-detail.md +0 -1
  75. package/skills/ae-engage/references/task-experiment-report.md +0 -1
  76. package/skills/ae-engage/references/task-list.md +0 -1
  77. package/skills/ae-engage/references/task-metric-detail.md +0 -1
  78. package/skills/ae-engage/references/task-stats.md +0 -1
  79. package/skills/ae-engage/references/update-channel-status.md +0 -1
  80. package/skills/ae-engage/references/update-config-channel-status.md +0 -1
  81. package/skills/ae-engage/references/validate-flow-node-config.md +0 -1
  82. package/skills/ae-engage/references/whitelist-list.md +0 -1
  83. package/skills/ae-kb/SKILL.md +1 -0
  84. package/skills/ae-team/SKILL.md +1 -1
  85. package/dist/auth-W2JZ3DKA.js +0 -170
  86. package/dist/chunk-BI5ZVD6T.js +0 -186
  87. package/dist/chunk-MNHE6SSI.js +0 -79
  88. package/dist/chunk-MWWYGZ76.js +0 -130
  89. package/dist/chunk-SRRUNQIM.js +0 -312
  90. 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("\u7528\u6237\u53D6\u6D88\u9009\u62E9");
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 \u9700\u8981 TTY \u624D\u80FD\u8FDB\u884C\u591A\u9009\uFF1B\u8BF7\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u4E2D\u8FD0\u884C")
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 \u9009\u62E9 \xB7 a \u5168\u9009/\u5168\u4E0D\u9009 \xB7 enter \u786E\u8BA4 \xB7 q \u53D6\u6D88)\x1B[K
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("\u65E0\u53EF\u9009\u9879"));
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("\u9700\u8981 TTY \u624D\u80FD\u8FDB\u884C\u5355\u9009"));
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 \u9009\u62E9 \xB7 enter \u786E\u8BA4 \xB7 q \u53D6\u6D88)\x1B[K
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("\u65E0\u53EF\u9009\u9879"));
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("\u9700\u8981 TTY \u624D\u80FD\u8FDB\u884C\u5355\u9009"));
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 = { filterScope: true }) {
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.filterScope && (raw._scope === "system" || raw._scope === "company")) continue;
402
- const transport = raw.type === "http" || raw.transport === "http" || typeof raw.url === "string" ? "http" : "stdio";
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
- out.push(...parseMcpServers(scopedMcpJson?.mcpServers, "workspace", workspace.name, { filterScope: true }));
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(projectMcpServers(workspaceClaudeJson, workspace.dir), "workspace", workspace.name, {
451
- filterScope: false
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(projectMcpServers(globalClaudeJson, workspace.dir), "global", void 0, {
457
- filterScope: false
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-SRRUNQIM.js";
9
+ } from "./chunk-U6TKB3IV.js";
4
10
  import {
5
- forceMigrateFromFallback,
6
- getActiveHost,
7
- getConfigDir,
8
- safeJsonParse,
9
- safeReadJsonFile
10
- } from "./chunk-BI5ZVD6T.js";
11
+ loadMcpToken
12
+ } from "./chunk-JM34JPCO.js";
11
13
  import {
12
- logger
13
- } from "./chunk-MNHE6SSI.js";
14
+ getActiveHost,
15
+ getFallbackMcpToken,
16
+ logger,
17
+ safeJsonParse
18
+ } from "./chunk-SRJIAOBN.js";
14
19
 
15
20
  // src/core/mcp.ts
16
- import fs from "fs";
17
- import path from "path";
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
- function loadMcpTokenStore() {
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 store = loadMcpTokenStore();
101
- if (store[hostUrl]) {
102
- return store[hostUrl];
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
- const updatedStore = { ...store, [hostUrl]: mcpToken };
107
- saveMcpTokenStore(updatedStore);
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 store[hostUrl];
126
+ _mcpTokenCache.delete(hostUrl);
115
127
  } else {
116
- for (const key of Object.keys(store)) {
117
- delete store[key];
118
- }
128
+ _mcpTokenCache.clear();
119
129
  }
120
- saveMcpTokenStore(store);
121
130
  }
122
131
  function setMcpTokenManual(token, hostUrl) {
123
- const store = loadMcpTokenStore();
124
- store[hostUrl] = token;
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
- logger.warn(`MCP request failed (HTTP ${resp.status}) for ${hostUrl}, trying fallback`);
190
- const migrated = forceMigrateFromFallback();
191
- if (migrated && migrated[hostUrl]) {
192
- logger.info(`MCP token refreshed from fallback for ${hostUrl}`);
193
- process.stderr.write(`[ae-cli] MCP token refreshed from fallback for ${hostUrl}
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"] = migrated[hostUrl];
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
- return safeJsonParse(text);
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
- loadMcpTokenStore,
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-SRRUNQIM.js";
5
+ } from "./chunk-U6TKB3IV.js";
6
6
  import {
7
- safeJsonParse
8
- } from "./chunk-BI5ZVD6T.js";
7
+ SecureStoreAuthError
8
+ } from "./chunk-JM34JPCO.js";
9
9
  import {
10
- logger
11
- } from "./chunk-MNHE6SSI.js";
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 ((resp.status === 401 || resp.status === 403) && retry) {
49
- clearToken(resolvedHost);
50
- return request(method, modulePath, params, body, false, resolvedHost);
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 ((resp.status === 401 || resp.status === 403) && retry) {
85
- clearToken(resolvedHost);
86
- return uploadRequest(modulePath, form, params, false, resolvedHost);
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);