omnara 0.5.1 → 0.5.3
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/cli.js +449 -265
- package/dist/cli.js.map +4 -4
- package/dist/orchestrator/orchestrator-prompt.d.ts +1 -1
- package/dist/package-info.d.ts +9 -0
- package/dist/utils/claude-code-check.d.ts +13 -0
- package/dist/utils/executable.d.ts +53 -0
- package/dist/utils/version-check.d.ts +44 -2
- package/dist/utils/websocket.d.ts +5 -0
- package/package.json +5 -2
- package/scripts/build-executable.ts +244 -0
- package/scripts/generate-release-json.ts +136 -0
- package/scripts/upload-release.ts +388 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Upload Omnara CLI releases to R2/S3 storage.
|
|
4
|
+
*
|
|
5
|
+
* This unified script handles both prerelease and stable release uploads.
|
|
6
|
+
* It generates the release.json manifest and uploads all binaries.
|
|
7
|
+
*
|
|
8
|
+
* Required environment variables:
|
|
9
|
+
* AWS_ACCESS_KEY_ID - R2/S3 access key
|
|
10
|
+
* AWS_SECRET_ACCESS_KEY - R2/S3 secret key
|
|
11
|
+
* AWS_ENDPOINT_URL - R2 endpoint (e.g., https://<account_id>.r2.cloudflarestorage.com)
|
|
12
|
+
* BUCKET_NAME - Bucket name (e.g., omnara-releases)
|
|
13
|
+
*
|
|
14
|
+
* Optional:
|
|
15
|
+
* RELEASE_BASE_URL - Base URL for downloads (default: https://releases.omnara.com)
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* # Upload a stable release (to /{version}/ and /latest/)
|
|
19
|
+
* bun run scripts/upload-release.ts --version 0.5.0
|
|
20
|
+
*
|
|
21
|
+
* # Upload a prerelease (to /prerelease/{version}/ and /prerelease/latest/)
|
|
22
|
+
* bun run scripts/upload-release.ts --version 0.5.0-pre.abc1234 --prerelease
|
|
23
|
+
*
|
|
24
|
+
* # Dry run (generate release.json only, no upload)
|
|
25
|
+
* bun run scripts/upload-release.ts --version 0.5.0 --dry-run
|
|
26
|
+
*
|
|
27
|
+
* # Skip version existence check
|
|
28
|
+
* bun run scripts/upload-release.ts --version 0.5.0 --force
|
|
29
|
+
*
|
|
30
|
+
* This script expects executables to already be built in dist/exe/
|
|
31
|
+
* Run `mise run build-exe-all` or `bun run scripts/build-executable.ts --all` first.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { createHash } from 'node:crypto';
|
|
35
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
36
|
+
import { dirname, join, resolve } from 'node:path';
|
|
37
|
+
import { $ } from 'bun';
|
|
38
|
+
|
|
39
|
+
const projectRoot = resolve(dirname(import.meta.path), '..');
|
|
40
|
+
const exeDir = join(projectRoot, 'dist', 'exe');
|
|
41
|
+
const pkgJsonPath = join(projectRoot, 'package.json');
|
|
42
|
+
|
|
43
|
+
// Colors for terminal output
|
|
44
|
+
const colors = {
|
|
45
|
+
reset: '\x1b[0m',
|
|
46
|
+
red: '\x1b[31m',
|
|
47
|
+
green: '\x1b[32m',
|
|
48
|
+
yellow: '\x1b[33m',
|
|
49
|
+
blue: '\x1b[34m',
|
|
50
|
+
cyan: '\x1b[36m',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function log(msg: string): void {
|
|
54
|
+
console.log(`${colors.blue}==>${colors.reset} ${msg}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function success(msg: string): void {
|
|
58
|
+
console.log(`${colors.green}==>${colors.reset} ${msg}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function warn(msg: string): void {
|
|
62
|
+
console.log(`${colors.yellow}warning:${colors.reset} ${msg}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function error(msg: string): never {
|
|
66
|
+
console.error(`${colors.red}error:${colors.reset} ${msg}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Config {
|
|
71
|
+
version: string;
|
|
72
|
+
prerelease: boolean;
|
|
73
|
+
dryRun: boolean;
|
|
74
|
+
force: boolean;
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
endpoint: string;
|
|
77
|
+
bucket: string;
|
|
78
|
+
accessKey: string;
|
|
79
|
+
secretKey: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface PlatformInfo {
|
|
83
|
+
url: string;
|
|
84
|
+
sha256: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ReleaseInfo {
|
|
88
|
+
version: string;
|
|
89
|
+
released_at: string;
|
|
90
|
+
platforms: Record<string, PlatformInfo>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseArgs(): Partial<Config> {
|
|
94
|
+
const args = process.argv.slice(2);
|
|
95
|
+
const config: Partial<Config> = {
|
|
96
|
+
prerelease: false,
|
|
97
|
+
dryRun: false,
|
|
98
|
+
force: false,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < args.length; i++) {
|
|
102
|
+
const arg = args[i];
|
|
103
|
+
if (arg === '--version' && args[i + 1]) {
|
|
104
|
+
config.version = args[++i];
|
|
105
|
+
} else if (arg === '--base-url' && args[i + 1]) {
|
|
106
|
+
config.baseUrl = args[++i];
|
|
107
|
+
} else if (arg === '--prerelease') {
|
|
108
|
+
config.prerelease = true;
|
|
109
|
+
} else if (arg === '--dry-run') {
|
|
110
|
+
config.dryRun = true;
|
|
111
|
+
} else if (arg === '--force') {
|
|
112
|
+
config.force = true;
|
|
113
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
114
|
+
printHelp();
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return config;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function printHelp(): void {
|
|
123
|
+
console.log(`
|
|
124
|
+
Upload Omnara CLI releases to R2/S3 storage.
|
|
125
|
+
|
|
126
|
+
Usage:
|
|
127
|
+
bun run scripts/upload-release.ts [options]
|
|
128
|
+
|
|
129
|
+
Options:
|
|
130
|
+
--version <version> Version to upload (defaults to package.json version)
|
|
131
|
+
--base-url <url> Base URL for downloads (default: https://releases.omnara.com)
|
|
132
|
+
--prerelease Upload as prerelease (to /prerelease/ prefix)
|
|
133
|
+
--dry-run Generate release.json only, skip upload
|
|
134
|
+
--force Skip version existence check
|
|
135
|
+
--help, -h Show this help message
|
|
136
|
+
|
|
137
|
+
Environment Variables:
|
|
138
|
+
AWS_ACCESS_KEY_ID R2/S3 access key (required for upload)
|
|
139
|
+
AWS_SECRET_ACCESS_KEY R2/S3 secret key (required for upload)
|
|
140
|
+
AWS_ENDPOINT_URL R2 endpoint URL (required for upload)
|
|
141
|
+
BUCKET_NAME Bucket name (required for upload)
|
|
142
|
+
RELEASE_BASE_URL Base URL for downloads
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
# Upload stable release
|
|
146
|
+
bun run scripts/upload-release.ts --version 0.5.0
|
|
147
|
+
|
|
148
|
+
# Upload prerelease
|
|
149
|
+
bun run scripts/upload-release.ts --version 0.5.0-pre.abc1234 --prerelease
|
|
150
|
+
|
|
151
|
+
# Dry run (no upload)
|
|
152
|
+
bun run scripts/upload-release.ts --version 0.5.0 --dry-run
|
|
153
|
+
`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getPackageVersion(): string {
|
|
157
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as { version?: string };
|
|
158
|
+
return pkgJson.version ?? '0.0.0';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function checkEnvVars(): { endpoint: string; bucket: string; accessKey: string; secretKey: string } {
|
|
162
|
+
const accessKey = process.env.AWS_ACCESS_KEY_ID;
|
|
163
|
+
const secretKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
164
|
+
const endpoint = process.env.AWS_ENDPOINT_URL ?? process.env.R2_ENDPOINT_URL;
|
|
165
|
+
const bucket = process.env.BUCKET_NAME ?? process.env.R2_BUCKET;
|
|
166
|
+
|
|
167
|
+
const missing: string[] = [];
|
|
168
|
+
if (!accessKey) missing.push('AWS_ACCESS_KEY_ID');
|
|
169
|
+
if (!secretKey) missing.push('AWS_SECRET_ACCESS_KEY');
|
|
170
|
+
if (!endpoint) missing.push('AWS_ENDPOINT_URL');
|
|
171
|
+
if (!bucket) missing.push('BUCKET_NAME');
|
|
172
|
+
|
|
173
|
+
if (missing.length > 0) {
|
|
174
|
+
error(`Missing required environment variables: ${missing.join(', ')}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { endpoint: endpoint!, bucket: bucket!, accessKey: accessKey!, secretKey: secretKey! };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function calculateSha256(filePath: string): string {
|
|
181
|
+
const content = readFileSync(filePath);
|
|
182
|
+
return createHash('sha256').update(content).digest('hex');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function extractPlatform(filename: string): string {
|
|
186
|
+
return filename.replace(/^omnara-/, '').replace(/\.exe$/, '');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getExecutableFiles(): string[] {
|
|
190
|
+
if (!existsSync(exeDir)) {
|
|
191
|
+
error(`Executable directory not found: ${exeDir}\nRun 'bun run scripts/build-executable.ts --all' first.`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const files = readdirSync(exeDir).filter((f) => {
|
|
195
|
+
return f.startsWith('omnara-') && !f.endsWith('.sha256') && !f.endsWith('.json');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (files.length === 0) {
|
|
199
|
+
error(`No executable files found in ${exeDir}\nRun 'bun run scripts/build-executable.ts --all' first.`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return files;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function generateReleaseJson(version: string, baseUrl: string, prefix: string): ReleaseInfo {
|
|
206
|
+
const files = getExecutableFiles();
|
|
207
|
+
const platforms: Record<string, PlatformInfo> = {};
|
|
208
|
+
|
|
209
|
+
log('Generating release.json...');
|
|
210
|
+
console.log('');
|
|
211
|
+
|
|
212
|
+
for (const file of files) {
|
|
213
|
+
const filePath = join(exeDir, file);
|
|
214
|
+
const platform = extractPlatform(file);
|
|
215
|
+
const sha256 = calculateSha256(filePath);
|
|
216
|
+
|
|
217
|
+
// URL path: {baseUrl}/{prefix}/{version}/{file}
|
|
218
|
+
// For prerelease: https://releases.omnara.com/prerelease/0.5.0-pre.abc1234/omnara-darwin-arm64
|
|
219
|
+
// For release: https://releases.omnara.com/0.5.0/omnara-darwin-arm64
|
|
220
|
+
const urlPath = prefix ? `${prefix}/${version}` : version;
|
|
221
|
+
platforms[platform] = {
|
|
222
|
+
url: `${baseUrl}/${urlPath}/${file}`,
|
|
223
|
+
sha256,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Write individual .sha256 file
|
|
227
|
+
writeFileSync(`${filePath}.sha256`, sha256);
|
|
228
|
+
|
|
229
|
+
console.log(` ${colors.cyan}${platform}${colors.reset}`);
|
|
230
|
+
console.log(` SHA256: ${sha256}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const release: ReleaseInfo = {
|
|
234
|
+
version,
|
|
235
|
+
released_at: new Date().toISOString(),
|
|
236
|
+
platforms,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const releasePath = join(exeDir, 'release.json');
|
|
240
|
+
writeFileSync(releasePath, JSON.stringify(release, null, 2) + '\n');
|
|
241
|
+
|
|
242
|
+
console.log('');
|
|
243
|
+
success(`Generated ${releasePath}`);
|
|
244
|
+
|
|
245
|
+
return release;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function checkVersionExists(baseUrl: string, prefix: string, version: string): Promise<boolean> {
|
|
249
|
+
const urlPath = prefix ? `${prefix}/${version}` : version;
|
|
250
|
+
const releaseUrl = `${baseUrl}/${urlPath}/release.json`;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const response = await fetch(releaseUrl, { method: 'HEAD' });
|
|
254
|
+
return response.ok;
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function uploadFile(
|
|
261
|
+
filePath: string,
|
|
262
|
+
destPath: string,
|
|
263
|
+
contentType: string,
|
|
264
|
+
config: Config
|
|
265
|
+
): Promise<void> {
|
|
266
|
+
const { endpoint, bucket } = config;
|
|
267
|
+
|
|
268
|
+
await $`aws s3 cp ${filePath} s3://${bucket}/${destPath} --endpoint-url ${endpoint} --content-type ${contentType}`.quiet();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function uploadRelease(config: Config, prefix: string): Promise<void> {
|
|
272
|
+
const files = getExecutableFiles();
|
|
273
|
+
const { version } = config;
|
|
274
|
+
|
|
275
|
+
// Determine paths
|
|
276
|
+
const versionPath = prefix ? `${prefix}/${version}` : version;
|
|
277
|
+
const latestPath = prefix ? `${prefix}/latest` : 'latest';
|
|
278
|
+
|
|
279
|
+
// Upload binaries to version directory
|
|
280
|
+
log(`Uploading executables to ${versionPath}/...`);
|
|
281
|
+
for (const file of files) {
|
|
282
|
+
const filePath = join(exeDir, file);
|
|
283
|
+
console.log(` ${file}`);
|
|
284
|
+
await uploadFile(filePath, `${versionPath}/${file}`, 'application/octet-stream', config);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Upload release.json to version directory
|
|
288
|
+
log(`Uploading release.json to ${versionPath}/...`);
|
|
289
|
+
await uploadFile(join(exeDir, 'release.json'), `${versionPath}/release.json`, 'application/json', config);
|
|
290
|
+
|
|
291
|
+
// Upload binaries to latest directory
|
|
292
|
+
log(`Uploading executables to ${latestPath}/...`);
|
|
293
|
+
for (const file of files) {
|
|
294
|
+
const filePath = join(exeDir, file);
|
|
295
|
+
console.log(` ${file}`);
|
|
296
|
+
await uploadFile(filePath, `${latestPath}/${file}`, 'application/octet-stream', config);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Upload release.json to latest directory
|
|
300
|
+
log(`Uploading release.json to ${latestPath}/...`);
|
|
301
|
+
await uploadFile(join(exeDir, 'release.json'), `${latestPath}/release.json`, 'application/json', config);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function main(): Promise<void> {
|
|
305
|
+
console.log('');
|
|
306
|
+
console.log(' Omnara CLI Release Uploader');
|
|
307
|
+
console.log('');
|
|
308
|
+
|
|
309
|
+
const parsedArgs = parseArgs();
|
|
310
|
+
const version = parsedArgs.version ?? getPackageVersion();
|
|
311
|
+
const prerelease = parsedArgs.prerelease ?? false;
|
|
312
|
+
const dryRun = parsedArgs.dryRun ?? false;
|
|
313
|
+
const force = parsedArgs.force ?? false;
|
|
314
|
+
const baseUrl = parsedArgs.baseUrl ?? process.env.RELEASE_BASE_URL ?? 'https://releases.omnara.com';
|
|
315
|
+
|
|
316
|
+
// Prefix for prerelease uploads
|
|
317
|
+
const prefix = prerelease ? 'prerelease' : '';
|
|
318
|
+
|
|
319
|
+
log(`Version: ${version}`);
|
|
320
|
+
log(`Type: ${prerelease ? 'prerelease' : 'stable release'}`);
|
|
321
|
+
log(`Base URL: ${baseUrl}`);
|
|
322
|
+
if (dryRun) {
|
|
323
|
+
warn('Dry run mode - will not upload');
|
|
324
|
+
}
|
|
325
|
+
console.log('');
|
|
326
|
+
|
|
327
|
+
// Check if version already exists (unless force or dry-run)
|
|
328
|
+
if (!dryRun && !force) {
|
|
329
|
+
log('Checking if version already exists...');
|
|
330
|
+
const exists = await checkVersionExists(baseUrl, prefix, version);
|
|
331
|
+
if (exists) {
|
|
332
|
+
warn(`Version ${version} already exists at ${baseUrl}/${prefix ? prefix + '/' : ''}${version}/`);
|
|
333
|
+
warn('Use --force to overwrite');
|
|
334
|
+
process.exit(0);
|
|
335
|
+
}
|
|
336
|
+
success('Version does not exist, proceeding with upload');
|
|
337
|
+
console.log('');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Generate release.json
|
|
341
|
+
generateReleaseJson(version, baseUrl, prefix);
|
|
342
|
+
console.log('');
|
|
343
|
+
|
|
344
|
+
// Upload if not dry run
|
|
345
|
+
if (!dryRun) {
|
|
346
|
+
// Check environment variables
|
|
347
|
+
const envVars = checkEnvVars();
|
|
348
|
+
|
|
349
|
+
const config: Config = {
|
|
350
|
+
version,
|
|
351
|
+
prerelease,
|
|
352
|
+
dryRun,
|
|
353
|
+
force,
|
|
354
|
+
baseUrl,
|
|
355
|
+
...envVars,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
await uploadRelease(config, prefix);
|
|
359
|
+
|
|
360
|
+
console.log('');
|
|
361
|
+
success(`${prerelease ? 'Prerelease' : 'Release'} ${version} uploaded successfully!`);
|
|
362
|
+
console.log('');
|
|
363
|
+
console.log(' Binaries available at:');
|
|
364
|
+
const versionPath = prefix ? `${prefix}/${version}` : version;
|
|
365
|
+
const latestPath = prefix ? `${prefix}/latest` : 'latest';
|
|
366
|
+
console.log(` ${baseUrl}/${versionPath}/`);
|
|
367
|
+
console.log(` ${baseUrl}/${latestPath}/`);
|
|
368
|
+
console.log('');
|
|
369
|
+
|
|
370
|
+
if (prerelease) {
|
|
371
|
+
console.log(' To install prerelease:');
|
|
372
|
+
console.log(' PRERELEASE=1 curl -fsSL https://omnara.com/install/install.sh | bash');
|
|
373
|
+
} else {
|
|
374
|
+
console.log(' To install:');
|
|
375
|
+
console.log(' curl -fsSL https://omnara.com/install/install.sh | bash');
|
|
376
|
+
}
|
|
377
|
+
console.log('');
|
|
378
|
+
} else {
|
|
379
|
+
console.log('');
|
|
380
|
+
success('Dry run complete - release.json generated but not uploaded');
|
|
381
|
+
console.log('');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
main().catch((err) => {
|
|
386
|
+
console.error(err);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
});
|