@thinkingai/ae-cli 1.0.27 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -16
- package/README.zh.md +1 -16
- package/dist/auth-H3GJD55A.js +457 -0
- package/dist/{auth-TXFJHPXU.js → auth-U25QJU5D.js} +3 -3
- package/dist/{chunk-B4UL2VIJ.js → chunk-CLJF7MQA.js} +1 -1
- package/dist/{chunk-6T7245YE.js → chunk-EGEIXA2Z.js} +90 -18
- package/dist/{chunk-2NGWYMLB.js → chunk-GLNZSDKO.js} +88 -50
- package/dist/chunk-GSJGPNKK.js +49 -0
- package/dist/{chunk-VT6VC4PA.js → chunk-I42JGQ2J.js} +26 -11
- package/dist/chunk-JM34JPCO.js +239 -0
- package/dist/chunk-NOO24N7W.js +11 -0
- package/dist/chunk-SF3KTPIC.js +98 -0
- package/dist/chunk-SRJIAOBN.js +244 -0
- package/dist/chunk-U6TKB3IV.js +171 -0
- package/dist/{chunk-TG64AQV4.js → chunk-VJFUB3H3.js} +99 -56
- package/dist/{client-IU2E5IOL.js → client-IN42F223.js} +4 -4
- package/dist/config-64G36TJL.js +130 -0
- package/dist/index.js +52 -25
- package/dist/{model-CC7U5XOJ.js → model-VZCYNIOG.js} +11 -10
- package/dist/{raw-TUHZQ4UB.js → raw-WJIWC6YP.js} +13 -7
- package/dist/{sync-EVEDS7YE.js → sync-NVASAISS.js} +146 -47
- package/dist/{te-agent-FLTDYV3L.js → te-agent-YV4DXEXK.js} +24 -23
- package/dist/{te-analysis-CMXADIJM.js → te-analysis-CB3DA5I5.js} +61 -13
- package/dist/{te-audience-LPZFFSXM.js → te-audience-W7SCQFC7.js} +7 -5
- package/dist/{te-common-BXQFKTB3.js → te-common-FBXVWBSI.js} +21 -9
- package/dist/{te-community-K5S3VOZY.js → te-community-LNVDZSGW.js} +6 -4
- package/dist/{te-dataops-567IMCQG.js → te-dataops-IPCSSWYH.js} +29 -27
- package/dist/{te-engage-TBUTGW5F.js → te-engage-KARARMIR.js} +6 -4
- package/dist/{te-kb-XHVBWP5E.js → te-kb-2Z2CARN3.js} +7 -5
- package/dist/{te-meta-NQ4U3VJW.js → te-meta-PDKLMWXN.js} +6 -4
- package/dist/{te-team-556APRIU.js → te-team-SW5B7DHO.js} +17 -12
- package/package.json +6 -2
- package/skills/ae-agent/SKILL.md +2 -1
- package/skills/ae-analysis/SKILL.md +1 -1
- package/skills/ae-analysis/references/list_clusters.md +4 -2
- package/skills/ae-analysis-global/SKILL.md +51 -0
- package/skills/ae-analysis-global/references/list_query_clusters.md +67 -0
- package/skills/ae-community/SKILL.md +2 -3
- package/skills/ae-dataops/SKILL.md +1 -2
- package/skills/ae-engage/SKILL.md +1 -0
- package/skills/ae-engage/references/add-approver.md +0 -1
- package/skills/ae-engage/references/add-channel.md +0 -1
- package/skills/ae-engage/references/approver-list.md +0 -1
- package/skills/ae-engage/references/cancel-query-by-request-id.md +0 -1
- package/skills/ae-engage/references/channel-detail.md +0 -1
- package/skills/ae-engage/references/channel-list.md +0 -1
- package/skills/ae-engage/references/config-channel-detail.md +0 -1
- package/skills/ae-engage/references/config-channel-list.md +0 -1
- package/skills/ae-engage/references/config-item-analysis-report.md +0 -1
- package/skills/ae-engage/references/config-item-detail.md +0 -1
- package/skills/ae-engage/references/config-item-list.md +0 -1
- package/skills/ae-engage/references/config-item-strategy-comparison.md +0 -1
- package/skills/ae-engage/references/config-item-trigger-report.md +0 -1
- package/skills/ae-engage/references/copy-config-template.md +0 -1
- package/skills/ae-engage/references/delete-channel.md +0 -1
- package/skills/ae-engage/references/delete-config-channel.md +0 -1
- package/skills/ae-engage/references/delete-config-item.md +0 -1
- package/skills/ae-engage/references/delete-flow.md +0 -1
- package/skills/ae-engage/references/flow-ab-split-node-report.md +0 -1
- package/skills/ae-engage/references/flow-detail.md +0 -1
- package/skills/ae-engage/references/flow-list.md +0 -1
- package/skills/ae-engage/references/flow-node-config-schema.md +0 -1
- package/skills/ae-engage/references/flow-node-detail-report.md +0 -1
- package/skills/ae-engage/references/flow-node-overview-report.md +0 -1
- package/skills/ae-engage/references/flow-process-report.md +0 -1
- package/skills/ae-engage/references/manage-flow.md +0 -1
- package/skills/ae-engage/references/manage-strategy.md +0 -1
- package/skills/ae-engage/references/manage-task.md +0 -1
- package/skills/ae-engage/references/modify-flow-base-info.md +0 -1
- package/skills/ae-engage/references/strategy-detail.md +0 -1
- package/skills/ae-engage/references/strategy-list.md +0 -1
- package/skills/ae-engage/references/task-data-detail.md +0 -1
- package/skills/ae-engage/references/task-data-overview.md +0 -1
- package/skills/ae-engage/references/task-detail.md +0 -1
- package/skills/ae-engage/references/task-experiment-report.md +0 -1
- package/skills/ae-engage/references/task-list.md +0 -1
- package/skills/ae-engage/references/task-metric-detail.md +0 -1
- package/skills/ae-engage/references/task-stats.md +0 -1
- package/skills/ae-engage/references/update-channel-status.md +0 -1
- package/skills/ae-engage/references/update-config-channel-status.md +0 -1
- package/skills/ae-engage/references/validate-flow-node-config.md +0 -1
- package/skills/ae-engage/references/whitelist-list.md +0 -1
- package/skills/ae-kb/SKILL.md +1 -0
- package/skills/ae-team/SKILL.md +1 -1
- package/dist/auth-W2JZ3DKA.js +0 -170
- package/dist/chunk-BI5ZVD6T.js +0 -186
- package/dist/chunk-MNHE6SSI.js +0 -79
- package/dist/chunk-MWWYGZ76.js +0 -130
- package/dist/chunk-SRRUNQIM.js +0 -312
- package/dist/config-WH3R3IRY.js +0 -258
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
|
-
|
|
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
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
printError,
|
|
3
|
+
printOutput
|
|
4
|
+
} from "./chunk-CLJF7MQA.js";
|
|
5
|
+
import {
|
|
6
|
+
clearMcpToken,
|
|
7
|
+
setMcpTokenManual,
|
|
8
|
+
validateMcpToken
|
|
9
|
+
} from "./chunk-GLNZSDKO.js";
|
|
10
|
+
import "./chunk-GSJGPNKK.js";
|
|
11
|
+
import "./chunk-NOO24N7W.js";
|
|
12
|
+
import {
|
|
13
|
+
clearToken,
|
|
14
|
+
getAuthStatus,
|
|
15
|
+
resolveHost,
|
|
16
|
+
setTokenManual,
|
|
17
|
+
validateToken
|
|
18
|
+
} from "./chunk-U6TKB3IV.js";
|
|
19
|
+
import {
|
|
20
|
+
clear,
|
|
21
|
+
load,
|
|
22
|
+
save
|
|
23
|
+
} from "./chunk-JM34JPCO.js";
|
|
24
|
+
import {
|
|
25
|
+
loadConfig,
|
|
26
|
+
logger,
|
|
27
|
+
saveConfig
|
|
28
|
+
} from "./chunk-SRJIAOBN.js";
|
|
29
|
+
|
|
30
|
+
// src/core/device-auth.ts
|
|
31
|
+
import { execFileSync } from "child_process";
|
|
32
|
+
var DeviceFlowUnsupportedError = class extends Error {
|
|
33
|
+
constructor() {
|
|
34
|
+
super("This AE server does not support device code login (it may be an older server version).");
|
|
35
|
+
this.name = "DeviceFlowUnsupportedError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function looksLikeMissingEndpoint(status, contentType, body) {
|
|
39
|
+
if (status === 404) return true;
|
|
40
|
+
if (contentType && contentType.toLowerCase().includes("text/html")) return true;
|
|
41
|
+
const head = body.trimStart().toLowerCase();
|
|
42
|
+
return head.startsWith("<!doctype html") || head.startsWith("<html");
|
|
43
|
+
}
|
|
44
|
+
function openBrowser(url, _execFile) {
|
|
45
|
+
let parsedUrl;
|
|
46
|
+
try {
|
|
47
|
+
parsedUrl = new URL(url);
|
|
48
|
+
} catch {
|
|
49
|
+
logger.warn(`openBrowser: invalid URL, not opening`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
53
|
+
logger.warn(`openBrowser: unsafe protocol "${parsedUrl.protocol}", not opening`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const safeLogUrl = `${parsedUrl.origin}${parsedUrl.pathname}`;
|
|
57
|
+
const exec = _execFile ?? execFileSync;
|
|
58
|
+
try {
|
|
59
|
+
if (process.platform === "darwin") {
|
|
60
|
+
exec("open", [url], { timeout: 5e3 });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (process.platform === "win32") {
|
|
64
|
+
exec("cmd", ["/c", "start", "", url], { timeout: 5e3 });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
exec("xdg-open", [url], { timeout: 5e3 });
|
|
68
|
+
return true;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
logger.warn(`openBrowser: failed to open ${safeLogUrl}: ${e.message}`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function authorizeDevice(host) {
|
|
75
|
+
const base = host.replace(/\/+$/, "");
|
|
76
|
+
const url = `${base}/api/auth/device/authorize`;
|
|
77
|
+
let resp;
|
|
78
|
+
try {
|
|
79
|
+
resp = await fetch(url, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "Content-Type": "application/json" }
|
|
82
|
+
});
|
|
83
|
+
} catch (e) {
|
|
84
|
+
throw new Error(`Device authorize request failed: ${e.message}`);
|
|
85
|
+
}
|
|
86
|
+
const contentType = resp.headers.get("content-type");
|
|
87
|
+
const text = await resp.text().catch(() => "");
|
|
88
|
+
if (looksLikeMissingEndpoint(resp.status, contentType, text)) {
|
|
89
|
+
throw new DeviceFlowUnsupportedError();
|
|
90
|
+
}
|
|
91
|
+
if (!resp.ok) {
|
|
92
|
+
throw new Error(`Device authorize returned HTTP ${resp.status}: ${text}`);
|
|
93
|
+
}
|
|
94
|
+
let body;
|
|
95
|
+
try {
|
|
96
|
+
body = JSON.parse(text);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error("Device authorize returned invalid JSON");
|
|
99
|
+
}
|
|
100
|
+
if (!body.device_code || !body.user_code) {
|
|
101
|
+
throw new Error("Device authorize response missing required fields");
|
|
102
|
+
}
|
|
103
|
+
return body;
|
|
104
|
+
}
|
|
105
|
+
async function pollDeviceToken(host, deviceCode) {
|
|
106
|
+
const base = host.replace(/\/+$/, "");
|
|
107
|
+
const url = `${base}/api/auth/device/token`;
|
|
108
|
+
let resp;
|
|
109
|
+
try {
|
|
110
|
+
resp = await fetch(url, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { "Content-Type": "application/json" },
|
|
113
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
114
|
+
});
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return { status: "error", message: `Network error: ${e.message}` };
|
|
117
|
+
}
|
|
118
|
+
if (resp.status === 200) {
|
|
119
|
+
let body;
|
|
120
|
+
try {
|
|
121
|
+
body = await resp.json();
|
|
122
|
+
} catch {
|
|
123
|
+
return { status: "error", message: "Token response is not valid JSON" };
|
|
124
|
+
}
|
|
125
|
+
if (!body.access_token) {
|
|
126
|
+
return { status: "error", message: "Token response missing access_token" };
|
|
127
|
+
}
|
|
128
|
+
return { status: "approved", tokens: body };
|
|
129
|
+
}
|
|
130
|
+
if (resp.status === 400) {
|
|
131
|
+
let errorBody;
|
|
132
|
+
try {
|
|
133
|
+
errorBody = await resp.json();
|
|
134
|
+
} catch {
|
|
135
|
+
errorBody = {};
|
|
136
|
+
}
|
|
137
|
+
const err = errorBody.error;
|
|
138
|
+
if (err === "authorization_pending") return { status: "pending" };
|
|
139
|
+
if (err === "slow_down") return { status: "slow_down" };
|
|
140
|
+
if (err === "expired_token") return { status: "expired" };
|
|
141
|
+
return { status: "error", message: `Unknown error: ${err ?? "unknown"}` };
|
|
142
|
+
}
|
|
143
|
+
const contentType = resp.headers.get("content-type");
|
|
144
|
+
const text = await resp.text().catch(() => "");
|
|
145
|
+
if (looksLikeMissingEndpoint(resp.status, contentType, text)) {
|
|
146
|
+
return { status: "unsupported" };
|
|
147
|
+
}
|
|
148
|
+
return { status: "error", message: `Unexpected HTTP ${resp.status} from token endpoint` };
|
|
149
|
+
}
|
|
150
|
+
function buildVerificationUrl(host, userCode) {
|
|
151
|
+
return `${host.replace(/\/+$/, "")}/device-activate?user_code=${encodeURIComponent(userCode)}`;
|
|
152
|
+
}
|
|
153
|
+
async function pollDeviceFlow(host, deviceCode, options = {}, onStatus) {
|
|
154
|
+
const emit = (msg) => {
|
|
155
|
+
if (onStatus) onStatus(msg);
|
|
156
|
+
};
|
|
157
|
+
let intervalMs = options.intervalMs ?? 5e3;
|
|
158
|
+
const expiresIn = options.expiresIn ?? 300;
|
|
159
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
160
|
+
let pollCount = 0;
|
|
161
|
+
while (Date.now() < deadline) {
|
|
162
|
+
await sleep(intervalMs);
|
|
163
|
+
pollCount++;
|
|
164
|
+
const result = await pollDeviceToken(host, deviceCode);
|
|
165
|
+
if (result.status === "approved") {
|
|
166
|
+
emit(`Authorization successful! (polled ${pollCount} time(s))`);
|
|
167
|
+
return result.tokens;
|
|
168
|
+
}
|
|
169
|
+
if (result.status === "pending") {
|
|
170
|
+
if (pollCount % 5 === 0) {
|
|
171
|
+
const remaining = Math.max(0, Math.round((deadline - Date.now()) / 1e3));
|
|
172
|
+
emit(`Waiting for authorization... (expires in ${remaining}s)`);
|
|
173
|
+
}
|
|
174
|
+
logger.info(`device-auth: poll #${pollCount} pending`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (result.status === "slow_down") {
|
|
178
|
+
intervalMs += 5e3;
|
|
179
|
+
logger.info(`device-auth: slow_down received, new interval=${intervalMs}ms`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (result.status === "expired") {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Device code has expired. Please run ae-cli auth login again to restart the authorization flow.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (result.status === "unsupported") {
|
|
188
|
+
throw new DeviceFlowUnsupportedError();
|
|
189
|
+
}
|
|
190
|
+
if (result.status === "error") {
|
|
191
|
+
logger.warn(`device-auth: poll error: ${result.message}`);
|
|
192
|
+
emit(`Poll error: ${result.message}`);
|
|
193
|
+
if (result.message.includes("HTTP 401") || result.message.includes("HTTP 403")) {
|
|
194
|
+
throw new Error(`Authorization denied (${result.message}). Please check your configuration or run ae-cli auth login again.`);
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Authorization timed out (not completed within ${expiresIn}s). Please run ae-cli auth login again.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
async function runDeviceFlow(host, options = {}, onStatus) {
|
|
204
|
+
const emit = (msg) => {
|
|
205
|
+
if (onStatus) onStatus(msg);
|
|
206
|
+
};
|
|
207
|
+
emit("Requesting authorization code from server...");
|
|
208
|
+
const auth = await authorizeDevice(host);
|
|
209
|
+
const userCode = auth.user_code;
|
|
210
|
+
const verifyUrl = buildVerificationUrl(host, userCode);
|
|
211
|
+
const expiresIn = auth.expires_in ?? 300;
|
|
212
|
+
emit("");
|
|
213
|
+
emit(`Verification code: ${userCode}`);
|
|
214
|
+
emit(`Open the following link in your browser to complete authorization:`);
|
|
215
|
+
emit(` ${verifyUrl}`);
|
|
216
|
+
emit("");
|
|
217
|
+
if (!options.noBrowser) {
|
|
218
|
+
const opened = openBrowser(verifyUrl);
|
|
219
|
+
if (opened) {
|
|
220
|
+
emit("(Attempted to open the link in your browser)");
|
|
221
|
+
} else {
|
|
222
|
+
emit("(Could not open browser automatically; please copy the link above manually)");
|
|
223
|
+
}
|
|
224
|
+
emit("");
|
|
225
|
+
}
|
|
226
|
+
return pollDeviceFlow(host, auth.device_code, { intervalMs: (auth.interval ?? 5) * 1e3, expiresIn }, onStatus);
|
|
227
|
+
}
|
|
228
|
+
function sleep(ms) {
|
|
229
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/commands/auth.ts
|
|
233
|
+
var HOST_OPTION_DESC = "Override active AE host URL (e.g., https://ta.thinkingdata.cn)";
|
|
234
|
+
function resolveAuthHost(program, opts) {
|
|
235
|
+
return resolveHost(opts.host || program.opts().host);
|
|
236
|
+
}
|
|
237
|
+
function persistDeviceTokens(host, tokens) {
|
|
238
|
+
const DEFAULT_EXPIRES_IN = 72e3;
|
|
239
|
+
const MAX_EXPIRES_IN = 86400 * 365;
|
|
240
|
+
let expiresInSeconds = tokens.expires_in ?? DEFAULT_EXPIRES_IN;
|
|
241
|
+
if (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0 || expiresInSeconds > MAX_EXPIRES_IN) {
|
|
242
|
+
logger.warn(`auth login: expires_in=${tokens.expires_in} is out of range, falling back to ${DEFAULT_EXPIRES_IN}s`);
|
|
243
|
+
expiresInSeconds = DEFAULT_EXPIRES_IN;
|
|
244
|
+
}
|
|
245
|
+
const expiresAt = new Date(Date.now() + expiresInSeconds * 1e3).toISOString();
|
|
246
|
+
save(host, {
|
|
247
|
+
accessToken: tokens.access_token,
|
|
248
|
+
refreshToken: tokens.refresh_token ?? "",
|
|
249
|
+
accessExpiresAt: expiresAt,
|
|
250
|
+
mcpToken: tokens.mcp_token ?? void 0
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function loginSummary(host, tokens) {
|
|
254
|
+
return {
|
|
255
|
+
authenticated: true,
|
|
256
|
+
host,
|
|
257
|
+
token: tokens.access_token.slice(0, 8) + "...",
|
|
258
|
+
mcpToken: tokens.mcp_token ? tokens.mcp_token.slice(0, 8) + "..." : null
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function resolveTeClaudeBase(host) {
|
|
262
|
+
const override = process.env.TE_CLAUDE_BASE_URL;
|
|
263
|
+
if (override) return override.replace(/\/+$/, "");
|
|
264
|
+
const base = host.replace(/\/+$/, "");
|
|
265
|
+
const basePath = process.env.TE_CLAUDE_BASE_PATH || "/agent";
|
|
266
|
+
return base.endsWith(basePath) ? base : base + basePath;
|
|
267
|
+
}
|
|
268
|
+
function registerAuth(program) {
|
|
269
|
+
const auth = program.command("auth").description("Authentication management");
|
|
270
|
+
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) => {
|
|
271
|
+
const host = resolveAuthHost(program, opts);
|
|
272
|
+
if (!host) {
|
|
273
|
+
printError("config", "No AE host configured.", "Run: ae-cli config set-host <url>");
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
const config = loadConfig();
|
|
277
|
+
if (!config.hosts[host]) {
|
|
278
|
+
config.hosts[host] = { label: host };
|
|
279
|
+
if (!config.activeHost) {
|
|
280
|
+
config.activeHost = host;
|
|
281
|
+
}
|
|
282
|
+
saveConfig(config);
|
|
283
|
+
logger.info(`Host config auto-saved for ${host}`);
|
|
284
|
+
process.stderr.write(`[ae-cli] Config saved for ${host}
|
|
285
|
+
`);
|
|
286
|
+
}
|
|
287
|
+
const noBrowser = !opts.browser;
|
|
288
|
+
const fmt = program.opts().format || "json";
|
|
289
|
+
const teClaudeBase = resolveTeClaudeBase(host);
|
|
290
|
+
const emit = (msg) => process.stderr.write(`[ae-cli] ${msg}
|
|
291
|
+
`);
|
|
292
|
+
try {
|
|
293
|
+
if (opts.deviceCode) {
|
|
294
|
+
const tokens2 = await pollDeviceFlow(teClaudeBase, opts.deviceCode, {}, emit);
|
|
295
|
+
persistDeviceTokens(host, tokens2);
|
|
296
|
+
logger.info(`Device flow login successful for ${host} (resumed)`);
|
|
297
|
+
emit("Login successful! Token saved securely.");
|
|
298
|
+
printOutput(loginSummary(host, tokens2), fmt);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (opts.wait === false) {
|
|
302
|
+
const authResp = await authorizeDevice(teClaudeBase);
|
|
303
|
+
printOutput(
|
|
304
|
+
{
|
|
305
|
+
device_code: authResp.device_code,
|
|
306
|
+
user_code: authResp.user_code,
|
|
307
|
+
verification_url: buildVerificationUrl(teClaudeBase, authResp.user_code),
|
|
308
|
+
interval: authResp.interval,
|
|
309
|
+
expires_in: authResp.expires_in,
|
|
310
|
+
next: `Open verification_url in a browser to authorize, then run: ae-cli auth login --device-code ${authResp.device_code}`
|
|
311
|
+
},
|
|
312
|
+
fmt
|
|
313
|
+
);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const tokens = await runDeviceFlow(teClaudeBase, { noBrowser }, emit);
|
|
317
|
+
persistDeviceTokens(host, tokens);
|
|
318
|
+
logger.info(`Device flow login successful for ${host}`);
|
|
319
|
+
emit("Login successful! Token saved securely.");
|
|
320
|
+
printOutput(loginSummary(host, tokens), fmt);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
if (err instanceof DeviceFlowUnsupportedError) {
|
|
323
|
+
printError(
|
|
324
|
+
"auth",
|
|
325
|
+
err.message,
|
|
326
|
+
"Use `ae-cli auth set-token <token>` to authenticate, or upgrade the AE server to a version that supports device login."
|
|
327
|
+
);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
printError("auth", err.message);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
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) => {
|
|
335
|
+
const host = resolveAuthHost(program, opts);
|
|
336
|
+
if (!host) {
|
|
337
|
+
printError("config", "No AE host configured.", "Run: ae-cli config set-host <url>");
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
const config = loadConfig();
|
|
341
|
+
if (!config.hosts[host]) {
|
|
342
|
+
config.hosts[host] = { label: host };
|
|
343
|
+
if (!config.activeHost) {
|
|
344
|
+
config.activeHost = host;
|
|
345
|
+
}
|
|
346
|
+
saveConfig(config);
|
|
347
|
+
process.stderr.write(`[ae-cli] Config saved for ${host}
|
|
348
|
+
`);
|
|
349
|
+
printOutput({ saved: true, config }, program.opts().format || "json");
|
|
350
|
+
}
|
|
351
|
+
process.stderr.write(`[ae-cli] Validating token...
|
|
352
|
+
`);
|
|
353
|
+
const isValid = await validateToken(token, host);
|
|
354
|
+
if (!isValid) {
|
|
355
|
+
logger.warn(`Token validation failed for ${host}`);
|
|
356
|
+
process.stderr.write(`[ae-cli] Token validation failed
|
|
357
|
+
`);
|
|
358
|
+
printError("auth", "Invalid token", "Please check your token and try again");
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
setTokenManual(token, host);
|
|
362
|
+
logger.info(`Token manually set and validated for ${host}`);
|
|
363
|
+
process.stderr.write(`[ae-cli] Token verified and saved for ${host}
|
|
364
|
+
`);
|
|
365
|
+
printOutput({ saved: true, host, verified: true }, program.opts().format || "json");
|
|
366
|
+
});
|
|
367
|
+
auth.command("status").description("Show authentication status for a host (--host or active host)").option("--host <url>", HOST_OPTION_DESC).action(async (opts) => {
|
|
368
|
+
const host = resolveAuthHost(program, opts);
|
|
369
|
+
if (!host) {
|
|
370
|
+
printOutput({ authenticated: false, host: "(none)", hint: "Run: ae-cli config set-host <url>" }, program.opts().format || "json");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const secureEntry = load(host);
|
|
374
|
+
if (secureEntry) {
|
|
375
|
+
const pastStaticExpiry = new Date(secureEntry.accessExpiresAt).getTime() < Date.now();
|
|
376
|
+
printOutput(
|
|
377
|
+
{
|
|
378
|
+
authenticated: true,
|
|
379
|
+
host,
|
|
380
|
+
source: "secure-store",
|
|
381
|
+
accessExpiresAt: secureEntry.accessExpiresAt,
|
|
382
|
+
accessExpiresAtNote: "advisory: access token auto-slides on use server-side; re-login only if a command returns an auth error",
|
|
383
|
+
pastStaticExpiry,
|
|
384
|
+
hasMcpToken: !!secureEntry.mcpToken
|
|
385
|
+
},
|
|
386
|
+
program.opts().format || "json"
|
|
387
|
+
);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const tokenStatus = getAuthStatus(host);
|
|
391
|
+
const authenticated = tokenStatus.authenticated;
|
|
392
|
+
printOutput({ authenticated, host, source: tokenStatus.source ?? "none" }, program.opts().format || "json");
|
|
393
|
+
});
|
|
394
|
+
auth.command("logout").description("Clear stored token for a host (--host or active host)").option("--host <url>", HOST_OPTION_DESC).action((opts) => {
|
|
395
|
+
const host = resolveAuthHost(program, opts);
|
|
396
|
+
if (!host) {
|
|
397
|
+
printError("config", "No AE host configured.", "Run: ae-cli config set-host <url>");
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
clearToken(host);
|
|
401
|
+
clear(host);
|
|
402
|
+
process.stderr.write(`[ae-cli] Token cleared for ${host}
|
|
403
|
+
`);
|
|
404
|
+
printOutput({ cleared: true, host }, program.opts().format || "json");
|
|
405
|
+
});
|
|
406
|
+
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) => {
|
|
407
|
+
const host = resolveAuthHost(program, opts);
|
|
408
|
+
if (!host) {
|
|
409
|
+
printError("config", "No AE host configured.", "Use --host <url> or run: ae-cli config set-host <url>");
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const config = loadConfig();
|
|
413
|
+
if (!config.hosts[host]) {
|
|
414
|
+
config.hosts[host] = { label: host };
|
|
415
|
+
if (!config.activeHost) {
|
|
416
|
+
config.activeHost = host;
|
|
417
|
+
}
|
|
418
|
+
saveConfig(config);
|
|
419
|
+
process.stderr.write(`[ae-cli] Host config saved for ${host}
|
|
420
|
+
`);
|
|
421
|
+
}
|
|
422
|
+
process.stderr.write(`[ae-cli] Validating MCP token...
|
|
423
|
+
`);
|
|
424
|
+
const isValid = await validateMcpToken(token, host);
|
|
425
|
+
if (!isValid) {
|
|
426
|
+
process.stderr.write(`[ae-cli] MCP token validation failed
|
|
427
|
+
`);
|
|
428
|
+
printError("auth", "Invalid MCP token", "Please check your MCP token and try again");
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
setMcpTokenManual(token, host);
|
|
432
|
+
process.stderr.write(`[ae-cli] MCP token verified and saved for ${host}
|
|
433
|
+
`);
|
|
434
|
+
printOutput({ saved: true, host, verified: true, type: "mcp-token" }, program.opts().format || "json");
|
|
435
|
+
});
|
|
436
|
+
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) => {
|
|
437
|
+
if (opts.all) {
|
|
438
|
+
clearMcpToken();
|
|
439
|
+
process.stderr.write(`[ae-cli] All MCP tokens cleared
|
|
440
|
+
`);
|
|
441
|
+
printOutput({ cleared: true, type: "all-mcp-tokens" }, program.opts().format || "json");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const host = resolveAuthHost(program, opts);
|
|
445
|
+
if (!host) {
|
|
446
|
+
printError("config", "No AE host configured.", "Use --host <url> or run: ae-cli config set-host <url>");
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
clearMcpToken(host);
|
|
450
|
+
process.stderr.write(`[ae-cli] MCP token cleared for ${host}
|
|
451
|
+
`);
|
|
452
|
+
printOutput({ cleared: true, host }, program.opts().format || "json");
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
export {
|
|
456
|
+
registerAuth
|
|
457
|
+
};
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
saveToken,
|
|
8
8
|
setTokenManual,
|
|
9
9
|
validateToken
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-U6TKB3IV.js";
|
|
11
|
+
import "./chunk-JM34JPCO.js";
|
|
12
|
+
import "./chunk-SRJIAOBN.js";
|
|
13
13
|
export {
|
|
14
14
|
clearToken,
|
|
15
15
|
getAuthStatus,
|