@vdntio/clai 0.1.0-alpha.1

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/ai/index.d.ts +24 -0
  4. package/dist/ai/index.js +78 -0
  5. package/dist/ai/mock.d.ts +17 -0
  6. package/dist/ai/mock.js +49 -0
  7. package/dist/ai/parser.d.ts +14 -0
  8. package/dist/ai/parser.js +109 -0
  9. package/dist/ai/prompt.d.ts +12 -0
  10. package/dist/ai/prompt.js +76 -0
  11. package/dist/ai/providers/index.d.ts +1 -0
  12. package/dist/ai/providers/index.js +2 -0
  13. package/dist/ai/providers/openrouter.d.ts +31 -0
  14. package/dist/ai/providers/openrouter.js +142 -0
  15. package/dist/ai/types.d.ts +46 -0
  16. package/dist/ai/types.js +15 -0
  17. package/dist/cli/index.d.ts +19 -0
  18. package/dist/cli/index.js +71 -0
  19. package/dist/config/index.d.ts +12 -0
  20. package/dist/config/index.js +363 -0
  21. package/dist/config/types.d.ts +76 -0
  22. package/dist/config/types.js +40 -0
  23. package/dist/context/directory.d.ts +18 -0
  24. package/dist/context/directory.js +71 -0
  25. package/dist/context/history.d.ts +16 -0
  26. package/dist/context/history.js +89 -0
  27. package/dist/context/index.d.ts +24 -0
  28. package/dist/context/index.js +61 -0
  29. package/dist/context/redaction.d.ts +14 -0
  30. package/dist/context/redaction.js +57 -0
  31. package/dist/context/stdin.d.ts +13 -0
  32. package/dist/context/stdin.js +86 -0
  33. package/dist/context/system.d.ts +11 -0
  34. package/dist/context/system.js +56 -0
  35. package/dist/context/types.d.ts +31 -0
  36. package/dist/context/types.js +10 -0
  37. package/dist/error/index.d.ts +30 -0
  38. package/dist/error/index.js +50 -0
  39. package/dist/logging/file-logger.d.ts +12 -0
  40. package/dist/logging/file-logger.js +66 -0
  41. package/dist/logging/index.d.ts +15 -0
  42. package/dist/logging/index.js +33 -0
  43. package/dist/logging/logger.d.ts +15 -0
  44. package/dist/logging/logger.js +60 -0
  45. package/dist/main.d.ts +2 -0
  46. package/dist/main.js +192 -0
  47. package/dist/output/execute.d.ts +30 -0
  48. package/dist/output/execute.js +144 -0
  49. package/dist/output/index.d.ts +4 -0
  50. package/dist/output/index.js +7 -0
  51. package/dist/output/types.d.ts +48 -0
  52. package/dist/output/types.js +34 -0
  53. package/dist/output/validate.d.ts +23 -0
  54. package/dist/output/validate.js +42 -0
  55. package/dist/safety/index.d.ts +34 -0
  56. package/dist/safety/index.js +59 -0
  57. package/dist/safety/patterns.d.ts +23 -0
  58. package/dist/safety/patterns.js +96 -0
  59. package/dist/safety/types.d.ts +20 -0
  60. package/dist/safety/types.js +18 -0
  61. package/dist/signals/index.d.ts +4 -0
  62. package/dist/signals/index.js +35 -0
  63. package/dist/ui/App.d.ts +4 -0
  64. package/dist/ui/App.js +57 -0
  65. package/dist/ui/components/ActionPrompt.d.ts +8 -0
  66. package/dist/ui/components/ActionPrompt.js +9 -0
  67. package/dist/ui/components/CommandDisplay.d.ts +9 -0
  68. package/dist/ui/components/CommandDisplay.js +13 -0
  69. package/dist/ui/components/DangerousWarning.d.ts +6 -0
  70. package/dist/ui/components/DangerousWarning.js +6 -0
  71. package/dist/ui/components/Spinner.d.ts +15 -0
  72. package/dist/ui/components/Spinner.js +17 -0
  73. package/dist/ui/hooks/useAnimation.d.ts +27 -0
  74. package/dist/ui/hooks/useAnimation.js +85 -0
  75. package/dist/ui/hooks/useTerminalSize.d.ts +12 -0
  76. package/dist/ui/hooks/useTerminalSize.js +56 -0
  77. package/dist/ui/hooks/useTimeout.d.ts +9 -0
  78. package/dist/ui/hooks/useTimeout.js +31 -0
  79. package/dist/ui/index.d.ts +25 -0
  80. package/dist/ui/index.js +80 -0
  81. package/dist/ui/output.d.ts +20 -0
  82. package/dist/ui/output.js +56 -0
  83. package/dist/ui/spinner.d.ts +13 -0
  84. package/dist/ui/spinner.js +58 -0
  85. package/dist/ui/types.d.ts +105 -0
  86. package/dist/ui/types.js +60 -0
  87. package/dist/ui/utils/formatCommand.d.ts +50 -0
  88. package/dist/ui/utils/formatCommand.js +113 -0
  89. package/package.json +68 -0
@@ -0,0 +1,85 @@
1
+ // src/ui/hooks/useAnimation.ts
2
+ // Hook for smooth frame-based animations
3
+ import { useState, useEffect, useRef } from 'react';
4
+ /**
5
+ * Hook that cycles through animation frames at a specified interval
6
+ *
7
+ * @param frames - Array of frame strings to cycle through
8
+ * @param interval - Time between frames in milliseconds
9
+ * @param enabled - Whether animation is active (default true)
10
+ * @returns Current frame string
11
+ */
12
+ export function useAnimation(frames, interval = 80, enabled = true) {
13
+ const [frameIndex, setFrameIndex] = useState(0);
14
+ const frameRef = useRef(0);
15
+ useEffect(() => {
16
+ if (!enabled || frames.length === 0) {
17
+ return;
18
+ }
19
+ const timer = setInterval(() => {
20
+ frameRef.current = (frameRef.current + 1) % frames.length;
21
+ setFrameIndex(frameRef.current);
22
+ }, interval);
23
+ return () => {
24
+ clearInterval(timer);
25
+ };
26
+ }, [frames, interval, enabled]);
27
+ return frames[frameIndex] ?? frames[0] ?? '';
28
+ }
29
+ /**
30
+ * Hook for a pulsing animation effect (alternates between two states)
31
+ *
32
+ * @param interval - Time between pulses in milliseconds
33
+ * @param enabled - Whether pulsing is active
34
+ * @returns Boolean indicating current pulse state
35
+ */
36
+ export function usePulse(interval = 500, enabled = true) {
37
+ const [isPulsed, setIsPulsed] = useState(false);
38
+ useEffect(() => {
39
+ if (!enabled) {
40
+ return;
41
+ }
42
+ const timer = setInterval(() => {
43
+ setIsPulsed((prev) => !prev);
44
+ }, interval);
45
+ return () => {
46
+ clearInterval(timer);
47
+ };
48
+ }, [interval, enabled]);
49
+ return isPulsed;
50
+ }
51
+ /**
52
+ * Hook for a typewriter-style reveal animation
53
+ *
54
+ * @param text - Full text to reveal
55
+ * @param speed - Characters per second
56
+ * @param enabled - Whether animation is active
57
+ * @returns Currently visible portion of text
58
+ */
59
+ export function useTypewriter(text, speed = 30, enabled = true) {
60
+ const [visibleLength, setVisibleLength] = useState(enabled ? 0 : text.length);
61
+ useEffect(() => {
62
+ if (!enabled) {
63
+ setVisibleLength(text.length);
64
+ return;
65
+ }
66
+ if (visibleLength >= text.length) {
67
+ return;
68
+ }
69
+ const interval = 1000 / speed;
70
+ const timer = setTimeout(() => {
71
+ setVisibleLength((prev) => Math.min(prev + 1, text.length));
72
+ }, interval);
73
+ return () => {
74
+ clearTimeout(timer);
75
+ };
76
+ }, [text, speed, enabled, visibleLength]);
77
+ // Reset when text changes
78
+ useEffect(() => {
79
+ if (enabled) {
80
+ setVisibleLength(0);
81
+ }
82
+ }, [text, enabled]);
83
+ return text.slice(0, visibleLength);
84
+ }
85
+ export default useAnimation;
@@ -0,0 +1,12 @@
1
+ import type { TerminalSize } from '../types.js';
2
+ /**
3
+ * Hook that returns terminal dimensions with responsive breakpoints
4
+ * Automatically updates on terminal resize
5
+ *
6
+ * Breakpoints:
7
+ * - isNarrow: < 60 columns (compact layout, aggressive truncation)
8
+ * - isMedium: 60-100 columns (standard layout)
9
+ * - isWide: >= 100 columns (full layout with borders and padding)
10
+ */
11
+ export declare function useTerminalSize(): TerminalSize;
12
+ export default useTerminalSize;
@@ -0,0 +1,56 @@
1
+ // src/ui/hooks/useTerminalSize.ts
2
+ // Hook for responsive terminal dimensions with breakpoints
3
+ import { useState, useEffect } from 'react';
4
+ // Breakpoint thresholds
5
+ const NARROW_THRESHOLD = 60;
6
+ const WIDE_THRESHOLD = 100;
7
+ /**
8
+ * Get current terminal dimensions
9
+ * Falls back to 80x24 if not available (standard terminal size)
10
+ */
11
+ function getTerminalSize() {
12
+ return {
13
+ width: process.stdout.columns || 80,
14
+ height: process.stdout.rows || 24,
15
+ };
16
+ }
17
+ /**
18
+ * Calculate breakpoints from width
19
+ */
20
+ function getBreakpoints(width) {
21
+ return {
22
+ isNarrow: width < NARROW_THRESHOLD,
23
+ isMedium: width >= NARROW_THRESHOLD && width < WIDE_THRESHOLD,
24
+ isWide: width >= WIDE_THRESHOLD,
25
+ };
26
+ }
27
+ /**
28
+ * Hook that returns terminal dimensions with responsive breakpoints
29
+ * Automatically updates on terminal resize
30
+ *
31
+ * Breakpoints:
32
+ * - isNarrow: < 60 columns (compact layout, aggressive truncation)
33
+ * - isMedium: 60-100 columns (standard layout)
34
+ * - isWide: >= 100 columns (full layout with borders and padding)
35
+ */
36
+ export function useTerminalSize() {
37
+ const [size, setSize] = useState(getTerminalSize());
38
+ useEffect(() => {
39
+ const handleResize = () => {
40
+ setSize(getTerminalSize());
41
+ };
42
+ // Listen for terminal resize events
43
+ process.stdout.on('resize', handleResize);
44
+ // Cleanup listener on unmount
45
+ return () => {
46
+ process.stdout.off('resize', handleResize);
47
+ };
48
+ }, []);
49
+ const breakpoints = getBreakpoints(size.width);
50
+ return {
51
+ width: size.width,
52
+ height: size.height,
53
+ ...breakpoints,
54
+ };
55
+ }
56
+ export default useTerminalSize;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Hook that executes a callback after a specified delay
3
+ * Automatically clears on unmount or when delay changes
4
+ *
5
+ * @param callback - Function to execute when timer expires
6
+ * @param delay - Delay in milliseconds, or null/0 to disable
7
+ */
8
+ export declare function useTimeout(callback: () => void, delay: number | null | undefined): void;
9
+ export default useTimeout;
@@ -0,0 +1,31 @@
1
+ // src/ui/hooks/useTimeout.ts
2
+ // Custom hook for configurable timeout handling
3
+ import { useEffect, useRef } from 'react';
4
+ /**
5
+ * Hook that executes a callback after a specified delay
6
+ * Automatically clears on unmount or when delay changes
7
+ *
8
+ * @param callback - Function to execute when timer expires
9
+ * @param delay - Delay in milliseconds, or null/0 to disable
10
+ */
11
+ export function useTimeout(callback, delay) {
12
+ const callbackRef = useRef(callback);
13
+ // Update callback ref when callback changes (avoids stale closures)
14
+ useEffect(() => {
15
+ callbackRef.current = callback;
16
+ }, [callback]);
17
+ useEffect(() => {
18
+ // Don't set timeout if delay is null, undefined, or <= 0
19
+ if (delay === null || delay === undefined || delay <= 0) {
20
+ return;
21
+ }
22
+ const timer = setTimeout(() => {
23
+ callbackRef.current();
24
+ }, delay);
25
+ // Cleanup on unmount or delay change
26
+ return () => {
27
+ clearTimeout(timer);
28
+ };
29
+ }, [delay]);
30
+ }
31
+ export default useTimeout;
@@ -0,0 +1,25 @@
1
+ import { type RenderOptions, type RenderResult } from './types.js';
2
+ export { UserAction, UIPhase } from './types.js';
3
+ export type { RenderOptions, RenderResult, UIState, AppProps, TerminalSize, } from './types.js';
4
+ export { useTimeout } from './hooks/useTimeout.js';
5
+ export { useTerminalSize } from './hooks/useTerminalSize.js';
6
+ export { useAnimation, usePulse, useTypewriter } from './hooks/useAnimation.js';
7
+ export { formatCommand, formatCounter, getCommandDisplayWidth, truncateMiddle, wrapText, createSeparator, } from './utils/formatCommand.js';
8
+ export { createSpinner, withSpinner } from './spinner.js';
9
+ export { printCommand, printWarning, printError, printSuccess, printInfo, } from './output.js';
10
+ export { Spinner } from './components/Spinner.js';
11
+ export { CommandDisplay } from './components/CommandDisplay.js';
12
+ export { DangerousWarning } from './components/DangerousWarning.js';
13
+ export { ActionPrompt } from './components/ActionPrompt.js';
14
+ export { App } from './App.js';
15
+ /**
16
+ * Render the interactive UI for command selection
17
+ *
18
+ * In TTY mode: Shows Ink-based interactive UI with keyboard navigation
19
+ * In piped mode: Returns first command immediately without UI
20
+ *
21
+ * @param options - Render options with commands, config, and danger status
22
+ * @returns Promise resolving to user action and selected command
23
+ */
24
+ export declare function renderUI(options: RenderOptions): Promise<RenderResult>;
25
+ export default renderUI;
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink';
3
+ import { App } from './App.js';
4
+ import { UserAction, } from './types.js';
5
+ // Re-export types and enums
6
+ export { UserAction, UIPhase } from './types.js';
7
+ // Re-export hooks
8
+ export { useTimeout } from './hooks/useTimeout.js';
9
+ export { useTerminalSize } from './hooks/useTerminalSize.js';
10
+ export { useAnimation, usePulse, useTypewriter } from './hooks/useAnimation.js';
11
+ // Re-export utilities
12
+ export { formatCommand, formatCounter, getCommandDisplayWidth, truncateMiddle, wrapText, createSeparator, } from './utils/formatCommand.js';
13
+ // Re-export spinner and output
14
+ export { createSpinner, withSpinner } from './spinner.js';
15
+ export { printCommand, printWarning, printError, printSuccess, printInfo, } from './output.js';
16
+ // Re-export components
17
+ export { Spinner } from './components/Spinner.js';
18
+ export { CommandDisplay } from './components/CommandDisplay.js';
19
+ export { DangerousWarning } from './components/DangerousWarning.js';
20
+ export { ActionPrompt } from './components/ActionPrompt.js';
21
+ export { App } from './App.js';
22
+ /**
23
+ * Render the interactive UI for command selection
24
+ *
25
+ * In TTY mode: Shows Ink-based interactive UI with keyboard navigation
26
+ * In piped mode: Returns first command immediately without UI
27
+ *
28
+ * @param options - Render options with commands, config, and danger status
29
+ * @returns Promise resolving to user action and selected command
30
+ */
31
+ export function renderUI(options) {
32
+ const { commands, config, isDangerous } = options;
33
+ // Debug logging
34
+ if (config.debug) {
35
+ console.error('[UI] renderUI called');
36
+ console.error(`[UI] Commands: ${commands.length}, Dangerous: ${isDangerous}`);
37
+ console.error(`[UI] stdin.isTTY: ${process.stdin.isTTY}, stdout.isTTY: ${process.stdout.isTTY}`);
38
+ }
39
+ // Check if we're in TTY mode (interactive terminal)
40
+ const isTTY = process.stdin.isTTY === true && process.stdout.isTTY === true;
41
+ // If not TTY (piped), return first command immediately without UI
42
+ if (!isTTY) {
43
+ if (config.debug) {
44
+ console.error('[UI] Non-TTY mode, returning first command');
45
+ }
46
+ return Promise.resolve({
47
+ action: UserAction.Execute,
48
+ command: commands[0] ?? '',
49
+ });
50
+ }
51
+ // TTY mode: render Ink UI
52
+ if (config.debug) {
53
+ console.error('[UI] TTY mode, rendering Ink UI');
54
+ }
55
+ return new Promise((resolve) => {
56
+ const { unmount, waitUntilExit } = render(_jsx(App, { commands: commands, isDangerous: isDangerous, config: config, onComplete: (action, command) => {
57
+ if (config.debug) {
58
+ console.error(`[UI] onComplete: ${action}, ${command}`);
59
+ }
60
+ unmount();
61
+ resolve({ action, command });
62
+ } }), {
63
+ // Render to stderr so stdout stays clean for command output
64
+ stdout: process.stderr,
65
+ // Note: Do NOT pass debug: true to Ink - it makes renders static/append-only
66
+ // Our config.debug is for clai debug output, not Ink's internal debug mode
67
+ });
68
+ // Handle any errors during rendering
69
+ waitUntilExit().catch((err) => {
70
+ if (config.debug) {
71
+ console.error('[UI] Render error:', err);
72
+ }
73
+ resolve({
74
+ action: UserAction.Abort,
75
+ command: commands[0] ?? '',
76
+ });
77
+ });
78
+ });
79
+ }
80
+ export default renderUI;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Print a command to stdout with nice formatting
3
+ */
4
+ export declare function printCommand(command: string, isDangerous?: boolean): void;
5
+ /**
6
+ * Print a warning to stderr
7
+ */
8
+ export declare function printWarning(message: string): void;
9
+ /**
10
+ * Print an error to stderr
11
+ */
12
+ export declare function printError(message: string): void;
13
+ /**
14
+ * Print a success message to stderr
15
+ */
16
+ export declare function printSuccess(message: string): void;
17
+ /**
18
+ * Print info to stderr
19
+ */
20
+ export declare function printInfo(message: string): void;
@@ -0,0 +1,56 @@
1
+ // src/ui/output.ts
2
+ // Pretty output formatting for non-interactive mode
3
+ const isTTY = process.stdout.isTTY;
4
+ // ANSI color codes
5
+ const colors = {
6
+ reset: '\x1b[0m',
7
+ bold: '\x1b[1m',
8
+ dim: '\x1b[2m',
9
+ green: '\x1b[32m',
10
+ cyan: '\x1b[36m',
11
+ yellow: '\x1b[33m',
12
+ red: '\x1b[31m',
13
+ };
14
+ function color(text, ...codes) {
15
+ if (!isTTY)
16
+ return text;
17
+ return codes.join('') + text + colors.reset;
18
+ }
19
+ /**
20
+ * Print a command to stdout with nice formatting
21
+ */
22
+ export function printCommand(command, isDangerous = false) {
23
+ if (isTTY) {
24
+ const promptColor = isDangerous ? colors.red : colors.green;
25
+ const cmdColor = isDangerous ? colors.red : colors.cyan;
26
+ process.stdout.write(`${promptColor}${colors.bold}$${colors.reset} ${cmdColor}${command}${colors.reset}\n`);
27
+ }
28
+ else {
29
+ // Clean output for piping
30
+ process.stdout.write(command);
31
+ }
32
+ }
33
+ /**
34
+ * Print a warning to stderr
35
+ */
36
+ export function printWarning(message) {
37
+ process.stderr.write(color(`⚠️ ${message}`, colors.yellow) + '\n');
38
+ }
39
+ /**
40
+ * Print an error to stderr
41
+ */
42
+ export function printError(message) {
43
+ process.stderr.write(color(`✗ ${message}`, colors.red) + '\n');
44
+ }
45
+ /**
46
+ * Print a success message to stderr
47
+ */
48
+ export function printSuccess(message) {
49
+ process.stderr.write(color(`✓ ${message}`, colors.green) + '\n');
50
+ }
51
+ /**
52
+ * Print info to stderr
53
+ */
54
+ export function printInfo(message) {
55
+ process.stderr.write(color(message, colors.dim) + '\n');
56
+ }
@@ -0,0 +1,13 @@
1
+ export interface SpinnerInstance {
2
+ stop: (finalMessage?: string) => void;
3
+ update: (message: string) => void;
4
+ }
5
+ /**
6
+ * Create a simple terminal spinner
7
+ * Renders to stderr to keep stdout clean
8
+ */
9
+ export declare function createSpinner(message: string): SpinnerInstance;
10
+ /**
11
+ * Run an async function with a spinner
12
+ */
13
+ export declare function withSpinner<T>(message: string, fn: () => Promise<T>, successMessage?: string): Promise<T>;
@@ -0,0 +1,58 @@
1
+ // src/ui/spinner.ts
2
+ // Simple terminal spinner for loading states (non-Ink)
3
+ const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
4
+ const INTERVAL = 80;
5
+ /**
6
+ * Create a simple terminal spinner
7
+ * Renders to stderr to keep stdout clean
8
+ */
9
+ export function createSpinner(message) {
10
+ // Only show spinner in TTY mode
11
+ if (!process.stderr.isTTY) {
12
+ return {
13
+ stop: () => { },
14
+ update: () => { },
15
+ };
16
+ }
17
+ let frameIndex = 0;
18
+ let currentMessage = message;
19
+ let stopped = false;
20
+ const render = () => {
21
+ if (stopped)
22
+ return;
23
+ const frame = FRAMES[frameIndex % FRAMES.length];
24
+ process.stderr.write(`\r\x1b[36m${frame}\x1b[0m ${currentMessage}`);
25
+ frameIndex++;
26
+ };
27
+ const timer = setInterval(render, INTERVAL);
28
+ render();
29
+ return {
30
+ stop: (finalMessage) => {
31
+ stopped = true;
32
+ clearInterval(timer);
33
+ // Clear the line
34
+ process.stderr.write('\r\x1b[K');
35
+ if (finalMessage) {
36
+ process.stderr.write(`${finalMessage}\n`);
37
+ }
38
+ },
39
+ update: (msg) => {
40
+ currentMessage = msg;
41
+ },
42
+ };
43
+ }
44
+ /**
45
+ * Run an async function with a spinner
46
+ */
47
+ export async function withSpinner(message, fn, successMessage) {
48
+ const spinner = createSpinner(message);
49
+ try {
50
+ const result = await fn();
51
+ spinner.stop(successMessage);
52
+ return result;
53
+ }
54
+ catch (error) {
55
+ spinner.stop();
56
+ throw error;
57
+ }
58
+ }
@@ -0,0 +1,105 @@
1
+ import type { Config } from '../config/types.js';
2
+ /**
3
+ * User action choices for command handling
4
+ */
5
+ export declare enum UserAction {
6
+ Execute = "execute",
7
+ Abort = "abort"
8
+ }
9
+ /**
10
+ * UI display phases
11
+ */
12
+ export declare enum UIPhase {
13
+ Loading = "loading",
14
+ Select = "select",
15
+ Done = "done"
16
+ }
17
+ /**
18
+ * UI state for the interactive prompt
19
+ */
20
+ export interface UIState {
21
+ phase: UIPhase;
22
+ commands: string[];
23
+ selectedIndex: number;
24
+ selectedAction: UserAction;
25
+ isDangerous: boolean;
26
+ error?: string;
27
+ }
28
+ /**
29
+ * Props for the main App component
30
+ */
31
+ export interface AppProps {
32
+ commands: string[];
33
+ isDangerous: boolean;
34
+ config: Config;
35
+ onComplete: (action: UserAction, command: string) => void;
36
+ }
37
+ /**
38
+ * Options for renderUI function
39
+ */
40
+ export interface RenderOptions {
41
+ commands: string[];
42
+ config: Config;
43
+ isDangerous: boolean;
44
+ }
45
+ /**
46
+ * Result from renderUI function
47
+ */
48
+ export interface RenderResult {
49
+ action: UserAction;
50
+ command: string;
51
+ }
52
+ /**
53
+ * Terminal size breakpoints
54
+ */
55
+ export interface TerminalBreakpoints {
56
+ isNarrow: boolean;
57
+ isMedium: boolean;
58
+ isWide: boolean;
59
+ }
60
+ /**
61
+ * Terminal dimensions with breakpoints
62
+ */
63
+ export interface TerminalSize extends TerminalBreakpoints {
64
+ width: number;
65
+ height: number;
66
+ }
67
+ /**
68
+ * Animation frame configuration
69
+ */
70
+ export interface AnimationConfig {
71
+ frames: readonly string[];
72
+ interval: number;
73
+ }
74
+ /**
75
+ * Color palette for the UI
76
+ */
77
+ export declare const COLORS: {
78
+ readonly command: "#00d9ff";
79
+ readonly commandDim: "#0099b3";
80
+ readonly prompt: "#00ff88";
81
+ readonly execute: "#00ff88";
82
+ readonly executeDanger: "#ff4444";
83
+ readonly abort: "#ffaa00";
84
+ readonly danger: "#ff4444";
85
+ readonly dangerBg: "#330000";
86
+ readonly warning: "#ffaa00";
87
+ readonly border: "#444444";
88
+ readonly borderFocus: "#666666";
89
+ readonly dim: "#666666";
90
+ readonly highlight: "#ffffff";
91
+ readonly selected: "#00ff88";
92
+ readonly selectedBg: "#003311";
93
+ };
94
+ /**
95
+ * Spinner animation frames (smooth braille animation)
96
+ */
97
+ export declare const SPINNER_FRAMES: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
98
+ /**
99
+ * Alternative spinner for wider terminals (dots animation)
100
+ */
101
+ export declare const SPINNER_DOTS: readonly ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"];
102
+ /**
103
+ * Pulsing animation frames for attention
104
+ */
105
+ export declare const PULSE_FRAMES: readonly ["●", "○"];
@@ -0,0 +1,60 @@
1
+ // src/ui/types.ts
2
+ // Types and enums for the interactive UI
3
+ /**
4
+ * User action choices for command handling
5
+ */
6
+ export var UserAction;
7
+ (function (UserAction) {
8
+ UserAction["Execute"] = "execute";
9
+ UserAction["Abort"] = "abort";
10
+ })(UserAction || (UserAction = {}));
11
+ /**
12
+ * UI display phases
13
+ */
14
+ export var UIPhase;
15
+ (function (UIPhase) {
16
+ UIPhase["Loading"] = "loading";
17
+ UIPhase["Select"] = "select";
18
+ UIPhase["Done"] = "done";
19
+ })(UIPhase || (UIPhase = {}));
20
+ /**
21
+ * Color palette for the UI
22
+ */
23
+ export const COLORS = {
24
+ // Primary colors
25
+ command: '#00d9ff', // Bright cyan for commands
26
+ commandDim: '#0099b3', // Dimmed cyan
27
+ prompt: '#00ff88', // Green for $ prompt
28
+ // Action colors
29
+ execute: '#00ff88', // Green for execute
30
+ executeDanger: '#ff4444', // Red for dangerous execute
31
+ abort: '#ffaa00', // Orange/yellow for abort
32
+ // Warning colors
33
+ danger: '#ff4444', // Red for danger
34
+ dangerBg: '#330000', // Dark red background
35
+ warning: '#ffaa00', // Yellow/orange warning
36
+ // UI chrome
37
+ border: '#444444', // Subtle border
38
+ borderFocus: '#666666', // Focused border
39
+ dim: '#666666', // Dimmed text
40
+ highlight: '#ffffff', // Highlighted text
41
+ // Selection
42
+ selected: '#00ff88', // Selected item
43
+ selectedBg: '#003311', // Selected background
44
+ };
45
+ /**
46
+ * Spinner animation frames (smooth braille animation)
47
+ */
48
+ export const SPINNER_FRAMES = [
49
+ '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'
50
+ ];
51
+ /**
52
+ * Alternative spinner for wider terminals (dots animation)
53
+ */
54
+ export const SPINNER_DOTS = [
55
+ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'
56
+ ];
57
+ /**
58
+ * Pulsing animation frames for attention
59
+ */
60
+ export const PULSE_FRAMES = ['●', '○'];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Format a command for display in the terminal
3
+ * Truncates with ellipsis if too long for available width
4
+ *
5
+ * @param command - The command string to format
6
+ * @param maxWidth - Maximum width available (in columns)
7
+ * @returns Formatted command string
8
+ */
9
+ export declare function formatCommand(command: string, maxWidth: number): string;
10
+ /**
11
+ * Format command counter like [1/3]
12
+ *
13
+ * @param currentIndex - Current index (0-based)
14
+ * @param total - Total number of commands
15
+ * @returns Formatted counter string
16
+ */
17
+ export declare function formatCounter(currentIndex: number, total: number): string;
18
+ /**
19
+ * Calculate available width for command display
20
+ * Accounts for prefix, counter, and padding
21
+ *
22
+ * @param terminalWidth - Total terminal width
23
+ * @param hasCounter - Whether counter will be shown
24
+ * @returns Width available for command text
25
+ */
26
+ export declare function getCommandDisplayWidth(terminalWidth: number, hasCounter?: boolean): number;
27
+ /**
28
+ * Truncate text with ellipsis in the middle (for paths)
29
+ *
30
+ * @param text - Text to truncate
31
+ * @param maxLength - Maximum length
32
+ * @returns Truncated text
33
+ */
34
+ export declare function truncateMiddle(text: string, maxLength: number): string;
35
+ /**
36
+ * Wrap text to fit within a maximum width
37
+ *
38
+ * @param text - Text to wrap
39
+ * @param maxWidth - Maximum line width
40
+ * @returns Array of wrapped lines
41
+ */
42
+ export declare function wrapText(text: string, maxWidth: number): string[];
43
+ /**
44
+ * Create a visual separator line
45
+ *
46
+ * @param width - Width of the separator
47
+ * @param char - Character to use (default: ─)
48
+ * @returns Separator string
49
+ */
50
+ export declare function createSeparator(width: number, char?: string): string;