@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
@@ -0,0 +1,454 @@
1
+ import {
2
+ printError,
3
+ printOutput
4
+ } from "./chunk-CLJF7MQA.js";
5
+ import {
6
+ clearMcpToken,
7
+ setMcpTokenManual,
8
+ validateMcpToken
9
+ } from "./chunk-WCHI7725.js";
10
+ import "./chunk-GSJGPNKK.js";
11
+ import {
12
+ clear,
13
+ clearToken,
14
+ getAuthStatus,
15
+ load,
16
+ resolveHost,
17
+ save,
18
+ setTokenManual,
19
+ validateToken
20
+ } from "./chunk-CAQYQA4R.js";
21
+ import {
22
+ loadConfig,
23
+ logger,
24
+ saveConfig
25
+ } from "./chunk-SRJIAOBN.js";
26
+
27
+ // src/core/device-auth.ts
28
+ import { execFileSync } from "child_process";
29
+ var DeviceFlowUnsupportedError = class extends Error {
30
+ constructor() {
31
+ super("This AE server does not support device code login (it may be an older server version).");
32
+ this.name = "DeviceFlowUnsupportedError";
33
+ }
34
+ };
35
+ function looksLikeMissingEndpoint(status, contentType, body) {
36
+ if (status === 404) return true;
37
+ if (contentType && contentType.toLowerCase().includes("text/html")) return true;
38
+ const head = body.trimStart().toLowerCase();
39
+ return head.startsWith("<!doctype html") || head.startsWith("<html");
40
+ }
41
+ function openBrowser(url, _execFile) {
42
+ let parsedUrl;
43
+ try {
44
+ parsedUrl = new URL(url);
45
+ } catch {
46
+ logger.warn(`openBrowser: invalid URL, not opening`);
47
+ return false;
48
+ }
49
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
50
+ logger.warn(`openBrowser: unsafe protocol "${parsedUrl.protocol}", not opening`);
51
+ return false;
52
+ }
53
+ const safeLogUrl = `${parsedUrl.origin}${parsedUrl.pathname}`;
54
+ const exec = _execFile ?? execFileSync;
55
+ try {
56
+ if (process.platform === "darwin") {
57
+ exec("open", [url], { timeout: 5e3 });
58
+ return true;
59
+ }
60
+ if (process.platform === "win32") {
61
+ exec("cmd", ["/c", "start", "", url], { timeout: 5e3 });
62
+ return true;
63
+ }
64
+ exec("xdg-open", [url], { timeout: 5e3 });
65
+ return true;
66
+ } catch (e) {
67
+ logger.warn(`openBrowser: failed to open ${safeLogUrl}: ${e.message}`);
68
+ return false;
69
+ }
70
+ }
71
+ async function authorizeDevice(host) {
72
+ const base = host.replace(/\/+$/, "");
73
+ const url = `${base}/api/auth/device/authorize`;
74
+ let resp;
75
+ try {
76
+ resp = await fetch(url, {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/json" }
79
+ });
80
+ } catch (e) {
81
+ throw new Error(`Device authorize request failed: ${e.message}`);
82
+ }
83
+ const contentType = resp.headers.get("content-type");
84
+ const text = await resp.text().catch(() => "");
85
+ if (looksLikeMissingEndpoint(resp.status, contentType, text)) {
86
+ throw new DeviceFlowUnsupportedError();
87
+ }
88
+ if (!resp.ok) {
89
+ throw new Error(`Device authorize returned HTTP ${resp.status}: ${text}`);
90
+ }
91
+ let body;
92
+ try {
93
+ body = JSON.parse(text);
94
+ } catch {
95
+ throw new Error("Device authorize returned invalid JSON");
96
+ }
97
+ if (!body.device_code || !body.user_code) {
98
+ throw new Error("Device authorize response missing required fields");
99
+ }
100
+ return body;
101
+ }
102
+ async function pollDeviceToken(host, deviceCode) {
103
+ const base = host.replace(/\/+$/, "");
104
+ const url = `${base}/api/auth/device/token`;
105
+ let resp;
106
+ try {
107
+ resp = await fetch(url, {
108
+ method: "POST",
109
+ headers: { "Content-Type": "application/json" },
110
+ body: JSON.stringify({ device_code: deviceCode })
111
+ });
112
+ } catch (e) {
113
+ return { status: "error", message: `Network error: ${e.message}` };
114
+ }
115
+ if (resp.status === 200) {
116
+ let body;
117
+ try {
118
+ body = await resp.json();
119
+ } catch {
120
+ return { status: "error", message: "Token response is not valid JSON" };
121
+ }
122
+ if (!body.access_token) {
123
+ return { status: "error", message: "Token response missing access_token" };
124
+ }
125
+ return { status: "approved", tokens: body };
126
+ }
127
+ if (resp.status === 400) {
128
+ let errorBody;
129
+ try {
130
+ errorBody = await resp.json();
131
+ } catch {
132
+ errorBody = {};
133
+ }
134
+ const err = errorBody.error;
135
+ if (err === "authorization_pending") return { status: "pending" };
136
+ if (err === "slow_down") return { status: "slow_down" };
137
+ if (err === "expired_token") return { status: "expired" };
138
+ return { status: "error", message: `Unknown error: ${err ?? "unknown"}` };
139
+ }
140
+ const contentType = resp.headers.get("content-type");
141
+ const text = await resp.text().catch(() => "");
142
+ if (looksLikeMissingEndpoint(resp.status, contentType, text)) {
143
+ return { status: "unsupported" };
144
+ }
145
+ return { status: "error", message: `Unexpected HTTP ${resp.status} from token endpoint` };
146
+ }
147
+ function buildVerificationUrl(host, userCode) {
148
+ return `${host.replace(/\/+$/, "")}/device-activate?user_code=${encodeURIComponent(userCode)}`;
149
+ }
150
+ async function pollDeviceFlow(host, deviceCode, options = {}, onStatus) {
151
+ const emit = (msg) => {
152
+ if (onStatus) onStatus(msg);
153
+ };
154
+ let intervalMs = options.intervalMs ?? 5e3;
155
+ const expiresIn = options.expiresIn ?? 300;
156
+ const deadline = Date.now() + expiresIn * 1e3;
157
+ let pollCount = 0;
158
+ while (Date.now() < deadline) {
159
+ await sleep(intervalMs);
160
+ pollCount++;
161
+ const result = await pollDeviceToken(host, deviceCode);
162
+ if (result.status === "approved") {
163
+ emit(`Authorization successful! (polled ${pollCount} time(s))`);
164
+ return result.tokens;
165
+ }
166
+ if (result.status === "pending") {
167
+ if (pollCount % 5 === 0) {
168
+ const remaining = Math.max(0, Math.round((deadline - Date.now()) / 1e3));
169
+ emit(`Waiting for authorization... (expires in ${remaining}s)`);
170
+ }
171
+ logger.info(`device-auth: poll #${pollCount} pending`);
172
+ continue;
173
+ }
174
+ if (result.status === "slow_down") {
175
+ intervalMs += 5e3;
176
+ logger.info(`device-auth: slow_down received, new interval=${intervalMs}ms`);
177
+ continue;
178
+ }
179
+ if (result.status === "expired") {
180
+ throw new Error(
181
+ `Device code has expired. Please run ae-cli auth login again to restart the authorization flow.`
182
+ );
183
+ }
184
+ if (result.status === "unsupported") {
185
+ throw new DeviceFlowUnsupportedError();
186
+ }
187
+ if (result.status === "error") {
188
+ logger.warn(`device-auth: poll error: ${result.message}`);
189
+ emit(`Poll error: ${result.message}`);
190
+ if (result.message.includes("HTTP 401") || result.message.includes("HTTP 403")) {
191
+ throw new Error(`Authorization denied (${result.message}). Please check your configuration or run ae-cli auth login again.`);
192
+ }
193
+ continue;
194
+ }
195
+ }
196
+ throw new Error(
197
+ `Authorization timed out (not completed within ${expiresIn}s). Please run ae-cli auth login again.`
198
+ );
199
+ }
200
+ async function runDeviceFlow(host, options = {}, onStatus) {
201
+ const emit = (msg) => {
202
+ if (onStatus) onStatus(msg);
203
+ };
204
+ emit("Requesting authorization code from server...");
205
+ const auth = await authorizeDevice(host);
206
+ const userCode = auth.user_code;
207
+ const verifyUrl = buildVerificationUrl(host, userCode);
208
+ const expiresIn = auth.expires_in ?? 300;
209
+ emit("");
210
+ emit(`Verification code: ${userCode}`);
211
+ emit(`Open the following link in your browser to complete authorization:`);
212
+ emit(` ${verifyUrl}`);
213
+ emit("");
214
+ if (!options.noBrowser) {
215
+ const opened = openBrowser(verifyUrl);
216
+ if (opened) {
217
+ emit("(Attempted to open the link in your browser)");
218
+ } else {
219
+ emit("(Could not open browser automatically; please copy the link above manually)");
220
+ }
221
+ emit("");
222
+ }
223
+ return pollDeviceFlow(host, auth.device_code, { intervalMs: (auth.interval ?? 5) * 1e3, expiresIn }, onStatus);
224
+ }
225
+ function sleep(ms) {
226
+ return new Promise((resolve) => setTimeout(resolve, ms));
227
+ }
228
+
229
+ // src/commands/auth.ts
230
+ var HOST_OPTION_DESC = "Override active AE host URL (e.g., https://ta.thinkingdata.cn)";
231
+ function resolveAuthHost(program, opts) {
232
+ return resolveHost(opts.host || program.opts().host);
233
+ }
234
+ function persistDeviceTokens(host, tokens) {
235
+ const DEFAULT_EXPIRES_IN = 72e3;
236
+ const MAX_EXPIRES_IN = 86400 * 365;
237
+ let expiresInSeconds = tokens.expires_in ?? DEFAULT_EXPIRES_IN;
238
+ if (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0 || expiresInSeconds > MAX_EXPIRES_IN) {
239
+ logger.warn(`auth login: expires_in=${tokens.expires_in} is out of range, falling back to ${DEFAULT_EXPIRES_IN}s`);
240
+ expiresInSeconds = DEFAULT_EXPIRES_IN;
241
+ }
242
+ const expiresAt = new Date(Date.now() + expiresInSeconds * 1e3).toISOString();
243
+ save(host, {
244
+ accessToken: tokens.access_token,
245
+ refreshToken: tokens.refresh_token ?? "",
246
+ accessExpiresAt: expiresAt,
247
+ mcpToken: tokens.mcp_token ?? void 0
248
+ });
249
+ }
250
+ function loginSummary(host, tokens) {
251
+ return {
252
+ authenticated: true,
253
+ host,
254
+ token: tokens.access_token.slice(0, 8) + "...",
255
+ mcpToken: tokens.mcp_token ? tokens.mcp_token.slice(0, 8) + "..." : null
256
+ };
257
+ }
258
+ function resolveTeClaudeBase(host) {
259
+ const override = process.env.TE_CLAUDE_BASE_URL;
260
+ if (override) return override.replace(/\/+$/, "");
261
+ const base = host.replace(/\/+$/, "");
262
+ const basePath = process.env.TE_CLAUDE_BASE_PATH || "/agent";
263
+ return base.endsWith(basePath) ? base : base + basePath;
264
+ }
265
+ function registerAuth(program) {
266
+ const auth = program.command("auth").description("Authentication management");
267
+ auth.command("login").description("Log in to AE using the device code flow (cross-platform; --no-browser to print URL only)").option("--host <url>", HOST_OPTION_DESC).option("--no-browser", "Do not auto-open the browser; only print the authorization URL (headless / Linux)").option("--no-wait", "Request the device code and print the authorization URL as JSON, then exit without polling (for AI agents / split-flow). Resume with --device-code").option("--device-code <code>", "Resume a split-flow login: poll for the given device code until authorized, then save the token").action(async (opts) => {
268
+ const host = resolveAuthHost(program, opts);
269
+ if (!host) {
270
+ printError("config", "No AE host configured.", "Run: ae-cli config set-host <url>");
271
+ process.exit(1);
272
+ }
273
+ const config = loadConfig();
274
+ if (!config.hosts[host]) {
275
+ config.hosts[host] = { label: host };
276
+ if (!config.activeHost) {
277
+ config.activeHost = host;
278
+ }
279
+ saveConfig(config);
280
+ logger.info(`Host config auto-saved for ${host}`);
281
+ process.stderr.write(`[ae-cli] Config saved for ${host}
282
+ `);
283
+ }
284
+ const noBrowser = !opts.browser;
285
+ const fmt = program.opts().format || "json";
286
+ const teClaudeBase = resolveTeClaudeBase(host);
287
+ const emit = (msg) => process.stderr.write(`[ae-cli] ${msg}
288
+ `);
289
+ try {
290
+ if (opts.deviceCode) {
291
+ const tokens2 = await pollDeviceFlow(teClaudeBase, opts.deviceCode, {}, emit);
292
+ persistDeviceTokens(host, tokens2);
293
+ logger.info(`Device flow login successful for ${host} (resumed)`);
294
+ emit("Login successful! Token saved securely.");
295
+ printOutput(loginSummary(host, tokens2), fmt);
296
+ return;
297
+ }
298
+ if (opts.wait === false) {
299
+ const authResp = await authorizeDevice(teClaudeBase);
300
+ printOutput(
301
+ {
302
+ device_code: authResp.device_code,
303
+ user_code: authResp.user_code,
304
+ verification_url: buildVerificationUrl(teClaudeBase, authResp.user_code),
305
+ interval: authResp.interval,
306
+ expires_in: authResp.expires_in,
307
+ next: `Open verification_url in a browser to authorize, then run: ae-cli auth login --device-code ${authResp.device_code}`
308
+ },
309
+ fmt
310
+ );
311
+ return;
312
+ }
313
+ const tokens = await runDeviceFlow(teClaudeBase, { noBrowser }, emit);
314
+ persistDeviceTokens(host, tokens);
315
+ logger.info(`Device flow login successful for ${host}`);
316
+ emit("Login successful! Token saved securely.");
317
+ printOutput(loginSummary(host, tokens), fmt);
318
+ } catch (err) {
319
+ if (err instanceof DeviceFlowUnsupportedError) {
320
+ printError(
321
+ "auth",
322
+ err.message,
323
+ "Use `ae-cli auth set-token <token>` to authenticate, or upgrade the AE server to a version that supports device login."
324
+ );
325
+ process.exit(1);
326
+ }
327
+ printError("auth", err.message);
328
+ process.exit(1);
329
+ }
330
+ });
331
+ auth.command("set-token <token>").description("Manually set authentication token for a host (--host or active host)").option("--host <url>", HOST_OPTION_DESC).action(async (token, opts) => {
332
+ const host = resolveAuthHost(program, opts);
333
+ if (!host) {
334
+ printError("config", "No AE host configured.", "Run: ae-cli config set-host <url>");
335
+ process.exit(1);
336
+ }
337
+ const config = loadConfig();
338
+ if (!config.hosts[host]) {
339
+ config.hosts[host] = { label: host };
340
+ if (!config.activeHost) {
341
+ config.activeHost = host;
342
+ }
343
+ saveConfig(config);
344
+ process.stderr.write(`[ae-cli] Config saved for ${host}
345
+ `);
346
+ printOutput({ saved: true, config }, program.opts().format || "json");
347
+ }
348
+ process.stderr.write(`[ae-cli] Validating token...
349
+ `);
350
+ const isValid = await validateToken(token, host);
351
+ if (!isValid) {
352
+ logger.warn(`Token validation failed for ${host}`);
353
+ process.stderr.write(`[ae-cli] Token validation failed
354
+ `);
355
+ printError("auth", "Invalid token", "Please check your token and try again");
356
+ process.exit(1);
357
+ }
358
+ setTokenManual(token, host);
359
+ logger.info(`Token manually set and validated for ${host}`);
360
+ process.stderr.write(`[ae-cli] Token verified and saved for ${host}
361
+ `);
362
+ printOutput({ saved: true, host, verified: true }, program.opts().format || "json");
363
+ });
364
+ auth.command("status").description("Show authentication status for a host (--host or active host)").option("--host <url>", HOST_OPTION_DESC).action(async (opts) => {
365
+ const host = resolveAuthHost(program, opts);
366
+ if (!host) {
367
+ printOutput({ authenticated: false, host: "(none)", hint: "Run: ae-cli config set-host <url>" }, program.opts().format || "json");
368
+ return;
369
+ }
370
+ const secureEntry = load(host);
371
+ if (secureEntry) {
372
+ const pastStaticExpiry = new Date(secureEntry.accessExpiresAt).getTime() < Date.now();
373
+ printOutput(
374
+ {
375
+ authenticated: true,
376
+ host,
377
+ source: "secure-store",
378
+ accessExpiresAt: secureEntry.accessExpiresAt,
379
+ accessExpiresAtNote: "advisory: access token auto-slides on use server-side; re-login only if a command returns an auth error",
380
+ pastStaticExpiry,
381
+ hasMcpToken: !!secureEntry.mcpToken
382
+ },
383
+ program.opts().format || "json"
384
+ );
385
+ return;
386
+ }
387
+ const tokenStatus = getAuthStatus(host);
388
+ const authenticated = tokenStatus.authenticated;
389
+ printOutput({ authenticated, host, source: tokenStatus.source ?? "none" }, program.opts().format || "json");
390
+ });
391
+ auth.command("logout").description("Clear stored token for a host (--host or active host)").option("--host <url>", HOST_OPTION_DESC).action((opts) => {
392
+ const host = resolveAuthHost(program, opts);
393
+ if (!host) {
394
+ printError("config", "No AE host configured.", "Run: ae-cli config set-host <url>");
395
+ process.exit(1);
396
+ }
397
+ clearToken(host);
398
+ clear(host);
399
+ process.stderr.write(`[ae-cli] Token cleared for ${host}
400
+ `);
401
+ printOutput({ cleared: true, host }, program.opts().format || "json");
402
+ });
403
+ auth.command("set-mcp-token <token>").description("Manually set MCP token for a host (--host or active host)").option("--host <url>", HOST_OPTION_DESC).action(async (token, opts) => {
404
+ const host = resolveAuthHost(program, opts);
405
+ if (!host) {
406
+ printError("config", "No AE host configured.", "Use --host <url> or run: ae-cli config set-host <url>");
407
+ process.exit(1);
408
+ }
409
+ const config = loadConfig();
410
+ if (!config.hosts[host]) {
411
+ config.hosts[host] = { label: host };
412
+ if (!config.activeHost) {
413
+ config.activeHost = host;
414
+ }
415
+ saveConfig(config);
416
+ process.stderr.write(`[ae-cli] Host config saved for ${host}
417
+ `);
418
+ }
419
+ process.stderr.write(`[ae-cli] Validating MCP token...
420
+ `);
421
+ const isValid = await validateMcpToken(token, host);
422
+ if (!isValid) {
423
+ process.stderr.write(`[ae-cli] MCP token validation failed
424
+ `);
425
+ printError("auth", "Invalid MCP token", "Please check your MCP token and try again");
426
+ process.exit(1);
427
+ }
428
+ setMcpTokenManual(token, host);
429
+ process.stderr.write(`[ae-cli] MCP token verified and saved for ${host}
430
+ `);
431
+ printOutput({ saved: true, host, verified: true, type: "mcp-token" }, program.opts().format || "json");
432
+ });
433
+ auth.command("clear-mcp-token").description("Clear stored MCP token for a host (--host or active host; --all clears all)").option("--host <url>", HOST_OPTION_DESC).option("--all", "Clear MCP tokens for all hosts").action((opts) => {
434
+ if (opts.all) {
435
+ clearMcpToken();
436
+ process.stderr.write(`[ae-cli] All MCP tokens cleared
437
+ `);
438
+ printOutput({ cleared: true, type: "all-mcp-tokens" }, program.opts().format || "json");
439
+ return;
440
+ }
441
+ const host = resolveAuthHost(program, opts);
442
+ if (!host) {
443
+ printError("config", "No AE host configured.", "Use --host <url> or run: ae-cli config set-host <url>");
444
+ process.exit(1);
445
+ }
446
+ clearMcpToken(host);
447
+ process.stderr.write(`[ae-cli] MCP token cleared for ${host}
448
+ `);
449
+ printOutput({ cleared: true, host }, program.opts().format || "json");
450
+ });
451
+ }
452
+ export {
453
+ registerAuth
454
+ };
@@ -7,9 +7,8 @@ import {
7
7
  saveToken,
8
8
  setTokenManual,
9
9
  validateToken
10
- } from "./chunk-SRRUNQIM.js";
11
- import "./chunk-BI5ZVD6T.js";
12
- import "./chunk-MNHE6SSI.js";
10
+ } from "./chunk-CAQYQA4R.js";
11
+ import "./chunk-SRJIAOBN.js";
13
12
  export {
14
13
  clearToken,
15
14
  getAuthStatus,
@@ -0,0 +1,217 @@
1
+ import {
2
+ TeAgentCredentialsError,
3
+ tryLoadTeAgentSandboxCredentials
4
+ } from "./chunk-KAEZTSXN.js";
5
+ import {
6
+ getActiveHost
7
+ } from "./chunk-SRJIAOBN.js";
8
+
9
+ // src/core/te-agent-client.ts
10
+ var TeAgentApiError = class extends Error {
11
+ constructor(message, status, code, body) {
12
+ super(message);
13
+ this.status = status;
14
+ this.code = code;
15
+ this.body = body;
16
+ this.name = "TeAgentApiError";
17
+ }
18
+ status;
19
+ code;
20
+ body;
21
+ };
22
+ var DEFAULT_TIMEOUT_MS = 3e4;
23
+ var UPLOAD_TIMEOUT_MS = 12e4;
24
+ async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_TIMEOUT_MS) {
25
+ const controller = new AbortController();
26
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
27
+ try {
28
+ return await fetch(url, { ...init, signal: controller.signal });
29
+ } catch (err) {
30
+ if (err?.name === "AbortError") {
31
+ throw new TeAgentApiError(`Request timed out (${timeoutMs}ms): ${url}`, 0, "TIMEOUT");
32
+ }
33
+ throw new TeAgentApiError(`Network request failed: ${err?.message ?? err}`, 0, "NETWORK_ERROR");
34
+ } finally {
35
+ clearTimeout(timer);
36
+ }
37
+ }
38
+ function teClaudeBaseFromActiveHost() {
39
+ const override = process.env.TE_CLAUDE_BASE_URL;
40
+ if (override) return override.replace(/\/+$/, "");
41
+ const h = getActiveHost();
42
+ if (!h) return void 0;
43
+ const base = h.replace(/\/+$/, "");
44
+ const bp = process.env.TE_CLAUDE_BASE_PATH || "/agent";
45
+ return base.endsWith(bp) ? base : base + bp;
46
+ }
47
+ async function signRequest(method, path, rawBody) {
48
+ const sandboxCred = tryLoadTeAgentSandboxCredentials();
49
+ if (sandboxCred && sandboxCred.sandboxId && sandboxCred.sandboxSecretKey) {
50
+ const headers2 = {
51
+ "X-Sandbox-Id": sandboxCred.sandboxId,
52
+ "X-Sandbox-Secret-Key": sandboxCred.sandboxSecretKey
53
+ };
54
+ if (method === "POST" || method === "PATCH") {
55
+ headers2["Content-Type"] = "application/json";
56
+ }
57
+ return {
58
+ url: `${sandboxCred.url.replace(/\/$/, "")}${path}`,
59
+ headers: headers2,
60
+ rawBody
61
+ };
62
+ }
63
+ const { getToken } = await import("./auth-H2376DEF.js");
64
+ const baseUrl = sandboxCred?.url || teClaudeBaseFromActiveHost();
65
+ if (!baseUrl) {
66
+ throw new TeAgentCredentialsError(
67
+ "Cannot determine the te-claude service URL",
68
+ "Run ae-cli config set-host <url> to configure the service URL, or execute inside a te-agent sandbox"
69
+ );
70
+ }
71
+ const accessToken = await getToken(getActiveHost() || baseUrl);
72
+ const headers = {
73
+ "Authorization": `bearer ${accessToken}`
74
+ };
75
+ if (method === "POST" || method === "PATCH") {
76
+ headers["Content-Type"] = "application/json";
77
+ }
78
+ return {
79
+ url: `${baseUrl.replace(/\/$/, "")}${path}`,
80
+ headers,
81
+ rawBody
82
+ };
83
+ }
84
+ async function parseResponse(response, defaultErrorPrefix) {
85
+ const text = await response.text();
86
+ let parsed = void 0;
87
+ if (text) {
88
+ try {
89
+ parsed = JSON.parse(text);
90
+ } catch {
91
+ parsed = text;
92
+ }
93
+ }
94
+ if (!response.ok) {
95
+ if (response.status === 401) {
96
+ throw new TeAgentApiError(
97
+ `Session expired or unauthorized. Please run: ae-cli auth login`,
98
+ response.status,
99
+ "auth_expired",
100
+ parsed
101
+ );
102
+ }
103
+ const message = typeof parsed === "object" && parsed && typeof parsed.error === "string" ? parsed.error : `${defaultErrorPrefix} ${response.status}`;
104
+ const code = typeof parsed === "object" && parsed && typeof parsed.code === "string" ? parsed.code : void 0;
105
+ throw new TeAgentApiError(message, response.status, code, parsed);
106
+ }
107
+ return parsed;
108
+ }
109
+ async function postToMainApp(path, body) {
110
+ const rawBody = JSON.stringify(body);
111
+ const signed = await signRequest("POST", path, rawBody);
112
+ const response = await fetchWithTimeout(signed.url, {
113
+ method: "POST",
114
+ headers: signed.headers,
115
+ body: signed.rawBody
116
+ });
117
+ return parseResponse(response, "Main app returned");
118
+ }
119
+ async function getFromMainApp(path) {
120
+ const signed = await signRequest("GET", path, "");
121
+ const response = await fetchWithTimeout(signed.url, {
122
+ method: "GET",
123
+ headers: signed.headers
124
+ });
125
+ return parseResponse(response, "Main app returned");
126
+ }
127
+ async function getSandboxModels(currentModelId) {
128
+ const qs = currentModelId ? `?current=${encodeURIComponent(currentModelId)}` : "";
129
+ const data = await getFromMainApp(`/api/sandbox/models${qs}`);
130
+ return Array.isArray(data?.models) ? data.models : [];
131
+ }
132
+ async function postSandboxModelSelection(args) {
133
+ return postToMainApp("/api/sandbox/models/select", args);
134
+ }
135
+ async function getSandboxSyncPullCandidates(args) {
136
+ const qs = new URLSearchParams({
137
+ workspacePath: args.workspacePath,
138
+ kind: args.kind
139
+ });
140
+ return getFromMainApp(
141
+ `/api/sandbox/sync/pull/candidates?${qs.toString()}`
142
+ );
143
+ }
144
+ async function postSandboxSyncPull(args) {
145
+ return postToMainApp("/api/sandbox/sync/pull", {
146
+ workspacePath: args.workspacePath,
147
+ kind: args.kind,
148
+ skills: args.skills,
149
+ mcp: args.mcp,
150
+ mode: "merge",
151
+ ifUnmodifiedSince: args.ifUnmodifiedSince
152
+ });
153
+ }
154
+ async function deleteFromMainApp(path) {
155
+ const signed = await signRequest("DELETE", path, "");
156
+ const response = await fetchWithTimeout(signed.url, {
157
+ method: "DELETE",
158
+ headers: signed.headers
159
+ });
160
+ return parseResponse(response, "Main app returned");
161
+ }
162
+ async function patchToMainApp(path, body) {
163
+ const rawBody = JSON.stringify(body);
164
+ const signed = await signRequest("PATCH", path, rawBody);
165
+ const response = await fetchWithTimeout(signed.url, {
166
+ method: "PATCH",
167
+ headers: signed.headers,
168
+ body: signed.rawBody
169
+ });
170
+ return parseResponse(response, "Main app returned");
171
+ }
172
+ async function uploadToMainApp(path, formData) {
173
+ const sandboxCred = tryLoadTeAgentSandboxCredentials();
174
+ let baseUrl;
175
+ let authHeaders;
176
+ if (sandboxCred && sandboxCred.sandboxId && sandboxCred.sandboxSecretKey) {
177
+ baseUrl = sandboxCred.url.replace(/\/$/, "");
178
+ authHeaders = {
179
+ "X-Sandbox-Id": sandboxCred.sandboxId,
180
+ "X-Sandbox-Secret-Key": sandboxCred.sandboxSecretKey
181
+ };
182
+ } else {
183
+ const { getToken } = await import("./auth-H2376DEF.js");
184
+ baseUrl = (sandboxCred?.url || teClaudeBaseFromActiveHost() || "").replace(/\/$/, "");
185
+ if (!baseUrl) {
186
+ throw new TeAgentCredentialsError(
187
+ "Cannot determine the te-claude service URL",
188
+ "Run ae-cli config set-host <url> to configure the service URL, or execute inside a te-agent sandbox"
189
+ );
190
+ }
191
+ const accessToken = await getToken(getActiveHost() || baseUrl);
192
+ authHeaders = { "Authorization": `bearer ${accessToken}` };
193
+ }
194
+ const response = await fetchWithTimeout(
195
+ `${baseUrl}${path}`,
196
+ {
197
+ method: "POST",
198
+ headers: authHeaders,
199
+ body: formData
200
+ },
201
+ UPLOAD_TIMEOUT_MS
202
+ );
203
+ return parseResponse(response, "Upload failed");
204
+ }
205
+
206
+ export {
207
+ TeAgentApiError,
208
+ postToMainApp,
209
+ getFromMainApp,
210
+ getSandboxModels,
211
+ postSandboxModelSelection,
212
+ getSandboxSyncPullCandidates,
213
+ postSandboxSyncPull,
214
+ deleteFromMainApp,
215
+ patchToMainApp,
216
+ uploadToMainApp
217
+ };