@agentuity/cli 0.0.42 → 0.0.44

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 (249) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/cli.ts +7 -5
  4. package/dist/api.d.ts +3 -3
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/auth.d.ts +10 -2
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/banner.d.ts.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cmd/auth/api.d.ts +4 -4
  11. package/dist/cmd/auth/api.d.ts.map +1 -1
  12. package/dist/cmd/auth/index.d.ts.map +1 -1
  13. package/dist/cmd/auth/login.d.ts.map +1 -1
  14. package/dist/cmd/auth/signup.d.ts.map +1 -1
  15. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  16. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  17. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  18. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  20. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  21. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  22. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  23. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  24. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  25. package/dist/cmd/auth/whoami.d.ts +2 -0
  26. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  27. package/dist/cmd/bundle/ast.d.ts +14 -3
  28. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  29. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  30. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  31. package/dist/cmd/bundle/bundler.d.ts +6 -1
  32. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  33. package/dist/cmd/bundle/file.d.ts.map +1 -1
  34. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  36. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  37. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  38. package/dist/cmd/bundle/index.d.ts +1 -1
  39. package/dist/cmd/bundle/index.d.ts.map +1 -1
  40. package/dist/cmd/bundle/plugin.d.ts +2 -0
  41. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  42. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  43. package/dist/cmd/cloud/domain.d.ts +17 -0
  44. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  45. package/dist/cmd/cloud/index.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  47. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  50. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  51. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  52. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  53. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  56. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  57. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  58. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  59. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  60. package/dist/cmd/cloud/ssh.d.ts +2 -0
  61. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  62. package/dist/cmd/dev/api.d.ts +18 -0
  63. package/dist/cmd/dev/api.d.ts.map +1 -0
  64. package/dist/cmd/dev/download.d.ts +11 -0
  65. package/dist/cmd/dev/download.d.ts.map +1 -0
  66. package/dist/cmd/dev/index.d.ts.map +1 -1
  67. package/dist/cmd/dev/templates.d.ts +3 -0
  68. package/dist/cmd/dev/templates.d.ts.map +1 -0
  69. package/dist/cmd/env/delete.d.ts +2 -0
  70. package/dist/cmd/env/delete.d.ts.map +1 -0
  71. package/dist/cmd/env/get.d.ts +2 -0
  72. package/dist/cmd/env/get.d.ts.map +1 -0
  73. package/dist/cmd/env/import.d.ts +2 -0
  74. package/dist/cmd/env/import.d.ts.map +1 -0
  75. package/dist/cmd/env/index.d.ts +2 -0
  76. package/dist/cmd/env/index.d.ts.map +1 -0
  77. package/dist/cmd/env/list.d.ts.map +1 -0
  78. package/dist/cmd/env/pull.d.ts +2 -0
  79. package/dist/cmd/env/pull.d.ts.map +1 -0
  80. package/dist/cmd/env/push.d.ts +2 -0
  81. package/dist/cmd/env/push.d.ts.map +1 -0
  82. package/dist/cmd/env/set.d.ts +2 -0
  83. package/dist/cmd/env/set.d.ts.map +1 -0
  84. package/dist/cmd/profile/show.d.ts.map +1 -1
  85. package/dist/cmd/project/create.d.ts.map +1 -1
  86. package/dist/cmd/project/delete.d.ts.map +1 -1
  87. package/dist/cmd/project/download.d.ts +1 -1
  88. package/dist/cmd/project/download.d.ts.map +1 -1
  89. package/dist/cmd/project/list.d.ts.map +1 -1
  90. package/dist/cmd/project/show.d.ts.map +1 -1
  91. package/dist/cmd/project/template-flow.d.ts +5 -1
  92. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  93. package/dist/cmd/secret/delete.d.ts +2 -0
  94. package/dist/cmd/secret/delete.d.ts.map +1 -0
  95. package/dist/cmd/secret/get.d.ts +2 -0
  96. package/dist/cmd/secret/get.d.ts.map +1 -0
  97. package/dist/cmd/secret/import.d.ts +2 -0
  98. package/dist/cmd/secret/import.d.ts.map +1 -0
  99. package/dist/cmd/secret/index.d.ts +2 -0
  100. package/dist/cmd/secret/index.d.ts.map +1 -0
  101. package/dist/cmd/secret/list.d.ts +2 -0
  102. package/dist/cmd/secret/list.d.ts.map +1 -0
  103. package/dist/cmd/secret/pull.d.ts +2 -0
  104. package/dist/cmd/secret/pull.d.ts.map +1 -0
  105. package/dist/cmd/secret/push.d.ts +2 -0
  106. package/dist/cmd/secret/push.d.ts.map +1 -0
  107. package/dist/cmd/secret/set.d.ts +2 -0
  108. package/dist/cmd/secret/set.d.ts.map +1 -0
  109. package/dist/cmd/version/index.d.ts.map +1 -1
  110. package/dist/config.d.ts +11 -3
  111. package/dist/config.d.ts.map +1 -1
  112. package/dist/crypto/box.d.ts +65 -0
  113. package/dist/crypto/box.d.ts.map +1 -0
  114. package/dist/crypto/box.test.d.ts +2 -0
  115. package/dist/crypto/box.test.d.ts.map +1 -0
  116. package/dist/download.d.ts.map +1 -1
  117. package/dist/env-util.d.ts +67 -0
  118. package/dist/env-util.d.ts.map +1 -0
  119. package/dist/env-util.test.d.ts +2 -0
  120. package/dist/env-util.test.d.ts.map +1 -0
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/schema-parser.d.ts.map +1 -1
  124. package/dist/steps.d.ts +4 -1
  125. package/dist/steps.d.ts.map +1 -1
  126. package/dist/terminal.d.ts.map +1 -1
  127. package/dist/tui.d.ts +32 -2
  128. package/dist/tui.d.ts.map +1 -1
  129. package/dist/types.d.ts +250 -127
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/utils/detectSubagent.d.ts +15 -0
  132. package/dist/utils/detectSubagent.d.ts.map +1 -0
  133. package/dist/utils/zip.d.ts +7 -0
  134. package/dist/utils/zip.d.ts.map +1 -0
  135. package/package.json +11 -3
  136. package/src/api-errors.md +2 -2
  137. package/src/api.ts +12 -7
  138. package/src/auth.ts +116 -7
  139. package/src/banner.ts +13 -6
  140. package/src/cli.ts +709 -36
  141. package/src/cmd/auth/api.ts +10 -16
  142. package/src/cmd/auth/index.ts +3 -1
  143. package/src/cmd/auth/login.ts +24 -8
  144. package/src/cmd/auth/signup.ts +15 -11
  145. package/src/cmd/auth/ssh/add.ts +263 -0
  146. package/src/cmd/auth/ssh/api.ts +94 -0
  147. package/src/cmd/auth/ssh/delete.ts +102 -0
  148. package/src/cmd/auth/ssh/index.ts +10 -0
  149. package/src/cmd/auth/ssh/list.ts +74 -0
  150. package/src/cmd/auth/whoami.ts +69 -0
  151. package/src/cmd/bundle/ast.test.ts +565 -0
  152. package/src/cmd/bundle/ast.ts +457 -44
  153. package/src/cmd/bundle/bundler.ts +255 -57
  154. package/src/cmd/bundle/file.ts +6 -12
  155. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  156. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  157. package/src/cmd/bundle/index.ts +11 -11
  158. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  159. package/src/cmd/bundle/plugin.ts +373 -53
  160. package/src/cmd/cloud/deploy.ts +336 -0
  161. package/src/cmd/cloud/domain.ts +92 -0
  162. package/src/cmd/cloud/index.ts +11 -0
  163. package/src/cmd/cloud/resource/add.ts +56 -0
  164. package/src/cmd/cloud/resource/delete.ts +120 -0
  165. package/src/cmd/cloud/resource/index.ts +11 -0
  166. package/src/cmd/cloud/resource/list.ts +69 -0
  167. package/src/cmd/cloud/scp/download.ts +59 -0
  168. package/src/cmd/cloud/scp/index.ts +9 -0
  169. package/src/cmd/cloud/scp/upload.ts +62 -0
  170. package/src/cmd/cloud/ssh.ts +68 -0
  171. package/src/cmd/dev/api.ts +46 -0
  172. package/src/cmd/dev/download.ts +111 -0
  173. package/src/cmd/dev/index.ts +362 -34
  174. package/src/cmd/dev/templates.ts +84 -0
  175. package/src/cmd/env/delete.ts +47 -0
  176. package/src/cmd/env/get.ts +53 -0
  177. package/src/cmd/env/import.ts +102 -0
  178. package/src/cmd/env/index.ts +22 -0
  179. package/src/cmd/env/list.ts +56 -0
  180. package/src/cmd/env/pull.ts +80 -0
  181. package/src/cmd/env/push.ts +37 -0
  182. package/src/cmd/env/set.ts +71 -0
  183. package/src/cmd/index.ts +2 -2
  184. package/src/cmd/profile/show.ts +15 -6
  185. package/src/cmd/project/create.ts +7 -2
  186. package/src/cmd/project/delete.ts +75 -18
  187. package/src/cmd/project/download.ts +3 -3
  188. package/src/cmd/project/list.ts +8 -8
  189. package/src/cmd/project/show.ts +3 -7
  190. package/src/cmd/project/template-flow.ts +186 -48
  191. package/src/cmd/secret/delete.ts +40 -0
  192. package/src/cmd/secret/get.ts +54 -0
  193. package/src/cmd/secret/import.ts +64 -0
  194. package/src/cmd/secret/index.ts +22 -0
  195. package/src/cmd/secret/list.ts +56 -0
  196. package/src/cmd/secret/pull.ts +78 -0
  197. package/src/cmd/secret/push.ts +37 -0
  198. package/src/cmd/secret/set.ts +45 -0
  199. package/src/cmd/version/index.ts +2 -1
  200. package/src/config.ts +257 -27
  201. package/src/crypto/box.test.ts +431 -0
  202. package/src/crypto/box.ts +477 -0
  203. package/src/download.ts +1 -0
  204. package/src/env-util.test.ts +194 -0
  205. package/src/env-util.ts +290 -0
  206. package/src/index.ts +5 -1
  207. package/src/schema-parser.ts +2 -3
  208. package/src/steps.ts +144 -10
  209. package/src/terminal.ts +24 -23
  210. package/src/tui.ts +208 -68
  211. package/src/types.ts +292 -202
  212. package/src/utils/detectSubagent.ts +31 -0
  213. package/src/utils/zip.ts +38 -0
  214. package/dist/cmd/example/create-user.d.ts +0 -2
  215. package/dist/cmd/example/create-user.d.ts.map +0 -1
  216. package/dist/cmd/example/create.d.ts +0 -2
  217. package/dist/cmd/example/create.d.ts.map +0 -1
  218. package/dist/cmd/example/deploy.d.ts.map +0 -1
  219. package/dist/cmd/example/index.d.ts.map +0 -1
  220. package/dist/cmd/example/list.d.ts.map +0 -1
  221. package/dist/cmd/example/optional-auth.d.ts +0 -3
  222. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  223. package/dist/cmd/example/run-command.d.ts +0 -2
  224. package/dist/cmd/example/run-command.d.ts.map +0 -1
  225. package/dist/cmd/example/sound.d.ts +0 -3
  226. package/dist/cmd/example/sound.d.ts.map +0 -1
  227. package/dist/cmd/example/spinner.d.ts +0 -2
  228. package/dist/cmd/example/spinner.d.ts.map +0 -1
  229. package/dist/cmd/example/steps.d.ts +0 -2
  230. package/dist/cmd/example/steps.d.ts.map +0 -1
  231. package/dist/cmd/example/version.d.ts +0 -2
  232. package/dist/cmd/example/version.d.ts.map +0 -1
  233. package/dist/logger.d.ts +0 -24
  234. package/dist/logger.d.ts.map +0 -1
  235. package/src/cmd/example/create-user.ts +0 -38
  236. package/src/cmd/example/create.ts +0 -31
  237. package/src/cmd/example/deploy.ts +0 -36
  238. package/src/cmd/example/index.ts +0 -29
  239. package/src/cmd/example/list.ts +0 -32
  240. package/src/cmd/example/optional-auth.ts +0 -38
  241. package/src/cmd/example/run-command.ts +0 -45
  242. package/src/cmd/example/sound.ts +0 -14
  243. package/src/cmd/example/spinner.ts +0 -44
  244. package/src/cmd/example/steps.ts +0 -66
  245. package/src/cmd/example/version.ts +0 -13
  246. package/src/logger.ts +0 -235
  247. /package/dist/cmd/{example → cloud}/deploy.d.ts +0 -0
  248. /package/dist/cmd/{example → cloud}/index.d.ts +0 -0
  249. /package/dist/cmd/{example → env}/list.d.ts +0 -0
package/src/tui.ts CHANGED
@@ -4,9 +4,12 @@
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
+ import { stringWidth } from 'bun';
7
8
  import enquirer from 'enquirer';
8
- import type { OrganizationList } from '@agentuity/server';
9
+ import { type OrganizationList, projectList } from '@agentuity/server';
9
10
  import type { ColorScheme } from './terminal';
11
+ import { type APIClient as APIClientType } from './api';
12
+ import * as readline from 'readline';
10
13
 
11
14
  // Icons
12
15
  const ICONS = {
@@ -18,8 +21,13 @@ const ICONS = {
18
21
  bullet: '•',
19
22
  } as const;
20
23
 
21
- function shouldUseColors(): boolean {
22
- return !process.env.NO_COLOR && process.env.TERM !== 'dumb' && !!process.stdout.isTTY;
24
+ export function shouldUseColors(): boolean {
25
+ return (
26
+ !process.env.NO_COLOR &&
27
+ !process.env.CI &&
28
+ process.env.TERM !== 'dumb' &&
29
+ !!process.stdout.isTTY
30
+ );
23
31
  }
24
32
 
25
33
  // Color definitions (light/dark adaptive) using Bun.color
@@ -71,10 +79,15 @@ function getColors() {
71
79
  } as const;
72
80
  }
73
81
 
74
- let currentColorScheme: ColorScheme = 'dark';
82
+ let currentColorScheme: ColorScheme = process.env.CI ? 'light' : 'dark';
75
83
 
76
84
  export function setColorScheme(scheme: ColorScheme): void {
77
85
  currentColorScheme = scheme;
86
+ process.env.COLOR_SCHEME = scheme;
87
+ }
88
+
89
+ export function isDarkMode(): boolean {
90
+ return currentColorScheme === 'dark';
78
91
  }
79
92
 
80
93
  function getColor(colorKey: keyof ReturnType<typeof getColors>): string {
@@ -141,6 +154,15 @@ export function muted(text: string): string {
141
154
  return `${color}${text}${reset}`;
142
155
  }
143
156
 
157
+ /**
158
+ * Format text in warn color
159
+ */
160
+ export function warn(text: string): string {
161
+ const color = getColor('warning');
162
+ const reset = getColor('reset');
163
+ return `${color}${text}${reset}`;
164
+ }
165
+
144
166
  /**
145
167
  * Format text in bold
146
168
  */
@@ -153,13 +175,13 @@ export function bold(text: string): string {
153
175
  /**
154
176
  * Format text as a link (blue and underlined)
155
177
  */
156
- export function link(url: string): string {
178
+ export function link(url: string, title?: string): string {
157
179
  const color = getColor('link');
158
180
  const reset = getColor('reset');
159
181
 
160
- // Check if terminal supports hyperlinks (OSC 8)
161
- if (supportsHyperlinks()) {
162
- return `\x1b]8;;${url}\x07${color}${url}${reset}\x1b]8;;\x07`;
182
+ // Check if terminal supports hyperlinks (OSC 8) and colors are enabled
183
+ if (shouldUseColors() && supportsHyperlinks()) {
184
+ return `\x1b]8;;${url}\x07${color}${title ?? url}${reset}\x1b]8;;\x07`;
163
185
  }
164
186
 
165
187
  return `${color}${url}${reset}`;
@@ -207,24 +229,40 @@ export function newline(): void {
207
229
  console.log('');
208
230
  }
209
231
 
232
+ /**
233
+ * Get the display width of a string, handling ANSI codes and OSC 8 hyperlinks
234
+ *
235
+ * Note: Bun.stringWidth() counts OSC 8 hyperlink escape sequences in the width,
236
+ * which causes incorrect alignment. We strip OSC 8 codes first, then use Bun.stringWidth()
237
+ * to handle regular ANSI codes and unicode characters correctly.
238
+ */
239
+ function getDisplayWidth(str: string): number {
240
+ // Remove OSC-8 hyperlink sequences using Unicode escapes (\u001b = ESC, \u0007 = BEL) to satisfy linter
241
+ // eslint-disable-next-line no-control-regex
242
+ const withoutOSC8 = str.replace(/\u001b\]8;;[^\u0007]*\u0007/g, '');
243
+ return Bun.stringWidth(withoutOSC8);
244
+ }
245
+
210
246
  /**
211
247
  * Pad a string to a specific length on the right
212
248
  */
213
249
  export function padRight(str: string, length: number, pad = ' '): string {
214
- if (str.length >= length) {
250
+ const displayWidth = getDisplayWidth(str);
251
+ if (displayWidth >= length) {
215
252
  return str;
216
253
  }
217
- return str + pad.repeat(length - str.length);
254
+ return str + pad.repeat(length - displayWidth);
218
255
  }
219
256
 
220
257
  /**
221
258
  * Pad a string to a specific length on the left
222
259
  */
223
260
  export function padLeft(str: string, length: number, pad = ' '): string {
224
- if (str.length >= length) {
261
+ const displayWidth = getDisplayWidth(str);
262
+ if (displayWidth >= length) {
225
263
  return str;
226
264
  }
227
- return pad.repeat(length - str.length) + str;
265
+ return pad.repeat(length - displayWidth) + str;
228
266
  }
229
267
 
230
268
  interface BannerOptions {
@@ -244,11 +282,8 @@ interface BannerOptions {
244
282
  * Responsive to terminal width - adapts to narrow terminals
245
283
  */
246
284
  export function banner(title: string, body: string, options?: BannerOptions): void {
247
- // Get terminal width, default to 80 if not available, minimum 40
248
- const termWidth = process.stdout.columns || 80;
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;
285
+ // Get terminal width, default to 120 if not available
286
+ const termWidth = process.stdout.columns || 120;
252
287
 
253
288
  const border = {
254
289
  topLeft: '╭',
@@ -259,15 +294,27 @@ export function banner(title: string, body: string, options?: BannerOptions): vo
259
294
  vertical: '│',
260
295
  };
261
296
 
262
- // Split body into lines and wrap if needed
263
- const bodyLines = wrapText(body, maxWidth - padding); // -4 for padding and borders
264
-
265
- // Calculate width based on content
297
+ // Calculate content width first (before wrapping)
266
298
  const titleWidth = getDisplayWidth(title);
267
- const maxBodyWidth = Math.max(...bodyLines.map((line) => getDisplayWidth(line)));
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;
299
+ const bodyLines = body.split('\n');
300
+ const maxBodyWidth = Math.max(0, ...bodyLines.map((line) => getDisplayWidth(line)));
301
+ const requiredContentWidth = Math.max(titleWidth, maxBodyWidth);
302
+
303
+ // Box width = content + borders (2) + side spaces (2)
304
+ const boxWidth = Math.min(requiredContentWidth + 4, termWidth);
305
+
306
+ // If required content width exceeds terminal width, skip box and print plain text
307
+ if (requiredContentWidth + 4 > termWidth) {
308
+ console.log('\n' + bold(title));
309
+ console.log(body + '\n');
310
+ return;
311
+ }
312
+
313
+ // Inner width is box width minus borders (2) and side spaces (2)
314
+ const innerWidth = boxWidth - 4;
315
+
316
+ // Wrap text to fit box width
317
+ const wrappedBodyLines = wrapText(body, innerWidth);
271
318
 
272
319
  // Colors
273
320
  const borderColor = getColor('muted');
@@ -293,10 +340,7 @@ export function banner(title: string, body: string, options?: BannerOptions): vo
293
340
  const titleDisplayWidth = getDisplayWidth(title);
294
341
  if (options?.centerTitle === true || options?.centerTitle === undefined) {
295
342
  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
- );
343
+ const titleRightPadding = Math.max(0, innerWidth - titlePadding - titleDisplayWidth);
300
344
  const titleLine =
301
345
  ' '.repeat(titlePadding) +
302
346
  `${titleColor}${bold(title)}${reset}` +
@@ -305,7 +349,7 @@ export function banner(title: string, body: string, options?: BannerOptions): vo
305
349
  `${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`
306
350
  );
307
351
  } else {
308
- const titleRightPadding = Math.max(0, Math.max(0, innerWidth - titleDisplayWidth) - padding);
352
+ const titleRightPadding = Math.max(0, innerWidth - titleDisplayWidth);
309
353
  const titleLine = `${titleColor}${bold(title)}${reset}` + ' '.repeat(titleRightPadding);
310
354
  lines.push(
311
355
  `${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`
@@ -320,9 +364,9 @@ export function banner(title: string, body: string, options?: BannerOptions): vo
320
364
  }
321
365
 
322
366
  // Body lines
323
- for (const line of bodyLines) {
367
+ for (const line of wrappedBodyLines) {
324
368
  const lineWidth = getDisplayWidth(line);
325
- const linePadding = Math.max(0, Math.max(0, innerWidth - lineWidth) - padding);
369
+ const linePadding = Math.max(0, innerWidth - lineWidth);
326
370
  lines.push(
327
371
  `${borderColor}${border.vertical} ${reset}${line}${' '.repeat(linePadding)}${borderColor} ${border.vertical}${reset}`
328
372
  );
@@ -464,7 +508,7 @@ export function showSignupBenefits(): void {
464
508
  ];
465
509
 
466
510
  console.log('');
467
- lines.forEach((line) => console.log(CYAN + line + RESET));
511
+ lines.map((line) => console.log(CYAN + line + RESET));
468
512
  console.log('');
469
513
  }
470
514
 
@@ -472,24 +516,32 @@ export function showSignupBenefits(): void {
472
516
  * Display a message when unauthenticated to let the user know certain capabilities are disabled
473
517
  */
474
518
  export function showLoggedOutMessage(): void {
475
- const CYAN = Bun.color('yellow', 'ansi-16m');
519
+ const YELLOW = Bun.color('yellow', 'ansi-16m');
476
520
  const TEXT =
477
521
  currentColorScheme === 'dark' ? Bun.color('white', 'ansi') : Bun.color('black', 'ansi');
478
522
  const RESET = '\x1b[0m';
479
523
 
524
+ const signupTitle = 'Sign up / Login';
525
+ const showInline = supportsHyperlinks();
526
+ const signupURL = 'https://app.agentuity.com/sign-up';
527
+ const signupLink = showInline
528
+ ? link(signupURL, signupTitle)
529
+ : ' '.repeat(stringWidth(signupTitle));
530
+ const showNewLine = showInline ? '' : `║ ${RESET}${link(signupURL)}${YELLOW} ║`;
531
+
480
532
  const lines = [
481
533
  '╔══════════════════════════════════════════════╗',
482
534
  `║ ⨺ Unauthenticated (local mode) ║`,
483
535
  '║ ║',
484
- `║ ${TEXT}Certain capabilities such as the AI services${CYAN} ║`,
485
- `║ ${TEXT}and devmode remote are unavailable when${CYAN} ║`,
486
- `║ ${TEXT}unauthenticated.${CYAN} ║`,
536
+ `║ ${TEXT}Certain capabilities such as the AI services${YELLOW} ║`,
537
+ `║ ${TEXT}and devmode remote are unavailable when${YELLOW} ║`,
538
+ `║ ${TEXT}unauthenticated.${YELLOW} ${signupLink}${YELLOW} ║`,
539
+ showNewLine,
487
540
  '╚══════════════════════════════════════════════╝',
488
541
  ];
489
542
 
490
543
  console.log('');
491
- lines.forEach((line) => console.log(CYAN + line + RESET));
492
- console.log('');
544
+ lines.filter(Boolean).map((line) => console.log(YELLOW + line + RESET));
493
545
  }
494
546
 
495
547
  /**
@@ -544,20 +596,6 @@ export async function copyToClipboard(text: string): Promise<boolean> {
544
596
  }
545
597
  }
546
598
 
547
- /**
548
- * Get the display width of a string, handling ANSI codes and OSC 8 hyperlinks
549
- *
550
- * Note: Bun.stringWidth() counts OSC 8 hyperlink escape sequences in the width,
551
- * which causes incorrect alignment. We strip OSC 8 codes first, then use Bun.stringWidth()
552
- * to handle regular ANSI codes and unicode characters correctly.
553
- */
554
- function getDisplayWidth(str: string): number {
555
- // Strip OSC 8 hyperlink sequences: \x1b]8;;URL\x07...\x1b]8;;\x07
556
- // eslint-disable-next-line no-control-regex
557
- const withoutOSC8 = str.replace(/\x1b\]8;;[^\x07]*\x07/g, '');
558
- return Bun.stringWidth(withoutOSC8);
559
- }
560
-
561
599
  /**
562
600
  * Extract ANSI codes from the beginning of a string
563
601
  */
@@ -667,6 +705,11 @@ export interface SimpleSpinnerOptions<T> {
667
705
  type?: 'simple';
668
706
  message: string;
669
707
  callback: (() => Promise<T>) | Promise<T>;
708
+ /**
709
+ * If true, clear the spinner output on success (no icon, no message)
710
+ * Defaults to false
711
+ */
712
+ clearOnSuccess?: boolean;
670
713
  }
671
714
 
672
715
  /**
@@ -676,6 +719,11 @@ export interface ProgressSpinnerOptions<T> {
676
719
  type: 'progress';
677
720
  message: string;
678
721
  callback: (progress: SpinnerProgressCallback) => Promise<T>;
722
+ /**
723
+ * If true, clear the spinner output on success (no icon, no message)
724
+ * Defaults to false
725
+ */
726
+ clearOnSuccess?: boolean;
679
727
  }
680
728
 
681
729
  /**
@@ -726,7 +774,7 @@ export async function spinner<T>(
726
774
  const reset = getColor('reset');
727
775
 
728
776
  // If no TTY, just execute the callback without animation
729
- if (!process.stdout.isTTY) {
777
+ if (!process.stderr.isTTY) {
730
778
  try {
731
779
  const result =
732
780
  options.type === 'progress'
@@ -735,6 +783,12 @@ export async function spinner<T>(
735
783
  ? await options.callback()
736
784
  : await options.callback;
737
785
 
786
+ // If clearOnSuccess is true, don't show success message
787
+ if (!options.clearOnSuccess) {
788
+ const successColor = getColor('success');
789
+ console.error(`${successColor}${ICONS.success} ${message}${reset}`);
790
+ }
791
+
738
792
  return result;
739
793
  } catch (err) {
740
794
  const errorColor = getColor('error');
@@ -757,7 +811,7 @@ export async function spinner<T>(
757
811
  let currentProgress: number | undefined;
758
812
 
759
813
  // Hide cursor
760
- process.stdout.write('\x1B[?25l');
814
+ process.stderr.write('\x1B[?25l');
761
815
 
762
816
  // Start animation
763
817
  const interval = setInterval(() => {
@@ -772,7 +826,7 @@ export async function spinner<T>(
772
826
  : '';
773
827
 
774
828
  // Clear line and render
775
- process.stdout.write('\r\x1B[K' + `${frame} ${message}${progressIndicator}`);
829
+ process.stderr.write('\r\x1B[K' + `${frame} ${message}${progressIndicator}`);
776
830
  frameIndex++;
777
831
  }, 120);
778
832
 
@@ -792,27 +846,30 @@ export async function spinner<T>(
792
846
 
793
847
  // Clear interval and line
794
848
  clearInterval(interval);
795
- process.stdout.write('\r\x1B[K');
849
+ process.stderr.write('\r\x1B[K');
796
850
 
797
- // Show success
798
- const successColor = getColor('success');
799
- console.log(`${successColor}${ICONS.success} ${message}${reset}`);
851
+ // If clearOnSuccess is false, show success message
852
+ if (!options.clearOnSuccess) {
853
+ // Show success
854
+ const successColor = getColor('success');
855
+ console.error(`${successColor}${ICONS.success} ${message}${reset}`);
856
+ }
800
857
 
801
858
  // Show cursor
802
- process.stdout.write('\x1B[?25h');
859
+ process.stderr.write('\x1B[?25h');
803
860
 
804
861
  return result;
805
862
  } catch (err) {
806
863
  // Clear interval and line
807
864
  clearInterval(interval);
808
- process.stdout.write('\r\x1B[K');
865
+ process.stderr.write('\r\x1B[K');
809
866
 
810
867
  // Show error
811
868
  const errorColor = getColor('error');
812
869
  console.error(`${errorColor}${ICONS.error} ${message}${reset}`);
813
870
 
814
871
  // Show cursor
815
- process.stdout.write('\x1B[?25h');
872
+ process.stderr.write('\x1B[?25h');
816
873
 
817
874
  throw err;
818
875
  }
@@ -847,12 +904,10 @@ export interface CommandRunnerOptions {
847
904
  * If true or undefined, will truncate each line of output
848
905
  */
849
906
  truncate?: boolean;
850
-
851
907
  /**
852
908
  * If undefined, will show up to 3 last lines of output while running. Customize the number with this property.
853
909
  */
854
910
  maxLinesOutput?: number;
855
-
856
911
  /**
857
912
  * If undefined, will show up to 10 last lines on failure. Customize the number with this property.
858
913
  */
@@ -1076,6 +1131,33 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
1076
1131
  }
1077
1132
  }
1078
1133
 
1134
+ /**
1135
+ * Prompt user for text input
1136
+ * Returns the input string
1137
+ */
1138
+ export async function prompt(message: string): Promise<string> {
1139
+ process.stdout.write(message);
1140
+
1141
+ // Check if we're in a TTY environment
1142
+ if (!process.stdin.isTTY) {
1143
+ console.log('');
1144
+ return '';
1145
+ }
1146
+
1147
+ // Use readline for full line input
1148
+ const rl = readline.createInterface({
1149
+ input: process.stdin,
1150
+ output: process.stdout,
1151
+ });
1152
+
1153
+ return new Promise((resolve) => {
1154
+ rl.question('', (answer: string) => {
1155
+ rl.close();
1156
+ resolve(answer);
1157
+ });
1158
+ });
1159
+ }
1160
+
1079
1161
  export async function selectOrganization(
1080
1162
  orgs: OrganizationList,
1081
1163
  initial?: string
@@ -1087,17 +1169,75 @@ export async function selectOrganization(
1087
1169
  );
1088
1170
  }
1089
1171
 
1090
- if (orgs.length === 1) {
1091
- return orgs[0].id;
1172
+ if (process.env.AGENTUITY_CLOUD_ORG_ID) {
1173
+ const org = orgs.find((o) => o.id === process.env.AGENTUITY_CLOUD_ORG_ID);
1174
+ if (org) {
1175
+ return org.id;
1176
+ }
1177
+ }
1178
+
1179
+ if (!process.stdin.isTTY) {
1180
+ if (orgs.length === 1) {
1181
+ return orgs[0].id;
1182
+ }
1183
+ if (initial) {
1184
+ return initial;
1185
+ }
1186
+ fatal(
1187
+ 'Organization selection required but cannot prompt in non-interactive environment. Set AGENTUITY_CLOUD_ORG_ID or provide a default organization using --org-id'
1188
+ );
1092
1189
  }
1093
1190
 
1094
1191
  const response = await enquirer.prompt<{ action: string }>({
1095
1192
  type: 'select',
1096
1193
  name: 'action',
1097
1194
  message: 'Select an organization',
1098
- initial,
1195
+ initial: initial || (orgs.length === 1 ? orgs[0].id : undefined),
1099
1196
  choices: orgs.map((o) => ({ message: o.name, name: o.id })),
1100
1197
  });
1101
1198
 
1102
1199
  return response.action;
1103
1200
  }
1201
+
1202
+ /**
1203
+ * show a project list picker
1204
+ *
1205
+ * @param apiClient
1206
+ * @param showDeployment
1207
+ * @returns
1208
+ */
1209
+ export async function showProjectList(
1210
+ apiClient: APIClientType,
1211
+ showDeploymentId = false
1212
+ ): Promise<string> {
1213
+ const projects = await spinner({
1214
+ message: 'Fetching projects',
1215
+ clearOnSuccess: true,
1216
+ callback: () => {
1217
+ return projectList(apiClient, showDeploymentId);
1218
+ },
1219
+ });
1220
+
1221
+ if (projects.length === 0) {
1222
+ return '';
1223
+ }
1224
+
1225
+ // TODO: might want to sort by the last org_id we used
1226
+ if (projects) {
1227
+ projects.sort((a, b) => {
1228
+ return a.name.localeCompare(b.name);
1229
+ });
1230
+ }
1231
+
1232
+ const response = await enquirer.prompt<{ id: string }>({
1233
+ type: 'select',
1234
+ name: 'id',
1235
+ message: 'Select a project:',
1236
+ choices: projects.map((p) => ({
1237
+ name: p.id,
1238
+ message: `${p.name.padEnd(25, ' ')} ${muted(p.id)} ${showDeploymentId ? muted(p.latestDeploymentId ?? 'no deployment') : ''}`,
1239
+ })),
1240
+ });
1241
+
1242
+ return response.id;
1243
+ }