sintbot 1.0.9 → 1.0.11

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.en.json CHANGED
@@ -1,4 +1,32 @@
1
1
  [
2
+ {
3
+ "version": "1.0.11",
4
+ "name": "Icon libraries from the setup wizard",
5
+ "items": [
6
+ {
7
+ "t": "The wizard installs the icon libraries",
8
+ "d": "When the design icon libraries (Lucide, Tabler, Phosphor and Pixelarticons) are missing, the setup wizard now offers to install them in one go (about 170 MB). With them, the bot uses standard icons by name when it draws a design instead of drawing its own."
9
+ }
10
+ ]
11
+ },
12
+ {
13
+ "version": "1.0.10",
14
+ "name": "A steadier background service",
15
+ "items": [
16
+ {
17
+ "t": "No service without a chat platform",
18
+ "d": "If no Feishu, Slack or WeCom bot is connected yet, installing the background service now stops and points you to the console to connect one and bind a group first. Before, the service would exit as soon as it started."
19
+ },
20
+ {
21
+ "t": "Keys from environment variables reach the service",
22
+ "d": "When the config takes keys from environment variables, installing the service now asks whether to write them into the service definition, readable only by you. Without them, models that use those keys do not work in the service."
23
+ },
24
+ {
25
+ "t": "Clearer wording",
26
+ "d": "Trial runs show the model from your config instead of the model's own claim (third-party models often call themselves Claude). The health check gives a missing chat platform a row of its own, and in a local terminal trial the model-switch hint says how to switch."
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "1.0.9",
4
32
  "name": "A steadier setup wizard",
package/CHANGELOG.json CHANGED
@@ -1,4 +1,34 @@
1
1
  [
2
+ {
3
+ "version": "1.0.11",
4
+ "date": "2026-10-26",
5
+ "name": "安装向导代装设计图标库",
6
+ "items": [
7
+ {
8
+ "t": "向导代装设计图标库",
9
+ "d": "安装向导发现设计图标库(Lucide、Tabler、Phosphor、Pixelarticons)没装时,会问一句要不要一次装齐(约 170 MB);装好后机器人出设计图能按名字取到标准图标,不用自己画。"
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "version": "1.0.10",
15
+ "date": "2026-10-26",
16
+ "name": "常驻服务装得更稳",
17
+ "items": [
18
+ {
19
+ "t": "没接聊天平台时不装常驻服务",
20
+ "d": "安装时还没接飞书、Slack 或企业微信,就不再装常驻服务(装了也会一启动就退出),而是提示先在控制台里接好平台、绑群,再装服务。"
21
+ },
22
+ {
23
+ "t": "环境变量里的 key 能带进服务",
24
+ "d": "配置里的 key 取自环境变量时,装常驻服务会问一句要不要把它们写进服务定义(只有你自己能读);不写的话,服务里用这些 key 的模型用不了。"
25
+ },
26
+ {
27
+ "t": "说法更准",
28
+ "d": "试跑模型时直接显示配置里的型号,不再靠模型自报(第三方模型常自称 Claude);体检里「没配任何聊天平台」单列一行;在本机终端里试跑时,切模型的提示说清楚该怎么切。"
29
+ }
30
+ ]
31
+ },
2
32
  {
3
33
  "version": "1.0.9",
4
34
  "date": "2026-10-26",
package/bin/sbot.js CHANGED
@@ -213,7 +213,8 @@ const SUBHELP = {
213
213
  + ' 把 sintbot start --ui 装成常驻服务:崩溃自动拉起、登录后自启(macOS = launchd,Linux = systemd --user)\n'
214
214
  + ' install 写单元文件并立即启动;服务里固定用当前的 node、sbot 与配置文件的绝对路径,PATH 取当前 shell 的\n'
215
215
  + ' --dry-run 只打印要写的单元文件,不动系统;--ui-host 0.0.0.0 对局域网开控制台(须先 sintbot ui --set-password,否则拒装)\n'
216
- + ' 服务拿不到当前 shell 的环境变量:配置里 env:VAR 引用的 key 会被点名;--pass-env 把它们的值写进单元文件(文件权限 600)\n'
216
+ + ' 服务拿不到当前 shell 的环境变量:配置里 env:VAR 引用的 key 会被点名;--pass-env 把它们的值写进单元文件(文件权限 600),终端里装时不给也会问一句\n'
217
+ + ' 还没接任何聊天平台时不装(退出码 3):服务会一启动就退出 —— 先 sintbot ui 在控制台接平台\n'
217
218
  + ' Windows 不自动装:打印 NSSM / pm2 的现成命令。日志在 ~/.sbot/logs/;同一份状态只许一个 daemon,第二个会直接报错退出',
218
219
  version: '用法: sintbot version (也认 sbot --version / -v)\n'
219
220
  + ' 打印版本号',
@@ -1225,11 +1226,27 @@ async function cmdService(rest) {
1225
1226
  let raw
1226
1227
  try { raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) } catch (e) { return die(`读不了配置 ${configPath}:${e.message}`) }
1227
1228
  if (uiHost && !isLoopbackHost(uiHost) && !((raw.ui || {}).auth)) return die(`控制台对外(--ui-host ${uiHost})要先设密码:sintbot ui --set-password${values.config ? ' --config ' + values.config : ''} —— 否则服务一启动就退出、被反复拉起`)
1228
- // 服务进程拿不到当前 shell 的环境变量:点名配置里的 env: 引用;--pass-env 才把值写进单元文件
1229
+ // 一个聊天平台都没接:sintbot start 一启动就报错退出,装成服务同样是反复拉起的死循环 ⇒ 也在装之前拦。
1230
+ // 退出码 3 单列,install.sh 据此改口「先去控制台接平台」(env: 引用按当前 shell 解析,与服务带 --pass-env 时一致)
1231
+ const plat = platformsConfigured(loadConfig(configPath))
1232
+ if (!plat.feishu && !plat.wecom && !plat.slack) {
1233
+ const cfgArg = values.config ? ' --config ' + values.config : ''
1234
+ process.stderr.write(red(`sbot: 还没接聊天平台 —— 装成服务也会一启动就退出、被反复拉起。先运行 sintbot ui${cfgArg},在控制台「设置」里填飞书 / Slack / 企业微信的凭据并绑群(或重跑 sintbot setup),再装服务`) + '\n')
1235
+ process.exit(3)
1236
+ }
1237
+ // 服务进程拿不到当前 shell 的环境变量:点名配置里的 env: 引用;--pass-env 才把值写进单元文件。
1238
+ // 终端里装又没给 --pass-env 时问一句(默认是):不带进去,服务里用这些 key 的模型 / 平台直接用不了
1229
1239
  const refs = svc.envRefs(raw)
1230
1240
  const inShell = refs.filter((v) => process.env[v])
1231
1241
  const nowhere = refs.filter((v) => !process.env[v])
1232
- const env = values['pass-env'] ? Object.fromEntries(inShell.map((v) => [v, process.env[v]])) : {}
1242
+ let passEnv = !!values['pass-env']
1243
+ if (!passEnv && inShell.length && process.stdin.isTTY) {
1244
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr })
1245
+ const a = await new Promise((res) => rl.question(`${cyan('?')} 配置用到的环境变量 ${inShell.join(' ')} 服务里拿不到 —— 把它们的值写进服务定义吗?(文件权限 600;以后换 key 要重跑这条命令) ${dim('(Y/n)')} `, res))
1246
+ rl.close()
1247
+ passEnv = !/^n/i.test(String(a).trim())
1248
+ }
1249
+ const env = passEnv ? Object.fromEntries(inShell.map((v) => [v, process.env[v]])) : {}
1233
1250
  const tools = ['claude', 'codex'].map((n) => whichSync(n)).filter(Boolean)
1234
1251
  const node = svc.stableNodeBin({ execPath: process.execPath, candidates: [whichSync('node')] })
1235
1252
  const sbotBin = path.resolve(__dirname, 'sbot.js')
@@ -1244,7 +1261,7 @@ async function cmdService(rest) {
1244
1261
  })
1245
1262
  if (node.warn) process.stderr.write(yellow('⚠ ' + node.warn) + '\n')
1246
1263
  if (svc.versionedPath(sbotBin)) process.stderr.write(yellow(`⚠ sbot 装在带版本号的 node 目录下(${sbotBin}),换 node 版本后要重装 sintbot 并重跑 sintbot service install`) + '\n')
1247
- if (inShell.length && !values['pass-env']) process.stderr.write(yellow(`⚠ 配置引用的环境变量 ${inShell.join(' ')} 只在当前终端里有,服务里拿不到 —— 加 --pass-env 把值写进服务定义(文件权限 600),或把值直接写进配置(配置本身是 600)`) + '\n')
1264
+ if (inShell.length && !passEnv) process.stderr.write(yellow(`⚠ 配置引用的环境变量 ${inShell.join(' ')} 只在当前终端里有,服务里拿不到 —— 加 --pass-env 把值写进服务定义(文件权限 600),或把值直接写进配置(配置本身是 600)`) + '\n')
1248
1265
  if (nowhere.length) process.stderr.write(yellow(`⚠ 配置引用的环境变量 ${nowhere.join(' ')} 当前终端里也没有 —— 用到它们的模型 / 平台不会工作`) + '\n')
1249
1266
  if (platform !== 'darwin' && platform !== 'linux') { process.stdout.write(svc.windowsHint(spec) + '\n'); return }
1250
1267
  if (!tools.length) process.stderr.write(yellow('⚠ PATH 里没找到 claude / codex —— 服务能起,但群里的轮次会失败;装好后重跑 sintbot service install') + '\n')
@@ -1400,6 +1417,30 @@ async function cmdSetup(rest) {
1400
1417
  codexLoggedIn = !!(codexLogin && codexLogin.ok && codexLogin.loggedIn)
1401
1418
  process.stdout.write(` ${cyan('✓')} codex ${dim(codexBin)}${codexOut ? ' ' + yellow('没登录 —— GPT 群、生图、额度用完时自动切到 codex 都要它:跑一次 codex login') : ''}\n`)
1402
1419
  }
1420
+ // 设计图标库(Lucide / Tabler / Phosphor / Pixelarticons):agent 出设计图时按名字取图标,缺了只能自己画、
1421
+ // 与设计图对不上。缺哪套问一句一次装齐(默认装);没人回答(输入已结束)不装,doctor 会接着提示
1422
+ {
1423
+ const iconRoots = () => design.iconRoots({ base: path.join(__dirname, '..'), extra: (raw.design && raw.design.iconsDir) || null })
1424
+ const missing = () => Object.entries(iconRoots()).filter(([, v]) => !v).map(([k]) => k)
1425
+ const miss = missing()
1426
+ if (!miss.length) process.stdout.write(` ${cyan('✓')} 设计图标库 ${dim(Object.values(design.ICON_LIBS).map((l) => l.label).join(' · '))}\n`)
1427
+ else {
1428
+ const libs = miss.map((k) => design.ICON_LIBS[k])
1429
+ const pkgs = libs.map((l) => l.pkg)
1430
+ const mb = Math.round(libs.reduce((n, l) => n + (l.mb || 0), 0))
1431
+ process.stdout.write(` ${gray('○')} 设计图标库缺 ${libs.map((l) => l.label).join(' / ')}(出设计图时 agent 按名字取图标)\n`)
1432
+ if (await askYN(`现在安装(npm i -g ${pkgs.join(' ')},约 ${mb} MB)?`, true, false)) {
1433
+ const r = installdeps.installPackages(pkgs, { log: (l) => process.stdout.write(dim(' ' + l) + '\n') })
1434
+ const still = missing()
1435
+ if (r.ok && !still.length) process.stdout.write(` ${cyan('✓')} 设计图标库已装\n`)
1436
+ else if (r.ok) {
1437
+ const pre = installdeps.globalPrefix()
1438
+ const dir = pre ? (process.platform === 'win32' ? path.join(pre, 'node_modules') : path.join(pre, 'lib', 'node_modules')) : 'npm 全局目录下的 node_modules'
1439
+ process.stdout.write(` ${yellow('!')} 装好了,但 sbot 找不到 ${still.join(' / ')} —— 在配置里把 design.iconsDir 设成 ${dir}\n`)
1440
+ } else process.stdout.write(` ${red('✗')} 安装失败(退出码 ${r.code})—— 多半是 npm 全局目录没有写权限,之后可以自己装:npm i -g ${pkgs.join(' ')}\n`)
1441
+ } else process.stdout.write(dim(` 要用时再装:npm i -g ${pkgs.join(' ')}`) + '\n')
1442
+ }
1443
+ }
1403
1444
  process.stdout.write(dim(` 配置文件:${cfgPath}`) + '\n\n')
1404
1445
 
1405
1446
  // 地区:决定有国际站的厂商连哪个站、要不要问代理、聊天平台推荐哪个(时区只给默认值,以回答为准)
@@ -1418,7 +1459,7 @@ async function cmdSetup(rest) {
1418
1459
  const readyKeyed = () => Object.keys(raw.providers).filter((n) => raw.providers[n].baseUrl && !raw.providers[n].hidden && setupmodels.keyPresent(raw.providers[n].apiKey))
1419
1460
  const verified = [] // 本次向导里试跑通过的
1420
1461
  const tried = new Set() // 本次试跑过的(不论成败),结尾不再重复问
1421
- // 试跑一轮(与控制台「模型 → 测试」同一个 runTurn):不用工具,只要一句自我介绍。
1462
+ // 试跑一轮(与控制台「模型 → 测试」同一个 runTurn 和提示词):不用工具,只要一句回复。
1422
1463
  // 关掉 claude 的自动重试:key 错时它默认反复重试约 3 分钟才放弃,关掉后半秒内就拿到 401 原文
1423
1464
  const tryTurn = async (name) => {
1424
1465
  const p = raw.providers[name]
@@ -1428,7 +1469,7 @@ async function cmdSetup(rest) {
1428
1469
  const t0 = Date.now()
1429
1470
  try {
1430
1471
  const { result, aborted } = await runner.runTurn({
1431
- bin: claudeBin || claudeName, prompt: '用一句话自我介绍,并说出你的模型名。', provider, model: provider.model,
1472
+ bin: claudeBin || claudeName, prompt: verify.TRIAL_PROMPT, provider, model: provider.model,
1432
1473
  workdir: os.tmpdir(), permissionMode: (raw.agent && raw.agent.permissionMode) || 'dontAsk', signal: ac.signal,
1433
1474
  extraEnv: { CLAUDE_CODE_MAX_RETRIES: '0' },
1434
1475
  })
@@ -1436,13 +1477,14 @@ async function cmdSetup(rest) {
1436
1477
  if (aborted || ac.signal.aborted) return { ok: false, text: `timeout:${Math.round((Date.now() - t0) / 1000)} 秒内没有回复`, ms: Date.now() - t0 }
1437
1478
  const ok = !!text && !(result && result.isError)
1438
1479
  verify.recordTurn(provider, ok, text) // doctor / 控制台据此报「能用」或「key 不对」
1439
- return { ok, text: text || '没有返回内容', ms: Date.now() - t0 }
1480
+ return { ok, text: text || '没有返回内容', ms: Date.now() - t0, model: provider.model }
1440
1481
  } catch (e) {
1441
1482
  verify.recordTurn(provider, false, (e && e.message) || String(e))
1442
1483
  return { ok: false, text: (e && e.message) || String(e), ms: Date.now() - t0 }
1443
1484
  } finally { clearTimeout(timer) }
1444
1485
  }
1445
- const reportTurn = (label, r) => process.stdout.write(` ${cyan('✓')} ${label} 能用(${(r.ms / 1000).toFixed(1)} 秒):${r.text.replace(/\s+/g, ' ').slice(0, 80)}\n`)
1486
+ // 型号取配置里的(r.model),不靠模型自报:走 Claude Code 的第三方模型常自称 Claude,新用户会以为没切成
1487
+ const reportTurn = (label, r) => process.stdout.write(` ${cyan('✓')} ${label}${r.model ? ' · ' + r.model : ''} 能用(${(r.ms / 1000).toFixed(1)} 秒):${r.text.replace(/\s+/g, ' ').slice(0, 80)}\n`)
1446
1488
  // 配一家用 API key 的:选站点 → 告诉去哪拿 key → 粘贴 → 当场试跑,失败说人话、可重试
1447
1489
  const configureKeyed = async (name) => {
1448
1490
  const info = setupmodels.PROVIDER_INFO[name] || { label: name }
@@ -1801,10 +1843,16 @@ async function cmdSetup(rest) {
1801
1843
  const cfg = loadConfig(cfgPath)
1802
1844
  await runDoctor(cfg)
1803
1845
  const cfgArg = cfgPath === path.join(os.homedir(), '.sbot', 'config.json') ? '' : ` --config ${cfgPath}`
1846
+ // 一个平台都没接时守护进程起不来(装服务会被拦下):先去控制台接平台,再装服务
1847
+ const plat = platformsConfigured(cfg)
1848
+ const noPlatform = !plat.feishu && !plat.wecom && !plat.slack
1804
1849
  process.stdout.write('\n' + gray('— 下一步 —') + '\n'
1805
1850
  + ` sintbot agent run --provider ${cfg.defaultProvider} "用一句话自我介绍"${cfgArg} ${dim('先在终端试一轮')}\n`
1806
- + ` sintbot service install${cfgArg} ${dim('装成常驻服务(开机/登录自启,崩溃自动拉起)')}\n`
1807
- + ` sintbot ui${cfgArg} ${dim('打开控制台:绑群、切模型、看实时活动')}\n`
1851
+ + (noPlatform
1852
+ ? ` sintbot ui${cfgArg} ${dim('还没接聊天平台:在控制台「设置」里填飞书 / Slack / 企业微信的凭据、绑群')}\n`
1853
+ + ` sintbot service install${cfgArg} ${dim('接好平台后,装成常驻服务(开机/登录自启,崩溃自动拉起)')}\n`
1854
+ : ` sintbot service install${cfgArg} ${dim('装成常驻服务(开机/登录自启,崩溃自动拉起)')}\n`
1855
+ + ` sintbot ui${cfgArg} ${dim('打开控制台:绑群、切模型、看实时活动')}\n`)
1808
1856
  + (!((cfg.providers || {})[cfg.defaultProvider] || {}).baseUrl
1809
1857
  ? '\n' + yellow('⚠ 订阅条款:') + '用个人的 Claude / ChatGPT 订阅登录来服务多人群聊,可能不符合提供方的使用条款;团队使用请改用 API key 或企业账号,并自行确认合规。\n' : ''))
1810
1858
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sintbot",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Drive headless Claude Code (or Codex) from Slack / Feishu / WeCom group chats — one group, one project, swappable model brain. A standalone daemon, not a Claude Code plugin.",
5
5
  "bin": {
6
6
  "sbot": "bin/sbot.js",
@@ -203,10 +203,11 @@ const T = {
203
203
  // ⚠ 「唯一写入方」这条约束没作废,只是换了兑现方式:群内切走的是**与控制台同一套写入动作**
204
204
  // (配置落盘 + 运行时覆盖 + 清会话一起做),不是新开一条写法。
205
205
  // `current`/`list` 都是预格式化的「模型名 · 型号」,免得把名字与型号混为一谈。
206
- // 没有注入写入器时(CLI 试跑)如实说不支持,别给一条按了没反应的指令。
206
+ // 没有注入写入器只发生在 CLI 试跑(sintbot chat;守护进程总会注入):如实说这里切不了、群里能切,
207
+ // 别给一条按了没反应的指令,也别说成「群内不支持」(1.0.9 及以前那样说,读起来像群里也切不了)。
207
208
  modelInfo: (current, list, canSwitch) => `【当前模型】${current}\n【可用】${list}\n` + (canSwitch
208
209
  ? '换模型:发「/model 名字」,例如 /model volcano\n⚠ 换完是**新的一段对话**,之前聊的内容不再带着'
209
- : '切换请管理员到控制台(群内不支持切换)'),
210
+ : '这里是终端试跑,不能切换 —— 换模型用 sintbot chat --provider 名字;在群里发「/model 名字」就能切'),
210
211
  modelUnknown: (want, names) => `⚠ 没有「${want}」这个模型\n【可用】${names}\n发「/model 名字」换,名字要一模一样`,
211
212
  modelSame: (current) => `本群已经在用 ${current},没有变化`,
212
213
  // ⚠ 忙的时候不切:换模型要清会话,而当前这轮正靠它跑;悄悄换掉等于把人手头的活废了。
@@ -485,7 +486,7 @@ function helpText(group, provider, hasImagen, caps = {}) {
485
486
  '· /skip — 只放弃手头这件,排队的继续;之前聊的还记得',
486
487
  '· /stop — 手头和排队的全取消;之前聊的还记得',
487
488
  '· /new — 全取消,并忘掉之前聊的,从头开始',
488
- '· /model — 本群在用的模型' + (canSwitchModel ? ';换:/model 名字(换完之前聊的不再带着)' : '(要换找管理员)'),
489
+ '· /model — 本群在用的模型' + (canSwitchModel ? ';换:/model 名字(换完之前聊的不再带着)' : '(终端试跑里不能切,换用 --provider)'),
489
490
  // 压缩那半句(v0.11.92):群里会冒出「🔄 会话已自动压缩 + 额度」这条通知,/help 里得有它的出处 ——
490
491
  // 否则第一次见到的人只会问「这是什么」;放在 /hud 这行,因为会话规模正是它管的。
491
492
  '· /hud — 额度与本会话用量;聊太长会自动压缩,群里会提示',
@@ -82,4 +82,8 @@ function when(at, now = Date.now()) {
82
82
  return d.toDateString() === n.toDateString() ? `今天 ${pad(d.getHours())}:${pad(d.getMinutes())}` : `${d.getMonth() + 1} 月 ${d.getDate()} 日`
83
83
  }
84
84
 
85
- module.exports = { fingerprint, providerFp, feishuFp, slackFp, lookup, record, recordTurn, recordPrecheck, when, REFRESH_MS }
85
+ // 试跑一轮用的提示词(向导试跑与控制台「模型 → 测试」共用)。不让模型自报身份:走 Claude Code 的第三方模型
86
+ // 常照系统提示自称 Claude,新用户会以为没切成;型号由调用方按配置显示
87
+ const TRIAL_PROMPT = '用一句话回复,说明你已准备好开始工作。'
88
+
89
+ module.exports = { fingerprint, providerFp, feishuFp, slackFp, lookup, record, recordTurn, recordPrecheck, when, REFRESH_MS, TRIAL_PROMPT }
package/src/design.js CHANGED
@@ -3154,16 +3154,18 @@ function _landingSentence(v, { mobile = false } = {}) {
3154
3154
  // 误报成「描边不合规」,而误报会让整份报告被无视(v0.11.29 间距/阴影不测是同一条理由)。
3155
3155
  // ⚠ `dirs` 从字符串数组改成结构:一个目录是什么形态、什么字重、文件名带什么后缀,**都得显式声明**,
3156
3156
  // 不能再靠对路径做正则(phosphor 的 fill 目录叫 `fill` 不叫 `filled`,正则当场就错)。
3157
+ // mb = 装好后实际占的磁盘(2026-10 在 macOS 上 du 实测;几千个小文件按块计,比 npm 报的 unpackedSize 大一倍多)。
3158
+ // 向导问「要不要代装」时报合计,版本变了会有出入
3157
3159
  const ICON_LIBS = {
3158
3160
  // lucide 与 tabler 的分发文件属性逐字相同(24 网格 / fill:none / stroke:currentColor / 2 / round),
3159
3161
  // **参数同构才是混用的依据** —— 检查按参数判、不按库名判,兜底也只在同构的两套之间成立。
3160
- lucide: { pkg: 'lucide-static', label: 'Lucide', license: 'ISC', form: 'stroke', grid: 24, dirs: [{ dir: 'icons', style: 'outlined' }] },
3161
- tabler: { pkg: '@tabler/icons', label: 'Tabler', license: 'MIT', form: 'stroke', grid: 24, dirs: [{ dir: 'icons/outline', style: 'outlined' }, { dir: 'icons/filled', style: 'filled' }] },
3162
+ lucide: { pkg: 'lucide-static', mb: 66, label: 'Lucide', license: 'ISC', form: 'stroke', grid: 24, dirs: [{ dir: 'icons', style: 'outlined' }] },
3163
+ tabler: { pkg: '@tabler/icons', mb: 49, label: 'Tabler', license: 'MIT', form: 'stroke', grid: 24, dirs: [{ dir: 'icons/outline', style: 'outlined' }, { dir: 'icons/filled', style: 'filled' }] },
3162
3164
  // ⚠ phosphor:**六个字重是六套独立几何**(不是同一套图的粗细参数),且 regular 档文件名无后缀、
3163
3165
  // 其余带 `-{字重}` 后缀(`gear.svg` / `gear-bold.svg`)—— 这是 `suffix` 字段存在的唯一理由。
3164
3166
  // duotone 用两条 path + opacity 0.2 做副色,副色仍继承 currentColor(不是第二种颜色)。
3165
3167
  phosphor: {
3166
- pkg: '@phosphor-icons/core', label: 'Phosphor', license: 'MIT', form: 'fill', grid: 256,
3168
+ pkg: '@phosphor-icons/core', mb: 37, label: 'Phosphor', license: 'MIT', form: 'fill', grid: 256,
3167
3169
  dirs: [
3168
3170
  { dir: 'assets/thin', style: 'outlined', weight: 'thin', suffix: '-thin' },
3169
3171
  { dir: 'assets/light', style: 'outlined', weight: 'light', suffix: '-light' },
@@ -3175,7 +3177,7 @@ const ICON_LIBS = {
3175
3177
  },
3176
3178
  // ⚠ pixelart:实心变体是**同目录 `-solid` 后缀**(不是子目录),而且**只覆盖 53/651**(实测)
3177
3179
  // ⇒ 系统那边**不声明支持实心** —— 声明了就是句假话,八成的名字取不到实心。
3178
- pixelart: { pkg: 'pixelarticons', label: 'Pixelarticons', license: 'MIT', form: 'fill', grid: 24, dirs: [{ dir: 'svg', style: 'outlined' }] },
3180
+ pixelart: { pkg: 'pixelarticons', mb: 15, label: 'Pixelarticons', license: 'MIT', form: 'fill', grid: 24, dirs: [{ dir: 'svg', style: 'outlined' }] },
3179
3181
  }
3180
3182
  // ── 图标系统(v0.11.53)= 与设计风格**平级**的实体 ────────────────────────────
3181
3183
  // **图标系统 = 图形从哪来 + 叫什么名 + 怎么起名;风格 = 我用哪套 + 怎么画它。**
package/src/health.js CHANGED
@@ -277,10 +277,10 @@ async function checkHealth(cfg, opts = {}) {
277
277
  if (!cfg.feishu.botOpenId) add('warn', 'feishu.botOpenId', (cfg.feishu.onlyWhenMentioned
278
278
  ? '没填 — 开着「只在 @ 时回复」却认不出哪个 @ 是机器人,群里一条都不会回'
279
279
  : '没填 — 分不清 @机器人 和 @别人,群里 @ 别人的消息也会接') + ';重跑 sintbot setup 选飞书预检可自动填')
280
- } else if (!hasId && !hasSecret) { if (!plat.wecom && !plat.slack) add('warn', 'feishu', '没配任何聊天平台 — sintbot start 收不到消息') }
280
+ } else if (!hasId && !hasSecret) { if (!plat.wecom && !plat.slack) add('warn', 'platforms', '没配任何聊天平台 — sintbot start 收不到消息') }
281
281
  else add('fail', 'feishu', hasId ? '缺 appSecret' : '缺 appId')
282
282
  } else if (!plat.wecom && !plat.slack) {
283
- add('warn', 'feishu', '没配任何聊天平台 — sintbot start 收不到消息')
283
+ add('warn', 'platforms', '没配任何聊天平台 — sintbot start 收不到消息')
284
284
  }
285
285
  // 写了 wecom 块就要报:env: 引用解析成空串也算缺(真机踩到 —— 非交互 shell 没 source 凭据文件,doctor 一声不吭地把群报成「没配企微」)
286
286
  if (cfg.wecom) {
@@ -21,8 +21,9 @@ function chooseRegistry(configured, officialReachable) {
21
21
  return { registry: MIRROR, reason: 'mirror' }
22
22
  }
23
23
 
24
+ // pkg 可以是一个包名,也可以是一组(设计图标库四个包一条命令装齐)
24
25
  function installArgs(pkg, registry) {
25
- return ['install', '-g', pkg, ...(registry ? ['--registry', registry] : [])]
26
+ return ['install', '-g', ...[].concat(pkg), ...(registry ? ['--registry', registry] : [])]
26
27
  }
27
28
 
28
29
  function runNpm(args, opts = {}) {
@@ -55,6 +56,11 @@ function globalPrefix(run = runNpm) {
55
56
  function installGlobal(name, { run = runNpm, log = () => {} } = {}) {
56
57
  const pkg = PACKAGES[name]
57
58
  if (!pkg) throw new Error(`unknown package: ${name}`)
59
+ return installPackages(pkg, { run, log })
60
+ }
61
+
62
+ // 按包名装(一个或一组),选源同上。只给向导装确定的包用(Claude Code / Codex / 设计图标库),不接用户输入的名字
63
+ function installPackages(pkg, { run = runNpm, log = () => {} } = {}) {
58
64
  const { registry, reason } = pickRegistry(run)
59
65
  if (reason === 'mirror') log(`npm 官方源连不上,改用国内镜像 ${MIRROR}`)
60
66
  const args = installArgs(pkg, registry)
@@ -63,4 +69,4 @@ function installGlobal(name, { run = runNpm, log = () => {} } = {}) {
63
69
  return { ok: r.status === 0, pkg, registry, code: r.status }
64
70
  }
65
71
 
66
- module.exports = { OFFICIAL, MIRROR, PACKAGES, isOfficial, chooseRegistry, installArgs, pickRegistry, globalPrefix, installGlobal }
72
+ module.exports = { OFFICIAL, MIRROR, PACKAGES, isOfficial, chooseRegistry, installArgs, pickRegistry, globalPrefix, installGlobal, installPackages }
package/src/ui/server.js CHANGED
@@ -627,11 +627,11 @@ function createUiServer({ configPath, getLiveState = null, getConnHealth = null,
627
627
  try {
628
628
  const { result } = await runTurn({
629
629
  bin: cfg.agent.bin,
630
- prompt: prompt || '用一句话自我介绍,并说出你的模型名。',
630
+ prompt: prompt || verify.TRIAL_PROMPT,
631
631
  provider,
632
632
  model: provider.model,
633
633
  permissionMode: cfg.agent.permissionMode,
634
- signal: ac.signal, // a one-line self-intro — needs no tools, so no allow list
634
+ signal: ac.signal, // a one-line reply — needs no tools, so no allow list
635
635
  })
636
636
  const ok = !(result && result.isError)
637
637
  verify.recordTurn(provider, ok, (result && result.text) || '', vopts) // doctor / 概览据此报「能用」或「key 不对」