@amodalai/amodal 0.3.89 → 0.3.91
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/CHANGELOG.md +46 -0
- package/dist/src/commands/audit.d.ts +1 -3
- package/dist/src/commands/audit.d.ts.map +1 -1
- package/dist/src/commands/audit.js +4 -53
- package/dist/src/commands/audit.js.map +1 -1
- package/dist/src/commands/build-manifest-types.js +1 -1
- package/dist/src/commands/build-tools.d.ts +3 -10
- package/dist/src/commands/build-tools.d.ts.map +1 -1
- package/dist/src/commands/build-tools.js +5 -118
- package/dist/src/commands/build-tools.js.map +1 -1
- package/dist/src/commands/build.d.ts +23 -5
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +53 -34
- package/dist/src/commands/build.js.map +1 -1
- package/dist/src/commands/chat.d.ts +1 -1
- package/dist/src/commands/chat.js +5 -5
- package/dist/src/commands/chat.js.map +1 -1
- package/dist/src/commands/deploy.d.ts +1 -1
- package/dist/src/commands/deploy.d.ts.map +1 -1
- package/dist/src/commands/deploy.js +3 -61
- package/dist/src/commands/deploy.js.map +1 -1
- package/dist/src/commands/deployments.d.ts.map +1 -1
- package/dist/src/commands/deployments.js +3 -36
- package/dist/src/commands/deployments.js.map +1 -1
- package/dist/src/commands/dev.d.ts.map +1 -1
- package/dist/src/commands/dev.js +7 -10
- package/dist/src/commands/dev.js.map +1 -1
- package/dist/src/commands/experiment.d.ts +1 -3
- package/dist/src/commands/experiment.d.ts.map +1 -1
- package/dist/src/commands/experiment.js +4 -102
- package/dist/src/commands/experiment.js.map +1 -1
- package/dist/src/commands/promote.d.ts.map +1 -1
- package/dist/src/commands/promote.js +3 -21
- package/dist/src/commands/promote.js.map +1 -1
- package/dist/src/commands/rollback.d.ts.map +1 -1
- package/dist/src/commands/rollback.js +3 -24
- package/dist/src/commands/rollback.js.map +1 -1
- package/dist/src/commands/secrets.d.ts.map +1 -1
- package/dist/src/commands/secrets.js +2 -102
- package/dist/src/commands/secrets.js.map +1 -1
- package/dist/src/commands/serve.d.ts +2 -11
- package/dist/src/commands/serve.d.ts.map +1 -1
- package/dist/src/commands/serve.js +44 -87
- package/dist/src/commands/serve.js.map +1 -1
- package/dist/src/commands/status.d.ts.map +1 -1
- package/dist/src/commands/status.js +3 -49
- package/dist/src/commands/status.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -8
- package/src/commands/audit.ts +4 -71
- package/src/commands/build-manifest-types.ts +1 -1
- package/src/commands/build-tools.ts +5 -142
- package/src/commands/build.test.ts +14 -9
- package/src/commands/build.ts +73 -33
- package/src/commands/chat.ts +5 -5
- package/src/commands/deploy.test.ts +2 -13
- package/src/commands/deploy.ts +5 -67
- package/src/commands/deployments.ts +3 -39
- package/src/commands/dev.ts +7 -10
- package/src/commands/experiment.ts +4 -110
- package/src/commands/promote.ts +3 -21
- package/src/commands/rollback.ts +3 -24
- package/src/commands/secrets.test.ts +12 -134
- package/src/commands/secrets.ts +2 -116
- package/src/commands/serve.ts +46 -93
- package/src/commands/status.ts +3 -51
- package/src/e2e-commands.test.ts +18 -17
- package/dist/src/shared/platform-client.d.ts +0 -123
- package/dist/src/shared/platform-client.d.ts.map +0 -1
- package/dist/src/shared/platform-client.js +0 -280
- package/dist/src/shared/platform-client.js.map +0 -1
- package/src/commands/audit.test.ts +0 -92
- package/src/commands/experiment.test.ts +0 -125
- package/src/shared/platform-client.test.ts +0 -106
- package/src/shared/platform-client.ts +0 -367
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
import { readProjectLink } from '../commands/link.js';
|
|
7
|
-
import { readRcFile } from '../commands/login.js';
|
|
8
|
-
/**
|
|
9
|
-
* Resolve platform URL and API key from multiple sources:
|
|
10
|
-
* 1. Explicit options (flags)
|
|
11
|
-
* 2. .amodal/project.json (platformUrl from `amodal link`)
|
|
12
|
-
* 3. ~/.amodalrc (auth token from `amodal login`)
|
|
13
|
-
* 4. Env vars (fallback)
|
|
14
|
-
*/
|
|
15
|
-
export async function resolvePlatformConfig(options) {
|
|
16
|
-
let url = options?.url;
|
|
17
|
-
let apiKey = options?.apiKey;
|
|
18
|
-
// Try project link for URL
|
|
19
|
-
if (!url) {
|
|
20
|
-
const link = await readProjectLink();
|
|
21
|
-
if (link?.platformUrl) {
|
|
22
|
-
url = link.platformUrl;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
// Try rc file for auth token
|
|
26
|
-
if (!apiKey) {
|
|
27
|
-
const rc = await readRcFile();
|
|
28
|
-
if (rc.platform?.token) {
|
|
29
|
-
apiKey = rc.platform.token;
|
|
30
|
-
// Also use the URL from rc if still missing
|
|
31
|
-
if (!url && rc.platform.url) {
|
|
32
|
-
url = rc.platform.url;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
// Env vars as fallback
|
|
37
|
-
if (!url)
|
|
38
|
-
url = process.env['PLATFORM_API_URL'];
|
|
39
|
-
if (!apiKey)
|
|
40
|
-
apiKey = process.env['PLATFORM_API_KEY'];
|
|
41
|
-
if (!url)
|
|
42
|
-
throw new Error('Platform URL not found. Run `amodal login` + `amodal link`, or set PLATFORM_API_URL.');
|
|
43
|
-
if (!apiKey)
|
|
44
|
-
throw new Error('Platform auth not found. Run `amodal login`, or set PLATFORM_API_KEY.');
|
|
45
|
-
return { url: url.replace(/\/$/, ''), apiKey };
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Platform API client for snapshot deployments.
|
|
49
|
-
*
|
|
50
|
-
* Resolves credentials from: explicit options → project link → rc file → env vars.
|
|
51
|
-
* Use `PlatformClient.create()` for async auto-discovery, or `new PlatformClient()` for sync usage.
|
|
52
|
-
*/
|
|
53
|
-
export class PlatformClient {
|
|
54
|
-
baseUrl;
|
|
55
|
-
apiKey;
|
|
56
|
-
constructor(options) {
|
|
57
|
-
const url = options?.url ?? process.env['PLATFORM_API_URL'];
|
|
58
|
-
const apiKey = options?.apiKey ?? process.env['PLATFORM_API_KEY'];
|
|
59
|
-
if (!url)
|
|
60
|
-
throw new Error('Platform URL not found. Run `amodal login` + `amodal link`, or set PLATFORM_API_URL.');
|
|
61
|
-
if (!apiKey)
|
|
62
|
-
throw new Error('Platform auth not found. Run `amodal login`, or set PLATFORM_API_KEY.');
|
|
63
|
-
this.baseUrl = url.replace(/\/$/, '');
|
|
64
|
-
this.apiKey = apiKey;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Create a PlatformClient with auto-discovery of credentials.
|
|
68
|
-
* Resolves from: explicit options → project link → rc file → env vars.
|
|
69
|
-
*/
|
|
70
|
-
static async create(options) {
|
|
71
|
-
const config = await resolvePlatformConfig(options);
|
|
72
|
-
return new PlatformClient(config);
|
|
73
|
-
}
|
|
74
|
-
headers() {
|
|
75
|
-
return {
|
|
76
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
77
|
-
'Content-Type': 'application/json',
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
async request(method, path, body) {
|
|
81
|
-
const url = `${this.baseUrl}${path}`;
|
|
82
|
-
let resp = await fetch(url, {
|
|
83
|
-
method,
|
|
84
|
-
headers: this.headers(),
|
|
85
|
-
...(body ? { body: JSON.stringify(body) } : {}),
|
|
86
|
-
});
|
|
87
|
-
// Auto-refresh on 401
|
|
88
|
-
if (resp.status === 401) {
|
|
89
|
-
const refreshed = await this.tryRefreshToken();
|
|
90
|
-
if (refreshed) {
|
|
91
|
-
resp = await fetch(url, {
|
|
92
|
-
method,
|
|
93
|
-
headers: this.headers(),
|
|
94
|
-
...(body ? { body: JSON.stringify(body) } : {}),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (!resp.ok) {
|
|
99
|
-
let detail = '';
|
|
100
|
-
try {
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
102
|
-
const errBody = await resp.json();
|
|
103
|
-
detail = errBody.error ? `: ${errBody.error}` : '';
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// ignore parse errors
|
|
107
|
-
}
|
|
108
|
-
throw new Error(`Platform API ${method} ${path} failed (${resp.status})${detail}`);
|
|
109
|
-
}
|
|
110
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
111
|
-
return resp.json();
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Try to refresh the token using the stored refresh token.
|
|
115
|
-
* Updates both the in-memory key and the rc file on success.
|
|
116
|
-
*/
|
|
117
|
-
async tryRefreshToken() {
|
|
118
|
-
try {
|
|
119
|
-
const { readRcFile } = await import('../commands/login.js');
|
|
120
|
-
const rc = await readRcFile();
|
|
121
|
-
if (!rc.platform?.refreshToken)
|
|
122
|
-
return false;
|
|
123
|
-
const res = await fetch(`${this.baseUrl}/api/auth/refresh`, {
|
|
124
|
-
method: 'POST',
|
|
125
|
-
headers: { 'Content-Type': 'application/json' },
|
|
126
|
-
body: JSON.stringify({ refresh_token: rc.platform.refreshToken }),
|
|
127
|
-
});
|
|
128
|
-
if (!res.ok)
|
|
129
|
-
return false;
|
|
130
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
131
|
-
const data = (await res.json());
|
|
132
|
-
if (!data.access_token)
|
|
133
|
-
return false;
|
|
134
|
-
// Update in-memory
|
|
135
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- private field update
|
|
136
|
-
this.apiKey = data.access_token;
|
|
137
|
-
// Persist to rc file
|
|
138
|
-
rc.platform.token = data.access_token;
|
|
139
|
-
if (data.refresh_token)
|
|
140
|
-
rc.platform.refreshToken = data.refresh_token;
|
|
141
|
-
const { writeFile } = await import('node:fs/promises');
|
|
142
|
-
const { homedir } = await import('node:os');
|
|
143
|
-
const path = await import('node:path');
|
|
144
|
-
const rcPath = path.join(homedir(), '.amodalrc');
|
|
145
|
-
await writeFile(rcPath, JSON.stringify(rc, null, 2) + '\n', { mode: 0o600 });
|
|
146
|
-
process.stderr.write('[auth] Token refreshed automatically.\n');
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Upload a snapshot and deploy it.
|
|
155
|
-
*/
|
|
156
|
-
async uploadSnapshot(snapshot, options = {}) {
|
|
157
|
-
return this.request('POST', '/api/snapshot-deployments', {
|
|
158
|
-
snapshot,
|
|
159
|
-
environment: options.environment ?? 'production',
|
|
160
|
-
appId: options.appId,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Trigger a runtime-app build on the build server.
|
|
165
|
-
* Sends the repo tarball to the build server which builds the SPA and uploads to R2.
|
|
166
|
-
*/
|
|
167
|
-
async triggerBuild(buildServerUrl, appId, deployId, repoTarball) {
|
|
168
|
-
const url = `${buildServerUrl}/build`;
|
|
169
|
-
const formData = new FormData();
|
|
170
|
-
formData.append('appId', appId);
|
|
171
|
-
formData.append('deployId', deployId);
|
|
172
|
-
// Convert ReadStream to Blob for FormData
|
|
173
|
-
const chunks = [];
|
|
174
|
-
for await (const chunk of repoTarball) {
|
|
175
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ReadStream chunks are Buffer/Uint8Array
|
|
176
|
-
chunks.push(chunk);
|
|
177
|
-
}
|
|
178
|
-
const blob = new Blob(chunks, { type: 'application/gzip' });
|
|
179
|
-
formData.append('repo', blob, 'repo.tar.gz');
|
|
180
|
-
const resp = await fetch(url, {
|
|
181
|
-
method: 'POST',
|
|
182
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
183
|
-
body: formData,
|
|
184
|
-
});
|
|
185
|
-
if (!resp.ok) {
|
|
186
|
-
let detail = '';
|
|
187
|
-
try {
|
|
188
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
189
|
-
const errBody = await resp.json();
|
|
190
|
-
detail = errBody.message ?? errBody.error ?? '';
|
|
191
|
-
}
|
|
192
|
-
catch { /* ignore */ }
|
|
193
|
-
throw new Error(`Build server failed (${resp.status}): ${detail}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Trigger a remote build:
|
|
198
|
-
* 1. Get scoped R2 temp credentials from the platform API
|
|
199
|
-
* 2. Upload the tarball directly to R2 with those creds
|
|
200
|
-
* 3. Tell the platform API to trigger a Fly Machine build
|
|
201
|
-
*
|
|
202
|
-
* Returns a buildId for polling.
|
|
203
|
-
*/
|
|
204
|
-
async triggerRemoteBuild(appId, environment, tarballPath, message) {
|
|
205
|
-
// Step 1: Mint scoped R2 temp credentials for the upload
|
|
206
|
-
const uploadInfo = await this.request('POST', '/api/deploys/build?action=upload-url', { appId });
|
|
207
|
-
// Step 2: Upload tarball directly to R2 with the scoped temp creds
|
|
208
|
-
const { readFileSync } = await import('node:fs');
|
|
209
|
-
const tarball = readFileSync(tarballPath);
|
|
210
|
-
const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
211
|
-
const s3 = new S3Client({
|
|
212
|
-
region: 'auto',
|
|
213
|
-
endpoint: uploadInfo.endpoint,
|
|
214
|
-
credentials: {
|
|
215
|
-
accessKeyId: uploadInfo.accessKeyId,
|
|
216
|
-
secretAccessKey: uploadInfo.secretAccessKey,
|
|
217
|
-
sessionToken: uploadInfo.sessionToken,
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
await s3.send(new PutObjectCommand({
|
|
221
|
-
Bucket: uploadInfo.bucket,
|
|
222
|
-
Key: uploadInfo.tarballKey,
|
|
223
|
-
Body: tarball,
|
|
224
|
-
ContentType: 'application/gzip',
|
|
225
|
-
}));
|
|
226
|
-
// Step 3: Trigger the build
|
|
227
|
-
const result = await this.request('POST', '/api/deploys/build?action=trigger', { appId, tarballKey: uploadInfo.tarballKey, environment, message });
|
|
228
|
-
return result;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Poll build status.
|
|
232
|
-
*/
|
|
233
|
-
async getBuildStatus(buildId) {
|
|
234
|
-
return this.request('GET', `/api/builds/${encodeURIComponent(buildId)}/status`);
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* List deployments for the authenticated app.
|
|
238
|
-
*/
|
|
239
|
-
async listDeployments(options = {}) {
|
|
240
|
-
const params = new URLSearchParams();
|
|
241
|
-
if (options.environment)
|
|
242
|
-
params.set('environment', options.environment);
|
|
243
|
-
if (options.limit)
|
|
244
|
-
params.set('limit', String(options.limit));
|
|
245
|
-
const qs = params.toString();
|
|
246
|
-
return this.request('GET', `/api/snapshot-deployments${qs ? `?${qs}` : ''}`);
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Rollback to a previous deployment.
|
|
250
|
-
*/
|
|
251
|
-
async rollback(options = {}) {
|
|
252
|
-
return this.request('POST', '/api/snapshot-deployments/rollback', {
|
|
253
|
-
deployId: options.deployId,
|
|
254
|
-
environment: options.environment ?? 'production',
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Promote a deployment from one environment to another.
|
|
259
|
-
*/
|
|
260
|
-
async promote(fromEnv, toEnv = 'production') {
|
|
261
|
-
return this.request('POST', '/api/snapshot-deployments/promote', {
|
|
262
|
-
fromEnvironment: fromEnv,
|
|
263
|
-
toEnvironment: toEnv,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Get status of a specific deployment.
|
|
268
|
-
*/
|
|
269
|
-
async getStatus(deployId) {
|
|
270
|
-
return this.request('GET', `/api/snapshot-deployments/${deployId}`);
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Get the active snapshot for an environment.
|
|
274
|
-
*/
|
|
275
|
-
async getActiveSnapshot(environment = 'production') {
|
|
276
|
-
const params = new URLSearchParams({ environment });
|
|
277
|
-
return this.request('GET', `/api/snapshot-deployments/active?${params.toString()}`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
//# sourceMappingURL=platform-client.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"platform-client.js","sourceRoot":"","sources":["../../../src/shared/platform-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAC,eAAe,EAAC,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAC,UAAU,EAAC,MAAM,sBAAsB,CAAC;AAkBhD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAG3C;IACC,IAAI,GAAG,GAAG,OAAO,EAAE,GAAG,CAAC;IACvB,IAAI,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAE7B,2BAA2B;IAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,MAAM,eAAe,EAAE,CAAC;QACrC,IAAI,IAAI,EAAE,WAAW,EAAE,CAAC;YACtB,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC;QACzB,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,EAAE,GAAG,MAAM,UAAU,EAAE,CAAC;QAC9B,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC;YACvB,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;YAC3B,4CAA4C;YAC5C,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;gBAC5B,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;YACxB,CAAC;QACH,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,CAAC,GAAG;QAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM;QAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAEtD,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,sFAAsF,CAAC,CAAC;IAClH,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;IAEtG,OAAO,EAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,EAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IACR,OAAO,CAAS;IAChB,MAAM,CAAS;IAEhC,YAAY,OAAyC;QACnD,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAClE,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,sFAAsF,CAAC,CAAC;QAClH,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QACtG,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAyC;QAC3D,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACpD,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IAEO,OAAO;QACb,OAAO;YACL,eAAe,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;YACxC,cAAc,EAAE,kBAAkB;SACnC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,OAAO,CAAI,MAAc,EAAE,IAAY,EAAE,IAAc;QACnE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;QACrC,IAAI,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC1B,MAAM;YACN,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;YACvB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C,CAAC,CAAC;QAEH,sBAAsB;QACtB,IAAI,IAAI,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACxB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;YAC/C,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;oBACtB,MAAM;oBACN,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;oBACvB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC9C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,uEAAuE;gBACvE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAsB,CAAC;gBACtD,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,gBAAgB,MAAM,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;QACrF,CAAC;QAED,uEAAuE;QACvE,OAAO,IAAI,CAAC,IAAI,EAAgB,CAAC;IACnC,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,eAAe;QAC3B,IAAI,CAAC;YACH,MAAM,EAAC,UAAU,EAAC,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;YAC1D,MAAM,EAAE,GAAG,MAAM,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY;gBAAE,OAAO,KAAK,CAAC;YAE7C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,mBAAmB,EAAE;gBAC1D,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAC,cAAc,EAAE,kBAAkB,EAAC;gBAC7C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAC,aAAa,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAC,CAAC;aAChE,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC;YAE1B,uEAAuE;YACvE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoD,CAAC;YACnF,IAAI,CAAC,IAAI,CAAC,YAAY;gBAAE,OAAO,KAAK,CAAC;YAErC,mBAAmB;YACnB,+FAA+F;YAC9F,IAAoC,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC;YAEjE,qBAAqB;YACrB,EAAE,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC;YACtC,IAAI,IAAI,CAAC,aAAa;gBAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC;YACtE,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;YACrD,MAAM,EAAC,OAAO,EAAC,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1C,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;YACjD,MAAM,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAC,IAAI,EAAE,KAAK,EAAC,CAAC,CAAC;YAE3E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,QAAwB,EAAE,UAG3C,EAAE;QACJ,OAAO,IAAI,CAAC,OAAO,CAAyB,MAAM,EAAE,2BAA2B,EAAE;YAC/E,QAAQ;YACR,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,YAAY;YAChD,KAAK,EAAE,OAAO,CAAC,KAAK;SACrB,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,YAAY,CAChB,cAAsB,EACtB,KAAa,EACb,QAAgB,EAChB,WAAyC;QAEzC,MAAM,GAAG,GAAG,GAAG,cAAc,QAAQ,CAAC;QAEtC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAEtC,0CAA0C;QAC1C,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YACtC,kHAAkH;YAClH,MAAM,CAAC,IAAI,CAAC,KAAmB,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,EAAC,IAAI,EAAE,kBAAkB,EAAC,CAAC,CAAC;QAC1D,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAE7C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE,EAAC;YACjD,IAAI,EAAE,QAAQ;SACf,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,uEAAuE;gBACvE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAwC,CAAC;gBACxE,MAAM,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YAClD,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,MAAM,MAAM,MAAM,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,kBAAkB,CACtB,KAAa,EACb,WAAmB,EACnB,WAAmB,EACnB,OAAgB;QAEhB,yDAAyD;QAEzD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,CASnC,MAAM,EACN,sCAAsC,EACtC,EAAC,KAAK,EAAC,CACR,CAAC;QAEF,mEAAmE;QACnE,MAAM,EAAC,YAAY,EAAC,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;QAE1C,MAAM,EAAC,QAAQ,EAAE,gBAAgB,EAAC,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACxE,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC;YACtB,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,WAAW,EAAE;gBACX,WAAW,EAAE,UAAU,CAAC,WAAW;gBACnC,eAAe,EAAE,UAAU,CAAC,eAAe;gBAC3C,YAAY,EAAE,UAAU,CAAC,YAAY;aACtC;SACF,CAAC,CAAC;QAEH,MAAM,EAAE,CAAC,IAAI,CACX,IAAI,gBAAgB,CAAC;YACnB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,GAAG,EAAE,UAAU,CAAC,UAAU;YAC1B,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,kBAAkB;SAChC,CAAC,CACH,CAAC;QAEF,4BAA4B;QAE5B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAC/B,MAAM,EACN,mCAAmC,EACnC,EAAC,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,EAAC,CACjE,CAAC;QAEF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,OAAe;QAMlC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,eAAe,kBAAkB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClF,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CAAC,UAGlB,EAAE;QACJ,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,WAAW;YAAE,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QACxE,IAAI,OAAO,CAAC,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9D,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,OAAO,CAA2B,KAAK,EAAE,4BAA4B,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzG,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,UAGX,EAAE;QACJ,OAAO,IAAI,CAAC,OAAO,CAAyB,MAAM,EAAE,oCAAoC,EAAE;YACxF,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,YAAY;SACjD,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,QAAgB,YAAY;QACzD,OAAO,IAAI,CAAC,OAAO,CAAyB,MAAM,EAAE,mCAAmC,EAAE;YACvF,eAAe,EAAE,OAAO;YACxB,aAAa,EAAE,KAAK;SACrB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,QAAgB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAyB,KAAK,EAAE,6BAA6B,QAAQ,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,cAAsB,YAAY;QACxD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAC,WAAW,EAAC,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,OAAO,CAAiB,KAAK,EAAE,oCAAoC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACtG,CAAC;CACF"}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
8
|
-
|
|
9
|
-
describe('runAudit', () => {
|
|
10
|
-
let fetchSpy: ReturnType<typeof vi.fn>;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
vi.clearAllMocks();
|
|
14
|
-
fetchSpy = vi.fn();
|
|
15
|
-
vi.stubGlobal('fetch', fetchSpy);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('outputs JSON format', async () => {
|
|
19
|
-
fetchSpy.mockResolvedValue({
|
|
20
|
-
ok: true,
|
|
21
|
-
json: () => Promise.resolve({
|
|
22
|
-
sessionId: 'sess-1',
|
|
23
|
-
events: [
|
|
24
|
-
{id: 'e1', eventType: 'tool_call', data: {}, tokenCount: 10, durationMs: 50, createdAt: '2026-03-15T10:00:00Z'},
|
|
25
|
-
],
|
|
26
|
-
}),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
30
|
-
|
|
31
|
-
const {runAudit} = await import('./audit.js');
|
|
32
|
-
await runAudit({
|
|
33
|
-
sessionId: 'sess-1',
|
|
34
|
-
format: 'json',
|
|
35
|
-
platformUrl: 'http://localhost:4000',
|
|
36
|
-
platformApiKey: 'key-123',
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
expect(fetchSpy).toHaveBeenCalledOnce();
|
|
40
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
41
|
-
expect(output).toContain('"sessionId"');
|
|
42
|
-
stdoutSpy.mockRestore();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('outputs table format by default', async () => {
|
|
46
|
-
fetchSpy.mockResolvedValue({
|
|
47
|
-
ok: true,
|
|
48
|
-
json: () => Promise.resolve({
|
|
49
|
-
sessionId: 'sess-1',
|
|
50
|
-
events: [
|
|
51
|
-
{id: 'e1', eventType: 'tool_call', data: {}, tokenCount: null, durationMs: 50, createdAt: '2026-03-15T10:00:00Z'},
|
|
52
|
-
{id: 'e2', eventType: 'session_end', data: {}, tokenCount: null, durationMs: null, createdAt: '2026-03-15T10:01:00Z'},
|
|
53
|
-
],
|
|
54
|
-
}),
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
58
|
-
|
|
59
|
-
const {runAudit} = await import('./audit.js');
|
|
60
|
-
await runAudit({
|
|
61
|
-
sessionId: 'sess-1',
|
|
62
|
-
platformUrl: 'http://localhost:4000',
|
|
63
|
-
platformApiKey: 'key-123',
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
67
|
-
expect(output).toContain('tool_call');
|
|
68
|
-
expect(output).toContain('session_end');
|
|
69
|
-
expect(output).toContain('Total: 2 events');
|
|
70
|
-
stdoutSpy.mockRestore();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('handles empty events', async () => {
|
|
74
|
-
fetchSpy.mockResolvedValue({
|
|
75
|
-
ok: true,
|
|
76
|
-
json: () => Promise.resolve({sessionId: 'sess-1', events: []}),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
80
|
-
|
|
81
|
-
const {runAudit} = await import('./audit.js');
|
|
82
|
-
await runAudit({
|
|
83
|
-
sessionId: 'sess-1',
|
|
84
|
-
platformUrl: 'http://localhost:4000',
|
|
85
|
-
platformApiKey: 'key-123',
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
89
|
-
expect(output).toContain('No audit events found');
|
|
90
|
-
stdoutSpy.mockRestore();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
8
|
-
|
|
9
|
-
describe('runExperimentCommand', () => {
|
|
10
|
-
let fetchSpy: ReturnType<typeof vi.fn>;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
vi.clearAllMocks();
|
|
14
|
-
fetchSpy = vi.fn();
|
|
15
|
-
vi.stubGlobal('fetch', fetchSpy);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('lists experiments', async () => {
|
|
19
|
-
fetchSpy.mockResolvedValue({
|
|
20
|
-
ok: true,
|
|
21
|
-
json: () => Promise.resolve({
|
|
22
|
-
experiments: [
|
|
23
|
-
{id: 'exp-1', name: 'test-exp', status: 'draft'},
|
|
24
|
-
],
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
29
|
-
|
|
30
|
-
const {runExperimentCommand} = await import('./experiment.js');
|
|
31
|
-
await runExperimentCommand({
|
|
32
|
-
action: 'list',
|
|
33
|
-
platformUrl: 'http://localhost:4000',
|
|
34
|
-
platformApiKey: 'key-123',
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
38
|
-
expect(output).toContain('exp-1');
|
|
39
|
-
expect(output).toContain('test-exp');
|
|
40
|
-
stdoutSpy.mockRestore();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('creates an experiment', async () => {
|
|
44
|
-
fetchSpy.mockResolvedValue({
|
|
45
|
-
ok: true,
|
|
46
|
-
json: () => Promise.resolve({id: 'exp-new'}),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
50
|
-
|
|
51
|
-
const {runExperimentCommand} = await import('./experiment.js');
|
|
52
|
-
await runExperimentCommand({
|
|
53
|
-
action: 'create',
|
|
54
|
-
name: 'my-experiment',
|
|
55
|
-
controlConfig: '{"model":"claude-sonnet-4-20250514"}',
|
|
56
|
-
variantConfig: '{"model":"gpt-4o"}',
|
|
57
|
-
platformUrl: 'http://localhost:4000',
|
|
58
|
-
platformApiKey: 'key-123',
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
expect(fetchSpy).toHaveBeenCalledOnce();
|
|
62
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
63
|
-
expect(output).toContain('Created experiment: exp-new');
|
|
64
|
-
stdoutSpy.mockRestore();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('deploys an experiment', async () => {
|
|
68
|
-
fetchSpy.mockResolvedValue({ok: true, json: () => Promise.resolve({status: 'ok'})});
|
|
69
|
-
|
|
70
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
71
|
-
|
|
72
|
-
const {runExperimentCommand} = await import('./experiment.js');
|
|
73
|
-
await runExperimentCommand({
|
|
74
|
-
action: 'deploy',
|
|
75
|
-
id: 'exp-1',
|
|
76
|
-
platformUrl: 'http://localhost:4000',
|
|
77
|
-
platformApiKey: 'key-123',
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
81
|
-
expect(output).toContain('Deployed experiment: exp-1');
|
|
82
|
-
stdoutSpy.mockRestore();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('watches an experiment', async () => {
|
|
86
|
-
fetchSpy.mockResolvedValue({
|
|
87
|
-
ok: true,
|
|
88
|
-
json: () => Promise.resolve({id: 'exp-1', name: 'test', status: 'deployed'}),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
92
|
-
|
|
93
|
-
const {runExperimentCommand} = await import('./experiment.js');
|
|
94
|
-
await runExperimentCommand({
|
|
95
|
-
action: 'watch',
|
|
96
|
-
id: 'exp-1',
|
|
97
|
-
platformUrl: 'http://localhost:4000',
|
|
98
|
-
platformApiKey: 'key-123',
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
102
|
-
expect(output).toContain('"id": "exp-1"');
|
|
103
|
-
stdoutSpy.mockRestore();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('handles empty experiment list', async () => {
|
|
107
|
-
fetchSpy.mockResolvedValue({
|
|
108
|
-
ok: true,
|
|
109
|
-
json: () => Promise.resolve({experiments: []}),
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
113
|
-
|
|
114
|
-
const {runExperimentCommand} = await import('./experiment.js');
|
|
115
|
-
await runExperimentCommand({
|
|
116
|
-
action: 'list',
|
|
117
|
-
platformUrl: 'http://localhost:4000',
|
|
118
|
-
platformApiKey: 'key-123',
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const output = stdoutSpy.mock.calls.map(([s]) => s).join('');
|
|
122
|
-
expect(output).toContain('No experiments found');
|
|
123
|
-
stdoutSpy.mockRestore();
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
8
|
-
import {PlatformClient} from './platform-client.js';
|
|
9
|
-
|
|
10
|
-
describe('PlatformClient', () => {
|
|
11
|
-
const originalFetch = globalThis.fetch;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
vi.restoreAllMocks();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
globalThis.fetch = originalFetch;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('throws if PLATFORM_API_URL not set', () => {
|
|
22
|
-
const origUrl = process.env['PLATFORM_API_URL'];
|
|
23
|
-
const origKey = process.env['PLATFORM_API_KEY'];
|
|
24
|
-
delete process.env['PLATFORM_API_URL'];
|
|
25
|
-
delete process.env['PLATFORM_API_KEY'];
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
expect(() => new PlatformClient()).toThrow('Platform URL not found');
|
|
29
|
-
} finally {
|
|
30
|
-
if (origUrl) process.env['PLATFORM_API_URL'] = origUrl;
|
|
31
|
-
if (origKey) process.env['PLATFORM_API_KEY'] = origKey;
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('throws if PLATFORM_API_KEY not set', () => {
|
|
36
|
-
expect(() => new PlatformClient({url: 'http://localhost:4000'})).toThrow('Platform auth not found');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('creates client with explicit options', () => {
|
|
40
|
-
const client = new PlatformClient({url: 'http://localhost:4000', apiKey: 'test-key'});
|
|
41
|
-
expect(client).toBeDefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('uploads snapshot via POST', async () => {
|
|
45
|
-
const mockResponse = {
|
|
46
|
-
id: 'deploy-abc1234',
|
|
47
|
-
environment: 'production',
|
|
48
|
-
isActive: true,
|
|
49
|
-
createdAt: new Date().toISOString(),
|
|
50
|
-
createdBy: 'test',
|
|
51
|
-
source: 'cli',
|
|
52
|
-
snapshotSize: 1024,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
56
|
-
ok: true,
|
|
57
|
-
json: () => Promise.resolve(mockResponse),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const client = new PlatformClient({url: 'http://localhost:4000', apiKey: 'key'});
|
|
61
|
-
const result = await client.uploadSnapshot({
|
|
62
|
-
deployId: 'deploy-abc1234',
|
|
63
|
-
createdAt: new Date().toISOString(),
|
|
64
|
-
createdBy: 'test',
|
|
65
|
-
source: 'cli',
|
|
66
|
-
config: {name: 'test', version: '1.0', models: {main: {provider: 'a', model: 'b'}}},
|
|
67
|
-
connections: {},
|
|
68
|
-
skills: [],
|
|
69
|
-
automations: [],
|
|
70
|
-
knowledge: [],
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
expect(result.id).toBe('deploy-abc1234');
|
|
74
|
-
|
|
75
|
-
const fetchCall = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string, RequestInit];
|
|
76
|
-
expect(fetchCall[0]).toBe('http://localhost:4000/api/snapshot-deployments');
|
|
77
|
-
expect(fetchCall[1].method).toBe('POST');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('lists deployments via GET', async () => {
|
|
81
|
-
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
82
|
-
ok: true,
|
|
83
|
-
json: () => Promise.resolve([]),
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const client = new PlatformClient({url: 'http://localhost:4000', apiKey: 'key'});
|
|
87
|
-
const result = await client.listDeployments({environment: 'staging', limit: 5});
|
|
88
|
-
|
|
89
|
-
expect(result).toEqual([]);
|
|
90
|
-
|
|
91
|
-
const fetchCall = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string];
|
|
92
|
-
expect(fetchCall[0]).toContain('environment=staging');
|
|
93
|
-
expect(fetchCall[0]).toContain('limit=5');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('throws on non-ok response', async () => {
|
|
97
|
-
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
98
|
-
ok: false,
|
|
99
|
-
status: 401,
|
|
100
|
-
json: () => Promise.resolve({error: 'Unauthorized'}),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const client = new PlatformClient({url: 'http://localhost:4000', apiKey: 'bad-key'});
|
|
104
|
-
await expect(client.listDeployments()).rejects.toThrow('failed (401)');
|
|
105
|
-
});
|
|
106
|
-
});
|