a-novel is the single command-line tool for the Agora storyverse: it builds,
tests, runs and releases every project across the
a-novel and
a-novel-kit organizations — one CLI in place
of the per-repo Makefiles and bash scripts.
This repository — the stack — hosts that CLI and anchors local checkouts of the projects it operates on:
a-novel/ this repo (github.com/a-novel-kit/stack)
├── cli/ the a-novel CLI (Go)
├── app/ a-novel org checkouts (services) — gitignored
└── kit/ a-novel-kit org checkouts (libraries) — gitignored
app/ and kit/ are not submodules — they are plain clones managed by
a-novel core sync, kept out of this repo's history.
Building the CLI needs Go; a-novel core setup then preflights your environment
and requires git and a running Podman. The rest of the toolchain (Node + pnpm,
gh) and the per-OS install commands live in the
developer onboarding guide.
go install github.com/a-novel-kit/stack/cli/cmd/a-novel@latest
a-novel core setup # one-time bootstrap: env checks, state dirs, shell rc, daemon
a-novel core sync # clone / fast-forward the workspace repos into app/ and kit/go install pulls and builds the binary straight from the module path — no
upfront clone. core setup then clones the stack repo itself into
~/git-projects/a-novel (the default stack) when it's missing, so you don't
bootstrap the workspace by hand. It is otherwise idempotent, and installs a block
in your shell rc (zsh, bash and fish are supported) that auto-starts the
background daemon and loads tab-completion in every new shell.
Verify:
a-novel --version # the binary resolves a build version
a-novel core status # the daemon is up
a-novel run ps # the daemon answers over its unix socketAfter a git pull that touches cli/, run a-novel install to rebuild and
reinstall — it hands the daemon off without dropping running services.
For day-to-day work, open the terminal UI:
a-novel run uiIt is a full-screen dashboard: a services list on the left, a tabbed detail and
live log viewer on the right, and a command palette (press Esc) covering the
entire CLI surface — :start, :kill, :infra-start, and so on. Press ?
for a searchable key and command reference; q quits.
The UI is a thin client over the same daemon as the command line, so anything you do in it is visible to the CLI and vice-versa. If you learn one command, learn this one.
Everything the UI does is also a discrete command — what you reach for in scripts, in CI, or when you already know exactly what you want.
Build and test discover their targets in the working tree and show an
interactive picker by default; -y skips it and runs everything sequentially
(also the default when there is no TTY):
a-novel test -y # run every Go + pnpm test in the tree
a-novel build -y --type=podman # build only the Podman imagesRun services. The daemon auto-starts a target's dependencies — Postgres, migrations — before the target itself:
a-novel run start service-json-keys/rest # <service>/<target>
a-novel run logs service-json-keys/rest --follow
a-novel run ps # what is running
a-novel run kill service-json-keys/restRelease. Cut a tag locally; CI's release workflow publishes from the pushed tag:
a-novel publish version 0.21.0 # or: patch / minor / majorRun a-novel <verb> --help for any command's full flags. The complete
reference — command tree, daemon architecture, state directories, the compose
contract — is in cli/README.md.
Lint, format and generate are deliberately not CLI verbs: each repo exposes them as uniform pnpm scripts (
pnpm lint:go,pnpm format:go,pnpm generate:go, …).
Two identities talk to GitHub: you (the operator, via gh) and the org
bot (a GitHub App). Anything that authors or merges — PR creation, edits,
ready/merge/close — runs as you. The bot is strictly for comments, so automated
notes attribute to <app>[bot] rather than a human account.
The bot's signing keys never touch a dev machine. To comment as the bot you
trigger your org's dispatcher workflow with your own gh token; the
workflow holds the key and posts:
gh auth login # operator: pick GitHub.com + SSH
a-novel core bot-comment a-novel service-authentication 123 \
--body "automated review note"bot-comment dispatches the
bot-comment workflow in the target
org's dispatcher repo and waits for the run. The workflow mints a short-lived
token scoped to the single target repo (issues + pull-request writes only — no
contents, so it cannot push, merge or release), then posts a comment and
exits — it never authors or edits a PR/issue. So an operator
(or the agent) only needs gh plus actions: write on that dispatcher repo;
no .pem is ever stored locally, and a compromised machine cannot do
anything as the bot beyond ask for a comment. It handles top-level comments on
PRs or issues, and --reply-to replies inside a PR review thread.
There is one dispatcher per org, because the App key is an org-level secret and org secrets do not cross org boundaries:
| Org | Dispatcher repo |
|---|---|
a-novel-kit |
a-novel-kit/stack |
a-novel |
a-novel/.github |
One-time GitHub App setup (per org) + dispatcher secrets
Each org has a dedicated GitHub App (anovelbot-agent for a-novel,
anovelkitbot-agent for a-novel-kit). Both are already registered — these
steps are kept for new orgs or App re-creation.
Per org. Register a GitHub App on the org with:
- Repository permissions:
Issues: Read and write,Pull requests: Read and write,Metadata: Read-only— and nothing else, so the bot cannot push, merge or release. - Webhook: disabled. The App is outbound-only.
- Install the App on all repositories of the org.
The App ID is public and inlined in
bot-comment.yaml; no Installation ID is
needed — create-github-app-token resolves it from the org owner.
Dispatcher secret (once per org — never on a dev machine). Generate a
private key from each App's settings page (downloads a .pem) and store it as
an organization-level Actions secret named AGENT_BOT_PRIVATE_KEY in that
org (one in a-novel for anovelbot-agent, one in a-novel-kit for
anovelkitbot-agent). Make sure its repository visibility includes that org's
dispatcher repo. The workflow derives which key/App/owner to use from
github.repository_owner, so the two copies are identical.
Then grant each operator/agent actions: write on the dispatcher repo so they
can dispatch. Verify end-to-end by commenting on a scratch PR or issue:
a-novel core bot-comment a-novel <repo> <number> --body "bot test"Releases, merges and pushes to master are human-only. a-novel publish version refuses to run without a TTY, and the real boundary is server-side:
branch protection on master, tag protection on v*, and a comments-only bot
whose signing keys live only in CI (never on a dev machine — see
GitHub access), reached through a per-org dispatcher workflow
that can do nothing but post a comment. The org-wide security policy lives in
a-novel/.github.