@leviyuan/lodestar 0.14.9 → 0.15.0

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
@@ -16,9 +16,9 @@ AI 不是帮手,是倍率。它放大的不是体力,是你 —— 你的直觉
16
16
 
17
17
  ### 🧠 双后端:Claude Code + Codex
18
18
 
19
- 夜航星同时接入 Claude Code 和 Codex 两个 agent 后端,**默认 Claude Code**。群里发 `model` 可在 Claude·GLM-5.2 Codex·GPT-5.5 之间一键切换,选择按群持久化。
19
+ 夜航星同时接入 Claude Code 和 Codex 两个 agent 后端,**默认 Claude Code**。群里发 `model` 会按“账号 动态模型 effort”选择,结果按群持久化;可用项以当前账号和上游实时返回为准。
20
20
 
21
- 强烈推荐 Claude Code 搭配 **GLM-5.2** —— 开放 1M token 上下文窗口,长会话不易丢前文。订阅了 GLM Coding Plan,`hi` 控制台会直接展示套餐档位、5 小时滚动窗口与月度用量;每条回复的 footer 也会带上当前 5h 窗口的已用百分比(`5h·N%`),额度消耗随时可见。
21
+ 使用 GLM Coding Plan 时,夜航星会从端点动态获取模型,并根据真实 turn 观测上下文窗口。`hi` 控制台会展示套餐档位、滚动窗口与月度用量;每条回复的 footer 也会带上当前窗口用量。
22
22
 
23
23
  **1. 装包**
24
24
 
@@ -48,7 +48,7 @@ lodestar-setup
48
48
 
49
49
  **3. 拉机器人进群**
50
50
 
51
- 群名 = `projects_root` 下的目录名(没建会自动建)。发条消息,默认由 Claude·GLM-5.2 接管(群里发 `model` 可切到 Codex·GPT-5.5)
51
+ 群名 = `projects_root` 下的目录名(没建会自动建)。发条消息后由当前默认 Claude source 接管;群里发 `model` 可选择其他已配置账号、模型与 effort
52
52
 
53
53
  群里发这些**裸词**(不要斜杠,大小写不敏感)可以控 daemon:
54
54
 
@@ -56,11 +56,11 @@ lodestar-setup
56
56
  | --- | --- |
57
57
  | `hi` | 未运行时同一张卡动态启动并收束为控制台;运行中弹控制台 |
58
58
  | `stop` / `st` | 软打断当前 turn,子进程保活,排队消息打 ❌ |
59
- | `kill` / `kl` | 用状态卡展示关闭 Codex 进程,`threadId` 落盘 |
60
- | `restart` / `rs` | 进行中:打断 + 放弃后台进程 + 恢复;空闲:列出项目最近 24h 会话(不足 10 条补更早),选一个在本群接续恢复 |
59
+ | `kill` / `kl` | 用状态卡展示关闭当前 agent 进程,resume id 落盘 |
60
+ | `restart` / `rs` | 进程存活:打断 + 放弃后台任务 + 恢复当前会话;进程已停:列出同一 cwd 的历史会话,选一个在本群创建独立分支 |
61
61
  | `clear` / `cl` | 用状态卡展示杀进程并开新 thread(等价 `/clear`)|
62
62
  | `compact` / `cm` | 主动触发当前 thread 的上下文压缩,完成后状态卡收束 |
63
- | `model` / `md` | 展示固定两项面板(Codex·GPT-5.5 / Claude·GLM-5.2),一键生效,按群持久化 |
63
+ | `model` / `md` | 展示账号→动态模型→effort 面板,按群持久化 |
64
64
  | `task` | 打开项目任务清单面板,启用飞书任务清单自动化(预览版) |
65
65
 
66
66
  **并发 worktree 群**
@@ -74,18 +74,22 @@ lodestar-setup
74
74
 
75
75
  `删` 会先确认对应 worktree 群没有正在运行的 Codex session,再检查 worktree 没有未提交变更,然后解散群并删除 worktree 目录;分支保留,合并和分支清理由 agent 处理。
76
76
 
77
- **临时会话 / 分叉 / 回滚**(Claude 后端)
77
+ **临时会话 / 分叉 / 回退**(Codex 与 Claude)
78
78
 
79
- 在同一个项目目录里多开 Claude 会话,加上语义化的分叉/回滚——基于 Claude 原生的会话 fork(派生新会话 id,原对话不动):
79
+ 在同一个工作目录里多开会话,并基于后端原生 fork 做语义化分叉/回退。Claude 使用 `forkSession + resumeSessionAt`,Codex 使用 app-server `thread/fork + lastTurnId`;两者都会派生新会话 id,源对话不动:
80
80
 
81
81
  | 指令 | 行为 |
82
82
  | --- | --- |
83
- | `btw` | 开一个临时群 `<project>*MMDD-HHMM`(同一个项目目录,自动启动一个干净 Claude) |
84
- | `bye` | 解散当前临时群(只能在 `*` 开头的临时群里用) |
85
- | `fk` / `fork` | 列出当前会话的每条用户输入(倒序),选一条从这条**之前**开临时群分叉,原会话不动 |
86
- | `bk` / `back` | 立刻终止当前 + 列用户输入,选一条 当前会话回退到这条**之前**,并发一张回滚段 Write 记录卡(代码块,可复制后编辑重发) |
83
+ | `btw` | 开一个临时群 `<session>*MMDD-HHMM`,继承当前账号/模型/cwd 并启动干净会话 |
84
+ | `bye` | 停止并解散当前临时群(只能在带 `*MMDD-HHMM` 后缀的临时群里用) |
85
+ | `fk` / `fork` | 列出当前会话的每条用户输入(倒序),选一条从这条**之前**开临时群分叉,原会话不动 |
86
+ | `bk` / `back` | 列用户输入,选一条后才停止当前进程本群改接到这条**之前**的新分支,并附观察到的文件变更记录 |
87
87
 
88
- 分叉/回滚点以「用户输入」为分界:选第 N 条 = 回到这条发出之前的状态。`rs` 空闲模式的历史列表数据源是 Claude 自己的会话存档(`~/.claude/projects/<本项目目录编码>/*.jsonl`),同一工作目录的全部会话都列出来,worktree(不同目录)不会混进来。
88
+ 分叉/回退点以「用户输入」为分界:选第 N 条 = 回到这条发出之前的对话状态,选中的输入本身不包含。临时群和源群共享同一个 cwd;`fk`/`bk` **只改变对话历史,不复制或回滚磁盘文件**,Shell/MCP 等外部副作用也不会被撤销。需要文件隔离时使用 `wt`。
89
+
90
+ `rs` 的历史发现由后端自己负责:Claude 读取同 cwd 的 transcript,Codex 调用 app-server `thread/list(cwd=...)`;不会扫描/复制 Codex rollout,也不会维护第二套会话索引。选择历史会话时创建独立 fork,避免两个群共同写同一个 thread。
91
+
92
+ 选择后原 `rs` 卡会原位更新,保留所选历史的后端、时间、摘要和源会话,并显示当前群的新分支状态。Codex 会直接显示新 thread;Claude 在首条输入前显示“已准备”,该 fork intent 会持久化,daemon 重启或进程退出后仍会继续同一分支。
89
93
 
90
94
  ---
91
95
 
@@ -157,7 +161,7 @@ bin = "~/.local/bin/reclaude" # 换 claude 包装器;路径错直接报错,不
157
161
  ---
158
162
 
159
163
  > [!TIP]
160
- > 想 7×24 长跑,用 `systemd --user`(Linux/macOS)或 Windows 任务计划程序拉起 `lodestar-daemon`。重启后上次活跃的 sessions 会并发自动 `--resume` 接回。
164
+ > 想 7×24 长跑,Linux 使用 `systemd --user`,macOS 使用 `launchd`,Windows 使用任务计划程序拉起 `lodestar-daemon`。重启后上次活跃的 sessions 会并发自动恢复。
161
165
 
162
166
  ---
163
167
 
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as I,spawn as b}from"node:child_process";import{existsSync as k,mkdirSync as _,readFileSync as h,writeFileSync as u}from"node:fs";import{createInterface as m}from"node:readline/promises";import{delimiter as c,dirname as p,join as F}from"node:path";import{homedir as l}from"node:os";import{fileURLToPath as r}from"node:url";import{homedir as j}from"node:os";import{join as Z}from"node:path";var G=j(),T=process.platform==="win32";function v($,q,J){if($)return $;if(q)return Z(q,"lodestar");return J}function g(){if(T)return Z(process.env.APPDATA??Z(G,"AppData","Roaming"),"Lodestar");return Z(G,".config","lodestar")}function D(){if(T)return Z(process.env.LOCALAPPDATA??Z(G,"AppData","Local"),"Lodestar");return Z(G,".local","share","lodestar")}var w=v(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,g()),W=v(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,D()),y=process.env.LODESTAR_CONFIG??Z(w,"config.toml"),$z=Z(W,"daemon.pid"),qz=Z(W,"daemon.log"),Jz=Z(W,"session-chat-map.json"),Qz=Z(W,"session-resume-map.json"),Yz=Z(W,"session-turns-map.json"),Zz=Z(W,"session-model-map.json"),Xz=Z(W,"tasklist-map.json"),Wz=Z(W,"context-window-cache.json"),Vz=Z(W,"notify-callbacks.json"),Mz=Z(W,"alive-on-shutdown.json"),Hz=Z(W,"inbox"),Uz=Z(W,"debug.sock"),Nz=Z(W,"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"},f=m({input:process.stdin,output:process.stdout});async function L($,q={}){while(!0){let J=q.default?`${z.dim} [${q.default}]${z.reset}`:"",Q=(await f.question(`${z.cyan}? ${z.reset}${$}${J}
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}}function a($){let q="═".repeat(58);console.log(`
2
+ import{execSync as t,spawn as G}from"node:child_process";import{existsSync as B,mkdirSync as I,readFileSync as C}from"node:fs";import{createInterface as e}from"node:readline/promises";import{delimiter as zz,dirname as $z,join as F}from"node:path";import{homedir as qz}from"node:os";import{fileURLToPath as Jz}from"node:url";import{homedir as m}from"node:os";import{join as Y}from"node:path";var w=m(),u=process.platform==="win32";function g($,q,Q){if($)return $;if(q)return Y(q,"lodestar");return Q}function p(){if(u)return Y(process.env.APPDATA??Y(w,"AppData","Roaming"),"Lodestar");return Y(w,".config","lodestar")}function c(){if(u)return Y(process.env.LOCALAPPDATA??Y(w,"AppData","Local"),"Lodestar");return Y(w,".local","share","lodestar")}var x=g(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,p()),H=g(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,c()),O=process.env.LODESTAR_CONFIG??Y(x,"config.toml"),Uz=Y(H,"daemon.pid"),bz=Y(H,"daemon.log"),Lz=Y(H,"session-chat-map.json"),Gz=Y(H,"session-resume-map.json"),kz=Y(H,"session-turns-map.json"),Oz=Y(H,"session-model-map.json"),wz=Y(H,"temp-session-leases.json"),Ez=Y(H,"tasklist-map.json"),Rz=Y(H,"context-window-cache.json"),Bz=Y(H,"notify-callbacks.json"),Fz=Y(H,"alive-on-shutdown.json"),xz=Y(H,"inbox"),Tz=Y(H,"debug.sock"),vz=Y(H,"debug-context.json");import{chmodSync as l,closeSync as T,fsyncSync as P,mkdirSync as r,openSync as _,renameSync as d,unlinkSync as s,writeFileSync as a}from"node:fs";import{randomUUID as o}from"node:crypto";import{dirname as S}from"node:path";var f=384,i=448;function n($){let q=S($);r(q,{recursive:!0,mode:i})}function v($,q){n($);let Q=`${$}.${process.pid}.${o()}.tmp`,J=null;try{J=_(Q,"wx",f),a(J,q),P(J),T(J),J=null,d(Q,$);try{l($,f)}catch{}if(process.platform!=="win32"){let X=null;try{X=_(S($),"r"),P(X)}catch{}finally{if(X!==null)try{T(X)}catch{}}}}catch(X){if(J!==null)try{T(J)}catch{}try{s(Q)}catch{}throw X}}var z={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",red:"\x1B[31m"},L=e({input:process.stdin,output:process.stdout});async function b($,q={}){while(!0){let Q=q.default?`${z.dim} [${q.default}]${z.reset}`:"",J=(await L.question(`${z.cyan}? ${z.reset}${$}${Q}
3
+ ${z.green}>${z.reset} `)).trim();if(!J&&q.default!==void 0)return q.default;if(!J&&q.required){console.log(`${z.red}必填,请重新输入${z.reset}`);continue}return J}}function Qz($){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
- `)}function E($,q,J){console.log(`
6
- ${z.bold}${z.yellow}[${$}/${q}] ${J}${z.reset}
7
- `)}function R($){return $.replace(/\\/g,"\\\\").replace(/"/g,"\\\"")}function O($){let q=process.env.PATH??"";if(!q)return null;let J=process.platform==="win32"?[`${$}.cmd`,`${$}.bat`,`${$}.exe`,$]:[$];for(let Q of q.split(c)){if(!Q)continue;for(let V of J){let M=F(Q,V);if(k(M))return M}}return null}function B($){let q=process.platform==="win32"?"npm.cmd":"npm";return new Promise((J)=>{let Q=b(q,["install","-g",`${$}@latest`],{stdio:"inherit",shell:process.platform==="win32"});Q.on("exit",(V)=>J(V===0)),Q.on("error",()=>J(!1))})}function x($){try{let q=I(`"${$}" login status 2>&1`,{timeout:1e4}).toString();return/Logged in using ChatGPT/i.test(q)}catch{return!1}}async function d($){return new Promise((q)=>{let J=b($,["login"],{stdio:"inherit",shell:process.platform==="win32"});J.on("exit",(Q)=>q(Q===0)),J.on("error",()=>q(!1))})}function o(){return process.env.CLAUDE_CONFIG_DIR||F(l(),".claude")}async function s($){try{let q=await fetch("https://open.bigmodel.cn/api/anthropic/v1/models",{headers:{Authorization:`Bearer ${$}`},signal:AbortSignal.timeout(1e4)});if(!q.ok)return null;let J=await q.json(),Q=(Array.isArray(J?.data)?J.data:[]).map((X)=>typeof X?.display_name==="string"&&X.display_name?X.display_name:String(X?.id??"")).filter(Boolean);if(!Q.length)return null;let V=(X)=>{let Y=X.match(/(\d+(?:\.\d+)?)/);return Y?Number(Y[1]):-1},M=(X)=>X.replace(/-Turbo|-Air$/i,""),U=[...Q.filter((X)=>!/-Turbo|-Air/i.test(X))].sort((X,Y)=>V(Y)-V(X)),H=U[0],N=U[U.length-1],K={};if(H)K.ANTHROPIC_DEFAULT_OPUS_MODEL=`${H}[1m]`,K.ANTHROPIC_DEFAULT_SONNET_MODEL=`${H}[1m]`;if(N&&M(N)!==M(H))K.ANTHROPIC_DEFAULT_HAIKU_MODEL=N;return Object.keys(K).length?K:null}catch{return null}}async function i($){try{let q=o();_(q,{recursive:!0});let J=F(q,"settings.json"),Q={};if(k(J)){let U=h(J,"utf8");try{let H=JSON.parse(U);if(!H||typeof H!=="object"||Array.isArray(H))return{error:`${J} 内容不是 JSON 对象, 拒绝覆盖, 请手动检查`};Q=H}catch{return{error:`${J} 解析失败 (JSON 语法错), 拒绝覆盖, 请手动修复后重跑`}}}let V=Q.env&&typeof Q.env==="object"&&!Array.isArray(Q.env)?Q.env:{},M={ANTHROPIC_AUTH_TOKEN:$,ANTHROPIC_BASE_URL:"https://open.bigmodel.cn/api/anthropic",API_TIMEOUT_MS:"3000000",CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:"1"},P=await s($);if(P)Object.assign(M,P);return Q.env={...V,...M},u(J,JSON.stringify(Q,null,2)+`
8
- `,{mode:384}),{path:J}}catch(q){return{error:q?.message??String(q)}}}function t($){try{if(process.platform==="win32")b(process.env.ComSpec??"cmd.exe",["/c","start",'""',$],{detached:!0,stdio:"ignore",windowsHide:!0}).unref();else if(process.platform==="darwin")b("open",[$],{detached:!0,stdio:"ignore"}).unref();else b("xdg-open",[$],{detached:!0,stdio:"ignore"}).unref()}catch{}}async function n($,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 e(){try{let $=p(r(import.meta.url)),q=F($,"lodestar.js");if(!k(q))return{error:`找不到 daemon bundle: ${q}`};let J=b(process.execPath,[q],{detached:!0,stdio:"ignore",windowsHide:!0});return J.unref(),{pid:J.pid}}catch($){return{error:$?.message??String($)}}}async function A(){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(k(y)){if(console.log(`${z.yellow}发现已有配置: ${y}${z.reset}`),(await L("覆盖? (y/N)",{default:"n"})).toLowerCase()!=="y"){console.log("已取消"),f.close();return}}a("Lodestar 安装向导"),console.log("Lodestar 把 Feishu (飞书) 群聊接到 AI agent。"),console.log("默认后端是 Claude Code; Codex 是可选第二后端, 群里发 model 一键切换。"),console.log("每个群对应一个项目目录, agent 在那里跑、能读写文件。"),console.log(),console.log(`${z.yellow}⚠ Claude 官方限制:${z.reset}`),console.log(` ${z.bold}Claude 订阅 (Pro/Max OAuth 登录) 不支持本项目${z.reset},`),console.log(" 必须走 API 方式 —— 推荐 GLM Coding Plan, 或自备 Anthropic API key。"),console.log(),console.log("本向导依次做 4 件事:"),console.log(` ${z.dim}1) 确保 Claude Code CLI 已装好${z.reset}`),console.log(` ${z.dim}2) GLM Coding Plan API key (推荐, 可选) + Codex (可选)${z.reset}`),console.log(` ${z.dim}3) Feishu 自建应用 (含权限 / 事件 / 发版 + 凭据测试)${z.reset}`),console.log(` ${z.dim}4) 工作目录, 自动启动 daemon${z.reset}`),console.log(),await f.question(`${z.dim}按 Enter 开始 (Ctrl+C 退出)...${z.reset}`),E(1,4,"准备 Claude Code CLI");let $=O("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@latest${z.reset}`),console.log(),!await B("@anthropic-ai/claude-code"))console.error(`
9
- ${z.red}安装失败。${z.reset}`),console.error("请手动运行后再开向导:"),console.error(` ${z.cyan}npm install -g @anthropic-ai/claude-code@latest${z.reset}`),console.error(` ${z.cyan}lodestar-setup${z.reset}`),f.close(),process.exit(1);$=O("claude"),console.log(`${z.green}✓ 安装完成${z.reset}: ${z.dim}${$??"(应该装好了, 但 PATH 找不到 — 重开终端再试)"}${z.reset}`)}console.log(),console.log(`${z.dim}下一步可选配 GLM Coding Plan 自动写入路由; 不配则用本机 Claude Code 现有配置。${z.reset}`),console.log(`${z.dim}记住: 别用 \`claude\` 走订阅 OAuth 登录 —— 订阅不支持本项目, 要用 API key。${z.reset}`),E(2,4,"GLM Coding Plan (推荐, 可选)"),console.log("GLM Coding Plan 给 Claude Code 接 GLM 系列模型 (支持 1M token 上下文, 中文友好)。"),console.log("订阅后在智谱开放平台拿一个 API key, 粘到下面 —— 向导自动写进 ~/.claude/settings.json。"),console.log(` ${z.dim}拿 key: https://open.bigmodel.cn → 控制台 → API Keys${z.reset}`),console.log(` ${z.dim}不给也行: 以本机 Claude Code 现有配置启动 (确保是 API key 方式, 非订阅)。${z.reset}`),console.log();let q=await L("GLM API key (直接回车跳过)",{});if(q){let Y=await i(q);if("path"in Y)console.log(`${z.green}✓ GLM 路由已写入${z.reset}: ${z.dim}${Y.path}${z.reset}`),console.log(`${z.dim}模型 slots 按端点当前列表自动选取(最新一代 opus/sonnet, 1M ctx)${z.reset}`);else console.log(`${z.red}✗ 写入失败:${z.reset} ${Y.error}`),console.log(`${z.dim}跳过 GLM 配置, 以本机 Claude Code 现有配置启动。${z.reset}`)}else console.log(`${z.dim}已跳过 GLM, 以本机 Claude Code 现有配置启动。${z.reset}`);if(console.log(),console.log(`${z.bold}Codex (可选第二后端)${z.reset}`),console.log(` ${z.dim}想用 Codex 的话, 登录 ChatGPT 订阅即可, 模型/effort 走 ~/.codex/config.toml, 群里发 model 切换。${z.reset}`),(await L("现在顺便配置 Codex 后端吗?",{default:"n"})).toLowerCase()==="y"){let Y=O("codex");if(!Y){if(console.log(`${z.dim}未找到 codex CLI, 安装 @openai/codex...${z.reset}`),await B("@openai/codex"))Y=O("codex")}if(Y&&x(Y))console.log(`${z.green}✓ Codex 已登录 ChatGPT${z.reset}`);else if(Y)if(console.log(`${z.dim}启动 \`codex login\`...${z.reset}`),await d(Y)&&x(Y))console.log(`${z.green}✓ Codex 已登录 ChatGPT${z.reset}`);else console.log(`${z.yellow}Codex 登录未完成 — 不影响默认 Claude 后端; 需要时随时跑 codex login。${z.reset}`);else console.log(`${z.yellow}Codex 安装失败 — 不影响默认 Claude 后端; 需要时手动 npm i -g @openai/codex。${z.reset}`)}else console.log(`${z.dim}已跳过 Codex (需要时随时跑 codex login, 群里发 model 切)。${z.reset}`);E(3,4,"Feishu 自建应用");let Q="https://open.feishu.cn/app";console.log("打开飞书开放平台 (浏览器):"),console.log(` ${z.cyan}${Q}${z.reset}`),t(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:chat:create${z.reset} ${z.dim}# wt: 自动创建 worktree 群${z.reset}`),console.log(` • ${z.bold}im:chat:delete${z.reset} ${z.dim}# wt: 解散 worktree 群${z.reset}`),console.log(` • ${z.bold}im:chat.members:read${z.reset} ${z.dim}# wt: 判断发起人是否已在群内${z.reset}`),console.log(` • ${z.bold}im:chat.members:write_only${z.reset} ${z.dim}# wt: 把发起人拉进已有 worktree 群${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.dim}# 敏感: 接收群里所有消息${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 V="",M="";while(!0){V=await L("App ID (以 cli_ 开头)",{required:!0}),M=await L("App Secret",{required:!0}),console.log(`${z.dim}测试中... (调 tenant_access_token endpoint)${z.reset}`);let Y=await n(V,M);if(Y.ok){console.log(`${z.green}✓ Feishu 凭据测试通过${z.reset}`);break}if(console.log(`${z.red}✗ 测试失败:${z.reset} ${Y.error}`),console.log(`${z.dim}最常见原因: app_id / app_secret 抄错, 或应用还没 "发布上线" (步骤 ⑤)。${z.reset}`),console.log(),(await L("重新填? (Y/n)",{default:"y"})).toLowerCase()==="n")console.log(`${z.yellow}已取消, 配置未写盘。${z.reset}`),f.close(),process.exit(1)}E(4,4,"工作目录 + 启动"),console.log("每个 Feishu 群对应 projects_root 下同名的目录。"),console.log();let P=process.platform==="win32"?process.env.USERPROFILE??"C:\\Users\\Default":process.env.HOME??"/root",U=await L("projects_root",{default:P});_(w,{recursive:!0});let H=["# Lodestar config — generated by `lodestar-setup`","# Edit by hand or re-run setup to overwrite.","","[feishu]",`app_id = "${R(V)}"`,`app_secret = "${R(M)}"`,"","[runtime]",`projects_root = "${R(U)}"`,""];u(y,H.join(`
10
- `),{mode:384}),console.log(`
11
- ${z.green}${z.bold}✓ 配置已写入${z.reset}`),console.log(` ${z.cyan}${y}${z.reset}`),console.log(`
12
- ${z.bold}启动 daemon...${z.reset}`);let N=e(),K=process.platform==="win32"?"\\":"/",X=process.platform==="win32"?`${process.env.LOCALAPPDATA??"%LOCALAPPDATA%"}\\Lodestar\\daemon-YYYY-MM-DD.log`:`${process.env.HOME??"~"}/.local/share/lodestar/daemon-YYYY-MM-DD.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}${U}${K}<群名>${z.reset} 下的目录名 (新群第一条消息会自动建)`),console.log(" ③ 在群里发任意一条消息, 默认由 Claude 接管"),console.log(` ${z.dim}(群里发 model 可切到 Codex·GPT-5.5)${z.reset}`),console.log(),console.log("日志 (按日滚动, 保留近 7 天):"),console.log(` ${z.cyan}${X}${z.reset}`),console.log(),console.log(`${z.dim}若长期跑后台, 参考 README "7×24 守护" 一节配 systemd / Windows 后台托管。${z.reset}`);else console.log(`${z.yellow}启动失败: ${N.error}${z.reset}`),console.log(`手动运行: ${z.cyan}lodestar-daemon${z.reset}`);console.log(),f.close()}A().catch(($)=>{console.error(`
5
+ `)}function E($,q,Q){console.log(`
6
+ ${z.bold}${z.yellow}[${$}/${q}] ${Q}${z.reset}
7
+ `)}function y($){return $.replace(/\\/g,"\\\\").replace(/"/g,"\\\"")}function R($){let q=process.env.PATH??"";if(!q)return null;let Q=process.platform==="win32"?[`${$}.cmd`,`${$}.bat`,`${$}.exe`,$]:[$];for(let J of q.split(zz)){if(!J)continue;for(let X of Q){let N=F(J,X);if(B(N))return N}}return null}function A($){let q=process.platform==="win32"?"npm.cmd":"npm";return new Promise((Q)=>{let J=G(q,["install","-g",`${$}@latest`],{stdio:"inherit",shell:process.platform==="win32"});J.on("exit",(X)=>Q(X===0)),J.on("error",()=>Q(!1))})}function D($){try{let q=t(`"${$}" login status 2>&1`,{timeout:1e4}).toString();return/Logged in using ChatGPT/i.test(q)}catch{return!1}}async function Yz($){return new Promise((q)=>{let Q=G($,["login"],{stdio:"inherit",shell:process.platform==="win32"});Q.on("exit",(J)=>q(J===0)),Q.on("error",()=>q(!1))})}function Zz(){return process.env.CLAUDE_CONFIG_DIR||F(qz(),".claude")}async function Xz($){try{let q=await fetch("https://open.bigmodel.cn/api/anthropic/v1/models",{headers:{Authorization:`Bearer ${$}`},signal:AbortSignal.timeout(1e4)});if(!q.ok)return null;let Q=await q.json(),J=(Array.isArray(Q?.data)?Q.data:[]).map((W)=>typeof W?.display_name==="string"&&W.display_name?W.display_name:String(W?.id??"")).filter(Boolean);if(!J.length)return null;let X=(W)=>{let Z=W.match(/(\d+(?:\.\d+)?)/);return Z?Number(Z[1]):-1},N=(W)=>W.replace(/-Turbo|-Air$/i,""),K=[...J.filter((W)=>!/-Turbo|-Air/i.test(W))].sort((W,Z)=>X(Z)-X(W)),V=K[0],M=K[K.length-1],U={};if(V)U.ANTHROPIC_DEFAULT_OPUS_MODEL=`${V}[1m]`,U.ANTHROPIC_DEFAULT_SONNET_MODEL=`${V}[1m]`;if(M&&N(M)!==N(V))U.ANTHROPIC_DEFAULT_HAIKU_MODEL=M;return Object.keys(U).length?U:null}catch{return null}}async function Wz($){try{let q=Zz();I(q,{recursive:!0});let Q=F(q,"settings.json"),J={};if(B(Q)){let K=C(Q,"utf8");try{let V=JSON.parse(K);if(!V||typeof V!=="object"||Array.isArray(V))return{error:`${Q} 内容不是 JSON 对象, 拒绝覆盖, 请手动检查`};J=V}catch{return{error:`${Q} 解析失败 (JSON 语法错), 拒绝覆盖, 请手动修复后重跑`}}}let X=J.env&&typeof J.env==="object"&&!Array.isArray(J.env)?J.env:{},N={ANTHROPIC_AUTH_TOKEN:$,ANTHROPIC_BASE_URL:"https://open.bigmodel.cn/api/anthropic",API_TIMEOUT_MS:"3000000",CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:"1"},k=await Xz($);if(k)Object.assign(N,k);return J.env={...X,...N},v(Q,JSON.stringify(J,null,2)+`
8
+ `),{path:Q}}catch(q){return{error:q?.message??String(q)}}}function Hz($){try{if(process.platform==="win32")G(process.env.ComSpec??"cmd.exe",["/c","start",'""',$],{detached:!0,stdio:"ignore",windowsHide:!0}).unref();else if(process.platform==="darwin")G("open",[$],{detached:!0,stdio:"ignore"}).unref();else G("xdg-open",[$],{detached:!0,stdio:"ignore"}).unref()}catch{}}async function Nz($,q){try{let J=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}),signal:AbortSignal.timeout(15000)})).json();if(J.tenant_access_token)return{ok:!0};return{ok:!1,error:`飞书拒绝: code=${J.code} msg=${J.msg??"(no msg)"}`}}catch(Q){return{ok:!1,error:`网络错误: ${Q?.message??String(Q)}`}}}function Vz(){try{let $=$z(Jz(import.meta.url)),q=F($,"lodestar.js");if(!B(q))return{error:`找不到 daemon bundle: ${q}`};let Q=G(process.execPath,[q],{detached:!0,stdio:"ignore",windowsHide:!0});return Q.unref(),{pid:Q.pid}}catch($){return{error:$?.message??String($)}}}async function h(){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(B(O)){if(console.log(`${z.yellow}发现已有配置: ${O}${z.reset}`),(await b("覆盖? (y/N)",{default:"n"})).toLowerCase()!=="y"){console.log("已取消"),L.close();return}}Qz("Lodestar 安装向导"),console.log("Lodestar 把 Feishu (飞书) 群聊接到 AI agent。"),console.log("默认后端是 Claude Code; Codex 是可选第二后端, 群里发 model 一键切换。"),console.log("每个群对应一个项目目录, agent 在那里跑、能读写文件。"),console.log(),console.log(`${z.yellow}⚠ Claude 官方限制:${z.reset}`),console.log(` ${z.bold}Claude 订阅 (Pro/Max OAuth 登录) 不支持本项目${z.reset},`),console.log(" 必须走 API 方式 —— 推荐 GLM Coding Plan, 或自备 Anthropic API key。"),console.log(),console.log("本向导依次做 4 件事:"),console.log(` ${z.dim}1) 确保 Claude Code CLI 已装好${z.reset}`),console.log(` ${z.dim}2) GLM Coding Plan API key (推荐, 可选) + Codex (可选)${z.reset}`),console.log(` ${z.dim}3) Feishu 自建应用 (含权限 / 事件 / 发版 + 凭据测试)${z.reset}`),console.log(` ${z.dim}4) 工作目录, 自动启动 daemon${z.reset}`),console.log(),await L.question(`${z.dim}按 Enter 开始 (Ctrl+C 退出)...${z.reset}`),E(1,4,"准备 Claude Code CLI");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@latest${z.reset}`),console.log(),!await A("@anthropic-ai/claude-code"))console.error(`
9
+ ${z.red}安装失败。${z.reset}`),console.error("请手动运行后再开向导:"),console.error(` ${z.cyan}npm install -g @anthropic-ai/claude-code@latest${z.reset}`),console.error(` ${z.cyan}lodestar-setup${z.reset}`),L.close(),process.exit(1);$=R("claude"),console.log(`${z.green}✓ 安装完成${z.reset}: ${z.dim}${$??"(应该装好了, 但 PATH 找不到 — 重开终端再试)"}${z.reset}`)}console.log(),console.log(`${z.dim}下一步可选配 GLM Coding Plan 自动写入路由; 不配则用本机 Claude Code 现有配置。${z.reset}`),console.log(`${z.dim}记住: 别用 \`claude\` 走订阅 OAuth 登录 —— 订阅不支持本项目, 要用 API key。${z.reset}`),E(2,4,"GLM Coding Plan (推荐, 可选)"),console.log("GLM Coding Plan 给 Claude Code 接 GLM 系列模型 (支持 1M token 上下文, 中文友好)。"),console.log("订阅后在智谱开放平台拿一个 API key, 粘到下面 —— 向导自动写进 ~/.claude/settings.json。"),console.log(` ${z.dim}拿 key: https://open.bigmodel.cn → 控制台 → API Keys${z.reset}`),console.log(` ${z.dim}不给也行: 以本机 Claude Code 现有配置启动 (确保是 API key 方式, 非订阅)。${z.reset}`),console.log();let q=await b("GLM API key (直接回车跳过)",{});if(q){let Z=await Wz(q);if("path"in Z)console.log(`${z.green}✓ GLM 路由已写入${z.reset}: ${z.dim}${Z.path}${z.reset}`),console.log(`${z.dim}模型 slots 按端点当前列表自动选取(最新一代 opus/sonnet, 1M ctx)${z.reset}`);else console.log(`${z.red}✗ 写入失败:${z.reset} ${Z.error}`),console.log(`${z.dim}跳过 GLM 配置, 以本机 Claude Code 现有配置启动。${z.reset}`)}else console.log(`${z.dim}已跳过 GLM, 以本机 Claude Code 现有配置启动。${z.reset}`);if(console.log(),console.log(`${z.bold}Codex (可选第二后端)${z.reset}`),console.log(` ${z.dim}想用 Codex 的话, 登录 ChatGPT 订阅即可, 模型/effort 走 ~/.codex/config.toml, 群里发 model 切换。${z.reset}`),(await b("现在顺便配置 Codex 后端吗?",{default:"n"})).toLowerCase()==="y"){let Z=R("codex");if(!Z){if(console.log(`${z.dim}未找到 codex CLI, 安装 @openai/codex...${z.reset}`),await A("@openai/codex"))Z=R("codex")}if(Z&&D(Z))console.log(`${z.green}✓ Codex 已登录 ChatGPT${z.reset}`);else if(Z)if(console.log(`${z.dim}启动 \`codex login\`...${z.reset}`),await Yz(Z)&&D(Z))console.log(`${z.green}✓ Codex 已登录 ChatGPT${z.reset}`);else console.log(`${z.yellow}Codex 登录未完成 — 不影响默认 Claude 后端; 需要时随时跑 codex login。${z.reset}`);else console.log(`${z.yellow}Codex 安装失败 — 不影响默认 Claude 后端; 需要时手动 npm i -g @openai/codex。${z.reset}`)}else console.log(`${z.dim}已跳过 Codex (需要时随时跑 codex login, 群里发 model 切)。${z.reset}`);E(3,4,"Feishu 自建应用");let J="https://open.feishu.cn/app";console.log("打开飞书开放平台 (浏览器):"),console.log(` ${z.cyan}${J}${z.reset}`),Hz(J),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:chat:create${z.reset} ${z.dim}# wt: 自动创建 worktree 群${z.reset}`),console.log(` • ${z.bold}im:chat:delete${z.reset} ${z.dim}# wt: 解散 worktree 群${z.reset}`),console.log(` • ${z.bold}im:chat.members:read${z.reset} ${z.dim}# wt: 判断发起人是否已在群内${z.reset}`),console.log(` • ${z.bold}im:chat.members:write_only${z.reset} ${z.dim}# wt: 把发起人拉进已有 worktree 群${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.dim}# 敏感: 接收群里所有消息${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 X="",N="";while(!0){X=await b("App ID (以 cli_ 开头)",{required:!0}),N=await b("App Secret",{required:!0}),console.log(`${z.dim}测试中... (调 tenant_access_token endpoint)${z.reset}`);let Z=await Nz(X,N);if(Z.ok){console.log(`${z.green}✓ Feishu 凭据测试通过${z.reset}`);break}if(console.log(`${z.red}✗ 测试失败:${z.reset} ${Z.error}`),console.log(`${z.dim}最常见原因: app_id / app_secret 抄错, 或应用还没 "发布上线" (步骤 ⑤)。${z.reset}`),console.log(),(await b("重新填? (Y/n)",{default:"y"})).toLowerCase()==="n")console.log(`${z.yellow}已取消, 配置未写盘。${z.reset}`),L.close(),process.exit(1)}E(4,4,"工作目录 + 启动"),console.log("每个 Feishu 群对应 projects_root 下同名的目录。"),console.log();let k=process.platform==="win32"?process.env.USERPROFILE??"C:\\Users\\Default":process.env.HOME??"/root",K=await b("projects_root",{default:k});I(x,{recursive:!0});let V=["# Lodestar config — generated by `lodestar-setup`","# Edit by hand or re-run setup to overwrite.","","[feishu]",`app_id = "${y(X)}"`,`app_secret = "${y(N)}"`,"","[runtime]",`projects_root = "${y(K)}"`,""];v(O,V.join(`
10
+ `)),console.log(`
11
+ ${z.green}${z.bold}✓ 配置已写入${z.reset}`),console.log(` ${z.cyan}${O}${z.reset}`),console.log(`
12
+ ${z.bold}启动 daemon...${z.reset}`);let M=Vz(),U=process.platform==="win32"?"\\":"/",W=process.platform==="win32"?`${process.env.LOCALAPPDATA??"%LOCALAPPDATA%"}\\Lodestar\\daemon-YYYY-MM-DD.log`:`${process.env.HOME??"~"}/.local/share/lodestar/daemon-YYYY-MM-DD.log`;if(M.pid)console.log(`${z.green}✓ daemon 已在后台启动${z.reset} (pid ${M.pid})`),console.log(),console.log(`${z.bold}最后一步: 在 Feishu 验证${z.reset}`),console.log(" ① 把机器人拉进任意飞书群"),console.log(` ② 群名 = ${z.cyan}${K}${U}<群名>${z.reset} 下的目录名 (新群第一条消息会自动建)`),console.log(" ③ 在群里发任意一条消息, 默认由 Claude 接管"),console.log(` ${z.dim}(群里发 model 可选择已配置账号、动态模型和 effort)${z.reset}`),console.log(),console.log("日志 (按日滚动, 保留近 7 天):"),console.log(` ${z.cyan}${W}${z.reset}`),console.log(),console.log(`${z.dim}若长期跑后台, Linux systemd --user,macOS launchd,Windows 配任务计划程序。${z.reset}`);else console.log(`${z.yellow}启动失败: ${M.error}${z.reset}`),console.log(`手动运行: ${z.cyan}lodestar-daemon${z.reset}`);console.log(),L.close()}h().catch(($)=>{console.error(`
13
13
  lodestar-setup: ${$?.message??$}`),process.exit(1)});
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as f}from"node:child_process";import{existsSync as U,readFileSync as y}from"node:fs";import{homedir as W}from"node:os";import{join as z}from"node:path";var X=W(),B=process.platform==="win32";function b(J,K,Y){if(J)return J;if(K)return z(K,"lodestar");return Y}function H(){if(B)return z(process.env.APPDATA??z(X,"AppData","Roaming"),"Lodestar");return z(X,".config","lodestar")}function V(){if(B)return z(process.env.LOCALAPPDATA??z(X,"AppData","Local"),"Lodestar");return z(X,".local","share","lodestar")}var w=b(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,H()),Q=b(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,V()),x=process.env.LODESTAR_CONFIG??z(w,"config.toml"),Z=z(Q,"daemon.pid"),S=z(Q,"daemon.log"),E=z(Q,"session-chat-map.json"),R=z(Q,"session-resume-map.json"),T=z(Q,"session-turns-map.json"),v=z(Q,"session-model-map.json"),h=z(Q,"tasklist-map.json"),k=z(Q,"context-window-cache.json"),F=z(Q,"notify-callbacks.json"),j=z(Q,"alive-on-shutdown.json"),m=z(Q,"inbox"),C=z(Q,"debug.sock"),A=z(Q,"debug-context.json");import{execSync as G}from"node:child_process";import{readFileSync as L,writeFileSync as t}from"node:fs";function M(J){try{if(process.platform==="linux")return L(`/proc/${J}/cmdline`,"utf8").replace(/\0/g," ").trim();if(process.platform==="darwin")return G(`ps -p ${J} -o args=`,{encoding:"utf8",timeout:2000}).trim();if(process.platform==="win32")return G(`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${J}').CommandLine"`,{encoding:"utf8",timeout:5000}).trim()||null;return null}catch{return null}}function N(J,K){if(!Number.isFinite(J)||J<=0||!K)return!1;let Y=M(J);if(!Y)return!1;if(process.platform==="win32")return Y.toLowerCase().includes(K.toLowerCase());return Y.includes(K)}var q={reset:"\x1B[0m",bold:"\x1B[1m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"};function P(J){return new Promise((K)=>setTimeout(K,J))}async function u(){if(!U(Z)){console.log(`${q.yellow}Lodestar daemon 未运行${q.reset} ${q.dim}(${Z} 不存在)${q.reset}`);return}let J=y(Z,"utf8").split(`
3
- `),K=parseInt((J[0]??"").trim(),10),Y=(J[1]??"").trim();if(!Number.isFinite(K)||K<=0)console.error(`${q.red}PID 文件格式坏:${q.reset} ${Z}`),process.exit(1);if(Y&&!N(K,Y)){console.log(`${q.yellow}PID ${K} 上没有 daemon (stale 文件)${q.reset}`),console.log(`${q.dim}手删 ${Z} 后再试${q.reset}`);return}console.log(`${q.bold}停止 daemon${q.reset} (pid ${K})...`);try{if(process.platform==="win32")f(`taskkill /PID ${K} /T /F`,{stdio:"ignore"});else process.kill(K,"SIGTERM")}catch($){console.error(`${q.red}发信号失败:${q.reset} ${$?.message??$}`),process.exit(1)}for(let $=0;$<50;$++){if(!U(Z)){console.log(`${q.green}✓ daemon 已停${q.reset}`);return}await P(100)}console.log(`${q.yellow}已发 SIGTERM, 但 5s 内 daemon 没清掉 ${Z}${q.reset}`),console.log(`${q.dim}它可能还在收尾 (落盘 alive marker / 关 WS); 下次启动 pid-guard 会自动判别。${q.reset}`)}u().catch((J)=>{console.error(`${q.red}lodestar-stop:${q.reset} ${J?.message??J}`),process.exit(1)});
2
+ import{execSync as f}from"node:child_process";import{existsSync as U,readFileSync as y}from"node:fs";import{homedir as W}from"node:os";import{join as q}from"node:path";var X=W(),B=process.platform==="win32";function b(J,K,Y){if(J)return J;if(K)return q(K,"lodestar");return Y}function H(){if(B)return q(process.env.APPDATA??q(X,"AppData","Roaming"),"Lodestar");return q(X,".config","lodestar")}function V(){if(B)return q(process.env.LOCALAPPDATA??q(X,"AppData","Local"),"Lodestar");return q(X,".local","share","lodestar")}var w=b(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,H()),Q=b(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,V()),x=process.env.LODESTAR_CONFIG??q(w,"config.toml"),Z=q(Q,"daemon.pid"),S=q(Q,"daemon.log"),E=q(Q,"session-chat-map.json"),R=q(Q,"session-resume-map.json"),v=q(Q,"session-turns-map.json"),T=q(Q,"session-model-map.json"),h=q(Q,"temp-session-leases.json"),k=q(Q,"tasklist-map.json"),F=q(Q,"context-window-cache.json"),m=q(Q,"notify-callbacks.json"),j=q(Q,"alive-on-shutdown.json"),C=q(Q,"inbox"),l=q(Q,"debug.sock"),A=q(Q,"debug-context.json");import{execSync as G}from"node:child_process";import{readFileSync as L,writeFileSync as I}from"node:fs";function M(J){try{if(process.platform==="linux")return L(`/proc/${J}/cmdline`,"utf8").replace(/\0/g," ").trim();if(process.platform==="darwin")return G(`ps -p ${J} -o args=`,{encoding:"utf8",timeout:2000}).trim();if(process.platform==="win32")return G(`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${J}').CommandLine"`,{encoding:"utf8",timeout:5000}).trim()||null;return null}catch{return null}}function N(J,K){if(!Number.isFinite(J)||J<=0||!K)return!1;let Y=M(J);if(!Y)return!1;if(process.platform==="win32")return Y.toLowerCase().includes(K.toLowerCase());return Y.includes(K)}var z={reset:"\x1B[0m",bold:"\x1B[1m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"};function P(J){return new Promise((K)=>setTimeout(K,J))}async function u(){if(!U(Z)){console.log(`${z.yellow}Lodestar daemon 未运行${z.reset} ${z.dim}(${Z} 不存在)${z.reset}`);return}let J=y(Z,"utf8").split(`
3
+ `),K=parseInt((J[0]??"").trim(),10),Y=(J[1]??"").trim();if(!Number.isFinite(K)||K<=0)console.error(`${z.red}PID 文件格式坏:${z.reset} ${Z}`),process.exit(1);if(Y&&!N(K,Y)){console.log(`${z.yellow}PID ${K} 上没有 daemon (stale 文件)${z.reset}`),console.log(`${z.dim}手删 ${Z} 后再试${z.reset}`);return}console.log(`${z.bold}停止 daemon${z.reset} (pid ${K})...`);try{if(process.platform==="win32")f(`taskkill /PID ${K} /T /F`,{stdio:"ignore"});else process.kill(K,"SIGTERM")}catch($){console.error(`${z.red}发信号失败:${z.reset} ${$?.message??$}`),process.exit(1)}for(let $=0;$<50;$++){if(!U(Z)){console.log(`${z.green}✓ daemon 已停${z.reset}`);return}await P(100)}console.log(`${z.yellow}已发 SIGTERM, 但 5s 内 daemon 没清掉 ${Z}${z.reset}`),console.log(`${z.dim}它可能还在收尾 (落盘 alive marker / 关 WS); 下次启动 pid-guard 会自动判别。${z.reset}`)}u().catch((J)=>{console.error(`${z.red}lodestar-stop:${z.reset} ${J?.message??J}`),process.exit(1)});
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as b}from"node:child_process";import{existsSync as g}from"node:fs";import{homedir as d}from"node:os";import{join as o}from"node:path";var r=d(),a=process.platform==="win32";function n(e,l,m){if(e)return e;if(l)return o(l,"lodestar");return m}function y(){if(a)return o(process.env.APPDATA??o(r,"AppData","Roaming"),"Lodestar");return o(r,".config","lodestar")}function u(){if(a)return o(process.env.LOCALAPPDATA??o(r,"AppData","Local"),"Lodestar");return o(r,".local","share","lodestar")}var C=n(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,y()),t=n(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,u()),K=process.env.LODESTAR_CONFIG??o(C,"config.toml"),$=o(t,"daemon.pid"),f=o(t,"daemon.log"),S=o(t,"session-chat-map.json"),i=o(t,"session-resume-map.json"),q=o(t,"session-turns-map.json"),z=o(t,"session-model-map.json"),J=o(t,"tasklist-map.json"),Q=o(t,"context-window-cache.json"),Y=o(t,"notify-callbacks.json"),Z=o(t,"alive-on-shutdown.json"),W=o(t,"inbox"),X=o(t,"debug.sock"),B=o(t,"debug-context.json");var s={reset:"\x1B[0m",bold:"\x1B[1m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"},p=["@leviyuan/lodestar@latest","@openai/codex@latest","@anthropic-ai/claude-code@latest","@anthropic-ai/claude-agent-sdk@latest","@anthropic-ai/sdk@latest"];function w(){let e=process.platform==="win32"?"npm.cmd":"npm";return new Promise((l)=>{let m=b(e,["install","-g","--include=optional",...p],{stdio:"inherit",shell:process.platform==="win32"});m.on("exit",(c)=>l(c??1)),m.on("error",(c)=>{console.error(`${s.red}spawn npm 失败:${s.reset} ${c.message}`),l(1)})})}async function x(){console.log(`${s.bold}更新 Lodestar + Codex CLI + Claude Code / SDK${s.reset}`),console.log(`${s.dim}npm i -g ${p.join(" ")}${s.reset}
3
- `);let e=await w();if(e!==0)console.error(`
4
- ${s.red}更新失败 (npm exit ${e})${s.reset}`),process.exit(e);if(console.log(`
5
- ${s.green}✓ 更新完成${s.reset}`),g($))console.log(),console.log(`${s.yellow}检测到 daemon 仍在跑老版本进程, 用新版本需要重启:${s.reset}`),console.log(` ${s.dim}# systemd / Windows 后台托管的:${s.reset}`),console.log(` ${s.cyan}systemctl --user restart lodestar${s.reset} ${s.dim}# Linux/macOS${s.reset}`),console.log(` ${s.dim}# 或手动重启:${s.reset}`),console.log(` ${s.cyan}lodestar-stop && lodestar-daemon${s.reset}`)}x().catch((e)=>{console.error(`${s.red}lodestar-update:${s.reset} ${e?.message??e}`),process.exit(1)});
2
+ import{spawn as g}from"node:child_process";import{existsSync as x}from"node:fs";import{homedir as p}from"node:os";import{join as o}from"node:path";var m=p(),a=process.platform==="win32";function n(t,l,r){if(t)return t;if(l)return o(l,"lodestar");return r}function u(){if(a)return o(process.env.APPDATA??o(m,"AppData","Roaming"),"Lodestar");return o(m,".config","lodestar")}function y(){if(a)return o(process.env.LOCALAPPDATA??o(m,"AppData","Local"),"Lodestar");return o(m,".local","share","lodestar")}var C=n(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,u()),e=n(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,y()),K=process.env.LODESTAR_CONFIG??o(C,"config.toml"),$=o(e,"daemon.pid"),f=o(e,"daemon.log"),S=o(e,"session-chat-map.json"),i=o(e,"session-resume-map.json"),q=o(e,"session-turns-map.json"),z=o(e,"session-model-map.json"),J=o(e,"temp-session-leases.json"),Q=o(e,"tasklist-map.json"),Y=o(e,"context-window-cache.json"),Z=o(e,"notify-callbacks.json"),W=o(e,"alive-on-shutdown.json"),X=o(e,"inbox"),B=o(e,"debug.sock"),G=o(e,"debug-context.json");var s={reset:"\x1B[0m",bold:"\x1B[1m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"},d=["@leviyuan/lodestar@latest","@openai/codex@latest","@anthropic-ai/claude-code@latest","@anthropic-ai/claude-agent-sdk@latest","@anthropic-ai/sdk@latest"];function b(){let t=process.platform==="win32"?"npm.cmd":"npm";return new Promise((l)=>{let r=g(t,["install","-g","--include=optional",...d],{stdio:"inherit",shell:process.platform==="win32"});r.on("exit",(c)=>l(c??1)),r.on("error",(c)=>{console.error(`${s.red}spawn npm 失败:${s.reset} ${c.message}`),l(1)})})}async function w(){console.log(`${s.bold}更新 Lodestar + Codex CLI + Claude Code / SDK${s.reset}`),console.log(`${s.dim}npm i -g ${d.join(" ")}${s.reset}
3
+ `);let t=await b();if(t!==0)console.error(`
4
+ ${s.red}更新失败 (npm exit ${t})${s.reset}`),process.exit(t);if(console.log(`
5
+ ${s.green}✓ 更新完成${s.reset}`),x($))console.log(),console.log(`${s.yellow}检测到 daemon 仍在跑老版本进程, 用新版本需要重启:${s.reset}`),console.log(` ${s.dim}# Linux systemd --user 托管的:${s.reset}`),console.log(` ${s.cyan}systemctl --user restart lodestar${s.reset} ${s.dim}# Linux${s.reset}`),console.log(` ${s.dim}# macOS launchd / Windows 任务计划:用对应服务管理器重启。${s.reset}`),console.log(` ${s.dim}# 或手动重启:${s.reset}`),console.log(` ${s.cyan}lodestar-stop && lodestar-daemon${s.reset}`)}w().catch((t)=>{console.error(`${s.red}lodestar-update:${s.reset} ${t?.message??t}`),process.exit(1)});