@baimingtao/lbs-agent 1.2.0 → 1.4.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 +47 -30
- package/interfaces/cli/_impl/cli_theme.py +233 -0
- package/interfaces/cli/_impl/commands/chat_repl.py +107 -104
- package/interfaces/cli/_impl/commands/setup.py +27 -27
- package/package.json +1 -1
- package/scripts/init.js +42 -37
package/bin/lbs.js
CHANGED
|
@@ -46,17 +46,21 @@ const INIT_SCRIPT = path.join(PKG_ROOT, 'scripts', 'init.js');
|
|
|
46
46
|
|
|
47
47
|
// ── 颜色与图标 (对标 Hermes banner/colors) ─────────────────────
|
|
48
48
|
|
|
49
|
+
// ── Aurora Pink 配色 ───────────────────────────────────────────
|
|
50
|
+
|
|
49
51
|
const C = {
|
|
50
|
-
reset: '\x1b[0m',
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
53
|
+
pink: '\x1b[38;2;255;79;164m', // #FF4FA4
|
|
54
|
+
pinkL: '\x1b[38;2;255;167;213m', // #FFA7D5
|
|
55
|
+
purple: '\x1b[38;2;194;107;255m', // #C26BFF
|
|
56
|
+
blue: '\x1b[38;2;110;139;255m', // #6E8BFF
|
|
57
|
+
mint: '\x1b[38;2;152;216;200m', // #98D8C8
|
|
58
|
+
mintD: '\x1b[38;2;111;196;176m', // #6FC4B0
|
|
59
|
+
ok: '\x1b[38;2;62;229;138m', // #3EE58A
|
|
60
|
+
warn: '\x1b[38;2;255;179;71m', // #FFB347
|
|
61
|
+
err: '\x1b[38;2;255;107;129m', // #FF6B81
|
|
62
|
+
subtle: '\x1b[38;2;138;138;150m', // #8A8A96
|
|
63
|
+
faint: '\x1b[38;2;90;90;102m', // #5A5A66
|
|
60
64
|
};
|
|
61
65
|
|
|
62
66
|
function supportsColor() {
|
|
@@ -70,25 +74,38 @@ function paint(text, ...colors) {
|
|
|
70
74
|
return colors.join('') + text + C.reset;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
//
|
|
77
|
+
// 逐字符渐变
|
|
78
|
+
function gradient(text) {
|
|
79
|
+
if (!supportsColor()) return text;
|
|
80
|
+
const stops = [
|
|
81
|
+
'\x1b[38;2;255;79;164m', // pink
|
|
82
|
+
'\x1b[38;2;255;127;184m', // pink-pu
|
|
83
|
+
'\x1b[38;2;224;119;220m', // mid
|
|
84
|
+
'\x1b[38;2;194;107;255m', // purple
|
|
85
|
+
'\x1b[38;2;160;123;240m', // pu-blue
|
|
86
|
+
'\x1b[38;2;130;131;248m', // blue-ish
|
|
87
|
+
'\x1b[38;2;110;139;255m', // blue
|
|
88
|
+
];
|
|
89
|
+
const n = text.length;
|
|
90
|
+
let out = '';
|
|
91
|
+
for (let i = 0; i < n; i++) {
|
|
92
|
+
if (text[i] === ' ') { out += ' '; continue; }
|
|
93
|
+
const idx = Math.min(Math.floor(i / Math.max(n, 1) * stops.length), stops.length - 1);
|
|
94
|
+
out += stops[idx] + text[i] + C.reset;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
74
98
|
|
|
75
|
-
|
|
76
|
-
'',
|
|
77
|
-
' ' + paint('╔══╗', C.gold) + ' ' + paint('╔═══╗', C.cyan) + ' ' + paint('╔═════╗', C.cyan),
|
|
78
|
-
' ' + paint('║ ║', C.gold) + ' ' + paint('║ ║', C.cyan) + ' ' + paint('║ ╔═╝', C.cyan),
|
|
79
|
-
' ' + paint('╠══╣', C.gold) + ' ' + paint('╠═══╣', C.cyan) + ' ' + paint('║ ╚═╗', C.cyan),
|
|
80
|
-
' ' + paint('║ ║', C.gold) + ' ' + paint('║ ║', C.cyan) + ' ' + paint('╚═════╝', C.cyan),
|
|
81
|
-
' ' + paint('╚══╝', C.gold) + ' ' + paint('╚═══╝', C.cyan) + ' ' + paint('AGENT', C.dim),
|
|
82
|
-
'',
|
|
83
|
-
].join('\n');
|
|
99
|
+
// ── Banner — 极简留白 ─────────────────────────────────────────
|
|
84
100
|
|
|
85
101
|
function printBanner() {
|
|
86
|
-
|
|
87
|
-
console.log(
|
|
88
|
-
console.log(
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(' ' + paint('✦', C.pink));
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(' ' + gradient('LBS Agent'));
|
|
106
|
+
console.log(' ' + paint('Autonomous AI Runtime', C.subtle));
|
|
89
107
|
console.log();
|
|
90
108
|
}
|
|
91
|
-
|
|
92
109
|
// ── 初始化检查 ─────────────────────────────────────────────────
|
|
93
110
|
|
|
94
111
|
function isInitialized() {
|
|
@@ -98,13 +115,13 @@ function isInitialized() {
|
|
|
98
115
|
function ensureInitialized() {
|
|
99
116
|
if (isInitialized()) return true;
|
|
100
117
|
|
|
101
|
-
console.log(paint('⚙ ', C.
|
|
102
|
-
console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.
|
|
118
|
+
console.log(paint('⚙ ', C.pink) + paint('首次运行,正在初始化 LBS Agent...', C.bold));
|
|
119
|
+
console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.mint));
|
|
103
120
|
console.log(paint(' 创建 Python 虚拟环境 + 安装依赖(约 1-2 分钟)', C.dim));
|
|
104
121
|
console.log();
|
|
105
122
|
|
|
106
123
|
if (!fs.existsSync(INIT_SCRIPT)) {
|
|
107
|
-
console.error(paint('✖ ', C.
|
|
124
|
+
console.error(paint('✖ ', C.err) + '初始化脚本不存在: ' + INIT_SCRIPT);
|
|
108
125
|
return false;
|
|
109
126
|
}
|
|
110
127
|
|
|
@@ -115,11 +132,11 @@ function ensureInitialized() {
|
|
|
115
132
|
});
|
|
116
133
|
|
|
117
134
|
if (result.status !== 0) {
|
|
118
|
-
console.error(paint('\n✖ ', C.
|
|
135
|
+
console.error(paint('\n✖ ', C.err) + '初始化失败');
|
|
119
136
|
return false;
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
console.log(paint('\n✓ ', C.
|
|
139
|
+
console.log(paint('\n✓ ', C.ok) + paint('初始化完成!', C.bold, C.ok));
|
|
123
140
|
console.log();
|
|
124
141
|
return true;
|
|
125
142
|
}
|
|
@@ -140,7 +157,7 @@ if (subcmd === 'init' || subcmd === '--init') {
|
|
|
140
157
|
});
|
|
141
158
|
process.exit(result.status || 0);
|
|
142
159
|
}
|
|
143
|
-
console.error(paint('✖ ', C.
|
|
160
|
+
console.error(paint('✖ ', C.err) + '初始化脚本不存在');
|
|
144
161
|
process.exit(1);
|
|
145
162
|
}
|
|
146
163
|
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""LBS CLI Theme — Aurora Pink 现代终端设计系统。
|
|
2
|
+
|
|
3
|
+
设计原则:
|
|
4
|
+
留白 > 密集
|
|
5
|
+
符号 > 字符画
|
|
6
|
+
渐变 > 纯色
|
|
7
|
+
|
|
8
|
+
对标: Claude Code, Gemini CLI, Warp Terminal, Charm BubbleTea
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ── 色彩 ───────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
class C:
|
|
20
|
+
RESET = "\033[0m"
|
|
21
|
+
BOLD = "\033[1m"
|
|
22
|
+
DIM = "\033[2m"
|
|
23
|
+
ITALIC = "\033[3m"
|
|
24
|
+
|
|
25
|
+
# Aurora Pink 渐变 (Pink → Purple → Blue)
|
|
26
|
+
PINK = "\033[38;2;255;79;164m" # #FF4FA4
|
|
27
|
+
PINK_L = "\033[38;2;255;167;213m" # #FFA7D5
|
|
28
|
+
PURPLE = "\033[38;2;194;107;255m" # #C26BFF
|
|
29
|
+
BLUE = "\033[38;2;110;139;255m" # #6E8BFF
|
|
30
|
+
|
|
31
|
+
# Mint (Web 端品牌色)
|
|
32
|
+
MINT = "\033[38;2;152;216;200m" # #98D8C8
|
|
33
|
+
MINT_D = "\033[38;2;111;196;176m" # #6FC4B0
|
|
34
|
+
|
|
35
|
+
# 状态
|
|
36
|
+
OK = "\033[38;2;62;229;138m" # #3EE58A
|
|
37
|
+
WARN = "\033[38;2;255;179;71m" # #FFB347
|
|
38
|
+
ERR = "\033[38;2;255;107;129m" # #FF6B81
|
|
39
|
+
INFO = "\033[38;2;101;168;255m" # #65A8FF
|
|
40
|
+
|
|
41
|
+
# 文字
|
|
42
|
+
TEXT = "\033[38;2;235;235;245m" # #EBEBF5 (正文)
|
|
43
|
+
SUBTLE = "\033[38;2;138;138;150m" # #8A8A96 (辅助)
|
|
44
|
+
FAINT = "\033[38;2;90;90;102m" # #5A5A66 (最弱)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _ok() -> bool:
|
|
48
|
+
if os.environ.get("NO_COLOR"):
|
|
49
|
+
return False
|
|
50
|
+
if os.environ.get("TERM") == "dumb":
|
|
51
|
+
return False
|
|
52
|
+
return sys.stdout.isatty()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def p(text: str, *codes: str) -> str:
|
|
56
|
+
"""上色"""
|
|
57
|
+
if not _ok():
|
|
58
|
+
return text
|
|
59
|
+
return "".join(codes) + text + C.RESET
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def dim(text: str) -> str:
|
|
63
|
+
return p(text, C.SUBTLE)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def faint(text: str) -> str:
|
|
67
|
+
return p(text, C.FAINT)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def pink(text: str) -> str:
|
|
71
|
+
return p(text, C.PINK)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def purple(text: str) -> str:
|
|
75
|
+
return p(text, C.PURPLE)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def mint(text: str) -> str:
|
|
79
|
+
return p(text, C.MINT_D)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def ok(text: str) -> str:
|
|
83
|
+
return p(text, C.OK)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def err(text: str) -> str:
|
|
87
|
+
return p(text, C.ERR)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── 组件 ───────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
def gradient_text(text: str) -> str:
|
|
93
|
+
"""Pink → Purple → Blue 逐字符渐变。"""
|
|
94
|
+
if not _ok():
|
|
95
|
+
return text
|
|
96
|
+
colors = [C.PINK, C.PINK_L, "\033[38;2;255;127;184m",
|
|
97
|
+
"\033[38;2;224;119;220m", C.PURPLE,
|
|
98
|
+
"\033[38;2;160;123;240m", C.BLUE]
|
|
99
|
+
n = len(text)
|
|
100
|
+
result = []
|
|
101
|
+
for i, ch in enumerate(text):
|
|
102
|
+
if ch == " ":
|
|
103
|
+
result.append(ch)
|
|
104
|
+
continue
|
|
105
|
+
idx = min(int(i / max(n, 1) * len(colors)), len(colors) - 1)
|
|
106
|
+
result.append(colors[idx] + ch + C.RESET)
|
|
107
|
+
return "".join(result)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def header() -> str:
|
|
111
|
+
"""启动 header — 极简留白风格。"""
|
|
112
|
+
lines = [
|
|
113
|
+
"",
|
|
114
|
+
f" {p('✦', C.PINK)}",
|
|
115
|
+
"",
|
|
116
|
+
f" {gradient_text('LBS Agent')}",
|
|
117
|
+
f" {dim('Autonomous AI Runtime')}",
|
|
118
|
+
"",
|
|
119
|
+
]
|
|
120
|
+
return "\n".join(lines)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def session_card(rows: list[tuple[str, str]]) -> str:
|
|
124
|
+
"""Session 信息卡 — 上下布局,图标 + label + value。"""
|
|
125
|
+
icons = {
|
|
126
|
+
"session": "◇",
|
|
127
|
+
"workspace": "▸",
|
|
128
|
+
"agent": "◐",
|
|
129
|
+
"model": "✦",
|
|
130
|
+
"provider": "☁",
|
|
131
|
+
"memory": "◈",
|
|
132
|
+
"runtime": "⚙",
|
|
133
|
+
"turns": "⟩",
|
|
134
|
+
"elapsed": "⏱",
|
|
135
|
+
}
|
|
136
|
+
lines = []
|
|
137
|
+
for label, value in rows:
|
|
138
|
+
icon = icons.get(label, "·")
|
|
139
|
+
lines.append(f" {p(icon, C.PURPLE)} {dim(f'{label:11}')} {value}")
|
|
140
|
+
return "\n".join(lines)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def separator(width: int = 52) -> str:
|
|
144
|
+
"""分隔线 — 最弱色"""
|
|
145
|
+
return p("─" * width, C.FAINT)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def prompt_symbol() -> str:
|
|
149
|
+
"""输入提示符"""
|
|
150
|
+
return p("❯", C.PINK)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def footer() -> str:
|
|
154
|
+
"""底部快捷键栏"""
|
|
155
|
+
keys = [
|
|
156
|
+
("↵", "Send"),
|
|
157
|
+
("↑↓", "History"),
|
|
158
|
+
("Tab", "Complete"),
|
|
159
|
+
("/help", "Commands"),
|
|
160
|
+
("Ctrl+C", "Exit"),
|
|
161
|
+
]
|
|
162
|
+
parts = []
|
|
163
|
+
for key, label in keys:
|
|
164
|
+
parts.append(f"{p(key, C.PURPLE)} {faint(label)}")
|
|
165
|
+
return faint(" ") + faint(" · ".join(parts))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def usage_line(seconds: float, iterations: int, tools: int) -> str:
|
|
169
|
+
"""用量条"""
|
|
170
|
+
parts = [
|
|
171
|
+
p(f"{seconds:.1f}s", C.MINT_D),
|
|
172
|
+
faint(f"iter {iterations}"),
|
|
173
|
+
faint(f"tools {tools}"),
|
|
174
|
+
]
|
|
175
|
+
return " " + faint(" · ").join(parts)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def result_text(content: str) -> str:
|
|
179
|
+
"""最终结果"""
|
|
180
|
+
return p(content, C.TEXT)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def error_text(content: str) -> str:
|
|
184
|
+
"""错误"""
|
|
185
|
+
return p(f"✕ {content}", C.ERR)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def agent_tag(name: str) -> str:
|
|
189
|
+
"""Agent 标签"""
|
|
190
|
+
return p(f"◐ {name}", C.PURPLE)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def model_tag(model: str) -> str:
|
|
194
|
+
"""模型标签"""
|
|
195
|
+
return p(f"✦ {model}", C.PINK)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def react_header(agent_display: str, model_chain: list[str], tools: list[str]) -> str:
|
|
199
|
+
"""ReAct 任务头 — 极简留白"""
|
|
200
|
+
model_str = faint(" → ").join(pink(m) for m in model_chain)
|
|
201
|
+
tools_str = faint(", ").join(mint(t) for t in tools)
|
|
202
|
+
return "\n".join([
|
|
203
|
+
separator(),
|
|
204
|
+
f" {agent_tag(agent_display)}",
|
|
205
|
+
f" {model_str}",
|
|
206
|
+
f" {faint('tools')} {tools_str}" if tools_str else "",
|
|
207
|
+
separator(),
|
|
208
|
+
])
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def react_iter(iteration: int) -> str:
|
|
212
|
+
"""迭代标记"""
|
|
213
|
+
return f"\n {p('⟡', C.PINK)} {p(f'iter {iteration}', C.BOLD, C.PINK)}"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def thinking_block(content: str) -> str:
|
|
217
|
+
"""思考块"""
|
|
218
|
+
return p(content, C.ITALIC, C.MINT_D)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def tool_card(name: str, status: str, duration: str = "") -> str:
|
|
222
|
+
"""工具调用卡片"""
|
|
223
|
+
status_color = C.OK if status == "ok" else C.ERR
|
|
224
|
+
icon = "✓" if status == "ok" else "✕"
|
|
225
|
+
dur = f" {faint(duration)}" if duration else ""
|
|
226
|
+
return f" {p(icon, status_color)} {faint(name)}{dur}"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def timeline_item(timestamp: str, label: str, success: bool = True) -> str:
|
|
230
|
+
"""时间线条目"""
|
|
231
|
+
icon = "✓" if success else "✕"
|
|
232
|
+
color = C.OK if success else C.ERR
|
|
233
|
+
return f" {faint(timestamp)} {p(icon, color)} {dim(label)}"
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
"""LBS Agent REPL
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- 用户进入交互循环,每次输入一行
|
|
5
|
-
- 内建命令以 `/` 开头(/help, /exit, /model, /agent, /session, /status, /clear, /new)
|
|
6
|
-
- 其他输入走 Orchestrator.run_agent_sync()
|
|
7
|
-
- 每次 turn 自动保存到 session(持久化)
|
|
8
|
-
|
|
9
|
-
用法:
|
|
10
|
-
lbs chat # 默认 agent
|
|
11
|
-
lbs chat --agent code
|
|
12
|
-
lbs chat --agent code --provider minmax --model MiniMax-M3
|
|
13
|
-
lbs chat --session <id> # 恢复已有 session
|
|
14
|
-
lbs chat --query "single query" # 非交互(等价于 lbs run)
|
|
1
|
+
"""LBS Agent REPL — Aurora Terminal v2。
|
|
2
|
+
|
|
3
|
+
极简留白,符号驱动,渐变品牌。
|
|
15
4
|
"""
|
|
16
5
|
from __future__ import annotations
|
|
17
6
|
|
|
@@ -23,27 +12,22 @@ from typing import Optional
|
|
|
23
12
|
from application.container import AgentSystemContainer
|
|
24
13
|
from config.config import AppConfig
|
|
25
14
|
from interfaces.cli._impl.commands.common import project_root
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
\033[1;36m╚══╝\033[0m \033[1;36m╚═══╝\033[0m \033[2mAGENT\033[0m
|
|
34
|
-
\033[0m
|
|
35
|
-
\033[2mInteractive Chat · /help for commands · /exit to quit\033[0m
|
|
36
|
-
"""
|
|
15
|
+
from interfaces.cli._impl.cli_theme import (
|
|
16
|
+
C, p, dim, faint, pink, purple, mint, ok, err,
|
|
17
|
+
gradient_text, header, session_card, separator,
|
|
18
|
+
prompt_symbol, footer, usage_line, result_text,
|
|
19
|
+
error_text, agent_tag, model_tag, react_header,
|
|
20
|
+
react_iter, thinking_block, tool_card, timeline_item,
|
|
21
|
+
)
|
|
37
22
|
|
|
38
23
|
|
|
39
24
|
@dataclass
|
|
40
25
|
class ChatState:
|
|
41
|
-
"""REPL 运行时状态。"""
|
|
42
26
|
container: object = None
|
|
43
27
|
orchestrator: object = None
|
|
44
28
|
session_service: object = None
|
|
45
29
|
session_id: str = ""
|
|
46
|
-
agent_name: str = "
|
|
30
|
+
agent_name: str = "chat"
|
|
47
31
|
provider_name: str = ""
|
|
48
32
|
model_name: str = ""
|
|
49
33
|
turn_count: int = 0
|
|
@@ -51,11 +35,9 @@ class ChatState:
|
|
|
51
35
|
|
|
52
36
|
|
|
53
37
|
def _resolve_agent(config: AppConfig) -> str:
|
|
54
|
-
"""选默认 agent:chat 优先(REPL 专用),其次 main/第一个。"""
|
|
55
38
|
names = list(config.agents.agents.keys()) if config.agents else []
|
|
56
39
|
if not names:
|
|
57
40
|
raise RuntimeError("没有配置任何 agent")
|
|
58
|
-
# chat 是 REPL 专用(无 evidence 强制,纯对话)
|
|
59
41
|
if "chat" in names:
|
|
60
42
|
return "chat"
|
|
61
43
|
if "main" in names:
|
|
@@ -63,90 +45,114 @@ def _resolve_agent(config: AppConfig) -> str:
|
|
|
63
45
|
return names[0]
|
|
64
46
|
|
|
65
47
|
|
|
48
|
+
def _print_banner(state: ChatState) -> None:
|
|
49
|
+
"""启动 — 极简留白"""
|
|
50
|
+
print()
|
|
51
|
+
print(header())
|
|
52
|
+
|
|
53
|
+
rows = [
|
|
54
|
+
("session", faint(state.session_id) if state.session_id else faint("(unsaved)")),
|
|
55
|
+
("agent", agent_tag(state.agent_name)),
|
|
56
|
+
("model", model_tag(state.model_name) if state.model_name else faint("(default)")),
|
|
57
|
+
("provider", purple(state.provider_name) if state.provider_name else faint("(default)")),
|
|
58
|
+
("turns", faint(str(state.turn_count))),
|
|
59
|
+
]
|
|
60
|
+
print(session_card(rows))
|
|
61
|
+
print()
|
|
62
|
+
print(separator())
|
|
63
|
+
print(f" {faint('Ask anything...')}")
|
|
64
|
+
print(separator())
|
|
65
|
+
print()
|
|
66
|
+
|
|
67
|
+
|
|
66
68
|
def _print_turn_header(state: ChatState) -> None:
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
print(f"\n{agent_label} {turn_label} \033[2m›\033[0m ", end="", flush=True)
|
|
69
|
+
"""输入提示符 — 只有 ❯"""
|
|
70
|
+
print()
|
|
71
|
+
print(f" {prompt_symbol()} ", end="", flush=True)
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
def _print_help() -> None:
|
|
74
|
-
print(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
""
|
|
75
|
+
print()
|
|
76
|
+
print(f" {gradient_text('Commands')}")
|
|
77
|
+
print()
|
|
78
|
+
cmds = [
|
|
79
|
+
("/help", "显示帮助"),
|
|
80
|
+
("/exit", "退出"),
|
|
81
|
+
("/new", "新 session"),
|
|
82
|
+
("/status", "状态"),
|
|
83
|
+
("/agent", "切换 agent"),
|
|
84
|
+
("/model", "切换 model"),
|
|
85
|
+
("/history", "历史"),
|
|
86
|
+
("/clear", "清屏"),
|
|
87
|
+
]
|
|
88
|
+
for cmd, desc in cmds:
|
|
89
|
+
print(f" {pink(cmd.ljust(12))} {faint(desc)}")
|
|
90
|
+
print()
|
|
89
91
|
|
|
90
92
|
|
|
91
93
|
def _cmd_list_agents(state: ChatState) -> None:
|
|
92
94
|
agents = state.container.config.agents.agents
|
|
93
|
-
print(
|
|
95
|
+
print()
|
|
94
96
|
for name, a in agents.items():
|
|
95
|
-
|
|
96
|
-
print(f" {name
|
|
97
|
+
current = f" {ok('← current')}" if name == state.agent_name else ""
|
|
98
|
+
print(f" {agent_tag(name)} {faint(a.display_name)}{current}")
|
|
99
|
+
print()
|
|
97
100
|
|
|
98
101
|
|
|
99
102
|
def _cmd_list_models(state: ChatState) -> None:
|
|
100
103
|
config = state.container.config
|
|
101
104
|
provider_name = state.provider_name or config.models.default_provider
|
|
102
105
|
if provider_name not in config.models.providers:
|
|
103
|
-
|
|
106
|
+
msg = f"无 provider '{provider_name}'"
|
|
107
|
+
print(f" {error_text(msg)}")
|
|
104
108
|
return
|
|
105
|
-
|
|
106
|
-
print(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
prov = config.models.providers[provider_name]
|
|
110
|
+
print()
|
|
111
|
+
print(f" {faint('provider')} {purple(provider_name)}")
|
|
112
|
+
for m in prov.models:
|
|
113
|
+
marker = f" {ok('←')}" if m == state.model_name else ""
|
|
114
|
+
print(f" {model_tag(m)}{marker}")
|
|
115
|
+
print()
|
|
110
116
|
|
|
111
117
|
|
|
112
118
|
def _cmd_history(state: ChatState, n: int = 5) -> None:
|
|
113
119
|
if not state.session_id:
|
|
114
|
-
print(" 无 session")
|
|
120
|
+
print(f" {faint('无 session')}")
|
|
115
121
|
return
|
|
116
122
|
try:
|
|
117
123
|
messages = list(state.session_service.session_store.load_session_messages(state.session_id))
|
|
118
|
-
# 显示 user/assistant 配对
|
|
119
124
|
recent = []
|
|
120
125
|
for msg in messages[-n*2:]:
|
|
121
126
|
role = getattr(msg, "role", "?")
|
|
122
127
|
content = getattr(msg, "content", "")[:120]
|
|
123
128
|
if role in ("user", "assistant"):
|
|
124
129
|
recent.append((role, content))
|
|
125
|
-
print(
|
|
130
|
+
print()
|
|
126
131
|
for role, content in recent:
|
|
127
|
-
|
|
132
|
+
rc = C.PINK if role == "user" else C.MINT_D
|
|
133
|
+
print(f" {p(role.ljust(9), rc)} {faint(content)}")
|
|
134
|
+
print()
|
|
128
135
|
except Exception as e:
|
|
129
|
-
|
|
136
|
+
msg = f"读取失败: {e}"
|
|
137
|
+
print(f" {error_text(msg)}")
|
|
130
138
|
|
|
131
139
|
|
|
132
140
|
def _cmd_status(state: ChatState) -> None:
|
|
133
141
|
elapsed = int(time.time() - state.started_at)
|
|
134
142
|
rows = [
|
|
135
|
-
("session", state.session_id
|
|
136
|
-
("agent",
|
|
137
|
-
("
|
|
138
|
-
("
|
|
139
|
-
("turns",
|
|
140
|
-
("elapsed",
|
|
143
|
+
("session", faint(state.session_id) if state.session_id else faint("(unsaved)")),
|
|
144
|
+
("agent", agent_tag(state.agent_name)),
|
|
145
|
+
("model", model_tag(state.model_name) if state.model_name else faint("(default)")),
|
|
146
|
+
("provider", purple(state.provider_name) if state.provider_name else faint("(default)")),
|
|
147
|
+
("turns", faint(str(state.turn_count))),
|
|
148
|
+
("elapsed", faint(f"{elapsed}s")),
|
|
141
149
|
]
|
|
142
150
|
print()
|
|
143
|
-
|
|
144
|
-
print(f" \033[2m{label:9}\033[0m │ {val}")
|
|
151
|
+
print(session_card(rows))
|
|
145
152
|
print()
|
|
146
153
|
|
|
147
154
|
|
|
148
155
|
def _handle_command(state: ChatState, line: str) -> bool:
|
|
149
|
-
"""处理 / 命令。返回 True 表示继续循环,False 表示退出。"""
|
|
150
156
|
parts = line.strip().split(maxsplit=1)
|
|
151
157
|
cmd = parts[0].lower()
|
|
152
158
|
arg = parts[1] if len(parts) > 1 else ""
|
|
@@ -159,13 +165,14 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
159
165
|
state.session_id = ""
|
|
160
166
|
state.turn_count = 0
|
|
161
167
|
state.started_at = time.time()
|
|
162
|
-
print("
|
|
168
|
+
print(f" {ok('◇')} {faint('new session')}")
|
|
163
169
|
elif cmd == "/session":
|
|
164
170
|
if arg:
|
|
165
171
|
state.session_id = arg
|
|
166
|
-
print(f"
|
|
172
|
+
print(f" {ok('◇')} {faint('switched to')} {pink(arg)}")
|
|
167
173
|
else:
|
|
168
|
-
|
|
174
|
+
sid = state.session_id or "(none)"
|
|
175
|
+
print(f" {faint('session:')} {sid}")
|
|
169
176
|
elif cmd == "/status":
|
|
170
177
|
_cmd_status(state)
|
|
171
178
|
elif cmd == "/agent":
|
|
@@ -175,15 +182,16 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
175
182
|
agents = state.container.config.agents.agents
|
|
176
183
|
if arg in agents:
|
|
177
184
|
state.agent_name = arg
|
|
178
|
-
print(f"
|
|
185
|
+
print(f" {ok('◇')} {faint('agent')} {agent_tag(arg)}")
|
|
179
186
|
else:
|
|
180
|
-
|
|
187
|
+
msg = f"无 agent '{arg}'"
|
|
188
|
+
print(f" {error_text(msg)}")
|
|
181
189
|
elif cmd == "/model":
|
|
182
190
|
if not arg:
|
|
183
191
|
_cmd_list_models(state)
|
|
184
192
|
else:
|
|
185
193
|
state.model_name = arg
|
|
186
|
-
print(f"
|
|
194
|
+
print(f" {ok('◇')} {faint('model')} {model_tag(arg)}")
|
|
187
195
|
elif cmd == "/clear":
|
|
188
196
|
print("\033[2J\033[H", end="")
|
|
189
197
|
elif cmd == "/history":
|
|
@@ -191,20 +199,18 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
191
199
|
n = int(arg) if arg else 5
|
|
192
200
|
_cmd_history(state, n)
|
|
193
201
|
except ValueError:
|
|
194
|
-
print(" /history [n]
|
|
202
|
+
print(f" {error_text('/history [n] — n 须为整数')}")
|
|
195
203
|
elif cmd == "/save":
|
|
196
|
-
|
|
197
|
-
print(" session 已在每次 turn 自动保存")
|
|
204
|
+
print(f" {faint('session 每次 turn 自动保存')}")
|
|
198
205
|
else:
|
|
199
|
-
print(f"
|
|
206
|
+
print(f" {error_text(f'未知命令: {cmd}')}")
|
|
200
207
|
return True
|
|
201
208
|
|
|
202
209
|
|
|
203
210
|
def _run_turn(state: ChatState, user_input: str) -> None:
|
|
204
|
-
"""
|
|
211
|
+
"""跑一轮对话"""
|
|
205
212
|
t0 = time.time()
|
|
206
213
|
try:
|
|
207
|
-
# 把 user_input 作为 task
|
|
208
214
|
result = state.orchestrator.run_agent_sync(
|
|
209
215
|
state.agent_name,
|
|
210
216
|
user_input,
|
|
@@ -212,24 +218,23 @@ def _run_turn(state: ChatState, user_input: str) -> None:
|
|
|
212
218
|
session_id=state.session_id,
|
|
213
219
|
)
|
|
214
220
|
elapsed = time.time() - t0
|
|
215
|
-
# 显示结果
|
|
216
221
|
if result.success:
|
|
217
222
|
content = (result.content or "").strip()
|
|
218
|
-
print(f"\n
|
|
223
|
+
print(f"\n{result_text(content)}")
|
|
219
224
|
else:
|
|
220
|
-
|
|
221
|
-
print(f"\n
|
|
222
|
-
|
|
223
|
-
print(f"\033[2m {elapsed:.1f}s │ iter {result.iterations} │ tools {result.total_tool_calls}\033[0m")
|
|
224
|
-
# 保存 session_id(Orchestrator 内部会创建/复用)
|
|
225
|
+
e = result.error or "(无错误信息)"
|
|
226
|
+
print(f"\n {error_text(e[:300])}")
|
|
227
|
+
print(usage_line(elapsed, result.iterations, result.total_tool_calls))
|
|
225
228
|
if not state.session_id:
|
|
226
|
-
|
|
229
|
+
sid = result.session_id if hasattr(result, "session_id") and result.session_id else ""
|
|
230
|
+
state.session_id = sid or f"chat-{int(state.started_at)}"
|
|
227
231
|
except KeyboardInterrupt:
|
|
228
|
-
print("\n
|
|
232
|
+
print(f"\n {p('⏸', C.WARN)} {faint('已中断')}")
|
|
229
233
|
except Exception as e:
|
|
230
234
|
elapsed = time.time() - t0
|
|
231
|
-
|
|
232
|
-
print(f"\
|
|
235
|
+
msg = f"{type(e).__name__}: {str(e)[:200]}"
|
|
236
|
+
print(f"\n {error_text(msg)}")
|
|
237
|
+
print(f" {faint(f'{elapsed:.1f}s')}")
|
|
233
238
|
|
|
234
239
|
|
|
235
240
|
def cmd_chat(
|
|
@@ -243,13 +248,12 @@ def cmd_chat(
|
|
|
243
248
|
"""进入 chat REPL 或单次 query。"""
|
|
244
249
|
container = AgentSystemContainer(config=config, project_root=project_root)
|
|
245
250
|
|
|
246
|
-
# 选 agent
|
|
247
251
|
if not agent_name:
|
|
248
252
|
agent_name = _resolve_agent(config)
|
|
249
253
|
|
|
250
|
-
# 验证 agent 存在
|
|
251
254
|
if agent_name not in config.agents.agents:
|
|
252
|
-
|
|
255
|
+
msg = f"Agent '{agent_name}' 不存在"
|
|
256
|
+
print(f" {error_text(msg)}")
|
|
253
257
|
return 1
|
|
254
258
|
|
|
255
259
|
state = ChatState(
|
|
@@ -262,7 +266,6 @@ def cmd_chat(
|
|
|
262
266
|
session_id=session_id,
|
|
263
267
|
)
|
|
264
268
|
|
|
265
|
-
# 单次 query 模式(--query,非交互)
|
|
266
269
|
if query:
|
|
267
270
|
state.turn_count = 1
|
|
268
271
|
_print_turn_header(state)
|
|
@@ -270,10 +273,10 @@ def cmd_chat(
|
|
|
270
273
|
_run_turn(state, query)
|
|
271
274
|
return 0
|
|
272
275
|
|
|
273
|
-
# REPL
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
print(
|
|
276
|
+
# REPL
|
|
277
|
+
_print_banner(state)
|
|
278
|
+
print(footer())
|
|
279
|
+
print()
|
|
277
280
|
|
|
278
281
|
while True:
|
|
279
282
|
try:
|
|
@@ -283,7 +286,7 @@ def cmd_chat(
|
|
|
283
286
|
print("\n")
|
|
284
287
|
break
|
|
285
288
|
except KeyboardInterrupt:
|
|
286
|
-
print("\n
|
|
289
|
+
print(f"\n {p('用 /exit 退出', C.WARN)}")
|
|
287
290
|
continue
|
|
288
291
|
|
|
289
292
|
line = line.strip()
|
|
@@ -296,5 +299,5 @@ def cmd_chat(
|
|
|
296
299
|
state.turn_count += 1
|
|
297
300
|
_run_turn(state, line)
|
|
298
301
|
|
|
299
|
-
print("\n
|
|
302
|
+
print(f"\n {ok('◇')} {faint('Session saved. Bye.')}")
|
|
300
303
|
return 0
|
|
@@ -73,34 +73,34 @@ def _paint(text: str, *codes: str) -> str:
|
|
|
73
73
|
def _header(title: str) -> None:
|
|
74
74
|
"""打印章节标题 (带边框)。"""
|
|
75
75
|
print()
|
|
76
|
-
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.
|
|
77
|
-
print(_paint(" │ ", _C.
|
|
78
|
-
+ _paint(" " * max(0, 47 - len(title) - 14) + "│", _C.
|
|
79
|
-
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.
|
|
76
|
+
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.PINK))
|
|
77
|
+
print(_paint(" │ ", _C.PINK) + _paint(f"⚙ LBS Setup — {title}", _C.BOLD, _C.PINK)
|
|
78
|
+
+ _paint(" " * max(0, 47 - len(title) - 14) + "│", _C.PINK))
|
|
79
|
+
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.PINK))
|
|
80
80
|
print()
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def _info(msg: str) -> None:
|
|
84
|
-
print(_paint(" ℹ ", _C.
|
|
84
|
+
print(_paint(" ℹ ", _C.VIOLET) + msg)
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def _success(msg: str) -> None:
|
|
88
|
-
print(_paint(" ✓ ", _C.
|
|
88
|
+
print(_paint(" ✓ ", _C.OK) + _paint(msg, _C.OK))
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def _warn(msg: str) -> None:
|
|
92
|
-
print(_paint(" ⚠ ", _C.
|
|
92
|
+
print(_paint(" ⚠ ", _C.HONEY) + _paint(msg, _C.HONEY))
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
def _error(msg: str) -> None:
|
|
96
|
-
print(_paint(" ✖ ", _C.
|
|
96
|
+
print(_paint(" ✖ ", _C.ERR) + _paint(msg, _C.ERR))
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def _prompt(label: str, default: str = "") -> str:
|
|
100
100
|
"""带默认值的输入提示。"""
|
|
101
101
|
suffix = f" [{default}]" if default else ""
|
|
102
102
|
try:
|
|
103
|
-
val = input(_paint(f" ▸ {label}{suffix}: ", _C.
|
|
103
|
+
val = input(_paint(f" ▸ {label}{suffix}: ", _C.MINT)).strip()
|
|
104
104
|
except (EOFError, KeyboardInterrupt):
|
|
105
105
|
print()
|
|
106
106
|
return default
|
|
@@ -109,9 +109,9 @@ def _prompt(label: str, default: str = "") -> str:
|
|
|
109
109
|
|
|
110
110
|
def _prompt_choice(label: str, options: list[str], default: str = "") -> str:
|
|
111
111
|
"""带选项列表的输入提示。"""
|
|
112
|
-
print(_paint(f" ▸ {label}", _C.
|
|
112
|
+
print(_paint(f" ▸ {label}", _C.MINT))
|
|
113
113
|
for i, opt in enumerate(options, 1):
|
|
114
|
-
marker = _paint("→", _C.
|
|
114
|
+
marker = _paint("→", _C.OK) if opt == default else " "
|
|
115
115
|
print(f" {marker} {i}. {opt}")
|
|
116
116
|
val = _prompt("选择编号或直接输入", default)
|
|
117
117
|
if val.isdigit():
|
|
@@ -125,7 +125,7 @@ def _prompt_password(label: str) -> str:
|
|
|
125
125
|
"""密码输入(不回显)。"""
|
|
126
126
|
import getpass
|
|
127
127
|
try:
|
|
128
|
-
val = getpass.getpass(_paint(f" ▸ {label}: ", _C.
|
|
128
|
+
val = getpass.getpass(_paint(f" ▸ {label}: ", _C.MINT))
|
|
129
129
|
except (EOFError, KeyboardInterrupt):
|
|
130
130
|
print()
|
|
131
131
|
return ""
|
|
@@ -467,10 +467,10 @@ def run_setup(section: str | None = None) -> int:
|
|
|
467
467
|
root.mkdir(parents=True, exist_ok=True)
|
|
468
468
|
|
|
469
469
|
print()
|
|
470
|
-
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.
|
|
471
|
-
print(_paint(" │ ", _C.
|
|
472
|
-
+ _paint(" │", _C.
|
|
473
|
-
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.
|
|
470
|
+
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.PINK))
|
|
471
|
+
print(_paint(" │ ", _C.PINK) + _paint("⚙ LBS Agent Setup Wizard", _C.BOLD, _C.PINK)
|
|
472
|
+
+ _paint(" │", _C.PINK))
|
|
473
|
+
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.PINK))
|
|
474
474
|
print()
|
|
475
475
|
_info(f"配置目录: {root}")
|
|
476
476
|
print()
|
|
@@ -496,15 +496,15 @@ def run_setup(section: str | None = None) -> int:
|
|
|
496
496
|
|
|
497
497
|
def _print_done() -> None:
|
|
498
498
|
print()
|
|
499
|
-
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.
|
|
500
|
-
print(_paint(" │ ", _C.
|
|
501
|
-
+ _paint(" │", _C.
|
|
502
|
-
print(_paint(" │ │", _C.
|
|
503
|
-
print(_paint(" │ ", _C.
|
|
504
|
-
print(_paint(" │ ", _C.
|
|
505
|
-
print(_paint(" │ │", _C.
|
|
506
|
-
print(_paint(" │ ", _C.
|
|
507
|
-
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.
|
|
499
|
+
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.OK))
|
|
500
|
+
print(_paint(" │ ", _C.OK) + _paint("✅ 配置完成!", _C.BOLD, _C.OK)
|
|
501
|
+
+ _paint(" │", _C.OK))
|
|
502
|
+
print(_paint(" │ │", _C.OK))
|
|
503
|
+
print(_paint(" │ ", _C.OK) + _paint("配置文件:", _C.BOLD) + paint_path(_models_yaml_path()) + _paint(" │", _C.OK))
|
|
504
|
+
print(_paint(" │ ", _C.OK) + _paint("密钥文件:", _C.BOLD) + paint_path(_env_path()) + _paint(" │", _C.OK))
|
|
505
|
+
print(_paint(" │ │", _C.OK))
|
|
506
|
+
print(_paint(" │ ", _C.OK) + _paint("现在可以运行:", _C.BOLD) + paint_cmd(" lbs status") + _paint(" │", _C.OK))
|
|
507
|
+
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.OK))
|
|
508
508
|
print()
|
|
509
509
|
|
|
510
510
|
|
|
@@ -512,8 +512,8 @@ def paint_path(p: Path) -> str:
|
|
|
512
512
|
s = str(p)
|
|
513
513
|
if len(s) > 40:
|
|
514
514
|
s = "..." + s[-37:]
|
|
515
|
-
return _paint(s, _C.
|
|
515
|
+
return _paint(s, _C.MINT)
|
|
516
516
|
|
|
517
517
|
|
|
518
518
|
def paint_cmd(cmd: str) -> str:
|
|
519
|
-
return _paint(cmd, _C.
|
|
519
|
+
return _paint(cmd, _C.MINT, _C.BOLD)
|
package/package.json
CHANGED
package/scripts/init.js
CHANGED
|
@@ -29,10 +29,15 @@ const PIP = isWin ? path.join(VENV_DIR, 'Scripts', 'pip.exe') : path.join(VENV_D
|
|
|
29
29
|
// ── 颜色 ───────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
31
|
const C = {
|
|
32
|
-
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
33
|
+
sakura: '\x1b[38;2;255;183;197m', // #FFB7C5
|
|
34
|
+
sakuraDark: '\x1b[38;2;232;154;168m', // #E89AA8
|
|
35
|
+
mint: '\x1b[38;2;152;216;200m', // #98D8C8
|
|
36
|
+
mintDark: '\x1b[38;2;111;196;176m', // #6FC4B0
|
|
37
|
+
violet: '\x1b[38;2;110;90;168m', // #6E5AA8
|
|
38
|
+
honey: '\x1b[38;2;255;184;107m', // #FFB86B
|
|
39
|
+
green: '\x1b[38;2;69;214;163m', // #45D6A3
|
|
40
|
+
red: '\x1b[38;2;255;107;129m', // #FF6B81
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
function paint(text, ...colors) {
|
|
@@ -58,7 +63,7 @@ function ensureDir(dir) {
|
|
|
58
63
|
// ── Step 1: 检测系统 Python ────────────────────────────────────
|
|
59
64
|
|
|
60
65
|
function findSystemPython() {
|
|
61
|
-
console.log(paint(' 🔍 ', C.
|
|
66
|
+
console.log(paint(' 🔍 ', C.pink) + '检测系统 Python...');
|
|
62
67
|
|
|
63
68
|
const candidates = isWin
|
|
64
69
|
? ['python', 'python3', 'py -3']
|
|
@@ -68,7 +73,7 @@ function findSystemPython() {
|
|
|
68
73
|
const result = run(cmd, ['--version'], { silent: true, shell: isWin });
|
|
69
74
|
if (result.status === 0) {
|
|
70
75
|
const version = (result.stdout || result.stderr || '').trim();
|
|
71
|
-
console.log(paint(' ✓ ', C.
|
|
76
|
+
console.log(paint(' ✓ ', C.ok) + `${cmd}: ${version}`);
|
|
72
77
|
|
|
73
78
|
const match = version.match(/(\d+)\.(\d+)/);
|
|
74
79
|
if (match) {
|
|
@@ -76,11 +81,11 @@ function findSystemPython() {
|
|
|
76
81
|
const minor = parseInt(match[2]);
|
|
77
82
|
if (major >= 3 && minor >= 10) return cmd;
|
|
78
83
|
}
|
|
79
|
-
console.log(paint(' ⚠ ', C.
|
|
84
|
+
console.log(paint(' ⚠ ', C.warn) + '版本低于 3.10,继续寻找...');
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
console.error(paint('\n ✖ ', C.
|
|
88
|
+
console.error(paint('\n ✖ ', C.err) + '未找到 Python 3.10+');
|
|
84
89
|
console.error(paint(' ', C.dim) + 'Windows: https://www.python.org/downloads/');
|
|
85
90
|
console.error(paint(' ', C.dim) + 'macOS: brew install python@3.12');
|
|
86
91
|
console.error(paint(' ', C.dim) + 'Linux: sudo apt install python3 python3-venv');
|
|
@@ -91,20 +96,20 @@ function findSystemPython() {
|
|
|
91
96
|
|
|
92
97
|
function createVenv(sysPython) {
|
|
93
98
|
if (fs.existsSync(PYTHON)) {
|
|
94
|
-
console.log(paint(' ✓ ', C.
|
|
99
|
+
console.log(paint(' ✓ ', C.ok) + '虚拟环境已存在,跳过创建');
|
|
95
100
|
return true;
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
// 确保 state root 存在
|
|
99
104
|
ensureDir(STATE_ROOT);
|
|
100
|
-
console.log(paint(' 📦 ', C.
|
|
105
|
+
console.log(paint(' 📦 ', C.pink) + `创建 Python 虚拟环境 → ${paint(STATE_ROOT + '/venv', C.mint)}`);
|
|
101
106
|
|
|
102
107
|
const result = run(sysPython, ['-m', 'venv', VENV_DIR]);
|
|
103
108
|
if (result.status !== 0 || !fs.existsSync(PYTHON)) {
|
|
104
|
-
console.error(paint(' ✖ ', C.
|
|
109
|
+
console.error(paint(' ✖ ', C.err) + '创建虚拟环境失败');
|
|
105
110
|
return false;
|
|
106
111
|
}
|
|
107
|
-
console.log(paint(' ✓ ', C.
|
|
112
|
+
console.log(paint(' ✓ ', C.ok) + 'venv 已创建');
|
|
108
113
|
return true;
|
|
109
114
|
}
|
|
110
115
|
|
|
@@ -112,17 +117,17 @@ function createVenv(sysPython) {
|
|
|
112
117
|
|
|
113
118
|
function installDeps() {
|
|
114
119
|
if (!fs.existsSync(REQUIREMENTS)) {
|
|
115
|
-
console.error(paint(' ✖ ', C.
|
|
120
|
+
console.error(paint(' ✖ ', C.err) + 'requirements.txt 不存在: ' + REQUIREMENTS);
|
|
116
121
|
return false;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
console.log(paint(' 📥 ', C.
|
|
124
|
+
console.log(paint(' 📥 ', C.pink) + '安装 Python 依赖 (可能需要 1-2 分钟)...');
|
|
120
125
|
const result = run(PIP, ['install', '-r', REQUIREMENTS, '--disable-pip-version-check']);
|
|
121
126
|
if (result.status !== 0) {
|
|
122
|
-
console.error(paint(' ✖ ', C.
|
|
127
|
+
console.error(paint(' ✖ ', C.err) + '依赖安装失败');
|
|
123
128
|
return false;
|
|
124
129
|
}
|
|
125
|
-
console.log(paint(' ✓ ', C.
|
|
130
|
+
console.log(paint(' ✓ ', C.ok) + '依赖安装完成');
|
|
126
131
|
return true;
|
|
127
132
|
}
|
|
128
133
|
|
|
@@ -156,22 +161,22 @@ function createDefaultDirs() {
|
|
|
156
161
|
fs.writeFileSync(configPath, defaultConfig, 'utf-8');
|
|
157
162
|
}
|
|
158
163
|
|
|
159
|
-
console.log(paint(' ✓ ', C.
|
|
164
|
+
console.log(paint(' ✓ ', C.ok) + '目录结构已创建');
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
// ── Step 5: 验证 ───────────────────────────────────────────────
|
|
163
168
|
|
|
164
169
|
function verifyInstall() {
|
|
165
|
-
console.log(paint(' 🔍 ', C.
|
|
170
|
+
console.log(paint(' 🔍 ', C.pink) + '验证安装...');
|
|
166
171
|
const result = run(PYTHON, [path.join(PKG_ROOT, 'lbs.py'), 'status'], {
|
|
167
172
|
silent: true,
|
|
168
173
|
cwd: PKG_ROOT,
|
|
169
174
|
env: { ...process.env, LBS_HOME: STATE_ROOT },
|
|
170
175
|
});
|
|
171
176
|
if (result.status === 0) {
|
|
172
|
-
console.log(paint(' ✓ ', C.
|
|
177
|
+
console.log(paint(' ✓ ', C.ok) + 'lbs status 运行正常');
|
|
173
178
|
} else {
|
|
174
|
-
console.log(paint(' ⚠ ', C.
|
|
179
|
+
console.log(paint(' ⚠ ', C.warn) + 'lbs status 运行异常(可能是配置问题,不影响初始化)');
|
|
175
180
|
}
|
|
176
181
|
return true;
|
|
177
182
|
}
|
|
@@ -186,19 +191,19 @@ function writeFlag() {
|
|
|
186
191
|
platform: os.platform(),
|
|
187
192
|
node_version: process.version,
|
|
188
193
|
}, null, 2));
|
|
189
|
-
console.log(paint(' ✓ ', C.
|
|
194
|
+
console.log(paint(' ✓ ', C.ok) + '.lbs-initialized 已创建');
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
// ── 主流程 ─────────────────────────────────────────────────────
|
|
193
198
|
|
|
194
199
|
function main() {
|
|
195
200
|
console.log();
|
|
196
|
-
console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.
|
|
197
|
-
console.log(paint(' │ ', C.
|
|
198
|
-
console.log(paint(' └─────────────────────────────────────────────────────────┘', C.
|
|
201
|
+
console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.pink));
|
|
202
|
+
console.log(paint(' │ ', C.pink) + paint('⚙ LBS Agent — 初始化', C.bold, C.pink) + paint(' │', C.pink));
|
|
203
|
+
console.log(paint(' └─────────────────────────────────────────────────────────┘', C.pink));
|
|
199
204
|
console.log();
|
|
200
|
-
console.log(paint(' 包目录 (只读): ', C.dim) + paint(PKG_ROOT, C.
|
|
201
|
-
console.log(paint(' 状态目录 (可写): ', C.dim) + paint(STATE_ROOT, C.
|
|
205
|
+
console.log(paint(' 包目录 (只读): ', C.dim) + paint(PKG_ROOT, C.mint));
|
|
206
|
+
console.log(paint(' 状态目录 (可写): ', C.dim) + paint(STATE_ROOT, C.mint));
|
|
202
207
|
console.log();
|
|
203
208
|
|
|
204
209
|
// Step 1: Python
|
|
@@ -221,17 +226,17 @@ function main() {
|
|
|
221
226
|
writeFlag();
|
|
222
227
|
|
|
223
228
|
console.log();
|
|
224
|
-
console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.
|
|
225
|
-
console.log(paint(' │ ', C.
|
|
226
|
-
console.log(paint(' │ │', C.
|
|
227
|
-
console.log(paint(' │ ', C.
|
|
228
|
-
console.log(paint(' │ ', C.
|
|
229
|
-
console.log(paint(' │ ', C.
|
|
230
|
-
console.log(paint(' │ ', C.
|
|
231
|
-
console.log(paint(' │ ', C.
|
|
232
|
-
console.log(paint(' │ │', C.
|
|
233
|
-
console.log(paint(' │ ', C.
|
|
234
|
-
console.log(paint(' └─────────────────────────────────────────────────────────┘', C.
|
|
229
|
+
console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.ok));
|
|
230
|
+
console.log(paint(' │ ', C.ok) + paint('✅ 初始化完成!', C.bold, C.ok) + paint(' │', C.ok));
|
|
231
|
+
console.log(paint(' │ │', C.ok));
|
|
232
|
+
console.log(paint(' │ ', C.ok) + paint('下一步:', C.bold) + paint(' │', C.ok));
|
|
233
|
+
console.log(paint(' │ ', C.ok) + paint(' lbs setup 配置模型/供应商/Agent', C.mint) + paint(' │', C.ok));
|
|
234
|
+
console.log(paint(' │ ', C.ok) + paint(' lbs status 查看系统状态', C.mint) + paint(' │', C.ok));
|
|
235
|
+
console.log(paint(' │ ', C.ok) + paint(' lbs help 查看所有命令', C.mint) + paint(' │', C.ok));
|
|
236
|
+
console.log(paint(' │ ', C.ok) + paint(' lbs web 启动 Web 界面', C.mint) + paint(' │', C.ok));
|
|
237
|
+
console.log(paint(' │ │', C.ok));
|
|
238
|
+
console.log(paint(' │ ', C.ok) + paint('状态目录: ', C.dim) + paint(STATE_ROOT, C.mint) + paint(' │', C.ok));
|
|
239
|
+
console.log(paint(' └─────────────────────────────────────────────────────────┘', C.ok));
|
|
235
240
|
console.log();
|
|
236
241
|
}
|
|
237
242
|
|