@baimingtao/lbs-agent 1.0.0 → 1.1.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/bin/lbs.js CHANGED
@@ -2,13 +2,16 @@
2
2
  /**
3
3
  * lbs — CLI wrapper for Leaving Blank Space Agent.
4
4
  *
5
- * 用户通过 npm install -g @lbs/agent 安装后,
6
- * 直接使用 `lbs` 命令启动 Agent CLI。
5
+ * 架构 (对标 Hermes):
6
+ * 包代码 (只读) ← npm install -g 安装的目录
7
+ * state root (可写) ← %LOCALAPPDATA%/lbs-agent 或 ~/.lbs-agent
8
+ * ├── venv/ ← Python 虚拟环境
9
+ * ├── config.yaml ← 用户配置
10
+ * ├── .env ← API keys
11
+ * ├── sessions/ ← 会话数据
12
+ * └── ...
7
13
  *
8
- * 工作流程:
9
- * 1. 检查 Python 虚拟环境是否已创建
10
- * 2. 如果没有,自动运行 init 脚本
11
- * 3. 用虚拟环境的 Python 执行 lbs.py
14
+ * venv 不再建在包目录里,升级/重装 npm 包不会丢失运行时状态。
12
15
  */
13
16
 
14
17
  const { spawn, spawnSync } = require('child_process');
@@ -16,92 +19,169 @@ const path = require('path');
16
19
  const fs = require('fs');
17
20
  const os = require('os');
18
21
 
19
- // 解析项目根目录(npm install 后 bin 的上级是包根目录)
20
22
  const PKG_ROOT = path.resolve(__dirname, '..');
21
- const PYTHON = process.env.LBS_PYTHON || (
22
- process.platform === 'win32'
23
- ? path.join(PKG_ROOT, 'venv', 'Scripts', 'python.exe')
24
- : path.join(PKG_ROOT, 'venv', 'bin', 'python')
25
- );
26
- const INIT_FLAG = path.join(PKG_ROOT, '.lbs-initialized');
23
+
24
+ // ── State Root 解析 (对标 Hermes get_hermes_home) ──────────────
25
+
26
+ function getStateRoot() {
27
+ // 1. 环境变量 LBS_HOME 优先
28
+ const envHome = process.env.LBS_HOME;
29
+ if (envHome) return path.resolve(envHome);
30
+
31
+ // 2. 平台默认: Windows → %LOCALAPPDATA%/lbs-agent, 其他 → ~/.lbs-agent
32
+ if (process.platform === 'win32') {
33
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
34
+ return path.join(localAppData, 'lbs-agent');
35
+ }
36
+ return path.join(os.homedir(), '.lbs-agent');
37
+ }
38
+
39
+ const STATE_ROOT = getStateRoot();
40
+ const VENV_DIR = path.join(STATE_ROOT, 'venv');
41
+ const VENV_PYTHON = process.platform === 'win32'
42
+ ? path.join(VENV_DIR, 'Scripts', 'python.exe')
43
+ : path.join(VENV_DIR, 'bin', 'python');
44
+ const INIT_FLAG = path.join(STATE_ROOT, '.lbs-initialized');
27
45
  const INIT_SCRIPT = path.join(PKG_ROOT, 'scripts', 'init.js');
28
46
 
29
- // 检查是否已初始化
47
+ // ── 颜色与图标 (对标 Hermes banner/colors) ─────────────────────
48
+
49
+ const C = {
50
+ reset: '\x1b[0m',
51
+ bold: '\x1b[1m',
52
+ dim: '\x1b[2m',
53
+ red: '\x1b[31m',
54
+ green: '\x1b[32m',
55
+ yellow: '\x1b[33m',
56
+ blue: '\x1b[34m',
57
+ magenta: '\x1b[35m',
58
+ cyan: '\x1b[36m',
59
+ gold: '\x1b[1;38;2;255;191;0m',
60
+ };
61
+
62
+ function supportsColor() {
63
+ if (process.env.NO_COLOR) return false;
64
+ if (process.env.TERM === 'dumb') return false;
65
+ return process.stdout.isTTY;
66
+ }
67
+
68
+ function paint(text, ...colors) {
69
+ if (!supportsColor()) return text;
70
+ return colors.join('') + text + C.reset;
71
+ }
72
+
73
+ // ── ASCII Logo ─────────────────────────────────────────────────
74
+
75
+ const LOGO = String.raw`
76
+ ${paint('██╗', C.gold)} ${paint('██╗', C.gold)}${paint('███████╗', C.cyan)}${paint('██████╗', C.cyan)} ${paint('██╗', C.gold)} ${paint('██╗', C.gold)}${paint('███████╗', C.cyan)}${paint('███████╗', C.cyan)}
77
+ ${paint('██║', C.gold)} ${paint('██║', C.gold)}${paint('██╔════╝', C.cyan)}${paint('██╔══██╗', C.cyan)}${paint('████╗', C.gold)} ${paint('████║', C.gold)}${paint('██╔════╝', C.cyan)}${paint('██╔════╝', C.cyan)}
78
+ ${paint('███████║', C.cyan)}${paint('█████╗', C.cyan)} ${paint('██████╔╝', C.cyan)}${paint('██╔████╔██║', C.gold)}${paint('█████╗', C.cyan)} ${paint('███████╗', C.gold)}${paint('█████╗', C.cyan)}
79
+ ${paint('██╔══██║', C.cyan)}${paint('██╔══╝', C.cyan)} ${paint('██╔══██╗', C.cyan)}${paint('██║╚██╔╝██║', C.gold)}${paint('██╔══╝', C.cyan)} ${paint('╚════██║', C.gold)}${paint('╚════╝', C.cyan)}
80
+ ${paint('██║', C.gold)} ${paint('██║', C.gold)}${paint('███████╗', C.cyan)}${paint('██║', C.gold)} ${paint('██║', C.gold)}${paint('██║', C.gold)} ${paint('╚═╝', C.gold)} ${paint('██║', C.gold)}${paint('███████╗', C.gold)}${paint('███████║', C.cyan)} ${paint('██║', C.gold)} ${paint('██║', C.gold)}${paint('╚██████╔╝', C.gold)}
81
+ ${paint('╚═╝', C.gold)} ${paint('╚═╝', C.gold)}${paint('╚══════╝', C.dim)}${paint('╚═╝', C.gold)} ${paint('╚═╝', C.gold)}${paint('╚═╝', C.gold)} ${paint('╚═╝', C.gold)}${paint('╚══════╝', C.dim)}${paint('╚══════╝', C.dim)} ${paint('╚═╝', C.gold)} ${paint('╚═╝', C.gold)} ${paint('╚═════╝', C.dim)}
82
+ `;
83
+
84
+ function printBanner() {
85
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf-8'));
86
+ console.log();
87
+ console.log(LOGO);
88
+ console.log(paint(` LBS Agent v${pkg.version}`, C.bold, C.gold));
89
+ console.log(paint(` Leaving Blank Space Agent — 多 Agent 编排框架`, C.dim));
90
+ console.log();
91
+ }
92
+
93
+ // ── 初始化检查 ─────────────────────────────────────────────────
94
+
30
95
  function isInitialized() {
31
- return fs.existsSync(INIT_FLAG) && fs.existsSync(PYTHON);
96
+ return fs.existsSync(INIT_FLAG) && fs.existsSync(VENV_PYTHON);
32
97
  }
33
98
 
34
- // 首次运行自动初始化
35
99
  function ensureInitialized() {
36
100
  if (isInitialized()) return true;
37
101
 
38
- console.log('🔧 首次运行,正在初始化 LBS Agent...');
39
- console.log(' 创建 Python 虚拟环境 + 安装依赖(约 1-2 分钟)\n');
102
+ console.log(paint(' ', C.gold) + paint('首次运行,正在初始化 LBS Agent...', C.bold));
103
+ console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.cyan));
104
+ console.log(paint(' 创建 Python 虚拟环境 + 安装依赖(约 1-2 分钟)', C.dim));
105
+ console.log();
40
106
 
41
107
  if (!fs.existsSync(INIT_SCRIPT)) {
42
- console.error(' 初始化脚本不存在: ' + INIT_SCRIPT);
43
- console.error(' 请手动运行: python -m venv venv && venv/bin/pip install -r requirements.txt');
108
+ console.error(paint(' ', C.red) + '初始化脚本不存在: ' + INIT_SCRIPT);
44
109
  return false;
45
110
  }
46
111
 
47
112
  const result = spawnSync('node', [INIT_SCRIPT], {
48
113
  cwd: PKG_ROOT,
49
114
  stdio: 'inherit',
50
- env: { ...process.env, LBS_PKG_ROOT: PKG_ROOT },
115
+ env: { ...process.env, LBS_STATE_ROOT: STATE_ROOT, LBS_PKG_ROOT: PKG_ROOT },
51
116
  });
52
117
 
53
118
  if (result.status !== 0) {
54
- console.error('\n 初始化失败。请手动运行:');
55
- console.error(' python -m venv venv');
56
- console.error(' ' + (process.platform === 'win32' ? 'venv\\Scripts\\pip' : 'venv/bin/pip') + ' install -r requirements.txt');
119
+ console.error(paint('\n ', C.red) + '初始化失败');
57
120
  return false;
58
121
  }
59
122
 
60
- console.log('\n 初始化完成!\n');
123
+ console.log(paint('\n ', C.green) + paint('初始化完成!', C.bold, C.green));
124
+ console.log();
61
125
  return true;
62
126
  }
63
127
 
64
- // 主逻辑
128
+ // ── 主逻辑 ─────────────────────────────────────────────────────
129
+
65
130
  const args = process.argv.slice(2);
131
+ const subcmd = args[0];
66
132
 
67
- // 特殊命令:不需要初始化
68
- if (args[0] === 'init' || args[0] === '--init') {
133
+ // 不需要初始化的命令
134
+ if (subcmd === 'init' || subcmd === '--init') {
135
+ printBanner();
69
136
  if (fs.existsSync(INIT_SCRIPT)) {
70
- const result = spawnSync('node', [INIT_SCRIPT], { cwd: PKG_ROOT, stdio: 'inherit' });
137
+ const result = spawnSync('node', [INIT_SCRIPT], {
138
+ cwd: PKG_ROOT,
139
+ stdio: 'inherit',
140
+ env: { ...process.env, LBS_STATE_ROOT: STATE_ROOT, LBS_PKG_ROOT: PKG_ROOT },
141
+ });
71
142
  process.exit(result.status || 0);
72
143
  }
73
- console.error('初始化脚本不存在');
144
+ console.error(paint('✖ ', C.red) + '初始化脚本不存在');
74
145
  process.exit(1);
75
146
  }
76
147
 
77
- if (args[0] === 'doctor' || args[0] === '--doctor') {
78
- // doctor 不需要完整初始化,只需 Python 存在
79
- const sysPython = process.env.LBS_PYTHON || (
80
- process.platform === 'win32' ? 'python' : 'python3'
81
- );
148
+ if (subcmd === 'version' || subcmd === '--version' || subcmd === '-v') {
149
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf-8'));
150
+ console.log(`lbs ${pkg.version}`);
151
+ process.exit(0);
152
+ }
153
+
154
+ if (subcmd === 'doctor' || subcmd === '--doctor') {
155
+ // doctor 用系统 Python,不需要完整初始化
156
+ const sysPython = process.env.LBS_PYTHON || (process.platform === 'win32' ? 'python' : 'python3');
82
157
  const result = spawnSync(sysPython, [path.join(PKG_ROOT, 'lbs.py'), 'doctor'], {
83
- cwd: PKG_ROOT,
158
+ cwd: process.cwd(),
84
159
  stdio: 'inherit',
160
+ env: { ...process.env, LBS_HOME: STATE_ROOT },
85
161
  });
86
162
  process.exit(result.status || 0);
87
163
  }
88
164
 
89
- if (args[0] === 'version' || args[0] === '--version' || args[0] === '-v') {
90
- const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf-8'));
91
- console.log(`lbs ${pkg.version}`);
92
- process.exit(0);
165
+ if (subcmd === 'setup' || subcmd === '--setup') {
166
+ // setup 需要初始化
167
+ if (!ensureInitialized()) process.exit(1);
168
+ const result = spawnSync(VENV_PYTHON, [path.join(PKG_ROOT, 'lbs.py'), 'setup', ...args.slice(1)], {
169
+ cwd: process.cwd(),
170
+ stdio: 'inherit',
171
+ env: { ...process.env, LBS_HOME: STATE_ROOT },
172
+ });
173
+ process.exit(result.status || 0);
93
174
  }
94
175
 
95
- // 确保初始化
176
+ // 默认:确保初始化后用 venv Python 执行
96
177
  if (!ensureInitialized()) {
97
178
  process.exit(1);
98
179
  }
99
180
 
100
- // 用虚拟环境 Python 执行
101
- const child = spawn(PYTHON, [path.join(PKG_ROOT, 'lbs.py'), ...args], {
181
+ const child = spawn(VENV_PYTHON, [path.join(PKG_ROOT, 'lbs.py'), ...args], {
102
182
  cwd: process.cwd(),
103
183
  stdio: 'inherit',
104
- env: process.env,
184
+ env: { ...process.env, LBS_HOME: STATE_ROOT },
105
185
  });
106
186
 
107
187
  child.on('close', (code) => {
@@ -2,20 +2,38 @@
2
2
 
3
3
  提供统一的 resolve_state_root() 函数,
4
4
  被多个模块用于定位 session/memory/cron 等运行时数据的存储位置。
5
+
6
+ 架构对标 Hermes:
7
+ 包代码 (只读) ← pip/npm 安装的目录
8
+ state root (可写) ← %LOCALAPPDATA%/lbs-agent (Windows) 或 ~/.lbs-agent (其他)
9
+
10
+ 这样 venv/config/sessions 全在 state root,
11
+ 升级/重装包不会丢失运行时状态。
5
12
  """
6
13
 
7
14
  from __future__ import annotations
8
15
 
9
16
  import os
17
+ import sys
10
18
  from pathlib import Path
11
19
 
12
20
 
21
+ def _platform_default_state_root() -> Path:
22
+ """返回平台默认的 state root 路径 (对标 Hermes _get_platform_default_hermes_home)。"""
23
+ if sys.platform == "win32":
24
+ local_appdata = os.environ.get("LOCALAPPDATA", "").strip()
25
+ base = Path(local_appdata) if local_appdata else Path.home() / "AppData" / "Local"
26
+ return base / "lbs-agent"
27
+ return Path.home() / ".lbs-agent"
28
+
29
+
13
30
  def resolve_state_root(project_root: str | Path | None = None) -> Path:
14
31
  """解析状态根目录。优先级:
15
32
 
16
33
  1. LBS_HOME 环境变量
17
34
  2. project_root / .lbs-agent(如果传了 project_root)
18
- 3. 默认:~/.lbs-agent
35
+ 3. 平台默认:Windows → %LOCALAPPDATA%/lbs-agent, 其他 → ~/.lbs-agent
36
+
19
37
  返回 Path 对象,调用者可直接使用 .exists() / / 等 Path 操作。
20
38
  """
21
39
  env = os.environ.get("LBS_HOME")
@@ -25,4 +43,4 @@ def resolve_state_root(project_root: str | Path | None = None) -> Path:
25
43
  if project_root:
26
44
  return Path(str(project_root)).resolve() / ".lbs-agent"
27
45
 
28
- return Path.home() / ".lbs-agent"
46
+ return _platform_default_state_root()
@@ -0,0 +1,519 @@
1
+ """LBS Agent 交互式配置向导 (对标 Hermes setup wizard)。
2
+
3
+ 用法:
4
+ lbs setup — 完整向导
5
+ lbs setup model — 只配置模型/供应商
6
+ lbs setup agent — 只配置 Agent 参数
7
+ lbs setup keys — 只配置 API Keys
8
+
9
+ 配置写入 state_root (LBS_HOME),不修改包目录里的出厂 models.yaml/agents.yaml。
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import Any, Optional
18
+
19
+ try:
20
+ import yaml
21
+ except ImportError:
22
+ yaml = None
23
+
24
+
25
+ # ═══════════════════════════════════════════════════════════════
26
+ # State Root 解析 (不 import config.state_root, 架构边界要求)
27
+ # ═══════════════════════════════════════════════════════════════
28
+
29
+ def _resolve_state_root() -> Path:
30
+ """解析状态根目录。优先级: LBS_HOME 环境变量 → 平台默认。"""
31
+ env = os.environ.get("LBS_HOME")
32
+ if env:
33
+ return Path(env).expanduser().resolve()
34
+ if sys.platform == "win32":
35
+ local_appdata = os.environ.get("LOCALAPPDATA", "").strip()
36
+ base = Path(local_appdata) if local_appdata else Path.home() / "AppData" / "Local"
37
+ return base / "lbs-agent"
38
+ return Path.home() / ".lbs-agent"
39
+
40
+
41
+ # ═══════════════════════════════════════════════════════════════
42
+ # 颜色与 UI 工具 (对标 Hermes colors.py)
43
+ # ═══════════════════════════════════════════════════════════════
44
+
45
+ class _C:
46
+ """ANSI 颜色码。"""
47
+ RESET = "\033[0m"
48
+ BOLD = "\033[1m"
49
+ DIM = "\033[2m"
50
+ RED = "\033[31m"
51
+ GREEN = "\033[32m"
52
+ YELLOW = "\033[33m"
53
+ BLUE = "\033[34m"
54
+ MAGENTA = "\033[35m"
55
+ CYAN = "\033[36m"
56
+ GOLD = "\033[1;38;2;255;191;0m"
57
+
58
+
59
+ def _use_color() -> bool:
60
+ if os.environ.get("NO_COLOR"):
61
+ return False
62
+ if os.environ.get("TERM") == "dumb":
63
+ return False
64
+ return sys.stdout.isatty()
65
+
66
+
67
+ def _paint(text: str, *codes: str) -> str:
68
+ if not _use_color():
69
+ return text
70
+ return "".join(codes) + text + _C.RESET
71
+
72
+
73
+ def _header(title: str) -> None:
74
+ """打印章节标题 (带边框)。"""
75
+ print()
76
+ print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.MAGENTA))
77
+ print(_paint(" │ ", _C.MAGENTA) + _paint(f"⚙ LBS Setup — {title}", _C.BOLD, _C.GOLD)
78
+ + _paint(" " * max(0, 47 - len(title) - 14) + "│", _C.MAGENTA))
79
+ print(_paint(" └─────────────────────────────────────────────────────────┘", _C.MAGENTA))
80
+ print()
81
+
82
+
83
+ def _info(msg: str) -> None:
84
+ print(_paint(" ℹ ", _C.BLUE) + msg)
85
+
86
+
87
+ def _success(msg: str) -> None:
88
+ print(_paint(" ✓ ", _C.GREEN) + _paint(msg, _C.GREEN))
89
+
90
+
91
+ def _warn(msg: str) -> None:
92
+ print(_paint(" ⚠ ", _C.YELLOW) + _paint(msg, _C.YELLOW))
93
+
94
+
95
+ def _error(msg: str) -> None:
96
+ print(_paint(" ✖ ", _C.RED) + _paint(msg, _C.RED))
97
+
98
+
99
+ def _prompt(label: str, default: str = "") -> str:
100
+ """带默认值的输入提示。"""
101
+ suffix = f" [{default}]" if default else ""
102
+ try:
103
+ val = input(_paint(f" ▸ {label}{suffix}: ", _C.CYAN)).strip()
104
+ except (EOFError, KeyboardInterrupt):
105
+ print()
106
+ return default
107
+ return val or default
108
+
109
+
110
+ def _prompt_choice(label: str, options: list[str], default: str = "") -> str:
111
+ """带选项列表的输入提示。"""
112
+ print(_paint(f" ▸ {label}", _C.CYAN))
113
+ for i, opt in enumerate(options, 1):
114
+ marker = _paint("→", _C.GREEN) if opt == default else " "
115
+ print(f" {marker} {i}. {opt}")
116
+ val = _prompt("选择编号或直接输入", default)
117
+ if val.isdigit():
118
+ idx = int(val) - 1
119
+ if 0 <= idx < len(options):
120
+ return options[idx]
121
+ return val
122
+
123
+
124
+ def _prompt_password(label: str) -> str:
125
+ """密码输入(不回显)。"""
126
+ import getpass
127
+ try:
128
+ val = getpass.getpass(_paint(f" ▸ {label}: ", _C.CYAN))
129
+ except (EOFError, KeyboardInterrupt):
130
+ print()
131
+ return ""
132
+ return val.strip()
133
+
134
+
135
+ def _prompt_yes_no(label: str, default: bool = True) -> bool:
136
+ """是/否选择。"""
137
+ suffix = "Y/n" if default else "y/N"
138
+ val = _prompt(f"{label} ({suffix})", "y" if default else "n")
139
+ return val.lower().startswith("y")
140
+
141
+
142
+ # ═══════════════════════════════════════════════════════════════
143
+ # 配置文件读写
144
+ # ═══════════════════════════════════════════════════════════════
145
+
146
+ def _state_root() -> Path:
147
+ return _resolve_state_root()
148
+
149
+
150
+ def _models_yaml_path() -> Path:
151
+ """用户级 models.yaml 覆盖文件。"""
152
+ return _state_root() / "models.override.yaml"
153
+
154
+
155
+ def _agents_yaml_path() -> Path:
156
+ """用户级 agents.yaml 覆盖文件。"""
157
+ return _state_root() / "agents.override.yaml"
158
+
159
+
160
+ def _env_path() -> Path:
161
+ """.env 文件,存 API keys。"""
162
+ return _state_root() / ".env"
163
+
164
+
165
+ def _load_yaml(path: Path) -> dict[str, Any]:
166
+ if not yaml or not path.exists():
167
+ return {}
168
+ try:
169
+ with open(path, "r", encoding="utf-8") as f:
170
+ return yaml.safe_load(f) or {}
171
+ except Exception:
172
+ return {}
173
+
174
+
175
+ def _save_yaml(path: Path, data: dict[str, Any]) -> None:
176
+ if not yaml:
177
+ _error("PyYAML 未安装,无法保存配置")
178
+ return
179
+ path.parent.mkdir(parents=True, exist_ok=True)
180
+ with open(path, "w", encoding="utf-8") as f:
181
+ yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
182
+
183
+
184
+ def _load_env() -> dict[str, str]:
185
+ env: dict[str, str] = {}
186
+ path = _env_path()
187
+ if path.exists():
188
+ for line in path.read_text(encoding="utf-8").splitlines():
189
+ line = line.strip()
190
+ if line and not line.startswith("#") and "=" in line:
191
+ key, _, val = line.partition("=")
192
+ env[key.strip()] = val.strip()
193
+ return env
194
+
195
+
196
+ def _save_env(env: dict[str, str]) -> None:
197
+ path = _env_path()
198
+ path.parent.mkdir(parents=True, exist_ok=True)
199
+ lines = [f"{k}={v}" for k, v in sorted(env.items())]
200
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
201
+
202
+
203
+ # ═══════════════════════════════════════════════════════════════
204
+ # 内置供应商目录
205
+ # ═══════════════════════════════════════════════════════════════
206
+
207
+ _PROVIDERS = {
208
+ "openai": {
209
+ "display": "OpenAI",
210
+ "env_key": "OPENAI_API_KEY",
211
+ "base_url": "https://api.openai.com/v1",
212
+ "api_mode": "chat_completions",
213
+ "models": ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-4.1", "o3-mini"],
214
+ },
215
+ "anthropic": {
216
+ "display": "Anthropic",
217
+ "env_key": "ANTHROPIC_API_KEY",
218
+ "base_url": "https://api.anthropic.com",
219
+ "api_mode": "anthropic_messages",
220
+ "models": ["claude-opus-4.6", "claude-sonnet-4.6", "claude-sonnet-4.5", "claude-haiku-4.5"],
221
+ },
222
+ "deepseek": {
223
+ "display": "DeepSeek",
224
+ "env_key": "DEEPSEEK_API_KEY",
225
+ "base_url": "https://api.deepseek.com/v1",
226
+ "api_mode": "chat_completions",
227
+ "models": ["deepseek-v4-pro", "deepseek-v4-flash", "deepseek-chat", "deepseek-reasoner"],
228
+ },
229
+ "openrouter": {
230
+ "display": "OpenRouter (聚合 300+ 模型)",
231
+ "env_key": "OPENROUTER_API_KEY",
232
+ "base_url": "https://openrouter.ai/api/v1",
233
+ "api_mode": "chat_completions",
234
+ "models": ["openai/gpt-5.5", "anthropic/claude-sonnet-4.6", "google/gemini-3-pro-preview"],
235
+ },
236
+ "qwen": {
237
+ "display": "通义千问 (阿里云)",
238
+ "env_key": "QWEN_API_KEY",
239
+ "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
240
+ "api_mode": "chat_completions",
241
+ "models": ["qwen3.7-max", "qwen3.6-plus", "qwen3.5-plus", "qwen-max", "qwen-plus"],
242
+ },
243
+ "zai": {
244
+ "display": "智谱 AI (GLM)",
245
+ "env_key": "ZAI_API_KEY",
246
+ "base_url": "https://open.bigmodel.cn/api/paas/v4",
247
+ "api_mode": "chat_completions",
248
+ "models": ["glm-5.2", "glm-5.1", "glm-5", "glm-4.7", "glm-4-plus", "glm-4-flash"],
249
+ },
250
+ "gemini": {
251
+ "display": "Google Gemini",
252
+ "env_key": "GEMINI_API_KEY",
253
+ "base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
254
+ "api_mode": "chat_completions",
255
+ "models": ["gemini-3-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro"],
256
+ },
257
+ "kimi": {
258
+ "display": "Kimi (月之暗面)",
259
+ "env_key": "KIMI_API_KEY",
260
+ "base_url": "https://api.moonshot.cn/v1",
261
+ "api_mode": "chat_completions",
262
+ "models": ["kimi-k2.6", "kimi-k2.5", "moonshot-v1-128k"],
263
+ },
264
+ "custom": {
265
+ "display": "自定义供应商 (兼容 OpenAI/Anthropic API)",
266
+ "env_key": "",
267
+ "base_url": "",
268
+ "api_mode": "chat_completions",
269
+ "models": [],
270
+ },
271
+ }
272
+
273
+
274
+ # ═══════════════════════════════════════════════════════════════
275
+ # Step 1: 模型 & 供应商配置
276
+ # ═══════════════════════════════════════════════════════════════
277
+
278
+ def setup_model_provider() -> None:
279
+ """配置推理供应商和默认模型。"""
280
+ _header("模型 & 供应商")
281
+
282
+ provider_keys = list(_PROVIDERS.keys())
283
+ provider_displays = [f"{_PROVIDERS[k]['display']}" for k in provider_keys]
284
+
285
+ _info("选择你的 LLM 供应商(决定 API 端点和认证方式)")
286
+ _info(f" 文档: https://lbs-agent.nousresearch.com/docs/providers")
287
+ print()
288
+
289
+ choice = _prompt_choice("选择供应商", provider_displays, default=provider_displays[0])
290
+ idx = provider_displays.index(choice) if choice in provider_displays else 0
291
+ provider_key = provider_keys[idx]
292
+ provider = _PROVIDERS[provider_key]
293
+
294
+ print()
295
+ _info(f"已选: {provider['display']}")
296
+
297
+ # 输入 API Key
298
+ env = _load_env()
299
+ existing_key = env.get(provider["env_key"], "") if provider["env_key"] else ""
300
+ if provider["env_key"]:
301
+ print()
302
+ if existing_key:
303
+ _info(f"已有 API Key: {existing_key[:8]}...{existing_key[-4:]}")
304
+ if not _prompt_yes_no("是否更换 Key?", default=False):
305
+ pass
306
+ else:
307
+ key = _prompt_password(f"输入 {provider['display']} API Key")
308
+ if key:
309
+ env[provider["env_key"]] = key
310
+ else:
311
+ key = _prompt_password(f"输入 {provider['display']} API Key")
312
+ if key:
313
+ env[provider["env_key"]] = key
314
+ _success("API Key 已保存")
315
+
316
+ # 自定义供应商需要额外信息
317
+ if provider_key == "custom":
318
+ print()
319
+ custom_name = _prompt("供应商名称 (如 myapi)", "custom")
320
+ custom_base = _prompt("API Base URL", "")
321
+ custom_mode = _prompt_choice("API 模式", ["chat_completions", "anthropic_messages"], "chat_completions")
322
+ custom_key_env = f"{custom_name.upper()}_API_KEY"
323
+ key = _prompt_password(f"输入 API Key")
324
+ if key:
325
+ env[custom_key_env] = key
326
+
327
+ provider = {
328
+ "display": custom_name,
329
+ "env_key": custom_key_env,
330
+ "base_url": custom_base,
331
+ "api_mode": custom_mode,
332
+ "models": [],
333
+ }
334
+ provider_key = custom_name
335
+
336
+ # 选择模型
337
+ print()
338
+ models = provider["models"]
339
+ if models:
340
+ _info(f"可选模型:")
341
+ model = _prompt_choice("选择默认模型", models, default=models[0])
342
+ else:
343
+ model = _prompt("输入模型名称", "")
344
+
345
+ # 写入 override YAML
346
+ override = _load_yaml(_models_yaml_path())
347
+
348
+ # 写入 default
349
+ override["default"] = {
350
+ "provider": provider_key,
351
+ "model": model,
352
+ }
353
+
354
+ # 写入 builtin/custom 供应商
355
+ if provider_key == "custom" and provider_key not in _PROVIDERS:
356
+ custom_section = override.setdefault("custom", {})
357
+ custom_section[provider_key] = {
358
+ "display_name": provider["display"],
359
+ "base_url": provider["base_url"],
360
+ "api_mode": provider["api_mode"],
361
+ "api_key": f"${{{provider['env_key']}}}" if provider["env_key"] else "",
362
+ "models": [model] if model else [],
363
+ }
364
+ else:
365
+ builtin_section = override.setdefault("builtin", {})
366
+ if provider_key not in builtin_section:
367
+ builtin_section[provider_key] = {
368
+ "api_key": f"${{{provider['env_key']}}}" if provider["env_key"] else "",
369
+ "base_url": provider["base_url"],
370
+ "api_mode": provider["api_mode"],
371
+ }
372
+
373
+ _save_yaml(_models_yaml_path(), override)
374
+ _save_env(env)
375
+
376
+ print()
377
+ _success(f"模型配置已保存: {_models_yaml_path()}")
378
+ _success(f"默认模型: {provider_key} / {model}")
379
+
380
+
381
+ # ═══════════════════════════════════════════════════════════════
382
+ # Step 2: Agent 参数
383
+ # ═══════════════════════════════════════════════════════════════
384
+
385
+ def setup_agent_settings() -> None:
386
+ """配置 Agent 运行参数。"""
387
+ _header("Agent 设置")
388
+
389
+ override = _load_yaml(_agents_yaml_path())
390
+ global_cfg = override.get("global", {})
391
+
392
+ _info("配置 Agent 全局参数")
393
+ print()
394
+
395
+ max_concurrent = int(_prompt("最大并发 Agent 数", str(global_cfg.get("max_concurrent", 3))))
396
+ max_depth = int(_prompt("最大嵌套深度", str(global_cfg.get("max_spawn_depth", 1))))
397
+ default_timeout = int(_prompt("默认超时 (秒)", str(global_cfg.get("default_timeout", 600))))
398
+ auto_approve = _prompt_yes_no("自动批准危险操作 (不推荐)", default=global_cfg.get("auto_approve_dangerous", False))
399
+
400
+ override["global"] = {
401
+ "max_concurrent": max_concurrent,
402
+ "max_spawn_depth": max_depth,
403
+ "default_timeout": default_timeout,
404
+ "auto_approve_dangerous": auto_approve,
405
+ }
406
+
407
+ _save_yaml(_agents_yaml_path(), override)
408
+ print()
409
+ _success(f"Agent 配置已保存: {_agents_yaml_path()}")
410
+
411
+
412
+ # ═══════════════════════════════════════════════════════════════
413
+ # Step 3: API Keys 管理
414
+ # ═══════════════════════════════════════════════════════════════
415
+
416
+ def setup_keys() -> None:
417
+ """配置所有 API Keys。"""
418
+ _header("API Keys")
419
+
420
+ env = _load_env()
421
+
422
+ known_keys = [
423
+ ("OPENAI_API_KEY", "OpenAI"),
424
+ ("ANTHROPIC_API_KEY", "Anthropic"),
425
+ ("DEEPSEEK_API_KEY", "DeepSeek"),
426
+ ("OPENROUTER_API_KEY", "OpenRouter"),
427
+ ("QWEN_API_KEY", "通义千问"),
428
+ ("ZAI_API_KEY", "智谱 AI"),
429
+ ("GEMINI_API_KEY", "Google Gemini"),
430
+ ("KIMI_API_KEY", "Kimi"),
431
+ ]
432
+
433
+ for env_key, display_name in known_keys:
434
+ existing = env.get(env_key, "")
435
+ if existing:
436
+ _info(f"{display_name}: {existing[:8]}...{existing[-4:]}")
437
+ if not _prompt_yes_no(f" 更换?", default=False):
438
+ continue
439
+ else:
440
+ if not _prompt_yes_no(f"配置 {display_name} Key?", default=False):
441
+ continue
442
+ key = _prompt_password(f"输入 {display_name} API Key")
443
+ if key:
444
+ env[env_key] = key
445
+ _success(f"{display_name} Key 已保存")
446
+ print()
447
+
448
+ _save_env(env)
449
+ _success(f"Keys 已保存到: {_env_path()}")
450
+
451
+
452
+ # ═══════════════════════════════════════════════════════════════
453
+ # 主入口
454
+ # ═══════════════════════════════════════════════════════════════
455
+
456
+ SECTIONS = [
457
+ ("model", "模型 & 供应商", setup_model_provider),
458
+ ("agent", "Agent 设置", setup_agent_settings),
459
+ ("keys", "API Keys", setup_keys),
460
+ ]
461
+
462
+
463
+ def run_setup(section: str | None = None) -> int:
464
+ """运行配置向导。"""
465
+ # 确保 state root 存在
466
+ root = _state_root()
467
+ root.mkdir(parents=True, exist_ok=True)
468
+
469
+ print()
470
+ print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.GOLD))
471
+ print(_paint(" │ ", _C.GOLD) + _paint("⚙ LBS Agent Setup Wizard", _C.BOLD, _C.GOLD)
472
+ + _paint(" │", _C.GOLD))
473
+ print(_paint(" └─────────────────────────────────────────────────────────┘", _C.GOLD))
474
+ print()
475
+ _info(f"配置目录: {root}")
476
+ print()
477
+
478
+ if section:
479
+ # 只运行指定 section
480
+ for key, label, func in SECTIONS:
481
+ if key == section:
482
+ func()
483
+ _print_done()
484
+ return 0
485
+ _error(f"未知配置项: {section}")
486
+ _info(f"可用项: {', '.join(k for k, _, _ in SECTIONS)}")
487
+ return 1
488
+
489
+ # 完整向导
490
+ for key, label, func in SECTIONS:
491
+ func()
492
+
493
+ _print_done()
494
+ return 0
495
+
496
+
497
+ def _print_done() -> None:
498
+ print()
499
+ print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.GREEN))
500
+ print(_paint(" │ ", _C.GREEN) + _paint("✅ 配置完成!", _C.BOLD, _C.GREEN)
501
+ + _paint(" │", _C.GREEN))
502
+ print(_paint(" │ │", _C.GREEN))
503
+ print(_paint(" │ ", _C.GREEN) + _paint("配置文件:", _C.BOLD) + paint_path(_models_yaml_path()) + _paint(" │", _C.GREEN))
504
+ print(_paint(" │ ", _C.GREEN) + _paint("密钥文件:", _C.BOLD) + paint_path(_env_path()) + _paint(" │", _C.GREEN))
505
+ print(_paint(" │ │", _C.GREEN))
506
+ print(_paint(" │ ", _C.GREEN) + _paint("现在可以运行:", _C.BOLD) + paint_cmd(" lbs status") + _paint(" │", _C.GREEN))
507
+ print(_paint(" └─────────────────────────────────────────────────────────┘", _C.GREEN))
508
+ print()
509
+
510
+
511
+ def paint_path(p: Path) -> str:
512
+ s = str(p)
513
+ if len(s) > 40:
514
+ s = "..." + s[-37:]
515
+ return _paint(s, _C.CYAN)
516
+
517
+
518
+ def paint_cmd(cmd: str) -> str:
519
+ return _paint(cmd, _C.CYAN, _C.BOLD)
@@ -50,6 +50,7 @@ from interfaces.cli._impl.commands.cron import (
50
50
  )
51
51
  from interfaces.cli._impl.commands.soul import cmd_soul_list, cmd_soul_switch, cmd_soul_show
52
52
  from interfaces.cli._impl.commands.smoke_test import main as smoke_main
53
+ from interfaces.cli._impl.commands.setup import run_setup
53
54
 
54
55
 
55
56
  # --- 命令注册表 ---
@@ -657,6 +658,12 @@ def _cmd_doctor_handler(config: Any) -> int:
657
658
  return 1 if report.get("failed", 0) else 0
658
659
 
659
660
 
661
+ def _cmd_setup_handler(_config: Any) -> int:
662
+ """交互式配置向导。"""
663
+ section = sys.argv[2] if len(sys.argv) > 2 else None
664
+ return run_setup(section)
665
+
666
+
660
667
  # --- 注册表实例 ---
661
668
 
662
669
  _REGISTRY = CommandRegistry()
@@ -689,6 +696,7 @@ def _build_registry() -> None:
689
696
  _REGISTRY.register("gateway", _cmd_gateway_handler, help="网关操作")
690
697
  _REGISTRY.register("doctor", _cmd_doctor_handler, help="诊断配置问题")
691
698
  _REGISTRY.register("smoke-test", lambda _config: smoke_main(), aliases=["smoke"], help="快速冒烟测试")
699
+ _REGISTRY.register("setup", _cmd_setup_handler, help="交互式配置向导(model/agent/keys)")
692
700
 
693
701
 
694
702
  # --- 入口 ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baimingtao/lbs-agent",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/scripts/init.js CHANGED
@@ -2,15 +2,13 @@
2
2
  /**
3
3
  * LBS Agent 初始化脚本 — 跨平台 (Windows / macOS / Linux)
4
4
  *
5
- * 功能:
6
- * 1. 检测系统 Python (3.10+)
7
- * 2. 创建虚拟环境 venv/
8
- * 3. 安装 requirements.txt 依赖
9
- * 4. 创建 .lbs-initialized 标记文件
5
+ * 架构 (对标 Hermes):
6
+ * venv 建在 state root (%LOCALAPPDATA%/lbs-agent/venv), 不在包目录
7
+ * config/sessions 也在 state root, npm 升级不丢数据
10
8
  *
11
- * 用法:
12
- * node scripts/init.js # 正常初始化
13
- * LBS_PKG_ROOT=/path node scripts/init.js # 指定项目根目录
9
+ * 环境变量:
10
+ * LBS_STATE_ROOT ← bin/lbs.js 传入的 state root 路径
11
+ * LBS_PKG_ROOT bin/lbs.js 传入的包根路径
14
12
  */
15
13
 
16
14
  const { spawnSync } = require('child_process');
@@ -19,31 +17,48 @@ const fs = require('fs');
19
17
  const os = require('os');
20
18
 
21
19
  const PKG_ROOT = process.env.LBS_PKG_ROOT || path.resolve(__dirname, '..');
22
- const VENV_DIR = path.join(PKG_ROOT, 'venv');
23
- const INIT_FLAG = path.join(PKG_ROOT, '.lbs-initialized');
20
+ const STATE_ROOT = process.env.LBS_STATE_ROOT || path.join(os.homedir(), '.lbs-agent');
21
+ const VENV_DIR = path.join(STATE_ROOT, 'venv');
22
+ const INIT_FLAG = path.join(STATE_ROOT, '.lbs-initialized');
24
23
  const REQUIREMENTS = path.join(PKG_ROOT, 'requirements.txt');
25
24
 
26
25
  const isWin = process.platform === 'win32';
27
26
  const PYTHON = isWin ? path.join(VENV_DIR, 'Scripts', 'python.exe') : path.join(VENV_DIR, 'bin', 'python');
28
27
  const PIP = isWin ? path.join(VENV_DIR, 'Scripts', 'pip.exe') : path.join(VENV_DIR, 'bin', 'pip');
29
28
 
30
- function log(msg) { console.log(msg); }
31
- function err(msg) { console.error(msg); }
29
+ // ── 颜色 ───────────────────────────────────────────────────────
30
+
31
+ const C = {
32
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
33
+ red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
34
+ blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m',
35
+ gold: '\x1b[1;38;2;255;191;0m',
36
+ };
37
+
38
+ function paint(text, ...colors) {
39
+ if (process.env.NO_COLOR || process.env.TERM === 'dumb' || !process.stdout.isTTY) return text;
40
+ return colors.join('') + text + C.reset;
41
+ }
42
+
43
+ // ── 工具函数 ───────────────────────────────────────────────────
32
44
 
33
45
  function run(cmd, args, opts = {}) {
34
- const result = spawnSync(cmd, args, {
46
+ return spawnSync(cmd, args, {
35
47
  stdio: opts.silent ? 'pipe' : 'inherit',
36
48
  encoding: 'utf-8',
37
49
  shell: isWin,
38
50
  ...opts,
39
51
  });
40
- return result;
41
52
  }
42
53
 
43
- // ── Step 1: 检测系统 Python ──────────────────────────
54
+ function ensureDir(dir) {
55
+ fs.mkdirSync(dir, { recursive: true });
56
+ }
57
+
58
+ // ── Step 1: 检测系统 Python ────────────────────────────────────
44
59
 
45
60
  function findSystemPython() {
46
- log('🔍 检测系统 Python...');
61
+ console.log(paint(' 🔍 ', C.gold) + '检测系统 Python...');
47
62
 
48
63
  const candidates = isWin
49
64
  ? ['python', 'python3', 'py -3']
@@ -53,102 +68,138 @@ function findSystemPython() {
53
68
  const result = run(cmd, ['--version'], { silent: true, shell: isWin });
54
69
  if (result.status === 0) {
55
70
  const version = (result.stdout || result.stderr || '').trim();
56
- log(`${cmd}: ${version}`);
71
+ console.log(paint('', C.green) + `${cmd}: ${version}`);
57
72
 
58
- // 检查版本 >= 3.10
59
73
  const match = version.match(/(\d+)\.(\d+)/);
60
74
  if (match) {
61
75
  const major = parseInt(match[1]);
62
76
  const minor = parseInt(match[2]);
63
- if (major >= 3 && minor >= 10) {
64
- return cmd;
65
- }
77
+ if (major >= 3 && minor >= 10) return cmd;
66
78
  }
67
- log(` ⚠ 版本低于 3.10,继续寻找...`);
79
+ console.log(paint('', C.yellow) + '版本低于 3.10,继续寻找...');
68
80
  }
69
81
  }
70
82
 
71
- err('\n 未找到 Python 3.10+');
72
- err(' 请安装 Python:');
73
- err(' Windows: https://www.python.org/downloads/');
74
- err(' macOS: brew install python@3.12');
75
- err(' Linux: sudo apt install python3 python3-venv');
83
+ console.error(paint('\n ', C.red) + '未找到 Python 3.10+');
84
+ console.error(paint(' ', C.dim) + 'Windows: https://www.python.org/downloads/');
85
+ console.error(paint(' ', C.dim) + 'macOS: brew install python@3.12');
86
+ console.error(paint(' ', C.dim) + 'Linux: sudo apt install python3 python3-venv');
76
87
  return null;
77
88
  }
78
89
 
79
- // ── Step 2: 创建虚拟环境 ─────────────────────────────
90
+ // ── Step 2: 创建 state root + venv ─────────────────────────────
80
91
 
81
92
  function createVenv(sysPython) {
82
93
  if (fs.existsSync(PYTHON)) {
83
- log('✓ 虚拟环境已存在,跳过创建');
94
+ console.log(paint(' ', C.green) + '虚拟环境已存在,跳过创建');
84
95
  return true;
85
96
  }
86
97
 
87
- log('📦 创建 Python 虚拟环境...');
98
+ // 确保 state root 存在
99
+ ensureDir(STATE_ROOT);
100
+ console.log(paint(' 📦 ', C.gold) + `创建 Python 虚拟环境 → ${paint(STATE_ROOT + '/venv', C.cyan)}`);
101
+
88
102
  const result = run(sysPython, ['-m', 'venv', VENV_DIR]);
89
103
  if (result.status !== 0 || !fs.existsSync(PYTHON)) {
90
- err(' 创建虚拟环境失败');
104
+ console.error(paint(' ', C.red) + '创建虚拟环境失败');
91
105
  return false;
92
106
  }
93
- log(' ✓ venv/ 已创建');
107
+ console.log(paint(' ✓ ', C.green) + 'venv 已创建');
94
108
  return true;
95
109
  }
96
110
 
97
- // ── Step 3: 安装依赖 ─────────────────────────────────
111
+ // ── Step 3: 安装依赖 ───────────────────────────────────────────
98
112
 
99
113
  function installDeps() {
100
114
  if (!fs.existsSync(REQUIREMENTS)) {
101
- err(' requirements.txt 不存在: ' + REQUIREMENTS);
115
+ console.error(paint(' ', C.red) + 'requirements.txt 不存在: ' + REQUIREMENTS);
102
116
  return false;
103
117
  }
104
118
 
105
- log('📥 安装 Python 依赖 (可能需要 1-2 分钟)...');
106
- const pipCmd = isWin ? PIP : PIP;
107
- const result = run(pipCmd, ['install', '-r', REQUIREMENTS, '--disable-pip-version-check']);
119
+ console.log(paint(' 📥 ', C.gold) + '安装 Python 依赖 (可能需要 1-2 分钟)...');
120
+ const result = run(PIP, ['install', '-r', REQUIREMENTS, '--disable-pip-version-check']);
108
121
  if (result.status !== 0) {
109
- err(' 依赖安装失败');
122
+ console.error(paint(' ', C.red) + '依赖安装失败');
110
123
  return false;
111
124
  }
112
- log(' ✓ 依赖安装完成');
125
+ console.log(paint(' ✓ ', C.green) + '依赖安装完成');
113
126
  return true;
114
127
  }
115
128
 
116
- // ── Step 4: 验证 ─────────────────────────────────────
129
+ // ── Step 4: 创建默认目录结构 ───────────────────────────────────
130
+
131
+ function createDefaultDirs() {
132
+ const dirs = ['sessions', 'uploads', 'skills', 'skills/active', 'skills/local', 'approvals'];
133
+ for (const d of dirs) {
134
+ ensureDir(path.join(STATE_ROOT, d));
135
+ }
136
+
137
+ // 默认 config.yaml (如果不存在)
138
+ const configPath = path.join(STATE_ROOT, 'config.yaml');
139
+ if (!fs.existsSync(configPath)) {
140
+ const defaultConfig = [
141
+ '# LBS Agent 配置文件',
142
+ '# 运行 lbs setup 进入交互式配置向导',
143
+ '',
144
+ 'model:',
145
+ ' provider: ""',
146
+ ' name: ""',
147
+ '',
148
+ 'agent:',
149
+ ' max_iterations: 10',
150
+ ' default_agent: chat',
151
+ '',
152
+ '# 自定义供应商列表',
153
+ 'custom_providers: []',
154
+ '',
155
+ ].join('\n');
156
+ fs.writeFileSync(configPath, defaultConfig, 'utf-8');
157
+ }
158
+
159
+ console.log(paint(' ✓ ', C.green) + '目录结构已创建');
160
+ }
161
+
162
+ // ── Step 5: 验证 ───────────────────────────────────────────────
117
163
 
118
164
  function verifyInstall() {
119
- log('🔍 验证安装...');
165
+ console.log(paint(' 🔍 ', C.gold) + '验证安装...');
120
166
  const result = run(PYTHON, [path.join(PKG_ROOT, 'lbs.py'), 'status'], {
121
167
  silent: true,
122
168
  cwd: PKG_ROOT,
169
+ env: { ...process.env, LBS_HOME: STATE_ROOT },
123
170
  });
124
171
  if (result.status === 0) {
125
- log(' ✓ lbs status 运行正常');
126
- return true;
172
+ console.log(paint(' ✓ ', C.green) + 'lbs status 运行正常');
173
+ } else {
174
+ console.log(paint(' ⚠ ', C.yellow) + 'lbs status 运行异常(可能是配置问题,不影响初始化)');
127
175
  }
128
- err(' ⚠ lbs status 运行异常(可能是配置问题,不影响初始化)');
129
- return true; // 不阻断
176
+ return true;
130
177
  }
131
178
 
132
- // ── Step 5: 写入标记 ─────────────────────────────────
179
+ // ── Step 6: 写入标记 ───────────────────────────────────────────
133
180
 
134
181
  function writeFlag() {
135
182
  fs.writeFileSync(INIT_FLAG, JSON.stringify({
136
183
  initialized_at: new Date().toISOString(),
184
+ state_root: STATE_ROOT,
185
+ pkg_root: PKG_ROOT,
137
186
  platform: os.platform(),
138
187
  node_version: process.version,
139
188
  }, null, 2));
140
- log(' ✓ .lbs-initialized 已创建');
189
+ console.log(paint(' ✓ ', C.green) + '.lbs-initialized 已创建');
141
190
  }
142
191
 
143
- // ── 主流程 ───────────────────────────────────────────
192
+ // ── 主流程 ─────────────────────────────────────────────────────
144
193
 
145
194
  function main() {
146
- console.log(`
147
- ╔══════════════════════════════════════════════╗
148
- LBS Agent — 初始化
149
- ║ Leaving Blank Space Agent ║
150
- ╚══════════════════════════════════════════════╝
151
- `);
195
+ console.log();
196
+ console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.gold));
197
+ console.log(paint(' │ ', C.gold) + paint('⚙ LBS Agent — 初始化', C.bold, C.gold) + paint(' │', C.gold));
198
+ console.log(paint(' └─────────────────────────────────────────────────────────┘', C.gold));
199
+ console.log();
200
+ console.log(paint(' 包目录 (只读): ', C.dim) + paint(PKG_ROOT, C.cyan));
201
+ console.log(paint(' 状态目录 (可写): ', C.dim) + paint(STATE_ROOT, C.cyan));
202
+ console.log();
152
203
 
153
204
  // Step 1: Python
154
205
  const sysPython = findSystemPython();
@@ -160,28 +211,28 @@ function main() {
160
211
  // Step 3: deps
161
212
  if (!installDeps()) process.exit(1);
162
213
 
163
- // Step 4: verify
214
+ // Step 4: dirs
215
+ createDefaultDirs();
216
+
217
+ // Step 5: verify
164
218
  verifyInstall();
165
219
 
166
- // Step 5: flag
220
+ // Step 6: flag
167
221
  writeFlag();
168
222
 
169
- console.log(`
170
- ╔══════════════════════════════════════════════╗
171
- ✅ 初始化完成!
172
- ║ ║
173
- ║ 使用方法: ║
174
- lbs status 查看系统状态 ║
175
- lbs models 查看模型列表 ║
176
- lbs help 查看所有命令
177
- ║ ║
178
- ║ 启动 Web 端: ║
179
- ║ lbs web 打开浏览器界面 ║
180
- ║ ║
181
- ║ 如果 lbs 命令不可用,手动运行: ║
182
- ║ node bin/lbs.js status ║
183
- ╚══════════════════════════════════════════════╝
184
- `);
223
+ console.log();
224
+ console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.green));
225
+ console.log(paint(' │ ', C.green) + paint('✅ 初始化完成!', C.bold, C.green) + paint(' │', C.green));
226
+ console.log(paint(' │ │', C.green));
227
+ console.log(paint(' │ ', C.green) + paint('下一步:', C.bold) + paint(' │', C.green));
228
+ console.log(paint(' │ ', C.green) + paint(' lbs setup 配置模型/供应商/Agent', C.cyan) + paint(' │', C.green));
229
+ console.log(paint(' │ ', C.green) + paint(' lbs status 查看系统状态', C.cyan) + paint(' │', C.green));
230
+ console.log(paint(' │ ', C.green) + paint(' lbs help 查看所有命令', C.cyan) + paint(' │', C.green));
231
+ console.log(paint(' │ ', C.green) + paint(' lbs web 启动 Web 界面', C.cyan) + paint(' │', C.green));
232
+ console.log(paint(' │ │', C.green));
233
+ console.log(paint(' │ ', C.green) + paint('状态目录: ', C.dim) + paint(STATE_ROOT, C.cyan) + paint(' │', C.green));
234
+ console.log(paint(' └─────────────────────────────────────────────────────────┘', C.green));
235
+ console.log();
185
236
  }
186
237
 
187
238
  main();