granola-toolkit 0.13.0 → 0.14.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/README.md +8 -1
- package/dist/cli.js +81 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,6 +81,8 @@ Inspect individual meetings:
|
|
|
81
81
|
granola meeting list --limit 10
|
|
82
82
|
granola meeting list --search planning
|
|
83
83
|
granola meeting view 1234abcd
|
|
84
|
+
granola meeting notes 1234abcd
|
|
85
|
+
granola meeting transcript 1234abcd --format json
|
|
84
86
|
granola meeting export 1234abcd --format yaml
|
|
85
87
|
```
|
|
86
88
|
|
|
@@ -143,7 +145,7 @@ The flow is:
|
|
|
143
145
|
2. fetch documents from Granola's API
|
|
144
146
|
3. optionally load the local cache for transcript data
|
|
145
147
|
4. resolve a meeting by full id or unique id prefix
|
|
146
|
-
5. render either a list, a
|
|
148
|
+
5. render either a list, a combined meeting view, focused notes/transcript output, or a machine-readable export bundle
|
|
147
149
|
|
|
148
150
|
The human-readable `view` command shows:
|
|
149
151
|
|
|
@@ -151,6 +153,11 @@ The human-readable `view` command shows:
|
|
|
151
153
|
- the selected notes content
|
|
152
154
|
- transcript lines when the local cache is available
|
|
153
155
|
|
|
156
|
+
The focused meeting subcommands are:
|
|
157
|
+
|
|
158
|
+
- `meeting notes` for just the selected note output
|
|
159
|
+
- `meeting transcript` for just the selected transcript output
|
|
160
|
+
|
|
154
161
|
The machine-readable `export` command includes:
|
|
155
162
|
|
|
156
163
|
- a meeting summary
|
package/dist/cli.js
CHANGED
|
@@ -327,14 +327,22 @@ var StoredSessionTokenProvider = class {
|
|
|
327
327
|
}
|
|
328
328
|
async invalidate() {
|
|
329
329
|
const session = await this.loadSession().catch(() => void 0);
|
|
330
|
-
if (session?.refreshToken && session.clientId) {
|
|
330
|
+
if (session?.refreshToken && session.clientId) try {
|
|
331
331
|
const refreshedSession = await refreshGranolaSession(session, this.options.fetchImpl);
|
|
332
332
|
this.#session = refreshedSession;
|
|
333
333
|
await this.store.writeSession(refreshedSession);
|
|
334
334
|
return;
|
|
335
|
+
} catch {
|
|
336
|
+
if (!this.options.source) {
|
|
337
|
+
this.#session = void 0;
|
|
338
|
+
await this.store.clearSession();
|
|
339
|
+
throw new Error("failed to refresh stored Granola session");
|
|
340
|
+
}
|
|
335
341
|
}
|
|
336
342
|
if (this.options.source) {
|
|
337
|
-
|
|
343
|
+
const sourcedSession = await this.options.source.loadSession();
|
|
344
|
+
this.#session = sourcedSession;
|
|
345
|
+
await this.store.writeSession(sourcedSession);
|
|
338
346
|
return;
|
|
339
347
|
}
|
|
340
348
|
this.#session = void 0;
|
|
@@ -1361,6 +1369,7 @@ function buildMeetingTranscript(document, cacheData) {
|
|
|
1361
1369
|
loaded: false,
|
|
1362
1370
|
segmentCount: 0,
|
|
1363
1371
|
transcript: null,
|
|
1372
|
+
transcriptRecord: null,
|
|
1364
1373
|
transcriptText: null
|
|
1365
1374
|
};
|
|
1366
1375
|
const rawSegments = cacheData.transcripts[document.id] ?? [];
|
|
@@ -1369,6 +1378,7 @@ function buildMeetingTranscript(document, cacheData) {
|
|
|
1369
1378
|
loaded: true,
|
|
1370
1379
|
segmentCount: 0,
|
|
1371
1380
|
transcript: null,
|
|
1381
|
+
transcriptRecord: null,
|
|
1372
1382
|
transcriptText: null
|
|
1373
1383
|
};
|
|
1374
1384
|
const transcript = buildTranscriptExport(cacheDocumentForMeeting(document, cacheData), normalisedSegments, rawSegments);
|
|
@@ -1376,6 +1386,7 @@ function buildMeetingTranscript(document, cacheData) {
|
|
|
1376
1386
|
loaded: true,
|
|
1377
1387
|
segmentCount: transcript.segments.length,
|
|
1378
1388
|
transcript: serialiseTranscript(transcript),
|
|
1389
|
+
transcriptRecord: transcript,
|
|
1379
1390
|
transcriptText: renderTranscriptExport(transcript, "text")
|
|
1380
1391
|
};
|
|
1381
1392
|
}
|
|
@@ -1504,6 +1515,14 @@ function renderMeetingExport(record, format = "json") {
|
|
|
1504
1515
|
case "yaml": return toYaml(record);
|
|
1505
1516
|
}
|
|
1506
1517
|
}
|
|
1518
|
+
function renderMeetingNotes(document, format = "markdown") {
|
|
1519
|
+
return renderNoteExport(buildNoteExport(document), format);
|
|
1520
|
+
}
|
|
1521
|
+
function renderMeetingTranscript(document, cacheData, format = "text") {
|
|
1522
|
+
const transcript = buildMeetingTranscript(document, cacheData).transcriptRecord;
|
|
1523
|
+
if (!transcript) return "";
|
|
1524
|
+
return renderTranscriptExport(transcript, format);
|
|
1525
|
+
}
|
|
1507
1526
|
//#endregion
|
|
1508
1527
|
//#region src/commands/shared.ts
|
|
1509
1528
|
function debug(enabled, ...values) {
|
|
@@ -1515,16 +1534,18 @@ function meetingHelp() {
|
|
|
1515
1534
|
return `Granola meeting
|
|
1516
1535
|
|
|
1517
1536
|
Usage:
|
|
1518
|
-
granola meeting <list|view|export> [options]
|
|
1537
|
+
granola meeting <list|view|export|notes|transcript> [options]
|
|
1519
1538
|
|
|
1520
1539
|
Subcommands:
|
|
1521
1540
|
list List meetings from the Granola API
|
|
1522
1541
|
view <id> Show a single meeting with notes and transcript text
|
|
1523
1542
|
export <id> Export a single meeting as JSON or YAML
|
|
1543
|
+
notes <id> Show a single meeting's notes
|
|
1544
|
+
transcript <id> Show a single meeting's transcript
|
|
1524
1545
|
|
|
1525
1546
|
Options:
|
|
1526
1547
|
--cache <path> Path to Granola cache JSON for transcript data
|
|
1527
|
-
--format <value> list/view: text, json, yaml; export: json, yaml
|
|
1548
|
+
--format <value> list/view: text, json, yaml; export: json, yaml; notes: markdown, json, yaml, raw; transcript: text, json, yaml, raw
|
|
1528
1549
|
--limit <n> Number of meetings for list (default: 20)
|
|
1529
1550
|
--search <query> Filter list by title, id, or tag
|
|
1530
1551
|
--timeout <value> Request timeout, e.g. 2m, 30s, 120000 (default: 2m)
|
|
@@ -1560,6 +1581,26 @@ function resolveExportFormat(value) {
|
|
|
1560
1581
|
default: throw new Error("invalid meeting export format: expected json or yaml");
|
|
1561
1582
|
}
|
|
1562
1583
|
}
|
|
1584
|
+
function resolveNotesFormat(value) {
|
|
1585
|
+
switch (value) {
|
|
1586
|
+
case void 0: return "markdown";
|
|
1587
|
+
case "json":
|
|
1588
|
+
case "markdown":
|
|
1589
|
+
case "raw":
|
|
1590
|
+
case "yaml": return value;
|
|
1591
|
+
default: throw new Error("invalid meeting notes format: expected markdown, json, yaml, or raw");
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
function resolveTranscriptFormat$1(value) {
|
|
1595
|
+
switch (value) {
|
|
1596
|
+
case void 0: return "text";
|
|
1597
|
+
case "json":
|
|
1598
|
+
case "raw":
|
|
1599
|
+
case "text":
|
|
1600
|
+
case "yaml": return value;
|
|
1601
|
+
default: throw new Error("invalid meeting transcript format: expected text, json, yaml, or raw");
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1563
1604
|
function parseLimit(value) {
|
|
1564
1605
|
if (value === void 0) return 20;
|
|
1565
1606
|
if (typeof value !== "string" || !/^\d+$/.test(value)) throw new Error("invalid meeting limit: expected a positive integer");
|
|
@@ -1589,18 +1630,25 @@ const meetingCommand = {
|
|
|
1589
1630
|
case "export":
|
|
1590
1631
|
if (!id) throw new Error("meeting export requires an id");
|
|
1591
1632
|
return await exportMeeting(id, commandFlags, globalFlags);
|
|
1633
|
+
case "notes":
|
|
1634
|
+
if (!id) throw new Error("meeting notes requires an id");
|
|
1635
|
+
return await notes(id, commandFlags, globalFlags);
|
|
1636
|
+
case "transcript":
|
|
1637
|
+
if (!id) throw new Error("meeting transcript requires an id");
|
|
1638
|
+
return await transcript(id, commandFlags, globalFlags);
|
|
1592
1639
|
case void 0:
|
|
1593
1640
|
console.log(meetingHelp());
|
|
1594
1641
|
return 1;
|
|
1595
|
-
default: throw new Error("invalid meeting command: expected list, view, or
|
|
1642
|
+
default: throw new Error("invalid meeting command: expected list, view, export, notes, or transcript");
|
|
1596
1643
|
}
|
|
1597
1644
|
}
|
|
1598
1645
|
};
|
|
1599
|
-
async function loadMeetingData(commandFlags, globalFlags) {
|
|
1646
|
+
async function loadMeetingData(commandFlags, globalFlags, options = {}) {
|
|
1600
1647
|
const config = await loadConfig({
|
|
1601
1648
|
globalFlags,
|
|
1602
1649
|
subcommandFlags: commandFlags
|
|
1603
1650
|
});
|
|
1651
|
+
if (options.requireCache && !config.transcripts.cacheFile) throw new Error(`Granola cache file not found. Pass --cache or create .granola.toml. Expected locations include: ${granolaCacheCandidates().join(", ")}`);
|
|
1604
1652
|
if (config.transcripts.cacheFile && !existsSync(config.transcripts.cacheFile)) throw new Error(`Granola cache file not found: ${config.transcripts.cacheFile}`);
|
|
1605
1653
|
debug(config.debug, "using config", config.configFileUsed ?? "(none)");
|
|
1606
1654
|
debug(config.debug, "supabase", config.supabase);
|
|
@@ -1613,6 +1661,15 @@ async function loadMeetingData(commandFlags, globalFlags) {
|
|
|
1613
1661
|
granolaClient
|
|
1614
1662
|
};
|
|
1615
1663
|
}
|
|
1664
|
+
async function loadResolvedMeeting(id, commandFlags, globalFlags, options = {}) {
|
|
1665
|
+
const { cacheData, config, granolaClient } = await loadMeetingData(commandFlags, globalFlags, options);
|
|
1666
|
+
console.log("Fetching meeting from Granola API...");
|
|
1667
|
+
return {
|
|
1668
|
+
cacheData,
|
|
1669
|
+
config,
|
|
1670
|
+
document: resolveMeeting(await granolaClient.listDocuments({ timeoutMs: config.notes.timeoutMs }), id)
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1616
1673
|
async function list(commandFlags, globalFlags) {
|
|
1617
1674
|
const format = resolveListFormat(commandFlags.format);
|
|
1618
1675
|
const limit = parseLimit(commandFlags.limit);
|
|
@@ -1629,20 +1686,32 @@ async function list(commandFlags, globalFlags) {
|
|
|
1629
1686
|
}
|
|
1630
1687
|
async function view(id, commandFlags, globalFlags) {
|
|
1631
1688
|
const format = resolveViewFormat(commandFlags.format);
|
|
1632
|
-
const { cacheData,
|
|
1633
|
-
|
|
1634
|
-
const meeting = buildMeetingRecord(resolveMeeting(await granolaClient.listDocuments({ timeoutMs: config.notes.timeoutMs }), id), cacheData);
|
|
1689
|
+
const { cacheData, document } = await loadResolvedMeeting(id, commandFlags, globalFlags);
|
|
1690
|
+
const meeting = buildMeetingRecord(document, cacheData);
|
|
1635
1691
|
console.log(renderMeetingView(meeting, format).trimEnd());
|
|
1636
1692
|
return 0;
|
|
1637
1693
|
}
|
|
1638
1694
|
async function exportMeeting(id, commandFlags, globalFlags) {
|
|
1639
1695
|
const format = resolveExportFormat(commandFlags.format);
|
|
1640
|
-
const { cacheData,
|
|
1641
|
-
|
|
1642
|
-
const meeting = buildMeetingRecord(resolveMeeting(await granolaClient.listDocuments({ timeoutMs: config.notes.timeoutMs }), id), cacheData);
|
|
1696
|
+
const { cacheData, document } = await loadResolvedMeeting(id, commandFlags, globalFlags);
|
|
1697
|
+
const meeting = buildMeetingRecord(document, cacheData);
|
|
1643
1698
|
console.log(renderMeetingExport(meeting, format).trimEnd());
|
|
1644
1699
|
return 0;
|
|
1645
1700
|
}
|
|
1701
|
+
async function notes(id, commandFlags, globalFlags) {
|
|
1702
|
+
const format = resolveNotesFormat(commandFlags.format);
|
|
1703
|
+
const { document } = await loadResolvedMeeting(id, commandFlags, globalFlags);
|
|
1704
|
+
console.log(renderMeetingNotes(document, format).trimEnd());
|
|
1705
|
+
return 0;
|
|
1706
|
+
}
|
|
1707
|
+
async function transcript(id, commandFlags, globalFlags) {
|
|
1708
|
+
const format = resolveTranscriptFormat$1(commandFlags.format);
|
|
1709
|
+
const { cacheData, document } = await loadResolvedMeeting(id, commandFlags, globalFlags, { requireCache: true });
|
|
1710
|
+
const output = renderMeetingTranscript(document, cacheData, format);
|
|
1711
|
+
if (!output.trim()) throw new Error(`no transcript found for meeting: ${document.id}`);
|
|
1712
|
+
console.log(output.trimEnd());
|
|
1713
|
+
return 0;
|
|
1714
|
+
}
|
|
1646
1715
|
//#endregion
|
|
1647
1716
|
//#region src/commands/notes.ts
|
|
1648
1717
|
function notesHelp() {
|