omnara 0.5.3 → 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 CHANGED
@@ -177,9 +177,11 @@ var BASE_ALLOWED_WORKER_TOOLS = [
177
177
  "Skill",
178
178
  "SlashCommand"
179
179
  ];
180
+ var INTERRUPT_TIMEOUT_MS = 5e3;
180
181
  var AIClient = class {
181
182
  baseOptions;
182
183
  query;
184
+ abortController;
183
185
  currentModel = "sonnet";
184
186
  constructor(config) {
185
187
  const defaultOptions = {
@@ -202,17 +204,23 @@ var AIClient = class {
202
204
  this.ensureWorkingDirectory(this.baseOptions.cwd);
203
205
  }
204
206
  async *streamUserMessage(prompt2, overrides) {
207
+ this.abortController = new AbortController();
205
208
  const q = this.createQuery(prompt2, overrides);
206
209
  this.query = q;
207
- for await (const message of q) {
208
- yield message;
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;
209
217
  }
210
- this.query = void 0;
211
218
  }
212
219
  createQuery(prompt2, overrides) {
213
220
  const merged = {
214
221
  ...this.baseOptions,
215
- ...overrides
222
+ ...overrides,
223
+ abortController: this.abortController
216
224
  };
217
225
  this.ensureWorkingDirectory(merged.cwd);
218
226
  return query2({
@@ -221,10 +229,25 @@ var AIClient = class {
221
229
  });
222
230
  }
223
231
  async cancel() {
224
- if (this.query) {
225
- await this.query.interrupt();
226
- } else {
227
- console.warn("No query to interrupt. Ignoring");
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();
228
251
  }
229
252
  }
230
253
  isGenerating() {
@@ -602,8 +625,22 @@ function createEnvelope(sessionId, content, senderId, metadata, payloadMetadata)
602
625
  }
603
626
  function toMessageEnvelopes(message, sessionId) {
604
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
+ };
605
641
  switch (message.type) {
606
642
  case "assistant": {
643
+ const subagentMetadata = buildSubagentPayloadMetadata();
607
644
  for (const block of message.message.content) {
608
645
  if (isTextBlock(block)) {
609
646
  const content = {
@@ -611,9 +648,15 @@ function toMessageEnvelopes(message, sessionId) {
611
648
  text: block.text
612
649
  };
613
650
  envelopes.push(
614
- createEnvelope(sessionId, content, sessionId, {
615
- role: "assistant"
616
- })
651
+ createEnvelope(
652
+ sessionId,
653
+ content,
654
+ sessionId,
655
+ {
656
+ role: "assistant"
657
+ },
658
+ subagentMetadata
659
+ )
617
660
  );
618
661
  } else if (isToolUseBlock(block)) {
619
662
  const content = {
@@ -623,15 +666,29 @@ function toMessageEnvelopes(message, sessionId) {
623
666
  arguments: normalizeToolArguments(block.input)
624
667
  };
625
668
  envelopes.push(
626
- createEnvelope(sessionId, content, sessionId, {
627
- role: "assistant"
628
- })
669
+ createEnvelope(
670
+ sessionId,
671
+ content,
672
+ sessionId,
673
+ {
674
+ role: "assistant"
675
+ },
676
+ subagentMetadata
677
+ )
629
678
  );
630
679
  }
631
680
  }
632
681
  break;
633
682
  }
634
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();
635
692
  for (const block of message.message.content) {
636
693
  if (isToolResultBlock(block)) {
637
694
  const content = {
@@ -641,9 +698,15 @@ function toMessageEnvelopes(message, sessionId) {
641
698
  is_error: Boolean(block.is_error)
642
699
  };
643
700
  envelopes.push(
644
- createEnvelope(sessionId, content, sessionId, {
645
- role: "assistant"
646
- })
701
+ createEnvelope(
702
+ sessionId,
703
+ content,
704
+ sessionId,
705
+ {
706
+ role: "assistant"
707
+ },
708
+ subagentMetadata
709
+ )
647
710
  );
648
711
  } else if (isTextBlock(block)) {
649
712
  const content = {
@@ -651,9 +714,15 @@ function toMessageEnvelopes(message, sessionId) {
651
714
  text: block.text
652
715
  };
653
716
  envelopes.push(
654
- createEnvelope(sessionId, content, sessionId, {
655
- role: "user"
656
- })
717
+ createEnvelope(
718
+ sessionId,
719
+ content,
720
+ sessionId,
721
+ {
722
+ role: "user"
723
+ },
724
+ subagentMetadata
725
+ )
657
726
  );
658
727
  }
659
728
  }
@@ -666,6 +735,13 @@ function toMessageEnvelopes(message, sessionId) {
666
735
  type: "text",
667
736
  text: delta
668
737
  };
738
+ const streamPayloadMetadata = {
739
+ stream_delta: true
740
+ };
741
+ const subagentMeta = buildSubagentPayloadMetadata();
742
+ if (subagentMeta) {
743
+ Object.assign(streamPayloadMetadata, subagentMeta);
744
+ }
669
745
  envelopes.push(
670
746
  createEnvelope(
671
747
  sessionId,
@@ -674,9 +750,7 @@ function toMessageEnvelopes(message, sessionId) {
674
750
  {
675
751
  role: "assistant"
676
752
  },
677
- {
678
- stream_delta: true
679
- }
753
+ streamPayloadMetadata
680
754
  )
681
755
  );
682
756
  }
@@ -783,6 +857,7 @@ var Session = class {
783
857
  sessionInitSubscribers = [];
784
858
  queue = [];
785
859
  generating = false;
860
+ waitingForMessage = null;
786
861
  announcementPromise = null;
787
862
  announced = false;
788
863
  shouldSummarize = false;
@@ -804,19 +879,29 @@ var Session = class {
804
879
  sessionId: this.id,
805
880
  queueLength: this.queue.length
806
881
  });
807
- if (!this.generating && !this.ai.isGenerating()) {
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()) {
808
889
  this.startGenerate();
809
890
  }
810
891
  }
811
892
  async *[Symbol.asyncIterator]() {
812
893
  while (true) {
813
- if (this.queue.length === 0) {
814
- logger.debug("[Session] Emptied queue", {
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", {
815
899
  sessionId: this.id
816
900
  });
817
- return;
901
+ batch = await new Promise((resolve) => {
902
+ this.waitingForMessage = resolve;
903
+ });
818
904
  }
819
- const batch = this.queue.splice(0, this.queue.length);
820
905
  logger.debug("[Session] Yielding batch to SDK", {
821
906
  sessionId: this.id,
822
907
  batchSize: batch.length
@@ -849,28 +934,40 @@ var Session = class {
849
934
  const summaryTextBlocks = [];
850
935
  let resultMessage = null;
851
936
  await this.emitProgress("running", "Generating response");
852
- for await (const message of this.ai.streamUserMessage(this, {
853
- ...this.sdkSessionIdentifier ? { resume: this.sdkSessionIdentifier } : {}
854
- })) {
855
- this.captureSdkSessionId(message);
856
- if (message.type === "assistant") {
857
- this.collectAssistantSummaryBlocks(message, summaryTextBlocks);
858
- }
859
- if (message.type === "result") {
860
- resultMessage = message;
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
+ }
861
959
  }
862
- for (const envelope of toMessageEnvelopes(message, this.id)) {
863
- await this.dispatchEnvelope(envelope);
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;
864
965
  }
966
+ throw error;
865
967
  }
866
- this.resetRetryState();
867
- const complete = await this.buildCompletionEnvelope(resultMessage, summaryTextBlocks);
868
- await Promise.all([
869
- this.emitProgress("idle", "Ready for next message"),
870
- this.dispatchEnvelope(complete)
871
- ]);
872
968
  } finally {
873
969
  this.generating = false;
970
+ this.waitingForMessage = null;
874
971
  logger.debug("[Session] Generation finished", {
875
972
  sessionId: this.id,
876
973
  pendingMessages: this.queue.length
@@ -2690,6 +2787,33 @@ var AgentOrchestrator = class {
2690
2787
  commit_sha: commitSha
2691
2788
  };
2692
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
+ }
2693
2817
  return {
2694
2818
  type: "git_write",
2695
2819
  success: true,
@@ -3653,7 +3777,7 @@ var control_message_schema_default = {
3653
3777
  },
3654
3778
  GitWriteRequest: {
3655
3779
  additionalProperties: false,
3656
- 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',
3657
3781
  properties: {
3658
3782
  type: {
3659
3783
  const: "git_write",
@@ -3665,7 +3789,8 @@ var control_message_schema_default = {
3665
3789
  enum: [
3666
3790
  "commit",
3667
3791
  "commit_push",
3668
- "commit_push_pr"
3792
+ "commit_push_pr",
3793
+ "commit_checkout"
3669
3794
  ],
3670
3795
  title: "Action",
3671
3796
  type: "string"
@@ -4747,12 +4872,23 @@ var machine_message_schema_default = {
4747
4872
  type: "string"
4748
4873
  },
4749
4874
  method: {
4750
- const: "start_session",
4875
+ enum: [
4876
+ "start_session",
4877
+ "stop_session"
4878
+ ],
4751
4879
  title: "Method",
4752
4880
  type: "string"
4753
4881
  },
4754
4882
  payload: {
4755
- $ref: "#/$defs/MachineStartSessionPayload"
4883
+ anyOf: [
4884
+ {
4885
+ $ref: "#/$defs/MachineStartSessionPayload"
4886
+ },
4887
+ {
4888
+ $ref: "#/$defs/MachineStopSessionPayload"
4889
+ }
4890
+ ],
4891
+ title: "Payload"
4756
4892
  }
4757
4893
  },
4758
4894
  required: [
@@ -4926,6 +5062,22 @@ var machine_message_schema_default = {
4926
5062
  title: "MachineStatus",
4927
5063
  type: "string"
4928
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
+ },
4929
5081
  WorkspaceConfig: {
4930
5082
  additionalProperties: false,
4931
5083
  description: "Workspace-level configuration persisted alongside metadata.",
@@ -9908,7 +10060,7 @@ function extractErrorMessage11(error) {
9908
10060
  // src/daemon/machine-supervisor.ts
9909
10061
  import { randomUUID as randomUUID7 } from "node:crypto";
9910
10062
  import os5 from "node:os";
9911
- import { setTimeout as delay4 } from "node:timers/promises";
10063
+ import { setTimeout as delay5 } from "node:timers/promises";
9912
10064
  import WebSocket3 from "ws";
9913
10065
  import * as Sentry21 from "@sentry/node";
9914
10066
 
@@ -17735,9 +17887,11 @@ async function ensureWorkspace(client, accessToken, payload) {
17735
17887
 
17736
17888
  // src/daemon/session-runner.ts
17737
17889
  import { spawn as spawn3 } from "node:child_process";
17890
+ import { setTimeout as delay4 } from "node:timers/promises";
17738
17891
  import * as Sentry20 from "@sentry/node";
17739
17892
  import fs11 from "node:fs";
17740
17893
  import path13 from "node:path";
17894
+ var GRACEFUL_STOP_DELAY_MS = 2e3;
17741
17895
  var SessionManager = class {
17742
17896
  backendUrl;
17743
17897
  frontendUrl;
@@ -17770,6 +17924,47 @@ var SessionManager = class {
17770
17924
  throw error;
17771
17925
  }
17772
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
+ }
17773
17968
  };
17774
17969
  var SessionRunner = class {
17775
17970
  spec;
@@ -17900,7 +18095,7 @@ async function createSessionLogPath(workspaceId, launchId) {
17900
18095
 
17901
18096
  // src/package-info.ts
17902
18097
  var isBundled = true;
17903
- var PACKAGE_VERSION = isBundled ? "0.5.3" : "0.0.0-dev";
18098
+ var PACKAGE_VERSION = isBundled ? "0.6.0" : "0.0.0-dev";
17904
18099
  var PACKAGE_DESCRIPTION = isBundled ? "Command-line interface for Omnara." : "Command-line interface for Omnara.";
17905
18100
 
17906
18101
  // src/daemon/machine-supervisor.ts
@@ -18009,13 +18204,13 @@ var MachineSupervisor = class {
18009
18204
  } else {
18010
18205
  this.logger.debug("Machine supervisor still waiting for login\u2026");
18011
18206
  }
18012
- await delay4(LOGIN_WAIT_DELAY_MS2);
18207
+ await delay5(LOGIN_WAIT_DELAY_MS2);
18013
18208
  } else {
18014
18209
  Sentry21.captureException(error);
18015
18210
  this.logger.warn(
18016
18211
  `Machine supervisor error: ${error.message}. Retrying in ${Math.round(retryDelayMs / 1e3)}s.`
18017
18212
  );
18018
- await delay4(retryDelayMs);
18213
+ await delay5(retryDelayMs);
18019
18214
  retryDelayMs = calculateBackoff(retryDelayMs);
18020
18215
  }
18021
18216
  }
@@ -18172,12 +18367,18 @@ var MachineSupervisor = class {
18172
18367
  }
18173
18368
  }
18174
18369
  async handleRpcRequest(message) {
18175
- if (message.method !== "start_session") {
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 {
18176
18375
  await this.sendRpcResponse(message.request_id, "error", `Unknown method ${message.method}`);
18177
- return;
18178
18376
  }
18377
+ }
18378
+ async handleStartSession(message) {
18179
18379
  try {
18180
- const result = await this.launchWorkspaceSessionFromRpc(message.payload);
18380
+ const payload = message.payload;
18381
+ const result = await this.launchWorkspaceSessionFromRpc(payload);
18181
18382
  await this.sendRpcResponse(message.request_id, "ok", void 0, {
18182
18383
  launch_id: result.launchId,
18183
18384
  user_session_id: result.userSessionId,
@@ -18196,6 +18397,25 @@ var MachineSupervisor = class {
18196
18397
  await this.sendRpcResponse(message.request_id, "error", messageText);
18197
18398
  }
18198
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
+ }
18199
18419
  async sendRpcResponse(requestId, status, error, body) {
18200
18420
  const response = {
18201
18421
  type: "rpc_response",
@@ -18317,7 +18537,7 @@ var MachineSupervisor = class {
18317
18537
  return;
18318
18538
  }
18319
18539
  }
18320
- await delay4(250);
18540
+ await delay5(250);
18321
18541
  }
18322
18542
  throw new Error("Machine has not finished registering yet. Try again in a few seconds.");
18323
18543
  }
@@ -18606,6 +18826,44 @@ function compareVersions(v1, v2) {
18606
18826
  }
18607
18827
  return 0;
18608
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
+ }
18609
18867
  async function runInstallScript(options) {
18610
18868
  const os6 = platform();
18611
18869
  const silent = options?.silent ?? false;
@@ -18649,6 +18907,38 @@ async function runInstallScript(options) {
18649
18907
  return false;
18650
18908
  }
18651
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
+ }
18652
18942
  async function checkForUpdate() {
18653
18943
  const currentVersion = PACKAGE_VERSION;
18654
18944
  if (!currentVersion) {
@@ -18704,6 +18994,9 @@ async function attemptUpdate() {
18704
18994
  if (!currentVersion) {
18705
18995
  return false;
18706
18996
  }
18997
+ if (isNpmInstallation()) {
18998
+ return await handleNpmInstallation();
18999
+ }
18707
19000
  const updateInfo = await checkForUpdate();
18708
19001
  if (!updateInfo.available) {
18709
19002
  const channel = IS_PRERELEASE ? " (prerelease channel)" : "";