Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.DS_Store
.agent-lock.json
__pycache__/
*.pyc
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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

11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
```

Expand Down
40 changes: 36 additions & 4 deletions bin/codeman
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}"
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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 ;;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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() {
Expand All @@ -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)"
Expand Down Expand Up @@ -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"
Expand Down
29 changes: 29 additions & 0 deletions config/config.env.example
Original file line number Diff line number Diff line change
@@ -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

23 changes: 23 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -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).

16 changes: 16 additions & 0 deletions docs/diagrams/config-env-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Config Env Sync (Template → Local)

```mermaid
flowchart TD
T[config/config.env.example<br/>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<br/>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
```

104 changes: 68 additions & 36 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,12 @@ <h3>Resume by session UUID</h3>
</div>
</section>

<section class="section" id="notify">
<div class="section__head">
<h2>Slack/Discord notifications</h2>
<p>Baby steps. It either works or it tells you exactly why it doesn’t.</p>
</div>
<section class="section" id="notify">
<div class="section__head">
<h2>Slack/Discord notifications</h2>
<p>Baby steps. It either works or it tells you exactly why it doesn’t.</p>
<p class="muted">Notifications only fire when you run Codex via <code>codeman</code> (for example <code>codeman l3</code>), not <code>codex</code> directly.</p>
</div>

<div class="steps">
<div class="step">
Expand All @@ -258,43 +259,74 @@ <h3>Create a webhook URL</h3>
<p class="muted">Slack: Incoming Webhook. Discord: channel webhook.</p>
</div>
</div>
<div class="step">
<div class="step__n">2</div>
<div class="step__body">
<h3>Export it in your shell</h3>
<div class="code" data-copy>
<button class="copy" type="button" aria-label="Copy notification env vars">Copy</button>
<pre><code>export CODEMAN_DISCORD_WEBHOOK_URL='https://discord.com/api/webhooks/...'
export CODEMAN_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/...'</code></pre>
</div>
</div>
</div>
<div class="step">
<div class="step__n">3</div>
<div class="step__body">
<h3>Persist it</h3>
<p class="muted">Put the exports into <code>~/.zshrc</code> (or <code>~/.bashrc</code>), then reload.</p>
<div class="code" data-copy>
<button class="copy" type="button" aria-label="Copy shell reload command">Copy</button>
<pre><code>source ~/.zshrc</code></pre>
</div>
</div>
</div>
<div class="step">
<div class="step__n">4</div>
<div class="step__body">
<h3>Test</h3>
<div class="step">
<div class="step__n">2</div>
<div class="step__body">
<h3>Set the webhook URL</h3>
<p class="muted">
Put it in your shell (<code>~/.zshrc</code>/<code>~/.bashrc</code>) or in
<code>~/.config/codeman/config.env</code> (sourced automatically by <code>codeman</code>).
</p>
<div class="code" data-copy>
<button class="copy" type="button" aria-label="Copy notification env vars">Copy</button>
<pre><code># 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/...'</code></pre>
</div>
</div>
</div>
<div class="step">
<div class="step__n">3</div>
<div class="step__body">
<h3>Persist it</h3>
<p class="muted">
Shell: put the exports into <code>~/.zshrc</code> (or <code>~/.bashrc</code>), then reload.
For <code>config.env</code>, use the template in the repo at <code>config/config.env.example</code>
(it’s sourced on every <code>codeman</code> run). Optionally keep it synced with template updates:
</p>
<div class="code" data-copy>
<button class="copy" type="button" aria-label="Copy persist commands">Copy</button>
<pre><code># shell
source ~/.zshrc

# in a repo checkout (optional)
make config-sync</code></pre>
</div>
</div>
</div>
<div class="step">
<div class="step__n">4</div>
<div class="step__body">
<h3>Test</h3>
<div class="code" data-copy>
<button class="copy" type="button" aria-label="Copy notify test command">Copy</button>
<pre><code>codeman notify-test</code></pre>
</div>
<p class="muted">
If you see <code>ℹ️ No Slack/Discord integration configured</code>, the env var isn’t set in this shell.
</p>
</div>
</div>
</div>
</section>
</p>
</div>
</div>
<div class="step">
<div class="step__n">5</div>
<div class="step__body">
<h3>Choose when notifications fire (optional)</h3>
<p class="muted">
<code>reply</code> notifies when Codex produces a user-visible assistant message (useful for interactive sessions).
</p>
<div class="code" data-copy>
<button class="copy" type="button" aria-label="Copy notification controls">Copy</button>
<pre><code>export CODEMAN_NOTIFY_ON='wait,reply,complete'
export CODEMAN_NOTIFY_REPLY_COOLDOWN_SEC=0</code></pre>
</div>
</div>
</div>
</div>
</section>

<section class="section" id="completion">
<div class="section__head">
Expand Down
Loading
Loading