Add documentation, PreCompact hook, gcal improvements, and marketplace
Documentation: - Add commands/README.md documenting all slash commands - Add skills/README.md documenting skill structure and patterns - Add .claude-plugin/marketplace.json for local dev testing Hooks: - Add PreCompact hook to remind about context preservation - Update hooks/README.md with new hook GCal improvements: - Add scripts/next_event.py for single event lookup - Update SKILL.md with simplified format and allowed-tools: Read 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
18
.claude-plugin/marketplace.json
Normal file
18
.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "will-homelab-dev",
|
||||||
|
"description": "Development marketplace for will-homelab plugin",
|
||||||
|
"owner": {
|
||||||
|
"name": "Will"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "will-homelab",
|
||||||
|
"description": "Personal assistant and multi-agent system for homelab management",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"source": "./",
|
||||||
|
"author": {
|
||||||
|
"name": "Will"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
commands/README.md
Normal file
81
commands/README.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Commands
|
||||||
|
|
||||||
|
Slash commands for quick actions. User-invoked (type `/command` to trigger).
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
### Top-Level
|
||||||
|
|
||||||
|
| Command | Aliases | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `/pa` | `/assistant`, `/ask` | Personal assistant entrypoint |
|
||||||
|
| `/programmer` | | Code development tasks |
|
||||||
|
| `/gcal` | `/calendar`, `/cal` | Google Calendar access |
|
||||||
|
| `/usage` | `/stats` | View usage statistics |
|
||||||
|
|
||||||
|
### Kubernetes (`/k8s:*`)
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/k8s:cluster-status` | Quick cluster health overview |
|
||||||
|
| `/k8s:deploy` | Deploy applications to cluster |
|
||||||
|
| `/k8s:diagnose` | Diagnose Kubernetes issues |
|
||||||
|
|
||||||
|
### System Admin (`/sysadmin:*`)
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/sysadmin:health` | System health check |
|
||||||
|
| `/sysadmin:update` | System package updates |
|
||||||
|
| `/sysadmin:autonomy` | Set autonomy level for session |
|
||||||
|
|
||||||
|
## Command Format
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
name: command-name
|
||||||
|
description: What this command does
|
||||||
|
aliases: [alias1, alias2]
|
||||||
|
invokes: skill:skill-name # or workflow: or agent:
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command Title
|
||||||
|
|
||||||
|
Instructions for Claude when command is invoked.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command vs Skill
|
||||||
|
|
||||||
|
| Aspect | Command | Skill |
|
||||||
|
|--------|---------|-------|
|
||||||
|
| **Invocation** | User types `/command` | Claude decides automatically |
|
||||||
|
| **Discovery** | Explicit | Based on description matching |
|
||||||
|
| **Use case** | Quick actions, shortcuts | Domain expertise, workflows |
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
commands/
|
||||||
|
├── README.md
|
||||||
|
├── pa.md
|
||||||
|
├── gcal.md
|
||||||
|
├── usage.md
|
||||||
|
├── programmer.md
|
||||||
|
├── k8s/
|
||||||
|
│ ├── cluster-status.md
|
||||||
|
│ ├── deploy.md
|
||||||
|
│ └── diagnose.md
|
||||||
|
└── sysadmin/
|
||||||
|
├── health.md
|
||||||
|
├── update.md
|
||||||
|
└── autonomy.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Namespaced commands use subdirectories (e.g., `k8s/deploy.md` → `/k8s:deploy`).
|
||||||
|
|
||||||
|
## Adding Commands
|
||||||
|
|
||||||
|
1. Create `commands/name.md` (or `commands/namespace/name.md`)
|
||||||
|
2. Add YAML frontmatter with name, description, invokes
|
||||||
|
3. Write instructions in Markdown body
|
||||||
|
4. Update `component-registry.json` if needed for routing
|
||||||
@@ -7,6 +7,7 @@ Event handlers that run automatically during Claude Code sessions.
|
|||||||
| Event | Script | Purpose |
|
| Event | Script | Purpose |
|
||||||
|-------|--------|---------|
|
|-------|--------|---------|
|
||||||
| `SessionStart` | `scripts/session-start.sh` | Load context, check for pending items |
|
| `SessionStart` | `scripts/session-start.sh` | Load context, check for pending items |
|
||||||
|
| `PreCompact` | `scripts/pre-compact.sh` | Remind to preserve context before compaction |
|
||||||
|
|
||||||
## Hook Events
|
## Hook Events
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,16 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"PreCompact": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.claude/hooks/scripts/pre-compact.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
hooks/scripts/pre-compact.sh
Executable file
13
hooks/scripts/pre-compact.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Pre-compact hook - saves context before compaction
|
||||||
|
# Output reminds Claude to summarize important context
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "PreCompact:Callback hook success: Success"
|
||||||
|
echo "PreCompact hook additional context: <EXTREMELY_IMPORTANT>"
|
||||||
|
echo "Context compaction is about to occur. Before compacting:"
|
||||||
|
echo "1. Note any pending tasks or decisions that need to be preserved"
|
||||||
|
echo "2. Summarize key context that should persist"
|
||||||
|
echo "3. If working on a multi-step task, note current progress"
|
||||||
|
echo "</EXTREMELY_IMPORTANT>"
|
||||||
105
skills/README.md
Normal file
105
skills/README.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Skills
|
||||||
|
|
||||||
|
Agent skills that extend Claude's capabilities. Model-invoked (Claude decides when to use them).
|
||||||
|
|
||||||
|
## Available Skills
|
||||||
|
|
||||||
|
| Skill | Description | Scripts |
|
||||||
|
|-------|-------------|---------|
|
||||||
|
| `gmail` | Gmail read access | `check_unread.py`, `check_urgent.py`, `search.py` |
|
||||||
|
| `gcal` | Google Calendar access | `agenda.py`, `next_event.py` |
|
||||||
|
| `k8s-quick-status` | Kubernetes cluster health | `quick-status.sh` |
|
||||||
|
| `sysadmin-health` | Arch Linux health check | `health-check.sh` |
|
||||||
|
| `usage` | Session usage tracking | `usage_report.py` |
|
||||||
|
| `programmer-add-project` | Register projects | (workflow only) |
|
||||||
|
|
||||||
|
## Skill Structure
|
||||||
|
|
||||||
|
Each skill uses the "Resources Pattern":
|
||||||
|
|
||||||
|
```
|
||||||
|
skills/skill-name/
|
||||||
|
├── SKILL.md # Main instructions (required)
|
||||||
|
├── scripts/ # Executable helpers
|
||||||
|
│ ├── action1.py
|
||||||
|
│ └── action2.sh
|
||||||
|
└── references/ # Documentation
|
||||||
|
└── patterns.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skill Format
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
name: skill-name
|
||||||
|
description: What it does. Use when [trigger conditions].
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skill Name
|
||||||
|
|
||||||
|
## Quick Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/main-action.py [args]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Request Routing
|
||||||
|
|
||||||
|
| User Request | Action |
|
||||||
|
|--------------|--------|
|
||||||
|
| "do X" | Run script with args |
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
- Read-only / Write allowed
|
||||||
|
- Model tier preferences
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Elements
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
Critical for discovery. Include:
|
||||||
|
- What the skill does
|
||||||
|
- When Claude should use it (trigger phrases)
|
||||||
|
|
||||||
|
### allowed-tools
|
||||||
|
|
||||||
|
Restricts which tools Claude can use:
|
||||||
|
- `Bash` - Run commands/scripts
|
||||||
|
- `Read` - Read files
|
||||||
|
- `Write` - Write files
|
||||||
|
- `Edit` - Edit files
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
|
||||||
|
Standalone executables that do the work:
|
||||||
|
- Python: `#!/usr/bin/env python3`
|
||||||
|
- Bash: `#!/bin/bash`
|
||||||
|
- Must be executable: `chmod +x`
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
Supporting documentation:
|
||||||
|
- Query patterns
|
||||||
|
- API reference
|
||||||
|
- Examples
|
||||||
|
|
||||||
|
## Adding a Skill
|
||||||
|
|
||||||
|
1. Create `skills/name/SKILL.md`
|
||||||
|
2. Add scripts to `skills/name/scripts/`
|
||||||
|
3. Make scripts executable
|
||||||
|
4. Update `component-registry.json` with triggers
|
||||||
|
5. Test: ask Claude something that should trigger it
|
||||||
|
|
||||||
|
## Skill vs Command
|
||||||
|
|
||||||
|
| Aspect | Skill | Command |
|
||||||
|
|--------|-------|---------|
|
||||||
|
| **Invocation** | Claude decides | User types `/command` |
|
||||||
|
| **Discovery** | Based on description | Explicit |
|
||||||
|
| **Use case** | Domain expertise | Quick actions |
|
||||||
@@ -1,48 +1,59 @@
|
|||||||
---
|
---
|
||||||
name: gcal
|
name: gcal
|
||||||
description: Google Calendar read access — agenda overview, event details, smart summaries
|
description: Google Calendar read access — agenda, events, schedule. Use when asked about calendar, meetings, schedule, or what's on today/tomorrow.
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
|
- Read
|
||||||
---
|
---
|
||||||
|
|
||||||
# Google Calendar Skill
|
# Google Calendar Skill
|
||||||
|
|
||||||
Access Google Calendar via Python API. Uses OAuth credentials at `~/.gmail-mcp/`.
|
Access Google Calendar via Python API. Uses OAuth credentials at `~/.gmail-mcp/`.
|
||||||
|
|
||||||
## Command Routing
|
## Quick Commands
|
||||||
|
|
||||||
### 1. Exact Subcommand Match (priority)
|
```bash
|
||||||
|
GCAL_PY=~/.claude/mcp/gmail/venv/bin/python
|
||||||
|
SCRIPTS=~/.claude/skills/gcal/scripts
|
||||||
|
|
||||||
| Input | Action |
|
# Today's agenda
|
||||||
|-------|--------|
|
$GCAL_PY $SCRIPTS/agenda.py today
|
||||||
| `today` | Today's agenda |
|
|
||||||
| `tomorrow` | Tomorrow's agenda |
|
|
||||||
| `week` | Next 7 days, grouped by day |
|
|
||||||
| `next` | Next upcoming event only |
|
|
||||||
| `summary` | Sonnet-powered week analysis |
|
|
||||||
|
|
||||||
### 2. Natural Language Fallback
|
# Tomorrow's agenda
|
||||||
|
$GCAL_PY $SCRIPTS/agenda.py tomorrow
|
||||||
|
|
||||||
| Input | Routes To |
|
# This week (7 days)
|
||||||
|-------|-----------|
|
$GCAL_PY $SCRIPTS/agenda.py week
|
||||||
| "what's on today", "today's meetings" | today |
|
|
||||||
| "this week", "next 7 days" | week |
|
|
||||||
| "next meeting", "what's next" | next |
|
|
||||||
| "am I busy tomorrow", "tomorrow's schedule" | tomorrow |
|
|
||||||
| "overview", "summarize my week" | summary |
|
|
||||||
|
|
||||||
### 3. Smart Default (no args)
|
# Next event only
|
||||||
|
$GCAL_PY $SCRIPTS/next_event.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Script Reference
|
||||||
|
|
||||||
|
| Script | Purpose | Args |
|
||||||
|
|--------|---------|------|
|
||||||
|
| `agenda.py` | List events for time range | `today`, `tomorrow`, `week` |
|
||||||
|
| `next_event.py` | Next upcoming event | none |
|
||||||
|
|
||||||
|
## Request Routing
|
||||||
|
|
||||||
|
| User Request | Script | Args |
|
||||||
|
|--------------|--------|------|
|
||||||
|
| "What's on today?" | `agenda.py` | `today` |
|
||||||
|
| "Tomorrow's schedule" | `agenda.py` | `tomorrow` |
|
||||||
|
| "This week" | `agenda.py` | `week` |
|
||||||
|
| "Next meeting" | `next_event.py` | none |
|
||||||
|
| "Am I free at 3pm?" | `agenda.py` | `today` (then analyze) |
|
||||||
|
|
||||||
|
### Smart Default (no args)
|
||||||
|
|
||||||
- Before 6pm → today
|
- Before 6pm → today
|
||||||
- After 6pm → tomorrow
|
- After 6pm → tomorrow
|
||||||
|
|
||||||
### 4. Ambiguous Input
|
## Delegation Helper (Advanced)
|
||||||
|
|
||||||
Ask for clarification rather than guess.
|
For LLM-assisted operations:
|
||||||
|
|
||||||
## Delegated Operations
|
|
||||||
|
|
||||||
Use the delegation helper for cost-efficient operations:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GCAL_PY=~/.claude/mcp/gmail/venv/bin/python
|
GCAL_PY=~/.claude/mcp/gmail/venv/bin/python
|
||||||
@@ -50,27 +61,13 @@ HELPER=~/.claude/mcp/delegation/gcal_delegate.py
|
|||||||
|
|
||||||
# Haiku tier - fetch and format
|
# Haiku tier - fetch and format
|
||||||
$GCAL_PY $HELPER today
|
$GCAL_PY $HELPER today
|
||||||
$GCAL_PY $HELPER tomorrow
|
|
||||||
$GCAL_PY $HELPER week
|
|
||||||
$GCAL_PY $HELPER next
|
|
||||||
|
|
||||||
# Sonnet tier - analyze (spawns claude --model sonnet)
|
# Sonnet tier - analyze (spawns claude --model sonnet)
|
||||||
$GCAL_PY $HELPER summary
|
$GCAL_PY $HELPER summary
|
||||||
```
|
```
|
||||||
|
|
||||||
## Delegation Tiers
|
|
||||||
|
|
||||||
| Subcommand | Tier | Reason |
|
|
||||||
|------------|------|--------|
|
|
||||||
| today, tomorrow, week, next | Haiku | Fetch + format only |
|
|
||||||
| summary | Sonnet | Requires understanding/analysis |
|
|
||||||
|
|
||||||
## Output Format
|
## Output Format
|
||||||
|
|
||||||
The helper returns JSON. Format for user as:
|
|
||||||
|
|
||||||
### Simple List (today/tomorrow/next)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
📅 Today — Thursday, Jan 2
|
📅 Today — Thursday, Jan 2
|
||||||
|
|
||||||
@@ -83,49 +80,25 @@ The helper returns JSON. Format for user as:
|
|||||||
No more events today.
|
No more events today.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Grouped by Day (week)
|
### Context Fields
|
||||||
|
|
||||||
```
|
|
||||||
📅 This Week — Jan 2-8
|
|
||||||
|
|
||||||
━━━ Thursday, Jan 2 ━━━
|
|
||||||
9:00 AM Team standup (30m)
|
|
||||||
2:00 PM Project review (1h)
|
|
||||||
|
|
||||||
━━━ Friday, Jan 3 ━━━
|
|
||||||
11:00 AM Client call (1h)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Context Fields (show when available)
|
|
||||||
|
|
||||||
- 📍 Location or meeting link
|
- 📍 Location or meeting link
|
||||||
- 👥 Attendee count
|
- 👥 Attendee count
|
||||||
- 📝 Description snippet (first ~50 chars)
|
- 📝 Description snippet (if relevant)
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
### Missing Calendar Scope
|
### Missing Calendar Scope
|
||||||
|
|
||||||
If helper reports scope error:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Calendar access not authorized. To fix:
|
Calendar access not authorized. To fix:
|
||||||
1. Delete cached token: rm ~/.gmail-mcp/token.json
|
1. Delete cached token: rm ~/.gmail-mcp/token.json
|
||||||
2. Run /gcal today to re-authenticate with Calendar scope
|
2. Run /gcal today to re-authenticate with Calendar scope
|
||||||
```
|
```
|
||||||
|
|
||||||
### No Events
|
|
||||||
|
|
||||||
```
|
|
||||||
📅 Today — Thursday, Jan 2
|
|
||||||
|
|
||||||
No events scheduled.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Policy
|
## Policy
|
||||||
|
|
||||||
- Read-only operations only
|
- **Read-only** operations only
|
||||||
- Show context (attendees, location) by default
|
- Show **context** (attendees, location) by default
|
||||||
- Summarize results, don't dump raw data
|
- **Summarize** results, don't dump raw data
|
||||||
- Start with lowest capable model tier
|
- Start with **lowest capable** model tier
|
||||||
- Escalate only when task complexity requires
|
|
||||||
|
|||||||
61
skills/gcal/scripts/next_event.py
Executable file
61
skills/gcal/scripts/next_event.py
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Get the next upcoming calendar event."""
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Set credentials path
|
||||||
|
os.environ.setdefault('GMAIL_CREDENTIALS_PATH', os.path.expanduser('~/.gmail-mcp/credentials.json'))
|
||||||
|
|
||||||
|
from gmail_mcp.utils.GCP.gmail_auth import get_calendar_service
|
||||||
|
|
||||||
|
|
||||||
|
def format_event(event):
|
||||||
|
"""Format a single event for display."""
|
||||||
|
start = event['start'].get('dateTime', event['start'].get('date'))
|
||||||
|
|
||||||
|
if 'T' in start:
|
||||||
|
start_dt = datetime.fromisoformat(start.replace('Z', '+00:00'))
|
||||||
|
time_str = start_dt.strftime('%I:%M %p').lstrip('0')
|
||||||
|
date_str = start_dt.strftime('%A, %b %d')
|
||||||
|
else:
|
||||||
|
time_str = "All day"
|
||||||
|
date_str = start
|
||||||
|
|
||||||
|
summary = event.get('summary', '(No title)')
|
||||||
|
location = event.get('location', '')
|
||||||
|
|
||||||
|
print(f"📅 Next Event — {date_str}")
|
||||||
|
print()
|
||||||
|
print(f" {time_str} {summary}")
|
||||||
|
if location:
|
||||||
|
print(f" 📍 {location}")
|
||||||
|
|
||||||
|
# Show attendees if available
|
||||||
|
attendees = event.get('attendees', [])
|
||||||
|
if attendees:
|
||||||
|
print(f" 👥 {len(attendees)} attendees")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
service = get_calendar_service()
|
||||||
|
now = datetime.utcnow().isoformat() + 'Z'
|
||||||
|
|
||||||
|
events_result = service.events().list(
|
||||||
|
calendarId='primary',
|
||||||
|
timeMin=now,
|
||||||
|
maxResults=1,
|
||||||
|
singleEvents=True,
|
||||||
|
orderBy='startTime'
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
events = events_result.get('items', [])
|
||||||
|
|
||||||
|
if not events:
|
||||||
|
print("📅 No upcoming events")
|
||||||
|
return
|
||||||
|
|
||||||
|
format_event(events[0])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user