Douglas McIlroy, one of the Unix Godfathers, once wrote: "This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface."
One of the biggest intuitions Claude Code got right from the start is its "form factor": running in the terminal. The Unix philosophy is about manipulating text streams and files via composable tools (even if we sort of lost the "small" part along the way), and this is exactly the ecosystem Claude Code gets to play in. It can stitch tools together on the fly with pipes and redirects, and you can hand it new capabilities by simply adding a command to the shell.
Another advantage of the shell composability is the ability to surgically query and extract just the data you need; Claude Code is perfectly able to extract just the small part of the payload it needs via the use of well-known utilities like cut or tail and so on. This is of great benefit: even if Claude Code offers models with a 1 million token context window, keeping the context lean is, in general, a great idea. Accuracy stays high, we avoid context degradation and lost-in-the-middle effects, and the harness and the model perform at their best.
At Nearform, many teams use Claude Code every day, and a lot of us are reaching for new CLI tooling (or dusting off our old Unix hacker tricks) to get even more out of AI-native engineering.
So let's take a look at some of our battle-tested favourites! If you’re on macOS, all these tools are just a brew install toolname away.
A tour of CLI tools for Claude Code #
jq and yq
jq
yq
These two tools are a very important building block for your CLI adventures: many of the programs we’ll see on this list can emit structured output, and structured output in general is everywhere in today's landscape, usually JSON, sometimes YAML in the DevOps world.
jq is the universal adapter able to reshape JSON payloads precisely as you (or Claude Code) need. You can filter, reformat, extract, and build new objects. As we mentioned, this technique is essential for preserving context. You do not want to blow up your context with massive objects, so being able to extract only what is needed is extremely useful.
yq is the same idea for YAML, and it can also convert YAML to JSON and vice versa. Essential if you work with things like Kubernetes manifests, CI configs, docker compose files, and so on.
gh pr list --json number,title,author | jq '.[] | select(.author.login=="giorgiovilardo") | .title'
acli jira workitem view FOO-1234 --json | jq '.fields.summary'
yq -o=json '.services | keys' docker-compose.yml
Claude Code usually uses both on its own, but if you want to give a gentle nudge:
- When working with JSON data, just extract the data you need using `jq`
- When working with YAML data, just extract the data you need using `yq`
- Use the `-o=json` flag on `yq` if you want JSON to pass to `jq`
CLAUDE.md
jq is a tiny programming language of its own; we strongly advise taking a look at the documentation to understand its mental model.
Transforming data via pipes to use it in a different way is something very fundamental that we'll build upon during the course of the article.
gh - the GitHub CLI
gh
Many of our projects are hosted on GitHub, so having programmatic access to issues, pull requests, actions, etc., is critical to keep the inner dev loop tight; we don't want to tab out to a browser, and not having to copy-paste issue context or PR descriptions is fantastic.
gh is very easy to use: install it, set it up with gh auth, and you're ready to go.
gh repo clone owner/repository
gh issue view issue_number
gh pr create
We suggest setting up a Pull Request template and referencing it in your CLAUDE.md or in your PR-writing skill; this will point Claude Code in the right direction when compiling a PR.
- Use the `gh` CLI to interact with GitHub
- Segment your work into atomic and logical commits, so a PR can be reviewed commit by commit
- Use @.github/pull_request_template.md when writing PR text
CLAUDE.md
For folks on GitLab, the glab CLI is also available.
acli - the Atlassian CLI
acli
Love it or hate it, the Atlassian suite is a powerhouse among project management tools; Jira and Confluence are everywhere, and you're bound to use them at some point. With Atlassian's own acli, we can let Claude Code interact with tickets and pages.
acli auth login # just do it once
acli jira workitem view FOO-1234
acli jira workitem comment list --key FOO-1234 --json
acli jira workitem search --jql "sprint in openSprints() AND project = TEAM" --paginate
### Jira
When working with Jira, interact with it using `acli`.
These are some common operations:
- Use `acli jira workitem view` to read an issue
- Use `acli jira workitem comment list --key FOO-1234 --json` to read issue comments
- Use `acli jira workitem search --jql "jql_expr"` for JQL search
- Use `acli confluence` to search the product docs
CLAUDE.md
With just acli and gh, Claude Code can close the entire issue lifecycle; we've personally solved simple issues with Plan Mode and prompts like Read FOO-1234 from Jira, fix it, then post a PR and update the ticket. Confluence (accessed with the acli confluence subcommands) fits in too: as a doc source or as storage for more complex flows like Spec-Driven Development (SDD), where Claude Code can pull PRDs and similar artifacts.
aws, gcloud, az, and kubectl
aws
gcloud
az
kubectl
Why not extend our development loop to operations? Every major (and some minor!) cloud provider has a competent CLI. Reading logs, describing a failing deployment, listing errors - these are things we do constantly, and they're more useful when done directly by Claude Code while it gathers context.
aws logs tail /ecs/my-service --since 1h --format short
kubectl describe pod my-app-xxxx
kubectl logs deploy/my-app --tail 200
And since these CLIs all speak JSON, jq comes right back into the picture. Need to track down every bucket that's been sitting around since before 2022, maybe to bill someone or just to clean up? aws hands you the data, jq does the rest:
aws s3api list-buckets --output json \
| jq -r '.Buckets[] | select(.CreationDate < "2022-01-01") | .Name'
It's all read-only investigation, exactly how you want it. Mutations are a different story; layer permission rules in settings.json on top of your CLAUDE.md directives so they always go through you. Claude Code reads and parses freely, but asks for confirmation before anything sensitive.
- Use read-only kubectl commands to investigate; never mutate something without asking for permission
CLAUDE.md
{
"permissions": {
"allow": ["Bash(kubectl get:*)", "Bash(kubectl logs:*)"],
"ask": ["Bash(kubectl scale:*)", "Bash(kubectl rollout:*)"],
"deny": ["Bash(kubectl delete:*)"]
}
}
.claude/settings.json
Build on this idea, based on your project's technical stack. Many other providers now offer CLIs to interact with their service; Sentry, Stripe, and Datadog all have official support, and for many others, there's some community CLI. Composable tools all the way!
hurl
hurl
When you need a CLI-based HTTP client, curl is all you want; it's fantastic software, and proof that open source is art. It's not incredibly ergonomic though, and it's REALLY difficult to use if you need to concatenate requests, send big JSON payloads, or handle the other things web devs do all the time.
There are easier-to-use alternatives (we recommend xh), but we want to explore a different execution model; hurl executes requests that are described in files with a .hurl extension, commonly called hurlfiles.
We can capture data from one request and use it in the next, use variables injected from the environment (or passed with flags), keep a record of what Claude Code ran, document bug reproductions, and lots of other use cases.
Test mode probably deserves its own small section, as it's excellent in CI, for application testing, and it can produce a very thorough report in various formats, including JSON, for further programmatic analysis.
POST https://{{env}}.example.com/login
{"user": "demo", "pass": "demo"}
HTTP 200 # if not 200, the command fails
[Captures]
token: jsonpath "$.access_token"
GET https://api.example.com/me
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.email" exists
test_profile_page.hurl
hurl --test smoke.hurl
hurl --variable env=staging login.hurl
Claude Code is excellent at writing hurlfiles, even from documentation or OpenAPI files, and the grammar is structured but easy to write and remember, so it's not out of place in documentation or ADRs. hurl also ships with the companion command hurlfmt, which converts curl commands into hurl. This pairs beautifully with the browser's dev console "Copy as cURL" right-click option.
pbpaste | hurlfmt --in curl > flow.hurl
pbpaste | hurlfmt --in curl >> flow.hurl
pbpaste | hurlfmt --in curl >> flow.hurl
The HTTP exchange is now a reviewable artifact, living in version control next to the code it exercises. hurl is also well supported by many editors, with syntax highlighting and completions, so the file stays pleasant to read and edit long after you've captured it.
We had great success introducing customers to the tool; it's flexible and can be leveraged by Claude Code to accelerate its already top-class context-gathering.
hyperfine
hyperfine
The venerable time utility is useful when you need to measure how much something takes in a pinch, but it is severely underequipped to deal with more rigorous benchmarking processes. That's where hyperfine comes in. It is a command-line benchmarking tool, essential when you're working on raw performance.
It runs a command many times, does warmup runs, accounts for shell spawn overhead, and reports mean/standard deviation/min/max with outlier detection. The reason it matters for agentic work is that LLMs are prone to "it feels faster" claims; hyperfine replaces vibes with numbers, and we cannot rave enough about how powerful this technique is to give Claude Code an objective to reach autonomously.
hyperfine --warmup 3 './build.sh'
hyperfine 'node old.js' 'node new.js'
hyperfine --prepare 'make clean' 'make'
hyperfine -p 'make clean' --parameter-scan num_threads 1 10 'make --jobs {num_threads}'
Like many tools on this list, it can also export in formats like Markdown and JSON, which is quite important for data analysis and agent work.
hyperfine --warmup 3 --export-json bench.json 'node new.js'
Why are we stressing the importance? Let's take a look at...
duckdb
duckdb
duckdb is an amazing project; born to offer an OLAP alternative to SQLite, leveraging columnar storage and a vectorised execution engine, it evolved into a real Swiss army knife for local data analysis. It can natively ingest and query CSV, JSON, Parquet, its own .duckdb format, has tons of functions to deal with nested and complex structures, and can be deployed on a web client with WASM. It really packs all of that into a single, no-dependencies binary.
That single-binary, file-as-database design is exactly why duckdb pairs so well with Claude Code: it turns "raw data" into "queryable data" with zero setup. We see three common scenarios in real production projects.
The first one is the analytical scratchpad. Point it at a file and query, no schema, no database, nothing to clean up.
curl -s 'https://swapi.info/api/people/' > people.json
duckdb -c "
SELECT name, height::INT AS height_cm, gender
FROM read_json_auto('people.json')
WHERE height != 'unknown'
ORDER BY height_cm DESC
LIMIT 5;
"
This has more than one benefit; it saves you context first and foremost, and lets you query your JSON data extremely precisely, extracting just what is needed for the task at hand.
The second use case is the mini time-series db. Running incremental initiatives like tech debt fixing, benchmarks with hyperfine, optimisation/test runs? Track results in a .duckdb file and give Claude Code some data to optimise against.
npx eslint -f json . | duckdb tech_debt.duckdb "
CREATE TABLE warnings AS SELECT *, now() AS run_at FROM read_json_auto('/dev/stdin');
ALTER TABLE warnings ALTER run_at SET DEFAULT now();
"
npx eslint -f json . | duckdb tech_debt.duckdb \
"INSERT INTO warnings BY NAME SELECT * FROM read_json_auto('/dev/stdin')"
duckdb tech_debt.duckdb "
SELECT run_at, count(*) AS total_warnings
FROM (SELECT run_at, unnest(messages) AS m FROM warnings)
GROUP BY run_at ORDER BY run_at;
"
hyperfine --warmup 3 --export-json bench.json 'node new.js'
duckdb perf.duckdb "
CREATE TABLE runs AS SELECT *, now() AS run_at FROM read_json_auto('bench.json');
ALTER TABLE runs ALTER run_at SET DEFAULT now();
"
Claude Code is awesome at writing SQL, so get fancy: ask for a delta query across the last N runs, or which lint rule is the worst offender. Giving the agent a metric it can compare across runs is a game-changer.
The last use case is agent memory. This is getting rarer as Claude Code gets better and better, but it's still a useful technique to know, as using duckdb guarantees queryability and you can automatically inject it with hooks.
duckdb .claude/state.duckdb "
CREATE TABLE tasks (
id INTEGER, title TEXT,
status TEXT DEFAULT 'todo',
created_at TIMESTAMPTZ DEFAULT now()
);
"
Then wire a SessionStart hook that queries the open tasks. Its stdout lands straight in Claude Code's context at the start of every session:
{
"hooks": {
"SessionStart": [
{ "hooks": [ {
"type": "command",
"command": "echo 'Open TODOs:'; duckdb -noheader -list .claude/state.duckdb \"SELECT '- ' || title FROM tasks WHERE status = 'todo' ORDER BY created_at\""
} ] }
]
}
}
.claude/settings.json
The same pattern unlocks "tooled" SDD: rather than dropping specs, plans, and task lists into markdown files the agent has to re-read in full, keep them in a queryable store, let Claude Code update their status as work progresses, and pull in only the slice relevant to the current task.
scc
scc
Another easy source of trackable metrics is the codebase itself. This is especially handy if you are a tech lead, EM, or PM who lives in documents, ADRs and PRDs, where you regularly need to gauge work, size up codebases and modules, or just produce some visuals.
scc (but there are many equivalent utilities, with more or less the same options) counts lines of code, blanks, and comments across a tree in milliseconds, broken down by language, with a complexity estimate per file. If you feel fancy, scc can also provide COCOMO estimations or count unique lines of code to provide a "DRY-ness" index.
scc gives you a point-in-time snapshot; a natural companion is the family of tools that mine the git history, surfacing code churn, how much of each file survives over the years, and which areas are hotspots. Same spirit, with the time dimension baked in, and the output stores just as nicely in duckdb for trends.
scc # whole-repo breakdown by language
scc src/legacy # size just the module you're about to touch
scc --by-file --sort complexity | head # more complex first
"The payment module is 14k lines of high-complexity Python" is a great hook for an ADR, and cleanup and refactors can be measured against an objective baseline. --format json for the usual structured output, ready to be stored, queried, and massaged with all the techniques we've covered so far.
just
just
We all love make as a staple utility of projects everywhere, but the problem with it is that, more often than not, it's used only as a command runner rather than the full build system it is. This is not cost-free; phony targets, indentation rules, $$ for shell variables, every recipe line in its own subshell, and many more quirks that are worth dealing with only if you have a real need for make special sauce.
just throws away the build system baggage, keeping only the command runner functionality, which excellently covers lots of mileage for many code projects.
test:
go test ./...
lint:
golangci-lint run
deploy env="staging":
./scripts/deploy.sh {{env}}
quality: lint test
[working-directory: 'frontend']
frontend-quality:
npm run quality
justfile
just --list
just test
just deploy prod
Claude Code loves a well-done justfile as it's a win from a discoverability and correctness point of view. just --list is a self-documenting list of public recipes (just allows for private recipes). Parameters are first-class, it can automatically load environment files, and it can use set shell and allows for inline recipes with programming languages. We also get benefits in centralising naming for common operations, and we can recycle the just recipes in CI.
With Docker's multi-stage builds, having just around won't lead to wasting precious container space, as you'll just use it in the first stage, for building the project.
nix
nix
"But Giorgio, we'd really love to standardise these tools. But it's a chore to tell the whole team to install them, and we don't have a good distribution story on our laptops". I know, I feel you. And this section is probably not for you, but if you want to live (relatively) dangerously and can accept the cuts that come with living on the bleeding edge of software, there's a big payoff for you. Enter Nix.
Nix is, long story short, a way to create reproducible, declarative and reliable systems. It sadly suffers from a naming problem: Nix Language is used to write .nix files, which are used by the nix package manager to deterministically install systems. NixOS is a Linux distribution completely based on Nix technology. It's incredibly powerful, though: you can declare exactly what is needed to build your project, or what is needed to develop it, and then you can package it with exactly the minimum amount of software needed to make it run.
If you use Nix, your dev environment is exactly the same as CI and Production, because it's built by the same exact closure of input packages. There's also zero conflict with any other software on your laptop, and you can use Nix to manage your system with home-manager. Nix is also able to generate OCI-compliant images without Docker for your deployments.
Claude Code is surprisingly competent at working with Nix files, even if it's still a relatively niche technology; if you want to invest time in it, it will pay off in spades. You can pin all these tools in a flake.nix file:
devShells.default = pkgs.mkShell {
packages = with pkgs; [ gh hurl duckdb hyperfine just jq yq scc ];
shellHook = ''
export DATABASE_URL="postgres://localhost/myapp_dev"
'';
};
flake.nix
and just invoke nix develop to get into a shell with all these tools available. Resources like Zero To Nix are starting to become more popular, so, in time, Nix might start to appear even more! And for the absolute sceptics, once installed, you can use Nix to try one-off commands without installing them with snippets like nix run nixpkgs#hyperfine -- --help.
timew - Timewarrior
timew
Working on multiple projects? Need some visibility on where your time went for your weekly timesheet? Look no further than timew (and, if you do not like duckdb as memory, its sibling project Taskwarrior), a plain-text, CLI-first time tracker that Claude Code can drive as it works.
It's very simple to use: you clock on against one or more tags, then clock off when done. You can ask Claude Code to use it via its harness directives or use hooks. This way, the time tracking happens as a side effect of the work itself. It's a thing of beauty if you are very busy or are juggling more than one activity at once.
timew start payments bugfix
timew stop
timew tag @3 billable
timew summary :week
timew summary :week payments
## Time tracking
- Wrap work in `timew start <project> <kind>` / `timew stop`; project is one of: payments, infra, web.
- Use the same project tag the task belongs to, so summaries line up across the team.
- Run `timew summary :week` when asked "where did the time go?".
CLAUDE.md
timewarrior has many features and options, and it's also extendable; make sure to check it out if you need powerful time management for your Claude Code sessions!
pandoc, poppler and qpdf
pandoc
poppler
qpdf
Sometimes you need to work on formats which are not exactly machine-friendly, or need to convert data that needs to land on the desk of less technical functions. PDF, Word and Excel documents are some of the most common formats in every company out there. They are simply that ubiquitous.
With pandoc, the universal document converter, you give Claude Code eyes and hands for formats that could have otherwise been touched only programmatically. Markdown to DOCX to HTML to LaTeX to PDF is absolutely doable!
pandoc --list-input-formats
pandoc --list-output-formats
pandoc -f docx -t markdown A_doc.docx -o A_doc.md
It also supports Lua scripts for the transformation; you can pipe output into it, and it can convert web pages if you pass a URL as the input document name.
If you work extensively with PDFs, we suggest adding two more arrows to your quiver: poppler and qpdf. pandoc can't work with PDFs as input files, and that's where you need poppler (and poppler-utils on certain Linux distributions). It will install a series of CLI utilities, like pdftotext, that will make working with PDF a little less maddening.
pdftotext -layout tabular_data.pdf
pdfinfo report.pdf
pdftocairo -png -r 300 -singlefile some_pdf.pdf some_pdf_screens
qpdf instead is for manipulating PDFs: split them, join them, reorder pages, rotate pages, and with many other useful editing tools.
qpdf --empty --pages input.pdf 1-3,5,6-10 -- output.pdf
qpdf --empty --pages doc_1.pdf doc_2.pdf ... -- doc_join.pdf
ffmpeg, sox, and imagemagick
ffmpeg
sox
imagemagick
More and more applications today have rich media capabilities: video, live streams, audio, generated images. If your team ships any of that, or you’re interested in extracting data from some of these formats, we can use the media trio of ffmpeg (video, audio, images), sox (audio) and imagemagick (images) to process and work on that data.
ffmpeg -i talk.mp4 -vn -ar 16000 -ac 1 audio.wav
ffprobe -v quiet -print_format json -show_format -show_streams vid.mp4
magick in.png -resize 800x out.jpg
magick in.heic out.png
magick identify -format '%wx%h %m\n' in.png
sox --i in.wav
Using these utilities, you can also make media testable. SSIM filters in ffmpeg calculate video similarity, and you can extract various structured data with all tools, fingerprint media, calculate distance, and so on.
The media trio, driven by Claude Code, is also capable of carrying lots of weight for Content and Marketing teams, proving Claude Code is a transformative technology, suitable even for teams not strictly composed of developers. Want to experiment with TikTok-style video subtitles without committing to a vendor and skipping a potentially slow and costly procurement process? Been there, done that. It's a couple of Python scripts and an ffmpeg re-encode away, entirely driven by a Claude Code prompt. Have fun and experiment away!
marp and typst
marp
typst
We've seen tools to give Claude Code the ability to read and convert document formats, but in case your work is more focused on the production of documents, you want a slightly different toolkit.
For presentations, there are lots of relatively similar frameworks, and slidev earns a shoutout since it's incredibly powerful (you can write Vue.js components and embed them into your slides), but it's also quite complex and needs a JavaScript toolchain, which is sometimes too much for a quick slide deck.
That's why we absolutely love and recommend marp as it is incredibly simple and effective. You write your slides in Markdown, separating them with a horizontal rule (---) and then "compile" them to the format you prefer. There is the possibility of adding custom HTML tags (e.g. for 2-column layouts), adding custom CSS, theming, support for presenter notes, and more bells and whistles. It's fast, easy, and Claude helps a ton in drafting your presentations. Being plain text helps in making the artifact reviewable and composable with the other tools (generate a slide deck from a video transcription? Easy as pie!)
---
marp: true
paginate: true
theme: some-theme
---
**Subtitle**
Some Author
Your Company
---
## First slide
Lorem ipsum...
deck.md
marp --theme some-theme.css deck.md -o deck.pdf
marp deck.md --pptx -o deck.pptx
On the other end of the spectrum, we want to highlight typst as a fully fledged typesetting engine. Basically, a modern alternative to LaTeX. When you need maximum control and precision, it's the tool you want. You describe your document in a .typ file, using a very powerful DSL (Domain-Specific Language). This DSL has various utilities to programmatically deal with your content, i.e. you can output documents based on structured data:
{
"title": "Q2 Sales Report",
"rows": [
{ "name": "Widget", "units": 1200, "revenue": 24000 },
{ "name": "Gadget", "units": 860, "revenue": 43000 },
{ "name": "Gizmo", "units": 430, "revenue": 12900 }
]
}
sales.json
#let data = json(sys.inputs.data)
#set page(margin: 2cm)
#set text(size: 11pt)
= #data.title
#table(
columns: 3,
align: (left, right, right),
table.header[*Product*][*Units*][*Revenue*],
..for row in data.rows {
(row.name, str(row.units), "$" + str(row.revenue))
},
)
report.typ
typst compile --input data=sales.json report.typ report.pdf
typst watch book.typ
Other than single document production, typst has performed well in document generation pipelines and microservices; you can prepare the document template, add some styling, and use it programmatically to generate documents like invoices, transport documents, and whatnot. It is miles better than the usual approach of spinning a headless browser and using the print to PDF function.
There are no more excuses now to delay the book you always wanted to publish: Claude Code can even help typeset it!
Practical customisation and integration #
Creating your own CLI tools for Claude Code
What if your project (or company) could have its own special command? Investing in tooling is always an excellent idea; that runbook, that kubectl one-liner, that series of API calls, all could become a mycompanycli logs payments --env prod --json or myprojectcli fix-timesheets. You now have self-documenting and easy-to-reach commands, encoding knowledge that is often transmitted orally or passively, making it impossible to get wrong.
Other than accessibility and discoverability, you're also compiling the guardrails for the agent (and the humans) into the binary. Your execsql command that only connects to a read replica is physically unable to INSERT. Your reconcile-orders will automatically convert the time zones. No one can skip or forget about that critical footnote of the business process if the process is executed by a binary.
Whatever you build, give it some flags to output structured data; JSON mainly, but YAML has its uses in certain domains. Structured output means that you should now know the drill: composability into jq, storage in duckdb, compatibility with the rest of the kit.
Also be sure to build proper --help flags; think about agents, not just humans. Help text is the only documentation that's always there, always matches the installed version, and needs zero setup to find. When Claude Code meets a tool it doesn't know, it just runs mycli --help, so that output is your best shot at landing it on the right command immediately.
A few best practices to build the help text:
- Split functionality into well-named subcommands - the agent will query them one at a time, allowing for progressive disclosure;
- Keep the top-level help small - just describe subcommands;
- Put runnable examples in the text - they're the fastest way to make the agent do the right thing;
- Document every flag, its default, and its units - agents and humans do not like to guess;
- Have a
--dry-runflag documenting what is going to happen; - Make error messages prescribe the fix; do not just report the failure.
$ mycli --help
Your personal CLI.
Usage: mycli <COMMAND>
Commands:
run Do some stuff
show Show some stuff
edit Edit some other stuff in $EDITOR
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
$ mycli run --help
Do some stuff
Usage: mycli run
Options:
-e, --environment One of prod, staging, test, dev
-h, --help Print help
We recommend building your CLI in Go or Rust. For Rust, we like clap, while for Go, there's urfave/cli v3. Both are fantastic libraries that set the bar very high and allow you to quickly and easily write great CLIs.
The killer feature of building on these languages is distribution. Both cross-compile to native Mac, Linux and Windows binaries that are statically linked, with no runtime or dependencies to install. The output is a single file that's modest on resources and starts instantly. That makes a shared internal tool trivial to distribute to every machine, and a shared toolset is a shared capability for every teammate in the org, human or agentic.
Security considerations
If you're worried about handing Claude Code the keys to your company, you're right; security is a top-tier concern for all of our customers, and we take it very seriously. We deal with it as a posture, integrating it from the first moment.
Other than all the usual safeguards (disaster recovery plans, separation of concerns, etc.), we usually use two main techniques to keep Claude Code shell adventures secure.
Least privilege must be enforced at the credential level; CLAUDE.md directives are fine, but you do need to account for surprises. Use DSNs (Data Source Name, aka db connection string) that only point at read replicas, fine-grained GitHub tokens, and cloud roles with no write privileges. If the credential can't do the dangerous thing, no amount of clever prompting can make it happen. Heck, get creative; want to run a write on a production database? Do it via the project CLI that you built, encapsulating logic to clone some production pod only after asking for an approval on Slack via an integration, only with a witness present, whatever floats your boat. Keep yourself accountable.
The second layer of our defence in depth is Claude Code’s own permission rules. You can (and should) reinforce your settings.json - one good rule of thumb could be to allow for read-only operations, ask for mutations and outright deny irreversible operations.
{
"permissions": {
"allow": ["Bash(gh pr view:*)", "Bash(kubectl get:*)", "Bash(aws logs:*)"],
"ask": ["Bash(gh pr merge:*)", "Bash(kubectl scale:*)"],
"deny": ["Bash(kubectl delete:*)", "Bash(aws s3 rb:*)", "Bash(:* --force:*)"]
}
}
.claude/settings.json
Use Enterprise Managed Settings to push a series of non-overridable rules everywhere, and make sure each of your company projects correctly sets up some project-specific rules that are shared via VCS.
Conclusion #
Hopefully, this article inspired you in building your CLI workflow; CLIs and TUIs are in a great renaissance period, owing a lot to the "modern Unix" community, and owing some to Claude Code, as the first real AI agent built on that fantastic, compounding platform and unified interface that is the shell.
There are lots more programs to explore and enjoy: CLIs that talk to your company workspace, faster alternatives for common commands, data wrangling tools, DevOps tooling. We picked what has performed the best in our projects. But if you have a different use case, try something small and try to fit it into your Claude Code experience. More often than not, it will be a net benefit.
McIlroy was right in the seventies, and he's still right now. Small tools, working together, talking text. It’s a great platform for developers to work on, and it turns out it’s a great platform for agents too.
If you or your team are interested in exploring these technologies/techniques more and setting you up for success with AI-Native Engineering and Claude Code, get in touch. We’d love to talk about how to enhance your AI capabilities with tooling and more.
But wait - there's more. #
Nearform publishes real-world learnings on data & AI, engineering, and digital strategy - with more merged in weekly.
Insights
Perspectives on AI in engineering, product development, and strategy, for enterprise executives.
Community
Deep dives and tutorials by engineers, for engineers.