@alpacakit/channels 0.1.0-beta.19

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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +772 -0
  3. package/dist/binding-compiler.d.ts +29 -0
  4. package/dist/binding-compiler.d.ts.map +1 -0
  5. package/dist/binding-compiler.js +40 -0
  6. package/dist/cli-coercion.d.ts +5 -0
  7. package/dist/cli-coercion.d.ts.map +1 -0
  8. package/dist/cli-coercion.js +51 -0
  9. package/dist/cli-compiler.d.ts +46 -0
  10. package/dist/cli-compiler.d.ts.map +1 -0
  11. package/dist/cli-compiler.js +213 -0
  12. package/dist/cli-executor.d.ts +25 -0
  13. package/dist/cli-executor.d.ts.map +1 -0
  14. package/dist/cli-executor.js +44 -0
  15. package/dist/cli-help-projection.d.ts +13 -0
  16. package/dist/cli-help-projection.d.ts.map +1 -0
  17. package/dist/cli-help-projection.js +159 -0
  18. package/dist/cli-model.d.ts +240 -0
  19. package/dist/cli-model.d.ts.map +1 -0
  20. package/dist/cli-model.js +89 -0
  21. package/dist/cli-presentation.d.ts +23 -0
  22. package/dist/cli-presentation.d.ts.map +1 -0
  23. package/dist/cli-presentation.js +265 -0
  24. package/dist/cli-schema.d.ts +4 -0
  25. package/dist/cli-schema.d.ts.map +1 -0
  26. package/dist/cli-schema.js +7 -0
  27. package/dist/cli-tree.d.ts +4 -0
  28. package/dist/cli-tree.d.ts.map +1 -0
  29. package/dist/cli-tree.js +421 -0
  30. package/dist/cli-values.d.ts +24 -0
  31. package/dist/cli-values.d.ts.map +1 -0
  32. package/dist/cli-values.js +40 -0
  33. package/dist/cli.d.ts +6 -0
  34. package/dist/cli.d.ts.map +1 -0
  35. package/dist/cli.js +5 -0
  36. package/dist/codecs.d.ts +25 -0
  37. package/dist/codecs.d.ts.map +1 -0
  38. package/dist/codecs.js +74 -0
  39. package/dist/contract.d.ts +12 -0
  40. package/dist/contract.d.ts.map +1 -0
  41. package/dist/contract.js +28 -0
  42. package/dist/factory.d.ts +16 -0
  43. package/dist/factory.d.ts.map +1 -0
  44. package/dist/factory.js +188 -0
  45. package/dist/index.d.ts +9 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +8 -0
  48. package/dist/internal.d.ts +3 -0
  49. package/dist/internal.d.ts.map +1 -0
  50. package/dist/internal.js +3 -0
  51. package/dist/introspection.d.ts +40 -0
  52. package/dist/introspection.d.ts.map +1 -0
  53. package/dist/introspection.js +56 -0
  54. package/dist/mcp.d.ts +26 -0
  55. package/dist/mcp.d.ts.map +1 -0
  56. package/dist/mcp.js +84 -0
  57. package/dist/model.d.ts +166 -0
  58. package/dist/model.d.ts.map +1 -0
  59. package/dist/model.js +22 -0
  60. package/dist/runtime.d.ts +47 -0
  61. package/dist/runtime.d.ts.map +1 -0
  62. package/dist/runtime.js +140 -0
  63. package/dist/schema.d.ts +14 -0
  64. package/dist/schema.d.ts.map +1 -0
  65. package/dist/schema.js +70 -0
  66. package/dist/tree-internal.d.ts +14 -0
  67. package/dist/tree-internal.d.ts.map +1 -0
  68. package/dist/tree-internal.js +20 -0
  69. package/dist/values.d.ts +32 -0
  70. package/dist/values.d.ts.map +1 -0
  71. package/dist/values.js +30 -0
  72. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,772 @@
1
+ # @alpacakit/channels
2
+
3
+ Define an entry once, bind it to CLI and MCP, and use the same typed endpoint
4
+ for argument parsing, help, MCP schemas, input decoding, and projection.
5
+
6
+ HTTP routing is not currently provided. The supported entry-tree channels are
7
+ CLI and MCP.
8
+
9
+ ## The mental model
10
+
11
+ You only need four concepts:
12
+
13
+ ```text
14
+ Entry = what the command accepts and what it projects
15
+ Binding = how one channel supplies that input
16
+ Endpoint = the Entry and its Bindings bundled together
17
+ Tree = where the Endpoint lives in the command hierarchy
18
+ ```
19
+
20
+ A useful shorthand is:
21
+
22
+ ```text
23
+ What? -> Entry
24
+ How? -> Binding
25
+ Where? -> Tree
26
+ Bundle them -> Endpoint
27
+ ```
28
+
29
+ Suppose an application exposes this CLI command:
30
+
31
+ ```console
32
+ my-app hint "entry tree" --limit 5
33
+ ```
34
+
35
+ The same operation is exposed to MCP with this JSON input:
36
+
37
+ ```json
38
+ {
39
+ "query": "entry tree",
40
+ "limit": 5
41
+ }
42
+ ```
43
+
44
+ The definitions have this structure:
45
+
46
+ ```text
47
+ ┌────────────────────────────────────────┐
48
+ │ Entry: HintEntry │
49
+ │ │
50
+ │ query: required string │
51
+ │ limit: optional integer, default 20 │
52
+ │ │
53
+ │ project the values into a HintRequest │
54
+ └───────────────────┬────────────────────┘
55
+
56
+ │ attach channel input mappings
57
+
58
+ ┌────────────────────────────────────────┐
59
+ │ Endpoint: HintEndpoint │
60
+ │ │
61
+ │ ┌────────────────────────────────────┐ │
62
+ │ │ CLI Binding │ │
63
+ │ │ query -> positional argument │ │
64
+ │ │ limit -> --limit / -n │ │
65
+ │ └────────────────────────────────────┘ │
66
+ │ │
67
+ │ ┌────────────────────────────────────┐ │
68
+ │ │ MCP Binding │ │
69
+ │ │ query -> JSON property "query" │ │
70
+ │ │ limit -> JSON property "limit" │ │
71
+ │ └────────────────────────────────────┘ │
72
+ └───────────────────┬────────────────────┘
73
+
74
+ │ place the endpoint at an address
75
+
76
+ ┌────────────────────────────────────────┐
77
+ │ Tree │
78
+ │ │
79
+ │ my-app │
80
+ │ └── hint │
81
+ │ └── HintEndpoint │
82
+ └────────────────────────────────────────┘
83
+ ```
84
+
85
+ The important rule is that value types, validation, requiredness, defaults, and
86
+ projection are declared only on the Entry. A Binding only says how a channel
87
+ supplies a parameter. A Tree only says where an Endpoint lives.
88
+
89
+ The following sections build that example from top to bottom.
90
+
91
+ ## 1. Define what the command does: Entry
92
+
93
+ An Entry answers four questions:
94
+
95
+ ```text
96
+ What values does this operation accept?
97
+
98
+ How are those values validated?
99
+
100
+ Which defaults are applied?
101
+
102
+ What application value is produced?
103
+ ```
104
+
105
+ ```ts
106
+ import {
107
+ codecs,
108
+ defineEntry,
109
+ defineParameter,
110
+ optional,
111
+ required,
112
+ } from "@alpacakit/channels";
113
+
114
+ const HintEntry = defineEntry({
115
+ name: "hint",
116
+
117
+ parameters: [
118
+ defineParameter({
119
+ name: "query",
120
+ codec: codecs.string(),
121
+ requirement: required(),
122
+ description: "Text to search for",
123
+ }),
124
+
125
+ defineParameter({
126
+ name: "limit",
127
+ codec: codecs.positiveInteger({ max: 100 }),
128
+ requirement: optional({ default: 20 }),
129
+ description: "Maximum number of hints",
130
+ }),
131
+ ],
132
+
133
+ project: ({ values }) => ({
134
+ query: values.query,
135
+ limit: values.limit,
136
+ }),
137
+ });
138
+ ```
139
+
140
+ In plain English, this Entry means:
141
+
142
+ ```text
143
+ hint
144
+
145
+ Input:
146
+ query
147
+ - string
148
+ - required
149
+
150
+ limit
151
+ - positive integer
152
+ - at most 100
153
+ - defaults to 20
154
+
155
+ Projection:
156
+ {
157
+ query: string,
158
+ limit: number
159
+ }
160
+ ```
161
+
162
+ There is no CLI or MCP logic here. The Entry describes the operation itself.
163
+
164
+ Because `limit` has a default, `values.limit` is typed as `number`, not
165
+ `number | undefined`. Only optional parameters without a default remain
166
+ optional in `values`.
167
+
168
+ ## 2. Define how each channel supplies input: Bindings
169
+
170
+ A CLI Binding maps the Entry parameters to argv syntax:
171
+
172
+ ```ts
173
+ import { bindCliEntry } from "@alpacakit/channels/cli";
174
+
175
+ const HintCliBinding = bindCliEntry({
176
+ parameters: [
177
+ { kind: "positional", parameterName: "query" },
178
+ { kind: "option", parameterName: "limit", flags: ["--limit", "-n"] },
179
+ ],
180
+ });
181
+ ```
182
+
183
+ A binding row only needs `kind` and `parameterName`. The rest is derived from
184
+ the parameter name and only stated when you want to override it:
185
+
186
+ - `query` needs no `valueName` (help shows `QUERY`) and no `variadic` (defaults
187
+ to `false`).
188
+ - `limit` would default to the single flag `--limit`; here `flags` is given
189
+ only to add the `-n` alias.
190
+
191
+ The mapping is:
192
+
193
+ ```text
194
+ my-app hint "entry tree" --limit 5
195
+ └────┬────┘ └───┬───┘
196
+ │ │
197
+ ▼ ▼
198
+ query limit
199
+ ```
200
+
201
+ Do not repeat `query`'s string codec or `limit`'s default in the Binding. Those
202
+ facts already belong to the Entry.
203
+
204
+ An MCP Binding selects the Entry parameters exposed as tool input:
205
+
206
+ ```ts
207
+ import { bindMcpEntry } from "@alpacakit/channels/mcp";
208
+
209
+ const HintMcpBinding = bindMcpEntry("query", "limit");
210
+ ```
211
+
212
+ The mapping is:
213
+
214
+ ```text
215
+ {
216
+ "query": "entry tree",
217
+ "limit": 5
218
+ }
219
+ │ │
220
+ ▼ ▼
221
+ query limit
222
+ ```
223
+
224
+ ## 3. Bundle the Entry and Bindings: Endpoint
225
+
226
+ An Endpoint is the public execution identity for the operation:
227
+
228
+ ```ts
229
+ import { defineEntryEndpoint } from "@alpacakit/channels";
230
+
231
+ const HintEndpoint = defineEntryEndpoint(
232
+ HintEntry,
233
+ HintCliBinding,
234
+ HintMcpBinding,
235
+ );
236
+ ```
237
+
238
+ Its structure is:
239
+
240
+ ```text
241
+ HintEndpoint
242
+
243
+ ├── operation
244
+ │ └── HintEntry
245
+
246
+ ├── CLI input mapping
247
+ │ ├── query is positional
248
+ │ └── limit is --limit / -n
249
+
250
+ └── MCP input mapping
251
+ ├── expose query
252
+ └── expose limit
253
+ ```
254
+
255
+ Keep the object returned by `defineEntryEndpoint()`. CLI result narrowing and
256
+ all MCP runtime APIs use endpoint identity. Do not reconstruct an equivalent
257
+ Endpoint later.
258
+
259
+ ## 4. Give the Endpoint an address: Tree
260
+
261
+ Place the Endpoint under the `hint` command:
262
+
263
+ ```ts
264
+ import {
265
+ defineEntryNode,
266
+ defineEntryTree,
267
+ } from "@alpacakit/channels";
268
+
269
+ const CommandTree = defineEntryTree(
270
+ defineEntryNode({
271
+ name: "my-app",
272
+ description: "My application",
273
+
274
+ children: [
275
+ defineEntryNode({
276
+ name: "hint",
277
+ description: "Find implementation hints",
278
+ endpoints: [HintEndpoint],
279
+ }),
280
+ ],
281
+ }),
282
+ );
283
+ ```
284
+
285
+ The resulting hierarchy is:
286
+
287
+ ```text
288
+ CommandTree
289
+
290
+ my-app <- root surface name
291
+
292
+ └── hint <- token that appears in argv
293
+
294
+ └── HintEndpoint <- operation executed here
295
+ ```
296
+
297
+ The root name is the program or surface name. It is not included in the argv
298
+ passed to the router.
299
+
300
+ Independent modules can contribute canonical nodes and merge them without an
301
+ intermediate route model:
302
+
303
+ ```ts
304
+ const children = mergeEntryTreeNodes([
305
+ ...configCommandNodes,
306
+ ...integrationCommandNodes,
307
+ ]);
308
+ ```
309
+
310
+ Nodes with the same name are merged recursively. Their descriptions and
311
+ channel segment overrides must agree; identical endpoint objects are
312
+ deduplicated by identity.
313
+
314
+ An endpoint placed directly on the root is the default command:
315
+
316
+ ```text
317
+ my-app
318
+ ├── DefaultEndpoint <- runs for []
319
+ └── hint
320
+ └── HintEndpoint <- runs for ["hint", ...]
321
+ ```
322
+
323
+ ## 5. Execute it from CLI
324
+
325
+ ```ts
326
+ import {
327
+ createCliRouter,
328
+ isCliResolvedEndpoint,
329
+ } from "@alpacakit/channels/cli";
330
+
331
+ const router = createCliRouter(CommandTree);
332
+
333
+ const result = router.resolve([
334
+ "hint",
335
+ "entry tree",
336
+ "--limit",
337
+ "5",
338
+ ]);
339
+
340
+ if (isCliResolvedEndpoint(result, HintEndpoint)) {
341
+ console.log(result.projection);
342
+ }
343
+ ```
344
+
345
+ The typed projection is:
346
+
347
+ ```ts
348
+ {
349
+ query: "entry tree",
350
+ limit: 5,
351
+ }
352
+ ```
353
+
354
+ At runtime the data flows through these stages:
355
+
356
+ ```text
357
+ CLI argv
358
+
359
+ │ ["hint", "entry tree", "--limit", "5"]
360
+
361
+ CLI Router
362
+
363
+ │ find HintEndpoint from the Tree
364
+
365
+ CLI Binding
366
+
367
+ │ query = "entry tree"
368
+ │ limit = "5"
369
+
370
+ Shared Decoder
371
+
372
+ │ validate query as a string
373
+ │ coerce and validate limit as an integer
374
+
375
+ HintEntry.project()
376
+
377
+
378
+ {
379
+ query: "entry tree",
380
+ limit: 5
381
+ }
382
+ ```
383
+
384
+ A stored Tree may contain many differently typed entries, so its endpoints are
385
+ deliberately type-erased. `isCliResolvedEndpoint()` checks endpoint identity and
386
+ restores the exact projection type for the selected Endpoint.
387
+
388
+ The same router also exposes a channel-derived help surface:
389
+
390
+ ```ts
391
+ const help = router.help;
392
+ ```
393
+
394
+ For applications with many endpoints, an optional typed registry replaces a
395
+ manual identity-check chain:
396
+
397
+ ```ts
398
+ const executor = createCliExecutor(router, [
399
+ handleCliEndpoint(HintEndpoint, ({ projection }) => runHint(projection)),
400
+ ]);
401
+ ```
402
+
403
+ The registry rejects duplicate, missing, and out-of-router endpoints when it is
404
+ created. Handlers, output policy, and process exit behavior remain outside the
405
+ Entry definition.
406
+
407
+ ## 6. Execute the same Endpoint from MCP
408
+
409
+ Compile the Endpoint once, then reuse the handle for both registration and
410
+ per-invocation decoding:
411
+
412
+ ```ts
413
+ import {
414
+ createMcpEndpoint,
415
+ isMcpResolved,
416
+ } from "@alpacakit/channels/mcp";
417
+
418
+ const HintMcpEndpoint = createMcpEndpoint(HintEndpoint);
419
+ ```
420
+
421
+ `createMcpEndpoint()` validates the MCP binding once and returns a handle:
422
+
423
+ ```text
424
+ HintMcpEndpoint
425
+
426
+ ├── inputSchema <- raw Zod object shape for tool registration
427
+
428
+ └── parse(raw) <- decode + project one invocation
429
+ ```
430
+
431
+ `inputSchema` is a raw Zod object shape suitable for MCP tool registration. It
432
+ contains descriptions, validation, and requiredness derived from `HintEntry`.
433
+
434
+ Decode and project an MCP invocation through the same handle:
435
+
436
+ ```ts
437
+ const result = HintMcpEndpoint.parse({
438
+ query: "entry tree",
439
+ limit: 5,
440
+ });
441
+
442
+ if (isMcpResolved(result)) {
443
+ console.log(result.projection);
444
+ }
445
+ ```
446
+
447
+ Create the handle once (typically at startup) and reuse it for every request.
448
+ The binding is compiled a single time, so `parse()` does no per-invocation
449
+ recompilation, and an invalid binding throws `EntryTreeDefinitionError`
450
+ eagerly — exactly like `createCliRouter()`.
451
+
452
+ The MCP runtime path is:
453
+
454
+ ```text
455
+ MCP JSON
456
+
457
+ │ { query: "entry tree", limit: 5 }
458
+
459
+ HintEndpoint
460
+
461
+
462
+ MCP Binding
463
+
464
+ │ select query and limit
465
+
466
+ Shared Decoder
467
+
468
+ │ validate both values
469
+
470
+ HintEntry.project()
471
+
472
+
473
+ {
474
+ query: "entry tree",
475
+ limit: 5
476
+ }
477
+ ```
478
+
479
+ CLI and MCP have different inputs, but they converge on the same decoder,
480
+ defaults, and projection:
481
+
482
+ ```text
483
+ CLI argv ──> CLI Binding ──┐
484
+
485
+
486
+ HintEndpoint
487
+
488
+
489
+ Shared Decoder
490
+
491
+
492
+ HintEntry.project()
493
+
494
+
495
+ same projection
496
+
497
+
498
+ MCP JSON ──> MCP Binding ──┘
499
+ ```
500
+
501
+ If `limit` is omitted from either channel, both produce the Entry default:
502
+
503
+ ```ts
504
+ {
505
+ query: "entry tree",
506
+ limit: 20,
507
+ }
508
+ ```
509
+
510
+ ## Where each decision belongs
511
+
512
+ When adding a command, ask these questions in order:
513
+
514
+ | Question | Definition |
515
+ | --- | --- |
516
+ | What values does the operation need? | Entry parameters |
517
+ | How are they validated and defaulted? | Entry codecs and requirements |
518
+ | What application value should be produced? | Entry `project()` |
519
+ | How does CLI supply each value? | CLI Binding |
520
+ | Which values does MCP expose? | MCP Binding |
521
+ | What object ties the operation and channels together? | Endpoint |
522
+ | Under which command path does it live? | Tree |
523
+
524
+ Do not put CLI flags, MCP property selection, or command-tree placement on the
525
+ Entry. Do not repeat codecs, requiredness, or defaults in Bindings.
526
+
527
+ ## Suggested application file layout
528
+
529
+ For a larger application, separating these responsibilities keeps the model
530
+ easy to navigate:
531
+
532
+ ```text
533
+ src/
534
+ ├── hint-entry.ts
535
+ │ └── HintEntry
536
+ │ parameters, codecs, defaults, projection
537
+
538
+ ├── hint-endpoint.ts
539
+ │ └── HintEndpoint
540
+ │ CLI and MCP Bindings
541
+
542
+ ├── command-tree.ts
543
+ │ └── CommandTree
544
+ │ command hierarchy
545
+
546
+ ├── cli.ts
547
+ │ └── createCliRouter(CommandTree)
548
+
549
+ └── mcp.ts
550
+ └── createMcpEndpoint(HintEndpoint)
551
+ ```
552
+
553
+ ## Package entries
554
+
555
+ The neutral model and channel adapters are intentionally separate imports:
556
+
557
+ | Import | Purpose |
558
+ | --- | --- |
559
+ | `@alpacakit/channels` | Parameters, codecs, entries, output contracts, and entry-tree builders |
560
+ | `@alpacakit/channels/cli` | CLI bindings/coercion, router, typed executor, help traversal, and text renderers |
561
+ | `@alpacakit/channels/mcp` | MCP binding, the compiled endpoint handle, output schema projection, and the resolved-result guard |
562
+
563
+ `zod` is a peer dependency. In this workspace, depend on the package with
564
+ `workspace:*` and use the workspace Zod version.
565
+
566
+ ## Parameter and codec reference
567
+
568
+ Built-in codecs are:
569
+
570
+ - `codecs.string()`
571
+ - `codecs.stringList()`
572
+ - `codecs.boolean()`
573
+ - `codecs.positiveInteger({ max })`
574
+ - `codecs.stringEnum(values)`
575
+ - `codecs.stringEnumList(values)`
576
+
577
+ Use `required()` for required input, `optional()` for an optional value without
578
+ a default, and `optional({ default })` for a materialized default.
579
+
580
+ Defaults belong to the Entry, not a channel. They are applied even when an
581
+ optional parameter is not exposed by the selected channel. A required parameter
582
+ must be reachable through every channel binding on that Endpoint.
583
+
584
+ `codecs.define({ schema, shape })` is available for advanced codecs. Its shape
585
+ is the CLI representation contract and must match the schema's value type.
586
+ Arrays are limited to supported scalar element shapes.
587
+
588
+ ## CLI reference
589
+
590
+ Use `overrideCliSegment()` when the CLI spelling differs from the logical node
591
+ name:
592
+
593
+ ```ts
594
+ import { overrideCliSegment } from "@alpacakit/channels/cli";
595
+
596
+ defineEntryNode({
597
+ name: "hint",
598
+ description: "Find implementation hints",
599
+ segments: [overrideCliSegment({ token: "h", aliases: ["hi"] })],
600
+ endpoints: [HintEndpoint],
601
+ });
602
+ ```
603
+
604
+ CLI binding and routing rules:
605
+
606
+ - An option with no `flags` defaults to a single `--<kebab-case parameter
607
+ name>` long flag. Provide `flags` only to add aliases or a short flag.
608
+ - A value-taking option's `valueName` defaults to the upper snake-cased
609
+ parameter name. Boolean flags reject `valueName` because they take no value.
610
+ - A positional's `valueName` defaults to the upper snake-cased parameter name,
611
+ and `variadic` defaults to `false`. State either only to override it.
612
+ - Options use reachable `-x` or `--long` flags. Requiredness comes from the
613
+ Entry parameter, not the Binding.
614
+ - A defaulted boolean option is a flag by default. `negated: true` adds its
615
+ generated `--no-...` long flags.
616
+ - A boolean without a default accepts an explicit value unless `booleanMode`
617
+ selects another supported behavior.
618
+ - Array options may be repeated. `repeatMode: "comma_or_repeat"` also accepts
619
+ comma-separated values.
620
+ - A variadic positional requires an array codec and must be the final
621
+ positional.
622
+ - An executable node cannot have both positional arguments and child commands.
623
+ - `--` forces all remaining tokens to be parsed as positionals.
624
+ - A matching child command wins over an executable parent.
625
+ - `coercion: "json"` parses strict JSON before Entry validation.
626
+ `coercion: "json_or_string"` preserves malformed JSON as text. Final value
627
+ validation always belongs to the Entry codec.
628
+
629
+ `createCliRouter()` compiles and validates the complete CLI surface. Invalid
630
+ definitions throw `EntryTreeDefinitionError` at construction time.
631
+
632
+ The router's `help` property contains the compiled hierarchy, aliases,
633
+ render-ready option groups, positionals, requiredness, defaults, repeat modes,
634
+ and generated negative flags. Authored option-section names are resolved during
635
+ projection, so renderers consume `optionGroups` without performing a join.
636
+ `findCliHelpSurface()`, `listCliHelpSurfaces()`, `renderCliHelp()`, and
637
+ `formatCliRouterError()` provide standard lookup, traversal, one-surface text
638
+ rendering, and diagnostics without writing output or choosing an exit code.
639
+ `renderCliHelp()` implements `CliHelpRender`; its context carries the full
640
+ selected `programName`, root `executableName`, path, and description preference.
641
+ The renderer path uses canonical CLI segment tokens; a directly invoked alias
642
+ may still be preserved in `programName` for user-facing usage text.
643
+ `--help`, complete-reference framing, and `--version` remain application policy.
644
+
645
+ The router returns a discriminated union:
646
+
647
+ - `resolved`
648
+ - `unknown_command`
649
+ - `incomplete_command`
650
+ - `invalid_input`
651
+ - `internal_error`
652
+
653
+ `path` is preserved for diagnostics. Consumers may use the standard formatter
654
+ or supply their own wording. Process exit codes remain consumer policy.
655
+
656
+ ## MCP reference
657
+
658
+ `createMcpEndpoint()` compiles the Endpoint once and returns the handle used for
659
+ both schema generation and runtime parsing. Do not keep a second runtime copy of
660
+ the MCP Binding.
661
+
662
+ ```ts
663
+ declare const toolArguments: Record<string, unknown>;
664
+
665
+ const mcpEndpoint = createMcpEndpoint(HintEndpoint);
666
+ const inputShape = mcpEndpoint.inputSchema;
667
+ const result = mcpEndpoint.parse(toolArguments);
668
+ ```
669
+
670
+ `inputSchema` is a raw Zod object shape. It includes only MCP-bound parameters
671
+ and projects their schemas, descriptions, and requiredness.
672
+
673
+ `parse()` applies Entry defaults to all parameters, validates bound input, and
674
+ runs the Entry projection. It returns:
675
+
676
+ - `resolved` with the typed projection
677
+ - `invalid_input` for missing input, codec failures, or an `EntryInputError`
678
+ deliberately thrown by `project()`
679
+ - `internal_error` with the original error for an unexpected `project()` throw
680
+
681
+ `isMcpResolved(result)` narrows a parse result to its `resolved` variant, the
682
+ MCP counterpart of `isCliResolvedEndpoint()`.
683
+
684
+ An invalid Endpoint binding throws `EntryTreeDefinitionError` from
685
+ `createMcpEndpoint()` itself, so definition errors surface at construction rather
686
+ than on the first invocation.
687
+
688
+ MCP tool naming and registration stay with the consumer. Hierarchical MCP tool
689
+ names are not currently derived from the Tree.
690
+
691
+ ## Output contracts
692
+
693
+ An output contract is optional. Add one when the same successful result schema
694
+ must drive runtime verification and MCP structured output:
695
+
696
+ ```ts
697
+ import {
698
+ createOutputContract,
699
+ defineEntry,
700
+ defineEntryEndpoint,
701
+ verifyEntryOutput,
702
+ } from "@alpacakit/channels";
703
+ import {
704
+ bindMcpEntry,
705
+ createMcpOutputSchema,
706
+ } from "@alpacakit/channels/mcp";
707
+ import { z } from "zod";
708
+
709
+ const HintOutputSchema = z
710
+ .object({
711
+ hints: z.array(z.string()).readonly(),
712
+ })
713
+ .strict()
714
+ .readonly();
715
+
716
+ const EntryWithOutput = defineEntry({
717
+ name: "hint",
718
+ parameters: [],
719
+ project: () => null,
720
+ output: createOutputContract(HintOutputSchema),
721
+ });
722
+
723
+ const EndpointWithOutput = defineEntryEndpoint(
724
+ EntryWithOutput,
725
+ bindMcpEntry(),
726
+ );
727
+
728
+ const outputSchema = createMcpOutputSchema(EndpointWithOutput);
729
+
730
+ const verified = verifyEntryOutput(EntryWithOutput, {
731
+ hints: ["A validated result"],
732
+ });
733
+
734
+ void outputSchema;
735
+ void verified;
736
+ ```
737
+
738
+ `createMcpOutputSchema()` requires an object output contract. Invalid output
739
+ passed to `verifyEntryOutput()` throws `EntryOutputContractError`.
740
+
741
+ ## Error boundaries
742
+
743
+ - `EntryTreeDefinitionError` means the Tree or a channel Binding is invalid.
744
+ Treat it as a startup or programmer error.
745
+ - `EntryInputError` may be thrown inside `project()` for an expected semantic
746
+ input rejection. It becomes `invalid_input`. Pass
747
+ `{ parameterName: "name" }` to associate all issues with one Entry parameter;
748
+ an unknown parameter name is an `internal_error` programmer failure.
749
+ - Codec and required-input failures become `invalid_input`.
750
+ - Any other projection exception becomes `internal_error` and retains the
751
+ original error. It is never reported as caller input failure.
752
+ - `EntryOutputContractError` means a handler returned a value outside its
753
+ declared success contract.
754
+
755
+ ## Scope
756
+
757
+ The package currently provides the neutral entry tree, CLI router, optional CLI
758
+ executor and text renderers, and MCP endpoint adapter. It does not provide an
759
+ HTTP router, process exit policy, automatic `--help`/`--version` handling, MCP
760
+ tool registration, authentication, or a network listener.
761
+
762
+ Every command surface uses `defineEntryEndpoint()` with `bindCliEntry()` and
763
+ `bindMcpEntry()`. There is no separate legacy option-descriptor API.
764
+
765
+ ## Zod peer and source linking
766
+
767
+ Packed consumers are type-checked against the declared `zod: ^4.3.6` peer. A
768
+ packed channels build was verified with both Zod 4.3.6 and 4.4.3. Source-linking
769
+ the package is different: if channels resolves its development Zod 4.3.6 while
770
+ the consumer resolves 4.4.3, TypeScript can expand the two distinct Zod type
771
+ graphs until it reports excessive depth or exhausts memory. When linking source,
772
+ use exactly one shared Zod installation/version, or consume a packed tarball.