@thinkingai/ae-cli 6.1.14 → 6.1.16
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-GBMV6TEJ.js → auth-2WTQOP77.js} +3 -3
- package/dist/{auth-NDSXE54J.js → auth-77BUFLGC.js} +57 -13
- package/dist/{capability-TAMDRZYV.js → capability-72DTW5M2.js} +9 -9
- package/dist/{capability-U7TDEEEG.js → capability-PJHNI4GJ.js} +9 -9
- package/dist/{chunk-AFXA7BRK.js → chunk-4KVPKXFX.js} +5 -5
- package/dist/chunk-4SGZG4XY.js +311 -0
- package/dist/chunk-6EIJSNBD.js +8 -0
- package/dist/chunk-C4MGVGJW.js +13 -0
- package/dist/{chunk-WZRX4KOH.js → chunk-GT46FPXN.js} +4 -6
- package/dist/{chunk-5XUSIK27.js → chunk-ILIU36SU.js} +10 -7
- package/dist/{chunk-JHENBQ5B.js → chunk-LYVNONC4.js} +0 -35
- package/dist/{chunk-JUW4AJXN.js → chunk-P3FGXJTU.js} +12 -7
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/{chunk-753BUTNZ.js → chunk-RGKJGKT7.js} +2 -2
- package/dist/{chunk-UIHQJK5E.js → chunk-SAU3QFIQ.js} +3 -3
- package/dist/{chunk-VLWOLBGZ.js → chunk-UOUS37JQ.js} +149 -161
- package/dist/{chunk-AXDXJTPC.js → chunk-UW5UN47B.js} +19 -2
- package/dist/chunk-VPKZ7I72.js +509 -0
- package/dist/{chunk-QATA32VR.js → chunk-VR3LCBHW.js} +2 -2
- package/dist/{chunk-IBH3LDAH.js → chunk-YA6SMTXG.js} +3 -3
- package/dist/chunk-ZZUOD757.js +598 -0
- package/dist/{client-L2YDMHQ6.js → client-TKG4WBHN.js} +4 -5
- package/dist/{community-report-client-M2RW4MXD.js → community-report-client-FI4LNVYS.js} +3 -2
- package/dist/{config-OL2LWGBV.js → config-RE6CMGPK.js} +7 -7
- package/dist/data-integration-2MYMANJI.js +3383 -0
- package/dist/index.js +68 -34
- package/dist/local-data-upload-client-BWHSUQQK.js +167 -0
- package/dist/{memory-MUP7PPL7.js → memory-CHRU2F7W.js} +7 -6
- package/dist/{memory-U4O5PMXH.js → memory-YK33G4T7.js} +7 -6
- package/dist/{metadata-5MIMNIMT.js → metadata-UILXHBWF.js} +10 -9
- package/dist/{metadata-LERKDJN6.js → metadata-XXR34N5P.js} +10 -9
- package/dist/{model-JTUEO5M4.js → model-K3KLWIW6.js} +7 -3
- package/dist/model-NR3JHFSJ.js +139 -0
- package/dist/{sync-MOSFNBVR.js → sync-DAVKYVMW.js} +8 -6
- package/dist/sync-FCKOVWWS.js +10261 -0
- package/dist/{te-agent-IFKZDHZI.js → te-agent-4BKBODMF.js} +686 -71
- package/dist/te-agent-HLW4VTQK.js +3893 -0
- package/dist/{te-analysis-FCHRTNIY.js → te-analysis-O6DCO6BS.js} +36 -13
- package/dist/{te-analysis-QG7UKGDA.js → te-analysis-ZMNGOVNW.js} +36 -13
- package/dist/{te-community-HNKVTERD.js → te-community-6HPBWJUZ.js} +24 -19
- package/dist/{te-community-IWE5B7W6.js → te-community-HLC43QKH.js} +24 -20
- package/dist/{te-dataops-KXCEB4CS.js → te-dataops-EJP56W3K.js} +57 -34
- package/dist/{te-dataops-KQPYNAE3.js → te-dataops-HDRUXY4K.js} +59 -34
- package/dist/{te-engage-D6EG3NOR.js → te-engage-FGBGQ4IY.js} +9 -8
- package/dist/{te-engage-ZSMIJLUW.js → te-engage-RAK5PESW.js} +9 -8
- package/dist/{te-experiment-K5US7RMG.js → te-experiment-SO5MPDMJ.js} +9 -8
- package/dist/{te-experiment-WA7TFMEL.js → te-experiment-VZF7BT6G.js} +9 -8
- package/dist/{te-kb-OIH3T6CS.js → te-kb-APXBWBDY.js} +6 -6
- package/dist/{te-system-AZ3URMUO.js → te-system-YARIK4S5.js} +7 -4
- package/dist/te-system-Z77IKZFN.js +2213 -0
- package/dist/{te-team-GZPU6UWA.js → te-team-EFKWYKMK.js} +7 -7
- package/dist/{update-TOBFXF2V.js → update-OGPSZM5A.js} +7 -7
- package/package.json +13 -3
- package/skills/ae-agent/SKILL.md +21 -11
- package/skills/ae-agent/references/approval-effect.md +44 -0
- package/skills/ae-agent/references/approval-request.md +46 -0
- package/skills/ae-agent/references/approval-task.md +45 -0
- package/skills/ae-agent/references/approval-type.md +32 -0
- package/skills/ae-agent/references/command_index.md +19 -0
- package/skills/ae-analysis/SKILL.md +11 -7
- package/skills/ae-analysis/references/adhoc_run.md +2 -0
- package/skills/ae-analysis/references/ai_models.md +27 -0
- package/skills/ae-analysis/references/analysis_gateway_assets.md +3 -2
- package/skills/ae-analysis/references/command_index.md +4 -1
- package/skills/ae-analysis/references/dashboard_get.md +8 -2
- package/skills/ae-analysis/references/dashboard_report_data_run.md +3 -1
- package/skills/ae-analysis/references/dashboard_update.md +4 -1
- package/skills/ae-analysis/references/project_space_business_filter_upsert.md +17 -0
- package/skills/ae-analysis/references/user_tag_models.md +4 -4
- package/skills/ae-data-integration/SKILL.md +54 -0
- package/skills/ae-data-integration/references/handoff.md +43 -0
- package/skills/ae-data-integration/references/local-analysis.md +27 -0
- package/skills/ae-data-integration/references/reuse.md +41 -0
- package/skills/ae-data-integration/references/sink-upload.md +58 -0
- package/skills/ae-data-integration/references/source-inspect.md +55 -0
- package/skills/ae-data-integration/references/sync-json-upload.md +60 -0
- package/skills/ae-data-integration/references/tracking-plan.md +35 -0
- package/skills/ae-data-integration/references/transform.md +65 -0
- package/skills/ae-data-integration/references/ue-mapping.md +142 -0
- package/skills/ae-data-integration/references/ue-routing.md +35 -0
- package/skills/ae-data-integration-helper/SKILL.md +1 -1
- package/skills/ae-dataops/SKILL.md +1 -1
- package/skills/ae-dataops/references/dataops-query.md +4 -4
- package/skills/ae-generate-tracking-plan/SKILL.md +54 -3
- package/skills/ae-metadata/SKILL.md +0 -1
- package/dist/chunk-3FY3RJ26.js +0 -293
- package/dist/chunk-S5NTSDBS.js +0 -198
- package/dist/chunk-ZQKDZXDO.js +0 -317
- package/dist/cli-token-4UPER74P.js +0 -21
|
@@ -0,0 +1,2213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deleteFromMainApp,
|
|
3
|
+
downloadFromMainApp,
|
|
4
|
+
getFromMainApp,
|
|
5
|
+
patchToMainApp,
|
|
6
|
+
postToMainApp,
|
|
7
|
+
putToMainApp,
|
|
8
|
+
tryLoadTeAgentSandboxCredentials,
|
|
9
|
+
uploadToMainApp
|
|
10
|
+
} from "./chunk-VPKZ7I72.js";
|
|
11
|
+
import "./chunk-6EIJSNBD.js";
|
|
12
|
+
import "./chunk-4SGZG4XY.js";
|
|
13
|
+
import {
|
|
14
|
+
safeJsonParse
|
|
15
|
+
} from "./chunk-ZZUOD757.js";
|
|
16
|
+
import {
|
|
17
|
+
CliValidationError
|
|
18
|
+
} from "./chunk-UW5UN47B.js";
|
|
19
|
+
import "./chunk-LYVNONC4.js";
|
|
20
|
+
import "./chunk-QGM4M3NI.js";
|
|
21
|
+
|
|
22
|
+
// src/commands/te-system/shared.ts
|
|
23
|
+
import { readFileSync } from "fs";
|
|
24
|
+
var SECRET_KEYS = /* @__PURE__ */ new Set([
|
|
25
|
+
"appsecret",
|
|
26
|
+
"bottoken",
|
|
27
|
+
"apptoken",
|
|
28
|
+
"clientsecret",
|
|
29
|
+
"apikey",
|
|
30
|
+
"secret",
|
|
31
|
+
"token"
|
|
32
|
+
]);
|
|
33
|
+
function assertAdminPath(path3) {
|
|
34
|
+
if (path3 === "/api/admin" || path3.startsWith("/api/admin/")) return;
|
|
35
|
+
throw new CliValidationError("System commands may only call /api/admin endpoints");
|
|
36
|
+
}
|
|
37
|
+
async function getAdmin(path3, host) {
|
|
38
|
+
assertAdminPath(path3);
|
|
39
|
+
return getFromMainApp(path3, host);
|
|
40
|
+
}
|
|
41
|
+
async function uploadAdminForm(path3, formData, host) {
|
|
42
|
+
assertAdminPath(path3);
|
|
43
|
+
return uploadToMainApp(path3, formData, host);
|
|
44
|
+
}
|
|
45
|
+
async function downloadAdmin(path3, outputPath, host) {
|
|
46
|
+
assertAdminPath(path3);
|
|
47
|
+
return downloadFromMainApp(path3, outputPath, host);
|
|
48
|
+
}
|
|
49
|
+
async function executeAdminRequest(ctx, request) {
|
|
50
|
+
assertAdminPath(request.path);
|
|
51
|
+
const host = ctx.host();
|
|
52
|
+
switch (request.method) {
|
|
53
|
+
case "GET":
|
|
54
|
+
return getFromMainApp(request.path, host);
|
|
55
|
+
case "POST":
|
|
56
|
+
return postToMainApp(request.path, request.body ?? {}, host);
|
|
57
|
+
case "PATCH":
|
|
58
|
+
return patchToMainApp(request.path, request.body ?? {}, host);
|
|
59
|
+
case "PUT":
|
|
60
|
+
return putToMainApp(request.path, request.body ?? {}, host);
|
|
61
|
+
case "DELETE":
|
|
62
|
+
return deleteFromMainApp(request.path, host);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function createAdminCommand(config) {
|
|
66
|
+
return {
|
|
67
|
+
service: "system",
|
|
68
|
+
command: config.command,
|
|
69
|
+
description: config.description,
|
|
70
|
+
flags: config.flags,
|
|
71
|
+
risk: config.risk,
|
|
72
|
+
helpText: config.helpText,
|
|
73
|
+
validate: config.validate,
|
|
74
|
+
preflight: config.risk === "high-risk-write" ? (ctx) => {
|
|
75
|
+
const request = config.prepare(ctx);
|
|
76
|
+
assertAdminPath(request.path);
|
|
77
|
+
} : void 0,
|
|
78
|
+
dryRun: (ctx) => {
|
|
79
|
+
const request = config.prepare(ctx);
|
|
80
|
+
assertAdminPath(request.path);
|
|
81
|
+
return {
|
|
82
|
+
method: request.method,
|
|
83
|
+
url: request.path,
|
|
84
|
+
...request.body === void 0 ? {} : { body: config.redactDryRun ? redactSecrets(request.body) : request.body }
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
execute: async (ctx) => executeAdminRequest(ctx, config.prepare(ctx))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function createAdminDownloadCommand(config) {
|
|
91
|
+
return {
|
|
92
|
+
service: "system",
|
|
93
|
+
command: config.command,
|
|
94
|
+
description: config.description,
|
|
95
|
+
flags: config.flags,
|
|
96
|
+
risk: "read",
|
|
97
|
+
helpText: config.helpText,
|
|
98
|
+
validate: config.validate,
|
|
99
|
+
dryRun: (ctx) => {
|
|
100
|
+
const request = config.prepare(ctx);
|
|
101
|
+
assertAdminPath(request.path);
|
|
102
|
+
return {
|
|
103
|
+
method: "GET",
|
|
104
|
+
url: request.path,
|
|
105
|
+
body: { outputPath: request.outputPath }
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
execute: async (ctx) => {
|
|
109
|
+
const request = config.prepare(ctx);
|
|
110
|
+
return downloadAdmin(request.path, request.outputPath, ctx.host());
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function optionalString(ctx, name) {
|
|
115
|
+
const value = ctx.str(name).trim();
|
|
116
|
+
return value || void 0;
|
|
117
|
+
}
|
|
118
|
+
function assertEnum(flagName, value, values) {
|
|
119
|
+
if (!value || values.includes(value)) return;
|
|
120
|
+
throw new CliValidationError(`--${flagName} must be one of: ${values.join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
function withQuery(path3, values) {
|
|
123
|
+
const query = new URLSearchParams();
|
|
124
|
+
for (const [key, value] of Object.entries(values)) {
|
|
125
|
+
if (value === void 0 || value === "") continue;
|
|
126
|
+
query.set(key, String(value));
|
|
127
|
+
}
|
|
128
|
+
const encoded = query.toString();
|
|
129
|
+
return encoded ? `${path3}?${encoded}` : path3;
|
|
130
|
+
}
|
|
131
|
+
function readJsonSource(raw, flagName) {
|
|
132
|
+
if (raw === "-") {
|
|
133
|
+
return readFileSync(0, "utf8");
|
|
134
|
+
}
|
|
135
|
+
if (raw.startsWith("@")) {
|
|
136
|
+
const filePath = raw.slice(1).trim();
|
|
137
|
+
if (!filePath) {
|
|
138
|
+
throw new CliValidationError(`--${flagName} @file path cannot be empty`);
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
return readFileSync(filePath, "utf8");
|
|
142
|
+
} catch {
|
|
143
|
+
throw new CliValidationError(`Unable to read JSON file for --${flagName}`, {
|
|
144
|
+
hint: "Check that the file exists and is readable."
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return raw;
|
|
149
|
+
}
|
|
150
|
+
function readJsonFlag(ctx, name) {
|
|
151
|
+
const raw = ctx.str(name);
|
|
152
|
+
try {
|
|
153
|
+
return safeJsonParse(readJsonSource(raw, name));
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof CliValidationError) throw error;
|
|
156
|
+
throw new CliValidationError(`--${name} must be valid JSON`, {
|
|
157
|
+
hint: `Pass inline JSON, @file, or - for stdin.`
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function requireJsonObject(ctx, name) {
|
|
162
|
+
const value = readJsonFlag(ctx, name);
|
|
163
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
164
|
+
throw new CliValidationError(`--${name} must be a JSON object`);
|
|
165
|
+
}
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
function optionalJsonObject(ctx, name) {
|
|
169
|
+
if (!optionalString(ctx, name)) return void 0;
|
|
170
|
+
return requireJsonObject(ctx, name);
|
|
171
|
+
}
|
|
172
|
+
function requireJsonArray(ctx, name) {
|
|
173
|
+
const value = readJsonFlag(ctx, name);
|
|
174
|
+
if (!Array.isArray(value)) {
|
|
175
|
+
throw new CliValidationError(`--${name} must be a JSON array`);
|
|
176
|
+
}
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
function requireStringArray(ctx, name) {
|
|
180
|
+
const value = requireJsonArray(ctx, name);
|
|
181
|
+
if (value.length === 0 || value.some((item) => typeof item !== "string" || !item.trim())) {
|
|
182
|
+
throw new CliValidationError(`--${name} must be a non-empty JSON array of strings`);
|
|
183
|
+
}
|
|
184
|
+
return value.map((item) => item.trim());
|
|
185
|
+
}
|
|
186
|
+
function requireBoundedStringArray(ctx, name, max) {
|
|
187
|
+
const values = requireStringArray(ctx, name);
|
|
188
|
+
if (values.length > max) {
|
|
189
|
+
throw new CliValidationError(`--${name} supports at most ${max} values`);
|
|
190
|
+
}
|
|
191
|
+
if (new Set(values).size !== values.length) {
|
|
192
|
+
throw new CliValidationError(`--${name} must not contain duplicate values`);
|
|
193
|
+
}
|
|
194
|
+
return values;
|
|
195
|
+
}
|
|
196
|
+
function redactSecrets(value) {
|
|
197
|
+
if (Array.isArray(value)) return value.map(redactSecrets);
|
|
198
|
+
if (!value || typeof value !== "object") return value;
|
|
199
|
+
return Object.fromEntries(
|
|
200
|
+
Object.entries(value).map(([key, item]) => [
|
|
201
|
+
key,
|
|
202
|
+
SECRET_KEYS.has(key.toLowerCase()) ? "***" : redactSecrets(item)
|
|
203
|
+
])
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
function encodeId(value) {
|
|
207
|
+
return encodeURIComponent(value);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/commands/te-system/members.ts
|
|
211
|
+
var MEMBER_STATUSES = ["all", "enabled", "disabled"];
|
|
212
|
+
var MEMBER_ROLES = ["agent_admin", "member"];
|
|
213
|
+
var SORT_DIRECTIONS = ["asc", "desc"];
|
|
214
|
+
function memberBatchBody(ctx) {
|
|
215
|
+
const members = requireJsonArray(ctx, "members");
|
|
216
|
+
if (members.length === 0 || members.some(
|
|
217
|
+
(member) => !member || typeof member !== "object" || Array.isArray(member) || typeof member.openId !== "string" || !member.openId.trim()
|
|
218
|
+
)) {
|
|
219
|
+
throw new CliValidationError(
|
|
220
|
+
"--members must contain objects with a non-empty openId",
|
|
221
|
+
{ hint: `Example: --members '[{"openId":"ou_x","loginName":"alice"}]'` }
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
members,
|
|
226
|
+
...optionalString(ctx, "rule-id") ? { ruleId: ctx.str("rule-id") } : {},
|
|
227
|
+
createSandbox: ctx.bool("create-sandbox")
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
var listMemberCandidates = createAdminCommand({
|
|
231
|
+
command: "+list-member-candidates",
|
|
232
|
+
description: "List TE company users that can be added to Agent system management",
|
|
233
|
+
flags: [],
|
|
234
|
+
risk: "read",
|
|
235
|
+
prepare: () => ({ method: "GET", path: "/api/admin/te-members" })
|
|
236
|
+
});
|
|
237
|
+
var listMembers = createAdminCommand({
|
|
238
|
+
command: "+list-members",
|
|
239
|
+
description: "List Agent system members in the current company",
|
|
240
|
+
flags: [
|
|
241
|
+
{ name: "q", type: "string", desc: "Search login name or display name" },
|
|
242
|
+
{
|
|
243
|
+
name: "status",
|
|
244
|
+
type: "string",
|
|
245
|
+
desc: `Member status filter: ${MEMBER_STATUSES.join(" | ")}`
|
|
246
|
+
},
|
|
247
|
+
{ name: "page", type: "number", min: 1, desc: "Page number (default: 1)" },
|
|
248
|
+
{ name: "page-size", type: "number", min: 1, max: 100, desc: "Page size (1-100, default: 20)" },
|
|
249
|
+
{ name: "all", type: "boolean", desc: "Return all matching members without pagination" },
|
|
250
|
+
{ name: "sort-field", type: "string", desc: "Optional sort field: periodUsedAmount" },
|
|
251
|
+
{
|
|
252
|
+
name: "sort-dir",
|
|
253
|
+
type: "string",
|
|
254
|
+
desc: `Sort direction: ${SORT_DIRECTIONS.join(" | ")}`
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
risk: "read",
|
|
258
|
+
validate: (ctx) => {
|
|
259
|
+
assertEnum("status", optionalString(ctx, "status"), MEMBER_STATUSES);
|
|
260
|
+
assertEnum("sort-dir", optionalString(ctx, "sort-dir"), SORT_DIRECTIONS);
|
|
261
|
+
const sortField = optionalString(ctx, "sort-field");
|
|
262
|
+
if (sortField && sortField !== "periodUsedAmount") {
|
|
263
|
+
throw new CliValidationError("--sort-field must be periodUsedAmount");
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
prepare: (ctx) => ({
|
|
267
|
+
method: "GET",
|
|
268
|
+
path: withQuery("/api/admin/members", {
|
|
269
|
+
q: optionalString(ctx, "q"),
|
|
270
|
+
status: optionalString(ctx, "status"),
|
|
271
|
+
page: ctx.optionalNum("page"),
|
|
272
|
+
pageSize: ctx.optionalNum("page-size"),
|
|
273
|
+
all: ctx.bool("all") ? true : void 0,
|
|
274
|
+
sortField: optionalString(ctx, "sort-field"),
|
|
275
|
+
sortDir: optionalString(ctx, "sort-dir")
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
});
|
|
279
|
+
var addMembers = createAdminCommand({
|
|
280
|
+
command: "+add-members",
|
|
281
|
+
description: "Add one or more TE users to the Agent system member list",
|
|
282
|
+
flags: [
|
|
283
|
+
{
|
|
284
|
+
name: "members",
|
|
285
|
+
type: "string",
|
|
286
|
+
required: true,
|
|
287
|
+
sensitive: true,
|
|
288
|
+
desc: "JSON array, @file, or stdin (-): openId plus optional loginName/displayName"
|
|
289
|
+
},
|
|
290
|
+
{ name: "rule-id", type: "string", desc: "Optional quota rule ID to bind to every member" },
|
|
291
|
+
{
|
|
292
|
+
name: "create-sandbox",
|
|
293
|
+
type: "boolean",
|
|
294
|
+
desc: "Create and bind a personal sandbox for every added member"
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
risk: "write",
|
|
298
|
+
prepare: (ctx) => ({
|
|
299
|
+
method: "POST",
|
|
300
|
+
path: "/api/admin/members/batch-add",
|
|
301
|
+
body: memberBatchBody(ctx)
|
|
302
|
+
})
|
|
303
|
+
});
|
|
304
|
+
var setMemberStatus = createAdminCommand({
|
|
305
|
+
command: "+set-member-status",
|
|
306
|
+
description: "Enable or disable an Agent system member",
|
|
307
|
+
flags: [
|
|
308
|
+
{ name: "open-id", type: "string", required: true, desc: "TE user openId" },
|
|
309
|
+
{ name: "enabled", type: "boolean", required: true, desc: "Whether the member can use Agent" },
|
|
310
|
+
{ name: "display-name", type: "string", desc: "Optional display name update" }
|
|
311
|
+
],
|
|
312
|
+
risk: "write",
|
|
313
|
+
prepare: (ctx) => ({
|
|
314
|
+
method: "PATCH",
|
|
315
|
+
path: "/api/admin/members",
|
|
316
|
+
body: {
|
|
317
|
+
openId: ctx.str("open-id"),
|
|
318
|
+
enabled: ctx.bool("enabled"),
|
|
319
|
+
...optionalString(ctx, "display-name") ? { displayName: ctx.str("display-name") } : {}
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
});
|
|
323
|
+
var setMemberRole = createAdminCommand({
|
|
324
|
+
command: "+set-member-role",
|
|
325
|
+
description: "Grant or revoke the Agent administrator role for a member",
|
|
326
|
+
flags: [
|
|
327
|
+
{ name: "user-id", type: "string", required: true, desc: "Agent database user ID from +list-members" },
|
|
328
|
+
{
|
|
329
|
+
name: "role",
|
|
330
|
+
type: "string",
|
|
331
|
+
required: true,
|
|
332
|
+
desc: `Target role: ${MEMBER_ROLES.join(" | ")}`
|
|
333
|
+
}
|
|
334
|
+
],
|
|
335
|
+
risk: "write",
|
|
336
|
+
validate: (ctx) => assertEnum("role", optionalString(ctx, "role"), MEMBER_ROLES),
|
|
337
|
+
prepare: (ctx) => ({
|
|
338
|
+
method: "PATCH",
|
|
339
|
+
path: `/api/admin/members/${encodeId(ctx.str("user-id"))}/role`,
|
|
340
|
+
body: { agentRole: ctx.str("role") }
|
|
341
|
+
})
|
|
342
|
+
});
|
|
343
|
+
var removeMember = createAdminCommand({
|
|
344
|
+
command: "+remove-member",
|
|
345
|
+
description: "Remove a non-root member from Agent system management",
|
|
346
|
+
flags: [
|
|
347
|
+
{ name: "user-id", type: "string", required: true, desc: "Agent database user ID from +list-members" }
|
|
348
|
+
],
|
|
349
|
+
risk: "high-risk-write",
|
|
350
|
+
prepare: (ctx) => ({
|
|
351
|
+
method: "DELETE",
|
|
352
|
+
path: `/api/admin/members/${encodeId(ctx.str("user-id"))}`
|
|
353
|
+
})
|
|
354
|
+
});
|
|
355
|
+
var getMemberStats = createAdminCommand({
|
|
356
|
+
command: "+get-member-stats",
|
|
357
|
+
description: "Get token usage and recent conversation statistics for one company member",
|
|
358
|
+
flags: [
|
|
359
|
+
{ name: "user-id", type: "string", required: true, desc: "Agent database user ID from +list-members" },
|
|
360
|
+
{ name: "days", type: "number", min: 1, max: 365, desc: "Recent range in days (1-365, default: 30)" }
|
|
361
|
+
],
|
|
362
|
+
risk: "read",
|
|
363
|
+
prepare: (ctx) => ({
|
|
364
|
+
method: "GET",
|
|
365
|
+
path: withQuery(`/api/admin/members/${encodeId(ctx.str("user-id"))}/stats`, {
|
|
366
|
+
days: ctx.optionalNum("days")
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
});
|
|
370
|
+
var memberCommands = [
|
|
371
|
+
listMemberCandidates,
|
|
372
|
+
listMembers,
|
|
373
|
+
addMembers,
|
|
374
|
+
setMemberStatus,
|
|
375
|
+
setMemberRole,
|
|
376
|
+
removeMember,
|
|
377
|
+
getMemberStats
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
// src/commands/te-system/sandboxes.ts
|
|
381
|
+
function batchCreateBody(ctx) {
|
|
382
|
+
const userIds = requireStringArray(ctx, "user-ids");
|
|
383
|
+
if (userIds.length > 100) {
|
|
384
|
+
throw new CliValidationError("--user-ids supports at most 100 users per request");
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
userIds,
|
|
388
|
+
...optionalString(ctx, "description") ? { description: ctx.str("description") } : {}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
var listSandboxes = createAdminCommand({
|
|
392
|
+
command: "+list-sandboxes",
|
|
393
|
+
description: "List sandboxes managed by the current company",
|
|
394
|
+
flags: [],
|
|
395
|
+
risk: "read",
|
|
396
|
+
prepare: () => ({ method: "GET", path: "/api/admin/sandboxes" })
|
|
397
|
+
});
|
|
398
|
+
var getSandboxConfig = createAdminCommand({
|
|
399
|
+
command: "+get-sandbox-config",
|
|
400
|
+
description: "Get sandbox feature status and company create/active seat limits",
|
|
401
|
+
flags: [],
|
|
402
|
+
risk: "read",
|
|
403
|
+
prepare: () => ({ method: "GET", path: "/api/admin/sandbox/config" })
|
|
404
|
+
});
|
|
405
|
+
var batchCreateSandboxes = createAdminCommand({
|
|
406
|
+
command: "+batch-create-sandboxes",
|
|
407
|
+
description: "Create personal sandboxes for up to 100 company members",
|
|
408
|
+
flags: [
|
|
409
|
+
{
|
|
410
|
+
name: "user-ids",
|
|
411
|
+
type: "string",
|
|
412
|
+
required: true,
|
|
413
|
+
sensitive: true,
|
|
414
|
+
desc: "Non-empty JSON string array, @file, or stdin (-) of Agent user IDs"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: "description",
|
|
418
|
+
type: "string",
|
|
419
|
+
maxLength: 48,
|
|
420
|
+
desc: "Optional sandbox description (max 48 characters)"
|
|
421
|
+
}
|
|
422
|
+
],
|
|
423
|
+
risk: "write",
|
|
424
|
+
prepare: (ctx) => ({
|
|
425
|
+
method: "POST",
|
|
426
|
+
path: "/api/admin/sandboxes/batch-create",
|
|
427
|
+
body: batchCreateBody(ctx)
|
|
428
|
+
})
|
|
429
|
+
});
|
|
430
|
+
var updateSandbox = createAdminCommand({
|
|
431
|
+
command: "+update-sandbox",
|
|
432
|
+
description: "Update a sandbox description",
|
|
433
|
+
flags: [
|
|
434
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" },
|
|
435
|
+
{
|
|
436
|
+
name: "description",
|
|
437
|
+
type: "string",
|
|
438
|
+
required: true,
|
|
439
|
+
maxLength: 48,
|
|
440
|
+
desc: "New sandbox description (max 48 characters)"
|
|
441
|
+
}
|
|
442
|
+
],
|
|
443
|
+
risk: "write",
|
|
444
|
+
prepare: (ctx) => ({
|
|
445
|
+
method: "PUT",
|
|
446
|
+
path: `/api/admin/sandboxes/${encodeId(ctx.str("id"))}`,
|
|
447
|
+
body: { description: ctx.str("description") }
|
|
448
|
+
})
|
|
449
|
+
});
|
|
450
|
+
var setSandboxEnabled = createAdminCommand({
|
|
451
|
+
command: "+set-sandbox-enabled",
|
|
452
|
+
description: "Enable or disable a sandbox",
|
|
453
|
+
flags: [
|
|
454
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" },
|
|
455
|
+
{ name: "enabled", type: "boolean", required: true, desc: "Whether the sandbox is enabled" }
|
|
456
|
+
],
|
|
457
|
+
risk: "write",
|
|
458
|
+
prepare: (ctx) => ({
|
|
459
|
+
method: "PATCH",
|
|
460
|
+
path: `/api/admin/sandboxes/${encodeId(ctx.str("id"))}/enabled`,
|
|
461
|
+
body: { enabled: ctx.bool("enabled") }
|
|
462
|
+
})
|
|
463
|
+
});
|
|
464
|
+
var startSandbox = createAdminCommand({
|
|
465
|
+
command: "+start-sandbox",
|
|
466
|
+
description: "Start the container for an enabled sandbox",
|
|
467
|
+
flags: [
|
|
468
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" }
|
|
469
|
+
],
|
|
470
|
+
risk: "write",
|
|
471
|
+
prepare: (ctx) => ({
|
|
472
|
+
method: "POST",
|
|
473
|
+
path: `/api/admin/sandboxes/${encodeId(ctx.str("id"))}/containers/start`
|
|
474
|
+
})
|
|
475
|
+
});
|
|
476
|
+
var stopSandbox = createAdminCommand({
|
|
477
|
+
command: "+stop-sandbox",
|
|
478
|
+
description: "Stop the container for a sandbox",
|
|
479
|
+
flags: [
|
|
480
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" }
|
|
481
|
+
],
|
|
482
|
+
risk: "write",
|
|
483
|
+
prepare: (ctx) => ({
|
|
484
|
+
method: "POST",
|
|
485
|
+
path: `/api/admin/sandboxes/${encodeId(ctx.str("id"))}/containers/stop`
|
|
486
|
+
})
|
|
487
|
+
});
|
|
488
|
+
var listSandboxUsers = createAdminCommand({
|
|
489
|
+
command: "+list-sandbox-users",
|
|
490
|
+
description: "List users bound to a sandbox",
|
|
491
|
+
flags: [
|
|
492
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" }
|
|
493
|
+
],
|
|
494
|
+
risk: "read",
|
|
495
|
+
prepare: (ctx) => ({
|
|
496
|
+
method: "GET",
|
|
497
|
+
path: `/api/admin/sandboxes/${encodeId(ctx.str("id"))}/users`
|
|
498
|
+
})
|
|
499
|
+
});
|
|
500
|
+
var bindSandboxUser = createAdminCommand({
|
|
501
|
+
command: "+bind-sandbox-user",
|
|
502
|
+
description: "Bind a company member to a sandbox",
|
|
503
|
+
flags: [
|
|
504
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" },
|
|
505
|
+
{ name: "user-id", type: "string", required: true, desc: "Agent user ID from +list-members" }
|
|
506
|
+
],
|
|
507
|
+
risk: "write",
|
|
508
|
+
prepare: (ctx) => ({
|
|
509
|
+
method: "POST",
|
|
510
|
+
path: `/api/admin/sandboxes/${encodeId(ctx.str("id"))}/users`,
|
|
511
|
+
body: { userId: ctx.str("user-id") }
|
|
512
|
+
})
|
|
513
|
+
});
|
|
514
|
+
var unbindSandboxUser = createAdminCommand({
|
|
515
|
+
command: "+unbind-sandbox-user",
|
|
516
|
+
description: "Remove a user binding from a sandbox",
|
|
517
|
+
flags: [
|
|
518
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" },
|
|
519
|
+
{ name: "user-id", type: "string", required: true, desc: "Agent user ID from +list-members" }
|
|
520
|
+
],
|
|
521
|
+
risk: "high-risk-write",
|
|
522
|
+
prepare: (ctx) => ({
|
|
523
|
+
method: "DELETE",
|
|
524
|
+
path: withQuery(`/api/admin/sandboxes/${encodeId(ctx.str("id"))}/users`, {
|
|
525
|
+
userId: ctx.str("user-id")
|
|
526
|
+
})
|
|
527
|
+
})
|
|
528
|
+
});
|
|
529
|
+
var removeSandbox = createAdminCommand({
|
|
530
|
+
command: "+remove-sandbox",
|
|
531
|
+
description: "Delete a sandbox and its user bindings",
|
|
532
|
+
flags: [
|
|
533
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox ID from +list-sandboxes" }
|
|
534
|
+
],
|
|
535
|
+
risk: "high-risk-write",
|
|
536
|
+
prepare: (ctx) => ({
|
|
537
|
+
method: "DELETE",
|
|
538
|
+
path: `/api/admin/sandboxes/${encodeId(ctx.str("id"))}`
|
|
539
|
+
})
|
|
540
|
+
});
|
|
541
|
+
var sandboxCommands = [
|
|
542
|
+
listSandboxes,
|
|
543
|
+
getSandboxConfig,
|
|
544
|
+
batchCreateSandboxes,
|
|
545
|
+
updateSandbox,
|
|
546
|
+
setSandboxEnabled,
|
|
547
|
+
startSandbox,
|
|
548
|
+
stopSandbox,
|
|
549
|
+
listSandboxUsers,
|
|
550
|
+
bindSandboxUser,
|
|
551
|
+
unbindSandboxUser,
|
|
552
|
+
removeSandbox
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
// src/commands/te-system/models.ts
|
|
556
|
+
var BIZ_TYPES = ["AE_AGENT", "AI_QA"];
|
|
557
|
+
var listSystemModels = createAdminCommand({
|
|
558
|
+
command: "+list-system-models",
|
|
559
|
+
description: "List system models and current company visibility",
|
|
560
|
+
flags: [],
|
|
561
|
+
risk: "read",
|
|
562
|
+
prepare: () => ({ method: "GET", path: "/api/admin/system-models" })
|
|
563
|
+
});
|
|
564
|
+
var setSystemModelEnabled = createAdminCommand({
|
|
565
|
+
command: "+set-system-model-enabled",
|
|
566
|
+
description: "Enable or disable a system model for the current company",
|
|
567
|
+
flags: [
|
|
568
|
+
{ name: "model-id", type: "string", required: true, desc: "System Model.id from +list-system-models" },
|
|
569
|
+
{ name: "enabled", type: "boolean", required: true, desc: "Whether the model is visible to the company" }
|
|
570
|
+
],
|
|
571
|
+
risk: "write",
|
|
572
|
+
prepare: (ctx) => ({
|
|
573
|
+
method: "PATCH",
|
|
574
|
+
path: `/api/admin/system-models/${encodeId(ctx.str("model-id"))}/company-enabled`,
|
|
575
|
+
body: { enabled: ctx.bool("enabled") }
|
|
576
|
+
})
|
|
577
|
+
});
|
|
578
|
+
var getModelSyncSettings = createAdminCommand({
|
|
579
|
+
command: "+get-model-sync-settings",
|
|
580
|
+
description: "Get the company policy for newly synchronized system models",
|
|
581
|
+
flags: [],
|
|
582
|
+
risk: "read",
|
|
583
|
+
prepare: () => ({ method: "GET", path: "/api/admin/models/sync-settings" })
|
|
584
|
+
});
|
|
585
|
+
var setModelSyncSettings = createAdminCommand({
|
|
586
|
+
command: "+set-model-sync-settings",
|
|
587
|
+
description: "Set whether newly synchronized system models are enabled for the company",
|
|
588
|
+
flags: [
|
|
589
|
+
{
|
|
590
|
+
name: "new-system-models-enabled-by-default",
|
|
591
|
+
type: "boolean",
|
|
592
|
+
required: true,
|
|
593
|
+
desc: "Whether future system models are enabled for this company by default"
|
|
594
|
+
}
|
|
595
|
+
],
|
|
596
|
+
risk: "write",
|
|
597
|
+
prepare: (ctx) => ({
|
|
598
|
+
method: "PATCH",
|
|
599
|
+
path: "/api/admin/models/sync-settings",
|
|
600
|
+
body: {
|
|
601
|
+
newSystemModelsEnabledByDefault: ctx.bool("new-system-models-enabled-by-default")
|
|
602
|
+
}
|
|
603
|
+
})
|
|
604
|
+
});
|
|
605
|
+
var getSystemModelPriceRules = createAdminCommand({
|
|
606
|
+
command: "+get-system-model-price-rules",
|
|
607
|
+
description: "Get the stored pricing rule snapshot for one managed system model",
|
|
608
|
+
flags: [
|
|
609
|
+
{ name: "model-id", type: "string", required: true, desc: "System Model.id from +list-system-models" }
|
|
610
|
+
],
|
|
611
|
+
risk: "read",
|
|
612
|
+
prepare: (ctx) => ({
|
|
613
|
+
method: "GET",
|
|
614
|
+
path: `/api/admin/system-models/${encodeId(ctx.str("model-id"))}/price-rules`
|
|
615
|
+
})
|
|
616
|
+
});
|
|
617
|
+
var listCompanyModels = createAdminCommand({
|
|
618
|
+
command: "+list-company-models",
|
|
619
|
+
description: "List all company models, including disabled models",
|
|
620
|
+
flags: [],
|
|
621
|
+
risk: "read",
|
|
622
|
+
prepare: () => ({ method: "GET", path: "/api/admin/company-models" })
|
|
623
|
+
});
|
|
624
|
+
var setCompanyModelEnabled = createAdminCommand({
|
|
625
|
+
command: "+set-company-model-enabled",
|
|
626
|
+
description: "Enable or disable a company model for all company users",
|
|
627
|
+
flags: [
|
|
628
|
+
{ name: "model-id", type: "string", required: true, desc: "Company Model.id from +list-company-models" },
|
|
629
|
+
{ name: "enabled", type: "boolean", required: true, desc: "Whether the company model is enabled" }
|
|
630
|
+
],
|
|
631
|
+
risk: "write",
|
|
632
|
+
prepare: (ctx) => ({
|
|
633
|
+
method: "PATCH",
|
|
634
|
+
path: `/api/admin/company-models/${encodeId(ctx.str("model-id"))}`,
|
|
635
|
+
body: { enabled: ctx.bool("enabled") }
|
|
636
|
+
})
|
|
637
|
+
});
|
|
638
|
+
var getDefaultModels = createAdminCommand({
|
|
639
|
+
command: "+get-default-models",
|
|
640
|
+
description: "Get the company default models for Agent and AI QA",
|
|
641
|
+
flags: [],
|
|
642
|
+
risk: "read",
|
|
643
|
+
prepare: () => ({ method: "GET", path: "/api/admin/models/default-config" })
|
|
644
|
+
});
|
|
645
|
+
var setDefaultModel = createAdminCommand({
|
|
646
|
+
command: "+set-default-model",
|
|
647
|
+
description: "Set the company default model for Agent or AI QA",
|
|
648
|
+
flags: [
|
|
649
|
+
{ name: "model-id", type: "string", required: true, desc: "Model.id from a system/company model list" },
|
|
650
|
+
{
|
|
651
|
+
name: "biz-type",
|
|
652
|
+
type: "string",
|
|
653
|
+
default: "AE_AGENT",
|
|
654
|
+
desc: `Default model slot: ${BIZ_TYPES.join(" | ")}`
|
|
655
|
+
}
|
|
656
|
+
],
|
|
657
|
+
risk: "write",
|
|
658
|
+
validate: (ctx) => assertEnum("biz-type", optionalString(ctx, "biz-type"), BIZ_TYPES),
|
|
659
|
+
prepare: (ctx) => ({
|
|
660
|
+
method: "POST",
|
|
661
|
+
path: "/api/admin/models/default-config",
|
|
662
|
+
body: {
|
|
663
|
+
modelId: ctx.str("model-id"),
|
|
664
|
+
bizType: ctx.str("biz-type") || "AE_AGENT"
|
|
665
|
+
}
|
|
666
|
+
})
|
|
667
|
+
});
|
|
668
|
+
var clearDefaultModel = createAdminCommand({
|
|
669
|
+
command: "+clear-default-model",
|
|
670
|
+
description: "Clear the company default model for Agent or AI QA",
|
|
671
|
+
flags: [
|
|
672
|
+
{
|
|
673
|
+
name: "biz-type",
|
|
674
|
+
type: "string",
|
|
675
|
+
default: "AE_AGENT",
|
|
676
|
+
desc: `Default model slot: ${BIZ_TYPES.join(" | ")}`
|
|
677
|
+
}
|
|
678
|
+
],
|
|
679
|
+
risk: "high-risk-write",
|
|
680
|
+
validate: (ctx) => assertEnum("biz-type", optionalString(ctx, "biz-type"), BIZ_TYPES),
|
|
681
|
+
prepare: (ctx) => ({
|
|
682
|
+
method: "DELETE",
|
|
683
|
+
path: withQuery("/api/admin/models/default-config", {
|
|
684
|
+
bizType: ctx.str("biz-type") || "AE_AGENT"
|
|
685
|
+
})
|
|
686
|
+
})
|
|
687
|
+
});
|
|
688
|
+
var modelCommands = [
|
|
689
|
+
listSystemModels,
|
|
690
|
+
setSystemModelEnabled,
|
|
691
|
+
getModelSyncSettings,
|
|
692
|
+
setModelSyncSettings,
|
|
693
|
+
getSystemModelPriceRules,
|
|
694
|
+
listCompanyModels,
|
|
695
|
+
setCompanyModelEnabled,
|
|
696
|
+
getDefaultModels,
|
|
697
|
+
setDefaultModel,
|
|
698
|
+
clearDefaultModel
|
|
699
|
+
];
|
|
700
|
+
|
|
701
|
+
// src/commands/te-system/usage.ts
|
|
702
|
+
import { resolve } from "path";
|
|
703
|
+
var GROUP_BY = ["user", "model", "date", "app_type"];
|
|
704
|
+
var SORT_BY = ["totalTokens", "cost", "share", "requestCount"];
|
|
705
|
+
var SORT_DIRECTIONS2 = ["asc", "desc"];
|
|
706
|
+
var EXPORT_SCOPES = ["full", "drill"];
|
|
707
|
+
var DATE_PATTERN = "^\\d{4}-\\d{2}-\\d{2}$";
|
|
708
|
+
function validateDatePair(ctx, required, supportsDays = false) {
|
|
709
|
+
const startDate = optionalString(ctx, "start-date");
|
|
710
|
+
const endDate = optionalString(ctx, "end-date");
|
|
711
|
+
const days = ctx.optionalNum("days");
|
|
712
|
+
if (Boolean(startDate) !== Boolean(endDate)) {
|
|
713
|
+
throw new CliValidationError("--start-date and --end-date must be provided together");
|
|
714
|
+
}
|
|
715
|
+
if (required && (!startDate || !endDate)) {
|
|
716
|
+
throw new CliValidationError("--start-date and --end-date are required");
|
|
717
|
+
}
|
|
718
|
+
if (supportsDays && days !== void 0 && startDate) {
|
|
719
|
+
throw new CliValidationError("--days cannot be combined with --start-date/--end-date");
|
|
720
|
+
}
|
|
721
|
+
if (startDate && endDate && startDate > endDate) {
|
|
722
|
+
throw new CliValidationError("--start-date cannot be later than --end-date");
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function usageRangeQuery(ctx) {
|
|
726
|
+
return {
|
|
727
|
+
days: ctx.optionalNum("days"),
|
|
728
|
+
startDate: optionalString(ctx, "start-date"),
|
|
729
|
+
endDate: optionalString(ctx, "end-date")
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function usageCombinationQuery(ctx) {
|
|
733
|
+
return {
|
|
734
|
+
startDate: ctx.str("start-date"),
|
|
735
|
+
endDate: ctx.str("end-date"),
|
|
736
|
+
parentDimension: ctx.str("parent-dimension"),
|
|
737
|
+
openId: optionalString(ctx, "open-id"),
|
|
738
|
+
modelId: optionalString(ctx, "model-id"),
|
|
739
|
+
modelScope: optionalString(ctx, "model-scope"),
|
|
740
|
+
appType: optionalString(ctx, "app-type"),
|
|
741
|
+
date: optionalString(ctx, "date"),
|
|
742
|
+
page: ctx.optionalNum("page"),
|
|
743
|
+
pageSize: ctx.optionalNum("page-size"),
|
|
744
|
+
sortBy: optionalString(ctx, "sort-by") || "totalTokens",
|
|
745
|
+
sortDir: optionalString(ctx, "sort-dir") || "desc"
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function validateUsageCombination(ctx) {
|
|
749
|
+
validateDatePair(ctx, true);
|
|
750
|
+
const parent = optionalString(ctx, "parent-dimension");
|
|
751
|
+
assertEnum("parent-dimension", parent, GROUP_BY);
|
|
752
|
+
assertEnum("sort-by", optionalString(ctx, "sort-by"), SORT_BY);
|
|
753
|
+
assertEnum("sort-dir", optionalString(ctx, "sort-dir"), SORT_DIRECTIONS2);
|
|
754
|
+
const openId = optionalString(ctx, "open-id");
|
|
755
|
+
const modelId = optionalString(ctx, "model-id");
|
|
756
|
+
const modelScope = optionalString(ctx, "model-scope");
|
|
757
|
+
const appType = optionalString(ctx, "app-type");
|
|
758
|
+
const date = optionalString(ctx, "date");
|
|
759
|
+
const supplied = [openId, modelId, modelScope, appType, date].filter(Boolean).length;
|
|
760
|
+
if (parent === "user" && (!openId || supplied !== 1)) {
|
|
761
|
+
throw new CliValidationError("user drill-down requires only --open-id");
|
|
762
|
+
}
|
|
763
|
+
if (parent === "model" && (!modelId || !modelScope || supplied !== 2)) {
|
|
764
|
+
throw new CliValidationError("model drill-down requires only --model-id and --model-scope");
|
|
765
|
+
}
|
|
766
|
+
if (parent === "app_type" && (!appType || supplied !== 1)) {
|
|
767
|
+
throw new CliValidationError("app_type drill-down requires only --app-type");
|
|
768
|
+
}
|
|
769
|
+
if (parent === "date" && (!date || supplied !== 1)) {
|
|
770
|
+
throw new CliValidationError("date drill-down requires only --date");
|
|
771
|
+
}
|
|
772
|
+
if (date && (date < ctx.str("start-date") || date > ctx.str("end-date"))) {
|
|
773
|
+
throw new CliValidationError("--date must be within --start-date and --end-date");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
var COMBINATION_FLAGS = [
|
|
777
|
+
{ name: "start-date", type: "string", required: true, pattern: DATE_PATTERN, desc: "Start date (YYYY-MM-DD)" },
|
|
778
|
+
{ name: "end-date", type: "string", required: true, pattern: DATE_PATTERN, desc: "End date (YYYY-MM-DD)" },
|
|
779
|
+
{ name: "parent-dimension", type: "string", required: true, desc: `Parent dimension: ${GROUP_BY.join(" | ")}` },
|
|
780
|
+
{ name: "open-id", type: "string", desc: "Required only for user drill-down" },
|
|
781
|
+
{ name: "model-id", type: "string", desc: "Required only for model drill-down" },
|
|
782
|
+
{ name: "model-scope", type: "string", desc: "Required only for model drill-down" },
|
|
783
|
+
{ name: "app-type", type: "string", desc: "Required only for app_type drill-down" },
|
|
784
|
+
{ name: "date", type: "string", pattern: DATE_PATTERN, desc: "Required only for date drill-down" },
|
|
785
|
+
{ name: "page", type: "number", min: 1, desc: "Page number (default: 1)" },
|
|
786
|
+
{ name: "page-size", type: "number", min: 1, max: 100, desc: "Page size (1-100, default: 20)" },
|
|
787
|
+
{ name: "sort-by", type: "string", default: "totalTokens", desc: `Sort by: ${SORT_BY.join(" | ")}` },
|
|
788
|
+
{ name: "sort-dir", type: "string", default: "desc", desc: `Sort direction: ${SORT_DIRECTIONS2.join(" | ")}` }
|
|
789
|
+
];
|
|
790
|
+
var getUsageSummary = createAdminCommand({
|
|
791
|
+
command: "+get-usage-summary",
|
|
792
|
+
description: "Get company token and cost usage summary",
|
|
793
|
+
flags: [
|
|
794
|
+
{ name: "days", type: "number", min: 1, max: 365, desc: "Relative range in days (1-365, default: 30)" },
|
|
795
|
+
{
|
|
796
|
+
name: "start-date",
|
|
797
|
+
type: "string",
|
|
798
|
+
pattern: DATE_PATTERN,
|
|
799
|
+
desc: "Start date (YYYY-MM-DD); requires --end-date"
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
name: "end-date",
|
|
803
|
+
type: "string",
|
|
804
|
+
pattern: DATE_PATTERN,
|
|
805
|
+
desc: "End date (YYYY-MM-DD); requires --start-date"
|
|
806
|
+
},
|
|
807
|
+
{ name: "refresh", type: "boolean", desc: "Bypass the cached overview and refresh from the source" }
|
|
808
|
+
],
|
|
809
|
+
risk: "read",
|
|
810
|
+
validate: (ctx) => validateDatePair(ctx, false, true),
|
|
811
|
+
prepare: (ctx) => ({
|
|
812
|
+
method: "GET",
|
|
813
|
+
path: withQuery("/api/admin/stats/summary", {
|
|
814
|
+
...usageRangeQuery(ctx),
|
|
815
|
+
refresh: ctx.bool("refresh") ? "true" : void 0
|
|
816
|
+
})
|
|
817
|
+
})
|
|
818
|
+
});
|
|
819
|
+
var getUsageDetails = createAdminCommand({
|
|
820
|
+
command: "+get-usage-details",
|
|
821
|
+
description: "Get paginated company usage details grouped by one dimension",
|
|
822
|
+
flags: [
|
|
823
|
+
{
|
|
824
|
+
name: "start-date",
|
|
825
|
+
type: "string",
|
|
826
|
+
required: true,
|
|
827
|
+
pattern: DATE_PATTERN,
|
|
828
|
+
desc: "Start date (YYYY-MM-DD)"
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
name: "end-date",
|
|
832
|
+
type: "string",
|
|
833
|
+
required: true,
|
|
834
|
+
pattern: DATE_PATTERN,
|
|
835
|
+
desc: "End date (YYYY-MM-DD)"
|
|
836
|
+
},
|
|
837
|
+
{ name: "group-by", type: "string", default: "user", desc: `Group by: ${GROUP_BY.join(" | ")}` },
|
|
838
|
+
{ name: "search", type: "string", maxLength: 100, desc: "Optional search text (max 100 characters)" },
|
|
839
|
+
{ name: "open-id", type: "string", desc: "Filter by TE user openId" },
|
|
840
|
+
{ name: "model-id", type: "string", desc: "Filter by model ID" },
|
|
841
|
+
{ name: "model-scope", type: "string", desc: "Model scope; requires --model-id" },
|
|
842
|
+
{ name: "app-type", type: "string", desc: "Filter by product/application type" },
|
|
843
|
+
{ name: "page", type: "number", min: 1, desc: "Page number (default: 1)" },
|
|
844
|
+
{ name: "page-size", type: "number", min: 1, max: 100, desc: "Page size (1-100, default: 20)" },
|
|
845
|
+
{ name: "sort-by", type: "string", default: "totalTokens", desc: `Sort by: ${SORT_BY.join(" | ")}` },
|
|
846
|
+
{ name: "sort-dir", type: "string", default: "desc", desc: `Sort direction: ${SORT_DIRECTIONS2.join(" | ")}` }
|
|
847
|
+
],
|
|
848
|
+
risk: "read",
|
|
849
|
+
validate: (ctx) => {
|
|
850
|
+
validateDatePair(ctx, true);
|
|
851
|
+
assertEnum("group-by", optionalString(ctx, "group-by"), GROUP_BY);
|
|
852
|
+
assertEnum("sort-by", optionalString(ctx, "sort-by"), SORT_BY);
|
|
853
|
+
assertEnum("sort-dir", optionalString(ctx, "sort-dir"), SORT_DIRECTIONS2);
|
|
854
|
+
if (optionalString(ctx, "model-scope") && !optionalString(ctx, "model-id")) {
|
|
855
|
+
throw new CliValidationError("--model-scope requires --model-id");
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
prepare: (ctx) => ({
|
|
859
|
+
method: "GET",
|
|
860
|
+
path: withQuery("/api/admin/stats/details", {
|
|
861
|
+
startDate: ctx.str("start-date"),
|
|
862
|
+
endDate: ctx.str("end-date"),
|
|
863
|
+
groupBy: ctx.str("group-by") || "user",
|
|
864
|
+
search: optionalString(ctx, "search"),
|
|
865
|
+
openId: optionalString(ctx, "open-id"),
|
|
866
|
+
modelId: optionalString(ctx, "model-id"),
|
|
867
|
+
modelScope: optionalString(ctx, "model-scope"),
|
|
868
|
+
appType: optionalString(ctx, "app-type"),
|
|
869
|
+
page: ctx.optionalNum("page"),
|
|
870
|
+
pageSize: ctx.optionalNum("page-size"),
|
|
871
|
+
sortBy: ctx.str("sort-by") || "totalTokens",
|
|
872
|
+
sortDir: ctx.str("sort-dir") || "desc"
|
|
873
|
+
})
|
|
874
|
+
})
|
|
875
|
+
});
|
|
876
|
+
var getAgentToolCalls = createAdminCommand({
|
|
877
|
+
command: "+get-agent-tool-calls",
|
|
878
|
+
description: "Get the company Agent tool-call count for a usage range",
|
|
879
|
+
flags: [
|
|
880
|
+
{ name: "days", type: "number", min: 1, max: 365, desc: "Relative range in days (1-365, default: 30)" },
|
|
881
|
+
{ name: "start-date", type: "string", pattern: DATE_PATTERN, desc: "Start date (YYYY-MM-DD); requires --end-date" },
|
|
882
|
+
{ name: "end-date", type: "string", pattern: DATE_PATTERN, desc: "End date (YYYY-MM-DD); requires --start-date" },
|
|
883
|
+
{ name: "refresh", type: "boolean", desc: "Bypass the cached overview and refresh from the source" }
|
|
884
|
+
],
|
|
885
|
+
risk: "read",
|
|
886
|
+
validate: (ctx) => validateDatePair(ctx, false, true),
|
|
887
|
+
prepare: (ctx) => ({
|
|
888
|
+
method: "GET",
|
|
889
|
+
path: withQuery("/api/admin/stats/agent-tool-calls", {
|
|
890
|
+
...usageRangeQuery(ctx),
|
|
891
|
+
refresh: ctx.bool("refresh") ? "true" : void 0
|
|
892
|
+
})
|
|
893
|
+
})
|
|
894
|
+
});
|
|
895
|
+
var getUsageCombinations = createAdminCommand({
|
|
896
|
+
command: "+get-usage-combinations",
|
|
897
|
+
description: "Drill one usage group into the remaining dimensions",
|
|
898
|
+
flags: COMBINATION_FLAGS,
|
|
899
|
+
risk: "read",
|
|
900
|
+
validate: validateUsageCombination,
|
|
901
|
+
prepare: (ctx) => ({
|
|
902
|
+
method: "GET",
|
|
903
|
+
path: withQuery("/api/admin/stats/combinations", usageCombinationQuery(ctx))
|
|
904
|
+
})
|
|
905
|
+
});
|
|
906
|
+
var exportUsage = createAdminDownloadCommand({
|
|
907
|
+
command: "+export-usage",
|
|
908
|
+
description: "Export filtered one-dimension usage groups to a CSV file",
|
|
909
|
+
flags: [
|
|
910
|
+
{ name: "start-date", type: "string", required: true, pattern: DATE_PATTERN, desc: "Start date (YYYY-MM-DD)" },
|
|
911
|
+
{ name: "end-date", type: "string", required: true, pattern: DATE_PATTERN, desc: "End date (YYYY-MM-DD)" },
|
|
912
|
+
{ name: "group-by", type: "string", default: "user", desc: `Group by: ${GROUP_BY.join(" | ")}` },
|
|
913
|
+
{ name: "search", type: "string", maxLength: 100, desc: "Optional search text (max 100 characters)" },
|
|
914
|
+
{ name: "open-id", type: "string", desc: "Filter by TE user openId" },
|
|
915
|
+
{ name: "model-id", type: "string", desc: "Filter by model ID" },
|
|
916
|
+
{ name: "model-scope", type: "string", desc: "Model scope; requires --model-id" },
|
|
917
|
+
{ name: "app-type", type: "string", desc: "Filter by product/application type" },
|
|
918
|
+
{ name: "sort-by", type: "string", default: "totalTokens", desc: `Sort by: ${SORT_BY.join(" | ")}` },
|
|
919
|
+
{ name: "sort-dir", type: "string", default: "desc", desc: `Sort direction: ${SORT_DIRECTIONS2.join(" | ")}` },
|
|
920
|
+
{ name: "output", type: "string", required: true, desc: "New local CSV path; existing files are never overwritten" }
|
|
921
|
+
],
|
|
922
|
+
validate: (ctx) => {
|
|
923
|
+
validateDatePair(ctx, true);
|
|
924
|
+
assertEnum("group-by", optionalString(ctx, "group-by"), GROUP_BY);
|
|
925
|
+
assertEnum("sort-by", optionalString(ctx, "sort-by"), SORT_BY);
|
|
926
|
+
assertEnum("sort-dir", optionalString(ctx, "sort-dir"), SORT_DIRECTIONS2);
|
|
927
|
+
if (optionalString(ctx, "model-scope") && !optionalString(ctx, "model-id")) {
|
|
928
|
+
throw new CliValidationError("--model-scope requires --model-id");
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
prepare: (ctx) => ({
|
|
932
|
+
path: withQuery("/api/admin/stats/export", {
|
|
933
|
+
startDate: ctx.str("start-date"),
|
|
934
|
+
endDate: ctx.str("end-date"),
|
|
935
|
+
groupBy: ctx.str("group-by") || "user",
|
|
936
|
+
search: optionalString(ctx, "search"),
|
|
937
|
+
openId: optionalString(ctx, "open-id"),
|
|
938
|
+
modelId: optionalString(ctx, "model-id"),
|
|
939
|
+
modelScope: optionalString(ctx, "model-scope"),
|
|
940
|
+
appType: optionalString(ctx, "app-type"),
|
|
941
|
+
sortBy: ctx.str("sort-by") || "totalTokens",
|
|
942
|
+
sortDir: ctx.str("sort-dir") || "desc"
|
|
943
|
+
}),
|
|
944
|
+
outputPath: resolve(ctx.str("output"))
|
|
945
|
+
})
|
|
946
|
+
});
|
|
947
|
+
var exportUsageDetails = createAdminDownloadCommand({
|
|
948
|
+
command: "+export-usage-details",
|
|
949
|
+
description: "Export full or drill-down multi-dimension usage details to a streamed CSV file",
|
|
950
|
+
flags: [
|
|
951
|
+
{ name: "scope", type: "string", required: true, desc: `Export scope: ${EXPORT_SCOPES.join(" | ")}` },
|
|
952
|
+
{ name: "days", type: "number", min: 1, max: 365, desc: "Relative range for full export (1-365, default: 30)" },
|
|
953
|
+
{ name: "start-date", type: "string", pattern: DATE_PATTERN, desc: "Start date (YYYY-MM-DD)" },
|
|
954
|
+
{ name: "end-date", type: "string", pattern: DATE_PATTERN, desc: "End date (YYYY-MM-DD)" },
|
|
955
|
+
...COMBINATION_FLAGS.filter((flag) => !["start-date", "end-date", "page", "page-size", "sort-by", "sort-dir"].includes(flag.name)).map((flag) => ({ ...flag, required: false })),
|
|
956
|
+
{ name: "output", type: "string", required: true, desc: "New local CSV path; existing files are never overwritten" }
|
|
957
|
+
],
|
|
958
|
+
validate: (ctx) => {
|
|
959
|
+
const scope = optionalString(ctx, "scope");
|
|
960
|
+
assertEnum("scope", scope, EXPORT_SCOPES);
|
|
961
|
+
if (scope === "drill") {
|
|
962
|
+
validateUsageCombination(ctx);
|
|
963
|
+
} else {
|
|
964
|
+
validateDatePair(ctx, false, true);
|
|
965
|
+
if (optionalString(ctx, "parent-dimension") || optionalString(ctx, "open-id") || optionalString(ctx, "model-id") || optionalString(ctx, "model-scope") || optionalString(ctx, "app-type") || optionalString(ctx, "date")) {
|
|
966
|
+
throw new CliValidationError("full export does not accept drill-down filters");
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
prepare: (ctx) => ({
|
|
971
|
+
path: withQuery("/api/admin/stats/detailed-export", {
|
|
972
|
+
scope: ctx.str("scope"),
|
|
973
|
+
...usageRangeQuery(ctx),
|
|
974
|
+
...ctx.str("scope") === "drill" ? usageCombinationQuery(ctx) : {}
|
|
975
|
+
}),
|
|
976
|
+
outputPath: resolve(ctx.str("output"))
|
|
977
|
+
})
|
|
978
|
+
});
|
|
979
|
+
var usageCommands = [
|
|
980
|
+
getUsageSummary,
|
|
981
|
+
getUsageDetails,
|
|
982
|
+
getAgentToolCalls,
|
|
983
|
+
getUsageCombinations,
|
|
984
|
+
exportUsage,
|
|
985
|
+
exportUsageDetails
|
|
986
|
+
];
|
|
987
|
+
|
|
988
|
+
// src/commands/te-system/cost-control.ts
|
|
989
|
+
function createRuleBody(ctx) {
|
|
990
|
+
const rule = requireJsonObject(ctx, "rule");
|
|
991
|
+
for (const key of ["name", "subjectType", "periodType"]) {
|
|
992
|
+
if (typeof rule[key] !== "string" || !rule[key].trim()) {
|
|
993
|
+
throw new CliValidationError(`--rule.${key} is required`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return rule;
|
|
997
|
+
}
|
|
998
|
+
var getCostSummary = createAdminCommand({
|
|
999
|
+
command: "+get-cost-summary",
|
|
1000
|
+
description: "Get company cost, quota, and usage summary",
|
|
1001
|
+
flags: [],
|
|
1002
|
+
risk: "read",
|
|
1003
|
+
prepare: () => ({ method: "GET", path: "/api/admin/cost-control/summary" })
|
|
1004
|
+
});
|
|
1005
|
+
var getBalance = createAdminCommand({
|
|
1006
|
+
command: "+get-balance",
|
|
1007
|
+
description: "Get the current company model account balance and currency",
|
|
1008
|
+
flags: [],
|
|
1009
|
+
risk: "read",
|
|
1010
|
+
prepare: () => ({ method: "GET", path: "/api/admin/cost-control/balance" })
|
|
1011
|
+
});
|
|
1012
|
+
var listOverLimitUsers = createAdminCommand({
|
|
1013
|
+
command: "+list-over-limit-users",
|
|
1014
|
+
description: "List company members currently over cost or token quota limits",
|
|
1015
|
+
flags: [],
|
|
1016
|
+
risk: "read",
|
|
1017
|
+
prepare: () => ({ method: "GET", path: "/api/admin/cost-control/over-limit-users" })
|
|
1018
|
+
});
|
|
1019
|
+
var getBalanceAlert = createAdminCommand({
|
|
1020
|
+
command: "+get-balance-alert",
|
|
1021
|
+
description: "Get the company balance alert configuration and current status",
|
|
1022
|
+
flags: [],
|
|
1023
|
+
risk: "read",
|
|
1024
|
+
prepare: () => ({ method: "GET", path: "/api/admin/cost-control/balance-alert" })
|
|
1025
|
+
});
|
|
1026
|
+
var setBalanceAlert = createAdminCommand({
|
|
1027
|
+
command: "+set-balance-alert",
|
|
1028
|
+
description: "Enable, update, or disable the company balance alert",
|
|
1029
|
+
flags: [
|
|
1030
|
+
{ name: "enabled", type: "boolean", required: true, desc: "Whether the balance alert is enabled" },
|
|
1031
|
+
{ name: "threshold", type: "string", desc: "Positive alert threshold; required when enabling" }
|
|
1032
|
+
],
|
|
1033
|
+
risk: "write",
|
|
1034
|
+
validate: (ctx) => {
|
|
1035
|
+
if (ctx.bool("enabled") && !optionalString(ctx, "threshold")) {
|
|
1036
|
+
throw new CliValidationError("--threshold is required when --enabled is true");
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
prepare: (ctx) => ({
|
|
1040
|
+
method: "PATCH",
|
|
1041
|
+
path: "/api/admin/cost-control/balance-alert",
|
|
1042
|
+
body: {
|
|
1043
|
+
enabled: ctx.bool("enabled"),
|
|
1044
|
+
...optionalString(ctx, "threshold") ? { threshold: ctx.str("threshold") } : {}
|
|
1045
|
+
}
|
|
1046
|
+
})
|
|
1047
|
+
});
|
|
1048
|
+
var listQuotaRules = createAdminCommand({
|
|
1049
|
+
command: "+list-quota-rules",
|
|
1050
|
+
description: "List company cost and token quota rules",
|
|
1051
|
+
flags: [],
|
|
1052
|
+
risk: "read",
|
|
1053
|
+
prepare: () => ({ method: "GET", path: "/api/admin/quota-rules" })
|
|
1054
|
+
});
|
|
1055
|
+
var createQuotaRule = createAdminCommand({
|
|
1056
|
+
command: "+create-quota-rule",
|
|
1057
|
+
description: "Create a company or user cost/token quota rule",
|
|
1058
|
+
flags: [
|
|
1059
|
+
{
|
|
1060
|
+
name: "rule",
|
|
1061
|
+
type: "string",
|
|
1062
|
+
required: true,
|
|
1063
|
+
sensitive: true,
|
|
1064
|
+
desc: "Quota rule JSON object, @file, or stdin (-)"
|
|
1065
|
+
}
|
|
1066
|
+
],
|
|
1067
|
+
risk: "write",
|
|
1068
|
+
prepare: (ctx) => ({
|
|
1069
|
+
method: "POST",
|
|
1070
|
+
path: "/api/admin/quota-rules",
|
|
1071
|
+
body: createRuleBody(ctx)
|
|
1072
|
+
})
|
|
1073
|
+
});
|
|
1074
|
+
var updateQuotaRule = createAdminCommand({
|
|
1075
|
+
command: "+update-quota-rule",
|
|
1076
|
+
description: "Update an existing cost/token quota rule",
|
|
1077
|
+
flags: [
|
|
1078
|
+
{ name: "id", type: "string", required: true, desc: "Quota rule ID from +list-quota-rules" },
|
|
1079
|
+
{
|
|
1080
|
+
name: "rule",
|
|
1081
|
+
type: "string",
|
|
1082
|
+
required: true,
|
|
1083
|
+
sensitive: true,
|
|
1084
|
+
desc: "Partial quota rule JSON object, @file, or stdin (-)"
|
|
1085
|
+
}
|
|
1086
|
+
],
|
|
1087
|
+
risk: "write",
|
|
1088
|
+
prepare: (ctx) => ({
|
|
1089
|
+
method: "PATCH",
|
|
1090
|
+
path: `/api/admin/quota-rules/${encodeId(ctx.str("id"))}`,
|
|
1091
|
+
body: requireJsonObject(ctx, "rule")
|
|
1092
|
+
})
|
|
1093
|
+
});
|
|
1094
|
+
var removeQuotaRule = createAdminCommand({
|
|
1095
|
+
command: "+remove-quota-rule",
|
|
1096
|
+
description: "Delete a cost/token quota rule",
|
|
1097
|
+
flags: [
|
|
1098
|
+
{ name: "id", type: "string", required: true, desc: "Quota rule ID from +list-quota-rules" }
|
|
1099
|
+
],
|
|
1100
|
+
risk: "high-risk-write",
|
|
1101
|
+
prepare: (ctx) => ({
|
|
1102
|
+
method: "DELETE",
|
|
1103
|
+
path: `/api/admin/quota-rules/${encodeId(ctx.str("id"))}`
|
|
1104
|
+
})
|
|
1105
|
+
});
|
|
1106
|
+
var bindQuotaRuleUser = createAdminCommand({
|
|
1107
|
+
command: "+bind-quota-rule-user",
|
|
1108
|
+
description: "Bind a quota rule to a TE user",
|
|
1109
|
+
flags: [
|
|
1110
|
+
{ name: "id", type: "string", required: true, desc: "Quota rule ID from +list-quota-rules" },
|
|
1111
|
+
{ name: "open-id", type: "string", required: true, desc: "TE user openId" }
|
|
1112
|
+
],
|
|
1113
|
+
risk: "write",
|
|
1114
|
+
prepare: (ctx) => ({
|
|
1115
|
+
method: "POST",
|
|
1116
|
+
path: `/api/admin/quota-rules/${encodeId(ctx.str("id"))}/bind-user`,
|
|
1117
|
+
body: { openId: ctx.str("open-id") }
|
|
1118
|
+
})
|
|
1119
|
+
});
|
|
1120
|
+
var costControlCommands = [
|
|
1121
|
+
getCostSummary,
|
|
1122
|
+
getBalance,
|
|
1123
|
+
listOverLimitUsers,
|
|
1124
|
+
getBalanceAlert,
|
|
1125
|
+
setBalanceAlert,
|
|
1126
|
+
listQuotaRules,
|
|
1127
|
+
createQuotaRule,
|
|
1128
|
+
updateQuotaRule,
|
|
1129
|
+
removeQuotaRule,
|
|
1130
|
+
bindQuotaRuleUser
|
|
1131
|
+
];
|
|
1132
|
+
|
|
1133
|
+
// src/commands/te-system/channels.ts
|
|
1134
|
+
var CHANNEL_TYPES = /* @__PURE__ */ new Set(["feishu", "slack", "lark"]);
|
|
1135
|
+
function createChannelBody(ctx) {
|
|
1136
|
+
const channel = requireJsonObject(ctx, "channel");
|
|
1137
|
+
if (typeof channel.name !== "string" || !channel.name.trim()) {
|
|
1138
|
+
throw new CliValidationError("--channel.name is required");
|
|
1139
|
+
}
|
|
1140
|
+
if (typeof channel.type !== "string" || !CHANNEL_TYPES.has(channel.type)) {
|
|
1141
|
+
throw new CliValidationError("--channel.type must be one of: feishu, slack, lark");
|
|
1142
|
+
}
|
|
1143
|
+
if (!channel.config || typeof channel.config !== "object" || Array.isArray(channel.config)) {
|
|
1144
|
+
throw new CliValidationError("--channel.config must be a JSON object");
|
|
1145
|
+
}
|
|
1146
|
+
const config = channel.config;
|
|
1147
|
+
const requiredConfigKeys = channel.type === "slack" ? ["botToken", "appToken"] : ["appId", "appSecret"];
|
|
1148
|
+
for (const key of requiredConfigKeys) {
|
|
1149
|
+
if (typeof config[key] !== "string" || !config[key].trim()) {
|
|
1150
|
+
throw new CliValidationError(`--channel.config.${key} is required for ${channel.type}`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return channel;
|
|
1154
|
+
}
|
|
1155
|
+
function updateChannelBody(ctx) {
|
|
1156
|
+
const channel = requireJsonObject(ctx, "channel");
|
|
1157
|
+
if (Object.keys(channel).length === 0) {
|
|
1158
|
+
throw new CliValidationError("--channel must contain at least one update field");
|
|
1159
|
+
}
|
|
1160
|
+
if ("type" in channel) {
|
|
1161
|
+
throw new CliValidationError("--channel.type cannot be changed after channel creation");
|
|
1162
|
+
}
|
|
1163
|
+
return channel;
|
|
1164
|
+
}
|
|
1165
|
+
var listChannels = createAdminCommand({
|
|
1166
|
+
command: "+list-channels",
|
|
1167
|
+
description: "List configured Feishu, Lark, and Slack channels",
|
|
1168
|
+
flags: [],
|
|
1169
|
+
risk: "read",
|
|
1170
|
+
prepare: () => ({ method: "GET", path: "/api/admin/channels" })
|
|
1171
|
+
});
|
|
1172
|
+
var createChannel = createAdminCommand({
|
|
1173
|
+
command: "+create-channel",
|
|
1174
|
+
description: "Create a Feishu, Lark, or Slack channel",
|
|
1175
|
+
flags: [
|
|
1176
|
+
{
|
|
1177
|
+
name: "channel",
|
|
1178
|
+
type: "string",
|
|
1179
|
+
required: true,
|
|
1180
|
+
sensitive: true,
|
|
1181
|
+
desc: "Channel JSON object, @file, or stdin (-); may contain credentials"
|
|
1182
|
+
}
|
|
1183
|
+
],
|
|
1184
|
+
risk: "write",
|
|
1185
|
+
redactDryRun: true,
|
|
1186
|
+
prepare: (ctx) => ({
|
|
1187
|
+
method: "POST",
|
|
1188
|
+
path: "/api/admin/channels",
|
|
1189
|
+
body: createChannelBody(ctx)
|
|
1190
|
+
})
|
|
1191
|
+
});
|
|
1192
|
+
var updateChannel = createAdminCommand({
|
|
1193
|
+
command: "+update-channel",
|
|
1194
|
+
description: "Update channel settings, credentials, model, prompt, or enabled state",
|
|
1195
|
+
flags: [
|
|
1196
|
+
{ name: "id", type: "string", required: true, desc: "Channel ID from +list-channels" },
|
|
1197
|
+
{
|
|
1198
|
+
name: "channel",
|
|
1199
|
+
type: "string",
|
|
1200
|
+
required: true,
|
|
1201
|
+
sensitive: true,
|
|
1202
|
+
desc: "Partial channel JSON object, @file, or stdin (-); may contain credentials"
|
|
1203
|
+
}
|
|
1204
|
+
],
|
|
1205
|
+
risk: "write",
|
|
1206
|
+
redactDryRun: true,
|
|
1207
|
+
prepare: (ctx) => ({
|
|
1208
|
+
method: "PUT",
|
|
1209
|
+
path: `/api/admin/channels/${encodeId(ctx.str("id"))}`,
|
|
1210
|
+
body: updateChannelBody(ctx)
|
|
1211
|
+
})
|
|
1212
|
+
});
|
|
1213
|
+
var removeChannel = createAdminCommand({
|
|
1214
|
+
command: "+remove-channel",
|
|
1215
|
+
description: "Delete a channel, unbind its users, and stop its runtime connection",
|
|
1216
|
+
flags: [
|
|
1217
|
+
{ name: "id", type: "string", required: true, desc: "Channel ID from +list-channels" }
|
|
1218
|
+
],
|
|
1219
|
+
risk: "high-risk-write",
|
|
1220
|
+
prepare: (ctx) => ({
|
|
1221
|
+
method: "DELETE",
|
|
1222
|
+
path: `/api/admin/channels/${encodeId(ctx.str("id"))}`
|
|
1223
|
+
})
|
|
1224
|
+
});
|
|
1225
|
+
var channelCommands = [
|
|
1226
|
+
listChannels,
|
|
1227
|
+
createChannel,
|
|
1228
|
+
updateChannel,
|
|
1229
|
+
removeChannel
|
|
1230
|
+
];
|
|
1231
|
+
|
|
1232
|
+
// src/commands/te-system/sandbox-tools.ts
|
|
1233
|
+
import { spawn } from "child_process";
|
|
1234
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1235
|
+
import path2 from "path";
|
|
1236
|
+
|
|
1237
|
+
// src/commands/te-system/sandbox-tool-package.ts
|
|
1238
|
+
import { createWriteStream } from "fs";
|
|
1239
|
+
import {
|
|
1240
|
+
lstat,
|
|
1241
|
+
mkdir,
|
|
1242
|
+
mkdtemp,
|
|
1243
|
+
readFile,
|
|
1244
|
+
readdir,
|
|
1245
|
+
readlink,
|
|
1246
|
+
realpath,
|
|
1247
|
+
rm,
|
|
1248
|
+
stat,
|
|
1249
|
+
writeFile
|
|
1250
|
+
} from "fs/promises";
|
|
1251
|
+
import { tmpdir } from "os";
|
|
1252
|
+
import path from "path";
|
|
1253
|
+
import archiver from "archiver";
|
|
1254
|
+
var TOOL_UPLOAD_LIMITS = {
|
|
1255
|
+
maxArchiveBytes: 50 * 1024 * 1024,
|
|
1256
|
+
maxUnpackedBytes: 500 * 1024 * 1024,
|
|
1257
|
+
maxFileBytes: 50 * 1024 * 1024,
|
|
1258
|
+
maxFiles: 1e4
|
|
1259
|
+
};
|
|
1260
|
+
var TOOL_NAME_RE = /^[a-z][a-z0-9._-]{0,63}$/;
|
|
1261
|
+
var EXACT_VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
1262
|
+
var SAFE_VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/;
|
|
1263
|
+
var PACKAGE_NAME_RE = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
1264
|
+
var RESERVED_COMMANDS = /* @__PURE__ */ new Set([
|
|
1265
|
+
"claude",
|
|
1266
|
+
"node",
|
|
1267
|
+
"npm",
|
|
1268
|
+
"npx",
|
|
1269
|
+
"bash",
|
|
1270
|
+
"sh",
|
|
1271
|
+
"python",
|
|
1272
|
+
"python3",
|
|
1273
|
+
"pip",
|
|
1274
|
+
"pip3",
|
|
1275
|
+
"chromium",
|
|
1276
|
+
"git",
|
|
1277
|
+
"lark-cli",
|
|
1278
|
+
"env",
|
|
1279
|
+
"sudo",
|
|
1280
|
+
"curl",
|
|
1281
|
+
"wget"
|
|
1282
|
+
]);
|
|
1283
|
+
function isValidRelativePath(value) {
|
|
1284
|
+
if (!value || value.length > 500) return false;
|
|
1285
|
+
if (/[\x00-\x1f\x7f]/.test(value) || value.includes("\\")) return false;
|
|
1286
|
+
if (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value)) return false;
|
|
1287
|
+
if (value.endsWith("/") || value.includes("//")) return false;
|
|
1288
|
+
const segments = value.split("/");
|
|
1289
|
+
return !segments.some(
|
|
1290
|
+
(segment) => segment === "" || segment === "." || segment === ".." || segment === "current"
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
function requirePlainObject(value, label) {
|
|
1294
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1295
|
+
throw new CliValidationError(`${label} must be a JSON object`);
|
|
1296
|
+
}
|
|
1297
|
+
return value;
|
|
1298
|
+
}
|
|
1299
|
+
function rejectUnknownKeys(value, keys, label) {
|
|
1300
|
+
const unknown = Object.keys(value).filter((key) => !keys.includes(key));
|
|
1301
|
+
if (unknown.length > 0) {
|
|
1302
|
+
throw new CliValidationError(
|
|
1303
|
+
`${label} contains unsupported fields: ${unknown.join(", ")}`
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function validateToolManifest(value) {
|
|
1308
|
+
const manifest = requirePlainObject(value, "tool.json");
|
|
1309
|
+
rejectUnknownKeys(
|
|
1310
|
+
manifest,
|
|
1311
|
+
[
|
|
1312
|
+
"schemaVersion",
|
|
1313
|
+
"name",
|
|
1314
|
+
"displayName",
|
|
1315
|
+
"description",
|
|
1316
|
+
"version",
|
|
1317
|
+
"commands"
|
|
1318
|
+
],
|
|
1319
|
+
"tool.json"
|
|
1320
|
+
);
|
|
1321
|
+
if (manifest.schemaVersion !== 1) {
|
|
1322
|
+
throw new CliValidationError("tool.json schemaVersion must be 1");
|
|
1323
|
+
}
|
|
1324
|
+
if (typeof manifest.name !== "string" || !TOOL_NAME_RE.test(manifest.name)) {
|
|
1325
|
+
throw new CliValidationError(
|
|
1326
|
+
"tool.json name must start with a lowercase letter and contain only lowercase letters, numbers, ., _, or - (max 64)"
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
if (typeof manifest.version !== "string" || !SAFE_VERSION_RE.test(manifest.version)) {
|
|
1330
|
+
throw new CliValidationError(
|
|
1331
|
+
"tool.json version contains unsupported characters or is too long"
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
if (manifest.displayName !== void 0 && (typeof manifest.displayName !== "string" || manifest.displayName.length > 200)) {
|
|
1335
|
+
throw new CliValidationError(
|
|
1336
|
+
"tool.json displayName must be a string of at most 200 characters"
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
if (manifest.description !== void 0 && (typeof manifest.description !== "string" || manifest.description.length > 2e3)) {
|
|
1340
|
+
throw new CliValidationError(
|
|
1341
|
+
"tool.json description must be a string of at most 2000 characters"
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
if (!Array.isArray(manifest.commands) || manifest.commands.length < 1 || manifest.commands.length > 64) {
|
|
1345
|
+
throw new CliValidationError(
|
|
1346
|
+
"tool.json commands must contain between 1 and 64 entries"
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
const commandNames = /* @__PURE__ */ new Set();
|
|
1350
|
+
const commands2 = manifest.commands.map(
|
|
1351
|
+
(rawCommand, index) => {
|
|
1352
|
+
const command = requirePlainObject(
|
|
1353
|
+
rawCommand,
|
|
1354
|
+
`tool.json commands[${index}]`
|
|
1355
|
+
);
|
|
1356
|
+
rejectUnknownKeys(
|
|
1357
|
+
command,
|
|
1358
|
+
["name", "entry", "runtime"],
|
|
1359
|
+
`tool.json commands[${index}]`
|
|
1360
|
+
);
|
|
1361
|
+
if (typeof command.name !== "string" || !TOOL_NAME_RE.test(command.name)) {
|
|
1362
|
+
throw new CliValidationError(
|
|
1363
|
+
`tool.json commands[${index}].name is invalid`
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
if (RESERVED_COMMANDS.has(command.name)) {
|
|
1367
|
+
throw new CliValidationError(
|
|
1368
|
+
`tool.json command name is reserved: ${command.name}`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
if (commandNames.has(command.name)) {
|
|
1372
|
+
throw new CliValidationError(
|
|
1373
|
+
`tool.json contains duplicate command name: ${command.name}`
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
commandNames.add(command.name);
|
|
1377
|
+
if (typeof command.entry !== "string" || !isValidRelativePath(command.entry)) {
|
|
1378
|
+
throw new CliValidationError(
|
|
1379
|
+
`tool.json commands[${index}].entry must be a safe relative path`
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
if (command.runtime !== "node" && command.runtime !== "native") {
|
|
1383
|
+
throw new CliValidationError(
|
|
1384
|
+
`tool.json commands[${index}].runtime must be node or native`
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
return {
|
|
1388
|
+
name: command.name,
|
|
1389
|
+
entry: command.entry,
|
|
1390
|
+
runtime: command.runtime
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
);
|
|
1394
|
+
return {
|
|
1395
|
+
schemaVersion: 1,
|
|
1396
|
+
name: manifest.name,
|
|
1397
|
+
...manifest.displayName === void 0 ? {} : { displayName: manifest.displayName },
|
|
1398
|
+
...manifest.description === void 0 ? {} : { description: manifest.description },
|
|
1399
|
+
version: manifest.version,
|
|
1400
|
+
commands: commands2
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function parseExactNpmPackageSpec(raw) {
|
|
1404
|
+
const value = raw.trim();
|
|
1405
|
+
const separator = value.lastIndexOf("@");
|
|
1406
|
+
const name = separator > 0 ? value.slice(0, separator) : "";
|
|
1407
|
+
const version = separator > 0 ? value.slice(separator + 1) : "";
|
|
1408
|
+
if (!PACKAGE_NAME_RE.test(name) || !EXACT_VERSION_RE.test(version)) {
|
|
1409
|
+
throw new CliValidationError(
|
|
1410
|
+
"--package must be an exact registry package version, for example eslint@9.32.0 or @scope/cli@1.2.3",
|
|
1411
|
+
{
|
|
1412
|
+
hint: "Tags, ranges, URLs, Git sources, aliases, and local paths are not allowed."
|
|
1413
|
+
}
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
return { name, version, spec: `${name}@${version}` };
|
|
1417
|
+
}
|
|
1418
|
+
function defaultCommandName(packageName) {
|
|
1419
|
+
const slash = packageName.lastIndexOf("/");
|
|
1420
|
+
return slash >= 0 ? packageName.slice(slash + 1) : packageName;
|
|
1421
|
+
}
|
|
1422
|
+
function packagePath(packageName) {
|
|
1423
|
+
return path.posix.join("node_modules", ...packageName.split("/"));
|
|
1424
|
+
}
|
|
1425
|
+
function manifestFromNpmPackage(expected, packageJson, toolName) {
|
|
1426
|
+
if (packageJson.name !== expected.name || packageJson.version !== expected.version) {
|
|
1427
|
+
throw new CliValidationError(
|
|
1428
|
+
`npm installed ${String(packageJson.name)}@${String(packageJson.version)}, expected ${expected.spec}`
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
const bins = [];
|
|
1432
|
+
if (typeof packageJson.bin === "string") {
|
|
1433
|
+
bins.push([defaultCommandName(expected.name), packageJson.bin]);
|
|
1434
|
+
} else if (packageJson.bin && typeof packageJson.bin === "object" && !Array.isArray(packageJson.bin)) {
|
|
1435
|
+
for (const [name, entry] of Object.entries(
|
|
1436
|
+
packageJson.bin
|
|
1437
|
+
)) {
|
|
1438
|
+
if (typeof entry !== "string") {
|
|
1439
|
+
throw new CliValidationError(
|
|
1440
|
+
`npm package bin entry must be a string: ${name}`
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
bins.push([name, entry]);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if (bins.length === 0) {
|
|
1447
|
+
throw new CliValidationError(
|
|
1448
|
+
`npm package ${expected.spec} does not expose a CLI through package.json bin`
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
const commands2 = bins.map(([name, entry]) => {
|
|
1452
|
+
const normalizedEntry = entry.replace(/^\.\//, "");
|
|
1453
|
+
if (!isValidRelativePath(normalizedEntry)) {
|
|
1454
|
+
throw new CliValidationError(
|
|
1455
|
+
`npm package bin path must stay inside the package: ${name}`
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
const packageRoot = packagePath(expected.name);
|
|
1459
|
+
const commandEntry = path.posix.join(packageRoot, normalizedEntry);
|
|
1460
|
+
if (!commandEntry.startsWith(`${packageRoot}/`)) {
|
|
1461
|
+
throw new CliValidationError(
|
|
1462
|
+
`npm package bin path must stay inside the package: ${name}`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
return {
|
|
1466
|
+
name,
|
|
1467
|
+
entry: commandEntry,
|
|
1468
|
+
runtime: "node"
|
|
1469
|
+
};
|
|
1470
|
+
});
|
|
1471
|
+
return validateToolManifest({
|
|
1472
|
+
schemaVersion: 1,
|
|
1473
|
+
name: toolName || defaultCommandName(expected.name),
|
|
1474
|
+
displayName: expected.name,
|
|
1475
|
+
...typeof packageJson.description === "string" ? { description: packageJson.description.slice(0, 2e3) } : {},
|
|
1476
|
+
version: expected.version,
|
|
1477
|
+
commands: commands2
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
async function pathExists(target) {
|
|
1481
|
+
try {
|
|
1482
|
+
await lstat(target);
|
|
1483
|
+
return true;
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
if (error.code === "ENOENT") return false;
|
|
1486
|
+
throw error;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
async function loadManifest(root, manifestPath) {
|
|
1490
|
+
const rootManifest = path.join(root, "tool.json");
|
|
1491
|
+
const explicitPath = manifestPath ? path.resolve(manifestPath) : rootManifest;
|
|
1492
|
+
if (manifestPath && explicitPath !== rootManifest && await pathExists(rootManifest)) {
|
|
1493
|
+
throw new CliValidationError(
|
|
1494
|
+
"The upload root already contains tool.json; remove --manifest or remove the duplicate root manifest"
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
let raw;
|
|
1498
|
+
try {
|
|
1499
|
+
raw = await readFile(explicitPath, "utf8");
|
|
1500
|
+
} catch {
|
|
1501
|
+
throw new CliValidationError(
|
|
1502
|
+
`Unable to read tool manifest: ${explicitPath}`
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
let parsed;
|
|
1506
|
+
try {
|
|
1507
|
+
parsed = JSON.parse(raw);
|
|
1508
|
+
} catch {
|
|
1509
|
+
throw new CliValidationError("tool.json must contain valid UTF-8 JSON");
|
|
1510
|
+
}
|
|
1511
|
+
return { manifest: validateToolManifest(parsed), sourcePath: explicitPath };
|
|
1512
|
+
}
|
|
1513
|
+
function isNpmBinLink(relativePath) {
|
|
1514
|
+
const segments = relativePath.split("/");
|
|
1515
|
+
return segments.length >= 3 && segments[segments.length - 2] === ".bin" && segments[segments.length - 3] === "node_modules";
|
|
1516
|
+
}
|
|
1517
|
+
function ensureInsideRoot(rootRealPath, targetRealPath, label) {
|
|
1518
|
+
const relative = path.relative(rootRealPath, targetRealPath);
|
|
1519
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
1520
|
+
throw new CliValidationError(`${label} resolves outside the upload root`);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
function npmBinWrapper(linkRelativePath, targetRealPath, rootRealPath) {
|
|
1524
|
+
const targetRelativePath = path.relative(
|
|
1525
|
+
path.dirname(path.join(rootRealPath, ...linkRelativePath.split("/"))),
|
|
1526
|
+
targetRealPath
|
|
1527
|
+
).split(path.sep).join("/");
|
|
1528
|
+
if (!targetRelativePath || /[\r\n\x00]/.test(targetRelativePath)) {
|
|
1529
|
+
throw new CliValidationError(
|
|
1530
|
+
`Unable to materialize npm bin link: ${linkRelativePath}`
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
const quotedTarget = `'${targetRelativePath.replace(/'/g, `'\\''`)}'`;
|
|
1534
|
+
return [
|
|
1535
|
+
"#!/usr/bin/env bash",
|
|
1536
|
+
"set -euo pipefail",
|
|
1537
|
+
'SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"',
|
|
1538
|
+
`exec "$SCRIPT_DIR"/${quotedTarget} "$@"`,
|
|
1539
|
+
""
|
|
1540
|
+
].join("\n");
|
|
1541
|
+
}
|
|
1542
|
+
async function collectArchiveEntries(root, manifestSourcePath) {
|
|
1543
|
+
const rootRealPath = await realpath(root);
|
|
1544
|
+
const entries = [];
|
|
1545
|
+
const npmBinTargets = /* @__PURE__ */ new Set();
|
|
1546
|
+
let fileCount = 0;
|
|
1547
|
+
let unpackedBytes = 0;
|
|
1548
|
+
async function walk(directory) {
|
|
1549
|
+
const children = await readdir(directory, { withFileTypes: true });
|
|
1550
|
+
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
1551
|
+
for (const child of children) {
|
|
1552
|
+
const sourcePath = path.join(directory, child.name);
|
|
1553
|
+
const relativePath = path.relative(root, sourcePath).split(path.sep).join("/");
|
|
1554
|
+
if (!isValidRelativePath(relativePath)) {
|
|
1555
|
+
throw new CliValidationError(
|
|
1556
|
+
`Upload root contains an unsafe path: ${relativePath}`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
const info = await lstat(sourcePath);
|
|
1560
|
+
if (info.isDirectory()) {
|
|
1561
|
+
await walk(sourcePath);
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
if (sourcePath === manifestSourcePath || relativePath === "tool.json")
|
|
1565
|
+
continue;
|
|
1566
|
+
fileCount += 1;
|
|
1567
|
+
if (fileCount > TOOL_UPLOAD_LIMITS.maxFiles - 1) {
|
|
1568
|
+
throw new CliValidationError(
|
|
1569
|
+
`Upload root exceeds ${TOOL_UPLOAD_LIMITS.maxFiles} files`
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
if (info.isSymbolicLink()) {
|
|
1573
|
+
if (!isNpmBinLink(relativePath)) {
|
|
1574
|
+
throw new CliValidationError(
|
|
1575
|
+
`Symbolic links are only allowed under node_modules/.bin: ${relativePath}`
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
const rawTarget = await readlink(sourcePath);
|
|
1579
|
+
if (/[\r\n\x00]/.test(rawTarget)) {
|
|
1580
|
+
throw new CliValidationError(
|
|
1581
|
+
`npm bin link has an unsafe target: ${relativePath}`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
const targetRealPath = await realpath(sourcePath);
|
|
1585
|
+
ensureInsideRoot(
|
|
1586
|
+
rootRealPath,
|
|
1587
|
+
targetRealPath,
|
|
1588
|
+
`npm bin link ${relativePath}`
|
|
1589
|
+
);
|
|
1590
|
+
const targetInfo = await stat(targetRealPath);
|
|
1591
|
+
if (!targetInfo.isFile()) {
|
|
1592
|
+
throw new CliValidationError(
|
|
1593
|
+
`npm bin link must resolve to a regular file: ${relativePath}`
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
npmBinTargets.add(
|
|
1597
|
+
path.relative(rootRealPath, targetRealPath).split(path.sep).join("/")
|
|
1598
|
+
);
|
|
1599
|
+
const content = npmBinWrapper(
|
|
1600
|
+
relativePath,
|
|
1601
|
+
targetRealPath,
|
|
1602
|
+
rootRealPath
|
|
1603
|
+
);
|
|
1604
|
+
unpackedBytes += Buffer.byteLength(content);
|
|
1605
|
+
if (unpackedBytes > TOOL_UPLOAD_LIMITS.maxUnpackedBytes) {
|
|
1606
|
+
throw new CliValidationError(
|
|
1607
|
+
"Upload root exceeds the 500 MB unpacked size limit"
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
entries.push({ content, archivePath: relativePath, mode: 493 });
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
if (!info.isFile()) {
|
|
1614
|
+
throw new CliValidationError(
|
|
1615
|
+
`Upload root contains an unsupported file type: ${relativePath}`
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
if (info.size > TOOL_UPLOAD_LIMITS.maxFileBytes) {
|
|
1619
|
+
throw new CliValidationError(
|
|
1620
|
+
`File exceeds the 50 MB limit: ${relativePath}`
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
unpackedBytes += info.size;
|
|
1624
|
+
if (unpackedBytes > TOOL_UPLOAD_LIMITS.maxUnpackedBytes) {
|
|
1625
|
+
throw new CliValidationError(
|
|
1626
|
+
"Upload root exceeds the 500 MB unpacked size limit"
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
entries.push({
|
|
1630
|
+
sourcePath,
|
|
1631
|
+
archivePath: relativePath,
|
|
1632
|
+
mode: info.mode & 511
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
await walk(root);
|
|
1637
|
+
return entries.map(
|
|
1638
|
+
(entry) => npmBinTargets.has(entry.archivePath) ? { ...entry, mode: entry.mode | 73 } : entry
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
async function writeArchive(archivePath, manifest, entries) {
|
|
1642
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
1643
|
+
const output = createWriteStream(archivePath, { mode: 384 });
|
|
1644
|
+
const zip = archiver("zip", { zlib: { level: 9 } });
|
|
1645
|
+
output.on("close", resolvePromise);
|
|
1646
|
+
output.on("error", rejectPromise);
|
|
1647
|
+
zip.on("error", rejectPromise);
|
|
1648
|
+
zip.on("warning", (error) => {
|
|
1649
|
+
if (error.code !== "ENOENT")
|
|
1650
|
+
rejectPromise(error);
|
|
1651
|
+
});
|
|
1652
|
+
zip.pipe(output);
|
|
1653
|
+
zip.append(`${JSON.stringify(manifest, null, 2)}
|
|
1654
|
+
`, {
|
|
1655
|
+
name: "tool.json",
|
|
1656
|
+
mode: 420
|
|
1657
|
+
});
|
|
1658
|
+
for (const entry of entries) {
|
|
1659
|
+
if (entry.sourcePath) {
|
|
1660
|
+
zip.file(entry.sourcePath, {
|
|
1661
|
+
name: entry.archivePath,
|
|
1662
|
+
mode: entry.mode
|
|
1663
|
+
});
|
|
1664
|
+
} else {
|
|
1665
|
+
zip.append(entry.content ?? "", {
|
|
1666
|
+
name: entry.archivePath,
|
|
1667
|
+
mode: entry.mode
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
void zip.finalize();
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
async function createToolArchive(rootPath, manifestPath) {
|
|
1675
|
+
const root = path.resolve(rootPath);
|
|
1676
|
+
let rootInfo;
|
|
1677
|
+
try {
|
|
1678
|
+
rootInfo = await stat(root);
|
|
1679
|
+
} catch {
|
|
1680
|
+
throw new CliValidationError(`Upload root does not exist: ${root}`);
|
|
1681
|
+
}
|
|
1682
|
+
if (!rootInfo.isDirectory()) {
|
|
1683
|
+
throw new CliValidationError(`Upload root must be a directory: ${root}`);
|
|
1684
|
+
}
|
|
1685
|
+
const { manifest, sourcePath } = await loadManifest(root, manifestPath);
|
|
1686
|
+
const entries = await collectArchiveEntries(root, sourcePath);
|
|
1687
|
+
const entryPaths = new Set(entries.map((entry) => entry.archivePath));
|
|
1688
|
+
for (const command of manifest.commands) {
|
|
1689
|
+
if (!entryPaths.has(command.entry)) {
|
|
1690
|
+
throw new CliValidationError(
|
|
1691
|
+
`tool.json command entry does not exist in the upload root: ${command.entry}`
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
const archiveDirectory = await mkdtemp(
|
|
1696
|
+
path.join(tmpdir(), "ae-cli-sandbox-tool-")
|
|
1697
|
+
);
|
|
1698
|
+
const archivePath = path.join(
|
|
1699
|
+
archiveDirectory,
|
|
1700
|
+
`${manifest.name}-${manifest.version}.zip`
|
|
1701
|
+
);
|
|
1702
|
+
try {
|
|
1703
|
+
await writeArchive(archivePath, manifest, entries);
|
|
1704
|
+
const archiveInfo = await stat(archivePath);
|
|
1705
|
+
if (archiveInfo.size > TOOL_UPLOAD_LIMITS.maxArchiveBytes) {
|
|
1706
|
+
throw new CliValidationError(
|
|
1707
|
+
"Compressed tool package exceeds the 50 MB upload limit"
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
return { archivePath, manifest, archiveBytes: archiveInfo.size };
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
await rm(archiveDirectory, { recursive: true, force: true });
|
|
1713
|
+
throw error;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
async function createNpmInstallRoot() {
|
|
1717
|
+
const root = await mkdtemp(path.join(tmpdir(), "ae-cli-npm-tool-"));
|
|
1718
|
+
await mkdir(root, { recursive: true });
|
|
1719
|
+
return root;
|
|
1720
|
+
}
|
|
1721
|
+
async function writeGeneratedManifest(root, manifest) {
|
|
1722
|
+
await writeFile(
|
|
1723
|
+
path.join(root, "tool.json"),
|
|
1724
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
1725
|
+
`,
|
|
1726
|
+
{
|
|
1727
|
+
mode: 384
|
|
1728
|
+
}
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
async function removeTemporaryTree(root) {
|
|
1732
|
+
await rm(root, { recursive: true, force: true });
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/commands/te-system/sandbox-tools.ts
|
|
1736
|
+
var TOOL_UPLOAD_PATH = "/api/admin/sandbox-tools";
|
|
1737
|
+
var TOOL_UPLOAD_POLICY_PATH = "/api/admin/sandbox-tools/upload-policy";
|
|
1738
|
+
var NPM_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1739
|
+
var TARGET_MODES = ["selected", "all-running"];
|
|
1740
|
+
var OPERATION_ACTIONS = ["activate", "deactivate"];
|
|
1741
|
+
var SORT_ORDERS = ["asc", "desc"];
|
|
1742
|
+
function optionalFlag(ctx, name) {
|
|
1743
|
+
const value = ctx.str(name).trim();
|
|
1744
|
+
return value || void 0;
|
|
1745
|
+
}
|
|
1746
|
+
function optionalBoundedStringArray(ctx, name, max) {
|
|
1747
|
+
return optionalString(ctx, name) ? requireBoundedStringArray(ctx, name, max) : void 0;
|
|
1748
|
+
}
|
|
1749
|
+
function validateCommandNamesByToolId(value, toolIds) {
|
|
1750
|
+
if (!value) return void 0;
|
|
1751
|
+
const normalized = {};
|
|
1752
|
+
for (const [toolId, commandNames] of Object.entries(value)) {
|
|
1753
|
+
if (!toolIds.includes(toolId)) {
|
|
1754
|
+
throw new CliValidationError("--command-names-by-tool-id keys must exist in --tool-ids");
|
|
1755
|
+
}
|
|
1756
|
+
if (!Array.isArray(commandNames) || commandNames.length === 0 || commandNames.length > 64 || commandNames.some((name) => typeof name !== "string" || !name.trim())) {
|
|
1757
|
+
throw new CliValidationError(
|
|
1758
|
+
"--command-names-by-tool-id values must be arrays of 1-64 non-empty command names"
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
normalized[toolId] = commandNames.map((name) => name.trim());
|
|
1762
|
+
}
|
|
1763
|
+
return normalized;
|
|
1764
|
+
}
|
|
1765
|
+
function validateExpectedSnapshots(value, toolIds) {
|
|
1766
|
+
if (!value) return void 0;
|
|
1767
|
+
const normalized = {};
|
|
1768
|
+
for (const [toolId, snapshot] of Object.entries(value)) {
|
|
1769
|
+
if (!toolIds.includes(toolId)) {
|
|
1770
|
+
throw new CliValidationError("--expected-tool-snapshots-by-id keys must exist in --tool-ids");
|
|
1771
|
+
}
|
|
1772
|
+
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) {
|
|
1773
|
+
throw new CliValidationError("--expected-tool-snapshots-by-id values must be objects");
|
|
1774
|
+
}
|
|
1775
|
+
const item = snapshot;
|
|
1776
|
+
if (typeof item.name !== "string" || !item.name.trim() || item.version !== null && typeof item.version !== "string" || typeof item.packagePath !== "string" || !item.packagePath.trim() || !Array.isArray(item.commands) || item.commands.length === 0 || item.commands.length > 64 || item.commands.some((name) => typeof name !== "string" || !name.trim())) {
|
|
1777
|
+
throw new CliValidationError(
|
|
1778
|
+
"--expected-tool-snapshots-by-id contains an invalid tool snapshot"
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
normalized[toolId] = {
|
|
1782
|
+
name: item.name.trim(),
|
|
1783
|
+
version: item.version,
|
|
1784
|
+
packagePath: item.packagePath.trim(),
|
|
1785
|
+
commands: item.commands.map((name) => name.trim())
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
return normalized;
|
|
1789
|
+
}
|
|
1790
|
+
function sandboxToolOperationBody(ctx) {
|
|
1791
|
+
const mode = optionalString(ctx, "target-mode");
|
|
1792
|
+
assertEnum("target-mode", mode, TARGET_MODES);
|
|
1793
|
+
const sandboxIds = optionalBoundedStringArray(ctx, "sandbox-ids", 50);
|
|
1794
|
+
if (mode === "selected" && !sandboxIds) {
|
|
1795
|
+
throw new CliValidationError("selected mode requires --sandbox-ids with 1-50 sandbox IDs");
|
|
1796
|
+
}
|
|
1797
|
+
if (mode === "all-running" && sandboxIds) {
|
|
1798
|
+
throw new CliValidationError("all-running mode does not accept --sandbox-ids");
|
|
1799
|
+
}
|
|
1800
|
+
const toolIds = requireBoundedStringArray(ctx, "tool-ids", 20);
|
|
1801
|
+
const commandNamesByToolId = validateCommandNamesByToolId(
|
|
1802
|
+
optionalJsonObject(ctx, "command-names-by-tool-id"),
|
|
1803
|
+
toolIds
|
|
1804
|
+
);
|
|
1805
|
+
const expectedToolSnapshotsById = validateExpectedSnapshots(
|
|
1806
|
+
optionalJsonObject(ctx, "expected-tool-snapshots-by-id"),
|
|
1807
|
+
toolIds
|
|
1808
|
+
);
|
|
1809
|
+
return {
|
|
1810
|
+
target: { mode, ...sandboxIds ? { sandboxIds } : {} },
|
|
1811
|
+
toolIds,
|
|
1812
|
+
...commandNamesByToolId ? { commandNamesByToolId } : {},
|
|
1813
|
+
...expectedToolSnapshotsById ? { expectedToolSnapshotsById } : {}
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
var TOOL_OPERATION_FLAGS = [
|
|
1817
|
+
{ name: "target-mode", type: "string", required: true, desc: `Target mode: ${TARGET_MODES.join(" | ")}` },
|
|
1818
|
+
{ name: "sandbox-ids", type: "string", sensitive: true, desc: "Selected mode only: JSON/@file array of 1-50 sandbox IDs" },
|
|
1819
|
+
{ name: "tool-ids", type: "string", required: true, desc: "JSON/@file array of 1-20 sandbox tool IDs" },
|
|
1820
|
+
{ name: "command-names-by-tool-id", type: "string", desc: "Optional JSON/@file map of tool ID to selected command names" },
|
|
1821
|
+
{ name: "expected-tool-snapshots-by-id", type: "string", sensitive: true, desc: "Optional JSON/@file optimistic-lock snapshots keyed by tool ID" }
|
|
1822
|
+
];
|
|
1823
|
+
async function requireUploadPolicy(ctx) {
|
|
1824
|
+
const policy = await getAdmin(
|
|
1825
|
+
TOOL_UPLOAD_POLICY_PATH,
|
|
1826
|
+
ctx.host()
|
|
1827
|
+
);
|
|
1828
|
+
if (!policy || policy.enabled !== true || policy.archiveFormat !== "zip" || policy.manifestFile !== "tool.json" || !policy.limits || !Number.isFinite(policy.limits.maxArchiveBytes)) {
|
|
1829
|
+
throw new CliValidationError(
|
|
1830
|
+
"The server returned an invalid sandbox tool upload policy"
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
return policy;
|
|
1834
|
+
}
|
|
1835
|
+
function requireLinuxSandbox() {
|
|
1836
|
+
const credentials = tryLoadTeAgentSandboxCredentials();
|
|
1837
|
+
if (process.platform !== "linux" || !credentials?.sandboxId || !credentials.sandboxSecretKey) {
|
|
1838
|
+
throw new CliValidationError(
|
|
1839
|
+
"+npm-install must run inside a Linux te-agent sandbox",
|
|
1840
|
+
{
|
|
1841
|
+
hint: "Install and package the npm CLI in its target Linux runtime, then authenticate as root or agent_admin."
|
|
1842
|
+
}
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
async function uploadArchive(ctx, archivePath, archiveBytes, maxArchiveBytes) {
|
|
1847
|
+
if (archiveBytes > maxArchiveBytes) {
|
|
1848
|
+
throw new CliValidationError(
|
|
1849
|
+
`Compressed tool package exceeds the server limit of ${maxArchiveBytes} bytes`
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
const archive = await readFile2(archivePath);
|
|
1853
|
+
const formData = new FormData();
|
|
1854
|
+
formData.append(
|
|
1855
|
+
"file",
|
|
1856
|
+
new Blob([new Uint8Array(archive)], { type: "application/zip" }),
|
|
1857
|
+
path2.basename(archivePath)
|
|
1858
|
+
);
|
|
1859
|
+
return uploadAdminForm(TOOL_UPLOAD_PATH, formData, ctx.host());
|
|
1860
|
+
}
|
|
1861
|
+
async function packageAndUpload(ctx, root, manifestPath, policy) {
|
|
1862
|
+
const created = await createToolArchive(root, manifestPath);
|
|
1863
|
+
try {
|
|
1864
|
+
const result = await uploadArchive(
|
|
1865
|
+
ctx,
|
|
1866
|
+
created.archivePath,
|
|
1867
|
+
created.archiveBytes,
|
|
1868
|
+
policy.limits.maxArchiveBytes
|
|
1869
|
+
);
|
|
1870
|
+
return {
|
|
1871
|
+
...result,
|
|
1872
|
+
package: {
|
|
1873
|
+
name: created.manifest.name,
|
|
1874
|
+
version: created.manifest.version,
|
|
1875
|
+
archiveBytes: created.archiveBytes
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
} finally {
|
|
1879
|
+
await removeTemporaryTree(path2.dirname(created.archivePath));
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
async function runNpmInstall(root, spec, allowScripts) {
|
|
1883
|
+
const args = [
|
|
1884
|
+
"install",
|
|
1885
|
+
"--prefix",
|
|
1886
|
+
root,
|
|
1887
|
+
"--omit=dev",
|
|
1888
|
+
"--no-audit",
|
|
1889
|
+
"--no-fund",
|
|
1890
|
+
"--save-exact",
|
|
1891
|
+
...allowScripts ? [] : ["--ignore-scripts"],
|
|
1892
|
+
spec
|
|
1893
|
+
];
|
|
1894
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
1895
|
+
let timedOut = false;
|
|
1896
|
+
const child = spawn("npm", args, {
|
|
1897
|
+
shell: false,
|
|
1898
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
1899
|
+
});
|
|
1900
|
+
const timer = setTimeout(() => {
|
|
1901
|
+
timedOut = true;
|
|
1902
|
+
child.kill("SIGTERM");
|
|
1903
|
+
}, NPM_INSTALL_TIMEOUT_MS);
|
|
1904
|
+
child.stderr.on("data", (chunk) => {
|
|
1905
|
+
const text = chunk.toString();
|
|
1906
|
+
process.stderr.write(text);
|
|
1907
|
+
});
|
|
1908
|
+
child.on("error", (error) => {
|
|
1909
|
+
clearTimeout(timer);
|
|
1910
|
+
rejectPromise(
|
|
1911
|
+
new CliValidationError(`Unable to start npm: ${error.message}`)
|
|
1912
|
+
);
|
|
1913
|
+
});
|
|
1914
|
+
child.on("close", (code, signal) => {
|
|
1915
|
+
clearTimeout(timer);
|
|
1916
|
+
if (code === 0) {
|
|
1917
|
+
resolvePromise();
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
rejectPromise(
|
|
1921
|
+
new CliValidationError(
|
|
1922
|
+
timedOut ? `npm install timed out after ${NPM_INSTALL_TIMEOUT_MS / 6e4} minutes` : `npm install failed${code === null ? ` (${signal ?? "terminated"})` : ` with exit code ${code}`}`
|
|
1923
|
+
)
|
|
1924
|
+
);
|
|
1925
|
+
});
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
async function readInstalledPackageJson(root, packageName) {
|
|
1929
|
+
const packageJsonPath = path2.join(
|
|
1930
|
+
root,
|
|
1931
|
+
"node_modules",
|
|
1932
|
+
...packageName.split("/"),
|
|
1933
|
+
"package.json"
|
|
1934
|
+
);
|
|
1935
|
+
let raw;
|
|
1936
|
+
try {
|
|
1937
|
+
raw = await readFile2(packageJsonPath, "utf8");
|
|
1938
|
+
} catch {
|
|
1939
|
+
throw new CliValidationError(
|
|
1940
|
+
`npm did not install the expected package: ${packageName}`
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
try {
|
|
1944
|
+
return JSON.parse(raw);
|
|
1945
|
+
} catch {
|
|
1946
|
+
throw new CliValidationError(
|
|
1947
|
+
`Installed package.json is invalid: ${packageName}`
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
async function validateCommandEntries(root, entries) {
|
|
1952
|
+
for (const entry of entries) {
|
|
1953
|
+
const entryPath = path2.join(root, ...entry.split("/"));
|
|
1954
|
+
let info;
|
|
1955
|
+
try {
|
|
1956
|
+
info = await stat2(entryPath);
|
|
1957
|
+
} catch {
|
|
1958
|
+
throw new CliValidationError(
|
|
1959
|
+
`Installed npm command entry does not exist: ${entry}`
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
if (!info.isFile()) {
|
|
1963
|
+
throw new CliValidationError(
|
|
1964
|
+
`Installed npm command entry is not a regular file: ${entry}`
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
var uploadSandboxTool = {
|
|
1970
|
+
service: "system",
|
|
1971
|
+
command: "+upload-sandbox-tool",
|
|
1972
|
+
description: "Package and upload a custom sandbox tool from a local directory",
|
|
1973
|
+
flags: [
|
|
1974
|
+
{
|
|
1975
|
+
name: "path",
|
|
1976
|
+
type: "string",
|
|
1977
|
+
required: true,
|
|
1978
|
+
desc: "Tool package root; tool.json must be at this directory root unless --manifest is used"
|
|
1979
|
+
},
|
|
1980
|
+
{
|
|
1981
|
+
name: "manifest",
|
|
1982
|
+
type: "string",
|
|
1983
|
+
desc: "Optional external tool.json path when the package root does not contain one"
|
|
1984
|
+
}
|
|
1985
|
+
],
|
|
1986
|
+
risk: "write",
|
|
1987
|
+
validate: (ctx) => {
|
|
1988
|
+
if (!ctx.str("path").trim()) {
|
|
1989
|
+
throw new CliValidationError("--path cannot be empty");
|
|
1990
|
+
}
|
|
1991
|
+
},
|
|
1992
|
+
dryRun: (ctx) => ({
|
|
1993
|
+
method: "POST",
|
|
1994
|
+
url: TOOL_UPLOAD_PATH,
|
|
1995
|
+
body: {
|
|
1996
|
+
path: path2.resolve(ctx.str("path")),
|
|
1997
|
+
manifest: optionalFlag(ctx, "manifest"),
|
|
1998
|
+
archiveFormat: "zip",
|
|
1999
|
+
activatesTool: false
|
|
2000
|
+
}
|
|
2001
|
+
}),
|
|
2002
|
+
execute: async (ctx) => {
|
|
2003
|
+
const policy = await requireUploadPolicy(ctx);
|
|
2004
|
+
return packageAndUpload(
|
|
2005
|
+
ctx,
|
|
2006
|
+
ctx.str("path"),
|
|
2007
|
+
optionalFlag(ctx, "manifest"),
|
|
2008
|
+
policy
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
var npmInstallSandboxTool = {
|
|
2013
|
+
service: "system",
|
|
2014
|
+
command: "+npm-install",
|
|
2015
|
+
description: "Install an exact npm CLI package in the sandbox and upload it as a shared tool",
|
|
2016
|
+
flags: [
|
|
2017
|
+
{
|
|
2018
|
+
name: "package",
|
|
2019
|
+
type: "string",
|
|
2020
|
+
required: true,
|
|
2021
|
+
desc: "Exact registry package version, for example eslint@9.32.0 or @scope/cli@1.2.3"
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
name: "name",
|
|
2025
|
+
type: "string",
|
|
2026
|
+
desc: "Optional shared tool identifier; defaults to the unscoped npm package name"
|
|
2027
|
+
},
|
|
2028
|
+
{
|
|
2029
|
+
name: "allow-scripts",
|
|
2030
|
+
type: "boolean",
|
|
2031
|
+
default: false,
|
|
2032
|
+
desc: "Allow npm lifecycle scripts; disabled by default for supply-chain safety"
|
|
2033
|
+
}
|
|
2034
|
+
],
|
|
2035
|
+
risk: "write",
|
|
2036
|
+
validate: (ctx) => {
|
|
2037
|
+
parseExactNpmPackageSpec(ctx.str("package"));
|
|
2038
|
+
},
|
|
2039
|
+
dryRun: (ctx) => {
|
|
2040
|
+
const spec = parseExactNpmPackageSpec(ctx.str("package"));
|
|
2041
|
+
return {
|
|
2042
|
+
method: "POST",
|
|
2043
|
+
url: TOOL_UPLOAD_PATH,
|
|
2044
|
+
body: {
|
|
2045
|
+
package: spec.spec,
|
|
2046
|
+
name: optionalFlag(ctx, "name"),
|
|
2047
|
+
ignoreScripts: !ctx.bool("allow-scripts"),
|
|
2048
|
+
installLocation: "temporary sandbox directory",
|
|
2049
|
+
activatesTool: false
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
},
|
|
2053
|
+
execute: async (ctx) => {
|
|
2054
|
+
requireLinuxSandbox();
|
|
2055
|
+
const spec = parseExactNpmPackageSpec(ctx.str("package"));
|
|
2056
|
+
const policy = await requireUploadPolicy(ctx);
|
|
2057
|
+
const root = await createNpmInstallRoot();
|
|
2058
|
+
try {
|
|
2059
|
+
await runNpmInstall(root, spec.spec, ctx.bool("allow-scripts"));
|
|
2060
|
+
const packageJson = await readInstalledPackageJson(root, spec.name);
|
|
2061
|
+
const manifest = manifestFromNpmPackage(
|
|
2062
|
+
spec,
|
|
2063
|
+
packageJson,
|
|
2064
|
+
optionalFlag(ctx, "name")
|
|
2065
|
+
);
|
|
2066
|
+
await validateCommandEntries(
|
|
2067
|
+
root,
|
|
2068
|
+
manifest.commands.map((command) => command.entry)
|
|
2069
|
+
);
|
|
2070
|
+
await writeGeneratedManifest(root, manifest);
|
|
2071
|
+
return await packageAndUpload(ctx, root, void 0, policy);
|
|
2072
|
+
} finally {
|
|
2073
|
+
await removeTemporaryTree(root);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
var listSandboxTools = createAdminCommand({
|
|
2078
|
+
command: "+list-sandbox-tools",
|
|
2079
|
+
description: "List preset and custom sandbox tools registered for the current company",
|
|
2080
|
+
flags: [],
|
|
2081
|
+
risk: "read",
|
|
2082
|
+
prepare: () => ({ method: "GET", path: "/api/admin/sandbox-tools" })
|
|
2083
|
+
});
|
|
2084
|
+
var syncSandboxTools = createAdminCommand({
|
|
2085
|
+
command: "+sync-sandbox-tools",
|
|
2086
|
+
description: "Synchronize the current company sandbox tool registry from the system manifest",
|
|
2087
|
+
flags: [],
|
|
2088
|
+
risk: "write",
|
|
2089
|
+
prepare: () => ({ method: "POST", path: "/api/admin/sandbox-tools/sync", body: {} })
|
|
2090
|
+
});
|
|
2091
|
+
var getSandboxToolDistribution = createAdminCommand({
|
|
2092
|
+
command: "+get-sandbox-tool-distribution",
|
|
2093
|
+
description: "Get the sandboxes to which one tool is currently distributed",
|
|
2094
|
+
flags: [
|
|
2095
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox tool ID from +list-sandbox-tools" }
|
|
2096
|
+
],
|
|
2097
|
+
risk: "read",
|
|
2098
|
+
prepare: (ctx) => ({
|
|
2099
|
+
method: "GET",
|
|
2100
|
+
path: `/api/admin/sandbox-tools/${encodeId(ctx.str("id"))}/distribution`
|
|
2101
|
+
})
|
|
2102
|
+
});
|
|
2103
|
+
var setSandboxToolEnabled = createAdminCommand({
|
|
2104
|
+
command: "+set-sandbox-tool-enabled",
|
|
2105
|
+
description: "Enable or disable a sandbox tool in the company registry",
|
|
2106
|
+
flags: [
|
|
2107
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox tool ID from +list-sandbox-tools" },
|
|
2108
|
+
{ name: "enabled", type: "boolean", required: true, desc: "Whether the sandbox tool is enabled" }
|
|
2109
|
+
],
|
|
2110
|
+
risk: "write",
|
|
2111
|
+
prepare: (ctx) => ({
|
|
2112
|
+
method: "PATCH",
|
|
2113
|
+
path: `/api/admin/sandbox-tools/${encodeId(ctx.str("id"))}`,
|
|
2114
|
+
body: { enabled: ctx.bool("enabled") }
|
|
2115
|
+
})
|
|
2116
|
+
});
|
|
2117
|
+
var removeSandboxTool = createAdminCommand({
|
|
2118
|
+
command: "+remove-sandbox-tool",
|
|
2119
|
+
description: "Delete a sandbox tool after the server verifies it is fully reclaimed",
|
|
2120
|
+
flags: [
|
|
2121
|
+
{ name: "id", type: "string", required: true, desc: "Sandbox tool ID from +list-sandbox-tools" }
|
|
2122
|
+
],
|
|
2123
|
+
risk: "high-risk-write",
|
|
2124
|
+
prepare: (ctx) => ({
|
|
2125
|
+
method: "DELETE",
|
|
2126
|
+
path: `/api/admin/sandbox-tools/${encodeId(ctx.str("id"))}`
|
|
2127
|
+
})
|
|
2128
|
+
});
|
|
2129
|
+
function createSandboxToolOperationCommand(command, description, route) {
|
|
2130
|
+
return createAdminCommand({
|
|
2131
|
+
command,
|
|
2132
|
+
description,
|
|
2133
|
+
flags: TOOL_OPERATION_FLAGS,
|
|
2134
|
+
risk: "write",
|
|
2135
|
+
validate: (ctx) => {
|
|
2136
|
+
sandboxToolOperationBody(ctx);
|
|
2137
|
+
},
|
|
2138
|
+
prepare: (ctx) => ({
|
|
2139
|
+
method: "POST",
|
|
2140
|
+
path: `/api/admin/sandbox-tools/${route}`,
|
|
2141
|
+
body: sandboxToolOperationBody(ctx)
|
|
2142
|
+
})
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
var activateSandboxTools = createSandboxToolOperationCommand(
|
|
2146
|
+
"+activate-sandbox-tools",
|
|
2147
|
+
"Activate selected tool commands in selected or all running sandboxes",
|
|
2148
|
+
"activate"
|
|
2149
|
+
);
|
|
2150
|
+
var deactivateSandboxTools = createSandboxToolOperationCommand(
|
|
2151
|
+
"+deactivate-sandbox-tools",
|
|
2152
|
+
"Deactivate selected managed tool commands in selected or all running sandboxes",
|
|
2153
|
+
"deactivate"
|
|
2154
|
+
);
|
|
2155
|
+
var refreshSandboxToolStatus = createSandboxToolOperationCommand(
|
|
2156
|
+
"+refresh-sandbox-tool-status",
|
|
2157
|
+
"Refresh the observed tool state in selected or all running sandboxes",
|
|
2158
|
+
"status"
|
|
2159
|
+
);
|
|
2160
|
+
var listSandboxToolOperations = createAdminCommand({
|
|
2161
|
+
command: "+list-sandbox-tool-operations",
|
|
2162
|
+
description: "List sandbox tool activation and deactivation operation history",
|
|
2163
|
+
flags: [
|
|
2164
|
+
{ name: "page", type: "number", min: 1, desc: "Page number (default: 1)" },
|
|
2165
|
+
{ name: "page-size", type: "number", min: 1, max: 100, desc: "Page size (1-100, default: 20)" },
|
|
2166
|
+
{ name: "action", type: "string", desc: `Operation action: ${OPERATION_ACTIONS.join(" | ")}` },
|
|
2167
|
+
{ name: "operator", type: "string", maxLength: 100, desc: "Operator name or ID filter" },
|
|
2168
|
+
{ name: "sort-order", type: "string", desc: `Sort order: ${SORT_ORDERS.join(" | ")}` }
|
|
2169
|
+
],
|
|
2170
|
+
risk: "read",
|
|
2171
|
+
validate: (ctx) => {
|
|
2172
|
+
assertEnum("action", optionalString(ctx, "action"), OPERATION_ACTIONS);
|
|
2173
|
+
assertEnum("sort-order", optionalString(ctx, "sort-order"), SORT_ORDERS);
|
|
2174
|
+
},
|
|
2175
|
+
prepare: (ctx) => ({
|
|
2176
|
+
method: "GET",
|
|
2177
|
+
path: withQuery("/api/admin/sandbox-tools/operations", {
|
|
2178
|
+
page: ctx.optionalNum("page"),
|
|
2179
|
+
pageSize: ctx.optionalNum("page-size"),
|
|
2180
|
+
action: optionalString(ctx, "action"),
|
|
2181
|
+
operator: optionalString(ctx, "operator"),
|
|
2182
|
+
sortOrder: optionalString(ctx, "sort-order")
|
|
2183
|
+
})
|
|
2184
|
+
})
|
|
2185
|
+
});
|
|
2186
|
+
var sandboxToolCommands = [
|
|
2187
|
+
uploadSandboxTool,
|
|
2188
|
+
npmInstallSandboxTool,
|
|
2189
|
+
listSandboxTools,
|
|
2190
|
+
syncSandboxTools,
|
|
2191
|
+
getSandboxToolDistribution,
|
|
2192
|
+
setSandboxToolEnabled,
|
|
2193
|
+
removeSandboxTool,
|
|
2194
|
+
activateSandboxTools,
|
|
2195
|
+
deactivateSandboxTools,
|
|
2196
|
+
refreshSandboxToolStatus,
|
|
2197
|
+
listSandboxToolOperations
|
|
2198
|
+
];
|
|
2199
|
+
|
|
2200
|
+
// src/commands/te-system/index.ts
|
|
2201
|
+
var commands = [
|
|
2202
|
+
...memberCommands,
|
|
2203
|
+
...sandboxCommands,
|
|
2204
|
+
...modelCommands,
|
|
2205
|
+
...usageCommands,
|
|
2206
|
+
...costControlCommands,
|
|
2207
|
+
...channelCommands,
|
|
2208
|
+
...sandboxToolCommands
|
|
2209
|
+
];
|
|
2210
|
+
var te_system_default = commands;
|
|
2211
|
+
export {
|
|
2212
|
+
te_system_default as default
|
|
2213
|
+
};
|