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 |
|
||||
|-------|--------|---------|
|
||||
| `SessionStart` | `scripts/session-start.sh` | Load context, check for pending items |
|
||||
| `PreCompact` | `scripts/pre-compact.sh` | Remind to preserve context before compaction |
|
||||
|
||||
## 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
|
||||
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:
|
||||
- Bash
|
||||
- Read
|
||||
---
|
||||
|
||||
# Google Calendar Skill
|
||||
|
||||
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` | Today's agenda |
|
||||
| `tomorrow` | Tomorrow's agenda |
|
||||
| `week` | Next 7 days, grouped by day |
|
||||
| `next` | Next upcoming event only |
|
||||
| `summary` | Sonnet-powered week analysis |
|
||||
# Today's agenda
|
||||
$GCAL_PY $SCRIPTS/agenda.py today
|
||||
|
||||
### 2. Natural Language Fallback
|
||||
# Tomorrow's agenda
|
||||
$GCAL_PY $SCRIPTS/agenda.py tomorrow
|
||||
|
||||
| Input | Routes To |
|
||||
|-------|-----------|
|
||||
| "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 |
|
||||
# This week (7 days)
|
||||
$GCAL_PY $SCRIPTS/agenda.py week
|
||||
|
||||
### 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
|
||||
- After 6pm → tomorrow
|
||||
|
||||
### 4. Ambiguous Input
|
||||
## Delegation Helper (Advanced)
|
||||
|
||||
Ask for clarification rather than guess.
|
||||
|
||||
## Delegated Operations
|
||||
|
||||
Use the delegation helper for cost-efficient operations:
|
||||
For LLM-assisted operations:
|
||||
|
||||
```bash
|
||||
GCAL_PY=~/.claude/mcp/gmail/venv/bin/python
|
||||
@@ -50,27 +61,13 @@ HELPER=~/.claude/mcp/delegation/gcal_delegate.py
|
||||
|
||||
# Haiku tier - fetch and format
|
||||
$GCAL_PY $HELPER today
|
||||
$GCAL_PY $HELPER tomorrow
|
||||
$GCAL_PY $HELPER week
|
||||
$GCAL_PY $HELPER next
|
||||
|
||||
# Sonnet tier - analyze (spawns claude --model sonnet)
|
||||
$GCAL_PY $HELPER summary
|
||||
```
|
||||
|
||||
## Delegation Tiers
|
||||
|
||||
| Subcommand | Tier | Reason |
|
||||
|------------|------|--------|
|
||||
| today, tomorrow, week, next | Haiku | Fetch + format only |
|
||||
| summary | Sonnet | Requires understanding/analysis |
|
||||
|
||||
## Output Format
|
||||
|
||||
The helper returns JSON. Format for user as:
|
||||
|
||||
### Simple List (today/tomorrow/next)
|
||||
|
||||
```
|
||||
📅 Today — Thursday, Jan 2
|
||||
|
||||
@@ -83,49 +80,25 @@ The helper returns JSON. Format for user as:
|
||||
No more events today.
|
||||
```
|
||||
|
||||
### Grouped by Day (week)
|
||||
|
||||
```
|
||||
📅 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)
|
||||
### Context Fields
|
||||
|
||||
- 📍 Location or meeting link
|
||||
- 👥 Attendee count
|
||||
- 📝 Description snippet (first ~50 chars)
|
||||
- 📝 Description snippet (if relevant)
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Missing Calendar Scope
|
||||
|
||||
If helper reports scope error:
|
||||
|
||||
```
|
||||
Calendar access not authorized. To fix:
|
||||
1. Delete cached token: rm ~/.gmail-mcp/token.json
|
||||
2. Run /gcal today to re-authenticate with Calendar scope
|
||||
```
|
||||
|
||||
### No Events
|
||||
|
||||
```
|
||||
📅 Today — Thursday, Jan 2
|
||||
|
||||
No events scheduled.
|
||||
```
|
||||
|
||||
## Policy
|
||||
|
||||
- Read-only operations only
|
||||
- Show context (attendees, location) by default
|
||||
- Summarize results, don't dump raw data
|
||||
- Start with lowest capable model tier
|
||||
- Escalate only when task complexity requires
|
||||
- **Read-only** operations only
|
||||
- Show **context** (attendees, location) by default
|
||||
- **Summarize** results, don't dump raw data
|
||||
- Start with **lowest capable** model tier
|
||||
|
||||
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