@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
package/src/steps.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step progress UI component for showing animated steps with callbacks
|
|
3
|
+
*
|
|
4
|
+
* Displays a checklist where each step animates in place with a spinner,
|
|
5
|
+
* then shows success, skipped, or error icon based on callback result.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ColorScheme } from './terminal';
|
|
9
|
+
|
|
10
|
+
// Spinner frames
|
|
11
|
+
const FRAMES = ['◐', '◓', '◑', '◒'];
|
|
12
|
+
|
|
13
|
+
// Icons
|
|
14
|
+
const ICONS = {
|
|
15
|
+
success: '✓',
|
|
16
|
+
skipped: '○',
|
|
17
|
+
error: '✗',
|
|
18
|
+
pending: '☐',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
// Color definitions (light/dark adaptive)
|
|
22
|
+
const COLORS = {
|
|
23
|
+
cyan: { light: '\x1b[36m', dark: '\x1b[96m' },
|
|
24
|
+
blue: { light: '\x1b[34m', dark: '\x1b[94m' },
|
|
25
|
+
magenta: { light: '\x1b[35m', dark: '\x1b[95m' },
|
|
26
|
+
green: { light: '\x1b[32m', dark: '\x1b[92m' },
|
|
27
|
+
yellow: { light: '\x1b[33m', dark: '\x1b[93m' },
|
|
28
|
+
red: { light: '\x1b[31m', dark: '\x1b[91m' },
|
|
29
|
+
gray: { light: '\x1b[90m', dark: '\x1b[37m' },
|
|
30
|
+
bold: '\x1b[1m',
|
|
31
|
+
strikethrough: '\x1b[9m',
|
|
32
|
+
reset: '\x1b[0m',
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
// Spinner color sequence
|
|
36
|
+
const SPINNER_COLORS = ['cyan', 'blue', 'magenta', 'cyan'] as const;
|
|
37
|
+
|
|
38
|
+
let currentColorScheme: ColorScheme = 'dark';
|
|
39
|
+
|
|
40
|
+
export function setStepsColorScheme(scheme: ColorScheme): void {
|
|
41
|
+
currentColorScheme = scheme;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getColor(colorKey: keyof typeof COLORS): string {
|
|
45
|
+
const color = COLORS[colorKey];
|
|
46
|
+
if (typeof color === 'string') {
|
|
47
|
+
return color;
|
|
48
|
+
}
|
|
49
|
+
return color[currentColorScheme];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Step outcome
|
|
54
|
+
*/
|
|
55
|
+
export type StepOutcome =
|
|
56
|
+
| { status: 'success' }
|
|
57
|
+
| { status: 'skipped'; reason?: string }
|
|
58
|
+
| { status: 'error'; message: string };
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Helper functions for creating step outcomes
|
|
62
|
+
*/
|
|
63
|
+
export const stepSuccess = (): StepOutcome => ({ status: 'success' });
|
|
64
|
+
export const stepSkipped = (reason?: string): StepOutcome => ({ status: 'skipped', reason });
|
|
65
|
+
export const stepError = (message: string): StepOutcome => ({ status: 'error', message });
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Progress callback function
|
|
69
|
+
*/
|
|
70
|
+
export type ProgressCallback = (progress: number) => void;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Step definition (without progress tracking)
|
|
74
|
+
*/
|
|
75
|
+
export interface SimpleStep {
|
|
76
|
+
type?: 'simple';
|
|
77
|
+
label: string;
|
|
78
|
+
run: () => Promise<StepOutcome>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Step definition (with progress tracking)
|
|
83
|
+
*/
|
|
84
|
+
export interface ProgressStep {
|
|
85
|
+
type: 'progress';
|
|
86
|
+
label: string;
|
|
87
|
+
run: (progress: ProgressCallback) => Promise<StepOutcome>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Step definition (discriminated union)
|
|
92
|
+
*/
|
|
93
|
+
export type Step = SimpleStep | ProgressStep;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Internal step state
|
|
97
|
+
*/
|
|
98
|
+
type StepState =
|
|
99
|
+
| {
|
|
100
|
+
type: 'simple';
|
|
101
|
+
label: string;
|
|
102
|
+
run: () => Promise<StepOutcome>;
|
|
103
|
+
outcome?: StepOutcome;
|
|
104
|
+
progress?: number;
|
|
105
|
+
}
|
|
106
|
+
| {
|
|
107
|
+
type: 'progress';
|
|
108
|
+
label: string;
|
|
109
|
+
run: (progress: ProgressCallback) => Promise<StepOutcome>;
|
|
110
|
+
outcome?: StepOutcome;
|
|
111
|
+
progress?: number;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Run a series of steps with animated progress
|
|
116
|
+
*
|
|
117
|
+
* Each step runs its callback while showing a spinner animation.
|
|
118
|
+
* Steps can complete with success, skipped, or error status.
|
|
119
|
+
* Exits with code 1 if any step errors.
|
|
120
|
+
*/
|
|
121
|
+
export async function runSteps(steps: Step[]): Promise<void> {
|
|
122
|
+
const state: StepState[] = steps.map((s) => {
|
|
123
|
+
const stepType = s.type === 'progress' ? 'progress' : 'simple';
|
|
124
|
+
return stepType === 'progress'
|
|
125
|
+
? {
|
|
126
|
+
type: 'progress' as const,
|
|
127
|
+
label: s.label,
|
|
128
|
+
run: s.run as (progress: ProgressCallback) => Promise<StepOutcome>,
|
|
129
|
+
}
|
|
130
|
+
: { type: 'simple' as const, label: s.label, run: s.run as () => Promise<StepOutcome> };
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Hide cursor
|
|
134
|
+
process.stdout.write('\x1B[?25l');
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// Initial render
|
|
138
|
+
process.stdout.write(renderSteps(state, -1) + '\n');
|
|
139
|
+
|
|
140
|
+
for (let stepIndex = 0; stepIndex < state.length; stepIndex++) {
|
|
141
|
+
const step = state[stepIndex];
|
|
142
|
+
let frameIndex = 0;
|
|
143
|
+
|
|
144
|
+
// Start spinner animation
|
|
145
|
+
const interval = setInterval(() => {
|
|
146
|
+
const colorKey = SPINNER_COLORS[frameIndex % SPINNER_COLORS.length];
|
|
147
|
+
const color = getColor(colorKey);
|
|
148
|
+
const frame = `${color}${COLORS.bold}${FRAMES[frameIndex % FRAMES.length]}${COLORS.reset}`;
|
|
149
|
+
|
|
150
|
+
// Move cursor up to the top of checklist
|
|
151
|
+
process.stdout.write(`\x1B[${state.length}A`);
|
|
152
|
+
process.stdout.write(renderSteps(state, stepIndex, frame) + '\n');
|
|
153
|
+
|
|
154
|
+
frameIndex++;
|
|
155
|
+
}, 120);
|
|
156
|
+
|
|
157
|
+
// Run the step with progress tracking
|
|
158
|
+
const progressCallback: ProgressCallback = (progress: number) => {
|
|
159
|
+
step.progress = Math.min(100, Math.max(0, progress));
|
|
160
|
+
|
|
161
|
+
// Move cursor up
|
|
162
|
+
process.stdout.write(`\x1B[${state.length}A`);
|
|
163
|
+
process.stdout.write(renderSteps(state, stepIndex) + '\n');
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Use discriminant to determine if step has progress callback
|
|
168
|
+
const outcome =
|
|
169
|
+
step.type === 'progress' ? await step.run(progressCallback) : await step.run();
|
|
170
|
+
step.outcome = outcome;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
step.outcome = {
|
|
173
|
+
status: 'error',
|
|
174
|
+
message: err instanceof Error ? err.message : String(err),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
clearInterval(interval);
|
|
179
|
+
|
|
180
|
+
// Clear progress and final render with outcome
|
|
181
|
+
step.progress = undefined;
|
|
182
|
+
process.stdout.write(`\x1B[${state.length}A`);
|
|
183
|
+
process.stdout.write(renderSteps(state, stepIndex) + '\n');
|
|
184
|
+
|
|
185
|
+
// If error, show error message and exit
|
|
186
|
+
if (step.outcome?.status === 'error') {
|
|
187
|
+
const errorColor = getColor('red');
|
|
188
|
+
console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}\n`);
|
|
189
|
+
process.stdout.write('\x1B[?25h'); // Show cursor
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Show cursor again
|
|
195
|
+
process.stdout.write('\x1B[?25h\n');
|
|
196
|
+
} catch (err) {
|
|
197
|
+
// Ensure cursor is shown even if something goes wrong
|
|
198
|
+
process.stdout.write('\x1B[?25h');
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Render a progress indicator
|
|
205
|
+
*/
|
|
206
|
+
function renderProgress(progress: number): string {
|
|
207
|
+
const cyanColor = getColor('cyan');
|
|
208
|
+
|
|
209
|
+
const percentage = `${Math.floor(progress)}%`;
|
|
210
|
+
return ` ${cyanColor}${percentage}${COLORS.reset}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Render all steps as a multiline string
|
|
215
|
+
*/
|
|
216
|
+
function renderSteps(steps: StepState[], activeIndex: number, spinner?: string): string {
|
|
217
|
+
const grayColor = getColor('gray');
|
|
218
|
+
const greenColor = getColor('green');
|
|
219
|
+
const yellowColor = getColor('yellow');
|
|
220
|
+
const redColor = getColor('red');
|
|
221
|
+
|
|
222
|
+
const lines: string[] = [];
|
|
223
|
+
|
|
224
|
+
steps.forEach((s, i) => {
|
|
225
|
+
if (s.outcome?.status === 'success') {
|
|
226
|
+
lines.push(
|
|
227
|
+
`${greenColor}${ICONS.success}${COLORS.reset} ${grayColor}${COLORS.strikethrough}${s.label}${COLORS.reset}`
|
|
228
|
+
);
|
|
229
|
+
} else if (s.outcome?.status === 'skipped') {
|
|
230
|
+
const reason = s.outcome.reason ? ` ${grayColor}(${s.outcome.reason})${COLORS.reset}` : '';
|
|
231
|
+
lines.push(
|
|
232
|
+
`${yellowColor}${ICONS.skipped}${COLORS.reset} ${grayColor}${COLORS.strikethrough}${s.label}${COLORS.reset}${reason}`
|
|
233
|
+
);
|
|
234
|
+
} else if (s.outcome?.status === 'error') {
|
|
235
|
+
lines.push(`${redColor}${ICONS.error}${COLORS.reset} ${s.label}`);
|
|
236
|
+
} else if (i === activeIndex && spinner) {
|
|
237
|
+
const progressIndicator = s.progress !== undefined ? renderProgress(s.progress) : '';
|
|
238
|
+
lines.push(`${spinner} ${s.label}${progressIndicator}`);
|
|
239
|
+
} else {
|
|
240
|
+
lines.push(`${grayColor}${ICONS.pending}${COLORS.reset} ${s.label}`);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return lines.join('\n');
|
|
245
|
+
}
|
package/src/terminal.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export type ColorScheme = 'light' | 'dark';
|
|
2
|
+
|
|
3
|
+
export async function detectColorScheme(): Promise<ColorScheme> {
|
|
4
|
+
const debug = process.env.DEBUG_COLORS === 'true';
|
|
5
|
+
|
|
6
|
+
// Check for explicit override
|
|
7
|
+
if (process.env.COLOR_SCHEME === 'light') {
|
|
8
|
+
if (debug) console.log('[DEBUG] Using COLOR_SCHEME=light override');
|
|
9
|
+
return 'light';
|
|
10
|
+
}
|
|
11
|
+
if (process.env.COLOR_SCHEME === 'dark') {
|
|
12
|
+
if (debug) console.log('[DEBUG] Using COLOR_SCHEME=dark override');
|
|
13
|
+
return 'dark';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Check if we have stdout TTY at minimum
|
|
17
|
+
if (!process.stdout.isTTY) {
|
|
18
|
+
if (debug) console.log('[DEBUG] stdout not a TTY, defaulting to dark');
|
|
19
|
+
return 'dark'; // Default to dark mode
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Try to query terminal background color using OSC 11 (most reliable)
|
|
23
|
+
if (debug) console.log('[DEBUG] Querying terminal background with OSC 11...');
|
|
24
|
+
try {
|
|
25
|
+
const bgColor = await queryTerminalBackground();
|
|
26
|
+
if (bgColor) {
|
|
27
|
+
const luminance = calculateLuminance(bgColor);
|
|
28
|
+
const scheme = luminance > 0.5 ? 'light' : 'dark';
|
|
29
|
+
if (debug)
|
|
30
|
+
console.log(
|
|
31
|
+
`[DEBUG] OSC 11 response: rgb(${bgColor.r},${bgColor.g},${bgColor.b}), luminance: ${luminance.toFixed(2)}, scheme: ${scheme}`
|
|
32
|
+
);
|
|
33
|
+
return scheme;
|
|
34
|
+
} else {
|
|
35
|
+
if (debug) console.log('[DEBUG] OSC 11 query timed out or no response');
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (debug) console.log('[DEBUG] OSC 11 query failed:', error);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Fall back to COLORFGBG environment variable (less reliable)
|
|
42
|
+
if (process.env.COLORFGBG) {
|
|
43
|
+
// COLORFGBG format is "foreground;background"
|
|
44
|
+
// This is unreliable but better than nothing
|
|
45
|
+
const parts = process.env.COLORFGBG.split(';');
|
|
46
|
+
const fg = parseInt(parts[0] || '7', 10);
|
|
47
|
+
const bg = parseInt(parts[1] || '0', 10);
|
|
48
|
+
|
|
49
|
+
// Heuristic: if background is 0 (black) and foreground is light (>=7), it's likely dark mode
|
|
50
|
+
// if background is light (>=7) and foreground is dark (<7), it's likely light mode
|
|
51
|
+
const scheme = bg >= 7 || bg > fg ? 'light' : 'dark';
|
|
52
|
+
if (debug)
|
|
53
|
+
console.log(
|
|
54
|
+
`[DEBUG] COLORFGBG fallback: ${process.env.COLORFGBG} (fg:${fg}, bg:${bg}), scheme: ${scheme}`
|
|
55
|
+
);
|
|
56
|
+
return scheme;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (debug) console.log('[DEBUG] Defaulting to dark mode');
|
|
60
|
+
return 'dark'; // Default to dark mode
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface RGBColor {
|
|
64
|
+
r: number;
|
|
65
|
+
g: number;
|
|
66
|
+
b: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function queryTerminalBackground(): Promise<RGBColor | null> {
|
|
70
|
+
// Skip if stdin is not available or not a TTY
|
|
71
|
+
if (!process.stdin.isTTY) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const timeout = setTimeout(() => {
|
|
77
|
+
cleanup();
|
|
78
|
+
resolve(null);
|
|
79
|
+
}, 100); // 100ms timeout
|
|
80
|
+
|
|
81
|
+
let response = '';
|
|
82
|
+
|
|
83
|
+
const onData = (data: Buffer) => {
|
|
84
|
+
response += data.toString();
|
|
85
|
+
|
|
86
|
+
// Look for OSC 11 response patterns:
|
|
87
|
+
// Pattern 1: ESC ] 11 ; rgb:RRRR/GGGG/BBBB ESC \ (xterm with ESC \ terminator)
|
|
88
|
+
// Pattern 2: ESC ] 11 ; rgb:RRRR/GGGG/BBBB BEL (xterm with BEL terminator)
|
|
89
|
+
// The color values can be 8-bit (RR), 12-bit (RRR), or 16-bit (RRRR)
|
|
90
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Control characters needed for ANSI escape sequences
|
|
91
|
+
const match = response.match(
|
|
92
|
+
// eslint-disable-next-line no-control-regex
|
|
93
|
+
/\x1b\]11;rgb:([0-9a-f]+)\/([0-9a-f]+)\/([0-9a-f]+)(?:\x1b\\|\x07)/i
|
|
94
|
+
);
|
|
95
|
+
if (match) {
|
|
96
|
+
cleanup();
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
|
|
99
|
+
// Parse RGB values - they can be different bit depths
|
|
100
|
+
// Normalize to 8-bit (0-255) by taking the most significant bits
|
|
101
|
+
const parseColorValue = (hex: string): number => {
|
|
102
|
+
if (hex.length === 4) {
|
|
103
|
+
// 16-bit: RRRR -> take first 2 chars
|
|
104
|
+
return parseInt(hex.slice(0, 2), 16);
|
|
105
|
+
} else if (hex.length === 3) {
|
|
106
|
+
// 12-bit: RRR -> take first 2 chars
|
|
107
|
+
return parseInt(hex.slice(0, 2), 16);
|
|
108
|
+
} else {
|
|
109
|
+
// 8-bit: RR
|
|
110
|
+
return parseInt(hex, 16);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const r = parseColorValue(match[1]);
|
|
115
|
+
const g = parseColorValue(match[2]);
|
|
116
|
+
const b = parseColorValue(match[3]);
|
|
117
|
+
|
|
118
|
+
resolve({ r, g, b });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const cleanup = () => {
|
|
123
|
+
process.stdin.setRawMode(false);
|
|
124
|
+
process.stdin.removeListener('data', onData);
|
|
125
|
+
process.stdin.pause(); // Pause stdin to allow process to exit
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Set stdin to raw mode to read escape sequences
|
|
129
|
+
process.stdin.setRawMode(true);
|
|
130
|
+
process.stdin.resume(); // Resume to receive data
|
|
131
|
+
process.stdin.on('data', onData);
|
|
132
|
+
|
|
133
|
+
// Send OSC 11 query with BEL terminator (more widely supported)
|
|
134
|
+
// Using BEL (\x07) as terminator instead of ESC \ for better compatibility
|
|
135
|
+
process.stdout.write('\x1b]11;?\x07');
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function calculateLuminance(color: RGBColor): number {
|
|
140
|
+
// Convert RGB to relative luminance using the formula from WCAG
|
|
141
|
+
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
142
|
+
const rsRGB = color.r / 255;
|
|
143
|
+
const gsRGB = color.g / 255;
|
|
144
|
+
const bsRGB = color.b / 255;
|
|
145
|
+
|
|
146
|
+
const r = rsRGB <= 0.03928 ? rsRGB / 12.92 : Math.pow((rsRGB + 0.055) / 1.055, 2.4);
|
|
147
|
+
const g = gsRGB <= 0.03928 ? gsRGB / 12.92 : Math.pow((gsRGB + 0.055) / 1.055, 2.4);
|
|
148
|
+
const b = bsRGB <= 0.03928 ? bsRGB / 12.92 : Math.pow((bsRGB + 0.055) / 1.055, 2.4);
|
|
149
|
+
|
|
150
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
151
|
+
}
|
package/src/tui.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Terminal UI Utilities
|
|
2
|
+
|
|
3
|
+
The `tui` module provides semantic helpers for formatted, colorized console output.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import * as tui from '@agentuity/cli/tui';
|
|
9
|
+
|
|
10
|
+
// Or from within the package:
|
|
11
|
+
import * as tui from './tui';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Message Functions
|
|
15
|
+
|
|
16
|
+
Print semantic messages with automatic icons and colors:
|
|
17
|
+
|
|
18
|
+
### `tui.success(message: string)`
|
|
19
|
+
|
|
20
|
+
Print a success message with a green checkmark (✓).
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
tui.success('Welcome to Agentuity! You are now logged in');
|
|
24
|
+
// Output: ✓ Welcome to Agentuity! You are now logged in (in green)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### `tui.error(message: string)`
|
|
28
|
+
|
|
29
|
+
Print an error message with a red X (✗).
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
tui.error('Connection failed');
|
|
33
|
+
// Output: ✗ Connection failed (in red)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `tui.warning(message: string)`
|
|
37
|
+
|
|
38
|
+
Print a warning message with a yellow warning icon (⚠).
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
tui.warning('CLI Upgrade Required');
|
|
42
|
+
// Output: ⚠ CLI Upgrade Required (in yellow)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `tui.info(message: string)`
|
|
46
|
+
|
|
47
|
+
Print an info message with a cyan info icon (ℹ).
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
tui.info('Processing request...');
|
|
51
|
+
// Output: ℹ Processing request... (in cyan)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## List Functions
|
|
55
|
+
|
|
56
|
+
### `tui.bullet(message: string)`
|
|
57
|
+
|
|
58
|
+
Print a bulleted list item (•).
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
tui.bullet('First item');
|
|
62
|
+
tui.bullet('Second item');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `tui.arrow(message: string)`
|
|
66
|
+
|
|
67
|
+
Print an arrow item (→), useful for showing next steps.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
tui.arrow('Run: agentuity deploy');
|
|
71
|
+
tui.arrow('Visit: https://app.agentuity.com');
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Text Formatting
|
|
75
|
+
|
|
76
|
+
These functions return formatted strings (don't print directly):
|
|
77
|
+
|
|
78
|
+
### `tui.bold(text: string): string`
|
|
79
|
+
|
|
80
|
+
Format text in bold.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
console.log(`Name: ${tui.bold('Production')}`);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `tui.muted(text: string): string`
|
|
87
|
+
|
|
88
|
+
Format text in muted/gray color.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
console.log(`${tui.muted('(optional)')}`);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `tui.link(url: string): string`
|
|
95
|
+
|
|
96
|
+
Format text as a clickable link (blue and underlined). If the terminal supports OSC 8 hyperlinks, creates a clickable link.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
console.log(`Visit: ${tui.link('https://agentuity.com')}`);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Supported terminals for clickable links:
|
|
103
|
+
|
|
104
|
+
- iTerm2
|
|
105
|
+
- WezTerm
|
|
106
|
+
- Ghostty
|
|
107
|
+
- Apple Terminal
|
|
108
|
+
- Hyper
|
|
109
|
+
- Kitty
|
|
110
|
+
- Windows Terminal
|
|
111
|
+
|
|
112
|
+
## Utility Functions
|
|
113
|
+
|
|
114
|
+
### `tui.newline()`
|
|
115
|
+
|
|
116
|
+
Print a blank line (shorthand for `console.log('')`).
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
tui.success('Done!');
|
|
120
|
+
tui.newline();
|
|
121
|
+
console.log('Next steps:');
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `tui.waitForAnyKey(message?: string): Promise<void>`
|
|
125
|
+
|
|
126
|
+
Wait for the user to press Enter before continuing. Useful for pausing execution to let the user read output or confirm an action.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
await tui.waitForAnyKey(); // Uses default message: "Press Enter to continue..."
|
|
130
|
+
await tui.waitForAnyKey('Press Enter to open the browser...');
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Behavior:**
|
|
134
|
+
|
|
135
|
+
- Waits for any key press (Enter, Space, etc.)
|
|
136
|
+
- Automatically exits with code 1 if CTRL+C is pressed
|
|
137
|
+
- Uses raw mode to capture single keypress
|
|
138
|
+
|
|
139
|
+
**Use cases:**
|
|
140
|
+
|
|
141
|
+
- Pausing before opening a browser
|
|
142
|
+
- Letting user read important information
|
|
143
|
+
- Confirming before proceeding with an action
|
|
144
|
+
|
|
145
|
+
### `tui.copyToClipboard(text: string): Promise<boolean>`
|
|
146
|
+
|
|
147
|
+
Copy text to the system clipboard. Returns `true` if successful, `false` otherwise.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const copied = await tui.copyToClipboard('ABC123');
|
|
151
|
+
if (copied) {
|
|
152
|
+
tui.success('Code copied to clipboard!');
|
|
153
|
+
} else {
|
|
154
|
+
console.log('Copy the following code: ABC123');
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Platform support:**
|
|
159
|
+
|
|
160
|
+
- macOS: Uses `pbcopy`
|
|
161
|
+
- Windows: Uses `clip`
|
|
162
|
+
- Linux: Tries `xclip`, falls back to `xsel`
|
|
163
|
+
|
|
164
|
+
**Note:** Silently fails if clipboard utilities are not available (returns `false`).
|
|
165
|
+
|
|
166
|
+
### `tui.padRight(str: string, length: number, pad?: string): string`
|
|
167
|
+
|
|
168
|
+
Pad a string to a specific length on the right.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const name = tui.padRight('prod', 10, ' ');
|
|
172
|
+
console.log(`${name} ${tui.muted('(active)')}`);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `tui.padLeft(str: string, length: number, pad?: string): string`
|
|
176
|
+
|
|
177
|
+
Pad a string to a specific length on the left.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const count = tui.padLeft('5', 3, '0');
|
|
181
|
+
console.log(count); // "005"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Color Scheme
|
|
185
|
+
|
|
186
|
+
The TUI module automatically adapts colors for light and dark terminals. You can set the color scheme:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { setColorScheme } from './tui';
|
|
190
|
+
|
|
191
|
+
setColorScheme('dark'); // or 'light'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Note:** The CLI automatically detects and sets the color scheme based on terminal settings.
|
|
195
|
+
|
|
196
|
+
## Banner
|
|
197
|
+
|
|
198
|
+
### `tui.banner(title: string, body: string)`
|
|
199
|
+
|
|
200
|
+
Display a formatted banner with a bordered box around the content. Perfect for important messages, welcome screens, or upgrade notices.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
tui.banner('Welcome', 'Thank you for using Agentuity! Your setup is complete.');
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Output:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
╭──────────────────────────────────────────────────╮
|
|
210
|
+
│ │
|
|
211
|
+
│ Welcome │
|
|
212
|
+
│ │
|
|
213
|
+
│ Thank you for using Agentuity! Your setup is │
|
|
214
|
+
│ complete. │
|
|
215
|
+
│ │
|
|
216
|
+
╰──────────────────────────────────────────────────╯
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Features:**
|
|
220
|
+
|
|
221
|
+
- Automatically wraps text to fit within 80 characters
|
|
222
|
+
- Centers the title
|
|
223
|
+
- Pads body text with borders
|
|
224
|
+
- Supports embedded formatting (bold, muted, links)
|
|
225
|
+
- Handles unicode characters correctly (emoji, CJK characters) using `Bun.stringWidth()`
|
|
226
|
+
|
|
227
|
+
## Complete Example
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import * as tui from './tui';
|
|
231
|
+
|
|
232
|
+
tui.info('Deploying application...');
|
|
233
|
+
tui.newline();
|
|
234
|
+
|
|
235
|
+
tui.bullet('Building assets');
|
|
236
|
+
tui.bullet('Uploading to server');
|
|
237
|
+
tui.bullet('Running migrations');
|
|
238
|
+
|
|
239
|
+
tui.newline();
|
|
240
|
+
tui.success('Deployment complete!');
|
|
241
|
+
tui.newline();
|
|
242
|
+
|
|
243
|
+
console.log('Next steps:');
|
|
244
|
+
tui.arrow(`Visit ${tui.link('https://app.agentuity.com')}`);
|
|
245
|
+
tui.arrow(`View logs: ${tui.bold('agentuity logs')}`);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Design Philosophy
|
|
249
|
+
|
|
250
|
+
- **Semantic over presentational**: Use `tui.success()` instead of manually coloring text green
|
|
251
|
+
- **Consistent icons**: Each message type has a standard icon
|
|
252
|
+
- **Adaptive colors**: Colors adjust for light/dark terminals
|
|
253
|
+
- **No logger prefix**: TUI functions use raw console output, no `[INFO]` prefixes
|
|
254
|
+
- **Composable**: Text formatting functions return strings for flexible composition
|