claw-code 0.2.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.
- claw_code-0.2.0.dist-info/METADATA +560 -0
- claw_code-0.2.0.dist-info/RECORD +110 -0
- claw_code-0.2.0.dist-info/WHEEL +5 -0
- claw_code-0.2.0.dist-info/entry_points.txt +2 -0
- claw_code-0.2.0.dist-info/licenses/LICENSE +68 -0
- claw_code-0.2.0.dist-info/top_level.txt +1 -0
- src/QueryEngine.py +19 -0
- src/Tool.py +15 -0
- src/__init__.py +29 -0
- src/assistant/__init__.py +16 -0
- src/bootstrap/__init__.py +16 -0
- src/bootstrap_graph.py +27 -0
- src/bridge/__init__.py +16 -0
- src/buddy/__init__.py +16 -0
- src/cli/__init__.py +16 -0
- src/command_graph.py +34 -0
- src/commands.py +90 -0
- src/components/__init__.py +16 -0
- src/config.py +58 -0
- src/constants/__init__.py +16 -0
- src/context.py +47 -0
- src/coordinator/__init__.py +16 -0
- src/costHook.py +8 -0
- src/cost_tracker.py +13 -0
- src/deferred_init.py +31 -0
- src/dialogLaunchers.py +15 -0
- src/direct_modes.py +21 -0
- src/entrypoints/__init__.py +16 -0
- src/execution_registry.py +51 -0
- src/history.py +22 -0
- src/hooks/__init__.py +16 -0
- src/init_wizard.py +238 -0
- src/ink.py +6 -0
- src/interactiveHelpers.py +5 -0
- src/keybindings/__init__.py +16 -0
- src/main.py +274 -0
- src/memdir/__init__.py +16 -0
- src/migrations/__init__.py +16 -0
- src/model_detection.py +96 -0
- src/models.py +49 -0
- src/moreright/__init__.py +16 -0
- src/native_ts/__init__.py +16 -0
- src/outputStyles/__init__.py +16 -0
- src/parity_audit.py +138 -0
- src/permissions.py +20 -0
- src/plugins/__init__.py +16 -0
- src/port_manifest.py +52 -0
- src/prefetch.py +23 -0
- src/projectOnboardingState.py +10 -0
- src/query.py +13 -0
- src/query_engine.py +289 -0
- src/reference_data/__init__.py +1 -0
- src/reference_data/archive_surface_snapshot.json +63 -0
- src/reference_data/commands_snapshot.json +1037 -0
- src/reference_data/subsystems/assistant.json +8 -0
- src/reference_data/subsystems/bootstrap.json +8 -0
- src/reference_data/subsystems/bridge.json +32 -0
- src/reference_data/subsystems/buddy.json +13 -0
- src/reference_data/subsystems/cli.json +26 -0
- src/reference_data/subsystems/components.json +32 -0
- src/reference_data/subsystems/constants.json +28 -0
- src/reference_data/subsystems/coordinator.json +8 -0
- src/reference_data/subsystems/entrypoints.json +15 -0
- src/reference_data/subsystems/hooks.json +32 -0
- src/reference_data/subsystems/keybindings.json +21 -0
- src/reference_data/subsystems/memdir.json +15 -0
- src/reference_data/subsystems/migrations.json +18 -0
- src/reference_data/subsystems/moreright.json +8 -0
- src/reference_data/subsystems/native_ts.json +11 -0
- src/reference_data/subsystems/outputStyles.json +8 -0
- src/reference_data/subsystems/plugins.json +9 -0
- src/reference_data/subsystems/remote.json +11 -0
- src/reference_data/subsystems/schemas.json +8 -0
- src/reference_data/subsystems/screens.json +10 -0
- src/reference_data/subsystems/server.json +10 -0
- src/reference_data/subsystems/services.json +32 -0
- src/reference_data/subsystems/skills.json +27 -0
- src/reference_data/subsystems/state.json +13 -0
- src/reference_data/subsystems/types.json +18 -0
- src/reference_data/subsystems/upstreamproxy.json +9 -0
- src/reference_data/subsystems/utils.json +32 -0
- src/reference_data/subsystems/vim.json +12 -0
- src/reference_data/subsystems/voice.json +8 -0
- src/reference_data/tools_snapshot.json +922 -0
- src/remote/__init__.py +16 -0
- src/remote_runtime.py +25 -0
- src/repl.py +577 -0
- src/replLauncher.py +5 -0
- src/runtime.py +205 -0
- src/schemas/__init__.py +16 -0
- src/screens/__init__.py +16 -0
- src/server/__init__.py +16 -0
- src/services/__init__.py +16 -0
- src/services/ollama_adapter.py +251 -0
- src/services/ollama_setup.py +192 -0
- src/session_store.py +79 -0
- src/setup.py +77 -0
- src/skills/__init__.py +16 -0
- src/state/__init__.py +16 -0
- src/system_init.py +23 -0
- src/task.py +5 -0
- src/tasks.py +11 -0
- src/tool_pool.py +37 -0
- src/tools.py +96 -0
- src/transcript.py +23 -0
- src/types/__init__.py +16 -0
- src/upstreamproxy/__init__.py +16 -0
- src/utils/__init__.py +16 -0
- src/vim/__init__.py +16 -0
- src/voice/__init__.py +16 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .commands import PORTED_COMMANDS, execute_command
|
|
6
|
+
from .tools import PORTED_TOOLS, execute_tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class MirroredCommand:
|
|
11
|
+
name: str
|
|
12
|
+
source_hint: str
|
|
13
|
+
|
|
14
|
+
def execute(self, prompt: str) -> str:
|
|
15
|
+
return execute_command(self.name, prompt).message
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class MirroredTool:
|
|
20
|
+
name: str
|
|
21
|
+
source_hint: str
|
|
22
|
+
|
|
23
|
+
def execute(self, payload: str) -> str:
|
|
24
|
+
return execute_tool(self.name, payload).message
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class ExecutionRegistry:
|
|
29
|
+
commands: tuple[MirroredCommand, ...]
|
|
30
|
+
tools: tuple[MirroredTool, ...]
|
|
31
|
+
|
|
32
|
+
def command(self, name: str) -> MirroredCommand | None:
|
|
33
|
+
lowered = name.lower()
|
|
34
|
+
for command in self.commands:
|
|
35
|
+
if command.name.lower() == lowered:
|
|
36
|
+
return command
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def tool(self, name: str) -> MirroredTool | None:
|
|
40
|
+
lowered = name.lower()
|
|
41
|
+
for tool in self.tools:
|
|
42
|
+
if tool.name.lower() == lowered:
|
|
43
|
+
return tool
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_execution_registry() -> ExecutionRegistry:
|
|
48
|
+
return ExecutionRegistry(
|
|
49
|
+
commands=tuple(MirroredCommand(module.name, module.source_hint) for module in PORTED_COMMANDS),
|
|
50
|
+
tools=tuple(MirroredTool(module.name, module.source_hint) for module in PORTED_TOOLS),
|
|
51
|
+
)
|
src/history.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class HistoryEvent:
|
|
8
|
+
title: str
|
|
9
|
+
detail: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class HistoryLog:
|
|
14
|
+
events: list[HistoryEvent] = field(default_factory=list)
|
|
15
|
+
|
|
16
|
+
def add(self, title: str, detail: str) -> None:
|
|
17
|
+
self.events.append(HistoryEvent(title=title, detail=detail))
|
|
18
|
+
|
|
19
|
+
def as_markdown(self) -> str:
|
|
20
|
+
lines = ['# Session History', '']
|
|
21
|
+
lines.extend(f'- {event.title}: {event.detail}' for event in self.events)
|
|
22
|
+
return '\n'.join(lines)
|
src/hooks/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Python package placeholder for the archived `hooks` subsystem."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SNAPSHOT_PATH = Path(__file__).resolve().parent.parent / 'reference_data' / 'subsystems' / 'hooks.json'
|
|
9
|
+
_SNAPSHOT = json.loads(SNAPSHOT_PATH.read_text())
|
|
10
|
+
|
|
11
|
+
ARCHIVE_NAME = _SNAPSHOT['archive_name']
|
|
12
|
+
MODULE_COUNT = _SNAPSHOT['module_count']
|
|
13
|
+
SAMPLE_FILES = tuple(_SNAPSHOT['sample_files'])
|
|
14
|
+
PORTING_NOTE = f"Python placeholder package for '{ARCHIVE_NAME}' with {MODULE_COUNT} archived module references."
|
|
15
|
+
|
|
16
|
+
__all__ = ['ARCHIVE_NAME', 'MODULE_COUNT', 'PORTING_NOTE', 'SAMPLE_FILES']
|
src/init_wizard.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
First-run wizard for Claw Code initialization.
|
|
3
|
+
Detects Ollama, RAM, pulls model, writes config.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .config import load_config
|
|
13
|
+
from .services.ollama_adapter import OllamaAdapter
|
|
14
|
+
from .services.ollama_setup import (
|
|
15
|
+
check_ollama_installed,
|
|
16
|
+
ensure_config_exists,
|
|
17
|
+
pull_model,
|
|
18
|
+
validate_setup,
|
|
19
|
+
print_validation_report,
|
|
20
|
+
)
|
|
21
|
+
from .model_detection import get_available_models, detect_best_model
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def print_banner():
|
|
28
|
+
"""Print welcome banner"""
|
|
29
|
+
print("\n" + "=" * 70)
|
|
30
|
+
print(" 🚀 Welcome to Claw Code - Local Claude Alternative")
|
|
31
|
+
print("=" * 70)
|
|
32
|
+
print("\nThis wizard will help you get started in under 3 minutes.\n")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def step_1_check_ollama():
|
|
36
|
+
"""Step 1: Verify Ollama installation"""
|
|
37
|
+
print("📋 Step 1: Checking Ollama installation...")
|
|
38
|
+
|
|
39
|
+
if check_ollama_installed():
|
|
40
|
+
print("✓ Ollama CLI found\n")
|
|
41
|
+
return True
|
|
42
|
+
else:
|
|
43
|
+
print("✗ Ollama not installed")
|
|
44
|
+
print("\n→ Download from: https://ollama.ai")
|
|
45
|
+
print("→ Then run: claw-code init\n")
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def step_2_detect_system():
|
|
50
|
+
"""Step 2: Detect system VRAM"""
|
|
51
|
+
print("📋 Step 2: Detecting system resources...")
|
|
52
|
+
|
|
53
|
+
vram_gb = OllamaAdapter.get_available_vram_gb()
|
|
54
|
+
print(f"✓ Detected: {vram_gb:.1f}GB VRAM\n")
|
|
55
|
+
|
|
56
|
+
return vram_gb
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def step_3_recommend_model(vram_gb: float):
|
|
60
|
+
"""Step 3: Recommend model tier"""
|
|
61
|
+
print("📋 Step 3: Recommending model for your system...")
|
|
62
|
+
|
|
63
|
+
from .services.ollama_adapter import MODEL_TIERS
|
|
64
|
+
|
|
65
|
+
for tier in MODEL_TIERS:
|
|
66
|
+
if tier.vram_min_gb <= vram_gb <= tier.vram_max_gb:
|
|
67
|
+
print(f"✓ Recommended: {tier.name}")
|
|
68
|
+
print(f" VRAM: {tier.vram_required_gb}GB | Speed: {tier.tokens_per_sec_estimate} tok/s")
|
|
69
|
+
print(f" Use: {tier.use_case}\n")
|
|
70
|
+
return tier.name
|
|
71
|
+
|
|
72
|
+
# Fallback
|
|
73
|
+
print("✓ Using conservative tier: phi4-mini\n")
|
|
74
|
+
return "phi4-mini"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def step_4_check_models():
|
|
78
|
+
"""Step 4: Check if model is already available"""
|
|
79
|
+
print("📋 Step 4: Checking for available models...")
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
available = get_available_models()
|
|
83
|
+
if available:
|
|
84
|
+
print(f"✓ Found {len(available)} model(s) installed:")
|
|
85
|
+
for model in available:
|
|
86
|
+
print(f" - {model}")
|
|
87
|
+
print()
|
|
88
|
+
return available
|
|
89
|
+
else:
|
|
90
|
+
print("⚠ No models installed yet\n")
|
|
91
|
+
return None
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.warning(f"Could not list models: {e}")
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def step_5_pull_model(recommended_model: str, available_models: list[str] | None):
|
|
98
|
+
"""Step 5: Pull model if needed"""
|
|
99
|
+
print("📋 Step 5: Model setup...")
|
|
100
|
+
|
|
101
|
+
# Check if recommended is already available
|
|
102
|
+
if available_models and recommended_model in available_models:
|
|
103
|
+
print(f"✓ {recommended_model} already installed\n")
|
|
104
|
+
return recommended_model
|
|
105
|
+
|
|
106
|
+
print(f"→ Need to download {recommended_model} (~6GB)")
|
|
107
|
+
print("→ This might take a few minutes...\n")
|
|
108
|
+
|
|
109
|
+
response = input("Pull now? (y/n): ").strip().lower()
|
|
110
|
+
|
|
111
|
+
if response == "y":
|
|
112
|
+
print()
|
|
113
|
+
if pull_model(recommended_model):
|
|
114
|
+
print(f"\n✓ {recommended_model} ready\n")
|
|
115
|
+
return recommended_model
|
|
116
|
+
else:
|
|
117
|
+
print(f"\n✗ Failed to pull {recommended_model}")
|
|
118
|
+
print("→ Try manually: ollama pull " + recommended_model)
|
|
119
|
+
return None
|
|
120
|
+
else:
|
|
121
|
+
print(f"\nℹ To pull manually: ollama pull {recommended_model}\n")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def step_6_start_ollama():
|
|
126
|
+
"""Step 6: Instruct to start Ollama if not running"""
|
|
127
|
+
print("📋 Step 6: Checking if Ollama is running...")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
OllamaAdapter()
|
|
131
|
+
print("✓ Ollama server is running\n")
|
|
132
|
+
return True
|
|
133
|
+
except RuntimeError:
|
|
134
|
+
print("⚠ Ollama server not running")
|
|
135
|
+
print("\n→ In another terminal, run: ollama serve\n")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def step_7_create_config():
|
|
140
|
+
"""Step 7: Create configuration file"""
|
|
141
|
+
print("📋 Step 7: Creating configuration...")
|
|
142
|
+
|
|
143
|
+
config_path = ensure_config_exists()
|
|
144
|
+
print(f"✓ Config created: {config_path}\n")
|
|
145
|
+
|
|
146
|
+
return config_path
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def step_8_validate():
|
|
150
|
+
"""Step 8: Validate complete setup"""
|
|
151
|
+
print("📋 Step 8: Validating setup...")
|
|
152
|
+
|
|
153
|
+
report = validate_setup()
|
|
154
|
+
|
|
155
|
+
if report["ollama_running"] and report["model_available"]:
|
|
156
|
+
print(f"✓ Everything ready! Model: {report['model_available']}\n")
|
|
157
|
+
return True
|
|
158
|
+
else:
|
|
159
|
+
if not report["ollama_running"]:
|
|
160
|
+
print("⚠ Ollama server not detected (start with: ollama serve)")
|
|
161
|
+
if not report["model_available"]:
|
|
162
|
+
print("⚠ No model detected (pull with: ollama pull qwen2.5-coder:7b)")
|
|
163
|
+
print()
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def print_next_steps():
|
|
168
|
+
"""Print what to do next"""
|
|
169
|
+
print("=" * 70)
|
|
170
|
+
print(" 🎉 Setup Complete!")
|
|
171
|
+
print("=" * 70 + "\n")
|
|
172
|
+
|
|
173
|
+
print("Next steps:\n")
|
|
174
|
+
print("1. Make sure Ollama is running (in another terminal):")
|
|
175
|
+
print(" ollama serve\n")
|
|
176
|
+
|
|
177
|
+
print("2. Start Claw Code interactive REPL:")
|
|
178
|
+
print(" claw-code\n")
|
|
179
|
+
|
|
180
|
+
print("3. Try some commands:")
|
|
181
|
+
print(" > write a Python quicksort")
|
|
182
|
+
print(" > explain this function")
|
|
183
|
+
print(" > help\n")
|
|
184
|
+
|
|
185
|
+
print("Commands:")
|
|
186
|
+
print(" /clear — Clear conversation history")
|
|
187
|
+
print(" /exit — Exit the REPL")
|
|
188
|
+
print(" /session — Show session info")
|
|
189
|
+
print(" /help — Show help\n")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def run_init_wizard() -> bool:
|
|
193
|
+
"""Run the complete initialization wizard"""
|
|
194
|
+
print_banner()
|
|
195
|
+
|
|
196
|
+
# Step 1: Check Ollama
|
|
197
|
+
if not step_1_check_ollama():
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
# Step 2: Detect system
|
|
201
|
+
vram_gb = step_2_detect_system()
|
|
202
|
+
|
|
203
|
+
# Step 3: Recommend model
|
|
204
|
+
recommended = step_3_recommend_model(vram_gb)
|
|
205
|
+
|
|
206
|
+
# Step 4: Check available models
|
|
207
|
+
available = step_4_check_models()
|
|
208
|
+
|
|
209
|
+
# Step 5: Pull model if needed
|
|
210
|
+
model = step_5_pull_model(recommended, available)
|
|
211
|
+
|
|
212
|
+
# Step 6: Check if Ollama is running
|
|
213
|
+
ollama_running = step_6_start_ollama()
|
|
214
|
+
|
|
215
|
+
# Step 7: Create config
|
|
216
|
+
step_7_create_config()
|
|
217
|
+
|
|
218
|
+
# Step 8: Validate
|
|
219
|
+
ready = step_8_validate()
|
|
220
|
+
|
|
221
|
+
# Next steps
|
|
222
|
+
print_next_steps()
|
|
223
|
+
|
|
224
|
+
if not ollama_running:
|
|
225
|
+
print("⚠ Please start Ollama (ollama serve) in another terminal,")
|
|
226
|
+
print(" then run: claw-code\n")
|
|
227
|
+
elif not model:
|
|
228
|
+
print("⚠ Please pull a model manually, then run: claw-code\n")
|
|
229
|
+
else:
|
|
230
|
+
print("✓ Ready to go! Run: claw-code\n")
|
|
231
|
+
|
|
232
|
+
return ready
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
logging.basicConfig(level=logging.INFO)
|
|
237
|
+
success = run_init_wizard()
|
|
238
|
+
sys.exit(0 if success else 1)
|
src/ink.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Python package placeholder for the archived `keybindings` subsystem."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SNAPSHOT_PATH = Path(__file__).resolve().parent.parent / 'reference_data' / 'subsystems' / 'keybindings.json'
|
|
9
|
+
_SNAPSHOT = json.loads(SNAPSHOT_PATH.read_text())
|
|
10
|
+
|
|
11
|
+
ARCHIVE_NAME = _SNAPSHOT['archive_name']
|
|
12
|
+
MODULE_COUNT = _SNAPSHOT['module_count']
|
|
13
|
+
SAMPLE_FILES = tuple(_SNAPSHOT['sample_files'])
|
|
14
|
+
PORTING_NOTE = f"Python placeholder package for '{ARCHIVE_NAME}' with {MODULE_COUNT} archived module references."
|
|
15
|
+
|
|
16
|
+
__all__ = ['ARCHIVE_NAME', 'MODULE_COUNT', 'PORTING_NOTE', 'SAMPLE_FILES']
|
src/main.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .bootstrap_graph import build_bootstrap_graph
|
|
6
|
+
from .command_graph import build_command_graph
|
|
7
|
+
from .commands import execute_command, get_command, get_commands, render_command_index
|
|
8
|
+
from .direct_modes import run_deep_link, run_direct_connect
|
|
9
|
+
from .init_wizard import run_init_wizard
|
|
10
|
+
from .parity_audit import run_parity_audit
|
|
11
|
+
from .permissions import ToolPermissionContext
|
|
12
|
+
from .port_manifest import build_port_manifest
|
|
13
|
+
from .query_engine import QueryEnginePort
|
|
14
|
+
from .remote_runtime import run_remote_mode, run_ssh_mode, run_teleport_mode
|
|
15
|
+
from .repl import run_repl
|
|
16
|
+
from .runtime import PortRuntime
|
|
17
|
+
from .session_store import load_session
|
|
18
|
+
from .setup import run_setup
|
|
19
|
+
from .tool_pool import assemble_tool_pool
|
|
20
|
+
from .tools import execute_tool, get_tool, get_tools, render_tool_index
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
24
|
+
parser = argparse.ArgumentParser(description='Claw Code - Local Claude AI CLI powered by Ollama')
|
|
25
|
+
|
|
26
|
+
# Top-level flags
|
|
27
|
+
parser.add_argument('--version', action='store_true', help='show version and exit')
|
|
28
|
+
parser.add_argument('--resume', type=str, help='resume a previous session by ID')
|
|
29
|
+
|
|
30
|
+
subparsers = parser.add_subparsers(dest='command', required=False)
|
|
31
|
+
|
|
32
|
+
# Initialization wizard
|
|
33
|
+
subparsers.add_parser('init', help='first-run setup wizard (checks Ollama, pulls model)')
|
|
34
|
+
|
|
35
|
+
# Interactive REPL (implicit if no command)
|
|
36
|
+
|
|
37
|
+
# Existing commands
|
|
38
|
+
subparsers.add_parser('summary', help='render a Markdown summary of the Python porting workspace')
|
|
39
|
+
subparsers.add_parser('manifest', help='print the current Python workspace manifest')
|
|
40
|
+
subparsers.add_parser('parity-audit', help='compare the Python workspace against the local ignored TypeScript archive when available')
|
|
41
|
+
subparsers.add_parser('setup-report', help='render the startup/prefetch setup report')
|
|
42
|
+
subparsers.add_parser('command-graph', help='show command graph segmentation')
|
|
43
|
+
subparsers.add_parser('tool-pool', help='show assembled tool pool with default settings')
|
|
44
|
+
subparsers.add_parser('bootstrap-graph', help='show the mirrored bootstrap/runtime graph stages')
|
|
45
|
+
list_parser = subparsers.add_parser('subsystems', help='list the current Python modules in the workspace')
|
|
46
|
+
list_parser.add_argument('--limit', type=int, default=32)
|
|
47
|
+
|
|
48
|
+
commands_parser = subparsers.add_parser('commands', help='list mirrored command entries from the archived snapshot')
|
|
49
|
+
commands_parser.add_argument('--limit', type=int, default=20)
|
|
50
|
+
commands_parser.add_argument('--query')
|
|
51
|
+
commands_parser.add_argument('--no-plugin-commands', action='store_true')
|
|
52
|
+
commands_parser.add_argument('--no-skill-commands', action='store_true')
|
|
53
|
+
|
|
54
|
+
tools_parser = subparsers.add_parser('tools', help='list mirrored tool entries from the archived snapshot')
|
|
55
|
+
tools_parser.add_argument('--limit', type=int, default=20)
|
|
56
|
+
tools_parser.add_argument('--query')
|
|
57
|
+
tools_parser.add_argument('--simple-mode', action='store_true')
|
|
58
|
+
tools_parser.add_argument('--no-mcp', action='store_true')
|
|
59
|
+
tools_parser.add_argument('--deny-tool', action='append', default=[])
|
|
60
|
+
tools_parser.add_argument('--deny-prefix', action='append', default=[])
|
|
61
|
+
|
|
62
|
+
route_parser = subparsers.add_parser('route', help='route a prompt across mirrored command/tool inventories')
|
|
63
|
+
route_parser.add_argument('prompt')
|
|
64
|
+
route_parser.add_argument('--limit', type=int, default=5)
|
|
65
|
+
|
|
66
|
+
bootstrap_parser = subparsers.add_parser('bootstrap', help='build a runtime-style session report from the mirrored inventories')
|
|
67
|
+
bootstrap_parser.add_argument('prompt')
|
|
68
|
+
bootstrap_parser.add_argument('--limit', type=int, default=5)
|
|
69
|
+
|
|
70
|
+
loop_parser = subparsers.add_parser('turn-loop', help='run a small stateful turn loop for the mirrored runtime')
|
|
71
|
+
loop_parser.add_argument('prompt')
|
|
72
|
+
loop_parser.add_argument('--limit', type=int, default=5)
|
|
73
|
+
loop_parser.add_argument('--max-turns', type=int, default=3)
|
|
74
|
+
loop_parser.add_argument('--structured-output', action='store_true')
|
|
75
|
+
loop_parser.add_argument('--stream', action='store_true', help='stream output in real-time from Ollama')
|
|
76
|
+
|
|
77
|
+
flush_parser = subparsers.add_parser('flush-transcript', help='persist and flush a temporary session transcript')
|
|
78
|
+
flush_parser.add_argument('prompt')
|
|
79
|
+
|
|
80
|
+
load_session_parser = subparsers.add_parser('load-session', help='load a previously persisted session')
|
|
81
|
+
load_session_parser.add_argument('session_id')
|
|
82
|
+
|
|
83
|
+
remote_parser = subparsers.add_parser('remote-mode', help='simulate remote-control runtime branching')
|
|
84
|
+
remote_parser.add_argument('target')
|
|
85
|
+
ssh_parser = subparsers.add_parser('ssh-mode', help='simulate SSH runtime branching')
|
|
86
|
+
ssh_parser.add_argument('target')
|
|
87
|
+
teleport_parser = subparsers.add_parser('teleport-mode', help='simulate teleport runtime branching')
|
|
88
|
+
teleport_parser.add_argument('target')
|
|
89
|
+
direct_parser = subparsers.add_parser('direct-connect-mode', help='simulate direct-connect runtime branching')
|
|
90
|
+
direct_parser.add_argument('target')
|
|
91
|
+
deep_link_parser = subparsers.add_parser('deep-link-mode', help='simulate deep-link runtime branching')
|
|
92
|
+
deep_link_parser.add_argument('target')
|
|
93
|
+
|
|
94
|
+
show_command = subparsers.add_parser('show-command', help='show one mirrored command entry by exact name')
|
|
95
|
+
show_command.add_argument('name')
|
|
96
|
+
show_tool = subparsers.add_parser('show-tool', help='show one mirrored tool entry by exact name')
|
|
97
|
+
show_tool.add_argument('name')
|
|
98
|
+
|
|
99
|
+
exec_command_parser = subparsers.add_parser('exec-command', help='execute a mirrored command shim by exact name')
|
|
100
|
+
exec_command_parser.add_argument('name')
|
|
101
|
+
exec_command_parser.add_argument('prompt')
|
|
102
|
+
|
|
103
|
+
exec_tool_parser = subparsers.add_parser('exec-tool', help='execute a mirrored tool shim by exact name')
|
|
104
|
+
exec_tool_parser.add_argument('name')
|
|
105
|
+
exec_tool_parser.add_argument('payload')
|
|
106
|
+
return parser
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def main(argv: list[str] | None = None) -> int:
|
|
110
|
+
parser = build_parser()
|
|
111
|
+
args = parser.parse_args(argv)
|
|
112
|
+
|
|
113
|
+
# Handle --version
|
|
114
|
+
if args.version:
|
|
115
|
+
print("claw-code v0.1.0 (Phase 1)")
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
# Handle init command
|
|
119
|
+
if args.command == 'init':
|
|
120
|
+
success = run_init_wizard()
|
|
121
|
+
return 0 if success else 1
|
|
122
|
+
|
|
123
|
+
# Handle --resume flag
|
|
124
|
+
if args.resume:
|
|
125
|
+
print(f"Resuming session {args.resume}...")
|
|
126
|
+
try:
|
|
127
|
+
engine = QueryEnginePort.from_saved_session(args.resume)
|
|
128
|
+
print(f"Loaded {len(engine.mutable_messages)} messages from session")
|
|
129
|
+
# TODO: Drop into REPL with loaded session
|
|
130
|
+
print("⚠ Full session resumption coming in Phase 2.5")
|
|
131
|
+
return 0
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f"✗ Failed to load session: {e}")
|
|
134
|
+
return 1
|
|
135
|
+
|
|
136
|
+
# Default to REPL if no command
|
|
137
|
+
if not args.command:
|
|
138
|
+
run_repl()
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
# Original command handling below
|
|
142
|
+
manifest = build_port_manifest()
|
|
143
|
+
if args.command == 'summary':
|
|
144
|
+
print(QueryEnginePort(manifest).render_summary())
|
|
145
|
+
return 0
|
|
146
|
+
if args.command == 'manifest':
|
|
147
|
+
print(manifest.to_markdown())
|
|
148
|
+
return 0
|
|
149
|
+
if args.command == 'parity-audit':
|
|
150
|
+
print(run_parity_audit().to_markdown())
|
|
151
|
+
return 0
|
|
152
|
+
if args.command == 'setup-report':
|
|
153
|
+
print(run_setup().as_markdown())
|
|
154
|
+
return 0
|
|
155
|
+
if args.command == 'command-graph':
|
|
156
|
+
print(build_command_graph().as_markdown())
|
|
157
|
+
return 0
|
|
158
|
+
if args.command == 'tool-pool':
|
|
159
|
+
print(assemble_tool_pool().as_markdown())
|
|
160
|
+
return 0
|
|
161
|
+
if args.command == 'bootstrap-graph':
|
|
162
|
+
print(build_bootstrap_graph().as_markdown())
|
|
163
|
+
return 0
|
|
164
|
+
if args.command == 'subsystems':
|
|
165
|
+
for subsystem in manifest.top_level_modules[: args.limit]:
|
|
166
|
+
print(f'{subsystem.name}\t{subsystem.file_count}\t{subsystem.notes}')
|
|
167
|
+
return 0
|
|
168
|
+
if args.command == 'commands':
|
|
169
|
+
if args.query:
|
|
170
|
+
print(render_command_index(limit=args.limit, query=args.query))
|
|
171
|
+
else:
|
|
172
|
+
commands = get_commands(include_plugin_commands=not args.no_plugin_commands, include_skill_commands=not args.no_skill_commands)
|
|
173
|
+
output_lines = [f'Command entries: {len(commands)}', '']
|
|
174
|
+
output_lines.extend(f'- {module.name} — {module.source_hint}' for module in commands[: args.limit])
|
|
175
|
+
print('\n'.join(output_lines))
|
|
176
|
+
return 0
|
|
177
|
+
if args.command == 'tools':
|
|
178
|
+
if args.query:
|
|
179
|
+
print(render_tool_index(limit=args.limit, query=args.query))
|
|
180
|
+
else:
|
|
181
|
+
permission_context = ToolPermissionContext.from_iterables(args.deny_tool, args.deny_prefix)
|
|
182
|
+
tools = get_tools(simple_mode=args.simple_mode, include_mcp=not args.no_mcp, permission_context=permission_context)
|
|
183
|
+
output_lines = [f'Tool entries: {len(tools)}', '']
|
|
184
|
+
output_lines.extend(f'- {module.name} — {module.source_hint}' for module in tools[: args.limit])
|
|
185
|
+
print('\n'.join(output_lines))
|
|
186
|
+
return 0
|
|
187
|
+
if args.command == 'route':
|
|
188
|
+
matches = PortRuntime().route_prompt(args.prompt, limit=args.limit)
|
|
189
|
+
if not matches:
|
|
190
|
+
print('No mirrored command/tool matches found.')
|
|
191
|
+
return 0
|
|
192
|
+
for match in matches:
|
|
193
|
+
print(f'{match.kind}\t{match.name}\t{match.score}\t{match.source_hint}')
|
|
194
|
+
return 0
|
|
195
|
+
if args.command == 'bootstrap':
|
|
196
|
+
print(PortRuntime().bootstrap_session(args.prompt, limit=args.limit).as_markdown())
|
|
197
|
+
return 0
|
|
198
|
+
if args.command == 'turn-loop':
|
|
199
|
+
if hasattr(args, 'stream') and args.stream:
|
|
200
|
+
# Stream mode: real-time output
|
|
201
|
+
runtime = PortRuntime()
|
|
202
|
+
for event in runtime.stream_turn_loop(args.prompt, limit=args.limit, max_turns=args.max_turns, structured_output=args.structured_output):
|
|
203
|
+
if event['type'] == 'message_delta':
|
|
204
|
+
print(event['text'], end='', flush=True)
|
|
205
|
+
elif event['type'] == 'message_stop':
|
|
206
|
+
print(f"\n\n[stop_reason={event['stop_reason']}]")
|
|
207
|
+
else:
|
|
208
|
+
# Buffered mode: collect and display
|
|
209
|
+
results = PortRuntime().run_turn_loop(args.prompt, limit=args.limit, max_turns=args.max_turns, structured_output=args.structured_output)
|
|
210
|
+
for idx, result in enumerate(results, start=1):
|
|
211
|
+
print(f'## Turn {idx}')
|
|
212
|
+
print(result.output)
|
|
213
|
+
print(f'stop_reason={result.stop_reason}')
|
|
214
|
+
return 0
|
|
215
|
+
if args.command == 'flush-transcript':
|
|
216
|
+
engine = QueryEnginePort.from_workspace()
|
|
217
|
+
engine.submit_message(args.prompt)
|
|
218
|
+
path = engine.persist_session()
|
|
219
|
+
print(path)
|
|
220
|
+
print(f'flushed={engine.transcript_store.flushed}')
|
|
221
|
+
return 0
|
|
222
|
+
if args.command == 'load-session':
|
|
223
|
+
session = load_session(args.session_id)
|
|
224
|
+
print(f'{session.session_id}\n{len(session.messages)} messages\nin={session.input_tokens} out={session.output_tokens}')
|
|
225
|
+
return 0
|
|
226
|
+
if args.command == 'remote-mode':
|
|
227
|
+
print(run_remote_mode(args.target).as_text())
|
|
228
|
+
return 0
|
|
229
|
+
if args.command == 'ssh-mode':
|
|
230
|
+
print(run_ssh_mode(args.target).as_text())
|
|
231
|
+
return 0
|
|
232
|
+
if args.command == 'teleport-mode':
|
|
233
|
+
print(run_teleport_mode(args.target).as_text())
|
|
234
|
+
return 0
|
|
235
|
+
if args.command == 'direct-connect-mode':
|
|
236
|
+
print(run_direct_connect(args.target).as_text())
|
|
237
|
+
return 0
|
|
238
|
+
if args.command == 'deep-link-mode':
|
|
239
|
+
print(run_deep_link(args.target).as_text())
|
|
240
|
+
return 0
|
|
241
|
+
if args.command == 'show-command':
|
|
242
|
+
module = get_command(args.name)
|
|
243
|
+
if module is None:
|
|
244
|
+
print(f'Command not found: {args.name}')
|
|
245
|
+
return 1
|
|
246
|
+
print('\n'.join([module.name, module.source_hint, module.responsibility]))
|
|
247
|
+
return 0
|
|
248
|
+
if args.command == 'show-tool':
|
|
249
|
+
module = get_tool(args.name)
|
|
250
|
+
if module is None:
|
|
251
|
+
print(f'Tool not found: {args.name}')
|
|
252
|
+
return 1
|
|
253
|
+
print('\n'.join([module.name, module.source_hint, module.responsibility]))
|
|
254
|
+
return 0
|
|
255
|
+
if args.command == 'exec-command':
|
|
256
|
+
result = execute_command(args.name, args.prompt)
|
|
257
|
+
print(result.message)
|
|
258
|
+
return 0 if result.handled else 1
|
|
259
|
+
if args.command == 'exec-tool':
|
|
260
|
+
result = execute_tool(args.name, args.payload)
|
|
261
|
+
print(result.message)
|
|
262
|
+
return 0 if result.handled else 1
|
|
263
|
+
parser.error(f'unknown command: {args.command}')
|
|
264
|
+
return 2
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def cli_entry() -> None:
|
|
268
|
+
"""Entry point for pip-installed 'claw-code' command"""
|
|
269
|
+
import sys
|
|
270
|
+
sys.exit(main())
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == '__main__':
|
|
274
|
+
raise SystemExit(main())
|
src/memdir/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Python package placeholder for the archived `memdir` subsystem."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SNAPSHOT_PATH = Path(__file__).resolve().parent.parent / 'reference_data' / 'subsystems' / 'memdir.json'
|
|
9
|
+
_SNAPSHOT = json.loads(SNAPSHOT_PATH.read_text())
|
|
10
|
+
|
|
11
|
+
ARCHIVE_NAME = _SNAPSHOT['archive_name']
|
|
12
|
+
MODULE_COUNT = _SNAPSHOT['module_count']
|
|
13
|
+
SAMPLE_FILES = tuple(_SNAPSHOT['sample_files'])
|
|
14
|
+
PORTING_NOTE = f"Python placeholder package for '{ARCHIVE_NAME}' with {MODULE_COUNT} archived module references."
|
|
15
|
+
|
|
16
|
+
__all__ = ['ARCHIVE_NAME', 'MODULE_COUNT', 'PORTING_NOTE', 'SAMPLE_FILES']
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Python package placeholder for the archived `migrations` subsystem."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SNAPSHOT_PATH = Path(__file__).resolve().parent.parent / 'reference_data' / 'subsystems' / 'migrations.json'
|
|
9
|
+
_SNAPSHOT = json.loads(SNAPSHOT_PATH.read_text())
|
|
10
|
+
|
|
11
|
+
ARCHIVE_NAME = _SNAPSHOT['archive_name']
|
|
12
|
+
MODULE_COUNT = _SNAPSHOT['module_count']
|
|
13
|
+
SAMPLE_FILES = tuple(_SNAPSHOT['sample_files'])
|
|
14
|
+
PORTING_NOTE = f"Python placeholder package for '{ARCHIVE_NAME}' with {MODULE_COUNT} archived module references."
|
|
15
|
+
|
|
16
|
+
__all__ = ['ARCHIVE_NAME', 'MODULE_COUNT', 'PORTING_NOTE', 'SAMPLE_FILES']
|