diff --git a/framework/agents/_shared/cli-tools-protocol.md b/framework/agents/_shared/cli-tools-protocol.md index 6cb490a..1079c9f 100644 --- a/framework/agents/_shared/cli-tools-protocol.md +++ b/framework/agents/_shared/cli-tools-protocol.md @@ -17,6 +17,7 @@ Avant d'utiliser un outil, vérifier sa disponibilité : - **Google Workspace** : `gws` (`recommended` depuis 2026-04-29) — remplace les MCPs Calendar + Gmail + Drive (~10-12K tokens cumulés économisés). Install : `npm i -g @googleworkspace/cli` - **Base de données** : `neonctl` si Neon, sinon `psql`/`mysql` direct - **Containers** : `docker`, `kubectl` direct +- **Capture web** : `shot-scraper` (`required` depuis 2026-05-06) — **toute capture web sans exception** : screenshots, JS execution, arbre d'accessibilité. Remplace `mcp__chrome-devtools__*` globalement. Protocole : `_shared/shot-scraper-protocol.md`. Install : `pip install shot-scraper && shot-scraper install`. ## Linear — CLI-first (depuis 2026-04-29) @@ -71,7 +72,7 @@ Ces 5 MCPs n'ont pas de CLI viable couvrant 80%+ du besoin — les conserver : | **Figma** | Lecture des nodes/tokens/variables Figma impossible sans API | `mcp__plugin_figma_figma__*` | | **Linear** | MCP fallback quand `LINEAR_API_KEY` absent ou pour opérations complexes | `mcp__claude_ai_Linear__*` | | **Pencil/Penpot** | Design interactif .pen files — aucun équivalent CLI | `mcp__pencil__*` | -| **Chrome DevTools** | Automation browser — seul MCP autorisé (jamais `claude-in-chrome` ni Playwright) | `mcp__plugin_chrome-devtools-mcp_chrome-devtools__*` | +| **Chrome DevTools** | ⚠️ Remplacé par `shot-scraper` CLI depuis 2026-05-06 — n'utiliser que si `shot-scraper` non disponible et pour les cas réseau (network monitoring) non couverts | `mcp__plugin_chrome-devtools-mcp_chrome-devtools__*` | | **shadcn registry** | Accès au registre de composants shadcn/ui — pas de CLI équivalent | `mcp__clerk__*` | ## Drifts à surveiller diff --git a/framework/cli/internal/installer/catalog.go b/framework/cli/internal/installer/catalog.go index 591da6d..835cf21 100644 --- a/framework/cli/internal/installer/catalog.go +++ b/framework/cli/internal/installer/catalog.go @@ -149,10 +149,11 @@ var All = []Installable{ // --- External tools --- &ExternalModule{ - base: base{key: "shot-scraper", flag: "--with-shot-scraper", label: "shot-scraper", description: "captures web headless (screenshots, JS, accessibilité) — remplace mcp__chrome-devtools__* (required)", category: "tools"}, - dep: "pip3", - runCmd: []string{"sh", "-c", "pip3 install shot-scraper && shot-scraper install"}, - message: "pip3 requis : brew install python3 (macOS) · apt install python3-pip (Linux)", + base: base{key: "shot-scraper", flag: "--with-shot-scraper", label: "shot-scraper", description: "captures web headless (screenshots, JS, accessibilité) — remplace mcp__chrome-devtools__* (required)", enabledByDefault: true, category: "tools"}, + dep: "pip3", + runCmd: []string{"sh", "-c", "pip3 install shot-scraper && shot-scraper install"}, + message: "pip3 requis : brew install python3 (macOS) · apt install python3-pip (Linux)", + skipIfInstalled: "shot-scraper", }, &NoopModule{ base: base{key: "faru", flag: "--with-faru", label: "faru", description: "kanban git-natif agent-first — crée docs/backlog/ (installe faru manuellement : npm install -g faru)", category: "tools"}, diff --git a/framework/cli/internal/installer/external.go b/framework/cli/internal/installer/external.go index d299265..fc5deb2 100644 --- a/framework/cli/internal/installer/external.go +++ b/framework/cli/internal/installer/external.go @@ -9,12 +9,14 @@ import ( // ExternalModule installs a tool via an external CLI (npm, npx, pip…). // If msgOnly is true, it only prints installation instructions without running anything. // If dep is set and the required CLI is missing, it prints a hint and skips. +// If skipIfInstalled is set and that command is found in PATH, the install is skipped (idempotent). type ExternalModule struct { base - dep string // required CLI dependency (checked via exec.LookPath) - runCmd []string // command to execute when dep is found - msgOnly bool // skip execution, print message instead - message string // informational message (used when msgOnly or on dep-miss) + dep string // required CLI dependency (checked via exec.LookPath) + runCmd []string // command to execute when dep is found + msgOnly bool // skip execution, print message instead + message string // informational message (used when msgOnly or on dep-miss) + skipIfInstalled string // if non-empty, skip install when this command is already in PATH } func (m *ExternalModule) Install(ctx *Context) error { @@ -23,6 +25,13 @@ func (m *ExternalModule) Install(ctx *Context) error { return nil } + if m.skipIfInstalled != "" { + if _, err := exec.LookPath(m.skipIfInstalled); err == nil { + ctx.progress(m.label, "déjà installé") + return nil + } + } + if m.dep != "" { if _, err := exec.LookPath(m.dep); err != nil { hint := m.message diff --git a/framework/cli/internal/installer/modules_test.go b/framework/cli/internal/installer/modules_test.go index 62a829c..8f83335 100644 --- a/framework/cli/internal/installer/modules_test.go +++ b/framework/cli/internal/installer/modules_test.go @@ -48,13 +48,13 @@ func TestDefaultModules(t *testing.T) { t.Errorf("DefaultModules len: got %d, want %d", len(defaults), len(All)) } // modules enabled by default - for _, key := range []string{"figma-skills", "swift-skills", "flutter-skills", "context-audit", "obsidian-skills", "symbols", "architecture-diagram", "rtk-hook"} { + for _, key := range []string{"figma-skills", "swift-skills", "flutter-skills", "context-audit", "obsidian-skills", "symbols", "architecture-diagram", "rtk-hook", "shot-scraper"} { if !defaults[key] { t.Errorf("%s should be enabled by default", key) } } // modules disabled by default - for _, key := range []string{"vps", "agent-teams", "memory-loop", "xavier-hook", "hue", "caveman", "caveman-output", "logo-generator", "accountability", "context-mode", "refusal-scope", "sentinel", "statusline", "figma-mcp", "shot-scraper", "faru", "kami", "code-graph", "cloud-clis", "database-clis", "security-clis", "notif-clis", "container-clis", "monitoring-clis", "ai-clis", "doc-clis", "data-clis", "design-clis", "devops-clis", "mobile-clis"} { + for _, key := range []string{"vps", "agent-teams", "memory-loop", "xavier-hook", "hue", "caveman", "caveman-output", "logo-generator", "accountability", "context-mode", "refusal-scope", "sentinel", "statusline", "figma-mcp", "faru", "kami", "code-graph", "cloud-clis", "database-clis", "security-clis", "notif-clis", "container-clis", "monitoring-clis", "ai-clis", "doc-clis", "data-clis", "design-clis", "devops-clis", "mobile-clis"} { if defaults[key] { t.Errorf("%s should be disabled by default", key) }