From ba7babc88443fb33c6826182360cf6467d1e026e Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Fri, 10 Apr 2026 21:48:13 -0600 Subject: [PATCH] remove 6 dead CLI commands: callosum, formatter, planner, detect-created, config, help --- sol.py | 13 +--- tests/test_config_cli.py | 31 -------- tests/test_help_cli.py | 153 --------------------------------------- tests/test_planner.py | 13 ---- tests/test_sol.py | 23 +----- think/callosum.py | 57 --------------- think/config_cli.py | 57 --------------- think/detect_created.py | 25 ------- think/formatters.py | 90 ----------------------- think/help_cli.py | 122 ------------------------------- think/planner.py | 39 ---------- 11 files changed, 2 insertions(+), 621 deletions(-) delete mode 100644 tests/test_config_cli.py delete mode 100644 tests/test_help_cli.py delete mode 100644 think/config_cli.py delete mode 100644 think/help_cli.py diff --git a/sol.py b/sol.py index e5b4ab30f..d063a2469 100644 --- a/sol.py +++ b/sol.py @@ -40,20 +40,15 @@ COMMANDS: dict[str, str] = { # think package - daily processing and analysis "import": "think.importers.cli", "dream": "think.dream", - "planner": "think.planner", "indexer": "think.indexer", "supervisor": "think.supervisor", "schedule": "think.scheduler", - "detect-created": "think.detect_created", "top": "think.top", "health": "think.health_cli", - "callosum": "think.callosum", "notify": "think.notify_cli", "password": "think.password_cli", "streams": "think.streams", "journal-stats": "think.journal_stats", - "config": "think.config_cli", - "formatter": "think.formatters", # observe package - multimodal capture "transcribe": "observe.transcribe", "describe": "observe.describe", @@ -67,7 +62,6 @@ COMMANDS: dict[str, str] = { "talent": "think.talent_cli", "call": "think.call", "engage": "think.engage", - "help": "think.help_cli", "chat": "think.chat_cli", "heartbeat": "think.heartbeat", # convey package - web UI @@ -99,13 +93,11 @@ GROUPS: dict[str, list[str]] = { "Think (daily processing)": [ "import", "dream", - "planner", "indexer", "supervisor", "schedule", "top", "health", - "callosum", "notify", "heartbeat", ], @@ -131,14 +123,11 @@ GROUPS: dict[str, list[str]] = { "maint", ], "Specialized tools": [ - "config", "password", "streams", "journal-stats", - "formatter", - "detect-created", ], - "Help": ["help", "chat"], + "Help": ["chat"], } diff --git a/tests/test_config_cli.py b/tests/test_config_cli.py deleted file mode 100644 index 03d31591c..000000000 --- a/tests/test_config_cli.py +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-only -# Copyright (c) 2026 sol pbc - -"""Tests for the sol config CLI.""" - -import json - -from think.config_cli import main - - -def test_config_prints_json(monkeypatch, capsys): - """Default command prints full config JSON.""" - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") - monkeypatch.setattr("sys.argv", ["sol config"]) - - main() - - output = capsys.readouterr().out - config = json.loads(output) - assert "identity" in config - - -def test_config_env_prints_path(monkeypatch, capsys): - """env subcommand prints the journal path.""" - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") - monkeypatch.setattr("sys.argv", ["sol config", "env"]) - - main() - - output = capsys.readouterr().out.strip() - assert output == "tests/fixtures/journal" diff --git a/tests/test_help_cli.py b/tests/test_help_cli.py deleted file mode 100644 index ddf2e4419..000000000 --- a/tests/test_help_cli.py +++ /dev/null @@ -1,153 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-only -# Copyright (c) 2026 sol pbc - -"""Tests for think.help_cli.""" - -import io -import json -import subprocess -import sys -from unittest.mock import MagicMock, patch - -import pytest - -from think.help_cli import main - - -@pytest.fixture(autouse=True) -def _set_journal_path(monkeypatch): - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") - - -def _make_popen(stdout_lines, *, returncode=0): - """Build a mock Popen whose stdout yields *stdout_lines*.""" - proc = MagicMock() - proc.stdin = MagicMock() - proc.stdout = io.StringIO("\n".join(stdout_lines) + "\n") - proc.stderr = MagicMock() - proc.stderr.read.return_value = "" - proc.returncode = returncode - proc.wait.return_value = returncode - return proc - - -def test_help_no_question_shows_static_help(monkeypatch): - monkeypatch.setattr(sys, "argv", ["sol help"]) - - with patch("sol.print_help") as mock_print_help: - main() - - mock_print_help.assert_called_once() - - -def test_help_parses_question(monkeypatch): - monkeypatch.setattr(sys, "argv", ["sol help", "how", "do", "I", "search"]) - mock_proc = _make_popen( - ['{"event":"finish","result":"Use sol call journal search"}'], - ) - - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc) as mock_cls: - main() - - call_args = mock_cls.call_args - assert call_args[0][0] == ["sol", "agents"] - assert call_args[1]["stdin"] == subprocess.PIPE - assert call_args[1]["stdout"] == subprocess.PIPE - assert call_args[1]["text"] is True - - written = mock_proc.stdin.write.call_args[0][0] - payload = json.loads(written.strip()) - assert payload["prompt"] == "how do I search" - mock_proc.stdin.close.assert_called_once() - - -def test_help_ndjson_config(monkeypatch): - monkeypatch.setattr(sys, "argv", ["sol help", "show", "todo", "commands"]) - mock_proc = _make_popen(['{"event":"finish","result":"ok"}']) - - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): - main() - - written = mock_proc.stdin.write.call_args[0][0] - payload = json.loads(written.strip()) - assert payload == {"name": "unified", "prompt": "show todo commands"} - - -def test_help_parses_finish_event(monkeypatch, capsys): - monkeypatch.setattr(sys, "argv", ["sol help", "how", "to", "search"]) - mock_proc = _make_popen( - [ - '{"event":"start","ts":1}', - '{"event":"thinking","ts":2,"summary":"..."}', - '{"event":"finish","ts":3,"result":"Use `sol call journal search`."}', - ] - ) - - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): - main() - - captured = capsys.readouterr() - assert "Use `sol call journal search`." in captured.out - - -def test_help_uses_last_finish_event(monkeypatch, capsys): - monkeypatch.setattr(sys, "argv", ["sol help", "search"]) - mock_proc = _make_popen( - [ - '{"event":"finish","ts":1,"result":"old result"}', - '{"event":"finish","ts":2,"result":"new result"}', - ] - ) - - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): - main() - - captured = capsys.readouterr() - assert "new result" in captured.out - assert "old result" not in captured.out - - -def test_help_handles_error_event(monkeypatch, capsys): - monkeypatch.setattr(sys, "argv", ["sol help", "bad", "request"]) - mock_proc = _make_popen( - ['{"event":"error","error":"provider unavailable"}'], - returncode=1, - ) - - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): - with pytest.raises(SystemExit) as exc_info: - main() - - assert exc_info.value.code == 1 - captured = capsys.readouterr() - assert "provider unavailable" in captured.err - - -def test_help_handles_empty_finish_result(monkeypatch, capsys): - monkeypatch.setattr(sys, "argv", ["sol help", "empty"]) - mock_proc = _make_popen(['{"event":"finish","result":""}']) - - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): - with pytest.raises(SystemExit) as exc_info: - main() - - assert exc_info.value.code == 1 - captured = capsys.readouterr() - assert "empty result" in captured.err.lower() - - -def test_help_handles_timeout(monkeypatch, capsys): - monkeypatch.setattr(sys, "argv", ["sol help", "slow", "question"]) - mock_proc = _make_popen([]) - mock_proc.wait.side_effect = subprocess.TimeoutExpired( - cmd=["sol", "agents"], timeout=120 - ) - - with patch("think.help_cli.subprocess.Popen", return_value=mock_proc): - with pytest.raises(SystemExit) as exc_info: - main() - - assert exc_info.value.code == 1 - mock_proc.kill.assert_called_once() - captured = capsys.readouterr() - assert "timed out" in captured.err.lower() diff --git a/tests/test_planner.py b/tests/test_planner.py index 373b76a0b..0a7a5399c 100644 --- a/tests/test_planner.py +++ b/tests/test_planner.py @@ -44,16 +44,3 @@ def test_generate_plan(monkeypatch): monkeypatch.setattr("think.models.generate", mock_generate) result = mod.generate_plan("do something") assert result == "plan" - - -def test_planner_main(tmp_path, monkeypatch, capsys): - sys.modules.pop("think.planner", None) - mod = importlib.import_module("think.planner") - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) - monkeypatch.setattr(mod, "generate_plan", lambda *a, **k: "ok") - task = tmp_path / "t.txt" - task.write_text("hi") - monkeypatch.setattr("sys.argv", ["sol planner", str(task)]) - mod.main() - out = capsys.readouterr().out.strip() - assert out == "ok" diff --git a/tests/test_sol.py b/tests/test_sol.py index eb9f4f825..6a2624e9f 100644 --- a/tests/test_sol.py +++ b/tests/test_sol.py @@ -258,27 +258,6 @@ class TestMain: assert "--day" in captured_argv assert "20250101" in captured_argv - def test_main_help_command_with_question_dispatches(self, monkeypatch): - """Test 'help' with extra args dispatches to help module.""" - monkeypatch.setattr(sys, "argv", ["sol", "help", "how", "do", "I", "search"]) - - captured_argv = [] - - def mock_main(): - captured_argv.extend(sys.argv) - - mock_module = MagicMock() - mock_module.main = mock_main - - with patch("importlib.import_module", return_value=mock_module): - with pytest.raises(SystemExit): - sol.main() - - assert captured_argv[0] == "sol help" - assert "how" in captured_argv - assert "search" in captured_argv - - class TestCommandRegistry: """Tests for command registry completeness.""" @@ -297,6 +276,6 @@ class TestCommandRegistry: def test_critical_commands_registered(self): """Test that critical commands are registered.""" - critical = ["import", "agents", "dream", "indexer", "transcribe", "help"] + critical = ["import", "agents", "dream", "indexer", "transcribe"] for cmd in critical: assert cmd in sol.COMMANDS, f"Critical command '{cmd}' not registered" diff --git a/think/callosum.py b/think/callosum.py index 07cda2bdf..6dd8a9ad8 100644 --- a/think/callosum.py +++ b/think/callosum.py @@ -556,60 +556,3 @@ def _cmd_send(args) -> None: else: print("Failed to send (is callosum running?)", file=sys.stderr) sys.exit(1) - - -def main() -> None: - """CLI entry point for Callosum message bus tools.""" - import argparse - - from think.utils import setup_cli - - parser = argparse.ArgumentParser( - description="Callosum message bus tools", - epilog="Run 'sol callosum' with no subcommand to listen to all events.", - ) - subparsers = parser.add_subparsers(dest="subcommand") - - # --- listen subcommand --- - listen_parser = subparsers.add_parser( - "listen", help="Listen to events on the message bus" - ) - listen_parser.add_argument("--tract", help="Filter to a specific tract") - listen_parser.add_argument("--event", help="Filter to a specific event type") - listen_parser.add_argument( - "-p", "--pretty", action="store_true", help="Pretty-print JSON output" - ) - - # --- send subcommand --- - send_parser = subparsers.add_parser( - "send", - help="Send a message to the bus", - epilog=( - "Examples:\n" - " sol callosum send observe described day=20250101 segment=143045_300\n" - ' sol callosum send \'{"tract":"test","event":"ping"}\'\n' - " echo '{...}' | sol callosum send" - ), - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - send_parser.add_argument( - "args", nargs="*", help="tract event [key=value ...] or JSON string" - ) - - args = setup_cli(parser) - - if args.subcommand == "send": - _cmd_send(args) - else: - # Default: listen (both bare 'sol callosum' and 'sol callosum listen') - if not hasattr(args, "tract"): - args.tract = None - if not hasattr(args, "event"): - args.event = None - if not hasattr(args, "pretty"): - args.pretty = False - _cmd_listen(args) - - -if __name__ == "__main__": - main() diff --git a/think/config_cli.py b/think/config_cli.py deleted file mode 100644 index 41284c912..000000000 --- a/think/config_cli.py +++ /dev/null @@ -1,57 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-only -# Copyright (c) 2026 sol pbc - -"""CLI for inspecting journal configuration. - -Shows the resolved journal configuration as JSON, or prints the journal path. - -Usage: - sol config Show full config JSON - sol config env Show journal path - sol config facet rename OLD NEW Rename a facet -""" - -from __future__ import annotations - -import argparse -import json -import sys - -from think.utils import get_config, get_journal_info, setup_cli - - -def main() -> None: - parser = argparse.ArgumentParser(description="Show journal configuration") - subparsers = parser.add_subparsers(dest="subcommand") - subparsers.add_parser("env", help="Show journal path and source") - - # facet subcommand with its own sub-subcommands - facet_parser = subparsers.add_parser("facet", help="Facet management") - facet_sub = facet_parser.add_subparsers(dest="facet_action") - rename_parser = facet_sub.add_parser("rename", help="Rename a facet") - rename_parser.add_argument("old_name", help="Current facet name") - rename_parser.add_argument("new_name", help="New facet name") - - args = setup_cli(parser) - - if args.subcommand == "env": - path, _source = get_journal_info() - print(path) - elif args.subcommand == "facet": - if args.facet_action == "rename": - from think.facets import rename_facet - - try: - rename_facet(args.old_name, args.new_name) - except ValueError as exc: - print(f"Error: {exc}", file=sys.stderr) - sys.exit(1) - else: - facet_parser.print_help() - else: - config = get_config() - print(json.dumps(config, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/think/detect_created.py b/think/detect_created.py index 96c7f8844..273e39965 100644 --- a/think/detect_created.py +++ b/think/detect_created.py @@ -124,28 +124,3 @@ def detect_created( return result except json.JSONDecodeError: return None - - -def main(): - """Main CLI entry point for detect_created utility.""" - import argparse - - from .utils import setup_cli - - parser = argparse.ArgumentParser( - description="Detect creation time information from media file metadata" - ) - parser.add_argument("file_path", help="Path to the media file to analyze") - - args = setup_cli(parser) - - result = detect_created(args.file_path) - if result is not None: - print(json.dumps(result, indent=2)) - else: - print("Failed to detect creation time information", file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/think/formatters.py b/think/formatters.py index b44344c7c..119b1843f 100644 --- a/think/formatters.py +++ b/think/formatters.py @@ -33,11 +33,9 @@ JSONL formatters receive list[dict] entries and are responsible for: Markdown formatters receive str text and perform semantic chunking. """ -import argparse import fnmatch import json import os -import sys from importlib import import_module from pathlib import Path from typing import Any, Callable @@ -363,91 +361,3 @@ def _format_chunk_summary(chunks: list[dict], raw_chunks: list[dict] | None) -> print(f' intro: "{intro[:60]}{"..." if len(intro) > 60 else ""}"') print(f" {preview[:70]}{'...' if len(preview) > 70 else ''}") print() - - -def main() -> None: - """CLI entry point for sol formatter.""" - from think.utils import setup_cli - - parser = argparse.ArgumentParser( - description="Convert JSONL or Markdown files to formatted chunks" - ) - parser.add_argument("file", help="Path to JSONL or Markdown file") - parser.add_argument( - "-f", - "--format", - choices=["json", "markdown", "summary"], - default="json", - help="Output format (default: json)", - ) - parser.add_argument( - "-i", - "--index", - type=int, - help="Show only the chunk at this index", - ) - parser.add_argument( - "--join", - action="store_true", - help="Output concatenated markdown (shorthand for --format=markdown)", - ) - parser.add_argument( - "--context", - type=str, - help="JSON string of context to pass to formatter", - ) - args = setup_cli(parser) - - # --join is shorthand for --format=markdown - if args.join: - args.format = "markdown" - - try: - context = json.loads(args.context) if args.context else None - except json.JSONDecodeError as e: - print(f"Error parsing context JSON: {e}", file=sys.stderr) - sys.exit(1) - - try: - chunks, meta = format_file(args.file, context) - except (ValueError, FileNotFoundError) as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - # For summary format on markdown files, get raw chunks with metadata - raw_chunks = None - if args.format == "summary" and args.file.endswith(".md"): - from think.markdown import chunk_markdown - - text = load_markdown(args.file) - raw_chunks = chunk_markdown(text) - - # Filter to single chunk if requested - if args.index is not None: - if 0 <= args.index < len(chunks): - chunks = [chunks[args.index]] - if raw_chunks: - raw_chunks = [raw_chunks[args.index]] - else: - print( - f"Error: Index {args.index} out of range (0-{len(chunks) - 1})", - file=sys.stderr, - ) - sys.exit(1) - - if args.format == "markdown": - # Output concatenated markdown with header first - parts = [] - if meta.get("header"): - parts.append(meta["header"]) - parts.extend(chunk["markdown"] for chunk in chunks) - print("\n".join(parts)) - elif args.format == "summary": - _format_chunk_summary(chunks, raw_chunks) - else: - # Output JSON object with metadata and chunks - print(json.dumps({"meta": meta, "chunks": chunks}, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/think/help_cli.py b/think/help_cli.py deleted file mode 100644 index 43f193594..000000000 --- a/think/help_cli.py +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-only -# Copyright (c) 2026 sol pbc - -"""CLI command for interactive help with sol commands.""" - -from __future__ import annotations - -import argparse -import json -import subprocess -import sys - -from think.utils import setup_cli - - -def _read_stdin() -> str: - """Read a question from stdin. Shows a prompt if running in a terminal.""" - if sys.stdin.isatty(): - print( - "Enter your question (Ctrl+D to submit):", - file=sys.stderr, - ) - try: - return sys.stdin.read().strip() - except KeyboardInterrupt: - return "" - - -def main() -> None: - """Entry point for ``sol help``.""" - parser = argparse.ArgumentParser( - prog="sol help", - description="Get help with sol commands", - ) - parser.add_argument( - "question", - nargs="*", - help="Question about sol commands", - ) - - args = setup_cli(parser) - - if not args.question: - question = _read_stdin() - if not question: - # Imported here to avoid circular import (sol.py imports think.help_cli). - from sol import print_help - - print_help() - return - else: - question = " ".join(args.question).strip() - - config = {"name": "unified", "prompt": question} - config_json = json.dumps(config) - - print("Thinking...", end="", file=sys.stderr, flush=True) - - try: - proc = subprocess.Popen( - ["sol", "agents"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - except Exception as exc: - print(f"\rError: failed to run help agent: {exc}", file=sys.stderr) - sys.exit(1) - - assert proc.stdin is not None # for type checker - proc.stdin.write(config_json + "\n") - proc.stdin.close() - - finish_result: str | None = None - errors: list[str] = [] - - assert proc.stdout is not None # for type checker - for line in proc.stdout: - line = line.strip() - if not line: - continue - - try: - event = json.loads(line) - except json.JSONDecodeError: - continue - - event_type = event.get("event") - if event_type == "error": - errors.append(str(event.get("error", "Unknown error"))) - elif event_type == "finish": - result_value = event.get("result") - finish_result = "" if result_value is None else str(result_value) - - try: - proc.wait(timeout=120) - except subprocess.TimeoutExpired: - proc.kill() - print("\rError: help request timed out after 120 seconds.", file=sys.stderr) - sys.exit(1) - - # Clear the "Thinking..." indicator. - print("\r \r", end="", file=sys.stderr, flush=True) - - for message in errors: - print(f"Error: {message}", file=sys.stderr) - - if finish_result is not None and finish_result.strip(): - print(finish_result) - return - - if finish_result is not None: - print("Error: help agent returned an empty result.", file=sys.stderr) - sys.exit(1) - - stderr_output = proc.stderr.read() if proc.stderr else "" - if proc.returncode != 0 and stderr_output.strip(): - print(f"Error: {stderr_output.strip()}", file=sys.stderr) - else: - print("Error: no help response received.", file=sys.stderr) - sys.exit(1) diff --git a/think/planner.py b/think/planner.py index 1f76af1d6..1aa7b5caa 100644 --- a/think/planner.py +++ b/think/planner.py @@ -1,13 +1,9 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -import argparse -import os -import sys from pathlib import Path from .prompts import load_prompt -from .utils import setup_cli def _load_prompt() -> str: @@ -28,38 +24,3 @@ def generate_plan(request: str) -> str: thinking_budget=4096, system_instruction=_load_prompt(), ) - - -def parse_args() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - description="Generate an agent plan using configured provider" - ) - parser.add_argument( - "task", - nargs="?", - help="Path to .txt file with the request or '-' for stdin", - ) - parser.add_argument("-q", "--query", help="Request text directly") - return parser - - -def main() -> None: - parser = parse_args() - args = setup_cli(parser) - if args.query: - request = args.query - elif args.task is None: - parser.error("request not provided") - elif args.task == "-": - request = sys.stdin.read() - else: - if not os.path.isfile(args.task): - parser.error(f"File not found: {args.task}") - request = Path(args.task).read_text(encoding="utf-8") - - plan = generate_plan(request) - print(plan) - - -if __name__ == "__main__": # pragma: no cover - manual execution - main() -- 2.51.2