@agentuity/cli 0.1.13 → 0.1.15

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 (173) hide show
  1. package/dist/auth.d.ts +1 -1
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +12 -7
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +94 -97
  7. package/dist/cli.js.map +1 -1
  8. package/dist/cmd/ai/index.d.ts.map +1 -1
  9. package/dist/cmd/ai/index.js +6 -1
  10. package/dist/cmd/ai/index.js.map +1 -1
  11. package/dist/cmd/ai/opencode/index.d.ts +3 -0
  12. package/dist/cmd/ai/opencode/index.d.ts.map +1 -0
  13. package/dist/cmd/ai/opencode/index.js +27 -0
  14. package/dist/cmd/ai/opencode/index.js.map +1 -0
  15. package/dist/cmd/ai/opencode/install.d.ts +3 -0
  16. package/dist/cmd/ai/opencode/install.d.ts.map +1 -0
  17. package/dist/cmd/ai/opencode/install.js +102 -0
  18. package/dist/cmd/ai/opencode/install.js.map +1 -0
  19. package/dist/cmd/ai/opencode/run.d.ts +3 -0
  20. package/dist/cmd/ai/opencode/run.d.ts.map +1 -0
  21. package/dist/cmd/ai/opencode/run.js +88 -0
  22. package/dist/cmd/ai/opencode/run.js.map +1 -0
  23. package/dist/cmd/ai/opencode/uninstall.d.ts +3 -0
  24. package/dist/cmd/ai/opencode/uninstall.d.ts.map +1 -0
  25. package/dist/cmd/ai/opencode/uninstall.js +82 -0
  26. package/dist/cmd/ai/opencode/uninstall.js.map +1 -0
  27. package/dist/cmd/auth/index.d.ts.map +1 -1
  28. package/dist/cmd/auth/index.js +3 -0
  29. package/dist/cmd/auth/index.js.map +1 -1
  30. package/dist/cmd/auth/org/index.d.ts +2 -0
  31. package/dist/cmd/auth/org/index.d.ts.map +1 -0
  32. package/dist/cmd/auth/org/index.js +121 -0
  33. package/dist/cmd/auth/org/index.js.map +1 -0
  34. package/dist/cmd/build/vite/beacon-plugin.d.ts +19 -0
  35. package/dist/cmd/build/vite/beacon-plugin.d.ts.map +1 -0
  36. package/dist/cmd/build/vite/beacon-plugin.js +137 -0
  37. package/dist/cmd/build/vite/beacon-plugin.js.map +1 -0
  38. package/dist/cmd/build/vite/vite-builder.d.ts +2 -0
  39. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  40. package/dist/cmd/build/vite/vite-builder.js +12 -2
  41. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  42. package/dist/cmd/build/webanalytics-generator.js +25 -9
  43. package/dist/cmd/build/webanalytics-generator.js.map +1 -1
  44. package/dist/cmd/cloud/db/get.d.ts.map +1 -1
  45. package/dist/cmd/cloud/db/get.js +7 -0
  46. package/dist/cmd/cloud/db/get.js.map +1 -1
  47. package/dist/cmd/cloud/db/list.d.ts.map +1 -1
  48. package/dist/cmd/cloud/db/list.js +19 -6
  49. package/dist/cmd/cloud/db/list.js.map +1 -1
  50. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  51. package/dist/cmd/cloud/deploy.js +24 -1
  52. package/dist/cmd/cloud/deploy.js.map +1 -1
  53. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  54. package/dist/cmd/cloud/deployment/show.js +5 -0
  55. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  56. package/dist/cmd/cloud/index.d.ts.map +1 -1
  57. package/dist/cmd/cloud/index.js +3 -0
  58. package/dist/cmd/cloud/index.js.map +1 -1
  59. package/dist/cmd/cloud/region/index.d.ts +2 -0
  60. package/dist/cmd/cloud/region/index.d.ts.map +1 -0
  61. package/dist/cmd/cloud/region/index.js +136 -0
  62. package/dist/cmd/cloud/region/index.js.map +1 -0
  63. package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
  64. package/dist/cmd/cloud/sandbox/snapshot/build.js +35 -5
  65. package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
  66. package/dist/cmd/cloud/scp/download.d.ts.map +1 -1
  67. package/dist/cmd/cloud/scp/download.js +4 -2
  68. package/dist/cmd/cloud/scp/download.js.map +1 -1
  69. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -1
  70. package/dist/cmd/cloud/scp/upload.js +4 -2
  71. package/dist/cmd/cloud/scp/upload.js.map +1 -1
  72. package/dist/cmd/cloud/ssh.d.ts.map +1 -1
  73. package/dist/cmd/cloud/ssh.js +3 -1
  74. package/dist/cmd/cloud/ssh.js.map +1 -1
  75. package/dist/cmd/cloud/storage/get.d.ts.map +1 -1
  76. package/dist/cmd/cloud/storage/get.js +12 -5
  77. package/dist/cmd/cloud/storage/get.js.map +1 -1
  78. package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
  79. package/dist/cmd/cloud/storage/list.js +10 -0
  80. package/dist/cmd/cloud/storage/list.js.map +1 -1
  81. package/dist/cmd/dev/index.d.ts.map +1 -1
  82. package/dist/cmd/dev/index.js +62 -5
  83. package/dist/cmd/dev/index.js.map +1 -1
  84. package/dist/cmd/help/index.d.ts.map +1 -1
  85. package/dist/cmd/help/index.js +8 -18
  86. package/dist/cmd/help/index.js.map +1 -1
  87. package/dist/cmd/project/create.d.ts.map +1 -1
  88. package/dist/cmd/project/create.js +18 -9
  89. package/dist/cmd/project/create.js.map +1 -1
  90. package/dist/cmd/project/download.d.ts +3 -1
  91. package/dist/cmd/project/download.d.ts.map +1 -1
  92. package/dist/cmd/project/download.js +5 -0
  93. package/dist/cmd/project/download.js.map +1 -1
  94. package/dist/cmd/project/import.d.ts +2 -0
  95. package/dist/cmd/project/import.d.ts.map +1 -0
  96. package/dist/cmd/project/import.js +88 -0
  97. package/dist/cmd/project/import.js.map +1 -0
  98. package/dist/cmd/project/index.d.ts.map +1 -1
  99. package/dist/cmd/project/index.js +3 -0
  100. package/dist/cmd/project/index.js.map +1 -1
  101. package/dist/cmd/project/reconcile.d.ts +67 -0
  102. package/dist/cmd/project/reconcile.d.ts.map +1 -0
  103. package/dist/cmd/project/reconcile.js +458 -0
  104. package/dist/cmd/project/reconcile.js.map +1 -0
  105. package/dist/cmd/project/template-flow.d.ts +13 -1
  106. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  107. package/dist/cmd/project/template-flow.js +56 -18
  108. package/dist/cmd/project/template-flow.js.map +1 -1
  109. package/dist/config.d.ts +8 -3
  110. package/dist/config.d.ts.map +1 -1
  111. package/dist/config.js +50 -21
  112. package/dist/config.js.map +1 -1
  113. package/dist/legacy-check.d.ts.map +1 -1
  114. package/dist/legacy-check.js +8 -0
  115. package/dist/legacy-check.js.map +1 -1
  116. package/dist/program-ref.d.ts +4 -0
  117. package/dist/program-ref.d.ts.map +1 -0
  118. package/dist/program-ref.js +8 -0
  119. package/dist/program-ref.js.map +1 -0
  120. package/dist/regions.d.ts +8 -0
  121. package/dist/regions.d.ts.map +1 -0
  122. package/dist/regions.js +77 -0
  123. package/dist/regions.js.map +1 -0
  124. package/dist/tui/prompt.d.ts.map +1 -1
  125. package/dist/tui/prompt.js +7 -1
  126. package/dist/tui/prompt.js.map +1 -1
  127. package/dist/tui.d.ts.map +1 -1
  128. package/dist/tui.js +79 -25
  129. package/dist/tui.js.map +1 -1
  130. package/dist/types.d.ts +1 -0
  131. package/dist/types.d.ts.map +1 -1
  132. package/dist/types.js +1 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +7 -7
  135. package/src/auth.ts +14 -13
  136. package/src/cli.ts +118 -114
  137. package/src/cmd/ai/index.ts +6 -1
  138. package/src/cmd/ai/opencode/index.ts +28 -0
  139. package/src/cmd/ai/opencode/install.ts +120 -0
  140. package/src/cmd/ai/opencode/run.ts +103 -0
  141. package/src/cmd/ai/opencode/uninstall.ts +90 -0
  142. package/src/cmd/auth/index.ts +3 -0
  143. package/src/cmd/auth/org/index.ts +142 -0
  144. package/src/cmd/build/vite/beacon-plugin.ts +164 -0
  145. package/src/cmd/build/vite/vite-builder.ts +19 -2
  146. package/src/cmd/build/webanalytics-generator.ts +25 -9
  147. package/src/cmd/cloud/db/get.ts +7 -0
  148. package/src/cmd/cloud/db/list.ts +20 -6
  149. package/src/cmd/cloud/deploy.ts +32 -1
  150. package/src/cmd/cloud/deployment/show.ts +5 -0
  151. package/src/cmd/cloud/index.ts +3 -0
  152. package/src/cmd/cloud/region/index.ts +157 -0
  153. package/src/cmd/cloud/sandbox/snapshot/build.ts +42 -5
  154. package/src/cmd/cloud/scp/download.ts +6 -2
  155. package/src/cmd/cloud/scp/upload.ts +6 -2
  156. package/src/cmd/cloud/ssh.ts +5 -1
  157. package/src/cmd/cloud/storage/get.ts +12 -5
  158. package/src/cmd/cloud/storage/list.ts +11 -0
  159. package/src/cmd/dev/index.ts +62 -5
  160. package/src/cmd/help/index.ts +8 -22
  161. package/src/cmd/project/create.ts +19 -9
  162. package/src/cmd/project/download.ts +7 -1
  163. package/src/cmd/project/import.ts +98 -0
  164. package/src/cmd/project/index.ts +3 -0
  165. package/src/cmd/project/reconcile.ts +606 -0
  166. package/src/cmd/project/template-flow.ts +69 -18
  167. package/src/config.ts +58 -22
  168. package/src/legacy-check.ts +10 -0
  169. package/src/program-ref.ts +11 -0
  170. package/src/regions.ts +95 -0
  171. package/src/tui/prompt.ts +10 -3
  172. package/src/tui.ts +82 -26
  173. package/src/types.ts +2 -0
@@ -10,6 +10,7 @@ import {
10
10
  getServiceUrls,
11
11
  APIClient as ServerAPIClient,
12
12
  createResources,
13
+ validateDatabaseName,
13
14
  } from '@agentuity/server';
14
15
  import type { Logger } from '@agentuity/core';
15
16
  import * as tui from '../../tui';
@@ -56,7 +57,20 @@ interface CreateFlowOptions {
56
57
  apiClient?: APIClient;
57
58
  }
58
59
 
59
- export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
60
+ export interface CreateFlowResult {
61
+ projectId?: string;
62
+ orgId?: string;
63
+ name: string;
64
+ path: string;
65
+ template: string;
66
+ installed: boolean;
67
+ built: boolean;
68
+ domains?: string[];
69
+ success: boolean;
70
+ error?: string;
71
+ }
72
+
73
+ export async function runCreateFlow(options: CreateFlowOptions): Promise<CreateFlowResult> {
60
74
  const {
61
75
  projectName: initialProjectName,
62
76
  dir: targetDir,
@@ -168,7 +182,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
168
182
  const home = homedir();
169
183
  if (dest === '/' || dest === home) {
170
184
  logger.fatal(`Refusing to delete protected path: ${dest}`, ErrorCode.VALIDATION_FAILED);
171
- return;
185
+ return undefined as never;
172
186
  }
173
187
  rmSync(dest, { recursive: true, force: true });
174
188
  tui.success(`Deleted ${dest}`);
@@ -193,7 +207,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
193
207
  `Template "${initialTemplate}" not found\n\nAvailable templates:\n${availableTemplates}`,
194
208
  ErrorCode.RESOURCE_NOT_FOUND
195
209
  );
196
- return;
210
+ return undefined as never;
197
211
  }
198
212
  selectedTemplate = found;
199
213
  } else if (skipPrompts || templates.length === 1) {
@@ -222,7 +236,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
222
236
  const found = templates.find((t) => t.id === templateId);
223
237
  if (!found) {
224
238
  logger.fatal('Template selection failed', ErrorCode.USER_CANCELLED);
225
- return;
239
+ return undefined as never;
226
240
  }
227
241
  selectedTemplate = found;
228
242
  }
@@ -237,7 +251,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
237
251
  });
238
252
 
239
253
  // Setup project (replace placeholders, install deps, build)
240
- await setupProject({
254
+ const setupResult = await setupProject({
241
255
  dest,
242
256
  projectName: projectName === '.' ? basename(dest) : projectName,
243
257
  dirName: dirName === '.' ? basename(dest) : dirName,
@@ -246,15 +260,24 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
246
260
  logger,
247
261
  });
248
262
 
249
- // Re-display template selection after spinners clear it (only if user actually selected)
250
- if (!skipPrompts && templates.length > 1) {
263
+ // If setup failed, skip resource prompts and registration - just show error and return
264
+ if (!setupResult.success) {
265
+ tui.warning('Project setup failed. Skipping resource configuration.');
266
+ return {
267
+ name: projectName,
268
+ path: dest,
269
+ template: selectedTemplate.id,
270
+ installed: !options.noInstall,
271
+ built: false,
272
+ success: false,
273
+ error: 'Project setup completed with errors',
274
+ };
275
+ }
276
+
277
+ // Add separator bar if we're going to show resource prompts
278
+ if (!skipPrompts && auth && apiClient && catalystClient && orgId && region) {
251
279
  const { symbols, tuiColors } = tui;
252
- console.log(`${tuiColors.completed(symbols.completed)} Select a template:`);
253
- console.log(`${tuiColors.secondary(symbols.bar)} ${tuiColors.muted(selectedTemplate.name)}`);
254
- // Only add bar if we're going to show resource prompts
255
- if (auth && apiClient && catalystClient && orgId && region) {
256
- console.log(tuiColors.secondary(symbols.bar));
257
- }
280
+ console.log(tuiColors.secondary(symbols.bar));
258
281
  }
259
282
 
260
283
  let _domains = domains;
@@ -342,10 +365,17 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
342
365
  }
343
366
  switch (choices.db_action) {
344
367
  case 'Create New': {
345
- const dbName = await prompt.text({
368
+ const dbNameInput = await prompt.text({
346
369
  message: 'Database name',
347
- hint: 'Optional - press Enter to auto-generate',
370
+ hint: 'Optional - lowercase letters, digits, underscores only',
371
+ validate: (value: string) => {
372
+ const trimmed = value.trim();
373
+ if (trimmed === '') return true;
374
+ const result = validateDatabaseName(trimmed);
375
+ return result.valid ? true : result.error!;
376
+ },
348
377
  });
378
+ const dbName = dbNameInput.trim() || undefined;
349
379
  const dbDescription = await prompt.text({
350
380
  message: 'Database description',
351
381
  hint: 'Optional - press Enter to skip',
@@ -357,7 +387,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
357
387
  return createResources(catalystClient, orgId, region!, [
358
388
  {
359
389
  type: 'db',
360
- name: dbName || undefined,
390
+ name: dbName,
361
391
  description: dbDescription || undefined,
362
392
  },
363
393
  ]);
@@ -578,7 +608,11 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
578
608
 
579
609
  // Show completion message
580
610
  if (!skipPrompts) {
581
- tui.success('✨ Project created successfully!\n');
611
+ if (setupResult.success) {
612
+ tui.success('✨ Project created successfully!\n');
613
+ } else {
614
+ tui.warning('Project created with errors (see above)\n');
615
+ }
582
616
 
583
617
  // Show next steps in a box with primary color for commands
584
618
  if (dirName !== '.') {
@@ -598,7 +632,11 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
598
632
  `${tui.tuiColors.muted('⭐️ Follow us:')} ${tui.link('https://github.com/agentuity/sdk')}`
599
633
  );
600
634
  } else {
601
- tui.success('✨ Project created successfully!');
635
+ if (setupResult.success) {
636
+ tui.success('✨ Project created successfully!');
637
+ } else {
638
+ tui.warning('Project created with errors');
639
+ }
602
640
  }
603
641
 
604
642
  playSound();
@@ -616,6 +654,19 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
616
654
  if (authEnabled && !templateHasAuth) {
617
655
  printIntegrationExamples();
618
656
  }
657
+
658
+ return {
659
+ projectId,
660
+ orgId,
661
+ name: projectName,
662
+ path: dest,
663
+ template: selectedTemplate.id,
664
+ installed: !options.noInstall,
665
+ built: !options.noBuild && setupResult.success,
666
+ domains: _domains,
667
+ success: setupResult.success,
668
+ error: setupResult.success ? undefined : 'Project setup completed with errors',
669
+ };
619
670
  }
620
671
 
621
672
  /**
package/src/config.ts CHANGED
@@ -359,6 +359,29 @@ export async function saveOrgId(orgId: string): Promise<void> {
359
359
  await saveConfig(config);
360
360
  }
361
361
 
362
+ export async function clearOrgId(): Promise<void> {
363
+ const config = await getOrInitConfig();
364
+ if (config.preferences) {
365
+ delete (config.preferences as Record<string, unknown>).orgId;
366
+ await saveConfig(config);
367
+ }
368
+ }
369
+
370
+ export async function saveRegion(region: string): Promise<void> {
371
+ const config = await getOrInitConfig();
372
+ config.preferences = config.preferences || {};
373
+ (config.preferences as Record<string, unknown>).region = region;
374
+ await saveConfig(config);
375
+ }
376
+
377
+ export async function clearRegion(): Promise<void> {
378
+ const config = await getOrInitConfig();
379
+ if (config.preferences) {
380
+ delete (config.preferences as Record<string, unknown>).region;
381
+ await saveConfig(config);
382
+ }
383
+ }
384
+
362
385
  export async function getAuth(): Promise<AuthData | null> {
363
386
  const config = await loadConfig();
364
387
  const profileName = config?.name || defaultProfileName;
@@ -590,26 +613,29 @@ export async function createProjectConfig(dir: string, config: InitialProjectCon
590
613
  await Bun.write(envPath, content);
591
614
  await chmod(envPath, 0o600);
592
615
 
593
- // generate the vscode settings
616
+ // generate the vscode settings (only if they don't already exist)
594
617
  const vscodeDir = join(dir, '.vscode');
595
- mkdirSync(vscodeDir);
596
-
597
- const settings = {
598
- 'search.exclude': {
599
- '**/.git/**': true,
600
- '**/node_modules/**': true,
601
- '**/bun.lock': true,
602
- '**/.agentuity/**': true,
603
- },
604
- 'json.schemas': [
605
- {
606
- fileMatch: ['agentuity.json'],
607
- url: 'https://agentuity.dev/schema/cli/v1/agentuity.json',
618
+ const settingsPath = join(vscodeDir, 'settings.json');
619
+ if (!(await Bun.file(settingsPath).exists())) {
620
+ mkdirSync(vscodeDir, { recursive: true });
621
+
622
+ const settings = {
623
+ 'search.exclude': {
624
+ '**/.git/**': true,
625
+ '**/node_modules/**': true,
626
+ '**/bun.lock': true,
627
+ '**/.agentuity/**': true,
608
628
  },
609
- ],
610
- };
629
+ 'json.schemas': [
630
+ {
631
+ fileMatch: ['agentuity.json'],
632
+ url: 'https://agentuity.dev/schema/cli/v1/agentuity.json',
633
+ },
634
+ ],
635
+ };
611
636
 
612
- await Bun.write(join(vscodeDir, 'settings.json'), JSON.stringify(settings, null, 2));
637
+ await Bun.write(settingsPath, JSON.stringify(settings, null, 2));
638
+ }
613
639
  }
614
640
 
615
641
  export async function updateProjectConfig(
@@ -721,13 +747,18 @@ interface RegionsCacheData {
721
747
  /**
722
748
  * Get the default region using priority ordering:
723
749
  * 1. AGENTUITY_REGION environment variable
724
- * 2. First entry in region-{profile}.json (nearest region, sorted by distance)
725
- * 3. 'local' for local profile, 'usc' otherwise
750
+ * 2. 'local' for local profile
751
+ * 3. Saved region preference (config.preferences.region)
752
+ * 4. First entry in region-{profile}.json (nearest region, sorted by distance)
753
+ * 5. 'usc' fallback
726
754
  *
727
755
  * Used for API calls that can hit any Catalyst instance (global database operations).
728
756
  * Note: This is NOT called when --region flag is provided (handled at command level).
729
757
  */
730
- export async function getDefaultRegion(profileName = 'production'): Promise<string> {
758
+ export async function getDefaultRegion(
759
+ profileName = 'production',
760
+ config?: Config | null
761
+ ): Promise<string> {
731
762
  // 1. Check environment variable first
732
763
  if (process.env.AGENTUITY_REGION) {
733
764
  return process.env.AGENTUITY_REGION;
@@ -738,7 +769,12 @@ export async function getDefaultRegion(profileName = 'production'): Promise<stri
738
769
  return 'local';
739
770
  }
740
771
 
741
- // 3. Check cached regions file (sorted by distance)
772
+ // 3. Check saved region preference
773
+ if (config?.preferences?.region) {
774
+ return config.preferences.region;
775
+ }
776
+
777
+ // 4. Check cached regions file (sorted by distance)
742
778
  try {
743
779
  const cachePath = join(getDefaultConfigDir(), `regions-${profileName}.json`);
744
780
  const file = Bun.file(cachePath);
@@ -752,7 +788,7 @@ export async function getDefaultRegion(profileName = 'production'): Promise<stri
752
788
  // Fall through to default
753
789
  }
754
790
 
755
- // 4. Final fallback
791
+ // 5. Final fallback
756
792
  return 'usc';
757
793
  }
758
794
 
@@ -23,6 +23,11 @@ export async function checkLegacyCLI(): Promise<void> {
23
23
  join(homeDir, '.local/bin/agentuity'), // XDG user bin
24
24
  ];
25
25
 
26
+ // Exclude the currently running executable from the legacy check
27
+ // This prevents the new CLI from detecting itself as legacy when installed
28
+ // in standard system locations like /usr/local/bin
29
+ const currentExecutable = process.execPath;
30
+
26
31
  const foundInstalls: LegacyInstall[] = [];
27
32
 
28
33
  // Check if Homebrew manages the agentuity package
@@ -40,6 +45,11 @@ export async function checkLegacyCLI(): Promise<void> {
40
45
 
41
46
  // Check file system locations
42
47
  for (const location of legacyLocations) {
48
+ // Skip if this is the currently running executable
49
+ if (location === currentExecutable) {
50
+ continue;
51
+ }
52
+
43
53
  const file = Bun.file(location);
44
54
  if (await file.exists()) {
45
55
  try {
@@ -0,0 +1,11 @@
1
+ import type { Command } from 'commander';
2
+
3
+ let programRef: Command | null = null;
4
+
5
+ export function setProgram(program: Command): void {
6
+ programRef = program;
7
+ }
8
+
9
+ export function getProgram(): Command | null {
10
+ return programRef;
11
+ }
package/src/regions.ts ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Region caching utilities for avoiding repeated API calls
3
+ */
4
+ import { join } from 'node:path';
5
+ import { mkdir, unlink } from 'node:fs/promises';
6
+ import type { Logger } from '@agentuity/core';
7
+ import { listRegions, type RegionList } from '@agentuity/server';
8
+ import { getDefaultConfigDir } from './config';
9
+ import type { APIClient } from './api';
10
+
11
+ const REGIONS_CACHE_MAX_AGE_MS = 5 * 24 * 60 * 60 * 1000; // 5 days
12
+ const LEGACY_REGIONS_CACHE_FILE = 'regions.json';
13
+
14
+ function getRegionsCacheFile(profileName: string): string {
15
+ return `regions-${profileName}.json`;
16
+ }
17
+
18
+ async function removeLegacyRegionsCache(logger: Logger): Promise<void> {
19
+ try {
20
+ const legacyPath = join(getDefaultConfigDir(), LEGACY_REGIONS_CACHE_FILE);
21
+ const file = Bun.file(legacyPath);
22
+ if (await file.exists()) {
23
+ await unlink(legacyPath);
24
+ logger.trace('removed legacy regions cache file');
25
+ }
26
+ } catch {
27
+ // Ignore errors when removing legacy file
28
+ }
29
+ }
30
+
31
+ interface RegionsCacheData {
32
+ timestamp: number;
33
+ regions: RegionList;
34
+ }
35
+
36
+ async function getCachedRegions(profileName: string, logger: Logger): Promise<RegionList | null> {
37
+ try {
38
+ // Clean up legacy single-file cache from older versions
39
+ await removeLegacyRegionsCache(logger);
40
+
41
+ const cachePath = join(getDefaultConfigDir(), getRegionsCacheFile(profileName));
42
+ const file = Bun.file(cachePath);
43
+ if (!(await file.exists())) {
44
+ return null;
45
+ }
46
+ const data: RegionsCacheData = await file.json();
47
+ const age = Date.now() - data.timestamp;
48
+ if (age > REGIONS_CACHE_MAX_AGE_MS) {
49
+ logger.trace('regions cache expired for profile %s (age: %dms)', profileName, age);
50
+ return null;
51
+ }
52
+ logger.trace('using cached regions for profile %s (age: %dms)', profileName, age);
53
+ return data.regions;
54
+ } catch (error) {
55
+ logger.trace('failed to read regions cache for profile %s: %s', profileName, error);
56
+ return null;
57
+ }
58
+ }
59
+
60
+ async function saveRegionsCache(
61
+ profileName: string,
62
+ regions: RegionList,
63
+ logger: Logger
64
+ ): Promise<void> {
65
+ try {
66
+ const cacheDir = getDefaultConfigDir();
67
+ await mkdir(cacheDir, { recursive: true });
68
+ const cachePath = join(cacheDir, getRegionsCacheFile(profileName));
69
+ const data: RegionsCacheData = {
70
+ timestamp: Date.now(),
71
+ regions,
72
+ };
73
+ await Bun.write(cachePath, JSON.stringify(data));
74
+ logger.trace('saved regions cache for profile %s', profileName);
75
+ } catch (error) {
76
+ logger.trace('failed to save regions cache for profile %s: %s', profileName, error);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Fetch regions from API with caching
82
+ */
83
+ export async function fetchRegionsWithCache(
84
+ profileName: string,
85
+ apiClient: APIClient,
86
+ logger: Logger
87
+ ): Promise<RegionList> {
88
+ const cached = await getCachedRegions(profileName, logger);
89
+ if (cached) {
90
+ return cached;
91
+ }
92
+ const regions = await listRegions(apiClient);
93
+ await saveRegionsCache(profileName, regions, logger);
94
+ return regions;
95
+ }
package/src/tui/prompt.ts CHANGED
@@ -166,9 +166,16 @@ export class PromptFlow {
166
166
  readline.moveCursor(process.stdout, 0, -linesToClear);
167
167
  readline.clearScreenDown(process.stdout);
168
168
 
169
- process.stdout.write(
170
- `${colors.completed(symbols.completed)} ${message}\n${colors.secondary(symbols.bar)} ${colors.muted(value)}\n${colors.secondary(symbols.bar)}\n`
171
- );
169
+ // If value is empty, only show message and separator (no value line)
170
+ if (value === '') {
171
+ process.stdout.write(
172
+ `${colors.completed(symbols.completed)} ${message}\n${colors.secondary(symbols.bar)}\n`
173
+ );
174
+ } else {
175
+ process.stdout.write(
176
+ `${colors.completed(symbols.completed)} ${message}\n${colors.secondary(symbols.bar)} ${colors.muted(value)}\n${colors.secondary(symbols.bar)}\n`
177
+ );
178
+ }
172
179
 
173
180
  this.states.push({
174
181
  type: 'completed',
package/src/tui.ts CHANGED
@@ -247,7 +247,8 @@ export function getSeverityColor(severity: string): (text: string) => string {
247
247
  export function success(message: string): void {
248
248
  const color = getColor('success');
249
249
  const reset = getColor('reset');
250
- process.stderr.write(`${color}${ICONS.success} ${message}${reset}\n`);
250
+ // Clear line first to ensure no leftover content from previous output
251
+ process.stderr.write(`\r\x1b[2K${color}${ICONS.success} ${message}${reset}\n`);
251
252
  }
252
253
 
253
254
  /**
@@ -348,6 +349,11 @@ export function link(url: string, title?: string, color = getColor('link')): str
348
349
  * Check if terminal supports OSC 8 hyperlinks
349
350
  */
350
351
  export function supportsHyperlinks(): boolean {
352
+ // No hyperlink support without a TTY
353
+ if (!process.stdout.isTTY) {
354
+ return false;
355
+ }
356
+
351
357
  const term = process.env.TERM || '';
352
358
  const termProgram = process.env.TERM_PROGRAM || '';
353
359
  const wtSession = process.env.WT_SESSION || '';
@@ -360,7 +366,6 @@ export function supportsHyperlinks(): boolean {
360
366
  termProgram.includes('Apple_Terminal') ||
361
367
  termProgram.includes('Hyper') ||
362
368
  term.includes('xterm-kitty') ||
363
- term.includes('xterm-256color') ||
364
369
  wtSession !== '' // Windows Terminal
365
370
  );
366
371
  }
@@ -816,9 +821,7 @@ export function showLoggedOutMessage(appBaseUrl: string, hasProfile = false): vo
816
821
  const RESET = '\x1b[0m';
817
822
 
818
823
  const signupTitle = hasProfile ? 'Login' : 'Sign up / Login';
819
- const signupURL = hasProfile
820
- ? `${appBaseUrl}/sign-in`
821
- : `${appBaseUrl}/sign-up`;
824
+ const signupURL = hasProfile ? `${appBaseUrl}/sign-in` : `${appBaseUrl}/sign-up`;
822
825
  const showInline = supportsHyperlinks();
823
826
  const signupLink = showInline
824
827
  ? link(signupURL, signupTitle)
@@ -827,7 +830,11 @@ export function showLoggedOutMessage(appBaseUrl: string, hasProfile = false): vo
827
830
  // Padding needed: 46 - 17 - signupTitle.length - 1 (space before link) = 28 - signupTitle.length
828
831
  const paddingLength = 28 - signupTitle.length;
829
832
  const padding = ' '.repeat(paddingLength);
830
- const showNewLine = showInline ? '' : `║ ${RESET}${link(signupURL)}${YELLOW} ║`;
833
+ // When not showing inline hyperlink, show URL on separate line with proper padding
834
+ // Box format: "║ " + content + "║" = 48 chars total
835
+ // Content area = 46 chars, with leading space = 45 chars for URL + padding
836
+ const urlPadding = Math.max(0, 45 - signupURL.length);
837
+ const showNewLine = showInline ? '' : `║ ${RESET}${link(signupURL)}${YELLOW}${' '.repeat(urlPadding)}║`;
831
838
 
832
839
  const lines = [
833
840
  '╔══════════════════════════════════════════════╗',
@@ -842,6 +849,8 @@ export function showLoggedOutMessage(appBaseUrl: string, hasProfile = false): vo
842
849
 
843
850
  console.log('');
844
851
  lines.filter(Boolean).map((line) => console.log(YELLOW + line + RESET));
852
+ console.log('');
853
+ console.log('');
845
854
  }
846
855
 
847
856
  /**
@@ -1301,11 +1310,17 @@ export async function spinner<T>(
1301
1310
  // Stop animation
1302
1311
  clearInterval(interval);
1303
1312
 
1304
- // Move cursor to start of output, clear all lines
1313
+ // Move cursor to start of output, clear only our lines (not to end of screen)
1305
1314
  if (linesRendered > 0) {
1306
1315
  process.stderr.write(`\x1b[${linesRendered}A`);
1316
+ for (let i = 0; i < linesRendered; i++) {
1317
+ process.stderr.write('\x1b[2K'); // Clear entire line
1318
+ if (i < linesRendered - 1) {
1319
+ process.stderr.write('\x1b[B'); // Move down one line
1320
+ }
1321
+ }
1322
+ process.stderr.write(`\x1b[${linesRendered}A\r`); // Move back up
1307
1323
  }
1308
- process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
1309
1324
  process.stderr.write('\x1B[?25h'); // Show cursor
1310
1325
 
1311
1326
  process.exit(130); // Standard exit code for SIGINT
@@ -1361,11 +1376,22 @@ export async function spinner<T>(
1361
1376
  // Stop animation first
1362
1377
  clearInterval(interval);
1363
1378
 
1364
- // Move cursor to start of output, clear all lines
1379
+ // Move cursor to start of output, clear only our lines (not to end of screen)
1365
1380
  if (linesRendered > 0) {
1366
1381
  process.stderr.write(`\x1b[${linesRendered}A`);
1382
+ for (let i = 0; i < linesRendered; i++) {
1383
+ process.stderr.write('\r\x1b[2K'); // Clear entire line
1384
+ if (i < linesRendered - 1) {
1385
+ process.stderr.write('\x1b[B'); // Move down one line
1386
+ }
1387
+ }
1388
+ // After loop, cursor is at last cleared line (linesRendered - 1 from start)
1389
+ // Move up (linesRendered - 1) to get back to start position
1390
+ if (linesRendered > 1) {
1391
+ process.stderr.write(`\x1b[${linesRendered - 1}A`);
1392
+ }
1393
+ process.stderr.write('\r');
1367
1394
  }
1368
- process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
1369
1395
  process.stderr.write('\x1B[?25h'); // Show cursor
1370
1396
 
1371
1397
  // If clearOnSuccess is false, show success message
@@ -1382,11 +1408,22 @@ export async function spinner<T>(
1382
1408
  // Stop animation first
1383
1409
  clearInterval(interval);
1384
1410
 
1385
- // Move cursor to start of output, clear all lines
1411
+ // Move cursor to start of output, clear only our lines (not to end of screen)
1386
1412
  if (linesRendered > 0) {
1387
1413
  process.stderr.write(`\x1b[${linesRendered}A`);
1414
+ for (let i = 0; i < linesRendered; i++) {
1415
+ process.stderr.write('\r\x1b[2K'); // Clear entire line
1416
+ if (i < linesRendered - 1) {
1417
+ process.stderr.write('\x1b[B'); // Move down one line
1418
+ }
1419
+ }
1420
+ // After loop, cursor is at last cleared line (linesRendered - 1 from start)
1421
+ // Move up (linesRendered - 1) to get back to start position
1422
+ if (linesRendered > 1) {
1423
+ process.stderr.write(`\x1b[${linesRendered - 1}A`);
1424
+ }
1425
+ process.stderr.write('\r');
1388
1426
  }
1389
- process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
1390
1427
  process.stderr.write('\x1B[?25h'); // Show cursor
1391
1428
 
1392
1429
  // Show error
@@ -1559,7 +1596,8 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
1559
1596
 
1560
1597
  for (const line of lines) {
1561
1598
  if (line.trim()) {
1562
- allOutputLines.push(line);
1599
+ // Strip ANSI codes from command output to prevent cursor/display issues
1600
+ allOutputLines.push(stripAnsi(line));
1563
1601
  renderOutput(maxLinesOutput); // Show last N lines while streaming
1564
1602
  }
1565
1603
  }
@@ -1580,25 +1618,44 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
1580
1618
  if (linesRendered > 0) {
1581
1619
  // Move up to the command line
1582
1620
  process.stdout.write(`\x1b[${linesRendered}A`);
1583
- // Clear each line (entire line) and move cursor back up
1621
+ // Clear each line (entire line)
1584
1622
  for (let i = 0; i < linesRendered; i++) {
1585
- process.stdout.write('\x1b[2K'); // Clear entire line
1623
+ process.stdout.write('\r\x1b[2K'); // Clear entire line
1586
1624
  if (i < linesRendered - 1) {
1587
1625
  process.stdout.write('\x1b[B'); // Move down one line
1588
1626
  }
1589
1627
  }
1590
- // Move cursor back up to original position
1591
- process.stdout.write(`\x1b[${linesRendered}A\r`);
1628
+ // After loop, cursor is at last cleared line (linesRendered - 1 from start)
1629
+ // Move up (linesRendered - 1) to get back to start position
1630
+ if (linesRendered > 1) {
1631
+ process.stdout.write(`\x1b[${linesRendered - 1}A`);
1632
+ }
1633
+ process.stdout.write('\r');
1592
1634
  }
1593
1635
  return exitCode;
1594
1636
  }
1595
1637
 
1596
- // Clear all rendered lines completely
1638
+ // Determine how many lines to show in final output
1639
+ const finalLinesToShow = exitCode === 0 ? maxLinesOutput : maxLinesOnFailure;
1640
+ const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
1641
+
1642
+ // Clear all rendered lines completely (only our lines, not previous output)
1597
1643
  if (linesRendered > 0) {
1598
1644
  // Move up to the command line (first line of our output)
1599
1645
  process.stdout.write(`\x1b[${linesRendered}A`);
1600
- // Move to beginning of line and clear from cursor to end of screen
1601
- process.stdout.write('\r\x1b[J');
1646
+ // Clear the lines we rendered during streaming
1647
+ for (let i = 0; i < linesRendered; i++) {
1648
+ process.stdout.write('\r\x1b[2K'); // Clear entire line
1649
+ if (i < linesRendered - 1) {
1650
+ process.stdout.write('\x1b[B'); // Move down one line
1651
+ }
1652
+ }
1653
+ // After loop, cursor is at last cleared line (linesRendered - 1 from start)
1654
+ // Move up (linesRendered - 1) to get back to start position
1655
+ if (linesRendered > 1) {
1656
+ process.stdout.write(`\x1b[${linesRendered - 1}A`);
1657
+ }
1658
+ process.stdout.write('\r');
1602
1659
  }
1603
1660
 
1604
1661
  // Determine icon based on exit code
@@ -1610,19 +1667,18 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
1610
1667
  `\r\x1b[K${statusColor}${icon}${reset} ${cmdColor}${displayCmd}${reset}\n`
1611
1668
  );
1612
1669
 
1613
- // Determine how many lines to show in final output
1614
- const finalLinesToShow = exitCode === 0 ? maxLinesOutput : maxLinesOnFailure;
1615
-
1616
- // Show final output lines
1617
- const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
1670
+ // Show final output lines (clearing each line first in case we're using more lines than before)
1618
1671
  for (const line of finalOutputLines) {
1619
1672
  const displayLine =
1620
1673
  truncate && getDisplayWidth(line) > maxLineWidth
1621
1674
  ? truncateToWidth(line, maxLineWidth)
1622
1675
  : line;
1623
- process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
1676
+ process.stdout.write(`\x1b[2K${mutedColor}${displayLine}${reset}\n`);
1624
1677
  }
1625
1678
 
1679
+ // If we're showing more lines than we had before, the extra lines may contain old content
1680
+ // We've already written over them, so they're clean now
1681
+
1626
1682
  return exitCode;
1627
1683
  } catch (err) {
1628
1684
  // Move cursor up to clear our UI
package/src/types.ts CHANGED
@@ -50,6 +50,7 @@ export const ConfigSchema = zod.object({
50
50
  last_legacy_warning: zod.number().optional().describe('Last legacy CLI warning timestamp'),
51
51
  signup_banner_shown: zod.boolean().optional().describe('If the signup banner was shown'),
52
52
  orgId: zod.string().optional().describe('Default organization ID'),
53
+ region: zod.string().optional().describe('Default cloud region'),
53
54
  project_dir: zod.string().optional().describe('Last used project directory'),
54
55
  })
55
56
  .optional()
@@ -61,6 +62,7 @@ export const ConfigSchema = zod.object({
61
62
  })
62
63
  .optional()
63
64
  .describe('the gravity client information'),
65
+
64
66
  });
65
67
 
66
68
  export type Config = zod.infer<typeof ConfigSchema>;