@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.
Files changed (31) hide show
  1. package/README.md +1 -16
  2. package/README.zh.md +1 -16
  3. package/dist/{auth-5NSDQFQB.js → auth-H3GJD55A.js} +8 -5
  4. package/dist/{auth-H2376DEF.js → auth-U25QJU5D.js} +2 -1
  5. package/dist/{chunk-IMBKVXKY.js → chunk-EGEIXA2Z.js} +82 -10
  6. package/dist/{chunk-WCHI7725.js → chunk-GLNZSDKO.js} +48 -5
  7. package/dist/{chunk-LU7SXK4Q.js → chunk-I42JGQ2J.js} +4 -2
  8. package/dist/{chunk-CAQYQA4R.js → chunk-JM34JPCO.js} +3 -163
  9. package/dist/chunk-NOO24N7W.js +11 -0
  10. package/dist/chunk-SF3KTPIC.js +98 -0
  11. package/dist/chunk-U6TKB3IV.js +171 -0
  12. package/dist/{chunk-AD5V3ZPJ.js → chunk-VJFUB3H3.js} +97 -9
  13. package/dist/{client-FCMK3XEK.js → client-IN42F223.js} +3 -2
  14. package/dist/index.js +42 -20
  15. package/dist/{model-EZ6K7FXI.js → model-VZCYNIOG.js} +4 -6
  16. package/dist/{raw-VEF3D6UD.js → raw-WJIWC6YP.js} +5 -3
  17. package/dist/{sync-NRODEVNH.js → sync-NVASAISS.js} +118 -21
  18. package/dist/{te-agent-7U7JLJKU.js → te-agent-YV4DXEXK.js} +1 -2
  19. package/dist/{te-analysis-A2OSGHQ4.js → te-analysis-CB3DA5I5.js} +4 -2
  20. package/dist/{te-audience-LI67PPGO.js → te-audience-W7SCQFC7.js} +4 -2
  21. package/dist/{te-common-35WVU3C5.js → te-common-FBXVWBSI.js} +4 -2
  22. package/dist/{te-community-LEAP6PWT.js → te-community-LNVDZSGW.js} +4 -2
  23. package/dist/{te-dataops-N2LGZBOX.js → te-dataops-IPCSSWYH.js} +27 -25
  24. package/dist/{te-engage-H7C72Z46.js → te-engage-KARARMIR.js} +4 -2
  25. package/dist/{te-kb-I2OKTBAI.js → te-kb-2Z2CARN3.js} +5 -3
  26. package/dist/{te-meta-HBTIEDRO.js → te-meta-PDKLMWXN.js} +4 -2
  27. package/dist/{te-team-EM5OWRQL.js → te-team-SW5B7DHO.js} +14 -9
  28. package/package.json +1 -1
  29. package/skills/ae-agent/SKILL.md +1 -1
  30. package/dist/chunk-KAEZTSXN.js +0 -99
  31. 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
- # First run — interactive host setup + auto-login
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-WCHI7725.js";
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-CAQYQA4R.js";
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,
@@ -7,7 +7,8 @@ import {
7
7
  saveToken,
8
8
  setTokenManual,
9
9
  validateToken
10
- } from "./chunk-CAQYQA4R.js";
10
+ } from "./chunk-U6TKB3IV.js";
11
+ import "./chunk-JM34JPCO.js";
11
12
  import "./chunk-SRJIAOBN.js";
12
13
  export {
13
14
  clearToken,
@@ -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
  };
@@ -2,9 +2,14 @@ import {
2
2
  getAnalysisMappingPathForClusterMode
3
3
  } from "./chunk-GSJGPNKK.js";
4
4
  import {
5
- getToken,
5
+ PermissionError
6
+ } from "./chunk-NOO24N7W.js";
7
+ import {
8
+ getToken
9
+ } from "./chunk-U6TKB3IV.js";
10
+ import {
6
11
  loadMcpToken
7
- } from "./chunk-CAQYQA4R.js";
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
- logger.warn(`MCP request failed (HTTP ${resp.status}) for ${hostUrl}, re-minting token`);
182
- if (resp.status === 401 || resp.status === 403) {
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
- return safeJsonParse(text);
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,9 +1,11 @@
1
1
  import {
2
- SecureStoreAuthError,
3
2
  clearToken,
4
3
  getToken,
5
4
  resolveHost
6
- } from "./chunk-CAQYQA4R.js";
5
+ } from "./chunk-U6TKB3IV.js";
6
+ import {
7
+ SecureStoreAuthError
8
+ } from "./chunk-JM34JPCO.js";
7
9
  import {
8
10
  logger,
9
11
  safeJsonParse
@@ -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,11 @@
1
+ // src/core/errors.ts
2
+ var PermissionError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "PermissionError";
6
+ }
7
+ };
8
+
9
+ export {
10
+ PermissionError
11
+ };
@@ -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
+ };