local-coding-agent 1.1.0__py3-none-any.whl
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.
- local_coding_agent/__init__.py +314 -0
- local_coding_agent/__main__.py +5 -0
- local_coding_agent/acp_server/__init__.py +38 -0
- local_coding_agent/acp_server/_codec.py +95 -0
- local_coding_agent/acp_server/_protocol.py +13 -0
- local_coding_agent/acp_server/_server.py +663 -0
- local_coding_agent/acp_server/_session.py +24 -0
- local_coding_agent/acp_server/_tools.py +126 -0
- local_coding_agent/ast_compactor.py +130 -0
- local_coding_agent/atomizer.py +77 -0
- local_coding_agent/benchmark/__init__.py +57 -0
- local_coding_agent/benchmark/_cases.py +307 -0
- local_coding_agent/benchmark/_judge.py +175 -0
- local_coding_agent/benchmark/_oracles.py +157 -0
- local_coding_agent/benchmark/_runner.py +259 -0
- local_coding_agent/benchmark/_stats.py +22 -0
- local_coding_agent/benchmark/_summary.py +143 -0
- local_coding_agent/benchmark_oracle_worker.py +169 -0
- local_coding_agent/calibration.py +154 -0
- local_coding_agent/capability.py +450 -0
- local_coding_agent/chat_compaction.py +276 -0
- local_coding_agent/cli/__init__.py +12 -0
- local_coding_agent/cli/_dispatch.py +377 -0
- local_coding_agent/cli/_handlers.py +320 -0
- local_coding_agent/cli/_handlers2.py +725 -0
- local_coding_agent/cli/_input.py +61 -0
- local_coding_agent/cli/_main.py +159 -0
- local_coding_agent/cli/_parser.py +327 -0
- local_coding_agent/context_manager.py +155 -0
- local_coding_agent/controller/__init__.py +18 -0
- local_coding_agent/controller/_constants.py +108 -0
- local_coding_agent/controller/_controller.py +1008 -0
- local_coding_agent/controller/_post_apply.py +216 -0
- local_coding_agent/delegator.py +180 -0
- local_coding_agent/desktop/__init__.py +8 -0
- local_coding_agent/desktop/app.py +100 -0
- local_coding_agent/desktop/assets/lucide.min.js +12 -0
- local_coding_agent/desktop/assets/tailwind.css +1 -0
- local_coding_agent/desktop/client_js.py +1271 -0
- local_coding_agent/desktop/components.py +555 -0
- local_coding_agent/desktop/server/__init__.py +37 -0
- local_coding_agent/desktop/server/_handlers.py +2178 -0
- local_coding_agent/desktop/server/_models.py +204 -0
- local_coding_agent/desktop/server/_server.py +347 -0
- local_coding_agent/desktop/server/_telemetry.py +87 -0
- local_coding_agent/desktop/styles.py +76 -0
- local_coding_agent/desktop/ui.py +53 -0
- local_coding_agent/doctor.py +413 -0
- local_coding_agent/hooks/__init__.py +19 -0
- local_coding_agent/hooks/_adapters.py +458 -0
- local_coding_agent/hooks/_bridge.py +346 -0
- local_coding_agent/hooks/_decision.py +26 -0
- local_coding_agent/lsp/__init__.py +60 -0
- local_coding_agent/lsp/_client.py +322 -0
- local_coding_agent/lsp/_fallback.py +297 -0
- local_coding_agent/lsp/_manager.py +173 -0
- local_coding_agent/lsp/_mock.py +200 -0
- local_coding_agent/lsp/_types.py +398 -0
- local_coding_agent/mcp_config.py +398 -0
- local_coding_agent/mcp_server.py +377 -0
- local_coding_agent/memory.py +140 -0
- local_coding_agent/mode_router.py +200 -0
- local_coding_agent/model_scanner.py +525 -0
- local_coding_agent/monitor.py +635 -0
- local_coding_agent/observation_policy.py +184 -0
- local_coding_agent/ollama_adapter.py +736 -0
- local_coding_agent/plan_mode.py +653 -0
- local_coding_agent/prescriptions.py +278 -0
- local_coding_agent/profiles.py +239 -0
- local_coding_agent/repository_tools.py +782 -0
- local_coding_agent/ripgrep.py +326 -0
- local_coding_agent/semantic_linter.py +122 -0
- local_coding_agent/service.py +405 -0
- local_coding_agent/session_events.py +684 -0
- local_coding_agent/session_query.py +578 -0
- local_coding_agent/skill_config.py +426 -0
- local_coding_agent/smoke.py +268 -0
- local_coding_agent/speculative_racing.py +85 -0
- local_coding_agent/spill.py +258 -0
- local_coding_agent/stats.py +255 -0
- local_coding_agent/stdio.py +132 -0
- local_coding_agent/subagent.py +516 -0
- local_coding_agent/task.py +90 -0
- local_coding_agent/task_store.py +142 -0
- local_coding_agent/tasks.py +240 -0
- local_coding_agent/terminal/__init__.py +35 -0
- local_coding_agent/terminal/_manager.py +156 -0
- local_coding_agent/terminal/_process.py +148 -0
- local_coding_agent/terminal/_session.py +452 -0
- local_coding_agent/terminal/_tools.py +233 -0
- local_coding_agent/terminal/_types.py +60 -0
- local_coding_agent/tool_call_parser.py +178 -0
- local_coding_agent/validators.py +563 -0
- local_coding_agent/vram_fit.py +240 -0
- local_coding_agent/worker_pool.py +516 -0
- local_coding_agent-1.1.0.dist-info/METADATA +202 -0
- local_coding_agent-1.1.0.dist-info/RECORD +101 -0
- local_coding_agent-1.1.0.dist-info/WHEEL +5 -0
- local_coding_agent-1.1.0.dist-info/entry_points.txt +3 -0
- local_coding_agent-1.1.0.dist-info/licenses/LICENSE +21 -0
- local_coding_agent-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Task input loading helpers for the local-coding-agent CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..task import TaskEnvelope
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_task_file(path: str | Path) -> TaskEnvelope:
|
|
12
|
+
# utf-8-sig: PowerShell 5.1 `Set-Content -Encoding UTF8` writes a BOM.
|
|
13
|
+
raw = Path(path).read_bytes().decode("utf-8-sig")
|
|
14
|
+
value = json.loads(raw)
|
|
15
|
+
if not isinstance(value, dict):
|
|
16
|
+
raise ValueError("task JSON must be an object")
|
|
17
|
+
return TaskEnvelope.from_mapping(value)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_task_input(
|
|
21
|
+
task_value: str | Path | None = None,
|
|
22
|
+
task_file: str | Path | None = None,
|
|
23
|
+
) -> TaskEnvelope:
|
|
24
|
+
if task_file is not None:
|
|
25
|
+
return load_task_file(task_file)
|
|
26
|
+
if task_value is not None:
|
|
27
|
+
val_str = str(task_value).strip()
|
|
28
|
+
if (val_str.startswith("'") and val_str.endswith("'")) or (val_str.startswith('"') and val_str.endswith('"')):
|
|
29
|
+
stripped = val_str[1:-1].strip()
|
|
30
|
+
if stripped.startswith("{") and stripped.endswith("}"):
|
|
31
|
+
val_str = stripped
|
|
32
|
+
if val_str.startswith("{") and val_str.endswith("}"):
|
|
33
|
+
try:
|
|
34
|
+
parsed = json.loads(val_str)
|
|
35
|
+
if not isinstance(parsed, dict):
|
|
36
|
+
raise ValueError("task JSON must be an object")
|
|
37
|
+
return TaskEnvelope.from_mapping(parsed)
|
|
38
|
+
except json.JSONDecodeError:
|
|
39
|
+
# Try replacing single quotes with double quotes if standard JSON failed
|
|
40
|
+
try:
|
|
41
|
+
import ast
|
|
42
|
+
parsed_ast = ast.literal_eval(val_str)
|
|
43
|
+
if isinstance(parsed_ast, dict):
|
|
44
|
+
return TaskEnvelope.from_mapping(parsed_ast)
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
raise ValueError(f"malformed inline task JSON: {val_str}")
|
|
48
|
+
try:
|
|
49
|
+
path = Path(task_value)
|
|
50
|
+
if path.is_file():
|
|
51
|
+
return load_task_file(path)
|
|
52
|
+
except (OSError, ValueError):
|
|
53
|
+
pass
|
|
54
|
+
try:
|
|
55
|
+
parsed = json.loads(val_str)
|
|
56
|
+
if isinstance(parsed, dict):
|
|
57
|
+
return TaskEnvelope.from_mapping(parsed)
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
return load_task_file(task_value)
|
|
61
|
+
raise ValueError("--task or --task-file is required")
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""CLI main entry point for the local-coding-agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Sequence
|
|
8
|
+
|
|
9
|
+
from ..calibration import calibrate_for_model
|
|
10
|
+
from ..controller import Controller
|
|
11
|
+
from ..memory import ModelMemoryManager
|
|
12
|
+
from ..ollama_adapter import OllamaError, build_client
|
|
13
|
+
from ..profiles import get_profile
|
|
14
|
+
from ..benchmark import run_benchmark, write_artifact
|
|
15
|
+
|
|
16
|
+
from ._dispatch import handle_subcommand
|
|
17
|
+
from ._input import load_task_input
|
|
18
|
+
from ._parser import build_parser
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
22
|
+
args = build_parser().parse_args(argv)
|
|
23
|
+
|
|
24
|
+
if args.subcommand:
|
|
25
|
+
sub_code = handle_subcommand(args)
|
|
26
|
+
if sub_code != -1:
|
|
27
|
+
return sub_code
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
overrides = {}
|
|
31
|
+
if args.endpoint:
|
|
32
|
+
overrides["endpoint"] = args.endpoint
|
|
33
|
+
if args.num_ctx is not None:
|
|
34
|
+
overrides["num_ctx"] = args.num_ctx
|
|
35
|
+
profile = get_profile(args.profile, **overrides)
|
|
36
|
+
client = build_client(profile)
|
|
37
|
+
if args.benchmark:
|
|
38
|
+
if args.task is not None or getattr(args, "task_file", None) is not None:
|
|
39
|
+
raise ValueError("--benchmark cannot be combined with --task or --task-file")
|
|
40
|
+
if args.unload_all or args.unload_model or args.vram_limit_bytes is not None or args.calibrate_workers is not None:
|
|
41
|
+
raise ValueError("--benchmark cannot be combined with memory controls")
|
|
42
|
+
if args.benchmark_repeats <= 0:
|
|
43
|
+
raise ValueError("--benchmark-repeats must be positive")
|
|
44
|
+
if args.benchmark_timeout_seconds <= 0:
|
|
45
|
+
raise ValueError("--benchmark-timeout-seconds must be positive")
|
|
46
|
+
names = tuple(args.benchmark_models or (
|
|
47
|
+
"qwen3-8b-q6k",
|
|
48
|
+
"qwen3.8-27b-q4",
|
|
49
|
+
"qwen2.5-coder",
|
|
50
|
+
"ornith-9b",
|
|
51
|
+
"qwen3-coder-30b",
|
|
52
|
+
"devstral-small-2-24b",
|
|
53
|
+
))
|
|
54
|
+
artifact = {
|
|
55
|
+
"schema": "local-coding-agent/benchmark-v1",
|
|
56
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
57
|
+
"repeats": args.benchmark_repeats,
|
|
58
|
+
"models": [],
|
|
59
|
+
}
|
|
60
|
+
for name in names:
|
|
61
|
+
benchmark_profile = get_profile(
|
|
62
|
+
name,
|
|
63
|
+
**overrides,
|
|
64
|
+
timeout_seconds=args.benchmark_timeout_seconds,
|
|
65
|
+
)
|
|
66
|
+
benchmark_client = build_client(benchmark_profile)
|
|
67
|
+
try:
|
|
68
|
+
available = benchmark_client.available_models()
|
|
69
|
+
available_names = {
|
|
70
|
+
item.get("name")
|
|
71
|
+
for item in available.get("models", [])
|
|
72
|
+
if isinstance(item, dict) and isinstance(item.get("name"), str)
|
|
73
|
+
}
|
|
74
|
+
if benchmark_profile.model not in available_names:
|
|
75
|
+
artifact["models"].append(
|
|
76
|
+
{
|
|
77
|
+
"profile": name,
|
|
78
|
+
"model": benchmark_profile.model,
|
|
79
|
+
"status": "unavailable",
|
|
80
|
+
"error": f"model is not present in Ollama /api/tags: {benchmark_profile.model}",
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
continue
|
|
84
|
+
model_info = next(
|
|
85
|
+
(
|
|
86
|
+
item
|
|
87
|
+
for item in available.get("models", [])
|
|
88
|
+
if isinstance(item, dict) and item.get("name") == benchmark_profile.model
|
|
89
|
+
),
|
|
90
|
+
{},
|
|
91
|
+
)
|
|
92
|
+
memory = ModelMemoryManager(benchmark_client)
|
|
93
|
+
memory_before = memory.snapshot().as_dict()
|
|
94
|
+
if memory_before["models"]:
|
|
95
|
+
memory.unload_all()
|
|
96
|
+
run = run_benchmark(
|
|
97
|
+
name,
|
|
98
|
+
benchmark_client,
|
|
99
|
+
repeats=args.benchmark_repeats,
|
|
100
|
+
max_turns=args.max_turns,
|
|
101
|
+
)
|
|
102
|
+
run["profile"] = benchmark_profile.__dict__
|
|
103
|
+
run["status"] = "completed"
|
|
104
|
+
run["memory_before"] = memory_before
|
|
105
|
+
run["memory_after"] = memory.snapshot().as_dict()
|
|
106
|
+
run["ollama_model_info"] = {
|
|
107
|
+
key: model_info.get(key)
|
|
108
|
+
for key in ("name", "size", "digest", "details", "capabilities")
|
|
109
|
+
if key in model_info
|
|
110
|
+
}
|
|
111
|
+
artifact["models"].append(run)
|
|
112
|
+
except OllamaError as error:
|
|
113
|
+
artifact["models"].append(
|
|
114
|
+
{
|
|
115
|
+
"profile": name,
|
|
116
|
+
"model": benchmark_profile.model,
|
|
117
|
+
"status": "unavailable",
|
|
118
|
+
"error": {"kind": error.kind, "message": str(error)},
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
write_artifact(args.benchmark_output, artifact)
|
|
122
|
+
print(json.dumps(artifact, ensure_ascii=False, indent=2))
|
|
123
|
+
return 0 if artifact["models"] and all(item["status"] == "completed" for item in artifact["models"]) else 1
|
|
124
|
+
if args.keep_model and args.vram_limit_bytes is None:
|
|
125
|
+
raise ValueError("--keep-model requires --vram-limit-bytes")
|
|
126
|
+
if args.calibrate_workers is not None:
|
|
127
|
+
report = calibrate_for_model(
|
|
128
|
+
client,
|
|
129
|
+
profile.model,
|
|
130
|
+
vram_budget_bytes=args.calibrate_workers,
|
|
131
|
+
per_worker_context_bytes=args.parallel_context_bytes,
|
|
132
|
+
)
|
|
133
|
+
print(json.dumps({"status": "calibrated", "report": report}, ensure_ascii=False, indent=2))
|
|
134
|
+
return 0
|
|
135
|
+
if args.unload_all or args.unload_model or args.vram_limit_bytes is not None:
|
|
136
|
+
manager = ModelMemoryManager(client)
|
|
137
|
+
if args.unload_all:
|
|
138
|
+
snapshot = manager.unload_all()
|
|
139
|
+
elif args.unload_model:
|
|
140
|
+
snapshot = manager.unload_model(args.unload_model)
|
|
141
|
+
else:
|
|
142
|
+
snapshot = manager.snapshot()
|
|
143
|
+
if args.vram_limit_bytes is not None:
|
|
144
|
+
snapshot = manager.enforce_limit(args.vram_limit_bytes, keep=tuple(args.keep_model))
|
|
145
|
+
print(json.dumps({"status": "memory_reconciled", "memory": snapshot.as_dict()}, ensure_ascii=False, indent=2))
|
|
146
|
+
return 0
|
|
147
|
+
if args.task is None and getattr(args, "task_file", None) is None:
|
|
148
|
+
raise ValueError("--task or --task-file is required unless memory controls are used")
|
|
149
|
+
task = load_task_input(args.task, getattr(args, "task_file", None))
|
|
150
|
+
result = Controller(
|
|
151
|
+
client,
|
|
152
|
+
args.workspace,
|
|
153
|
+
max_turns=args.max_turns,
|
|
154
|
+
).run(task, apply=args.apply)
|
|
155
|
+
except (OSError, UnicodeError, json.JSONDecodeError, ValueError, OllamaError) as error:
|
|
156
|
+
print(json.dumps({"status": "failed", "error": {"kind": "input", "message": str(error)}}, ensure_ascii=False))
|
|
157
|
+
return 2
|
|
158
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
159
|
+
return 0 if result.get("status") == "accepted" else 1
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Argument parser construction for the local-coding-agent CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..profiles import list_profiles
|
|
10
|
+
|
|
11
|
+
DEFAULT_PROFILE = "qwen2.5-coder"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _default_profile() -> str:
|
|
15
|
+
return os.environ.get("LCA_PROFILE") or DEFAULT_PROFILE
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _default_workspace() -> Path:
|
|
19
|
+
return Path(os.environ.get("LCA_WORKSPACE") or Path.cwd())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
prog="local-agent",
|
|
25
|
+
description="Local Coding Agent: Bounded controller for atomic coding tasks.",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Root options (backward compatibility)
|
|
29
|
+
parser.add_argument("--task", help="UTF-8 JSON task envelope (inline JSON string or file path)")
|
|
30
|
+
parser.add_argument("--task-file", type=Path, dest="task_file", help="Path to UTF-8 JSON task envelope file")
|
|
31
|
+
parser.add_argument("--workspace", type=Path, default=_default_workspace(), help="Workspace directory (env: LCA_WORKSPACE)")
|
|
32
|
+
parser.add_argument("--profile", choices=list_profiles(), default=_default_profile(), help="Model profile to use (env: LCA_PROFILE)")
|
|
33
|
+
parser.add_argument("--model", help="Override the profile model tag for any installed Ollama/llama.cpp model (e.g. qwen2.5-coder:7b)")
|
|
34
|
+
parser.add_argument("--endpoint", help="Override the profile Ollama endpoint")
|
|
35
|
+
parser.add_argument("--max-turns", type=int, default=4)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--apply",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Apply the accepted patch to the workspace instead of proposal-only mode",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument("--num-ctx", type=int, help="Override model context window in tokens")
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--benchmark",
|
|
44
|
+
action="store_true",
|
|
45
|
+
help="Run the fixed proposal-only benchmark instead of one task",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--benchmark-model",
|
|
49
|
+
action="append",
|
|
50
|
+
choices=list_profiles(),
|
|
51
|
+
dest="benchmark_models",
|
|
52
|
+
help="Benchmark one named profile; repeat the option for multiple models",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument("--benchmark-repeats", type=int, default=1)
|
|
55
|
+
parser.add_argument("--benchmark-timeout-seconds", type=float, default=300)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--benchmark-output",
|
|
58
|
+
type=Path,
|
|
59
|
+
default=Path(".local-run") / "benchmarks" / "latest.json",
|
|
60
|
+
)
|
|
61
|
+
memory_group = parser.add_mutually_exclusive_group()
|
|
62
|
+
memory_group.add_argument("--unload-model", metavar="MODEL", help="Unload one model from Ollama VRAM")
|
|
63
|
+
memory_group.add_argument("--unload-all", action="store_true", help="Unload every model currently held by Ollama")
|
|
64
|
+
parser.add_argument("--vram-limit-bytes", type=int, help="Evict unprotected models until this VRAM budget fits")
|
|
65
|
+
parser.add_argument("--keep-model", action="append", default=[], help="Model name to protect during VRAM eviction")
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--calibrate-workers",
|
|
68
|
+
type=int,
|
|
69
|
+
metavar="VRAM_BYTES",
|
|
70
|
+
help="Derive a bounded worker count for the selected profile model within this VRAM budget",
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--parallel-context-bytes",
|
|
74
|
+
type=int,
|
|
75
|
+
help="Measured incremental VRAM estimate per concurrent request context/KV cache",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Subcommands
|
|
79
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="Available subcommands")
|
|
80
|
+
|
|
81
|
+
# 1. delegate (run)
|
|
82
|
+
del_p = subparsers.add_parser("delegate", aliases=["run"], help="Delegate an atomic task to local model")
|
|
83
|
+
del_p.add_argument("--task", help="UTF-8 JSON task envelope (inline string or file path)")
|
|
84
|
+
del_p.add_argument("--task-file", type=Path, help="Path to task JSON envelope file")
|
|
85
|
+
del_p.add_argument("--workspace", type=Path, default=_default_workspace(), help="Workspace directory (env: LCA_WORKSPACE)")
|
|
86
|
+
del_p.add_argument("--profile", choices=list_profiles(), default=_default_profile(), help="Model profile to use (env: LCA_PROFILE)")
|
|
87
|
+
del_p.add_argument("--model", help="Override the profile model tag for any installed Ollama/llama.cpp model")
|
|
88
|
+
del_p.add_argument("--endpoint", help="Override Ollama/OpenAI endpoint")
|
|
89
|
+
del_p.add_argument("--max-turns", type=int, default=4, help="Maximum conversation turns")
|
|
90
|
+
del_p.add_argument("--num-ctx", type=int, help="Override context window in tokens")
|
|
91
|
+
del_p.add_argument("--speculative-drafts", type=int, default=1, help="Number of concurrent speculative drafts to race")
|
|
92
|
+
del_p.add_argument("--apply", action="store_true", help="Apply accepted patch directly with auto-rollback")
|
|
93
|
+
del_p.add_argument("--json", action="store_true", help="Ensure JSON output")
|
|
94
|
+
|
|
95
|
+
# 2. decompose (atomize)
|
|
96
|
+
dec_p = subparsers.add_parser("decompose", aliases=["atomize"], help="Preflight and decompose wide tasks into atomic envelopes")
|
|
97
|
+
dec_p.add_argument("--task", help="UTF-8 JSON task envelope (inline string or file path)")
|
|
98
|
+
dec_p.add_argument("--task-file", type=Path, help="Path to task JSON envelope file")
|
|
99
|
+
dec_p.add_argument("--strategy", choices=["by_files", "per_file"], default="by_files", help="Decomposition strategy")
|
|
100
|
+
dec_p.add_argument("--budget-files", type=int, default=5, help="Maximum allowed files per subtask envelope")
|
|
101
|
+
dec_p.add_argument("--budget-bytes", type=int, default=32000, help="Maximum context bytes")
|
|
102
|
+
dec_p.add_argument("--budget-checks", type=int, default=3, help="Maximum test checks per envelope")
|
|
103
|
+
dec_p.add_argument("--json", action="store_true", help="Output decomposition result in JSON format")
|
|
104
|
+
|
|
105
|
+
# 3. profiles
|
|
106
|
+
prof_p = subparsers.add_parser("profiles", help="List and inspect model profiles")
|
|
107
|
+
prof_p.add_argument("profile_action", nargs="?", choices=["list", "get"], default="list", help="Action: list or get")
|
|
108
|
+
prof_p.add_argument("name", nargs="?", help="Profile name (for 'get')")
|
|
109
|
+
prof_p.add_argument("--check-ollama", action="store_true", help="Query Ollama /api/tags to show local install status")
|
|
110
|
+
prof_p.add_argument("--endpoint", default="http://127.0.0.1:11434", help="Ollama endpoint")
|
|
111
|
+
prof_p.add_argument("--json", action="store_true", help="Output profiles in JSON format")
|
|
112
|
+
|
|
113
|
+
# 4. memory
|
|
114
|
+
mem_p = subparsers.add_parser("memory", help="Inspect and manage Ollama VRAM allocation")
|
|
115
|
+
mem_p.add_argument("memory_action", choices=["status", "unload", "unload-all", "enforce"], help="Memory action")
|
|
116
|
+
mem_p.add_argument("model", nargs="?", help="Model name to unload (for 'unload')")
|
|
117
|
+
mem_p.add_argument("--limit", type=int, dest="limit", help="VRAM limit in bytes (for 'enforce')")
|
|
118
|
+
mem_p.add_argument("--keep", action="append", default=[], help="Model name to protect from eviction")
|
|
119
|
+
mem_p.add_argument("--profile", default=DEFAULT_PROFILE, help="Profile to derive client settings")
|
|
120
|
+
mem_p.add_argument("--endpoint", default="http://127.0.0.1:11434", help="Ollama endpoint")
|
|
121
|
+
mem_p.add_argument("--json", action="store_true", help="Output memory status in JSON")
|
|
122
|
+
|
|
123
|
+
# 5. calibrate
|
|
124
|
+
cal_p = subparsers.add_parser("calibrate", help="Calculate worker capacity from VRAM budget")
|
|
125
|
+
cal_p.add_argument("--vram-bytes", type=int, required=True, help="Target VRAM budget in bytes")
|
|
126
|
+
cal_p.add_argument("--profile", choices=list_profiles(), default="qwen3-8b-q6k", help="Model profile")
|
|
127
|
+
cal_p.add_argument("--parallel-context-bytes", type=int, help="Context VRAM delta per worker")
|
|
128
|
+
cal_p.add_argument("--endpoint", help="Ollama endpoint")
|
|
129
|
+
cal_p.add_argument("--json", action="store_true", help="Output report in JSON format")
|
|
130
|
+
|
|
131
|
+
# 6. benchmark
|
|
132
|
+
bench_p = subparsers.add_parser("benchmark", help="Run benchmark across model profiles")
|
|
133
|
+
bench_p.add_argument("--model", action="append", choices=list_profiles(), dest="benchmark_models", help="Model profile to benchmark")
|
|
134
|
+
bench_p.add_argument("--repeats", type=int, default=1, dest="benchmark_repeats", help="Benchmark repeats")
|
|
135
|
+
bench_p.add_argument("--timeout-seconds", type=float, default=300, dest="benchmark_timeout_seconds", help="Timeout per model run")
|
|
136
|
+
bench_p.add_argument("--output", type=Path, default=Path(".local-run") / "benchmarks" / "latest.json", dest="benchmark_output", help="Output artifact path")
|
|
137
|
+
bench_p.add_argument("--max-turns", type=int, default=4, help="Max turns per task")
|
|
138
|
+
bench_p.add_argument("--json", action="store_true", help="Output benchmark results in JSON format")
|
|
139
|
+
bench_p.add_argument("--ladder", action="store_true", help="Run adaptive capability ladder benchmark")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# 7. apply
|
|
143
|
+
app_p = subparsers.add_parser("apply", help="Safely apply patch to workspace with verification and auto-rollback")
|
|
144
|
+
app_p.add_argument("--patch-file", type=Path, help="Path to unified diff patch file")
|
|
145
|
+
app_p.add_argument("--patch", help="Unified diff patch string")
|
|
146
|
+
app_p.add_argument("--workspace", type=Path, default=Path.cwd(), help="Target workspace path")
|
|
147
|
+
app_p.add_argument("--file", action="append", dest="files", default=[], help="Declared file scope (repeatable; the patch must touch only these files)")
|
|
148
|
+
app_p.add_argument("--check", action="append", dest="checks", default=[], help="Targeted check command(s) to verify")
|
|
149
|
+
app_p.add_argument("--json", action="store_true", help="Output result in JSON format")
|
|
150
|
+
|
|
151
|
+
# 8. doctor
|
|
152
|
+
doc_p = subparsers.add_parser("doctor", help="Run system diagnostics and model recommendations")
|
|
153
|
+
doc_p.add_argument("--endpoint", default="http://127.0.0.1:11434", help="Ollama endpoint to check")
|
|
154
|
+
doc_p.add_argument("--fix", action="store_true", help="Auto-remediate missing MCP configs, Agent Skills, and setup")
|
|
155
|
+
doc_p.add_argument("--dry-run", action="store_true", help="Preview remediation actions without writing")
|
|
156
|
+
doc_p.add_argument("--json", action="store_true", help="Output diagnostic report in JSON format")
|
|
157
|
+
doc_p.add_argument("--strict", action="store_true", help="Exit with code 1 if any check fails")
|
|
158
|
+
|
|
159
|
+
# 9. init-mcp
|
|
160
|
+
mcp_p = subparsers.add_parser("init-mcp", help="Generate or configure MCP client integration")
|
|
161
|
+
mcp_p.add_argument(
|
|
162
|
+
"--client",
|
|
163
|
+
choices=["claude", "cursor", "windsurf", "cline", "antigravity", "opencode", "codex", "chatgpt", "vscode", "auto", "all"],
|
|
164
|
+
default="claude",
|
|
165
|
+
help="Target MCP client (default: claude, or 'auto'/'all')",
|
|
166
|
+
)
|
|
167
|
+
mcp_p.add_argument("--claude", action="store_const", const="claude", dest="client", help="Configure for Claude Desktop & Claude Code")
|
|
168
|
+
mcp_p.add_argument("--cursor", action="store_const", const="cursor", dest="client", help="Configure for Cursor")
|
|
169
|
+
mcp_p.add_argument("--windsurf", action="store_const", const="windsurf", dest="client", help="Configure for Windsurf")
|
|
170
|
+
mcp_p.add_argument("--cline", action="store_const", const="cline", dest="client", help="Configure for Cline Desktop / Extension")
|
|
171
|
+
mcp_p.add_argument("--antigravity", action="store_const", const="antigravity", dest="client", help="Configure for Antigravity Desktop & agy CLI")
|
|
172
|
+
mcp_p.add_argument("--opencode", action="store_const", const="opencode", dest="client", help="Configure for OpenCode Desktop & CLI")
|
|
173
|
+
mcp_p.add_argument("--codex", action="store_const", const="codex", dest="client", help="Configure Codex Desktop & Codex CLI (~/.codex/config.toml)")
|
|
174
|
+
mcp_p.add_argument("--chatgpt", action="store_const", const="codex", dest="client", help="Legacy alias for --codex")
|
|
175
|
+
mcp_p.add_argument("--vscode", action="store_const", const="cline", dest="client", help="Configure for VS Code / Cline")
|
|
176
|
+
mcp_p.add_argument("--auto", action="store_const", const="auto", dest="client", help="Auto-detect IDEs in workspace and host environment")
|
|
177
|
+
mcp_p.add_argument("--all", action="store_const", const="all", dest="client", help="Configure all detected IDE environments")
|
|
178
|
+
mcp_p.add_argument("--workspace", default=".", help="Workspace path for the MCP server")
|
|
179
|
+
mcp_p.add_argument("--profile", default="qwen3-8b-q6k", help="Default profile for MCP delegation")
|
|
180
|
+
mcp_p.add_argument("--dry-run", action="store_true", help="Print config without writing to disk")
|
|
181
|
+
mcp_p.add_argument("--write", action="store_true", help="Write/merge directly to client configuration file")
|
|
182
|
+
mcp_p.add_argument("--path", type=Path, help="Explicit configuration file path")
|
|
183
|
+
|
|
184
|
+
# 10. init-skill (skill)
|
|
185
|
+
skill_p = subparsers.add_parser("init-skill", aliases=["skill"], help="Export or install Agent Skill to agent directories")
|
|
186
|
+
skill_p.add_argument(
|
|
187
|
+
"--client",
|
|
188
|
+
choices=["codex", "antigravity", "claude", "workspace", "auto", "all"],
|
|
189
|
+
default="auto",
|
|
190
|
+
help="Target agent ecosystem (default: auto)",
|
|
191
|
+
)
|
|
192
|
+
skill_p.add_argument("--workspace", default=".", help="Workspace path")
|
|
193
|
+
skill_p.add_argument("--target-dir", type=Path, dest="path", help="Explicit target directory or file path")
|
|
194
|
+
skill_p.add_argument("--dry-run", action="store_true", help="Preview target paths without writing")
|
|
195
|
+
skill_p.add_argument("--write", action="store_true", help="Write SKILL.md to target directories")
|
|
196
|
+
skill_p.add_argument("--print", action="store_true", dest="print_content", help="Print SKILL.md to stdout")
|
|
197
|
+
skill_p.add_argument("--json", action="store_true", help="Output result in JSON format")
|
|
198
|
+
|
|
199
|
+
# 11. test-run / smoke
|
|
200
|
+
smoke_p = subparsers.add_parser("test-run", aliases=["smoke"], help="Run interactive end-to-end smoke test")
|
|
201
|
+
smoke_p.add_argument("--profile", default="qwen2.5-coder", help="Model profile to use")
|
|
202
|
+
smoke_p.add_argument("--mock", action="store_true", help="Use scripted mock model instead of live Ollama")
|
|
203
|
+
smoke_p.add_argument(
|
|
204
|
+
"--allow-mock-fallback",
|
|
205
|
+
action="store_true",
|
|
206
|
+
help="Explicitly allow a synthetic mock fallback when live Ollama is unavailable",
|
|
207
|
+
)
|
|
208
|
+
smoke_p.add_argument("--no-fallback", action="store_true", help=argparse.SUPPRESS)
|
|
209
|
+
|
|
210
|
+
# 12. serve-mcp
|
|
211
|
+
serve_mcp_p = subparsers.add_parser("serve-mcp", help="Run the MCP stdio server")
|
|
212
|
+
serve_mcp_p.add_argument("--workspace-ref", default="workspace")
|
|
213
|
+
serve_mcp_p.add_argument("--workspace", default=".")
|
|
214
|
+
serve_mcp_p.add_argument("--enable-tasks", action="store_true", help="Mount Tasks extension and apply_proposal")
|
|
215
|
+
serve_mcp_p.add_argument("--profile", help="Default model profile")
|
|
216
|
+
serve_mcp_p.add_argument("--endpoint", help="Ollama API endpoint")
|
|
217
|
+
|
|
218
|
+
# 13. monitor
|
|
219
|
+
mon_p = subparsers.add_parser("monitor", help="Start the live HTTP monitoring dashboard")
|
|
220
|
+
mon_p.add_argument("--host", default="127.0.0.1")
|
|
221
|
+
mon_p.add_argument("--port", type=int, default=8765)
|
|
222
|
+
# 14. skeletonize
|
|
223
|
+
skel_p = subparsers.add_parser("skeletonize", help="Skeletonize source file by collapsing non-target structures")
|
|
224
|
+
skel_p.add_argument("file", type=Path, help="Path to source file")
|
|
225
|
+
skel_p.add_argument("--symbol", action="append", dest="symbols", default=[], help="Symbol name to keep expanded")
|
|
226
|
+
skel_p.add_argument("--json", action="store_true", help="Output skeleton in JSON format")
|
|
227
|
+
|
|
228
|
+
# 15. lint-patch
|
|
229
|
+
lint_p = subparsers.add_parser("lint-patch", help="Run sub-50ms fast static linter pre-gates on a patch")
|
|
230
|
+
lint_p.add_argument("--patch-file", type=Path, help="Path to unified diff patch file")
|
|
231
|
+
lint_p.add_argument("--patch", help="Unified diff patch string")
|
|
232
|
+
lint_p.add_argument("--workspace", type=Path, default=Path.cwd(), help="Target workspace path")
|
|
233
|
+
lint_p.add_argument("--json", action="store_true", help="Output linter report in JSON format")
|
|
234
|
+
|
|
235
|
+
# 16. ui (app)
|
|
236
|
+
ui_p = subparsers.add_parser("ui", aliases=["app"], help="[Experimental Preview] Start the standalone Web Workbench & Coding Arena")
|
|
237
|
+
ui_p.add_argument("--host", default="127.0.0.1")
|
|
238
|
+
ui_p.add_argument("--port", type=int, default=8766, help="Port to bind (default: 8766, distinct from monitor)")
|
|
239
|
+
ui_p.add_argument("--experimental", action="store_true", help="Acknowledge running the experimental web workbench preview")
|
|
240
|
+
|
|
241
|
+
# 17. desktop
|
|
242
|
+
desk_p = subparsers.add_parser("desktop", help="Start the Standalone Desktop AI Coding Harness (R23)")
|
|
243
|
+
desk_p.add_argument("--host", default="127.0.0.1", help="Host address to bind")
|
|
244
|
+
desk_p.add_argument("--port", type=int, default=8767, help="Port to bind; use 0 for an ephemeral port")
|
|
245
|
+
desk_p.add_argument("--browser", action="store_true", help="Force open in system browser instead of native window")
|
|
246
|
+
desk_p.add_argument("--headless", action="store_true", help="Run as a sidecar and print one JSON readiness record")
|
|
247
|
+
desk_p.add_argument("--workspace", default=".", help="Target workspace path")
|
|
248
|
+
desk_p.add_argument("--profile", default="qwen2.5-coder", help="Default model profile")
|
|
249
|
+
|
|
250
|
+
# 18. spill-read (R24)
|
|
251
|
+
spill_p = subparsers.add_parser("spill-read", help="Read or paginate a spilled tool output artifact (R24)")
|
|
252
|
+
spill_p.add_argument("locator", nargs="?", default=None, help="Spill locator token (e.g. locator:spill:... or path)")
|
|
253
|
+
spill_p.add_argument("--locator", dest="opt_locator", help="Explicit spill locator")
|
|
254
|
+
spill_p.add_argument("--offset", type=int, default=0, help="0-based line offset")
|
|
255
|
+
spill_p.add_argument("--limit", type=int, default=1000, help="Maximum number of lines to read")
|
|
256
|
+
spill_p.add_argument("--json", action="store_true", help="Output result in JSON format")
|
|
257
|
+
|
|
258
|
+
# 19. grep (R24)
|
|
259
|
+
grep_p = subparsers.add_parser("grep", help="Fast ripgrep / regex code search across workspace (R24)")
|
|
260
|
+
grep_p.add_argument("query", help="Search query string or regex pattern")
|
|
261
|
+
grep_p.add_argument("paths", nargs="*", default=[], help="Glob filters or file paths (e.g. *.py)")
|
|
262
|
+
grep_p.add_argument("--regex", action="store_true", help="Treat query as regular expression")
|
|
263
|
+
grep_p.add_argument("--case-sensitive", action="store_true", help="Perform case-sensitive matching")
|
|
264
|
+
grep_p.add_argument("--max-results", type=int, default=100, help="Maximum number of matching lines")
|
|
265
|
+
grep_p.add_argument("--workspace", type=Path, default=Path.cwd(), help="Workspace root directory")
|
|
266
|
+
grep_p.add_argument("--json", action="store_true", help="Output matches in JSON format")
|
|
267
|
+
|
|
268
|
+
# 20. lsp (R25)
|
|
269
|
+
lsp_p = subparsers.add_parser("lsp", help="Run LSP code intelligence query (R25)")
|
|
270
|
+
lsp_p.add_argument("--operation", choices=["definition", "references", "hover", "symbols"], required=True, help="LSP query operation")
|
|
271
|
+
lsp_p.add_argument("--file", type=Path, required=True, help="Target source file path")
|
|
272
|
+
lsp_p.add_argument("--line", type=int, default=0, help="0-based cursor line number")
|
|
273
|
+
lsp_p.add_argument("--char", type=int, default=0, help="0-based cursor column offset")
|
|
274
|
+
lsp_p.add_argument("--workspace", type=Path, default=Path.cwd(), help="Workspace root directory")
|
|
275
|
+
lsp_p.add_argument("--json", action="store_true", help="Output result in JSON format")
|
|
276
|
+
|
|
277
|
+
# 21. serve-acp (R29)
|
|
278
|
+
acp_p = subparsers.add_parser("serve-acp", help="Start Agent Client Protocol (ACP) JSON-RPC stdio server (R29)")
|
|
279
|
+
acp_p.add_argument("--workspace", type=Path, default=Path.cwd(), help="Default workspace directory")
|
|
280
|
+
acp_p.add_argument("--profile", default=DEFAULT_PROFILE, help="Default model profile")
|
|
281
|
+
acp_p.add_argument("--framing", choices=["auto", "jsonl", "content-length"], default="auto", help="Framing mode")
|
|
282
|
+
|
|
283
|
+
# 22. chat (R23 mode feature CLI parity)
|
|
284
|
+
chat_p = subparsers.add_parser("chat", help="Interactive chat: single-shot prompt or --repl multi-turn session")
|
|
285
|
+
chat_p.add_argument("prompt", nargs="?", default="", help="The user message (optional in --repl mode)")
|
|
286
|
+
chat_p.add_argument("--mode", choices=["chat", "build", "plan", "hybrid"], default="hybrid", help="Operational mode (default: hybrid auto-classifies)")
|
|
287
|
+
chat_p.add_argument("--profile", choices=list_profiles(), default=_default_profile(), help="Model profile to use (env: LCA_PROFILE)")
|
|
288
|
+
chat_p.add_argument("--model", help="Override the profile model tag for any installed model")
|
|
289
|
+
chat_p.add_argument("--num-ctx", type=int, help="Override model context window in tokens")
|
|
290
|
+
chat_p.add_argument("--workspace", type=Path, default=_default_workspace(), help="Workspace directory (env: LCA_WORKSPACE)")
|
|
291
|
+
chat_p.add_argument(
|
|
292
|
+
"--repl",
|
|
293
|
+
action="store_true",
|
|
294
|
+
help="Run an interactive multi-turn REPL with persistent session history",
|
|
295
|
+
)
|
|
296
|
+
chat_p.add_argument("--session-id", dest="session_id", help="Session id to create/resume (default: auto-generated)")
|
|
297
|
+
chat_p.add_argument("--list-sessions", dest="list_sessions", action="store_true", help="List persisted sessions and exit")
|
|
298
|
+
chat_p.add_argument("--json", action="store_true", help="Output result in JSON format")
|
|
299
|
+
|
|
300
|
+
# 23. scan-models
|
|
301
|
+
scan_p = subparsers.add_parser("scan-models", help="Discover and index local GGUF models across drives")
|
|
302
|
+
scan_p.add_argument("--deep", action="store_true", help="Perform deep filesystem scan across all system drives")
|
|
303
|
+
scan_p.add_argument("--drives", help="Comma-separated drive letters to target (e.g. C,D,Q)")
|
|
304
|
+
scan_p.add_argument("--add-dir", dest="add_dir", help="Add custom directory to persistent model registry")
|
|
305
|
+
scan_p.add_argument("--remove-dir", dest="remove_dir", help="Remove custom directory from registry")
|
|
306
|
+
scan_p.add_argument("--list-dirs", action="store_true", help="List all registered custom model directories")
|
|
307
|
+
scan_p.add_argument("--json", action="store_true", help="Output discovered models in JSON format")
|
|
308
|
+
|
|
309
|
+
# 24. sessions
|
|
310
|
+
sess_p = subparsers.add_parser("sessions", help="List and inspect persisted chat sessions")
|
|
311
|
+
sess_p.add_argument("session_action", nargs="?", choices=["list", "show"], default="list", help="Action: list or show")
|
|
312
|
+
sess_p.add_argument("session_id", nargs="?", help="Session id (for 'show')")
|
|
313
|
+
sess_p.add_argument("--limit", type=int, default=20, help="Maximum number of sessions to list")
|
|
314
|
+
sess_p.add_argument("--json", action="store_true", help="Output result in JSON format")
|
|
315
|
+
|
|
316
|
+
# 25. init-agent (cloud-agent wiring to local /v1)
|
|
317
|
+
ia_p = subparsers.add_parser(
|
|
318
|
+
"init-agent",
|
|
319
|
+
help="Wire a cloud coding agent (Codex, OpenAI-compatible tools) to the Desktop Harness /v1 endpoint",
|
|
320
|
+
)
|
|
321
|
+
ia_p.add_argument("--agent", choices=["codex", "generic"], default="codex", help="Agent config format to emit")
|
|
322
|
+
ia_p.add_argument("--desktop-url", dest="desktop_url", default="http://127.0.0.1:8767", help="Desktop Harness base URL serving the OpenAI-compatible /v1 API")
|
|
323
|
+
ia_p.add_argument("--model", help="Model id agents should request (default: Ollama tag of LCA_PROFILE/qwen2.5-coder)")
|
|
324
|
+
ia_p.add_argument("--write", action="store_true", help="Write the config into the agent's home directory (default: preview only)")
|
|
325
|
+
ia_p.add_argument("--json", action="store_true", help="Output result in JSON format")
|
|
326
|
+
|
|
327
|
+
return parser
|