@baimingtao/lbs-agent 1.2.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,25 +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
79
  const LOGO = [
76
80
  '',
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
- '',
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),
83
86
  ].join('\n');
84
87
 
85
88
  function printBanner() {
86
89
  const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf-8'));
87
90
  console.log(LOGO);
88
- console.log(paint(' v' + pkg.version, C.bold, C.gold) + paint(' Leaving Blank Space 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));
89
94
  console.log();
90
95
  }
91
-
92
96
  // ── 初始化检查 ─────────────────────────────────────────────────
93
97
 
94
98
  function isInitialized() {
@@ -98,8 +102,8 @@ function isInitialized() {
98
102
  function ensureInitialized() {
99
103
  if (isInitialized()) return true;
100
104
 
101
- console.log(paint('⚙ ', C.gold) + paint('首次运行,正在初始化 LBS Agent...', C.bold));
102
- 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));
103
107
  console.log(paint(' 创建 Python 虚拟环境 + 安装依赖(约 1-2 分钟)', C.dim));
104
108
  console.log();
105
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,17 +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;38;2;255;191;0m
29
- \033[1;36m╔══╗\033[0m \033[1;36m╔═══╗\033[0m \033[1;36m╔═════╗\033[0m
30
- \033[1;36m║ ║\033[0m \033[1;36m║ ║\033[0m \033[1;36m║ ╔═╝\033[0m
31
- \033[1;36m╠══╣\033[0m \033[1;36m╠═══╣\033[0m \033[1;36m║ ╚═╗\033[0m
32
- \033[1;36m║ ║\033[0m \033[1;36m║ ║\033[0m \033[1;36m╚═════╝\033[0m
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
- """
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
+ )
37
32
 
38
33
 
39
34
  @dataclass
@@ -63,85 +58,104 @@ def _resolve_agent(config: AppConfig) -> str:
63
58
  return names[0]
64
59
 
65
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
+
66
72
  def _print_turn_header(state: ChatState) -> None:
67
73
  """每次 turn 的 header(agent + turn 编号)。"""
68
- agent_label = f"\033[1;36m{state.agent_name}\033[0m"
69
- turn_label = f"\033[2mturn {state.turn_count}\033[0m"
70
- print(f"\n{agent_label} {turn_label} \033[2m›\033[0m ", end="", flush=True)
74
+ print()
75
+ print(turn_prompt(state.agent_name, state.turn_count), end="", flush=True)
71
76
 
72
77
 
73
78
  def _print_help() -> None:
74
- print("""
75
- \033[1;36m内建命令:\033[0m
76
- /help 显示本帮助
77
- /exit /quit 退出 chat(自动保存 session)
78
- /new 开始新 session
79
- /session [id] 查看或切换 session(不带参数显示当前)
80
- /status 显示 session 状态
81
- /agent 列出可用 agents
82
- /agent <name> 切换到指定 agent
83
- /model 列出当前 provider/models
84
- /model <model> 切换 model(当前 provider 内)
85
- /clear 清屏
86
- /history [n] 显示最近 n 轮对话(默认 5)
87
- /save 手动保存当前 session
88
- """)
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()
89
95
 
90
96
 
91
97
  def _cmd_list_agents(state: ChatState) -> None:
92
98
  agents = state.container.config.agents.agents
93
- print("\n\033[1;36m可用 agents:\033[0m")
99
+ print()
94
100
  for name, a in agents.items():
95
- marker = " \033[1;32m<-- 当前\033[0m" if name == state.agent_name else ""
96
- 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()
97
107
 
98
108
 
99
109
  def _cmd_list_models(state: ChatState) -> None:
100
110
  config = state.container.config
101
111
  provider_name = state.provider_name or config.models.default_provider
102
112
  if provider_name not in config.models.providers:
103
- print(f" 无 provider '{provider_name}'")
113
+ print(f" {error_block(f'无 provider {provider_name!r}')}")
104
114
  return
105
115
  p = config.models.providers[provider_name]
106
- 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})')}")
107
118
  for m in p.models:
108
- marker = " \033[1;32m<-- 当前\033[0m" if m == state.model_name else ""
109
- 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()
110
122
 
111
123
 
112
124
  def _cmd_history(state: ChatState, n: int = 5) -> None:
113
125
  if not state.session_id:
114
- print(" 无 session")
126
+ print(f" {dim('无 session')}")
115
127
  return
116
128
  try:
117
129
  messages = list(state.session_service.session_store.load_session_messages(state.session_id))
118
- # 显示 user/assistant 配对
119
130
  recent = []
120
131
  for msg in messages[-n*2:]:
121
132
  role = getattr(msg, "role", "?")
122
133
  content = getattr(msg, "content", "")[:120]
123
134
  if role in ("user", "assistant"):
124
135
  recent.append((role, content))
125
- print(f"\n\033[1;36m最近 {len(recent)} 条消息\033[0m")
136
+ print()
137
+ print(f" {dim(f'最近 {len(recent)} 条消息')}")
126
138
  for role, content in recent:
127
- 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()
128
142
  except Exception as e:
129
- print(f" 读取失败:{e}")
143
+ print(f" {error_block(f'读取失败: {e}')}")
130
144
 
131
145
 
132
146
  def _cmd_status(state: ChatState) -> None:
133
147
  elapsed = int(time.time() - state.started_at)
134
148
  rows = [
135
- ("session", state.session_id or "\033[2m(unsaved)\033[0m"),
136
- ("agent", state.agent_name),
137
- ("provider", state.provider_name or "\033[2m(default)\033[0m"),
138
- ("model", state.model_name or "\033[2m(default)\033[0m"),
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)")),
139
153
  ("turns", str(state.turn_count)),
140
154
  ("elapsed", f"{elapsed}s"),
141
155
  ]
142
156
  print()
143
157
  for label, val in rows:
144
- print(f" \033[2m{label:9}\033[0m │ {val}")
158
+ print(kv_row(label, val))
145
159
  print()
146
160
 
147
161
 
@@ -159,13 +173,13 @@ def _handle_command(state: ChatState, line: str) -> bool:
159
173
  state.session_id = ""
160
174
  state.turn_count = 0
161
175
  state.started_at = time.time()
162
- print("\033[1;32m session 已开始\033[0m")
176
+ print(f" {mint_label('')} {dim('new session')}")
163
177
  elif cmd == "/session":
164
178
  if arg:
165
179
  state.session_id = arg
166
- print(f"\033[1;32m切换到 session: {arg}\033[0m")
180
+ print(f" {mint_label('')} {dim('switched to')} {paint(arg, Theme.SAKURA_DARK)}")
167
181
  else:
168
- print(f" 当前 session: {state.session_id or '()'}")
182
+ print(f" {dim('session:')} {state.session_id or dim('(none)')}")
169
183
  elif cmd == "/status":
170
184
  _cmd_status(state)
171
185
  elif cmd == "/agent":
@@ -175,15 +189,15 @@ def _handle_command(state: ChatState, line: str) -> bool:
175
189
  agents = state.container.config.agents.agents
176
190
  if arg in agents:
177
191
  state.agent_name = arg
178
- print(f"\033[1;32m切换到 agent: {arg}\033[0m")
192
+ print(f" {mint_label('')} {dim('agent')} {agent_badge(arg)}")
179
193
  else:
180
- print(f" 无 agent '{arg}'")
194
+ print(f" {error_block(f'无 agent {arg!r}')}")
181
195
  elif cmd == "/model":
182
196
  if not arg:
183
197
  _cmd_list_models(state)
184
198
  else:
185
199
  state.model_name = arg
186
- print(f"\033[1;32m切换到 model: {arg}\033[0m")
200
+ print(f" {mint_label('')} {dim('model')} {model_pill(arg)}")
187
201
  elif cmd == "/clear":
188
202
  print("\033[2J\033[H", end="")
189
203
  elif cmd == "/history":
@@ -191,12 +205,11 @@ def _handle_command(state: ChatState, line: str) -> bool:
191
205
  n = int(arg) if arg else 5
192
206
  _cmd_history(state, n)
193
207
  except ValueError:
194
- print(" /history [n]n 必须是整数")
208
+ print(f" {error_block('/history [n]n 必须是整数')}")
195
209
  elif cmd == "/save":
196
- # session 自动持久化在每次 turn,无需手动保存
197
- print(" session 已在每次 turn 自动保存")
210
+ print(f" {dim('session 自动保存在每次 turn')}")
198
211
  else:
199
- print(f" 未知命令:{cmd}(输入 /help 查看)")
212
+ print(f" {error_block(f'未知命令: {cmd} — 输入 /help')}")
200
213
  return True
201
214
 
202
215
 
@@ -204,7 +217,6 @@ def _run_turn(state: ChatState, user_input: str) -> None:
204
217
  """跑一轮对话(同步阻塞)。"""
205
218
  t0 = time.time()
206
219
  try:
207
- # 把 user_input 作为 task
208
220
  result = state.orchestrator.run_agent_sync(
209
221
  state.agent_name,
210
222
  user_input,
@@ -215,21 +227,21 @@ def _run_turn(state: ChatState, user_input: str) -> None:
215
227
  # 显示结果
216
228
  if result.success:
217
229
  content = (result.content or "").strip()
218
- print(f"\n\033[1;37m{content}\033[0m")
230
+ print(f"\n{result_block(content)}")
219
231
  else:
220
232
  err = result.error or "(无错误信息)"
221
- print(f"\n\033[1;31m✗ {err[:300]}\033[0m")
222
- # 状态行
223
- print(f"\033[2m {elapsed:.1f}s │ iter {result.iterations} │ tools {result.total_tool_calls}\033[0m")
224
- # 保存 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
225
237
  if not state.session_id:
226
238
  state.session_id = result.session_id if hasattr(result, "session_id") and result.session_id else f"chat-{int(state.started_at)}"
227
239
  except KeyboardInterrupt:
228
- print("\n\033[1;33m 已中断\033[0m")
240
+ print(f"\n {paint('', Theme.HONEY)} {dim('已中断')}")
229
241
  except Exception as e:
230
242
  elapsed = time.time() - t0
231
- print(f"\n\033[1;31m {type(e).__name__}: {str(e)[:200]}\033[0m")
232
- 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')}")
233
245
 
234
246
 
235
247
  def cmd_chat(
@@ -249,7 +261,8 @@ def cmd_chat(
249
261
 
250
262
  # 验证 agent 存在
251
263
  if agent_name not in config.agents.agents:
252
- print(f"Agent '{agent_name}' 不存在")
264
+ msg = f"Agent '{agent_name}' 不存在"
265
+ print(f" {error_block(msg)}")
253
266
  return 1
254
267
 
255
268
  state = ChatState(
@@ -262,7 +275,7 @@ def cmd_chat(
262
275
  session_id=session_id,
263
276
  )
264
277
 
265
- # 单次 query 模式(--query,非交互)
278
+ # 单次 query 模式
266
279
  if query:
267
280
  state.turn_count = 1
268
281
  _print_turn_header(state)
@@ -271,9 +284,8 @@ def cmd_chat(
271
284
  return 0
272
285
 
273
286
  # REPL 模式
274
- print(BANNER)
287
+ _print_banner(state)
275
288
  _cmd_status(state)
276
- print("\n输入 /help 查看命令。Ctrl+C 中断当前 turn,/exit 退出。\n")
277
289
 
278
290
  while True:
279
291
  try:
@@ -283,7 +295,7 @@ def cmd_chat(
283
295
  print("\n")
284
296
  break
285
297
  except KeyboardInterrupt:
286
- print("\n\033[1;33m(用 /exit 退出)\033[0m")
298
+ print(f"\n {paint('用 /exit 退出', Theme.HONEY)}")
287
299
  continue
288
300
 
289
301
  line = line.strip()
@@ -296,5 +308,5 @@ def cmd_chat(
296
308
  state.turn_count += 1
297
309
  _run_turn(state, line)
298
310
 
299
- print("\n\033[1;36m→ Session 已保存。退出 chat。\033[0m")
311
+ print(f"\n {mint_label('✓')} {dim('Session saved. Bye.')}")
300
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.2.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
  }