@levu/snap 0.1.1 → 0.2.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +26 -2
  3. package/dist/dx/terminal/index.d.ts +2 -1
  4. package/dist/dx/terminal/index.js +3 -1
  5. package/dist/dx/terminal/intro-outro.d.ts +4 -0
  6. package/dist/dx/terminal/intro-outro.js +44 -0
  7. package/dist/dx/terminal/output.d.ts +13 -1
  8. package/dist/dx/terminal/output.js +43 -2
  9. package/dist/dx/tui/index.d.ts +12 -0
  10. package/dist/dx/tui/index.js +12 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +2 -0
  13. package/dist/tui/component-adapters/autocomplete.d.ts +15 -0
  14. package/dist/tui/component-adapters/autocomplete.js +34 -0
  15. package/dist/tui/component-adapters/note.d.ts +7 -0
  16. package/dist/tui/component-adapters/note.js +23 -0
  17. package/dist/tui/component-adapters/password.d.ts +7 -0
  18. package/dist/tui/component-adapters/password.js +24 -0
  19. package/dist/tui/component-adapters/progress.d.ts +7 -0
  20. package/dist/tui/component-adapters/progress.js +44 -0
  21. package/dist/tui/component-adapters/spinner.d.ts +10 -0
  22. package/dist/tui/component-adapters/spinner.js +48 -0
  23. package/dist/tui/component-adapters/tasks.d.ts +9 -0
  24. package/dist/tui/component-adapters/tasks.js +31 -0
  25. package/docs/component-reference.md +474 -0
  26. package/docs/getting-started.md +242 -0
  27. package/docs/help-contract-spec.md +29 -0
  28. package/docs/integration-examples.md +677 -0
  29. package/docs/module-authoring-guide.md +156 -0
  30. package/docs/snap-args.md +323 -0
  31. package/docs/snap-help.md +372 -0
  32. package/docs/snap-runtime.md +394 -0
  33. package/docs/snap-terminal.md +410 -0
  34. package/docs/snap-tui.md +529 -0
  35. package/package.json +4 -2
@@ -0,0 +1,529 @@
1
+ # SnapTui - Typed TUI Flow Definitions
2
+
3
+ `SnapTui` provides type-safe helpers for defining structured TUI flows with validation and custom component support.
4
+
5
+ ## Import
6
+
7
+ ```typescript
8
+ import * as SnapTui from 'snap-framework';
9
+ ```
10
+
11
+ ## Core Concepts
12
+
13
+ ### Structured vs. Legacy Flows
14
+
15
+ Snap supports two TUI definition styles:
16
+
17
+ 1. **Legacy**: Simple string array of step IDs
18
+ ```typescript
19
+ tui: { steps: ['step1', 'step2'] }
20
+ ```
21
+
22
+ 2. **Structured**: Full component definitions with validation
23
+ ```typescript
24
+ tui: {
25
+ flow: SnapTui.defineTuiFlow({
26
+ entryStepId: 'step1',
27
+ steps: [/* ... */]
28
+ })
29
+ }
30
+ ```
31
+
32
+ ### Component Types
33
+
34
+ SnapTui supports these component types:
35
+
36
+ - **text**: Single-line text input
37
+ - **select**: Single-choice selection
38
+ - **multiselect**: Multi-choice selection
39
+ - **confirm**: Boolean yes/no confirmation
40
+ - **custom**: Custom renderer (for advanced use cases)
41
+
42
+ ## API Reference
43
+
44
+ ### `defineTuiFlow(flow)`
45
+
46
+ Defines a validated TUI flow structure.
47
+
48
+ ```typescript
49
+ const flow = SnapTui.defineTuiFlow({
50
+ entryStepId: 'start',
51
+ steps: [
52
+ {
53
+ stepId: 'start',
54
+ title: 'Start Step',
55
+ components: [/* ... */]
56
+ }
57
+ ]
58
+ });
59
+ ```
60
+
61
+ **Validation:**
62
+ - At least one step required
63
+ - `entryStepId` must reference an existing step
64
+ - All components are validated
65
+
66
+ ### `defineTuiStep(step)`
67
+
68
+ Defines a validated TUI step.
69
+
70
+ ```typescript
71
+ const step = SnapTui.defineTuiStep({
72
+ stepId: 'collect-name',
73
+ title: 'Enter your name',
74
+ components: [
75
+ {
76
+ componentId: 'name',
77
+ type: 'text',
78
+ label: 'Name',
79
+ arg: 'name',
80
+ required: true
81
+ }
82
+ ]
83
+ });
84
+ ```
85
+
86
+ ### `defineTuiComponent(component)`
87
+
88
+ Defines a validated TUI component.
89
+
90
+ ```typescript
91
+ const component = SnapTui.defineTuiComponent({
92
+ componentId: 'operation',
93
+ type: 'select',
94
+ label: 'Choose operation',
95
+ arg: 'op',
96
+ required: true,
97
+ options: [
98
+ { value: 'create', label: 'Create new' },
99
+ { value: 'update', label: 'Update existing' }
100
+ ]
101
+ });
102
+ ```
103
+
104
+ **Component Options:**
105
+
106
+ | Field | Type | Description |
107
+ |-------|------|-------------|
108
+ | `componentId` | `string` | Unique identifier for the component |
109
+ | `type` | `'text'\|'select'\|'multiselect'\|'confirm'\|'custom'` | Component type |
110
+ | `label` | `string` | Display label for the prompt |
111
+ | `arg` | `string` | Argument key to store value in |
112
+ | `required` | `boolean` | Whether input is required |
113
+ | `options` | `TuiOption[]` | Options for select/multiselect |
114
+ | `initialValue` | `string` | Default value for text input |
115
+ | `placeholder` | `string` | Placeholder text |
116
+
117
+ ### `defineTuiOptions(options)`
118
+
119
+ Defines validated select/multiselect options.
120
+
121
+ ```typescript
122
+ const options = SnapTui.defineTuiOptions([
123
+ { value: 'option1', label: 'Option 1' },
124
+ { value: 'option2', label: 'Option 2' }
125
+ ]);
126
+ ```
127
+
128
+ ### `defineCustomTuiComponent(component)`
129
+
130
+ Defines a custom TUI component with a renderer.
131
+
132
+ ```typescript
133
+ const customComponent = SnapTui.defineCustomTuiComponent({
134
+ componentId: 'searchable-select',
135
+ label: 'Search and select',
136
+ arg: 'target',
137
+ renderer: 'searchable-select',
138
+ config: {
139
+ maxItems: 8,
140
+ allowCustomValue: true
141
+ }
142
+ });
143
+ ```
144
+
145
+ ## Complete Examples
146
+
147
+ ### Basic Text Input
148
+
149
+ ```typescript
150
+ import type { ModuleContract } from 'snap-framework';
151
+ import { ExitCode } from 'snap-framework';
152
+ import * as SnapTui from 'snap-framework';
153
+
154
+ const greeterModule: ModuleContract = {
155
+ moduleId: 'greet',
156
+ description: 'Greeting module',
157
+ actions: [
158
+ {
159
+ actionId: 'hello',
160
+ description: 'Say hello',
161
+ tui: {
162
+ flow: SnapTui.defineTuiFlow({
163
+ entryStepId: 'collect-name',
164
+ steps: [
165
+ SnapTui.defineTuiStep({
166
+ stepId: 'collect-name',
167
+ title: 'Enter your name',
168
+ components: [
169
+ SnapTui.defineTuiComponent({
170
+ componentId: 'name',
171
+ type: 'text',
172
+ label: 'Your name',
173
+ arg: 'name',
174
+ required: true,
175
+ placeholder: 'Enter your name here...'
176
+ })
177
+ ]
178
+ })
179
+ ]
180
+ })
181
+ },
182
+ commandline: { requiredArgs: ['name'] },
183
+ help: {
184
+ summary: 'Say hello to someone.',
185
+ args: [{ name: 'name', required: true, description: 'Name to greet' }]
186
+ },
187
+ run: async (context) => {
188
+ const name = String(context.args.name ?? 'World');
189
+ return {
190
+ ok: true,
191
+ mode: context.mode,
192
+ exitCode: ExitCode.SUCCESS,
193
+ data: `Hello, ${name}!`
194
+ };
195
+ }
196
+ }
197
+ ]
198
+ };
199
+ ```
200
+
201
+ ### Select Component
202
+
203
+ ```typescript
204
+ const deployModule: ModuleContract = {
205
+ moduleId: 'deploy',
206
+ description: 'Deployment module',
207
+ actions: [
208
+ {
209
+ actionId: 'start',
210
+ description: 'Start deployment',
211
+ tui: {
212
+ flow: SnapTui.defineTuiFlow({
213
+ entryStepId: 'select-environment',
214
+ steps: [
215
+ SnapTui.defineTuiStep({
216
+ stepId: 'select-environment',
217
+ title: 'Select Environment',
218
+ components: [
219
+ SnapTui.defineTuiComponent({
220
+ componentId: 'env',
221
+ type: 'select',
222
+ label: 'Environment',
223
+ arg: 'environment',
224
+ required: true,
225
+ options: SnapTui.defineTuiOptions([
226
+ { value: 'development', label: 'Development (Local)' },
227
+ { value: 'staging', label: 'Staging (Pre-production)' },
228
+ { value: 'production', label: 'Production (Live)' }
229
+ ])
230
+ })
231
+ ]
232
+ })
233
+ ]
234
+ })
235
+ },
236
+ commandline: { requiredArgs: ['environment'] },
237
+ help: {
238
+ summary: 'Deploy to environment.',
239
+ args: [{ name: 'environment', required: true, description: 'Target env' }]
240
+ },
241
+ run: async (context) => {
242
+ const environment = String(context.args.environment ?? '');
243
+ return {
244
+ ok: true,
245
+ mode: context.mode,
246
+ exitCode: ExitCode.SUCCESS,
247
+ data: `Deployed to ${environment}`
248
+ };
249
+ }
250
+ }
251
+ ]
252
+ };
253
+ ```
254
+
255
+ ### Multi-Step Flow with Multiple Components
256
+
257
+ ```typescript
258
+ const userModule: ModuleContract = {
259
+ moduleId: 'user',
260
+ description: 'User management',
261
+ actions: [
262
+ {
263
+ actionId: 'create',
264
+ description: 'Create a new user',
265
+ tui: {
266
+ flow: SnapTui.defineTuiFlow({
267
+ entryStepId: 'collect-basic',
268
+ steps: [
269
+ {
270
+ stepId: 'collect-basic',
271
+ title: 'Basic Information',
272
+ components: [
273
+ SnapTui.defineTuiComponent({
274
+ componentId: 'name',
275
+ type: 'text',
276
+ label: 'Full name',
277
+ arg: 'name',
278
+ required: true
279
+ }),
280
+ SnapTui.defineTuiComponent({
281
+ componentId: 'email',
282
+ type: 'text',
283
+ label: 'Email address',
284
+ arg: 'email',
285
+ required: true
286
+ })
287
+ ]
288
+ },
289
+ {
290
+ stepId: 'collect-roles',
291
+ title: 'Assign Roles',
292
+ components: [
293
+ SnapTui.defineTuiComponent({
294
+ componentId: 'roles',
295
+ type: 'multiselect',
296
+ label: 'User roles',
297
+ arg: 'roles',
298
+ required: true,
299
+ options: SnapTui.defineTuiOptions([
300
+ { value: 'admin', label: 'Administrator' },
301
+ { value: 'editor', label: 'Editor' },
302
+ { value: 'viewer', label: 'Viewer' }
303
+ ])
304
+ })
305
+ ]
306
+ },
307
+ {
308
+ stepId: 'confirm',
309
+ title: 'Confirm Creation',
310
+ components: [
311
+ SnapTui.defineTuiComponent({
312
+ componentId: 'confirmed',
313
+ type: 'confirm',
314
+ label: 'Create this user?',
315
+ arg: 'confirmed',
316
+ required: true
317
+ })
318
+ ]
319
+ }
320
+ ]
321
+ })
322
+ },
323
+ commandline: {
324
+ requiredArgs: ['name', 'email'],
325
+ optionalArgs: ['roles']
326
+ },
327
+ help: {
328
+ summary: 'Create a new user account.',
329
+ args: [
330
+ { name: 'name', required: true, description: 'User full name' },
331
+ { name: 'email', required: true, description: 'User email' },
332
+ { name: 'roles', required: false, description: 'Comma-separated roles' }
333
+ ]
334
+ },
335
+ run: async (context) => {
336
+ const name = String(context.args.name ?? '');
337
+ const email = String(context.args.email ?? '');
338
+ const rolesArg = context.args.roles;
339
+
340
+ let roles: string[] = ['viewer'];
341
+ if (Array.isArray(rolesArg)) {
342
+ roles = rolesArg as string[];
343
+ } else if (typeof rolesArg === 'string') {
344
+ roles = rolesArg.split(',');
345
+ }
346
+
347
+ return {
348
+ ok: true,
349
+ mode: context.mode,
350
+ exitCode: ExitCode.SUCCESS,
351
+ data: { name, email, roles }
352
+ };
353
+ }
354
+ }
355
+ ]
356
+ };
357
+ ```
358
+
359
+ ### Custom Component
360
+
361
+ ```typescript
362
+ const advancedModule: ModuleContract = {
363
+ moduleId: 'advanced',
364
+ description: 'Advanced operations',
365
+ actions: [
366
+ {
367
+ actionId: 'select-resource',
368
+ description: 'Select from resources',
369
+ tui: {
370
+ flow: SnapTui.defineTuiFlow({
371
+ entryStepId: 'search',
372
+ steps: [
373
+ {
374
+ stepId: 'search',
375
+ title: 'Select Resource',
376
+ components: [
377
+ SnapTui.defineCustomTuiComponent({
378
+ componentId: 'resource',
379
+ label: 'Search resources',
380
+ arg: 'resource',
381
+ renderer: 'searchable-select',
382
+ config: {
383
+ maxItems: 10,
384
+ allowCustomValue: false,
385
+ searchPlaceholder: 'Type to search...'
386
+ }
387
+ })
388
+ ]
389
+ }
390
+ ]
391
+ })
392
+ },
393
+ commandline: { requiredArgs: ['resource'] },
394
+ help: {
395
+ summary: 'Select a resource.',
396
+ args: [{ name: 'resource', required: true, description: 'Resource ID or name' }]
397
+ },
398
+ run: async (context) => {
399
+ const resource = String(context.args.resource ?? '');
400
+ return {
401
+ ok: true,
402
+ mode: context.mode,
403
+ exitCode: ExitCode.SUCCESS,
404
+ data: { resource }
405
+ };
406
+ }
407
+ }
408
+ ]
409
+ };
410
+ ```
411
+
412
+ ## Advanced Patterns
413
+
414
+ ### Conditional Step Display
415
+
416
+ Use flow control to skip steps:
417
+
418
+ ```typescript
419
+ run: async (context) => {
420
+ const skipConfig = SnapArgs.readBooleanArg(context.args, 'skip-config');
421
+
422
+ if (skipConfig) {
423
+ context.flow.jump('confirm');
424
+ } else {
425
+ context.flow.next();
426
+ }
427
+
428
+ // ... rest of logic
429
+ }
430
+ ```
431
+
432
+ ### Dynamic Options
433
+
434
+ Options can be computed at runtime:
435
+
436
+ ```typescript
437
+ const getEnvironmentOptions = async (): Promise<TuiOptionContract[]> => {
438
+ const envs = await fetchEnvironments();
439
+ return envs.map(env => ({
440
+ value: env.name,
441
+ label: `${env.name} (${env.region})`
442
+ }));
443
+ };
444
+
445
+ // Use in flow
446
+ flow: SnapTui.defineTuiFlow({
447
+ entryStepId: 'select-env',
448
+ steps: [
449
+ {
450
+ stepId: 'select-env',
451
+ title: 'Select Environment',
452
+ components: [
453
+ {
454
+ componentId: 'env',
455
+ type: 'select',
456
+ label: 'Environment',
457
+ arg: 'environment',
458
+ required: true,
459
+ options: await getEnvironmentOptions()
460
+ }
461
+ ]
462
+ }
463
+ ]
464
+ })
465
+ ```
466
+
467
+ ### Type Safety
468
+
469
+ Define interfaces for your data:
470
+
471
+ ```typescript
472
+ interface CreateUserInput {
473
+ name: string;
474
+ email: string;
475
+ roles: string[];
476
+ confirmed: boolean;
477
+ }
478
+
479
+ run: async (context): Promise<ActionResultEnvelope<CreateUserInput>> => {
480
+ // TypeScript knows the shape
481
+ return {
482
+ ok: true,
483
+ mode: context.mode,
484
+ exitCode: ExitCode.SUCCESS,
485
+ data: {
486
+ name: String(context.args.name),
487
+ email: String(context.args.email),
488
+ roles: context.args.roles as string[],
489
+ confirmed: Boolean(context.args.confirmed)
490
+ }
491
+ };
492
+ }
493
+ ```
494
+
495
+ ## Best Practices
496
+
497
+ 1. **Use structured flows** - Better validation and IDE support
498
+ 2. **Group related inputs** - One step per logical grouping
499
+ 3. **Provide clear labels** - Help users understand each field
500
+ 4. **Use confirm for destructive actions** - Always confirm deletes, etc.
501
+ 5. **Set reasonable defaults** - Reduce user typing with `initialValue`
502
+ 6. **Validate arg names** - Match component `arg` to action's expected args
503
+ 7. **Keep steps focused** - One logical operation per step
504
+
505
+ ## Component Validation
506
+
507
+ SnapTui validates at definition time:
508
+
509
+ ```typescript
510
+ // Throws: componentId cannot be empty
511
+ SnapTui.defineTuiComponent({
512
+ componentId: '',
513
+ type: 'text',
514
+ label: 'Label',
515
+ arg: 'x'
516
+ });
517
+
518
+ // Throws: options required for select
519
+ SnapTui.defineTuiComponent({
520
+ componentId: 'x',
521
+ type: 'select',
522
+ label: 'Label',
523
+ arg: 'x',
524
+ options: [] // Empty!
525
+ });
526
+
527
+ // Throws: empty options array
528
+ SnapTui.defineTuiOptions([]);
529
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levu/snap",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "snap": "./dist/cli-entry.js"
@@ -25,7 +25,9 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
+ "docs",
28
29
  "README.md",
29
- "LICENSE"
30
+ "LICENSE",
31
+ "CHANGELOG.md"
30
32
  ]
31
33
  }