### Install OpenCode Workspace with Notify Source: https://github.com/kdcokenny/opencode-notify/blob/main/README.md This command installs the complete OpenCode workspace, which includes the opencode-notify plugin. This is an optional, all-in-one installation method. ```bash ocx add kdco/workspace --from https://registry.kdco.dev ``` -------------------------------- ### Manual Plugin Installation File Structure Source: https://github.com/kdcokenny/opencode-notify/blob/main/README.md If not using OCX, manually install the plugin by copying its files into the `.opencode/plugins/` directory. Ensure the exact multi-file layout is preserved. ```bash - .opencode/plugins/notify.ts - .opencode/plugins/notify/backend.ts - .opencode/plugins/notify/cmux.ts - .opencode/plugins/notify/status.ts - .opencode/plugins/notify/title.ts - .opencode/plugins/kdco-primitives/index.ts - .opencode/plugins/kdco-primitives/cmux.ts - .opencode/plugins/kdco-primitives/get-project-id.ts - .opencode/plugins/kdco-primitives/log-warn.ts - .opencode/plugins/kdco-primitives/mutex.ts - .opencode/plugins/kdco-primitives/shell.ts - .opencode/plugins/kdco-primitives/temp.ts - .opencode/plugins/kdco-primitives/terminal-detect.ts - .opencode/plugins/kdco-primitives/types.ts - .opencode/plugins/kdco-primitives/with-timeout.ts ``` -------------------------------- ### Install Alerter on macOS Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/errors.md Install the 'alerter' tool using Homebrew to enable notifications on macOS. Verify the installation by checking its path. ```bash brew install vjeantet/tap/alerter which alerter ``` -------------------------------- ### Install Alerter on macOS Source: https://github.com/kdcokenny/opencode-notify/blob/main/README.md On macOS 13+, install `vjeantet/alerter` using Homebrew to enable native notifications. Ensure `alerter` is in your PATH. ```bash brew install vjeantet/tap/alerter ``` -------------------------------- ### Cache File Format Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-getprojectid.md Provides examples of the content stored in the cache file, showing both the 40-character git ID and the 16-character path hash formats. ```plaintext abc123def456... (40 hex chars for git ID) ``` ```plaintext def456abc123... (16 hex chars for path hash) ``` -------------------------------- ### Example .git File in Worktree Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-getprojectid.md Illustrates the content of a .git file when it's a symbolic link or file pointing to the actual git directory in a worktree scenario. ```plaintext gitdir: /home/user/my-repo/.git/worktrees/feature ``` -------------------------------- ### Default NotifyPlugin Configuration Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/notify-plugin.md Example of the default configuration file structure for opencode-notify. All keys are optional. ```json { "notifyChildSessions": false, "terminal": "ghostty", "sounds": { "idle": "Glass", "error": "Basso", "permission": "Submarine", "question": "Submarine" }, "quietHours": { "enabled": false, "start": "22:00", "end": "08:00" } } ``` -------------------------------- ### Example Usage of sendNotificationWithFallback Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/backend-notifications.md Demonstrates how to use `sendNotificationWithFallback` with specific cmux and desktop notification functions. Ensure `sendCmuxNotification` and `sendDesktopNotificationByPlatform` are correctly implemented and imported. ```typescript await sendNotificationWithFallback({ preferCmux: true, tryCmuxNotify: () => sendCmuxNotification({ title: "Ready", body: "Task complete", }), sendDesktopNotification: () => sendDesktopNotificationByPlatform({ platform: process.platform, title: "Ready", message: "Task complete", sendNodeNotifierNotification: () => notifier.notify({}), }), }) ``` -------------------------------- ### Customized NotifyPlugin Configuration Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/notify-plugin.md Example of a customized configuration file to override default behaviors like notifying child sessions and enabling quiet hours. ```json { "notifyChildSessions": true, "quietHours": { "enabled": true, "start": "22:00", "end": "08:00" }, "sounds": { "idle": "Ping", "error": "Basso", "permission": "Submarine" } } ``` -------------------------------- ### Example: Git Command with Timeout Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-timeout.md Demonstrates using `withTimeout` to set a timeout for a `git` command executed via `Bun.spawn`. If the command times out, it kills the process and logs a message. Requires `kdco-primitives` to be imported. ```typescript import { withTimeout, TimeoutError } from "kdco-primitives" try { const proc = Bun.spawn(["git", "rev-list", "--max-parents=0", "--all"], { cwd: projectRoot, stdout: "pipe", }) const exitCode = await withTimeout( proc.exited, 5000, "git command timed out" ) if (exitCode === 0) { console.log("Success") } } catch (error) { if (error instanceof TimeoutError) { proc.kill() console.log(`Command didn't complete in ${error.timeoutMs}ms`) } } ``` -------------------------------- ### Example Usage of getProjectId Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-getprojectid.md Demonstrates how to use getProjectId with different directory types: a git repository, a git worktree, and a non-git directory, showing the expected output format for each. ```typescript // Git repository — returns stable root commit hash const projectId = await getProjectId("/home/user/my-repo") // Returns: "abc123def456..." (40 chars, git root commit) // Git worktree — resolves to shared .git, returns same ID as main worktree const projectId = await getProjectId("/home/user/my-repo/.git/worktrees/feature") // Returns: "abc123def456..." (same as parent repo) // Non-git directory — returns path hash const projectId = await getProjectId("/home/user/random-folder") // Returns: "def456abc123..." (16 chars, path hash) ``` -------------------------------- ### Example: Basic Usage of withTimeout Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-timeout.md Illustrates the basic usage of the `withTimeout` function to wrap a `fetchData` operation with a 5000ms timeout and a custom error message. ```typescript const result = await withTimeout( fetchData(), 5000, "Data fetch timed out" ) ``` -------------------------------- ### Example Usage of resolveCmuxNotificationCommand Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/cmux-notifications.md Demonstrates how to use `resolveCmuxNotificationCommand` to check for cmux availability and trust. If cmux is available, it sends a cmux notification; otherwise, it falls back to desktop notifications. ```typescript const cmuxPath = resolveCmuxNotificationCommand() if (cmuxPath) { // cmux is available and trusted await sendCmuxNotification({ title: "Ready", body: "Done" }) } else { // Fall back to desktop notifications await sendDesktopNotification({ title: "Ready", message: "Done" }) } ``` -------------------------------- ### OSC Title Integration Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/architecture.md Shows the state transitions for setting terminal titles using OSC sequences. The spinner cycles through Unicode characters to animate the 'busy' state. ```text Base title (from env) ↓ [parseOscTitleContext()] ↓ State: idle → "Project Name" State: busy → "⠋ Project Name" (animate) State: error → "✗ Project Name" ``` -------------------------------- ### Tool Execute Before Hook Input Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/events.md This represents the input to a tool lifecycle hook that triggers before a tool is executed. It specifically processes the 'question' tool, sending notifications and updating session status. ```javascript { tool: "question", sessionID: "abc123", callID: "call789", // ... other properties } ``` -------------------------------- ### Example: Multiple Operations with Timeouts Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-timeout.md Shows how to sequentially wrap multiple asynchronous operations (`fetch1`, `fetch2`) with individual timeouts using `withTimeout`. If any operation times out, it catches the `TimeoutError` and re-throws it. ```typescript async function runWithTimeouts() { try { const data1 = await withTimeout(fetch1(), 3000, "Fetch 1 timed out") const data2 = await withTimeout(fetch2(), 3000, "Fetch 2 timed out") return [data1, data2] } catch (error) { if (error instanceof TimeoutError) { console.log("One of the fetches timed out") } throw error } } ``` -------------------------------- ### Create a Mutex Instance Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-mutex.md Instantiates a new Mutex object. No parameters are required as it starts in an unlocked state. ```typescript const mutex = new Mutex() ``` -------------------------------- ### Example: Handling Timeout with Specific Message Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-timeout.md Shows how to handle a timeout for a `slowOperation` using `withTimeout`, including logging the timeout duration if a `TimeoutError` occurs, or logging other errors. ```typescript try { const result = await withTimeout(slowOperation(), 1000) console.log("Completed:", result) } catch (error) { if (error instanceof TimeoutError) { console.log(`Timed out after ${error.timeoutMs}ms`) } else { console.log("Other error:", error) } } ``` -------------------------------- ### Example: Handling TimeoutError Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-timeout.md Demonstrates how to use a try-catch block to handle potential `TimeoutError` exceptions when using `withTimeout`. It checks if the error is an instance of `TimeoutError` and logs the timeout duration. ```typescript try { await withTimeout(slowOperation(), 5000) } catch (error) { if (error instanceof TimeoutError) { console.log(`Operation timed out after ${error.timeoutMs}ms`) } } ``` -------------------------------- ### Enable and Configure Quiet Hours Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/configuration.md Schedule a time window to suppress all notifications. This example suppresses notifications from 10 PM to 8 AM. ```json { "quietHours": { "enabled": true, "start": "22:00", "end": "08:00" } } ``` -------------------------------- ### Use a Mutex for Exclusive Access Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/README.md This example demonstrates how to use a Mutex to ensure that only one caller can execute a critical operation at a time. The `criticalOperation` function will be executed exclusively. ```typescript const mutex = new Mutex() const result = await mutex.runExclusive(async () => { // Only one caller at a time return await criticalOperation() }) ``` -------------------------------- ### Question Asked Event Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/events.md This event is triggered when the AI poses a direct question to the user. Notifications are sent unless suppressed by quiet hours, and status indicators for 'question' are updated. ```javascript { type: "question.asked", properties: { sessionID: "abc123", id: "q456", question: "Which version should I use?", tool: { callID: "call789", // ... other properties } } } ``` -------------------------------- ### Terminal Detection Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/architecture.md Illustrates the output of the `detectTerminalInfo` function, which identifies terminal properties like name, process name, and bundle ID. This is used for focus detection, notification sender configuration, and OSC title support detection. ```text Ghostty, Kitty, iTerm2, WezTerm, Alacritty, etc. (37+ terminals) ↓ [detectTerminalInfo(config)] ↓ TerminalInfo { name: "ghostty", processName: "Ghostty", bundleId: "com.mitchellh.ghostty" (macOS) } ``` -------------------------------- ### Permission Asked Event Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/events.md This event is triggered when the AI requests a new permission grant. Notifications are always sent, and status updates indicate that the system is waiting for input. Suppression by terminal focus is skipped. ```javascript { type: "permission.asked", properties: { id: "perm123", description: "Read config file", // ... other properties } } ``` -------------------------------- ### Minimal Configuration (Uses All Defaults) Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/configuration.md This is the most basic configuration, relying entirely on default settings. ```json {} ``` -------------------------------- ### Plugin Initialization Steps Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/lifecycle.md Describes the sequence of operations when the OpenCode notify plugin is loaded, from function call to event handler registration. ```text 1. NotifyPlugin function called with { client } ↓ 2. Load configuration from ~/.config/opencode/kdco-notify.json ├─ Parse JSON ├─ Merge with defaults └─ Validate (quiet hours format, etc.) ↓ 3. Detect terminal ├─ Use config.terminal override if provided ├─ Otherwise detect via detect-terminal library ├─ Map to process name for focus detection └─ Get macOS bundle ID (if available) ↓ 4. Resolve cmux command ├─ Try to locate cmux executable ├─ Validate it's not in untrusted location (CWD, /tmp, etc.) ├─ Check cmux workflow availability └─ Return absolute path or undefined ↓ 5. Parse OSC title context ├─ Read terminal title from environment ├─ Check if terminal supports OSC title setting └─ Store for later title updates ↓ 6. Initialize runtime state ├─ Create deduplication maps (empty) ├─ Create status tracking maps (empty) ├─ Set up animation state (idle, no timers) └─ Mark cmux status updates as enabled (or disabled if OSC title take precedence) ↓ 7. Register event handlers ├─ tool.execute.before hook └─ event hook ↓ 8. Return plugin handlers object ↓ Plugin ready, awaiting events ``` -------------------------------- ### NotifyPlugin Initialization and Event Loop Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/architecture.md Illustrates the main stages of the NotifyPlugin's lifecycle within the OpenCode plugin system, from startup initialization to the ongoing event loop. ```text OpenCode Plugin System ↓ [NotifyPlugin entry point] ↓ ┌───────────────────────────────────────┐ │ Initialization (startup) │ ├───────────────────────────────────────┤ │ • Load config from ~/.config/ │ │ • Detect terminal emulator │ │ • Resolve cmux command (if available) │ │ • Parse OSC title context │ │ • Initialize state maps │ └───────────────────────────────────────┘ ↓ ┌───────────────────────────────────────┐ │ Event Loop (plugin lifetime) │ ├───────────────────────────────────────┤ │ • tool.execute.before hook │ │ • event hook │ └───────────────────────────────────────┘ ``` -------------------------------- ### Tool Execution Handling for Question Tool Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/lifecycle.md Details the process for the 'tool.execute.before' hook, specifically how it handles the 'question' tool, including checks for notification eligibility. ```text Hook input: { tool: "question", sessionID, callID, ... } ↓ 1. Is tool === "question"? ├─ Yes → Continue └─ No → Exit (ignore other tools) ↓ 2. Build question status transition └─ buildCmuxSessionStatusTransitionForQuestionTool(sessionID) ↓ 3. Apply transition (update status/title) ↓ 4. Build dedup key └─ "question:{sessionID}:{callID}" ↓ 5. Check if should notify ├─ Recently notified? (1500ms window) ├─ Quiet hours? └─ No other suppression (questions bypass focus check) ↓ 6. If should notify: handleQuestionAsked() ``` -------------------------------- ### All Options Set Configuration Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/configuration.md A comprehensive configuration setting all available options, including disabling child session notifications, specifying a terminal, custom sounds, and detailed quiet hours. ```json { "notifyChildSessions": false, "terminal": "ghostty", "sounds": { "idle": "Glass", "error": "Basso", "permission": "Submarine", "question": "Submarine" }, "quietHours": { "enabled": true, "start": "22:00", "end": "08:00" } } ``` -------------------------------- ### Deadlock Warning: Nested Locks with Same Mutex Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-mutex.md This example demonstrates a deadlock scenario that occurs when attempting to acquire the same mutex twice. Avoid this pattern to prevent your application from freezing. ```typescript // ✗ DEADLOCK: Same mutex acquired twice await mutex.acquire() await mutex.acquire() // Waits forever! ``` -------------------------------- ### Build Alerter Arguments Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/backend-notifications.md Constructs command-line arguments for the alerter tool. Use this to prepare arguments for `Bun.spawn` or `execFile` when sending desktop notifications via the alerter. ```typescript export function buildAlerterArguments(options: DesktopNotificationOptions): string[] ``` ```typescript const args = buildAlerterArguments({ title: "Error", message: "Compilation failed", sound: "Basso", }) // Returns: ["alerter", "--message", "Compilation failed", "--title", "Error", "--sound", "Basso"] ``` -------------------------------- ### Notification Sending Process with Fallback Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/lifecycle.md Details the steps for sending a notification, prioritizing `cmux` for richer notifications and falling back to direct desktop notifications if `cmux` fails or is not preferred. ```plaintext [sendNotification(options, runtime)] ↓ [sendNotificationWithFallback] ├─ preferCmux is false? │ └─ Send desktop notification directly → Done │ └─ preferCmux is true? ├─ Try: sendCmuxNotification() │ ├─ Build cmux args: ["notify", "--title", title, "--body", body, ...] │ ├─ Spawn: cmux + args │ ├─ Wait: withTimeout(exited, 1500ms) │ ├─ Check exit code │ │ ├─ 0 → Success, return true │ │ └─ non-0 → return false │ └─ On timeout: kill process, return false │ └─ cmux succeeded? ├─ Yes → Done └─ No → Continue to desktop fallback ↓ [sendDesktopNotification(options)] ↓ [sendDesktopNotificationByPlatform] ├─ platform === "darwin"? │ └─ Call sendMacOSAlerterNotification() │ ├─ Build alerter args │ ├─ Spawn alerter │ ├─ Wait for exit │ └─ Check exit code │ └─ platform !== "darwin"? └─ Call notifier.notify() ``` -------------------------------- ### Error Handling with try-catch Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-getprojectid.md Demonstrates how to handle potential errors during project ID generation using a try-catch block, including logging the error message. ```typescript try { const id = await getProjectId("/nonexistent/path", client) } catch (error) { if (error instanceof Error) { console.error("Project ID generation failed:", error.message) } } ``` -------------------------------- ### Permission Updated Event Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/events.md This event occurs when the status of a previously requested permission changes (e.g., granted or denied). Notifications are sent unless suppressed by quiet hours or terminal focus. ```javascript { type: "permission.updated", properties: { id: "perm123", status: "granted", // ... other properties } } ``` -------------------------------- ### Session Error Event Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/events.md This event is triggered when a session fails due to an error. Notifications are sent unless suppressed, and status updates indicate an error state for both cmux and the OSC title. ```javascript { type: "session.error", properties: { sessionID: "abc123", error: "Compilation failed: unexpected token", // ... other properties } } ``` -------------------------------- ### buildAlerterArguments Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/INDEX.md Builds the command-line arguments for the macOS alerter. ```APIDOC ## buildAlerterArguments ### Description Build alerter CLI args. ### Module [[api-reference/backend-notifications]] ``` -------------------------------- ### Get Real Temporary Directory Path Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-misc.md Retrieves the system's temporary directory path, resolving any symbolic links. This is crucial for compatibility with tools that require the actual path, especially on macOS. ```typescript export function getTempDir(): string ``` ```typescript const tempDir = getTempDir() // macOS: "/private/var/folders/xx/xxxxx/T" // Linux: "/tmp" // Windows: "C:\\Users\\name\\AppData\\Local\\Temp" // Use for creating temp files const tempFile = path.join(getTempDir(), `script-${Date.now()}.sh`) ``` -------------------------------- ### Project File Structure Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/INDEX.md Overview of the directory structure for the opencode-notify project, indicating the purpose of each key file and directory. ```text /workspace/home/output/ ├── README.md ← Start here ├── INDEX.md ← This file ├── architecture.md ← How the system is designed ├── lifecycle.md ← Event and notification flow ├── configuration.md ← Config file format and options ├── events.md ← Event types and handling ├── types.md ← Type definitions ├── errors.md ← Error handling and troubleshooting └── api-reference/ ├── notify-plugin.md ← Main plugin entry point ├── backend-notifications.md ← Platform-specific notifications ├── cmux-notifications.md ← cmux integration ├── kdco-primitives-getprojectid.md ├── kdco-primitives-timeout.md ├── kdco-primitives-shell.md ├── kdco-primitives-mutex.md └── kdco-primitives-misc.md ``` -------------------------------- ### Session Idle Event Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/events.md This event is triggered when a session completes successfully and is ready for user review. Notifications are sent unless suppressed by quiet hours, terminal focus, or specific session configurations. ```javascript { type: "session.idle", properties: { sessionID: "abc123", title: "Refactor auth module", // ... other properties } } ``` -------------------------------- ### Safe Usage with Bun.spawn Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-shell.md Illustrates safe usage patterns with Bun.spawn. Direct array arguments avoid shell parsing, while using '-c' with an escaped string requires explicit escaping. ```typescript // ✓ Safe: Use with Bun.spawn (preferred, no shell parsing) const proc = Bun.spawn(["echo", userInput]) ``` ```typescript // ✓ Safe: Escape if shell parsing is required const proc = Bun.spawn(["bash", "-c", `echo "${escapeBash(userInput)}"`]) ``` -------------------------------- ### Typical Usage with Imports Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-getprojectid.md Shows a typical usage pattern for getProjectId, including the necessary import statement and how to use the generated project ID for data caching. ```typescript import { getProjectId } from "kdco-primitives" const projectId = await getProjectId(process.cwd()) const storageKey = `delegation-cache:${projectId}` // Store delegation data keyed by stable project ID await storage.set(storageKey, delegationData) ``` -------------------------------- ### Session Status Update Event Example Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/events.md This intermediary event signifies a change in the session's state during execution. It is used for status updates like clearing the cmux status or updating the OSC title, but does not send user notifications. ```javascript { type: "session.status", properties: { sessionID: "abc123", status: "processing", // ... other properties } } ``` -------------------------------- ### buildCmuxStatusArgs Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/INDEX.md Builds the command-line arguments for cmux status. ```APIDOC ## buildCmuxStatusArgs ### Description Build cmux status args. ### Module [[api-reference/cmux-notifications]] ``` -------------------------------- ### buildAlerterArguments Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/backend-notifications.md Builds command-line arguments for the alerter tool, formatting notification options into a string array suitable for execution. ```APIDOC ## buildAlerterArguments ### Description Builds command-line arguments for the alerter tool. Returns an argv array starting with "alerter" followed by `--flag value` pairs. Arguments are only included if provided in options (except title and message which are required). ### Method Signature ```typescript export function buildAlerterArguments(options: DesktopNotificationOptions): string[] ``` ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - **options** (DesktopNotificationOptions) - Required - Notification parameters - **title** (string) - Required - Notification title - **message** (string) - Required - Notification message - **subtitle** (string | undefined) - Optional - Optional subtitle - **sound** (string | undefined) - Optional - Optional sound name - **senderBundleId** (string | null | undefined) - Optional - Optional bundle ID ### Return Type `string[]` — Array of command-line arguments suitable for `Bun.spawn` or `execFile`. ### Example ```typescript const args = buildAlerterArguments({ title: "Error", message: "Compilation failed", sound: "Basso", }) // Returns: ["alerter", "--message", "Compilation failed", "--title", "Error", "--sound", "Basso"] ``` ``` -------------------------------- ### Send macOS Notification with Alerter Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/backend-notifications.md Sends desktop notifications on macOS using the `alerter` command-line tool. It checks for the `alerter` executable, builds the command arguments from provided options, and spawns the process. Returns `true` on success and `false` if `alerter` is not found or the command fails. ```typescript export async function sendMacOSAlerterNotification( options: DesktopNotificationOptions, runtime?: AlerterRuntime, ): Promise ``` ```typescript const sent = await sendMacOSAlerterNotification({ title: "Build Complete", message: "Your build finished successfully", subtitle: "main branch", sound: "Glass", senderBundleId: "com.apple.Terminal", }) if (!sent) { console.log("Could not send notification") } ``` -------------------------------- ### CmuxExecutionOptions Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/types.md Options for cmux command execution, including timeout and custom process spawning. ```APIDOC ## CmuxExecutionOptions ### Description Options for cmux command execution, including timeout and custom process spawning. ### Fields - **timeoutMs** (number) - Optional - Command timeout in milliseconds. Defaults to 1500. - **spawnProcess** (function) - Optional - Process spawning function for testing. Defaults to `Bun.spawn`. - **cmuxCommand** (string) - Optional - Path to cmux executable. Defaults to undefined. ``` -------------------------------- ### Include Child Sessions with Custom Sounds Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/configuration.md Enable notifications for child sessions and customize sounds for different event types like idle, error, permission, and question. ```json { "notifyChildSessions": true, "sounds": { "idle": "Ping", "error": "Basso", "permission": "Submarine", "question": "Hero" } } ``` -------------------------------- ### Serialize Database Writes with runExclusive Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-mutex.md Illustrates using `runExclusive` to serialize database write operations. This ensures that record updates are processed one at a time, preventing potential data corruption. ```typescript const dbMutex = new Mutex() async function updateRecord(id: string, data: Record) { return dbMutex.runExclusive(async () => { const record = await db.get(id) record.data = data record.updatedAt = new Date() await db.put(record) return record }) } ``` -------------------------------- ### Utility Functions Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/README.md Helper functions for project ID retrieval, promise timeouts, shell escaping, and logging. ```APIDOC ## getProjectId ### Description Retrieves the stable project ID for a given project root. ### Method async ### Parameters - **projectRoot**: string - The root path of the project. - **client**: any (optional) - The client instance. ### Response Promise ## withTimeout ### Description Wraps a promise with a timeout, rejecting if the promise does not resolve within the specified time. ### Method async ### Parameters - **promise**: Promise - The promise to wrap. - **ms**: number - The timeout duration in milliseconds. - **message**: string (optional) - An optional message for the timeout error. ### Response Promise ## escapeBash ### Description Escapes a string for safe use in a Bash shell. ### Method string ### Parameters - **str**: string - The string to escape. ### Response string ## escapeAppleScript ### Description Escapes a string for safe use in AppleScript. ### Method string ### Parameters - **str**: string - The string to escape. ### Response string ## escapeBatch ### Description Escapes a string for safe use in a Windows Batch script. ### Method string ### Parameters - **str**: string - The string to escape. ### Response string ## Mutex ### Description A class for serializing asynchronous operations using a mutex. ### Method class ## logWarn ### Description Logs a warning message. ### Method void ### Parameters - **client**: any - The client context. - **service**: string - The service emitting the warning. - **message**: string - The warning message. ## isInsideTmux ### Description Detects if the current environment is inside a Tmux session. ### Method boolean ### Response boolean ``` -------------------------------- ### logWarn Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-misc.md Logs a warning using the OpenCode client if available, otherwise falls back to console.warn. When a client is provided, the warning integrates with the OpenCode UI log panel. ```APIDOC ## logWarn ### Description Log a warning message via OpenCode client or console fallback. ### Method Signature `logWarn(client: OpencodeClient | undefined, service: string, message: string): void` ### Parameters #### Parameters - `client` (OpencodeClient | undefined) - Optional - OpenCode client for UI integration - `service` (string) - Required - Service name for log categorization - `message` (string) - Required - Warning message ### Return Type `void` — No return value. ### Example ```typescript // With client - integrates with OpenCode UI logWarn(client, "delegation", "Task timed out after 30s") // Without client - logs to console logWarn(undefined, "delegation", "Task timed out after 30s") // Output: [delegation] Task timed out after 30s ``` ``` -------------------------------- ### Manual Acquire and Release Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-mutex.md Demonstrates the manual process of acquiring a mutex lock, executing a critical operation within a try-finally block, and releasing the lock. This pattern ensures the lock is always released, even if errors occur. ```typescript const mutex = new Mutex() await mutex.acquire() try { // Critical section - only one task runs here at a time await criticalOperation() } finally { mutex.release() // Always release! } ``` -------------------------------- ### NotifyPlugin Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/INDEX.md The entry point for the plugin and its lifecycle management. ```APIDOC ## NotifyPlugin ### Description Plugin entry point and lifecycle management. ### Module [[api-reference/notify-plugin]] ``` -------------------------------- ### Event Processing Workflow Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/lifecycle.md Details the steps involved when an OpenCode event arrives, including building status transitions and handling specific event types. ```text Event { type: "session.idle", properties: { sessionID, ... } } ↓ [Enter event handler] ↓ 1. Build session status transition ├─ Determine logical state from event type │ ├─ session.idle → "idle" │ ├─ session.error → "error" │ ├─ permission.asked → "busy" (waiting for input) │ ├─ question.asked → "busy" (with question marker) │ └─ session.status → varies (animated-busy, etc.) │ └─ Return CmuxSessionStatusTransition or null ↓ 2. Apply status transition to all transport layers ├─ [applyOscTitleSessionStatusTransition] │ ├─ Check if state changed │ ├─ If animated-busy: start spinner │ └─ If not animated-busy: update title or clear spinner │ └─ [applyCmuxSessionStatusTransition] ├─ Check if state changed ├─ If animated-busy: start cmux animation └─ If not animated-busy: enqueue status write (set-status or clear-status) ↓ 3. Event-specific handling ├─ session.idle/status │ ├─ Extract sessionID │ ├─ Build dedup key │ └─ If should notify: call handleSessionIdle() │ ├─ session.error │ ├─ Extract sessionID and error message │ └─ Call handleSessionError() │ ├─ permission.asked/updated │ ├─ Build dedup key │ └─ Call notifyPermissionIfNeeded() │ └─ question.asked ├─ Build dedup key from question properties └─ Call notifyQuestionIfNeeded() ↓ [Exit event handler] ``` -------------------------------- ### Status Update Lifecycle Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/lifecycle.md This diagram illustrates the lifecycle of a status update, detailing the steps from needing an update to its completion. It highlights the use of a queue to manage write intents and prevent race conditions. ```text [New status update needed] ↓ 1. Build write intent └─ sessionID, kind (set-status or clear-status), text ↓ 2. Check if intent is necessary └─ Compare with latest committed/pending/in-flight write └─ Skip if unchanged ↓ 3. Enqueue intent ├─ Add to pendingCmuxSessionStatusWrites map └─ Trigger drainCmuxSessionStatusWrites() ↓ 4. Drain queue serially ├─ Dequeue next pending write │ ├─ If set-status: sendCmuxStatus() │ └─ If clear-status: clearCmuxStatus() │ ├─ Wait for completion (1500ms timeout) │ ├─ Success → Move to committedCmuxSessionStatusWrites │ └─ Failure → Disable further updates, clear state │ ├─ Special handling for clear-status │ └─ If session now idle, prune its state │ └─ Loop until queue empty ↓ [Status update complete] ``` -------------------------------- ### Notification Flow Diagram Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/architecture.md Visualizes the step-by-step process of handling session events and sending notifications, including status extraction, transport updates, notification filtering, and final delivery via cmux or desktop notifiers. ```text Session event (session.idle, session.error, etc.) ↓ ┌─────────────────────────────┐ │ Extract & validate event │ └─────────────────────────────┘ ↓ ┌─────────────────────────────┐ │ Build status transition │ │ (idle, busy, error, etc.) │ └─────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ Apply status to all transports: │ │ 1. cmux status (enqueue write) │ │ 2. OSC title (update with spinner) │ └─────────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────┐ │ Should notify? (Multi-stage check) │ ├──────────────────────────────────────────────────────────┤ │ 1. Event type: Some always notify (question, permission) │ │ 2. Parent check: Skip if child session & notifyChildren │ │ 3. Quiet hours: Skip if in configured window │ │ 4. Focus: Skip if terminal focused (macOS) & not question │ │ 5. Dedup: Skip if same event sent <1500ms ago │ └──────────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────┐ │ Send notification │ ├──────────────────────────────────────────────────────────┤ │ IF preferCmux && cmuxCommand available: │ │ • Try: cmux notify --title ... --body ... │ │ • Timeout: 1500ms │ │ • On success: Done │ │ • On failure: Fall through to desktop │ │ │ │ Desktop notification: │ │ IF macOS: alerter --title ... --message ... │ │ ELSE: node-notifier (Windows/Linux) │ └──────────────────────────────────────────────────────────┘ ``` -------------------------------- ### Handling Invalid JSON Configuration Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/errors.md Illustrates how to gracefully handle an invalid JSON configuration file by falling back to default settings. This prevents application crashes due to malformed config. ```typescript try { const content = await fs.readFile(configPath, "utf8") const userConfig = JSON.parse(content) } catch { return DEFAULT_CONFIG } ``` -------------------------------- ### Graceful Degradation Error Handling Strategy Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/architecture.md Outlines the error handling approach where features are tried sequentially, and if one fails, a fallback mechanism is used. This ensures operation continues even if specific features or configurations are unavailable. ```text Try feature (e.g., alerter, cmux) ↓ Feature fails ↓ Degrade gracefully: ├─ alerter missing → use node-notifier ├─ cmux unavailable → use desktop ├─ terminal detection fails → use generic name ├─ config invalid → use defaults └─ session query fails → assume parent ↓ Continue operation ``` -------------------------------- ### NotificationRuntime Interface Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/types.md Defines the runtime configuration for notification delivery, including preferences for using cmux. ```typescript interface NotificationRuntime { preferCmux: boolean cmuxCommand?: string } ``` -------------------------------- ### buildCmuxNotifyArgs Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/INDEX.md Builds the command-line arguments for cmux notify. ```APIDOC ## buildCmuxNotifyArgs ### Description Build cmux notify args. ### Module [[api-reference/cmux-notifications]] ``` -------------------------------- ### Send Notification via cmux Source: https://github.com/kdcokenny/opencode-notify/blob/main/README.md Use this command to send notifications through the cmux native path. If cmux is unavailable or fails, it falls back to the desktop notification path. ```bash cmux notify --title "..." --subtitle "..." --body "..." ``` -------------------------------- ### OpenCode Notify Plugin Entry Point Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/README.md The main entry point for the OpenCode Notify plugin. It returns plugin handlers for OpenCode. ```typescript // Returns plugin handlers for OpenCode const NotifyPlugin: Plugin = async (ctx) => { ... } ``` -------------------------------- ### kdco-primitives Index Exports Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-misc.md Re-exports all utilities from the kdco-primitives package. Import any utility directly from the 'kdco-primitives' package. ```typescript export { getProjectId } from "./get-project-id" export { logWarn } from "./log-warn" export { Mutex } from "./mutex" export { assertShellSafe, escapeAppleScript, escapeBash, escapeBatch } from "./shell" export { getTempDir } from "./temp" export { canUseCmuxWorkflow, detectCmuxContext, type CmuxContext, type CmuxEnvironment, type ResolveExecutable } from "./cmux" export { isInsideTmux } from "./terminal-detect" export type { OpencodeClient } from "./types" export { TimeoutError, withTimeout } from "./with-timeout" ``` ```typescript import { getProjectId, logWarn, Mutex, escapeBash } from "kdco-primitives" ``` -------------------------------- ### getTempDir Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-misc.md Returns the system temp directory with symlinks resolved. This is critical on macOS where os.tmpdir() returns /var/folders/... (a symlink) but many tools need the real path /private/var/folders/... Resolves symlinks using fs.realpathSync.native() for maximum compatibility with file watchers and path comparisons. ```APIDOC ## getTempDir ### Description Get the real temp directory path, resolving symlinks. ### Method Signature `getTempDir(): string` ### Return Type `string` — The real (resolved) temp directory path. ### Description Returns the system temp directory with symlinks resolved. This is critical on macOS where `os.tmpdir()` returns `/var/folders/...` (a symlink) but many tools need the real path `/private/var/folders/...`. Resolves symlinks using `fs.realpathSync.native()` for maximum compatibility with file watchers and path comparisons. ### Example ```typescript const tempDir = getTempDir() // macOS: "/private/var/folders/xx/xxxxx/T" // Linux: "/tmp" // Windows: "C:\\Users\\name\\AppData\\Local\\Temp" // Use for creating temp files const tempFile = path.join(getTempDir(), `script-${Date.now()}.sh`) ``` ### Motivation Many tools require the real path to temp directory: - Bun's test harness - VS Code - Eclipse Theia - File watching systems Using the symlink path can cause issues with path comparisons and file watching. ``` -------------------------------- ### Default Configuration Object Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/configuration.md The default configuration object used by the plugin when no configuration file is present. It defines default values for notification behavior and sounds. ```typescript const DEFAULT_CONFIG = { notifyChildSessions: false, sounds: { idle: "Glass", error: "Basso", permission: "Submarine", }, quietHours: { enabled: false, start: "22:00", end: "08:00", }, } ``` -------------------------------- ### CmuxExecutionOptions Type Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/types.md Specifies the options available for executing cmux commands. This includes optional timeout settings, a custom process spawning function for testing, and the path to the cmux executable. ```typescript type CmuxExecutionOptions = { timeoutMs?: number spawnProcess?: (command: string[]) => CmuxProcess cmuxCommand?: string } ``` -------------------------------- ### Notify Configuration Interface Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/README.md Defines the configuration structure for the notification system, including settings for child sessions, sounds, and quiet hours. ```typescript interface NotifyConfig { notifyChildSessions: boolean sounds: { idle: string, error: string, permission: string, question?: string } quietHours: { enabled: boolean, start: string, end: string } terminal?: string } ``` -------------------------------- ### Enable Child Session Notifications Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/configuration.md Configure the plugin to include notifications for child or sub-session events. This is useful to avoid missing events from spawned tasks. ```json { "notifyChildSessions": true } ``` -------------------------------- ### buildCmuxClearStatusArgs Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/INDEX.md Builds the command-line arguments for clearing cmux status. ```APIDOC ## buildCmuxClearStatusArgs ### Description Build cmux clear-status args. ### Module [[api-reference/cmux-notifications]] ``` -------------------------------- ### runExclusive(fn: () => Promise) Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-mutex.md Executes a given asynchronous function exclusively, automatically handling lock acquisition and release. This is the recommended method for using the mutex. ```APIDOC ## runExclusive(fn: () => Promise) Execute a function with automatic lock acquire/release. ```typescript async runExclusive(fn: () => Promise): Promise ``` ### Parameters #### Parameters - **fn** (`() => Promise`) - Required - Async function to execute exclusively ### Description Automatically acquires the lock before executing the function and releases it after completion, even if the function throws. This is the preferred way to use the mutex. ### Example: Serialize tmux Commands ```typescript const tmuxMutex = new Mutex() async function getTmuxWindows() { return tmuxMutex.runExclusive(async () => { const proc = Bun.spawn(["tmux", "list-windows"]) return await proc.exited }) } // Multiple concurrent calls are serialized automatically const code1 = getTmuxWindows() const code2 = getTmuxWindows() const [c1, c2] = await Promise.all([code1, code2]) // Only one tmux command runs at a time ``` ### Example: Serialize Database Writes ```typescript const dbMutex = new Mutex() async function updateRecord(id: string, data: Record) { return dbMutex.runExclusive(async () => { const record = await db.get(id) record.data = data record.updatedAt = new Date() await db.put(record) return record }) } ``` ### Example: Error Handling ```typescript const mutex = new Mutex() try { const result = await mutex.runExclusive(async () => { if (someCondition) { throw new Error("Something went wrong") } return "success" }) } catch (error) { console.log("Operation failed:", error) // Lock is automatically released even though we threw } ``` ``` -------------------------------- ### Send Desktop Notification by Platform Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/backend-notifications.md Routes notifications to the appropriate platform-specific backend. This function handles the logic for sending notifications on macOS, Windows, and Linux. ```typescript export async function sendDesktopNotificationByPlatform( options: DesktopNotificationRouterOptions, ): Promise ``` ```typescript await sendDesktopNotificationByPlatform({ platform: process.platform, title: "Deploy Complete", message: "v1.2.3 deployed to production", sound: "Glass", senderBundleId: terminalBundleId, sendNodeNotifierNotification: () => notifier.notify({ title: "Deploy Complete", message: "v1.2.3 deployed to production", }), }) ``` -------------------------------- ### Build Cmux Set Status Arguments Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/cmux-notifications.md Builds command-line arguments for the `cmux set-status` command. ```typescript export function buildCmuxStatusArgs(payload: CmuxStatusPayload): string[] ``` -------------------------------- ### Serialize tmux Commands with runExclusive Source: https://github.com/kdcokenny/opencode-notify/blob/main/_autodocs/api-reference/kdco-primitives-mutex.md Shows how to use `runExclusive` to serialize asynchronous operations, specifically for interacting with tmux commands. This prevents concurrent execution of `tmux list-windows`, ensuring predictable output. ```typescript const tmuxMutex = new Mutex() async function getTmuxWindows() { return tmuxMutex.runExclusive(async () => { const proc = Bun.spawn(["tmux", "list-windows"]) return await proc.exited }) } // Multiple concurrent calls are serialized automatically const code1 = getTmuxWindows() const code2 = getTmuxWindows() const [c1, c2] = await Promise.all([code1, code2]) // Only one tmux command runs at a time ```