@adhd/apigen-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,146 +1,434 @@
1
1
  # @adhd/apigen-cli
2
2
 
3
- The user-facing CLI for **apigen** take any `.ts` file and expose its exports as an
4
- **MCP server, HTTP API (Fastify/Express), CLI, or JSON Schema**, with **zero changes to
5
- the source**. Functions are the single source of truth; everything is derived.
3
+ **apigen: code-first, polyglot API generation.**
6
4
 
7
- > This documents the **v1** (TypeScript-only) implementation that ships today. The
8
- > transport-neutral, polyglot **v2** standard is specified in
9
- > [`docs/apigen/SPEC.md`](../../../docs/apigen/SPEC.md) and is in active build-out.
5
+ Write ordinary functions in your language of choice. Get **type-safe, production-ready servers** in any protocol — MCP, HTTP (Fastify/Express), CLI, JSON Schema, Python Flask, or Python gRPC — with **zero framework boilerplate, zero annotations, and zero changes to your source**.
6
+
7
+ ```bash
8
+ npx @adhd/apigen-cli --help
9
+ ```
10
+
11
+ ## What you can build
12
+
13
+ apigen turns your functions into running servers. Here's what that means in practice:
14
+
15
+ **A polyglot microservice from one command.** Mount TypeScript and Python files behind a single port — Fastify, Express, Flask, and gRPC all sharing one HTTP/1.1 + HTTP/2 endpoint, with per-namespace health and partial-availability isolation.
16
+
17
+ **An AI tool server from a single file.** Extract your business logic as an MCP server over stdio, SSE, or streaming HTTP — ready for any MCP host (Claude Desktop, Cursor, custom agents).
18
+
19
+ **Generated, deployable projects.** Emit a complete, runnable server project to disk, complete with `package.json`, `tsconfig.json`, and workspace-linked dependencies. The generated code is type-checked and imports only the dependencies your functions actually use.
20
+
21
+ **Cross-language type fidelity.** Types that normally break in JSON — `Date`, `bigint`, `Decimal`, `Uint8Array`, discriminated unions, nominal classes — survive the wire with exact precision, and they mean the *same bytes* whether the function runs in TypeScript or Python.
22
+
23
+ **Pluggable middleware via composition.** Add health endpoints, per-operation logging, auth, or rate-limiting with `--use` plugins — never by editing your business logic.
24
+
25
+ ```ts
26
+ // This file. That's it. No decorators, no framework, no schema.
27
+ // apigen reads it and serves it as any protocol you ask for.
28
+ export async function greet(name: string): Promise<string> {
29
+ return `hello, ${name}`;
30
+ }
31
+ ```
32
+
33
+ ```bash
34
+ # → MCP server (AI agents consume your function as a tool)
35
+ npx @adhd/apigen-cli run --source greet.ts --type mcp
36
+
37
+ # → HTTP server on port 3000
38
+ npx @adhd/apigen-cli run --source greet.ts --type api-fastify --opt port=3000
39
+
40
+ # → CLI tool (interactive command from your function)
41
+ npx @adhd/apigen-cli run --source greet.ts --type cli
42
+ ```
43
+
44
+ The protocol is a deployment choice, never a rewrite.
10
45
 
11
46
  ---
12
47
 
13
- ## Install / launch
48
+ ## Install
14
49
 
15
- The CLI is not bundled to `dist` by default. Use whichever fits:
50
+ ### Via npm (published package)
16
51
 
17
52
  ```bash
18
- cd <repo-root>
53
+ npx @adhd/apigen-cli <command> # run without installing
54
+ npm install -g @adhd/apigen-cli # global install
55
+ apigen --help
56
+ ```
19
57
 
20
- # A) Dev — run the TypeScript entry directly (no build):
21
- alias apigen='npx tsx packages/apigen/cli/src/index.ts'
58
+ ### From source (this monorepo)
22
59
 
23
- # B) Bundled bin — build once, then run dist (bin name: apigen-cli):
60
+ ```bash
24
61
  npx nx build apigen-cli
25
- # → node packages/apigen/cli/dist/index.js
62
+ alias apigen='node dist/entrypoint/apigen-cli/index.js'
26
63
  ```
27
64
 
28
- **Commands:** `run`, `generate`, `run-registry`, `generate-registry`
29
- **`--type` (target plugin):** `mcp` · `api-fastify` · `api-express` · `cli` · `jsonschema`
30
- **Server knobs:** passed through repeatable `--opt key=value`.
65
+ ---
66
+
67
+ ## Quickstart
31
68
 
32
- A ready fixture lives at `packages/apigen/cli/src/test/fixtures/api.ts` (`getUser`, `sendEmail`),
33
- used in the examples below.
69
+ ### One function one server one curl
34
70
 
35
- ---
71
+ ```bash
72
+ # Create your source
73
+ echo 'export async function greet(name: string): Promise<string> {
74
+ return `hello, ${name}`;
75
+ }' > hello.ts
76
+
77
+ # Serve it live
78
+ npx @adhd/apigen-cli run --source hello.ts --type api-fastify --opt port=8080
36
79
 
37
- ## Run a live server `run`
80
+ # Call it arguments in {"data":{…}} envelope
81
+ curl -X POST http://localhost:8080/hello/greet \
82
+ -H 'content-type: application/json' \
83
+ -d '{"data":{"name":"ada"}}'
84
+ # → {"result":"hello, ada"}
85
+ ```
86
+
87
+ ### One source, every protocol
38
88
 
39
89
  ```bash
40
- # MCP over stdio (default transport)
41
- apigen run --source packages/apigen/cli/src/test/fixtures/api.ts --type mcp
90
+ # MCP server (stdio for AI agents)
91
+ npx @adhd/apigen-cli run --source hello.ts --type mcp
92
+
93
+ # MCP over SSE
94
+ npx @adhd/apigen-cli run --source hello.ts --type mcp --opt transport=sse --opt port=3000
42
95
 
43
- # MCP over HTTP transports
44
- apigen run --source .../api.ts --type mcp --opt transport=sse --opt port=3000
45
- apigen run --source .../api.ts --type mcp --opt transport=streaming-http --opt port=3000
96
+ # HTTP (Express)
97
+ npx @adhd/apigen-cli run --source hello.ts --type api-express --opt port=3001
46
98
 
47
- # HTTP API (Fastify or Express) — --namespace sets the route prefix segment
48
- apigen run --source packages/apigen/cli/src/test/fixtures/api.ts \
49
- --type api-fastify --namespace api --opt port=3000
99
+ # CLI tool (interactive)
100
+ npx @adhd/apigen-cli run --source hello.ts --type cli
101
+
102
+ # JSON Schema (generate to disk)
103
+ npx @adhd/apigen-cli generate --source hello.ts --type jsonschema --out-dir ./schema
50
104
  ```
51
105
 
52
- Call an HTTP server — **method must be uppercase `POST`**, arguments wrapped in
53
- `{"data":{…}}` (the request envelope), route is `/<namespace>/<fn>`:
106
+ ### Multi-language, one port
54
107
 
55
108
  ```bash
56
- curl -X POST http://127.0.0.1:3000/api/getUser \
57
- -H 'content-type: application/json' \
58
- -d '{"data":{"userId":"abc"}}'
59
- # {"id":"abc"}
109
+ # TypeScript + Python behind a single HTTP/gRPC front
110
+ npx @adhd/apigen-cli serve \
111
+ --source money.ts \
112
+ --source orders.py \
113
+ --port 8080
114
+
115
+ # Verify everything is healthy
116
+ curl http://localhost:8080/_meta/health
117
+ # → {"status":"ok","hosts":{"money":"ready","orders":"ready"}}
118
+ # A failed host degrades only its own namespace (503/UNAVAILABLE).
119
+ # The rest keep serving.
60
120
  ```
61
121
 
62
- ### `--opt` keys read by the server plugins
122
+ ### With middleware
63
123
 
64
- | key | targets | default | meaning |
65
- |---|---|---|---|
66
- | `transport` | `mcp` | `stdio` | `stdio` \| `sse` \| `streaming-http` |
67
- | `port` | `mcp` (http transports), `api-fastify`, `api-express` | `3000` | listen port |
68
- | `host` | `mcp` (http), `api-fastify`, `api-express` | `127.0.0.1` | bind host |
69
- | `routePrefix` | `api-fastify`, `api-express` | `""` | path prefix before `/<namespace>/<fn>` |
124
+ ```bash
125
+ npx @adhd/apigen-cli run --source hello.ts --type api-fastify --use health --use logger
126
+ # health GET /_meta/health (live health check)
127
+ # logger per-operation structured logging to stderr
128
+ ```
129
+
130
+ ---
131
+
132
+ ## What you get: the generated server
133
+
134
+ When you `generate`, apigen emits a **real, runnable project**:
135
+
136
+ ```
137
+ ./out/api/
138
+ ├── package.json # only the deps your code actually uses
139
+ ├── tsconfig.json # ready to compile
140
+ ├── node_modules/ # linked (with --link-workspace) or via npm install
141
+ ├── server.ts # the generated server entry point
142
+ └── routes.ts # generated route handlers
143
+ ```
70
144
 
71
- `mcp` over `stdio` keeps **stdout** clean for the JSON-RPC channel — all logs go to stderr.
145
+ ```bash
146
+ # Build output that runs standalone
147
+ npx @adhd/apigen-cli generate --source hello.ts --type api-fastify --out-dir ./out/api
148
+ cd ./out/api && npm install && npx tsx server.ts
149
+ ```
150
+
151
+ The generated server imports `dispatch`/`buildFnTable` from `@adhd/apigen-engine-runtime` — no inlined dispatch logic — so it behaves identically to `run`.
72
152
 
73
153
  ---
74
154
 
75
- ## Generate to disk — `generate`
155
+ ## Commands
156
+
157
+ ### `apigen run` — Serve a source file as a live server
158
+
159
+ Start a live server from TypeScript or Python source. Functions are imported at runtime via `tsx` and handed to the selected plugin.
160
+
161
+ ```bash
162
+ apigen run --source <path> --type <plugin-id> [options]
163
+ ```
164
+
165
+ | Flag | Description |
166
+ |------|-------------|
167
+ | `--source <path>` | Source file (`.ts`, `.py`) |
168
+ | `--type <plugin-id>` | Output plugin — see [Plugins](#plugins) |
169
+ | `--export <mode>` | Export selection: `"default"` \| `"<name>"` \| omit for named |
170
+ | `--tsconfig <path>` | Explicit tsconfig.json |
171
+ | `--namespace <name>` | Route prefix (default: tsconfig folder name) |
172
+ | `--opt <key>=<value>` | Plugin option (repeatable) |
173
+ | `--use <plugin>` | Middleware plugin (repeatable) |
174
+ | `--config <path>` | Projection-override config file |
175
+ | `--v2` | Use v2 unified orchestrator |
176
+
177
+ The server handles `SIGINT`/`SIGTERM` gracefully.
178
+
179
+ ### `apigen generate` — Generate server artifacts to disk
180
+
181
+ Extract TypeScript source and write generated files to an output directory. The output includes resolution scaffolding (`package.json`, `tsconfig.json`) so it runs standalone.
182
+
183
+ ```bash
184
+ apigen generate --source <path> --type <plugin-id> --out-dir <path> [options]
185
+ ```
186
+
187
+ | Flag | Description |
188
+ |------|-------------|
189
+ | `--source <path>` | TypeScript source file |
190
+ | `--type <plugin-id>` | Output plugin |
191
+ | `--out-dir <path>` | Output directory |
192
+ | `--export <mode>` | Export selection |
193
+ | `--tsconfig <path>` | Explicit tsconfig.json |
194
+ | `--namespace <name>` | Package namespace |
195
+ | `--opt <key>=<value>` | Plugin option (repeatable) |
196
+ | `--use <plugin>` | Middleware plugin (repeatable) |
197
+ | `--config <path>` | Projection-override config file |
198
+ | `--link-workspace` | Emit workspace-linked `node_modules` (for monorepo dev before publish) |
199
+ | `--v2` | Use v2 unified orchestrator |
76
200
 
77
201
  ```bash
78
- # MCP server source
79
- apigen generate --source packages/apigen/cli/src/test/fixtures/api.ts \
80
- --type mcp --out-dir /tmp/apigen-out
81
- node /tmp/apigen-out/server.ts # or: npx tsx /tmp/apigen-out/server.ts
202
+ # Generate a Fastify HTTP server
203
+ apigen generate --source hello.ts --type api-fastify --out-dir ./out/http
204
+ cd ./out/http && npm install && npx tsx server.ts
82
205
 
83
- apigen generate --source .../api.ts --type api-fastify --out-dir /tmp/out-http # routes.ts
84
- apigen generate --source .../api.ts --type cli --out-dir /tmp/out-cli # cli.ts
85
- apigen generate --source .../api.ts --type jsonschema --out-dir /tmp/out-schema # *.json
206
+ # Generate an MCP server project with workspace linking
207
+ apigen generate --source hello.ts --type mcp --out-dir ./out/mcp --link-workspace
208
+ node ./out/mcp/server.ts
86
209
  ```
87
210
 
88
- Generated servers import `dispatch`/`buildFnTable` from `@adhd/apigen-runtime` no inlined
89
- dispatch logic — so disk output behaves identically to `run`.
211
+ Per-surface dependencies are automatically patched into `package.json`: if your function uses `Decimal`, only then does `decimal.js` appear as a dependency.
212
+
213
+ ### `apigen serve` — Multi-source, multi-language front server
214
+
215
+ Mount many source files across languages behind a single HTTP/1.1 + gRPC (HTTP/2) port. This is a small distributed system in one command: each source runs as a child process supervised with health probes and partial-availability isolation.
216
+
217
+ ```bash
218
+ apigen serve --source <path> [--source ...] --port <port> [--mount <ns>=<plugin> ...]
219
+ ```
220
+
221
+ | Flag | Description |
222
+ |------|-------------|
223
+ | `--source <path>` | Source to mount (repeatable; `.ts` → api-fastify, `.py` → py-flask) |
224
+ | `--port <port>` | Front TCP port (HTTP + gRPC multiplexed) |
225
+ | `--mount <ns>=<plugin>` | Pin namespace to specific plugin (repeatable; e.g. `ledger=py-grpc`) |
226
+
227
+ **Namespace by default** is the source filename stem. So `--source money.ts` creates namespace `money` served via `api-fastify`. Override with `--mount orders=api-express`.
228
+
229
+ ```bash
230
+ # Mix TypeScript and Python, four frameworks, one port
231
+ apigen serve \
232
+ --source money.ts \
233
+ --source orders.ts \
234
+ --source billing.py \
235
+ --source ledger.py \
236
+ --port 8080 \
237
+ --mount orders=api-express \
238
+ --mount ledger=py-grpc
239
+ ```
240
+
241
+ **Architecture:**
242
+ - **Protocol demux** — A raw `net.Server` peeks the first 3 bytes. HTTP/2 prior-knowledge (h2c/gRPC) starts with `PRI`; everything else is HTTP/1.1. Both share one TCP port.
243
+ - **Spawn + supervise** — Each source starts as a child `apigen run` subprocess on a free loopback port. Children are tracked by PID.
244
+ - **Readiness probes** — HTTP hosts via `GET /_meta/health`, gRPC hosts via TCP connect. Both poll with 15s timeout.
245
+ - **Partial availability** — A dead child fails only its `/<ns>/*` routes (503 HTTP, UNAVAILABLE gRPC).
246
+ - **Orphan-free teardown** — `SIGINT`/`SIGTERM` sends `SIGTERM` then `SIGKILL` after 3s grace.
247
+ - **Python pre-provisioning** — The managed Python venv is set up before any child spawns, so first-time `pip install` doesn't eat the ready timeout.
248
+
249
+ ### `apigen run-registry` — Multi-package live server
250
+
251
+ Discover packages by nx tag and wire them into one live server.
252
+
253
+ ```bash
254
+ apigen run-registry --packages-dir <path> --type <plugin-id> [options]
255
+ ```
256
+
257
+ All discovered packages are passed to `plugin.run()` in one invocation — one server, all namespaces.
258
+
259
+ ### `apigen generate-registry` — Multi-package artifact generation
260
+
261
+ Discover packages by nx tag and generate artifacts for all in one pass.
262
+
263
+ ```bash
264
+ apigen generate-registry --packages-dir <path> --type <plugin-id> --out-dir <path> [options]
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Plugins
270
+
271
+ | ID | Language | `run` | `generate` | What it builds |
272
+ |----|----------|-------|------------|----------------|
273
+ | `mcp` | TS | ✓ | ✓ | **MCP server** — model-context-protocol AI tool server. Transports: stdio, SSE, streaming-http |
274
+ | `jsonschema` | TS | | ✓ | **JSON Schema** — per-operation input/output schema files |
275
+ | `api-fastify` | TS | ✓ | ✓ | **Fastify HTTP server** — high-performance, plugin-based HTTP |
276
+ | `api-express` | TS | ✓ | ✓ | **Express HTTP server** — familiar Node.js HTTP framework |
277
+ | `cli` / `cli-output` | TS | ✓ | ✓ | **Interactive CLI tool** — your functions become command-line commands |
278
+ | `py-flask` | Py | ✓ | ✓ | **Python Flask server** — Python HTTP via the Flask framework |
279
+ | `py-grpc` | Py | ✓ | ✓ | **Python gRPC server** — HTTP/2 gRPC with protobuf wire format |
280
+
281
+ Non-TS plugins (`py-flask`, `py-grpc`) bypass TypeScript compilation — they spawn a Python subprocess. All plugins use the same canonical wire format, so a `Decimal` is the same JSON string from Fastify, Flask, or gRPC.
90
282
 
91
283
  ---
92
284
 
93
- ## Multi-package — `run-registry` / `generate-registry`
285
+ ## Common flags
286
+
287
+ ### `--opt <key>=<value>` — Plugin options
288
+
289
+ Passed through to the target plugin. Recognized keys:
290
+
291
+ | Key | Applies to | Default | Meaning |
292
+ |-----|------------|---------|---------|
293
+ | `transport` | `mcp` | `stdio` | `stdio` \| `sse` \| `streaming-http` |
294
+ | `port` | `mcp` (HTTP), `api-fastify`, `api-express`, `py-flask` | `3000` | Listen port |
295
+ | `host` | All HTTP transports | `127.0.0.1` | Bind address |
296
+ | `routePrefix` | `api-fastify`, `api-express` | `""` | Path prefix before `/<namespace>/<fn>` |
297
+ | `http.verb.<id>=GET/POST` | All HTTP plugins | (derived) | Override HTTP verb per operation |
298
+
299
+ MCP over `stdio` keeps **stdout** clean for JSON-RPC — all logs go to stderr.
94
300
 
95
- Discover packages in a directory by **nx tag** and wire them into one surface:
301
+ ### `--use <plugin>` Middleware plugins
302
+
303
+ Compose cross-cutting behavior without editing your source code. Accepts:
304
+
305
+ - **Built-in slugs:** `health` — mounts `GET /_meta/health`; `logger` — per-operation structured logging
306
+ - **Package specifiers:** any npm package exporting a Plugin interface
307
+ - **Local paths:** filesystem path to a plugin module
96
308
 
97
309
  ```bash
98
- apigen run-registry --packages-dir packages/apigen/cli/src/test/fixtures/registry \
99
- --tag api --type mcp
100
- apigen generate-registry --packages-dir <dir> --tag api --type mcp --out-dir /tmp/reg-out
310
+ apigen run --source hello.ts --type api-fastify --use health --use logger
101
311
  ```
102
312
 
103
- `--tag` / `--exclude-tag` are repeatable; untagged exports stay out of the surface.
313
+ Multiple `--use` plugins compose in declaration order.
314
+
315
+ ### `--export <mode>` — Export selection
316
+
317
+ Controls which exports become API operations:
318
+
319
+ | Value | Behavior |
320
+ |-------|----------|
321
+ | _(omit)_ | Named exports (`export function f`, `export const f = …`) |
322
+ | `default` | Default-exported function or object |
323
+ | `<name>` | A named object whose properties become operations |
324
+
325
+ > **v1 limitation:** `export { x as y }` aliases and anonymous defaults mis-name routes in v1 (named by declaration identifier). v2 names by exported symbol. Fixed by the `--v2` flag.
326
+
327
+ ### `--v2` — Unified orchestrator
328
+
329
+ Activates the v2 detect→extract→merge→collision-check→generate/run pipeline. Uses a shared `ExtractionSession` (one ts-morph `Project` per tsconfig). Collision checks are a hard error (no silent last-writer-wins). v1 remains the default for backward compatibility.
330
+
331
+ ### `--config <path>` — Projection-override file
332
+
333
+ JSON file for out-of-source HTTP verb and naming overrides (Tenet 1 — source is never modified). CLI `--opt` flags override file values.
334
+
335
+ ### `--link-workspace` — Pre-publish bridge
336
+
337
+ When generating output that depends on `@adhd/apigen-*` packages not yet published, emits a workspace-linked `node_modules` so the output runs in place. For published consumers: omit this flag and run `npm install` instead.
338
+
339
+ ---
340
+
341
+ ## Middleware system (plugins via `--use`)
342
+
343
+ apigen's architecture supports **layer and mount plugins** that compose at runtime:
344
+
345
+ - **Layer plugins** intercept the request/response cycle — validate, log, rate-limit, add auth context.
346
+ - **Mount plugins** add routes — health endpoints, metrics, status pages.
347
+
348
+ Built-in plugins:
349
+
350
+ | Plugin | Type | What it does |
351
+ |--------|------|-------------|
352
+ | `health` | Mount | Adds `GET /_meta/health` with aggregate status |
353
+ | `logger` | Layer | Per-operation pino logging to stderr |
354
+
355
+ Write your own by exporting a Plugin object (with `capabilities` field) from any npm package or local file.
356
+
357
+ ---
358
+
359
+ ## Type safety and rich types
360
+
361
+ apigen infers JSON Schema from your TypeScript types. Types that plain JSON would mangle are carried through a canonical wire format:
362
+
363
+ | Type | Wire encoding | Survives? |
364
+ |------|--------------|-----------|
365
+ | `Date` | RFC3339 UTC string | ✓ |
366
+ | `bigint` / `int64` | Decimal string | ✓ (no precision loss past 2⁵³) |
367
+ | `Decimal` | Decimal string | ✓ (exact, never a binary float) |
368
+ | `Uint8Array` / bytes | Base64 | ✓ |
369
+ | Discriminated unions (`Dog \| Cat`) | Tagged variant | ✓ |
370
+ | Nominal classes | Reconstructed instance | ✓ |
371
+ | `readonly T[]` | Preserved element type | ✓ |
372
+
373
+ Cross-language fidelity: a `Decimal("123.456")` produces the same JSON string from TypeScript, Python Flask, and Python gRPC — verified by the conformance suite.
104
374
 
105
375
  ---
106
376
 
107
- ## Flags
377
+ ## Logging
108
378
 
109
- ### Export shapes`--export`
110
- - *(omit)* — named exports (`export function f`, `export const f = …`).
111
- - `--export default` — a default-exported function/object.
112
- - `--export <objName>` — a named object whose properties are the operations.
379
+ All logs go to **stderr** only stdout is protocol-clean for MCP stdio transport.
113
380
 
114
- > **Known v1 limitations (tracked, fixed by v2):** `export { x as y }` aliases,
115
- > anonymous default exports, and CJS-source files mis-name routes because v1 names by the
116
- > *declaration* identifier rather than the *exported symbol*. v2 (`docs/apigen/SPEC.md`)
117
- > names by exported symbol and closes these.
381
+ | Flag | Env var | Values | Default |
382
+ |------|---------|--------|---------|
383
+ | `--log-level <level>` | `APIGEN_LOG_LEVEL` | `trace` \| `debug` \| `info` \| `warn` \| `error` \| `fatal` \| `silent` | `info` |
384
+ | `--log-format <format>` | `APIGEN_LOG_FORMAT` | `json` \| `pretty` | plain |
385
+ | `--log-file <path>` | `APIGEN_LOG_FILE` | file path | stderr |
118
386
 
119
- ### Resolution
120
- - `--tsconfig <path>` — explicit tsconfig; otherwise the nearest one, else a builtin default.
121
- - `--namespace <name>` — the package id / route prefix segment. Defaults to the tsconfig
122
- folder name, falling back to the source file's folder.
387
+ ---
123
388
 
124
- ### Plugin options
125
- - `--opt key=value` — repeatable; forwarded to the target plugin (see table above).
389
+ ## Fail-fast guards
126
390
 
127
- ### Logging (stderr stdout stays protocol-clean)
128
- - `--log-level trace|debug|info|warn|error|fatal|silent` (env `APIGEN_LOG_LEVEL`)
129
- - `--log-format json|pretty` (env `APIGEN_LOG_FORMAT`)
130
- - `--log-file <path>` — write logs to a file instead of stderr (env `APIGEN_LOG_FILE`)
391
+ The CLI catches common mistakes early with actionable messages:
131
392
 
132
- Lifecycle logged: compile server start host/port route/tool list → per-requestshutdown.
393
+ - **0 functions found:** `--source` points to generated output or a type-only file `"0 functions found — looks like generated output or the wrong source file."`
394
+ - **Missing `decimal.js`:** Your function uses `Decimal` but the package isn't installed → `"function quote takes a Decimal; install \`decimal.js\`."`
395
+ - **Non-TS plugin bypass:** `--type py-flask` with `.py` source skips the TS pipeline (which would crash on a non-TS file)
133
396
 
134
397
  ---
135
398
 
136
399
  ## Nx integration
137
400
 
138
- - Cache-aware generate target: `@adhd/apigen-nx:generate` (see [`../nx`](../nx)).
139
- - Scaffold a new output plugin: `nx g @adhd/apigen-nx:plugin <name>`.
401
+ - **Cache-aware generate target:** `@adhd/apigen-generator-nx:generate` run `apigen generate` as a cached Nx target. See [`packages/apigen/apigen-generator-nx`](../../packages/apigen/apigen-generator-nx).
402
+ - **Scaffold a new output plugin:** `nx g @adhd/apigen-generator-nx:plugin <name>` — generates a buildable plugin package implementing the `OutputPlugin` interface. Use this to add a new codegen target (e.g., `nestjs`, `hono`, `spring`) without forking apigen.
403
+ - **Scaffold a new host language:** `nx g @adhd/apigen-generator-nx:host <name>` — generates a host-language harness with a "red-by-construction" conformance manifest. Use this to add a new runtime (e.g., Rust, Go, Java) to the polyglot serve topology.
404
+
405
+ ---
406
+
407
+ ## Architecture
408
+
409
+ apigen works in three stages:
410
+
411
+ 1. **Extract** — Parse your source (TypeScript via ts-morph), infer JSON Schema for every parameter and return type, detect rich types (Date, Decimal, bigint, etc.).
412
+ 2. **Compose** — Merge schemas across packages, run collision checks (no silent name shadowing).
413
+ 3. **Project** — Hand the composed descriptor to the selected output plugin for live serving or code generation.
140
414
 
141
- ## Develop
415
+ The v2 architecture (`docs/apigen/SPEC.md`) extends this to a polyglot detect → extract → merge → project pipeline with a canonical JSON descriptor as the neutral contract. Python plugins already ship; Rust/Go/Java host languages are designed in the SPEC.
416
+
417
+ ---
418
+
419
+ ## Developing
142
420
 
143
421
  ```bash
144
- npx nx build apigen-cli # bundle
145
- npx nx test apigen-cli # Vitest (unit + integration)
422
+ npx nx build apigen-cli # Bundle (Vite)
423
+ npx nx test apigen-cli # Test (Vitest unit + integration)
424
+ npx nx run apigen-cli:bench # Benchmarks (--expose-gc)
146
425
  ```
426
+
427
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
428
+
429
+ ---
430
+
431
+ ## Spec
432
+
433
+ - **v2 Canonical Spec:** [`docs/apigen/SPEC.md`](../../docs/apigen/SPEC.md) — architecture, canonical descriptor, polyglot topology
434
+ - **Guided Tour:** [`docs/apigen/DEMO.md`](../../docs/apigen/DEMO.md) — runnable walk-through from zero