# omni-dev User Guide
A comprehensive guide to using omni-dev's AI-powered commit message
intelligence.
## Table of Contents
1. [Your First Improvement](#your-first-improvement)
2. [Getting Started](#getting-started)
3. [Core Concepts](#core-concepts)
4. [Command Reference](#command-reference)
5. [Claude Code Integration](#claude-code-integration)
6. [Atlassian Integration](#atlassian---jira-and-confluence-integration)
7. [Datadog Integration](#datadog-integration)
8. [Gmail Integration](#gmail-integration)
9. [Contextual Intelligence](#contextual-intelligence)
10. [Workflows](#workflows)
11. [Advanced Usage](#advanced-usage)
12. [Best Practices](#best-practices)
## Your First Improvement
The fastest way to learn omni-dev is to run it on a throwaway commit you
control end to end. This tutorial takes about 5 minutes in any git repo
and walks through the three core commands — `view`, `twiddle`, `check`.
First-time setup (install + auth + `.omni-dev/`) is covered in
[Getting Started](getting-started.md). This tutorial assumes you've done
that already.
### Step 1 — Make a deliberately bad commit on a scratch branch
```bash
git checkout -b omni-dev-tutorial
echo "" >> README.md
git add README.md
git commit -m "wip"
```
### Step 2 — Inspect it with `view`
```bash
omni-dev git commit message view 'HEAD~1..HEAD'
```
Expected: YAML output describing the commit, its diff, and the
field-presence summary. Skim it — you don't have to read it all.
### Step 3 — Improve it with `twiddle`
```bash
omni-dev git commit message twiddle 'HEAD~1..HEAD'
```
Expected: omni-dev prints a suggested rewritten message (something like
`docs(readme): add trailing newline`), shows a before/after diff, and
prompts `Apply these amendments? [y/N]`. Press `y`.
### Step 4 — Verify with `git log`
```bash
git log --oneline HEAD~1..HEAD
```
Expected: the subject line is now the AI-suggested message.
### Step 5 — Validate against your guidelines with `check`
```bash
omni-dev git commit message check 'HEAD~1..HEAD'
```
Expected: the check passes (exit 0). If you have project-specific scopes
in `.omni-dev/scopes.yaml` that the suggestion didn't use, re-run
`twiddle` — see the [Configuration Guide](configuration.md) to teach
omni-dev about your scopes.
### Cleanup
```bash
git checkout - && git branch -D omni-dev-tutorial
```
### What just happened
You ran the three core commands — `view` (analyse), `twiddle` (improve),
`check` (validate) — that together cover the full omni-dev workflow.
Everything else in this guide builds on these three.
## Getting Started
### Prerequisites
1. **Install omni-dev**
```bash
cargo install omni-dev
```
2. **Authenticate.** By default, `export CLAUDE_API_KEY="sk-ant-..."`.
See [Authentication](configuration.md#authentication) for the full
reference (alternative env-var names, `.env` files, CI/CD secrets,
non-Anthropic backends).
3. **Verify Installation**
```bash
omni-dev --version
omni-dev help-all ```
### First Use
Transform your commit messages and create professional PRs in 4 steps:
```bash
# 1. Navigate to your git repository
cd your-project
# 2. Improve recent commits with AI intelligence
omni-dev git commit message twiddle 'HEAD~5..HEAD' --use-context
# 3. Review and apply the suggestions
# The tool will show you before/after and ask for confirmation
# 4. Create a professional PR with AI-generated description
omni-dev git branch create pr
# Analyzes your commits and generates comprehensive PR description
```
## Core Concepts
### The Four-Command Workflow
omni-dev follows a simple analyze → improve → apply → ship workflow:
```bash
# 📊 ANALYZE: See detailed commit information
omni-dev git commit message view 'HEAD~3..HEAD'
# 🤖 IMPROVE: Get AI-powered suggestions
omni-dev git commit message twiddle 'HEAD~3..HEAD' --use-context
# ✏️ APPLY: Apply specific amendments manually
omni-dev git commit message amend amendments.yaml
# 🚀 SHIP: Create professional PR with AI description
omni-dev git branch create pr
```
### Key Benefits
- **Contextual**: Understands your project structure and conventions
- **Safe**: Always asks for confirmation before making changes
- **Intelligent**: Uses actual code changes, not just file names
- **Batch-Aware**: Handles large commit ranges efficiently
- **Professional**: Generates conventional commit format
## Command Reference
### `twiddle` - AI-Powered Improvement
The main command for improving commit messages:
```bash
# Basic usage with contextual intelligence
omni-dev git commit message twiddle 'origin/main..HEAD' --use-context
# Common options
omni-dev git commit message twiddle [RANGE] [OPTIONS]
```
**Key Options:**
| `--fresh` | Ignore existing messages and generate fresh ones from the diffs alone (the default; conflicts with `--refine`) | `--fresh` |
| `--refine` | Use the existing messages as the starting point for AI refinement (conflicts with `--fresh`) | `--refine` |
| `--use-context` | Enable AI contextual intelligence | `--use-context` |
| `--work-context TEXT` | Describe the work being done to steer suggestions | `--work-context "feature: user auth"` |
| `--branch-context TEXT` | Override the context detected from the branch name | `--branch-context "bugfix: login flow"` |
| `--context-dir PATH` | Custom context directory | `--context-dir ./config` |
| `--no-context` | Disable contextual features | `--no-context` |
| `--model MODEL` | Claude API model to use (defaults from settings or the model registry) | `--model claude-sonnet-4-5` |
| `--beta-header KEY:VALUE` | Beta header to send with API requests (only sent if the model supports it) | `--beta-header key:value` |
| `--concurrency N` | Number of parallel commit processors (default: 4) | `--concurrency 2` |
| `--no-coherence` | Skip cross-commit coherence refinement pass | `--no-coherence` |
| `--no-ai` | Skip AI; amend commits to a deterministic suggestion (detected type and scope, corrected against `scopes.yaml`) and leave already-conforming commits untouched | `--no-ai` |
| `--auto-apply` | Apply changes without confirmation | `--auto-apply` |
| `--allow-pushed` | Allow amending commits already in remote main branches (rewrites published history) | `--allow-pushed` |
| `--check` | Run commit message validation after applying amendments | `--check` |
| `--save-only FILE` | Save suggestions to file instead of applying | `--save-only suggestions.yaml` |
| `--quiet` | Only show errors/warnings, suppress info-level output | `--quiet` |
**Commit Range Examples:**
```bash
# Last 5 commits
omni-dev git commit message twiddle 'HEAD~5..HEAD'
# All commits on current branch vs main
omni-dev git commit message twiddle 'origin/main..HEAD'
# Specific range between commits
omni-dev git commit message twiddle 'abc123..def456'
# Single commit
omni-dev git commit message twiddle 'HEAD^..HEAD'
```
### `view` - Analysis and Inspection
Analyze commits without making changes:
```bash
# Analyze recent commits (YAML output)
omni-dev git commit message view 'HEAD~3..HEAD'
# Analyze current branch vs main
omni-dev git branch info main
# Without an argument, the base branch is resolved remote-first:
# origin/main, then origin/master, then local main/master
omni-dev git branch info
```
The output includes:
- Commit metadata (hash, author, date, message)
- File changes and diff statistics
- Conventional commit type detection
- Proposed improvements
- Remote branch tracking
### `amend` - Manual Application
Apply specific amendments from a YAML file:
```bash
# Apply amendments from file
omni-dev git commit message amend amendments.yaml
```
YAML format:
```yaml
amendments:
- commit: "abc123def456..."
message: |
feat(auth): implement OAuth2 authentication
Add comprehensive authentication system:
- OAuth2 integration with Google/GitHub
- JWT token management
- User session handling
- Role-based access control
```
#### Safety: already-pushed commits
Amending rewrites history, so `amend` (and `twiddle`'s apply step) refuses to
touch a commit that is already contained in a remote main branch (for example
`origin/main`). The same containment information appears in `view` output as
the per-commit `in_main_branches` field.
If you are certain you want to rewrite published history, pass the override
flag:
```bash
omni-dev git commit message amend --allow-pushed amendments.yaml
```
Main-branch detection is local and offline: it uses the remote's symbolic
`HEAD` ref when available, falling back to common branch names
(`main`/`master`/`develop`). Remotes whose main branch cannot be determined
locally are skipped conservatively.
### `check` - Commit Message Validation
Validate commit messages against guidelines without modifying anything.
Useful in CI, pre-push hooks, and as a non-destructive sibling to `twiddle`.
```bash
# Default range: commits ahead of the default base branch, resolved
# remote-first (origin/main, then origin/master, then local main/master);
# errors if none of these exist — pass an explicit range in that case
omni-dev git commit message check
# Explicit range
omni-dev git commit message check 'HEAD~5..HEAD'
# CI-friendly: exit non-zero on any issue (warnings included)
omni-dev git commit message check --strict
# Quiet output (errors/warnings only)
omni-dev git commit message check --quiet
# Show analysis for passing commits too
omni-dev git commit message check --verbose
omni-dev git commit message check --show-passing
# Structured output for tooling
omni-dev git commit message check -o json
omni-dev git commit message check -o yaml
# Offer to apply suggested fixes when issues are found
omni-dev git commit message check --twiddle
```
**Key Options:**
| `--strict` | Exit non-zero if any issue is reported (including warnings) |
| `--quiet` | Suppress info-level output |
| `--verbose` | Include detailed analysis for every commit |
| `--show-passing` | Include passing commits in the report |
| `-o, --output text\|json\|yaml` | Output format (default `text`) |
| `--no-coherence` | Skip the cross-commit coherence pass |
| `--no-suggestions` | Skip generating corrected message suggestions |
| `--twiddle` | When issues are found, prompt to apply suggested fixes |
| `--guidelines PATH` | Use a guidelines file outside `.omni-dev/` |
| `--context-dir PATH` | Custom context directory |
| `--concurrency N` | Maximum concurrent AI requests (default 4) |
| `--model MODEL` / `--beta-header KEY:VALUE` | Override the Claude model and beta headers |
### `create pr` - AI-Powered Pull Request Creation
Generate professional pull requests with AI-analyzed descriptions:
```bash
# Create PR with AI-generated description
omni-dev git branch create pr
# Create PR for specific base branch
omni-dev git branch create pr main
# Common options
omni-dev git branch create pr [BASE_BRANCH] [OPTIONS]
```
**Key Options:**
| `--base BRANCH` | Base branch (defaults to `main` / `master`) | `--base release/2.x` |
| `--ready` | Force the PR to open as ready-for-review | `--ready` |
| `--draft` | Force the PR to open as a draft | `--draft` |
| `--no-push` | Skip the implicit `git push` before creating the PR | `--no-push` |
| `--model MODEL` | Override the Claude model used to draft the PR body | `--model claude-opus-4-7` |
| `--context-dir PATH` | Custom context directory (defaults to `.omni-dev/`) | `--context-dir ./config` |
| `--auto-apply` | Create/update PR without confirmation | `--auto-apply` |
| `--save-only FILE` | Save PR details to YAML file instead of creating | `--save-only pr-details.yaml` |
| `--from-commits` | Drive PR generation from commit messages instead of the diff (faster, no diff bytes are sent to the AI) | `--from-commits` |
**What it does:**
- Analyzes your branch commits and changes
- Generates comprehensive PR title and description using AI
- Fills in PR template sections automatically
- Handles both new PR creation and existing PR updates
- Creates YAML file with structured PR details for editing
**Requirements:**
- Clean working directory (no uncommitted changes)
- GitHub CLI (`gh`) installed and authenticated (see [GitHub authentication](#github-authentication))
- Branch pushed to remote (will push automatically if needed)
- Claude API key configured
**Example Output:**
The command creates a `pr-details.yaml` file with structure like:
```yaml
title: "feat(auth): implement comprehensive OAuth2 authentication system"
description: |
# Pull Request
## Description
This PR implements a comprehensive OAuth2 authentication system that enables
users to sign in using Google and GitHub providers. The implementation includes
secure token management, session handling, and role-based access control.
## Type of Change
- [x] New feature (non-breaking change which adds functionality)
## Changes Made
- Added OAuth2 integration with Google and GitHub providers
- Implemented JWT token validation and refresh mechanisms
- Created user session management system
- Added role-based access control middleware
- Updated authentication documentation
## Testing
- [x] All existing tests pass
- [x] New tests added for authentication flows
- [x] Manual testing performed with both providers
## Additional Notes
This implementation follows OAuth2 best practices and includes comprehensive
error handling for edge cases.
```
## Claude Code Integration
omni-dev ships a family of subcommands that integrate with [Claude
Code](https://docs.anthropic.com/claude/docs/claude-code) — Anthropic's
agentic coding CLI. These cover four use cases:
- **Conversational AI** (`ai chat`) — one-shot Q&A against the configured model.
- **Conversation history export** (`ai claude history sync`) — mirror Claude
Code sessions to disk for analysis or coaching workflows.
- **Skill distribution** (`ai claude skills`) — share `.claude/skills/`
definitions across repositories and worktrees.
- **Command-template generation** (`commands generate`) — bootstrap canonical
slash-commands into a project's `.claude/commands/` directory.
All of these honour the same AI backend dispatch as the rest of omni-dev
(see [Configuration Guide — AI Backend Selection](configuration.md#ai-backend-selection)).
### `ai chat` — Conversational AI
A lightweight CLI front-end for chatting with the configured Claude model.
Use it when you want open-ended Q&A — `twiddle` is for commit-message
amendments, `check` is for commit-message validation, `view` is for
analysis, and `ai chat` is for everything else (a sanity check on a
config, a rubber-duck on an error message, a quick "summarise this
output").
Each prompt is **single-turn from the model's perspective** — the CLI
wraps an interactive loop around independent calls and does not pass prior
turns back. The interactive UX feels like multi-turn but the model sees
each prompt fresh. If you need true multi-turn reasoning with full
context, use Claude Code itself.
The CLI uses a hard-coded system prompt of "You are a helpful assistant.";
MCP callers can override it via the `system_prompt` parameter (see the
[`ai_chat`](mcp.md#ai--config-5-tools) tool entry).
```bash
# Start an interactive session against the configured backend
omni-dev ai chat
# Override the model for this session only
omni-dev ai chat --model claude-opus-4-7
```
The session reads stdin one line at a time and prints the response. Type
`Ctrl+D` (EOF) to exit.
### `ai claude history sync` — Export Conversation History
Export Claude Code conversation history to a target directory as one
`.jsonl` (and optionally `.md`) per chat, grouped by encoded project slug.
Re-running is idempotent: unchanged sessions are skipped, modified sessions
are rewritten via tempfile + rename, and source `mtime` is preserved on the
target so downstream tooling can sort chronologically.
```bash
# Basic export to ~/coaching/claude-history
omni-dev ai claude history sync --target ~/coaching/claude-history
# Both formats side-by-side (markdown is LLM-friendly with YAML frontmatter)
omni-dev ai claude history sync --target ~/history --output-format jsonl,markdown
# Restrict to one project (encoded slug or decoded cwd path)
omni-dev ai claude history sync --target ~/history --project -Users-jky-wrk
# Window: relative duration or RFC 3339
omni-dev ai claude history sync --target ~/history --since 7d
omni-dev ai claude history sync --target ~/history --since 2026-04-01T00:00:00Z
# Hide system-side events from markdown (jsonl is byte-identical regardless)
omni-dev ai claude history sync --target ~/history --output-format markdown --exclude-system
# Preview without touching the target
omni-dev ai claude history sync --target ~/history --dry-run
# Delete target files for sessions removed upstream (scoped to listed formats)
omni-dev ai claude history sync --target ~/history --prune
```
The export is a behavioural transcript — prompts, responses, thinking,
tool calls, tool-result metadata, and structured agent-to-user interactions
(`AskUserQuestion`, denials, interrupts). Sub-agent internal turns, large
tool-output sidecars, and auto-memory are deliberately excluded. See
`omni-dev ai claude history sync --help` for the complete flag reference.
### `ai claude skills` — Distribute Skills Across Repositories
A [Claude Code skill](https://docs.anthropic.com/claude/docs/claude-code-skills)
is a directory under `.claude/skills/<name>/` containing a `SKILL.md`
manifest plus any supporting files. Claude Code auto-discovers skills from
the working directory, so the canonical pattern is to keep them in a
project repository.
When the same skill set needs to be available across multiple repositories
or worktrees, copying becomes a maintenance burden. `ai claude skills`
solves this by **symlinking** each `<target>/.claude/skills/<name>` to
`<source>/.claude/skills/<name>` and adding a managed block to
`<target>/.git/info/exclude` so git ignores the symlinks. Updates in the
source are seen immediately by every target; `clean` removes both the
symlinks and the exclude-block entries; `status` reports residue.
```bash
# Sync skills from the current repo into itself and all its worktrees
omni-dev ai claude skills sync --worktrees
# Sync from a canonical source into a specific target
omni-dev ai claude skills sync --source ~/wrk/canonical --target ~/wrk/feature-branch
# Preview what would change
omni-dev ai claude skills sync --source ~/wrk/canonical --target ~/wrk/feature-branch --dry-run
# Inspect symlinks and exclude entries left behind by a prior sync
omni-dev ai claude skills status
omni-dev ai claude skills status --worktrees --format yaml
# Remove the symlinks and exclude-block entries
omni-dev ai claude skills clean --worktrees
omni-dev ai claude skills clean --dry-run
# Run against another repository without cd-ing into it (like `git -C`)
omni-dev ai claude skills sync -C ~/wrk/canonical --target ../feature-branch
omni-dev ai claude skills status -C ~/wrk/feature-branch
```
`-C/--repo <PATH>` replaces the current directory as the default source
(`sync`) or target (`status`, `clean`), and a relative `--source`/`--target`
resolves against it; an absolute path is used as given.
**End-to-end walkthrough**:
```bash
# 1. Add a new skill in your canonical repo
mkdir -p ~/wrk/canonical/.claude/skills/my-skill
$EDITOR ~/wrk/canonical/.claude/skills/my-skill/SKILL.md
# 2. Push it into every worktree of a downstream repo
cd ~/wrk/downstream
omni-dev ai claude skills sync --source ~/wrk/canonical --worktrees
# 3. Verify Claude Code picks it up (the symlink appears in the listing)
omni-dev ai claude skills status
# 4. Later, when you no longer want this skill set, clean up
omni-dev ai claude skills clean --worktrees
```
Same source and target is a no-op (the command short-circuits). The
target's `.git/info/exclude` is the only file modified outside
`.claude/skills/`, and the managed block is delimited so manual entries
are preserved across syncs and cleans.
See also: [`claude_skills_sync` / `claude_skills_status` / `claude_skills_clean`](mcp.md#ai--config-5-tools)
for the MCP equivalents, and [CI Path-Split](ci-path-split.md) for the in-repo
CI alternative — splitting required checks so skills can live in the project
repo yet still iterate in seconds, instead of being symlinked in from a source.
### `ai claude cli model resolve` — Model Resolution Diagnostics
Print how Claude Code resolves the active model in the current directory
(useful when project / user / env settings disagree).
```bash
omni-dev ai claude cli model resolve
```
### `commands generate` — Generate Claude Code Slash-Commands
Generates [Claude Code slash-command](https://docs.anthropic.com/claude/docs/claude-code-slash-commands)
templates into the project's `.claude/commands/` directory. Each template
is a self-contained workflow manifest (YAML frontmatter declaring allowed
tools, argument hints, and model selection, followed by step-by-step
instructions) that drives a multi-step omni-dev operation from inside a
Claude Code session.
Three templates ship with omni-dev:
| `commit-twiddle` | `.claude/commands/commit-twiddle.md` | Invoke `omni-dev twiddle`, review the suggested amendments, apply them. |
| `pr-create` | `.claude/commands/pr-create.md` | Run the `view` → `twiddle` → `branch create pr` pipeline end-to-end. |
| `pr-update` | `.claude/commands/pr-update.md` | Update an existing PR's body from the current commit set. |
| `all` | All three of the above | Bootstrap a fresh project. |
```bash
# Bootstrap all three templates
omni-dev commands generate all
# Or generate them individually
omni-dev commands generate commit-twiddle
omni-dev commands generate pr-create
omni-dev commands generate pr-update
```
Run from the repository root; the command will create `.claude/commands/`
if it does not exist and writes one file per subcommand (e.g. `✅ Generated
.claude/commands/commit-twiddle.md`). Commit the files to share the
workflows with your team — Claude Code picks them up automatically, so
collaborators can invoke `/commit-twiddle`, `/pr-create`, or `/pr-update`
inside a Claude Code session with no extra setup.
### `atlassian` - JIRA and Confluence Integration
Read, edit, and manage JIRA issues and Confluence pages from the command line.
Content is represented as JFM (JIRA-Flavored Markdown) with YAML frontmatter,
enabling round-trip editing between your editor and Atlassian Cloud.
See the [JFM Specification](specs/jfm.md) for full technical details on the
markdown format.
#### Authentication Setup
Configure your Atlassian Cloud credentials:
```bash
# Interactive credential setup (prompts for instance URL, email, API token)
omni-dev atlassian auth login
# Verify credentials work
omni-dev atlassian auth status
# Remove stored Atlassian credentials from settings.json
omni-dev atlassian auth logout
```
`auth logout` removes the `ATLASSIAN_INSTANCE_URL`, `ATLASSIAN_EMAIL`, and
`ATLASSIAN_API_TOKEN`, and `ATLASSIAN_PAT` keys (including token `_FILE` and
`_COMMAND` companions) from the active profile's `env` map (the base `env`
map when no profile is selected), leaving all other settings intact.
Credentials are stored in `~/.omni-dev/settings.json`. You can also use
environment variables:
```bash
export ATLASSIAN_INSTANCE_URL=https://myorg.atlassian.net
export ATLASSIAN_EMAIL=you@example.com
export ATLASSIAN_API_TOKEN=your-token
```
Environment variables take precedence over the settings file.
`ATLASSIAN_API_TOKEN` also accepts an `ATLASSIAN_API_TOKEN_FILE` companion
naming an absolute path to a file holding the token, instead of the value
itself — the Docker/Kubernetes secrets convention. The file must be a
regular file that is either yours and owner-only (`chmod 600`), or owned by root and not writable by others (the shape Kubernetes and Docker secrets take), and setting
both `ATLASSIAN_API_TOKEN` and `ATLASSIAN_API_TOKEN_FILE` in the same place
(both exported, or both in the same settings.json map) is an error. See [ADR-0089](adrs/adr-0089.md).
`ATLASSIAN_API_TOKEN_COMMAND` fetches the token on demand from a password manager or keychain instead; see [secret-commands.md](secret-commands.md).
#### Server/Data Center Personal Access Tokens
For Jira 8.14+ or Confluence 7.9+, configure an explicit Bearer PAT:
```bash
omni-dev atlassian auth login --auth-mode bearer
# Prompts for instance URL and PAT; no email required.
omni-dev atlassian auth status --service jira
# For a Confluence installation:
omni-dev atlassian auth status --service confluence
```
Alternatively, export credentials for one invocation/session:
```bash
export ATLASSIAN_INSTANCE_URL=https://jira.example.com/jira
export ATLASSIAN_PAT=your-personal-access-token
```
A profile keeps a self-hosted tenant separate from Cloud credentials:
```json
{
"profiles": {
"self-hosted": {
"env": {
"ATLASSIAN_INSTANCE_URL": "https://jira.example.com/jira",
"ATLASSIAN_PAT_FILE": "/run/secrets/atlassian-pat"
}
}
}
}
```
Run `omni-dev --profile self-hosted atlassian auth status` to select it.
Include any
self-hosted context path, for example `https://jira.example.com/jira` or
`https://docs.example.com/confluence`. Both tokens accept `_FILE` and `_COMMAND`
companions through the [secret resolver](secret-commands.md); configure only one
source for each secret in a layer.
`ATLASSIAN_API_TOKEN` always means Basic authentication and still requires
`ATLASSIAN_EMAIL`. If both API-token and PAT sources are configured, loading
fails with a conflict; it never switches modes after an authentication failure.
An email alone does not prevent PAT authentication. Empty or blank tokens fail.
Cloud OAuth 2.0 3LO is not supported by this PAT flow.
Login replaces the other mode's stored keys and companions in the selected
profile (or base `env`); a helper for the selected token refuses replacement
before any write. Unset exported token sources before login, since they override
settings. Profiles remain isolated from base credentials. Logout removes both
token modes and their companions in that map; exported credentials remain set.
PAT support covers shared authentication and service-specific current-user
verification. Most Jira/Confluence operations still use Cloud endpoints, account
IDs and payloads, so PAT setup does **not** imply full Server/Data Center command
compatibility. Jira verification uses `/rest/api/2/myself`; Confluence uses
`/rest/api/user/current` relative to your base/context path, without adding
`/wiki`. An anonymous Confluence response is rejected even when HTTP is 200.
The `atlassian_auth_status` MCP tool only inspects configuration presence. Its
`has_pat`, `auth_mode`, and `configuration_state` fields distinguish a PAT-only
configuration from missing, incomplete or conflicting configuration. It does
not read secret files, run helpers, or authenticate remotely. `has_token` retains
its existing meaning of API-token presence.
To keep multiple Atlassian tenants (e.g. `work` and `personal`) on one machine
and pick one per command, store each tenant's variables in a named **profile**
and select it with `--profile <name>` (or `OMNI_DEV_PROFILE`). See
[Credential Profiles](configuration-best-practices.md#credential-profiles).
To override just the **instance URL** for a single invocation — for example to
target a specific tenant without switching profiles — pass the
`--instance <URL>` flag (or set `OMNI_DEV_ATLASSIAN_INSTANCE`). It is scoped
to the `atlassian jira` and `atlassian confluence` subtrees (plus the leaf
`atlassian auth status`), so it applies to every JIRA and Confluence command
(email and API token still come from the environment/settings) and can be
placed either right after `atlassian jira`/`atlassian confluence` or after
the leaf subcommand — but not before `atlassian`, and not on `atlassian
convert`, `auth login`, or `auth logout`, which never read it:
```bash
omni-dev atlassian jira --instance https://other.atlassian.net read PROJ-1
omni-dev atlassian jira read PROJ-1 --instance https://other.atlassian.net
```
#### Destructive Commands
> **⚠️ Destructive commands require confirmation.**
>
> Atlassian's destructive subcommands (deletes and removes) prompt for
> confirmation by default and refuse to run unless either the user explicitly
> confirms (CLI, via `--force`/`--dry-run`) or the caller opts in (MCP, via
> `confirm: true`):
>
> - `omni-dev atlassian jira delete <KEY>`
> - `omni-dev atlassian jira comment delete <KEY> <COMMENT_ID>`
> - `omni-dev atlassian jira worklog delete <KEY> <WORKLOG_ID>`
> - `omni-dev atlassian jira version delete <VERSION_ID>`
> - `omni-dev atlassian jira link remove --link-id <ID>`
> - `omni-dev atlassian jira link remote delete <KEY> --link-id <ID>`
> - `omni-dev atlassian jira watcher remove --user <ACCOUNT_ID> <KEY>`
> - `omni-dev atlassian confluence delete <ID>`
> - `omni-dev atlassian confluence comment delete <COMMENT_ID> --kind <KIND>`
> - `omni-dev atlassian confluence label remove --labels <LABELS> <ID>`
> - `omni-dev atlassian confluence attachment delete <ATTACHMENT_ID>`
>
> **CLI behaviour.** Each command prompts on stdin:
>
> ```text
> Delete PROJ-123 (Fix login)? [y/N]
> ```
>
> Typing anything other than `y` (case-insensitive, whitespace-trimmed)
> prints `Cancelled.` and exits without calling the API. Two escape
> hatches:
>
> - `--force` skips the prompt — for scripts.
> - `--dry-run` prints `Would delete PROJ-123 (Fix login).` and exits without
> calling the API. `--dry-run` takes precedence over `--force`, so a
> scripted `--force` invocation can be sanity-checked by adding
> `--dry-run` without removing the force flag.
>
> **MCP behaviour.** The matching MCP tools (`jira_delete`,
> `jira_link_remove`, `jira_watcher_remove`, `confluence_delete`,
> `confluence_label_remove`) require an explicit `confirm: true` parameter
> and refuse to run otherwise:
>
> ```text
> Refusing to delete PROJ-123: pass `confirm: true` to authorise this irreversible operation.
> ```
>
> The exception is `confluence_attachment_delete`, which mirrors the CLI's
> `--force` mode and deletes without a `confirm` parameter.
>
> Interactive prompts catch human accidents; `--force` keeps scripts
> working; `--dry-run` is a server-free preview; the MCP `confirm: true`
> requirement gives assistants an explicit opt-in. See
> [ADR-0027](adrs/adr-0027.md) for the full design rationale.
#### JIRA: Reading and Writing Issues
```bash
# Read an issue as JFM markdown
omni-dev atlassian jira read PROJ-123
omni-dev atlassian jira read PROJ-123 --out-file issue.md
omni-dev atlassian jira read PROJ-123 --format adf # raw ADF JSON
# Include specific custom fields
omni-dev atlassian jira read PROJ-123 --fields "Acceptance Criteria,customfield_10088"
# Include every populated custom field
omni-dev atlassian jira read PROJ-123 --all-fields
# Write changes back (prompts for confirmation)
omni-dev atlassian jira write PROJ-123 issue.md
omni-dev atlassian jira write PROJ-123 issue.md --force
omni-dev atlassian jira write PROJ-123 issue.md --dry-run
# Update fields without re-posting the description body
omni-dev atlassian jira write PROJ-123 --no-content --assignee 5b10a2844c20165700ede21g
omni-dev atlassian jira write PROJ-123 --no-content --reporter "" --set-field "Priority=High"
# Array fields take comma-separated values or a YAML list (same payload)
omni-dev atlassian jira write PROJ-123 --no-content --set-field "Labels=triage,backend"
omni-dev atlassian jira write PROJ-123 --no-content --set-field "Labels=[triage, backend]"
# Interactive edit: fetch -> $EDITOR -> push
omni-dev atlassian jira edit PROJ-123
```
`--assignee` / `--reporter` take an Atlassian `accountId` — pass the empty
string `""` to clear, or `"-1"` to trigger automatic assignment. Use
[`jira user search`](#jira-user-search) to resolve a display name or email
to an `accountId`. To set an issue's parent for hierarchy (Epic → Story or
Story → Sub-task), use [`jira link parent`](#jira-issue-links) — the canonical
hierarchy surface.
The edit command opens an interactive loop:
1. Fetches the issue and writes JFM to a temp file
2. Prompts: `[A]ccept, [S]how, [E]dit, or [Q]uit?`
3. On accept, converts back to ADF and pushes changes
JFM output example:
```markdown
---
type: jira
instance: https://myorg.atlassian.net
key: PROJ-123
summary: Implement user authentication
status: In Progress
issue_type: Story
assignee: Alice Smith
labels:
- backend
---
This story covers the implementation of OAuth2-based authentication...
```
#### JIRA: Search
Search issues using JQL or convenience flags:
```bash
# Raw JQL
omni-dev atlassian jira search --jql "project = PROJ AND status = Open"
# Convenience flags (combined with AND)
omni-dev atlassian jira search --project PROJ --status "In Progress"
omni-dev atlassian jira search --assignee alice --limit 100
# Fetch all results (auto-paginates)
omni-dev atlassian jira search --jql "project = PROJ" --limit 0
```
Output is a formatted table: `KEY | STATUS | ASSIGNEE | SUMMARY`.
#### JIRA: Create Issues
Create issues from JFM markdown or CLI flags:
```bash
# From a plain markdown body — no frontmatter needed (all metadata via flags)
omni-dev atlassian jira create body.md --project PROJ --type Story --summary "Fix login"
# From a JFM file (project, type, summary read from frontmatter)
omni-dev atlassian jira create issue.md
# Flags override matching frontmatter fields
omni-dev atlassian jira create issue.md --project PROJ --type Bug --summary "Fix login"
# From ADF JSON (all metadata via flags)
omni-dev atlassian jira create body.json --format adf --project PROJ --summary "Title"
# Set custom fields inline (repeatable)
omni-dev atlassian jira create issue.md --set-field "Story Points=5" \
--set-field "Sprint=customfield_10020"
# Values may contain spaces, parentheses, and '=' — only shell quoting is needed
omni-dev atlassian jira create issue.md \
--set-field "Work Type=Product Features (Planned)"
# Target a specific instance (overrides ATLASSIAN_INSTANCE_URL / settings.json)
omni-dev atlassian jira create issue.md --instance https://other.atlassian.net
# Preview without creating
omni-dev atlassian jira create issue.md --dry-run
```
Metadata is resolved with this precedence: **CLI flags first, then JFM
frontmatter, then a derived or default value** (issue type defaults to
`Task`). Flags always win.
Frontmatter is optional. A file with no `---` block is treated entirely as
the issue body, with every field taken from flags — the same way the MCP
`jira_create` tool works. The frontmatter `instance:` field, if present, is
ignored: it never routes requests. The target instance comes from `--instance`
(when given), otherwise from auth config (`ATLASSIAN_INSTANCE_URL` env /
`settings.json`), never from the document.
Prints the created issue key (e.g., `PROJ-124`) to stdout.
`--set-field "NAME=VALUE"` semantics:
- **Splitting.** The argument is split on the **first** `=` only. `NAME` is
everything before it (surrounding whitespace trimmed); `VALUE` is everything
after, kept verbatim — spaces, parentheses, and further `=` are all literal,
so the only quoting needed is your shell's.
- **Value typing.** `VALUE` is parsed as a YAML scalar (number, bool) when
possible, otherwise a string. The shape actually sent then follows the
field's schema: option/select fields send `{"value": "…"}`, number fields
send a JSON number, array fields take a list, and rich-text
(ADF) fields take JFM markdown. So `--set-field "Story Points=5"` sends the
number `5` for a numeric field, whereas an option field receives the string
`"5"` wrapped as `{"value": "5"}`.
- **Array fields.** Fields whose schema is array-typed (labels, components,
fix versions, multi-selects) accept either a YAML flow list
(`Labels=[a, b, c]`) or a comma-separated string (`Labels=a,b,c`) — both
send the same payload. Comma-split elements are whitespace-trimmed and
empty elements are dropped, so `Labels=a , b ,` sends `["a", "b"]` and
`Labels=""` clears the field. A literal comma inside one element needs the
list form: `Labels=["a,b"]`. The value **replaces** the field's current
contents; appending requires read → merge → write.
- **Field resolution.** `NAME` resolves against the create screen for the
target project + issue type. A `customfield_<digits>` id is matched first;
otherwise the **display name** is matched exactly. The field **must be on the
create screen** — an unknown name fails up front with the list of accepted
fields (it is never silently dropped), and an ambiguous display name lists
the matching ids.
- **Option validation.** For option fields whose allowed values are known, a
value outside that set is rejected before the request with a clear,
field-named error. Fields that do not enumerate their values are sent as-is
and validated by JIRA (its error is surfaced verbatim).
- **Precedence.** A `--set-field` entry overrides the frontmatter
`custom_fields:` entry of the same name.
Run [`jira project create-meta`](#jira-projects) to see which fields a
project + issue type accepts, their types, and allowed values.
#### JIRA: Transitions
List and execute workflow transitions:
```bash
# List available transitions
omni-dev atlassian jira transition list PROJ-123
# Execute a transition by name (case-insensitive)
omni-dev atlassian jira transition execute PROJ-123 "In Progress"
# Execute by ID
omni-dev atlassian jira transition execute PROJ-123 21
```
Transitions whose screen requires input can supply it in the same request:
```bash
# Resolve an issue, setting the resolution and an atomic comment
omni-dev atlassian jira transition execute PROJ-123 Resolve \
--resolution Fixed --comment "Resolved via CI"
# Set an arbitrary transition-screen field (same NAME=VALUE syntax as
# `jira write --set-field`); repeat for multiple fields
omni-dev atlassian jira transition execute PROJ-123 Resolve \
--set-field "Severity=High"
```
`--resolution NAME` sets the resolution (sent as `{"name": …}`); `--set-field
"NAME=VALUE"` resolves against the transition's screen fields; and `--comment`
(JFM markdown) rides in the transition itself when the screen accepts a comment
— satisfying a mandatory-comment screen — otherwise it is posted as a separate
comment after the transition.
#### JIRA: Comments
```bash
# List comments on an issue
omni-dev atlassian jira comment list PROJ-123
# Add a comment from a file
omni-dev atlassian jira comment add PROJ-123 comment.md
# Add from stdin
# Add ADF JSON comment
omni-dev atlassian jira comment add PROJ-123 body.json --format adf
# Edit an existing comment (needs the comment id from `comment list`)
omni-dev atlassian jira comment edit PROJ-123 10010 revised.md
# Delete a comment (prompts for confirmation; --force skips it, --dry-run previews)
omni-dev atlassian jira comment delete PROJ-123 10010
```
#### JIRA: Delete Issues
```bash
# Delete with confirmation prompt
omni-dev atlassian jira delete PROJ-123
# Skip confirmation
omni-dev atlassian jira delete PROJ-123 --force
```
#### JIRA: Projects
```bash
# List all accessible projects
omni-dev atlassian jira project list
omni-dev atlassian jira project list --limit 100
```
**Create-screen introspection** — before creating an issue, ask which fields
the project + issue type actually requires and what values they accept. This is
the pre-flight alternative to attempting a create, parsing the HTTP 400
(`customfield_NNNNN is required`), and chasing down `field list` / `field
options` to recover:
```bash
# What does it take to create a Task in PROJ?
omni-dev atlassian jira project create-meta --project PROJ --issue-type Task
# Machine-readable output for scripting / agents
omni-dev atlassian jira project create-meta --project PROJ --issue-type Bug -o yaml
```
For each field on the create screen this reports `field_id`, `name`,
`required`, `schema_type`, any `custom` plugin URI, the `allowed_values`
(resolving option / select / cascading-select, including nested `children`),
and a `default_value` when the screen defines one. The table view sorts
required fields first. Output formats match the other list commands: `table`
(default), `json`, `yaml`, `yamls`, and `jsonl` (one line per field).
#### JIRA: Fields
JIRA installations are heavily customised — most real projects add custom
fields (story points, epic links, severity, customer impact, etc.) and
each gets an opaque ID like `customfield_10001`. The names you see in the
JIRA web UI are display labels; the API only accepts the IDs. The `field`
subcommand is how you discover them.
A field can have different option lists per **context** (per-project,
per-issue-type, or per-screen scoping). Most fields have exactly one
context, in which case `field options` will auto-discover it. Fields with
multiple contexts require `--context-id` to pick the right option set.
```bash
# List all field definitions (display name → customfield_NNNNN mapping)
omni-dev atlassian jira field list
# Search by name (case-insensitive substring match)
omni-dev atlassian jira field list --search "story"
omni-dev atlassian jira field list --search "severity"
# Show options for a custom field (auto-discovers context)
omni-dev atlassian jira field options --field-id customfield_10001
# Specify context explicitly when the field has multiple contexts
omni-dev atlassian jira field options --field-id customfield_10001 --context-id 12345
# Machine-readable output for scripting
omni-dev atlassian jira field list --search "epic" -o yaml
omni-dev atlassian jira field options --field-id customfield_10001 -o json
```
Output formats: `table` (default, human-readable), `json`, `yaml`,
`yamls` (YAML stream), and `jsonl` (JSON Lines).
**End-to-end walkthrough** — set a custom field on a new issue:
```bash
# 1. Find the field by name
omni-dev atlassian jira field list --search "story points"
# → customfield_10016: "Story Points"
# 2. (Number fields have no options — skip step 3.)
# For an enum-style field, list its allowed values:
omni-dev atlassian jira field list --search "severity"
# → customfield_10042: "Severity"
omni-dev atlassian jira field options --field-id customfield_10042
# → "Low", "Medium", "High", "Critical"
# 3. Pass the field ID and value when creating or writing the issue
# (see `jira create` / `jira write` for the syntax).
```
See also: [`jira_field_list` / `jira_field_options` / `jira_project_create_meta`](mcp.md#jira--extensions-25-tools)
for the MCP equivalents (`search`, `field_id`, and `project` / `issue_type`
parameters mirror the CLI flags).
#### JIRA: Agile Boards
```bash
# List boards
omni-dev atlassian jira board list
omni-dev atlassian jira board list --project PROJ --type scrum
# List issues on a board
omni-dev atlassian jira board issues --board-id 1
omni-dev atlassian jira board issues --board-id 1 --jql "status = Open"
```
#### JIRA: Sprints
```bash
# List sprints for a board
omni-dev atlassian jira sprint list --board-id 1
omni-dev atlassian jira sprint list --board-id 1 --state active
# List issues in a sprint
omni-dev atlassian jira sprint issues --sprint-id 10
omni-dev atlassian jira sprint issues --sprint-id 10 --jql "status = Open"
# Add issues to a sprint
omni-dev atlassian jira sprint add --sprint-id 10 --issues PROJ-1,PROJ-2,PROJ-3
# Create a new sprint (start/end dates and goal optional)
omni-dev atlassian jira sprint create --board-id 1 --name "Sprint 42" \
--start-date 2026-05-01 --end-date 2026-05-14 --goal "Ship checkout v2"
# Update an existing sprint (only supplied fields change)
omni-dev atlassian jira sprint update --sprint-id 10 --state active
omni-dev atlassian jira sprint update --sprint-id 10 --name "Sprint 42 (extended)" \
--end-date 2026-05-21
# Delete a sprint (prompts to confirm; --force skips it, --dry-run previews)
omni-dev atlassian jira sprint delete --sprint-id 10
```
#### JIRA: Watchers
```bash
# List watchers
omni-dev atlassian jira watcher list PROJ-123
# Add or remove a watcher (account ID — use `jira user search` to resolve)
omni-dev atlassian jira watcher add PROJ-123 --user 5b10a2844c20165700ede21g
omni-dev atlassian jira watcher remove PROJ-123 --user 5b10a2844c20165700ede21g
```
#### JIRA: Worklogs
```bash
# List worklog entries
omni-dev atlassian jira worklog list PROJ-123
omni-dev atlassian jira worklog list PROJ-123 --limit 100
# Log time (`--time-spent` accepts JIRA duration format: "2h 30m", "1d", "45m")
omni-dev atlassian jira worklog add PROJ-123 --time-spent "2h 30m" \
--comment "Investigated cache invalidation"
omni-dev atlassian jira worklog add PROJ-123 --time-spent 1d \
--started "2026-04-16T09:00:00.000+0000"
# Correct an entry (needs the worklog id from `worklog list`; pass any subset of fields)
omni-dev atlassian jira worklog edit PROJ-123 10010 --time-spent "3h" \
--comment "Revised estimate"
# Remove an entry (prompts to confirm; --force skips it, --dry-run previews)
omni-dev atlassian jira worklog delete PROJ-123 10010
```
#### JIRA: Versions
Manage a project's release versions (the values behind `fixVersion` /
`affectedVersion`).
```bash
# List versions (filter with --released/--unreleased, --archived/--unarchived)
omni-dev atlassian jira version list --project PROJ
omni-dev atlassian jira version list --project PROJ --unreleased
# Create a version
omni-dev atlassian jira version create --project PROJ --name "1.0.0" \
--release-date 2026-06-01
# Release / archive / rename an existing version (id from `version list`)
omni-dev atlassian jira version release 10000 --release-date 2026-06-01
omni-dev atlassian jira version archive 10000
omni-dev atlassian jira version rename 10000 "1.0.1" --description "Patch release"
# Delete a version (prompts to confirm; --force skips it, --dry-run previews).
# Optionally reassign issues' fix/affected versions first.
omni-dev atlassian jira version delete 10000
omni-dev atlassian jira version delete 10000 --move-fix-issues-to 10001
```
#### JIRA: Labels
Add or remove individual labels **incrementally** — leaving the issue's other
labels untouched, unlike `jira write --set labels=…` (a full-array replace).
JIRA labels cannot contain spaces.
```bash
omni-dev atlassian jira label add PROJ-123 --labels backend,urgent
omni-dev atlassian jira label remove PROJ-123 --labels stale
```
#### JIRA: Components
Manage a project's components (project-level CRUD, distinct from setting an
issue's `components` field via `jira create`/`write`).
```bash
# List a project's components
omni-dev atlassian jira component list --project PROJ
# Create / rename / delete a component
omni-dev atlassian jira component create --project PROJ --name Backend \
--description "Server side"
omni-dev atlassian jira component update 10000 --name "Backend Services"
# Delete (prompts to confirm; --force skips it, --dry-run previews).
# Optionally reassign referencing issues first with --move-issues-to.
omni-dev atlassian jira component delete 10000
```
#### JIRA: User Search
Resolve a display name or email substring to an Atlassian `accountId` —
required input for `jira write --assignee/--reporter` and `jira watcher
add/remove`.
```bash
omni-dev atlassian jira user search --query "Alice"
omni-dev atlassian jira user search --query "@example.com" --limit 100
```
#### JIRA: Development Info
Show linked PRs, branches, and repositories for an issue (requires JIRA's
GitHub/Bitbucket integration).
```bash
omni-dev atlassian jira dev PROJ-123
omni-dev atlassian jira dev PROJ-123 --type pullrequest
omni-dev atlassian jira dev PROJ-123 --app GitHub --summary
```
#### JIRA: Issue Links
```bash
# List links on an issue (shows link IDs)
omni-dev atlassian jira link list PROJ-123
# List available link types
omni-dev atlassian jira link types
# Create a link
omni-dev atlassian jira link create --type Blocks --inward PROJ-1 --outward PROJ-2
# Remove a link by ID (get IDs from `link list`)
omni-dev atlassian jira link remove --link-id 12345
# Set an issue's parent — Epic → Story or Story → Sub-task
# (`--epic`/`--issue` are accepted as aliases for `--parent`/`--child`)
omni-dev atlassian jira link parent --parent EPIC-1 --child PROJ-2
```
Remote (external-URL) links point out to non-JIRA resources (Confluence pages,
Bitbucket PRs, external trackers):
```bash
# List remote links on an issue (shows remote link IDs)
omni-dev atlassian jira link remote list PROJ-123
# Add a remote link
omni-dev atlassian jira link remote create PROJ-123 \
--url https://example.com/design --title "Design doc" --relationship "relates to"
# Delete a remote link by ID (prompts to confirm; --force skips it, --dry-run previews)
omni-dev atlassian jira link remote delete PROJ-123 --link-id 10010
```
#### JIRA: Changelog
View change history for one or more issues:
```bash
omni-dev atlassian jira changelog --keys PROJ-1
omni-dev atlassian jira changelog --keys PROJ-1,PROJ-2 --limit 100
```
#### JIRA: Attachments
```bash
# Upload one or more files to an issue (single multipart request)
omni-dev atlassian jira attachment upload PROJ-123 ./build.log
omni-dev atlassian jira attachment upload PROJ-123 ./build.log ./screenshot.png
# Download all attachments
omni-dev atlassian jira attachment download --key PROJ-123
omni-dev atlassian jira attachment download --key PROJ-123 --output-dir ./files
# Filter by filename
omni-dev atlassian jira attachment download --key PROJ-123 --filter screenshot
# Download only images (png, jpeg, gif, svg, webp)
omni-dev atlassian jira attachment images --key PROJ-123
omni-dev atlassian jira attachment images --key PROJ-123 --output-dir ./images
# Delete an attachment by ID (permanent — prompts for confirmation)
omni-dev atlassian jira attachment delete 10042
omni-dev atlassian jira attachment delete 10042 --dry-run # preview, no API call
omni-dev atlassian jira attachment delete 10042 --force # skip the prompt
```
#### Confluence: Reading and Writing Pages
```bash
# Read a page as JFM markdown
omni-dev atlassian confluence read 12345
omni-dev atlassian confluence read 12345 --out-file page.md
omni-dev atlassian confluence read 12345 --format adf
# Write changes back
omni-dev atlassian confluence write 12345 page.md
omni-dev atlassian confluence write 12345 page.md --force
omni-dev atlassian confluence write 12345 page.md --dry-run
# Interactive edit
omni-dev atlassian confluence edit 12345
```
Confluence JFM output example:
```markdown
---
type: confluence
instance: https://myorg.atlassian.net
page_id: "12345"
title: Architecture Overview
space_key: ENG
status: current
version: 7
---
# Architecture Overview
Page body content here...
```
#### Confluence: Comparing Pages
Compares two versions of a Confluence page using a **structurally-aware
diff**: the engine walks the ADF (Atlassian Document Format) tree and
splits each version into heading-delimited sections (paths like
`/h2#background`, `/h3#implementation`). The output describes *which
sections* changed and *how* — not raw text deltas — which makes it
ergonomic for AI agents and human reviewers alike.
Two commands:
- `omni-dev atlassian confluence compare run <PAGE_ID>` — diff two versions
of a page; emits a YAML envelope with per-section change summaries and
drill-in cursors.
- `omni-dev atlassian confluence compare section --cursor <CURSOR>` —
drill into a single section using a cursor returned by `run`.
**Detail levels** (`--detail`):
- `summary` — aggregate counts only (sections added, modified, removed;
characters changed). Smallest output.
- `outline` (default) — per-section change kind, one-line summaries, and
drill-in cursors for `compare section`. Ideal balance for surveying a
page.
- `full` — embeds full per-section deltas. Budget-truncated if the output
exceeds `--budget` (default ~16 KiB ≈ 4000 tokens).
**Version selectors** for `--from` and `--to`:
- `latest` — the most recent version (default `--to`).
- `previous` — the version before `--to` (default `--from`).
- `v-N` — `N` versions back from `--to` (e.g. `v-3`).
- A bare integer — that exact version number.
- An ISO 8601 timestamp — the version that was current at that time.
**Filtering and trimming**:
- `--filter-section /h2#name` — restrict to sections matching the given
path. Repeatable.
- `--min-change-chars <N>` — drop sections with fewer than `N` characters
of changed text. Useful for ignoring formatting-only edits.
- `--ignore-whitespace` — collapse runs of whitespace inside text nodes
before diffing.
- `--include body,title,labels,metadata` — choose which top-level fields
to diff. Default: `body,title,metadata` (labels and other metadata
excluded by default).
- `--budget <BYTES>` — output budget. Defaults to `16384` (~16 KiB).
```bash
omni-dev atlassian confluence compare run 12345
omni-dev atlassian confluence compare run 12345 --from v-5 --to latest
omni-dev atlassian confluence compare run 12345 \
--from 2026-01-01T00:00:00Z --to 2026-05-11T00:00:00Z
omni-dev atlassian confluence compare run 12345 --detail summary
omni-dev atlassian confluence compare run 12345 --detail full --budget 65536
omni-dev atlassian confluence compare run 12345 \
--filter-section /h2#background --filter-section /h2#design \
--ignore-whitespace
omni-dev atlassian confluence compare section --cursor <CURSOR> --format unified
omni-dev atlassian confluence compare section --cursor <CURSOR> --format side-by-side
omni-dev atlassian confluence compare section --cursor <CURSOR> --format markdown-inline
```
**End-to-end walkthrough**:
```bash
# 1. Survey the changes between previous and latest
omni-dev atlassian confluence compare run 12345
# → outline with per-section summaries and cursors
# 2. Drill into a section flagged as modified
omni-dev atlassian confluence compare section --cursor <CURSOR_FROM_STEP_1>
# → unified diff for that section only
```
See also: [`confluence_compare` / `confluence_compare_section`](mcp.md#confluence-13-tools)
for the MCP equivalents.
#### Confluence: Search
Search pages using CQL or convenience flags:
```bash
# Raw CQL
omni-dev atlassian confluence search --cql "space = ENG AND title ~ 'auth'"
# Convenience flags
omni-dev atlassian confluence search --space ENG
omni-dev atlassian confluence search --title architecture
omni-dev atlassian confluence search --space ENG --title auth --limit 100
```
#### Confluence: Create Pages
```bash
# From JFM file
omni-dev atlassian confluence create page.md
# From CLI flags
omni-dev atlassian confluence create page.md --space ENG --title "New Page"
# With parent page
omni-dev atlassian confluence create page.md --space ENG --title "Child" --parent 12345
# Preview
omni-dev atlassian confluence create page.md --dry-run
```
#### Confluence: Copy Pages
Copy a single page under a destination parent, carrying its attachments,
labels, and properties (but not its restrictions):
```bash
omni-dev atlassian confluence copy 12345 --parent 67890 --title "Copy of Page"
```
#### Confluence: Delete Pages
```bash
# Delete (moves to trash, prompts for confirmation)
omni-dev atlassian confluence delete 12345
# Skip confirmation
omni-dev atlassian confluence delete 12345 --force
# Permanently purge (requires space admin)
omni-dev atlassian confluence delete 12345 --force --purge
```
#### Confluence: Children
List direct children of a page or top-level pages in a space.
```bash
# Direct children of a page
omni-dev atlassian confluence children 12345
# Top-level pages in a space (no parent ID)
omni-dev atlassian confluence children --space ENG
# Recursive tree (--max-depth 0 = unlimited)
omni-dev atlassian confluence children 12345 --recursive
omni-dev atlassian confluence children --space ENG --recursive --max-depth 3
```
#### Confluence: Comments
```bash
# List comments
omni-dev atlassian confluence comment list 12345
omni-dev atlassian confluence comment list 12345 --limit 100
# Add a comment from a file or stdin
omni-dev atlassian confluence comment add 12345 comment.md
# Add an ADF JSON comment
omni-dev atlassian confluence comment add 12345 body.json --format adf
# Edit a comment's body (needs the comment id from `comment list` and its --kind)
omni-dev atlassian confluence comment edit 555 --kind footer revised.md
# Delete a comment (prompts to confirm; --force skips it, --dry-run previews)
omni-dev atlassian confluence comment delete 555 --kind inline
# Resolve / reopen an inline comment
omni-dev atlassian confluence comment resolve 555
omni-dev atlassian confluence comment reopen 555
```
#### Confluence: Labels
```bash
# List labels on a page
omni-dev atlassian confluence label list 12345
# Add or remove labels (comma-separated)
omni-dev atlassian confluence label add 12345 --labels architecture,reviewed
omni-dev atlassian confluence label remove 12345 --labels deprecated
```
#### Confluence: Watchers
Manage per-user watch state on a page. Confluence's public API exposes
check/add/remove for a user (defaulting to the authenticated user) but not a
list of every watcher, so this offers `status` rather than `list`.
```bash
# Check whether you (or --account-id <ID>) watch a page
omni-dev atlassian confluence watcher status 12345
# Start / stop watching a page
omni-dev atlassian confluence watcher add 12345
omni-dev atlassian confluence watcher remove 12345 --account-id 5b10ac8d82e05b22cc7d4ef5
```
#### Confluence: Restrictions
View and manage read/update restrictions on a page (who may view or edit it).
```bash
# Show the current restrictions
omni-dev atlassian confluence restriction get 12345
# Grant / revoke a user or group for an operation (read | update)
omni-dev atlassian confluence restriction grant 12345 --operation update \
--account-id 5b10ac8d82e05b22cc7d4ef5
omni-dev atlassian confluence restriction revoke 12345 --operation read --group developers
```
#### Confluence: User Search
```bash
omni-dev atlassian confluence user search --query "Alice"
omni-dev atlassian confluence user search --query "@example.com" --limit 50
```
#### Confluence: Bulk Download
Recursively download a page tree (or an entire space) to disk. Each page is
written to a `<title>.md` (or `.adf.json`) file mirroring the page tree.
```bash
# Download a subtree starting at a single page
omni-dev atlassian confluence download 12345 --output-dir ./pages
# Download every top-level page in a space
omni-dev atlassian confluence download --space ENG --output-dir ./eng-docs
# Download as raw ADF JSON instead of JFM
omni-dev atlassian confluence download 12345 --format adf
# Filter by title (case-insensitive substring; non-matching parents are
# still traversed so deeply-nested matches still surface)
omni-dev atlassian confluence download --space ENG --title-filter "auth"
# Resume after an interrupted run (uses a manifest to skip done pages)
omni-dev atlassian confluence download --space ENG --resume
# Tune concurrency and depth
omni-dev atlassian confluence download --space ENG --concurrency 16 --max-depth 5
# Conflict resolution when a file already exists
omni-dev atlassian confluence download --space ENG --on-conflict overwrite
omni-dev atlassian confluence download --space ENG --on-conflict skip
# Also fetch each page's attachment binaries into an `attachments/`
# subdirectory beside its content file (full-page snapshots)
omni-dev atlassian confluence download --space ENG --include-attachments
```
#### Confluence: Attachments
Manage attachment binaries on a page. Use `list` to discover attachment IDs,
then `download` to pull a single binary off the page without dropping out to
`curl` (credentials stay inside the wrapper).
```bash
# Upload a file as a new attachment
omni-dev atlassian confluence attachment upload 12345 ./diagram.png
# Upload a new binary VERSION of an existing attachment (bumps its version
# rather than adding a second attachment)
omni-dev atlassian confluence attachment update 12345 att-98765 ./diagram-v2.png \
--comment "Updated diagram"
# List a page's attachments (the ID column feeds `download`/`update`/`delete`)
omni-dev atlassian confluence attachment list 12345
# Download one attachment by ID — defaults to its filename in the cwd
omni-dev atlassian confluence attachment download att-98765
# Write it to an explicit path (an existing directory is joined with the
# attachment's filename)
omni-dev atlassian confluence attachment download att-98765 --out-file ./diagram.png
omni-dev atlassian confluence attachment download att-98765 --out-file ./downloads/
```
To capture a whole page tree *with* its attachment binaries in one command,
use `confluence download --include-attachments` (above).
`--on-conflict` accepts `backup` (default — writes `.bak` and overwrites),
`skip`, or `overwrite`.
#### Offline Format Conversion
Convert between JFM markdown and ADF JSON locally without credentials:
```bash
# Markdown to ADF JSON
omni-dev atlassian convert to-adf issue.md
omni-dev atlassian convert to-adf issue.md --compact
# ADF JSON to markdown
omni-dev atlassian convert from-adf issue.json
omni-dev atlassian convert from-adf issue.json --strip-local-ids # cleaner output
# Pipe for inspection
`--strip-local-ids` drops the `localId` attributes ADF emits on tables,
panels, etc. — useful when the rendered markdown is going to a human
reviewer rather than back into Atlassian.
#### Auto-Pagination
All commands that query paginated endpoints auto-paginate transparently.
Use `--limit` to control how many results are fetched:
```bash
# Default: up to 50 results
omni-dev atlassian jira search --project PROJ
# Fetch more
omni-dev atlassian jira search --project PROJ --limit 200
# Fetch all (no limit)
omni-dev atlassian jira search --project PROJ --limit 0
```
#### JFM Markdown Syntax
JFM supports standard GitHub-Flavored Markdown plus directives for
JIRA-specific elements:
**Standard markdown**: headings, bold, italic, code, strikethrough, links,
images, lists, task lists, tables, code blocks, blockquotes, horizontal rules.
**Inline directives** for JIRA constructs without markdown equivalents:
```markdown
Status: :status[In Progress]{color=blue}
Assigned to: :mention[Alice]{id=abc123}
Due: :date[2026-04-15]
Emoji: :smile:
```
**Container directives** for panels and other blocks:
```markdown
:::panel{type=info}
This is an info panel with **rich** content inside.
:::
:::expand{title="Click to expand"}
Hidden content here.
:::
```
**Leaf block directives** for smart links and cards:
```markdown
::card[https://example.com/page]
```
## Datadog Integration
omni-dev exposes read-only access to the Datadog v1/v2 APIs through the
`omni-dev datadog` command tree. The full reference — authentication,
every family's CLI subcommands with worked examples and sample output,
rate-limit behaviour, and troubleshooting — lives in
**[docs/datadog.md](datadog.md)**.
Quick orientation:
```bash
# One-time credential setup (writes ~/.omni-dev/settings.json)
omni-dev datadog auth login
omni-dev datadog auth status
# Examples from the nine capability families
omni-dev datadog metrics query --query 'avg:system.cpu.user{*}' --from 15m
omni-dev datadog monitor list --tags env:prod
omni-dev datadog dashboard list
omni-dev datadog logs search --filter 'service:api status:error' --from 1h
omni-dev datadog events list --filter 'service:api' --sources kubernetes
omni-dev datadog slo list --tags team:platform
omni-dev datadog downtime list --active-only
omni-dev datadog hosts list --filter env:prod
```
Every Datadog CLI subcommand has a matching `datadog_*` MCP tool — see
[docs/mcp.md](mcp.md#datadog-14-tools).
## Gmail Integration
omni-dev exposes read access (and, opt-in, label mutation) to the Gmail v1
API through the `omni-dev gmail` command tree, plus a durable local
archive via `gmail sync`. New to this integration? Follow the
**[Gmail Quickstart](gmail-quickstart.md)** for a zero-to-synced-archive
walkthrough. The full reference — prerequisites (bring-your-own Google
Cloud OAuth2 client), authentication, every subcommand with worked
examples, rate-limit behaviour, and troubleshooting — lives in
**[docs/gmail.md](gmail.md)**.
Quick orientation:
```bash
# One-time OAuth2 client setup (create your own Google Cloud project first —
# see docs/gmail.md#prerequisites), then log in (opens a browser)
export GMAIL_CLIENT_ID=...
export GMAIL_CLIENT_SECRET=...
omni-dev gmail auth login
omni-dev gmail auth status
# Search, read, and browse
omni-dev gmail search --query 'label:finance after:2026/01/01' --limit 50
omni-dev gmail read <message-id>
omni-dev gmail thread <thread-id>
omni-dev gmail label list
# Label mutation needs the gmail.modify scope (auth login --modify), as do
# draft create/update and insert
omni-dev gmail label add <message-id> --label IMPORTANT
omni-dev gmail label remove <message-id> --label UNREAD
# Maintain a durable local archive (.eml files + a JSONL manifest)
omni-dev gmail sync --output-dir ~/mail-archive --query 'label:finance'
```
Every read-only Gmail CLI subcommand except `sync` has a matching
`gmail_*` MCP tool — see [docs/mcp.md](mcp.md#gmail-8-tools). Label
mutation and `sync` (a long-running bulk filesystem operation, a poor fit
for a synchronous MCP call) are CLI-only in this release.
A second mailbox doesn't need a second `--profile` — an `--account NAME`
flag (scoped to `gmail` commands) selects a named Gmail account
independently of everything else in your environment; see
[gmail.md#multiple-accounts](gmail.md#multiple-accounts).
## Contextual Intelligence
### Overview
Contextual intelligence makes omni-dev understand your project to provide better suggestions:
- **Project Context**: Conventions from `.omni-dev/` configuration
- **Branch Context**: Work type from branch naming patterns
- **File Context**: Architectural understanding of changed files
- **Pattern Context**: Recognition of work patterns across commits
### Setting Up Context
#### 1. Create Context Directory
```bash
mkdir .omni-dev
```
#### 2. Define Project Scopes (`.omni-dev/scopes.yaml`)
Tell omni-dev about your project's areas. See
[`omni-dev-directory.md`](omni-dev-directory.md#scopesyaml) for the file's
format contract and validation behaviour.
```yaml
scopes:
- name: "auth"
description: "Authentication and authorization systems"
examples:
- "auth: add OAuth2 support"
- "auth: fix token validation"
file_patterns:
- "src/auth/**"
- "auth.rs"
- "middleware/auth.rs"
- name: "api"
description: "REST API endpoints and handlers"
examples:
- "api: add user endpoints"
- "api: improve error handling"
file_patterns:
- "src/api/**"
- "handlers/**"
- "routes/**"
- name: "ui"
description: "User interface components"
examples:
- "ui: add responsive navigation"
- "ui: fix mobile layout"
file_patterns:
- "src/components/**"
- "*.vue"
- "*.tsx"
- name: "docs"
description: "Documentation and guides"
examples:
- "docs: add API reference"
- "docs: update installation guide"
file_patterns:
- "docs/**"
- "*.md"
- "README*"
```
#### 3. Set Commit Guidelines (`.omni-dev/commit-guidelines.md`)
Define your project's commit message standards. See
[`omni-dev-directory.md`](omni-dev-directory.md#commit-guidelinesmd) for the
full format contract — including precedence between project-scope, user-scope,
and global fallbacks — and the validation messages omni-dev emits on a
malformed file.
```markdown
# Project Commit Guidelines
## Format
Use conventional commits: `type(scope): description`
## Types We Use
- `feat` - New features
- `fix` - Bug fixes
- `docs` - Documentation changes
- `refactor` - Code restructuring
- `test` - Adding tests
- `chore` - Build/tooling changes
## Style Rules
- Keep subject line under 50 characters
- Use imperative mood: "Add feature" not "Added feature"
- Capitalize first letter of description
- No period at end of subject line
## Our Scopes
- `auth` - Authentication systems
- `api` - Backend API changes
- `ui` - Frontend interface
- `db` - Database changes
- `deploy` - Deployment/infrastructure
## Examples
```
feat(auth): add OAuth2 Google integration
fix(api): resolve rate limiting edge case
docs(readme): update installation instructions
refactor(ui): extract common button component
```
### Branch Context Detection
omni-dev automatically detects work type from branch names:
| `feature/auth-system` | feature | Feature development |
| `fix/login-bug` | fix | Bug fix |
| `docs/api-guide` | docs | Documentation |
| `refactor/user-service` | refactor | Code restructuring |
| `JIRA-123-user-auth` | feature | Ticket-based |
| `username/feature-name` | feature | User branches |
### Intelligent Verbosity
omni-dev adjusts message detail based on change significance:
- **Comprehensive**: Major features, architectural changes
- Multi-paragraph descriptions
- Bulleted feature lists
- Impact statements
- **Detailed**: Moderate changes, multi-file updates
- Subject + explanatory body
- Key change highlights
- **Concise**: Minor changes, single-file updates
- Clear conventional format
- Essential information only
## Workflows
### Feature Branch Cleanup
Clean up commits before merging:
```bash
# 1. Work on feature branch with quick commits
git checkout -b feature/user-dashboard
git commit -m "wip"
git commit -m "fix stuff"
git commit -m "add more"
# 2. Before merging, improve all commit messages
omni-dev git commit message twiddle 'main..HEAD' --use-context
# 3. Review suggestions and apply
# ✅ Professional commit history ready for review
```
### Refine, Inspect, or Validate
By default twiddle generates fresh messages from the diffs alone (`--fresh`).
When the existing messages are already close to right, refine them instead —
and you can inspect the analysis or validate the result:
```bash
# Keep the existing messages as the AI's starting point
omni-dev git commit message twiddle 'main..HEAD' --refine
# Skip the AI entirely: amend to a deterministic type(scope) suggestion
omni-dev git commit message twiddle 'main..HEAD' --no-ai
# Validate the amended messages right after applying them
omni-dev git commit message twiddle 'main..HEAD' --auto-apply --check
```
### Complete Feature Development Workflow
End-to-end workflow from feature development to PR creation:
```bash
# 1. Create and work on feature branch
git checkout -b feature/user-authentication
# ... make changes and commits ...
# 2. Improve commit messages with AI
omni-dev git commit message twiddle 'main..HEAD' --use-context
# 3. Create professional PR with AI-generated description
omni-dev git branch create pr
# ✅ Complete: clean commits + comprehensive PR ready for team review
```
### PR Creation and Updates
Handle PR creation and updates efficiently:
```bash
# Create new PR with AI-generated description
omni-dev git branch create pr main
# If PR already exists, update it with new description
omni-dev git branch create pr --auto-apply
# Save PR details for review before creating
omni-dev git branch create pr --save-only review-pr.yaml
# Review and edit the file...
# Then create manually using GitHub CLI or web interface
# Drive the PR description from commit messages (no diff sent to AI)
# Useful when the branch's commits are well-crafted and convey the intent
omni-dev git branch create pr --from-commits
```
### Collaborative PR Workflow
Work with existing PRs and team feedback:
```bash
# Update existing PR after new commits
git add . && git commit -m "address review feedback"
omni-dev git branch create pr # Updates existing PR
# Generate PR description without creating (for draft PRs)
omni-dev git branch create pr --save-only draft-pr.yaml
# Use the content to update draft PR manually
```
### Large Codebase Migration
Handle large commit ranges efficiently:
```bash
# Process 100+ commits with parallel processing
omni-dev git commit message twiddle 'HEAD~100..HEAD' --concurrency 5
# Save suggestions for review before applying
omni-dev git commit message twiddle 'HEAD~50..HEAD' --save-only review.yaml
# Review the file, then apply manually
omni-dev git commit message amend review.yaml
```
### Legacy Repository Cleanup
Improve old commit messages:
```bash
# Analyze what needs improvement
omni-dev git commit message view 'HEAD~20..HEAD'
# Apply contextual improvements
omni-dev git commit message twiddle 'HEAD~20..HEAD' --use-context
# For very old commits, might need specific handling
git rebase -i HEAD~20 # Interactive rebase first if needed
```
### Team Onboarding
Set up consistent commit standards:
```bash
# 1. Set up project context (one-time setup)
mkdir .omni-dev
# Create scopes.yaml and commit-guidelines.md
# 2. Add to team documentation
echo "Use: omni-dev git commit message twiddle 'main..HEAD' --use-context" >> CONTRIBUTING.md
# 3. Include in CI/PR checks
# Add validation that commit messages follow conventions
```
## Advanced Usage
### Custom Context Directory
Use a different location for context files:
```bash
# Use custom context directory
omni-dev git commit message twiddle 'HEAD~5..HEAD' --context-dir ./project-config
# Context files would be in:
# ./project-config/scopes.yaml
# ./project-config/commit-guidelines.md
```
### Concurrency Configuration
Adjust parallel processing based on your needs:
```bash
# Lower concurrency for complex commits (reduces API load)
omni-dev git commit message twiddle 'HEAD~20..HEAD' --concurrency 2
# Higher concurrency for faster processing
omni-dev git commit message twiddle 'HEAD~20..HEAD' --concurrency 8
# Skip coherence pass for independent commits
omni-dev git commit message twiddle 'HEAD~10..HEAD' --no-coherence
```
### Integration with Git Hooks
Set up automatic improvement in git hooks:
```bash
# .git/hooks/pre-push (make executable)
#!/bin/bash
echo "🤖 Analyzing commit messages..."
echo "💡 Consider running: omni-dev git commit message twiddle 'origin/main..HEAD' --use-context"
exit 1
}
```
### Save and Review Workflow
For high-stakes changes, save suggestions first:
```bash
# 1. Save suggestions to file
omni-dev git commit message twiddle 'HEAD~10..HEAD' --save-only suggestions.yaml
# 2. Review the suggestions file
cat suggestions.yaml
# 3. Edit if needed, then apply
omni-dev git commit message amend suggestions.yaml
```
## Best Practices
### 1. Use Contextual Intelligence
Always use `--use-context` for best results:
```bash
# ✅ Good - uses project context
omni-dev git commit message twiddle 'main..HEAD' --use-context
# ⚠️ Basic - misses project-specific intelligence
omni-dev git commit message twiddle 'main..HEAD'
```
### 2. Set Up Project Context
Invest time in setting up `.omni-dev/` configuration:
- Define meaningful scopes for your project
- Document your commit conventions
- Include file pattern matching for accuracy
### 3. Batch Size Guidelines
| Small projects | 6-8 commits | Faster processing |
| Medium projects | 4-5 commits | Balanced accuracy/speed |
| Large projects | 2-3 commits | More context per batch |
| Complex changes | 1-2 commits | Maximum accuracy |
### 4. Review Before Applying
For important branches, always review suggestions:
```bash
# Save first, review, then apply
omni-dev git commit message twiddle 'main..HEAD' --save-only review.yaml
# Review the file...
omni-dev git commit message amend review.yaml
```
### 5. Clean Working Directory
Always ensure clean working directory:
```bash
# Check status first
git status
# Commit or stash changes before running omni-dev
```
### 6. API Key Security
Keep your Claude API key in an environment variable or `.env` file;
never in command-line arguments or scripts. See
[Authentication](configuration.md#authentication) for the canonical
setup guide.
### 7. Integration with Team Workflow
Make it part of your team's process:
```bash
# Add to PR template
echo "- [ ] Run \`omni-dev git commit message twiddle 'main..HEAD' --use-context\`" >> .github/pull_request_template.md
# Document in CONTRIBUTING.md
echo "Before creating a PR, clean up commit messages with omni-dev" >> CONTRIBUTING.md
# Add PR creation to workflow
echo "Create PR with: \`omni-dev git branch create pr\`" >> CONTRIBUTING.md
```
### 8. PR Creation Best Practices
Optimize your PR creation workflow:
```bash
# ✅ Good - Clean commits first, then create PR
omni-dev git commit message twiddle 'main..HEAD' --use-context
omni-dev git branch create pr
# ✅ Good - Review PR details before creating
omni-dev git branch create pr --save-only review.yaml
# Edit file if needed, then use GitHub CLI or web interface
# ⚠️ Caution - Ensure working directory is clean
git status # Check for uncommitted changes first
# ✅ Good - Use base branch when not default
omni-dev git branch create pr develop # For non-main base branches
```
## Troubleshooting
See [Troubleshooting Guide](troubleshooting.md) for common issues and solutions.
## Need Help?
- 📖 [Configuration Guide](configuration.md) - Detailed setup instructions
- 🔧 [Troubleshooting](troubleshooting.md) - Common issues
- 📝 [Examples](examples.md) - Real-world usage examples
- 💬 [GitHub Discussions](https://github.com/rust-works/omni-dev/discussions) - Community support
- 🐛 [GitHub Issues](https://github.com/rust-works/omni-dev/issues) - Bug reports and features
## GitHub authentication
By default, omni-dev uses the GitHub CLI's existing credentials, including any
`GH_TOKEN` or `GITHUB_TOKEN` override. `OMNI_DEV_GITHUB_AUTH=pat` explicitly selects
this default.
For an independent GitHub App installation identity, create a GitHub App, install
it on the repositories you need, and generate an RSA private key in the App
settings. Grant the App permissions for the operations you use: for example,
Issues and Pull requests read access for polling, write access for mutations,
and Checks/Commit statuses read access for PR checks. Projects queries require
corresponding organization Projects permissions; some user-only GitHub APIs
cannot be accessed with installation tokens.
```bash
export OMNI_DEV_GITHUB_AUTH=app
export OMNI_DEV_GITHUB_APP_ID=123456
export OMNI_DEV_GITHUB_APP_INSTALLATION_ID=789012
export OMNI_DEV_GITHUB_APP_PRIVATE_KEY_PATH=/absolute/path/to/app-private-key.pem
omni-dev ai jev route 1813 --repo rust-works/omni-dev
```
Use the numeric App ID (not its client ID) and installation ID. The private-key
file must contain an unencrypted RSA PKCS#1 (`BEGIN RSA PRIVATE KEY`, as downloaded
from GitHub) or PKCS#8 (`BEGIN PRIVATE KEY`) PEM. Restrict file access to your user
(e.g. `chmod 600`). These settings are process environment variables; pass them
to the daemon's environment too when using background polling.
App mode sets both `GH_TOKEN` and `GITHUB_TOKEN` on each `gh` child process,
overriding inherited personal tokens without changing the parent environment.
Installation tokens are cached only in process memory and refreshed within five
minutes of the expiry returned by GitHub (normally one hour). Concurrent calls
share a mint; separate CLI processes mint separately. Keys are read again on
refresh, so replacing a key file takes effect at the next refresh. Tokens, JWTs,
private keys and token response bodies are excluded from auth errors and logs.
Incomplete configuration, an unreadable/invalid key, or an exchange failure
stops that call with an error; App mode never falls back to personal credentials.
For HTTP 401/403/404, check the IDs, active key, installation, repository access
and App permissions. Local `gh --version`/`--help` probes do not mint tokens.
The App exchange currently targets GitHub.com; `GH_HOST` must be unset or
`github.com`, and Enterprise-specific host options are unsupported in App mode.
GitHub documents [JWT signing requirements](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app)
and the [installation token exchange](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app).