@agentuity/cli 0.0.42 → 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 (101) hide show
  1. package/bin/cli.ts +7 -5
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cmd/auth/index.d.ts.map +1 -1
  4. package/dist/cmd/auth/whoami.d.ts +2 -0
  5. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  6. package/dist/cmd/bundle/index.d.ts +1 -1
  7. package/dist/cmd/bundle/index.d.ts.map +1 -1
  8. package/dist/cmd/cloud/deploy.d.ts +2 -0
  9. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  10. package/dist/cmd/cloud/index.d.ts +2 -0
  11. package/dist/cmd/cloud/index.d.ts.map +1 -0
  12. package/dist/cmd/dev/index.d.ts.map +1 -1
  13. package/dist/cmd/env/delete.d.ts +2 -0
  14. package/dist/cmd/env/delete.d.ts.map +1 -0
  15. package/dist/cmd/env/get.d.ts +2 -0
  16. package/dist/cmd/env/get.d.ts.map +1 -0
  17. package/dist/cmd/env/import.d.ts +2 -0
  18. package/dist/cmd/env/import.d.ts.map +1 -0
  19. package/dist/cmd/env/index.d.ts +2 -0
  20. package/dist/cmd/env/index.d.ts.map +1 -0
  21. package/dist/cmd/env/list.d.ts +2 -0
  22. package/dist/cmd/env/list.d.ts.map +1 -0
  23. package/dist/cmd/env/pull.d.ts +2 -0
  24. package/dist/cmd/env/pull.d.ts.map +1 -0
  25. package/dist/cmd/env/push.d.ts +2 -0
  26. package/dist/cmd/env/push.d.ts.map +1 -0
  27. package/dist/cmd/env/set.d.ts +2 -0
  28. package/dist/cmd/env/set.d.ts.map +1 -0
  29. package/dist/cmd/project/download.d.ts +1 -1
  30. package/dist/cmd/project/download.d.ts.map +1 -1
  31. package/dist/cmd/project/template-flow.d.ts +1 -1
  32. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  33. package/dist/cmd/secret/delete.d.ts +2 -0
  34. package/dist/cmd/secret/delete.d.ts.map +1 -0
  35. package/dist/cmd/secret/get.d.ts +2 -0
  36. package/dist/cmd/secret/get.d.ts.map +1 -0
  37. package/dist/cmd/secret/import.d.ts +2 -0
  38. package/dist/cmd/secret/import.d.ts.map +1 -0
  39. package/dist/cmd/secret/index.d.ts +2 -0
  40. package/dist/cmd/secret/index.d.ts.map +1 -0
  41. package/dist/cmd/secret/list.d.ts +2 -0
  42. package/dist/cmd/secret/list.d.ts.map +1 -0
  43. package/dist/cmd/secret/pull.d.ts +2 -0
  44. package/dist/cmd/secret/pull.d.ts.map +1 -0
  45. package/dist/cmd/secret/push.d.ts +2 -0
  46. package/dist/cmd/secret/push.d.ts.map +1 -0
  47. package/dist/cmd/secret/set.d.ts +2 -0
  48. package/dist/cmd/secret/set.d.ts.map +1 -0
  49. package/dist/cmd/version/index.d.ts.map +1 -1
  50. package/dist/config.d.ts +2 -0
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/env-util.d.ts +67 -0
  53. package/dist/env-util.d.ts.map +1 -0
  54. package/dist/env-util.test.d.ts +2 -0
  55. package/dist/env-util.test.d.ts.map +1 -0
  56. package/dist/index.d.ts +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/schema-parser.d.ts.map +1 -1
  59. package/dist/steps.d.ts.map +1 -1
  60. package/dist/tui.d.ts +1 -1
  61. package/dist/tui.d.ts.map +1 -1
  62. package/dist/types.d.ts +1 -1
  63. package/dist/types.d.ts.map +1 -1
  64. package/package.json +1 -1
  65. package/src/cli.ts +45 -4
  66. package/src/cmd/auth/index.ts +2 -1
  67. package/src/cmd/auth/whoami.ts +69 -0
  68. package/src/cmd/bundle/index.ts +2 -2
  69. package/src/cmd/cloud/deploy.ts +129 -0
  70. package/src/cmd/cloud/index.ts +8 -0
  71. package/src/cmd/dev/index.ts +5 -3
  72. package/src/cmd/env/delete.ts +62 -0
  73. package/src/cmd/env/get.ts +66 -0
  74. package/src/cmd/env/import.ts +117 -0
  75. package/src/cmd/env/index.ts +22 -0
  76. package/src/cmd/env/list.ts +69 -0
  77. package/src/cmd/env/pull.ts +93 -0
  78. package/src/cmd/env/push.ts +55 -0
  79. package/src/cmd/env/set.ts +86 -0
  80. package/src/cmd/project/download.ts +1 -1
  81. package/src/cmd/project/template-flow.ts +42 -2
  82. package/src/cmd/secret/delete.ts +55 -0
  83. package/src/cmd/secret/get.ts +67 -0
  84. package/src/cmd/secret/import.ts +79 -0
  85. package/src/cmd/secret/index.ts +22 -0
  86. package/src/cmd/secret/list.ts +69 -0
  87. package/src/cmd/secret/pull.ts +91 -0
  88. package/src/cmd/secret/push.ts +55 -0
  89. package/src/cmd/secret/set.ts +60 -0
  90. package/src/cmd/version/index.ts +2 -1
  91. package/src/config.ts +35 -5
  92. package/src/env-util.test.ts +194 -0
  93. package/src/env-util.ts +290 -0
  94. package/src/index.ts +5 -1
  95. package/src/schema-parser.ts +2 -3
  96. package/src/steps.ts +79 -4
  97. package/src/tui.ts +18 -9
  98. package/src/types.ts +1 -1
  99. package/dist/logger.d.ts +0 -24
  100. package/dist/logger.d.ts.map +0 -1
  101. package/src/logger.ts +0 -235
@@ -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
+ });
@@ -0,0 +1,86 @@
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
+ looksLikeSecret,
13
+ } from '../../env-util';
14
+ import { getCommand } from '../../command-prefix';
15
+
16
+ export const setSubcommand = createSubcommand({
17
+ name: 'set',
18
+ description: 'Set an environment variable',
19
+ requiresAuth: true,
20
+ schema: {
21
+ args: z.object({
22
+ key: z.string().describe('the environment variable key'),
23
+ value: z.string().describe('the environment variable value'),
24
+ }),
25
+ options: z.object({
26
+ dir: z.string().optional().describe('project directory (default: current directory)'),
27
+ }),
28
+ },
29
+
30
+ async handler(ctx) {
31
+ const { args, opts, config } = ctx;
32
+ const dir = opts?.dir ?? process.cwd();
33
+
34
+ // Validate key doesn't start with AGENTUITY_
35
+ if (args.key.startsWith('AGENTUITY_')) {
36
+ tui.fatal('Cannot set AGENTUITY_ prefixed variables. These are reserved for system use.');
37
+ }
38
+
39
+ // Detect if this looks like a secret
40
+ if (looksLikeSecret(args.key, args.value)) {
41
+ tui.warning(`The variable '${args.key}' looks like it should be a secret.`);
42
+ tui.info(`Secrets should be stored using: ${getCommand('secret set <key> <value>')}`);
43
+ tui.info('This keeps them more secure and properly masked in the cloud.');
44
+
45
+ const response = await tui.confirm(
46
+ 'Do you still want to store this as a regular environment variable?',
47
+ false
48
+ );
49
+
50
+ if (!response) {
51
+ tui.info(
52
+ `Cancelled. Use "${getCommand('secret set')}" to store this as a secret instead.`
53
+ );
54
+ return;
55
+ }
56
+ }
57
+
58
+ // Load project config to get project ID
59
+ const projectConfig = await loadProjectConfig(dir);
60
+ if (!projectConfig) {
61
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
62
+ }
63
+
64
+ const apiUrl = getAPIBaseURL(config);
65
+ const client = new APIClient(apiUrl, config);
66
+
67
+ // Set in cloud
68
+ await tui.spinner('Setting environment variable in cloud', () => {
69
+ return projectEnvUpdate(client, {
70
+ id: projectConfig.projectId,
71
+ env: { [args.key]: args.value },
72
+ });
73
+ });
74
+
75
+ // Update local .env.production file
76
+ const envFilePath = await findEnvFile(dir);
77
+ const currentEnv = await readEnvFile(envFilePath);
78
+ currentEnv[args.key] = args.value;
79
+
80
+ // Filter out AGENTUITY_ keys before writing
81
+ const filteredEnv = filterAgentuitySdkKeys(currentEnv);
82
+ await writeEnvFile(envFilePath, filteredEnv);
83
+
84
+ tui.success(`Environment variable '${args.key}' set successfully (cloud + ${envFilePath})`);
85
+ },
86
+ });
@@ -13,7 +13,7 @@ import { tmpdir } from 'node:os';
13
13
  import { finished } from 'node:stream/promises';
14
14
  import { createGunzip } from 'node:zlib';
15
15
  import { extract, type Headers } from 'tar-fs';
16
- import type { Logger } from '../../logger';
16
+ import type { Logger } from '@agentuity/core';
17
17
  import * as tui from '../../tui';
18
18
  import { downloadWithSpinner } from '../../download';
19
19
  import type { TemplateInfo } from './templates';
@@ -7,9 +7,10 @@ import {
7
7
  projectCreate,
8
8
  projectExists,
9
9
  listOrganizations,
10
+ projectEnvUpdate,
10
11
  type OrganizationList,
11
12
  } from '@agentuity/server';
12
- import type { Logger } from '../../logger';
13
+ import type { Logger } from '@agentuity/core';
13
14
  import * as tui from '../../tui';
14
15
  import { playSound } from '../../sound';
15
16
  import { fetchTemplates, type TemplateInfo } from './templates';
@@ -17,7 +18,13 @@ import { downloadTemplate, setupProject } from './download';
17
18
  import { showBanner } from '../../banner';
18
19
  import type { AuthData, Config } from '../../types';
19
20
  import { getAPIBaseURL, APIClient } from '../../api';
20
- import { createProjectConfig } from '../../config';
21
+ import { createProjectConfig, saveOrgId } from '../../config';
22
+ import {
23
+ findEnvFile,
24
+ readEnvFile,
25
+ filterAgentuitySdkKeys,
26
+ splitEnvAndSecrets,
27
+ } from '../../env-util';
21
28
 
22
29
  interface CreateFlowOptions {
23
30
  projectName?: string;
@@ -81,6 +88,10 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
81
88
  tui.fatal('no organizations could be found for your login');
82
89
  }
83
90
  orgId = await tui.selectOrganization(orgs, config?.preferences?.orgId);
91
+
92
+ if (orgId && orgId !== config?.preferences?.orgId) {
93
+ await saveOrgId(orgId);
94
+ }
84
95
  }
85
96
 
86
97
  if (!projectName && !skipPrompts) {
@@ -201,6 +212,8 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
201
212
  });
202
213
 
203
214
  if (auth && client && orgId) {
215
+ let projectId: string | undefined;
216
+
204
217
  await tui.spinner('Registering your project', async () => {
205
218
  const res = await projectCreate(client, {
206
219
  name: projectName,
@@ -208,6 +221,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
208
221
  provider: 'bunjs',
209
222
  });
210
223
  if (res.success && res.data) {
224
+ projectId = res.data.id;
211
225
  return createProjectConfig(dest, {
212
226
  projectId: res.data.id,
213
227
  orgId,
@@ -216,6 +230,32 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
216
230
  }
217
231
  tui.fatal(res.message ?? 'failed to register project');
218
232
  });
233
+
234
+ // After registration, push any existing env/secrets from .env.production
235
+ if (projectId) {
236
+ await tui.spinner('Syncing environment variables', async () => {
237
+ try {
238
+ const envFilePath = await findEnvFile(dest);
239
+ const localEnv = await readEnvFile(envFilePath);
240
+ const filteredEnv = filterAgentuitySdkKeys(localEnv);
241
+
242
+ if (Object.keys(filteredEnv).length > 0) {
243
+ const { env, secrets } = splitEnvAndSecrets(filteredEnv);
244
+ await projectEnvUpdate(client, {
245
+ id: projectId!,
246
+ env,
247
+ secrets,
248
+ });
249
+ logger.debug(
250
+ `Synced ${Object.keys(filteredEnv).length} environment variables to cloud`
251
+ );
252
+ }
253
+ } catch (error) {
254
+ // Non-fatal: just log the error
255
+ logger.debug('Failed to sync environment variables:', error);
256
+ }
257
+ });
258
+ }
219
259
  }
220
260
 
221
261
  // Step 8: Show completion message
@@ -0,0 +1,55 @@
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 { findEnvFile, readEnvFile, writeEnvFile, filterAgentuitySdkKeys } from '../../env-util';
8
+
9
+ export const deleteSubcommand = createSubcommand({
10
+ name: 'delete',
11
+ aliases: ['del', 'remove', 'rm'],
12
+ description: 'Delete a secret',
13
+ requiresAuth: true,
14
+ schema: {
15
+ args: z.object({
16
+ key: z.string().describe('the secret key to delete'),
17
+ }),
18
+ options: z.object({
19
+ dir: z.string().optional().describe('project directory (default: current directory)'),
20
+ }),
21
+ },
22
+
23
+ async handler(ctx) {
24
+ const { args, opts, config } = ctx;
25
+ const dir = opts?.dir ?? process.cwd();
26
+
27
+ // Load project config to get project ID
28
+ const projectConfig = await loadProjectConfig(dir);
29
+ if (!projectConfig) {
30
+ tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
31
+ }
32
+
33
+ const apiUrl = getAPIBaseURL(config);
34
+ const client = new APIClient(apiUrl, config);
35
+
36
+ // Delete from cloud (using secrets field)
37
+ await tui.spinner('Deleting secret from cloud', () => {
38
+ return projectEnvDelete(client, {
39
+ id: projectConfig.projectId,
40
+ secrets: [args.key],
41
+ });
42
+ });
43
+
44
+ // Update local .env.production file
45
+ const envFilePath = await findEnvFile(dir);
46
+ const currentEnv = await readEnvFile(envFilePath);
47
+ delete currentEnv[args.key];
48
+
49
+ // Filter out AGENTUITY_ keys before writing
50
+ const filteredEnv = filterAgentuitySdkKeys(currentEnv);
51
+ await writeEnvFile(envFilePath, filteredEnv);
52
+
53
+ tui.success(`Secret '${args.key}' deleted successfully (cloud + ${envFilePath})`);
54
+ },
55
+ });
@@ -0,0 +1,67 @@
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 a secret value',
12
+ requiresAuth: true,
13
+ schema: {
14
+ args: z.object({
15
+ key: z.string().describe('the secret key'),
16
+ }),
17
+ options: z.object({
18
+ dir: z.string().optional().describe('project directory (default: current directory)'),
19
+ mask: z
20
+ .boolean()
21
+ .default(!!process.stdout.isTTY)
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 secrets', () => {
41
+ return projectGet(client, { id: projectConfig.projectId, mask: false });
42
+ });
43
+
44
+ // Look for the key in secrets
45
+ const value = project.secrets?.[args.key];
46
+
47
+ if (value === undefined) {
48
+ tui.fatal(`Secret '${args.key}' not found`);
49
+ }
50
+
51
+ if (process.stdout.isTTY) {
52
+ // Display the value, masked by default
53
+ if (opts?.mask) {
54
+ tui.success(`${args.key}=${maskSecret(value)}`);
55
+ } else {
56
+ tui.success(`${args.key}=${value}`);
57
+ }
58
+ } else {
59
+ // Display the value, masked by default
60
+ if (opts?.mask) {
61
+ console.log(`${args.key}=${maskSecret(value)}`);
62
+ } else {
63
+ console.log(`${args.key}=${value}`);
64
+ }
65
+ }
66
+ },
67
+ });
@@ -0,0 +1,79 @@
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
+ } from '../../env-util';
14
+
15
+ export const importSubcommand = createSubcommand({
16
+ name: 'import',
17
+ description: 'Import secrets from a file to cloud and local .env.production',
18
+ requiresAuth: true,
19
+ schema: {
20
+ args: z.object({
21
+ file: z.string().describe('path to the .env file to import'),
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
+ // Read the import file
39
+ const importedSecrets = await readEnvFile(args.file);
40
+
41
+ if (Object.keys(importedSecrets).length === 0) {
42
+ tui.warning(`No secrets found in ${args.file}`);
43
+ return;
44
+ }
45
+
46
+ // Filter out AGENTUITY_ prefixed keys
47
+ const filteredSecrets = filterAgentuitySdkKeys(importedSecrets);
48
+
49
+ if (Object.keys(filteredSecrets).length === 0) {
50
+ tui.warning('No valid secrets to import (all were AGENTUITY_ prefixed)');
51
+ return;
52
+ }
53
+
54
+ const apiUrl = getAPIBaseURL(config);
55
+ const client = new APIClient(apiUrl, config);
56
+
57
+ // Push to cloud (using secrets field)
58
+ await tui.spinner('Importing secrets to cloud', () => {
59
+ return projectEnvUpdate(client, {
60
+ id: projectConfig.projectId,
61
+ secrets: filteredSecrets,
62
+ });
63
+ });
64
+
65
+ // Merge with local .env.production file
66
+ const localEnvPath = await findEnvFile(dir);
67
+ const localEnv = await readEnvFile(localEnvPath);
68
+ const mergedEnv = mergeEnvVars(localEnv, filteredSecrets);
69
+
70
+ await writeEnvFile(localEnvPath, mergedEnv, {
71
+ skipKeys: Object.keys(mergedEnv).filter((k) => k.startsWith('AGENTUITY_')),
72
+ });
73
+
74
+ const count = Object.keys(filteredSecrets).length;
75
+ tui.success(
76
+ `Imported ${count} secret${count !== 1 ? 's' : ''} from ${args.file} to cloud and ${localEnvPath}`
77
+ );
78
+ },
79
+ });
@@ -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: 'secret',
12
+ description: 'Manage secrets 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 secrets',
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(!!process.stdout.isTTY)
20
+ .describe('mask the values in output (default: true in TTY for secrets)'),
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 secrets', () => {
39
+ return projectGet(client, { id: projectConfig.projectId, mask: false });
40
+ });
41
+
42
+ const secrets = project.secrets || {};
43
+
44
+ if (Object.keys(secrets).length === 0) {
45
+ tui.info('No secrets found');
46
+ return;
47
+ }
48
+
49
+ // Display the secrets
50
+ if (process.stdout.isTTY) {
51
+ tui.newline();
52
+ tui.success(`Secrets (${Object.keys(secrets).length}):`);
53
+ tui.newline();
54
+ }
55
+
56
+ const sortedKeys = Object.keys(secrets).sort();
57
+ // For secrets, masking is enabled by default in TTY (can be disabled with --no-mask)
58
+ const shouldMask = opts?.mask !== false;
59
+ for (const key of sortedKeys) {
60
+ const value = secrets[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,91 @@
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 secrets 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 secrets from cloud', () => {
42
+ return projectGet(client, { id: projectConfig.projectId, mask: false });
43
+ });
44
+
45
+ const cloudSecrets = project.secrets || {};
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, cloudSecrets);
59
+ } else {
60
+ // Local values take priority (only add new keys from cloud)
61
+ mergedEnv = mergeEnvVars(cloudSecrets, 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(cloudSecrets).length;
89
+ tui.success(`Pulled ${count} secret${count !== 1 ? 's' : ''} to ${targetEnvPath}`);
90
+ },
91
+ });
@@ -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 { findEnvFile, readEnvFile, filterAgentuitySdkKeys } from '../../env-util';
8
+
9
+ export const pushSubcommand = createSubcommand({
10
+ name: 'push',
11
+ description: 'Push secrets 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
30
+ const envFilePath = await findEnvFile(dir);
31
+ const localEnv = await readEnvFile(envFilePath);
32
+
33
+ // Filter out AGENTUITY_ prefixed keys (don't push SDK keys)
34
+ const filteredSecrets = filterAgentuitySdkKeys(localEnv);
35
+
36
+ if (Object.keys(filteredSecrets).length === 0) {
37
+ tui.warning('No secrets to push');
38
+ return;
39
+ }
40
+
41
+ const apiUrl = getAPIBaseURL(config);
42
+ const client = new APIClient(apiUrl, config);
43
+
44
+ // Push to cloud (using secrets field)
45
+ await tui.spinner('Pushing secrets to cloud', () => {
46
+ return projectEnvUpdate(client, {
47
+ id: projectConfig.projectId,
48
+ secrets: filteredSecrets,
49
+ });
50
+ });
51
+
52
+ const count = Object.keys(filteredSecrets).length;
53
+ tui.success(`Pushed ${count} secret${count !== 1 ? 's' : ''} to cloud`);
54
+ },
55
+ });