@agentuity/cli 0.0.85 → 0.0.87

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 (106) hide show
  1. package/bin/cli.ts +9 -0
  2. package/dist/bun-path.d.ts +9 -0
  3. package/dist/bun-path.d.ts.map +1 -0
  4. package/dist/bun-path.js +24 -0
  5. package/dist/bun-path.js.map +1 -0
  6. package/dist/cmd/ai/index.d.ts.map +1 -1
  7. package/dist/cmd/ai/index.js +1 -0
  8. package/dist/cmd/ai/index.js.map +1 -1
  9. package/dist/cmd/build/ast.d.ts.map +1 -1
  10. package/dist/cmd/build/ast.js +5 -0
  11. package/dist/cmd/build/ast.js.map +1 -1
  12. package/dist/cmd/build/bundler.d.ts +1 -1
  13. package/dist/cmd/build/bundler.d.ts.map +1 -1
  14. package/dist/cmd/build/bundler.js +99 -81
  15. package/dist/cmd/build/bundler.js.map +1 -1
  16. package/dist/cmd/build/patch/_util.js +6 -6
  17. package/dist/cmd/build/patch/_util.js.map +1 -1
  18. package/dist/cmd/build/patch/llm.js +1 -1
  19. package/dist/cmd/build/patch/llm.js.map +1 -1
  20. package/dist/cmd/build/plugin.d.ts.map +1 -1
  21. package/dist/cmd/build/plugin.js +21 -14
  22. package/dist/cmd/build/plugin.js.map +1 -1
  23. package/dist/cmd/build/route-discovery.d.ts +8 -4
  24. package/dist/cmd/build/route-discovery.d.ts.map +1 -1
  25. package/dist/cmd/build/route-discovery.js +10 -5
  26. package/dist/cmd/build/route-discovery.js.map +1 -1
  27. package/dist/cmd/cloud/scp/download.js +3 -3
  28. package/dist/cmd/cloud/scp/download.js.map +1 -1
  29. package/dist/cmd/cloud/scp/upload.js +3 -3
  30. package/dist/cmd/cloud/scp/upload.js.map +1 -1
  31. package/dist/cmd/cloud/ssh.js +3 -3
  32. package/dist/cmd/cloud/ssh.js.map +1 -1
  33. package/dist/cmd/dev/index.d.ts.map +1 -1
  34. package/dist/cmd/dev/index.js +11 -1
  35. package/dist/cmd/dev/index.js.map +1 -1
  36. package/dist/cmd/index.d.ts.map +1 -1
  37. package/dist/cmd/index.js +7 -0
  38. package/dist/cmd/index.js.map +1 -1
  39. package/dist/cmd/profile/create.d.ts.map +1 -1
  40. package/dist/cmd/profile/create.js +1 -0
  41. package/dist/cmd/profile/create.js.map +1 -1
  42. package/dist/cmd/project/download.d.ts.map +1 -1
  43. package/dist/cmd/project/download.js +5 -15
  44. package/dist/cmd/project/download.js.map +1 -1
  45. package/dist/cmd/upgrade/index.d.ts +20 -0
  46. package/dist/cmd/upgrade/index.d.ts.map +1 -0
  47. package/dist/cmd/upgrade/index.js +307 -0
  48. package/dist/cmd/upgrade/index.js.map +1 -0
  49. package/dist/cmd/version/index.d.ts.map +1 -1
  50. package/dist/cmd/version/index.js +1 -0
  51. package/dist/cmd/version/index.js.map +1 -1
  52. package/dist/config.d.ts +1 -1
  53. package/dist/config.d.ts.map +1 -1
  54. package/dist/config.js +12 -94
  55. package/dist/config.js.map +1 -1
  56. package/dist/download.d.ts.map +1 -1
  57. package/dist/download.js +1 -6
  58. package/dist/download.js.map +1 -1
  59. package/dist/git-helper.d.ts +22 -0
  60. package/dist/git-helper.d.ts.map +1 -0
  61. package/dist/git-helper.js +71 -0
  62. package/dist/git-helper.js.map +1 -0
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/tui.d.ts.map +1 -1
  68. package/dist/tui.js +16 -0
  69. package/dist/tui.js.map +1 -1
  70. package/dist/types.d.ts +7 -0
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/types.js.map +1 -1
  73. package/dist/utils/dependency-checker.d.ts +20 -0
  74. package/dist/utils/dependency-checker.d.ts.map +1 -0
  75. package/dist/utils/dependency-checker.js +161 -0
  76. package/dist/utils/dependency-checker.js.map +1 -0
  77. package/dist/version-check.d.ts +13 -0
  78. package/dist/version-check.d.ts.map +1 -0
  79. package/dist/version-check.js +177 -0
  80. package/dist/version-check.js.map +1 -0
  81. package/package.json +3 -3
  82. package/src/bun-path.ts +26 -0
  83. package/src/cmd/ai/index.ts +1 -0
  84. package/src/cmd/build/ast.ts +7 -0
  85. package/src/cmd/build/bundler.ts +115 -94
  86. package/src/cmd/build/patch/_util.ts +6 -6
  87. package/src/cmd/build/patch/llm.ts +1 -1
  88. package/src/cmd/build/plugin.ts +23 -16
  89. package/src/cmd/build/route-discovery.ts +10 -5
  90. package/src/cmd/cloud/scp/download.ts +3 -3
  91. package/src/cmd/cloud/scp/upload.ts +3 -3
  92. package/src/cmd/cloud/ssh.ts +3 -3
  93. package/src/cmd/dev/index.ts +17 -1
  94. package/src/cmd/index.ts +8 -0
  95. package/src/cmd/profile/create.ts +1 -0
  96. package/src/cmd/project/download.ts +6 -14
  97. package/src/cmd/upgrade/index.ts +365 -0
  98. package/src/cmd/version/index.ts +1 -0
  99. package/src/config.ts +12 -121
  100. package/src/download.ts +1 -7
  101. package/src/git-helper.ts +74 -0
  102. package/src/index.ts +2 -0
  103. package/src/tui.ts +19 -0
  104. package/src/types.ts +7 -0
  105. package/src/utils/dependency-checker.ts +207 -0
  106. package/src/version-check.ts +234 -0
@@ -14,6 +14,7 @@ import type { Logger } from '../../types';
14
14
  import { generateWorkbenchMainTsx, generateWorkbenchIndexHtml } from './workbench';
15
15
  import { analyzeWorkbench, type WorkbenchAnalysis } from './ast';
16
16
  import { type DeployOptions } from '../../schemas/deploy';
17
+ import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
17
18
 
18
19
  const minBunVersion = '>=1.3.3';
19
20
 
@@ -102,7 +103,6 @@ export async function bundle({
102
103
  dev = false,
103
104
  rootDir,
104
105
  project,
105
- port,
106
106
  outDir: customOutDir,
107
107
  tag,
108
108
  logsUrl,
@@ -131,6 +131,14 @@ export async function bundle({
131
131
  const versionOutput = await checkBunVersion();
132
132
  output.push(...versionOutput);
133
133
 
134
+ // Check and upgrade @agentuity/* dependencies if needed
135
+ const upgradeResult = await checkAndUpgradeDependencies(rootDir, logger);
136
+ if (upgradeResult.failed.length > 0 && process.stdin.isTTY) {
137
+ throw new BuildFailedError({
138
+ message: `Failed to upgrade dependencies: ${upgradeResult.failed.join(', ')}`,
139
+ });
140
+ }
141
+
134
142
  const outDir = customOutDir ?? join(rootDir, '.agentuity');
135
143
  const srcDir = join(rootDir, 'src');
136
144
 
@@ -377,6 +385,26 @@ export async function bundle({
377
385
  }
378
386
  }
379
387
 
388
+ // must always set for the template always
389
+ define['process.env.AGENTUITY_PUBLIC_WORKBENCH_PATH'] = JSON.stringify('');
390
+
391
+ // Analyze workbench config early to set environment variables for web build
392
+ if (existsSync(appFile)) {
393
+ if (!workbench) {
394
+ const appContent = await Bun.file(appFile).text();
395
+ workbench = analyzeWorkbench(appContent);
396
+ }
397
+
398
+ if (workbench.hasWorkbench) {
399
+ // Create workbench config with proper defaults
400
+ const defaultConfig = { route: '/workbench', headers: {} };
401
+ const config = { ...defaultConfig, ...workbench.config };
402
+
403
+ // Add to define so process.env.AGENTUITY_PUBLIC_WORKBENCH_PATH gets replaced at build time
404
+ define['process.env.AGENTUITY_PUBLIC_WORKBENCH_PATH'] = JSON.stringify(config.route);
405
+ }
406
+ }
407
+
380
408
  // web folder is optional
381
409
  const webDir = join(srcDir, 'web');
382
410
  if (existsSync(webDir)) {
@@ -468,106 +496,99 @@ export async function bundle({
468
496
  }
469
497
 
470
498
  // Bundle workbench app if detected via setupWorkbench
471
- if (existsSync(appFile)) {
472
- if (!workbench) {
473
- const appContent = await Bun.file(appFile).text();
474
- workbench = analyzeWorkbench(appContent);
475
- }
476
-
477
- if (workbench.hasWorkbench) {
478
- // Create workbench config with proper defaults
479
- const defaultConfig = { route: '/workbench', headers: {}, port: port || 3500 };
480
- const config = { ...defaultConfig, ...workbench.config };
481
- try {
482
- // Generate workbench files on the fly instead of using files from package
483
- const tempWorkbenchDir = join(outDir, 'temp-workbench');
484
- mkdirSync(tempWorkbenchDir, { recursive: true });
485
-
486
- // Generate files using templates
487
- await Bun.write(join(tempWorkbenchDir, 'main.tsx'), generateWorkbenchMainTsx(config));
488
- const workbenchIndexFile = join(tempWorkbenchDir, 'index.html');
489
- await Bun.write(workbenchIndexFile, generateWorkbenchIndexHtml());
490
-
491
- // Bundle workbench using generated files
492
- // Disable splitting to avoid CommonJS/ESM module resolution conflicts
493
- const workbenchBuildConfig: Bun.BuildConfig = {
494
- entrypoints: [workbenchIndexFile],
495
- outdir: join(outDir, 'workbench'),
496
- sourcemap: dev ? 'inline' : 'linked',
497
- target: 'browser',
498
- format: 'esm',
499
- banner: `// Generated file. DO NOT EDIT`,
500
- minify: !dev,
501
- drop: isProd ? ['debugger'] : undefined,
502
- splitting: false,
503
- packages: 'bundle',
504
- conditions: ['browser', 'import', 'default'],
505
- naming: {
506
- entry: '[dir]/[name].[ext]',
507
- chunk: 'workbench/chunk/[name]-[hash].[ext]',
508
- asset: 'workbench/asset/[name]-[hash].[ext]',
509
- },
510
- };
499
+ if (existsSync(appFile) && workbench && workbench.hasWorkbench) {
500
+ // Create workbench config with proper defaults
501
+ const defaultConfig = { route: '/workbench', headers: {} };
502
+ const config = { ...defaultConfig, ...workbench.config };
503
+ try {
504
+ // Generate workbench files on the fly instead of using files from package
505
+ const tempWorkbenchDir = join(outDir, 'temp-workbench');
506
+ mkdirSync(tempWorkbenchDir, { recursive: true });
507
+
508
+ // Generate files using templates
509
+ await Bun.write(join(tempWorkbenchDir, 'main.tsx'), generateWorkbenchMainTsx(config));
510
+ const workbenchIndexFile = join(tempWorkbenchDir, 'index.html');
511
+ await Bun.write(workbenchIndexFile, generateWorkbenchIndexHtml());
512
+
513
+ // Bundle workbench using generated files
514
+ // Disable splitting to avoid CommonJS/ESM module resolution conflicts
515
+ const workbenchBuildConfig: Bun.BuildConfig = {
516
+ entrypoints: [workbenchIndexFile],
517
+ outdir: join(outDir, 'workbench'),
518
+ sourcemap: dev ? 'inline' : 'linked',
519
+ target: 'browser',
520
+ format: 'esm',
521
+ banner: `// Generated file. DO NOT EDIT`,
522
+ minify: !dev,
523
+ drop: isProd ? ['debugger'] : undefined,
524
+ splitting: false,
525
+ packages: 'bundle',
526
+ conditions: ['browser', 'import', 'default'],
527
+ naming: {
528
+ entry: '[dir]/[name].[ext]',
529
+ chunk: 'workbench/chunk/[name]-[hash].[ext]',
530
+ asset: 'workbench/asset/[name]-[hash].[ext]',
531
+ },
532
+ };
511
533
 
512
- const workbenchResult = await Bun.build(workbenchBuildConfig);
513
- if (workbenchResult.success) {
514
- logger.debug('Workbench bundled successfully');
515
- // Clean up temp directory
516
- rmSync(tempWorkbenchDir, { recursive: true, force: true });
517
- } else {
518
- logger.error('Workbench bundling failed. Logs:', workbenchResult.logs);
519
- if (workbenchResult.logs.length === 0) {
520
- logger.error('No build logs available. Checking generated files...');
521
- logger.error('Temp dir exists:', await Bun.file(tempWorkbenchDir).exists());
522
- logger.error('Index file exists:', await Bun.file(workbenchIndexFile).exists());
523
- logger.error(
524
- 'Main.tsx exists:',
525
- await Bun.file(join(tempWorkbenchDir, 'main.tsx')).exists()
526
- );
527
- }
528
- // Clean up temp directory even on failure
529
- rmSync(tempWorkbenchDir, { recursive: true, force: true });
530
- logger.fatal('Workbench bundling failed');
534
+ const workbenchResult = await Bun.build(workbenchBuildConfig);
535
+ if (workbenchResult.success) {
536
+ logger.debug('Workbench bundled successfully');
537
+ // Clean up temp directory
538
+ rmSync(tempWorkbenchDir, { recursive: true, force: true });
539
+ } else {
540
+ logger.error('Workbench bundling failed. Logs:', workbenchResult.logs);
541
+ if (workbenchResult.logs.length === 0) {
542
+ logger.error('No build logs available. Checking generated files...');
543
+ logger.error('Temp dir exists:', await Bun.file(tempWorkbenchDir).exists());
544
+ logger.error('Index file exists:', await Bun.file(workbenchIndexFile).exists());
545
+ logger.error(
546
+ 'Main.tsx exists:',
547
+ await Bun.file(join(tempWorkbenchDir, 'main.tsx')).exists()
548
+ );
531
549
  }
532
- } catch (error) {
533
- logger.error('Failed to bundle workbench:', error);
534
- // Collect all error messages
535
- const errorMessages: string[] = [];
536
- if (error instanceof AggregateError && Array.isArray(error.errors)) {
537
- for (const err of error.errors) {
538
- // Extract useful info from Bun's ResolveMessage errors
539
- if (err && typeof err === 'object') {
540
- const errObj = err as Record<string, unknown>;
541
- if (typeof errObj.message === 'string') {
542
- errorMessages.push(` ${errObj.message}`);
543
- }
544
- const position = errObj.position as Record<string, unknown> | undefined;
545
- if (position?.file && position?.line && position?.column) {
546
- errorMessages.push(
547
- ` at ${position.file}:${position.line}:${position.column}`
548
- );
549
- }
550
+ // Clean up temp directory even on failure
551
+ rmSync(tempWorkbenchDir, { recursive: true, force: true });
552
+ logger.fatal('Workbench bundling failed');
553
+ }
554
+ } catch (error) {
555
+ logger.error('Failed to bundle workbench:', error);
556
+ // Collect all error messages
557
+ const errorMessages: string[] = [];
558
+ if (error instanceof AggregateError && Array.isArray(error.errors)) {
559
+ for (const err of error.errors) {
560
+ // Extract useful info from Bun's ResolveMessage errors
561
+ if (err && typeof err === 'object') {
562
+ const errObj = err as Record<string, unknown>;
563
+ if (typeof errObj.message === 'string') {
564
+ errorMessages.push(` ${errObj.message}`);
565
+ }
566
+ const position = errObj.position as Record<string, unknown> | undefined;
567
+ if (position?.file && position?.line && position?.column) {
568
+ errorMessages.push(
569
+ ` at ${position.file}:${position.line}:${position.column}`
570
+ );
550
571
  }
551
572
  }
552
573
  }
574
+ }
553
575
 
554
- // Show different tips based on whether we're in a monorepo or published package
555
- const isMonorepo = await Bun.file(join(rootDir, '../../packages')).exists();
556
- if (isMonorepo) {
557
- errorMessages.push(
558
- '\nTip: Make sure all @agentuity/* packages are built by',
559
- 'running "bun run build" from the monorepo root.'
560
- );
561
- } else {
562
- errorMessages.push(
563
- '\nTip: If you see module resolution errors, try running',
564
- '"bun install" to ensure all dependencies are installed.'
565
- );
566
- }
567
-
568
- // Don't continue if workbench bundling fails
569
- logger.fatal(errorMessages.join('\n'));
576
+ // Show different tips based on whether we're in a monorepo or published package
577
+ const isMonorepo = await Bun.file(join(rootDir, '../../packages')).exists();
578
+ if (isMonorepo) {
579
+ errorMessages.push(
580
+ '\nTip: Make sure all @agentuity/* packages are built by',
581
+ 'running "bun run build" from the monorepo root.'
582
+ );
583
+ } else {
584
+ errorMessages.push(
585
+ '\nTip: If you see module resolution errors, try running',
586
+ '"bun install" to ensure all dependencies are installed.'
587
+ );
570
588
  }
589
+
590
+ // Don't continue if workbench bundling fails
591
+ logger.fatal(errorMessages.join('\n'));
571
592
  }
572
593
  }
573
594
 
@@ -44,13 +44,13 @@ export function generateGatewayEnvGuard(
44
44
  provider: string
45
45
  ): string {
46
46
  return `{
47
- const apikey = process.env.AGENTUITY_SDK_KEY;
48
- const url = process.env.AGENTUITY_AIGATEWAY_URL || process.env.AGENTUITY_TRANSPORT_URL || (apikey ? 'https://agentuity.ai' : '');
49
- if (url && apikey) {
50
- process.env.${apikey} = ${apikeyval};
51
- process.env.${apibase} = url + '/gateway/${provider}';
47
+ const _agentuity_sdk_key = process.env.AGENTUITY_SDK_KEY;
48
+ const _agentuity_url = process.env.AGENTUITY_AIGATEWAY_URL || process.env.AGENTUITY_TRANSPORT_URL || (_agentuity_sdk_key ? 'https://agentuity.ai' : '');
49
+ if (_agentuity_url && _agentuity_sdk_key) {
50
+ process.env.${apikey} = _agentuity_sdk_key;
51
+ process.env.${apibase} = _agentuity_url + '/gateway/${provider}';
52
52
  console.debug('Enabled Agentuity AI Gateway for ${provider}');
53
- } else {
53
+ } else if (!process.env.${apikey}) {
54
54
  ${generateEnvWarning(apikey)}
55
55
  }
56
56
  }
@@ -24,7 +24,7 @@ export function generatePatches(): Map<string, PatchModule> {
24
24
  const patches = new Map<string, PatchModule>();
25
25
  registerLLMPatch(
26
26
  patches,
27
- '@anthropic-ai',
27
+ '@anthropic-ai/sdk',
28
28
  'index',
29
29
  'ANTHROPIC_API_KEY',
30
30
  'ANTHROPIC_BASE_URL',
@@ -375,6 +375,8 @@ import { readFileSync, existsSync } from 'node:fs';
375
375
  index += html;
376
376
  }
377
377
  }
378
+ // make paths absolute
379
+ index = index.replaceAll('./web/', '/web/');
378
380
  const webstatic = serveStatic({ root: import.meta.dir + '/web' });
379
381
  // In dev mode, serve from source; in prod, serve from build output
380
382
  const publicRoot = ${isDevMode} ? ${JSON.stringify(join(srcDir, 'web', 'public'))} : import.meta.dir + '/web/public';
@@ -389,16 +391,8 @@ import { readFileSync, existsSync } from 'node:fs';
389
391
  if (path.includes('..') || path.includes('%2e%2e')) {
390
392
  return c.notFound();
391
393
  }
392
- // Only serve from public folder at root (skip /web/* routes and /)
393
- if (path !== '/' && !path.startsWith('/web/')) {
394
- try {
395
- // serveStatic calls next() internally if file not found
396
- return await publicstatic(c, next);
397
- } catch (err) {
398
- return next();
399
- }
400
- }
401
- return next();
394
+ // serve default for any path not explicitly matched
395
+ return c.html(index);
402
396
  });
403
397
  })();`);
404
398
  }
@@ -495,6 +489,14 @@ import { readFileSync, existsSync } from 'node:fs';
495
489
  }
496
490
 
497
491
  for (const apiFile of apiFiles) {
492
+ // Quick check: skip files that don't contain createRouter or Hono
493
+ // This avoids expensive AST parsing for utility files
494
+ const fileContent = await Bun.file(apiFile).text();
495
+ if (!fileContent.includes('createRouter') && !fileContent.includes('new Hono')) {
496
+ logger.trace(`Skipping ${apiFile}: no createRouter or Hono found`);
497
+ continue;
498
+ }
499
+
498
500
  try {
499
501
  const routes = await parseRoute(rootDir, apiFile, projectId, deploymentId);
500
502
 
@@ -574,12 +576,17 @@ import { readFileSync, existsSync } from 'node:fs';
574
576
  });
575
577
  }
576
578
  } catch (error) {
577
- // Skip files that don't have createRouter (they might be utilities)
578
- if (
579
- error instanceof Error &&
580
- error.message.includes('could not find an proper createRouter')
581
- ) {
582
- logger.trace(`Skipping ${apiFile}: no createRouter found`);
579
+ // Skip files that don't have proper router setup despite containing createRouter/Hono
580
+ // (e.g., files that import but don't use them, or have syntax errors)
581
+ if (error instanceof Error) {
582
+ if (
583
+ error.message.includes('could not find default export') ||
584
+ error.message.includes('could not find an proper createRouter')
585
+ ) {
586
+ logger.trace(`Skipping ${apiFile}: ${error.message}`);
587
+ } else {
588
+ throw error;
589
+ }
583
590
  } else {
584
591
  throw error;
585
592
  }
@@ -19,17 +19,21 @@ export interface DiscoveredRouteFile {
19
19
  }
20
20
 
21
21
  /**
22
- * Recursively discover all TypeScript route files in an API directory
22
+ * Recursively discover all TypeScript files in an API directory.
23
+ * Any .ts file CAN be a route if it exports a router, but not all files MUST be routes.
24
+ * Files without router exports (utility files, types, helpers) are discovered but gracefully skipped during processing.
25
+ *
23
26
  * Supports nested structures like:
24
- * - src/api/index.ts (root API router)
27
+ * - src/api/index.ts (root API router - handled separately)
25
28
  * - src/api/auth/route.ts -> mounted at /api/auth
29
+ * - src/api/auth/login.ts -> mounted at /api/auth
26
30
  * - src/api/v1/users/route.ts -> mounted at /api/v1/users
27
- * - src/api/admin/users/login.ts -> mounted at /api/admin/users (any .ts file works)
31
+ * - src/api/auth/helpers.ts -> discovered but skipped if no router export
28
32
  *
29
33
  * @param apiDir - Absolute path to the src/api directory
30
34
  * @param currentDir - Current directory being scanned (used for recursion)
31
35
  * @param results - Accumulated results (used for recursion)
32
- * @returns Array of discovered route files with mount information
36
+ * @returns Array of discovered TypeScript files with mount information
33
37
  */
34
38
  export function discoverRouteFiles(
35
39
  apiDir: string,
@@ -63,8 +67,9 @@ export function discoverRouteFiles(
63
67
 
64
68
  // For subdirectory files, determine mount path
65
69
  // src/api/auth/route.ts -> /api/auth
70
+ // src/api/auth/login.ts -> /api/auth
66
71
  // src/api/v1/users/route.ts -> /api/v1/users
67
- // src/api/admin/login.ts -> /api/admin
72
+ // src/api/admin/helpers.ts -> /api/admin (discovered but may be skipped during processing)
68
73
  const pathParts = relativePath.split('/');
69
74
  pathParts.pop(); // Remove filename
70
75
 
@@ -36,7 +36,7 @@ export const downloadCommand = createSubcommand({
36
36
  description: 'Download multiple files',
37
37
  },
38
38
  ],
39
- requires: { apiClient: true, auth: true },
39
+ requires: { apiClient: true, auth: true, region: true },
40
40
  optional: { project: true },
41
41
  prerequisites: ['cloud deploy'],
42
42
  schema: {
@@ -51,7 +51,7 @@ export const downloadCommand = createSubcommand({
51
51
  },
52
52
 
53
53
  async handler(ctx) {
54
- const { apiClient, args, opts, project, projectDir, config } = ctx;
54
+ const { apiClient, args, opts, project, projectDir, config, region } = ctx;
55
55
 
56
56
  let identifier = opts?.identifier ?? project?.projectId;
57
57
 
@@ -59,7 +59,7 @@ export const downloadCommand = createSubcommand({
59
59
  identifier = await tui.showProjectList(apiClient, true);
60
60
  }
61
61
 
62
- const hostname = getIONHost(config);
62
+ const hostname = getIONHost(config, region);
63
63
  const destination = args.destination ?? projectDir;
64
64
 
65
65
  try {
@@ -39,7 +39,7 @@ export const uploadCommand = createSubcommand({
39
39
  description: 'Upload multiple files',
40
40
  },
41
41
  ],
42
- requires: { apiClient: true, auth: true },
42
+ requires: { apiClient: true, auth: true, region: true },
43
43
  schema: {
44
44
  args,
45
45
  options,
@@ -54,7 +54,7 @@ export const uploadCommand = createSubcommand({
54
54
  prerequisites: ['cloud deploy'],
55
55
 
56
56
  async handler(ctx) {
57
- const { apiClient, args, opts, project, projectDir, config } = ctx;
57
+ const { apiClient, args, opts, project, projectDir, config, region } = ctx;
58
58
 
59
59
  let identifier = opts?.identifier ?? project?.projectId;
60
60
 
@@ -62,7 +62,7 @@ export const uploadCommand = createSubcommand({
62
62
  identifier = await tui.showProjectList(apiClient, true);
63
63
  }
64
64
 
65
- const hostname = getIONHost(config);
65
+ const hostname = getIONHost(config, region);
66
66
  const destination = args.destination ?? '.';
67
67
 
68
68
  try {
@@ -35,13 +35,13 @@ export const sshSubcommand = createSubcommand({
35
35
  },
36
36
  ],
37
37
  toplevel: true,
38
- requires: { auth: true, apiClient: true },
38
+ requires: { auth: true, apiClient: true, region: true },
39
39
  optional: { project: true },
40
40
  prerequisites: ['cloud deploy'],
41
41
  schema: { args, options },
42
42
 
43
43
  async handler(ctx) {
44
- const { apiClient, project, projectDir, args, config, opts } = ctx;
44
+ const { apiClient, project, projectDir, args, config, opts, region } = ctx;
45
45
 
46
46
  let projectId = project?.projectId;
47
47
  let identifier = args?.identifier;
@@ -56,7 +56,7 @@ export const sshSubcommand = createSubcommand({
56
56
  projectId = await tui.showProjectList(apiClient, true);
57
57
  }
58
58
 
59
- const hostname = getIONHost(config);
59
+ const hostname = getIONHost(config, region);
60
60
 
61
61
  const cmd = ['ssh', `${identifier ?? projectId}@${hostname}`, command].filter(
62
62
  Boolean
@@ -16,7 +16,7 @@ import { type Config, createCommand } from '../../types';
16
16
  import * as tui from '../../tui';
17
17
  import { createAgentTemplates, createAPITemplates } from './templates';
18
18
  import { generateEndpoint, type DevmodeResponse } from './api';
19
- import { APIClient, getAPIBaseURL, getGravityDevModeURL } from '../../api';
19
+ import { APIClient, getAppBaseURL, getAPIBaseURL, getGravityDevModeURL } from '../../api';
20
20
  import { download } from './download';
21
21
  import { createDevmodeSyncService } from './sync';
22
22
  import { getDevmodeDeploymentId } from '../build/ast';
@@ -105,6 +105,7 @@ export const command = createCommand({
105
105
  let devmode: DevmodeResponse | undefined;
106
106
  let gravityBin: string | undefined;
107
107
  let gravityURL: string | undefined;
108
+ let appURL: string | undefined;
108
109
 
109
110
  if (auth && project && opts.public) {
110
111
  // Generate devmode endpoint only when using --public
@@ -123,6 +124,7 @@ export const command = createCommand({
123
124
  config = _config;
124
125
  devmode = endpoint;
125
126
  gravityURL = getGravityDevModeURL(project.region, config);
127
+ appURL = `${getAppBaseURL(config)}/r/${project.projectId}`;
126
128
  logger.trace('gravity url: %s', gravityURL);
127
129
  }
128
130
 
@@ -179,6 +181,9 @@ export const command = createCommand({
179
181
  ? tui.link(`http://127.0.0.1:${opts.port}${workbench.config?.route ?? '/workbench'}`)
180
182
  : tui.warn('Disabled')) +
181
183
  '\n' +
184
+ tui.muted(tui.padRight('Dashboard:', padding)) +
185
+ (appURL ? tui.link(appURL) : tui.warn('Disabled')) +
186
+ '\n' +
182
187
  (canDoInput
183
188
  ? '\n' + tui.muted('Press ') + tui.bold('h') + tui.muted(' for keyboard shortcuts')
184
189
  : '');
@@ -933,6 +938,17 @@ export const command = createCommand({
933
938
  return;
934
939
  }
935
940
 
941
+ // Ignore .git folder
942
+ if (changedFile && (changedFile === '.git' || changedFile.startsWith('.git/'))) {
943
+ logger.trace(
944
+ 'File change ignored (.git folder): %s (event: %s, file: %s)',
945
+ watchDir,
946
+ eventType,
947
+ changedFile
948
+ );
949
+ return;
950
+ }
951
+
936
952
  // Ignore changes in .agentuity directory (build output)
937
953
  // Check both relative path and normalized absolute path
938
954
  const isInAgentuityDir =
package/src/cmd/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { CommandDefinition } from '../types';
2
+ import { isRunningFromExecutable } from './upgrade';
2
3
 
3
4
  // Use dynamic imports for bundler compatibility while maintaining lazy loading
4
5
  export async function discoverCommands(): Promise<CommandDefinition[]> {
@@ -12,12 +13,19 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
12
13
  import('./profile').then((m) => m.command),
13
14
  import('./project').then((m) => m.command),
14
15
  import('./repl').then((m) => m.command),
16
+ import('./upgrade').then((m) => m.command),
15
17
  import('./version').then((m) => m.command),
16
18
  ]);
17
19
 
18
20
  const commands: CommandDefinition[] = [];
21
+ const isExecutable = isRunningFromExecutable();
19
22
 
20
23
  for (const cmd of commandModules) {
24
+ // Skip commands that require running from an executable when not in one
25
+ if (cmd.executable && !isExecutable) {
26
+ continue;
27
+ }
28
+
21
29
  commands.push(cmd);
22
30
 
23
31
  // Auto-create hidden top-level aliases for subcommands with toplevel: true
@@ -73,6 +73,7 @@ export const createCommand = createSubcommand({
73
73
  if (name === 'local') {
74
74
  // if we're creating a local profile, go ahead and fill it out for the dev to make it easier to get started
75
75
  const localConfig = (await loadConfig(filename)) as Config;
76
+ localConfig.name = name;
76
77
  localConfig.overrides = {
77
78
  api_url: 'https://api.agentuity.io',
78
79
  app_url: 'https://app.agentuity.io',
@@ -228,21 +228,13 @@ export async function setupProject(options: SetupOptions): Promise<void> {
228
228
  }
229
229
 
230
230
  // Initialize git repository if git is available
231
- const gitPath = Bun.which('git');
232
- if (gitPath) {
231
+ // Check for real git (not macOS stub that triggers Xcode CLT popup)
232
+ const { isGitAvailable, getDefaultBranch } = await import('../../git-helper');
233
+ const gitAvailable = await isGitAvailable();
234
+
235
+ if (gitAvailable) {
233
236
  // Get default branch from git config, fallback to 'main'
234
- let defaultBranch = 'main';
235
- try {
236
- const result = Bun.spawnSync(['git', 'config', '--global', 'init.defaultBranch']);
237
- if (result.exitCode === 0) {
238
- const branch = result.stdout.toString().trim();
239
- if (branch) {
240
- defaultBranch = branch;
241
- }
242
- }
243
- } catch {
244
- // Ignore errors, use fallback
245
- }
237
+ const defaultBranch = (await getDefaultBranch()) || 'main';
246
238
 
247
239
  // Git is available, initialize repository
248
240
  await tui.runCommand({