Refactor devcontainer and MCP tools for containerized development

Ajout et configuration d’un environnement de développement conteneurisé (devcontainer) avec support multi-langages (Go, Rust, Python), intégration d’outils LLM locaux/cloud (LM Studio, Claude Pro), et refonte des outils MCP : suppression du module sandbox, correction de la gestion des chemins projet dans jj.py, ajout d’une fonction jj_new_tool, et structuration du Makefile pour le build multiplateforme. Mise à jour des scripts (docker-entrypoint.sh, setup-devcontainer.sh), du README.md et de bin/jj-ai-commit.sh pour automatiser la génération de messages de commit à partir des résumés des fichiers modifiés.
This commit is contained in:
2026-03-11 16:34:09 +01:00
parent 1c9cee35df
commit d6fb4e33e5
30 changed files with 3502 additions and 193 deletions

0
.builder-ready Normal file
View File

View File

@@ -0,0 +1,95 @@
{
"cachedGrowthBookFeatures": {
"tengu_claudeai_mcp_connectors": true,
"tengu_event_sampling_config": {},
"tengu_frond_boric": {},
"tengu_log_segment_events": false,
"tengu_log_datadog_events": true,
"tengu_marble_anvil": true,
"tengu_tool_pear": false,
"tengu_scarf_coffee": false,
"tengu_herring_clock": false,
"tengu_keybinding_customization_release": true,
"tengu_marble_whisper": true,
"tengu_amber_quartz": false,
"tengu_thinkback": false,
"tengu_cicada_nap_ms": 0,
"tengu_miraculo_the_bard": false,
"tengu_miraculo_the_bard2": false,
"tengu_paper_halyard": false,
"tengu_chomp_inflection": false,
"tengu_marble_sandcastle": true,
"tengu_desktop_upsell": {
"enable_shortcut_tip": false,
"enable_startup_dialog": false
},
"tengu_marble_lantern_disabled": false,
"tengu_pid_based_version_locking": true,
"tengu_mcp_elicitation": false,
"tengu_kairos_cron": true,
"tengu_pebble_leaf_prune": false,
"tengu_swinburne_dune": false,
"tengu_coral_fern": false,
"tengu_basalt_3kr": false,
"tengu_summarize_tool_results": true,
"tengu_bergotte_lantern": false,
"tengu_sotto_voce": true,
"tengu_system_prompt_global_cache": true,
"tengu_turtle_carbon": true,
"tengu_glacier_2xr": false,
"enhanced_telemetry_beta": false,
"tengu_streaming_tool_execution2": true,
"tengu_moth_copse": false,
"tengu_tool_search_unsupported_models": [
"haiku"
],
"tengu_defer_all_bn4": false,
"tengu_fgts": false,
"tengu_tst_hint_m7r": false,
"tengu_attribution_header": true,
"tengu_prompt_cache_1h_config": {
"allowlist": [
"repl_main_thread*",
"sdk",
"auto_mode"
]
},
"tengu_disable_streaming_to_non_streaming_fallback": false,
"tengu_tool_input_aliasing": false,
"tengu_satin_quoll": {},
"tengu_amber_prism": false,
"tengu_tight_weave": true,
"tengu_auto_background_agents": false
},
"firstStartTime": "2026-03-11T12:56:06.706Z",
"opusProMigrationComplete": true,
"sonnet1m45MigrationComplete": true,
"userID": "2525622ce171b408689c31f61f66ba3433629fa13b0656ed565073ba5f6dc2c6",
"clientDataCache": {
"data": {},
"timestamp": 1773233813304
},
"changelogLastFetched": 1773233777137,
"oauthAccount": {
"accountUuid": "f093e810-c7e5-4d6b-902b-ec1fa73cbc94",
"emailAddress": "eric.coissac@gmail.com",
"organizationUuid": "c5e61747-337f-4fed-bf22-ca9925e92650",
"hasExtraUsageEnabled": true,
"billingType": "stripe_subscription",
"accountCreatedAt": "2025-07-29T14:22:00.696404Z",
"subscriptionCreatedAt": "2025-10-02T16:14:05.694173Z",
"displayName": "Eric",
"organizationRole": "admin",
"workspaceRole": null,
"organizationName": "eric.coissac@gmail.com's Organization"
},
"claudeCodeFirstTokenDate": "2025-10-05T14:58:34.301962Z",
"penguinModeOrgEnabled": true,
"groveConfigCache": {
"f093e810-c7e5-4d6b-902b-ec1fa73cbc94": {
"grove_enabled": true,
"timestamp": 1773233813326
}
},
"cachedExtraUsageDisabledReason": "out_of_credits"
}

View File

@@ -0,0 +1 @@
{"claudeAiOauth":{"accessToken":"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","refreshToken":"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","expiresAt":1773262609887,"scopes":["user:inference","user:mcp_servers","user:profile","user:sessions:claude_code"],"subscriptionType":"pro","rateLimitTier":"default_claude_ai"}}

View File

@@ -0,0 +1,3 @@
{
"firstStartTime": "2026-03-11T12:56:06.706Z"
}

View File

@@ -0,0 +1,58 @@
{
"cachedGrowthBookFeatures": {
"tengu_claudeai_mcp_connectors": true,
"tengu_event_sampling_config": {},
"tengu_frond_boric": {},
"tengu_log_segment_events": false,
"tengu_log_datadog_events": true,
"tengu_marble_anvil": true,
"tengu_tool_pear": false,
"tengu_scarf_coffee": false,
"tengu_herring_clock": false,
"tengu_keybinding_customization_release": true,
"tengu_marble_whisper": true,
"tengu_amber_quartz": false,
"tengu_thinkback": false,
"tengu_cicada_nap_ms": 0,
"tengu_miraculo_the_bard": false,
"tengu_miraculo_the_bard2": false,
"tengu_paper_halyard": false,
"tengu_chomp_inflection": false,
"tengu_marble_sandcastle": true,
"tengu_desktop_upsell": {
"enable_shortcut_tip": false,
"enable_startup_dialog": false
},
"tengu_marble_lantern_disabled": false
},
"firstStartTime": "2026-03-11T12:56:06.706Z",
"opusProMigrationComplete": true,
"sonnet1m45MigrationComplete": true,
"userID": "2525622ce171b408689c31f61f66ba3433629fa13b0656ed565073ba5f6dc2c6",
"clientDataCache": {
"data": {},
"timestamp": 1773233813304
},
"changelogLastFetched": 1773233777137,
"oauthAccount": {
"accountUuid": "f093e810-c7e5-4d6b-902b-ec1fa73cbc94",
"emailAddress": "eric.coissac@gmail.com",
"organizationUuid": "c5e61747-337f-4fed-bf22-ca9925e92650",
"hasExtraUsageEnabled": true,
"billingType": "stripe_subscription",
"accountCreatedAt": "2025-07-29T14:22:00.696404Z",
"subscriptionCreatedAt": "2025-10-02T16:14:05.694173Z",
"displayName": "Eric",
"organizationRole": "admin",
"workspaceRole": null,
"organizationName": "eric.coissac@gmail.com's Organization"
},
"claudeCodeFirstTokenDate": "2025-10-05T14:58:34.301962Z",
"penguinModeOrgEnabled": true,
"groveConfigCache": {
"f093e810-c7e5-4d6b-902b-ec1fa73cbc94": {
"grove_enabled": true,
"timestamp": 1773233813326
}
}
}

View File

@@ -0,0 +1,59 @@
{
"cachedGrowthBookFeatures": {
"tengu_claudeai_mcp_connectors": true,
"tengu_event_sampling_config": {},
"tengu_frond_boric": {},
"tengu_log_segment_events": false,
"tengu_log_datadog_events": true,
"tengu_marble_anvil": true,
"tengu_tool_pear": false,
"tengu_scarf_coffee": false,
"tengu_herring_clock": false,
"tengu_keybinding_customization_release": true,
"tengu_marble_whisper": true,
"tengu_amber_quartz": false,
"tengu_thinkback": false,
"tengu_cicada_nap_ms": 0,
"tengu_miraculo_the_bard": false,
"tengu_miraculo_the_bard2": false,
"tengu_paper_halyard": false,
"tengu_chomp_inflection": false,
"tengu_marble_sandcastle": true,
"tengu_desktop_upsell": {
"enable_shortcut_tip": false,
"enable_startup_dialog": false
},
"tengu_marble_lantern_disabled": false,
"tengu_pid_based_version_locking": true
},
"firstStartTime": "2026-03-11T12:56:06.706Z",
"opusProMigrationComplete": true,
"sonnet1m45MigrationComplete": true,
"userID": "2525622ce171b408689c31f61f66ba3433629fa13b0656ed565073ba5f6dc2c6",
"clientDataCache": {
"data": {},
"timestamp": 1773233813304
},
"changelogLastFetched": 1773233777137,
"oauthAccount": {
"accountUuid": "f093e810-c7e5-4d6b-902b-ec1fa73cbc94",
"emailAddress": "eric.coissac@gmail.com",
"organizationUuid": "c5e61747-337f-4fed-bf22-ca9925e92650",
"hasExtraUsageEnabled": true,
"billingType": "stripe_subscription",
"accountCreatedAt": "2025-07-29T14:22:00.696404Z",
"subscriptionCreatedAt": "2025-10-02T16:14:05.694173Z",
"displayName": "Eric",
"organizationRole": "admin",
"workspaceRole": null,
"organizationName": "eric.coissac@gmail.com's Organization"
},
"claudeCodeFirstTokenDate": "2025-10-05T14:58:34.301962Z",
"penguinModeOrgEnabled": true,
"groveConfigCache": {
"f093e810-c7e5-4d6b-902b-ec1fa73cbc94": {
"grove_enabled": true,
"timestamp": 1773233813326
}
}
}

View File

@@ -0,0 +1,92 @@
{
"cachedGrowthBookFeatures": {
"tengu_claudeai_mcp_connectors": true,
"tengu_event_sampling_config": {},
"tengu_frond_boric": {},
"tengu_log_segment_events": false,
"tengu_log_datadog_events": true,
"tengu_marble_anvil": true,
"tengu_tool_pear": false,
"tengu_scarf_coffee": false,
"tengu_herring_clock": false,
"tengu_keybinding_customization_release": true,
"tengu_marble_whisper": true,
"tengu_amber_quartz": false,
"tengu_thinkback": false,
"tengu_cicada_nap_ms": 0,
"tengu_miraculo_the_bard": false,
"tengu_miraculo_the_bard2": false,
"tengu_paper_halyard": false,
"tengu_chomp_inflection": false,
"tengu_marble_sandcastle": true,
"tengu_desktop_upsell": {
"enable_shortcut_tip": false,
"enable_startup_dialog": false
},
"tengu_marble_lantern_disabled": false,
"tengu_pid_based_version_locking": true,
"tengu_mcp_elicitation": false,
"tengu_kairos_cron": true,
"tengu_pebble_leaf_prune": false,
"tengu_swinburne_dune": false,
"tengu_coral_fern": false,
"tengu_basalt_3kr": false,
"tengu_summarize_tool_results": true,
"tengu_bergotte_lantern": false,
"tengu_sotto_voce": true,
"tengu_system_prompt_global_cache": true,
"tengu_turtle_carbon": true,
"tengu_glacier_2xr": false,
"enhanced_telemetry_beta": false,
"tengu_streaming_tool_execution2": true,
"tengu_moth_copse": false,
"tengu_tool_search_unsupported_models": [
"haiku"
],
"tengu_defer_all_bn4": false,
"tengu_fgts": false,
"tengu_tst_hint_m7r": false,
"tengu_attribution_header": true,
"tengu_prompt_cache_1h_config": {
"allowlist": [
"repl_main_thread*",
"sdk",
"auto_mode"
]
},
"tengu_disable_streaming_to_non_streaming_fallback": false,
"tengu_tool_input_aliasing": false,
"tengu_satin_quoll": {}
},
"firstStartTime": "2026-03-11T12:56:06.706Z",
"opusProMigrationComplete": true,
"sonnet1m45MigrationComplete": true,
"userID": "2525622ce171b408689c31f61f66ba3433629fa13b0656ed565073ba5f6dc2c6",
"clientDataCache": {
"data": {},
"timestamp": 1773233813304
},
"changelogLastFetched": 1773233777137,
"oauthAccount": {
"accountUuid": "f093e810-c7e5-4d6b-902b-ec1fa73cbc94",
"emailAddress": "eric.coissac@gmail.com",
"organizationUuid": "c5e61747-337f-4fed-bf22-ca9925e92650",
"hasExtraUsageEnabled": true,
"billingType": "stripe_subscription",
"accountCreatedAt": "2025-07-29T14:22:00.696404Z",
"subscriptionCreatedAt": "2025-10-02T16:14:05.694173Z",
"displayName": "Eric",
"organizationRole": "admin",
"workspaceRole": null,
"organizationName": "eric.coissac@gmail.com's Organization"
},
"claudeCodeFirstTokenDate": "2025-10-05T14:58:34.301962Z",
"penguinModeOrgEnabled": true,
"groveConfigCache": {
"f093e810-c7e5-4d6b-902b-ec1fa73cbc94": {
"grove_enabled": true,
"timestamp": 1773233813326
}
},
"cachedExtraUsageDisabledReason": "out_of_credits"
}

View File

@@ -0,0 +1,93 @@
{
"cachedGrowthBookFeatures": {
"tengu_claudeai_mcp_connectors": true,
"tengu_event_sampling_config": {},
"tengu_frond_boric": {},
"tengu_log_segment_events": false,
"tengu_log_datadog_events": true,
"tengu_marble_anvil": true,
"tengu_tool_pear": false,
"tengu_scarf_coffee": false,
"tengu_herring_clock": false,
"tengu_keybinding_customization_release": true,
"tengu_marble_whisper": true,
"tengu_amber_quartz": false,
"tengu_thinkback": false,
"tengu_cicada_nap_ms": 0,
"tengu_miraculo_the_bard": false,
"tengu_miraculo_the_bard2": false,
"tengu_paper_halyard": false,
"tengu_chomp_inflection": false,
"tengu_marble_sandcastle": true,
"tengu_desktop_upsell": {
"enable_shortcut_tip": false,
"enable_startup_dialog": false
},
"tengu_marble_lantern_disabled": false,
"tengu_pid_based_version_locking": true,
"tengu_mcp_elicitation": false,
"tengu_kairos_cron": true,
"tengu_pebble_leaf_prune": false,
"tengu_swinburne_dune": false,
"tengu_coral_fern": false,
"tengu_basalt_3kr": false,
"tengu_summarize_tool_results": true,
"tengu_bergotte_lantern": false,
"tengu_sotto_voce": true,
"tengu_system_prompt_global_cache": true,
"tengu_turtle_carbon": true,
"tengu_glacier_2xr": false,
"enhanced_telemetry_beta": false,
"tengu_streaming_tool_execution2": true,
"tengu_moth_copse": false,
"tengu_tool_search_unsupported_models": [
"haiku"
],
"tengu_defer_all_bn4": false,
"tengu_fgts": false,
"tengu_tst_hint_m7r": false,
"tengu_attribution_header": true,
"tengu_prompt_cache_1h_config": {
"allowlist": [
"repl_main_thread*",
"sdk",
"auto_mode"
]
},
"tengu_disable_streaming_to_non_streaming_fallback": false,
"tengu_tool_input_aliasing": false,
"tengu_satin_quoll": {},
"tengu_amber_prism": false
},
"firstStartTime": "2026-03-11T12:56:06.706Z",
"opusProMigrationComplete": true,
"sonnet1m45MigrationComplete": true,
"userID": "2525622ce171b408689c31f61f66ba3433629fa13b0656ed565073ba5f6dc2c6",
"clientDataCache": {
"data": {},
"timestamp": 1773233813304
},
"changelogLastFetched": 1773233777137,
"oauthAccount": {
"accountUuid": "f093e810-c7e5-4d6b-902b-ec1fa73cbc94",
"emailAddress": "eric.coissac@gmail.com",
"organizationUuid": "c5e61747-337f-4fed-bf22-ca9925e92650",
"hasExtraUsageEnabled": true,
"billingType": "stripe_subscription",
"accountCreatedAt": "2025-07-29T14:22:00.696404Z",
"subscriptionCreatedAt": "2025-10-02T16:14:05.694173Z",
"displayName": "Eric",
"organizationRole": "admin",
"workspaceRole": null,
"organizationName": "eric.coissac@gmail.com's Organization"
},
"claudeCodeFirstTokenDate": "2025-10-05T14:58:34.301962Z",
"penguinModeOrgEnabled": true,
"groveConfigCache": {
"f093e810-c7e5-4d6b-902b-ec1fa73cbc94": {
"grove_enabled": true,
"timestamp": 1773233813326
}
},
"cachedExtraUsageDisabledReason": "out_of_credits"
}

2186
.claude-devcontainer/cache/changelog.md vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"fetchedAt": "2026-03-11T12:56:07.264Z",
"plugins": [
{
"plugin": "code-review@claude-plugins-official",
"added_at": "2026-02-11T03:16:31.424Z",
"reason": "just-a-test",
"text": "This is a test #5"
},
{
"plugin": "fizz@testmkt-marketplace",
"added_at": "2026-02-12T00:00:00.000Z",
"reason": "security",
"text": "this is a security test"
}
]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
{"parentUuid":null,"isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":"Search for documentation about orla (github.com/dorcha-inc/orla) configuration, specifically:\n1. How to configure llm_backend for OpenAI-compatible API\n2. What config file format orla uses (YAML, TOML, JSON?)\n3. Where the config file should be placed (~/.orla, ~/.config/orla, orla.yaml, etc.)\n4. How to use a custom OpenAI-compatible endpoint with LM Studio\n\nLook at the source code at /usr/local/go/pkg/mod/github.com/dorcha-inc/ or any cached Go module. Also check if there are any orla config files in the system."},"uuid":"2f88d857-b27c-4a04-8199-33af4af15e10","timestamp":"2026-03-11T13:44:25.806Z"}
{"parentUuid":"2f88d857-b27c-4a04-8199-33af4af15e10","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01WRMXsZiGrk8P27RVrsVz5J","type":"message","role":"assistant","content":[{"type":"text","text":"I'll search for orla documentation and configuration information. Let me start by exploring the system for orla-related files and checking the Go modules."}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":12529,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":12529,"ephemeral_1h_input_tokens":0},"output_tokens":5,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CYwPjgdkNcUV5hmMaieH8","type":"assistant","uuid":"453a074b-95c7-40a7-92d9-75c1f60871b6","timestamp":"2026-03-11T13:44:27.428Z"}
{"parentUuid":"453a074b-95c7-40a7-92d9-75c1f60871b6","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01WRMXsZiGrk8P27RVrsVz5J","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01MFu86Ver8CeFKTXEJ3oRxP","name":"Bash","input":{"command":"find /usr/local/go/pkg/mod/github.com/dorcha-inc -type f -name \"*.md\" -o -name \"*.go\" -o -name \"*.yaml\" -o -name \"*.yml\" -o -name \"*.toml\" -o -name \"*.json\" 2>/dev/null | head -30"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":12529,"cache_read_input_tokens":0,"output_tokens":162,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":12529},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CYwPjgdkNcUV5hmMaieH8","type":"assistant","uuid":"cbb729b2-0cda-4dd6-be31-a70566af412d","timestamp":"2026-03-11T13:44:27.776Z"}
{"parentUuid":"cbb729b2-0cda-4dd6-be31-a70566af412d","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"progress","data":{"type":"hook_progress","hookEvent":"PostToolUse","hookName":"PostToolUse:Bash","command":"callback"},"parentToolUseID":"toolu_01MFu86Ver8CeFKTXEJ3oRxP","toolUseID":"toolu_01MFu86Ver8CeFKTXEJ3oRxP","timestamp":"2026-03-11T13:44:28.915Z","uuid":"7d01d136-c1d7-4bda-b83a-0dfbe3cdd8dd"}
{"parentUuid":"cbb729b2-0cda-4dd6-be31-a70566af412d","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01MFu86Ver8CeFKTXEJ3oRxP","type":"tool_result","content":"/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.githooks/README.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.github/FUNDING.yml\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.github/workflows/build.yml\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.github/workflows/release.yml\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.gitleaks.toml\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.golangci.yml\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.goreleaser.yaml\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.vscode/extensions.json\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.vscode/settings.json\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/CHANGELOG.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/CODE_OF_CONDUCT.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/CONTRIBUTING.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/CONTRIBUTORS.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/MAINTAINERS.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/README.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/SECURITY.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla-test/README.md\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla-test/call_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla-test/init_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla-test/main.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla-test/validate.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/agent_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/cache_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/daemon_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/main.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/main_test.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/serve_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/tool_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/tool_info_cmd.go\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/cmd/orla/tool_install_cmd.go","is_error":false}]},"uuid":"3595f29e-b29e-47b5-9038-fb279612d521","timestamp":"2026-03-11T13:44:28.915Z","sourceToolAssistantUUID":"cbb729b2-0cda-4dd6-be31-a70566af412d"}
{"parentUuid":"3595f29e-b29e-47b5-9038-fb279612d521","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01MCbhjbsfVEBevEHCJiDfTX","type":"message","role":"assistant","content":[{"type":"text","text":"Let me search for README and configuration files more specifically:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":5,"cache_creation_input_tokens":1368,"cache_read_input_tokens":12529,"cache_creation":{"ephemeral_5m_input_tokens":1368,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CYwPjuSgNzwbFmNS5c1wn","type":"assistant","uuid":"2e05d35b-a758-4dad-8f40-18c7bd896100","timestamp":"2026-03-11T13:44:29.770Z"}
{"parentUuid":"2e05d35b-a758-4dad-8f40-18c7bd896100","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01MCbhjbsfVEBevEHCJiDfTX","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_014z1XCyo2WK9cACqxC5kMoE","name":"Read","input":{"file_path":"/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/README.md"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":5,"cache_creation_input_tokens":1368,"cache_read_input_tokens":12529,"cache_creation":{"ephemeral_5m_input_tokens":1368,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CYwPjuSgNzwbFmNS5c1wn","type":"assistant","uuid":"d655eebd-77b7-4463-b9bf-e94dd7f4d93a","timestamp":"2026-03-11T13:44:30.278Z"}
{"parentUuid":"d655eebd-77b7-4463-b9bf-e94dd7f4d93a","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"Tool permission request failed: Error: Tool use aborted","is_error":true,"tool_use_id":"toolu_014z1XCyo2WK9cACqxC5kMoE"}]},"uuid":"71cbb790-8980-4dbe-874e-8b4278bc8761","timestamp":"2026-03-11T13:44:30.339Z","toolUseResult":"Error: Tool permission request failed: Error: Tool use aborted","sourceToolAssistantUUID":"d655eebd-77b7-4463-b9bf-e94dd7f4d93a"}
{"parentUuid":"71cbb790-8980-4dbe-874e-8b4278bc8761","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01MCbhjbsfVEBevEHCJiDfTX","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01MkzKb5UxgTwtSWVYk1fY6A","name":"Bash","input":{"command":"find /usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1 -type f \\( -name \"*.md\" -o -name \"config*\" \\) | grep -i config"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":5,"cache_creation_input_tokens":1368,"cache_read_input_tokens":12529,"cache_creation":{"ephemeral_5m_input_tokens":1368,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CYwPjuSgNzwbFmNS5c1wn","type":"assistant","uuid":"95a62859-af65-409b-945f-afe0cc9859f9","timestamp":"2026-03-11T13:44:31.180Z"}
{"parentUuid":"95a62859-af65-409b-945f-afe0cc9859f9","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"<tool_use_error>Cancelled: parallel tool call Read(/usr/local/go/pkg/mod/github.com/dorcha-…) errored</tool_use_error>","is_error":true,"tool_use_id":"toolu_01MkzKb5UxgTwtSWVYk1fY6A"}]},"uuid":"1cb9f541-7610-4371-8465-6a5e9f318ef4","timestamp":"2026-03-11T13:44:31.186Z","toolUseResult":"Cancelled: parallel tool call Read(/usr/local/go/pkg/mod/github.com/dorcha-…) errored","sourceToolAssistantUUID":"95a62859-af65-409b-945f-afe0cc9859f9"}
{"parentUuid":"1cb9f541-7610-4371-8465-6a5e9f318ef4","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01MCbhjbsfVEBevEHCJiDfTX","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01E5HuDmbXkhQ76W7LmjsHoZ","name":"Bash","input":{"command":"find /usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1 -type f -name \"*.go\" | xargs grep -l \"llm_backend\\|config\" | head -10"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":5,"cache_creation_input_tokens":1368,"cache_read_input_tokens":12529,"output_tokens":281,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":1368},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CYwPjuSgNzwbFmNS5c1wn","type":"assistant","uuid":"186d25b8-2bac-41d7-9c44-ae85f9532513","timestamp":"2026-03-11T13:44:31.842Z"}
{"parentUuid":"186d25b8-2bac-41d7-9c44-ae85f9532513","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"<tool_use_error>Cancelled: parallel tool call Read(/usr/local/go/pkg/mod/github.com/dorcha-…) errored</tool_use_error>","is_error":true,"tool_use_id":"toolu_01E5HuDmbXkhQ76W7LmjsHoZ"}]},"uuid":"4101ff98-02cd-498f-bb79-d2036b317c8a","timestamp":"2026-03-11T13:44:31.848Z","toolUseResult":"Cancelled: parallel tool call Read(/usr/local/go/pkg/mod/github.com/dorcha-…) errored","sourceToolAssistantUUID":"186d25b8-2bac-41d7-9c44-ae85f9532513"}
{"parentUuid":"4101ff98-02cd-498f-bb79-d2036b317c8a","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01PAGPw45scRgRLysCoMGxr2","type":"message","role":"assistant","content":[{"type":"text","text":"Let me try a different approach:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":6,"cache_creation_input_tokens":493,"cache_read_input_tokens":13897,"cache_creation":{"ephemeral_5m_input_tokens":493,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CYwPk7s3a5xGfh79ZPxqE","type":"assistant","uuid":"9ea694cd-a2a3-498c-a878-baa49c58000c","timestamp":"2026-03-11T13:44:32.688Z"}
{"parentUuid":"9ea694cd-a2a3-498c-a878-baa49c58000c","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01PAGPw45scRgRLysCoMGxr2","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01XqVdyDYPWtWbH29c9Pmtcz","name":"Bash","input":{"command":"ls -la /usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":6,"cache_creation_input_tokens":493,"cache_read_input_tokens":13897,"cache_creation":{"ephemeral_5m_input_tokens":493,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CYwPk7s3a5xGfh79ZPxqE","type":"assistant","uuid":"bbb0b718-7ea9-46f5-9740-7c1c6722381d","timestamp":"2026-03-11T13:44:33.023Z"}
{"parentUuid":"bbb0b718-7ea9-46f5-9740-7c1c6722381d","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01PAGPw45scRgRLysCoMGxr2","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_016nzp7WpZDqLwFYi4Z84Z3P","name":"Bash","input":{"command":"grep -r \"llm_backend\" /usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1 2>/dev/null | head -20"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":6,"cache_creation_input_tokens":493,"cache_read_input_tokens":13897,"output_tokens":174,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":493},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CYwPk7s3a5xGfh79ZPxqE","type":"assistant","uuid":"ec4cdee6-b0aa-4e89-a458-d70299f1c358","timestamp":"2026-03-11T13:44:33.250Z"}
{"parentUuid":"ec4cdee6-b0aa-4e89-a458-d70299f1c358","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"progress","data":{"type":"hook_progress","hookEvent":"PostToolUse","hookName":"PostToolUse:Bash","command":"callback"},"parentToolUseID":"toolu_016nzp7WpZDqLwFYi4Z84Z3P","toolUseID":"toolu_016nzp7WpZDqLwFYi4Z84Z3P","timestamp":"2026-03-11T13:44:36.191Z","uuid":"ed1cd086-1858-42e6-a33c-6695935a9e29"}
{"parentUuid":"ec4cdee6-b0aa-4e89-a458-d70299f1c358","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_016nzp7WpZDqLwFYi4Z84Z3P","type":"tool_result","content":"/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.github/workflows/release.yml: - name: Test with llm_backend configuration\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/.github/workflows/release.yml: - name: Test with llm_backend configuration\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/README.md:After installation, configure Orla to use your remote Ollama server by setting either the `OLLAMA_HOST` or the `ORLA_OLLAMA_HOST` environment variable, or using the `llm_backend` configuration in your `orla.yaml`:\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/README.md:llm_backend:\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/config/config.go:\tLLMBackend *core.LLMBackend `yaml:\"llm_backend,omitempty\" mapstructure:\"llm_backend\"` // LLM backend configuration (endpoint, type, api_key)\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama.go:// 1. llm_backend.endpoint config (if llm_backend.type is \"ollama\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama.go:\t// Check llm_backend config first\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama.go:\t\t\treturn \"\", fmt.Errorf(\"llm_backend.endpoint is required if llm_backend is set\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama.go:\t\t\treturn \"\", fmt.Errorf(\"llm_backend.type is required if llm_backend is set\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama.go:\t\t\treturn \"\", fmt.Errorf(\"[BUG] llm_backend.type must be %s, got '%s': we should not be using this function for non-ollama inference servers\", core.LLMInferenceAPITypeOllama, cfg.LLMBackend.Type)\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama_integration_test.go:// using llm_backend configuration. This test uses testcontainers to spin up an Ollama container\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama_integration_test.go:\t// Test with llm_backend configuration pointing to the container\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama_test.go:// Tests for getOllamaEndpoint with llm_backend configuration\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama_test.go:\tassert.Contains(t, err.Error(), \"llm_backend.endpoint is required\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama_test.go:\tassert.Contains(t, err.Error(), \"llm_backend.type is required\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/ollama_test.go:\tassert.Contains(t, err.Error(), \"llm_backend.type must be\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/openai.go:\t\treturn \"\", \"\", fmt.Errorf(\"llm_backend configuration is required for OpenAI-compatible API\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/openai.go:\t\treturn \"\", \"\", fmt.Errorf(\"llm_backend.endpoint is required\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/openai.go:\t\treturn \"\", \"\", fmt.Errorf(\"llm_backend.type is required if llm_backend is set\")\n/usr/local/go/pkg/mod/github.com/dorcha-inc/orla@v1.1.1/internal/model/openai.go:\t\treturn \"\", \"\", fmt.Errorf(\"[BUG] llm_backend.type must be %s, got '%s': we should not be using this function for non-openai inference servers\", core.LLMInferenceAPITypeOpenAI, cfg.LLMBackend.Type)","is_error":false}]},"uuid":"5992985f-2797-46aa-b0de-b01c601b9412","timestamp":"2026-03-11T13:44:36.191Z","sourceToolAssistantUUID":"ec4cdee6-b0aa-4e89-a458-d70299f1c358"}
{"parentUuid":"bbb0b718-7ea9-46f5-9740-7c1c6722381d","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.","is_error":true,"tool_use_id":"toolu_01XqVdyDYPWtWbH29c9Pmtcz"}]},"uuid":"93dc9304-3433-4bb3-9677-0cacff2838d8","timestamp":"2026-03-11T13:48:40.019Z","toolUseResult":"User rejected tool use","sourceToolAssistantUUID":"bbb0b718-7ea9-46f5-9740-7c1c6722381d"}
{"parentUuid":"93dc9304-3433-4bb3-9677-0cacff2838d8","isSidechain":true,"userType":"external","cwd":"/workspace","sessionId":"33cd9dcf-39f6-440b-a49f-a568379ee676","version":"2.1.71","gitBranch":"HEAD","agentId":"a5343c3e31ff952a3","slug":"rippling-frolicking-kettle","type":"user","message":{"role":"user","content":[{"type":"text","text":"[Request interrupted by user for tool use]"}]},"uuid":"9a098ad5-598c-4309-839f-6edd999922b8","timestamp":"2026-03-11T13:48:40.020Z"}

View File

@@ -0,0 +1,74 @@
# Snapshot file
# Unset all aliases to avoid conflicts with functions
unalias -a 2>/dev/null || true
# Functions
# Shell Options
shopt -u autocd
shopt -u assoc_expand_once
shopt -u cdable_vars
shopt -u cdspell
shopt -u checkhash
shopt -u checkjobs
shopt -s checkwinsize
shopt -s cmdhist
shopt -u compat31
shopt -u compat32
shopt -u compat40
shopt -u compat41
shopt -u compat42
shopt -u compat43
shopt -u compat44
shopt -s complete_fullquote
shopt -u direxpand
shopt -u dirspell
shopt -u dotglob
shopt -u execfail
shopt -u expand_aliases
shopt -u extdebug
shopt -u extglob
shopt -s extquote
shopt -u failglob
shopt -s force_fignore
shopt -s globasciiranges
shopt -s globskipdots
shopt -u globstar
shopt -u gnu_errfmt
shopt -u histappend
shopt -u histreedit
shopt -u histverify
shopt -s hostcomplete
shopt -u huponexit
shopt -u inherit_errexit
shopt -s interactive_comments
shopt -u lastpipe
shopt -u lithist
shopt -u localvar_inherit
shopt -u localvar_unset
shopt -s login_shell
shopt -u mailwarn
shopt -u no_empty_cmd_completion
shopt -u nocaseglob
shopt -u nocasematch
shopt -u noexpand_translation
shopt -u nullglob
shopt -s patsub_replacement
shopt -s progcomp
shopt -u progcomp_alias
shopt -s promptvars
shopt -u restricted_shell
shopt -u shift_verbose
shopt -s sourcepath
shopt -u varredir_close
shopt -u xpg_echo
set -o braceexpand
set -o hashall
set -o interactive-comments
set -o monitor
set -o onecmd
shopt -s expand_aliases
# Aliases
# Check for rg availability
if ! (unalias rg 2>/dev/null; command -v rg) >/dev/null 2>&1; then
alias rg='/home/devuser/.local/share/zed/external_agents/claude-agent-acp/0.21.0/node_modules/\@anthropic-ai/claude-agent-sdk/vendor/ripgrep/arm64-linux/rg'
fi
export PATH=/usr/local/cargo/bin\:/usr/local/go/bin\:/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

View File

@@ -8,4 +8,6 @@
"terminal.integrated.shell.linux": "/bin/bash", "terminal.integrated.shell.linux": "/bin/bash",
}, },
"features": {}, "features": {},
"postCreateCommand": "npm install -g @anthropic-ai/claude-code",
"shutdownAction": "stopCompose",
} }

View File

@@ -1,15 +1,24 @@
services: services:
pmocrazy-coder: pmocrazy-coder:
build: image: niepce.petite-maison-orange.fr/public/pmocrazycoder:latest
context: ..
dockerfile: Dockerfile
volumes: volumes:
- ..:/workspace - ..:/workspace
- ./config/claude:/home/devuser/.claude
- ./config/opencode:/home/devuser/.config/opencode
- ./config/aichat:/home/devuser/.config/aichat
- ./config/ai_index:/workspace/.ai_index
- ./config/cargo-registry:/home/devuser/.cargo/registry
- ./config/cargo-git:/home/devuser/.cargo/git
- ./config/cargo-bin:/home/devuser/.cargo/bin
- ./config/go-cache:/home/devuser/.cache/go
- ./config/go-bin:/home/devuser/go/bin
- ./config/python-local:/home/devuser/.local
- ./config/npm-cache:/home/devuser/.npm
- ./config/r-libs:/home/devuser/R
working_dir: /workspace working_dir: /workspace
ports:
- "1249:1248"
environment: environment:
- LOCAL_LLM_API=http://host.docker.internal:1248/v1 - LOCAL_LLM_API=http://host.docker.internal:1248/v1
- CLAUDE_CONFIG_DIR=/home/devuser/.claude
user: devuser user: devuser
entrypoint: ["/usr/local/bin/entrypoint.sh"] entrypoint: ["/usr/local/bin/entrypoint.sh"]
command: ["bash", "-c", "tail -f /dev/null"] command: ["bash", "-c", "tail -f /dev/null"]

4
.gitignore vendored
View File

@@ -209,5 +209,5 @@ cython_debug/
# PyPI configuration file # PyPI configuration file
.pypirc .pypirc
# AI Index .devcontainer/config/
.ai_index/ pmocrazycoder-latest.oci.tar

View File

@@ -24,13 +24,12 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable && \
RUN npm install -g typescript tsc @types/node eslint prettier pyright typescript-language-server RUN npm install -g typescript tsc @types/node eslint prettier pyright typescript-language-server
RUN cargo binstall --strategies crate-meta-data jj-cli RUN cargo binstall --strategies crate-meta-data jj-cli aichat \
&& cargo install --git https://github.com/nilskch/jj-lsp.git
RUN cargo install --git https://github.com/nilskch/jj-lsp.git
RUN gem install github-linguist RUN gem install github-linguist
RUN pip install --no-cache-dir --break-system-packages ruff fastmcp ipython RUN pip install --no-cache-dir --break-system-packages ruff fastmcp ipython tasklin
RUN curl -fsSL https://opencode.ai/install | bash && \ RUN curl -fsSL https://opencode.ai/install | bash && \
mkdir -p /root/.config/opencode && \ mkdir -p /root/.config/opencode && \
@@ -39,11 +38,11 @@ RUN curl -fsSL https://opencode.ai/install | bash && \
ENV GOPATH=/usr/local/go ENV GOPATH=/usr/local/go
ENV GOBIN=/usr/local/go/bin ENV GOBIN=/usr/local/go/bin
RUN go install golang.org/x/tools/gopls@latest \ RUN go install golang.org/x/tools/gopls@latest \
&& go install github.com/isaacphi/mcp-language-server@latest \ && go install github.com/isaacphi/mcp-language-server@latest
&& go install github.com/dorcha-inc/orla/cmd/orla@latest
RUN mkdir -p /etc/bash_completion.d \ RUN mkdir -p /etc/bash_completion.d \
&& orla completion bash > /etc/bash_completion.d/orla \ && curl https://raw.githubusercontent.com/sigoden/aichat/main/scripts/completions/aichat.bash \
> /etc/bash_completion.d/aichat \
&& jj util completion bash > /etc/bash_completion.d/jj && jj util completion bash > /etc/bash_completion.d/jj
RUN rustup component add rust-analyzer --toolchain nightly RUN rustup component add rust-analyzer --toolchain nightly
@@ -71,6 +70,18 @@ COPY --chown=${USERNAME}:${USERNAME} opencode /home/${USERNAME}/.config/opencode
USER $USERNAME USER $USERNAME
WORKDIR /workspace WORKDIR /workspace
ARG OCI_VERSION=latest
ARG OCI_CREATED=unknown
ARG OCI_REVISION=unknown
LABEL org.opencontainers.image.title="PMOCrazyCoder" \
org.opencontainers.image.description="AI-powered dev container with Rust, Go, Python, Node, Jujutsu, Claude Code and opencode" \
org.opencontainers.image.url="https://niepce.petite-maison-orange.fr/public/pmocrazycoder" \
org.opencontainers.image.source="https://niepce.petite-maison-orange.fr/public/pmocrazycoder" \
org.opencontainers.image.version="${OCI_VERSION}" \
org.opencontainers.image.created="${OCI_CREATED}" \
org.opencontainers.image.revision="${OCI_REVISION}" \
org.opencontainers.image.licenses="CECILL-2.1"
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["/bin/bash"] CMD ["/bin/bash"]

157
Makefile
View File

@@ -1,28 +1,120 @@
# Makefile pour dev container AI + MCP bootstrap # =============================================================================
# Image identity
# =============================================================================
# Variables IMAGE_NAME := pmocrazycoder
PROJECT_DIR := $(shell pwd) IMAGE_TAG := latest
# =============================================================================
# Registry
# =============================================================================
REGISTRY := niepce.petite-maison-orange.fr/public
# =============================================================================
# Build platforms
# =============================================================================
PLATFORMS := linux/amd64,linux/arm64
# =============================================================================
# Derived image identifiers
# =============================================================================
FULL_IMAGE := $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
OCI_ARCHIVE := $(IMAGE_NAME)-$(IMAGE_TAG).oci.tar
# =============================================================================
# Docker buildx builder
# =============================================================================
BUILDER := $(IMAGE_NAME)-builder
# =============================================================================
# OCI image annotations (injected via --build-arg)
# =============================================================================
OCI_VERSION := $(IMAGE_TAG)
OCI_CREATED := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
OCI_REVISION := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
# =============================================================================
# Build context
# =============================================================================
PROJECT_DIR := $(shell pwd)
BUILD_CONTEXT := .
DOCKERFILE := Dockerfile
BUILD_ARGS := \
--build-arg OCI_VERSION=$(OCI_VERSION) \
--build-arg OCI_CREATED=$(OCI_CREATED) \
--build-arg OCI_REVISION=$(OCI_REVISION)
# =============================================================================
# Dev container
# =============================================================================
WORKSPACE := /workspace
CONTAINER_NAME := ai-dev-container CONTAINER_NAME := ai-dev-container
WORKSPACE := /workspace MCP_LOG := $(WORKSPACE)/.ai_index/mcp.log
MCP_LOG := $(WORKSPACE)/.ai_index/mcp.log
# --- Dev Container Management --- # =============================================================================
# Targets
# =============================================================================
# Build the container .PHONY: all build build-multiplatform push run run-mcp \
init-index mcp-list embeddings update-graph \
restart-mcp bootstrap clean clean-builder help
## all : Build multi-platform image, push to registry
all: build-multiplatform push
## build : Build for the native platform and load into the local Docker daemon
build: build:
docker build -t $(CONTAINER_NAME) . docker build $(BUILD_ARGS) \
--tag $(FULL_IMAGE) \
--file $(DOCKERFILE) \
$(BUILD_CONTEXT)
# Run interactive container with mounted project ## build-multiplatform : Build for $(PLATFORMS) and export as a local OCI archive
build-multiplatform: .builder-ready
docker buildx build $(BUILD_ARGS) \
--builder $(BUILDER) \
--platform $(PLATFORMS) \
--tag $(FULL_IMAGE) \
--file $(DOCKERFILE) \
--provenance=false \
--output type=oci,dest=$(OCI_ARCHIVE) \
$(BUILD_CONTEXT)
# Create the buildx builder with docker-container driver (once)
.builder-ready:
docker buildx inspect $(BUILDER) > /dev/null 2>&1 \
|| docker buildx create --name $(BUILDER) --driver docker-container --bootstrap
touch .builder-ready
## push : Push the OCI archive to the registry using skopeo
push: $(OCI_ARCHIVE)
skopeo copy \
--dest-tls-verify=true \
--multi-arch all \
oci-archive:$(OCI_ARCHIVE) \
docker://$(FULL_IMAGE)
# Ensure the OCI archive exists before pushing
$(OCI_ARCHIVE):
$(MAKE) build-multiplatform
## run : Run interactive container with mounted project
run: run:
docker run -v $(PROJECT_DIR):$(WORKSPACE) -it --rm $(CONTAINER_NAME) docker run -v $(PROJECT_DIR):$(WORKSPACE) -it --rm $(FULL_IMAGE)
# Run container with MCP server in background ## run-mcp : Run container with MCP server in background
run-mcp: run-mcp:
docker run -v $(PROJECT_DIR):$(WORKSPACE) -it --rm $(CONTAINER_NAME) bash docker run -v $(PROJECT_DIR):$(WORKSPACE) -it --rm $(FULL_IMAGE) bash
# --- MCP / Index Management --- ## init-index : Initialize .ai_index directory structure
# Initialize .ai_index
init-index: init-index:
@echo "[MAKE] Initializing .ai_index..." @echo "[MAKE] Initializing .ai_index..."
mkdir -p $(PROJECT_DIR)/.ai_index/ast mkdir -p $(PROJECT_DIR)/.ai_index/ast
@@ -31,36 +123,45 @@ init-index:
sqlite3 $(PROJECT_DIR)/.ai_index/graph.db "VACUUM;"; \ sqlite3 $(PROJECT_DIR)/.ai_index/graph.db "VACUUM;"; \
fi fi
# Run MCP tool (list imports) ## mcp-list : Run MCP list_imports tool
mcp-list: mcp-list:
@echo "[MAKE] Running MCP list_imports.py..." @echo "[MAKE] Running MCP list_imports.py..."
python3 $(PROJECT_DIR)/opt/dev-tools/list_imports.py --project $(WORKSPACE) python3 $(PROJECT_DIR)/opt/dev-tools/list_imports.py --project $(WORKSPACE)
# Update embeddings (example) ## embeddings : Generate embeddings
embeddings: embeddings:
@echo "[MAKE] Generating embeddings..." @echo "[MAKE] Generating embeddings..."
python3 $(PROJECT_DIR)/opt/dev-tools/generate_embeddings.py --project $(WORKSPACE) python3 $(PROJECT_DIR)/opt/dev-tools/generate_embeddings.py --project $(WORKSPACE)
# Update graph (example) ## update-graph : Update dependency graph
update-graph: update-graph:
@echo "[MAKE] Updating dependency graph..." @echo "[MAKE] Updating dependency graph..."
python3 $(PROJECT_DIR)/opt/dev-tools/update_graph.py --project $(WORKSPACE) python3 $(PROJECT_DIR)/opt/dev-tools/update_graph.py --project $(WORKSPACE)
# Clean logs and temporary files ## restart-mcp : Restart MCP server inside running container
clean:
@echo "[MAKE] Cleaning .ai_index..."
rm -f $(PROJECT_DIR)/.ai_index/*.log
rm -rf $(PROJECT_DIR)/.ai_index/ast/*
rm -rf $(PROJECT_DIR)/.ai_index/embeddings/*
# Restart MCP server inside container
restart-mcp: restart-mcp:
docker exec -it $(CONTAINER_NAME) bash -c "\ docker exec -it $(CONTAINER_NAME) bash -c "\
pkill -f mcp-server; \ pkill -f mcp-server; \
/opt/bin/mcp-server --project $(WORKSPACE) > $(MCP_LOG) 2>&1 & \ /opt/bin/mcp-server --project $(WORKSPACE) > $(MCP_LOG) 2>&1 & \
" "
# --- Bootstrap for new project --- ## bootstrap : Build container and init index
# Build container, init index
bootstrap: build init-index bootstrap: build init-index
@echo "[MAKE] Bootstrap complete. Run 'make run' to start the container." @echo "[MAKE] Bootstrap complete. Run 'make run' to start the container."
## clean : Remove OCI archive, local image and generated files
clean: clean-builder
docker rmi --force $(FULL_IMAGE) 2>/dev/null || true
rm -f $(OCI_ARCHIVE)
rm -f $(PROJECT_DIR)/.ai_index/*.log
rm -rf $(PROJECT_DIR)/.ai_index/ast/*
rm -rf $(PROJECT_DIR)/.ai_index/embeddings/*
## clean-builder : Remove the buildx builder
clean-builder:
docker buildx rm $(BUILDER) 2>/dev/null || true
rm -f .builder-ready
## help : Show this help
help:
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/^## //'

140
README.md
View File

@@ -1,19 +1,133 @@
# 🧠 PMOCrazyCoder # PMOCrazyCoder
Un assistant IA **local**, pour coder en Go, Rust et R — Environnement de développement conteneurisé avec assistance IA intégrée — Go, Rust, Python, TypeScript, R, Ruby — et outillage IA local (LM Studio) ou cloud (Claude Pro).
sans cloud, sans API key, tout sur votre M3 Max.
## 🎯 Vision ## Vision
> *Un indexeur de code, des graphes de dépendances, et un assistant intelligent — > Un container de développement complet, multi-langages, avec LSP, outils IA et MCP intégrés,
> intégré à Zed, 100 % local.* > utilisable depuis Zed, VS Code ou Cursor, avec persistance par projet.
### 🛠️ Ce quon construit ## Ce que contient l'image
- ✅ MCP tools pour Zed (ex : `get_similar_functions`, `explain_imports`) ### Langages et compilateurs
- ✅ Indexeur de code (Go / Rust / R) via Tree-sitter - **Go** (dernière version stable via script)
- ✅ Graphe de dépendances & similarité (stocké localement) - **Rust** (stable + nightly, avec rustfmt, clippy, rust-analyzer, rust-src)
- ✅ Tout tourne en local : **LM Studio** (`localhost:1248`) + LanceDB + Rust/Python - **Python 3** (avec ruff, fastmcp, ipython, tasklin)
*(hors conteneur — le conteneur y accède via `host.docker.internal:1248`)* - **Node.js / npm** (avec TypeScript, ESLint, Prettier, Pyright, typescript-language-server)
- **Ruby** (avec github-linguist)
- **R**
- **Clang / LLVM** (clangd inclus)
### 📦 Structure future (à venir) ### Outils de développement
- `jj` (Jujutsu VCS) + `jj-lsp`
- `gopls`, `mcp-language-server`
- `cargo-edit`, `cargo-watch`, `cargo-outdated`, `cargo-make`, `cargo-binstall`
- `sqlite3`, `cmake`, `make`, `git`
- `aichat` (client LLM CLI)
### Assistants IA
- **Claude Code** (installé au `postCreateCommand`)
- **opencode** (configuré via l'entrypoint)
- **aichat** (configuré via l'entrypoint, connecté au LLM local)
## Utilisation rapide
### Ajouter l'environnement à un projet existant
```bash
curl -fsSL https://gargoton.petite-maison-orange.fr/eric/PMOCrazycoder/raw/branch/main/setup-devcontainer.sh | bash
```
Ou avec un endpoint LLM personnalisé :
```bash
LOCAL_LLM_API=http://monserveur:1248/v1 bash setup-devcontainer.sh
```
Le script télécharge les fichiers `devcontainer.json` et `docker-compose.yml` directement depuis le dépôt Gitea, crée les répertoires de persistance dans `.devcontainer/config/` et met à jour `.gitignore`.
### Ouvrir dans le container
Ouvrir le projet dans VS Code, Zed ou Cursor et choisir "Reopen in Container". L'image est tirée automatiquement depuis le registry.
### Authentifier Claude Code
Dans le panneau Claude Agent de Zed, lancer `/login` et choisir "Log in with Claude.ai" (abonnement Pro/Max).
## Persistance par projet
Toutes les données et caches sont persistés dans `.devcontainer/config/` (ignoré par git) :
| Répertoire | Contenu |
|---|---|
| `config/claude/` | Sessions et config Claude Code |
| `config/opencode/` | Config opencode |
| `config/aichat/` | Config aichat |
| `config/ai_index/` | Index de code du projet |
| `config/cargo-registry/` | Cache crates.io |
| `config/cargo-git/` | Cache dépendances git Cargo |
| `config/cargo-bin/` | Binaires Rust installés pour le projet |
| `config/go-cache/` | Cache modules Go |
| `config/go-bin/` | Binaires Go installés pour le projet |
| `config/python-local/` | Paquets pip user + gems Ruby |
| `config/npm-cache/` | Cache npm |
| `config/r-libs/` | Paquets R installés |
### Installer des binaires persistants
```bash
# Rust — persiste dans .devcontainer/config/cargo-bin/
cargo install cargo-nextest
# Go — persiste dans .devcontainer/config/go-bin/
GOBIN=~/go/bin go install github.com/some/tool@latest
# Python — persiste dans .devcontainer/config/python-local/
pip install --user monpaquet
# R — persiste dans .devcontainer/config/r-libs/
R -e 'install.packages("tidyverse")'
```
## LLM local
L'entrypoint configure automatiquement opencode et aichat pour pointer sur `LOCAL_LLM_API`
(défaut : `http://host.docker.internal:1248/v1`).
Variables d'environnement disponibles dans le container :
| Variable | Défaut | Description |
|---|---|---|
| `LOCAL_LLM_API` | `http://host.docker.internal:1248/v1` | Endpoint OpenAI-compatible |
| `LLM_MODEL` | `ollama:qwen/qwen3-coder-next@4bit` | Modèle à utiliser |
| `TEMPERATURE` | `0.5` | Température |
| `NUM_CTX` | `32768` | Taille de contexte |
| `MAXTOKEN` | `4096` | Tokens max en sortie |
## Construire et publier l'image
```bash
# Build natif (local)
make build
# Build multi-arch (amd64 + arm64) + push vers le registry
make all
# Push uniquement (si l'archive OCI existe déjà)
make push
# Lister toutes les targets
make help
```
L'image est publiée sur `niepce.petite-maison-orange.fr/public/pmocrazycoder:latest`.
Le push utilise `skopeo` — assurez-vous d'être authentifié sur le registry.
## Sources
- Dépôt git : `https://gargoton.petite-maison-orange.fr/eric/PMOCrazycoder`
- Registry OCI : `niepce.petite-maison-orange.fr/public/pmocrazycoder:latest`
## Licence
CECILL-2.1

View File

@@ -1,16 +1,48 @@
#!/bin/bash #!/bin/bash
LOCAL_LLM_API=${LOCAL_LLM_API:-http://host.docker.internal:1248/v1} LOCAL_LLM_API=${LOCAL_LLM_API:-http://host.docker.internal:1248/v1}
MODEL="${LLM_MODEL:-qwen3-coder}"
DIFF=$(jj diff) llm_query() {
PROMPT='Analyse ce diff et réponds UNIQUEMENT avec un objet JSON valide contenant "title" (string) et "message" (string) pour le message de commit. Pas de texte avant ou après le JSON.' local content="$1"
jq -n --arg content "$content" --arg model "$MODEL" \
'{"model": $model, "messages": [{"role": "user", "content": $content}]}' \
| curl -s -X POST \
-H "Content-Type: application/json" \
-d @- \
"$LOCAL_LLM_API/chat/completions" \
| jq -r '.choices[0].message.content'
}
CONTENT="${PROMPT} # Get list of modified files
${DIFF}" FILES=$(jj diff --summary | awk '{print $2}')
REQUEST=$(jq -n --arg content "$CONTENT" '{"model": "qwen3-coder", "messages": [{"role": "user", "content": $content}]}') if [[ -z "$FILES" ]]; then
echo "No changes to commit." >&2
exit 1
fi
curl -s -X POST \ # Pass 1: summarize each file diff individually
-H "Content-Type: application/json" \ SUMMARIES=""
-d "$REQUEST" \ while IFS= read -r file; do
"$LOCAL_LLM_API/chat/completions" | jq -r '.choices[0].message.content' | jq -r '.title + "\n\n" + .message' FILE_DIFF=$(jj diff "$file" 2>/dev/null)
if [[ -z "$FILE_DIFF" ]]; then
continue
fi
PROMPT="Résume en 2-3 phrases les changements apportés dans ce diff pour le fichier '$file'. Sois concis et factuel.
$FILE_DIFF"
SUMMARY=$(llm_query "$PROMPT")
SUMMARIES="${SUMMARIES}### $file
$SUMMARY
"
done <<< "$FILES"
# Pass 2: generate commit message from summaries
COMMIT_PROMPT="À partir des résumés suivants de fichiers modifiés, génère un message de commit git.
Réponds UNIQUEMENT avec un objet JSON valide contenant \"title\" (string, max 72 chars, en anglais, style imperative) et \"message\" (string, détails en français). Pas de texte avant ou après le JSON.
$SUMMARIES"
llm_query "$COMMIT_PROMPT" | jq -r '.title + "\n\n" + .message'

View File

@@ -6,7 +6,7 @@ export USERNAME=${USERNAME:-devuser}
export WORKSPACE=${WORKSPACE:-/workspace} export WORKSPACE=${WORKSPACE:-/workspace}
export LOCAL_LLM_API=${LOCAL_LLM_API:-http://host.docker.internal:1248/v1} export LOCAL_LLM_API=${LOCAL_LLM_API:-http://host.docker.internal:1248/v1}
export LLM_MODEL=${LLM_MODEL:-'ollama:qwen/qwen3-coder-next@4bit'} export LLM_MODEL=${LLM_MODEL:-'ollama:qwen/qwen3-coder-next@4bit'}
export ORLA_OLLAMA_HOST=$(sed -E 's@(https?://[^/]+).*$@\1@' <<< $LOCAL_LLM_API) export AICHAT_API_BASE=$(sed -E 's@(https?://[^/]+).*$@\1@' <<< $LOCAL_LLM_API)
export MAXTOKEN=${MAXTOKEN:-4096} export MAXTOKEN=${MAXTOKEN:-4096}
export NUM_CTX=${NUM_CTX:-32768} export NUM_CTX=${NUM_CTX:-32768}
export REPEAT_PENALTY=${REPEAT_PENALTY:-1.3} export REPEAT_PENALTY=${REPEAT_PENALTY:-1.3}
@@ -26,10 +26,28 @@ fi
if [ -n "$LOCAL_LLM_API" ]; then if [ -n "$LOCAL_LLM_API" ]; then
if ! grep -q "LOCAL_LLM_API" /home/${USERNAME}/.bashrc; then if ! grep -q "LOCAL_LLM_API" /home/${USERNAME}/.bashrc; then
echo "export LOCAL_LLM_API=$LOCAL_LLM_API" >> /home/${USERNAME}/.bashrc echo "export LOCAL_LLM_API=$LOCAL_LLM_API" >> /home/${USERNAME}/.bashrc
echo "export ORLA_OLLAMA_HOST=$ORLA_OLLAMA_HOST" >> /home/${USERNAME}/.bashrc echo "export AICHAT_API_BASE=$AICHAT_API_BASE" >> /home/${USERNAME}/.bashrc
echo "export ORLA_MODEL='${LLM_MODEL}'" >> /home/${USERNAME}/.bashrc echo "export PATH=\$HOME/.cargo/bin:\$HOME/go/bin:\$HOME/.local/bin:\$PATH" >> /home/${USERNAME}/.bashrc
fi fi
# --- Configure aichat ---
mkdir -p /home/${USERNAME}/.config/aichat
AICHAT_CONFIG=/home/${USERNAME}/.config/aichat/config.yaml
cat > $AICHAT_CONFIG <<EOF
model: lmstudio:${LLM_MODEL#*:}
clients:
- type: openai-compatible
name: lmstudio
api_base: ${LOCAL_LLM_API}
api_key: placeholder
models:
- name: ${LLM_MODEL#*:}
max_input_tokens: ${NUM_CTX}
temperature: ${TEMPERATURE}
top_p: ${TOP_P}
EOF
chown ${USERNAME}:${USERNAME} ${AICHAT_CONFIG}
mkdir -p /home/${USERNAME}/.config/opencode mkdir -p /home/${USERNAME}/.config/opencode
CONFIG_FILE=/home/${USERNAME}/.config/opencode/opencode.json CONFIG_FILE=/home/${USERNAME}/.config/opencode/opencode.json

99
setup-devcontainer.sh Executable file
View File

@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# setup-devcontainer.sh
#
# Adds the PMOCrazyCoder devcontainer environment to any project.
# Usage: bash setup-devcontainer.sh [project-dir]
#
# Example (run from the project root):
# bash setup-devcontainer.sh
#
# Or download and run directly:
# curl -fsSL https://gargoton.petite-maison-orange.fr/eric/PMOCrazycoder/raw/branch/main/setup-devcontainer.sh | bash
set -euo pipefail
PROJECT_DIR="${1:-$(pwd)}"
DEVCONTAINER_DIR="$PROJECT_DIR/.devcontainer"
LOCAL_LLM_API="${LOCAL_LLM_API:-http://host.docker.internal:1248/v1}"
GIT_RAW="https://gargoton.petite-maison-orange.fr/eric/PMOCrazycoder/raw/branch/main"
# --- Colors ---
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RESET='\033[0m'
info() { echo -e "${GREEN}[setup]${RESET} $*"; }
warning() { echo -e "${YELLOW}[setup]${RESET} $*"; }
error() { echo -e "${RED}[setup]${RESET} $*" >&2; exit 1; }
fetch() {
local src="$1" dst="$2"
curl -fsSL "$GIT_RAW/$src" -o "$dst" \
|| error "Failed to fetch $src from $GIT_RAW"
}
# --- Checks ---
[[ -d "$PROJECT_DIR" ]] || error "Directory not found: $PROJECT_DIR"
command -v curl >/dev/null || error "curl is required"
# --- .devcontainer/ ---
info "Creating .devcontainer structure in $PROJECT_DIR"
mkdir -p "$DEVCONTAINER_DIR/config/claude"
mkdir -p "$DEVCONTAINER_DIR/config/opencode"
mkdir -p "$DEVCONTAINER_DIR/config/aichat"
mkdir -p "$DEVCONTAINER_DIR/config/ai_index"
mkdir -p "$DEVCONTAINER_DIR/config/cargo-registry"
mkdir -p "$DEVCONTAINER_DIR/config/cargo-git"
mkdir -p "$DEVCONTAINER_DIR/config/cargo-bin"
mkdir -p "$DEVCONTAINER_DIR/config/go-cache"
mkdir -p "$DEVCONTAINER_DIR/config/go-bin"
mkdir -p "$DEVCONTAINER_DIR/config/python-local"
mkdir -p "$DEVCONTAINER_DIR/config/npm-cache"
mkdir -p "$DEVCONTAINER_DIR/config/r-libs"
# --- devcontainer.json ---
DEVCONTAINER_JSON="$DEVCONTAINER_DIR/devcontainer.json"
if [[ -f "$DEVCONTAINER_JSON" ]]; then
warning "devcontainer.json already exists, skipping"
else
fetch ".devcontainer/devcontainer.json" "$DEVCONTAINER_JSON"
# Rename the service name to match the project
sed -i.bak "s/\"name\": \"PMOCrazyCoder\"/\"name\": \"$(basename "$PROJECT_DIR")\"/" "$DEVCONTAINER_JSON"
rm -f "$DEVCONTAINER_JSON.bak"
info "Created devcontainer.json"
fi
# --- docker-compose.yml ---
COMPOSE_FILE="$DEVCONTAINER_DIR/docker-compose.yml"
if [[ -f "$COMPOSE_FILE" ]]; then
warning "docker-compose.yml already exists, skipping"
else
fetch ".devcontainer/docker-compose.yml" "$COMPOSE_FILE"
# Patch LOCAL_LLM_API if overridden
if [[ "$LOCAL_LLM_API" != "http://host.docker.internal:1248/v1" ]]; then
sed -i.bak "s|http://host.docker.internal:1248/v1|${LOCAL_LLM_API}|g" "$COMPOSE_FILE"
rm -f "$COMPOSE_FILE.bak"
fi
info "Created docker-compose.yml"
fi
# --- .gitignore ---
GITIGNORE="$PROJECT_DIR/.gitignore"
if [[ -f "$GITIGNORE" ]]; then
if grep -q "\.devcontainer/config/" "$GITIGNORE"; then
warning ".gitignore already contains .devcontainer/config/, skipping"
else
printf '\n# PMOCrazyCoder devcontainer runtime config\n.devcontainer/config/\n' >> "$GITIGNORE"
info "Updated .gitignore"
fi
else
printf '# PMOCrazyCoder devcontainer runtime config\n.devcontainer/config/\n' > "$GITIGNORE"
info "Created .gitignore"
fi
echo
info "Done. Next steps:"
echo " 1. Open the project in VS Code / Zed / Cursor"
echo " 2. Reopen in container (the image will be pulled from the registry)"
echo " 3. Run /login in the Claude Agent panel to authenticate"
echo
info "To use a different local LLM API endpoint:"
echo " LOCAL_LLM_API=http://myserver:1248/v1 bash setup-devcontainer.sh"

View File

@@ -1,2 +1 @@
from .jj import * from .jj import *
from .sandbox import *

View File

@@ -1,10 +1,9 @@
from crazy_mcp import Context, mcp from crazy_mcp import Context, mcp
from jujutsu import jj_log from jujutsu import jj_log, jj_new
from sandbox import get_active_project, get_project_path
@mcp.tool(description="Get the commit history for a project") @mcp.tool(description="Get the commit history for a project")
def jj_log_tool(project_name: str | None = None, ctx: Context = None) -> dict: def jj_log_tool(project_name: str | None = None, ctx: Context | None = None) -> dict:
""" """
Get the commit history for a project. Get the commit history for a project.
@@ -15,19 +14,70 @@ def jj_log_tool(project_name: str | None = None, ctx: Context = None) -> dict:
Returns: Returns:
dict: The commit history as JSON structure dict: The commit history as JSON structure
""" """
# If no project name is provided, try to get the active project
if project_name is None: if project_name is None:
if ctx is None: if ctx is None:
raise ValueError("ctx is required when project_name is None") raise ValueError("ctx is required when project_name is None")
project_info = get_active_project(ctx.session_id) project_info = ctx.get_state("active_project")
if project_info is None: if project_info is None:
raise ValueError("No active project set and no project name provided") raise ValueError("No active project set and no project name provided")
project_path = project_info[1] project_path = project_info[1] if isinstance(project_info, tuple) else None
else:
project_path = get_project_path(project_name)
if project_path is None: if project_path is None:
raise ValueError(f"Project '{project_name}' not found") raise ValueError("No project path found in active project")
else:
sandbox_root = "/sandbox"
project_path = f"{sandbox_root}/{project_name}"
# Call the jj_log function from jujutsu module
return jj_log(project_path) return jj_log(project_path)
@mcp.tool(description="Create a new commit with AI-generated description")
def jj_new_tool(
project_name: str | None = None,
revisions: list[str] | None = None,
message: str | None = None,
no_edit: bool = False,
insert_after: str | None = None,
insert_before: str | None = None,
options: list[str] | None = None,
ctx: Context | None = None,
) -> str:
"""
Create a new commit after documenting the current change with AI.
Args:
project_name (str, optional): The name of the project. If None, uses the active project.
revisions (list[str], optional): List of parent revisions (default: [@])
message (str, optional): The change description to use
no_edit (bool): Do not edit the newly created change
insert_after (str, optional): Insert the new change after the given commit(s)
insert_before (str, optional): Insert the new change before the given commit(s)
options (list[str], optional): List of additional options
ctx (Context, optional): The MCP context object containing session information
Returns:
str: Output of jj new command
"""
if project_name is None:
if ctx is None:
raise ValueError("ctx is required when project_name is None")
project_info = ctx.get_state("active_project")
if project_info is None:
raise ValueError("No active project set and no project name provided")
project_path = project_info[1] if isinstance(project_info, tuple) else None
if project_path is None:
raise ValueError("No project path found in active project")
else:
sandbox_root = "/sandbox"
project_path = f"{sandbox_root}/{project_name}"
return jj_new(
revisions=revisions,
message=message,
no_edit=no_edit,
insert_after=insert_after,
insert_before=insert_before,
options=options,
working_dir=project_path,
)

View File

@@ -1,89 +0,0 @@
from crazy_mcp import Context, mcp
from sandbox import (
get_active_project,
install_project,
remove_project,
sandboxed_project_list,
set_active_project,
)
@mcp.tool(
description="Clone a Git repository into the sandbox. Note: Access to the cloned project is only possible through MCP tools of this server. WARNING: This operation creates a sandboxed environment that prevents direct filesystem access to the project contents. Any attempt to access project files outside of the MCP tools will be blocked by security restrictions."
)
def install_project_tool(url: str, project_name: str = None) -> str:
"""
Install a Git project into the sandbox environment.
Args:
url: The Git repository URL to clone
project_name: Optional project name (if None, derived from URL)
Returns:
str: Confirmation message with project name
"""
project_name = install_project(url, project_name)
return f"Project '{project_name}' installed successfully in sandbox"
@mcp.tool(
description="Remove a project from the sandbox. Note: Access to the cloned project is only possible through MCP tools of this server. WARNING: This operation removes the entire project sandboxed environment and all associated security restrictions will be enforced."
)
def remove_project_tool(project_name: str) -> str:
"""
Remove a project from the sandbox environment.
Args:
project_name (str): The name of the project to remove
Returns:
str: Confirmation message with project name
"""
success = remove_project(project_name)
if success:
return f"Project '{project_name}' removed successfully from sandbox"
else:
return f"Failed to remove project '{project_name}' from sandbox"
@mcp.tool(
description="List all projects available in the sandbox. Note: Direct filesystem access to project contents is forbidden. All project interactions must be done through MCP tools."
)
def list_sandboxed_projects() -> list:
"""
Get a list of all projects available in the sandbox.
Returns:
list: A list of project names
"""
return sandboxed_project_list()
@mcp.tool(description="Set the active project for a given session ID")
def set_active_project_tool(project_name: str, ctx: Context) -> bool:
"""
Set the active project for a given session ID.
Args:
project_name (str): The name of the project to set as active
ctx (Context): The MCP context object containing session information
Returns:
bool: True if successful, False if the project doesn't exist
"""
return set_active_project(ctx.session_id, project_name)
@mcp.tool(description="Get the active project for a given session ID")
def get_active_project_tool(ctx: Context) -> tuple[str, str] | None:
"""
Get the active project for a given session ID.
Args:
ctx (Context): The MCP context object containing session information
Returns:
tuple: (project_name, project_path) if session exists, None otherwise
"""
project_info = get_active_project(ctx.session_id)
return project_info

View File

@@ -1,8 +1,8 @@
import json import json
import os import os
import urllib.request
import urllib.error import urllib.error
from typing import Dict, List, Optional import urllib.request
from typing import Dict
def structure_to_json(text: str, schema: Dict) -> Dict: def structure_to_json(text: str, schema: Dict) -> Dict:
@@ -17,7 +17,9 @@ def structure_to_json(text: str, schema: Dict) -> Dict:
dict: The parsed JSON object dict: The parsed JSON object
""" """
# Get the API URL from environment variable # Get the API URL from environment variable
local_llm_api = os.environ.get("LOCAL_LLM_API", "http://host.docker.internal:1248/v1") local_llm_api = os.environ.get(
"LOCAL_LLM_API", "http://host.docker.internal:1248/v1"
)
# Format the schema as JSON string # Format the schema as JSON string
schema_json = json.dumps(schema, indent=2) schema_json = json.dumps(schema, indent=2)
@@ -34,9 +36,7 @@ Schema:
# Build the request body # Build the request body
request_body = { request_body = {
"model": "qwen3-coder", "model": "qwen3-coder",
"messages": [ "messages": [{"role": "user", "content": prompt_text}],
{"role": "user", "content": prompt_text}
]
} }
# Build the URL # Build the URL
@@ -45,10 +45,7 @@ Schema:
# Create the request # Create the request
data = json.dumps(request_body).encode("utf-8") data = json.dumps(request_body).encode("utf-8")
req = urllib.request.Request( req = urllib.request.Request(
url, url, data=data, headers={"Content-Type": "application/json"}, method="POST"
data=data,
headers={"Content-Type": "application/json"},
method="POST"
) )
try: try:
@@ -73,4 +70,7 @@ Schema:
# Parse the JSON content # Parse the JSON content
return json.loads(clean_content) return json.loads(clean_content)
except (json.JSONDecodeError, KeyError, IndexError) as e: except (json.JSONDecodeError, KeyError, IndexError) as e:
return {"error": f"Failed to parse JSON: {e}", "raw_response": json.dumps(result)} return {
"error": f"Failed to parse JSON: {e}",
"raw_response": json.dumps(result),
}

View File

@@ -1,8 +1,8 @@
import unittest
from unittest.mock import patch, MagicMock
import json import json
import os import os
import sys import sys
import unittest
from unittest.mock import MagicMock, patch
sys.path.insert(0, "/workspace/src") sys.path.insert(0, "/workspace/src")

View File

@@ -1,10 +1,8 @@
import os
import shlex
from typing import Optional from typing import Optional
from json_extract import structure_to_json from json_extract import structure_to_json
from jujutsu.command import jj_log as jj_log_command
from jujutsu.command import jj_diff from jujutsu.command import jj_diff
from jujutsu.command import jj_log as jj_log_command
from jujutsu.command import jj_new as jj_new_command from jujutsu.command import jj_new as jj_new_command
from .command import jj from .command import jj

View File

@@ -1,4 +1,3 @@
import json
import os import os
import subprocess import subprocess
from typing import List, Optional from typing import List, Optional