@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.
- package/CHANGELOG.md +41 -0
- package/README.md +27 -2
- package/dist/dx/terminal/index.d.ts +2 -1
- package/dist/dx/terminal/index.js +3 -1
- package/dist/dx/terminal/intro-outro.d.ts +4 -0
- package/dist/dx/terminal/intro-outro.js +44 -0
- package/dist/dx/terminal/output.d.ts +13 -1
- package/dist/dx/terminal/output.js +43 -2
- package/dist/dx/tui/index.d.ts +12 -0
- package/dist/dx/tui/index.js +12 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/tui/component-adapters/autocomplete.d.ts +15 -0
- package/dist/tui/component-adapters/autocomplete.js +34 -0
- package/dist/tui/component-adapters/multiline-text.d.ts +13 -0
- package/dist/tui/component-adapters/multiline-text.js +166 -0
- package/dist/tui/component-adapters/note.d.ts +7 -0
- package/dist/tui/component-adapters/note.js +23 -0
- package/dist/tui/component-adapters/password.d.ts +7 -0
- package/dist/tui/component-adapters/password.js +24 -0
- package/dist/tui/component-adapters/progress.d.ts +7 -0
- package/dist/tui/component-adapters/progress.js +44 -0
- package/dist/tui/component-adapters/spinner.d.ts +21 -0
- package/dist/tui/component-adapters/spinner.js +89 -0
- package/dist/tui/component-adapters/tasks.d.ts +9 -0
- package/dist/tui/component-adapters/tasks.js +31 -0
- package/dist/tui/component-adapters/text.d.ts +4 -0
- package/dist/tui/component-adapters/text.js +18 -0
- package/docs/component-reference.md +557 -0
- package/docs/getting-started.md +242 -0
- package/docs/help-contract-spec.md +29 -0
- package/docs/integration-examples.md +677 -0
- package/docs/module-authoring-guide.md +156 -0
- package/docs/snap-args.md +323 -0
- package/docs/snap-help.md +372 -0
- package/docs/snap-runtime.md +394 -0
- package/docs/snap-terminal.md +410 -0
- package/docs/snap-tui.md +529 -0
- 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
|
+
```
|