@agentuity/cli 0.0.71 → 0.0.73
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/bin/cli.ts +19 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +77 -19
- package/dist/cli.js.map +1 -1
- package/dist/cmd/auth/api.d.ts +2 -2
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/api.js +15 -14
- package/dist/cmd/auth/api.js.map +1 -1
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/login.js +37 -16
- package/dist/cmd/auth/login.js.map +1 -1
- package/dist/cmd/auth/ssh/api.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/api.js +3 -2
- package/dist/cmd/auth/ssh/api.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +56 -8
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/bundler.d.ts +1 -2
- package/dist/cmd/build/bundler.d.ts.map +1 -1
- package/dist/cmd/build/bundler.js +30 -8
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/format-schema.d.ts +6 -0
- package/dist/cmd/build/format-schema.d.ts.map +1 -0
- package/dist/cmd/build/format-schema.js +60 -0
- package/dist/cmd/build/format-schema.js.map +1 -0
- package/dist/cmd/build/index.d.ts.map +1 -1
- package/dist/cmd/build/index.js +13 -0
- package/dist/cmd/build/index.js.map +1 -1
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +72 -2
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/cloud/db/create.js +2 -2
- package/dist/cmd/cloud/db/create.js.map +1 -1
- package/dist/cmd/cloud/db/delete.js +2 -2
- package/dist/cmd/cloud/db/delete.js.map +1 -1
- package/dist/cmd/cloud/db/get.js +2 -2
- package/dist/cmd/cloud/db/get.js.map +1 -1
- package/dist/cmd/cloud/db/list.js +2 -2
- package/dist/cmd/cloud/db/list.js.map +1 -1
- package/dist/cmd/cloud/db/logs.js +2 -2
- package/dist/cmd/cloud/db/logs.js.map +1 -1
- package/dist/cmd/cloud/db/sql.js +2 -2
- package/dist/cmd/cloud/db/sql.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +163 -24
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
- package/dist/cmd/cloud/deployment/show.js +34 -10
- package/dist/cmd/cloud/deployment/show.js.map +1 -1
- package/dist/cmd/cloud/session/get.js +2 -2
- package/dist/cmd/cloud/session/get.js.map +1 -1
- package/dist/cmd/cloud/session/list.js +2 -2
- package/dist/cmd/cloud/session/list.js.map +1 -1
- package/dist/cmd/cloud/storage/create.js +2 -2
- package/dist/cmd/cloud/storage/create.js.map +1 -1
- package/dist/cmd/cloud/storage/delete.js +2 -2
- package/dist/cmd/cloud/storage/delete.js.map +1 -1
- package/dist/cmd/cloud/storage/download.js +2 -2
- package/dist/cmd/cloud/storage/download.js.map +1 -1
- package/dist/cmd/cloud/storage/get.js +2 -2
- package/dist/cmd/cloud/storage/get.js.map +1 -1
- package/dist/cmd/cloud/storage/list.js +2 -2
- package/dist/cmd/cloud/storage/list.js.map +1 -1
- package/dist/cmd/cloud/storage/upload.js +2 -2
- package/dist/cmd/cloud/storage/upload.js.map +1 -1
- package/dist/cmd/cloud/thread/delete.js +2 -2
- package/dist/cmd/cloud/thread/delete.js.map +1 -1
- package/dist/cmd/cloud/thread/get.js +2 -2
- package/dist/cmd/cloud/thread/get.js.map +1 -1
- package/dist/cmd/cloud/thread/list.js +2 -2
- package/dist/cmd/cloud/thread/list.js.map +1 -1
- package/dist/cmd/dev/agents.d.ts.map +1 -1
- package/dist/cmd/dev/agents.js +2 -2
- package/dist/cmd/dev/agents.js.map +1 -1
- package/dist/cmd/dev/sync.d.ts.map +1 -1
- package/dist/cmd/dev/sync.js +2 -2
- package/dist/cmd/dev/sync.js.map +1 -1
- package/dist/cmd/project/show.d.ts.map +1 -1
- package/dist/cmd/project/show.js +8 -7
- package/dist/cmd/project/show.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +14 -2
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -1
- package/dist/config.js.map +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +5 -1
- package/dist/output.js.map +1 -1
- package/dist/tui.d.ts +46 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +217 -39
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +85 -25
- package/src/cmd/auth/api.ts +20 -26
- package/src/cmd/auth/login.ts +36 -17
- package/src/cmd/auth/ssh/api.ts +5 -6
- package/src/cmd/build/ast.ts +67 -8
- package/src/cmd/build/bundler.ts +37 -9
- package/src/cmd/build/format-schema.ts +66 -0
- package/src/cmd/build/index.ts +14 -0
- package/src/cmd/build/plugin.ts +86 -2
- package/src/cmd/cloud/db/create.ts +2 -2
- package/src/cmd/cloud/db/delete.ts +2 -2
- package/src/cmd/cloud/db/get.ts +2 -2
- package/src/cmd/cloud/db/list.ts +2 -2
- package/src/cmd/cloud/db/logs.ts +2 -2
- package/src/cmd/cloud/db/sql.ts +2 -2
- package/src/cmd/cloud/deploy.ts +187 -24
- package/src/cmd/cloud/deployment/show.ts +42 -10
- package/src/cmd/cloud/session/get.ts +2 -2
- package/src/cmd/cloud/session/list.ts +2 -2
- package/src/cmd/cloud/storage/create.ts +2 -2
- package/src/cmd/cloud/storage/delete.ts +2 -2
- package/src/cmd/cloud/storage/download.ts +2 -2
- package/src/cmd/cloud/storage/get.ts +2 -2
- package/src/cmd/cloud/storage/list.ts +2 -2
- package/src/cmd/cloud/storage/upload.ts +2 -2
- package/src/cmd/cloud/thread/delete.ts +2 -2
- package/src/cmd/cloud/thread/get.ts +2 -2
- package/src/cmd/cloud/thread/list.ts +2 -2
- package/src/cmd/dev/agents.ts +2 -4
- package/src/cmd/dev/sync.ts +6 -8
- package/src/cmd/project/show.ts +8 -6
- package/src/cmd/project/template-flow.ts +21 -2
- package/src/config.ts +19 -6
- package/src/output.ts +7 -1
- package/src/tui.ts +302 -41
package/src/config.ts
CHANGED
|
@@ -51,6 +51,16 @@ export async function saveProfile(path: string): Promise<void> {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export async function getProfile(): Promise<string> {
|
|
54
|
+
// Check environment variable first
|
|
55
|
+
if (process.env.AGENTUITY_PROFILE) {
|
|
56
|
+
const profileName = process.env.AGENTUITY_PROFILE;
|
|
57
|
+
const envProfilePath = join(getDefaultConfigDir(), `${profileName}.yaml`);
|
|
58
|
+
const envFile = Bun.file(envProfilePath);
|
|
59
|
+
if (await envFile.exists()) {
|
|
60
|
+
return envProfilePath;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
const profilePath = getProfilePath();
|
|
55
65
|
const defaultConfigPath = getDefaultConfigPath();
|
|
56
66
|
|
|
@@ -727,12 +737,7 @@ export async function loadProjectSDKKey(
|
|
|
727
737
|
logger.trace(`[SDK_KEY] AGENTUITY_SDK_KEY not found in any file`);
|
|
728
738
|
}
|
|
729
739
|
|
|
730
|
-
export function getCatalystAPIClient(
|
|
731
|
-
config: Config | null,
|
|
732
|
-
logger: Logger,
|
|
733
|
-
auth: AuthData,
|
|
734
|
-
region: string
|
|
735
|
-
) {
|
|
740
|
+
export function getCatalystAPIClient(logger: Logger, auth: AuthData, region: string) {
|
|
736
741
|
const serviceUrls = getServiceUrls(region);
|
|
737
742
|
const catalystUrl = serviceUrls.catalyst;
|
|
738
743
|
return new ServerAPIClient(catalystUrl, logger, auth.apiKey);
|
|
@@ -745,3 +750,11 @@ export function getIONHost(config: Config | null) {
|
|
|
745
750
|
const url = new URL(config?.overrides?.ion_url ?? 'https://ion.agentuity.cloud');
|
|
746
751
|
return url.hostname;
|
|
747
752
|
}
|
|
753
|
+
|
|
754
|
+
export function getStreamURL(region: string, config: Config | null) {
|
|
755
|
+
if (config?.name === 'local') {
|
|
756
|
+
return 'https://streams.agentuity.io';
|
|
757
|
+
}
|
|
758
|
+
const serviceUrls = getServiceUrls(region);
|
|
759
|
+
return serviceUrls.stream;
|
|
760
|
+
}
|
package/src/output.ts
CHANGED
|
@@ -39,7 +39,13 @@ export function isQuietMode(options: GlobalOptions): boolean {
|
|
|
39
39
|
* Check if progress indicators should be disabled
|
|
40
40
|
*/
|
|
41
41
|
export function shouldDisableProgress(options: GlobalOptions): boolean {
|
|
42
|
-
return
|
|
42
|
+
return (
|
|
43
|
+
options.noProgress === true ||
|
|
44
|
+
options.json === true ||
|
|
45
|
+
options.quiet === true ||
|
|
46
|
+
options.logLevel === 'debug' ||
|
|
47
|
+
options.logLevel === 'trace'
|
|
48
|
+
);
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
/**
|
package/src/tui.ts
CHANGED
|
@@ -341,6 +341,85 @@ function getDisplayWidth(str: string): number {
|
|
|
341
341
|
return Bun.stringWidth(withoutOSC8);
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Strip all ANSI escape sequences from a string
|
|
346
|
+
*/
|
|
347
|
+
function stripAnsi(str: string): string {
|
|
348
|
+
// eslint-disable-next-line no-control-regex
|
|
349
|
+
return str.replace(/\u001b\[[0-9;]*m/g, '').replace(/\u001b\]8;;[^\u0007]*\u0007/g, '');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Truncate a string to a maximum display width, handling ANSI codes and Unicode correctly
|
|
354
|
+
* Preserves ANSI escape sequences and doesn't break multi-byte characters or grapheme clusters
|
|
355
|
+
*/
|
|
356
|
+
function truncateToWidth(str: string, maxWidth: number, ellipsis = '...'): string {
|
|
357
|
+
const totalWidth = getDisplayWidth(str);
|
|
358
|
+
if (totalWidth <= maxWidth) {
|
|
359
|
+
return str;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Strip ANSI to get visible text
|
|
363
|
+
const visible = stripAnsi(str);
|
|
364
|
+
|
|
365
|
+
// Use Intl.Segmenter for grapheme-aware iteration
|
|
366
|
+
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
|
367
|
+
const segments = Array.from(segmenter.segment(visible));
|
|
368
|
+
|
|
369
|
+
// Find the cutoff point by accumulating display width
|
|
370
|
+
let currentWidth = 0;
|
|
371
|
+
let cutIndex = 0;
|
|
372
|
+
const targetWidth = maxWidth - ellipsis.length;
|
|
373
|
+
|
|
374
|
+
for (let i = 0; i < segments.length; i++) {
|
|
375
|
+
const segment = segments[i].segment;
|
|
376
|
+
const segmentWidth = Bun.stringWidth(segment);
|
|
377
|
+
|
|
378
|
+
if (currentWidth + segmentWidth > targetWidth) {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
currentWidth += segmentWidth;
|
|
383
|
+
cutIndex = segments[i].index + segment.length;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Now reconstruct with ANSI codes preserved
|
|
387
|
+
// Walk through original string and copy characters + ANSI codes until we hit cutIndex in visible content
|
|
388
|
+
let result = '';
|
|
389
|
+
let visibleIndex = 0;
|
|
390
|
+
let i = 0;
|
|
391
|
+
|
|
392
|
+
while (i < str.length && visibleIndex < cutIndex) {
|
|
393
|
+
// Check for ANSI escape sequence
|
|
394
|
+
if (str[i] === '\u001b') {
|
|
395
|
+
// Copy entire ANSI sequence
|
|
396
|
+
// eslint-disable-next-line no-control-regex
|
|
397
|
+
const match = str.slice(i).match(/^\u001b\[[0-9;]*m/);
|
|
398
|
+
if (match) {
|
|
399
|
+
result += match[0];
|
|
400
|
+
i += match[0].length;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Check for OSC 8 hyperlink
|
|
405
|
+
// eslint-disable-next-line no-control-regex
|
|
406
|
+
const oscMatch = str.slice(i).match(/^\u001b\]8;;[^\u0007]*\u0007/);
|
|
407
|
+
if (oscMatch) {
|
|
408
|
+
result += oscMatch[0];
|
|
409
|
+
i += oscMatch[0].length;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Copy visible character
|
|
415
|
+
result += str[i];
|
|
416
|
+
visibleIndex++;
|
|
417
|
+
i++;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return result + ellipsis;
|
|
421
|
+
}
|
|
422
|
+
|
|
344
423
|
/**
|
|
345
424
|
* Pad a string to a specific length on the right
|
|
346
425
|
*/
|
|
@@ -796,6 +875,11 @@ function wrapText(text: string, maxWidth: number): string[] {
|
|
|
796
875
|
*/
|
|
797
876
|
export type SpinnerProgressCallback = (progress: number) => void;
|
|
798
877
|
|
|
878
|
+
/**
|
|
879
|
+
* Log callback for spinner
|
|
880
|
+
*/
|
|
881
|
+
export type SpinnerLogCallback = (message: string) => void;
|
|
882
|
+
|
|
799
883
|
/**
|
|
800
884
|
* Spinner options (simple without progress)
|
|
801
885
|
*/
|
|
@@ -824,10 +908,53 @@ export interface ProgressSpinnerOptions<T> {
|
|
|
824
908
|
clearOnSuccess?: boolean;
|
|
825
909
|
}
|
|
826
910
|
|
|
911
|
+
/**
|
|
912
|
+
* Spinner options (with logger streaming)
|
|
913
|
+
*/
|
|
914
|
+
export interface LoggerSpinnerOptions<T> {
|
|
915
|
+
type: 'logger';
|
|
916
|
+
message: string;
|
|
917
|
+
callback: (log: SpinnerLogCallback) => Promise<T>;
|
|
918
|
+
/**
|
|
919
|
+
* If true, clear the spinner output on success (no icon, no message)
|
|
920
|
+
* Defaults to false
|
|
921
|
+
*/
|
|
922
|
+
clearOnSuccess?: boolean;
|
|
923
|
+
/**
|
|
924
|
+
* Maximum number of log lines to show while running
|
|
925
|
+
* If < 0, shows all lines. Defaults to 3.
|
|
926
|
+
*/
|
|
927
|
+
maxLines?: number;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Spinner options (with countdown timer)
|
|
932
|
+
*/
|
|
933
|
+
export interface CountdownSpinnerOptions<T> {
|
|
934
|
+
type: 'countdown';
|
|
935
|
+
message: string;
|
|
936
|
+
timeoutMs: number;
|
|
937
|
+
callback: () => Promise<T>;
|
|
938
|
+
/**
|
|
939
|
+
* If true, clear the spinner output on success (no icon, no message)
|
|
940
|
+
* Defaults to false
|
|
941
|
+
*/
|
|
942
|
+
clearOnSuccess?: boolean;
|
|
943
|
+
/**
|
|
944
|
+
* Optional callback to handle Enter key press
|
|
945
|
+
* Can be used to open a URL in the browser
|
|
946
|
+
*/
|
|
947
|
+
onEnterPress?: () => void;
|
|
948
|
+
}
|
|
949
|
+
|
|
827
950
|
/**
|
|
828
951
|
* Spinner options (discriminated union)
|
|
829
952
|
*/
|
|
830
|
-
export type SpinnerOptions<T> =
|
|
953
|
+
export type SpinnerOptions<T> =
|
|
954
|
+
| SimpleSpinnerOptions<T>
|
|
955
|
+
| ProgressSpinnerOptions<T>
|
|
956
|
+
| LoggerSpinnerOptions<T>
|
|
957
|
+
| CountdownSpinnerOptions<T>;
|
|
831
958
|
|
|
832
959
|
/**
|
|
833
960
|
* Run a callback with an animated spinner (simple overload)
|
|
@@ -882,9 +1009,16 @@ export async function spinner<T>(
|
|
|
882
1009
|
const result =
|
|
883
1010
|
options.type === 'progress'
|
|
884
1011
|
? await options.callback(() => {})
|
|
885
|
-
:
|
|
886
|
-
? await options.callback()
|
|
887
|
-
|
|
1012
|
+
: options.type === 'logger'
|
|
1013
|
+
? await options.callback((logMessage: string) => {
|
|
1014
|
+
// In non-TTY mode, just write logs directly to stdout
|
|
1015
|
+
process.stdout.write(logMessage + '\n');
|
|
1016
|
+
})
|
|
1017
|
+
: options.type === 'countdown'
|
|
1018
|
+
? await options.callback()
|
|
1019
|
+
: typeof options.callback === 'function'
|
|
1020
|
+
? await options.callback()
|
|
1021
|
+
: await options.callback;
|
|
888
1022
|
|
|
889
1023
|
// If clearOnSuccess is true, don't show success message
|
|
890
1024
|
// Also skip success message in JSON mode
|
|
@@ -914,47 +1048,168 @@ export async function spinner<T>(
|
|
|
914
1048
|
|
|
915
1049
|
let frameIndex = 0;
|
|
916
1050
|
let currentProgress: number | undefined;
|
|
1051
|
+
let remainingTime: number | undefined;
|
|
1052
|
+
const logLines: string[] = [];
|
|
1053
|
+
const maxLines = options.type === 'logger' ? (options.maxLines ?? 3) : 0;
|
|
1054
|
+
const mutedColor = getColor('muted');
|
|
1055
|
+
let linesRendered = 0;
|
|
917
1056
|
|
|
918
|
-
//
|
|
919
|
-
process.stderr.
|
|
1057
|
+
// Get terminal width for truncation
|
|
1058
|
+
const termWidth = process.stderr.columns || 80;
|
|
1059
|
+
const maxLineWidth = Math.min(80, termWidth);
|
|
1060
|
+
|
|
1061
|
+
// Function to render spinner with optional log lines
|
|
1062
|
+
const renderSpinner = () => {
|
|
1063
|
+
// Move cursor up to start of our output area if we've rendered before
|
|
1064
|
+
if (linesRendered > 0) {
|
|
1065
|
+
process.stderr.write(`\x1b[${linesRendered}A`);
|
|
1066
|
+
}
|
|
920
1067
|
|
|
921
|
-
// Start animation
|
|
922
|
-
const interval = setInterval(() => {
|
|
923
1068
|
const colorDef = spinnerColors[frameIndex % spinnerColors.length];
|
|
924
1069
|
const color = colorDef[currentColorScheme];
|
|
925
1070
|
const frame = `${color}${bold}${frames[frameIndex % frames.length]}${reset}`;
|
|
926
1071
|
|
|
927
|
-
// Add progress indicator if available
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1072
|
+
// Add progress indicator or countdown timer if available
|
|
1073
|
+
let indicator = '';
|
|
1074
|
+
if (currentProgress !== undefined) {
|
|
1075
|
+
indicator = ` ${cyanColor}${Math.floor(currentProgress)}%${reset}`;
|
|
1076
|
+
} else if (remainingTime !== undefined) {
|
|
1077
|
+
const minutes = Math.floor(remainingTime / 60);
|
|
1078
|
+
const seconds = Math.floor(remainingTime % 60);
|
|
1079
|
+
const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
1080
|
+
indicator = ` ${mutedColor}(${timeStr} remaining)${reset}`;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Render spinner line
|
|
1084
|
+
process.stderr.write(`\r\x1b[K${frame} ${message}${indicator}\n`);
|
|
1085
|
+
|
|
1086
|
+
// Render log lines if in logger mode
|
|
1087
|
+
if (options.type === 'logger') {
|
|
1088
|
+
const displayLines = maxLines < 0 ? logLines : logLines.slice(-maxLines);
|
|
1089
|
+
for (const line of displayLines) {
|
|
1090
|
+
const displayLine =
|
|
1091
|
+
getDisplayWidth(line) > maxLineWidth ? truncateToWidth(line, maxLineWidth) : line;
|
|
1092
|
+
process.stderr.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
|
|
1093
|
+
}
|
|
1094
|
+
linesRendered = 1 + displayLines.length;
|
|
1095
|
+
} else {
|
|
1096
|
+
linesRendered = 1;
|
|
1097
|
+
}
|
|
932
1098
|
|
|
933
|
-
// Clear line and render
|
|
934
|
-
process.stderr.write('\r\x1B[K' + `${frame} ${message}${progressIndicator}`);
|
|
935
1099
|
frameIndex++;
|
|
936
|
-
}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// Save cursor position and hide cursor
|
|
1103
|
+
process.stderr.write('\x1B[s\x1B[?25l');
|
|
1104
|
+
|
|
1105
|
+
// Initial render
|
|
1106
|
+
renderSpinner();
|
|
1107
|
+
|
|
1108
|
+
// Start animation
|
|
1109
|
+
const interval = setInterval(renderSpinner, 120);
|
|
937
1110
|
|
|
938
1111
|
// Progress callback
|
|
939
1112
|
const progressCallback: SpinnerProgressCallback = (progress: number) => {
|
|
940
1113
|
currentProgress = Math.min(100, Math.max(0, progress));
|
|
941
1114
|
};
|
|
942
1115
|
|
|
1116
|
+
// Log callback
|
|
1117
|
+
const logCallback: SpinnerLogCallback = (logMessage: string) => {
|
|
1118
|
+
logLines.push(logMessage);
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
// Countdown interval tracking
|
|
1122
|
+
let countdownInterval: NodeJS.Timeout | undefined;
|
|
1123
|
+
let keypressListener: ((chunk: Buffer) => void) | undefined;
|
|
1124
|
+
|
|
1125
|
+
// Helper to clean up all resources
|
|
1126
|
+
const cleanup = () => {
|
|
1127
|
+
if (countdownInterval) {
|
|
1128
|
+
clearInterval(countdownInterval);
|
|
1129
|
+
}
|
|
1130
|
+
if (keypressListener) {
|
|
1131
|
+
process.stdin.off('data', keypressListener);
|
|
1132
|
+
if (process.stdin.isTTY) {
|
|
1133
|
+
process.stdin.setRawMode(false);
|
|
1134
|
+
process.stdin.pause();
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
process.off('SIGINT', cleanupAndExit);
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
// Set up SIGINT handler for clean exit
|
|
1141
|
+
const cleanupAndExit = () => {
|
|
1142
|
+
cleanup();
|
|
1143
|
+
|
|
1144
|
+
// Stop animation
|
|
1145
|
+
clearInterval(interval);
|
|
1146
|
+
|
|
1147
|
+
// Move cursor to start of output, clear all lines
|
|
1148
|
+
if (linesRendered > 0) {
|
|
1149
|
+
process.stderr.write(`\x1b[${linesRendered}A`);
|
|
1150
|
+
}
|
|
1151
|
+
process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
|
|
1152
|
+
process.stderr.write('\x1B[?25h'); // Show cursor
|
|
1153
|
+
|
|
1154
|
+
process.exit(130); // Standard exit code for SIGINT
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
process.on('SIGINT', cleanupAndExit);
|
|
1158
|
+
|
|
943
1159
|
try {
|
|
1160
|
+
// For countdown, set up timer tracking and optional keyboard listener
|
|
1161
|
+
if (options.type === 'countdown') {
|
|
1162
|
+
const startTime = Date.now();
|
|
1163
|
+
remainingTime = options.timeoutMs / 1000;
|
|
1164
|
+
countdownInterval = setInterval(() => {
|
|
1165
|
+
const elapsed = Date.now() - startTime;
|
|
1166
|
+
remainingTime = Math.max(0, (options.timeoutMs - elapsed) / 1000);
|
|
1167
|
+
}, 100);
|
|
1168
|
+
|
|
1169
|
+
// Set up Enter key listener if callback provided
|
|
1170
|
+
if (options.onEnterPress && process.stdin.isTTY) {
|
|
1171
|
+
process.stdin.setRawMode(true);
|
|
1172
|
+
process.stdin.resume();
|
|
1173
|
+
|
|
1174
|
+
keypressListener = (chunk: Buffer) => {
|
|
1175
|
+
const key = chunk.toString();
|
|
1176
|
+
// Check for Enter key (both \r and \n)
|
|
1177
|
+
if (key === '\r' || key === '\n') {
|
|
1178
|
+
options.onEnterPress!();
|
|
1179
|
+
}
|
|
1180
|
+
// Check for Ctrl+C - let it propagate as SIGINT
|
|
1181
|
+
if (key === '\x03') {
|
|
1182
|
+
process.kill(process.pid, 'SIGINT');
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
process.stdin.on('data', keypressListener);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
944
1190
|
// Execute callback
|
|
945
1191
|
const result =
|
|
946
|
-
options.type === '
|
|
947
|
-
? await options.callback(
|
|
948
|
-
:
|
|
949
|
-
? await options.callback()
|
|
950
|
-
:
|
|
1192
|
+
options.type === 'countdown'
|
|
1193
|
+
? await options.callback()
|
|
1194
|
+
: options.type === 'progress'
|
|
1195
|
+
? await options.callback(progressCallback)
|
|
1196
|
+
: options.type === 'logger'
|
|
1197
|
+
? await options.callback(logCallback)
|
|
1198
|
+
: typeof options.callback === 'function'
|
|
1199
|
+
? await options.callback()
|
|
1200
|
+
: await options.callback;
|
|
1201
|
+
|
|
1202
|
+
cleanup();
|
|
951
1203
|
|
|
952
1204
|
// Stop animation first
|
|
953
1205
|
clearInterval(interval);
|
|
954
1206
|
|
|
955
|
-
//
|
|
956
|
-
|
|
957
|
-
|
|
1207
|
+
// Move cursor to start of output, clear all lines
|
|
1208
|
+
if (linesRendered > 0) {
|
|
1209
|
+
process.stderr.write(`\x1b[${linesRendered}A`);
|
|
1210
|
+
}
|
|
1211
|
+
process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
|
|
1212
|
+
process.stderr.write('\x1B[?25h'); // Show cursor
|
|
958
1213
|
|
|
959
1214
|
// If clearOnSuccess is false, show success message
|
|
960
1215
|
if (!options.clearOnSuccess) {
|
|
@@ -965,20 +1220,32 @@ export async function spinner<T>(
|
|
|
965
1220
|
|
|
966
1221
|
return result;
|
|
967
1222
|
} catch (err) {
|
|
1223
|
+
cleanup();
|
|
1224
|
+
|
|
968
1225
|
// Stop animation first
|
|
969
1226
|
clearInterval(interval);
|
|
970
1227
|
|
|
971
|
-
//
|
|
972
|
-
|
|
1228
|
+
// Move cursor to start of output, clear all lines
|
|
1229
|
+
if (linesRendered > 0) {
|
|
1230
|
+
process.stderr.write(`\x1b[${linesRendered}A`);
|
|
1231
|
+
}
|
|
1232
|
+
process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
|
|
1233
|
+
process.stderr.write('\x1B[?25h'); // Show cursor
|
|
973
1234
|
|
|
974
1235
|
// Show error
|
|
975
1236
|
const errorColor = getColor('error');
|
|
976
|
-
|
|
1237
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1238
|
+
console.error(`${errorColor}${ICONS.error} ${message}: ${errorMessage}${reset}`);
|
|
977
1239
|
|
|
978
1240
|
throw err;
|
|
979
1241
|
}
|
|
980
1242
|
}
|
|
981
1243
|
|
|
1244
|
+
/**
|
|
1245
|
+
* Alias for spinner function (for better semantics when using progress/logger types)
|
|
1246
|
+
*/
|
|
1247
|
+
export const progress = spinner;
|
|
1248
|
+
|
|
982
1249
|
/**
|
|
983
1250
|
* Options for running a command with streaming output
|
|
984
1251
|
*/
|
|
@@ -1072,11 +1339,8 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
|
|
|
1072
1339
|
const maxLineWidth = Math.min(80, termWidth);
|
|
1073
1340
|
|
|
1074
1341
|
// Truncate command if needed
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
// Simple truncation for now - could be smarter about this
|
|
1078
|
-
displayCmd = displayCmd.slice(0, maxCmdWidth - 3) + '...';
|
|
1079
|
-
}
|
|
1342
|
+
const displayCmd =
|
|
1343
|
+
getDisplayWidth(command) > maxCmdWidth ? truncateToWidth(command, maxCmdWidth) : command;
|
|
1080
1344
|
|
|
1081
1345
|
// Store all output lines, display subset based on context
|
|
1082
1346
|
const allOutputLines: string[] = [];
|
|
@@ -1100,11 +1364,8 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
|
|
|
1100
1364
|
|
|
1101
1365
|
// Render output lines
|
|
1102
1366
|
for (const line of displayLines) {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if (getDisplayWidth(displayLine) > maxLineWidth) {
|
|
1106
|
-
displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
|
|
1107
|
-
}
|
|
1367
|
+
const displayLine =
|
|
1368
|
+
getDisplayWidth(line) > maxLineWidth ? truncateToWidth(line, maxLineWidth) : line;
|
|
1108
1369
|
process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
|
|
1109
1370
|
}
|
|
1110
1371
|
|
|
@@ -1198,10 +1459,10 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
|
|
|
1198
1459
|
// Show final output lines
|
|
1199
1460
|
const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
|
|
1200
1461
|
for (const line of finalOutputLines) {
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1462
|
+
const displayLine =
|
|
1463
|
+
truncate && getDisplayWidth(line) > maxLineWidth
|
|
1464
|
+
? truncateToWidth(line, maxLineWidth)
|
|
1465
|
+
: line;
|
|
1205
1466
|
process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
|
|
1206
1467
|
}
|
|
1207
1468
|
|