Skip to content

Commit 153fa84

Browse files
committed
Add tmux availability check for conditional interactive_bash tool registration
- Implement getTmuxPath() utility to detect tmux availability at plugin load time - Add getCachedTmuxPath() for retrieving cached tmux path - Add startBackgroundCheck() for asynchronous tmux detection - Conditionally register interactive_bash tool only when tmux is available - Silently skip registration without error messages if tmux not found - Export utilities from tools/interactive-bash/index.ts Tool now gracefully handles systems without tmux installed. 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
1 parent 2d2834f commit 153fa84

File tree

5 files changed

+84
-5
lines changed

5 files changed

+84
-5
lines changed

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
getCurrentSessionTitle,
4545
} from "./features/claude-code-session-state";
4646
import { updateTerminalTitle } from "./features/terminal";
47-
import { builtinTools, createCallOmoAgent, createBackgroundTools, createLookAt } from "./tools";
47+
import { builtinTools, createCallOmoAgent, createBackgroundTools, createLookAt, interactive_bash, getTmuxPath } from "./tools";
4848
import { BackgroundManager } from "./features/background-agent";
4949
import { createBuiltinMcps } from "./mcp";
5050
import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig, type HookName } from "./config";
@@ -263,6 +263,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
263263
? await createGoogleAntigravityAuthPlugin(ctx)
264264
: null;
265265

266+
const tmuxAvailable = await getTmuxPath();
267+
266268
return {
267269
...(googleAuthHooks ? { auth: googleAuthHooks.auth } : {}),
268270

@@ -271,6 +273,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
271273
...backgroundTools,
272274
call_omo_agent: callOmoAgent,
273275
look_at: lookAt,
276+
...(tmuxAvailable ? { interactive_bash } : {}),
274277
},
275278

276279
"chat.message": async (input, output) => {

src/tools/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import { grep } from "./grep"
2121
import { glob } from "./glob"
2222
import { slashcommand } from "./slashcommand"
2323
import { skill } from "./skill"
24-
import { interactive_bash } from "./interactive-bash"
24+
25+
export { interactive_bash, startBackgroundCheck as startTmuxCheck } from "./interactive-bash"
26+
export { getTmuxPath } from "./interactive-bash/utils"
2527

2628
import {
2729
createBackgroundTask,
@@ -63,5 +65,4 @@ export const builtinTools = {
6365
glob,
6466
slashcommand,
6567
skill,
66-
interactive_bash,
6768
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import { interactive_bash } from "./tools"
2+
import { startBackgroundCheck } from "./utils"
23

3-
export { interactive_bash }
4+
export { interactive_bash, startBackgroundCheck }

src/tools/interactive-bash/tools.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { tool } from "@opencode-ai/plugin/tool"
22
import { DEFAULT_TIMEOUT_MS, INTERACTIVE_BASH_DESCRIPTION } from "./constants"
3+
import { getCachedTmuxPath } from "./utils"
34

45
/**
56
* Quote-aware command tokenizer with escape handling
@@ -53,13 +54,15 @@ export const interactive_bash = tool({
5354
},
5455
execute: async (args) => {
5556
try {
57+
const tmuxPath = getCachedTmuxPath() ?? "tmux"
58+
5659
const parts = tokenizeCommand(args.tmux_command)
5760

5861
if (parts.length === 0) {
5962
return "Error: Empty tmux command"
6063
}
6164

62-
const proc = Bun.spawn(["tmux", ...parts], {
65+
const proc = Bun.spawn([tmuxPath, ...parts], {
6366
stdout: "pipe",
6467
stderr: "pipe",
6568
})
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { spawn } from "bun"
2+
3+
let tmuxPath: string | null = null
4+
let initPromise: Promise<string | null> | null = null
5+
6+
async function findTmuxPath(): Promise<string | null> {
7+
const isWindows = process.platform === "win32"
8+
const cmd = isWindows ? "where" : "which"
9+
10+
try {
11+
const proc = spawn([cmd, "tmux"], {
12+
stdout: "pipe",
13+
stderr: "pipe",
14+
})
15+
16+
const exitCode = await proc.exited
17+
if (exitCode !== 0) {
18+
return null
19+
}
20+
21+
const stdout = await new Response(proc.stdout).text()
22+
const path = stdout.trim().split("\n")[0]
23+
24+
if (!path) {
25+
return null
26+
}
27+
28+
const verifyProc = spawn([path, "-V"], {
29+
stdout: "pipe",
30+
stderr: "pipe",
31+
})
32+
33+
const verifyExitCode = await verifyProc.exited
34+
if (verifyExitCode !== 0) {
35+
return null
36+
}
37+
38+
return path
39+
} catch {
40+
return null
41+
}
42+
}
43+
44+
export async function getTmuxPath(): Promise<string | null> {
45+
if (tmuxPath !== null) {
46+
return tmuxPath
47+
}
48+
49+
if (initPromise) {
50+
return initPromise
51+
}
52+
53+
initPromise = (async () => {
54+
const path = await findTmuxPath()
55+
tmuxPath = path
56+
return path
57+
})()
58+
59+
return initPromise
60+
}
61+
62+
export function getCachedTmuxPath(): string | null {
63+
return tmuxPath
64+
}
65+
66+
export function startBackgroundCheck(): void {
67+
if (!initPromise) {
68+
initPromise = getTmuxPath()
69+
initPromise.catch(() => {})
70+
}
71+
}

0 commit comments

Comments
 (0)