Thanks for your interest in contributing to the Neo4j CLI, issues and pull requests are welcome.
If you want to contribute code, make sure to sign the CLA.
The full suite of tests can be run using the following command:
make testThe CLI can be run locally without building a binary:
make run-neo4jTo lint the codebase:
make lintTo format all Go source files:
make fmtEach CLI binary embeds a generated agent-skill bundle (<bin>/internal/skill/bundle/) that documents its cobra tree. The bundles are committed and regenerated whenever the command surface changes.
To regenerate every binary's bundle:
make generateUnder the hood this runs go generate ./..., which invokes each binary's <bin>/internal/skill/gen/main.go generator. The generator walks the binary's cobra tree and rewrites bundle/SKILL.md plus bundle/references/*.md.
CI runs the following gate on every PR — it fails if the committed bundles are stale relative to the current cobra tree:
make generate-checkIf this fails, run make generate locally and commit the resulting diff.
Each binary has a hand-written <bin>/internal/skill/additions.md that is inlined into the generated SKILL.md under "Gotchas". Edit additions.md (not the generated bundle/SKILL.md) and re-run make generate. The same applies to description.txt, which feeds the frontmatter description field.
To add agent-skill support to a new standalone binary:
- Expose
NewCmd(cfg)from a<newcli>/app/app.gopackage the generator can import. - Copy
neo4j-cli/internal/skill/to<newcli>/internal/skill/, then editdescription.txt,additions.md, and the import ingen/main.go. - Mount the subcommand in the binary's entrypoint:
cmd.AddCommand(skill.NewCmd(cfg, binskill.Bundle, "<newcli>")). - Run
go run ./<newcli>/internal/skill/gento bootstrapbundle/, then commit the result. No edits tocommon/skill/are needed.
Opt-in experimental behaviour is gated via the registry at common/clicfg/flags.go. Add an entry there, then gate code with cfg.Flags.Enabled("flag.<area>-<feature>"). Users can enable a flag via neo4j-cli config set flag.<area>-<feature> true or NEO4J_CLI_FLAG_<AREA>_<FEATURE>=1. Default to false; delete the flag and its gated branch in the same PR on GA. Full convention: .agents/feature-flags.md.
The full release lifecycle (changelog → Release PR → GitHub Release → npm publish) is documented in RELEASING.md. The short version: add a changelog entry on your PR, merge normally, and changie opens a separate Release PR — merging that is what ships binaries and npm packages.
As well as your code changes, pull requests need a changelog entry. These are added using the tool changie. You will need to install this using the following command:
go install github.com/miniscruff/changie@latestIf changie is not available, you may need to add /go/bin to your path: export PATH="$HOME/go/bin:$PATH"
Run make changelog and follow the prompts. Changie will ask you to select a change kind, then generate a YAML file in .changes/unreleased/. Commit it alongside your code changes.
For non-interactive use (e.g. scripts or agents):
changie new --projects neo4j-cli --kind Patch --body "your change description"All .go files must begin with the following license comment:
// Copyright (c) "Neo4j"
// Neo4j Sweden AB [http://neo4j.com]To check that all files comply, run:
make license-checkNote:
make license-checkrequires a Unix shell (bash/sh) withfindandxargs. It will not work natively on Windows without WSL or Git Bash.
Builds for releases are handled in GitHub Actions. If you want to create local builds, there are a couple of approaches.
To build neo4j-cli into the bin/ directory:
make buildTo remove build artifacts:
make cleanIf you want to build binaries for all varieties of platforms, you can do so with the following command:
GORELEASER_CURRENT_TAG=dev goreleaser release --snapshot --cleanIn the above command, GORELEASER_CURRENT_TAG can be substituted for any version of your choosing.
The CLI aims to provide a consistent and reliable experience to the end user. Any change made to the CLI must comform to the following guidelines.
- All commands must be singular
- ✅
neo4j-cli aura instance - ❌
neo4j-cli aura instances
- ✅
- Verbs and nouns should be separate, with the action at the end
- ✅
neo4j-cli aura instance list - ❌
neo4j-cli aura list-instance - ❌
neo4j-cli aura list instance
- ✅
- Input identifiers (command names, aliases, flag long names) must be kebab-case. Single-character flag shorthands are exempt.
- ✅
--database-name,instance snapshot list - ❌
--database_name,--databaseName
- ✅
To avoid confusion, this guide uses the term flags to refer to any named argument, whether it has values or not (e.g. -l, --format json) and arguments exclusively for positional arguments (e.g. list 1234).
- Only one argument should be used, if more than one is needed, use flags instead. This is to avoid confusion when passing parameters without enough context
- ✅
neo4j-cli aura instance get <id> - ❌
neo4j-cli aura instance get <id> <deployment-id> - ✅
neo4j-cli aura instance get <id> --deployment-id <deployment-id> ⚠️ neo4j-cli aura instance get --instance-id <id> --deployment-id <deployment-id>
This valid, but the option above is preferred as it is more concise
- ✅
- The argument must always refer to the closest noun
- ❌
neo4j-cli aura instance snapshot list <instance-id> - ✅
neo4j-cli aura instance snapshot list --instance-id <instance-id>
- ❌
- No arguments between commands
- ❌
neo4j-cli aura project <project-id> instance get <id> - ✅
neo4j-cli aura instance get <id> --project-id <project-id>
- ❌
- Flags, if set, take precedence over global configuration or default values
- Flags should have descriptions, if the flag is expected to be always set. The description must start with
(required)
- Read operations should support the following
--formatoptions:json: Provides the raw JSON output of the API, formatted to be human-readable.table: Provides a subset of the output, formatted to be human readable on a table. Try to keep the table output below 120 characters to avoid overflowing the screen.
- Rendered output field names (JSON/TOON keys, table headers) must be snake_case.
- ✅
bolt_port,connection_uri,project_id - ❌
bolt-port,connectionUri,projectId - Exemptions: wire/parse structs (external payloads), config keys (
aura.base-url), Docker label constants (org.neo4j.cli.*), and enum/status values.
- ✅
These guidelines are based on https://clig.dev
Aura CLI is divided in top level commands, for example:
instanceconfig
Each of these commands handle a certain resource of the API and have several subcommands for the actions, for example:
instance listinstance get
Nested subcommands are also allowed, for example:
instance snapshot list
Folders and files should follow the same structure as the commands. So for example, instance snapshot list should be implemented in the folder subcommands/instance/snapshot/list.go. A single command per file
Every new write leaf must set Annotations["write"] = "true" so the root --rw gate applies automatically.
Most commands targetting API resources contain some of the following subcommands as actions:
getlistdeletecreate
Commands may also have some extra, specific commands, such as instance pause.
For asynchronous operations (i.e. operations that trigger a job that won't be finished in the same request), the flag --wait can be used to wait until the operation has been completed, generally polling for the status. If this flag is not set, all operations must finish when the request has been completed, even if a job is pending.