fix: Windows hook support via Node.js conversion
- Convert statusline.sh and gsd-check-update.sh to Node.js - Remove gsd-notify.sh (blocking popups, not true toasts) - Remove --force-notify and --no-notify flags from installer - Use `node` command for cross-platform hook execution Fixes #114 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
51
hooks/gsd-check-update.js
Executable file
51
hooks/gsd-check-update.js
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env node
|
||||
// Check for GSD updates in background, write result to cache
|
||||
// Called by SessionStart hook - runs once per session
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { execSync, spawn } = require('child_process');
|
||||
|
||||
const homeDir = os.homedir();
|
||||
const cacheDir = path.join(homeDir, '.claude', 'cache');
|
||||
const cacheFile = path.join(cacheDir, 'gsd-update-check.json');
|
||||
const versionFile = path.join(homeDir, '.claude', 'get-shit-done', 'VERSION');
|
||||
|
||||
// Ensure cache directory exists
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Run check in background (spawn detached process)
|
||||
const child = spawn(process.execPath, ['-e', `
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const cacheFile = ${JSON.stringify(cacheFile)};
|
||||
const versionFile = ${JSON.stringify(versionFile)};
|
||||
|
||||
let installed = '0.0.0';
|
||||
try {
|
||||
installed = fs.readFileSync(versionFile, 'utf8').trim();
|
||||
} catch (e) {}
|
||||
|
||||
let latest = null;
|
||||
try {
|
||||
latest = execSync('npm view get-shit-done-cc version', { encoding: 'utf8', timeout: 10000 }).trim();
|
||||
} catch (e) {}
|
||||
|
||||
const result = {
|
||||
update_available: latest && installed !== latest,
|
||||
installed,
|
||||
latest: latest || 'unknown',
|
||||
checked: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
fs.writeFileSync(cacheFile, JSON.stringify(result));
|
||||
`], {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
});
|
||||
|
||||
child.unref();
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Check for GSD updates in background, write result to cache
|
||||
# Called by SessionStart hook - runs once per session
|
||||
|
||||
CACHE_FILE="$HOME/.claude/cache/gsd-update-check.json"
|
||||
mkdir -p "$HOME/.claude/cache"
|
||||
|
||||
# Run check in background (non-blocking)
|
||||
(
|
||||
installed=$(cat "$HOME/.claude/get-shit-done/VERSION" 2>/dev/null || echo "0.0.0")
|
||||
latest=$(npm view get-shit-done-cc version 2>/dev/null)
|
||||
|
||||
if [[ -n "$latest" && "$installed" != "$latest" ]]; then
|
||||
echo "{\"update_available\":true,\"installed\":\"$installed\",\"latest\":\"$latest\",\"checked\":$(date +%s)}" > "$CACHE_FILE"
|
||||
else
|
||||
echo "{\"update_available\":false,\"installed\":\"$installed\",\"latest\":\"${latest:-unknown}\",\"checked\":$(date +%s)}" > "$CACHE_FILE"
|
||||
fi
|
||||
) &
|
||||
|
||||
exit 0
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/bin/bash
|
||||
# GSD Completion Notification Hook
|
||||
# Cross-platform alert when Claude stops (task complete, needs input, etc.)
|
||||
|
||||
input=$(cat)
|
||||
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
||||
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
||||
transcript_path=$(echo "$input" | jq -r '.transcript_path // empty')
|
||||
|
||||
# Extract project name
|
||||
project="Claude Code"
|
||||
if [[ -n "$cwd" ]]; then
|
||||
project=$(basename "$cwd")
|
||||
fi
|
||||
|
||||
# Check todo list for current/completed task
|
||||
message=""
|
||||
if [[ -n "$session_id" ]]; then
|
||||
todo_file=$(ls -t "$HOME/.claude/todos/${session_id}"*.json 2>/dev/null | head -1)
|
||||
if [[ -f "$todo_file" ]]; then
|
||||
# Get most recently completed task, or in-progress task
|
||||
completed=$(jq -r '[.[] | select(.status=="completed")] | last | .content // empty' "$todo_file" 2>/dev/null)
|
||||
if [[ -n "$completed" ]]; then
|
||||
message="Completed: $completed"
|
||||
else
|
||||
in_progress=$(jq -r '.[] | select(.status=="in_progress") | .content' "$todo_file" 2>/dev/null | head -1)
|
||||
if [[ -n "$in_progress" ]]; then
|
||||
message="Paused: $in_progress"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback: generic message
|
||||
if [[ -z "$message" ]]; then
|
||||
message="Ready for input"
|
||||
fi
|
||||
|
||||
# Send notification based on OS
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
osascript -e "display alert \"GSD: $project\" message \"$message\" as informational" &>/dev/null &
|
||||
;;
|
||||
Linux)
|
||||
if command -v notify-send &>/dev/null; then
|
||||
notify-send "GSD: $project" "$message" --urgency=normal
|
||||
elif command -v zenity &>/dev/null; then
|
||||
zenity --info --title="GSD: $project" --text="$message" &
|
||||
fi
|
||||
;;
|
||||
MINGW*|CYGWIN*|MSYS*)
|
||||
# Windows via PowerShell
|
||||
if command -v powershell.exe &>/dev/null; then
|
||||
powershell.exe -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('$message', 'GSD: $project', 'OK', 'Information')" &>/dev/null &
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
84
hooks/statusline.js
Executable file
84
hooks/statusline.js
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env node
|
||||
// Claude Code Statusline - GSD Edition
|
||||
// Shows: model | current task | directory | context usage
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// Read JSON from stdin
|
||||
let input = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => input += chunk);
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(input);
|
||||
const model = data.model?.display_name || 'Claude';
|
||||
const dir = data.workspace?.current_dir || process.cwd();
|
||||
const session = data.session_id || '';
|
||||
const remaining = data.context_window?.remaining_percentage;
|
||||
|
||||
// Context window display (shows USED percentage)
|
||||
let ctx = '';
|
||||
if (remaining != null) {
|
||||
const rem = Math.round(remaining);
|
||||
const used = 100 - rem;
|
||||
|
||||
// Build progress bar (10 segments)
|
||||
const filled = Math.floor(used / 10);
|
||||
const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
|
||||
|
||||
// Color based on usage
|
||||
if (used < 50) {
|
||||
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
|
||||
} else if (used < 65) {
|
||||
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
|
||||
} else if (used < 80) {
|
||||
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
|
||||
} else {
|
||||
ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
|
||||
}
|
||||
}
|
||||
|
||||
// Current task from todos
|
||||
let task = '';
|
||||
const homeDir = os.homedir();
|
||||
const todosDir = path.join(homeDir, '.claude', 'todos');
|
||||
if (session && fs.existsSync(todosDir)) {
|
||||
const files = fs.readdirSync(todosDir)
|
||||
.filter(f => f.startsWith(session) && f.includes('-agent-') && f.endsWith('.json'))
|
||||
.map(f => ({ name: f, mtime: fs.statSync(path.join(todosDir, f)).mtime }))
|
||||
.sort((a, b) => b.mtime - a.mtime);
|
||||
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
const todos = JSON.parse(fs.readFileSync(path.join(todosDir, files[0].name), 'utf8'));
|
||||
const inProgress = todos.find(t => t.status === 'in_progress');
|
||||
if (inProgress) task = inProgress.activeForm || '';
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// GSD update available?
|
||||
let gsdUpdate = '';
|
||||
const cacheFile = path.join(homeDir, '.claude', 'cache', 'gsd-update-check.json');
|
||||
if (fs.existsSync(cacheFile)) {
|
||||
try {
|
||||
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
||||
if (cache.update_available) {
|
||||
gsdUpdate = '\x1b[33m⬆ /gsd:update\x1b[0m │ ';
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Output
|
||||
const dirname = path.basename(dir);
|
||||
if (task) {
|
||||
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
|
||||
} else {
|
||||
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent fail - don't break statusline on parse errors
|
||||
}
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Claude Code Statusline - GSD Edition
|
||||
# Shows: model | current task | directory | context usage
|
||||
|
||||
input=$(cat)
|
||||
model=$(echo "$input" | jq -r '.model.display_name')
|
||||
dir=$(echo "$input" | jq -r '.workspace.current_dir')
|
||||
session=$(echo "$input" | jq -r '.session_id')
|
||||
remaining=$(echo "$input" | jq -r '.context_window.remaining_percentage // empty')
|
||||
|
||||
# Context window display (shows USED percentage)
|
||||
ctx=""
|
||||
if [ -n "$remaining" ]; then
|
||||
rem=$(printf "%.0f" "$remaining")
|
||||
used=$((100 - rem))
|
||||
|
||||
# Build progress bar (10 segments) - fills as context is consumed
|
||||
filled=$((used / 10))
|
||||
bar=""
|
||||
for ((i=0; i<filled; i++)); do bar+="█"; done
|
||||
for ((i=filled; i<10; i++)); do bar+="░"; done
|
||||
|
||||
# Color based on usage with blinking skull at 80%+
|
||||
if [ "$used" -lt 50 ]; then
|
||||
ctx=$' \033[32m'"$bar $used%"$'\033[0m'
|
||||
elif [ "$used" -lt 65 ]; then
|
||||
ctx=$' \033[33m'"$bar $used%"$'\033[0m'
|
||||
elif [ "$used" -lt 80 ]; then
|
||||
ctx=$' \033[38;5;208m'"$bar $used%"$'\033[0m'
|
||||
else
|
||||
# Blinking red with skull
|
||||
ctx=$' \033[5;31m💀 '"$bar $used%"$'\033[0m'
|
||||
fi
|
||||
fi
|
||||
|
||||
# Current task from todos
|
||||
task=""
|
||||
todo=$(ls -t "$HOME/.claude/todos/${session}"-agent-*.json 2>/dev/null | head -1)
|
||||
if [[ -f "$todo" ]]; then
|
||||
task=$(jq -r '.[] | select(.status=="in_progress") | .activeForm' "$todo" 2>/dev/null | head -1)
|
||||
fi
|
||||
|
||||
# GSD update available?
|
||||
gsd_update=""
|
||||
if [[ -f "$HOME/.claude/cache/gsd-update-check.json" ]]; then
|
||||
update_available=$(jq -r '.update_available' "$HOME/.claude/cache/gsd-update-check.json" 2>/dev/null)
|
||||
if [[ "$update_available" == "true" ]]; then
|
||||
gsd_update=$'\033[33m⬆ /gsd:update\033[0m │ '
|
||||
fi
|
||||
fi
|
||||
|
||||
# Output
|
||||
dirname=$(basename "$dir")
|
||||
if [[ -n "$task" ]]; then
|
||||
printf '%s\033[2m%s\033[0m │ \033[1m%s\033[0m │ \033[2m%s\033[0m%s' "$gsd_update" "$model" "$task" "$dirname" "$ctx"
|
||||
else
|
||||
printf '%s\033[2m%s\033[0m │ \033[2m%s\033[0m%s' "$gsd_update" "$model" "$dirname" "$ctx"
|
||||
fi
|
||||
Reference in New Issue
Block a user