@agentuity/cli 0.0.35 → 0.0.42
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/AGENTS.md +2 -2
- package/README.md +4 -4
- package/dist/api.d.ts +6 -22
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts +0 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/banner.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/login.d.ts +1 -2
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/logout.d.ts +1 -2
- package/dist/cmd/auth/logout.d.ts.map +1 -1
- package/dist/cmd/auth/signup.d.ts +1 -2
- package/dist/cmd/auth/signup.d.ts.map +1 -1
- package/dist/cmd/bundle/ast.d.ts +2 -0
- package/dist/cmd/bundle/ast.d.ts.map +1 -1
- package/dist/cmd/bundle/bundler.d.ts +1 -0
- package/dist/cmd/bundle/bundler.d.ts.map +1 -1
- package/dist/cmd/bundle/patch/index.d.ts.map +1 -1
- package/dist/cmd/bundle/patch/llm.d.ts +3 -0
- package/dist/cmd/bundle/patch/llm.d.ts.map +1 -0
- package/dist/cmd/bundle/plugin.d.ts.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/delete.d.ts.map +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 +3 -0
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/config.d.ts +11 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/sound.d.ts.map +1 -1
- package/dist/tui.d.ts +16 -7
- package/dist/tui.d.ts.map +1 -1
- package/dist/types.d.ts +70 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/api.ts +27 -138
- package/src/auth.ts +87 -71
- package/src/banner.ts +7 -2
- package/src/cli.ts +7 -16
- package/src/cmd/auth/api.ts +40 -29
- package/src/cmd/auth/login.ts +7 -20
- package/src/cmd/auth/logout.ts +3 -3
- package/src/cmd/auth/signup.ts +6 -6
- package/src/cmd/bundle/ast.ts +169 -4
- package/src/cmd/bundle/bundler.ts +1 -0
- package/src/cmd/bundle/patch/index.ts +4 -0
- package/src/cmd/bundle/patch/llm.ts +36 -0
- package/src/cmd/bundle/plugin.ts +42 -1
- package/src/cmd/dev/index.ts +100 -1
- package/src/cmd/example/optional-auth.ts +1 -1
- package/src/cmd/index.ts +1 -0
- package/src/cmd/profile/README.md +1 -1
- package/src/cmd/project/create.ts +10 -2
- package/src/cmd/project/delete.ts +43 -2
- package/src/cmd/project/download.ts +17 -0
- package/src/cmd/project/list.ts +33 -2
- package/src/cmd/project/show.ts +35 -3
- package/src/cmd/project/template-flow.ts +60 -5
- package/src/config.ts +77 -5
- package/src/index.ts +2 -2
- package/src/logger.ts +1 -1
- package/src/sound.ts +9 -3
- package/src/tui.ts +234 -104
- package/src/types.ts +97 -34
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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generatePatches as aisdkGeneratePatches } from './aisdk';
|
|
2
|
+
import { generatePatches as llmGeneratePatches } from './llm';
|
|
2
3
|
import { type PatchModule, searchBackwards } from './_util';
|
|
3
4
|
|
|
4
5
|
export function generatePatches(): Map<string, PatchModule> {
|
|
@@ -6,6 +7,9 @@ export function generatePatches(): Map<string, PatchModule> {
|
|
|
6
7
|
for (const [name, patch] of aisdkGeneratePatches()) {
|
|
7
8
|
patches.set(name, patch);
|
|
8
9
|
}
|
|
10
|
+
for (const [name, patch] of llmGeneratePatches()) {
|
|
11
|
+
patches.set(name, patch);
|
|
12
|
+
}
|
|
9
13
|
return patches;
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type PatchModule, generateEnvGuard, generateGatewayEnvGuard } from './_util';
|
|
2
|
+
|
|
3
|
+
function registerLLMPatch(
|
|
4
|
+
patches: Map<string, PatchModule>,
|
|
5
|
+
module: string,
|
|
6
|
+
filename: string,
|
|
7
|
+
key: string,
|
|
8
|
+
baseurl: string,
|
|
9
|
+
name: string
|
|
10
|
+
) {
|
|
11
|
+
patches.set(module, {
|
|
12
|
+
module,
|
|
13
|
+
filename,
|
|
14
|
+
body: {
|
|
15
|
+
before: generateEnvGuard(
|
|
16
|
+
key,
|
|
17
|
+
generateGatewayEnvGuard(key, 'process.env.AGENTUITY_SDK_KEY', baseurl, name)
|
|
18
|
+
),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function generatePatches(): Map<string, PatchModule> {
|
|
24
|
+
const patches = new Map<string, PatchModule>();
|
|
25
|
+
registerLLMPatch(
|
|
26
|
+
patches,
|
|
27
|
+
'@anthropic-ai',
|
|
28
|
+
'index',
|
|
29
|
+
'ANTHROPIC_API_KEY',
|
|
30
|
+
'ANTHROPIC_BASE_URL',
|
|
31
|
+
'anthropic'
|
|
32
|
+
);
|
|
33
|
+
registerLLMPatch(patches, 'groq-sdk', 'index', 'GROQ_API_KEY', 'GROQ_BASE_URL', 'groq');
|
|
34
|
+
registerLLMPatch(patches, 'openai', 'index', 'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'openai');
|
|
35
|
+
return patches;
|
|
36
|
+
}
|
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',
|
package/src/cmd/dev/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { z } from 'zod';
|
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { bundle } from '../bundle/bundler';
|
|
5
5
|
import { existsSync, FSWatcher, watch } from 'node:fs';
|
|
6
|
+
import { loadBuildMetadata } from '../../config';
|
|
7
|
+
import type { BuildMetadata } from '../../types';
|
|
6
8
|
import * as tui from '../../tui';
|
|
7
9
|
|
|
8
10
|
export const command = createCommand({
|
|
@@ -38,6 +40,32 @@ export const command = createCommand({
|
|
|
38
40
|
process.exit(1);
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
const devmodebody =
|
|
44
|
+
tui.muted('Local: ') +
|
|
45
|
+
tui.link('http://127.0.0.1:3000') +
|
|
46
|
+
'\n\n' +
|
|
47
|
+
tui.muted('Press ') +
|
|
48
|
+
tui.bold('h') +
|
|
49
|
+
tui.muted(' for keyboard shortcuts');
|
|
50
|
+
|
|
51
|
+
function showBanner() {
|
|
52
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
53
|
+
padding: 2,
|
|
54
|
+
topSpacer: false,
|
|
55
|
+
bottomSpacer: false,
|
|
56
|
+
centerTitle: false,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
showBanner();
|
|
61
|
+
|
|
62
|
+
const env = { ...process.env };
|
|
63
|
+
env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
64
|
+
env.AGENTUITY_ENV = 'development';
|
|
65
|
+
env.NODE_ENV = 'development';
|
|
66
|
+
env.PORT = '3000';
|
|
67
|
+
env.AGENTUITY_PORT = env.PORT;
|
|
68
|
+
|
|
41
69
|
const agentuityDir = resolve(rootDir, '.agentuity');
|
|
42
70
|
const appPath = resolve(agentuityDir, 'app.js');
|
|
43
71
|
|
|
@@ -54,6 +82,7 @@ export const command = createCommand({
|
|
|
54
82
|
let shuttingDownForRestart = false;
|
|
55
83
|
let pendingRestart = false;
|
|
56
84
|
let building = false;
|
|
85
|
+
let metadata: BuildMetadata | undefined;
|
|
57
86
|
|
|
58
87
|
// Track restart timestamps to detect restart loops
|
|
59
88
|
const restartTimestamps: number[] = [];
|
|
@@ -200,6 +229,10 @@ export const command = createCommand({
|
|
|
200
229
|
return;
|
|
201
230
|
}
|
|
202
231
|
|
|
232
|
+
metadata = await loadBuildMetadata(agentuityDir);
|
|
233
|
+
|
|
234
|
+
env.AGENTUITY_LOG_LEVEL = logger.level;
|
|
235
|
+
|
|
203
236
|
logger.trace('Starting dev server: %s', appPath);
|
|
204
237
|
// Use shell to run in a process group for proper cleanup
|
|
205
238
|
// The 'exec' ensures the shell is replaced by the actual process
|
|
@@ -207,7 +240,8 @@ export const command = createCommand({
|
|
|
207
240
|
cwd: rootDir,
|
|
208
241
|
stdout: 'inherit',
|
|
209
242
|
stderr: 'inherit',
|
|
210
|
-
stdin: 'inherit',
|
|
243
|
+
stdin: process.stdin.isTTY ? 'ignore' : 'inherit', // Don't inherit stdin, we handle it ourselves
|
|
244
|
+
env,
|
|
211
245
|
});
|
|
212
246
|
|
|
213
247
|
running = true;
|
|
@@ -286,6 +320,71 @@ export const command = createCommand({
|
|
|
286
320
|
await restart();
|
|
287
321
|
logger.trace('Initial restart completed, setting up watchers');
|
|
288
322
|
|
|
323
|
+
// Setup keyboard shortcuts (only if we have a TTY)
|
|
324
|
+
if (process.stdin.isTTY) {
|
|
325
|
+
logger.trace('Setting up keyboard shortcuts');
|
|
326
|
+
process.stdin.setRawMode(true);
|
|
327
|
+
process.stdin.resume();
|
|
328
|
+
process.stdin.setEncoding('utf8');
|
|
329
|
+
|
|
330
|
+
const showHelp = () => {
|
|
331
|
+
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
332
|
+
console.log(tui.muted(' h') + ' - show this help');
|
|
333
|
+
console.log(tui.muted(' c') + ' - clear console');
|
|
334
|
+
console.log(tui.muted(' r') + ' - restart server');
|
|
335
|
+
console.log(tui.muted(' o') + ' - show routes');
|
|
336
|
+
console.log(tui.muted(' a') + ' - show agents');
|
|
337
|
+
console.log(tui.muted(' q') + ' - quit\n');
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const showRoutes = () => {
|
|
341
|
+
tui.info('API Route Detail');
|
|
342
|
+
console.table(metadata?.routes, ['method', 'path', 'filename']);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const showAgents = () => {
|
|
346
|
+
tui.info('Agent Detail');
|
|
347
|
+
console.table(metadata?.agents, ['name', 'filename', 'description']);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
process.stdin.on('data', (data) => {
|
|
351
|
+
const key = data.toString();
|
|
352
|
+
|
|
353
|
+
// Handle Ctrl+C
|
|
354
|
+
if (key === '\u0003') {
|
|
355
|
+
cleanup();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Handle other shortcuts
|
|
360
|
+
switch (key) {
|
|
361
|
+
case 'h':
|
|
362
|
+
showHelp();
|
|
363
|
+
break;
|
|
364
|
+
case 'c':
|
|
365
|
+
console.clear();
|
|
366
|
+
showBanner();
|
|
367
|
+
break;
|
|
368
|
+
case 'r':
|
|
369
|
+
tui.info('Manually restarting server...');
|
|
370
|
+
restart();
|
|
371
|
+
break;
|
|
372
|
+
case 'o':
|
|
373
|
+
showRoutes();
|
|
374
|
+
break;
|
|
375
|
+
case 'a':
|
|
376
|
+
showAgents();
|
|
377
|
+
break;
|
|
378
|
+
case 'q':
|
|
379
|
+
tui.info('Shutting down...');
|
|
380
|
+
cleanup();
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
logger.trace('✓ Keyboard shortcuts enabled');
|
|
386
|
+
}
|
|
387
|
+
|
|
289
388
|
// Patterns to ignore (generated files that change during build)
|
|
290
389
|
const ignorePatterns = [
|
|
291
390
|
/\.generated\.(js|ts|d\.ts)$/,
|
|
@@ -10,7 +10,7 @@ export const optionalAuthSubcommand: SubcommandDefinition = {
|
|
|
10
10
|
|
|
11
11
|
// Type guard to check if auth is present
|
|
12
12
|
const ctxWithAuth = ctx as CommandContext<true>;
|
|
13
|
-
if (
|
|
13
|
+
if (ctxWithAuth.auth) {
|
|
14
14
|
const auth = ctxWithAuth.auth as AuthData;
|
|
15
15
|
// User chose to authenticate
|
|
16
16
|
tui.success('You are authenticated!');
|
package/src/cmd/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
|
|
|
28
28
|
aliases: subcommand.aliases,
|
|
29
29
|
hidden: true,
|
|
30
30
|
requiresAuth: subcommand.requiresAuth,
|
|
31
|
+
optionalAuth: subcommand.optionalAuth,
|
|
31
32
|
schema: subcommand.schema,
|
|
32
33
|
handler: subcommand.handler,
|
|
33
34
|
};
|
|
@@ -77,4 +77,4 @@ The `name` field is extracted using the regex: `/\bname:\s+["']?([\w-_]+)["']?/`
|
|
|
77
77
|
- Profile files must have `.yaml` extension
|
|
78
78
|
- Profile names must match: `^[\w-_]{3,}$` (3+ chars, alphanumeric, dashes, underscores)
|
|
79
79
|
- The config loader (`loadConfig()`) automatically uses the active profile
|
|
80
|
-
- If no profile is selected or the file doesn't exist, falls back to `
|
|
80
|
+
- If no profile is selected or the file doesn't exist, falls back to `production.yaml`
|
|
@@ -7,7 +7,7 @@ export const createProjectSubcommand = createSubcommand({
|
|
|
7
7
|
description: 'Create a new project',
|
|
8
8
|
aliases: ['new'],
|
|
9
9
|
toplevel: true,
|
|
10
|
-
|
|
10
|
+
optionalAuth: true,
|
|
11
11
|
schema: {
|
|
12
12
|
options: z.object({
|
|
13
13
|
name: z.string().optional().describe('Project name'),
|
|
@@ -32,11 +32,17 @@ export const createProjectSubcommand = createSubcommand({
|
|
|
32
32
|
.default(true)
|
|
33
33
|
.describe('Run bun run build after installing (use --no-build to skip)'),
|
|
34
34
|
confirm: z.boolean().optional().describe('Skip confirmation prompts'),
|
|
35
|
+
register: z
|
|
36
|
+
.boolean()
|
|
37
|
+
.default(true)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Register the project, if authenticated (use --no-register to skip)'),
|
|
35
40
|
}),
|
|
36
41
|
},
|
|
37
42
|
|
|
38
43
|
async handler(ctx) {
|
|
39
|
-
const { logger, opts } = ctx;
|
|
44
|
+
const { logger, opts, auth, config } = ctx;
|
|
45
|
+
|
|
40
46
|
await runCreateFlow({
|
|
41
47
|
projectName: opts.name,
|
|
42
48
|
dir: opts.dir,
|
|
@@ -47,6 +53,8 @@ export const createProjectSubcommand = createSubcommand({
|
|
|
47
53
|
noBuild: opts.build === false,
|
|
48
54
|
skipPrompts: opts.confirm === true,
|
|
49
55
|
logger,
|
|
56
|
+
auth: opts.register === true ? auth : undefined,
|
|
57
|
+
config: config!,
|
|
50
58
|
});
|
|
51
59
|
},
|
|
52
60
|
});
|
|
@@ -1,13 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { createSubcommand } from '../../types';
|
|
3
|
+
import * as tui from '../../tui';
|
|
4
|
+
import { projectDelete } from '@agentuity/server';
|
|
5
|
+
import { getAPIBaseURL, APIClient } from '../../api';
|
|
2
6
|
|
|
3
7
|
export const deleteSubcommand = createSubcommand({
|
|
4
8
|
name: 'delete',
|
|
5
9
|
description: 'Delete a project',
|
|
6
10
|
aliases: ['rm', 'del'],
|
|
7
11
|
requiresAuth: true,
|
|
12
|
+
schema: {
|
|
13
|
+
args: z.object({
|
|
14
|
+
id: z.string().describe('the project id'),
|
|
15
|
+
}),
|
|
16
|
+
options: z.object({
|
|
17
|
+
confirm: z.boolean().optional().describe('Skip confirmation prompts'),
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
8
20
|
|
|
9
21
|
async handler(ctx) {
|
|
10
|
-
const {
|
|
11
|
-
|
|
22
|
+
const { args, opts, config } = ctx;
|
|
23
|
+
|
|
24
|
+
const apiUrl = getAPIBaseURL(config);
|
|
25
|
+
const client = new APIClient(apiUrl, config);
|
|
26
|
+
|
|
27
|
+
const skipConfirm = opts?.confirm === true;
|
|
28
|
+
|
|
29
|
+
if (!process.stdout.isTTY && !skipConfirm) {
|
|
30
|
+
tui.fatal('no TTY and --confirm is false');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!skipConfirm) {
|
|
34
|
+
const ok = await tui.confirm('Are you sure you want to delete', false);
|
|
35
|
+
if (!ok) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const deleted = await tui.spinner('Deleting project', async () => {
|
|
41
|
+
const val = await projectDelete(client!, args.id);
|
|
42
|
+
if (val.length === 1 && val[0] === args.id) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (deleted) {
|
|
49
|
+
tui.success(`Project ${args.id} deleted`);
|
|
50
|
+
} else {
|
|
51
|
+
tui.warning(`${args.id} not found`);
|
|
52
|
+
}
|
|
12
53
|
},
|
|
13
54
|
});
|
|
@@ -225,6 +225,23 @@ export async function setupProject(options: SetupOptions): Promise<void> {
|
|
|
225
225
|
clearOnSuccess: true,
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
// Configure git user in CI environments (where git config may not be set)
|
|
229
|
+
if (process.env.CI) {
|
|
230
|
+
await tui.runCommand({
|
|
231
|
+
command: 'git config user.email',
|
|
232
|
+
cwd: dest,
|
|
233
|
+
cmd: ['git', 'config', 'user.email', 'agentuity@example.com'],
|
|
234
|
+
clearOnSuccess: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await tui.runCommand({
|
|
238
|
+
command: 'git config user.name',
|
|
239
|
+
cwd: dest,
|
|
240
|
+
cmd: ['git', 'config', 'user.name', 'Agentuity'],
|
|
241
|
+
clearOnSuccess: true,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
228
245
|
// Add all files
|
|
229
246
|
await tui.runCommand({
|
|
230
247
|
command: 'git add .',
|
package/src/cmd/project/list.ts
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { createSubcommand } from '../../types';
|
|
3
|
+
import * as tui from '../../tui';
|
|
4
|
+
import { projectList } from '@agentuity/server';
|
|
5
|
+
import { getAPIBaseURL, APIClient } from '../../api';
|
|
2
6
|
|
|
3
7
|
export const listSubcommand = createSubcommand({
|
|
4
8
|
name: 'list',
|
|
5
9
|
description: 'List all projects',
|
|
6
10
|
aliases: ['ls'],
|
|
7
11
|
requiresAuth: true,
|
|
12
|
+
schema: {
|
|
13
|
+
options: z.object({
|
|
14
|
+
format: z
|
|
15
|
+
.enum(['json', 'table'])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('the output format: json, table (default)'),
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
8
20
|
|
|
9
21
|
async handler(ctx) {
|
|
10
|
-
const {
|
|
11
|
-
|
|
22
|
+
const { config, opts } = ctx;
|
|
23
|
+
|
|
24
|
+
const apiUrl = getAPIBaseURL(config);
|
|
25
|
+
const client = new APIClient(apiUrl, config);
|
|
26
|
+
|
|
27
|
+
const projects = await tui.spinner('Fetching projects', () => {
|
|
28
|
+
return projectList(client!);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// TODO: might want to sort by the last org_id we used
|
|
32
|
+
if (projects) {
|
|
33
|
+
projects.sort((a, b) => {
|
|
34
|
+
return a.name.localeCompare(b.name);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (opts?.format === 'json') {
|
|
39
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
40
|
+
} else {
|
|
41
|
+
console.table(projects, ['id', 'name', 'orgName']);
|
|
42
|
+
}
|
|
12
43
|
},
|
|
13
44
|
});
|