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.
package/dist/cli.js CHANGED
@@ -21,8 +21,128 @@ import "@anthropic-ai/claude-agent-sdk";
21
21
  import fs from "fs";
22
22
  import * as Sentry from "@sentry/node";
23
23
  import {
24
- query
24
+ query as query2
25
25
  } from "@anthropic-ai/claude-agent-sdk";
26
+
27
+ // src/utils/claude-code-check.ts
28
+ import chalk from "chalk";
29
+ import { query } from "@anthropic-ai/claude-agent-sdk";
30
+ import { execSync } from "child_process";
31
+
32
+ // src/utils/executable.ts
33
+ function isBunSingleFileExecutable() {
34
+ const arg1 = process.argv[1] ?? "";
35
+ return arg1.startsWith("/$bunfs/");
36
+ }
37
+ function getSpawnArgs(commandArgs) {
38
+ if (isBunSingleFileExecutable()) {
39
+ return commandArgs;
40
+ }
41
+ const scriptPath = process.argv[1] ?? "";
42
+ return [scriptPath, ...commandArgs];
43
+ }
44
+ function getExecutablePath() {
45
+ return process.execPath;
46
+ }
47
+
48
+ // src/utils/claude-code-check.ts
49
+ function claudeExecutablePath() {
50
+ if (!isBunSingleFileExecutable()) {
51
+ return {};
52
+ }
53
+ try {
54
+ const command = process.platform === "win32" ? "where claude" : "which claude";
55
+ const result = execSync(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
56
+ const foundPath = result.trim().split("\n")[0];
57
+ if (foundPath) {
58
+ return { pathToClaudeCodeExecutable: foundPath };
59
+ }
60
+ } catch {
61
+ }
62
+ console.error(
63
+ "[Omnara] Claude Code executable not found in PATH. Please ensure Claude Code is installed: https://claude.ai/download"
64
+ );
65
+ return {};
66
+ }
67
+ async function performClaudeCodeCheck() {
68
+ if (process.env.OMNARA_SKIP_CLAUDE_CHECK === "true") {
69
+ return true;
70
+ }
71
+ const indicator = createClaudeCheckIndicator();
72
+ try {
73
+ indicator?.start();
74
+ for await (const message of query({
75
+ prompt: "Respond with one word.",
76
+ options: {
77
+ maxTurns: 1,
78
+ model: "haiku",
79
+ settingSources: ["user", "project", "local"],
80
+ // Required for Bun single-file executable compatibility
81
+ ...claudeExecutablePath()
82
+ }
83
+ })) {
84
+ void message;
85
+ }
86
+ indicator?.stop("ok");
87
+ return true;
88
+ } catch {
89
+ indicator?.stop("fail");
90
+ console.log(
91
+ chalk.red(
92
+ "\nError: Omnara couldn't run because it relies on Claude Code, which isn't installed or authenticated. Try running `claude /login`.\n"
93
+ )
94
+ );
95
+ return false;
96
+ }
97
+ }
98
+ function createClaudeCheckIndicator() {
99
+ if (!process.stdout.isTTY) {
100
+ return {
101
+ start() {
102
+ console.log("Checking Claude Code auth...");
103
+ },
104
+ stop(status) {
105
+ if (status !== "ok") {
106
+ console.log("Claude Code auth failed.");
107
+ }
108
+ }
109
+ };
110
+ }
111
+ const label = "Checking Claude Code auth";
112
+ const frames = ["-", "\\", "|", "/"];
113
+ let index = 0;
114
+ let interval = null;
115
+ let running = false;
116
+ const stopLine = (status) => {
117
+ if (!running) {
118
+ return;
119
+ }
120
+ running = false;
121
+ if (interval) {
122
+ clearInterval(interval);
123
+ }
124
+ const statusText = status === "ok" ? chalk.green("OK") : chalk.red("FAILED");
125
+ const padding = " ";
126
+ process.stdout.write(`\r${label} ${statusText}${padding}
127
+ `);
128
+ };
129
+ return {
130
+ start() {
131
+ if (running) {
132
+ return;
133
+ }
134
+ running = true;
135
+ process.stdout.write(`${label} ...`);
136
+ interval = setInterval(() => {
137
+ process.stdout.write(`\r${label} ${frames[index]} `);
138
+ index = (index + 1) % frames.length;
139
+ }, 120);
140
+ },
141
+ stop: stopLine
142
+ };
143
+ }
144
+
145
+ // src/orchestrator/ai-client.ts
26
146
  var MODEL_MAP = {
27
147
  haiku: "claude-haiku-4-5-20251001",
28
148
  sonnet: "claude-sonnet-4-5-20250929",
@@ -68,7 +188,9 @@ var AIClient = class {
68
188
  cwd: process.cwd(),
69
189
  includePartialMessages: false,
70
190
  allowedTools: [...BASE_ALLOWED_ORCHESTRATOR_TOOLS],
71
- settingSources: ["user", "project", "local"]
191
+ settingSources: ["user", "project", "local"],
192
+ // Required for Bun single-file executable compatibility
193
+ ...claudeExecutablePath()
72
194
  };
73
195
  this.baseOptions = {
74
196
  ...defaultOptions,
@@ -93,7 +215,7 @@ var AIClient = class {
93
215
  ...overrides
94
216
  };
95
217
  this.ensureWorkingDirectory(merged.cwd);
96
- return query({
218
+ return query2({
97
219
  prompt: prompt2,
98
220
  options: merged
99
221
  });
@@ -156,7 +278,7 @@ import { simpleGit as simpleGit2 } from "simple-git";
156
278
 
157
279
  // src/orchestrator/session.ts
158
280
  import {
159
- query as query2
281
+ query as query3
160
282
  } from "@anthropic-ai/claude-agent-sdk";
161
283
  import { setTimeout as delay } from "node:timers/promises";
162
284
 
@@ -868,7 +990,7 @@ var Session = class {
868
990
  }
869
991
  const summarizationPrompt = `<assistant-output>${combined}<assistant-output>`;
870
992
  let summary = "";
871
- for await (const message of query2({
993
+ for await (const message of query3({
872
994
  prompt: summarizationPrompt,
873
995
  options: {
874
996
  systemPrompt: `Provide a 3-5 sentence summary of the actions taken by the assistant.
@@ -878,7 +1000,8 @@ ONLY include content like key takeaways, actions (do NOT specify by whom), futur
878
1000
  This content should be sufficient to be inserted into something like a commit message or pull request description.`,
879
1001
  maxTurns: 1,
880
1002
  model: "haiku",
881
- allowedTools: []
1003
+ allowedTools: [],
1004
+ ...claudeExecutablePath()
882
1005
  }
883
1006
  })) {
884
1007
  if (message.type === "assistant") {
@@ -941,7 +1064,7 @@ This content should be sufficient to be inserted into something like a commit me
941
1064
  }
942
1065
  const prompt2 = this.buildCommitMessagePrompt(conversationContext, diff);
943
1066
  let commitMessage = "";
944
- for await (const message of query2({
1067
+ for await (const message of query3({
945
1068
  prompt: prompt2,
946
1069
  options: {
947
1070
  systemPrompt: `You generate clear, concise git commit messages based on conversation context and code changes.
@@ -956,7 +1079,8 @@ Guidelines:
956
1079
  Return ONLY the commit message, no explanation or markdown formatting.`,
957
1080
  maxTurns: 1,
958
1081
  model: "haiku",
959
- allowedTools: []
1082
+ allowedTools: [],
1083
+ ...claudeExecutablePath()
960
1084
  }
961
1085
  })) {
962
1086
  if (message.type === "assistant") {
@@ -2016,13 +2140,11 @@ Orchestrator: [message_worker: task="refactor database", message="User wants to
2016
2140
  });
2017
2141
  }
2018
2142
 
2019
- // src/orchestrator/orchestrator-prompt.ts
2020
- import { readFileSync } from "fs";
2021
- var orchestratorPromptUrl = new URL("./orchestrator-prompt.md", import.meta.url);
2022
- var ORCHESTRATOR_SYSTEM_PROMPT = readFileSync(orchestratorPromptUrl, "utf-8");
2143
+ // src/orchestrator/orchestrator-prompt.md
2144
+ var orchestrator_prompt_default = '# Role: Task Orchestrator\n\nYou are Omnara, an orchestrator agent. Your job is to coordinate work by delegating to worker agents while staying responsive to user interaction.\n\n## The Mental Model\n\n**You stay zoomed out. Workers go zoomed in.**\n\nYou are a coordinator who maintains the big picture. Workers are specialists who dive deep into specific domains. When a worker investigates authentication, they accumulate valuable context about the auth system. That context is an asset - reuse it.\n\n**Context is valuable. Accuracy over speed.**\n\nA worker who already understands a domain will do better work than a fresh worker starting from zero. Always prefer routing work to workers with relevant context over launching new workers.\n\n---\n\n## Tool Restrictions\n\n**FORBIDDEN tools (delegate these to workers):**\n- Write, Edit, Bash, NotebookEdit\n\n**Allowed tools (read-only, use sparingly):**\n- Read, Grep, Glob - For quick lookups (1-2 calls max)\n- WebFetch, WebSearch - For external information\n- TodoWrite - For tracking multi-step tasks\n\n**Why?** Using forbidden tools blocks your context. Workers execute operations without blocking you. Your job is to coordinate, not implement.\n\n---\n\n## The Routing Decision\n\nWhen a user request comes in, follow this decision tree:\n\n```\nNew request arrives\n |\n v\nIs there a completed worker whose domain matches this request?\n |\n YES --> Route to that worker (message_worker)\n NO --> Is there a running worker whose domain matches?\n |\n YES --> Queue message to that worker (message_worker, interrupt=false)\n NO --> Launch a new worker\n```\n\n**How to identify matching domains:** Check the worker-status in your system prompt and [Automated Worker Summary] messages. They show task names, status, worktree, and completion summaries.\n\n### When to Route to Existing Workers\n\n**Workers evolve over time.** A worker\'s task name reflects what they started with, but they accumulate context as they receive follow-up messages. They may handle related work that wasn\'t part of their original task but shares context. When deciding whether to route to an existing worker, don\'t just match on task name - consider whether the worker has touched relevant files, worked in the same domain, or has context that would be useful even if the new request isn\'t exactly what they originally did.\n\nRoute to an existing worker when:\n- The request involves files/code that worker has touched\n- The request is in the same feature area\n- The request builds on or modifies previous work\n- The user is asking about work a worker did\n- Understanding prior decisions would be valuable\n- **A bug or question comes up related to work a worker previously did** - Message that worker to investigate/fix it instead of investigating yourself or asking the user for direction. The worker has the context and will be more effective.\n\n### When to Launch New Workers\n\nLaunch a new worker only when:\n- No existing worker has relevant context\n- The request is in a completely unrelated domain\n- The user explicitly asks for a separate task\n- Work would conflict with an existing worker\'s activity\n\n### Routing Examples\n\n```\nWorker "auth-impl" previously: Implemented login/logout\n\nUser: "Add password reset"\n--> Message "auth-impl" (same domain, they know the auth system)\n\nUser: "Add dark mode to settings"\n--> Launch new worker (unrelated domain)\n\nUser: "Why did you use JWT instead of sessions?"\n--> Message "auth-impl" (they made that decision)\n```\n\n---\n\n## Using message_worker\n\n**interrupt=false (default):** Queue message for after current work\n- Use for: Follow-up tasks, additional requirements, questions\n\n**interrupt=true:** Stop current work immediately\n- Use only when: User explicitly says stop, or new info contradicts current direction\n\n**Message content should include:**\n- What the user is asking for\n- How it relates to the worker\'s previous work (if not obvious)\n- Any new context from the user\n\n**After messaging:** Don\'t poll with get_workers. You\'ll be automatically notified when the worker completes.\n\n---\n\n## Launching Workers\n\nWhen you do need to launch new workers:\n\n**Provide clear instructions:**\n- What to do\n- What information to return\n- Any relevant context you gathered\n\n**Use worktree_name for isolation:**\n- `worktree_name="feature-x"` creates an isolated git branch\n- Multiple workers with same worktree_name share that branch\n- Different worktree_names = fully isolated work\n- Omit for trivial changes (typos, docs)\n\n**Example delegation:**\n```\nUser: "Add rate limiting to the API"\n\nlaunch_worker with prompt: "Add rate limiting middleware to the API.\nApply globally with 100 requests per 15 minutes.\nDocument the configuration in comments."\nworktree_name: "add-rate-limiting"\n```\n\n---\n\n## Multiple Workers\n\n**Parallel workers are appropriate when:**\n- Tasks are in completely different domains\n- Tasks don\'t depend on each other\'s results\n- Example: "Fix auth bug AND add logging feature" - 2 workers, different worktrees\n\n**Sequential work should reuse the same worker:**\n- "Research auth libraries, then implement" - One worker does both\n- "Implement feature, then add tests" - Same worker continues\n\n**Avoid:** Launching parallel workers for tightly related work. The workers won\'t share context and may make inconsistent decisions.\n\n---\n\n## Worktree Patterns\n\n**Same worktree, same worker (preferred for sequential work):**\n```\nlaunch_worker(worktree_name="feature-x", ...)\n[worker completes]\nmessage_worker(task="feature-x", message="Now do the next step...")\n```\nContext preserved. Worker knows the domain.\n\n**Different worktrees (for unrelated parallel work):**\n```\nlaunch_worker(worktree_name="feature-x", ...)\nlaunch_worker(worktree_name="feature-y", ...)\n```\nFully isolated. Different features/branches.\n\n**Same worktree, different workers (rare - only when you want fresh perspective):**\n```\nlaunch_worker(worktree_name="feature-x", ...)\n[worker completes]\nlaunch_worker(worktree_name="feature-x", ...) # New worker, context discarded\n```\nUse sparingly. Loses accumulated knowledge.\n\n---\n\n## TodoWrite\n\nUse for multi-step tasks (2+ sequential steps) to give users visibility.\n\n**Todo states:** pending, in_progress, completed\n**Keep exactly ONE todo in_progress at a time.**\n\n```\nUser: "Add OAuth, then test it"\n\nTodoWrite:\n1. "Implement OAuth" - in_progress\n2. "Test OAuth flow" - pending\n\n[Worker completes step 1]\nUpdate: step 1 completed, step 2 in_progress\n```\n\nDon\'t use TodoWrite for single tasks or simple lookups.\n\n---\n\n## Responding to Worker Completions\n\nWhen a worker completes, you receive an [Automated Worker Summary] message containing what the worker accomplished.\n\n**Your response should:**\n1. Report ONLY the new completion (not previously reported ones)\n2. Keep it brief: "The auth worker completed. Login/logout endpoints are implemented."\n3. Note next steps if applicable\n\n**Don\'t:** Re-summarize all workers. Re-explain previous completions. Provide comprehensive recaps unless asked.\n\n---\n\n## Remember\n\n**Your role:** Coordinator, not implementer. Stay zoomed out.\n\n**Tool restrictions:** Never use Write, Edit, Bash, NotebookEdit directly. Always delegate.\n\n**Routing priority:** Existing workers with context > New workers. Context is valuable.\n\n**Accuracy over speed:** One worker with good context beats two workers duplicating effort.\n\n**Stay responsive:** Delegate to keep your context unblocked for user interaction.\n\n**Worker status:** Check worker-status in your system prompt and [Automated Worker Summary] messages. Don\'t poll with get_workers.\n\n**When in doubt:** Route to an existing worker if one has relevant context. Otherwise, delegate to a new worker.\n';
2023
2145
 
2024
2146
  // src/orchestrator/utils/completions-manager.ts
2025
- import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
2147
+ import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
2026
2148
  import { glob } from "glob";
2027
2149
  import * as path6 from "path";
2028
2150
  import * as Sentry4 from "@sentry/node";
@@ -2034,9 +2156,14 @@ var CompletionsManager = class {
2034
2156
  return this.slashCommandsCache.slashCommands;
2035
2157
  }
2036
2158
  let commands = [];
2037
- for await (const message of query3({
2159
+ for await (const message of query4({
2038
2160
  prompt: "Respond with one word.",
2039
- options: { maxTurns: 1, model: "haiku", settingSources: ["user", "project", "local"] }
2161
+ options: {
2162
+ maxTurns: 1,
2163
+ model: "haiku",
2164
+ settingSources: ["user", "project", "local"],
2165
+ ...claudeExecutablePath()
2166
+ }
2040
2167
  })) {
2041
2168
  if (message.type === "system" && message.subtype === "init") {
2042
2169
  console.log("Fetched available slash commands:", message.slash_commands);
@@ -2269,7 +2396,7 @@ var AgentOrchestrator = class {
2269
2396
  mcpServers: {
2270
2397
  worker: this.workerMcpServer
2271
2398
  },
2272
- systemPrompt: ORCHESTRATOR_SYSTEM_PROMPT
2399
+ systemPrompt: orchestrator_prompt_default
2273
2400
  }
2274
2401
  }));
2275
2402
  this.aiClient = mainClientFactory();
@@ -3022,7 +3149,7 @@ var AgentOrchestrator = class {
3022
3149
  const statusSnippet = this.buildCurrentWorkerStatus();
3023
3150
  return `${statusSnippet}
3024
3151
 
3025
- ${ORCHESTRATOR_SYSTEM_PROMPT}`;
3152
+ ${orchestrator_prompt_default}`;
3026
3153
  }
3027
3154
  buildCurrentWorkerStatus() {
3028
3155
  const workers = this.workerManager.listWorkers();
@@ -3090,7 +3217,7 @@ function extractSummary(complete) {
3090
3217
  // src/relay/relay-bridge.ts
3091
3218
  import { randomUUID as randomUUID5 } from "crypto";
3092
3219
  import EventEmitter from "events";
3093
- import WebSocket from "ws";
3220
+ import WebSocket2 from "ws";
3094
3221
  import { simpleGit as simpleGit3 } from "simple-git";
3095
3222
  import * as Sentry11 from "@sentry/node";
3096
3223
 
@@ -7487,7 +7614,7 @@ function tryOpenBrowser(url) {
7487
7614
  }
7488
7615
 
7489
7616
  // src/auth/login-flow.ts
7490
- import chalk from "chalk";
7617
+ import chalk2 from "chalk";
7491
7618
  async function performVerificationLogin({
7492
7619
  backendUrlOption,
7493
7620
  frontendUrlOption,
@@ -7496,7 +7623,7 @@ async function performVerificationLogin({
7496
7623
  const backendUrl = resolveBackendUrl(backendUrlOption);
7497
7624
  const frontendUrl = resolveFrontendUrl(frontendUrlOption);
7498
7625
  const client = new ApiClient({ baseUrl: backendUrl });
7499
- console.log(chalk.yellow("Authenticating CLI\u2026"));
7626
+ console.log(chalk2.yellow("Authenticating CLI\u2026"));
7500
7627
  const loopback = await LoopbackListener.create();
7501
7628
  const pkce = loopback ? generatePkcePair() : null;
7502
7629
  const sessionOptions = {
@@ -7529,7 +7656,7 @@ async function performVerificationLogin({
7529
7656
  await store.save(updated);
7530
7657
  latestCredentials = updated;
7531
7658
  if (!successLogged) {
7532
- console.log(chalk.green("\n\nLogin successful. Credentials saved."));
7659
+ console.log(chalk2.green("\n\nLogin successful. Credentials saved."));
7533
7660
  successLogged = true;
7534
7661
  }
7535
7662
  completed = true;
@@ -7545,7 +7672,7 @@ async function performVerificationLogin({
7545
7672
  }
7546
7673
  while (!completed) {
7547
7674
  const input2 = await prompt(
7548
- chalk.gray("\nInput fallback code if automatic login doesn't complete: ")
7675
+ chalk2.gray("\nInput fallback code if automatic login doesn't complete: ")
7549
7676
  );
7550
7677
  const cleaned = input2.trim();
7551
7678
  if (completed) {
@@ -7618,10 +7745,10 @@ function reportLoopbackError(error) {
7618
7745
  }
7619
7746
  function printSessionInstructions(session) {
7620
7747
  console.log(
7621
- chalk.blue.bold("\nOpen this link in your browser if it did not open automatically:")
7748
+ chalk2.blue.bold("\nOpen this link in your browser if it did not open automatically:")
7622
7749
  );
7623
- console.log(chalk.blueBright.underline(`${session.verificationUriComplete}`));
7624
- console.log(chalk.blue("\nApprove the request, then return to the CLI."));
7750
+ console.log(chalk2.blueBright.underline(`${session.verificationUriComplete}`));
7751
+ console.log(chalk2.blue("\nApprove the request, then return to the CLI."));
7625
7752
  }
7626
7753
  function extractErrorMessage4(error) {
7627
7754
  if (error instanceof Error && error.message) {
@@ -7729,6 +7856,27 @@ async function ensureUserSession(client, accessToken, options) {
7729
7856
  );
7730
7857
  }
7731
7858
 
7859
+ // src/utils/websocket.ts
7860
+ import WebSocket from "ws";
7861
+ function safeCloseWebSocket(ws) {
7862
+ try {
7863
+ ws.once("error", () => {
7864
+ });
7865
+ if (ws.readyState === WebSocket.CONNECTING) {
7866
+ ws.terminate();
7867
+ } else if (ws.readyState === WebSocket.OPEN) {
7868
+ ws.close();
7869
+ }
7870
+ } catch {
7871
+ }
7872
+ setImmediate(() => {
7873
+ try {
7874
+ ws.removeAllListeners();
7875
+ } catch {
7876
+ }
7877
+ });
7878
+ }
7879
+
7732
7880
  // src/relay/relay-bridge.ts
7733
7881
  var MAX_QUEUE_SIZE = 500;
7734
7882
  var LOGIN_WAIT_DELAY_MS = 3e4;
@@ -7773,7 +7921,7 @@ var RelayBridge = class extends EventEmitter {
7773
7921
  throw new Error("Relay bridge already started");
7774
7922
  }
7775
7923
  this.connectionHandle = this.orchestrator.addClient({
7776
- canSend: () => !this.canceled && this.ws?.readyState === WebSocket.OPEN,
7924
+ canSend: () => !this.canceled && this.ws?.readyState === WebSocket2.OPEN,
7777
7925
  sendEnvelope: async (message) => {
7778
7926
  this.sendFrame(message);
7779
7927
  },
@@ -7830,7 +7978,7 @@ var RelayBridge = class extends EventEmitter {
7830
7978
  this.connectionHandle.disconnect();
7831
7979
  this.connectionHandle = null;
7832
7980
  }
7833
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
7981
+ if (this.ws && this.ws.readyState === WebSocket2.OPEN) {
7834
7982
  this.ws.close(1e3, "CLI shutting down");
7835
7983
  }
7836
7984
  this.ws = null;
@@ -7847,7 +7995,7 @@ var RelayBridge = class extends EventEmitter {
7847
7995
  this.userSessionId = registration.userSessionId;
7848
7996
  const agentSessionId = this.ensureAgentSessionExists();
7849
7997
  const url = buildRelayWebsocketUrl(this.backendUrl, registration.relayWsPath);
7850
- const ws = new WebSocket(url, {
7998
+ const ws = new WebSocket2(url, {
7851
7999
  headers: {
7852
8000
  "x-relay-token": registration.relayToken
7853
8001
  }
@@ -7932,7 +8080,7 @@ var RelayBridge = class extends EventEmitter {
7932
8080
  }
7933
8081
  const serialized = JSON.stringify(frame);
7934
8082
  const target = socket ?? this.ws;
7935
- if (target && target.readyState === WebSocket.OPEN) {
8083
+ if (target && target.readyState === WebSocket2.OPEN) {
7936
8084
  try {
7937
8085
  target.send(serialized);
7938
8086
  return;
@@ -8327,7 +8475,7 @@ var RelayBridge = class extends EventEmitter {
8327
8475
  };
8328
8476
  }
8329
8477
  const socket = this.ws;
8330
- if (!socket || socket.readyState !== WebSocket.OPEN) {
8478
+ if (!socket || socket.readyState !== WebSocket2.OPEN) {
8331
8479
  return;
8332
8480
  }
8333
8481
  const frame = {
@@ -8398,7 +8546,7 @@ var RelayBridge = class extends EventEmitter {
8398
8546
  this.sendFrame(frame);
8399
8547
  }
8400
8548
  flushQueue() {
8401
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
8549
+ if (!this.ws || this.ws.readyState !== WebSocket2.OPEN) {
8402
8550
  return;
8403
8551
  }
8404
8552
  while (this.pendingOutbound.length > 0) {
@@ -8424,7 +8572,7 @@ var RelayBridge = class extends EventEmitter {
8424
8572
  const interval = Math.max(this.heartbeatIntervalMs, 5e3);
8425
8573
  this.pingTimer = setInterval(() => {
8426
8574
  const socket = this.ws;
8427
- if (!socket || socket.readyState !== WebSocket.OPEN) {
8575
+ if (!socket || socket.readyState !== WebSocket2.OPEN) {
8428
8576
  return;
8429
8577
  }
8430
8578
  const frame = {
@@ -8442,7 +8590,7 @@ var RelayBridge = class extends EventEmitter {
8442
8590
  }
8443
8591
  }
8444
8592
  closeCurrentSocket() {
8445
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
8593
+ if (this.ws && this.ws.readyState === WebSocket2.OPEN) {
8446
8594
  this.ws.close(1012, "Reconnecting");
8447
8595
  }
8448
8596
  }
@@ -8495,14 +8643,7 @@ function waitForOpen(ws) {
8495
8643
  const settle = (error) => {
8496
8644
  cleanup();
8497
8645
  if (error) {
8498
- try {
8499
- if (ws.readyState === WebSocket.CONNECTING) {
8500
- ws.terminate();
8501
- } else if (ws.readyState === WebSocket.OPEN) {
8502
- ws.close();
8503
- }
8504
- } catch {
8505
- }
8646
+ safeCloseWebSocket(ws);
8506
8647
  reject(error);
8507
8648
  } else {
8508
8649
  resolve();
@@ -9063,7 +9204,7 @@ function extractErrorMessage8(error) {
9063
9204
  }
9064
9205
 
9065
9206
  // src/daemon/daemon-launcher.ts
9066
- import chalk2 from "chalk";
9207
+ import chalk3 from "chalk";
9067
9208
 
9068
9209
  // src/daemon/session-pid-registry.ts
9069
9210
  import fs10 from "node:fs/promises";
@@ -9257,7 +9398,7 @@ async function ensureMachineNameConfigured(configStore, logger3) {
9257
9398
  const defaultName = os4.hostname();
9258
9399
  let machineName = defaultName;
9259
9400
  if (process.stdin.isTTY && process.stdout.isTTY) {
9260
- const answer = (await prompt(chalk2.blue(`Enter a name for this machine [${defaultName}]: `))).trim();
9401
+ const answer = (await prompt(chalk3.blue(`Enter a name for this machine [${defaultName}]: `))).trim();
9261
9402
  if (answer) {
9262
9403
  machineName = answer;
9263
9404
  }
@@ -9450,12 +9591,11 @@ async function registerDirectory(directoryPath, options) {
9450
9591
  await registry.ensureDirectory(normalized, { label: options.label, updateLastUsed: true });
9451
9592
  }
9452
9593
  async function spawnDaemonProcess(options, logger3) {
9453
- const entry = process.argv[1];
9454
- if (!entry) {
9455
- throw new Error("Unable to determine CLI entry path for daemon start");
9456
- }
9594
+ const args = getSpawnArgs(["daemon", "run-service"]);
9595
+ const execPath = getExecutablePath();
9457
9596
  logger3.info("Starting Omnara daemon...");
9458
- const child = spawn2(process.execPath, [entry, "daemon", "run-service"], {
9597
+ logger3.debug(`Spawning daemon: ${execPath} ${args.join(" ")}`);
9598
+ const child = spawn2(execPath, args, {
9459
9599
  detached: true,
9460
9600
  stdio: "ignore",
9461
9601
  env: {
@@ -9546,10 +9686,10 @@ function extractErrorMessage10(error) {
9546
9686
 
9547
9687
  // src/commands/daemon.ts
9548
9688
  import { Command as Command2 } from "commander";
9549
- import * as Sentry24 from "@sentry/node";
9689
+ import * as Sentry25 from "@sentry/node";
9550
9690
 
9551
9691
  // src/daemon/run-daemon.ts
9552
- import * as Sentry23 from "@sentry/node";
9692
+ import * as Sentry24 from "@sentry/node";
9553
9693
 
9554
9694
  // src/daemon/daemon-service.ts
9555
9695
  import * as Sentry22 from "@sentry/node";
@@ -9769,7 +9909,7 @@ function extractErrorMessage11(error) {
9769
9909
  import { randomUUID as randomUUID7 } from "node:crypto";
9770
9910
  import os5 from "node:os";
9771
9911
  import { setTimeout as delay4 } from "node:timers/promises";
9772
- import WebSocket2 from "ws";
9912
+ import WebSocket3 from "ws";
9773
9913
  import * as Sentry21 from "@sentry/node";
9774
9914
 
9775
9915
  // ../shared-js/dist/utils/eff_wordlist.json
@@ -17651,10 +17791,6 @@ var SessionRunner = class {
17651
17791
  if (this.child) {
17652
17792
  throw new Error("Session runner already started");
17653
17793
  }
17654
- const entry = process.argv[1];
17655
- if (!entry) {
17656
- throw new Error("Unable to determine CLI entry path for session runner");
17657
- }
17658
17794
  const sessionLogPath = await createSessionLogPath(this.spec.workspaceId, this.spec.launchId);
17659
17795
  this.logger.info(`Session ${this.spec.launchId} logs available at ${sessionLogPath}`);
17660
17796
  await withSessionRegistry(async (registry) => {
@@ -17668,8 +17804,8 @@ var SessionRunner = class {
17668
17804
  }
17669
17805
  return [registry, null];
17670
17806
  });
17671
- const args = [
17672
- entry,
17807
+ const execPath = getExecutablePath();
17808
+ const args = getSpawnArgs([
17673
17809
  "daemon",
17674
17810
  "run-session",
17675
17811
  "--user-session-id",
@@ -17678,7 +17814,8 @@ var SessionRunner = class {
17678
17814
  this.spec.workspaceId,
17679
17815
  "--directory",
17680
17816
  this.directory.path
17681
- ];
17817
+ ]);
17818
+ this.logger.debug(`Spawning session: ${execPath} ${args.join(" ")}`);
17682
17819
  if (this.spec.worktreeName) {
17683
17820
  args.push("--worktree-name", this.spec.worktreeName);
17684
17821
  }
@@ -17703,7 +17840,7 @@ var SessionRunner = class {
17703
17840
  let logFd = null;
17704
17841
  try {
17705
17842
  logFd = fs11.openSync(sessionLogPath, "a");
17706
- const child = spawn3(process.execPath, args, {
17843
+ const child = spawn3(execPath, args, {
17707
17844
  cwd: this.directory.path,
17708
17845
  stdio: ["ignore", logFd, logFd],
17709
17846
  env,
@@ -17722,7 +17859,7 @@ var SessionRunner = class {
17722
17859
  this.child = null;
17723
17860
  });
17724
17861
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
17725
- const command = `${process.execPath} ${args.join(" ")}`;
17862
+ const command = `${execPath} ${args.join(" ")}`;
17726
17863
  await withSessionRegistry(async (registry) => {
17727
17864
  registry[this.spec.userSessionId] = {
17728
17865
  pid: child.pid ?? -1,
@@ -17762,15 +17899,9 @@ async function createSessionLogPath(workspaceId, launchId) {
17762
17899
  }
17763
17900
 
17764
17901
  // src/package-info.ts
17765
- import { createRequire } from "node:module";
17766
- var require2 = createRequire(import.meta.url);
17767
- var pkg = require2("../package.json");
17768
- var FALLBACK_NAME = pkg.name ?? "omnara";
17769
- var FALLBACK_VERSION = pkg.version ?? "0.0.0";
17770
- var FALLBACK_DESCRIPTION = pkg.description ?? "Command-line interface for Omnara.";
17771
- var PACKAGE_NAME = true ? "omnara" : FALLBACK_NAME;
17772
- var PACKAGE_VERSION = true ? "0.5.2" : FALLBACK_VERSION;
17773
- var PACKAGE_DESCRIPTION = true ? "Command-line interface for Omnara." : FALLBACK_DESCRIPTION;
17902
+ var isBundled = true;
17903
+ var PACKAGE_VERSION = isBundled ? "0.5.3" : "0.0.0-dev";
17904
+ var PACKAGE_DESCRIPTION = isBundled ? "Command-line interface for Omnara." : "Command-line interface for Omnara.";
17774
17905
 
17775
17906
  // src/daemon/machine-supervisor.ts
17776
17907
  var HEARTBEAT_INTERVAL_MS = 1e4;
@@ -17957,7 +18088,7 @@ var MachineSupervisor = class {
17957
18088
  const url = buildMachineWebsocketUrl(this.backendUrl, identity.wsPath);
17958
18089
  this.logger.info(`Connecting to machine websocket at ${url}`);
17959
18090
  await new Promise((resolve, reject) => {
17960
- const ws = new WebSocket2(url, {
18091
+ const ws = new WebSocket3(url, {
17961
18092
  headers: {
17962
18093
  authorization: `Bearer ${identity.token}`
17963
18094
  }
@@ -17974,17 +18105,7 @@ var MachineSupervisor = class {
17974
18105
  }
17975
18106
  this.clearHeartbeatTimer();
17976
18107
  this.websocket = null;
17977
- try {
17978
- if (ws.readyState === WebSocket2.CONNECTING) {
17979
- ws.terminate();
17980
- } else if (ws.readyState === WebSocket2.OPEN) {
17981
- ws.removeAllListeners();
17982
- ws.close();
17983
- } else {
17984
- ws.removeAllListeners();
17985
- }
17986
- } catch {
17987
- }
18108
+ safeCloseWebSocket(ws);
17988
18109
  if (error) {
17989
18110
  reject(error);
17990
18111
  } else {
@@ -18232,7 +18353,7 @@ var MachineSupervisor = class {
18232
18353
  }
18233
18354
  async sendMessage(payload) {
18234
18355
  const ws = this.websocket;
18235
- if (!ws || ws.readyState !== WebSocket2.OPEN) {
18356
+ if (!ws || ws.readyState !== WebSocket3.OPEN) {
18236
18357
  throw new Error("Machine websocket is not connected");
18237
18358
  }
18238
18359
  await new Promise((resolve, reject) => {
@@ -18415,6 +18536,237 @@ var DaemonService = class {
18415
18536
  // src/daemon/run-daemon.ts
18416
18537
  import fs12 from "node:fs";
18417
18538
  import path14 from "node:path";
18539
+
18540
+ // src/utils/version-check.ts
18541
+ import chalk4 from "chalk";
18542
+ import { execSync as execSync2 } from "node:child_process";
18543
+ import { realpathSync } from "node:fs";
18544
+ import { platform, arch } from "node:os";
18545
+ import * as Sentry23 from "@sentry/node";
18546
+ var RELEASE_BASE_URL = process.env.OMNARA_RELEASE_URL ?? "https://releases.omnara.com";
18547
+ var FRONTEND_BASE_URL = process.env.OMNARA_FRONTEND_URL ?? "https://omnara.com";
18548
+ var IS_PRERELEASE = process.env.PRERELEASE === "1";
18549
+ var INSTALL_SCRIPT_URL_UNIX = `${FRONTEND_BASE_URL}/install/install.sh`;
18550
+ var INSTALL_SCRIPT_URL_WINDOWS = `${FRONTEND_BASE_URL}/install/install.ps1`;
18551
+ function detectPlatform() {
18552
+ const os6 = platform();
18553
+ const cpuArch = arch();
18554
+ let platformId;
18555
+ if (os6 === "darwin" && cpuArch === "arm64") {
18556
+ platformId = "darwin-arm64";
18557
+ } else if (os6 === "darwin" && cpuArch === "x64") {
18558
+ platformId = "darwin-x64";
18559
+ } else if (os6 === "linux" && cpuArch === "x64") {
18560
+ platformId = isMusl() ? "linux-x64-musl" : "linux-x64";
18561
+ } else if (os6 === "linux" && cpuArch === "arm64") {
18562
+ platformId = "linux-arm64";
18563
+ } else if (os6 === "win32" && cpuArch === "x64") {
18564
+ platformId = "windows-x64";
18565
+ } else {
18566
+ throw new Error(`Unsupported platform: ${os6}-${cpuArch}`);
18567
+ }
18568
+ return platformId;
18569
+ }
18570
+ function isMusl() {
18571
+ try {
18572
+ const result = execSync2("ldd --version 2>&1 || true", { encoding: "utf8" });
18573
+ return result.toLowerCase().includes("musl");
18574
+ } catch {
18575
+ return false;
18576
+ }
18577
+ }
18578
+ async function fetchLatestRelease() {
18579
+ const controller = new AbortController();
18580
+ const timeout = setTimeout(() => controller.abort(), 5e3);
18581
+ timeout.unref?.();
18582
+ try {
18583
+ const basePath = IS_PRERELEASE ? `${RELEASE_BASE_URL}/prerelease` : RELEASE_BASE_URL;
18584
+ const url = `${basePath}/latest/release.json`;
18585
+ const response = await fetch(url, { signal: controller.signal });
18586
+ if (!response.ok) {
18587
+ return null;
18588
+ }
18589
+ return await response.json();
18590
+ } catch {
18591
+ return null;
18592
+ } finally {
18593
+ clearTimeout(timeout);
18594
+ }
18595
+ }
18596
+ function compareVersions(v1, v2) {
18597
+ const clean1 = v1.replace(/^v/, "");
18598
+ const clean2 = v2.replace(/^v/, "");
18599
+ const parts1 = clean1.split(".").map(Number);
18600
+ const parts2 = clean2.split(".").map(Number);
18601
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
18602
+ const part1 = parts1[i] || 0;
18603
+ const part2 = parts2[i] || 0;
18604
+ if (part1 > part2) return 1;
18605
+ if (part1 < part2) return -1;
18606
+ }
18607
+ return 0;
18608
+ }
18609
+ async function runInstallScript(options) {
18610
+ const os6 = platform();
18611
+ const silent = options?.silent ?? false;
18612
+ const stdio = silent ? "pipe" : "inherit";
18613
+ if (!silent) {
18614
+ if (IS_PRERELEASE) {
18615
+ console.log(chalk4.blue("\nRunning Omnara installation script (prerelease channel)...\n"));
18616
+ } else {
18617
+ console.log(chalk4.blue("\nRunning Omnara installation script...\n"));
18618
+ }
18619
+ }
18620
+ const env = { ...process.env };
18621
+ if (IS_PRERELEASE) {
18622
+ env.PRERELEASE = "1";
18623
+ }
18624
+ try {
18625
+ if (os6 === "win32") {
18626
+ const prereleaseEnv = IS_PRERELEASE ? '$env:PRERELEASE="1"; ' : "";
18627
+ const cmd = `powershell -ExecutionPolicy Bypass -Command "& { ${prereleaseEnv}iwr -useb '${INSTALL_SCRIPT_URL_WINDOWS}' | iex }"`;
18628
+ execSync2(cmd, { stdio, env });
18629
+ } else {
18630
+ const cmd = `curl -fsSL '${INSTALL_SCRIPT_URL_UNIX}' | bash`;
18631
+ execSync2(cmd, { stdio, shell: "/bin/bash", env });
18632
+ }
18633
+ return true;
18634
+ } catch (error) {
18635
+ Sentry23.captureException(error);
18636
+ if (!silent) {
18637
+ console.log(chalk4.red("\nInstallation script failed."));
18638
+ console.log(chalk4.yellow("Please install manually:"));
18639
+ const prereleasePrefix = IS_PRERELEASE ? "PRERELEASE=1 " : "";
18640
+ if (os6 === "win32") {
18641
+ const psPrefix = IS_PRERELEASE ? '$env:PRERELEASE="1"; ' : "";
18642
+ console.log(chalk4.cyan(` ${psPrefix}irm ${INSTALL_SCRIPT_URL_WINDOWS} | iex`));
18643
+ } else {
18644
+ console.log(
18645
+ chalk4.cyan(` ${prereleasePrefix}curl -fsSL ${INSTALL_SCRIPT_URL_UNIX} | bash`)
18646
+ );
18647
+ }
18648
+ }
18649
+ return false;
18650
+ }
18651
+ }
18652
+ async function checkForUpdate() {
18653
+ const currentVersion = PACKAGE_VERSION;
18654
+ if (!currentVersion) {
18655
+ return { available: false, currentVersion: "0.0.0", isPrerelease: IS_PRERELEASE };
18656
+ }
18657
+ try {
18658
+ const release = await fetchLatestRelease();
18659
+ if (!release) {
18660
+ return { available: false, currentVersion, isPrerelease: IS_PRERELEASE };
18661
+ }
18662
+ const platformId = detectPlatform();
18663
+ const hasPlatformBinary = !!release.platforms[platformId];
18664
+ if (!hasPlatformBinary) {
18665
+ return { available: false, currentVersion, isPrerelease: IS_PRERELEASE };
18666
+ }
18667
+ const updateAvailable = compareVersions(release.version, currentVersion) > 0;
18668
+ return {
18669
+ available: updateAvailable,
18670
+ latestVersion: release.version,
18671
+ currentVersion,
18672
+ isPrerelease: IS_PRERELEASE
18673
+ };
18674
+ } catch {
18675
+ return { available: false, currentVersion, isPrerelease: IS_PRERELEASE };
18676
+ }
18677
+ }
18678
+ async function performUpdate(options) {
18679
+ const silent = options?.silent ?? false;
18680
+ try {
18681
+ if (!silent) {
18682
+ const client = new DaemonClient();
18683
+ const status = await client.getStatus().catch(() => null);
18684
+ if (status) {
18685
+ console.log(chalk4.yellow("Stopping daemon for update..."));
18686
+ await client.stop("UPDATING").catch(() => {
18687
+ });
18688
+ }
18689
+ }
18690
+ const success = await runInstallScript({ silent });
18691
+ if (success && !silent) {
18692
+ console.log(chalk4.green("\nUpdate complete!"));
18693
+ console.log(chalk4.white("The daemon will restart automatically.\n"));
18694
+ }
18695
+ return success;
18696
+ } catch (error) {
18697
+ Sentry23.captureException(error);
18698
+ return false;
18699
+ }
18700
+ }
18701
+ async function attemptUpdate() {
18702
+ try {
18703
+ const currentVersion = PACKAGE_VERSION;
18704
+ if (!currentVersion) {
18705
+ return false;
18706
+ }
18707
+ const updateInfo = await checkForUpdate();
18708
+ if (!updateInfo.available) {
18709
+ const channel = IS_PRERELEASE ? " (prerelease channel)" : "";
18710
+ console.log(chalk4.grey(`Omnara is up to date${channel}!
18711
+ `));
18712
+ return false;
18713
+ }
18714
+ const channelLabel = IS_PRERELEASE ? " (prerelease)" : "";
18715
+ console.log(
18716
+ chalk4.yellow(
18717
+ "\u2550".repeat(60) + `
18718
+ Update available for Omnara CLI${channelLabel}
18719
+ Current version: ${updateInfo.currentVersion}
18720
+ Latest version: ${updateInfo.latestVersion}
18721
+ ${"\u2550".repeat(60)}`
18722
+ )
18723
+ );
18724
+ const success = await performUpdate();
18725
+ if (success) {
18726
+ console.log(chalk4.white("Please restart `omnara` to use the new version.\n"));
18727
+ return true;
18728
+ }
18729
+ return false;
18730
+ } catch (e) {
18731
+ Sentry23.captureException(e);
18732
+ }
18733
+ return false;
18734
+ }
18735
+
18736
+ // src/daemon/run-daemon.ts
18737
+ var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
18738
+ function startUpdateChecker(logger3, onUpdateInstalled) {
18739
+ const checkForUpdates = async () => {
18740
+ try {
18741
+ logger3.debug("Checking for updates...");
18742
+ const updateInfo = await checkForUpdate();
18743
+ if (updateInfo.available) {
18744
+ logger3.info(
18745
+ `Update available: ${updateInfo.currentVersion} -> ${updateInfo.latestVersion}`
18746
+ );
18747
+ logger3.info("Starting background update...");
18748
+ const success = await runInstallScript({ silent: true });
18749
+ if (success) {
18750
+ logger3.info("Update installed. Triggering graceful shutdown for restart...");
18751
+ onUpdateInstalled();
18752
+ } else {
18753
+ logger3.warn("Background update failed. Will retry on next check.");
18754
+ }
18755
+ } else {
18756
+ logger3.debug(`No updates available (current: ${updateInfo.currentVersion})`);
18757
+ }
18758
+ } catch (error) {
18759
+ Sentry24.captureException(error);
18760
+ logger3.error("Update check failed:", error);
18761
+ }
18762
+ };
18763
+ setTimeout(() => {
18764
+ void checkForUpdates();
18765
+ }, 1e4);
18766
+ return setInterval(() => {
18767
+ void checkForUpdates();
18768
+ }, UPDATE_CHECK_INTERVAL_MS);
18769
+ }
18418
18770
  async function runDaemonProcess() {
18419
18771
  await ensureOmnaraHome();
18420
18772
  await ensureDirectory(DAEMON_LOG_DIR);
@@ -18466,11 +18818,18 @@ async function runDaemonProcess() {
18466
18818
  machineSupervisor,
18467
18819
  logger: logger3
18468
18820
  });
18821
+ const updateCheckInterval = startUpdateChecker(logger3, () => {
18822
+ logger3.info("Update installed, initiating graceful shutdown...");
18823
+ void service.stop().catch((error) => {
18824
+ Sentry24.captureException(error);
18825
+ logger3.error("Failed to stop daemon after update:", error);
18826
+ });
18827
+ });
18469
18828
  const makeSignalHandler = (signal) => {
18470
18829
  return () => {
18471
18830
  logger3.warn(`Received ${signal}. Stopping daemon...`);
18472
18831
  void service.stop().catch((error) => {
18473
- Sentry23.captureException(error);
18832
+ Sentry24.captureException(error);
18474
18833
  logger3.error("Failed to stop daemon:", error);
18475
18834
  });
18476
18835
  };
@@ -18483,10 +18842,11 @@ async function runDaemonProcess() {
18483
18842
  await service.start();
18484
18843
  await service.waitForShutdown();
18485
18844
  } catch (error) {
18486
- Sentry23.captureException(error);
18845
+ Sentry24.captureException(error);
18487
18846
  logger3.error("Daemon exited with error:", error);
18488
18847
  process.exitCode = 1;
18489
18848
  } finally {
18849
+ clearInterval(updateCheckInterval);
18490
18850
  process.off("SIGINT", sigint);
18491
18851
  process.off("SIGTERM", sigterm);
18492
18852
  await service.stop().catch(() => void 0);
@@ -18543,7 +18903,7 @@ function buildDaemonCommand() {
18543
18903
  try {
18544
18904
  process.chdir(options.directory);
18545
18905
  } catch (error) {
18546
- Sentry24.captureException(error);
18906
+ Sentry25.captureException(error);
18547
18907
  logger.error(
18548
18908
  `Unable to switch to directory ${options.directory}: ${error.message}`
18549
18909
  );
@@ -18562,7 +18922,7 @@ function buildDaemonCommand() {
18562
18922
  worktreeName: options.worktreeName
18563
18923
  });
18564
18924
  } catch (error) {
18565
- Sentry24.captureException(error);
18925
+ Sentry25.captureException(error);
18566
18926
  logger.error("User session exited with error:", error);
18567
18927
  process.exitCode = process.exitCode || 1;
18568
18928
  }
@@ -18594,190 +18954,6 @@ function buildKillCommand() {
18594
18954
  return kill;
18595
18955
  }
18596
18956
 
18597
- // src/utils/version-check.ts
18598
- import chalk3 from "chalk";
18599
- import { execSync } from "child_process";
18600
- import * as Sentry25 from "@sentry/node";
18601
- async function fetchLatestVersion(packageName) {
18602
- const controller = new AbortController();
18603
- const timeout = setTimeout(() => controller.abort(), 3e3);
18604
- timeout.unref?.();
18605
- try {
18606
- const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
18607
- const response = await fetch(url, { signal: controller.signal });
18608
- if (!response.ok) {
18609
- return null;
18610
- }
18611
- const packageInfo = await response.json();
18612
- return packageInfo["dist-tags"]?.latest ?? null;
18613
- } catch {
18614
- return null;
18615
- } finally {
18616
- clearTimeout(timeout);
18617
- }
18618
- }
18619
- function compareVersions(v1, v2) {
18620
- const parts1 = v1.split(".").map(Number);
18621
- const parts2 = v2.split(".").map(Number);
18622
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
18623
- const part1 = parts1[i] || 0;
18624
- const part2 = parts2[i] || 0;
18625
- if (part1 > part2) return 1;
18626
- if (part1 < part2) return -1;
18627
- }
18628
- return 0;
18629
- }
18630
- async function attemptUpdate() {
18631
- try {
18632
- const currentVersion = PACKAGE_VERSION;
18633
- const packageName = PACKAGE_NAME;
18634
- if (!currentVersion || !packageName) {
18635
- return false;
18636
- }
18637
- const latestVersion = await fetchLatestVersion(packageName);
18638
- if (!latestVersion) {
18639
- return false;
18640
- }
18641
- if (compareVersions(latestVersion, currentVersion) > 0) {
18642
- const client = new DaemonClient();
18643
- console.log(
18644
- chalk3.yellow(
18645
- "\u2550".repeat(60),
18646
- `
18647
- Update available for Omnara CLI`,
18648
- `
18649
- Current version: ${currentVersion}`,
18650
- `
18651
- Latest version: ${latestVersion}`,
18652
- `
18653
- ${"\u2550".repeat(60)}`
18654
- )
18655
- );
18656
- const status = await client.getStatus();
18657
- if (status) {
18658
- console.log(chalk3.yellow("\nPausing daemon..."));
18659
- client.stop("UPDATING");
18660
- }
18661
- console.log(
18662
- chalk3.blue(
18663
- "\nAuto-updating Omnara CLI:",
18664
- `
18665
-
18666
- $ npm install -g ${packageName}@latest`,
18667
- `
18668
-
18669
- Installing...
18670
-
18671
- `
18672
- )
18673
- );
18674
- try {
18675
- execSync("npm install -g omnara@latest");
18676
- console.log(chalk3.green("\n\nUpdate complete! Rerun `omnara` to restart session.\n"));
18677
- return true;
18678
- } catch (err) {
18679
- console.log(
18680
- chalk3.red(
18681
- "\nAuto-update failed. Please install manually:",
18682
- `
18683
-
18684
- $ npm install -g ${packageName}@latest`,
18685
- `
18686
-
18687
- If you see EACCES/permission errors, retry with:`,
18688
- `
18689
-
18690
- $ sudo npm install -g ${packageName}@latest`,
18691
- "\n"
18692
- )
18693
- );
18694
- Sentry25.captureException(err);
18695
- }
18696
- } else {
18697
- console.log(chalk3.grey("Omnara is up to date!\n"));
18698
- }
18699
- } catch (e) {
18700
- Sentry25.captureException(e);
18701
- console.log(e);
18702
- }
18703
- return false;
18704
- }
18705
-
18706
- // src/utils/claude-code-check.ts
18707
- import chalk4 from "chalk";
18708
- import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
18709
- async function performClaudeCodeCheck() {
18710
- if (process.env.OMNARA_SKIP_CLAUDE_CHECK === "true") {
18711
- return true;
18712
- }
18713
- const indicator = createClaudeCheckIndicator();
18714
- try {
18715
- indicator?.start();
18716
- for await (const message of query4({
18717
- prompt: "Respond with one word.",
18718
- options: { maxTurns: 1, model: "haiku", settingSources: ["user", "project", "local"] }
18719
- })) {
18720
- void message;
18721
- }
18722
- indicator?.stop("ok");
18723
- return true;
18724
- } catch {
18725
- indicator?.stop("fail");
18726
- console.log(
18727
- chalk4.red(
18728
- "\nError: Omnara couldn't run because it relies on Claude Code, which isn't installed or authenticated. Try running `claude /login`.\n"
18729
- )
18730
- );
18731
- return false;
18732
- }
18733
- }
18734
- function createClaudeCheckIndicator() {
18735
- if (!process.stdout.isTTY) {
18736
- return {
18737
- start() {
18738
- console.log("Checking Claude Code auth...");
18739
- },
18740
- stop(status) {
18741
- if (status !== "ok") {
18742
- console.log("Claude Code auth failed.");
18743
- }
18744
- }
18745
- };
18746
- }
18747
- const label = "Checking Claude Code auth";
18748
- const frames = ["-", "\\", "|", "/"];
18749
- let index = 0;
18750
- let interval = null;
18751
- let running = false;
18752
- const stopLine = (status) => {
18753
- if (!running) {
18754
- return;
18755
- }
18756
- running = false;
18757
- if (interval) {
18758
- clearInterval(interval);
18759
- }
18760
- const statusText = status === "ok" ? chalk4.green("OK") : chalk4.red("FAILED");
18761
- const padding = " ";
18762
- process.stdout.write(`\r${label} ${statusText}${padding}
18763
- `);
18764
- };
18765
- return {
18766
- start() {
18767
- if (running) {
18768
- return;
18769
- }
18770
- running = true;
18771
- process.stdout.write(`${label} ...`);
18772
- interval = setInterval(() => {
18773
- process.stdout.write(`\r${label} ${frames[index]} `);
18774
- index = (index + 1) % frames.length;
18775
- }, 120);
18776
- },
18777
- stop: stopLine
18778
- };
18779
- }
18780
-
18781
18957
  // src/cli.ts
18782
18958
  import chalk5 from "chalk";
18783
18959
  function createProgram() {
@@ -18814,6 +18990,9 @@ async function run(argv) {
18814
18990
  }
18815
18991
  }
18816
18992
  function isMainModule() {
18993
+ if (isBunSingleFileExecutable()) {
18994
+ return true;
18995
+ }
18817
18996
  if (!import.meta.url || !process.argv[1]) {
18818
18997
  return false;
18819
18998
  }