@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.
- package/bin/cli.ts +7 -5
- package/dist/banner.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cmd/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/whoami.d.ts +2 -0
- package/dist/cmd/auth/whoami.d.ts.map +1 -0
- package/dist/cmd/bundle/ast.d.ts +2 -0
- package/dist/cmd/bundle/ast.d.ts.map +1 -1
- package/dist/cmd/bundle/index.d.ts +1 -1
- package/dist/cmd/bundle/index.d.ts.map +1 -1
- package/dist/cmd/bundle/plugin.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts +2 -0
- package/dist/cmd/cloud/deploy.d.ts.map +1 -0
- package/dist/cmd/cloud/index.d.ts +2 -0
- package/dist/cmd/cloud/index.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/env/delete.d.ts +2 -0
- package/dist/cmd/env/delete.d.ts.map +1 -0
- package/dist/cmd/env/get.d.ts +2 -0
- package/dist/cmd/env/get.d.ts.map +1 -0
- package/dist/cmd/env/import.d.ts +2 -0
- package/dist/cmd/env/import.d.ts.map +1 -0
- package/dist/cmd/env/index.d.ts +2 -0
- package/dist/cmd/env/index.d.ts.map +1 -0
- package/dist/cmd/env/list.d.ts +2 -0
- package/dist/cmd/env/list.d.ts.map +1 -0
- package/dist/cmd/env/pull.d.ts +2 -0
- package/dist/cmd/env/pull.d.ts.map +1 -0
- package/dist/cmd/env/push.d.ts +2 -0
- package/dist/cmd/env/push.d.ts.map +1 -0
- package/dist/cmd/env/set.d.ts +2 -0
- package/dist/cmd/env/set.d.ts.map +1 -0
- package/dist/cmd/project/delete.d.ts.map +1 -1
- package/dist/cmd/project/download.d.ts +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/list.d.ts.map +1 -1
- package/dist/cmd/project/show.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/secret/delete.d.ts +2 -0
- package/dist/cmd/secret/delete.d.ts.map +1 -0
- package/dist/cmd/secret/get.d.ts +2 -0
- package/dist/cmd/secret/get.d.ts.map +1 -0
- package/dist/cmd/secret/import.d.ts +2 -0
- package/dist/cmd/secret/import.d.ts.map +1 -0
- package/dist/cmd/secret/index.d.ts +2 -0
- package/dist/cmd/secret/index.d.ts.map +1 -0
- package/dist/cmd/secret/list.d.ts +2 -0
- package/dist/cmd/secret/list.d.ts.map +1 -0
- package/dist/cmd/secret/pull.d.ts +2 -0
- package/dist/cmd/secret/pull.d.ts.map +1 -0
- package/dist/cmd/secret/push.d.ts +2 -0
- package/dist/cmd/secret/push.d.ts.map +1 -0
- package/dist/cmd/secret/set.d.ts +2 -0
- package/dist/cmd/secret/set.d.ts.map +1 -0
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/env-util.d.ts +67 -0
- package/dist/env-util.d.ts.map +1 -0
- package/dist/env-util.test.d.ts +2 -0
- package/dist/env-util.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/schema-parser.d.ts.map +1 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/tui.d.ts +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/types.d.ts +35 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/banner.ts +7 -2
- package/src/cli.ts +46 -5
- package/src/cmd/auth/index.ts +2 -1
- package/src/cmd/auth/login.ts +2 -4
- package/src/cmd/auth/whoami.ts +69 -0
- package/src/cmd/bundle/ast.ts +169 -4
- package/src/cmd/bundle/index.ts +2 -2
- package/src/cmd/bundle/plugin.ts +42 -1
- package/src/cmd/cloud/deploy.ts +129 -0
- package/src/cmd/cloud/index.ts +8 -0
- package/src/cmd/dev/index.ts +93 -9
- package/src/cmd/env/delete.ts +62 -0
- package/src/cmd/env/get.ts +66 -0
- package/src/cmd/env/import.ts +117 -0
- package/src/cmd/env/index.ts +22 -0
- package/src/cmd/env/list.ts +69 -0
- package/src/cmd/env/pull.ts +93 -0
- package/src/cmd/env/push.ts +55 -0
- package/src/cmd/env/set.ts +86 -0
- package/src/cmd/project/create.ts +1 -1
- package/src/cmd/project/delete.ts +43 -2
- package/src/cmd/project/download.ts +1 -1
- package/src/cmd/project/list.ts +33 -2
- package/src/cmd/project/show.ts +35 -3
- package/src/cmd/project/template-flow.ts +53 -12
- package/src/cmd/secret/delete.ts +55 -0
- package/src/cmd/secret/get.ts +67 -0
- package/src/cmd/secret/import.ts +79 -0
- package/src/cmd/secret/index.ts +22 -0
- package/src/cmd/secret/list.ts +69 -0
- package/src/cmd/secret/pull.ts +91 -0
- package/src/cmd/secret/push.ts +55 -0
- package/src/cmd/secret/set.ts +60 -0
- package/src/cmd/version/index.ts +2 -1
- package/src/config.ts +60 -7
- package/src/env-util.test.ts +194 -0
- package/src/env-util.ts +290 -0
- package/src/index.ts +5 -1
- package/src/schema-parser.ts +2 -3
- package/src/steps.ts +79 -4
- package/src/tui.ts +92 -56
- package/src/types.ts +30 -1
- package/dist/logger.d.ts +0 -24
- package/dist/logger.d.ts.map +0 -1
- 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(
|
|
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(
|
|
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(
|
|
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 {
|
package/src/cmd/auth/index.ts
CHANGED
|
@@ -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
|
});
|
package/src/cmd/auth/login.ts
CHANGED
|
@@ -16,10 +16,8 @@ export const loginCommand = createSubcommand({
|
|
|
16
16
|
const appUrl = getAppBaseURL(config);
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
|
|
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
|
+
});
|
package/src/cmd/bundle/ast.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/cmd/bundle/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
6
|
+
export const command = createCommand({
|
|
7
7
|
name: 'bundle',
|
|
8
8
|
description: 'Bundle Agentuity application for deployment',
|
|
9
9
|
aliases: ['build'],
|
package/src/cmd/bundle/plugin.ts
CHANGED
|
@@ -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 {
|
|
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
|
+
});
|