diff --git a/.gitignore b/.gitignore
index e6e4972..13f3ae5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
.DS_Store
.agent-lock.json
+__pycache__/
+*.pyc
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9768956
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+.PHONY: help config-sync config-sync-dry config-sync-diff
+
+help:
+ @echo "Targets:"
+ @echo " make config-sync Sync ~/.config/codeman/config.env with config/config.env.example (prompts on deletions)"
+ @echo " make config-sync-dry Same as config-sync, but doesn't write"
+ @echo " make config-sync-diff Print a diff of the proposed changes (may include secrets)"
+
+config-sync:
+ @python3 scripts/sync_config_env.py --template config/config.env.example --config "$$HOME/.config/codeman/config.env"
+
+config-sync-dry:
+ @python3 scripts/sync_config_env.py --template config/config.env.example --config "$$HOME/.config/codeman/config.env" --dry-run
+
+config-sync-diff:
+ @python3 scripts/sync_config_env.py --template config/config.env.example --config "$$HOME/.config/codeman/config.env" --dry-run --show-diff
+
diff --git a/README.md b/README.md
index 0c8feae..2a172c6 100644
--- a/README.md
+++ b/README.md
@@ -119,6 +119,12 @@ If Slack or Discord webhook is configured, Codeman can notify when:
```
3. Persist it so it works in every terminal
- Add the `export ...` line(s) to `~/.zshrc` (or `~/.bashrc`)
+ - Or add them to `~/.config/codeman/config.env` (sourced automatically by `codeman`)
+ - Template: `config/config.env.example`
+ - Optional: keep it in sync with the template:
+ ```bash
+ make config-sync
+ ```
- Then reload your shell:
```bash
source ~/.zshrc
@@ -131,7 +137,7 @@ If Slack or Discord webhook is configured, Codeman can notify when:
- If you see `π Webhook is configured, but notifications are disabled...`, remove `-N` or unset `CODEMAN_NOTIFY_DISABLED`.
5. Choose when notifications should fire (optional)
```bash
- export CODEMAN_NOTIFY_ON='wait,complete'
+ export CODEMAN_NOTIFY_ON='wait,reply,complete'
```
6. Customize the label (optional)
```bash
@@ -161,8 +167,9 @@ Optional controls:
export CODEMAN_NOTIFY_PREFIX='MBP-Blue'
export CODEMAN_NOTIFY_HOST_CODENAME='MBP-Blue'
export CODEMAN_NOTIFY_PROJECT_CODENAME='HiveCore'
-export CODEMAN_NOTIFY_ON='wait,complete'
+export CODEMAN_NOTIFY_ON='wait,reply,complete'
export CODEMAN_NOTIFY_COOLDOWN_SEC=30
+export CODEMAN_NOTIFY_REPLY_COOLDOWN_SEC=0
export CODEMAN_NOTIFY_DISABLED=1
```
diff --git a/bin/codeman b/bin/codeman
index 7dc33ed..3dc5802 100755
--- a/bin/codeman
+++ b/bin/codeman
@@ -26,6 +26,7 @@ CONFIG_FILE="$CONFIG_DIR/config.env"
CODEMAN_NOTIFY_LAST_WAIT=0
CODEMAN_NOTIFY_LAST_COMPLETE=0
+CODEMAN_NOTIFY_LAST_REPLY=0
CODEMAN_NOTIFY_SUPPRESS=0
CODEMAN_CONFIRM="${CODEMAN_CONFIRM:-1}"
CODEMAN_SAVED_NOTIFY_PREFIX="${CODEMAN_SAVED_NOTIFY_PREFIX:-}"
@@ -274,6 +275,9 @@ HIGH-RISK WARNING π¨
WEBHOOK NOTIFICATIONS π
Set CODEMAN_SLACK_WEBHOOK_URL and/or CODEMAN_DISCORD_WEBHOOK_URL.
+ You can set these in your shell (e.g. ~/.zshrc) or in:
+ ~/.config/codeman/config.env
+ (override the directory with CODEMAN_CONFIG_DIR).
Triggered on:
- wait/approval signals (sandbox denied, permission issues)
- task completion
@@ -282,8 +286,9 @@ WEBHOOK NOTIFICATIONS π
- CODEMAN_NOTIFY_PREFIX="HOST-CODENAME"
- CODEMAN_NOTIFY_HOST_CODENAME="HOST-CODENAME"
- CODEMAN_NOTIFY_PROJECT_CODENAME="PROJECT-CODENAME"
- - CODEMAN_NOTIFY_ON=wait,complete
+ - CODEMAN_NOTIFY_ON=wait,reply,complete
- CODEMAN_NOTIFY_COOLDOWN_SEC=30
+ - CODEMAN_NOTIFY_REPLY_COOLDOWN_SEC=0
- CODEMAN_NOTIFY_DISABLED=1
Runtime flags:
@@ -530,7 +535,7 @@ mode_flags() {
event_enabled() {
local event="$1"
- local configured="${CODEMAN_NOTIFY_ON:-wait,complete}"
+ local configured="${CODEMAN_NOTIFY_ON:-wait,reply,complete}"
case ",$configured," in
*",$event,"*) return 0 ;;
*) return 1 ;;
@@ -610,7 +615,12 @@ notify_with_cooldown() {
local message="$2"
local cooldown now last
- cooldown="${CODEMAN_NOTIFY_COOLDOWN_SEC:-30}"
+ # reply is typically wanted on every assistant response, so default it to no cooldown.
+ if [ "$kind" = "reply" ]; then
+ cooldown="${CODEMAN_NOTIFY_REPLY_COOLDOWN_SEC:-0}"
+ else
+ cooldown="${CODEMAN_NOTIFY_COOLDOWN_SEC:-30}"
+ fi
now="$(date +%s)"
if [ "$kind" = "wait" ]; then
@@ -619,6 +629,12 @@ notify_with_cooldown() {
return 0
fi
CODEMAN_NOTIFY_LAST_WAIT="$now"
+ elif [ "$kind" = "reply" ]; then
+ last="$CODEMAN_NOTIFY_LAST_REPLY"
+ if [ $((now - last)) -lt "$cooldown" ]; then
+ return 0
+ fi
+ CODEMAN_NOTIFY_LAST_REPLY="$now"
else
last="$CODEMAN_NOTIFY_LAST_COMPLETE"
if [ $((now - last)) -lt "$cooldown" ]; then
@@ -632,7 +648,8 @@ notify_with_cooldown() {
latest_session_file() {
# shellcheck disable=SC2012
- ls -1t "$HOME"/.codex/sessions/*/*/*/*.jsonl 2>/dev/null | head -n 1 || true
+ local codex_home="${CODEX_HOME:-$HOME/.codex}"
+ ls -1t "$codex_home"/sessions/*/*/*/*.jsonl 2>/dev/null | head -n 1 || true
}
handle_session_line() {
@@ -652,6 +669,11 @@ handle_session_line() {
if [ "$evt" = "task_complete" ] && event_enabled complete; then
notify_with_cooldown complete "β
${prefix} π ${project} π Codeman task complete (mode: ${mode})."
fi
+ # Fired when the assistant produces a user-visible message. This is the closest thing
+ # to "finished responding" while staying in an interactive Codex session.
+ if [ "$evt" = "agent_message" ] && event_enabled reply; then
+ notify_with_cooldown reply "π¬ ${prefix} π ${project} β
Codex replied (mode: ${mode})."
+ fi
;;
response_item)
evt="$(printf '%s\n' "$line" | jq -r '.payload.type // empty' 2>/dev/null || true)"
@@ -857,6 +879,16 @@ run_codex() {
fi
set -e
+ # Codex sessions currently don't emit a reliable "task_complete" event in the jsonl.
+ # Send a completion notification based on the process exit instead.
+ if event_enabled complete; then
+ if [ "${rc:-1}" -eq 0 ]; then
+ notify_with_cooldown complete "β
${prefix} π ${project} π Codeman run complete (mode: ${mode})."
+ else
+ notify_with_cooldown complete "β ${prefix} π ${project} π₯ Codeman exited with code ${rc} (mode: ${mode})."
+ fi
+ fi
+
: > "$stop_file"
wait "$monitor_pid" 2>/dev/null || true
rm -f "$stop_file"
diff --git a/config/config.env.example b/config/config.env.example
new file mode 100644
index 0000000..263fc32
--- /dev/null
+++ b/config/config.env.example
@@ -0,0 +1,29 @@
+# Codeman config template (sourced by: ~/.config/codeman/config.env)
+# This file uses shell syntax: KEY=VALUE. Quotes are recommended for strings/URLs.
+#
+# Install:
+# mkdir -p ~/.config/codeman
+# cp /path/to/codeman/config/config.env.example ~/.config/codeman/config.env
+#
+# Security:
+# Treat webhook URLs like secrets. If one leaks, rotate it immediately.
+
+# Pick one (or set both)
+# CODEMAN_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/...'
+CODEMAN_DISCORD_WEBHOOK_URL='https://discord.com/api/webhooks/...'
+
+# When to notify: wait (needs approval), reply (assistant responded), complete (run ended)
+CODEMAN_NOTIFY_ON='wait,reply,complete'
+
+# Cooldowns (seconds)
+# CODEMAN_NOTIFY_COOLDOWN_SEC=30
+CODEMAN_NOTIFY_REPLY_COOLDOWN_SEC=0
+
+# Labels
+# CODEMAN_NOTIFY_PREFIX='MBP-Blue'
+# CODEMAN_NOTIFY_HOST_CODENAME='MBP-Blue'
+# CODEMAN_NOTIFY_PROJECT_CODENAME='HiveCore'
+
+# Disable notifications globally
+# CODEMAN_NOTIFY_DISABLED=1
+
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..b7e7664
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,23 @@
+# Codeman Docs
+
+This folder contains the static website published via GitHub Pages.
+
+## Notifications Config
+
+Codeman loads configuration from:
+
+- Shell environment variables (for example in `~/.zshrc` or `~/.bashrc`)
+- `~/.config/codeman/config.env` (sourced automatically by `codeman`; override dir with `CODEMAN_CONFIG_DIR`)
+
+Template for `config.env` lives in the repo:
+
+- `config/config.env.example`
+
+To sync a local `~/.config/codeman/config.env` with template updates (adds new keys, prompts before deleting unknown keys):
+
+```bash
+make config-sync
+```
+
+Note: `make config-sync-diff` prints a diff that may include secrets (webhook URLs).
+
diff --git a/docs/diagrams/config-env-sync.md b/docs/diagrams/config-env-sync.md
new file mode 100644
index 0000000..9684b7e
--- /dev/null
+++ b/docs/diagrams/config-env-sync.md
@@ -0,0 +1,16 @@
+# Config Env Sync (Template β Local)
+
+```mermaid
+flowchart TD
+ T[config/config.env.example
Repo template] -->|make config-sync| S[scripts/sync_config_env.py]
+ S -->|reads existing (if present)| C[(~/.config/codeman/config.env)]
+ S -->|writes updated file
keeps template order/comments| C
+ S -->|optional: dry-run/diff| O[stdout / diff output]
+
+ %% Legend / Notes
+ subgraph Notes
+ N1[Unknown keys in local config: prompt before deletion (default keep)]
+ N2[Diff output may include secrets (webhook URLs)]
+ end
+```
+
diff --git a/docs/index.html b/docs/index.html
index eb1bfd7..6929bf2 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -244,11 +244,12 @@
Baby steps. It either works or it tells you exactly why it doesnβt.
-Baby steps. It either works or it tells you exactly why it doesnβt.
+Notifications only fire when you run Codex via codeman (for example codeman l3), not codex directly.
Slack: Incoming Webhook. Discord: channel webhook.
export CODEMAN_DISCORD_WEBHOOK_URL='https://discord.com/api/webhooks/...'
-export CODEMAN_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/...'
- Put the exports into ~/.zshrc (or ~/.bashrc), then reload.
source ~/.zshrc
-
+ Put it in your shell (~/.zshrc/~/.bashrc) or in
+ ~/.config/codeman/config.env (sourced automatically by codeman).
+
# Option A: shell
+export CODEMAN_DISCORD_WEBHOOK_URL='https://discord.com/api/webhooks/...'
+export CODEMAN_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/...'
+
+# Option B: ~/.config/codeman/config.env (no export needed)
+CODEMAN_DISCORD_WEBHOOK_URL='https://discord.com/api/webhooks/...'
+CODEMAN_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/...'
+
+ Shell: put the exports into ~/.zshrc (or ~/.bashrc), then reload.
+ For config.env, use the template in the repo at config/config.env.example
+ (itβs sourced on every codeman run). Optionally keep it synced with template updates:
+
# shell
+source ~/.zshrc
+
+# in a repo checkout (optional)
+make config-sync
+ codeman notify-test
If you see βΉοΈ No Slack/Discord integration configured, the env var isnβt set in this shell.
-
+ reply notifies when Codex produces a user-visible assistant message (useful for interactive sessions).
+
export CODEMAN_NOTIFY_ON='wait,reply,complete'
+export CODEMAN_NOTIFY_REPLY_COOLDOWN_SEC=0
+