@agentuity/cli 0.0.70 → 0.0.72

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 (91) hide show
  1. package/dist/cmd/auth/api.d.ts.map +1 -1
  2. package/dist/cmd/auth/api.js +1 -1
  3. package/dist/cmd/auth/api.js.map +1 -1
  4. package/dist/cmd/build/bundler.d.ts +4 -3
  5. package/dist/cmd/build/bundler.d.ts.map +1 -1
  6. package/dist/cmd/build/bundler.js +104 -10
  7. package/dist/cmd/build/bundler.js.map +1 -1
  8. package/dist/cmd/build/index.d.ts.map +1 -1
  9. package/dist/cmd/build/index.js +1 -0
  10. package/dist/cmd/build/index.js.map +1 -1
  11. package/dist/cmd/cloud/db/create.js +2 -2
  12. package/dist/cmd/cloud/db/create.js.map +1 -1
  13. package/dist/cmd/cloud/db/delete.js +2 -2
  14. package/dist/cmd/cloud/db/delete.js.map +1 -1
  15. package/dist/cmd/cloud/db/get.js +2 -2
  16. package/dist/cmd/cloud/db/get.js.map +1 -1
  17. package/dist/cmd/cloud/db/list.js +2 -2
  18. package/dist/cmd/cloud/db/list.js.map +1 -1
  19. package/dist/cmd/cloud/db/logs.js +2 -2
  20. package/dist/cmd/cloud/db/logs.js.map +1 -1
  21. package/dist/cmd/cloud/db/sql.js +2 -2
  22. package/dist/cmd/cloud/db/sql.js.map +1 -1
  23. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  24. package/dist/cmd/cloud/deploy.js +164 -24
  25. package/dist/cmd/cloud/deploy.js.map +1 -1
  26. package/dist/cmd/cloud/session/get.js +2 -2
  27. package/dist/cmd/cloud/session/get.js.map +1 -1
  28. package/dist/cmd/cloud/session/list.js +2 -2
  29. package/dist/cmd/cloud/session/list.js.map +1 -1
  30. package/dist/cmd/cloud/storage/create.js +2 -2
  31. package/dist/cmd/cloud/storage/create.js.map +1 -1
  32. package/dist/cmd/cloud/storage/delete.js +2 -2
  33. package/dist/cmd/cloud/storage/delete.js.map +1 -1
  34. package/dist/cmd/cloud/storage/download.js +2 -2
  35. package/dist/cmd/cloud/storage/download.js.map +1 -1
  36. package/dist/cmd/cloud/storage/get.js +2 -2
  37. package/dist/cmd/cloud/storage/get.js.map +1 -1
  38. package/dist/cmd/cloud/storage/list.js +2 -2
  39. package/dist/cmd/cloud/storage/list.js.map +1 -1
  40. package/dist/cmd/cloud/storage/upload.js +2 -2
  41. package/dist/cmd/cloud/storage/upload.js.map +1 -1
  42. package/dist/cmd/cloud/thread/delete.js +2 -2
  43. package/dist/cmd/cloud/thread/delete.js.map +1 -1
  44. package/dist/cmd/cloud/thread/get.js +2 -2
  45. package/dist/cmd/cloud/thread/get.js.map +1 -1
  46. package/dist/cmd/cloud/thread/list.js +2 -2
  47. package/dist/cmd/cloud/thread/list.js.map +1 -1
  48. package/dist/cmd/dev/index.d.ts.map +1 -1
  49. package/dist/cmd/dev/index.js +1 -0
  50. package/dist/cmd/dev/index.js.map +1 -1
  51. package/dist/config.d.ts +2 -1
  52. package/dist/config.d.ts.map +1 -1
  53. package/dist/config.js +8 -1
  54. package/dist/config.js.map +1 -1
  55. package/dist/env-util.d.ts.map +1 -1
  56. package/dist/env-util.js +0 -3
  57. package/dist/env-util.js.map +1 -1
  58. package/dist/output.d.ts.map +1 -1
  59. package/dist/output.js +5 -1
  60. package/dist/output.js.map +1 -1
  61. package/dist/tui.d.ts +27 -1
  62. package/dist/tui.d.ts.map +1 -1
  63. package/dist/tui.js +140 -33
  64. package/dist/tui.js.map +1 -1
  65. package/package.json +3 -3
  66. package/src/cmd/auth/api.ts +1 -5
  67. package/src/cmd/build/bundler.ts +138 -13
  68. package/src/cmd/build/index.ts +1 -0
  69. package/src/cmd/cloud/db/create.ts +2 -2
  70. package/src/cmd/cloud/db/delete.ts +2 -2
  71. package/src/cmd/cloud/db/get.ts +2 -2
  72. package/src/cmd/cloud/db/list.ts +2 -2
  73. package/src/cmd/cloud/db/logs.ts +2 -2
  74. package/src/cmd/cloud/db/sql.ts +2 -2
  75. package/src/cmd/cloud/deploy.ts +188 -24
  76. package/src/cmd/cloud/session/get.ts +2 -2
  77. package/src/cmd/cloud/session/list.ts +2 -2
  78. package/src/cmd/cloud/storage/create.ts +2 -2
  79. package/src/cmd/cloud/storage/delete.ts +2 -2
  80. package/src/cmd/cloud/storage/download.ts +2 -2
  81. package/src/cmd/cloud/storage/get.ts +2 -2
  82. package/src/cmd/cloud/storage/list.ts +2 -2
  83. package/src/cmd/cloud/storage/upload.ts +2 -2
  84. package/src/cmd/cloud/thread/delete.ts +2 -2
  85. package/src/cmd/cloud/thread/get.ts +2 -2
  86. package/src/cmd/cloud/thread/list.ts +2 -2
  87. package/src/cmd/dev/index.ts +1 -0
  88. package/src/config.ts +9 -6
  89. package/src/env-util.ts +0 -3
  90. package/src/output.ts +7 -1
  91. package/src/tui.ts +192 -34
@@ -74,9 +74,9 @@ export const listSubcommand = createSubcommand({
74
74
  },
75
75
 
76
76
  async handler(ctx) {
77
- const { logger, args, opts, options, orgId, region, config, auth } = ctx;
77
+ const { logger, args, opts, options, orgId, region, auth } = ctx;
78
78
 
79
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
79
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
80
80
 
81
81
  const resources = await tui.spinner({
82
82
  message: `Fetching storage for ${orgId} in ${region}`,
@@ -49,9 +49,9 @@ export const uploadSubcommand = createSubcommand({
49
49
  },
50
50
 
51
51
  async handler(ctx) {
52
- const { logger, args, opts, options, orgId, region, config, auth } = ctx;
52
+ const { logger, args, opts, options, orgId, region, auth } = ctx;
53
53
 
54
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
54
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
55
55
 
56
56
  // Fetch bucket credentials
57
57
  const resources = await tui.spinner({
@@ -24,8 +24,8 @@ export const deleteSubcommand = createSubcommand({
24
24
  }),
25
25
  },
26
26
  async handler(ctx) {
27
- const { config, logger, auth, args, region } = ctx;
28
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
27
+ const { logger, auth, args, region } = ctx;
28
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
29
29
 
30
30
  try {
31
31
  await threadDelete(catalystClient, { id: args.thread_id });
@@ -37,8 +37,8 @@ export const getSubcommand = createSubcommand({
37
37
  response: ThreadGetResponseSchema,
38
38
  },
39
39
  async handler(ctx) {
40
- const { config, logger, auth, args, options, region } = ctx;
41
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
40
+ const { logger, auth, args, options, region } = ctx;
41
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
42
42
 
43
43
  try {
44
44
  const thread = await threadGet(catalystClient, { id: args.thread_id });
@@ -62,8 +62,8 @@ export const listSubcommand = createSubcommand({
62
62
  response: ThreadListResponseSchema,
63
63
  },
64
64
  async handler(ctx) {
65
- const { config, logger, auth, project, opts, options, region } = ctx;
66
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
65
+ const { logger, auth, project, opts, options, region } = ctx;
66
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
67
67
 
68
68
  const projectId = opts.projectId || project?.projectId;
69
69
  const orgId = opts.orgId;
@@ -499,6 +499,7 @@ export const command = createCommand({
499
499
  deploymentId,
500
500
  port: opts.port,
501
501
  region: project?.region ?? 'local',
502
+ logger,
502
503
  });
503
504
  building = false;
504
505
  buildCompletedAt = Date.now();
package/src/config.ts CHANGED
@@ -727,12 +727,7 @@ export async function loadProjectSDKKey(
727
727
  logger.trace(`[SDK_KEY] AGENTUITY_SDK_KEY not found in any file`);
728
728
  }
729
729
 
730
- export function getCatalystAPIClient(
731
- config: Config | null,
732
- logger: Logger,
733
- auth: AuthData,
734
- region: string
735
- ) {
730
+ export function getCatalystAPIClient(logger: Logger, auth: AuthData, region: string) {
736
731
  const serviceUrls = getServiceUrls(region);
737
732
  const catalystUrl = serviceUrls.catalyst;
738
733
  return new ServerAPIClient(catalystUrl, logger, auth.apiKey);
@@ -745,3 +740,11 @@ export function getIONHost(config: Config | null) {
745
740
  const url = new URL(config?.overrides?.ion_url ?? 'https://ion.agentuity.cloud');
746
741
  return url.hostname;
747
742
  }
743
+
744
+ export function getStreamURL(region: string, config: Config | null) {
745
+ if (config?.name === 'local') {
746
+ return 'https://streams.agentuity.io';
747
+ }
748
+ const serviceUrls = getServiceUrls(region);
749
+ return serviceUrls.stream;
750
+ }
package/src/env-util.ts CHANGED
@@ -70,7 +70,6 @@ export async function readEnvFile(path: string): Promise<EnvVars> {
70
70
  const file = Bun.file(path);
71
71
 
72
72
  if (!(await file.exists())) {
73
- console.log(`[ENV] File does not exist: ${path}`);
74
73
  return {};
75
74
  }
76
75
 
@@ -85,8 +84,6 @@ export async function readEnvFile(path: string): Promise<EnvVars> {
85
84
  }
86
85
  }
87
86
 
88
- console.log(`[ENV] Read ${Object.keys(env).length} variables from: ${path}`);
89
- console.log(`[ENV] Variables: ${Object.keys(env).join(', ')}`);
90
87
  return env;
91
88
  }
92
89
 
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 options.noProgress === true || options.json === true || options.quiet === true;
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,32 @@ 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
+
827
930
  /**
828
931
  * Spinner options (discriminated union)
829
932
  */
830
- export type SpinnerOptions<T> = SimpleSpinnerOptions<T> | ProgressSpinnerOptions<T>;
933
+ export type SpinnerOptions<T> =
934
+ | SimpleSpinnerOptions<T>
935
+ | ProgressSpinnerOptions<T>
936
+ | LoggerSpinnerOptions<T>;
831
937
 
832
938
  /**
833
939
  * Run a callback with an animated spinner (simple overload)
@@ -882,9 +988,14 @@ export async function spinner<T>(
882
988
  const result =
883
989
  options.type === 'progress'
884
990
  ? await options.callback(() => {})
885
- : typeof options.callback === 'function'
886
- ? await options.callback()
887
- : await options.callback;
991
+ : options.type === 'logger'
992
+ ? await options.callback((logMessage: string) => {
993
+ // In non-TTY mode, just write logs directly to stdout
994
+ process.stdout.write(logMessage + '\n');
995
+ })
996
+ : typeof options.callback === 'function'
997
+ ? await options.callback()
998
+ : await options.callback;
888
999
 
889
1000
  // If clearOnSuccess is true, don't show success message
890
1001
  // Also skip success message in JSON mode
@@ -914,12 +1025,22 @@ export async function spinner<T>(
914
1025
 
915
1026
  let frameIndex = 0;
916
1027
  let currentProgress: number | undefined;
1028
+ const logLines: string[] = [];
1029
+ const maxLines = options.type === 'logger' ? (options.maxLines ?? 3) : 0;
1030
+ const mutedColor = getColor('muted');
1031
+ let linesRendered = 0;
917
1032
 
918
- // Save cursor position and hide cursor
919
- process.stderr.write('\x1B[s\x1B[?25l');
1033
+ // Get terminal width for truncation
1034
+ const termWidth = process.stderr.columns || 80;
1035
+ const maxLineWidth = Math.min(80, termWidth);
1036
+
1037
+ // Function to render spinner with optional log lines
1038
+ const renderSpinner = () => {
1039
+ // Move cursor up to start of our output area if we've rendered before
1040
+ if (linesRendered > 0) {
1041
+ process.stderr.write(`\x1b[${linesRendered}A`);
1042
+ }
920
1043
 
921
- // Start animation
922
- const interval = setInterval(() => {
923
1044
  const colorDef = spinnerColors[frameIndex % spinnerColors.length];
924
1045
  const color = colorDef[currentColorScheme];
925
1046
  const frame = `${color}${bold}${frames[frameIndex % frames.length]}${reset}`;
@@ -930,31 +1051,64 @@ export async function spinner<T>(
930
1051
  ? ` ${cyanColor}${Math.floor(currentProgress)}%${reset}`
931
1052
  : '';
932
1053
 
933
- // Clear line and render
934
- process.stderr.write('\r\x1B[K' + `${frame} ${message}${progressIndicator}`);
1054
+ // Render spinner line
1055
+ process.stderr.write(`\r\x1b[K${frame} ${message}${progressIndicator}\n`);
1056
+
1057
+ // Render log lines if in logger mode
1058
+ if (options.type === 'logger') {
1059
+ const displayLines = maxLines < 0 ? logLines : logLines.slice(-maxLines);
1060
+ for (const line of displayLines) {
1061
+ const displayLine =
1062
+ getDisplayWidth(line) > maxLineWidth ? truncateToWidth(line, maxLineWidth) : line;
1063
+ process.stderr.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
1064
+ }
1065
+ linesRendered = 1 + displayLines.length;
1066
+ } else {
1067
+ linesRendered = 1;
1068
+ }
1069
+
935
1070
  frameIndex++;
936
- }, 120);
1071
+ };
1072
+
1073
+ // Save cursor position and hide cursor
1074
+ process.stderr.write('\x1B[s\x1B[?25l');
1075
+
1076
+ // Initial render
1077
+ renderSpinner();
1078
+
1079
+ // Start animation
1080
+ const interval = setInterval(renderSpinner, 120);
937
1081
 
938
1082
  // Progress callback
939
1083
  const progressCallback: SpinnerProgressCallback = (progress: number) => {
940
1084
  currentProgress = Math.min(100, Math.max(0, progress));
941
1085
  };
942
1086
 
1087
+ // Log callback
1088
+ const logCallback: SpinnerLogCallback = (logMessage: string) => {
1089
+ logLines.push(logMessage);
1090
+ };
1091
+
943
1092
  try {
944
1093
  // Execute callback
945
1094
  const result =
946
1095
  options.type === 'progress'
947
1096
  ? await options.callback(progressCallback)
948
- : typeof options.callback === 'function'
949
- ? await options.callback()
950
- : await options.callback;
1097
+ : options.type === 'logger'
1098
+ ? await options.callback(logCallback)
1099
+ : typeof options.callback === 'function'
1100
+ ? await options.callback()
1101
+ : await options.callback;
951
1102
 
952
1103
  // Stop animation first
953
1104
  clearInterval(interval);
954
1105
 
955
- // Restore cursor position, clear to end of screen, show cursor
956
- // This removes spinner and any partial output that happened during animation
957
- process.stderr.write('\x1B[u\x1B[J\x1B[?25h');
1106
+ // Move cursor to start of output, clear all lines
1107
+ if (linesRendered > 0) {
1108
+ process.stderr.write(`\x1b[${linesRendered}A`);
1109
+ }
1110
+ process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
1111
+ process.stderr.write('\x1B[?25h'); // Show cursor
958
1112
 
959
1113
  // If clearOnSuccess is false, show success message
960
1114
  if (!options.clearOnSuccess) {
@@ -968,17 +1122,27 @@ export async function spinner<T>(
968
1122
  // Stop animation first
969
1123
  clearInterval(interval);
970
1124
 
971
- // Restore cursor position, clear to end of screen, show cursor
972
- process.stderr.write('\x1B[u\x1B[J\x1B[?25h');
1125
+ // Move cursor to start of output, clear all lines
1126
+ if (linesRendered > 0) {
1127
+ process.stderr.write(`\x1b[${linesRendered}A`);
1128
+ }
1129
+ process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
1130
+ process.stderr.write('\x1B[?25h'); // Show cursor
973
1131
 
974
1132
  // Show error
975
1133
  const errorColor = getColor('error');
976
- console.error(`${errorColor}${ICONS.error} ${message}${reset}`);
1134
+ const errorMessage = err instanceof Error ? err.message : String(err);
1135
+ console.error(`${errorColor}${ICONS.error} ${message}: ${errorMessage}${reset}`);
977
1136
 
978
1137
  throw err;
979
1138
  }
980
1139
  }
981
1140
 
1141
+ /**
1142
+ * Alias for spinner function (for better semantics when using progress/logger types)
1143
+ */
1144
+ export const progress = spinner;
1145
+
982
1146
  /**
983
1147
  * Options for running a command with streaming output
984
1148
  */
@@ -1072,11 +1236,8 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
1072
1236
  const maxLineWidth = Math.min(80, termWidth);
1073
1237
 
1074
1238
  // Truncate command if needed
1075
- let displayCmd = command;
1076
- if (getDisplayWidth(displayCmd) > maxCmdWidth) {
1077
- // Simple truncation for now - could be smarter about this
1078
- displayCmd = displayCmd.slice(0, maxCmdWidth - 3) + '...';
1079
- }
1239
+ const displayCmd =
1240
+ getDisplayWidth(command) > maxCmdWidth ? truncateToWidth(command, maxCmdWidth) : command;
1080
1241
 
1081
1242
  // Store all output lines, display subset based on context
1082
1243
  const allOutputLines: string[] = [];
@@ -1100,11 +1261,8 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
1100
1261
 
1101
1262
  // Render output lines
1102
1263
  for (const line of displayLines) {
1103
- // Truncate line if needed
1104
- let displayLine = line;
1105
- if (getDisplayWidth(displayLine) > maxLineWidth) {
1106
- displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
1107
- }
1264
+ const displayLine =
1265
+ getDisplayWidth(line) > maxLineWidth ? truncateToWidth(line, maxLineWidth) : line;
1108
1266
  process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
1109
1267
  }
1110
1268
 
@@ -1198,10 +1356,10 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
1198
1356
  // Show final output lines
1199
1357
  const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
1200
1358
  for (const line of finalOutputLines) {
1201
- let displayLine = line;
1202
- if (truncate && getDisplayWidth(displayLine) > maxLineWidth) {
1203
- displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
1204
- }
1359
+ const displayLine =
1360
+ truncate && getDisplayWidth(line) > maxLineWidth
1361
+ ? truncateToWidth(line, maxLineWidth)
1362
+ : line;
1205
1363
  process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
1206
1364
  }
1207
1365