@blogic-cz/agent-tools
v1.7.5
Published
CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure platform, Azure DevOps, logs, sessions, and audit
Maintainers
Readme
@blogic-cz/agent-tools
Safe CLI wrappers for AI coding agents. Tools for GitHub, observability, databases, Kubernetes, Azure platform, Azure DevOps, logs, OpenCode sessions, audit history and generic JSON REST requests.
The REST credential proxy adds api-tool, an exported TypeScript client and the operator-only api-proxy runtime. Deploy its private credential store under a separate service identity outside agent authority, with verified TLS. Its client configuration is separate from agent-tools.json5.
Why
AI agents need CLI access. Giving them raw gh, kubectl, psql is dangerous — they can leak credentials, hit production, or run destructive commands.
These tools wrap each CLI with:
- Security guardrails — blocked paths, secret detection, dangerous command filtering
- Audit trail — every command goes through a wrapper, not raw shell
- Project config — agents use the right cluster, namespace, and database without guessing
- Consistent output — structured JSON or token-efficient TOON format for LLM consumption
Installation
Recommended: Copy the repo URL and tell your AI agent to install it. The agent will set up everything — dev dependency, config file, credential guard, skill — in the right places for your project.
Install @blogic-cz/agent-tools from https://github.com/blogic-cz/agent-tools as a dev dependency, install the agent-tools skill, set up the credential guard, and configure the tools I need for this project.
Manual installation
bun add -d @blogic-cz/agent-toolseffect is a peer dependency with the range ^4.0.0-rc.117, which matches
@effect/platform-bun. If your project depends on effect too, use a version in this
range. Two copies of effect are structurally incompatible types, so the values this
package exports stop typechecking in your project when the versions drift.
Skill installation
Install per-tool skills for only the tools your project uses:
npx skills add blogic-cz/agent-tools --path skills/gh-tool
npx skills add blogic-cz/agent-tools --path skills/observability-tool
npx skills add blogic-cz/agent-tools --path skills/db-tool
npx skills add blogic-cz/agent-tools --path skills/k8s-tool
npx skills add blogic-cz/agent-tools --path skills/az-tool
npx skills add blogic-cz/agent-tools --path skills/azdo-tool
npx skills add blogic-cz/agent-tools --path skills/logs-tool
npx skills add blogic-cz/agent-tools --path skills/session-toolOr install the all-in-one skill that covers every tool:
npx skills add blogic-cz/agent-tools --path skills/agent-toolsRequirements: Bun >=1.0.0
For agents reading this
Follow these steps to set up agent-tools in the consumer project.
Step 1 — Install
- JavaScript/TypeScript project (has
package.json): Install as a dev dependency:bun add -d @blogic-cz/agent-tools - Non-JavaScript project (no
package.json): Skip installation — run tools directly withbunx:bunx gh-tool pr status bunx observability-tool trace get <trace-id> bunx k8s-tool pods --env test
Step 2 — Configure
Ask the user which tools they need (gh, observability, db, k8s, az, logs, session, audit). Create agent-tools.json5 in the project root with only the selected tools configured. See examples/agent-tools.json5 for the full config reference with all options documented.
Minimal starting config:
{
$schema: "https://raw.githubusercontent.com/blogic-cz/agent-tools/main/schemas/agent-tools.schema.json",
}Step 3 — Credential Guard
Set up the credential guard for the agent's platform:
- Claude Code: Add the
PreToolUsehook to.claude/settings.json— see Setup for Claude Code - OpenCode: Create the plugin at
.opencode/plugins/credential-guard.ts— see Setup for OpenCode
Step 4 — Test & Demo
For each tool the user selected in Step 2:
- Run
bun <tool-name> --helpto verify it works (e.g.bun gh-tool --help) - Summarize the key commands available
- Show the user what data they have access to based on their
agent-tools.json5— e.g. which environments, clusters, namespaces, databases, or profiles are configured and reachable
Step 5 — Skill & Agent Docs
Install only the per-tool skills the project needs (recommended), or the all-in-one skill:
# Per-tool skills (recommended) — install only what the project uses
npx skills add blogic-cz/agent-tools --path skills/gh-tool
npx skills add blogic-cz/agent-tools --path skills/observability-tool
npx skills add blogic-cz/agent-tools --path skills/db-tool
npx skills add blogic-cz/agent-tools --path skills/k8s-tool
npx skills add blogic-cz/agent-tools --path skills/az-tool
npx skills add blogic-cz/agent-tools --path skills/azdo-tool
npx skills add blogic-cz/agent-tools --path skills/logs-tool
npx skills add blogic-cz/agent-tools --path skills/session-tool
# All-in-one skill (alternative) — all tools in a single skill
npx skills add blogic-cz/agent-tools --path skills/agent-toolsDo not run these commands for the user — they require interactive selection.
Available per-tool skills:
| Skill | Install when project uses |
| -------------------- | ------------------------------------------- |
| gh-tool | GitHub PRs, issues, workflows, CI checks |
| observability-tool | Tempo traces, Loki logs, Prometheus metrics |
| db-tool | SQL queries, schema introspection |
| k8s-tool | Kubernetes pods, logs, deployments |
| az-tool | Azure VMs, web apps, storage, AKS, ACR |
| azdo-tool | Azure DevOps pipelines, builds |
| logs-tool | Application log reading (local and remote) |
| session-tool | OpenCode session history browsing |
| agent-tools | All of the above in a single skill |
Then update the project's AGENTS.md and/or CLAUDE.md:
Add rows to the skills table for each installed skill (if one exists):
| GitHub PRs, issues, workflows | `gh-tool` | | Database queries, schema | `db-tool` |Add or update the Tooling section:
## Tooling For tool wrappers and operational patterns, load the relevant tool skill (`gh-tool`, `db-tool`, etc.).
Step 6 — Custom Tool Scaffold
Create an agent-tools/ directory in the project root with an example tool so the user has a working template for building project-specific tools. Copy the scaffold from examples/custom-tool/:
agent-tools/
package.json # private package depending on @blogic-cz/agent-tools
tsconfig.json # extends root tsconfig
noop.ts # placeholder export for typecheck
example-tool/
index.ts # ping-pong example using Effect CLIAfter creating the files, run bun install in the agent-tools/ directory (or from the workspace root if it's a monorepo). Then verify:
bun run agent-tools/example-tool/index.ts pingQuick Start
- Install the package in your project
- Create
agent-tools.json5in your project root:
{
$schema: "https://raw.githubusercontent.com/blogic-cz/agent-tools/main/schemas/agent-tools.schema.json",
defaultEnvironment: "test", // optional: any string (e.g. "local", "test", "prod")
audit: {
retentionDays: 90,
dbPath: "~/.agent-tools/audit.sqlite",
},
vpns: {
exampleVpn: {
// auto defaults to true:
// darwin -> macos-scutil, linux -> linux-nmcli, win32 -> windows-rasdial
name: "ExampleVPN",
// Reuse package-managed connections for 30 seconds after the last command. Set 0 for immediate cleanup.
idleDisconnectMs: 30000,
// Total window for stop plus disconnected-status confirmation.
disconnectTimeoutMs: 10000,
// Optional: pass IPSec shared secret to macOS scutil from env without storing the value in config.
secretEnvVar: "EXAMPLE_VPN_IPSEC_SHARED_SECRET",
},
},
kubernetes: {
default: {
clusterId: "your-cluster-id",
namespaces: { test: "your-ns-test", prod: "your-ns-prod" },
prerequisites: [{ type: "vpn", key: "exampleVpn" }],
// agent-tools starts disconnected VPNs, shares package-local leases, and disconnects only connections it owns.
},
},
logs: {
default: {
localDir: "apps/web-app/logs",
remotePath: "/app/logs",
},
},
observability: {
// Profile name (selected via --profile, or used automatically when it's the only one)
default: {
// Environment name (selected via --env)
environments: {
local: {
url: "http://localhost:40300",
prometheusUid: "prometheus",
lokiUid: "loki",
},
},
},
},
}- Run tools:
bun gh-tool pr status
bun observability-tool trace get 0b7bdf0dde1c55458364ba5588a8075e --env local
bun observability-tool trace search '{ status = error }' --env local --start now-6h
bun k8s-tool kubectl --env test --cmd "get pods"
bun logs-tool list --env local
bun audit-tool list --limit 20bun gh-tool pr review-triage # interactive summary of PR feedback
bun gh-tool pr watch --prs 12,34 --format jsonl # transition-only multi-PR CI stream
bun k8s-tool pods --env test # list pods (structured command)- Hook up the credential guard in your agent config (Claude Code, OpenCode, etc.):
import { handleToolExecuteBefore } from "@blogic-cz/agent-tools/credential-guard";
export default { handleToolExecuteBefore };Guard refusals include recovery guidance for the policy that blocked the command. Direct CLI
refusals preserve the configured wrapper or suggestion. Built-in wrappers support --help;
consult this README and the tool's own help to find an existing bounded operation.
Interactive and login shells load startup files. Supported noninteractive literal forms pass only when the guard proves their complete closed body. The guard sees command text, not external script contents, so removing shell flags does not certify a script. The guard provides no generic approved external-script executor. Review or implement a repository-owned bounded operation with secret injection and filtered output when existing tools do not cover the task. Environment approval permits exact nonsensitive reads, not script execution or environment forwarding.
Tools
| Binary | Description |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- |
| gh-tool | GitHub CLI wrapper — PR management, issues, workflows, composite commands (review-triage, reply-and-resolve) |
| observability-tool | LGTM wrapper — Tempo traces, Loki log correlation, and Prometheus metrics via Grafana |
| audit-tool | Audit trail browser — inspect recent tool invocations and purge old entries |
| db-tool | Database query tool — SQL execution, schema introspection |
| k8s-tool | Kubernetes tool — kubectl wrapper + structured commands (pods, logs, describe, exec, top) |
| az-tool | Azure platform tool — read-only inspection of VMs, web apps, storage, AKS, ACR |
| azdo-tool | Azure DevOps tool — pipelines, builds, repos |
| logs-tool | Application logs — read local and remote (k8s pod) logs |
| session-tool | OpenCode session browser — list, read, search sessions |
| api-tool | JSON REST client using an operator-issued capability |
| api-proxy | Isolated operator service holding upstream REST credentials |
All tools support --help for full usage documentation. Legacy agent-tools-* binary names (e.g. agent-tools-gh) still work for backwards compatibility.
Kubernetes command safety
k8s-tool parses generic kubectl commands into arguments and invokes kubectl directly. Shell pipelines, chaining, substitution, user overrides of the configured cluster or credentials, mutating config/auth subcommands, and cluster-info dump are rejected. Direct Secret reads, raw kubeconfig output, filename/kustomize reads, and kubectl diff are also blocked.
Pod exec is limited to direct redis-cli PING/INFO and ls diagnostics. Generic exec cannot read file contents; use logs-tool, which confines files to the configured log directory, tails them through an internal structured operation, and applies the same case-insensitive literal substring filter locally and remotely. Configured log directories are a trusted boundary and must not permit adversarial symlink replacement during reads.
Azure command safety
Azure is split across two binaries. azdo-tool covers Azure DevOps (pipelines, repos, devops invoke); az-tool covers the Azure platform (vm, webapp, storage, aks, acr, monitor, …). Sending a DevOps group to az-tool fails with a pointer to azdo-tool rather than running.
az-tool resolves the verb positionally — the last word before the first flag — so a flag value can never be read as a command. Only read-only verbs are allowed (list, show, describe, exists, search, history, version, validate, wait, plus the list-, show-, check-, and get- families); mutating verbs and unknown verbs are both rejected. Credential reads are blocked regardless of verb: the get-access-token/get-credentials/list-keys/list-connection-strings/list-publishing-profiles family, and any command whose group path contains secret, secrets, key, keys, credential, credentials, appsettings, admin-key, query-key, sas, password, or connection-string — for those the listing itself returns values (storage account keys list, webapp config appsettings list). Key Vault is narrower: keyvault secret list/list-versions return names and attributes and are allowed, keyvault secret show is not, and the key/certificate subgroups are allowed because they expose only public material. Shell syntax is rejected and commands are spawned as an argument vector, never through a shell.
The subscription comes from the azurePlatform config profile and is appended to every command; a user-supplied --subscription is rejected, and with no azurePlatform section the tool refuses to run rather than falling back to whatever az login last selected. The set of configured profiles is therefore the allowlist of reachable subscriptions.
Two optional scope guards sit on top. A profile keyed prod/production, or carrying production: true, cannot be reached through auto-selection or the default key — --profile must name it explicitly, mirroring k8s-tool's refusal of implicit prod. And allowedResourceGroups rejects any command naming a group outside the list; an empty or absent list allows every group in the subscription. A group is recognised both from -g/--resource-group — every occurrence, since the Azure CLI is argparse-based and a repeated flag takes the last value — and from the /resourceGroups/<name> segment of a full ARM resource ID passed to a flag such as --scope or --resource. --ids is rejected outright because it carries its own subscription as well as its own resource group, side-stepping both guards. The resource-group check only fires when a command names a group, so subscription-wide reads such as vm list remain unrestricted — it guards against touching the wrong group, it does not partition the subscription.
azdo-tool keeps its allowlist of read-only operations (list, run, show, show-tags) and rejects create/delete/update/cancel/queue anywhere in the command. run is accepted only in the pipelines group — acr run and acr task run execute arbitrary commands in Azure and are blocked. The acr and account groups remain reachable from azdo-tool for backwards compatibility; new work should use az-tool for them. Because those two groups address the Azure platform rather than Azure DevOps, the platform credential rules apply to them here as well — acr credential show is refused by both tools. The verb and segment lists behind that live in src/shared/azure-credentials.ts so the two tools cannot drift apart.
Gist commands
bun gh-tool gist list
bun gh-tool gist view --id <gist-id>
bun gh-tool gist create --body "snippet" --filename example.txt
bun gh-tool gist edit --id <gist-id> --body "updated" --filename example.txt
bun gh-tool gist delete --id <gist-id> --confirmGists are secret by default. gist delete prints a dry run unless --confirm is passed. gist edit requires a content or mutation flag and never opens an editor.
pr create refuses when an open PR already matches the head and base branches. Pass
--update-if-exists to update that open PR explicitly. Output includes updated: true
for an update and updated: false for a new PR. Only open PRs are selected, and their
state is rechecked before editing. A concurrent close or merge after that read can still
race with the edit. Bare or current heads match the selected repository owner's PRs;
use --head owner:branch to opt into updating a fork's PR. pr edit --body "" clears
the body; pr view always includes body.
pr merge --match-head-commit <full-40-character-SHA> requires the PR head to match
the reviewed commit, including during a dry run. Missing or invalid head information,
lookup failures, and mismatches refuse the merge before any mutation. With --confirm,
the same SHA is passed to GitHub's merge condition so a concurrent head change also
refuses the merge. The asynchronous stack fallback cannot enforce this condition and
is refused when the flag is present. Keep the reviewed SHA requirement and use a merge
path that enforces it after reviewing the current head.
Stacked pull requests
GitHub's native stacked PRs refuse the GraphQL merge and require the asynchronous REST merge, which lands every unmerged PR in the stack up to and including the requested one.
bun gh-tool pr stack view --pr 123 # membership and order
bun gh-tool pr stack merge --pr 123 --strategy squash # ordered plan, merges nothing
bun gh-tool pr stack merge --pr 123 --strategy squash --confirm # one request, whole stackOrdinary pr merge refuses a stack member that has open PRs below it, and names them.
Merging such a member through the asynchronous endpoint would land those PRs too, which
its own dry-run never disclosed. The bottom member still merges normally.
stack merge targets the top open member, so one call lands the stack. It checks every open
member first and refuses the whole operation if any is a draft, conflicting, or has failing or
pending checks — a partial stack merge leaves a parent on the trunk and a broken child.
pr stack unstack dissolves a stack. GitHub owns the base of every stacked PR, so pr edit
--base on a member refuses and names that command rather than letting GitHub's bare 422
through; merging the stack needs no retarget at all. pr review-triage counts open members
below the PR as blocking, because a member cannot land on its own while they are open.
Membership comes from GitHub's stacks API, never from chaining baseRefName to headRefName.
That chain both over-reports (a promotion PR whose head is main adopts every ordinary PR as a
child) and under-reports (a PR based on a stack member's branch need not be in the stack).
A live end-to-end proof lives in tests/e2e/stack-merge.e2e.ts. It is excluded from
bun run check because it creates and mutates a throwaway GitHub repository:
AGENT_TOOLS_E2E=1 bun tests/e2e/stack-merge.e2e.tsIt builds a real three-deep stack and asserts the thing no mock can prove — that each squash commit on the trunk adds only its own PR's changes, so a squashed parent is not replayed inside its child.
gh-tool machine contracts
pr view adds headSha and baseSha; failed-check evidence adds the same SHA pair. Review summaries, inline comments, and threads add commitSha plus feedbackOrigin: current_head only for an exact commitSha === headSha, pre_existing for a different known SHA (not an obsolescence verdict), and unknown when either SHA is absent. Issue comments always use commitSha: null and feedbackOrigin: unknown. review-triage preserves existing fields and adds inlineComments plus per-kind feedbackOriginCounts; batch triage returns the same object per PR. pr request-review --reviewers alice,bob emits sorted submittedReviewers (normalized input), newlyRequested and alreadyPending (the submitted logins split by whether a pending request already existed, so a fresh re-request is distinguishable from a no-op), and requestedReviewers (GitHub-confirmed result). pr last-human-reviewer derives currentRequestedReviewers from live reviewRequests only; timeline events are not replayed because GitHub clears a pending request on review submit without emitting ReviewRequestRemovedEvent.
pr watch --prs 12,34 --until terminal --format jsonl accepts at most 50 unique, digits-only PR numbers and emits only JSONL state transitions. Identity uses repo/pr/headSha/runId/attempt/jobId; runId, attempt, and jobId are omitted from an event rather than emitted as null, and checkId is no longer emitted. State/bucket revisions emit even when identity stays stable; supersedes appears only when identity changes. Open PRs with no checks become terminal only after three stable empty snapshots, allowing bounded GitHub eventual consistency, and carry checksObserved: false — a terminal snapshot with no observed check is never green evidence. pr checks, batch checks, triage, and batch triage keep stderr silent with --format json; JSONL watch is also informationally silent. Failures still return structured nonzero errors on stderr.
Useful flows:
bun gh-tool pr checks-failed --pr 123 --with-logs --format json # includes diagnosis
bun gh-tool pr rerun-checks --pr 123 --failed-only --watch --timeout 600
bun gh-tool pr trigger-checks --pr 123 --workflow dotnet-pull-request.yml # only when zero checks reported
bun gh-tool pr watch --prs 123,124 --format jsonl --timeout 600
bun gh-tool pr edit-comment --comment-id 456 --body-file correction.md # Edit your own review comment
bun gh-tool pr edit-comment --comment-id 789 --kind issue --pr 123 --body "Corrected detail"
bun gh-tool pr reply-and-resolve --comment-id 456 --body "Done" # infers PR and thread
bun gh-tool pr request-review --repo be --pr 123 --reviewers alice,bob
bun gh-tool pr review --pr 123 --event comment --body "Notes, no verdict"
bun gh-tool pr review --pr 123 --event request-changes --body-stdin --confirm <<'EOF'
Rebase breaks two call sites; the Interop spec belongs in the domain module.
EOF
bun gh-tool pr review --pr 123 --event approve --confirm
# Optional --pr/--thread-id retain legacy flow and are validated before either mutation.pr edit-comment defaults to inline review comments; pass --kind issue for PR discussion
comments. It checks the authenticated GitHub user owns the comment before editing, and --pr
also verifies the comment belongs to that pull request.
pr review creates and submits a review in one call, for a verdict reached from a diff with no pending review to submit. pr submit-review takes the same --event for a review that is already pending. --event approve and --event request-changes change whether the PR can merge, so both require --confirm, the same gate pr merge uses; --event comment carries no verdict and needs none. --event comment and --event request-changes require a non-empty body — GitHub rejects a bodyless one. GitHub also refuses a verdict on your own PR; that failure returns a hint pointing at --event comment instead of the raw API error.
Reruns preflight every target before mutation and fail closed with evidence_unavailable when attempt jobs or logs cannot be read. Failed jobs use one gh run rerun RUN --failed mutation per workflow run. Without --watch, output returns current attempt metadata immediately; with --watch, discovery and watching share one absolute --timeout deadline and report discovery_timeout or watch_timeout with latest attempt state. Repeated matching pre-test infrastructure failures return escalation_required without mutation. See skills/gh-tool/SKILL.md for operating guidance; this section is canonical for added output fields.
Zero reported checks is a state, not an error: gh pr checks exits nonzero on an empty result, and every command that reads checks maps that to []. pr trigger-checks covers the case where GitHub dropped the pull_request event and no run exists at all — it dispatches the named workflow_dispatch workflow on the PR head branch, then compares the created run's headSha back to the PR head and reports matchesPrHead. A run dispatched on the wrong ref goes green for another branch and must never be read as PR evidence, so workflow run also returns the discovered runId/headSha instead of a bare dispatched: true.
pr feedback returns the whole inventory by default. Narrow it with --only visible-open|needs-human-reply|current-head (narrowed filters drop issue comments), drop bots with --exclude-authors github-actions,dependabot, and read omitted for what each filter removed. Base64 report payloads in bodies are replaced with [base64 payload omitted]; pass --raw-bodies to keep them. review-triage --omit reviews,inlineComments trims a repeated snapshot to the verdict and check state, and lists what it left out in omittedSections.
An empty thread list can mean the reviewer is still drafting: comments inside a pending review are invisible to the API until the reviewer submits, and they keep their draft time in createdAt. pr threads and pr comments therefore also report reviewId and updatedAt. Equal reviewId values mark one submitted batch; join that id to the matching reviews[].submittedAt from pr feedback for the exact time the batch became visible. updatedAt is a cheap proxy for the same moment, but it also moves when someone edits the comment. Never conclude from an older createdAt that an earlier scan missed anything.
Call the wrappers through bun run --silent <tool> in repos that expose them as package scripts; without --silent every invocation echoes the resolved command line into the agent's context.
audit-tool reads the same SQLite file the wrappers write to. By default that file lives at ~/.agent-tools/audit.sqlite, and you can override both path and retention per repo with the global audit config section.
Audit Logging
Every tool invocation is automatically recorded to a local SQLite database — zero configuration required. The audit trail captures which tool ran, what arguments it received, how long it took, whether it succeeded, and which project directory it was called from.
How it works
Each CLI wrapper (gh, observability, k8s, db, az, logs, session, audit) writes a row to ~/.agent-tools/audit.sqlite on every execution. Logging is fire-and-forget — if the database is unavailable or write fails, the tool continues normally. Audit never blocks or slows down your workflow.
Entries older than retentionDays (default: 90) are automatically purged on each write.
Browsing the audit trail
# Recent 20 entries (default)
bun audit-tool list
# Last 50 entries, JSON format
bun audit-tool list --limit 50 --format json
# Filter by tool
bun audit-tool list --tool gh
# Filter by project directory
bun audit-tool list --project /Users/me/my-repo
# Purge entries older than 30 days
bun audit-tool purge --days 30Audit Configuration
Both the database path and retention period are configurable in agent-tools.json5:
{
audit: {
retentionDays: 90, // days before auto-purge (default: 90)
dbPath: "~/.agent-tools/audit.sqlite", // database file location
},
}All settings are optional — audit works out of the box with sensible defaults.
What gets recorded
| Column | Description |
| ----------- | ----------------------------------------------------------------------- |
| ts | ISO 8601 timestamp |
| tool | Tool name (gh, observability, k8s, db, az, logs, session) |
| project | Working directory (process.cwd()) |
| args | Command-line arguments (JSON array) |
| duration | Execution time in milliseconds |
| success | 1 (success) or 0 (failure) |
| error | Error message if failed, null otherwise |
| exit_code | Process exit code |
Configuration
Config is loaded by walking up from the current working directory to the nearest regular config file:
agent-tools.jsonagent-tools.json5
That nearest regular config is the base. Local override files are then merged from that directory down to the current working directory:
agent-tools.local.jsonagent-tools.local.json5
Later files override earlier files. Objects are merged deeply; arrays and primitive values are replaced. Missing config = zero-config mode (works for gh-tool; others require config).
Use agent-tools.local.json5 for machine-specific ports, paths, and worktree overrides. Keep local files gitignored.
Global Settings
Use defaultEnvironment to set the default target for tools that support environments (k8s-tool, logs-tool, db-tool). Passing --env explicitly always takes precedence. Note that tools will block implicit production access if defaultEnvironment is set to "prod".
{
defaultEnvironment: "test",
}IDE Autocompletion
Add $schema to your config file:
{
$schema: "https://raw.githubusercontent.com/blogic-cz/agent-tools/main/schemas/agent-tools.schema.json",
}Named Profiles
Each tool section supports multiple named profiles. Select with --profile <name>:
{
azure: {
default: { organization: "https://dev.azure.com/main-org", defaultProject: "platform" },
legacy: { organization: "https://dev.azure.com/old-org", defaultProject: "app" },
},
}bun azdo-tool cmd --cmd "pipelines list" # uses "default" profile
bun azdo-tool cmd --cmd "pipelines list" --profile legacy # uses "legacy" profileProfile resolution: --profile flag > auto-select (single profile) > "default" key > error.
Full Config Reference
See examples/agent-tools.json5 for a complete example with all options documented.
Authentication
Each tool uses its own auth method — no unified token store:
| Tool | Auth Method |
| -------------------- | -------------------------------------------------------------------------------------------- |
| gh-tool | gh CLI session (gh auth login) or GITHUB_TOKEN env var |
| observability-tool | Grafana URL from config plus optional token from tokenEnvVar |
| k8s-tool | Existing kubectl context (kubeconfig). Cluster ID from config resolves context automatically |
| az-tool | az CLI session (az login). Subscription pinned by the azurePlatform config profile |
| azdo-tool | az CLI session (az login) |
| db-tool | Password from env var defined by passwordEnvVar in config (e.g. AGENT_TOOLS_DB_PASSWORD) |
| logs-tool | No auth — reads local files or uses k8s-tool for remote access |
| api-tool | Caller capability and proxy HTTPS origin in client configuration |
| api-proxy | Private operator store and policy under a separated service identity |
The REST proxy uses its separate private operator store described in REST proxy setup. Other tool configs reference credential sources. Provider secrets are never stored in the caller client config file. The db-tool config references env var names only:
{
databases: {
default: {
passwordEnvVar: "AGENT_TOOLS_DB_PASSWORD", // tool reads process.env[passwordEnvVar] at runtime
},
},
}Endpoint overrides: hostEnvVar and portEnvVar
A database environment can also take its host and port from environment variables:
| Field | Effect |
| ------------ | -------------------------------------------------------------------------- |
| hostEnvVar | Name of an environment variable that overrides the literal host when set |
| portEnvVar | Name of an environment variable that overrides the literal port when set |
These are overrides with fallback, not requirements — the opposite of passwordEnvVar, which fails when its variable is unset:
- variable set to a value → that value wins over the literal in the config;
- variable unset or empty → the literal
host/portin the config is used, and no error is raised; portEnvVarset to something that is not an integer between 1 and 65535 → the command fails. It never falls back to the literal, because that would query a different database while you believe the override applied.
The variable is read from the process environment first (Bun loads .env files into it), then from ~/.zshrc exports.
The typical use is a repository where every git worktree runs its own database container on its own host port, published by that worktree's .env:
{
database: {
default: {
environments: {
local: {
host: "127.0.0.1",
port: 40543, // the main checkout's database
user: "app",
database: "app",
portEnvVar: "APP_POSTGRES_PORT", // a worktree's .env sets this to e.g. 40643
hostEnvVar: "APP_POSTGRES_HOST", // optional, same fallback rule
prerequisites: [],
},
},
},
},
}Run from the main checkout, where APP_POSTGRES_PORT is unset, db-tool connects to 127.0.0.1:40543. Run from a worktree whose .env sets APP_POSTGRES_PORT=40643, the same config connects to 127.0.0.1:40643 — so db-tool and job-tool inspect that worktree's own database instead of the main checkout's.
Database VPN prerequisites can be set at the database profile or environment level. If an environment declares vpn or prerequisites, that environment config replaces the profile prerequisites; prerequisites: [] explicitly disables inherited VPN setup. DB commands try the query directly first and only connect VPN prerequisites if direct access fails. Package-managed VPNs remain reusable for idleDisconnectMs (default 30000) after the last lease; 0 restores immediate cleanup. Preconnected or leave-running connections are treated as external and never stopped automatically. If runtime state is corrupt, unknown, or contains legacy artifacts, first stop all agent-tools processes using the VPN, then remove that VPN state directory under ~/.agent-tools/runtime/vpn-prerequisites.
{
vpns: {
officeVpn: { name: "OfficeVPN" },
prodVpn: { name: "ProdVPN" },
},
database: {
default: {
vpn: "officeVpn",
environments: {
local: {
host: "127.0.0.1",
port: 5432,
user: "app",
database: "app",
prerequisites: [], // no VPN for local/direct access
},
prod: {
host: "db.prod.internal",
port: 5432,
user: "readonly",
database: "app",
passwordEnvVar: "AGENT_TOOLS_DB_PROD_PASSWORD",
vpn: "prodVpn", // overrides database.default.vpn
},
},
},
},
}Set the values in your shell:
export AGENT_TOOLS_DB_PASSWORD="your-password"
export GITHUB_TOKEN="ghp_xxxxxxxxxxxx"The wrappers check outgoing user content and redact recognized credentials in agent-facing output, errors, and audit records. Detection uses credential patterns and exact values of sensitive process environment variables of at least eight characters. Unknown credentials and transformed or encoded values may evade detection.
Credential Guard
The guard blocks agents from accessing sensitive files, leaking secrets, and running dangerous commands. Every block message links to the source — if an agent thinks a block is wrong, it can fork the repo and submit a PR.
What it blocks:
- Reads of secret files (
.env,.pem,.key,.ssh/, etc.) - Writes containing detected secrets (API keys, tokens, passwords)
- Dangerous shell patterns (
printenv,cat .env, etc.) - Direct CLI usage (
gh,kubectl,psql,az) — must use wrapper tools
Content checks in wrappers
GitHub text and file publication, database queries, Azure and Kubernetes command arguments, and observability requests are checked before execution. GitHub file uploads use snapshots of the inspected bytes. Generated release notes and existing pending reviews or draft releases are inspected before publication. Authentication headers and connection credentials remain inside the tools.
GitHub API requests validate the serialized body that will be sent, including JSON keys and decoded string values. Serialization happens once, before authentication lookup or network access. File publication checks both the supplied path and its resolved target against known credential locations before reading content. This includes SSH, AWS, Kubernetes, Azure, and common CLI credential stores. An unknown filename can still contain credentials, so content checks also apply.
The two direct GitHub CLI launch paths receive an explicit environment containing supported GitHub authentication, host/configuration, proxy/CA, and platform variables. Unrelated parent variables and runtime loader settings are not inherited. Other tools retain their own environment contracts; this does not isolate the parent agent or prevent GitHub CLI access to its configured credential files.
Results retain their JSON or TOON structure, with detected credentials replaced by [REDACTED]. Audit records are sanitized before storage; reading older records also sanitizes the returned content but does not rewrite the database.
These checks apply to these wrappers. They do not intercept arbitrary shell programs or other agent tools. GitHub does not offer an atomic content-version condition for submitting pending reviews or publishing existing drafts, so a concurrent remote edit between inspection and publication remains possible.
Setup for Claude Code
Claude Code uses shell command hooks. The package ships a ready-made wrapper script.
- Add to
.claude/settings.json(or.claude/settings.local.jsonfor gitignored config):
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "bun node_modules/@blogic-cz/agent-tools/src/credential-guard/claude-hook.ts"
}
]
}
]
}
}That's it. The hook reads tool input from stdin, runs the guard, and exits with code 2 (blocked + reason on stderr) or 0 (allowed).
The guard permits static printenv reads and simple echo/printf expansions only for exact names in credentialGuard.allowedEnvironmentVariables. Quoted empty defaults such as "${NAME:-}" follow the same display rules, including printf data arguments with a literal format; unquoted defaults, nonempty defaults, and other parameter operators stay blocked. The default list is empty. Multiple names are allowed only when each name is approved. Approved environment values may be displayed, but redirecting their pipeline or passing them to an executor stays blocked.
Python inline programs and quoted stdin heredocs can also display approved values through os.getenv("NAME"), os.environ.get("NAME", "default"), or os.environ["NAME"]. The complete program must import unaliased os, plus json for JSON display, followed by print(...) statements. json.dumps(...) supports dictionaries and lists of approved reads, including comprehensions over a nonempty literal list of approved names. Every dictionary key and environment read must use an approved name. Each projection has at most 16 entries; the program has at most 65,536 characters. Computed or unbounded names, aliases, escaped or interpolated strings, additional statements, and whole-environment output remain unsupported. The same redirection and pipeline restrictions apply.
Literal assignments such as F=README.md; cat "$F" work in supported static commands. Bounded proofs also support a literal for loop over a fixed list, a single ASCII-letter path binding such as cd PATH && A=docs/report.md && shasum -a 256 "$A" or F=README.md; cat "$F", including the existing git add -- "$T/file" form, and literal path braces such as rg -n -i PATTERN src/{a,b} docs/{guide,api}.md. Path braces work with supported passive text commands and cat, and support one unquoted comma list per path, at most 16 members per list and 32 generated paths per command, within 64 KiB. Nested braces, ranges, empty members, mixed quoting, command positions, and compound commands remain unsupported. Path bindings accept literal values and ;, newline, or && sequencing; a binding conditional on successful cd is proved only for uses in that same && list. Their uses must be supported read-only file operands or explicit git add -- paths. Rebinding, executable operands, startup-variable names, and uncertain scopes cannot establish this proof. Loop binders are a single lowercase letter or T, and their uses must occupy supported data operands. Every body command and expanded path is checked; unsupported control flow, mutations such as printf -v, and unknown command roles cannot establish the proof. Other variable assignments and brace expansions retain conservative refusal. Fully quoted literal braces remain usable in static searches.
Ordinary navigation operands such as cd "$HOME" and git -C "$WORKTREE_ROOT" status --short do not need display permission. This does not authorize printing those values, using them as code/options, or reading unknown file paths. In printf, approved values belong in data arguments after a fixed nonmutating format, for example printf -- '%s' "$WORKSPACE_LABEL".
Literal herdr agent prompt commands can be composed with independent cat readers and passive text pipelines. Prompt text stays data; command substitutions, environment mutation, unknown consumers, and executable pipelines cannot establish the proof. All reader operands and redirects still receive the ordinary sensitive-path checks. Literal loops also support herdr pane read ID --source recent-unwrapped --lines N, with the binder restricted to ID, and 2>&1 file-descriptor duplication. A bounded Python stdin processor supports only import json,sys; d=json.load(sys.stdin); print(d["result"].get("text","") if isinstance(d.get("result"),dict) else d), including insignificant spacing and single lowercase-letter local renaming. Extra imports, statements, calls, runtime options, arguments, and other Python programs cannot establish this loop proof. The existing loop size and command limits apply.
Prompt composition also supports one cd [--] PATH with a literal relative or absolute directory at the start of the final && chain. All subsequent inspection must remain in that chain, so a failed cd cannot reach its readers. Directory names may contain quoted spaces. Multiple cd commands, later ; or newline boundaries, tilde or variable expansion, other cd options, background jobs, ||, and navigation in pipelines remain unproved. Supported Git inspection can use literal -C PATH prefixes with grep, status, rev-parse, or rev-list; execution and startup options remain unsupported. Navigation composition refuses relative file redirects, including redirects on cd and Git commands, because those use the shell directory before the command runs. Absolute redirects still receive ordinary path checks. Named directories receive sensitive-path checks with directory-prefix semantics. Only a proved cd chain qualifies relative reader operands with that directory; Git -C qualifies that command's file operands without changing shell state. Git grep uses the shared grep pattern-versus-file checks. This checks textual operands and does not resolve aliases, symlinks, ambient directories, or command lookup.
The guard recognizes rtk, rtk proxy, command, and supported env prefixes and checks commands in pipelines and chains. Quoted filenames, comments, and redirects retain sensitive-path checks. Jq filters and inline awk programs qualify only when their actual program and options establish passive behavior. Literal environment-command text in rg, grep, git grep, echo, and printf is allowed only with supported command roles and passive pipeline consumers. Environment-source evidence and startup identity checks also apply to the complete original invocation. Custom dangerous-command patterns apply to both the original input and proved expansions.
Jq admission uses a closed grammar for .field traversal, literal string/numeric indexes, []/[]?, parentheses, //, pipelines, nested objects/arrays, literal JSON values, comparisons, arithmetic and and/or. It admits the pure zero-argument filters length, keys, keys_unsorted, type, values, empty, add, min, max, sort, unique, flatten, not, reverse, to_entries and from_entries; single-argument calls are map, map_values, select, contains, has, sort_by, group_by, unique_by, any, all and join. Operator precedence remains jq's responsibility; the guard validates complete operands and delimiters without evaluating a filter. Unknown functions/syntax, comments, interpolation, variables, env/$ENV, imports/includes and program-loading options refuse. A string or field named env stays data. Limits are 65,536 UTF-8 bytes, 4,096 tokens and 64 nested expression levels. Known formatting/input flags must precede the literal filter. File operands, redirects, downstream consumers and configured command/path rules still apply. Unquoted file globs and tilde paths anywhere in a compound jq invocation refuse; quote or escape genuinely literal filenames, or provide explicit nonsensitive paths.
This is a textual jq proof under trusted native executables and host startup configuration. Normal jq loads a home .jq file, which can redefine builtins; the guard does not inspect it or prove module provenance. -L does not suppress that startup file. The supported exact POSIX child-process form env HOME=/dev/null jq 'FILTER' file.json avoids the user home-file lookup. Other explicit startup assignments/env prefixes anywhere in the compound invocation do not receive the jq proof, including producer/consumer cwd mutations. Literal directory changes must satisfy the existing gated navigation proof. Neither form certifies JSON contents, native binaries, aliases or symlinks.
Every jq compound command must have a supported native data, navigation, declared frontend or existing literal-script role. Unknown heads, builtin wrappers, directory-stack commands and shell state mutations cannot establish context. Declared CLI producers and literal script roles retain their ordinary policies; their source contents are not certified passive.
Syntax compilation supports python3 -I -S -m py_compile src/main.py, also python, known RTK/command wrappers and combined/reordered -I, -S, -B flags before -m. Both isolation flags are required: -I excludes cwd/user imports and Python startup variables, and -S disables site hooks. The module must be exactly py_compile, followed by 1–32 literal nonsensitive .py paths. Stdin filename lists, unknown flags/modules, expansions, startup mutations and executable output consumers refuse. Target source is compiled without execution, but __pycache__ writes still occur with -B, and error output may quote source. This does not inspect source contents or attest filesystem/runtime identity.
With any additionalBlockedPaths rule, implicit compiler cache/temp filenames cannot be proved against that policy, so py_compile refuses. Use a cache-free syntax parse instead: python3 -I -S -B -c 'import ast; ast.parse(open("src/main.py", "rb").read(), filename="src/main.py")'. This form remains subject to source-path, inline-program and command policies. It parses the named source without executing it or writing target bytecode caches. The refusal explains the cache boundary separately from environment access.
Paths are checked against sensitive filename patterns and configured path rules. Supported proofs apply those checks to their expanded operands. Ordinary parent-directory and absolute paths may be allowed; the guard does not resolve filesystem paths or symlinks, or enforce repository confinement. The default kubeconfig pattern protects every path segment containing kubeconfig, including ancestor directories and names without separators such as mykubeconfig or kubeconfig2. Only a final ordinary Markdown basename that does not start with kubeconfig, such as check-kubeconfig-topology.md or cluster-kubeconfig-notes.md, has the documentation exception; the .md suffix must be the exact end of the path. A kubeconfig-containing ancestor still matches the blocked pattern; kubeconfig.md remains protected. Existing explicit path allowances take precedence over blocked patterns, including template names such as prod-kubeconfig/.env.example and configured additionalAllowedPaths. Otherwise, configured blocked paths still apply to documentation names. This filename convention does not attest file contents, and there is no blanket Markdown or memory-directory exception.
Recognized Linux process-environment paths such as /proc/self/environ, /proc/thread-self/environ, and PID/task variants are blocked before configured path exceptions.
Single literal echo/printf writes with output redirection and single cat/tee heredoc writes are allowed. Heredoc braces are data. Use a quoted delimiter for literal code; unquoted heredocs containing $, backticks, or backslashes do not receive the literal-write exception. The exception permits only supported combinations with navigation or selected inspections; unsupported following commands remain outside it. Supported complete Python stdin heredocs are checked as inline programs, with the normal runtime-argument, path, execution, and environment checks.
A supported Python stdin heredoc can be followed by complete literal cat/tee heredoc writers and passive consumers. The guard retains every writer header, file operand and redirect while excluding only the proved literal bodies from path evidence. Incomplete bodies, executable/unknown headers and unsupported following commands gain no exception. Heredoc composition is bounded to 32 bodies and 65,536 bytes. This separates prose from path operands; Bash writes/output are not content attestation, and Write/Edit secret scanning remains separate.
Quoted single-letter literal bindings such as D="/workspace/docs/topic"; ls -la "$D"; grep -n X "$D/file.md" receive the existing closed path proof. Finite literal loops can use the binder inside a jq filename, for example for i in 1 2; do jq '.rows // [] | length' sample-$i.json; done; the filter/options and every materialized path are checked. A derived path list such as f=$(git grep -l PATTERN -- '*.cs') remains unproved: the input-only guard cannot inspect output paths, shell splitting or sensitive matches. Use direct git grep output or separately checked literal paths. This refusal identifies a local derived binding rather than claiming ambient environment access. A literal binding in a script/executable operand also remains outside the file-data proof.
The same finite loop proof admits numeric --pr selectors for bun run gh-tool pr checks, checks-failed, info and review-triage, with optional literal --repo, and git diff [one literal revision-shaped operand] -- literal filenames. Git diff loop prefix options, including output/order files and no-index mode, remain unproved. Git can implicitly compare filesystem paths outside a worktree or when a path points outside it. The optional operand also receives ordinary/custom file policy as a potential path, including proved cwd and Git -C qualification; the guard does not certify repository context or guarantee that it is a revision. Every generated path receives the ordinary policy. One literal local directory binding may be used only by the first successful gated cd, followed by supported literal commands including Git clone without executable configuration/upload-pack options. Variable script operands and HOME-dependent tilde bindings remain unproved.
Literal Herdr prompt pipelines may compose with independent commands such as pane close or a literal script operation. The prompt pipeline's consumers must be supported data commands; each sibling still receives ordinary path, environment, runtime and configured policy checks. If the lexer finds an unterminated quoted argument, recovery explains shell encoding. A single-quoted apostrophe can be encoded as 'don'"'"'t'. File-input options are recommended only when the command documents one.
Bun's late startup-flag scan respects a separate -- after a supported literal task/file operand. Words thereafter are tool argv for this scan, including -p:Example=false; actual startup controls before the separator still refuse. Missing targets, runtime subcommands and embedded/attached separator text do not receive this boundary. Task/file lookup retains the existing trusted host/project assumption, including task lifecycle hooks; source contents are not inspected. Explicit interpreter lookup/loader variables such as BUN_OPTIONS, NODE_OPTIONS, PYTHONPATH and BASH_ENV cannot establish startup context. Literal ordinary selector/deadline prefixes remain subject to the existing path/environment rules.
Shared Git diff inspection uses a finite argv-role table. Output/order-file values and positional revision/file words receive ordinary/custom path policy, with proved cwd and Git -C qualification. Known required search/prefix values are data; optional context/rename/color/relative values attach to their option and never consume the next file. Short clusters are read left to right, so -pO.env has a file role and -S-O.env has a search-data role. Exact long options take priority over unique supported abbreviations; ambiguous/unknown options, incomplete roles and executable callbacks refuse with Git-specific recovery. This does not inspect repository state, trusted native executables/configuration, task contents, aliases or symlinks.
Python JSON parsing, dictionary iteration, and printing are not themselves evidence of credential access. They follow the same access and execution rules as other supported inline programs. This intentionally permits ordinary JSON output that older versions refused. Unknown secrets in ordinary files can therefore reach arbitrary shell output; wrapper content checks do not protect that output. A refusal caused by unsupported syntax means the guard could not establish its policy conditions, not that it observed a secret being read.
The bounded Python subprocess proof supports one immutable literal argv binding followed by one subprocess.run call. A literal cwd is replayed as a quoted, success-gated directory change before the nested argv, so relative reader operands and named directories receive the ordinary path policies. Supported directories are literal relative or absolute paths, including quoted spaces. Empty, dynamic, Unicode, escaped, tilde and other unsupported directory forms refuse rather than dropping the context. Cwd proof recursively unwraps only supported command, RTK proxy and environment wrappers. Environment directory/split-string options and unproved wrapper forms refuse. Only supported passive text, reader, metadata, literal Bun script and blocked CLI roles receive nested replay; shell executors, inline interpreter programs, unproved sed programs, find execution actions and unknown command roles refuse. Nested blocked CLI, environment, sensitive-path and custom-command policies remain active. Outer shell redirects retain their original shell directory. Arbitrary multi-call Python and check_output programs remain unsupported; direct Git metadata commands provide index/tree inspection.
Supported runtime script/tool operands form an argument boundary: a later tool -c configuration flag is data. Genuine eval and loader flags, Bun exec/repl and unproved startup-option arity still refuse. Literal Herdr prompts can compose with literal agent-get, pane-read and agent-read forms and passive consumers. Both read forms require a literal identifier followed by --source recent-unwrapped --lines N, with positive integer N. A separate sleep N command accepts one literal nonnegative integer or decimal duration; delay pipelines, options and extra operands receive no composition proof. Delay redirects receive the ordinary sensitive-path checks. The wrapper sleep-polling guard still applies. One literal --sql operand on the supported bun run db-tool sql/query shape can contain SQL braces; substitutions, duplicate SQL operands and unknown wrapper options receive no exception. SQL output pipelines require closed passive consumers, so shell, xargs and unknown executors refuse. Native cut joins those passive consumers for byte, character and field selection, including after a proved sed print program. Its literal file operands and redirects still receive the shared path checks, and expansion, environment, blocked CLI and configured command policies remain active. This recognizes textual native command roles; it does not verify binaries, aliases or command lookup. Numeric $? can appear only in passive echo/printf data operands, with passive pipeline consumers. Explicitly approved environment names can appear in quoted equality or blankness tests, including the quoted empty default ${NAME:-}. This does not add default-approved names or permit shell evaluators. One sed print program with numeric or escaped-regex addresses/ranges and known -n/-E flags is passive; execution/read/write callbacks, extra programs and script files receive no print proof. Local file bindings use that same closed print proof and never a generic sed-word fallback. Ordinary path and configured command policies still apply to all these forms.
Literal shell bodies receive recursive shared policy replay under exact sh -c/bash -c and zsh -f -c invocations, using only these bare executable names or their /bin/ and /usr/bin/ paths and known RTK/command wrappers. Both protected paths and blocked CLIs remain enforced inside the body, along with environment and custom-command policies. A conventional shell-family name or numeric version in an unproved executing operand/control position refuses; proved literal text, reader and file-metadata operands remain data. Only the listed sh/bash/zsh identities receive body replay; rbash/rksh/ksh93 and versioned names refuse, and arbitrary renamed executables are outside this identity proof. Replay permits only existing closed body roles/materializations, at most eight shell levels and 64 KiB per invocation. Unproved options, environment prefixes, active exec/nohup/sudo wrappers, positional/script/stdin forms, executable binders, control flow and outer directory changes around a wrapper refuse. A body-internal proved cd affects body readers; outer redirects retain the outer shell directory. This is a textual body proof, not verification of native binaries or all host startup code. In particular, zsh -f still sources the global zshenv file. For unsupported executable inventories, use separate literal metadata requests and literal candidate invocations; no program name or --version flag receives a special exception.
A separate bounded Node proof supports literal package.json inventory loops that print bin metadata or use the supported scripts filter. The complete invocation must preserve loader identity through known passive commands, closed literal writers, or proved inventories. Startup/environment mutations, unknown commands, loader changes, and other dynamic loading remain unproved and are refused.
Setup for OpenCode
OpenCode loads plugins automatically from .opencode/plugins/. Create a plugin file:
.opencode/plugins/credential-guard.ts
import { createCredentialGuard } from "@blogic-cz/agent-tools/credential-guard";
import { loadConfig } from "@blogic-cz/agent-tools/config";
export const CredentialGuard = async () => ({
"tool.execute.before": createCredentialGuard((await loadConfig())?.credentialGuard)
.handleToolExecuteBefore,
});If the package isn't already in your project dependencies, add a .opencode/package.json:
{
"dependencies": {
"@blogic-cz/agent-tools": "*"
}
}OpenCode installs plugin dependencies automatically at startup.
Custom patterns
Use the credentialGuard config section to extend built-in defaults (arrays are merged, not replaced):
{
credentialGuard: {
// Approve names only after checking that their values cannot contain secrets.
allowedEnvironmentVariables: ["TEST_FLAG", "WORKSPACE_LABEL"],
additionalBlockedPaths: ["private/secrets/"],
additionalAllowedPaths: ["apps/web-app/.env.test"],
additionalBlockedCliTools: [{ tool: "helm", suggestion: "Use agent-tools-k8s instead" }],
additionalDangerousBashPatterns: ["rm -rf /"],
},
}The packaged Claude hook loads this configuration from the current directory and its parent directories. Custom adapters must load the configuration and pass credentialGuard to createCredentialGuard, as the OpenCode example does. Invalid configuration blocks the tool call.
Extending the guard
The guard source is at src/credential-guard/index.ts. Fork the repo, adjust patterns, submit a PR: https://github.com/blogic-cz/agent-tools
Development & Evaluation
Run Evaluation Harness
The evaluation harness runs a set of test cases against the tools to ensure quality and reliability:
bun run tests/eval/run.tsLicense
MIT
