crabcode 0.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.
- crabcode-0.1.0.dist-info/METADATA +23 -0
- crabcode-0.1.0.dist-info/RECORD +64 -0
- crabcode-0.1.0.dist-info/WHEEL +4 -0
- crabcode-0.1.0.dist-info/entry_points.txt +2 -0
- crabcode_cli/__init__.py +3 -0
- crabcode_cli/app.py +142 -0
- crabcode_cli/banner.py +42 -0
- crabcode_cli/pipe.py +58 -0
- crabcode_cli/render/__init__.py +0 -0
- crabcode_cli/render/messages.py +19 -0
- crabcode_cli/repl.py +1263 -0
- crabcode_core/__init__.py +3 -0
- crabcode_core/api/__init__.py +6 -0
- crabcode_core/api/anthropic_adapter.py +250 -0
- crabcode_core/api/base.py +66 -0
- crabcode_core/api/codex_adapter.py +390 -0
- crabcode_core/api/openai_adapter.py +221 -0
- crabcode_core/api/registry.py +40 -0
- crabcode_core/compact/__init__.py +7 -0
- crabcode_core/compact/compact.py +150 -0
- crabcode_core/config/__init__.py +3 -0
- crabcode_core/config/claudemd.py +53 -0
- crabcode_core/config/manager.py +152 -0
- crabcode_core/events.py +392 -0
- crabcode_core/mcp/__init__.py +4 -0
- crabcode_core/mcp/client.py +163 -0
- crabcode_core/mcp/config.py +52 -0
- crabcode_core/permissions/__init__.py +3 -0
- crabcode_core/permissions/manager.py +106 -0
- crabcode_core/prompts/__init__.py +0 -0
- crabcode_core/prompts/context.py +209 -0
- crabcode_core/prompts/profile.py +66 -0
- crabcode_core/prompts/system.py +405 -0
- crabcode_core/prompts/templates.py +60 -0
- crabcode_core/query/__init__.py +0 -0
- crabcode_core/query/loop.py +442 -0
- crabcode_core/session/__init__.py +3 -0
- crabcode_core/session/storage.py +153 -0
- crabcode_core/skills/__init__.py +5 -0
- crabcode_core/skills/loader.py +147 -0
- crabcode_core/tools/__init__.py +29 -0
- crabcode_core/tools/agent.py +160 -0
- crabcode_core/tools/bash.py +127 -0
- crabcode_core/tools/diff_utils.py +120 -0
- crabcode_core/tools/file_edit.py +145 -0
- crabcode_core/tools/file_read.py +123 -0
- crabcode_core/tools/file_write.py +102 -0
- crabcode_core/tools/glob.py +109 -0
- crabcode_core/tools/grep.py +130 -0
- crabcode_core/tools/lint.py +368 -0
- crabcode_core/tools/memory.py +236 -0
- crabcode_core/tools/skill.py +112 -0
- crabcode_core/types/__init__.py +73 -0
- crabcode_core/types/config.py +103 -0
- crabcode_core/types/event.py +105 -0
- crabcode_core/types/message.py +177 -0
- crabcode_core/types/tool.py +138 -0
- crabcode_search/__init__.py +11 -0
- crabcode_search/background.py +371 -0
- crabcode_search/chunker.py +517 -0
- crabcode_search/embedder.py +447 -0
- crabcode_search/indexer.py +212 -0
- crabcode_search/store.py +222 -0
- crabcode_search/tool.py +232 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crabcode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CrabCode - AI coding assistant in the terminal
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: anthropic>=0.40.0
|
|
7
|
+
Requires-Dist: httpx>=0.27.0
|
|
8
|
+
Requires-Dist: mcp>=1.0.0
|
|
9
|
+
Requires-Dist: openai>=1.50.0
|
|
10
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
12
|
+
Requires-Dist: rich>=13.0.0
|
|
13
|
+
Requires-Dist: tiktoken>=0.7.0
|
|
14
|
+
Requires-Dist: typer>=0.12.0
|
|
15
|
+
Provides-Extra: bedrock
|
|
16
|
+
Requires-Dist: anthropic[bedrock]>=0.40.0; extra == 'bedrock'
|
|
17
|
+
Provides-Extra: search
|
|
18
|
+
Requires-Dist: modelscope>=1.10.0; extra == 'search'
|
|
19
|
+
Requires-Dist: sentence-transformers>=2.0.0; extra == 'search'
|
|
20
|
+
Requires-Dist: tree-sitter-language-pack>=1.0.0; extra == 'search'
|
|
21
|
+
Requires-Dist: usearch>=2.12.0; extra == 'search'
|
|
22
|
+
Provides-Extra: vertex
|
|
23
|
+
Requires-Dist: anthropic[vertex]>=0.40.0; extra == 'vertex'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
crabcode_cli/__init__.py,sha256=EmiIl6vNhF_COIsCBw4ND_rmu8EFVVGvWoMh1uIVenA,63
|
|
2
|
+
crabcode_cli/app.py,sha256=kYmRDq3RUFxHU5FGMEqa6K5DzCn6Vd1FHmaC1R9HWEw,5212
|
|
3
|
+
crabcode_cli/banner.py,sha256=K0ak6Ng4l85C3TWnh64ulvdF4ReJSRsaWe8DOjse3Zg,1403
|
|
4
|
+
crabcode_cli/pipe.py,sha256=oAF_dPsXOOhJveV5AzuyH7QukBqDhx3m6i7qFMfvlLw,1810
|
|
5
|
+
crabcode_cli/repl.py,sha256=cVGkbdHTmFnpLUzAuZ9j7-3kutv5BshdhOxneXGp-7o,45532
|
|
6
|
+
crabcode_cli/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
crabcode_cli/render/messages.py,sha256=n9OsbLIAU3FyO9gwGMMJld7zq-71N0caz9ecmE2kIjo,568
|
|
8
|
+
crabcode_core/__init__.py,sha256=-MK4aDYG9K0Ndpfd03Fit34RBWzvNQgQpeYk8zqBHAg,73
|
|
9
|
+
crabcode_core/events.py,sha256=3CjajpVUlL3Aye48QVAyduUpNWlaUkH_LWUv-6Cf_aI,15148
|
|
10
|
+
crabcode_core/api/__init__.py,sha256=66G1xF1GKYMmfpmbyE9PigFx-_1Me3vIteI-1Xw8iAE,260
|
|
11
|
+
crabcode_core/api/anthropic_adapter.py,sha256=ir3iaXMyuZX2bz6sas7xz3JFNTmtUdFqJk_XAStk9cI,9077
|
|
12
|
+
crabcode_core/api/base.py,sha256=BZin6-FQo3OIugocA8Y3Hd7z0B3_gXwC9SXoQnoh2dk,1864
|
|
13
|
+
crabcode_core/api/codex_adapter.py,sha256=kollddHCR_1_Lz_NVSUCn5eGHHjj5csbeJqosuP8TC0,14780
|
|
14
|
+
crabcode_core/api/openai_adapter.py,sha256=DnIoQF9L0Qg9yXt9hfwS6F148B69QmmJucvabGkIL7o,7464
|
|
15
|
+
crabcode_core/api/registry.py,sha256=EAZxKTIv2VNHWsgbMTb95V0xf15ct36PIRJj4E9ZYcE,1566
|
|
16
|
+
crabcode_core/compact/__init__.py,sha256=P5C6D0MilT3FAnmaAxc8NeMi2CZLEMB-CwEzKUGqSlY,206
|
|
17
|
+
crabcode_core/compact/compact.py,sha256=mA_6gfZvMq6U-WvEKr8tsu2jnDy9psYBRbkvbqy_oPA,4514
|
|
18
|
+
crabcode_core/config/__init__.py,sha256=wbSnNVCqj673phu2pvIFz-f5HM5bLvH-FdD9b01HNRA,84
|
|
19
|
+
crabcode_core/config/claudemd.py,sha256=cg1Uzg_n5UWN5m528c59VbYkqYp4tb8kfLHWGqjFGVY,1778
|
|
20
|
+
crabcode_core/config/manager.py,sha256=BkjJcU5JUfYCQJF3aTU5nOOET0TwieIv-Db_yabiUL4,4837
|
|
21
|
+
crabcode_core/mcp/__init__.py,sha256=axvOgOWHjpKs7WFEkf8Za_4mm_oNe0tYyD94dAMnWTE,182
|
|
22
|
+
crabcode_core/mcp/client.py,sha256=Rmm1Twgruv34WkEXBfcn7ofeltCyP5ykzwAir6D-6Ng,4885
|
|
23
|
+
crabcode_core/mcp/config.py,sha256=z0iz-8Lgt2M7Ko9r6yMue_T1_AcE-37ldcd8kCvQkRM,1714
|
|
24
|
+
crabcode_core/permissions/__init__.py,sha256=YfNm9B3NPQnVxDvRukkW787XbOfJBh3ck47lwyblQgk,131
|
|
25
|
+
crabcode_core/permissions/manager.py,sha256=U6Tw0QpZVmYBtXzGcHAy110ge9U-_7Jdjt3-t7rhxOk,3540
|
|
26
|
+
crabcode_core/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
crabcode_core/prompts/context.py,sha256=skhtywNIfFQcdiTBEF5QU-6zixW08DfjqXHiU2OCCKU,6652
|
|
28
|
+
crabcode_core/prompts/profile.py,sha256=MWxdrC0vVc6QfaRa5sgKEC8Gi_s61F9ZaxjoCTiiEPc,2088
|
|
29
|
+
crabcode_core/prompts/system.py,sha256=88ePx8eYaKsHOUbjlmLRuJH488n61QjVneDY_fAAt6Y,27416
|
|
30
|
+
crabcode_core/prompts/templates.py,sha256=yJiQDphi8ilx0nrS7TW8ysxmiWukcmek-S5unAdvqQQ,2128
|
|
31
|
+
crabcode_core/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
crabcode_core/query/loop.py,sha256=HbmJv4VXkfhUm7xNdAI9A0wmz_nD5bU1EQnuhLWdYgo,15352
|
|
33
|
+
crabcode_core/session/__init__.py,sha256=NdWh1WvqPExjzwOdj9sBT5nUGD-Us_GiBNKucjoIr3k,131
|
|
34
|
+
crabcode_core/session/storage.py,sha256=wKqEx4USsNnxoeZV4aA9oJNBdUagryQqK6fhVpSe5sc,5101
|
|
35
|
+
crabcode_core/skills/__init__.py,sha256=BXXoMXXwBUOmkbEBHCSHbHazOa6XzvhdCUkBckrMuEA,174
|
|
36
|
+
crabcode_core/skills/loader.py,sha256=IhU7Gm8zCbQyPmUFWVbgYeJZuxdp8i1qfSfxsyF2qDc,4718
|
|
37
|
+
crabcode_core/tools/__init__.py,sha256=_B1pQrmyiexctwpDpZt2nJlBNe0rCh7LVwiknZBy1_4,914
|
|
38
|
+
crabcode_core/tools/agent.py,sha256=mxQn_GgPYv-wMQEzvktdc3e71_OahSCnYl6heslUshA,5915
|
|
39
|
+
crabcode_core/tools/bash.py,sha256=jEwbCg9lWuU3u3gOwZ8DyD0gcjUAM8llIx0zQpBppKE,4695
|
|
40
|
+
crabcode_core/tools/diff_utils.py,sha256=IYuEe53zyvb1FAEIU0vcsEPQGCz1TZCyekMhJC5r0AE,3480
|
|
41
|
+
crabcode_core/tools/file_edit.py,sha256=s0Y95EOEN2_1VKnGZHSbOyYODo7rsqQtY5EYKNJC2ww,5091
|
|
42
|
+
crabcode_core/tools/file_read.py,sha256=D5ausRFj9wRCI7E28KWZ07XtPjoN4Jp7MnEBzQTOwFY,3990
|
|
43
|
+
crabcode_core/tools/file_write.py,sha256=hCgiSuFAlfGsUgZXLjYUJxgv85zuDmj04qvDtidVTx0,3528
|
|
44
|
+
crabcode_core/tools/glob.py,sha256=tSoKOgNSX-hmq0ftQRTD7KzfQBHH56lJgc2XJqpuhnA,3472
|
|
45
|
+
crabcode_core/tools/grep.py,sha256=f8t2zaggB4hBwODQW-mnEVl9LFIBvC2ADIza294yFUo,4249
|
|
46
|
+
crabcode_core/tools/lint.py,sha256=GueRqdh3_qFsQRLFZbOpTFn-tTME8YRrvbe5mZ4wWDw,12514
|
|
47
|
+
crabcode_core/tools/memory.py,sha256=SR3FQ6zhRkTvOqGaIYL5fnorMmsLHRwRDDqzM_uukLc,8690
|
|
48
|
+
crabcode_core/tools/skill.py,sha256=g0NlvHLlCCeJStgNBvRwTBEUeygcKjvHtG8hOVJOnW8,3980
|
|
49
|
+
crabcode_core/types/__init__.py,sha256=h-uuoClhnMDxPKiahMIB9KXo4y2giYO-WNdB_LSXsh0,1446
|
|
50
|
+
crabcode_core/types/config.py,sha256=WLBf2mcY8BjvcyE4_CVXl9HyvWYfDC15S9imjG5jnIE,3606
|
|
51
|
+
crabcode_core/types/event.py,sha256=Fu-JvcmkpNp5IGTYKnif3fZMVR0zREpeLARE7nfoccU,2181
|
|
52
|
+
crabcode_core/types/message.py,sha256=wg5urxbhDMcjBwzE-2VqpJCaxzIr_cZh1UsT7KlrBI0,4593
|
|
53
|
+
crabcode_core/types/tool.py,sha256=QOTXf9h0GkG5Dk5Cx58OCxOHK_xnIEgkIOLeRqWl8DU,4132
|
|
54
|
+
crabcode_search/__init__.py,sha256=Fw3-cySOi83Y6WSduXUTC62ANXA5lf7PX3kSnQoXEUs,272
|
|
55
|
+
crabcode_search/background.py,sha256=urkJ6RV-aQvCRlaQr2n08S7NiY2sCDEjC-vOZw4vRl0,11098
|
|
56
|
+
crabcode_search/chunker.py,sha256=gtpMWAjNBEAmr9cfhU5uOcuXZFDhEpNRubVbttaxT14,16148
|
|
57
|
+
crabcode_search/embedder.py,sha256=suPEUgzfSfy4YwFmhHVisB0ii_7GLIdOQ4T6EI1D230,14569
|
|
58
|
+
crabcode_search/indexer.py,sha256=wNHrzB9oW8eRcKek6ZtXJdRB_HgFLj_1LhqqtG_tiDw,7522
|
|
59
|
+
crabcode_search/store.py,sha256=7E7fkQ25CgX0LlIp6wrgQNkVS9epUaqLpfoDA_V-BWg,8002
|
|
60
|
+
crabcode_search/tool.py,sha256=s3Tn8zUOYKAjbAL_HE18nwzX9zxLttvyy9Lc-v7nb5o,8828
|
|
61
|
+
crabcode-0.1.0.dist-info/METADATA,sha256=dDLzEjUtB0lQnpEdJI83Yx35E3ERpuwHvke0YjbEXd4,831
|
|
62
|
+
crabcode-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
63
|
+
crabcode-0.1.0.dist-info/entry_points.txt,sha256=tpYKME-yDLFb2eqwryWJG-d-HW4dkvDbzwogRl2jays,52
|
|
64
|
+
crabcode-0.1.0.dist-info/RECORD,,
|
crabcode_cli/__init__.py
ADDED
crabcode_cli/app.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""CrabCode CLI entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
name="crabcode",
|
|
14
|
+
help="CrabCode — AI coding assistant in the terminal",
|
|
15
|
+
add_completion=False,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
def main(
|
|
21
|
+
prompt: Optional[str] = typer.Argument(None, help="Prompt to send (pipe mode)"),
|
|
22
|
+
pipe: bool = typer.Option(False, "-p", "--pipe", help="Run in pipe mode (non-interactive)"),
|
|
23
|
+
model: Optional[str] = typer.Option(None, "-m", "--model", help="Model to use"),
|
|
24
|
+
provider: Optional[str] = typer.Option(None, "--provider", help="API provider (anthropic/openai/codex/router)"),
|
|
25
|
+
base_url: Optional[str] = typer.Option(None, "--base-url", help="API base URL (for routers)"),
|
|
26
|
+
api_format: Optional[str] = typer.Option(None, "--api-format", help="API format for router (anthropic/openai)"),
|
|
27
|
+
model_profile: Optional[str] = typer.Option(None, "--model-profile", "-M", help="Use a named model from settings.models"),
|
|
28
|
+
cwd: Optional[str] = typer.Option(None, "--cwd", help="Working directory"),
|
|
29
|
+
resume: Optional[str] = typer.Option(None, "-r", "--resume", help="Resume a session by ID"),
|
|
30
|
+
continue_last: bool = typer.Option(False, "-c", "--continue", help="Continue most recent session"),
|
|
31
|
+
) -> None:
|
|
32
|
+
"""CrabCode — AI coding assistant in the terminal."""
|
|
33
|
+
from crabcode_core.types.config import ApiConfig, CrabCodeSettings
|
|
34
|
+
|
|
35
|
+
work_dir = cwd or os.getcwd()
|
|
36
|
+
|
|
37
|
+
settings = CrabCodeSettings()
|
|
38
|
+
|
|
39
|
+
from crabcode_core.config.manager import ConfigManager
|
|
40
|
+
file_settings = ConfigManager(cwd=work_dir).load()
|
|
41
|
+
for key, val in file_settings.env.items():
|
|
42
|
+
os.environ.setdefault(key, val)
|
|
43
|
+
if file_settings.api.provider:
|
|
44
|
+
settings.api.provider = file_settings.api.provider
|
|
45
|
+
if file_settings.api.model:
|
|
46
|
+
settings.api.model = file_settings.api.model
|
|
47
|
+
if file_settings.api.base_url:
|
|
48
|
+
settings.api.base_url = file_settings.api.base_url
|
|
49
|
+
if file_settings.api.api_key_env:
|
|
50
|
+
settings.api.api_key_env = file_settings.api.api_key_env
|
|
51
|
+
if file_settings.api.format:
|
|
52
|
+
settings.api.format = file_settings.api.format
|
|
53
|
+
if not file_settings.api.thinking_enabled:
|
|
54
|
+
settings.api.thinking_enabled = file_settings.api.thinking_enabled
|
|
55
|
+
if file_settings.api.max_tokens != 16384:
|
|
56
|
+
settings.api.max_tokens = file_settings.api.max_tokens
|
|
57
|
+
|
|
58
|
+
if file_settings.models:
|
|
59
|
+
settings.models = {**file_settings.models, **settings.models}
|
|
60
|
+
if file_settings.default_model and not settings.default_model:
|
|
61
|
+
settings.default_model = file_settings.default_model
|
|
62
|
+
|
|
63
|
+
if file_settings.permissions.run_everything:
|
|
64
|
+
settings.permissions.run_everything = True
|
|
65
|
+
|
|
66
|
+
if model:
|
|
67
|
+
settings.api.model = model
|
|
68
|
+
if provider:
|
|
69
|
+
settings.api.provider = provider
|
|
70
|
+
if base_url:
|
|
71
|
+
settings.api.base_url = base_url
|
|
72
|
+
if api_format:
|
|
73
|
+
settings.api.format = api_format
|
|
74
|
+
if model_profile:
|
|
75
|
+
if model_profile in file_settings.models:
|
|
76
|
+
settings.models = file_settings.models
|
|
77
|
+
settings.default_model = model_profile
|
|
78
|
+
else:
|
|
79
|
+
typer.echo(
|
|
80
|
+
f"Warning: model profile '{model_profile}' not found in settings.models. "
|
|
81
|
+
f"Available: {list(file_settings.models.keys()) or '(none configured)'}",
|
|
82
|
+
err=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
stdin_text = ""
|
|
86
|
+
if not sys.stdin.isatty():
|
|
87
|
+
stdin_text = sys.stdin.read().strip()
|
|
88
|
+
|
|
89
|
+
if pipe or stdin_text:
|
|
90
|
+
text = prompt or stdin_text
|
|
91
|
+
if not text:
|
|
92
|
+
typer.echo("Error: no prompt provided", err=True)
|
|
93
|
+
raise typer.Exit(1)
|
|
94
|
+
|
|
95
|
+
from crabcode_cli.pipe import run_pipe
|
|
96
|
+
asyncio.run(run_pipe(text, settings=settings, cwd=work_dir))
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if prompt:
|
|
100
|
+
from crabcode_cli.pipe import run_pipe
|
|
101
|
+
asyncio.run(run_pipe(prompt, settings=settings, cwd=work_dir))
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
resume_id: str | None = None
|
|
105
|
+
if resume:
|
|
106
|
+
resume_id = resume
|
|
107
|
+
elif continue_last:
|
|
108
|
+
from crabcode_core.session.storage import SessionStorage
|
|
109
|
+
sessions = SessionStorage.list_sessions(work_dir)
|
|
110
|
+
if sessions:
|
|
111
|
+
resume_id = sessions[0]["session_id"]
|
|
112
|
+
else:
|
|
113
|
+
typer.echo("No previous sessions found.", err=True)
|
|
114
|
+
|
|
115
|
+
if file_settings.extra_tools:
|
|
116
|
+
try:
|
|
117
|
+
from crabcode_search.background import (
|
|
118
|
+
is_codebase_search_enabled,
|
|
119
|
+
maybe_spawn_background_indexer,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if is_codebase_search_enabled(file_settings.extra_tools):
|
|
123
|
+
maybe_spawn_background_indexer(
|
|
124
|
+
cwd=work_dir,
|
|
125
|
+
tool_config=file_settings.tool_settings.get("CodebaseSearch", {}),
|
|
126
|
+
)
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
from crabcode_cli.repl import run_repl
|
|
131
|
+
asyncio.run(run_repl(settings=settings, cwd=work_dir, resume_session_id=resume_id))
|
|
132
|
+
# Native library threads (PyTorch, FAISS) may keep the process alive
|
|
133
|
+
# after the asyncio loop shuts down; force-exit to avoid hanging.
|
|
134
|
+
os._exit(0)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def entry() -> None:
|
|
138
|
+
app()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
entry()
|
crabcode_cli/banner.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""CrabCode ASCII banner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
# ASCII crab art — hand-crafted
|
|
10
|
+
_CRAB = (
|
|
11
|
+
" ▐▛██▜▌ ▐▛██▜▌ \n"
|
|
12
|
+
" ▝▜██████▛▘ ▝▜██████▛▘ \n"
|
|
13
|
+
" ▐██████████▌ \n"
|
|
14
|
+
" ▐████████████▌ \n"
|
|
15
|
+
" ▐████████████▌ \n"
|
|
16
|
+
" ▝▜████████▛▘ \n"
|
|
17
|
+
" ▐▛██▛▘ ▝▜██▜▌ \n"
|
|
18
|
+
" ▐▛▘ ▝▜▌ \n"
|
|
19
|
+
"▝▘ ▝▘ \n"
|
|
20
|
+
" "
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# "CrabCode" in figlet slant font
|
|
24
|
+
_CRABCODE = r""" ______ __ ______ __
|
|
25
|
+
/ ____/________ _/ /_ / ____/___ ____/ /__
|
|
26
|
+
/ / / ___/ __ `/ __ \/ / / __ \/ __ / _ \
|
|
27
|
+
/ /___/ / / /_/ / /_/ / /___/ /_/ / /_/ / __/
|
|
28
|
+
\____/_/ \__,_/_.___/\____/\____/\__,_/\___/ """
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def print_banner(console: Console) -> None:
|
|
32
|
+
"""Print the CrabCode banner with ASCII crab art on the left."""
|
|
33
|
+
grid = Table.grid(padding=(0, 3))
|
|
34
|
+
grid.add_column(no_wrap=True)
|
|
35
|
+
grid.add_column(no_wrap=True)
|
|
36
|
+
grid.add_row(
|
|
37
|
+
Text(_CRAB, style="bold red"),
|
|
38
|
+
Text(_CRABCODE, style="bold cyan"),
|
|
39
|
+
)
|
|
40
|
+
console.print()
|
|
41
|
+
console.print(grid)
|
|
42
|
+
console.print()
|
crabcode_cli/pipe.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Pipe mode — reads from stdin, sends to Core, writes to stdout."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from crabcode_core.events import CoreSession
|
|
9
|
+
from crabcode_core.types.config import CrabCodeSettings
|
|
10
|
+
from crabcode_core.types.event import (
|
|
11
|
+
ErrorEvent,
|
|
12
|
+
PermissionRequestEvent,
|
|
13
|
+
PermissionResponseEvent,
|
|
14
|
+
StreamTextEvent,
|
|
15
|
+
ThinkingEvent,
|
|
16
|
+
ToolResultEvent,
|
|
17
|
+
ToolUseEvent,
|
|
18
|
+
TurnCompleteEvent,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def run_pipe(
|
|
23
|
+
prompt: str,
|
|
24
|
+
settings: CrabCodeSettings | None = None,
|
|
25
|
+
cwd: str = ".",
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Run a single prompt through the core and print the response."""
|
|
28
|
+
session = CoreSession(cwd=cwd, settings=settings)
|
|
29
|
+
|
|
30
|
+
async for event in session.send_message(prompt):
|
|
31
|
+
if isinstance(event, StreamTextEvent):
|
|
32
|
+
sys.stdout.write(event.text)
|
|
33
|
+
sys.stdout.flush()
|
|
34
|
+
elif isinstance(event, ThinkingEvent):
|
|
35
|
+
pass
|
|
36
|
+
elif isinstance(event, ToolUseEvent):
|
|
37
|
+
sys.stderr.write(f"\n[Tool: {event.tool_name}]\n")
|
|
38
|
+
sys.stderr.flush()
|
|
39
|
+
elif isinstance(event, PermissionRequestEvent):
|
|
40
|
+
await session.respond_permission(
|
|
41
|
+
PermissionResponseEvent(
|
|
42
|
+
tool_use_id=event.tool_use_id, allowed=True
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
elif isinstance(event, ToolResultEvent):
|
|
46
|
+
if event.is_error:
|
|
47
|
+
sys.stderr.write(f"\n[Error: {event.result}]\n")
|
|
48
|
+
sys.stderr.flush()
|
|
49
|
+
elif isinstance(event, ErrorEvent):
|
|
50
|
+
sys.stderr.write(f"\nError: {event.message}\n")
|
|
51
|
+
sys.stderr.flush()
|
|
52
|
+
if not event.recoverable:
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
elif isinstance(event, TurnCompleteEvent):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
sys.stdout.write("\n")
|
|
58
|
+
sys.stdout.flush()
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Message rendering utilities for the CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.markdown import Markdown
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def render_assistant_markdown(text: str, console: Console | None = None) -> None:
|
|
11
|
+
"""Render assistant text as Markdown."""
|
|
12
|
+
c = console or Console()
|
|
13
|
+
c.print(Markdown(text))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def render_error(message: str, console: Console | None = None) -> None:
|
|
17
|
+
"""Render an error message."""
|
|
18
|
+
c = console or Console()
|
|
19
|
+
c.print(Panel(message, title="Error", border_style="red"))
|