@leviyuan/lodestar 0.1.0 → 2.0.14
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 +76 -67
- package/cli.ts +176 -0
- package/config.ts +135 -0
- package/daemon.ts +1080 -144
- package/email-worker.ts +534 -0
- package/env-bootstrap.ts +7 -0
- package/feishu-mcp.ts +482 -0
- package/package.json +36 -37
- package/runtime-api.ts +569 -0
- package/scripts/runtime-thread.sh +91 -0
- package/status-dashboard.ts +733 -0
- package/src/cardkit.ts +0 -215
- package/src/cards.ts +0 -304
- package/src/claude-process.ts +0 -301
- package/src/config.ts +0 -83
- package/src/feishu.ts +0 -365
- package/src/instructions.ts +0 -22
- package/src/log.ts +0 -11
- package/src/paths.ts +0 -41
- package/src/session.ts +0 -447
package/README.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
[English](docs/README-en.md) | 中文
|
|
2
|
+
|
|
1
3
|
<p align="center">
|
|
2
|
-
<img src="
|
|
4
|
+
<img src="promo-cn.jpg" alt="夜航星 Lodestar" width="100%">
|
|
3
5
|
</p>
|
|
4
6
|
|
|
5
|
-
# 夜航星 (Lodestar)
|
|
7
|
+
# 夜航星 (Lodestar) v2
|
|
8
|
+
|
|
9
|
+
> **DeepSeek TUI 迁移版**
|
|
10
|
+
>
|
|
11
|
+
> 夜航星已从 Claude Code 迁移至 DeepSeek TUI。核心交互逻辑由 Runtime API + SSE 事件流替代了原有的 tmux spawn + JSONL 文件 IPC。飞书群的协作体验保持不变。
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
飞书 (Lark) 协作通道,用于 [DeepSeek TUI](https://github.com/deepseek-ai/deepseek-tui) — 在飞书群里和 DeepSeek 协作,会话持久化、多项目并行、7×24 可用。
|
|
8
14
|
|
|
9
15
|
## 项目哲学
|
|
10
16
|
|
|
@@ -12,93 +18,96 @@ AI 不是帮手,是倍率。它放大的不是体力,是你——你的直
|
|
|
12
18
|
|
|
13
19
|
夜航星让这件事真正发生:在你思考的地方接住想法,在你转身之后继续把它推向终点。一个群,一个项目,一段不熄灯的对话。你醒着它在听,你睡了它还在跑。
|
|
14
20
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
每个飞书群对应一个 Claude 会话。**群名 = `~/` 下的项目目录名**。
|
|
18
|
-
|
|
19
|
-
- 在群里发任意文字 — Claude 接管这一轮,回复以**流式打字机**实时渲染在一张飞书卡片里。
|
|
20
|
-
- 思考过程、每一次工具调用都在卡片里被收纳为**可展开折叠面板**:折起来是概述,展开是详情。你随时能审阅它在做什么。
|
|
21
|
-
- 需要授权的操作(执行命令、修改文件……)会单独弹一张橙色**权限卡片**,你在群里点 `允许` / `始终允许` / `拒绝` 就行。
|
|
22
|
-
- **图片、文件双向互传**:用户发到群里的图/文件,Claude 通过消息里的 `[file: /abs/path]` 提示就能读;Claude 想把文件发回来,在回复任意位置写 `[[send: /abs/path]]`,标记会被剥离,文件以独立消息出现在群里。出站路径限制在该会话的工作目录、`/tmp/lodestar-*` 与 inbox 之内,`/etc`、`~/.ssh`、`~/.config` 等敏感目录被白名单拒绝。
|
|
23
|
-
- 一轮跑完,卡片合上、可转发;下一句话开新一轮。
|
|
24
|
-
|
|
25
|
-
### 文本控制指令
|
|
26
|
-
|
|
27
|
-
直接发这四个**裸词**(不需要斜杠,不区分大小写),daemon 拦截、不转发给 Claude:
|
|
28
|
-
|
|
29
|
-
| 指令 | 行为 |
|
|
30
|
-
| --- | --- |
|
|
31
|
-
| `hi` | 未运行时启动;运行中弹一张**控制台卡片**(状态行 + 中断/clear/终止/ls 按钮) |
|
|
32
|
-
| `kill` | 优雅关闭 Claude 进程;记住 `sessionId`,下次 `restart` 还能 resume |
|
|
33
|
-
| `restart` | 用上一次的 `sessionId` 重启会话(保留上下文) |
|
|
34
|
-
| `clear` | 杀掉进程并启动一个全新 session(等价于 Claude Code 的 `/clear`) |
|
|
35
|
-
|
|
36
|
-
> 这四个词被全局保留:在群里发 "hi" 当问候也会触发控制台卡片,不会到 Claude 那边。换来的是手机上单手打字的便利。
|
|
21
|
+
## 核心功能
|
|
37
22
|
|
|
38
|
-
|
|
23
|
+
- **Markdown 卡片渲染** — 标题、列表、代码块自动转为飞书富文本卡片
|
|
24
|
+
- **实时反馈** — 👌→✅ 表情追踪消息进度,输出流式转发到群里
|
|
25
|
+
- **权限审批卡片** — 一键批准或拒绝,群里点按钮就能远程授权
|
|
26
|
+
- **图片与文件双向传输** — 截图发给 DeepSeek,DeepSeek 也能传文件回来
|
|
27
|
+
- **会话管理** — 发 `hi` 开工,`restart` / `kill` / `clear` 随时管控
|
|
28
|
+
- **邮件通道** — 受控协作入口:白名单准入、预算上限、权限隔离
|
|
29
|
+
- **状态仪表盘** — 赛博朋克风格监控面板,所有会话一目了然
|
|
30
|
+
- **可靠性保障** — WS 指数退避重连、断线自动恢复、7×24 无人值守
|
|
39
31
|
|
|
40
32
|
## 安装
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- `im:message:send_as_bot` / `im:message` / `im:chat:readonly` / `im:resource`
|
|
49
|
-
- `cardkit:card:read` `cardkit:card:write`
|
|
50
|
-
`cardkit:card.element:read` `cardkit:card.element:write`
|
|
51
|
-
`cardkit:card.settings:read` `cardkit:card.settings:write`
|
|
52
|
-
|
|
53
|
-
### 2. 配置
|
|
34
|
+
```bash
|
|
35
|
+
npm i -g @leviyuan/lodestar
|
|
36
|
+
lodestar configure # 生成 ~/.deepseek/lodestar.toml
|
|
37
|
+
vi ~/.deepseek/lodestar.toml # 填入飞书凭证
|
|
38
|
+
lodestar daemon # 启动
|
|
39
|
+
```
|
|
54
40
|
|
|
55
|
-
|
|
41
|
+
配置文件 `~/.deepseek/lodestar.toml`:
|
|
56
42
|
|
|
57
43
|
```toml
|
|
58
44
|
[feishu]
|
|
59
|
-
app_id
|
|
60
|
-
app_secret = "
|
|
45
|
+
app_id = "cli_xxxxxxxxxxxxxxxx"
|
|
46
|
+
app_secret = "your_app_secret_here"
|
|
61
47
|
|
|
62
48
|
[runtime]
|
|
63
|
-
|
|
49
|
+
port = 7878
|
|
50
|
+
api_token = "your_token_here"
|
|
64
51
|
```
|
|
65
52
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
### 3. 启动
|
|
53
|
+
前置:`deepseek serve --http` 已运行:
|
|
69
54
|
|
|
70
55
|
```bash
|
|
71
|
-
|
|
72
|
-
cd ~/lodestar
|
|
73
|
-
bun install
|
|
74
|
-
bun daemon.ts
|
|
56
|
+
DEEPSEEK_RUNTIME_TOKEN=lodestar-runtime-token-v2 deepseek serve --http --port 7878
|
|
75
57
|
```
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
> **小贴士**:群名首次出现时,daemon 会自动在 `~/{群名}/` 创建项目目录并 `git init`。换句话说,开新群 = 开新项目。
|
|
59
|
+
## 指令
|
|
80
60
|
|
|
81
|
-
|
|
61
|
+
| 指令 | 作用 |
|
|
62
|
+
|------|------|
|
|
63
|
+
| `hi` | 创建或查看会话状态 |
|
|
64
|
+
| `restart` | 重启会话(fork 保留上下文) |
|
|
65
|
+
| `kill` | 关闭会话(archive) |
|
|
66
|
+
| `clear` | 清空上下文(新建 thread) |
|
|
67
|
+
| `ls [path]` | 查看项目目录树 |
|
|
68
|
+
| 普通消息 | 注入为 turn,DeepSeek 处理后回复 |
|
|
82
69
|
|
|
83
|
-
|
|
70
|
+
## CLI 命令
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
| 命令 | 作用 |
|
|
73
|
+
|------|------|
|
|
74
|
+
| `lodestar daemon` | 启动守护进程 |
|
|
75
|
+
| `lodestar mcp` | 单独启动 MCP server(调试用) |
|
|
76
|
+
| `lodestar configure` | 生成配置文件 |
|
|
77
|
+
| `lodestar status` | 查看运行状态 |
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
Type=simple
|
|
92
|
-
ExecStart=/home/USER/.bun/bin/bun /home/USER/lodestar/daemon.ts
|
|
93
|
-
Restart=always
|
|
94
|
-
RestartSec=3
|
|
79
|
+
## 架构
|
|
95
80
|
|
|
96
|
-
[Install]
|
|
97
|
-
WantedBy=default.target
|
|
98
81
|
```
|
|
82
|
+
飞书服务器
|
|
83
|
+
│ WebSocket
|
|
84
|
+
▼
|
|
85
|
+
daemon.ts (消息路由进程)
|
|
86
|
+
│
|
|
87
|
+
├── Lark WS Client (指数退避重连)
|
|
88
|
+
├── RuntimeApiClient ────HTTP──┐
|
|
89
|
+
├── MessageQueue │
|
|
90
|
+
├── SseEventHandler ←──SSE────┤
|
|
91
|
+
│ │
|
|
92
|
+
│ hi → POST /v1/threads │
|
|
93
|
+
│ restart → fork + archive │
|
|
94
|
+
│ kill → archive │
|
|
95
|
+
│ clear → archive + new │
|
|
96
|
+
└─────────────────────────────┼── localhost:7878
|
|
97
|
+
▼
|
|
98
|
+
┌──────────────────────────┐
|
|
99
|
+
│ deepseek serve --http │
|
|
100
|
+
│ Runtime API │
|
|
101
|
+
│ │
|
|
102
|
+
│ 加载 feishu MCP server │
|
|
103
|
+
└──────────────────────────┘
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 仓库
|
|
99
107
|
|
|
100
|
-
|
|
108
|
+
- **GitHub**: https://github.com/leviyuan/lodestar
|
|
109
|
+
- **npm**: https://www.npmjs.com/package/@leviyuan/lodestar
|
|
101
110
|
|
|
102
|
-
##
|
|
111
|
+
## 许可证
|
|
103
112
|
|
|
104
113
|
[MIT](LICENSE)
|
package/cli.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* 夜航星 (Lodestar) CLI — npm 全局命令入口
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* lodestar daemon 启动飞书消息路由守护进程
|
|
7
|
+
* lodestar mcp 启动 Feishu MCP Server (供 DeepSeek TUI 加载)
|
|
8
|
+
* lodestar configure 生成默认配置文件 ~/.deepseek/lodestar/.env
|
|
9
|
+
* lodestar status 查看运行状态
|
|
10
|
+
* lodestar --help 显示帮助
|
|
11
|
+
*
|
|
12
|
+
* npm 全局安装后: npm i -g lodestar
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync, chmodSync } from 'fs'
|
|
16
|
+
import { homedir } from 'os'
|
|
17
|
+
import { join } from 'path'
|
|
18
|
+
|
|
19
|
+
const STATE_DIR = join(homedir(), '.deepseek', 'lodestar')
|
|
20
|
+
const CONFIG_FILE = join(homedir(), '.deepseek', 'lodestar.toml')
|
|
21
|
+
|
|
22
|
+
function printHelp(): void {
|
|
23
|
+
console.log(`
|
|
24
|
+
夜航星 (Lodestar) v2.0.0 — DeepSeek TUI 飞书群协作通道
|
|
25
|
+
|
|
26
|
+
用法: lodestar <command>
|
|
27
|
+
|
|
28
|
+
命令:
|
|
29
|
+
daemon 启动飞书消息路由守护进程
|
|
30
|
+
mcp 启动 Feishu MCP Server (供 deepseek serve 加载)
|
|
31
|
+
configure 生成/更新配置文件 ~/.deepseek/lodestar.toml
|
|
32
|
+
status 查看运行状态
|
|
33
|
+
|
|
34
|
+
示例:
|
|
35
|
+
lodestar configure # 首次安装后运行
|
|
36
|
+
lodestar daemon # 启动守护进程
|
|
37
|
+
lodestar mcp # 单独启动 MCP server (调试用)
|
|
38
|
+
|
|
39
|
+
配置:
|
|
40
|
+
飞书凭证配置在 ~/.deepseek/lodestar/.env
|
|
41
|
+
格式: FEISHU_APP_ID=xxx\\nFEISHU_APP_SECRET=xxx
|
|
42
|
+
`.trim())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function cmdDaemon(): Promise<void> {
|
|
46
|
+
console.log('启动夜航星守护进程...')
|
|
47
|
+
try {
|
|
48
|
+
await import('./daemon.ts')
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('daemon 启动失败:', err)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function cmdMcp(): Promise<void> {
|
|
56
|
+
console.log('启动 Feishu MCP Server...')
|
|
57
|
+
try {
|
|
58
|
+
await import('./feishu-mcp.ts')
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error('MCP server 启动失败:', err)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function cmdConfigure(): void {
|
|
66
|
+
mkdirSync(STATE_DIR, { recursive: true })
|
|
67
|
+
|
|
68
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
69
|
+
const template = [
|
|
70
|
+
'# 夜航星 (Lodestar) 配置文件',
|
|
71
|
+
'# 从飞书开发者后台获取凭证: https://open.feishu.cn/app',
|
|
72
|
+
'',
|
|
73
|
+
'[feishu]',
|
|
74
|
+
'app_id = "cli_xxxxxxxxxxxxxxxx"',
|
|
75
|
+
'app_secret = "your_app_secret_here"',
|
|
76
|
+
'',
|
|
77
|
+
'[projects]',
|
|
78
|
+
'# 项目根目录 (默认 ~/)',
|
|
79
|
+
'root = "~/"',
|
|
80
|
+
'',
|
|
81
|
+
'[runtime]',
|
|
82
|
+
'# DeepSeek Runtime API 端口 (deepseek serve --http --port)',
|
|
83
|
+
'port = 7878',
|
|
84
|
+
'api_token = "your_token_here"',
|
|
85
|
+
'',
|
|
86
|
+
].join('\n')
|
|
87
|
+
writeFileSync(CONFIG_FILE, template)
|
|
88
|
+
chmodSync(CONFIG_FILE, 0o600)
|
|
89
|
+
console.log(`✅ 配置文件已生成: ${CONFIG_FILE}`)
|
|
90
|
+
console.log(' 请编辑此文件填入你的飞书 App ID 和 Secret')
|
|
91
|
+
} else {
|
|
92
|
+
console.log(`配置文件已存在: ${CONFIG_FILE}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Also ensure other state dirs
|
|
96
|
+
mkdirSync(join(STATE_DIR, 'inbox'), { recursive: true })
|
|
97
|
+
mkdirSync(join(STATE_DIR, 'messages'), { recursive: true })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cmdStatus(): void {
|
|
101
|
+
console.log('夜航星 运行状态')
|
|
102
|
+
console.log('────────────────')
|
|
103
|
+
|
|
104
|
+
// Check daemon
|
|
105
|
+
const pidFile = join(STATE_DIR, 'daemon.pid')
|
|
106
|
+
if (existsSync(pidFile)) {
|
|
107
|
+
try {
|
|
108
|
+
const pid = parseInt(readFileSync(pidFile, 'utf8').trim())
|
|
109
|
+
process.kill(pid, 0) // check if alive
|
|
110
|
+
console.log(`🟢 daemon 运行中 (PID ${pid})`)
|
|
111
|
+
} catch {
|
|
112
|
+
console.log('🔴 daemon 已停止 (stale PID)')
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
console.log('⚪ daemon 未启动')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check config
|
|
119
|
+
if (existsSync(CONFIG_FILE)) {
|
|
120
|
+
const content = readFileSync(CONFIG_FILE, 'utf8')
|
|
121
|
+
const hasId = content.includes('app_id = "cli_') && !content.includes('cli_xxxxxxxxxxxxxxxx')
|
|
122
|
+
const hasSecret = content.includes('app_secret = "') && !content.includes('your_app_secret_here')
|
|
123
|
+
console.log(hasId && hasSecret ? '🟢 凭证 已配置' : '🟡 凭证 未配置 (运行 lodestar configure 查看)')
|
|
124
|
+
} else {
|
|
125
|
+
console.log('🔴 凭证 配置文件不存在 (运行 lodestar configure)')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check serve
|
|
129
|
+
try {
|
|
130
|
+
const resp = require('child_process').execSync('curl -sf http://localhost:7878/health 2>/dev/null', { timeout: 3000 }).toString()
|
|
131
|
+
if (resp.includes('ok')) console.log('🟢 serve 运行中 (localhost:7878)')
|
|
132
|
+
} catch {
|
|
133
|
+
console.log('⚪ serve 未运行 (启动: deepseek serve --http --port 7878)')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// MCP config
|
|
137
|
+
const mcpFile = join(homedir(), '.deepseek', 'mcp.json')
|
|
138
|
+
if (existsSync(mcpFile)) {
|
|
139
|
+
try {
|
|
140
|
+
const mcp = JSON.parse(readFileSync(mcpFile, 'utf8'))
|
|
141
|
+
if (mcp.feishu) console.log('🟢 MCP 已注册')
|
|
142
|
+
else console.log('🟡 MCP 未注册 (启动 daemon 自动注入)')
|
|
143
|
+
} catch {
|
|
144
|
+
console.log('🟡 MCP 配置解析失败')
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
console.log('⚪ MCP 未配置')
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Main ──
|
|
152
|
+
const cmd = process.argv[2]
|
|
153
|
+
|
|
154
|
+
switch (cmd) {
|
|
155
|
+
case 'daemon':
|
|
156
|
+
cmdDaemon()
|
|
157
|
+
break
|
|
158
|
+
case 'mcp':
|
|
159
|
+
cmdMcp()
|
|
160
|
+
break
|
|
161
|
+
case 'configure':
|
|
162
|
+
cmdConfigure()
|
|
163
|
+
break
|
|
164
|
+
case 'status':
|
|
165
|
+
cmdStatus()
|
|
166
|
+
break
|
|
167
|
+
case '--help':
|
|
168
|
+
case '-h':
|
|
169
|
+
case undefined:
|
|
170
|
+
printHelp()
|
|
171
|
+
break
|
|
172
|
+
default:
|
|
173
|
+
console.error(`未知命令: ${cmd}`)
|
|
174
|
+
console.error('运行 lodestar --help 查看帮助')
|
|
175
|
+
process.exit(1)
|
|
176
|
+
}
|
package/config.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 夜航星配置加载器 — 从 ~/.deepseek/lodestar.toml 读取配置
|
|
3
|
+
*
|
|
4
|
+
* TOML 格式:
|
|
5
|
+
* [feishu]
|
|
6
|
+
* app_id = "cli_xxx"
|
|
7
|
+
* app_secret = "xxx"
|
|
8
|
+
*
|
|
9
|
+
* [projects]
|
|
10
|
+
* root = "~/projects" # 可选,默认 ~/
|
|
11
|
+
*
|
|
12
|
+
* [runtime]
|
|
13
|
+
* port = 7878
|
|
14
|
+
* api_token = "your_token_here"
|
|
15
|
+
*
|
|
16
|
+
* [email] # 可选
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync, existsSync } from 'fs'
|
|
20
|
+
import { homedir } from 'os'
|
|
21
|
+
import { join } from 'path'
|
|
22
|
+
|
|
23
|
+
export interface LodestarConfig {
|
|
24
|
+
feishu: {
|
|
25
|
+
app_id: string
|
|
26
|
+
app_secret: string
|
|
27
|
+
}
|
|
28
|
+
projects: {
|
|
29
|
+
root: string
|
|
30
|
+
}
|
|
31
|
+
runtime: {
|
|
32
|
+
port: number
|
|
33
|
+
api_token: string
|
|
34
|
+
}
|
|
35
|
+
email?: {
|
|
36
|
+
imap_host?: string
|
|
37
|
+
imap_port?: number
|
|
38
|
+
imap_user?: string
|
|
39
|
+
imap_pass?: string
|
|
40
|
+
smtp_host?: string
|
|
41
|
+
smtp_port?: number
|
|
42
|
+
smtp_user?: string
|
|
43
|
+
smtp_pass?: string
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const CONFIG_PATH = join(homedir(), '.deepseek', 'lodestar.toml')
|
|
48
|
+
|
|
49
|
+
function expandTilde(v: string): string {
|
|
50
|
+
return v.replace(/^~(?=\/|$)/, homedir())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let cached: LodestarConfig | null = null
|
|
54
|
+
|
|
55
|
+
export function loadConfig(): LodestarConfig {
|
|
56
|
+
if (cached) return cached
|
|
57
|
+
|
|
58
|
+
const defaults: LodestarConfig = {
|
|
59
|
+
feishu: { app_id: '', app_secret: '' },
|
|
60
|
+
projects: { root: homedir() },
|
|
61
|
+
runtime: {
|
|
62
|
+
port: 7878,
|
|
63
|
+
api_token: 'your_token_here',
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!existsSync(CONFIG_PATH)) return defaults
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const raw = readFileSync(CONFIG_PATH, 'utf8')
|
|
71
|
+
const parsed = (Bun as any).TOML.parse(raw) as Record<string, any>
|
|
72
|
+
|
|
73
|
+
const config: LodestarConfig = { ...defaults }
|
|
74
|
+
|
|
75
|
+
if (parsed.feishu) {
|
|
76
|
+
config.feishu = {
|
|
77
|
+
app_id: parsed.feishu.app_id ?? '',
|
|
78
|
+
app_secret: parsed.feishu.app_secret ?? '',
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (parsed.projects?.root) {
|
|
83
|
+
config.projects.root = expandTilde(String(parsed.projects.root))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (parsed.runtime) {
|
|
87
|
+
if (parsed.runtime.port) config.runtime.port = Number(parsed.runtime.port)
|
|
88
|
+
if (parsed.runtime.api_token) config.runtime.api_token = String(parsed.runtime.api_token)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (parsed.email) {
|
|
92
|
+
config.email = {
|
|
93
|
+
imap_host: parsed.email.imap_host,
|
|
94
|
+
imap_port: parsed.email.imap_port ? Number(parsed.email.imap_port) : undefined,
|
|
95
|
+
imap_user: parsed.email.imap_user,
|
|
96
|
+
imap_pass: parsed.email.imap_pass,
|
|
97
|
+
smtp_host: parsed.email.smtp_host,
|
|
98
|
+
smtp_port: parsed.email.smtp_port ? Number(parsed.email.smtp_port) : undefined,
|
|
99
|
+
smtp_user: parsed.email.smtp_user,
|
|
100
|
+
smtp_pass: parsed.email.smtp_pass,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
cached = config
|
|
105
|
+
return config
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error('lodestar: failed to parse config:', err)
|
|
108
|
+
return defaults
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Populate process.env from config for backward compat with env-based code. */
|
|
113
|
+
export function populateEnv(): void {
|
|
114
|
+
const cfg = loadConfig()
|
|
115
|
+
|
|
116
|
+
if (cfg.feishu.app_id && !process.env.FEISHU_APP_ID) {
|
|
117
|
+
process.env.FEISHU_APP_ID = cfg.feishu.app_id
|
|
118
|
+
}
|
|
119
|
+
if (cfg.feishu.app_secret && !process.env.FEISHU_APP_SECRET) {
|
|
120
|
+
process.env.FEISHU_APP_SECRET = cfg.feishu.app_secret
|
|
121
|
+
}
|
|
122
|
+
if (cfg.projects.root && !process.env.FEISHU_PROJECTS_ROOT) {
|
|
123
|
+
process.env.FEISHU_PROJECTS_ROOT = cfg.projects.root
|
|
124
|
+
}
|
|
125
|
+
if (cfg.runtime.port && !process.env.DEEPSEEK_API_URL) {
|
|
126
|
+
process.env.DEEPSEEK_API_URL = `http://localhost:${cfg.runtime.port}`
|
|
127
|
+
}
|
|
128
|
+
if (cfg.runtime.api_token && !process.env.DEEPSEEK_API_TOKEN) {
|
|
129
|
+
process.env.DEEPSEEK_API_TOKEN = cfg.runtime.api_token
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getStateDir(): string {
|
|
134
|
+
return join(homedir(), '.deepseek', 'lodestar')
|
|
135
|
+
}
|