personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

remove 6 dead CLI commands: callosum, formatter, planner, detect-created, config, help

+2 -621
+1 -12
sol.py
··· 40 40 # think package - daily processing and analysis 41 41 "import": "think.importers.cli", 42 42 "dream": "think.dream", 43 - "planner": "think.planner", 44 43 "indexer": "think.indexer", 45 44 "supervisor": "think.supervisor", 46 45 "schedule": "think.scheduler", 47 - "detect-created": "think.detect_created", 48 46 "top": "think.top", 49 47 "health": "think.health_cli", 50 - "callosum": "think.callosum", 51 48 "notify": "think.notify_cli", 52 49 "password": "think.password_cli", 53 50 "streams": "think.streams", 54 51 "journal-stats": "think.journal_stats", 55 - "config": "think.config_cli", 56 - "formatter": "think.formatters", 57 52 # observe package - multimodal capture 58 53 "transcribe": "observe.transcribe", 59 54 "describe": "observe.describe", ··· 67 62 "talent": "think.talent_cli", 68 63 "call": "think.call", 69 64 "engage": "think.engage", 70 - "help": "think.help_cli", 71 65 "chat": "think.chat_cli", 72 66 "heartbeat": "think.heartbeat", 73 67 # convey package - web UI ··· 99 93 "Think (daily processing)": [ 100 94 "import", 101 95 "dream", 102 - "planner", 103 96 "indexer", 104 97 "supervisor", 105 98 "schedule", 106 99 "top", 107 100 "health", 108 - "callosum", 109 101 "notify", 110 102 "heartbeat", 111 103 ], ··· 131 123 "maint", 132 124 ], 133 125 "Specialized tools": [ 134 - "config", 135 126 "password", 136 127 "streams", 137 128 "journal-stats", 138 - "formatter", 139 - "detect-created", 140 129 ], 141 - "Help": ["help", "chat"], 130 + "Help": ["chat"], 142 131 } 143 132 144 133
-31
tests/test_config_cli.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """Tests for the sol config CLI.""" 5 - 6 - import json 7 - 8 - from think.config_cli import main 9 - 10 - 11 - def test_config_prints_json(monkeypatch, capsys): 12 - """Default command prints full config JSON.""" 13 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 14 - monkeypatch.setattr("sys.argv", ["sol config"]) 15 - 16 - main() 17 - 18 - output = capsys.readouterr().out 19 - config = json.loads(output) 20 - assert "identity" in config 21 - 22 - 23 - def test_config_env_prints_path(monkeypatch, capsys): 24 - """env subcommand prints the journal path.""" 25 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 26 - monkeypatch.setattr("sys.argv", ["sol config", "env"]) 27 - 28 - main() 29 - 30 - output = capsys.readouterr().out.strip() 31 - assert output == "tests/fixtures/journal"
-153
tests/test_help_cli.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """Tests for think.help_cli.""" 5 - 6 - import io 7 - import json 8 - import subprocess 9 - import sys 10 - from unittest.mock import MagicMock, patch 11 - 12 - import pytest 13 - 14 - from think.help_cli import main 15 - 16 - 17 - @pytest.fixture(autouse=True) 18 - def _set_journal_path(monkeypatch): 19 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 20 - 21 - 22 - def _make_popen(stdout_lines, *, returncode=0): 23 - """Build a mock Popen whose stdout yields *stdout_lines*.""" 24 - proc = MagicMock() 25 - proc.stdin = MagicMock() 26 - proc.stdout = io.StringIO("\n".join(stdout_lines) + "\n") 27 - proc.stderr = MagicMock() 28 - proc.stderr.read.return_value = "" 29 - proc.returncode = returncode 30 - proc.wait.return_value = returncode 31 - return proc 32 - 33 - 34 - def test_help_no_question_shows_static_help(monkeypatch): 35 - monkeypatch.setattr(sys, "argv", ["sol help"]) 36 - 37 - with patch("sol.print_help") as mock_print_help: 38 - main() 39 - 40 - mock_print_help.assert_called_once() 41 - 42 - 43 - def test_help_parses_question(monkeypatch): 44 - monkeypatch.setattr(sys, "argv", ["sol help", "how", "do", "I", "search"]) 45 - mock_proc = _make_popen( 46 - ['{"event":"finish","result":"Use sol call journal search"}'], 47 - ) 48 - 49 - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc) as mock_cls: 50 - main() 51 - 52 - call_args = mock_cls.call_args 53 - assert call_args[0][0] == ["sol", "agents"] 54 - assert call_args[1]["stdin"] == subprocess.PIPE 55 - assert call_args[1]["stdout"] == subprocess.PIPE 56 - assert call_args[1]["text"] is True 57 - 58 - written = mock_proc.stdin.write.call_args[0][0] 59 - payload = json.loads(written.strip()) 60 - assert payload["prompt"] == "how do I search" 61 - mock_proc.stdin.close.assert_called_once() 62 - 63 - 64 - def test_help_ndjson_config(monkeypatch): 65 - monkeypatch.setattr(sys, "argv", ["sol help", "show", "todo", "commands"]) 66 - mock_proc = _make_popen(['{"event":"finish","result":"ok"}']) 67 - 68 - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): 69 - main() 70 - 71 - written = mock_proc.stdin.write.call_args[0][0] 72 - payload = json.loads(written.strip()) 73 - assert payload == {"name": "unified", "prompt": "show todo commands"} 74 - 75 - 76 - def test_help_parses_finish_event(monkeypatch, capsys): 77 - monkeypatch.setattr(sys, "argv", ["sol help", "how", "to", "search"]) 78 - mock_proc = _make_popen( 79 - [ 80 - '{"event":"start","ts":1}', 81 - '{"event":"thinking","ts":2,"summary":"..."}', 82 - '{"event":"finish","ts":3,"result":"Use `sol call journal search`."}', 83 - ] 84 - ) 85 - 86 - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): 87 - main() 88 - 89 - captured = capsys.readouterr() 90 - assert "Use `sol call journal search`." in captured.out 91 - 92 - 93 - def test_help_uses_last_finish_event(monkeypatch, capsys): 94 - monkeypatch.setattr(sys, "argv", ["sol help", "search"]) 95 - mock_proc = _make_popen( 96 - [ 97 - '{"event":"finish","ts":1,"result":"old result"}', 98 - '{"event":"finish","ts":2,"result":"new result"}', 99 - ] 100 - ) 101 - 102 - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): 103 - main() 104 - 105 - captured = capsys.readouterr() 106 - assert "new result" in captured.out 107 - assert "old result" not in captured.out 108 - 109 - 110 - def test_help_handles_error_event(monkeypatch, capsys): 111 - monkeypatch.setattr(sys, "argv", ["sol help", "bad", "request"]) 112 - mock_proc = _make_popen( 113 - ['{"event":"error","error":"provider unavailable"}'], 114 - returncode=1, 115 - ) 116 - 117 - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): 118 - with pytest.raises(SystemExit) as exc_info: 119 - main() 120 - 121 - assert exc_info.value.code == 1 122 - captured = capsys.readouterr() 123 - assert "provider unavailable" in captured.err 124 - 125 - 126 - def test_help_handles_empty_finish_result(monkeypatch, capsys): 127 - monkeypatch.setattr(sys, "argv", ["sol help", "empty"]) 128 - mock_proc = _make_popen(['{"event":"finish","result":""}']) 129 - 130 - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): 131 - with pytest.raises(SystemExit) as exc_info: 132 - main() 133 - 134 - assert exc_info.value.code == 1 135 - captured = capsys.readouterr() 136 - assert "empty result" in captured.err.lower() 137 - 138 - 139 - def test_help_handles_timeout(monkeypatch, capsys): 140 - monkeypatch.setattr(sys, "argv", ["sol help", "slow", "question"]) 141 - mock_proc = _make_popen([]) 142 - mock_proc.wait.side_effect = subprocess.TimeoutExpired( 143 - cmd=["sol", "agents"], timeout=120 144 - ) 145 - 146 - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): 147 - with pytest.raises(SystemExit) as exc_info: 148 - main() 149 - 150 - assert exc_info.value.code == 1 151 - mock_proc.kill.assert_called_once() 152 - captured = capsys.readouterr() 153 - assert "timed out" in captured.err.lower()
-13
tests/test_planner.py
··· 44 44 monkeypatch.setattr("think.models.generate", mock_generate) 45 45 result = mod.generate_plan("do something") 46 46 assert result == "plan" 47 - 48 - 49 - def test_planner_main(tmp_path, monkeypatch, capsys): 50 - sys.modules.pop("think.planner", None) 51 - mod = importlib.import_module("think.planner") 52 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 53 - monkeypatch.setattr(mod, "generate_plan", lambda *a, **k: "ok") 54 - task = tmp_path / "t.txt" 55 - task.write_text("hi") 56 - monkeypatch.setattr("sys.argv", ["sol planner", str(task)]) 57 - mod.main() 58 - out = capsys.readouterr().out.strip() 59 - assert out == "ok"
+1 -22
tests/test_sol.py
··· 258 258 assert "--day" in captured_argv 259 259 assert "20250101" in captured_argv 260 260 261 - def test_main_help_command_with_question_dispatches(self, monkeypatch): 262 - """Test 'help' with extra args dispatches to help module.""" 263 - monkeypatch.setattr(sys, "argv", ["sol", "help", "how", "do", "I", "search"]) 264 - 265 - captured_argv = [] 266 - 267 - def mock_main(): 268 - captured_argv.extend(sys.argv) 269 - 270 - mock_module = MagicMock() 271 - mock_module.main = mock_main 272 - 273 - with patch("importlib.import_module", return_value=mock_module): 274 - with pytest.raises(SystemExit): 275 - sol.main() 276 - 277 - assert captured_argv[0] == "sol help" 278 - assert "how" in captured_argv 279 - assert "search" in captured_argv 280 - 281 - 282 261 class TestCommandRegistry: 283 262 """Tests for command registry completeness.""" 284 263 ··· 297 276 298 277 def test_critical_commands_registered(self): 299 278 """Test that critical commands are registered.""" 300 - critical = ["import", "agents", "dream", "indexer", "transcribe", "help"] 279 + critical = ["import", "agents", "dream", "indexer", "transcribe"] 301 280 for cmd in critical: 302 281 assert cmd in sol.COMMANDS, f"Critical command '{cmd}' not registered"
-57
think/callosum.py
··· 556 556 else: 557 557 print("Failed to send (is callosum running?)", file=sys.stderr) 558 558 sys.exit(1) 559 - 560 - 561 - def main() -> None: 562 - """CLI entry point for Callosum message bus tools.""" 563 - import argparse 564 - 565 - from think.utils import setup_cli 566 - 567 - parser = argparse.ArgumentParser( 568 - description="Callosum message bus tools", 569 - epilog="Run 'sol callosum' with no subcommand to listen to all events.", 570 - ) 571 - subparsers = parser.add_subparsers(dest="subcommand") 572 - 573 - # --- listen subcommand --- 574 - listen_parser = subparsers.add_parser( 575 - "listen", help="Listen to events on the message bus" 576 - ) 577 - listen_parser.add_argument("--tract", help="Filter to a specific tract") 578 - listen_parser.add_argument("--event", help="Filter to a specific event type") 579 - listen_parser.add_argument( 580 - "-p", "--pretty", action="store_true", help="Pretty-print JSON output" 581 - ) 582 - 583 - # --- send subcommand --- 584 - send_parser = subparsers.add_parser( 585 - "send", 586 - help="Send a message to the bus", 587 - epilog=( 588 - "Examples:\n" 589 - " sol callosum send observe described day=20250101 segment=143045_300\n" 590 - ' sol callosum send \'{"tract":"test","event":"ping"}\'\n' 591 - " echo '{...}' | sol callosum send" 592 - ), 593 - formatter_class=argparse.RawDescriptionHelpFormatter, 594 - ) 595 - send_parser.add_argument( 596 - "args", nargs="*", help="tract event [key=value ...] or JSON string" 597 - ) 598 - 599 - args = setup_cli(parser) 600 - 601 - if args.subcommand == "send": 602 - _cmd_send(args) 603 - else: 604 - # Default: listen (both bare 'sol callosum' and 'sol callosum listen') 605 - if not hasattr(args, "tract"): 606 - args.tract = None 607 - if not hasattr(args, "event"): 608 - args.event = None 609 - if not hasattr(args, "pretty"): 610 - args.pretty = False 611 - _cmd_listen(args) 612 - 613 - 614 - if __name__ == "__main__": 615 - main()
-57
think/config_cli.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """CLI for inspecting journal configuration. 5 - 6 - Shows the resolved journal configuration as JSON, or prints the journal path. 7 - 8 - Usage: 9 - sol config Show full config JSON 10 - sol config env Show journal path 11 - sol config facet rename OLD NEW Rename a facet 12 - """ 13 - 14 - from __future__ import annotations 15 - 16 - import argparse 17 - import json 18 - import sys 19 - 20 - from think.utils import get_config, get_journal_info, setup_cli 21 - 22 - 23 - def main() -> None: 24 - parser = argparse.ArgumentParser(description="Show journal configuration") 25 - subparsers = parser.add_subparsers(dest="subcommand") 26 - subparsers.add_parser("env", help="Show journal path and source") 27 - 28 - # facet subcommand with its own sub-subcommands 29 - facet_parser = subparsers.add_parser("facet", help="Facet management") 30 - facet_sub = facet_parser.add_subparsers(dest="facet_action") 31 - rename_parser = facet_sub.add_parser("rename", help="Rename a facet") 32 - rename_parser.add_argument("old_name", help="Current facet name") 33 - rename_parser.add_argument("new_name", help="New facet name") 34 - 35 - args = setup_cli(parser) 36 - 37 - if args.subcommand == "env": 38 - path, _source = get_journal_info() 39 - print(path) 40 - elif args.subcommand == "facet": 41 - if args.facet_action == "rename": 42 - from think.facets import rename_facet 43 - 44 - try: 45 - rename_facet(args.old_name, args.new_name) 46 - except ValueError as exc: 47 - print(f"Error: {exc}", file=sys.stderr) 48 - sys.exit(1) 49 - else: 50 - facet_parser.print_help() 51 - else: 52 - config = get_config() 53 - print(json.dumps(config, indent=2)) 54 - 55 - 56 - if __name__ == "__main__": 57 - main()
-25
think/detect_created.py
··· 124 124 return result 125 125 except json.JSONDecodeError: 126 126 return None 127 - 128 - 129 - def main(): 130 - """Main CLI entry point for detect_created utility.""" 131 - import argparse 132 - 133 - from .utils import setup_cli 134 - 135 - parser = argparse.ArgumentParser( 136 - description="Detect creation time information from media file metadata" 137 - ) 138 - parser.add_argument("file_path", help="Path to the media file to analyze") 139 - 140 - args = setup_cli(parser) 141 - 142 - result = detect_created(args.file_path) 143 - if result is not None: 144 - print(json.dumps(result, indent=2)) 145 - else: 146 - print("Failed to detect creation time information", file=sys.stderr) 147 - sys.exit(1) 148 - 149 - 150 - if __name__ == "__main__": 151 - main()
-90
think/formatters.py
··· 33 33 Markdown formatters receive str text and perform semantic chunking. 34 34 """ 35 35 36 - import argparse 37 36 import fnmatch 38 37 import json 39 38 import os 40 - import sys 41 39 from importlib import import_module 42 40 from pathlib import Path 43 41 from typing import Any, Callable ··· 363 361 print(f' intro: "{intro[:60]}{"..." if len(intro) > 60 else ""}"') 364 362 print(f" {preview[:70]}{'...' if len(preview) > 70 else ''}") 365 363 print() 366 - 367 - 368 - def main() -> None: 369 - """CLI entry point for sol formatter.""" 370 - from think.utils import setup_cli 371 - 372 - parser = argparse.ArgumentParser( 373 - description="Convert JSONL or Markdown files to formatted chunks" 374 - ) 375 - parser.add_argument("file", help="Path to JSONL or Markdown file") 376 - parser.add_argument( 377 - "-f", 378 - "--format", 379 - choices=["json", "markdown", "summary"], 380 - default="json", 381 - help="Output format (default: json)", 382 - ) 383 - parser.add_argument( 384 - "-i", 385 - "--index", 386 - type=int, 387 - help="Show only the chunk at this index", 388 - ) 389 - parser.add_argument( 390 - "--join", 391 - action="store_true", 392 - help="Output concatenated markdown (shorthand for --format=markdown)", 393 - ) 394 - parser.add_argument( 395 - "--context", 396 - type=str, 397 - help="JSON string of context to pass to formatter", 398 - ) 399 - args = setup_cli(parser) 400 - 401 - # --join is shorthand for --format=markdown 402 - if args.join: 403 - args.format = "markdown" 404 - 405 - try: 406 - context = json.loads(args.context) if args.context else None 407 - except json.JSONDecodeError as e: 408 - print(f"Error parsing context JSON: {e}", file=sys.stderr) 409 - sys.exit(1) 410 - 411 - try: 412 - chunks, meta = format_file(args.file, context) 413 - except (ValueError, FileNotFoundError) as e: 414 - print(f"Error: {e}", file=sys.stderr) 415 - sys.exit(1) 416 - 417 - # For summary format on markdown files, get raw chunks with metadata 418 - raw_chunks = None 419 - if args.format == "summary" and args.file.endswith(".md"): 420 - from think.markdown import chunk_markdown 421 - 422 - text = load_markdown(args.file) 423 - raw_chunks = chunk_markdown(text) 424 - 425 - # Filter to single chunk if requested 426 - if args.index is not None: 427 - if 0 <= args.index < len(chunks): 428 - chunks = [chunks[args.index]] 429 - if raw_chunks: 430 - raw_chunks = [raw_chunks[args.index]] 431 - else: 432 - print( 433 - f"Error: Index {args.index} out of range (0-{len(chunks) - 1})", 434 - file=sys.stderr, 435 - ) 436 - sys.exit(1) 437 - 438 - if args.format == "markdown": 439 - # Output concatenated markdown with header first 440 - parts = [] 441 - if meta.get("header"): 442 - parts.append(meta["header"]) 443 - parts.extend(chunk["markdown"] for chunk in chunks) 444 - print("\n".join(parts)) 445 - elif args.format == "summary": 446 - _format_chunk_summary(chunks, raw_chunks) 447 - else: 448 - # Output JSON object with metadata and chunks 449 - print(json.dumps({"meta": meta, "chunks": chunks}, indent=2)) 450 - 451 - 452 - if __name__ == "__main__": 453 - main()
-122
think/help_cli.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """CLI command for interactive help with sol commands.""" 5 - 6 - from __future__ import annotations 7 - 8 - import argparse 9 - import json 10 - import subprocess 11 - import sys 12 - 13 - from think.utils import setup_cli 14 - 15 - 16 - def _read_stdin() -> str: 17 - """Read a question from stdin. Shows a prompt if running in a terminal.""" 18 - if sys.stdin.isatty(): 19 - print( 20 - "Enter your question (Ctrl+D to submit):", 21 - file=sys.stderr, 22 - ) 23 - try: 24 - return sys.stdin.read().strip() 25 - except KeyboardInterrupt: 26 - return "" 27 - 28 - 29 - def main() -> None: 30 - """Entry point for ``sol help``.""" 31 - parser = argparse.ArgumentParser( 32 - prog="sol help", 33 - description="Get help with sol commands", 34 - ) 35 - parser.add_argument( 36 - "question", 37 - nargs="*", 38 - help="Question about sol commands", 39 - ) 40 - 41 - args = setup_cli(parser) 42 - 43 - if not args.question: 44 - question = _read_stdin() 45 - if not question: 46 - # Imported here to avoid circular import (sol.py imports think.help_cli). 47 - from sol import print_help 48 - 49 - print_help() 50 - return 51 - else: 52 - question = " ".join(args.question).strip() 53 - 54 - config = {"name": "unified", "prompt": question} 55 - config_json = json.dumps(config) 56 - 57 - print("Thinking...", end="", file=sys.stderr, flush=True) 58 - 59 - try: 60 - proc = subprocess.Popen( 61 - ["sol", "agents"], 62 - stdin=subprocess.PIPE, 63 - stdout=subprocess.PIPE, 64 - stderr=subprocess.PIPE, 65 - text=True, 66 - ) 67 - except Exception as exc: 68 - print(f"\rError: failed to run help agent: {exc}", file=sys.stderr) 69 - sys.exit(1) 70 - 71 - assert proc.stdin is not None # for type checker 72 - proc.stdin.write(config_json + "\n") 73 - proc.stdin.close() 74 - 75 - finish_result: str | None = None 76 - errors: list[str] = [] 77 - 78 - assert proc.stdout is not None # for type checker 79 - for line in proc.stdout: 80 - line = line.strip() 81 - if not line: 82 - continue 83 - 84 - try: 85 - event = json.loads(line) 86 - except json.JSONDecodeError: 87 - continue 88 - 89 - event_type = event.get("event") 90 - if event_type == "error": 91 - errors.append(str(event.get("error", "Unknown error"))) 92 - elif event_type == "finish": 93 - result_value = event.get("result") 94 - finish_result = "" if result_value is None else str(result_value) 95 - 96 - try: 97 - proc.wait(timeout=120) 98 - except subprocess.TimeoutExpired: 99 - proc.kill() 100 - print("\rError: help request timed out after 120 seconds.", file=sys.stderr) 101 - sys.exit(1) 102 - 103 - # Clear the "Thinking..." indicator. 104 - print("\r \r", end="", file=sys.stderr, flush=True) 105 - 106 - for message in errors: 107 - print(f"Error: {message}", file=sys.stderr) 108 - 109 - if finish_result is not None and finish_result.strip(): 110 - print(finish_result) 111 - return 112 - 113 - if finish_result is not None: 114 - print("Error: help agent returned an empty result.", file=sys.stderr) 115 - sys.exit(1) 116 - 117 - stderr_output = proc.stderr.read() if proc.stderr else "" 118 - if proc.returncode != 0 and stderr_output.strip(): 119 - print(f"Error: {stderr_output.strip()}", file=sys.stderr) 120 - else: 121 - print("Error: no help response received.", file=sys.stderr) 122 - sys.exit(1)
-39
think/planner.py
··· 1 1 # SPDX-License-Identifier: AGPL-3.0-only 2 2 # Copyright (c) 2026 sol pbc 3 3 4 - import argparse 5 - import os 6 - import sys 7 4 from pathlib import Path 8 5 9 6 from .prompts import load_prompt 10 - from .utils import setup_cli 11 7 12 8 13 9 def _load_prompt() -> str: ··· 28 24 thinking_budget=4096, 29 25 system_instruction=_load_prompt(), 30 26 ) 31 - 32 - 33 - def parse_args() -> argparse.ArgumentParser: 34 - parser = argparse.ArgumentParser( 35 - description="Generate an agent plan using configured provider" 36 - ) 37 - parser.add_argument( 38 - "task", 39 - nargs="?", 40 - help="Path to .txt file with the request or '-' for stdin", 41 - ) 42 - parser.add_argument("-q", "--query", help="Request text directly") 43 - return parser 44 - 45 - 46 - def main() -> None: 47 - parser = parse_args() 48 - args = setup_cli(parser) 49 - if args.query: 50 - request = args.query 51 - elif args.task is None: 52 - parser.error("request not provided") 53 - elif args.task == "-": 54 - request = sys.stdin.read() 55 - else: 56 - if not os.path.isfile(args.task): 57 - parser.error(f"File not found: {args.task}") 58 - request = Path(args.task).read_text(encoding="utf-8") 59 - 60 - plan = generate_plan(request) 61 - print(plan) 62 - 63 - 64 - if __name__ == "__main__": # pragma: no cover - manual execution 65 - main()