@agentuity/cli 0.1.13 → 0.1.15
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 +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +12 -7
- package/dist/auth.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +94 -97
- 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/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/index.js +3 -0
- package/dist/cmd/auth/index.js.map +1 -1
- package/dist/cmd/auth/org/index.d.ts +2 -0
- package/dist/cmd/auth/org/index.d.ts.map +1 -0
- package/dist/cmd/auth/org/index.js +121 -0
- package/dist/cmd/auth/org/index.js.map +1 -0
- package/dist/cmd/build/vite/beacon-plugin.d.ts +19 -0
- package/dist/cmd/build/vite/beacon-plugin.d.ts.map +1 -0
- package/dist/cmd/build/vite/beacon-plugin.js +137 -0
- package/dist/cmd/build/vite/beacon-plugin.js.map +1 -0
- package/dist/cmd/build/vite/vite-builder.d.ts +2 -0
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +12 -2
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/build/webanalytics-generator.js +25 -9
- package/dist/cmd/build/webanalytics-generator.js.map +1 -1
- package/dist/cmd/cloud/db/get.d.ts.map +1 -1
- package/dist/cmd/cloud/db/get.js +7 -0
- package/dist/cmd/cloud/db/get.js.map +1 -1
- package/dist/cmd/cloud/db/list.d.ts.map +1 -1
- package/dist/cmd/cloud/db/list.js +19 -6
- package/dist/cmd/cloud/db/list.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +24 -1
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
- package/dist/cmd/cloud/deployment/show.js +5 -0
- package/dist/cmd/cloud/deployment/show.js.map +1 -1
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +3 -0
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/cloud/region/index.d.ts +2 -0
- package/dist/cmd/cloud/region/index.d.ts.map +1 -0
- package/dist/cmd/cloud/region/index.js +136 -0
- package/dist/cmd/cloud/region/index.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +35 -5
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/cmd/cloud/scp/download.d.ts.map +1 -1
- package/dist/cmd/cloud/scp/download.js +4 -2
- 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 +4 -2
- 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 +3 -1
- package/dist/cmd/cloud/ssh.js.map +1 -1
- package/dist/cmd/cloud/storage/get.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/get.js +12 -5
- 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 +10 -0
- package/dist/cmd/cloud/storage/list.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +62 -5
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/help/index.d.ts.map +1 -1
- package/dist/cmd/help/index.js +8 -18
- package/dist/cmd/help/index.js.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/create.js +18 -9
- 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/import.d.ts +2 -0
- package/dist/cmd/project/import.d.ts.map +1 -0
- package/dist/cmd/project/import.js +88 -0
- package/dist/cmd/project/import.js.map +1 -0
- package/dist/cmd/project/index.d.ts.map +1 -1
- package/dist/cmd/project/index.js +3 -0
- package/dist/cmd/project/index.js.map +1 -1
- package/dist/cmd/project/reconcile.d.ts +67 -0
- package/dist/cmd/project/reconcile.d.ts.map +1 -0
- package/dist/cmd/project/reconcile.js +458 -0
- package/dist/cmd/project/reconcile.js.map +1 -0
- package/dist/cmd/project/template-flow.d.ts +13 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +56 -18
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/config.d.ts +8 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +50 -21
- package/dist/config.js.map +1 -1
- package/dist/legacy-check.d.ts.map +1 -1
- package/dist/legacy-check.js +8 -0
- package/dist/legacy-check.js.map +1 -1
- package/dist/program-ref.d.ts +4 -0
- package/dist/program-ref.d.ts.map +1 -0
- package/dist/program-ref.js +8 -0
- package/dist/program-ref.js.map +1 -0
- package/dist/regions.d.ts +8 -0
- package/dist/regions.d.ts.map +1 -0
- package/dist/regions.js +77 -0
- package/dist/regions.js.map +1 -0
- 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 +79 -25
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/auth.ts +14 -13
- package/src/cli.ts +118 -114
- 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/auth/index.ts +3 -0
- package/src/cmd/auth/org/index.ts +142 -0
- package/src/cmd/build/vite/beacon-plugin.ts +164 -0
- package/src/cmd/build/vite/vite-builder.ts +19 -2
- package/src/cmd/build/webanalytics-generator.ts +25 -9
- package/src/cmd/cloud/db/get.ts +7 -0
- package/src/cmd/cloud/db/list.ts +20 -6
- package/src/cmd/cloud/deploy.ts +32 -1
- package/src/cmd/cloud/deployment/show.ts +5 -0
- package/src/cmd/cloud/index.ts +3 -0
- package/src/cmd/cloud/region/index.ts +157 -0
- package/src/cmd/cloud/sandbox/snapshot/build.ts +42 -5
- package/src/cmd/cloud/scp/download.ts +6 -2
- package/src/cmd/cloud/scp/upload.ts +6 -2
- package/src/cmd/cloud/ssh.ts +5 -1
- package/src/cmd/cloud/storage/get.ts +12 -5
- package/src/cmd/cloud/storage/list.ts +11 -0
- package/src/cmd/dev/index.ts +62 -5
- package/src/cmd/help/index.ts +8 -22
- package/src/cmd/project/create.ts +19 -9
- package/src/cmd/project/download.ts +7 -1
- package/src/cmd/project/import.ts +98 -0
- package/src/cmd/project/index.ts +3 -0
- package/src/cmd/project/reconcile.ts +606 -0
- package/src/cmd/project/template-flow.ts +69 -18
- package/src/config.ts +58 -22
- package/src/legacy-check.ts +10 -0
- package/src/program-ref.ts +11 -0
- package/src/regions.ts +95 -0
- package/src/tui/prompt.ts +10 -3
- package/src/tui.ts +82 -26
- package/src/types.ts +2 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import { join, basename } from 'node:path';
|
|
2
|
+
import { existsSync, statSync } from 'node:fs';
|
|
3
|
+
import type { Logger } from '@agentuity/core';
|
|
4
|
+
import {
|
|
5
|
+
projectGet,
|
|
6
|
+
projectCreate,
|
|
7
|
+
projectEnvUpdate,
|
|
8
|
+
listOrganizations,
|
|
9
|
+
type OrganizationList,
|
|
10
|
+
type RegionList,
|
|
11
|
+
} from '@agentuity/server';
|
|
12
|
+
import type { APIClient } from '../../api';
|
|
13
|
+
import type { AuthData, Config, Project } from '../../types';
|
|
14
|
+
import { loadProjectConfig, createProjectConfig } from '../../config';
|
|
15
|
+
import * as tui from '../../tui';
|
|
16
|
+
import { createPrompt } from '../../tui';
|
|
17
|
+
import { isTTY } from '../../auth';
|
|
18
|
+
import {
|
|
19
|
+
findExistingEnvFile,
|
|
20
|
+
readEnvFile,
|
|
21
|
+
writeEnvFile,
|
|
22
|
+
filterAgentuitySdkKeys,
|
|
23
|
+
splitEnvAndSecrets,
|
|
24
|
+
} from '../../env-util';
|
|
25
|
+
import { fetchRegionsWithCache } from '../../regions';
|
|
26
|
+
|
|
27
|
+
export interface ReconcileResult {
|
|
28
|
+
status: 'valid' | 'imported' | 'skipped' | 'error';
|
|
29
|
+
project?: Project;
|
|
30
|
+
message?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ReconcileOptions {
|
|
34
|
+
dir: string;
|
|
35
|
+
auth: AuthData;
|
|
36
|
+
apiClient: APIClient;
|
|
37
|
+
config: Config;
|
|
38
|
+
logger: Logger;
|
|
39
|
+
interactive?: boolean;
|
|
40
|
+
/** If true, skip prompts and just validate */
|
|
41
|
+
validateOnly?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Dependencies that can be injected for testing
|
|
46
|
+
*/
|
|
47
|
+
export interface ReconcileDeps {
|
|
48
|
+
projectGet: typeof projectGet;
|
|
49
|
+
projectCreate: typeof projectCreate;
|
|
50
|
+
projectEnvUpdate: typeof projectEnvUpdate;
|
|
51
|
+
listOrganizations: typeof listOrganizations;
|
|
52
|
+
loadProjectConfig: typeof loadProjectConfig;
|
|
53
|
+
createProjectConfig: typeof createProjectConfig;
|
|
54
|
+
isTTY: typeof isTTY;
|
|
55
|
+
confirm: typeof tui.confirm;
|
|
56
|
+
selectOrganization: typeof tui.selectOrganization;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const defaultDeps: ReconcileDeps = {
|
|
60
|
+
projectGet,
|
|
61
|
+
projectCreate,
|
|
62
|
+
projectEnvUpdate,
|
|
63
|
+
listOrganizations,
|
|
64
|
+
loadProjectConfig,
|
|
65
|
+
createProjectConfig,
|
|
66
|
+
isTTY,
|
|
67
|
+
confirm: tui.confirm,
|
|
68
|
+
selectOrganization: tui.selectOrganization,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Try to load project config, returning null if not found or invalid
|
|
73
|
+
* @internal Exported for testing
|
|
74
|
+
*/
|
|
75
|
+
export async function tryLoadProjectConfig(
|
|
76
|
+
dir: string,
|
|
77
|
+
config: Config | null,
|
|
78
|
+
deps: Pick<ReconcileDeps, 'loadProjectConfig'> = defaultDeps
|
|
79
|
+
): Promise<Project | null> {
|
|
80
|
+
try {
|
|
81
|
+
return await deps.loadProjectConfig(dir, config);
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get the default project name from package.json or directory name
|
|
89
|
+
* @internal Exported for testing
|
|
90
|
+
*/
|
|
91
|
+
export async function getDefaultProjectName(dir: string): Promise<string> {
|
|
92
|
+
const pkgPath = join(dir, 'package.json');
|
|
93
|
+
if (await Bun.file(pkgPath).exists()) {
|
|
94
|
+
try {
|
|
95
|
+
const pkg = await Bun.file(pkgPath).json();
|
|
96
|
+
if (pkg.name && typeof pkg.name === 'string' && pkg.name.trim()) {
|
|
97
|
+
// Strip org scope if present (e.g., @myorg/project-name -> project-name)
|
|
98
|
+
return pkg.name.replace(/^@[^/]+\//, '').trim();
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Fall through to directory name
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return basename(dir);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if a directory contains a valid Agentuity project structure
|
|
109
|
+
* @internal Exported for testing
|
|
110
|
+
*/
|
|
111
|
+
export async function isValidProjectStructure(dir: string): Promise<boolean> {
|
|
112
|
+
// Check 1: package.json with @agentuity/runtime and agentuity.config.ts
|
|
113
|
+
const pkgPath = join(dir, 'package.json');
|
|
114
|
+
const configPath = join(dir, 'agentuity.config.ts');
|
|
115
|
+
|
|
116
|
+
if (await Bun.file(pkgPath).exists()) {
|
|
117
|
+
try {
|
|
118
|
+
const pkg = await Bun.file(pkgPath).json();
|
|
119
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
120
|
+
if (deps['@agentuity/runtime'] && (await Bun.file(configPath).exists())) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Fall through to check child project
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check 2: ./agentuity/ subdirectory exists with valid structure (parent project with child)
|
|
129
|
+
const agentuityDir = join(dir, 'agentuity');
|
|
130
|
+
if (existsSync(agentuityDir) && statSync(agentuityDir).isDirectory()) {
|
|
131
|
+
const childPkgPath = join(agentuityDir, 'package.json');
|
|
132
|
+
const childConfigPath = join(agentuityDir, 'agentuity.config.ts');
|
|
133
|
+
|
|
134
|
+
if (await Bun.file(childPkgPath).exists()) {
|
|
135
|
+
try {
|
|
136
|
+
const childPkg = await Bun.file(childPkgPath).json();
|
|
137
|
+
const childDeps = { ...childPkg.dependencies, ...childPkg.devDependencies };
|
|
138
|
+
if (childDeps['@agentuity/runtime'] && (await Bun.file(childConfigPath).exists())) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Invalid package.json in child - fall through
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Update or create .env file with new SDK key
|
|
152
|
+
*/
|
|
153
|
+
async function updateSdkKeyInEnv(dir: string, sdkKey: string): Promise<void> {
|
|
154
|
+
const envPath = join(dir, '.env');
|
|
155
|
+
const envFile = Bun.file(envPath);
|
|
156
|
+
|
|
157
|
+
if (await envFile.exists()) {
|
|
158
|
+
// Update existing .env - read, modify, write
|
|
159
|
+
const existing = await readEnvFile(envPath);
|
|
160
|
+
existing.AGENTUITY_SDK_KEY = sdkKey;
|
|
161
|
+
await writeEnvFile(envPath, existing);
|
|
162
|
+
} else {
|
|
163
|
+
// Create new .env
|
|
164
|
+
const comment =
|
|
165
|
+
'# AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
|
|
166
|
+
const content = `${comment}\nAGENTUITY_SDK_KEY=${sdkKey}\n`;
|
|
167
|
+
await Bun.write(envPath, content);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Sync existing environment variables to the new project
|
|
173
|
+
*/
|
|
174
|
+
async function syncEnvToProject(
|
|
175
|
+
dir: string,
|
|
176
|
+
projectId: string,
|
|
177
|
+
apiClient: APIClient,
|
|
178
|
+
logger: Logger
|
|
179
|
+
): Promise<void> {
|
|
180
|
+
try {
|
|
181
|
+
const envFilePath = await findExistingEnvFile(dir);
|
|
182
|
+
const localEnv = await readEnvFile(envFilePath);
|
|
183
|
+
const filteredEnv = filterAgentuitySdkKeys(localEnv);
|
|
184
|
+
|
|
185
|
+
if (Object.keys(filteredEnv).length > 0) {
|
|
186
|
+
const { env, secrets } = splitEnvAndSecrets(filteredEnv);
|
|
187
|
+
await projectEnvUpdate(apiClient, {
|
|
188
|
+
id: projectId,
|
|
189
|
+
env,
|
|
190
|
+
secrets,
|
|
191
|
+
});
|
|
192
|
+
logger.debug(`Synced ${Object.keys(filteredEnv).length} environment variables to cloud`);
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// Non-fatal: just log the error
|
|
196
|
+
logger.debug('Failed to sync environment variables:', error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Prompt user to select an organization
|
|
202
|
+
*/
|
|
203
|
+
async function selectOrg(
|
|
204
|
+
orgs: OrganizationList,
|
|
205
|
+
config: Config,
|
|
206
|
+
defaultOrgId?: string
|
|
207
|
+
): Promise<string> {
|
|
208
|
+
return tui.selectOrganization(orgs, defaultOrgId ?? config.preferences?.orgId);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Prompt user to select a region from the available regions
|
|
213
|
+
*/
|
|
214
|
+
async function selectRegion(regions: RegionList, defaultRegion?: string): Promise<string> {
|
|
215
|
+
if (regions.length === 0) {
|
|
216
|
+
throw new Error('No cloud regions available');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (regions.length === 1) {
|
|
220
|
+
return regions[0].region;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Build options from API regions
|
|
224
|
+
const options = regions.map((r) => ({
|
|
225
|
+
value: r.region,
|
|
226
|
+
label: `${r.description} (${r.region})`,
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
// Move default to top if found
|
|
230
|
+
const defaultValue = defaultRegion ?? regions[0].region;
|
|
231
|
+
const defaultIndex = options.findIndex((r) => r.value === defaultValue);
|
|
232
|
+
if (defaultIndex > 0) {
|
|
233
|
+
const [defaultItem] = options.splice(defaultIndex, 1);
|
|
234
|
+
options.unshift(defaultItem);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const prompt = createPrompt();
|
|
238
|
+
return prompt.select({
|
|
239
|
+
message: 'Select a region:',
|
|
240
|
+
options,
|
|
241
|
+
initial: options[0].value,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Prompt user for text input with validation
|
|
247
|
+
*/
|
|
248
|
+
async function textPrompt(options: {
|
|
249
|
+
message: string;
|
|
250
|
+
initial?: string;
|
|
251
|
+
validate?: (value: string) => boolean | string;
|
|
252
|
+
}): Promise<string> {
|
|
253
|
+
const prompt = createPrompt();
|
|
254
|
+
return prompt.text({
|
|
255
|
+
message: options.message,
|
|
256
|
+
initial: options.initial,
|
|
257
|
+
hint: options.initial ? `(default: ${options.initial})` : undefined,
|
|
258
|
+
validate: options.validate,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Import an existing project (with invalid/inaccessible agentuity.json) to user's org
|
|
264
|
+
*/
|
|
265
|
+
async function importExistingProject(
|
|
266
|
+
opts: ReconcileOptions,
|
|
267
|
+
existingConfig: Project,
|
|
268
|
+
orgs: OrganizationList
|
|
269
|
+
): Promise<ReconcileResult> {
|
|
270
|
+
const { dir, apiClient, config, logger } = opts;
|
|
271
|
+
|
|
272
|
+
tui.warning(
|
|
273
|
+
"You don't have access to this project. It may have been deleted or transferred to another organization."
|
|
274
|
+
);
|
|
275
|
+
tui.newline();
|
|
276
|
+
|
|
277
|
+
const shouldImport = await tui.confirm(
|
|
278
|
+
'Would you like to import this project to your organization?',
|
|
279
|
+
true
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
if (!shouldImport) {
|
|
283
|
+
return { status: 'skipped', message: 'Project import cancelled.' };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
tui.newline();
|
|
287
|
+
|
|
288
|
+
// Select org
|
|
289
|
+
const orgId = await selectOrg(orgs, config, existingConfig.orgId);
|
|
290
|
+
|
|
291
|
+
// Fetch regions and select
|
|
292
|
+
const regions = await tui.spinner({
|
|
293
|
+
message: 'Fetching regions',
|
|
294
|
+
clearOnSuccess: true,
|
|
295
|
+
callback: () => fetchRegionsWithCache(config.name, apiClient, logger),
|
|
296
|
+
});
|
|
297
|
+
const region = await selectRegion(regions, existingConfig.region);
|
|
298
|
+
|
|
299
|
+
// Get project name
|
|
300
|
+
const defaultName = await getDefaultProjectName(dir);
|
|
301
|
+
const projectName = await textPrompt({
|
|
302
|
+
message: 'Project name:',
|
|
303
|
+
initial: defaultName,
|
|
304
|
+
validate: (value: string) => {
|
|
305
|
+
if (!value || value.trim().length === 0) {
|
|
306
|
+
return 'Project name is required';
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Create the project
|
|
313
|
+
const newProject = await tui.spinner({
|
|
314
|
+
message: 'Registering project',
|
|
315
|
+
clearOnSuccess: true,
|
|
316
|
+
callback: async () => {
|
|
317
|
+
return projectCreate(apiClient, {
|
|
318
|
+
name: projectName,
|
|
319
|
+
orgId,
|
|
320
|
+
cloudRegion: region,
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Update .env with new SDK key
|
|
326
|
+
await updateSdkKeyInEnv(dir, newProject.sdkKey);
|
|
327
|
+
tui.success('Updated AGENTUITY_SDK_KEY in .env');
|
|
328
|
+
|
|
329
|
+
// Create new agentuity.json
|
|
330
|
+
await createProjectConfig(dir, {
|
|
331
|
+
projectId: newProject.id,
|
|
332
|
+
orgId,
|
|
333
|
+
sdkKey: newProject.sdkKey,
|
|
334
|
+
region,
|
|
335
|
+
});
|
|
336
|
+
tui.success('Updated agentuity.json');
|
|
337
|
+
|
|
338
|
+
// Sync env vars
|
|
339
|
+
await tui.spinner({
|
|
340
|
+
message: 'Syncing environment variables',
|
|
341
|
+
clearOnSuccess: true,
|
|
342
|
+
callback: () => syncEnvToProject(dir, newProject.id, apiClient, logger),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const project: Project = {
|
|
346
|
+
projectId: newProject.id,
|
|
347
|
+
orgId,
|
|
348
|
+
region,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
tui.success('Project imported successfully!');
|
|
352
|
+
|
|
353
|
+
return { status: 'imported', project };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Create a new project from an unregistered local project
|
|
358
|
+
*/
|
|
359
|
+
async function createNewProject(opts: ReconcileOptions): Promise<ReconcileResult> {
|
|
360
|
+
const { dir, apiClient, config, logger } = opts;
|
|
361
|
+
|
|
362
|
+
tui.warning('This project is not registered with Agentuity Cloud.');
|
|
363
|
+
tui.newline();
|
|
364
|
+
|
|
365
|
+
const shouldCreate = await tui.confirm('Would you like to register it now?', true);
|
|
366
|
+
|
|
367
|
+
if (!shouldCreate) {
|
|
368
|
+
return { status: 'skipped', message: 'Project registration cancelled.' };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
tui.newline();
|
|
372
|
+
|
|
373
|
+
// Fetch user's orgs
|
|
374
|
+
const orgs = await tui.spinner({
|
|
375
|
+
message: 'Fetching organizations',
|
|
376
|
+
clearOnSuccess: true,
|
|
377
|
+
callback: () => listOrganizations(apiClient),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
if (orgs.length === 0) {
|
|
381
|
+
return { status: 'error', message: 'No organizations found for your account.' };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Select org
|
|
385
|
+
const orgId = await selectOrg(orgs, config);
|
|
386
|
+
|
|
387
|
+
// Fetch regions and select
|
|
388
|
+
const regions = await tui.spinner({
|
|
389
|
+
message: 'Fetching regions',
|
|
390
|
+
clearOnSuccess: true,
|
|
391
|
+
callback: () => fetchRegionsWithCache(config.name, apiClient, logger),
|
|
392
|
+
});
|
|
393
|
+
const region = await selectRegion(regions);
|
|
394
|
+
|
|
395
|
+
// Get project name from package.json or prompt
|
|
396
|
+
const defaultName = await getDefaultProjectName(dir);
|
|
397
|
+
const projectName = await textPrompt({
|
|
398
|
+
message: 'Project name:',
|
|
399
|
+
initial: defaultName,
|
|
400
|
+
validate: (value: string) => {
|
|
401
|
+
if (!value || value.trim().length === 0) {
|
|
402
|
+
return 'Project name is required';
|
|
403
|
+
}
|
|
404
|
+
return true;
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Create the project
|
|
409
|
+
const newProject = await tui.spinner({
|
|
410
|
+
message: 'Registering project',
|
|
411
|
+
clearOnSuccess: true,
|
|
412
|
+
callback: async () => {
|
|
413
|
+
return projectCreate(apiClient, {
|
|
414
|
+
name: projectName,
|
|
415
|
+
orgId,
|
|
416
|
+
cloudRegion: region,
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Update/create .env with SDK key
|
|
422
|
+
await updateSdkKeyInEnv(dir, newProject.sdkKey);
|
|
423
|
+
tui.success('Updated AGENTUITY_SDK_KEY in .env');
|
|
424
|
+
|
|
425
|
+
// Create agentuity.json
|
|
426
|
+
await createProjectConfig(dir, {
|
|
427
|
+
projectId: newProject.id,
|
|
428
|
+
orgId,
|
|
429
|
+
sdkKey: newProject.sdkKey,
|
|
430
|
+
region,
|
|
431
|
+
});
|
|
432
|
+
tui.success('Created agentuity.json');
|
|
433
|
+
|
|
434
|
+
// Sync env vars
|
|
435
|
+
await tui.spinner({
|
|
436
|
+
message: 'Syncing environment variables',
|
|
437
|
+
clearOnSuccess: true,
|
|
438
|
+
callback: () => syncEnvToProject(dir, newProject.id, apiClient, logger),
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const project: Project = {
|
|
442
|
+
projectId: newProject.id,
|
|
443
|
+
orgId,
|
|
444
|
+
region,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
tui.success('Project registered successfully!');
|
|
448
|
+
|
|
449
|
+
return { status: 'imported', project };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Reconcile a project - validate access or import if needed
|
|
454
|
+
*
|
|
455
|
+
* This function checks if the current directory has a valid agentuity.json
|
|
456
|
+
* and if the user has access to the project. If not, it offers to import
|
|
457
|
+
* the project to the user's organization.
|
|
458
|
+
*
|
|
459
|
+
* For directories without agentuity.json, it validates the project structure
|
|
460
|
+
* and offers to register the project with Agentuity Cloud.
|
|
461
|
+
*/
|
|
462
|
+
export async function reconcileProject(opts: ReconcileOptions): Promise<ReconcileResult> {
|
|
463
|
+
const { dir, apiClient, config, logger, interactive = isTTY(), validateOnly } = opts;
|
|
464
|
+
|
|
465
|
+
// 1. Check if agentuity.json exists
|
|
466
|
+
const projectConfig = await tryLoadProjectConfig(dir, config);
|
|
467
|
+
|
|
468
|
+
if (projectConfig) {
|
|
469
|
+
// 2. Validate access to existing project
|
|
470
|
+
try {
|
|
471
|
+
const project = await projectGet(apiClient, { id: projectConfig.projectId, keys: false });
|
|
472
|
+
|
|
473
|
+
// 3. Check if orgId matches user's orgs
|
|
474
|
+
const userOrgs = await listOrganizations(apiClient);
|
|
475
|
+
const hasAccess = userOrgs.some((org) => org.id === project.orgId);
|
|
476
|
+
|
|
477
|
+
if (hasAccess) {
|
|
478
|
+
return { status: 'valid', project: projectConfig };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// User doesn't have access - offer to import
|
|
482
|
+
if (!interactive || validateOnly) {
|
|
483
|
+
return {
|
|
484
|
+
status: 'error',
|
|
485
|
+
message:
|
|
486
|
+
"You don't have access to this project. Run interactively to import it to your organization.",
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return await importExistingProject(opts, projectConfig, userOrgs);
|
|
491
|
+
} catch (err) {
|
|
492
|
+
// Project not found or access denied
|
|
493
|
+
logger.debug('Failed to get project:', err);
|
|
494
|
+
|
|
495
|
+
if (!interactive || validateOnly) {
|
|
496
|
+
return {
|
|
497
|
+
status: 'error',
|
|
498
|
+
message:
|
|
499
|
+
'Project not found or access denied. Run interactively to import it to your organization.',
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const userOrgs = await listOrganizations(apiClient);
|
|
504
|
+
return await importExistingProject(opts, projectConfig, userOrgs);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 4. No agentuity.json - validate project structure
|
|
509
|
+
const isValid = await isValidProjectStructure(dir);
|
|
510
|
+
|
|
511
|
+
if (!isValid) {
|
|
512
|
+
return {
|
|
513
|
+
status: 'error',
|
|
514
|
+
message:
|
|
515
|
+
'This directory does not appear to be a valid Agentuity project. ' +
|
|
516
|
+
'Expected agentuity.config.ts and @agentuity/runtime dependency, ' +
|
|
517
|
+
'or an agentuity/ subdirectory.',
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (!interactive || validateOnly) {
|
|
522
|
+
return {
|
|
523
|
+
status: 'error',
|
|
524
|
+
message:
|
|
525
|
+
'Project must be registered with Agentuity Cloud. ' +
|
|
526
|
+
'Run interactively or use "agentuity project import".',
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// 5. Prompt to create new project
|
|
531
|
+
return await createNewProject(opts);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Run project import directly (for the import command)
|
|
536
|
+
*/
|
|
537
|
+
export async function runProjectImport(opts: ReconcileOptions): Promise<ReconcileResult> {
|
|
538
|
+
const { dir, apiClient, config, interactive = true, validateOnly = false } = opts;
|
|
539
|
+
|
|
540
|
+
// Check if agentuity.json already exists and is valid
|
|
541
|
+
const projectConfig = await tryLoadProjectConfig(dir, config);
|
|
542
|
+
|
|
543
|
+
if (projectConfig) {
|
|
544
|
+
try {
|
|
545
|
+
const project = await projectGet(apiClient, { id: projectConfig.projectId, keys: false });
|
|
546
|
+
const userOrgs = await listOrganizations(apiClient);
|
|
547
|
+
const hasAccess = userOrgs.some((org) => org.id === project.orgId);
|
|
548
|
+
|
|
549
|
+
if (hasAccess) {
|
|
550
|
+
tui.info('This project is already registered and you have access to it.');
|
|
551
|
+
return { status: 'valid', project: projectConfig };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Has agentuity.json but no access - offer to import
|
|
555
|
+
if (!interactive) {
|
|
556
|
+
return {
|
|
557
|
+
status: 'error',
|
|
558
|
+
message:
|
|
559
|
+
"You don't have access to this project. Run interactively to import it to your organization.",
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return await importExistingProject(opts, projectConfig, userOrgs);
|
|
564
|
+
} catch {
|
|
565
|
+
// Project doesn't exist - offer to import
|
|
566
|
+
if (!interactive) {
|
|
567
|
+
return {
|
|
568
|
+
status: 'error',
|
|
569
|
+
message: 'Project not found. Run interactively to import it to your organization.',
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const userOrgs = await listOrganizations(apiClient);
|
|
574
|
+
return await importExistingProject(opts, projectConfig, userOrgs);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// No agentuity.json - validate structure and create new project
|
|
579
|
+
const isValid = await isValidProjectStructure(dir);
|
|
580
|
+
|
|
581
|
+
if (!isValid) {
|
|
582
|
+
return {
|
|
583
|
+
status: 'error',
|
|
584
|
+
message:
|
|
585
|
+
'This directory does not appear to be a valid Agentuity project. ' +
|
|
586
|
+
'Expected agentuity.config.ts and @agentuity/runtime dependency, ' +
|
|
587
|
+
'or an agentuity/ subdirectory.',
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (validateOnly) {
|
|
592
|
+
return {
|
|
593
|
+
status: 'valid',
|
|
594
|
+
message: 'Project structure is valid and ready to import.',
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (!interactive) {
|
|
599
|
+
return {
|
|
600
|
+
status: 'error',
|
|
601
|
+
message: 'Project import requires interactive mode.',
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return await createNewProject(opts);
|
|
606
|
+
}
|