@agentuity/cli 0.0.71 → 0.0.73

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 (132) hide show
  1. package/bin/cli.ts +19 -5
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +77 -19
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cmd/auth/api.d.ts +2 -2
  6. package/dist/cmd/auth/api.d.ts.map +1 -1
  7. package/dist/cmd/auth/api.js +15 -14
  8. package/dist/cmd/auth/api.js.map +1 -1
  9. package/dist/cmd/auth/login.d.ts.map +1 -1
  10. package/dist/cmd/auth/login.js +37 -16
  11. package/dist/cmd/auth/login.js.map +1 -1
  12. package/dist/cmd/auth/ssh/api.d.ts.map +1 -1
  13. package/dist/cmd/auth/ssh/api.js +3 -2
  14. package/dist/cmd/auth/ssh/api.js.map +1 -1
  15. package/dist/cmd/build/ast.d.ts.map +1 -1
  16. package/dist/cmd/build/ast.js +56 -8
  17. package/dist/cmd/build/ast.js.map +1 -1
  18. package/dist/cmd/build/bundler.d.ts +1 -2
  19. package/dist/cmd/build/bundler.d.ts.map +1 -1
  20. package/dist/cmd/build/bundler.js +30 -8
  21. package/dist/cmd/build/bundler.js.map +1 -1
  22. package/dist/cmd/build/format-schema.d.ts +6 -0
  23. package/dist/cmd/build/format-schema.d.ts.map +1 -0
  24. package/dist/cmd/build/format-schema.js +60 -0
  25. package/dist/cmd/build/format-schema.js.map +1 -0
  26. package/dist/cmd/build/index.d.ts.map +1 -1
  27. package/dist/cmd/build/index.js +13 -0
  28. package/dist/cmd/build/index.js.map +1 -1
  29. package/dist/cmd/build/plugin.d.ts.map +1 -1
  30. package/dist/cmd/build/plugin.js +72 -2
  31. package/dist/cmd/build/plugin.js.map +1 -1
  32. package/dist/cmd/cloud/db/create.js +2 -2
  33. package/dist/cmd/cloud/db/create.js.map +1 -1
  34. package/dist/cmd/cloud/db/delete.js +2 -2
  35. package/dist/cmd/cloud/db/delete.js.map +1 -1
  36. package/dist/cmd/cloud/db/get.js +2 -2
  37. package/dist/cmd/cloud/db/get.js.map +1 -1
  38. package/dist/cmd/cloud/db/list.js +2 -2
  39. package/dist/cmd/cloud/db/list.js.map +1 -1
  40. package/dist/cmd/cloud/db/logs.js +2 -2
  41. package/dist/cmd/cloud/db/logs.js.map +1 -1
  42. package/dist/cmd/cloud/db/sql.js +2 -2
  43. package/dist/cmd/cloud/db/sql.js.map +1 -1
  44. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  45. package/dist/cmd/cloud/deploy.js +163 -24
  46. package/dist/cmd/cloud/deploy.js.map +1 -1
  47. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  48. package/dist/cmd/cloud/deployment/show.js +34 -10
  49. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  50. package/dist/cmd/cloud/session/get.js +2 -2
  51. package/dist/cmd/cloud/session/get.js.map +1 -1
  52. package/dist/cmd/cloud/session/list.js +2 -2
  53. package/dist/cmd/cloud/session/list.js.map +1 -1
  54. package/dist/cmd/cloud/storage/create.js +2 -2
  55. package/dist/cmd/cloud/storage/create.js.map +1 -1
  56. package/dist/cmd/cloud/storage/delete.js +2 -2
  57. package/dist/cmd/cloud/storage/delete.js.map +1 -1
  58. package/dist/cmd/cloud/storage/download.js +2 -2
  59. package/dist/cmd/cloud/storage/download.js.map +1 -1
  60. package/dist/cmd/cloud/storage/get.js +2 -2
  61. package/dist/cmd/cloud/storage/get.js.map +1 -1
  62. package/dist/cmd/cloud/storage/list.js +2 -2
  63. package/dist/cmd/cloud/storage/list.js.map +1 -1
  64. package/dist/cmd/cloud/storage/upload.js +2 -2
  65. package/dist/cmd/cloud/storage/upload.js.map +1 -1
  66. package/dist/cmd/cloud/thread/delete.js +2 -2
  67. package/dist/cmd/cloud/thread/delete.js.map +1 -1
  68. package/dist/cmd/cloud/thread/get.js +2 -2
  69. package/dist/cmd/cloud/thread/get.js.map +1 -1
  70. package/dist/cmd/cloud/thread/list.js +2 -2
  71. package/dist/cmd/cloud/thread/list.js.map +1 -1
  72. package/dist/cmd/dev/agents.d.ts.map +1 -1
  73. package/dist/cmd/dev/agents.js +2 -2
  74. package/dist/cmd/dev/agents.js.map +1 -1
  75. package/dist/cmd/dev/sync.d.ts.map +1 -1
  76. package/dist/cmd/dev/sync.js +2 -2
  77. package/dist/cmd/dev/sync.js.map +1 -1
  78. package/dist/cmd/project/show.d.ts.map +1 -1
  79. package/dist/cmd/project/show.js +8 -7
  80. package/dist/cmd/project/show.js.map +1 -1
  81. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  82. package/dist/cmd/project/template-flow.js +14 -2
  83. package/dist/cmd/project/template-flow.js.map +1 -1
  84. package/dist/config.d.ts +2 -1
  85. package/dist/config.d.ts.map +1 -1
  86. package/dist/config.js +17 -1
  87. package/dist/config.js.map +1 -1
  88. package/dist/output.d.ts.map +1 -1
  89. package/dist/output.js +5 -1
  90. package/dist/output.js.map +1 -1
  91. package/dist/tui.d.ts +46 -1
  92. package/dist/tui.d.ts.map +1 -1
  93. package/dist/tui.js +217 -39
  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/cli.ts +85 -25
  99. package/src/cmd/auth/api.ts +20 -26
  100. package/src/cmd/auth/login.ts +36 -17
  101. package/src/cmd/auth/ssh/api.ts +5 -6
  102. package/src/cmd/build/ast.ts +67 -8
  103. package/src/cmd/build/bundler.ts +37 -9
  104. package/src/cmd/build/format-schema.ts +66 -0
  105. package/src/cmd/build/index.ts +14 -0
  106. package/src/cmd/build/plugin.ts +86 -2
  107. package/src/cmd/cloud/db/create.ts +2 -2
  108. package/src/cmd/cloud/db/delete.ts +2 -2
  109. package/src/cmd/cloud/db/get.ts +2 -2
  110. package/src/cmd/cloud/db/list.ts +2 -2
  111. package/src/cmd/cloud/db/logs.ts +2 -2
  112. package/src/cmd/cloud/db/sql.ts +2 -2
  113. package/src/cmd/cloud/deploy.ts +187 -24
  114. package/src/cmd/cloud/deployment/show.ts +42 -10
  115. package/src/cmd/cloud/session/get.ts +2 -2
  116. package/src/cmd/cloud/session/list.ts +2 -2
  117. package/src/cmd/cloud/storage/create.ts +2 -2
  118. package/src/cmd/cloud/storage/delete.ts +2 -2
  119. package/src/cmd/cloud/storage/download.ts +2 -2
  120. package/src/cmd/cloud/storage/get.ts +2 -2
  121. package/src/cmd/cloud/storage/list.ts +2 -2
  122. package/src/cmd/cloud/storage/upload.ts +2 -2
  123. package/src/cmd/cloud/thread/delete.ts +2 -2
  124. package/src/cmd/cloud/thread/get.ts +2 -2
  125. package/src/cmd/cloud/thread/list.ts +2 -2
  126. package/src/cmd/dev/agents.ts +2 -4
  127. package/src/cmd/dev/sync.ts +6 -8
  128. package/src/cmd/project/show.ts +8 -6
  129. package/src/cmd/project/template-flow.ts +21 -2
  130. package/src/config.ts +19 -6
  131. package/src/output.ts +7 -1
  132. package/src/tui.ts +302 -41
@@ -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,8 +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',
81
+ const resp = await apiClient.get(
84
82
  '/cli/auth/ssh-keys',
85
83
  APIResponseSchema(z.array(SSHKeySchema))
86
84
  );
@@ -95,6 +93,7 @@ export async function listSSHKeys(apiClient: APIClient): Promise<SSHKey[]> {
95
93
  const RemoveSSHKeysError = StructuredError('RemoveSSHKeysError');
96
94
 
97
95
  export async function removeSSHKey(apiClient: APIClient, fingerprint: string): Promise<boolean> {
96
+ // NOTE: Using .request() here because DELETE with body is required by the API
98
97
  const resp = await apiClient.request(
99
98
  'DELETE',
100
99
  '/cli/auth/ssh-keys',
@@ -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,6 +862,48 @@ 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,
@@ -1,7 +1,9 @@
1
- import { $ } from 'bun';
1
+ import { $, semver } from 'bun';
2
2
  import { join, relative, resolve, dirname, basename } from 'node:path';
3
3
  import { cpSync, existsSync, mkdirSync, rmSync, readdirSync } from 'node:fs';
4
4
  import gitParseUrl from 'git-url-parse';
5
+ import { StructuredError } from '@agentuity/core';
6
+ import * as tui from '../../tui';
5
7
  import AgentuityBundler, { getBuildMetadata } from './plugin';
6
8
  import { getFilesRecursively } from './file';
7
9
  import { getVersion } from '../../version';
@@ -10,11 +12,31 @@ import { fixDuplicateExportsInDirectory } from './fix-duplicate-exports';
10
12
  import type { Logger } from '../../types';
11
13
  import { generateWorkbenchMainTsx, generateWorkbenchIndexHtml } from './workbench-templates';
12
14
  import { analyzeWorkbench } from './ast';
13
- import { StructuredError } from '@agentuity/core';
14
- import { DeployOptionsSchema, type DeployOptions } from '../../schemas/deploy';
15
+ import { type DeployOptions } from '../../schemas/deploy';
15
16
 
16
- // Re-export for backward compatibility
17
- export { DeployOptionsSchema };
17
+ const minBunVersion = '>=1.3.3';
18
+
19
+ async function checkBunVersion() {
20
+ if (semver.satisfies(Bun.version, minBunVersion)) {
21
+ return;
22
+ }
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?');
27
+ if (ok) {
28
+ await $`bun upgrade`.quiet();
29
+ const version = (await $`bun -v`.quiet().text()).trim();
30
+ tui.success(`Bun upgraded to ${version}`);
31
+ return;
32
+ }
33
+ }
34
+ throw new InvalidBunVersion({
35
+ current: Bun.version,
36
+ required: minBunVersion,
37
+ message: `Please see https://bun.sh/ for information on how to download the latest version.`,
38
+ });
39
+ }
18
40
 
19
41
  export interface BundleOptions extends DeployOptions {
20
42
  rootDir: string;
@@ -36,6 +58,10 @@ type BuildLogs = BuildResult['logs'];
36
58
  const AppFileNotFoundError = StructuredError('AppFileNotFoundError');
37
59
  const AgentsDirNotFoundError = StructuredError('AgentsDirNotFoundError');
38
60
  const BuildFailedError = StructuredError('BuildFailedError')<{ logs?: BuildLogs }>();
61
+ const InvalidBunVersion = StructuredError('InvalidBunVersion')<{
62
+ current: string;
63
+ required: string;
64
+ }>();
39
65
 
40
66
  const handleBuildFailure = (buildResult: BuildResult) => {
41
67
  // Collect all build errors with full details
@@ -83,6 +109,9 @@ export async function bundle({
83
109
  message: `App file not found at expected location: ${appFile}`,
84
110
  });
85
111
  }
112
+
113
+ await checkBunVersion();
114
+
86
115
  const outDir = customOutDir ?? join(rootDir, '.agentuity');
87
116
  const srcDir = join(rootDir, 'src');
88
117
 
@@ -154,16 +183,13 @@ export async function bundle({
154
183
  // Common externals for native modules (same as legacy CLI)
155
184
  const commonExternals = ['bun', 'fsevents', 'chromium-bidi', 'sharp'];
156
185
 
157
- // OpenTelemetry packages need to be externalized due to CommonJS/ESM hybrid issues
158
- const otelExternals = ['@opentelemetry/*', '@traceloop/*'];
159
-
160
186
  // Allow projects to specify custom externals via package.json "externals" field
161
187
  const customExternals: string[] = [];
162
188
  if (pkgContents.externals && Array.isArray(pkgContents.externals)) {
163
189
  customExternals.push(...pkgContents.externals.filter((e: unknown) => typeof e === 'string'));
164
190
  }
165
191
 
166
- const externalPatterns = [...commonExternals, ...otelExternals, ...customExternals];
192
+ const externalPatterns = [...commonExternals, ...customExternals];
167
193
 
168
194
  // For production builds: install externals FIRST, then discover full dependency tree
169
195
  // This prevents bundling dependencies that will be in node_modules anyway
@@ -286,6 +312,8 @@ export async function bundle({
286
312
  id: projectId ?? '',
287
313
  name: pkgContents.name,
288
314
  version: pkgContents.version,
315
+ description: pkgContents.description,
316
+ keywords: pkgContents.keywords,
289
317
  orgId: orgId ?? '',
290
318
  };
291
319
  buildmetadata.deployment = {
@@ -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 {
@@ -371,10 +371,30 @@ import { readFileSync, existsSync } from 'node:fs';
371
371
  }
372
372
  }
373
373
  const webstatic = serveStatic({ root: import.meta.dir + '/web' });
374
+ // In dev mode, serve from source; in prod, serve from build output
375
+ const publicRoot = ${isDevMode} ? ${JSON.stringify(join(srcDir, 'web', 'public'))} : import.meta.dir + '/web/public';
376
+ const publicstatic = serveStatic({ root: publicRoot, rewriteRequestPath: (path) => path });
374
377
  router.get('/', (c) => c.html(index));
375
378
  router.get('/web/chunk/*', webstatic);
376
379
  router.get('/web/asset/*', webstatic);
377
- router.get('/public/*', webstatic);
380
+ // Serve public assets at root (e.g., /favicon.ico) - must be last
381
+ router.get('/*', async (c, next) => {
382
+ const path = c.req.path;
383
+ // Prevent directory traversal attacks
384
+ if (path.includes('..') || path.includes('%2e%2e')) {
385
+ return c.notFound();
386
+ }
387
+ // Only serve from public folder at root (skip /web/* routes and /)
388
+ if (path !== '/' && !path.startsWith('/web/')) {
389
+ try {
390
+ // serveStatic calls next() internally if file not found
391
+ return await publicstatic(c, next);
392
+ } catch (err) {
393
+ return next();
394
+ }
395
+ }
396
+ return next();
397
+ });
378
398
  })();`);
379
399
  }
380
400
 
@@ -415,7 +435,11 @@ import { readFileSync, existsSync } from 'node:fs';
415
435
 
416
436
  for (const subdir of subdirs) {
417
437
  const fullPath = join(agentBaseDir, subdir);
418
- if (!agentDirs.has(fullPath)) {
438
+ // Check if this directory or any subdirectory contains agents
439
+ const hasAgentInTree = Array.from(agentDirs).some((agentDir) =>
440
+ agentDir.startsWith(fullPath)
441
+ );
442
+ if (!hasAgentInTree) {
419
443
  throw new Error(
420
444
  `Directory ${subdir} in src/agent must contain at least one agent (a file with a createAgent export)`
421
445
  );
@@ -447,6 +471,56 @@ import { readFileSync, existsSync } from 'node:fs';
447
471
  if (statSync(apiFile).isFile()) {
448
472
  try {
449
473
  const routes = await parseRoute(rootDir, apiFile, projectId, deploymentId);
474
+
475
+ // Extract schemas from agents for routes that use validators
476
+ for (const route of routes) {
477
+ // Check if route has custom schema overrides from validator({ input, output })
478
+ const hasCustomInput = route.config?.inputSchemaVariable;
479
+ const hasCustomOutput = route.config?.outputSchemaVariable;
480
+
481
+ // If route uses agent.validator(), get schemas from the agent (unless overridden)
482
+ if (
483
+ route.config?.agentImportPath &&
484
+ (!hasCustomInput || !hasCustomOutput)
485
+ ) {
486
+ const agentImportPath = route.config.agentImportPath as string;
487
+ // Match by import path: @agent/zod-test -> src/agent/zod-test/agent.ts
488
+ // Normalize import path by removing leading '@' -> agent/zod-test
489
+ const importPattern = agentImportPath.replace(/^@/, '');
490
+ // Escape regex special characters for safe pattern matching
491
+ const escapedPattern = importPattern.replace(
492
+ /[.*+?^${}()|[\]\\]/g,
493
+ '\\$&'
494
+ );
495
+ // Match as complete path segment to avoid false positives (e.g., "agent/hello" matching "agent/hello-world")
496
+ const segmentPattern = new RegExp(`(^|/)${escapedPattern}(/|$)`);
497
+
498
+ for (const [, agentMd] of agentMetadata) {
499
+ const agentFilename = agentMd.get('filename');
500
+ if (agentFilename && segmentPattern.test(agentFilename)) {
501
+ // Use agent schemas unless overridden
502
+ const inputSchemaCode = hasCustomInput
503
+ ? undefined
504
+ : agentMd.get('inputSchemaCode');
505
+ const outputSchemaCode = hasCustomOutput
506
+ ? undefined
507
+ : agentMd.get('outputSchemaCode');
508
+
509
+ if (inputSchemaCode || outputSchemaCode) {
510
+ route.schema = {
511
+ input: inputSchemaCode,
512
+ output: outputSchemaCode,
513
+ };
514
+ }
515
+ break;
516
+ }
517
+ }
518
+ }
519
+
520
+ // TODO: Extract inline schema code from custom validator({ input: z.string(), output: ... })
521
+ // For now, custom schema overrides with inline code are not extracted (would require parsing the validator call's object expression)
522
+ }
523
+
450
524
  apiRoutesMetadata.push(...routes);
451
525
 
452
526
  // Collect route info for RouteRegistry generation
@@ -594,6 +668,16 @@ await (async() => {
594
668
  projectId,
595
669
  };
596
670
 
671
+ // Extract schema codes if available
672
+ const inputSchemaCode = v.get('inputSchemaCode');
673
+ const outputSchemaCode = v.get('outputSchemaCode');
674
+ if (inputSchemaCode || outputSchemaCode) {
675
+ agentData.schema = {
676
+ input: inputSchemaCode,
677
+ output: outputSchemaCode,
678
+ };
679
+ }
680
+
597
681
  const evalsStr = v.get('evals');
598
682
  if (evalsStr) {
599
683
  logger.trace(
@@ -31,7 +31,7 @@ export const createSubcommand = defineSubcommand({
31
31
  },
32
32
 
33
33
  async handler(ctx) {
34
- const { logger, opts, orgId, region, config, auth, options } = ctx;
34
+ const { logger, opts, orgId, region, auth, options } = ctx;
35
35
 
36
36
  // Handle dry-run mode
37
37
  if (isDryRunMode(options)) {
@@ -49,7 +49,7 @@ export const createSubcommand = defineSubcommand({
49
49
  };
50
50
  }
51
51
 
52
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
52
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
53
53
 
54
54
  try {
55
55
  const created = await tui.spinner({
@@ -35,9 +35,9 @@ export const deleteSubcommand = createSubcommand({
35
35
  },
36
36
 
37
37
  async handler(ctx) {
38
- const { logger, args, opts, config, orgId, region, auth, options } = ctx;
38
+ const { logger, args, opts, orgId, region, auth, options } = ctx;
39
39
 
40
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
40
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
41
41
 
42
42
  let dbName = args.name;
43
43
 
@@ -65,9 +65,9 @@ export const getSubcommand = createSubcommand({
65
65
  },
66
66
 
67
67
  async handler(ctx) {
68
- const { logger, args, opts, options, orgId, region, config, auth } = ctx;
68
+ const { logger, args, opts, options, orgId, region, auth } = ctx;
69
69
 
70
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
70
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
71
71
 
72
72
  const resources = await tui.spinner({
73
73
  message: `Fetching database ${args.name}`,
@@ -46,9 +46,9 @@ export const listSubcommand = createSubcommand({
46
46
  },
47
47
 
48
48
  async handler(ctx) {
49
- const { logger, opts, options, orgId, region, config, auth } = ctx;
49
+ const { logger, opts, options, orgId, region, auth } = ctx;
50
50
 
51
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
51
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
52
52
 
53
53
  const resources = await tui.spinner({
54
54
  message: `Fetching databases for ${orgId} in ${region}`,
@@ -78,14 +78,14 @@ export const logsSubcommand = createSubcommand({
78
78
  response: DbLogsResponseSchema,
79
79
  },
80
80
  async handler(ctx) {
81
- const { args, options, orgId, region, config, logger, auth } = ctx;
81
+ const { args, options, orgId, region, logger, auth } = ctx;
82
82
  const showTimestamps = ctx.opts.timestamps ?? true;
83
83
  const showSessionId = ctx.opts.showSessionId ?? false;
84
84
  const showUsername = ctx.opts.showUsername ?? false;
85
85
  const prettySQL = ctx.opts.pretty ?? false;
86
86
 
87
87
  try {
88
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
88
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
89
89
 
90
90
  const logs = await dbLogs(catalystClient, {
91
91
  database: args.database,
@@ -42,9 +42,9 @@ export const sqlSubcommand = createSubcommand({
42
42
  },
43
43
 
44
44
  async handler(ctx) {
45
- const { logger, args, options, orgId, region, config, auth } = ctx;
45
+ const { logger, args, options, orgId, region, auth } = ctx;
46
46
 
47
- const catalystClient = getCatalystAPIClient(config, logger, auth, region);
47
+ const catalystClient = getCatalystAPIClient(logger, auth, region);
48
48
 
49
49
  const result = await tui.spinner({
50
50
  message: `Executing query on ${args.name}`,