OrcAI.Tool 0.9.0

There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global OrcAI.Tool --version 0.9.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local OrcAI.Tool --version 0.9.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=OrcAI.Tool&version=0.9.0
                    
nuke :add-package OrcAI.Tool --version 0.9.0
                    

OrcAI CLI

<p align="center"> <img src="assets/orcai_banner.png" alt="OrcAI" /> </p>

A CLI tool for orchestrating bulk GitHub work across many repositories. From a single YAML config, OrcAI creates a GitHub Project, opens templated issues in every target repo, and hands them off to whoever (or whatever) does the work — a human teammate, a bot, or an AI agent like GitHub Copilot or OpenCode.

Features

  • Declarative YAML jobs — one config defines the project, target repos, issue template, assignment behaviour, nudge policy, and notification template.
  • Bulk, idempotent issue creation across any number of repos — a lock file makes re-runs free; glob and brace expansion ("jobs/**/*.{yml,yaml}") for fanning out.
  • GitHub Project auto-management — finds or creates the board and links every issue to it.
  • Assign to anyoneassign.to accepts any GitHub user, bot, or GitHub App handle. Default is @copilot, but humans, OpenCode, and custom App bots all work.
  • Comment-based triggersassign.via: comment posts a slash command (e.g. /opencode) instead of assigning, for agents that listen for mentions. comment-and-assign does both.
  • Tag anyone in templated commentsassign.comment, nudge.comment, and notify all support {assignee}, {job.owner}, and {repo.codeowners} tokens, resolved from YAML and CODEOWNERS files at runtime.
  • orcai nudge — re-trigger stale issues (no linked PR yet) by reassignment, comment, or both.
  • orcai notify — broadcast a templated comment to issues and/or PRs from the lock file; filter by state, dry-run, and inject extra --data key=value template variables.
  • Auto issue-body updates — when the Markdown template changes, existing issues' bodies are updated without re-running the structural work (hash-based detection).
  • Dependent jobsdependsOn gates a downstream job on the completion of an upstream one (pr_merged or issue_closed), either per-repo (filter eligible repos individually) or all-repos (block the run until the entire upstream batch is done). orcai run resolves the full dependency chain automatically; orcai graph renders it as an ASCII tree; orcai validate catches cycles and missing upstream files.
  • Robust at scale — built-in rate limiting (60 writes/min, configurable) with exponential-backoff retry; closed-issue policy (create/reopen/skip/fail); concurrency control; --continue-on-error; JSON output for CI.
  • Multiple auth methods — ambient gh CLI, PAT, or GitHub App (manifest flow supported via orcai auth create-app). A PAT is only required when the assignee is @copilot.

Installation

OrcAI is distributed as a .NET global tool. Requires .NET 10 or later.

dotnet tool install --global OrcAI.Tool

Then run it as orcai.

Prerequisites

  • gh CLI: Install from cli.github.com — must be installed and on PATH
  • Authentication: The easiest option is to ensure gh is authenticated (gh auth login). OrcAI will use it automatically. For other methods see docs/cli-reference.md.

Quick start

1. Authenticate

The simplest option — if you already use the gh CLI, just make sure it's authenticated:

gh auth login

That's it. OrcAI will pick up the token automatically.

For PAT, GitHub App, or environment variable auth see docs/cli-reference.md.

2. Run a job

# Single config file
orcai run jobs/my-upgrade.yml

# All configs in a directory (quote the glob to prevent shell expansion)
orcai run "jobs/*.yml" --continue-on-error --json

# Limit concurrency to avoid rate limits
orcai run "jobs/*.yml" --max-concurrency 2

run finds or creates a GitHub Project, creates issues from your template, adds them to the project, and triggers the configured assignee — whether that's @copilot, another bot, an AI agent like OpenCode, or a human teammate. Triggering can be via assignment, a templated comment (e.g. a slash command), or both. On success a lock file (<basename>.lock.json) is written alongside the YAML for fast idempotent re-runs.

Commands

Command Description
orcai auth pat/app/create-app/switch Store credentials or switch profiles for all other commands
orcai generate Scaffold a YAML job config and stub issue template
orcai run Execute a bulk upgrade job (supports globs, concurrency control, JSON output)
orcai nudge Re-trigger stale issues with no linked PR (reassign, comment, or both)
orcai notify Post a templated comment to issues and/or PRs from the lock file
orcai validate Validate YAML config(s) and verify all repos are accessible
orcai info Display the current state of a job
orcai cleanup Tear down everything created by run
orcai graph Render the dependsOn dependency graph as an ASCII tree

For full flag details, output formats, lock file schema, and advanced usage see docs/cli-reference.md. For config file settings see docs/config.md.

The original Nushell scripts (orca.nu, cleanup.nu) are documented in docs/nushell-scripts.md.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.10.5 104 9/7/2026
0.10.4 119 9/3/2026
0.10.3 100 9/2/2026
0.10.2 87 9/2/2026
0.10.1 98 9/1/2026
0.10.0 105 8/30/2026
0.9.0 103 8/30/2026
0.8.1 297 6/16/2026
0.8.0 171 6/9/2026
0.7.5-beta6 121 6/9/2026
0.7.4-beta5 117 5/21/2026
0.7.3-beta4 126 5/19/2026
0.7.2-beta3 127 5/18/2026
0.7.1-beta2 136 5/18/2026
0.7.0-beta1 128 5/13/2026
0.6.0 168 5/7/2026
0.5.1 304 3/17/2026
0.5.0 176 3/16/2026
0.4.4 178 3/16/2026
0.4.3 167 3/16/2026
Loading failed

`provider:` YAML field — selects the issue/project tracking backend for a job.
`type: github` (default; omitting `provider:` entirely preserves current behaviour, no change needed to existing YAMLs).
`type: local` — tracks project/issue state as YAML + Markdown files on disk instead of calling the GitHub API. Useful for trying out a job's fan-out behaviour without touching GitHub, or for tracking work in a repo synced via git rather than the GitHub API.
`root:` (optional, `local` only) — where the local store is written, resolved relative to the YAML file's directory (same convention as `issue.template`). Defaults to `.orcai-local` next to the YAML file.
A job using `provider: local` never requires or resolves GitHub authentication — `orcai run`/`generate`/etc. work with no `GH_TOKEN`, PAT, App credentials, or `gh` CLI login at all.
Known limitations: `provider: local` has no PR tracking or bulk repo inspection. `orcai nudge`'s closed-PR handling and `orcai info`'s PR summary always show zero PRs for local jobs, and any `dependsOn` condition needing bulk repo state is unavailable.
`--provider local` flag on `orcai generate` — scaffolds a job YAML with the `provider: { type: local }` block instead of the default (GitHub).
`action:` YAML field — typed, explicit action to execute after issue creation. Supported types:
`assign-copilot` (default when `action:` is absent) — assigns `@copilot`, with an optional trigger comment.
`assign` — assigns any GitHub user or bot (`to` required, `comment` optional).
`comment` — posts a comment only, no assignment (`comment` required).
`comment-and-assign` — posts a comment then assigns (`to` and `comment` required).
`cmd` — runs a shell command or script per repo (`execute` for a script path, `run` for an inline command; mutually exclusive). Supports `args` and `cwd`. Template variables use `{{var}}` syntax: `{{repo}}`, `{{org}}`, `{{issue_number}}`, `{{issue_url}}`, `{{job_title}}`, `{{issue_text}}`, `{{issue_hash}}`, `{{yaml_hash}}`, `{{project_number}}`, `{{run_datetime}}`.
`noop` — skip the action step entirely (replaces `job.skipCopilot: true`).
`cmd-checkout` — clones the target repo (bare, `--depth 1`) and runs the command inside it. Worktrees are reused when the same repo appears across multiple jobs. Extra template variables: `{{checkout_path}}` and `{{job_title_slug}}`.
`cmd-to-github` — checkout → run → commit all changes → push branch → open PR. Supports three write-back modes: `open-pr` (default), `push-branch`, and `fork-and-pr`. Optional fields: `branch`, `commitMessage`, `prTitle`, `prBody`, `errorIfNoDiff`.
`open-pr`/`fork-and-pr` append a `Closes #{{issue_number}}` line to the PR body by default (unless the body already references the issue), so GitHub links the PR to the issue and auto-closes it on merge.
The push (`open-pr`/`push-branch`/`fork-and-pr`) always uses `git push --force`. The orcai-owned branch is always force-pushed by design, and every run starts from a fresh clone with no local remote-tracking ref for it, so `--force-with-lease` would reject the push as stale as soon as any prior run had already pushed to that branch.
Per-step failures (`CmdToGithubCheckoutFailed`/`CmdToGithubNoDiff`/`CmdToGithubPushFailed`/`CmdToGithubOpenPrFailed`) are persisted in the lock file and cleared again the next time the step succeeds, so a one-off failure doesn't get reported forever.
`copy:` list on `cmd`, `cmd-checkout`, and `cmd-to-github` actions — Docker-`COPY`-style staging of input files (e.g. a helper script or prompt file) from where `orcai` is invoked into the command's working directory before it runs. Each entry has `from` (static path or glob; zero matches is a hard error), `to` (exact file path on a single match, destination directory on a glob match), and `keep` (default `false` — deletes the copied file(s) again after the command finishes; for `cmd-to-github` this happens before the commit, so scratch files never leak into the PR diff).
New global config fields (`~/.config/orcai/config.json` / `.orcai/config.json`):
`checkoutRoot` — override the directory where repos are cloned for `cmd-checkout` and `cmd-to-github`. Defaults to an OS temp directory scoped to the run.
`action.writeBack` — global default write-back mode for `cmd-to-github` (`open-pr` | `push-branch` | `fork-and-pr`). Nested under `action` in the config JSON. Overridden by `writeBack` in the job YAML.
`action.onClosedPr` — global default for `cmd-to-github`'s `onClosedPr` (`skip` | `recreate` | `reopen` | `fail`). Nested under `action` in the config JSON. Overridden by `onClosedPr` in the job YAML.
`cmd-to-github` (`open-pr`/`fork-and-pr` write-back modes) is now idempotent by design instead of only by accident of a clean lock file. Before cloning, `orcai run` checks live GitHub state (a PR linked to the issue via `closingPullRequests`, and whether the branch already exists on the remote) — never the lock file, so behaviour is identical whether or not one is present:
An **open** PR with unchanged YAML/template hashes is left alone entirely — no clone, no re-run of `execute`, no push. This holds even with no lock file present at all (deleted, or a fresh checkout on CI): with no prior lock to compare against, the content is assumed unchanged rather than treated as changed, so a missing lock file never forces a redo/force-push of an already-open PR.
A **merged** PR is always left alone, regardless of hash changes.
A **closed** (unmerged) PR is handled per the new `onClosedPr` field (see below).
When no PR is found but the branch already exists on the remote and hashes are unchanged, only `gh pr create` is retried against the existing branch — still no clone/execute/push.
`onClosedPr` field on `cmd-to-github` (job YAML, mirrors `job.onClosedIssue`'s shape) — controls what happens when the only PR found for the branch is closed without merging. Values: `skip` (default — treat as an intentional decision, do nothing), `recreate` (redo the full run and open a brand-new PR), `reopen` (`gh pr reopen` plus a force-push of fresh content to the existing branch, no new PR), `fail` (record a failure requiring manual intervention). Has no effect with `writeBack: push-branch`, which never creates a PR.
**GitHub App permission**: The **Contents** permission on the GitHub App must now be set to **Read & write** (instead of Read) to support push-based action types (`cmd-to-github` with `open-pr` or `push-branch`). OrcAI injects the resolved App/PAT token into the checkout git/PR subprocesses automatically — no separate credential setup is required.
`dependsOn` YAML field — gates a downstream job on the completion state of one or more upstream jobs. Each entry specifies a `job` (relative path), `condition` (`pr_merged` | `issue_closed`), `scope` (`per_repo` | `all_repos`), and `untrackedRepos` (`include` | `skip`). Multiple entries use AND logic.
`orcai run` now resolves `dependsOn` chains in topological order before executing. Passing a downstream YAML is sufficient — upstream dependencies are discovered and run automatically. The `scope: all_repos` option blocks the entire downstream run when any upstream repo has not met the condition; `scope: per_repo` (default) filters the downstream repo list individually.
`orcai graph <yaml>` — new command that renders the `dependsOn` dependency tree as an ASCII diagram. File-system only; no GitHub API calls. Supports `--json` output.
`orcai validate` now detects circular `dependsOn` references and missing upstream files, reporting them as configuration errors.
`orcai nudge --on-closed-pr` — controls what happens when the only PRs found for an issue are closed without merging. Values: `skip` (default — don't nudge), `nudge` (re-trigger the assignee anyway), `fail` (report as a failure). Merged PRs are always treated as done and never trigger this flag.
`orcai migrate <yaml>` — upgrades a job YAML and its sibling `.lock.json` in place to the current schema version, preserving old runtime behaviour (e.g. `assign:`/`skipCopilot` → the equivalent `action:` block, `onClosedIssue` default preserved explicitly). The lock file step is purely local — no GitHub calls — so migrating never forces a job's repos back onto the live lookup path the way deleting the lock file would. Supports `--dryrun` and `--json`; always backs up any file it changes to `<file>.bak` first. Designed to extend to future schema hops (v2→v3, etc.) without restructuring — each hop is an independent step.
**BREAKING**: Issue and project identifiers are now opaque strings instead of GitHub-shaped integers (internal groundwork for supporting non-GitHub providers in the future). User-visible effects:
**Lock files**: the `.lock.json` format has changed. Existing lock files fail to load with a message pointing at `orcai migrate <yaml>` (upgrades the lock file in place, no GitHub calls) as the recommended fix; deleting the file and re-running `orcai run` still works but re-fetches state from GitHub for every repo.
**`--json` output**: issue and project numbers in `orcai run --json`, `orcai info --json`, and `orcai cleanup --json` are now emitted as JSON strings instead of numbers (e.g. `"issueNumber": "42"` instead of `"issueNumber": 42`). Human-readable console output (e.g. `#42`) is unchanged.
**BREAKING**: `onClosedIssue` default changed from `create` to `skip`. Previously, when a closed issue with a matching title was found, OrcAI would open a new issue alongside it. Now it treats the closed issue as already done and skips the repo. To restore the old behaviour, add `onClosedIssue: create` to the `job:` block in your YAML. The `redoOnClosed` YAML field and config option (added as a workaround for the wrong default on checkout actions) have been removed; use `onClosedIssue: create` instead.
**BREAKING**: `assign:` YAML block removed. Validation fails with a migration message when `assign:` is present. Migrate to `action: { type: assign-copilot, ... }` or the appropriate action type.
**BREAKING**: `job.skipCopilot` removed. Validation fails with a migration message when present. Use `action: { type: noop }` to skip assignment, or omit `action:` to assign `@copilot`.
**BREAKING**: `--skip-copilot` CLI flag removed from `orcai run` and `orcai generate`. Use `action: { type: noop }` in the YAML instead.
**BREAKING**: `skipCopilot` and `assign` fields removed from the global/local JSON config (`~/.config/orcai/config.json`). `action:` is per-job only.
**BREAKING**: Top-level `writeBack` config key moved to `action.writeBack`. Update config files from `"writeBack": "..."` to `"action": { "writeBack": "..." }`.
`orcai generate` no longer generates a `skipCopilot` comment line; generates an `action:` comment block instead.
`orcai generate` now scaffolds a `version: 2` field at the top of every generated job YAML, so newly-created files self-declare their schema version the same way `orcai migrate` stamps it onto upgraded ones. The field is inert today (ignored by the parser) but lets future schema migrations detect a file's version without a structural heuristic.
`orcai nudge` and `orcai notify` derive the `{assignee}` template variable from the job's `action:` type rather than `assign.to`.
`orcai nudge` now surfaces PR state when checking for existing PRs. The `state` field (`OPEN`, `CLOSED`, `MERGED`) is stored on PR entries in the lock file; old lock files without the field default to `OPEN` on load.
`orcai nudge` no longer treats a closed PR in the lock file as "PR exists — skip". Only open PRs in the lock suppress the live check. Closed PR entries (e.g. written by `orcai info --save-lock`) are now ignored by the lock-file fast-path, so nudge correctly proceeds to a live GitHub check for those issues.
`orcai nudge --save-lock` now persists all discovered PRs with their state to the lock file, so closed PRs are visible via `orcai info`.