sintbot 1.0.14 → 1.0.16

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,38 @@
1
1
  [
2
+ {
3
+ "version": "1.0.16",
4
+ "date": "2026-10-30",
5
+ "name": "Safer AI-assisted setup, quieter design tools",
6
+ "items": [
7
+ {
8
+ "t": "Safer AI-assisted installation",
9
+ "d": "API keys are masked as they are entered in the local terminal, keeping them out of AI conversation logs. Without an interactive terminal, the installer stops if Node is missing or too old; otherwise it skips model setup and the background service and explains what remains."
10
+ },
11
+ {
12
+ "t": "Clearer install and first-use flow",
13
+ "d": "Add bilingual guides for AI-assisted installation and first use. The one-line preview lists planned operations and confirmations, and warns that a model trial may use quota or incur a charge."
14
+ },
15
+ {
16
+ "t": "Design and icon libraries on demand",
17
+ "d": "Both libraries are collapsed by default; style presets are recommended only for a new product with no visual direction yet."
18
+ }
19
+ ]
20
+ },
21
+ {
22
+ "version": "1.0.15",
23
+ "date": "2026-10-30",
24
+ "name": "Clearer console settings",
25
+ "items": [
26
+ {
27
+ "t": "Software updates and history together",
28
+ "d": "Update controls and version history now share one card at the bottom of Settings. A small dot appears by the version number when a newer stable release is available."
29
+ },
30
+ {
31
+ "t": "A wider Bot & network card",
32
+ "d": "The card spans the full settings grid, with the bot name and proxy arranged side by side. Background proxy details can be expanded when needed."
33
+ }
34
+ ]
35
+ },
2
36
  {
3
37
  "version": "1.0.14",
4
38
  "date": "2026-10-30",
package/CHANGELOG.json CHANGED
@@ -1,4 +1,38 @@
1
1
  [
2
+ {
3
+ "version": "1.0.16",
4
+ "date": "2026-10-30",
5
+ "name": "AI 安装更安全,设计入口更克制",
6
+ "items": [
7
+ {
8
+ "t": "AI 协助安装更安全",
9
+ "d": "API key 在本机终端输入时会遮蔽回显,避免进入 AI 对话记录。没有交互终端时,缺少合格 Node 就停止;已有 Node 时则跳过模型配置和后台服务并说明后续步骤。"
10
+ },
11
+ {
12
+ "t": "安装流程与首次使用更清晰",
13
+ "d": "新增中英文 AI 安装与首次使用指南;一键预览会列出操作和确认项,并提示模型试跑可能消耗额度或产生费用。"
14
+ },
15
+ {
16
+ "t": "设计库和图标库按需展示",
17
+ "d": "两类资源默认折叠;仅在从零设计新产品且尚未确定视觉方向时推荐风格预设。"
18
+ }
19
+ ]
20
+ },
21
+ {
22
+ "version": "1.0.15",
23
+ "date": "2026-10-30",
24
+ "name": "控制台设置布局更清晰",
25
+ "items": [
26
+ {
27
+ "t": "软件更新与版本历史合并",
28
+ "d": "合并为一张卡片并移到设置页底部;检测到新版时,版本号旁会显示一个提示点。"
29
+ },
30
+ {
31
+ "t": "机器人与网络卡片横跨整行",
32
+ "d": "机器人的名字和网络代理并排显示,后台网络详情可以展开查看。"
33
+ }
34
+ ]
35
+ },
2
36
  {
3
37
  "version": "1.0.14",
4
38
  "date": "2026-10-30",
package/README.md CHANGED
@@ -43,7 +43,9 @@ Full source, docs and tests are public and downloadable under the [PolyForm Shie
43
43
  curl -fsSL https://sintbot.com/install.sh | bash
44
44
  ```
45
45
 
46
- The script checks for Node.js 22+, installs sintbot, sets up a model with a live trial turn and offers to install the background service, then opens the console at http://127.0.0.1:4600. It asks before every change and never uses `sudo`. Read [install.sh](./install.sh) first, or add `--dry-run` to see what it would do.
46
+ The script checks for Node.js 22+, installs or upgrades sintbot, sets up a model with a live trial turn and offers to install the background service, then opens the console at http://127.0.0.1:4600. It asks before Homebrew Node changes and background-service setup, and never uses `sudo`. Read [install.sh](./install.sh) first, or add `--dry-run` to preview the plan.
47
+
48
+ Installing and setting up SintBot with an AI assistant? Follow the [AI-assisted install and first-use guide](./docs/AI-ASSISTED-INSTALL.md); it covers the target-machine terminal, first console setup and a safe first task.
47
49
 
48
50
  To install by hand instead:
49
51
 
package/README.zh-CN.md CHANGED
@@ -43,7 +43,9 @@ SintBot 是一个跑在你自己机器上的守护进程,把能写代码的 AI
43
43
  curl -fsSL https://sintbot.com/install.sh | bash
44
44
  ```
45
45
 
46
- 脚本会检查并装好 Node.js 22+、装 sintbot、配好模型(当场试跑一轮),再问要不要装成后台服务,最后打开控制台 http://127.0.0.1:4600。每一步先问再动,不用 `sudo`。可以先读 [install.sh](./install.sh),或加 `--dry-run` 只看它会做什么。
46
+ 脚本会检查 Node.js 22+、安装或升级 sintbot、配置模型(当场试跑一轮),并询问是否安装后台服务,最后打开控制台 http://127.0.0.1:4600。安装或升级 Node 的 Homebrew 操作和后台服务会先询问,不用 `sudo`。可以先读 [install.sh](./install.sh),或加 `--dry-run` 预览计划。
47
+
48
+ 想让本机 AI 助手帮忙安装并完成首次使用?先看[AI 安装与首次使用说明](./docs/zh-CN/AI-ASSISTED-INSTALL.md),里面有本机终端、控制台初始配置和安全试跑步骤。
47
49
 
48
50
  也可以手动装:
49
51
 
package/bin/sbot.js CHANGED
@@ -12,6 +12,7 @@ const { pathToFileURL } = require('node:url')
12
12
  const path = require('node:path')
13
13
  const readline = require('node:readline')
14
14
  const { parseArgs } = require('node:util')
15
+ const { createReadlineSecretMask } = require('../src/cli-secret')
15
16
  const { tr } = require('../src/clilang')
16
17
  const { PROXY_ENV_KEYS } = require('../src/core/proxy')
17
18
  const { loadConfig, findConfigPath, defaultConfigPath, applyProxyEnv, deriveNoProxy, resolveSecret, platformsConfigured, chatPlatform } = require('../src/config')
@@ -1406,6 +1407,7 @@ async function cmdSetup(rest) {
1406
1407
  // On EOF, pending asks resolve to null → the caller falls back to its default,
1407
1408
  // so a short/redirected input just accepts defaults instead of hanging.
1408
1409
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
1410
+ const secretMask = createReadlineSecretMask(rl)
1409
1411
  // 问题交给 readline 当提示符写(setPrompt + prompt),不直接写 stdout:readline 一重绘这一行(改窗口大小、退格)
1410
1412
  // 就按它记的提示符重写 —— 默认是 `> `,1.0.11 实装里问题文字被吞成「> y」、试跑时冒出一个 `>`。问完和空闲时置空。
1411
1413
  rl.setPrompt('')
@@ -1421,11 +1423,13 @@ async function cmdSetup(rest) {
1421
1423
  // 1.0.8 的模型菜单默认「1」→ key 为空 → 回菜单,3 分钟往输出里写了 18 GB。所以 ① 会转圈或有副作用的问题
1422
1424
  // 另给一个「输入已结束」时的答案(endedDef);② 兜底:输入结束后还问了这么多次就中止,配置不落盘。
1423
1425
  let askedAfterEnd = 0
1424
- const ask = async (q, def, endedDef = def) => {
1426
+ const ask = async (q, def, endedDef = def, { maskInput = false } = {}) => {
1425
1427
  const line = `${cyan('?')} ${q}${def ? dim(` [${def}]`) : ''} `
1426
1428
  if (closed) process.stdout.write(line)
1427
1429
  else { rl.setPrompt(line); rl.prompt() }
1428
- const a = await nextLine()
1430
+ if (maskInput && !closed) secretMask.start(line)
1431
+ let a
1432
+ try { a = await nextLine() } finally { if (maskInput) secretMask.stop() }
1429
1433
  if (!closed) rl.setPrompt('')
1430
1434
  if (a == null) {
1431
1435
  process.stdout.write('\n')
@@ -1438,10 +1442,10 @@ async function cmdSetup(rest) {
1438
1442
  const a = (await ask(`${q} ${dim(defYes ? '(Y/n)' : '(y/N)')}`, '', endedYes ? 'y' : 'n')).toLowerCase()
1439
1443
  return a ? a[0] === 'y' : !!defYes
1440
1444
  }
1441
- // Key/secret prompt: echoes visibly (so you can see the paste landed) and then
1442
- // confirms the length — fully hiding it made it look like nothing was entered.
1445
+ // Key/secret prompt: mask terminal echo so a local AI agent's terminal capture
1446
+ // cannot copy the credential into its transcript; confirm receipt by length.
1443
1447
  const askKey = async (q) => {
1444
- const v = await ask(q, '')
1448
+ const v = await ask(q, '', undefined, { maskInput: true })
1445
1449
  if (v) process.stdout.write(dim(` ✓ 已记录(${v.length} 位)`) + '\n')
1446
1450
  return v
1447
1451
  }
@@ -1917,6 +1921,7 @@ async function cmdSetup(rest) {
1917
1921
  else delete raw.feishu
1918
1922
  }
1919
1923
  } finally {
1924
+ secretMask.restore()
1920
1925
  rl.close()
1921
1926
  }
1922
1927
 
@@ -0,0 +1,43 @@
1
+ # Install and start using SintBot with an AI assistant
2
+
3
+ [简体中文](./zh-CN/AI-ASSISTED-INSTALL.md) · English
4
+
5
+ An AI assistant can install SintBot only if it can use a **local terminal on the computer where SintBot will run**. A chat-only session or cloud shell cannot install it on your Mac. If the assistant cannot reach that terminal, run the install command in Terminal on the target computer, then ask the assistant to continue with verification.
6
+
7
+ The one-line installer supports macOS and Linux (Linux support is experimental). Windows users should follow the [manual install steps](./install.md#windows).
8
+
9
+ ## Shortest path
10
+
11
+ 1. **Inspect first.** Check the target OS, Node version, installed SintBot version and service state. Do not read or print configuration or credentials.
12
+ 2. **Pick the right path.** For a fresh install, download the official installer to a temporary file, inspect it, and run its no-change preview; execute that same file after approval. For an upgrade, update only the package; do not repeat model setup or its trial. Restarting a running service can announce the upgrade to connected groups.
13
+ 3. **Confirm once.** Before changing anything, give one short summary of Homebrew Node changes, the required Claude Code CLI if missing, the selected model and possible trial charge, and whether the background service will be installed or restarted. Ask only for choices that are still needed. After approval, answer installer prompts using those choices; do not ask for the same approval again. Stop and ask if an unexpected change appears.
14
+ 4. **Run locally.** Use the target computer's interactive terminal. Let the user sign in themselves and type API keys directly at SintBot's masked terminal prompt. Never copy credentials into chat or the agent transcript, and do not use `sudo`.
15
+ 5. **Complete the first use.** Check the version, health and service state. In the console, follow **Get started**: confirm a usable model, connect a chat platform if needed, bind one group to one project folder, then send a read-only test request. To test a model before connecting a platform, use **Playground**.
16
+
17
+ If the user needs to create or approve a chat-platform app, guide them with the relevant [platform setup guide](./platforms/). The user enters credentials directly in the local console. Ask for only the next decision needed; do not make them repeat information already provided.
18
+
19
+ ## If the assistant cannot use a local terminal
20
+
21
+ Do not run a real installation on another computer or in a cloud shell. A no-change `--dry-run` preview is fine, but without an interactive terminal a real install stops when Node 22+ is missing; if Node is present, it may install only the package and report incomplete setup. Have the user run the [one-line install](./install.md#one-line-install) on the target computer, then continue verification.
22
+
23
+ ## Copyable request
24
+
25
+ ```text
26
+ Help me install or upgrade SintBot on this computer and get through the first use. Use only the local terminal on the computer where it will run. First check the OS, Node version, SintBot version and service state; do not read configuration or credentials.
27
+
28
+ For a fresh install, download the official installer to a temporary file, inspect it and run its no-change preview; execute that same file after approval. For an upgrade, update only the package; do not rerun model setup or its trial. Before changes, give me one concise summary covering Node / Claude Code installs, model choice and possible trial cost, and background-service install or restart (including any group notice). Ask once for the decisions needed. After I approve, answer matching installer prompts from my choices without asking again; stop if a new change is required.
29
+
30
+ Use the target computer's interactive terminal. I will sign in and type any API key directly at the masked local prompt; never put credentials in chat. Do not use sudo or reset existing data. After installation, verify the version, health and service state, then guide me through the console's Get started steps. We can use Playground first, or connect a platform, bind one group to one project and send a read-only test. Ask only for the next required choice; stop on errors and explain the cause.
31
+ ```
32
+
33
+ ## Give an AI agent a clear task
34
+
35
+ For coding work, include the goal, acceptance criteria and constraints. For example:
36
+
37
+ ```text
38
+ Goal: …
39
+ Done means: …
40
+ Constraints: …
41
+
42
+ Read the project's instructions and current state first. If the request is clear, proceed; ask only when a missing fact blocks the work. Finish with a short summary of changes, checks and anything still open.
43
+ ```
@@ -0,0 +1,199 @@
1
+ # Install
2
+
3
+ English · [简体中文](./zh-CN/install.md)
4
+
5
+ The [one-line install](#one-line-install) is the easiest path for new users. A manual install takes three steps: install the package, run `sintbot setup`, then run `sintbot service install`. If an AI assistant will install and configure it, follow the [AI-assisted install and first-use guide](./AI-ASSISTED-INSTALL.md) to keep the flow on the target machine and credentials out of chat. The sections below add platform-specific details.
6
+
7
+ The package installs two equivalent commands, `sintbot` and `sbot`.
8
+
9
+ ## One-line install
10
+
11
+ On macOS and Linux (Linux support is experimental):
12
+
13
+ ```bash
14
+ curl -fsSL https://sintbot.com/install.sh | bash
15
+ ```
16
+
17
+ The script runs four numbered steps and skips work that is already done. It prompts before a Homebrew Node install or upgrade and before installing the background service (Enter accepts the default); installing or upgrading SintBot is part of the requested install. During model setup, a missing required Claude Code CLI is installed automatically. The terminal only installs SintBot and sets up a model: it does not ask where you are or for a proxy, a projects folder or chat platform credentials. You set those in the console afterwards.
18
+
19
+ 1. **Environment: Node.js 22+.** In an interactive install, the script asks first. It can install or upgrade Node with Homebrew on a Mac that has Homebrew; otherwise (no Homebrew, or Linux), it prints the [nodejs.org](https://nodejs.org) download link and stops. Without an interactive terminal, a real install stops instead of installing or upgrading Node automatically.
20
+ **npm registry.** If you have configured a registry, the script uses it. If you are on the official registry, it first tests the connection. If the official registry is unreachable (common in mainland China), it uses the `registry.npmmirror.com` mirror for this run.
21
+ 2. **Install sintbot.** It runs `npm i -g sintbot`. If the latest version is already installed, it says so and moves on; an older version is upgraded. If the global npm directory is not writable, it does not fall back to `sudo`; it prints the commands that move the global directory into your home folder.
22
+ 3. **Model**, `sintbot setup`. The wizard installs Claude Code if it is missing (see the next section), then sets up a model with a live trial turn. See [section 2](#2-configure-sintbot-setup).
23
+ 4. **Background service.** It asks whether to run `sintbot service install`, which starts SintBot at login and restarts it on crash. The console then stays up at `http://127.0.0.1:4600`, even before a chat platform is connected, and the script opens it in your browser (not over SSH). If the service is already running and nothing was upgraded, the step is skipped; after an upgrade it asks whether to restart the service, which announces the upgrade in connected groups.
24
+
25
+ The script ends with the console address and what to do next: connect Slack, Feishu or WeCom under Settings, bind a group under Groups, then @mention the bot in that group. `sintbot chat` lets you try it in the terminal first.
26
+
27
+ - To preview the script without installing or changing the system or answering prompts, run `curl -fsSL https://sintbot.com/install.sh | bash -s -- --dry-run`. When npm is available, it checks registry reachability and the latest version; it also reads service status and warns that setup may install the required CLI and send a billable trial request. The script source is [install.sh](../install.sh) at the root of the repository.
28
+ - Without an interactive terminal (for example in CI), a real install stops if Node 22+ is missing. If Node is already available, it may install only the package; the wizard and service steps are skipped and setup is reported as incomplete. A non-interactive `--dry-run` can still preview the plan.
29
+ - The script and the wizard print Chinese if your locale is Chinese (`LANG` starting with `zh`) or, on macOS, the system language is Chinese; otherwise English. Set `SINTBOT_LANG=en` or `SINTBOT_LANG=zh` to choose, for example `curl -fsSL https://sintbot.com/install.sh | SINTBOT_LANG=en bash`.
30
+ - The one-line install does not support Windows; follow the manual steps below.
31
+
32
+ ## Claude Code: install and sign in
33
+
34
+ SintBot runs every turn through the `claude` CLI on this machine (Codex groups use `codex`), so Claude Code must be installed. This is true even if you only use DeepSeek or another API-key model: Claude Code is the agent framework, and in that case it needs no login.
35
+
36
+ **Install it** in any of these ways:
37
+
38
+ - **Let the wizard do it.** If `sintbot setup` finds no Claude Code, it installs it when run in a terminal. It runs the npm command below and switches to the mirror when the official registry is unreachable.
39
+ - **npm:** `npm i -g @anthropic-ai/claude-code`. If the official registry is unreachable, add `--registry https://registry.npmmirror.com`.
40
+ - **The official installer:** see the [Claude Code setup guide](https://code.claude.com/docs/en/setup).
41
+
42
+ When `claude --version` prints a version, you are done. Upgrade with `claude update` (or `npm i -g @anthropic-ai/claude-code@latest` for an npm install).
43
+
44
+ **Authenticate** according to the model you plan to use:
45
+
46
+ | Model | What to do |
47
+ |---|---|
48
+ | DeepSeek, Zhipu GLM, MiniMax, Volcano Ark and other API-key models | **No login.** Paste the key in the wizard; see [models.md](./models.md#use-deepseek-or-another-api-key-model-in-three-steps) |
49
+ | Claude, on a subscription (Pro / Max) | Run `claude auth login` and sign in in the browser that opens. `claude auth status` shows whether you are signed in |
50
+ | Claude, on an Anthropic API key (pay as you go) | Run `claude auth login --console`, or put `ANTHROPIC_API_KEY` in the daemon's environment |
51
+
52
+ - **Headless machines** (such as a Mac mini used as a server): use Screen Sharing (`vnc://<machine-ip>`) to reach the desktop and run `claude auth login` once. The login is saved to disk, so the daemon keeps working unattended.
53
+ - For the terms-of-service question around serving a group from a personal subscription, see [Security in the README](../README.md#security).
54
+ - **To verify:** run `claude --version`, then `claude auth status` (subscription only), then `sintbot agent run "introduce yourself in one sentence"`, a read-only trial turn in the terminal.
55
+
56
+ ## Requirements
57
+
58
+ | Requirement | Check | Notes |
59
+ |---|---|---|
60
+ | Node.js ≥ 22 | `node -v` | The current LTS is fine. The browser takeover page needs Node's built-in WebSocket |
61
+ | Claude Code | `claude --version` | `sintbot setup` can install it for you; see the previous section. With API-key models only, it can stay signed out |
62
+ | Codex CLI (optional) | `codex --version` | Needed only with a ChatGPT account, for GPT groups and image generation. Run `npm i -g @openai/codex`, then `codex login` (browser OAuth, needs a desktop) |
63
+ | Design icon libraries (optional) | `sintbot doctor` (the `design.icons` row) | Lucide, Tabler, Phosphor and Pixelarticons, about 170 MB in all. The agent takes icons from them by name when it draws a design; without them it draws its own, which may not match the design |
64
+
65
+ The first step of `sintbot setup` checks all of these. If Claude Code is missing, it installs it. Codex and the design icon libraries are optional; if they are missing, the wizard prints the install command and moves on. The login status of Claude and Codex is shown in the model step.
66
+
67
+ ## 1. Install the package
68
+
69
+ ```bash
70
+ npm i -g sintbot
71
+ sintbot version
72
+ ```
73
+
74
+ The package is **`sintbot`**. The `sbot` package on npm belongs to someone else, so do not install that one by mistake.
75
+
76
+ > ⚠ If the machine has both a Homebrew node and an nvm node, `npm i -g` installs into **the one your current shell uses**. The service uses whichever node was active when you ran `sintbot service install`, because that path is written into the service. When the two differ, you get "I upgraded but the old version is still running". Run `sintbot service install --dry-run` to see which node the service uses.
77
+
78
+ To run from source:
79
+
80
+ ```bash
81
+ git clone https://github.com/sintlab/sintbot.git && cd sintbot
82
+ npm install
83
+ npm link # optional: link the sintbot / sbot commands globally
84
+ ```
85
+
86
+ ## 2. Configure: `sintbot setup`
87
+
88
+ This is an interactive wizard; Enter accepts the default (the value in brackets, or the capital letter in `(Y/n)`). It only covers what SintBot needs to run on this machine:
89
+
90
+ 1. **Environment.** Claude Code (installed if missing), Codex and the design icon libraries (optional; the install command is printed). See [Requirements](#requirements).
91
+ 2. **Model.** The wizard first shows the login status of Claude and ChatGPT (Codex), plus any models whose keys are already set, then recommends in this order:
92
+ - If Claude is signed in, the default is Claude; otherwise, if Codex is signed in, the default is Codex. Nothing more is asked; add other models in the console under Models.
93
+ - If neither is signed in, it **recommends DeepSeek**: it tells you to get a key at platform.deepseek.com and **runs a trial turn** as soon as you paste it. You can also pick another provider (Zhipu GLM, MiniMax, Volcano Ark), sign in to Claude or Codex in another terminal and come back to re-check, or skip for now.
94
+ - Zhipu GLM and MiniMax have international sites (Z.ai and minimax.io), and a key only works on the site that issued it. The wizard tries one site and, if the key is rejected, tries the other one automatically. Without Claude Code it cannot run a trial, so it asks which site issued the key.
95
+ - If the trial turn fails, the wizard tells you why: a wrong key, an empty balance or missing identity verification, a network problem (is a global proxy on?), or a wrong model name. You can paste the key again and retry.
96
+ - The wizard picks the default model itself (a signed-in subscription first, then a model whose trial passed, then one with a key) and runs a trial turn if it has not had one yet. Groups can switch any time with `/model`.
97
+ - If Codex is not installed or not signed in, the Codex preset and the automatic fallback to Codex are left out of the config. Kimi is not a preset; add it in the console under Models → Add model → Custom. See [models.md](./models.md).
98
+
99
+ The config is written to `~/.sbot/config.json` with permissions 600. If the current directory already has a `sbot.config.json`, that file is updated instead. Running `sintbot setup` again only fills in what is missing and leaves existing values alone.
100
+
101
+ The wizard ends with a short health check that lists only failures; `sintbot doctor` shows the full list.
102
+
103
+ **Everything else is in the console** (`sintbot ui`, or `http://127.0.0.1:4600` once the service runs):
104
+
105
+ | What | Where in the console |
106
+ |---|---|
107
+ | Chat platform credentials (Slack, Feishu, WeCom), precheck, Feishu admins | Settings; each platform has a step-by-step guide. See [platforms/](./platforms/) |
108
+ | Binding groups to project folders | Groups |
109
+ | Bot name, network proxy | Settings → Bot & network |
110
+ | Projects folder | Read-only in the console (it is the fence for what the console may bind). The default is `~/Projects`; to change it, edit `projectsRoots` in the config or run `sintbot setup --full` |
111
+
112
+ **`sintbot setup --full`** asks the old full set of questions in the terminal, for servers without a browser: proxy routing, bot name, chat platform credentials with precheck, projects folder and a first group. This part of the wizard is in Chinese only.
113
+
114
+ ## 3. Run as a service: `sintbot service install`
115
+
116
+ ```bash
117
+ sintbot service install # write and start the service
118
+ sintbot service install --ui-host 0.0.0.0 # open the console to the LAN (run sintbot ui --set-password first)
119
+ sintbot service status | restart | logs | uninstall
120
+ ```
121
+
122
+ - **macOS:** writes `~/Library/LaunchAgents/com.sbot.daemon.plist`, a LaunchAgent that starts at login and restarts 15 seconds after a crash.
123
+ - **Linux** (experimental: in 1.0 the generated unit is covered by unit tests but has not been installed on a real machine): writes `~/.config/systemd/user/sbot.service`. To start at boot without logging in, also run `loginctl enable-linger $USER`.
124
+ - **Logs:** `~/.sbot/logs/err.log`; `sintbot service logs` shows the tail.
125
+ - **What the service pins:** the node path, the sintbot entry point, the absolute config path, and the PATH of your current shell (with the directories of `claude` and `codex` first). **After changing node, moving the config or newly installing claude, run `sintbot service install` again.**
126
+ - **Node path:** a Homebrew node really lives in a versioned directory that disappears on upgrade, so install switches to an unversioned path to the same program (such as `/opt/homebrew/bin/node`). For nvm, volta and other managers without a stable path, it keeps the original path and reminds you to reinstall the service after changing node versions.
127
+ - **Environment variables:** the service does **not** inherit your shell's variables, so install writes the values it needs into the service definition (the file is set to 600, and `--dry-run` masks them): every `"env:NAME"` in the config, and your shell's `https_proxy` and similar variables when the config has no proxy. After changing a key or the proxy, run install again; or put the values directly in the config, which is itself 600 (for the proxy: the console's Settings → Bot & network).
128
+ - **Console exposure:** if the console is opened beyond this machine (`--ui-host` set to a non-loopback address) without a password, install refuses; otherwise the service would exit on every start.
129
+ - **No chat platform yet:** the service still installs and runs the console only. Connect a platform under Settings and save; the service restarts itself within seconds and connects. (Versions 1.0.10 and 1.0.11 refused to install in this case.)
130
+ - **`sintbot ui` while the service runs** opens the service's console instead of failing on the busy port.
131
+ - ⚠ **Do not start a second daemon.** Once the service is installed, do not also run `sintbot start` by hand: only one daemon is allowed per OS user (lock file `~/.sbot/daemon.lock`), and a second one reports the running one's pid and exits. If another machine or OS user starts a daemon with **the same bot app**, Feishu and Slack hand each message **to one of the connections at random**, so the other never sees it. WeCom lets the new connection replace the old one, and a connection replaced twice within 10 minutes stops reconnecting.
132
+
133
+ ### Upgrade
134
+
135
+ The console's **Settings → Software updates** checks for stable releases at startup and once daily; you can also check manually. Automatic checks are always on. An administrator must confirm before installation. SintBot waits for every group task and queue to clear, then installs only the exact version just checked. It never escalates privileges: if the global install is not writable or this process is not running from that install, use the terminal instead.
136
+
137
+ After verifying the installed version, a managed background service restarts automatically. A foreground process must be restarted manually. You can ignore the detected version and choose whether to announce an upgrade in groups. A failed network check leaves the last successful result intact.
138
+
139
+ ```bash
140
+ npm i -g sintbot@latest && sintbot service restart
141
+ ```
142
+
143
+ A restart aborts turns in progress. An aborted turn is redone automatically after the restart, and the group is told. The safest time to restart is when every group is idle on the console's Overview page.
144
+
145
+ ## macOS: a Mac mini as a server
146
+
147
+ Unattended use needs three things: no sleep, automatic restart after a power cut, and automatic login to the desktop after a reboot.
148
+
149
+ ```bash
150
+ sudo pmset -a sleep 0 disablesleep 1 autorestart 1 powernap 0
151
+ ```
152
+
153
+ Then turn on **automatic login** in System Settings → Users & Groups. A LaunchAgent only starts inside a user session, because it reads the logins in `~/.claude` and `~/.codex` and the keychain; without automatic login, the service does not come back after a reboot.
154
+
155
+ `codex login` needs a browser. Do it once over Screen Sharing (`vnc://<machine-ip>`); the credentials are saved to disk and work unattended afterwards.
156
+
157
+ ## Windows
158
+
159
+ > 1.0 has not been verified on a real Windows machine (CI runs Linux only). The notes below follow the code's behavior; please open an issue if something does not work.
160
+
161
+ - If `claude` was installed with npm, it is really a `claude.cmd` shim. This is handled automatically; just write `claude` in the config.
162
+ - **Service:** on Windows, `sintbot service install` does not install anything. Instead it prints ready-made commands for [NSSM](https://nssm.cc/) and pm2; run them. With NSSM, pass API keys and proxy variables into the service environment with `nssm set SintBot AppEnvironmentExtra …`.
163
+ - The default projects folder is `D:\Projects` (or `%USERPROFILE%\Projects` if there is no D: drive).
164
+
165
+ ## Proxies (mainland China)
166
+
167
+ In mainland China, official Claude, ChatGPT / Codex and Slack need a proxy, while domestic model endpoints, Feishu and WeCom connect directly. Set the proxy in the console under Settings → Bot & network (or with `sintbot setup --full`); the config's `proxy` block is then applied in two places:
168
+
169
+ - It is injected into every spawned `claude` / `codex` process, without overriding values already set in your shell.
170
+ - The daemon also uses it for its own Slack connection. Node's `fetch` and WebSocket ignore `HTTP_PROXY`, so this is handled separately.
171
+
172
+ **Domestic model endpoints should connect directly.** Routing DeepSeek, Zhipu, MiniMax or Volcano Ark through a server abroad and back is slow and can trip the provider's risk controls. When the proxy is set in the console or by setup, `noProxy` is generated from the providers you enabled. If you set the proxy in your shell yourself, add these hosts to `NO_PROXY`:
173
+
174
+ ```bash
175
+ export NO_PROXY=api.deepseek.com,open.bigmodel.cn,api.minimaxi.com,ark.cn-beijing.volces.com,localhost,127.0.0.1
176
+ ```
177
+
178
+ To check that the proxy works, run `curl -I https://chatgpt.com/`. A 403 means it works (Cloudflare blocks bare curl); a timeout or a refused connection means it does not.
179
+
180
+ ## Troubleshooting
181
+
182
+ | Symptom | Cause and fix |
183
+ |---|---|
184
+ | The health check or the group says "not logged in" / `Not logged in` | The default model is still `claude`, but this machine is not signed in to Claude. If you only use DeepSeek or another API-key model, run `sintbot setup` again and pick the model, send `/model deepseek` in the group, or change the group's model in the console. To use Claude, run `claude auth login` |
185
+ | The trial turn says the key is wrong (401) | The key was not copied in full. For Zhipu and MiniMax the wizard already tried both the mainland and the international site, so the key itself is wrong. Run `sintbot setup` again and paste it |
186
+ | Insufficient balance / identity not verified | Top up or complete identity verification in the provider's console, then retry |
187
+ | Domestic models are slow or unreachable | With a global proxy on, requests go abroad and back. Set the model hosts to connect directly; see [Proxies](#proxies-mainland-china) above |
188
+ | npm installs are slow or fail | Add `--registry https://registry.npmmirror.com`; the one-line install and the wizard switch automatically |
189
+ | A permission error (EACCES) on global install | Do not use `sudo`. Move the global npm directory into your home folder; see step 3 of [One-line install](#one-line-install) |
190
+ | GitHub is unreachable | The Chinese introduction and install steps are on [the website](https://sintbot.com/zh/); the README is also on the [npmmirror package page](https://npmmirror.com/package/sintbot) |
191
+ | No reply after @-mentioning the bot | Check the platform rows of `sintbot doctor`, and the latest errors in `sintbot service logs` |
192
+
193
+ ## Uninstall
194
+
195
+ ```bash
196
+ sintbot service uninstall
197
+ npm rm -g sintbot
198
+ rm -rf ~/.sbot # config, session ledger and logs live here; project directories are untouched
199
+ ```
@@ -0,0 +1,43 @@
1
+ # 让 AI 助手帮你安装并开始使用 SintBot
2
+
3
+ [English](../AI-ASSISTED-INSTALL.md) · 简体中文
4
+
5
+ AI 助手只有能操作 **SintBot 实际运行那台电脑的本机终端**,才能直接安装。纯聊天或云端 Shell 不能把软件装到你的 Mac。若助手连不上本机终端,就在目标电脑的「终端」里运行安装命令,再让助手继续核对。
6
+
7
+ 一键安装支持 macOS 和 Linux(Linux 为实验性支持);Windows 请按[手动步骤](./install.md#windows)。
8
+
9
+ ## 最短流程
10
+
11
+ 1. **先检查。** 核对目标系统、Node 版本、SintBot 版本和服务状态;不要读取或打印配置、凭据。
12
+ 2. **选对路径。** 新装先把官方安装脚本下载到临时文件、检查并做无修改预览;获准后执行同一份文件。升级只更新程序包,不重跑模型配置或试跑。重启正在运行的服务可能会向已接入的群播报升级。
13
+ 3. **一次确认。** 改动前用一条简短摘要列清:是否要用 Homebrew 安装 / 升级 Node、缺少时安装必需的 Claude Code CLI、模型选择及试跑可能产生的费用、是否安装 / 重启后台服务。只问还没确定的选项。获得批准后,助手按已选方案处理安装器重复出现的相同选项,不再重复打断;若出现未说明的新改动,先停下询问。
14
+ 4. **在本机执行。** 使用目标电脑的交互终端。账号登录由用户本人完成;API key 只由用户在 SintBot 本机遮罩提示中输入。不要把凭据复制进聊天或助手记录,也不要用 `sudo`。
15
+ 5. **完成首次使用。** 核对版本、体检和服务状态。在控制台按「开始使用」完成:确认模型可用、按需接聊天平台、把一个群绑定到一个项目目录,再发一条只读测试消息。暂时不接平台,也能先在「试跑」里验证模型。
16
+
17
+ 如果还要创建或审批聊天平台应用,助手按对应的[平台接入说明](./platforms/)逐步引导;凭据由用户直接填在本机控制台。每次只问下一步必需的信息,不重复索要已经提供的内容。
18
+
19
+ ## 助手没有本机终端时
20
+
21
+ 不要在另一台电脑或云端 Shell 上真实安装。可以做不改动系统的 `--dry-run` 预览;没有交互终端时,真实安装若缺少 Node 22+ 会停止,若已有 Node 也可能只安装程序包并提示配置未完成。让用户在目标电脑运行[一键安装](./install.md#一键安装),再由助手继续核对。
22
+
23
+ ## 可直接发给 AI 助手
24
+
25
+ ```text
26
+ 请帮我在这台电脑安装或升级 SintBot,并完成首次使用。只能操作它实际运行这台电脑的本机终端。先核对系统、Node 版本、SintBot 版本和服务状态;不要读取配置或凭据。
27
+
28
+ 新装先把官方安装脚本下载到临时文件、检查并运行无修改预览;获准后执行同一份文件。升级只更新程序包,不重跑模型配置或试跑。改动前用一条简短摘要说明 Node / Claude Code 安装、模型选择与试跑可能产生的费用、后台服务安装或重启(含群播报),一次问清需要我决定的选项。获得我的批准后,按已选内容处理重复的安装器确认,不要再问一遍;若出现新的改动要求就先停下。
29
+
30
+ 只用目标电脑的交互终端。我会自行登录,并在本机遮罩提示中输入 API key;不要把凭据放进聊天。不要用 sudo 或重置现有数据。安装后核对版本、体检和服务状态,再带我完成控制台「开始使用」:可以先在「试跑」验证模型;需要群聊时再接平台、绑一个群到一个项目目录,并发一条只读测试。每次只问下一项必需选择;遇错先停下说明原因。
31
+ ```
32
+
33
+ ## 给 AI agent 派清楚任务
34
+
35
+ 写代码时,说明目标、验收结果和限制。例如:
36
+
37
+ ```text
38
+ 目标:……
39
+ 完成标准:……
40
+ 限制:……
41
+
42
+ 先看项目说明和当前状态。信息足够就直接执行;只有缺少的信息会阻塞工作时才问我。完成后简要列出改动、检查结果和未完成项。
43
+ ```
@@ -0,0 +1,197 @@
1
+ # 安装
2
+
3
+ [English](../install.md) · 简体中文
4
+
5
+ 新用户用[一键安装](#一键安装)最省事;手动装就是三步:装包 → `sintbot setup` → `sintbot service install`。如果请 AI 助手安装并带你完成首次使用,先看[AI 安装与首次使用说明](./AI-ASSISTED-INSTALL.md),让流程留在目标电脑上,凭据不进聊天。下面按平台补充细节。
6
+
7
+ ## 一键安装
8
+
9
+ macOS / Linux(Linux 为实验性支持):
10
+
11
+ ```bash
12
+ curl -fsSL https://sintbot.com/install.sh | bash
13
+ ```
14
+
15
+ 脚本分四步,每一步先检查、已经有的跳过。通过 Homebrew 安装 / 升级 Node、安装后台服务前会先询问(回车 = 接受默认值);安装或升级 SintBot 本身属于这次安装流程。模型配置时若缺少必需的 Claude Code CLI,向导会自动安装。终端里只装好、配好模型:不问你在哪,也不要求填代理、项目目录、聊天平台凭据 —— 这些装完在控制台里配。
16
+
17
+ 1. **环境:Node 22+**:没有或版本太旧时,交互安装会先询问;Mac 上有 Homebrew 可用它装 / 升级,否则会给出 [nodejs.org](https://nodejs.org) 的下载地址后停下。没有交互终端时不会自动装 / 升级 Node,真实安装会直接停止。
18
+ **npm 源**:你配过 npm 源就照用;还是官方源时先试连一下,连不上(国内常见)本次改用国内镜像 `registry.npmmirror.com`。
19
+ 2. **装 sintbot**:`npm i -g sintbot`。已经是最新版就直接跳过,旧版本直接升级。npm 全局目录没有写权限时不会用 sudo,而是给出改装到用户目录的命令。
20
+ 3. **配模型** `sintbot setup`:检查 Claude Code,没装就装上(见下一节);然后配模型,当场试跑一轮。见下文[第 2 节](#2-配置sintbot-setup)。
21
+ 4. **后台服务**:问要不要 `sintbot service install`(登录后自启、崩溃自动拉起)。装好后控制台常开在 `http://127.0.0.1:4600` —— 还没接聊天平台也开着,脚本会在浏览器里打开它(SSH 连进来装的只打印地址)。服务已经在跑、这次也没升级就跳过这一步;升级了会问要不要重启服务(重启会在已接的群里播报升级)。
22
+
23
+ 结尾给出控制台地址和接下来做什么:在「设置」里接飞书 / Slack / 企业微信,在「群 · 项目」里绑群,然后在群里 @ 机器人。不接平台也能先用 `sintbot chat` 在终端里试。
24
+
25
+ - 想先看看它会做什么:`curl -fsSL https://sintbot.com/install.sh | bash -s -- --dry-run`(不安装、不修改、也不提问;npm 可用时检查源和最新版,并读取服务状态,还会提示模型向导可能安装必需组件和发送计费试跑);脚本源码就是仓库根目录的 [install.sh](../../install.sh)。
26
+ - 没有可交互的终端时(如 CI),若已有 Node 22+ 仍可能只装包,向导和常驻服务会跳过并提示未完成;若没有合格 Node,真实安装会停止。无交互 `--dry-run` 仍可预览计划。
27
+ - 脚本和向导的输出语言跟着系统:`LANG` 以 `zh` 开头,或者 macOS 的系统语言是中文,就是中文,否则英文;想指定就设 `SINTBOT_LANG=zh` 或 `en`,例如 `curl -fsSL https://sintbot.com/install.sh | SINTBOT_LANG=zh bash`。
28
+ - Windows 不支持一键安装,按下面手动来。
29
+
30
+ ## Claude Code:安装与登录
31
+
32
+ SintBot 每一轮都调用本机的 `claude` 命令行干活(Codex 群调用 `codex`),所以这台机器上要有 Claude Code —— 用 DeepSeek 等模型时也一样,它是执行框架,只是不用登录。
33
+
34
+ **安装**,任选一种:
35
+
36
+ - 让向导装:`sintbot setup` 在终端里运行时发现没装就直接装(就是下一条的 npm 命令,源连不上会自动换镜像)。
37
+ - npm:`npm i -g @anthropic-ai/claude-code`(国内连不上官方源时加 `--registry https://registry.npmmirror.com`)。
38
+ - 官方安装程序:见 [Claude Code 安装说明](https://code.claude.com/docs/zh-CN/setup)。
39
+
40
+ 装好后 `claude --version` 能打出版本号即可。升级用 `claude update`(npm 装的也可以 `npm i -g @anthropic-ai/claude-code@latest`)。
41
+
42
+ **认证**,按要用的模型选一种:
43
+
44
+ | 要用的模型 | 怎么做 |
45
+ |---|---|
46
+ | DeepSeek / 智谱 GLM / MiniMax / 火山方舟等(用 API key) | **不用登录**,装好即可;key 在向导里粘贴,见 [models.md](./models.md#三步接上-deepseek-等模型) |
47
+ | Claude,用订阅(Pro / Max) | 终端跑 `claude auth login`,在打开的浏览器里登录;`claude auth status` 查看是否已登录 |
48
+ | Claude,用 Anthropic API key(按量计费) | `claude auth login --console`;或把 `ANTHROPIC_API_KEY` 放进守护进程的环境 |
49
+
50
+ - 没有屏幕的机器(如当服务器用的 Mac mini):用屏幕共享(`vnc://机器IP`)进桌面跑一次 `claude auth login`,登录态落盘后无人值守也能用。
51
+ - 用个人订阅服务多人群聊的条款问题,见 [中文 README](../../README.zh-CN.md#安全须知)的「安全须知」。
52
+ - 验证:`claude --version` → `claude auth status`(用订阅时)→ `sintbot agent run "用一句话自我介绍"`(先在终端试一轮,只读)。
53
+
54
+ ## 前置
55
+
56
+ | 要求 | 检查 | 说明 |
57
+ |---|---|---|
58
+ | Node ≥ 22 | `node -v` | 当前 LTS 即可(浏览器接管页要用 Node 自带的 WebSocket) |
59
+ | Claude Code | `claude --version` | 可以让 `sintbot setup` 替你装;安装与登录见上一节。只用 DeepSeek 等 API key 模型的话装了不登录也行 |
60
+ | Codex CLI(可选) | `codex --version` | 有 ChatGPT 账号才用得上:GPT 群与生图。`npm i -g @openai/codex` 后 `codex login`(浏览器 OAuth,要图形界面) |
61
+ | 设计图标库(可选) | `sintbot doctor` 的 `design.icons` 行 | Lucide、Tabler、Phosphor、Pixelarticons 四套,合计约 170 MB。agent 出设计图时按名字从这里取图标;没装就只能自己画,和设计图对不上 |
62
+
63
+ `sintbot setup` 第一步会检查这几样:缺 Claude Code 就直接装;Codex 和设计图标库是可选的,缺了只打印装法、不问;Claude 与 Codex 的登录状态在模型那一步一起显示。
64
+
65
+ ## 1. 装包
66
+
67
+ ```bash
68
+ npm i -g sintbot
69
+ sintbot version
70
+ ```
71
+
72
+ 包名是 **`sintbot`**(npm 上叫 `sbot` 的是别人的包,别装错)。装出两个同义命令:`sbot` 和 `sintbot`。
73
+
74
+ > ⚠ 同一台机器同时有 Homebrew 的 node 和 nvm 的 node 时,`npm i -g` 装进的是**当前 shell 那份**。常驻服务用的是装服务那一刻的 node(`sintbot service install` 会把路径写进服务),两者对不上就会出现「升级了但跑的还是旧版」。用 `sintbot service install --dry-run` 看服务里写的是哪个 node。
75
+
76
+ 从源码跑:
77
+
78
+ ```bash
79
+ git clone https://github.com/sintlab/sintbot.git && cd sintbot
80
+ npm install
81
+ npm link # 可选:把 sbot 命令链到全局
82
+ ```
83
+
84
+ ## 2. 配置:`sintbot setup`
85
+
86
+ 交互式向导,回车即默认(方括号里的值,`(Y/n)` 大写的那个)。只问这台机器跑起来需要的两样:
87
+
88
+ 1. **环境**:Claude Code(缺了就装)、Codex 和设计图标库(可选,缺了打印装法),见上文「前置」
89
+ 2. **模型**:先显示 Claude 订阅、ChatGPT(Codex)的登录状态和已有 key 的模型,按这个顺序推荐:
90
+ - Claude 已登录 → 默认 Claude;否则 Codex 已登录 → 默认 Codex。不再多问;别的模型在控制台「模型 · 库」里加
91
+ - 都没有 → **推荐 DeepSeek**:告诉你去 platform.deepseek.com 拿 key,粘贴后**当场试跑一轮**。也可以选别家(智谱 GLM / MiniMax / 火山方舟)、另开终端登录 Claude 或 Codex 后回来重新检测,或先跳过
92
+ - 智谱 GLM、MiniMax 有国际站(Z.ai / minimax.io),key 只在开它的那个站能用:向导先试一个站,认证失败自动换另一个站再试;没装 Claude Code 试跑不了时,才问 key 是哪个站开的
93
+ - 试跑失败会说原因:key 不对、余额不足或没实名、连不上(是不是开着全局代理)、模型名不对;可以重新粘贴再试
94
+ - 默认模型由向导自动选(已登录的订阅优先,其次试跑通过的,再次有 key 的),还没试跑过就直接试一轮;群里 `/model` 随时切
95
+ - 没装或没登录 codex 时,codex 预设和「额度用完自动切到 codex」不写进配置。Kimi 不在预设里,要在控制台「模型 · 库 → 添加模型 → 自定义」添加。见 [models.md](./models.md)
96
+
97
+ 配置写到 `~/.sbot/config.json`(权限 600);当前目录已有 `sbot.config.json` 时更新那份。再跑一次 `sintbot setup` 只补缺项,不动已填的。
98
+
99
+ 向导最后做一次简短体检,只列失败项;完整的看 `sintbot doctor`。
100
+
101
+ **其余的都在控制台里配**(`sintbot ui`,装了服务就是 `http://127.0.0.1:4600`):
102
+
103
+ | 配什么 | 控制台里的位置 |
104
+ |---|---|
105
+ | 聊天平台凭据(飞书 / Slack / 企业微信)、预检、飞书管理员 | 「设置」,每个平台都有分步指南;另见 [platforms/](./platforms/) |
106
+ | 把群绑到项目目录 | 「群 · 项目」 |
107
+ | 机器人名字、网络代理 | 「设置 → 机器人与网络」 |
108
+ | 项目根目录 | 控制台里只读(它是控制台能绑哪些目录的围栏)。默认 `~/Projects`;要改就编辑配置里的 `projectsRoots`,或运行 `sintbot setup --full` |
109
+
110
+ **`sintbot setup --full`**:在终端里问旧的完整一套,给没有浏览器的服务器用 —— 代理分流、机器人名、聊天平台凭据与预检、项目根目录、绑第一个群。
111
+
112
+ ## 3. 常驻:`sintbot service install`
113
+
114
+ ```bash
115
+ sintbot service install # 写入并启动服务
116
+ sintbot service install --ui-host 0.0.0.0 # 控制台对局域网开放(先 sintbot ui --set-password)
117
+ sintbot service status | restart | logs | uninstall
118
+ ```
119
+
120
+ - **macOS**:写 `~/Library/LaunchAgents/com.sbot.daemon.plist`(LaunchAgent,随用户登录启动,崩溃 15 秒后拉起)
121
+ - **Linux**(实验性:1.0 只用单元测试核对过生成的单元文件,没在真机上装过):写 `~/.config/systemd/user/sbot.service`;要开机就起(不必先登录)再跑一次 `loginctl enable-linger $USER`
122
+ - 日志:`~/.sbot/logs/err.log`(`sintbot service logs` 看末尾)
123
+ - 服务里固定的是:node 的路径、sbot 入口、配置文件绝对路径,以及当前 shell 的 PATH(`claude` / `codex` 所在目录排最前)。**换了 node、挪了配置、新装了 claude 之后,重跑一次 `sintbot service install`**
124
+ - node 路径:Homebrew 装的 node 实际在带版本号的目录里,升级后旧路径就没了 —— install 会换成指向同一个程序、不带版本号的路径(如 `/opt/homebrew/bin/node`)。nvm / volta 这类找不到稳定路径的照用原路径,并提醒你换 node 版本后重装服务
125
+ - 环境变量:服务里**没有**你 shell 里的变量,install 会把需要的值直接写进服务定义(文件权限 600,`--dry-run` 打印时值是遮住的):配置里所有 `"env:变量名"` 引用,以及配置没写代理时 shell 里的 `https_proxy` 这类变量。换了 key 或代理要重跑 install;或者干脆在配置里填明文(配置文件本身就是 600;代理在控制台「设置 → 机器人与网络」填)
126
+ - 控制台对外(`--ui-host` 非本机地址)而没设密码时,install 直接拒绝 —— 否则服务每次一启动就退出
127
+ - 还没接聊天平台也照装:服务先只开控制台,在「设置」里接好平台、保存后,几秒内自己重启并连上(1.0.10、1.0.11 这种情况会拒绝安装)
128
+ - 服务在跑时运行 `sintbot ui`,会直接打开服务里的控制台,不报端口被占用
129
+ - ⚠ 装了服务就别再手动 `sintbot start`:同一个系统用户只允许一个守护进程(锁文件 `~/.sbot/daemon.lock`),第二个会报出已在运行的那个的 pid 后退出。另一台机器或另一个系统用户用**同一个机器人应用**再起一个的话:飞书和 Slack 会把每条消息**随机交给其中一个连接**(另一个就收不到),企业微信则是新连接顶掉旧连接,10 分钟内被顶两次就不再重连
130
+
131
+ ### 升级
132
+
133
+ 控制台「设置 → 软件更新」启动时及之后每天自动检查稳定版,也可手动检查。自动检查始终开启。发现新版后由管理员明确确认才安装;所有群任务和队列空闲后,只升级到刚检测到的精确版本。全局安装目录不可写或当前不是全局安装时不会提权,页面会提示改用终端。
134
+
135
+ 受托管的后台服务会在安装并核对版本后自动重启;前台运行则需手动重启。可忽略当前版本,并选择升级后是否向群播报。网络检查失败不会清掉上次成功结果。
136
+
137
+ ```bash
138
+ npm i -g sintbot@latest && sintbot service restart
139
+ ```
140
+
141
+ 重启会中止正在跑的轮次;被中止的那一轮在重启后自动重做,群里会说一声。最稳妥的做法是挑控制台「概览」里各群都空闲的时候重启。
142
+
143
+ ## macOS:当服务器用的 Mac mini
144
+
145
+ 无人值守要满足:不休眠、断电自恢复、重启后自动进桌面。
146
+
147
+ ```bash
148
+ sudo pmset -a sleep 0 disablesleep 1 autorestart 1 powernap 0
149
+ ```
150
+
151
+ 再到「系统设置 → 用户与群组」打开**自动登录**。LaunchAgent 要有用户会话才会启动(它要读 `~/.claude` / `~/.codex` 的登录态和钥匙串),不开自动登录,重启后服务不会起来。
152
+
153
+ `codex login` 要浏览器:用屏幕共享(`vnc://机器IP`)进桌面做一次,凭据落盘后无人值守也能用。
154
+
155
+ ## Windows
156
+
157
+ > 1.0 没有在 Windows 真机上验证过(持续集成只跑 Linux)。下面几条按代码行为写成,遇到问题请提 issue。
158
+
159
+ - `claude` 若是 npm 装的,实际是 `claude.cmd` 垫片 —— 已自动处理,配置里写 `claude` 即可
160
+ - 服务化:`sintbot service install` 在 Windows 上不自动装,而是打印 [NSSM](https://nssm.cc/) 与 pm2 的现成命令,照着执行即可。用 NSSM 时,API key 和代理变量要用 `nssm set SintBot AppEnvironmentExtra …` 带进服务环境
161
+ - 项目根目录默认 `D:\Projects`(没有 D 盘则 `%USERPROFILE%\Projects`)
162
+
163
+ ## 代理
164
+
165
+ 在中国大陆:官方 Claude、ChatGPT / Codex、Slack 要走代理,国内模型端点、飞书、企业微信直连。在控制台「设置 → 机器人与网络」填代理(或用 `sintbot setup --full`)后,配置里的 `proxy` 块会:
166
+
167
+ - 注入到每次 spawn 的 `claude` / `codex` 子进程(不覆盖 shell 里已有的值);
168
+ - 也用于守护进程自己连 Slack(Node 的 `fetch` / WebSocket 不读 `HTTP_PROXY` 环境变量,所以单独处理)。
169
+
170
+ **国内模型端点要直连**:DeepSeek、智谱、MiniMax、火山方舟绕到境外再连回来,既慢,也可能触发对方的风控。在控制台或向导里填代理时,`noProxy` 会按你启用的 provider 自动生成;如果是自己在 shell 里配的代理,把这些域名加进 `NO_PROXY`:
171
+
172
+ ```bash
173
+ export NO_PROXY=api.deepseek.com,open.bigmodel.cn,api.minimaxi.com,ark.cn-beijing.volces.com,localhost,127.0.0.1
174
+ ```
175
+
176
+ 验证代理通没通:`curl -I https://chatgpt.com/` 返回 403 就是通了(Cloudflare 拦裸 curl);超时或连接被拒才是不通。
177
+
178
+ ## 常见问题
179
+
180
+ | 现象 | 原因与做法 |
181
+ |---|---|
182
+ | 体检或群里报「未登录」/ `Not logged in` | 默认模型还是 `claude`,但这台机器没登录 Claude。只用 DeepSeek 等:重跑 `sintbot setup` 选模型,或群里 `/model deepseek`、控制台给群换模型;要用 Claude 就跑 `claude auth login` |
183
+ | 试跑报「key 不对」(401) | key 没复制完整。智谱 / MiniMax 向导已经把中国站和国际站都试过了,说明 key 本身不对。重跑 `sintbot setup` 重新粘贴 |
184
+ | 报余额不足 / 未实名 | 到厂商后台充值、完成实名后再试 |
185
+ | 很慢或连不上国内模型 | 开着全局代理(梯子)时请求会绕到境外:把模型域名设成直连,见上文「代理」 |
186
+ | npm 安装很慢或失败 | 加 `--registry https://registry.npmmirror.com`;一键安装和向导会自动换 |
187
+ | 全局安装报权限错误(EACCES) | 别用 sudo,把 npm 全局目录改到用户目录,做法见上文「一键安装」第 3 条 |
188
+ | GitHub 打不开 | 中文介绍和安装步骤在[官网](https://sintbot.com/zh/)上都有;英文 README 在 [npmmirror 的包页](https://npmmirror.com/package/sintbot)上也能看到 |
189
+ | 群里 @ 了没反应 | `sintbot doctor` 看平台那几行;`sintbot service logs` 看最近的错误 |
190
+
191
+ ## 卸载
192
+
193
+ ```bash
194
+ sintbot service uninstall
195
+ npm rm -g sintbot
196
+ rm -rf ~/.sbot # 配置、会话账本、日志都在这里;项目目录不受影响
197
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sintbot",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
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",
@@ -15,6 +15,10 @@
15
15
  "sbot.config.example.json",
16
16
  "README.md",
17
17
  "README.zh-CN.md",
18
+ "docs/install.md",
19
+ "docs/AI-ASSISTED-INSTALL.md",
20
+ "docs/zh-CN/install.md",
21
+ "docs/zh-CN/AI-ASSISTED-INSTALL.md",
18
22
  "LICENSE",
19
23
  "THIRD-PARTY-NOTICES.md",
20
24
  "docs/slack-app-manifest.yaml"
@@ -0,0 +1,36 @@
1
+ // Mask characters echoed by a terminal readline prompt while a secret is being entered.
2
+ // Readline's _writeToOutput is the single output path for terminal echo; prompts and
3
+ // cursor-control sequences stay intact so paste, backspace and arrow-key editing work.
4
+
5
+ const CONTROL = /(\x1b\[[0-?]*[ -/]*[@-~]|\x1b[()][A-Za-z0-9]|\x08 \x08|[\x00-\x1f\x7f])/g
6
+ const CONTROL_TOKEN = /^(?:\x1b\[[0-?]*[ -/]*[@-~]|\x1b[()][A-Za-z0-9]|\x08 \x08|[\x00-\x1f\x7f])$/
7
+
8
+ function maskReadlineEcho(text) {
9
+ return String(text).split(CONTROL).map((part) => {
10
+ if (!part || CONTROL_TOKEN.test(part)) return part
11
+ return '•'.repeat(Array.from(part).length)
12
+ }).join('')
13
+ }
14
+
15
+ function createReadlineSecretMask(rl) {
16
+ if (!rl || typeof rl._writeToOutput !== 'function') throw new TypeError('readline interface required')
17
+ const original = rl._writeToOutput
18
+ let activePrompt = null
19
+
20
+ rl._writeToOutput = function (text) {
21
+ if (activePrompt === null) return original.call(this, text)
22
+ text = String(text)
23
+ const promptAt = text.lastIndexOf(activePrompt)
24
+ if (promptAt < 0) return original.call(this, maskReadlineEcho(text))
25
+ const promptEnd = promptAt + activePrompt.length
26
+ return original.call(this, text.slice(0, promptEnd) + maskReadlineEcho(text.slice(promptEnd)))
27
+ }
28
+
29
+ return {
30
+ start(prompt) { activePrompt = String(prompt) },
31
+ stop() { activePrompt = null },
32
+ restore() { activePrompt = null; rl._writeToOutput = original },
33
+ }
34
+ }
35
+
36
+ module.exports = { maskReadlineEcho, createReadlineSecretMask }
@@ -1921,6 +1921,10 @@ class Engine {
1921
1921
  // 第三方 logo 的品牌色)。真防线是让偏离**可见**,判断权留给群里的人。
1922
1922
  {
1923
1923
  const docs = (this.styleDocs && this.styleDocs(group.workdir)) || []
1924
+ // 风格库曾对任何页面样式/出图任务都主动推荐,但预设效果并不总符合项目需求。
1925
+ // 现在按是否属于全新产品创建、以及是否已有视觉方向来决定是否提示。
1926
+ L.push('【风格库提示边界】仅在明确启动全新产品/应用/网站且未提供视觉方向时,才可简短推荐风格预设。已有项目的页面、功能迭代、改版、缺陷修复、图片素材或按参考图实现时,不要主动提风格库,按项目现状、参考素材和本轮说明执行;主动询问风格或预设时再介绍。')
1927
+ L.push(' 符合条件时,先简短询问是否需要看预设;需要时再展示少量候选预览,由对方选定后保存。已有视觉方向就直接照做,不要为选风格中断工作。')
1924
1928
  if (docs.length) {
1925
1929
  L.push('【本项目的设计口径】出图、写页面样式前按它来:')
1926
1930
  if (docs.length > 1) {
@@ -1974,14 +1978,6 @@ class Engine {
1974
1978
  } else {
1975
1979
  L.push('· ⚠ 本项目**还没选图标系统**(没有 design/icons.md)⇒ 取不到图标、图标这一类也**不会被检查**,页面图标与设计图对不对得上全靠人眼')
1976
1980
  }
1977
- } else {
1978
- // ⚠ 一份档都没有时,上面整块**一个字都不出现** —— 于是新项目连「有设计规范这回事」都不知道:
1979
- // agent 不会想到去落一份,群成员也无从知道有这个东西。能力早就有了,到不了新项目而已
1980
- // (与「17 份内置档声明了却报未启用」是同一个病:**能力的到达率**,不是能力本身)。
1981
- // ⚠ 但绝不能让它自己挑一个落下去 —— **风格是人的判断**,替用户选了等于把这个决定悄悄做掉;
1982
- // 所以措辞是「先问群里」。两行封顶:清单每轮都付 token,没档的项目多数根本不做设计。
1983
- L.push('【本项目的设计口径】**本项目还没有风格档** —— 色板/字体/圆角/图标没有统一依据,做出来的页面每次都会不一样,也没法在交付前自查。')
1984
- L.push('· 这次的活儿若涉及**写页面样式、出图、做设计**:先 `sbot design styles` 看有哪些风格(`sbot design swatch <slug…>` 出预览图可直接发群里让人挑),**问清楚群里要哪一种**再 `sbot design use <slug>` 落档 —— ⚠ **别替用户挑**:风格是人的判断,你选了等于把这个决定悄悄替他做了。落档之后色板/字体/圆角会自动进你的上下文,交付前的样式自查也才有依据')
1985
1981
  }
1986
1982
  // ⭐ 图标系统是「群里的人能不能自己看、自己换」的唯一通道 —— 群里没有按钮,全靠对话,
1987
1983
  // 而 agent **不知道有这条命令就等于这个能力不存在**(v0.11.49 那两条同病:声明了却报
@@ -242,6 +242,18 @@ body::after{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;opac
242
242
  .mark .ver{font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.3px;color:var(--ember-soft);
243
243
  border:1px solid rgba(var(--ember-rgb),.32);border-radius:6px;padding:1px 7px;align-self:center;white-space:nowrap}
244
244
  .mark .ver:empty{display:none}
245
+ .mark .ver-update{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;width:14px;height:16px;margin:0 -4px;padding:0;
246
+ border:0;border-radius:50%;background:transparent;cursor:pointer}
247
+ .mark .ver-update[hidden]{display:none}
248
+ .mark .ver-update:focus-visible{outline:2px solid var(--focus);outline-offset:1px}
249
+ .mark .update-dot{width:5px;height:5px;border-radius:50%;flex:none;background:var(--ember-soft);box-shadow:0 0 0 2px rgba(var(--ember-rgb),.13);
250
+ animation:update-dot-flash 1.4s steps(1,end) infinite;transition:transform .15s,box-shadow .15s}
251
+ .mark .ver-update:hover .update-dot{transform:scale(1.25);box-shadow:0 0 0 3px rgba(var(--ember-rgb),.18)}
252
+ @keyframes update-dot-flash{
253
+ 0%,64%,100%{opacity:1;box-shadow:0 0 0 2px rgba(var(--ember-rgb),.13)}
254
+ 65%,99%{opacity:.28;box-shadow:0 0 0 0 rgba(var(--ember-rgb),0)}
255
+ }
256
+ @media(prefers-reduced-motion:reduce){.mark .update-dot{animation:none}}
245
257
  .top .spacer{flex:1}
246
258
  .daemon{display:flex;align-items:center;gap:9px;font-family:var(--sans);font-size:12px;font-weight:500;color:var(--dim)}
247
259
  /* 顶栏服务器时钟:整体一枚 chip 胶囊(与全站 .chip 同语言)——「服务器 · 时区 · 时:分:秒」。
@@ -584,8 +596,32 @@ label.f{display:block;font-family:var(--sans);font-size:11.5px;font-weight:600;l
584
596
  .fx-org-row.on:hover{background:none;color:var(--ok)}
585
597
 
586
598
  h2.sec{font-family:var(--sans);font-size:14px;letter-spacing:.2px;color:var(--ink);margin:34px 2px 14px;font-weight:700}
599
+ /* 风格与图标库都是按需入口,在「模型」页保持低强调并默认折叠。 */
600
+ .library-fold{margin-top:30px}
601
+ .library-fold>summary{list-style:none;cursor:pointer;user-select:none;display:flex;align-items:center;gap:7px;margin:0 2px;padding:7px 0;color:var(--faint);font-family:var(--sans);font-size:12px;font-weight:500;transition:color .16s}
602
+ .library-fold>summary::-webkit-details-marker{display:none}
603
+ .library-fold>summary:hover{color:var(--dim)}
604
+ .library-fold>summary .caret{display:inline-flex;color:var(--faint);transition:transform .2s}
605
+ .library-fold>summary .caret svg{width:13px;height:13px}
606
+ .library-fold[open]>summary .caret{transform:rotate(90deg)}
607
+ .library-fold-content{padding-top:10px}
587
608
  /* 版本历史(设置页):时间线式;当前版本行 ember 高亮 */
588
- #verhistBox{max-height:380px;overflow-y:auto}
609
+ #verhistBox{max-height:240px;overflow-y:auto}
610
+ .update-history{margin-top:18px;padding-top:15px;border-top:1px solid var(--line)}
611
+ .update-history .subhead{font-size:12px;font-weight:650;color:var(--dim);margin-bottom:5px}
612
+ #botNetCard{container-type:inline-size;align-self:start;grid-column:1/-1}
613
+ #botNetCard .botnet-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
614
+ #botNetCard .botnet-fields .field{min-width:0;margin:0}
615
+ #botNetCard .botnet-extra{margin-top:11px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--inset-bg)}
616
+ #botNetCard .botnet-extra>summary{list-style:none;cursor:pointer;color:var(--faint);font-size:11px;user-select:none}
617
+ #botNetCard .botnet-extra>summary::-webkit-details-marker{display:none}
618
+ #botNetCard .botnet-extra>summary::before{content:'›';display:inline-block;margin-right:7px;color:var(--ember);transition:transform .15s}
619
+ #botNetCard .botnet-extra[open]>summary{color:var(--dim);margin-bottom:5px}
620
+ #botNetCard .botnet-extra[open]>summary::before{transform:rotate(90deg)}
621
+ #botNetCard .botnet-extra .hint{margin-top:3px;overflow-wrap:anywhere}
622
+ #botNetCard .botnet-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:14px;padding-top:13px;border-top:1px solid var(--line)}
623
+ #botNetCard .botnet-actions .hint{flex:1 1 240px;min-width:0;margin:0}
624
+ @container (max-width:440px){#botNetCard .botnet-fields{grid-template-columns:minmax(0,1fr)}}
589
625
  .vh-row{padding:12px 2px;border-bottom:1px solid var(--line)}
590
626
  .vh-row:last-child{border:none}
591
627
  .vh-head{display:flex;align-items:baseline;gap:10px}
@@ -783,6 +819,7 @@ button.btn.nsmini{padding:1px 9px;font-size:11px;font-weight:500;border-radius:7
783
819
  /* 超窄(手机竖屏):静态的版本徽章让位给动态的 daemon 状态文字 */
784
820
  @media(max-width:430px){
785
821
  .mark .ver{display:none}
822
+ .mark .ver-update:not([hidden]){width:12px;height:12px;margin-left:-2px;margin-right:-4px}
786
823
  .fxapp{display:none} /* 手机竖屏:顶栏只留运行状态 */
787
824
  /* nav → edge-to-edge scroll strip if it ever overflows; .navsp(flex:1) pins the 试跑 icon
788
825
  right while the text tabs fit (compact icon ⇒ they do at ≤500px), else it collapses and scrolls */
@@ -840,7 +877,7 @@ d.dataset.theme=t;if(l!=='en'&&l!=='zh')l=/^zh/i.test((navigator.language||''))?
840
877
  <div class="top">
841
878
  <!-- 品牌字标不进翻译层(data-i18n-skip):「Sint」「Bot」是两个文本节点,英文界面下翻译层会把恰好等于某条译文的
842
879
  英文反查回中文(i18nZhOrig),「机器人 → Bot」一加,切回中文时左上角就成了「Sint机器人」(10-24 发版前撞到)。 -->
843
- <div class="mark"><a class="word" href="https://sintbot.com" target="_blank" rel="noopener" title="SintBot 官网" style="color:inherit;text-decoration:none"><span data-i18n-skip>Sint<i class="trace" aria-hidden="true"></i>Bot</span></a><span class="ver">__SBOT_VERSION__</span><small>控制台</small></div>
880
+ <div class="mark"><a class="word" href="https://sintbot.com" target="_blank" rel="noopener" title="SintBot 官网" style="color:inherit;text-decoration:none"><span data-i18n-skip>Sint<i class="trace" aria-hidden="true"></i>Bot</span></a><span class="ver">__SBOT_VERSION__</span><button class="ver-update" id="topUpdateBadge" type="button" hidden title="发现新版本,打开软件更新" aria-label="发现新版本,打开软件更新"><span class="update-dot" aria-hidden="true"></span></button><small>控制台</small></div>
844
881
  <div class="spacer"></div>
845
882
  <div class="topright">
846
883
  <!-- 当前连着的飞书应用(名称 + 图标)。左边 .mark 是产品品牌、固定不变;这块是「这台机器
@@ -1313,7 +1350,7 @@ async function autoRefresh(){
1313
1350
  if(sig!==lastStateSig){
1314
1351
  lastStateSig=sig;
1315
1352
  renderOverview(); tabSig.overview=sig; // 纯展示,直接重画
1316
- updateDaemon();updateTopFxApp(); // 顶栏两块在页签之外,得跟 updateDaemon 一起显式更新
1353
+ updateDaemon();updateTopFxApp();updateTopUpdateBadge(); // 顶栏状态在页签之外,得随状态一起显式更新
1317
1354
  }else{updateDaemon();updateOverviewLive();updateConnLive();}
1318
1355
  }catch{/* 断网/401 交给 SSE 那条自愈路径,这里静默 */}
1319
1356
  }
@@ -1513,16 +1550,34 @@ function renderProviders(){
1513
1550
  <h2 class="sec" style="margin-top:32px">插件库 · MCP</h2>
1514
1551
  <div class="hint" style="margin:0 2px 14px">本机装好的 MCP · 按运行时分两套 · 命令行安装、即时刷新</div>
1515
1552
  <div id="plugLib"><span class="spin"></span></div>
1516
- <h2 class="sec" style="margin-top:32px">设计风格库<button class="btn ghost" id="styleRescan" title="重新扫描风格预设(导入或改过自定义风格后用)" style="margin-left:10px;padding:2px 9px;font-size:10.5px;vertical-align:middle">重新扫描</button><button class="btn" id="styleNew" title="用一张表单配一套自己的风格:色板、圆角、字重逐项定,右侧实时看效果。存进库后全部群都能用" style="margin-left:6px;padding:2px 9px;font-size:10.5px;vertical-align:middle">+ 新建风格</button></h2>
1517
- <div class="hint" style="margin:0 2px 14px">图标由风格档的 tokens 实时派生,改了色板/圆角当场跟着变 · 在群里说一句即可给某个项目定风格 · 当前哪个项目用哪套,看「群·项目」页群卡的「设计风格」行</div>
1518
- <div id="styleLib"><span class="spin"></span></div>
1519
- <h2 class="sec" style="margin-top:32px">图标库</h2>
1520
- <div class="hint" style="margin:0 2px 14px">与设计风格<b>平级</b>:风格管怎么画(颜色/字体/圆角/描边),图标库管图形从哪来、叫什么名 · 一个项目分别选,在「群·项目」页那个群的「图标」行</div>
1521
- <div id="iconLib"><span class="spin"></span></div>`;
1522
- // 风格库与插件库同性质(全局资源、按群使用)⇒ 同住「模型」页;此前放在「群·项目」页底部,
1523
- // 既概念错位、又占掉那一页 45% 版面(得滚过所有群卡才看得见)。
1524
- loadStyleLib();
1525
- loadIconLib();
1553
+ <details class="library-fold" id="styleLibFold"${styleLibFoldOpen?' open':''}>
1554
+ <summary><span class="caret">${IC.chevronRight}</span><span>设计风格库</span><span class="hint" style="font-size:10.5px;font-weight:400">按需管理</span></summary>
1555
+ <div class="library-fold-content">
1556
+ <div class="inline" style="justify-content:space-between;gap:8px;margin:0 2px 10px;flex-wrap:wrap">
1557
+ <div class="hint">仅在全新产品且未定方向时按需使用预设</div>
1558
+ <div class="inline" style="gap:6px"><button class="btn ghost" id="styleRescan" title="重新扫描风格预设(导入或改过自定义风格后用)" style="padding:2px 9px;font-size:10.5px">重新扫描</button><button class="btn" id="styleNew" title="用一张表单配一套自己的风格:色板、圆角、字重逐项定,右侧实时看效果。存进库后全部群都能用" style="padding:2px 9px;font-size:10.5px">+ 新建风格</button></div>
1559
+ </div>
1560
+ <div id="styleLib"><span class="spin"></span></div>
1561
+ </div>
1562
+ </details>
1563
+ <details class="library-fold" id="iconLibFold"${iconLibFoldOpen?' open':''}>
1564
+ <summary><span class="caret">${IC.chevronRight}</span><span>图标库</span><span class="hint" style="font-size:10.5px;font-weight:400">按需查看</span></summary>
1565
+ <div class="library-fold-content">
1566
+ <div class="hint" style="margin:0 2px 14px">与设计风格<b>平级</b>:风格管怎么画(颜色/字体/圆角/描边),图标库管图形从哪来、叫什么名 · 一个项目分别选,在「群·项目」页那个群的「图标」行</div>
1567
+ <div id="iconLib"><span class="spin"></span></div>
1568
+ </div>
1569
+ </details>`;
1570
+ // 风格库按需展开再加载与绘制,默认折叠以降低存在感;页内重绘时保留用户刚才的展开状态。
1571
+ const styleFold=$('#styleLibFold');
1572
+ if(styleFold){
1573
+ styleFold.addEventListener('toggle',()=>{styleLibFoldOpen=styleFold.open;if(styleFold.open)loadStyleLib();});
1574
+ if(styleFold.open)loadStyleLib();
1575
+ }
1576
+ const iconFold=$('#iconLibFold');
1577
+ if(iconFold){
1578
+ iconFold.addEventListener('toggle',()=>{iconLibFoldOpen=iconFold.open;if(iconFold.open)loadIconLib();});
1579
+ if(iconFold.open)loadIconLib();
1580
+ }
1526
1581
  $('#styleRescan').onclick=async()=>{styleLib=null;$('#styleLib').innerHTML='<span class="spin"></span>';await loadStyleLib();toast('风格库已重新扫描','ok');};
1527
1582
  $('#styleNew').onclick=()=>openStyleModal(null);
1528
1583
  if($('#showAll'))$('#showAll').onclick=()=>{showAll=!showAll;renderProviders();};
@@ -1833,6 +1888,7 @@ async function paintGroupIconStrips(){
1833
1888
  把它们并排放,「换一套到底换了什么」一眼就有答案。
1834
1889
  ⚠ 图标包不随产品分发(四套合计约 160MB) ⇒ 没装的如实标「未安装」,不留空白(空白会被读成「这套是空的」)。*/
1835
1890
  let iconLib=null;
1891
+ let iconLibFoldOpen=false;
1836
1892
  /* 取一次就缓存。⚠ 样张是**系统的属性、不是项目的属性** ⇒ 它不进 /api/state 那条 15s 心跳:
1837
1893
  塞进去就是每个绑定群每次心跳多读 8 个 svg、多背几 KB,而这份数据全部群共用同一套。
1838
1894
  群卡「图标」行那排缩略图因此也走这里,前端按系统名对上 —— 同一份数据两处用,服务端零额外开销。*/
@@ -1919,6 +1975,7 @@ function fitStrips(box){
1919
1975
  function refitAllStrips(){for(const id of ['#iconLib','#groups']){const b=$(id);if(b)fitStrips(b);}}
1920
1976
  /* 风格库(只读):内置预设 + ~/.sbot/design-styles 自定义。 */
1921
1977
  let styleLib=null;
1978
+ let styleLibFoldOpen=false;
1922
1979
  async function loadStyleLib(){
1923
1980
  const box=$('#styleLib'); if(!box)return;
1924
1981
  if(styleLib){paintStyleLib();return;}
@@ -2493,8 +2550,8 @@ function renderGroups(){
2493
2550
  <div style="flex:1;min-width:0">${permInner}</div></div>
2494
2551
  <div class="kv" style="align-items:flex-start"><span class="kk">基础技能</span>
2495
2552
  <div style="flex:1;min-width:0">
2496
- <div class="inline" style="gap:6px;flex-wrap:wrap">${isWe?'<span class="chip cap" title="发送(文件/图片/消息) · 拉近 7 天群历史 —— 均针对本群;企业微信没有编辑/撤回/表情回应">企业微信 · 收发群消息</span>':'<span class="chip cap" title="发送(文件/图片/消息) · 拉历史 · 回复/编辑/撤回 · 下载文件 · 表情回应 —— 均针对本群">飞书 · 收发/管理群消息</span>'}<span class="chip cap" title="群里直接对它说「每天9点拉构建报告」就会创建;支持创建/修改/删除定时任务,到点触发执行或只发提醒 —— 下方「定时任务」区可查看/管理">定时任务</span><span class="chip cap" title="内置多套设计风格预设,也能从参考网址/参考图提炼;挑方向时会把候选画成预览图发群里让你指着选(图由风格档 tokens 派生);写进项目 DESIGN.md 后该项目生图/图表全程遵循;全部风格见「模型 · 库」页的「设计风格库」">设计风格档</span></div>
2497
- <div class="hint" style="margin-top:5px">默认内建 · ${isWe?'企业微信收发/拉历史':'飞书收发/拉历史/回复编辑撤回/下载/表情'} · <b>定时任务</b>(群里说一句即可让 agent 创建/修改) · <b>设计风格档</b>(内置预设/参考图提色,落盘 DESIGN.md) · <b>生图</b>(需机器已登录 codex,借订阅后台生图) · agent 首轮自知(身份/项目/模型/权限/控制台地址 + 已启用插件)${baseSkillNote}</div>
2553
+ <div class="inline" style="gap:6px;flex-wrap:wrap">${isWe?'<span class="chip cap" title="发送(文件/图片/消息) · 拉近 7 天群历史 —— 均针对本群;企业微信没有编辑/撤回/表情回应">企业微信 · 收发群消息</span>':'<span class="chip cap" title="发送(文件/图片/消息) · 拉历史 · 回复/编辑/撤回 · 下载文件 · 表情回应 —— 均针对本群">飞书 · 收发/管理群消息</span>'}<span class="chip cap" title="群里直接对它说「每天9点拉构建报告」就会创建;支持创建/修改/删除定时任务,到点触发执行或只发提醒 —— 下方「定时任务」区可查看/管理">定时任务</span><span class="chip cap" title="按需使用:仅从零设计新产品且未定视觉方向时推荐预设;也可按要求提炼参考并写入项目 DESIGN.md">设计风格档</span></div>
2554
+ <div class="hint" style="margin-top:5px">默认内建 · ${isWe?'企业微信收发/拉历史':'飞书收发/拉历史/回复编辑撤回/下载/表情'} · <b>定时任务</b>(群里说一句即可让 agent 创建/修改) · <b>设计风格档</b>(新产品设计时按需使用) · <b>生图</b>(需机器已登录 codex,借订阅后台生图) · agent 首轮自知(身份/项目/模型/权限/控制台地址 + 已启用插件)${baseSkillNote}</div>
2498
2555
  </div></div>
2499
2556
  <div class="kv" style="align-items:flex-start"><span class="kk">插件</span>
2500
2557
  <div style="flex:1;min-width:0">
@@ -2562,7 +2619,7 @@ function renderGroups(){
2562
2619
  <div class="hint" style="margin-top:3px;line-height:1.55">${esc(st.description||'—')}</div>
2563
2620
  <button class="btn" data-stylecollect="${esc(g.chatId)}" data-stylerel="${esc(st.rel||'')}" title="把这一份复制进全局风格库,之后别的群也能直接挑到它(项目里那份不动)" style="padding:2px 9px;font-size:10.5px;margin-top:8px">收录进风格库</button>
2564
2621
  </div></div>`).join('')
2565
- :'<span class="dim">未设置 · 设计任务开始时 agent 会先从风格库挑候选发群里让你选</span>'}
2622
+ :'<span class="dim">未设置 · 仅从零设计新产品且未定方向时推荐风格库</span>'}
2566
2623
  ${(g.styles||[]).length>1?'<div class="hint faint" style="margin-top:10px;line-height:1.6">这个项目有多份风格档 —— 出图时按任务挑对应那份(小程序与后台网站的圆角、间距、字号本来就该不一样)</div>':''}
2567
2624
  </div></div>
2568
2625
  <div class="kv" style="align-items:flex-start"><span class="kk">图标</span>
@@ -3261,17 +3318,19 @@ function renderSettings(){
3261
3318
  // 代理给连 Claude / ChatGPT 用,国内模型端点自动直连(服务端按已配的模型生成直连名单)
3262
3319
  const envPx=S.envProxy||'';
3263
3320
  const botCard=`<div class="card" id="botNetCard"><div class="lbl">机器人与网络</div>
3264
- <div class="field" style="max-width:320px"><label class="f">机器人名字</label><input id="bBrand" placeholder="SintBot" value="${esc(S.brand||'')}"/>
3265
- <div class="hint" style="margin-top:5px">自我介绍、升级播报里的自称 · 留空 = SintBot</div></div>
3266
- <div class="field" style="margin:12px 0 0;max-width:360px"><label class="f">网络代理</label><input id="pxHttp" placeholder="http://127.0.0.1:7890" value="${esc((px&&px.http)||'')}"/>
3267
- <div class="hint" style="margin-top:5px">连不上 Claude / ChatGPT 时填 · 国内模型端点自动直连 · 留空 = 不用代理</div>
3268
- ${envPx?`<div class="hint" style="margin-top:4px">后台服务的环境里已有代理:<span class="mono">${esc(envPx)}</span></div>`:''}
3269
- ${px&&px.noProxy?`<div class="hint" style="margin-top:4px">直连:<span class="mono">${esc(px.noProxy)}</span></div>`:''}</div>
3270
- <button class="btn primary" id="bSave" style="margin-top:14px">保存</button>
3271
- <div class="hint" style="margin-top:10px">代理第一次填,新的对话轮次就生效;改已有的地址要重启后台服务(sintbot service restart)。</div>
3321
+ <div class="botnet-fields">
3322
+ <div class="field"><label class="f">机器人名字</label><input id="bBrand" placeholder="SintBot" value="${esc(S.brand||'')}"/>
3323
+ <div class="hint" style="margin-top:5px">自我介绍、升级播报里的自称 · 留空 = SintBot</div></div>
3324
+ <div class="field"><label class="f">网络代理</label><input id="pxHttp" placeholder="http://127.0.0.1:7890" value="${esc((px&&px.http)||'')}"/>
3325
+ <div class="hint" style="margin-top:5px">连不上 Claude / ChatGPT 时填 · 国内模型端点自动直连 · 留空 = 不用代理</div></div>
3326
+ </div>
3327
+ ${envPx||(px&&px.noProxy)?`<details class="botnet-extra"><summary>后台网络状态</summary>
3328
+ ${envPx?`<div class="hint">后台服务的环境里已有代理:<span class="mono">${esc(envPx)}</span></div>`:''}
3329
+ ${px&&px.noProxy?`<div class="hint">直连:<span class="mono">${esc(px.noProxy)}</span></div>`:''}
3330
+ </details>`:''}
3331
+ <div class="botnet-actions"><button class="btn primary" id="bSave">保存</button>
3332
+ <div class="hint">代理第一次填,新的对话轮次就生效;改已有的地址要重启后台服务(sintbot service restart)。</div></div>
3272
3333
  </div>`;
3273
- const verhistCard=`<div class="card" style="grid-column:1/-1" id="verhistCard"><div class="lbl">版本历史</div>
3274
- <div id="verhistBox"><div class="empty">加载中…</div></div></div>`;
3275
3334
  const updateJob=upd&&upd.job;
3276
3335
  const updateBusy=!!(updateJob&&['queued','installing','restarting','restart-needed'].includes(updateJob.state));
3277
3336
  const updateText=!upd?'后台版本检查不可用':updateJob?esc(updateJob.message||updateJob.state):upd.available?`检测到新版本 v${esc(upd.latestVersion)} · 当前 v${esc(upd.currentVersion)}`:upd.checkedAt?`当前为最新版本 v${esc(upd.currentVersion)}`:'尚未检查新版';
@@ -3288,6 +3347,8 @@ function renderSettings(){
3288
3347
  <button class="btn" id="updIgnore" ${!upd||!upd.available?'disabled':''}>忽略此版本</button>
3289
3348
  </div>
3290
3349
  <div class="hint" style="margin-top:10px">只安装检测到的稳定版本;所有群任务与队列清空后才会安装。需要可写的全局安装目录,不会提权;后台服务会自动重启,前台运行需手动重启。</div>
3350
+ <div class="update-history"><div class="subhead">版本历史</div>
3351
+ <div id="verhistBox"><div class="empty">加载中…</div></div></div>
3291
3352
  </div>`;
3292
3353
  $('#settings').innerHTML=`<div class="grid cols2 stagger">
3293
3354
  ${netCard}
@@ -3311,10 +3372,10 @@ function renderSettings(){
3311
3372
  ${fxFeishuCard(fe)}
3312
3373
  ${fxSlackCard(S.slack||null)}
3313
3374
  ${fxWecomCard(S.wecom||null)}
3314
- ${updateCard}
3315
3375
  ${aboutCard}
3316
- ${verhistCard}
3376
+ ${updateCard}
3317
3377
  </div>`;
3378
+ updateTopUpdateBadge();
3318
3379
  loadChangelog();
3319
3380
  $('#bSave').onclick=async()=>{const brand=$('#bBrand').value.trim(),http=$('#pxHttp').value.trim();
3320
3381
  if(http&&!/^https?:\/\/\S+$/i.test(http)){toast('代理地址要像 http://127.0.0.1:7890','err');return;}
@@ -3704,6 +3765,16 @@ const splitList=s=>s.split(/[\n,]+/).map(x=>x.trim()).filter(Boolean);
3704
3765
  // 「开始使用」卡里的「去某页」按钮:委托到 document,重绘不用重新绑
3705
3766
  document.addEventListener('click',(e)=>{const b=e.target.closest&&e.target.closest('[data-gotab]');if(!b)return;const t=document.querySelector('[data-tab="'+b.dataset.gotab+'"]');if(t)t.click();});
3706
3767
  function renderAll(){renderOverview();renderProviders();renderGroups();renderPlayground();renderSettings();updateDaemon();updateTopFxApp();}
3768
+ function updateTopUpdateBadge(){
3769
+ const badge=$('#topUpdateBadge');if(!badge)return;
3770
+ badge.hidden=!(S&&S.update&&S.update.available);
3771
+ }
3772
+ (()=>{const badge=$('#topUpdateBadge');if(!badge)return;badge.onclick=()=>{
3773
+ const nav=$('nav [data-tab="settings"]');if(nav)nav.click();
3774
+ const card=$('#updateCard');if(!card)return;
3775
+ requestAnimationFrame(()=>{card.scrollIntoView({behavior:'smooth',block:'start'});
3776
+ card.style.boxShadow='0 0 0 2px rgba(var(--ember-rgb),.45)';setTimeout(()=>card.style.boxShadow='',1400);});
3777
+ };})();
3707
3778
  // 顶栏应用胶囊。数据同「设置」那张身份条(同一份落盘缓存),没取过就整块不显示 —— 顶栏不适合
3708
3779
  // 放「未获取」这类待办文案。头像 JS 注入并留 🤖 兜底(远端图,控制台可能开在断外网的机器上)。
3709
3780
  let topFxAvSrc='';
@@ -3826,6 +3897,7 @@ const I18N_EN=[
3826
3897
  ["机器人名字","Bot name"],
3827
3898
  ["自我介绍、升级播报里的自称 · 留空 = SintBot","How it refers to itself in introductions and upgrade notices · empty = SintBot"],
3828
3899
  ["网络代理","Network proxy"],
3900
+ ["后台网络状态","Background network details"],
3829
3901
  ["连不上 Claude / ChatGPT 时填 · 国内模型端点自动直连 · 留空 = 不用代理","Set this if Claude / ChatGPT can't be reached · China model endpoints connect directly · empty = no proxy"],
3830
3902
  ["后台服务的环境里已有代理:","The background service already has a proxy in its environment: "],
3831
3903
  ["直连:","Direct: "],
@@ -3950,6 +4022,9 @@ const I18N_EN=[
3950
4022
  ["插件库 · MCP","Plugin library · MCP"],
3951
4023
  ["本机装好的 MCP · 按运行时分两套 · 命令行安装、即时刷新","MCP servers installed on this machine · two sets, one per runtime · installed from the command line, refreshes instantly"],
3952
4024
  ["设计风格库","Design style library"],
4025
+ ["按需管理","Manage on demand"],
4026
+ ["按需查看","View on demand"],
4027
+ ["仅在全新产品且未定方向时按需使用预设","Use presets on demand only for a new product with no visual direction"],
3953
4028
  ["重新扫描风格预设(导入或改过自定义风格后用)","Rescan style presets (after importing or editing a custom style)"],
3954
4029
  ["重新扫描","Rescan"],
3955
4030
  ["用一张表单配一套自己的风格:色板、圆角、字重逐项定,右侧实时看效果。存进库后全部群都能用","Build your own style with a form: set palette, radius and weight one by one, with a live preview on the right. Once saved to the library every group can use it"],
@@ -4248,11 +4323,11 @@ const I18N_EN=[
4248
4323
  ["飞书 · 收发/管理群消息","Feishu · send and manage group messages"],
4249
4324
  ["群里直接对它说「每天9点拉构建报告」就会创建;支持创建/修改/删除定时任务,到点触发执行或只发提醒 —— 下方「定时任务」区可查看/管理","Just tell it in the group \"pull the build report at 9 every day\" and it creates one; supports creating/editing/deleting scheduled tasks that run the agent or just send a reminder when due — view/manage them in the \"Scheduled tasks\" section below"],
4250
4325
  ["定时任务","Scheduled tasks"],
4251
- ["内置多套设计风格预设,也能从参考网址/参考图提炼;挑方向时会把候选画成预览图发群里让你指着选(图由风格档 tokens 派生);写进项目 DESIGN.md 后该项目生图/图表全程遵循;全部风格见「模型 · 库」页的「设计风格库」","Ships with multiple design style presets and can extract one from a reference URL or image; when choosing a direction it renders candidates as previews and posts them to the group for you to point at (derived from the profile tokens); once written to the project DESIGN.md, every image and chart in that project follows it; see all styles under \"Design style library\" on the Models · Library page"],
4326
+ ["按需使用:仅从零设计新产品且未定视觉方向时推荐预设;也可按要求提炼参考并写入项目 DESIGN.md","On demand: presets are recommended only for a new product with no visual direction; references can also be extracted and saved to the project DESIGN.md when requested"],
4252
4327
  ["设计风格档","Design style profiles"],
4253
4328
  ["默认内建 · 飞书收发/拉历史/回复编辑撤回/下载/表情 ·","Built in by default · Feishu send/receive, history, reply/edit/recall, download, reactions · "],
4254
4329
  ["(群里说一句即可让 agent 创建/修改) ·"," (one sentence in the group lets the agent create or edit them) · "],
4255
- ["(内置预设/参考图提色,落盘 DESIGN.md) ·"," (built-in presets / colors extracted from references, saved to DESIGN.md) · "],
4330
+ ["(新产品设计时按需使用) ·"," (used on demand for new product design) · "],
4256
4331
  ["生图","Image generation"],
4257
4332
  ["(需机器已登录 codex,借订阅后台生图) · agent 首轮自知(身份/项目/模型/权限/控制台地址 + 已启用插件)"," (requires codex signed in on this machine; generates via the subscription in the background) · agent self-awareness on the first turn (identity/project/model/permissions/console URL + enabled plugins)"],
4258
4333
  ["(需机器已登录 codex,借订阅后台生图) · agent 首轮自知(身份/项目/模型/权限/控制台地址 + 已启用插件) ·"," (requires codex signed in on this machine; generates via the subscription in the background) · agent self-awareness on the first turn (identity/project/model/permissions/console URL + enabled plugins) · "],
@@ -4318,7 +4393,7 @@ const I18N_EN=[
4318
4393
  ["(未命名)","(untitled)"],
4319
4394
  ["这份档管这一块","This profile covers this platform"],
4320
4395
  ["把这一份复制进全局风格库,之后别的群也能直接挑到它(项目里那份不动)","Copy this profile into the global style library so other groups can pick it too (the project copy stays untouched)"],
4321
- ["未设置 · 设计任务开始时 agent 会先从风格库挑候选发群里让你选","Not set · when a design task starts, the agent picks candidates from the style library and posts them to the group for you to choose"],
4396
+ ["未设置 · 仅从零设计新产品且未定方向时推荐风格库","Not set · the style library is recommended only for a new product with no visual direction"],
4322
4397
  ["这个项目有多份风格档 —— 出图时按任务挑对应那份(小程序与后台网站的圆角、间距、字号本来就该不一样)","This project has several style profiles — the matching one is chosen per task when generating images (a mini program and an admin site should not share radii, spacing, and type sizes)"],
4323
4398
  ["图标","Icons"],
4324
4399
  ["未选 · 取图标会取不到","Not chosen · icon lookups will fail"],
@@ -4507,6 +4582,7 @@ const I18N_EN=[
4507
4582
  ["开关只控制本程序入口,不会改动反向代理或隧道。","These switches control this app only; they do not change the reverse proxy or tunnel."],
4508
4583
  ["局域网监听按当前端口即时切换;关闭后只允许本机访问。系统防火墙可能仍会拦截。","LAN binding changes immediately on the current port; when off, only this machine can connect. The system firewall may still block access."],
4509
4584
  ["软件更新","Software updates"],
4585
+ ["发现新版本,打开软件更新","New version available; open software updates"],
4510
4586
  ["后台版本检查不可用","Version checks are unavailable in this console"],
4511
4587
  ["自动检查稳定版(启动时及每天)","Automatically checks for stable releases at startup and daily"],
4512
4588
  ["升级后向群里播报(记住选择)","Announce upgrades in groups (remember this choice)"],
@@ -5262,7 +5338,7 @@ updateLangBtn();applyI18n(document.body); // 首屏静态 HTML 先翻一遍,
5262
5338
  /* ══════════ 界面语言 · 完 ════════════════════════════════════════════════════ */
5263
5339
 
5264
5340
  /* 版本历史:CHANGELOG.json 经 /api/changelog 一次拉取;当前版本(=顶栏徽章)标「当前」。
5265
- 顶栏版本徽章可点 → 跳设置页并滚动到卡片(短暂高亮指位)。
5341
+ 顶栏版本徽章可点 → 跳设置页并滚动到软件更新与版本历史卡片(短暂高亮指位)。
5266
5342
  英文界面另拉一份英文本(/api/changelog-en,按 version 对应):正文是数据不是界面文案,词典翻不了它
5267
5343
  ⇒ 有英文用英文、没有的版本(与条数对不上的条目)原样回退中文;日期与「当前」徽章两语言共用一套。 */
5268
5344
  let CHANGELOG=null,CHANGELOG_EN=null;
@@ -5283,7 +5359,7 @@ async function loadChangelog(){
5283
5359
  v.onclick=()=>{if(!S)return;
5284
5360
  $$('nav button').forEach(x=>x.classList.toggle('active',x.dataset.tab==='settings'));
5285
5361
  $$('.tab').forEach(t=>t.classList.toggle('on',t.id==='settings'));
5286
- const c=$('#verhistCard');if(c){c.scrollIntoView({behavior:'smooth',block:'start'});
5362
+ const c=$('#updateCard');if(c){c.scrollIntoView({behavior:'smooth',block:'start'});
5287
5363
  c.style.boxShadow='0 0 0 2px rgba(var(--ember-rgb),.45)';setTimeout(()=>c.style.boxShadow='',1400);}};})();
5288
5364
 
5289
5365
  /* ── 自绘下拉组件(见 .csel CSS):原生 <select> 的弹出列表是 OS 渲染、CSS 改不动 —— 保留 select
@@ -5381,7 +5457,7 @@ function startLive(){
5381
5457
  // recover whatever SSE dropped while we were away (SSE has no replay of its own);
5382
5458
  // 重连(非首连)还拉一次全量 state —— 断流期间的 live/conn 变化就地补齐,不整页重渲染。
5383
5459
  es.addEventListener('hello',()=>{$('#conn').classList.add('live');syncActivity(true);
5384
- if(++helloN>1)api('/api/state').then(r=>{S=r;updateDaemon();updateOverviewLive();updateConnLive();startClock();}).catch(()=>{});});
5460
+ if(++helloN>1)api('/api/state').then(r=>{S=r;updateDaemon();updateTopUpdateBadge();updateOverviewLive();updateConnLive();startClock();}).catch(()=>{});});
5385
5461
  es.addEventListener('tick',ev=>{try{const d=JSON.parse(ev.data);if(S){if(d.live)S.live=d.live;if('conn' in d)S.conn=d.conn;}updateDaemon();updateOverviewLive();updateConnLive();}catch{}});
5386
5462
  startAutoRefresh();
5387
5463
  es.addEventListener('conn',ev=>{try{const c=JSON.parse(ev.data);if(S){S.conn=Object.assign({},connDict(S.conn),{[c.platform||'feishu']:c});updateConnLive();}}catch{}}); // 连接状态变化即时推(不等心跳);事件带 platform 名,落进字典对应槽