@forwardimpact/basecamp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +229 -0
- package/build.js +124 -0
- package/config/scheduler.json +28 -0
- package/package.json +37 -0
- package/scheduler.js +552 -0
- package/scripts/build-pkg.sh +117 -0
- package/scripts/compile.sh +26 -0
- package/scripts/install.sh +108 -0
- package/scripts/pkg-resources/conclusion.html +62 -0
- package/scripts/pkg-resources/welcome.html +64 -0
- package/scripts/postinstall +46 -0
- package/scripts/uninstall.sh +56 -0
- package/template/.claude/settings.json +40 -0
- package/template/.claude/skills/create-presentations/SKILL.md +75 -0
- package/template/.claude/skills/create-presentations/references/slide.css +35 -0
- package/template/.claude/skills/create-presentations/scripts/convert-to-pdf.js +32 -0
- package/template/.claude/skills/doc-collab/SKILL.md +112 -0
- package/template/.claude/skills/draft-emails/SKILL.md +191 -0
- package/template/.claude/skills/draft-emails/scripts/scan-emails.sh +33 -0
- package/template/.claude/skills/extract-entities/SKILL.md +466 -0
- package/template/.claude/skills/extract-entities/references/TEMPLATES.md +131 -0
- package/template/.claude/skills/extract-entities/scripts/state.py +100 -0
- package/template/.claude/skills/meeting-prep/SKILL.md +135 -0
- package/template/.claude/skills/organize-files/SKILL.md +146 -0
- package/template/.claude/skills/organize-files/scripts/organize-by-type.sh +42 -0
- package/template/.claude/skills/organize-files/scripts/summarize.sh +21 -0
- package/template/.claude/skills/sync-apple-calendar/SKILL.md +101 -0
- package/template/.claude/skills/sync-apple-calendar/references/SCHEMA.md +80 -0
- package/template/.claude/skills/sync-apple-calendar/scripts/sync.py +233 -0
- package/template/.claude/skills/sync-apple-mail/SKILL.md +131 -0
- package/template/.claude/skills/sync-apple-mail/references/SCHEMA.md +88 -0
- package/template/.claude/skills/sync-apple-mail/scripts/parse-emlx.py +104 -0
- package/template/.claude/skills/sync-apple-mail/scripts/sync.py +348 -0
- package/template/CLAUDE.md +152 -0
- package/template/USER.md +5 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Note Templates
|
|
2
|
+
|
|
3
|
+
Templates for creating new knowledge base notes.
|
|
4
|
+
|
|
5
|
+
## People
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
# {Full Name}
|
|
9
|
+
|
|
10
|
+
## Info
|
|
11
|
+
**Role:** {role or inferred role with qualifier}
|
|
12
|
+
**Organization:** [[Organizations/{organization}]]
|
|
13
|
+
**Email:** {email}
|
|
14
|
+
**Aliases:** {comma-separated variants}
|
|
15
|
+
**First met:** {YYYY-MM-DD}
|
|
16
|
+
**Last seen:** {YYYY-MM-DD}
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
{2-3 sentences: who they are, why you know them, what you're working on}
|
|
20
|
+
|
|
21
|
+
## Connected to
|
|
22
|
+
- [[Organizations/{Org}]] — works at
|
|
23
|
+
- [[People/{Person}]] — {relationship}
|
|
24
|
+
- [[Projects/{Project}]] — {role}
|
|
25
|
+
|
|
26
|
+
## Activity
|
|
27
|
+
- **{YYYY-MM-DD}** ({meeting|email|voice memo}): {Summary with [[Folder/Name]] links}
|
|
28
|
+
|
|
29
|
+
## Key facts
|
|
30
|
+
{substantive facts only — leave empty if none}
|
|
31
|
+
|
|
32
|
+
## Open items
|
|
33
|
+
{commitments and next steps only — leave empty if none}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Organizations
|
|
37
|
+
|
|
38
|
+
```markdown
|
|
39
|
+
# {Organization Name}
|
|
40
|
+
|
|
41
|
+
## Info
|
|
42
|
+
**Type:** {company|team|institution}
|
|
43
|
+
**Industry:** {industry}
|
|
44
|
+
**Relationship:** {customer|prospect|partner|vendor}
|
|
45
|
+
**Domain:** {primary email domain}
|
|
46
|
+
**Aliases:** {comma-separated}
|
|
47
|
+
**First met:** {YYYY-MM-DD}
|
|
48
|
+
**Last seen:** {YYYY-MM-DD}
|
|
49
|
+
|
|
50
|
+
## Summary
|
|
51
|
+
{2-3 sentences}
|
|
52
|
+
|
|
53
|
+
## People
|
|
54
|
+
- [[People/{Person}]] — {role}
|
|
55
|
+
|
|
56
|
+
## Contacts
|
|
57
|
+
{for transactional contacts who don't get their own notes}
|
|
58
|
+
|
|
59
|
+
## Projects
|
|
60
|
+
- [[Projects/{Project}]] — {relationship}
|
|
61
|
+
|
|
62
|
+
## Activity
|
|
63
|
+
- **{YYYY-MM-DD}** ({type}): {Summary}
|
|
64
|
+
|
|
65
|
+
## Key facts
|
|
66
|
+
|
|
67
|
+
## Open items
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Projects
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+
# {Project Name}
|
|
74
|
+
|
|
75
|
+
## Info
|
|
76
|
+
**Type:** {deal|product|initiative|hiring}
|
|
77
|
+
**Status:** {active|planning|on hold|completed|cancelled}
|
|
78
|
+
**Started:** {YYYY-MM-DD}
|
|
79
|
+
**Last activity:** {YYYY-MM-DD}
|
|
80
|
+
|
|
81
|
+
## Summary
|
|
82
|
+
{2-3 sentences}
|
|
83
|
+
|
|
84
|
+
## People
|
|
85
|
+
- [[People/{Person}]] — {role}
|
|
86
|
+
|
|
87
|
+
## Organizations
|
|
88
|
+
- [[Organizations/{Org}]] — {relationship}
|
|
89
|
+
|
|
90
|
+
## Related
|
|
91
|
+
- [[Topics/{Topic}]] — {relationship}
|
|
92
|
+
|
|
93
|
+
## Timeline
|
|
94
|
+
**{YYYY-MM-DD}** ({type})
|
|
95
|
+
{What happened with [[links]]}
|
|
96
|
+
|
|
97
|
+
## Decisions
|
|
98
|
+
- **{YYYY-MM-DD}**: {Decision}. {Rationale}.
|
|
99
|
+
|
|
100
|
+
## Open items
|
|
101
|
+
|
|
102
|
+
## Key facts
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Topics
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
# {Topic Name}
|
|
109
|
+
|
|
110
|
+
## About
|
|
111
|
+
{1-2 sentences}
|
|
112
|
+
|
|
113
|
+
**Keywords:** {comma-separated}
|
|
114
|
+
**Aliases:** {other references}
|
|
115
|
+
**First mentioned:** {YYYY-MM-DD}
|
|
116
|
+
**Last mentioned:** {YYYY-MM-DD}
|
|
117
|
+
|
|
118
|
+
## Related
|
|
119
|
+
- [[People/{Person}]] — {relationship}
|
|
120
|
+
- [[Organizations/{Org}]] — {relationship}
|
|
121
|
+
|
|
122
|
+
## Log
|
|
123
|
+
**{YYYY-MM-DD}** ({type}: {title})
|
|
124
|
+
{Summary with [[links]]}
|
|
125
|
+
|
|
126
|
+
## Decisions
|
|
127
|
+
|
|
128
|
+
## Open items
|
|
129
|
+
|
|
130
|
+
## Key facts
|
|
131
|
+
```
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manage graph_processed state for entity extraction.
|
|
3
|
+
|
|
4
|
+
Commands:
|
|
5
|
+
check - Find unprocessed or changed source files
|
|
6
|
+
update - Mark a file as processed (updates its hash in state)
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 scripts/state.py check # List new/changed files
|
|
10
|
+
python3 scripts/state.py update <file-path> # Mark file as processed
|
|
11
|
+
python3 scripts/state.py update <file1> <file2> … # Mark multiple files
|
|
12
|
+
|
|
13
|
+
State file: ~/.cache/fit/basecamp/state/graph_processed (TSV: path<TAB>hash)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
STATE_FILE = Path.home() / ".cache/fit/basecamp/state/graph_processed"
|
|
22
|
+
SOURCE_DIRS = [
|
|
23
|
+
Path.home() / ".cache/fit/basecamp/apple_mail",
|
|
24
|
+
Path.home() / ".cache/fit/basecamp/apple_calendar",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def file_hash(path):
|
|
29
|
+
"""Compute SHA-256 hash of a file."""
|
|
30
|
+
h = hashlib.sha256()
|
|
31
|
+
with open(path, "rb") as f:
|
|
32
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
33
|
+
h.update(chunk)
|
|
34
|
+
return h.hexdigest()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_state():
|
|
38
|
+
"""Load the state file into a dict of {path: hash}."""
|
|
39
|
+
state = {}
|
|
40
|
+
if STATE_FILE.exists():
|
|
41
|
+
for line in STATE_FILE.read_text().splitlines():
|
|
42
|
+
parts = line.split("\t", 1)
|
|
43
|
+
if len(parts) == 2:
|
|
44
|
+
state[parts[0]] = parts[1]
|
|
45
|
+
return state
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def save_state(state):
|
|
49
|
+
"""Write the full state dict back to the state file."""
|
|
50
|
+
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
lines = [f"{path}\t{h}" for path, h in sorted(state.items())]
|
|
52
|
+
STATE_FILE.write_text("\n".join(lines) + "\n" if lines else "")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check():
|
|
56
|
+
"""Find source files that are new or have changed since last processing."""
|
|
57
|
+
state = load_state()
|
|
58
|
+
new_files = []
|
|
59
|
+
for source_dir in SOURCE_DIRS:
|
|
60
|
+
if not source_dir.is_dir():
|
|
61
|
+
continue
|
|
62
|
+
for f in source_dir.iterdir():
|
|
63
|
+
if not f.is_file():
|
|
64
|
+
continue
|
|
65
|
+
path_str = str(f)
|
|
66
|
+
h = file_hash(f)
|
|
67
|
+
if state.get(path_str) != h:
|
|
68
|
+
new_files.append(path_str)
|
|
69
|
+
for f in sorted(new_files):
|
|
70
|
+
print(f)
|
|
71
|
+
return len(new_files)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def update(file_paths):
|
|
75
|
+
"""Mark files as processed by updating their hashes in state."""
|
|
76
|
+
state = load_state()
|
|
77
|
+
for fp in file_paths:
|
|
78
|
+
p = Path(fp)
|
|
79
|
+
if not p.exists():
|
|
80
|
+
print(f"Warning: File not found: {fp}", file=sys.stderr)
|
|
81
|
+
continue
|
|
82
|
+
state[str(p)] = file_hash(p)
|
|
83
|
+
save_state(state)
|
|
84
|
+
print(f"Updated {len(file_paths)} file(s) in graph state")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
if len(sys.argv) < 2:
|
|
89
|
+
print(__doc__, file=sys.stderr)
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
cmd = sys.argv[1]
|
|
93
|
+
if cmd == "check":
|
|
94
|
+
count = check()
|
|
95
|
+
print(f"\n{count} file(s) to process", file=sys.stderr)
|
|
96
|
+
elif cmd == "update" and len(sys.argv) >= 3:
|
|
97
|
+
update(sys.argv[2:])
|
|
98
|
+
else:
|
|
99
|
+
print(__doc__, file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: meeting-prep
|
|
3
|
+
description: Prepare for meetings by gathering context from the knowledge base and calendar. Use when the user asks to prep for a meeting or wants a briefing on upcoming meetings. Creates personalized briefings with attendee history, open items, and suggested talking points.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Meeting Prep
|
|
7
|
+
|
|
8
|
+
Help the user prepare for meetings by gathering context from the knowledge base
|
|
9
|
+
and calendar. Creates personalized briefing documents with attendee history,
|
|
10
|
+
open items, and suggested talking points.
|
|
11
|
+
|
|
12
|
+
## Trigger
|
|
13
|
+
|
|
14
|
+
Run when the user asks to prep for a meeting, or wants a briefing on upcoming
|
|
15
|
+
meetings.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- Calendar data synced in `~/.cache/fit/basecamp/apple_calendar/`
|
|
20
|
+
- Knowledge base populated (from `extract-entities` skill)
|
|
21
|
+
|
|
22
|
+
## Inputs
|
|
23
|
+
|
|
24
|
+
- `~/.cache/fit/basecamp/apple_calendar/*.json` — calendar events
|
|
25
|
+
- `knowledge/People/*.md` — attendee context
|
|
26
|
+
- `knowledge/Organizations/*.md` — company context
|
|
27
|
+
- `knowledge/Projects/*.md` — project context
|
|
28
|
+
|
|
29
|
+
## Outputs
|
|
30
|
+
|
|
31
|
+
- Meeting brief printed to the user (not saved to a file)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Critical: Always Look Up Context First
|
|
36
|
+
|
|
37
|
+
**BEFORE creating any meeting brief, you MUST look up the attendees in the
|
|
38
|
+
knowledge base.**
|
|
39
|
+
|
|
40
|
+
When the user asks to prep for a meeting:
|
|
41
|
+
|
|
42
|
+
1. **STOP** — Do not create a generic brief
|
|
43
|
+
2. **SEARCH** — Look up each attendee: `rg -l "Attendee Name" knowledge/`
|
|
44
|
+
3. **READ** — Read their notes: `cat "knowledge/People/Attendee Name.md"`
|
|
45
|
+
4. **UNDERSTAND** — Extract role, organization, history, open items
|
|
46
|
+
5. **THEN BRIEF** — Create the meeting brief using this context
|
|
47
|
+
|
|
48
|
+
## Key Principles
|
|
49
|
+
|
|
50
|
+
**Ask, don't guess:**
|
|
51
|
+
|
|
52
|
+
- If unclear which meeting, ASK
|
|
53
|
+
- If multiple upcoming meetings, offer choices
|
|
54
|
+
- **WRONG:** "Here's a generic meeting prep template"
|
|
55
|
+
- **RIGHT:** "I see meetings with Sarah (2pm) and John (4pm). Which one?"
|
|
56
|
+
|
|
57
|
+
**Be thorough, not generic:**
|
|
58
|
+
|
|
59
|
+
- Include specific history, open items, and context from knowledge base
|
|
60
|
+
- Reference actual past interactions and commitments
|
|
61
|
+
|
|
62
|
+
## Processing Flow
|
|
63
|
+
|
|
64
|
+
### Step 1: Identify the Meeting
|
|
65
|
+
|
|
66
|
+
If specified, look it up in calendar:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
ls ~/.cache/fit/basecamp/apple_calendar/ 2>/dev/null
|
|
70
|
+
cat "$HOME/.cache/fit/basecamp/apple_calendar/event123.json"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If "prep me for my next meeting":
|
|
74
|
+
|
|
75
|
+
- List upcoming events
|
|
76
|
+
- Find the next meeting with external attendees
|
|
77
|
+
- Confirm with user if unclear
|
|
78
|
+
|
|
79
|
+
### Step 2: Parse Calendar Event
|
|
80
|
+
|
|
81
|
+
Extract: summary, start/end time, attendees (names and emails), description.
|
|
82
|
+
|
|
83
|
+
### Step 3: Gather Context from Knowledge Base
|
|
84
|
+
|
|
85
|
+
For each attendee:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
rg -l "attendee_name" knowledge/People/
|
|
89
|
+
rg -l "attendee_email" knowledge/People/
|
|
90
|
+
cat "knowledge/People/Attendee Name.md"
|
|
91
|
+
cat "knowledge/Organizations/Their Company.md"
|
|
92
|
+
rg -l "attendee_name" knowledge/Projects/
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Extract: role/title, company, key facts, previous interactions, open items.
|
|
96
|
+
|
|
97
|
+
### Step 4: Create Meeting Brief
|
|
98
|
+
|
|
99
|
+
Format:
|
|
100
|
+
|
|
101
|
+
```markdown
|
|
102
|
+
Meeting Brief: {Attendee Name}
|
|
103
|
+
{Time} today / {Company}
|
|
104
|
+
|
|
105
|
+
About {First Name}
|
|
106
|
+
{Role at company}. {Key background — 1-2 sentences}. {What they focus on}.
|
|
107
|
+
|
|
108
|
+
Your History
|
|
109
|
+
- {Date}: {Brief description of interaction/outcome}
|
|
110
|
+
- {Date}: {Brief description}
|
|
111
|
+
- {Date}: {Brief description}
|
|
112
|
+
|
|
113
|
+
Open Items
|
|
114
|
+
- {Action item} (they asked {date})
|
|
115
|
+
- {Action item}
|
|
116
|
+
|
|
117
|
+
Suggested Talking Points
|
|
118
|
+
- {Concrete suggestion based on history}
|
|
119
|
+
- {Reference relevant entities with [[wiki-links]]}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Guidelines:**
|
|
123
|
+
|
|
124
|
+
- Use `[[Name]]` wiki-link syntax for cross-references
|
|
125
|
+
- Keep "About" section to 2-3 sentences max
|
|
126
|
+
- History: reverse chronological, 3-5 most relevant items
|
|
127
|
+
- Talking points: concrete, not generic
|
|
128
|
+
- If no notes exist for a person, mention that and offer to create one
|
|
129
|
+
|
|
130
|
+
## Constraints
|
|
131
|
+
|
|
132
|
+
- Only prep for meetings with external attendees
|
|
133
|
+
- Skip internal calendar blocks (DND, Focus Time, Lunch)
|
|
134
|
+
- For meetings with multiple attendees, create sections for each key person
|
|
135
|
+
- Prioritize recent interactions (last 30 days)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: organize-files
|
|
3
|
+
description: Organize, tidy up, and find files in ~/Desktop/ and ~/Downloads/. Use when the user asks to find, organize, clean up, or tidy files on their Mac. Always previews changes before acting and never deletes without explicit confirmation. Extracts entities from document files using the extract-entities skill.
|
|
4
|
+
compatibility: Requires macOS filesystem access
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Organize Files
|
|
8
|
+
|
|
9
|
+
Organize, tidy up, and find files in `~/Desktop/` and `~/Downloads/`. Always
|
|
10
|
+
previews changes before acting and never deletes without explicit confirmation.
|
|
11
|
+
|
|
12
|
+
After organizing, extract entities from document files by invoking the
|
|
13
|
+
**`extract-entities`** skill.
|
|
14
|
+
|
|
15
|
+
## Trigger
|
|
16
|
+
|
|
17
|
+
Run when the user asks to find, organize, clean up, or tidy files on their Mac.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- macOS filesystem access
|
|
22
|
+
|
|
23
|
+
## Inputs
|
|
24
|
+
|
|
25
|
+
- User's description of what to organize or find
|
|
26
|
+
- Source directories: `~/Desktop/` and `~/Downloads/`
|
|
27
|
+
|
|
28
|
+
## Outputs
|
|
29
|
+
|
|
30
|
+
- Organized files moved to logical subdirectories within `~/Desktop/` and
|
|
31
|
+
`~/Downloads/`
|
|
32
|
+
- Entity extraction triggered on document files via the **`extract-entities`**
|
|
33
|
+
skill
|
|
34
|
+
- Summary of actions taken
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Core Capabilities
|
|
39
|
+
|
|
40
|
+
1. **Find files** — Locate files by name, type, or content in `~/Desktop/` and
|
|
41
|
+
`~/Downloads/`
|
|
42
|
+
2. **Organize files** — Move files into logical subfolders
|
|
43
|
+
3. **Tidy up** — Clean up cluttered `~/Desktop/` and `~/Downloads/`
|
|
44
|
+
4. **Create structure** — Set up folder hierarchies
|
|
45
|
+
5. **Extract entities** — After organizing, invoke the **`extract-entities`**
|
|
46
|
+
skill on document files to populate the knowledge graph
|
|
47
|
+
|
|
48
|
+
## Key Principles
|
|
49
|
+
|
|
50
|
+
**Always preview before acting:**
|
|
51
|
+
|
|
52
|
+
- Show what files will be affected BEFORE moving/deleting
|
|
53
|
+
- List proposed changes and ask for confirmation
|
|
54
|
+
|
|
55
|
+
**Be conservative with destructive operations:**
|
|
56
|
+
|
|
57
|
+
- Never delete without explicit confirmation
|
|
58
|
+
- Prefer moving to a "to-review" folder over deleting
|
|
59
|
+
|
|
60
|
+
## Summarizing Contents
|
|
61
|
+
|
|
62
|
+
Get an overview of both directories:
|
|
63
|
+
|
|
64
|
+
bash scripts/summarize.sh
|
|
65
|
+
|
|
66
|
+
## Finding Files
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
find ~/Downloads -maxdepth 1 -name "*.pdf" -type f
|
|
70
|
+
find ~/Desktop -maxdepth 1 -iname "*AI*" -type f
|
|
71
|
+
find ~/Desktop -maxdepth 1 -type f \( -name "*.png" -o -name "*.jpg" \)
|
|
72
|
+
find ~/Downloads -maxdepth 1 -type f -mtime -7 # last 7 days
|
|
73
|
+
find ~/Downloads -maxdepth 1 -type f -mtime +30 # older than 30 days
|
|
74
|
+
find ~/Desktop -maxdepth 1 \( -name "Screenshot*" -o -name "Screen Shot*" \)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Organizing by File Type
|
|
78
|
+
|
|
79
|
+
Organize a directory into type-based subdirectories (Documents, Images,
|
|
80
|
+
Archives, Installers, Screenshots):
|
|
81
|
+
|
|
82
|
+
bash scripts/organize-by-type.sh ~/Downloads
|
|
83
|
+
bash scripts/organize-by-type.sh ~/Desktop
|
|
84
|
+
|
|
85
|
+
The script creates subdirectories and moves matching files. It does NOT delete
|
|
86
|
+
anything.
|
|
87
|
+
|
|
88
|
+
## Entity Extraction
|
|
89
|
+
|
|
90
|
+
After organizing files, identify document files that may contain entity
|
|
91
|
+
information (people, organizations, projects, topics) and invoke the
|
|
92
|
+
**`extract-entities`** skill to process them.
|
|
93
|
+
|
|
94
|
+
### Which files to send for extraction
|
|
95
|
+
|
|
96
|
+
**Include:** `.pdf`, `.txt`, `.md`, `.rtf`, `.doc`, `.docx`, `.csv`, `.xlsx`
|
|
97
|
+
|
|
98
|
+
**Exclude:** Images, installers, archives, media, system files
|
|
99
|
+
|
|
100
|
+
### How to invoke
|
|
101
|
+
|
|
102
|
+
After organizing, collect the paths of document files and invoke the
|
|
103
|
+
**`extract-entities`** skill, passing the file paths as ad-hoc file inputs.
|
|
104
|
+
|
|
105
|
+
## Output Format
|
|
106
|
+
|
|
107
|
+
**Plan:**
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Organization Plan: Desktop & Downloads Cleanup
|
|
111
|
+
|
|
112
|
+
Found 47 files to organize:
|
|
113
|
+
- 23 screenshots → ~/Desktop/Screenshots/
|
|
114
|
+
- 12 PDFs → ~/Downloads/Documents/
|
|
115
|
+
- 8 images → ~/Downloads/Images/
|
|
116
|
+
- 4 DMGs → ~/Downloads/Installers/
|
|
117
|
+
|
|
118
|
+
Document files for entity extraction: 12
|
|
119
|
+
→ Will invoke extract-entities skill after organizing
|
|
120
|
+
|
|
121
|
+
Should I proceed?
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Results:**
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Organization Complete
|
|
128
|
+
|
|
129
|
+
Moved 47 files:
|
|
130
|
+
- 23 screenshots to ~/Desktop/Screenshots/
|
|
131
|
+
- 12 PDFs to ~/Downloads/Documents/
|
|
132
|
+
- 8 images to ~/Downloads/Images/
|
|
133
|
+
- 4 DMGs to ~/Downloads/Installers/
|
|
134
|
+
|
|
135
|
+
Entity extraction: invoked extract-entities on 12 document files
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Safety Rules
|
|
139
|
+
|
|
140
|
+
1. **Never delete without permission** — "cleanup" means organize, not delete
|
|
141
|
+
2. **Don't touch system folders** — /System, /Library, /Applications
|
|
142
|
+
3. **Don't touch hidden files** — files starting with `.` unless asked
|
|
143
|
+
4. **Limit scope** — only operate on `~/Desktop/` and `~/Downloads/`
|
|
144
|
+
5. **Limit depth** — use `-maxdepth 1` unless user wants recursive
|
|
145
|
+
6. **Show before doing** — always preview first
|
|
146
|
+
7. **Quote paths** — handle spaces: `"$HOME/My Documents"`
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Organize files in a directory by type into subdirectories.
|
|
3
|
+
#
|
|
4
|
+
# Usage: bash scripts/organize-by-type.sh <directory>
|
|
5
|
+
#
|
|
6
|
+
# Creates subdirectories (Documents, Images, Archives, Installers, Screenshots)
|
|
7
|
+
# and moves matching files. Only operates on top-level files (-maxdepth 1).
|
|
8
|
+
# Prints each move with -v flag. Does NOT delete any files.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
if [ $# -ne 1 ]; then
|
|
13
|
+
echo "Usage: bash scripts/organize-by-type.sh <directory>" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
DIR="$1"
|
|
18
|
+
|
|
19
|
+
if [ ! -d "$DIR" ]; then
|
|
20
|
+
echo "Error: Directory not found: $DIR" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Create subdirectories
|
|
25
|
+
mkdir -p "$DIR"/{Documents,Images,Archives,Installers,Screenshots}
|
|
26
|
+
|
|
27
|
+
# Screenshots
|
|
28
|
+
find "$DIR" -maxdepth 1 -type f \( -name "Screenshot*" -o -name "Screen Shot*" \) -exec mv -v {} "$DIR/Screenshots/" \;
|
|
29
|
+
|
|
30
|
+
# Documents
|
|
31
|
+
find "$DIR" -maxdepth 1 -type f \( -name "*.pdf" -o -name "*.doc*" -o -name "*.txt" -o -name "*.md" -o -name "*.rtf" -o -name "*.csv" -o -name "*.xlsx" \) -exec mv -v {} "$DIR/Documents/" \;
|
|
32
|
+
|
|
33
|
+
# Images (excluding screenshots already moved)
|
|
34
|
+
find "$DIR" -maxdepth 1 -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.gif" -o -name "*.webp" \) -exec mv -v {} "$DIR/Images/" \;
|
|
35
|
+
|
|
36
|
+
# Archives
|
|
37
|
+
find "$DIR" -maxdepth 1 -type f \( -name "*.zip" -o -name "*.tar.gz" -o -name "*.rar" \) -exec mv -v {} "$DIR/Archives/" \;
|
|
38
|
+
|
|
39
|
+
# Installers
|
|
40
|
+
find "$DIR" -maxdepth 1 -type f -name "*.dmg" -exec mv -v {} "$DIR/Installers/" \;
|
|
41
|
+
|
|
42
|
+
echo "Organization complete: $DIR"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Summarize the contents of ~/Desktop/ and ~/Downloads/.
|
|
3
|
+
#
|
|
4
|
+
# Usage: bash scripts/summarize.sh
|
|
5
|
+
#
|
|
6
|
+
# Counts files by type in both directories (top-level only).
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
for dir in "$HOME/Desktop" "$HOME/Downloads"; do
|
|
11
|
+
[ -d "$dir" ] || continue
|
|
12
|
+
echo "=== $(basename "$dir") ==="
|
|
13
|
+
echo "Screenshots: $(find "$dir" -maxdepth 1 \( -name 'Screenshot*' -o -name 'Screen Shot*' \) 2>/dev/null | wc -l | tr -d ' ')"
|
|
14
|
+
echo "PDFs: $(find "$dir" -maxdepth 1 -name '*.pdf' 2>/dev/null | wc -l | tr -d ' ')"
|
|
15
|
+
echo "Images: $(find "$dir" -maxdepth 1 \( -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o -name '*.webp' \) 2>/dev/null | wc -l | tr -d ' ')"
|
|
16
|
+
echo "Documents: $(find "$dir" -maxdepth 1 \( -name '*.doc*' -o -name '*.txt' -o -name '*.md' -o -name '*.rtf' \) 2>/dev/null | wc -l | tr -d ' ')"
|
|
17
|
+
echo "Archives: $(find "$dir" -maxdepth 1 \( -name '*.zip' -o -name '*.tar.gz' -o -name '*.rar' \) 2>/dev/null | wc -l | tr -d ' ')"
|
|
18
|
+
echo "Installers: $(find "$dir" -maxdepth 1 -name '*.dmg' 2>/dev/null | wc -l | tr -d ' ')"
|
|
19
|
+
echo "Other: $(find "$dir" -maxdepth 1 -type f ! \( -name 'Screenshot*' -o -name 'Screen Shot*' -o -name '*.pdf' -o -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o -name '*.webp' -o -name '*.doc*' -o -name '*.txt' -o -name '*.md' -o -name '*.rtf' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.rar' -o -name '*.dmg' -o -name '.DS_Store' -o -name '.localized' \) 2>/dev/null | wc -l | tr -d ' ')"
|
|
20
|
+
echo ""
|
|
21
|
+
done
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sync-apple-calendar
|
|
3
|
+
description: Sync calendar events from the macOS Calendar app's local SQLite database into ~/.cache/fit/basecamp/apple_calendar/ as JSON files. Use on a schedule or when the user asks to sync their calendar. Requires macOS with Calendar app configured and Full Disk Access granted.
|
|
4
|
+
compatibility: Requires macOS with Apple Calendar configured and Full Disk Access granted to the terminal
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Sync Apple Calendar
|
|
8
|
+
|
|
9
|
+
Sync calendar events from the macOS Calendar app's local SQLite database into
|
|
10
|
+
`~/.cache/fit/basecamp/apple_calendar/` as JSON files. This is an automated data
|
|
11
|
+
pipeline skill — it ingests raw calendar data that other skills (like
|
|
12
|
+
`extract-entities` and `meeting-prep`) consume downstream.
|
|
13
|
+
|
|
14
|
+
## Trigger
|
|
15
|
+
|
|
16
|
+
Run this skill on a schedule (every 5 minutes) or when the user asks to sync
|
|
17
|
+
their calendar.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- macOS with the built-in Calendar app configured
|
|
22
|
+
- Full Disk Access granted to the terminal (System Settings → Privacy & Security
|
|
23
|
+
→ Full Disk Access)
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
- `~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb` —
|
|
28
|
+
Apple Calendar SQLite database (Sonoma+/macOS 14+)
|
|
29
|
+
- `~/Library/Calendars/Calendar.sqlitedb` — fallback path for older macOS
|
|
30
|
+
|
|
31
|
+
## Outputs
|
|
32
|
+
|
|
33
|
+
- `~/.cache/fit/basecamp/apple_calendar/{event_id}.json` — one JSON file per
|
|
34
|
+
event (14-day sliding window)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Implementation
|
|
39
|
+
|
|
40
|
+
Run the sync as a single Python script. This avoids N+1 sqlite3 invocations (one
|
|
41
|
+
per event for attendees) and handles all data transformation in one pass:
|
|
42
|
+
|
|
43
|
+
python3 scripts/sync.py
|
|
44
|
+
|
|
45
|
+
The script:
|
|
46
|
+
|
|
47
|
+
1. Finds the Calendar database (Sonoma+ path first, then fallback)
|
|
48
|
+
2. Queries all events in a 14-day past/future window with a single SQL query
|
|
49
|
+
3. Batch-fetches all attendees for those events in one query
|
|
50
|
+
4. Writes one JSON file per event to `~/.cache/fit/basecamp/apple_calendar/`
|
|
51
|
+
5. Cleans up JSON files for events now outside the window
|
|
52
|
+
6. Reports summary (events synced, files cleaned up)
|
|
53
|
+
|
|
54
|
+
## Database Schema
|
|
55
|
+
|
|
56
|
+
See [references/SCHEMA.md](references/SCHEMA.md) for the complete Apple Calendar
|
|
57
|
+
SQLite schema including table structures, column names, and important caveats
|
|
58
|
+
(e.g., Identity uses `address` not `email`, Participant has no `display_name`).
|
|
59
|
+
|
|
60
|
+
## Output Format
|
|
61
|
+
|
|
62
|
+
Each `{event_id}.json` file:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"id": "apple_cal_123",
|
|
67
|
+
"summary": "Meeting with Sarah Chen",
|
|
68
|
+
"start": { "dateTime": "2025-02-12T15:00:00+01:00", "timeZone": "Europe/Paris" },
|
|
69
|
+
"end": { "dateTime": "2025-02-12T16:00:00+01:00", "timeZone": "Europe/Paris" },
|
|
70
|
+
"allDay": false,
|
|
71
|
+
"location": "Zoom",
|
|
72
|
+
"description": "Discuss Q2 roadmap",
|
|
73
|
+
"conferenceUrl": null,
|
|
74
|
+
"calendar": "Calendar",
|
|
75
|
+
"organizer": { "email": "sarah@acme.com", "name": "Chen, Sarah" },
|
|
76
|
+
"attendees": [
|
|
77
|
+
{
|
|
78
|
+
"email": "sarah@acme.com",
|
|
79
|
+
"name": "Chen, Sarah",
|
|
80
|
+
"status": "accepted",
|
|
81
|
+
"role": "required",
|
|
82
|
+
"self": false
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Error Handling
|
|
89
|
+
|
|
90
|
+
- Database not found → Calendar not configured, report and stop
|
|
91
|
+
- Permission denied → Full Disk Access not granted, report and stop
|
|
92
|
+
- Database locked → wait 2 seconds, retry once
|
|
93
|
+
- Skip events with no summary (likely cancelled or placeholder)
|
|
94
|
+
|
|
95
|
+
## Constraints
|
|
96
|
+
|
|
97
|
+
- Open database read-only (`-readonly`)
|
|
98
|
+
- This sync is stateless — always queries the current 14-day window
|
|
99
|
+
- All-day events may have null end times — use start date as end date
|
|
100
|
+
- All-day events have timezone `_float` — omit timezone from output
|
|
101
|
+
- Output format matches Google Calendar event format for downstream consistency
|