crabcode 0.1.0__py3-none-any.whl → 0.1.3__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.3.dist-info/METADATA +48 -0
- crabcode-0.1.3.dist-info/RECORD +130 -0
- crabcode_cli/__init__.py +1 -1
- crabcode_cli/__main__.py +4 -0
- crabcode_cli/app.py +308 -4
- crabcode_cli/pipe.py +87 -26
- crabcode_cli/repl.py +1611 -187
- crabcode_core/__init__.py +1 -1
- crabcode_core/agent_manager.py +597 -0
- crabcode_core/api/__init__.py +15 -1
- crabcode_core/api/anthropic_adapter.py +53 -6
- crabcode_core/api/azure_adapter.py +68 -0
- crabcode_core/api/base.py +25 -1
- crabcode_core/api/codex_adapter.py +290 -18
- crabcode_core/api/gemini_adapter.py +26 -0
- crabcode_core/api/model_info.py +70 -0
- crabcode_core/api/ollama_adapter.py +27 -0
- crabcode_core/api/openai_adapter.py +54 -11
- crabcode_core/api/registry.py +18 -0
- crabcode_core/compact/compact.py +77 -8
- crabcode_core/config/claudemd.py +6 -2
- crabcode_core/config/manager.py +7 -1
- crabcode_core/events.py +789 -30
- crabcode_core/hooks/__init__.py +5 -0
- crabcode_core/hooks/manager.py +267 -0
- crabcode_core/logging_utils.py +108 -0
- crabcode_core/lsp/__init__.py +6 -0
- crabcode_core/lsp/client.py +757 -0
- crabcode_core/lsp/diagnostics.py +130 -0
- crabcode_core/lsp/manager.py +582 -0
- crabcode_core/mcp/client.py +7 -2
- crabcode_core/mcp/config.py +5 -1
- crabcode_core/permissions/manager.py +21 -2
- crabcode_core/plan/__init__.py +5 -0
- crabcode_core/plan/executor.py +225 -0
- crabcode_core/plan/types.py +154 -0
- crabcode_core/prompts/context.py +11 -4
- crabcode_core/prompts/system.py +82 -5
- crabcode_core/prompts/templates.py +13 -0
- crabcode_core/query/loop.py +693 -67
- crabcode_core/schedule/__init__.py +5 -0
- crabcode_core/schedule/models.py +204 -0
- crabcode_core/schedule/store.py +349 -0
- crabcode_core/session/__init__.py +10 -1
- crabcode_core/session/export.py +141 -0
- crabcode_core/session/meta_db.py +346 -0
- crabcode_core/session/storage.py +483 -15
- crabcode_core/session/title_gen.py +52 -0
- crabcode_core/skills/__init__.py +3 -2
- crabcode_core/skills/loader.py +70 -5
- crabcode_core/skills/matcher.py +203 -0
- crabcode_core/snapshot/__init__.py +21 -0
- crabcode_core/snapshot/snapshot.py +422 -0
- crabcode_core/snapshot/tracker.py +250 -0
- crabcode_core/team/__init__.py +27 -0
- crabcode_core/team/bridge.py +66 -0
- crabcode_core/team/inbox.py +102 -0
- crabcode_core/team/manager.py +577 -0
- crabcode_core/team/message_bus.py +365 -0
- crabcode_core/team/models.py +191 -0
- crabcode_core/team/recovery.py +150 -0
- crabcode_core/tools/__init__.py +46 -0
- crabcode_core/tools/_input_helpers.py +39 -0
- crabcode_core/tools/agent.py +259 -66
- crabcode_core/tools/ask_user.py +122 -0
- crabcode_core/tools/bash.py +8 -0
- crabcode_core/tools/browser.py +662 -0
- crabcode_core/tools/checklist.py +241 -0
- crabcode_core/tools/checkpoint.py +66 -0
- crabcode_core/tools/file_edit.py +59 -3
- crabcode_core/tools/file_read.py +33 -2
- crabcode_core/tools/file_write.py +34 -3
- crabcode_core/tools/glob.py +11 -2
- crabcode_core/tools/grep.py +41 -20
- crabcode_core/tools/lint.py +4 -0
- crabcode_core/tools/memory.py +4 -1
- crabcode_core/tools/revert.py +90 -0
- crabcode_core/tools/schedule.py +612 -0
- crabcode_core/tools/skill.py +14 -0
- crabcode_core/tools/switch_mode.py +171 -0
- crabcode_core/tools/team.py +324 -0
- crabcode_core/tools/web_search.py +320 -0
- crabcode_core/types/__init__.py +10 -0
- crabcode_core/types/config.py +72 -2
- crabcode_core/types/event.py +135 -0
- crabcode_core/types/message.py +16 -1
- crabcode_core/types/tool.py +20 -0
- crabcode_core/utf8_sanitize.py +29 -0
- crabcode_gateway/__init__.py +3 -0
- crabcode_gateway/acp/__init__.py +13 -0
- crabcode_gateway/acp/agent.py +635 -0
- crabcode_gateway/acp/session.py +114 -0
- crabcode_gateway/acp/transport.py +39 -0
- crabcode_gateway/acp/types.py +82 -0
- crabcode_gateway/adapter.py +30 -0
- crabcode_gateway/event_bus.py +184 -0
- crabcode_gateway/grpc/__init__.py +0 -0
- crabcode_gateway/grpc/proto/crabcode.proto +138 -0
- crabcode_gateway/grpc/server.py +415 -0
- crabcode_gateway/middleware.py +237 -0
- crabcode_gateway/routes/__init__.py +0 -0
- crabcode_gateway/routes/agent.py +153 -0
- crabcode_gateway/routes/config.py +184 -0
- crabcode_gateway/routes/event.py +356 -0
- crabcode_gateway/routes/health.py +14 -0
- crabcode_gateway/routes/permission.py +59 -0
- crabcode_gateway/routes/session.py +371 -0
- crabcode_gateway/routes/snapshot.py +87 -0
- crabcode_gateway/schemas.py +563 -0
- crabcode_gateway/server.py +186 -0
- crabcode_search/embedder.py +57 -7
- crabcode_search/tool.py +18 -2
- crabcode-0.1.0.dist-info/METADATA +0 -23
- crabcode-0.1.0.dist-info/RECORD +0 -64
- {crabcode-0.1.0.dist-info → crabcode-0.1.3.dist-info}/WHEEL +0 -0
- {crabcode-0.1.0.dist-info → crabcode-0.1.3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crabcode
|
|
3
|
+
Version: 0.1.3
|
|
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: all
|
|
16
|
+
Requires-Dist: agent-client-protocol>=0.9.0; extra == 'all'
|
|
17
|
+
Requires-Dist: anthropic[bedrock]>=0.40.0; extra == 'all'
|
|
18
|
+
Requires-Dist: anthropic[vertex]>=0.40.0; extra == 'all'
|
|
19
|
+
Requires-Dist: fastapi>=0.115.0; extra == 'all'
|
|
20
|
+
Requires-Dist: grpcio-health-checking>=1.65.0; extra == 'all'
|
|
21
|
+
Requires-Dist: grpcio>=1.65.0; extra == 'all'
|
|
22
|
+
Requires-Dist: httpx>=0.27.0; extra == 'all'
|
|
23
|
+
Requires-Dist: modelscope>=1.10.0; extra == 'all'
|
|
24
|
+
Requires-Dist: playwright>=1.48.0; extra == 'all'
|
|
25
|
+
Requires-Dist: sentence-transformers>=2.0.0; extra == 'all'
|
|
26
|
+
Requires-Dist: sse-starlette>=2.0.0; extra == 'all'
|
|
27
|
+
Requires-Dist: tree-sitter-language-pack>=1.0.0; extra == 'all'
|
|
28
|
+
Requires-Dist: usearch>=2.12.0; extra == 'all'
|
|
29
|
+
Requires-Dist: uvicorn[standard]>=0.30.0; extra == 'all'
|
|
30
|
+
Provides-Extra: bedrock
|
|
31
|
+
Requires-Dist: anthropic[bedrock]>=0.40.0; extra == 'bedrock'
|
|
32
|
+
Provides-Extra: browser
|
|
33
|
+
Requires-Dist: playwright>=1.48.0; extra == 'browser'
|
|
34
|
+
Provides-Extra: gateway
|
|
35
|
+
Requires-Dist: agent-client-protocol>=0.9.0; extra == 'gateway'
|
|
36
|
+
Requires-Dist: fastapi>=0.115.0; extra == 'gateway'
|
|
37
|
+
Requires-Dist: grpcio-health-checking>=1.65.0; extra == 'gateway'
|
|
38
|
+
Requires-Dist: grpcio>=1.65.0; extra == 'gateway'
|
|
39
|
+
Requires-Dist: httpx>=0.27.0; extra == 'gateway'
|
|
40
|
+
Requires-Dist: sse-starlette>=2.0.0; extra == 'gateway'
|
|
41
|
+
Requires-Dist: uvicorn[standard]>=0.30.0; extra == 'gateway'
|
|
42
|
+
Provides-Extra: search
|
|
43
|
+
Requires-Dist: modelscope>=1.10.0; extra == 'search'
|
|
44
|
+
Requires-Dist: sentence-transformers>=2.0.0; extra == 'search'
|
|
45
|
+
Requires-Dist: tree-sitter-language-pack>=1.0.0; extra == 'search'
|
|
46
|
+
Requires-Dist: usearch>=2.12.0; extra == 'search'
|
|
47
|
+
Provides-Extra: vertex
|
|
48
|
+
Requires-Dist: anthropic[vertex]>=0.40.0; extra == 'vertex'
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
crabcode_cli/__init__.py,sha256=3OCzOxlK28hMxxTQHAdbnvbEZXEb07WWKnCjyDwhW-0,63
|
|
2
|
+
crabcode_cli/__main__.py,sha256=ekwH_ayJ5YpefhbXSDj-IM9mS9YrCwWpoDIH92V6tjg,75
|
|
3
|
+
crabcode_cli/app.py,sha256=RJXlRgXygGIg2N_uJqFOMMPFDL1HuZVP0vmF3wVI-ps,17612
|
|
4
|
+
crabcode_cli/banner.py,sha256=K0ak6Ng4l85C3TWnh64ulvdF4ReJSRsaWe8DOjse3Zg,1403
|
|
5
|
+
crabcode_cli/pipe.py,sha256=M79twqrmeCy1QxiQ-jDThYy_CzPHE5llbQsDXQRAoW8,4162
|
|
6
|
+
crabcode_cli/repl.py,sha256=6ApZtL7K-P14HRws6vP-1RSNGLsSP6YbJ_7xY3Y_bZo,103474
|
|
7
|
+
crabcode_cli/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
crabcode_cli/render/messages.py,sha256=n9OsbLIAU3FyO9gwGMMJld7zq-71N0caz9ecmE2kIjo,568
|
|
9
|
+
crabcode_core/__init__.py,sha256=bW4jmccyG4G7EZO1yb_4yZP4zxwS_wmyuZXhIIhlv0Y,73
|
|
10
|
+
crabcode_core/agent_manager.py,sha256=fu-t3xmiNRQUjJhUCdicLA6lflyD7TbGLpANckBRqnk,22869
|
|
11
|
+
crabcode_core/events.py,sha256=7IrCHMB8oYGzr6zcvAPnLmV4john8HGszZEbH3zU9-Y,48382
|
|
12
|
+
crabcode_core/logging_utils.py,sha256=H6E7lldUAeIeQzQooMl0MrQ07YlSO-tra_QlzxcOdks,3657
|
|
13
|
+
crabcode_core/utf8_sanitize.py,sha256=RRTGgwA9jqK-HLR1WUSFwgG4xJn7ZukTDu7M6Oi3fHI,936
|
|
14
|
+
crabcode_core/api/__init__.py,sha256=IXs4hLgC4cT85TLdmS4KgtPKYDytadkwvrDyBshr5ag,674
|
|
15
|
+
crabcode_core/api/anthropic_adapter.py,sha256=xZPxI4Z1ega0ZOSgwZBn6ttBCXybyGT8Z8XT20OhZ_o,10999
|
|
16
|
+
crabcode_core/api/azure_adapter.py,sha256=zJ7q3Ni3sVwK3Bbju_nO8t8kPEA_bL6scVFC-XRPRh4,2360
|
|
17
|
+
crabcode_core/api/base.py,sha256=AvOD8lwEc60GeuMZgvjN7xrHqkPkWhyhHDIXnJQ4XoU,2871
|
|
18
|
+
crabcode_core/api/codex_adapter.py,sha256=zrwVS-Nvv0ABDhrRqNTCHbFLMNdT3gqYO0_5GjMtyXU,26511
|
|
19
|
+
crabcode_core/api/gemini_adapter.py,sha256=RVk3zlLPqDtzG8w5psu4aSoFyFLua5qQx3subqz-5DQ,1130
|
|
20
|
+
crabcode_core/api/model_info.py,sha256=kzbEbZ9BqJffu8VQwPAZC5WXKs1qHNFq-MnM6h5XWDI,2002
|
|
21
|
+
crabcode_core/api/ollama_adapter.py,sha256=ZpTg11zOUGFE0J88qBXL7AvxDQU5PjWg2wWvm4uxUvY,1158
|
|
22
|
+
crabcode_core/api/openai_adapter.py,sha256=wqEdUn-hq3ht2XHOgGweRR1MxEKPgOxYglKWQtTDeLU,9855
|
|
23
|
+
crabcode_core/api/registry.py,sha256=tCVRE74bMUn9ukCbGp3bDtSOYZ6_EmsNfdVnnkGN6k4,2413
|
|
24
|
+
crabcode_core/compact/__init__.py,sha256=P5C6D0MilT3FAnmaAxc8NeMi2CZLEMB-CwEzKUGqSlY,206
|
|
25
|
+
crabcode_core/compact/compact.py,sha256=dsdqaA7NTHMxHdwNoBorlymJtchgiYKN_V2KS5jlSp0,7008
|
|
26
|
+
crabcode_core/config/__init__.py,sha256=wbSnNVCqj673phu2pvIFz-f5HM5bLvH-FdD9b01HNRA,84
|
|
27
|
+
crabcode_core/config/claudemd.py,sha256=eUJQRdbT3fP0lB6C-Yc2pVBt0u4Z0qBEamXtkOErJWw,1973
|
|
28
|
+
crabcode_core/config/manager.py,sha256=OJ1o-wBhk1NCI-09Vzv9lb1jOcB8fn9rda428CGlYjc,5263
|
|
29
|
+
crabcode_core/hooks/__init__.py,sha256=wfOnbLuJqtLezKYWYmBTAE9qG-eaG7wSfgx1-kS7N64,165
|
|
30
|
+
crabcode_core/hooks/manager.py,sha256=dsySmLq1G-7ZAdf8hq-9FhknchlA5DWKQBA_ApirNPE,9387
|
|
31
|
+
crabcode_core/lsp/__init__.py,sha256=np9EYVvOi94ZFxLWW_kD13AfwaIhN0Vcp8-pHaLDn6c,189
|
|
32
|
+
crabcode_core/lsp/client.py,sha256=AdQ2hzryM4RrPDXdi168yV3kQCkb69O1B1-MNUIFWts,26699
|
|
33
|
+
crabcode_core/lsp/diagnostics.py,sha256=hcQq9MJY8HqMtKPKnRWfGK7GOgqmQAi2niaIKVUWNTI,4218
|
|
34
|
+
crabcode_core/lsp/manager.py,sha256=morGtndY0dE-O88u4LalOnnAl8KzOWmQHqnUTCphtfs,18969
|
|
35
|
+
crabcode_core/mcp/__init__.py,sha256=axvOgOWHjpKs7WFEkf8Za_4mm_oNe0tYyD94dAMnWTE,182
|
|
36
|
+
crabcode_core/mcp/client.py,sha256=3e7LQKq8kNyE2E6-wmSa_MpysgsznuPUS6sxWgp2RiA,5233
|
|
37
|
+
crabcode_core/mcp/config.py,sha256=DQZ39LX-smZ11oyqf-KnJvcqEriJF68Q6LewWn0hrdk,1955
|
|
38
|
+
crabcode_core/permissions/__init__.py,sha256=YfNm9B3NPQnVxDvRukkW787XbOfJBh3ck47lwyblQgk,131
|
|
39
|
+
crabcode_core/permissions/manager.py,sha256=JngKqqKk_1rtcBSmV9aNB08jDQ-d98_Y3xwhw69bOQ0,4217
|
|
40
|
+
crabcode_core/plan/__init__.py,sha256=co_SEC__Z3OtijC9hxdVWIcdUBsUVsJnQ-LkxJfpAL8,169
|
|
41
|
+
crabcode_core/plan/executor.py,sha256=H3krzji0PYGIEDv-bLRzcVGD2C9AOoEeqKUu-pIjE-Q,8357
|
|
42
|
+
crabcode_core/plan/types.py,sha256=BuYxXh8jLLyQZtcNG3F_UJOLMfbGlAE-mffKl69lGgE,5178
|
|
43
|
+
crabcode_core/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
+
crabcode_core/prompts/context.py,sha256=EHFdO-3_386pnFPIDTruy2xDmrIZDgHwvU5v52_vWDQ,7250
|
|
45
|
+
crabcode_core/prompts/profile.py,sha256=MWxdrC0vVc6QfaRa5sgKEC8Gi_s61F9ZaxjoCTiiEPc,2088
|
|
46
|
+
crabcode_core/prompts/system.py,sha256=XHIAOPR5m3RSiPm141wv3AJpOZLMEz5tpt7eALNuM8s,33389
|
|
47
|
+
crabcode_core/prompts/templates.py,sha256=XZRdGXWc1-OGksOp1r6nML7Cr0T1SmT_ktulSs1VqPw,2570
|
|
48
|
+
crabcode_core/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
crabcode_core/query/loop.py,sha256=Hf6PL_mdQClM0jFPQw-1eO-dhoSWFTHXpBAOdK5PK60,42004
|
|
50
|
+
crabcode_core/schedule/__init__.py,sha256=Pf1VyLKpZuvtxkUyYmJv0m79vYfN9p542A26UTBS-0E,149
|
|
51
|
+
crabcode_core/schedule/models.py,sha256=T3Z7wapFCkuE_ZeIDK2Clq8WGThKs88ENjXklT1_LE8,7847
|
|
52
|
+
crabcode_core/schedule/store.py,sha256=3NL8xKgyDvndiuw3Zc32_rt6Bze3VmTbVd9_DM67MII,12455
|
|
53
|
+
crabcode_core/session/__init__.py,sha256=0E_Flt_OkcFVkxbRTDVxot3TrUZ23T5qspIJqlYnwtE,441
|
|
54
|
+
crabcode_core/session/export.py,sha256=ElD3yd5KbuOWGK2TSUpULqUgloDpab3X2hdUbFiZ_iY,4453
|
|
55
|
+
crabcode_core/session/meta_db.py,sha256=xIlafhJ39Khcvoor-qzxKdnZt91G3IIPw6fZ-Zn5KxU,13294
|
|
56
|
+
crabcode_core/session/storage.py,sha256=zimJH4Z4aFrQaZ9PPer1TVAOtNIASs1crBxWPcLhJ3Y,24816
|
|
57
|
+
crabcode_core/session/title_gen.py,sha256=TrIqrWRSScZjVGOaG3h4zl7PhxPMD-86zLIYcVdElP4,1507
|
|
58
|
+
crabcode_core/skills/__init__.py,sha256=2A99hK6_T53EPy-hcYYu4FobhUAZjLL-O7ObIyG3yuQ,296
|
|
59
|
+
crabcode_core/skills/loader.py,sha256=VbT3C84xQ3AOf96MTZmyk3J71Z06M2sGajaQKciBXME,7142
|
|
60
|
+
crabcode_core/skills/matcher.py,sha256=gPt-nYz9YmUK_Pb3KjivrYl6izDSBiYo3HlrDhxcggs,7066
|
|
61
|
+
crabcode_core/snapshot/__init__.py,sha256=ACgq5C5_ChCXTW8UJWzhWz8vAJLw5MKtNyt9F1fYKQk,418
|
|
62
|
+
crabcode_core/snapshot/snapshot.py,sha256=RPcywyzxUYlg1fFSVxUAvpJnLLlN0rXMN6rk-NaqwJQ,17101
|
|
63
|
+
crabcode_core/snapshot/tracker.py,sha256=7ykjcb32vAPqAczvMmCxyRjP4lxg74ctl7s_ItRE3WI,8392
|
|
64
|
+
crabcode_core/team/__init__.py,sha256=llNqc6kSc5ng3WI1_-ABFe9RXc189A3MMXb1awKB4Ko,502
|
|
65
|
+
crabcode_core/team/bridge.py,sha256=RhKzk_KFBi7xKN1sqjhSJZ9ADXp9A458aoxgoAPl67c,2081
|
|
66
|
+
crabcode_core/team/inbox.py,sha256=Dkli61VNJ-PiedvqoTlQNSmKOq5GgCfpYVp5KwbZCFc,3629
|
|
67
|
+
crabcode_core/team/manager.py,sha256=tjOcTQDP-LcHjEyRiTquis92dXdPwSrImp0Oqcuh4Mk,20131
|
|
68
|
+
crabcode_core/team/message_bus.py,sha256=0Ztj9ikGIzItUuPekSOMS_5YJ7l79CG4T4HK2IPeslQ,13377
|
|
69
|
+
crabcode_core/team/models.py,sha256=P98f7q4oDMH6QU7Ev8DhpYkYmCknG50u_c5VPq3eBXE,5568
|
|
70
|
+
crabcode_core/team/recovery.py,sha256=Lwdcas-zUxqVv9y1EuM4X-4Ixbr7wkugm1dyjMe3nuk,4913
|
|
71
|
+
crabcode_core/tools/__init__.py,sha256=G_KC87cvsj5y1ncHgN5dCM-3glXbAC2fxM-cvMpCDiM,2336
|
|
72
|
+
crabcode_core/tools/_input_helpers.py,sha256=d7sketqhc2kWodNeuUVfiD40-GnKArsrmxGxfjtXEAE,1298
|
|
73
|
+
crabcode_core/tools/agent.py,sha256=35aLPdgDIqv-jNba2_SV6RIGrOPhxS4fNMi8kRmqt-g,14000
|
|
74
|
+
crabcode_core/tools/ask_user.py,sha256=6CjWaX6f-3RWPyBSSWq4r5m63vRT_wu5_dflAbvE2fY,4466
|
|
75
|
+
crabcode_core/tools/bash.py,sha256=nUztFdJ5a9Zt1megDo0Geda8Oa54mPjMtd4ReDzqbvc,5061
|
|
76
|
+
crabcode_core/tools/browser.py,sha256=3x8sRdZZetlO5uRpbWchmryLQ0EiUtTvScFPoR2GZnU,28267
|
|
77
|
+
crabcode_core/tools/checklist.py,sha256=fZCHi1ft_Yo1bXMBcG-wMdZOFdPQZGOdeHdwYboBDkE,9265
|
|
78
|
+
crabcode_core/tools/checkpoint.py,sha256=l0BJeNOJ9Q9P6AUnqqiQmYb9wG0CzFK2XD3RKeuEpuw,2554
|
|
79
|
+
crabcode_core/tools/diff_utils.py,sha256=IYuEe53zyvb1FAEIU0vcsEPQGCz1TZCyekMhJC5r0AE,3480
|
|
80
|
+
crabcode_core/tools/file_edit.py,sha256=SX9r0e7tBbAH-7xWMERaEqFQI9Fjb7L9AwfJiD8rtPU,7471
|
|
81
|
+
crabcode_core/tools/file_read.py,sha256=62sjx1wHNaaOF3XOAKubJ6jhT5V5XWUEnnLwhYqOsDw,5085
|
|
82
|
+
crabcode_core/tools/file_write.py,sha256=6iKg6kyrIcLGeyC2iwN8Z9WE1CDi7L90yFYU1O_GwzU,4829
|
|
83
|
+
crabcode_core/tools/glob.py,sha256=cB9DKE1r0dZqeojzxkfix4am4eI2bCqyDmgK5xc_wdY,3876
|
|
84
|
+
crabcode_core/tools/grep.py,sha256=ZR_9c-LehzHSV8CnVljG3u4-LoLglFhMDeTg4ey4Wk4,5299
|
|
85
|
+
crabcode_core/tools/lint.py,sha256=Kxu5jPq6rbf1Wfw7yXy6lQc_GpT0M4ADK87NEaW1buc,12692
|
|
86
|
+
crabcode_core/tools/memory.py,sha256=YuclnzEGUMgLrizmaJwg-sjMlY6Y6EYlKFhFe_kqgKo,8838
|
|
87
|
+
crabcode_core/tools/revert.py,sha256=uodlTPg9qyJbvwkW9zLuV35zeNc6Z4l5TJwnO03C0fY,3366
|
|
88
|
+
crabcode_core/tools/schedule.py,sha256=tfqCBRXM8kPzR7Mxyogly6Ui8kueqzGxaLalgyVRn3s,21954
|
|
89
|
+
crabcode_core/tools/skill.py,sha256=QdxSh4lRjBOR1J9ggU3_1sZtyETeYxXui9eBj-VFNk4,4763
|
|
90
|
+
crabcode_core/tools/switch_mode.py,sha256=qS9oo8CZhw99gO2g7wchOhezavGersprbOPHkCIO1e0,7328
|
|
91
|
+
crabcode_core/tools/team.py,sha256=Y4kyT5zFvd1qH1Y8tr-VW393ZyLKUfwryncBwvyLc_8,12503
|
|
92
|
+
crabcode_core/tools/web_search.py,sha256=SmQLjPmvLCNeAM0i1nT4NtsNGrlJXqxoipvcQoJujX4,12009
|
|
93
|
+
crabcode_core/types/__init__.py,sha256=gghuU5D2Zgy9AKWge55hqOTphxj2K-Q4oeQPUuaFvCU,1664
|
|
94
|
+
crabcode_core/types/config.py,sha256=C7NJmBpjjShYGX9Fa15xQspaBi6zW8COi4bG596xe7A,6449
|
|
95
|
+
crabcode_core/types/event.py,sha256=XHzl9CGj-Tn0ApkDK1C2IxWY2l9-I0YGX7Sezkcx2UU,5223
|
|
96
|
+
crabcode_core/types/message.py,sha256=w_pO6lZa8xVDVkWyjSF9wZ8y3avQu2V7L772VqqRdrU,5144
|
|
97
|
+
crabcode_core/types/tool.py,sha256=DIzWBH_aTOuEc3KLgniyEMbKluhR86OrTet2-ixcJFw,5197
|
|
98
|
+
crabcode_gateway/__init__.py,sha256=eURgiXIn5nv-c9MRBcgFaQgpXowvL7_Mq1AJ8a1Trf8,84
|
|
99
|
+
crabcode_gateway/adapter.py,sha256=jlFzke-V4LfgA20QDQuOMSVWmrrt9QYM1n8Mee_d3lQ,842
|
|
100
|
+
crabcode_gateway/event_bus.py,sha256=g9EpAk3HkK0MGUCq0YhYT7mJUVqmGJKveCUgBbSTvfY,6300
|
|
101
|
+
crabcode_gateway/middleware.py,sha256=yTjdZPJTTBbncLYU0CLbAx1NAAWqls5maZqsrlb9DfU,7782
|
|
102
|
+
crabcode_gateway/schemas.py,sha256=PK5PXofcCGjzzwiPVSqHnuholdpIJHskfOh3sjyf7iE,15215
|
|
103
|
+
crabcode_gateway/server.py,sha256=X6KZQ1OgILbBhZBxms0KIe_NPdt0wBAK1ZrTIdcwH2Y,5696
|
|
104
|
+
crabcode_gateway/acp/__init__.py,sha256=PJUCdGBi_ud--LoWxbYbzO7gAgdFCzY_UKeB3JhUvUA,352
|
|
105
|
+
crabcode_gateway/acp/agent.py,sha256=D2v7fhHHZzmRek_Gze2YZ3MUALBAZ0SSxtNsL4loM2I,24750
|
|
106
|
+
crabcode_gateway/acp/session.py,sha256=rbN1EcCQw2-LEr-bbW3x0TVmeL5ZGeEaSfBX6LEmjws,4049
|
|
107
|
+
crabcode_gateway/acp/transport.py,sha256=IWU3ih_9heY-EH-AhbprAtpcaj8ZvtWMMS76CAX82QA,1119
|
|
108
|
+
crabcode_gateway/acp/types.py,sha256=_hGt2o7CnhfgC_lvOTtdc_Z1hk6y3WE3RCdJ0Qz0hws,2424
|
|
109
|
+
crabcode_gateway/grpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
|
+
crabcode_gateway/grpc/server.py,sha256=JWTK0d4EjSWVPEYALHxFfO69e6B9oueDHnLUMUWwq0U,16576
|
|
111
|
+
crabcode_gateway/grpc/proto/crabcode.proto,sha256=J4eLfFowt5yqiDDajQF1r3_YAiAmxU-hZ-cDO2UEciQ,3435
|
|
112
|
+
crabcode_gateway/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
113
|
+
crabcode_gateway/routes/agent.py,sha256=G2AbNZUKlaupfZ_rSBmFNAdB476oQUoNrnGgGrNRlhk,4970
|
|
114
|
+
crabcode_gateway/routes/config.py,sha256=rVJtK0ZZD-4LMh3zWtyqqePmOBxLZZIiD5d5ItRxzLM,5985
|
|
115
|
+
crabcode_gateway/routes/event.py,sha256=Kc3GbCiHdRn9WdZJ6DQHlEhVpf2ro0hNI7F-pJCNwYo,13341
|
|
116
|
+
crabcode_gateway/routes/health.py,sha256=GzR1RAGNmOPXEyUQNRE0ANJsbDMOQHQGNRhA3dRz87Y,293
|
|
117
|
+
crabcode_gateway/routes/permission.py,sha256=kC114yHMC-C6FzhQ6wE6TM8R5rOTg-1ZQgviIXPaHSg,1790
|
|
118
|
+
crabcode_gateway/routes/session.py,sha256=mqBpdcxfPOLNINsFNSx9zHwIJxxeaZH3MILw8vjIzuU,12473
|
|
119
|
+
crabcode_gateway/routes/snapshot.py,sha256=Ax1782J76ES1Vh9Y4yGiHNIqcu8Be-LPKggfGd7bjU8,3403
|
|
120
|
+
crabcode_search/__init__.py,sha256=Fw3-cySOi83Y6WSduXUTC62ANXA5lf7PX3kSnQoXEUs,272
|
|
121
|
+
crabcode_search/background.py,sha256=urkJ6RV-aQvCRlaQr2n08S7NiY2sCDEjC-vOZw4vRl0,11098
|
|
122
|
+
crabcode_search/chunker.py,sha256=gtpMWAjNBEAmr9cfhU5uOcuXZFDhEpNRubVbttaxT14,16148
|
|
123
|
+
crabcode_search/embedder.py,sha256=NlNuOHS7t4JvaWXkNU-T_xxnkjRxFrr3l57dlB5IRkc,16728
|
|
124
|
+
crabcode_search/indexer.py,sha256=wNHrzB9oW8eRcKek6ZtXJdRB_HgFLj_1LhqqtG_tiDw,7522
|
|
125
|
+
crabcode_search/store.py,sha256=7E7fkQ25CgX0LlIp6wrgQNkVS9epUaqLpfoDA_V-BWg,8002
|
|
126
|
+
crabcode_search/tool.py,sha256=-hOW6n3gWDVq-kVKVs3ATOIOaeQtihCFi2VXNb9t00Q,9422
|
|
127
|
+
crabcode-0.1.3.dist-info/METADATA,sha256=v0EnVaE4fvh2C1NIGZ2N2azPba-MXkX3sju_lJw5PsE,2107
|
|
128
|
+
crabcode-0.1.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
129
|
+
crabcode-0.1.3.dist-info/entry_points.txt,sha256=tpYKME-yDLFb2eqwryWJG-d-HW4dkvDbzwogRl2jays,52
|
|
130
|
+
crabcode-0.1.3.dist-info/RECORD,,
|
crabcode_cli/__init__.py
CHANGED
crabcode_cli/__main__.py
ADDED
crabcode_cli/app.py
CHANGED
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
from typing import Optional
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
|
+
from crabcode_core.logging_utils import configure_logging, get_logger
|
|
11
12
|
|
|
12
13
|
app = typer.Typer(
|
|
13
14
|
name="crabcode",
|
|
@@ -15,19 +16,22 @@ app = typer.Typer(
|
|
|
15
16
|
add_completion=False,
|
|
16
17
|
)
|
|
17
18
|
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
@app.command()
|
|
20
23
|
def main(
|
|
21
24
|
prompt: Optional[str] = typer.Argument(None, help="Prompt to send (pipe mode)"),
|
|
22
25
|
pipe: bool = typer.Option(False, "-p", "--pipe", help="Run in pipe mode (non-interactive)"),
|
|
23
26
|
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)"),
|
|
27
|
+
provider: Optional[str] = typer.Option(None, "--provider", help="API provider (anthropic/openai/codex/ollama/gemini/azure/bedrock/vertex/router)"),
|
|
25
28
|
base_url: Optional[str] = typer.Option(None, "--base-url", help="API base URL (for routers)"),
|
|
26
29
|
api_format: Optional[str] = typer.Option(None, "--api-format", help="API format for router (anthropic/openai)"),
|
|
27
30
|
model_profile: Optional[str] = typer.Option(None, "--model-profile", "-M", help="Use a named model from settings.models"),
|
|
28
31
|
cwd: Optional[str] = typer.Option(None, "--cwd", help="Working directory"),
|
|
29
32
|
resume: Optional[str] = typer.Option(None, "-r", "--resume", help="Resume a session by ID"),
|
|
30
33
|
continue_last: bool = typer.Option(False, "-c", "--continue", help="Continue most recent session"),
|
|
34
|
+
image: Optional[list[str]] = typer.Option(None, "-i", "--image", help="Image file(s) to attach (repeatable)"),
|
|
31
35
|
) -> None:
|
|
32
36
|
"""CrabCode — AI coding assistant in the terminal."""
|
|
33
37
|
from crabcode_core.types.config import ApiConfig, CrabCodeSettings
|
|
@@ -48,12 +52,20 @@ def main(
|
|
|
48
52
|
settings.api.base_url = file_settings.api.base_url
|
|
49
53
|
if file_settings.api.api_key_env:
|
|
50
54
|
settings.api.api_key_env = file_settings.api.api_key_env
|
|
55
|
+
if file_settings.api.http_headers:
|
|
56
|
+
settings.api.http_headers = dict(file_settings.api.http_headers)
|
|
51
57
|
if file_settings.api.format:
|
|
52
58
|
settings.api.format = file_settings.api.format
|
|
53
59
|
if not file_settings.api.thinking_enabled:
|
|
54
60
|
settings.api.thinking_enabled = file_settings.api.thinking_enabled
|
|
55
61
|
if file_settings.api.max_tokens != 16384:
|
|
56
62
|
settings.api.max_tokens = file_settings.api.max_tokens
|
|
63
|
+
if file_settings.api.azure_endpoint:
|
|
64
|
+
settings.api.azure_endpoint = file_settings.api.azure_endpoint
|
|
65
|
+
if file_settings.api.azure_api_version:
|
|
66
|
+
settings.api.azure_api_version = file_settings.api.azure_api_version
|
|
67
|
+
if file_settings.api.azure_deployment:
|
|
68
|
+
settings.api.azure_deployment = file_settings.api.azure_deployment
|
|
57
69
|
|
|
58
70
|
if file_settings.models:
|
|
59
71
|
settings.models = {**file_settings.models, **settings.models}
|
|
@@ -62,6 +74,9 @@ def main(
|
|
|
62
74
|
|
|
63
75
|
if file_settings.permissions.run_everything:
|
|
64
76
|
settings.permissions.run_everything = True
|
|
77
|
+
settings.logging = file_settings.logging.model_copy(deep=True)
|
|
78
|
+
|
|
79
|
+
configure_logging(work_dir, settings.logging)
|
|
65
80
|
|
|
66
81
|
if model:
|
|
67
82
|
settings.api.model = model
|
|
@@ -82,6 +97,30 @@ def main(
|
|
|
82
97
|
err=True,
|
|
83
98
|
)
|
|
84
99
|
|
|
100
|
+
# Read image files into base64 attachments
|
|
101
|
+
image_attachments: list[dict[str, str]] | None = None
|
|
102
|
+
if image:
|
|
103
|
+
import base64
|
|
104
|
+
import mimetypes
|
|
105
|
+
from pathlib import Path
|
|
106
|
+
|
|
107
|
+
image_attachments = []
|
|
108
|
+
for img_path_str in image:
|
|
109
|
+
img_path = Path(img_path_str)
|
|
110
|
+
if not img_path.exists():
|
|
111
|
+
typer.echo(f"Error: image file not found: {img_path_str}", err=True)
|
|
112
|
+
raise typer.Exit(1)
|
|
113
|
+
if img_path.stat().st_size > 20 * 1024 * 1024:
|
|
114
|
+
typer.echo(f"Error: image file too large (>20MB): {img_path_str}", err=True)
|
|
115
|
+
raise typer.Exit(1)
|
|
116
|
+
media_type = mimetypes.guess_type(img_path_str)[0] or "image/png"
|
|
117
|
+
if not media_type.startswith("image/"):
|
|
118
|
+
typer.echo(f"Error: not an image file: {img_path_str}", err=True)
|
|
119
|
+
raise typer.Exit(1)
|
|
120
|
+
with open(img_path, "rb") as f:
|
|
121
|
+
data = base64.b64encode(f.read()).decode("ascii")
|
|
122
|
+
image_attachments.append({"media_type": media_type, "data": data})
|
|
123
|
+
|
|
85
124
|
stdin_text = ""
|
|
86
125
|
if not sys.stdin.isatty():
|
|
87
126
|
stdin_text = sys.stdin.read().strip()
|
|
@@ -93,12 +132,12 @@ def main(
|
|
|
93
132
|
raise typer.Exit(1)
|
|
94
133
|
|
|
95
134
|
from crabcode_cli.pipe import run_pipe
|
|
96
|
-
asyncio.run(run_pipe(text, settings=settings, cwd=work_dir))
|
|
135
|
+
asyncio.run(run_pipe(text, settings=settings, cwd=work_dir, images=image_attachments))
|
|
97
136
|
return
|
|
98
137
|
|
|
99
138
|
if prompt:
|
|
100
139
|
from crabcode_cli.pipe import run_pipe
|
|
101
|
-
asyncio.run(run_pipe(prompt, settings=settings, cwd=work_dir))
|
|
140
|
+
asyncio.run(run_pipe(prompt, settings=settings, cwd=work_dir, images=image_attachments))
|
|
102
141
|
return
|
|
103
142
|
|
|
104
143
|
resume_id: str | None = None
|
|
@@ -125,7 +164,7 @@ def main(
|
|
|
125
164
|
tool_config=file_settings.tool_settings.get("CodebaseSearch", {}),
|
|
126
165
|
)
|
|
127
166
|
except Exception:
|
|
128
|
-
|
|
167
|
+
logger.exception("Failed to start background indexer bootstrap")
|
|
129
168
|
|
|
130
169
|
from crabcode_cli.repl import run_repl
|
|
131
170
|
asyncio.run(run_repl(settings=settings, cwd=work_dir, resume_session_id=resume_id))
|
|
@@ -134,7 +173,272 @@ def main(
|
|
|
134
173
|
os._exit(0)
|
|
135
174
|
|
|
136
175
|
|
|
176
|
+
sessions_app = typer.Typer(
|
|
177
|
+
name="sessions",
|
|
178
|
+
help="Manage CrabCode sessions",
|
|
179
|
+
)
|
|
180
|
+
app.add_typer(sessions_app, name="sessions")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@sessions_app.command("list")
|
|
184
|
+
def sessions_list(
|
|
185
|
+
all_projects: bool = typer.Option(False, "--all", "-a", help="List sessions across all projects"),
|
|
186
|
+
cwd: Optional[str] = typer.Option(None, "--cwd", help="Working directory"),
|
|
187
|
+
limit: int = typer.Option(20, "--limit", "-n", help="Max number of sessions to show"),
|
|
188
|
+
) -> None:
|
|
189
|
+
"""List recent sessions."""
|
|
190
|
+
work_dir = cwd or os.getcwd()
|
|
191
|
+
if all_projects:
|
|
192
|
+
from crabcode_core.session.meta_db import SessionMetaStore
|
|
193
|
+
store = SessionMetaStore()
|
|
194
|
+
rows = store.list_recent(limit=limit)
|
|
195
|
+
store.close()
|
|
196
|
+
else:
|
|
197
|
+
from crabcode_core.session.storage import SessionStorage
|
|
198
|
+
rows = SessionStorage.list_sessions(work_dir)[:limit]
|
|
199
|
+
rows = [
|
|
200
|
+
{"id": r["session_id"], "cwd": work_dir, **{k: v for k, v in r.items() if k != "session_id"}}
|
|
201
|
+
for r in rows
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
if not rows:
|
|
205
|
+
typer.echo("No sessions found.")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
for i, r in enumerate(rows, 1):
|
|
209
|
+
sid = r.get("id", "")[:8]
|
|
210
|
+
cwd_col = r.get("cwd", "")
|
|
211
|
+
if len(cwd_col) > 30:
|
|
212
|
+
cwd_col = "…" + cwd_col[-29:]
|
|
213
|
+
model = r.get("model", "") or ""
|
|
214
|
+
tokens = r.get("tokens_used", 0)
|
|
215
|
+
tokens_str = f"{tokens // 1000}k" if tokens >= 1000 else str(tokens)
|
|
216
|
+
preview = r.get("title", "") or r.get("first_user_message", "") or r.get("preview", "")
|
|
217
|
+
project_part = f" [{cwd_col}]" if all_projects else ""
|
|
218
|
+
typer.echo(f" {i:>3}. {sid} {model[:16]:<16} {tokens_str:>6} tok{project_part} {preview[:50]}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@sessions_app.command("search")
|
|
222
|
+
def sessions_search(
|
|
223
|
+
query: str = typer.Argument(..., help="Search query"),
|
|
224
|
+
limit: int = typer.Option(20, "--limit", "-n", help="Max results"),
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Search sessions by title or first message."""
|
|
227
|
+
from crabcode_core.session.storage import SessionStorage
|
|
228
|
+
results = SessionStorage.search_sessions(query, limit=limit)
|
|
229
|
+
if not results:
|
|
230
|
+
typer.echo(f"No sessions matching \"{query}\".")
|
|
231
|
+
return
|
|
232
|
+
for i, r in enumerate(results, 1):
|
|
233
|
+
sid = r.get("id", "")[:8]
|
|
234
|
+
cwd_col = r.get("cwd", "")
|
|
235
|
+
if len(cwd_col) > 30:
|
|
236
|
+
cwd_col = "…" + cwd_col[-29:]
|
|
237
|
+
model = r.get("model", "") or ""
|
|
238
|
+
tokens = r.get("tokens_used", 0)
|
|
239
|
+
tokens_str = f"{tokens // 1000}k" if tokens >= 1000 else str(tokens)
|
|
240
|
+
preview = r.get("title", "") or r.get("first_user_message", "")
|
|
241
|
+
typer.echo(f" {i:>3}. {sid} {model[:16]:<16} {tokens_str:>6} tok [{cwd_col}] {preview[:50]}")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@sessions_app.command("export")
|
|
245
|
+
def sessions_export(
|
|
246
|
+
session_id: str = typer.Argument(..., help="Session ID (full or prefix)"),
|
|
247
|
+
fmt: str = typer.Option("md", "--format", "-f", help="Export format: md or json"),
|
|
248
|
+
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
249
|
+
cwd: Optional[str] = typer.Option(None, "--cwd", help="Working directory"),
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Export a session transcript to Markdown or JSON."""
|
|
252
|
+
work_dir = cwd or os.getcwd()
|
|
253
|
+
from crabcode_core.session.export import export_json, export_markdown
|
|
254
|
+
if fmt == "json":
|
|
255
|
+
content = export_json(session_id, work_dir)
|
|
256
|
+
ext = ".json"
|
|
257
|
+
else:
|
|
258
|
+
content = export_markdown(session_id, work_dir)
|
|
259
|
+
ext = ".md"
|
|
260
|
+
out_path = output or os.path.join(work_dir, f"{session_id[:8]}{ext}")
|
|
261
|
+
with open(out_path, "w", encoding="utf-8") as f:
|
|
262
|
+
f.write(content)
|
|
263
|
+
typer.echo(f"Exported to {out_path}")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@sessions_app.command("prune")
|
|
267
|
+
def sessions_prune(
|
|
268
|
+
days: int = typer.Option(30, "--days", "-d", help="Archive sessions older than N days"),
|
|
269
|
+
delete_files: bool = typer.Option(False, "--delete-files", help="Also delete JSONL transcript files"),
|
|
270
|
+
) -> None:
|
|
271
|
+
"""Archive old sessions and optionally delete their files."""
|
|
272
|
+
from crabcode_core.session.meta_db import SessionMetaStore
|
|
273
|
+
store = SessionMetaStore()
|
|
274
|
+
archived = store.auto_archive(days=days)
|
|
275
|
+
typer.echo(f"Archived {archived} session(s) older than {days} days.")
|
|
276
|
+
if delete_files:
|
|
277
|
+
purged = store.purge_archived()
|
|
278
|
+
for entry in purged:
|
|
279
|
+
sid = entry["id"]
|
|
280
|
+
cwd = entry.get("cwd", "")
|
|
281
|
+
if cwd:
|
|
282
|
+
from crabcode_core.session.storage import get_transcript_path
|
|
283
|
+
path = get_transcript_path(cwd, sid)
|
|
284
|
+
try:
|
|
285
|
+
if path.exists():
|
|
286
|
+
path.unlink()
|
|
287
|
+
except OSError:
|
|
288
|
+
pass
|
|
289
|
+
typer.echo(f"Purged {len(purged)} archived session(s) from database and disk.")
|
|
290
|
+
else:
|
|
291
|
+
purged = store.purge_archived()
|
|
292
|
+
typer.echo(f"Purged {len(purged)} archived session(s) from database.")
|
|
293
|
+
store.close()
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@app.command("gateway")
|
|
297
|
+
def gateway(
|
|
298
|
+
port: int = typer.Option(4096, "--port", "-p", help="HTTP port"),
|
|
299
|
+
grpc_port: Optional[int] = typer.Option(None, "--grpc-port", help="gRPC port (optional)"),
|
|
300
|
+
host: str = typer.Option("127.0.0.1", "--host", help="Bind address"),
|
|
301
|
+
password: Optional[str] = typer.Option(None, "--password", help="Basic Auth password"),
|
|
302
|
+
cors: Optional[str] = typer.Option(None, "--cors", help="Allowed CORS origin"),
|
|
303
|
+
log_level: str = typer.Option("info", "--log-level", help="Log level"),
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Start the CrabCode HTTP/gRPC gateway server."""
|
|
306
|
+
from crabcode_core.logging_utils import configure_logging
|
|
307
|
+
from crabcode_core.types.config import CrabCodeSettings, LoggingSettings
|
|
308
|
+
|
|
309
|
+
log_settings = LoggingSettings(level=log_level.upper())
|
|
310
|
+
configure_logging(os.getcwd(), log_settings)
|
|
311
|
+
|
|
312
|
+
cors_origins = [cors] if cors else None
|
|
313
|
+
|
|
314
|
+
from crabcode_gateway.server import run_server
|
|
315
|
+
|
|
316
|
+
run_server(
|
|
317
|
+
host=host,
|
|
318
|
+
port=port,
|
|
319
|
+
grpc_port=grpc_port,
|
|
320
|
+
password=password,
|
|
321
|
+
cors_origins=cors_origins,
|
|
322
|
+
log_level=log_level,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@app.command("acp")
|
|
327
|
+
def acp_cmd(
|
|
328
|
+
port: int = typer.Option(4096, "--port", "-p", help="Internal HTTP server port"),
|
|
329
|
+
host: str = typer.Option("127.0.0.1", "--host", help="Internal HTTP server host"),
|
|
330
|
+
cwd: Optional[str] = typer.Option(None, "--cwd", help="Working directory"),
|
|
331
|
+
log_level: str = typer.Option("warning", "--log-level", help="Log level"),
|
|
332
|
+
) -> None:
|
|
333
|
+
"""Start ACP (Agent Client Protocol) server on stdio.
|
|
334
|
+
|
|
335
|
+
This command starts an internal Gateway HTTP server and then
|
|
336
|
+
runs the ACP agent, communicating with the editor over
|
|
337
|
+
stdin/stdout via JSON-RPC (ndjson).
|
|
338
|
+
|
|
339
|
+
Used by ACP-compatible editors (Zed, JetBrains, Neovim) to
|
|
340
|
+
integrate CrabCode as a coding agent backend.
|
|
341
|
+
|
|
342
|
+
Example Zed settings.json::
|
|
343
|
+
|
|
344
|
+
{
|
|
345
|
+
"agent": {
|
|
346
|
+
"profiles": {
|
|
347
|
+
"crabcode": {
|
|
348
|
+
"command": "crabcode",
|
|
349
|
+
"args": ["acp"]
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
"""
|
|
355
|
+
import asyncio
|
|
356
|
+
|
|
357
|
+
from crabcode_core.logging_utils import configure_logging
|
|
358
|
+
from crabcode_core.types.config import CrabCodeSettings, LoggingSettings
|
|
359
|
+
|
|
360
|
+
work_dir = cwd or os.getcwd()
|
|
361
|
+
log_settings = LoggingSettings(level=log_level.upper())
|
|
362
|
+
configure_logging(work_dir, log_settings)
|
|
363
|
+
|
|
364
|
+
async def _run() -> None:
|
|
365
|
+
# 1. Start internal Gateway HTTP server
|
|
366
|
+
from crabcode_gateway.server import GatewayServer
|
|
367
|
+
|
|
368
|
+
server = GatewayServer(host=host, port=port, log_level=log_level)
|
|
369
|
+
await server.start_background()
|
|
370
|
+
|
|
371
|
+
# Wait for server to be ready
|
|
372
|
+
import httpx
|
|
373
|
+
async with httpx.AsyncClient() as health_client:
|
|
374
|
+
for _ in range(20):
|
|
375
|
+
try:
|
|
376
|
+
resp = await health_client.get(f"http://{host}:{port}/health")
|
|
377
|
+
if resp.status_code == 200:
|
|
378
|
+
break
|
|
379
|
+
except Exception:
|
|
380
|
+
pass
|
|
381
|
+
await asyncio.sleep(0.25)
|
|
382
|
+
|
|
383
|
+
# 2. Run ACP agent on stdio
|
|
384
|
+
from crabcode_gateway.acp.transport import run_acp_server
|
|
385
|
+
from crabcode_gateway.acp.types import ACPConfig
|
|
386
|
+
|
|
387
|
+
config = ACPConfig(base_url=f"http://{host}:{port}")
|
|
388
|
+
try:
|
|
389
|
+
await run_acp_server(config)
|
|
390
|
+
finally:
|
|
391
|
+
await server.stop()
|
|
392
|
+
|
|
393
|
+
asyncio.run(_run())
|
|
394
|
+
os._exit(0)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@app.command("stats")
|
|
398
|
+
def stats(
|
|
399
|
+
project: bool = typer.Option(False, "--project", "-p", help="Show only current project stats"),
|
|
400
|
+
cwd: Optional[str] = typer.Option(None, "--cwd", help="Working directory"),
|
|
401
|
+
) -> None:
|
|
402
|
+
"""Show usage statistics."""
|
|
403
|
+
work_dir = os.path.abspath(cwd or os.getcwd())
|
|
404
|
+
from crabcode_core.session.meta_db import SessionMetaStore
|
|
405
|
+
store = SessionMetaStore()
|
|
406
|
+
|
|
407
|
+
def _fmt_tok(n: int) -> str:
|
|
408
|
+
if n >= 1_000_000:
|
|
409
|
+
return f"{n / 1_000_000:.1f}M"
|
|
410
|
+
if n >= 1_000:
|
|
411
|
+
return f"{n / 1_000:.1f}k"
|
|
412
|
+
return str(n)
|
|
413
|
+
|
|
414
|
+
if project:
|
|
415
|
+
p = store.stats_by_project(work_dir)
|
|
416
|
+
typer.echo(f"Project: {work_dir}")
|
|
417
|
+
typer.echo(f" Sessions: {p['total_sessions']} | Tokens: {_fmt_tok(p['total_tokens'])} | Messages: {p['total_messages']}")
|
|
418
|
+
else:
|
|
419
|
+
g = store.stats_global()
|
|
420
|
+
p = store.stats_by_project(work_dir)
|
|
421
|
+
models = store.stats_by_model(limit=5)
|
|
422
|
+
typer.echo(f"Global: {g['total_sessions']} sessions | {_fmt_tok(g['total_tokens'])} tokens | {g['active_projects']} projects")
|
|
423
|
+
typer.echo(f"This week: {g['week_sessions']} sessions | {_fmt_tok(g['week_tokens'])} tokens")
|
|
424
|
+
typer.echo(f"This project: {p['total_sessions']} sessions | {_fmt_tok(p['total_tokens'])} tokens | {p['total_messages']} messages")
|
|
425
|
+
if models:
|
|
426
|
+
model_parts = [f"{m['model']} ({_fmt_tok(m['tokens'])})" for m in models]
|
|
427
|
+
typer.echo(f"Top models: {', '.join(model_parts)}")
|
|
428
|
+
store.close()
|
|
429
|
+
|
|
430
|
+
|
|
137
431
|
def entry() -> None:
|
|
432
|
+
known_subcommands = {"main", "sessions", "stats", "gateway", "acp"}
|
|
433
|
+
args = sys.argv[1:]
|
|
434
|
+
# Preserve root --help so users can still discover all subcommands.
|
|
435
|
+
if args and args[0] in ("--help", "-h"):
|
|
436
|
+
app()
|
|
437
|
+
return
|
|
438
|
+
# If the first positional argument is not a known subcommand, default to main.
|
|
439
|
+
first_positional = next((a for a in args if not a.startswith("-")), None)
|
|
440
|
+
if first_positional not in known_subcommands:
|
|
441
|
+
sys.argv.insert(1, "main")
|
|
138
442
|
app()
|
|
139
443
|
|
|
140
444
|
|