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.
Files changed (3) hide show
  1. package/README.md +8 -1
  2. package/dist/cli.js +81 -12
  3. 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 human-readable meeting view, or a machine-readable export bundle
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
- this.#session = await this.options.source.loadSession();
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 export");
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, config, granolaClient } = await loadMeetingData(commandFlags, globalFlags);
1633
- console.log("Fetching meeting from Granola API...");
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, config, granolaClient } = await loadMeetingData(commandFlags, globalFlags);
1641
- console.log("Fetching meeting from Granola API...");
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granola-toolkit",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "CLI toolkit for exporting and working with Granola notes and transcripts",
5
5
  "keywords": [
6
6
  "cli",