@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.
- package/dist/auth-5NSDQFQB.js +454 -0
- package/dist/{auth-TXFJHPXU.js → auth-H2376DEF.js} +2 -3
- package/dist/chunk-AD5V3ZPJ.js +217 -0
- package/dist/chunk-CAQYQA4R.js +399 -0
- package/dist/{chunk-B4UL2VIJ.js → chunk-CLJF7MQA.js} +1 -1
- package/dist/chunk-GSJGPNKK.js +49 -0
- package/dist/{chunk-6T7245YE.js → chunk-IMBKVXKY.js} +8 -8
- package/dist/chunk-KAEZTSXN.js +99 -0
- package/dist/{chunk-VT6VC4PA.js → chunk-LU7SXK4Q.js} +24 -11
- package/dist/chunk-OJDNO5QY.js +63 -0
- package/dist/chunk-SRJIAOBN.js +244 -0
- package/dist/{chunk-2NGWYMLB.js → chunk-WCHI7725.js} +45 -50
- package/dist/{client-IU2E5IOL.js → client-FCMK3XEK.js} +3 -4
- package/dist/config-64G36TJL.js +130 -0
- package/dist/index.js +28 -23
- package/dist/{model-CC7U5XOJ.js → model-EZ6K7FXI.js} +15 -12
- package/dist/{raw-TUHZQ4UB.js → raw-VEF3D6UD.js} +11 -7
- package/dist/{sync-EVEDS7YE.js → sync-NRODEVNH.js} +36 -34
- package/dist/{te-agent-FLTDYV3L.js → te-agent-7U7JLJKU.js} +25 -23
- package/dist/{te-analysis-CMXADIJM.js → te-analysis-A2OSGHQ4.js} +59 -13
- package/dist/{te-audience-LPZFFSXM.js → te-audience-LI67PPGO.js} +5 -5
- package/dist/{te-common-BXQFKTB3.js → te-common-35WVU3C5.js} +19 -9
- package/dist/{te-community-K5S3VOZY.js → te-community-LEAP6PWT.js} +4 -4
- package/dist/{te-dataops-567IMCQG.js → te-dataops-N2LGZBOX.js} +4 -4
- package/dist/{te-engage-TBUTGW5F.js → te-engage-H7C72Z46.js} +4 -4
- package/dist/{te-kb-XHVBWP5E.js → te-kb-I2OKTBAI.js} +5 -5
- package/dist/{te-meta-NQ4U3VJW.js → te-meta-HBTIEDRO.js} +4 -4
- package/dist/{te-team-556APRIU.js → te-team-EM5OWRQL.js} +7 -7
- package/package.json +6 -2
- package/skills/ae-agent/SKILL.md +1 -0
- 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/chunk-TG64AQV4.js +0 -262
- package/dist/config-WH3R3IRY.js +0 -258
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getActiveHost,
|
|
3
|
+
getConfigDir,
|
|
4
|
+
logger,
|
|
5
|
+
safeJsonParse,
|
|
6
|
+
safeReadJsonFile
|
|
7
|
+
} from "./chunk-SRJIAOBN.js";
|
|
8
|
+
|
|
9
|
+
// src/core/auth.ts
|
|
10
|
+
import fs2 from "fs";
|
|
11
|
+
import path2 from "path";
|
|
12
|
+
|
|
13
|
+
// src/core/secure-store.ts
|
|
14
|
+
import crypto from "crypto";
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import os from "os";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import { execFileSync } from "child_process";
|
|
19
|
+
var STORE_DIR_SUFFIX = "secure-tokens";
|
|
20
|
+
var SCRYPT_SALT = Buffer.from("ae-cli-secure-store-v1");
|
|
21
|
+
var SCRYPT_N = 16384;
|
|
22
|
+
var SCRYPT_R = 8;
|
|
23
|
+
var SCRYPT_P = 1;
|
|
24
|
+
var KEY_LEN = 32;
|
|
25
|
+
var NONCE_LEN = 12;
|
|
26
|
+
var REFRESH_TOKEN_PATH = "/v1/oauth/refreshForToken";
|
|
27
|
+
function getMachineId() {
|
|
28
|
+
if (process.platform === "darwin") {
|
|
29
|
+
try {
|
|
30
|
+
const out = execFileSync(
|
|
31
|
+
"ioreg",
|
|
32
|
+
["-rd1", "-c", "IOPlatformExpertDevice"],
|
|
33
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
34
|
+
);
|
|
35
|
+
const m = out.match(/IOPlatformUUID.*?=.*?"([0-9A-F-]{36})"/i);
|
|
36
|
+
if (m?.[1]) return `darwin:${m[1]}`;
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (process.platform === "linux") {
|
|
41
|
+
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
42
|
+
try {
|
|
43
|
+
const id = fs.readFileSync(p, "utf8").trim();
|
|
44
|
+
if (id) return `linux:${id}`;
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (process.platform === "win32") {
|
|
50
|
+
try {
|
|
51
|
+
const out = execFileSync(
|
|
52
|
+
"reg",
|
|
53
|
+
["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"],
|
|
54
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
55
|
+
);
|
|
56
|
+
const m = out.match(/MachineGuid\s+REG_SZ\s+([0-9a-f-]+)/i);
|
|
57
|
+
if (m?.[1]) return `win32:${m[1]}`;
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const fallback = `${os.homedir()}|${os.hostname()}|${os.platform()}`;
|
|
62
|
+
logger.warn("secure-store: using fallback machine-id (HOME+hostname+platform)");
|
|
63
|
+
return `fallback:${fallback}`;
|
|
64
|
+
}
|
|
65
|
+
var _cachedMachineId;
|
|
66
|
+
function getCachedMachineId() {
|
|
67
|
+
if (!_cachedMachineId) _cachedMachineId = getMachineId();
|
|
68
|
+
return _cachedMachineId;
|
|
69
|
+
}
|
|
70
|
+
function deriveKey(machineId) {
|
|
71
|
+
return crypto.scryptSync(machineId, SCRYPT_SALT, KEY_LEN, {
|
|
72
|
+
N: SCRYPT_N,
|
|
73
|
+
r: SCRYPT_R,
|
|
74
|
+
p: SCRYPT_P
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
var _cachedKey;
|
|
78
|
+
function getKey() {
|
|
79
|
+
if (!_cachedKey) _cachedKey = deriveKey(getCachedMachineId());
|
|
80
|
+
return _cachedKey;
|
|
81
|
+
}
|
|
82
|
+
function encrypt(plaintext) {
|
|
83
|
+
const key = getKey();
|
|
84
|
+
const nonce = crypto.randomBytes(NONCE_LEN);
|
|
85
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, nonce);
|
|
86
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
87
|
+
const tag = cipher.getAuthTag();
|
|
88
|
+
return {
|
|
89
|
+
nonce: nonce.toString("hex"),
|
|
90
|
+
tag: tag.toString("hex"),
|
|
91
|
+
data: encrypted.toString("hex")
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function decrypt(blob) {
|
|
95
|
+
const key = getKey();
|
|
96
|
+
const nonce = Buffer.from(blob.nonce, "hex");
|
|
97
|
+
const tag = Buffer.from(blob.tag, "hex");
|
|
98
|
+
const data = Buffer.from(blob.data, "hex");
|
|
99
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, nonce);
|
|
100
|
+
decipher.setAuthTag(tag);
|
|
101
|
+
const decrypted = Buffer.concat([decipher.update(data), decipher.final()]);
|
|
102
|
+
return decrypted.toString("utf8");
|
|
103
|
+
}
|
|
104
|
+
function storeDir() {
|
|
105
|
+
return path.join(getConfigDir(), STORE_DIR_SUFFIX);
|
|
106
|
+
}
|
|
107
|
+
function tokenFilePath(host) {
|
|
108
|
+
const safe = host.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
109
|
+
return path.join(storeDir(), `${safe}.enc.json`);
|
|
110
|
+
}
|
|
111
|
+
function ensureStoreDir() {
|
|
112
|
+
const dir = storeDir();
|
|
113
|
+
if (!fs.existsSync(dir)) {
|
|
114
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
115
|
+
try {
|
|
116
|
+
fs.chmodSync(dir, 448);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function save(host, payload) {
|
|
122
|
+
ensureStoreDir();
|
|
123
|
+
const plaintext = JSON.stringify(payload);
|
|
124
|
+
const blob = encrypt(plaintext);
|
|
125
|
+
const filePath = tokenFilePath(host);
|
|
126
|
+
fs.writeFileSync(filePath, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
127
|
+
try {
|
|
128
|
+
fs.chmodSync(filePath, 384);
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
logger.info(`secure-store: token saved for ${host}`);
|
|
132
|
+
}
|
|
133
|
+
function load(host) {
|
|
134
|
+
const filePath = tokenFilePath(host);
|
|
135
|
+
if (!fs.existsSync(filePath)) return null;
|
|
136
|
+
try {
|
|
137
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
138
|
+
const blob = safeJsonParse(raw);
|
|
139
|
+
if (!blob?.nonce || !blob?.tag || !blob?.data) {
|
|
140
|
+
logger.warn(`secure-store: malformed blob for ${host}`);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const plaintext = decrypt(blob);
|
|
144
|
+
return JSON.parse(plaintext);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
logger.error(`secure-store: decrypt failed for ${host}: ${e.message}`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function clear(host) {
|
|
151
|
+
const filePath = tokenFilePath(host);
|
|
152
|
+
if (fs.existsSync(filePath)) {
|
|
153
|
+
fs.rmSync(filePath);
|
|
154
|
+
logger.info(`secure-store: token cleared for ${host}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function getValidAccessToken(host) {
|
|
158
|
+
const payload = load(host);
|
|
159
|
+
if (!payload) {
|
|
160
|
+
throw new SecureStoreAuthError(
|
|
161
|
+
`No stored token for ${host}. Please run: ae-cli auth login`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const expiresAt = new Date(payload.accessExpiresAt).getTime();
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
const needsRefresh = expiresAt - now < 6e4;
|
|
167
|
+
if (!needsRefresh) {
|
|
168
|
+
return payload.accessToken;
|
|
169
|
+
}
|
|
170
|
+
if (payload.refreshToken) {
|
|
171
|
+
logger.info(`secure-store: access token past local expiry for ${host}, refreshing\u2026`);
|
|
172
|
+
const refreshed = await doRefresh(host, payload.refreshToken);
|
|
173
|
+
save(host, { ...refreshed, mcpToken: payload.mcpToken });
|
|
174
|
+
logger.info(`secure-store: token refreshed for ${host}`);
|
|
175
|
+
return refreshed.accessToken;
|
|
176
|
+
}
|
|
177
|
+
logger.info(`secure-store: ${host} past static local expiry, no refresh token \u2014 returning stored token, server validates (lazy)`);
|
|
178
|
+
return payload.accessToken;
|
|
179
|
+
}
|
|
180
|
+
function loadMcpToken(host) {
|
|
181
|
+
return load(host)?.mcpToken ?? null;
|
|
182
|
+
}
|
|
183
|
+
async function doRefresh(host, refreshToken) {
|
|
184
|
+
const base = host.replace(/\/+$/, "");
|
|
185
|
+
const url = `${base}${REFRESH_TOKEN_PATH}`;
|
|
186
|
+
let resp;
|
|
187
|
+
try {
|
|
188
|
+
resp = await fetch(url, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { "Content-Type": "application/json" },
|
|
191
|
+
body: JSON.stringify({ refreshToken })
|
|
192
|
+
});
|
|
193
|
+
} catch (e) {
|
|
194
|
+
throw new SecureStoreAuthError(
|
|
195
|
+
`Token refresh network error for ${host}: ${e.message}. Please run: ae-cli auth login`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (!resp.ok) {
|
|
199
|
+
throw new SecureStoreAuthError(
|
|
200
|
+
`Token refresh failed (HTTP ${resp.status}) for ${host}. Please run: ae-cli auth login`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
let body;
|
|
204
|
+
try {
|
|
205
|
+
body = safeJsonParse(await resp.text());
|
|
206
|
+
} catch {
|
|
207
|
+
throw new SecureStoreAuthError(
|
|
208
|
+
`Token refresh returned invalid JSON for ${host}. Please run: ae-cli auth login`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (body.return_code !== 0) {
|
|
212
|
+
throw new SecureStoreAuthError(
|
|
213
|
+
`Token refresh error (${body.return_code}: ${body.return_message ?? "unknown"}) for ${host}. Please run: ae-cli auth login`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const d = body.data;
|
|
217
|
+
const newAccess = d?.accessToken ?? d?.access_token ?? "";
|
|
218
|
+
const newRefresh = d?.refreshToken ?? d?.refresh_token ?? refreshToken;
|
|
219
|
+
const expiresIn = d?.expiresIn ?? d?.expires_in ?? 72e3;
|
|
220
|
+
if (!newAccess) {
|
|
221
|
+
throw new SecureStoreAuthError(
|
|
222
|
+
`Token refresh returned empty accessToken for ${host}. Please run: ae-cli auth login`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
accessToken: newAccess,
|
|
227
|
+
refreshToken: newRefresh,
|
|
228
|
+
accessExpiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString()
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
var SecureStoreAuthError = class extends Error {
|
|
232
|
+
constructor(message) {
|
|
233
|
+
super(message);
|
|
234
|
+
this.name = "SecureStoreAuthError";
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
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
|
+
export {
|
|
386
|
+
save,
|
|
387
|
+
load,
|
|
388
|
+
clear,
|
|
389
|
+
loadMcpToken,
|
|
390
|
+
SecureStoreAuthError,
|
|
391
|
+
resolveHost,
|
|
392
|
+
validateToken,
|
|
393
|
+
loadToken,
|
|
394
|
+
saveToken,
|
|
395
|
+
clearToken,
|
|
396
|
+
setTokenManual,
|
|
397
|
+
getAuthStatus,
|
|
398
|
+
getToken
|
|
399
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSandboxClusterInfoFilePath,
|
|
3
|
+
logger,
|
|
4
|
+
safeReadJsonFile
|
|
5
|
+
} from "./chunk-SRJIAOBN.js";
|
|
6
|
+
|
|
7
|
+
// src/core/cluster-info.ts
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
var GLOBAL_QUERY_CONFIG_KEY = "sw_cfg_enable_global_query";
|
|
11
|
+
var CLUSTER_INFO_FILE_ENV = "AE_CLUSTER_INFO_FILE";
|
|
12
|
+
function getClusterInfoFilePath() {
|
|
13
|
+
return process.env[CLUSTER_INFO_FILE_ENV] || getSandboxClusterInfoFilePath();
|
|
14
|
+
}
|
|
15
|
+
function loadClusterInfo() {
|
|
16
|
+
const file = getClusterInfoFilePath();
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(file)) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
const raw = safeReadJsonFile(file);
|
|
22
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
23
|
+
} catch (err) {
|
|
24
|
+
logger.warn(`Failed to load cluster info from ${file}: ${err.message}`);
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isGlobalQueryModeEnabled() {
|
|
29
|
+
return loadClusterInfo()[GLOBAL_QUERY_CONFIG_KEY] === true;
|
|
30
|
+
}
|
|
31
|
+
function setGlobalQueryModeEnabled(enabled) {
|
|
32
|
+
const file = getClusterInfoFilePath();
|
|
33
|
+
const dir = path.dirname(file);
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
35
|
+
const next = { [GLOBAL_QUERY_CONFIG_KEY]: enabled };
|
|
36
|
+
fs.writeFileSync(file, JSON.stringify(next, null, 2));
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
function getAnalysisMappingPathForClusterMode(enabled = isGlobalQueryModeEnabled()) {
|
|
40
|
+
return enabled ? "analysis-global" : "analysis";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
GLOBAL_QUERY_CONFIG_KEY,
|
|
45
|
+
getClusterInfoFilePath,
|
|
46
|
+
isGlobalQueryModeEnabled,
|
|
47
|
+
setGlobalQueryModeEnabled,
|
|
48
|
+
getAnalysisMappingPathForClusterMode
|
|
49
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/core/multiselect.ts
|
|
2
2
|
var MultiselectCancelled = class extends Error {
|
|
3
3
|
constructor() {
|
|
4
|
-
super("
|
|
4
|
+
super("Selection cancelled by user");
|
|
5
5
|
this.name = "MultiselectCancelled";
|
|
6
6
|
}
|
|
7
7
|
};
|
|
@@ -27,7 +27,7 @@ function promptMultiselect(opts) {
|
|
|
27
27
|
const stdin = process.stdin;
|
|
28
28
|
if (!stdin.isTTY) {
|
|
29
29
|
return Promise.reject(
|
|
30
|
-
new Error("ae-cli sync
|
|
30
|
+
new Error("ae-cli sync requires a TTY for multi-select; please run in an interactive terminal")
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
const rows = buildRows(items);
|
|
@@ -48,7 +48,7 @@ function promptMultiselect(opts) {
|
|
|
48
48
|
if (renderCount > 0) {
|
|
49
49
|
stderr.write(`\x1B[${totalLines()}A`);
|
|
50
50
|
}
|
|
51
|
-
stderr.write(`${title} (space
|
|
51
|
+
stderr.write(`${title} (space: select \xB7 a: select all/none \xB7 enter: confirm \xB7 q: cancel)\x1B[K
|
|
52
52
|
`);
|
|
53
53
|
stderr.write(`\x1B[K
|
|
54
54
|
`);
|
|
@@ -128,10 +128,10 @@ function promptMultiselect(opts) {
|
|
|
128
128
|
}
|
|
129
129
|
function promptSingleCheckboxSelect(opts) {
|
|
130
130
|
const { title, items } = opts;
|
|
131
|
-
if (items.length === 0) return Promise.reject(new Error("
|
|
131
|
+
if (items.length === 0) return Promise.reject(new Error("No items to select"));
|
|
132
132
|
const stderr = process.stderr;
|
|
133
133
|
const stdin = process.stdin;
|
|
134
|
-
if (!stdin.isTTY) return Promise.reject(new Error("
|
|
134
|
+
if (!stdin.isTTY) return Promise.reject(new Error("A TTY is required for single-select"));
|
|
135
135
|
const preselectedIndex = items.findIndex((item) => item.preselected);
|
|
136
136
|
let cursor = preselectedIndex >= 0 ? preselectedIndex : 0;
|
|
137
137
|
let selected = preselectedIndex >= 0 ? preselectedIndex : null;
|
|
@@ -139,7 +139,7 @@ function promptSingleCheckboxSelect(opts) {
|
|
|
139
139
|
const lines = items.length + 2;
|
|
140
140
|
function render() {
|
|
141
141
|
if (renderCount > 0) stderr.write(`\x1B[${lines}A`);
|
|
142
|
-
stderr.write(`${title} (space
|
|
142
|
+
stderr.write(`${title} (space: select \xB7 enter: confirm \xB7 q: cancel)\x1B[K
|
|
143
143
|
`);
|
|
144
144
|
stderr.write(`\x1B[K
|
|
145
145
|
`);
|
|
@@ -193,10 +193,10 @@ function promptSingleCheckboxSelect(opts) {
|
|
|
193
193
|
}
|
|
194
194
|
function promptSingleSelect(opts) {
|
|
195
195
|
const { title, items } = opts;
|
|
196
|
-
if (items.length === 0) return Promise.reject(new Error("
|
|
196
|
+
if (items.length === 0) return Promise.reject(new Error("No items to select"));
|
|
197
197
|
const stderr = process.stderr;
|
|
198
198
|
const stdin = process.stdin;
|
|
199
|
-
if (!stdin.isTTY) return Promise.reject(new Error("
|
|
199
|
+
if (!stdin.isTTY) return Promise.reject(new Error("A TTY is required for single-select"));
|
|
200
200
|
let cursor = 0;
|
|
201
201
|
let renderCount = 0;
|
|
202
202
|
const lines = items.length + 1;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSandboxRuntimeRoot
|
|
3
|
+
} from "./chunk-SRJIAOBN.js";
|
|
4
|
+
|
|
5
|
+
// src/core/te-agent-credentials.ts
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
function getCredentialsPath() {
|
|
10
|
+
return join(process.env.HOME || homedir(), ".te-agent", "credentials.json");
|
|
11
|
+
}
|
|
12
|
+
function getPersistentEnvPath() {
|
|
13
|
+
return join(getSandboxRuntimeRoot(), ".env");
|
|
14
|
+
}
|
|
15
|
+
function parseDotEnv(raw) {
|
|
16
|
+
const result = {};
|
|
17
|
+
for (const line of raw.split("\n")) {
|
|
18
|
+
if (/^\s*#/.test(line) || line.trim() === "") continue;
|
|
19
|
+
const match = /^\s*([^=]+)=(.*)$/.exec(line);
|
|
20
|
+
if (!match) continue;
|
|
21
|
+
const key = match[1].trim();
|
|
22
|
+
let value = match[2].trim();
|
|
23
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
24
|
+
value = value.slice(1, -1).replace(/\\"/g, '"');
|
|
25
|
+
}
|
|
26
|
+
if (value !== "") result[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function readPersistentEnv() {
|
|
31
|
+
const envPath = getPersistentEnvPath();
|
|
32
|
+
try {
|
|
33
|
+
const raw = readFileSync(envPath, "utf8");
|
|
34
|
+
return parseDotEnv(raw);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err?.code === "ENOENT") return null;
|
|
37
|
+
throw new TeAgentCredentialsError(
|
|
38
|
+
`Failed to read ${envPath}: ${err?.message ?? err}`,
|
|
39
|
+
"Check file permissions, or inject runtime credentials via TE_CLAUDE_BASE_URL / SANDBOX_ID / SECRET_KEY"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
var TeAgentCredentialsError = class extends Error {
|
|
44
|
+
constructor(message, hint) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.hint = hint;
|
|
47
|
+
this.name = "TeAgentCredentialsError";
|
|
48
|
+
}
|
|
49
|
+
hint;
|
|
50
|
+
};
|
|
51
|
+
function readLegacyCredentials() {
|
|
52
|
+
const credPath = getCredentialsPath();
|
|
53
|
+
try {
|
|
54
|
+
const raw = readFileSync(credPath, "utf8");
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
const main = parsed?.mainApp;
|
|
57
|
+
return main && typeof main === "object" ? main : null;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err?.code === "ENOENT") return null;
|
|
60
|
+
if (err instanceof SyntaxError) {
|
|
61
|
+
throw new TeAgentCredentialsError(
|
|
62
|
+
`Failed to parse ${credPath}: ${err.message}`,
|
|
63
|
+
"The credentials file may be corrupted; alternatively inject runtime credentials via TE_CLAUDE_BASE_URL / SANDBOX_ID / SECRET_KEY"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
throw new TeAgentCredentialsError(
|
|
67
|
+
`Failed to read ${credPath}: ${err?.message ?? err}`,
|
|
68
|
+
"Check file permissions, or inject runtime credentials via TE_CLAUDE_BASE_URL / SANDBOX_ID / SECRET_KEY"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function tryLoadTeAgentSandboxCredentials() {
|
|
73
|
+
const legacy = readLegacyCredentials();
|
|
74
|
+
const persistent = readPersistentEnv();
|
|
75
|
+
const url = process.env.TE_CLAUDE_BASE_URL || persistent?.TE_CLAUDE_BASE_URL || legacy?.url;
|
|
76
|
+
if (typeof url !== "string" || !url) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const sandboxId = process.env.SANDBOX_ID || persistent?.SANDBOX_ID || legacy?.sandboxId || null;
|
|
80
|
+
const sandboxSecretKey = process.env.SECRET_KEY || process.env.SANDBOX_SECRET_KEY || persistent?.SECRET_KEY || persistent?.SANDBOX_SECRET_KEY || legacy?.sandboxSecretKey || legacy?.secretKey || null;
|
|
81
|
+
return {
|
|
82
|
+
url,
|
|
83
|
+
sandboxId: typeof sandboxId === "string" ? sandboxId : null,
|
|
84
|
+
sandboxSecretKey: typeof sandboxSecretKey === "string" ? sandboxSecretKey : null
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function getClaudeConfigDir() {
|
|
88
|
+
const fromEnv = process.env.CLAUDE_CONFIG_DIR;
|
|
89
|
+
if (typeof fromEnv === "string" && fromEnv.length > 0) {
|
|
90
|
+
return fromEnv;
|
|
91
|
+
}
|
|
92
|
+
return join(homedir(), ".claude");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
TeAgentCredentialsError,
|
|
97
|
+
tryLoadTeAgentSandboxCredentials,
|
|
98
|
+
getClaudeConfigDir
|
|
99
|
+
};
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SecureStoreAuthError,
|
|
2
3
|
clearToken,
|
|
3
4
|
getToken,
|
|
4
5
|
resolveHost
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CAQYQA4R.js";
|
|
6
7
|
import {
|
|
8
|
+
logger,
|
|
7
9
|
safeJsonParse
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
logger
|
|
11
|
-
} from "./chunk-MNHE6SSI.js";
|
|
10
|
+
} from "./chunk-SRJIAOBN.js";
|
|
12
11
|
|
|
13
12
|
// src/core/client.ts
|
|
14
13
|
import WebSocket from "ws";
|
|
@@ -45,9 +44,16 @@ async function request(method, modulePath, params = {}, body = null, retry = tru
|
|
|
45
44
|
const text = await resp.text();
|
|
46
45
|
const data = safeJsonParse(text);
|
|
47
46
|
logger.api(method, url, resp.status, body, data);
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
if (resp.status === 401) {
|
|
48
|
+
if (retry) {
|
|
49
|
+
clearToken(resolvedHost);
|
|
50
|
+
return request(method, modulePath, params, body, false, resolvedHost);
|
|
51
|
+
}
|
|
52
|
+
throw new SecureStoreAuthError(`Session expired or unauthorized for ${resolvedHost}. Please run: ae-cli auth login`);
|
|
53
|
+
}
|
|
54
|
+
if (resp.status === 403) {
|
|
55
|
+
const m = data && typeof data === "object" && typeof data.error === "string" ? data.error : "Permission denied";
|
|
56
|
+
throw new Error(m);
|
|
51
57
|
}
|
|
52
58
|
if (data.return_code === -1001 && retry) {
|
|
53
59
|
clearToken(resolvedHost);
|
|
@@ -81,9 +87,16 @@ async function uploadRequest(modulePath, form, params = {}, retry = true, hostUr
|
|
|
81
87
|
const text = await resp.text();
|
|
82
88
|
const data = safeJsonParse(text);
|
|
83
89
|
logger.api("POST", url, resp.status, { upload: true }, data);
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
if (resp.status === 401) {
|
|
91
|
+
if (retry) {
|
|
92
|
+
clearToken(resolvedHost);
|
|
93
|
+
return uploadRequest(modulePath, form, params, false, resolvedHost);
|
|
94
|
+
}
|
|
95
|
+
throw new SecureStoreAuthError(`Session expired or unauthorized for ${resolvedHost}. Please run: ae-cli auth login`);
|
|
96
|
+
}
|
|
97
|
+
if (resp.status === 403) {
|
|
98
|
+
const m = data && typeof data === "object" && typeof data.error === "string" ? data.error : "Permission denied";
|
|
99
|
+
throw new Error(m);
|
|
87
100
|
}
|
|
88
101
|
if (data.return_code === -1001 && retry) {
|
|
89
102
|
clearToken(resolvedHost);
|