@neocompose/cli
v0.1.1
Published
Neo Compose schema-as-code CLI: a C#-flavored working copy of your project schema, bidirectionally synced with Neo Compose.
Readme
neo — Neo Compose schema-as-code CLI
Implements specs/schema-as-code-cli.md: a git-like C# working copy of a project version's schema, bidirectionally synced with Convex, plus NeoScript and content commands.
Run
npm i -g @neocompose/cli # customers: standalone package, `neo` on PATH
node cli/dist/neo.mjs <command> # repo dev: runs TS directly via tsxPackaging (@neocompose/cli)
The CLI publishes as a standalone npm package from cli/:
cli/package.json—bin: neo, runtime deps are onlyconvexand@inquirer/prompts.node cli/build.mjs(alsoprepublishOnly) bundlessrc/main.tsintodist/neo.mjswith esbuild: all shared monorepo code (src/models,src/database/neoscript, the evaluator,convex/_generatedreferences) is compiled INTO the bundle, so the published artifact has no source dependency on this repo. The build fails if the bundle imports any package not declared as a dependency.cli/dist/is gitignored; publish withcd cli && npm publish.
Interactive UX
In a terminal, missing arguments become prompts (@inquirer/prompts):
neo init walks project → branch/version → directory; neo login picks the
profile and opens the browser for the device code; neo branch switch /
neo merge offer pickers; neo push confirms (and offers bump acceptance on
rejection); neo release cut previews the derived floor before asking for
the bump; pull conflicts offer markers / mine-all / theirs-all. Every prompt
degrades in CI/pipes (no TTY, CI, or NEO_NO_INTERACTIVE set): commands
keep their flag-driven behavior, prompts that would block instead raise an
error naming the flag to pass, and output is plain (also NO_COLOR).
Commands
| Command | Purpose |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| login | Device-code OAuth (neo-cli-editor / --profile release); --token-stdin / NEO_COMPOSE_TOKEN for CI |
| init --project <id> [--version <id>] | Scaffold neo/ (csproj, attributes, .gitignore) + first pull |
| pull [--force] | Sync from server; field-level three-way merge; conflict markers on overlap |
| push [--dry-run] [--accept-bump] | Atomic CAS transaction; assigns ids to creates; canonical rewrite |
| status / diff | Working-copy changes vs base |
| dev [--push] | Convex websocket sync-signal subscription + file watcher |
| resolve --mine\|--theirs | Conflict-marker sugar (editing the file is the real path) |
| script check\|eval\|apply | Compile/evaluate NeoScript with the web's own compiler/evaluator |
| records / values / loc | Content reads + writes with JSON-array batch stdin |
Working-copy layout and the C# subset are documented in
cli/skill/SKILL.md (the agent onboarding surface) and the
spec. State lives in neo/.neo/state.json (gitignored): per-record base
content hashes — the compare-and-swap tokens — plus conflict bookkeeping.
Architecture notes
Convex-direct, fully typed: the CLI calls Convex with
api.*references and inferred types (cli/src/convex.ts), authenticated by a Convex JWT minted from the scoped device-flow session. Pushes go through the session-gatedserverProjectRecordOperations.commitFromSession(same per-change scope gates + CAS as the web's S2S commit). HTTP is used only for the auth plane and for content verbs that reuse Next-side write services. Record payloads are narrowed with the samesrc/modelsguards the web app uses.Round-trip invariant:
pull→pushis always a no-op. Enforced by the consumption-model partition (cli/src/schema/emit.ts): every record field is either expressed in C#, carried inExtraJson, or held as a volatile field in state and merged back at reconstruction.The parser (
cli/src/schema/parse.ts) is a hand-rolled recursive-descent parser for the constrained subset with file:line:column diagnostics.Server surfaces:
schema/document(read),schema/transactions(atomic CAS write),projectExportData.schemaSignal(websocket signal).Tests:
npx vitest run cli(round-trip corpus, parser, merge matrix).
