@poco-ai/tokenarena 0.2.2-beta.2 → 0.2.4
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.js +847 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3276,18 +3276,706 @@ View your dashboard at: ${apiUrl}/usage`);
|
|
|
3276
3276
|
}
|
|
3277
3277
|
|
|
3278
3278
|
// src/commands/init.ts
|
|
3279
|
-
import { execFileSync as
|
|
3280
|
-
import { existsSync as
|
|
3279
|
+
import { execFileSync as execFileSync4, spawn } from "child_process";
|
|
3280
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3281
3281
|
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
3282
|
+
import { homedir as homedir14, platform as platform4 } from "os";
|
|
3283
|
+
import { dirname as dirname3, join as join17, posix, win32 } from "path";
|
|
3284
|
+
|
|
3285
|
+
// src/infrastructure/service/index.ts
|
|
3286
|
+
import { platform as platform3 } from "os";
|
|
3287
|
+
|
|
3288
|
+
// src/infrastructure/service/linux-systemd.ts
|
|
3289
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
3290
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3282
3291
|
import { homedir as homedir12, platform } from "os";
|
|
3283
|
-
import {
|
|
3292
|
+
import { join as join15 } from "path";
|
|
3293
|
+
|
|
3294
|
+
// src/utils/command.ts
|
|
3295
|
+
import { execSync } from "child_process";
|
|
3296
|
+
function isCommandAvailable(command) {
|
|
3297
|
+
try {
|
|
3298
|
+
execSync(`command -v ${command}`, { stdio: "ignore" });
|
|
3299
|
+
return true;
|
|
3300
|
+
} catch {
|
|
3301
|
+
return false;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
// src/infrastructure/service/utils.ts
|
|
3306
|
+
var SERVICE_PATH_FALLBACKS = [
|
|
3307
|
+
"/opt/homebrew/bin",
|
|
3308
|
+
"/usr/local/bin",
|
|
3309
|
+
"/usr/bin",
|
|
3310
|
+
"/bin",
|
|
3311
|
+
"/usr/sbin",
|
|
3312
|
+
"/sbin"
|
|
3313
|
+
];
|
|
3314
|
+
var SERVICE_ENV_KEYS = [
|
|
3315
|
+
"TOKEN_ARENA_DEV",
|
|
3316
|
+
"XDG_CONFIG_HOME",
|
|
3317
|
+
"XDG_STATE_HOME",
|
|
3318
|
+
"XDG_RUNTIME_DIR"
|
|
3319
|
+
];
|
|
3320
|
+
function dedupePaths(paths) {
|
|
3321
|
+
return [...new Set(paths.filter(Boolean))];
|
|
3322
|
+
}
|
|
3323
|
+
function getManagedServiceEnvironment(env = process.env) {
|
|
3324
|
+
const pathEntries = dedupePaths([
|
|
3325
|
+
...env.PATH?.split(":") ?? [],
|
|
3326
|
+
...SERVICE_PATH_FALLBACKS
|
|
3327
|
+
]);
|
|
3328
|
+
const next = {
|
|
3329
|
+
PATH: pathEntries.join(":")
|
|
3330
|
+
};
|
|
3331
|
+
for (const key of SERVICE_ENV_KEYS) {
|
|
3332
|
+
const value = env[key];
|
|
3333
|
+
if (value) {
|
|
3334
|
+
next[key] = value;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
return next;
|
|
3338
|
+
}
|
|
3339
|
+
function resolveManagedDaemonCommand(execPath = process.execPath, argv = process.argv) {
|
|
3340
|
+
const scriptPath = argv[1];
|
|
3341
|
+
if (!scriptPath) {
|
|
3342
|
+
throw new Error("\u65E0\u6CD5\u89E3\u6790 CLI \u5165\u53E3\u8DEF\u5F84\uFF0C\u8BF7\u901A\u8FC7 tokenarena \u547D\u4EE4\u91CD\u65B0\u6267\u884C\u3002");
|
|
3343
|
+
}
|
|
3344
|
+
return {
|
|
3345
|
+
execPath,
|
|
3346
|
+
args: [scriptPath, "daemon", "--service"]
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
function escapeDoubleQuotedValue(value) {
|
|
3350
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
3351
|
+
}
|
|
3352
|
+
function escapeXml(value) {
|
|
3353
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
// src/infrastructure/service/linux-systemd.ts
|
|
3357
|
+
var SYSTEMD_SERVICE_NAME = "tokenarena";
|
|
3358
|
+
function getLinuxSystemdServiceDir(homePath = homedir12()) {
|
|
3359
|
+
return join15(homePath, ".config/systemd/user");
|
|
3360
|
+
}
|
|
3361
|
+
function getLinuxSystemdServiceFile(homePath = homedir12()) {
|
|
3362
|
+
return join15(
|
|
3363
|
+
getLinuxSystemdServiceDir(homePath),
|
|
3364
|
+
`${SYSTEMD_SERVICE_NAME}.service`
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
function buildSystemdServiceContent(options) {
|
|
3368
|
+
const envLines = Object.entries(options.environment).map(
|
|
3369
|
+
([key, value]) => `Environment="${escapeDoubleQuotedValue(key)}=${escapeDoubleQuotedValue(value)}"`
|
|
3370
|
+
).join("\n");
|
|
3371
|
+
const execArgs = [options.execPath, ...options.args].map((value) => `"${escapeDoubleQuotedValue(value)}"`).join(" ");
|
|
3372
|
+
return `[Unit]
|
|
3373
|
+
Description=TokenArena Daemon - AI Usage Tracker
|
|
3374
|
+
After=network-online.target
|
|
3375
|
+
Wants=network-online.target
|
|
3376
|
+
|
|
3377
|
+
[Service]
|
|
3378
|
+
Type=simple
|
|
3379
|
+
ExecStart=${execArgs}
|
|
3380
|
+
Restart=on-failure
|
|
3381
|
+
RestartSec=10
|
|
3382
|
+
${envLines}
|
|
3383
|
+
|
|
3384
|
+
[Install]
|
|
3385
|
+
WantedBy=default.target
|
|
3386
|
+
`;
|
|
3387
|
+
}
|
|
3388
|
+
function getSystemdSupport() {
|
|
3389
|
+
if (platform() !== "linux") {
|
|
3390
|
+
return {
|
|
3391
|
+
ok: false,
|
|
3392
|
+
reason: "systemd \u4EC5\u5728 Linux \u4E0A\u53EF\u7528\u3002"
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
if (!isCommandAvailable("systemctl")) {
|
|
3396
|
+
return {
|
|
3397
|
+
ok: false,
|
|
3398
|
+
reason: "\u672A\u68C0\u6D4B\u5230 systemctl\u3002"
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
return { ok: true };
|
|
3402
|
+
}
|
|
3403
|
+
function execSystemctl(args) {
|
|
3404
|
+
execFileSync2("systemctl", ["--user", ...args], {
|
|
3405
|
+
stdio: "inherit"
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
function ensureSystemdAvailable() {
|
|
3409
|
+
const support = getSystemdSupport();
|
|
3410
|
+
if (support.ok) {
|
|
3411
|
+
return true;
|
|
3412
|
+
}
|
|
3413
|
+
logger.info(formatBullet(`systemd \u4E0D\u53EF\u7528\u3002${support.reason}`, "warning"));
|
|
3414
|
+
return false;
|
|
3415
|
+
}
|
|
3416
|
+
function createLinuxSystemdServiceBackend() {
|
|
3417
|
+
function isInstalled() {
|
|
3418
|
+
return existsSync16(getLinuxSystemdServiceFile());
|
|
3419
|
+
}
|
|
3420
|
+
async function setup(skipPrompt = false) {
|
|
3421
|
+
if (!ensureSystemdAvailable()) {
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
const serviceDir = getLinuxSystemdServiceDir();
|
|
3425
|
+
const serviceFile = getLinuxSystemdServiceFile();
|
|
3426
|
+
logger.info(formatHeader("\u8BBE\u7F6E systemd \u670D\u52A1", "TokenArena daemon"));
|
|
3427
|
+
if (!skipPrompt) {
|
|
3428
|
+
const shouldSetup = await promptConfirm({
|
|
3429
|
+
message: "\u662F\u5426\u521B\u5EFA\u5E76\u542F\u7528 systemd \u7528\u6237\u670D\u52A1\uFF1F",
|
|
3430
|
+
defaultValue: true
|
|
3431
|
+
});
|
|
3432
|
+
if (!shouldSetup) {
|
|
3433
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u670D\u52A1\u8BBE\u7F6E\u3002"));
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
try {
|
|
3438
|
+
const command = resolveManagedDaemonCommand();
|
|
3439
|
+
const content = buildSystemdServiceContent({
|
|
3440
|
+
environment: getManagedServiceEnvironment(),
|
|
3441
|
+
execPath: command.execPath,
|
|
3442
|
+
args: command.args
|
|
3443
|
+
});
|
|
3444
|
+
mkdirSync3(serviceDir, { recursive: true });
|
|
3445
|
+
writeFileSync5(serviceFile, content, "utf-8");
|
|
3446
|
+
execSystemctl(["daemon-reload"]);
|
|
3447
|
+
execSystemctl(["enable", SYSTEMD_SERVICE_NAME]);
|
|
3448
|
+
execSystemctl(["start", SYSTEMD_SERVICE_NAME]);
|
|
3449
|
+
logger.info(formatSection("\u670D\u52A1\u5DF2\u8BBE\u7F6E"));
|
|
3450
|
+
logger.info(formatBullet(`\u670D\u52A1\u6587\u4EF6: ${serviceFile}`, "success"));
|
|
3451
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u542F\u7528\u5E76\u542F\u52A8", "success"));
|
|
3452
|
+
logger.info(
|
|
3453
|
+
formatKeyValue("\u67E5\u770B\u72B6\u6001", "systemctl --user status tokenarena")
|
|
3454
|
+
);
|
|
3455
|
+
} catch (err) {
|
|
3456
|
+
logger.error(`\u8BBE\u7F6E\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3457
|
+
throw err;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
async function start() {
|
|
3461
|
+
if (!ensureSystemdAvailable()) {
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
if (!isInstalled()) {
|
|
3465
|
+
logger.info(
|
|
3466
|
+
formatBullet(
|
|
3467
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3468
|
+
"warning"
|
|
3469
|
+
)
|
|
3470
|
+
);
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
try {
|
|
3474
|
+
execSystemctl(["start", SYSTEMD_SERVICE_NAME]);
|
|
3475
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u542F\u52A8", "success"));
|
|
3476
|
+
} catch (err) {
|
|
3477
|
+
logger.error(`\u542F\u52A8\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3478
|
+
throw err;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
async function stop() {
|
|
3482
|
+
if (!ensureSystemdAvailable()) {
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
if (!isInstalled()) {
|
|
3486
|
+
logger.info(
|
|
3487
|
+
formatBullet(
|
|
3488
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3489
|
+
"warning"
|
|
3490
|
+
)
|
|
3491
|
+
);
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
try {
|
|
3495
|
+
execSystemctl(["stop", SYSTEMD_SERVICE_NAME]);
|
|
3496
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u505C\u6B62", "success"));
|
|
3497
|
+
} catch (err) {
|
|
3498
|
+
logger.error(`\u505C\u6B62\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3499
|
+
throw err;
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
async function restart() {
|
|
3503
|
+
if (!ensureSystemdAvailable()) {
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
if (!isInstalled()) {
|
|
3507
|
+
logger.info(
|
|
3508
|
+
formatBullet(
|
|
3509
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3510
|
+
"warning"
|
|
3511
|
+
)
|
|
3512
|
+
);
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
try {
|
|
3516
|
+
execSystemctl(["restart", SYSTEMD_SERVICE_NAME]);
|
|
3517
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u91CD\u542F", "success"));
|
|
3518
|
+
} catch (err) {
|
|
3519
|
+
logger.error(`\u91CD\u542F\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3520
|
+
throw err;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async function status() {
|
|
3524
|
+
if (!ensureSystemdAvailable()) {
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
if (!isInstalled()) {
|
|
3528
|
+
logger.info(
|
|
3529
|
+
formatBullet(
|
|
3530
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3531
|
+
"warning"
|
|
3532
|
+
)
|
|
3533
|
+
);
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
try {
|
|
3537
|
+
execSystemctl(["status", SYSTEMD_SERVICE_NAME]);
|
|
3538
|
+
} catch {
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
async function uninstall(skipPrompt = false) {
|
|
3542
|
+
const serviceFile = getLinuxSystemdServiceFile();
|
|
3543
|
+
if (!existsSync16(serviceFile)) {
|
|
3544
|
+
logger.info(formatBullet("\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002", "warning"));
|
|
3545
|
+
return;
|
|
3546
|
+
}
|
|
3547
|
+
if (!skipPrompt) {
|
|
3548
|
+
const shouldUninstall = await promptConfirm({
|
|
3549
|
+
message: "\u662F\u5426\u5378\u8F7D systemd \u670D\u52A1\uFF1F",
|
|
3550
|
+
defaultValue: false
|
|
3551
|
+
});
|
|
3552
|
+
if (!shouldUninstall) {
|
|
3553
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u5378\u8F7D\u3002"));
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
const support = getSystemdSupport();
|
|
3558
|
+
if (support.ok) {
|
|
3559
|
+
try {
|
|
3560
|
+
execSystemctl(["stop", SYSTEMD_SERVICE_NAME]);
|
|
3561
|
+
} catch {
|
|
3562
|
+
}
|
|
3563
|
+
try {
|
|
3564
|
+
execSystemctl(["disable", SYSTEMD_SERVICE_NAME]);
|
|
3565
|
+
} catch {
|
|
3566
|
+
}
|
|
3567
|
+
} else {
|
|
3568
|
+
logger.info(
|
|
3569
|
+
formatBullet(
|
|
3570
|
+
`\u5F53\u524D\u65E0\u6CD5\u8BBF\u95EE systemd\uFF0C\u5C06\u53EA\u5220\u9664\u670D\u52A1\u6587\u4EF6\u3002${support.reason}`,
|
|
3571
|
+
"warning"
|
|
3572
|
+
)
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
try {
|
|
3576
|
+
rmSync2(serviceFile);
|
|
3577
|
+
if (support.ok) {
|
|
3578
|
+
execSystemctl(["daemon-reload"]);
|
|
3579
|
+
}
|
|
3580
|
+
logger.info(formatSection("\u670D\u52A1\u5DF2\u5378\u8F7D"));
|
|
3581
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u505C\u7528\u5E76\u5220\u9664", "success"));
|
|
3582
|
+
} catch (err) {
|
|
3583
|
+
logger.error(`\u5378\u8F7D\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3584
|
+
throw err;
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
return {
|
|
3588
|
+
displayName: "systemd \u7528\u6237\u670D\u52A1",
|
|
3589
|
+
canSetup: getSystemdSupport,
|
|
3590
|
+
isInstalled,
|
|
3591
|
+
getDefinitionPath: getLinuxSystemdServiceFile,
|
|
3592
|
+
getStatusHint: () => "systemctl --user status tokenarena",
|
|
3593
|
+
setup,
|
|
3594
|
+
start,
|
|
3595
|
+
stop,
|
|
3596
|
+
restart,
|
|
3597
|
+
status,
|
|
3598
|
+
uninstall
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
// src/infrastructure/service/macos-launchd.ts
|
|
3603
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3604
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
3605
|
+
import { homedir as homedir13, platform as platform2 } from "os";
|
|
3606
|
+
import { join as join16 } from "path";
|
|
3607
|
+
var MACOS_LAUNCHD_LABEL = "com.poco-ai.tokenarena";
|
|
3608
|
+
function getCurrentUid() {
|
|
3609
|
+
return typeof process.getuid === "function" ? process.getuid() : null;
|
|
3610
|
+
}
|
|
3611
|
+
function getMacosLaunchAgentDir(homePath = homedir13()) {
|
|
3612
|
+
return join16(homePath, "Library/LaunchAgents");
|
|
3613
|
+
}
|
|
3614
|
+
function getMacosLaunchAgentFile(homePath = homedir13()) {
|
|
3615
|
+
return join16(getMacosLaunchAgentDir(homePath), `${MACOS_LAUNCHD_LABEL}.plist`);
|
|
3616
|
+
}
|
|
3617
|
+
function getMacosLaunchdDomain(uid = getCurrentUid()) {
|
|
3618
|
+
if (uid == null) {
|
|
3619
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237 UID\u3002");
|
|
3620
|
+
}
|
|
3621
|
+
return `gui/${uid}`;
|
|
3622
|
+
}
|
|
3623
|
+
function getMacosLaunchdServiceTarget(uid = getCurrentUid()) {
|
|
3624
|
+
return `${getMacosLaunchdDomain(uid)}/${MACOS_LAUNCHD_LABEL}`;
|
|
3625
|
+
}
|
|
3626
|
+
function getMacosLaunchdLogPaths(stateDir = getStateDir()) {
|
|
3627
|
+
return {
|
|
3628
|
+
stdoutPath: join16(stateDir, "launchd.stdout.log"),
|
|
3629
|
+
stderrPath: join16(stateDir, "launchd.stderr.log")
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
function renderPlistArray(values) {
|
|
3633
|
+
return values.map((value) => ` <string>${escapeXml(value)}</string>`).join("\n");
|
|
3634
|
+
}
|
|
3635
|
+
function renderPlistDict(entries) {
|
|
3636
|
+
return Object.entries(entries).map(
|
|
3637
|
+
([key, value]) => ` <key>${escapeXml(key)}</key>
|
|
3638
|
+
<string>${escapeXml(value)}</string>`
|
|
3639
|
+
).join("\n");
|
|
3640
|
+
}
|
|
3641
|
+
function buildLaunchdPlist(options) {
|
|
3642
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3643
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3644
|
+
<plist version="1.0">
|
|
3645
|
+
<dict>
|
|
3646
|
+
<key>Label</key>
|
|
3647
|
+
<string>${escapeXml(options.label)}</string>
|
|
3648
|
+
<key>ProgramArguments</key>
|
|
3649
|
+
<array>
|
|
3650
|
+
${renderPlistArray(options.programArguments)}
|
|
3651
|
+
</array>
|
|
3652
|
+
<key>WorkingDirectory</key>
|
|
3653
|
+
<string>${escapeXml(options.workingDirectory)}</string>
|
|
3654
|
+
<key>EnvironmentVariables</key>
|
|
3655
|
+
<dict>
|
|
3656
|
+
${renderPlistDict(options.environment)}
|
|
3657
|
+
</dict>
|
|
3658
|
+
<key>KeepAlive</key>
|
|
3659
|
+
<dict>
|
|
3660
|
+
<key>SuccessfulExit</key>
|
|
3661
|
+
<false/>
|
|
3662
|
+
</dict>
|
|
3663
|
+
<key>ProcessType</key>
|
|
3664
|
+
<string>Background</string>
|
|
3665
|
+
<key>ThrottleInterval</key>
|
|
3666
|
+
<integer>30</integer>
|
|
3667
|
+
<key>ExitTimeOut</key>
|
|
3668
|
+
<integer>15</integer>
|
|
3669
|
+
<key>StandardOutPath</key>
|
|
3670
|
+
<string>${escapeXml(options.standardOutPath)}</string>
|
|
3671
|
+
<key>StandardErrorPath</key>
|
|
3672
|
+
<string>${escapeXml(options.standardErrorPath)}</string>
|
|
3673
|
+
</dict>
|
|
3674
|
+
</plist>
|
|
3675
|
+
`;
|
|
3676
|
+
}
|
|
3677
|
+
function getLaunchctlSupport() {
|
|
3678
|
+
if (platform2() !== "darwin") {
|
|
3679
|
+
return {
|
|
3680
|
+
ok: false,
|
|
3681
|
+
reason: "launchd \u4EC5\u5728 macOS \u4E0A\u53EF\u7528\u3002"
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
if (!isCommandAvailable("launchctl")) {
|
|
3685
|
+
return {
|
|
3686
|
+
ok: false,
|
|
3687
|
+
reason: "\u672A\u68C0\u6D4B\u5230 launchctl\u3002"
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
const uid = getCurrentUid();
|
|
3691
|
+
if (uid == null) {
|
|
3692
|
+
return {
|
|
3693
|
+
ok: false,
|
|
3694
|
+
reason: "\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237 UID\u3002"
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
try {
|
|
3698
|
+
execFileSync3("launchctl", ["print", getMacosLaunchdDomain(uid)], {
|
|
3699
|
+
stdio: "ignore"
|
|
3700
|
+
});
|
|
3701
|
+
return { ok: true };
|
|
3702
|
+
} catch {
|
|
3703
|
+
return {
|
|
3704
|
+
ok: false,
|
|
3705
|
+
reason: "\u5F53\u524D\u672A\u68C0\u6D4B\u5230\u56FE\u5F62\u5316\u767B\u5F55\u4F1A\u8BDD\uFF0C\u8BF7\u5728\u684C\u9762\u7EC8\u7AEF\u4E2D\u6267\u884C\u3002"
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
function execLaunchctl(args, inherit = true) {
|
|
3710
|
+
execFileSync3("launchctl", args, {
|
|
3711
|
+
stdio: inherit ? "inherit" : "ignore"
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3714
|
+
function isLoaded() {
|
|
3715
|
+
try {
|
|
3716
|
+
execLaunchctl(["print", getMacosLaunchdServiceTarget()], false);
|
|
3717
|
+
return true;
|
|
3718
|
+
} catch {
|
|
3719
|
+
return false;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
function ensureLaunchctlAvailable() {
|
|
3723
|
+
const support = getLaunchctlSupport();
|
|
3724
|
+
if (support.ok) {
|
|
3725
|
+
return true;
|
|
3726
|
+
}
|
|
3727
|
+
logger.info(formatBullet(`launchd \u4E0D\u53EF\u7528\u3002${support.reason}`, "warning"));
|
|
3728
|
+
return false;
|
|
3729
|
+
}
|
|
3730
|
+
function writeLaunchAgentPlist() {
|
|
3731
|
+
const plistFile = getMacosLaunchAgentFile();
|
|
3732
|
+
const { stdoutPath, stderrPath } = getMacosLaunchdLogPaths();
|
|
3733
|
+
const command = resolveManagedDaemonCommand();
|
|
3734
|
+
const plist = buildLaunchdPlist({
|
|
3735
|
+
label: MACOS_LAUNCHD_LABEL,
|
|
3736
|
+
programArguments: [command.execPath, ...command.args],
|
|
3737
|
+
environment: getManagedServiceEnvironment(),
|
|
3738
|
+
workingDirectory: homedir13(),
|
|
3739
|
+
standardOutPath: stdoutPath,
|
|
3740
|
+
standardErrorPath: stderrPath
|
|
3741
|
+
});
|
|
3742
|
+
ensureAppDirs();
|
|
3743
|
+
mkdirSync4(getMacosLaunchAgentDir(), { recursive: true });
|
|
3744
|
+
writeFileSync6(plistFile, plist, "utf-8");
|
|
3745
|
+
return plistFile;
|
|
3746
|
+
}
|
|
3747
|
+
function bootstrapLaunchAgent() {
|
|
3748
|
+
const domain = getMacosLaunchdDomain();
|
|
3749
|
+
const serviceTarget = getMacosLaunchdServiceTarget();
|
|
3750
|
+
const plistFile = getMacosLaunchAgentFile();
|
|
3751
|
+
if (isLoaded()) {
|
|
3752
|
+
try {
|
|
3753
|
+
execLaunchctl(["bootout", serviceTarget]);
|
|
3754
|
+
} catch {
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
execLaunchctl(["bootstrap", domain, plistFile]);
|
|
3758
|
+
execLaunchctl(["enable", serviceTarget]);
|
|
3759
|
+
execLaunchctl(["kickstart", "-k", serviceTarget]);
|
|
3760
|
+
}
|
|
3761
|
+
function createMacosLaunchdServiceBackend() {
|
|
3762
|
+
function isInstalled() {
|
|
3763
|
+
return existsSync17(getMacosLaunchAgentFile());
|
|
3764
|
+
}
|
|
3765
|
+
async function setup(skipPrompt = false) {
|
|
3766
|
+
if (!ensureLaunchctlAvailable()) {
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
logger.info(formatHeader("\u8BBE\u7F6E launchd \u670D\u52A1", "TokenArena daemon"));
|
|
3770
|
+
if (!skipPrompt) {
|
|
3771
|
+
const shouldSetup = await promptConfirm({
|
|
3772
|
+
message: "\u662F\u5426\u521B\u5EFA\u5E76\u542F\u7528 launchd \u7528\u6237\u670D\u52A1\uFF1F",
|
|
3773
|
+
defaultValue: true
|
|
3774
|
+
});
|
|
3775
|
+
if (!shouldSetup) {
|
|
3776
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u670D\u52A1\u8BBE\u7F6E\u3002"));
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
try {
|
|
3781
|
+
const plistFile = writeLaunchAgentPlist();
|
|
3782
|
+
bootstrapLaunchAgent();
|
|
3783
|
+
logger.info(formatSection("\u670D\u52A1\u5DF2\u8BBE\u7F6E"));
|
|
3784
|
+
logger.info(formatBullet(`\u670D\u52A1\u6587\u4EF6: ${plistFile}`, "success"));
|
|
3785
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u542F\u7528\u5E76\u542F\u52A8", "success"));
|
|
3786
|
+
logger.info(
|
|
3787
|
+
formatKeyValue(
|
|
3788
|
+
"\u67E5\u770B\u72B6\u6001",
|
|
3789
|
+
`launchctl print ${getMacosLaunchdServiceTarget()}`
|
|
3790
|
+
)
|
|
3791
|
+
);
|
|
3792
|
+
} catch (err) {
|
|
3793
|
+
logger.error(`\u8BBE\u7F6E\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3794
|
+
throw err;
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
async function start() {
|
|
3798
|
+
if (!ensureLaunchctlAvailable()) {
|
|
3799
|
+
return;
|
|
3800
|
+
}
|
|
3801
|
+
if (!isInstalled()) {
|
|
3802
|
+
logger.info(
|
|
3803
|
+
formatBullet(
|
|
3804
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3805
|
+
"warning"
|
|
3806
|
+
)
|
|
3807
|
+
);
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
try {
|
|
3811
|
+
if (!isLoaded()) {
|
|
3812
|
+
execLaunchctl([
|
|
3813
|
+
"bootstrap",
|
|
3814
|
+
getMacosLaunchdDomain(),
|
|
3815
|
+
getMacosLaunchAgentFile()
|
|
3816
|
+
]);
|
|
3817
|
+
}
|
|
3818
|
+
execLaunchctl(["enable", getMacosLaunchdServiceTarget()]);
|
|
3819
|
+
execLaunchctl(["kickstart", "-k", getMacosLaunchdServiceTarget()]);
|
|
3820
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u542F\u52A8", "success"));
|
|
3821
|
+
} catch (err) {
|
|
3822
|
+
logger.error(`\u542F\u52A8\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3823
|
+
throw err;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
async function stop() {
|
|
3827
|
+
if (!isInstalled()) {
|
|
3828
|
+
logger.info(
|
|
3829
|
+
formatBullet(
|
|
3830
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3831
|
+
"warning"
|
|
3832
|
+
)
|
|
3833
|
+
);
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
if (!ensureLaunchctlAvailable()) {
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
if (!isLoaded()) {
|
|
3840
|
+
logger.info(formatBullet("\u670D\u52A1\u5F53\u524D\u672A\u8FD0\u884C\u3002", "warning"));
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3843
|
+
try {
|
|
3844
|
+
execLaunchctl(["bootout", getMacosLaunchdServiceTarget()]);
|
|
3845
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u505C\u6B62", "success"));
|
|
3846
|
+
} catch (err) {
|
|
3847
|
+
logger.error(`\u505C\u6B62\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3848
|
+
throw err;
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
async function restart() {
|
|
3852
|
+
if (!ensureLaunchctlAvailable()) {
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
if (!isInstalled()) {
|
|
3856
|
+
logger.info(
|
|
3857
|
+
formatBullet(
|
|
3858
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3859
|
+
"warning"
|
|
3860
|
+
)
|
|
3861
|
+
);
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
try {
|
|
3865
|
+
if (isLoaded()) {
|
|
3866
|
+
execLaunchctl(["kickstart", "-k", getMacosLaunchdServiceTarget()]);
|
|
3867
|
+
} else {
|
|
3868
|
+
execLaunchctl([
|
|
3869
|
+
"bootstrap",
|
|
3870
|
+
getMacosLaunchdDomain(),
|
|
3871
|
+
getMacosLaunchAgentFile()
|
|
3872
|
+
]);
|
|
3873
|
+
execLaunchctl(["enable", getMacosLaunchdServiceTarget()]);
|
|
3874
|
+
execLaunchctl(["kickstart", "-k", getMacosLaunchdServiceTarget()]);
|
|
3875
|
+
}
|
|
3876
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u91CD\u542F", "success"));
|
|
3877
|
+
} catch (err) {
|
|
3878
|
+
logger.error(`\u91CD\u542F\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3879
|
+
throw err;
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
async function status() {
|
|
3883
|
+
if (!isInstalled()) {
|
|
3884
|
+
logger.info(
|
|
3885
|
+
formatBullet(
|
|
3886
|
+
"\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u8FD0\u884C 'tokenarena service setup'\u3002",
|
|
3887
|
+
"warning"
|
|
3888
|
+
)
|
|
3889
|
+
);
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3892
|
+
if (!ensureLaunchctlAvailable()) {
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3895
|
+
try {
|
|
3896
|
+
execLaunchctl(["print", getMacosLaunchdServiceTarget()]);
|
|
3897
|
+
} catch {
|
|
3898
|
+
logger.info(formatBullet("\u670D\u52A1\u5F53\u524D\u672A\u52A0\u8F7D\u3002", "warning"));
|
|
3899
|
+
logger.info(formatKeyValue("\u670D\u52A1\u6587\u4EF6", getMacosLaunchAgentFile()));
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
async function uninstall(skipPrompt = false) {
|
|
3903
|
+
const plistFile = getMacosLaunchAgentFile();
|
|
3904
|
+
if (!existsSync17(plistFile)) {
|
|
3905
|
+
logger.info(formatBullet("\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002", "warning"));
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
if (!skipPrompt) {
|
|
3909
|
+
const shouldUninstall = await promptConfirm({
|
|
3910
|
+
message: "\u662F\u5426\u5378\u8F7D launchd \u670D\u52A1\uFF1F",
|
|
3911
|
+
defaultValue: false
|
|
3912
|
+
});
|
|
3913
|
+
if (!shouldUninstall) {
|
|
3914
|
+
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u5378\u8F7D\u3002"));
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
const support = getLaunchctlSupport();
|
|
3919
|
+
if (support.ok && isLoaded()) {
|
|
3920
|
+
try {
|
|
3921
|
+
execLaunchctl(["bootout", getMacosLaunchdServiceTarget()]);
|
|
3922
|
+
} catch {
|
|
3923
|
+
}
|
|
3924
|
+
} else if (!support.ok) {
|
|
3925
|
+
logger.info(
|
|
3926
|
+
formatBullet(
|
|
3927
|
+
`\u5F53\u524D\u65E0\u6CD5\u8BBF\u95EE launchd\uFF0C\u4F1A\u76F4\u63A5\u5220\u9664\u672C\u5730 plist \u6587\u4EF6\u3002${support.reason}`,
|
|
3928
|
+
"warning"
|
|
3929
|
+
)
|
|
3930
|
+
);
|
|
3931
|
+
}
|
|
3932
|
+
try {
|
|
3933
|
+
const { stdoutPath, stderrPath } = getMacosLaunchdLogPaths();
|
|
3934
|
+
rmSync3(plistFile);
|
|
3935
|
+
rmSync3(stdoutPath, { force: true });
|
|
3936
|
+
rmSync3(stderrPath, { force: true });
|
|
3937
|
+
logger.info(formatSection("\u670D\u52A1\u5DF2\u5378\u8F7D"));
|
|
3938
|
+
logger.info(formatBullet("\u670D\u52A1\u5DF2\u505C\u7528\u5E76\u5220\u9664", "success"));
|
|
3939
|
+
} catch (err) {
|
|
3940
|
+
logger.error(`\u5378\u8F7D\u670D\u52A1\u5931\u8D25: ${err.message}`);
|
|
3941
|
+
throw err;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
return {
|
|
3945
|
+
displayName: "launchd \u7528\u6237\u670D\u52A1",
|
|
3946
|
+
canSetup: getLaunchctlSupport,
|
|
3947
|
+
isInstalled,
|
|
3948
|
+
getDefinitionPath: getMacosLaunchAgentFile,
|
|
3949
|
+
getStatusHint: () => `launchctl print ${getMacosLaunchdServiceTarget()}`,
|
|
3950
|
+
setup,
|
|
3951
|
+
start,
|
|
3952
|
+
stop,
|
|
3953
|
+
restart,
|
|
3954
|
+
status,
|
|
3955
|
+
uninstall
|
|
3956
|
+
};
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
// src/infrastructure/service/index.ts
|
|
3960
|
+
function getServiceBackend(currentPlatform = platform3()) {
|
|
3961
|
+
switch (currentPlatform) {
|
|
3962
|
+
case "linux":
|
|
3963
|
+
return createLinuxSystemdServiceBackend();
|
|
3964
|
+
case "darwin":
|
|
3965
|
+
return createMacosLaunchdServiceBackend();
|
|
3966
|
+
default:
|
|
3967
|
+
return null;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
// src/commands/init.ts
|
|
3284
3972
|
function joinForPlatform(currentPlatform, ...parts) {
|
|
3285
3973
|
return currentPlatform === "win32" ? win32.join(...parts) : posix.join(...parts);
|
|
3286
3974
|
}
|
|
3287
3975
|
function basenameLikeShell(input2) {
|
|
3288
3976
|
return input2.split(/[\\/]+/).pop()?.replace(/\.exe$/i, "") ?? input2;
|
|
3289
3977
|
}
|
|
3290
|
-
function getBrowserLaunchCommand(url, currentPlatform =
|
|
3978
|
+
function getBrowserLaunchCommand(url, currentPlatform = platform4()) {
|
|
3291
3979
|
switch (currentPlatform) {
|
|
3292
3980
|
case "darwin":
|
|
3293
3981
|
return {
|
|
@@ -3324,11 +4012,11 @@ function resolvePowerShellProfilePath() {
|
|
|
3324
4012
|
const systemRoot = process.env.SYSTEMROOT || "C:\\Windows";
|
|
3325
4013
|
const candidates = [
|
|
3326
4014
|
"pwsh.exe",
|
|
3327
|
-
|
|
4015
|
+
join17(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
3328
4016
|
];
|
|
3329
4017
|
for (const command of candidates) {
|
|
3330
4018
|
try {
|
|
3331
|
-
const output =
|
|
4019
|
+
const output = execFileSync4(
|
|
3332
4020
|
command,
|
|
3333
4021
|
[
|
|
3334
4022
|
"-NoLogo",
|
|
@@ -3351,10 +4039,10 @@ function resolvePowerShellProfilePath() {
|
|
|
3351
4039
|
return null;
|
|
3352
4040
|
}
|
|
3353
4041
|
function resolveShellAliasSetup(options = {}) {
|
|
3354
|
-
const currentPlatform = options.currentPlatform ??
|
|
4042
|
+
const currentPlatform = options.currentPlatform ?? platform4();
|
|
3355
4043
|
const env = options.env ?? process.env;
|
|
3356
|
-
const homeDir = options.homeDir ??
|
|
3357
|
-
const pathExists = options.exists ??
|
|
4044
|
+
const homeDir = options.homeDir ?? homedir14();
|
|
4045
|
+
const pathExists = options.exists ?? existsSync18;
|
|
3358
4046
|
const shellFromEnv = env.SHELL ? basenameLikeShell(env.SHELL).toLowerCase() : "";
|
|
3359
4047
|
const shellName = shellFromEnv || (currentPlatform === "win32" ? "powershell" : "");
|
|
3360
4048
|
const aliasName = "ta";
|
|
@@ -3510,6 +4198,30 @@ async function runInit(opts = {}) {
|
|
|
3510
4198
|
logger.info(formatBullet("TokenArena \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002", "success"));
|
|
3511
4199
|
logger.info(formatKeyValue("\u63A7\u5236\u53F0", `${apiUrl}/usage`));
|
|
3512
4200
|
await setupShellAlias();
|
|
4201
|
+
await setupBackgroundService();
|
|
4202
|
+
}
|
|
4203
|
+
async function setupBackgroundService() {
|
|
4204
|
+
const backend = getServiceBackend();
|
|
4205
|
+
if (!backend) {
|
|
4206
|
+
return;
|
|
4207
|
+
}
|
|
4208
|
+
const support = backend.canSetup();
|
|
4209
|
+
if (!support.ok) {
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
4212
|
+
const shouldSetup = await promptConfirm({
|
|
4213
|
+
message: `\u662F\u5426\u8BBE\u7F6E ${backend.displayName} \u4EE5\u81EA\u52A8\u8FD0\u884C daemon\uFF1F`,
|
|
4214
|
+
defaultValue: true
|
|
4215
|
+
});
|
|
4216
|
+
if (!shouldSetup) {
|
|
4217
|
+
logger.info(formatBullet("\u5DF2\u8DF3\u8FC7\u540E\u53F0\u670D\u52A1\u8BBE\u7F6E\u3002"));
|
|
4218
|
+
return;
|
|
4219
|
+
}
|
|
4220
|
+
try {
|
|
4221
|
+
await backend.setup(true);
|
|
4222
|
+
} catch (err) {
|
|
4223
|
+
logger.warn(`${backend.displayName} \u8BBE\u7F6E\u5931\u8D25: ${err.message}`);
|
|
4224
|
+
}
|
|
3513
4225
|
}
|
|
3514
4226
|
async function setupShellAlias() {
|
|
3515
4227
|
const setup = resolveShellAliasSetup();
|
|
@@ -3527,7 +4239,7 @@ async function setupShellAlias() {
|
|
|
3527
4239
|
try {
|
|
3528
4240
|
await mkdir(dirname3(setup.configFile), { recursive: true });
|
|
3529
4241
|
let existingContent = "";
|
|
3530
|
-
if (
|
|
4242
|
+
if (existsSync18(setup.configFile)) {
|
|
3531
4243
|
existingContent = await readFile(setup.configFile, "utf-8");
|
|
3532
4244
|
}
|
|
3533
4245
|
const normalizedContent = existingContent.toLowerCase();
|
|
@@ -3571,6 +4283,9 @@ function log(msg) {
|
|
|
3571
4283
|
function sleep(ms) {
|
|
3572
4284
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3573
4285
|
}
|
|
4286
|
+
function getDaemonExitCode(opts = {}) {
|
|
4287
|
+
return opts.service ? 0 : 1;
|
|
4288
|
+
}
|
|
3574
4289
|
async function runDaemon(opts = {}) {
|
|
3575
4290
|
const config = loadConfig();
|
|
3576
4291
|
if (!config?.apiKey) {
|
|
@@ -3592,12 +4307,14 @@ async function runDaemon(opts = {}) {
|
|
|
3592
4307
|
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u542F\u52A8 daemon\u3002", "warning"));
|
|
3593
4308
|
return;
|
|
3594
4309
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
4310
|
+
const message = opts.service ? "Not configured. Exiting service mode." : "Not configured. Run `tokenarena init` first.";
|
|
4311
|
+
logger.error(message);
|
|
4312
|
+
process.exit(getDaemonExitCode(opts));
|
|
3597
4313
|
}
|
|
3598
4314
|
const interval = opts.interval || config.syncInterval || DEFAULT_INTERVAL;
|
|
3599
4315
|
const intervalMin = Math.round(interval / 6e4);
|
|
3600
|
-
|
|
4316
|
+
const stopHint = opts.service ? "service mode" : "Ctrl+C to stop";
|
|
4317
|
+
log(`Daemon started (sync every ${intervalMin}m, ${stopHint})`);
|
|
3601
4318
|
while (true) {
|
|
3602
4319
|
try {
|
|
3603
4320
|
await runSync(config, {
|
|
@@ -3607,8 +4324,9 @@ async function runDaemon(opts = {}) {
|
|
|
3607
4324
|
});
|
|
3608
4325
|
} catch (err) {
|
|
3609
4326
|
if (err.message === "UNAUTHORIZED") {
|
|
3610
|
-
|
|
3611
|
-
|
|
4327
|
+
const message = opts.service ? "API key invalid. Exiting service mode." : "API key invalid. Exiting.";
|
|
4328
|
+
log(message);
|
|
4329
|
+
process.exit(getDaemonExitCode(opts));
|
|
3612
4330
|
}
|
|
3613
4331
|
log(`Sync error: ${err.message}`);
|
|
3614
4332
|
}
|
|
@@ -3720,8 +4438,8 @@ async function runSyncCommand(opts = {}) {
|
|
|
3720
4438
|
}
|
|
3721
4439
|
|
|
3722
4440
|
// src/commands/uninstall.ts
|
|
3723
|
-
import { existsSync as
|
|
3724
|
-
import { homedir as
|
|
4441
|
+
import { existsSync as existsSync19, readFileSync as readFileSync10, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
4442
|
+
import { homedir as homedir15, platform as platform5 } from "os";
|
|
3725
4443
|
function removeShellAlias() {
|
|
3726
4444
|
const shell = process.env.SHELL;
|
|
3727
4445
|
if (!shell) return;
|
|
@@ -3730,22 +4448,22 @@ function removeShellAlias() {
|
|
|
3730
4448
|
let configFile;
|
|
3731
4449
|
switch (shellName) {
|
|
3732
4450
|
case "zsh":
|
|
3733
|
-
configFile = `${
|
|
4451
|
+
configFile = `${homedir15()}/.zshrc`;
|
|
3734
4452
|
break;
|
|
3735
4453
|
case "bash":
|
|
3736
|
-
if (
|
|
3737
|
-
configFile = `${
|
|
4454
|
+
if (platform5() === "darwin" && existsSync19(`${homedir15()}/.bash_profile`)) {
|
|
4455
|
+
configFile = `${homedir15()}/.bash_profile`;
|
|
3738
4456
|
} else {
|
|
3739
|
-
configFile = `${
|
|
4457
|
+
configFile = `${homedir15()}/.bashrc`;
|
|
3740
4458
|
}
|
|
3741
4459
|
break;
|
|
3742
4460
|
case "fish":
|
|
3743
|
-
configFile = `${
|
|
4461
|
+
configFile = `${homedir15()}/.config/fish/config.fish`;
|
|
3744
4462
|
break;
|
|
3745
4463
|
default:
|
|
3746
4464
|
return;
|
|
3747
4465
|
}
|
|
3748
|
-
if (!
|
|
4466
|
+
if (!existsSync19(configFile)) return;
|
|
3749
4467
|
try {
|
|
3750
4468
|
let content = readFileSync10(configFile, "utf-8");
|
|
3751
4469
|
const aliasPatterns = [
|
|
@@ -3769,7 +4487,7 @@ function removeShellAlias() {
|
|
|
3769
4487
|
content = next;
|
|
3770
4488
|
}
|
|
3771
4489
|
}
|
|
3772
|
-
|
|
4490
|
+
writeFileSync7(configFile, content, "utf-8");
|
|
3773
4491
|
logger.info(`Removed shell alias from ${configFile}`);
|
|
3774
4492
|
} catch (err) {
|
|
3775
4493
|
logger.warn(
|
|
@@ -3780,7 +4498,12 @@ function removeShellAlias() {
|
|
|
3780
4498
|
async function runUninstall() {
|
|
3781
4499
|
const configPath = getConfigPath();
|
|
3782
4500
|
const configDir = getConfigDir();
|
|
3783
|
-
|
|
4501
|
+
const stateDir = getStateDir();
|
|
4502
|
+
const runtimeDir = getRuntimeDirPath();
|
|
4503
|
+
const serviceBackend = getServiceBackend();
|
|
4504
|
+
const hasInstalledService = serviceBackend?.isInstalled() ?? false;
|
|
4505
|
+
const hasLocalArtifacts = existsSync19(configPath) || existsSync19(configDir) || existsSync19(stateDir) || existsSync19(runtimeDir) || hasInstalledService;
|
|
4506
|
+
if (!hasLocalArtifacts) {
|
|
3784
4507
|
logger.info(formatHeader("\u5378\u8F7D TokenArena"));
|
|
3785
4508
|
logger.info(formatBullet("\u672A\u53D1\u73B0\u672C\u5730\u914D\u7F6E\uFF0C\u65E0\u9700\u5378\u8F7D\u3002"));
|
|
3786
4509
|
return;
|
|
@@ -3796,8 +4519,11 @@ async function runUninstall() {
|
|
|
3796
4519
|
logger.info(formatKeyValue("API Key", maskSecret(config.apiKey)));
|
|
3797
4520
|
}
|
|
3798
4521
|
logger.info(formatKeyValue("\u914D\u7F6E\u76EE\u5F55", configDir));
|
|
3799
|
-
logger.info(formatKeyValue("\u72B6\u6001\u76EE\u5F55",
|
|
3800
|
-
logger.info(formatKeyValue("\u8FD0\u884C\u76EE\u5F55",
|
|
4522
|
+
logger.info(formatKeyValue("\u72B6\u6001\u76EE\u5F55", stateDir));
|
|
4523
|
+
logger.info(formatKeyValue("\u8FD0\u884C\u76EE\u5F55", runtimeDir));
|
|
4524
|
+
if (hasInstalledService && serviceBackend) {
|
|
4525
|
+
logger.info(formatKeyValue("\u540E\u53F0\u670D\u52A1", serviceBackend.getDefinitionPath()));
|
|
4526
|
+
}
|
|
3801
4527
|
const shouldUninstall = await promptConfirm({
|
|
3802
4528
|
message: "\u786E\u8BA4\u7EE7\u7EED\u5378\u8F7D\u672C\u5730 TokenArena \u6570\u636E\uFF1F",
|
|
3803
4529
|
defaultValue: false
|
|
@@ -3806,24 +4532,37 @@ async function runUninstall() {
|
|
|
3806
4532
|
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u5378\u8F7D\u3002", "warning"));
|
|
3807
4533
|
return;
|
|
3808
4534
|
}
|
|
3809
|
-
|
|
4535
|
+
if (hasInstalledService && serviceBackend) {
|
|
4536
|
+
const shouldRemoveService = await promptConfirm({
|
|
4537
|
+
message: `\u68C0\u6D4B\u5230\u5DF2\u5B89\u88C5 ${serviceBackend.displayName}\uFF0C\u662F\u5426\u4E00\u5E76\u5378\u8F7D\uFF1F`,
|
|
4538
|
+
defaultValue: true
|
|
4539
|
+
});
|
|
4540
|
+
if (shouldRemoveService) {
|
|
4541
|
+
try {
|
|
4542
|
+
await serviceBackend.uninstall(true);
|
|
4543
|
+
} catch (err) {
|
|
4544
|
+
logger.warn(`\u540E\u53F0\u670D\u52A1\u5378\u8F7D\u5931\u8D25: ${err.message}`);
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
3810
4548
|
logger.info(formatSection("\u6267\u884C\u7ED3\u679C"));
|
|
3811
|
-
|
|
3812
|
-
|
|
4549
|
+
if (existsSync19(configPath)) {
|
|
4550
|
+
deleteConfig();
|
|
4551
|
+
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\u3002", "success"));
|
|
4552
|
+
}
|
|
4553
|
+
if (existsSync19(configDir)) {
|
|
3813
4554
|
try {
|
|
3814
|
-
|
|
4555
|
+
rmSync4(configDir, { recursive: false, force: true });
|
|
3815
4556
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u76EE\u5F55\u3002", "success"));
|
|
3816
4557
|
} catch {
|
|
3817
4558
|
}
|
|
3818
4559
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
rmSync2(stateDir, { recursive: true, force: true });
|
|
4560
|
+
if (existsSync19(stateDir)) {
|
|
4561
|
+
rmSync4(stateDir, { recursive: true, force: true });
|
|
3822
4562
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u72B6\u6001\u6570\u636E\u3002", "success"));
|
|
3823
4563
|
}
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
rmSync2(runtimeDir, { recursive: true, force: true });
|
|
4564
|
+
if (existsSync19(runtimeDir)) {
|
|
4565
|
+
rmSync4(runtimeDir, { recursive: true, force: true });
|
|
3827
4566
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u8FD0\u884C\u65F6\u6570\u636E\u3002", "success"));
|
|
3828
4567
|
}
|
|
3829
4568
|
removeShellAlias();
|
|
@@ -3945,9 +4684,70 @@ async function runHome(program) {
|
|
|
3945
4684
|
}
|
|
3946
4685
|
}
|
|
3947
4686
|
|
|
4687
|
+
// src/commands/service.ts
|
|
4688
|
+
function printUsage(backendName) {
|
|
4689
|
+
logger.info(formatHeader("TokenArena \u540E\u53F0\u670D\u52A1\u7BA1\u7406"));
|
|
4690
|
+
if (backendName) {
|
|
4691
|
+
logger.info(formatKeyValue("\u5F53\u524D\u5B9E\u73B0", backendName));
|
|
4692
|
+
}
|
|
4693
|
+
logger.info(formatSection("\u53EF\u7528\u64CD\u4F5C"));
|
|
4694
|
+
logger.info(formatBullet("setup - \u521B\u5EFA\u5E76\u542F\u7528\u670D\u52A1"));
|
|
4695
|
+
logger.info(formatBullet("start - \u542F\u52A8\u670D\u52A1"));
|
|
4696
|
+
logger.info(formatBullet("stop - \u505C\u6B62\u670D\u52A1"));
|
|
4697
|
+
logger.info(formatBullet("restart - \u91CD\u542F\u670D\u52A1"));
|
|
4698
|
+
logger.info(formatBullet("status - \u67E5\u770B\u670D\u52A1\u72B6\u6001"));
|
|
4699
|
+
logger.info(formatBullet("uninstall - \u5378\u8F7D\u670D\u52A1"));
|
|
4700
|
+
}
|
|
4701
|
+
async function runServiceCommand(opts) {
|
|
4702
|
+
const backend = getServiceBackend();
|
|
4703
|
+
if (!backend) {
|
|
4704
|
+
logger.info(
|
|
4705
|
+
formatBullet(
|
|
4706
|
+
"\u540E\u53F0\u670D\u52A1\u4EC5\u5728 Linux(systemd) \u548C macOS(launchd) \u4E0A\u652F\u6301\u3002",
|
|
4707
|
+
"warning"
|
|
4708
|
+
)
|
|
4709
|
+
);
|
|
4710
|
+
return;
|
|
4711
|
+
}
|
|
4712
|
+
const action = opts.action?.toLowerCase();
|
|
4713
|
+
if (!action) {
|
|
4714
|
+
printUsage(backend.displayName);
|
|
4715
|
+
const support = backend.canSetup();
|
|
4716
|
+
if (!support.ok && support.reason) {
|
|
4717
|
+
logger.info(
|
|
4718
|
+
formatBullet(`\u5F53\u524D\u73AF\u5883\u6682\u4E0D\u53EF\u7BA1\u7406\u670D\u52A1\u3002${support.reason}`, "warning")
|
|
4719
|
+
);
|
|
4720
|
+
}
|
|
4721
|
+
return;
|
|
4722
|
+
}
|
|
4723
|
+
switch (action) {
|
|
4724
|
+
case "setup":
|
|
4725
|
+
await backend.setup(opts.skipPrompt);
|
|
4726
|
+
break;
|
|
4727
|
+
case "start":
|
|
4728
|
+
await backend.start();
|
|
4729
|
+
break;
|
|
4730
|
+
case "stop":
|
|
4731
|
+
await backend.stop();
|
|
4732
|
+
break;
|
|
4733
|
+
case "restart":
|
|
4734
|
+
await backend.restart();
|
|
4735
|
+
break;
|
|
4736
|
+
case "status":
|
|
4737
|
+
await backend.status();
|
|
4738
|
+
break;
|
|
4739
|
+
case "uninstall":
|
|
4740
|
+
await backend.uninstall(opts.skipPrompt);
|
|
4741
|
+
break;
|
|
4742
|
+
default:
|
|
4743
|
+
logger.error(`\u672A\u77E5\u64CD\u4F5C: ${action}`);
|
|
4744
|
+
process.exit(1);
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
|
|
3948
4748
|
// src/infrastructure/runtime/cli-version.ts
|
|
3949
4749
|
import { readFileSync as readFileSync11 } from "fs";
|
|
3950
|
-
import { dirname as dirname4, join as
|
|
4750
|
+
import { dirname as dirname4, join as join18 } from "path";
|
|
3951
4751
|
import { fileURLToPath } from "url";
|
|
3952
4752
|
var FALLBACK_VERSION = "0.0.0";
|
|
3953
4753
|
var cachedVersion;
|
|
@@ -3955,7 +4755,7 @@ function getCliVersion(metaUrl = import.meta.url) {
|
|
|
3955
4755
|
if (cachedVersion) {
|
|
3956
4756
|
return cachedVersion;
|
|
3957
4757
|
}
|
|
3958
|
-
const packageJsonPath =
|
|
4758
|
+
const packageJsonPath = join18(
|
|
3959
4759
|
dirname4(fileURLToPath(metaUrl)),
|
|
3960
4760
|
"..",
|
|
3961
4761
|
"package.json"
|
|
@@ -3991,9 +4791,14 @@ function createCli() {
|
|
|
3991
4791
|
program.command("sync").description("Manually sync usage data to server").addOption(new Option("--quiet").hideHelp()).action(async (opts) => {
|
|
3992
4792
|
await runSyncCommand(opts);
|
|
3993
4793
|
});
|
|
3994
|
-
program.command("daemon").description("Run continuous sync (every 5 minutes by default)").option("--interval <ms>", "Sync interval in milliseconds", parseInt).action(async (opts) => {
|
|
4794
|
+
program.command("daemon").description("Run continuous sync (every 5 minutes by default)").option("--interval <ms>", "Sync interval in milliseconds", parseInt).addOption(new Option("--service").hideHelp()).action(async (opts) => {
|
|
3995
4795
|
await runDaemon(opts);
|
|
3996
4796
|
});
|
|
4797
|
+
program.command("service [action]").description(
|
|
4798
|
+
"Manage background service (setup|start|stop|restart|status|uninstall)"
|
|
4799
|
+
).action(async (action) => {
|
|
4800
|
+
await runServiceCommand({ action });
|
|
4801
|
+
});
|
|
3997
4802
|
program.command("status").description("Show configuration and detected tools").action(async () => {
|
|
3998
4803
|
await runStatus();
|
|
3999
4804
|
});
|
|
@@ -4011,7 +4816,7 @@ function createCli() {
|
|
|
4011
4816
|
}
|
|
4012
4817
|
|
|
4013
4818
|
// src/infrastructure/runtime/main-module.ts
|
|
4014
|
-
import { existsSync as
|
|
4819
|
+
import { existsSync as existsSync20, realpathSync } from "fs";
|
|
4015
4820
|
import { resolve } from "path";
|
|
4016
4821
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4017
4822
|
function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
|
|
@@ -4022,7 +4827,7 @@ function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
|
|
|
4022
4827
|
try {
|
|
4023
4828
|
return realpathSync(argvEntry) === realpathSync(currentModulePath);
|
|
4024
4829
|
} catch {
|
|
4025
|
-
if (!
|
|
4830
|
+
if (!existsSync20(argvEntry)) {
|
|
4026
4831
|
return false;
|
|
4027
4832
|
}
|
|
4028
4833
|
return resolve(argvEntry) === resolve(currentModulePath);
|