Skip to content

Commit 6fada82

Browse files
committed
Improve commands hints.
1 parent a774d85 commit 6fada82

6 files changed

Lines changed: 38 additions & 136 deletions

File tree

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ A Python port of the Emacs [gptel-agent-harness](https://github.com/beacoder/gpt
3737
- **Sessions** — auto-saved after every response to
3838
`~/.local/share/python-agent-harness/sessions/`, LLM-generated titles
3939
(one-shot per session, fired when the agent loop finishes; the file is
40-
renamed to `<title>_<TS>.md`), `restore` (with `--latest`) /
41-
`sessions` CLI commands.
42-
- **Commands**`init` (create/update AGENTS.md), `review` (uncommitted
43-
changes / commit / branch / PR), and custom commands from
44-
`prompts/commands/*.txt`; `summary` and `explain` are TUI slash
45-
commands only. Tool availability: `init`/`review` may use **all
40+
renamed to `<title>_<TS>.md`), `/restore` (with `--latest`) and
41+
`/sessions` TUI commands.
42+
- **Commands**`/init` (create/update AGENTS.md), `/review` (uncommitted
43+
changes / commit / branch / PR), `/summary`, `/explain` and custom
44+
commands from `prompts/commands/*.txt` — all TUI slash commands.
45+
Tool availability: `/init`/`/review` may use **all
4646
tools except PlanExit** (the PlanExit tool is hidden for the run,
4747
including for spawned sub-agents); custom commands may use all tools
4848
including PlanExit; `compact`/`summary` run with **no tools** (a
@@ -129,8 +129,7 @@ TUI slash commands: `/plan` `/build` `/init` `/review` `/explain`
129129
`/restore` `/clear` `/exit``/explain [project] [target]` explains
130130
code and `/summary` appends a conversation summary (both TUI-only);
131131
`/sessions` lists saved sessions and `/restore [path|title|--latest]`
132-
restores one (they are also CLI subcommands; in the TUI, `/restore`
133-
additionally matches sessions by title substring).
132+
restores one (`/restore` matches sessions by title substring).
134133

135134
Input editing: type your message, press **Enter** for a new line, and
136135
**Esc then Enter** (or **Alt+Enter**) to submit. **Up/Down** recall

python_agent_harness/cli.py

Lines changed: 5 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
"""CLI entry points: interactive TUI session and command-line commands.
1+
"""CLI entry points: interactive TUI session and configuration.
22
33
Commands:
44
run [project] interactive TUI agent session (default)
55
config [--init] show effective LLM config / write a template file
6-
init [project] create/update AGENTS.md
7-
review [args] review uncommitted changes / commit / branch / PR
8-
<custom> any prompt file in prompts/commands/ (except
9-
TUI-only slash commands such as explain)
10-
summary summarize the current (saved) session
11-
sessions list saved sessions
12-
restore <file> restore a saved session
13-
restore-latest restore the newest session
6+
<custom> any prompt file in prompts/commands/
7+
8+
init, review, sessions, restore (and summary/explain) are TUI slash
9+
commands only — see the TUI help for /init /review /sessions /restore.
1410
1511
Configuration (LLM etc.) is read from a JSON file, by default
1612
~/.config/python-agent-harness/config.json; see `config --init`.
@@ -28,7 +24,6 @@
2824
SessionCommand, find_command, load_custom_commands,
2925
)
3026
from .agent_session import AgentSession
31-
from .session_store import SessionStore
3227
from .tools import default_registry
3328

3429

@@ -127,16 +122,6 @@ def cmd_config(args: argparse.Namespace) -> int:
127122
return 0
128123

129124

130-
def cmd_init(args: argparse.Namespace) -> int:
131-
_run_command(initialize_command(), args.project, args.extra, args.config)
132-
return 0
133-
134-
135-
def cmd_review(args: argparse.Namespace) -> int:
136-
_run_command(review_command(), args.project, args.arguments, args.config)
137-
return 0
138-
139-
140125
def cmd_custom(args: argparse.Namespace) -> int:
141126
cmd = find_command(args.command_name)
142127
if cmd is None:
@@ -171,59 +156,6 @@ def _adopt(session: AgentSession, kw: dict) -> AgentSession:
171156
return session
172157

173158

174-
def cmd_sessions(args: argparse.Namespace) -> int:
175-
files = SessionStore.list_sessions()
176-
if not files:
177-
print("no saved sessions")
178-
return 0
179-
for f in files:
180-
try:
181-
meta = SessionStore.parse_metadata(open(f, encoding="utf-8").read())
182-
except OSError as e:
183-
print(f"{os.path.basename(f):60s} (unreadable: {e})")
184-
continue
185-
print(
186-
f"{os.path.basename(f):60s} "
187-
f"model={meta.get('gptel-model', '?'):20s} "
188-
f"project={meta.get('python-agent-harness--project-dir', '?')}"
189-
)
190-
return 0
191-
192-
193-
def cmd_restore(args: argparse.Namespace) -> int:
194-
from .session_store import SessionStore as Store
195-
from .session_store import title_from_filename
196-
197-
path = args.file
198-
if not path and args.latest:
199-
path = Store.latest_session()
200-
if not path:
201-
print("no session file given", file=sys.stderr)
202-
return 1
203-
try:
204-
text = open(path, encoding="utf-8").read()
205-
except OSError as e:
206-
print(f"cannot read {path}: {e}", file=sys.stderr)
207-
return 1
208-
meta = Store.parse_metadata(text)
209-
body = Store.strip_metadata(text)
210-
project = meta.get("python-agent-harness--project-dir") or os.getcwd()
211-
model = meta.get("gptel-model") or config.DEFAULT_MODEL
212-
session = make_session(
213-
project,
214-
config_path=args.config,
215-
model=model,
216-
)
217-
session.store.file_path = path
218-
title = title_from_filename(path)
219-
session.store.title = title
220-
print(f"restored: {path} (project={project}, model={model})")
221-
print("conversation preview:")
222-
print("\n".join(body.splitlines()[:20]))
223-
session.close()
224-
return 0
225-
226-
227159
def _add_config_arg(
228160
parser: argparse.ArgumentParser, suppress: bool = False
229161
) -> None:
@@ -255,16 +187,6 @@ def build_parser() -> argparse.ArgumentParser:
255187
p_config.add_argument("--path", metavar="PATH", help="config file path")
256188
p_config.set_defaults(func=cmd_config)
257189

258-
p_init = sub.add_parser("init", help="create/update AGENTS.md")
259-
_add_config_arg(p_init, suppress=True)
260-
p_init.add_argument("project", nargs="?", help="project directory")
261-
p_init.add_argument("--extra", help="extra instructions")
262-
263-
p_review = sub.add_parser("review", help="review code changes")
264-
_add_config_arg(p_review, suppress=True)
265-
p_review.add_argument("project", nargs="?")
266-
p_review.add_argument("arguments", nargs="?", help="commit/branch/PR, or empty")
267-
268190
for cmd in load_custom_commands():
269191
if cmd.name in TUI_ONLY_COMMANDS:
270192
continue # TUI slash command only (e.g. /explain)
@@ -273,15 +195,6 @@ def build_parser() -> argparse.ArgumentParser:
273195
p.add_argument("project", nargs="?")
274196
p.add_argument("extra", nargs="?", help="arguments for the command")
275197
p.set_defaults(func=cmd_custom, command_name=cmd.name)
276-
277-
p_sessions = sub.add_parser("sessions", help="list saved sessions")
278-
p_sessions.set_defaults(func=cmd_sessions)
279-
280-
p_restore = sub.add_parser("restore", help="restore a saved session")
281-
_add_config_arg(p_restore, suppress=True)
282-
p_restore.add_argument("file", nargs="?", help="session file path")
283-
p_restore.add_argument("--latest", action="store_true", help="restore newest session")
284-
p_restore.set_defaults(func=cmd_restore)
285198
return parser
286199

287200

@@ -292,10 +205,6 @@ def main(argv: list[str] | None = None) -> int:
292205
return cmd_run(args)
293206
if args.command == "config":
294207
return cmd_config(args)
295-
if args.command == "init":
296-
return cmd_init(args)
297-
if args.command == "review":
298-
return cmd_review(args)
299208
if hasattr(args, "func"):
300209
return args.func(args)
301210
parser.print_help()

python_agent_harness/tui.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,8 @@ def run(self) -> None:
574574
"Type a message — Enter for a new line, Esc then Enter "
575575
"(or Alt+Enter) to submit. Up/Down recall history.",
576576
border_style="blue",
577-
)
577+
),
578+
markup=False,
578579
)
579580
if config.LLM_LOG_ENABLED:
580581
self.console.print(f"[dim]LLM logs: {self.session.client.log_path}[/dim]")
@@ -859,7 +860,8 @@ def _handle_slash(self, line: str) -> bool:
859860
"/sessions list saved sessions\n"
860861
"/restore [path | title | --latest] restore a saved session\n"
861862
"Ctrl-C cancels the current execution (app stays open); "
862-
"Ctrl-D or /exit quits."
863+
"Ctrl-D or /exit quits.",
864+
markup=False,
863865
)
864866
else:
865867
self.console.print(f"unknown command: {cmd}")

tests/test_cli.py

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ def test_explain_not_a_cli_subcommand(self):
106106
)
107107
self.assertNotIn("explain", subparsers.choices)
108108
self.assertIn("run", subparsers.choices)
109-
self.assertIn("review", subparsers.choices)
109+
self.assertNotIn("review", subparsers.choices)
110+
self.assertNotIn("init", subparsers.choices)
111+
self.assertNotIn("sessions", subparsers.choices)
112+
self.assertNotIn("restore", subparsers.choices)
110113
cmd = find_command("explain")
111114
self.assertIsNotNone(cmd)
112115
self.assertEqual(cmd.name, "explain")
@@ -209,36 +212,17 @@ def _loop(*a, **kw):
209212

210213

211214
class TestCliSessionCommands(unittest.TestCase):
212-
def test_cmd_restore_unreadable_file(self):
213-
"""An unreadable session file must produce a clean error, not a
214-
traceback."""
215-
from types import SimpleNamespace
216-
215+
def test_removed_cli_subcommands(self):
216+
"""init/review/sessions/restore are TUI slash commands only."""
217217
from python_agent_harness import cli
218218

219-
args = SimpleNamespace(file="/tmp", latest=False, config=None)
220-
self.assertEqual(cli.cmd_restore(args), 1)
221-
222-
def test_cmd_sessions_skips_unreadable_files(self):
223-
"""A session entry that cannot be read (e.g. a directory named
224-
*.md) is listed as unreadable instead of crashing."""
225-
import os
226-
import tempfile
227-
from pathlib import Path
228-
229-
from python_agent_harness import cli, config
230-
231-
with tempfile.TemporaryDirectory() as d:
232-
old_dir = config.SESSION_DIR
233-
config.SESSION_DIR = Path(d)
234-
try:
235-
sessions = Path(d) / "python-agent-harness" / "sessions"
236-
sessions.mkdir(parents=True)
237-
(sessions / "broken.md").mkdir() # dir masquerading as a session
238-
(sessions / "good_260805120000.md").write_text("hello")
239-
self.assertEqual(cli.cmd_sessions(None), 0)
240-
finally:
241-
config.SESSION_DIR = old_dir
219+
parser = cli.build_parser()
220+
subparsers = next(
221+
a for a in parser._actions
222+
if a.__class__.__name__ == "_SubParsersAction"
223+
)
224+
for name in ("init", "review", "sessions", "restore"):
225+
self.assertNotIn(name, subparsers.choices)
242226

243227

244228
def _load(path, project_dir=None, with_context=False):

tests/test_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def test_config_flag_both_positions(self):
158158
self.assertEqual(before.config, "/x.json")
159159
after = parser.parse_args(["run", "/tmp", "--config", "/x.json"])
160160
self.assertEqual(after.config, "/x.json")
161-
plain = parser.parse_args(["sessions"])
161+
plain = parser.parse_args(["config"])
162162
self.assertIsNone(plain.config)
163163

164164

tests/test_tui.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,14 @@ def test_help_lists_command_slashes(self):
590590
out = buf.getvalue()
591591
for s in ("/init", "/review", "/explain"):
592592
self.assertIn(s, out)
593+
# bracket usage text must not be swallowed by rich markup
594+
for s in (
595+
"/init [project] [--extra TEXT] create/update AGENTS.md",
596+
"/review [project] [commit|branch|PR] review code changes",
597+
"/explain [project] [target]",
598+
"/restore [path | title | --latest] restore a saved session",
599+
):
600+
self.assertIn(s, out)
593601

594602
def test_completer_slash_commands(self):
595603
from prompt_toolkit.document import Document

0 commit comments

Comments
 (0)