Skip to content

Commit 48a5d34

Browse files
authored
Merge pull request #53 from obviousbread/feat/omp-support
feat: add Oh My Pi (omp) support
2 parents 2b854f6 + 078e605 commit 48a5d34

11 files changed

Lines changed: 423 additions & 12 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ cn usage status
5151

5252
## Features
5353

54-
- **Multi-tool support** - Claude Code, OpenAI Codex, Google Gemini CLI
54+
- **Multi-tool support** - Claude Code, OpenAI Codex, Google Gemini CLI, Oh My Pi (omp)
5555
- **Works everywhere** - Terminal, VSCode, Cursor, or any editor
5656
- **Cross-platform** - macOS, Linux, Windows
5757
- **Native notifications** - Uses system notification APIs
@@ -145,6 +145,7 @@ npm packages are published with GitHub Actions Trusted Publisher and npm provena
145145
| `cn on claude` | Enable for Claude Code only |
146146
| `cn on codex` | Enable for Codex only |
147147
| `cn on gemini` | Enable for Gemini CLI only |
148+
| `cn on omp` | Enable for Oh My Pi (omp) only |
148149
| `cn off` | Disable notifications |
149150
| `cn off all` | Explicit alias for disabling all tools |
150151
| `cn test` | Send test notification |
@@ -173,10 +174,13 @@ Code-Notify uses the hook systems built into AI coding tools:
173174
- **Claude Code**: `~/.claude/settings.json`
174175
- **Codex**: `~/.codex/config.toml`
175176
- **Gemini CLI**: `~/.gemini/settings.json`
177+
- **Oh My Pi (omp)**: `~/.omp/agent/extensions/code-notify.js`
176178

177179
For Codex, Code-Notify configures `notify = ["/absolute/path/to/notifier.sh", "codex"]` and reads the JSON payload Codex appends on completion.
178180
Codex currently exposes completion events through `notify`; approval and `request_permissions` prompts do not currently arrive through this hook.
179181

182+
For omp, Code-Notify writes a small managed extension module to `~/.omp/agent/extensions/code-notify.js`, because omp loads extension modules instead of reading hook commands from a config file. The extension forwards omp's `agent_end` event to the same `notifier.sh`, so sound, voice, Slack/Discord channels, click-through, the global mute switch, and rate limiting all work unchanged. Like Codex, omp currently exposes completion events; approval and idle prompts are not yet wired.
183+
180184
When enabled, it adds hooks that call the notification script when tasks complete:
181185

182186
```json

lib/code-notify/commands/global.sh

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ enable_notifications_global() {
373373

374374
if [[ -z "$installed_tools" ]]; then
375375
warning "No supported AI tools detected"
376-
info "Supported tools: Claude Code, Codex, Gemini CLI"
376+
info "Supported tools: Claude Code, Codex, Gemini CLI, Oh My Pi (omp)"
377377
return 1
378378
fi
379379

@@ -440,6 +440,7 @@ enable_single_tool() {
440440
"claude") config_file="$GLOBAL_SETTINGS_FILE" ;;
441441
"codex") config_file="$CODEX_CONFIG_FILE" ;;
442442
"gemini") config_file="$GEMINI_SETTINGS_FILE" ;;
443+
"omp") config_file="$OMP_EXTENSION_FILE" ;;
443444
esac
444445

445446
success "$tool: ENABLED"
@@ -470,7 +471,7 @@ disable_notifications_global() {
470471
# No tool specified - disable all enabled tools
471472
local disabled_count=0
472473

473-
for t in claude codex gemini; do
474+
for t in claude codex gemini omp; do
474475
if is_tool_enabled "$t"; then
475476
if disable_single_tool "$t" "quiet"; then
476477
((disabled_count++))
@@ -580,6 +581,19 @@ show_status() {
580581
echo " ${DIM}- Gemini CLI: not installed${RESET}"
581582
fi
582583

584+
# Oh My Pi (omp)
585+
if is_tool_installed "omp"; then
586+
if is_tool_enabled "omp"; then
587+
echo " ${CHECK_MARK} omp: ${GREEN}ENABLED${RESET}"
588+
echo " Config: $OMP_EXTENSION_FILE"
589+
echo " Events: completion via agent_end extension"
590+
else
591+
echo " ${MUTE} omp: ${DIM}DISABLED${RESET}"
592+
fi
593+
else
594+
echo " ${DIM}- omp: not installed${RESET}"
595+
fi
596+
583597
# Voice status
584598
echo ""
585599
if is_voice_enabled "global"; then
@@ -937,12 +951,13 @@ show_voice_status() {
937951
fi
938952

939953
# Per-tool voice
940-
for tool in claude codex gemini; do
954+
for tool in claude codex gemini omp; do
941955
local tool_display
942956
case "$tool" in
943957
"claude") tool_display="Claude" ;;
944958
"codex") tool_display="Codex" ;;
945959
"gemini") tool_display="Gemini" ;;
960+
"omp") tool_display="omp" ;;
946961
esac
947962

948963
if is_voice_enabled "tool" "$tool"; then

lib/code-notify/core/config.sh

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ CODEX_CONFIG_FILE="$CODEX_HOME/config.toml"
5454
GEMINI_HOME="${GEMINI_HOME:-$HOME/.gemini}"
5555
GEMINI_SETTINGS_FILE="$GEMINI_HOME/settings.json"
5656

57+
# Oh My Pi (omp) paths
58+
OMP_HOME="${OMP_HOME:-$HOME/.omp}"
59+
OMP_EXTENSIONS_DIR="${OMP_EXTENSIONS_DIR:-$OMP_HOME/agent/extensions}"
60+
OMP_EXTENSION_FILE="$OMP_EXTENSIONS_DIR/code-notify.js"
61+
OMP_EXTENSION_MARKER="code-notify: managed omp extension"
62+
5763
# Ensure config directory exists
5864
ensure_config_dir() {
5965
mkdir -p "$CONFIG_DIR" "$BACKUP_DIR"
@@ -1551,6 +1557,95 @@ PYTHON
15511557
fi
15521558
}
15531559

1560+
# ============================================
1561+
# Oh My Pi (omp) integration
1562+
# ============================================
1563+
# omp loads JS/TS extension modules from ~/.omp/agent/extensions instead of
1564+
# reading hook commands from a config file. So enabling = writing a managed
1565+
# extension that forwards omp's agent_end event to notifier.sh; disabling =
1566+
# removing that file. All downstream delivery (sound, voice, channels,
1567+
# click-through, kill switch, rate limiting) is reused through notifier.sh.
1568+
1569+
# Generate the omp extension module that forwards completion events to the notifier
1570+
generate_omp_extension() {
1571+
local notify_script="$1"
1572+
# Escape backslashes and double quotes for safe embedding in the JS string literal
1573+
notify_script="${notify_script//\\/\\\\}"
1574+
notify_script="${notify_script//\"/\\\"}"
1575+
cat << EOF
1576+
// ${OMP_EXTENSION_MARKER}
1577+
// Created by \`cn on omp\`; removed by \`cn off omp\`. Do not edit -- regenerated on enable.
1578+
// omp (Oh My Pi) loads extension modules instead of config-file hooks, so this
1579+
// file forwards the "agent finished" event to code-notify's notifier.
1580+
const NOTIFIER = "${notify_script}";
1581+
1582+
export default function codeNotify(pi) {
1583+
pi.on("agent_end", async (event, ctx) => {
1584+
// Only ping the top-level interactive session; skip subagents/headless runs
1585+
// unless OMP_NOTIFY_ALL=1 is set.
1586+
if (!ctx.hasUI && process.env.OMP_NOTIFY_ALL !== "1") return;
1587+
// Map the final stop reason to the notifier hook type so a failed run produces an
1588+
// error notification instead of a false success. Scan back to the last assistant
1589+
// turn: the array can end in a tool-result or custom (bash/compaction) message
1590+
// that would otherwise mask the outcome.
1591+
const messages = Array.isArray(event?.messages) ? event.messages : [];
1592+
let reason;
1593+
for (let i = messages.length - 1; i >= 0; i--) {
1594+
if (messages[i] && messages[i].role === "assistant") { reason = messages[i].stopReason; break; }
1595+
}
1596+
const hook = (reason === "error" || reason === "aborted" || reason === "length")
1597+
? "error" : "stop";
1598+
try {
1599+
// Pass the project name via cwd so the notifier derives it from basename(\$PWD),
1600+
// keeping the notification scoped globally (not project-scoped) so that
1601+
// \`cn off\` / the global kill switch correctly suppresses it.
1602+
await pi.exec(NOTIFIER, [hook, "omp"], { timeout: 5000, cwd: ctx.cwd || undefined });
1603+
} catch (err) {
1604+
// Never let notification failures disrupt the session.
1605+
pi.logger?.debug?.(\`code-notify: notifier exec failed: \${err}\`);
1606+
}
1607+
});
1608+
}
1609+
EOF
1610+
}
1611+
1612+
# Check if omp notifications are enabled
1613+
is_omp_enabled() {
1614+
[[ -f "$OMP_EXTENSION_FILE" ]] && grep -q "$OMP_EXTENSION_MARKER" "$OMP_EXTENSION_FILE" 2>/dev/null
1615+
}
1616+
1617+
# Enable omp notifications by writing a managed extension module
1618+
enable_omp_hooks() {
1619+
local notify_script
1620+
notify_script="$(get_notify_script)"
1621+
1622+
# Refuse to overwrite a file we didn't create (consistent with disable_omp_hooks which
1623+
# preserves non-managed files). Check before creating any dirs so a refused enable
1624+
# leaves the filesystem untouched.
1625+
if [[ -f "$OMP_EXTENSION_FILE" ]] && ! grep -q "$OMP_EXTENSION_MARKER" "$OMP_EXTENSION_FILE" 2>/dev/null; then
1626+
error "Refusing to overwrite existing non-code-notify extension: $OMP_EXTENSION_FILE"
1627+
info "Remove or rename that file manually, then run \`cn on omp\` again."
1628+
return 1
1629+
fi
1630+
1631+
mkdir -p "$OMP_EXTENSIONS_DIR"
1632+
atomic_write "$OMP_EXTENSION_FILE" "$(generate_omp_extension "$notify_script")"
1633+
}
1634+
1635+
# Disable omp notifications by removing our managed extension module
1636+
disable_omp_hooks() {
1637+
if [[ ! -f "$OMP_EXTENSION_FILE" ]]; then
1638+
return 0
1639+
fi
1640+
1641+
# Only remove our own managed file. Never touch ~/.omp/agent or its subdirs --
1642+
# that is omp's data directory (agent.db, sessions, config.yml, ...), not ours.
1643+
if grep -q "$OMP_EXTENSION_MARKER" "$OMP_EXTENSION_FILE" 2>/dev/null; then
1644+
rm -f "$OMP_EXTENSION_FILE"
1645+
fi
1646+
return 0
1647+
}
1648+
15541649
# ============================================
15551650
# Multi-tool helpers
15561651
# ============================================
@@ -1569,6 +1664,9 @@ enable_tool() {
15691664
"gemini")
15701665
enable_gemini_hooks
15711666
;;
1667+
"omp")
1668+
enable_omp_hooks
1669+
;;
15721670
*)
15731671
return 1
15741672
;;
@@ -1589,6 +1687,9 @@ disable_tool() {
15891687
"gemini")
15901688
disable_gemini_hooks
15911689
;;
1690+
"omp")
1691+
disable_omp_hooks
1692+
;;
15921693
*)
15931694
return 1
15941695
;;
@@ -1609,6 +1710,9 @@ is_tool_enabled() {
16091710
"gemini")
16101711
is_gemini_enabled
16111712
;;
1713+
"omp")
1714+
is_omp_enabled
1715+
;;
16121716
*)
16131717
return 1
16141718
;;

lib/code-notify/core/notifier.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ get_tool_display_name() {
128128
"claude") echo "Claude" ;;
129129
"codex") echo "Codex" ;;
130130
"gemini") echo "Gemini" ;;
131+
"omp") echo "omp" ;;
131132
*) echo "AI" ;;
132133
esac
133134
}

lib/code-notify/utils/detect.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ detect_gemini_cli() {
121121
return 1
122122
}
123123

124+
# Detect Oh My Pi (omp) installation.
125+
# Binary-only, like detect_codex / detect_gemini_cli: a directory is not a reliable
126+
# "installed" signal here. ~/.omp/agent is omp's data dir (agent.db, sessions, ...) --
127+
# it persists after the binary is uninstalled, and code-notify itself creates the
128+
# extensions/ subdir on enable, so keying on it gives false positives. The legacy `pi`
129+
# alias is intentionally NOT accepted: `pi` is a common generic binary name and would
130+
# misfire. OMP_HOME is still honoured for the echoed config location.
131+
detect_omp() {
132+
if command -v omp &> /dev/null; then
133+
echo "${OMP_HOME:-$HOME/.omp}"
134+
return 0
135+
fi
136+
return 1
137+
}
138+
124139
# Get list of all installed AI coding tools
125140
get_installed_tools() {
126141
local tools=()
@@ -137,6 +152,10 @@ get_installed_tools() {
137152
tools+=("gemini")
138153
fi
139154

155+
if detect_omp &> /dev/null; then
156+
tools+=("omp")
157+
fi
158+
140159
# Return space-separated list
141160
echo "${tools[*]}"
142161
}
@@ -155,6 +174,9 @@ is_tool_installed() {
155174
"gemini")
156175
detect_gemini_cli &> /dev/null
157176
;;
177+
"omp")
178+
detect_omp &> /dev/null
179+
;;
158180
*)
159181
return 1
160182
;;

lib/code-notify/utils/help.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ show_help() {
1414
${BOLD}Code-Notify${RESET} - Desktop notifications for AI coding tools
1515
1616
${BOLD}SUPPORTED TOOLS:${RESET}
17-
Claude Code, OpenAI Codex, Google Gemini CLI
17+
Claude Code, OpenAI Codex, Google Gemini CLI, Oh My Pi (omp)
1818
1919
${BOLD}USAGE:${RESET}
2020
$cmd_name <command> [tool]
2121
2222
${BOLD}COMMANDS:${RESET}
2323
${GREEN}on${RESET} Enable notifications (all detected tools)
2424
${GREEN}on${RESET} all Enable notifications (explicit alias for all detected tools)
25-
${GREEN}on${RESET} <tool> Enable for specific tool (claude/codex/gemini)
25+
${GREEN}on${RESET} <tool> Enable for specific tool (claude/codex/gemini/omp)
2626
${GREEN}off${RESET} Disable notifications (all tools)
2727
${GREEN}off${RESET} all Disable notifications (explicit alias for all tools)
2828
${GREEN}off${RESET} <tool> Disable for specific tool
@@ -51,6 +51,7 @@ ${BOLD}TOOL NAMES:${RESET}
5151
${CYAN}claude${RESET} Claude Code
5252
${CYAN}codex${RESET} OpenAI Codex CLI
5353
${CYAN}gemini${RESET} Google Gemini CLI
54+
${CYAN}omp${RESET} Oh My Pi (omp)
5455
5556
${BOLD}PROJECT COMMANDS:${RESET}
5657
${GREEN}project on${RESET} Enable for current project
@@ -67,6 +68,7 @@ ${BOLD}ALERT TYPES:${RESET}
6768
Claude events: ${CYAN}SubagentStart${RESET}, ${CYAN}SubagentStop${RESET}, ${CYAN}TeammateIdle${RESET}, ${CYAN}TaskCreated${RESET}, ${CYAN}TaskCompleted${RESET}
6869
Note: alert-type matching applies to Claude Code and Gemini CLI hooks.
6970
Codex currently exposes completion events through its notify payload.
71+
omp exposes completion events through a generated extension module.
7072
7173
${BOLD}VOICE COMMANDS:${RESET}
7274
${GREEN}voice on${RESET} Enable voice for all tools

lib/code-notify/utils/voice.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ disable_voice() {
7272
rm -f "$VOICE_DIR/voice-claude"
7373
rm -f "$VOICE_DIR/voice-codex"
7474
rm -f "$VOICE_DIR/voice-gemini"
75+
rm -f "$VOICE_DIR/voice-omp"
7576
;;
7677
"global"|*)
7778
rm -f "$GLOBAL_VOICE_FILE"

scripts/install-windows.ps1

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1908,24 +1908,31 @@ function Invoke-CodeNotify {
19081908
)
19091909
19101910
$toolCommands = @("claude", "codex", "gemini")
1911+
$unsupportedTools = @("omp")
19111912
19121913
switch ($Command.ToLower()) {
19131914
"on" {
1914-
if ($SubCommand -and ($toolCommands -contains $SubCommand.ToLower())) {
1915+
if ($SubCommand -and ($unsupportedTools -contains $SubCommand.ToLower())) {
1916+
Write-Warning "omp is not yet supported on Windows. Windows support is planned."
1917+
} elseif ($SubCommand -and ($toolCommands -contains $SubCommand.ToLower())) {
19151918
Enable-Notifications -Tool $SubCommand
19161919
} else {
19171920
Enable-Notifications
19181921
}
19191922
}
19201923
"off" {
1921-
if ($SubCommand -and ($toolCommands -contains $SubCommand.ToLower())) {
1924+
if ($SubCommand -and ($unsupportedTools -contains $SubCommand.ToLower())) {
1925+
Write-Warning "omp is not yet supported on Windows. Windows support is planned."
1926+
} elseif ($SubCommand -and ($toolCommands -contains $SubCommand.ToLower())) {
19221927
Disable-Notifications -Tool $SubCommand
19231928
} else {
19241929
Disable-Notifications
19251930
}
19261931
}
19271932
"status" {
1928-
if ($SubCommand -and ($toolCommands -contains $SubCommand.ToLower())) {
1933+
if ($SubCommand -and ($unsupportedTools -contains $SubCommand.ToLower())) {
1934+
Write-Warning "omp is not yet supported on Windows. Windows support is planned."
1935+
} elseif ($SubCommand -and ($toolCommands -contains $SubCommand.ToLower())) {
19291936
Show-Status -Tool $SubCommand
19301937
} else {
19311938
Show-Status

scripts/run_tests.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,14 @@ else
251251
test_fail "ask_user alert preservation failed"
252252
fi
253253

254+
# Test 24: omp (Oh My Pi) extension install/detect/notify
255+
test_start "omp extension support"
256+
if bash tests/test-omp-extension.sh >/dev/null 2>&1; then
257+
test_pass
258+
else
259+
test_fail "omp extension support failed"
260+
fi
261+
254262
# Summary
255263
echo ""
256264
echo "Test Summary:"

0 commit comments

Comments
 (0)