@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.
Files changed (41) hide show
  1. package/config/scheduler.json +5 -0
  2. package/package.json +1 -1
  3. package/src/basecamp.js +288 -57
  4. package/template/.claude/agents/chief-of-staff.md +6 -2
  5. package/template/.claude/agents/concierge.md +2 -3
  6. package/template/.claude/agents/librarian.md +4 -6
  7. package/template/.claude/agents/recruiter.md +269 -0
  8. package/template/.claude/settings.json +0 -4
  9. package/template/.claude/skills/analyze-cv/SKILL.md +269 -0
  10. package/template/.claude/skills/create-presentations/SKILL.md +2 -2
  11. package/template/.claude/skills/create-presentations/references/slide.css +1 -1
  12. package/template/.claude/skills/create-presentations/scripts/convert-to-pdf.mjs +47 -0
  13. package/template/.claude/skills/draft-emails/SKILL.md +85 -123
  14. package/template/.claude/skills/draft-emails/scripts/scan-emails.mjs +66 -0
  15. package/template/.claude/skills/draft-emails/scripts/send-email.mjs +118 -0
  16. package/template/.claude/skills/extract-entities/SKILL.md +2 -2
  17. package/template/.claude/skills/extract-entities/scripts/state.mjs +130 -0
  18. package/template/.claude/skills/manage-tasks/SKILL.md +242 -0
  19. package/template/.claude/skills/organize-files/SKILL.md +3 -3
  20. package/template/.claude/skills/organize-files/scripts/organize-by-type.mjs +105 -0
  21. package/template/.claude/skills/organize-files/scripts/summarize.mjs +84 -0
  22. package/template/.claude/skills/process-hyprnote/SKILL.md +2 -2
  23. package/template/.claude/skills/right-to-be-forgotten/SKILL.md +333 -0
  24. package/template/.claude/skills/send-chat/SKILL.md +170 -0
  25. package/template/.claude/skills/sync-apple-calendar/SKILL.md +5 -5
  26. package/template/.claude/skills/sync-apple-calendar/scripts/sync.mjs +325 -0
  27. package/template/.claude/skills/sync-apple-mail/SKILL.md +6 -6
  28. package/template/.claude/skills/sync-apple-mail/scripts/parse-emlx.mjs +374 -0
  29. package/template/.claude/skills/sync-apple-mail/scripts/sync.mjs +629 -0
  30. package/template/.claude/skills/track-candidates/SKILL.md +376 -0
  31. package/template/.claude/skills/upstream-skill/SKILL.md +207 -0
  32. package/template/.claude/skills/weekly-update/SKILL.md +250 -0
  33. package/template/CLAUDE.md +68 -40
  34. package/template/.claude/skills/create-presentations/scripts/convert-to-pdf.js +0 -32
  35. package/template/.claude/skills/draft-emails/scripts/scan-emails.sh +0 -34
  36. package/template/.claude/skills/extract-entities/scripts/state.py +0 -100
  37. package/template/.claude/skills/organize-files/scripts/organize-by-type.sh +0 -42
  38. package/template/.claude/skills/organize-files/scripts/summarize.sh +0 -21
  39. package/template/.claude/skills/sync-apple-calendar/scripts/sync.py +0 -242
  40. package/template/.claude/skills/sync-apple-mail/scripts/parse-emlx.py +0 -104
  41. package/template/.claude/skills/sync-apple-mail/scripts/sync.py +0 -455
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: manage-tasks
3
+ description: Create, update, list, and close tasks on per-person task boards in knowledge/Tasks/. Manages task lifecycle, extracts action items from other skills, and keeps boards current. Use when the user asks to add, update, or review tasks, or when chained from extract-entities or process-hyprnote.
4
+ ---
5
+
6
+ # Manage Tasks
7
+
8
+ Manage per-person task boards in `knowledge/Tasks/`. Each person has a single
9
+ living document that tracks all their open, in-progress, blocked, and recently
10
+ completed tasks. Task boards are the **canonical source** for task tracking —
11
+ other notes (People, Projects) link to them rather than duplicating.
12
+
13
+ ## Trigger
14
+
15
+ Run this skill:
16
+
17
+ - When the user asks to add, update, close, or list tasks
18
+ - When chained from `extract-entities` or `process-hyprnote` with extracted
19
+ action items
20
+ - When the user asks to see someone's task board or workload
21
+ - On a schedule to perform housekeeping (prune done items, flag overdue)
22
+
23
+ ## Prerequisites
24
+
25
+ - `knowledge/Tasks/` directory exists
26
+ - User identity configured in `USER.md`
27
+
28
+ ## Inputs
29
+
30
+ ### User-initiated
31
+
32
+ - Person name and task details from the user's request
33
+
34
+ ### Chained from other skills
35
+
36
+ Action items extracted by `extract-entities` or `process-hyprnote`, passed as
37
+ structured data:
38
+
39
+ ```
40
+ TASKS:
41
+ - Owner: {Full Name}
42
+ Task: {Description — starts with a verb}
43
+ Priority: high|medium|low
44
+ Due: YYYY-MM-DD (if known)
45
+ Source: meeting|email
46
+ Source date: YYYY-MM-DD
47
+ Project: {Project Name} (if applicable)
48
+ Context: {Brief context about where this came from}
49
+ ```
50
+
51
+ ## Outputs
52
+
53
+ - `knowledge/Tasks/{Person Name}.md` — created or updated task boards
54
+
55
+ ---
56
+
57
+ ## Task Board Format
58
+
59
+ Each task board is a markdown file with four sections, always in this order:
60
+
61
+ ```markdown
62
+ # {Person Name}
63
+
64
+ ## In Progress
65
+ ## Open
66
+ ## Blocked
67
+ ## Recently Done
68
+ ```
69
+
70
+ All four sections are always present, even if empty.
71
+
72
+ **Task entry format:**
73
+
74
+ ```
75
+ - [ ] **{Task title}** | {priority} | due {YYYY-MM-DD} | [[Projects/Name]]
76
+ {Context line with source info and backlinks.}
77
+ ```
78
+
79
+ **Key conventions:**
80
+
81
+ - **Priorities:** `high` | `medium` | `low` — omit if medium (default)
82
+ - **Due dates:** Only include if there's a real deadline. Format:
83
+ `due YYYY-MM-DD`
84
+ - **No task IDs.** Tasks are identified by their bold title. Keep titles unique
85
+ within a person's board.
86
+ - **Recently Done:** Keep the last 14 days. Older items pruned during
87
+ housekeeping.
88
+
89
+ ## Before Starting
90
+
91
+ 1. Read `USER.md` to get user identity.
92
+ 2. Determine the operation: **add**, **update**, **close**, **list**, or
93
+ **housekeeping**.
94
+
95
+ ## Step 1: Resolve the Person
96
+
97
+ For any task operation, resolve the person to their canonical name:
98
+
99
+ ```bash
100
+ ls knowledge/Tasks/
101
+ rg "{name}" knowledge/People/
102
+ ```
103
+
104
+ If the person doesn't have a task board yet, create one from the template (Step
105
+ 4 covers creation).
106
+
107
+ ## Step 2: Read Current Task Board
108
+
109
+ ```bash
110
+ cat "knowledge/Tasks/{Person Name}.md"
111
+ ```
112
+
113
+ Parse existing tasks to:
114
+
115
+ - Avoid duplicates (same person, similar description)
116
+ - Understand current workload
117
+ - Find the right insertion point
118
+
119
+ ## Step 3: Perform the Operation
120
+
121
+ ### Add a Task
122
+
123
+ 1. Check for duplicates — same person, similar task title or description. If a
124
+ near-duplicate exists, update it instead of creating a new entry.
125
+ 2. Determine the section:
126
+ - Default: `## Open`
127
+ - If user says "I'm working on" / "started" → `## In Progress`
128
+ - If blocked → `## Blocked`
129
+ 3. Format the task entry:
130
+ ```
131
+ - [ ] **{Task title}** | {priority} | due {YYYY-MM-DD} | [[Projects/Name]]
132
+ {Context line with source info and backlinks.}
133
+ ```
134
+ 4. Add the entry at the **bottom** of the appropriate section.
135
+ 5. If the task references a project, verify the project note exists.
136
+
137
+ ### Update a Task
138
+
139
+ 1. Find the task by title (fuzzy match OK — bold text between `**`).
140
+ 2. Apply changes:
141
+ - **Status change:** Move the entire entry between sections
142
+ - **Priority change:** Update the `| {priority} |` segment
143
+ - **Due date change:** Update or add `| due YYYY-MM-DD |`
144
+ - **Add context:** Append to the indented line
145
+ 3. Use the Edit tool for targeted modifications.
146
+
147
+ ### Close a Task
148
+
149
+ 1. Find the task by title.
150
+ 2. Remove it from its current section.
151
+ 3. Add to the **top** of `## Recently Done`:
152
+ ```
153
+ - [x] **{Task title}** | completed {YYYY-MM-DD}
154
+ ```
155
+ (Drop priority, due date, project link, and context — keep it compact.)
156
+
157
+ ### List Tasks
158
+
159
+ Query across all task boards:
160
+
161
+ ```bash
162
+ # All open/in-progress tasks
163
+ rg "^- \[ \] \*\*" knowledge/Tasks/
164
+
165
+ # Tasks for a specific project
166
+ rg "Projects/{Name}" knowledge/Tasks/
167
+
168
+ # High priority tasks
169
+ rg "\| high \|" knowledge/Tasks/
170
+
171
+ # Overdue tasks — find all due dates and compare against today
172
+ rg "due 20[0-9]{2}-[0-9]{2}-[0-9]{2}" knowledge/Tasks/
173
+
174
+ # Blocked tasks
175
+ rg -A1 "^- \[ \]" knowledge/Tasks/ | rg -B1 "Waiting on"
176
+ ```
177
+
178
+ Present results in a clean summary, grouped by person or project as appropriate
179
+ for the user's question.
180
+
181
+ ### Housekeeping (Scheduled)
182
+
183
+ Run across all task boards:
184
+
185
+ 1. **Prune done items:** Remove completed tasks older than 14 days from
186
+ `## Recently Done`.
187
+ 2. **Flag overdue:** Any task with `due {date}` in the past that's still in
188
+ `## Open` or `## In Progress` — check if it needs attention. Do NOT
189
+ auto-modify the task; instead, report overdue items to the user.
190
+ 3. **Deduplicate:** If identical tasks appear on the same board, merge them.
191
+ 4. **Validate links:** Spot-check that `[[People/]]` and `[[Projects/]]`
192
+ references point to existing notes.
193
+
194
+ ## Step 4: Write Updates
195
+
196
+ ### New task board
197
+
198
+ Create `knowledge/Tasks/{Person Name}.md` with all four sections:
199
+
200
+ ```markdown
201
+ # {Person Name}
202
+
203
+ ## In Progress
204
+
205
+ ## Open
206
+
207
+ ## Blocked
208
+
209
+ ## Recently Done
210
+ ```
211
+
212
+ ### Existing task board
213
+
214
+ Use the Edit tool to make targeted changes — add, move, or modify individual
215
+ task entries. Do NOT rewrite the entire file.
216
+
217
+ ## Step 5: Migrate Open Items (One-time)
218
+
219
+ When first setting up task boards, or when the user asks, migrate existing
220
+ `## Open items` from People and Project notes:
221
+
222
+ 1. Scan notes for `## Open items` sections with content:
223
+ ```bash
224
+ rg -l "## Open items" knowledge/People/ knowledge/Projects/
225
+ ```
226
+ 2. For each note with open items, read the items and convert them to task board
227
+ entries.
228
+ 3. Add each item to the appropriate person's task board.
229
+ 4. **Do NOT remove** the original open items from source notes — they serve as
230
+ the historical record. The task board becomes the living tracker.
231
+
232
+ ## Quality Checklist
233
+
234
+ - [ ] Task title is clear and actionable (starts with a verb)
235
+ - [ ] Priority set appropriately (omit if medium)
236
+ - [ ] Due date included only if there's a real deadline
237
+ - [ ] Project linked with `[[Projects/Name]]` if applicable
238
+ - [ ] No duplicate tasks on the board
239
+ - [ ] Recently Done pruned to last 14 days (housekeeping)
240
+ - [ ] All backlinks use absolute paths `[[Folder/Name]]`
241
+ - [ ] Context line is concise (1-2 lines max)
242
+ - [ ] New task board has all four sections present
@@ -61,7 +61,7 @@ Run when the user asks to find, organize, clean up, or tidy files on their Mac.
61
61
 
62
62
  Get an overview of both directories:
63
63
 
64
- bash scripts/summarize.sh
64
+ node scripts/summarize.mjs
65
65
 
66
66
  ## Finding Files
67
67
 
@@ -79,8 +79,8 @@ find ~/Desktop -maxdepth 1 \( -name "Screenshot*" -o -name "Screen Shot*" \)
79
79
  Organize a directory into type-based subdirectories (Documents, Images,
80
80
  Archives, Installers, Screenshots):
81
81
 
82
- bash scripts/organize-by-type.sh ~/Downloads
83
- bash scripts/organize-by-type.sh ~/Desktop
82
+ node scripts/organize-by-type.mjs ~/Downloads
83
+ node scripts/organize-by-type.mjs ~/Desktop
84
84
 
85
85
  The script creates subdirectories and moves matching files. It does NOT delete
86
86
  anything.
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Organize files in a directory by type into subdirectories.
4
+ *
5
+ * Scans the top level of the given directory and moves files into category
6
+ * subdirectories: Screenshots, Documents, Images, Archives, and Installers.
7
+ * Categories are determined by file extension and name prefix. Files that do
8
+ * not match any category are left in place. Does NOT delete anything.
9
+ */
10
+
11
+ import {
12
+ existsSync,
13
+ mkdirSync,
14
+ readdirSync,
15
+ renameSync,
16
+ statSync,
17
+ } from "node:fs";
18
+ import { extname, join } from "node:path";
19
+
20
+ const HELP = `organize-by-type — sort files into type-based subdirectories
21
+
22
+ Usage: node scripts/organize-by-type.mjs <directory> [-h|--help]
23
+
24
+ Creates subdirectories (Documents, Images, Archives, Installers, Screenshots)
25
+ and moves matching top-level files into them. Does NOT delete any files.`;
26
+
27
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
28
+ console.log(HELP);
29
+ process.exit(0);
30
+ }
31
+
32
+ const CATEGORIES = [
33
+ {
34
+ name: "Screenshots",
35
+ test: (name) =>
36
+ name.startsWith("Screenshot") || name.startsWith("Screen Shot"),
37
+ },
38
+ {
39
+ name: "Documents",
40
+ test: (_name, ext) =>
41
+ [
42
+ ".pdf",
43
+ ".doc",
44
+ ".docx",
45
+ ".txt",
46
+ ".md",
47
+ ".rtf",
48
+ ".csv",
49
+ ".xlsx",
50
+ ].includes(ext),
51
+ },
52
+ {
53
+ name: "Images",
54
+ test: (_name, ext) =>
55
+ [".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(ext),
56
+ },
57
+ {
58
+ name: "Archives",
59
+ test: (name, ext) =>
60
+ [".zip", ".rar"].includes(ext) || name.endsWith(".tar.gz"),
61
+ },
62
+ {
63
+ name: "Installers",
64
+ test: (_name, ext) => ext === ".dmg",
65
+ },
66
+ ];
67
+
68
+ function main() {
69
+ const dir = process.argv[2];
70
+ if (!dir) {
71
+ console.error("Usage: node scripts/organize-by-type.mjs <directory>");
72
+ process.exit(1);
73
+ }
74
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) {
75
+ console.error(`Error: Directory not found: ${dir}`);
76
+ process.exit(1);
77
+ }
78
+
79
+ // Create subdirectories
80
+ for (const cat of CATEGORIES) {
81
+ mkdirSync(join(dir, cat.name), { recursive: true });
82
+ }
83
+
84
+ let moved = 0;
85
+ for (const name of readdirSync(dir)) {
86
+ const fullPath = join(dir, name);
87
+ const stat = statSync(fullPath, { throwIfNoEntry: false });
88
+ if (!stat || !stat.isFile()) continue;
89
+
90
+ const ext = extname(name).toLowerCase();
91
+ for (const cat of CATEGORIES) {
92
+ if (cat.test(name, ext)) {
93
+ const dest = join(dir, cat.name, name);
94
+ renameSync(fullPath, dest);
95
+ console.log(`${name} -> ${cat.name}/`);
96
+ moved++;
97
+ break;
98
+ }
99
+ }
100
+ }
101
+
102
+ console.log(`\nOrganization complete: ${dir} (${moved} files moved)`);
103
+ }
104
+
105
+ main();
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Summarize the contents of ~/Desktop/ and ~/Downloads/.
4
+ *
5
+ * Counts top-level files in both directories by type (Screenshots, PDFs,
6
+ * Images, Documents, Archives, Installers, Other) and prints a human-readable
7
+ * table for each. Used by the organize-files skill to preview directory
8
+ * contents before organizing.
9
+ */
10
+
11
+ import { existsSync, readdirSync, statSync } from "node:fs";
12
+ import { extname, join } from "node:path";
13
+ import { homedir } from "node:os";
14
+
15
+ const HELP = `summarize — count files by type in ~/Desktop/ and ~/Downloads/
16
+
17
+ Usage: node scripts/summarize.mjs [-h|--help]
18
+
19
+ Prints a summary of file types found at the top level of each directory.`;
20
+
21
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
22
+ console.log(HELP);
23
+ process.exit(0);
24
+ }
25
+
26
+ const HOME = homedir();
27
+
28
+ function countFiles(dir) {
29
+ if (!existsSync(dir)) return null;
30
+
31
+ const counts = {
32
+ Screenshots: 0,
33
+ PDFs: 0,
34
+ Images: 0,
35
+ Documents: 0,
36
+ Archives: 0,
37
+ Installers: 0,
38
+ Other: 0,
39
+ };
40
+
41
+ for (const name of readdirSync(dir)) {
42
+ const fullPath = join(dir, name);
43
+ const stat = statSync(fullPath, { throwIfNoEntry: false });
44
+ if (!stat || !stat.isFile()) continue;
45
+ if (name.startsWith(".")) continue;
46
+
47
+ const ext = extname(name).toLowerCase();
48
+ if (name.startsWith("Screenshot") || name.startsWith("Screen Shot")) {
49
+ counts.Screenshots++;
50
+ } else if (ext === ".pdf") {
51
+ counts.PDFs++;
52
+ } else if ([".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(ext)) {
53
+ counts.Images++;
54
+ } else if (
55
+ [".doc", ".docx", ".txt", ".md", ".rtf", ".csv", ".xlsx"].includes(ext)
56
+ ) {
57
+ counts.Documents++;
58
+ } else if ([".zip", ".rar"].includes(ext) || name.endsWith(".tar.gz")) {
59
+ counts.Archives++;
60
+ } else if (ext === ".dmg") {
61
+ counts.Installers++;
62
+ } else {
63
+ counts.Other++;
64
+ }
65
+ }
66
+
67
+ return counts;
68
+ }
69
+
70
+ function main() {
71
+ for (const dirName of ["Desktop", "Downloads"]) {
72
+ const dir = join(HOME, dirName);
73
+ const counts = countFiles(dir);
74
+ if (!counts) continue;
75
+
76
+ console.log(`=== ${dirName} ===`);
77
+ for (const [label, count] of Object.entries(counts)) {
78
+ console.log(`${label.padEnd(12)} ${count}`);
79
+ }
80
+ console.log("");
81
+ }
82
+ }
83
+
84
+ main();
@@ -308,11 +308,11 @@ absolute paths: `[[People/Name]]`, `[[Organizations/Name]]`,
308
308
  After processing each session, mark its files as processed:
309
309
 
310
310
  ```bash
311
- python3 .claude/skills/extract-entities/scripts/state.py update \
311
+ node .claude/skills/extract-entities/scripts/state.mjs update \
312
312
  "$HOME/Library/Application Support/hyprnote/sessions/{uuid}/_memo.md"
313
313
 
314
314
  # Also mark _summary.md if it exists
315
- python3 .claude/skills/extract-entities/scripts/state.py update \
315
+ node .claude/skills/extract-entities/scripts/state.mjs update \
316
316
  "$HOME/Library/Application Support/hyprnote/sessions/{uuid}/_summary.md"
317
317
  ```
318
318