@levu/snap 0.1.0 → 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 (138) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +33 -2
  3. package/dist/cli/cli-runner.d.ts +37 -0
  4. package/dist/cli/cli-runner.js +161 -0
  5. package/dist/cli-entry.js +6 -64
  6. package/dist/core/contracts/action-contract.d.ts +1 -3
  7. package/dist/core/contracts/tui-contract.d.ts +47 -0
  8. package/dist/core/contracts/tui-contract.js +1 -0
  9. package/dist/core/registry/action-registry.js +3 -1
  10. package/dist/dx/args/env.d.ts +6 -0
  11. package/dist/dx/args/env.js +15 -0
  12. package/dist/dx/args/index.d.ts +4 -0
  13. package/dist/dx/args/index.js +3 -0
  14. package/dist/dx/args/readers.d.ts +5 -0
  15. package/dist/dx/args/readers.js +36 -0
  16. package/dist/dx/args/types.d.ts +5 -0
  17. package/dist/dx/args/types.js +1 -0
  18. package/dist/dx/help/builder.d.ts +10 -0
  19. package/dist/dx/help/builder.js +11 -0
  20. package/dist/dx/help/index.d.ts +4 -0
  21. package/dist/dx/help/index.js +2 -0
  22. package/dist/dx/help/schema.d.ts +14 -0
  23. package/dist/dx/help/schema.js +33 -0
  24. package/dist/dx/runtime/action-result.d.ts +12 -0
  25. package/dist/dx/runtime/action-result.js +35 -0
  26. package/dist/dx/runtime/flow.d.ts +9 -0
  27. package/dist/dx/runtime/flow.js +19 -0
  28. package/dist/dx/runtime/index.d.ts +4 -0
  29. package/dist/dx/runtime/index.js +2 -0
  30. package/dist/dx/terminal/index.d.ts +3 -0
  31. package/dist/dx/terminal/index.js +3 -0
  32. package/dist/dx/terminal/intro-outro.d.ts +4 -0
  33. package/dist/dx/terminal/intro-outro.js +44 -0
  34. package/dist/dx/terminal/output.d.ts +19 -0
  35. package/dist/dx/terminal/output.js +59 -0
  36. package/dist/dx/tui/components.d.ts +6 -0
  37. package/dist/dx/tui/components.js +40 -0
  38. package/dist/dx/tui/flow.d.ts +2 -0
  39. package/dist/dx/tui/flow.js +14 -0
  40. package/dist/dx/tui/index.d.ts +16 -0
  41. package/dist/dx/tui/index.js +15 -0
  42. package/dist/dx/tui/no-result.d.ts +13 -0
  43. package/dist/dx/tui/no-result.js +18 -0
  44. package/dist/help/help-renderer.js +5 -1
  45. package/dist/help/hierarchy-resolver.js +1 -1
  46. package/dist/index.d.ts +16 -0
  47. package/dist/index.js +11 -0
  48. package/dist/runtime/dispatch.d.ts +2 -1
  49. package/dist/runtime/engine.d.ts +2 -1
  50. package/dist/runtime/engine.js +22 -1
  51. package/dist/runtime/mode-resolver.d.ts +3 -2
  52. package/dist/runtime/runtime-context.d.ts +8 -1
  53. package/dist/tui/component-adapters/autocomplete.d.ts +15 -0
  54. package/dist/tui/component-adapters/autocomplete.js +34 -0
  55. package/dist/tui/component-adapters/cancel.d.ts +6 -0
  56. package/dist/tui/component-adapters/cancel.js +20 -0
  57. package/dist/tui/component-adapters/confirm.d.ts +2 -0
  58. package/dist/tui/component-adapters/confirm.js +13 -1
  59. package/dist/tui/component-adapters/multiselect.d.ts +4 -0
  60. package/dist/tui/component-adapters/multiselect.js +23 -3
  61. package/dist/tui/component-adapters/note.d.ts +7 -0
  62. package/dist/tui/component-adapters/note.js +23 -0
  63. package/dist/tui/component-adapters/password.d.ts +7 -0
  64. package/dist/tui/component-adapters/password.js +24 -0
  65. package/dist/tui/component-adapters/progress.d.ts +7 -0
  66. package/dist/tui/component-adapters/progress.js +44 -0
  67. package/dist/tui/component-adapters/readline-utils.d.ts +1 -0
  68. package/dist/tui/component-adapters/readline-utils.js +2 -0
  69. package/dist/tui/component-adapters/select.d.ts +2 -0
  70. package/dist/tui/component-adapters/select.js +25 -3
  71. package/dist/tui/component-adapters/spinner.d.ts +10 -0
  72. package/dist/tui/component-adapters/spinner.js +48 -0
  73. package/dist/tui/component-adapters/tasks.d.ts +9 -0
  74. package/dist/tui/component-adapters/tasks.js +31 -0
  75. package/dist/tui/component-adapters/text.d.ts +2 -0
  76. package/dist/tui/component-adapters/text.js +21 -4
  77. package/dist/tui/custom/custom-prompt.d.ts +16 -0
  78. package/dist/tui/custom/custom-prompt.js +72 -0
  79. package/dist/tui/custom/index.d.ts +2 -0
  80. package/dist/tui/custom/index.js +1 -0
  81. package/dist/tui/prompt-toolkit.d.ts +15 -0
  82. package/dist/tui/prompt-toolkit.js +17 -0
  83. package/docs/component-reference.md +474 -0
  84. package/docs/getting-started.md +242 -0
  85. package/docs/integration-examples.md +677 -0
  86. package/docs/module-authoring-guide.md +105 -1
  87. package/docs/snap-args.md +323 -0
  88. package/docs/snap-help.md +372 -0
  89. package/docs/snap-runtime.md +394 -0
  90. package/docs/snap-terminal.md +410 -0
  91. package/docs/snap-tui.md +529 -0
  92. package/package.json +15 -2
  93. package/.github/workflows/ci.yml +0 -26
  94. package/plans/260209-1547-hub-dual-runtime-framework/phase-01-foundation-and-contracts.md +0 -71
  95. package/plans/260209-1547-hub-dual-runtime-framework/phase-02-runtime-and-state-machine.md +0 -76
  96. package/plans/260209-1547-hub-dual-runtime-framework/phase-03-tui-components-and-policies.md +0 -71
  97. package/plans/260209-1547-hub-dual-runtime-framework/phase-04-help-system-and-ai-readability.md +0 -69
  98. package/plans/260209-1547-hub-dual-runtime-framework/phase-05-testing-and-quality-gates.md +0 -79
  99. package/plans/260209-1547-hub-dual-runtime-framework/phase-06-sample-modules-and-adoption.md +0 -75
  100. package/plans/260209-1547-hub-dual-runtime-framework/plan.md +0 -105
  101. package/plans/260209-1547-hub-dual-runtime-framework/reports/planner-report.md +0 -27
  102. package/plans/260209-1547-hub-dual-runtime-framework/research/researcher-01-report.md +0 -166
  103. package/plans/260209-1547-hub-dual-runtime-framework/research/researcher-02-report.md +0 -87
  104. package/plans/260209-1547-hub-dual-runtime-framework/scout/scout-01-report.md +0 -24
  105. package/src/cli/help-command.ts +0 -1
  106. package/src/cli-entry.ts +0 -83
  107. package/src/core/contracts/action-contract.ts +0 -30
  108. package/src/core/contracts/help-contract.ts +0 -20
  109. package/src/core/contracts/module-contract.ts +0 -7
  110. package/src/core/errors/framework-errors.ts +0 -26
  111. package/src/core/registry/action-registry.ts +0 -94
  112. package/src/help/help-command.ts +0 -32
  113. package/src/help/help-model.ts +0 -10
  114. package/src/help/help-renderer.ts +0 -21
  115. package/src/help/hierarchy-resolver.ts +0 -54
  116. package/src/index.ts +0 -10
  117. package/src/modules/sample-content/module.ts +0 -66
  118. package/src/modules/sample-system/module.ts +0 -74
  119. package/src/runtime/dispatch.ts +0 -64
  120. package/src/runtime/engine.ts +0 -59
  121. package/src/runtime/mode-resolver.ts +0 -18
  122. package/src/runtime/resume-store.ts +0 -53
  123. package/src/runtime/runtime-context.ts +0 -10
  124. package/src/runtime/state-machine.ts +0 -77
  125. package/src/tui/accessibility-footer.ts +0 -11
  126. package/src/tui/component-adapters/confirm.ts +0 -8
  127. package/src/tui/component-adapters/group.ts +0 -12
  128. package/src/tui/component-adapters/multiselect.ts +0 -22
  129. package/src/tui/component-adapters/select.ts +0 -18
  130. package/src/tui/component-adapters/text.ts +0 -13
  131. package/src/tui/interrupt-handlers.ts +0 -15
  132. package/tests/e2e/cli-smoke.e2e.test.ts +0 -19
  133. package/tests/integration/runtime-dispatch.integration.test.ts +0 -23
  134. package/tests/transcript/help.transcript.test.ts +0 -20
  135. package/tests/unit/state-machine.test.ts +0 -22
  136. package/tsconfig.build.json +0 -9
  137. package/tsconfig.json +0 -17
  138. package/vitest.config.ts +0 -8
@@ -1,9 +1,29 @@
1
+ import { multiselect } from '@clack/prompts';
2
+ import { isInteractiveTerminal } from './readline-utils.js';
3
+ import { unwrapClackResult } from './cancel.js';
1
4
  export const runMultiSelectPrompt = async (input) => {
2
5
  if (input.options.length === 0) {
3
6
  throw new Error(`No options available for multiselect prompt: ${input.message}`);
4
7
  }
5
- if (input.initialValues && input.initialValues.length > 0) {
6
- return input.initialValues.filter((value) => input.options.some((option) => option.value === value));
8
+ const defaultValues = input.initialValues && input.initialValues.length > 0
9
+ ? input.initialValues.filter((value) => input.options.some((option) => option.value === value))
10
+ : [input.options[0].value];
11
+ if (!isInteractiveTerminal()) {
12
+ return defaultValues;
7
13
  }
8
- return [input.options[0].value];
14
+ const message = input.showInstructions === false
15
+ ? input.message
16
+ : `${input.message} (space to toggle, a to select all)`;
17
+ const selected = await multiselect({
18
+ message,
19
+ options: input.options.map((option) => ({
20
+ value: option.value,
21
+ label: option.label,
22
+ hint: option.hint,
23
+ disabled: option.disabled
24
+ })),
25
+ initialValues: defaultValues,
26
+ required: input.required ?? false
27
+ });
28
+ return unwrapClackResult(selected);
9
29
  };
@@ -0,0 +1,7 @@
1
+ import type { Writable } from 'node:stream';
2
+ export interface NoteInput {
3
+ message: string;
4
+ title?: string;
5
+ format?: (line: string) => string;
6
+ }
7
+ export declare const note: (input: NoteInput, stdout?: Writable) => void;
@@ -0,0 +1,23 @@
1
+ const boxChars = {
2
+ topLeft: '╮',
3
+ topRight: '╯',
4
+ bottomLeft: '╰',
5
+ bottomRight: '╯',
6
+ horizontal: '─',
7
+ vertical: '│'
8
+ };
9
+ export const note = (input, stdout = process.stdout) => {
10
+ const { message, title = '', format } = input;
11
+ const lines = message.split('\n');
12
+ const formattedLines = format ? lines.map(format) : lines;
13
+ const maxLength = Math.max(...formattedLines.map((line) => line.length), title.length);
14
+ const padding = 2;
15
+ // Top line
16
+ stdout.write(`${boxChars.vertical} ${title.padEnd(maxLength + padding)} ${boxChars.topLeft}${boxChars.horizontal.repeat(maxLength + padding * 2)}${boxChars.horizontal}\n`);
17
+ // Message lines
18
+ for (const line of formattedLines) {
19
+ stdout.write(`${boxChars.vertical} ${' '.repeat(maxLength + padding)} ${boxChars.vertical} ${line.padEnd(maxLength + padding)} ${boxChars.vertical}\n`);
20
+ }
21
+ // Bottom line
22
+ stdout.write(`${boxChars.horizontal}${boxChars.horizontal.repeat(maxLength + padding * 2)}${boxChars.horizontal}${boxChars.horizontal.repeat(maxLength + padding * 2)}${boxChars.vertical}\n`);
23
+ };
@@ -0,0 +1,7 @@
1
+ export interface PasswordPromptInput {
2
+ message: string;
3
+ required?: boolean;
4
+ validate?: (value: string) => string | Error | undefined;
5
+ mask?: string;
6
+ }
7
+ export declare const runPasswordPrompt: (input: PasswordPromptInput) => Promise<string>;
@@ -0,0 +1,24 @@
1
+ import { password as clackPassword } from '@clack/prompts';
2
+ import { isInteractiveTerminal } from './readline-utils.js';
3
+ import { unwrapClackResult } from './cancel.js';
4
+ export const runPasswordPrompt = async (input) => {
5
+ if (!isInteractiveTerminal()) {
6
+ // For non-interactive terminals, read from stdin in a secure way
7
+ // or return empty/throw error
8
+ if (input.required) {
9
+ throw new Error(`Password required: ${input.message}`);
10
+ }
11
+ return '';
12
+ }
13
+ const value = await clackPassword({
14
+ message: input.message,
15
+ mask: input.mask ?? '•',
16
+ validate: (raw) => {
17
+ if (input.required && (!raw || raw.trim().length === 0)) {
18
+ return `Password is required`;
19
+ }
20
+ return input.validate?.(raw ?? '');
21
+ }
22
+ });
23
+ return unwrapClackResult(value);
24
+ };
@@ -0,0 +1,7 @@
1
+ export interface Progress {
2
+ start(message: string): void;
3
+ message(message: string): void;
4
+ stop(message?: string): void;
5
+ }
6
+ export declare const createProgress: () => Progress;
7
+ export declare const progress: () => Progress;
@@ -0,0 +1,44 @@
1
+ import { isInteractiveTerminal } from './readline-utils.js';
2
+ export const createProgress = () => {
3
+ // Non-interactive fallback
4
+ if (!isInteractiveTerminal()) {
5
+ let currentMessage = '';
6
+ return {
7
+ start(message) {
8
+ currentMessage = message;
9
+ process.stdout.write(`${message}...\n`);
10
+ },
11
+ message(newMessage) {
12
+ currentMessage = newMessage;
13
+ process.stdout.write(`${newMessage}\n`);
14
+ },
15
+ stop(finalMessage) {
16
+ if (finalMessage) {
17
+ process.stdout.write(`${finalMessage}\n`);
18
+ }
19
+ }
20
+ };
21
+ }
22
+ // Interactive progress using @clack/prompts
23
+ let progressInstance = null;
24
+ return {
25
+ start(message) {
26
+ // Lazy load progress
27
+ import('@clack/prompts').then(({ progress }) => {
28
+ progressInstance = progress();
29
+ progressInstance.start(message);
30
+ });
31
+ },
32
+ message(newMessage) {
33
+ if (progressInstance) {
34
+ progressInstance.message(newMessage);
35
+ }
36
+ },
37
+ stop(finalMessage) {
38
+ if (progressInstance) {
39
+ progressInstance.stop(finalMessage);
40
+ }
41
+ }
42
+ };
43
+ };
44
+ export const progress = createProgress;
@@ -0,0 +1 @@
1
+ export declare const isInteractiveTerminal: () => boolean;
@@ -0,0 +1,2 @@
1
+ import { stdin as input, stdout as output } from 'node:process';
2
+ export const isInteractiveTerminal = () => Boolean(input.isTTY && output.isTTY);
@@ -1,6 +1,8 @@
1
1
  export interface SelectOption {
2
2
  value: string;
3
3
  label: string;
4
+ hint?: string;
5
+ disabled?: boolean;
4
6
  }
5
7
  export interface SelectPromptInput {
6
8
  message: string;
@@ -1,7 +1,29 @@
1
+ import { select } from '@clack/prompts';
2
+ import { isInteractiveTerminal } from './readline-utils.js';
3
+ import { unwrapClackResult } from './cancel.js';
1
4
  export const runSelectPrompt = async (input) => {
2
- const selected = input.initialValue ?? input.options[0]?.value;
3
- if (!selected) {
5
+ if (input.options.length === 0) {
4
6
  throw new Error(`No options available for select prompt: ${input.message}`);
5
7
  }
6
- return selected;
8
+ const initialValue = input.initialValue && input.options.some((option) => option.value === input.initialValue)
9
+ ? input.initialValue
10
+ : undefined;
11
+ const defaultValue = initialValue ?? input.options[0]?.value;
12
+ if (!defaultValue) {
13
+ throw new Error(`No options available for select prompt: ${input.message}`);
14
+ }
15
+ if (!isInteractiveTerminal()) {
16
+ return defaultValue;
17
+ }
18
+ const selection = await select({
19
+ message: input.message,
20
+ options: input.options.map((option) => ({
21
+ value: option.value,
22
+ label: option.label,
23
+ hint: option.hint,
24
+ disabled: option.disabled
25
+ })),
26
+ initialValue: defaultValue
27
+ });
28
+ return unwrapClackResult(selection);
7
29
  };
@@ -0,0 +1,10 @@
1
+ export interface SpinnerOptions {
2
+ message?: string;
3
+ }
4
+ export interface Spinner {
5
+ start(message?: string): void;
6
+ stop(message?: string): void;
7
+ message(message: string): void;
8
+ }
9
+ export declare const createSpinner: (options?: SpinnerOptions) => Spinner;
10
+ export declare const spinner: (options?: SpinnerOptions) => Spinner;
@@ -0,0 +1,48 @@
1
+ import { spinner as clackSpinner } from '@clack/prompts';
2
+ import { isInteractiveTerminal } from './readline-utils.js';
3
+ export const createSpinner = (options = {}) => {
4
+ // Non-interactive fallback
5
+ if (!isInteractiveTerminal()) {
6
+ let currentMessage = options.message ?? '';
7
+ return {
8
+ start(message) {
9
+ currentMessage = message ?? currentMessage;
10
+ if (currentMessage) {
11
+ process.stdout.write(`${currentMessage}...\n`);
12
+ }
13
+ },
14
+ stop(message) {
15
+ if (message) {
16
+ process.stdout.write(`${message}\n`);
17
+ }
18
+ },
19
+ message(newMessage) {
20
+ currentMessage = newMessage;
21
+ }
22
+ };
23
+ }
24
+ // Interactive spinner using @clack/prompts
25
+ const internalSpinner = clackSpinner();
26
+ return {
27
+ start(message) {
28
+ if (message) {
29
+ internalSpinner.start(message);
30
+ }
31
+ else if (options.message) {
32
+ internalSpinner.start(options.message);
33
+ }
34
+ },
35
+ stop(message) {
36
+ if (message) {
37
+ internalSpinner.stop(message);
38
+ }
39
+ else {
40
+ internalSpinner.stop();
41
+ }
42
+ },
43
+ message(newMessage) {
44
+ internalSpinner.message(newMessage);
45
+ }
46
+ };
47
+ };
48
+ export const spinner = createSpinner;
@@ -0,0 +1,9 @@
1
+ export interface Task {
2
+ title: string;
3
+ task: (message: (msg: string) => void) => Promise<string>;
4
+ }
5
+ export interface TasksOptions {
6
+ /** Called when a task is cancelled */
7
+ onCancel?: (results: Record<string, string>) => void;
8
+ }
9
+ export declare const tasks: (taskList: Task[], options?: TasksOptions) => Promise<Record<string, string>>;
@@ -0,0 +1,31 @@
1
+ import { isInteractiveTerminal } from './readline-utils.js';
2
+ export const tasks = async (taskList, options = {}) => {
3
+ const results = {};
4
+ if (!isInteractiveTerminal()) {
5
+ // Non-interactive: just run tasks without spinner
6
+ for (const taskItem of taskList) {
7
+ try {
8
+ const result = await taskItem.task(() => { });
9
+ results[taskItem.title] = result;
10
+ process.stdout.write(`${taskItem.title}: ${result}\n`);
11
+ }
12
+ catch (error) {
13
+ results[taskItem.title] = error instanceof Error ? error.message : String(error);
14
+ }
15
+ }
16
+ return results;
17
+ }
18
+ // Import clack dynamically for interactive mode
19
+ const { tasks: clackTasks } = await import('@clack/prompts');
20
+ // Wrap tasks to collect results
21
+ const wrappedTasks = taskList.map((task) => ({
22
+ ...task,
23
+ task: async (message) => {
24
+ const result = await task.task(message);
25
+ results[task.title] = result;
26
+ return result;
27
+ }
28
+ }));
29
+ await clackTasks(wrappedTasks, options);
30
+ return results;
31
+ };
@@ -2,5 +2,7 @@ export interface TextPromptInput {
2
2
  message: string;
3
3
  initialValue?: string;
4
4
  required?: boolean;
5
+ placeholder?: string;
6
+ validate?: (value: string) => string | Error | undefined;
5
7
  }
6
8
  export declare const runTextPrompt: (input: TextPromptInput) => Promise<string>;
@@ -1,7 +1,24 @@
1
+ import { text } from '@clack/prompts';
2
+ import { isInteractiveTerminal } from './readline-utils.js';
3
+ import { unwrapClackResult } from './cancel.js';
1
4
  export const runTextPrompt = async (input) => {
2
- const value = input.initialValue ?? '';
3
- if (input.required && value.trim().length === 0) {
4
- throw new Error(`Required text value missing: ${input.message}`);
5
+ const fallbackValue = input.initialValue ?? '';
6
+ if (!isInteractiveTerminal()) {
7
+ if (input.required && fallbackValue.trim().length === 0) {
8
+ throw new Error(`Required text value missing: ${input.message}`);
9
+ }
10
+ return fallbackValue;
5
11
  }
6
- return value;
12
+ const value = await text({
13
+ message: input.message,
14
+ initialValue: input.initialValue,
15
+ placeholder: input.placeholder,
16
+ validate: (raw) => {
17
+ if (input.required && (!raw || raw.trim().length === 0)) {
18
+ return `Required text value missing: ${input.message}`;
19
+ }
20
+ return input.validate?.(raw ?? '');
21
+ }
22
+ });
23
+ return unwrapClackResult(value);
7
24
  };
@@ -0,0 +1,16 @@
1
+ export interface CustomPromptInput<TValue = string> {
2
+ message: string;
3
+ defaultValue?: string;
4
+ required?: boolean;
5
+ signal?: AbortSignal;
6
+ parse?: (raw: string) => TValue;
7
+ validate?: (value: TValue) => string | undefined;
8
+ onValue?: (value: TValue) => void;
9
+ onSubmit?: (value: TValue) => void;
10
+ onCancel?: () => void;
11
+ }
12
+ export interface CustomPromptRunner {
13
+ run<TValue = string>(input: CustomPromptInput<TValue>): Promise<TValue>;
14
+ }
15
+ export declare const runCustomPrompt: <TValue = string>(input: CustomPromptInput<TValue>) => Promise<TValue>;
16
+ export declare const createCustomPromptRunner: () => CustomPromptRunner;
@@ -0,0 +1,72 @@
1
+ import { text } from '@clack/prompts';
2
+ import { isInteractiveTerminal } from '../component-adapters/readline-utils.js';
3
+ import { unwrapClackResult } from '../component-adapters/cancel.js';
4
+ const toAbortError = () => {
5
+ const error = new Error('Prompt aborted.');
6
+ error.name = 'AbortError';
7
+ return error;
8
+ };
9
+ const assertNotAborted = (signal) => {
10
+ if (signal?.aborted) {
11
+ throw toAbortError();
12
+ }
13
+ };
14
+ const defaultParse = (raw) => raw;
15
+ export const runCustomPrompt = async (input) => {
16
+ const parse = input.parse ?? (defaultParse);
17
+ const fallback = input.defaultValue ?? '';
18
+ const validateValue = (value) => {
19
+ const message = input.validate?.(value);
20
+ if (message)
21
+ throw new Error(message);
22
+ };
23
+ const parseAndValidate = (raw) => {
24
+ const parsed = parse(raw);
25
+ validateValue(parsed);
26
+ input.onValue?.(parsed);
27
+ return parsed;
28
+ };
29
+ assertNotAborted(input.signal);
30
+ if (!isInteractiveTerminal()) {
31
+ if (input.required && fallback.trim().length === 0) {
32
+ throw new Error(`Required value missing: ${input.message}`);
33
+ }
34
+ const value = parseAndValidate(fallback);
35
+ input.onSubmit?.(value);
36
+ return value;
37
+ }
38
+ const onAbort = () => {
39
+ input.onCancel?.();
40
+ };
41
+ input.signal?.addEventListener('abort', onAbort, { once: true });
42
+ try {
43
+ const rawValue = await text({
44
+ message: input.message,
45
+ initialValue: input.defaultValue,
46
+ validate: (value) => {
47
+ if ((!value || value.length === 0) && input.required && fallback.trim().length === 0) {
48
+ return `Required value missing: ${input.message}`;
49
+ }
50
+ try {
51
+ parseAndValidate(value && value.length > 0 ? value : fallback);
52
+ return undefined;
53
+ }
54
+ catch (error) {
55
+ return error instanceof Error ? error.message : 'Invalid value';
56
+ }
57
+ },
58
+ signal: input.signal
59
+ });
60
+ assertNotAborted(input.signal);
61
+ const resolved = unwrapClackResult(rawValue);
62
+ const value = parseAndValidate(resolved.length > 0 ? resolved : fallback);
63
+ input.onSubmit?.(value);
64
+ return value;
65
+ }
66
+ finally {
67
+ input.signal?.removeEventListener('abort', onAbort);
68
+ }
69
+ };
70
+ export const createCustomPromptRunner = () => ({
71
+ run: runCustomPrompt
72
+ });
@@ -0,0 +1,2 @@
1
+ export type { CustomPromptInput, CustomPromptRunner } from './custom-prompt.js';
2
+ export { runCustomPrompt, createCustomPromptRunner } from './custom-prompt.js';
@@ -0,0 +1 @@
1
+ export { runCustomPrompt, createCustomPromptRunner } from './custom-prompt.js';
@@ -0,0 +1,15 @@
1
+ import type { ConfirmPromptInput } from './component-adapters/confirm.js';
2
+ import type { GroupStep } from './component-adapters/group.js';
3
+ import type { MultiSelectPromptInput } from './component-adapters/multiselect.js';
4
+ import type { SelectPromptInput } from './component-adapters/select.js';
5
+ import type { TextPromptInput } from './component-adapters/text.js';
6
+ import { type CustomPromptInput } from './custom/custom-prompt.js';
7
+ export interface PromptToolkit {
8
+ text(input: TextPromptInput): Promise<string>;
9
+ confirm(input: ConfirmPromptInput): Promise<boolean>;
10
+ select(input: SelectPromptInput): Promise<string>;
11
+ multiselect(input: MultiSelectPromptInput): Promise<string[]>;
12
+ group<T = unknown>(steps: GroupStep<T>[]): Promise<Record<string, T>>;
13
+ custom<T>(input: CustomPromptInput<T>): Promise<T>;
14
+ }
15
+ export declare const createPromptToolkit: () => PromptToolkit;
@@ -0,0 +1,17 @@
1
+ import { runConfirmPrompt } from './component-adapters/confirm.js';
2
+ import { runGroupPrompt } from './component-adapters/group.js';
3
+ import { runMultiSelectPrompt } from './component-adapters/multiselect.js';
4
+ import { runSelectPrompt } from './component-adapters/select.js';
5
+ import { runTextPrompt } from './component-adapters/text.js';
6
+ import { createCustomPromptRunner } from './custom/custom-prompt.js';
7
+ export const createPromptToolkit = () => {
8
+ const customRunner = createCustomPromptRunner();
9
+ return {
10
+ text: runTextPrompt,
11
+ confirm: runConfirmPrompt,
12
+ select: runSelectPrompt,
13
+ multiselect: runMultiSelectPrompt,
14
+ group: runGroupPrompt,
15
+ custom: customRunner.run
16
+ };
17
+ };