@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
@@ -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
+ });
@@ -31,7 +31,7 @@ export const createProjectSubcommand = createSubcommand({
31
31
  .optional()
32
32
  .default(true)
33
33
  .describe('Run bun run build after installing (use --no-build to skip)'),
34
- confirm: z.boolean().optional().describe('Show confirmation prompts'),
34
+ confirm: z.boolean().optional().describe('Skip confirmation prompts'),
35
35
  register: z
36
36
  .boolean()
37
37
  .default(true)
@@ -1,13 +1,54 @@
1
+ import { z } from 'zod';
1
2
  import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectDelete } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
2
6
 
3
7
  export const deleteSubcommand = createSubcommand({
4
8
  name: 'delete',
5
9
  description: 'Delete a project',
6
10
  aliases: ['rm', 'del'],
7
11
  requiresAuth: true,
12
+ schema: {
13
+ args: z.object({
14
+ id: z.string().describe('the project id'),
15
+ }),
16
+ options: z.object({
17
+ confirm: z.boolean().optional().describe('Skip confirmation prompts'),
18
+ }),
19
+ },
8
20
 
9
21
  async handler(ctx) {
10
- const { logger } = ctx;
11
- logger.info('TODO: Implement project delete functionality');
22
+ const { args, opts, config } = ctx;
23
+
24
+ const apiUrl = getAPIBaseURL(config);
25
+ const client = new APIClient(apiUrl, config);
26
+
27
+ const skipConfirm = opts?.confirm === true;
28
+
29
+ if (!process.stdout.isTTY && !skipConfirm) {
30
+ tui.fatal('no TTY and --confirm is false');
31
+ }
32
+
33
+ if (!skipConfirm) {
34
+ const ok = await tui.confirm('Are you sure you want to delete', false);
35
+ if (!ok) {
36
+ return;
37
+ }
38
+ }
39
+
40
+ const deleted = await tui.spinner('Deleting project', async () => {
41
+ const val = await projectDelete(client!, args.id);
42
+ if (val.length === 1 && val[0] === args.id) {
43
+ return true;
44
+ }
45
+ return false;
46
+ });
47
+
48
+ if (deleted) {
49
+ tui.success(`Project ${args.id} deleted`);
50
+ } else {
51
+ tui.warning(`${args.id} not found`);
52
+ }
12
53
  },
13
54
  });
@@ -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';
@@ -1,13 +1,44 @@
1
+ import { z } from 'zod';
1
2
  import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectList } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
2
6
 
3
7
  export const listSubcommand = createSubcommand({
4
8
  name: 'list',
5
9
  description: 'List all projects',
6
10
  aliases: ['ls'],
7
11
  requiresAuth: true,
12
+ schema: {
13
+ options: z.object({
14
+ format: z
15
+ .enum(['json', 'table'])
16
+ .optional()
17
+ .describe('the output format: json, table (default)'),
18
+ }),
19
+ },
8
20
 
9
21
  async handler(ctx) {
10
- const { logger } = ctx;
11
- logger.info('TODO: Implement project list functionality');
22
+ const { config, opts } = ctx;
23
+
24
+ const apiUrl = getAPIBaseURL(config);
25
+ const client = new APIClient(apiUrl, config);
26
+
27
+ const projects = await tui.spinner('Fetching projects', () => {
28
+ return projectList(client!);
29
+ });
30
+
31
+ // TODO: might want to sort by the last org_id we used
32
+ if (projects) {
33
+ projects.sort((a, b) => {
34
+ return a.name.localeCompare(b.name);
35
+ });
36
+ }
37
+
38
+ if (opts?.format === 'json') {
39
+ console.log(JSON.stringify(projects, null, 2));
40
+ } else {
41
+ console.table(projects, ['id', 'name', 'orgName']);
42
+ }
12
43
  },
13
44
  });
@@ -1,12 +1,44 @@
1
+ import { z } from 'zod';
1
2
  import { createSubcommand } from '../../types';
3
+ import * as tui from '../../tui';
4
+ import { projectGet } from '@agentuity/server';
5
+ import { getAPIBaseURL, APIClient } from '../../api';
2
6
 
3
7
  export const showSubcommand = createSubcommand({
4
8
  name: 'show',
5
- description: 'Show project details',
9
+ aliases: ['get'],
10
+ description: 'Show project detail',
6
11
  requiresAuth: true,
12
+ schema: {
13
+ args: z.object({
14
+ id: z.string().describe('the project id'),
15
+ }),
16
+ options: z.object({
17
+ format: z
18
+ .enum(['json', 'table'])
19
+ .optional()
20
+ .describe('the output format: json, table (default)'),
21
+ }),
22
+ },
7
23
 
8
24
  async handler(ctx) {
9
- const { logger } = ctx;
10
- logger.info('TODO: Implement project show functionality');
25
+ const { opts, args, config } = ctx;
26
+
27
+ const apiUrl = getAPIBaseURL(config);
28
+ const client = new APIClient(apiUrl, config);
29
+
30
+ const project = await tui.spinner('Fetching project', () => {
31
+ return projectGet(client!, { id: args.id, mask: true });
32
+ });
33
+
34
+ if (!project) {
35
+ tui.fatal('Project not found');
36
+ }
37
+
38
+ if (opts?.format === 'json') {
39
+ console.log(JSON.stringify(project, null, 2));
40
+ } else {
41
+ console.table([project], ['id', 'orgId']);
42
+ }
11
43
  },
12
44
  });
@@ -4,12 +4,13 @@ import { cwd } from 'node:process';
4
4
  import { homedir } from 'node:os';
5
5
  import enquirer from 'enquirer';
6
6
  import {
7
- createProject,
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;
@@ -53,10 +60,8 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
53
60
  tui.info(`📋 Loading templates from local directory: ${templateDir}...\n`);
54
61
  }
55
62
 
56
- let templates: TemplateInfo[] = [];
57
-
58
- await tui.spinner('Fetching templates', async () => {
59
- templates = await fetchTemplates(templateDir, templateBranch);
63
+ const templates = await tui.spinner('Fetching templates', async () => {
64
+ return fetchTemplates(templateDir, templateBranch);
60
65
  });
61
66
 
62
67
  if (templates.length === 0) {
@@ -73,13 +78,20 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
73
78
  if (auth) {
74
79
  const apiUrl = getAPIBaseURL(config);
75
80
  client = new APIClient(apiUrl, config);
76
- await tui.spinner('Fetching organizations', async () => {
81
+ orgs = await tui.spinner('Fetching organizations', async () => {
77
82
  const resp = await listOrganizations(client!);
78
83
  if (resp.data) {
79
- orgs = resp.data;
84
+ return resp.data;
80
85
  }
81
86
  });
82
- orgId = await tui.selectOrganization(orgs!, config?.preferences?.orgId);
87
+ if (!orgs) {
88
+ tui.fatal('no organizations could be found for your login');
89
+ }
90
+ orgId = await tui.selectOrganization(orgs, config?.preferences?.orgId);
91
+
92
+ if (orgId && orgId !== config?.preferences?.orgId) {
93
+ await saveOrgId(orgId);
94
+ }
83
95
  }
84
96
 
85
97
  if (!projectName && !skipPrompts) {
@@ -200,21 +212,50 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
200
212
  });
201
213
 
202
214
  if (auth && client && orgId) {
215
+ let projectId: string | undefined;
216
+
203
217
  await tui.spinner('Registering your project', async () => {
204
- const res = await createProject(client, {
218
+ const res = await projectCreate(client, {
205
219
  name: projectName,
206
220
  organization_id: orgId,
207
221
  provider: 'bunjs',
208
222
  });
209
223
  if (res.success && res.data) {
224
+ projectId = res.data.id;
210
225
  return createProjectConfig(dest, {
211
226
  projectId: res.data.id,
212
227
  orgId,
213
228
  apiKey: res.data.api_key,
214
229
  });
215
230
  }
216
- tui.fatal(res.message!);
231
+ tui.fatal(res.message ?? 'failed to register project');
217
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
+ }
218
259
  }
219
260
 
220
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
+ });