@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.
- package/AGENTS.md +139 -0
- package/README.md +239 -0
- package/bin/cli.ts +71 -0
- package/dist/api.d.ts +25 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/auth.d.ts +7 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/banner.d.ts +2 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cmd/auth/api.d.ts +9 -0
- package/dist/cmd/auth/api.d.ts.map +1 -0
- package/dist/cmd/auth/index.d.ts +2 -0
- package/dist/cmd/auth/index.d.ts.map +1 -0
- package/dist/cmd/auth/login.d.ts +3 -0
- package/dist/cmd/auth/login.d.ts.map +1 -0
- package/dist/cmd/auth/logout.d.ts +3 -0
- package/dist/cmd/auth/logout.d.ts.map +1 -0
- package/dist/cmd/bundle/ast.d.ts +2 -0
- package/dist/cmd/bundle/ast.d.ts.map +1 -0
- package/dist/cmd/bundle/bundler.d.ts +6 -0
- package/dist/cmd/bundle/bundler.d.ts.map +1 -0
- package/dist/cmd/bundle/file.d.ts +2 -0
- package/dist/cmd/bundle/file.d.ts.map +1 -0
- package/dist/cmd/bundle/index.d.ts +2 -0
- package/dist/cmd/bundle/index.d.ts.map +1 -0
- package/dist/cmd/bundle/plugin.d.ts +4 -0
- package/dist/cmd/bundle/plugin.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts +2 -0
- package/dist/cmd/dev/index.d.ts.map +1 -0
- package/dist/cmd/example/create-user.d.ts +2 -0
- package/dist/cmd/example/create-user.d.ts.map +1 -0
- package/dist/cmd/example/create.d.ts +2 -0
- package/dist/cmd/example/create.d.ts.map +1 -0
- package/dist/cmd/example/deploy.d.ts +2 -0
- package/dist/cmd/example/deploy.d.ts.map +1 -0
- package/dist/cmd/example/index.d.ts +2 -0
- package/dist/cmd/example/index.d.ts.map +1 -0
- package/dist/cmd/example/list.d.ts +2 -0
- package/dist/cmd/example/list.d.ts.map +1 -0
- package/dist/cmd/example/run-command.d.ts +2 -0
- package/dist/cmd/example/run-command.d.ts.map +1 -0
- package/dist/cmd/example/sound.d.ts +3 -0
- package/dist/cmd/example/sound.d.ts.map +1 -0
- package/dist/cmd/example/spinner.d.ts +2 -0
- package/dist/cmd/example/spinner.d.ts.map +1 -0
- package/dist/cmd/example/steps.d.ts +2 -0
- package/dist/cmd/example/steps.d.ts.map +1 -0
- package/dist/cmd/example/version.d.ts +2 -0
- package/dist/cmd/example/version.d.ts.map +1 -0
- package/dist/cmd/index.d.ts +3 -0
- package/dist/cmd/index.d.ts.map +1 -0
- package/dist/cmd/profile/create.d.ts +2 -0
- package/dist/cmd/profile/create.d.ts.map +1 -0
- package/dist/cmd/profile/delete.d.ts +2 -0
- package/dist/cmd/profile/delete.d.ts.map +1 -0
- package/dist/cmd/profile/index.d.ts +2 -0
- package/dist/cmd/profile/index.d.ts.map +1 -0
- package/dist/cmd/profile/list.d.ts +3 -0
- package/dist/cmd/profile/list.d.ts.map +1 -0
- package/dist/cmd/profile/show.d.ts +2 -0
- package/dist/cmd/profile/show.d.ts.map +1 -0
- package/dist/cmd/profile/use.d.ts +2 -0
- package/dist/cmd/profile/use.d.ts.map +1 -0
- package/dist/cmd/project/create.d.ts +2 -0
- package/dist/cmd/project/create.d.ts.map +1 -0
- package/dist/cmd/project/delete.d.ts +2 -0
- package/dist/cmd/project/delete.d.ts.map +1 -0
- package/dist/cmd/project/index.d.ts +2 -0
- package/dist/cmd/project/index.d.ts.map +1 -0
- package/dist/cmd/project/list.d.ts +2 -0
- package/dist/cmd/project/list.d.ts.map +1 -0
- package/dist/cmd/project/show.d.ts +2 -0
- package/dist/cmd/project/show.d.ts.map +1 -0
- package/dist/cmd/version/index.d.ts +2 -0
- package/dist/cmd/version/index.d.ts.map +1 -0
- package/dist/command-prefix.d.ts +11 -0
- package/dist/command-prefix.d.ts.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/legacy-check.d.ts +6 -0
- package/dist/legacy-check.d.ts.map +1 -0
- package/dist/logger.d.ts +24 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/schema-parser.d.ts +24 -0
- package/dist/schema-parser.d.ts.map +1 -0
- package/dist/sound.d.ts +2 -0
- package/dist/sound.d.ts.map +1 -0
- package/dist/steps.d.ts +59 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/terminal.d.ts +3 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/tui.d.ts +156 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +46 -0
- package/src/api-errors.md +115 -0
- package/src/api.ts +186 -0
- package/src/auth.ts +91 -0
- package/src/banner.ts +23 -0
- package/src/cli.ts +198 -0
- package/src/cmd/auth/README.md +95 -0
- package/src/cmd/auth/api.ts +71 -0
- package/src/cmd/auth/index.ts +9 -0
- package/src/cmd/auth/login.ts +76 -0
- package/src/cmd/auth/logout.ts +14 -0
- package/src/cmd/bundle/ast.ts +228 -0
- package/src/cmd/bundle/bundler.ts +88 -0
- package/src/cmd/bundle/file.ts +16 -0
- package/src/cmd/bundle/index.ts +38 -0
- package/src/cmd/bundle/plugin.ts +259 -0
- package/src/cmd/dev/index.ts +83 -0
- package/src/cmd/example/create-user.ts +38 -0
- package/src/cmd/example/create.ts +31 -0
- package/src/cmd/example/deploy.ts +36 -0
- package/src/cmd/example/index.ts +27 -0
- package/src/cmd/example/list.ts +32 -0
- package/src/cmd/example/run-command.ts +45 -0
- package/src/cmd/example/sound.ts +14 -0
- package/src/cmd/example/spinner.ts +44 -0
- package/src/cmd/example/steps.ts +66 -0
- package/src/cmd/example/version.ts +13 -0
- package/src/cmd/index.ts +46 -0
- package/src/cmd/profile/README.md +80 -0
- package/src/cmd/profile/create.ts +57 -0
- package/src/cmd/profile/delete.ts +52 -0
- package/src/cmd/profile/index.ts +12 -0
- package/src/cmd/profile/list.ts +27 -0
- package/src/cmd/profile/show.ts +54 -0
- package/src/cmd/profile/use.ts +30 -0
- package/src/cmd/project/create.ts +247 -0
- package/src/cmd/project/delete.ts +13 -0
- package/src/cmd/project/index.ts +11 -0
- package/src/cmd/project/list.ts +13 -0
- package/src/cmd/project/show.ts +12 -0
- package/src/cmd/version/index.ts +16 -0
- package/src/command-prefix.ts +43 -0
- package/src/config.ts +304 -0
- package/src/index.ts +40 -0
- package/src/legacy-check.ts +127 -0
- package/src/logger.ts +235 -0
- package/src/runtime.ts +22 -0
- package/src/schema-parser.ts +213 -0
- package/src/sound.ts +25 -0
- package/src/steps.ts +245 -0
- package/src/terminal.ts +151 -0
- package/src/tui.md +254 -0
- package/src/tui.ts +838 -0
- package/src/types.ts +243 -0
- 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
|
+
}
|