@agentuity/cli 0.0.55 → 0.0.56

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 (43) hide show
  1. package/dist/cmd/ai/schema/generate.d.ts +3 -0
  2. package/dist/cmd/ai/schema/generate.d.ts.map +1 -0
  3. package/dist/cmd/ai/schema/generate.js +50 -0
  4. package/dist/cmd/ai/schema/generate.js.map +1 -0
  5. package/dist/cmd/ai/schema/index.d.ts.map +1 -1
  6. package/dist/cmd/ai/schema/index.js +2 -1
  7. package/dist/cmd/ai/schema/index.js.map +1 -1
  8. package/dist/cmd/build/ast.d.ts.map +1 -1
  9. package/dist/cmd/build/ast.js +101 -81
  10. package/dist/cmd/build/ast.js.map +1 -1
  11. package/dist/cmd/build/ast.test.js +135 -370
  12. package/dist/cmd/build/ast.test.js.map +1 -1
  13. package/dist/cmd/build/bundler.d.ts.map +1 -1
  14. package/dist/cmd/build/bundler.js +8 -5
  15. package/dist/cmd/build/bundler.js.map +1 -1
  16. package/dist/cmd/build/plugin.d.ts.map +1 -1
  17. package/dist/cmd/build/plugin.js +9 -1
  18. package/dist/cmd/build/plugin.js.map +1 -1
  19. package/dist/cmd/cloud/session/get.d.ts.map +1 -1
  20. package/dist/cmd/cloud/session/get.js +77 -17
  21. package/dist/cmd/cloud/session/get.js.map +1 -1
  22. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  23. package/dist/cmd/project/template-flow.js +1 -0
  24. package/dist/cmd/project/template-flow.js.map +1 -1
  25. package/dist/config.d.ts +27 -3
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +31 -3
  28. package/dist/config.js.map +1 -1
  29. package/dist/types.d.ts +24 -2
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/types.js +4 -75
  32. package/dist/types.js.map +1 -1
  33. package/package.json +3 -3
  34. package/src/cmd/ai/schema/generate.ts +64 -0
  35. package/src/cmd/ai/schema/index.ts +2 -1
  36. package/src/cmd/build/ast.test.ts +157 -549
  37. package/src/cmd/build/ast.ts +115 -89
  38. package/src/cmd/build/bundler.ts +8 -5
  39. package/src/cmd/build/plugin.ts +10 -1
  40. package/src/cmd/cloud/session/get.ts +91 -19
  41. package/src/cmd/project/template-flow.ts +1 -0
  42. package/src/config.ts +44 -5
  43. package/src/types.ts +5 -84
@@ -790,111 +790,137 @@ export async function parseRoute(
790
790
  const routes: RouteDefinition = [];
791
791
  const routePrefix = filename.includes('src/agents') ? '/agent' : '/api';
792
792
 
793
- for (const body of ast.body) {
794
- if (body.type === 'ExpressionStatement') {
795
- const statement = body as ASTExpressionStatement;
796
- const callee = statement.expression.callee;
797
- if (callee.object.type === 'Identifier') {
798
- const identifier = callee.object as ASTNodeIdentifier;
799
- if (identifier.name === variableName) {
800
- let method = (callee.property as ASTNodeIdentifier).name;
801
- let type = 'api';
802
- const action = statement.expression.arguments[0];
803
- let suffix = '';
804
- let config: Record<string, unknown> | undefined;
805
- switch (method) {
806
- case 'get':
807
- case 'put':
808
- case 'post':
809
- case 'patch':
810
- case 'delete': {
811
- const theaction = action as ASTLiteral;
812
- if (theaction.type === 'Literal') {
813
- suffix = theaction.value;
793
+ try {
794
+ for (const body of ast.body) {
795
+ if (body.type === 'ExpressionStatement') {
796
+ const statement = body as ASTExpressionStatement;
797
+
798
+ // Validate that the expression is a call expression (e.g. function call)
799
+ if (statement.expression.type !== 'CallExpression') {
800
+ continue;
801
+ }
802
+
803
+ const callee = statement.expression.callee;
804
+
805
+ // Validate that the callee is a member expression (e.g. object.method())
806
+ // This handles cases like 'console.log()' or 'router.get()'
807
+ // direct function calls like 'myFunc()' have type 'Identifier' and will be skipped
808
+ if (callee.type !== 'MemberExpression') {
809
+ continue;
810
+ }
811
+
812
+ if (callee.object.type === 'Identifier' && statement.expression.arguments?.length > 0) {
813
+ const identifier = callee.object as ASTNodeIdentifier;
814
+ if (identifier.name === variableName) {
815
+ let method = (callee.property as ASTNodeIdentifier).name;
816
+ let type = 'api';
817
+ const action = statement.expression.arguments[0];
818
+ let suffix = '';
819
+ let config: Record<string, unknown> | undefined;
820
+ switch (method) {
821
+ case 'get':
822
+ case 'put':
823
+ case 'post':
824
+ case 'patch':
825
+ case 'delete': {
826
+ if (action && (action as ASTLiteral).type === 'Literal') {
827
+ suffix = (action as ASTLiteral).value;
828
+ } else {
829
+ throw new Error(
830
+ `unsupported HTTP method ${method} in ${filename} at line ${body.start}`
831
+ );
832
+ }
814
833
  break;
815
834
  }
816
- break;
817
- }
818
- case 'stream':
819
- case 'sse':
820
- case 'websocket': {
821
- type = method;
822
- method = 'post';
823
- const theaction = action as ASTLiteral;
824
- if (theaction.type === 'Literal') {
825
- suffix = theaction.value;
835
+ case 'stream':
836
+ case 'sse':
837
+ case 'websocket': {
838
+ type = method;
839
+ method = 'post';
840
+ const theaction = action as ASTLiteral;
841
+ if (theaction.type === 'Literal') {
842
+ suffix = theaction.value;
843
+ break;
844
+ }
826
845
  break;
827
846
  }
828
- break;
829
- }
830
- case 'sms': {
831
- type = method;
832
- method = 'post';
833
- const theaction = action as ASTObjectExpression;
834
- if (theaction.type === 'ObjectExpression') {
835
- config = {};
836
- theaction.properties.forEach((p) => {
837
- if (p.value.type === 'Literal') {
838
- const literal = p.value as ASTLiteral;
839
- config![p.key.name] = literal.value;
847
+ case 'sms': {
848
+ type = method;
849
+ method = 'post';
850
+ const theaction = action as ASTObjectExpression;
851
+ if (theaction.type === 'ObjectExpression') {
852
+ config = {};
853
+ theaction.properties.forEach((p) => {
854
+ if (p.value.type === 'Literal') {
855
+ const literal = p.value as ASTLiteral;
856
+ config![p.key.name] = literal.value;
857
+ }
858
+ });
859
+ const number = theaction.properties.find((p) => p.key.name === 'number');
860
+ if (number && number.value.type === 'Literal') {
861
+ const phoneNumber = number.value as ASTLiteral;
862
+ suffix = hash(phoneNumber.value);
863
+ break;
840
864
  }
841
- });
842
- const number = theaction.properties.find((p) => p.key.name === 'number');
843
- if (number && number.value.type === 'Literal') {
844
- const phoneNumber = number.value as ASTLiteral;
845
- suffix = hash(phoneNumber.value);
846
- break;
847
865
  }
866
+ break;
848
867
  }
849
- break;
850
- }
851
- case 'email': {
852
- type = method;
853
- method = 'post';
854
- const theaction = action as ASTLiteral;
855
- if (theaction.type === 'Literal') {
856
- const email = theaction.value;
857
- suffix = hash(email);
868
+ case 'email': {
869
+ type = method;
870
+ method = 'post';
871
+ const theaction = action as ASTLiteral;
872
+ if (theaction.type === 'Literal') {
873
+ const email = theaction.value;
874
+ suffix = hash(email);
875
+ break;
876
+ }
858
877
  break;
859
878
  }
860
- break;
861
- }
862
- case 'cron': {
863
- type = method;
864
- method = 'post';
865
- const theaction = action as ASTLiteral;
866
- if (theaction.type === 'Literal') {
867
- const number = theaction.value;
868
- suffix = hash(number);
879
+ case 'cron': {
880
+ type = method;
881
+ method = 'post';
882
+ const theaction = action as ASTLiteral;
883
+ if (theaction.type === 'Literal') {
884
+ const number = theaction.value;
885
+ suffix = hash(number);
886
+ break;
887
+ }
869
888
  break;
870
889
  }
871
- break;
890
+ default: {
891
+ throw new Error(
892
+ `unsupported router method ${method} in ${filename} at line ${body.start}`
893
+ );
894
+ }
872
895
  }
896
+ const thepath = `${routePrefix}/${routeName}/${suffix}`
897
+ .replaceAll(/\/{2,}/g, '/')
898
+ .replaceAll(/\/$/g, '');
899
+ const id = generateRouteId(
900
+ projectId,
901
+ deploymentId,
902
+ type,
903
+ method,
904
+ rel,
905
+ thepath,
906
+ version
907
+ );
908
+ routes.push({
909
+ id,
910
+ method: method as 'get' | 'post' | 'put' | 'delete' | 'patch',
911
+ type: type as 'api' | 'sms' | 'email' | 'cron',
912
+ filename: rel,
913
+ path: thepath,
914
+ version,
915
+ config,
916
+ });
873
917
  }
874
- const thepath = `${routePrefix}/${routeName}/${suffix}`
875
- .replaceAll(/\/{2,}/g, '/')
876
- .replaceAll(/\/$/g, '');
877
- const id = generateRouteId(
878
- projectId,
879
- deploymentId,
880
- type,
881
- method,
882
- rel,
883
- thepath,
884
- version
885
- );
886
- routes.push({
887
- id,
888
- method: method as 'get' | 'post' | 'put' | 'delete' | 'patch',
889
- type: type as 'api' | 'sms' | 'email' | 'cron',
890
- filename: rel,
891
- path: thepath,
892
- version,
893
- config,
894
- });
895
918
  }
896
919
  }
897
920
  }
921
+ } catch (error) {
922
+ const err = error instanceof Error ? error : new Error(String(error));
923
+ throw new Error(`Failed to parse route file ${filename}: ${err.message}`);
898
924
  }
899
925
  return routes;
900
926
  }
@@ -358,13 +358,16 @@ export async function bundle({
358
358
  .map((s) => s.trim())
359
359
  .filter(Boolean);
360
360
  }
361
- const branch = $`git branch --show-current`.nothrow().quiet();
362
- if (branch) {
363
- const _branch = await branch.text();
364
- if (_branch) {
365
- buildmetadata.deployment.git.branch = _branch.trim();
361
+ let branch = process.env.GITHUB_HEAD_REF;
362
+ if (!branch) {
363
+ const branchText = $`git branch --show-current`.nothrow().quiet();
364
+ if (branchText) {
365
+ branch = await branchText.text();
366
366
  }
367
367
  }
368
+ if (branch) {
369
+ buildmetadata.deployment.git.branch = branch.trim();
370
+ }
368
371
  const commit = $`git rev-parse HEAD`.nothrow().quiet();
369
372
  if (commit) {
370
373
  const sha = await commit.text();
@@ -493,7 +493,6 @@ const AgentuityBundler: BunPlugin = {
493
493
  projectId,
494
494
  deploymentId
495
495
  );
496
- routeDefinitions = [...routeDefinitions, ...definitions];
497
496
 
498
497
  let agentDetail: Record<string, string> = {};
499
498
 
@@ -504,6 +503,7 @@ const AgentuityBundler: BunPlugin = {
504
503
  }
505
504
  agentDetail = {
506
505
  name,
506
+ id: md.get('id')!,
507
507
  path: `.${agent}`,
508
508
  filename: md.get('filename')!,
509
509
  identifier: md.get('identifier')!,
@@ -514,8 +514,14 @@ const AgentuityBundler: BunPlugin = {
514
514
  agentDetail.parent = parentName;
515
515
  }
516
516
  agentInfo.push(agentDetail);
517
+ for (const def of definitions) {
518
+ def.agentIds = [agentDetail.agentId, agentDetail.id];
519
+ }
517
520
  }
518
521
 
522
+ // do this after handling the agent association (if any)
523
+ routeDefinitions = [...routeDefinitions, ...definitions];
524
+
519
525
  let buffer = `await (async() => {
520
526
  const { createAgentMiddleware, getRouter, registerAgent } = await import('@agentuity/runtime');
521
527
  const router = getRouter()!;
@@ -657,6 +663,9 @@ if (route !== '/workbench') {
657
663
  if (!v.has('name')) {
658
664
  throw new Error('agent metadata is missing expected name property');
659
665
  }
666
+ if (!v.has('agentId')) {
667
+ throw new Error('agent metadata is missing expected agentId property');
668
+ }
660
669
 
661
670
  const parentName = v.get('parent');
662
671
  if (parentName) {
@@ -1,11 +1,29 @@
1
1
  import { z } from 'zod';
2
2
  import { createSubcommand } from '../../../types';
3
3
  import * as tui from '../../../tui';
4
- import { sessionGet } from '@agentuity/server';
4
+ import { sessionGet, type SpanNode } from '@agentuity/server';
5
5
  import { getCommand } from '../../../command-prefix';
6
6
  import { ErrorCode } from '../../../errors';
7
7
  import { getCatalystAPIClient } from '../../../config';
8
8
 
9
+ const SpanNodeSchema: z.ZodType<SpanNode> = z.lazy(() =>
10
+ z.object({
11
+ id: z.string().describe('Span ID'),
12
+ duration: z.number().describe('Duration in milliseconds'),
13
+ operation: z.string().describe('Operation name'),
14
+ attributes: z.record(z.string(), z.unknown()).describe('Span attributes'),
15
+ children: z.array(SpanNodeSchema).optional().describe('Child spans'),
16
+ })
17
+ );
18
+
19
+ const RouteInfoSchema = z
20
+ .object({
21
+ id: z.string().describe('Route ID'),
22
+ method: z.string().describe('HTTP method'),
23
+ path: z.string().describe('Route path'),
24
+ })
25
+ .nullable();
26
+
9
27
  const SessionGetResponseSchema = z.object({
10
28
  id: z.string().describe('Session ID'),
11
29
  created_at: z.string().describe('Creation timestamp'),
@@ -26,8 +44,15 @@ const SessionGetResponseSchema = z.object({
26
44
  url: z.string().describe('Request URL'),
27
45
  route_id: z.string().describe('Route ID'),
28
46
  thread_id: z.string().describe('Thread ID'),
29
- agentNames: z.array(z.string()).describe('Agent names'),
30
- evalRuns: z
47
+ agents: z
48
+ .array(
49
+ z.object({
50
+ name: z.string(),
51
+ identifier: z.string(),
52
+ })
53
+ )
54
+ .describe('Agents'),
55
+ eval_runs: z
31
56
  .array(
32
57
  z.object({
33
58
  id: z.string(),
@@ -40,8 +65,41 @@ const SessionGetResponseSchema = z.object({
40
65
  })
41
66
  )
42
67
  .describe('Eval runs'),
68
+ timeline: SpanNodeSchema.nullable().optional().describe('Session timeline'),
69
+ route: RouteInfoSchema.optional().describe('Route information'),
43
70
  });
44
71
 
72
+ function formatDuration(ms: number): string {
73
+ if (ms < 1) {
74
+ return `${(ms * 1000).toFixed(0)}µs`;
75
+ }
76
+ if (ms < 1000) {
77
+ return `${ms.toFixed(1)}ms`;
78
+ }
79
+ return `${(ms / 1000).toFixed(2)}s`;
80
+ }
81
+
82
+ function printTimeline(node: SpanNode, prefix: string, isLast = true): void {
83
+ const connector = isLast ? '└── ' : '├── ';
84
+ const duration = tui.muted(`(${formatDuration(node.duration)})`);
85
+ let extra = '';
86
+ if (node.operation.startsWith('agentuity.')) {
87
+ if ('name' in node.attributes && 'key' in node.attributes) {
88
+ extra = tui.colorSuccess(`${node.attributes.name} ${node.attributes.key}`) + ' ';
89
+ }
90
+ }
91
+ if (node.operation.startsWith('HTTP ') && 'http.url' in node.attributes) {
92
+ extra = `${tui.colorSuccess(node.attributes['http.url'] as string)} `;
93
+ }
94
+ console.log(`${prefix}${connector}${node.operation} ${extra}${duration}`);
95
+
96
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
97
+ const children = node.children ?? [];
98
+ children.forEach((child, index) => {
99
+ printTimeline(child, childPrefix, index === children.length - 1);
100
+ });
101
+ }
102
+
45
103
  export const getSubcommand = createSubcommand({
46
104
  name: 'get',
47
105
  description: 'Get details about a specific session',
@@ -83,8 +141,8 @@ export const getSubcommand = createSubcommand({
83
141
  url: session.url,
84
142
  route_id: session.route_id,
85
143
  thread_id: session.thread_id,
86
- agentNames: enriched.agentNames,
87
- evalRuns: enriched.evalRuns.map((run) => ({
144
+ agents: enriched.agents,
145
+ eval_runs: enriched.evalRuns.map((run) => ({
88
146
  id: run.id,
89
147
  eval_id: run.eval_id,
90
148
  created_at: run.created_at,
@@ -93,6 +151,8 @@ export const getSubcommand = createSubcommand({
93
151
  error: run.error,
94
152
  result: run.result,
95
153
  })),
154
+ timeline: enriched.timeline,
155
+ route: enriched.route,
96
156
  };
97
157
 
98
158
  if (options.json) {
@@ -100,17 +160,14 @@ export const getSubcommand = createSubcommand({
100
160
  return result;
101
161
  }
102
162
 
103
- tui.banner(`Session ${session.id}`, `Status: ${session.success ? 'Success' : 'Failed'}`);
104
-
105
163
  console.log(tui.bold('ID: ') + session.id);
106
164
  console.log(tui.bold('Project: ') + session.project_id);
107
165
  console.log(tui.bold('Deployment: ') + (session.deployment_id || '-'));
108
- console.log(tui.bold('Created: ') + new Date(session.created_at).toLocaleString());
109
166
  console.log(tui.bold('Start: ') + new Date(session.start_time).toLocaleString());
110
167
  if (session.end_time) {
111
168
  console.log(tui.bold('End: ') + new Date(session.end_time).toLocaleString());
112
169
  }
113
- if (session.duration) {
170
+ if (session.duration && session.end_time) {
114
171
  console.log(
115
172
  tui.bold('Duration: ') + `${(session.duration / 1_000_000).toFixed(0)}ms`
116
173
  );
@@ -118,23 +175,32 @@ export const getSubcommand = createSubcommand({
118
175
  console.log(tui.bold('Method: ') + session.method);
119
176
  console.log(tui.bold('URL: ') + tui.link(session.url, session.url));
120
177
  console.log(tui.bold('Trigger: ') + session.trigger);
121
- console.log(tui.bold('Environment: ') + session.env);
178
+ if (session.env !== 'production') {
179
+ console.log(tui.bold('Environment: ') + session.env);
180
+ }
122
181
  console.log(tui.bold('Dev Mode: ') + (session.devmode ? 'Yes' : 'No'));
123
- console.log(tui.bold('Success: ') + (session.success ? '✓' : '✗'));
182
+ console.log(
183
+ tui.bold('Success: ') +
184
+ (session.success ? tui.colorSuccess('✓') : tui.colorError('✗'))
185
+ );
124
186
  console.log(tui.bold('Pending: ') + (session.pending ? 'Yes' : 'No'));
125
187
  if (session.error) {
126
188
  console.log(tui.bold('Error: ') + tui.error(session.error));
127
189
  }
128
- if (enriched.agentNames.length > 0) {
129
- const agentDisplay = enriched.agentNames
130
- .map((name, idx) => {
131
- const agentId = session.agent_ids[idx];
132
- return `${name} ${tui.muted(`(${agentId})`)}`;
133
- })
190
+ if (enriched.agents.length > 0) {
191
+ const agentDisplay = enriched.agents
192
+ .map((agent) => `${agent.name} ${tui.muted(`(${agent.identifier})`)}`)
134
193
  .join(', ');
135
194
  console.log(tui.bold('Agents: ') + agentDisplay);
136
195
  }
137
- console.log(tui.bold('Route ID: ') + session.route_id);
196
+ if (enriched.route) {
197
+ console.log(
198
+ tui.bold('Route: ') +
199
+ `${enriched.route.method.toUpperCase()} ${enriched.route.path} ${tui.muted(`(${enriched.route.id})`)}`
200
+ );
201
+ } else {
202
+ console.log(tui.bold('Route ID: ') + session.route_id);
203
+ }
138
204
  console.log(tui.bold('Thread ID: ') + session.thread_id);
139
205
 
140
206
  if (enriched.evalRuns.length > 0) {
@@ -143,7 +209,7 @@ export const getSubcommand = createSubcommand({
143
209
  const evalTableData = enriched.evalRuns.map((run) => ({
144
210
  ID: run.id,
145
211
  'Eval ID': run.eval_id,
146
- Success: run.success ? '✓' : '✗',
212
+ Success: run.success ? tui.colorSuccess('✓') : tui.colorError('✗'),
147
213
  Pending: run.pending ? '⏳' : '✓',
148
214
  Error: run.error || 'No',
149
215
  Created: new Date(run.created_at).toLocaleString(),
@@ -159,6 +225,12 @@ export const getSubcommand = createSubcommand({
159
225
  ]);
160
226
  }
161
227
 
228
+ if (result.timeline) {
229
+ console.log('');
230
+ console.log(tui.bold('Timeline:'));
231
+ printTimeline(result.timeline, '');
232
+ }
233
+
162
234
  return result;
163
235
  } catch (ex) {
164
236
  tui.fatal(
@@ -325,6 +325,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
325
325
  deployment: {
326
326
  resources: resourceConfig,
327
327
  },
328
+ region: region ?? 'usc',
328
329
  });
329
330
  },
330
331
  });
package/src/config.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
3
  import type { Logger } from '@agentuity/core';
4
4
  import {
5
5
  BuildMetadataSchema,
@@ -486,6 +486,7 @@ class ProjectConfigNotFoundExpection extends Error {
486
486
  type ProjectConfig = z.infer<typeof ProjectSchema>;
487
487
 
488
488
  function generateJSON5WithComments(
489
+ jsonSchema: string,
489
490
  schema: z.ZodObject<z.ZodRawShape>,
490
491
  data: Record<string, unknown>
491
492
  ): string {
@@ -493,6 +494,8 @@ function generateJSON5WithComments(
493
494
  const shape = schema.shape;
494
495
  const keys = Object.keys(shape);
495
496
 
497
+ lines.push(` "$schema": "${jsonSchema}",`);
498
+
496
499
  for (let i = 0; i < keys.length; i++) {
497
500
  const key = keys[i];
498
501
  const fieldSchema = shape[key] as z.ZodTypeAny;
@@ -551,23 +554,59 @@ export async function loadProjectConfig(
551
554
  return result.data;
552
555
  }
553
556
 
554
- type InitialProjectConfig = ProjectConfig & {
555
- sdkKey: string;
556
- };
557
+ export const InitialProjectConfigSchema = z.intersection(
558
+ ProjectSchema,
559
+ z.object({
560
+ sdkKey: z.string().describe('the project specific SDK key'),
561
+ $schema: z.string().optional(),
562
+ })
563
+ );
564
+
565
+ type InitialProjectConfig = z.infer<typeof InitialProjectConfigSchema>;
557
566
 
558
567
  export async function createProjectConfig(dir: string, config: InitialProjectConfig) {
559
568
  const { sdkKey, ...sanitizedConfig } = config;
560
569
 
570
+ // generate the project config
561
571
  const configPath = join(dir, 'agentuity.json');
562
- const json5Content = generateJSON5WithComments(ProjectSchema, sanitizedConfig);
572
+ const json5Content = generateJSON5WithComments(
573
+ 'https://agentuity.dev/schema/cli/v1/agentuity.json',
574
+ ProjectSchema,
575
+ sanitizedConfig
576
+ );
563
577
  await Bun.write(configPath, json5Content + '\n');
564
578
 
579
+ // generate the .env file with initial secret
565
580
  const envPath = join(dir, '.env');
566
581
  const comment =
567
582
  '# AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
568
583
  const content = `${comment}\nAGENTUITY_SDK_KEY=${sdkKey}\n`;
569
584
  await Bun.write(envPath, content);
570
585
  await chmod(envPath, 0o600);
586
+
587
+ // generate the vscode settings
588
+ const vscodeDir = join(dir, '.vscode');
589
+ mkdirSync(vscodeDir);
590
+
591
+ const settings = {
592
+ 'files.associations': {
593
+ 'agentuity.json': 'jsonc',
594
+ },
595
+ 'search.exclude': {
596
+ '**/.git/**': true,
597
+ '**/node_modules/**': true,
598
+ '**/bun.lock': true,
599
+ '**/.agentuity/**': true,
600
+ },
601
+ 'json.schemas': [
602
+ {
603
+ fileMatch: ['agentuity.json'],
604
+ url: 'https://agentuity.dev/schema/cli/v1/agentuity.json',
605
+ },
606
+ ],
607
+ };
608
+
609
+ await Bun.write(join(vscodeDir, 'settings.json'), JSON.stringify(settings, null, 2));
571
610
  }
572
611
 
573
612
  export async function loadBuildMetadata(dir: string): Promise<BuildMetadata> {