-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcli.py
More file actions
127 lines (108 loc) · 4.4 KB
/
cli.py
File metadata and controls
127 lines (108 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""Interactive REPL.
uv run ccm # default: real tools, current dir
uv run ccm --provider anthropic
uv run ccm --yolo # auto-allow ask permissions
"""
from __future__ import annotations
import argparse
import asyncio
import sys
from .claude_md import load as load_claude_md
from .harness import Harness
from .hooks import HookEngine, load_hook_settings
from .llm import LLMClient
from .permissions import PermissionEngine, load_settings, stdin_ask_handler
from .slash import builtin_registry, register_markdown_commands
from .tokens import print_waterfall
from .tools import default_registry, real_registry
SYSTEM_REAL = (
"You are an autonomous coding assistant with access to a shell and the "
"filesystem (Bash, Read, Glob, Grep). Investigate before answering. "
"Cite file paths and line numbers in your final answer. Be terse."
)
async def run_repl(args: argparse.Namespace) -> int:
model = args.model or {
"openai": "gpt-4o-mini",
"anthropic": "claude-sonnet-4-6",
"ollama": "qwen2:7b",
}.get(args.provider, "gpt-4o-mini")
llm = LLMClient(provider=args.provider, model=model, base_url=args.base_url)
registry = real_registry() if args.tools == "real" else default_registry()
claude_md = "" if args.no_claude_md else load_claude_md(args.cwd)
perm = PermissionEngine(
settings=load_settings(args.cwd),
ask_handler=stdin_ask_handler,
bypass=args.yolo,
)
hooks = HookEngine(load_hook_settings(args.cwd))
harness = Harness(
llm_client=llm,
tool_registry=registry,
system_prompt=SYSTEM_REAL if args.tools == "real" else (
"You are a helpful assistant with tools."
),
claude_md=claude_md,
use_exact_cache=not args.no_cache,
use_semantic_cache=not args.no_cache,
use_prompt_cache=not args.no_cache,
use_microcompact=args.microcompact,
permission_engine=perm,
hook_engine=hooks,
)
slash = builtin_registry()
register_markdown_commands(slash, args.cwd)
harness.slash = slash
print(f"claude-code-mini (provider={args.provider}, model={model}, tools={args.tools})")
print("Type a message, or /help for slash commands. Ctrl-D or /exit to leave.\n")
loop_count = 0
while True:
loop_count += 1
try:
line = await asyncio.to_thread(input, f"[{loop_count}] > ")
except (EOFError, KeyboardInterrupt):
print()
break
line = line.strip()
if not line:
continue
outcome = await slash.dispatch(line, harness)
if outcome.handled:
if outcome.message:
print(outcome.message)
if outcome.exit:
break
continue
try:
answer = await harness.run(line, max_turns=args.max_turns)
except Exception as e: # noqa: BLE001
print(f"error: {e}")
continue
print(answer)
print("\n=== TOKEN WATERFALL ===")
print_waterfall(harness.records)
return 0
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(prog="ccm",
description="claude-code-mini REPL")
parser.add_argument("--provider", choices=["openai", "anthropic", "ollama"],
default="openai")
parser.add_argument("--model", default=None)
parser.add_argument("--base-url", default=None,
help="override API base URL; Ollama default is "
"http://localhost:11434/v1")
parser.add_argument("--tools", choices=["demo", "real"], default="real")
parser.add_argument("--cwd", default=None)
parser.add_argument("--no-claude-md", action="store_true")
parser.add_argument("--no-cache", action="store_true")
parser.add_argument("--microcompact", action="store_true",
help="enable Stage 3 microcompact (LLM summary call)")
parser.add_argument("--yolo", action="store_true",
help="auto-allow any tool call that would otherwise prompt")
parser.add_argument("--max-turns", type=int, default=8)
args = parser.parse_args(argv)
return asyncio.run(run_repl(args))
def main_sync(argv: list[str] | None = None) -> int:
"""Console script entrypoint (ccm)."""
return main(argv)
if __name__ == "__main__":
sys.exit(main())