@forwardimpact/basecamp 2.4.2 → 2.6.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 +10 -5
- package/package.json +1 -1
- package/src/basecamp.js +101 -729
- package/template/.claude/agents/chief-of-staff.md +14 -3
- package/template/.claude/agents/head-hunter.md +436 -0
- package/template/.claude/agents/librarian.md +1 -1
- package/template/.claude/settings.json +4 -1
- package/template/.claude/skills/analyze-cv/SKILL.md +39 -7
- package/template/.claude/skills/draft-emails/SKILL.md +29 -9
- package/template/.claude/skills/draft-emails/scripts/scan-emails.mjs +4 -4
- package/template/.claude/skills/draft-emails/scripts/send-email.mjs +41 -6
- package/template/.claude/skills/meeting-prep/SKILL.md +7 -4
- package/template/.claude/skills/process-hyprnote/SKILL.md +17 -8
- package/template/.claude/skills/process-hyprnote/scripts/scan.mjs +246 -0
- package/template/.claude/skills/scan-open-candidates/SKILL.md +476 -0
- package/template/.claude/skills/scan-open-candidates/scripts/state.mjs +396 -0
- package/template/.claude/skills/sync-apple-calendar/SKILL.md +41 -0
- package/template/.claude/skills/sync-apple-calendar/scripts/query.mjs +301 -0
- package/template/.claude/skills/synthesize-deck/SKILL.md +296 -0
- package/template/.claude/skills/synthesize-deck/scripts/extract-pptx.mjs +210 -0
- package/template/.claude/skills/track-candidates/SKILL.md +45 -0
- package/template/.claude/skills/workday-requisition/SKILL.md +86 -53
- package/template/.claude/skills/workday-requisition/scripts/parse-workday.mjs +103 -37
- package/template/CLAUDE.md +13 -3
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Manage head-hunter agent state files.
|
|
4
|
+
*
|
|
5
|
+
* Provides atomic operations on the 5 state files used by the head-hunter agent:
|
|
6
|
+
* cursor.tsv — source rotation state (source, last_checked, position)
|
|
7
|
+
* seen.tsv — deduplication index (source, id, date)
|
|
8
|
+
* prospects.tsv — prospect index (name, source, date, strength, level)
|
|
9
|
+
* failures.tsv — consecutive failure counts (source, count)
|
|
10
|
+
* log.md — append-only activity log
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node scripts/state.mjs cursor get <source>
|
|
14
|
+
* node scripts/state.mjs cursor set <source> <timestamp> <position>
|
|
15
|
+
* node scripts/state.mjs seen check <source> <id>
|
|
16
|
+
* node scripts/state.mjs seen add <source> <id> [<date>]
|
|
17
|
+
* node scripts/state.mjs prospect add <name> <source> <strength> <level>
|
|
18
|
+
* node scripts/state.mjs prospect list [--limit N]
|
|
19
|
+
* node scripts/state.mjs failure get <source>
|
|
20
|
+
* node scripts/state.mjs failure increment <source>
|
|
21
|
+
* node scripts/state.mjs failure reset <source>
|
|
22
|
+
* node scripts/state.mjs log <message>
|
|
23
|
+
* node scripts/state.mjs log-wake <source> <summary>
|
|
24
|
+
* node scripts/state.mjs summary
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
appendFileSync,
|
|
29
|
+
existsSync,
|
|
30
|
+
mkdirSync,
|
|
31
|
+
readFileSync,
|
|
32
|
+
writeFileSync,
|
|
33
|
+
} from "node:fs";
|
|
34
|
+
import { join } from "node:path";
|
|
35
|
+
import { homedir } from "node:os";
|
|
36
|
+
|
|
37
|
+
const HOME = homedir();
|
|
38
|
+
const STATE_DIR = join(HOME, ".cache/fit/basecamp/head-hunter");
|
|
39
|
+
|
|
40
|
+
const PATHS = {
|
|
41
|
+
cursor: join(STATE_DIR, "cursor.tsv"),
|
|
42
|
+
seen: join(STATE_DIR, "seen.tsv"),
|
|
43
|
+
prospects: join(STATE_DIR, "prospects.tsv"),
|
|
44
|
+
failures: join(STATE_DIR, "failures.tsv"),
|
|
45
|
+
log: join(STATE_DIR, "log.md"),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
49
|
+
console.log(`state — manage head-hunter agent state
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
node scripts/state.mjs <command> [args]
|
|
53
|
+
|
|
54
|
+
Cursor commands (source rotation):
|
|
55
|
+
cursor get <source> Get current cursor for source
|
|
56
|
+
cursor set <source> <timestamp> <position> Update cursor position
|
|
57
|
+
cursor list List all cursors
|
|
58
|
+
|
|
59
|
+
Seen commands (deduplication):
|
|
60
|
+
seen check <source> <id> Check if ID was already seen (exit 0=seen, 1=new)
|
|
61
|
+
seen add <source> <id> [<date>] Mark ID as seen (date defaults to today)
|
|
62
|
+
seen batch <source> <id1> <id2> ... Mark multiple IDs as seen
|
|
63
|
+
|
|
64
|
+
Prospect commands:
|
|
65
|
+
prospect add <name> <source> <strength> <level> Add a new prospect
|
|
66
|
+
prospect list [--limit N] List recent prospects
|
|
67
|
+
prospect count Count total prospects
|
|
68
|
+
|
|
69
|
+
Failure commands:
|
|
70
|
+
failure get <source> Get failure count for source
|
|
71
|
+
failure increment <source> Increment failure count
|
|
72
|
+
failure reset <source> Reset failure count to 0
|
|
73
|
+
|
|
74
|
+
Log commands:
|
|
75
|
+
log <message> Append raw text to log.md
|
|
76
|
+
log-wake <source> <summary> Append a formatted wake cycle entry
|
|
77
|
+
|
|
78
|
+
Summary:
|
|
79
|
+
summary Print state overview
|
|
80
|
+
|
|
81
|
+
State dir: ~/.cache/fit/basecamp/head-hunter/`);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- Ensure state directory exists ---
|
|
86
|
+
|
|
87
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
88
|
+
|
|
89
|
+
// --- File helpers ---
|
|
90
|
+
|
|
91
|
+
function readTsv(file) {
|
|
92
|
+
if (!existsSync(file)) return [];
|
|
93
|
+
return readFileSync(file, "utf8")
|
|
94
|
+
.split("\n")
|
|
95
|
+
.filter((l) => l.trim());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writeTsv(file, lines) {
|
|
99
|
+
writeFileSync(file, lines.join("\n") + (lines.length ? "\n" : ""));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function appendTsv(file, line) {
|
|
103
|
+
appendFileSync(file, line + "\n");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function today() {
|
|
107
|
+
return new Date().toISOString().slice(0, 10);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- Cursor operations ---
|
|
111
|
+
|
|
112
|
+
function cursorGet(source) {
|
|
113
|
+
const lines = readTsv(PATHS.cursor);
|
|
114
|
+
const line = lines.find((l) => l.startsWith(source + "\t"));
|
|
115
|
+
if (!line) {
|
|
116
|
+
console.log(`No cursor for source: ${source}`);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const [, timestamp, position] = line.split("\t");
|
|
120
|
+
console.log(`${source}\t${timestamp}\t${position}`);
|
|
121
|
+
return { source, timestamp, position };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function cursorSet(source, timestamp, position) {
|
|
125
|
+
const lines = readTsv(PATHS.cursor);
|
|
126
|
+
const newLine = `${source}\t${timestamp}\t${position}`;
|
|
127
|
+
const idx = lines.findIndex((l) => l.startsWith(source + "\t"));
|
|
128
|
+
if (idx !== -1) {
|
|
129
|
+
lines[idx] = newLine;
|
|
130
|
+
} else {
|
|
131
|
+
lines.push(newLine);
|
|
132
|
+
}
|
|
133
|
+
writeTsv(PATHS.cursor, lines);
|
|
134
|
+
console.log(`Cursor updated: ${newLine}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function cursorList() {
|
|
138
|
+
const lines = readTsv(PATHS.cursor);
|
|
139
|
+
if (lines.length === 0) {
|
|
140
|
+
console.log("No cursors.");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
console.log(line);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- Seen operations ---
|
|
149
|
+
|
|
150
|
+
function seenCheck(source, id) {
|
|
151
|
+
const lines = readTsv(PATHS.seen);
|
|
152
|
+
const found = lines.some((l) => {
|
|
153
|
+
const parts = l.split("\t");
|
|
154
|
+
return parts[0] === source && parts[1] === id;
|
|
155
|
+
});
|
|
156
|
+
if (found) {
|
|
157
|
+
console.log(`SEEN: ${source}\t${id}`);
|
|
158
|
+
process.exit(0);
|
|
159
|
+
} else {
|
|
160
|
+
console.log(`NEW: ${source}\t${id}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function seenAdd(source, id, date) {
|
|
166
|
+
appendTsv(PATHS.seen, `${source}\t${id}\t${date || today()}`);
|
|
167
|
+
console.log(`Marked seen: ${source}\t${id}\t${date || today()}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function seenBatch(source, ids) {
|
|
171
|
+
const d = today();
|
|
172
|
+
const lines = ids.map((id) => `${source}\t${id}\t${d}`);
|
|
173
|
+
appendFileSync(PATHS.seen, lines.join("\n") + "\n");
|
|
174
|
+
console.log(`Marked ${ids.length} IDs as seen for ${source}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- Prospect operations ---
|
|
178
|
+
|
|
179
|
+
function prospectAdd(name, source, strength, level) {
|
|
180
|
+
const d = today();
|
|
181
|
+
appendTsv(PATHS.prospects, `${name}\t${source}\t${d}\t${strength}\t${level}`);
|
|
182
|
+
console.log(`Prospect added: ${name} (${strength}, ${level})`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function prospectList(limit) {
|
|
186
|
+
const lines = readTsv(PATHS.prospects);
|
|
187
|
+
if (lines.length === 0) {
|
|
188
|
+
console.log("No prospects.");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Show most recent first
|
|
192
|
+
const display = lines.slice(-limit).reverse();
|
|
193
|
+
for (const line of display) {
|
|
194
|
+
console.log(line);
|
|
195
|
+
}
|
|
196
|
+
if (lines.length > limit) {
|
|
197
|
+
console.log(`\n... ${lines.length - limit} more (${lines.length} total)`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function prospectCount() {
|
|
202
|
+
const lines = readTsv(PATHS.prospects);
|
|
203
|
+
console.log(lines.length);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Failure operations ---
|
|
207
|
+
|
|
208
|
+
function failureGet(source) {
|
|
209
|
+
const lines = readTsv(PATHS.failures);
|
|
210
|
+
const line = lines.find((l) => l.startsWith(source + "\t"));
|
|
211
|
+
if (!line) {
|
|
212
|
+
console.log(`0`);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
const count = parseInt(line.split("\t")[1], 10) || 0;
|
|
216
|
+
console.log(`${count}`);
|
|
217
|
+
return count;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function failureIncrement(source) {
|
|
221
|
+
const lines = readTsv(PATHS.failures);
|
|
222
|
+
const idx = lines.findIndex((l) => l.startsWith(source + "\t"));
|
|
223
|
+
if (idx !== -1) {
|
|
224
|
+
const count = parseInt(lines[idx].split("\t")[1], 10) || 0;
|
|
225
|
+
lines[idx] = `${source}\t${count + 1}`;
|
|
226
|
+
} else {
|
|
227
|
+
lines.push(`${source}\t1`);
|
|
228
|
+
}
|
|
229
|
+
writeTsv(PATHS.failures, lines);
|
|
230
|
+
const newCount = parseInt(
|
|
231
|
+
lines.find((l) => l.startsWith(source + "\t")).split("\t")[1],
|
|
232
|
+
10,
|
|
233
|
+
);
|
|
234
|
+
console.log(`Failures for ${source}: ${newCount}`);
|
|
235
|
+
if (newCount >= 3) {
|
|
236
|
+
console.log(
|
|
237
|
+
`WARNING: ${source} has ${newCount} consecutive failures (suspended at ≥3)`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function failureReset(source) {
|
|
243
|
+
const lines = readTsv(PATHS.failures);
|
|
244
|
+
const idx = lines.findIndex((l) => l.startsWith(source + "\t"));
|
|
245
|
+
if (idx !== -1) {
|
|
246
|
+
lines[idx] = `${source}\t0`;
|
|
247
|
+
writeTsv(PATHS.failures, lines);
|
|
248
|
+
}
|
|
249
|
+
console.log(`Failures reset for ${source}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// --- Log operations ---
|
|
253
|
+
|
|
254
|
+
function logAppend(message) {
|
|
255
|
+
appendFileSync(PATHS.log, message + "\n");
|
|
256
|
+
console.log("Log entry appended.");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function logWake(source, summary) {
|
|
260
|
+
const timestamp = new Date().toISOString().slice(0, 16).replace("T", " ");
|
|
261
|
+
const entry = `\n## ${today()} ${timestamp.slice(11)} — Wake (${source})\n\n${summary}\n\n---\n`;
|
|
262
|
+
appendFileSync(PATHS.log, entry);
|
|
263
|
+
console.log(`Wake cycle logged for ${source}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// --- Summary ---
|
|
267
|
+
|
|
268
|
+
function summary() {
|
|
269
|
+
const cursors = readTsv(PATHS.cursor);
|
|
270
|
+
const seen = readTsv(PATHS.seen);
|
|
271
|
+
const prospects = readTsv(PATHS.prospects);
|
|
272
|
+
const failures = readTsv(PATHS.failures);
|
|
273
|
+
|
|
274
|
+
console.log("=== Head-Hunter State Summary ===\n");
|
|
275
|
+
|
|
276
|
+
console.log(`Sources: ${cursors.length}`);
|
|
277
|
+
for (const line of cursors) {
|
|
278
|
+
const [source, timestamp, position] = line.split("\t");
|
|
279
|
+
const failLine = failures.find((l) => l.startsWith(source + "\t"));
|
|
280
|
+
const failCount = failLine ? parseInt(failLine.split("\t")[1], 10) : 0;
|
|
281
|
+
const status = failCount >= 3 ? " [SUSPENDED]" : "";
|
|
282
|
+
console.log(` ${source}: last=${timestamp}, pos=${position}${status}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log(`\nSeen: ${seen.length} entries`);
|
|
286
|
+
console.log(`Prospects: ${prospects.length} total`);
|
|
287
|
+
|
|
288
|
+
// Strength breakdown
|
|
289
|
+
const byStrength = {};
|
|
290
|
+
for (const line of prospects) {
|
|
291
|
+
const strength = line.split("\t")[3] || "unknown";
|
|
292
|
+
byStrength[strength] = (byStrength[strength] || 0) + 1;
|
|
293
|
+
}
|
|
294
|
+
for (const [k, v] of Object.entries(byStrength)) {
|
|
295
|
+
console.log(` ${k}: ${v}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Recent prospects (last 5)
|
|
299
|
+
if (prospects.length > 0) {
|
|
300
|
+
console.log("\nRecent prospects:");
|
|
301
|
+
for (const line of prospects.slice(-5)) {
|
|
302
|
+
const [name, source, date, strength, level] = line.split("\t");
|
|
303
|
+
console.log(
|
|
304
|
+
` ${date} | ${name} | ${strength} | ${level} | via ${source}`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// --- CLI Router ---
|
|
311
|
+
|
|
312
|
+
const cliArgs = process.argv.slice(2);
|
|
313
|
+
const cmd = cliArgs[0];
|
|
314
|
+
const sub = cliArgs[1];
|
|
315
|
+
|
|
316
|
+
switch (cmd) {
|
|
317
|
+
case "cursor":
|
|
318
|
+
if (sub === "get" && cliArgs[2]) cursorGet(cliArgs[2]);
|
|
319
|
+
else if (sub === "set" && cliArgs[2] && cliArgs[3] && cliArgs[4])
|
|
320
|
+
cursorSet(cliArgs[2], cliArgs[3], cliArgs[4]);
|
|
321
|
+
else if (sub === "list") cursorList();
|
|
322
|
+
else {
|
|
323
|
+
console.error(
|
|
324
|
+
"Usage: cursor get|set|list <source> [<timestamp> <position>]",
|
|
325
|
+
);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
|
|
330
|
+
case "seen":
|
|
331
|
+
if (sub === "check" && cliArgs[2] && cliArgs[3])
|
|
332
|
+
seenCheck(cliArgs[2], cliArgs[3]);
|
|
333
|
+
else if (sub === "add" && cliArgs[2] && cliArgs[3])
|
|
334
|
+
seenAdd(cliArgs[2], cliArgs[3], cliArgs[4]);
|
|
335
|
+
else if (sub === "batch" && cliArgs[2] && cliArgs.length > 3)
|
|
336
|
+
seenBatch(cliArgs[2], cliArgs.slice(3));
|
|
337
|
+
else {
|
|
338
|
+
console.error("Usage: seen check|add|batch <source> <id> [...]");
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
|
|
343
|
+
case "prospect":
|
|
344
|
+
if (sub === "add" && cliArgs[2] && cliArgs[3] && cliArgs[4] && cliArgs[5])
|
|
345
|
+
prospectAdd(cliArgs[2], cliArgs[3], cliArgs[4], cliArgs[5]);
|
|
346
|
+
else if (sub === "list") {
|
|
347
|
+
const lIdx = cliArgs.indexOf("--limit");
|
|
348
|
+
const lim = lIdx !== -1 ? parseInt(cliArgs[lIdx + 1], 10) || 10 : 10;
|
|
349
|
+
prospectList(lim);
|
|
350
|
+
} else if (sub === "count") prospectCount();
|
|
351
|
+
else {
|
|
352
|
+
console.error(
|
|
353
|
+
"Usage: prospect add|list|count <name> <source> <strength> <level>",
|
|
354
|
+
);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
|
|
359
|
+
case "failure":
|
|
360
|
+
if (sub === "get" && cliArgs[2]) failureGet(cliArgs[2]);
|
|
361
|
+
else if (sub === "increment" && cliArgs[2]) failureIncrement(cliArgs[2]);
|
|
362
|
+
else if (sub === "reset" && cliArgs[2]) failureReset(cliArgs[2]);
|
|
363
|
+
else {
|
|
364
|
+
console.error("Usage: failure get|increment|reset <source>");
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
break;
|
|
368
|
+
|
|
369
|
+
case "log":
|
|
370
|
+
if (cliArgs.length >= 2) logAppend(cliArgs.slice(1).join(" "));
|
|
371
|
+
else {
|
|
372
|
+
console.error("Usage: log <message>");
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case "log-wake":
|
|
378
|
+
if (cliArgs[1] && cliArgs.length >= 3)
|
|
379
|
+
logWake(cliArgs[1], cliArgs.slice(2).join(" "));
|
|
380
|
+
else {
|
|
381
|
+
console.error("Usage: log-wake <source> <summary>");
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
|
|
386
|
+
case "summary":
|
|
387
|
+
summary();
|
|
388
|
+
break;
|
|
389
|
+
|
|
390
|
+
default:
|
|
391
|
+
console.error(
|
|
392
|
+
"Unknown command. Run with --help for usage.\n" +
|
|
393
|
+
"Commands: cursor, seen, prospect, failure, log, log-wake, summary",
|
|
394
|
+
);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
@@ -96,6 +96,47 @@ Each `{event_id}.json` file:
|
|
|
96
96
|
- Database locked → wait 2 seconds, retry once
|
|
97
97
|
- Skip events with no summary (likely cancelled or placeholder)
|
|
98
98
|
|
|
99
|
+
## Querying Events
|
|
100
|
+
|
|
101
|
+
After syncing, use the query script to filter events by date or time window.
|
|
102
|
+
**Agents should use this script instead of writing bespoke calendar parsers.**
|
|
103
|
+
|
|
104
|
+
node scripts/query.mjs [options]
|
|
105
|
+
|
|
106
|
+
### Time filters (combinable)
|
|
107
|
+
|
|
108
|
+
| Flag | Description |
|
|
109
|
+
| ------------------------------- | --------------------------------------------------------- |
|
|
110
|
+
| `--today` | Events starting today (default if no filter given) |
|
|
111
|
+
| `--tomorrow` | Events starting tomorrow |
|
|
112
|
+
| `--upcoming 2h` | Events starting within interval (e.g., `2h`, `30m`, `1d`) |
|
|
113
|
+
| `--date 2026-03-09` | Events on a specific date |
|
|
114
|
+
| `--range 2026-03-09 2026-03-11` | Events between two dates (inclusive) |
|
|
115
|
+
|
|
116
|
+
### Output options
|
|
117
|
+
|
|
118
|
+
| Flag | Description |
|
|
119
|
+
| ------------------- | ----------------------------------------------- |
|
|
120
|
+
| `--json` | Output as JSON array (default: formatted table) |
|
|
121
|
+
| `--include-all-day` | Include all-day events (excluded by default) |
|
|
122
|
+
| `--no-attendees` | Omit attendee names from output |
|
|
123
|
+
|
|
124
|
+
### Examples
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Concierge: get today + tomorrow for triage
|
|
128
|
+
node scripts/query.mjs --today --tomorrow
|
|
129
|
+
|
|
130
|
+
# Meeting-prep: get upcoming meetings in next 2 hours as JSON
|
|
131
|
+
node scripts/query.mjs --upcoming 2h --json
|
|
132
|
+
|
|
133
|
+
# Chief-of-staff: get this week's events
|
|
134
|
+
node scripts/query.mjs --range 2026-03-09 2026-03-13
|
|
135
|
+
|
|
136
|
+
# Quick count check
|
|
137
|
+
node scripts/query.mjs --today --json | node -e "process.stdin.on('data',d=>console.log(JSON.parse(d).length+' events today'))"
|
|
138
|
+
```
|
|
139
|
+
|
|
99
140
|
## Constraints
|
|
100
141
|
|
|
101
142
|
- Open database read-only (`readOnly: true`)
|