docs: explain the two catalogues, and gate the files they generate
Documents what the preceding commits added, so it lands after them rather than describing things that are not there yet. Both new generators are wired up here too, because a generated file that nothing checks is a file that drifts. The controls table in `README.md` is now generated from the command catalogue, which is the point of having one — the six hand-written rows it replaces had already drifted in the copy under `apps/cli/`. `make controls` rewrites it and `CONTROLS.md`, then syncs the CLI's README, and it has to run in that order because `sync-readme.mjs` copies the file the first step writes into. Both `make preflight` and the PR gate fail on a stale one. `make models:refresh` is deliberately not in either. Every other generated file in this repo derives from something committed beside it, so its drift gate can only fire when a human changed the input. This one derives from models.dev, which changes whenever a vendor ships a model, so gating on it would need network to pass and would turn PRs that touched nothing red. Treat a refresh like a lockfile bump. Both generated tables spell each chord for both platforms. A single column was Mac-only exactly where it mattered, which is the bug the catalogue exists to end, and `README.md` is the highest-traffic place to make it. The model flags get their own rows, the config file keys are documented as per-backend and why, and the two capability matrices gain a line for what each backend can enumerate about itself — the asymmetry the seed list exists for.
This commit is contained in:
@@ -73,6 +73,18 @@ jobs:
|
||||
# generated file matches what the generator emits. That is a property of
|
||||
# the repo, not of the platform, so running it twice doubles the runtime
|
||||
# and the flake surface for no extra signal.
|
||||
# CONTROLS.md and the short table in README.md are generated from the
|
||||
# overlay's command catalog. Before the README check, because that one
|
||||
# copies the root README.md that this one writes into.
|
||||
- name: Controls reference is not stale
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
if ! node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/gen-controls.mjs --check; then
|
||||
echo "::error file=CONTROLS.md::The controls reference is stale. Run 'make controls' and commit the result."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: README is not stale
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
|
||||
@@ -49,6 +49,8 @@ wrapper either way — every recipe is one `pnpm` or `node` call — so use thos
|
||||
| `make doctor` | `node apps/cli/dist/index.js doctor --cwd apps/web` |
|
||||
| `make check` | `pnpm lint && pnpm typecheck && pnpm test` |
|
||||
| `make readme` | `node scripts/sync-readme.mjs` |
|
||||
| `make controls` | `node --experimental-strip-types scripts/gen-controls.mjs`, then `make readme` |
|
||||
| `make models:refresh` | `node scripts/gen-models.mjs` — refetches the seed model list |
|
||||
| `make storybook`| `pnpm turbo run storybook --filter=@airship/overlay` |
|
||||
|
||||
`pnpm dev:web` rather than a bare `vite dev`: the site cannot start until
|
||||
@@ -163,6 +165,8 @@ the adapter is explicit about each gap rather than faking it:
|
||||
| Cost | `total_cost_usd` | tokens only |
|
||||
| Fork a session | `forkSession` | starts a fresh thread, and says so |
|
||||
| `maxTurns` / `maxBudgetUsd` | enforced | unsupported; warned about at startup |
|
||||
| Model selection | `options.model` | `ThreadOptions.model` |
|
||||
| Listing its models | `query.supportedModels()`, account-scoped | **nothing** — no subcommand, no RPC |
|
||||
|
||||
Codex items are normalized into the same tool vocabulary Claude uses (`command_execution` →
|
||||
`Bash`, `file_change` → `Edit`/`Write`/`Delete`), so one copy of the summarization rules serves
|
||||
@@ -186,6 +190,8 @@ It is the most capable of the three in several places, and the adapter uses all
|
||||
| Native undo | none | `session.revert`, snapshot-backed |
|
||||
| Cost | tokens only | real cost per message |
|
||||
| Reasoning effort | `modelReasoningEffort` | **none** — `--effort` is ignored |
|
||||
| Model selection | `ThreadOptions.model` | `{providerID, modelID}` on the prompt |
|
||||
| Listing its models | **nothing** | `client.config.providers()`, only what is authed |
|
||||
|
||||
Gaps handled explicitly rather than faked:
|
||||
|
||||
@@ -242,6 +248,69 @@ into a real value. That indirection is why the install command appears once, in
|
||||
why `resolve.ts` throws at module load on an unknown token rather than shipping the literal
|
||||
`{{installCommand}}` to a visitor.
|
||||
|
||||
### The controls reference
|
||||
|
||||
`CONTROLS.md`, and the short table between the `<!-- controls:start -->` markers in
|
||||
`README.md`, are generated:
|
||||
|
||||
```bash
|
||||
make controls # rewrites both, then syncs apps/cli/README.md, then commit them
|
||||
```
|
||||
|
||||
The source is `packages/overlay/src/keys/catalog.ts` — the same table the runtime binds
|
||||
from, the shortcuts panel and the ⌘K palette render from, and every tooltip chip resolves
|
||||
against. Nothing else in the editor is allowed to spell a chord: `MenuItem.command` renders
|
||||
one from the catalog, and `keys/catalog.test.ts` fails any `hint:` literal that looks like a
|
||||
keystroke.
|
||||
|
||||
That is not tidiness. Chords used to be string literals at each `keys.bind` call site, so
|
||||
twenty-seven of the thirty-three shortcuts appeared nowhere in the product or the docs, five
|
||||
menu rows showed Mac glyphs to Windows users, one advertised `⌘Z` for a feature ⌘Z has never
|
||||
run, and the only reference — six hand-written rows in `README.md` — had already drifted in
|
||||
its own copy under `apps/cli/`.
|
||||
|
||||
`gen-controls.mjs` imports the `.ts` catalog directly under `--experimental-strip-types`,
|
||||
which is why that module may contain **no value imports**; type imports are erased before
|
||||
resolution and are free. `keys/catalog.test.ts` enforces it, and
|
||||
`keys/controls-doc.test.ts` byte-compares the committed files against the same renderers the
|
||||
script uses — so drift fails the suite even where the script itself cannot run.
|
||||
|
||||
Run it before `make readme`, never after: it writes into the root `README.md` that
|
||||
`sync-readme.mjs` copies. `make controls` does both in that order.
|
||||
|
||||
### The seed model list
|
||||
|
||||
`packages/protocol/src/models.ts` is generated from [models.dev](https://models.dev):
|
||||
|
||||
```bash
|
||||
make models:refresh # refetches, rewrites the module, then commit it
|
||||
```
|
||||
|
||||
It exists because of an asymmetry between the backends. Claude answers
|
||||
`query.supportedModels()` and OpenCode answers `client.config.providers()`, both live and both
|
||||
scoped to what you are actually signed in to — so for those two this is only what the picker
|
||||
paints before the answer arrives, and what it falls back to offline. **Codex can enumerate
|
||||
nothing**, at any layer, so for that backend this file *is* the list.
|
||||
|
||||
Two things about it are deliberate.
|
||||
|
||||
**It is not in `make preflight`.** `gen-controls.mjs --check` belongs there because it derives
|
||||
from a file committed beside it, so it can only drift when someone edits that file. This one
|
||||
derives from a remote registry that changes whenever a vendor ships a model — gating on it
|
||||
would need network to pass and would turn PRs that touched nothing red. `reference/NEXT-STEPS.md`
|
||||
§7 describes what that costs. Treat a refresh like a lockfile bump: deliberate, and reviewed as
|
||||
a diff.
|
||||
|
||||
**Judgement lives in `scripts/models.curation.json`, not in the output.** The mechanical filter
|
||||
— `tool_call`, `reasoning`, a release-date floor — admits things like `gpt-realtime-2.1`, which
|
||||
is not a coding model. The deny list, the cap, and the Claude CLI aliases models.dev cannot know
|
||||
about are all in that file, so the generated module stays purely derived and the taste is what
|
||||
gets reviewed.
|
||||
|
||||
The generator formats its own output through the repo's biome before writing. Without that,
|
||||
`ultracite fix` would reformat the file the first time anyone linted and `--check` would report
|
||||
stale against a file nobody touched.
|
||||
|
||||
### The social card
|
||||
|
||||
`public/og.png` is generated, not drawn:
|
||||
|
||||
@@ -151,6 +151,9 @@ preflight: ## Run the full CI gate locally (lint + typecheck + test + route drif
|
||||
@$(MAKE) typecheck
|
||||
@printf "$(CYAN)» Test$(RESET)\n"
|
||||
@$(MAKE) test
|
||||
@printf "$(CYAN)» Controls reference$(RESET)\n"
|
||||
@node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/gen-controls.mjs --check
|
||||
@node scripts/sync-readme.mjs --check
|
||||
@printf "$(CYAN)» Route tree$(RESET)\n"
|
||||
@$(MAKE) "web:build"
|
||||
@if ! git diff --quiet -- $(GENERATED); then \
|
||||
@@ -167,6 +170,8 @@ preflight: ## Run the full CI gate locally (lint + typecheck + test + route drif
|
||||
preflight\:fix: ## preflight, but autofix first (ultracite fix + regenerate routes)
|
||||
@printf "$(CYAN)» Fix (ultracite)$(RESET)\n"
|
||||
@$(MAKE) fix
|
||||
@printf "$(CYAN)» Controls reference$(RESET)\n"
|
||||
@$(MAKE) controls
|
||||
@printf "$(CYAN)» Route tree$(RESET)\n"
|
||||
@$(MAKE) "web:build"
|
||||
@printf "$(CYAN)» Typecheck$(RESET)\n"
|
||||
@@ -283,7 +288,23 @@ demo: ## One-shot: install + build, then print the two-terminal recipe
|
||||
|
||||
##@ Release (@airshiplabs/cli)
|
||||
|
||||
.PHONY: readme release release\:ci release\:retry release\:version
|
||||
.PHONY: controls models\:refresh readme release release\:ci release\:retry release\:version
|
||||
|
||||
# Before `readme`, always: this writes the short table into the root README.md,
|
||||
# and `readme` copies the root README.md to apps/cli. Run the other way round
|
||||
# and the CLI's copy is one generation behind.
|
||||
controls: ## Regenerate CONTROLS.md and the README table from the command catalog
|
||||
@node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/gen-controls.mjs
|
||||
@$(MAKE) readme
|
||||
|
||||
# Deliberately NOT in `preflight`, unlike `controls`. Every other generated file
|
||||
# here derives from something committed beside it, so its drift gate can only
|
||||
# fire when a human changed the input. This one derives from models.dev, which
|
||||
# changes whenever a vendor ships a model — gating on it would need network to
|
||||
# pass and would go red on PRs that touched nothing. See the header of
|
||||
# scripts/gen-models.mjs, and NEXT-STEPS §7 for what that costs.
|
||||
models\:refresh: ## Refresh the seed model catalogue from models.dev
|
||||
@node scripts/gen-models.mjs
|
||||
|
||||
readme: ## Regenerate apps/cli/README.md from the root README.md
|
||||
@node scripts/sync-readme.mjs
|
||||
|
||||
@@ -106,16 +106,37 @@ to the canvas — the parameter takes the internal mode name, so it is `shell`,
|
||||
|
||||
Open any route of your app in Airship — `/pricing`, `/settings` — and every frame opens there.
|
||||
|
||||
On the canvas:
|
||||
The controls, in short — the table below is generated from the editor's own command
|
||||
catalog, so it cannot drift from what the keys actually do:
|
||||
|
||||
| Gesture | |
|
||||
| --- | --- |
|
||||
| wheel / two-finger | pan |
|
||||
| ⌘/ctrl-wheel, pinch | zoom at cursor |
|
||||
| space-drag, middle-drag | pan |
|
||||
| ⇧1 / ⇧2 / ⇧0 | fit / zoom to selection / 100% |
|
||||
| `H` | hand tool (view mode) |
|
||||
| `F` | add a frame |
|
||||
<!-- controls:start -->
|
||||
| | macOS | Windows / Linux |
|
||||
| --- | --- | --- |
|
||||
| pan the canvas | Wheel / two-finger | Wheel / two-finger |
|
||||
| zoom at the cursor | ⌘-wheel / pinch | Ctrl-wheel / pinch |
|
||||
| pan without the hand | Space-drag | Space-drag |
|
||||
| select an element | Click | Click |
|
||||
| edit text in place | Double-click | Double-click |
|
||||
| open the element menu | Right-click | Right-click |
|
||||
| scrub a number | Drag a field's glyph | Drag a field's glyph |
|
||||
| undo | ⌘Z | Ctrl+Z |
|
||||
| delete element | ⌫ or Del | Backspace or Del |
|
||||
| duplicate | ⌘D | Ctrl+D |
|
||||
| edit text | ↩ or T | Enter or T |
|
||||
| move | V | V |
|
||||
| inspect | I | I |
|
||||
| zoom in | ⌘= or = | Ctrl+= or = |
|
||||
| zoom out | ⌘- or - | Ctrl+- or - |
|
||||
| zoom to 100% | ⌘0 or ⇧0 | Ctrl+0 or Shift+0 |
|
||||
| zoom to fit | ⇧1 | Shift+1 |
|
||||
| hand tool | H | H |
|
||||
| add a frame | F | F |
|
||||
| send | ⌘↩ | Ctrl+Enter |
|
||||
| keyboard shortcuts | ? | ? |
|
||||
| command palette | ⌘K | Ctrl+K |
|
||||
|
||||
Press `?` in the editor for all of them, or see [CONTROLS.md](./CONTROLS.md).
|
||||
<!-- controls:end -->
|
||||
|
||||
## Edit and View
|
||||
|
||||
@@ -166,6 +187,7 @@ you what you're giving up at startup.
|
||||
| `--effort` | yes | yes | **ignored** |
|
||||
| `--max-turns`, `--max-budget` | yes | **ignored** | **ignored** |
|
||||
| `--model` | a model name | a model name | needs the `provider/model` form |
|
||||
| Lists its own models | yes | **no** — Airship ships a list | yes, the ones you are signed in to |
|
||||
| `--safe` | checks each edit and command | **real sandbox** | asks before each edit and command |
|
||||
| Install | included | included | **you install it yourself** |
|
||||
|
||||
@@ -255,7 +277,7 @@ forwarded.
|
||||
| Flag | | Default |
|
||||
| --- | --- | --- |
|
||||
| `-a, --agent <name>` | Coding agent: `claude`, `codex`, `opencode`. | `claude` |
|
||||
| `-m, --model <id>` | Model id. | the agent's own default |
|
||||
| `-m, --model <id>` | Model for whichever agent runs. Per-backend flags below outrank it. | the agent's own default |
|
||||
| `--effort <level>` | Reasoning effort: `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. | |
|
||||
| `--max-turns <n>` | Cap agent turns per edit (claude only). | `24` |
|
||||
| `--max-budget <usd>` | Stop an edit if it exceeds this cost in USD (claude only). | |
|
||||
@@ -263,6 +285,12 @@ forwarded.
|
||||
|
||||
`-m` is `--model`, not `--mode`. `--mode` has no short alias.
|
||||
|
||||
You can also pick the model from the editor. The agent button in the chat header opens a
|
||||
group per backend; choosing a row picks the backend **and** its model in one go, and a box
|
||||
at the bottom takes any id the list does not offer. That choice is per backend and is
|
||||
remembered, so switching between them does not carry one backend's model to another. The
|
||||
flags below are the resting default it starts from.
|
||||
|
||||
### Sandbox
|
||||
|
||||
| Flag | | Default |
|
||||
@@ -273,6 +301,9 @@ forwarded.
|
||||
|
||||
| Flag | | Default |
|
||||
| --- | --- | --- |
|
||||
| `--claude-model <id>` | Model for the `claude` backend. Takes an alias or a full id. | `--model`, then the agent's own |
|
||||
| `--codex-model <id>` | Model for the `codex` backend. | `--model`, then the agent's own |
|
||||
| `--opencode-model <provider/model>` | Model for the `opencode` backend. Needs the `provider/model` form. | `--model`, then the agent's own |
|
||||
| `--codex-path <path>` | Path to the `codex` binary. | bundled |
|
||||
| `--codex-config <k=v>` | Extra `codex --config` pair; repeatable. | |
|
||||
| `--opencode-path <path>` | Path to the `opencode` binary. | found on PATH |
|
||||
@@ -339,10 +370,17 @@ one. Every key is a flag name, in either kebab or camel case:
|
||||
"agent": "claude",
|
||||
"mode": "canvas",
|
||||
"target": 3000,
|
||||
"safe": true
|
||||
"safe": true,
|
||||
"claudeModel": "opus",
|
||||
"codexModel": "gpt-5.3-codex"
|
||||
}
|
||||
```
|
||||
|
||||
Models are keyed per backend — `claudeModel`, `codexModel`, `opencodeModel` — because the
|
||||
editor's picker can switch backends mid-session, and one shared `model` would follow it and
|
||||
hand Codex an id only Claude answers to. A plain `"model"` still works and applies to
|
||||
whichever backend runs, with the per-backend keys taking precedence.
|
||||
|
||||
Airship looks for it from `--cwd` upwards and **stops at your repository root**, so a stray
|
||||
config file somewhere above your repo won't affect you. Misspell a key and it says so, with a
|
||||
suggestion — it never quietly ignores one.
|
||||
@@ -359,8 +397,9 @@ AIRSHIP_CWD AIRSHIP_EFFORT AIRSHIP_OPENCODE_PATH
|
||||
AIRSHIP_MODE AIRSHIP_MAX_TURNS AIRSHIP_OPENCODE_URL
|
||||
AIRSHIP_EXEC AIRSHIP_MAX_BUDGET AIRSHIP_OPENCODE_AGENT
|
||||
AIRSHIP_OPEN AIRSHIP_COMMIT AIRSHIP_OPENCODE_CONFIG
|
||||
AIRSHIP_SAFE AIRSHIP_JSON AIRSHIP_QUIET
|
||||
AIRSHIP_DEBUG
|
||||
AIRSHIP_SAFE AIRSHIP_JSON AIRSHIP_OPENCODE_MODEL
|
||||
AIRSHIP_DEBUG AIRSHIP_QUIET AIRSHIP_CLAUDE_MODEL
|
||||
AIRSHIP_CODEX_MODEL
|
||||
```
|
||||
|
||||
`AIRSHIP_HELP` and `AIRSHIP_VERSION` are deliberately not read — exporting one would leave the
|
||||
|
||||
+74
-11
@@ -108,14 +108,59 @@ to the canvas — the parameter takes the internal mode name, so it is `shell`,
|
||||
|
||||
Open any route of your app in Airship — `/pricing`, `/settings` — and every frame opens there.
|
||||
|
||||
On the canvas:
|
||||
The controls, in short — the table below is generated from the editor's own command
|
||||
catalog, so it cannot drift from what the keys actually do:
|
||||
|
||||
| Gesture | |
|
||||
| --- | --- |
|
||||
| wheel / two-finger | pan |
|
||||
| ⌘/ctrl-wheel, pinch | zoom at cursor |
|
||||
| space-drag, middle-drag | pan |
|
||||
| ⇧1 / ⇧2 / ⇧0 | fit / zoom to selection / 100% |
|
||||
<!-- controls:start -->
|
||||
| | macOS | Windows / Linux |
|
||||
| --- | --- | --- |
|
||||
| pan the canvas | Wheel / two-finger | Wheel / two-finger |
|
||||
| zoom at the cursor | ⌘-wheel / pinch | Ctrl-wheel / pinch |
|
||||
| pan without the hand | Space-drag | Space-drag |
|
||||
| select an element | Click | Click |
|
||||
| edit text in place | Double-click | Double-click |
|
||||
| open the element menu | Right-click | Right-click |
|
||||
| scrub a number | Drag a field's glyph | Drag a field's glyph |
|
||||
| undo | ⌘Z | Ctrl+Z |
|
||||
| delete element | ⌫ or Del | Backspace or Del |
|
||||
| duplicate | ⌘D | Ctrl+D |
|
||||
| edit text | ↩ or T | Enter or T |
|
||||
| move | V | V |
|
||||
| inspect | I | I |
|
||||
| zoom in | ⌘= or = | Ctrl+= or = |
|
||||
| zoom out | ⌘- or - | Ctrl+- or - |
|
||||
| zoom to 100% | ⌘0 or ⇧0 | Ctrl+0 or Shift+0 |
|
||||
| zoom to fit | ⇧1 | Shift+1 |
|
||||
| hand tool | H | H |
|
||||
| add a frame | F | F |
|
||||
| send | ⌘↩ | Ctrl+Enter |
|
||||
| keyboard shortcuts | ? | ? |
|
||||
| command palette | ⌘K | Ctrl+K |
|
||||
|
||||
Press `?` in the editor for all of them, or see [CONTROLS.md](https://github.com/0xnyn/airship/blob/main/CONTROLS.md).
|
||||
<!-- controls:end -->
|
||||
|
||||
## Edit and View
|
||||
|
||||
The two modes point the editor at different things, and the panels follow.
|
||||
|
||||
**Edit** is about an element: hover to highlight, click to select, and the agent panel and
|
||||
inspector are open on either side of it.
|
||||
|
||||
**View** is about your frames. The page underneath is fully interactive — click through it,
|
||||
fill in forms, scroll — so there is no element selection, and the two panels that depend on
|
||||
one step aside. In their place the left panel lists every frame, and a minimap appears in
|
||||
the bottom-right corner:
|
||||
|
||||
- Click a frame in the list to go to it without changing your zoom; double-click to zoom
|
||||
to it. Rename it in place, and use `⋯` for its device size, rotate, duplicate and delete.
|
||||
- The list is stacking order, front-most at the top. Drag a row anywhere along it — or
|
||||
press ↑↓ on its handle — to restack frames that overlap on the canvas; up is forward.
|
||||
- Drag the minimap's indicator and the canvas travels with it; press anywhere else on the
|
||||
map to jump there. Pan far off your frames and it keeps pointing back at them.
|
||||
|
||||
Your panel arrangement is remembered per mode, so switching back returns the inspector
|
||||
exactly as you left it.
|
||||
|
||||
## Inspector
|
||||
|
||||
@@ -144,6 +189,7 @@ you what you're giving up at startup.
|
||||
| `--effort` | yes | yes | **ignored** |
|
||||
| `--max-turns`, `--max-budget` | yes | **ignored** | **ignored** |
|
||||
| `--model` | a model name | a model name | needs the `provider/model` form |
|
||||
| Lists its own models | yes | **no** — Airship ships a list | yes, the ones you are signed in to |
|
||||
| `--safe` | checks each edit and command | **real sandbox** | asks before each edit and command |
|
||||
| Install | included | included | **you install it yourself** |
|
||||
|
||||
@@ -233,7 +279,7 @@ forwarded.
|
||||
| Flag | | Default |
|
||||
| --- | --- | --- |
|
||||
| `-a, --agent <name>` | Coding agent: `claude`, `codex`, `opencode`. | `claude` |
|
||||
| `-m, --model <id>` | Model id. | the agent's own default |
|
||||
| `-m, --model <id>` | Model for whichever agent runs. Per-backend flags below outrank it. | the agent's own default |
|
||||
| `--effort <level>` | Reasoning effort: `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. | |
|
||||
| `--max-turns <n>` | Cap agent turns per edit (claude only). | `24` |
|
||||
| `--max-budget <usd>` | Stop an edit if it exceeds this cost in USD (claude only). | |
|
||||
@@ -241,6 +287,12 @@ forwarded.
|
||||
|
||||
`-m` is `--model`, not `--mode`. `--mode` has no short alias.
|
||||
|
||||
You can also pick the model from the editor. The agent button in the chat header opens a
|
||||
group per backend; choosing a row picks the backend **and** its model in one go, and a box
|
||||
at the bottom takes any id the list does not offer. That choice is per backend and is
|
||||
remembered, so switching between them does not carry one backend's model to another. The
|
||||
flags below are the resting default it starts from.
|
||||
|
||||
### Sandbox
|
||||
|
||||
| Flag | | Default |
|
||||
@@ -251,6 +303,9 @@ forwarded.
|
||||
|
||||
| Flag | | Default |
|
||||
| --- | --- | --- |
|
||||
| `--claude-model <id>` | Model for the `claude` backend. Takes an alias or a full id. | `--model`, then the agent's own |
|
||||
| `--codex-model <id>` | Model for the `codex` backend. | `--model`, then the agent's own |
|
||||
| `--opencode-model <provider/model>` | Model for the `opencode` backend. Needs the `provider/model` form. | `--model`, then the agent's own |
|
||||
| `--codex-path <path>` | Path to the `codex` binary. | bundled |
|
||||
| `--codex-config <k=v>` | Extra `codex --config` pair; repeatable. | |
|
||||
| `--opencode-path <path>` | Path to the `opencode` binary. | found on PATH |
|
||||
@@ -317,10 +372,17 @@ one. Every key is a flag name, in either kebab or camel case:
|
||||
"agent": "claude",
|
||||
"mode": "canvas",
|
||||
"target": 3000,
|
||||
"safe": true
|
||||
"safe": true,
|
||||
"claudeModel": "opus",
|
||||
"codexModel": "gpt-5.3-codex"
|
||||
}
|
||||
```
|
||||
|
||||
Models are keyed per backend — `claudeModel`, `codexModel`, `opencodeModel` — because the
|
||||
editor's picker can switch backends mid-session, and one shared `model` would follow it and
|
||||
hand Codex an id only Claude answers to. A plain `"model"` still works and applies to
|
||||
whichever backend runs, with the per-backend keys taking precedence.
|
||||
|
||||
Airship looks for it from `--cwd` upwards and **stops at your repository root**, so a stray
|
||||
config file somewhere above your repo won't affect you. Misspell a key and it says so, with a
|
||||
suggestion — it never quietly ignores one.
|
||||
@@ -337,8 +399,9 @@ AIRSHIP_CWD AIRSHIP_EFFORT AIRSHIP_OPENCODE_PATH
|
||||
AIRSHIP_MODE AIRSHIP_MAX_TURNS AIRSHIP_OPENCODE_URL
|
||||
AIRSHIP_EXEC AIRSHIP_MAX_BUDGET AIRSHIP_OPENCODE_AGENT
|
||||
AIRSHIP_OPEN AIRSHIP_COMMIT AIRSHIP_OPENCODE_CONFIG
|
||||
AIRSHIP_SAFE AIRSHIP_JSON AIRSHIP_QUIET
|
||||
AIRSHIP_DEBUG
|
||||
AIRSHIP_SAFE AIRSHIP_JSON AIRSHIP_OPENCODE_MODEL
|
||||
AIRSHIP_DEBUG AIRSHIP_QUIET AIRSHIP_CLAUDE_MODEL
|
||||
AIRSHIP_CODEX_MODEL
|
||||
```
|
||||
|
||||
`AIRSHIP_HELP` and `AIRSHIP_VERSION` are deliberately not read — exporting one would leave the
|
||||
|
||||
Reference in New Issue
Block a user