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 +449 -270
- 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
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
2020
|
-
|
|
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
|
|
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
|
|
2159
|
+
for await (const message of query4({
|
|
2038
2160
|
prompt: "Respond with one word.",
|
|
2039
|
-
options: {
|
|
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:
|
|
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
|
-
${
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
7748
|
+
chalk2.blue.bold("\nOpen this link in your browser if it did not open automatically:")
|
|
7622
7749
|
);
|
|
7623
|
-
console.log(
|
|
7624
|
-
console.log(
|
|
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 ===
|
|
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 ===
|
|
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
|
|
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 ===
|
|
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 !==
|
|
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 !==
|
|
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 !==
|
|
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 ===
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
9454
|
-
|
|
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
|
-
|
|
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
|
|
9689
|
+
import * as Sentry25 from "@sentry/node";
|
|
9550
9690
|
|
|
9551
9691
|
// src/daemon/run-daemon.ts
|
|
9552
|
-
import * as
|
|
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
|
|
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
|
|
17672
|
-
|
|
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(
|
|
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 = `${
|
|
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
|
-
|
|
17766
|
-
var
|
|
17767
|
-
var
|
|
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
|
|
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
|
-
|
|
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 !==
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|