@leviyuan/lodestar 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -4,62 +4,48 @@
4
4
 
5
5
  # 夜航星 (Lodestar)
6
6
 
7
- **把 Claude Code 装进你的飞书群。一个群 = 一个项目 = 一段不熄灯的对话。**
8
-
9
- 离开终端,但不离开 Claude Code。手机上、地铁里、半夜的床上,你只要拇指能点字,Claude 就在另一头跑着。
10
-
11
- ## 它为什么存在
12
-
13
7
  AI 不是帮手,是倍率。它放大的不是体力,是你——你的直觉、判断和品味,每一样都被乘以一个你以前不敢想的系数。最终走多远,取决于被放大的你有多强。
14
8
 
15
9
  夜航星让这件事真正发生:在你思考的地方接住想法,在你转身之后继续把它推向终点。**你醒着它在听,你睡了它还在跑。**
16
10
 
17
11
  ## 你会得到什么
18
12
 
19
- - 🌊 **真·流式卡片**:token 级渲染同一张卡,不刷屏
20
- - 🧠 **思考透明**:thinking 流式 + turn 后自动收起
21
- - 🔧 **工具调用折叠**:每次工具一格面板,折起概述/展开细节
22
- - 🔐 **审批就地完成**:工具卡上三按钮,不破坏时序
23
- - ❓ **结构化追问**:Ask 选项行 + 自由文本回答 + 多题翻页
24
- - ⌨️ **Type-ahead 不打断**:连珠炮全收,排队下一轮合并处理
25
- - 🔢 **合并消息加序号**:`[#N]\n` 前缀让模型看清独立边界
26
- - ⏳ **排队反应可见**:消息进队列加 ⏳,消化/取消自动清/换 ❌
27
- - ⏰ **定时唤醒可见化**:Cron / ScheduleWakeup 到点自开新卡
13
+ - 🌊 **流式卡片**:token 级渲染同一张卡,不刷屏;assistant 段、工具调用、追问全收纳在一张卡的不同面板里
14
+ - 🔧 **工具一格一面板**:折起概述、展开细节;连续 `Read` 自动合批一格;权限/审批就地三按钮
15
+ - **结构化追问**:`AskUserQuestion` 选项行 + 自由文本回答 + 多题翻页
16
+ - ⌨️ **连珠炮安全**:type-ahead 全收,排队进 ⏳ 反应,下一轮合并喂回模型,用 `<u>...</u>` 拆开独立消息
28
17
  - 📊 **footer 实时指标**:`✅ ⏱时长 · 📊上下文% · 💰本轮成本`
29
- - 📦 **`hi` 弹控制台**:跨群项目、上下文%、订阅额度一屏看完
30
- - 📎 **图文双向互传**:`[file:]` 进、`[[send:]]` 出,路径白名单
31
- - 📲 **关键时刻加急**:Ask / 审批 / done 锁屏推送,定时不打扰
18
+ - 📦 **`hi` 控制台**:跨群项目、上下文%、订阅额度一屏看完
19
+ - 📎 **图文双向**:`[file: /abs/path]` 进、`[[send: /abs/path]]`
20
+ - 📲 **关键时刻加急**:Ask / 审批 / done 走 `im:message.urgent` 锁屏推送,定时唤醒不打扰
32
21
  - 🛑 **`stop` 软打断**:取消当前 turn + 清队列,子进程保活
33
22
  - 🗂 **多项目并发**:一个 daemon 持 N 群 ↔ N session
34
- - 🔄 **自动 resume**:重启自动续接,session_id 落盘不丢
35
- - 🛡 **守护级稳定**:WS watchdog + 单 PID + alive marker(自动 resume 上次活跃 session)
36
- - 📡 **HTTP 通知端点**:任意本机进程 `POST /notify` 一行 curl 把 markdown 推成卡片,info / warn / error 染色
23
+ - 🔄 **自动 resume**:重启后上次活跃 session 全部 `--resume`,主动 `kill` 过的不吵醒
24
+ - 🛡 **守护级稳定**:WS watchdog + 单 PID + alive marker
25
+ - 📡 **HTTP 通知端点**:任意本机进程 `POST /notify` 把 markdown 推成卡片,info / warn / error 染色
26
+ - ⏰ **定时任务**:让 Claude 自己定期跑一轮,每次 fire 起 fresh 子进程不累积上下文;silent 只推结果 / verbose 全 transcript 可切;hi 面板带删/切按钮
37
27
 
38
28
  ## 怎么用
39
29
 
40
- 每个飞书群对应一个 Claude 会话。**群名 = 用户主目录下的项目目录名**(`projects_root` 配置项可改)。这套绑定是骨架,新群第一次发消息时,daemon 会自动 `mkdir -p <projects_root>/<群名>` + `git init` 把项目骨架打起来,**开新群 = 开新项目**。
41
-
42
- 在群里发任意文字,Claude 接管这一轮。回复以流式打字机渲染在一张卡片里,工具调用、思考过程、权限审批、追问选项,全都收纳在这张卡片的不同面板里——一目了然,可转发,可回看。
30
+ 每个飞书群对应一个 Claude 会话。**群名 = `projects_root` 下的目录名**。新群第一次发消息时 daemon 自动 `mkdir -p` + `git init`,开新群 = 开新项目。
43
31
 
44
- 下一句话开新一轮卡片。
32
+ 群里发任意文字,Claude 接管这一轮,流式打字机渲染在一张卡片里。下一句话开新一轮卡片。
45
33
 
46
- ### 文本控制指令
34
+ ### 控制指令
47
35
 
48
- 直接发这四个**裸词**(不需要斜杠,不区分大小写),daemon 拦截、不转发给 Claude:
36
+ 直接发这五个**裸词**(不要斜杠,大小写不敏感),daemon 拦截、不转发给 Claude:
49
37
 
50
38
  | 指令 | 行为 |
51
39
  | --- | --- |
52
40
  | `hi` | 未运行时启动;运行中弹一张**状态卡片** |
53
- | `stop` | 软打断当前 turn + 清空 type-ahead 排队;子进程保活,刚排队中的消息会被打 `CrossMark` 反应表示取消 |
54
- | `kill` | 优雅关闭 Claude 进程;`sessionId` 仍记在磁盘,下次 `restart` 还能 resume |
55
- | `restart` | 用上一次的 `sessionId` 重启会话(保留上下文);无进程时也能用,等于"恢复上一会话" |
56
- | `clear` | 杀掉当前进程并启动一个全新 session(等价于 Claude Code 的 `/clear`);**无进程时无效** |
57
-
58
- > 这五个词被全局保留:在群里发 "hi" 当问候也会触发控制台卡片,不会到 Claude 那边。换来的是手机上单手打字的便利。
41
+ | `stop` | 软打断当前 turn + 清队列;子进程保活,排队中的消息打 |
42
+ | `kill` | 优雅关闭 Claude 进程;`sessionId` 落盘,可 `restart` 接回 |
43
+ | `restart` | 用上次 `sessionId` 重启(保留上下文);无进程时也能用 |
44
+ | `clear` | 杀进程并开新 session(等价 `/clear`);无进程时无效 |
59
45
 
60
46
  ## 安装
61
47
 
62
- Windows / macOS / Linux 通吃,只要有 Node ≥ 18。
48
+ Windows / macOS / Linux 通吃,只要 Node ≥ 18。
63
49
 
64
50
  ### 1. 装包
65
51
 
@@ -67,26 +53,22 @@ Windows / macOS / Linux 通吃,只要有 Node ≥ 18。
67
53
  npm i -g @leviyuan/lodestar
68
54
  ```
69
55
 
70
- `@anthropic-ai/claude-code` 是 peer dep,npm 7+ 会自动连带装,装完终端里 `lodestar-daemon`、`lodestar-setup`、`claude` 三个命令都在 PATH 上。
71
-
72
- > **Windows**:[nodejs.org](https://nodejs.org) 下 LTS MSI 装好 Node,然后开 cmd / PowerShell 跑上面那行。
73
- > **没装过 Bun 也行**,这个包发布出去就是纯 Node 跑的。
56
+ `@anthropic-ai/claude-code` 是 peer dep,npm 7+ 自动连带装。装完 `lodestar-daemon` / `lodestar-setup` / `claude` 三个命令进 PATH
74
57
 
75
58
  ### 2. 飞书自建应用
76
59
 
77
- 去[飞书开放平台](https://open.feishu.cn/app)→ 创建企业自建应用,然后:
60
+ 去[飞书开放平台](https://open.feishu.cn/app)→ 创建企业自建应用:
78
61
 
79
- 1. **添加机器人能力**:左侧"添加应用能力"→"机器人"→ 点 **添加** 按钮。
80
- 2. **开通权限**(权限管理 → **开通权限**):
62
+ 1. **添加机器人能力**:左侧"添加应用能力"→"机器人"→**添加**。
63
+ 2. **开通权限**(权限管理 → 开通权限):
81
64
  - 消息:`im:message:send_as_bot` `im:message` `im:chat` `im:resource` `im:message.urgent` `im:message.group_msg`(敏感,需审批) `im:message.group_at_msg:readonly`
82
65
  - 卡片:`cardkit:card:read` `cardkit:card:write`
83
- - ⚠️ **`im:message.group_msg` 是核心**:没它机器人只收 @ 自己的消息,拿不到群里其他对话。敏感权限要走审批,填用途后个人开发者通常秒过。
84
66
  3. **订阅事件**(事件与回调,拆两个子页):
85
- - **事件配置** 页:订阅方式选 **长连接** → 保存 → 添加事件 `im.message.receive_v1`(收群消息)
86
- - **回调配置** 页:订阅方式选 **长连接** → 保存 → 添加事件 `card.action.trigger`(卡片按钮回调)
87
- 4. **发布版本**:页面顶部 **创建版本** → 滚到底点 **保存** → 弹框点 **发布**。**没发版的应用不会收到事件**,这一步常被忘记。
88
- 5. **拿凭据**:凭据与基础信息页拷 `App ID`(`cli_xxxxxxxxxx`)和 `App Secret`,下一步配置向导会问你。
89
- 6. **拉机器人进群**:想用的飞书群群设置 → 群机器人 → 添加机器人选你的应用。**群名要等于用户主目录下的项目目录名**,daemon 用这个绑定群 Claude session。
67
+ - **事件配置**:订阅方式选**长连接** → 保存 → 添加 `im.message.receive_v1`
68
+ - **回调配置**:订阅方式选**长连接** → 保存 → 添加 `card.action.trigger`
69
+ 4. **发布版本**:顶部 **创建版本** → 滚到底 **保存** → 弹框 **发布**。**没发版收不到任何事件**。
70
+ 5. **拿凭据**:`App ID`(`cli_xxx`)和 `App Secret`,下一步要用。
71
+ 6. **拉机器人进群**:群设置 → 群机器人 → 添加选你的应用。**群名 = `projects_root` 下的目录名**。
90
72
 
91
73
  ### 3. 跑配置向导
92
74
 
@@ -94,35 +76,22 @@ npm i -g @leviyuan/lodestar
94
76
  lodestar-setup
95
77
  ```
96
78
 
97
- 交互式问你 3 件事:
79
+ 四步走完即可:
98
80
 
99
- 1. 上一步拿到的 `App ID` / `App Secret`
100
- 2. **LLM 后端**(4 选 1):
101
- - **Anthropic 官方**:`claude.ai` 订阅或 API key,美元结算
102
- - **GLM 智谱 coding plan**:国内可访问,人民币计费,跟 Claude Code 协议原生兼容
103
- - **DeepSeek + anthropic-proxy**:最便宜,需要自己跑一个 proxy 转协议
104
- - **自定义 `base_url`**:高级
105
- 3. `projects_root`(默认是用户主目录)
81
+ 1. **Claude CLI**:没装会自动 `npm i -g @anthropic-ai/claude-code`
82
+ 2. **LLM 后端**(2 选 1):
83
+ - **已配过**:claude.ai 订阅 / API key / 已设环境变量,直接用
84
+ - **用 DeepSeek**(推荐,国内可用,人民币计费):粘 DeepSeek API key,向导写好 8 `ANTHROPIC_*` / `CLAUDE_CODE_*` 到 `[claude.env]` 节,daemon 拉起 claude 时自动注入,**不碰系统环境变量**
85
+ 3. **Feishu 凭据**:粘上一步的 `App ID` / `App Secret`,向导调 `tenant_access_token` 端点验真,失败重输
86
+ 4. **`projects_root`**(默认用户主目录),写盘后自动 detach 启动 daemon
106
87
 
107
- 写到:
88
+ 配置写到:
108
89
  - Linux / macOS:`~/.config/lodestar/config.toml`
109
90
  - Windows:`%APPDATA%\Lodestar\config.toml`
110
91
 
111
- 设了 LLM 后端的话,daemon 拉起 claude 子进程时会自动注入 `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` —— **不用碰系统环境变量**。
112
-
113
- > 想跳过向导手写配置也行,schema 见[配置文件](#配置文件)章节。
114
-
115
- ### 4. 启动
92
+ ### 4. 7×24 守护(可选)
116
93
 
117
- ```bash
118
- lodestar-daemon
119
- ```
120
-
121
- 把机器人拉进任意飞书群,发一条消息 —— Claude 上线。
122
-
123
- ### 5. 7×24 守护(可选)
124
-
125
- **Linux / macOS** 用 `systemd --user`(把 `ExecStart` 路径换成你 `which lodestar-daemon` 的结果):
94
+ **Linux / macOS** 用 `systemd --user`(`ExecStart` 换成你 `which lodestar-daemon` 的路径):
126
95
 
127
96
  ```ini
128
97
  [Unit]
@@ -143,13 +112,13 @@ WantedBy=default.target
143
112
  systemctl --user enable --now lodestar
144
113
  ```
145
114
 
146
- **Windows** 用 Task Scheduler 设登录时拉起 `lodestar-daemon`;或者干脆开一个 cmd / PowerShell 窗口让它一直挂着(关窗就停)。
115
+ **Windows** 用 Task Scheduler 在登录时拉起 `lodestar-daemon`。
147
116
 
148
- WS watchdog + alive-marker 联手:每次重启,daemon 会把**上次还在运行的 session 全部 `--resume` 自动复活**;你主动 `kill` 过的不会被吵醒。
117
+ 每次重启,上次还活着的 session 全部 `--resume` 自动复活;主动 `kill` 过的留在停机态。
149
118
 
150
119
  ### 配置文件
151
120
 
152
- 向导写出来的 TOML 长这样:
121
+ 向导写出来的 TOML:
153
122
 
154
123
  ```toml
155
124
  [feishu]
@@ -159,21 +128,23 @@ app_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
159
128
  [runtime]
160
129
  projects_root = "/home/you"
161
130
 
162
- [claude] # 可选,留空则走 claude 自带登录
163
- base_url = "https://open.bigmodel.cn/api/anthropic"
164
- auth_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
131
+ # 可选,daemon 拉起 claude 子进程时注入这些 env (DeepSeek / GLM / 任意 anthropic 兼容后端)
132
+ [claude.env]
133
+ ANTHROPIC_BASE_URL = "https://api.deepseek.com/anthropic"
134
+ ANTHROPIC_AUTH_TOKEN = "sk-xxxxxxxx"
135
+ # ... 选 DeepSeek 时向导自动填全 8 个变量
165
136
 
166
- [notify] # 可选,默认 127.0.0.1:9876
137
+ # 可选,默认 127.0.0.1:9876
138
+ [notify]
167
139
  bind = "127.0.0.1"
168
140
  port = 9876
169
141
  ```
170
142
 
171
- 路径覆盖:`LODESTAR_CONFIG=/abs/path.toml`、`LODESTAR_CONFIG_DIR=...`、`XDG_CONFIG_HOME` 都认。运行时状态走 Linux/Mac `~/.local/share/lodestar/` 或 Windows `%LOCALAPPDATA%\Lodestar\`(`LODESTAR_DATA_DIR` / `XDG_DATA_HOME` 可改写) —— daemon.pid、daemon.log、session-chat-map、session-resume-map、alive-marker、inbox/ 都在那里。
143
+ 路径覆盖:`LODESTAR_CONFIG` / `LODESTAR_CONFIG_DIR` / `XDG_CONFIG_HOME` 都认。运行时状态在 Linux/Mac `~/.local/share/lodestar/` 或 Windows `%LOCALAPPDATA%\Lodestar\`(`LODESTAR_DATA_DIR` / `XDG_DATA_HOME` 可改) —— daemon.pid、daemon.log、session-chat-map、alive-marker、inbox/ 都在那里。
172
144
 
173
145
  ## 通知端点(Notify)
174
146
 
175
- 本机任何进程都能往群里推一张卡片 —— 不走 SDK、不走鉴权,daemon 启动时
176
- 顺带跑一个 loopback HTTP listener,默认绑 `127.0.0.1:9876`。
147
+ 本机任何进程都能往群里推一张卡片 —— 不走 SDK、不走鉴权,daemon 启动时顺带跑一个 loopback HTTP listener,默认绑 `127.0.0.1:9876`。
177
148
 
178
149
  ```bash
179
150
  curl -fsS -X POST http://127.0.0.1:9876/notify \
@@ -181,28 +152,18 @@ curl -fsS -X POST http://127.0.0.1:9876/notify \
181
152
  -d '{"project":"feishu","text":"**build done** 12 files"}'
182
153
  ```
183
154
 
184
- 请求体:
185
-
186
155
  | 字段 | 必需 | 说明 |
187
156
  | --- | --- | --- |
188
- | `project` | ✅ | 飞书群名(= session 名 = `~/` 下的项目目录名)|
189
- | `text` | ✅ | Feishu schema-2.0 markdown:`**bold**`、`` `code` ``、`[link](url)`、`<font color='red'>…</font>`;~30 KB 上限,超限返回 502 |
190
- | `title` | | 卡片 header 标题,默认等于 `project` |
191
- | `level` | | `info`(默认,蓝)/ `warn`(黄)/ `error`(红)|
157
+ | `project` | ✅ | 飞书群名(= session 名 = 项目目录名)|
158
+ | `text` | ✅ | Feishu schema-2.0 markdown:`**bold**`、`` `code` ``、`[link](url)`、`<font color='red'>…</font>`;~30 KB 上限 |
159
+ | `title` | | 卡片 header,默认等于 `project` |
160
+ | `level` | | `info`(蓝,默认)/ `warn`(黄)/ `error`(红)|
192
161
 
193
162
  响应:`200 {ok, chat_id, message_id}` / `400` 参数错 / `404` 群没绑定过 / `502` 飞书 API 拒收。
194
163
 
195
- > ⚠️ 群必须**至少有一条消息**触达过 daemon(WS 收到过),否则 `chatIdForSession` 查不到绑定,返回 404。新建群第一次发消息后即可用。
196
-
197
- 可选配置(Linux/Mac `~/.config/lodestar/config.toml`;Windows `%APPDATA%\Lodestar\config.toml`):
164
+ > ⚠️ 群必须**至少有一条消息**触达过 daemon(WS 收到过),否则 404。新建群第一次发消息后即可用。
198
165
 
199
- ```toml
200
- [notify]
201
- bind = "127.0.0.1" # 默认 loopback;改 0.0.0.0 必须自己加前置鉴权
202
- port = 9876
203
- ```
204
-
205
- cron / systemd hook 的常见用法:
166
+ cron / systemd hook 用法:
206
167
 
207
168
  ```cron
208
169
  0 3 * * * /usr/local/bin/backup.sh \
@@ -212,10 +173,33 @@ cron / systemd hook 的常见用法:
212
173
  -d '{"project":"ops","level":"error","text":"❌ nightly backup FAILED"}'
213
174
  ```
214
175
 
215
- > 想让 Claude Code 自动调这个 endpoint(说一句"build 完通知我"就自己推),
216
- > 建议你自己写一个 skill —— 在 `~/.claude/skills/feishu-notify/SKILL.md`
217
- > 放一个 frontmatter + 触发关键词,把上面这段 curl 的 shape 抄进去即可。
218
- > 项目本身不附带 skill 文件,要不要装、装成什么样,完全交给你。
176
+ > 想让 Claude Code 自己说一句 "build 完通知我" 就推送 —— daemon 启动时**自动同步** `~/.claude/skills/feishu-notify/SKILL.md`,主端 Claude 立即就能用关键词触发(`LODESTAR_DISABLE_SKILL_SYNC=1` 关闭自动同步)
177
+
178
+ ## 定时任务(Schedule)
179
+
180
+ 让 Claude **自己**定期跑一轮 —— 比上面 cron-curl 推卡片有用一个量级:每次 fire 起一个**全新的 Claude 子进程**(无 `--resume`、fresh `sessionId`、`bypassPermissions`、cwd = 项目目录),让它读最新的 git 状态 / 日志文件 / API,自己分析,然后把结果按你定义的格式贴回群里。
181
+
182
+ **怎么加**:在群里跟 Claude 说"以后每天早上 9 点 silent 给我总结昨天的 PR"就行 —— claude 会调 MCP 工具 `schedule_create` 落到 daemon 的 `schedules.json`。
183
+
184
+ | 工具 | 用途 |
185
+ | --- | --- |
186
+ | `schedule_create` | cron 周期(标准 5 段 `m h dom mon dow`,支持 `*` `*/N` `a,b,c` `a-b`) |
187
+ | `schedule_once` | 一次性延时(对齐内置 `ScheduleWakeup` 语义) |
188
+ | `schedule_list` | 当前项目所有 schedule |
189
+ | `schedule_delete` | 按 id 删除 |
190
+
191
+ MCP 工具是 daemon spawn claude 时通过 `--mcp-config` 注入的 —— **不用碰 `~/.claude.json`**;每个群独立 URL path `/mcp/<project>` 钉死项目,跨群零泄漏。
192
+
193
+ **两种 fire 输出**:
194
+
195
+ | 模式 | 输出 | 适用 |
196
+ | --- | --- | --- |
197
+ | `silent`(默认) | 只把最终 assistant text 推一张 notify 风格卡 | 每日报告、状态摘要、不打扰 |
198
+ | `verbose` | 完整 transcript:prompt 折叠 + assistant 段 + 工具调用 panel(input + output)+ 耗时/token footer | 偶尔检查任务跑得对不对 |
199
+
200
+ **`hi` 面板**:任意群发 `hi`,**⏰ 定时任务** 段列出 daemon 名下所有 schedule(跨项目全局 dashboard),每条带 `📁 项目` + `🔕silent` / `📜verbose` + 下次触发时间,展开看完整 prompt + cron + 上次触发,两个按钮 **切换 mode** / **🗑 删除** 走 `update_multi` 原地刷新当前卡。
201
+
202
+ **对比 SDK 自带的 `ScheduleWakeup` / `CronCreate`**:daemon 这一层补三件事 —— 持久化(daemon 重启不丢)、每次 fire 干净进程(不累积上下文)、跨项目可见(hi 面板)。
219
203
 
220
204
  ## 许可
221
205
 
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as f}from"node:child_process";import{existsSync as U,mkdirSync as v,writeFileSync as D}from"node:fs";import{createInterface as h}from"node:readline/promises";import{delimiter as u,dirname as I,join as F}from"node:path";import{fileURLToPath as m}from"node:url";import{homedir as E}from"node:os";import{join as Y}from"node:path";var M=E(),x=process.platform==="win32";function B($,q,J){if($)return $;if(q)return Y(q,"lodestar");return J}function _(){if(x)return Y(process.env.APPDATA??Y(M,"AppData","Roaming"),"Lodestar");return Y(M,".config","lodestar")}function j(){if(x)return Y(process.env.LOCALAPPDATA??Y(M,"AppData","Local"),"Lodestar");return Y(M,".local","share","lodestar")}var G=B(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,_()),b=B(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,j()),V=process.env.LODESTAR_CONFIG??Y(G,"config.toml"),o=Y(b,"daemon.pid"),a=Y(b,"daemon.log"),t=Y(b,"session-chat-map.json"),n=Y(b,"session-resume-map.json"),e=Y(b,"alive-on-shutdown.json"),C=Y(b,"inbox"),zz=Y(b,"debug.sock"),$z=Y(b,"debug-context.json");var z={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",red:"\x1B[31m"},W=h({input:process.stdin,output:process.stdout});async function y($,q={}){while(!0){let J=q.default?`${z.dim} [${q.default}]${z.reset}`:"",Q=(await W.question(`${z.cyan}? ${z.reset}${$}${J}
2
+ import{spawn as f}from"node:child_process";import{existsSync as U,mkdirSync as v,writeFileSync as h}from"node:fs";import{createInterface as u}from"node:readline/promises";import{delimiter as D,dirname as I,join as O}from"node:path";import{fileURLToPath as m}from"node:url";import{homedir as A}from"node:os";import{join as Y}from"node:path";var M=A(),x=process.platform==="win32";function B($,q,J){if($)return $;if(q)return Y(q,"lodestar");return J}function _(){if(x)return Y(process.env.APPDATA??Y(M,"AppData","Roaming"),"Lodestar");return Y(M,".config","lodestar")}function j(){if(x)return Y(process.env.LOCALAPPDATA??Y(M,"AppData","Local"),"Lodestar");return Y(M,".local","share","lodestar")}var G=B(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,_()),b=B(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,j()),V=process.env.LODESTAR_CONFIG??Y(G,"config.toml"),o=Y(b,"daemon.pid"),a=Y(b,"daemon.log"),t=Y(b,"session-chat-map.json"),n=Y(b,"session-resume-map.json"),e=Y(b,"schedules.json"),C=Y(b,"alive-on-shutdown.json"),zz=Y(b,"inbox"),$z=Y(b,"debug.sock"),qz=Y(b,"debug-context.json");var z={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",red:"\x1B[31m"},W=u({input:process.stdin,output:process.stdout});async function y($,q={}){while(!0){let J=q.default?`${z.dim} [${q.default}]${z.reset}`:"",Q=(await W.question(`${z.cyan}? ${z.reset}${$}${J}
3
3
  ${z.green}>${z.reset} `)).trim();if(!Q&&q.default!==void 0)return q.default;if(!Q&&q.required){console.log(`${z.red}必填,请重新输入${z.reset}`);continue}return Q}}async function c($,q){while(!0){console.log(`${z.cyan}? ${z.reset}${$}`);for(let Z=0;Z<q.length;Z++)console.log(` ${z.bold}[${Z+1}]${z.reset} ${q[Z].label}`);let J=(await W.question(`${z.green}选择 [1-${q.length}]:${z.reset} `)).trim(),Q=parseInt(J,10)-1;if(Q>=0&&Q<q.length)return q[Q].value;console.log(`${z.red}无效选择${z.reset}`)}}function r($){let q="═".repeat(58);console.log(`
4
4
  ${z.bold}${z.cyan}╔${q}╗${z.reset}`),console.log(`${z.bold}${z.cyan}║ ${$.padEnd(54)} ║${z.reset}`),console.log(`${z.bold}${z.cyan}╚${q}╝${z.reset}
5
5
  `)}function P($,q,J){console.log(`
6
6
  ${z.bold}${z.yellow}[${$}/${q}] ${J}${z.reset}
7
- `)}function H($){return $.replace(/\\/g,"\\\\").replace(/"/g,"\\\"")}function R($){let q=process.env.PATH??"";if(!q)return null;let J=process.platform==="win32"?[`${$}.cmd`,`${$}.bat`,`${$}.exe`,$]:[$];for(let Q of q.split(u)){if(!Q)continue;for(let Z of J){let g=F(Q,Z);if(U(g))return g}}return null}async function l(){let $=process.platform==="win32"?"npm.cmd":"npm";return new Promise((q)=>{let J=f($,["install","-g","@anthropic-ai/claude-code"],{stdio:"inherit",shell:process.platform==="win32"});J.on("exit",(Q)=>q(Q===0)),J.on("error",()=>q(!1))})}function k($){try{if(process.platform==="win32")f(process.env.ComSpec??"cmd.exe",["/c","start",'""',$],{detached:!0,stdio:"ignore",windowsHide:!0}).unref();else if(process.platform==="darwin")f("open",[$],{detached:!0,stdio:"ignore"}).unref();else f("xdg-open",[$],{detached:!0,stdio:"ignore"}).unref()}catch{}}async function p($,q){try{let Q=await(await fetch("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({app_id:$,app_secret:q})})).json();if(Q.tenant_access_token)return{ok:!0};return{ok:!1,error:`飞书拒绝: code=${Q.code} msg=${Q.msg??"(no msg)"}`}}catch(J){return{ok:!1,error:`网络错误: ${J?.message??String(J)}`}}}function s(){try{let $=I(m(import.meta.url)),q=F($,"lodestar.js");if(!U(q))return{error:`找不到 daemon bundle: ${q}`};let J=f(process.execPath,[q],{detached:!0,stdio:"ignore",windowsHide:!0});return J.unref(),{pid:J.pid}}catch($){return{error:$?.message??String($)}}}async function O(){if(!process.stdin.isTTY)console.error(`${z.red}lodestar-setup: stdin 不是 TTY,无法交互式输入。${z.reset}`),console.error("请直接在 cmd / PowerShell / Terminal 里跑,不要 pipe 或重定向 stdin。"),process.exit(1);if(U(V)){if(console.log(`${z.yellow}发现已有配置: ${V}${z.reset}`),(await y("覆盖? (y/N)",{default:"n"})).toLowerCase()!=="y"){console.log("已取消"),W.close();return}}r("Lodestar 安装向导"),console.log("Lodestar 把 Feishu (飞书) 群聊接到 Claude Code。"),console.log("每个群对应一个项目目录, Claude 在那里跑、能读写文件。"),console.log(),console.log("本向导依次做 4 件事:"),console.log(` ${z.dim}1) 确保 claude CLI 已装好${z.reset}`),console.log(` ${z.dim}2) 选 LLM 后端 (订阅 / API key / DeepSeek)${z.reset}`),console.log(` ${z.dim}3) Feishu 自建应用 (含权限 / 事件 / 发版 + 凭据测试)${z.reset}`),console.log(` ${z.dim}4) 工作目录, 自动启动 daemon${z.reset}`),console.log(),await W.question(`${z.dim}按 Enter 开始 (Ctrl+C 退出)...${z.reset}`),P(1,4,"准备 Claude Code");let $=R("claude");if($)console.log(`${z.green}✓ claude CLI 已就位${z.reset}: ${z.dim}${$}${z.reset}`);else{if(console.log(`${z.yellow}未在 PATH 找到 claude CLI, 自动安装...${z.reset}`),console.log(`${z.dim}运行: npm install -g @anthropic-ai/claude-code${z.reset}`),console.log(),!await l())console.error(`
8
- ${z.red}安装失败。${z.reset}`),console.error("请手动运行后再开向导:"),console.error(` ${z.cyan}npm install -g @anthropic-ai/claude-code${z.reset}`),console.error(` ${z.cyan}lodestar-setup${z.reset}`),W.close(),process.exit(1);$=R("claude"),console.log(`${z.green}✓ 安装完成${z.reset}: ${z.dim}${$??"(应该装好了, 但 PATH 找不到 — 重开终端再试)"}${z.reset}`)}P(2,4,"LLM 后端"),console.log("claude CLI 默认走 Anthropic 官方, 需要 claude.ai 订阅或 API key。"),console.log("国内访问 anthropic.com 不一定通, 也可以让 claude 走 DeepSeek 后端。"),console.log();let q=await c("你的 claude 怎么走?",[{label:"已经配过 (订阅 / API key / 已设环境变量), 直接用",value:"existing"},{label:`${z.bold}用 DeepSeek${z.reset}${z.dim} (国内可用, 人民币计费, 推荐)${z.reset}`,value:"deepseek"}]),J={};if(q==="deepseek"){console.log(),console.log("打开浏览器拿 DeepSeek API key:");let X="https://platform.deepseek.com/api_keys";console.log(` ${z.cyan}${X}${z.reset}`),k(X),console.log(`${z.dim}(如果浏览器没自动开, 复制上面 URL 粘到浏览器)${z.reset}`),console.log();let L=await y("DeepSeek API key (以 sk- 开头)",{required:!0});J.ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic",J.ANTHROPIC_AUTH_TOKEN=L,J.ANTHROPIC_MODEL="deepseek-v4-pro",J.ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-pro",J.ANTHROPIC_DEFAULT_SONNET_MODEL="deepseek-v4-pro",J.ANTHROPIC_DEFAULT_HAIKU_MODEL="deepseek-v4-flash",J.CLAUDE_CODE_SUBAGENT_MODEL="deepseek-v4-flash",J.CLAUDE_CODE_EFFORT_LEVEL="max",console.log(),console.log(`${z.green}✓ DeepSeek 配置已记录${z.reset} (写到 [claude.env] 节, daemon 拉起 claude 时自动注入)`)}else console.log(`${z.dim}OK, 跳过 LLM 后端配置 — daemon 启动时会继承当前环境 + claude 自带 auth。${z.reset}`);P(3,4,"Feishu 自建应用");let Q="https://open.feishu.cn/app";console.log("打开飞书开放平台 (浏览器):"),console.log(` ${z.cyan}${Q}${z.reset}`),k(Q),console.log(`${z.dim}(如果浏览器没自动开, 复制上面 URL 粘到浏览器)${z.reset}`),console.log(),console.log(`${z.bold}详细操作步骤:${z.reset}`),console.log(),console.log(` ${z.bold}① 创建应用${z.reset}`),console.log(` 点 "创建企业自建应用", 填名字 (如 ${z.dim}Lodestar${z.reset}), logo 随意。`),console.log(),console.log(` ${z.bold}② 添加机器人能力${z.reset}`),console.log(` 左侧菜单 "${z.cyan}添加应用能力${z.reset}" → 找到 "机器人" → 点 "${z.bold}添加${z.reset}" 按钮。`),console.log(),console.log(` ${z.bold}③ 申请权限 (左侧 "${z.cyan}权限管理${z.reset}" → "${z.bold}开通权限${z.reset}")${z.reset}`),console.log(` ${z.yellow}缺一个都会让 daemon 启动后默默丢消息, 一定要全开。${z.reset}`),console.log(` ${z.dim}消息类:${z.reset}`),console.log(` • ${z.bold}im:message:send_as_bot${z.reset} ${z.dim}# 以机器人身份发消息${z.reset}`),console.log(` • ${z.bold}im:message${z.reset} ${z.dim}# 接收/操作消息 (核心)${z.reset}`),console.log(` • ${z.bold}im:chat${z.reset} ${z.dim}# 读/写群信息 (匹配群名 ↔ 项目目录)${z.reset}`),console.log(` • ${z.bold}im:resource${z.reset} ${z.dim}# 上传/下载附件 (图文双向)${z.reset}`),console.log(` • ${z.bold}im:message.urgent${z.reset} ${z.dim}# 加急推送 (锁屏通知 / Ask)${z.reset}`),console.log(` • ${z.bold}im:message.group_msg${z.reset} ${z.red}# 敏感: 接收群里所有消息${z.reset}`),console.log(` ${z.dim}└ 关键: 没它机器人只收 @ 自己的消息, 拿不到群里其他对话, 一定要开${z.reset}`),console.log(` ${z.dim}└ 敏感权限要走审批: 申请时填用途, 个人开发者通常秒过${z.reset}`),console.log(` • ${z.bold}im:message.group_at_msg:readonly${z.reset} ${z.dim}# 读 @ 机器人消息 (兜底)${z.reset}`),console.log(` ${z.dim}卡片类 (Card Kit):${z.reset}`),console.log(` • ${z.bold}cardkit:card:read${z.reset} ${z.dim}# 读卡片状态${z.reset}`),console.log(` • ${z.bold}cardkit:card:write${z.reset} ${z.dim}# 创建/更新卡片 (流式渲染核心)${z.reset}`),console.log(),console.log(` ${z.bold}④ 订阅事件 (左侧 "${z.cyan}事件与回调${z.reset}", 拆两个子页:)${z.reset}`),console.log(` ${z.dim}a)${z.reset} ${z.bold}事件配置${z.reset} 页:`),console.log(` ${z.yellow}• "订阅方式" → 选 "长连接" → 点保存${z.reset}`),console.log(` • 添加事件: ${z.bold}im.message.receive_v1${z.reset} ${z.dim}# 收群消息${z.reset}`),console.log(` ${z.dim}b)${z.reset} ${z.bold}回调配置${z.reset} 页:`),console.log(` ${z.yellow}• "订阅方式" → 选 "长连接" → 点保存${z.reset}`),console.log(` • 添加事件: ${z.bold}card.action.trigger${z.reset} ${z.dim}# 卡片按钮点击回调${z.reset}`),console.log(),console.log(` ${z.bold}⑤ 发布版本${z.reset}`),console.log(` 页面顶部 "${z.bold}创建版本${z.reset}" → 滚到底点 "${z.bold}保存${z.reset}" → 弹框点 "${z.bold}发布${z.reset}"。`),console.log(` ${z.yellow}没发版的应用收不到任何事件 — 这步九成新手会忘!${z.reset}`),console.log(),console.log(` ${z.bold}⑥ 拿凭据${z.reset}`),console.log(` 左侧 "凭证与基础信息" → 顶部 ${z.bold}App ID${z.reset} (${z.dim}cli_...${z.reset}) 和 ${z.bold}App Secret${z.reset}, 待会粘到下面。`),console.log(),console.log(` ${z.bold}⑦ 把机器人拉进群${z.reset}`),console.log(" 想用的飞书群 → 群设置 → 群机器人 → 添加机器人 → 选你的应用。"),console.log(` ${z.yellow}群名要等于 projects_root 下的项目目录名${z.reset} (下一步设, 默认是用户主目录)。`),console.log();let Z="",g="";while(!0){Z=await y("App ID (以 cli_ 开头)",{required:!0}),g=await y("App Secret",{required:!0}),console.log(`${z.dim}测试中... (调 tenant_access_token endpoint)${z.reset}`);let X=await p(Z,g);if(X.ok){console.log(`${z.green}✓ Feishu 凭据测试通过${z.reset}`);break}if(console.log(`${z.red}✗ 测试失败:${z.reset} ${X.error}`),console.log(`${z.dim}最常见原因: app_id / app_secret 抄错, 或应用还没 "发布上线" (步骤 ⑤)。${z.reset}`),console.log(),(await y("重新填? (Y/n)",{default:"y"})).toLowerCase()==="n")console.log(`${z.yellow}已取消, 配置未写盘。${z.reset}`),W.close(),process.exit(1)}P(4,4,"工作目录 + 启动"),console.log("每个 Feishu 群对应 projects_root 下同名的目录。"),console.log();let S=process.platform==="win32"?process.env.USERPROFILE??"C:\\Users\\Default":process.env.HOME??"/root",w=await y("projects_root",{default:S});v(G,{recursive:!0});let K=["# Lodestar config — generated by `lodestar-setup`","# Edit by hand or re-run setup to overwrite.","","[feishu]",`app_id = "${H(Z)}"`,`app_secret = "${H(g)}"`,"","[runtime]",`projects_root = "${H(w)}"`,""];if(Object.keys(J).length>0){K.push("# Env vars injected into the spawned claude CLI subprocess."),K.push("# Used to redirect claude to DeepSeek / GLM / other anthropic-compatible"),K.push("# backends without touching system env vars."),K.push("[claude.env]");for(let[X,L]of Object.entries(J))K.push(`${X} = "${H(L)}"`);K.push("")}D(V,K.join(`
7
+ `)}function H($){return $.replace(/\\/g,"\\\\").replace(/"/g,"\\\"")}function k($){let q=process.env.PATH??"";if(!q)return null;let J=process.platform==="win32"?[`${$}.cmd`,`${$}.bat`,`${$}.exe`,$]:[$];for(let Q of q.split(D)){if(!Q)continue;for(let Z of J){let g=O(Q,Z);if(U(g))return g}}return null}async function l(){let $=process.platform==="win32"?"npm.cmd":"npm";return new Promise((q)=>{let J=f($,["install","-g","@anthropic-ai/claude-code"],{stdio:"inherit",shell:process.platform==="win32"});J.on("exit",(Q)=>q(Q===0)),J.on("error",()=>q(!1))})}function F($){try{if(process.platform==="win32")f(process.env.ComSpec??"cmd.exe",["/c","start",'""',$],{detached:!0,stdio:"ignore",windowsHide:!0}).unref();else if(process.platform==="darwin")f("open",[$],{detached:!0,stdio:"ignore"}).unref();else f("xdg-open",[$],{detached:!0,stdio:"ignore"}).unref()}catch{}}async function p($,q){try{let Q=await(await fetch("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({app_id:$,app_secret:q})})).json();if(Q.tenant_access_token)return{ok:!0};return{ok:!1,error:`飞书拒绝: code=${Q.code} msg=${Q.msg??"(no msg)"}`}}catch(J){return{ok:!1,error:`网络错误: ${J?.message??String(J)}`}}}function s(){try{let $=I(m(import.meta.url)),q=O($,"lodestar.js");if(!U(q))return{error:`找不到 daemon bundle: ${q}`};let J=f(process.execPath,[q],{detached:!0,stdio:"ignore",windowsHide:!0});return J.unref(),{pid:J.pid}}catch($){return{error:$?.message??String($)}}}async function R(){if(!process.stdin.isTTY)console.error(`${z.red}lodestar-setup: stdin 不是 TTY,无法交互式输入。${z.reset}`),console.error("请直接在 cmd / PowerShell / Terminal 里跑,不要 pipe 或重定向 stdin。"),process.exit(1);if(U(V)){if(console.log(`${z.yellow}发现已有配置: ${V}${z.reset}`),(await y("覆盖? (y/N)",{default:"n"})).toLowerCase()!=="y"){console.log("已取消"),W.close();return}}r("Lodestar 安装向导"),console.log("Lodestar 把 Feishu (飞书) 群聊接到 Claude Code。"),console.log("每个群对应一个项目目录, Claude 在那里跑、能读写文件。"),console.log(),console.log("本向导依次做 4 件事:"),console.log(` ${z.dim}1) 确保 claude CLI 已装好${z.reset}`),console.log(` ${z.dim}2) 选 LLM 后端 (订阅 / API key / DeepSeek)${z.reset}`),console.log(` ${z.dim}3) Feishu 自建应用 (含权限 / 事件 / 发版 + 凭据测试)${z.reset}`),console.log(` ${z.dim}4) 工作目录, 自动启动 daemon${z.reset}`),console.log(),await W.question(`${z.dim}按 Enter 开始 (Ctrl+C 退出)...${z.reset}`),P(1,4,"准备 Claude Code");let $=k("claude");if($)console.log(`${z.green}✓ claude CLI 已就位${z.reset}: ${z.dim}${$}${z.reset}`);else{if(console.log(`${z.yellow}未在 PATH 找到 claude CLI, 自动安装...${z.reset}`),console.log(`${z.dim}运行: npm install -g @anthropic-ai/claude-code${z.reset}`),console.log(),!await l())console.error(`
8
+ ${z.red}安装失败。${z.reset}`),console.error("请手动运行后再开向导:"),console.error(` ${z.cyan}npm install -g @anthropic-ai/claude-code${z.reset}`),console.error(` ${z.cyan}lodestar-setup${z.reset}`),W.close(),process.exit(1);$=k("claude"),console.log(`${z.green}✓ 安装完成${z.reset}: ${z.dim}${$??"(应该装好了, 但 PATH 找不到 — 重开终端再试)"}${z.reset}`)}P(2,4,"LLM 后端"),console.log("claude CLI 默认走 Anthropic 官方, 需要 claude.ai 订阅或 API key。"),console.log("国内访问 anthropic.com 不一定通, 也可以让 claude 走 DeepSeek 后端。"),console.log();let q=await c("你的 claude 怎么走?",[{label:"已经配过 (订阅 / API key / 已设环境变量), 直接用",value:"existing"},{label:`${z.bold}用 DeepSeek${z.reset}${z.dim} (国内可用, 人民币计费, 推荐)${z.reset}`,value:"deepseek"}]),J={};if(q==="deepseek"){console.log(),console.log("打开浏览器拿 DeepSeek API key:");let X="https://platform.deepseek.com/api_keys";console.log(` ${z.cyan}${X}${z.reset}`),F(X),console.log(`${z.dim}(如果浏览器没自动开, 复制上面 URL 粘到浏览器)${z.reset}`),console.log();let L=await y("DeepSeek API key (以 sk- 开头)",{required:!0});J.ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic",J.ANTHROPIC_AUTH_TOKEN=L,J.ANTHROPIC_MODEL="deepseek-v4-pro",J.ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-pro",J.ANTHROPIC_DEFAULT_SONNET_MODEL="deepseek-v4-pro",J.ANTHROPIC_DEFAULT_HAIKU_MODEL="deepseek-v4-flash",J.CLAUDE_CODE_SUBAGENT_MODEL="deepseek-v4-flash",J.CLAUDE_CODE_EFFORT_LEVEL="max",console.log(),console.log(`${z.green}✓ DeepSeek 配置已记录${z.reset} (写到 [claude.env] 节, daemon 拉起 claude 时自动注入)`)}else console.log(`${z.dim}OK, 跳过 LLM 后端配置 — daemon 启动时会继承当前环境 + claude 自带 auth。${z.reset}`);P(3,4,"Feishu 自建应用");let Q="https://open.feishu.cn/app";console.log("打开飞书开放平台 (浏览器):"),console.log(` ${z.cyan}${Q}${z.reset}`),F(Q),console.log(`${z.dim}(如果浏览器没自动开, 复制上面 URL 粘到浏览器)${z.reset}`),console.log(),console.log(`${z.bold}详细操作步骤:${z.reset}`),console.log(),console.log(` ${z.bold}① 创建应用${z.reset}`),console.log(` 点 "创建企业自建应用", 填名字 (如 ${z.dim}Lodestar${z.reset}), logo 随意。`),console.log(),console.log(` ${z.bold}② 添加机器人能力${z.reset}`),console.log(` 左侧菜单 "${z.cyan}添加应用能力${z.reset}" → 找到 "机器人" → 点 "${z.bold}添加${z.reset}" 按钮。`),console.log(),console.log(` ${z.bold}③ 申请权限 (左侧 "${z.cyan}权限管理${z.reset}" → "${z.bold}开通权限${z.reset}")${z.reset}`),console.log(` ${z.yellow}缺一个都会让 daemon 启动后默默丢消息, 一定要全开。${z.reset}`),console.log(` ${z.dim}消息类:${z.reset}`),console.log(` • ${z.bold}im:message:send_as_bot${z.reset} ${z.dim}# 以机器人身份发消息${z.reset}`),console.log(` • ${z.bold}im:message${z.reset} ${z.dim}# 接收/操作消息 (核心)${z.reset}`),console.log(` • ${z.bold}im:chat${z.reset} ${z.dim}# 读/写群信息 (匹配群名 ↔ 项目目录)${z.reset}`),console.log(` • ${z.bold}im:resource${z.reset} ${z.dim}# 上传/下载附件 (图文双向)${z.reset}`),console.log(` • ${z.bold}im:message.urgent${z.reset} ${z.dim}# 加急推送 (锁屏通知 / Ask)${z.reset}`),console.log(` • ${z.bold}im:message.group_msg${z.reset} ${z.red}# 敏感: 接收群里所有消息${z.reset}`),console.log(` ${z.dim}└ 关键: 没它机器人只收 @ 自己的消息, 拿不到群里其他对话, 一定要开${z.reset}`),console.log(` ${z.dim}└ 敏感权限要走审批: 申请时填用途, 个人开发者通常秒过${z.reset}`),console.log(` • ${z.bold}im:message.group_at_msg:readonly${z.reset} ${z.dim}# 读 @ 机器人消息 (兜底)${z.reset}`),console.log(` ${z.dim}卡片类 (Card Kit):${z.reset}`),console.log(` • ${z.bold}cardkit:card:read${z.reset} ${z.dim}# 读卡片状态${z.reset}`),console.log(` • ${z.bold}cardkit:card:write${z.reset} ${z.dim}# 创建/更新卡片 (流式渲染核心)${z.reset}`),console.log(),console.log(` ${z.bold}④ 订阅事件 (左侧 "${z.cyan}事件与回调${z.reset}", 拆两个子页:)${z.reset}`),console.log(` ${z.dim}a)${z.reset} ${z.bold}事件配置${z.reset} 页:`),console.log(` ${z.yellow}• "订阅方式" → 选 "长连接" → 点保存${z.reset}`),console.log(` • 添加事件: ${z.bold}im.message.receive_v1${z.reset} ${z.dim}# 收群消息${z.reset}`),console.log(` ${z.dim}b)${z.reset} ${z.bold}回调配置${z.reset} 页:`),console.log(` ${z.yellow}• "订阅方式" → 选 "长连接" → 点保存${z.reset}`),console.log(` • 添加事件: ${z.bold}card.action.trigger${z.reset} ${z.dim}# 卡片按钮点击回调${z.reset}`),console.log(),console.log(` ${z.bold}⑤ 发布版本${z.reset}`),console.log(` 页面顶部 "${z.bold}创建版本${z.reset}" → 滚到底点 "${z.bold}保存${z.reset}" → 弹框点 "${z.bold}发布${z.reset}"。`),console.log(` ${z.yellow}没发版的应用收不到任何事件 — 这步九成新手会忘!${z.reset}`),console.log(),console.log(` ${z.bold}⑥ 拿凭据${z.reset}`),console.log(` 左侧 "凭证与基础信息" → 顶部 ${z.bold}App ID${z.reset} (${z.dim}cli_...${z.reset}) 和 ${z.bold}App Secret${z.reset}, 待会粘到下面。`),console.log(),console.log(` ${z.bold}⑦ 把机器人拉进群${z.reset}`),console.log(" 想用的飞书群 → 群设置 → 群机器人 → 添加机器人 → 选你的应用。"),console.log(` ${z.yellow}群名要等于 projects_root 下的项目目录名${z.reset} (下一步设, 默认是用户主目录)。`),console.log();let Z="",g="";while(!0){Z=await y("App ID (以 cli_ 开头)",{required:!0}),g=await y("App Secret",{required:!0}),console.log(`${z.dim}测试中... (调 tenant_access_token endpoint)${z.reset}`);let X=await p(Z,g);if(X.ok){console.log(`${z.green}✓ Feishu 凭据测试通过${z.reset}`);break}if(console.log(`${z.red}✗ 测试失败:${z.reset} ${X.error}`),console.log(`${z.dim}最常见原因: app_id / app_secret 抄错, 或应用还没 "发布上线" (步骤 ⑤)。${z.reset}`),console.log(),(await y("重新填? (Y/n)",{default:"y"})).toLowerCase()==="n")console.log(`${z.yellow}已取消, 配置未写盘。${z.reset}`),W.close(),process.exit(1)}P(4,4,"工作目录 + 启动"),console.log("每个 Feishu 群对应 projects_root 下同名的目录。"),console.log();let S=process.platform==="win32"?process.env.USERPROFILE??"C:\\Users\\Default":process.env.HOME??"/root",w=await y("projects_root",{default:S});v(G,{recursive:!0});let K=["# Lodestar config — generated by `lodestar-setup`","# Edit by hand or re-run setup to overwrite.","","[feishu]",`app_id = "${H(Z)}"`,`app_secret = "${H(g)}"`,"","[runtime]",`projects_root = "${H(w)}"`,""];if(Object.keys(J).length>0){K.push("# Env vars injected into the spawned claude CLI subprocess."),K.push("# Used to redirect claude to DeepSeek / GLM / other anthropic-compatible"),K.push("# backends without touching system env vars."),K.push("[claude.env]");for(let[X,L]of Object.entries(J))K.push(`${X} = "${H(L)}"`);K.push("")}h(V,K.join(`
9
9
  `),{mode:384}),console.log(`
10
10
  ${z.green}${z.bold}✓ 配置已写入${z.reset}`),console.log(` ${z.cyan}${V}${z.reset}`),console.log(`
11
- ${z.bold}启动 daemon...${z.reset}`);let N=s(),T=process.platform==="win32"?"\\":"/",A=process.platform==="win32"?`${process.env.LOCALAPPDATA??"%LOCALAPPDATA%"}\\Lodestar\\daemon.log`:`${process.env.HOME??"~"}/.local/share/lodestar/daemon.log`;if(N.pid)console.log(`${z.green}✓ daemon 已在后台启动${z.reset} (pid ${N.pid})`),console.log(),console.log(`${z.bold}最后一步: 在 Feishu 验证${z.reset}`),console.log(" ① 把机器人拉进任意飞书群"),console.log(` ② 群名 = ${z.cyan}${w}${T}<群名>${z.reset} 下的目录名 (新群第一条消息会自动建)`),console.log(" ③ 在群里发任意一条消息, Claude 上线"),console.log(),console.log("日志:"),console.log(` ${z.cyan}${A}${z.reset}`),console.log(),console.log(`${z.dim}若长期跑后台, 参考 README "7×24 守护" 一节配 systemd / Task Scheduler。${z.reset}`);else console.log(`${z.yellow}启动失败: ${N.error}${z.reset}`),console.log(`手动运行: ${z.cyan}lodestar-daemon${z.reset}`);console.log(),W.close()}O().catch(($)=>{console.error(`
11
+ ${z.bold}启动 daemon...${z.reset}`);let N=s(),T=process.platform==="win32"?"\\":"/",E=process.platform==="win32"?`${process.env.LOCALAPPDATA??"%LOCALAPPDATA%"}\\Lodestar\\daemon.log`:`${process.env.HOME??"~"}/.local/share/lodestar/daemon.log`;if(N.pid)console.log(`${z.green}✓ daemon 已在后台启动${z.reset} (pid ${N.pid})`),console.log(),console.log(`${z.bold}最后一步: 在 Feishu 验证${z.reset}`),console.log(" ① 把机器人拉进任意飞书群"),console.log(` ② 群名 = ${z.cyan}${w}${T}<群名>${z.reset} 下的目录名 (新群第一条消息会自动建)`),console.log(" ③ 在群里发任意一条消息, Claude 上线"),console.log(),console.log("日志:"),console.log(` ${z.cyan}${E}${z.reset}`),console.log(),console.log(`${z.dim}若长期跑后台, 参考 README "7×24 守护" 一节配 systemd / Task Scheduler。${z.reset}`);else console.log(`${z.yellow}启动失败: ${N.error}${z.reset}`),console.log(`手动运行: ${z.cyan}lodestar-daemon${z.reset}`);console.log(),W.close()}R().catch(($)=>{console.error(`
12
12
  lodestar-setup: ${$?.message??$}`),process.exit(1)});