@baimingtao/lbs-agent 1.1.0 → 1.3.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
@@ -46,17 +46,21 @@ const INIT_SCRIPT = path.join(PKG_ROOT, 'scripts', 'init.js');
46
46
 
47
47
  // ── 颜色与图标 (对标 Hermes banner/colors) ─────────────────────
48
48
 
49
+ // ── Sakura × Mint 配色 (对标 Web 端 tailwind.config.js) ───────
50
+
49
51
  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',
52
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
53
+ // 品牌色
54
+ sakura: '\x1b[38;2;255;183;197m', // #FFB7C5
55
+ sakuraDark: '\x1b[38;2;232;154;168m', // #E89AA8
56
+ mint: '\x1b[38;2;152;216;200m', // #98D8C8
57
+ mintDark: '\x1b[38;2;111;196;176m', // #6FC4B0
58
+ // 功能色
59
+ violet: '\x1b[38;2;110;90;168m', // #6E5AA8
60
+ honey: '\x1b[38;2;255;184;107m', // #FFB86B
61
+ green: '\x1b[38;2;69;214;163m', // #45D6A3
62
+ red: '\x1b[38;2;255;107;129m', // #FF6B81
63
+ blue: '\x1b[38;2;101;168;255m', // #65A8FF
60
64
  };
61
65
 
62
66
  function supportsColor() {
@@ -70,26 +74,25 @@ function paint(text, ...colors) {
70
74
  return colors.join('') + text + C.reset;
71
75
  }
72
76
 
73
- // ── ASCII Logo ─────────────────────────────────────────────────
77
+ // ── ASCII Logo — Sakura 主题 ───────────────────────────────────
74
78
 
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
- `;
79
+ const LOGO = [
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');
83
87
 
84
88
  function printBanner() {
85
89
  const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf-8'));
86
- console.log();
87
90
  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));
91
+ console.log(paint(' v' + pkg.version, C.bold, C.sakuraDark) +
92
+ paint(' · ', C.sakura) +
93
+ paint('Leaving Blank Space Agent', C.dim));
90
94
  console.log();
91
95
  }
92
-
93
96
  // ── 初始化检查 ─────────────────────────────────────────────────
94
97
 
95
98
  function isInitialized() {
@@ -99,8 +102,8 @@ function isInitialized() {
99
102
  function ensureInitialized() {
100
103
  if (isInitialized()) return true;
101
104
 
102
- console.log(paint('⚙ ', C.gold) + paint('首次运行,正在初始化 LBS Agent...', C.bold));
103
- console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.cyan));
105
+ console.log(paint('⚙ ', C.sakura) + paint('首次运行,正在初始化 LBS Agent...', C.bold));
106
+ console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.mint));
104
107
  console.log(paint(' 创建 Python 虚拟环境 + 安装依赖(约 1-2 分钟)', C.dim));
105
108
  console.log();
106
109
 
@@ -0,0 +1,200 @@
1
+ """LBS CLI 主题系统 — Sakura × Mint (对标 Web 端 tailwind.config.js)。
2
+
3
+ Web 端配色 → ANSI True Color 映射:
4
+ sakura #FFB7C5 → 粉色 (品牌主色)
5
+ mint #98D8C8 → 薄荷绿 (辅助色)
6
+ berry #2B1828 → 深莓 (正文)
7
+ violet #6E5AA8 → 紫色 (Agent)
8
+ honey #FFB86B → 蜂蜜 (警告)
9
+ green #45D6A3 → 翠绿 (成功)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import sys
16
+
17
+
18
+ # ── ANSI True Color 码 (直接用 Web 端 hex) ─────────────────────
19
+
20
+ class Theme:
21
+ """LBS CLI 色彩主题,映射 Web 端 tailwind config。"""
22
+
23
+ # 基础
24
+ RESET = "\033[0m"
25
+ BOLD = "\033[1m"
26
+ DIM = "\033[2m"
27
+ ITALIC = "\033[3m"
28
+ UNDERLINE = "\033[4m"
29
+
30
+ # 品牌色 (对标 tailwind)
31
+ SAKURA = "\033[38;2;255;183;197m" # #FFB7C5
32
+ SAKURA_LIGHT = "\033[38;2;255;212;222m" # #FFD4DE
33
+ SAKURA_DARK = "\033[38;2;232;154;168m" # #E89AA8
34
+ MINT = "\033[38;2;152;216;200m" # #98D8C8
35
+ MINT_LIGHT = "\033[38;2;184;232;220m" # #B8E8DC
36
+ MINT_DARK = "\033[38;2;111;196;176m" # #6FC4B0
37
+
38
+ # 功能色
39
+ BERRY = "\033[38;2;43;24;40m" # #2B1828 (深莓正文)
40
+ BERRY_LIGHT = "\033[38;2;74;47;69m" # #4A2F45
41
+ VIOLET = "\033[38;2;110;90;168m" # #6E5AA8
42
+ VIOLET_LIGHT = "\033[38;2;144;128;200m" # #9080C8
43
+ HONEY = "\033[38;2;255;184;107m" # #FFB86B
44
+ GREEN = "\033[38;2;69;214;163m" # #45D6A3
45
+ RED = "\033[38;2;255;107;129m" # #FF6B81
46
+ BLUE = "\033[38;2;101;168;255m" # #65A8FF
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
+ """检测是否使用彩色输出。"""
63
+ if os.environ.get("NO_COLOR"):
64
+ return False
65
+ if os.environ.get("TERM") == "dumb":
66
+ return False
67
+ return sys.stdout.isatty()
68
+
69
+
70
+ def paint(text: str, *codes: str) -> str:
71
+ """给文本上色 (无 TTY 时自动降级)。"""
72
+ if not _use_color():
73
+ return text
74
+ return "".join(codes) + text + Theme.RESET
75
+
76
+
77
+ # ── 高级组件 ───────────────────────────────────────────────────
78
+
79
+ def sakura_label(text: str) -> str:
80
+ """粉色标签 (用于品牌名)。"""
81
+ return paint(text, Theme.BOLD, Theme.SAKURA_DARK)
82
+
83
+
84
+ def mint_label(text: str) -> str:
85
+ """薄荷标签 (用于成功状态)。"""
86
+ return paint(text, Theme.BOLD, Theme.MINT_DARK)
87
+
88
+
89
+ def dim(text: str) -> str:
90
+ """灰色辅助文字。"""
91
+ return paint(text, Theme.DIM)
92
+
93
+
94
+ def agent_badge(name: str) -> str:
95
+ """Agent 名称紫色胶囊。"""
96
+ return paint(f" {name} ", Theme.BOLD, Theme.VIOLET)
97
+
98
+
99
+ def model_pill(model: str) -> str:
100
+ """模型名粉色文字。"""
101
+ return paint(model, Theme.SAKURA_DARK)
102
+
103
+
104
+ def status_dot(ok: bool = True) -> str:
105
+ """状态圆点。"""
106
+ color = Theme.GREEN if ok else Theme.RED
107
+ return paint("●", color)
108
+
109
+
110
+ def divider(width: int = 56) -> str:
111
+ """分隔线 — 樱花粉渐变薄荷。"""
112
+ return paint("─" * width, Theme.SAKURA, Theme.DIM)
113
+
114
+
115
+ def header_box(title: str, subtitle: str = "") -> str:
116
+ """带圆角的标题框 (对标 Web 端 glass card)。"""
117
+ w = max(len(title) + 4, 44)
118
+ top = paint("╭" + "─" * (w - 2) + "╮", Theme.SAKURA)
119
+ bottom = paint("╰" + "─" * (w - 2) + "╯", Theme.SAKURA)
120
+ mid_title = paint(f"│ {title}", Theme.BOLD, Theme.SAKURA_DARK)
121
+ mid_title += " " * (w - 2 - len(title) - 1) + paint("│", Theme.SAKURA)
122
+ lines = [top, mid_title]
123
+ if subtitle:
124
+ mid_sub = paint(f"│ ", Theme.SAKURA) + paint(subtitle, Theme.DIM)
125
+ mid_sub += " " * (w - 2 - len(subtitle) - 1) + paint("│", Theme.SAKURA)
126
+ lines.append(mid_sub)
127
+ lines.append(bottom)
128
+ return "\n".join(lines)
129
+
130
+
131
+ def kv_row(label: str, value: str, label_width: int = 9) -> str:
132
+ """键值行 (对标 Web 端 status dock)。"""
133
+ return f" {paint(label.ljust(label_width), Theme.DIM)} {paint('│', Theme.SAKURA, Theme.DIM)} {value}"
134
+
135
+
136
+ def turn_prompt(agent: str, turn: int) -> str:
137
+ """Chat turn 提示符。"""
138
+ return f"{agent_badge(agent)} {dim(f'turn {turn}')} {paint('›', Theme.SAKURA)} "
139
+
140
+
141
+ def usage_bar(seconds: float, iterations: int, tools: int) -> str:
142
+ """用量状态条 (对标 Web 端 status dock)。"""
143
+ parts = [
144
+ paint(f"{seconds:.1f}s", Theme.MINT_DARK),
145
+ paint(f"iter {iterations}", Theme.DIM),
146
+ paint(f"tools {tools}", Theme.DIM),
147
+ ]
148
+ return " " + paint(" │ ", Theme.SAKURA, Theme.DIM).join(parts)
149
+
150
+
151
+ def react_header(agent_display: str, model_chain: list[str], tools: list[str]) -> str:
152
+ """ReAct 迭代头 (对标 Web 端 ChatFlow 的任务头)。"""
153
+ model_str = paint(" → ", Theme.SAKURA).join(
154
+ paint(m, Theme.SAKURA_DARK) for m in model_chain
155
+ )
156
+ tools_str = paint(", ", Theme.DIM).join(
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),
165
+ ]
166
+ return "\n".join(lines)
167
+
168
+
169
+ def react_iteration(iteration: int, max_iterations: int, phase: str = "") -> str:
170
+ """ReAct 迭代标记。"""
171
+ phase_str = f" {paint(phase, Theme.VIOLET)}" if phase else ""
172
+ return f"\n {paint('⟡', Theme.SAKURA)} {paint(f'iter {iteration}', Theme.BOLD, Theme.SAKURA_DARK)}{paint(f'/{max_iterations}', Theme.DIM)}{phase_str}"
173
+
174
+
175
+ def thinking_block(content: str) -> str:
176
+ """思考过程块 (薄荷绿斜体)。"""
177
+ return paint(content, Theme.ITALIC, Theme.MINT_DARK)
178
+
179
+
180
+ def result_block(content: str) -> str:
181
+ """最终结果块。"""
182
+ return paint(content, Theme.BOLD)
183
+
184
+
185
+ def error_block(content: str) -> str:
186
+ """错误块。"""
187
+ return paint(f"✗ {content}", Theme.BOLD, Theme.RED)
188
+
189
+
190
+ def logo() -> str:
191
+ """LBS Agent ASCII Logo — Sakura 主题。"""
192
+ lines = [
193
+ "",
194
+ f" {paint('╔══╗', Theme.SAKURA)} {paint('╔═══╗', Theme.MINT)} {paint('╔═════╗', Theme.MINT)}",
195
+ f" {paint('║ ║', Theme.SAKURA)} {paint('║ ║', Theme.MINT)} {paint('║ ╔═╝', Theme.MINT)}",
196
+ f" {paint('╠══╣', Theme.SAKURA)} {paint('╠═══╣', Theme.MINT)} {paint('║ ╚═╗', Theme.MINT)}",
197
+ f" {paint('║ ║', Theme.SAKURA)} {paint('║ ║', Theme.MINT)} {paint('╚═════╝', Theme.MINT)}",
198
+ f" {paint('╚══╝', Theme.SAKURA)} {paint('╚═══╝', Theme.MINT)} {paint('AGENT', Theme.DIM)}",
199
+ ]
200
+ return "\n".join(lines)
@@ -1,4 +1,4 @@
1
- """LBS Agent REPL 交互模式(最小可工作版本)。
1
+ """LBS Agent REPL 交互模式。
2
2
 
3
3
  设计:
4
4
  - 用户进入交互循环,每次输入一行
@@ -23,14 +23,12 @@ from typing import Optional
23
23
  from application.container import AgentSystemContainer
24
24
  from config.config import AppConfig
25
25
  from interfaces.cli._impl.commands.common import project_root
26
-
27
-
28
- BANNER = """\033[1;36m
29
- ╔══════════════════════════════════════════════════════════════╗
30
- ║ LBS Agent — Interactive Chat Mode ║
31
- ║ 内建命令: /help /exit /new /clear /status /model /agent ║
32
- ║ 输入 / 开头是命令;其他内容发送给当前 agent ║
33
- ╚══════════════════════════════════════════════════════════════╝\033[0m"""
26
+ from interfaces.cli._impl.cli_theme import (
27
+ Theme, paint, dim, logo, agent_badge, model_pill, status_dot,
28
+ divider, header_box, kv_row, turn_prompt, usage_bar,
29
+ react_header, react_iteration, thinking_block, result_block,
30
+ error_block, sakura_label, mint_label,
31
+ )
34
32
 
35
33
 
36
34
  @dataclass
@@ -60,81 +58,105 @@ def _resolve_agent(config: AppConfig) -> str:
60
58
  return names[0]
61
59
 
62
60
 
61
+ def _print_banner(state: ChatState) -> None:
62
+ """启动 banner — Sakura 主题。"""
63
+ print()
64
+ print(logo())
65
+ print(f" {paint('LBS', Theme.BOLD, Theme.SAKURA_DARK)} {dim('Agent')} "
66
+ f"{paint('·', Theme.SAKURA)} {dim('Interactive Chat')}")
67
+ print()
68
+ print(f" {dim('Type a message, or')} {sakura_label('/help')} {dim('for commands')}")
69
+ print()
70
+
71
+
63
72
  def _print_turn_header(state: ChatState) -> None:
64
73
  """每次 turn 的 header(agent + turn 编号)。"""
65
- print(f"\n\033[1;33m[{state.agent_name} • turn {state.turn_count}]\033[0m ", end="", flush=True)
74
+ print()
75
+ print(turn_prompt(state.agent_name, state.turn_count), end="", flush=True)
66
76
 
67
77
 
68
78
  def _print_help() -> None:
69
- print("""
70
- \033[1;36m内建命令:\033[0m
71
- /help 显示本帮助
72
- /exit /quit 退出 chat(自动保存 session)
73
- /new 开始新 session
74
- /session [id] 查看或切换 session(不带参数显示当前)
75
- /status 显示 session 状态
76
- /agent 列出可用 agents
77
- /agent <name> 切换到指定 agent
78
- /model 列出当前 provider/models
79
- /model <model> 切换 model(当前 provider 内)
80
- /clear 清屏
81
- /history [n] 显示最近 n 轮对话(默认 5)
82
- /save 手动保存当前 session
83
- """)
79
+ print()
80
+ print(f" {header_box('Commands', '/ prefix to run')}")
81
+ print()
82
+ cmds = [
83
+ ("/help", "显示本帮助"),
84
+ ("/exit", "退出 chat"),
85
+ ("/new", "开始新 session"),
86
+ ("/status", "显示 session 状态"),
87
+ ("/agent", "列出 / 切换 agent"),
88
+ ("/model", "列出 / 切换 model"),
89
+ ("/history", "显示最近对话"),
90
+ ("/clear", "清屏"),
91
+ ]
92
+ for cmd, desc in cmds:
93
+ print(f" {sakura_label(cmd.ljust(12))} {dim(desc)}")
94
+ print()
84
95
 
85
96
 
86
97
  def _cmd_list_agents(state: ChatState) -> None:
87
98
  agents = state.container.config.agents.agents
88
- print("\n\033[1;36m可用 agents:\033[0m")
99
+ print()
89
100
  for name, a in agents.items():
90
- marker = " \033[1;32m<-- 当前\033[0m" if name == state.agent_name else ""
91
- print(f" {name:25} {a.display_name}{marker}")
101
+ if name == state.agent_name:
102
+ marker = f" {status_dot(True)} {dim('current')}"
103
+ else:
104
+ marker = ""
105
+ print(f" {agent_badge(name)} {dim(a.display_name)}{marker}")
106
+ print()
92
107
 
93
108
 
94
109
  def _cmd_list_models(state: ChatState) -> None:
95
110
  config = state.container.config
96
111
  provider_name = state.provider_name or config.models.default_provider
97
112
  if provider_name not in config.models.providers:
98
- print(f" 无 provider '{provider_name}'")
113
+ print(f" {error_block(f'无 provider {provider_name!r}')}")
99
114
  return
100
115
  p = config.models.providers[provider_name]
101
- print(f"\n\033[1;36m当前 provider: {provider_name} ({p.display_name or provider_name})\033[0m")
116
+ print()
117
+ print(f" {dim('provider')} {paint(provider_name, Theme.VIOLET)} {dim(f'({p.display_name or provider_name})')}")
102
118
  for m in p.models:
103
- marker = " \033[1;32m<-- 当前\033[0m" if m == state.model_name else ""
104
- print(f" {m}{marker}")
119
+ marker = f" {status_dot(True)}" if m == state.model_name else ""
120
+ print(f" {model_pill(m)}{marker}")
121
+ print()
105
122
 
106
123
 
107
124
  def _cmd_history(state: ChatState, n: int = 5) -> None:
108
125
  if not state.session_id:
109
- print(" 无 session")
126
+ print(f" {dim('无 session')}")
110
127
  return
111
128
  try:
112
129
  messages = list(state.session_service.session_store.load_session_messages(state.session_id))
113
- # 显示 user/assistant 配对
114
130
  recent = []
115
131
  for msg in messages[-n*2:]:
116
132
  role = getattr(msg, "role", "?")
117
133
  content = getattr(msg, "content", "")[:120]
118
134
  if role in ("user", "assistant"):
119
135
  recent.append((role, content))
120
- print(f"\n\033[1;36m最近 {len(recent)} 条消息\033[0m")
136
+ print()
137
+ print(f" {dim(f'最近 {len(recent)} 条消息')}")
121
138
  for role, content in recent:
122
- print(f" {role:11} {content}")
139
+ role_color = Theme.SAKURA_DARK if role == "user" else Theme.MINT_DARK
140
+ print(f" {paint(role.ljust(9), role_color)} {paint('│', Theme.SAKURA, Theme.DIM)} {dim(content)}")
141
+ print()
123
142
  except Exception as e:
124
- print(f" 读取失败:{e}")
143
+ print(f" {error_block(f'读取失败: {e}')}")
125
144
 
126
145
 
127
146
  def _cmd_status(state: ChatState) -> None:
128
147
  elapsed = int(time.time() - state.started_at)
129
- print(f"""
130
- \033[1;36mSession 状态:\033[0m
131
- session_id : {state.session_id or '(未保存)'}
132
- agent : {state.agent_name}
133
- provider : {state.provider_name or '(默认)'}
134
- model : {state.model_name or '(默认)'}
135
- turns : {state.turn_count}
136
- elapsed : {elapsed}s
137
- """)
148
+ rows = [
149
+ ("session", state.session_id or dim("(unsaved)")),
150
+ ("agent", agent_badge(state.agent_name)),
151
+ ("provider", state.provider_name or dim("(default)")),
152
+ ("model", model_pill(state.model_name) if state.model_name else dim("(default)")),
153
+ ("turns", str(state.turn_count)),
154
+ ("elapsed", f"{elapsed}s"),
155
+ ]
156
+ print()
157
+ for label, val in rows:
158
+ print(kv_row(label, val))
159
+ print()
138
160
 
139
161
 
140
162
  def _handle_command(state: ChatState, line: str) -> bool:
@@ -151,13 +173,13 @@ def _handle_command(state: ChatState, line: str) -> bool:
151
173
  state.session_id = ""
152
174
  state.turn_count = 0
153
175
  state.started_at = time.time()
154
- print("\033[1;32m session 已开始\033[0m")
176
+ print(f" {mint_label('')} {dim('new session')}")
155
177
  elif cmd == "/session":
156
178
  if arg:
157
179
  state.session_id = arg
158
- print(f"\033[1;32m切换到 session: {arg}\033[0m")
180
+ print(f" {mint_label('')} {dim('switched to')} {paint(arg, Theme.SAKURA_DARK)}")
159
181
  else:
160
- print(f" 当前 session: {state.session_id or '()'}")
182
+ print(f" {dim('session:')} {state.session_id or dim('(none)')}")
161
183
  elif cmd == "/status":
162
184
  _cmd_status(state)
163
185
  elif cmd == "/agent":
@@ -167,15 +189,15 @@ def _handle_command(state: ChatState, line: str) -> bool:
167
189
  agents = state.container.config.agents.agents
168
190
  if arg in agents:
169
191
  state.agent_name = arg
170
- print(f"\033[1;32m切换到 agent: {arg}\033[0m")
192
+ print(f" {mint_label('')} {dim('agent')} {agent_badge(arg)}")
171
193
  else:
172
- print(f" 无 agent '{arg}'")
194
+ print(f" {error_block(f'无 agent {arg!r}')}")
173
195
  elif cmd == "/model":
174
196
  if not arg:
175
197
  _cmd_list_models(state)
176
198
  else:
177
199
  state.model_name = arg
178
- print(f"\033[1;32m切换到 model: {arg}\033[0m")
200
+ print(f" {mint_label('')} {dim('model')} {model_pill(arg)}")
179
201
  elif cmd == "/clear":
180
202
  print("\033[2J\033[H", end="")
181
203
  elif cmd == "/history":
@@ -183,12 +205,11 @@ def _handle_command(state: ChatState, line: str) -> bool:
183
205
  n = int(arg) if arg else 5
184
206
  _cmd_history(state, n)
185
207
  except ValueError:
186
- print(" /history [n]n 必须是整数")
208
+ print(f" {error_block('/history [n]n 必须是整数')}")
187
209
  elif cmd == "/save":
188
- # session 自动持久化在每次 turn,无需手动保存
189
- print(" session 已在每次 turn 自动保存")
210
+ print(f" {dim('session 自动保存在每次 turn')}")
190
211
  else:
191
- print(f" 未知命令:{cmd}(输入 /help 查看)")
212
+ print(f" {error_block(f'未知命令: {cmd} — 输入 /help')}")
192
213
  return True
193
214
 
194
215
 
@@ -196,7 +217,6 @@ def _run_turn(state: ChatState, user_input: str) -> None:
196
217
  """跑一轮对话(同步阻塞)。"""
197
218
  t0 = time.time()
198
219
  try:
199
- # 把 user_input 作为 task
200
220
  result = state.orchestrator.run_agent_sync(
201
221
  state.agent_name,
202
222
  user_input,
@@ -207,20 +227,21 @@ def _run_turn(state: ChatState, user_input: str) -> None:
207
227
  # 显示结果
208
228
  if result.success:
209
229
  content = (result.content or "").strip()
210
- print(f"\n\033[1;32m{content}\033[0m")
230
+ print(f"\n{result_block(content)}")
211
231
  else:
212
232
  err = result.error or "(无错误信息)"
213
- print(f"\n\033[1;31m✗ 失败: {err[:300]}\033[0m")
214
- print(f"\033[2m 耗时: {elapsed:.1f}s | 迭代: {result.iterations} | 工具: {result.total_tool_calls}\033[0m")
215
- # 保存 session_id(Orchestrator 内部会创建/复用)
233
+ print(f"\n {error_block(err[:300])}")
234
+ # 用量状态条
235
+ print(usage_bar(elapsed, result.iterations, result.total_tool_calls))
236
+ # 保存 session_id
216
237
  if not state.session_id:
217
238
  state.session_id = result.session_id if hasattr(result, "session_id") and result.session_id else f"chat-{int(state.started_at)}"
218
239
  except KeyboardInterrupt:
219
- print("\n\033[1;33m(已中断)\033[0m")
240
+ print(f"\n {paint('⏸', Theme.HONEY)} {dim('已中断')}")
220
241
  except Exception as e:
221
242
  elapsed = time.time() - t0
222
- print(f"\n\033[1;31m✗ 异常: {type(e).__name__}: {str(e)[:200]}\033[0m")
223
- print(f"\033[2m 耗时: {elapsed:.1f}s\033[0m")
243
+ print(f"\n {error_block(f'{type(e).__name__}: {str(e)[:200]}')}")
244
+ print(f" {dim(f'{elapsed:.1f}s')}")
224
245
 
225
246
 
226
247
  def cmd_chat(
@@ -240,7 +261,8 @@ def cmd_chat(
240
261
 
241
262
  # 验证 agent 存在
242
263
  if agent_name not in config.agents.agents:
243
- print(f"Agent '{agent_name}' 不存在")
264
+ msg = f"Agent '{agent_name}' 不存在"
265
+ print(f" {error_block(msg)}")
244
266
  return 1
245
267
 
246
268
  state = ChatState(
@@ -253,7 +275,7 @@ def cmd_chat(
253
275
  session_id=session_id,
254
276
  )
255
277
 
256
- # 单次 query 模式(--query,非交互)
278
+ # 单次 query 模式
257
279
  if query:
258
280
  state.turn_count = 1
259
281
  _print_turn_header(state)
@@ -262,9 +284,8 @@ def cmd_chat(
262
284
  return 0
263
285
 
264
286
  # REPL 模式
265
- print(BANNER)
287
+ _print_banner(state)
266
288
  _cmd_status(state)
267
- print("\n输入 /help 查看命令。Ctrl+C 中断当前 turn,/exit 退出。\n")
268
289
 
269
290
  while True:
270
291
  try:
@@ -274,7 +295,7 @@ def cmd_chat(
274
295
  print("\n")
275
296
  break
276
297
  except KeyboardInterrupt:
277
- print("\n\033[1;33m(用 /exit 退出)\033[0m")
298
+ print(f"\n {paint('用 /exit 退出', Theme.HONEY)}")
278
299
  continue
279
300
 
280
301
  line = line.strip()
@@ -287,5 +308,5 @@ def cmd_chat(
287
308
  state.turn_count += 1
288
309
  _run_turn(state, line)
289
310
 
290
- print("\n\033[1;36m→ Session 已保存。退出 chat。\033[0m")
311
+ print(f"\n {mint_label('✓')} {dim('Session saved. Bye.')}")
291
312
  return 0
@@ -73,15 +73,15 @@ def _paint(text: str, *codes: str) -> str:
73
73
  def _header(title: str) -> None:
74
74
  """打印章节标题 (带边框)。"""
75
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))
76
+ print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.SAKURA))
77
+ print(_paint(" │ ", _C.SAKURA) + _paint(f"⚙ LBS Setup — {title}", _C.BOLD, _C.SAKURA)
78
+ + _paint(" " * max(0, 47 - len(title) - 14) + "│", _C.SAKURA))
79
+ print(_paint(" └─────────────────────────────────────────────────────────┘", _C.SAKURA))
80
80
  print()
81
81
 
82
82
 
83
83
  def _info(msg: str) -> None:
84
- print(_paint(" ℹ ", _C.BLUE) + msg)
84
+ print(_paint(" ℹ ", _C.VIOLET) + msg)
85
85
 
86
86
 
87
87
  def _success(msg: str) -> None:
@@ -89,7 +89,7 @@ def _success(msg: str) -> None:
89
89
 
90
90
 
91
91
  def _warn(msg: str) -> None:
92
- print(_paint(" ⚠ ", _C.YELLOW) + _paint(msg, _C.YELLOW))
92
+ print(_paint(" ⚠ ", _C.HONEY) + _paint(msg, _C.HONEY))
93
93
 
94
94
 
95
95
  def _error(msg: str) -> None:
@@ -100,7 +100,7 @@ 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.CYAN)).strip()
103
+ val = input(_paint(f" ▸ {label}{suffix}: ", _C.MINT)).strip()
104
104
  except (EOFError, KeyboardInterrupt):
105
105
  print()
106
106
  return default
@@ -109,7 +109,7 @@ 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.CYAN))
112
+ print(_paint(f" ▸ {label}", _C.MINT))
113
113
  for i, opt in enumerate(options, 1):
114
114
  marker = _paint("→", _C.GREEN) if opt == default else " "
115
115
  print(f" {marker} {i}. {opt}")
@@ -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.CYAN))
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.GOLD))
471
- print(_paint(" │ ", _C.GOLD) + _paint("⚙ LBS Agent Setup Wizard", _C.BOLD, _C.GOLD)
472
- + _paint(" │", _C.GOLD))
473
- print(_paint(" └─────────────────────────────────────────────────────────┘", _C.GOLD))
470
+ print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.SAKURA))
471
+ print(_paint(" │ ", _C.SAKURA) + _paint("⚙ LBS Agent Setup Wizard", _C.BOLD, _C.SAKURA)
472
+ + _paint(" │", _C.SAKURA))
473
+ print(_paint(" └─────────────────────────────────────────────────────────┘", _C.SAKURA))
474
474
  print()
475
475
  _info(f"配置目录: {root}")
476
476
  print()
@@ -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.CYAN)
515
+ return _paint(s, _C.MINT)
516
516
 
517
517
 
518
518
  def paint_cmd(cmd: str) -> str:
519
- return _paint(cmd, _C.CYAN, _C.BOLD)
519
+ return _paint(cmd, _C.MINT, _C.BOLD)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baimingtao/lbs-agent",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
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
- 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',
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.gold) + '检测系统 Python...');
66
+ console.log(paint(' 🔍 ', C.sakura) + '检测系统 Python...');
62
67
 
63
68
  const candidates = isWin
64
69
  ? ['python', 'python3', 'py -3']
@@ -76,7 +81,7 @@ 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.yellow) + '版本低于 3.10,继续寻找...');
84
+ console.log(paint(' ⚠ ', C.honey) + '版本低于 3.10,继续寻找...');
80
85
  }
81
86
  }
82
87
 
@@ -97,7 +102,7 @@ function createVenv(sysPython) {
97
102
 
98
103
  // 确保 state root 存在
99
104
  ensureDir(STATE_ROOT);
100
- console.log(paint(' 📦 ', C.gold) + `创建 Python 虚拟环境 → ${paint(STATE_ROOT + '/venv', C.cyan)}`);
105
+ console.log(paint(' 📦 ', C.sakura) + `创建 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)) {
@@ -116,7 +121,7 @@ function installDeps() {
116
121
  return false;
117
122
  }
118
123
 
119
- console.log(paint(' 📥 ', C.gold) + '安装 Python 依赖 (可能需要 1-2 分钟)...');
124
+ console.log(paint(' 📥 ', C.sakura) + '安装 Python 依赖 (可能需要 1-2 分钟)...');
120
125
  const result = run(PIP, ['install', '-r', REQUIREMENTS, '--disable-pip-version-check']);
121
126
  if (result.status !== 0) {
122
127
  console.error(paint(' ✖ ', C.red) + '依赖安装失败');
@@ -162,7 +167,7 @@ function createDefaultDirs() {
162
167
  // ── Step 5: 验证 ───────────────────────────────────────────────
163
168
 
164
169
  function verifyInstall() {
165
- console.log(paint(' 🔍 ', C.gold) + '验证安装...');
170
+ console.log(paint(' 🔍 ', C.sakura) + '验证安装...');
166
171
  const result = run(PYTHON, [path.join(PKG_ROOT, 'lbs.py'), 'status'], {
167
172
  silent: true,
168
173
  cwd: PKG_ROOT,
@@ -171,7 +176,7 @@ function verifyInstall() {
171
176
  if (result.status === 0) {
172
177
  console.log(paint(' ✓ ', C.green) + 'lbs status 运行正常');
173
178
  } else {
174
- console.log(paint(' ⚠ ', C.yellow) + 'lbs status 运行异常(可能是配置问题,不影响初始化)');
179
+ console.log(paint(' ⚠ ', C.honey) + 'lbs status 运行异常(可能是配置问题,不影响初始化)');
175
180
  }
176
181
  return true;
177
182
  }
@@ -193,12 +198,12 @@ function writeFlag() {
193
198
 
194
199
  function main() {
195
200
  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));
201
+ console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.sakura));
202
+ console.log(paint(' │ ', C.sakura) + paint('⚙ LBS Agent — 初始化', C.bold, C.sakura) + paint(' │', C.sakura));
203
+ console.log(paint(' └─────────────────────────────────────────────────────────┘', C.sakura));
199
204
  console.log();
200
- console.log(paint(' 包目录 (只读): ', C.dim) + paint(PKG_ROOT, C.cyan));
201
- console.log(paint(' 状态目录 (可写): ', C.dim) + paint(STATE_ROOT, C.cyan));
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
@@ -225,12 +230,12 @@ function main() {
225
230
  console.log(paint(' │ ', C.green) + paint('✅ 初始化完成!', C.bold, C.green) + paint(' │', C.green));
226
231
  console.log(paint(' │ │', C.green));
227
232
  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));
233
+ console.log(paint(' │ ', C.green) + paint(' lbs setup 配置模型/供应商/Agent', C.mint) + paint(' │', C.green));
234
+ console.log(paint(' │ ', C.green) + paint(' lbs status 查看系统状态', C.mint) + paint(' │', C.green));
235
+ console.log(paint(' │ ', C.green) + paint(' lbs help 查看所有命令', C.mint) + paint(' │', C.green));
236
+ console.log(paint(' │ ', C.green) + paint(' lbs web 启动 Web 界面', C.mint) + paint(' │', C.green));
232
237
  console.log(paint(' │ │', C.green));
233
- console.log(paint(' │ ', C.green) + paint('状态目录: ', C.dim) + paint(STATE_ROOT, C.cyan) + paint(' │', C.green));
238
+ console.log(paint(' │ ', C.green) + paint('状态目录: ', C.dim) + paint(STATE_ROOT, C.mint) + paint(' │', C.green));
234
239
  console.log(paint(' └─────────────────────────────────────────────────────────┘', C.green));
235
240
  console.log();
236
241
  }