diff --git a/CLAUDE.md b/CLAUDE.md index 96b6189..d9608f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,9 +16,10 @@ The tool integrates with Claude Code's hook system to send real-time notificatio The entire implementation is a single Babashka script (`iamwaiting`) with these key functions: -- `load-config` - Loads webhook URL and toggles from config file or environment variable +- `load-config` - Loads webhook URL, toggles, and Spiceflow URL from config file or environment variables - `send-discord-webhook` - Makes HTTP POST request to Discord webhook API -- `format-waiting-message` - Formats the notification message with project context +- `get-tmux-session` - Detects current tmux session name (if running inside tmux) +- `format-waiting-message` - Formats the notification message with project context, tmux session, and Spiceflow link - `setup-config` - Interactive setup wizard for webhook configuration - `test-webhook` - Sends a test message to verify configuration - `send-waiting-notification` - Main function that sends the notification (respects toggles) @@ -33,6 +34,7 @@ Configuration is stored in `~/.iamwaiting/config.edn` with the following structu ```clojure {:webhook-url "https://discord.com/api/webhooks/..." :user-id "123456789012345678" ; Optional: Discord user ID for @mentions + :spiceflow-url "https://hostname:5173" ; Optional: Spiceflow URL for session links :toggles {:idle-prompt true ; Enable idle prompt notifications :permission-prompt true ; Enable permission prompt notifications :permission-prompt-ping true}} ; Enable @mentions on permission prompts @@ -41,6 +43,7 @@ Configuration is stored in `~/.iamwaiting/config.edn` with the following structu Alternatively, configuration can be set via environment variables: - `IAMWAITING_WEBHOOK_URL` - Discord webhook URL (required) - `IAMWAITING_USER_ID` - Discord user ID for @mentions (optional) +- `IAMWAITING_SPICEFLOW_URL` - Spiceflow base URL for session links (optional) ### Notification Toggles @@ -96,13 +99,13 @@ The `:permission-prompt-ping` toggle controls whether you get @mentioned (pinged ## Installation -Install via bbin (recommended): +Install via symlink (recommended): ```bash -bbin install git@git.ajet.fyi:ajet-industries/iamwaiting.git +ln -s /path/to/repos/iamwaiting/iamwaiting ~/.local/bin/iamwaiting ``` -This installs `iamwaiting` to `~/.local/bin/iamwaiting` (ensure `~/.local/bin` is in your PATH). +This creates a symlink at `~/.local/bin/iamwaiting` pointing to the script (ensure `~/.local/bin` is in your PATH). Using a symlink means changes to the script are immediately available without reinstalling. ## Common Commands @@ -116,6 +119,9 @@ iamwaiting setup # Test the webhook configuration iamwaiting test + +# Show current configuration +iamwaiting status ``` ### Usage @@ -198,13 +204,17 @@ The tool uses Discord's webhook API which requires: Notifications include: - ā³ Waiting indicator - šŸ“ Current working directory / project name +- šŸ–„ļø Tmux session name (if running inside tmux) +- šŸ”— Spiceflow session link (if Spiceflow URL configured and running in tmux) - šŸ• Timestamp (HH:mm:ss format) - @mention (if user ID is configured, it's a permission prompt, and ping toggle is enabled) -Example message (idle prompt): +Example message (with Spiceflow link): ``` ā³ **Claude is waiting** in `ajet-industries` šŸ“ Path: `/home/user/repos/ajet-industries` +šŸ–„ļø Tmux: `spiceflow-bold-sun-9841` +šŸ”— **[Open in Spiceflow](https://hostname:5173/session/spiceflow-bold-sun-9841)** šŸ• Time: 14:23:45 ``` @@ -212,16 +222,17 @@ Example message (permission prompt with ping enabled): ``` <@123456789012345678> ā³ **Claude is waiting** in `ajet-industries` šŸ“ Path: `/home/user/repos/ajet-industries` +šŸ–„ļø Tmux: `spiceflow-bold-sun-9841` +šŸ”— **[Open in Spiceflow](https://hostname:5173/session/spiceflow-bold-sun-9841)** šŸ• Time: 14:23:45 šŸ“¢ Type: `permission_prompt` ``` -Example message (permission prompt with ping disabled): +Example message (without tmux/Spiceflow): ``` ā³ **Claude is waiting** in `ajet-industries` šŸ“ Path: `/home/user/repos/ajet-industries` šŸ• Time: 14:23:45 -šŸ“¢ Type: `permission_prompt` ``` ### Error Handling @@ -236,6 +247,7 @@ Example message (permission prompt with ping disabled): Uses Babashka standard libraries: - `babashka.http-client` - HTTP requests to Discord API - `babashka.fs` - File system operations for config management +- `babashka.process` - Shell command execution (for tmux session detection) - `cheshire.core` - JSON encoding/decoding - `clojure.string` - String manipulation diff --git a/iamwaiting b/iamwaiting index 14090a2..91b1421 100755 --- a/iamwaiting +++ b/iamwaiting @@ -3,6 +3,7 @@ (ns iamwaiting.core (:require [babashka.http-client :as http] [babashka.fs :as fs] + [babashka.process :as process] [cheshire.core :as json] [clojure.string :as str])) @@ -16,12 +17,15 @@ (System/getenv "IAMWAITING_WEBHOOK_URL")) user-id (or (:user-id config) (System/getenv "IAMWAITING_USER_ID")) + spiceflow-url (or (:spiceflow-url config) + (System/getenv "IAMWAITING_SPICEFLOW_URL")) toggles (or (:toggles config) {:idle-prompt true :permission-prompt true :permission-prompt-ping true})] {:webhook-url webhook-url :user-id user-id + :spiceflow-url spiceflow-url :toggles toggles})) (defn send-discord-webhook [webhook-url message] @@ -39,9 +43,20 @@ (catch Exception e {:success false :error (.getMessage e)}))) -(defn format-waiting-message [event-data user-id toggles] +(defn get-tmux-session [] + "Get the current tmux session name if running inside tmux" + (when (System/getenv "TMUX") + (try + (let [result (process/shell {:out :string :err :string} + "tmux" "display-message" "-p" "#S")] + (when (zero? (:exit result)) + (str/trim (:out result)))) + (catch Exception _ nil)))) + +(defn format-waiting-message [event-data user-id toggles spiceflow-url] "Format a message for Claude waiting event" (let [cwd (or (:cwd event-data) (System/getProperty "user.dir")) + tmux-session (get-tmux-session) project-name (fs/file-name cwd) timestamp (java.time.LocalDateTime/now) formatted-time (.format timestamp @@ -58,10 +73,15 @@ ;; Ping user if configured, it's a permission prompt, and ping toggle is enabled ping-enabled? (get toggles :permission-prompt-ping true) user-ping (when (and user-id is-permission-prompt? ping-enabled?) - (str "<@" user-id "> "))] + (str "<@" user-id "> ")) + ;; Build Spiceflow session link if both URL and tmux session are available + spiceflow-link (when (and spiceflow-url tmux-session) + (str spiceflow-url "/session/" tmux-session))] (str user-ping "ā³ **Claude is waiting** in `" project-name "`\n" "šŸ“ Path: `" cwd "`\n" + (when tmux-session (str "šŸ–„ļø Tmux: `" tmux-session "`\n")) + (when spiceflow-link (str "šŸ”— **[Open in Spiceflow](" spiceflow-link ")**\n")) "šŸ• Time: " formatted-time "\n" (when session-id (str "šŸ”‘ Session: `" session-id "`\n")) (when notification-type (str "šŸ“¢ Type: `" notification-type "`\n")) @@ -89,7 +109,16 @@ (let [user-id-input (str/trim (read-line)) user-id (when-not (str/blank? user-id-input) user-id-input)] - ;; NEW: Toggle prompts + (println "\nEnter your Spiceflow URL (optional, for session links in notifications):") + (println "(e.g., https://framework-desktop:5173)\n") + (print "> ") + (flush) + (let [spiceflow-url-input (str/trim (read-line)) + spiceflow-url (when-not (str/blank? spiceflow-url-input) + ;; Remove trailing slash if present + (str/replace spiceflow-url-input #"/$" ""))] + + ;; Toggle prompts (println "\nšŸ“¬ Notification Preferences") (print "Enable notifications for idle prompts? (y/n, default: y): ") (flush) @@ -115,7 +144,8 @@ :toggles {:idle-prompt idle-enabled? :permission-prompt perm-enabled? :permission-prompt-ping ping-enabled?}} - user-id (assoc :user-id user-id))] + user-id (assoc :user-id user-id) + spiceflow-url (assoc :spiceflow-url spiceflow-url))] ;; Create config directory (fs/create-dirs (fs/parent config-file)) @@ -126,9 +156,11 @@ (when user-id (println (str "āœ“ User ID configured - you will be @mentioned on permission prompts: " (if ping-enabled? "enabled" "disabled")))) + (when spiceflow-url + (println (str "āœ“ Spiceflow URL: " spiceflow-url))) (println (str "āœ“ Idle prompts: " (if idle-enabled? "enabled" "disabled"))) (println (str "āœ“ Permission prompts: " (if perm-enabled? "enabled" "disabled"))) - (println "\nTest the webhook with: ./iamwaiting test")))))) + (println "\nTest the webhook with: ./iamwaiting test"))))))) (defn test-webhook [] "Test the webhook configuration" @@ -190,7 +222,7 @@ (println (str "Notification disabled for " toggle-key))) ;; Original notification logic - (let [message (format-waiting-message event-data (:user-id config) (:toggles config)) + (let [message (format-waiting-message event-data (:user-id config) (:toggles config) (:spiceflow-url config)) result (send-discord-webhook (:webhook-url config) message)] (if (:success result) (println "āœ“ Notification sent") @@ -234,14 +266,29 @@ toggles (or (:toggles config) {:idle-prompt true :permission-prompt true - :permission-prompt-ping true}) - user-id (:user-id config)] - (println "šŸ“Š Current Toggle Status\n") + :permission-prompt-ping true})] + (println "šŸ“Š Toggle Status\n") (println (str "Idle prompts: " (if (:idle-prompt toggles) "āœ… enabled" "āŒ disabled"))) (println (str "Permission prompts: " (if (:permission-prompt toggles) "āœ… enabled" "āŒ disabled"))) - (println (str "Permission ping: " (if (:permission-prompt-ping toggles) "āœ… enabled" "āŒ disabled"))) - (when user-id - (println (str "\nšŸ‘¤ User ID configured: " user-id))))) + (println (str "Permission ping: " (if (:permission-prompt-ping toggles) "āœ… enabled" "āŒ disabled"))))) + +(defn show-config [] + "Display current configuration" + (let [config (load-config) + toggles (or (:toggles config) + {:idle-prompt true + :permission-prompt true + :permission-prompt-ping true}) + user-id (:user-id config) + spiceflow-url (:spiceflow-url config)] + (println "āš™ļø Configuration\n") + (println (str "Webhook URL: " (if (:webhook-url config) "āœ… configured" "āŒ not configured"))) + (println (str "User ID: " (or user-id "not configured"))) + (println (str "Spiceflow URL: " (or spiceflow-url "not configured"))) + (println "\nšŸ“Š Toggles\n") + (println (str "Idle prompts: " (if (:idle-prompt toggles) "āœ… enabled" "āŒ disabled"))) + (println (str "Permission prompts: " (if (:permission-prompt toggles) "āœ… enabled" "āŒ disabled"))) + (println (str "Permission ping: " (if (:permission-prompt-ping toggles) "āœ… enabled" "āŒ disabled"))))) (defn show-help [] (println "iamwaiting - Send Discord notifications when Claude is waiting") @@ -249,6 +296,7 @@ (println "Usage:") (println " iamwaiting setup Set up Discord webhook configuration") (println " iamwaiting test Test webhook configuration") + (println " iamwaiting status Show current configuration") (println " iamwaiting toggle status Show current toggle status") (println " iamwaiting toggle idle-prompt Toggle idle prompt notifications") (println " iamwaiting toggle permission-prompt Toggle permission prompt notifications") @@ -273,6 +321,7 @@ (case command "setup" (setup-config) "test" (test-webhook) + "status" (show-config) "toggle" (let [subcommand (second args)] (case subcommand "status" (show-toggle-status)