@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.
- package/bin/cli.ts +7 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cmd/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/whoami.d.ts +2 -0
- package/dist/cmd/auth/whoami.d.ts.map +1 -0
- package/dist/cmd/bundle/index.d.ts +1 -1
- package/dist/cmd/bundle/index.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts +2 -0
- package/dist/cmd/cloud/deploy.d.ts.map +1 -0
- package/dist/cmd/cloud/index.d.ts +2 -0
- package/dist/cmd/cloud/index.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/env/delete.d.ts +2 -0
- package/dist/cmd/env/delete.d.ts.map +1 -0
- package/dist/cmd/env/get.d.ts +2 -0
- package/dist/cmd/env/get.d.ts.map +1 -0
- package/dist/cmd/env/import.d.ts +2 -0
- package/dist/cmd/env/import.d.ts.map +1 -0
- package/dist/cmd/env/index.d.ts +2 -0
- package/dist/cmd/env/index.d.ts.map +1 -0
- package/dist/cmd/env/list.d.ts +2 -0
- package/dist/cmd/env/list.d.ts.map +1 -0
- package/dist/cmd/env/pull.d.ts +2 -0
- package/dist/cmd/env/pull.d.ts.map +1 -0
- package/dist/cmd/env/push.d.ts +2 -0
- package/dist/cmd/env/push.d.ts.map +1 -0
- package/dist/cmd/env/set.d.ts +2 -0
- package/dist/cmd/env/set.d.ts.map +1 -0
- package/dist/cmd/project/download.d.ts +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/secret/delete.d.ts +2 -0
- package/dist/cmd/secret/delete.d.ts.map +1 -0
- package/dist/cmd/secret/get.d.ts +2 -0
- package/dist/cmd/secret/get.d.ts.map +1 -0
- package/dist/cmd/secret/import.d.ts +2 -0
- package/dist/cmd/secret/import.d.ts.map +1 -0
- package/dist/cmd/secret/index.d.ts +2 -0
- package/dist/cmd/secret/index.d.ts.map +1 -0
- package/dist/cmd/secret/list.d.ts +2 -0
- package/dist/cmd/secret/list.d.ts.map +1 -0
- package/dist/cmd/secret/pull.d.ts +2 -0
- package/dist/cmd/secret/pull.d.ts.map +1 -0
- package/dist/cmd/secret/push.d.ts +2 -0
- package/dist/cmd/secret/push.d.ts.map +1 -0
- package/dist/cmd/secret/set.d.ts +2 -0
- package/dist/cmd/secret/set.d.ts.map +1 -0
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/env-util.d.ts +67 -0
- package/dist/env-util.d.ts.map +1 -0
- package/dist/env-util.test.d.ts +2 -0
- package/dist/env-util.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/schema-parser.d.ts.map +1 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/tui.d.ts +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +45 -4
- package/src/cmd/auth/index.ts +2 -1
- package/src/cmd/auth/whoami.ts +69 -0
- package/src/cmd/bundle/index.ts +2 -2
- package/src/cmd/cloud/deploy.ts +129 -0
- package/src/cmd/cloud/index.ts +8 -0
- package/src/cmd/dev/index.ts +5 -3
- package/src/cmd/env/delete.ts +62 -0
- package/src/cmd/env/get.ts +66 -0
- package/src/cmd/env/import.ts +117 -0
- package/src/cmd/env/index.ts +22 -0
- package/src/cmd/env/list.ts +69 -0
- package/src/cmd/env/pull.ts +93 -0
- package/src/cmd/env/push.ts +55 -0
- package/src/cmd/env/set.ts +86 -0
- package/src/cmd/project/download.ts +1 -1
- package/src/cmd/project/template-flow.ts +42 -2
- package/src/cmd/secret/delete.ts +55 -0
- package/src/cmd/secret/get.ts +67 -0
- package/src/cmd/secret/import.ts +79 -0
- package/src/cmd/secret/index.ts +22 -0
- package/src/cmd/secret/list.ts +69 -0
- package/src/cmd/secret/pull.ts +91 -0
- package/src/cmd/secret/push.ts +55 -0
- package/src/cmd/secret/set.ts +60 -0
- package/src/cmd/version/index.ts +2 -1
- package/src/config.ts +35 -5
- package/src/env-util.test.ts +194 -0
- package/src/env-util.ts +290 -0
- package/src/index.ts +5 -1
- package/src/schema-parser.ts +2 -3
- package/src/steps.ts +79 -4
- package/src/tui.ts +18 -9
- package/src/types.ts +1 -1
- package/dist/logger.d.ts +0 -24
- package/dist/logger.d.ts.map +0 -1
- 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 '
|
|
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 '
|
|
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
|
+
});
|