Skip to content

Agent-first toolchain: CLI, guest SDK and a home template - #10

Merged
Mathieu2301 merged 6 commits into
mainfrom
mathieu/agent-cli
Sep 14, 2026
Merged

Mathieu2301 merged 6 commits into
mainfrom
mathieu/agent-cli

Conversation

@Mathieu2301

Copy link
Copy Markdown
Member

Three packages that let a coding agent build, check and publish a home component without being a Miakapp developer.

@miakapp/cli

Builds, checks, publishes and rolls back. Git stays the user's — the tool never commits, never bundles and never invents a control-plane endpoint.

  • Closed RFC 0004 §13.2 client: capability, delivery, finalization, activation, plus the two reconciliation reads a lost response needs.
  • Publisher-side pre-check of the RFC 0002 §7.2 artifact rules, so module syntax, a dynamic import, a source map or an oversized program fails before an upload capability is spent.
  • One stable exit code and one stable failure kind per outcome, plus --json, so an agent decides without parsing prose. Exit 7 is unknown_outcome: the effect may already have committed, so the next step is a read, never a blind retry.
  • The Home Key is read from MIAKAPP_HOME_KEY only, never from an argument. A test asserts it appears on neither stdout nor stderr.

--expected-generation is required rather than discovered, because RFC 0004 §13.2 publishes no read for the current component pointer. Asking for the number beat inventing a route.

@miakapp/component

The guest half of the ABI 1 bridge. Shapes mirror component-runtime/src/runtime-broker.ts rather than a reading of RFC 0002 — the broker is the authority, and a disagreement is a bug in this package.

  • Coalesced render loop spaced to the thirty-per-second ABI rate, so a burst cannot terminate the instance.
  • Typed builders for the twelve semantic node types, with closed token enums and no way to emit a key or an undefined value the bridge would reject.
  • Staleness exposed rather than hidden, per RFC 0002 §12.2.
  • An unknown outcome stays distinct from a failure, so a component cannot retry a call the home may already have applied.
  • Local refusal of the operations the broker answers by terminating: publishing or calling while staged, suspended or inactive.

templates/home

A working home: coordinator, component, project file, 8 tests. The coordinator configuration is a pure function of its options, so the authorization that matters is tested without a relay or a network.

One test exists for an otherwise silent failure: a component receives the intersection of what it requests and what the coordinator grants, so a requirement in miakapp.yaml that stateAccess does not cover is not a publication error — the path simply never arrives.

Also fixes

canonicalHttpsUrl demanded byte-for-byte URL round-tripping, and URL re-serializes a bare origin with a trailing slash. Every origin-only control plane was rejected, including https://control.example.test, which is RFC 0004 §3's own example issuer.

Verification

bun run check:packages — 31 CLI tests, 38 component tests, 8 template tests, plus the offline artifact check on a real bundle. bun test at the root: 218 passing.

🤖 Generated with Claude Code

Mathieu2301 and others added 6 commits September 13, 2026 03:33
Adds packages/cli, the agent-first command surface for building, checking,
publishing and rolling back one home component. Git stays the user's: the
tool never commits, never bundles and never invents a control-plane endpoint.

- closed RFC 0004 §13.2 client: capability, delivery, finalization, activation,
  with the two reconciliation reads a lost response needs;
- publisher-side pre-check of the RFC 0002 §7.2 artifact rules, so module
  syntax, a dynamic import, a source map or an oversized program fails before
  an upload capability is spent;
- one stable exit code and one stable failure kind per outcome, plus --json,
  so an agent decides without parsing prose;
- the Home Key is read from MIAKAPP_HOME_KEY only, never from an argument.

--expected-generation is required rather than discovered: RFC 0004 §13.2
publishes no read for the current component pointer, and asking for the number
is better than guessing one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds packages/component, the typed guest half of the ABI 1 bridge. Shapes
mirror component-runtime/src/runtime-broker.ts rather than a reading of the
RFC: the broker is the authority, and a disagreement is a bug here.

- handshake, coalesced render loop and lifecycle, with commits spaced to the
  thirty-per-second ABI rate so a burst cannot terminate the instance;
- typed builders for the twelve semantic node types, with closed token enums
  and no way to emit a key or an undefined value the bridge would reject;
- state projection that exposes staleness instead of pretending cached values
  are current, per RFC 0002 §12.2;
- call correlation that keeps an unknown outcome distinct from a failure, so a
  component cannot retry a call the home may already have applied;
- local refusal of the operations the broker answers by terminating: publishing
  or calling while staged, suspended or inactive.

packages/component/examples/salon.ts bundles to a 11 KB classic Worker program
that miakapp check accepts, which exercises both packages end to end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A coordinator that owns the logic, a component that is the interface, and a
project file that ties them to a control plane. It drives one lamp and reports
one temperature; both are meant to be replaced.

The coordinator configuration is a pure function of its options, so the
authorization that matters is tested without a relay, a control plane or a
network: a non-owner is refused, a coordinator-originated call is refused, a
malformed argument is refused before the hardware is touched, and a hardware
failure is not reported as success.

One test exists for a failure that is otherwise silent. A component receives
the intersection of what it requests and what the coordinator grants, so a
requirement in miakapp.yaml that stateAccess does not cover is not a
publication error — the path simply never arrives and the interface renders a
hole. The correspondence is asserted instead.

The Miakapp dependencies use file: paths so the template stays verified inside
the repository; the README gives the three commands that replace them. Wired as
check:template, which rebuilds the SDK, the CLI and the guest SDK, reinstalls
from source and runs typecheck, bundle, tests and the offline artifact check.

Also fixes a real bug this template found. canonicalHttpsUrl demanded
byte-for-byte URL round-tripping, and `URL` re-serializes a bare origin with a
trailing slash. Every origin-only control plane was therefore rejected,
including `https://control.example.test`, which is RFC 0004 §3's own example
issuer. An empty path is now accepted in either spelling and nothing else is
relaxed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI was red on `templates/home/test/home.test.ts`: "Cannot find package
'miakapi'". The template is not a workspace member — it resolves `miakapi` and
`@miakapp/cli` through `file:` links that exist only after its own install — so
the root sweep could never run it. It reached it anyway because `bun test foo/`
is a substring filter, not a directory scope, and `test/` also selects
`templates/home/test/`. On Bun 1.4 the test then resolved `miakapi` by
self-reference and passed; on the 1.2.23 CI pins it did not.

Two halves, because either alone leaves the template unproven:

- the root sweep names its files as globs, so it runs exactly what the root
  install resolves and nothing from templates/;
- CI runs `check:packages`, which installs the template from scratch and runs
  its build, tests and `@miakapp/cli check`. That script already existed and
  nothing called it — the same rot the template was written to avoid.

`test/check-coverage.test.ts` pins both. It expands the sweep globs, walks the
tree for test files and manifests, follows the script chain, and reads the
workflow, so it fails when a test file escapes every check CI runs rather than
when someone forgets to update a list. Six negative controls, each verified red
then reverted: substring filters restored; a sweep directory dropped;
`check:packages` removed from the workflow; the template dropped from
`check:packages`; a new unlinked project with tests; a test file in a
subdirectory the globs miss.

Verified on Bun 1.2.23 with no template install, as CI runs it: `bun run check`
216/216, `bun run check:packages` 31 + 38 + 8 green and the template artifact
publishable.
`examples/salon.ts` imports the package by name, the way a consumer writes it,
so its types resolve through `exports` to `dist/index.d.ts`. The check ran
`typecheck` first, before `build` had produced that file, so from a clean tree
the example could not compile: TS2307, then two implicit `any` on the callbacks
it could no longer type.

Nothing caught it because nothing ran the script. It passed by hand only when a
`dist/` from an earlier build was still lying around. Now that CI runs
`check:packages` on a fresh checkout, the order has to be real: build, then
typecheck, then test.

The example keeps importing by package name rather than a relative path into
`src`, which is the point — it compiles against the surface consumers get.

Verified on Bun 1.2.23 from a clean tree (no `dist/` anywhere, no template
install): `bun run check` 216/216, `bun run check:packages` green through cli,
component and the template.
…ockfile

Running `check:packages` on a fresh checkout exposed two more ways the template
was never really exercised.

`bunx @miakapp/cli check` went to the registry and got a 404. The package is
`private: true`, so it exists nowhere to fetch — and the template already
declares it as a devDependency, which links `node_modules/.bin/miakapp`. The
scripts now call that binary, so they run the CLI this checkout built rather
than whatever a registry might one day answer. The READMEs keep `bunx` for the
first `init`, where there is no project to install into yet.

`templates/home/bun.lock` declared lockfileVersion 2, written by a newer Bun
than the 1.2.23 this repository pins. CI could not parse it: "Unknown lockfile
version", "Ignoring lockfile", then a fresh resolve and "Saved lockfile". A
lockfile that the pinned toolchain silently discards is worse than none, since
it still looks like a pin. Regenerated with 1.2.23 as version 1, which both
1.2.23 and 1.4 read, and `check:template` now installs `--frozen-lockfile` so
drift fails loudly instead of being rewritten in passing.

Three assertions added to test/check-coverage.test.ts, each verified red then
reverted: a template script fetching a private package through bunx; a lockfile
whose format differs from the root's; an install in a CI-run script that is not
frozen. Nine controls total on that file now.

Verified on Bun 1.2.23, clean tree: `bun run check` 219/219,
`bun run check:packages` green through cli, component and the template, with no
lockfile warning.
@Mathieu2301

Copy link
Copy Markdown
Member Author

Green — and the template is now actually exercised

This PR was red on templates/home/test/home.test.ts: Cannot find package 'miakapi'. Fixing it turned up three defects that all shared one cause — the template's own check existed and nothing ran it.

Defect Fix
1 The root sweep reached into templates/home, which the root install cannot resolve. bun test foo/ is a substring filter, not a directory scope, so test/ also selects templates/home/test/. On Bun 1.4 the test then resolved miakapi by self-reference and passed; on the 1.2.23 CI pins, it did not. The sweep names its files as globs. CI runs check:packages, which installs the template from scratch and runs its build, tests and miakapp check.
2 check:component typechecked examples/salon.ts — which imports by package name, as a consumer does — before build had produced dist/index.d.ts. It only ever passed on a stale dist/. Build, then typecheck, then test.
3 bunx @miakapp/cli fetched from the registry and got a 404: the package is private. And templates/home/bun.lock was lockfileVersion 2, which 1.2.23 cannot parse — CI logged Ignoring lockfile, resolved afresh and saved a new one. Call the miakapp binary the template's own install links. Lockfile regenerated as version 1; check:template installs --frozen-lockfile.

test/check-coverage.test.ts pins all of it. It derives rather than restates: it expands the sweep globs with Bun.Glob, walks the tree for test files, manifests and lockfiles, follows the bun run / bun --filter / cd chain, and reads the workflow. So it goes red when a test file escapes every check CI runs — not when someone forgets to update a list.

Nine negative controls, each verified red then reverted: substring filters restored · a sweep directory dropped · check:packages removed from the workflow · the template dropped from check:packages · a new unlinked project with tests · a test file in a subdirectory the globs miss · a template script bunx-ing the private CLI · a lockfile format differing from the root's · an unfrozen install in a CI-run script.

Two of the six original controls passed green on the first attempt, exactly as on #179 — the discovery had missed .ts files and root-level paths. Worth repeating: a coverage test has to be broken on purpose before it is worth anything.

Verified, on Bun 1.2.23 installed separately to match the pin, from a clean tree with no dist/ and no template install: bun run check 219/219, bun run check:packages green through cli (31), component (38) and the template (8), artifact publishable, no lockfile warning. Same result in CI on eb136c8.

@Mathieu2301

Copy link
Copy Markdown
Member Author

Note issue d'une répétition de la porte de sortie de l'atelier F jouée ce matin sur l'union locale de main + #10#14 (détail complet en commentaire de #14).

Un point concerne directement le templates/ de cette PR : templates/home/README.md prescrit, pour sortir le template du dépôt,

bun remove miakapi @miakapp/component @miakapp/cli
bun add miakapi @miakapp/component
bun add -d @miakapp/cli

Vérifié sur le registre :

  • @miakapp/component404, et private: true
  • @miakapp/cli404, et private: true
  • miakapi3.0.31, c'est-à-dire la V3

Les deux 404 sont bruyants, donc inoffensifs. Le troisième ne l'est pas : bun add miakapi réussit et installe la V3 alors que le SDK V4 d'ici est 4.0.0-alpha.0, non publié. Quelqu'un qui suit ce README obtient un template silencieusement câblé sur la génération précédente.

Je ne touche à rien : la correction dépend de la décision de publication (quels noms, quel tag, quand), et elle n'est pas de mon ressort. Mais tant que les paquets ne sont pas publiés, cette section du README promet un chemin qui n'existe pas.

@Mathieu2301
Mathieu2301 merged commit 77443ae into main Sep 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant