omnara 0.5.2 → 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.
@@ -4,5 +4,5 @@
4
4
  * The orchestrator's role is to coordinate and delegate work, not to execute tasks directly.
5
5
  * It should ALWAYS use workers to handle all implementation work.
6
6
  */
7
- declare const ORCHESTRATOR_SYSTEM_PROMPT: string;
7
+ import ORCHESTRATOR_SYSTEM_PROMPT from './orchestrator-prompt.md';
8
8
  export { ORCHESTRATOR_SYSTEM_PROMPT };
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Package information constants.
3
+ *
4
+ * These are replaced at build time by esbuild/bun with values from package.json.
5
+ * The build process uses --define flags to replace __PACKAGE_* constants.
6
+ *
7
+ * In bundled context: Constants are replaced with literal strings at compile time.
8
+ * In dev context: Falls back to default values (actual version comes from build).
9
+ */
1
10
  export declare const PACKAGE_NAME: string;
2
11
  export declare const PACKAGE_VERSION: string;
3
12
  export declare const PACKAGE_DESCRIPTION: string;
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Finds the path to the Claude Code executable.
3
+ *
4
+ * When running from a Bun single-file executable, the SDK's default resolution
5
+ * fails because it looks for cli.js relative to the bundled module location,
6
+ * which points to Bun's virtual filesystem (/$bunfs/root/cli.js).
7
+ *
8
+ * For Bun SFE, we MUST find the real claude executable via `which`.
9
+ * For Node.js, we can let the SDK handle it (return empty object).
10
+ */
11
+ export declare function claudeExecutablePath(): {
12
+ pathToClaudeCodeExecutable?: string;
13
+ };
1
14
  /**
2
15
  * Performs the Claude Code check and displays warning if needed
3
16
  * @param options - Optional configuration for the check
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Utilities for handling Bun single-file executable (SFE) detection and spawning.
3
+ *
4
+ * BACKGROUND (from Bun docs and GitHub issues):
5
+ * When Bun compiles a script into a single-file executable, it creates a virtual
6
+ * filesystem at /$bunfs/root/ where the embedded code lives. This means:
7
+ *
8
+ * When running as a Bun SFE:
9
+ * - process.execPath = /path/to/omnara (the compiled binary)
10
+ * - process.argv[0] = "bun"
11
+ * - process.argv[1] = "/$bunfs/root/omnara-darwin-arm64" (virtual filesystem path)
12
+ *
13
+ * When running via Node.js or `bun run`:
14
+ * - process.execPath = /usr/bin/node or /path/to/bun
15
+ * - process.argv[0] = same as execPath
16
+ * - process.argv[1] = /path/to/cli.js (the actual script file)
17
+ *
18
+ * KEY INSIGHT (from Jarred Sumner, Bun team):
19
+ * - For the executable path, use `process.execPath`
20
+ * - The /$bunfs/ path is NOT a usable filesystem location
21
+ *
22
+ * DETECTION:
23
+ * Check if process.argv[1] starts with "/$bunfs/" to detect SFE mode.
24
+ *
25
+ * SPAWNING:
26
+ * In SFE mode: spawn(process.execPath, ['daemon', 'run-service'])
27
+ * In script mode: spawn(process.execPath, [process.argv[1], 'daemon', 'run-service'])
28
+ *
29
+ * References:
30
+ * - https://bun.com/docs/bundler/executables
31
+ * - https://github.com/oven-sh/bun/issues/8476
32
+ */
33
+ /**
34
+ * Detects if running as a Bun single-file executable.
35
+ * In Bun SFE, process.argv[1] points to Bun's virtual filesystem (/$bunfs/root/...).
36
+ *
37
+ * This is the official detection method per Bun documentation and GitHub issues.
38
+ */
39
+ export declare function isBunSingleFileExecutable(): boolean;
40
+ /**
41
+ * Gets the arguments needed to spawn a new instance of this CLI.
42
+ * For Bun SFE, returns just the command args.
43
+ * For Node.js, prepends the script path.
44
+ *
45
+ * @param commandArgs - The CLI command and arguments (e.g., ['daemon', 'run-service'])
46
+ * @returns The full args array to pass to spawn(process.execPath, args)
47
+ */
48
+ export declare function getSpawnArgs(commandArgs: string[]): string[];
49
+ /**
50
+ * Gets the executable path for spawning a new instance of this CLI.
51
+ * Always returns process.execPath (works for both Node.js and Bun SFE).
52
+ */
53
+ export declare function getExecutablePath(): string;
@@ -1,5 +1,47 @@
1
+ export declare const IS_PRERELEASE: boolean;
1
2
  /**
2
- * Checks if a newer version is available and logs a message if so.
3
- * This function is non-blocking and will not throw errors.
3
+ * Detects if the CLI was installed via npm (not as a standalone binary).
4
+ */
5
+ export declare function isNpmInstallation(): boolean;
6
+ /**
7
+ * Runs the installation script to install/update the binary version.
8
+ * Returns true if installation succeeded.
9
+ * Passes PRERELEASE=1 env var to install script if in prerelease mode.
10
+ *
11
+ * @param options.silent - If true, suppress output (for daemon background updates)
12
+ */
13
+ export declare function runInstallScript(options?: {
14
+ silent?: boolean;
15
+ }): Promise<boolean>;
16
+ /**
17
+ * Handles npm installation: show warning and migrate to binary.
18
+ */
19
+ export declare function handleNpmInstallation(): Promise<boolean>;
20
+ /**
21
+ * Checks if an update is available.
22
+ * Checks prerelease channel if PRERELEASE=1 env var is set.
23
+ */
24
+ export declare function checkForUpdate(): Promise<{
25
+ available: boolean;
26
+ latestVersion?: string;
27
+ currentVersion: string;
28
+ isPrerelease: boolean;
29
+ }>;
30
+ /**
31
+ * Performs an update by running the install script.
32
+ * Used by both CLI and daemon for updates.
33
+ */
34
+ export declare function performUpdate(options?: {
35
+ silent?: boolean;
36
+ }): Promise<boolean>;
37
+ /**
38
+ * Background update check for the daemon.
39
+ * Checks for updates and triggers install script if available.
40
+ */
41
+ export declare function daemonUpdateCheck(): Promise<boolean>;
42
+ /**
43
+ * Checks for updates and performs auto-update if available.
44
+ * Called when CLI starts.
45
+ * Returns true if an update was installed and the CLI should exit.
4
46
  */
5
47
  export declare function attemptUpdate(): Promise<boolean>;
@@ -0,0 +1,5 @@
1
+ import WebSocket from 'ws';
2
+ /**
3
+ * Safely closes a WebSocket without throwing or emitting unhandled errors.
4
+ */
5
+ export declare function safeCloseWebSocket(ws: WebSocket): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnara",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "description": "Command-line interface for Omnara.",
6
6
  "main": "dist/cli.js",
@@ -11,6 +11,9 @@
11
11
  "scripts": {
12
12
  "build": "npm run build:types && node build.mjs && npm run copy-assets",
13
13
  "build:types": "tsc --emitDeclarationOnly --project tsconfig.json",
14
+ "build:exe": "bun run scripts/build-executable.ts",
15
+ "build:exe:all": "bun run scripts/build-executable.ts --all",
16
+ "build:release": "bun run scripts/build-executable.ts --all && bun run scripts/generate-release-json.ts",
14
17
  "copy-assets": "node scripts/copy-assets.mjs",
15
18
  "preinstall": "node scripts/preinstall.js"
16
19
  },
@@ -31,7 +34,7 @@
31
34
  "@anthropic-ai/claude-agent-sdk": "^0.1.42",
32
35
  "@anthropic-ai/sdk": "^0.67.0",
33
36
  "@modelcontextprotocol/sdk": "^1.20.1",
34
- "@sentry/node": "^10.26.0",
37
+ "@sentry/node": "^10.28.0",
35
38
  "@types/glob": "^8.1.0",
36
39
  "ajv": "^8.17.1",
37
40
  "ajv-formats": "^2.1.1",
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Build script for creating Bun single-file executables.
4
+ *
5
+ * This script uses Bun's built-in compile feature to create standalone
6
+ * executables that include the Bun runtime and all dependencies.
7
+ *
8
+ * Usage:
9
+ * bun run scripts/build-executable.ts # Build for current platform
10
+ * bun run scripts/build-executable.ts --all # Build for all platforms
11
+ * bun run scripts/build-executable.ts --target linux-x64 # Build for specific target
12
+ *
13
+ * Supported targets:
14
+ * - darwin-arm64 (macOS Apple Silicon)
15
+ * - darwin-x64 (macOS Intel)
16
+ * - linux-x64 (Linux x64)
17
+ * - linux-arm64 (Linux ARM64)
18
+ * - windows-x64 (Windows x64)
19
+ */
20
+
21
+ import { spawn } from 'bun';
22
+ import { existsSync, mkdirSync, statSync } from 'node:fs';
23
+ import { dirname, join, resolve } from 'node:path';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ // Get the path to the bun executable (handles cases where bun isn't in PATH)
27
+ const bunPath = process.execPath;
28
+
29
+ const projectRoot = resolve(dirname(import.meta.path), '..');
30
+ const distDir = join(projectRoot, 'dist');
31
+ const exeDir = join(distDir, 'exe');
32
+ const entryPoint = join(projectRoot, 'src', 'cli.ts');
33
+
34
+ // Read package.json for version info
35
+ const pkgJson = JSON.parse(
36
+ readFileSync(join(projectRoot, 'package.json'), 'utf8')
37
+ ) as { version?: string; name?: string; description?: string };
38
+
39
+ const version = pkgJson.version ?? '0.0.0';
40
+ const packageName = pkgJson.name ?? 'omnara';
41
+ const packageDescription = pkgJson.description ?? 'Command-line interface for Omnara.';
42
+
43
+ // Bun compile targets
44
+ type BunTarget =
45
+ | 'bun-darwin-arm64'
46
+ | 'bun-darwin-x64'
47
+ | 'bun-linux-x64'
48
+ | 'bun-linux-arm64'
49
+ | 'bun-linux-x64-baseline'
50
+ | 'bun-linux-arm64-musl'
51
+ | 'bun-linux-x64-musl'
52
+ | 'bun-windows-x64'
53
+ | 'bun-windows-x64-baseline';
54
+
55
+ interface TargetConfig {
56
+ bunTarget: BunTarget;
57
+ outputName: string;
58
+ description: string;
59
+ }
60
+
61
+ const TARGETS: Record<string, TargetConfig> = {
62
+ 'darwin-arm64': {
63
+ bunTarget: 'bun-darwin-arm64',
64
+ outputName: 'omnara-darwin-arm64',
65
+ description: 'macOS Apple Silicon',
66
+ },
67
+ 'darwin-x64': {
68
+ bunTarget: 'bun-darwin-x64',
69
+ outputName: 'omnara-darwin-x64',
70
+ description: 'macOS Intel',
71
+ },
72
+ 'linux-x64': {
73
+ bunTarget: 'bun-linux-x64',
74
+ outputName: 'omnara-linux-x64',
75
+ description: 'Linux x64',
76
+ },
77
+ 'linux-arm64': {
78
+ bunTarget: 'bun-linux-arm64',
79
+ outputName: 'omnara-linux-arm64',
80
+ description: 'Linux ARM64',
81
+ },
82
+ 'linux-x64-musl': {
83
+ bunTarget: 'bun-linux-x64-musl',
84
+ outputName: 'omnara-linux-x64-musl',
85
+ description: 'Linux x64 (musl/Alpine)',
86
+ },
87
+ 'windows-x64': {
88
+ bunTarget: 'bun-windows-x64',
89
+ outputName: 'omnara-windows-x64.exe',
90
+ description: 'Windows x64',
91
+ },
92
+ };
93
+
94
+ // Map current platform to target name
95
+ function getCurrentPlatformTarget(): string {
96
+ const platform = process.platform;
97
+ const arch = process.arch;
98
+
99
+ if (platform === 'darwin' && arch === 'arm64') return 'darwin-arm64';
100
+ if (platform === 'darwin' && arch === 'x64') return 'darwin-x64';
101
+ if (platform === 'linux' && arch === 'x64') return 'linux-x64';
102
+ if (platform === 'linux' && arch === 'arm64') return 'linux-arm64';
103
+ if (platform === 'win32' && arch === 'x64') return 'windows-x64';
104
+
105
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
106
+ }
107
+
108
+ async function buildTarget(targetName: string, config: TargetConfig): Promise<boolean> {
109
+ const outputPath = join(exeDir, config.outputName);
110
+
111
+ console.log(`\nBuilding ${config.description} (${targetName})...`);
112
+ console.log(` Target: ${config.bunTarget}`);
113
+ console.log(` Output: ${outputPath}`);
114
+ console.log(` Version: ${version}`);
115
+
116
+ const args = [
117
+ 'build',
118
+ entryPoint,
119
+ '--compile',
120
+ '--target', config.bunTarget,
121
+ '--outfile', outputPath,
122
+ '--minify',
123
+ '--sourcemap',
124
+ // Define package info constants at compile time (values must be JSON strings)
125
+ '--define', `__PACKAGE_NAME__=${JSON.stringify(packageName)}`,
126
+ '--define', `__PACKAGE_VERSION__=${JSON.stringify(version)}`,
127
+ '--define', `__PACKAGE_DESCRIPTION__=${JSON.stringify(packageDescription)}`,
128
+ ];
129
+
130
+ // Debug: print full command
131
+ console.log(` Command: ${bunPath} ${args.join(' ')}`);
132
+
133
+ const proc = spawn({
134
+ cmd: [bunPath, ...args],
135
+ cwd: projectRoot,
136
+ stdout: 'inherit',
137
+ stderr: 'inherit',
138
+ });
139
+
140
+ const exitCode = await proc.exited;
141
+
142
+ if (exitCode !== 0) {
143
+ console.error(` Build failed with exit code ${exitCode}`);
144
+ return false;
145
+ }
146
+
147
+ // Get file size
148
+ if (existsSync(outputPath)) {
149
+ const stats = statSync(outputPath);
150
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
151
+ console.log(` Size: ${sizeMB} MB`);
152
+ }
153
+
154
+ return true;
155
+ }
156
+
157
+ async function main() {
158
+ const args = process.argv.slice(2);
159
+
160
+ console.log('='.repeat(60));
161
+ console.log(`Omnara CLI Executable Builder (v${version})`);
162
+ console.log('='.repeat(60));
163
+
164
+ // Create output directory
165
+ mkdirSync(exeDir, { recursive: true });
166
+
167
+ let targets: string[] = [];
168
+
169
+ if (args.includes('--all')) {
170
+ // Build all targets
171
+ targets = Object.keys(TARGETS);
172
+ console.log('\nBuilding for all platforms...');
173
+ } else if (args.includes('--target')) {
174
+ // Build specific target
175
+ const targetIndex = args.indexOf('--target');
176
+ const targetName = args[targetIndex + 1];
177
+
178
+ if (!targetName || !TARGETS[targetName]) {
179
+ console.error(`Invalid target: ${targetName}`);
180
+ console.error(`Available targets: ${Object.keys(TARGETS).join(', ')}`);
181
+ process.exit(1);
182
+ }
183
+
184
+ targets = [targetName];
185
+ } else {
186
+ // Build for current platform
187
+ const currentTarget = getCurrentPlatformTarget();
188
+ targets = [currentTarget];
189
+ console.log(`\nBuilding for current platform: ${currentTarget}`);
190
+ }
191
+
192
+ const results: { target: string; success: boolean }[] = [];
193
+
194
+ for (const targetName of targets) {
195
+ const config = TARGETS[targetName];
196
+ if (!config) {
197
+ console.error(`Unknown target: ${targetName}`);
198
+ results.push({ target: targetName, success: false });
199
+ continue;
200
+ }
201
+
202
+ const success = await buildTarget(targetName, config);
203
+ results.push({ target: targetName, success });
204
+ }
205
+
206
+ // Print summary
207
+ console.log('\n' + '='.repeat(60));
208
+ console.log('Build Summary');
209
+ console.log('='.repeat(60));
210
+
211
+ let allSuccess = true;
212
+ for (const result of results) {
213
+ const status = result.success ? 'SUCCESS' : 'FAILED';
214
+ const config = TARGETS[result.target];
215
+ console.log(` ${result.target}: ${status}`);
216
+ if (result.success && config) {
217
+ console.log(` -> ${join(exeDir, config.outputName)}`);
218
+ }
219
+ if (!result.success) allSuccess = false;
220
+ }
221
+
222
+ console.log('');
223
+
224
+ if (!allSuccess) {
225
+ process.exit(1);
226
+ }
227
+
228
+ // Print usage instructions for single target builds
229
+ if (targets.length === 1 && results[0]?.success) {
230
+ const config = TARGETS[targets[0]];
231
+ if (config) {
232
+ const binaryPath = join(exeDir, config.outputName);
233
+ console.log('To test the executable:');
234
+ console.log(` ${binaryPath} --help`);
235
+ console.log(` ${binaryPath} --version`);
236
+ console.log('');
237
+ }
238
+ }
239
+ }
240
+
241
+ main().catch((error) => {
242
+ console.error('Build failed:', error);
243
+ process.exit(1);
244
+ });
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Generates release.json manifest for CLI releases.
4
+ *
5
+ * This script scans the dist/exe directory for built binaries,
6
+ * calculates SHA256 checksums, and generates a release.json file
7
+ * that the auto-update system uses to check for new versions.
8
+ *
9
+ * Usage:
10
+ * bun run scripts/generate-release-json.ts
11
+ * bun run scripts/generate-release-json.ts --version 0.5.0
12
+ * bun run scripts/generate-release-json.ts --base-url https://releases.example.com
13
+ */
14
+
15
+ import { createHash } from 'node:crypto';
16
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { dirname, join, resolve } from 'node:path';
18
+
19
+ const projectRoot = resolve(dirname(import.meta.path), '..');
20
+ const exeDir = join(projectRoot, 'dist', 'exe');
21
+ const pkgJsonPath = join(projectRoot, 'package.json');
22
+
23
+ interface PlatformInfo {
24
+ url: string;
25
+ sha256: string;
26
+ }
27
+
28
+ interface ReleaseInfo {
29
+ version: string;
30
+ released_at: string;
31
+ platforms: Record<string, PlatformInfo>;
32
+ }
33
+
34
+ function parseArgs(): { version?: string; baseUrl: string } {
35
+ const args = process.argv.slice(2);
36
+ let version: string | undefined;
37
+ let baseUrl = process.env.RELEASE_BASE_URL ?? 'https://releases.omnara.com';
38
+
39
+ for (let i = 0; i < args.length; i++) {
40
+ if (args[i] === '--version' && args[i + 1]) {
41
+ version = args[i + 1];
42
+ i++;
43
+ } else if (args[i] === '--base-url' && args[i + 1]) {
44
+ baseUrl = args[i + 1];
45
+ i++;
46
+ }
47
+ }
48
+
49
+ return { version, baseUrl };
50
+ }
51
+
52
+ function getPackageVersion(): string {
53
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as { version?: string };
54
+ return pkgJson.version ?? '0.0.0';
55
+ }
56
+
57
+ function calculateSha256(filePath: string): string {
58
+ const content = readFileSync(filePath);
59
+ return createHash('sha256').update(content).digest('hex');
60
+ }
61
+
62
+ function extractPlatform(filename: string): string {
63
+ // omnara-darwin-arm64 -> darwin-arm64
64
+ // omnara-linux-x64-musl -> linux-x64-musl
65
+ // omnara-windows-x64.exe -> windows-x64
66
+ return filename.replace(/^omnara-/, '').replace(/\.exe$/, '');
67
+ }
68
+
69
+ function main(): void {
70
+ const { version: argVersion, baseUrl } = parseArgs();
71
+ const version = argVersion ?? getPackageVersion();
72
+
73
+ console.log('='.repeat(60));
74
+ console.log('Generating release.json');
75
+ console.log('='.repeat(60));
76
+ console.log(` Version: ${version}`);
77
+ console.log(` Base URL: ${baseUrl}`);
78
+ console.log(` Exe Dir: ${exeDir}`);
79
+ console.log('');
80
+
81
+ if (!existsSync(exeDir)) {
82
+ console.error(`Error: Executable directory not found: ${exeDir}`);
83
+ console.error('Run "bun run scripts/build-executable.ts --all" first.');
84
+ process.exit(1);
85
+ }
86
+
87
+ const files = readdirSync(exeDir).filter((f) => {
88
+ return f.startsWith('omnara-') && !f.endsWith('.sha256') && !f.endsWith('.json');
89
+ });
90
+
91
+ if (files.length === 0) {
92
+ console.error('Error: No executable files found in dist/exe/');
93
+ console.error('Run "bun run scripts/build-executable.ts --all" first.');
94
+ process.exit(1);
95
+ }
96
+
97
+ console.log('Found executables:');
98
+ const platforms: Record<string, PlatformInfo> = {};
99
+
100
+ for (const file of files) {
101
+ const filePath = join(exeDir, file);
102
+ const platform = extractPlatform(file);
103
+ const sha256 = calculateSha256(filePath);
104
+
105
+ platforms[platform] = {
106
+ url: `${baseUrl}/${version}/${file}`,
107
+ sha256,
108
+ };
109
+
110
+ // Also write individual .sha256 file
111
+ const sha256Path = `${filePath}.sha256`;
112
+ writeFileSync(sha256Path, sha256);
113
+
114
+ console.log(` ${platform}:`);
115
+ console.log(` File: ${file}`);
116
+ console.log(` SHA256: ${sha256}`);
117
+ console.log(` URL: ${platforms[platform].url}`);
118
+ }
119
+
120
+ const release: ReleaseInfo = {
121
+ version,
122
+ released_at: new Date().toISOString(),
123
+ platforms,
124
+ };
125
+
126
+ const releasePath = join(exeDir, 'release.json');
127
+ writeFileSync(releasePath, JSON.stringify(release, null, 2) + '\n');
128
+
129
+ console.log('');
130
+ console.log(`Generated: ${releasePath}`);
131
+ console.log('');
132
+ console.log('Release manifest:');
133
+ console.log(JSON.stringify(release, null, 2));
134
+ }
135
+
136
+ main();