omnara 0.5.2 → 0.6.0
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 +804 -332
- package/dist/cli.js.map +4 -4
- package/dist/daemon/machine-supervisor.d.ts +2 -0
- package/dist/daemon/session-runner.d.ts +10 -0
- package/dist/orchestrator/ai-client.d.ts +1 -0
- package/dist/orchestrator/orchestrator-prompt.d.ts +1 -1
- package/dist/orchestrator/session.d.ts +1 -0
- 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 +40 -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",
|
|
@@ -57,9 +177,11 @@ var BASE_ALLOWED_WORKER_TOOLS = [
|
|
|
57
177
|
"Skill",
|
|
58
178
|
"SlashCommand"
|
|
59
179
|
];
|
|
180
|
+
var INTERRUPT_TIMEOUT_MS = 5e3;
|
|
60
181
|
var AIClient = class {
|
|
61
182
|
baseOptions;
|
|
62
183
|
query;
|
|
184
|
+
abortController;
|
|
63
185
|
currentModel = "sonnet";
|
|
64
186
|
constructor(config) {
|
|
65
187
|
const defaultOptions = {
|
|
@@ -68,7 +190,9 @@ var AIClient = class {
|
|
|
68
190
|
cwd: process.cwd(),
|
|
69
191
|
includePartialMessages: false,
|
|
70
192
|
allowedTools: [...BASE_ALLOWED_ORCHESTRATOR_TOOLS],
|
|
71
|
-
settingSources: ["user", "project", "local"]
|
|
193
|
+
settingSources: ["user", "project", "local"],
|
|
194
|
+
// Required for Bun single-file executable compatibility
|
|
195
|
+
...claudeExecutablePath()
|
|
72
196
|
};
|
|
73
197
|
this.baseOptions = {
|
|
74
198
|
...defaultOptions,
|
|
@@ -80,29 +204,50 @@ var AIClient = class {
|
|
|
80
204
|
this.ensureWorkingDirectory(this.baseOptions.cwd);
|
|
81
205
|
}
|
|
82
206
|
async *streamUserMessage(prompt2, overrides) {
|
|
207
|
+
this.abortController = new AbortController();
|
|
83
208
|
const q = this.createQuery(prompt2, overrides);
|
|
84
209
|
this.query = q;
|
|
85
|
-
|
|
86
|
-
|
|
210
|
+
try {
|
|
211
|
+
for await (const message of q) {
|
|
212
|
+
yield message;
|
|
213
|
+
}
|
|
214
|
+
} finally {
|
|
215
|
+
this.query = void 0;
|
|
216
|
+
this.abortController = void 0;
|
|
87
217
|
}
|
|
88
|
-
this.query = void 0;
|
|
89
218
|
}
|
|
90
219
|
createQuery(prompt2, overrides) {
|
|
91
220
|
const merged = {
|
|
92
221
|
...this.baseOptions,
|
|
93
|
-
...overrides
|
|
222
|
+
...overrides,
|
|
223
|
+
abortController: this.abortController
|
|
94
224
|
};
|
|
95
225
|
this.ensureWorkingDirectory(merged.cwd);
|
|
96
|
-
return
|
|
226
|
+
return query2({
|
|
97
227
|
prompt: prompt2,
|
|
98
228
|
options: merged
|
|
99
229
|
});
|
|
100
230
|
}
|
|
101
231
|
async cancel() {
|
|
102
|
-
if (this.query) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
232
|
+
if (!this.query) {
|
|
233
|
+
console.warn("[AIClient] No query to interrupt. Ignoring");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const interruptPromise = this.query.interrupt();
|
|
238
|
+
const timeoutPromise = new Promise(
|
|
239
|
+
(resolve) => setTimeout(() => resolve("timeout"), INTERRUPT_TIMEOUT_MS)
|
|
240
|
+
);
|
|
241
|
+
const result = await Promise.race([interruptPromise, timeoutPromise]);
|
|
242
|
+
if (result === "timeout") {
|
|
243
|
+
console.warn(
|
|
244
|
+
`[AIClient] interrupt() timed out after ${INTERRUPT_TIMEOUT_MS}ms, falling back to abort`
|
|
245
|
+
);
|
|
246
|
+
this.abortController?.abort();
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.warn("[AIClient] interrupt() failed, falling back to abort:", error);
|
|
250
|
+
this.abortController?.abort();
|
|
106
251
|
}
|
|
107
252
|
}
|
|
108
253
|
isGenerating() {
|
|
@@ -156,7 +301,7 @@ import { simpleGit as simpleGit2 } from "simple-git";
|
|
|
156
301
|
|
|
157
302
|
// src/orchestrator/session.ts
|
|
158
303
|
import {
|
|
159
|
-
query as
|
|
304
|
+
query as query3
|
|
160
305
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
161
306
|
import { setTimeout as delay } from "node:timers/promises";
|
|
162
307
|
|
|
@@ -480,8 +625,22 @@ function createEnvelope(sessionId, content, senderId, metadata, payloadMetadata)
|
|
|
480
625
|
}
|
|
481
626
|
function toMessageEnvelopes(message, sessionId) {
|
|
482
627
|
const envelopes = [];
|
|
628
|
+
const messageAny = message;
|
|
629
|
+
const subagentSessionId = typeof messageAny.session_id === "string" ? messageAny.session_id : void 0;
|
|
630
|
+
const parentToolUseId = typeof messageAny.parent_tool_use_id === "string" ? messageAny.parent_tool_use_id : void 0;
|
|
631
|
+
const buildSubagentPayloadMetadata = () => {
|
|
632
|
+
const metadata = {};
|
|
633
|
+
if (parentToolUseId) {
|
|
634
|
+
metadata.parent_tool_use_id = parentToolUseId;
|
|
635
|
+
}
|
|
636
|
+
if (subagentSessionId && subagentSessionId !== sessionId) {
|
|
637
|
+
metadata.subagent_session_id = subagentSessionId;
|
|
638
|
+
}
|
|
639
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
640
|
+
};
|
|
483
641
|
switch (message.type) {
|
|
484
642
|
case "assistant": {
|
|
643
|
+
const subagentMetadata = buildSubagentPayloadMetadata();
|
|
485
644
|
for (const block of message.message.content) {
|
|
486
645
|
if (isTextBlock(block)) {
|
|
487
646
|
const content = {
|
|
@@ -489,9 +648,15 @@ function toMessageEnvelopes(message, sessionId) {
|
|
|
489
648
|
text: block.text
|
|
490
649
|
};
|
|
491
650
|
envelopes.push(
|
|
492
|
-
createEnvelope(
|
|
493
|
-
|
|
494
|
-
|
|
651
|
+
createEnvelope(
|
|
652
|
+
sessionId,
|
|
653
|
+
content,
|
|
654
|
+
sessionId,
|
|
655
|
+
{
|
|
656
|
+
role: "assistant"
|
|
657
|
+
},
|
|
658
|
+
subagentMetadata
|
|
659
|
+
)
|
|
495
660
|
);
|
|
496
661
|
} else if (isToolUseBlock(block)) {
|
|
497
662
|
const content = {
|
|
@@ -501,15 +666,29 @@ function toMessageEnvelopes(message, sessionId) {
|
|
|
501
666
|
arguments: normalizeToolArguments(block.input)
|
|
502
667
|
};
|
|
503
668
|
envelopes.push(
|
|
504
|
-
createEnvelope(
|
|
505
|
-
|
|
506
|
-
|
|
669
|
+
createEnvelope(
|
|
670
|
+
sessionId,
|
|
671
|
+
content,
|
|
672
|
+
sessionId,
|
|
673
|
+
{
|
|
674
|
+
role: "assistant"
|
|
675
|
+
},
|
|
676
|
+
subagentMetadata
|
|
677
|
+
)
|
|
507
678
|
);
|
|
508
679
|
}
|
|
509
680
|
}
|
|
510
681
|
break;
|
|
511
682
|
}
|
|
512
683
|
case "user": {
|
|
684
|
+
if (parentToolUseId) {
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
const firstBlock = message.message.content[0];
|
|
688
|
+
if (isTextBlock(firstBlock) && (firstBlock.text === "[Request interrupted by user]" || firstBlock.text === "[Request interrupted by user for tool use]")) {
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
const subagentMetadata = buildSubagentPayloadMetadata();
|
|
513
692
|
for (const block of message.message.content) {
|
|
514
693
|
if (isToolResultBlock(block)) {
|
|
515
694
|
const content = {
|
|
@@ -519,9 +698,15 @@ function toMessageEnvelopes(message, sessionId) {
|
|
|
519
698
|
is_error: Boolean(block.is_error)
|
|
520
699
|
};
|
|
521
700
|
envelopes.push(
|
|
522
|
-
createEnvelope(
|
|
523
|
-
|
|
524
|
-
|
|
701
|
+
createEnvelope(
|
|
702
|
+
sessionId,
|
|
703
|
+
content,
|
|
704
|
+
sessionId,
|
|
705
|
+
{
|
|
706
|
+
role: "assistant"
|
|
707
|
+
},
|
|
708
|
+
subagentMetadata
|
|
709
|
+
)
|
|
525
710
|
);
|
|
526
711
|
} else if (isTextBlock(block)) {
|
|
527
712
|
const content = {
|
|
@@ -529,9 +714,15 @@ function toMessageEnvelopes(message, sessionId) {
|
|
|
529
714
|
text: block.text
|
|
530
715
|
};
|
|
531
716
|
envelopes.push(
|
|
532
|
-
createEnvelope(
|
|
533
|
-
|
|
534
|
-
|
|
717
|
+
createEnvelope(
|
|
718
|
+
sessionId,
|
|
719
|
+
content,
|
|
720
|
+
sessionId,
|
|
721
|
+
{
|
|
722
|
+
role: "user"
|
|
723
|
+
},
|
|
724
|
+
subagentMetadata
|
|
725
|
+
)
|
|
535
726
|
);
|
|
536
727
|
}
|
|
537
728
|
}
|
|
@@ -544,6 +735,13 @@ function toMessageEnvelopes(message, sessionId) {
|
|
|
544
735
|
type: "text",
|
|
545
736
|
text: delta
|
|
546
737
|
};
|
|
738
|
+
const streamPayloadMetadata = {
|
|
739
|
+
stream_delta: true
|
|
740
|
+
};
|
|
741
|
+
const subagentMeta = buildSubagentPayloadMetadata();
|
|
742
|
+
if (subagentMeta) {
|
|
743
|
+
Object.assign(streamPayloadMetadata, subagentMeta);
|
|
744
|
+
}
|
|
547
745
|
envelopes.push(
|
|
548
746
|
createEnvelope(
|
|
549
747
|
sessionId,
|
|
@@ -552,9 +750,7 @@ function toMessageEnvelopes(message, sessionId) {
|
|
|
552
750
|
{
|
|
553
751
|
role: "assistant"
|
|
554
752
|
},
|
|
555
|
-
|
|
556
|
-
stream_delta: true
|
|
557
|
-
}
|
|
753
|
+
streamPayloadMetadata
|
|
558
754
|
)
|
|
559
755
|
);
|
|
560
756
|
}
|
|
@@ -661,6 +857,7 @@ var Session = class {
|
|
|
661
857
|
sessionInitSubscribers = [];
|
|
662
858
|
queue = [];
|
|
663
859
|
generating = false;
|
|
860
|
+
waitingForMessage = null;
|
|
664
861
|
announcementPromise = null;
|
|
665
862
|
announced = false;
|
|
666
863
|
shouldSummarize = false;
|
|
@@ -682,19 +879,29 @@ var Session = class {
|
|
|
682
879
|
sessionId: this.id,
|
|
683
880
|
queueLength: this.queue.length
|
|
684
881
|
});
|
|
685
|
-
if (
|
|
882
|
+
if (this.waitingForMessage) {
|
|
883
|
+
const resolver = this.waitingForMessage;
|
|
884
|
+
this.waitingForMessage = null;
|
|
885
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
886
|
+
void this.emitProgress("running", "Generating response");
|
|
887
|
+
resolver(batch);
|
|
888
|
+
} else if (!this.generating && !this.ai.isGenerating()) {
|
|
686
889
|
this.startGenerate();
|
|
687
890
|
}
|
|
688
891
|
}
|
|
689
892
|
async *[Symbol.asyncIterator]() {
|
|
690
893
|
while (true) {
|
|
691
|
-
|
|
692
|
-
|
|
894
|
+
let batch;
|
|
895
|
+
if (this.queue.length > 0) {
|
|
896
|
+
batch = this.queue.splice(0, this.queue.length);
|
|
897
|
+
} else {
|
|
898
|
+
logger.debug("[Session] Iterator waiting for messages", {
|
|
693
899
|
sessionId: this.id
|
|
694
900
|
});
|
|
695
|
-
|
|
901
|
+
batch = await new Promise((resolve) => {
|
|
902
|
+
this.waitingForMessage = resolve;
|
|
903
|
+
});
|
|
696
904
|
}
|
|
697
|
-
const batch = this.queue.splice(0, this.queue.length);
|
|
698
905
|
logger.debug("[Session] Yielding batch to SDK", {
|
|
699
906
|
sessionId: this.id,
|
|
700
907
|
batchSize: batch.length
|
|
@@ -727,28 +934,40 @@ var Session = class {
|
|
|
727
934
|
const summaryTextBlocks = [];
|
|
728
935
|
let resultMessage = null;
|
|
729
936
|
await this.emitProgress("running", "Generating response");
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
937
|
+
try {
|
|
938
|
+
for await (const message of this.ai.streamUserMessage(this, {
|
|
939
|
+
...this.sdkSessionIdentifier ? { resume: this.sdkSessionIdentifier } : {}
|
|
940
|
+
})) {
|
|
941
|
+
this.captureSdkSessionId(message);
|
|
942
|
+
if (message.type === "assistant") {
|
|
943
|
+
this.collectAssistantSummaryBlocks(message, summaryTextBlocks);
|
|
944
|
+
}
|
|
945
|
+
if (message.type === "result") {
|
|
946
|
+
resultMessage = message;
|
|
947
|
+
this.resetRetryState();
|
|
948
|
+
const complete = await this.buildCompletionEnvelope(resultMessage, summaryTextBlocks);
|
|
949
|
+
await Promise.all([
|
|
950
|
+
this.emitProgress("idle", "Ready for next message"),
|
|
951
|
+
this.dispatchEnvelope(complete)
|
|
952
|
+
]);
|
|
953
|
+
summaryTextBlocks.length = 0;
|
|
954
|
+
resultMessage = null;
|
|
955
|
+
}
|
|
956
|
+
for (const envelope of toMessageEnvelopes(message, this.id)) {
|
|
957
|
+
await this.dispatchEnvelope(envelope);
|
|
958
|
+
}
|
|
739
959
|
}
|
|
740
|
-
|
|
741
|
-
|
|
960
|
+
} catch (error) {
|
|
961
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
962
|
+
logger.info("[Session] Generation aborted", { sessionId: this.id });
|
|
963
|
+
await this.emitProgress("idle", "Generation cancelled");
|
|
964
|
+
return;
|
|
742
965
|
}
|
|
966
|
+
throw error;
|
|
743
967
|
}
|
|
744
|
-
this.resetRetryState();
|
|
745
|
-
const complete = await this.buildCompletionEnvelope(resultMessage, summaryTextBlocks);
|
|
746
|
-
await Promise.all([
|
|
747
|
-
this.emitProgress("idle", "Ready for next message"),
|
|
748
|
-
this.dispatchEnvelope(complete)
|
|
749
|
-
]);
|
|
750
968
|
} finally {
|
|
751
969
|
this.generating = false;
|
|
970
|
+
this.waitingForMessage = null;
|
|
752
971
|
logger.debug("[Session] Generation finished", {
|
|
753
972
|
sessionId: this.id,
|
|
754
973
|
pendingMessages: this.queue.length
|
|
@@ -868,7 +1087,7 @@ var Session = class {
|
|
|
868
1087
|
}
|
|
869
1088
|
const summarizationPrompt = `<assistant-output>${combined}<assistant-output>`;
|
|
870
1089
|
let summary = "";
|
|
871
|
-
for await (const message of
|
|
1090
|
+
for await (const message of query3({
|
|
872
1091
|
prompt: summarizationPrompt,
|
|
873
1092
|
options: {
|
|
874
1093
|
systemPrompt: `Provide a 3-5 sentence summary of the actions taken by the assistant.
|
|
@@ -878,7 +1097,8 @@ ONLY include content like key takeaways, actions (do NOT specify by whom), futur
|
|
|
878
1097
|
This content should be sufficient to be inserted into something like a commit message or pull request description.`,
|
|
879
1098
|
maxTurns: 1,
|
|
880
1099
|
model: "haiku",
|
|
881
|
-
allowedTools: []
|
|
1100
|
+
allowedTools: [],
|
|
1101
|
+
...claudeExecutablePath()
|
|
882
1102
|
}
|
|
883
1103
|
})) {
|
|
884
1104
|
if (message.type === "assistant") {
|
|
@@ -941,7 +1161,7 @@ This content should be sufficient to be inserted into something like a commit me
|
|
|
941
1161
|
}
|
|
942
1162
|
const prompt2 = this.buildCommitMessagePrompt(conversationContext, diff);
|
|
943
1163
|
let commitMessage = "";
|
|
944
|
-
for await (const message of
|
|
1164
|
+
for await (const message of query3({
|
|
945
1165
|
prompt: prompt2,
|
|
946
1166
|
options: {
|
|
947
1167
|
systemPrompt: `You generate clear, concise git commit messages based on conversation context and code changes.
|
|
@@ -956,7 +1176,8 @@ Guidelines:
|
|
|
956
1176
|
Return ONLY the commit message, no explanation or markdown formatting.`,
|
|
957
1177
|
maxTurns: 1,
|
|
958
1178
|
model: "haiku",
|
|
959
|
-
allowedTools: []
|
|
1179
|
+
allowedTools: [],
|
|
1180
|
+
...claudeExecutablePath()
|
|
960
1181
|
}
|
|
961
1182
|
})) {
|
|
962
1183
|
if (message.type === "assistant") {
|
|
@@ -2016,13 +2237,11 @@ Orchestrator: [message_worker: task="refactor database", message="User wants to
|
|
|
2016
2237
|
});
|
|
2017
2238
|
}
|
|
2018
2239
|
|
|
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");
|
|
2240
|
+
// src/orchestrator/orchestrator-prompt.md
|
|
2241
|
+
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
2242
|
|
|
2024
2243
|
// src/orchestrator/utils/completions-manager.ts
|
|
2025
|
-
import { query as
|
|
2244
|
+
import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
|
|
2026
2245
|
import { glob } from "glob";
|
|
2027
2246
|
import * as path6 from "path";
|
|
2028
2247
|
import * as Sentry4 from "@sentry/node";
|
|
@@ -2034,9 +2253,14 @@ var CompletionsManager = class {
|
|
|
2034
2253
|
return this.slashCommandsCache.slashCommands;
|
|
2035
2254
|
}
|
|
2036
2255
|
let commands = [];
|
|
2037
|
-
for await (const message of
|
|
2256
|
+
for await (const message of query4({
|
|
2038
2257
|
prompt: "Respond with one word.",
|
|
2039
|
-
options: {
|
|
2258
|
+
options: {
|
|
2259
|
+
maxTurns: 1,
|
|
2260
|
+
model: "haiku",
|
|
2261
|
+
settingSources: ["user", "project", "local"],
|
|
2262
|
+
...claudeExecutablePath()
|
|
2263
|
+
}
|
|
2040
2264
|
})) {
|
|
2041
2265
|
if (message.type === "system" && message.subtype === "init") {
|
|
2042
2266
|
console.log("Fetched available slash commands:", message.slash_commands);
|
|
@@ -2269,7 +2493,7 @@ var AgentOrchestrator = class {
|
|
|
2269
2493
|
mcpServers: {
|
|
2270
2494
|
worker: this.workerMcpServer
|
|
2271
2495
|
},
|
|
2272
|
-
systemPrompt:
|
|
2496
|
+
systemPrompt: orchestrator_prompt_default
|
|
2273
2497
|
}
|
|
2274
2498
|
}));
|
|
2275
2499
|
this.aiClient = mainClientFactory();
|
|
@@ -2563,6 +2787,33 @@ var AgentOrchestrator = class {
|
|
|
2563
2787
|
commit_sha: commitSha
|
|
2564
2788
|
};
|
|
2565
2789
|
}
|
|
2790
|
+
if (req.action === "commit_checkout") {
|
|
2791
|
+
try {
|
|
2792
|
+
const currentBranch = await git.revparse(["--abbrev-ref", "HEAD"]);
|
|
2793
|
+
const branchTrimmed = currentBranch.trim();
|
|
2794
|
+
const repoPath = this.workerManager.repoPath;
|
|
2795
|
+
const mainGit = simpleGit2(repoPath);
|
|
2796
|
+
await mainGit.raw(["checkout", "-d", branchTrimmed]);
|
|
2797
|
+
logger2.info(`[AgentOrchestrator] Checked out ${branchTrimmed} in main worktree`);
|
|
2798
|
+
return {
|
|
2799
|
+
type: "git_write",
|
|
2800
|
+
success: true,
|
|
2801
|
+
message: `Committed changes. The main workspace checked out the changes on this branch.`,
|
|
2802
|
+
commit_sha: commitSha
|
|
2803
|
+
};
|
|
2804
|
+
} catch (checkoutError) {
|
|
2805
|
+
logger2.error("[AgentOrchestrator] Failed to checkout in main worktree", {
|
|
2806
|
+
error: checkoutError
|
|
2807
|
+
});
|
|
2808
|
+
return {
|
|
2809
|
+
type: "git_write",
|
|
2810
|
+
success: true,
|
|
2811
|
+
message: `Committed successfully but failed to checkout in main worktree: ${checkoutError instanceof Error ? checkoutError.message : String(checkoutError)}`,
|
|
2812
|
+
commit_sha: commitSha,
|
|
2813
|
+
error: `Checkout failed: ${checkoutError instanceof Error ? checkoutError.message : String(checkoutError)}`
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2566
2817
|
return {
|
|
2567
2818
|
type: "git_write",
|
|
2568
2819
|
success: true,
|
|
@@ -3022,7 +3273,7 @@ var AgentOrchestrator = class {
|
|
|
3022
3273
|
const statusSnippet = this.buildCurrentWorkerStatus();
|
|
3023
3274
|
return `${statusSnippet}
|
|
3024
3275
|
|
|
3025
|
-
${
|
|
3276
|
+
${orchestrator_prompt_default}`;
|
|
3026
3277
|
}
|
|
3027
3278
|
buildCurrentWorkerStatus() {
|
|
3028
3279
|
const workers = this.workerManager.listWorkers();
|
|
@@ -3090,7 +3341,7 @@ function extractSummary(complete) {
|
|
|
3090
3341
|
// src/relay/relay-bridge.ts
|
|
3091
3342
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3092
3343
|
import EventEmitter from "events";
|
|
3093
|
-
import
|
|
3344
|
+
import WebSocket2 from "ws";
|
|
3094
3345
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
3095
3346
|
import * as Sentry11 from "@sentry/node";
|
|
3096
3347
|
|
|
@@ -3526,7 +3777,7 @@ var control_message_schema_default = {
|
|
|
3526
3777
|
},
|
|
3527
3778
|
GitWriteRequest: {
|
|
3528
3779
|
additionalProperties: false,
|
|
3529
|
-
description: 'Request to commit and optionally push changes.\n\nThe action field specifies what operation to perform:\n- "commit": Create a git commit with a generated message\n- "commit_push": Create a commit and push to origin\n- "commit_push_pr": Create a commit, push, and create a PR (not yet implemented)',
|
|
3780
|
+
description: 'Request to commit and optionally push changes.\n\nThe action field specifies what operation to perform:\n- "commit": Create a git commit with a generated message\n- "commit_push": Create a commit and push to origin\n- "commit_push_pr": Create a commit, push, and create a PR (not yet implemented)\n- "commit_checkout": Create a commit and checkout the branch in the main worktree',
|
|
3530
3781
|
properties: {
|
|
3531
3782
|
type: {
|
|
3532
3783
|
const: "git_write",
|
|
@@ -3538,7 +3789,8 @@ var control_message_schema_default = {
|
|
|
3538
3789
|
enum: [
|
|
3539
3790
|
"commit",
|
|
3540
3791
|
"commit_push",
|
|
3541
|
-
"commit_push_pr"
|
|
3792
|
+
"commit_push_pr",
|
|
3793
|
+
"commit_checkout"
|
|
3542
3794
|
],
|
|
3543
3795
|
title: "Action",
|
|
3544
3796
|
type: "string"
|
|
@@ -4620,12 +4872,23 @@ var machine_message_schema_default = {
|
|
|
4620
4872
|
type: "string"
|
|
4621
4873
|
},
|
|
4622
4874
|
method: {
|
|
4623
|
-
|
|
4875
|
+
enum: [
|
|
4876
|
+
"start_session",
|
|
4877
|
+
"stop_session"
|
|
4878
|
+
],
|
|
4624
4879
|
title: "Method",
|
|
4625
4880
|
type: "string"
|
|
4626
4881
|
},
|
|
4627
4882
|
payload: {
|
|
4628
|
-
|
|
4883
|
+
anyOf: [
|
|
4884
|
+
{
|
|
4885
|
+
$ref: "#/$defs/MachineStartSessionPayload"
|
|
4886
|
+
},
|
|
4887
|
+
{
|
|
4888
|
+
$ref: "#/$defs/MachineStopSessionPayload"
|
|
4889
|
+
}
|
|
4890
|
+
],
|
|
4891
|
+
title: "Payload"
|
|
4629
4892
|
}
|
|
4630
4893
|
},
|
|
4631
4894
|
required: [
|
|
@@ -4799,6 +5062,22 @@ var machine_message_schema_default = {
|
|
|
4799
5062
|
title: "MachineStatus",
|
|
4800
5063
|
type: "string"
|
|
4801
5064
|
},
|
|
5065
|
+
MachineStopSessionPayload: {
|
|
5066
|
+
additionalProperties: false,
|
|
5067
|
+
description: "Payload instructing the daemon to terminate a running session.",
|
|
5068
|
+
properties: {
|
|
5069
|
+
user_session_id: {
|
|
5070
|
+
format: "uuid",
|
|
5071
|
+
title: "User Session Id",
|
|
5072
|
+
type: "string"
|
|
5073
|
+
}
|
|
5074
|
+
},
|
|
5075
|
+
required: [
|
|
5076
|
+
"user_session_id"
|
|
5077
|
+
],
|
|
5078
|
+
title: "MachineStopSessionPayload",
|
|
5079
|
+
type: "object"
|
|
5080
|
+
},
|
|
4802
5081
|
WorkspaceConfig: {
|
|
4803
5082
|
additionalProperties: false,
|
|
4804
5083
|
description: "Workspace-level configuration persisted alongside metadata.",
|
|
@@ -7487,7 +7766,7 @@ function tryOpenBrowser(url) {
|
|
|
7487
7766
|
}
|
|
7488
7767
|
|
|
7489
7768
|
// src/auth/login-flow.ts
|
|
7490
|
-
import
|
|
7769
|
+
import chalk2 from "chalk";
|
|
7491
7770
|
async function performVerificationLogin({
|
|
7492
7771
|
backendUrlOption,
|
|
7493
7772
|
frontendUrlOption,
|
|
@@ -7496,7 +7775,7 @@ async function performVerificationLogin({
|
|
|
7496
7775
|
const backendUrl = resolveBackendUrl(backendUrlOption);
|
|
7497
7776
|
const frontendUrl = resolveFrontendUrl(frontendUrlOption);
|
|
7498
7777
|
const client = new ApiClient({ baseUrl: backendUrl });
|
|
7499
|
-
console.log(
|
|
7778
|
+
console.log(chalk2.yellow("Authenticating CLI\u2026"));
|
|
7500
7779
|
const loopback = await LoopbackListener.create();
|
|
7501
7780
|
const pkce = loopback ? generatePkcePair() : null;
|
|
7502
7781
|
const sessionOptions = {
|
|
@@ -7529,7 +7808,7 @@ async function performVerificationLogin({
|
|
|
7529
7808
|
await store.save(updated);
|
|
7530
7809
|
latestCredentials = updated;
|
|
7531
7810
|
if (!successLogged) {
|
|
7532
|
-
console.log(
|
|
7811
|
+
console.log(chalk2.green("\n\nLogin successful. Credentials saved."));
|
|
7533
7812
|
successLogged = true;
|
|
7534
7813
|
}
|
|
7535
7814
|
completed = true;
|
|
@@ -7545,7 +7824,7 @@ async function performVerificationLogin({
|
|
|
7545
7824
|
}
|
|
7546
7825
|
while (!completed) {
|
|
7547
7826
|
const input2 = await prompt(
|
|
7548
|
-
|
|
7827
|
+
chalk2.gray("\nInput fallback code if automatic login doesn't complete: ")
|
|
7549
7828
|
);
|
|
7550
7829
|
const cleaned = input2.trim();
|
|
7551
7830
|
if (completed) {
|
|
@@ -7618,10 +7897,10 @@ function reportLoopbackError(error) {
|
|
|
7618
7897
|
}
|
|
7619
7898
|
function printSessionInstructions(session) {
|
|
7620
7899
|
console.log(
|
|
7621
|
-
|
|
7900
|
+
chalk2.blue.bold("\nOpen this link in your browser if it did not open automatically:")
|
|
7622
7901
|
);
|
|
7623
|
-
console.log(
|
|
7624
|
-
console.log(
|
|
7902
|
+
console.log(chalk2.blueBright.underline(`${session.verificationUriComplete}`));
|
|
7903
|
+
console.log(chalk2.blue("\nApprove the request, then return to the CLI."));
|
|
7625
7904
|
}
|
|
7626
7905
|
function extractErrorMessage4(error) {
|
|
7627
7906
|
if (error instanceof Error && error.message) {
|
|
@@ -7729,6 +8008,27 @@ async function ensureUserSession(client, accessToken, options) {
|
|
|
7729
8008
|
);
|
|
7730
8009
|
}
|
|
7731
8010
|
|
|
8011
|
+
// src/utils/websocket.ts
|
|
8012
|
+
import WebSocket from "ws";
|
|
8013
|
+
function safeCloseWebSocket(ws) {
|
|
8014
|
+
try {
|
|
8015
|
+
ws.once("error", () => {
|
|
8016
|
+
});
|
|
8017
|
+
if (ws.readyState === WebSocket.CONNECTING) {
|
|
8018
|
+
ws.terminate();
|
|
8019
|
+
} else if (ws.readyState === WebSocket.OPEN) {
|
|
8020
|
+
ws.close();
|
|
8021
|
+
}
|
|
8022
|
+
} catch {
|
|
8023
|
+
}
|
|
8024
|
+
setImmediate(() => {
|
|
8025
|
+
try {
|
|
8026
|
+
ws.removeAllListeners();
|
|
8027
|
+
} catch {
|
|
8028
|
+
}
|
|
8029
|
+
});
|
|
8030
|
+
}
|
|
8031
|
+
|
|
7732
8032
|
// src/relay/relay-bridge.ts
|
|
7733
8033
|
var MAX_QUEUE_SIZE = 500;
|
|
7734
8034
|
var LOGIN_WAIT_DELAY_MS = 3e4;
|
|
@@ -7773,7 +8073,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
7773
8073
|
throw new Error("Relay bridge already started");
|
|
7774
8074
|
}
|
|
7775
8075
|
this.connectionHandle = this.orchestrator.addClient({
|
|
7776
|
-
canSend: () => !this.canceled && this.ws?.readyState ===
|
|
8076
|
+
canSend: () => !this.canceled && this.ws?.readyState === WebSocket2.OPEN,
|
|
7777
8077
|
sendEnvelope: async (message) => {
|
|
7778
8078
|
this.sendFrame(message);
|
|
7779
8079
|
},
|
|
@@ -7830,7 +8130,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
7830
8130
|
this.connectionHandle.disconnect();
|
|
7831
8131
|
this.connectionHandle = null;
|
|
7832
8132
|
}
|
|
7833
|
-
if (this.ws && this.ws.readyState ===
|
|
8133
|
+
if (this.ws && this.ws.readyState === WebSocket2.OPEN) {
|
|
7834
8134
|
this.ws.close(1e3, "CLI shutting down");
|
|
7835
8135
|
}
|
|
7836
8136
|
this.ws = null;
|
|
@@ -7847,7 +8147,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
7847
8147
|
this.userSessionId = registration.userSessionId;
|
|
7848
8148
|
const agentSessionId = this.ensureAgentSessionExists();
|
|
7849
8149
|
const url = buildRelayWebsocketUrl(this.backendUrl, registration.relayWsPath);
|
|
7850
|
-
const ws = new
|
|
8150
|
+
const ws = new WebSocket2(url, {
|
|
7851
8151
|
headers: {
|
|
7852
8152
|
"x-relay-token": registration.relayToken
|
|
7853
8153
|
}
|
|
@@ -7932,7 +8232,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
7932
8232
|
}
|
|
7933
8233
|
const serialized = JSON.stringify(frame);
|
|
7934
8234
|
const target = socket ?? this.ws;
|
|
7935
|
-
if (target && target.readyState ===
|
|
8235
|
+
if (target && target.readyState === WebSocket2.OPEN) {
|
|
7936
8236
|
try {
|
|
7937
8237
|
target.send(serialized);
|
|
7938
8238
|
return;
|
|
@@ -8327,7 +8627,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
8327
8627
|
};
|
|
8328
8628
|
}
|
|
8329
8629
|
const socket = this.ws;
|
|
8330
|
-
if (!socket || socket.readyState !==
|
|
8630
|
+
if (!socket || socket.readyState !== WebSocket2.OPEN) {
|
|
8331
8631
|
return;
|
|
8332
8632
|
}
|
|
8333
8633
|
const frame = {
|
|
@@ -8398,7 +8698,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
8398
8698
|
this.sendFrame(frame);
|
|
8399
8699
|
}
|
|
8400
8700
|
flushQueue() {
|
|
8401
|
-
if (!this.ws || this.ws.readyState !==
|
|
8701
|
+
if (!this.ws || this.ws.readyState !== WebSocket2.OPEN) {
|
|
8402
8702
|
return;
|
|
8403
8703
|
}
|
|
8404
8704
|
while (this.pendingOutbound.length > 0) {
|
|
@@ -8424,7 +8724,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
8424
8724
|
const interval = Math.max(this.heartbeatIntervalMs, 5e3);
|
|
8425
8725
|
this.pingTimer = setInterval(() => {
|
|
8426
8726
|
const socket = this.ws;
|
|
8427
|
-
if (!socket || socket.readyState !==
|
|
8727
|
+
if (!socket || socket.readyState !== WebSocket2.OPEN) {
|
|
8428
8728
|
return;
|
|
8429
8729
|
}
|
|
8430
8730
|
const frame = {
|
|
@@ -8442,7 +8742,7 @@ var RelayBridge = class extends EventEmitter {
|
|
|
8442
8742
|
}
|
|
8443
8743
|
}
|
|
8444
8744
|
closeCurrentSocket() {
|
|
8445
|
-
if (this.ws && this.ws.readyState ===
|
|
8745
|
+
if (this.ws && this.ws.readyState === WebSocket2.OPEN) {
|
|
8446
8746
|
this.ws.close(1012, "Reconnecting");
|
|
8447
8747
|
}
|
|
8448
8748
|
}
|
|
@@ -8495,14 +8795,7 @@ function waitForOpen(ws) {
|
|
|
8495
8795
|
const settle = (error) => {
|
|
8496
8796
|
cleanup();
|
|
8497
8797
|
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
|
-
}
|
|
8798
|
+
safeCloseWebSocket(ws);
|
|
8506
8799
|
reject(error);
|
|
8507
8800
|
} else {
|
|
8508
8801
|
resolve();
|
|
@@ -9063,7 +9356,7 @@ function extractErrorMessage8(error) {
|
|
|
9063
9356
|
}
|
|
9064
9357
|
|
|
9065
9358
|
// src/daemon/daemon-launcher.ts
|
|
9066
|
-
import
|
|
9359
|
+
import chalk3 from "chalk";
|
|
9067
9360
|
|
|
9068
9361
|
// src/daemon/session-pid-registry.ts
|
|
9069
9362
|
import fs10 from "node:fs/promises";
|
|
@@ -9257,7 +9550,7 @@ async function ensureMachineNameConfigured(configStore, logger3) {
|
|
|
9257
9550
|
const defaultName = os4.hostname();
|
|
9258
9551
|
let machineName = defaultName;
|
|
9259
9552
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
9260
|
-
const answer = (await prompt(
|
|
9553
|
+
const answer = (await prompt(chalk3.blue(`Enter a name for this machine [${defaultName}]: `))).trim();
|
|
9261
9554
|
if (answer) {
|
|
9262
9555
|
machineName = answer;
|
|
9263
9556
|
}
|
|
@@ -9450,12 +9743,11 @@ async function registerDirectory(directoryPath, options) {
|
|
|
9450
9743
|
await registry.ensureDirectory(normalized, { label: options.label, updateLastUsed: true });
|
|
9451
9744
|
}
|
|
9452
9745
|
async function spawnDaemonProcess(options, logger3) {
|
|
9453
|
-
const
|
|
9454
|
-
|
|
9455
|
-
throw new Error("Unable to determine CLI entry path for daemon start");
|
|
9456
|
-
}
|
|
9746
|
+
const args = getSpawnArgs(["daemon", "run-service"]);
|
|
9747
|
+
const execPath = getExecutablePath();
|
|
9457
9748
|
logger3.info("Starting Omnara daemon...");
|
|
9458
|
-
|
|
9749
|
+
logger3.debug(`Spawning daemon: ${execPath} ${args.join(" ")}`);
|
|
9750
|
+
const child = spawn2(execPath, args, {
|
|
9459
9751
|
detached: true,
|
|
9460
9752
|
stdio: "ignore",
|
|
9461
9753
|
env: {
|
|
@@ -9546,10 +9838,10 @@ function extractErrorMessage10(error) {
|
|
|
9546
9838
|
|
|
9547
9839
|
// src/commands/daemon.ts
|
|
9548
9840
|
import { Command as Command2 } from "commander";
|
|
9549
|
-
import * as
|
|
9841
|
+
import * as Sentry25 from "@sentry/node";
|
|
9550
9842
|
|
|
9551
9843
|
// src/daemon/run-daemon.ts
|
|
9552
|
-
import * as
|
|
9844
|
+
import * as Sentry24 from "@sentry/node";
|
|
9553
9845
|
|
|
9554
9846
|
// src/daemon/daemon-service.ts
|
|
9555
9847
|
import * as Sentry22 from "@sentry/node";
|
|
@@ -9768,8 +10060,8 @@ function extractErrorMessage11(error) {
|
|
|
9768
10060
|
// src/daemon/machine-supervisor.ts
|
|
9769
10061
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
9770
10062
|
import os5 from "node:os";
|
|
9771
|
-
import { setTimeout as
|
|
9772
|
-
import
|
|
10063
|
+
import { setTimeout as delay5 } from "node:timers/promises";
|
|
10064
|
+
import WebSocket3 from "ws";
|
|
9773
10065
|
import * as Sentry21 from "@sentry/node";
|
|
9774
10066
|
|
|
9775
10067
|
// ../shared-js/dist/utils/eff_wordlist.json
|
|
@@ -17595,9 +17887,11 @@ async function ensureWorkspace(client, accessToken, payload) {
|
|
|
17595
17887
|
|
|
17596
17888
|
// src/daemon/session-runner.ts
|
|
17597
17889
|
import { spawn as spawn3 } from "node:child_process";
|
|
17890
|
+
import { setTimeout as delay4 } from "node:timers/promises";
|
|
17598
17891
|
import * as Sentry20 from "@sentry/node";
|
|
17599
17892
|
import fs11 from "node:fs";
|
|
17600
17893
|
import path13 from "node:path";
|
|
17894
|
+
var GRACEFUL_STOP_DELAY_MS = 2e3;
|
|
17601
17895
|
var SessionManager = class {
|
|
17602
17896
|
backendUrl;
|
|
17603
17897
|
frontendUrl;
|
|
@@ -17630,6 +17924,47 @@ var SessionManager = class {
|
|
|
17630
17924
|
throw error;
|
|
17631
17925
|
}
|
|
17632
17926
|
}
|
|
17927
|
+
/**
|
|
17928
|
+
* Stop a running session by user session ID.
|
|
17929
|
+
*
|
|
17930
|
+
* Looks up the session in the PID registry, sends SIGTERM for graceful
|
|
17931
|
+
* shutdown, waits briefly, then sends SIGKILL if still alive.
|
|
17932
|
+
*
|
|
17933
|
+
* @returns true if a running session was found and terminated, false if
|
|
17934
|
+
* the session was not running.
|
|
17935
|
+
*/
|
|
17936
|
+
async stopSession(userSessionId) {
|
|
17937
|
+
return await withSessionRegistry(async (registry) => {
|
|
17938
|
+
const record = registry[userSessionId];
|
|
17939
|
+
if (!record) {
|
|
17940
|
+
return [registry, false];
|
|
17941
|
+
}
|
|
17942
|
+
const alive = await isSessionProcessAlive(record);
|
|
17943
|
+
if (!alive) {
|
|
17944
|
+
delete registry[userSessionId];
|
|
17945
|
+
return [registry, false];
|
|
17946
|
+
}
|
|
17947
|
+
try {
|
|
17948
|
+
process.kill(record.pid, "SIGTERM");
|
|
17949
|
+
this.logger.debug(`Sent SIGTERM to session ${userSessionId} (pid ${record.pid})`);
|
|
17950
|
+
} catch (error) {
|
|
17951
|
+
this.logger.debug(`SIGTERM failed for pid ${record.pid}: ${String(error)}`);
|
|
17952
|
+
delete registry[userSessionId];
|
|
17953
|
+
return [registry, false];
|
|
17954
|
+
}
|
|
17955
|
+
await delay4(GRACEFUL_STOP_DELAY_MS);
|
|
17956
|
+
const stillAlive = await isProcessAlive3(record.pid);
|
|
17957
|
+
if (stillAlive) {
|
|
17958
|
+
try {
|
|
17959
|
+
process.kill(record.pid, "SIGKILL");
|
|
17960
|
+
this.logger.debug(`Sent SIGKILL to session ${userSessionId} (pid ${record.pid})`);
|
|
17961
|
+
} catch {
|
|
17962
|
+
}
|
|
17963
|
+
}
|
|
17964
|
+
delete registry[userSessionId];
|
|
17965
|
+
return [registry, true];
|
|
17966
|
+
});
|
|
17967
|
+
}
|
|
17633
17968
|
};
|
|
17634
17969
|
var SessionRunner = class {
|
|
17635
17970
|
spec;
|
|
@@ -17651,10 +17986,6 @@ var SessionRunner = class {
|
|
|
17651
17986
|
if (this.child) {
|
|
17652
17987
|
throw new Error("Session runner already started");
|
|
17653
17988
|
}
|
|
17654
|
-
const entry = process.argv[1];
|
|
17655
|
-
if (!entry) {
|
|
17656
|
-
throw new Error("Unable to determine CLI entry path for session runner");
|
|
17657
|
-
}
|
|
17658
17989
|
const sessionLogPath = await createSessionLogPath(this.spec.workspaceId, this.spec.launchId);
|
|
17659
17990
|
this.logger.info(`Session ${this.spec.launchId} logs available at ${sessionLogPath}`);
|
|
17660
17991
|
await withSessionRegistry(async (registry) => {
|
|
@@ -17668,8 +17999,8 @@ var SessionRunner = class {
|
|
|
17668
17999
|
}
|
|
17669
18000
|
return [registry, null];
|
|
17670
18001
|
});
|
|
17671
|
-
const
|
|
17672
|
-
|
|
18002
|
+
const execPath = getExecutablePath();
|
|
18003
|
+
const args = getSpawnArgs([
|
|
17673
18004
|
"daemon",
|
|
17674
18005
|
"run-session",
|
|
17675
18006
|
"--user-session-id",
|
|
@@ -17678,7 +18009,8 @@ var SessionRunner = class {
|
|
|
17678
18009
|
this.spec.workspaceId,
|
|
17679
18010
|
"--directory",
|
|
17680
18011
|
this.directory.path
|
|
17681
|
-
];
|
|
18012
|
+
]);
|
|
18013
|
+
this.logger.debug(`Spawning session: ${execPath} ${args.join(" ")}`);
|
|
17682
18014
|
if (this.spec.worktreeName) {
|
|
17683
18015
|
args.push("--worktree-name", this.spec.worktreeName);
|
|
17684
18016
|
}
|
|
@@ -17703,7 +18035,7 @@ var SessionRunner = class {
|
|
|
17703
18035
|
let logFd = null;
|
|
17704
18036
|
try {
|
|
17705
18037
|
logFd = fs11.openSync(sessionLogPath, "a");
|
|
17706
|
-
const child = spawn3(
|
|
18038
|
+
const child = spawn3(execPath, args, {
|
|
17707
18039
|
cwd: this.directory.path,
|
|
17708
18040
|
stdio: ["ignore", logFd, logFd],
|
|
17709
18041
|
env,
|
|
@@ -17722,7 +18054,7 @@ var SessionRunner = class {
|
|
|
17722
18054
|
this.child = null;
|
|
17723
18055
|
});
|
|
17724
18056
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17725
|
-
const command = `${
|
|
18057
|
+
const command = `${execPath} ${args.join(" ")}`;
|
|
17726
18058
|
await withSessionRegistry(async (registry) => {
|
|
17727
18059
|
registry[this.spec.userSessionId] = {
|
|
17728
18060
|
pid: child.pid ?? -1,
|
|
@@ -17762,15 +18094,9 @@ async function createSessionLogPath(workspaceId, launchId) {
|
|
|
17762
18094
|
}
|
|
17763
18095
|
|
|
17764
18096
|
// 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;
|
|
18097
|
+
var isBundled = true;
|
|
18098
|
+
var PACKAGE_VERSION = isBundled ? "0.6.0" : "0.0.0-dev";
|
|
18099
|
+
var PACKAGE_DESCRIPTION = isBundled ? "Command-line interface for Omnara." : "Command-line interface for Omnara.";
|
|
17774
18100
|
|
|
17775
18101
|
// src/daemon/machine-supervisor.ts
|
|
17776
18102
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -17878,13 +18204,13 @@ var MachineSupervisor = class {
|
|
|
17878
18204
|
} else {
|
|
17879
18205
|
this.logger.debug("Machine supervisor still waiting for login\u2026");
|
|
17880
18206
|
}
|
|
17881
|
-
await
|
|
18207
|
+
await delay5(LOGIN_WAIT_DELAY_MS2);
|
|
17882
18208
|
} else {
|
|
17883
18209
|
Sentry21.captureException(error);
|
|
17884
18210
|
this.logger.warn(
|
|
17885
18211
|
`Machine supervisor error: ${error.message}. Retrying in ${Math.round(retryDelayMs / 1e3)}s.`
|
|
17886
18212
|
);
|
|
17887
|
-
await
|
|
18213
|
+
await delay5(retryDelayMs);
|
|
17888
18214
|
retryDelayMs = calculateBackoff(retryDelayMs);
|
|
17889
18215
|
}
|
|
17890
18216
|
}
|
|
@@ -17957,7 +18283,7 @@ var MachineSupervisor = class {
|
|
|
17957
18283
|
const url = buildMachineWebsocketUrl(this.backendUrl, identity.wsPath);
|
|
17958
18284
|
this.logger.info(`Connecting to machine websocket at ${url}`);
|
|
17959
18285
|
await new Promise((resolve, reject) => {
|
|
17960
|
-
const ws = new
|
|
18286
|
+
const ws = new WebSocket3(url, {
|
|
17961
18287
|
headers: {
|
|
17962
18288
|
authorization: `Bearer ${identity.token}`
|
|
17963
18289
|
}
|
|
@@ -17974,17 +18300,7 @@ var MachineSupervisor = class {
|
|
|
17974
18300
|
}
|
|
17975
18301
|
this.clearHeartbeatTimer();
|
|
17976
18302
|
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
|
-
}
|
|
18303
|
+
safeCloseWebSocket(ws);
|
|
17988
18304
|
if (error) {
|
|
17989
18305
|
reject(error);
|
|
17990
18306
|
} else {
|
|
@@ -18051,12 +18367,18 @@ var MachineSupervisor = class {
|
|
|
18051
18367
|
}
|
|
18052
18368
|
}
|
|
18053
18369
|
async handleRpcRequest(message) {
|
|
18054
|
-
if (message.method
|
|
18370
|
+
if (message.method === "start_session") {
|
|
18371
|
+
await this.handleStartSession(message);
|
|
18372
|
+
} else if (message.method === "stop_session") {
|
|
18373
|
+
await this.handleStopSession(message);
|
|
18374
|
+
} else {
|
|
18055
18375
|
await this.sendRpcResponse(message.request_id, "error", `Unknown method ${message.method}`);
|
|
18056
|
-
return;
|
|
18057
18376
|
}
|
|
18377
|
+
}
|
|
18378
|
+
async handleStartSession(message) {
|
|
18058
18379
|
try {
|
|
18059
|
-
const
|
|
18380
|
+
const payload = message.payload;
|
|
18381
|
+
const result = await this.launchWorkspaceSessionFromRpc(payload);
|
|
18060
18382
|
await this.sendRpcResponse(message.request_id, "ok", void 0, {
|
|
18061
18383
|
launch_id: result.launchId,
|
|
18062
18384
|
user_session_id: result.userSessionId,
|
|
@@ -18075,6 +18397,25 @@ var MachineSupervisor = class {
|
|
|
18075
18397
|
await this.sendRpcResponse(message.request_id, "error", messageText);
|
|
18076
18398
|
}
|
|
18077
18399
|
}
|
|
18400
|
+
async handleStopSession(message) {
|
|
18401
|
+
const payload = message.payload;
|
|
18402
|
+
const userSessionId = payload.user_session_id;
|
|
18403
|
+
try {
|
|
18404
|
+
const stopped = await this.sessionManager.stopSession(userSessionId);
|
|
18405
|
+
if (stopped) {
|
|
18406
|
+
this.logger.info(`Stopped session ${userSessionId}`);
|
|
18407
|
+
await this.sendRpcResponse(message.request_id, "ok");
|
|
18408
|
+
} else {
|
|
18409
|
+
this.logger.debug(`Session ${userSessionId} was not running`);
|
|
18410
|
+
await this.sendRpcResponse(message.request_id, "not_found", "Session not running");
|
|
18411
|
+
}
|
|
18412
|
+
} catch (error) {
|
|
18413
|
+
Sentry21.captureException(error);
|
|
18414
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
18415
|
+
this.logger.warn(`Failed to stop session ${userSessionId}: ${messageText}`);
|
|
18416
|
+
await this.sendRpcResponse(message.request_id, "error", messageText);
|
|
18417
|
+
}
|
|
18418
|
+
}
|
|
18078
18419
|
async sendRpcResponse(requestId, status, error, body) {
|
|
18079
18420
|
const response = {
|
|
18080
18421
|
type: "rpc_response",
|
|
@@ -18196,7 +18537,7 @@ var MachineSupervisor = class {
|
|
|
18196
18537
|
return;
|
|
18197
18538
|
}
|
|
18198
18539
|
}
|
|
18199
|
-
await
|
|
18540
|
+
await delay5(250);
|
|
18200
18541
|
}
|
|
18201
18542
|
throw new Error("Machine has not finished registering yet. Try again in a few seconds.");
|
|
18202
18543
|
}
|
|
@@ -18232,7 +18573,7 @@ var MachineSupervisor = class {
|
|
|
18232
18573
|
}
|
|
18233
18574
|
async sendMessage(payload) {
|
|
18234
18575
|
const ws = this.websocket;
|
|
18235
|
-
if (!ws || ws.readyState !==
|
|
18576
|
+
if (!ws || ws.readyState !== WebSocket3.OPEN) {
|
|
18236
18577
|
throw new Error("Machine websocket is not connected");
|
|
18237
18578
|
}
|
|
18238
18579
|
await new Promise((resolve, reject) => {
|
|
@@ -18415,12 +18756,316 @@ var DaemonService = class {
|
|
|
18415
18756
|
// src/daemon/run-daemon.ts
|
|
18416
18757
|
import fs12 from "node:fs";
|
|
18417
18758
|
import path14 from "node:path";
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18421
|
-
|
|
18422
|
-
|
|
18423
|
-
|
|
18759
|
+
|
|
18760
|
+
// src/utils/version-check.ts
|
|
18761
|
+
import chalk4 from "chalk";
|
|
18762
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
18763
|
+
import { realpathSync } from "node:fs";
|
|
18764
|
+
import { platform, arch } from "node:os";
|
|
18765
|
+
import * as Sentry23 from "@sentry/node";
|
|
18766
|
+
var RELEASE_BASE_URL = process.env.OMNARA_RELEASE_URL ?? "https://releases.omnara.com";
|
|
18767
|
+
var FRONTEND_BASE_URL = process.env.OMNARA_FRONTEND_URL ?? "https://omnara.com";
|
|
18768
|
+
var IS_PRERELEASE = process.env.PRERELEASE === "1";
|
|
18769
|
+
var INSTALL_SCRIPT_URL_UNIX = `${FRONTEND_BASE_URL}/install/install.sh`;
|
|
18770
|
+
var INSTALL_SCRIPT_URL_WINDOWS = `${FRONTEND_BASE_URL}/install/install.ps1`;
|
|
18771
|
+
function detectPlatform() {
|
|
18772
|
+
const os6 = platform();
|
|
18773
|
+
const cpuArch = arch();
|
|
18774
|
+
let platformId;
|
|
18775
|
+
if (os6 === "darwin" && cpuArch === "arm64") {
|
|
18776
|
+
platformId = "darwin-arm64";
|
|
18777
|
+
} else if (os6 === "darwin" && cpuArch === "x64") {
|
|
18778
|
+
platformId = "darwin-x64";
|
|
18779
|
+
} else if (os6 === "linux" && cpuArch === "x64") {
|
|
18780
|
+
platformId = isMusl() ? "linux-x64-musl" : "linux-x64";
|
|
18781
|
+
} else if (os6 === "linux" && cpuArch === "arm64") {
|
|
18782
|
+
platformId = "linux-arm64";
|
|
18783
|
+
} else if (os6 === "win32" && cpuArch === "x64") {
|
|
18784
|
+
platformId = "windows-x64";
|
|
18785
|
+
} else {
|
|
18786
|
+
throw new Error(`Unsupported platform: ${os6}-${cpuArch}`);
|
|
18787
|
+
}
|
|
18788
|
+
return platformId;
|
|
18789
|
+
}
|
|
18790
|
+
function isMusl() {
|
|
18791
|
+
try {
|
|
18792
|
+
const result = execSync2("ldd --version 2>&1 || true", { encoding: "utf8" });
|
|
18793
|
+
return result.toLowerCase().includes("musl");
|
|
18794
|
+
} catch {
|
|
18795
|
+
return false;
|
|
18796
|
+
}
|
|
18797
|
+
}
|
|
18798
|
+
async function fetchLatestRelease() {
|
|
18799
|
+
const controller = new AbortController();
|
|
18800
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
18801
|
+
timeout.unref?.();
|
|
18802
|
+
try {
|
|
18803
|
+
const basePath = IS_PRERELEASE ? `${RELEASE_BASE_URL}/prerelease` : RELEASE_BASE_URL;
|
|
18804
|
+
const url = `${basePath}/latest/release.json`;
|
|
18805
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
18806
|
+
if (!response.ok) {
|
|
18807
|
+
return null;
|
|
18808
|
+
}
|
|
18809
|
+
return await response.json();
|
|
18810
|
+
} catch {
|
|
18811
|
+
return null;
|
|
18812
|
+
} finally {
|
|
18813
|
+
clearTimeout(timeout);
|
|
18814
|
+
}
|
|
18815
|
+
}
|
|
18816
|
+
function compareVersions(v1, v2) {
|
|
18817
|
+
const clean1 = v1.replace(/^v/, "");
|
|
18818
|
+
const clean2 = v2.replace(/^v/, "");
|
|
18819
|
+
const parts1 = clean1.split(".").map(Number);
|
|
18820
|
+
const parts2 = clean2.split(".").map(Number);
|
|
18821
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
18822
|
+
const part1 = parts1[i] || 0;
|
|
18823
|
+
const part2 = parts2[i] || 0;
|
|
18824
|
+
if (part1 > part2) return 1;
|
|
18825
|
+
if (part1 < part2) return -1;
|
|
18826
|
+
}
|
|
18827
|
+
return 0;
|
|
18828
|
+
}
|
|
18829
|
+
function isNpmInstallation() {
|
|
18830
|
+
if (isBunSingleFileExecutable()) {
|
|
18831
|
+
return false;
|
|
18832
|
+
}
|
|
18833
|
+
const execPath = process.argv[1] ?? "";
|
|
18834
|
+
if (execPath.includes("node_modules")) {
|
|
18835
|
+
return true;
|
|
18836
|
+
}
|
|
18837
|
+
if (execPath.includes("/lib/node_modules/") || execPath.includes("\\node_modules\\")) {
|
|
18838
|
+
return true;
|
|
18839
|
+
}
|
|
18840
|
+
try {
|
|
18841
|
+
const realPath = realpathSync(execPath);
|
|
18842
|
+
if (realPath.includes("node_modules")) {
|
|
18843
|
+
return true;
|
|
18844
|
+
}
|
|
18845
|
+
} catch {
|
|
18846
|
+
}
|
|
18847
|
+
return false;
|
|
18848
|
+
}
|
|
18849
|
+
function uninstallNpmVersion() {
|
|
18850
|
+
console.log(chalk4.blue("Uninstalling npm version of Omnara..."));
|
|
18851
|
+
try {
|
|
18852
|
+
execSync2("npm uninstall -g omnara", { stdio: "inherit" });
|
|
18853
|
+
return true;
|
|
18854
|
+
} catch {
|
|
18855
|
+
try {
|
|
18856
|
+
if (platform() !== "win32") {
|
|
18857
|
+
execSync2("sudo npm uninstall -g omnara", { stdio: "inherit" });
|
|
18858
|
+
return true;
|
|
18859
|
+
}
|
|
18860
|
+
} catch {
|
|
18861
|
+
}
|
|
18862
|
+
}
|
|
18863
|
+
console.log(chalk4.yellow("Could not automatically uninstall npm version."));
|
|
18864
|
+
console.log(chalk4.yellow("Please run manually: npm uninstall -g omnara"));
|
|
18865
|
+
return false;
|
|
18866
|
+
}
|
|
18867
|
+
async function runInstallScript(options) {
|
|
18868
|
+
const os6 = platform();
|
|
18869
|
+
const silent = options?.silent ?? false;
|
|
18870
|
+
const stdio = silent ? "pipe" : "inherit";
|
|
18871
|
+
if (!silent) {
|
|
18872
|
+
if (IS_PRERELEASE) {
|
|
18873
|
+
console.log(chalk4.blue("\nRunning Omnara installation script (prerelease channel)...\n"));
|
|
18874
|
+
} else {
|
|
18875
|
+
console.log(chalk4.blue("\nRunning Omnara installation script...\n"));
|
|
18876
|
+
}
|
|
18877
|
+
}
|
|
18878
|
+
const env = { ...process.env };
|
|
18879
|
+
if (IS_PRERELEASE) {
|
|
18880
|
+
env.PRERELEASE = "1";
|
|
18881
|
+
}
|
|
18882
|
+
try {
|
|
18883
|
+
if (os6 === "win32") {
|
|
18884
|
+
const prereleaseEnv = IS_PRERELEASE ? '$env:PRERELEASE="1"; ' : "";
|
|
18885
|
+
const cmd = `powershell -ExecutionPolicy Bypass -Command "& { ${prereleaseEnv}iwr -useb '${INSTALL_SCRIPT_URL_WINDOWS}' | iex }"`;
|
|
18886
|
+
execSync2(cmd, { stdio, env });
|
|
18887
|
+
} else {
|
|
18888
|
+
const cmd = `curl -fsSL '${INSTALL_SCRIPT_URL_UNIX}' | bash`;
|
|
18889
|
+
execSync2(cmd, { stdio, shell: "/bin/bash", env });
|
|
18890
|
+
}
|
|
18891
|
+
return true;
|
|
18892
|
+
} catch (error) {
|
|
18893
|
+
Sentry23.captureException(error);
|
|
18894
|
+
if (!silent) {
|
|
18895
|
+
console.log(chalk4.red("\nInstallation script failed."));
|
|
18896
|
+
console.log(chalk4.yellow("Please install manually:"));
|
|
18897
|
+
const prereleasePrefix = IS_PRERELEASE ? "PRERELEASE=1 " : "";
|
|
18898
|
+
if (os6 === "win32") {
|
|
18899
|
+
const psPrefix = IS_PRERELEASE ? '$env:PRERELEASE="1"; ' : "";
|
|
18900
|
+
console.log(chalk4.cyan(` ${psPrefix}irm ${INSTALL_SCRIPT_URL_WINDOWS} | iex`));
|
|
18901
|
+
} else {
|
|
18902
|
+
console.log(
|
|
18903
|
+
chalk4.cyan(` ${prereleasePrefix}curl -fsSL ${INSTALL_SCRIPT_URL_UNIX} | bash`)
|
|
18904
|
+
);
|
|
18905
|
+
}
|
|
18906
|
+
}
|
|
18907
|
+
return false;
|
|
18908
|
+
}
|
|
18909
|
+
}
|
|
18910
|
+
async function handleNpmInstallation() {
|
|
18911
|
+
const os6 = platform();
|
|
18912
|
+
const installCmd = os6 === "win32" ? `irm ${INSTALL_SCRIPT_URL_WINDOWS} | iex` : `curl -fsSL ${INSTALL_SCRIPT_URL_UNIX} | bash`;
|
|
18913
|
+
const border = chalk4.yellow("=".repeat(60));
|
|
18914
|
+
console.log("\n" + border);
|
|
18915
|
+
console.log(chalk4.yellow.bold(" npm Installation Detected"));
|
|
18916
|
+
console.log(border);
|
|
18917
|
+
console.log(
|
|
18918
|
+
chalk4.white(`
|
|
18919
|
+
Omnara CLI is now distributed as a standalone executable
|
|
18920
|
+
with automatic updates and improved performance.
|
|
18921
|
+
|
|
18922
|
+
Migrating to the new installation method...
|
|
18923
|
+
`)
|
|
18924
|
+
);
|
|
18925
|
+
uninstallNpmVersion();
|
|
18926
|
+
const success = await runInstallScript();
|
|
18927
|
+
if (success) {
|
|
18928
|
+
console.log(chalk4.green("\nMigration complete!"));
|
|
18929
|
+
console.log(chalk4.white("Please restart your terminal and run `omnara` again.\n"));
|
|
18930
|
+
return true;
|
|
18931
|
+
} else {
|
|
18932
|
+
console.log(
|
|
18933
|
+
chalk4.yellow(`
|
|
18934
|
+
To complete migration manually, run:
|
|
18935
|
+
|
|
18936
|
+
${chalk4.cyan(installCmd)}
|
|
18937
|
+
`)
|
|
18938
|
+
);
|
|
18939
|
+
return false;
|
|
18940
|
+
}
|
|
18941
|
+
}
|
|
18942
|
+
async function checkForUpdate() {
|
|
18943
|
+
const currentVersion = PACKAGE_VERSION;
|
|
18944
|
+
if (!currentVersion) {
|
|
18945
|
+
return { available: false, currentVersion: "0.0.0", isPrerelease: IS_PRERELEASE };
|
|
18946
|
+
}
|
|
18947
|
+
try {
|
|
18948
|
+
const release = await fetchLatestRelease();
|
|
18949
|
+
if (!release) {
|
|
18950
|
+
return { available: false, currentVersion, isPrerelease: IS_PRERELEASE };
|
|
18951
|
+
}
|
|
18952
|
+
const platformId = detectPlatform();
|
|
18953
|
+
const hasPlatformBinary = !!release.platforms[platformId];
|
|
18954
|
+
if (!hasPlatformBinary) {
|
|
18955
|
+
return { available: false, currentVersion, isPrerelease: IS_PRERELEASE };
|
|
18956
|
+
}
|
|
18957
|
+
const updateAvailable = compareVersions(release.version, currentVersion) > 0;
|
|
18958
|
+
return {
|
|
18959
|
+
available: updateAvailable,
|
|
18960
|
+
latestVersion: release.version,
|
|
18961
|
+
currentVersion,
|
|
18962
|
+
isPrerelease: IS_PRERELEASE
|
|
18963
|
+
};
|
|
18964
|
+
} catch {
|
|
18965
|
+
return { available: false, currentVersion, isPrerelease: IS_PRERELEASE };
|
|
18966
|
+
}
|
|
18967
|
+
}
|
|
18968
|
+
async function performUpdate(options) {
|
|
18969
|
+
const silent = options?.silent ?? false;
|
|
18970
|
+
try {
|
|
18971
|
+
if (!silent) {
|
|
18972
|
+
const client = new DaemonClient();
|
|
18973
|
+
const status = await client.getStatus().catch(() => null);
|
|
18974
|
+
if (status) {
|
|
18975
|
+
console.log(chalk4.yellow("Stopping daemon for update..."));
|
|
18976
|
+
await client.stop("UPDATING").catch(() => {
|
|
18977
|
+
});
|
|
18978
|
+
}
|
|
18979
|
+
}
|
|
18980
|
+
const success = await runInstallScript({ silent });
|
|
18981
|
+
if (success && !silent) {
|
|
18982
|
+
console.log(chalk4.green("\nUpdate complete!"));
|
|
18983
|
+
console.log(chalk4.white("The daemon will restart automatically.\n"));
|
|
18984
|
+
}
|
|
18985
|
+
return success;
|
|
18986
|
+
} catch (error) {
|
|
18987
|
+
Sentry23.captureException(error);
|
|
18988
|
+
return false;
|
|
18989
|
+
}
|
|
18990
|
+
}
|
|
18991
|
+
async function attemptUpdate() {
|
|
18992
|
+
try {
|
|
18993
|
+
const currentVersion = PACKAGE_VERSION;
|
|
18994
|
+
if (!currentVersion) {
|
|
18995
|
+
return false;
|
|
18996
|
+
}
|
|
18997
|
+
if (isNpmInstallation()) {
|
|
18998
|
+
return await handleNpmInstallation();
|
|
18999
|
+
}
|
|
19000
|
+
const updateInfo = await checkForUpdate();
|
|
19001
|
+
if (!updateInfo.available) {
|
|
19002
|
+
const channel = IS_PRERELEASE ? " (prerelease channel)" : "";
|
|
19003
|
+
console.log(chalk4.grey(`Omnara is up to date${channel}!
|
|
19004
|
+
`));
|
|
19005
|
+
return false;
|
|
19006
|
+
}
|
|
19007
|
+
const channelLabel = IS_PRERELEASE ? " (prerelease)" : "";
|
|
19008
|
+
console.log(
|
|
19009
|
+
chalk4.yellow(
|
|
19010
|
+
"\u2550".repeat(60) + `
|
|
19011
|
+
Update available for Omnara CLI${channelLabel}
|
|
19012
|
+
Current version: ${updateInfo.currentVersion}
|
|
19013
|
+
Latest version: ${updateInfo.latestVersion}
|
|
19014
|
+
${"\u2550".repeat(60)}`
|
|
19015
|
+
)
|
|
19016
|
+
);
|
|
19017
|
+
const success = await performUpdate();
|
|
19018
|
+
if (success) {
|
|
19019
|
+
console.log(chalk4.white("Please restart `omnara` to use the new version.\n"));
|
|
19020
|
+
return true;
|
|
19021
|
+
}
|
|
19022
|
+
return false;
|
|
19023
|
+
} catch (e) {
|
|
19024
|
+
Sentry23.captureException(e);
|
|
19025
|
+
}
|
|
19026
|
+
return false;
|
|
19027
|
+
}
|
|
19028
|
+
|
|
19029
|
+
// src/daemon/run-daemon.ts
|
|
19030
|
+
var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
19031
|
+
function startUpdateChecker(logger3, onUpdateInstalled) {
|
|
19032
|
+
const checkForUpdates = async () => {
|
|
19033
|
+
try {
|
|
19034
|
+
logger3.debug("Checking for updates...");
|
|
19035
|
+
const updateInfo = await checkForUpdate();
|
|
19036
|
+
if (updateInfo.available) {
|
|
19037
|
+
logger3.info(
|
|
19038
|
+
`Update available: ${updateInfo.currentVersion} -> ${updateInfo.latestVersion}`
|
|
19039
|
+
);
|
|
19040
|
+
logger3.info("Starting background update...");
|
|
19041
|
+
const success = await runInstallScript({ silent: true });
|
|
19042
|
+
if (success) {
|
|
19043
|
+
logger3.info("Update installed. Triggering graceful shutdown for restart...");
|
|
19044
|
+
onUpdateInstalled();
|
|
19045
|
+
} else {
|
|
19046
|
+
logger3.warn("Background update failed. Will retry on next check.");
|
|
19047
|
+
}
|
|
19048
|
+
} else {
|
|
19049
|
+
logger3.debug(`No updates available (current: ${updateInfo.currentVersion})`);
|
|
19050
|
+
}
|
|
19051
|
+
} catch (error) {
|
|
19052
|
+
Sentry24.captureException(error);
|
|
19053
|
+
logger3.error("Update check failed:", error);
|
|
19054
|
+
}
|
|
19055
|
+
};
|
|
19056
|
+
setTimeout(() => {
|
|
19057
|
+
void checkForUpdates();
|
|
19058
|
+
}, 1e4);
|
|
19059
|
+
return setInterval(() => {
|
|
19060
|
+
void checkForUpdates();
|
|
19061
|
+
}, UPDATE_CHECK_INTERVAL_MS);
|
|
19062
|
+
}
|
|
19063
|
+
async function runDaemonProcess() {
|
|
19064
|
+
await ensureOmnaraHome();
|
|
19065
|
+
await ensureDirectory(DAEMON_LOG_DIR);
|
|
19066
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19067
|
+
const fileName = `${timestamp}-${process.pid}.log`;
|
|
19068
|
+
const logPath = path14.join(DAEMON_LOG_DIR, fileName);
|
|
18424
19069
|
const stream = fs12.createWriteStream(logPath, {
|
|
18425
19070
|
flags: "a",
|
|
18426
19071
|
mode: DEFAULT_FILE_MODE2
|
|
@@ -18466,11 +19111,18 @@ async function runDaemonProcess() {
|
|
|
18466
19111
|
machineSupervisor,
|
|
18467
19112
|
logger: logger3
|
|
18468
19113
|
});
|
|
19114
|
+
const updateCheckInterval = startUpdateChecker(logger3, () => {
|
|
19115
|
+
logger3.info("Update installed, initiating graceful shutdown...");
|
|
19116
|
+
void service.stop().catch((error) => {
|
|
19117
|
+
Sentry24.captureException(error);
|
|
19118
|
+
logger3.error("Failed to stop daemon after update:", error);
|
|
19119
|
+
});
|
|
19120
|
+
});
|
|
18469
19121
|
const makeSignalHandler = (signal) => {
|
|
18470
19122
|
return () => {
|
|
18471
19123
|
logger3.warn(`Received ${signal}. Stopping daemon...`);
|
|
18472
19124
|
void service.stop().catch((error) => {
|
|
18473
|
-
|
|
19125
|
+
Sentry24.captureException(error);
|
|
18474
19126
|
logger3.error("Failed to stop daemon:", error);
|
|
18475
19127
|
});
|
|
18476
19128
|
};
|
|
@@ -18483,10 +19135,11 @@ async function runDaemonProcess() {
|
|
|
18483
19135
|
await service.start();
|
|
18484
19136
|
await service.waitForShutdown();
|
|
18485
19137
|
} catch (error) {
|
|
18486
|
-
|
|
19138
|
+
Sentry24.captureException(error);
|
|
18487
19139
|
logger3.error("Daemon exited with error:", error);
|
|
18488
19140
|
process.exitCode = 1;
|
|
18489
19141
|
} finally {
|
|
19142
|
+
clearInterval(updateCheckInterval);
|
|
18490
19143
|
process.off("SIGINT", sigint);
|
|
18491
19144
|
process.off("SIGTERM", sigterm);
|
|
18492
19145
|
await service.stop().catch(() => void 0);
|
|
@@ -18543,7 +19196,7 @@ function buildDaemonCommand() {
|
|
|
18543
19196
|
try {
|
|
18544
19197
|
process.chdir(options.directory);
|
|
18545
19198
|
} catch (error) {
|
|
18546
|
-
|
|
19199
|
+
Sentry25.captureException(error);
|
|
18547
19200
|
logger.error(
|
|
18548
19201
|
`Unable to switch to directory ${options.directory}: ${error.message}`
|
|
18549
19202
|
);
|
|
@@ -18562,7 +19215,7 @@ function buildDaemonCommand() {
|
|
|
18562
19215
|
worktreeName: options.worktreeName
|
|
18563
19216
|
});
|
|
18564
19217
|
} catch (error) {
|
|
18565
|
-
|
|
19218
|
+
Sentry25.captureException(error);
|
|
18566
19219
|
logger.error("User session exited with error:", error);
|
|
18567
19220
|
process.exitCode = process.exitCode || 1;
|
|
18568
19221
|
}
|
|
@@ -18594,190 +19247,6 @@ function buildKillCommand() {
|
|
|
18594
19247
|
return kill;
|
|
18595
19248
|
}
|
|
18596
19249
|
|
|
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
19250
|
// src/cli.ts
|
|
18782
19251
|
import chalk5 from "chalk";
|
|
18783
19252
|
function createProgram() {
|
|
@@ -18814,6 +19283,9 @@ async function run(argv) {
|
|
|
18814
19283
|
}
|
|
18815
19284
|
}
|
|
18816
19285
|
function isMainModule() {
|
|
19286
|
+
if (isBunSingleFileExecutable()) {
|
|
19287
|
+
return true;
|
|
19288
|
+
}
|
|
18817
19289
|
if (!import.meta.url || !process.argv[1]) {
|
|
18818
19290
|
return false;
|
|
18819
19291
|
}
|