@agentuity/cli 0.0.72 → 0.0.74
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 +19 -5
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +13 -9
- package/dist/auth.js.map +1 -1
- package/dist/banner.js +1 -1
- package/dist/banner.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +79 -21
- package/dist/cli.js.map +1 -1
- package/dist/cmd/ai/prompt/api.d.ts.map +1 -1
- package/dist/cmd/ai/prompt/api.js +5 -4
- package/dist/cmd/ai/prompt/api.js.map +1 -1
- package/dist/cmd/auth/api.d.ts +2 -2
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/api.js +15 -14
- package/dist/cmd/auth/api.js.map +1 -1
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/login.js +37 -16
- package/dist/cmd/auth/login.js.map +1 -1
- package/dist/cmd/auth/ssh/api.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/api.js +3 -2
- package/dist/cmd/auth/ssh/api.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +76 -14
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/bundler.d.ts +3 -1
- package/dist/cmd/build/bundler.d.ts.map +1 -1
- package/dist/cmd/build/bundler.js +21 -9
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/format-schema.d.ts +6 -0
- package/dist/cmd/build/format-schema.d.ts.map +1 -0
- package/dist/cmd/build/format-schema.js +60 -0
- package/dist/cmd/build/format-schema.js.map +1 -0
- package/dist/cmd/build/index.d.ts.map +1 -1
- package/dist/cmd/build/index.js +13 -0
- package/dist/cmd/build/index.js.map +1 -1
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +123 -32
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/build/route-discovery.d.ts +50 -0
- package/dist/cmd/build/route-discovery.d.ts.map +1 -0
- package/dist/cmd/build/route-discovery.js +143 -0
- package/dist/cmd/build/route-discovery.js.map +1 -0
- package/dist/cmd/build/route-registry.d.ts.map +1 -1
- package/dist/cmd/build/route-registry.js +25 -10
- package/dist/cmd/build/route-registry.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +8 -6
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
- package/dist/cmd/cloud/deployment/show.js +34 -10
- package/dist/cmd/cloud/deployment/show.js.map +1 -1
- package/dist/cmd/dev/agents.d.ts.map +1 -1
- package/dist/cmd/dev/agents.js +2 -2
- package/dist/cmd/dev/agents.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +21 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/dev/sync.d.ts.map +1 -1
- package/dist/cmd/dev/sync.js +2 -2
- package/dist/cmd/dev/sync.js.map +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/download.js +16 -2
- package/dist/cmd/project/download.js.map +1 -1
- package/dist/cmd/project/list.d.ts.map +1 -1
- package/dist/cmd/project/list.js +2 -10
- package/dist/cmd/project/list.js.map +1 -1
- package/dist/cmd/project/show.d.ts.map +1 -1
- package/dist/cmd/project/show.js +8 -7
- package/dist/cmd/project/show.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +14 -2
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/steps.d.ts +20 -30
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +339 -184
- package/dist/steps.js.map +1 -1
- package/dist/tui/box.d.ts.map +1 -1
- package/dist/tui/box.js +8 -4
- package/dist/tui/box.js.map +1 -1
- package/dist/tui/prompt.d.ts.map +1 -1
- package/dist/tui/prompt.js +7 -2
- package/dist/tui/prompt.js.map +1 -1
- package/dist/tui.d.ts +20 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +90 -18
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/auth.ts +13 -10
- package/src/banner.ts +1 -1
- package/src/cli.ts +89 -27
- package/src/cmd/ai/prompt/api.ts +5 -4
- package/src/cmd/auth/api.ts +20 -22
- package/src/cmd/auth/login.ts +36 -17
- package/src/cmd/auth/ssh/api.ts +5 -9
- package/src/cmd/build/ast.ts +88 -14
- package/src/cmd/build/bundler.ts +32 -11
- package/src/cmd/build/format-schema.ts +66 -0
- package/src/cmd/build/index.ts +14 -0
- package/src/cmd/build/plugin.ts +146 -36
- package/src/cmd/build/route-discovery.ts +197 -0
- package/src/cmd/build/route-registry.ts +26 -10
- package/src/cmd/cloud/deploy.ts +19 -6
- package/src/cmd/cloud/deployment/show.ts +42 -10
- package/src/cmd/dev/agents.ts +2 -10
- package/src/cmd/dev/index.ts +25 -0
- package/src/cmd/dev/sync.ts +2 -12
- package/src/cmd/project/download.ts +16 -2
- package/src/cmd/project/list.ts +2 -9
- package/src/cmd/project/show.ts +8 -6
- package/src/cmd/project/template-flow.ts +21 -2
- package/src/config.ts +10 -0
- package/src/index.ts +2 -2
- package/src/steps.ts +397 -229
- package/src/tui/box.ts +8 -4
- package/src/tui/prompt.ts +7 -4
- package/src/tui.ts +125 -20
package/src/cmd/build/plugin.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { createLogger } from '@agentuity/server';
|
|
|
16
16
|
import type { LogLevel } from '../../types';
|
|
17
17
|
import { toCamelCase, toPascalCase } from '../../utils/string';
|
|
18
18
|
import { generateRouteRegistry, type RouteInfo } from './route-registry';
|
|
19
|
+
import { discoverRouteFiles } from './route-discovery';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Setup lifecycle types by analyzing app.ts for setup() function
|
|
@@ -371,10 +372,30 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
371
372
|
}
|
|
372
373
|
}
|
|
373
374
|
const webstatic = serveStatic({ root: import.meta.dir + '/web' });
|
|
375
|
+
// In dev mode, serve from source; in prod, serve from build output
|
|
376
|
+
const publicRoot = ${isDevMode} ? ${JSON.stringify(join(srcDir, 'web', 'public'))} : import.meta.dir + '/web/public';
|
|
377
|
+
const publicstatic = serveStatic({ root: publicRoot, rewriteRequestPath: (path) => path });
|
|
374
378
|
router.get('/', (c) => c.html(index));
|
|
375
379
|
router.get('/web/chunk/*', webstatic);
|
|
376
380
|
router.get('/web/asset/*', webstatic);
|
|
377
|
-
|
|
381
|
+
// Serve public assets at root (e.g., /favicon.ico) - must be last
|
|
382
|
+
router.get('/*', async (c, next) => {
|
|
383
|
+
const path = c.req.path;
|
|
384
|
+
// Prevent directory traversal attacks
|
|
385
|
+
if (path.includes('..') || path.includes('%2e%2e')) {
|
|
386
|
+
return c.notFound();
|
|
387
|
+
}
|
|
388
|
+
// Only serve from public folder at root (skip /web/* routes and /)
|
|
389
|
+
if (path !== '/' && !path.startsWith('/web/')) {
|
|
390
|
+
try {
|
|
391
|
+
// serveStatic calls next() internally if file not found
|
|
392
|
+
return await publicstatic(c, next);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
return next();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return next();
|
|
398
|
+
});
|
|
378
399
|
})();`);
|
|
379
400
|
}
|
|
380
401
|
|
|
@@ -415,7 +436,11 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
415
436
|
|
|
416
437
|
for (const subdir of subdirs) {
|
|
417
438
|
const fullPath = join(agentBaseDir, subdir);
|
|
418
|
-
if
|
|
439
|
+
// Check if this directory or any subdirectory contains agents
|
|
440
|
+
const hasAgentInTree = Array.from(agentDirs).some((agentDir) =>
|
|
441
|
+
agentDir.startsWith(fullPath)
|
|
442
|
+
);
|
|
443
|
+
if (!hasAgentInTree) {
|
|
419
444
|
throw new Error(
|
|
420
445
|
`Directory ${subdir} in src/agent must contain at least one agent (a file with a createAgent export)`
|
|
421
446
|
);
|
|
@@ -437,46 +462,116 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
437
462
|
const apiRoutesMetadata: BuildMetadata['routes'] = [];
|
|
438
463
|
const routeInfoList: RouteInfo[] = [];
|
|
439
464
|
const apiDir = join(srcDir, 'api');
|
|
465
|
+
// Track subdirectory routes for auto-mounting
|
|
466
|
+
const subRouteInserts: string[] = [];
|
|
440
467
|
if (existsSync(apiDir)) {
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
468
|
+
// Use recursive route discovery to find all route files in subdirectories
|
|
469
|
+
const discoveredRoutes = discoverRouteFiles(apiDir);
|
|
470
|
+
|
|
471
|
+
// Collect all API files: index.ts and discovered subdirectory routes
|
|
472
|
+
const apiFiles: string[] = [];
|
|
473
|
+
|
|
474
|
+
// Check for root index.ts
|
|
475
|
+
const indexFile = join(apiDir, 'index.ts');
|
|
476
|
+
if (existsSync(indexFile)) {
|
|
477
|
+
apiFiles.push(indexFile);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Add all discovered route files
|
|
481
|
+
for (const route of discoveredRoutes) {
|
|
482
|
+
apiFiles.push(route.filepath);
|
|
483
|
+
|
|
484
|
+
// Generate auto-mount code for this subdirectory route
|
|
485
|
+
subRouteInserts.push(`await (async() => {
|
|
486
|
+
const { getRouter } = await import('@agentuity/runtime');
|
|
487
|
+
const router = getRouter()!;
|
|
488
|
+
const ${route.variableName} = (await import('${route.importPath}')).default;
|
|
489
|
+
router.route('${route.mountPath}', ${route.variableName});
|
|
490
|
+
})();`);
|
|
491
|
+
}
|
|
445
492
|
|
|
446
493
|
for (const apiFile of apiFiles) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
filename: route.filename,
|
|
458
|
-
hasValidator: route.config?.hasValidator === true,
|
|
459
|
-
routeType: route.type || 'api',
|
|
460
|
-
agentVariable: route.config?.agentVariable as string | undefined,
|
|
461
|
-
agentImportPath: route.config?.agentImportPath as string | undefined,
|
|
462
|
-
inputSchemaVariable: route.config?.inputSchemaVariable as
|
|
463
|
-
| string
|
|
464
|
-
| undefined,
|
|
465
|
-
outputSchemaVariable: route.config?.outputSchemaVariable as
|
|
466
|
-
| string
|
|
467
|
-
| undefined,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
} catch (error) {
|
|
471
|
-
// Skip files that don't have createRouter (they might be utilities)
|
|
494
|
+
try {
|
|
495
|
+
const routes = await parseRoute(rootDir, apiFile, projectId, deploymentId);
|
|
496
|
+
|
|
497
|
+
// Extract schemas from agents for routes that use validators
|
|
498
|
+
for (const route of routes) {
|
|
499
|
+
// Check if route has custom schema overrides from validator({ input, output })
|
|
500
|
+
const hasCustomInput = route.config?.inputSchemaVariable;
|
|
501
|
+
const hasCustomOutput = route.config?.outputSchemaVariable;
|
|
502
|
+
|
|
503
|
+
// If route uses agent.validator(), get schemas from the agent (unless overridden)
|
|
472
504
|
if (
|
|
473
|
-
|
|
474
|
-
|
|
505
|
+
route.config?.agentImportPath &&
|
|
506
|
+
(!hasCustomInput || !hasCustomOutput)
|
|
475
507
|
) {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
508
|
+
const agentImportPath = route.config.agentImportPath as string;
|
|
509
|
+
// Match by import path: @agent/zod-test -> src/agent/zod-test/agent.ts
|
|
510
|
+
// Normalize import path by removing leading '@' -> agent/zod-test
|
|
511
|
+
const importPattern = agentImportPath.replace(/^@/, '');
|
|
512
|
+
// Escape regex special characters for safe pattern matching
|
|
513
|
+
const escapedPattern = importPattern.replace(
|
|
514
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
515
|
+
'\\$&'
|
|
516
|
+
);
|
|
517
|
+
// Match as complete path segment to avoid false positives (e.g., "agent/hello" matching "agent/hello-world")
|
|
518
|
+
const segmentPattern = new RegExp(`(^|/)${escapedPattern}(/|$)`);
|
|
519
|
+
|
|
520
|
+
for (const [, agentMd] of agentMetadata) {
|
|
521
|
+
const agentFilename = agentMd.get('filename');
|
|
522
|
+
if (agentFilename && segmentPattern.test(agentFilename)) {
|
|
523
|
+
// Use agent schemas unless overridden
|
|
524
|
+
const inputSchemaCode = hasCustomInput
|
|
525
|
+
? undefined
|
|
526
|
+
: agentMd.get('inputSchemaCode');
|
|
527
|
+
const outputSchemaCode = hasCustomOutput
|
|
528
|
+
? undefined
|
|
529
|
+
: agentMd.get('outputSchemaCode');
|
|
530
|
+
|
|
531
|
+
if (inputSchemaCode || outputSchemaCode) {
|
|
532
|
+
route.schema = {
|
|
533
|
+
input: inputSchemaCode,
|
|
534
|
+
output: outputSchemaCode,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
479
540
|
}
|
|
541
|
+
|
|
542
|
+
// TODO: Extract inline schema code from custom validator({ input: z.string(), output: ... })
|
|
543
|
+
// For now, custom schema overrides with inline code are not extracted (would require parsing the validator call's object expression)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
apiRoutesMetadata.push(...routes);
|
|
547
|
+
|
|
548
|
+
// Collect route info for RouteRegistry generation
|
|
549
|
+
for (const route of routes) {
|
|
550
|
+
routeInfoList.push({
|
|
551
|
+
method: route.method.toUpperCase(),
|
|
552
|
+
path: route.path,
|
|
553
|
+
filename: route.filename,
|
|
554
|
+
hasValidator: route.config?.hasValidator === true,
|
|
555
|
+
routeType: route.type || 'api',
|
|
556
|
+
agentVariable: route.config?.agentVariable as string | undefined,
|
|
557
|
+
agentImportPath: route.config?.agentImportPath as string | undefined,
|
|
558
|
+
inputSchemaVariable: route.config?.inputSchemaVariable as
|
|
559
|
+
| string
|
|
560
|
+
| undefined,
|
|
561
|
+
outputSchemaVariable: route.config?.outputSchemaVariable as
|
|
562
|
+
| string
|
|
563
|
+
| undefined,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
} catch (error) {
|
|
567
|
+
// Skip files that don't have createRouter (they might be utilities)
|
|
568
|
+
if (
|
|
569
|
+
error instanceof Error &&
|
|
570
|
+
error.message.includes('could not find an proper createRouter')
|
|
571
|
+
) {
|
|
572
|
+
logger.trace(`Skipping ${apiFile}: no createRouter found`);
|
|
573
|
+
} else {
|
|
574
|
+
throw error;
|
|
480
575
|
}
|
|
481
576
|
}
|
|
482
577
|
}
|
|
@@ -499,6 +594,11 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
499
594
|
})();`);
|
|
500
595
|
}
|
|
501
596
|
|
|
597
|
+
// Auto-mount subdirectory routes (src/api/foo/route.ts -> /api/foo)
|
|
598
|
+
for (const subRouteInsert of subRouteInserts) {
|
|
599
|
+
inserts.push(subRouteInsert);
|
|
600
|
+
}
|
|
601
|
+
|
|
502
602
|
// Only create the workbench routes if workbench is actually configured
|
|
503
603
|
if (workbenchConfig) {
|
|
504
604
|
inserts.push(`await (async() => {
|
|
@@ -594,6 +694,16 @@ await (async() => {
|
|
|
594
694
|
projectId,
|
|
595
695
|
};
|
|
596
696
|
|
|
697
|
+
// Extract schema codes if available
|
|
698
|
+
const inputSchemaCode = v.get('inputSchemaCode');
|
|
699
|
+
const outputSchemaCode = v.get('outputSchemaCode');
|
|
700
|
+
if (inputSchemaCode || outputSchemaCode) {
|
|
701
|
+
agentData.schema = {
|
|
702
|
+
input: inputSchemaCode,
|
|
703
|
+
output: outputSchemaCode,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
597
707
|
const evalsStr = v.get('evals');
|
|
598
708
|
if (evalsStr) {
|
|
599
709
|
logger.trace(
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { existsSync, readdirSync, lstatSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { toCamelCase } from '../../utils/string';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Information about a discovered route file
|
|
7
|
+
*/
|
|
8
|
+
export interface DiscoveredRouteFile {
|
|
9
|
+
/** Full path to the route file */
|
|
10
|
+
filepath: string;
|
|
11
|
+
/** Relative path from apiDir (e.g., 'auth/route.ts', 'v1/users/route.ts') */
|
|
12
|
+
relativePath: string;
|
|
13
|
+
/** Mount path for the route (e.g., '/api/auth', '/api/v1/users') */
|
|
14
|
+
mountPath: string;
|
|
15
|
+
/** Safe variable name for importing (e.g., 'authRoute', 'v1UsersRoute') */
|
|
16
|
+
variableName: string;
|
|
17
|
+
/** Import path relative to build output (e.g., './src/api/auth/route') */
|
|
18
|
+
importPath: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Recursively discover all TypeScript route files in an API directory
|
|
23
|
+
* Supports nested structures like:
|
|
24
|
+
* - src/api/index.ts (root API router)
|
|
25
|
+
* - src/api/auth/route.ts -> mounted at /api/auth
|
|
26
|
+
* - src/api/v1/users/route.ts -> mounted at /api/v1/users
|
|
27
|
+
* - src/api/admin/users/login.ts -> mounted at /api/admin/users (any .ts file works)
|
|
28
|
+
*
|
|
29
|
+
* @param apiDir - Absolute path to the src/api directory
|
|
30
|
+
* @param currentDir - Current directory being scanned (used for recursion)
|
|
31
|
+
* @param results - Accumulated results (used for recursion)
|
|
32
|
+
* @returns Array of discovered route files with mount information
|
|
33
|
+
*/
|
|
34
|
+
export function discoverRouteFiles(
|
|
35
|
+
apiDir: string,
|
|
36
|
+
currentDir: string = apiDir,
|
|
37
|
+
results: DiscoveredRouteFile[] = []
|
|
38
|
+
): DiscoveredRouteFile[] {
|
|
39
|
+
if (!existsSync(currentDir)) {
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const entries = readdirSync(currentDir);
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const entryPath = join(currentDir, entry);
|
|
47
|
+
const stat = lstatSync(entryPath);
|
|
48
|
+
|
|
49
|
+
// Skip symlinks to prevent infinite recursion
|
|
50
|
+
if (stat.isSymbolicLink()) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (stat.isFile() && entry.endsWith('.ts') && !entry.endsWith('.generated.ts')) {
|
|
55
|
+
// Found a TypeScript file
|
|
56
|
+
const relativePath = relative(apiDir, entryPath);
|
|
57
|
+
const isRootIndex = relativePath === 'index.ts';
|
|
58
|
+
|
|
59
|
+
// Skip root index.ts - it's handled separately
|
|
60
|
+
if (isRootIndex) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// For subdirectory files, determine mount path
|
|
65
|
+
// src/api/auth/route.ts -> /api/auth
|
|
66
|
+
// src/api/v1/users/route.ts -> /api/v1/users
|
|
67
|
+
// src/api/admin/login.ts -> /api/admin
|
|
68
|
+
const pathParts = relativePath.split('/');
|
|
69
|
+
pathParts.pop(); // Remove filename
|
|
70
|
+
|
|
71
|
+
// Skip files directly in src/api/ to avoid mount path conflicts with root index.ts
|
|
72
|
+
if (pathParts.length === 0) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const mountPath = `/api/${pathParts.join('/')}`;
|
|
77
|
+
|
|
78
|
+
// Generate safe variable name
|
|
79
|
+
// auth/route.ts -> authRoute
|
|
80
|
+
// v1/users/route.ts -> v1UsersRoute
|
|
81
|
+
// admin/login.ts -> adminLoginRoute
|
|
82
|
+
const variableParts = pathParts.map((p, idx) => {
|
|
83
|
+
const camel = toCamelCase(p);
|
|
84
|
+
// Capitalize first letter of all parts except the first
|
|
85
|
+
return idx === 0 ? camel : camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
86
|
+
});
|
|
87
|
+
const baseName = entry.replace('.ts', '');
|
|
88
|
+
if (baseName !== 'route' && baseName !== 'index') {
|
|
89
|
+
const camelBase = toCamelCase(baseName);
|
|
90
|
+
// Always capitalize the base name since it's not the first part
|
|
91
|
+
variableParts.push(camelBase.charAt(0).toUpperCase() + camelBase.slice(1));
|
|
92
|
+
}
|
|
93
|
+
const variableName = variableParts.join('') + 'Route';
|
|
94
|
+
|
|
95
|
+
// Generate import path relative to build output
|
|
96
|
+
// src/api/auth/route.ts -> ./src/api/auth/route
|
|
97
|
+
const importPath = './src/api/' + relativePath.replace('.ts', '');
|
|
98
|
+
|
|
99
|
+
results.push({
|
|
100
|
+
filepath: entryPath,
|
|
101
|
+
relativePath,
|
|
102
|
+
mountPath,
|
|
103
|
+
variableName,
|
|
104
|
+
importPath,
|
|
105
|
+
});
|
|
106
|
+
} else if (stat.isDirectory()) {
|
|
107
|
+
// Recursively scan subdirectories
|
|
108
|
+
discoverRouteFiles(apiDir, entryPath, results);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return results;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Detect potential route path conflicts
|
|
117
|
+
*
|
|
118
|
+
* @param routes - Array of route metadata with method and path
|
|
119
|
+
* @returns Array of conflict descriptions
|
|
120
|
+
*/
|
|
121
|
+
export interface RouteConflict {
|
|
122
|
+
type: 'duplicate' | 'ambiguous-param' | 'wildcard-overlap';
|
|
123
|
+
routes: Array<{ method: string; path: string; filename: string }>;
|
|
124
|
+
message: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function detectRouteConflicts(
|
|
128
|
+
routes: Array<{ method: string; path: string; filename: string }>
|
|
129
|
+
): RouteConflict[] {
|
|
130
|
+
const conflicts: RouteConflict[] = [];
|
|
131
|
+
|
|
132
|
+
// Group routes by method+path
|
|
133
|
+
const methodPathMap = new Map<string, Array<{ path: string; filename: string }>>();
|
|
134
|
+
|
|
135
|
+
for (const route of routes) {
|
|
136
|
+
const key = `${route.method.toUpperCase()} ${route.path}`;
|
|
137
|
+
if (!methodPathMap.has(key)) {
|
|
138
|
+
methodPathMap.set(key, []);
|
|
139
|
+
}
|
|
140
|
+
methodPathMap.get(key)!.push({ path: route.path, filename: route.filename });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for exact duplicates
|
|
144
|
+
for (const [methodPath, routeList] of methodPathMap.entries()) {
|
|
145
|
+
if (routeList.length > 1) {
|
|
146
|
+
const [method] = methodPath.split(' ', 2);
|
|
147
|
+
conflicts.push({
|
|
148
|
+
type: 'duplicate',
|
|
149
|
+
routes: routeList.map((r) => ({ method, path: r.path, filename: r.filename })),
|
|
150
|
+
message: `Duplicate route: ${methodPath} defined in ${routeList.length} files`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for ambiguous parameter routes (same method, different param names)
|
|
156
|
+
// e.g., GET /users/:id and GET /users/:userId
|
|
157
|
+
const methodGroups = new Map<string, Array<{ path: string; filename: string }>>();
|
|
158
|
+
for (const route of routes) {
|
|
159
|
+
const method = route.method.toUpperCase();
|
|
160
|
+
if (!methodGroups.has(method)) {
|
|
161
|
+
methodGroups.set(method, []);
|
|
162
|
+
}
|
|
163
|
+
methodGroups.get(method)!.push({ path: route.path, filename: route.filename });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const [method, routeList] of methodGroups.entries()) {
|
|
167
|
+
// Normalize params to check for conflicts
|
|
168
|
+
const normalized = routeList.map((r) => ({
|
|
169
|
+
...r,
|
|
170
|
+
normalizedPath: r.path.replace(/:[^/]+/g, ':param'),
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
const pathMap = new Map<string, Array<{ path: string; filename: string }>>();
|
|
174
|
+
for (const route of normalized) {
|
|
175
|
+
if (!pathMap.has(route.normalizedPath)) {
|
|
176
|
+
pathMap.set(route.normalizedPath, []);
|
|
177
|
+
}
|
|
178
|
+
pathMap.get(route.normalizedPath)!.push({ path: route.path, filename: route.filename });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const [normalizedPath, paths] of pathMap.entries()) {
|
|
182
|
+
if (paths.length > 1 && normalizedPath.includes(':param')) {
|
|
183
|
+
// Check if the actual param names differ
|
|
184
|
+
const uniquePaths = new Set(paths.map((p) => p.path));
|
|
185
|
+
if (uniquePaths.size > 1) {
|
|
186
|
+
conflicts.push({
|
|
187
|
+
type: 'ambiguous-param',
|
|
188
|
+
routes: paths.map(({ path, filename }) => ({ method, path, filename })),
|
|
189
|
+
message: `Ambiguous param routes: ${method} ${Array.from(uniquePaths).join(', ')}`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return conflicts;
|
|
197
|
+
}
|
|
@@ -37,13 +37,13 @@ export interface RouteInfo {
|
|
|
37
37
|
* @param routes - Array of route information
|
|
38
38
|
*/
|
|
39
39
|
export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void {
|
|
40
|
-
// Filter routes by type
|
|
41
|
-
const apiRoutes = routes.filter((r) => r.
|
|
42
|
-
const websocketRoutes = routes.filter((r) => r.
|
|
43
|
-
const sseRoutes = routes.filter((r) => r.
|
|
40
|
+
// Filter routes by type (include ALL routes, not just those with validators)
|
|
41
|
+
const apiRoutes = routes.filter((r) => r.routeType === 'api');
|
|
42
|
+
const websocketRoutes = routes.filter((r) => r.routeType === 'websocket');
|
|
43
|
+
const sseRoutes = routes.filter((r) => r.routeType === 'sse');
|
|
44
44
|
|
|
45
45
|
if (apiRoutes.length === 0 && websocketRoutes.length === 0 && sseRoutes.length === 0) {
|
|
46
|
-
// No
|
|
46
|
+
// No routes, skip generation
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -52,11 +52,15 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
|
|
|
52
52
|
const agentImports = new Map<string, string>(); // Maps agent variable to unique import name
|
|
53
53
|
const schemaImports = new Set<string>(); // Track which schema variables we've seen
|
|
54
54
|
|
|
55
|
-
// Combine all
|
|
56
|
-
const
|
|
55
|
+
// Combine all routes for import collection
|
|
56
|
+
const allRoutes = [...apiRoutes, ...websocketRoutes, ...sseRoutes];
|
|
57
57
|
|
|
58
|
-
// First pass: collect all unique agents and schema variables
|
|
59
|
-
|
|
58
|
+
// First pass: collect all unique agents and schema variables (only for routes with validators)
|
|
59
|
+
allRoutes.forEach((route) => {
|
|
60
|
+
// Skip routes without validators - they won't need imports
|
|
61
|
+
if (!route.hasValidator) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
60
64
|
// If this route uses an agent, import it directly
|
|
61
65
|
if (route.agentVariable && route.agentImportPath && !agentImports.has(route.agentVariable)) {
|
|
62
66
|
// Resolve the import path (could be @agent/hello, ../shared, etc.)
|
|
@@ -90,7 +94,11 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
|
|
|
90
94
|
|
|
91
95
|
// Import schema variables from route files
|
|
92
96
|
const routeFileImports = new Map<string, Set<string>>(); // Maps route file to schema variables
|
|
93
|
-
|
|
97
|
+
allRoutes.forEach((route) => {
|
|
98
|
+
// Only import schemas for routes with validators
|
|
99
|
+
if (!route.hasValidator) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
94
102
|
if (route.inputSchemaVariable || route.outputSchemaVariable) {
|
|
95
103
|
const filename = route.filename.replace(/\\/g, '/');
|
|
96
104
|
const importPath = `../${filename.replace(/\.ts$/, '')}`;
|
|
@@ -120,6 +128,14 @@ export function generateRouteRegistry(srcDir: string, routes: RouteInfo[]): void
|
|
|
120
128
|
const generateRouteEntry = (route: RouteInfo): string => {
|
|
121
129
|
const routeKey = route.path; // Use path only for websocket/sse, or METHOD path for API
|
|
122
130
|
|
|
131
|
+
// If route doesn't have a validator, use never for schemas
|
|
132
|
+
if (!route.hasValidator) {
|
|
133
|
+
return ` '${routeKey}': {
|
|
134
|
+
inputSchema: never;
|
|
135
|
+
outputSchema: never;
|
|
136
|
+
};`;
|
|
137
|
+
}
|
|
138
|
+
|
|
123
139
|
// If we have an agent variable, we can infer types from it
|
|
124
140
|
if (route.agentVariable) {
|
|
125
141
|
const importName = agentImports.get(route.agentVariable)!;
|
package/src/cmd/cloud/deploy.ts
CHANGED
|
@@ -6,7 +6,14 @@ import { tmpdir } from 'node:os';
|
|
|
6
6
|
import { createSubcommand } from '../../types';
|
|
7
7
|
import * as tui from '../../tui';
|
|
8
8
|
import { saveProjectDir, getDefaultConfigDir } from '../../config';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
runSteps,
|
|
11
|
+
stepSuccess,
|
|
12
|
+
stepSkipped,
|
|
13
|
+
stepError,
|
|
14
|
+
type Step,
|
|
15
|
+
type StepContext,
|
|
16
|
+
} from '../../steps';
|
|
10
17
|
import { bundle } from '../build/bundler';
|
|
11
18
|
import { loadBuildMetadata, getStreamURL } from '../../config';
|
|
12
19
|
import {
|
|
@@ -179,8 +186,9 @@ export const deploySubcommand = createSubcommand({
|
|
|
179
186
|
if (!deployment) {
|
|
180
187
|
return stepError('deployment was null');
|
|
181
188
|
}
|
|
189
|
+
let capturedOutput: string[] = [];
|
|
182
190
|
try {
|
|
183
|
-
await bundle({
|
|
191
|
+
const bundleResult = await bundle({
|
|
184
192
|
rootDir: resolve(projectDir),
|
|
185
193
|
dev: false,
|
|
186
194
|
deploymentId: deployment.id,
|
|
@@ -195,23 +203,28 @@ export const deploySubcommand = createSubcommand({
|
|
|
195
203
|
region: project.region,
|
|
196
204
|
logger: ctx.logger,
|
|
197
205
|
});
|
|
206
|
+
capturedOutput = bundleResult.output;
|
|
198
207
|
build = await loadBuildMetadata(join(projectDir, '.agentuity'));
|
|
199
208
|
instructions = await projectDeploymentUpdate(
|
|
200
209
|
apiClient,
|
|
201
210
|
deployment.id,
|
|
202
211
|
build
|
|
203
212
|
);
|
|
204
|
-
return stepSuccess();
|
|
213
|
+
return stepSuccess(capturedOutput.length > 0 ? capturedOutput : undefined);
|
|
205
214
|
} catch (ex) {
|
|
206
215
|
const _ex = ex as Error;
|
|
207
|
-
return stepError(
|
|
216
|
+
return stepError(
|
|
217
|
+
_ex.message ?? 'Error building your project',
|
|
218
|
+
_ex,
|
|
219
|
+
capturedOutput.length > 0 ? capturedOutput : undefined
|
|
220
|
+
);
|
|
208
221
|
}
|
|
209
222
|
},
|
|
210
223
|
},
|
|
211
224
|
{
|
|
212
|
-
type: 'progress',
|
|
213
225
|
label: 'Encrypt and Upload Deployment',
|
|
214
|
-
run: async (
|
|
226
|
+
run: async (stepCtx: StepContext) => {
|
|
227
|
+
const progress = stepCtx.progress;
|
|
215
228
|
if (!deployment) {
|
|
216
229
|
return stepError('deployment was null');
|
|
217
230
|
}
|
|
@@ -15,6 +15,10 @@ const DeploymentShowResponseSchema = z.object({
|
|
|
15
15
|
tags: z.array(z.string()).describe('Deployment tags'),
|
|
16
16
|
customDomains: z.array(z.string()).optional().describe('Custom domains'),
|
|
17
17
|
cloudRegion: z.string().optional().describe('Cloud region'),
|
|
18
|
+
resourceDb: z.string().nullable().optional().describe('the database name'),
|
|
19
|
+
resourceStorage: z.string().nullable().optional().describe('the storage name'),
|
|
20
|
+
deploymentLogsURL: z.string().nullable().optional().describe('the url to the deployment logs'),
|
|
21
|
+
buildLogsURL: z.string().nullable().optional().describe('the url to the build logs'),
|
|
18
22
|
metadata: z
|
|
19
23
|
.object({
|
|
20
24
|
git: z
|
|
@@ -87,27 +91,51 @@ export const showSubcommand = createSubcommand({
|
|
|
87
91
|
|
|
88
92
|
// Skip TUI output in JSON mode
|
|
89
93
|
if (!options.json) {
|
|
90
|
-
|
|
91
|
-
console.log(tui.bold('
|
|
92
|
-
console.log(tui.bold('
|
|
93
|
-
console.log(tui.bold('
|
|
94
|
-
console.log(tui.bold('
|
|
94
|
+
const maxWidth = 18;
|
|
95
|
+
console.log(tui.bold('ID:'.padEnd(maxWidth)) + deployment.id);
|
|
96
|
+
console.log(tui.bold('Project:'.padEnd(maxWidth)) + projectId);
|
|
97
|
+
console.log(tui.bold('State:'.padEnd(maxWidth)) + (deployment.state || 'unknown'));
|
|
98
|
+
console.log(tui.bold('Active:'.padEnd(maxWidth)) + (deployment.active ? 'Yes' : 'No'));
|
|
99
|
+
console.log(
|
|
100
|
+
tui.bold('Created:'.padEnd(maxWidth)) +
|
|
101
|
+
new Date(deployment.createdAt).toLocaleString()
|
|
102
|
+
);
|
|
95
103
|
if (deployment.updatedAt) {
|
|
96
104
|
console.log(
|
|
97
|
-
tui.bold('Updated:
|
|
105
|
+
tui.bold('Updated:'.padEnd(maxWidth)) +
|
|
106
|
+
new Date(deployment.updatedAt).toLocaleString()
|
|
98
107
|
);
|
|
99
108
|
}
|
|
100
109
|
if (deployment.message) {
|
|
101
|
-
console.log(tui.bold('Message:
|
|
110
|
+
console.log(tui.bold('Message:'.padEnd(maxWidth)) + deployment.message);
|
|
102
111
|
}
|
|
103
112
|
if (deployment.tags.length > 0) {
|
|
104
|
-
console.log(tui.bold('Tags:
|
|
113
|
+
console.log(tui.bold('Tags:'.padEnd(maxWidth)) + deployment.tags.join(', '));
|
|
105
114
|
}
|
|
106
115
|
if (deployment.customDomains && deployment.customDomains.length > 0) {
|
|
107
|
-
console.log(
|
|
116
|
+
console.log(
|
|
117
|
+
tui.bold('Domains:'.padEnd(maxWidth)) + deployment.customDomains.join(', ')
|
|
118
|
+
);
|
|
108
119
|
}
|
|
109
120
|
if (deployment.cloudRegion) {
|
|
110
|
-
console.log(tui.bold('Region:
|
|
121
|
+
console.log(tui.bold('Region:'.padEnd(maxWidth)) + deployment.cloudRegion);
|
|
122
|
+
}
|
|
123
|
+
if (deployment.resourceDb) {
|
|
124
|
+
console.log(tui.bold('Database:'.padEnd(maxWidth)) + deployment.resourceDb);
|
|
125
|
+
}
|
|
126
|
+
if (deployment.resourceStorage) {
|
|
127
|
+
console.log(tui.bold('Storage:'.padEnd(maxWidth)) + deployment.resourceStorage);
|
|
128
|
+
}
|
|
129
|
+
if (deployment.deploymentLogsURL) {
|
|
130
|
+
console.log(
|
|
131
|
+
tui.bold('Deployment Logs:'.padEnd(maxWidth)) +
|
|
132
|
+
tui.link(deployment.deploymentLogsURL)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (deployment.buildLogsURL) {
|
|
136
|
+
console.log(
|
|
137
|
+
tui.bold('Build Logs:'.padEnd(maxWidth)) + tui.link(deployment.buildLogsURL)
|
|
138
|
+
);
|
|
111
139
|
}
|
|
112
140
|
|
|
113
141
|
// Git metadata
|
|
@@ -153,6 +181,10 @@ export const showSubcommand = createSubcommand({
|
|
|
153
181
|
customDomains: deployment.customDomains ?? undefined,
|
|
154
182
|
cloudRegion: deployment.cloudRegion ?? undefined,
|
|
155
183
|
metadata: deployment.metadata ?? undefined,
|
|
184
|
+
resourceDb: deployment.resourceDb ?? undefined,
|
|
185
|
+
resourceStorage: deployment.resourceStorage ?? undefined,
|
|
186
|
+
deploymentLogsURL: deployment.deploymentLogsURL ?? undefined,
|
|
187
|
+
buildLogsURL: deployment.buildLogsURL ?? undefined,
|
|
156
188
|
};
|
|
157
189
|
} catch (ex) {
|
|
158
190
|
tui.fatal(`Failed to show deployment: ${ex}`);
|
package/src/cmd/dev/agents.ts
CHANGED
|
@@ -63,17 +63,9 @@ export const agentsSubcommand = createSubcommand({
|
|
|
63
63
|
const queryParams = deploymentId ? `?deploymentId=${deploymentId}` : '';
|
|
64
64
|
|
|
65
65
|
const response = options.json
|
|
66
|
-
? await apiClient.
|
|
67
|
-
'GET',
|
|
68
|
-
`/cli/agent/${projectId}${queryParams}`,
|
|
69
|
-
AgentsResponseSchema
|
|
70
|
-
)
|
|
66
|
+
? await apiClient.get(`/cli/agent/${projectId}${queryParams}`, AgentsResponseSchema)
|
|
71
67
|
: await tui.spinner('Fetching agents', async () => {
|
|
72
|
-
return apiClient.
|
|
73
|
-
'GET',
|
|
74
|
-
`/cli/agent/${projectId}${queryParams}`,
|
|
75
|
-
AgentsResponseSchema
|
|
76
|
-
);
|
|
68
|
+
return apiClient.get(`/cli/agent/${projectId}${queryParams}`, AgentsResponseSchema);
|
|
77
69
|
});
|
|
78
70
|
|
|
79
71
|
if (!response.success) {
|