@agentuity/cli 0.0.87 → 0.0.89
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -6
- package/dist/cli.js.map +1 -1
- package/dist/cmd/build/bundler.d.ts.map +1 -1
- package/dist/cmd/build/bundler.js +142 -7
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/config-loader.d.ts +16 -0
- package/dist/cmd/build/config-loader.d.ts.map +1 -0
- package/dist/cmd/build/config-loader.js +165 -0
- package/dist/cmd/build/config-loader.js.map +1 -0
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +16 -2
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/build/workbench.d.ts +1 -0
- package/dist/cmd/build/workbench.d.ts.map +1 -1
- package/dist/cmd/build/workbench.js +8 -1
- package/dist/cmd/build/workbench.js.map +1 -1
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +2 -0
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/cloud/redis/get.d.ts +2 -0
- package/dist/cmd/cloud/redis/get.d.ts.map +1 -0
- package/dist/cmd/cloud/redis/get.js +62 -0
- package/dist/cmd/cloud/redis/get.js.map +1 -0
- package/dist/cmd/cloud/redis/index.d.ts +2 -0
- package/dist/cmd/cloud/redis/index.d.ts.map +1 -0
- package/dist/cmd/cloud/redis/index.js +13 -0
- package/dist/cmd/cloud/redis/index.js.map +1 -0
- package/dist/cmd/upgrade/index.js +1 -1
- package/dist/cmd/upgrade/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tui.d.ts +5 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +7 -0
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -4
- package/src/cli.ts +12 -6
- package/src/cmd/build/bundler.ts +169 -8
- package/src/cmd/build/config-loader.ts +200 -0
- package/src/cmd/build/plugin.ts +18 -2
- package/src/cmd/build/workbench.ts +9 -1
- package/src/cmd/cloud/index.ts +2 -0
- package/src/cmd/cloud/redis/get.ts +72 -0
- package/src/cmd/cloud/redis/index.ts +13 -0
- package/src/cmd/upgrade/index.ts +1 -1
- package/src/index.ts +4 -0
- package/src/tui.ts +8 -0
- package/src/types.ts +73 -0
package/src/cmd/build/bundler.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $, semver } from 'bun';
|
|
2
2
|
import { join, relative, resolve, dirname, basename } from 'node:path';
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, rmSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, rmSync, readdirSync, readFileSync } from 'node:fs';
|
|
4
4
|
import gitParseUrl from 'git-url-parse';
|
|
5
5
|
import { StructuredError } from '@agentuity/core';
|
|
6
6
|
import * as tui from '../../tui';
|
|
@@ -10,11 +10,12 @@ import { getFilesRecursively } from './file';
|
|
|
10
10
|
import { getVersion } from '../../version';
|
|
11
11
|
import type { Project } from '../../types';
|
|
12
12
|
import { fixDuplicateExportsInDirectory } from './fix-duplicate-exports';
|
|
13
|
-
import type { Logger } from '../../types';
|
|
13
|
+
import type { Logger, BuildContext } from '../../types';
|
|
14
14
|
import { generateWorkbenchMainTsx, generateWorkbenchIndexHtml } from './workbench';
|
|
15
15
|
import { analyzeWorkbench, type WorkbenchAnalysis } from './ast';
|
|
16
16
|
import { type DeployOptions } from '../../schemas/deploy';
|
|
17
17
|
import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
|
|
18
|
+
import { loadBuildConfig, executeBuildConfig, mergeBuildConfig } from './config-loader';
|
|
18
19
|
|
|
19
20
|
const minBunVersion = '>=1.3.3';
|
|
20
21
|
|
|
@@ -79,6 +80,77 @@ const InvalidBunVersion = StructuredError('InvalidBunVersion')<{
|
|
|
79
80
|
required: string;
|
|
80
81
|
}>();
|
|
81
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Finds the workspace root by walking up the directory tree looking for a package.json with workspaces
|
|
85
|
+
*/
|
|
86
|
+
function findWorkspaceRoot(startDir: string): string | null {
|
|
87
|
+
let currentDir = startDir;
|
|
88
|
+
while (true) {
|
|
89
|
+
const pkgPath = join(currentDir, 'package.json');
|
|
90
|
+
if (existsSync(pkgPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
93
|
+
if (pkg.workspaces) {
|
|
94
|
+
return currentDir;
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// Ignore parse errors, continue searching
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const parent = resolve(currentDir, '..');
|
|
101
|
+
if (parent === currentDir) break; // reached filesystem root
|
|
102
|
+
currentDir = parent;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Finds a package by searching multiple locations:
|
|
109
|
+
* 1. App-level node_modules
|
|
110
|
+
* 2. Workspace root node_modules
|
|
111
|
+
* 3. Workspace packages directory (for workspace packages)
|
|
112
|
+
*
|
|
113
|
+
* @param rootDir - Root directory of the project
|
|
114
|
+
* @param packageName - Package name (e.g., '@agentuity/workbench')
|
|
115
|
+
* @param logger - Optional logger for debug messages
|
|
116
|
+
* @returns Path to the package directory, or null if not found
|
|
117
|
+
*/
|
|
118
|
+
function findPackagePath(rootDir: string, packageName: string, logger?: Logger): string | null {
|
|
119
|
+
const [scope, name] = packageName.startsWith('@')
|
|
120
|
+
? packageName.slice(1).split('/')
|
|
121
|
+
: [null, packageName];
|
|
122
|
+
|
|
123
|
+
// 1. Try app-level node_modules
|
|
124
|
+
const appLevelPath = scope
|
|
125
|
+
? join(rootDir, 'node_modules', `@${scope}`, name)
|
|
126
|
+
: join(rootDir, 'node_modules', name);
|
|
127
|
+
if (existsSync(appLevelPath)) {
|
|
128
|
+
logger?.debug(`Found ${packageName} at app level: ${appLevelPath}`);
|
|
129
|
+
return appLevelPath;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 2. Try workspace root node_modules
|
|
133
|
+
const workspaceRoot = findWorkspaceRoot(rootDir);
|
|
134
|
+
if (workspaceRoot) {
|
|
135
|
+
const rootLevelPath = scope
|
|
136
|
+
? join(workspaceRoot, 'node_modules', `@${scope}`, name)
|
|
137
|
+
: join(workspaceRoot, 'node_modules', name);
|
|
138
|
+
if (existsSync(rootLevelPath)) {
|
|
139
|
+
logger?.debug(`Found ${packageName} at workspace root: ${rootLevelPath}`);
|
|
140
|
+
return rootLevelPath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3. Try workspace packages directory
|
|
144
|
+
const workspacePackagePath = join(workspaceRoot, 'packages', name);
|
|
145
|
+
if (existsSync(workspacePackagePath)) {
|
|
146
|
+
logger?.debug(`Found ${packageName} in workspace packages: ${workspacePackagePath}`);
|
|
147
|
+
return workspacePackagePath;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
82
154
|
const handleBuildFailure = (buildResult: BuildResult) => {
|
|
83
155
|
// Collect all build errors with full details
|
|
84
156
|
const errorMessages = buildResult.logs
|
|
@@ -302,8 +374,23 @@ export async function bundle({
|
|
|
302
374
|
const tsconfigPath = join(rootDir, 'tsconfig.json');
|
|
303
375
|
const hasTsconfig = existsSync(tsconfigPath);
|
|
304
376
|
|
|
377
|
+
// Load user build config (if it exists)
|
|
378
|
+
const buildConfigFunction = await loadBuildConfig(rootDir);
|
|
379
|
+
|
|
380
|
+
// Helper to create build context for config function
|
|
381
|
+
const createBuildContext = (): BuildContext => ({
|
|
382
|
+
rootDir,
|
|
383
|
+
dev,
|
|
384
|
+
outDir,
|
|
385
|
+
srcDir,
|
|
386
|
+
orgId,
|
|
387
|
+
projectId,
|
|
388
|
+
region,
|
|
389
|
+
logger,
|
|
390
|
+
});
|
|
391
|
+
|
|
305
392
|
await (async () => {
|
|
306
|
-
const
|
|
393
|
+
const baseConfig: Bun.BuildConfig = {
|
|
307
394
|
entrypoints: appEntrypoints,
|
|
308
395
|
root: rootDir,
|
|
309
396
|
outdir: outDir,
|
|
@@ -330,7 +417,19 @@ export async function bundle({
|
|
|
330
417
|
},
|
|
331
418
|
tsconfig: hasTsconfig ? tsconfigPath : undefined,
|
|
332
419
|
};
|
|
333
|
-
|
|
420
|
+
|
|
421
|
+
// Apply user config for 'api' phase
|
|
422
|
+
let finalConfig = baseConfig;
|
|
423
|
+
if (buildConfigFunction) {
|
|
424
|
+
const userConfig = await executeBuildConfig(
|
|
425
|
+
buildConfigFunction,
|
|
426
|
+
'api',
|
|
427
|
+
createBuildContext()
|
|
428
|
+
);
|
|
429
|
+
finalConfig = mergeBuildConfig(baseConfig, userConfig);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const buildResult = await Bun.build(finalConfig);
|
|
334
433
|
if (!buildResult.success) {
|
|
335
434
|
handleBuildFailure(buildResult);
|
|
336
435
|
}
|
|
@@ -438,7 +537,7 @@ export async function bundle({
|
|
|
438
537
|
mkdirSync(join(webOutDir, 'asset'), { recursive: true });
|
|
439
538
|
const isLocalRegion = region === 'local' || region === 'l';
|
|
440
539
|
|
|
441
|
-
const
|
|
540
|
+
const baseConfig: Bun.BuildConfig = {
|
|
442
541
|
entrypoints: webEntrypoints,
|
|
443
542
|
root: webDir,
|
|
444
543
|
outdir: webOutDir,
|
|
@@ -467,7 +566,19 @@ export async function bundle({
|
|
|
467
566
|
},
|
|
468
567
|
tsconfig: hasTsconfig ? tsconfigPath : undefined,
|
|
469
568
|
};
|
|
470
|
-
|
|
569
|
+
|
|
570
|
+
// Apply user config for 'web' phase
|
|
571
|
+
let finalConfig = baseConfig;
|
|
572
|
+
if (buildConfigFunction) {
|
|
573
|
+
const userConfig = await executeBuildConfig(
|
|
574
|
+
buildConfigFunction,
|
|
575
|
+
'web',
|
|
576
|
+
createBuildContext()
|
|
577
|
+
);
|
|
578
|
+
finalConfig = mergeBuildConfig(baseConfig, userConfig);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const result = await Bun.build(finalConfig);
|
|
471
582
|
if (result.success) {
|
|
472
583
|
// Fix duplicate exports caused by Bun splitting bug
|
|
473
584
|
// See: https://github.com/oven-sh/bun/issues/5344
|
|
@@ -507,12 +618,51 @@ export async function bundle({
|
|
|
507
618
|
|
|
508
619
|
// Generate files using templates
|
|
509
620
|
await Bun.write(join(tempWorkbenchDir, 'main.tsx'), generateWorkbenchMainTsx(config));
|
|
621
|
+
|
|
622
|
+
// Copy the pre-built standalone.css from workbench package instead of generating it
|
|
623
|
+
// This ensures we use the built version with Tailwind already processed
|
|
624
|
+
const workbenchPackagePath = findPackagePath(rootDir, '@agentuity/workbench', logger);
|
|
625
|
+
const workbenchStylesOut = join(tempWorkbenchDir, 'styles.css');
|
|
626
|
+
|
|
627
|
+
if (workbenchPackagePath) {
|
|
628
|
+
const workbenchStylesPath = join(workbenchPackagePath, 'dist', 'standalone.css');
|
|
629
|
+
const workbenchStylesSourcePath = join(workbenchPackagePath, 'src', 'standalone.css');
|
|
630
|
+
|
|
631
|
+
if (existsSync(workbenchStylesPath)) {
|
|
632
|
+
cpSync(workbenchStylesPath, workbenchStylesOut);
|
|
633
|
+
logger.debug('Copied workbench dist/standalone.css to temp workbench dir');
|
|
634
|
+
} else if (existsSync(workbenchStylesSourcePath)) {
|
|
635
|
+
// Fallback: copy source CSS file (contains Tailwind directives that need processing)
|
|
636
|
+
cpSync(workbenchStylesSourcePath, workbenchStylesOut);
|
|
637
|
+
logger.warn(
|
|
638
|
+
'Workbench dist/standalone.css not found, using source CSS. Ensure @agentuity/workbench is built.'
|
|
639
|
+
);
|
|
640
|
+
} else {
|
|
641
|
+
throw new BuildFailedError({
|
|
642
|
+
message: `Workbench styles not found in ${workbenchPackagePath}. Expected either:
|
|
643
|
+
- ${workbenchStylesPath}
|
|
644
|
+
- ${workbenchStylesSourcePath}
|
|
645
|
+
|
|
646
|
+
Make sure @agentuity/workbench is built.`,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
throw new BuildFailedError({
|
|
651
|
+
message: `Workbench package not found. Searched in:
|
|
652
|
+
- App-level node_modules
|
|
653
|
+
- Workspace root node_modules
|
|
654
|
+
- Workspace packages directory
|
|
655
|
+
|
|
656
|
+
Make sure @agentuity/workbench is installed or available in the workspace.`,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
510
660
|
const workbenchIndexFile = join(tempWorkbenchDir, 'index.html');
|
|
511
661
|
await Bun.write(workbenchIndexFile, generateWorkbenchIndexHtml());
|
|
512
662
|
|
|
513
663
|
// Bundle workbench using generated files
|
|
514
664
|
// Disable splitting to avoid CommonJS/ESM module resolution conflicts
|
|
515
|
-
const
|
|
665
|
+
const workbenchBaseConfig: Bun.BuildConfig = {
|
|
516
666
|
entrypoints: [workbenchIndexFile],
|
|
517
667
|
outdir: join(outDir, 'workbench'),
|
|
518
668
|
sourcemap: dev ? 'inline' : 'linked',
|
|
@@ -531,7 +681,18 @@ export async function bundle({
|
|
|
531
681
|
},
|
|
532
682
|
};
|
|
533
683
|
|
|
534
|
-
|
|
684
|
+
// Apply user config for 'workbench' phase
|
|
685
|
+
let finalWorkbenchConfig = workbenchBaseConfig;
|
|
686
|
+
if (buildConfigFunction) {
|
|
687
|
+
const userConfig = await executeBuildConfig(
|
|
688
|
+
buildConfigFunction,
|
|
689
|
+
'workbench',
|
|
690
|
+
createBuildContext()
|
|
691
|
+
);
|
|
692
|
+
finalWorkbenchConfig = mergeBuildConfig(workbenchBaseConfig, userConfig);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const workbenchResult = await Bun.build(finalWorkbenchConfig);
|
|
535
696
|
if (workbenchResult.success) {
|
|
536
697
|
logger.debug('Workbench bundled successfully');
|
|
537
698
|
// Clean up temp directory
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { StructuredError } from '@agentuity/core';
|
|
3
|
+
import type { BuildConfigFunction, BuildPhase, BuildContext, BuildConfig } from '../../types';
|
|
4
|
+
|
|
5
|
+
const BuildConfigLoadError = StructuredError('BuildConfigLoadError');
|
|
6
|
+
const BuildConfigValidationError = StructuredError('BuildConfigValidationError');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reserved define keys that cannot be overridden by user config
|
|
10
|
+
*/
|
|
11
|
+
const RESERVED_DEFINE_PREFIXES = ['process.env.AGENTUITY_', 'process.env.NODE_ENV'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Load and validate agentuity.config.ts from the project root
|
|
15
|
+
*/
|
|
16
|
+
export async function loadBuildConfig(rootDir: string): Promise<BuildConfigFunction | null> {
|
|
17
|
+
const configPath = join(rootDir, 'agentuity.config.ts');
|
|
18
|
+
|
|
19
|
+
// Check if config file exists
|
|
20
|
+
const configFile = Bun.file(configPath);
|
|
21
|
+
if (!(await configFile.exists())) {
|
|
22
|
+
return null; // No config file is OK - it's optional
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Import the config file (Bun handles TypeScript natively)
|
|
27
|
+
// Dynamically import using absolute path; Bun resolves TS modules without a file:// URL
|
|
28
|
+
const configModule = await import(configPath);
|
|
29
|
+
|
|
30
|
+
// Get the default export
|
|
31
|
+
const configFunction = configModule.default;
|
|
32
|
+
|
|
33
|
+
// Validate it's a function
|
|
34
|
+
if (typeof configFunction !== 'function') {
|
|
35
|
+
throw new BuildConfigValidationError({
|
|
36
|
+
message: `agentuity.config.ts must export a default function, got ${typeof configFunction}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return configFunction as BuildConfigFunction;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
// If it's already our error, re-throw
|
|
43
|
+
if (error instanceof BuildConfigValidationError || error instanceof BuildConfigLoadError) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Wrap other errors
|
|
48
|
+
throw new BuildConfigLoadError({
|
|
49
|
+
message: `Failed to load agentuity.config.ts: ${error instanceof Error ? error.message : String(error)}`,
|
|
50
|
+
cause: error instanceof Error ? error : undefined,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Execute the build config function for a specific phase and validate the result
|
|
57
|
+
*/
|
|
58
|
+
export async function executeBuildConfig(
|
|
59
|
+
configFunction: BuildConfigFunction,
|
|
60
|
+
phase: BuildPhase,
|
|
61
|
+
context: BuildContext
|
|
62
|
+
): Promise<BuildConfig> {
|
|
63
|
+
try {
|
|
64
|
+
// Execute the config function (may be async)
|
|
65
|
+
const config = await configFunction(phase, context);
|
|
66
|
+
|
|
67
|
+
// Validate the result is an object
|
|
68
|
+
if (!config || typeof config !== 'object') {
|
|
69
|
+
throw new BuildConfigValidationError({
|
|
70
|
+
message: `Build config for phase "${phase}" must return an object, got ${typeof config}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate plugins array if provided
|
|
75
|
+
if (config.plugins !== undefined) {
|
|
76
|
+
if (!Array.isArray(config.plugins)) {
|
|
77
|
+
throw new BuildConfigValidationError({
|
|
78
|
+
message: `Build config plugins for phase "${phase}" must be an array, got ${typeof config.plugins}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Validate each plugin has a name property (basic BunPlugin check)
|
|
82
|
+
for (const plugin of config.plugins) {
|
|
83
|
+
if (!plugin || typeof plugin !== 'object' || !('name' in plugin)) {
|
|
84
|
+
throw new BuildConfigValidationError({
|
|
85
|
+
message: `Invalid plugin in phase "${phase}": plugins must be BunPlugin objects with a name property`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validate external array if provided
|
|
92
|
+
if (config.external !== undefined) {
|
|
93
|
+
if (!Array.isArray(config.external)) {
|
|
94
|
+
throw new BuildConfigValidationError({
|
|
95
|
+
message: `Build config external for phase "${phase}" must be an array, got ${typeof config.external}`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Validate each external is a string
|
|
99
|
+
for (const ext of config.external) {
|
|
100
|
+
if (typeof ext !== 'string') {
|
|
101
|
+
throw new BuildConfigValidationError({
|
|
102
|
+
message: `Invalid external in phase "${phase}": all externals must be strings, got ${typeof ext}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate and filter define object if provided
|
|
109
|
+
if (config.define !== undefined) {
|
|
110
|
+
if (typeof config.define !== 'object' || config.define === null) {
|
|
111
|
+
throw new BuildConfigValidationError({
|
|
112
|
+
message: `Build config define for phase "${phase}" must be an object, got ${typeof config.define}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for reserved keys and filter them out
|
|
117
|
+
const filteredDefine: Record<string, string> = {};
|
|
118
|
+
const blockedKeys: string[] = [];
|
|
119
|
+
|
|
120
|
+
for (const [key, value] of Object.entries(config.define)) {
|
|
121
|
+
// Check if this key starts with any reserved prefix
|
|
122
|
+
const isReserved = RESERVED_DEFINE_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
123
|
+
|
|
124
|
+
if (isReserved) {
|
|
125
|
+
blockedKeys.push(key);
|
|
126
|
+
continue; // Skip reserved keys
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate value is a string
|
|
130
|
+
if (typeof value !== 'string') {
|
|
131
|
+
throw new BuildConfigValidationError({
|
|
132
|
+
message: `Build config define values for phase "${phase}" must be strings, got ${typeof value} for key "${key}"`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
filteredDefine[key] = value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Warn if we blocked any keys
|
|
140
|
+
if (blockedKeys.length > 0) {
|
|
141
|
+
context.logger.warn(
|
|
142
|
+
`Build config for phase "${phase}" attempted to override reserved define keys (ignored): ${blockedKeys.join(', ')}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Replace with filtered version
|
|
147
|
+
config.define = filteredDefine;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return config;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
// If it's already our error, re-throw
|
|
153
|
+
if (error instanceof BuildConfigValidationError) {
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Wrap other errors
|
|
158
|
+
throw new BuildConfigLoadError({
|
|
159
|
+
message: `Failed to execute build config for phase "${phase}": ${error instanceof Error ? error.message : String(error)}`,
|
|
160
|
+
cause: error instanceof Error ? error : undefined,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Merge user build config with base Bun.BuildConfig
|
|
167
|
+
* User config is applied AFTER base config with safeguards
|
|
168
|
+
* Returns a complete Bun.BuildConfig with user overrides applied
|
|
169
|
+
*/
|
|
170
|
+
export function mergeBuildConfig(
|
|
171
|
+
baseConfig: import('bun').BuildConfig,
|
|
172
|
+
userConfig: BuildConfig
|
|
173
|
+
): import('bun').BuildConfig {
|
|
174
|
+
const merged = { ...baseConfig };
|
|
175
|
+
|
|
176
|
+
// Merge plugins (user plugins come AFTER Agentuity plugin)
|
|
177
|
+
if (userConfig.plugins && userConfig.plugins.length > 0) {
|
|
178
|
+
merged.plugins = [...(baseConfig.plugins ?? []), ...userConfig.plugins];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Merge external (combine arrays, dedupe)
|
|
182
|
+
if (userConfig.external && userConfig.external.length > 0) {
|
|
183
|
+
const existingExternal = Array.isArray(baseConfig.external)
|
|
184
|
+
? baseConfig.external
|
|
185
|
+
: baseConfig.external
|
|
186
|
+
? [baseConfig.external]
|
|
187
|
+
: [];
|
|
188
|
+
merged.external = [...new Set([...existingExternal, ...userConfig.external])];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Merge define (user defines come last, but reserved keys already filtered)
|
|
192
|
+
if (userConfig.define && Object.keys(userConfig.define).length > 0) {
|
|
193
|
+
merged.define = {
|
|
194
|
+
...baseConfig.define,
|
|
195
|
+
...userConfig.define,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return merged;
|
|
200
|
+
}
|
package/src/cmd/build/plugin.ts
CHANGED
|
@@ -336,8 +336,11 @@ const AgentuityBundler: BunPlugin = {
|
|
|
336
336
|
// Setup workbench configuration - evaluate fresh each time during builds
|
|
337
337
|
const workbenchConfig = await setupWorkbench(srcDir);
|
|
338
338
|
|
|
339
|
+
// Store web routes for later (must be registered AFTER API routes)
|
|
340
|
+
let webRoutesInsert: string | null = null;
|
|
341
|
+
|
|
339
342
|
if (existsSync(indexFile)) {
|
|
340
|
-
|
|
343
|
+
webRoutesInsert = `import { serveStatic } from 'hono/bun';
|
|
341
344
|
import { getRouter, registerDevModeRoutes } from '@agentuity/runtime';
|
|
342
345
|
import { readFileSync, existsSync } from 'node:fs';
|
|
343
346
|
|
|
@@ -387,14 +390,22 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
387
390
|
// Serve public assets at root (e.g., /favicon.ico) - must be last
|
|
388
391
|
router.get('/*', async (c, next) => {
|
|
389
392
|
const path = c.req.path;
|
|
393
|
+
// Skip API routes - let them 404 naturally
|
|
394
|
+
if (path === '/api' || path.startsWith('/api/')) {
|
|
395
|
+
return c.notFound();
|
|
396
|
+
}
|
|
390
397
|
// Prevent directory traversal attacks
|
|
391
398
|
if (path.includes('..') || path.includes('%2e%2e')) {
|
|
392
399
|
return c.notFound();
|
|
393
400
|
}
|
|
401
|
+
// Don't catch workbench routes (already handled above)
|
|
402
|
+
if (path.startsWith('/workbench')) {
|
|
403
|
+
return c.notFound();
|
|
404
|
+
}
|
|
394
405
|
// serve default for any path not explicitly matched
|
|
395
406
|
return c.html(index);
|
|
396
407
|
});
|
|
397
|
-
})()
|
|
408
|
+
})();`;
|
|
398
409
|
}
|
|
399
410
|
|
|
400
411
|
// Build agentInfo from all discovered agents and track directories
|
|
@@ -626,6 +637,11 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
626
637
|
})();`);
|
|
627
638
|
}
|
|
628
639
|
|
|
640
|
+
// Add web routes AFTER API routes (catch-all must be last)
|
|
641
|
+
if (webRoutesInsert) {
|
|
642
|
+
inserts.push(webRoutesInsert);
|
|
643
|
+
}
|
|
644
|
+
|
|
629
645
|
const file = Bun.file(args.path);
|
|
630
646
|
let contents = await file.text();
|
|
631
647
|
// Use AST-based parsing to reliably find createApp statement end
|
|
@@ -8,7 +8,6 @@ export function generateWorkbenchMainTsx(config: WorkbenchConfig): string {
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { createRoot } from 'react-dom/client';
|
|
10
10
|
import { App } from '@agentuity/workbench';
|
|
11
|
-
import '@agentuity/workbench/styles';
|
|
12
11
|
|
|
13
12
|
// Root element
|
|
14
13
|
const rootElement = document.getElementById('root');
|
|
@@ -23,6 +22,14 @@ root.render(<App configBase64="${encodedConfig}" />);
|
|
|
23
22
|
`;
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
export function generateWorkbenchStylesCss(): string {
|
|
26
|
+
// This file will be replaced with the actual dist/styles.css content during build
|
|
27
|
+
// We use @import here as a placeholder, but the bundler should resolve it to the built file
|
|
28
|
+
return `/* Generated workbench styles - will be replaced with dist/styles.css */
|
|
29
|
+
@import '@agentuity/workbench/styles-standalone';
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
export function generateWorkbenchIndexHtml(): string {
|
|
27
34
|
return `<!DOCTYPE html>
|
|
28
35
|
<html lang="en">
|
|
@@ -30,6 +37,7 @@ export function generateWorkbenchIndexHtml(): string {
|
|
|
30
37
|
<meta charset="UTF-8">
|
|
31
38
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
32
39
|
<title>Agentuity Workbench</title>
|
|
40
|
+
<link rel="stylesheet" href="./styles.css">
|
|
33
41
|
</head>
|
|
34
42
|
<body>
|
|
35
43
|
<div id="root"></div>
|
package/src/cmd/cloud/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createCommand } from '../../types';
|
|
2
2
|
import { deploySubcommand } from './deploy';
|
|
3
3
|
import { dbCommand } from './db';
|
|
4
|
+
import { redisCommand } from './redis';
|
|
4
5
|
import { storageCommand } from './storage';
|
|
5
6
|
import { sessionCommand } from './session';
|
|
6
7
|
import { threadCommand } from './thread';
|
|
@@ -34,6 +35,7 @@ export const command = createCommand({
|
|
|
34
35
|
secretCommand,
|
|
35
36
|
deploySubcommand,
|
|
36
37
|
dbCommand,
|
|
38
|
+
redisCommand,
|
|
37
39
|
storageCommand,
|
|
38
40
|
sessionCommand,
|
|
39
41
|
threadCommand,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listResources } from '@agentuity/server';
|
|
3
|
+
import { createSubcommand } from '../../../types';
|
|
4
|
+
import * as tui from '../../../tui';
|
|
5
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
6
|
+
import { getCommand } from '../../../command-prefix';
|
|
7
|
+
|
|
8
|
+
const RedisGetResponseSchema = z.object({
|
|
9
|
+
url: z.string().optional().describe('Redis connection URL'),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const showSubcommand = createSubcommand({
|
|
13
|
+
name: 'show',
|
|
14
|
+
aliases: ['info'],
|
|
15
|
+
description: 'Show Redis connection URL',
|
|
16
|
+
tags: ['read-only', 'fast', 'requires-auth'],
|
|
17
|
+
requires: { auth: true, org: true, region: true },
|
|
18
|
+
idempotent: true,
|
|
19
|
+
examples: [
|
|
20
|
+
{ command: getCommand('cloud redis show'), description: 'Show Redis connection URL' },
|
|
21
|
+
{
|
|
22
|
+
command: getCommand('cloud redis show --show-credentials'),
|
|
23
|
+
description: 'Show Redis URL with credentials visible',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
command: getCommand('--json cloud redis show'),
|
|
27
|
+
description: 'Show Redis URL as JSON',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
schema: {
|
|
31
|
+
options: z.object({
|
|
32
|
+
showCredentials: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe(
|
|
36
|
+
'Show credentials in plain text (default: masked in terminal, unmasked in JSON)'
|
|
37
|
+
),
|
|
38
|
+
}),
|
|
39
|
+
response: RedisGetResponseSchema,
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async handler(ctx) {
|
|
43
|
+
const { logger, opts, options, orgId, region, auth } = ctx;
|
|
44
|
+
|
|
45
|
+
const catalystClient = getCatalystAPIClient(logger, auth, region);
|
|
46
|
+
|
|
47
|
+
const resources = await tui.spinner({
|
|
48
|
+
message: `Fetching Redis for ${orgId} in ${region}`,
|
|
49
|
+
clearOnSuccess: true,
|
|
50
|
+
callback: async () => {
|
|
51
|
+
return listResources(catalystClient, orgId, region);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!resources.redis) {
|
|
56
|
+
tui.info('No Redis provisioned for this organization');
|
|
57
|
+
return { url: undefined };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const shouldShowCredentials = opts.showCredentials === true;
|
|
61
|
+
const shouldMask = !options.json && !shouldShowCredentials;
|
|
62
|
+
|
|
63
|
+
if (!options.json) {
|
|
64
|
+
const displayUrl = shouldMask ? tui.maskSecret(resources.redis.url) : resources.redis.url;
|
|
65
|
+
tui.output(tui.bold('Redis URL: ') + displayUrl);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
url: resources.redis.url,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createCommand } from '../../../types';
|
|
2
|
+
import { showSubcommand } from './get';
|
|
3
|
+
import { getCommand } from '../../../command-prefix';
|
|
4
|
+
|
|
5
|
+
export const redisCommand = createCommand({
|
|
6
|
+
name: 'redis',
|
|
7
|
+
description: 'Manage Redis resources',
|
|
8
|
+
tags: ['slow', 'requires-auth'],
|
|
9
|
+
examples: [
|
|
10
|
+
{ command: getCommand('cloud redis show'), description: 'Show Redis connection URL' },
|
|
11
|
+
],
|
|
12
|
+
subcommands: [showSubcommand],
|
|
13
|
+
});
|
package/src/cmd/upgrade/index.ts
CHANGED
|
@@ -300,7 +300,7 @@ export const command = createCommand({
|
|
|
300
300
|
if (!force) {
|
|
301
301
|
tui.info(`Current version: ${tui.muted(currentVersion)}`);
|
|
302
302
|
tui.info(`Latest version: ${tui.bold(latestVersion)}`);
|
|
303
|
-
tui.
|
|
303
|
+
tui.newline();
|
|
304
304
|
|
|
305
305
|
const shouldUpgrade = await tui.confirm('Do you want to upgrade?', true);
|
|
306
306
|
|
package/src/index.ts
CHANGED
|
@@ -104,6 +104,10 @@ export type {
|
|
|
104
104
|
Profile,
|
|
105
105
|
AuthData,
|
|
106
106
|
CommandSchemas,
|
|
107
|
+
BuildPhase,
|
|
108
|
+
BuildContext,
|
|
109
|
+
BuildConfig,
|
|
110
|
+
BuildConfigFunction,
|
|
107
111
|
} from './types';
|
|
108
112
|
export { createSubcommand, createCommand } from './types';
|
|
109
113
|
export type { ColorScheme } from './terminal';
|
package/src/tui.ts
CHANGED
|
@@ -346,6 +346,14 @@ export function newline(): void {
|
|
|
346
346
|
process.stderr.write('\n');
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Print plain text output without any prefix or icon
|
|
351
|
+
* Use for primary command output that shouldn't have semantic formatting
|
|
352
|
+
*/
|
|
353
|
+
export function output(message: string): void {
|
|
354
|
+
console.log(message);
|
|
355
|
+
}
|
|
356
|
+
|
|
349
357
|
/**
|
|
350
358
|
* Get the display width of a string, handling ANSI codes and OSC 8 hyperlinks
|
|
351
359
|
*
|