@baimingtao/lbs-agent 1.3.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 +44 -31
- package/interfaces/cli/_impl/cli_theme.py +179 -146
- package/interfaces/cli/_impl/commands/chat_repl.py +83 -92
- package/interfaces/cli/_impl/commands/setup.py +20 -20
- package/package.json +1 -1
- package/scripts/init.js +31 -31
package/bin/lbs.js
CHANGED
|
@@ -46,21 +46,21 @@ const INIT_SCRIPT = path.join(PKG_ROOT, 'scripts', 'init.js');
|
|
|
46
46
|
|
|
47
47
|
// ── 颜色与图标 (对标 Hermes banner/colors) ─────────────────────
|
|
48
48
|
|
|
49
|
-
// ──
|
|
49
|
+
// ── Aurora Pink 配色 ───────────────────────────────────────────
|
|
50
50
|
|
|
51
51
|
const C = {
|
|
52
52
|
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
function supportsColor() {
|
|
@@ -74,23 +74,36 @@ function paint(text, ...colors) {
|
|
|
74
74
|
return colors.join('') + text + C.reset;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
//
|
|
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
|
+
}
|
|
78
98
|
|
|
79
|
-
|
|
80
|
-
'',
|
|
81
|
-
' ' + paint('╔══╗', C.sakura) + ' ' + paint('╔═══╗', C.mint) + ' ' + paint('╔═════╗', C.mint),
|
|
82
|
-
' ' + paint('║ ║', C.sakura) + ' ' + paint('║ ║', C.mint) + ' ' + paint('║ ╔═╝', C.mint),
|
|
83
|
-
' ' + paint('╠══╣', C.sakura) + ' ' + paint('╠═══╣', C.mint) + ' ' + paint('║ ╚═╗', C.mint),
|
|
84
|
-
' ' + paint('║ ║', C.sakura) + ' ' + paint('║ ║', C.mint) + ' ' + paint('╚═════╝', C.mint),
|
|
85
|
-
' ' + paint('╚══╝', C.sakura) + ' ' + paint('╚═══╝', C.mint) + ' ' + paint('AGENT', C.dim),
|
|
86
|
-
].join('\n');
|
|
99
|
+
// ── Banner — 极简留白 ─────────────────────────────────────────
|
|
87
100
|
|
|
88
101
|
function printBanner() {
|
|
89
|
-
|
|
90
|
-
console.log(
|
|
91
|
-
console.log(
|
|
92
|
-
|
|
93
|
-
|
|
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));
|
|
94
107
|
console.log();
|
|
95
108
|
}
|
|
96
109
|
// ── 初始化检查 ─────────────────────────────────────────────────
|
|
@@ -102,13 +115,13 @@ function isInitialized() {
|
|
|
102
115
|
function ensureInitialized() {
|
|
103
116
|
if (isInitialized()) return true;
|
|
104
117
|
|
|
105
|
-
console.log(paint('⚙ ', C.
|
|
118
|
+
console.log(paint('⚙ ', C.pink) + paint('首次运行,正在初始化 LBS Agent...', C.bold));
|
|
106
119
|
console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.mint));
|
|
107
120
|
console.log(paint(' 创建 Python 虚拟环境 + 安装依赖(约 1-2 分钟)', C.dim));
|
|
108
121
|
console.log();
|
|
109
122
|
|
|
110
123
|
if (!fs.existsSync(INIT_SCRIPT)) {
|
|
111
|
-
console.error(paint('✖ ', C.
|
|
124
|
+
console.error(paint('✖ ', C.err) + '初始化脚本不存在: ' + INIT_SCRIPT);
|
|
112
125
|
return false;
|
|
113
126
|
}
|
|
114
127
|
|
|
@@ -119,11 +132,11 @@ function ensureInitialized() {
|
|
|
119
132
|
});
|
|
120
133
|
|
|
121
134
|
if (result.status !== 0) {
|
|
122
|
-
console.error(paint('\n✖ ', C.
|
|
135
|
+
console.error(paint('\n✖ ', C.err) + '初始化失败');
|
|
123
136
|
return false;
|
|
124
137
|
}
|
|
125
138
|
|
|
126
|
-
console.log(paint('\n✓ ', C.
|
|
139
|
+
console.log(paint('\n✓ ', C.ok) + paint('初始化完成!', C.bold, C.ok));
|
|
127
140
|
console.log();
|
|
128
141
|
return true;
|
|
129
142
|
}
|
|
@@ -144,7 +157,7 @@ if (subcmd === 'init' || subcmd === '--init') {
|
|
|
144
157
|
});
|
|
145
158
|
process.exit(result.status || 0);
|
|
146
159
|
}
|
|
147
|
-
console.error(paint('✖ ', C.
|
|
160
|
+
console.error(paint('✖ ', C.err) + '初始化脚本不存在');
|
|
148
161
|
process.exit(1);
|
|
149
162
|
}
|
|
150
163
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
"""LBS CLI
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
green #45D6A3 → 翠绿 (成功)
|
|
1
|
+
"""LBS CLI Theme — Aurora Pink 现代终端设计系统。
|
|
2
|
+
|
|
3
|
+
设计原则:
|
|
4
|
+
留白 > 密集
|
|
5
|
+
符号 > 字符画
|
|
6
|
+
渐变 > 纯色
|
|
7
|
+
|
|
8
|
+
对标: Claude Code, Gemini CLI, Warp Terminal, Charm BubbleTea
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
from __future__ import annotations
|
|
@@ -15,51 +14,37 @@ import os
|
|
|
15
14
|
import sys
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
# ──
|
|
19
|
-
|
|
20
|
-
class
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
OK = "\033[1;38;2;69;214;163m" # green bold
|
|
50
|
-
ERR = "\033[1;38;2;255;107;129m" # red bold
|
|
51
|
-
WARN = "\033[1;38;2;255;184;107m" # honey bold
|
|
52
|
-
INFO = "\033[38;2;101;168;255m" # blue
|
|
53
|
-
DIM_TEXT = "\033[2m"
|
|
54
|
-
|
|
55
|
-
# 渐变 (用多段 ANSI 近似模拟)
|
|
56
|
-
GRADIENT_PINK = "\033[1;38;2;255;183;197m"
|
|
57
|
-
GRADIENT_MINT = "\033[1;38;2;152;216;200m"
|
|
58
|
-
GRADIENT_VIOLET = "\033[1;38;2;110;90;168m"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _use_color() -> bool:
|
|
62
|
-
"""检测是否使用彩色输出。"""
|
|
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:
|
|
63
48
|
if os.environ.get("NO_COLOR"):
|
|
64
49
|
return False
|
|
65
50
|
if os.environ.get("TERM") == "dumb":
|
|
@@ -67,134 +52,182 @@ def _use_color() -> bool:
|
|
|
67
52
|
return sys.stdout.isatty()
|
|
68
53
|
|
|
69
54
|
|
|
70
|
-
def
|
|
71
|
-
"""
|
|
72
|
-
if not
|
|
55
|
+
def p(text: str, *codes: str) -> str:
|
|
56
|
+
"""上色"""
|
|
57
|
+
if not _ok():
|
|
73
58
|
return text
|
|
74
|
-
return "".join(codes) + text +
|
|
59
|
+
return "".join(codes) + text + C.RESET
|
|
75
60
|
|
|
76
61
|
|
|
77
|
-
|
|
62
|
+
def dim(text: str) -> str:
|
|
63
|
+
return p(text, C.SUBTLE)
|
|
78
64
|
|
|
79
|
-
def sakura_label(text: str) -> str:
|
|
80
|
-
"""粉色标签 (用于品牌名)。"""
|
|
81
|
-
return paint(text, Theme.BOLD, Theme.SAKURA_DARK)
|
|
82
65
|
|
|
66
|
+
def faint(text: str) -> str:
|
|
67
|
+
return p(text, C.FAINT)
|
|
83
68
|
|
|
84
|
-
def mint_label(text: str) -> str:
|
|
85
|
-
"""薄荷标签 (用于成功状态)。"""
|
|
86
|
-
return paint(text, Theme.BOLD, Theme.MINT_DARK)
|
|
87
69
|
|
|
70
|
+
def pink(text: str) -> str:
|
|
71
|
+
return p(text, C.PINK)
|
|
88
72
|
|
|
89
|
-
def dim(text: str) -> str:
|
|
90
|
-
"""灰色辅助文字。"""
|
|
91
|
-
return paint(text, Theme.DIM)
|
|
92
73
|
|
|
74
|
+
def purple(text: str) -> str:
|
|
75
|
+
return p(text, C.PURPLE)
|
|
93
76
|
|
|
94
|
-
def agent_badge(name: str) -> str:
|
|
95
|
-
"""Agent 名称紫色胶囊。"""
|
|
96
|
-
return paint(f" {name} ", Theme.BOLD, Theme.VIOLET)
|
|
97
77
|
|
|
78
|
+
def mint(text: str) -> str:
|
|
79
|
+
return p(text, C.MINT_D)
|
|
98
80
|
|
|
99
|
-
def model_pill(model: str) -> str:
|
|
100
|
-
"""模型名粉色文字。"""
|
|
101
|
-
return paint(model, Theme.SAKURA_DARK)
|
|
102
81
|
|
|
82
|
+
def ok(text: str) -> str:
|
|
83
|
+
return p(text, C.OK)
|
|
103
84
|
|
|
104
|
-
def status_dot(ok: bool = True) -> str:
|
|
105
|
-
"""状态圆点。"""
|
|
106
|
-
color = Theme.GREEN if ok else Theme.RED
|
|
107
|
-
return paint("●", color)
|
|
108
85
|
|
|
86
|
+
def err(text: str) -> str:
|
|
87
|
+
return p(text, C.ERR)
|
|
109
88
|
|
|
110
|
-
def divider(width: int = 56) -> str:
|
|
111
|
-
"""分隔线 — 樱花粉渐变薄荷。"""
|
|
112
|
-
return paint("─" * width, Theme.SAKURA, Theme.DIM)
|
|
113
89
|
|
|
90
|
+
# ── 组件 ───────────────────────────────────────────────────────
|
|
114
91
|
|
|
115
|
-
def
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
]
|
|
128
120
|
return "\n".join(lines)
|
|
129
121
|
|
|
130
122
|
|
|
131
|
-
def
|
|
132
|
-
"""
|
|
133
|
-
|
|
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)
|
|
134
141
|
|
|
135
142
|
|
|
136
|
-
def
|
|
137
|
-
"""
|
|
138
|
-
return
|
|
143
|
+
def separator(width: int = 52) -> str:
|
|
144
|
+
"""分隔线 — 最弱色"""
|
|
145
|
+
return p("─" * width, C.FAINT)
|
|
139
146
|
|
|
140
147
|
|
|
141
|
-
def
|
|
142
|
-
"""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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"),
|
|
147
161
|
]
|
|
148
|
-
|
|
162
|
+
parts = []
|
|
163
|
+
for key, label in keys:
|
|
164
|
+
parts.append(f"{p(key, C.PURPLE)} {faint(label)}")
|
|
165
|
+
return faint(" ") + faint(" · ".join(parts))
|
|
149
166
|
|
|
150
167
|
|
|
151
|
-
def
|
|
152
|
-
"""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
paint(t, Theme.MINT_DARK) for t in tools
|
|
158
|
-
)
|
|
159
|
-
lines = [
|
|
160
|
-
paint("━" * 56, Theme.SAKURA, Theme.DIM),
|
|
161
|
-
f" {paint('Agent', Theme.DIM)} {agent_badge(agent_display)}",
|
|
162
|
-
f" {paint('Model', Theme.DIM)} {model_str}",
|
|
163
|
-
f" {paint('Tools', Theme.DIM)} {tools_str}",
|
|
164
|
-
paint("━" * 56, Theme.SAKURA, Theme.DIM),
|
|
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}"),
|
|
165
174
|
]
|
|
166
|
-
return "
|
|
175
|
+
return " " + faint(" · ").join(parts)
|
|
167
176
|
|
|
168
177
|
|
|
169
|
-
def
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
return f"\n {paint('⟡', Theme.SAKURA)} {paint(f'iter {iteration}', Theme.BOLD, Theme.SAKURA_DARK)}{paint(f'/{max_iterations}', Theme.DIM)}{phase_str}"
|
|
178
|
+
def result_text(content: str) -> str:
|
|
179
|
+
"""最终结果"""
|
|
180
|
+
return p(content, C.TEXT)
|
|
173
181
|
|
|
174
182
|
|
|
175
|
-
def
|
|
176
|
-
"""
|
|
177
|
-
return
|
|
183
|
+
def error_text(content: str) -> str:
|
|
184
|
+
"""错误"""
|
|
185
|
+
return p(f"✕ {content}", C.ERR)
|
|
178
186
|
|
|
179
187
|
|
|
180
|
-
def
|
|
181
|
-
"""
|
|
182
|
-
return
|
|
188
|
+
def agent_tag(name: str) -> str:
|
|
189
|
+
"""Agent 标签"""
|
|
190
|
+
return p(f"◐ {name}", C.PURPLE)
|
|
183
191
|
|
|
184
192
|
|
|
185
|
-
def
|
|
186
|
-
"""
|
|
187
|
-
return
|
|
193
|
+
def model_tag(model: str) -> str:
|
|
194
|
+
"""模型标签"""
|
|
195
|
+
return p(f"✦ {model}", C.PINK)
|
|
188
196
|
|
|
189
197
|
|
|
190
|
-
def
|
|
191
|
-
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
f" {
|
|
197
|
-
f" {
|
|
198
|
-
f" {
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
|
@@ -24,21 +13,21 @@ from application.container import AgentSystemContainer
|
|
|
24
13
|
from config.config import AppConfig
|
|
25
14
|
from interfaces.cli._impl.commands.common import project_root
|
|
26
15
|
from interfaces.cli._impl.cli_theme import (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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,
|
|
31
21
|
)
|
|
32
22
|
|
|
33
23
|
|
|
34
24
|
@dataclass
|
|
35
25
|
class ChatState:
|
|
36
|
-
"""REPL 运行时状态。"""
|
|
37
26
|
container: object = None
|
|
38
27
|
orchestrator: object = None
|
|
39
28
|
session_service: object = None
|
|
40
29
|
session_id: str = ""
|
|
41
|
-
agent_name: str = "
|
|
30
|
+
agent_name: str = "chat"
|
|
42
31
|
provider_name: str = ""
|
|
43
32
|
model_name: str = ""
|
|
44
33
|
turn_count: int = 0
|
|
@@ -46,11 +35,9 @@ class ChatState:
|
|
|
46
35
|
|
|
47
36
|
|
|
48
37
|
def _resolve_agent(config: AppConfig) -> str:
|
|
49
|
-
"""选默认 agent:chat 优先(REPL 专用),其次 main/第一个。"""
|
|
50
38
|
names = list(config.agents.agents.keys()) if config.agents else []
|
|
51
39
|
if not names:
|
|
52
40
|
raise RuntimeError("没有配置任何 agent")
|
|
53
|
-
# chat 是 REPL 专用(无 evidence 强制,纯对话)
|
|
54
41
|
if "chat" in names:
|
|
55
42
|
return "chat"
|
|
56
43
|
if "main" in names:
|
|
@@ -59,38 +46,47 @@ def _resolve_agent(config: AppConfig) -> str:
|
|
|
59
46
|
|
|
60
47
|
|
|
61
48
|
def _print_banner(state: ChatState) -> None:
|
|
62
|
-
"""启动
|
|
49
|
+
"""启动 — 极简留白"""
|
|
63
50
|
print()
|
|
64
|
-
print(
|
|
65
|
-
|
|
66
|
-
|
|
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))
|
|
67
61
|
print()
|
|
68
|
-
print(
|
|
62
|
+
print(separator())
|
|
63
|
+
print(f" {faint('Ask anything...')}")
|
|
64
|
+
print(separator())
|
|
69
65
|
print()
|
|
70
66
|
|
|
71
67
|
|
|
72
68
|
def _print_turn_header(state: ChatState) -> None:
|
|
73
|
-
"""
|
|
69
|
+
"""输入提示符 — 只有 ❯"""
|
|
74
70
|
print()
|
|
75
|
-
print(
|
|
71
|
+
print(f" {prompt_symbol()} ", end="", flush=True)
|
|
76
72
|
|
|
77
73
|
|
|
78
74
|
def _print_help() -> None:
|
|
79
75
|
print()
|
|
80
|
-
print(f" {
|
|
76
|
+
print(f" {gradient_text('Commands')}")
|
|
81
77
|
print()
|
|
82
78
|
cmds = [
|
|
83
|
-
("/help", "
|
|
84
|
-
("/exit", "退出
|
|
85
|
-
("/new", "
|
|
86
|
-
("/status", "
|
|
87
|
-
("/agent", "
|
|
88
|
-
("/model", "
|
|
89
|
-
("/history", "
|
|
79
|
+
("/help", "显示帮助"),
|
|
80
|
+
("/exit", "退出"),
|
|
81
|
+
("/new", "新 session"),
|
|
82
|
+
("/status", "状态"),
|
|
83
|
+
("/agent", "切换 agent"),
|
|
84
|
+
("/model", "切换 model"),
|
|
85
|
+
("/history", "历史"),
|
|
90
86
|
("/clear", "清屏"),
|
|
91
87
|
]
|
|
92
88
|
for cmd, desc in cmds:
|
|
93
|
-
print(f" {
|
|
89
|
+
print(f" {pink(cmd.ljust(12))} {faint(desc)}")
|
|
94
90
|
print()
|
|
95
91
|
|
|
96
92
|
|
|
@@ -98,11 +94,8 @@ def _cmd_list_agents(state: ChatState) -> None:
|
|
|
98
94
|
agents = state.container.config.agents.agents
|
|
99
95
|
print()
|
|
100
96
|
for name, a in agents.items():
|
|
101
|
-
if name == state.agent_name
|
|
102
|
-
|
|
103
|
-
else:
|
|
104
|
-
marker = ""
|
|
105
|
-
print(f" {agent_badge(name)} {dim(a.display_name)}{marker}")
|
|
97
|
+
current = f" {ok('← current')}" if name == state.agent_name else ""
|
|
98
|
+
print(f" {agent_tag(name)} {faint(a.display_name)}{current}")
|
|
106
99
|
print()
|
|
107
100
|
|
|
108
101
|
|
|
@@ -110,20 +103,21 @@ def _cmd_list_models(state: ChatState) -> None:
|
|
|
110
103
|
config = state.container.config
|
|
111
104
|
provider_name = state.provider_name or config.models.default_provider
|
|
112
105
|
if provider_name not in config.models.providers:
|
|
113
|
-
|
|
106
|
+
msg = f"无 provider '{provider_name}'"
|
|
107
|
+
print(f" {error_text(msg)}")
|
|
114
108
|
return
|
|
115
|
-
|
|
109
|
+
prov = config.models.providers[provider_name]
|
|
116
110
|
print()
|
|
117
|
-
print(f" {
|
|
118
|
-
for m in
|
|
119
|
-
marker = f"
|
|
120
|
-
print(f" {
|
|
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}")
|
|
121
115
|
print()
|
|
122
116
|
|
|
123
117
|
|
|
124
118
|
def _cmd_history(state: ChatState, n: int = 5) -> None:
|
|
125
119
|
if not state.session_id:
|
|
126
|
-
print(f" {
|
|
120
|
+
print(f" {faint('无 session')}")
|
|
127
121
|
return
|
|
128
122
|
try:
|
|
129
123
|
messages = list(state.session_service.session_store.load_session_messages(state.session_id))
|
|
@@ -134,33 +128,31 @@ def _cmd_history(state: ChatState, n: int = 5) -> None:
|
|
|
134
128
|
if role in ("user", "assistant"):
|
|
135
129
|
recent.append((role, content))
|
|
136
130
|
print()
|
|
137
|
-
print(f" {dim(f'最近 {len(recent)} 条消息')}")
|
|
138
131
|
for role, content in recent:
|
|
139
|
-
|
|
140
|
-
print(f" {
|
|
132
|
+
rc = C.PINK if role == "user" else C.MINT_D
|
|
133
|
+
print(f" {p(role.ljust(9), rc)} {faint(content)}")
|
|
141
134
|
print()
|
|
142
135
|
except Exception as e:
|
|
143
|
-
|
|
136
|
+
msg = f"读取失败: {e}"
|
|
137
|
+
print(f" {error_text(msg)}")
|
|
144
138
|
|
|
145
139
|
|
|
146
140
|
def _cmd_status(state: ChatState) -> None:
|
|
147
141
|
elapsed = int(time.time() - state.started_at)
|
|
148
142
|
rows = [
|
|
149
|
-
("session", state.session_id
|
|
150
|
-
("agent",
|
|
151
|
-
("
|
|
152
|
-
("
|
|
153
|
-
("turns",
|
|
154
|
-
("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")),
|
|
155
149
|
]
|
|
156
150
|
print()
|
|
157
|
-
|
|
158
|
-
print(kv_row(label, val))
|
|
151
|
+
print(session_card(rows))
|
|
159
152
|
print()
|
|
160
153
|
|
|
161
154
|
|
|
162
155
|
def _handle_command(state: ChatState, line: str) -> bool:
|
|
163
|
-
"""处理 / 命令。返回 True 表示继续循环,False 表示退出。"""
|
|
164
156
|
parts = line.strip().split(maxsplit=1)
|
|
165
157
|
cmd = parts[0].lower()
|
|
166
158
|
arg = parts[1] if len(parts) > 1 else ""
|
|
@@ -173,13 +165,14 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
173
165
|
state.session_id = ""
|
|
174
166
|
state.turn_count = 0
|
|
175
167
|
state.started_at = time.time()
|
|
176
|
-
print(f" {
|
|
168
|
+
print(f" {ok('◇')} {faint('new session')}")
|
|
177
169
|
elif cmd == "/session":
|
|
178
170
|
if arg:
|
|
179
171
|
state.session_id = arg
|
|
180
|
-
print(f" {
|
|
172
|
+
print(f" {ok('◇')} {faint('switched to')} {pink(arg)}")
|
|
181
173
|
else:
|
|
182
|
-
|
|
174
|
+
sid = state.session_id or "(none)"
|
|
175
|
+
print(f" {faint('session:')} {sid}")
|
|
183
176
|
elif cmd == "/status":
|
|
184
177
|
_cmd_status(state)
|
|
185
178
|
elif cmd == "/agent":
|
|
@@ -189,15 +182,16 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
189
182
|
agents = state.container.config.agents.agents
|
|
190
183
|
if arg in agents:
|
|
191
184
|
state.agent_name = arg
|
|
192
|
-
print(f" {
|
|
185
|
+
print(f" {ok('◇')} {faint('agent')} {agent_tag(arg)}")
|
|
193
186
|
else:
|
|
194
|
-
|
|
187
|
+
msg = f"无 agent '{arg}'"
|
|
188
|
+
print(f" {error_text(msg)}")
|
|
195
189
|
elif cmd == "/model":
|
|
196
190
|
if not arg:
|
|
197
191
|
_cmd_list_models(state)
|
|
198
192
|
else:
|
|
199
193
|
state.model_name = arg
|
|
200
|
-
print(f" {
|
|
194
|
+
print(f" {ok('◇')} {faint('model')} {model_tag(arg)}")
|
|
201
195
|
elif cmd == "/clear":
|
|
202
196
|
print("\033[2J\033[H", end="")
|
|
203
197
|
elif cmd == "/history":
|
|
@@ -205,16 +199,16 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
205
199
|
n = int(arg) if arg else 5
|
|
206
200
|
_cmd_history(state, n)
|
|
207
201
|
except ValueError:
|
|
208
|
-
print(f" {
|
|
202
|
+
print(f" {error_text('/history [n] — n 须为整数')}")
|
|
209
203
|
elif cmd == "/save":
|
|
210
|
-
print(f" {
|
|
204
|
+
print(f" {faint('session 每次 turn 自动保存')}")
|
|
211
205
|
else:
|
|
212
|
-
print(f" {
|
|
206
|
+
print(f" {error_text(f'未知命令: {cmd}')}")
|
|
213
207
|
return True
|
|
214
208
|
|
|
215
209
|
|
|
216
210
|
def _run_turn(state: ChatState, user_input: str) -> None:
|
|
217
|
-
"""
|
|
211
|
+
"""跑一轮对话"""
|
|
218
212
|
t0 = time.time()
|
|
219
213
|
try:
|
|
220
214
|
result = state.orchestrator.run_agent_sync(
|
|
@@ -224,24 +218,23 @@ def _run_turn(state: ChatState, user_input: str) -> None:
|
|
|
224
218
|
session_id=state.session_id,
|
|
225
219
|
)
|
|
226
220
|
elapsed = time.time() - t0
|
|
227
|
-
# 显示结果
|
|
228
221
|
if result.success:
|
|
229
222
|
content = (result.content or "").strip()
|
|
230
|
-
print(f"\n{
|
|
223
|
+
print(f"\n{result_text(content)}")
|
|
231
224
|
else:
|
|
232
|
-
|
|
233
|
-
print(f"\n {
|
|
234
|
-
|
|
235
|
-
print(usage_bar(elapsed, result.iterations, result.total_tool_calls))
|
|
236
|
-
# 保存 session_id
|
|
225
|
+
e = result.error or "(无错误信息)"
|
|
226
|
+
print(f"\n {error_text(e[:300])}")
|
|
227
|
+
print(usage_line(elapsed, result.iterations, result.total_tool_calls))
|
|
237
228
|
if not state.session_id:
|
|
238
|
-
|
|
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)}"
|
|
239
231
|
except KeyboardInterrupt:
|
|
240
|
-
print(f"\n {
|
|
232
|
+
print(f"\n {p('⏸', C.WARN)} {faint('已中断')}")
|
|
241
233
|
except Exception as e:
|
|
242
234
|
elapsed = time.time() - t0
|
|
243
|
-
|
|
244
|
-
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')}")
|
|
245
238
|
|
|
246
239
|
|
|
247
240
|
def cmd_chat(
|
|
@@ -255,14 +248,12 @@ def cmd_chat(
|
|
|
255
248
|
"""进入 chat REPL 或单次 query。"""
|
|
256
249
|
container = AgentSystemContainer(config=config, project_root=project_root)
|
|
257
250
|
|
|
258
|
-
# 选 agent
|
|
259
251
|
if not agent_name:
|
|
260
252
|
agent_name = _resolve_agent(config)
|
|
261
253
|
|
|
262
|
-
# 验证 agent 存在
|
|
263
254
|
if agent_name not in config.agents.agents:
|
|
264
255
|
msg = f"Agent '{agent_name}' 不存在"
|
|
265
|
-
print(f" {
|
|
256
|
+
print(f" {error_text(msg)}")
|
|
266
257
|
return 1
|
|
267
258
|
|
|
268
259
|
state = ChatState(
|
|
@@ -275,7 +266,6 @@ def cmd_chat(
|
|
|
275
266
|
session_id=session_id,
|
|
276
267
|
)
|
|
277
268
|
|
|
278
|
-
# 单次 query 模式
|
|
279
269
|
if query:
|
|
280
270
|
state.turn_count = 1
|
|
281
271
|
_print_turn_header(state)
|
|
@@ -283,9 +273,10 @@ def cmd_chat(
|
|
|
283
273
|
_run_turn(state, query)
|
|
284
274
|
return 0
|
|
285
275
|
|
|
286
|
-
# REPL
|
|
276
|
+
# REPL
|
|
287
277
|
_print_banner(state)
|
|
288
|
-
|
|
278
|
+
print(footer())
|
|
279
|
+
print()
|
|
289
280
|
|
|
290
281
|
while True:
|
|
291
282
|
try:
|
|
@@ -295,7 +286,7 @@ def cmd_chat(
|
|
|
295
286
|
print("\n")
|
|
296
287
|
break
|
|
297
288
|
except KeyboardInterrupt:
|
|
298
|
-
print(f"\n {
|
|
289
|
+
print(f"\n {p('用 /exit 退出', C.WARN)}")
|
|
299
290
|
continue
|
|
300
291
|
|
|
301
292
|
line = line.strip()
|
|
@@ -308,5 +299,5 @@ def cmd_chat(
|
|
|
308
299
|
state.turn_count += 1
|
|
309
300
|
_run_turn(state, line)
|
|
310
301
|
|
|
311
|
-
print(f"\n {
|
|
302
|
+
print(f"\n {ok('◇')} {faint('Session saved. Bye.')}")
|
|
312
303
|
return 0
|
|
@@ -73,10 +73,10 @@ 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
|
|
|
@@ -85,7 +85,7 @@ def _info(msg: str) -> None:
|
|
|
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:
|
|
@@ -93,7 +93,7 @@ def _warn(msg: str) -> None:
|
|
|
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:
|
|
@@ -111,7 +111,7 @@ def _prompt_choice(label: str, options: list[str], default: str = "") -> str:
|
|
|
111
111
|
"""带选项列表的输入提示。"""
|
|
112
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():
|
|
@@ -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
|
|
package/package.json
CHANGED
package/scripts/init.js
CHANGED
|
@@ -63,7 +63,7 @@ function ensureDir(dir) {
|
|
|
63
63
|
// ── Step 1: 检测系统 Python ────────────────────────────────────
|
|
64
64
|
|
|
65
65
|
function findSystemPython() {
|
|
66
|
-
console.log(paint(' 🔍 ', C.
|
|
66
|
+
console.log(paint(' 🔍 ', C.pink) + '检测系统 Python...');
|
|
67
67
|
|
|
68
68
|
const candidates = isWin
|
|
69
69
|
? ['python', 'python3', 'py -3']
|
|
@@ -73,7 +73,7 @@ function findSystemPython() {
|
|
|
73
73
|
const result = run(cmd, ['--version'], { silent: true, shell: isWin });
|
|
74
74
|
if (result.status === 0) {
|
|
75
75
|
const version = (result.stdout || result.stderr || '').trim();
|
|
76
|
-
console.log(paint(' ✓ ', C.
|
|
76
|
+
console.log(paint(' ✓ ', C.ok) + `${cmd}: ${version}`);
|
|
77
77
|
|
|
78
78
|
const match = version.match(/(\d+)\.(\d+)/);
|
|
79
79
|
if (match) {
|
|
@@ -81,11 +81,11 @@ function findSystemPython() {
|
|
|
81
81
|
const minor = parseInt(match[2]);
|
|
82
82
|
if (major >= 3 && minor >= 10) return cmd;
|
|
83
83
|
}
|
|
84
|
-
console.log(paint(' ⚠ ', C.
|
|
84
|
+
console.log(paint(' ⚠ ', C.warn) + '版本低于 3.10,继续寻找...');
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
console.error(paint('\n ✖ ', C.
|
|
88
|
+
console.error(paint('\n ✖ ', C.err) + '未找到 Python 3.10+');
|
|
89
89
|
console.error(paint(' ', C.dim) + 'Windows: https://www.python.org/downloads/');
|
|
90
90
|
console.error(paint(' ', C.dim) + 'macOS: brew install python@3.12');
|
|
91
91
|
console.error(paint(' ', C.dim) + 'Linux: sudo apt install python3 python3-venv');
|
|
@@ -96,20 +96,20 @@ function findSystemPython() {
|
|
|
96
96
|
|
|
97
97
|
function createVenv(sysPython) {
|
|
98
98
|
if (fs.existsSync(PYTHON)) {
|
|
99
|
-
console.log(paint(' ✓ ', C.
|
|
99
|
+
console.log(paint(' ✓ ', C.ok) + '虚拟环境已存在,跳过创建');
|
|
100
100
|
return true;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// 确保 state root 存在
|
|
104
104
|
ensureDir(STATE_ROOT);
|
|
105
|
-
console.log(paint(' 📦 ', C.
|
|
105
|
+
console.log(paint(' 📦 ', C.pink) + `创建 Python 虚拟环境 → ${paint(STATE_ROOT + '/venv', C.mint)}`);
|
|
106
106
|
|
|
107
107
|
const result = run(sysPython, ['-m', 'venv', VENV_DIR]);
|
|
108
108
|
if (result.status !== 0 || !fs.existsSync(PYTHON)) {
|
|
109
|
-
console.error(paint(' ✖ ', C.
|
|
109
|
+
console.error(paint(' ✖ ', C.err) + '创建虚拟环境失败');
|
|
110
110
|
return false;
|
|
111
111
|
}
|
|
112
|
-
console.log(paint(' ✓ ', C.
|
|
112
|
+
console.log(paint(' ✓ ', C.ok) + 'venv 已创建');
|
|
113
113
|
return true;
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -117,17 +117,17 @@ function createVenv(sysPython) {
|
|
|
117
117
|
|
|
118
118
|
function installDeps() {
|
|
119
119
|
if (!fs.existsSync(REQUIREMENTS)) {
|
|
120
|
-
console.error(paint(' ✖ ', C.
|
|
120
|
+
console.error(paint(' ✖ ', C.err) + 'requirements.txt 不存在: ' + REQUIREMENTS);
|
|
121
121
|
return false;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
console.log(paint(' 📥 ', C.
|
|
124
|
+
console.log(paint(' 📥 ', C.pink) + '安装 Python 依赖 (可能需要 1-2 分钟)...');
|
|
125
125
|
const result = run(PIP, ['install', '-r', REQUIREMENTS, '--disable-pip-version-check']);
|
|
126
126
|
if (result.status !== 0) {
|
|
127
|
-
console.error(paint(' ✖ ', C.
|
|
127
|
+
console.error(paint(' ✖ ', C.err) + '依赖安装失败');
|
|
128
128
|
return false;
|
|
129
129
|
}
|
|
130
|
-
console.log(paint(' ✓ ', C.
|
|
130
|
+
console.log(paint(' ✓ ', C.ok) + '依赖安装完成');
|
|
131
131
|
return true;
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -161,22 +161,22 @@ function createDefaultDirs() {
|
|
|
161
161
|
fs.writeFileSync(configPath, defaultConfig, 'utf-8');
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
console.log(paint(' ✓ ', C.
|
|
164
|
+
console.log(paint(' ✓ ', C.ok) + '目录结构已创建');
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// ── Step 5: 验证 ───────────────────────────────────────────────
|
|
168
168
|
|
|
169
169
|
function verifyInstall() {
|
|
170
|
-
console.log(paint(' 🔍 ', C.
|
|
170
|
+
console.log(paint(' 🔍 ', C.pink) + '验证安装...');
|
|
171
171
|
const result = run(PYTHON, [path.join(PKG_ROOT, 'lbs.py'), 'status'], {
|
|
172
172
|
silent: true,
|
|
173
173
|
cwd: PKG_ROOT,
|
|
174
174
|
env: { ...process.env, LBS_HOME: STATE_ROOT },
|
|
175
175
|
});
|
|
176
176
|
if (result.status === 0) {
|
|
177
|
-
console.log(paint(' ✓ ', C.
|
|
177
|
+
console.log(paint(' ✓ ', C.ok) + 'lbs status 运行正常');
|
|
178
178
|
} else {
|
|
179
|
-
console.log(paint(' ⚠ ', C.
|
|
179
|
+
console.log(paint(' ⚠ ', C.warn) + 'lbs status 运行异常(可能是配置问题,不影响初始化)');
|
|
180
180
|
}
|
|
181
181
|
return true;
|
|
182
182
|
}
|
|
@@ -191,16 +191,16 @@ function writeFlag() {
|
|
|
191
191
|
platform: os.platform(),
|
|
192
192
|
node_version: process.version,
|
|
193
193
|
}, null, 2));
|
|
194
|
-
console.log(paint(' ✓ ', C.
|
|
194
|
+
console.log(paint(' ✓ ', C.ok) + '.lbs-initialized 已创建');
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// ── 主流程 ─────────────────────────────────────────────────────
|
|
198
198
|
|
|
199
199
|
function main() {
|
|
200
200
|
console.log();
|
|
201
|
-
console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.
|
|
202
|
-
console.log(paint(' │ ', C.
|
|
203
|
-
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));
|
|
204
204
|
console.log();
|
|
205
205
|
console.log(paint(' 包目录 (只读): ', C.dim) + paint(PKG_ROOT, C.mint));
|
|
206
206
|
console.log(paint(' 状态目录 (可写): ', C.dim) + paint(STATE_ROOT, C.mint));
|
|
@@ -226,17 +226,17 @@ function main() {
|
|
|
226
226
|
writeFlag();
|
|
227
227
|
|
|
228
228
|
console.log();
|
|
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.
|
|
235
|
-
console.log(paint(' │ ', C.
|
|
236
|
-
console.log(paint(' │ ', C.
|
|
237
|
-
console.log(paint(' │ │', C.
|
|
238
|
-
console.log(paint(' │ ', C.
|
|
239
|
-
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));
|
|
240
240
|
console.log();
|
|
241
241
|
}
|
|
242
242
|
|