@agentuity/cli 0.0.6

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 (158) hide show
  1. package/AGENTS.md +139 -0
  2. package/README.md +239 -0
  3. package/bin/cli.ts +71 -0
  4. package/dist/api.d.ts +25 -0
  5. package/dist/api.d.ts.map +1 -0
  6. package/dist/auth.d.ts +7 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/banner.d.ts +2 -0
  9. package/dist/banner.d.ts.map +1 -0
  10. package/dist/cli.d.ts +5 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cmd/auth/api.d.ts +9 -0
  13. package/dist/cmd/auth/api.d.ts.map +1 -0
  14. package/dist/cmd/auth/index.d.ts +2 -0
  15. package/dist/cmd/auth/index.d.ts.map +1 -0
  16. package/dist/cmd/auth/login.d.ts +3 -0
  17. package/dist/cmd/auth/login.d.ts.map +1 -0
  18. package/dist/cmd/auth/logout.d.ts +3 -0
  19. package/dist/cmd/auth/logout.d.ts.map +1 -0
  20. package/dist/cmd/bundle/ast.d.ts +2 -0
  21. package/dist/cmd/bundle/ast.d.ts.map +1 -0
  22. package/dist/cmd/bundle/bundler.d.ts +6 -0
  23. package/dist/cmd/bundle/bundler.d.ts.map +1 -0
  24. package/dist/cmd/bundle/file.d.ts +2 -0
  25. package/dist/cmd/bundle/file.d.ts.map +1 -0
  26. package/dist/cmd/bundle/index.d.ts +2 -0
  27. package/dist/cmd/bundle/index.d.ts.map +1 -0
  28. package/dist/cmd/bundle/plugin.d.ts +4 -0
  29. package/dist/cmd/bundle/plugin.d.ts.map +1 -0
  30. package/dist/cmd/dev/index.d.ts +2 -0
  31. package/dist/cmd/dev/index.d.ts.map +1 -0
  32. package/dist/cmd/example/create-user.d.ts +2 -0
  33. package/dist/cmd/example/create-user.d.ts.map +1 -0
  34. package/dist/cmd/example/create.d.ts +2 -0
  35. package/dist/cmd/example/create.d.ts.map +1 -0
  36. package/dist/cmd/example/deploy.d.ts +2 -0
  37. package/dist/cmd/example/deploy.d.ts.map +1 -0
  38. package/dist/cmd/example/index.d.ts +2 -0
  39. package/dist/cmd/example/index.d.ts.map +1 -0
  40. package/dist/cmd/example/list.d.ts +2 -0
  41. package/dist/cmd/example/list.d.ts.map +1 -0
  42. package/dist/cmd/example/run-command.d.ts +2 -0
  43. package/dist/cmd/example/run-command.d.ts.map +1 -0
  44. package/dist/cmd/example/sound.d.ts +3 -0
  45. package/dist/cmd/example/sound.d.ts.map +1 -0
  46. package/dist/cmd/example/spinner.d.ts +2 -0
  47. package/dist/cmd/example/spinner.d.ts.map +1 -0
  48. package/dist/cmd/example/steps.d.ts +2 -0
  49. package/dist/cmd/example/steps.d.ts.map +1 -0
  50. package/dist/cmd/example/version.d.ts +2 -0
  51. package/dist/cmd/example/version.d.ts.map +1 -0
  52. package/dist/cmd/index.d.ts +3 -0
  53. package/dist/cmd/index.d.ts.map +1 -0
  54. package/dist/cmd/profile/create.d.ts +2 -0
  55. package/dist/cmd/profile/create.d.ts.map +1 -0
  56. package/dist/cmd/profile/delete.d.ts +2 -0
  57. package/dist/cmd/profile/delete.d.ts.map +1 -0
  58. package/dist/cmd/profile/index.d.ts +2 -0
  59. package/dist/cmd/profile/index.d.ts.map +1 -0
  60. package/dist/cmd/profile/list.d.ts +3 -0
  61. package/dist/cmd/profile/list.d.ts.map +1 -0
  62. package/dist/cmd/profile/show.d.ts +2 -0
  63. package/dist/cmd/profile/show.d.ts.map +1 -0
  64. package/dist/cmd/profile/use.d.ts +2 -0
  65. package/dist/cmd/profile/use.d.ts.map +1 -0
  66. package/dist/cmd/project/create.d.ts +2 -0
  67. package/dist/cmd/project/create.d.ts.map +1 -0
  68. package/dist/cmd/project/delete.d.ts +2 -0
  69. package/dist/cmd/project/delete.d.ts.map +1 -0
  70. package/dist/cmd/project/index.d.ts +2 -0
  71. package/dist/cmd/project/index.d.ts.map +1 -0
  72. package/dist/cmd/project/list.d.ts +2 -0
  73. package/dist/cmd/project/list.d.ts.map +1 -0
  74. package/dist/cmd/project/show.d.ts +2 -0
  75. package/dist/cmd/project/show.d.ts.map +1 -0
  76. package/dist/cmd/version/index.d.ts +2 -0
  77. package/dist/cmd/version/index.d.ts.map +1 -0
  78. package/dist/command-prefix.d.ts +11 -0
  79. package/dist/command-prefix.d.ts.map +1 -0
  80. package/dist/config.d.ts +16 -0
  81. package/dist/config.d.ts.map +1 -0
  82. package/dist/index.d.ts +18 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/legacy-check.d.ts +6 -0
  85. package/dist/legacy-check.d.ts.map +1 -0
  86. package/dist/logger.d.ts +24 -0
  87. package/dist/logger.d.ts.map +1 -0
  88. package/dist/runtime.d.ts +3 -0
  89. package/dist/runtime.d.ts.map +1 -0
  90. package/dist/schema-parser.d.ts +24 -0
  91. package/dist/schema-parser.d.ts.map +1 -0
  92. package/dist/sound.d.ts +2 -0
  93. package/dist/sound.d.ts.map +1 -0
  94. package/dist/steps.d.ts +59 -0
  95. package/dist/steps.d.ts.map +1 -0
  96. package/dist/terminal.d.ts +3 -0
  97. package/dist/terminal.d.ts.map +1 -0
  98. package/dist/tui.d.ts +156 -0
  99. package/dist/tui.d.ts.map +1 -0
  100. package/dist/types.d.ts +164 -0
  101. package/dist/types.d.ts.map +1 -0
  102. package/dist/version.d.ts +10 -0
  103. package/dist/version.d.ts.map +1 -0
  104. package/package.json +46 -0
  105. package/src/api-errors.md +115 -0
  106. package/src/api.ts +186 -0
  107. package/src/auth.ts +91 -0
  108. package/src/banner.ts +23 -0
  109. package/src/cli.ts +198 -0
  110. package/src/cmd/auth/README.md +95 -0
  111. package/src/cmd/auth/api.ts +71 -0
  112. package/src/cmd/auth/index.ts +9 -0
  113. package/src/cmd/auth/login.ts +76 -0
  114. package/src/cmd/auth/logout.ts +14 -0
  115. package/src/cmd/bundle/ast.ts +228 -0
  116. package/src/cmd/bundle/bundler.ts +88 -0
  117. package/src/cmd/bundle/file.ts +16 -0
  118. package/src/cmd/bundle/index.ts +38 -0
  119. package/src/cmd/bundle/plugin.ts +259 -0
  120. package/src/cmd/dev/index.ts +83 -0
  121. package/src/cmd/example/create-user.ts +38 -0
  122. package/src/cmd/example/create.ts +31 -0
  123. package/src/cmd/example/deploy.ts +36 -0
  124. package/src/cmd/example/index.ts +27 -0
  125. package/src/cmd/example/list.ts +32 -0
  126. package/src/cmd/example/run-command.ts +45 -0
  127. package/src/cmd/example/sound.ts +14 -0
  128. package/src/cmd/example/spinner.ts +44 -0
  129. package/src/cmd/example/steps.ts +66 -0
  130. package/src/cmd/example/version.ts +13 -0
  131. package/src/cmd/index.ts +46 -0
  132. package/src/cmd/profile/README.md +80 -0
  133. package/src/cmd/profile/create.ts +57 -0
  134. package/src/cmd/profile/delete.ts +52 -0
  135. package/src/cmd/profile/index.ts +12 -0
  136. package/src/cmd/profile/list.ts +27 -0
  137. package/src/cmd/profile/show.ts +54 -0
  138. package/src/cmd/profile/use.ts +30 -0
  139. package/src/cmd/project/create.ts +247 -0
  140. package/src/cmd/project/delete.ts +13 -0
  141. package/src/cmd/project/index.ts +11 -0
  142. package/src/cmd/project/list.ts +13 -0
  143. package/src/cmd/project/show.ts +12 -0
  144. package/src/cmd/version/index.ts +16 -0
  145. package/src/command-prefix.ts +43 -0
  146. package/src/config.ts +304 -0
  147. package/src/index.ts +40 -0
  148. package/src/legacy-check.ts +127 -0
  149. package/src/logger.ts +235 -0
  150. package/src/runtime.ts +22 -0
  151. package/src/schema-parser.ts +213 -0
  152. package/src/sound.ts +25 -0
  153. package/src/steps.ts +245 -0
  154. package/src/terminal.ts +151 -0
  155. package/src/tui.md +254 -0
  156. package/src/tui.ts +838 -0
  157. package/src/types.ts +243 -0
  158. package/src/version.ts +29 -0
@@ -0,0 +1,127 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import * as tui from './tui';
4
+
5
+ interface LegacyInstall {
6
+ path: string;
7
+ method: 'homebrew' | 'manual' | 'install-script';
8
+ }
9
+
10
+ /**
11
+ * Check if the legacy (Go-based) Agentuity CLI is installed
12
+ * and block execution with migration instructions
13
+ */
14
+ export async function checkLegacyCLI(): Promise<void> {
15
+ const homeDir = homedir();
16
+
17
+ const legacyLocations = [
18
+ '/opt/homebrew/bin/agentuity', // Homebrew ARM64 (M1/M2/M3 Macs)
19
+ '/usr/local/bin/agentuity', // Homebrew Intel Macs / Linux
20
+ '/usr/bin/agentuity', // System install
21
+ join(homeDir, '.bin/agentuity'), // User bin from install script
22
+ join(homeDir, 'bin/agentuity'), // User bin alternate
23
+ join(homeDir, '.local/bin/agentuity'), // XDG user bin
24
+ ];
25
+
26
+ const foundInstalls: LegacyInstall[] = [];
27
+
28
+ // Check if Homebrew manages the agentuity package
29
+ let isBrewManaged = false;
30
+ try {
31
+ const brewCheck = Bun.spawn(['brew', 'list', '--versions', 'agentuity'], {
32
+ stdout: 'ignore',
33
+ stderr: 'ignore',
34
+ });
35
+ const exitCode = await brewCheck.exited;
36
+ isBrewManaged = exitCode === 0;
37
+ } catch {
38
+ // Homebrew not installed or command failed
39
+ }
40
+
41
+ // Check file system locations
42
+ for (const location of legacyLocations) {
43
+ const file = Bun.file(location);
44
+ if (await file.exists()) {
45
+ try {
46
+ // Check if it's a compiled binary (not TypeScript)
47
+ const proc = Bun.spawn(['file', location], { stdout: 'pipe' });
48
+ const output = await new Response(proc.stdout).text();
49
+ await proc.exited;
50
+
51
+ if (output.includes('Mach-O') || output.includes('ELF')) {
52
+ // Determine method: if brew manages the package, mark all installs as homebrew
53
+ // otherwise check if it's in user home (install-script) or system (manual)
54
+ const method = isBrewManaged
55
+ ? 'homebrew'
56
+ : location.includes(homeDir)
57
+ ? 'install-script'
58
+ : 'manual';
59
+ foundInstalls.push({ path: location, method });
60
+ }
61
+ } catch {
62
+ // Ignore errors
63
+ }
64
+ }
65
+ }
66
+
67
+ if (foundInstalls.length === 0 && !isBrewManaged) {
68
+ return; // No legacy CLI found
69
+ }
70
+
71
+ // Block execution and show removal instructions
72
+ tui.newline();
73
+ tui.error('Legacy CLI Conflict Detected');
74
+ tui.newline();
75
+
76
+ console.log(' The legacy (Go-based) Agentuity CLI is installed and conflicts with the new');
77
+ console.log(' TypeScript-based CLI. Please remove it before continuing.');
78
+ tui.newline();
79
+
80
+ // Filter installs into brew-managed and manual
81
+ const brewInstalls = foundInstalls.filter(
82
+ (install) => isBrewManaged || install.method === 'homebrew'
83
+ );
84
+ const manualInstalls = foundInstalls.filter(
85
+ (install) => !isBrewManaged && install.method !== 'homebrew'
86
+ );
87
+
88
+ // Show Homebrew removal section if any brew-managed installs
89
+ if (brewInstalls.length > 0) {
90
+ console.log(' ' + tui.bold('Remove via Homebrew:'));
91
+ tui.bullet('brew uninstall agentuity');
92
+ tui.newline();
93
+
94
+ // Show which files will be removed by brew
95
+ console.log(' ' + tui.muted('This will remove:'));
96
+ for (const install of brewInstalls) {
97
+ console.log(` "${install.path}"`);
98
+ }
99
+ tui.newline();
100
+ }
101
+
102
+ // Show manual removal section if any manual installs
103
+ if (manualInstalls.length > 0) {
104
+ console.log(' ' + tui.bold('Remove the following legacy CLI installations:'));
105
+ tui.newline();
106
+
107
+ for (const install of manualInstalls) {
108
+ console.log(` ${tui.muted('"' + install.path + '"')}`);
109
+
110
+ if (install.method === 'install-script') {
111
+ tui.bullet(`rm "${install.path}"`);
112
+ } else {
113
+ tui.bullet(`sudo rm "${install.path}"`);
114
+ }
115
+ }
116
+ tui.newline();
117
+ }
118
+
119
+ console.log(' ' + tui.bold('After removal, install the new CLI:'));
120
+ tui.bullet('bun install -g @agentuity/cli');
121
+ tui.newline();
122
+
123
+ console.log(` Learn more: ${tui.link('https://docs.agentuity.dev/cli/migration')}`);
124
+ tui.newline();
125
+
126
+ process.exit(1);
127
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,235 @@
1
+ import type { LogLevel } from './types';
2
+ import type { ColorScheme } from './terminal';
3
+
4
+ const LOG_LEVELS: Record<LogLevel, number> = {
5
+ debug: 0,
6
+ trace: 1,
7
+ info: 2,
8
+ warn: 3,
9
+ error: 4,
10
+ };
11
+
12
+ const BOLD = '\x1b[1m';
13
+ const RESET = '\x1b[0m';
14
+
15
+ // Helper to convert hex color to ANSI 24-bit color code
16
+ function hexToAnsi(hex: string): string {
17
+ const r = parseInt(hex.slice(1, 3), 16);
18
+ const g = parseInt(hex.slice(3, 5), 16);
19
+ const b = parseInt(hex.slice(5, 7), 16);
20
+ return `\x1b[38;2;${r};${g};${b}m`;
21
+ }
22
+
23
+ function shouldUseColors(): boolean {
24
+ // Check for NO_COLOR environment variable (any non-empty value disables colors)
25
+ if (process.env.NO_COLOR) {
26
+ return false;
27
+ }
28
+
29
+ // Check for TERM=dumb
30
+ if (process.env.TERM === 'dumb') {
31
+ return false;
32
+ }
33
+
34
+ // Check if stdout is a TTY
35
+ if (!process.stdout.isTTY) {
36
+ return false;
37
+ }
38
+
39
+ return true;
40
+ }
41
+
42
+ const USE_COLORS = shouldUseColors();
43
+
44
+ interface LogColors {
45
+ level: string;
46
+ message: string;
47
+ timestamp: string;
48
+ }
49
+
50
+ function getLogColors(scheme: ColorScheme): Record<LogLevel, LogColors> {
51
+ if (scheme === 'light') {
52
+ // Darker, high-contrast colors for light backgrounds
53
+ return {
54
+ trace: {
55
+ level: hexToAnsi('#008B8B') + BOLD, // Dark cyan
56
+ message: hexToAnsi('#4B4B4B'), // Dark gray
57
+ timestamp: hexToAnsi('#808080'), // Gray
58
+ },
59
+ debug: {
60
+ level: hexToAnsi('#0000CD') + BOLD, // Medium blue
61
+ message: hexToAnsi('#006400'), // Dark green
62
+ timestamp: hexToAnsi('#808080'),
63
+ },
64
+ info: {
65
+ level: hexToAnsi('#FF8C00') + BOLD, // Dark orange
66
+ message: hexToAnsi('#0066CC') + BOLD, // Strong blue
67
+ timestamp: hexToAnsi('#808080'),
68
+ },
69
+ warn: {
70
+ level: hexToAnsi('#9400D3') + BOLD, // Dark violet
71
+ message: hexToAnsi('#8B008B'), // Dark magenta
72
+ timestamp: hexToAnsi('#808080'),
73
+ },
74
+ error: {
75
+ level: hexToAnsi('#DC143C') + BOLD, // Crimson
76
+ message: hexToAnsi('#8B0000') + BOLD, // Dark red
77
+ timestamp: hexToAnsi('#808080'),
78
+ },
79
+ };
80
+ }
81
+
82
+ // Dark mode colors (brighter for dark backgrounds)
83
+ return {
84
+ trace: {
85
+ level: (Bun.color('cyan', 'ansi') ?? '') + BOLD,
86
+ message: Bun.color('gray', 'ansi') ?? '',
87
+ timestamp: hexToAnsi('#666666'),
88
+ },
89
+ debug: {
90
+ level: (Bun.color('blue', 'ansi') ?? '') + BOLD,
91
+ message: Bun.color('green', 'ansi') ?? '',
92
+ timestamp: hexToAnsi('#666666'),
93
+ },
94
+ info: {
95
+ level: (Bun.color('yellow', 'ansi') ?? '') + BOLD,
96
+ message: (Bun.color('white', 'ansi') ?? '') + BOLD,
97
+ timestamp: hexToAnsi('#666666'),
98
+ },
99
+ warn: {
100
+ level: (Bun.color('magenta', 'ansi') ?? '') + BOLD,
101
+ message: Bun.color('magenta', 'ansi') ?? '',
102
+ timestamp: hexToAnsi('#666666'),
103
+ },
104
+ error: {
105
+ level: (Bun.color('red', 'ansi') ?? '') + BOLD,
106
+ message: Bun.color('red', 'ansi') ?? '',
107
+ timestamp: hexToAnsi('#666666'),
108
+ },
109
+ };
110
+ }
111
+
112
+ export class Logger {
113
+ private level: LogLevel;
114
+ private showTimestamp: boolean;
115
+ private colorScheme: ColorScheme;
116
+ private colors: Record<LogLevel, LogColors>;
117
+ private showPrefix = true;
118
+
119
+ constructor(
120
+ level: LogLevel = 'info',
121
+ showTimestamp: boolean = false,
122
+ colorScheme: ColorScheme = 'dark'
123
+ ) {
124
+ this.level = level;
125
+ this.showTimestamp = showTimestamp;
126
+ this.colorScheme = colorScheme;
127
+ this.colors = getLogColors(this.colorScheme);
128
+ }
129
+
130
+ setLevel(level: LogLevel): void {
131
+ this.level = level;
132
+ }
133
+
134
+ setTimestamp(enabled: boolean): void {
135
+ this.showTimestamp = enabled;
136
+ }
137
+
138
+ setColorScheme(scheme: ColorScheme): void {
139
+ this.colorScheme = scheme;
140
+ this.colors = getLogColors(this.colorScheme);
141
+ }
142
+
143
+ setShowPrefix(show: boolean): void {
144
+ this.showPrefix = show;
145
+ }
146
+
147
+ private shouldLog(level: LogLevel): boolean {
148
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
149
+ }
150
+
151
+ private log(level: LogLevel, message: string, ...args: unknown[]): void {
152
+ if (!this.shouldLog(level) || !message) {
153
+ return;
154
+ }
155
+
156
+ const colors = this.colors[level];
157
+ const levelText = `[${level.toUpperCase()}]`;
158
+
159
+ let output = '';
160
+
161
+ if (USE_COLORS) {
162
+ if (this.showPrefix) {
163
+ if (this.showTimestamp) {
164
+ const timestamp = new Date().toISOString();
165
+ output = `${colors.timestamp}[${timestamp}]${RESET} ${colors.level}${levelText}${RESET} ${colors.message}${message}${RESET}`;
166
+ } else {
167
+ output = `${colors.level}${levelText}${RESET} ${colors.message}${message}${RESET}`;
168
+ }
169
+ } else {
170
+ // No prefix - just the message with color
171
+ output = `${colors.message}${message}${RESET}`;
172
+ }
173
+ } else {
174
+ // No colors - plain text output
175
+ if (this.showPrefix) {
176
+ if (this.showTimestamp) {
177
+ const timestamp = new Date().toISOString();
178
+ output = `[${timestamp}] ${levelText} ${message}`;
179
+ } else {
180
+ output = `${levelText} ${message}`;
181
+ }
182
+ } else {
183
+ // No prefix, no colors - just message
184
+ output = message;
185
+ }
186
+ }
187
+
188
+ if (level === 'error') {
189
+ if (args.length > 0) {
190
+ console.error(output, ...args);
191
+ } else {
192
+ console.error(output);
193
+ }
194
+ } else if (level === 'warn') {
195
+ if (args.length > 0) {
196
+ console.warn(output, ...args);
197
+ } else {
198
+ console.warn(output);
199
+ }
200
+ } else {
201
+ if (args.length > 0) {
202
+ console.log(output, ...args);
203
+ } else {
204
+ console.log(output);
205
+ }
206
+ }
207
+ }
208
+
209
+ debug(message: string, ...args: unknown[]): void {
210
+ this.log('debug', message, ...args);
211
+ }
212
+
213
+ trace(message: string, ...args: unknown[]): void {
214
+ this.log('trace', message, ...args);
215
+ }
216
+
217
+ info(message: string, ...args: unknown[]): void {
218
+ this.log('info', message, ...args);
219
+ }
220
+
221
+ warn(message: string, ...args: unknown[]): void {
222
+ this.log('warn', message, ...args);
223
+ }
224
+
225
+ error(message: string, ...args: unknown[]): void {
226
+ this.log('error', message, ...args);
227
+ }
228
+
229
+ fatal(message: string, ...args: unknown[]): never {
230
+ this.log('error', message, ...args);
231
+ process.exit(1);
232
+ }
233
+ }
234
+
235
+ export const logger = new Logger('info');
package/src/runtime.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { semver } from 'bun';
2
+
3
+ const MIN_BUN_VERSION = '1.3.0';
4
+
5
+ export function isBun(): boolean {
6
+ return typeof Bun !== 'undefined';
7
+ }
8
+
9
+ export function validateRuntime(): void {
10
+ if (!isBun()) {
11
+ console.error('Error: This CLI requires Bun runtime');
12
+ console.error('Please install Bun: https://bun.sh');
13
+ process.exit(1);
14
+ }
15
+
16
+ const bunVersion = Bun.version;
17
+ if (semver.satisfies(bunVersion, `>=${MIN_BUN_VERSION}`) === false) {
18
+ console.error(`Error: This CLI requires Bun ${MIN_BUN_VERSION} or higher`);
19
+ console.error(`Current Bun version: ${bunVersion}`);
20
+ process.exit(1);
21
+ }
22
+ }
@@ -0,0 +1,213 @@
1
+ import type { ZodType } from 'zod';
2
+ import type { CommandSchemas } from './types';
3
+
4
+ export interface ParsedArgs {
5
+ names: string[];
6
+ metadata: Array<{
7
+ name: string;
8
+ optional: boolean;
9
+ variadic: boolean;
10
+ }>;
11
+ }
12
+
13
+ export interface ParsedOption {
14
+ name: string;
15
+ description?: string;
16
+ type: 'string' | 'number' | 'boolean';
17
+ hasDefault?: boolean;
18
+ defaultValue?: unknown;
19
+ }
20
+
21
+ interface ZodTypeDef {
22
+ typeName?: string;
23
+ type?: string;
24
+ innerType?: unknown;
25
+ schema?: unknown;
26
+ shape?: (() => Record<string, unknown>) | Record<string, unknown>;
27
+ description?: string;
28
+ }
29
+
30
+ interface ZodTypeInternal {
31
+ _def: ZodTypeDef;
32
+ }
33
+
34
+ function unwrapSchema(schema: unknown): unknown {
35
+ let current = schema as ZodTypeInternal | undefined;
36
+
37
+ while (current?._def) {
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ const typeId = current._def.typeName || current._def.type || (current as any).type;
40
+
41
+ if ((typeId === 'ZodEffects' || typeId === 'effects') && current._def.schema) {
42
+ current = current._def.schema as ZodTypeInternal;
43
+ } else if ((typeId === 'ZodOptional' || typeId === 'optional') && current._def.innerType) {
44
+ current = current._def.innerType as ZodTypeInternal;
45
+ } else if ((typeId === 'ZodNullable' || typeId === 'nullable') && current._def.innerType) {
46
+ current = current._def.innerType as ZodTypeInternal;
47
+ } else if ((typeId === 'ZodDefault' || typeId === 'default') && current._def.innerType) {
48
+ current = current._def.innerType as ZodTypeInternal;
49
+ } else if ((typeId === 'ZodReadonly' || typeId === 'readonly') && current._def.innerType) {
50
+ current = current._def.innerType as ZodTypeInternal;
51
+ } else {
52
+ break;
53
+ }
54
+ }
55
+
56
+ return current;
57
+ }
58
+
59
+ function getShape(schema: ZodType): Record<string, unknown> {
60
+ const unwrapped = unwrapSchema(schema) as ZodTypeInternal;
61
+ const typeId = unwrapped?._def?.typeName || unwrapped?._def?.type;
62
+
63
+ if (typeId === 'ZodObject' || typeId === 'object') {
64
+ const shape = unwrapped._def.shape;
65
+ return typeof shape === 'function' ? shape() : (shape as Record<string, unknown>) || {};
66
+ }
67
+
68
+ return {};
69
+ }
70
+
71
+ export function parseArgsSchema(schema: ZodType): ParsedArgs {
72
+ const shape = getShape(schema);
73
+ const names: string[] = [];
74
+ const metadata: Array<{ name: string; optional: boolean; variadic: boolean }> = [];
75
+
76
+ for (const [key, value] of Object.entries(shape)) {
77
+ names.push(key);
78
+
79
+ /* eslint-disable @typescript-eslint/no-explicit-any */
80
+ const typeId =
81
+ (value as ZodTypeInternal)?._def?.typeName ||
82
+ (value as any)._def?.type ||
83
+ (value as any).type;
84
+ const unwrapped = unwrapSchema(value) as ZodTypeInternal;
85
+ const unwrappedTypeId =
86
+ unwrapped?._def?.typeName || (unwrapped as any)?._def?.type || (unwrapped as any)?.type;
87
+ /* eslint-enable @typescript-eslint/no-explicit-any */
88
+
89
+ const isOptional = typeId === 'ZodOptional' || typeId === 'optional';
90
+ const isVariadic = unwrappedTypeId === 'ZodArray' || unwrappedTypeId === 'array';
91
+
92
+ metadata.push({ name: key, optional: isOptional, variadic: isVariadic });
93
+ }
94
+
95
+ return { names, metadata };
96
+ }
97
+
98
+ /**
99
+ * Extract default value information from a Zod schema by walking the wrapper chain
100
+ */
101
+ function extractDefaultInfo(schema: unknown): {
102
+ hasDefault: boolean;
103
+ defaultValue?: unknown;
104
+ defaultIsFunction: boolean;
105
+ } {
106
+ let current = schema as ZodTypeInternal | undefined;
107
+
108
+ while (current?._def) {
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
+ const typeId = current._def.typeName || (current as any)._def?.type;
111
+
112
+ if (typeId === 'ZodDefault' || typeId === 'default') {
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ const rawDefaultValue = (current as any)._def?.defaultValue;
115
+ const defaultIsFunction = typeof rawDefaultValue === 'function';
116
+
117
+ return {
118
+ hasDefault: true,
119
+ defaultValue: rawDefaultValue,
120
+ defaultIsFunction,
121
+ };
122
+ }
123
+
124
+ // Continue through wrapper chain
125
+ if (
126
+ (typeId === 'ZodOptional' ||
127
+ typeId === 'optional' ||
128
+ typeId === 'ZodNullable' ||
129
+ typeId === 'nullable' ||
130
+ typeId === 'ZodEffects' ||
131
+ typeId === 'effects' ||
132
+ typeId === 'ZodReadonly' ||
133
+ typeId === 'readonly') &&
134
+ current._def.innerType
135
+ ) {
136
+ current = current._def.innerType as ZodTypeInternal;
137
+ } else if ((typeId === 'ZodEffects' || typeId === 'effects') && current._def.schema) {
138
+ current = current._def.schema as ZodTypeInternal;
139
+ } else {
140
+ break;
141
+ }
142
+ }
143
+
144
+ return { hasDefault: false, defaultIsFunction: false };
145
+ }
146
+
147
+ export function parseOptionsSchema(schema: ZodType): ParsedOption[] {
148
+ const shape = getShape(schema);
149
+ const options: ParsedOption[] = [];
150
+
151
+ for (const [key, value] of Object.entries(shape)) {
152
+ const unwrapped = unwrapSchema(value) as ZodTypeInternal;
153
+ const description =
154
+ (unwrapped as ZodTypeInternal)?._def?.description ??
155
+ (value as unknown as { description?: string })?.description ??
156
+ (value as ZodTypeInternal)?._def?.description;
157
+ /* eslint-disable @typescript-eslint/no-explicit-any */
158
+ const typeId =
159
+ unwrapped?._def?.typeName || (unwrapped as any)?._def?.type || (unwrapped as any)?.type;
160
+ /* eslint-enable @typescript-eslint/no-explicit-any */
161
+
162
+ // Extract default info using helper that walks the wrapper chain
163
+ const defaultInfo = extractDefaultInfo(value);
164
+
165
+ // Evaluate function defaults at parse-time for actual default value
166
+ const defaultValue = defaultInfo.defaultIsFunction
167
+ ? (defaultInfo.defaultValue as () => unknown)()
168
+ : defaultInfo.defaultValue;
169
+
170
+ let type: 'string' | 'number' | 'boolean' = 'string';
171
+ if (typeId === 'ZodNumber' || typeId === 'number') {
172
+ type = 'number';
173
+ } else if (typeId === 'ZodBoolean' || typeId === 'boolean') {
174
+ type = 'boolean';
175
+ }
176
+
177
+ options.push({
178
+ name: key,
179
+ type,
180
+ description,
181
+ hasDefault: defaultInfo.hasDefault,
182
+ defaultValue,
183
+ });
184
+ }
185
+
186
+ return options;
187
+ }
188
+
189
+ export function buildValidationInput(
190
+ schemas: CommandSchemas,
191
+ rawArgs: unknown[],
192
+ rawOptions: Record<string, unknown>
193
+ ): { args: Record<string, unknown>; options: Record<string, unknown> } {
194
+ const result = { args: {} as Record<string, unknown>, options: {} as Record<string, unknown> };
195
+
196
+ if (schemas.args) {
197
+ const parsed = parseArgsSchema(schemas.args);
198
+ for (let i = 0; i < parsed.names.length; i++) {
199
+ result.args[parsed.names[i]] = rawArgs[i];
200
+ }
201
+ }
202
+
203
+ if (schemas.options) {
204
+ const parsed = parseOptionsSchema(schemas.options);
205
+ for (const opt of parsed) {
206
+ if (rawOptions[opt.name] !== undefined) {
207
+ result.options[opt.name] = rawOptions[opt.name];
208
+ }
209
+ }
210
+ }
211
+
212
+ return result;
213
+ }
package/src/sound.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { $ } from 'bun';
2
+
3
+ export async function playSound(): Promise<void> {
4
+ const platform = process.platform;
5
+
6
+ let result;
7
+ switch (platform) {
8
+ case 'darwin':
9
+ result = await $`afplay /System/Library/Sounds/Glass.aiff`.quiet().nothrow();
10
+ break;
11
+ case 'linux':
12
+ result = await $`paplay /usr/share/sounds/freedesktop/stereo/complete.oga`
13
+ .quiet()
14
+ .nothrow();
15
+ break;
16
+ case 'win32':
17
+ result = await $`rundll32 user32.dll,MessageBeep 0x00000040`.quiet().nothrow();
18
+ break;
19
+ }
20
+
21
+ // Fallback to terminal bell if command failed or platform unsupported
22
+ if (!result || result.exitCode !== 0) {
23
+ process.stdout.write('\u0007');
24
+ }
25
+ }