@agentuity/cli 0.1.31 → 0.1.33
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/cmd/ai/opencode/run.d.ts.map +1 -1
- package/dist/cmd/ai/opencode/run.js +2 -0
- package/dist/cmd/ai/opencode/run.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +52 -2
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/env/delete.js +3 -4
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/import.d.ts.map +1 -1
- package/dist/cmd/cloud/env/import.js +4 -6
- package/dist/cmd/cloud/env/import.js.map +1 -1
- package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/env/pull.js +17 -25
- package/dist/cmd/cloud/env/pull.js.map +1 -1
- package/dist/cmd/cloud/env/set.d.ts.map +1 -1
- package/dist/cmd/cloud/env/set.js +3 -6
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/dist/cmd/cloud/region-lookup.d.ts +2 -2
- package/dist/cmd/cloud/region-lookup.d.ts.map +1 -1
- package/dist/cmd/cloud/region-lookup.js +7 -3
- package/dist/cmd/cloud/region-lookup.js.map +1 -1
- package/dist/cmd/cloud/scp/download.d.ts.map +1 -1
- package/dist/cmd/cloud/scp/download.js +1 -1
- package/dist/cmd/cloud/scp/download.js.map +1 -1
- package/dist/cmd/cloud/scp/upload.d.ts.map +1 -1
- package/dist/cmd/cloud/scp/upload.js +1 -1
- package/dist/cmd/cloud/scp/upload.js.map +1 -1
- package/dist/cmd/cloud/ssh.d.ts.map +1 -1
- package/dist/cmd/cloud/ssh.js +1 -1
- package/dist/cmd/cloud/ssh.js.map +1 -1
- package/dist/cmd/cloud/storage/create.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/create.js +7 -2
- package/dist/cmd/cloud/storage/create.js.map +1 -1
- package/dist/cmd/cloud/storage/get.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/get.js +6 -0
- package/dist/cmd/cloud/storage/get.js.map +1 -1
- package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/list.js +6 -0
- package/dist/cmd/cloud/storage/list.js.map +1 -1
- package/dist/cmd/project/auth/init.d.ts.map +1 -1
- package/dist/cmd/project/auth/init.js +10 -21
- package/dist/cmd/project/auth/init.js.map +1 -1
- package/dist/cmd/upgrade/index.js +4 -4
- package/dist/cmd/upgrade/index.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -2
- package/dist/config.js.map +1 -1
- package/dist/env-util.d.ts +8 -1
- package/dist/env-util.d.ts.map +1 -1
- package/dist/env-util.js +12 -3
- package/dist/env-util.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/installation-type.d.ts.map +1 -1
- package/dist/utils/installation-type.js +15 -2
- package/dist/utils/installation-type.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/ai/opencode/run.ts +4 -0
- package/src/cmd/cloud/deploy.ts +71 -1
- package/src/cmd/cloud/env/delete.ts +2 -4
- package/src/cmd/cloud/env/import.ts +3 -8
- package/src/cmd/cloud/env/pull.ts +17 -26
- package/src/cmd/cloud/env/set.ts +2 -8
- package/src/cmd/cloud/region-lookup.ts +19 -4
- package/src/cmd/cloud/scp/download.ts +2 -1
- package/src/cmd/cloud/scp/upload.ts +2 -1
- package/src/cmd/cloud/ssh.ts +2 -1
- package/src/cmd/cloud/storage/create.ts +7 -2
- package/src/cmd/cloud/storage/get.ts +6 -0
- package/src/cmd/cloud/storage/list.ts +6 -0
- package/src/cmd/project/auth/init.ts +10 -22
- package/src/cmd/upgrade/index.ts +4 -4
- package/src/config.ts +10 -2
- package/src/env-util.ts +20 -3
- package/src/types.ts +4 -1
- package/src/utils/installation-type.ts +18 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
2
|
import { createSubcommand } from '../../../types';
|
|
4
3
|
import * as tui from '../../../tui';
|
|
5
4
|
import { projectGet, orgEnvGet } from '@agentuity/server';
|
|
@@ -8,7 +7,6 @@ import {
|
|
|
8
7
|
readEnvFile,
|
|
9
8
|
writeEnvFile,
|
|
10
9
|
mergeEnvVars,
|
|
11
|
-
isReservedAgentuityKey,
|
|
12
10
|
} from '../../../env-util';
|
|
13
11
|
import { getCommand } from '../../../command-prefix';
|
|
14
12
|
import { resolveOrgId, isOrgScope } from './org-util';
|
|
@@ -93,7 +91,7 @@ export const pullSubcommand = createSubcommand({
|
|
|
93
91
|
const targetEnvPath = await findExistingEnvFile(projectDir);
|
|
94
92
|
const localEnv = await readEnvFile(targetEnvPath);
|
|
95
93
|
|
|
96
|
-
// Preserve local AGENTUITY_SDK_KEY
|
|
94
|
+
// Preserve local AGENTUITY_SDK_KEY
|
|
97
95
|
const localSdkKey = localEnv.AGENTUITY_SDK_KEY;
|
|
98
96
|
|
|
99
97
|
// Merge: cloud values override local if force=true, otherwise keep local
|
|
@@ -106,32 +104,25 @@ export const pullSubcommand = createSubcommand({
|
|
|
106
104
|
mergedEnv = mergeEnvVars(cloudEnv, localEnv);
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
//
|
|
107
|
+
// Determine the SDK key to use: cloud api_key is source of truth, fallback to local
|
|
108
|
+
const sdkKeyToWrite = cloudApiKey || localSdkKey;
|
|
109
|
+
if (sdkKeyToWrite) {
|
|
110
|
+
mergedEnv.AGENTUITY_SDK_KEY = sdkKeyToWrite;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Write to .env in a single operation, preserveExisting: false since we have the full merged state
|
|
110
114
|
await writeEnvFile(targetEnvPath, mergedEnv, {
|
|
111
|
-
|
|
115
|
+
preserveExisting: false,
|
|
116
|
+
addComment: (key) => {
|
|
117
|
+
if (key === 'AGENTUITY_SDK_KEY') {
|
|
118
|
+
return 'AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
},
|
|
112
122
|
});
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const dotEnvPath = join(projectDir, '.env');
|
|
117
|
-
const dotEnv = await readEnvFile(dotEnvPath);
|
|
118
|
-
|
|
119
|
-
// Cloud is source of truth: use cloud api_key if available, otherwise fallback to local
|
|
120
|
-
// For org scope, only restore if local key exists (orgs don't have api_key)
|
|
121
|
-
const sdkKeyToWrite = cloudApiKey || localSdkKey;
|
|
122
|
-
if (sdkKeyToWrite) {
|
|
123
|
-
dotEnv.AGENTUITY_SDK_KEY = sdkKeyToWrite;
|
|
124
|
-
await writeEnvFile(dotEnvPath, dotEnv, {
|
|
125
|
-
addComment: (key) => {
|
|
126
|
-
if (key === 'AGENTUITY_SDK_KEY') {
|
|
127
|
-
return 'AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
if (cloudApiKey && cloudApiKey !== localSdkKey) {
|
|
133
|
-
tui.info(`Wrote AGENTUITY_SDK_KEY to ${dotEnvPath}`);
|
|
134
|
-
}
|
|
124
|
+
if (cloudApiKey && cloudApiKey !== localSdkKey) {
|
|
125
|
+
tui.info(`Wrote AGENTUITY_SDK_KEY to ${targetEnvPath}`);
|
|
135
126
|
}
|
|
136
127
|
|
|
137
128
|
const count = Object.keys(cloudEnv).length;
|
package/src/cmd/cloud/env/set.ts
CHANGED
|
@@ -4,9 +4,7 @@ import * as tui from '../../../tui';
|
|
|
4
4
|
import { projectEnvUpdate, orgEnvUpdate } from '@agentuity/server';
|
|
5
5
|
import {
|
|
6
6
|
findExistingEnvFile,
|
|
7
|
-
readEnvFile,
|
|
8
7
|
writeEnvFile,
|
|
9
|
-
filterAgentuitySdkKeys,
|
|
10
8
|
looksLikeSecret,
|
|
11
9
|
isReservedAgentuityKey,
|
|
12
10
|
isPublicVarKey,
|
|
@@ -145,12 +143,8 @@ export const setSubcommand = createSubcommand({
|
|
|
145
143
|
let envFilePath: string | undefined;
|
|
146
144
|
if (projectDir) {
|
|
147
145
|
envFilePath = await findExistingEnvFile(projectDir);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Filter out AGENTUITY_ keys before writing
|
|
152
|
-
const filteredEnv = filterAgentuitySdkKeys(currentEnv);
|
|
153
|
-
await writeEnvFile(envFilePath, filteredEnv);
|
|
146
|
+
// Write only the new key - writeEnvFile preserves existing keys by default
|
|
147
|
+
await writeEnvFile(envFilePath, { [args.key]: args.value });
|
|
154
148
|
}
|
|
155
149
|
|
|
156
150
|
const successMsg = envFilePath
|
|
@@ -2,7 +2,7 @@ import type { Logger } from '@agentuity/core';
|
|
|
2
2
|
import { projectGet, sandboxGet, deploymentGet, type APIClient } from '@agentuity/server';
|
|
3
3
|
import { getResourceRegion, setResourceRegion } from '../../cache';
|
|
4
4
|
import { getGlobalCatalystAPIClient } from '../../config';
|
|
5
|
-
import type { AuthData } from '../../types';
|
|
5
|
+
import type { AuthData, Config } from '../../types';
|
|
6
6
|
import * as tui from '../../tui';
|
|
7
7
|
import { ErrorCode } from '../../errors';
|
|
8
8
|
|
|
@@ -35,7 +35,8 @@ export async function getIdentifierRegion(
|
|
|
35
35
|
apiClient: APIClient,
|
|
36
36
|
profileName = 'production',
|
|
37
37
|
identifier: string,
|
|
38
|
-
orgId?: string
|
|
38
|
+
orgId?: string,
|
|
39
|
+
config?: Config | null
|
|
39
40
|
): Promise<string> {
|
|
40
41
|
const identifierType = getIdentifierType(identifier);
|
|
41
42
|
|
|
@@ -57,8 +58,14 @@ export async function getIdentifierRegion(
|
|
|
57
58
|
const deployment = await deploymentGet(apiClient, identifier);
|
|
58
59
|
region = deployment.cloudRegion ?? null;
|
|
59
60
|
} else {
|
|
60
|
-
// sandbox
|
|
61
|
-
const globalClient = await getGlobalCatalystAPIClient(
|
|
61
|
+
// sandbox - pass config to getGlobalCatalystAPIClient for proper region resolution
|
|
62
|
+
const globalClient = await getGlobalCatalystAPIClient(
|
|
63
|
+
logger,
|
|
64
|
+
auth,
|
|
65
|
+
profileName,
|
|
66
|
+
orgId,
|
|
67
|
+
config
|
|
68
|
+
);
|
|
62
69
|
const sandbox = await sandboxGet(globalClient, { sandboxId: identifier, orgId });
|
|
63
70
|
region = sandbox.region ?? null;
|
|
64
71
|
}
|
|
@@ -70,6 +77,14 @@ export async function getIdentifierRegion(
|
|
|
70
77
|
);
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
// Validate region is a non-empty string
|
|
81
|
+
if (typeof region !== 'string' || region.trim() === '') {
|
|
82
|
+
tui.fatal(
|
|
83
|
+
`Invalid region returned for ${identifierType} '${identifier}': '${region}'. Use --region flag to specify a valid region.`,
|
|
84
|
+
ErrorCode.RESOURCE_NOT_FOUND
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
// Cache the result
|
|
74
89
|
await setResourceRegion(identifierType, profileName, identifier, region);
|
|
75
90
|
logger.trace(`[region-lookup] Cached region for ${identifier}: ${region}`);
|
package/src/cmd/cloud/ssh.ts
CHANGED
|
@@ -29,6 +29,9 @@ export const createSubcommand = defineSubcommand({
|
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
schema: {
|
|
32
|
+
options: z.object({
|
|
33
|
+
description: z.string().optional().describe('Optional description for the bucket'),
|
|
34
|
+
}),
|
|
32
35
|
response: z.object({
|
|
33
36
|
success: z.boolean().describe('Whether creation succeeded'),
|
|
34
37
|
name: z.string().describe('Created storage bucket name'),
|
|
@@ -36,7 +39,7 @@ export const createSubcommand = defineSubcommand({
|
|
|
36
39
|
},
|
|
37
40
|
|
|
38
41
|
async handler(ctx) {
|
|
39
|
-
const { logger, orgId, region, auth, options } = ctx;
|
|
42
|
+
const { logger, orgId, region, auth, options, opts } = ctx;
|
|
40
43
|
|
|
41
44
|
// Handle dry-run mode
|
|
42
45
|
if (isDryRunMode(options)) {
|
|
@@ -58,7 +61,9 @@ export const createSubcommand = defineSubcommand({
|
|
|
58
61
|
message: `Creating storage in ${region}`,
|
|
59
62
|
clearOnSuccess: true,
|
|
60
63
|
callback: async () => {
|
|
61
|
-
return createResources(catalystClient, orgId, region!, [
|
|
64
|
+
return createResources(catalystClient, orgId, region!, [
|
|
65
|
+
{ type: 's3', description: opts.description },
|
|
66
|
+
]);
|
|
62
67
|
},
|
|
63
68
|
});
|
|
64
69
|
|
|
@@ -15,6 +15,9 @@ const StorageGetResponseSchema = z.object({
|
|
|
15
15
|
endpoint: z.string().optional().describe('S3 endpoint URL'),
|
|
16
16
|
org_id: z.string().optional().describe('Organization ID that owns this bucket'),
|
|
17
17
|
org_name: z.string().optional().describe('Organization name that owns this bucket'),
|
|
18
|
+
bucket_type: z.string().optional().describe('Bucket type (user or snapshots)'),
|
|
19
|
+
internal: z.boolean().optional().describe('Whether this is a system-managed bucket'),
|
|
20
|
+
description: z.string().optional().describe('Optional description of the bucket'),
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
export const getSubcommand = createSubcommand({
|
|
@@ -134,6 +137,9 @@ export const getSubcommand = createSubcommand({
|
|
|
134
137
|
endpoint: bucket.endpoint ?? undefined,
|
|
135
138
|
org_id: bucket.org_id,
|
|
136
139
|
org_name: bucket.org_name,
|
|
140
|
+
bucket_type: bucket.bucket_type,
|
|
141
|
+
internal: bucket.internal,
|
|
142
|
+
description: bucket.description ?? undefined,
|
|
137
143
|
};
|
|
138
144
|
},
|
|
139
145
|
});
|
|
@@ -20,6 +20,9 @@ const StorageListResponseSchema = z.object({
|
|
|
20
20
|
cloud_region: z.string().optional().describe('Cloud region where bucket is hosted'),
|
|
21
21
|
org_id: z.string().optional().describe('Organization ID that owns this bucket'),
|
|
22
22
|
org_name: z.string().optional().describe('Organization name that owns this bucket'),
|
|
23
|
+
bucket_type: z.string().optional().describe('Bucket type (user or snapshots)'),
|
|
24
|
+
internal: z.boolean().optional().describe('Whether this is a system-managed bucket'),
|
|
25
|
+
description: z.string().optional().describe('Optional description of the bucket'),
|
|
23
26
|
})
|
|
24
27
|
)
|
|
25
28
|
.optional()
|
|
@@ -241,6 +244,9 @@ export const listSubcommand = createSubcommand({
|
|
|
241
244
|
cloud_region: s3.cloud_region,
|
|
242
245
|
org_id: s3.org_id,
|
|
243
246
|
org_name: s3.org_name,
|
|
247
|
+
bucket_type: s3.bucket_type,
|
|
248
|
+
internal: s3.internal,
|
|
249
|
+
description: s3.description ?? undefined,
|
|
244
250
|
})),
|
|
245
251
|
};
|
|
246
252
|
},
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
generateAuthSchemaSql,
|
|
13
13
|
getGeneratedSqlDir,
|
|
14
14
|
} from './shared';
|
|
15
|
+
import { readEnvFile, writeEnvFile } from '../../../env-util';
|
|
15
16
|
import enquirer from 'enquirer';
|
|
16
17
|
import * as fs from 'fs';
|
|
17
18
|
import * as path from 'path';
|
|
@@ -96,32 +97,23 @@ export const initSubcommand = createSubcommand({
|
|
|
96
97
|
|
|
97
98
|
const databaseName = dbInfo.name;
|
|
98
99
|
|
|
99
|
-
// Update .env with database URL
|
|
100
|
+
// Update .env with database URL using proper parsing
|
|
100
101
|
const envPath = path.join(projectDir, '.env');
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (fs.existsSync(envPath)) {
|
|
104
|
-
envContent = fs.readFileSync(envPath, 'utf-8');
|
|
105
|
-
if (!envContent.endsWith('\n') && envContent.length > 0) {
|
|
106
|
-
envContent += '\n';
|
|
107
|
-
}
|
|
108
|
-
}
|
|
102
|
+
const existingEnv = await readEnvFile(envPath);
|
|
109
103
|
|
|
110
104
|
// Check if DATABASE_URL already exists
|
|
111
|
-
const hasDatabaseUrl =
|
|
105
|
+
const hasDatabaseUrl = 'DATABASE_URL' in existingEnv;
|
|
112
106
|
|
|
113
107
|
if (dbInfo.url !== databaseUrl || !hasDatabaseUrl) {
|
|
114
108
|
if (hasDatabaseUrl) {
|
|
115
109
|
// DATABASE_URL exists, use AUTH_DATABASE_URL instead
|
|
116
|
-
|
|
117
|
-
fs.writeFileSync(envPath, envContent);
|
|
110
|
+
await writeEnvFile(envPath, { AUTH_DATABASE_URL: dbInfo.url });
|
|
118
111
|
tui.success('AUTH_DATABASE_URL added to .env');
|
|
119
112
|
tui.warning(
|
|
120
113
|
`DATABASE_URL already exists. Update your ${tui.bold('src/auth.ts')} to use AUTH_DATABASE_URL.`
|
|
121
114
|
);
|
|
122
115
|
} else {
|
|
123
|
-
|
|
124
|
-
fs.writeFileSync(envPath, envContent);
|
|
116
|
+
await writeEnvFile(envPath, { DATABASE_URL: dbInfo.url });
|
|
125
117
|
tui.success('DATABASE_URL added to .env');
|
|
126
118
|
}
|
|
127
119
|
} else {
|
|
@@ -129,18 +121,14 @@ export const initSubcommand = createSubcommand({
|
|
|
129
121
|
}
|
|
130
122
|
|
|
131
123
|
// Add AGENTUITY_AUTH_SECRET if not present
|
|
132
|
-
// Re-read
|
|
133
|
-
|
|
134
|
-
if (!envContent.endsWith('\n') && envContent.length > 0) {
|
|
135
|
-
envContent += '\n';
|
|
136
|
-
}
|
|
124
|
+
// Re-read env to get latest state
|
|
125
|
+
const currentEnv = await readEnvFile(envPath);
|
|
137
126
|
|
|
138
127
|
const hasAuthSecret =
|
|
139
|
-
|
|
128
|
+
'AGENTUITY_AUTH_SECRET' in currentEnv || 'BETTER_AUTH_SECRET' in currentEnv;
|
|
140
129
|
if (!hasAuthSecret) {
|
|
141
130
|
const devSecret = `dev-${crypto.randomUUID()}-CHANGE-ME`;
|
|
142
|
-
|
|
143
|
-
fs.writeFileSync(envPath, envContent);
|
|
131
|
+
await writeEnvFile(envPath, { AGENTUITY_AUTH_SECRET: devSecret });
|
|
144
132
|
tui.success('AGENTUITY_AUTH_SECRET added to .env (development default)');
|
|
145
133
|
tui.warning(
|
|
146
134
|
`Replace ${tui.bold('AGENTUITY_AUTH_SECRET')} with a secure value before deploying.`
|
package/src/cmd/upgrade/index.ts
CHANGED
|
@@ -138,7 +138,7 @@ export const command = createCommand({
|
|
|
138
138
|
// Check if we can upgrade based on installation type
|
|
139
139
|
if (installationType === 'source') {
|
|
140
140
|
tui.error('Upgrade is not available when running from source.');
|
|
141
|
-
tui.
|
|
141
|
+
tui.warning('You are running the CLI from source code (development mode).');
|
|
142
142
|
tui.info('Use git to update the source code instead.');
|
|
143
143
|
return {
|
|
144
144
|
upgraded: false,
|
|
@@ -150,10 +150,10 @@ export const command = createCommand({
|
|
|
150
150
|
|
|
151
151
|
if (installationType === 'local') {
|
|
152
152
|
tui.error('Upgrade is not available for local project installations.');
|
|
153
|
-
tui.
|
|
153
|
+
tui.warning('The CLI is installed as a project dependency.');
|
|
154
154
|
tui.newline();
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
console.log('To upgrade, update your package.json or run:');
|
|
156
|
+
console.log(` ${tui.muted('bun add @agentuity/cli@latest')}`);
|
|
157
157
|
return {
|
|
158
158
|
upgraded: false,
|
|
159
159
|
from: currentVersion,
|
package/src/config.ts
CHANGED
|
@@ -809,14 +809,16 @@ export async function getDefaultRegion(
|
|
|
809
809
|
* @param auth - Authentication data
|
|
810
810
|
* @param profileName - Profile name (default: 'production')
|
|
811
811
|
* @param orgId - Optional organization ID for CLI key authentication
|
|
812
|
+
* @param config - Optional config for region preference lookup
|
|
812
813
|
*/
|
|
813
814
|
export async function getGlobalCatalystAPIClient(
|
|
814
815
|
logger: Logger,
|
|
815
816
|
auth: AuthData,
|
|
816
817
|
profileName = 'production',
|
|
817
|
-
orgId?: string
|
|
818
|
+
orgId?: string,
|
|
819
|
+
config?: Config | null
|
|
818
820
|
) {
|
|
819
|
-
const region = await getDefaultRegion(profileName);
|
|
821
|
+
const region = await getDefaultRegion(profileName, config);
|
|
820
822
|
return getCatalystAPIClient(logger, auth, region, orgId);
|
|
821
823
|
}
|
|
822
824
|
|
|
@@ -828,6 +830,12 @@ export function getIONHost(config: Config | null, region: string) {
|
|
|
828
830
|
if (config?.name === 'local' || region === 'local') {
|
|
829
831
|
return 'ion.agentuity.io';
|
|
830
832
|
}
|
|
833
|
+
// Validate region is a non-empty string to prevent malformed hostnames
|
|
834
|
+
if (!region || typeof region !== 'string' || region.trim() === '') {
|
|
835
|
+
throw new Error(
|
|
836
|
+
`Invalid region: '${region}'. Region must be a non-empty string. Use --region flag to specify a valid region.`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
831
839
|
return `ion-${region}.agentuity.cloud`;
|
|
832
840
|
}
|
|
833
841
|
|
package/src/env-util.ts
CHANGED
|
@@ -149,7 +149,8 @@ export async function readEnvFile(path: string): Promise<EnvVars> {
|
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
151
|
* Write environment variables to an .env file
|
|
152
|
-
*
|
|
152
|
+
* By default, preserves existing keys that are not in the new vars.
|
|
153
|
+
* Use preserveExisting: false to completely overwrite the file.
|
|
153
154
|
*/
|
|
154
155
|
export async function writeEnvFile(
|
|
155
156
|
path: string,
|
|
@@ -157,20 +158,36 @@ export async function writeEnvFile(
|
|
|
157
158
|
options?: {
|
|
158
159
|
skipKeys?: string[];
|
|
159
160
|
addComment?: (key: string) => string | null;
|
|
161
|
+
/**
|
|
162
|
+
* When true (default), reads existing file first and merges with new vars.
|
|
163
|
+
* New vars take priority for matching keys, but all existing keys are preserved.
|
|
164
|
+
* When false, completely overwrites the file with only the provided vars.
|
|
165
|
+
*/
|
|
166
|
+
preserveExisting?: boolean;
|
|
160
167
|
}
|
|
161
168
|
): Promise<void> {
|
|
162
169
|
const skipKeys = options?.skipKeys || [];
|
|
170
|
+
const preserveExisting = options?.preserveExisting ?? true;
|
|
171
|
+
|
|
172
|
+
// If preserveExisting is true, read existing file and merge
|
|
173
|
+
let finalVars = vars;
|
|
174
|
+
if (preserveExisting) {
|
|
175
|
+
const existing = await readEnvFile(path);
|
|
176
|
+
// Merge: existing as base, new vars override
|
|
177
|
+
finalVars = { ...existing, ...vars };
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
const lines: string[] = [];
|
|
164
181
|
|
|
165
182
|
// Sort keys for consistent output
|
|
166
|
-
const sortedKeys = Object.keys(
|
|
183
|
+
const sortedKeys = Object.keys(finalVars).sort();
|
|
167
184
|
|
|
168
185
|
for (const key of sortedKeys) {
|
|
169
186
|
if (skipKeys.includes(key)) {
|
|
170
187
|
continue;
|
|
171
188
|
}
|
|
172
189
|
|
|
173
|
-
const value =
|
|
190
|
+
const value = finalVars[key];
|
|
174
191
|
|
|
175
192
|
// Add comment if provided
|
|
176
193
|
if (options?.addComment) {
|
package/src/types.ts
CHANGED
|
@@ -24,7 +24,10 @@ export const ConfigSchema = zod.object({
|
|
|
24
24
|
devmode: zod
|
|
25
25
|
.object({
|
|
26
26
|
hostname: zod.string().optional().describe('Development mode hostname'),
|
|
27
|
-
privateKey: zod
|
|
27
|
+
privateKey: zod
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Development mode private key (base64-encoded PEM)'),
|
|
28
31
|
})
|
|
29
32
|
.optional()
|
|
30
33
|
.describe('Development mode configuration'),
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Detects how the CLI was installed and is being run
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
|
|
5
7
|
export type InstallationType = 'global' | 'local' | 'source';
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -12,10 +14,24 @@ export type InstallationType = 'global' | 'local' | 'source';
|
|
|
12
14
|
* @returns 'source' - Running from source code (development)
|
|
13
15
|
*/
|
|
14
16
|
export function getInstallationType(): InstallationType {
|
|
15
|
-
// Normalize
|
|
17
|
+
// Normalize paths to POSIX separators for cross-platform compatibility
|
|
16
18
|
const mainPath = Bun.main.replace(/\\/g, '/');
|
|
19
|
+
// Bun.argv[1] contains the original invocation path (before symlink resolution)
|
|
20
|
+
const invokedPath = (Bun.argv[1] ?? '').replace(/\\/g, '/');
|
|
21
|
+
|
|
22
|
+
// Get bun's global bin directory from BUN_INSTALL or default to ~/.bun/bin
|
|
23
|
+
// Use os.homedir() as primary fallback to avoid "undefined/.bun" paths
|
|
24
|
+
const home = os.homedir() ?? process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
25
|
+
const bunInstall = (process.env.BUN_INSTALL ?? (home ? `${home}/.bun` : '')).replace(/\\/g, '/');
|
|
26
|
+
const globalBinDir = bunInstall ? `${bunInstall}/bin/` : '';
|
|
27
|
+
|
|
28
|
+
// Global install: invoked from bun's global bin directory (e.g., ~/.bun/bin/agentuity)
|
|
29
|
+
// This handles symlinks created by `bun add -g @agentuity/cli`
|
|
30
|
+
if (globalBinDir && invokedPath.startsWith(globalBinDir)) {
|
|
31
|
+
return 'global';
|
|
32
|
+
}
|
|
17
33
|
|
|
18
|
-
//
|
|
34
|
+
// Also check the resolved path for explicit global install locations
|
|
19
35
|
if (mainPath.includes('/.bun/install/global/')) {
|
|
20
36
|
return 'global';
|
|
21
37
|
}
|