@poco-ai/tokenarena 0.2.3 → 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 +1004 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-XJKRJ3K2.js +0 -413
- package/dist/chunk-XJKRJ3K2.js.map +0 -1
- package/dist/service-4U7K4DKW.js +0 -8
- package/dist/service-4U7K4DKW.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
formatBullet,
|
|
4
|
-
formatHeader,
|
|
5
|
-
formatKeyValue,
|
|
6
|
-
formatMutedPath,
|
|
7
|
-
formatSection,
|
|
8
|
-
formatStatusBadge,
|
|
9
|
-
isCommandAvailable,
|
|
10
|
-
isInteractiveTerminal,
|
|
11
|
-
logger,
|
|
12
|
-
maskSecret,
|
|
13
|
-
promptConfirm,
|
|
14
|
-
promptPassword,
|
|
15
|
-
promptSelect,
|
|
16
|
-
promptText,
|
|
17
|
-
runInstallService
|
|
18
|
-
} from "./chunk-XJKRJ3K2.js";
|
|
19
2
|
|
|
20
3
|
// src/parsers/claude-code.ts
|
|
21
4
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -1830,6 +1813,150 @@ function getDefaultApiUrl() {
|
|
|
1830
1813
|
return process.env.TOKEN_ARENA_API_URL || DEFAULT_API_URL;
|
|
1831
1814
|
}
|
|
1832
1815
|
|
|
1816
|
+
// src/infrastructure/ui/format.ts
|
|
1817
|
+
var hasColor = Boolean(process.stdout.isTTY && process.env.NO_COLOR !== "1");
|
|
1818
|
+
function withCode(code, value) {
|
|
1819
|
+
if (!hasColor) return value;
|
|
1820
|
+
return `\x1B[${code}m${value}\x1B[0m`;
|
|
1821
|
+
}
|
|
1822
|
+
function bold(value) {
|
|
1823
|
+
return withCode("1", value);
|
|
1824
|
+
}
|
|
1825
|
+
function dim(value) {
|
|
1826
|
+
return withCode("2", value);
|
|
1827
|
+
}
|
|
1828
|
+
function cyan(value) {
|
|
1829
|
+
return withCode("36", value);
|
|
1830
|
+
}
|
|
1831
|
+
function green(value) {
|
|
1832
|
+
return withCode("32", value);
|
|
1833
|
+
}
|
|
1834
|
+
function yellow(value) {
|
|
1835
|
+
return withCode("33", value);
|
|
1836
|
+
}
|
|
1837
|
+
function red(value) {
|
|
1838
|
+
return withCode("31", value);
|
|
1839
|
+
}
|
|
1840
|
+
function magenta(value) {
|
|
1841
|
+
return withCode("35", value);
|
|
1842
|
+
}
|
|
1843
|
+
function formatHeader(title, subtitle) {
|
|
1844
|
+
const lines = [`${cyan("\u25C8")} ${bold(title)}`];
|
|
1845
|
+
if (subtitle) {
|
|
1846
|
+
lines.push(dim(subtitle));
|
|
1847
|
+
}
|
|
1848
|
+
return `
|
|
1849
|
+
${lines.join("\n")}`;
|
|
1850
|
+
}
|
|
1851
|
+
function formatSection(title) {
|
|
1852
|
+
return `
|
|
1853
|
+
${bold(title)}`;
|
|
1854
|
+
}
|
|
1855
|
+
function formatKeyValue(label, value) {
|
|
1856
|
+
return ` ${dim(label.padEnd(14, " "))} ${value}`;
|
|
1857
|
+
}
|
|
1858
|
+
function formatBullet(value, tone = "neutral") {
|
|
1859
|
+
const icon = tone === "success" ? green("\u2714") : tone === "warning" ? yellow("!") : tone === "danger" ? red("\u2716") : cyan("\u2022");
|
|
1860
|
+
return ` ${icon} ${value}`;
|
|
1861
|
+
}
|
|
1862
|
+
function formatMutedPath(path) {
|
|
1863
|
+
return dim(path);
|
|
1864
|
+
}
|
|
1865
|
+
function maskSecret(value, visible = 8) {
|
|
1866
|
+
if (!value) return "(empty)";
|
|
1867
|
+
if (value.length <= visible) return value;
|
|
1868
|
+
return `${value.slice(0, visible)}\u2026`;
|
|
1869
|
+
}
|
|
1870
|
+
function formatStatusBadge(label, tone = "neutral") {
|
|
1871
|
+
if (tone === "success") return green(label);
|
|
1872
|
+
if (tone === "warning") return yellow(label);
|
|
1873
|
+
if (tone === "danger") return red(label);
|
|
1874
|
+
return magenta(label);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// src/infrastructure/ui/prompts.ts
|
|
1878
|
+
import {
|
|
1879
|
+
confirm,
|
|
1880
|
+
input,
|
|
1881
|
+
password,
|
|
1882
|
+
select
|
|
1883
|
+
} from "@inquirer/prompts";
|
|
1884
|
+
function isInteractiveTerminal() {
|
|
1885
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1886
|
+
}
|
|
1887
|
+
async function promptConfirm(options) {
|
|
1888
|
+
return confirm({
|
|
1889
|
+
message: options.message,
|
|
1890
|
+
default: options.defaultValue
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
async function promptText(options) {
|
|
1894
|
+
return input({
|
|
1895
|
+
message: options.message,
|
|
1896
|
+
default: options.defaultValue,
|
|
1897
|
+
validate: options.validate
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
async function promptPassword(options) {
|
|
1901
|
+
return password({
|
|
1902
|
+
message: options.message,
|
|
1903
|
+
mask: options.mask ?? "*",
|
|
1904
|
+
validate: options.validate
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
async function promptSelect(options) {
|
|
1908
|
+
return select({
|
|
1909
|
+
message: options.message,
|
|
1910
|
+
choices: [...options.choices],
|
|
1911
|
+
pageSize: Math.min(Math.max(options.choices.length, 6), 10)
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/utils/logger.ts
|
|
1916
|
+
var LOG_LEVELS = {
|
|
1917
|
+
debug: 0,
|
|
1918
|
+
info: 1,
|
|
1919
|
+
warn: 2,
|
|
1920
|
+
error: 3
|
|
1921
|
+
};
|
|
1922
|
+
var Logger = class {
|
|
1923
|
+
level;
|
|
1924
|
+
constructor(level = "info") {
|
|
1925
|
+
this.level = level;
|
|
1926
|
+
}
|
|
1927
|
+
setLevel(level) {
|
|
1928
|
+
this.level = level;
|
|
1929
|
+
}
|
|
1930
|
+
debug(msg) {
|
|
1931
|
+
if (LOG_LEVELS[this.level] <= LOG_LEVELS.debug) {
|
|
1932
|
+
process.stderr.write(`[debug] ${msg}
|
|
1933
|
+
`);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
info(msg) {
|
|
1937
|
+
if (LOG_LEVELS[this.level] <= LOG_LEVELS.info) {
|
|
1938
|
+
process.stdout.write(`${msg}
|
|
1939
|
+
`);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
warn(msg) {
|
|
1943
|
+
if (LOG_LEVELS[this.level] <= LOG_LEVELS.warn) {
|
|
1944
|
+
process.stderr.write(`warn: ${msg}
|
|
1945
|
+
`);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
error(msg) {
|
|
1949
|
+
if (LOG_LEVELS[this.level] <= LOG_LEVELS.error) {
|
|
1950
|
+
process.stderr.write(`error: ${msg}
|
|
1951
|
+
`);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
log(msg) {
|
|
1955
|
+
this.info(msg);
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
var logger = new Logger();
|
|
1959
|
+
|
|
1833
1960
|
// src/commands/config.ts
|
|
1834
1961
|
var VALID_KEYS = ["apiKey", "apiUrl", "syncInterval", "logLevel"];
|
|
1835
1962
|
function isConfigKey(value) {
|
|
@@ -2135,14 +2262,14 @@ import { hostname as hostname3 } from "os";
|
|
|
2135
2262
|
|
|
2136
2263
|
// src/domain/project-identity.ts
|
|
2137
2264
|
import { createHmac } from "crypto";
|
|
2138
|
-
function toProjectIdentity(
|
|
2139
|
-
if (
|
|
2265
|
+
function toProjectIdentity(input2) {
|
|
2266
|
+
if (input2.mode === "disabled") {
|
|
2140
2267
|
return { projectKey: "unknown", projectLabel: "Unknown Project" };
|
|
2141
2268
|
}
|
|
2142
|
-
if (
|
|
2143
|
-
return { projectKey:
|
|
2269
|
+
if (input2.mode === "raw") {
|
|
2270
|
+
return { projectKey: input2.project, projectLabel: input2.project };
|
|
2144
2271
|
}
|
|
2145
|
-
const projectKey = createHmac("sha256",
|
|
2272
|
+
const projectKey = createHmac("sha256", input2.salt).update(input2.project).digest("hex").slice(0, 16);
|
|
2146
2273
|
return {
|
|
2147
2274
|
projectKey,
|
|
2148
2275
|
projectLabel: `Project ${projectKey.slice(0, 6)}`
|
|
@@ -2158,13 +2285,13 @@ function shortHash(value) {
|
|
|
2158
2285
|
function normalizeApiUrl(apiUrl) {
|
|
2159
2286
|
return apiUrl.replace(/\/+$/, "");
|
|
2160
2287
|
}
|
|
2161
|
-
function buildUploadManifestScope(
|
|
2288
|
+
function buildUploadManifestScope(input2) {
|
|
2162
2289
|
return {
|
|
2163
|
-
apiKeyHash: shortHash(
|
|
2164
|
-
apiUrl: normalizeApiUrl(
|
|
2165
|
-
deviceId:
|
|
2166
|
-
projectHashSaltHash: shortHash(
|
|
2167
|
-
projectMode:
|
|
2290
|
+
apiKeyHash: shortHash(input2.apiKey),
|
|
2291
|
+
apiUrl: normalizeApiUrl(input2.apiUrl),
|
|
2292
|
+
deviceId: input2.deviceId,
|
|
2293
|
+
projectHashSaltHash: shortHash(input2.settings.projectHashSalt),
|
|
2294
|
+
projectMode: input2.settings.projectMode
|
|
2168
2295
|
};
|
|
2169
2296
|
}
|
|
2170
2297
|
function describeUploadManifestScopeChanges(previous, current) {
|
|
@@ -2234,20 +2361,20 @@ function buildRecordHashes(items, getKey, getHash) {
|
|
|
2234
2361
|
}
|
|
2235
2362
|
return hashes;
|
|
2236
2363
|
}
|
|
2237
|
-
function createUploadManifest(
|
|
2364
|
+
function createUploadManifest(input2) {
|
|
2238
2365
|
return {
|
|
2239
2366
|
buckets: buildRecordHashes(
|
|
2240
|
-
|
|
2367
|
+
input2.buckets,
|
|
2241
2368
|
getUploadBucketManifestKey,
|
|
2242
2369
|
getUploadBucketContentHash
|
|
2243
2370
|
),
|
|
2244
|
-
scope:
|
|
2371
|
+
scope: input2.scope,
|
|
2245
2372
|
sessions: buildRecordHashes(
|
|
2246
|
-
|
|
2373
|
+
input2.sessions,
|
|
2247
2374
|
getUploadSessionManifestKey,
|
|
2248
2375
|
getUploadSessionContentHash
|
|
2249
2376
|
),
|
|
2250
|
-
updatedAt:
|
|
2377
|
+
updatedAt: input2.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2251
2378
|
version: MANIFEST_VERSION
|
|
2252
2379
|
};
|
|
2253
2380
|
}
|
|
@@ -2260,21 +2387,21 @@ function countRemovedRecords(previous, current) {
|
|
|
2260
2387
|
}
|
|
2261
2388
|
return removed;
|
|
2262
2389
|
}
|
|
2263
|
-
function diffUploadManifest(
|
|
2390
|
+
function diffUploadManifest(input2) {
|
|
2264
2391
|
const nextManifest = createUploadManifest({
|
|
2265
|
-
buckets:
|
|
2266
|
-
scope:
|
|
2267
|
-
sessions:
|
|
2268
|
-
updatedAt:
|
|
2392
|
+
buckets: input2.buckets,
|
|
2393
|
+
scope: input2.scope,
|
|
2394
|
+
sessions: input2.sessions,
|
|
2395
|
+
updatedAt: input2.updatedAt
|
|
2269
2396
|
});
|
|
2270
|
-
const scopeChangedReasons =
|
|
2271
|
-
const previousBuckets =
|
|
2272
|
-
const previousSessions =
|
|
2273
|
-
const bucketsToUpload =
|
|
2397
|
+
const scopeChangedReasons = input2.previous ? describeUploadManifestScopeChanges(input2.previous.scope, input2.scope) : [];
|
|
2398
|
+
const previousBuckets = input2.previous && scopeChangedReasons.length === 0 ? input2.previous.buckets : {};
|
|
2399
|
+
const previousSessions = input2.previous && scopeChangedReasons.length === 0 ? input2.previous.sessions : {};
|
|
2400
|
+
const bucketsToUpload = input2.buckets.filter((bucket) => {
|
|
2274
2401
|
const key = getUploadBucketManifestKey(bucket);
|
|
2275
2402
|
return previousBuckets[key] !== nextManifest.buckets[key];
|
|
2276
2403
|
});
|
|
2277
|
-
const sessionsToUpload =
|
|
2404
|
+
const sessionsToUpload = input2.sessions.filter((session) => {
|
|
2278
2405
|
const key = getUploadSessionManifestKey(session);
|
|
2279
2406
|
return previousSessions[key] !== nextManifest.sessions[key];
|
|
2280
2407
|
});
|
|
@@ -2288,8 +2415,8 @@ function diffUploadManifest(input) {
|
|
|
2288
2415
|
),
|
|
2289
2416
|
scopeChangedReasons,
|
|
2290
2417
|
sessionsToUpload,
|
|
2291
|
-
unchangedBuckets:
|
|
2292
|
-
unchangedSessions:
|
|
2418
|
+
unchangedBuckets: input2.buckets.length - bucketsToUpload.length,
|
|
2419
|
+
unchangedSessions: input2.sessions.length - sessionsToUpload.length
|
|
2293
2420
|
};
|
|
2294
2421
|
}
|
|
2295
2422
|
|
|
@@ -3149,18 +3276,706 @@ View your dashboard at: ${apiUrl}/usage`);
|
|
|
3149
3276
|
}
|
|
3150
3277
|
|
|
3151
3278
|
// src/commands/init.ts
|
|
3152
|
-
import { execFileSync as
|
|
3153
|
-
import { existsSync as
|
|
3279
|
+
import { execFileSync as execFileSync4, spawn } from "child_process";
|
|
3280
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3154
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";
|
|
3155
3291
|
import { homedir as homedir12, platform } from "os";
|
|
3156
|
-
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
|
|
3157
3972
|
function joinForPlatform(currentPlatform, ...parts) {
|
|
3158
3973
|
return currentPlatform === "win32" ? win32.join(...parts) : posix.join(...parts);
|
|
3159
3974
|
}
|
|
3160
|
-
function basenameLikeShell(
|
|
3161
|
-
return
|
|
3975
|
+
function basenameLikeShell(input2) {
|
|
3976
|
+
return input2.split(/[\\/]+/).pop()?.replace(/\.exe$/i, "") ?? input2;
|
|
3162
3977
|
}
|
|
3163
|
-
function getBrowserLaunchCommand(url, currentPlatform =
|
|
3978
|
+
function getBrowserLaunchCommand(url, currentPlatform = platform4()) {
|
|
3164
3979
|
switch (currentPlatform) {
|
|
3165
3980
|
case "darwin":
|
|
3166
3981
|
return {
|
|
@@ -3197,11 +4012,11 @@ function resolvePowerShellProfilePath() {
|
|
|
3197
4012
|
const systemRoot = process.env.SYSTEMROOT || "C:\\Windows";
|
|
3198
4013
|
const candidates = [
|
|
3199
4014
|
"pwsh.exe",
|
|
3200
|
-
|
|
4015
|
+
join17(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
3201
4016
|
];
|
|
3202
4017
|
for (const command of candidates) {
|
|
3203
4018
|
try {
|
|
3204
|
-
const output =
|
|
4019
|
+
const output = execFileSync4(
|
|
3205
4020
|
command,
|
|
3206
4021
|
[
|
|
3207
4022
|
"-NoLogo",
|
|
@@ -3224,10 +4039,10 @@ function resolvePowerShellProfilePath() {
|
|
|
3224
4039
|
return null;
|
|
3225
4040
|
}
|
|
3226
4041
|
function resolveShellAliasSetup(options = {}) {
|
|
3227
|
-
const currentPlatform = options.currentPlatform ??
|
|
4042
|
+
const currentPlatform = options.currentPlatform ?? platform4();
|
|
3228
4043
|
const env = options.env ?? process.env;
|
|
3229
|
-
const homeDir = options.homeDir ??
|
|
3230
|
-
const pathExists = options.exists ??
|
|
4044
|
+
const homeDir = options.homeDir ?? homedir14();
|
|
4045
|
+
const pathExists = options.exists ?? existsSync18;
|
|
3231
4046
|
const shellFromEnv = env.SHELL ? basenameLikeShell(env.SHELL).toLowerCase() : "";
|
|
3232
4047
|
const shellName = shellFromEnv || (currentPlatform === "win32" ? "powershell" : "");
|
|
3233
4048
|
const aliasName = "ta";
|
|
@@ -3383,29 +4198,29 @@ async function runInit(opts = {}) {
|
|
|
3383
4198
|
logger.info(formatBullet("TokenArena \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002", "success"));
|
|
3384
4199
|
logger.info(formatKeyValue("\u63A7\u5236\u53F0", `${apiUrl}/usage`));
|
|
3385
4200
|
await setupShellAlias();
|
|
3386
|
-
await
|
|
4201
|
+
await setupBackgroundService();
|
|
3387
4202
|
}
|
|
3388
|
-
async function
|
|
3389
|
-
const
|
|
3390
|
-
if (
|
|
4203
|
+
async function setupBackgroundService() {
|
|
4204
|
+
const backend = getServiceBackend();
|
|
4205
|
+
if (!backend) {
|
|
3391
4206
|
return;
|
|
3392
4207
|
}
|
|
3393
|
-
|
|
4208
|
+
const support = backend.canSetup();
|
|
4209
|
+
if (!support.ok) {
|
|
3394
4210
|
return;
|
|
3395
4211
|
}
|
|
3396
4212
|
const shouldSetup = await promptConfirm({
|
|
3397
|
-
message:
|
|
4213
|
+
message: `\u662F\u5426\u8BBE\u7F6E ${backend.displayName} \u4EE5\u81EA\u52A8\u8FD0\u884C daemon\uFF1F`,
|
|
3398
4214
|
defaultValue: true
|
|
3399
4215
|
});
|
|
3400
4216
|
if (!shouldSetup) {
|
|
3401
|
-
logger.info(formatBullet("\u5DF2\u8DF3\u8FC7
|
|
4217
|
+
logger.info(formatBullet("\u5DF2\u8DF3\u8FC7\u540E\u53F0\u670D\u52A1\u8BBE\u7F6E\u3002"));
|
|
3402
4218
|
return;
|
|
3403
4219
|
}
|
|
3404
4220
|
try {
|
|
3405
|
-
|
|
3406
|
-
await runInstallService2({ action: "setup", skipPrompt: true });
|
|
4221
|
+
await backend.setup(true);
|
|
3407
4222
|
} catch (err) {
|
|
3408
|
-
logger.warn(
|
|
4223
|
+
logger.warn(`${backend.displayName} \u8BBE\u7F6E\u5931\u8D25: ${err.message}`);
|
|
3409
4224
|
}
|
|
3410
4225
|
}
|
|
3411
4226
|
async function setupShellAlias() {
|
|
@@ -3424,7 +4239,7 @@ async function setupShellAlias() {
|
|
|
3424
4239
|
try {
|
|
3425
4240
|
await mkdir(dirname3(setup.configFile), { recursive: true });
|
|
3426
4241
|
let existingContent = "";
|
|
3427
|
-
if (
|
|
4242
|
+
if (existsSync18(setup.configFile)) {
|
|
3428
4243
|
existingContent = await readFile(setup.configFile, "utf-8");
|
|
3429
4244
|
}
|
|
3430
4245
|
const normalizedContent = existingContent.toLowerCase();
|
|
@@ -3468,6 +4283,9 @@ function log(msg) {
|
|
|
3468
4283
|
function sleep(ms) {
|
|
3469
4284
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3470
4285
|
}
|
|
4286
|
+
function getDaemonExitCode(opts = {}) {
|
|
4287
|
+
return opts.service ? 0 : 1;
|
|
4288
|
+
}
|
|
3471
4289
|
async function runDaemon(opts = {}) {
|
|
3472
4290
|
const config = loadConfig();
|
|
3473
4291
|
if (!config?.apiKey) {
|
|
@@ -3489,12 +4307,14 @@ async function runDaemon(opts = {}) {
|
|
|
3489
4307
|
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u542F\u52A8 daemon\u3002", "warning"));
|
|
3490
4308
|
return;
|
|
3491
4309
|
}
|
|
3492
|
-
|
|
3493
|
-
|
|
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));
|
|
3494
4313
|
}
|
|
3495
4314
|
const interval = opts.interval || config.syncInterval || DEFAULT_INTERVAL;
|
|
3496
4315
|
const intervalMin = Math.round(interval / 6e4);
|
|
3497
|
-
|
|
4316
|
+
const stopHint = opts.service ? "service mode" : "Ctrl+C to stop";
|
|
4317
|
+
log(`Daemon started (sync every ${intervalMin}m, ${stopHint})`);
|
|
3498
4318
|
while (true) {
|
|
3499
4319
|
try {
|
|
3500
4320
|
await runSync(config, {
|
|
@@ -3504,8 +4324,9 @@ async function runDaemon(opts = {}) {
|
|
|
3504
4324
|
});
|
|
3505
4325
|
} catch (err) {
|
|
3506
4326
|
if (err.message === "UNAUTHORIZED") {
|
|
3507
|
-
|
|
3508
|
-
|
|
4327
|
+
const message = opts.service ? "API key invalid. Exiting service mode." : "API key invalid. Exiting.";
|
|
4328
|
+
log(message);
|
|
4329
|
+
process.exit(getDaemonExitCode(opts));
|
|
3509
4330
|
}
|
|
3510
4331
|
log(`Sync error: ${err.message}`);
|
|
3511
4332
|
}
|
|
@@ -3617,8 +4438,8 @@ async function runSyncCommand(opts = {}) {
|
|
|
3617
4438
|
}
|
|
3618
4439
|
|
|
3619
4440
|
// src/commands/uninstall.ts
|
|
3620
|
-
import { existsSync as
|
|
3621
|
-
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";
|
|
3622
4443
|
function removeShellAlias() {
|
|
3623
4444
|
const shell = process.env.SHELL;
|
|
3624
4445
|
if (!shell) return;
|
|
@@ -3627,22 +4448,22 @@ function removeShellAlias() {
|
|
|
3627
4448
|
let configFile;
|
|
3628
4449
|
switch (shellName) {
|
|
3629
4450
|
case "zsh":
|
|
3630
|
-
configFile = `${
|
|
4451
|
+
configFile = `${homedir15()}/.zshrc`;
|
|
3631
4452
|
break;
|
|
3632
4453
|
case "bash":
|
|
3633
|
-
if (
|
|
3634
|
-
configFile = `${
|
|
4454
|
+
if (platform5() === "darwin" && existsSync19(`${homedir15()}/.bash_profile`)) {
|
|
4455
|
+
configFile = `${homedir15()}/.bash_profile`;
|
|
3635
4456
|
} else {
|
|
3636
|
-
configFile = `${
|
|
4457
|
+
configFile = `${homedir15()}/.bashrc`;
|
|
3637
4458
|
}
|
|
3638
4459
|
break;
|
|
3639
4460
|
case "fish":
|
|
3640
|
-
configFile = `${
|
|
4461
|
+
configFile = `${homedir15()}/.config/fish/config.fish`;
|
|
3641
4462
|
break;
|
|
3642
4463
|
default:
|
|
3643
4464
|
return;
|
|
3644
4465
|
}
|
|
3645
|
-
if (!
|
|
4466
|
+
if (!existsSync19(configFile)) return;
|
|
3646
4467
|
try {
|
|
3647
4468
|
let content = readFileSync10(configFile, "utf-8");
|
|
3648
4469
|
const aliasPatterns = [
|
|
@@ -3666,7 +4487,7 @@ function removeShellAlias() {
|
|
|
3666
4487
|
content = next;
|
|
3667
4488
|
}
|
|
3668
4489
|
}
|
|
3669
|
-
|
|
4490
|
+
writeFileSync7(configFile, content, "utf-8");
|
|
3670
4491
|
logger.info(`Removed shell alias from ${configFile}`);
|
|
3671
4492
|
} catch (err) {
|
|
3672
4493
|
logger.warn(
|
|
@@ -3677,7 +4498,12 @@ function removeShellAlias() {
|
|
|
3677
4498
|
async function runUninstall() {
|
|
3678
4499
|
const configPath = getConfigPath();
|
|
3679
4500
|
const configDir = getConfigDir();
|
|
3680
|
-
|
|
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) {
|
|
3681
4507
|
logger.info(formatHeader("\u5378\u8F7D TokenArena"));
|
|
3682
4508
|
logger.info(formatBullet("\u672A\u53D1\u73B0\u672C\u5730\u914D\u7F6E\uFF0C\u65E0\u9700\u5378\u8F7D\u3002"));
|
|
3683
4509
|
return;
|
|
@@ -3693,8 +4519,11 @@ async function runUninstall() {
|
|
|
3693
4519
|
logger.info(formatKeyValue("API Key", maskSecret(config.apiKey)));
|
|
3694
4520
|
}
|
|
3695
4521
|
logger.info(formatKeyValue("\u914D\u7F6E\u76EE\u5F55", configDir));
|
|
3696
|
-
logger.info(formatKeyValue("\u72B6\u6001\u76EE\u5F55",
|
|
3697
|
-
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
|
+
}
|
|
3698
4527
|
const shouldUninstall = await promptConfirm({
|
|
3699
4528
|
message: "\u786E\u8BA4\u7EE7\u7EED\u5378\u8F7D\u672C\u5730 TokenArena \u6570\u636E\uFF1F",
|
|
3700
4529
|
defaultValue: false
|
|
@@ -3703,24 +4532,37 @@ async function runUninstall() {
|
|
|
3703
4532
|
logger.info(formatBullet("\u5DF2\u53D6\u6D88\u5378\u8F7D\u3002", "warning"));
|
|
3704
4533
|
return;
|
|
3705
4534
|
}
|
|
3706
|
-
|
|
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
|
+
}
|
|
3707
4548
|
logger.info(formatSection("\u6267\u884C\u7ED3\u679C"));
|
|
3708
|
-
|
|
3709
|
-
|
|
4549
|
+
if (existsSync19(configPath)) {
|
|
4550
|
+
deleteConfig();
|
|
4551
|
+
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\u3002", "success"));
|
|
4552
|
+
}
|
|
4553
|
+
if (existsSync19(configDir)) {
|
|
3710
4554
|
try {
|
|
3711
|
-
|
|
4555
|
+
rmSync4(configDir, { recursive: false, force: true });
|
|
3712
4556
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u76EE\u5F55\u3002", "success"));
|
|
3713
4557
|
} catch {
|
|
3714
4558
|
}
|
|
3715
4559
|
}
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
rmSync2(stateDir, { recursive: true, force: true });
|
|
4560
|
+
if (existsSync19(stateDir)) {
|
|
4561
|
+
rmSync4(stateDir, { recursive: true, force: true });
|
|
3719
4562
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u72B6\u6001\u6570\u636E\u3002", "success"));
|
|
3720
4563
|
}
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
rmSync2(runtimeDir, { recursive: true, force: true });
|
|
4564
|
+
if (existsSync19(runtimeDir)) {
|
|
4565
|
+
rmSync4(runtimeDir, { recursive: true, force: true });
|
|
3724
4566
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u8FD0\u884C\u65F6\u6570\u636E\u3002", "success"));
|
|
3725
4567
|
}
|
|
3726
4568
|
removeShellAlias();
|
|
@@ -3842,9 +4684,70 @@ async function runHome(program) {
|
|
|
3842
4684
|
}
|
|
3843
4685
|
}
|
|
3844
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
|
+
|
|
3845
4748
|
// src/infrastructure/runtime/cli-version.ts
|
|
3846
4749
|
import { readFileSync as readFileSync11 } from "fs";
|
|
3847
|
-
import { dirname as dirname4, join as
|
|
4750
|
+
import { dirname as dirname4, join as join18 } from "path";
|
|
3848
4751
|
import { fileURLToPath } from "url";
|
|
3849
4752
|
var FALLBACK_VERSION = "0.0.0";
|
|
3850
4753
|
var cachedVersion;
|
|
@@ -3852,7 +4755,7 @@ function getCliVersion(metaUrl = import.meta.url) {
|
|
|
3852
4755
|
if (cachedVersion) {
|
|
3853
4756
|
return cachedVersion;
|
|
3854
4757
|
}
|
|
3855
|
-
const packageJsonPath =
|
|
4758
|
+
const packageJsonPath = join18(
|
|
3856
4759
|
dirname4(fileURLToPath(metaUrl)),
|
|
3857
4760
|
"..",
|
|
3858
4761
|
"package.json"
|
|
@@ -3888,13 +4791,13 @@ function createCli() {
|
|
|
3888
4791
|
program.command("sync").description("Manually sync usage data to server").addOption(new Option("--quiet").hideHelp()).action(async (opts) => {
|
|
3889
4792
|
await runSyncCommand(opts);
|
|
3890
4793
|
});
|
|
3891
|
-
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) => {
|
|
3892
4795
|
await runDaemon(opts);
|
|
3893
4796
|
});
|
|
3894
4797
|
program.command("service [action]").description(
|
|
3895
|
-
"Manage
|
|
4798
|
+
"Manage background service (setup|start|stop|restart|status|uninstall)"
|
|
3896
4799
|
).action(async (action) => {
|
|
3897
|
-
await
|
|
4800
|
+
await runServiceCommand({ action });
|
|
3898
4801
|
});
|
|
3899
4802
|
program.command("status").description("Show configuration and detected tools").action(async () => {
|
|
3900
4803
|
await runStatus();
|
|
@@ -3913,7 +4816,7 @@ function createCli() {
|
|
|
3913
4816
|
}
|
|
3914
4817
|
|
|
3915
4818
|
// src/infrastructure/runtime/main-module.ts
|
|
3916
|
-
import { existsSync as
|
|
4819
|
+
import { existsSync as existsSync20, realpathSync } from "fs";
|
|
3917
4820
|
import { resolve } from "path";
|
|
3918
4821
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3919
4822
|
function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
|
|
@@ -3924,7 +4827,7 @@ function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
|
|
|
3924
4827
|
try {
|
|
3925
4828
|
return realpathSync(argvEntry) === realpathSync(currentModulePath);
|
|
3926
4829
|
} catch {
|
|
3927
|
-
if (!
|
|
4830
|
+
if (!existsSync20(argvEntry)) {
|
|
3928
4831
|
return false;
|
|
3929
4832
|
}
|
|
3930
4833
|
return resolve(argvEntry) === resolve(currentModulePath);
|