@poco-ai/tokenarena 0.1.5 → 0.1.6
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/index.d.ts +1 -1
- package/dist/index.js +777 -214
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1129,7 +1129,14 @@ function getRuntimeDir() {
|
|
|
1129
1129
|
var CONFIG_DIR = join9(getConfigHome(), "tokenarena");
|
|
1130
1130
|
var isDev = process.env.TOKEN_ARENA_DEV === "1";
|
|
1131
1131
|
var CONFIG_FILE = join9(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
|
|
1132
|
-
var DEFAULT_API_URL = "
|
|
1132
|
+
var DEFAULT_API_URL = "https://token.poco-ai.com";
|
|
1133
|
+
var VALID_CONFIG_KEYS = [
|
|
1134
|
+
"apiKey",
|
|
1135
|
+
"apiUrl",
|
|
1136
|
+
"deviceId",
|
|
1137
|
+
"syncInterval",
|
|
1138
|
+
"logLevel"
|
|
1139
|
+
];
|
|
1133
1140
|
function getConfigPath() {
|
|
1134
1141
|
return CONFIG_FILE;
|
|
1135
1142
|
}
|
|
@@ -1168,10 +1175,112 @@ function getOrCreateDeviceId(config) {
|
|
|
1168
1175
|
function validateApiKey(key) {
|
|
1169
1176
|
return key.startsWith("ta_");
|
|
1170
1177
|
}
|
|
1178
|
+
function isValidConfigKey(key) {
|
|
1179
|
+
return VALID_CONFIG_KEYS.includes(key);
|
|
1180
|
+
}
|
|
1171
1181
|
function getDefaultApiUrl() {
|
|
1172
1182
|
return process.env.TOKEN_ARENA_API_URL || DEFAULT_API_URL;
|
|
1173
1183
|
}
|
|
1174
1184
|
|
|
1185
|
+
// src/infrastructure/ui/format.ts
|
|
1186
|
+
var hasColor = Boolean(process.stdout.isTTY && process.env.NO_COLOR !== "1");
|
|
1187
|
+
function withCode(code, value) {
|
|
1188
|
+
if (!hasColor) return value;
|
|
1189
|
+
return `\x1B[${code}m${value}\x1B[0m`;
|
|
1190
|
+
}
|
|
1191
|
+
function bold(value) {
|
|
1192
|
+
return withCode("1", value);
|
|
1193
|
+
}
|
|
1194
|
+
function dim(value) {
|
|
1195
|
+
return withCode("2", value);
|
|
1196
|
+
}
|
|
1197
|
+
function cyan(value) {
|
|
1198
|
+
return withCode("36", value);
|
|
1199
|
+
}
|
|
1200
|
+
function green(value) {
|
|
1201
|
+
return withCode("32", value);
|
|
1202
|
+
}
|
|
1203
|
+
function yellow(value) {
|
|
1204
|
+
return withCode("33", value);
|
|
1205
|
+
}
|
|
1206
|
+
function red(value) {
|
|
1207
|
+
return withCode("31", value);
|
|
1208
|
+
}
|
|
1209
|
+
function magenta(value) {
|
|
1210
|
+
return withCode("35", value);
|
|
1211
|
+
}
|
|
1212
|
+
function formatHeader(title, subtitle) {
|
|
1213
|
+
const lines = [`${cyan("\u25C8")} ${bold(title)}`];
|
|
1214
|
+
if (subtitle) {
|
|
1215
|
+
lines.push(dim(subtitle));
|
|
1216
|
+
}
|
|
1217
|
+
return `
|
|
1218
|
+
${lines.join("\n")}`;
|
|
1219
|
+
}
|
|
1220
|
+
function formatSection(title) {
|
|
1221
|
+
return `
|
|
1222
|
+
${bold(title)}`;
|
|
1223
|
+
}
|
|
1224
|
+
function formatKeyValue(label, value) {
|
|
1225
|
+
return ` ${dim(label.padEnd(14, " "))} ${value}`;
|
|
1226
|
+
}
|
|
1227
|
+
function formatBullet(value, tone = "neutral") {
|
|
1228
|
+
const icon = tone === "success" ? green("\u2714") : tone === "warning" ? yellow("!") : tone === "danger" ? red("\u2716") : cyan("\u2022");
|
|
1229
|
+
return ` ${icon} ${value}`;
|
|
1230
|
+
}
|
|
1231
|
+
function formatMutedPath(path) {
|
|
1232
|
+
return dim(path);
|
|
1233
|
+
}
|
|
1234
|
+
function maskSecret(value, visible = 8) {
|
|
1235
|
+
if (!value) return "(empty)";
|
|
1236
|
+
if (value.length <= visible) return value;
|
|
1237
|
+
return `${value.slice(0, visible)}\u2026`;
|
|
1238
|
+
}
|
|
1239
|
+
function formatStatusBadge(label, tone = "neutral") {
|
|
1240
|
+
if (tone === "success") return green(label);
|
|
1241
|
+
if (tone === "warning") return yellow(label);
|
|
1242
|
+
if (tone === "danger") return red(label);
|
|
1243
|
+
return magenta(label);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/infrastructure/ui/prompts.ts
|
|
1247
|
+
import {
|
|
1248
|
+
confirm,
|
|
1249
|
+
input,
|
|
1250
|
+
password,
|
|
1251
|
+
select
|
|
1252
|
+
} from "@inquirer/prompts";
|
|
1253
|
+
function isInteractiveTerminal() {
|
|
1254
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1255
|
+
}
|
|
1256
|
+
async function promptConfirm(options) {
|
|
1257
|
+
return confirm({
|
|
1258
|
+
message: options.message,
|
|
1259
|
+
default: options.defaultValue
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
async function promptText(options) {
|
|
1263
|
+
return input({
|
|
1264
|
+
message: options.message,
|
|
1265
|
+
default: options.defaultValue,
|
|
1266
|
+
validate: options.validate
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
async function promptPassword(options) {
|
|
1270
|
+
return password({
|
|
1271
|
+
message: options.message,
|
|
1272
|
+
mask: options.mask ?? "*",
|
|
1273
|
+
validate: options.validate
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
async function promptSelect(options) {
|
|
1277
|
+
return select({
|
|
1278
|
+
message: options.message,
|
|
1279
|
+
choices: [...options.choices],
|
|
1280
|
+
pageSize: Math.min(Math.max(options.choices.length, 6), 10)
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1175
1284
|
// src/utils/logger.ts
|
|
1176
1285
|
var LOG_LEVELS = {
|
|
1177
1286
|
debug: 0,
|
|
@@ -1219,13 +1328,220 @@ var logger = new Logger();
|
|
|
1219
1328
|
|
|
1220
1329
|
// src/commands/config.ts
|
|
1221
1330
|
var VALID_KEYS = ["apiKey", "apiUrl", "syncInterval", "logLevel"];
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1331
|
+
function isConfigKey(value) {
|
|
1332
|
+
return isValidConfigKey(value) && VALID_KEYS.includes(value);
|
|
1333
|
+
}
|
|
1334
|
+
function formatConfigValue(key, value) {
|
|
1335
|
+
if (value === void 0 || value === null || value === "") {
|
|
1336
|
+
return "(empty)";
|
|
1337
|
+
}
|
|
1338
|
+
if (key === "apiKey") {
|
|
1339
|
+
return maskSecret(String(value));
|
|
1340
|
+
}
|
|
1341
|
+
if (key === "syncInterval") {
|
|
1342
|
+
const ms = Number(value);
|
|
1343
|
+
const minutes = Math.round(ms / 6e4);
|
|
1344
|
+
return `${minutes} \u5206\u949F (${ms} ms)`;
|
|
1345
|
+
}
|
|
1346
|
+
return String(value);
|
|
1347
|
+
}
|
|
1348
|
+
async function promptConfigSubcommand() {
|
|
1349
|
+
logger.info(
|
|
1350
|
+
formatHeader("\u914D\u7F6E\u4E2D\u5FC3", "\u901A\u8FC7\u4EA4\u4E92\u5F0F\u83DC\u5355\u67E5\u770B\u6216\u4FEE\u6539 TokenArena CLI \u914D\u7F6E\u3002")
|
|
1351
|
+
);
|
|
1352
|
+
return promptSelect({
|
|
1353
|
+
message: "\u8BF7\u9009\u62E9\u914D\u7F6E\u64CD\u4F5C",
|
|
1354
|
+
choices: [
|
|
1355
|
+
{
|
|
1356
|
+
name: "\u67E5\u770B\u5B8C\u6574\u914D\u7F6E",
|
|
1357
|
+
value: "show",
|
|
1358
|
+
description: "\u4EE5\u66F4\u9002\u5408\u9605\u8BFB\u7684\u65B9\u5F0F\u5C55\u793A\u5F53\u524D\u914D\u7F6E"
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
name: "\u8BFB\u53D6\u5355\u4E2A\u914D\u7F6E\u9879",
|
|
1362
|
+
value: "get",
|
|
1363
|
+
description: "\u67E5\u770B\u67D0\u4E2A\u914D\u7F6E\u952E\u5F53\u524D\u4FDD\u5B58\u7684\u503C"
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
name: "\u4FEE\u6539\u914D\u7F6E\u9879",
|
|
1367
|
+
value: "set",
|
|
1368
|
+
description: "\u66F4\u65B0 API Key\u3001API \u5730\u5740\u3001\u540C\u6B65\u95F4\u9694\u6216\u65E5\u5FD7\u7EA7\u522B"
|
|
1369
|
+
}
|
|
1370
|
+
]
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
async function promptConfigKey(message) {
|
|
1374
|
+
return promptSelect({
|
|
1375
|
+
message,
|
|
1376
|
+
choices: [
|
|
1377
|
+
{
|
|
1378
|
+
name: "apiKey",
|
|
1379
|
+
value: "apiKey",
|
|
1380
|
+
description: "\u4E0A\u4F20\u6570\u636E\u65F6\u4F7F\u7528\u7684 CLI API Key"
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
name: "apiUrl",
|
|
1384
|
+
value: "apiUrl",
|
|
1385
|
+
description: "TokenArena \u670D\u52A1\u7AEF\u5730\u5740"
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
name: "syncInterval",
|
|
1389
|
+
value: "syncInterval",
|
|
1390
|
+
description: "daemon \u9ED8\u8BA4\u540C\u6B65\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09"
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
name: "logLevel",
|
|
1394
|
+
value: "logLevel",
|
|
1395
|
+
description: "CLI \u65E5\u5FD7\u7EA7\u522B"
|
|
1396
|
+
}
|
|
1397
|
+
]
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
async function promptSyncIntervalValue(existingValue) {
|
|
1401
|
+
const preset = await promptSelect({
|
|
1402
|
+
message: "\u8BF7\u9009\u62E9\u9ED8\u8BA4\u540C\u6B65\u95F4\u9694",
|
|
1403
|
+
choices: [
|
|
1404
|
+
{
|
|
1405
|
+
name: "5 \u5206\u949F",
|
|
1406
|
+
value: String(5 * 6e4),
|
|
1407
|
+
description: "\u9002\u5408\u4F5C\u4E3A\u9ED8\u8BA4\u503C"
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: "10 \u5206\u949F",
|
|
1411
|
+
value: String(10 * 6e4),
|
|
1412
|
+
description: "\u66F4\u7701\u7535\uFF0C\u4ECD\u4FDD\u6301\u8F83\u53CA\u65F6\u540C\u6B65"
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: "30 \u5206\u949F",
|
|
1416
|
+
value: String(30 * 6e4),
|
|
1417
|
+
description: "\u9002\u5408\u4F4E\u9891\u4F7F\u7528\u573A\u666F"
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
name: "60 \u5206\u949F",
|
|
1421
|
+
value: String(60 * 6e4),
|
|
1422
|
+
description: "\u957F\u5468\u671F\u540E\u53F0\u540C\u6B65"
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
name: "\u81EA\u5B9A\u4E49\uFF08\u6BEB\u79D2\uFF09",
|
|
1426
|
+
value: "custom",
|
|
1427
|
+
description: "\u8F93\u5165\u4EFB\u610F\u6B63\u6574\u6570\u6BEB\u79D2\u503C"
|
|
1428
|
+
}
|
|
1429
|
+
]
|
|
1430
|
+
});
|
|
1431
|
+
if (preset !== "custom") {
|
|
1432
|
+
return preset;
|
|
1433
|
+
}
|
|
1434
|
+
return promptText({
|
|
1435
|
+
message: "\u8BF7\u8F93\u5165 syncInterval\uFF08\u6BEB\u79D2\uFF09",
|
|
1436
|
+
defaultValue: existingValue ? String(existingValue) : void 0,
|
|
1437
|
+
validate: (value) => {
|
|
1438
|
+
const parsed = Number.parseInt(value, 10);
|
|
1439
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
1440
|
+
return "\u8BF7\u8F93\u5165\u5927\u4E8E 0 \u7684\u6BEB\u79D2\u6570\uFF0C\u4F8B\u5982 300000\u3002";
|
|
1441
|
+
}
|
|
1442
|
+
return true;
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
async function promptConfigValue(key, existingValue) {
|
|
1447
|
+
switch (key) {
|
|
1448
|
+
case "apiKey":
|
|
1449
|
+
return promptPassword({
|
|
1450
|
+
message: "\u8BF7\u8F93\u5165\u65B0\u7684 CLI API Key",
|
|
1451
|
+
validate: (value) => validateApiKey(value) || 'API Key \u5FC5\u987B\u4EE5 "ta_" \u5F00\u5934\u3002'
|
|
1452
|
+
});
|
|
1453
|
+
case "apiUrl":
|
|
1454
|
+
return promptText({
|
|
1455
|
+
message: "\u8BF7\u8F93\u5165 API \u670D\u52A1\u5730\u5740",
|
|
1456
|
+
defaultValue: typeof existingValue === "string" && existingValue.length > 0 ? existingValue : getDefaultApiUrl(),
|
|
1457
|
+
validate: (value) => {
|
|
1458
|
+
try {
|
|
1459
|
+
const url = new URL(value);
|
|
1460
|
+
return Boolean(url.protocol && url.host) || "\u8BF7\u8F93\u5165\u5408\u6CD5 URL\u3002";
|
|
1461
|
+
} catch {
|
|
1462
|
+
return "\u8BF7\u8F93\u5165\u5408\u6CD5 URL\u3002";
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
case "syncInterval":
|
|
1467
|
+
return promptSyncIntervalValue(
|
|
1468
|
+
typeof existingValue === "number" ? existingValue : void 0
|
|
1469
|
+
);
|
|
1470
|
+
case "logLevel":
|
|
1471
|
+
return promptSelect({
|
|
1472
|
+
message: "\u8BF7\u9009\u62E9\u65E5\u5FD7\u7EA7\u522B",
|
|
1473
|
+
choices: [
|
|
1474
|
+
{
|
|
1475
|
+
name: "info",
|
|
1476
|
+
value: "info",
|
|
1477
|
+
description: "\u9ED8\u8BA4\uFF0C\u8F93\u51FA\u5E38\u89C4\u8FDB\u5EA6\u4E0E\u63D0\u793A"
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
name: "warn",
|
|
1481
|
+
value: "warn",
|
|
1482
|
+
description: "\u4EC5\u8F93\u51FA\u8B66\u544A\u4E0E\u9519\u8BEF"
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
name: "error",
|
|
1486
|
+
value: "error",
|
|
1487
|
+
description: "\u53EA\u8F93\u51FA\u9519\u8BEF"
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
name: "debug",
|
|
1491
|
+
value: "debug",
|
|
1492
|
+
description: "\u8F93\u51FA\u66F4\u8BE6\u7EC6\u7684\u8C03\u8BD5\u65E5\u5FD7"
|
|
1493
|
+
}
|
|
1494
|
+
]
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
function printConfigShow() {
|
|
1499
|
+
const config = loadConfig();
|
|
1500
|
+
if (!config) {
|
|
1501
|
+
logger.info(formatHeader("\u5F53\u524D\u914D\u7F6E", "\u5C1A\u672A\u521B\u5EFA\u672C\u5730\u914D\u7F6E\u6587\u4EF6\u3002"));
|
|
1502
|
+
logger.info(formatBullet("\u8FD0\u884C tokenarena init \u5B8C\u6210\u9996\u6B21\u914D\u7F6E\u3002", "warning"));
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
logger.info(formatHeader("\u5F53\u524D\u914D\u7F6E"));
|
|
1506
|
+
logger.info(formatSection("\u57FA\u7840\u914D\u7F6E"));
|
|
1507
|
+
logger.info(formatKeyValue("API Key", maskSecret(config.apiKey || "")));
|
|
1508
|
+
logger.info(
|
|
1509
|
+
formatKeyValue("API \u5730\u5740", config.apiUrl || "https://token.poco-ai.com")
|
|
1510
|
+
);
|
|
1511
|
+
logger.info(
|
|
1512
|
+
formatKeyValue(
|
|
1513
|
+
"\u540C\u6B65\u95F4\u9694",
|
|
1514
|
+
config.syncInterval ? `${Math.round(config.syncInterval / 6e4)} \u5206\u949F (${config.syncInterval} ms)` : "\u672A\u8BBE\u7F6E\uFF08daemon \u9ED8\u8BA4 5 \u5206\u949F\uFF09"
|
|
1515
|
+
)
|
|
1516
|
+
);
|
|
1517
|
+
logger.info(formatKeyValue("\u65E5\u5FD7\u7EA7\u522B", config.logLevel || "info"));
|
|
1518
|
+
if (config.deviceId) {
|
|
1519
|
+
logger.info(formatKeyValue("\u8BBE\u5907 ID", maskSecret(config.deviceId, 12)));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function handleConfig(args) {
|
|
1523
|
+
const interactive = isInteractiveTerminal();
|
|
1524
|
+
let sub = args[0];
|
|
1525
|
+
if (!sub) {
|
|
1526
|
+
if (!interactive) {
|
|
1527
|
+
logger.error("Usage: tokenarena config <get|set|show>");
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
}
|
|
1530
|
+
sub = await promptConfigSubcommand();
|
|
1531
|
+
}
|
|
1224
1532
|
switch (sub) {
|
|
1225
1533
|
case "get": {
|
|
1226
|
-
|
|
1534
|
+
let key = args[1];
|
|
1227
1535
|
if (!key) {
|
|
1228
|
-
|
|
1536
|
+
if (!interactive) {
|
|
1537
|
+
logger.error("Usage: tokenarena config get <key>");
|
|
1538
|
+
process.exit(1);
|
|
1539
|
+
}
|
|
1540
|
+
key = await promptConfigKey("\u8BF7\u9009\u62E9\u8981\u8BFB\u53D6\u7684\u914D\u7F6E\u9879");
|
|
1541
|
+
}
|
|
1542
|
+
if (!isConfigKey(key)) {
|
|
1543
|
+
logger.error(`Unknown config key: ${key}`);
|
|
1544
|
+
logger.error(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
1229
1545
|
process.exit(1);
|
|
1230
1546
|
}
|
|
1231
1547
|
const config = loadConfig();
|
|
@@ -1237,40 +1553,69 @@ function handleConfig(args) {
|
|
|
1237
1553
|
break;
|
|
1238
1554
|
}
|
|
1239
1555
|
case "set": {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1556
|
+
let key = args[1];
|
|
1557
|
+
if (!key) {
|
|
1558
|
+
if (!interactive) {
|
|
1559
|
+
logger.error("Usage: tokenarena config set <key> <value>");
|
|
1560
|
+
process.exit(1);
|
|
1561
|
+
}
|
|
1562
|
+
key = await promptConfigKey("\u8BF7\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u9879");
|
|
1245
1563
|
}
|
|
1246
|
-
if (!
|
|
1564
|
+
if (!isConfigKey(key)) {
|
|
1247
1565
|
logger.error(`Unknown config key: ${key}`);
|
|
1248
1566
|
logger.error(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
1249
1567
|
process.exit(1);
|
|
1250
1568
|
}
|
|
1251
1569
|
const config = loadConfig() || {
|
|
1252
1570
|
apiKey: "",
|
|
1253
|
-
apiUrl:
|
|
1571
|
+
apiUrl: getDefaultApiUrl()
|
|
1254
1572
|
};
|
|
1573
|
+
const record = config;
|
|
1574
|
+
let value = args[2];
|
|
1575
|
+
if (value === void 0) {
|
|
1576
|
+
if (!interactive) {
|
|
1577
|
+
logger.error("Usage: tokenarena config set <key> <value>");
|
|
1578
|
+
process.exit(1);
|
|
1579
|
+
}
|
|
1580
|
+
value = await promptConfigValue(key, record[key]);
|
|
1581
|
+
}
|
|
1582
|
+
let normalized = value;
|
|
1583
|
+
if (key === "apiKey" && !validateApiKey(value)) {
|
|
1584
|
+
logger.error('API Key must start with "ta_"');
|
|
1585
|
+
process.exit(1);
|
|
1586
|
+
}
|
|
1587
|
+
if (key === "apiUrl") {
|
|
1588
|
+
try {
|
|
1589
|
+
const url = new URL(value);
|
|
1590
|
+
normalized = url.toString().replace(/\/$/, "");
|
|
1591
|
+
} catch {
|
|
1592
|
+
logger.error("apiUrl must be a valid URL");
|
|
1593
|
+
process.exit(1);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1255
1596
|
if (key === "syncInterval") {
|
|
1256
|
-
|
|
1257
|
-
if (Number.isNaN(
|
|
1258
|
-
logger.error("syncInterval must be a number (milliseconds)");
|
|
1597
|
+
normalized = Number.parseInt(value, 10);
|
|
1598
|
+
if (Number.isNaN(normalized) || normalized <= 0) {
|
|
1599
|
+
logger.error("syncInterval must be a positive number (milliseconds)");
|
|
1259
1600
|
process.exit(1);
|
|
1260
1601
|
}
|
|
1261
1602
|
}
|
|
1262
|
-
|
|
1263
|
-
record[key] = value;
|
|
1603
|
+
record[key] = normalized;
|
|
1264
1604
|
saveConfig(config);
|
|
1265
|
-
|
|
1605
|
+
if (interactive) {
|
|
1606
|
+
logger.info(formatHeader("\u914D\u7F6E\u5DF2\u66F4\u65B0"));
|
|
1607
|
+
logger.info(formatKeyValue(key, formatConfigValue(key, normalized)));
|
|
1608
|
+
} else {
|
|
1609
|
+
logger.info(`Set ${key} = ${normalized}`);
|
|
1610
|
+
}
|
|
1266
1611
|
break;
|
|
1267
1612
|
}
|
|
1268
1613
|
case "show": {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
console.log("{}");
|
|
1614
|
+
if (interactive) {
|
|
1615
|
+
printConfigShow();
|
|
1272
1616
|
} else {
|
|
1273
|
-
|
|
1617
|
+
const config = loadConfig();
|
|
1618
|
+
console.log(config ? JSON.stringify(config, null, 2) : "{}");
|
|
1274
1619
|
}
|
|
1275
1620
|
break;
|
|
1276
1621
|
}
|
|
@@ -1286,14 +1631,14 @@ import { hostname as hostname3 } from "os";
|
|
|
1286
1631
|
|
|
1287
1632
|
// src/domain/project-identity.ts
|
|
1288
1633
|
import { createHmac } from "crypto";
|
|
1289
|
-
function toProjectIdentity(
|
|
1290
|
-
if (
|
|
1634
|
+
function toProjectIdentity(input2) {
|
|
1635
|
+
if (input2.mode === "disabled") {
|
|
1291
1636
|
return { projectKey: "unknown", projectLabel: "Unknown Project" };
|
|
1292
1637
|
}
|
|
1293
|
-
if (
|
|
1294
|
-
return { projectKey:
|
|
1638
|
+
if (input2.mode === "raw") {
|
|
1639
|
+
return { projectKey: input2.project, projectLabel: input2.project };
|
|
1295
1640
|
}
|
|
1296
|
-
const projectKey = createHmac("sha256",
|
|
1641
|
+
const projectKey = createHmac("sha256", input2.salt).update(input2.project).digest("hex").slice(0, 16);
|
|
1297
1642
|
return {
|
|
1298
1643
|
projectKey,
|
|
1299
1644
|
projectLabel: `Project ${projectKey.slice(0, 6)}`
|
|
@@ -1303,10 +1648,19 @@ function toProjectIdentity(input) {
|
|
|
1303
1648
|
// src/infrastructure/api/client.ts
|
|
1304
1649
|
import http from "http";
|
|
1305
1650
|
import https from "https";
|
|
1306
|
-
import { URL } from "url";
|
|
1651
|
+
import { URL as URL2 } from "url";
|
|
1307
1652
|
var MAX_RETRIES = 3;
|
|
1308
1653
|
var INITIAL_DELAY = 1e3;
|
|
1309
1654
|
var TIMEOUT_MS = 6e4;
|
|
1655
|
+
function getIngestPayloadSize(device, buckets, sessions) {
|
|
1656
|
+
const payload = {
|
|
1657
|
+
schemaVersion: 2,
|
|
1658
|
+
device,
|
|
1659
|
+
buckets,
|
|
1660
|
+
sessions: sessions ?? []
|
|
1661
|
+
};
|
|
1662
|
+
return Buffer.byteLength(JSON.stringify(payload));
|
|
1663
|
+
}
|
|
1310
1664
|
var ApiClient = class {
|
|
1311
1665
|
constructor(apiUrl, apiKey) {
|
|
1312
1666
|
this.apiUrl = apiUrl;
|
|
@@ -1336,14 +1690,15 @@ var ApiClient = class {
|
|
|
1336
1690
|
}
|
|
1337
1691
|
sendIngest(device, buckets, sessions, onProgress) {
|
|
1338
1692
|
return new Promise((resolve2, reject) => {
|
|
1339
|
-
const url = new
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1693
|
+
const url = new URL2("/api/usage/ingest", this.apiUrl);
|
|
1694
|
+
const body = Buffer.from(
|
|
1695
|
+
JSON.stringify({
|
|
1696
|
+
schemaVersion: 2,
|
|
1697
|
+
device,
|
|
1698
|
+
buckets,
|
|
1699
|
+
sessions: sessions ?? []
|
|
1700
|
+
})
|
|
1701
|
+
);
|
|
1347
1702
|
const totalBytes = body.length;
|
|
1348
1703
|
const mod = url.protocol === "https:" ? https : http;
|
|
1349
1704
|
const req = mod.request(
|
|
@@ -1416,7 +1771,7 @@ var ApiClient = class {
|
|
|
1416
1771
|
*/
|
|
1417
1772
|
async fetchSettings() {
|
|
1418
1773
|
return new Promise((resolve2, reject) => {
|
|
1419
|
-
const url = new
|
|
1774
|
+
const url = new URL2("/api/usage/settings", this.apiUrl);
|
|
1420
1775
|
const mod = url.protocol === "https:" ? https : http;
|
|
1421
1776
|
const req = mod.request(
|
|
1422
1777
|
url,
|
|
@@ -1467,7 +1822,7 @@ var ApiClient = class {
|
|
|
1467
1822
|
*/
|
|
1468
1823
|
async deleteAllData(opts) {
|
|
1469
1824
|
return new Promise((resolve2, reject) => {
|
|
1470
|
-
const url = new
|
|
1825
|
+
const url = new URL2("/api/usage/ingest", this.apiUrl);
|
|
1471
1826
|
if (opts?.hostname) {
|
|
1472
1827
|
url.searchParams.set("hostname", opts.hostname);
|
|
1473
1828
|
}
|
|
@@ -1731,16 +2086,24 @@ function getDetectedTools() {
|
|
|
1731
2086
|
// src/services/sync-service.ts
|
|
1732
2087
|
var BATCH_SIZE = 100;
|
|
1733
2088
|
var SESSION_BATCH_SIZE = 500;
|
|
2089
|
+
var PROGRESS_BAR_WIDTH = 28;
|
|
1734
2090
|
function formatBytes(bytes) {
|
|
1735
2091
|
if (bytes < 1024) return `${bytes}B`;
|
|
1736
2092
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
1737
2093
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
1738
2094
|
}
|
|
1739
|
-
function
|
|
1740
|
-
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1743
|
-
|
|
2095
|
+
function renderProgressBar(progress) {
|
|
2096
|
+
const safeProgress = Math.max(0, Math.min(progress, 1));
|
|
2097
|
+
const filled = Math.round(safeProgress * PROGRESS_BAR_WIDTH);
|
|
2098
|
+
return `${"\u2588".repeat(filled)}${"\u2591".repeat(PROGRESS_BAR_WIDTH - filled)}`;
|
|
2099
|
+
}
|
|
2100
|
+
function writeUploadProgress(sent, total, batchNum, totalBatches) {
|
|
2101
|
+
const pct = total > 0 ? Math.round(sent / total * 100) : 100;
|
|
2102
|
+
const progressBar = renderProgressBar(total > 0 ? sent / total : 1);
|
|
2103
|
+
const batchLabel = totalBatches > 1 ? ` \xB7 batch ${batchNum}/${totalBatches}` : "";
|
|
2104
|
+
process.stdout.write(
|
|
2105
|
+
`\r Uploading ${progressBar} ${String(pct).padStart(3, " ")}% \xB7 ${formatBytes(sent)}/${formatBytes(total)}${batchLabel}\x1B[K`
|
|
2106
|
+
);
|
|
1744
2107
|
}
|
|
1745
2108
|
function toDeviceMetadata(config) {
|
|
1746
2109
|
return {
|
|
@@ -1889,19 +2252,30 @@ async function runSync(config, opts = {}) {
|
|
|
1889
2252
|
const device = toDeviceMetadata(config);
|
|
1890
2253
|
const uploadBuckets = toUploadBuckets(allBuckets, settings, device);
|
|
1891
2254
|
const uploadSessions = toUploadSessions(allSessions, settings, device);
|
|
1892
|
-
if (!quiet) {
|
|
1893
|
-
const projectModeLabel = {
|
|
1894
|
-
hashed: "\u54C8\u5E0C\u5316",
|
|
1895
|
-
raw: "\u539F\u59CB\u540D\u79F0",
|
|
1896
|
-
disabled: "\u5DF2\u9690\u85CF"
|
|
1897
|
-
};
|
|
1898
|
-
logger.info(`\u{1F4C2} \u9879\u76EE\u6A21\u5F0F: ${projectModeLabel[settings.projectMode]}`);
|
|
1899
|
-
}
|
|
1900
2255
|
const bucketBatches = Math.ceil(uploadBuckets.length / BATCH_SIZE);
|
|
1901
2256
|
const sessionBatches = Math.ceil(
|
|
1902
2257
|
uploadSessions.length / SESSION_BATCH_SIZE
|
|
1903
2258
|
);
|
|
1904
2259
|
const totalBatches = Math.max(bucketBatches, sessionBatches, 1);
|
|
2260
|
+
const batchPayloadSizes = Array.from(
|
|
2261
|
+
{ length: totalBatches },
|
|
2262
|
+
(_, batchIdx) => getIngestPayloadSize(
|
|
2263
|
+
device,
|
|
2264
|
+
uploadBuckets.slice(
|
|
2265
|
+
batchIdx * BATCH_SIZE,
|
|
2266
|
+
(batchIdx + 1) * BATCH_SIZE
|
|
2267
|
+
),
|
|
2268
|
+
uploadSessions.slice(
|
|
2269
|
+
batchIdx * SESSION_BATCH_SIZE,
|
|
2270
|
+
(batchIdx + 1) * SESSION_BATCH_SIZE
|
|
2271
|
+
)
|
|
2272
|
+
)
|
|
2273
|
+
);
|
|
2274
|
+
const totalPayloadBytes = batchPayloadSizes.reduce(
|
|
2275
|
+
(sum, size) => sum + size,
|
|
2276
|
+
0
|
|
2277
|
+
);
|
|
2278
|
+
let uploadedBytesBeforeBatch = 0;
|
|
1905
2279
|
if (!quiet) {
|
|
1906
2280
|
const parts = [];
|
|
1907
2281
|
if (uploadBuckets.length > 0) {
|
|
@@ -1924,22 +2298,30 @@ async function runSync(config, opts = {}) {
|
|
|
1924
2298
|
(batchIdx + 1) * SESSION_BATCH_SIZE
|
|
1925
2299
|
);
|
|
1926
2300
|
const batchNum = batchIdx + 1;
|
|
1927
|
-
const prefix = totalBatches > 1 ? ` [${batchNum}/${totalBatches}] ` : " ";
|
|
1928
2301
|
const result = await apiClient.ingest(
|
|
1929
2302
|
device,
|
|
1930
2303
|
batch,
|
|
1931
2304
|
batchSessions.length > 0 ? batchSessions : void 0,
|
|
1932
2305
|
quiet ? void 0 : (sent, total) => {
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2306
|
+
writeUploadProgress(
|
|
2307
|
+
uploadedBytesBeforeBatch + sent,
|
|
2308
|
+
totalPayloadBytes || total,
|
|
2309
|
+
batchNum,
|
|
2310
|
+
totalBatches
|
|
1936
2311
|
);
|
|
1937
2312
|
}
|
|
1938
2313
|
);
|
|
1939
2314
|
totalIngested += result.ingested ?? batch.length;
|
|
1940
2315
|
totalSessionsSynced += result.sessions ?? batchSessions.length;
|
|
2316
|
+
uploadedBytesBeforeBatch += batchPayloadSizes[batchIdx] ?? 0;
|
|
1941
2317
|
}
|
|
1942
2318
|
if (!quiet && (totalBatches > 1 || uploadBuckets.length > 0)) {
|
|
2319
|
+
writeUploadProgress(
|
|
2320
|
+
totalPayloadBytes,
|
|
2321
|
+
totalPayloadBytes,
|
|
2322
|
+
totalBatches,
|
|
2323
|
+
totalBatches
|
|
2324
|
+
);
|
|
1943
2325
|
process.stdout.write("\n");
|
|
1944
2326
|
}
|
|
1945
2327
|
const syncParts = [`${totalIngested} buckets`];
|
|
@@ -1947,23 +2329,6 @@ async function runSync(config, opts = {}) {
|
|
|
1947
2329
|
syncParts.push(`${totalSessionsSynced} sessions`);
|
|
1948
2330
|
}
|
|
1949
2331
|
logger.info(`Synced ${syncParts.join(" + ")}.`);
|
|
1950
|
-
if (!quiet && totalSessionsSynced > 0) {
|
|
1951
|
-
const totalActive = uploadSessions.reduce(
|
|
1952
|
-
(sum, session) => sum + session.activeSeconds,
|
|
1953
|
-
0
|
|
1954
|
-
);
|
|
1955
|
-
const totalDuration = uploadSessions.reduce(
|
|
1956
|
-
(sum, session) => sum + session.durationSeconds,
|
|
1957
|
-
0
|
|
1958
|
-
);
|
|
1959
|
-
const totalMsgs = uploadSessions.reduce(
|
|
1960
|
-
(sum, session) => sum + session.messageCount,
|
|
1961
|
-
0
|
|
1962
|
-
);
|
|
1963
|
-
logger.info(
|
|
1964
|
-
` active: ${formatTime(totalActive)} / total: ${formatTime(totalDuration)}, ${totalMsgs} messages`
|
|
1965
|
-
);
|
|
1966
|
-
}
|
|
1967
2332
|
if (!quiet) {
|
|
1968
2333
|
logger.info(`
|
|
1969
2334
|
View your dashboard at: ${apiUrl}/usage`);
|
|
@@ -2016,64 +2381,17 @@ View your dashboard at: ${apiUrl}/usage`);
|
|
|
2016
2381
|
process.exit(1);
|
|
2017
2382
|
}
|
|
2018
2383
|
|
|
2019
|
-
// src/commands/daemon.ts
|
|
2020
|
-
var DEFAULT_INTERVAL = 5 * 6e4;
|
|
2021
|
-
function log(msg) {
|
|
2022
|
-
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
2023
|
-
process.stdout.write(`[${ts}] ${msg}
|
|
2024
|
-
`);
|
|
2025
|
-
}
|
|
2026
|
-
function sleep(ms) {
|
|
2027
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2028
|
-
}
|
|
2029
|
-
async function runDaemon(opts = {}) {
|
|
2030
|
-
const config = loadConfig();
|
|
2031
|
-
if (!config?.apiKey) {
|
|
2032
|
-
logger.error("Not configured. Run `tokenarena init` first.");
|
|
2033
|
-
process.exit(1);
|
|
2034
|
-
}
|
|
2035
|
-
const interval = opts.interval || config.syncInterval || DEFAULT_INTERVAL;
|
|
2036
|
-
const intervalMin = Math.round(interval / 6e4);
|
|
2037
|
-
log(`Daemon started (sync every ${intervalMin}m, Ctrl+C to stop)`);
|
|
2038
|
-
while (true) {
|
|
2039
|
-
try {
|
|
2040
|
-
await runSync(config, {
|
|
2041
|
-
quiet: true,
|
|
2042
|
-
source: "daemon",
|
|
2043
|
-
throws: true
|
|
2044
|
-
});
|
|
2045
|
-
} catch (err) {
|
|
2046
|
-
if (err.message === "UNAUTHORIZED") {
|
|
2047
|
-
log("API key invalid. Exiting.");
|
|
2048
|
-
process.exit(1);
|
|
2049
|
-
}
|
|
2050
|
-
log(`Sync error: ${err.message}`);
|
|
2051
|
-
}
|
|
2052
|
-
await sleep(interval);
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
2384
|
// src/commands/init.ts
|
|
2057
2385
|
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
2058
2386
|
import { existsSync as existsSync11 } from "fs";
|
|
2059
2387
|
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
2060
2388
|
import { homedir as homedir8, platform } from "os";
|
|
2061
2389
|
import { dirname as dirname2, join as join11, posix, win32 } from "path";
|
|
2062
|
-
import { createInterface } from "readline";
|
|
2063
2390
|
function joinForPlatform(currentPlatform, ...parts) {
|
|
2064
2391
|
return currentPlatform === "win32" ? win32.join(...parts) : posix.join(...parts);
|
|
2065
2392
|
}
|
|
2066
|
-
function
|
|
2067
|
-
|
|
2068
|
-
return new Promise((resolve2) => {
|
|
2069
|
-
rl.question(question, (answer) => {
|
|
2070
|
-
rl.close();
|
|
2071
|
-
resolve2(answer.trim());
|
|
2072
|
-
});
|
|
2073
|
-
});
|
|
2074
|
-
}
|
|
2075
|
-
function basenameLikeShell(input) {
|
|
2076
|
-
return input.split(/[\\/]+/).pop()?.replace(/\.exe$/i, "") ?? input;
|
|
2393
|
+
function basenameLikeShell(input2) {
|
|
2394
|
+
return input2.split(/[\\/]+/).pop()?.replace(/\.exe$/i, "") ?? input2;
|
|
2077
2395
|
}
|
|
2078
2396
|
function getBrowserLaunchCommand(url, currentPlatform = platform()) {
|
|
2079
2397
|
switch (currentPlatform) {
|
|
@@ -2210,43 +2528,62 @@ function resolveShellAliasSetup(options = {}) {
|
|
|
2210
2528
|
}
|
|
2211
2529
|
}
|
|
2212
2530
|
async function runInit(opts = {}) {
|
|
2213
|
-
logger.info("
|
|
2531
|
+
logger.info(formatHeader("TokenArena \u521D\u59CB\u5316"));
|
|
2214
2532
|
const existing = loadConfig();
|
|
2215
2533
|
if (existing?.apiKey) {
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2534
|
+
logger.info(formatSection("\u68C0\u6D4B\u5230\u5DF2\u6709\u914D\u7F6E"));
|
|
2535
|
+
logger.info(formatKeyValue("\u5F53\u524D API Key", maskSecret(existing.apiKey)));
|
|
2536
|
+
logger.info(
|
|
2537
|
+
formatKeyValue(
|
|
2538
|
+
"\u5F53\u524D API \u5730\u5740",
|
|
2539
|
+
existing.apiUrl || "https://token.poco-ai.com"
|
|
2540
|
+
)
|
|
2541
|
+
);
|
|
2542
|
+
const shouldOverwrite = await promptConfirm({
|
|
2543
|
+
message: "\u5DF2\u7ECF\u5B58\u5728\u672C\u5730\u914D\u7F6E\uFF0C\u662F\u5426\u8986\u76D6\u5E76\u91CD\u65B0\u521D\u59CB\u5316\uFF1F",
|
|
2544
|
+
defaultValue: false
|
|
2545
|
+
});
|
|
2546
|
+
if (!shouldOverwrite) {
|
|
2547
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002", "warning"));
|
|
2219
2548
|
return;
|
|
2220
2549
|
}
|
|
2221
2550
|
}
|
|
2222
2551
|
const apiUrl = opts.apiUrl || getDefaultApiUrl();
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
}
|
|
2232
|
-
logger.info(
|
|
2233
|
-
|
|
2552
|
+
const cliKeysUrl = `${apiUrl}/zh/settings/cli-keys`;
|
|
2553
|
+
logger.info(formatSection("\u7B2C 1 \u6B65\uFF1A\u51C6\u5907 API Key"));
|
|
2554
|
+
logger.info(formatBullet("\u6D4F\u89C8\u5668\u5C06\u5C1D\u8BD5\u81EA\u52A8\u6253\u5F00 CLI Key \u9875\u9762\u3002"));
|
|
2555
|
+
logger.info(formatKeyValue("Key \u9875\u9762", formatMutedPath(cliKeysUrl)));
|
|
2556
|
+
openBrowser(cliKeysUrl);
|
|
2557
|
+
const apiKey = await promptPassword({
|
|
2558
|
+
message: "\u8BF7\u7C98\u8D34\u4F60\u7684 CLI API Key",
|
|
2559
|
+
validate: (value) => validateApiKey(value) || 'API Key \u5FC5\u987B\u4EE5 "ta_" \u5F00\u5934\u3002'
|
|
2560
|
+
});
|
|
2561
|
+
logger.info(formatSection("\u7B2C 2 \u6B65\uFF1A\u9A8C\u8BC1 API Key"));
|
|
2562
|
+
logger.info(formatKeyValue("\u5F85\u9A8C\u8BC1 Key", maskSecret(apiKey)));
|
|
2234
2563
|
try {
|
|
2235
2564
|
const client = new ApiClient(apiUrl, apiKey);
|
|
2236
2565
|
const settings = await client.fetchSettings();
|
|
2237
2566
|
if (!settings) {
|
|
2238
2567
|
logger.info(
|
|
2239
|
-
|
|
2568
|
+
formatBullet(
|
|
2569
|
+
"\u65E0\u6CD5\u5728\u7EBF\u9A8C\u8BC1 Key\uFF08\u53EF\u80FD\u662F\u7F51\u7EDC\u539F\u56E0\uFF09\uFF0C\u5C06\u7EE7\u7EED\u4FDD\u5B58\u3002",
|
|
2570
|
+
"warning"
|
|
2571
|
+
)
|
|
2240
2572
|
);
|
|
2241
2573
|
} else {
|
|
2242
|
-
logger.info("Key
|
|
2574
|
+
logger.info(formatBullet("API Key \u9A8C\u8BC1\u6210\u529F\u3002", "success"));
|
|
2243
2575
|
}
|
|
2244
2576
|
} catch (err) {
|
|
2245
2577
|
if (err.message === "UNAUTHORIZED") {
|
|
2246
2578
|
logger.error("Invalid API key. Please check and try again.");
|
|
2247
2579
|
process.exit(1);
|
|
2248
2580
|
}
|
|
2249
|
-
logger.info(
|
|
2581
|
+
logger.info(
|
|
2582
|
+
formatBullet(
|
|
2583
|
+
"\u65E0\u6CD5\u5B8C\u6210\u5728\u7EBF\u9A8C\u8BC1\uFF08\u53EF\u80FD\u662F\u7F51\u7EDC\u539F\u56E0\uFF09\uFF0C\u5C06\u7EE7\u7EED\u4FDD\u5B58\u3002",
|
|
2584
|
+
"warning"
|
|
2585
|
+
)
|
|
2586
|
+
);
|
|
2250
2587
|
}
|
|
2251
2588
|
const config = {
|
|
2252
2589
|
apiKey,
|
|
@@ -2256,17 +2593,28 @@ Verifying key ${apiKey.slice(0, 8)}...`);
|
|
|
2256
2593
|
saveConfig(config);
|
|
2257
2594
|
const deviceId = getOrCreateDeviceId(config);
|
|
2258
2595
|
config.deviceId = deviceId;
|
|
2259
|
-
logger.info(
|
|
2596
|
+
logger.info(formatSection("\u7B2C 3 \u6B65\uFF1A\u5B8C\u6210\u672C\u5730\u6CE8\u518C"));
|
|
2260
2597
|
const tools = getDetectedTools();
|
|
2261
2598
|
if (tools.length > 0) {
|
|
2262
|
-
logger.info(
|
|
2599
|
+
logger.info(formatSection("\u68C0\u6D4B\u5230\u7684 AI CLI"));
|
|
2600
|
+
for (const tool of tools) {
|
|
2601
|
+
logger.info(formatBullet(tool.name, "success"));
|
|
2602
|
+
}
|
|
2263
2603
|
} else {
|
|
2264
|
-
logger.info("
|
|
2604
|
+
logger.info(formatSection("\u68C0\u6D4B\u5230\u7684 AI CLI"));
|
|
2605
|
+
logger.info(
|
|
2606
|
+
formatBullet(
|
|
2607
|
+
"\u5F53\u524D\u672A\u68C0\u6D4B\u5230\u5DF2\u5B89\u88C5\u5DE5\u5177\uFF0C\u7A0D\u540E\u5B89\u88C5\u540E\u4E5F\u53EF\u4EE5\u76F4\u63A5\u6267\u884C sync\u3002",
|
|
2608
|
+
"warning"
|
|
2609
|
+
)
|
|
2610
|
+
);
|
|
2265
2611
|
}
|
|
2266
|
-
logger.info("\
|
|
2612
|
+
logger.info(formatSection("\u9996\u6B21\u540C\u6B65"));
|
|
2613
|
+
logger.info(formatBullet("\u6B63\u5728\u4E0A\u4F20\u672C\u5730\u5DF2\u6709\u7684\u4F7F\u7528\u6570\u636E\u3002"));
|
|
2267
2614
|
await runSync(config, { source: "init" });
|
|
2268
|
-
logger.info(
|
|
2269
|
-
|
|
2615
|
+
logger.info(formatSection("\u521D\u59CB\u5316\u5B8C\u6210"));
|
|
2616
|
+
logger.info(formatBullet("TokenArena \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002", "success"));
|
|
2617
|
+
logger.info(formatKeyValue("\u63A7\u5236\u53F0", `${apiUrl}/usage`));
|
|
2270
2618
|
await setupShellAlias();
|
|
2271
2619
|
}
|
|
2272
2620
|
async function setupShellAlias() {
|
|
@@ -2274,11 +2622,12 @@ async function setupShellAlias() {
|
|
|
2274
2622
|
if (!setup) {
|
|
2275
2623
|
return;
|
|
2276
2624
|
}
|
|
2277
|
-
const
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
);
|
|
2281
|
-
if (
|
|
2625
|
+
const shouldCreateAlias = await promptConfirm({
|
|
2626
|
+
message: `\u662F\u5426\u4E3A ${setup.shellLabel} \u81EA\u52A8\u6DFB\u52A0 ta \u522B\u540D\uFF1F`,
|
|
2627
|
+
defaultValue: true
|
|
2628
|
+
});
|
|
2629
|
+
if (!shouldCreateAlias) {
|
|
2630
|
+
logger.info(formatBullet("\u5DF2\u8DF3\u8FC7 shell alias \u8BBE\u7F6E\u3002"));
|
|
2282
2631
|
return;
|
|
2283
2632
|
}
|
|
2284
2633
|
try {
|
|
@@ -2292,10 +2641,7 @@ Set up ${setup.shellLabel} alias 'ta' for 'tokenarena'? (Y/n) `
|
|
|
2292
2641
|
(pattern) => normalizedContent.includes(pattern.toLowerCase())
|
|
2293
2642
|
);
|
|
2294
2643
|
if (aliasExists) {
|
|
2295
|
-
logger.info(
|
|
2296
|
-
`
|
|
2297
|
-
Alias 'ta' already exists in ${setup.configFile}. Skipping.`
|
|
2298
|
-
);
|
|
2644
|
+
logger.info(formatBullet(`\u522B\u540D ta \u5DF2\u5B58\u5728\uFF1A${setup.configFile}`));
|
|
2299
2645
|
return;
|
|
2300
2646
|
}
|
|
2301
2647
|
const aliasWithComment = `
|
|
@@ -2303,18 +2649,76 @@ Alias 'ta' already exists in ${setup.configFile}. Skipping.`
|
|
|
2303
2649
|
${setup.aliasLine}
|
|
2304
2650
|
`;
|
|
2305
2651
|
await appendFile(setup.configFile, aliasWithComment, "utf-8");
|
|
2306
|
-
logger.info(
|
|
2307
|
-
|
|
2652
|
+
logger.info(formatSection("Shell alias"));
|
|
2653
|
+
logger.info(formatBullet(`\u5DF2\u5199\u5165 ${setup.configFile}`, "success"));
|
|
2308
2654
|
logger.info(
|
|
2309
|
-
|
|
2655
|
+
formatKeyValue("\u751F\u6548\u65B9\u5F0F", `\u6267\u884C '${setup.sourceHint}' \u6216\u91CD\u542F\u7EC8\u7AEF`)
|
|
2310
2656
|
);
|
|
2311
|
-
logger.info("
|
|
2657
|
+
logger.info(formatKeyValue("\u4E4B\u540E\u53EF\u7528", "ta sync"));
|
|
2312
2658
|
} catch (err) {
|
|
2659
|
+
logger.info(formatSection("Shell alias"));
|
|
2313
2660
|
logger.info(
|
|
2314
|
-
|
|
2315
|
-
|
|
2661
|
+
formatBullet(
|
|
2662
|
+
`\u65E0\u6CD5\u5199\u5165 ${setup.configFile}: ${err.message}`,
|
|
2663
|
+
"warning"
|
|
2664
|
+
)
|
|
2316
2665
|
);
|
|
2317
|
-
logger.info(
|
|
2666
|
+
logger.info(formatKeyValue("\u8BF7\u624B\u52A8\u6DFB\u52A0", setup.aliasLine));
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// src/commands/daemon.ts
|
|
2671
|
+
var DEFAULT_INTERVAL = 5 * 6e4;
|
|
2672
|
+
function log(msg) {
|
|
2673
|
+
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
2674
|
+
process.stdout.write(`[${ts}] ${msg}
|
|
2675
|
+
`);
|
|
2676
|
+
}
|
|
2677
|
+
function sleep(ms) {
|
|
2678
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2679
|
+
}
|
|
2680
|
+
async function runDaemon(opts = {}) {
|
|
2681
|
+
const config = loadConfig();
|
|
2682
|
+
if (!config?.apiKey) {
|
|
2683
|
+
if (isInteractiveTerminal()) {
|
|
2684
|
+
logger.info(
|
|
2685
|
+
formatHeader(
|
|
2686
|
+
"\u5C1A\u672A\u5B8C\u6210\u521D\u59CB\u5316",
|
|
2687
|
+
"\u542F\u52A8 daemon \u524D\u9700\u8981\u5148\u914D\u7F6E\u6709\u6548\u7684 API Key\u3002"
|
|
2688
|
+
)
|
|
2689
|
+
);
|
|
2690
|
+
const shouldInit = await promptConfirm({
|
|
2691
|
+
message: "\u662F\u5426\u5148\u8FDB\u5165\u521D\u59CB\u5316\u6D41\u7A0B\uFF1F",
|
|
2692
|
+
defaultValue: true
|
|
2693
|
+
});
|
|
2694
|
+
if (shouldInit) {
|
|
2695
|
+
await runInit();
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u542F\u52A8 daemon\u3002", "warning"));
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
logger.error("Not configured. Run `tokenarena init` first.");
|
|
2702
|
+
process.exit(1);
|
|
2703
|
+
}
|
|
2704
|
+
const interval = opts.interval || config.syncInterval || DEFAULT_INTERVAL;
|
|
2705
|
+
const intervalMin = Math.round(interval / 6e4);
|
|
2706
|
+
log(`Daemon started (sync every ${intervalMin}m, Ctrl+C to stop)`);
|
|
2707
|
+
while (true) {
|
|
2708
|
+
try {
|
|
2709
|
+
await runSync(config, {
|
|
2710
|
+
quiet: true,
|
|
2711
|
+
source: "daemon",
|
|
2712
|
+
throws: true
|
|
2713
|
+
});
|
|
2714
|
+
} catch (err) {
|
|
2715
|
+
if (err.message === "UNAUTHORIZED") {
|
|
2716
|
+
log("API key invalid. Exiting.");
|
|
2717
|
+
process.exit(1);
|
|
2718
|
+
}
|
|
2719
|
+
log(`Sync error: ${err.message}`);
|
|
2720
|
+
}
|
|
2721
|
+
await sleep(interval);
|
|
2318
2722
|
}
|
|
2319
2723
|
}
|
|
2320
2724
|
|
|
@@ -2324,58 +2728,94 @@ function formatMaybe(value) {
|
|
|
2324
2728
|
}
|
|
2325
2729
|
async function runStatus() {
|
|
2326
2730
|
const config = loadConfig();
|
|
2327
|
-
logger.info(
|
|
2731
|
+
logger.info(
|
|
2732
|
+
formatHeader(
|
|
2733
|
+
"TokenArena \u72B6\u6001",
|
|
2734
|
+
"\u67E5\u770B\u5F53\u524D\u914D\u7F6E\u3001\u5DF2\u68C0\u6D4B\u5DE5\u5177\u4EE5\u53CA\u6700\u8FD1\u4E00\u6B21\u540C\u6B65\u60C5\u51B5\u3002"
|
|
2735
|
+
)
|
|
2736
|
+
);
|
|
2737
|
+
logger.info(formatSection("\u914D\u7F6E"));
|
|
2328
2738
|
if (!config?.apiKey) {
|
|
2329
|
-
logger.info("
|
|
2330
|
-
logger.info("
|
|
2739
|
+
logger.info(formatKeyValue("\u72B6\u6001", formatStatusBadge("\u672A\u914D\u7F6E", "warning")));
|
|
2740
|
+
logger.info(formatBullet("\u8FD0\u884C tokenarena init \u5B8C\u6210\u9996\u6B21\u8BBE\u7F6E\u3002", "warning"));
|
|
2331
2741
|
} else {
|
|
2332
|
-
logger.info(
|
|
2333
|
-
logger.info(
|
|
2334
|
-
logger.info(
|
|
2742
|
+
logger.info(formatKeyValue("\u72B6\u6001", formatStatusBadge("\u5DF2\u914D\u7F6E", "success")));
|
|
2743
|
+
logger.info(formatKeyValue("\u914D\u7F6E\u6587\u4EF6", getConfigPath()));
|
|
2744
|
+
logger.info(formatKeyValue("API Key", maskSecret(config.apiKey)));
|
|
2745
|
+
logger.info(
|
|
2746
|
+
formatKeyValue("API URL", config.apiUrl || "https://token.poco-ai.com")
|
|
2747
|
+
);
|
|
2335
2748
|
if (config.syncInterval) {
|
|
2336
2749
|
logger.info(
|
|
2337
|
-
|
|
2750
|
+
formatKeyValue(
|
|
2751
|
+
"\u540C\u6B65\u95F4\u9694",
|
|
2752
|
+
`${Math.round(config.syncInterval / 6e4)} \u5206\u949F`
|
|
2753
|
+
)
|
|
2338
2754
|
);
|
|
2339
2755
|
}
|
|
2340
2756
|
}
|
|
2341
|
-
logger.info("\
|
|
2757
|
+
logger.info(formatSection("\u5DF2\u68C0\u6D4B\u5DE5\u5177"));
|
|
2342
2758
|
const detected = detectInstalledTools();
|
|
2343
2759
|
if (detected.length === 0) {
|
|
2344
|
-
logger.info("
|
|
2760
|
+
logger.info(formatBullet("\u672A\u68C0\u6D4B\u5230\u5DF2\u5B89\u88C5\u7684 AI CLI\u3002", "warning"));
|
|
2345
2761
|
} else {
|
|
2346
2762
|
for (const tool of detected) {
|
|
2347
|
-
logger.info(
|
|
2763
|
+
logger.info(formatBullet(tool.name, "success"));
|
|
2348
2764
|
}
|
|
2349
|
-
logger.info("");
|
|
2350
2765
|
}
|
|
2351
|
-
logger.info("
|
|
2766
|
+
logger.info(formatSection("\u652F\u6301\u7684\u5DE5\u5177"));
|
|
2352
2767
|
for (const tool of getAllTools()) {
|
|
2353
|
-
const installed = isToolInstalled(tool.id)
|
|
2354
|
-
logger.info(
|
|
2768
|
+
const installed = isToolInstalled(tool.id);
|
|
2769
|
+
logger.info(
|
|
2770
|
+
formatBullet(
|
|
2771
|
+
`${tool.name} \xB7 ${installed ? "\u5DF2\u5B89\u88C5" : "\u672A\u53D1\u73B0"}`,
|
|
2772
|
+
installed ? "success" : "neutral"
|
|
2773
|
+
)
|
|
2774
|
+
);
|
|
2355
2775
|
}
|
|
2356
2776
|
const syncState = loadSyncState();
|
|
2357
|
-
logger.info("\
|
|
2358
|
-
|
|
2359
|
-
logger.info(
|
|
2360
|
-
|
|
2777
|
+
logger.info(formatSection("\u540C\u6B65\u72B6\u6001"));
|
|
2778
|
+
const statusTone = syncState.status === "idle" ? "success" : syncState.status === "syncing" ? "warning" : "danger";
|
|
2779
|
+
logger.info(
|
|
2780
|
+
formatKeyValue("\u72B6\u6001", formatStatusBadge(syncState.status, statusTone))
|
|
2781
|
+
);
|
|
2782
|
+
logger.info(formatKeyValue("\u4E0A\u6B21\u5C1D\u8BD5", formatMaybe(syncState.lastAttemptAt)));
|
|
2783
|
+
logger.info(formatKeyValue("\u4E0A\u6B21\u6210\u529F", formatMaybe(syncState.lastSuccessAt)));
|
|
2361
2784
|
if (syncState.lastSource) {
|
|
2362
|
-
logger.info(
|
|
2785
|
+
logger.info(formatKeyValue("\u89E6\u53D1\u6765\u6E90", syncState.lastSource));
|
|
2363
2786
|
}
|
|
2364
2787
|
if (syncState.lastError) {
|
|
2365
|
-
logger.info(
|
|
2788
|
+
logger.info(formatKeyValue("\u9519\u8BEF\u4FE1\u606F", syncState.lastError));
|
|
2366
2789
|
}
|
|
2367
2790
|
if (syncState.lastResult) {
|
|
2368
2791
|
logger.info(
|
|
2369
|
-
|
|
2792
|
+
formatKeyValue(
|
|
2793
|
+
"\u6700\u8FD1\u7ED3\u679C",
|
|
2794
|
+
`${syncState.lastResult.buckets} buckets, ${syncState.lastResult.sessions} sessions`
|
|
2795
|
+
)
|
|
2370
2796
|
);
|
|
2371
2797
|
}
|
|
2372
|
-
logger.info("");
|
|
2373
2798
|
}
|
|
2374
2799
|
|
|
2375
2800
|
// src/commands/sync.ts
|
|
2376
2801
|
async function runSyncCommand(opts = {}) {
|
|
2377
2802
|
const config = loadConfig();
|
|
2378
2803
|
if (!config?.apiKey) {
|
|
2804
|
+
if (isInteractiveTerminal()) {
|
|
2805
|
+
logger.info(
|
|
2806
|
+
formatHeader("\u5C1A\u672A\u5B8C\u6210\u521D\u59CB\u5316", "\u540C\u6B65\u524D\u9700\u8981\u5148\u914D\u7F6E\u6709\u6548\u7684 API Key\u3002")
|
|
2807
|
+
);
|
|
2808
|
+
const shouldInit = await promptConfirm({
|
|
2809
|
+
message: "\u662F\u5426\u73B0\u5728\u8FDB\u5165\u521D\u59CB\u5316\u6D41\u7A0B\uFF1F",
|
|
2810
|
+
defaultValue: true
|
|
2811
|
+
});
|
|
2812
|
+
if (shouldInit) {
|
|
2813
|
+
await runInit();
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u540C\u6B65\u3002", "warning"));
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2379
2819
|
logger.error("Not configured. Run `tokenarena init` first.");
|
|
2380
2820
|
process.exit(1);
|
|
2381
2821
|
}
|
|
@@ -2388,16 +2828,6 @@ async function runSyncCommand(opts = {}) {
|
|
|
2388
2828
|
// src/commands/uninstall.ts
|
|
2389
2829
|
import { existsSync as existsSync12, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2390
2830
|
import { homedir as homedir9, platform as platform2 } from "os";
|
|
2391
|
-
import { createInterface as createInterface2 } from "readline";
|
|
2392
|
-
function prompt2(question) {
|
|
2393
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2394
|
-
return new Promise((resolve2) => {
|
|
2395
|
-
rl.question(question, (answer) => {
|
|
2396
|
-
rl.close();
|
|
2397
|
-
resolve2(answer.trim());
|
|
2398
|
-
});
|
|
2399
|
-
});
|
|
2400
|
-
}
|
|
2401
2831
|
function removeShellAlias() {
|
|
2402
2832
|
const shell = process.env.SHELL;
|
|
2403
2833
|
if (!shell) return;
|
|
@@ -2457,42 +2887,168 @@ async function runUninstall() {
|
|
|
2457
2887
|
const configPath = getConfigPath();
|
|
2458
2888
|
const configDir = getConfigDir();
|
|
2459
2889
|
if (!existsSync12(configPath)) {
|
|
2460
|
-
logger.info("
|
|
2890
|
+
logger.info(formatHeader("\u5378\u8F7D TokenArena"));
|
|
2891
|
+
logger.info(formatBullet("\u672A\u53D1\u73B0\u672C\u5730\u914D\u7F6E\uFF0C\u65E0\u9700\u5378\u8F7D\u3002"));
|
|
2461
2892
|
return;
|
|
2462
2893
|
}
|
|
2463
2894
|
const config = loadConfig();
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
"\nAre you sure you want to uninstall? This will delete all local data. (y/N) "
|
|
2895
|
+
logger.info(
|
|
2896
|
+
formatHeader(
|
|
2897
|
+
"\u5378\u8F7D TokenArena",
|
|
2898
|
+
"\u8BE5\u64CD\u4F5C\u4F1A\u5220\u9664\u672C\u5730\u914D\u7F6E\u3001\u540C\u6B65\u72B6\u6001\u4E0E\u8FD0\u884C\u65F6\u6587\u4EF6\u3002"
|
|
2899
|
+
)
|
|
2470
2900
|
);
|
|
2471
|
-
if (
|
|
2472
|
-
logger.info("
|
|
2901
|
+
if (config?.apiKey) {
|
|
2902
|
+
logger.info(formatKeyValue("API Key", maskSecret(config.apiKey)));
|
|
2903
|
+
}
|
|
2904
|
+
logger.info(formatKeyValue("\u914D\u7F6E\u76EE\u5F55", configDir));
|
|
2905
|
+
logger.info(formatKeyValue("\u72B6\u6001\u76EE\u5F55", getStateDir()));
|
|
2906
|
+
logger.info(formatKeyValue("\u8FD0\u884C\u76EE\u5F55", getRuntimeDirPath()));
|
|
2907
|
+
const shouldUninstall = await promptConfirm({
|
|
2908
|
+
message: "\u786E\u8BA4\u7EE7\u7EED\u5378\u8F7D\u672C\u5730 TokenArena \u6570\u636E\uFF1F",
|
|
2909
|
+
defaultValue: false
|
|
2910
|
+
});
|
|
2911
|
+
if (!shouldUninstall) {
|
|
2912
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u5378\u8F7D\u3002", "warning"));
|
|
2473
2913
|
return;
|
|
2474
2914
|
}
|
|
2475
2915
|
deleteConfig();
|
|
2476
|
-
logger.info("
|
|
2916
|
+
logger.info(formatSection("\u6267\u884C\u7ED3\u679C"));
|
|
2917
|
+
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\u3002", "success"));
|
|
2477
2918
|
if (existsSync12(configDir)) {
|
|
2478
2919
|
try {
|
|
2479
2920
|
rmSync2(configDir, { recursive: false, force: true });
|
|
2480
|
-
logger.info("
|
|
2921
|
+
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u76EE\u5F55\u3002", "success"));
|
|
2481
2922
|
} catch {
|
|
2482
2923
|
}
|
|
2483
2924
|
}
|
|
2484
2925
|
const stateDir = getStateDir();
|
|
2485
2926
|
if (existsSync12(stateDir)) {
|
|
2486
2927
|
rmSync2(stateDir, { recursive: true, force: true });
|
|
2487
|
-
logger.info("
|
|
2928
|
+
logger.info(formatBullet("\u5DF2\u5220\u9664\u72B6\u6001\u6570\u636E\u3002", "success"));
|
|
2488
2929
|
}
|
|
2489
2930
|
const runtimeDir = getRuntimeDirPath();
|
|
2490
2931
|
if (existsSync12(runtimeDir)) {
|
|
2491
2932
|
rmSync2(runtimeDir, { recursive: true, force: true });
|
|
2492
|
-
logger.info("
|
|
2933
|
+
logger.info(formatBullet("\u5DF2\u5220\u9664\u8FD0\u884C\u65F6\u6570\u636E\u3002", "success"));
|
|
2493
2934
|
}
|
|
2494
2935
|
removeShellAlias();
|
|
2495
|
-
logger.info("\
|
|
2936
|
+
logger.info(formatSection("\u5B8C\u6210"));
|
|
2937
|
+
logger.info(formatBullet("TokenArena \u5DF2\u4ECE\u672C\u5730\u5378\u8F7D\u5B8C\u6210\u3002", "success"));
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
// src/commands/home.ts
|
|
2941
|
+
function logHomeSummary() {
|
|
2942
|
+
const config = loadConfig();
|
|
2943
|
+
const configured = Boolean(config?.apiKey);
|
|
2944
|
+
logger.info(
|
|
2945
|
+
formatHeader(
|
|
2946
|
+
"TokenArena CLI",
|
|
2947
|
+
"\u901A\u8FC7\u66F4\u53CB\u597D\u7684\u4EA4\u4E92\u5B8C\u6210\u521D\u59CB\u5316\u3001\u540C\u6B65\u3001\u914D\u7F6E\u4E0E\u6E05\u7406\u3002"
|
|
2948
|
+
)
|
|
2949
|
+
);
|
|
2950
|
+
logger.info(
|
|
2951
|
+
formatKeyValue(
|
|
2952
|
+
"\u914D\u7F6E\u72B6\u6001",
|
|
2953
|
+
configured ? formatStatusBadge("\u5DF2\u914D\u7F6E", "success") : formatStatusBadge("\u672A\u914D\u7F6E", "warning")
|
|
2954
|
+
)
|
|
2955
|
+
);
|
|
2956
|
+
if (configured && config) {
|
|
2957
|
+
logger.info(formatKeyValue("API Key", maskSecret(config.apiKey)));
|
|
2958
|
+
logger.info(
|
|
2959
|
+
formatKeyValue("API \u5730\u5740", config.apiUrl || "https://token.poco-ai.com")
|
|
2960
|
+
);
|
|
2961
|
+
} else {
|
|
2962
|
+
logger.info(formatBullet("\u5EFA\u8BAE\u5148\u8FD0\u884C\u521D\u59CB\u5316\u6D41\u7A0B\u7ED1\u5B9A API Key\u3002", "warning"));
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
async function pickHomeAction() {
|
|
2966
|
+
return promptSelect({
|
|
2967
|
+
message: "\u8BF7\u9009\u62E9\u8981\u6267\u884C\u7684\u64CD\u4F5C",
|
|
2968
|
+
choices: [
|
|
2969
|
+
{
|
|
2970
|
+
name: "\u521D\u59CB\u5316 TokenArena",
|
|
2971
|
+
value: "init",
|
|
2972
|
+
description: "\u914D\u7F6E API Key\u3001\u68C0\u6D4B\u5DE5\u5177\u5E76\u6267\u884C\u9996\u6B21\u540C\u6B65"
|
|
2973
|
+
},
|
|
2974
|
+
{
|
|
2975
|
+
name: "\u67E5\u770B\u5F53\u524D\u72B6\u6001",
|
|
2976
|
+
value: "status",
|
|
2977
|
+
description: "\u67E5\u770B\u914D\u7F6E\u3001\u5DE5\u5177\u68C0\u6D4B\u7ED3\u679C\u4E0E\u6700\u8FD1\u540C\u6B65\u72B6\u6001"
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
name: "\u7ACB\u5373\u540C\u6B65",
|
|
2981
|
+
value: "sync",
|
|
2982
|
+
description: "\u624B\u52A8\u4E0A\u4F20\u672C\u5730\u6700\u65B0 token \u4F7F\u7528\u6570\u636E"
|
|
2983
|
+
},
|
|
2984
|
+
{
|
|
2985
|
+
name: "\u7BA1\u7406\u914D\u7F6E",
|
|
2986
|
+
value: "config",
|
|
2987
|
+
description: "\u67E5\u770B\u6216\u4FEE\u6539 API Key\u3001API \u5730\u5740\u3001\u540C\u6B65\u95F4\u9694\u7B49\u914D\u7F6E"
|
|
2988
|
+
},
|
|
2989
|
+
{
|
|
2990
|
+
name: "\u542F\u52A8\u5B88\u62A4\u540C\u6B65",
|
|
2991
|
+
value: "daemon",
|
|
2992
|
+
description: "\u6301\u7EED\u540E\u53F0\u540C\u6B65\uFF0C\u9002\u5408\u957F\u671F\u8FD0\u884C"
|
|
2993
|
+
},
|
|
2994
|
+
{
|
|
2995
|
+
name: "\u5378\u8F7D\u672C\u5730\u914D\u7F6E",
|
|
2996
|
+
value: "uninstall",
|
|
2997
|
+
description: "\u5220\u9664\u672C\u5730\u914D\u7F6E\u3001\u72B6\u6001\u4E0E\u8FD0\u884C\u65F6\u6587\u4EF6"
|
|
2998
|
+
},
|
|
2999
|
+
{
|
|
3000
|
+
name: "\u67E5\u770B\u5E2E\u52A9",
|
|
3001
|
+
value: "help",
|
|
3002
|
+
description: "\u5C55\u793A\u5B8C\u6574\u547D\u4EE4\u5E2E\u52A9"
|
|
3003
|
+
},
|
|
3004
|
+
{
|
|
3005
|
+
name: "\u9000\u51FA",
|
|
3006
|
+
value: "exit",
|
|
3007
|
+
description: "\u7ED3\u675F\u5F53\u524D\u4EA4\u4E92"
|
|
3008
|
+
}
|
|
3009
|
+
]
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
async function runHome(program) {
|
|
3013
|
+
while (true) {
|
|
3014
|
+
logHomeSummary();
|
|
3015
|
+
const action = await pickHomeAction();
|
|
3016
|
+
logger.info("");
|
|
3017
|
+
switch (action) {
|
|
3018
|
+
case "init":
|
|
3019
|
+
await runInit();
|
|
3020
|
+
break;
|
|
3021
|
+
case "status":
|
|
3022
|
+
await runStatus();
|
|
3023
|
+
break;
|
|
3024
|
+
case "sync":
|
|
3025
|
+
await runSyncCommand();
|
|
3026
|
+
break;
|
|
3027
|
+
case "config":
|
|
3028
|
+
await handleConfig([]);
|
|
3029
|
+
break;
|
|
3030
|
+
case "daemon":
|
|
3031
|
+
await runDaemon();
|
|
3032
|
+
return;
|
|
3033
|
+
case "uninstall":
|
|
3034
|
+
await runUninstall();
|
|
3035
|
+
break;
|
|
3036
|
+
case "help":
|
|
3037
|
+
program.outputHelp();
|
|
3038
|
+
break;
|
|
3039
|
+
case "exit":
|
|
3040
|
+
logger.info(formatBullet("\u5DF2\u9000\u51FA\u4EA4\u4E92\u5F0F\u4E3B\u9875\u3002", "neutral"));
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
const continueAnswer = await promptConfirm({
|
|
3044
|
+
message: "\u662F\u5426\u7EE7\u7EED\u6267\u884C\u5176\u4ED6\u64CD\u4F5C\uFF1F",
|
|
3045
|
+
defaultValue: true
|
|
3046
|
+
});
|
|
3047
|
+
if (!continueAnswer) {
|
|
3048
|
+
logger.info(formatBullet("\u4E0B\u6B21\u53EF\u76F4\u63A5\u8FD0\u884C tokenarena \u7EE7\u7EED\u3002", "neutral"));
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
2496
3052
|
}
|
|
2497
3053
|
|
|
2498
3054
|
// src/infrastructure/runtime/cli-version.ts
|
|
@@ -2524,11 +3080,15 @@ var CLI_VERSION = getCliVersion();
|
|
|
2524
3080
|
function createCli() {
|
|
2525
3081
|
const program = new Command();
|
|
2526
3082
|
program.name("tokenarena").description("Track token burn across AI coding tools").version(CLI_VERSION).showHelpAfterError().showSuggestionAfterError().helpCommand("help [command]", "Display help for command");
|
|
2527
|
-
program.action(() => {
|
|
3083
|
+
program.action(async () => {
|
|
2528
3084
|
const userArgs = process.argv.slice(2).filter((a) => !a.startsWith("-"));
|
|
2529
3085
|
if (userArgs.length > 0) {
|
|
2530
3086
|
program.error(`unknown command '${userArgs[0]}'`);
|
|
2531
3087
|
}
|
|
3088
|
+
if (isInteractiveTerminal()) {
|
|
3089
|
+
await runHome(program);
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
2532
3092
|
program.help();
|
|
2533
3093
|
});
|
|
2534
3094
|
program.command("init").description("Initialize configuration with API key").option("--api-url <url>", "Custom API server URL").action(async (opts) => {
|
|
@@ -2543,9 +3103,12 @@ function createCli() {
|
|
|
2543
3103
|
program.command("status").description("Show configuration and detected tools").action(async () => {
|
|
2544
3104
|
await runStatus();
|
|
2545
3105
|
});
|
|
2546
|
-
program.command("config").description("Manage configuration").argument("
|
|
2547
|
-
|
|
2548
|
-
|
|
3106
|
+
program.command("config").description("Manage configuration").argument("[subcommand]", "get|set|show").argument("[key]", "Config key").argument("[value]", "Config value").allowUnknownOption(true).action(async (subcommand, key, value) => {
|
|
3107
|
+
await handleConfig(
|
|
3108
|
+
[subcommand, key, value].filter(
|
|
3109
|
+
(item) => typeof item === "string"
|
|
3110
|
+
)
|
|
3111
|
+
);
|
|
2549
3112
|
});
|
|
2550
3113
|
program.command("uninstall").description("Remove all local configuration and data").action(async () => {
|
|
2551
3114
|
await runUninstall();
|
|
@@ -2576,12 +3139,12 @@ function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
|
|
|
2576
3139
|
function normalizeArgv(argv) {
|
|
2577
3140
|
return argv.filter((arg, index) => index < 2 || arg !== "--");
|
|
2578
3141
|
}
|
|
2579
|
-
function run(argv = process.argv) {
|
|
3142
|
+
async function run(argv = process.argv) {
|
|
2580
3143
|
const program = createCli();
|
|
2581
|
-
program.
|
|
3144
|
+
await program.parseAsync(normalizeArgv(argv));
|
|
2582
3145
|
}
|
|
2583
3146
|
if (isMainModule()) {
|
|
2584
|
-
run();
|
|
3147
|
+
void run();
|
|
2585
3148
|
}
|
|
2586
3149
|
export {
|
|
2587
3150
|
normalizeArgv,
|