@agentuity/cli 0.1.14 → 0.1.16
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/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +7 -6
- package/dist/auth.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +69 -11
- package/dist/cli.js.map +1 -1
- package/dist/cmd/ai/index.d.ts.map +1 -1
- package/dist/cmd/ai/index.js +6 -1
- package/dist/cmd/ai/index.js.map +1 -1
- package/dist/cmd/ai/opencode/index.d.ts +3 -0
- package/dist/cmd/ai/opencode/index.d.ts.map +1 -0
- package/dist/cmd/ai/opencode/index.js +27 -0
- package/dist/cmd/ai/opencode/index.js.map +1 -0
- package/dist/cmd/ai/opencode/install.d.ts +3 -0
- package/dist/cmd/ai/opencode/install.d.ts.map +1 -0
- package/dist/cmd/ai/opencode/install.js +102 -0
- package/dist/cmd/ai/opencode/install.js.map +1 -0
- package/dist/cmd/ai/opencode/run.d.ts +3 -0
- package/dist/cmd/ai/opencode/run.d.ts.map +1 -0
- package/dist/cmd/ai/opencode/run.js +88 -0
- package/dist/cmd/ai/opencode/run.js.map +1 -0
- package/dist/cmd/ai/opencode/uninstall.d.ts +3 -0
- package/dist/cmd/ai/opencode/uninstall.d.ts.map +1 -0
- package/dist/cmd/ai/opencode/uninstall.js +82 -0
- package/dist/cmd/ai/opencode/uninstall.js.map +1 -0
- package/dist/cmd/build/vite/beacon-plugin.d.ts.map +1 -1
- package/dist/cmd/build/vite/beacon-plugin.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +1 -1
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/env/delete.js +87 -34
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/get.d.ts.map +1 -1
- package/dist/cmd/cloud/env/get.js +50 -16
- package/dist/cmd/cloud/env/get.js.map +1 -1
- package/dist/cmd/cloud/env/import.d.ts.map +1 -1
- package/dist/cmd/cloud/env/import.js +76 -32
- package/dist/cmd/cloud/env/import.js.map +1 -1
- package/dist/cmd/cloud/env/index.d.ts.map +1 -1
- package/dist/cmd/cloud/env/index.js +6 -2
- package/dist/cmd/cloud/env/index.js.map +1 -1
- package/dist/cmd/cloud/env/list.d.ts.map +1 -1
- package/dist/cmd/cloud/env/list.js +94 -23
- package/dist/cmd/cloud/env/list.js.map +1 -1
- package/dist/cmd/cloud/env/org-util.d.ts +16 -0
- package/dist/cmd/cloud/env/org-util.d.ts.map +1 -0
- package/dist/cmd/cloud/env/org-util.js +28 -0
- package/dist/cmd/cloud/env/org-util.js.map +1 -0
- package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/env/pull.js +61 -29
- package/dist/cmd/cloud/env/pull.js.map +1 -1
- package/dist/cmd/cloud/env/push.d.ts.map +1 -1
- package/dist/cmd/cloud/env/push.js +69 -23
- package/dist/cmd/cloud/env/push.js.map +1 -1
- package/dist/cmd/cloud/env/set.d.ts.map +1 -1
- package/dist/cmd/cloud/env/set.js +69 -26
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/create-namespace.js +1 -1
- package/dist/cmd/cloud/keyvalue/create-namespace.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/delete-namespace.js +2 -2
- package/dist/cmd/cloud/keyvalue/delete-namespace.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/delete.js +1 -1
- package/dist/cmd/cloud/keyvalue/delete.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/get.js +1 -1
- package/dist/cmd/cloud/keyvalue/get.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/index.js +1 -1
- package/dist/cmd/cloud/keyvalue/index.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/keys.js +1 -1
- package/dist/cmd/cloud/keyvalue/keys.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/list-namespaces.js +1 -1
- package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/repl.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/repl.js +8 -5
- package/dist/cmd/cloud/keyvalue/repl.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/search.js +1 -1
- package/dist/cmd/cloud/keyvalue/search.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/set.js +1 -1
- package/dist/cmd/cloud/keyvalue/set.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/stats.js +1 -1
- package/dist/cmd/cloud/keyvalue/stats.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/util.d.ts +4 -4
- package/dist/cmd/cloud/keyvalue/util.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/util.js +4 -9
- package/dist/cmd/cloud/keyvalue/util.js.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/create.js +20 -2
- package/dist/cmd/project/create.js.map +1 -1
- package/dist/cmd/project/download.d.ts +3 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/download.js +5 -0
- package/dist/cmd/project/download.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +5 -0
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +188 -79
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/cmd/setup/index.d.ts.map +1 -1
- package/dist/cmd/setup/index.js +2 -1
- package/dist/cmd/setup/index.js.map +1 -1
- package/dist/onboarding/agentPrompt.d.ts +8 -0
- package/dist/onboarding/agentPrompt.d.ts.map +1 -0
- package/dist/onboarding/agentPrompt.js +263 -0
- package/dist/onboarding/agentPrompt.js.map +1 -0
- package/dist/schema-generator.d.ts +1 -1
- package/dist/schema-generator.d.ts.map +1 -1
- package/dist/schema-parser.d.ts +1 -1
- package/dist/schema-parser.d.ts.map +1 -1
- package/dist/schema-parser.js +36 -1
- package/dist/schema-parser.js.map +1 -1
- package/dist/tui/prompt.d.ts.map +1 -1
- package/dist/tui/prompt.js +7 -1
- package/dist/tui/prompt.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +91 -28
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/auth.ts +7 -6
- package/src/cli.ts +84 -11
- package/src/cmd/ai/index.ts +6 -1
- package/src/cmd/ai/opencode/index.ts +28 -0
- package/src/cmd/ai/opencode/install.ts +120 -0
- package/src/cmd/ai/opencode/run.ts +103 -0
- package/src/cmd/ai/opencode/uninstall.ts +90 -0
- package/src/cmd/build/vite/beacon-plugin.ts +3 -1
- package/src/cmd/build/vite/vite-builder.ts +5 -1
- package/src/cmd/cloud/env/delete.ts +100 -41
- package/src/cmd/cloud/env/get.ts +53 -16
- package/src/cmd/cloud/env/import.ts +86 -37
- package/src/cmd/cloud/env/index.ts +6 -2
- package/src/cmd/cloud/env/list.ts +102 -27
- package/src/cmd/cloud/env/org-util.ts +37 -0
- package/src/cmd/cloud/env/pull.ts +67 -31
- package/src/cmd/cloud/env/push.ts +81 -28
- package/src/cmd/cloud/env/set.ts +82 -33
- package/src/cmd/cloud/keyvalue/create-namespace.ts +1 -1
- package/src/cmd/cloud/keyvalue/delete-namespace.ts +2 -2
- package/src/cmd/cloud/keyvalue/delete.ts +1 -1
- package/src/cmd/cloud/keyvalue/get.ts +1 -1
- package/src/cmd/cloud/keyvalue/index.ts +1 -1
- package/src/cmd/cloud/keyvalue/keys.ts +1 -1
- package/src/cmd/cloud/keyvalue/list-namespaces.ts +1 -1
- package/src/cmd/cloud/keyvalue/repl.ts +8 -5
- package/src/cmd/cloud/keyvalue/search.ts +1 -1
- package/src/cmd/cloud/keyvalue/set.ts +1 -1
- package/src/cmd/cloud/keyvalue/stats.ts +1 -1
- package/src/cmd/cloud/keyvalue/util.ts +8 -17
- package/src/cmd/project/create.ts +21 -2
- package/src/cmd/project/download.ts +7 -1
- package/src/cmd/project/template-flow.ts +215 -80
- package/src/cmd/setup/index.ts +2 -1
- package/src/onboarding/agentPrompt.ts +263 -0
- package/src/schema-generator.ts +1 -1
- package/src/schema-parser.ts +42 -3
- package/src/tui/prompt.ts +10 -3
- package/src/tui.ts +95 -31
- package/src/types.ts +1 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createSubcommand } from '../../../types';
|
|
3
3
|
import * as tui from '../../../tui';
|
|
4
|
-
import { projectGet } from '@agentuity/server';
|
|
4
|
+
import { projectGet, orgEnvGet } from '@agentuity/server';
|
|
5
5
|
import { getCommand } from '../../../command-prefix';
|
|
6
|
+
import { resolveOrgId, isOrgScope } from './org-util';
|
|
6
7
|
|
|
7
8
|
const EnvItemSchema = z.object({
|
|
8
9
|
value: z.string().describe('Variable value'),
|
|
9
10
|
secret: z.boolean().describe('Whether this is a secret'),
|
|
11
|
+
scope: z.enum(['project', 'org']).describe('The scope of the variable'),
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
const EnvListResponseSchema = z.record(z.string(), EnvItemSchema);
|
|
@@ -15,14 +17,16 @@ export const listSubcommand = createSubcommand({
|
|
|
15
17
|
name: 'list',
|
|
16
18
|
aliases: ['ls'],
|
|
17
19
|
description: 'List all environment variables and secrets',
|
|
18
|
-
tags: ['read-only', 'fast', 'requires-auth'
|
|
20
|
+
tags: ['read-only', 'fast', 'requires-auth'],
|
|
19
21
|
examples: [
|
|
20
|
-
{ command: getCommand('env list'), description: 'List all variables' },
|
|
22
|
+
{ command: getCommand('env list'), description: 'List all variables (project + org merged)' },
|
|
21
23
|
{ command: getCommand('env list --no-mask'), description: 'Show unmasked values' },
|
|
22
24
|
{ command: getCommand('env list --secrets'), description: 'List only secrets' },
|
|
23
25
|
{ command: getCommand('env list --env-only'), description: 'List only env vars' },
|
|
26
|
+
{ command: getCommand('env list --org'), description: 'List only organization-level variables' },
|
|
24
27
|
],
|
|
25
|
-
requires: { auth: true,
|
|
28
|
+
requires: { auth: true, apiClient: true },
|
|
29
|
+
optional: { project: true },
|
|
26
30
|
idempotent: true,
|
|
27
31
|
schema: {
|
|
28
32
|
options: z.object({
|
|
@@ -32,39 +36,103 @@ export const listSubcommand = createSubcommand({
|
|
|
32
36
|
.describe('mask secret values in output (use --no-mask to show values)'),
|
|
33
37
|
secrets: z.boolean().default(false).describe('list only secrets'),
|
|
34
38
|
'env-only': z.boolean().default(false).describe('list only environment variables'),
|
|
39
|
+
org: z
|
|
40
|
+
.union([z.boolean(), z.string()])
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('list organization-level variables only (use --org for default org)'),
|
|
35
43
|
}),
|
|
36
44
|
response: EnvListResponseSchema,
|
|
37
45
|
},
|
|
38
|
-
webUrl: (ctx) =>
|
|
46
|
+
webUrl: (ctx) => {
|
|
47
|
+
// If --org flag is used, link to org settings; otherwise link to project settings
|
|
48
|
+
if (ctx.opts?.org) {
|
|
49
|
+
return `/settings/organization/env`;
|
|
50
|
+
}
|
|
51
|
+
return ctx.project ? `/projects/${encodeURIComponent(ctx.project.projectId)}/settings` : undefined;
|
|
52
|
+
},
|
|
39
53
|
|
|
40
54
|
async handler(ctx) {
|
|
41
|
-
const { opts, apiClient, project, options } = ctx;
|
|
42
|
-
|
|
43
|
-
// Fetch project with unmasked values
|
|
44
|
-
const projectData = await tui.spinner('Fetching variables', () => {
|
|
45
|
-
return projectGet(apiClient, { id: project.projectId, mask: false });
|
|
46
|
-
});
|
|
55
|
+
const { opts, apiClient, project, options, config } = ctx;
|
|
56
|
+
const useOrgScope = isOrgScope(opts?.org);
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
// Build combined result with type info
|
|
52
|
-
const result: Record<string, { value: string; secret: boolean }> = {};
|
|
58
|
+
// Build combined result with type info and scope
|
|
59
|
+
const result: Record<string, { value: string; secret: boolean; scope: 'project' | 'org' }> = {};
|
|
53
60
|
|
|
54
61
|
// Filter based on options
|
|
55
62
|
const showEnv = !opts?.secrets;
|
|
56
63
|
const showSecrets = !opts?.['env-only'];
|
|
57
64
|
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
if (useOrgScope) {
|
|
66
|
+
// Organization scope only
|
|
67
|
+
const orgId = await resolveOrgId(apiClient, config, opts!.org!);
|
|
68
|
+
|
|
69
|
+
const orgData = await tui.spinner('Fetching organization variables', () => {
|
|
70
|
+
return orgEnvGet(apiClient, { id: orgId, mask: false });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const orgEnv = orgData.env || {};
|
|
74
|
+
const orgSecrets = orgData.secrets || {};
|
|
75
|
+
|
|
76
|
+
if (showEnv) {
|
|
77
|
+
for (const [key, value] of Object.entries(orgEnv)) {
|
|
78
|
+
result[key] = { value, secret: false, scope: 'org' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (showSecrets) {
|
|
83
|
+
for (const [key, value] of Object.entries(orgSecrets)) {
|
|
84
|
+
result[key] = { value, secret: true, scope: 'org' };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else if (project) {
|
|
88
|
+
// Project context: show merged view (org + project, project takes precedence)
|
|
89
|
+
// First, get project's org ID and fetch org env
|
|
90
|
+
const projectData = await tui.spinner('Fetching variables', () => {
|
|
91
|
+
return projectGet(apiClient, { id: project.projectId, mask: false });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const projectEnv = projectData.env || {};
|
|
95
|
+
const projectSecrets = projectData.secrets || {};
|
|
96
|
+
|
|
97
|
+
// Try to get org-level env (may fail if no access, that's ok)
|
|
98
|
+
let orgEnv: Record<string, string> = {};
|
|
99
|
+
let orgSecrets: Record<string, string> = {};
|
|
100
|
+
|
|
101
|
+
if (projectData.orgId) {
|
|
102
|
+
try {
|
|
103
|
+
const orgData = await orgEnvGet(apiClient, { id: projectData.orgId, mask: false });
|
|
104
|
+
orgEnv = orgData.env || {};
|
|
105
|
+
orgSecrets = orgData.secrets || {};
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore errors - user may not have org env access
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Add org-level first (will be overridden by project-level)
|
|
112
|
+
if (showEnv) {
|
|
113
|
+
for (const [key, value] of Object.entries(orgEnv)) {
|
|
114
|
+
result[key] = { value, secret: false, scope: 'org' };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (showSecrets) {
|
|
118
|
+
for (const [key, value] of Object.entries(orgSecrets)) {
|
|
119
|
+
result[key] = { value, secret: true, scope: 'org' };
|
|
120
|
+
}
|
|
61
121
|
}
|
|
62
|
-
}
|
|
63
122
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
123
|
+
// Add project-level (overrides org-level)
|
|
124
|
+
if (showEnv) {
|
|
125
|
+
for (const [key, value] of Object.entries(projectEnv)) {
|
|
126
|
+
result[key] = { value, secret: false, scope: 'project' };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (showSecrets) {
|
|
130
|
+
for (const [key, value] of Object.entries(projectSecrets)) {
|
|
131
|
+
result[key] = { value, secret: true, scope: 'project' };
|
|
132
|
+
}
|
|
67
133
|
}
|
|
134
|
+
} else {
|
|
135
|
+
tui.fatal('Project context required. Run from a project directory or use --org for organization scope.');
|
|
68
136
|
}
|
|
69
137
|
|
|
70
138
|
// Skip TUI output in JSON mode
|
|
@@ -74,11 +142,17 @@ export const listSubcommand = createSubcommand({
|
|
|
74
142
|
} else {
|
|
75
143
|
tui.newline();
|
|
76
144
|
|
|
77
|
-
const
|
|
78
|
-
const
|
|
145
|
+
const projectCount = Object.values(result).filter(v => v.scope === 'project').length;
|
|
146
|
+
const orgCount = Object.values(result).filter(v => v.scope === 'org').length;
|
|
147
|
+
const secretCount = Object.values(result).filter(v => v.secret).length;
|
|
148
|
+
const envCount = Object.values(result).filter(v => !v.secret).length;
|
|
149
|
+
|
|
79
150
|
const parts: string[] = [];
|
|
80
151
|
if (envCount > 0) parts.push(`${envCount} env`);
|
|
81
152
|
if (secretCount > 0) parts.push(`${secretCount} secret${secretCount !== 1 ? 's' : ''}`);
|
|
153
|
+
if (!useOrgScope && projectCount > 0 && orgCount > 0) {
|
|
154
|
+
parts.push(`${projectCount} project, ${orgCount} org`);
|
|
155
|
+
}
|
|
82
156
|
|
|
83
157
|
tui.info(`Variables (${parts.join(', ')}):`);
|
|
84
158
|
tui.newline();
|
|
@@ -87,10 +161,11 @@ export const listSubcommand = createSubcommand({
|
|
|
87
161
|
const shouldMask = opts?.mask !== false;
|
|
88
162
|
|
|
89
163
|
for (const key of sortedKeys) {
|
|
90
|
-
const { value, secret } = result[key];
|
|
164
|
+
const { value, secret, scope } = result[key];
|
|
91
165
|
const displayValue = shouldMask && secret ? tui.maskSecret(value) : value;
|
|
92
166
|
const typeIndicator = secret ? ' [secret]' : '';
|
|
93
|
-
|
|
167
|
+
const scopeIndicator = !useOrgScope ? ` [${scope}]` : '';
|
|
168
|
+
console.log(`${tui.bold(key)}=${displayValue}${tui.muted(typeIndicator + scopeIndicator)}`);
|
|
94
169
|
}
|
|
95
170
|
}
|
|
96
171
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { APIClient } from '../../../api';
|
|
2
|
+
import type { Config } from '../../../types';
|
|
3
|
+
import * as tui from '../../../tui';
|
|
4
|
+
import { listOrganizations } from '@agentuity/server';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the organization ID for org-scoped env operations.
|
|
8
|
+
*
|
|
9
|
+
* @param apiClient - The API client
|
|
10
|
+
* @param config - The CLI config (may be null)
|
|
11
|
+
* @param orgOption - The --org option value (true for default/prompt, or explicit org ID)
|
|
12
|
+
* @returns The resolved organization ID
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveOrgId(
|
|
15
|
+
apiClient: APIClient,
|
|
16
|
+
config: Config | null,
|
|
17
|
+
orgOption: boolean | string
|
|
18
|
+
): Promise<string> {
|
|
19
|
+
// If an explicit org ID was provided (string), use it directly
|
|
20
|
+
if (typeof orgOption === 'string' && orgOption !== 'true') {
|
|
21
|
+
return orgOption;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Otherwise, we need to select an org
|
|
25
|
+
const orgs = await tui.spinner('Fetching organizations', () => listOrganizations(apiClient));
|
|
26
|
+
|
|
27
|
+
// Use preference if available, otherwise prompt
|
|
28
|
+
const preferredOrgId = config?.preferences?.orgId;
|
|
29
|
+
return tui.selectOrganization(orgs, preferredOrgId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Checks if we're operating in org scope based on the --org option.
|
|
34
|
+
*/
|
|
35
|
+
export function isOrgScope(orgOption: boolean | string | undefined): boolean {
|
|
36
|
+
return orgOption === true || (typeof orgOption === 'string' && orgOption.length > 0);
|
|
37
|
+
}
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { createSubcommand } from '../../../types';
|
|
4
4
|
import * as tui from '../../../tui';
|
|
5
|
-
import { projectGet } from '@agentuity/server';
|
|
5
|
+
import { projectGet, orgEnvGet } from '@agentuity/server';
|
|
6
6
|
import {
|
|
7
7
|
findExistingEnvFile,
|
|
8
8
|
readEnvFile,
|
|
@@ -11,41 +11,94 @@ import {
|
|
|
11
11
|
isReservedAgentuityKey,
|
|
12
12
|
} from '../../../env-util';
|
|
13
13
|
import { getCommand } from '../../../command-prefix';
|
|
14
|
+
import { resolveOrgId, isOrgScope } from './org-util';
|
|
14
15
|
|
|
15
16
|
const EnvPullResponseSchema = z.object({
|
|
16
17
|
success: z.boolean().describe('Whether pull succeeded'),
|
|
17
18
|
pulled: z.number().describe('Number of items pulled'),
|
|
18
19
|
path: z.string().describe('Local file path where variables were saved'),
|
|
19
20
|
force: z.boolean().describe('Whether force mode was used'),
|
|
21
|
+
scope: z.enum(['project', 'org']).describe('The scope from which variables were pulled'),
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
export const pullSubcommand = createSubcommand({
|
|
23
25
|
name: 'pull',
|
|
24
26
|
description: 'Pull environment variables from cloud to local .env file',
|
|
25
|
-
tags: ['slow', 'requires-auth'
|
|
27
|
+
tags: ['slow', 'requires-auth'],
|
|
26
28
|
idempotent: true,
|
|
27
29
|
examples: [
|
|
28
|
-
{ command: getCommand('env pull'), description: '
|
|
29
|
-
{ command: getCommand('env pull --force'), description: '
|
|
30
|
+
{ command: getCommand('env pull'), description: 'Pull from project' },
|
|
31
|
+
{ command: getCommand('env pull --force'), description: 'Overwrite local with cloud values' },
|
|
32
|
+
{ command: getCommand('env pull --org'), description: 'Pull from organization' },
|
|
30
33
|
],
|
|
31
|
-
requires: { auth: true,
|
|
34
|
+
requires: { auth: true, apiClient: true },
|
|
35
|
+
optional: { project: true },
|
|
32
36
|
prerequisites: ['cloud deploy'],
|
|
33
37
|
schema: {
|
|
34
38
|
options: z.object({
|
|
35
39
|
force: z.boolean().default(false).describe('overwrite local values with cloud values'),
|
|
40
|
+
org: z
|
|
41
|
+
.union([z.boolean(), z.string()])
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('pull from organization level (use --org for default org)'),
|
|
36
44
|
}),
|
|
37
45
|
response: EnvPullResponseSchema,
|
|
38
46
|
},
|
|
39
47
|
|
|
40
48
|
async handler(ctx) {
|
|
41
|
-
const { opts, apiClient, project, projectDir } = ctx;
|
|
49
|
+
const { opts, apiClient, project, projectDir, config } = ctx;
|
|
50
|
+
const useOrgScope = isOrgScope(opts?.org);
|
|
42
51
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
52
|
+
// Require project context for local file operations
|
|
53
|
+
if (!projectDir) {
|
|
54
|
+
tui.fatal('Project context required. Run from a project directory.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let cloudEnv: Record<string, string>;
|
|
58
|
+
let scope: 'project' | 'org';
|
|
59
|
+
|
|
60
|
+
if (useOrgScope) {
|
|
61
|
+
// Organization scope
|
|
62
|
+
const orgId = await resolveOrgId(apiClient, config, opts!.org!);
|
|
63
|
+
|
|
64
|
+
const orgData = await tui.spinner('Pulling environment variables from organization', () => {
|
|
65
|
+
return orgEnvGet(apiClient, { id: orgId, mask: false });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
cloudEnv = { ...orgData.env, ...orgData.secrets };
|
|
69
|
+
scope = 'org';
|
|
70
|
+
} else {
|
|
71
|
+
// Project scope
|
|
72
|
+
if (!project) {
|
|
73
|
+
tui.fatal('Project context required. Run from a project directory or use --org for organization scope.');
|
|
74
|
+
}
|
|
47
75
|
|
|
48
|
-
|
|
76
|
+
const projectData = await tui.spinner('Pulling environment variables from cloud', () => {
|
|
77
|
+
return projectGet(apiClient, { id: project.projectId, mask: false });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
cloudEnv = { ...projectData.env, ...projectData.secrets };
|
|
81
|
+
scope = 'project';
|
|
82
|
+
|
|
83
|
+
// Write AGENTUITY_SDK_KEY to .env if present and missing locally (project scope only)
|
|
84
|
+
if (projectData.api_key) {
|
|
85
|
+
const dotEnvPath = join(projectDir, '.env');
|
|
86
|
+
const dotEnv = await readEnvFile(dotEnvPath);
|
|
87
|
+
|
|
88
|
+
if (!dotEnv.AGENTUITY_SDK_KEY) {
|
|
89
|
+
dotEnv.AGENTUITY_SDK_KEY = projectData.api_key;
|
|
90
|
+
await writeEnvFile(dotEnvPath, dotEnv, {
|
|
91
|
+
addComment: (key) => {
|
|
92
|
+
if (key === 'AGENTUITY_SDK_KEY') {
|
|
93
|
+
return 'AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
tui.info(`Wrote AGENTUITY_SDK_KEY to ${dotEnvPath}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
49
102
|
|
|
50
103
|
// Target file is always .env
|
|
51
104
|
const targetEnvPath = await findExistingEnvFile(projectDir);
|
|
@@ -66,28 +119,10 @@ export const pullSubcommand = createSubcommand({
|
|
|
66
119
|
skipKeys: Object.keys(mergedEnv).filter(isReservedAgentuityKey),
|
|
67
120
|
});
|
|
68
121
|
|
|
69
|
-
// Write AGENTUITY_SDK_KEY to .env if present and missing locally
|
|
70
|
-
if (projectData.api_key) {
|
|
71
|
-
const dotEnvPath = join(projectDir, '.env');
|
|
72
|
-
const dotEnv = await readEnvFile(dotEnvPath);
|
|
73
|
-
|
|
74
|
-
if (!dotEnv.AGENTUITY_SDK_KEY) {
|
|
75
|
-
dotEnv.AGENTUITY_SDK_KEY = projectData.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
122
|
const count = Object.keys(cloudEnv).length;
|
|
123
|
+
const scopeLabel = useOrgScope ? 'organization' : 'project';
|
|
89
124
|
tui.success(
|
|
90
|
-
`Pulled ${count} environment variable${count !== 1 ? 's' : ''} to ${targetEnvPath}`
|
|
125
|
+
`Pulled ${count} environment variable${count !== 1 ? 's' : ''} from ${scopeLabel} to ${targetEnvPath}`
|
|
91
126
|
);
|
|
92
127
|
|
|
93
128
|
return {
|
|
@@ -95,6 +130,7 @@ export const pullSubcommand = createSubcommand({
|
|
|
95
130
|
pulled: count,
|
|
96
131
|
path: targetEnvPath,
|
|
97
132
|
force: opts?.force ?? false,
|
|
133
|
+
scope,
|
|
98
134
|
};
|
|
99
135
|
},
|
|
100
136
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createSubcommand } from '../../../types';
|
|
3
3
|
import * as tui from '../../../tui';
|
|
4
|
-
import { projectEnvUpdate } from '@agentuity/server';
|
|
4
|
+
import { projectEnvUpdate, orgEnvUpdate } from '@agentuity/server';
|
|
5
5
|
import {
|
|
6
6
|
findExistingEnvFile,
|
|
7
7
|
readEnvFile,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
validateNoPublicSecrets,
|
|
11
11
|
} from '../../../env-util';
|
|
12
12
|
import { getCommand } from '../../../command-prefix';
|
|
13
|
+
import { resolveOrgId, isOrgScope } from './org-util';
|
|
13
14
|
|
|
14
15
|
const EnvPushResponseSchema = z.object({
|
|
15
16
|
success: z.boolean().describe('Whether push succeeded'),
|
|
@@ -17,6 +18,7 @@ const EnvPushResponseSchema = z.object({
|
|
|
17
18
|
envCount: z.number().describe('Number of env vars pushed'),
|
|
18
19
|
secretCount: z.number().describe('Number of secrets pushed'),
|
|
19
20
|
source: z.string().describe('Source file path'),
|
|
21
|
+
scope: z.enum(['project', 'org']).describe('The scope where variables were pushed'),
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
export const pushSubcommand = createSubcommand({
|
|
@@ -28,18 +30,33 @@ export const pushSubcommand = createSubcommand({
|
|
|
28
30
|
'slow',
|
|
29
31
|
'api-intensive',
|
|
30
32
|
'requires-auth',
|
|
31
|
-
'requires-project',
|
|
32
33
|
],
|
|
33
34
|
idempotent: true,
|
|
34
|
-
examples: [
|
|
35
|
-
|
|
35
|
+
examples: [
|
|
36
|
+
{ command: getCommand('env push'), description: 'Push all variables to cloud (project)' },
|
|
37
|
+
{ command: getCommand('env push --org'), description: 'Push all variables to organization' },
|
|
38
|
+
],
|
|
39
|
+
requires: { auth: true, apiClient: true },
|
|
40
|
+
optional: { project: true },
|
|
36
41
|
prerequisites: ['env set'],
|
|
37
42
|
schema: {
|
|
43
|
+
options: z.object({
|
|
44
|
+
org: z
|
|
45
|
+
.union([z.boolean(), z.string()])
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('push to organization level (use --org for default org)'),
|
|
48
|
+
}),
|
|
38
49
|
response: EnvPushResponseSchema,
|
|
39
50
|
},
|
|
40
51
|
|
|
41
52
|
async handler(ctx) {
|
|
42
|
-
const { apiClient, project, projectDir } = ctx;
|
|
53
|
+
const { apiClient, project, projectDir, config, opts } = ctx;
|
|
54
|
+
const useOrgScope = isOrgScope(opts?.org);
|
|
55
|
+
|
|
56
|
+
// Always require projectDir since push reads from local .env file
|
|
57
|
+
if (!projectDir) {
|
|
58
|
+
tui.fatal('Project directory required. Run from a project directory.');
|
|
59
|
+
}
|
|
43
60
|
|
|
44
61
|
// Read local env file
|
|
45
62
|
const envFilePath = await findExistingEnvFile(projectDir);
|
|
@@ -56,6 +73,7 @@ export const pushSubcommand = createSubcommand({
|
|
|
56
73
|
envCount: 0,
|
|
57
74
|
secretCount: 0,
|
|
58
75
|
source: envFilePath,
|
|
76
|
+
scope: useOrgScope ? 'org' as const : 'project' as const,
|
|
59
77
|
};
|
|
60
78
|
}
|
|
61
79
|
|
|
@@ -74,29 +92,64 @@ export const pushSubcommand = createSubcommand({
|
|
|
74
92
|
}
|
|
75
93
|
}
|
|
76
94
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
if (useOrgScope) {
|
|
96
|
+
// Organization scope
|
|
97
|
+
const orgId = await resolveOrgId(apiClient, config, opts!.org!);
|
|
98
|
+
|
|
99
|
+
await tui.spinner('Pushing variables to organization', () => {
|
|
100
|
+
return orgEnvUpdate(apiClient, {
|
|
101
|
+
id: orgId,
|
|
102
|
+
env,
|
|
103
|
+
secrets,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const envCount = Object.keys(env).length;
|
|
108
|
+
const secretCount = Object.keys(secrets).length;
|
|
109
|
+
const totalCount = envCount + secretCount;
|
|
110
|
+
|
|
111
|
+
tui.success(
|
|
112
|
+
`Pushed ${totalCount} variable${totalCount !== 1 ? 's' : ''} to organization (${envCount} env, ${secretCount} secret${secretCount !== 1 ? 's' : ''})`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
pushed: totalCount,
|
|
118
|
+
envCount,
|
|
119
|
+
secretCount,
|
|
120
|
+
source: envFilePath,
|
|
121
|
+
scope: 'org' as const,
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
// Project scope (existing behavior)
|
|
125
|
+
if (!project) {
|
|
126
|
+
tui.fatal('Project context required. Run from a project directory or use --org for organization scope.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await tui.spinner('Pushing variables to cloud', () => {
|
|
130
|
+
return projectEnvUpdate(apiClient, {
|
|
131
|
+
id: project.projectId,
|
|
132
|
+
env,
|
|
133
|
+
secrets,
|
|
134
|
+
});
|
|
83
135
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
|
|
137
|
+
const envCount = Object.keys(env).length;
|
|
138
|
+
const secretCount = Object.keys(secrets).length;
|
|
139
|
+
const totalCount = envCount + secretCount;
|
|
140
|
+
|
|
141
|
+
tui.success(
|
|
142
|
+
`Pushed ${totalCount} variable${totalCount !== 1 ? 's' : ''} to cloud (${envCount} env, ${secretCount} secret${secretCount !== 1 ? 's' : ''})`
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
pushed: totalCount,
|
|
148
|
+
envCount,
|
|
149
|
+
secretCount,
|
|
150
|
+
source: envFilePath,
|
|
151
|
+
scope: 'project' as const,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
101
154
|
},
|
|
102
155
|
});
|