@agentuity/cli 0.0.72 → 0.0.74

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 (126) hide show
  1. package/bin/cli.ts +19 -5
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +13 -9
  4. package/dist/auth.js.map +1 -1
  5. package/dist/banner.js +1 -1
  6. package/dist/banner.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +79 -21
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cmd/ai/prompt/api.d.ts.map +1 -1
  11. package/dist/cmd/ai/prompt/api.js +5 -4
  12. package/dist/cmd/ai/prompt/api.js.map +1 -1
  13. package/dist/cmd/auth/api.d.ts +2 -2
  14. package/dist/cmd/auth/api.d.ts.map +1 -1
  15. package/dist/cmd/auth/api.js +15 -14
  16. package/dist/cmd/auth/api.js.map +1 -1
  17. package/dist/cmd/auth/login.d.ts.map +1 -1
  18. package/dist/cmd/auth/login.js +37 -16
  19. package/dist/cmd/auth/login.js.map +1 -1
  20. package/dist/cmd/auth/ssh/api.d.ts.map +1 -1
  21. package/dist/cmd/auth/ssh/api.js +3 -2
  22. package/dist/cmd/auth/ssh/api.js.map +1 -1
  23. package/dist/cmd/build/ast.d.ts.map +1 -1
  24. package/dist/cmd/build/ast.js +76 -14
  25. package/dist/cmd/build/ast.js.map +1 -1
  26. package/dist/cmd/build/bundler.d.ts +3 -1
  27. package/dist/cmd/build/bundler.d.ts.map +1 -1
  28. package/dist/cmd/build/bundler.js +21 -9
  29. package/dist/cmd/build/bundler.js.map +1 -1
  30. package/dist/cmd/build/format-schema.d.ts +6 -0
  31. package/dist/cmd/build/format-schema.d.ts.map +1 -0
  32. package/dist/cmd/build/format-schema.js +60 -0
  33. package/dist/cmd/build/format-schema.js.map +1 -0
  34. package/dist/cmd/build/index.d.ts.map +1 -1
  35. package/dist/cmd/build/index.js +13 -0
  36. package/dist/cmd/build/index.js.map +1 -1
  37. package/dist/cmd/build/plugin.d.ts.map +1 -1
  38. package/dist/cmd/build/plugin.js +123 -32
  39. package/dist/cmd/build/plugin.js.map +1 -1
  40. package/dist/cmd/build/route-discovery.d.ts +50 -0
  41. package/dist/cmd/build/route-discovery.d.ts.map +1 -0
  42. package/dist/cmd/build/route-discovery.js +143 -0
  43. package/dist/cmd/build/route-discovery.js.map +1 -0
  44. package/dist/cmd/build/route-registry.d.ts.map +1 -1
  45. package/dist/cmd/build/route-registry.js +25 -10
  46. package/dist/cmd/build/route-registry.js.map +1 -1
  47. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  48. package/dist/cmd/cloud/deploy.js +8 -6
  49. package/dist/cmd/cloud/deploy.js.map +1 -1
  50. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  51. package/dist/cmd/cloud/deployment/show.js +34 -10
  52. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  53. package/dist/cmd/dev/agents.d.ts.map +1 -1
  54. package/dist/cmd/dev/agents.js +2 -2
  55. package/dist/cmd/dev/agents.js.map +1 -1
  56. package/dist/cmd/dev/index.d.ts.map +1 -1
  57. package/dist/cmd/dev/index.js +21 -0
  58. package/dist/cmd/dev/index.js.map +1 -1
  59. package/dist/cmd/dev/sync.d.ts.map +1 -1
  60. package/dist/cmd/dev/sync.js +2 -2
  61. package/dist/cmd/dev/sync.js.map +1 -1
  62. package/dist/cmd/project/download.d.ts.map +1 -1
  63. package/dist/cmd/project/download.js +16 -2
  64. package/dist/cmd/project/download.js.map +1 -1
  65. package/dist/cmd/project/list.d.ts.map +1 -1
  66. package/dist/cmd/project/list.js +2 -10
  67. package/dist/cmd/project/list.js.map +1 -1
  68. package/dist/cmd/project/show.d.ts.map +1 -1
  69. package/dist/cmd/project/show.js +8 -7
  70. package/dist/cmd/project/show.js.map +1 -1
  71. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  72. package/dist/cmd/project/template-flow.js +14 -2
  73. package/dist/cmd/project/template-flow.js.map +1 -1
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/config.js +9 -0
  76. package/dist/config.js.map +1 -1
  77. package/dist/index.d.ts +2 -2
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/index.js.map +1 -1
  81. package/dist/steps.d.ts +20 -30
  82. package/dist/steps.d.ts.map +1 -1
  83. package/dist/steps.js +339 -184
  84. package/dist/steps.js.map +1 -1
  85. package/dist/tui/box.d.ts.map +1 -1
  86. package/dist/tui/box.js +8 -4
  87. package/dist/tui/box.js.map +1 -1
  88. package/dist/tui/prompt.d.ts.map +1 -1
  89. package/dist/tui/prompt.js +7 -2
  90. package/dist/tui/prompt.js.map +1 -1
  91. package/dist/tui.d.ts +20 -1
  92. package/dist/tui.d.ts.map +1 -1
  93. package/dist/tui.js +90 -18
  94. package/dist/tui.js.map +1 -1
  95. package/dist/types.d.ts +10 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/package.json +3 -3
  98. package/src/auth.ts +13 -10
  99. package/src/banner.ts +1 -1
  100. package/src/cli.ts +89 -27
  101. package/src/cmd/ai/prompt/api.ts +5 -4
  102. package/src/cmd/auth/api.ts +20 -22
  103. package/src/cmd/auth/login.ts +36 -17
  104. package/src/cmd/auth/ssh/api.ts +5 -9
  105. package/src/cmd/build/ast.ts +88 -14
  106. package/src/cmd/build/bundler.ts +32 -11
  107. package/src/cmd/build/format-schema.ts +66 -0
  108. package/src/cmd/build/index.ts +14 -0
  109. package/src/cmd/build/plugin.ts +146 -36
  110. package/src/cmd/build/route-discovery.ts +197 -0
  111. package/src/cmd/build/route-registry.ts +26 -10
  112. package/src/cmd/cloud/deploy.ts +19 -6
  113. package/src/cmd/cloud/deployment/show.ts +42 -10
  114. package/src/cmd/dev/agents.ts +2 -10
  115. package/src/cmd/dev/index.ts +25 -0
  116. package/src/cmd/dev/sync.ts +2 -12
  117. package/src/cmd/project/download.ts +16 -2
  118. package/src/cmd/project/list.ts +2 -9
  119. package/src/cmd/project/show.ts +8 -6
  120. package/src/cmd/project/template-flow.ts +21 -2
  121. package/src/config.ts +10 -0
  122. package/src/index.ts +2 -2
  123. package/src/steps.ts +397 -229
  124. package/src/tui/box.ts +8 -4
  125. package/src/tui/prompt.ts +7 -4
  126. package/src/tui.ts +125 -20
@@ -4,11 +4,11 @@ import type { APIClient } from '../../api';
4
4
  import { StructuredError } from '@agentuity/core';
5
5
 
6
6
  // Zod schemas for API validation
7
- const OTPStartDataSchema = z.object({
8
- otp: z.string(),
7
+ const CodeStartDataSchema = z.object({
8
+ code: z.string(),
9
9
  });
10
10
 
11
- const OTPCompleteDataSchema = z.object({
11
+ const CodeCompleteDataSchema = z.object({
12
12
  apiKey: z.string(),
13
13
  userId: z.string(),
14
14
  expires: z.number(),
@@ -20,8 +20,8 @@ const SignupCompleteDataSchema = z.object({
20
20
  expiresAt: z.number(),
21
21
  });
22
22
 
23
- const OTPCheckRequestSchema = z.object({
24
- otp: z.string(),
23
+ const CodeCheckRequestSchema = z.object({
24
+ code: z.string(),
25
25
  });
26
26
 
27
27
  // Exported result types
@@ -37,23 +37,23 @@ export interface SignupResult {
37
37
  expires: Date;
38
38
  }
39
39
 
40
- const OTPGenerationError = StructuredError(
41
- 'OTPGenerationError',
42
- 'Error generating the one-time login code'
40
+ const CodeGenerationError = StructuredError(
41
+ 'CodeGenerationError',
42
+ 'Error generating the login code'
43
43
  );
44
44
 
45
- export async function generateLoginOTP(apiClient: APIClient): Promise<string> {
46
- const resp = await apiClient.get('/cli/auth/start', APIResponseSchema(OTPStartDataSchema));
45
+ export async function generateLoginCode(apiClient: APIClient): Promise<string> {
46
+ const resp = await apiClient.get('/cli/auth/start', APIResponseSchema(CodeStartDataSchema));
47
47
 
48
48
  if (!resp.success) {
49
- throw new OTPGenerationError();
49
+ throw new CodeGenerationError();
50
50
  }
51
51
 
52
52
  if (!resp.data) {
53
- throw new OTPGenerationError();
53
+ throw new CodeGenerationError();
54
54
  }
55
55
 
56
- return resp.data.otp;
56
+ return resp.data.code;
57
57
  }
58
58
 
59
59
  const PollForLoginError = StructuredError('PollForLoginError');
@@ -64,18 +64,17 @@ const PollForLoginTimeout = StructuredError(
64
64
 
65
65
  export async function pollForLoginCompletion(
66
66
  apiClient: APIClient,
67
- otp: string,
68
- timeoutMs = 60000
67
+ code: string,
68
+ timeoutMs = 300000 // 5 minutes
69
69
  ): Promise<LoginResult> {
70
70
  const started = Date.now();
71
71
 
72
72
  while (Date.now() - started < timeoutMs) {
73
- const resp = await apiClient.request(
74
- 'POST',
73
+ const resp = await apiClient.post(
75
74
  '/cli/auth/check',
76
- APIResponseSchemaOptionalData(OTPCompleteDataSchema),
77
- { otp },
78
- OTPCheckRequestSchema
75
+ { code },
76
+ APIResponseSchemaOptionalData(CodeCompleteDataSchema),
77
+ CodeCheckRequestSchema
79
78
  );
80
79
 
81
80
  if (!resp.success) {
@@ -126,8 +125,7 @@ export async function pollForSignupCompletion(
126
125
 
127
126
  while (Date.now() - started < timeoutMs) {
128
127
  try {
129
- const resp = await apiClient.request(
130
- 'GET',
128
+ const resp = await apiClient.get(
131
129
  `/cli/auth/signup/${otp}`,
132
130
  APIResponseSchema(SignupCompleteDataSchema)
133
131
  );
@@ -1,7 +1,7 @@
1
1
  import { createSubcommand } from '../../types';
2
2
  import { getAppBaseURL } from '../../api';
3
3
  import { saveAuth } from '../../config';
4
- import { generateLoginOTP, pollForLoginCompletion } from './api';
4
+ import { generateLoginCode, pollForLoginCompletion } from './api';
5
5
  import * as tui from '../../tui';
6
6
  import { getCommand } from '../../command-prefix';
7
7
  import { ErrorCode } from '../../errors';
@@ -23,41 +23,60 @@ export const loginCommand = createSubcommand({
23
23
  const appUrl = getAppBaseURL(config);
24
24
 
25
25
  try {
26
- const otp = await tui.spinner({
27
- message: 'Generating login one time code...',
26
+ const code = await tui.spinner({
27
+ message: 'Generating login code...',
28
28
  clearOnSuccess: true,
29
29
  callback: () => {
30
- return generateLoginOTP(apiClient);
30
+ return generateLoginCode(apiClient);
31
31
  },
32
32
  });
33
33
 
34
- if (!otp) {
34
+ if (!code) {
35
35
  return;
36
36
  }
37
37
 
38
- const authURL = `${appUrl}/auth/cli`;
38
+ const authURL = `${appUrl}/auth/cli?code=${code}`;
39
39
 
40
- const copied = await tui.copyToClipboard(otp);
40
+ const copied = await tui.copyToClipboard(authURL);
41
41
 
42
+ tui.newline();
43
+ console.log(`Your login code: ${tui.bold(code)}`);
42
44
  tui.newline();
43
45
  if (copied) {
44
- console.log(`Code copied to clipboard: ${tui.bold(otp)}`);
46
+ console.log('Login URL copied to clipboard! Open it in your browser:');
45
47
  } else {
46
- console.log('Copy the following code:');
47
- tui.newline();
48
- console.log(` ${tui.bold(otp)}`);
48
+ console.log('Open this URL in your browser to approve the login:');
49
49
  }
50
50
  tui.newline();
51
- console.log('Then open the URL in your browser and paste the code:');
52
- tui.newline();
53
51
  console.log(` ${tui.link(authURL)}`);
54
52
  tui.newline();
55
- console.log(tui.muted('This code will expire in 60 seconds'));
53
+ console.log(tui.muted('Press Enter to open in your browser, or Ctrl+C to cancel'));
56
54
  tui.newline();
57
55
 
58
- console.log('Waiting for login to complete...');
59
-
60
- const result = await pollForLoginCompletion(apiClient, otp);
56
+ const result = await tui.spinner({
57
+ type: 'countdown',
58
+ message: 'Waiting for approval',
59
+ timeoutMs: 300000, // 5 minutes
60
+ clearOnSuccess: true,
61
+ onEnterPress: () => {
62
+ // Open URL in default browser
63
+ const platform = process.platform;
64
+ if (platform === 'win32') {
65
+ // Windows: use cmd.exe to invoke start (it's a shell builtin, not an executable)
66
+ // Empty string is required as the window title argument
67
+ Bun.spawn(['cmd', '/c', 'start', '', authURL], {
68
+ stdout: 'ignore',
69
+ stderr: 'ignore',
70
+ });
71
+ } else {
72
+ const command = platform === 'darwin' ? 'open' : 'xdg-open';
73
+ Bun.spawn([command, authURL], { stdout: 'ignore', stderr: 'ignore' });
74
+ }
75
+ },
76
+ callback: async () => {
77
+ return await pollForLoginCompletion(apiClient, code);
78
+ },
79
+ });
61
80
 
62
81
  await saveAuth({
63
82
  apiKey: result.apiKey,
@@ -58,11 +58,10 @@ const AddSSHKeyUnexpectedError = StructuredError(
58
58
  );
59
59
 
60
60
  export async function addSSHKey(apiClient: APIClient, publicKey: string): Promise<AddSSHKeyResult> {
61
- const resp = await apiClient.request(
62
- 'POST',
61
+ const resp = await apiClient.post(
63
62
  '/cli/auth/ssh-keys',
64
- APIResponseSchema(AddSSHKeyResponseSchema),
65
- { publicKey }
63
+ { publicKey },
64
+ APIResponseSchema(AddSSHKeyResponseSchema)
66
65
  );
67
66
 
68
67
  if (!resp.success) {
@@ -79,11 +78,7 @@ export async function addSSHKey(apiClient: APIClient, publicKey: string): Promis
79
78
  const ListSSHKeysError = StructuredError('ListSSHKeysError');
80
79
 
81
80
  export async function listSSHKeys(apiClient: APIClient): Promise<SSHKey[]> {
82
- const resp = await apiClient.request(
83
- 'GET',
84
- '/cli/auth/ssh-keys',
85
- APIResponseSchema(z.array(SSHKeySchema))
86
- );
81
+ const resp = await apiClient.get('/cli/auth/ssh-keys', APIResponseSchema(z.array(SSHKeySchema)));
87
82
 
88
83
  if (!resp.success) {
89
84
  throw new ListSSHKeysError({ message: resp.message });
@@ -95,6 +90,7 @@ export async function listSSHKeys(apiClient: APIClient): Promise<SSHKey[]> {
95
90
  const RemoveSSHKeysError = StructuredError('RemoveSSHKeysError');
96
91
 
97
92
  export async function removeSSHKey(apiClient: APIClient, fingerprint: string): Promise<boolean> {
93
+ // NOTE: Using .request() here because DELETE with body is required by the API
98
94
  const resp = await apiClient.request(
99
95
  'DELETE',
100
96
  '/cli/auth/ssh-keys',
@@ -1,5 +1,5 @@
1
1
  import * as acornLoose from 'acorn-loose';
2
- import { basename, dirname, relative } from 'node:path';
2
+ import { dirname, relative } from 'node:path';
3
3
  import { parse as parseCronExpression } from '@datasert/cronjs-parser';
4
4
  import { generate } from 'astring';
5
5
  import type { BuildMetadata } from '../../types';
@@ -10,6 +10,7 @@ import type { LogLevel } from '../../types';
10
10
  import { join } from 'node:path';
11
11
  import { existsSync, mkdirSync } from 'node:fs';
12
12
  import JSON5 from 'json5';
13
+ import { formatSchemaCode } from './format-schema';
13
14
 
14
15
  const logger = createLogger((process.env.AGENTUITY_LOG_LEVEL || 'info') as LogLevel);
15
16
 
@@ -213,11 +214,11 @@ function extractSchemaCode(callargexp: ASTObjectExpression): {
213
214
  for (const prop of schemaObj.properties) {
214
215
  if (prop.key.type === 'Identifier') {
215
216
  if (prop.key.name === 'input' && prop.value) {
216
- // Generate source code from AST node
217
- inputSchemaCode = generate(prop.value);
217
+ // Generate source code from AST node and format it
218
+ inputSchemaCode = formatSchemaCode(generate(prop.value));
218
219
  } else if (prop.key.name === 'output' && prop.value) {
219
- // Generate source code from AST node
220
- outputSchemaCode = generate(prop.value);
220
+ // Generate source code from AST node and format it
221
+ outputSchemaCode = formatSchemaCode(generate(prop.value));
221
222
  }
222
223
  }
223
224
  }
@@ -524,7 +525,7 @@ export async function parseAgentMetadata(
524
525
  });
525
526
  let exportName: string | undefined;
526
527
  const rel = relative(rootDir, filename);
527
- const name = basename(dirname(filename));
528
+ let name: string | undefined; // Will be set from createAgent identifier
528
529
  const version = hash(contents);
529
530
  const id = getAgentId(projectId, deploymentId, rel, version);
530
531
 
@@ -560,6 +561,9 @@ export async function parseAgentMetadata(
560
561
  );
561
562
  }
562
563
 
564
+ // Extract agent identifier from createAgent first argument
565
+ name = nameArg.value;
566
+
563
567
  const callargexp = configArg;
564
568
 
565
569
  // Extract schema code before processing metadata
@@ -588,7 +592,7 @@ export async function parseAgentMetadata(
588
592
  break;
589
593
  }
590
594
  }
591
- if (!result) {
595
+ if (!result && name) {
592
596
  result = createAgentMetadataNode(
593
597
  id,
594
598
  name,
@@ -656,6 +660,9 @@ export async function parseAgentMetadata(
656
660
  );
657
661
  }
658
662
 
663
+ // Extract agent identifier from createAgent first argument
664
+ name = nameArg.value;
665
+
659
666
  const callargexp = configArg;
660
667
 
661
668
  // Extract schema code before processing metadata
@@ -684,7 +691,7 @@ export async function parseAgentMetadata(
684
691
  break;
685
692
  }
686
693
  }
687
- if (!result) {
694
+ if (!result && name) {
688
695
  result = createAgentMetadataNode(
689
696
  id,
690
697
  name,
@@ -782,6 +789,8 @@ function hasValidatorCall(args: unknown[]): ValidatorInfo {
782
789
  // Check if this is a CallExpression with callee named 'validator'
783
790
  if (node.type === 'CallExpression') {
784
791
  const callExpr = node as ASTCallExpression;
792
+
793
+ // Check for standalone validator({ input, output })
785
794
  if (callExpr.callee.type === 'Identifier') {
786
795
  const identifier = callExpr.callee as ASTNodeIdentifier;
787
796
  if (identifier.name === 'validator') {
@@ -789,7 +798,13 @@ function hasValidatorCall(args: unknown[]): ValidatorInfo {
789
798
  const schemas = extractValidatorSchemas(callExpr);
790
799
  return { hasValidator: true, ...schemas };
791
800
  }
801
+ // Check for zValidator('json', schema)
802
+ if (identifier.name === 'zValidator') {
803
+ const schemas = extractZValidatorSchema(callExpr);
804
+ return { hasValidator: true, ...schemas };
805
+ }
792
806
  }
807
+
793
808
  // Check for agent.validator()
794
809
  if (callExpr.callee.type === 'MemberExpression') {
795
810
  const member = callExpr.callee as ASTMemberExpression;
@@ -799,7 +814,9 @@ function hasValidatorCall(args: unknown[]): ValidatorInfo {
799
814
  member.object.type === 'Identifier'
800
815
  ? (member.object as ASTNodeIdentifier).name
801
816
  : undefined;
802
- return { hasValidator: true, agentVariable };
817
+ // Also check for schema overrides: agent.validator({ input, output })
818
+ const schemas = extractValidatorSchemas(callExpr);
819
+ return { hasValidator: true, agentVariable, ...schemas };
803
820
  }
804
821
  }
805
822
  }
@@ -845,14 +862,59 @@ function extractValidatorSchemas(callExpr: ASTCallExpression): {
845
862
  return result;
846
863
  }
847
864
 
865
+ /**
866
+ * Extract schema from zValidator() call arguments
867
+ * Example: zValidator('json', mySchema) or zValidator('json', z.object({...}))
868
+ * Returns the schema as inputSchemaVariable since zValidator is for request body validation
869
+ * Only extracts schemas for 'json' target, not 'query', 'param', 'header', or 'cookie'
870
+ */
871
+ function extractZValidatorSchema(callExpr: ASTCallExpression): {
872
+ inputSchemaVariable?: string;
873
+ } {
874
+ const result: { inputSchemaVariable?: string } = {};
875
+
876
+ // zValidator requires at least 2 arguments: zValidator(target, schema)
877
+ if (!callExpr.arguments || callExpr.arguments.length < 2) {
878
+ return result;
879
+ }
880
+
881
+ // First argument should be 'json' literal
882
+ const targetArg = callExpr.arguments[0] as ASTNode;
883
+ if (targetArg.type === 'Literal') {
884
+ const targetValue = (targetArg as ASTLiteral).value;
885
+ // Only extract schemas for JSON body validation
886
+ if (targetValue !== 'json') {
887
+ return result;
888
+ }
889
+ } else {
890
+ // If first arg is not a literal, we can't determine the target, skip
891
+ return result;
892
+ }
893
+
894
+ // Second argument is the schema
895
+ const schemaArg = callExpr.arguments[1] as ASTNode;
896
+
897
+ // If it's an identifier (variable reference), extract the name
898
+ if (schemaArg.type === 'Identifier') {
899
+ result.inputSchemaVariable = (schemaArg as ASTNodeIdentifier).name;
900
+ }
901
+ // If it's inline schema (CallExpression like z.object({...})), we detect but don't extract yet
902
+ // TODO: Extract inline schema code
903
+
904
+ return result;
905
+ }
906
+
848
907
  export async function parseRoute(
849
908
  rootDir: string,
850
909
  filename: string,
851
910
  projectId: string,
852
911
  deploymentId: string
853
912
  ): Promise<BuildMetadata['routes']> {
854
- const contents = await Bun.file(filename).text();
855
- const version = hash(contents);
913
+ const rawContents = await Bun.file(filename).text();
914
+ const version = hash(rawContents);
915
+ // Transpile TypeScript to JavaScript so acorn-loose can parse it properly
916
+ const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
917
+ const contents = transpiler.transformSync(rawContents);
856
918
  const ast = acornLoose.parse(contents, {
857
919
  locations: true,
858
920
  ecmaVersion: 'latest',
@@ -933,12 +995,24 @@ export async function parseRoute(
933
995
  }
934
996
 
935
997
  const rel = relative(rootDir, filename);
936
- const dir = dirname(filename);
937
- const name = basename(dir);
938
998
 
939
999
  // For src/api/index.ts, we don't want to add the folder name since it's the root API router
940
1000
  const isRootApi = filename.includes('src/api/index.ts');
941
- const routeName = isRootApi ? '' : name;
1001
+
1002
+ // For nested routes, use the full path from src/api/ instead of just the immediate parent
1003
+ // e.g., src/api/v1/users/route.ts -> routeName = "v1/users"
1004
+ // src/api/auth/route.ts -> routeName = "auth"
1005
+ // src/api/test.ts -> routeName = "" (file directly in src/api/)
1006
+ let routeName = '';
1007
+ if (!isRootApi) {
1008
+ const apiMatch = filename.match(/src\/api\/(.+?)\/[^/]+\.ts$/);
1009
+ if (apiMatch) {
1010
+ // File in subdirectory: src/api/auth/route.ts -> "auth"
1011
+ routeName = apiMatch[1];
1012
+ }
1013
+ // For files directly in src/api/ (e.g., test.ts), routeName stays empty
1014
+ // This prevents double /api prefix since these files often define full paths
1015
+ }
942
1016
 
943
1017
  const routes: RouteDefinition = [];
944
1018
  const routePrefix = '/api';
@@ -4,6 +4,7 @@ import { cpSync, existsSync, mkdirSync, rmSync, readdirSync } from 'node:fs';
4
4
  import gitParseUrl from 'git-url-parse';
5
5
  import { StructuredError } from '@agentuity/core';
6
6
  import * as tui from '../../tui';
7
+ import { pauseStepUI } from '../../steps';
7
8
  import AgentuityBundler, { getBuildMetadata } from './plugin';
8
9
  import { getFilesRecursively } from './file';
9
10
  import { getVersion } from '../../version';
@@ -16,25 +17,38 @@ import { type DeployOptions } from '../../schemas/deploy';
16
17
 
17
18
  const minBunVersion = '>=1.3.3';
18
19
 
19
- async function checkBunVersion() {
20
+ async function checkBunVersion(): Promise<string[]> {
20
21
  if (semver.satisfies(Bun.version, minBunVersion)) {
21
- return;
22
+ return []; // Version is OK, no output needed
22
23
  }
23
- const message = `The current Bun installed is using version ${Bun.version}. This project requires Bun version ${minBunVersion} to build.`;
24
- tui.warning(message);
25
- if (process.stdin.isTTY) {
26
- const ok = await tui.confirm('You would you to upgrade now?');
24
+
25
+ const message = `Bun is using version ${Bun.version}. This project requires Bun version ${minBunVersion} to build.`;
26
+
27
+ if (process.stdin.isTTY && process.stdout.isTTY) {
28
+ // Pause the step UI for interactive prompt
29
+ const resume = pauseStepUI();
30
+
31
+ tui.warning(message);
32
+ const ok = await tui.confirm('Would you like to upgrade now?');
33
+
34
+ // Small delay to ensure console.log('') in confirm completes
35
+ await new Promise((resolve) => setTimeout(resolve, 10));
36
+
37
+ resume(); // Resume step UI
38
+
27
39
  if (ok) {
28
40
  await $`bun upgrade`.quiet();
29
41
  const version = (await $`bun -v`.quiet().text()).trim();
30
- tui.success(`Bun upgraded to ${version}`);
31
- return;
42
+ // Return success message to show in output box
43
+ return [tui.colorSuccess(`Upgraded Bun to ${version}`)];
32
44
  }
33
45
  }
46
+
47
+ // Failed to upgrade or user declined
34
48
  throw new InvalidBunVersion({
35
49
  current: Bun.version,
36
50
  required: minBunVersion,
37
- message: `Please see https://bun.sh/ for information on how to download the latest version.`,
51
+ message,
38
52
  });
39
53
  }
40
54
 
@@ -102,7 +116,9 @@ export async function bundle({
102
116
  env,
103
117
  region,
104
118
  logger,
105
- }: BundleOptions) {
119
+ }: BundleOptions): Promise<{ output: string[] }> {
120
+ const output: string[] = [];
121
+
106
122
  const appFile = join(rootDir, 'app.ts');
107
123
  if (!existsSync(appFile)) {
108
124
  throw new AppFileNotFoundError({
@@ -110,7 +126,8 @@ export async function bundle({
110
126
  });
111
127
  }
112
128
 
113
- await checkBunVersion();
129
+ const versionOutput = await checkBunVersion();
130
+ output.push(...versionOutput);
114
131
 
115
132
  const outDir = customOutDir ?? join(rootDir, '.agentuity');
116
133
  const srcDir = join(rootDir, 'src');
@@ -312,6 +329,8 @@ export async function bundle({
312
329
  id: projectId ?? '',
313
330
  name: pkgContents.name,
314
331
  version: pkgContents.version,
332
+ description: pkgContents.description,
333
+ keywords: pkgContents.keywords,
315
334
  orgId: orgId ?? '',
316
335
  };
317
336
  buildmetadata.deployment = {
@@ -713,4 +732,6 @@ export async function bundle({
713
732
  `${outDir}/.routemapping.json`,
714
733
  dev ? JSON.stringify(routeMapping, null, 2) : JSON.stringify(routeMapping)
715
734
  );
735
+
736
+ return { output };
716
737
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Simple formatter for schema code strings.
3
+ * Adds basic indentation and line breaks for readability.
4
+ */
5
+ export function formatSchemaCode(code: string): string {
6
+ if (!code) return code;
7
+
8
+ let indentLevel = 0;
9
+ const indentSize = 2;
10
+ const lines: string[] = [];
11
+ let currentLine = '';
12
+
13
+ for (let i = 0; i < code.length; i++) {
14
+ const char = code[i];
15
+ const nextChar = code[i + 1];
16
+ const prevChar = i > 0 ? code[i - 1] : '';
17
+
18
+ // Skip existing whitespace/newlines
19
+ if (char === '\n' || char === '\r' || (char === ' ' && prevChar === ' ')) {
20
+ continue;
21
+ }
22
+
23
+ // Handle opening braces
24
+ if (char === '{') {
25
+ currentLine += char;
26
+ lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
27
+ indentLevel++;
28
+ currentLine = '';
29
+ continue;
30
+ }
31
+
32
+ // Handle closing braces
33
+ if (char === '}') {
34
+ if (currentLine.trim()) {
35
+ lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
36
+ currentLine = '';
37
+ }
38
+ indentLevel--;
39
+ // Check if next char is closing paren - if so, put on same line
40
+ if (nextChar === ')') {
41
+ currentLine = '}';
42
+ } else {
43
+ lines.push(' '.repeat(indentLevel * indentSize) + char);
44
+ }
45
+ continue;
46
+ }
47
+
48
+ // Handle commas - add line break after
49
+ if (char === ',') {
50
+ currentLine += char;
51
+ lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
52
+ currentLine = '';
53
+ continue;
54
+ }
55
+
56
+ // Accumulate characters
57
+ currentLine += char;
58
+ }
59
+
60
+ // Add any remaining content
61
+ if (currentLine.trim()) {
62
+ lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
63
+ }
64
+
65
+ return lines.join('\n');
66
+ }
@@ -77,6 +77,20 @@ export const command = createCommand({
77
77
  logger: ctx.logger,
78
78
  });
79
79
 
80
+ // Copy profile-specific .env file AFTER bundling (bundler clears outDir first)
81
+ if (opts.dev && ctx.config?.name) {
82
+ const envSourcePath = join(absoluteProjectDir, `.env.${ctx.config.name}`);
83
+ const envDestPath = join(outDir, '.env');
84
+
85
+ const envFile = Bun.file(envSourcePath);
86
+ if (await envFile.exists()) {
87
+ await Bun.write(envDestPath, envFile);
88
+ ctx.logger.debug(`Copied ${envSourcePath} to ${envDestPath}`);
89
+ } else {
90
+ ctx.logger.debug(`No .env.${ctx.config.name} file found, skipping env copy`);
91
+ }
92
+ }
93
+
80
94
  // Run TypeScript type checking after registry generation (skip in dev mode)
81
95
  if (!opts.dev && !opts.skipTypeCheck) {
82
96
  try {