@luchenglong/cli-bridge 0.2.5 → 0.2.6
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/CHANGELOG.md +34 -0
- package/dynamic/codebuddy.host.js +334 -65
- package/mcp/cli-bridge-mcp.mjs +1 -1
- package/package.json +2 -1
- package/providers/codebuddy-catalog.mjs +236 -63
- package/providers/codebuddy-models-tool.mjs +106 -0
- package/providers/codebuddy.mjs +40 -45
- package/scripts/build.mjs +262 -253
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,40 @@ GitHub Release(`scripts/verify.mjs` 在 CI 与 prepack 双重锁一致)。
|
|
|
9
9
|
> - CodeBuddy 线:[docs/history/codebuddy/CHANGELOG.md](docs/history/codebuddy/CHANGELOG.md)(终版 1.6.2)
|
|
10
10
|
> - MiMo 线:[docs/history/mimo/CHANGELOG.md](docs/history/mimo/CHANGELOG.md)(终版 1.2.9)
|
|
11
11
|
|
|
12
|
+
## [0.2.6] - 2026-10-18
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **`codebuddy_models` / 设置面板的模型清单「不是实时、也是错的」**(用户报告)。根因:清单
|
|
16
|
+
读的是随包冻结的 `product.ioa.json` 静态表,实测已落后于本机 CLI ——
|
|
17
|
+
**静态 16 项 vs CLI 19 项**,其中 `minimax-m3` 实为 `minimax-m3-pay`、`kimi-k3-1` 实为
|
|
18
|
+
`kimi-k3-2`(两个 id 已失效),另缺 `step-5-preview` / `space-bunny` / `glm-5.3-flashx` / `kimi-k3-2`。
|
|
19
|
+
改为**以 CLI 为实时权威源**(对齐 `workbuddy-auth` 的「`agents[cli].models` 权威白名单」思路):
|
|
20
|
+
- **A. `account-live`**(首选):起 CLI 用无效模型触发服务端 400,「Currently supported models
|
|
21
|
+
for your account」即该账号的授权清单 —— 与 workbuddy-auth 从 `/v3/config` 取白名单同义。
|
|
22
|
+
- **B. `cli-help`**(兜底,如实标注):解析 `--help` 的 `Currently supported: (...)`,
|
|
23
|
+
零网络、随 CLI 版本自动更新。回落时 `source` 明确写 `cli-help` 并附 note,
|
|
24
|
+
**绝不把静态表冒充成账号可用清单**。
|
|
25
|
+
- 静态表降级为**仅倍率元数据**,不再参与「是否可用」判定。
|
|
26
|
+
- **面板 `defaultModel` 无视 `preferredBackend`**:旧实现硬编码 `DEFAULT_BACKEND`(codebuddy 面),
|
|
27
|
+
无论用户选哪个后端都只列 codebuddy 面的模型。现跟随设置里的默认后端,且账号探测走与
|
|
28
|
+
`buildArgv` 完全同一套端点/凭据注入(`faceEnv`)—— 否则会出现「标着 codebuddy-en、
|
|
29
|
+
实际问的是 codebuddy 面」的错配。
|
|
30
|
+
- **`--help` 名册折行导致清单抖动**:yargs 在长名册上任意列折行,实测把 `minimax-m3-pay`
|
|
31
|
+
从中间断成 `minimax-` + `m3-pay`,同一命令两次运行给出 18/19 两种结果。解析改为
|
|
32
|
+
「去掉括号内所有空白再按逗号切」,三次连跑稳定 19 项。
|
|
33
|
+
- **探测失败文案误导**:该面未登录时曾报「codebuddy CLI not found」,实际是「CLI 跑了但没吐
|
|
34
|
+
账号名册」。现记录每个候选的真实结局(spawn 失败 / exit code + 输出首段),错误可指导处置。
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- **`scripts/build.mjs` 支持内联多个片文件**:`providers/<id>-*.mjs` 全部按字典序内联进
|
|
38
|
+
dynamic 沙箱的同一作用域(此前只认写死的 `<id>-catalog.mjs`)。每撞一次 600 行红线就改一次
|
|
39
|
+
构建器的做法不可持续。
|
|
40
|
+
- **`codebuddy` provider 再拆一层**:`codebuddy.mjs` 864 → 593 行,拆出 `codebuddy-catalog.mjs`
|
|
41
|
+
(564)与 `codebuddy-models-tool.mjs`(101),三者均在红线内。
|
|
42
|
+
- **`.mjs` 入口支持**:`CODEBUDDY_BIN` 指向 `.js/.mjs/.cjs` 时经 node 包装(也让测试夹具能
|
|
43
|
+
完全脱离真实 CLI、可在任意机器复现)。
|
|
44
|
+
- 修的 `DEP0190` 告警:Windows `.cmd` 经 shell 时把命令拼成单字符串,不再传 args 数组。
|
|
45
|
+
|
|
12
46
|
## [0.2.5] - 2026-10-18
|
|
13
47
|
|
|
14
48
|
### Added
|
|
@@ -659,45 +659,7 @@ const SETTINGS_KEYS = ['preferredBackend', 'defaultModel', 'codebuddyEnToken', '
|
|
|
659
659
|
// • dynamic 沙箱形态:scripts/build.mjs 把该文件内联进同一作用域(模板顶部的
|
|
660
660
|
// 第三个占位标记),import 行被 stripModuleSyntax 剥掉,同名标识符天然可见。
|
|
661
661
|
// ⚠️ 本注释里绝不写出那个标记的字面量:build 做全局文本替换,注释中出现同样
|
|
662
|
-
// 会被替换成正文代码,把注释劈开 → 生成物语法崩。
|
|
663
|
-
// ── 三工具 schema(旧 preset/codebuddy-first verbatim;契约 §8)────────────────
|
|
664
|
-
const RUN_PARAMS = {
|
|
665
|
-
type: 'object',
|
|
666
|
-
additionalProperties: false,
|
|
667
|
-
required: ['prompt'],
|
|
668
|
-
properties: {
|
|
669
|
-
prompt: { type: 'string', description: 'The full task/instruction for codebuddy. Be complete and self-contained.' },
|
|
670
|
-
backend: { type: 'string', enum: BACKEND_ENUM, description: 'Which CLI face to dispatch to. codebuddy (default; domestic CodeBuddy npm CLI, product endpoint www.codebuddy.ai) for coding work; codebuddy-intl = CodeBuddy INTERNATIONAL face of the same npm CLI (product.ioa.json catalogue: claude-sonnet-5 / claude-opus-5 / gemini-3.1-pro / gpt-6-astra / hy3-ioa ...; aliases codebuddy-ioa / codebuddy-international) when the account is an international CodeBuddy account; codebuddy-en = WorkBuddy international edition (the WorkBuddyAI desktop bundled CLI at C:\\Program Files\\WorkBuddyAI, product endpoint www.workbuddy.ai; needs codebuddyEnToken unless DSH already holds a workbuddy key) — aliases workbuddy-en / workbuddy-ai also accepted; workbuddy = WorkBuddy domestic desktop CLI (product endpoint copilot.tencent.com, zero config), the office-scenario face: documents/slides/spreadsheets, knowledge-base lookups, image/video generation, WeChat/WeCom replies. New calls without a backend follow the user-preferred default backend (plugin settings). Continuing a session routes back to its owning backend automatically.' },
|
|
671
|
-
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'auto follows DSH plan state; plan = no writes; accept-edits = allow edits. Default auto.' },
|
|
672
|
-
model: { type: 'string', description: MODEL_DESCRIPTION },
|
|
673
|
-
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
674
|
-
maxTurns: { type: 'integer', description: 'Optional max agentic turns (1-500, default unlimited).' },
|
|
675
|
-
cwd: { type: 'string', description: 'Working directory for codebuddy. Defaults to the DSH workspace root.' },
|
|
676
|
-
addDirs: { type: 'array', items: { type: 'string' }, description: 'Extra directories to add to codebuddy workspace.' },
|
|
677
|
-
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
678
|
-
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
const CONTINUE_PARAMS = {
|
|
682
|
-
type: 'object',
|
|
683
|
-
additionalProperties: false,
|
|
684
|
-
required: ['prompt'],
|
|
685
|
-
properties: {
|
|
686
|
-
prompt: { type: 'string', description: 'Follow-up instruction for the ongoing codebuddy conversation.' },
|
|
687
|
-
sessionId: { type: 'string', description: 'codebuddy session id to resume (from a prior codebuddy_run result).' },
|
|
688
|
-
latest: { type: 'boolean', description: 'Continue the most recent codebuddy conversation instead of a specific id.' },
|
|
689
|
-
backend: { type: 'string', enum: BACKEND_ENUM, description: 'Which CLI face to resume on. codebuddy-en = WorkBuddy international (WorkBuddyAI desktop CLI; aliases workbuddy-en / workbuddy-ai accepted), workbuddy = WorkBuddy domestic desktop CLI. When omitted, the backend that owns the sessionId is used automatically; brand-new conversations follow the user-preferred default backend (plugin settings).' },
|
|
690
|
-
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'Execution mode; default auto.' },
|
|
691
|
-
model: { type: 'string', description: 'Optional model id (two layers: product face carries it, account licenses it; the credit multiplier of each id comes from the CLI descriptor — see codebuddy_run).' },
|
|
692
|
-
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
693
|
-
maxTurns: { type: 'integer', description: 'Optional max agentic turns.' },
|
|
694
|
-
cwd: { type: 'string', description: "Working directory for codebuddy; when resuming, defaults to the resumed session's project directory." },
|
|
695
|
-
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
696
|
-
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
const STATUS_PARAMS = { type: 'object', additionalProperties: false, required: [], properties: { cwd: { type: 'string', description: 'Optional: filter the snapshot to a single project (working directory).' } } }
|
|
700
|
-
|
|
662
|
+
// 会被替换成正文代码,把注释劈开 → 生成物语法崩。
|
|
701
663
|
function defineProvider (deps = {}) {
|
|
702
664
|
const env = deps.env || {}
|
|
703
665
|
let host = null // attach 后保存 hostOpts(preset/dynamic/MCP 形态宿主能力)
|
|
@@ -1084,12 +1046,23 @@ function defineProvider (deps = {}) {
|
|
|
1084
1046
|
}
|
|
1085
1047
|
},
|
|
1086
1048
|
|
|
1087
|
-
// codebuddy_models
|
|
1049
|
+
// codebuddy_models:实时列出本机 codebuddy 可用模型(账号探测优先,回落 CLI 名册)。
|
|
1088
1050
|
// 只在宿主有 spawn/fs 能力时注册(preset 注入 deps.process / MCP 注入 deps.nodeChild;
|
|
1089
1051
|
// dynamic 沙箱形态省略)。**失败必须 ok:false + 可操作错误**,绝不静默返回空列表或
|
|
1090
1052
|
// 伪造模型(工作手册红线 §7;对照 workbuddy-auth 的 catalog-store 静默降级 —— 不照抄)。
|
|
1091
1053
|
extraTools: (deps.process || deps.nodeChild || deps.nodeFs) ? [codebuddyModelsTool({
|
|
1092
|
-
env, deps, BACKEND_ENUM, normalizeBackend, resolveBackend, modelCatalogDetailed, getAccountCatalog
|
|
1054
|
+
env, deps, BACKEND_ENUM, normalizeBackend, resolveBackend, modelCatalogDetailed, getAccountCatalog,
|
|
1055
|
+
// faceEnv:让账号探测打到**请求的那个产品面**(与 buildArgv 完全同一套端点/凭据
|
|
1056
|
+
// 注入规则)。缺了它,探测会走 CLI 自身默认登录面 —— 标着 codebuddy-en 实际却
|
|
1057
|
+
// 问的是 codebuddy 面,得出的清单看着对其实是另一个面的(本轮修的就是这类偏差)。
|
|
1058
|
+
faceEnv: (backend) => {
|
|
1059
|
+
try {
|
|
1060
|
+
const settings = (deps.settings && typeof deps.settings === 'object') ? deps.settings : {}
|
|
1061
|
+
const authDomain = readBackendAuthDomain(backend, { env })
|
|
1062
|
+
const tok = resolveEnToken(backend === 'codebuddy-en' ? settings.codebuddyEnToken : null, { env })
|
|
1063
|
+
return endpointEnv(backend, settings.endpointOverride, authDomain, tok.token) || {}
|
|
1064
|
+
} catch (e) { return {} }
|
|
1065
|
+
}
|
|
1093
1066
|
})] : [],
|
|
1094
1067
|
|
|
1095
1068
|
// 设置分区(契约 §3):keystone 统一文件 {codebuddy:{...}};旧扁平文件的字段
|
|
@@ -1100,10 +1073,27 @@ function defineProvider (deps = {}) {
|
|
|
1100
1073
|
defaults: () => normalizeSettings(null),
|
|
1101
1074
|
normalize: (raw) => normalizeSettings(raw),
|
|
1102
1075
|
meta() {
|
|
1103
|
-
// defaultModel 下拉 =
|
|
1104
|
-
//
|
|
1105
|
-
//
|
|
1106
|
-
|
|
1076
|
+
// defaultModel 下拉 = **实时**取自 CLI(parseHelpModels 解析 `--help` 的
|
|
1077
|
+
// "Currently supported: (...)")。此前读磁盘 product.ioa.json 静态表,实测
|
|
1078
|
+
// 落后于 CLI(16 项 vs CLI 19 项,且 2 个 id 已改名),是「列表不对」的根因。
|
|
1079
|
+
// 面板必须同步返回,故用零网络的 cli-help roster;账号级权威清单由
|
|
1080
|
+
// codebuddy_models 工具(异步探测)提供。
|
|
1081
|
+
// 面跟随 preferredBackend(旧实现硬编码 codebuddy 面,无视用户设置)。
|
|
1082
|
+
const face = (currentPreferredBackend() && normalizeBackend(currentPreferredBackend())) || DEFAULT_BACKEND
|
|
1083
|
+
const live = cachedCliModels(face, () => fetchHelpModelsSync(env))
|
|
1084
|
+
let options = []
|
|
1085
|
+
let note = '账号实际可用性以服务端实时裁决;日常任务优先免费(x0.00)。'
|
|
1086
|
+
if (live && live.ok && live.ids.length) {
|
|
1087
|
+
const detail = modelCatalogDetailed(face, { env, accountModels: live.ids })
|
|
1088
|
+
const byId = new Map(detail.map((x) => [x.id, x]))
|
|
1089
|
+
options = live.ids.map((id) => {
|
|
1090
|
+
const d = byId.get(id)
|
|
1091
|
+
return { value: id, label: (d && d.label) || id }
|
|
1092
|
+
})
|
|
1093
|
+
note = '实时来自本机 codebuddy CLI 的声明名册(' + live.ids.length + ' 项,随 CLI 版本更新);当前面:' + face + '。账号级权威清单见 codebuddy_models。'
|
|
1094
|
+
} else {
|
|
1095
|
+
note = '未能从 CLI 读取模型名册(' + String((live && live.error) || 'unknown') + ')—— 下拉为空,可手动输入模型 id。'
|
|
1096
|
+
}
|
|
1107
1097
|
return {
|
|
1108
1098
|
titleSuffix: 'CodeBuddy',
|
|
1109
1099
|
fields: [
|
|
@@ -1115,7 +1105,7 @@ function defineProvider (deps = {}) {
|
|
|
1115
1105
|
{
|
|
1116
1106
|
key: 'defaultModel', label: '默认模型', type: 'select', allowCustom: true, placeholder: '(CLI 默认)',
|
|
1117
1107
|
options,
|
|
1118
|
-
note
|
|
1108
|
+
note
|
|
1119
1109
|
},
|
|
1120
1110
|
{ key: 'codebuddyEnToken', label: 'WorkBuddy 国际版 Token', type: 'secret', note: '仅 backend="codebuddy-en" 需要。国际版登录 token 被 protector key 封装不落盘,只能手工提供;留空则自动尝试 DSH 凭据库的 WORKBUDDY_TOKEN。' },
|
|
1121
1111
|
{ key: 'endpointOverride', label: '端点覆盖', type: 'text', placeholder: 'https://…/v2', note: '一般留空:端点按登录域自动对齐(www.codebuddy.cn 等)。仅在需要强制指定 API 端点时填写,且必须自带 /v2。' }
|
|
@@ -1199,6 +1189,7 @@ function defineProvider (deps = {}) {
|
|
|
1199
1189
|
return descriptor
|
|
1200
1190
|
}
|
|
1201
1191
|
|
|
1192
|
+
// ── inlined from providers/codebuddy-catalog.mjs ──
|
|
1202
1193
|
// providers/codebuddy-catalog.mjs — codebuddy 模型目录/积分/描述文件解析(纯逻辑)。
|
|
1203
1194
|
//
|
|
1204
1195
|
// 从 providers/codebuddy.mjs 拆出:主文件原 864 行远超 600 行红线(规则 §26)。
|
|
@@ -1431,6 +1422,206 @@ const BACKEND_ENUM = ['codebuddy', 'codebuddy-intl', 'codebuddy-ioa', 'codebuddy
|
|
|
1431
1422
|
return v && v.length ? v.slice() : null
|
|
1432
1423
|
}
|
|
1433
1424
|
|
|
1425
|
+
// ── 实时模型清单(CLI 是一手权威来源,对齐 workbuddy-auth 的「权威白名单」思路)──
|
|
1426
|
+
//
|
|
1427
|
+
// 为什么不再以静态表/磁盘描述文件为主:实测本机 codebuddy CLI --
|
|
1428
|
+
// • 账号实时清单(用无效模型触发 400 报文)= 19 项
|
|
1429
|
+
// • CLI `--help` 声明的 roster = 19 项,与账号清单完全一致
|
|
1430
|
+
// • 我们原先读的 product.ioa.json 静态表 = 16 项,且 2 个 id 已过时
|
|
1431
|
+
// (minimax-m3 → minimax-m3-pay、kimi-k3-1 → kimi-k3-2),另缺 4 个新模型。
|
|
1432
|
+
// 静态表是「随包冻结」的,必然落后于 CLI,故只保留作倍率元数据与最后兜底。
|
|
1433
|
+
//
|
|
1434
|
+
// 两级实时来源(都不静默冒充权威数据,各带 source 标签):
|
|
1435
|
+
// A. account-live —— 起 CLI 用无效模型探测「该账号当前支持」的清单(需该面已登录,
|
|
1436
|
+
// 一次网络往返;对照 workbuddy-auth 的 agents[cli].models 白名单语义)。
|
|
1437
|
+
// B. cli-help —— 解析 `--help` 的 "Currently supported: (...)"(零网络、随 CLI
|
|
1438
|
+
// 版本自动更新;产品面级 roster)。
|
|
1439
|
+
// 静态表仅作 credit 元数据与 descriptor-static 兜底标签,绝不冒充实时。
|
|
1440
|
+
|
|
1441
|
+
/** 解析 CLI `--help` 里声明的模型 roster。空/畸形输入返回 []。
|
|
1442
|
+
* ⚠️ 名册很长时 yargs 会在任意列**折行**(实测把 `minimax-m3-pay` 从中间断成
|
|
1443
|
+
* `minimax-` + `m3-pay`),若按行/按逗号直接切会丢项或产生残片(同一命令两次运行
|
|
1444
|
+
* 给出 18/19 两种结果)。故先去掉括号内所有空白再按逗号切:逗号是唯一分隔符,
|
|
1445
|
+
* 折行插入的空白一律视为噪声。 */
|
|
1446
|
+
function parseHelpModels(helpText) {
|
|
1447
|
+
const m = /Currently supported:\s*\(([^)]+)\)/.exec(String(helpText || ''))
|
|
1448
|
+
if (!m) return []
|
|
1449
|
+
const out = []
|
|
1450
|
+
for (const raw of m[1].replace(/\s+/g, '').split(',')) {
|
|
1451
|
+
const id = raw.trim()
|
|
1452
|
+
if (id && /^[A-Za-z0-9_.\-]+$/.test(id) && out.indexOf(id) < 0) out.push(id)
|
|
1453
|
+
}
|
|
1454
|
+
return out
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/** 解析 codebuddy CLI 的可执行候选(env > npm 全局 shim > PATH 名)。 */
|
|
1458
|
+
function cbCatalogCliCandidates(env) {
|
|
1459
|
+
const e = env || cbCatalogEnv || {}
|
|
1460
|
+
const out = []
|
|
1461
|
+
const push = (p) => { if (p && out.indexOf(p) < 0) out.push(p) }
|
|
1462
|
+
if (e.CODEBUDDY_BIN) push(String(e.CODEBUDDY_BIN))
|
|
1463
|
+
const joinP = (a, b) => cbCatalogPathJoin(a, b)
|
|
1464
|
+
if (e.APPDATA) push(joinP(joinP(e.APPDATA, 'npm'), 'codebuddy.cmd'))
|
|
1465
|
+
if (e.APPDATA) push(joinP(joinP(e.APPDATA, 'npm'), 'codebuddy'))
|
|
1466
|
+
push('codebuddy')
|
|
1467
|
+
return out
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
const CLI_MODEL_CACHE = new Map() // backend → { at, mode, ids }
|
|
1471
|
+
const CLI_MODEL_TTL_MS = 5 * 60 * 1000
|
|
1472
|
+
|
|
1473
|
+
// Windows .cmd/.bat 需要经 shell(Node 安全限制),但 `shell:true` + args 数组会触发
|
|
1474
|
+
// DEP0190(参数不转义)。这里统一做法:把命令与参数拼成【一个】字符串交给 shell,
|
|
1475
|
+
// 参数各自按 Windows 规则加引号 —— 既避开 DEP0190,又保证含空格参数不被拆开。
|
|
1476
|
+
function quoteWinArg(a) {
|
|
1477
|
+
const s = String(a)
|
|
1478
|
+
if (s === '') return '""'
|
|
1479
|
+
if (!/[\s"&|<>^]/.test(s)) return s
|
|
1480
|
+
return '"' + s.replace(/"/g, '\\"') + '"'
|
|
1481
|
+
}
|
|
1482
|
+
function cliCommandLine(exe, args) {
|
|
1483
|
+
return [exe].concat(args || []).map(quoteWinArg).join(' ')
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/** 把候选拆成 { cmd, prefixArgs }:.mjs/.js/.cjs 目标经 node 包装(测试夹具与自定义
|
|
1487
|
+
* 入口都靠这条),其余原样。 */
|
|
1488
|
+
function splitExeSpec(exe) {
|
|
1489
|
+
if (/\.(mjs|cjs|js)$/i.test(String(exe))) {
|
|
1490
|
+
const proc = globalThis.process
|
|
1491
|
+
return { cmd: (proc && proc.execPath) ? proc.execPath : 'node', prefixArgs: [String(exe)] }
|
|
1492
|
+
}
|
|
1493
|
+
return { cmd: String(exe), prefixArgs: [] }
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
function cliSpawnSync(exe, args, opts) {
|
|
1497
|
+
const proc = globalThis.process
|
|
1498
|
+
const child = (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule('node:child_process') : null
|
|
1499
|
+
if (!child || typeof child.spawnSync !== 'function') return null
|
|
1500
|
+
const base = Object.assign({
|
|
1501
|
+
encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'pipe']
|
|
1502
|
+
}, opts || {})
|
|
1503
|
+
const spec = splitExeSpec(exe)
|
|
1504
|
+
const argv = spec.prefixArgs.concat(args || [])
|
|
1505
|
+
const isCmd = /\.(cmd|bat)$/i.test(spec.cmd)
|
|
1506
|
+
if (isCmd) base.shell = true
|
|
1507
|
+
try {
|
|
1508
|
+
// shell 形态不带 args 数组(否则 DEP0190);非 shell 形态正常传数组。
|
|
1509
|
+
return isCmd
|
|
1510
|
+
? child.spawnSync(cliCommandLine(spec.cmd, argv), base)
|
|
1511
|
+
: child.spawnSync(spec.cmd, argv, base)
|
|
1512
|
+
} catch (e) { return null }
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* 同步取「CLI 声明的 roster」(零网络,随 CLI 版本走)。
|
|
1517
|
+
* @returns {{ok:boolean, ids:string[], mode:string, error?:string}}
|
|
1518
|
+
*/
|
|
1519
|
+
function fetchHelpModelsSync(env, opts) {
|
|
1520
|
+
const o = opts || {}
|
|
1521
|
+
const cands = cbCatalogCliCandidates(env)
|
|
1522
|
+
let lastErr = null
|
|
1523
|
+
for (const exe of cands) {
|
|
1524
|
+
const r = cliSpawnSync(exe, ['--help'], { timeout: Math.max(1000, Math.min(o.timeoutMs || 12000, 30000)) })
|
|
1525
|
+
if (!r || r.error) { lastErr = 'spawn failed: ' + String((r && r.error && r.error.message) || 'unavailable'); continue }
|
|
1526
|
+
const text = String(r.stdout || '') + String(r.stderr || '')
|
|
1527
|
+
const ids = parseHelpModels(text)
|
|
1528
|
+
if (ids.length) return { ok: true, ids, mode: 'cli-help', exe }
|
|
1529
|
+
lastErr = 'no roster in --help output'
|
|
1530
|
+
}
|
|
1531
|
+
return { ok: false, ids: [], mode: 'cli-help', error: lastErr || 'codebuddy CLI not found (set CODEBUDDY_BIN)' }
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* 异步探测「该账号当前支持」的清单:用无效模型触发 CLI 的 400 报文。
|
|
1536
|
+
* 对齐 workbuddy-auth 的权威白名单语义(服务器告诉我们这个账号能用什么)。
|
|
1537
|
+
* @returns {Promise<{ok:boolean, ids:string[], mode:string, error?:string}>}
|
|
1538
|
+
*/
|
|
1539
|
+
function probeAccountModels(env, opts) {
|
|
1540
|
+
const o = opts || {}
|
|
1541
|
+
const cands = cbCatalogCliCandidates(env)
|
|
1542
|
+
const proc = globalThis.process
|
|
1543
|
+
const child = (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule('node:child_process') : null
|
|
1544
|
+
if (!child || typeof child.spawn !== 'function') {
|
|
1545
|
+
return Promise.resolve({ ok: false, ids: [], mode: 'account-live', error: 'no spawn capability in this host form' })
|
|
1546
|
+
}
|
|
1547
|
+
const extraEnv = o.envExtra || {}
|
|
1548
|
+
return new Promise((resolve) => {
|
|
1549
|
+
let idx = 0
|
|
1550
|
+
// 记录每个候选的真实结局:区分「压根没找到 CLI」与「CLI 跑了但没吐账号名录」
|
|
1551
|
+
// (后者通常是该面未登录 / 端点拒了)—— 报错文案必须能指导下一步,不能一律
|
|
1552
|
+
// 说 "CLI not found"(本轮就踩到:codebuddy-en 无 token 时被误报成找不到 CLI)。
|
|
1553
|
+
const reasons = []
|
|
1554
|
+
const attempt = () => {
|
|
1555
|
+
if (idx >= cands.length) {
|
|
1556
|
+
resolve({
|
|
1557
|
+
ok: false, ids: [], mode: 'account-live',
|
|
1558
|
+
error: reasons.length ? reasons.join('; ') : 'no CLI candidate to probe',
|
|
1559
|
+
notFound: reasons.every((r) => /not found|ENOENT|spawn/i.test(r))
|
|
1560
|
+
})
|
|
1561
|
+
return
|
|
1562
|
+
}
|
|
1563
|
+
const exe = cands[idx++]
|
|
1564
|
+
let c
|
|
1565
|
+
try {
|
|
1566
|
+
// .cmd/.bat 需经 shell(Windows 安全限制);.mjs/.js 经 node 包装。
|
|
1567
|
+
// 与 cliSpawnSync 同一规则:shell 形态把命令拼成单字符串(避免 DEP0190)。
|
|
1568
|
+
const spec = splitExeSpec(exe)
|
|
1569
|
+
const isCmd = /\.(cmd|bat)$/i.test(spec.cmd)
|
|
1570
|
+
const probeArgs = spec.prefixArgs.concat(['-p', 'hi', '--model', '__clb_probe_invalid__', '--output-format', 'text'])
|
|
1571
|
+
const opts = {
|
|
1572
|
+
stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
1573
|
+
env: Object.assign({}, proc.env, extraEnv)
|
|
1574
|
+
}
|
|
1575
|
+
c = isCmd
|
|
1576
|
+
? child.spawn(cliCommandLine(spec.cmd, probeArgs), Object.assign({ shell: true }, opts))
|
|
1577
|
+
: child.spawn(spec.cmd, probeArgs, opts)
|
|
1578
|
+
} catch (e) { reasons.push(exe + ': spawn threw ' + String(e && e.message || e)); attempt(); return }
|
|
1579
|
+
let out = ''
|
|
1580
|
+
const timer = setTimeout(() => { try { c.kill() } catch (e) { } }, Math.max(5000, Math.min(o.timeoutMs || 30000, 90000)))
|
|
1581
|
+
const onData = (d) => { out += d; if (out.length > 400000) out = out.slice(-200000) }
|
|
1582
|
+
c.stdout.on('data', onData)
|
|
1583
|
+
c.stderr.on('data', onData)
|
|
1584
|
+
c.on('error', (e) => {
|
|
1585
|
+
clearTimeout(timer)
|
|
1586
|
+
reasons.push(exe + ': ' + String((e && e.code) || (e && e.message) || 'spawn error'))
|
|
1587
|
+
attempt()
|
|
1588
|
+
})
|
|
1589
|
+
c.on('close', (code) => {
|
|
1590
|
+
clearTimeout(timer)
|
|
1591
|
+
const ids = parseAccountModels(out)
|
|
1592
|
+
if (ids && ids.length) { resolve({ ok: true, ids, mode: 'account-live', exe }); return }
|
|
1593
|
+
const head = String(out).replace(/\s+/g, ' ').trim().slice(0, 160)
|
|
1594
|
+
reasons.push(exe + ': exit ' + code + ', no account roster' + (head ? ' — ' + head : ''))
|
|
1595
|
+
attempt()
|
|
1596
|
+
})
|
|
1597
|
+
}
|
|
1598
|
+
attempt()
|
|
1599
|
+
})
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/** 缓存读写(面板每次渲染都会取,避免反复起进程)。 */
|
|
1603
|
+
function cachedCliModels(backend, load, nowMs) {
|
|
1604
|
+
const at = Number(nowMs) || Date.now()
|
|
1605
|
+
const hit = CLI_MODEL_CACHE.get(backend)
|
|
1606
|
+
if (hit && at - hit.at < CLI_MODEL_TTL_MS) return hit
|
|
1607
|
+
const v = typeof load === 'function' ? load() : load
|
|
1608
|
+
if (v && v.ok && v.ids && v.ids.length) CLI_MODEL_CACHE.set(backend, { at, mode: v.mode, ids: v.ids.slice() })
|
|
1609
|
+
return v
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/** 读统一设置文件里 codebuddy 分区的 preferredBackend(meta 需要它才知道列哪个面)。 */
|
|
1613
|
+
function currentPreferredBackend() {
|
|
1614
|
+
try {
|
|
1615
|
+
const e = cbCatalogEnv || {}
|
|
1616
|
+
const p = cbCatalogRead(e.CLI_BRIDGE_SETTINGS_FILE || cbCatalogPathJoin(
|
|
1617
|
+
e.DSH_HOME || (e.APPDATA ? cbCatalogPathJoin(e.APPDATA, 'DSH Desktop', 'dsh-home') : ''), 'cli-bridge-settings.json'))
|
|
1618
|
+
if (!p) return null
|
|
1619
|
+
const all = JSON.parse(p)
|
|
1620
|
+
const v = all && all.codebuddy && all.codebuddy.preferredBackend
|
|
1621
|
+
return typeof v === 'string' && v ? v : null
|
|
1622
|
+
} catch (err) { return null }
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1434
1625
|
// CLI 报错里解析「该账号当前支持的模型」(旧 core parseAccountModels verbatim)。
|
|
1435
1626
|
function parseAccountModels(text) {
|
|
1436
1627
|
const s = String(text || '')
|
|
@@ -1566,6 +1757,52 @@ const CREDENTIAL_KEYS = ['WORKBUDDY_TOKEN', 'WORKBUDDY_AI_TOKEN', 'CODEBUDDY_EN_
|
|
|
1566
1757
|
return { token: null, source: null }
|
|
1567
1758
|
}
|
|
1568
1759
|
|
|
1760
|
+
// ── 三工具 schema(旧 preset/codebuddy-first verbatim;契约 §8 红线)────────────
|
|
1761
|
+
// 纯数据,无依赖 —— 放这里是为了让主描述符留在 600 行红线内(规则 §26)。
|
|
1762
|
+
const RUN_PARAMS = {
|
|
1763
|
+
type: 'object',
|
|
1764
|
+
additionalProperties: false,
|
|
1765
|
+
required: ['prompt'],
|
|
1766
|
+
properties: {
|
|
1767
|
+
prompt: { type: 'string', description: 'The full task/instruction for codebuddy. Be complete and self-contained.' },
|
|
1768
|
+
backend: { type: 'string', enum: BACKEND_ENUM, description: 'Which CLI face to dispatch to. codebuddy (default; domestic CodeBuddy npm CLI, product endpoint www.codebuddy.ai) for coding work; codebuddy-intl = CodeBuddy INTERNATIONAL face of the same npm CLI (product.ioa.json catalogue: claude-sonnet-5 / claude-opus-5 / gemini-3.1-pro / gpt-6-astra / hy3-ioa ...; aliases codebuddy-ioa / codebuddy-international) when the account is an international CodeBuddy account; codebuddy-en = WorkBuddy international edition (the WorkBuddyAI desktop bundled CLI at C:\\Program Files\\WorkBuddyAI, product endpoint www.workbuddy.ai; needs codebuddyEnToken unless DSH already holds a workbuddy key) — aliases workbuddy-en / workbuddy-ai also accepted; workbuddy = WorkBuddy domestic desktop CLI (product endpoint copilot.tencent.com, zero config), the office-scenario face: documents/slides/spreadsheets, knowledge-base lookups, image/video generation, WeChat/WeCom replies. New calls without a backend follow the user-preferred default backend (plugin settings). Continuing a session routes back to its owning backend automatically.' },
|
|
1769
|
+
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'auto follows DSH plan state; plan = no writes; accept-edits = allow edits. Default auto.' },
|
|
1770
|
+
model: { type: 'string', description: MODEL_DESCRIPTION },
|
|
1771
|
+
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
1772
|
+
maxTurns: { type: 'integer', description: 'Optional max agentic turns (1-500, default unlimited).' },
|
|
1773
|
+
cwd: { type: 'string', description: 'Working directory for codebuddy. Defaults to the DSH workspace root.' },
|
|
1774
|
+
addDirs: { type: 'array', items: { type: 'string' }, description: 'Extra directories to add to codebuddy workspace.' },
|
|
1775
|
+
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
1776
|
+
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
const CONTINUE_PARAMS = {
|
|
1780
|
+
type: 'object',
|
|
1781
|
+
additionalProperties: false,
|
|
1782
|
+
required: ['prompt'],
|
|
1783
|
+
properties: {
|
|
1784
|
+
prompt: { type: 'string', description: 'Follow-up instruction for the ongoing codebuddy conversation.' },
|
|
1785
|
+
sessionId: { type: 'string', description: 'codebuddy session id to resume (from a prior codebuddy_run result).' },
|
|
1786
|
+
latest: { type: 'boolean', description: 'Continue the most recent codebuddy conversation instead of a specific id.' },
|
|
1787
|
+
backend: { type: 'string', enum: BACKEND_ENUM, description: 'Which CLI face to resume on. codebuddy-en = WorkBuddy international (WorkBuddyAI desktop CLI; aliases workbuddy-en / workbuddy-ai accepted), workbuddy = WorkBuddy domestic desktop CLI. When omitted, the backend that owns the sessionId is used automatically; brand-new conversations follow the user-preferred default backend (plugin settings).' },
|
|
1788
|
+
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'Execution mode; default auto.' },
|
|
1789
|
+
model: { type: 'string', description: 'Optional model id (two layers: product face carries it, account licenses it; the credit multiplier of each id comes from the CLI descriptor — see codebuddy_run).' },
|
|
1790
|
+
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
1791
|
+
maxTurns: { type: 'integer', description: 'Optional max agentic turns.' },
|
|
1792
|
+
cwd: { type: 'string', description: "Working directory for codebuddy; when resuming, defaults to the resumed session's project directory." },
|
|
1793
|
+
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
1794
|
+
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const STATUS_PARAMS = { type: 'object', additionalProperties: false, required: [], properties: { cwd: { type: 'string', description: 'Optional: filter the snapshot to a single project (working directory).' } } }
|
|
1798
|
+
|
|
1799
|
+
// ── inlined from providers/codebuddy-models-tool.mjs ──
|
|
1800
|
+
// providers/codebuddy-models-tool.mjs — codebuddy_models 工具定义(纯逻辑)。
|
|
1801
|
+
//
|
|
1802
|
+
// 从 codebuddy-catalog.mjs 再拆出:两个文件都要留在 600 行红线内(规则 §26)。
|
|
1803
|
+
// scripts/build.mjs 会把 providers/codebuddy-*.mjs 全部内联进 dynamic 沙箱的同一个
|
|
1804
|
+
// 作用域,所以这里仍可引用 catalog 的函数(沙箱内天然可见);ESM 形态走本文件的 import。
|
|
1805
|
+
// ⚠️ 注释里绝不写出口令标记字面量(build 全局替换会劈开注释 → 生成物语法崩)。
|
|
1569
1806
|
// ── codebuddy_models 工具定义(搬自主文件;纯逻辑,依赖经 ctx 注入)────────────
|
|
1570
1807
|
// ctx = { env, deps, resolveBackend, modelCatalogDetailed, getAccountCatalog }
|
|
1571
1808
|
function codebuddyModelsTool(ctx) {
|
|
@@ -1574,7 +1811,7 @@ function codebuddyModelsTool(ctx) {
|
|
|
1574
1811
|
const env = c.env || {}
|
|
1575
1812
|
return {
|
|
1576
1813
|
name: 'codebuddy_models',
|
|
1577
|
-
description: 'List the models
|
|
1814
|
+
description: 'List the models the local codebuddy CLI can use for a given backend, read LIVE from the CLI. Primary source: an account probe (runs the CLI with a deliberately invalid model id, which makes the server print "Currently supported models for your account") — this is the authoritative licensed list for THIS account. If that face is not logged in, it falls back to the roster the installed CLI declares in --help and says so in the source field. Credit multipliers (x0.00 = free) come from the product descriptor as metadata. Returns ok:false with an actionable error when no live roster can be read — it never presents a frozen static table as if it were live.',
|
|
1578
1815
|
inputSchema: {
|
|
1579
1816
|
type: 'object', additionalProperties: false, required: [],
|
|
1580
1817
|
properties: {
|
|
@@ -1596,41 +1833,73 @@ function codebuddyModelsTool(ctx) {
|
|
|
1596
1833
|
lines.push(' ' + m.id + (m.creditLabel ? ' ' + m.creditLabel : '') + (flags.length ? ' [' + flags.join(',') + ']' : ''))
|
|
1597
1834
|
}
|
|
1598
1835
|
if (list.length > 40) lines.push(' … and ' + (list.length - 40) + ' more')
|
|
1599
|
-
|
|
1836
|
+
const srcLabel = v.source === 'account-live'
|
|
1837
|
+
? 'account-live (authoritative for this account)'
|
|
1838
|
+
: 'cli-help (installed CLI roster, not the licensed subset)'
|
|
1839
|
+
lines.push('source: ' + srcLabel)
|
|
1840
|
+
if (v.note) lines.push('note: ' + v.note)
|
|
1600
1841
|
return [{ type: 'text', text: lines.join('\n') }]
|
|
1601
1842
|
},
|
|
1602
1843
|
async logic(args) {
|
|
1603
1844
|
const a = args || {}
|
|
1604
|
-
const preferred = (deps.settings && deps.settings.preferredBackend) || null
|
|
1845
|
+
const preferred = currentPreferredBackend() || (deps.settings && deps.settings.preferredBackend) || null
|
|
1605
1846
|
const backend = c.normalizeBackend(a.backend) || c.resolveBackend({}, null, preferred)
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1847
|
+
|
|
1848
|
+
// ① 账号实时(最权威,对照 workbuddy-auth 的 agents[cli] 白名单语义)。
|
|
1849
|
+
// 该面未登录/无 token 时探测会失败 —— 失败就**明确降级到 CLI 名册并标注**,
|
|
1850
|
+
// 绝不把静态表冒充成账号可用清单。
|
|
1851
|
+
const extraEnv = (c.faceEnv && c.faceEnv(backend)) || {}
|
|
1852
|
+
const probe = await probeAccountModels(env, { envExtra: extraEnv, timeoutMs: a.timeoutMs })
|
|
1853
|
+
let ids = null
|
|
1854
|
+
let source = null
|
|
1855
|
+
let note = ''
|
|
1856
|
+
if (probe.ok && probe.ids.length) {
|
|
1857
|
+
ids = probe.ids
|
|
1858
|
+
source = 'account-live'
|
|
1859
|
+
} else {
|
|
1860
|
+
// ② CLI 声明的 roster(零网络,随 CLI 版本更新)。
|
|
1861
|
+
const help = fetchHelpModelsSync(env, { timeoutMs: a.timeoutMs })
|
|
1862
|
+
if (help.ok && help.ids.length) {
|
|
1863
|
+
ids = help.ids
|
|
1864
|
+
source = 'cli-help'
|
|
1865
|
+
note = 'account probe unavailable (' + String(probe.error || 'not logged in for this face') +
|
|
1866
|
+
'); showing the roster the installed CLI declares. It reflects the CLI version, not the licensed subset of this account.'
|
|
1867
|
+
}
|
|
1611
1868
|
}
|
|
1612
|
-
if (!
|
|
1869
|
+
if (!ids || !ids.length) {
|
|
1613
1870
|
return {
|
|
1614
1871
|
ok: false, backend,
|
|
1615
|
-
error: 'no model
|
|
1872
|
+
error: 'no live model roster could be read for backend "' + backend + '". ' +
|
|
1873
|
+
'Account probe: ' + String(probe.error || 'failed') + '. ' +
|
|
1874
|
+
'Install/fix the codebuddy CLI (CODEBUDDY_BIN) and make sure this face is logged in, then retry.'
|
|
1616
1875
|
}
|
|
1617
1876
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
let
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1877
|
+
|
|
1878
|
+
// ③ 倍率/授权元数据(来自描述文件;仅作标注,不参与「是否可用」判定)。
|
|
1879
|
+
let detail = []
|
|
1880
|
+
try { detail = c.modelCatalogDetailed(backend, { env, accountModels: ids }) || [] } catch (e) { detail = [] }
|
|
1881
|
+
const byId = new Map(detail.map((x) => [x.id, x]))
|
|
1882
|
+
let list = ids.map((id) => {
|
|
1883
|
+
const d = byId.get(id)
|
|
1884
|
+
return {
|
|
1885
|
+
id,
|
|
1886
|
+
authorized: d ? !!d.authorized : (source === 'account-live'),
|
|
1887
|
+
credits: d && typeof d.credits === 'number' ? d.credits : null,
|
|
1888
|
+
creditLabel: (d && (d.creditLabel || d.label)) || '',
|
|
1889
|
+
label: (d && d.label) || id
|
|
1890
|
+
}
|
|
1891
|
+
})
|
|
1624
1892
|
if (a.authorizedOnly) list = list.filter((m) => m.authorized)
|
|
1625
1893
|
if (a.freeOnly) list = list.filter((m) => m.credits === 0)
|
|
1626
1894
|
return {
|
|
1627
1895
|
ok: true, backend, models: list, count: list.length,
|
|
1628
|
-
source:
|
|
1629
|
-
|
|
1896
|
+
source, note: note || undefined,
|
|
1897
|
+
probedAt: Date.now()
|
|
1630
1898
|
}
|
|
1631
1899
|
}
|
|
1632
1900
|
}
|
|
1633
|
-
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1634
1903
|
|
|
1635
1904
|
// core/scheduling.mjs — CLI 调度(启用集合 + 调用优先级)单源纯逻辑。
|
|
1636
1905
|
//
|
package/mcp/cli-bridge-mcp.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import * as mimoMcp from './providers/mimo.mjs'
|
|
|
31
31
|
import * as opencodeMcp from './providers/opencode.mjs'
|
|
32
32
|
|
|
33
33
|
const SERVER_NAME = 'cli-bridge-mcp'
|
|
34
|
-
const SERVER_VERSION = '0.2.
|
|
34
|
+
const SERVER_VERSION = '0.2.6'
|
|
35
35
|
const PROTOCOL = '2024-11-05'
|
|
36
36
|
|
|
37
37
|
const PROVIDERS = [agyMcp, codebuddyMcp, mimoMcp, opencodeMcp]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luchenglong/cli-bridge",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "CLI Bridge for DeepSeek Harness (DSH): one package bridging four local coding-CLI agents — agy (Google Antigravity CLI), Tencent CodeBuddy Code, Xiaomi MiMo (MiMoCode / MiMo Desktop), and OpenCode. Each provider keeps its own agent preset (cordis-agy / codebuddy-first / cordis-mimo / opencode-first), tools (agy_run / codebuddy_run / mimo_run / opencode_run + continue + status), rate-limit/network fallback dialog, and zero-dependency MCP server, unified under a single home-level status light, one settings file, and one bundle patch. Merges agy-first-bridge, codebuddy-first-bridge and mimo-first-bridge.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -91,3 +91,4 @@
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
|