@oh-my-pi/pi-coding-agent 15.10.3 → 15.10.5

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.
Files changed (161) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/types/capability/rule-buckets.d.ts +1 -1
  3. package/dist/types/capability/rule.d.ts +6 -1
  4. package/dist/types/cli/update-cli.d.ts +11 -1
  5. package/dist/types/config/model-registry.d.ts +18 -1
  6. package/dist/types/discovery/at-imports.d.ts +15 -0
  7. package/dist/types/edit/diff.d.ts +3 -2
  8. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
  9. package/dist/types/eval/__tests__/js-context-manager.test.d.ts +1 -0
  10. package/dist/types/eval/backend.d.ts +7 -0
  11. package/dist/types/eval/bridge-timeout.d.ts +1 -1
  12. package/dist/types/eval/{llm-bridge.d.ts → completion-bridge.d.ts} +8 -8
  13. package/dist/types/eval/idle-timeout.d.ts +1 -1
  14. package/dist/types/eval/js/context-manager.d.ts +1 -0
  15. package/dist/types/eval/js/executor.d.ts +2 -0
  16. package/dist/types/eval/js/index.d.ts +1 -1
  17. package/dist/types/eval/js/shared/helpers.d.ts +6 -0
  18. package/dist/types/eval/js/shared/runtime.d.ts +5 -0
  19. package/dist/types/eval/js/worker-protocol.d.ts +6 -0
  20. package/dist/types/eval/py/executor.d.ts +7 -0
  21. package/dist/types/eval/py/index.d.ts +1 -1
  22. package/dist/types/export/ttsr.d.ts +14 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +8 -1
  24. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
  25. package/dist/types/internal-urls/local-protocol.d.ts +10 -0
  26. package/dist/types/mcp/oauth-flow.d.ts +2 -2
  27. package/dist/types/modes/components/custom-editor.d.ts +3 -0
  28. package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
  29. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  30. package/dist/types/modes/components/status-line/types.d.ts +31 -2
  31. package/dist/types/modes/image-references.d.ts +8 -3
  32. package/dist/types/modes/interactive-mode.d.ts +1 -1
  33. package/dist/types/modes/theme/theme.d.ts +2 -1
  34. package/dist/types/modes/types.d.ts +2 -1
  35. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  36. package/dist/types/session/agent-session.d.ts +0 -2
  37. package/dist/types/tools/ask.d.ts +1 -0
  38. package/dist/types/tools/browser/tab-worker.d.ts +15 -0
  39. package/dist/types/tools/index.d.ts +17 -0
  40. package/dist/types/tools/render-utils.d.ts +1 -1
  41. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  42. package/dist/types/utils/block-context.d.ts +35 -0
  43. package/dist/types/utils/image-loading.d.ts +12 -0
  44. package/package.json +29 -9
  45. package/src/capability/rule-buckets.ts +4 -2
  46. package/src/capability/rule.ts +10 -1
  47. package/src/cli/auth-broker-cli.ts +6 -7
  48. package/src/cli/auth-gateway-cli.ts +1 -1
  49. package/src/cli/list-models.ts +5 -0
  50. package/src/cli/update-cli.ts +138 -16
  51. package/src/config/model-registry.ts +81 -2
  52. package/src/debug/index.ts +4 -8
  53. package/src/discovery/at-imports.ts +273 -0
  54. package/src/discovery/builtin-rules/index.ts +4 -0
  55. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  56. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  57. package/src/discovery/helpers.ts +2 -1
  58. package/src/edit/diff.ts +114 -4
  59. package/src/edit/hashline/diff.ts +1 -1
  60. package/src/edit/hashline/execute.ts +1 -1
  61. package/src/edit/modes/patch.ts +6 -2
  62. package/src/edit/modes/replace.ts +1 -1
  63. package/src/edit/renderer.ts +12 -2
  64. package/src/eval/__tests__/agent-bridge.test.ts +13 -0
  65. package/src/eval/__tests__/{llm-bridge.test.ts → completion-bridge.test.ts} +60 -54
  66. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  67. package/src/eval/__tests__/js-context-manager.test.ts +241 -0
  68. package/src/eval/agent-bridge.ts +6 -1
  69. package/src/eval/backend.ts +15 -0
  70. package/src/eval/bridge-timeout.ts +1 -1
  71. package/src/eval/{llm-bridge.ts → completion-bridge.ts} +30 -27
  72. package/src/eval/idle-timeout.ts +1 -1
  73. package/src/eval/js/context-manager.ts +70 -8
  74. package/src/eval/js/executor.ts +3 -0
  75. package/src/eval/js/index.ts +7 -1
  76. package/src/eval/js/shared/helpers.ts +53 -6
  77. package/src/eval/js/shared/prelude.txt +4 -4
  78. package/src/eval/js/shared/runtime.ts +8 -0
  79. package/src/eval/js/tool-bridge.ts +3 -3
  80. package/src/eval/js/worker-core.ts +1 -0
  81. package/src/eval/js/worker-entry.ts +6 -0
  82. package/src/eval/js/worker-protocol.ts +6 -0
  83. package/src/eval/py/executor.ts +12 -0
  84. package/src/eval/py/index.ts +7 -1
  85. package/src/eval/py/prelude.py +46 -7
  86. package/src/eval/py/runner.py +1 -0
  87. package/src/exa/render.ts +1 -1
  88. package/src/export/ttsr.ts +122 -1
  89. package/src/extensibility/extensions/types.ts +8 -1
  90. package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
  91. package/src/extensibility/plugins/doctor.ts +1 -1
  92. package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
  93. package/src/goals/tools/goal-tool.ts +1 -1
  94. package/src/internal-urls/docs-index.generated.ts +8 -6
  95. package/src/internal-urls/local-protocol.ts +13 -0
  96. package/src/lsp/render.ts +8 -6
  97. package/src/mcp/oauth-flow.ts +3 -3
  98. package/src/mcp/render.ts +7 -1
  99. package/src/modes/components/custom-editor.ts +12 -6
  100. package/src/modes/components/login-dialog.ts +1 -1
  101. package/src/modes/components/oauth-selector.ts +4 -4
  102. package/src/modes/components/read-tool-group.ts +10 -3
  103. package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
  104. package/src/modes/components/status-line/index.ts +1 -0
  105. package/src/modes/components/status-line/types.ts +23 -8
  106. package/src/modes/components/tips.txt +1 -1
  107. package/src/modes/components/tool-execution.ts +1 -1
  108. package/src/modes/components/transcript-container.ts +17 -10
  109. package/src/modes/components/user-message.ts +6 -3
  110. package/src/modes/components/welcome.ts +1 -1
  111. package/src/modes/controllers/extension-ui-controller.ts +143 -127
  112. package/src/modes/controllers/input-controller.ts +36 -10
  113. package/src/modes/controllers/mcp-command-controller.ts +28 -12
  114. package/src/modes/controllers/selector-controller.ts +4 -11
  115. package/src/modes/controllers/ssh-command-controller.ts +2 -2
  116. package/src/modes/image-references.ts +13 -7
  117. package/src/modes/interactive-mode.ts +2 -2
  118. package/src/modes/rpc/rpc-mode.ts +1 -1
  119. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
  120. package/src/modes/theme/theme.ts +95 -1
  121. package/src/modes/types.ts +2 -1
  122. package/src/modes/utils/ui-helpers.ts +14 -5
  123. package/src/prompts/system/tiny-title-system.md +1 -1
  124. package/src/prompts/system/title-system.md +16 -3
  125. package/src/prompts/system/workflow-notice.md +1 -1
  126. package/src/prompts/tools/bash.md +1 -1
  127. package/src/prompts/tools/eval.md +6 -6
  128. package/src/sdk.ts +31 -14
  129. package/src/session/agent-session.ts +213 -155
  130. package/src/session/session-manager.ts +1 -1
  131. package/src/slash-commands/builtin-registry.ts +1 -1
  132. package/src/system-prompt.ts +15 -9
  133. package/src/task/render.ts +20 -8
  134. package/src/tools/ask.ts +14 -5
  135. package/src/tools/bash-interactive.ts +1 -1
  136. package/src/tools/bash.ts +14 -2
  137. package/src/tools/browser/render.ts +5 -2
  138. package/src/tools/browser/tab-worker.ts +211 -91
  139. package/src/tools/debug.ts +5 -2
  140. package/src/tools/eval-render.ts +8 -5
  141. package/src/tools/eval.ts +2 -2
  142. package/src/tools/gh-renderer.ts +29 -15
  143. package/src/tools/index.ts +32 -0
  144. package/src/tools/inspect-image-renderer.ts +12 -5
  145. package/src/tools/job.ts +9 -6
  146. package/src/tools/memory-render.ts +19 -5
  147. package/src/tools/read.ts +165 -18
  148. package/src/tools/render-utils.ts +3 -1
  149. package/src/tools/resolve.ts +1 -1
  150. package/src/tools/review.ts +1 -1
  151. package/src/tools/ssh.ts +4 -1
  152. package/src/tools/todo.ts +8 -1
  153. package/src/tools/tool-timeouts.ts +1 -1
  154. package/src/tools/write.ts +1 -1
  155. package/src/tui/code-cell.ts +1 -1
  156. package/src/utils/block-context.ts +312 -0
  157. package/src/utils/image-loading.ts +31 -1
  158. package/src/utils/title-generator.ts +2 -2
  159. package/src/web/search/providers/codex.ts +1 -1
  160. package/src/web/search/render.ts +14 -6
  161. /package/dist/types/eval/__tests__/{llm-bridge.test.d.ts → completion-bridge.test.d.ts} +0 -0
@@ -184,7 +184,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
184
184
  showNewVersionNotification(newVersion: string): void;
185
185
  clearEditor(): void;
186
186
  updatePendingMessagesDisplay(): void;
187
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
187
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void;
188
188
  flushCompactionQueue(options?: {
189
189
  willRetry?: boolean;
190
190
  }): Promise<void>;
@@ -6,7 +6,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
6
6
  /**
7
7
  * All available symbol keys organized by category.
8
8
  */
9
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.agents" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.editing" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers";
9
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.agents" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.editing" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal";
10
10
  export type SpinnerType = "status" | "activity";
11
11
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "pythonMode" | "statusLineSep" | "statusLineModel" | "statusLinePath" | "statusLineGitClean" | "statusLineGitDirty" | "statusLineContext" | "statusLineSpend" | "statusLineStaged" | "statusLineDirty" | "statusLineUntracked" | "statusLineOutput" | "statusLineCost" | "statusLineSubagents";
12
12
  /** Check if a string is a valid ThemeColor value */
@@ -80,6 +80,7 @@ export declare class Theme {
80
80
  running: string;
81
81
  shadowed: string;
82
82
  aborted: string;
83
+ done: string;
83
84
  };
84
85
  get nav(): {
85
86
  cursor: string;
@@ -28,6 +28,7 @@ import type { Theme } from "./theme/theme";
28
28
  export type CompactionQueuedMessage = {
29
29
  text: string;
30
30
  mode: "steer" | "followUp";
31
+ images?: ImageContent[];
31
32
  };
32
33
  export type SubmittedUserInput = {
33
34
  text: string;
@@ -155,7 +156,7 @@ export interface InteractiveModeContext {
155
156
  showNewVersionNotification(newVersion: string): void;
156
157
  clearEditor(): void;
157
158
  updatePendingMessagesDisplay(): void;
158
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
159
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void;
159
160
  flushCompactionQueue(options?: {
160
161
  willRetry?: boolean;
161
162
  }): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
- import type { AssistantMessage, Message } from "@oh-my-pi/pi-ai";
2
+ import type { AssistantMessage, ImageContent, Message } from "@oh-my-pi/pi-ai";
3
3
  import { type Component } from "@oh-my-pi/pi-tui";
4
4
  import type { InteractiveModeContext } from "../../modes/types";
5
5
  import type { SessionContext } from "../../session/session-manager";
@@ -42,7 +42,7 @@ export declare class UiHelpers {
42
42
  showWarning(warningMessage: string): void;
43
43
  showNewVersionNotification(newVersion: string): void;
44
44
  updatePendingMessagesDisplay(): void;
45
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
45
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void;
46
46
  isKnownSlashCommand(text: string): boolean;
47
47
  flushCompactionQueue(options?: {
48
48
  willRetry?: boolean;
@@ -299,8 +299,6 @@ export interface FreshSessionResult {
299
299
  sessionId: string;
300
300
  closedProviderSessions: number;
301
301
  }
302
- export declare const ANTHROPIC_TOOL_CALL_BATCH_CAP = 4;
303
- export declare function resolveToolCallBatchCapForModel(model: Model | undefined): number | undefined;
304
302
  export declare class AgentSession {
305
303
  #private;
306
304
  readonly agent: Agent;
@@ -79,6 +79,7 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
79
79
  }, z.core.$strip>>;
80
80
  }, z.core.$strip>;
81
81
  readonly strict = true;
82
+ readonly concurrency = "exclusive";
82
83
  readonly loadMode = "discoverable";
83
84
  constructor(session: ToolSession);
84
85
  static createIf(session: ToolSession): AskTool | null;
@@ -11,9 +11,24 @@ declare global {
11
11
  elementFromPoint(x: number, y: number): Element | null;
12
12
  };
13
13
  }
14
+ interface ScreenshotOptions {
15
+ selector?: string;
16
+ fullPage?: boolean;
17
+ save?: string;
18
+ silent?: boolean;
19
+ }
20
+ export interface InflightOp {
21
+ label: string;
22
+ startedAt: number;
23
+ }
24
+ /** Human-readable label for a screenshot op, used in op tracking + timeout errors. */
25
+ export declare function describeScreenshot(opts?: ScreenshotOptions): string;
26
+ /** Summarize still-running helpers (oldest first) so a cell timeout names what stalled. */
27
+ export declare function describeInflight(inflight: Map<number, InflightOp>): string;
14
28
  export declare class WorkerCore {
15
29
  #private;
16
30
  constructor(transport: Transport);
17
31
  nextElementId(): number;
18
32
  cacheElement(id: number, handle: ElementHandle): void;
19
33
  }
34
+ export {};
@@ -287,6 +287,23 @@ export declare const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[];
287
287
  * otherwise `DEFAULT_ESSENTIAL_TOOL_NAMES`.
288
288
  */
289
289
  export declare function computeEssentialBuiltinNames(settings: Settings): string[];
290
+ /**
291
+ * Filter the initial active tool set when `tools.discoveryMode === "all"`.
292
+ *
293
+ * Non-essential discoverable built-ins are hidden — the model rediscovers them
294
+ * via `search_tool_bm25` and activates them on demand. A tool survives hiding
295
+ * when it is essential, explicitly requested, restored from a prior selection,
296
+ * or required by a forced tool_choice feature (`forceActive`). The last case is
297
+ * load-bearing: a named tool_choice (e.g. the eager `todo` prelude) must
298
+ * reference a tool present in the request, or the provider rejects it with 400.
299
+ */
300
+ export declare function filterInitialToolsForDiscoveryAll(initialToolNames: string[], opts: {
301
+ loadModeOf: (name: string) => BuiltinToolLoadMode | undefined;
302
+ essentialNames: ReadonlySet<string>;
303
+ explicitlyRequested: ReadonlySet<string>;
304
+ restored: ReadonlySet<string>;
305
+ forceActive: ReadonlySet<string>;
306
+ }): string[];
290
307
  /**
291
308
  * Public callable factory map. External callers may invoke `BUILTIN_TOOLS.read(session)` or
292
309
  * `BUILTIN_TOOLS[name](session)` to construct a tool directly.
@@ -114,7 +114,7 @@ export declare function formatErrorDetail(message: string | undefined, theme: Th
114
114
  export declare function formatEmptyMessage(message: string, theme: Theme): string;
115
115
  export type CodeFrameMarker = "" | " " | "*" | "+" | "-" | ">";
116
116
  export declare function formatCodeFrameLine(marker: CodeFrameMarker, lineNumber: string | number, content: string, lineNumberWidth: number): string;
117
- export type ToolUIStatus = "success" | "error" | "warning" | "info" | "pending" | "running" | "aborted";
117
+ export type ToolUIStatus = "success" | "done" | "error" | "warning" | "info" | "pending" | "running" | "aborted";
118
118
  export type ToolUIColor = "success" | "error" | "warning" | "accent" | "muted";
119
119
  export interface ToolUITitleOptions {
120
120
  bold?: boolean;
@@ -15,7 +15,7 @@ export declare const TOOL_TIMEOUTS: {
15
15
  readonly eval: {
16
16
  readonly default: 30;
17
17
  readonly min: 1;
18
- readonly max: 600;
18
+ readonly max: 3600;
19
19
  };
20
20
  readonly browser: {
21
21
  readonly default: 30;
@@ -0,0 +1,35 @@
1
+ export interface LineSpan {
2
+ startLine: number;
3
+ endLine: number;
4
+ }
5
+ /** Where the source came from, so tree-sitter can pick a grammar. */
6
+ export interface BlockContextSource {
7
+ path?: string;
8
+ lang?: string;
9
+ }
10
+ export type LineEntry = {
11
+ kind: "line";
12
+ lineNumber: number;
13
+ text: string;
14
+ context: boolean;
15
+ } | {
16
+ kind: "ellipsis";
17
+ };
18
+ /**
19
+ * Resolve the off-window boundary lines for a visible window: tree-sitter
20
+ * syntactic spans first (covers brace and indentation languages), falling back
21
+ * to a lexical bracket scan when the grammar is unavailable. Returns a map of
22
+ * `lineNumber → source text` for the lines to surface, never including a line
23
+ * already visible.
24
+ */
25
+ export declare function findBlockContextLines(fullLines: readonly string[], visibleInput: ReadonlySet<number> | readonly number[], source?: BlockContextSource): Map<number, string>;
26
+ /**
27
+ * Build display entries for `visibleSpans` plus any off-window block-boundary
28
+ * lines, in source order, with `{ kind: "ellipsis" }` markers inserted across
29
+ * non-contiguous gaps. `options.lineText` lets callers substitute display text
30
+ * (e.g. column-truncated lines) for a given line number.
31
+ */
32
+ export declare function buildLineEntriesWithBlockContext(fullLines: readonly string[], visibleSpans: readonly LineSpan[], source?: BlockContextSource, options?: {
33
+ lineText?: (lineNumber: number, sourceText: string, context: boolean) => string;
34
+ }): LineEntry[];
35
+ export declare function lineEntriesToPlainText(entries: readonly LineEntry[], ellipsis?: string): string;
@@ -1,4 +1,5 @@
1
1
  import type { ImageContent } from "@oh-my-pi/pi-ai";
2
+ import { type ImageResizeOptions } from "./image-resize";
2
3
  export declare const MAX_IMAGE_INPUT_BYTES: number;
3
4
  export declare const SUPPORTED_INPUT_IMAGE_MIME_TYPES: Set<string>;
4
5
  export interface LoadImageInputOptions {
@@ -23,4 +24,15 @@ export declare class ImageInputTooLargeError extends Error {
23
24
  constructor(bytes: number, maxBytes: number);
24
25
  }
25
26
  export declare function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null>;
27
+ export interface NormalizeModelContextImagesOptions {
28
+ resize?: ImageResizeOptions;
29
+ }
30
+ /**
31
+ * Normalize image blocks before they enter agent/model context. This keeps
32
+ * provider request construction from having to resize an unbounded batch of
33
+ * large images on the streaming hot path. Images are processed sequentially on
34
+ * purpose: `resizeImage` may fan out multiple encoders for one image, so the
35
+ * outer image batch must stay bounded.
36
+ */
37
+ export declare function normalizeModelContextImages(images: ImageContent[] | undefined, options?: NormalizeModelContextImagesOptions): Promise<ImageContent[] | undefined>;
26
38
  export declare function loadImageInput(options: LoadImageInputOptions): Promise<LoadedImageInput | null>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.10.3",
4
+ "version": "15.10.5",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,14 +47,14 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.10.3",
51
- "@oh-my-pi/omp-stats": "15.10.3",
52
- "@oh-my-pi/pi-agent-core": "15.10.3",
53
- "@oh-my-pi/pi-ai": "15.10.3",
54
- "@oh-my-pi/pi-mnemopi": "15.10.3",
55
- "@oh-my-pi/pi-natives": "15.10.3",
56
- "@oh-my-pi/pi-tui": "15.10.3",
57
- "@oh-my-pi/pi-utils": "15.10.3",
50
+ "@oh-my-pi/hashline": "15.10.5",
51
+ "@oh-my-pi/omp-stats": "15.10.5",
52
+ "@oh-my-pi/pi-agent-core": "15.10.5",
53
+ "@oh-my-pi/pi-ai": "15.10.5",
54
+ "@oh-my-pi/pi-mnemopi": "15.10.5",
55
+ "@oh-my-pi/pi-natives": "15.10.5",
56
+ "@oh-my-pi/pi-tui": "15.10.5",
57
+ "@oh-my-pi/pi-utils": "15.10.5",
58
58
  "@opentelemetry/api": "^1.9.1",
59
59
  "@opentelemetry/context-async-hooks": "^2.7.1",
60
60
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -134,6 +134,14 @@
134
134
  "types": "./dist/types/cli/*.d.ts",
135
135
  "import": "./src/cli/*.ts"
136
136
  },
137
+ "./cli/gallery-fixtures": {
138
+ "types": "./dist/types/cli/gallery-fixtures/index.d.ts",
139
+ "import": "./src/cli/gallery-fixtures/index.ts"
140
+ },
141
+ "./cli/gallery-fixtures/*": {
142
+ "types": "./dist/types/cli/gallery-fixtures/*.d.ts",
143
+ "import": "./src/cli/gallery-fixtures/*.ts"
144
+ },
137
145
  "./cli/commands/*": {
138
146
  "types": "./dist/types/cli/commands/*.d.ts",
139
147
  "import": "./src/cli/commands/*.ts"
@@ -442,6 +450,14 @@
442
450
  "types": "./dist/types/modes/controllers/*.d.ts",
443
451
  "import": "./src/modes/controllers/*.ts"
444
452
  },
453
+ "./modes/setup-wizard": {
454
+ "types": "./dist/types/modes/setup-wizard/index.d.ts",
455
+ "import": "./src/modes/setup-wizard/index.ts"
456
+ },
457
+ "./modes/setup-wizard/*": {
458
+ "types": "./dist/types/modes/setup-wizard/*.d.ts",
459
+ "import": "./src/modes/setup-wizard/*.ts"
460
+ },
445
461
  "./modes/rpc/*": {
446
462
  "types": "./dist/types/modes/rpc/*.d.ts",
447
463
  "import": "./src/modes/rpc/*.ts"
@@ -499,6 +515,10 @@
499
515
  "types": "./dist/types/task/*.d.ts",
500
516
  "import": "./src/task/*.ts"
501
517
  },
518
+ "./tool-discovery/*": {
519
+ "types": "./dist/types/tool-discovery/*.d.ts",
520
+ "import": "./src/tool-discovery/*.ts"
521
+ },
502
522
  "./tools": {
503
523
  "types": "./dist/types/tools/index.d.ts",
504
524
  "import": "./src/tools/index.ts"
@@ -6,7 +6,7 @@
6
6
  * manager, and splits the rest into the always-apply and rulebook buckets.
7
7
  *
8
8
  * Bucket precedence (matches docs/rulebook-matching-pipeline.md §5):
9
- * 1. TTSR — non-empty `condition` that `TtsrManager.addRule` accepts
9
+ * 1. TTSR — non-empty `condition`/`astCondition` that `TtsrManager.addRule` accepts
10
10
  * 2. always — `alwaysApply === true`
11
11
  * 3. rulebook — has a `description`
12
12
  */
@@ -49,7 +49,9 @@ export function bucketRules(
49
49
  if (disabled.has(rule.name)) continue;
50
50
  if (!includeBuiltin && rule._source?.provider === BUILTIN_DEFAULTS_PROVIDER_ID) continue;
51
51
 
52
- const isTtsrRule = rule.condition && rule.condition.length > 0 ? ttsrManager.addRule(rule) : false;
52
+ const hasTtsrCondition =
53
+ (rule.condition && rule.condition.length > 0) || (rule.astCondition && rule.astCondition.length > 0);
54
+ const isTtsrRule = hasTtsrCondition ? ttsrManager.addRule(rule) : false;
53
55
  if (isTtsrRule) continue;
54
56
  if (rule.alwaysApply === true) {
55
57
  alwaysApplyRules.push(rule);
@@ -26,6 +26,8 @@ export interface RuleFrontmatter {
26
26
  alwaysApply?: boolean;
27
27
  /** New key for TTSR match conditions. */
28
28
  condition?: string | string[];
29
+ /** TTSR match condition(s) expressed as ast-grep patterns (edit/write streams only). */
30
+ astCondition?: string | string[];
29
31
  /** New key for TTSR stream scope. */
30
32
  scope?: string | string[];
31
33
  /** Per-rule TTSR interrupt mode override. */
@@ -51,6 +53,8 @@ export interface Rule {
51
53
  description?: string;
52
54
  /** Regex condition(s) that can trigger TTSR interruption. */
53
55
  condition?: string[];
56
+ /** ast-grep pattern condition(s) that can trigger TTSR interruption (edit/write streams only). */
57
+ astCondition?: string[];
54
58
  /** Optional stream scope tokens (for example: text, thinking, tool:edit(*.ts)). */
55
59
  scope?: string[];
56
60
  /** Per-rule TTSR interrupt mode override (falls back to global ttsr.interruptMode). */
@@ -188,10 +192,14 @@ function isLikelyFileGlob(value: string): boolean {
188
192
  * - legacy `ttsr_trigger` / `ttsrTrigger` are accepted as a `condition` fallback
189
193
  * - condition tokens that look like file globs become scope shorthands:
190
194
  * `*.rs` => `tool:edit(*.rs)`, `tool:write(*.rs)` and a catch-all condition `.*`
195
+ * - `astCondition` holds ast-grep patterns and is kept verbatim (no glob inference)
191
196
  */
192
- export function parseRuleConditionAndScope(frontmatter: RuleFrontmatter): Pick<Rule, "condition" | "scope"> {
197
+ export function parseRuleConditionAndScope(
198
+ frontmatter: RuleFrontmatter,
199
+ ): Pick<Rule, "condition" | "astCondition" | "scope"> {
193
200
  const rawCondition = frontmatter.condition ?? frontmatter.ttsr_trigger ?? frontmatter.ttsrTrigger;
194
201
  const parsedCondition = normalizeRuleField(rawCondition);
202
+ const astCondition = normalizeRuleField(frontmatter.astCondition);
195
203
  const parsedScope = normalizeScopeField(frontmatter.scope);
196
204
 
197
205
  const inferredScope: string[] = [];
@@ -213,6 +221,7 @@ export function parseRuleConditionAndScope(frontmatter: RuleFrontmatter): Pick<R
213
221
  const scope = [...(parsedScope ?? []), ...inferredScope];
214
222
  return {
215
223
  condition: condition.length > 0 ? Array.from(new Set(condition)) : undefined,
224
+ astCondition,
216
225
  scope: scope.length > 0 ? Array.from(new Set(scope)) : undefined,
217
226
  };
218
227
  }
@@ -30,6 +30,7 @@ import {
30
30
  type OAuthCredential,
31
31
  type OAuthProvider,
32
32
  type OAuthProviderInfo,
33
+ PROVIDER_REGISTRY,
33
34
  SqliteAuthCredentialStore,
34
35
  startAuthBroker,
35
36
  } from "@oh-my-pi/pi-ai";
@@ -75,13 +76,11 @@ const ACTIONS: readonly AuthBrokerAction[] = [
75
76
  ];
76
77
 
77
78
  /** Callback ports baked from the per-provider OAuth flow modules. */
78
- const CALLBACK_PORTS: Record<string, number> = {
79
- anthropic: 54545,
80
- "openai-codex": 1455,
81
- "google-gemini-cli": 8085,
82
- "google-antigravity": 51121,
83
- "gitlab-duo": 8080,
84
- };
79
+ const CALLBACK_PORTS: Record<string, number> = Object.fromEntries(
80
+ PROVIDER_REGISTRY.flatMap(provider =>
81
+ provider.callbackPort != null ? [[provider.id, provider.callbackPort] as [string, number]] : [],
82
+ ),
83
+ );
85
84
 
86
85
  function getTokenFilePath(): string {
87
86
  return path.join(getConfigRootDir(), "auth-broker.token");
@@ -357,7 +357,7 @@ export async function runAuthGatewayCommand(cmd: AuthGatewayCommandArgs): Promis
357
357
  /**
358
358
  * Providers whose chat endpoint expects a JSON-serialized credential blob
359
359
  * (`{ token, projectId, refreshToken, expiresAt, … }`) rather than the raw
360
- * access token. Mirrors `getOAuthApiKey` in `packages/ai/src/utils/oauth`.
360
+ * access token. Mirrors `getOAuthApiKey` in `packages/ai/src/registry/oauth`.
361
361
  */
362
362
  const STRUCTURED_API_KEY_PROVIDERS: ReadonlySet<string> = new Set([
363
363
  "github-copilot",
@@ -189,6 +189,11 @@ export async function runListModelsCommand(options: RunListModelsOptions): Promi
189
189
  modelRegistry.registerProvider(name, config, sourceId);
190
190
  }
191
191
  extensionsResult.runtime.pendingProviderRegistrations = [];
192
+ // Discover runtime (extension) provider catalogs now that they are registered.
193
+ // The full refresh in main.ts ran before extensions loaded, so this is the only
194
+ // point where extension-contributed dynamic providers get discovered. Cache-aware
195
+ // so it reuses the shared 24 h model cache instead of refetching every invocation.
196
+ await modelRegistry.refreshRuntimeProviders("online-if-uncached");
192
197
 
193
198
  await listModels(modelRegistry, searchPattern);
194
199
  }
@@ -2,9 +2,10 @@
2
2
  * Update CLI command handler.
3
3
  *
4
4
  * Handles `omp update` to check for and install updates.
5
- * Uses bun if available, otherwise downloads binary from GitHub releases.
5
+ * Uses the installer that owns the active omp executable when it can be detected.
6
6
  */
7
7
  import * as fs from "node:fs";
8
+ import * as os from "node:os";
8
9
  import * as path from "node:path";
9
10
  import { pipeline } from "node:stream/promises";
10
11
  import { $which, APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
@@ -14,6 +15,8 @@ import { theme } from "../modes/theme/theme";
14
15
 
15
16
  const REPO = "can1357/oh-my-pi";
16
17
  const PACKAGE = "@oh-my-pi/pi-coding-agent";
18
+ const HOMEBREW_FORMULA = "can1357/tap/omp";
19
+ const MISE_TOOL = "github:can1357/oh-my-pi";
17
20
  /**
18
21
  * Official npm registry origin.
19
22
  *
@@ -102,6 +105,46 @@ async function getBunGlobalBinDir(): Promise<string | undefined> {
102
105
  }
103
106
  }
104
107
 
108
+ async function getHomebrewFormulaPrefix(): Promise<string | undefined> {
109
+ if (!$which("brew")) return undefined;
110
+ for (const formula of [HOMEBREW_FORMULA, APP_NAME]) {
111
+ try {
112
+ const result = await $`brew --prefix ${formula}`.quiet().nothrow();
113
+ if (result.exitCode !== 0) continue;
114
+ const output = result.text().trim();
115
+ if (output.length > 0) return output;
116
+ } catch {}
117
+ }
118
+ return undefined;
119
+ }
120
+
121
+ async function getMiseBinDirs(): Promise<string[]> {
122
+ if (!$which("mise")) return [];
123
+ try {
124
+ const result = await $`mise bin-paths ${MISE_TOOL}`.quiet().nothrow();
125
+ if (result.exitCode !== 0) return [];
126
+ return result
127
+ .text()
128
+ .split(/\r?\n/)
129
+ .map(line => line.trim())
130
+ .filter(line => line.length > 0);
131
+ } catch {
132
+ return [];
133
+ }
134
+ }
135
+
136
+ function getMiseDataDir(): string {
137
+ const override = process.env.MISE_DATA_DIR;
138
+ if (override && override.length > 0) return override;
139
+ if (process.platform === "win32") {
140
+ const localAppData = process.env.LOCALAPPDATA;
141
+ if (localAppData && localAppData.length > 0) return path.join(localAppData, "mise");
142
+ }
143
+ const xdgDataHome = process.env.XDG_DATA_HOME;
144
+ if (xdgDataHome && xdgDataHome.length > 0) return path.join(xdgDataHome, "mise");
145
+ return path.join(os.homedir(), ".local", "share", "mise");
146
+ }
147
+
105
148
  function normalizePathForComparison(filePath: string): string {
106
149
  const normalized = path.normalize(filePath);
107
150
  if (process.platform === "win32") return normalized.toLowerCase();
@@ -129,34 +172,61 @@ function isPathInDirectory(filePath: string, directoryPath: string): boolean {
129
172
  // is a junction when Bun is installed via Scoop, so `bun pm bin -g` and the
130
173
  // PATH-resolved omp path can refer to the same directory through different
131
174
  // strings. path.resolve does not traverse junctions/symlinks; realpath does.
132
- // Resolve the file's parent directory to tolerate the file itself not yet
133
- // existing (e.g. a fresh install path) while still catching link-traversed
134
- // equality once the directory exists.
135
- const fileDir = tryRealpath(path.dirname(path.resolve(filePath)));
175
+ // Resolve both the file and its parent directory: the file catches manager
176
+ // links like Homebrew's `bin/omp -> Cellar/.../bin/omp`; the parent fallback
177
+ // still tolerates fresh install paths where the file does not exist yet.
136
178
  const dirReal = tryRealpath(path.resolve(directoryPath));
137
- if (!fileDir || !dirReal) return false;
179
+ if (!dirReal) return false;
180
+ const fileReal = tryRealpath(path.resolve(filePath));
181
+ if (fileReal && isPathInDirectoryLexical(fileReal, dirReal)) return true;
182
+ const fileDir = tryRealpath(path.dirname(path.resolve(filePath)));
183
+ if (!fileDir) return false;
138
184
  const resolvedFile = path.join(fileDir, path.basename(filePath));
139
185
  return isPathInDirectoryLexical(resolvedFile, dirReal);
140
186
  }
141
187
 
142
- type UpdateTarget = { method: "bun" } | { method: "binary"; path: string };
188
+ type UpdateMethod = "brew" | "mise" | "bun" | "binary";
189
+
190
+ interface UpdateMethodResolutionOptions {
191
+ homebrewPrefix?: string;
192
+ miseBinDirs?: readonly string[];
193
+ miseDataDir?: string;
194
+ }
143
195
 
144
- function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
145
- if (!bunBinDir) return "binary";
146
- return isPathInDirectory(ompPath, bunBinDir) ? "bun" : "binary";
196
+ type UpdateTarget = { method: "brew" } | { method: "mise" } | { method: "bun" } | { method: "binary"; path: string };
197
+
198
+ function resolveUpdateMethod(
199
+ ompPath: string,
200
+ bunBinDir: string | undefined,
201
+ options: UpdateMethodResolutionOptions = {},
202
+ ): UpdateMethod {
203
+ const { homebrewPrefix, miseBinDirs = [], miseDataDir } = options;
204
+ if (homebrewPrefix && isPathInDirectory(ompPath, path.join(homebrewPrefix, "bin"))) return "brew";
205
+ if (miseBinDirs.some(dir => isPathInDirectory(ompPath, dir))) return "mise";
206
+ if (miseDataDir && isPathInDirectory(ompPath, path.join(miseDataDir, "shims"))) return "mise";
207
+ if (bunBinDir && isPathInDirectory(ompPath, bunBinDir)) return "bun";
208
+ return "binary";
147
209
  }
148
210
 
149
- export function resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
150
- return resolveUpdateMethod(ompPath, bunBinDir);
211
+ export function resolveUpdateMethodForTest(
212
+ ompPath: string,
213
+ bunBinDir: string | undefined,
214
+ options: UpdateMethodResolutionOptions = {},
215
+ ): UpdateMethod {
216
+ return resolveUpdateMethod(ompPath, bunBinDir, options);
151
217
  }
152
218
  async function resolveUpdateTarget(): Promise<UpdateTarget> {
153
219
  const bunBinDir = await getBunGlobalBinDir();
220
+ const homebrewPrefix = await getHomebrewFormulaPrefix();
221
+ const miseAvailable = $which("mise") !== undefined;
222
+ const miseBinDirs = miseAvailable ? await getMiseBinDirs() : [];
223
+ const miseDataDir = miseAvailable ? getMiseDataDir() : undefined;
154
224
  const ompPath = resolveOmpPath();
155
225
 
156
226
  if (ompPath) {
157
- const method = resolveUpdateMethod(ompPath, bunBinDir);
158
- if (method === "bun") return { method };
159
- return { method, path: ompPath };
227
+ const method = resolveUpdateMethod(ompPath, bunBinDir, { homebrewPrefix, miseBinDirs, miseDataDir });
228
+ if (method === "binary") return { method, path: ompPath };
229
+ return { method };
160
230
  }
161
231
 
162
232
  if (bunBinDir) return { method: "bun" };
@@ -376,6 +446,18 @@ export function buildBunInstallArgs(expectedVersion: string, nativeTag: string =
376
446
  return args;
377
447
  }
378
448
 
449
+ export function buildHomebrewUpdateArgs(force: boolean): string[] {
450
+ return [force ? "reinstall" : "upgrade", HOMEBREW_FORMULA];
451
+ }
452
+
453
+ export function buildMiseUpgradeArgs(): string[] {
454
+ return ["upgrade", MISE_TOOL, "--bump"];
455
+ }
456
+
457
+ export function buildMiseForceInstallArgs(expectedVersion: string): string[] {
458
+ return ["install", "--force", `${MISE_TOOL}@${expectedVersion}`];
459
+ }
460
+
379
461
  /**
380
462
  * Update via bun package manager.
381
463
  */
@@ -390,6 +472,42 @@ async function updateViaBun(expectedVersion: string): Promise<void> {
390
472
  await printVerification(expectedVersion);
391
473
  }
392
474
 
475
+ async function updateViaHomebrew(expectedVersion: string, force: boolean): Promise<void> {
476
+ console.log(chalk.dim("Updating Homebrew formulae..."));
477
+ const update = await $`brew update`.nothrow();
478
+ if (update.exitCode !== 0) {
479
+ throw new Error(`brew update failed with exit code ${update.exitCode}`);
480
+ }
481
+
482
+ console.log(chalk.dim("Updating via Homebrew..."));
483
+ const args = buildHomebrewUpdateArgs(force);
484
+ const result = await $`brew ${args}`.nothrow();
485
+ if (result.exitCode !== 0) {
486
+ throw new Error(`brew ${args[0]} failed with exit code ${result.exitCode}`);
487
+ }
488
+
489
+ await printVerification(expectedVersion);
490
+ }
491
+
492
+ async function updateViaMise(expectedVersion: string, force: boolean): Promise<void> {
493
+ console.log(chalk.dim("Updating via mise..."));
494
+ const args = buildMiseUpgradeArgs();
495
+ const result = await $`mise ${args}`.nothrow();
496
+ if (result.exitCode !== 0) {
497
+ throw new Error(`mise upgrade failed with exit code ${result.exitCode}`);
498
+ }
499
+
500
+ if (force) {
501
+ const forceArgs = buildMiseForceInstallArgs(expectedVersion);
502
+ const forceResult = await $`mise ${forceArgs}`.nothrow();
503
+ if (forceResult.exitCode !== 0) {
504
+ throw new Error(`mise install --force failed with exit code ${forceResult.exitCode}`);
505
+ }
506
+ }
507
+
508
+ await printVerification(expectedVersion);
509
+ }
510
+
393
511
  /**
394
512
  * Download a release binary to a target path, replacing an existing file.
395
513
  */
@@ -457,7 +575,11 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
457
575
  // Choose update method based on the prioritized omp binary in PATH
458
576
  try {
459
577
  const target = await resolveUpdateTarget();
460
- if (target.method === "bun") {
578
+ if (target.method === "brew") {
579
+ await updateViaHomebrew(release.version, opts.force);
580
+ } else if (target.method === "mise") {
581
+ await updateViaMise(release.version, opts.force);
582
+ } else if (target.method === "bun") {
461
583
  await updateViaBun(release.version);
462
584
  } else {
463
585
  await updateViaBinaryAt(target.path, release.version);