lithe-cli 0.5.0__tar.gz → 0.6.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: lithe-cli
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: Command-line interface for the lithe agent kernel: chat, one-shot runs, undo, and run inspection.
5
5
  License: MIT License
6
6
 
@@ -38,13 +38,8 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
38
38
  Classifier: Typing :: Typed
39
39
  Requires-Python: >=3.10
40
40
  Description-Content-Type: text/markdown
41
- License-File: LICENSE
42
- Requires-Dist: lithe>=0.9.8
43
- Requires-Dist: prompt-toolkit>=3.0.38
44
41
  Provides-Extra: dev
45
- Requires-Dist: pytest>=8; extra == "dev"
46
- Requires-Dist: ruff>=0.4; extra == "dev"
47
- Dynamic: license-file
42
+ License-File: LICENSE
48
43
 
49
44
  # lithe-cli
50
45
 
@@ -101,39 +96,59 @@ servers — so you can see what a run would use before starting one.
101
96
  ```bash
102
97
  $ cd my-project
103
98
  $ lithe run "总结 README.md 的要点,存到 SUMMARY.md"
104
- ⚒ read_file {"path": "README.md"}
99
+ ⚒ read_file 读取 README.md
105
100
  ✓ 已读取 README.md(120 行) 0.1s
106
- ⚒ write_file {"path": "SUMMARY.md", "content": "..."}
101
+ ⚒ write_file 写入 SUMMARY.md
107
102
  ✓ 写入 SUMMARY.md 0.0s
108
103
  已把要点写入 SUMMARY.md。
109
104
  ── done · steps 3 · tokens 2100 · cost 0.0042
110
105
  ```
111
106
 
107
+ 修改已有文件时,助手优先调用 `edit_file` 或 `apply_patch` 做局部更新;只有新建文件或需要整体替换时才使用 `write_file`。工具调用界面只显示工具名、文件路径等摘要,不再展开完整参数。
108
+
112
109
  `--stream` streams tokens as they generate; `-v` adds per-call
113
110
  usage/context gauges; `--max-steps` caps the tool loop;
114
111
  `--context-window` enables fullness gauges.
115
112
 
116
- ### Interactive chat
117
-
118
- ```bash
119
- $ lithe chat
120
- ╭──────────────────────────────────────╮
121
- │ lithe 0.4.0 │
122
- │ model your-model │
123
- │ workdir /home/me/my-project │
124
- │ /help 查看会话命令,/exit 退出 │
125
- ╰──────────────────────────────────────╯
126
- lithe ❯ 把 a.txt 改成三行待办清单
127
- ⚒ write_file {"path": "a.txt", ...}
128
- ✓ 写入 a.txt 0.0s
129
- 已完成。
130
- ── done · steps 2 · tokens 180
131
- lithe ❯ /exit
113
+ ### Full-screen interface
114
+
115
+ On an interactive terminal, `lithe chat` and `lithe run TASK` open a
116
+ persistent full-screen interface instead of scribbling one-line events
117
+ into the console:
118
+
119
+ ```text
120
+ lithe 0.6.1 · your-model · /home/me/my-project ● 完成 · 0.2s
121
+ ╭─ 对话 ──────────────────────────────╮╭─ 运行状态 ─────────╮
122
+ │ 把 a.txt 改成三行待办清单 ││ ● 完成 · 0.2s │
123
+ │ ◆ update_todos ││ 步骤 2/35 · 0.2s │
124
+ │ ✓ 任务清单已更新(3 条) 0.0s ││ ✓ update_todos 0.0s│
125
+ │ ◆ edit_file · 局部修改 a.txt ││ ✓ edit_file 0.0s │
126
+ │ ✓ 已修改 a.txt 0.0s ││ ◆ 待办 1/3 │
127
+ │ 已完成。 ││ [~] 修改 a.txt │
128
+ │ ││ ◆ 会话用量 │
129
+ │ ││ 输入 1,024 · 输出 216│
130
+ │ ││ 缓存输入 768 │
131
+ │ ││ 合计 1,240 · $0.0021│
132
+ │ ││ 上下文 1,024 / 128,000 (0.8%)│
133
+ ╰─────────────────────────────────────╯╰────────────────────╯
134
+ lithe ❯ _
135
+ ● 完成 · step 2/35 · 1,240 tok · $0.0021 Enter 发送 · /help 命令
132
136
  ```
133
137
 
134
- History carries across turns within a session (each turn is its own run in
135
- the store, replayed as context for the next). `/new` clears the session,
136
- `/tools` lists the registered tools, `/help` lists the commands.
138
+ The left pane is the conversation (task, compact tool-call summaries
139
+ and outcomes, and the streaming assistant reply). The right sidebar shows
140
+ run status, recent tools, todos, session-wide cumulative input/output/
141
+ cached tokens and cost, plus the latest model-call context length and
142
+ window percentage. The screen stays up between turns; `Ctrl+C` cancels
143
+ the current turn while running and exits when idle. In `lithe run` mode
144
+ the result stays on screen until you press `q`. Layout, wrapping and
145
+ alignment are East-Asian-width aware; narrower terminals stack the two
146
+ panes vertically. Pipes and CI keep the plain per-line output unchanged.
147
+
148
+ History carries across turns within a session (each turn is its own run
149
+ in the store, replayed as context for the next). `/new` clears the
150
+ conversation history and session usage totals; `/tools` lists the
151
+ registered tools, `/help` lists the commands.
137
152
 
138
153
  ### Extra capabilities
139
154
 
@@ -53,39 +53,59 @@ servers — so you can see what a run would use before starting one.
53
53
  ```bash
54
54
  $ cd my-project
55
55
  $ lithe run "总结 README.md 的要点,存到 SUMMARY.md"
56
- ⚒ read_file {"path": "README.md"}
56
+ ⚒ read_file 读取 README.md
57
57
  ✓ 已读取 README.md(120 行) 0.1s
58
- ⚒ write_file {"path": "SUMMARY.md", "content": "..."}
58
+ ⚒ write_file 写入 SUMMARY.md
59
59
  ✓ 写入 SUMMARY.md 0.0s
60
60
  已把要点写入 SUMMARY.md。
61
61
  ── done · steps 3 · tokens 2100 · cost 0.0042
62
62
  ```
63
63
 
64
+ 修改已有文件时,助手优先调用 `edit_file` 或 `apply_patch` 做局部更新;只有新建文件或需要整体替换时才使用 `write_file`。工具调用界面只显示工具名、文件路径等摘要,不再展开完整参数。
65
+
64
66
  `--stream` streams tokens as they generate; `-v` adds per-call
65
67
  usage/context gauges; `--max-steps` caps the tool loop;
66
68
  `--context-window` enables fullness gauges.
67
69
 
68
- ### Interactive chat
69
-
70
- ```bash
71
- $ lithe chat
72
- ╭──────────────────────────────────────╮
73
- │ lithe 0.4.0 │
74
- │ model your-model │
75
- │ workdir /home/me/my-project │
76
- │ /help 查看会话命令,/exit 退出 │
77
- ╰──────────────────────────────────────╯
78
- lithe ❯ 把 a.txt 改成三行待办清单
79
- ⚒ write_file {"path": "a.txt", ...}
80
- ✓ 写入 a.txt 0.0s
81
- 已完成。
82
- ── done · steps 2 · tokens 180
83
- lithe ❯ /exit
70
+ ### Full-screen interface
71
+
72
+ On an interactive terminal, `lithe chat` and `lithe run TASK` open a
73
+ persistent full-screen interface instead of scribbling one-line events
74
+ into the console:
75
+
76
+ ```text
77
+ lithe 0.6.1 · your-model · /home/me/my-project ● 完成 · 0.2s
78
+ ╭─ 对话 ──────────────────────────────╮╭─ 运行状态 ─────────╮
79
+ │ 把 a.txt 改成三行待办清单 ││ ● 完成 · 0.2s │
80
+ │ ◆ update_todos ││ 步骤 2/35 · 0.2s │
81
+ │ ✓ 任务清单已更新(3 条) 0.0s ││ ✓ update_todos 0.0s│
82
+ │ ◆ edit_file · 局部修改 a.txt ││ ✓ edit_file 0.0s │
83
+ │ ✓ 已修改 a.txt 0.0s ││ ◆ 待办 1/3 │
84
+ │ 已完成。 ││ [~] 修改 a.txt │
85
+ │ ││ ◆ 会话用量 │
86
+ │ ││ 输入 1,024 · 输出 216│
87
+ │ ││ 缓存输入 768 │
88
+ │ ││ 合计 1,240 · $0.0021│
89
+ │ ││ 上下文 1,024 / 128,000 (0.8%)│
90
+ ╰─────────────────────────────────────╯╰────────────────────╯
91
+ lithe ❯ _
92
+ ● 完成 · step 2/35 · 1,240 tok · $0.0021 Enter 发送 · /help 命令
84
93
  ```
85
94
 
86
- History carries across turns within a session (each turn is its own run in
87
- the store, replayed as context for the next). `/new` clears the session,
88
- `/tools` lists the registered tools, `/help` lists the commands.
95
+ The left pane is the conversation (task, compact tool-call summaries
96
+ and outcomes, and the streaming assistant reply). The right sidebar shows
97
+ run status, recent tools, todos, session-wide cumulative input/output/
98
+ cached tokens and cost, plus the latest model-call context length and
99
+ window percentage. The screen stays up between turns; `Ctrl+C` cancels
100
+ the current turn while running and exits when idle. In `lithe run` mode
101
+ the result stays on screen until you press `q`. Layout, wrapping and
102
+ alignment are East-Asian-width aware; narrower terminals stack the two
103
+ panes vertically. Pipes and CI keep the plain per-line output unchanged.
104
+
105
+ History carries across turns within a session (each turn is its own run
106
+ in the store, replayed as context for the next). `/new` clears the
107
+ conversation history and session usage totals; `/tools` lists the
108
+ registered tools, `/help` lists the commands.
89
109
 
90
110
  ### Extra capabilities
91
111
 
@@ -1,3 +1,3 @@
1
1
  """lithe-cli: the command-line interface for the lithe agent kernel."""
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.6.1"
@@ -17,7 +17,6 @@ Everything here maps lithe's host contract onto CLI defaults:
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- import json
21
20
  import shutil
22
21
  import sys
23
22
  import time
@@ -43,7 +42,8 @@ from .ui import CYAN, GREEN, YELLOW, ui
43
42
 
44
43
  SYSTEM_PROMPT_BASE = (
45
44
  "你是运行在命令行里的助理,工作区是用户的当前目录。"
46
- "用提供的文件工具完成任务(读写前先读、谨慎修改),"
45
+ "用提供的文件工具完成任务(读写前先读、谨慎修改);修改已有文件时优先用 edit_file 或 apply_patch 做局部修改,"
46
+ "仅在新建文件或确需整体重写时使用 write_file。"
47
47
  "需要多步任务时先用 update_todos 列出计划,最后用简洁中文汇报结果。"
48
48
  )
49
49
 
@@ -135,7 +135,6 @@ def render_event(
135
135
  verbose: bool = False,
136
136
  stream: bool = False,
137
137
  pending: dict | None = None,
138
- dashboard=None,
139
138
  ) -> None:
140
139
  """Render one runtime event onto the terminal.
141
140
 
@@ -143,9 +142,6 @@ def render_event(
143
142
  elapsed seconds. ``stream`` suppresses the full-text echo of the final
144
143
  answer when its tokens were already streamed.
145
144
  """
146
- if dashboard is not None:
147
- dashboard.update(ev, verbose=verbose)
148
- return
149
145
  t = ev.get("type")
150
146
  pending = pending if pending is not None else {}
151
147
  if t == "assistant_delta":
@@ -158,7 +154,7 @@ def render_event(
158
154
  if text and not (stream and pending.get("_deltas")):
159
155
  ui.assistant(text)
160
156
  elif t == "tool_call":
161
- args = json.dumps(ev.get("args", {}), ensure_ascii=False)
157
+ args = ev.get("args") or {}
162
158
  ui.tool_call(str(ev.get("name")), args)
163
159
  if ev.get("id") is not None:
164
160
  pending[ev["id"]] = time.monotonic()
@@ -191,8 +187,17 @@ async def execute(
191
187
  task: str,
192
188
  history: list[dict] | None = None,
193
189
  run_id: str | None = None,
190
+ on_event=None,
191
+ stop=None,
194
192
  ) -> tuple[str, dict, Any]:
195
- """Run one task end-to-end; returns (run_id, done_event, host)."""
193
+ """Run one task end-to-end; returns (run_id, done_event, host).
194
+
195
+ ``on_event`` (the full-screen TUI supplies one) receives every kernel
196
+ event and silences the line-oriented printing below, including the
197
+ footer — the caller renders from the events instead. ``stop`` is the
198
+ kernel's cancellation handle, surfaced so Ctrl+C can cancel a turn
199
+ without killing the process.
200
+ """
196
201
  reg = build_registry(cfg)
197
202
  host = build_host(cfg, reg)
198
203
  rid = run_id or uuid.uuid4().hex[:12]
@@ -214,29 +219,17 @@ async def execute(
214
219
  ui.error(f"MCP {name}: {res}")
215
220
  ev: dict = {}
216
221
  pending: dict[str, float] = {}
217
- todo_path = cfg.store_dir / f"todos-{cfg.user_id}.json"
218
- dashboard = ui.dashboard(
219
- cfg.model or "(scripted)",
220
- str(cfg.workspace_dir.resolve()),
221
- rid,
222
- task,
223
- cfg.max_steps,
224
- JsonTodoStore(todo_path).list(),
225
- )
226
222
  try:
227
- if dashboard is not None:
228
- dashboard.start()
229
- async for ev in host.run(ctx, task, history=history):
230
- render_event(ev, cfg.verbose, cfg.stream, pending, dashboard)
231
- if ev.get("type") == "done":
232
- if dashboard is not None:
233
- dashboard.finish(ev)
234
- elif cfg.stream and pending.get("_deltas"):
235
- print()
223
+ async for ev in host.run(ctx, task, history=history, stop=stop):
224
+ if on_event is not None:
225
+ on_event(ev)
226
+ else:
227
+ render_event(ev, cfg.verbose, cfg.stream, pending)
228
+ if ev.get("type") == "done" and on_event is None:
229
+ if cfg.stream and pending.get("_deltas"):
230
+ print() # close the streaming line before the footer
236
231
  ui.footer(ev)
237
232
  finally:
238
- if dashboard is not None:
239
- dashboard.close()
240
233
  if manager is not None:
241
234
  await manager.close()
242
235
  return rid, ev, host
@@ -33,6 +33,7 @@ from . import prompts
33
33
  from .agent import build_registry, execute, sandbox_backend, undo
34
34
  from .config import config_file, load_config, require_endpoint
35
35
  from .prompts import open_history
36
+ from .tui import screen_supported
36
37
  from .ui import BOLD, CYAN, GREEN, RED, YELLOW, configure, truncate, ui
37
38
 
38
39
 
@@ -397,9 +398,17 @@ def main(argv: list[str] | None = None) -> int:
397
398
  configure(cfg.color)
398
399
  if args.command == "run":
399
400
  require_endpoint(cfg, interactive=not args.no_setup)
401
+ if screen_supported():
402
+ from .tui import run_screen
403
+
404
+ return asyncio.run(run_screen(cfg, args.task, "run"))
400
405
  return _cmd_run(cfg, args.task)
401
406
  if args.command == "chat":
402
407
  require_endpoint(cfg, interactive=not args.no_setup)
408
+ if screen_supported():
409
+ from .tui import run_screen
410
+
411
+ return asyncio.run(run_screen(cfg, None, "chat"))
403
412
  return _chat_loop(cfg)
404
413
  if args.command == "tools":
405
414
  return _cmd_tools(cfg)