@agent-nexus/cli
v1.5.1
Published
Official CLI for the Nexus AI agent platform.
Readme
@agent-nexus/cli
Official CLI for the Nexus AI agent platform. Manage agents, workflows, deployments, knowledge bases, and more from your terminal.
- Wraps the full Nexus Public API v1
- 53 command groups, 544 invocable subcommands
- Table, record, and JSON output modes
- Pipe-friendly: stdin input,
--jsonoutput, composable withjq - Zero config after
nexus auth login - Node.js 18+
Status: BETA -- read
COMPATIBILITY.mdbefore you script against this CLI. It states which surfaces are stable, which move, and what we cannot promise yet.
Table of Contents
- Installation
- Authentication
- Quick Start
- Global Options
- Input Patterns
- Output Modes
- Commands
- Common Patterns
- SDK Cross-Reference
- Error Handling
- Troubleshooting
- Configuration Files
- Related Resources
- License
Installation
# Install globally
npm install -g @agent-nexus/cli
# Or with pnpm
pnpm add -g @agent-nexus/cli
# Or with yarn
yarn global add @agent-nexus/cliRun a one-off command without installing:
npx @agent-nexus/cli agent listVerify the installation:
nexus --versionUpgrade to the latest version:
nexus upgradeThe CLI checks for updates once per day and prints a notice to stderr when a newer version is available. This check never delays command execution.
Authentication
Interactive Login
nexus auth loginThis opens the Nexus settings page in your browser. Copy your API key and paste it at the prompt. The key is validated against the API before being saved.
Non-Interactive Login
For CI/CD or scripting:
# Via flag
nexus auth login --api-key nxs_abc123
# Via environment variable (no login needed)
export NEXUS_API_KEY=nxs_abc123Verify Authentication
nexus auth whoamiPrints the API base URL and a masked version of your key (e.g., nxs_abc1...3def).
Logout
nexus auth logoutRemoves stored credentials from ~/.nexus-mcp/config.json.
API Key Resolution
The CLI resolves the API key in this order (first match wins):
| Priority | Source | Example |
| -------- | ----------------------- | ------------------------------------ |
| 1 | --api-key flag | nexus agent list --api-key nxs_... |
| 2 | NEXUS_API_KEY env var | export NEXUS_API_KEY=nxs_... |
| 3 | Config file | Written by nexus auth login |
Base URL Resolution
| Priority | Source | Default |
| -------- | ------------------------ | ------------------------------------------------------------------------- |
| 1 | --base-url flag | |
| 2 | NEXUS_BASE_URL env var | |
| 3 | Config file | |
| 4 | NEXUS_ENV env var | production = https://api.nexusgpt.io, dev = http://localhost:3001 |
| 5 | Default | https://api.nexusgpt.io |
Multi-Profile Support
The CLI supports multiple named profiles for managing different organizations or environments.
Create profiles
# Interactive (opens browser, prompts for key and profile name)
nexus auth login
# Non-interactive with explicit profile name
nexus auth login --profile work --api-key nxs_abc123
nexus auth login --profile personal --api-key nxs_xyz789Switch between profiles
nexus auth switch work # THIS MACHINE — every process reads it
nexus auth switch work --here # this DIRECTORY (writes .nexusrc)
eval "$(nexus auth switch work --session)" # this SHELL (sets NEXUS_PROFILE)A plain switch rewrites one value in ~/.nexus-mcp/config.json that every process on the
machine reads, so it repoints other terminals and agent sessions that have no binding of their
own — silently, mid-task. Use --here or --session to work on two organizations at once;
both outrank the machine-wide value and neither can be moved by another session's switch.
List all profiles
nexus auth list
# PROFILE ORGANIZATION BASE URL
# ▸ work Acme Corp https://api.nexusgpt.io
# personal My Startup https://api.nexusgpt.ioPin a directory to a profile
Create a .nexusrc file in your project directory so the CLI automatically uses the right profile:
cd ~/projects/acme
nexus auth pin work # same file as: nexus auth switch work --here
# Creates .nexusrc with { "profile": "work" }
cd ~/projects/startup
nexus auth pin personalCheck which profile is active
nexus auth status
# Using profile "work" (Acme Corp) — .nexusrc at /Users/you/projects/acme/.nexusrcProfile Resolution Order
When determining which profile to use, the CLI checks (first match wins):
| Priority | Source | Example |
| -------- | ----------------------- | ------------------------------------------------------------ |
| 1 | --api-key flag | Bypasses profiles entirely |
| 2 | --profile flag | nexus agent list --profile work — beats the env vars below |
| 3 | NEXUS_API_KEY env var | this shell — bypasses profiles entirely |
| 4 | NEXUS_PROFILE env var | this shell — auth switch <n> --session |
| 5 | .nexusrc file | this directory — auth switch <n> --here |
| 6 | Active profile | this machine — auth switch <n> |
| 7 | "default" profile | Fallback |
Remove profiles
nexus auth logout # removes active profile
nexus auth logout work # removes specific profile
nexus auth logout --all # removes everything
nexus auth unpin # removes .nexusrc from current directoryQuick Start
A complete walkthrough: create an agent, give it a knowledge base, deploy it, and test it.
# 1. Authenticate
nexus auth login
# 2. Create an agent
nexus agent create \
--first-name "Support" \
--last-name "Bot" \
--role "Customer Support" \
--prompt "You are a helpful customer support agent. Answer questions using the knowledge base."
# 3. Upload a document to the knowledge base
nexus document upload ./product-faq.pdf
# 4. Create a collection (retrieval-augmented generation index)
nexus collection create --name "Product FAQ"
# 5. Attach the document to the collection
nexus collection attach-documents <collection-id> --document-ids <document-id>
# 6. Attach the collection as a tool on the agent
nexus agent-tool create <agent-id> \
--type COLLECTION \
--collection-id <collection-id> \
--label "FAQ Search"
# 7. Deploy the agent as a web widget
nexus deployment create \
--name "Support Widget" \
--type web \
--agent-id <agent-id>
# 8. Test via the emulator
nexus emulator session create <deployment-id>
nexus emulator send <deployment-id> <session-id> \
--text "How do I reset my password?"Tip: Add
--jsonto any command and pipe tojqto extract IDs:AGENT_ID=$(nexus agent create --first-name Bot --last-name Helper --role QA --json | jq -r '.id')
Global Options
These flags are available on every command:
| Flag | Description |
| --------------------- | ---------------------------------------------------------------------------- |
| --json | Output results as JSON (for scripting and piping) |
| --api-key <key> | Override the API key for this invocation |
| --base-url <url> | Override the API base URL |
| --profile <name> | Use a specific named profile |
| --timeout <seconds> | HTTP request timeout in seconds (default 30; task execute defaults to 600) |
| --no-auto-update | Disable automatic CLI updates for this invocation |
| -v, --version | Print the CLI version and exit |
| --help | Show help for any command or subcommand |
Environment Variables
| Variable | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------ |
| NEXUS_API_KEY | API key (used when --api-key flag and config file are absent) |
| NEXUS_BASE_URL | API base URL override |
| NEXUS_ENV | Environment name: production (default) or dev |
| NEXUS_PROFILE | Profile name override for this shell only (same as --profile flag) |
| NEXUS_ORGANIZATION_ID | Organization a cross-org token acts on, for this shell only; outranks the profile's orgId |
| NEXUS_NO_AUTO_UPDATE | Turn the automatic updater off: no self-install and no version lookup (implied when CI is set) |
| NO_COLOR | Disable all color output (no-color.org) |
Input Patterns
The CLI offers flexible input for create and update commands.
The --body Flag
Most create/update commands accept --body for raw JSON input:
# Inline JSON
nexus agent create --body '{"firstName":"Ada","lastName":"Bot","role":"Assistant"}'
# From a JSON file
nexus agent create --body payload.json
# From stdin
cat payload.json | nexus agent create --body -
echo '{"firstName":"Ada","lastName":"Bot","role":"Assistant"}' | nexus agent create --body -Flag-Over-Body Merge
When you use both --body and individual flags, flags take precedence. The body provides defaults; flags override specific fields:
# Body sets firstName and role; --role flag overrides the role field
nexus agent create \
--body '{"firstName":"Ada","lastName":"Bot","role":"Assistant"}' \
--role "Senior Assistant"
# Result: { firstName: "Ada", lastName: "Bot", role: "Senior Assistant" }File and Stdin Input
Flags like --prompt, --content, and --description accept:
| Input | Example |
| ------------ | ---------------------------------------------------------------- |
| Literal text | --prompt "You are a helpful agent" |
| File path | --prompt ./system-prompt.md (auto-detected if the file exists) |
| Stdin | --prompt - (reads from stdin) |
# Load a prompt from a markdown file
nexus agent create --first-name Bot --last-name Helper --role QA --prompt ./prompt.md
# Pipe a prompt from another command
generate-prompt | nexus agent update abc-123 --prompt -Pagination
List commands support pagination:
nexus agent list --page 2 --limit 50The pagination footer shows total, page, and whether more available.
Output Modes
Table (Default for Lists)
ID FIRST NAME STATUS
──────────────────────────────────── ─────────────── ──────
abc-123-def-456 Support Bot ACTIVE
ghi-789-jkl-012 Sales Agent DRAFT
3 total · page 1 · more availableRecord (Default for Single Resources)
ID abc-123-def-456
Name Support Bot
Role Customer Support
Status ACTIVE
Created 2026-03-15T10:30:00.000ZJSON (--json)
nexus agent list --json{
"data": [{ "id": "abc-123", "firstName": "Support", "lastName": "Bot", "status": "ACTIVE" }],
"meta": { "total": 3, "page": 1, "paging": "has-more" }
}nexus agent get abc-123 --json{
"id": "abc-123",
"firstName": "Support",
"lastName": "Bot",
"role": "Customer Support",
"status": "ACTIVE"
}Important: Always use
--jsonwhen piping output tojqor other tools. The default table output is for humans and will break parsers.
Error Output in JSON Mode
When --json is active, errors are also returned as JSON:
{
"error": {
"message": "Authentication failed — invalid or missing API key.",
"hint": "Run \"nexus auth login\" to re-authenticate, or set NEXUS_API_KEY."
}
}Commands
All commands follow the pattern: nexus <group> <action> [arguments] [options]
Core Platform
| Command | Subcommands | Description |
| ---------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------- |
| auth | login logout switch list pin unpin status whoami | Authentication |
| agent | list get create update delete duplicate | AI agent management |
| agent-tool | list get create update delete | Agent tool configurations |
| version | list get create update delete restore publish | Prompt version management |
| folder | list create update delete assign | Agent folder organization |
| model | list | Available AI models |
Workflows & Execution
| Command | Subcommands | Description |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------- |
| workflow | list get create update delete duplicate publish unpublish validate test | Workflow CRUD and lifecycle |
| workflow node | create get update delete test variables output-format reload-props | Workflow node operations |
| workflow edge | create delete | Node connections |
| workflow branch | list create update delete | Branching logic |
| execution | list get graph output retry export node-result | Workflow execution history |
Knowledge & Documents
| Command | Subcommands | Description |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------- |
| document | list get upload create-text add-website import-google-sheets delete | Knowledge documents |
| collection | list get create update delete search documents attach-documents remove-document stats | Knowledge collections |
Skills & Tasks
| Command | Subcommands | Description |
| ---------------------------------------------------------------- | ---------------------------------------------- | ---------------------- |
| task | list get create update execute | AI task management |
| template | list get create upload generate | Document templates |
| external-tool | list get create update delete test | OpenAPI external tools |
Deployment & Testing
| Command | Subcommands | Description |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | --------------------------------- |
| deployment | list get create update delete duplicate stats embed-config embed-config-update | Agent deployments |
| deployment folder | list create update delete assign | Deployment folder organization |
| emulator | send | Send messages to test deployments |
| emulator session | create list get delete | Emulator session management |
| emulator scenario | save list get replay delete | Save and replay test scenarios |
Marketplace & Discovery
| Command | Subcommands | Description |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| tool | search get credentials connect resolve-options skills test execute connection-status create-credential delete-credential | Marketplace tool discovery |
Analytics & Operations
| Command | Subcommands | Description |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------- |
| analytics | overview feedback export | Organization analytics |
| eval | (subgroups: session, dataset, execute, judge, results, formats, judges) | AI task evaluation |
| ticket | list get create update comment comments | Bug and feature tracking |
| phone-number | search buy list get release | Phone number management |
| channel | setup connection list\|create whatsapp-sender list\|create\|get | Channel setup orchestrator |
| prompt-assistant | chat get-thread delete-thread | AI-assisted prompt writing |
Utility
| Command | Subcommands | Description |
| ---------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------- |
| api | (passthrough) | Call any API endpoint directly |
| docs | (topic browser) | View built-in documentation |
| upgrade | (self-update) | Upgrade the CLI binary to latest |
| skills | update list version where | Install/refresh the bundled .claude (skills + CLAUDE.md) into your project |
Full reference: See docs/command-reference.md for complete documentation of every command, option, and example.
Keeping the Claude Code skills up to date
nexus upgrade updates the CLI binary only. The .claude behavior layer
(CLAUDE.md + skills/) is bundled with each CLI release, so pull the latest
into your project separately:
nexus upgrade # 1. get the newest binary (and bundled skill set)
nexus skills update # 2. write that .claude into your projectnexus skills update auto-detects the project's existing .claude folder by
walking up from the current directory (existing .claude/ → CLAUDE.md →
git root), so it refreshes the owning project instead of dropping a stray
.claude in a subfolder. An existing, differing project CLAUDE.md is
preserved unless you pass --force. Use nexus skills where to preview
the target, --global for ~/.claude, or --dir for an explicit path.
Common Patterns
Extract IDs with jq
# Get the ID of a newly created agent
AGENT_ID=$(nexus agent create \
--first-name Bot --last-name Helper --role QA --json | jq -r '.id')
echo "Created agent: $AGENT_ID"Pipe JSON Output
# List all active agent IDs
nexus agent list --json | jq -r '.data[] | select(.status == "ACTIVE") | .id'
# Count deployments by type
nexus deployment list --json | jq '.data | group_by(.type) | map({type: .[0].type, count: length})'Bulk Operations
# Update all agents to use a specific model
nexus agent list --json | jq -r '.data[].id' | while read id; do
nexus agent update "$id" --model gpt-4o
echo "Updated $id"
doneRaw API Passthrough
For endpoints without a dedicated CLI command:
# GET request
nexus api GET /models
# POST with inline body
nexus api POST /agents --body '{"firstName":"Test","lastName":"Bot","role":"QA"}'
# GET with query parameters
nexus api GET /agents --query page=1 --query limit=5
# POST with body from file
nexus api PATCH /agents/abc-123 --body payload.json
# POST with body from stdin
echo '{"text":"hello"}' | nexus api POST /emulator/dep-1/sessions/s-1/messages --body -Suppress Confirmation Prompts (CI/CD)
# Skip delete confirmation
nexus agent delete abc-123 --yes
# Preview what would be deleted without executing
nexus agent delete abc-123 --dry-runLoad Prompts from Files
# Create an agent with a prompt from a markdown file
nexus agent create \
--first-name Support --last-name Bot --role "Customer Support" \
--prompt ./prompts/support-agent.md
# Update an agent's prompt from stdin
cat new-prompt.md | nexus agent update abc-123 --prompt -Workflow Build Pipeline
# Create, build, validate, test, and publish in one pipeline
WF_ID=$(nexus workflow create --name "Lead Qualifier" --json | jq -r '.id')
nexus workflow node create $WF_ID --type agentInputTrigger --name "Start"
nexus workflow node create $WF_ID --type aiTask --name "Qualify" \
--body '{"data":{"taskId":"task-123"}}'
nexus workflow validate $WF_ID
nexus workflow test $WF_ID --input '{"message":"I want to buy 100 units"}'
nexus workflow publish $WF_IDSDK Cross-Reference
Every CLI command maps to an SDK method. Use the SDK (@agent-nexus/sdk) when building applications; use the CLI for scripting and exploration.
| CLI Command | SDK Equivalent |
| --------------------------------------- | ----------------------------------------------------- |
| nexus agent list | client.agents.list() |
| nexus agent get <id> | client.agents.get(id) |
| nexus agent create --first-name X ... | client.agents.create({ firstName: "X", ... }) |
| nexus agent update <id> --role Y | client.agents.update(id, { role: "Y" }) |
| nexus agent delete <id> | client.agents.delete(id) |
| nexus agent-tool list <agentId> | client.agents.tools.list(agentId) |
| nexus version list <agentId> | client.agents.versions.list(agentId) |
| nexus workflow list | client.workflows.list() |
| nexus workflow publish <id> | client.workflows.publish(id) |
| nexus document upload <file> | client.documents.uploadFile(file) |
| nexus collection create --name X | client.documents.createCollection({ name: "X" }) |
| nexus deployment create --name X ... | client.deployments.create({ name: "X", ... }) |
| nexus emulator session create <depId> | client.emulator.createSession(depId) |
| nexus emulator send <depId> <sessId> | client.emulator.sendMessage(depId, sessId, { ... }) |
| nexus tool search --query X | client.tools.search({ query: "X" }) |
| nexus analytics overview | client.analytics.getOverview() |
| nexus model list | client.models.list() |
| nexus ticket create --title X ... | client.tickets.create({ title: "X", ... }) |
| nexus phone-number list | client.phoneNumbers.list() |
| nexus channel setup --type WHATSAPP | client.channels.getSetupStatus("WHATSAPP") |
| nexus channel connection create | client.channels.createConnection() |
| nexus channel whatsapp-sender create | client.channels.createWhatsAppSender({ ... }) |
Full SDK documentation: See @agent-nexus/sdk README
Error Handling
The CLI catches all errors and prints actionable messages with hints.
Error Types
| Error | Cause | Hint |
| -------------------------- | ------------------------------------- | ------------------------------------------------------------------------------ |
| Authentication failed | Invalid, missing, or expired API key | Run nexus auth login or set NEXUS_API_KEY |
| Not found (404) | Resource ID doesn't exist | Run nexus <resource> list to find valid IDs |
| Validation error (422) | Invalid request body or parameters | Add --json to see the details field |
| Connection error | Network issue or wrong base URL | Check --base-url and network connectivity |
| Client-side timeout | Response took longer than --timeout | Raise the limit: --timeout <seconds> (server may still complete the request) |
| API error (5xx) | Server-side error | Retry after a moment; report via nexus ticket create |
Exit Codes
| Code | Meaning |
| ---- | ------------------------------------------------------------- |
| 0 | Success |
| 1 | Any error (authentication, API, validation, connection, etc.) |
Error Format
Human-readable (default):
Error: Authentication failed — invalid or missing API key.
Run "nexus auth login" to re-authenticate, or set NEXUS_API_KEY.JSON (--json):
{
"error": {
"message": "Authentication failed — invalid or missing API key.",
"hint": "Run \"nexus auth login\" to re-authenticate, or set NEXUS_API_KEY."
}
}Troubleshooting
"No API key found"
Error: No API key found. Set NEXUS_API_KEY or run:
nexus auth loginFix: Run nexus auth login or set the NEXUS_API_KEY environment variable.
"Invalid key format -- keys start with nxs_"
Fix: Copy the full API key from Settings > API Keys, including the nxs_ prefix.
"Could not reach the Nexus API"
Fix: Check your network connection. If using a custom base URL, verify it:
nexus auth whoami # shows the current base URL"The request was still running after Ns, so the CLI stopped waiting"
The CLI's client-side timeout elapsed before the API responded — the server may still be processing (and completing) the request. This is not a network failure.
Fix: Raise the limit with the global --timeout flag, e.g. for a long structured-JSON generation:
nexus task execute task-123 --input "..." --timeout 900task execute already defaults to 600 s (all other commands default to 30 s).
"Validation failed (HTTP 401)"
Fix: Your API key may be expired or revoked. Regenerate it at Settings > API Keys and run nexus auth login again.
Colors Not Showing
The CLI disables colors when:
NO_COLORenvironment variable is set--no-colorflag is passed- stdout is not a TTY (e.g., piped to a file or another command)
Update Check Not Working
The version check cache is stored at ~/.nexus-mcp/version-check.json. Delete it to force a fresh check:
rm ~/.nexus-mcp/version-check.json
nexus agent list # triggers a new checkUpgrade Failed
If nexus upgrade fails (e.g., permission denied), run the install manually:
sudo npm install -g @agent-nexus/cli@latestConfiguration Files
| File | Purpose | Permissions |
| --------------------------------- | ------------------------------------------------------------- | ----------- |
| ~/.nexus-mcp/config.json | Profiles with API keys and base URLs | 0600 |
| ~/.nexus-mcp/version-check.json | Update check cache (auto-managed, checked once/day) | 0600 |
| .nexusrc | Directory-level profile pinning (created by nexus auth pin) | — |
The ~/.nexus-mcp/ directory is created with 0700 permissions. This path is shared with the @agent-nexus/mcp-server package.
Config File Format (V2)
{
"activeProfile": "work",
"profiles": {
"work": {
"apiKey": "nxs_...",
"baseUrl": "https://api.nexusgpt.io",
"orgName": "Acme Corp",
"orgId": "org_..."
},
"personal": {
"apiKey": "nxs_...",
"orgName": "My Startup"
}
}
}.nexusrc Format
{ "profile": "work" }Place in your project root. The CLI walks up the directory tree to find it. Consider adding .nexusrc to .gitignore.
Related Resources
| Resource | Link |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| Compatibility promise | COMPATIBILITY.md |
| SDK | @agent-nexus/sdk |
| Product Documentation | packages/docs |
| Claude Code Skills | claude-code-skills-nexus (separate repo) |
| API Reference | https://api.nexusgpt.io/api/public/v1 |
| Dashboard | app.nexusgpt.io |
| CLI Command Reference | docs/command-reference.md |
| Input/Output Guide | docs/input-output-patterns.md |
| Common Gotchas | docs/gotchas.md |
| Recipes | docs/recipes.md |
| Report Issues | nexus ticket create --type BUG --title "..." --description "..." |
| Request Features | nexus ticket create --type FEATURE_REQUEST --title "..." --description "..." |
Development
CI sweep gate
Every ready PR reports the CLI: Sweep context. It is the cli-sweep job of .github/workflows/pr-checks.yml, and it runs its work only when the PR touches the CLI's package graph — @agent-nexus/cli itself, or @agent-nexus/sdk / @nexus/types, which it depends on. On any other PR it reports skipped, which branch protection accepts.
Whether that context actually gates is not asserted here. Branch protection lives on GitHub and no file in this repository can see it, so a sentence claiming the check is required goes wrong silently the moment it is armed or disarmed. This paragraph read "which is a required check on
stagingandmain" while the context was required on neither — step 4 of the runbook below had never been performed — which made a red sweep read as blocking and its absence from a rollup read as impossible.
.github/required-contexts.jsondeclares the intent;scripts/required-contexts.tsreads it. Ask the live system rather than any prose:pnpm dlx tsx scripts/required-contexts.ts --reconcileThat needs admin —
GET /branches/{b}/protectionis admin-only and the defaultGITHUB_TOKENdoes not have it — so it cannot run in CI, and a declaration can sit unarmed indefinitely with nothing noticing.--verify(offline, and in CI viaGate specs) checks the declaration against the workflow, never against protection.
It must stay in pr-checks.yml rather than move to a workflow of its own: a workflow-level paths: filter makes a skipped workflow report no context at all, and protection then waits forever for a check that never arrives. A job-level if: reports skipped instead. See .github/required-contexts.json.
When it runs, it:
- Builds the CLI from PR sources (not npm)
- Authenticates against staging with a CI service profile
- Invokes
packages/cli/scripts/sweep.sh --profile ci --strict - Fails the PR on any FAIL or WARN
The sweep is read-only — it never invokes mutation verbs against staging. The --strict flag promotes WARN to FAIL so the --json contract is treated as load-bearing customer-facing behaviour.
Runbook — provisioning NEXUS_STAGING_API_KEY:
The CI workflow needs a staging API key in repo secrets. Provision it like so:
- Create a dedicated CI service org or use an existing read-only-scoped profile on staging
- Generate an API key from that profile's settings
- Add it to GitHub repo secrets as
NEXUS_STAGING_API_KEY(Settings → Secrets and variables → Actions) - Promote
CLI: Sweepto a required check onstagingandmainbranch protection rules
Until the secret is in place, the workflow fails at the "Authenticate against staging" step with a clear message — the job is intentionally advisory (not required) until provisioned.
Steps 1–3 are done and step 4 is not. The secret is provisioned (the sweep authenticates and runs its leaves), so the promotion is now due — and it is a branch-protection edit, which needs repo admin. Two things to confirm first, because arming a context carelessly makes PRs unmergeable with nothing red to point at:
- Every open PR must already carry the row. It does: a PR that touches no CLI file still reports
CLI: Sweepasskipping, because the job-levelif:skips the job rather than apaths:filter skipping the workflow. That is the entire reason this job lives inpr-checks.yml. - It must be reliably green when it does run. It was red on every CLI-touching PR until the sweep learned to SKIP a declared feature opt-out; confirm that fix is on
staging, not merely merged into a cluster, before arming.
Ask the live system rather than this page — the declaration and the arming are different facts and only one of them is in this repository:
pnpm dlx tsx scripts/required-contexts.ts --reconcile # needs repo adminA SKIP is a coverage loss, and it has to be declared
sweep.sh SKIPs a leaf when the backend declares the feature unavailable by policy — a 403 the environment is right to send, with no CLI defect behind it. Accepting that is correct. Reporting it as a bare number is not: 64 pass · 5 skip · 0 fail cannot tell an expected skip from a leaf that went dark this morning, and SKIP is excluded from the exit code by design.
So the accepted skips are declared in SWEEP_EXPECTED_SKIPS in src/command-universe.ts, and the sweep reads that declaration:
- a skip named there is accepted, and the report line says what coverage is gone;
- a skip not named there is a FAIL, naming the leaf and the remedy;
- a leaf named there that did not skip is reported
STALEand fails nothing — the environment recovering is good news, and a gate that reddens on good news gets its declarations deleted rather than its news read.
The summary carries the denominator: 64 pass · 5/5 declared skip · 0 warn · 0 fail.
The declared leaf stays safe on purpose. It is still executed on every run, so the day the environment answers again, coverage resumes with nobody remembering to flip anything. Parking it registration-only would stop the sweep watching it and would need a human to notice the recovery.
⚠️ A safe-with-fixture leaf that skips loses more than the others, because its non-emptiness assertion is the strongest thing the sweep asserts and the skip path bypasses it entirely. Its report line says so rather than reading like every other skip.
Local sweep
You can run the same sweep locally against any profile:
# Default profile, text output
bash packages/cli/scripts/sweep.sh
# Named profile, strict mode (mirrors CI exactly)
bash packages/cli/scripts/sweep.sh --profile prod --strict
# Machine-readable output (used by the /pinguin Claude skill)
bash packages/cli/scripts/sweep.sh --profile prod --jsonRelease process
🚨 A release takes TWO human merges. Your feature PR is the first. The release pull request that release-version.yml opens on main is the second, and nothing reaches npm until someone merges that one too. Waiting for a publish after a single merge is waiting for something that will not happen.
1 — Declare the release inside your PR. Add a changeset. Never touch the version field:
pnpm changeset # pick @agent-nexus/cli, pick a bump, write one summary lineThat writes .changeset/<two-random-words>.md, which you commit alongside your code. A new file per PR is exactly why two concurrent releases cannot conflict.
🚨 A hand-edited version value is REFUSED, and reaching for it is the most common way to get this wrong. version-bump-gate.yml runs scripts/check-release-intent.mjs on every PR that touches packages/{cli,sdk,mcp-server}, and a hand-edited version fails it with version was hand-edited. Editing that line does not ship faster: when a changeset is already pending for the same package it ships the change TWICE, and when the gate catches it the PR is simply red. The only version value you may write by hand is the initial X.Y.Z of a package your PR CREATES. A src/** change that deliberately releases nothing takes the no-version-bump label instead.
Full changeset rules, including what happens when you name the wrong package: .changeset/README.md.
2 — staging merges to main, carrying the changeset.
3 — release-version.yml opens the release pull request. On any push to main touching .changeset/** it runs changeset version on the fixed branch release/version-packages, force-pushes that branch, and opens or updates one PR titled chore(release): version packages. It writes nothing to main, so main keeps its changesets and every failed run is re-runnable.
4 — A HUMAN merges that release pull request. This is the second merge, and it is the one that publishes.
5 — mirror-public-packages.yml syncs and tags. That merge is a push to main by a real account, so on: push workflows fire. It copies the mirrored packages into the PUBLIC repository NexusGPT/agent-nexus and pushes <package>-v<version> whenever that tag is absent from the mirror. The tag step ensures by absence rather than by diff, so re-running it is safe and repairs a sync that died between commit and tag.
6 — The mirror publishes with provenance. The tag triggers release-cli.yml IN NexusGPT/agent-nexus, which builds and runs pnpm pack && npm publish ./*.tgz --access public --provenance under an OIDC trusted publisher — no NPM_TOKEN. Two things in that job look like omissions and are load-bearing:
- It sets no
registry-urlonsetup-node. That input writes//registry.npmjs.org/:_authToken=…into~/.npmrc, which makes npm prefer classic-token auth and defeats OIDC. - It packs and then calls
npm, rather thanpnpm publish. pnpm 10 does not forward--provenance.
Which packages ride this pipeline — read the list, do not trust one written down. From the repository root:
grep 'MIRROR_PACKAGES:' .github/workflows/mirror-public-packages.ymlWhat is declared and waiting to ship — the same predicate release-version.yml counts. From the repository root:
find .changeset -maxdepth 1 -name '*.md' ! -name 'README.md' | sortThe trusted publisher is configured on the MIRROR, never on NexusGPT/nexus. Trust binds one triple: repository NexusGPT/agent-nexus, the workflow file, and no environment. Drift in any of the three blocks the publish until trust is reconfigured from the @agent-nexus/cli settings page. The published attestation records that same triple, so ask npm instead of a document:
curl -s "https://registry.npmjs.org/-/npm/v1/attestations/@agent-nexus%2fcli@$(curl -s https://registry.npmjs.org/@agent-nexus/cli/latest | jq -r .version)" \
| jq -r '.attestations[] | select(.predicateType=="https://slsa.dev/provenance/v1") | .bundle.dsseEnvelope.payload' \
| base64 -d | jq '.predicate.buildDefinition.externalParameters.workflow'Provenance is live. Every published version carries a Sigstore attestation, and npm records the publisher as GitHub Actions rather than a person:
curl -s https://registry.npmjs.org/@agent-nexus/cli/latest \
| jq -r '"\(.name)@\(.version) published-by=\(._npmUser.name) provenance=\(.dist.attestations.provenance.predicateType // "NONE")"'The publish posture is audited daily, and that audit outranks this section. npm-publish-auth.yml runs scripts/audit-npm-publish-auth.mjs at 07:23 UTC against the live registry and fails when any @agent-nexus package's latest version was pushed by a human with a token. Known exceptions live in KNOWN_GAPS inside that script, each carrying its reason, and a waiver that outlives its problem fails the audit too. Read that run summary before believing any claim here — it re-derives the truth, and a sentence cannot.
Idempotency: npm refuses to re-publish an existing version and returns 403 EPUBLISHCONFLICT. That is the correct signal, not a bug. Recovery is a new changeset, never a hand-written version.
When a publish does not happen, work the pipeline — do not publish from a laptop. A token publish carries no provenance, breaks the trusted-publisher posture, and the daily audit reports it as a regression that KNOWN_GAPS must not excuse. Find the stage that stalled instead:
- No release PR on
main→ read the latestrelease-version.ymlrun.count=0means no changeset was ever declared; go back to step 1. - Release PR merged, nothing on npm → read the
mirror-public-packages.ymlrun for that merge. Both remaining stages are idempotent and dispatchable: the mirror sync from this repository, andrelease-cli.ymlinNexusGPT/agent-nexuswith the existingcli-v<version>tag as its input.
