@agentuity/cli 0.0.41 → 0.0.43

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 (117) hide show
  1. package/bin/cli.ts +7 -5
  2. package/dist/banner.d.ts.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cmd/auth/index.d.ts.map +1 -1
  5. package/dist/cmd/auth/login.d.ts.map +1 -1
  6. package/dist/cmd/auth/whoami.d.ts +2 -0
  7. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  8. package/dist/cmd/bundle/ast.d.ts +2 -0
  9. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  10. package/dist/cmd/bundle/index.d.ts +1 -1
  11. package/dist/cmd/bundle/index.d.ts.map +1 -1
  12. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  13. package/dist/cmd/cloud/deploy.d.ts +2 -0
  14. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  15. package/dist/cmd/cloud/index.d.ts +2 -0
  16. package/dist/cmd/cloud/index.d.ts.map +1 -0
  17. package/dist/cmd/dev/index.d.ts.map +1 -1
  18. package/dist/cmd/env/delete.d.ts +2 -0
  19. package/dist/cmd/env/delete.d.ts.map +1 -0
  20. package/dist/cmd/env/get.d.ts +2 -0
  21. package/dist/cmd/env/get.d.ts.map +1 -0
  22. package/dist/cmd/env/import.d.ts +2 -0
  23. package/dist/cmd/env/import.d.ts.map +1 -0
  24. package/dist/cmd/env/index.d.ts +2 -0
  25. package/dist/cmd/env/index.d.ts.map +1 -0
  26. package/dist/cmd/env/list.d.ts +2 -0
  27. package/dist/cmd/env/list.d.ts.map +1 -0
  28. package/dist/cmd/env/pull.d.ts +2 -0
  29. package/dist/cmd/env/pull.d.ts.map +1 -0
  30. package/dist/cmd/env/push.d.ts +2 -0
  31. package/dist/cmd/env/push.d.ts.map +1 -0
  32. package/dist/cmd/env/set.d.ts +2 -0
  33. package/dist/cmd/env/set.d.ts.map +1 -0
  34. package/dist/cmd/project/delete.d.ts.map +1 -1
  35. package/dist/cmd/project/download.d.ts +1 -1
  36. package/dist/cmd/project/download.d.ts.map +1 -1
  37. package/dist/cmd/project/list.d.ts.map +1 -1
  38. package/dist/cmd/project/show.d.ts.map +1 -1
  39. package/dist/cmd/project/template-flow.d.ts +1 -1
  40. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  41. package/dist/cmd/secret/delete.d.ts +2 -0
  42. package/dist/cmd/secret/delete.d.ts.map +1 -0
  43. package/dist/cmd/secret/get.d.ts +2 -0
  44. package/dist/cmd/secret/get.d.ts.map +1 -0
  45. package/dist/cmd/secret/import.d.ts +2 -0
  46. package/dist/cmd/secret/import.d.ts.map +1 -0
  47. package/dist/cmd/secret/index.d.ts +2 -0
  48. package/dist/cmd/secret/index.d.ts.map +1 -0
  49. package/dist/cmd/secret/list.d.ts +2 -0
  50. package/dist/cmd/secret/list.d.ts.map +1 -0
  51. package/dist/cmd/secret/pull.d.ts +2 -0
  52. package/dist/cmd/secret/pull.d.ts.map +1 -0
  53. package/dist/cmd/secret/push.d.ts +2 -0
  54. package/dist/cmd/secret/push.d.ts.map +1 -0
  55. package/dist/cmd/secret/set.d.ts +2 -0
  56. package/dist/cmd/secret/set.d.ts.map +1 -0
  57. package/dist/cmd/version/index.d.ts.map +1 -1
  58. package/dist/config.d.ts +4 -1
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/env-util.d.ts +67 -0
  61. package/dist/env-util.d.ts.map +1 -0
  62. package/dist/env-util.test.d.ts +2 -0
  63. package/dist/env-util.test.d.ts.map +1 -0
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/schema-parser.d.ts.map +1 -1
  67. package/dist/steps.d.ts.map +1 -1
  68. package/dist/tui.d.ts +1 -1
  69. package/dist/tui.d.ts.map +1 -1
  70. package/dist/types.d.ts +35 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/src/banner.ts +7 -2
  74. package/src/cli.ts +46 -5
  75. package/src/cmd/auth/index.ts +2 -1
  76. package/src/cmd/auth/login.ts +2 -4
  77. package/src/cmd/auth/whoami.ts +69 -0
  78. package/src/cmd/bundle/ast.ts +169 -4
  79. package/src/cmd/bundle/index.ts +2 -2
  80. package/src/cmd/bundle/plugin.ts +42 -1
  81. package/src/cmd/cloud/deploy.ts +129 -0
  82. package/src/cmd/cloud/index.ts +8 -0
  83. package/src/cmd/dev/index.ts +93 -9
  84. package/src/cmd/env/delete.ts +62 -0
  85. package/src/cmd/env/get.ts +66 -0
  86. package/src/cmd/env/import.ts +117 -0
  87. package/src/cmd/env/index.ts +22 -0
  88. package/src/cmd/env/list.ts +69 -0
  89. package/src/cmd/env/pull.ts +93 -0
  90. package/src/cmd/env/push.ts +55 -0
  91. package/src/cmd/env/set.ts +86 -0
  92. package/src/cmd/project/create.ts +1 -1
  93. package/src/cmd/project/delete.ts +43 -2
  94. package/src/cmd/project/download.ts +1 -1
  95. package/src/cmd/project/list.ts +33 -2
  96. package/src/cmd/project/show.ts +35 -3
  97. package/src/cmd/project/template-flow.ts +53 -12
  98. package/src/cmd/secret/delete.ts +55 -0
  99. package/src/cmd/secret/get.ts +67 -0
  100. package/src/cmd/secret/import.ts +79 -0
  101. package/src/cmd/secret/index.ts +22 -0
  102. package/src/cmd/secret/list.ts +69 -0
  103. package/src/cmd/secret/pull.ts +91 -0
  104. package/src/cmd/secret/push.ts +55 -0
  105. package/src/cmd/secret/set.ts +60 -0
  106. package/src/cmd/version/index.ts +2 -1
  107. package/src/config.ts +60 -7
  108. package/src/env-util.test.ts +194 -0
  109. package/src/env-util.ts +290 -0
  110. package/src/index.ts +5 -1
  111. package/src/schema-parser.ts +2 -3
  112. package/src/steps.ts +79 -4
  113. package/src/tui.ts +92 -56
  114. package/src/types.ts +30 -1
  115. package/dist/logger.d.ts +0 -24
  116. package/dist/logger.d.ts.map +0 -1
  117. package/src/logger.ts +0 -235
package/src/cli.ts CHANGED
@@ -15,7 +15,7 @@ export async function createCLI(version: string): Promise<Command> {
15
15
 
16
16
  program
17
17
  .option('--config <path>', 'Config file path', '~/.config/agentuity/production.yaml')
18
- .option('--log-level <level>', 'Log level', 'info')
18
+ .option('--log-level <level>', 'Log level', process.env.AGENTUITY_LOG_LEVEL ?? 'info')
19
19
  .option('--log-timestamp', 'Show timestamps in log output', false)
20
20
  .option('--no-log-prefix', 'Hide log level prefixes', false)
21
21
  .option('--color-scheme <scheme>', 'Color scheme: light or dark');
@@ -69,9 +69,14 @@ async function registerSubcommand(
69
69
  if (opt.type === 'boolean') {
70
70
  // Support negatable boolean options (--no-flag) when they have a default
71
71
  if (opt.hasDefault) {
72
+ // Evaluate default value (could be a function)
73
+ const defaultValue =
74
+ typeof opt.defaultValue === 'function' ? opt.defaultValue() : opt.defaultValue;
72
75
  cmd.option(`--no-${flag}`, desc);
76
+ cmd.option(`--${flag}`, desc, defaultValue);
77
+ } else {
78
+ cmd.option(`--${flag}`, desc);
73
79
  }
74
- cmd.option(`--${flag}`, desc);
75
80
  } else if (opt.type === 'number') {
76
81
  cmd.option(`--${flag} <${opt.name}>`, desc, parseFloat);
77
82
  } else {
@@ -108,10 +113,22 @@ async function registerSubcommand(
108
113
  const issues = (error as { issues: Array<{ path: string[]; message: string }> })
109
114
  .issues;
110
115
  for (const issue of issues) {
111
- baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
116
+ baseCtx.logger.error(
117
+ ` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
118
+ );
112
119
  }
113
120
  process.exit(1);
114
121
  }
122
+ if (
123
+ error &&
124
+ typeof error === 'object' &&
125
+ 'name' in error &&
126
+ error.name === 'ProjectConfigNotFoundExpection'
127
+ ) {
128
+ baseCtx.logger.fatal(
129
+ 'invalid project folder. use --dir to specify a different directory or change to a project folder'
130
+ );
131
+ }
115
132
  throw error;
116
133
  }
117
134
  } else {
@@ -146,10 +163,22 @@ async function registerSubcommand(
146
163
  const issues = (error as { issues: Array<{ path: string[]; message: string }> })
147
164
  .issues;
148
165
  for (const issue of issues) {
149
- baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
166
+ baseCtx.logger.error(
167
+ ` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
168
+ );
150
169
  }
151
170
  process.exit(1);
152
171
  }
172
+ if (
173
+ error &&
174
+ typeof error === 'object' &&
175
+ 'name' in error &&
176
+ error.name === 'ProjectConfigNotFoundExpection'
177
+ ) {
178
+ baseCtx.logger.fatal(
179
+ 'invalid project folder. use --dir to specify a different directory or change to a project folder'
180
+ );
181
+ }
153
182
  throw error;
154
183
  }
155
184
  } else {
@@ -176,10 +205,22 @@ async function registerSubcommand(
176
205
  const issues = (error as { issues: Array<{ path: string[]; message: string }> })
177
206
  .issues;
178
207
  for (const issue of issues) {
179
- baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
208
+ baseCtx.logger.error(
209
+ ` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
210
+ );
180
211
  }
181
212
  process.exit(1);
182
213
  }
214
+ if (
215
+ error &&
216
+ typeof error === 'object' &&
217
+ 'name' in error &&
218
+ error.name === 'ProjectConfigNotFoundExpection'
219
+ ) {
220
+ baseCtx.logger.fatal(
221
+ 'invalid project folder. use --dir to specify a different directory or change to a project folder'
222
+ );
223
+ }
183
224
  throw error;
184
225
  }
185
226
  } else {
@@ -2,9 +2,10 @@ import { createCommand } from '../../types';
2
2
  import { loginCommand } from './login';
3
3
  import { logoutCommand } from './logout';
4
4
  import { signupCommand } from './signup';
5
+ import { whoamiCommand } from './whoami';
5
6
 
6
7
  export const command = createCommand({
7
8
  name: 'auth',
8
9
  description: 'Authentication and authorization related commands',
9
- subcommands: [loginCommand, logoutCommand, signupCommand],
10
+ subcommands: [loginCommand, logoutCommand, signupCommand, whoamiCommand],
10
11
  });
@@ -16,10 +16,8 @@ export const loginCommand = createSubcommand({
16
16
  const appUrl = getAppBaseURL(config);
17
17
 
18
18
  try {
19
- let otp: string | undefined;
20
-
21
- await tui.spinner('Generating login one time code...', async () => {
22
- otp = await generateLoginOTP(apiUrl, config);
19
+ const otp = await tui.spinner('Generating login one time code...', () => {
20
+ return generateLoginOTP(apiUrl, config);
23
21
  });
24
22
 
25
23
  if (!otp) {
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { whoami } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
6
+
7
+ export const whoamiCommand = createSubcommand({
8
+ name: 'whoami',
9
+ description: 'Display information about the currently authenticated user',
10
+ requiresAuth: true,
11
+ schema: {
12
+ options: z.object({
13
+ format: z
14
+ .enum(['json', 'table'])
15
+ .optional()
16
+ .describe('the output format: json, table (default)'),
17
+ }),
18
+ },
19
+
20
+ async handler(ctx) {
21
+ const { config, opts, auth } = ctx;
22
+
23
+ const apiUrl = getAPIBaseURL(config);
24
+ const client = new APIClient(apiUrl, config);
25
+
26
+ const result = await tui.spinner('Fetching user information', () => {
27
+ return whoami(client!);
28
+ });
29
+
30
+ if (!result.data) {
31
+ tui.fatal('Failed to get user information');
32
+ }
33
+
34
+ const user = result.data;
35
+
36
+ if (opts?.format === 'json') {
37
+ console.log(
38
+ JSON.stringify(
39
+ {
40
+ userId: auth?.userId,
41
+ firstName: user.firstName,
42
+ lastName: user.lastName,
43
+ organizations: user.organizations,
44
+ },
45
+ null,
46
+ 2
47
+ )
48
+ );
49
+ } else {
50
+ const fullName = `${user.firstName} ${user.lastName}`;
51
+
52
+ tui.newline();
53
+ console.log(tui.bold('Currently logged in as:'));
54
+ tui.newline();
55
+ console.log(` ${tui.padRight('Name:', 15, ' ')} ${tui.bold(fullName)}`);
56
+ console.log(` ${tui.padRight('User ID:', 15, ' ')} ${tui.muted(auth?.userId || '')}`);
57
+ tui.newline();
58
+
59
+ if (user.organizations.length > 0) {
60
+ console.log(tui.bold('Organizations:'));
61
+ tui.newline();
62
+ for (const org of user.organizations) {
63
+ console.log(` ${tui.padRight(org.name, 30, ' ')} ${tui.muted(org.id)}`);
64
+ }
65
+ }
66
+ tui.newline();
67
+ }
68
+ },
69
+ });
@@ -1,6 +1,7 @@
1
1
  import * as acornLoose from 'acorn-loose';
2
2
  import { basename, dirname, relative } from 'node:path';
3
3
  import { generate } from 'astring';
4
+ import { BuildMetadata } from '../../types';
4
5
 
5
6
  interface ASTNode {
6
7
  type: string;
@@ -12,9 +13,7 @@ interface ASTNodeIdentifier extends ASTNode {
12
13
 
13
14
  interface ASTCallExpression extends ASTNode {
14
15
  arguments: unknown[];
15
- callee: {
16
- name: string;
17
- };
16
+ callee: ASTMemberExpression;
18
17
  }
19
18
 
20
19
  interface ASTPropertyNode {
@@ -31,10 +30,22 @@ interface ASTObjectExpression extends ASTNode {
31
30
  properties: ASTPropertyNode[];
32
31
  }
33
32
 
34
- interface ASTLiteral {
33
+ interface ASTLiteral extends ASTNode {
35
34
  value: string;
36
35
  }
37
36
 
37
+ interface ASTMemberExpression extends ASTNode {
38
+ object: ASTNode;
39
+ property: ASTNode;
40
+ computed: boolean;
41
+ optional: boolean;
42
+ name?: string;
43
+ }
44
+
45
+ interface ASTExpressionStatement extends ASTNode {
46
+ expression: ASTCallExpression;
47
+ }
48
+
38
49
  function parseObjectExpressionToMap(expr: ASTObjectExpression): Map<string, string> {
39
50
  const result = new Map<string, string>();
40
51
  for (const prop of expr.properties) {
@@ -98,6 +109,10 @@ function getAgentId(identifier: string): string {
98
109
  return hash(projectId, identifier);
99
110
  }
100
111
 
112
+ function generateRouteId(method: string, path: string): string {
113
+ return hash(projectId, method, path);
114
+ }
115
+
101
116
  type AcornParseResultType = ReturnType<typeof acornLoose.parse>;
102
117
 
103
118
  function augmentAgentMetadataNode(
@@ -229,3 +244,153 @@ export function parseAgentMetadata(
229
244
  `error parsing: ${filename}. could not find an proper createAgent defined in this file`
230
245
  );
231
246
  }
247
+
248
+ type RouteDefinition = BuildMetadata['routes'];
249
+
250
+ export async function parseRoute(
251
+ rootDir: string,
252
+ filename: string
253
+ ): Promise<BuildMetadata['routes']> {
254
+ const contents = await Bun.file(filename).text();
255
+ const version = hash(contents);
256
+ const ast = acornLoose.parse(contents, { ecmaVersion: 'latest', sourceType: 'module' });
257
+ let exportName: string | undefined;
258
+ let variableName: string | undefined;
259
+ for (const body of ast.body) {
260
+ if (body.type === 'ExportDefaultDeclaration') {
261
+ const identifier = body.declaration as ASTNodeIdentifier;
262
+ exportName = identifier.name;
263
+ break;
264
+ }
265
+ }
266
+ if (!exportName) {
267
+ throw new Error(`could not find default export for ${filename} using ${rootDir}`);
268
+ }
269
+ for (const body of ast.body) {
270
+ if (body.type === 'VariableDeclaration') {
271
+ for (const vardecl of body.declarations) {
272
+ if (vardecl.type === 'VariableDeclarator' && vardecl.id.type === 'Identifier') {
273
+ const identifier = vardecl.id as ASTNodeIdentifier;
274
+ if (identifier.name === exportName) {
275
+ if (vardecl.init?.type === 'CallExpression') {
276
+ const call = vardecl.init as ASTCallExpression;
277
+ if (call.callee.name === 'createRouter') {
278
+ variableName = identifier.name;
279
+ break;
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ }
286
+ }
287
+ if (!variableName) {
288
+ throw new Error(
289
+ `error parsing: ${filename}. could not find an proper createRouter defined in this file`
290
+ );
291
+ }
292
+
293
+ const rel = relative(rootDir, filename);
294
+ const name = basename(dirname(filename));
295
+ const routes: RouteDefinition = [];
296
+ const routePrefix = filename.includes('src/agents') ? '/agent' : '/api';
297
+
298
+ for (const body of ast.body) {
299
+ if (body.type === 'ExpressionStatement') {
300
+ const statement = body as ASTExpressionStatement;
301
+ const callee = statement.expression.callee;
302
+ if (callee.object.type === 'Identifier') {
303
+ const identifier = callee.object as ASTNodeIdentifier;
304
+ if (identifier.name === variableName) {
305
+ let method = (callee.property as ASTNodeIdentifier).name;
306
+ let type = 'api';
307
+ const action = statement.expression.arguments[0];
308
+ let suffix = '';
309
+ let config: Record<string, unknown> | undefined;
310
+ switch (method) {
311
+ case 'get':
312
+ case 'put':
313
+ case 'post':
314
+ case 'patch':
315
+ case 'delete': {
316
+ const theaction = action as ASTLiteral;
317
+ if (theaction.type === 'Literal') {
318
+ suffix = theaction.value;
319
+ break;
320
+ }
321
+ break;
322
+ }
323
+ case 'stream':
324
+ case 'sse':
325
+ case 'websocket': {
326
+ type = method;
327
+ method = 'post';
328
+ const theaction = action as ASTLiteral;
329
+ if (theaction.type === 'Literal') {
330
+ suffix = theaction.value;
331
+ break;
332
+ }
333
+ break;
334
+ }
335
+ case 'sms': {
336
+ type = method;
337
+ method = 'post';
338
+ const theaction = action as ASTObjectExpression;
339
+ if (theaction.type === 'ObjectExpression') {
340
+ config = {};
341
+ theaction.properties.forEach((p) => {
342
+ if (p.value.type === 'Literal') {
343
+ const literal = p.value as ASTLiteral;
344
+ config![p.key.name] = literal.value;
345
+ }
346
+ });
347
+ const number = theaction.properties.find((p) => p.key.name === 'number');
348
+ if (number && number.value.type === 'Literal') {
349
+ const phoneNumber = number.value as ASTLiteral;
350
+ suffix = hash(phoneNumber.value);
351
+ break;
352
+ }
353
+ }
354
+ break;
355
+ }
356
+ case 'email': {
357
+ type = method;
358
+ method = 'post';
359
+ const theaction = action as ASTLiteral;
360
+ if (theaction.type === 'Literal') {
361
+ const email = theaction.value;
362
+ suffix = hash(email);
363
+ break;
364
+ }
365
+ break;
366
+ }
367
+ case 'cron': {
368
+ type = method;
369
+ method = 'post';
370
+ const theaction = action as ASTLiteral;
371
+ if (theaction.type === 'Literal') {
372
+ const number = theaction.value;
373
+ suffix = hash(number);
374
+ break;
375
+ }
376
+ break;
377
+ }
378
+ }
379
+ const thepath = `${routePrefix}/${name}/${suffix}`
380
+ .replaceAll(/\/{2,}/g, '/')
381
+ .replaceAll(/\/$/g, '');
382
+ routes.push({
383
+ id: generateRouteId(method, thepath),
384
+ method: method as 'get' | 'post' | 'put' | 'delete' | 'patch',
385
+ type: type as 'api' | 'sms' | 'email' | 'cron',
386
+ filename: rel,
387
+ path: thepath,
388
+ version,
389
+ config,
390
+ });
391
+ }
392
+ }
393
+ }
394
+ }
395
+ return routes;
396
+ }
@@ -1,9 +1,9 @@
1
- import { createSubcommand } from '../../types';
1
+ import { createCommand } from '../../types';
2
2
  import { z } from 'zod';
3
3
  import { resolve } from 'node:path';
4
4
  import { bundle } from './bundler';
5
5
 
6
- export const command = createSubcommand({
6
+ export const command = createCommand({
7
7
  name: 'bundle',
8
8
  description: 'Bundle Agentuity application for deployment',
9
9
  aliases: ['build'],
@@ -1,7 +1,8 @@
1
1
  import type { BunPlugin } from 'bun';
2
2
  import { dirname, basename, join } from 'node:path';
3
3
  import { existsSync, writeFileSync } from 'node:fs';
4
- import { parseAgentMetadata } from './ast';
4
+ import type { BuildMetadata } from '../../types';
5
+ import { parseAgentMetadata, parseRoute } from './ast';
5
6
  import { applyPatch, generatePatches } from './patch';
6
7
 
7
8
  function toCamelCase(str: string): string {
@@ -115,6 +116,7 @@ const AgentuityBundler: BunPlugin = {
115
116
  Map<string, string>
116
117
  >();
117
118
  const transpiler = new Bun.Transpiler({ loader: 'ts' });
119
+ let routeDefinitions: BuildMetadata['routes'] = [];
118
120
 
119
121
  build.onResolve({ filter: /\/route\.ts$/, namespace: 'file' }, async (args) => {
120
122
  if (args.path.startsWith(srcDir)) {
@@ -194,6 +196,9 @@ const AgentuityBundler: BunPlugin = {
194
196
  .replace('/agents', '/agent')
195
197
  .replace('./', '/');
196
198
 
199
+ const definitions = await parseRoute(rootDir, join(srcDir, route + '.ts'));
200
+ routeDefinitions = [...routeDefinitions, ...definitions];
201
+
197
202
  let agentDetail: Record<string, string> = {};
198
203
 
199
204
  if (hasAgent) {
@@ -264,6 +269,42 @@ const AgentuityBundler: BunPlugin = {
264
269
  contents += `\n${inserts.join('\n')}`;
265
270
  }
266
271
 
272
+ // generate the build metadata
273
+ const metadata: BuildMetadata = {
274
+ routes: routeDefinitions,
275
+ agents: [],
276
+ };
277
+ for (const [, v] of agentMetadata) {
278
+ if (!v.has('filename')) {
279
+ throw new Error('agent metadata is missing expected filename property');
280
+ }
281
+ if (!v.has('id')) {
282
+ throw new Error('agent metadata is missing expected id property');
283
+ }
284
+ if (!v.has('identifier')) {
285
+ throw new Error('agent metadata is missing expected identifier property');
286
+ }
287
+ if (!v.has('version')) {
288
+ throw new Error('agent metadata is missing expected version property');
289
+ }
290
+ if (!v.has('name')) {
291
+ throw new Error('agent metadata is missing expected name property');
292
+ }
293
+ metadata.agents.push({
294
+ filename: v.get('filename')!,
295
+ id: v.get('id')!,
296
+ identifier: v.get('identifier')!,
297
+ version: v.get('version')!,
298
+ name: v.get('name')!,
299
+ description: v.get('description') ?? '<no description provided>',
300
+ });
301
+ }
302
+
303
+ const metadataFilename = Bun.file(
304
+ join(build.config.outdir!, 'agentuity.metadata.json')
305
+ );
306
+ await metadataFilename.write(JSON.stringify(metadata));
307
+
267
308
  return {
268
309
  contents,
269
310
  loader: 'ts',
@@ -0,0 +1,129 @@
1
+ import { createSubcommand } from '../../types';
2
+ import { z } from 'zod';
3
+ import { join } from 'node:path';
4
+ import * as tui from '../../tui';
5
+ import { loadProjectConfig, saveProjectDir } from '../../config';
6
+ import { runSteps, stepSuccess, stepSkipped, stepError } from '../../steps';
7
+ import { bundle } from '../bundle/bundler';
8
+ import { loadBuildMetadata } from '../../config';
9
+ import { projectEnvUpdate } from '@agentuity/server';
10
+ import { getAPIBaseURL, APIClient } from '../../api';
11
+ import {
12
+ findEnvFile,
13
+ readEnvFile,
14
+ filterAgentuitySdkKeys,
15
+ splitEnvAndSecrets,
16
+ } from '../../env-util';
17
+
18
+ export const deploySubcommand = createSubcommand({
19
+ name: 'deploy',
20
+ description: 'Deploy project to the Agentuity Cloud',
21
+ toplevel: true,
22
+ requiresAuth: true,
23
+ schema: {
24
+ options: z.object({
25
+ dir: z.string().optional().describe('Directory to use for the project'),
26
+ }),
27
+ },
28
+
29
+ async handler(ctx) {
30
+ const { opts, config } = ctx;
31
+ const dir = opts?.dir ?? process.cwd();
32
+ try {
33
+ const project = await loadProjectConfig(dir);
34
+ if (!project) {
35
+ console.log(project); // FIXME
36
+ }
37
+ await saveProjectDir(dir);
38
+
39
+ const apiUrl = getAPIBaseURL(config);
40
+ const client = new APIClient(apiUrl, config);
41
+
42
+ await runSteps([
43
+ {
44
+ label: 'Sync Environment Variables',
45
+ run: async () => {
46
+ try {
47
+ // Read local env file (.env.production or .env)
48
+ const envFilePath = await findEnvFile(dir);
49
+ const localEnv = await readEnvFile(envFilePath);
50
+
51
+ // Filter out AGENTUITY_ keys
52
+ const filteredEnv = filterAgentuitySdkKeys(localEnv);
53
+
54
+ if (Object.keys(filteredEnv).length === 0) {
55
+ return stepSkipped('no environment variables to sync');
56
+ }
57
+
58
+ // Split into env and secrets
59
+ const { env, secrets } = splitEnvAndSecrets(filteredEnv);
60
+
61
+ // Push to cloud
62
+ await projectEnvUpdate(client, {
63
+ id: project.projectId,
64
+ env,
65
+ secrets,
66
+ });
67
+
68
+ return stepSuccess();
69
+ } catch (ex) {
70
+ // Non-fatal: log warning but continue deployment
71
+ const _ex = ex as Error;
72
+ return stepSkipped(_ex.message ?? 'failed to sync env variables');
73
+ }
74
+ },
75
+ },
76
+ {
77
+ label: 'Create Deployment',
78
+ run: async () => {
79
+ // TODO: implement
80
+ await Bun.sleep(1500);
81
+ return stepSuccess();
82
+ },
83
+ },
84
+ {
85
+ label: 'Build, Verify and Package',
86
+ run: async () => {
87
+ try {
88
+ await bundle({
89
+ rootDir: dir,
90
+ dev: false,
91
+ });
92
+ await loadBuildMetadata(join(dir, '.agentuity'));
93
+ return stepSuccess();
94
+ } catch (ex) {
95
+ const _ex = ex as Error;
96
+ return stepError(_ex.message ?? 'Error building your project');
97
+ }
98
+ },
99
+ },
100
+ {
101
+ label: 'Encrypt and Upload Deployment',
102
+ run: async () => {
103
+ // TODO: implement
104
+ await Bun.sleep(800);
105
+ return stepSkipped('already up to date');
106
+ },
107
+ },
108
+ {
109
+ label: 'Provision Services',
110
+ run: async () => {
111
+ // TODO: implement
112
+ await Bun.sleep(1200);
113
+ return stepSuccess();
114
+ },
115
+ },
116
+ ]);
117
+ tui.success('Your project was deployed!');
118
+ tui.arrow(tui.link('https://project-123455666332.agentuity.run'));
119
+ } catch (ex) {
120
+ const _ex = ex as Error;
121
+ if (_ex.name === 'ProjectConfigNotFoundExpection') {
122
+ tui.fatal(
123
+ `The directory ${dir} does not contain a valid Agentuity project. Missing agentuity.json`
124
+ );
125
+ }
126
+ tui.fatal(`unxpected error trying to deploy project. ${ex}`);
127
+ }
128
+ },
129
+ });
@@ -0,0 +1,8 @@
1
+ import { createCommand } from '../../types';
2
+ import { deploySubcommand } from './deploy';
3
+
4
+ export const command = createCommand({
5
+ name: 'cloud',
6
+ description: 'Cloud related commands',
7
+ subcommands: [deploySubcommand],
8
+ });