Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions prompts/seed-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ You are creating a new Go CLI for a 37signals product using the seed templates.

**Skills & plugin:**
- `seed/.claude-plugin/` → `.claude-plugin/` (customize)
- `ln -s ../../skills/<app> .claude-plugin/skills/<app>` (create skills symlink)
Comment thread
jeremy marked this conversation as resolved.
Outdated
- `seed/skills/app/SKILL.md.tmpl` → `skills/<app>/SKILL.md` (customize)
- `seed/skills/embed.go.tmpl` → `skills/embed.go`

Expand Down
40 changes: 0 additions & 40 deletions seed/.claude-plugin/agents/context-linker.md

This file was deleted.

48 changes: 0 additions & 48 deletions seed/.claude-plugin/agents/navigator.md

This file was deleted.

26 changes: 26 additions & 0 deletions seed/.claude-plugin/commands/doctor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: {{.Name}}-doctor
description: Check {{.Name}} plugin health — CLI, auth, API connectivity, project context.
invocable: true
---

# /{{.Name}}-doctor

Run the {{.Name}} CLI health check and report results.

```bash
{{.Name}} doctor --json
```

Interpret the output:
- **pass**: Working correctly
- **warn**: Non-critical issue (e.g., shell completion not installed)
- **skip**: Check not run (e.g., unauthenticated or not applicable)
- **fail**: Broken — needs attention

For any failures, follow the `hint` field in the check output. Common fixes:
- Authentication failed → `{{.Name}} auth login`
- API unreachable → check network / VPN
- Plugin not installed → `claude plugin install {{.Name}}`
Comment thread
jeremy marked this conversation as resolved.
Outdated

Report results concisely: list failures and warnings with their hints. If everything passes, say so.
27 changes: 27 additions & 0 deletions seed/.claude-plugin/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-commit-check.sh",
"timeout": 5
}
]
}
]
}
}
63 changes: 63 additions & 0 deletions seed/.claude-plugin/hooks/post-commit-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# post-commit-check.sh - Check for {{.Name}} item references after git commits
#
# This hook runs after Bash tool use and checks if a git commit was made
# that references a {{.Name}} item ({{.Upper}}-12345, item-12345, etc.)

set -euo pipefail

# Read tool input from stdin (JSON with tool_name, tool_input, tool_output)
input=$(cat)

# Extract tool input (the bash command that was run)
tool_input=$(echo "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
Comment thread
jeremy marked this conversation as resolved.

# Only process git commit commands
if [[ ! "$tool_input" =~ ^git\ commit ]]; then
exit 0
fi

# Check if commit succeeded by looking for output patterns
tool_output=$(echo "$input" | jq -r '.tool_output // empty' 2>/dev/null)

# Skip if commit failed — detect error indicators before checking for success.
# Only match lines that look like git/hook errors, not commit subject lines
# (e.g. "[branch abc1234] Fix failed login" should not trigger this guard).
# We strip the "[branch hash] subject" success line before scanning for errors.
filtered_output=$(echo "$tool_output" | grep -v '^\[.*[a-f0-9]\{7,\}\]')
Comment thread
jeremy marked this conversation as resolved.
Outdated
if echo "$filtered_output" | grep -qiE '(^|[[:space:]])(error|fatal|aborted|rejected)[[:space:]:]|hook[[:space:]].*[[:space:]]failed|pre-commit[[:space:]].*[[:space:]]failed|^error:'; then
exit 0
fi

# Verify commit actually succeeded - look for commit hash pattern or "create mode"
if [[ ! "$tool_output" =~ \[.*[a-f0-9]{7,}\] ]] && [[ ! "$tool_output" =~ "create mode" ]]; then
exit 0
fi

# Look for item references in the commit message or branch name
branch=$(git branch --show-current 2>/dev/null || true)
last_commit_msg=$(git log -1 --format=%s 2>/dev/null || true)

# Patterns: {{.Upper}}-12345, item-12345, {{.Name}}-12345
todo_patterns='{{.Upper}}-[0-9]+|item-[0-9]+|{{.Name}}-[0-9]+'

found_in_branch=$(echo "$branch" | grep -oEi "$todo_patterns" | head -1 || true)
found_in_msg=$(echo "$last_commit_msg" | grep -oEi "$todo_patterns" | head -1 || true)

if [[ -n "$found_in_branch" ]] || [[ -n "$found_in_msg" ]]; then
ref="${found_in_msg:-$found_in_branch}"
# Extract just the number
item_id=$(echo "$ref" | grep -oE '[0-9]+')

cat << EOF
<hook-output>
Comment thread
jeremy marked this conversation as resolved.
Detected {{.Name}} item reference: $ref

To link this commit to {{.Name}}:
{{.Name}} comment "Commit $(git rev-parse --short HEAD 2>/dev/null): $last_commit_msg" --on $item_id

Or complete the item:
{{.Name}} done $item_id
</hook-output>
EOF
fi
86 changes: 29 additions & 57 deletions seed/.claude-plugin/hooks/session-start.sh
Original file line number Diff line number Diff line change
@@ -1,76 +1,48 @@
#!/usr/bin/env bash
# session-start.sh — Load CLI context at session start for Claude Code.
# session-start.sh - {{.Name}} plugin liveness check
#
# Emits config, auth status, and active profile so the agent knows
# what environment it's operating in.
#
# TODO: Replace CLI_NAME, config paths, and commands for your CLI.
# Lightweight: one subprocess call. Context priming happens on first
# use via the /{{.Name}} skill, not here.

set -euo pipefail

CLI_NAME="${CLI_NAME:-mycli}"

# Require jq for JSON parsing
if ! command -v jq &>/dev/null; then
exit 0
fi

# Find the CLI binary — prefer PATH, fall back to plugin's bin directory
if command -v "$CLI_NAME" &>/dev/null; then
CLI_BIN="$CLI_NAME"
else
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLI_BIN="${SCRIPT_DIR}/../../bin/${CLI_NAME}"
if [[ ! -x "$CLI_BIN" ]]; then
cat << EOF
if ! command -v {{.Name}} &>/dev/null; then
Comment thread
jeremy marked this conversation as resolved.
Outdated
cat << 'EOF'
<hook-output>
${CLI_NAME} plugin: CLI not found.
Install: https://github.com/basecamp/${CLI_NAME}-cli#installation
{{.Name}} plugin active — CLI not found on PATH.
Install: https://github.com/basecamp/{{.Name}}-cli#installation
</hook-output>
EOF
exit 0
fi
fi

# Get CLI version
cli_version=$("$CLI_BIN" --version 2>/dev/null | awk '{print $NF}' || true)

# Check if we have configuration
config_output=$("$CLI_BIN" config show --json 2>/dev/null || echo '{}')
has_config=$(echo "$config_output" | jq -r '.data // empty' 2>/dev/null)

if [[ -z "$has_config" ]] || [[ "$has_config" == "{}" ]]; then
exit 0
fi

# Build context message
context="${CLI_NAME} context loaded:"

if [[ -n "$cli_version" ]]; then
context+="\n CLI: v${cli_version}"
fi
auth_json=$({{.Name}} auth status --json 2>/dev/null || echo '{}')
Comment thread
jeremy marked this conversation as resolved.
Outdated

# Show active profile if using named profiles
active_profile=$("$CLI_BIN" profile show --json 2>/dev/null | jq -r '.data.name // empty' 2>/dev/null || true)
if [[ -n "$active_profile" ]]; then
context+="\n Profile: $active_profile"
if ! command -v jq &>/dev/null; then
cat << 'EOF'
<hook-output>
{{.Name}} plugin active.
</hook-output>
EOF
exit 0
fi
Comment thread
jeremy marked this conversation as resolved.
Outdated

# Check if authenticated
auth_status=$("$CLI_BIN" auth status --json 2>/dev/null || echo '{}')
is_auth=$(echo "$auth_status" | jq -r '.data.authenticated // false')

if [[ "$is_auth" != "true" ]]; then
context+="\n Auth: Not authenticated (run: ${CLI_NAME} auth login)"
is_auth=false
if parsed_auth=$(echo "$auth_json" | jq -er '.data.authenticated' 2>/dev/null); then
is_auth="$parsed_auth"
fi

cat << EOF
if [[ "$is_auth" == "true" ]]; then
cat << 'EOF'
<hook-output>
$(echo -e "$context")

Use \`${CLI_NAME}\` commands to interact with the API:
${CLI_NAME} auth login # Authenticate
${CLI_NAME} auth status # Check auth status
${CLI_NAME} doctor # Diagnose configuration
{{.Name}} plugin active.
</hook-output>
EOF
else
cat << 'EOF'
<hook-output>
{{.Name}} plugin active — not authenticated.
Run: {{.Name}} auth login
</hook-output>
EOF
fi
10 changes: 3 additions & 7 deletions seed/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
"name": "37signals",
"email": "support@37signals.com"
},
"hooks": {
"SessionStart": ["hooks/session-start.sh"]
},
"agents": [
"agents/navigator.md",
"agents/context-linker.md"
]
"homepage": "https://github.com/basecamp/{{.Name}}-cli",
"repository": "https://github.com/basecamp/{{.Name}}-cli",
"license": "MIT"
}
Loading