@agentuity/cli 0.0.41 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/bin/cli.ts +7 -5
  2. package/dist/banner.d.ts.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cmd/auth/index.d.ts.map +1 -1
  5. package/dist/cmd/auth/login.d.ts.map +1 -1
  6. package/dist/cmd/auth/whoami.d.ts +2 -0
  7. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  8. package/dist/cmd/bundle/ast.d.ts +2 -0
  9. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  10. package/dist/cmd/bundle/index.d.ts +1 -1
  11. package/dist/cmd/bundle/index.d.ts.map +1 -1
  12. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  13. package/dist/cmd/cloud/deploy.d.ts +2 -0
  14. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  15. package/dist/cmd/cloud/index.d.ts +2 -0
  16. package/dist/cmd/cloud/index.d.ts.map +1 -0
  17. package/dist/cmd/dev/index.d.ts.map +1 -1
  18. package/dist/cmd/env/delete.d.ts +2 -0
  19. package/dist/cmd/env/delete.d.ts.map +1 -0
  20. package/dist/cmd/env/get.d.ts +2 -0
  21. package/dist/cmd/env/get.d.ts.map +1 -0
  22. package/dist/cmd/env/import.d.ts +2 -0
  23. package/dist/cmd/env/import.d.ts.map +1 -0
  24. package/dist/cmd/env/index.d.ts +2 -0
  25. package/dist/cmd/env/index.d.ts.map +1 -0
  26. package/dist/cmd/env/list.d.ts +2 -0
  27. package/dist/cmd/env/list.d.ts.map +1 -0
  28. package/dist/cmd/env/pull.d.ts +2 -0
  29. package/dist/cmd/env/pull.d.ts.map +1 -0
  30. package/dist/cmd/env/push.d.ts +2 -0
  31. package/dist/cmd/env/push.d.ts.map +1 -0
  32. package/dist/cmd/env/set.d.ts +2 -0
  33. package/dist/cmd/env/set.d.ts.map +1 -0
  34. package/dist/cmd/project/delete.d.ts.map +1 -1
  35. package/dist/cmd/project/download.d.ts +1 -1
  36. package/dist/cmd/project/download.d.ts.map +1 -1
  37. package/dist/cmd/project/list.d.ts.map +1 -1
  38. package/dist/cmd/project/show.d.ts.map +1 -1
  39. package/dist/cmd/project/template-flow.d.ts +1 -1
  40. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  41. package/dist/cmd/secret/delete.d.ts +2 -0
  42. package/dist/cmd/secret/delete.d.ts.map +1 -0
  43. package/dist/cmd/secret/get.d.ts +2 -0
  44. package/dist/cmd/secret/get.d.ts.map +1 -0
  45. package/dist/cmd/secret/import.d.ts +2 -0
  46. package/dist/cmd/secret/import.d.ts.map +1 -0
  47. package/dist/cmd/secret/index.d.ts +2 -0
  48. package/dist/cmd/secret/index.d.ts.map +1 -0
  49. package/dist/cmd/secret/list.d.ts +2 -0
  50. package/dist/cmd/secret/list.d.ts.map +1 -0
  51. package/dist/cmd/secret/pull.d.ts +2 -0
  52. package/dist/cmd/secret/pull.d.ts.map +1 -0
  53. package/dist/cmd/secret/push.d.ts +2 -0
  54. package/dist/cmd/secret/push.d.ts.map +1 -0
  55. package/dist/cmd/secret/set.d.ts +2 -0
  56. package/dist/cmd/secret/set.d.ts.map +1 -0
  57. package/dist/cmd/version/index.d.ts.map +1 -1
  58. package/dist/config.d.ts +4 -1
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/env-util.d.ts +67 -0
  61. package/dist/env-util.d.ts.map +1 -0
  62. package/dist/env-util.test.d.ts +2 -0
  63. package/dist/env-util.test.d.ts.map +1 -0
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/schema-parser.d.ts.map +1 -1
  67. package/dist/steps.d.ts.map +1 -1
  68. package/dist/tui.d.ts +1 -1
  69. package/dist/tui.d.ts.map +1 -1
  70. package/dist/types.d.ts +35 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/src/banner.ts +7 -2
  74. package/src/cli.ts +46 -5
  75. package/src/cmd/auth/index.ts +2 -1
  76. package/src/cmd/auth/login.ts +2 -4
  77. package/src/cmd/auth/whoami.ts +69 -0
  78. package/src/cmd/bundle/ast.ts +169 -4
  79. package/src/cmd/bundle/index.ts +2 -2
  80. package/src/cmd/bundle/plugin.ts +42 -1
  81. package/src/cmd/cloud/deploy.ts +129 -0
  82. package/src/cmd/cloud/index.ts +8 -0
  83. package/src/cmd/dev/index.ts +93 -9
  84. package/src/cmd/env/delete.ts +62 -0
  85. package/src/cmd/env/get.ts +66 -0
  86. package/src/cmd/env/import.ts +117 -0
  87. package/src/cmd/env/index.ts +22 -0
  88. package/src/cmd/env/list.ts +69 -0
  89. package/src/cmd/env/pull.ts +93 -0
  90. package/src/cmd/env/push.ts +55 -0
  91. package/src/cmd/env/set.ts +86 -0
  92. package/src/cmd/project/create.ts +1 -1
  93. package/src/cmd/project/delete.ts +43 -2
  94. package/src/cmd/project/download.ts +1 -1
  95. package/src/cmd/project/list.ts +33 -2
  96. package/src/cmd/project/show.ts +35 -3
  97. package/src/cmd/project/template-flow.ts +53 -12
  98. package/src/cmd/secret/delete.ts +55 -0
  99. package/src/cmd/secret/get.ts +67 -0
  100. package/src/cmd/secret/import.ts +79 -0
  101. package/src/cmd/secret/index.ts +22 -0
  102. package/src/cmd/secret/list.ts +69 -0
  103. package/src/cmd/secret/pull.ts +91 -0
  104. package/src/cmd/secret/push.ts +55 -0
  105. package/src/cmd/secret/set.ts +60 -0
  106. package/src/cmd/version/index.ts +2 -1
  107. package/src/config.ts +60 -7
  108. package/src/env-util.test.ts +194 -0
  109. package/src/env-util.ts +290 -0
  110. package/src/index.ts +5 -1
  111. package/src/schema-parser.ts +2 -3
  112. package/src/steps.ts +79 -4
  113. package/src/tui.ts +92 -56
  114. package/src/types.ts +30 -1
  115. package/dist/logger.d.ts +0 -24
  116. package/dist/logger.d.ts.map +0 -1
  117. package/src/logger.ts +0 -235
@@ -3,6 +3,8 @@ import { z } from 'zod';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { bundle } from '../bundle/bundler';
5
5
  import { existsSync, FSWatcher, watch } from 'node:fs';
6
+ import { loadBuildMetadata, saveProjectDir } from '../../config';
7
+ import type { BuildMetadata } from '../../types';
6
8
  import * as tui from '../../tui';
7
9
 
8
10
  export const command = createCommand({
@@ -16,7 +18,7 @@ export const command = createCommand({
16
18
  optionalAuth: 'Continue without an account (local only)',
17
19
 
18
20
  async handler(ctx) {
19
- const { opts, logger } = ctx;
21
+ const { opts, logger, options } = ctx;
20
22
 
21
23
  const rootDir = resolve(opts.dir || process.cwd());
22
24
  const appTs = join(rootDir, 'app.ts');
@@ -38,14 +40,26 @@ export const command = createCommand({
38
40
  process.exit(1);
39
41
  }
40
42
 
41
- const devmodebody = tui.muted('Local: ') + tui.link('http://127.0.0.1:3000');
43
+ await saveProjectDir(rootDir);
44
+
45
+ const devmodebody =
46
+ tui.muted('Local: ') +
47
+ tui.link('http://127.0.0.1:3000') +
48
+ '\n\n' +
49
+ tui.muted('Press ') +
50
+ tui.bold('h') +
51
+ tui.muted(' for keyboard shortcuts');
52
+
53
+ function showBanner() {
54
+ tui.banner('⨺ Agentuity DevMode', devmodebody, {
55
+ padding: 2,
56
+ topSpacer: false,
57
+ bottomSpacer: false,
58
+ centerTitle: false,
59
+ });
60
+ }
42
61
 
43
- tui.banner('⨺ Agentuity DevMode', devmodebody, {
44
- padding: 2,
45
- topSpacer: false,
46
- bottomSpacer: false,
47
- centerTitle: false,
48
- });
62
+ showBanner();
49
63
 
50
64
  const env = { ...process.env };
51
65
  env.AGENTUITY_SDK_DEV_MODE = 'true';
@@ -70,6 +84,7 @@ export const command = createCommand({
70
84
  let shuttingDownForRestart = false;
71
85
  let pendingRestart = false;
72
86
  let building = false;
87
+ let metadata: BuildMetadata | undefined;
73
88
 
74
89
  // Track restart timestamps to detect restart loops
75
90
  const restartTimestamps: number[] = [];
@@ -216,6 +231,10 @@ export const command = createCommand({
216
231
  return;
217
232
  }
218
233
 
234
+ metadata = await loadBuildMetadata(agentuityDir);
235
+
236
+ env.AGENTUITY_LOG_LEVEL = options.logLevel;
237
+
219
238
  logger.trace('Starting dev server: %s', appPath);
220
239
  // Use shell to run in a process group for proper cleanup
221
240
  // The 'exec' ensures the shell is replaced by the actual process
@@ -223,7 +242,7 @@ export const command = createCommand({
223
242
  cwd: rootDir,
224
243
  stdout: 'inherit',
225
244
  stderr: 'inherit',
226
- stdin: 'inherit',
245
+ stdin: process.stdin.isTTY ? 'ignore' : 'inherit', // Don't inherit stdin, we handle it ourselves
227
246
  env,
228
247
  });
229
248
 
@@ -303,6 +322,71 @@ export const command = createCommand({
303
322
  await restart();
304
323
  logger.trace('Initial restart completed, setting up watchers');
305
324
 
325
+ // Setup keyboard shortcuts (only if we have a TTY)
326
+ if (process.stdin.isTTY) {
327
+ logger.trace('Setting up keyboard shortcuts');
328
+ process.stdin.setRawMode(true);
329
+ process.stdin.resume();
330
+ process.stdin.setEncoding('utf8');
331
+
332
+ const showHelp = () => {
333
+ console.log('\n' + tui.bold('Keyboard Shortcuts:'));
334
+ console.log(tui.muted(' h') + ' - show this help');
335
+ console.log(tui.muted(' c') + ' - clear console');
336
+ console.log(tui.muted(' r') + ' - restart server');
337
+ console.log(tui.muted(' o') + ' - show routes');
338
+ console.log(tui.muted(' a') + ' - show agents');
339
+ console.log(tui.muted(' q') + ' - quit\n');
340
+ };
341
+
342
+ const showRoutes = () => {
343
+ tui.info('API Route Detail');
344
+ console.table(metadata?.routes, ['method', 'path', 'filename']);
345
+ };
346
+
347
+ const showAgents = () => {
348
+ tui.info('Agent Detail');
349
+ console.table(metadata?.agents, ['name', 'filename', 'description']);
350
+ };
351
+
352
+ process.stdin.on('data', (data) => {
353
+ const key = data.toString();
354
+
355
+ // Handle Ctrl+C
356
+ if (key === '\u0003') {
357
+ cleanup();
358
+ return;
359
+ }
360
+
361
+ // Handle other shortcuts
362
+ switch (key) {
363
+ case 'h':
364
+ showHelp();
365
+ break;
366
+ case 'c':
367
+ console.clear();
368
+ showBanner();
369
+ break;
370
+ case 'r':
371
+ tui.info('Manually restarting server...');
372
+ restart();
373
+ break;
374
+ case 'o':
375
+ showRoutes();
376
+ break;
377
+ case 'a':
378
+ showAgents();
379
+ break;
380
+ case 'q':
381
+ tui.info('Shutting down...');
382
+ cleanup();
383
+ break;
384
+ }
385
+ });
386
+
387
+ logger.trace('✓ Keyboard shortcuts enabled');
388
+ }
389
+
306
390
  // Patterns to ignore (generated files that change during build)
307
391
  const ignorePatterns = [
308
392
  /\.generated\.(js|ts|d\.ts)$/,
@@ -0,0 +1,62 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectEnvDelete } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
6
+ import { loadProjectConfig } from '../../config';
7
+ import {
8
+ findExistingEnvFile,
9
+ readEnvFile,
10
+ writeEnvFile,
11
+ filterAgentuitySdkKeys,
12
+ } from '../../env-util';
13
+
14
+ export const deleteSubcommand = createSubcommand({
15
+ name: 'delete',
16
+ aliases: ['del', 'remove', 'rm'],
17
+ description: 'Delete an environment variable',
18
+ requiresAuth: true,
19
+ schema: {
20
+ args: z.object({
21
+ key: z.string().describe('the environment variable key to delete'),
22
+ }),
23
+ options: z.object({
24
+ dir: z.string().optional().describe('project directory (default: current directory)'),
25
+ }),
26
+ },
27
+
28
+ async handler(ctx) {
29
+ const { args, opts, config } = ctx;
30
+ const dir = opts?.dir ?? process.cwd();
31
+
32
+ // Load project config to get project ID
33
+ const projectConfig = await loadProjectConfig(dir);
34
+ if (!projectConfig) {
35
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
36
+ }
37
+
38
+ const apiUrl = getAPIBaseURL(config);
39
+ const client = new APIClient(apiUrl, config);
40
+
41
+ // Delete from cloud
42
+ await tui.spinner('Deleting environment variable from cloud', () => {
43
+ return projectEnvDelete(client, {
44
+ id: projectConfig.projectId,
45
+ env: [args.key],
46
+ });
47
+ });
48
+
49
+ // Update local .env file (prefer .env.production, fallback to .env)
50
+ const envFilePath = await findExistingEnvFile(dir);
51
+ const currentEnv = await readEnvFile(envFilePath);
52
+ delete currentEnv[args.key];
53
+
54
+ // Filter out AGENTUITY_ keys before writing
55
+ const filteredEnv = filterAgentuitySdkKeys(currentEnv);
56
+ await writeEnvFile(envFilePath, filteredEnv);
57
+
58
+ tui.success(
59
+ `Environment variable '${args.key}' deleted successfully (cloud + ${envFilePath})`
60
+ );
61
+ },
62
+ });
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectGet } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
6
+ import { loadProjectConfig } from '../../config';
7
+ import { maskSecret } from '../../env-util';
8
+
9
+ export const getSubcommand = createSubcommand({
10
+ name: 'get',
11
+ description: 'Get an environment variable value',
12
+ requiresAuth: true,
13
+ schema: {
14
+ args: z.object({
15
+ key: z.string().describe('the environment variable key'),
16
+ }),
17
+ options: z.object({
18
+ dir: z.string().optional().describe('project directory (default: current directory)'),
19
+ mask: z
20
+ .boolean()
21
+ .default(false)
22
+ .describe('mask the value in output (default: true in TTY, false otherwise)'),
23
+ }),
24
+ },
25
+
26
+ async handler(ctx) {
27
+ const { args, opts, config } = ctx;
28
+ const dir = opts?.dir ?? process.cwd();
29
+
30
+ // Load project config to get project ID
31
+ const projectConfig = await loadProjectConfig(dir);
32
+ if (!projectConfig) {
33
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
34
+ }
35
+
36
+ const apiUrl = getAPIBaseURL(config);
37
+ const client = new APIClient(apiUrl, config);
38
+
39
+ // Fetch project with unmasked secrets
40
+ const project = await tui.spinner('Fetching environment variables', () => {
41
+ return projectGet(client, { id: projectConfig.projectId, mask: false });
42
+ });
43
+
44
+ // Look for the key in env
45
+ const value = project.env?.[args.key];
46
+
47
+ if (value === undefined) {
48
+ tui.fatal(`Environment variable '${args.key}' not found`);
49
+ }
50
+
51
+ // Display the value, masked if requested
52
+ if (process.stdout.isTTY) {
53
+ if (opts?.mask) {
54
+ tui.success(`${args.key}=${maskSecret(value)}`);
55
+ } else {
56
+ tui.success(`${args.key}=${value}`);
57
+ }
58
+ } else {
59
+ if (opts?.mask) {
60
+ console.log(`${args.key}=${maskSecret(value)}`);
61
+ } else {
62
+ console.log(`${args.key}=${value}`);
63
+ }
64
+ }
65
+ },
66
+ });
@@ -0,0 +1,117 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectEnvUpdate } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
6
+ import { loadProjectConfig } from '../../config';
7
+ import {
8
+ findEnvFile,
9
+ readEnvFile,
10
+ writeEnvFile,
11
+ filterAgentuitySdkKeys,
12
+ mergeEnvVars,
13
+ splitEnvAndSecrets,
14
+ looksLikeSecret,
15
+ } from '../../env-util';
16
+ import { getCommand } from '../../command-prefix';
17
+
18
+ export const importSubcommand = createSubcommand({
19
+ name: 'import',
20
+ description: 'Import environment variables from a file to cloud and local .env.production',
21
+ requiresAuth: true,
22
+ schema: {
23
+ args: z.object({
24
+ file: z.string().describe('path to the .env file to import'),
25
+ }),
26
+ options: z.object({
27
+ dir: z.string().optional().describe('project directory (default: current directory)'),
28
+ }),
29
+ },
30
+
31
+ async handler(ctx) {
32
+ const { args, opts, config } = ctx;
33
+ const dir = opts?.dir ?? process.cwd();
34
+
35
+ // Load project config to get project ID
36
+ const projectConfig = await loadProjectConfig(dir);
37
+ if (!projectConfig) {
38
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
39
+ }
40
+
41
+ // Read the import file
42
+ const importedEnv = await readEnvFile(args.file);
43
+
44
+ if (Object.keys(importedEnv).length === 0) {
45
+ tui.warning(`No environment variables found in ${args.file}`);
46
+ return;
47
+ }
48
+
49
+ // Filter out AGENTUITY_ prefixed keys
50
+ const filteredEnv = filterAgentuitySdkKeys(importedEnv);
51
+
52
+ if (Object.keys(filteredEnv).length === 0) {
53
+ tui.warning('No valid environment variables to import (all were AGENTUITY_ prefixed)');
54
+ return;
55
+ }
56
+
57
+ // Check for potential secrets in the imported variables
58
+ const potentialSecrets: string[] = [];
59
+ for (const [key, value] of Object.entries(filteredEnv)) {
60
+ if (looksLikeSecret(key, value)) {
61
+ potentialSecrets.push(key);
62
+ }
63
+ }
64
+
65
+ if (potentialSecrets.length > 0) {
66
+ tui.warning(
67
+ `Found ${potentialSecrets.length} variable(s) that look like they should be secrets:`
68
+ );
69
+ for (const key of potentialSecrets) {
70
+ tui.info(` • ${key}`);
71
+ }
72
+ tui.info(`\nSecrets should be stored using: ${getCommand('secret import <file>')}`);
73
+ tui.info('This keeps them more secure and properly masked in the cloud.');
74
+
75
+ const response = await tui.confirm(
76
+ 'Do you still want to import these as regular environment variables?',
77
+ false
78
+ );
79
+
80
+ if (!response) {
81
+ tui.info(
82
+ `Cancelled. Use "${getCommand('secret import')}" to store these as secrets instead.`
83
+ );
84
+ return;
85
+ }
86
+ }
87
+
88
+ const apiUrl = getAPIBaseURL(config);
89
+ const client = new APIClient(apiUrl, config);
90
+
91
+ // Split into env and secrets based on key naming conventions
92
+ const { env: normalEnv, secrets } = splitEnvAndSecrets(filteredEnv);
93
+
94
+ // Push to cloud
95
+ await tui.spinner('Importing environment variables to cloud', () => {
96
+ return projectEnvUpdate(client, {
97
+ id: projectConfig.projectId,
98
+ env: normalEnv,
99
+ secrets: secrets,
100
+ });
101
+ });
102
+
103
+ // Merge with local .env.production file
104
+ const localEnvPath = await findEnvFile(dir);
105
+ const localEnv = await readEnvFile(localEnvPath);
106
+ const mergedEnv = mergeEnvVars(localEnv, filteredEnv);
107
+
108
+ await writeEnvFile(localEnvPath, mergedEnv, {
109
+ skipKeys: Object.keys(mergedEnv).filter((k) => k.startsWith('AGENTUITY_')),
110
+ });
111
+
112
+ const count = Object.keys(filteredEnv).length;
113
+ tui.success(
114
+ `Imported ${count} environment variable${count !== 1 ? 's' : ''} from ${args.file} to cloud and ${localEnvPath}`
115
+ );
116
+ },
117
+ });
@@ -0,0 +1,22 @@
1
+ import { createCommand } from '../../types';
2
+ import { pullSubcommand } from './pull';
3
+ import { pushSubcommand } from './push';
4
+ import { setSubcommand } from './set';
5
+ import { getSubcommand } from './get';
6
+ import { deleteSubcommand } from './delete';
7
+ import { importSubcommand } from './import';
8
+ import { listSubcommand } from './list';
9
+
10
+ export const command = createCommand({
11
+ name: 'env',
12
+ description: 'Manage environment variables for your project',
13
+ subcommands: [
14
+ listSubcommand,
15
+ pullSubcommand,
16
+ pushSubcommand,
17
+ setSubcommand,
18
+ getSubcommand,
19
+ deleteSubcommand,
20
+ importSubcommand,
21
+ ],
22
+ });
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectGet } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
6
+ import { loadProjectConfig } from '../../config';
7
+ import { maskSecret } from '../../env-util';
8
+
9
+ export const listSubcommand = createSubcommand({
10
+ name: 'list',
11
+ aliases: ['ls'],
12
+ description: 'List all environment variables',
13
+ requiresAuth: true,
14
+ schema: {
15
+ options: z.object({
16
+ dir: z.string().optional().describe('project directory (default: current directory)'),
17
+ mask: z
18
+ .boolean()
19
+ .default(false)
20
+ .describe('mask the values in output (default: false for env vars)'),
21
+ }),
22
+ },
23
+
24
+ async handler(ctx) {
25
+ const { opts, config } = ctx;
26
+ const dir = opts?.dir ?? process.cwd();
27
+
28
+ // Load project config to get project ID
29
+ const projectConfig = await loadProjectConfig(dir);
30
+ if (!projectConfig) {
31
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
32
+ }
33
+
34
+ const apiUrl = getAPIBaseURL(config);
35
+ const client = new APIClient(apiUrl, config);
36
+
37
+ // Fetch project with unmasked secrets
38
+ const project = await tui.spinner('Fetching environment variables', () => {
39
+ return projectGet(client, { id: projectConfig.projectId, mask: false });
40
+ });
41
+
42
+ const env = project.env || {};
43
+
44
+ if (Object.keys(env).length === 0) {
45
+ tui.info('No environment variables found');
46
+ return;
47
+ }
48
+
49
+ // Display the variables
50
+ if (process.stdout.isTTY) {
51
+ tui.newline();
52
+ tui.info(`Environment Variables (${Object.keys(env).length}):`);
53
+ tui.newline();
54
+ }
55
+
56
+ const sortedKeys = Object.keys(env).sort();
57
+ // For env vars, masking should be explicitly opted-in (default false)
58
+ const shouldMask = opts?.mask === true;
59
+ for (const key of sortedKeys) {
60
+ const value = env[key];
61
+ const displayValue = shouldMask ? maskSecret(value) : value;
62
+ if (process.stdout.isTTY) {
63
+ console.log(`${tui.bold(key)}=${displayValue}`);
64
+ } else {
65
+ console.log(`${key}=${displayValue}`);
66
+ }
67
+ }
68
+ },
69
+ });
@@ -0,0 +1,93 @@
1
+ import { z } from 'zod';
2
+ import { join } from 'node:path';
3
+ import { createSubcommand } from '../../types';
4
+ import * as tui from '../../tui';
5
+ import { projectGet } from '@agentuity/server';
6
+ import { getAPIBaseURL, APIClient } from '../../api';
7
+ import { loadProjectConfig } from '../../config';
8
+ import {
9
+ findEnvFile,
10
+ findExistingEnvFile,
11
+ readEnvFile,
12
+ writeEnvFile,
13
+ mergeEnvVars,
14
+ } from '../../env-util';
15
+
16
+ export const pullSubcommand = createSubcommand({
17
+ name: 'pull',
18
+ description: 'Pull environment variables from cloud to local .env.production file',
19
+ requiresAuth: true,
20
+ schema: {
21
+ options: z.object({
22
+ dir: z.string().optional().describe('project directory (default: current directory)'),
23
+ force: z.boolean().default(false).describe('overwrite local values with cloud values'),
24
+ }),
25
+ },
26
+
27
+ async handler(ctx) {
28
+ const { opts, config } = ctx;
29
+ const dir = opts?.dir ?? process.cwd();
30
+
31
+ // Load project config to get project ID
32
+ const projectConfig = await loadProjectConfig(dir);
33
+ if (!projectConfig) {
34
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
35
+ }
36
+
37
+ const apiUrl = getAPIBaseURL(config);
38
+ const client = new APIClient(apiUrl, config);
39
+
40
+ // Fetch project with unmasked secrets
41
+ const project = await tui.spinner('Pulling environment variables from cloud', () => {
42
+ return projectGet(client, { id: projectConfig.projectId, mask: false });
43
+ });
44
+
45
+ const cloudEnv = project.env || {};
46
+
47
+ // Read current local env from existing file (.env.production or .env)
48
+ const existingEnvPath = await findExistingEnvFile(dir);
49
+ const localEnv = await readEnvFile(existingEnvPath);
50
+
51
+ // Target file is always .env.production
52
+ const targetEnvPath = await findEnvFile(dir);
53
+
54
+ // Merge: cloud values override local if force=true, otherwise keep local
55
+ let mergedEnv: Record<string, string>;
56
+ if (opts?.force) {
57
+ // Cloud values take priority
58
+ mergedEnv = mergeEnvVars(localEnv, cloudEnv);
59
+ } else {
60
+ // Local values take priority (only add new keys from cloud)
61
+ mergedEnv = mergeEnvVars(cloudEnv, localEnv);
62
+ }
63
+
64
+ // Write to .env.production (skip AGENTUITY_ keys)
65
+ await writeEnvFile(targetEnvPath, mergedEnv, {
66
+ skipKeys: Object.keys(mergedEnv).filter((k) => k.startsWith('AGENTUITY_')),
67
+ });
68
+
69
+ // Write AGENTUITY_SDK_KEY to .env if present and missing locally
70
+ if (project.api_key) {
71
+ const dotEnvPath = join(dir, '.env');
72
+ const dotEnv = await readEnvFile(dotEnvPath);
73
+
74
+ if (!dotEnv.AGENTUITY_SDK_KEY) {
75
+ dotEnv.AGENTUITY_SDK_KEY = project.api_key;
76
+ await writeEnvFile(dotEnvPath, dotEnv, {
77
+ addComment: (key) => {
78
+ if (key === 'AGENTUITY_SDK_KEY') {
79
+ return 'AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
80
+ }
81
+ return null;
82
+ },
83
+ });
84
+ tui.info(`Wrote AGENTUITY_SDK_KEY to ${dotEnvPath}`);
85
+ }
86
+ }
87
+
88
+ const count = Object.keys(cloudEnv).length;
89
+ tui.success(
90
+ `Pulled ${count} environment variable${count !== 1 ? 's' : ''} to ${targetEnvPath}`
91
+ );
92
+ },
93
+ });
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectEnvUpdate } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
6
+ import { loadProjectConfig } from '../../config';
7
+ import { findExistingEnvFile, readEnvFile, filterAgentuitySdkKeys } from '../../env-util';
8
+
9
+ export const pushSubcommand = createSubcommand({
10
+ name: 'push',
11
+ description: 'Push environment variables from local .env.production file to cloud',
12
+ requiresAuth: true,
13
+ schema: {
14
+ options: z.object({
15
+ dir: z.string().optional().describe('project directory (default: current directory)'),
16
+ }),
17
+ },
18
+
19
+ async handler(ctx) {
20
+ const { opts, config } = ctx;
21
+ const dir = opts?.dir ?? process.cwd();
22
+
23
+ // Load project config to get project ID
24
+ const projectConfig = await loadProjectConfig(dir);
25
+ if (!projectConfig) {
26
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
27
+ }
28
+
29
+ // Read local env file (prefer .env.production, fallback to .env)
30
+ const envFilePath = await findExistingEnvFile(dir);
31
+ const localEnv = await readEnvFile(envFilePath);
32
+
33
+ // Filter out AGENTUITY_ prefixed keys (don't push SDK keys)
34
+ const filteredEnv = filterAgentuitySdkKeys(localEnv);
35
+
36
+ if (Object.keys(filteredEnv).length === 0) {
37
+ tui.warning('No environment variables to push');
38
+ return;
39
+ }
40
+
41
+ const apiUrl = getAPIBaseURL(config);
42
+ const client = new APIClient(apiUrl, config);
43
+
44
+ // Push to cloud
45
+ await tui.spinner('Pushing environment variables to cloud', () => {
46
+ return projectEnvUpdate(client, {
47
+ id: projectConfig.projectId,
48
+ env: filteredEnv,
49
+ });
50
+ });
51
+
52
+ const count = Object.keys(filteredEnv).length;
53
+ tui.success(`Pushed ${count} environment variable${count !== 1 ? 's' : ''} to cloud`);
54
+ },
55
+ });