deskbuddy 0.0.1 → 0.1.0

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/cli.js ADDED
@@ -0,0 +1,989 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/config.ts
4
+ import { randomUUID } from "crypto";
5
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
6
+ import { homedir, hostname } from "os";
7
+ import { join } from "path";
8
+ var PROD_API_BASE = "https://api.shifengintelli.com";
9
+ var TEST_API_BASE = "https://test-api.shifengintelli.com";
10
+ var DEFAULT_API_BASE = process.env.DESKBUDDY_API_BASE || PROD_API_BASE;
11
+ function environmentLabel(apiBase) {
12
+ const normalized = (apiBase || "").replace(/\/+$/, "");
13
+ if (normalized === PROD_API_BASE) return "\u751F\u4EA7";
14
+ if (normalized === TEST_API_BASE) return "\u6D4B\u8BD5";
15
+ return "\u81EA\u5B9A\u4E49";
16
+ }
17
+ function envOverride() {
18
+ const envValue = process.env.DESKBUDDY_API_BASE;
19
+ if (!envValue) return { active: false };
20
+ let fileValue;
21
+ try {
22
+ fileValue = JSON.parse(readFileSync(configPath(), "utf8")).apiBase;
23
+ } catch {
24
+ return { active: false, envValue };
25
+ }
26
+ if (!fileValue || fileValue === envValue) return { active: false, envValue, fileValue };
27
+ return { active: true, envValue, fileValue };
28
+ }
29
+ function deskbuddyHome() {
30
+ return process.env.DESKBUDDY_HOME || join(homedir(), ".deskbuddy");
31
+ }
32
+ function configPath() {
33
+ return join(deskbuddyHome(), "config.json");
34
+ }
35
+ function claudeHome() {
36
+ return process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
37
+ }
38
+ function claudeSettingsPath() {
39
+ return join(claudeHome(), "settings.json");
40
+ }
41
+ function codexHome() {
42
+ return process.env.CODEX_HOME || join(homedir(), ".codex");
43
+ }
44
+ function installedHookPath() {
45
+ return join(deskbuddyHome(), "hook.cjs");
46
+ }
47
+ function loadConfig() {
48
+ const fallback = {
49
+ clientId: randomUUID(),
50
+ hostName: hostname(),
51
+ enabledTools: [],
52
+ apiBase: DEFAULT_API_BASE
53
+ };
54
+ try {
55
+ const raw = readFileSync(configPath(), "utf8");
56
+ const parsed = JSON.parse(raw);
57
+ return {
58
+ clientId: parsed.clientId || fallback.clientId,
59
+ hostName: parsed.hostName || fallback.hostName,
60
+ enabledTools: parsed.enabledTools ?? [],
61
+ // 环境变量优先:联调时不用改用户的 config.json
62
+ apiBase: process.env.DESKBUDDY_API_BASE || parsed.apiBase || DEFAULT_API_BASE,
63
+ boundAt: parsed.boundAt,
64
+ // ⚠️ 这里是逐字段重建对象,不是展开 parsed —— 新增字段【必须】在这里也加一行,
65
+ // 否则它写得进 config.json、却永远读不出来(saveConfig 存的是本函数的返回值,
66
+ // 于是下一次保存还会把它抹掉),而且全程不报错
67
+ boundApiBase: parsed.boundApiBase
68
+ };
69
+ } catch {
70
+ return fallback;
71
+ }
72
+ }
73
+ function saveConfig(config) {
74
+ const home = deskbuddyHome();
75
+ mkdirSync(home, { recursive: true });
76
+ const tmp = configPath() + ".tmp";
77
+ writeFileSync(tmp, JSON.stringify(config, null, 2), "utf8");
78
+ renameSync(tmp, configPath());
79
+ }
80
+ function ensureClientId() {
81
+ const config = loadConfig();
82
+ if (!existsSync(configPath())) {
83
+ saveConfig(config);
84
+ }
85
+ return config.clientId;
86
+ }
87
+
88
+ // src/core/registry.ts
89
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
90
+ import { join as join3 } from "path";
91
+
92
+ // src/core/api.ts
93
+ import { execFileSync } from "child_process";
94
+ import { createHash, randomBytes, randomUUID as randomUUID2 } from "crypto";
95
+ import { readFileSync as readFileSync3 } from "fs";
96
+ import { release } from "os";
97
+
98
+ // src/core/hookFile.ts
99
+ import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
100
+ import { createRequire } from "module";
101
+ import { dirname, join as join2 } from "path";
102
+ import { fileURLToPath } from "url";
103
+ var here = dirname(fileURLToPath(import.meta.url));
104
+ function packagedHookPath() {
105
+ const candidates = [
106
+ join2(here, "hook.cjs"),
107
+ // dist/cli.js 旁边(发布形态)
108
+ join2(here, "..", "hook", "hook.cjs"),
109
+ // dist/../hook/
110
+ join2(here, "..", "..", "hook", "hook.cjs")
111
+ // src/core/../../hook/(开发形态)
112
+ ];
113
+ for (const c of candidates) {
114
+ if (existsSync2(c)) return c;
115
+ }
116
+ throw new Error("\u627E\u4E0D\u5230\u5305\u5185\u7684 hook.cjs\uFF0C\u5B89\u88C5\u5305\u53EF\u80FD\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5 deskbuddy");
117
+ }
118
+ function packageVersion() {
119
+ const require2 = createRequire(import.meta.url);
120
+ for (const c of ["../package.json", "../../package.json", "../../../package.json"]) {
121
+ try {
122
+ const pkg = require2(c);
123
+ if (pkg?.name === "deskbuddy" && pkg.version) return pkg.version;
124
+ } catch {
125
+ }
126
+ }
127
+ return "0.0.0-dev";
128
+ }
129
+ function hookVersionPath(target = installedHookPath()) {
130
+ return join2(dirname(target), "hook-version");
131
+ }
132
+ function installedHookVersion(target = installedHookPath()) {
133
+ try {
134
+ return readFileSync2(hookVersionPath(target), "utf8").trim() || null;
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+ function syncHookFile(opts = {}) {
140
+ const target = opts.target ?? installedHookPath();
141
+ const version = opts.version ?? packageVersion();
142
+ if (installedHookVersion(target) === version && existsSync2(target)) return false;
143
+ const source = opts.source ?? packagedHookPath();
144
+ mkdirSync2(dirname(target), { recursive: true });
145
+ copyFileSync(source, target);
146
+ writeFileSync2(hookVersionPath(target), version, "utf8");
147
+ return true;
148
+ }
149
+
150
+ // src/core/api.ts
151
+ var SERVICES = {
152
+ authCode: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyauthcodeapi",
153
+ authToken: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyauthtokenapi",
154
+ sessionReport: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddysessionreportapi",
155
+ toolRegistry: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddytoolregistryapi",
156
+ /** App 扫码确认时调的,CLI 用不到,列在这里只为对照 */
157
+ confirmAuth: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyconfirmauthapi"
158
+ };
159
+ var APP_ID = process.env.DESKBUDDY_APPID || "deskbuddy-cli";
160
+ var APP_SECRET = process.env.DESKBUDDY_APPSECRET || "3aeb6e0afc144e188dd3ede5c7865643";
161
+ var GATEWAY_VERSION = "1.0";
162
+ var DEFAULT_TIMEOUT_MS = 1e4;
163
+ function sign(p) {
164
+ const source = [p.appId, p.appSecret, p.timestamp, p.deviceId, p.deviceName, p.nonce, p.body].join("_");
165
+ return createHash("md5").update(source, "utf8").digest("hex");
166
+ }
167
+ function buildGatewayBody(service, data, requestId) {
168
+ return JSON.stringify({
169
+ service,
170
+ version: GATEWAY_VERSION,
171
+ requestId: requestId || randomUUID2(),
172
+ base: { encode: "false" },
173
+ // data 是业务 JSON 序列化后的字符串
174
+ data: JSON.stringify(data ?? {})
175
+ });
176
+ }
177
+ function buildHeaders(cfg, body, opts = {}) {
178
+ const timestamp = opts.timestamp || String(Date.now());
179
+ const nonce = opts.nonce || randomBytes(8).toString("hex");
180
+ return {
181
+ "Content-Type": "application/json;charset=UTF-8",
182
+ "Content-Length": String(Buffer.byteLength(body)),
183
+ "ShiFeng-AppId": APP_ID,
184
+ "ShiFeng-Timestamp": timestamp,
185
+ "ShiFeng-Device-ID": cfg.clientId,
186
+ "ShiFeng-Device-Name": cfg.hostName,
187
+ "ShiFeng-Nonce": nonce,
188
+ "ShiFeng-Signature": sign({
189
+ appId: APP_ID,
190
+ appSecret: APP_SECRET,
191
+ timestamp,
192
+ deviceId: cfg.clientId,
193
+ deviceName: cfg.hostName,
194
+ nonce,
195
+ body
196
+ })
197
+ // 没有 ShiFeng-Token:鉴权靠 clientId ↔ userId 绑定关系,CLI 不持有用户凭证
198
+ };
199
+ }
200
+ function isSuccessCode(code) {
201
+ return code === "0000";
202
+ }
203
+ var ApiError = class extends Error {
204
+ constructor(code, message) {
205
+ super(`${code}: ${message}`);
206
+ this.code = code;
207
+ this.name = "ApiError";
208
+ }
209
+ code;
210
+ };
211
+ function humanOs(platform = process.platform) {
212
+ const known = {
213
+ darwin: "macOS",
214
+ win32: "Windows",
215
+ linux: "Linux"
216
+ };
217
+ return known[platform] ?? platform;
218
+ }
219
+ var defaultOsVersionDeps = {
220
+ runSwVers: () => execFileSync("sw_vers", ["-productVersion"], {
221
+ encoding: "utf8",
222
+ stdio: ["ignore", "pipe", "ignore"],
223
+ timeout: 2e3
224
+ }),
225
+ readOsRelease: () => readFileSync3("/etc/os-release", "utf8"),
226
+ kernelRelease: () => release()
227
+ };
228
+ function productOsVersion(platform = process.platform, deps = defaultOsVersionDeps) {
229
+ try {
230
+ if (platform === "darwin") {
231
+ const v = deps.runSwVers().trim();
232
+ if (v) return v;
233
+ } else if (platform === "linux") {
234
+ const m = /^PRETTY_NAME=(.*)$/m.exec(deps.readOsRelease());
235
+ if (m) {
236
+ const v = m[1].trim().replace(/^(["'])(.*)\1$/, "$2").trim();
237
+ if (v) return v;
238
+ }
239
+ }
240
+ } catch {
241
+ }
242
+ try {
243
+ return deps.kernelRelease();
244
+ } catch {
245
+ return "";
246
+ }
247
+ }
248
+ var machineInfoCache = null;
249
+ function machineInfo() {
250
+ if (!machineInfoCache) {
251
+ machineInfoCache = {
252
+ os: humanOs(),
253
+ osVersion: productOsVersion(),
254
+ arch: process.arch,
255
+ cliVersion: packageVersion()
256
+ };
257
+ }
258
+ return machineInfoCache;
259
+ }
260
+ async function call(service, data, timeoutMs = DEFAULT_TIMEOUT_MS) {
261
+ const cfg = loadConfig();
262
+ const body = buildGatewayBody(service, data);
263
+ const controller = new AbortController();
264
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
265
+ try {
266
+ const res = await fetch(`${cfg.apiBase}/gateway`, {
267
+ method: "POST",
268
+ headers: buildHeaders(cfg, body),
269
+ body,
270
+ // 与签名用的是同一个字符串
271
+ signal: controller.signal
272
+ });
273
+ const json = await res.json();
274
+ if (!isSuccessCode(json?.code)) {
275
+ throw new ApiError(String(json?.code), json?.message ?? "");
276
+ }
277
+ return json?.data;
278
+ } finally {
279
+ clearTimeout(timer);
280
+ }
281
+ }
282
+ var api = {
283
+ authCode: (clientId, hostName) => call(SERVICES.authCode, { clientId, hostName, ...machineInfo() }),
284
+ authToken: (authCode) => call(SERVICES.authToken, { authCode }),
285
+ toolRegistry: () => call(SERVICES.toolRegistry, {}),
286
+ // 🔴 2 秒超时:这条调用发生在 hook 链路上,慢一点就是拖慢用户的 agent
287
+ report: (payload) => call(SERVICES.sessionReport, payload, 2e3)
288
+ };
289
+
290
+ // src/core/registry.ts
291
+ var FALLBACK = {
292
+ version: "builtin",
293
+ tools: {
294
+ claude_code: {
295
+ format: "cc-hook",
296
+ processNames: ["claude"],
297
+ titleSources: ["cc-ai-title", "git-branch", "project"],
298
+ events: { UserPromptSubmit: "watch", PreToolUse: "watch", PostToolUse: "watch", Stop: "done" },
299
+ lifecycle: { SessionStart: "ignore", SessionEnd: "remove" }
300
+ },
301
+ codex: {
302
+ format: "codex-toml",
303
+ processNames: ["codex"],
304
+ // codex-thread-name 是机会主义的一级:session_index.jsonl 里的 thread_name
305
+ // 原生只有 /rename 手动设,多数用户是空的,取不到就往下回落
306
+ titleSources: ["codex-thread-name", "git-branch", "project"],
307
+ events: {
308
+ UserPromptSubmit: "watch",
309
+ PreToolUse: "watch",
310
+ PostToolUse: "watch",
311
+ Stop: "done",
312
+ PermissionRequest: "ask_approval"
313
+ },
314
+ lifecycle: { SessionStart: "ignore", SessionEnd: "remove" }
315
+ }
316
+ }
317
+ };
318
+ function registryCachePath() {
319
+ return join3(deskbuddyHome(), "tool-registry.json");
320
+ }
321
+ function loadRegistry() {
322
+ try {
323
+ return JSON.parse(readFileSync4(registryCachePath(), "utf8"));
324
+ } catch {
325
+ return FALLBACK;
326
+ }
327
+ }
328
+ async function refreshRegistry() {
329
+ try {
330
+ const registry = await api.toolRegistry();
331
+ mkdirSync3(deskbuddyHome(), { recursive: true });
332
+ writeFileSync3(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
333
+ } catch {
334
+ }
335
+ }
336
+
337
+ // src/adapters/ccHook.ts
338
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
339
+ import { dirname as dirname2 } from "path";
340
+ var CC_EVENTS = [
341
+ "UserPromptSubmit",
342
+ "PreToolUse",
343
+ "PostToolUse",
344
+ "Notification",
345
+ "Stop",
346
+ "SessionEnd"
347
+ ];
348
+ function readSettings(path) {
349
+ if (!existsSync3(path)) return {};
350
+ const raw = readFileSync5(path, "utf8");
351
+ if (raw.trim() === "") return {};
352
+ try {
353
+ return JSON.parse(raw);
354
+ } catch {
355
+ throw new Error(`${path} \u89E3\u6790\u5931\u8D25\uFF0C\u8BF7\u5148\u4FEE\u590D\u8BE5\u6587\u4EF6\u518D\u91CD\u8BD5`);
356
+ }
357
+ }
358
+ function writeSettings(path, settings) {
359
+ mkdirSync4(dirname2(path), { recursive: true });
360
+ let mode;
361
+ try {
362
+ mode = statSync(path).mode & 511;
363
+ } catch {
364
+ mode = void 0;
365
+ }
366
+ const tmp = path + ".deskbuddy.tmp";
367
+ writeFileSync4(tmp, JSON.stringify(settings, null, 2), "utf8");
368
+ if (mode !== void 0) chmodSync(tmp, mode);
369
+ renameSync2(tmp, path);
370
+ }
371
+ function installCcHook(settingsPath, hookPath, events = CC_EVENTS, tool = "claude_code") {
372
+ const settings = readSettings(settingsPath);
373
+ settings.hooks ??= {};
374
+ for (const event of events) {
375
+ const groups = settings.hooks[event] ??= [];
376
+ const already = groups.some((g) => g.hooks?.some((h) => h.command?.includes(hookPath)));
377
+ if (already) continue;
378
+ groups.push({ hooks: [{ type: "command", command: `node ${hookPath} ${tool}` }] });
379
+ }
380
+ writeSettings(settingsPath, settings);
381
+ }
382
+ function uninstallCcHook(settingsPath, hookPath) {
383
+ const settings = readSettings(settingsPath);
384
+ if (!settings.hooks) return;
385
+ for (const event of Object.keys(settings.hooks)) {
386
+ const groups = settings.hooks[event] ?? [];
387
+ const kept = [];
388
+ for (const g of groups) {
389
+ const hooks = (g.hooks ?? []).filter((h) => !h.command?.includes(hookPath));
390
+ if (hooks.length > 0) kept.push({ ...g, hooks });
391
+ }
392
+ settings.hooks[event] = kept;
393
+ }
394
+ writeSettings(settingsPath, settings);
395
+ }
396
+
397
+ // src/adapters/codexHook.ts
398
+ import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
399
+ import { dirname as dirname3, join as join4 } from "path";
400
+ var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
401
+ var CODEX_MARKER_END = "# <<< deskbuddy <<<";
402
+ var CODEX_EVENTS = [
403
+ "UserPromptSubmit",
404
+ "PreToolUse",
405
+ "PostToolUse",
406
+ "Stop",
407
+ "SessionEnd",
408
+ "PermissionRequest"
409
+ ];
410
+ var CODEX_TRUST_NOTICE = "\u26A0\uFE0F \u4E0B\u6B21\u542F\u52A8 Codex \u65F6\u4F1A\u8981\u6C42\u4F60\u786E\u8BA4\u4FE1\u4EFB\u8FD9\u4E2A hook\uFF0C\u8BF7\u9009\u62E9\u540C\u610F\u2014\u2014\u4E0D\u786E\u8BA4\u7684\u8BDD Codex \u4E0D\u4F1A\u6267\u884C\u5B83\u3002";
411
+ function codexConfigPath() {
412
+ return join4(codexHome(), "config.toml");
413
+ }
414
+ function renderBlock(hookPath, events, tool) {
415
+ const command = JSON.stringify(`node ${hookPath} ${tool}`);
416
+ const lines = [CODEX_MARKER_BEGIN];
417
+ for (const event of events) {
418
+ lines.push(
419
+ `[[hooks.${event}]]`,
420
+ 'matcher = ""',
421
+ "hooks = [",
422
+ ` { type = "command", command = ${command}, async = true, timeoutSec = 5 }`,
423
+ "]",
424
+ ""
425
+ );
426
+ }
427
+ lines.push(CODEX_MARKER_END);
428
+ return lines.join("\n");
429
+ }
430
+ function writeAtomic(path, content) {
431
+ mkdirSync5(dirname3(path), { recursive: true });
432
+ let mode;
433
+ try {
434
+ mode = statSync2(path).mode & 511;
435
+ } catch {
436
+ mode = void 0;
437
+ }
438
+ const tmp = path + ".deskbuddy.tmp";
439
+ writeFileSync5(tmp, content, "utf8");
440
+ if (mode !== void 0) chmodSync2(tmp, mode);
441
+ renameSync3(tmp, path);
442
+ }
443
+ function assertSafeToAppend(raw, path) {
444
+ for (const quote of ['"""', "'''"]) {
445
+ if ((raw.split(quote).length - 1) % 2 === 1) {
446
+ throw new Error(`${path} \u91CC\u6709\u672A\u95ED\u5408\u7684\u591A\u884C\u5B57\u7B26\u4E32\uFF08${quote}\uFF09\uFF0C\u4E3A\u514D\u5199\u574F\u914D\u7F6E\u5DF2\u4E2D\u6B62\uFF0C\u8BF7\u5148\u4FEE\u590D\u8BE5\u6587\u4EF6`);
447
+ }
448
+ }
449
+ }
450
+ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex") {
451
+ const raw = existsSync4(configPath2) ? readFileSync6(configPath2, "utf8") : "";
452
+ if (raw.includes(CODEX_MARKER_BEGIN)) return;
453
+ assertSafeToAppend(raw, configPath2);
454
+ const block = renderBlock(hookPath, events, tool);
455
+ const head = raw === "" ? "" : (raw.endsWith("\n") ? raw : raw + "\n") + "\n";
456
+ writeAtomic(configPath2, head + block + "\n");
457
+ }
458
+ function uninstallCodexHook(configPath2) {
459
+ if (!existsSync4(configPath2)) return;
460
+ const raw = readFileSync6(configPath2, "utf8");
461
+ const lines = raw.split("\n");
462
+ const begin = lines.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
463
+ if (begin === -1) return;
464
+ const end = lines.findIndex((l, i) => i > begin && l.trim() === CODEX_MARKER_END);
465
+ if (end === -1) {
466
+ throw new Error(`${configPath2} \u91CC\u53EA\u627E\u5230\u5F00\u59CB\u6807\u8BB0\u3001\u6CA1\u6709\u7ED3\u675F\u6807\u8BB0\uFF0C\u4E3A\u514D\u8BEF\u5220\u5DF2\u4E2D\u6B62\uFF0C\u8BF7\u624B\u5DE5\u68C0\u67E5`);
467
+ }
468
+ const start = begin > 0 && lines[begin - 1].trim() === "" ? begin - 1 : begin;
469
+ lines.splice(start, end - start + 1);
470
+ writeAtomic(configPath2, lines.join("\n"));
471
+ }
472
+
473
+ // src/commands/tools.ts
474
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
475
+ function fileContains(path, needle) {
476
+ try {
477
+ if (!existsSync5(path)) return false;
478
+ return readFileSync7(path, "utf8").includes(needle);
479
+ } catch {
480
+ return false;
481
+ }
482
+ }
483
+ var ADAPTERS = {
484
+ "cc-hook": {
485
+ configPath: claudeSettingsPath,
486
+ install: (p, hook, events, tool) => installCcHook(p, hook, events, tool),
487
+ uninstall: (p, hook) => uninstallCcHook(p, hook),
488
+ isInstalled: (p, hook) => fileContains(p, hook),
489
+ defaultEvents: CC_EVENTS
490
+ },
491
+ "codex-toml": {
492
+ configPath: codexConfigPath,
493
+ install: (p, hook, events, tool) => installCodexHook(p, hook, events, tool),
494
+ uninstall: (p) => uninstallCodexHook(p),
495
+ isInstalled: (p) => fileContains(p, CODEX_MARKER_BEGIN),
496
+ defaultEvents: CODEX_EVENTS,
497
+ // 🔴 不打这句的话,用户会遇到「装成功了但没数据」,然后当成装失败
498
+ notice: CODEX_TRUST_NOTICE
499
+ }
500
+ };
501
+ function adapterFor(registry, tool) {
502
+ const format = registry?.tools?.[tool]?.format;
503
+ return format && ADAPTERS[format] || null;
504
+ }
505
+ function subscribedEvents(registry, tool, fallback) {
506
+ const entry = registry?.tools?.[tool];
507
+ if (!entry) return fallback;
508
+ const picked = [];
509
+ const add = (name, action) => {
510
+ if (!name || picked.includes(name)) return;
511
+ if (action === "ignore") return;
512
+ picked.push(name);
513
+ };
514
+ for (const [name, action] of Object.entries(entry.events ?? {})) add(name, action);
515
+ for (const [name, action] of Object.entries(entry.lifecycle ?? {})) add(name, action);
516
+ for (const name of Object.keys(entry.conditionalEvents ?? {})) add(name);
517
+ return picked.length > 0 ? picked : fallback;
518
+ }
519
+
520
+ // src/commands/disable.ts
521
+ function stdout(line) {
522
+ process.stdout.write(line + "\n");
523
+ }
524
+ function toolsToDisable(explicit) {
525
+ if (explicit.length > 0) return explicit;
526
+ const known = Object.keys(loadRegistry()?.tools ?? {});
527
+ return [.../* @__PURE__ */ new Set([...loadConfig().enabledTools, ...known])];
528
+ }
529
+ async function runDisable(tools, opts = {}) {
530
+ const out = opts.out ?? stdout;
531
+ const registry = loadRegistry();
532
+ const hookPath = installedHookPath();
533
+ const targets = toolsToDisable(tools);
534
+ const cfg = loadConfig();
535
+ const enabled = new Set(cfg.enabledTools);
536
+ let failed = 0;
537
+ for (const tool of targets) {
538
+ const adapter = adapterFor(registry, tool);
539
+ if (!adapter) {
540
+ continue;
541
+ }
542
+ const configPath2 = adapter.configPath();
543
+ try {
544
+ adapter.uninstall(configPath2, hookPath);
545
+ out(`\u2705 ${tool}\uFF1A\u5DF2\u4ECE ${configPath2} \u79FB\u9664`);
546
+ } catch (e) {
547
+ out(`\u2717 ${tool}\uFF1A${e.message}`);
548
+ failed += 1;
549
+ continue;
550
+ }
551
+ enabled.delete(tool);
552
+ }
553
+ cfg.enabledTools = [...enabled];
554
+ saveConfig(cfg);
555
+ if (!opts.brief) {
556
+ out("");
557
+ out("\u5DF2\u505C\u6B62\u91C7\u96C6\u3002\u6388\u6743\u4E0E clientId \u4ECD\u4FDD\u7559\uFF0C\u91CD\u65B0 deskbuddy enable \u5373\u53EF\u6062\u590D\u3002");
558
+ out("\u8981\u5F7B\u5E95\u6E05\u9664\u672C\u673A\u6570\u636E\u8BF7\u8FD0\u884C\uFF1Adeskbuddy logout");
559
+ }
560
+ return failed > 0 ? 1 : 0;
561
+ }
562
+
563
+ // src/commands/enable.ts
564
+ function stdout2(line) {
565
+ process.stdout.write(line + "\n");
566
+ }
567
+ function parseTools(arg) {
568
+ return (arg ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
569
+ }
570
+ async function runEnable(tools, opts = {}) {
571
+ const out = opts.out ?? stdout2;
572
+ if (tools.length === 0) {
573
+ out("\u7528\u6CD5\uFF1Adeskbuddy enable <tool[,tool]>\uFF0C\u4F8B\u5982 deskbuddy enable claude_code,codex");
574
+ return 1;
575
+ }
576
+ if (!loadConfig().boundAt) {
577
+ out("\u8FD8\u6CA1\u6709\u6388\u6743\u3002\u8BF7\u5148\u8FD0\u884C\uFF1Adeskbuddy setup");
578
+ return 1;
579
+ }
580
+ ensureClientId();
581
+ if (!opts.skipRefresh) await refreshRegistry();
582
+ const registry = loadRegistry();
583
+ const hookPath = installedHookPath();
584
+ syncHookFile();
585
+ const cfg = loadConfig();
586
+ const enabled = new Set(cfg.enabledTools);
587
+ let failed = 0;
588
+ for (const tool of tools) {
589
+ const adapter = adapterFor(registry, tool);
590
+ if (!adapter) {
591
+ out(`\u2717 ${tool}\uFF1A\u6620\u5C04\u8868\u91CC\u6CA1\u6709\u8FD9\u4E2A\u5DE5\u5177\uFF08\u6216\u5B83\u7684 format \u672C\u5BA2\u6237\u7AEF\u8FD8\u4E0D\u652F\u6301\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
592
+ failed += 1;
593
+ continue;
594
+ }
595
+ const configPath2 = adapter.configPath();
596
+ const events = subscribedEvents(registry, tool, adapter.defaultEvents);
597
+ try {
598
+ adapter.install(configPath2, hookPath, events, tool);
599
+ } catch (e) {
600
+ out(`\u2717 ${tool}\uFF1A${e.message}`);
601
+ failed += 1;
602
+ continue;
603
+ }
604
+ enabled.add(tool);
605
+ out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
606
+ if (adapter.notice) out(adapter.notice);
607
+ }
608
+ cfg.enabledTools = [...enabled];
609
+ saveConfig(cfg);
610
+ out("");
611
+ out("\u88C5\u5B8C\u4E86\u3002\u6CA1\u6709\u5E38\u9A7B\u8FDB\u7A0B \u2014\u2014 \u53EA\u662F\u5F80\u5404\u667A\u80FD\u4F53\u7684 hook \u914D\u7F6E\u91CC\u52A0\u4E86\u4E00\u6761\u8BB0\u5F55\u3002");
612
+ out("\u968F\u65F6\u53EF\u4EE5 deskbuddy status \u770B\u91C7\u96C6\u5230\u4E86\u4EC0\u4E48\u3001deskbuddy disable \u5E72\u51C0\u79FB\u9664\u3002");
613
+ return failed > 0 ? 1 : 0;
614
+ }
615
+
616
+ // src/commands/logout.ts
617
+ import { createInterface } from "readline";
618
+ import { existsSync as existsSync6, rmSync } from "fs";
619
+ function stdout3(line) {
620
+ process.stdout.write(line + "\n");
621
+ }
622
+ function askYesNo(question) {
623
+ return new Promise((resolve) => {
624
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
625
+ rl.question(question, (answer) => {
626
+ rl.close();
627
+ resolve(/^(y|yes)$/i.test(answer.trim()));
628
+ });
629
+ });
630
+ }
631
+ async function runLogout(opts = {}) {
632
+ const out = opts.out ?? stdout3;
633
+ const home = deskbuddyHome();
634
+ out("deskbuddy logout \u4F1A\u505A\u8FD9\u4E24\u4EF6\u4E8B\uFF1A");
635
+ out(" 1. \u4ECE\u5404\u667A\u80FD\u4F53\u7684\u914D\u7F6E\u91CC\u79FB\u9664 DeskBuddy \u7684 hook\uFF08\u4F60\u81EA\u5DF1\u7684 hook \u4E0D\u52A8\uFF09");
636
+ out(` 2. \u5220\u9664\u6574\u4E2A ${home}\uFF08clientId\u3001\u6388\u6743\u72B6\u6001\u3001\u4F1A\u8BDD\u72B6\u6001\u3001\u6620\u5C04\u8868\u7F13\u5B58\uFF09`);
637
+ out("");
638
+ out("\u5220\u6389\u4E4B\u540E\u518D\u7528\u5C31\u8981\u91CD\u65B0 deskbuddy setup \u626B\u7801\u3002");
639
+ out("\u6CE8\u610F\uFF1A\u8FD9\u53EA\u6E05\u9664\u672C\u673A\u6570\u636E\uFF0C\u670D\u52A1\u7AEF\u7684\u7ED1\u5B9A\u5173\u7CFB\u9700\u8981\u5728 App \u91CC\u64A4\u9500\uFF08\u6682\u672A\u652F\u6301\uFF09\uFF1B");
640
+ out("\u4F46\u91CD\u65B0 setup \u4F1A\u6362\u4E00\u4E2A\u65B0\u7684 clientId\uFF0C\u65E7\u7684\u4E0D\u4F1A\u518D\u4E0A\u62A5\u3002");
641
+ if (!opts.yes) {
642
+ const confirm = opts.confirm ?? (() => askYesNo("\u786E\u8BA4\u6267\u884C\uFF1F(y/N) "));
643
+ if (!await confirm()) {
644
+ out("\u5DF2\u53D6\u6D88\uFF0C\u4EC0\u4E48\u90FD\u6CA1\u52A8\u3002");
645
+ return 1;
646
+ }
647
+ }
648
+ await runDisable([], { out, brief: true });
649
+ if (existsSync6(home)) {
650
+ rmSync(home, { recursive: true, force: true });
651
+ }
652
+ out("");
653
+ out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
654
+ return 0;
655
+ }
656
+
657
+ // src/commands/setup.ts
658
+ import qrcode from "qrcode-terminal";
659
+ var AUTH_CODE_INVALID = "8250";
660
+ var DEFAULT_INTERVAL_SEC = 5;
661
+ var SETUP_TIMEOUT_MS = 5 * 6e4;
662
+ var MAX_CONSECUTIVE_INVALID = 3;
663
+ var REJECTED_FALLBACK = "\u8FD9\u53F0\u7535\u8111\u5DF2\u7ED1\u5B9A\u5176\u4ED6\u8D26\u53F7\u3002\u8BF7\u5728\u8FD9\u53F0\u7535\u8111\u4E0A\u6267\u884C deskbuddy logout \u540E\u91CD\u65B0 deskbuddy setup\u3002";
664
+ var SetupError = class extends Error {
665
+ };
666
+ async function pollForBinding(authCode, opts, deps) {
667
+ const intervalMs = (opts.intervalSec || DEFAULT_INTERVAL_SEC) * 1e3;
668
+ const timeoutMs = opts.timeoutMs ?? SETUP_TIMEOUT_MS;
669
+ const maxInvalid = opts.maxConsecutiveInvalid ?? MAX_CONSECUTIVE_INVALID;
670
+ const deadline = deps.now() + timeoutMs;
671
+ let consecutiveInvalid = 0;
672
+ while (deps.now() < deadline) {
673
+ try {
674
+ const res = await deps.authToken(authCode);
675
+ consecutiveInvalid = 0;
676
+ if (res?.status === "ok") return;
677
+ if (res?.status === "rejected") {
678
+ const msg = typeof res.message === "string" ? res.message.trim() : "";
679
+ throw new SetupError(msg || REJECTED_FALLBACK);
680
+ }
681
+ } catch (e) {
682
+ if (e instanceof SetupError) throw e;
683
+ if (e instanceof ApiError && String(e.code) === AUTH_CODE_INVALID) {
684
+ consecutiveInvalid += 1;
685
+ if (consecutiveInvalid >= maxInvalid) {
686
+ throw new SetupError(
687
+ "\u6388\u6743\u7801\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C deskbuddy setup\u3002\n\u82E5\u53CD\u590D\u5931\u8D25\uFF1A\u8FD9\u53F0\u7535\u8111\u53EF\u80FD\u5DF2\u7ED1\u5B9A\u5176\u4ED6\u8D26\u53F7\uFF0C\u5148\u6267\u884C deskbuddy logout \u518D\u91CD\u65B0 setup\u3002"
688
+ );
689
+ }
690
+ }
691
+ }
692
+ await deps.sleep(intervalMs);
693
+ }
694
+ throw new SetupError("\u7B49\u5F85\u6388\u6743\u8D85\u65F6\uFF085 \u5206\u949F\uFF09\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C deskbuddy setup");
695
+ }
696
+ function markBound(now = Date.now()) {
697
+ const cfg = loadConfig();
698
+ cfg.boundAt = now;
699
+ cfg.boundApiBase = cfg.apiBase;
700
+ saveConfig(cfg);
701
+ }
702
+ function applyApiBase(raw) {
703
+ let url;
704
+ try {
705
+ url = new URL(raw);
706
+ } catch {
707
+ throw new SetupError(`--api-base \u4E0D\u662F\u5408\u6CD5\u7684 URL\uFF1A${raw}`);
708
+ }
709
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
710
+ throw new SetupError(`--api-base \u53EA\u652F\u6301 http/https\uFF0C\u6536\u5230\uFF1A${url.protocol}`);
711
+ }
712
+ const normalized = url.toString().replace(/\/+$/, "");
713
+ const cfg = loadConfig();
714
+ cfg.apiBase = normalized;
715
+ saveConfig(cfg);
716
+ return normalized;
717
+ }
718
+ function parseApiBase(argv) {
719
+ const i = argv.indexOf("--api-base");
720
+ if (i === -1) return void 0;
721
+ const value = argv[i + 1];
722
+ if (!value || value.startsWith("-")) {
723
+ throw new Error("--api-base \u540E\u9762\u8981\u8DDF\u7F51\u5173\u5730\u5740\uFF0C\u4F8B\u5982 --api-base https://test-api.shifengintelli.com");
724
+ }
725
+ return value;
726
+ }
727
+ function sleep(ms) {
728
+ return new Promise((r) => setTimeout(r, ms));
729
+ }
730
+ function stdout4(line) {
731
+ process.stdout.write(line + "\n");
732
+ }
733
+ async function runSetup(opts = {}) {
734
+ const out = opts.out ?? stdout4;
735
+ const clientId = ensureClientId();
736
+ if (opts.apiBase) applyApiBase(opts.apiBase);
737
+ const cfg = loadConfig();
738
+ out(`\u670D\u52A1\u7AEF\uFF1A${cfg.apiBase}\uFF08${environmentLabel(cfg.apiBase)}\uFF09`);
739
+ out("");
740
+ const { authCode, interval, qrContent } = await api.authCode(clientId, cfg.hostName);
741
+ out("\u7528 DeskBuddy App \u626B\u63CF\u4E0B\u9762\u7684\u4E8C\u7EF4\u7801\u5B8C\u6210\u6388\u6743\uFF1A");
742
+ out("");
743
+ qrcode.generate(qrContent || authCode, { small: true }, (qr) => out(qr));
744
+ out(`\u626B\u4E0D\u51FA\u6765\u5C31\u5728 App \u91CC\u624B\u52A8\u8F93\u5165\u6388\u6743\u7801\uFF1A${authCode}`);
745
+ out("");
746
+ out("\u7B49\u5F85\u6388\u6743\u4E2D\u2026\uFF085 \u5206\u949F\u5185\u6709\u6548\uFF0CCtrl+C \u53EF\u4E2D\u6B62\uFF09");
747
+ await pollForBinding(
748
+ authCode,
749
+ { intervalSec: interval },
750
+ { authToken: (code) => api.authToken(code), sleep, now: () => Date.now() }
751
+ );
752
+ markBound();
753
+ await refreshRegistry();
754
+ out("");
755
+ out("\u2705 \u6388\u6743\u6210\u529F\u3002\u63A5\u4E0B\u6765\u8FD0\u884C\uFF1Adeskbuddy enable claude_code,codex");
756
+ return 0;
757
+ }
758
+
759
+ // src/core/state.ts
760
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readdirSync, readFileSync as readFileSync8, renameSync as renameSync4, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
761
+ import { join as join5 } from "path";
762
+
763
+ // src/core/process.ts
764
+ import { execFileSync as execFileSync2 } from "child_process";
765
+ function processName(pid) {
766
+ try {
767
+ const out = execFileSync2("ps", ["-p", String(pid), "-o", "comm="], {
768
+ encoding: "utf8",
769
+ stdio: ["ignore", "pipe", "ignore"]
770
+ });
771
+ const name = out.trim();
772
+ return name === "" ? null : name;
773
+ } catch {
774
+ return null;
775
+ }
776
+ }
777
+ function isAlive(pid, expectName) {
778
+ if (!Number.isFinite(pid) || pid <= 0) return false;
779
+ try {
780
+ process.kill(pid, 0);
781
+ } catch {
782
+ return false;
783
+ }
784
+ if (!expectName) return true;
785
+ const name = processName(pid);
786
+ return name != null && name.toLowerCase().includes(expectName.toLowerCase());
787
+ }
788
+
789
+ // src/core/state.ts
790
+ function stateDir() {
791
+ return join5(deskbuddyHome(), "state");
792
+ }
793
+ function statePath(sessionId) {
794
+ const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
795
+ return join5(stateDir(), `${safe || "unknown"}.json`);
796
+ }
797
+ function removeState(sessionId) {
798
+ try {
799
+ rmSync2(statePath(sessionId), { force: true });
800
+ } catch {
801
+ }
802
+ }
803
+ function readAliveStates(tool) {
804
+ if (!existsSync7(stateDir())) return [];
805
+ const alive = [];
806
+ for (const file of readdirSync(stateDir())) {
807
+ if (!file.endsWith(".json")) continue;
808
+ let s;
809
+ try {
810
+ s = JSON.parse(readFileSync8(join5(stateDir(), file), "utf8"));
811
+ } catch {
812
+ rmSync2(join5(stateDir(), file), { force: true });
813
+ continue;
814
+ }
815
+ if (s.tool !== tool) continue;
816
+ if (s.pid && !isAlive(s.pid, s.pidName)) {
817
+ removeState(s.sessionId);
818
+ continue;
819
+ }
820
+ alive.push(s);
821
+ }
822
+ return alive;
823
+ }
824
+
825
+ // src/commands/status.ts
826
+ function fmtTime(ms) {
827
+ const d = new Date(ms);
828
+ const p = (n) => String(n).padStart(2, "0");
829
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
830
+ }
831
+ function stdout5(line) {
832
+ process.stdout.write(line + "\n");
833
+ }
834
+ var STATE_LABELS = {
835
+ watch: "\u8FDB\u884C\u4E2D",
836
+ ask_approval: "\u5F85\u786E\u8BA4",
837
+ done: "\u5DF2\u5B8C\u6210",
838
+ failed: "\u5931\u8D25"
839
+ };
840
+ function relativeTime(from, now) {
841
+ const sec = Math.max(0, Math.round((now - from) / 1e3));
842
+ if (sec < 60) return `${sec} \u79D2\u524D`;
843
+ const min = Math.round(sec / 60);
844
+ if (min < 60) return `${min} \u5206\u949F\u524D`;
845
+ const hour = Math.round(min / 60);
846
+ if (hour < 24) return `${hour} \u5C0F\u65F6\u524D`;
847
+ return `${Math.round(hour / 24)} \u5929\u524D`;
848
+ }
849
+ function pad(s, width) {
850
+ const w = [...s].reduce((n, ch) => n + (ch.charCodeAt(0) > 11904 ? 2 : 1), 0);
851
+ return s + " ".repeat(Math.max(1, width - w));
852
+ }
853
+ function formatStatus(input) {
854
+ const lines = [];
855
+ const cfg = input.config;
856
+ lines.push("DeskBuddy \u72B6\u6001");
857
+ const account = cfg.boundAt ? `\u5DF2\u6388\u6743\uFF08clientId ${cfg.clientId.slice(0, 4)}\u2026\uFF0C\u7ED1\u5B9A\u4E8E ${fmtTime(cfg.boundAt)}\uFF09` : "\u672A\u6388\u6743 \u2014\u2014 \u8FD0\u884C deskbuddy setup \u626B\u7801\u6388\u6743";
858
+ lines.push(` ${pad("\u8D26\u53F7", 10)}${account}`);
859
+ lines.push(` ${pad("\u670D\u52A1\u7AEF", 10)}${cfg.apiBase}\uFF08${environmentLabel(cfg.apiBase)}\uFF09`);
860
+ if (cfg.boundAt && cfg.boundApiBase && cfg.boundApiBase !== cfg.apiBase) {
861
+ lines.push(
862
+ ` \u26A0\uFE0F \u6388\u6743\u65F6\u8FDE\u7684\u662F ${cfg.boundApiBase}\uFF08${environmentLabel(cfg.boundApiBase)}\uFF09\uFF0C\u4E0E\u5F53\u524D\u4E0D\u4E00\u81F4 \u2014\u2014 \u5F53\u524D\u73AF\u5883\u5E76\u4E0D\u8BA4\u8BC6\u8FD9\u53F0\u673A\u5668\uFF0C\u9700\u8981\u91CD\u65B0 deskbuddy setup`
863
+ );
864
+ }
865
+ if (input.envOverride?.active) {
866
+ lines.push(
867
+ ` \u26A0\uFE0F \u73AF\u5883\u53D8\u91CF DESKBUDDY_API_BASE=${input.envOverride.envValue} \u6B63\u5728\u8986\u76D6 config.json \u91CC\u7684 ${input.envOverride.fileValue}\uFF08\u591A\u534A\u662F\u5199\u8FDB\u4E86 .zshrc \u4E4B\u7C7B\uFF09`
868
+ );
869
+ }
870
+ if (input.tools.length === 0) {
871
+ lines.push(` ${pad("\u5DF2\u542F\u7528", 10)}\u65E0 \u2014\u2014 \u8FD0\u884C deskbuddy enable claude_code,codex`);
872
+ } else {
873
+ const desc = input.tools.map((t) => `${t.tool}\uFF08${t.installed ? "hook \u5DF2\u88C5" : "\u26A0\uFE0F hook \u672A\u88C5\uFF0C\u91CD\u8DD1 deskbuddy enable"}\uFF09`).join("\u3001");
874
+ lines.push(` ${pad("\u5DF2\u542F\u7528", 10)}${desc}`);
875
+ }
876
+ const versionNote = input.hookVersion === null ? "\u672A\u5B89\u88C5" : input.hookVersion === input.currentVersion ? `${input.hookVersion}\uFF08\u4E0E\u5F53\u524D\u5305\u4E00\u81F4\uFF09` : `${input.hookVersion} \u26A0\uFE0F \u5F53\u524D\u5305\u662F ${input.currentVersion}\uFF0C\u4E0B\u6B21\u8FD0\u884C\u4EFB\u610F deskbuddy \u547D\u4EE4\u4F1A\u81EA\u52A8\u6821\u51C6`;
877
+ lines.push(` ${pad("hook \u7248\u672C", 10)}${versionNote}`);
878
+ for (const t of input.tools) {
879
+ if (t.notice) lines.push(` ${t.notice}`);
880
+ }
881
+ lines.push("");
882
+ const sorted = [...input.sessions].sort((a, b) => b.updatedAt - a.updatedAt);
883
+ lines.push(`\u5F53\u524D\u672C\u5730\u4F1A\u8BDD\uFF08${sorted.length}\uFF09`);
884
+ if (sorted.length === 0) {
885
+ lines.push(" \u6682\u65E0 \u2014\u2014 \u667A\u80FD\u4F53\u8DD1\u8D77\u6765\u4E4B\u540E\u8FD9\u91CC\u5C31\u4F1A\u6709\u5185\u5BB9");
886
+ }
887
+ for (const s of sorted) {
888
+ lines.push(
889
+ ` ${pad(s.tool, 14)}${pad(s.title || s.project || "-", 24)}${pad(STATE_LABELS[s.state] ?? s.state, 8)}${relativeTime(s.updatedAt, input.now)}`
890
+ );
891
+ }
892
+ lines.push("");
893
+ lines.push("\u91C7\u96C6\u8303\u56F4\uFF1A\u53EA\u4E0A\u62A5 \u5DE5\u5177\u540D/\u72B6\u6001/\u4F1A\u8BDD\u6807\u9898/\u9879\u76EE\u76EE\u5F55\u540D/\u65F6\u95F4\u6233");
894
+ lines.push("\u4E0D\u4E0A\u62A5\uFF1Aprompt \u539F\u6587\u3001\u547D\u4EE4\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u4EE3\u7801\u5185\u5BB9");
895
+ return lines.join("\n");
896
+ }
897
+ function collectStatus(now = Date.now()) {
898
+ const config = loadConfig();
899
+ const registry = loadRegistry();
900
+ const hookPath = installedHookPath();
901
+ const tools = [];
902
+ const sessions = [];
903
+ for (const tool of config.enabledTools) {
904
+ const adapter = adapterFor(registry, tool);
905
+ if (!adapter) continue;
906
+ const configPath2 = adapter.configPath();
907
+ const installed = adapter.isInstalled(configPath2, hookPath);
908
+ tools.push({
909
+ tool,
910
+ configPath: configPath2,
911
+ installed,
912
+ notice: installed ? adapter.notice : void 0
913
+ });
914
+ sessions.push(...readAliveStates(tool));
915
+ }
916
+ return {
917
+ config,
918
+ hookVersion: installedHookVersion(),
919
+ currentVersion: packageVersion(),
920
+ tools,
921
+ sessions,
922
+ now,
923
+ envOverride: envOverride()
924
+ };
925
+ }
926
+ function runStatus(opts = {}) {
927
+ const out = opts.out ?? stdout5;
928
+ out(formatStatus(collectStatus()));
929
+ return 0;
930
+ }
931
+
932
+ // src/cli.ts
933
+ var USAGE = `deskbuddy \u2014\u2014 \u628A AI \u7F16\u7801\u667A\u80FD\u4F53\u6BCF\u4E2A\u4F1A\u8BDD\u7A97\u53E3\u7684\u72B6\u6001\u540C\u6B65\u5230\u684C\u9762\u786C\u4EF6\u5C4F\u5E55
934
+
935
+ \u7528\u6CD5:
936
+ deskbuddy setup [--api-base <url>] \u626B\u7801\u6388\u6743\uFF08--api-base \u6307\u5B9A\u7F51\u5173\u5E76\u8BB0\u4F4F\uFF0C\u9ED8\u8BA4\u751F\u4EA7\uFF09
937
+ deskbuddy enable <tool[,tool]> \u5B89\u88C5\u91C7\u96C6 hook\uFF08\u5982 claude_code,codex\uFF09
938
+ deskbuddy disable [tool[,tool]] \u79FB\u9664\u91C7\u96C6 hook\uFF08\u4E0D\u5E26\u53C2\u6570=\u5168\u90E8\uFF09
939
+ deskbuddy status \u770B\u91C7\u96C6\u5230\u4E86\u4EC0\u4E48\u3001\u4E0A\u62A5\u4E86\u54EA\u4E9B\u5B57\u6BB5
940
+ deskbuddy logout [--yes] \u79FB\u9664 hook \u5E76\u6E05\u9664\u672C\u673A\u6570\u636E
941
+
942
+ \u88C5\u5B8C\u6CA1\u6709\u4EFB\u4F55\u5E38\u9A7B\u8FDB\u7A0B\uFF1A\u53EA\u662F\u5F80\u5404\u667A\u80FD\u4F53\u7684 hook \u914D\u7F6E\u91CC\u52A0\u4E86\u4E00\u6761\u8BB0\u5F55\u3002
943
+ \u5F53\u524D\u8FDE\u7684\u662F\u54EA\u4E2A\u73AF\u5883\uFF0C\u7528 deskbuddy status \u770B\u7B2C\u4E8C\u884C\u3002
944
+ `;
945
+ async function main(argv) {
946
+ const cmd = argv[2];
947
+ const arg = argv[3];
948
+ if (cmd && !["-h", "--help", "help", "-v", "--version"].includes(cmd)) {
949
+ try {
950
+ syncHookFile();
951
+ } catch {
952
+ }
953
+ }
954
+ switch (cmd) {
955
+ case void 0:
956
+ case "-h":
957
+ case "--help":
958
+ case "help":
959
+ process.stdout.write(USAGE);
960
+ return 0;
961
+ case "-v":
962
+ case "--version":
963
+ process.stdout.write(packageVersion() + "\n");
964
+ return 0;
965
+ case "setup":
966
+ return runSetup({ apiBase: parseApiBase(argv) });
967
+ case "enable":
968
+ return runEnable(parseTools(arg));
969
+ case "disable":
970
+ return runDisable(parseTools(arg));
971
+ case "status":
972
+ return runStatus();
973
+ case "logout":
974
+ return runLogout({ yes: argv.includes("--yes") || argv.includes("-y") });
975
+ default:
976
+ process.stderr.write(`\u672A\u77E5\u547D\u4EE4\uFF1A${cmd}
977
+
978
+ ${USAGE}`);
979
+ return 1;
980
+ }
981
+ }
982
+ main(process.argv).then((code) => process.exit(code)).catch((e) => {
983
+ process.stderr.write(`\u2717 ${e.message}
984
+ `);
985
+ process.exit(1);
986
+ });
987
+ export {
988
+ main
989
+ };