@agentuity/cli 0.0.35 → 0.0.42

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 (73) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +4 -4
  3. package/dist/api.d.ts +6 -22
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/auth.d.ts +0 -2
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/banner.d.ts.map +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cmd/auth/api.d.ts.map +1 -1
  10. package/dist/cmd/auth/login.d.ts +1 -2
  11. package/dist/cmd/auth/login.d.ts.map +1 -1
  12. package/dist/cmd/auth/logout.d.ts +1 -2
  13. package/dist/cmd/auth/logout.d.ts.map +1 -1
  14. package/dist/cmd/auth/signup.d.ts +1 -2
  15. package/dist/cmd/auth/signup.d.ts.map +1 -1
  16. package/dist/cmd/bundle/ast.d.ts +2 -0
  17. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  18. package/dist/cmd/bundle/bundler.d.ts +1 -0
  19. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  20. package/dist/cmd/bundle/patch/index.d.ts.map +1 -1
  21. package/dist/cmd/bundle/patch/llm.d.ts +3 -0
  22. package/dist/cmd/bundle/patch/llm.d.ts.map +1 -0
  23. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  24. package/dist/cmd/dev/index.d.ts.map +1 -1
  25. package/dist/cmd/index.d.ts.map +1 -1
  26. package/dist/cmd/project/create.d.ts.map +1 -1
  27. package/dist/cmd/project/delete.d.ts.map +1 -1
  28. package/dist/cmd/project/download.d.ts.map +1 -1
  29. package/dist/cmd/project/list.d.ts.map +1 -1
  30. package/dist/cmd/project/show.d.ts.map +1 -1
  31. package/dist/cmd/project/template-flow.d.ts +3 -0
  32. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  33. package/dist/config.d.ts +11 -2
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/logger.d.ts +1 -1
  38. package/dist/logger.d.ts.map +1 -1
  39. package/dist/sound.d.ts.map +1 -1
  40. package/dist/tui.d.ts +16 -7
  41. package/dist/tui.d.ts.map +1 -1
  42. package/dist/types.d.ts +70 -7
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +3 -2
  45. package/src/api.ts +27 -138
  46. package/src/auth.ts +87 -71
  47. package/src/banner.ts +7 -2
  48. package/src/cli.ts +7 -16
  49. package/src/cmd/auth/api.ts +40 -29
  50. package/src/cmd/auth/login.ts +7 -20
  51. package/src/cmd/auth/logout.ts +3 -3
  52. package/src/cmd/auth/signup.ts +6 -6
  53. package/src/cmd/bundle/ast.ts +169 -4
  54. package/src/cmd/bundle/bundler.ts +1 -0
  55. package/src/cmd/bundle/patch/index.ts +4 -0
  56. package/src/cmd/bundle/patch/llm.ts +36 -0
  57. package/src/cmd/bundle/plugin.ts +42 -1
  58. package/src/cmd/dev/index.ts +100 -1
  59. package/src/cmd/example/optional-auth.ts +1 -1
  60. package/src/cmd/index.ts +1 -0
  61. package/src/cmd/profile/README.md +1 -1
  62. package/src/cmd/project/create.ts +10 -2
  63. package/src/cmd/project/delete.ts +43 -2
  64. package/src/cmd/project/download.ts +17 -0
  65. package/src/cmd/project/list.ts +33 -2
  66. package/src/cmd/project/show.ts +35 -3
  67. package/src/cmd/project/template-flow.ts +60 -5
  68. package/src/config.ts +77 -5
  69. package/src/index.ts +2 -2
  70. package/src/logger.ts +1 -1
  71. package/src/sound.ts +9 -3
  72. package/src/tui.ts +234 -104
  73. package/src/types.ts +97 -34
package/src/tui.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  * Provides semantic helpers for console output with automatic icons and colors.
5
5
  * Uses Bun's built-in color support and ANSI escape codes.
6
6
  */
7
-
7
+ import enquirer from 'enquirer';
8
+ import type { OrganizationList } from '@agentuity/server';
8
9
  import type { ColorScheme } from './terminal';
9
10
 
10
11
  // Icons
@@ -17,38 +18,58 @@ const ICONS = {
17
18
  bullet: '•',
18
19
  } as const;
19
20
 
21
+ function shouldUseColors(): boolean {
22
+ return !process.env.NO_COLOR && process.env.TERM !== 'dumb' && !!process.stdout.isTTY;
23
+ }
24
+
20
25
  // Color definitions (light/dark adaptive) using Bun.color
21
- const COLORS = {
22
- success: {
23
- light: Bun.color('#008000', 'ansi') || '\x1b[32m', // green
24
- dark: Bun.color('#00FF00', 'ansi') || '\x1b[92m', // bright green
25
- },
26
- error: {
27
- light: Bun.color('#CC0000', 'ansi') || '\x1b[31m', // red
28
- dark: Bun.color('#FF5555', 'ansi') || '\x1b[91m', // bright red
29
- },
30
- warning: {
31
- light: Bun.color('#B58900', 'ansi') || '\x1b[33m', // yellow
32
- dark: Bun.color('#FFFF55', 'ansi') || '\x1b[93m', // bright yellow
33
- },
34
- info: {
35
- light: Bun.color('#008B8B', 'ansi') || '\x1b[36m', // dark cyan
36
- dark: Bun.color('#55FFFF', 'ansi') || '\x1b[96m', // bright cyan
37
- },
38
- muted: {
39
- light: Bun.color('#808080', 'ansi') || '\x1b[90m', // gray
40
- dark: Bun.color('#888888', 'ansi') || '\x1b[90m', // darker gray
41
- },
42
- bold: {
43
- light: '\x1b[1m',
44
- dark: '\x1b[1m',
45
- },
46
- link: {
47
- light: '\x1b[34;4m', // blue underline (need ANSI for underline)
48
- dark: '\x1b[94;4m', // bright blue underline
49
- },
50
- reset: '\x1b[0m',
51
- } as const;
26
+ function getColors() {
27
+ const USE_COLORS = shouldUseColors();
28
+ if (!USE_COLORS) {
29
+ return {
30
+ success: { light: '', dark: '' },
31
+ error: { light: '', dark: '' },
32
+ warning: { light: '', dark: '' },
33
+ info: { light: '', dark: '' },
34
+ muted: { light: '', dark: '' },
35
+ bold: { light: '', dark: '' },
36
+ link: { light: '', dark: '' },
37
+ reset: '',
38
+ } as const;
39
+ }
40
+
41
+ return {
42
+ success: {
43
+ light: Bun.color('#008000', 'ansi') || '\x1b[32m', // green
44
+ dark: Bun.color('#00FF00', 'ansi') || '\x1b[92m', // bright green
45
+ },
46
+ error: {
47
+ light: Bun.color('#CC0000', 'ansi') || '\x1b[31m', // red
48
+ dark: Bun.color('#FF5555', 'ansi') || '\x1b[91m', // bright red
49
+ },
50
+ warning: {
51
+ light: Bun.color('#B58900', 'ansi') || '\x1b[33m', // yellow
52
+ dark: Bun.color('#FFFF55', 'ansi') || '\x1b[93m', // bright yellow
53
+ },
54
+ info: {
55
+ light: Bun.color('#008B8B', 'ansi') || '\x1b[36m', // dark cyan
56
+ dark: Bun.color('#55FFFF', 'ansi') || '\x1b[96m', // bright cyan
57
+ },
58
+ muted: {
59
+ light: Bun.color('#808080', 'ansi') || '\x1b[90m', // gray
60
+ dark: Bun.color('#888888', 'ansi') || '\x1b[90m', // darker gray
61
+ },
62
+ bold: {
63
+ light: '\x1b[1m',
64
+ dark: '\x1b[1m',
65
+ },
66
+ link: {
67
+ light: '\x1b[34;4m', // blue underline (need ANSI for underline)
68
+ dark: '\x1b[94;4m', // bright blue underline
69
+ },
70
+ reset: '\x1b[0m',
71
+ } as const;
72
+ }
52
73
 
53
74
  let currentColorScheme: ColorScheme = 'dark';
54
75
 
@@ -56,7 +77,8 @@ export function setColorScheme(scheme: ColorScheme): void {
56
77
  currentColorScheme = scheme;
57
78
  }
58
79
 
59
- function getColor(colorKey: keyof typeof COLORS): string {
80
+ function getColor(colorKey: keyof ReturnType<typeof getColors>): string {
81
+ const COLORS = getColors();
60
82
  const color = COLORS[colorKey];
61
83
  if (typeof color === 'string') {
62
84
  return color;
@@ -69,7 +91,7 @@ function getColor(colorKey: keyof typeof COLORS): string {
69
91
  */
70
92
  export function success(message: string): void {
71
93
  const color = getColor('success');
72
- const reset = COLORS.reset;
94
+ const reset = getColor('reset');
73
95
  console.log(`${color}${ICONS.success} ${message}${reset}`);
74
96
  }
75
97
 
@@ -78,7 +100,7 @@ export function success(message: string): void {
78
100
  */
79
101
  export function error(message: string): void {
80
102
  const color = getColor('error');
81
- const reset = COLORS.reset;
103
+ const reset = getColor('reset');
82
104
  console.error(`${color}${ICONS.error} ${message}${reset}`);
83
105
  }
84
106
 
@@ -87,7 +109,7 @@ export function error(message: string): void {
87
109
  */
88
110
  export function fatal(message: string): never {
89
111
  const color = getColor('error');
90
- const reset = COLORS.reset;
112
+ const reset = getColor('reset');
91
113
  console.error(`${color}${ICONS.error} ${message}${reset}`);
92
114
  process.exit(1);
93
115
  }
@@ -97,7 +119,7 @@ export function fatal(message: string): never {
97
119
  */
98
120
  export function warning(message: string, asError = false): void {
99
121
  const color = asError ? getColor('error') : getColor('warning');
100
- const reset = COLORS.reset;
122
+ const reset = getColor('reset');
101
123
  console.log(`${color}${ICONS.warning} ${message}${reset}`);
102
124
  }
103
125
 
@@ -106,7 +128,7 @@ export function warning(message: string, asError = false): void {
106
128
  */
107
129
  export function info(message: string): void {
108
130
  const color = getColor('info');
109
- const reset = COLORS.reset;
131
+ const reset = getColor('reset');
110
132
  console.log(`${color}${ICONS.info} ${message}${reset}`);
111
133
  }
112
134
 
@@ -115,7 +137,7 @@ export function info(message: string): void {
115
137
  */
116
138
  export function muted(text: string): string {
117
139
  const color = getColor('muted');
118
- const reset = COLORS.reset;
140
+ const reset = getColor('reset');
119
141
  return `${color}${text}${reset}`;
120
142
  }
121
143
 
@@ -124,7 +146,7 @@ export function muted(text: string): string {
124
146
  */
125
147
  export function bold(text: string): string {
126
148
  const color = getColor('bold');
127
- const reset = COLORS.reset;
149
+ const reset = getColor('reset');
128
150
  return `${color}${text}${reset}`;
129
151
  }
130
152
 
@@ -133,7 +155,7 @@ export function bold(text: string): string {
133
155
  */
134
156
  export function link(url: string): string {
135
157
  const color = getColor('link');
136
- const reset = COLORS.reset;
158
+ const reset = getColor('reset');
137
159
 
138
160
  // Check if terminal supports hyperlinks (OSC 8)
139
161
  if (supportsHyperlinks()) {
@@ -205,6 +227,15 @@ export function padLeft(str: string, length: number, pad = ' '): string {
205
227
  return pad.repeat(length - str.length) + str;
206
228
  }
207
229
 
230
+ interface BannerOptions {
231
+ padding?: number;
232
+ minWidth?: number;
233
+ topSpacer?: boolean;
234
+ middleSpacer?: boolean;
235
+ bottomSpacer?: boolean;
236
+ centerTitle?: boolean;
237
+ }
238
+
208
239
  /**
209
240
  * Display a formatted banner with title and body content
210
241
  * Creates a bordered box around the content
@@ -212,10 +243,12 @@ export function padLeft(str: string, length: number, pad = ' '): string {
212
243
  * Uses Bun.stringWidth() for accurate width calculation with ANSI codes and unicode
213
244
  * Responsive to terminal width - adapts to narrow terminals
214
245
  */
215
- export function banner(title: string, body: string): void {
246
+ export function banner(title: string, body: string, options?: BannerOptions): void {
216
247
  // Get terminal width, default to 80 if not available, minimum 40
217
248
  const termWidth = process.stdout.columns || 80;
218
- const maxWidth = Math.max(40, Math.min(termWidth - 2, 80)); // Between 40 and 80, with 2 char margin
249
+ const minWidth = options?.minWidth ?? 40;
250
+ const maxWidth = Math.max(minWidth, Math.min(termWidth - 2, 80)); // Between 40 and 80, with 2 char margin
251
+ const padding = options?.padding ?? 4;
219
252
 
220
253
  const border = {
221
254
  topLeft: '╭',
@@ -227,19 +260,19 @@ export function banner(title: string, body: string): void {
227
260
  };
228
261
 
229
262
  // Split body into lines and wrap if needed
230
- const bodyLines = wrapText(body, maxWidth - 4); // -4 for padding and borders
263
+ const bodyLines = wrapText(body, maxWidth - padding); // -4 for padding and borders
231
264
 
232
265
  // Calculate width based on content
233
266
  const titleWidth = getDisplayWidth(title);
234
267
  const maxBodyWidth = Math.max(...bodyLines.map((line) => getDisplayWidth(line)));
235
- const contentWidth = Math.max(titleWidth, maxBodyWidth);
236
- const boxWidth = Math.min(contentWidth + 4, maxWidth); // +4 for padding
237
- const innerWidth = boxWidth - 4;
268
+ const contentWidth = Math.max(minWidth, Math.max(titleWidth, maxBodyWidth) + padding);
269
+ const boxWidth = Math.min(contentWidth, maxWidth); // +N for padding
270
+ const innerWidth = boxWidth - padding;
238
271
 
239
272
  // Colors
240
273
  const borderColor = getColor('muted');
241
274
  const titleColor = getColor('info');
242
- const reset = COLORS.reset;
275
+ const reset = getColor('reset');
243
276
 
244
277
  // Build banner
245
278
  const lines: string[] = [];
@@ -249,41 +282,58 @@ export function banner(title: string, body: string): void {
249
282
  `${borderColor}${border.topLeft}${border.horizontal.repeat(boxWidth - 2)}${border.topRight}${reset}`
250
283
  );
251
284
 
252
- // Empty line
253
- lines.push(
254
- `${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
255
- );
285
+ if (options?.topSpacer === true || options?.topSpacer === undefined) {
286
+ // Empty line
287
+ lines.push(
288
+ `${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
289
+ );
290
+ }
256
291
 
257
292
  // Title (centered and bold)
258
293
  const titleDisplayWidth = getDisplayWidth(title);
259
- const titlePadding = Math.max(0, Math.floor((innerWidth - titleDisplayWidth) / 2));
260
- const titleRightPadding = Math.max(0, innerWidth - titlePadding - titleDisplayWidth);
261
- const titleLine =
262
- ' '.repeat(titlePadding) +
263
- `${titleColor}${bold(title)}${reset}` +
264
- ' '.repeat(titleRightPadding);
265
- lines.push(
266
- `${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`
267
- );
294
+ if (options?.centerTitle === true || options?.centerTitle === undefined) {
295
+ const titlePadding = Math.max(0, Math.floor((innerWidth - titleDisplayWidth) / 2));
296
+ const titleRightPadding = Math.max(
297
+ 0,
298
+ Math.max(0, innerWidth - titlePadding - titleDisplayWidth) - padding
299
+ );
300
+ const titleLine =
301
+ ' '.repeat(titlePadding) +
302
+ `${titleColor}${bold(title)}${reset}` +
303
+ ' '.repeat(titleRightPadding);
304
+ lines.push(
305
+ `${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`
306
+ );
307
+ } else {
308
+ const titleRightPadding = Math.max(0, Math.max(0, innerWidth - titleDisplayWidth) - padding);
309
+ const titleLine = `${titleColor}${bold(title)}${reset}` + ' '.repeat(titleRightPadding);
310
+ lines.push(
311
+ `${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`
312
+ );
313
+ }
268
314
 
269
- // Empty line
270
- lines.push(
271
- `${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
272
- );
315
+ if (options?.middleSpacer === true || options?.middleSpacer === undefined) {
316
+ // Empty line
317
+ lines.push(
318
+ `${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
319
+ );
320
+ }
273
321
 
274
322
  // Body lines
275
323
  for (const line of bodyLines) {
276
324
  const lineWidth = getDisplayWidth(line);
277
- const padding = Math.max(0, innerWidth - lineWidth);
325
+ const linePadding = Math.max(0, Math.max(0, innerWidth - lineWidth) - padding);
278
326
  lines.push(
279
- `${borderColor}${border.vertical} ${reset}${line}${' '.repeat(padding)}${borderColor} ${border.vertical}${reset}`
327
+ `${borderColor}${border.vertical} ${reset}${line}${' '.repeat(linePadding)}${borderColor} ${border.vertical}${reset}`
280
328
  );
281
329
  }
282
330
 
283
- // Empty line
284
- lines.push(
285
- `${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
286
- );
331
+ if (options?.bottomSpacer === true || options?.bottomSpacer === undefined) {
332
+ // Empty line
333
+ lines.push(
334
+ `${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
335
+ );
336
+ }
287
337
 
288
338
  // Bottom border
289
339
  lines.push(
@@ -418,6 +468,30 @@ export function showSignupBenefits(): void {
418
468
  console.log('');
419
469
  }
420
470
 
471
+ /**
472
+ * Display a message when unauthenticated to let the user know certain capabilities are disabled
473
+ */
474
+ export function showLoggedOutMessage(): void {
475
+ const CYAN = Bun.color('yellow', 'ansi-16m');
476
+ const TEXT =
477
+ currentColorScheme === 'dark' ? Bun.color('white', 'ansi') : Bun.color('black', 'ansi');
478
+ const RESET = '\x1b[0m';
479
+
480
+ const lines = [
481
+ '╔══════════════════════════════════════════════╗',
482
+ `║ ⨺ Unauthenticated (local mode) ║`,
483
+ '║ ║',
484
+ `║ ${TEXT}Certain capabilities such as the AI services${CYAN} ║`,
485
+ `║ ${TEXT}and devmode remote are unavailable when${CYAN} ║`,
486
+ `║ ${TEXT}unauthenticated.${CYAN} ║`,
487
+ '╚══════════════════════════════════════════════╝',
488
+ ];
489
+
490
+ console.log('');
491
+ lines.forEach((line) => console.log(CYAN + line + RESET));
492
+ console.log('');
493
+ }
494
+
421
495
  /**
422
496
  * Copy text to clipboard
423
497
  * Returns true if successful, false otherwise
@@ -507,7 +581,7 @@ function stripAnsiCodes(str: string): string {
507
581
  * Check if a string ends with ANSI reset code
508
582
  */
509
583
  function endsWithReset(str: string): boolean {
510
- return str.endsWith('\x1b[0m') || str.endsWith(COLORS.reset);
584
+ return str.endsWith('\x1b[0m') || str.endsWith(getColor('reset'));
511
585
  }
512
586
 
513
587
  /**
@@ -572,7 +646,7 @@ function wrapText(text: string, maxWidth: number): string[] {
572
646
  if (leadingCodes && hasReset) {
573
647
  for (let i = paragraphStart; i < allLines.length; i++) {
574
648
  if (!endsWithReset(allLines[i])) {
575
- allLines[i] += COLORS.reset;
649
+ allLines[i] += getColor('reset');
576
650
  }
577
651
  }
578
652
  }
@@ -649,6 +723,26 @@ export async function spinner<T>(
649
723
  }
650
724
 
651
725
  const message = options.message;
726
+ const reset = getColor('reset');
727
+
728
+ // If no TTY, just execute the callback without animation
729
+ if (!process.stdout.isTTY) {
730
+ try {
731
+ const result =
732
+ options.type === 'progress'
733
+ ? await options.callback(() => {})
734
+ : typeof options.callback === 'function'
735
+ ? await options.callback()
736
+ : await options.callback;
737
+
738
+ return result;
739
+ } catch (err) {
740
+ const errorColor = getColor('error');
741
+ console.error(`${errorColor}${ICONS.error} ${message}${reset}`);
742
+ throw err;
743
+ }
744
+ }
745
+
652
746
  const frames = ['◐', '◓', '◑', '◒'];
653
747
  const spinnerColors = [
654
748
  { light: '\x1b[36m', dark: '\x1b[96m' }, // cyan
@@ -657,7 +751,6 @@ export async function spinner<T>(
657
751
  { light: '\x1b[36m', dark: '\x1b[96m' }, // cyan
658
752
  ];
659
753
  const bold = '\x1b[1m';
660
- const reset = COLORS.reset;
661
754
  const cyanColor = { light: '\x1b[36m', dark: '\x1b[96m' }[currentColorScheme];
662
755
 
663
756
  let frameIndex = 0;
@@ -812,7 +905,7 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
812
905
  ? '\x1b[1m' + (Bun.color('#00008B', 'ansi') || '\x1b[34m')
813
906
  : Bun.color('#FFFFFF', 'ansi') || '\x1b[97m'; // bold dark blue / white
814
907
  const mutedColor = Bun.color('#808080', 'ansi') || '\x1b[90m';
815
- const reset = COLORS.reset;
908
+ const reset = getColor('reset');
816
909
 
817
910
  // Get terminal width
818
911
  const termWidth = process.stdout.columns || 80;
@@ -905,41 +998,52 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
905
998
  // Wait for process to exit
906
999
  const exitCode = await proc.exited;
907
1000
 
908
- // Move cursor up to redraw final state
1001
+ // If clearOnSuccess is true and command succeeded, clear everything
1002
+ if (clearOnSuccess && exitCode === 0) {
1003
+ if (linesRendered > 0) {
1004
+ // Move up to the command line
1005
+ process.stdout.write(`\x1b[${linesRendered}A`);
1006
+ // Clear each line (entire line) and move cursor back up
1007
+ for (let i = 0; i < linesRendered; i++) {
1008
+ process.stdout.write('\x1b[2K'); // Clear entire line
1009
+ if (i < linesRendered - 1) {
1010
+ process.stdout.write('\x1b[B'); // Move down one line
1011
+ }
1012
+ }
1013
+ // Move cursor back up to original position
1014
+ process.stdout.write(`\x1b[${linesRendered}A\r`);
1015
+ }
1016
+ return exitCode;
1017
+ }
1018
+
1019
+ // Clear all rendered lines completely
909
1020
  if (linesRendered > 0) {
1021
+ // Move up to the command line (first line of our output)
910
1022
  process.stdout.write(`\x1b[${linesRendered}A`);
1023
+ // Move to beginning of line and clear from cursor to end of screen
1024
+ process.stdout.write('\r\x1b[J');
911
1025
  }
912
1026
 
913
- // Clear all lines if clearOnSuccess is true and command succeeded
914
- if (clearOnSuccess && exitCode === 0) {
915
- // Clear all rendered lines
916
- for (let i = 0; i < linesRendered; i++) {
917
- process.stdout.write('\r\x1b[K\n');
918
- }
919
- // Move cursor back up
920
- process.stdout.write(`\x1b[${linesRendered}A`);
1027
+ // Determine icon based on exit code
1028
+ const icon = exitCode === 0 ? ICONS.success : ICONS.error;
1029
+ const statusColor = exitCode === 0 ? green : red;
921
1030
 
922
- // Show compact success: command
923
- process.stdout.write(
924
- `\r\x1b[K${green}${ICONS.success}${reset} ${cmdColor}${displayCmd}${reset}\n`
925
- );
926
- } else {
927
- // Determine how many lines to show in final output
928
- const finalLinesToShow = exitCode === 0 ? maxLinesOutput : maxLinesOnFailure;
929
-
930
- // Show final status with appropriate color
931
- const statusColor = exitCode === 0 ? green : red;
932
- process.stdout.write(`\r\x1b[K${statusColor}$${reset} ${cmdColor}${displayCmd}${reset}\n`);
933
-
934
- // Show final output lines
935
- const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
936
- for (const line of finalOutputLines) {
937
- let displayLine = line;
938
- if (truncate && getDisplayWidth(displayLine) > maxLineWidth) {
939
- displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
940
- }
941
- process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
1031
+ // Show final status: icon + command
1032
+ process.stdout.write(
1033
+ `\r\x1b[K${statusColor}${icon}${reset} ${cmdColor}${displayCmd}${reset}\n`
1034
+ );
1035
+
1036
+ // Determine how many lines to show in final output
1037
+ const finalLinesToShow = exitCode === 0 ? maxLinesOutput : maxLinesOnFailure;
1038
+
1039
+ // Show final output lines
1040
+ const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
1041
+ for (const line of finalOutputLines) {
1042
+ let displayLine = line;
1043
+ if (truncate && getDisplayWidth(displayLine) > maxLineWidth) {
1044
+ displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
942
1045
  }
1046
+ process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
943
1047
  }
944
1048
 
945
1049
  return exitCode;
@@ -971,3 +1075,29 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
971
1075
  process.stdout.write('\x1B[?25h');
972
1076
  }
973
1077
  }
1078
+
1079
+ export async function selectOrganization(
1080
+ orgs: OrganizationList,
1081
+ initial?: string
1082
+ ): Promise<string> {
1083
+ if (orgs.length === 0) {
1084
+ fatal(
1085
+ 'You do not belong to any organizations.\n' +
1086
+ 'Please contact support or create an organization at https://agentuity.com'
1087
+ );
1088
+ }
1089
+
1090
+ if (orgs.length === 1) {
1091
+ return orgs[0].id;
1092
+ }
1093
+
1094
+ const response = await enquirer.prompt<{ action: string }>({
1095
+ type: 'select',
1096
+ name: 'action',
1097
+ message: 'Select an organization',
1098
+ initial,
1099
+ choices: orgs.map((o) => ({ message: o.name, name: o.id })),
1100
+ });
1101
+
1102
+ return response.action;
1103
+ }