icore 1.0.18 → 2.0.0

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
@@ -5,705 +5,243 @@
5
5
  [![types](https://img.shields.io/npm/types/icore.svg)](https://www.npmjs.com/package/icore)
6
6
  [![license](https://img.shields.io/npm/l/icore.svg)](LICENSE)
7
7
 
8
- Small dependency-free command line interface and terminal presentation mechanics for [Node.js®](https://nodejs.org) applications.
8
+ Small dependency-free command line interface and terminal presentation
9
+ mechanics for [Node.js®](https://nodejs.org) applications.
9
10
 
10
- Supports a practical GNU-style option syntax:
11
+ `icore` owns the path from `process.argv` to terminal output:
11
12
 
12
- - long options: `--name value`, `--name=value`;
13
- - boolean flags: `--flag`, `--no-flag`;
14
- - short aliases: `-f`, `-n value`;
15
- - option terminator: `--`.
13
+ - declarative, type-inferred option schemas;
14
+ - multi-segment command resolution;
15
+ - separate prepare and execute phases;
16
+ - JSON, CSV, and text-table presentation;
17
+ - stdout/stderr writers and reusable terminal error policy.
16
18
 
17
- ## Installation
19
+ Application-specific API calls, configuration, domain behavior, and help text
20
+ remain in the consuming application.
18
21
 
19
- To use `icore` in your project, run:
22
+ ## Requirements And Installation
23
+
24
+ `icore` requires Node.js `>=16.9.0` and includes TypeScript declarations.
20
25
 
21
26
  ```sh
22
27
  npm install icore
23
28
  ```
24
29
 
25
- ## Table of Contents
26
-
27
- - [API Reference](#api-reference)
28
- - [`createTerminalApp()`](#createterminalapp)
29
- - [`createCommand()`](#createcommand)
30
- - [`command.define(command)`](#commanddefinecommand)
31
- - [`command.registry(commands)`](#commandregistrycommands)
32
- - [`command.run(command, args, context)`](#commandruncommand-args-context)
33
- - [`createCommands(commands)`](#createcommandscommands)
34
- - [`commands.prepare(args, options?)`](#commandsprepareargs-options)
35
- - [`commands.run(prepared, context)`](#commandsrunprepared-context)
36
- - [`commands.runFromArgs(args, context, options?)`](#commandsrunfromargsargs-context-options)
37
- - [`createPresentation()`](#createpresentation)
38
- - [`presentation.empty()`](#presentationempty)
39
- - [`presentation.text(value)`](#presentationtextvalue)
40
- - [`presentation.record(value)`](#presentationrecordvalue)
41
- - [`presentation.records(values)`](#presentationrecordsvalues)
42
- - [`presentation.table(rows)`](#presentationtablerows)
43
- - [`presentation.csv(rows)`](#presentationcsvrows)
44
- - [`presentation.render(result, format?)`](#presentationrenderresult-format)
45
- - [`presentation.renderers.*`](#presentationrenderers)
46
- - [`createOutput()`](#createoutput)
47
- - [`output.write(chunk)`](#outputwritechunk)
48
- - [`output.error(chunk)`](#outputerrorchunk)
49
- - [`output.stdout.write(chunk)`](#outputstdoutwritechunk)
50
- - [`output.stderr.write(chunk)`](#outputstderrwritechunk)
51
- - [Lower-Level Mechanics](#lower-level-mechanics)
52
- - [`parseArgv(args, schema?)`](#parseargv)
53
- - [`parseOptionsDetailed(schema, values)`](#parseoptionsdetailed)
54
- - [`resolveCommand(registry, positionals)`](#resolvecommand)
55
- - [`resolveCommandFromArgs(registry, args)`](#resolvecommandfromargs)
56
- - [`prepareCommandFromArgs(registry, args, options?)`](#preparecommandfromargs)
57
- - [`runPreparedCommand(prepared, context)`](#runpreparedcommand)
58
- - [`runCommandFromRegistry(registry, args, context, options?)`](#runcommandfromregistry)
59
- - [`runCommand(command, args, context, options?)`](#runcommand)
60
- - [Internal Source Layout](#internal-source-layout)
61
- - [How It Works](#how-it-works)
62
- - [Example](#example)
63
- - [Option Schemas](#option-schemas)
64
- - [Type Inference](#type-inference)
65
- - [Facade of arguments](#facade-of-arguments)
66
- - [Error Messages](#error-messages)
67
- - [Project Boundary](#project-boundary)
68
-
69
- ## API Reference
70
-
71
- Start at `createTerminalApp()` for regular terminal applications, then move to
72
- the construction methods only when the app needs explicit command,
73
- presentation, or output wiring.
74
-
75
- ### `createTerminalApp()`
76
-
77
- `createTerminalApp()` is the top-level terminal application composition point.
78
- It wires command execution, presentation rendering, and stdout/stderr delivery
79
- without taking ownership of application behavior.
30
+ Import public APIs from `icore`. Deep imports into `dist` or `src` are not part
31
+ of the package contract.
80
32
 
81
- The checked TypeScript contract lives in [`src/terminal/app.ts`](src/terminal/app.ts).
33
+ ## Contents
82
34
 
83
- The method returns an application object with `prepare(args)`,
84
- `runPrepared(prepared, context)`, and `run(args, context)`.
85
-
86
- Construction inputs:
87
-
88
- - `commands` is required; build it with [`createCommand()`](#createcommand) and
89
- [`command.registry(commands)`](#commandregistrycommands), or directly with
90
- [`createCommands(commands)`](#createcommandscommands);
91
- - `presentation` is optional; omit it to use [`createPresentation()`](#createpresentation);
92
- - `output` is optional; omit it to use [`createOutput()`](#createoutput);
93
- - `resolveFormat` is optional and customizes how a prepared command selects a
94
- presentation format.
95
-
96
- Returned app methods:
35
+ - [Quick Start](#quick-start)
36
+ - [Choose An API Level](#choose-an-api-level)
37
+ - [Supported Argument Syntax](#supported-argument-syntax)
38
+ - [Error Handling](#error-handling)
39
+ - [Guides](#guides)
40
+ - [Project Boundary](#project-boundary)
97
41
 
98
- - `app.prepare(args, options?)` delegates to
99
- [`commands.prepare(args, options?)`](#commandsprepareargs-options);
100
- - `app.runPrepared(prepared, context)` runs an already prepared command, then
101
- renders and writes terminal output;
102
- - `app.run(args, context, options?)` prepares the command, delegates command
103
- execution to [`commands.run(prepared, context)`](#commandsrunprepared-context),
104
- then renders and writes terminal output.
42
+ ## Quick Start
105
43
 
106
- Minimal shape:
44
+ Create commands, put them in a registry, and give that registry to
45
+ `createTerminalApp()`:
107
46
 
108
47
  ```ts
109
- const app = createTerminalApp({
110
- commands,
111
- presentation,
112
- output
113
- });
114
-
115
- const args = process.argv.slice(2);
116
- const exitCode = await app.run(args, context, {
117
- strict: true
118
- });
119
- ```
120
-
121
- Command handlers keep ownership of application work. The terminal app only
122
- accepts terminal-ready results: text, streaming text, presentation results, or
123
- no output. Application DTO mapping, config loading, network clients, and domain
124
- behavior stay in the consuming application.
125
-
126
- ### `createCommand()`
48
+ import {
49
+ createCommand,
50
+ createTerminalApp
51
+ } from 'icore';
127
52
 
128
- `createCommand()` returns the command mechanics entrypoint used to build
129
- `commands` for `createTerminalApp()`.
130
-
131
- Simplified shape:
132
-
133
- ```ts
134
53
  const command = createCommand();
135
54
 
136
- command.define(commandDefinition);
137
- command.registry(commandDefinitions);
138
- command.run(commandDefinition, args, context);
139
- ```
140
-
141
- Build `commands` before creating the terminal app:
142
-
143
- - `createCommand()` creates the command mechanics entrypoint;
144
- - `command.define(...)` declares one command;
145
- - `command.registry([...])` returns the `commands` object required by
146
- `createTerminalApp()`.
147
-
148
- The command definition keeps the application-specific parts: command path,
149
- option schema, and handler behavior. The terminal app only needs the resulting
150
- registry object.
151
-
152
- #### `command.define(command)`
153
-
154
- Declares one command and preserves its literal path and option schema types.
155
- Use it when defining commands inline before adding them to a registry.
156
-
157
- #### `command.registry(commands)`
158
-
159
- Builds the `commands` object required by `createTerminalApp()`. It keeps the
160
- registered command definitions, derived command names, and command flow methods
161
- together.
162
-
163
- #### `command.run(command, args, context)`
164
-
165
- Runs a single command without a registry. Use it for focused command execution,
166
- small tests, or custom flows where command path resolution is not needed.
167
-
168
- #### `createCommands(commands)`
169
-
170
- Use `createCommands(commands)` when the application already has command
171
- definitions and does not need the `createCommand()` object form.
172
-
173
- The returned object is the same `commands` contract consumed by
174
- `createTerminalApp()`: it can resolve, prepare, and run registered commands.
175
-
176
- Simplified shape:
177
-
178
- ```ts
179
- const commands = createCommands(commandDefinitions);
180
-
181
- commands.names;
182
- commands.registry;
183
- commands.resolve(positionals);
184
- commands.resolveFromArgs(args);
185
- commands.prepare(args, options);
186
- commands.run(prepared, context);
187
- commands.runFromArgs(args, context, options);
188
- ```
189
-
190
- Use `commands.prepare(...)` and `commands.run(...)` when application code needs
191
- the same two-phase flow used by `createTerminalApp()`. Use
192
- `commands.runFromArgs(...)` for custom terminal boundaries that still want the
193
- registry-level command flow.
194
-
195
- #### `commands.prepare(args, options?)`
196
-
197
- Resolves and validates a registered command without runtime context. This is
198
- the same preparation step used by `app.prepare(...)` and `app.run(...)`.
199
-
200
- #### `commands.run(prepared, context)`
201
-
202
- Runs an already prepared command with application context. Use it after
203
- `commands.prepare(...)` when the application needs a custom two-phase flow.
204
-
205
- #### `commands.runFromArgs(args, context, options?)`
206
-
207
- Resolves, prepares, and runs a registered command without the terminal
208
- application boundary. Use it when a custom boundary owns presentation or output.
209
-
210
- ### `createPresentation()`
211
-
212
- `createPresentation()` creates the presentation object used by
213
- `createTerminalApp()` to render terminal-ready results.
214
-
215
- Source of truth:
216
-
217
- - [src/presentation/facade.ts](src/presentation/facade.ts) for the presentation
218
- object;
219
- - [src/presentation/view.ts](src/presentation/view.ts) for presentation view
220
- factories;
221
- - [src/presentation/result-renderer.ts](src/presentation/result-renderer.ts)
222
- for rendering presentation results.
223
-
224
- Simplified shape:
225
-
226
- ```ts
227
- const presentation = createPresentation();
228
-
229
- presentation.empty();
230
- presentation.text(value);
231
- presentation.record(value);
232
- presentation.records(values);
233
- presentation.table(rows);
234
- presentation.csv(rows);
235
- presentation.render(result, format);
236
- ```
237
-
238
- The presentation object owns generic JSON, CSV, and table rendering mechanics.
239
- Application code still maps domain objects to presentation-ready values.
240
-
241
- #### `presentation.empty()`
242
-
243
- Creates a presentation result with no terminal output.
244
-
245
- #### `presentation.text(value)`
246
-
247
- Wraps ready terminal text. Use it when the command already owns the final text.
248
-
249
- #### `presentation.record(value)`
250
-
251
- Creates a generic one-record view. The renderer can print it as table, CSV, or
252
- JSON depending on the selected format.
253
-
254
- #### `presentation.records(values)`
255
-
256
- Creates a generic multi-record view. Application code still decides which fields
257
- belong in each record.
258
-
259
- #### `presentation.table(rows)`
260
-
261
- Creates an explicit table view from prepared text rows.
262
-
263
- #### `presentation.csv(rows)`
264
-
265
- Creates an explicit CSV view from scalar rows.
266
-
267
- #### `presentation.render(result, format?)`
268
-
269
- Renders a presentation result to terminal text. The default format is `table`.
270
-
271
- #### `presentation.renderers.*`
272
-
273
- Exposes lower-level JSON, table, and CSV renderers for custom presentation
274
- composition. Prefer `presentation.render(...)` for regular terminal commands.
275
-
276
- ### `createOutput()`
277
-
278
- `createOutput()` creates the output object used by `createTerminalApp()` to
279
- write rendered text.
280
-
281
- Source of truth:
282
-
283
- - [src/output/facade.ts](src/output/facade.ts) for the output object;
284
- - [src/output/node-writer.ts](src/output/node-writer.ts) for stdout and stderr
285
- writers;
286
- - [src/output/text-writer.ts](src/output/text-writer.ts) for backpressure-aware
287
- text writing.
288
-
289
- Simplified shape:
290
-
291
- ```ts
292
- const output = createOutput();
293
-
294
- await output.write(chunk);
295
- await output.error(chunk);
296
- await output.stdout.write(chunk);
297
- await output.stderr.write(chunk);
298
- ```
299
-
300
- The default output writes regular text to `stdout` and diagnostic text to
301
- `stderr`. Pass custom sinks when tests or applications need controlled output.
302
-
303
- #### `output.write(chunk)`
304
-
305
- Writes regular terminal output through stdout.
306
-
307
- #### `output.error(chunk)`
308
-
309
- Writes diagnostic terminal output through stderr.
310
-
311
- #### `output.stdout.write(chunk)`
312
-
313
- Writes directly to the stdout channel. Use this only when a specific channel
314
- must be passed around.
315
-
316
- #### `output.stderr.write(chunk)`
317
-
318
- Writes directly to the stderr channel. Use this only when a specific channel
319
- must be passed around.
320
-
321
- ### Lower-Level Mechanics
322
-
323
- Lower-level mechanics sit behind the construction methods. They are kept out of
324
- the main navigation to keep the application construction path first.
325
-
326
- For application code, prefer `createTerminalApp()` first. Drop to this level
327
- only when the terminal application flow or construction methods are too coarse.
328
-
329
- Source of truth:
330
-
331
- - [src/argv/parser.ts](src/argv/parser.ts) for raw argv parsing;
332
- - [src/options/parser.ts](src/options/parser.ts) for option value validation;
333
- - [src/command/mechanics.ts](src/command/mechanics.ts) for command resolution,
334
- preparation, and execution.
335
-
336
- #### Parsing And Resolution
337
-
338
- This group turns raw arguments or positionals into a selected command.
339
-
340
- - <a id="parseargv"></a>`parseArgv(args, schema?)` parses raw CLI arguments into positionals and raw
341
- option values;
342
- - <a id="resolvecommand"></a>`resolveCommand(registry, positionals)` resolves from already parsed
343
- positionals;
344
- - <a id="resolvecommandfromargs"></a>`resolveCommandFromArgs(registry, args)` resolves from raw CLI arguments by
345
- using registered command schemas.
346
-
347
- Use these methods when command selection is needed without validation or
348
- execution.
349
-
350
- #### Preparation
351
-
352
- This group validates command input without runtime context.
353
-
354
- - <a id="parseoptionsdetailed"></a>`parseOptionsDetailed(schema, values)` validates raw option values and keeps
355
- option presence metadata;
356
- - <a id="preparecommandfromargs"></a>`prepareCommandFromArgs(registry, args, options?)` resolves and validates a
357
- registered command;
358
- - `options.strict` rejects options before the command path.
359
-
360
- Use preparation when runtime resources should be created only after command
361
- selection is known.
362
-
363
- #### Execution
364
-
365
- This group calls command handlers after command input is prepared.
366
-
367
- - <a id="runpreparedcommand"></a>`runPreparedCommand(prepared, context)` runs an already prepared command;
368
- - <a id="runcommandfromregistry"></a>`runCommandFromRegistry(registry, args, context, options?)` prepares and runs
369
- a command from a registry;
370
- - <a id="runcommand"></a>`runCommand(command, args, context, options?)` runs a single command without a
371
- registry.
372
-
373
- Use execution primitives for custom boundaries. Regular CLI applications should
374
- usually stay at `createTerminalApp()`.
375
-
376
- ## Internal Source Layout
377
-
378
- The public package entrypoint remains `src/index.ts`. Internal source files are
379
- grouped by CLI framework responsibility:
380
-
381
- ```text
382
- src/
383
- argv/ raw argv token parsing
384
- options/ option schemas and option value validation
385
- command/ command resolution and execution mechanics
386
- presentation/ terminal view models and JSON/CSV/table renderers
387
- output/ stdout/stderr writer boundaries
388
- terminal/ command + presentation + output app composition
389
- errors/ machine-readable icore errors
390
- ```
391
-
392
- Consumers should continue importing from `icore`; deep imports are an internal
393
- source layout detail.
394
-
395
- ## How It Works
396
-
397
- ![yuml diagram](http://yuml.me/diagram/scruffy;dir:LR;/class/[*argv*%20{bg:gray}|External;hello%20--name%20Alice%20--uppercase]->[*matches*%20{bg:lavender}|System;parse,%20resolve,%20validate,%20infer]->[*typed%20result*%20{bg:honeydew}|Container;command=hello;%20name=Alice;%20uppercase=true]->[*your%20app*%20{bg:cornsilk}|System;business%20logic%20and%20output])
398
-
399
- ## Example
400
-
401
- More examples live in [examples/readme.md](examples/readme.md), including
402
- option schemas, CLI argument syntax, practical CLI patterns, and lower-level
403
- primitives.
404
-
405
- ```ts
406
- import { defineCommand, runCommand } from 'icore';
407
-
408
- const exampleCommand = defineCommand({
409
- path: ['hello'],
410
- options: {
411
- name: {
412
- type: 'string',
413
- default: 'world'
55
+ const commands = command.registry([
56
+ command.define({
57
+ path: ['hello'],
58
+ options: {
59
+ name: {
60
+ type: 'string',
61
+ default: 'world'
62
+ },
63
+ uppercase: {
64
+ type: 'boolean'
65
+ }
414
66
  },
415
- uppercase: {
416
- type: 'boolean'
67
+ handle({ options }) {
68
+ const greeting = `Hello, ${options.name}!`;
69
+
70
+ return `${options.uppercase ? greeting.toUpperCase() : greeting}\n`;
417
71
  }
418
- },
419
- async handle({ options }) {
420
- const message = `Hello, ${options.name}!`;
72
+ })
73
+ ] as const);
421
74
 
422
- return options.uppercase ? message.toUpperCase() : message;
423
- }
424
- });
75
+ const app = createTerminalApp({ commands });
425
76
 
426
- const output = await runCommand(
427
- exampleCommand,
428
- ['hello', '--name', 'Alice', '--uppercase'],
429
- {}
430
- );
77
+ async function main(args: readonly string[]): Promise<void> {
78
+ process.exitCode = await app.run(args, undefined, {
79
+ strict: true
80
+ });
81
+ }
431
82
 
432
- console.log(output);
83
+ void main(process.argv.slice(2));
433
84
  ```
434
85
 
435
- Terminal output:
86
+ After compiling the application:
436
87
 
437
88
  ```console
438
- $ node cli.js hello --name Alice --uppercase
439
- HELLO, ALICE!
440
- ```
441
-
442
- The command handler receives parsed options, user-provided option metadata,
443
- remaining positionals, prepared payload, and caller provided context.
444
-
445
- ## Option Schemas
446
-
447
- Options are described as plain objects.
448
-
449
- **Option names are exact.** `icore` does not normalize `camelCase` to
450
- `kebab-case`. Use quoted object keys when your public CLI option contains
451
- `-`.
452
-
453
- Each option can define an optional short alias:
454
-
455
- ```ts
456
- const schema = {
457
- name: {
458
- type: 'string',
459
- alias: 'n'
460
- },
461
- uppercase: {
462
- type: 'boolean',
463
- alias: 'u'
464
- }
465
- } as const;
466
- ```
467
-
468
- Aliases must be a single ASCII letter and unique within the schema. Parsed
469
- values are always returned by long option name.
470
-
471
- ### `type: 'string'`
472
-
473
- ```ts
474
- const schema = {
475
- name: {
476
- type: 'string',
477
- required: true
478
- },
479
- style: {
480
- type: 'string',
481
- choices: ['short', 'long'],
482
- default: 'short'
483
- }
484
- } as const;
485
- ```
486
-
487
- String options reject missing required values, blank strings such as `--name=`,
488
- boolean flag form, and values outside `choices`.
489
-
490
- ### `type: 'boolean'`
491
-
492
- ```ts
493
- const schema = {
494
- uppercase: {
495
- type: 'boolean'
496
- }
497
- } as const;
498
- ```
89
+ $ node dist/cli.js hello
90
+ Hello, world!
499
91
 
500
- Boolean options accept **flag form** and schema-known negation:
501
-
502
- ```sh
503
- --uppercase
504
- --no-uppercase
505
- ```
506
-
507
- Explicit values are rejected:
508
-
509
- ```sh
510
- --uppercase=true
511
- --uppercase=false
512
- --uppercase=yes
513
- --uppercase=
514
- ```
515
-
516
- `--uppercase false` keeps `--uppercase` as `true` and leaves `false` as a positional
517
- argument.
518
-
519
- Use `syntax: 'flag'` when a boolean option should accept only flag form:
520
-
521
- ```ts
522
- const schema = {
523
- uppercase: {
524
- type: 'boolean',
525
- default: false,
526
- syntax: 'flag'
527
- }
528
- } as const;
92
+ $ node dist/cli.js hello --name Alice --uppercase
93
+ HELLO, ALICE!
529
94
  ```
530
95
 
531
- With `syntax: 'flag'`, `--uppercase` is accepted, while `--uppercase=value`
532
- and `--no-uppercase` are rejected.
96
+ The schema determines the handler's option types. Required options and options
97
+ with defaults are always present; optional options are returned as
98
+ `T | undefined`.
533
99
 
534
- ### `type: 'number'`
100
+ `strict: true` rejects options placed before the command path. String output is
101
+ written exactly as returned, so include `\n` when line output is intended.
535
102
 
536
- ```ts
537
- const schema = {
538
- limit: {
539
- type: 'number',
540
- integer: true,
541
- min: 1,
542
- max: 1000,
543
- default: 100
544
- }
545
- } as const;
546
- ```
103
+ ## Choose An API Level
547
104
 
548
- Number options parse decimal numeric values and can validate integer and range
549
- constraints. Defaults are validated with the same rules as user-provided values.
105
+ Start with the highest-level API that fits the application:
550
106
 
551
- ## Type Inference
107
+ | Need | Start with | Detailed guide |
108
+ | --- | --- | --- |
109
+ | Complete terminal application | `createTerminalApp()` | [Terminal App](examples/terminal-app.md) |
110
+ | Commands and option schemas | `createCommand()` or `createCommands()` | [Option Schemas](examples/option-schemas.md) |
111
+ | Custom prepare/execute lifecycle | `commands.prepare()` and `commands.run()` | [Custom Command Flow](examples/custom-command-flow.md) |
112
+ | Presentation without command execution | `createPresentation()` | [Presentation And Output](examples/presentation-output.md) |
113
+ | Explicit stdout/stderr writing | `createOutput()` | [Output Writers](examples/output-writers.md) |
114
+ | Parser and resolver primitives | `parseArgv()`, `resolveCommand()`, and related exports | [Primitive Mechanics](examples/readme.md#primitive-mechanics) |
552
115
 
553
- Use `InferOptions` when you need the parsed option type explicitly.
116
+ The terminal application exposes the main lifecycle operations:
554
117
 
555
- ```ts
556
- import type { InferOptions } from 'icore';
557
-
558
- const schema = {
559
- name: {
560
- type: 'string',
561
- default: 'world'
562
- },
563
- uppercase: {
564
- type: 'boolean'
565
- }
566
- } as const;
118
+ - `app.run(args, context, options?)` prepares, executes, renders, and writes;
119
+ - `app.prepare(args, options?)` validates input without runtime context;
120
+ - `app.runPrepared(prepared, context)` executes an already prepared command;
121
+ - `app.writePreparedOutput(prepared, output)` writes caller-obtained output;
122
+ - `app.reportError(error, context?)` applies the configured terminal error
123
+ policy and returns a process-style exit code.
567
124
 
568
- type Options = InferOptions<typeof schema>;
569
- ```
125
+ Command handlers receive parsed `options`, option-presence metadata in
126
+ `provided`, remaining `positionals`, caller-owned `context`, and an optional
127
+ prepared `payload`.
570
128
 
571
- `Options` is equivalent to:
129
+ Supported terminal results are strings, async string streams, presentation
130
+ results, and `undefined`:
572
131
 
573
- ```ts
574
- type Options = {
575
- name: string;
576
- uppercase: boolean | undefined;
577
- };
132
+ ```text
133
+ argv resolve → validate/prepare → execute → render → stdout/stderr
578
134
  ```
579
135
 
580
- **Required options and options with defaults are always present.** Optional
581
- options without defaults are returned as `T | undefined`.
582
-
583
- Use `InferProvidedOptions` when you need the option presence type explicitly.
584
-
585
- ```ts
586
- import type { InferProvidedOptions } from 'icore';
136
+ Exact public exports live in [`src/index.ts`](src/index.ts) and in the bundled
137
+ TypeScript declarations.
587
138
 
588
- type Provided = InferProvidedOptions<typeof schema>;
589
- ```
139
+ ## Supported Argument Syntax
590
140
 
591
- `Provided` maps every schema option to `boolean`. `true` means the user
592
- specified that option explicitly; defaults keep the flag `false`.
141
+ The supported syntax is intentionally small and predictable:
593
142
 
594
- Use `MergeOptionsSchemas` when you need the merged schema type explicitly.
143
+ | Form | Example | Notes |
144
+ | --- | --- | --- |
145
+ | Long option with separate value | `--name Alice` | String and number options consume a value |
146
+ | Long option with attached value | `--name=Alice` | Equivalent to the separate form |
147
+ | Boolean flag | `--verbose` | Produces `true` |
148
+ | Boolean negation | `--no-cache` | Supported for known boolean options unless `syntax: 'flag'` |
149
+ | Short alias | `-v`, `-n Alice` | Must be declared in the schema as one ASCII letter |
150
+ | Option terminator | `--` | Every following token becomes positional |
595
151
 
596
- ```ts
597
- import type { MergeOptionsSchemas } from 'icore';
152
+ Option names are exact: `icore` does not convert `camelCase` to `kebab-case`.
153
+ Explicit boolean values such as `--verbose=true`, attached short values such as
154
+ `-nAlice`, and grouped aliases such as `-abc` are not supported.
598
155
 
599
- type Schema = MergeOptionsSchemas<[typeof nameOptions, typeof greetingOptions]>;
600
- ```
156
+ See [CLI Argument Syntax](examples/cli-argument-syntax.md) for parsing examples,
157
+ edge cases, duplicate handling, and the option terminator contract.
601
158
 
602
- Use `CommandName` when you need the inferred command name type explicitly.
159
+ ## Error Handling
603
160
 
604
- ```ts
605
- import type { CommandName } from 'icore';
161
+ CLI parsing, validation, resolution, and definition failures are reported as
162
+ `IcoreError`. It extends `Error` with:
606
163
 
607
- type Name = CommandName<typeof helloFormalCommand>;
608
- ```
164
+ - a stable machine-readable `code`;
165
+ - a `usage` or `definition` category;
166
+ - required, code-specific `details`.
609
167
 
610
- `Name` is equivalent to:
168
+ Use `isIcoreError(...)` instead of manually casting details. Passing a code to
169
+ the guard narrows the corresponding details shape:
611
170
 
612
171
  ```ts
613
- type Name = 'hello formal';
614
- ```
172
+ import {
173
+ createTerminalApp,
174
+ isIcoreError
175
+ } from 'icore';
615
176
 
616
- ## Facade of arguments
177
+ const help = 'Usage: cli hello [--name value]';
617
178
 
618
- Use `--` to stop option parsing. The terminator itself is not included in
619
- positionals; every following token is treated as positional, even when it starts
620
- with `-`.
621
-
622
- Short syntax is supported only for aliases declared in the option schema.
623
- Boolean aliases use flag form, such as `-f`; string and number aliases use a
624
- separate value, such as `-n value`.
625
-
626
- Attached short values such as `-nvalue` and grouped short booleans such as
627
- `-abc` are not supported yet. Unknown short tokens remain positional for
628
- compatibility.
629
-
630
- Negated syntax such as `--no-cache` is interpreted as `cache: false` when
631
- `cache` is a known boolean option without `syntax: 'flag'`. Unknown negated
632
- options, negation for string or number options, and negation for flag-only
633
- boolean options are rejected.
634
-
635
- ## Error Messages
636
-
637
- `icore` throws `IcoreError` objects for CLI parsing, option validation, and
638
- command resolution failures. `IcoreError` extends the regular `Error` class and
639
- adds a stable machine-readable `code` plus structured `details`.
179
+ const app = createTerminalApp({
180
+ commands,
181
+ errorPolicy: {
182
+ renderError(error) {
183
+ const message = error instanceof Error
184
+ ? error.message
185
+ : String(error);
640
186
 
641
- Applications should treat `error.message` as **display text**. Use `error.code`
642
- for machine-readable handling:
187
+ if (isIcoreError(error, 'UNKNOWN_COMMAND')) {
188
+ return `${message}\n\n${help}\n`;
189
+ }
643
190
 
644
- ```ts
645
- import { IcoreError } from 'icore';
646
-
647
- try {
648
- await main(args);
649
- } catch (error) {
650
- if (error instanceof IcoreError && error.code === 'UNKNOWN_COMMAND') {
651
- printHelp();
652
- process.exitCode = 2;
653
- return;
191
+ return `${message}\n`;
192
+ },
193
+ resolveExitCode(error) {
194
+ return isIcoreError(error) && error.category === 'usage'
195
+ ? 2
196
+ : 1;
197
+ }
654
198
  }
655
-
656
- throw error;
657
- }
199
+ });
658
200
  ```
659
201
 
660
- Supported error codes:
202
+ Inside the `UNKNOWN_COMMAND` branch, fields such as `error.details.command` and
203
+ `error.details.positionals` are strongly typed. `IcoreErrorDetailsMap` is the
204
+ public source of truth for every code. Direct `new IcoreError(...)` calls must
205
+ provide a third argument matching the selected code.
661
206
 
662
- ```ts
663
- type IcoreErrorCode =
664
- | 'UNKNOWN_COMMAND'
665
- | 'UNEXPECTED_ARGUMENT'
666
- | 'DUPLICATE_ARGUMENT'
667
- | 'EXPECTED_REQUIRED_ARGUMENT'
668
- | 'INVALID_OPTION_TYPE'
669
- | 'INVALID_OPTION_CHOICE'
670
- | 'UNEXPECTED_POSITIONAL'
671
- | 'INVALID_OPTION_ALIAS'
672
- | 'DUPLICATE_ALIAS'
673
- | 'INVALID_OPTION_DEFAULT'
674
- | 'DUPLICATE_COMMAND';
675
- ```
207
+ Without a custom policy, terminal apps write `Error.message + "\n"` (or
208
+ `String(error) + "\n"` for other thrown values) and return exit code `1`.
209
+ Application-specific help remains application policy.
676
210
 
677
- Applications can catch these errors and decide how to print them. For example,
678
- after printing `error.message`, terminal output can look like this:
211
+ Custom lifecycles can call `app.reportError(...)` to reuse the same rendering
212
+ and exit-code policy. The complete prepare, execute, write, and external phase
213
+ flow is shown in the
214
+ [Terminal App guide](examples/terminal-app.md#reuse-error-reporting-in-a-custom-lifecycle).
679
215
 
680
- ```console
681
- $ node cli.js hello --unknown
682
- Unexpected argument '--unknown'
216
+ ## Guides
683
217
 
684
- $ node cli.js hello --uppercase=yes
685
- Expected '--uppercase' as boolean flag
218
+ The [examples index](examples/readme.md) routes from regular terminal apps to
219
+ lower-level mechanics. Useful starting points:
686
220
 
687
- $ node cli.js hello --name=
688
- Expected '--name' as string
689
- ```
221
+ - [Terminal App](examples/terminal-app.md) regular application composition,
222
+ format resolution, and reusable error reporting;
223
+ - [Option Schemas](examples/option-schemas.md) — strings, booleans, numbers,
224
+ choices, defaults, aliases, and inferred types;
225
+ - [Practical CLI Patterns](examples/practical-cli-patterns.md) — help/version
226
+ shortcuts, shared options, and compatibility aliases;
227
+ - [Command Resolution](examples/command-resolution.md) — registries, explicit
228
+ resolution, and command-name guards;
229
+ - [Two-Phase Primitives](examples/two-phase-primitives.md) — preparation,
230
+ payloads, execution, and provided-option metadata;
231
+ - [Presentation Primitives](examples/presentation-primitives.md) — text, record,
232
+ table, CSV, JSON, and direct renderers.
690
233
 
691
- Errors thrown by command `prepare` and `handle` functions are application errors
692
- and pass through unchanged.
234
+ Release history is recorded in [CHANGELOG.md](CHANGELOG.md). Directional
235
+ decisions live in [docs/roadmap.md](docs/roadmap.md).
693
236
 
694
237
  ## Project Boundary
695
238
 
696
- `icore` is intended to be a **small CLI mechanics module**. It should **not**
697
- grow into a domain-specific framework for a particular SDK or API.
698
-
699
- Good responsibilities for `icore`:
239
+ `icore` is a small terminal mechanics module. It owns generic behavior such as
240
+ option validation, command resolution, typed handler input, presentation
241
+ rendering, error contracts, and stdout/stderr delivery.
700
242
 
701
- - option schema evaluation;
702
- - command path checking;
703
- - common argument errors;
704
- - typed command handler input;
705
- - generic JSON, CSV, and text table rendering;
706
- - stdout/stderr text writer mechanics.
243
+ It does not own application DTO mapping, API calls, configuration loading,
244
+ domain-specific validation, lifecycle resources, or help content. Keep those
245
+ responsibilities in the consuming application.
707
246
 
708
- Application-specific report mapping, scalar formatting, API calls, config
709
- loading, and domain behavior stay outside `icore`.
247
+ Licensed under the [MIT License](LICENSE).