@forwardimpact/basecamp 2.0.0 → 2.3.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/config/scheduler.json +5 -0
- package/package.json +1 -1
- package/src/basecamp.js +288 -57
- package/template/.claude/agents/chief-of-staff.md +6 -2
- package/template/.claude/agents/concierge.md +2 -3
- package/template/.claude/agents/librarian.md +4 -6
- package/template/.claude/agents/recruiter.md +269 -0
- package/template/.claude/settings.json +0 -4
- package/template/.claude/skills/analyze-cv/SKILL.md +269 -0
- package/template/.claude/skills/create-presentations/SKILL.md +2 -2
- package/template/.claude/skills/create-presentations/references/slide.css +1 -1
- package/template/.claude/skills/create-presentations/scripts/convert-to-pdf.mjs +47 -0
- package/template/.claude/skills/draft-emails/SKILL.md +85 -123
- package/template/.claude/skills/draft-emails/scripts/scan-emails.mjs +66 -0
- package/template/.claude/skills/draft-emails/scripts/send-email.mjs +118 -0
- package/template/.claude/skills/extract-entities/SKILL.md +2 -2
- package/template/.claude/skills/extract-entities/scripts/state.mjs +130 -0
- package/template/.claude/skills/manage-tasks/SKILL.md +242 -0
- package/template/.claude/skills/organize-files/SKILL.md +3 -3
- package/template/.claude/skills/organize-files/scripts/organize-by-type.mjs +105 -0
- package/template/.claude/skills/organize-files/scripts/summarize.mjs +84 -0
- package/template/.claude/skills/process-hyprnote/SKILL.md +2 -2
- package/template/.claude/skills/right-to-be-forgotten/SKILL.md +333 -0
- package/template/.claude/skills/send-chat/SKILL.md +170 -0
- package/template/.claude/skills/sync-apple-calendar/SKILL.md +5 -5
- package/template/.claude/skills/sync-apple-calendar/scripts/sync.mjs +325 -0
- package/template/.claude/skills/sync-apple-mail/SKILL.md +6 -6
- package/template/.claude/skills/sync-apple-mail/scripts/parse-emlx.mjs +374 -0
- package/template/.claude/skills/sync-apple-mail/scripts/sync.mjs +629 -0
- package/template/.claude/skills/track-candidates/SKILL.md +376 -0
- package/template/.claude/skills/upstream-skill/SKILL.md +207 -0
- package/template/.claude/skills/weekly-update/SKILL.md +250 -0
- package/template/CLAUDE.md +68 -40
- package/template/.claude/skills/create-presentations/scripts/convert-to-pdf.js +0 -32
- package/template/.claude/skills/draft-emails/scripts/scan-emails.sh +0 -34
- package/template/.claude/skills/extract-entities/scripts/state.py +0 -100
- package/template/.claude/skills/organize-files/scripts/organize-by-type.sh +0 -42
- package/template/.claude/skills/organize-files/scripts/summarize.sh +0 -21
- package/template/.claude/skills/sync-apple-calendar/scripts/sync.py +0 -242
- package/template/.claude/skills/sync-apple-mail/scripts/parse-emlx.py +0 -104
- package/template/.claude/skills/sync-apple-mail/scripts/sync.py +0 -455
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: weekly-update
|
|
3
|
+
description: Generate or refresh a weekly priorities document. Pulls from task boards, recent activity, and calendar to create a point-in-time snapshot. Use when the user asks to update their weekly, prep for the week, or on a Monday morning schedule.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Weekly Update
|
|
7
|
+
|
|
8
|
+
Generate or refresh a weekly priorities document in
|
|
9
|
+
`knowledge/Weeklies/{Person Name}/`. Each weekly is a point-in-time snapshot —
|
|
10
|
+
created at the start of the week, updated as priorities shift, and closed out
|
|
11
|
+
with a retrospective at end-of-week.
|
|
12
|
+
|
|
13
|
+
## Trigger
|
|
14
|
+
|
|
15
|
+
Run this skill:
|
|
16
|
+
|
|
17
|
+
- When the user asks to update their weekly or prep for the week
|
|
18
|
+
- Monday mornings (scheduled)
|
|
19
|
+
- When the user asks to review someone's weekly
|
|
20
|
+
- End-of-week to fill in accomplishments and retrospective
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- `knowledge/Weeklies/` directory exists
|
|
25
|
+
- `knowledge/Tasks/{Person Name}.md` exists (task board — see `manage-tasks`
|
|
26
|
+
skill)
|
|
27
|
+
- Calendar data in `~/.cache/fit/basecamp/apple_calendar/`
|
|
28
|
+
- User identity configured in `USER.md`
|
|
29
|
+
|
|
30
|
+
## Inputs
|
|
31
|
+
|
|
32
|
+
- `knowledge/Tasks/{Person Name}.md` — current task board
|
|
33
|
+
- `knowledge/People/{Person Name}.md` — recent activity, context
|
|
34
|
+
- `~/.cache/fit/basecamp/apple_calendar/*.json` — calendar events for the week
|
|
35
|
+
- Previous weekly document (if exists) — for continuity and carry-forward
|
|
36
|
+
|
|
37
|
+
## Outputs
|
|
38
|
+
|
|
39
|
+
- `knowledge/Weeklies/{Person Name}/{YYYY}-W{WW}.md` — the weekly document
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Weekly Document Format
|
|
44
|
+
|
|
45
|
+
```markdown
|
|
46
|
+
# {YYYY}-W{WW} — {Person Name}
|
|
47
|
+
|
|
48
|
+
> {Focus statement: one sentence summarizing the week's theme}
|
|
49
|
+
|
|
50
|
+
## Priorities
|
|
51
|
+
|
|
52
|
+
- [ ] Priority one — [[Projects/Name]]
|
|
53
|
+
- [ ] Priority two
|
|
54
|
+
- [x] Completed priority
|
|
55
|
+
|
|
56
|
+
## Key Meetings
|
|
57
|
+
|
|
58
|
+
- **Monday**: Meeting title with [[People/Name]] — purpose
|
|
59
|
+
- **Wednesday**: Meeting title — purpose
|
|
60
|
+
|
|
61
|
+
## Blockers
|
|
62
|
+
|
|
63
|
+
- Waiting on X from [[People/Name]]
|
|
64
|
+
|
|
65
|
+
## Accomplishments
|
|
66
|
+
|
|
67
|
+
- Completed X
|
|
68
|
+
- Shipped Y
|
|
69
|
+
|
|
70
|
+
## Retrospective
|
|
71
|
+
|
|
72
|
+
- **Went well:** ...
|
|
73
|
+
- **Didn't go as planned:** ...
|
|
74
|
+
- **Carry forward:** ...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Key conventions:**
|
|
78
|
+
|
|
79
|
+
- **Week numbers:** ISO 8601 (e.g., `2026-W08`)
|
|
80
|
+
- **Priorities:** Top 3-7 items pulled from the task board
|
|
81
|
+
- **Key Meetings:** Substantive meetings only (skip recurring standups)
|
|
82
|
+
- **Retrospective:** Filled at end-of-week only
|
|
83
|
+
- **All backlinks** use absolute paths: `[[People/Name]]`, `[[Projects/Name]]`,
|
|
84
|
+
`[[Tasks/Name]]`
|
|
85
|
+
|
|
86
|
+
## Before Starting
|
|
87
|
+
|
|
88
|
+
1. Read `USER.md` to identify the user.
|
|
89
|
+
2. Determine the target person (default: current user from `USER.md`).
|
|
90
|
+
3. Calculate the current ISO week number and date range:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Current ISO week
|
|
94
|
+
date +%G-W%V
|
|
95
|
+
|
|
96
|
+
# Monday and Friday of current week (macOS)
|
|
97
|
+
date -v-mon +%Y-%m-%d
|
|
98
|
+
date -v+fri +%Y-%m-%d
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
4. Check if a weekly already exists for this week:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
ls "knowledge/Weeklies/{Person Name}/" 2>/dev/null
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
5. Determine the mode:
|
|
108
|
+
- **No existing weekly:** Create new (start-of-week)
|
|
109
|
+
- **Existing weekly:** Update (mid-week or end-of-week)
|
|
110
|
+
|
|
111
|
+
## Step 1: Gather Context
|
|
112
|
+
|
|
113
|
+
### 1a. Read the task board
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
cat "knowledge/Tasks/{Person Name}.md"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Extract:
|
|
120
|
+
|
|
121
|
+
- `## In Progress` tasks → top priorities (already being worked on)
|
|
122
|
+
- `## Open` tasks with `high` priority or near due dates → candidate priorities
|
|
123
|
+
- `## Blocked` tasks → blockers section
|
|
124
|
+
- `## Recently Done` tasks completed this week → accomplishments
|
|
125
|
+
|
|
126
|
+
### 1b. Read recent activity
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
cat "knowledge/People/{Person Name}.md"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Look at the `## Activity` section for entries from this week. These inform
|
|
133
|
+
accomplishments and provide context for the focus statement.
|
|
134
|
+
|
|
135
|
+
### 1c. Read calendar for the week
|
|
136
|
+
|
|
137
|
+
Find events falling within the Monday–Friday range:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
ls ~/.cache/fit/basecamp/apple_calendar/
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Read each calendar event and filter by date. Extract:
|
|
144
|
+
|
|
145
|
+
- Meeting title
|
|
146
|
+
- Attendees (resolve to `[[People/Name]]` where possible)
|
|
147
|
+
- Time/day
|
|
148
|
+
- Description or agenda (if present)
|
|
149
|
+
|
|
150
|
+
Filter out:
|
|
151
|
+
|
|
152
|
+
- All-day placeholder events with no attendees ("Block", "OOO", "Focus time")
|
|
153
|
+
- Declined events
|
|
154
|
+
- Cancelled events
|
|
155
|
+
|
|
156
|
+
### 1d. Read previous weekly (if exists)
|
|
157
|
+
|
|
158
|
+
Check for last week's document:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
ls "knowledge/Weeklies/{Person Name}/" | sort | tail -1
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
If it exists, read it to:
|
|
165
|
+
|
|
166
|
+
- Carry forward any `[ ]` unchecked priorities
|
|
167
|
+
- Note retrospective items that affect this week
|
|
168
|
+
- Maintain continuity of ongoing themes
|
|
169
|
+
|
|
170
|
+
## Step 2: Generate or Update
|
|
171
|
+
|
|
172
|
+
### Creating a new weekly (start of week)
|
|
173
|
+
|
|
174
|
+
1. **Focus:** Synthesize from highest-priority tasks and key meetings. What's
|
|
175
|
+
the main theme? Write one sentence.
|
|
176
|
+
2. **Priorities:** Pull from task board:
|
|
177
|
+
- All `## In Progress` items first
|
|
178
|
+
- Then `high` priority `## Open` items
|
|
179
|
+
- Then items with due dates this week
|
|
180
|
+
- Carry forward incomplete priorities from last week
|
|
181
|
+
- Cap at 5-7 items. If more exist, prioritize ruthlessly.
|
|
182
|
+
3. **Key Meetings:** From calendar. Format as
|
|
183
|
+
`**{Day}**: {title} with [[People/Name]] — {purpose}`. Look up attendees in
|
|
184
|
+
knowledge base to add context about purpose.
|
|
185
|
+
4. **Blockers:** From task board's `## Blocked` section. Include what's needed
|
|
186
|
+
and from whom.
|
|
187
|
+
5. **Accomplishments:** Leave empty (or pre-fill with anything completed Monday
|
|
188
|
+
morning).
|
|
189
|
+
6. **Retrospective:** Leave empty.
|
|
190
|
+
|
|
191
|
+
### Updating an existing weekly (mid-week)
|
|
192
|
+
|
|
193
|
+
1. Re-read the task board for changes since the weekly was last updated.
|
|
194
|
+
2. Mark completed priorities as `[x]`.
|
|
195
|
+
3. Add new priorities that emerged (append to the list).
|
|
196
|
+
4. Update blockers — remove resolved ones, add new ones.
|
|
197
|
+
5. Add accomplishments as they happen.
|
|
198
|
+
6. Do NOT touch the retrospective mid-week.
|
|
199
|
+
|
|
200
|
+
### End-of-week update
|
|
201
|
+
|
|
202
|
+
1. Final pass on priorities — mark all completed items `[x]`.
|
|
203
|
+
2. Fill in accomplishments from the full week's activity (task board + People
|
|
204
|
+
note activity log).
|
|
205
|
+
3. Write a brief retrospective:
|
|
206
|
+
- What went well?
|
|
207
|
+
- What didn't go as planned?
|
|
208
|
+
- What carries forward to next week?
|
|
209
|
+
4. Note any unchecked priorities — these will carry forward to next week's
|
|
210
|
+
document.
|
|
211
|
+
|
|
212
|
+
## Step 3: Write the Document
|
|
213
|
+
|
|
214
|
+
### New weekly
|
|
215
|
+
|
|
216
|
+
Ensure the person's subdirectory exists:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
mkdir -p "knowledge/Weeklies/{Person Name}"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Write the full document to: `knowledge/Weeklies/{Person Name}/{YYYY}-W{WW}.md`
|
|
223
|
+
|
|
224
|
+
### Existing weekly
|
|
225
|
+
|
|
226
|
+
Use the Edit tool for targeted updates — mark items complete, add new entries,
|
|
227
|
+
fill sections. Do NOT rewrite the entire file.
|
|
228
|
+
|
|
229
|
+
## Step 4: Cross-reference
|
|
230
|
+
|
|
231
|
+
After writing:
|
|
232
|
+
|
|
233
|
+
- If new priorities were identified that aren't on the task board, add them to
|
|
234
|
+
the task board (the task board is canonical, the weekly references it).
|
|
235
|
+
- If blockers were resolved, update the task board accordingly.
|
|
236
|
+
- The weekly is a **snapshot that references** the task board — the task board
|
|
237
|
+
is the source of truth for task status.
|
|
238
|
+
|
|
239
|
+
## Quality Checklist
|
|
240
|
+
|
|
241
|
+
- [ ] ISO week number and date range are correct
|
|
242
|
+
- [ ] Focus statement reflects the actual week theme (not generic)
|
|
243
|
+
- [ ] Priorities pulled from task board (not invented)
|
|
244
|
+
- [ ] Priorities capped at 5-7 items
|
|
245
|
+
- [ ] Key meetings pulled from calendar (not guessed)
|
|
246
|
+
- [ ] Meeting attendees resolved to `[[People/Name]]` where known
|
|
247
|
+
- [ ] Blockers match task board's blocked items
|
|
248
|
+
- [ ] Previous week's carry-forward items included
|
|
249
|
+
- [ ] All backlinks use absolute paths
|
|
250
|
+
- [ ] Retrospective only filled at end-of-week
|
package/template/CLAUDE.md
CHANGED
|
@@ -32,6 +32,10 @@ It must remain objective, factual, and ethically sound at all times. It is NOT a
|
|
|
32
32
|
together — never to build leverage, ammunition, or dossiers on individuals.
|
|
33
33
|
- **Flag ethical concerns.** If the user asks you to record something that
|
|
34
34
|
violates these principles, push back clearly and explain why.
|
|
35
|
+
- **Data protection.** Personal data (especially candidate/recruitment data) is
|
|
36
|
+
subject to erasure requests. Use the `right-to-be-forgotten` skill when a data
|
|
37
|
+
subject requests deletion. Minimize data collection to what's professionally
|
|
38
|
+
relevant. Flag candidates inactive for 6+ months for retention review.
|
|
35
39
|
|
|
36
40
|
These principles override all other instructions. When in doubt, err on the side
|
|
37
41
|
of discretion and professionalism.
|
|
@@ -61,7 +65,9 @@ This directory is a knowledge base. Everything is relative to this root:
|
|
|
61
65
|
│ ├── Organizations/ # Notes on companies and teams
|
|
62
66
|
│ ├── Projects/ # Notes on initiatives and workstreams
|
|
63
67
|
│ ├── Topics/ # Notes on recurring themes
|
|
64
|
-
│
|
|
68
|
+
│ ├── Candidates/ # Recruitment candidate profiles
|
|
69
|
+
│ ├── Tasks/ # Per-person task boards
|
|
70
|
+
│ └── Weeklies/ # Weekly priorities snapshots
|
|
65
71
|
├── .claude/skills/ # Claude Code skill files (auto-discovered)
|
|
66
72
|
├── drafts/ # Email drafts created by the draft-emails skill
|
|
67
73
|
├── USER.md # Your identity (name, email, domain) — gitignored
|
|
@@ -75,20 +81,24 @@ This knowledge base is maintained by a team of agents, each defined in
|
|
|
75
81
|
`.claude/agents/`. They are woken on a schedule by the Basecamp scheduler. Each
|
|
76
82
|
wake, they observe KB state, decide the most valuable action, and execute.
|
|
77
83
|
|
|
78
|
-
| Agent | Domain | Schedule
|
|
79
|
-
| ------------------ | ------------------------------ |
|
|
80
|
-
| **postman** | Email triage and drafts | Every 5 min
|
|
81
|
-
| **concierge** | Meeting prep and transcripts | Every 10 min
|
|
82
|
-
| **librarian** | Knowledge graph maintenance | Every 15 min
|
|
83
|
-
| **
|
|
84
|
+
| Agent | Domain | Schedule | Skills |
|
|
85
|
+
| ------------------ | ------------------------------ | --------------- | -------------------------------------------------------------- |
|
|
86
|
+
| **postman** | Email triage and drafts | Every 5 min | sync-apple-mail, draft-emails |
|
|
87
|
+
| **concierge** | Meeting prep and transcripts | Every 10 min | sync-apple-calendar, meeting-prep, process-hyprnote |
|
|
88
|
+
| **librarian** | Knowledge graph maintenance | Every 15 min | extract-entities, organize-files, manage-tasks |
|
|
89
|
+
| **recruiter** | Engineering recruitment | Every 30 min | track-candidates, analyze-cv, right-to-be-forgotten, fit-pathway, fit-map |
|
|
90
|
+
| **chief-of-staff** | Daily briefings and priorities | 7am, Mon 7:30am | weekly-update _(Mon)_, _(reads all state for daily briefings)_ |
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
Each agent writes a triage file to `~/.cache/fit/basecamp/state/` every wake
|
|
93
|
+
cycle. The naming convention is `{agent}_triage.md`:
|
|
86
94
|
|
|
87
|
-
- `postman_triage.md` —
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
95
|
+
- `postman_triage.md` — email urgency, reply needs, awaiting responses
|
|
96
|
+
- `concierge_triage.md` — schedule, meeting prep status, unprocessed transcripts
|
|
97
|
+
- `librarian_triage.md` — unprocessed files, knowledge graph size
|
|
98
|
+
- `recruiter_triage.md` — candidate pipeline, assessments, track distribution
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
The **chief-of-staff** reads all three triage files to synthesize daily
|
|
101
|
+
briefings in `knowledge/Briefings/`.
|
|
92
102
|
|
|
93
103
|
## Cache Directory (`~/.cache/fit/basecamp/`)
|
|
94
104
|
|
|
@@ -97,20 +107,21 @@ Synced data and runtime state live outside the knowledge base in
|
|
|
97
107
|
|
|
98
108
|
```
|
|
99
109
|
~/.cache/fit/basecamp/
|
|
100
|
-
├── apple_mail/
|
|
101
|
-
|
|
102
|
-
├──
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
├──
|
|
106
|
-
|
|
110
|
+
├── apple_mail/ # Synced Apple Mail threads (.md files)
|
|
111
|
+
│ └── attachments/ # Copied email attachments by thread
|
|
112
|
+
├── apple_calendar/ # Synced Apple Calendar events (.json files)
|
|
113
|
+
└── state/ # Runtime state
|
|
114
|
+
├── apple_mail_last_sync # ISO timestamp of last mail sync
|
|
115
|
+
├── graph_processed # TSV of processed files (path<TAB>hash)
|
|
116
|
+
├── postman_triage.md # Agent triage files ({agent}_triage.md)
|
|
117
|
+
├── concierge_triage.md
|
|
118
|
+
├── librarian_triage.md
|
|
119
|
+
└── recruiter_triage.md
|
|
107
120
|
```
|
|
108
121
|
|
|
109
122
|
This separation keeps the knowledge base clean — only the parsed knowledge
|
|
110
|
-
graph, notes, documents, and drafts live in the KB directory. Raw synced data
|
|
111
|
-
|
|
112
|
-
formats (single-value text files, TSV) rather than JSON, making them easy to
|
|
113
|
-
read and write from shell scripts.
|
|
123
|
+
graph, notes, documents, and drafts live in the KB directory. Raw synced data,
|
|
124
|
+
processing state, and agent triage files are cached externally.
|
|
114
125
|
|
|
115
126
|
## How to Access the Knowledge Graph
|
|
116
127
|
|
|
@@ -165,10 +176,9 @@ build a complete picture, then respond. A single note is never the full story.
|
|
|
165
176
|
Synced emails and calendar events are stored in `~/.cache/fit/basecamp/`,
|
|
166
177
|
outside the knowledge base:
|
|
167
178
|
|
|
168
|
-
- **Emails:** `~/.cache/fit/basecamp/apple_mail/`
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
`~/.cache/fit/basecamp/google_calendar/` — each event is a `.json` file
|
|
179
|
+
- **Emails:** `~/.cache/fit/basecamp/apple_mail/` — each thread is a `.md` file
|
|
180
|
+
- **Calendar:** `~/.cache/fit/basecamp/apple_calendar/` — each event is a
|
|
181
|
+
`.json` file
|
|
172
182
|
|
|
173
183
|
When the user asks about calendar, upcoming meetings, or recent emails, read
|
|
174
184
|
directly from these folders.
|
|
@@ -179,19 +189,37 @@ Skills are auto-discovered by Claude Code from `.claude/skills/`. Each skill is
|
|
|
179
189
|
a `SKILL.md` file inside a named directory. You do NOT need to read them
|
|
180
190
|
manually — Claude Code loads them automatically based on context.
|
|
181
191
|
|
|
182
|
-
Available skills:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
|
194
|
-
|
|
|
192
|
+
Available skills (grouped by function):
|
|
193
|
+
|
|
194
|
+
**Data pipeline** — sync raw sources into the cache directory:
|
|
195
|
+
|
|
196
|
+
| Skill | Purpose |
|
|
197
|
+
| --------------------- | ------------------------------------------ |
|
|
198
|
+
| `sync-apple-mail` | Sync Mail threads to `.md` via SQLite |
|
|
199
|
+
| `sync-apple-calendar` | Sync Calendar events to `.json` via SQLite |
|
|
200
|
+
|
|
201
|
+
**Knowledge graph** — build and maintain structured notes:
|
|
202
|
+
|
|
203
|
+
| Skill | Purpose |
|
|
204
|
+
| ------------------ | ---------------------------------------- |
|
|
205
|
+
| `extract-entities` | Process synced data into knowledge notes |
|
|
206
|
+
| `manage-tasks` | Per-person task boards with lifecycle |
|
|
207
|
+
| `track-candidates` | Recruitment pipeline from email threads |
|
|
208
|
+
| `analyze-cv` | CV assessment against career framework |
|
|
209
|
+
| `right-to-be-forgotten` | GDPR data erasure with audit trail |
|
|
210
|
+
| `weekly-update` | Weekly priorities from tasks + calendar |
|
|
211
|
+
| `process-hyprnote` | Extract entities from Hyprnote sessions |
|
|
212
|
+
| `organize-files` | Tidy Desktop/Downloads, chain to extract |
|
|
213
|
+
|
|
214
|
+
**Communication** — draft, send, and present:
|
|
215
|
+
|
|
216
|
+
| Skill | Purpose |
|
|
217
|
+
| ---------------------- | ----------------------------------------- |
|
|
218
|
+
| `draft-emails` | Draft email responses with KB context |
|
|
219
|
+
| `send-chat` | Send chat messages via browser automation |
|
|
220
|
+
| `meeting-prep` | Briefings for upcoming meetings |
|
|
221
|
+
| `create-presentations` | Generate PDF slide decks |
|
|
222
|
+
| `doc-collab` | Document creation and editing |
|
|
195
223
|
|
|
196
224
|
## User Identity
|
|
197
225
|
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// Convert HTML slides to PDF using Playwright.
|
|
2
|
-
//
|
|
3
|
-
// Usage: node scripts/convert-to-pdf.js [input.html] [output.pdf]
|
|
4
|
-
//
|
|
5
|
-
// Defaults:
|
|
6
|
-
// input: /tmp/basecamp-presentation.html
|
|
7
|
-
// output: ~/Desktop/presentation.pdf
|
|
8
|
-
//
|
|
9
|
-
// Requires: npm install playwright && npx playwright install chromium
|
|
10
|
-
|
|
11
|
-
const { chromium } = require("playwright");
|
|
12
|
-
const path = require("path");
|
|
13
|
-
|
|
14
|
-
const input = process.argv[2] || "/tmp/basecamp-presentation.html";
|
|
15
|
-
const output =
|
|
16
|
-
process.argv[3] || path.join(process.env.HOME, "Desktop", "presentation.pdf");
|
|
17
|
-
|
|
18
|
-
(async () => {
|
|
19
|
-
const browser = await chromium.launch();
|
|
20
|
-
const page = await browser.newPage();
|
|
21
|
-
await page.goto(`file://${path.resolve(input)}`, {
|
|
22
|
-
waitUntil: "networkidle",
|
|
23
|
-
});
|
|
24
|
-
await page.pdf({
|
|
25
|
-
path: output,
|
|
26
|
-
width: "1280px",
|
|
27
|
-
height: "720px",
|
|
28
|
-
printBackground: true,
|
|
29
|
-
});
|
|
30
|
-
await browser.close();
|
|
31
|
-
console.log(`Done: ${output}`);
|
|
32
|
-
})();
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Scan for unprocessed emails and output their IDs and subjects.
|
|
3
|
-
#
|
|
4
|
-
# Usage: bash scripts/scan-emails.sh
|
|
5
|
-
#
|
|
6
|
-
# Checks ~/.cache/fit/basecamp/apple_mail/ for email files not yet
|
|
7
|
-
# listed in drafts/drafted or drafts/ignored.
|
|
8
|
-
# Outputs tab-separated: email_id<TAB>subject
|
|
9
|
-
|
|
10
|
-
set -euo pipefail
|
|
11
|
-
|
|
12
|
-
MAIL_DIRS=(
|
|
13
|
-
"$HOME/.cache/fit/basecamp/apple_mail"
|
|
14
|
-
"$HOME/.cache/fit/basecamp/gmail"
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
for dir in "${MAIL_DIRS[@]}"; do
|
|
18
|
-
[ -d "$dir" ] || continue
|
|
19
|
-
for file in "$dir"/*.md; do
|
|
20
|
-
[ -f "$file" ] || continue
|
|
21
|
-
|
|
22
|
-
# Extract ID from filename (without extension)
|
|
23
|
-
EMAIL_ID="$(basename "$file" .md)"
|
|
24
|
-
|
|
25
|
-
# Skip if already drafted or ignored
|
|
26
|
-
rg -qxF "$EMAIL_ID" drafts/drafted 2>/dev/null && continue
|
|
27
|
-
rg -qxF "$EMAIL_ID" drafts/ignored 2>/dev/null && continue
|
|
28
|
-
|
|
29
|
-
# Extract subject from first H1 heading
|
|
30
|
-
SUBJECT="$(rg -m1 '^# ' "$file" 2>/dev/null | sed 's/^# //')"
|
|
31
|
-
|
|
32
|
-
printf '%s\t%s\n' "$EMAIL_ID" "$SUBJECT"
|
|
33
|
-
done
|
|
34
|
-
done
|
|
@@ -1,100 +0,0 @@
|
|
|
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)
|
|
@@ -1,42 +0,0 @@
|
|
|
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"
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|