@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,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createSubcommand } from '../../types';
|
|
3
|
+
import * as tui from '../../tui';
|
|
4
|
+
import { whoami } from '@agentuity/server';
|
|
5
|
+
import { getAPIBaseURL, APIClient } from '../../api';
|
|
6
|
+
|
|
7
|
+
export const whoamiCommand = createSubcommand({
|
|
8
|
+
name: 'whoami',
|
|
9
|
+
description: 'Display information about the currently authenticated user',
|
|
10
|
+
requiresAuth: true,
|
|
11
|
+
schema: {
|
|
12
|
+
options: z.object({
|
|
13
|
+
format: z
|
|
14
|
+
.enum(['json', 'table'])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('the output format: json, table (default)'),
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async handler(ctx) {
|
|
21
|
+
const { config, opts, auth } = ctx;
|
|
22
|
+
|
|
23
|
+
const apiUrl = getAPIBaseURL(config);
|
|
24
|
+
const client = new APIClient(apiUrl, config);
|
|
25
|
+
|
|
26
|
+
const result = await tui.spinner('Fetching user information', () => {
|
|
27
|
+
return whoami(client!);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!result.data) {
|
|
31
|
+
tui.fatal('Failed to get user information');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const user = result.data;
|
|
35
|
+
|
|
36
|
+
if (opts?.format === 'json') {
|
|
37
|
+
console.log(
|
|
38
|
+
JSON.stringify(
|
|
39
|
+
{
|
|
40
|
+
userId: auth?.userId,
|
|
41
|
+
firstName: user.firstName,
|
|
42
|
+
lastName: user.lastName,
|
|
43
|
+
organizations: user.organizations,
|
|
44
|
+
},
|
|
45
|
+
null,
|
|
46
|
+
2
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
} else {
|
|
50
|
+
const fullName = `${user.firstName} ${user.lastName}`;
|
|
51
|
+
|
|
52
|
+
tui.newline();
|
|
53
|
+
console.log(tui.bold('Currently logged in as:'));
|
|
54
|
+
tui.newline();
|
|
55
|
+
console.log(` ${tui.padRight('Name:', 15, ' ')} ${tui.bold(fullName)}`);
|
|
56
|
+
console.log(` ${tui.padRight('User ID:', 15, ' ')} ${tui.muted(auth?.userId || '')}`);
|
|
57
|
+
tui.newline();
|
|
58
|
+
|
|
59
|
+
if (user.organizations.length > 0) {
|
|
60
|
+
console.log(tui.bold('Organizations:'));
|
|
61
|
+
tui.newline();
|
|
62
|
+
for (const org of user.organizations) {
|
|
63
|
+
console.log(` ${tui.padRight(org.name, 30, ' ')} ${tui.muted(org.id)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
tui.newline();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
});
|
package/src/cmd/bundle/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCommand } from '../../types';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { bundle } from './bundler';
|
|
5
5
|
|
|
6
|
-
export const command =
|
|
6
|
+
export const command = createCommand({
|
|
7
7
|
name: 'bundle',
|
|
8
8
|
description: 'Bundle Agentuity application for deployment',
|
|
9
9
|
aliases: ['build'],
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createSubcommand } from '../../types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import * as tui from '../../tui';
|
|
5
|
+
import { loadProjectConfig, saveProjectDir } from '../../config';
|
|
6
|
+
import { runSteps, stepSuccess, stepSkipped, stepError } from '../../steps';
|
|
7
|
+
import { bundle } from '../bundle/bundler';
|
|
8
|
+
import { loadBuildMetadata } from '../../config';
|
|
9
|
+
import { projectEnvUpdate } from '@agentuity/server';
|
|
10
|
+
import { getAPIBaseURL, APIClient } from '../../api';
|
|
11
|
+
import {
|
|
12
|
+
findEnvFile,
|
|
13
|
+
readEnvFile,
|
|
14
|
+
filterAgentuitySdkKeys,
|
|
15
|
+
splitEnvAndSecrets,
|
|
16
|
+
} from '../../env-util';
|
|
17
|
+
|
|
18
|
+
export const deploySubcommand = createSubcommand({
|
|
19
|
+
name: 'deploy',
|
|
20
|
+
description: 'Deploy project to the Agentuity Cloud',
|
|
21
|
+
toplevel: true,
|
|
22
|
+
requiresAuth: true,
|
|
23
|
+
schema: {
|
|
24
|
+
options: z.object({
|
|
25
|
+
dir: z.string().optional().describe('Directory to use for the project'),
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async handler(ctx) {
|
|
30
|
+
const { opts, config } = ctx;
|
|
31
|
+
const dir = opts?.dir ?? process.cwd();
|
|
32
|
+
try {
|
|
33
|
+
const project = await loadProjectConfig(dir);
|
|
34
|
+
if (!project) {
|
|
35
|
+
console.log(project); // FIXME
|
|
36
|
+
}
|
|
37
|
+
await saveProjectDir(dir);
|
|
38
|
+
|
|
39
|
+
const apiUrl = getAPIBaseURL(config);
|
|
40
|
+
const client = new APIClient(apiUrl, config);
|
|
41
|
+
|
|
42
|
+
await runSteps([
|
|
43
|
+
{
|
|
44
|
+
label: 'Sync Environment Variables',
|
|
45
|
+
run: async () => {
|
|
46
|
+
try {
|
|
47
|
+
// Read local env file (.env.production or .env)
|
|
48
|
+
const envFilePath = await findEnvFile(dir);
|
|
49
|
+
const localEnv = await readEnvFile(envFilePath);
|
|
50
|
+
|
|
51
|
+
// Filter out AGENTUITY_ keys
|
|
52
|
+
const filteredEnv = filterAgentuitySdkKeys(localEnv);
|
|
53
|
+
|
|
54
|
+
if (Object.keys(filteredEnv).length === 0) {
|
|
55
|
+
return stepSkipped('no environment variables to sync');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Split into env and secrets
|
|
59
|
+
const { env, secrets } = splitEnvAndSecrets(filteredEnv);
|
|
60
|
+
|
|
61
|
+
// Push to cloud
|
|
62
|
+
await projectEnvUpdate(client, {
|
|
63
|
+
id: project.projectId,
|
|
64
|
+
env,
|
|
65
|
+
secrets,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return stepSuccess();
|
|
69
|
+
} catch (ex) {
|
|
70
|
+
// Non-fatal: log warning but continue deployment
|
|
71
|
+
const _ex = ex as Error;
|
|
72
|
+
return stepSkipped(_ex.message ?? 'failed to sync env variables');
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: 'Create Deployment',
|
|
78
|
+
run: async () => {
|
|
79
|
+
// TODO: implement
|
|
80
|
+
await Bun.sleep(1500);
|
|
81
|
+
return stepSuccess();
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'Build, Verify and Package',
|
|
86
|
+
run: async () => {
|
|
87
|
+
try {
|
|
88
|
+
await bundle({
|
|
89
|
+
rootDir: dir,
|
|
90
|
+
dev: false,
|
|
91
|
+
});
|
|
92
|
+
await loadBuildMetadata(join(dir, '.agentuity'));
|
|
93
|
+
return stepSuccess();
|
|
94
|
+
} catch (ex) {
|
|
95
|
+
const _ex = ex as Error;
|
|
96
|
+
return stepError(_ex.message ?? 'Error building your project');
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'Encrypt and Upload Deployment',
|
|
102
|
+
run: async () => {
|
|
103
|
+
// TODO: implement
|
|
104
|
+
await Bun.sleep(800);
|
|
105
|
+
return stepSkipped('already up to date');
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
label: 'Provision Services',
|
|
110
|
+
run: async () => {
|
|
111
|
+
// TODO: implement
|
|
112
|
+
await Bun.sleep(1200);
|
|
113
|
+
return stepSuccess();
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
tui.success('Your project was deployed!');
|
|
118
|
+
tui.arrow(tui.link('https://project-123455666332.agentuity.run'));
|
|
119
|
+
} catch (ex) {
|
|
120
|
+
const _ex = ex as Error;
|
|
121
|
+
if (_ex.name === 'ProjectConfigNotFoundExpection') {
|
|
122
|
+
tui.fatal(
|
|
123
|
+
`The directory ${dir} does not contain a valid Agentuity project. Missing agentuity.json`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
tui.fatal(`unxpected error trying to deploy project. ${ex}`);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
});
|
package/src/cmd/dev/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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 } from '../../config';
|
|
6
|
+
import { loadBuildMetadata, saveProjectDir } from '../../config';
|
|
7
7
|
import type { BuildMetadata } from '../../types';
|
|
8
8
|
import * as tui from '../../tui';
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@ export const command = createCommand({
|
|
|
18
18
|
optionalAuth: 'Continue without an account (local only)',
|
|
19
19
|
|
|
20
20
|
async handler(ctx) {
|
|
21
|
-
const { opts, logger } = ctx;
|
|
21
|
+
const { opts, logger, options } = ctx;
|
|
22
22
|
|
|
23
23
|
const rootDir = resolve(opts.dir || process.cwd());
|
|
24
24
|
const appTs = join(rootDir, 'app.ts');
|
|
@@ -40,6 +40,8 @@ export const command = createCommand({
|
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
await saveProjectDir(rootDir);
|
|
44
|
+
|
|
43
45
|
const devmodebody =
|
|
44
46
|
tui.muted('Local: ') +
|
|
45
47
|
tui.link('http://127.0.0.1:3000') +
|
|
@@ -231,7 +233,7 @@ export const command = createCommand({
|
|
|
231
233
|
|
|
232
234
|
metadata = await loadBuildMetadata(agentuityDir);
|
|
233
235
|
|
|
234
|
-
env.AGENTUITY_LOG_LEVEL =
|
|
236
|
+
env.AGENTUITY_LOG_LEVEL = options.logLevel;
|
|
235
237
|
|
|
236
238
|
logger.trace('Starting dev server: %s', appPath);
|
|
237
239
|
// Use shell to run in a process group for proper cleanup
|
|
@@ -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
|
+
});
|