@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 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 execFileSync2, spawn } from "child_process";
3280
- import { existsSync as existsSync16 } from "fs";
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 { dirname as dirname3, join as join15, posix, win32 } from "path";
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
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 = platform()) {
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
- join15(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
4015
+ join17(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
3328
4016
  ];
3329
4017
  for (const command of candidates) {
3330
4018
  try {
3331
- const output = execFileSync2(
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 ?? platform();
4042
+ const currentPlatform = options.currentPlatform ?? platform4();
3355
4043
  const env = options.env ?? process.env;
3356
- const homeDir = options.homeDir ?? homedir12();
3357
- const pathExists = options.exists ?? existsSync16;
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 (existsSync16(setup.configFile)) {
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
- logger.error("Not configured. Run `tokenarena init` first.");
3596
- process.exit(1);
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
- log(`Daemon started (sync every ${intervalMin}m, Ctrl+C to stop)`);
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
- log("API key invalid. Exiting.");
3611
- process.exit(1);
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 existsSync17, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
3724
- import { homedir as homedir13, platform as platform2 } from "os";
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 = `${homedir13()}/.zshrc`;
4451
+ configFile = `${homedir15()}/.zshrc`;
3734
4452
  break;
3735
4453
  case "bash":
3736
- if (platform2() === "darwin" && existsSync17(`${homedir13()}/.bash_profile`)) {
3737
- configFile = `${homedir13()}/.bash_profile`;
4454
+ if (platform5() === "darwin" && existsSync19(`${homedir15()}/.bash_profile`)) {
4455
+ configFile = `${homedir15()}/.bash_profile`;
3738
4456
  } else {
3739
- configFile = `${homedir13()}/.bashrc`;
4457
+ configFile = `${homedir15()}/.bashrc`;
3740
4458
  }
3741
4459
  break;
3742
4460
  case "fish":
3743
- configFile = `${homedir13()}/.config/fish/config.fish`;
4461
+ configFile = `${homedir15()}/.config/fish/config.fish`;
3744
4462
  break;
3745
4463
  default:
3746
4464
  return;
3747
4465
  }
3748
- if (!existsSync17(configFile)) return;
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
- writeFileSync5(configFile, content, "utf-8");
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
- if (!existsSync17(configPath)) {
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", getStateDir()));
3800
- logger.info(formatKeyValue("\u8FD0\u884C\u76EE\u5F55", getRuntimeDirPath()));
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
- deleteConfig();
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
- logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\u3002", "success"));
3812
- if (existsSync17(configDir)) {
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
- rmSync2(configDir, { recursive: false, force: true });
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
- const stateDir = getStateDir();
3820
- if (existsSync17(stateDir)) {
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
- const runtimeDir = getRuntimeDirPath();
3825
- if (existsSync17(runtimeDir)) {
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 join16 } from "path";
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 = join16(
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 existsSync18, realpathSync } from "fs";
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 (!existsSync18(argvEntry)) {
4830
+ if (!existsSync20(argvEntry)) {
4026
4831
  return false;
4027
4832
  }
4028
4833
  return resolve(argvEntry) === resolve(currentModulePath);