@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.
Files changed (53) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +11 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/build/bundler.d.ts.map +1 -1
  5. package/dist/cmd/build/bundler.js +142 -7
  6. package/dist/cmd/build/bundler.js.map +1 -1
  7. package/dist/cmd/build/config-loader.d.ts +16 -0
  8. package/dist/cmd/build/config-loader.d.ts.map +1 -0
  9. package/dist/cmd/build/config-loader.js +165 -0
  10. package/dist/cmd/build/config-loader.js.map +1 -0
  11. package/dist/cmd/build/plugin.d.ts.map +1 -1
  12. package/dist/cmd/build/plugin.js +16 -2
  13. package/dist/cmd/build/plugin.js.map +1 -1
  14. package/dist/cmd/build/workbench.d.ts +1 -0
  15. package/dist/cmd/build/workbench.d.ts.map +1 -1
  16. package/dist/cmd/build/workbench.js +8 -1
  17. package/dist/cmd/build/workbench.js.map +1 -1
  18. package/dist/cmd/cloud/index.d.ts.map +1 -1
  19. package/dist/cmd/cloud/index.js +2 -0
  20. package/dist/cmd/cloud/index.js.map +1 -1
  21. package/dist/cmd/cloud/redis/get.d.ts +2 -0
  22. package/dist/cmd/cloud/redis/get.d.ts.map +1 -0
  23. package/dist/cmd/cloud/redis/get.js +62 -0
  24. package/dist/cmd/cloud/redis/get.js.map +1 -0
  25. package/dist/cmd/cloud/redis/index.d.ts +2 -0
  26. package/dist/cmd/cloud/redis/index.d.ts.map +1 -0
  27. package/dist/cmd/cloud/redis/index.js +13 -0
  28. package/dist/cmd/cloud/redis/index.js.map +1 -0
  29. package/dist/cmd/upgrade/index.js +1 -1
  30. package/dist/cmd/upgrade/index.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/tui.d.ts +5 -0
  35. package/dist/tui.d.ts.map +1 -1
  36. package/dist/tui.js +7 -0
  37. package/dist/tui.js.map +1 -1
  38. package/dist/types.d.ts +66 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/types.js.map +1 -1
  41. package/package.json +6 -4
  42. package/src/cli.ts +12 -6
  43. package/src/cmd/build/bundler.ts +169 -8
  44. package/src/cmd/build/config-loader.ts +200 -0
  45. package/src/cmd/build/plugin.ts +18 -2
  46. package/src/cmd/build/workbench.ts +9 -1
  47. package/src/cmd/cloud/index.ts +2 -0
  48. package/src/cmd/cloud/redis/get.ts +72 -0
  49. package/src/cmd/cloud/redis/index.ts +13 -0
  50. package/src/cmd/upgrade/index.ts +1 -1
  51. package/src/index.ts +4 -0
  52. package/src/tui.ts +8 -0
  53. package/src/types.ts +73 -0
@@ -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 config: Bun.BuildConfig = {
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
- const buildResult = await Bun.build(config);
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 config: Bun.BuildConfig = {
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
- const result = await Bun.build(config);
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 workbenchBuildConfig: Bun.BuildConfig = {
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
- const workbenchResult = await Bun.build(workbenchBuildConfig);
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
+ }
@@ -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
- inserts.push(`import { serveStatic } from 'hono/bun';
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>
@@ -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
+ });
@@ -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.info('');
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
  *