@levu/snap 0.1.1 → 0.3.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 (39) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +27 -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 +6 -0
  12. package/dist/index.js +3 -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/multiline-text.d.ts +13 -0
  16. package/dist/tui/component-adapters/multiline-text.js +166 -0
  17. package/dist/tui/component-adapters/note.d.ts +7 -0
  18. package/dist/tui/component-adapters/note.js +23 -0
  19. package/dist/tui/component-adapters/password.d.ts +7 -0
  20. package/dist/tui/component-adapters/password.js +24 -0
  21. package/dist/tui/component-adapters/progress.d.ts +7 -0
  22. package/dist/tui/component-adapters/progress.js +44 -0
  23. package/dist/tui/component-adapters/spinner.d.ts +21 -0
  24. package/dist/tui/component-adapters/spinner.js +89 -0
  25. package/dist/tui/component-adapters/tasks.d.ts +9 -0
  26. package/dist/tui/component-adapters/tasks.js +31 -0
  27. package/dist/tui/component-adapters/text.d.ts +4 -0
  28. package/dist/tui/component-adapters/text.js +18 -0
  29. package/docs/component-reference.md +557 -0
  30. package/docs/getting-started.md +242 -0
  31. package/docs/help-contract-spec.md +29 -0
  32. package/docs/integration-examples.md +677 -0
  33. package/docs/module-authoring-guide.md +156 -0
  34. package/docs/snap-args.md +323 -0
  35. package/docs/snap-help.md +372 -0
  36. package/docs/snap-runtime.md +394 -0
  37. package/docs/snap-terminal.md +410 -0
  38. package/docs/snap-tui.md +529 -0
  39. package/package.json +4 -2
@@ -0,0 +1,557 @@
1
+ # Component Reference & API Coverage
2
+
3
+ This document provides a complete reference of all Snap components available through the public API.
4
+
5
+ ## Basic Prompt Components (via `PromptToolkit`)
6
+
7
+ Accessed through `context.prompts` in your action handlers:
8
+
9
+ ```typescript
10
+ interface PromptToolkit {
11
+ text(input: TextPromptInput): Promise<string>;
12
+ confirm(input: ConfirmPromptInput): Promise<boolean>;
13
+ select(input: SelectPromptInput): Promise<string>;
14
+ multiselect(input: MultiSelectPromptInput): Promise<string[]>;
15
+ group<T = unknown>(steps: GroupStep<T>[]): Promise<Record<string, T>>;
16
+ custom<T>(input: CustomPromptInput<T>): Promise<T>;
17
+ }
18
+ ```
19
+
20
+ ### text
21
+ Single-line text input with validation and optional paste/multiline support.
22
+
23
+ ```typescript
24
+ const name = await context.prompts.text({
25
+ message: 'Enter your name:',
26
+ placeholder: 'John Doe',
27
+ defaultValue: '',
28
+ validate: (value) => {
29
+ if (!value) return 'Name is required';
30
+ return undefined;
31
+ }
32
+ });
33
+ ```
34
+
35
+ **With Paste Support:**
36
+ ```typescript
37
+ const description = await context.prompts.text({
38
+ message: 'Enter a description:',
39
+ paste: true, // Enable clipboard paste (Ctrl+V / Cmd+V)
40
+ multiline: true, // Allow multiple lines of input
41
+ placeholder: 'Paste or type your description here...'
42
+ });
43
+ ```
44
+
45
+ **Options:**
46
+ - `message` (required): The prompt message
47
+ - `placeholder`: Placeholder text when input is empty
48
+ - `defaultValue` / `initialValue`: Initial value
49
+ - `required`: Whether input is required (default: false)
50
+ - `validate`: Custom validation function
51
+ - `paste`: Enable clipboard paste support (default: false)
52
+ - `multiline`: Allow multiple lines of input (default: false, implied by paste: true)
53
+
54
+ **Paste Support Details:**
55
+ When `paste` is enabled, users can paste from clipboard using:
56
+ - macOS/Linux: `Cmd+V` or `Ctrl+V`
57
+ - Windows: `Ctrl+V`
58
+
59
+ Multi-line paste is automatically supported. Submit with:
60
+ - `Alt+Enter` or `Cmd+Enter`
61
+ - Double `Enter` (press Enter twice quickly)
62
+
63
+ ### confirm
64
+ Yes/no confirmation prompt.
65
+
66
+ ```typescript
67
+ const confirmed = await context.prompts.confirm({
68
+ message: 'Continue with deployment?'
69
+ });
70
+ ```
71
+
72
+ ### select
73
+ Single-choice selection from options.
74
+
75
+ ```typescript
76
+ const environment = await context.prompts.select({
77
+ message: 'Select environment:',
78
+ options: [
79
+ { value: 'dev', label: 'Development' },
80
+ { value: 'staging', label: 'Staging' },
81
+ { value: 'production', label: 'Production' }
82
+ ],
83
+ initialValue: 'dev'
84
+ });
85
+ ```
86
+
87
+ ### multiselect
88
+ Multi-choice selection from options.
89
+
90
+ ```typescript
91
+ const features = await context.prompts.multiselect({
92
+ message: 'Select features:',
93
+ options: [
94
+ { value: 'auth', label: 'Authentication' },
95
+ { value: 'db', label: 'Database' },
96
+ { value: 'cache', label: 'Caching' }
97
+ ],
98
+ required: false
99
+ });
100
+ ```
101
+
102
+ ### group
103
+ Run multiple prompts and collect results.
104
+
105
+ ```typescript
106
+ const results = await context.prompts.group([
107
+ {
108
+ key: 'name',
109
+ run: async () => await context.prompts.text({ message: 'Name:' })
110
+ },
111
+ {
112
+ key: 'email',
113
+ run: async () => await context.prompts.text({ message: 'Email:' })
114
+ }
115
+ ]);
116
+ // results = { name: '...', email: '...' }
117
+ ```
118
+
119
+ ### custom
120
+ Custom prompt with validation and parsing to different types.
121
+
122
+ ```typescript
123
+ const port = await context.prompts.custom<number>({
124
+ message: 'Enter port number:',
125
+ defaultValue: '3000',
126
+ required: true,
127
+ parse: (raw) => parseInt(raw, 10),
128
+ validate: (value) => {
129
+ if (isNaN(value)) return 'Must be a number';
130
+ if (value < 1 || value > 65535) return 'Must be between 1-65535';
131
+ return undefined;
132
+ }
133
+ });
134
+ ```
135
+
136
+ ## Advanced UI Components (via SnapTui)
137
+
138
+ Import these from `snap-framework` or access via `SnapTui` namespace:
139
+
140
+ ### Spinner (createSpinner/spinner)
141
+
142
+ **Loader component for async operations.**
143
+
144
+ ```typescript
145
+ import { createSpinner } from 'snap-framework';
146
+ // or
147
+ const spinner = SnapTui.createSpinner();
148
+
149
+ spinner.start('Loading...');
150
+ // ... do work
151
+ spinner.message('Still working...');
152
+ // ... more work
153
+ spinner.stop('Complete!');
154
+ ```
155
+
156
+ **⚠️ Important: Avoid Rapid Message Updates**
157
+
158
+ Updating the spinner message too frequently (multiple times per second) can cause UI tearing and rendering issues. The spinner uses ANSI escape codes to update in-place, and rapid updates can overwhelm the terminal.
159
+
160
+ **❌ AVOID - This causes UI tearing:**
161
+ ```typescript
162
+ // DON'T: Update in a tight loop
163
+ for (let i = 0; i < 1000; i++) {
164
+ spinner.message(`Processing item ${i}`); // Too fast!
165
+ await processItem(i);
166
+ }
167
+ ```
168
+
169
+ **✅ RECOMMENDED - Batch updates or throttle:**
170
+ ```typescript
171
+ // DO: Update at reasonable intervals
172
+ for (let i = 0; i < 1000; i++) {
173
+ // Only update every 100 items
174
+ if (i % 100 === 0) {
175
+ spinner.message(`Processing item ${i}`);
176
+ }
177
+ await processItem(i);
178
+ }
179
+ spinner.stop(`Processed ${1000} items`);
180
+ ```
181
+
182
+ **✅ Alternative - Use percentage for progress:**
183
+ ```typescript
184
+ const total = 1000;
185
+ for (let i = 0; i < total; i++) {
186
+ // Update every 5% progress
187
+ if (i % Math.floor(total * 0.05) === 0) {
188
+ const percent = Math.floor((i / total) * 100);
189
+ spinner.message(`Processing... ${percent}%`);
190
+ }
191
+ await processItem(i);
192
+ }
193
+ ```
194
+
195
+ **Interface:**
196
+ ```typescript
197
+ interface Spinner {
198
+ start(message?: string): void;
199
+ stop(message?: string): void;
200
+ cancel(message?: string): void;
201
+ error(message?: string): void;
202
+ message(message: string): void;
203
+ clear(): void;
204
+ readonly isCancelled: boolean;
205
+ }
206
+
207
+ interface SpinnerOptions {
208
+ message?: string;
209
+ indicator?: 'dots' | 'timer';
210
+ onCancel?: () => void;
211
+ cancelMessage?: string;
212
+ errorMessage?: string;
213
+ frames?: string[];
214
+ delay?: number;
215
+ styleFrame?: (frame: string) => string;
216
+ }
217
+ ```
218
+
219
+ ### Password Prompt (runPasswordPrompt)
220
+
221
+ **Secure text input for sensitive data.**
222
+
223
+ ```typescript
224
+ import { runPasswordPrompt } from 'snap-framework';
225
+
226
+ const password = await runPasswordPrompt({
227
+ message: 'Enter password:',
228
+ required: true,
229
+ mask: '•'
230
+ });
231
+ ```
232
+
233
+ **Interface:**
234
+ ```typescript
235
+ interface PasswordPromptInput {
236
+ message: string;
237
+ required?: boolean;
238
+ validate?: (value: string) => string | Error | undefined;
239
+ mask?: string;
240
+ }
241
+ ```
242
+
243
+ ### Progress (createProgress/progress)
244
+
245
+ **Progress indicator for quantified operations.**
246
+
247
+ ```typescript
248
+ const progress = SnapTui.createProgress();
249
+
250
+ progress.start('Processing files...');
251
+ progress.message('File 1 of 10...');
252
+ progress.message('File 2 of 10...');
253
+ progress.stop('✓ All files processed');
254
+ ```
255
+
256
+ **Interface:**
257
+ ```typescript
258
+ interface Progress {
259
+ start(message: string): void;
260
+ message(message: string): void;
261
+ stop(message?: string): void;
262
+ }
263
+ ```
264
+
265
+ ### Tasks (tasks)
266
+
267
+ **Sequential async operations with visual feedback.**
268
+
269
+ ```typescript
270
+ const results = await SnapTui.tasks([
271
+ {
272
+ title: 'Install dependencies',
273
+ task: async (msg) => {
274
+ msg('Installing packages...');
275
+ await installPackages();
276
+ return 'Dependencies installed';
277
+ }
278
+ },
279
+ {
280
+ title: 'Run tests',
281
+ task: async (msg) => {
282
+ msg('Running test suite...');
283
+ await runTests();
284
+ return 'All tests passed';
285
+ }
286
+ },
287
+ {
288
+ title: 'Build',
289
+ task: async (msg) => {
290
+ msg('Compiling...');
291
+ await build();
292
+ return 'Build complete';
293
+ }
294
+ }
295
+ ]);
296
+ ```
297
+
298
+ **Interface:**
299
+ ```typescript
300
+ interface Task {
301
+ title: string;
302
+ task: (message: (msg: string) => void) => Promise<string>;
303
+ }
304
+
305
+ interface TasksOptions {
306
+ onCancel?: (results: Record<string, string>) => void;
307
+ }
308
+ ```
309
+
310
+ ### Note (note)
311
+
312
+ **Decorative message box for important information.**
313
+
314
+ ```typescript
315
+ SnapTui.note({
316
+ message: 'This operation may take several minutes.\nPlease do not close your terminal.',
317
+ title: 'INFO'
318
+ });
319
+
320
+ // With custom formatting
321
+ SnapTui.note({
322
+ message: 'You can use --help to see all options.',
323
+ title: 'TIP',
324
+ format: (line) => `💡 ${line}`
325
+ });
326
+ ```
327
+
328
+ **Interface:**
329
+ ```typescript
330
+ interface NoteInput {
331
+ message: string;
332
+ title?: string;
333
+ format?: (line: string) => string;
334
+ }
335
+ ```
336
+
337
+ ### Autocomplete (runAutocompletePrompt)
338
+
339
+ **Searchable selection for large option lists.**
340
+
341
+ ```typescript
342
+ const selected = await SnapTui.runAutocompletePrompt({
343
+ message: 'Select a package:',
344
+ options: [
345
+ { value: 'react', label: 'React', hint: 'A JavaScript library for UIs' },
346
+ { value: 'vue', label: 'Vue.js', hint: 'Progressive framework' },
347
+ { value: 'svelte', label: 'Svelte', hint: 'Cybernetically enhanced web apps' }
348
+ ],
349
+ placeholder: 'Search packages...',
350
+ maxItems: 5,
351
+ required: true
352
+ });
353
+ ```
354
+
355
+ **Interface:**
356
+ ```typescript
357
+ interface AutocompleteOption {
358
+ value: string;
359
+ label: string;
360
+ hint?: string;
361
+ }
362
+
363
+ interface AutocompleteInput {
364
+ message: string;
365
+ options: AutocompleteOption[];
366
+ placeholder?: string;
367
+ initialValue?: string;
368
+ maxItems?: number;
369
+ required?: boolean;
370
+ validate?: (value: string) => string | Error | undefined;
371
+ }
372
+ ```
373
+
374
+ ## Terminal Utilities (via SnapTerminal)
375
+
376
+ ### Intro/Outro
377
+
378
+ **Consistent welcome and closing messages.**
379
+
380
+ ```typescript
381
+ import * as SnapTerminal from 'snap-framework';
382
+
383
+ SnapTerminal.intro('Welcome to My Tool');
384
+ SnapTerminal.outro('Thank you for using My Tool');
385
+ ```
386
+
387
+ ### Log
388
+
389
+ **Structured logging with different levels.**
390
+
391
+ ```typescript
392
+ SnapTerminal.log.info('Processing started...');
393
+ SnapTerminal.log.success('Operation completed!');
394
+ SnapTerminal.log.warn('Configuration file not found, using defaults');
395
+ SnapTerminal.log.error('Failed to connect to service');
396
+ ```
397
+
398
+ ## Terminal Output (via context.terminal)
399
+
400
+ Basic terminal output available in all actions:
401
+
402
+ ```typescript
403
+ context.terminal.line('Single line of output');
404
+ context.terminal.lines(['Line 1', 'Line 2', 'Line 3']);
405
+ context.terminal.error('Error message');
406
+ ```
407
+
408
+ ## Component Import Summary
409
+
410
+ | Component | Import From | Also Available Via |
411
+ |-----------|-------------|-------------------|
412
+ | text, confirm, select, multiselect, group, custom | `createPromptToolkit()` | `context.prompts` |
413
+ | text with paste/multiline support | `'snap-framework'` | `context.prompts.text({ paste: true, multiline: true })` |
414
+ | createSpinner, spinner | `'snap-framework'` | `SnapTui.createSpinner` |
415
+ | runPasswordPrompt | `'snap-framework'` | Direct import only |
416
+ | createProgress, progress | - | `SnapTui.createProgress` |
417
+ | tasks | - | `SnapTui.tasks` |
418
+ | note | - | `SnapTui.note` |
419
+ | runAutocompletePrompt | - | `SnapTui.runAutocompletePrompt` |
420
+ | intro, outro | - | `SnapTerminal.intro`, `SnapTerminal.outro` |
421
+ | log | - | `SnapTerminal.log` |
422
+
423
+ ## Example: Using Multiple Components
424
+
425
+ ```typescript
426
+ import type { ModuleContract } from 'snap-framework';
427
+ import { ExitCode } from 'snap-framework';
428
+ import { createSpinner, runPasswordPrompt } from 'snap-framework';
429
+ import * as SnapTui from 'snap-framework';
430
+ import * as SnapTerminal from 'snap-framework';
431
+
432
+ const myModule: ModuleContract = {
433
+ moduleId: 'my',
434
+ description: 'My module',
435
+ actions: [{
436
+ actionId: 'action',
437
+ description: 'My action',
438
+ tui: { steps: ['step1'] },
439
+ commandline: { requiredArgs: [] },
440
+ help: { summary: 'My action' },
441
+ run: async (context) => {
442
+ // Intro
443
+ SnapTerminal.intro('Starting operation');
444
+
445
+ // Note
446
+ SnapTui.note({
447
+ message: 'This will take a few moments.',
448
+ title: 'INFO'
449
+ });
450
+
451
+ // Spinner
452
+ const spinner = createSpinner();
453
+ spinner.start('Processing...');
454
+ await doWork();
455
+ spinner.stop('✓ Complete');
456
+
457
+ // Autocomplete
458
+ const selection = await SnapTui.runAutocompletePrompt({
459
+ message: 'Choose an option:',
460
+ options: [
461
+ { value: 'a', label: 'Option A' },
462
+ { value: 'b', label: 'Option B' }
463
+ ]
464
+ });
465
+
466
+ // Tasks
467
+ const results = await SnapTui.tasks([
468
+ {
469
+ title: 'Task 1',
470
+ task: async () => 'Done'
471
+ }
472
+ ]);
473
+
474
+ // Log
475
+ SnapTerminal.log.success('All operations complete');
476
+
477
+ // Outro
478
+ SnapTerminal.outro('Finished!');
479
+
480
+ return {
481
+ ok: true,
482
+ mode: context.mode,
483
+ exitCode: ExitCode.SUCCESS,
484
+ data: { selection, results }
485
+ };
486
+ }
487
+ }]
488
+ };
489
+ ```
490
+
491
+ ## Not Available (Use Alternatives)
492
+
493
+ ### Progress Bar
494
+ Snap uses spinner-style progress indicators rather than percentage-based progress bars. Use `createSpinner` or `createProgress` for visual feedback.
495
+
496
+ ### Table Display
497
+ Use terminal output with formatted strings:
498
+
499
+ ```typescript
500
+ context.terminal.line('Name Email Role');
501
+ context.terminal.line('───────────── ─────────────── ──────');
502
+ for (const user of users) {
503
+ context.terminal.line(
504
+ `${user.name.padEnd(13)} ${user.email.padEnd(16)} ${user.role}`
505
+ );
506
+ }
507
+ ```
508
+
509
+ ### Forms with Field Dependencies
510
+ Use conditional flow control in your action:
511
+
512
+ ```typescript
513
+ run: async (context) => {
514
+ const useFeature = await context.prompts.confirm({
515
+ message: 'Enable this feature?'
516
+ });
517
+
518
+ if (useFeature) {
519
+ const option = await context.prompts.select({
520
+ message: 'Choose configuration:',
521
+ options: [/* ... */]
522
+ });
523
+ // ... configure feature
524
+ }
525
+
526
+ return { ok: true, /* ... */ };
527
+ }
528
+ ```
529
+
530
+ ## Component Type Reference
531
+
532
+ ### TuiOptionContract
533
+ ```typescript
534
+ interface TuiOptionContract {
535
+ value: string;
536
+ label: string;
537
+ }
538
+ ```
539
+
540
+ ### AutocompleteOption
541
+ ```typescript
542
+ interface AutocompleteOption {
543
+ value: string;
544
+ label: string;
545
+ hint?: string;
546
+ }
547
+ ```
548
+
549
+ ### TuiComponentContract (Flow Definitions)
550
+ ```typescript
551
+ type TuiComponentContract =
552
+ | TextTuiComponent // type: 'text'
553
+ | SelectTuiComponent // type: 'select'
554
+ | MultiSelectTuiComponent // type: 'multiselect'
555
+ | ConfirmTuiComponent // type: 'confirm'
556
+ | TuiCustomComponentContract; // type: 'custom'
557
+ ```