# Tool parity: Gemini CLI vs Claude Code Goal: let Gemini CLI (the agent driven via `agent-helm`) do everything Claude Code can, by building the missing capabilities as small CLI tools in this repo. Gemini calls them through `run_shell_command`, so every tool follows the house rules: **text in, verifiable artifacts out**, single static Go binary, self-explanatory output. Sources: Gemini CLI tools reference (), Claude Code's toolset as of 2026-08. ## 1. Already at parity — nothing to build | Capability | Claude Code | Gemini CLI | |---|---|---| | Read/write/edit files | `Read` / `Write` / `Edit` | `read_file` / `write_file` / `replace` | | Find files / search text / list dirs | `Glob` / `Grep` | `glob` / `grep_search` / `list_directory`, plus `read_many_files` | | Shell, incl. background processes | `Bash` (+ background tasks) | `run_shell_command` (+ background processes) | | Web | `WebFetch` / `WebSearch` | `web_fetch` / `google_web_search` | | Ask the user a structured question | `AskUserQuestion` | `ask_user` | | Plan mode | `EnterPlanMode` / `ExitPlanMode` | `enter_plan_mode` / `exit_plan_mode` | | Skills / slash commands | `Skill` (`.claude/skills`) | `activate_skill` (`.gemini/skills`) | | Persistent memory | file-based memory dir | `save_memory` (simpler, but exists) | | Todo/task tracking | `TaskCreate`/`TaskUpdate`/… | `write_todos`, `tracker_*` (experimental) | | MCP servers + resources | MCP tools, `ListMcpResources`/`ReadMcpResource` | MCP tools, `list_mcp_resources`/`read_mcp_resource` | | Subagents | `Agent` (background, custom types) | subagents (experimental) — weaker, see `fanout` below | Not worth replicating (harness-internal to Claude Code, no value as a CLI): `ToolSearch`, `EndConversation`, `ReportFindings`, `ShareOnboardingGuide`, `DesignSync`, remote cloud execution. ## 2. Gaps → tools to build Ordered by expected value. Each becomes its own folder + binary + README, per repo convention. ### 2.1 `cronr` — scheduled/recurring runs *(Claude: `CronCreate`/`CronList`/`CronDelete`, `/loop`)* Gemini CLI has no way to schedule work. `cronr` manages **systemd user timers** so an agent can create recurring or one-shot scheduled jobs (including "run this prompt every morning" via `gemini -p …`). ``` cronr add nightly-ci-check --schedule "*-*-* 07:00" --cmd 'gemini -p "check CI status and notify"' cronr add once-reboot-check --at "2026-08-06 03:00" --cmd '…' # one-shot cronr list # name, schedule, next run, last result cronr logs nightly-ci-check # journalctl for the unit cronr rm nightly-ci-check ``` Output prints the generated unit files so the result is verifiable. No daemon of its own — systemd does the running. ### 2.2 `waitfor` — block until a condition holds *(Claude: `Monitor`)* Turns "poll every N seconds" into a single blocking tool call, so the agent doesn't burn turns polling. ``` waitfor --cmd "curl -sf https://gitea.brasse-pc.eu/api/healthz" --interval 30s --timeout 20m waitfor --cmd "ssh pi5 docker ps --format '{{.Names}}'" --matches 'gitea' --timeout 10m waitfor … --then 'notifyr send --msg "gitea is back up"' ``` Exit 0 = condition met, exit 3 = timeout; last output is printed either way. `--then` runs a command on success (composes with `notifyr`). ### 2.3 `notifyr` — push notifications, send **and read** *(Claude: `PushNotification`)* The ntfy server **already runs on the Pi5** and is the house-wide notification bus (topics like `Info`, `pi5-server-fel`, `ci-fel` — see infra-Doc `services/observability.md`). `notifyr` is a thin client so every agent uses it the same way: ``` notifyr send --topic ci-fel --title "Build failed" --msg "agent-tools arm64 test: FAIL" --priority high notifyr read --topic pi5-server-fel --since 2h # poll mode: what has alerted lately? ``` `read` (ntfy's `?poll=1&since=…`) is the underrated half: it lets an agent *check what the infra has been complaining about* before/after a change. Config (`~/.config/notifyr/config.json`): server URL + token. ### 2.4 `pagepub` — publish an HTML/Markdown report to a URL *(Claude: `Artifact`)* Claude Code can publish reports as web pages; Gemini cannot. `pagepub` rsyncs a file to a static-file host on the Pi5 (nginx container behind NPM, e.g. `pages.brasse-pc.eu`) and prints the stable URL. ``` pagepub publish report.html --slug ci-report → https://pages.brasse-pc.eu/ci-report/ pagepub publish notes.md --slug pi5-audit # .md rendered to HTML with built-in template pagepub list | rm ``` Requires the static host to exist first (small infra task; goes in infra-Doc + NPM proxy host via `npmctl`). ### 2.5 `nbcell` — Jupyter notebook editing *(Claude: `NotebookEdit`)* `.ipynb` is JSON that's miserable to edit via `replace`. `nbcell` exposes cells as text: ``` nbcell list nb.ipynb # index, type, first line, exec count nbcell show nb.ipynb 3 # cell source (and outputs with --outputs) nbcell edit nb.ipynb 3 --from-file cell.py nbcell add nb.ipynb --at 4 --type code --from-file new.py nbcell rm nb.ipynb 7 ``` ### 2.6 `wtreectl` — disposable git worktrees *(Claude: worktree isolation for agents)* Claude Code can give each subagent an isolated git worktree. `wtreectl` does the same for any agent: ``` wtreectl new [--branch dev/foo] # prints the new worktree path wtreectl list wtreectl clean # removes worktrees with no changes ``` Lets two agent sessions work in the same repo without trampling each other. ### 2.7 `fanout` — parallel subagent orchestration *(Claude: `Workflow`, `Agent`)* — roadmap, not agreed Runs N prompts as parallel headless agent processes (`gemini -p` / `claude -p`) with a concurrency cap, collecting each result as JSON in an output dir. A poor man's `Workflow`: ``` fanout run jobs.json --max 3 --out results/ ``` Heavier than the other tools and overlaps with agent-helm's territory — park until there's a concrete need. ## 3. Suggested tools from infra history Grounded in what the agent has already been doing per infra-Doc (`maintenance-and-gaps.md`, `services/source-control-and-deploy.md`, `services/observability.md`, the per-service "operational quick-ref" blocks). These help **any** agent (Claude or Gemini) administer the fleet, and they respect the read-only sudo policy (`ssh/claude-sudo-policy.md`): everything below is read-or-notify; mutations still go through Björn's supervised tmux flow. ### 3.1 `giteactl` — Gitea repos, Actions runs and CI logs The biggest recurring friction. Today: CI status is polled ad hoc, and logs for private repos are only readable as zst files under `/srv/storage1/gitea/actions_log/…` on the Pi5. Wraps the Gitea REST + Actions API: ``` giteactl runs [--limit 5] # status, branch, duration giteactl log [--job N] # fetches + decompresses the zst log giteactl wait [--timeout 30m] # block until latest run finishes; exit 0 = green giteactl wait-quiet [--max-active 1] # block until ≤N heavy builds are running giteactl release [] # rolling-release assets + checksums ``` `wait-quiet` encodes the hard-learned rule "serialize pushes — >2 heavy builds take the Pi5 down": `giteactl wait-quiet && git push`. Composes with `waitfor`/`notifyr`. ### 3.2 `fleet` — one-shot health snapshot of the whole homelab Every service doc ends with the same hand-rolled loop over `ssh pi5-claude sudo claude-docker ps/inspect …`. `fleet` does that loop once, properly: ``` fleet status # containers (state, image, restarts), disk/mergerfs fill %, failed systemd units fleet status --host brasse-linux01 fleet checks # Uptime Kuma monitor states + last ntfy alerts (via notifyr read) ``` Read-only by construction (claude-docker wrapper + sudo allowlist), so it needs no new permissions. Output is a stable text table an agent can diff between runs. ### 3.3 `envaudit` — compose ↔ `.env` key auditor Grounded in a real incident (`${STORAGE1}` undefined in `/srv/.env` → bad mount, rollback) and in maintenance-and-gaps' inline-secret findings: ``` envaudit check /srv/dockge-staks --env /srv/.env → UNDEFINED ${STORAGE1} used by media-stack/compose.yaml → INLINE LDAP_ADMIN_PASSWORD hardcoded in openldap/compose.yaml (should live in .env) → UNUSED OLD_API_KEY defined but referenced nowhere ``` Pure text analysis of compose files — safe to run anywhere, catches the two failure classes that have actually happened. ### 3.4 `reghelper` — docker-registry catalog & hygiene The LAN registry (`192.168.0.19:5000`) has no UI, no auth and no cleanup story, and the backup plan explicitly wants it slimmed: ``` reghelper ls # catalog + tags + image sizes reghelper tags reghelper prune-plan --keep 2 # prints the delete+GC commands (does NOT run them) ``` `prune-plan` deliberately only *prints* the mutation commands for the supervised tmux flow — same pattern as the sudo policy. ### 3.5 Backup status reader — once Backrest/restic exists future-plans.md has the whole Backrest+restic design chosen but unbuilt. When it lands, a `fleet backups` subcommand (last snapshot age per source, repo size, last check result) closes the loop — an agent can then *verify* backups instead of trusting them. Not a separate tool; park under `fleet`. ### Cross-reference `/home/brasse/repos/dify-agent-tools/` already has a specced-but-unbuilt set of FastAPI tools (file/image sorting, face recognition, ST-card export…) for the Dify platform. Different runtime (HTTP tools vs CLI binaries), same philosophy — don't duplicate those here. ## 4. Suggested build order 1. `notifyr` — smallest, everything else composes with it, ntfy already runs. 2. `giteactl` — removes the biggest daily friction (CI logs + build serialization). 3. `waitfor` + `cronr` — turns both agents into unattended operators. 4. `fleet` — replaces the hand-rolled health loops in every runbook. 5. `envaudit`, `reghelper`, `pagepub`, `nbcell`, `wtreectl` — as needed. 6. `fanout` — only if a concrete multi-agent need shows up.