cortico-world-workbuddy 0.3.0 → 0.3.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/webui.js CHANGED
@@ -21,7 +21,6 @@ var TEXT = {
21
21
  source: "\u6765\u6E90",
22
22
  token: "Token\uFF08\u63A9\u7801\uFF09",
23
23
  account: "\u8D26\u53F7",
24
- region: "\u533A\u57DF",
25
24
  upstream: "\u4E0A\u6E38",
26
25
  rediscover: "\u91CD\u65B0\u68C0\u6D4B\u672C\u673A\u767B\u5F55",
27
26
  pasteToken: "\u624B\u52A8\u7C98\u8D34 access token",
@@ -34,27 +33,25 @@ var TEXT = {
34
33
  lastError: "\u6700\u8FD1\u9519\u8BEF",
35
34
  models: "\u4E2A\u6A21\u578B",
36
35
  currentModel: "\u5F53\u524D\u6A21\u578B",
37
- priorityTitle: "\u4E2D\u8F6C\u7AD9\u4F18\u5148",
38
- priorityLabel: "\u662F\u5426\u542F\u7528Workbuddy",
39
- priorityDesc: "\u6253\u5F00\u540E\u684C\u5BA0\u6BCF\u8F6E\u5BF9\u8BDD\u4F18\u5148\u8C03\u7528 WorkBuddy \u7684\u6A21\u578B\uFF0C\u5173\u95ED\u5219\u5B8C\u5168\u8DF3\u8FC7 WorkBuddy\u3002\u9700\u8981 WorkBuddy \u684C\u9762\u7AEF\u4E3B\u8FDB\u7A0B\u5728\u8FD0\u884C\u3002",
40
36
  procRunning: "WorkBuddy \u4E3B\u8FDB\u7A0B\uFF1A\u8FD0\u884C\u4E2D",
41
37
  procStopped: "WorkBuddy \u4E3B\u8FDB\u7A0B\uFF1A\u672A\u68C0\u6D4B\u5230",
42
- noProcess: "\u672A\u68C0\u6D4B\u5230Workbuddy",
43
- enabledOn: "\u5DF2\u5F00\u542F\uFF1A\u684C\u5BA0\u4F18\u5148\u8D70 WorkBuddy \u6A21\u578B",
44
- enabledOff: "\u5DF2\u5173\u95ED\uFF1A\u684C\u5BA0\u8DF3\u8FC7 WorkBuddy",
45
- usageTitle: "\u7528\u91CF",
46
- usageDesc: "\u672C\u673A\u8BB0\u8D26\u7684\u8C03\u7528\u7528\u91CF\u4E0E WorkBuddy \u79EF\u5206\u5305\u4F59\u989D\u3002\u79EF\u5206\u53D6\u4E0A\u6E38\u6BCF\u6B21\u8C03\u7528\u56DE\u4F20\u7684\u771F\u5B9E\u6263\u8D39\uFF08\u53EF\u80FD\u4E3A 0\uFF09\uFF0C\u500D\u7387\u6309\u76EE\u5F55 + \u9650\u65F6\u4F18\u60E0\u5B9E\u65F6\u8BA1\u7B97\u3002",
38
+ usageTitle: "\u7528\u91CF\u4E0E\u6210\u672C",
39
+ usageDesc: "\u7ECF\u7531 WorkBuddy \u6D88\u8017\u7684 token \u4E0E\u79EF\u5206\uFF08\u672C\u5730\u8BB0\u8D26\uFF09\uFF1A\u79EF\u5206\u53D6\u4E0A\u6E38\u6BCF\u6B21\u8C03\u7528\u56DE\u4F20\u7684\u771F\u5B9E\u6263\u8D39\uFF08\u53EF\u80FD\u4E3A 0\uFF09\uFF0Ctoken \u53D6\u540C\u4E00\u6BB5 SSE \u7684 usage\uFF1B\u500D\u7387\u6309\u76EE\u5F55 + \u9650\u65F6\u4F18\u60E0\u5B9E\u65F6\u8BA1\u7B97\u3002",
47
40
  usageRefresh: "\u5237\u65B0\u4F59\u989D",
48
41
  usageClear: "\u6E05\u7A7A\u672C\u5730\u8D26\u672C",
49
42
  usageCleared: "\u672C\u5730\u8D26\u672C\u5DF2\u6E05\u7A7A",
50
43
  metricCalls: "\u8C03\u7528",
44
+ metricFailed: "\u5931\u8D25",
51
45
  metricCredit: "\u79EF\u5206",
52
46
  metricInput: "\u8F93\u5165 tok",
53
47
  metricOutput: "\u8F93\u51FA tok",
48
+ metricCached: "\u7F13\u5B58\u547D\u4E2D",
49
+ metricTotal: "\u5171 tok",
54
50
  spanToday: "\u4ECA\u5929",
55
51
  spanWeek: "\u8FD1 7 \u5929",
56
52
  spanMonth: "\u8FD1 30 \u5929",
57
- byModel: "\u6309\u6A21\u578B",
53
+ spanAll: "\u5168\u90E8",
54
+ byModel: "\u6309\u6A21\u578B\uFF08\u8FD1 30 \u5929\uFF09",
58
55
  balanceTitle: "\u79EF\u5206\u5305\u4F59\u989D",
59
56
  colPkgName: "\u79EF\u5206\u5305",
60
57
  colPkgRemain: "\u5269\u4F59",
@@ -100,7 +97,6 @@ var TEXT = {
100
97
  source: "Source",
101
98
  token: "Token (masked)",
102
99
  account: "Account",
103
- region: "Region",
104
100
  upstream: "Upstream",
105
101
  rediscover: "Re-detect local sign-in",
106
102
  pasteToken: "Paste an access token manually",
@@ -113,27 +109,25 @@ var TEXT = {
113
109
  lastError: "Last error",
114
110
  models: "models",
115
111
  currentModel: "Current model",
116
- priorityTitle: "Relay first",
117
- priorityLabel: "Enable Workbuddy",
118
- priorityDesc: "Same state as the \u201CRelay first\u201D button on the Home page: when on, the pet asks a WorkBuddy model first every turn; when off, WorkBuddy is skipped entirely. Requires the WorkBuddy desktop main process.",
119
112
  procRunning: "WorkBuddy process: running",
120
113
  procStopped: "WorkBuddy process: not detected",
121
- noProcess: "Workbuddy not detected",
122
- enabledOn: "On: the pet asks WorkBuddy first",
123
- enabledOff: "Off: the pet skips WorkBuddy",
124
- usageTitle: "Usage",
125
- usageDesc: "Locally recorded call usage plus the WorkBuddy credit balance. Credits come from the per-call figure the upstream returns (may be 0); multipliers combine the catalog with time-limited offers in real time.",
114
+ usageTitle: "Usage & cost",
115
+ usageDesc: "Tokens and credits spent through WorkBuddy (locally recorded): credits are the per-call figure the upstream returns (may be 0), tokens come from the same SSE usage block; multipliers combine the catalog with time-limited offers in real time.",
126
116
  usageRefresh: "Refresh balance",
127
117
  usageClear: "Clear local ledger",
128
118
  usageCleared: "Local ledger cleared",
129
119
  metricCalls: "Calls",
120
+ metricFailed: "Failed",
130
121
  metricCredit: "Credits",
131
122
  metricInput: "Input tok",
132
123
  metricOutput: "Output tok",
124
+ metricCached: "Cached",
125
+ metricTotal: "Total tok",
133
126
  spanToday: "Today",
134
127
  spanWeek: "Last 7 days",
135
128
  spanMonth: "Last 30 days",
136
- byModel: "By model",
129
+ spanAll: "All",
130
+ byModel: "By model (last 30 days)",
137
131
  balanceTitle: "Credit packages",
138
132
  colPkgName: "Package",
139
133
  colPkgRemain: "Remaining",
@@ -196,9 +190,10 @@ function mountModels(ctx) {
196
190
  const head = ui.rowbar();
197
191
  const refreshBtn = ui.button(s.refresh, { variant: "primary" });
198
192
  const stateChip = ui.chip("", "plain");
193
+ const procChip = ui.chip("", "plain");
199
194
  const currentChip = ui.chip("", "accent");
200
195
  const allBox = ui.checkbox(s.showAllModels, { checked: false });
201
- head.append(refreshBtn, stateChip, currentChip, allBox.el);
196
+ head.append(refreshBtn, stateChip, procChip, currentChip, allBox.el);
202
197
  sheet.body.append(head);
203
198
  const errLine = ui.msgline();
204
199
  sheet.body.append(errLine);
@@ -213,6 +208,7 @@ function mountModels(ctx) {
213
208
  const safeCount = models.filter((m) => m.safety?.allow).length;
214
209
  stateChip.textContent = `${s.lastRefresh}: ${fmtTs(state?.catalogAt, s)} \xB7 ${safeCount}/${models.length} ${s.models} \xB7 ${state?.safeOnly === false ? s.safeOnlyOff : s.safeOnlyOn}`;
215
210
  currentChip.textContent = `${s.currentModel}: ${state?.model || "\u2014"}`;
211
+ procChip.textContent = state?.running ? s.procRunning : s.procStopped;
216
212
  }
217
213
  function pickable(m) {
218
214
  if (state?.safeOnly === false) return true;
@@ -352,7 +348,6 @@ function mountAccount(ctx) {
352
348
  { k: s.source, v: state?.tokenSource ?? "\u2014" },
353
349
  { k: s.token, v: state?.tokenMask ?? "\u2014" },
354
350
  { k: s.account, v: state?.account?.name ?? "\u2014" },
355
- { k: s.region, v: state?.region ?? "\u2014" },
356
351
  { k: s.upstream, v: state?.baseUrl ?? "\u2014" }
357
352
  ];
358
353
  if (state?.lastError) rows.push({ k: s.lastError, v: state.lastError });
@@ -415,53 +410,14 @@ function mountAccount(ctx) {
415
410
  errLine.textContent = String(err?.message ?? err);
416
411
  });
417
412
  }
418
- function mountPriority(ctx) {
419
- const s = strings(ctx);
420
- const { ui } = ctx;
421
- const sheet = ui.sheet({ title: s.priorityTitle, desc: s.priorityDesc });
422
- ctx.root.append(sheet.el);
423
- const statusLine = ui.msgline();
424
- function apply(r) {
425
- if (!r) return;
426
- box.setChecked(r.prefer === true);
427
- statusLine.textContent = r.running ? s.procRunning : s.procStopped;
428
- }
429
- const box = ui.checkbox(s.priorityLabel, {
430
- checked: false,
431
- title: s.priorityDesc,
432
- onChange: async (checked) => {
433
- try {
434
- const r = await ctx.invoke("set", [checked]);
435
- if (r?.ok) {
436
- apply(r);
437
- ui.toast(checked ? s.enabledOn : s.enabledOff);
438
- } else {
439
- apply(r);
440
- box.setChecked(false);
441
- ui.toast(r?.error ?? s.noProcess, "bad");
442
- }
443
- } catch (err) {
444
- box.setChecked(false);
445
- ui.toast(String(err?.message ?? err), "bad");
446
- }
447
- }
448
- });
449
- sheet.body.append(box.el, statusLine);
450
- async function load() {
451
- apply(await ctx.invoke("state"));
452
- }
453
- ctx.stream({
454
- message: () => load().catch(() => {
455
- })
456
- });
457
- load().catch((err) => {
458
- statusLine.textContent = String(err?.message ?? err);
459
- });
460
- }
461
413
  function fmtNum(n) {
462
414
  if (typeof n !== "number" || !Number.isFinite(n)) return "\u2014";
463
415
  return String(Math.round(n * 100) / 100);
464
416
  }
417
+ function fmtCredit(bucket) {
418
+ const known = fmtNum(bucket?.credit);
419
+ return (bucket?.creditUnknown ?? 0) > 0 ? `${known} + ?` : known;
420
+ }
465
421
  function mountUsage(ctx) {
466
422
  const s = strings(ctx);
467
423
  const { ui } = ctx;
@@ -477,13 +433,22 @@ function mountUsage(ctx) {
477
433
  const errLine = ui.msgline();
478
434
  sheet.body.append(errLine);
479
435
  const overview = ui.table({
480
- head: ["", s.metricCalls, s.metricCredit, s.metricInput, s.metricOutput],
436
+ head: [
437
+ "",
438
+ s.metricCalls,
439
+ s.metricFailed,
440
+ s.metricCredit,
441
+ s.metricInput,
442
+ s.metricOutput,
443
+ s.metricCached,
444
+ s.metricTotal
445
+ ],
481
446
  maxHeight: "220px"
482
447
  });
483
448
  sheet.body.append(overview.el);
484
449
  const modelSection = ui.section(s.byModel);
485
450
  const modelTable = ui.table({
486
- head: [s.colModel, s.metricCalls, s.metricCredit, s.metricInput, s.metricOutput],
451
+ head: [s.colModel, s.metricCalls, s.metricFailed, s.metricCredit, s.metricInput, s.metricOutput, s.metricTotal],
487
452
  maxHeight: "260px"
488
453
  });
489
454
  modelSection.append(modelTable.el);
@@ -509,21 +474,43 @@ function mountUsage(ctx) {
509
474
  let state = null;
510
475
  function spanRow(label, bucket) {
511
476
  const b = bucket ?? {};
512
- return [label, String(b.calls ?? 0), fmtNum(b.credit), fmtNum(b.prompt), fmtNum(b.completion)];
477
+ return [
478
+ label,
479
+ String(b.calls ?? 0),
480
+ String(b.failed ?? 0),
481
+ fmtCredit(b),
482
+ fmtNum(b.prompt),
483
+ fmtNum(b.completion),
484
+ fmtNum(b.cached),
485
+ fmtNum(b.total)
486
+ ];
513
487
  }
514
488
  function render() {
515
489
  const totals = state?.summary?.totals ?? {};
516
- todayChip.textContent = `${s.todayCredit}: ${fmtNum(totals.today?.credit)} \xB7 ${s.todayTokens}: ${fmtNum(
490
+ todayChip.textContent = `${s.todayCredit}: ${fmtCredit(totals.today)} \xB7 ${s.todayTokens}: ${fmtNum(
517
491
  (totals.today?.prompt ?? 0) + (totals.today?.completion ?? 0)
518
492
  )}`;
519
493
  balanceChip.textContent = `${s.balanceTitle}: ${fmtNum(state?.balance?.total)}`;
520
- const spans = [spanRow(s.spanToday, totals.today), spanRow(s.spanWeek, totals.week), spanRow(s.spanMonth, totals.month)];
494
+ const spans = [
495
+ spanRow(s.spanToday, totals.today),
496
+ spanRow(s.spanWeek, totals.week),
497
+ spanRow(s.spanMonth, totals.month),
498
+ spanRow(s.spanAll, totals.all)
499
+ ];
521
500
  overview.clear();
522
501
  for (const row of spans) overview.addRow(row);
523
502
  const byModel = state?.summary?.byModel ?? [];
524
503
  modelTable.clear(byModel.length > 0 ? void 0 : s.noData);
525
504
  for (const m of byModel) {
526
- modelTable.addRow([m.model, String(m.calls ?? 0), fmtNum(m.credit), fmtNum(m.prompt), fmtNum(m.completion)]);
505
+ modelTable.addRow([
506
+ m.model,
507
+ String(m.calls ?? 0),
508
+ String(m.failed ?? 0),
509
+ fmtCredit(m),
510
+ fmtNum(m.prompt),
511
+ fmtNum(m.completion),
512
+ fmtNum(m.total)
513
+ ]);
527
514
  }
528
515
  const packages = state?.balance?.packages ?? [];
529
516
  pkgTable.clear(packages.length > 0 ? void 0 : s.noData);
@@ -620,7 +607,6 @@ var client_default = {
620
607
  panels: {
621
608
  models: { mount: mountModels },
622
609
  account: { mount: mountAccount },
623
- priority: { mount: mountPriority },
624
610
  usage: { mount: mountUsage }
625
611
  }
626
612
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-workbuddy",
3
- "version": "0.3.0",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "description": "把 Windows 端已登录的 WorkBuddy 客户端模型桥接进 Coopanion:读取本机登录态,列出模型目录与默认积分倍率,供 Coo 调用。",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "locales": {
34
34
  "zh": {
35
35
  "displayName": "WorkBuddy 桥接",
36
- "description": "把本机已登录的 WorkBuddy 客户端(国内版 / 国际版)模型接入 Coopanion,面板中可选模型并显示其默认积分倍率。"
36
+ "description": "把本机已登录的 WorkBuddy 客户端模型接入 Coopanion,面板中可选模型并显示其默认积分倍率。"
37
37
  },
38
38
  "en": {
39
39
  "displayName": "WorkBuddy Bridge",
@@ -78,6 +78,6 @@
78
78
  "win32"
79
79
  ],
80
80
  "devDependencies": {
81
- "esbuild": "^0.24.2"
81
+ "esbuild": "0.28.2"
82
82
  }
83
83
  }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 一次性迁移工具:清理 0.2.x 主页注入留在宿主 `build/console` 里的三样东西。
4
+ *
5
+ * 0.3.0 起扩展本体**不再改写宿主任何文件**,所以这个脚本不由扩展自动执行:
6
+ * 装过 0.2.x 的用户主动跑一次即可。它只删本扩展自己留下的东西:
7
+ * - `index.html` 里引用 `workbuddy-home.js` 的那一行 `<script>`
8
+ * - `workbuddy-home.js`
9
+ * - `index.html.workbuddy-backup`
10
+ * 绝不用备份覆盖 `index.html`(避免把宿主升级过的文件降级),也不碰其余内容。
11
+ *
12
+ * 用法:
13
+ * node scripts/clean-legacy-injection.mjs --check # 只列出会动的项,不改文件
14
+ * node scripts/clean-legacy-injection.mjs --clean # 执行清理(幂等)
15
+ * COOPANION_APP_ROOT=<...>/resources/app # 显式指定宿主位置
16
+ */
17
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { findAppRoot } from './lib/app-root.mjs';
21
+
22
+ const pkgDir = fileURLToPath(new URL('..', import.meta.url));
23
+ const checkOnly = process.argv.includes('--check');
24
+ const doClean = process.argv.includes('--clean');
25
+ if (checkOnly === doClean) {
26
+ console.error('用法:node scripts/clean-legacy-injection.mjs --check | --clean(二选一)');
27
+ process.exit(2);
28
+ }
29
+
30
+ const appRoot = process.env.COOPANION_APP_ROOT || findAppRoot({ packageDir: pkgDir });
31
+ if (!appRoot) {
32
+ console.error('✗ 找不到 Coopanion 的 resources/app(可设 COOPANION_APP_ROOT 指定)');
33
+ process.exit(2);
34
+ }
35
+ const consoleDir = join(appRoot, 'build', 'console');
36
+ const htmlPath = join(consoleDir, 'index.html');
37
+ if (!existsSync(htmlPath)) {
38
+ console.error(`✗ 找不到 ${htmlPath}`);
39
+ process.exit(2);
40
+ }
41
+
42
+ /** 只认「引用了 workbuddy-home.js 的 script 行」,其他一行都不动。 */
43
+ const INJECT_LINE = /<script[^>]*workbuddy-home\.js/i;
44
+
45
+ const text = readFileSync(htmlPath, 'utf8');
46
+ const newline = text.includes('\r\n') ? '\r\n' : '\n';
47
+ const lines = text.split(/\r?\n/);
48
+ const kept = lines.filter((line) => !INJECT_LINE.test(line));
49
+ const removedLines = lines.length - kept.length;
50
+
51
+ const files = [
52
+ join(consoleDir, 'workbuddy-home.js'),
53
+ join(consoleDir, 'index.html.workbuddy-backup'),
54
+ ].filter((p) => existsSync(p));
55
+
56
+ console.log(`宿主:${consoleDir}`);
57
+ console.log(`index.html 注入引用行:${removedLines} 行`);
58
+ for (const p of files) console.log(`残留文件:${p}`);
59
+
60
+ if (removedLines === 0 && files.length === 0) {
61
+ console.log('✓ 已经是干净的,无需清理(扩展本体不会重新注入)');
62
+ process.exit(0);
63
+ }
64
+ if (checkOnly) {
65
+ console.log('(--check:以上为待清理项,未做任何改动;执行请加 --clean)');
66
+ process.exit(0);
67
+ }
68
+
69
+ if (removedLines > 0) {
70
+ writeFileSync(htmlPath, kept.join(newline), 'utf8');
71
+ console.log(`✓ 已剥掉 ${removedLines} 行注入引用(保留原有 ${newline === '\r\n' ? 'CRLF' : 'LF'} 换行风格)`);
72
+ }
73
+ for (const p of files) {
74
+ rmSync(p, { force: true });
75
+ console.log(`✓ 已删除 ${p}`);
76
+ }
77
+ console.log('✓ 清理完成;扩展本体不会重新注入,卸载也不需要还原。');
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 显式卸载清理:只清本扩展自己的配置块、三份数据文件和手动 token/哨兵。
4
+ * 宿主界面卸载只删扩展包,不会调用 World.stop();普通 stop()/重启又不能删用户数据,
5
+ * 所以清理由用户在卸载后主动执行。--check 只读,--clean 才修改,且绝不打印 token。
6
+ *
7
+ * 用法:
8
+ * node scripts/clean-workbuddy.mjs --check
9
+ * node scripts/clean-workbuddy.mjs --clean
10
+ * 隔离验证可显式传 --bot-dir <部署目录> --data-dir <数据目录>。
11
+ */
12
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
13
+ import { join, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { findAppRoot } from './lib/app-root.mjs';
16
+
17
+ export const WORKBUDDY_DATA_FILES = ['workbuddy-usage.jsonl', 'workbuddy-catalog.json', 'workbuddy-balance.json'];
18
+
19
+ function readConfig(path) {
20
+ if (!existsSync(path)) return { value: null, text: '' };
21
+ const text = readFileSync(path, 'utf8');
22
+ return { value: JSON.parse(text), text };
23
+ }
24
+
25
+ /** 返回残留位置与布尔状态,不返回任何凭据内容。 */
26
+ export function inspectWorkbuddyResidue({ botDir, dataDir }) {
27
+ const configPath = join(botDir, 'config.json');
28
+ const envPath = join(botDir, '.env');
29
+ const config = readConfig(configPath);
30
+ const env = existsSync(envPath) ? readFileSync(envPath, 'utf8') : '';
31
+ return {
32
+ botDir,
33
+ dataDir,
34
+ configPath,
35
+ envPath,
36
+ configPresent: config.value?.worlds && Object.hasOwn(config.value.worlds, 'workbuddy'),
37
+ envPresent: /^[\t ]*accessToken[\t ]*=/m.test(env),
38
+ dataFiles: WORKBUDDY_DATA_FILES.map((name) => join(dataDir, name)).filter((path) => existsSync(path)),
39
+ };
40
+ }
41
+
42
+ /** 幂等清理;返回清理前后状态供 CLI 与契约测试核对。 */
43
+ export function cleanWorkbuddyResidue({ botDir, dataDir, clean = false }) {
44
+ const before = inspectWorkbuddyResidue({ botDir, dataDir });
45
+ if (clean) {
46
+ if (before.configPresent) {
47
+ const config = readConfig(before.configPath);
48
+ delete config.value.worlds.workbuddy;
49
+ const indent = /^\{\r?\n([\t ]+)\S/.exec(config.text)?.[1] ?? ' ';
50
+ const newline = config.text.includes('\r\n') ? '\r\n' : '\n';
51
+ const trailing = config.text.endsWith(newline) ? newline : '';
52
+ writeFileSync(before.configPath, `${JSON.stringify(config.value, null, indent)}${trailing}`, 'utf8');
53
+ }
54
+ if (before.envPresent) {
55
+ const env = readFileSync(before.envPath, 'utf8');
56
+ const next = env.replace(/^[\t ]*accessToken[\t ]*=.*(?:\r?\n|$)/gm, '');
57
+ writeFileSync(before.envPath, next, 'utf8');
58
+ }
59
+ for (const path of before.dataFiles) rmSync(path, { force: true });
60
+ }
61
+ return { before, after: inspectWorkbuddyResidue({ botDir, dataDir }) };
62
+ }
63
+
64
+ function argValue(name) {
65
+ const index = process.argv.indexOf(name);
66
+ return index >= 0 ? process.argv[index + 1] : '';
67
+ }
68
+
69
+ function runCli() {
70
+ const check = process.argv.includes('--check');
71
+ const clean = process.argv.includes('--clean');
72
+ if (check === clean) {
73
+ console.error('用法:node scripts/clean-workbuddy.mjs --check | --clean(二选一)');
74
+ process.exit(2);
75
+ }
76
+ const pkgDir = fileURLToPath(new URL('..', import.meta.url));
77
+ const appRoot = process.env.COOPANION_APP_ROOT || findAppRoot({ packageDir: pkgDir });
78
+ const defaultBotDir = appRoot ? join(appRoot, '..', '..', 'data', 'home', 'companion') : '';
79
+ const botDir = resolve(argValue('--bot-dir') || defaultBotDir);
80
+ const dataDir = resolve(argValue('--data-dir') || join(defaultBotDir, 'data'));
81
+ if (!defaultBotDir && (!argValue('--bot-dir') || !argValue('--data-dir'))) {
82
+ console.error('✗ 找不到 Coopanion 部署目录;请显式传 --bot-dir 与 --data-dir');
83
+ process.exit(2);
84
+ }
85
+ try {
86
+ const result = cleanWorkbuddyResidue({ botDir, dataDir, clean });
87
+ const state = clean ? result.after : result.before;
88
+ console.log(`配置 worlds.workbuddy:${state.configPresent ? '存在' : '无'}`);
89
+ console.log(`.env accessToken:${state.envPresent ? '存在(值未读取到输出)' : '无'}`);
90
+ for (const path of state.dataFiles) console.log(`数据文件:${path}`);
91
+ if (!clean) console.log('(--check:未做任何改动;卸载扩展后执行 --clean 清理)');
92
+ const left = state.configPresent || state.envPresent || state.dataFiles.length > 0;
93
+ console.log(clean && !left ? '✓ 本扩展的配置、数据与凭据残留已清理' : left ? '发现上述本扩展残留' : '✓ 没有本扩展残留');
94
+ process.exit(clean && left ? 1 : 0);
95
+ } catch (err) {
96
+ console.error(`✗ 清理失败:${err?.message ?? err}`);
97
+ process.exit(1);
98
+ }
99
+ }
100
+
101
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) runCli();