@thinkingai/ae-cli 1.0.27 → 1.0.28

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 (88) hide show
  1. package/dist/auth-5NSDQFQB.js +454 -0
  2. package/dist/{auth-TXFJHPXU.js → auth-H2376DEF.js} +2 -3
  3. package/dist/chunk-AD5V3ZPJ.js +217 -0
  4. package/dist/chunk-CAQYQA4R.js +399 -0
  5. package/dist/{chunk-B4UL2VIJ.js → chunk-CLJF7MQA.js} +1 -1
  6. package/dist/chunk-GSJGPNKK.js +49 -0
  7. package/dist/{chunk-6T7245YE.js → chunk-IMBKVXKY.js} +8 -8
  8. package/dist/chunk-KAEZTSXN.js +99 -0
  9. package/dist/{chunk-VT6VC4PA.js → chunk-LU7SXK4Q.js} +24 -11
  10. package/dist/chunk-OJDNO5QY.js +63 -0
  11. package/dist/chunk-SRJIAOBN.js +244 -0
  12. package/dist/{chunk-2NGWYMLB.js → chunk-WCHI7725.js} +45 -50
  13. package/dist/{client-IU2E5IOL.js → client-FCMK3XEK.js} +3 -4
  14. package/dist/config-64G36TJL.js +130 -0
  15. package/dist/index.js +28 -23
  16. package/dist/{model-CC7U5XOJ.js → model-EZ6K7FXI.js} +15 -12
  17. package/dist/{raw-TUHZQ4UB.js → raw-VEF3D6UD.js} +11 -7
  18. package/dist/{sync-EVEDS7YE.js → sync-NRODEVNH.js} +36 -34
  19. package/dist/{te-agent-FLTDYV3L.js → te-agent-7U7JLJKU.js} +25 -23
  20. package/dist/{te-analysis-CMXADIJM.js → te-analysis-A2OSGHQ4.js} +59 -13
  21. package/dist/{te-audience-LPZFFSXM.js → te-audience-LI67PPGO.js} +5 -5
  22. package/dist/{te-common-BXQFKTB3.js → te-common-35WVU3C5.js} +19 -9
  23. package/dist/{te-community-K5S3VOZY.js → te-community-LEAP6PWT.js} +4 -4
  24. package/dist/{te-dataops-567IMCQG.js → te-dataops-N2LGZBOX.js} +4 -4
  25. package/dist/{te-engage-TBUTGW5F.js → te-engage-H7C72Z46.js} +4 -4
  26. package/dist/{te-kb-XHVBWP5E.js → te-kb-I2OKTBAI.js} +5 -5
  27. package/dist/{te-meta-NQ4U3VJW.js → te-meta-HBTIEDRO.js} +4 -4
  28. package/dist/{te-team-556APRIU.js → te-team-EM5OWRQL.js} +7 -7
  29. package/package.json +6 -2
  30. package/skills/ae-agent/SKILL.md +1 -0
  31. package/skills/ae-analysis/SKILL.md +1 -1
  32. package/skills/ae-analysis/references/list_clusters.md +4 -2
  33. package/skills/ae-analysis-global/SKILL.md +51 -0
  34. package/skills/ae-analysis-global/references/list_query_clusters.md +67 -0
  35. package/skills/ae-community/SKILL.md +2 -3
  36. package/skills/ae-dataops/SKILL.md +1 -2
  37. package/skills/ae-engage/SKILL.md +1 -0
  38. package/skills/ae-engage/references/add-approver.md +0 -1
  39. package/skills/ae-engage/references/add-channel.md +0 -1
  40. package/skills/ae-engage/references/approver-list.md +0 -1
  41. package/skills/ae-engage/references/cancel-query-by-request-id.md +0 -1
  42. package/skills/ae-engage/references/channel-detail.md +0 -1
  43. package/skills/ae-engage/references/channel-list.md +0 -1
  44. package/skills/ae-engage/references/config-channel-detail.md +0 -1
  45. package/skills/ae-engage/references/config-channel-list.md +0 -1
  46. package/skills/ae-engage/references/config-item-analysis-report.md +0 -1
  47. package/skills/ae-engage/references/config-item-detail.md +0 -1
  48. package/skills/ae-engage/references/config-item-list.md +0 -1
  49. package/skills/ae-engage/references/config-item-strategy-comparison.md +0 -1
  50. package/skills/ae-engage/references/config-item-trigger-report.md +0 -1
  51. package/skills/ae-engage/references/copy-config-template.md +0 -1
  52. package/skills/ae-engage/references/delete-channel.md +0 -1
  53. package/skills/ae-engage/references/delete-config-channel.md +0 -1
  54. package/skills/ae-engage/references/delete-config-item.md +0 -1
  55. package/skills/ae-engage/references/delete-flow.md +0 -1
  56. package/skills/ae-engage/references/flow-ab-split-node-report.md +0 -1
  57. package/skills/ae-engage/references/flow-detail.md +0 -1
  58. package/skills/ae-engage/references/flow-list.md +0 -1
  59. package/skills/ae-engage/references/flow-node-config-schema.md +0 -1
  60. package/skills/ae-engage/references/flow-node-detail-report.md +0 -1
  61. package/skills/ae-engage/references/flow-node-overview-report.md +0 -1
  62. package/skills/ae-engage/references/flow-process-report.md +0 -1
  63. package/skills/ae-engage/references/manage-flow.md +0 -1
  64. package/skills/ae-engage/references/manage-strategy.md +0 -1
  65. package/skills/ae-engage/references/manage-task.md +0 -1
  66. package/skills/ae-engage/references/modify-flow-base-info.md +0 -1
  67. package/skills/ae-engage/references/strategy-detail.md +0 -1
  68. package/skills/ae-engage/references/strategy-list.md +0 -1
  69. package/skills/ae-engage/references/task-data-detail.md +0 -1
  70. package/skills/ae-engage/references/task-data-overview.md +0 -1
  71. package/skills/ae-engage/references/task-detail.md +0 -1
  72. package/skills/ae-engage/references/task-experiment-report.md +0 -1
  73. package/skills/ae-engage/references/task-list.md +0 -1
  74. package/skills/ae-engage/references/task-metric-detail.md +0 -1
  75. package/skills/ae-engage/references/task-stats.md +0 -1
  76. package/skills/ae-engage/references/update-channel-status.md +0 -1
  77. package/skills/ae-engage/references/update-config-channel-status.md +0 -1
  78. package/skills/ae-engage/references/validate-flow-node-config.md +0 -1
  79. package/skills/ae-engage/references/whitelist-list.md +0 -1
  80. package/skills/ae-kb/SKILL.md +1 -0
  81. package/skills/ae-team/SKILL.md +1 -1
  82. package/dist/auth-W2JZ3DKA.js +0 -170
  83. package/dist/chunk-BI5ZVD6T.js +0 -186
  84. package/dist/chunk-MNHE6SSI.js +0 -79
  85. package/dist/chunk-MWWYGZ76.js +0 -130
  86. package/dist/chunk-SRRUNQIM.js +0 -312
  87. package/dist/chunk-TG64AQV4.js +0 -262
  88. package/dist/config-WH3R3IRY.js +0 -258
@@ -1,79 +0,0 @@
1
- // src/core/logger.ts
2
- import fs from "fs";
3
- import path from "path";
4
- var LOG_DIR = path.join(process.env.HOME || "", ".ae-cli", "log");
5
- var Logger = class {
6
- logDir;
7
- date;
8
- constructor() {
9
- this.logDir = LOG_DIR;
10
- this.date = this.today();
11
- this.ensureDir();
12
- }
13
- today() {
14
- const d = /* @__PURE__ */ new Date();
15
- return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
16
- }
17
- timestamp() {
18
- return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").split(".")[0];
19
- }
20
- ensureDir() {
21
- if (!fs.existsSync(this.logDir)) {
22
- fs.mkdirSync(this.logDir, { recursive: true });
23
- }
24
- }
25
- filePath(level) {
26
- const today = this.today();
27
- if (today !== this.date) {
28
- this.date = today;
29
- }
30
- return path.join(this.logDir, `${this.date}_${level}.log`);
31
- }
32
- /**
33
- * 写入日志到文件(不输出到控制台,控制台由现有 process.stderr.write 负责)
34
- */
35
- write(level, message) {
36
- const ts = this.timestamp();
37
- const line = `[${ts}] ${message}
38
- `;
39
- try {
40
- fs.appendFileSync(this.filePath(level), line);
41
- } catch {
42
- }
43
- }
44
- /** 常规操作日志 */
45
- info(message) {
46
- this.write("info", message);
47
- }
48
- /** 警告日志 */
49
- warn(message) {
50
- this.write("warning", message);
51
- }
52
- /** 错误日志 */
53
- error(message) {
54
- this.write("error", message);
55
- }
56
- /** 记录 HTTP API 请求详情 */
57
- api(method, url, status, reqBody, respBody) {
58
- const parts = [`API ${method} ${url} \u2192 HTTP ${status}`];
59
- if (reqBody !== void 0 && reqBody !== null) {
60
- const s = typeof reqBody === "string" ? reqBody : JSON.stringify(reqBody);
61
- parts.push(`REQ: ${s.slice(0, 500)}`);
62
- }
63
- if (respBody !== void 0 && respBody !== null) {
64
- const s = typeof respBody === "string" ? respBody : JSON.stringify(respBody);
65
- parts.push(`RESP: ${s.slice(0, 500)}`);
66
- }
67
- this.info(parts.join(" | "));
68
- }
69
- /** 记录命令执行 */
70
- command(name, args) {
71
- const filtered = Object.entries(args).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `--${k}=${JSON.stringify(v)}`).join(" ");
72
- this.info(`CMD ${name}${filtered ? " " + filtered : ""}`);
73
- }
74
- };
75
- var logger = new Logger();
76
-
77
- export {
78
- logger
79
- };
@@ -1,130 +0,0 @@
1
- import {
2
- loadMcpTokenStore
3
- } from "./chunk-2NGWYMLB.js";
4
- import {
5
- forceMigrateFromFallback,
6
- safeJsonParse
7
- } from "./chunk-BI5ZVD6T.js";
8
- import {
9
- logger
10
- } from "./chunk-MNHE6SSI.js";
11
-
12
- // src/core/mcp-access.ts
13
- function buildUrl(host, path, params = {}) {
14
- const base = host.replace(/\/$/, "");
15
- const p = path.startsWith("/") ? path : `/${path}`;
16
- const url = new URL(`${base}${p}`);
17
- for (const [key, value] of Object.entries(params)) {
18
- if (value !== void 0 && value !== null) {
19
- url.searchParams.set(key, String(value));
20
- }
21
- }
22
- return url.toString();
23
- }
24
- function getMcpTokenForHost(host) {
25
- const store = loadMcpTokenStore();
26
- const normalizedHost = host.replace(/\/+$/, "");
27
- if (store[host]) return store[host];
28
- if (store[normalizedHost]) return store[normalizedHost];
29
- for (const [key, token] of Object.entries(store)) {
30
- if (key.replace(/\/+$/, "") === normalizedHost) {
31
- return token;
32
- }
33
- }
34
- return void 0;
35
- }
36
- function getTokenFromStore(store, host) {
37
- const normalizedHost = host.replace(/\/+$/, "");
38
- if (store[host]) return store[host];
39
- if (store[normalizedHost]) return store[normalizedHost];
40
- for (const [key, token] of Object.entries(store)) {
41
- if (key.replace(/\/+$/, "") === normalizedHost) {
42
- return token;
43
- }
44
- }
45
- return void 0;
46
- }
47
- function refreshMcpTokenFromFallback(host) {
48
- logger.warn(`KB MCP request failed for ${host}, trying fallback`);
49
- const migrated = forceMigrateFromFallback();
50
- const token = migrated ? getTokenFromStore(migrated, host) : void 0;
51
- if (token) {
52
- logger.info(`KB MCP token refreshed from fallback for ${host}`);
53
- process.stderr.write(`[ae-cli] MCP token refreshed from fallback for ${host}
54
- `);
55
- }
56
- return token;
57
- }
58
- function isMcpAuthFailure(resp, data) {
59
- return !resp.ok || data.return_code === -1001;
60
- }
61
- function parseKbResponse(resp, text) {
62
- if (resp.status === 401 || resp.status === 403) {
63
- throw new Error(`KB MCP token auth failed: HTTP ${resp.status} ${resp.statusText}`);
64
- }
65
- const data = safeJsonParse(text);
66
- if (data.return_code === -1001) {
67
- throw new Error(`KB MCP token auth failed: ${data.return_message || "unauthorized"} (code: ${data.return_code})`);
68
- }
69
- if (data.return_code !== 0 && data.return_code !== void 0) {
70
- throw new Error(`AE API error: ${data.return_message || "unknown"} (code: ${data.return_code})`);
71
- }
72
- return data.data !== void 0 ? data.data : data;
73
- }
74
- async function fetchWithMcpToken(host, input, init, mcpToken) {
75
- const headers = new Headers(init.headers);
76
- headers.set("mcp-token", mcpToken);
77
- const resp = await fetch(input, { ...init, headers });
78
- const text = await resp.text();
79
- if (!resp.ok) {
80
- const refreshedToken = refreshMcpTokenFromFallback(host);
81
- if (refreshedToken) {
82
- headers.set("mcp-token", refreshedToken);
83
- const retryResp = await fetch(input, { ...init, headers });
84
- return parseKbResponse(retryResp, await retryResp.text());
85
- }
86
- return parseKbResponse(resp, text);
87
- }
88
- const data = safeJsonParse(text);
89
- if (isMcpAuthFailure(resp, data)) {
90
- const refreshedToken = refreshMcpTokenFromFallback(host);
91
- if (refreshedToken) {
92
- headers.set("mcp-token", refreshedToken);
93
- const retryResp = await fetch(input, { ...init, headers });
94
- return parseKbResponse(retryResp, await retryResp.text());
95
- }
96
- }
97
- return parseKbResponse(resp, text);
98
- }
99
- async function kbApi(ctx, method, path, params = {}, body) {
100
- const host = ctx.host();
101
- const mcpToken = getMcpTokenForHost(host);
102
- if (!mcpToken) {
103
- return ctx.api(method, path, params, body);
104
- }
105
- const upperMethod = method.toUpperCase();
106
- return fetchWithMcpToken(host, buildUrl(host, path, params), {
107
- method: upperMethod,
108
- headers: {
109
- "Content-Type": "application/json"
110
- },
111
- body: upperMethod === "GET" ? void 0 : JSON.stringify(body ?? {})
112
- }, mcpToken);
113
- }
114
- async function kbUpload(ctx, path, form, params = {}) {
115
- const host = ctx.host();
116
- const mcpToken = getMcpTokenForHost(host);
117
- if (!mcpToken) {
118
- const { httpUpload } = await import("./client-IU2E5IOL.js");
119
- return httpUpload(path, form, params, host);
120
- }
121
- return fetchWithMcpToken(host, buildUrl(host, path, params), {
122
- method: "POST",
123
- body: form
124
- }, mcpToken);
125
- }
126
-
127
- export {
128
- kbApi,
129
- kbUpload
130
- };
@@ -1,312 +0,0 @@
1
- import {
2
- extractHostname,
3
- getActiveHost,
4
- getConfigDir,
5
- safeJsonParse,
6
- safeReadJsonFile
7
- } from "./chunk-BI5ZVD6T.js";
8
- import {
9
- logger
10
- } from "./chunk-MNHE6SSI.js";
11
-
12
- // src/core/auth.ts
13
- import fs from "fs";
14
- import path from "path";
15
- import { execFileSync } from "child_process";
16
- var TOKENS_FILE = path.join(getConfigDir(), "tokens.json");
17
- var MCP_TOKENS_FILE = path.join(getConfigDir(), "mcp-tokens.json");
18
- var LEGACY_DIR = path.join(process.env.HOME || "", ".te-mcp");
19
- var TOKEN_TTL_MS = 20 * 60 * 60 * 1e3;
20
- var OSASCRIPT_POLL_INTERVAL_MS = 2e3;
21
- var OSASCRIPT_POLL_TIMEOUT_MS = 6e4;
22
- function ensureDir() {
23
- const dir = getConfigDir();
24
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
25
- }
26
- function resolveHost(hostOverride) {
27
- if (hostOverride) return hostOverride;
28
- return getActiveHost();
29
- }
30
- async function validateToken(token, hostUrl) {
31
- const url = `${hostUrl}/v1/oauth/checkToken?accessToken=${token}`;
32
- try {
33
- const resp = await fetch(url, { method: "POST" });
34
- const respJson = safeJsonParse(await resp.text());
35
- return respJson?.return_code === 0;
36
- } catch {
37
- return false;
38
- }
39
- }
40
- function loadAllTokens() {
41
- try {
42
- const legacyTokens = path.join(LEGACY_DIR, "tokens.json");
43
- if (fs.existsSync(legacyTokens) && !fs.existsSync(TOKENS_FILE)) {
44
- const data = safeReadJsonFile(legacyTokens);
45
- const migrated = {};
46
- for (const [key, val] of Object.entries(data)) {
47
- const url = key.startsWith("http") ? key : `https://${key}`;
48
- migrated[url] = val;
49
- }
50
- ensureDir();
51
- fs.writeFileSync(TOKENS_FILE, JSON.stringify(migrated, null, 2));
52
- return migrated;
53
- }
54
- if (fs.existsSync(TOKENS_FILE)) {
55
- const data = safeReadJsonFile(TOKENS_FILE);
56
- let needsMigration = false;
57
- const migrated = {};
58
- for (const [key, val] of Object.entries(data)) {
59
- if (!key.startsWith("http")) {
60
- migrated[`https://${key}`] = val;
61
- needsMigration = true;
62
- } else {
63
- migrated[key] = val;
64
- }
65
- }
66
- if (needsMigration) {
67
- fs.writeFileSync(TOKENS_FILE, JSON.stringify(migrated, null, 2));
68
- return migrated;
69
- }
70
- return data;
71
- }
72
- } catch {
73
- }
74
- return {};
75
- }
76
- function saveAllTokens(tokens) {
77
- ensureDir();
78
- fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2));
79
- }
80
- function loadToken(hostUrl) {
81
- const tokens = loadAllTokens();
82
- const entry = tokens[hostUrl];
83
- if (!entry || !entry.token) return null;
84
- if (entry.updatedAt) {
85
- const age = Date.now() - new Date(entry.updatedAt).getTime();
86
- if (age > TOKEN_TTL_MS) {
87
- clearToken(hostUrl);
88
- return null;
89
- }
90
- }
91
- return { host: hostUrl, token: entry.token, updatedAt: entry.updatedAt };
92
- }
93
- function saveToken(token, hostUrl) {
94
- const tokens = loadAllTokens();
95
- tokens[hostUrl] = { token, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
96
- saveAllTokens(tokens);
97
- logger.info(`Token saved for ${hostUrl}`);
98
- }
99
- function clearToken(hostUrl) {
100
- const tokens = loadAllTokens();
101
- delete tokens[hostUrl];
102
- saveAllTokens(tokens);
103
- }
104
- function setTokenManual(token, hostUrl) {
105
- saveToken(token, hostUrl);
106
- }
107
- function getAuthStatus(hostUrl) {
108
- if (process.env.TE_TOKEN) {
109
- return { authenticated: true, host: hostUrl, source: "env:TE_TOKEN" };
110
- }
111
- const cached = loadToken(hostUrl);
112
- if (cached) {
113
- const ageMs = Date.now() - new Date(cached.updatedAt).getTime();
114
- const hours = Math.round(ageMs / 36e5);
115
- return { authenticated: true, host: hostUrl, tokenAge: `${hours}h ago`, source: "cache" };
116
- }
117
- return { authenticated: false, host: hostUrl };
118
- }
119
- function getCachedMcpToken(hostUrl) {
120
- try {
121
- if (!fs.existsSync(MCP_TOKENS_FILE)) return void 0;
122
- const store = safeReadJsonFile(MCP_TOKENS_FILE);
123
- const normalizedHost = hostUrl.replace(/\/+$/, "");
124
- if (store[hostUrl]) return store[hostUrl];
125
- if (store[normalizedHost]) return store[normalizedHost];
126
- for (const [key, token] of Object.entries(store)) {
127
- if (key.replace(/\/+$/, "") === normalizedHost) {
128
- return token;
129
- }
130
- }
131
- return void 0;
132
- } catch {
133
- return void 0;
134
- }
135
- }
136
- function extractTokenViaOsascript(hostUrl) {
137
- if (process.platform !== "darwin") return { token: null, error: "not_mac" };
138
- const hostname = extractHostname(hostUrl);
139
- const lines = [
140
- 'tell application "Google Chrome"',
141
- " repeat with w in windows",
142
- " repeat with t in tabs of w",
143
- ` if URL of t contains "${hostname}" then`,
144
- ` return execute t javascript "localStorage.getItem('ACCESS_TOKEN')"`,
145
- " end if",
146
- " end repeat",
147
- " end repeat",
148
- ' return "NO_TAB_FOUND"',
149
- "end tell"
150
- ];
151
- try {
152
- const args = lines.flatMap((line) => ["-e", line]);
153
- const result = execFileSync("osascript", args, { encoding: "utf8", timeout: 5e3 }).trim();
154
- if (result === "NO_TAB_FOUND") return { token: null, error: "no_tab" };
155
- if (!result || result === "missing value") return { token: null, error: "no_token" };
156
- return { token: result.replace(/^["']|["']$/g, ""), error: null };
157
- } catch (e) {
158
- const msg = e.message || "";
159
- if (msg.includes("not allowed") || msg.includes("assistive access") || msg.includes("(-1743)")) {
160
- return { token: null, error: "no_js_permission" };
161
- }
162
- return { token: null, error: "no_tab" };
163
- }
164
- }
165
- function extractTokenFromAllTabs() {
166
- const lines = [
167
- 'tell application "Google Chrome"',
168
- " repeat with w in windows",
169
- " repeat with t in tabs of w",
170
- " try",
171
- ` set tokenVal to execute t javascript "localStorage.getItem('ACCESS_TOKEN')"`,
172
- ' if tokenVal is not null and tokenVal is not missing value and tokenVal is not "" then',
173
- " return tokenVal",
174
- " end if",
175
- " end try",
176
- " end repeat",
177
- " end repeat",
178
- ' return "NO_TOKEN_FOUND"',
179
- "end tell"
180
- ];
181
- try {
182
- const args = lines.flatMap((line) => ["-e", line]);
183
- const result = execFileSync("osascript", args, { encoding: "utf8", timeout: 1e4 }).trim();
184
- if (result === "NO_TOKEN_FOUND" || !result || result === "missing value") {
185
- return { token: null, error: "no_token" };
186
- }
187
- return { token: result.replace(/^["']|["']$/g, ""), error: null };
188
- } catch (e) {
189
- const msg = e.message || "";
190
- if (msg.includes("not allowed") || msg.includes("assistive access") || msg.includes("(-1743)")) {
191
- return { token: null, error: "no_js_permission" };
192
- }
193
- return { token: null, error: "no_tab" };
194
- }
195
- }
196
- async function requestTokenViaOsascript(hostUrl) {
197
- logger.info(`Opening Chrome to ${hostUrl} for token extraction`);
198
- process.stderr.write(`[ae-cli] No AE tab found in Chrome. Opening ${hostUrl} ...
199
- `);
200
- process.stderr.write(`[ae-cli] Please login, then your token will be captured automatically.
201
- `);
202
- try {
203
- const openScript = [
204
- 'tell application "Google Chrome"',
205
- " activate",
206
- " if (count of windows) > 0 then",
207
- ` make new tab at end of tabs of window 1 with properties {URL:"${hostUrl}"}`,
208
- " else",
209
- ` open location "${hostUrl}"`,
210
- " end if",
211
- "end tell"
212
- ];
213
- execFileSync("osascript", openScript.flatMap((line) => ["-e", line]), { timeout: 5e3 });
214
- } catch {
215
- logger.warn(`Failed to open Chrome, user may need to open ${hostUrl} manually`);
216
- process.stderr.write(`[ae-cli] Could not open browser. Please open ${hostUrl} in Chrome manually.
217
- `);
218
- }
219
- const deadline = Date.now() + OSASCRIPT_POLL_TIMEOUT_MS;
220
- let pollCount = 0;
221
- while (Date.now() < deadline) {
222
- await new Promise((r) => setTimeout(r, OSASCRIPT_POLL_INTERVAL_MS));
223
- pollCount++;
224
- let result = extractTokenViaOsascript(hostUrl);
225
- if (result.error === "no_tab") {
226
- const fallback = extractTokenFromAllTabs();
227
- if (fallback.token) {
228
- logger.info("Token captured via all-tabs fallback search");
229
- result = fallback;
230
- }
231
- }
232
- if (result.token) {
233
- saveToken(result.token, hostUrl);
234
- logger.info(`Token captured after ${pollCount} poll(s)`);
235
- process.stderr.write(`[ae-cli] Token captured from Chrome for ${hostUrl}.
236
- `);
237
- return result.token;
238
- }
239
- if (result.error === "no_js_permission") {
240
- logger.warn("Chrome JS permission denied during polling");
241
- return null;
242
- }
243
- }
244
- logger.warn(`Token polling timed out after ${OSASCRIPT_POLL_TIMEOUT_MS / 1e3}s (${pollCount} polls)`);
245
- process.stderr.write(`[ae-cli] Polling timed out after ${OSASCRIPT_POLL_TIMEOUT_MS / 1e3}s.
246
- `);
247
- return null;
248
- }
249
- async function getToken(hostUrl) {
250
- if (!hostUrl) {
251
- throw new Error(
252
- `No AE host configured.
253
- Run: ae-cli config set-host`
254
- );
255
- }
256
- if (process.env.TE_TOKEN) {
257
- logger.info("Using token from env:TE_TOKEN");
258
- return process.env.TE_TOKEN;
259
- }
260
- const cached = loadToken(hostUrl);
261
- if (cached && cached.token) {
262
- logger.info(`Using cached token for ${hostUrl}`);
263
- return cached.token;
264
- }
265
- if (getCachedMcpToken(hostUrl)) {
266
- logger.info(`MCP token exists for ${hostUrl}; skipping browser token extraction`);
267
- throw new Error(
268
- `MCP token already exists for ${hostUrl}; skipped browser login.
269
- Use an MCP-token aware command, or run: ae-cli auth set-token <token>`
270
- );
271
- }
272
- const { token: osascriptToken, error } = extractTokenViaOsascript(hostUrl);
273
- if (error === "no_js_permission") {
274
- logger.warn("Chrome JS Apple Events not enabled");
275
- throw new Error(
276
- `Chrome JavaScript from Apple Events is not enabled.
277
- Enable it: Chrome menu \u2192 View \u2192 Developer \u2192 Allow JavaScript from Apple Events`
278
- );
279
- }
280
- if (error === "not_mac") {
281
- throw new Error(
282
- `Auto token extraction is only available on macOS + Chrome.
283
- Use: ae-cli auth set-token <token>`
284
- );
285
- }
286
- if (osascriptToken) {
287
- saveToken(osascriptToken, hostUrl);
288
- return osascriptToken;
289
- }
290
- const polledToken = await requestTokenViaOsascript(hostUrl);
291
- if (polledToken) {
292
- saveToken(polledToken, hostUrl);
293
- return polledToken;
294
- }
295
- throw new Error(
296
- `Cannot obtain token for ${hostUrl}.
297
- Options:
298
- 1. ae-cli auth login (macOS + Chrome)
299
- 2. ae-cli auth set-token <token>`
300
- );
301
- }
302
-
303
- export {
304
- resolveHost,
305
- validateToken,
306
- loadToken,
307
- saveToken,
308
- clearToken,
309
- setTokenManual,
310
- getAuthStatus,
311
- getToken
312
- };