add tmux session detection and spiceflow links
- Detect tmux session name when running inside tmux - Add spiceflow-url config option for session links in notifications - Include clickable "Open in Spiceflow" link in Discord messages - Add `iamwaiting status` command to show full configuration - Separate toggle status from configuration display - Update installation docs to use symlink instead of bbin Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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:
|
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
|
- `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
|
- `setup-config` - Interactive setup wizard for webhook configuration
|
||||||
- `test-webhook` - Sends a test message to verify configuration
|
- `test-webhook` - Sends a test message to verify configuration
|
||||||
- `send-waiting-notification` - Main function that sends the notification (respects toggles)
|
- `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
|
```clojure
|
||||||
{:webhook-url "https://discord.com/api/webhooks/..."
|
{:webhook-url "https://discord.com/api/webhooks/..."
|
||||||
:user-id "123456789012345678" ; Optional: Discord user ID for @mentions
|
: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
|
:toggles {:idle-prompt true ; Enable idle prompt notifications
|
||||||
:permission-prompt true ; Enable permission prompt notifications
|
:permission-prompt true ; Enable permission prompt notifications
|
||||||
:permission-prompt-ping true}} ; Enable @mentions on permission prompts
|
: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:
|
Alternatively, configuration can be set via environment variables:
|
||||||
- `IAMWAITING_WEBHOOK_URL` - Discord webhook URL (required)
|
- `IAMWAITING_WEBHOOK_URL` - Discord webhook URL (required)
|
||||||
- `IAMWAITING_USER_ID` - Discord user ID for @mentions (optional)
|
- `IAMWAITING_USER_ID` - Discord user ID for @mentions (optional)
|
||||||
|
- `IAMWAITING_SPICEFLOW_URL` - Spiceflow base URL for session links (optional)
|
||||||
|
|
||||||
### Notification Toggles
|
### Notification Toggles
|
||||||
|
|
||||||
@@ -96,13 +99,13 @@ The `:permission-prompt-ping` toggle controls whether you get @mentioned (pinged
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install via bbin (recommended):
|
Install via symlink (recommended):
|
||||||
|
|
||||||
```bash
|
```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
|
## Common Commands
|
||||||
|
|
||||||
@@ -116,6 +119,9 @@ iamwaiting setup
|
|||||||
|
|
||||||
# Test the webhook configuration
|
# Test the webhook configuration
|
||||||
iamwaiting test
|
iamwaiting test
|
||||||
|
|
||||||
|
# Show current configuration
|
||||||
|
iamwaiting status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
@@ -198,13 +204,17 @@ The tool uses Discord's webhook API which requires:
|
|||||||
Notifications include:
|
Notifications include:
|
||||||
- ⏳ Waiting indicator
|
- ⏳ Waiting indicator
|
||||||
- 📁 Current working directory / project name
|
- 📁 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)
|
- 🕐 Timestamp (HH:mm:ss format)
|
||||||
- @mention (if user ID is configured, it's a permission prompt, and ping toggle is enabled)
|
- @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`
|
⏳ **Claude is waiting** in `ajet-industries`
|
||||||
📁 Path: `/home/user/repos/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
|
🕐 Time: 14:23:45
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -212,16 +222,17 @@ Example message (permission prompt with ping enabled):
|
|||||||
```
|
```
|
||||||
<@123456789012345678> ⏳ **Claude is waiting** in `ajet-industries`
|
<@123456789012345678> ⏳ **Claude is waiting** in `ajet-industries`
|
||||||
📁 Path: `/home/user/repos/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
|
🕐 Time: 14:23:45
|
||||||
📢 Type: `permission_prompt`
|
📢 Type: `permission_prompt`
|
||||||
```
|
```
|
||||||
|
|
||||||
Example message (permission prompt with ping disabled):
|
Example message (without tmux/Spiceflow):
|
||||||
```
|
```
|
||||||
⏳ **Claude is waiting** in `ajet-industries`
|
⏳ **Claude is waiting** in `ajet-industries`
|
||||||
📁 Path: `/home/user/repos/ajet-industries`
|
📁 Path: `/home/user/repos/ajet-industries`
|
||||||
🕐 Time: 14:23:45
|
🕐 Time: 14:23:45
|
||||||
📢 Type: `permission_prompt`
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling
|
||||||
@@ -236,6 +247,7 @@ Example message (permission prompt with ping disabled):
|
|||||||
Uses Babashka standard libraries:
|
Uses Babashka standard libraries:
|
||||||
- `babashka.http-client` - HTTP requests to Discord API
|
- `babashka.http-client` - HTTP requests to Discord API
|
||||||
- `babashka.fs` - File system operations for config management
|
- `babashka.fs` - File system operations for config management
|
||||||
|
- `babashka.process` - Shell command execution (for tmux session detection)
|
||||||
- `cheshire.core` - JSON encoding/decoding
|
- `cheshire.core` - JSON encoding/decoding
|
||||||
- `clojure.string` - String manipulation
|
- `clojure.string` - String manipulation
|
||||||
|
|
||||||
|
|||||||
+61
-12
@@ -3,6 +3,7 @@
|
|||||||
(ns iamwaiting.core
|
(ns iamwaiting.core
|
||||||
(:require [babashka.http-client :as http]
|
(:require [babashka.http-client :as http]
|
||||||
[babashka.fs :as fs]
|
[babashka.fs :as fs]
|
||||||
|
[babashka.process :as process]
|
||||||
[cheshire.core :as json]
|
[cheshire.core :as json]
|
||||||
[clojure.string :as str]))
|
[clojure.string :as str]))
|
||||||
|
|
||||||
@@ -16,12 +17,15 @@
|
|||||||
(System/getenv "IAMWAITING_WEBHOOK_URL"))
|
(System/getenv "IAMWAITING_WEBHOOK_URL"))
|
||||||
user-id (or (:user-id config)
|
user-id (or (:user-id config)
|
||||||
(System/getenv "IAMWAITING_USER_ID"))
|
(System/getenv "IAMWAITING_USER_ID"))
|
||||||
|
spiceflow-url (or (:spiceflow-url config)
|
||||||
|
(System/getenv "IAMWAITING_SPICEFLOW_URL"))
|
||||||
toggles (or (:toggles config)
|
toggles (or (:toggles config)
|
||||||
{:idle-prompt true
|
{:idle-prompt true
|
||||||
:permission-prompt true
|
:permission-prompt true
|
||||||
:permission-prompt-ping true})]
|
:permission-prompt-ping true})]
|
||||||
{:webhook-url webhook-url
|
{:webhook-url webhook-url
|
||||||
:user-id user-id
|
:user-id user-id
|
||||||
|
:spiceflow-url spiceflow-url
|
||||||
:toggles toggles}))
|
:toggles toggles}))
|
||||||
|
|
||||||
(defn send-discord-webhook [webhook-url message]
|
(defn send-discord-webhook [webhook-url message]
|
||||||
@@ -39,9 +43,20 @@
|
|||||||
(catch Exception e
|
(catch Exception e
|
||||||
{:success false :error (.getMessage 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"
|
"Format a message for Claude waiting event"
|
||||||
(let [cwd (or (:cwd event-data) (System/getProperty "user.dir"))
|
(let [cwd (or (:cwd event-data) (System/getProperty "user.dir"))
|
||||||
|
tmux-session (get-tmux-session)
|
||||||
project-name (fs/file-name cwd)
|
project-name (fs/file-name cwd)
|
||||||
timestamp (java.time.LocalDateTime/now)
|
timestamp (java.time.LocalDateTime/now)
|
||||||
formatted-time (.format timestamp
|
formatted-time (.format timestamp
|
||||||
@@ -58,10 +73,15 @@
|
|||||||
;; Ping user if configured, it's a permission prompt, and ping toggle is enabled
|
;; Ping user if configured, it's a permission prompt, and ping toggle is enabled
|
||||||
ping-enabled? (get toggles :permission-prompt-ping true)
|
ping-enabled? (get toggles :permission-prompt-ping true)
|
||||||
user-ping (when (and user-id is-permission-prompt? ping-enabled?)
|
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
|
(str user-ping
|
||||||
"⏳ **Claude is waiting** in `" project-name "`\n"
|
"⏳ **Claude is waiting** in `" project-name "`\n"
|
||||||
"📁 Path: `" cwd "`\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"
|
"🕐 Time: " formatted-time "\n"
|
||||||
(when session-id (str "🔑 Session: `" session-id "`\n"))
|
(when session-id (str "🔑 Session: `" session-id "`\n"))
|
||||||
(when notification-type (str "📢 Type: `" notification-type "`\n"))
|
(when notification-type (str "📢 Type: `" notification-type "`\n"))
|
||||||
@@ -89,7 +109,16 @@
|
|||||||
(let [user-id-input (str/trim (read-line))
|
(let [user-id-input (str/trim (read-line))
|
||||||
user-id (when-not (str/blank? user-id-input) user-id-input)]
|
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")
|
(println "\n📬 Notification Preferences")
|
||||||
(print "Enable notifications for idle prompts? (y/n, default: y): ")
|
(print "Enable notifications for idle prompts? (y/n, default: y): ")
|
||||||
(flush)
|
(flush)
|
||||||
@@ -115,7 +144,8 @@
|
|||||||
:toggles {:idle-prompt idle-enabled?
|
:toggles {:idle-prompt idle-enabled?
|
||||||
:permission-prompt perm-enabled?
|
:permission-prompt perm-enabled?
|
||||||
:permission-prompt-ping ping-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
|
;; Create config directory
|
||||||
(fs/create-dirs (fs/parent config-file))
|
(fs/create-dirs (fs/parent config-file))
|
||||||
@@ -126,9 +156,11 @@
|
|||||||
(when user-id
|
(when user-id
|
||||||
(println (str "✓ User ID configured - you will be @mentioned on permission prompts: "
|
(println (str "✓ User ID configured - you will be @mentioned on permission prompts: "
|
||||||
(if ping-enabled? "enabled" "disabled"))))
|
(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 "✓ Idle prompts: " (if idle-enabled? "enabled" "disabled")))
|
||||||
(println (str "✓ Permission prompts: " (if perm-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 []
|
(defn test-webhook []
|
||||||
"Test the webhook configuration"
|
"Test the webhook configuration"
|
||||||
@@ -190,7 +222,7 @@
|
|||||||
(println (str "Notification disabled for " toggle-key)))
|
(println (str "Notification disabled for " toggle-key)))
|
||||||
|
|
||||||
;; Original notification logic
|
;; 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)]
|
result (send-discord-webhook (:webhook-url config) message)]
|
||||||
(if (:success result)
|
(if (:success result)
|
||||||
(println "✓ Notification sent")
|
(println "✓ Notification sent")
|
||||||
@@ -234,14 +266,29 @@
|
|||||||
toggles (or (:toggles config)
|
toggles (or (:toggles config)
|
||||||
{:idle-prompt true
|
{:idle-prompt true
|
||||||
:permission-prompt true
|
:permission-prompt true
|
||||||
:permission-prompt-ping true})
|
:permission-prompt-ping true})]
|
||||||
user-id (:user-id config)]
|
(println "📊 Toggle Status\n")
|
||||||
(println "📊 Current Toggle Status\n")
|
|
||||||
(println (str "Idle prompts: " (if (:idle-prompt toggles) "✅ enabled" "❌ disabled")))
|
(println (str "Idle prompts: " (if (:idle-prompt toggles) "✅ enabled" "❌ disabled")))
|
||||||
(println (str "Permission prompts: " (if (:permission-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")))
|
(println (str "Permission ping: " (if (:permission-prompt-ping toggles) "✅ enabled" "❌ disabled")))))
|
||||||
(when user-id
|
|
||||||
(println (str "\n👤 User ID configured: " user-id)))))
|
(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 []
|
(defn show-help []
|
||||||
(println "iamwaiting - Send Discord notifications when Claude is waiting")
|
(println "iamwaiting - Send Discord notifications when Claude is waiting")
|
||||||
@@ -249,6 +296,7 @@
|
|||||||
(println "Usage:")
|
(println "Usage:")
|
||||||
(println " iamwaiting setup Set up Discord webhook configuration")
|
(println " iamwaiting setup Set up Discord webhook configuration")
|
||||||
(println " iamwaiting test Test 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 status Show current toggle status")
|
||||||
(println " iamwaiting toggle idle-prompt <on|off> Toggle idle prompt notifications")
|
(println " iamwaiting toggle idle-prompt <on|off> Toggle idle prompt notifications")
|
||||||
(println " iamwaiting toggle permission-prompt <on|off> Toggle permission prompt notifications")
|
(println " iamwaiting toggle permission-prompt <on|off> Toggle permission prompt notifications")
|
||||||
@@ -273,6 +321,7 @@
|
|||||||
(case command
|
(case command
|
||||||
"setup" (setup-config)
|
"setup" (setup-config)
|
||||||
"test" (test-webhook)
|
"test" (test-webhook)
|
||||||
|
"status" (show-config)
|
||||||
"toggle" (let [subcommand (second args)]
|
"toggle" (let [subcommand (second args)]
|
||||||
(case subcommand
|
(case subcommand
|
||||||
"status" (show-toggle-status)
|
"status" (show-toggle-status)
|
||||||
|
|||||||
Reference in New Issue
Block a user