Skip to content

neo repo

Manage repositories

neo repo [OPTIONS] <COMMAND>

Create a repository.

Idempotent: a 409 conflict is reported as “already exists” and exits 0.

neo repo create [OPTIONS] <TARGET>
Option Value Required Default Env Values Description
<TARGET> yes <owner>/<name> — owner defaults to the authenticated handle when a stored login exists
--visibility <VISIBILITY> no public public, internal, private Visibility for the new repository. Defaults to public. Ignored when it already exists — create never changes an existing repo’s visibility

Ensure a repo exists, wire a git remote, and push this checkout.

Auth comes from neo auth login or NEOSOURCE_TOKEN env (set NEOSOURCE_USER too if you skip login).

neo repo push [OPTIONS] <TARGET> [GIT_ARGS]...
Option Value Required Default Env Values Description
<TARGET> yes <owner>/<name> — owner defaults to the authenticated handle when a stored login exists
--remote <REMOTE> no Remote name to create/update. Defaults to origin, or to neosource beside an origin that belongs to a different repo — see neo push --remote
--visibility <VISIBILITY> no public public, internal, private Visibility applied ONLY when the repo doesn’t exist yet and must be created; has no effect on an already-existing repo’s visibility. Defaults to public — note that the top-level neo push spells the same flag with a private default
--runner no Run the CI this push triggers on this machine — see neo push --runner
<GIT_ARGS> no Extra arguments forwarded to git push after the remote name. Defaults to HEAD:refs/heads/<branch> --follow-tags

Clone a repository from this forge.

The URL follows the same rule neo push uses for a remote it writes: SSH when this machine has a key registered (neo ssh setup) and an ssh client, HTTPS otherwise.

An HTTPS clone authenticates with the stored login or NEOSOURCE_TOKEN, for the clone only: the token is not written into the new checkout’s .git/config. Later git fetch/git push there authenticate through the credential helper neo auth login registers. With no login and no NEOSOURCE_TOKEN the clone is anonymous, which works for public repositories.

neo repo clone [OPTIONS] <TARGET> [DIR] [-- <GIT_ARGS>...]
Option Value Required Default Env Values Description
<TARGET> yes <owner>/<repo> (a trailing .git is accepted), or bare <repo> with the authenticated handle as owner
<DIR> no Directory to clone into. Defaults to git’s own choice, the repository name
<GIT_ARGS> no Extra arguments forwarded to git clone, after -- (e.g. neo repo clone acme/api -- --depth 1)

Take over a local checkout: create, push everything, become origin.

The other half of neo push, which never displaces an origin belonging to somewhere else (it writes neosource beside it, and takes the origin name only when nothing else claims it). Adopt makes neosource this checkout’s origin regardless and demotes the old one to a second remote (default github), so git push goes here from now on.

Order of operations: create the repo if it doesn’t exist → push refs/heads/* and refs/tags/* BY URL (never forced, never --mirror) → only then rewrite the remotes. A failed push therefore leaves the checkout untouched. The old origin URL is never deleted, and branches that tracked origin are re-pointed at the new one.

The adopted repo carries NO upstream-relationship record: neosource does not track it as imported from anywhere, and nothing syncs back unless you pass --mirror-back.

neo repo adopt [OPTIONS]
Option Value Required Default Env Values Description
--to <TO> no <owner>/<name> on neosource. Inferred when omitted: owner = your handle, name = the GitHub origin’s repo name, else the work-tree directory name
--visibility <VISIBILITY> no private public, internal, private Visibility applied ONLY when the repo has to be created. Defaults to private — adopt takes over an existing checkout, so the safe default is not to publish it
--old-remote <OLD_REMOTE> no github Name the current origin is renamed to. Refuses if a remote by that name already exists with a different URL — the old URL is never lost
--mirror-back no Also register a push mirror from neosource back to the GitHub repo origin pointed at, so the old home keeps receiving updates. The mirror pushes through the neosource GitHub App when it is installed on that repository with Contents and Workflows write; otherwise a linked classic GitHub login with the admin:public_key scope registers a deploy key. When neither is available the command prints what to fix (install the App, accept its permissions, or add a generic mirror) and still succeeds
--dry-run no Print the plan and exit, changing nothing. One read-only existence probe against the server is all it sends: nothing is created, pushed or rewritten
--yes no Accept a non-public target host (localhost, a private IP, .internal) without naming it via --host. Debug builds default to the local dev stack and NEOSOURCE_API is set in every dev worktree, so adopt refuses to repoint origin there unless you say so
--force no Proceed past the safety refusals: an origin already on this forge but pointing at a different repo, and a target repo that shares no history with this checkout

Show a repository’s configuration.

Prints the mutable settings (neo repo set changes these) plus version — the optimistic-concurrency token set sends back as expected_version so a concurrent edit is rejected rather than overwritten.

neo repo view [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, or bare <repo> with the authenticated handle as owner. Omitted, it is inferred from a checkout remote pointing at this host
--json no Print the repository record as JSON

Change a repository’s settings.

Reads the repo’s current version and sends it back as expected_version, so a concurrent edit is refused rather than silently overwritten. A refusal is TERMINAL: the command prints the conflict and exits 1 without retrying, because the state it decided against is already gone and nothing here can re-decide it. Flags you do not pass are left unchanged.

neo repo set [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, or bare <repo> with the authenticated handle as owner. Omitted, it is inferred from a checkout remote pointing at this host
--visibility <VISIBILITY> no public, internal, private Who can see the repository
--description <DESCRIPTION> no One-line description. Pass an empty string to clear it
--default-merge-method <DEFAULT_MERGE_METHOD> no merge, squash Method the merge button uses when a merge request names none. Must end up inside --allowed-merge-methods
--allowed-merge-methods <METHOD,...> no merge, squash Comma-separated set of methods the repository permits at all (merge, squash). Must contain the resulting default
--delete-branch-on-merge `<true false>` no true, false
--workspace-secrets <WORKSPACE_SECRETS> no auto, enabled, disabled Whether trusted CI runs on this repository receive the workspace’s CI secrets and may write its shared compile cache. auto (every repository’s starting point) grants them exactly while the repository’s creator is an owner or admin of its workspace, re-decided at every job; enabled and disabled override that. Changing it needs the right to manage the workspace’s CI secrets (a workspace admin or owner) — repository admin alone is refused. Takes effect on the next job claimed.
--ci-workflow-source <CI_WORKFLOW_SOURCE> no neosource, neosource-and-github Which workflow directories the repository runs. neosource (every repository’s starting point) runs only .neosource/workflows/; files in .github/workflows/ are reported in the Actions tab but never run. neosource-and-github runs .github/workflows/ too, in place — no file move — except a file named like one in .neosource/workflows/, which the native file shadows. Turning it on registers the default branch’s current files at once but starts no run itself: a promoted on: schedule workflow fires at its next tick, the others on the next push. Turning it off stops new runs, re-runs and approvals of those files at once; runs already queued or in progress are not cancelled. Needs repository admin.
--force no Skip the read and send no expected_version — last write wins. For when the current state genuinely does not matter
--json no Print the updated repository record as JSON

Rename a repository.

Metadata only — storage is keyed by an immutable repository ID, so no data moves. Every URL and git/jj remote naming the old path stops resolving; this command reports that and rewrites no remote of its own (repointing a checkout is neo repo adopt’s job).

neo repo rename [OPTIONS] --name <NAME> [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, or bare <repo> with the authenticated handle as owner. Omitted, it is inferred from a checkout remote pointing at this host
--name <NAME> yes The new name — the <name> half of <owner>/<name>
--json no Print the renamed repository record as JSON

Delete a repository.

Immediate for every reader: the repository stops resolving at once. Its data is reaped asynchronously afterwards. Forks are kept. Needs repository admin; a repository that does not exist or is already deleted exits non-zero.

On a terminal it asks you to type the full <owner>/<repo> back; anywhere else it refuses and sends nothing unless --yes is given.

neo repo delete [OPTIONS] <TARGET>
Option Value Required Default Env Values Description
<TARGET> yes <owner>/<repo>, in full. Never inferred from a checkout remote and never completed from the signed-in handle
--yes no Delete without asking — required when stdin is not a terminal

Read and write branch-protection rules

neo repo protection [OPTIONS] <COMMAND>

List every protection rule on the repository.

Rules are keyed on glob PATTERNS (**, release/*), not on branch names, so this is how you find out which pattern to view or edit — protection view --branch main 404s on a repo whose rule is **. Requires admin access: the route doubles as the permission probe, so a non-admin sees a 404 rather than an empty list.

neo repo protection list [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--json no Print the rule list as JSON

Show the rule keyed on exactly this pattern

neo repo protection view [OPTIONS] --branch <BRANCH> [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--branch <BRANCH> yes The pattern the rule is keyed on. A / is percent-encoded into one path segment for you (release/v1 → release%2Fv1)
--json no Print the rule as JSON

Create or update the rule keyed on this pattern.

The route is a FULL REPLACE whose body defaults every omitted field to off/zero, so this command reads the stored rule first and applies only the flags you pass — otherwise --required-reviews 2 would also clear require_pull_request on main. Pass --replace to get the route’s literal behaviour: the flags given, everything else off.

Unlike neo repo set there is no expected_version on this route, so two concurrent edits to one rule can still lose one of them.

neo repo protection set [OPTIONS] --branch <BRANCH> [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--branch <BRANCH> yes The pattern the rule is keyed on (main, release/*, **)
--required-check <CONTEXT> no A check context that must be green to merge. Repeat for several; together they REPLACE the stored list. Not comma-split, because a context legitimately contains commas (build (ubuntu, 22.04))
--clear-required-checks no Require no named checks at all, clearing the stored list
--required-reviews <N> no Approving reviews needed before a merge
--require-any-check `<true false>` no true, false
--strict-up-to-date `<true false>` no true, false
--require-conversation-resolution `<true false>` no true, false
--enforce-admins `<true false>` no true, false
--require-pull-request `<true false>` no true, false
--allow-force-pushes `<true false>` no true, false
--allow-deletions `<true false>` no true, false
--replace no Send exactly the flags given and default everything else to off/zero — the route’s own full-replace semantics
--json no Print the saved rule as JSON

Delete the rule keyed on this pattern.

No rule means no gate — neither the merge gate nor the push gate.

neo repo protection remove [OPTIONS] --branch <BRANCH> [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--branch <BRANCH> yes The pattern the rule is keyed on

Turn GitHub sync on or off, read its state, and manage preserved refs.

Attach, cancel and detach take a signed-in session or an access token with no workspace or repository lock — the one neo login mints has none. A token locked to a workspace or repository is refused with forbidden even when its owner is a repository admin.

neo repo github [OPTIONS] <COMMAND>

Show the repository’s GitHub relationship, pull-back health and blockers.

Prints the relationship (mode, authority, write health, sync state, the git pull-back’s schedule and failures), the latest attach, and every blocker with its fix code and the link where it is fixed. git_last_error is printed verbatim: a github_sync_would_discard_refs refusal lists each ref and the commands that rescue it. git_next_run_at reads not armed when nothing pulls the repository’s refs from GitHub. Requires read access.

A report: exits 0 even when blockers are listed, and 0 with a one-line answer for a repository that has no GitHub relationship. A repository that does not exist, or that you cannot read, exits 1.

neo repo github status [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--json no Print the response as JSON, exactly as the server sent it; null when the repository has no GitHub relationship

Preview the repository’s GitHub attach: what it would do to every ref.

Read-only. Shows the attach in flight, else the most recent one: its state, target and plan_hash; every ref with how its local and GitHub tips compare and what the attach does with it (preserved_as where a local tip is kept under neosource-preserved/); then, in a separate section, every ref a confirm would PUSH TO GITHUB; the open pull requests whose refs move; warnings; and blockers, each with its fix code and link. While the run is still planning the plan is being computed and the lists are empty. Requires repository admin.

Exits 1 when the preview lists blockers, after printing each fix; --json keeps that exit code. A repository with no attach run exits 0 with a one-line answer.

neo repo github plan [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--json no Print the response as JSON, exactly as the server sent it; null when the repository has no attach run

Connect the repository to the neosource GitHub App — the step before attach.

Binds the repository to a GitHub App installation for its GitHub upstream (the repository its GitHub relationship names), the server finding the installation and GitHub’s repository id: an installation you connected, or one your organisation’s workspace may use, that reaches that repository. The same as Connect GitHub App in Settings → GitHub. Nothing is written to GitHub and sync is not turned on; attach does that.

Refused, with the server’s code: github_app_install_required when no installation you may use reaches the GitHub repository (the message carries the install link — install the App there, then run this again); invalid_input when the repository has no GitHub upstream; conflict when it is already connected to a different GitHub repository, or that GitHub repository to another repository here.

Requires repository admin, from a signed-in session or a token. A repository-locked token is refused; a workspace-locked token reaches only installations claimed for that workspace.

neo repo github connect [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host

Attach the repository to GitHub: turn GitHub sync on.

Starts an attach to the GitHub repository the repository’s GitHub App binding names (connect makes one) — or reuses the one already in flight — waits for its plan, prints it as plan does, and confirms it. GitHub then becomes authoritative for the repository’s refs; a local tip the plan moves or drops is kept under neosource-preserved/, never lost. The attach itself runs in the background after the confirm; status follows it.

Asks before confirming on a terminal (default no); anywhere else it refuses and sends nothing unless --yes is given. A plan that PUSHES refs to GitHub (its publish list) needs a second consent: --publish, or a yes to a separate publish prompt. --yes alone never publishes: with publish rows and no --publish it confirms nothing and exits 1.

Exits 1 without confirming when the plan lists blockers — or, while the plan is still being computed, when the repository does — after printing each fix. Once they are fixed, run cancel and then this again: a planned run is not re-planned on its own. A confirm the server refuses because the plan expired or the relationship moved since it was read (relationship_conflict) confirms nothing: run this again to review the new plan. A confirmed plan that goes stale before it is applied is re-planned on the server (needs_reconfirm) and needs a new review — plan shows it, this command confirms it.

Requires repository admin, from a signed-in session or a token with no workspace or repository lock; a locked token is refused.

neo repo github attach [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--yes no Confirm without asking — required when stdin is not a terminal. Never implies --publish
--publish no Consent to pushing the plan’s publish list to GitHub under the refs’ own names. Needed (with --yes, or instead of the publish prompt) when the plan publishes; ignored when it publishes nothing
--timeout <TIMEOUT> no 300 Maximum seconds to wait for the plan to be computed (at most an hour). On timeout nothing is confirmed and the run stays; run this again to pick it up

Cancel the GitHub attach in flight.

Before the attach froze the relationship this only ends the run; after, the relationship is put back exactly as it was first. Nothing is written to GitHub. Refused once the attach is publishing or applying — past the point it can be undone. Exits 1 when no attach is in flight.

Requires repository admin, from a signed-in session or a token with no workspace or repository lock; a locked token is refused.

neo repo github cancel [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host

Detach the repository from GitHub: turn GitHub sync off, or move a read-only mirror to neosource.

Works on a synced repository and on a read-only mirror of GitHub (“Move to neosource” in Settings → GitHub). This deployment becomes authoritative for the repository from here on and stops pulling from GitHub, so pushes here are accepted; a pull already in flight may finish once, never over a newer push. Nothing is written to GitHub; commits pushed there after the last pull stay only there. Any GitHub App binding, every branch and tag, every preserved ref and the repository’s issues are kept. The repository can be attached again later.

Asks first on a terminal (default no); anywhere else it refuses and sends nothing unless --yes is given. Refused (relationship_conflict, nothing changed) while a push to GitHub has not settled — the message names it — while an attach holds the relationship (for a mirror: any attach in flight; cancel it first), or when the relationship moved since it was read.

Requires repository admin, from a signed-in session or a token with no workspace or repository lock; a locked token is refused.

neo repo github detach [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--yes no Detach without asking — required when stdin is not a terminal

List and delete preserved refs — local tips an attach kept under neosource-preserved/

neo repo github preserved [OPTIONS] <COMMAND>

List every branch and tag under neosource-preserved/.

Where an attach keeps a local tip it would otherwise have moved or dropped. These refs live only here: they are never pushed to GitHub. Each names the attach that made it when one is known. Requires read access.

neo repo github preserved list [OPTIONS] [TARGET]
Option Value Required Default Env Values Description
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--json no Print the response as JSON, exactly as the server sent it

Delete one preserved ref.

A local delete only: the ref never reached GitHub, so nothing is written there. The server refuses any name outside refs/heads/neosource-preserved/ and refs/tags/neosource-preserved/ (invalid_input); this is not a general branch delete. Requires repository admin.

A preserved ref can be the only reference to its commits, so this asks first on a terminal (default no). Anywhere else it refuses and sends nothing unless --yes is given. Exits 1 when nothing was deleted.

neo repo github preserved delete [OPTIONS] <REF> [TARGET]
Option Value Required Default Env Values Description
<REF> yes The full refname, e.g. refs/heads/neosource-preserved/2026-10-13-1a2b3c/main, as neo repo github preserved list prints it
<TARGET> no <owner>/<repo>, bare <repo>, or omitted to infer from a checkout remote pointing at this host
--yes no Delete without asking — required when stdin is not a terminal