granola-toolkit 0.33.0 → 0.34.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 +12 -2
  2. package/dist/cli.js +166 -47
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -79,6 +79,7 @@ Export notes:
79
79
  ```bash
80
80
  granola auth login
81
81
  granola notes
82
+ granola notes --folder Team
82
83
 
83
84
  node dist/cli.js notes --supabase "$HOME/Library/Application Support/Granola/supabase.json"
84
85
  node dist/cli.js notes --format json --output ./notes-json
@@ -91,6 +92,7 @@ Export transcripts:
91
92
  ```bash
92
93
  node dist/cli.js transcripts --cache "$HOME/Library/Application Support/Granola/cache-v3.json"
93
94
  node dist/cli.js transcripts --format yaml --output ./transcripts-yaml
95
+ granola transcripts --folder Team
94
96
  ```
95
97
 
96
98
  Inspect individual meetings:
@@ -143,6 +145,8 @@ The flow is:
143
145
  6. render that export as Markdown, JSON, YAML, or raw JSON
144
146
  7. write one file per document into the output directory
145
147
 
148
+ When you pass `--folder <id|name>`, the export is filtered to that folder and, by default, written into a stable per-folder subdirectory under the notes output root.
149
+
146
150
  Content is chosen in this order:
147
151
 
148
152
  1. `notes`
@@ -169,6 +173,8 @@ The flow is:
169
173
  5. render each export as text, JSON, YAML, or raw JSON
170
174
  6. write one file per document into the output directory
171
175
 
176
+ When you pass `--folder <id|name>`, the export is filtered to that folder and, by default, written into a stable per-folder subdirectory under the transcripts output root.
177
+
172
178
  Speaker labels are currently normalised to:
173
179
 
174
180
  - `You` for `microphone`
@@ -223,6 +229,8 @@ The current CLI surface includes:
223
229
  - `folder list`
224
230
  - `folder view <id|name>`
225
231
  - `meeting list --folder <id|name>`
232
+ - `notes --folder <id|name>`
233
+ - `transcripts --folder <id|name>`
226
234
 
227
235
  ### Server
228
236
 
@@ -250,9 +258,9 @@ The initial server API includes:
250
258
  - `POST /auth/logout`
251
259
  - `POST /auth/mode`
252
260
  - `POST /auth/refresh`
253
- - `POST /exports/notes`
261
+ - `POST /exports/notes` with optional `folderId`
254
262
  - `POST /exports/jobs/:id/rerun`
255
- - `POST /exports/transcripts`
263
+ - `POST /exports/transcripts` with optional `folderId`
256
264
 
257
265
  This is the shared runtime for `granola web` and `granola attach`.
258
266
 
@@ -287,6 +295,7 @@ The initial browser client includes:
287
295
  - app-state status from the shared core
288
296
  - an auth session panel for login, refresh, source switching, and sign-out
289
297
  - note and transcript export actions backed by the same local API
298
+ - folder-scoped export actions that follow the currently selected folder
290
299
  - a recent export-jobs panel with rerun actions
291
300
  - stronger empty and error states for list/detail failures
292
301
  - a server-access panel that can unlock or lock a password-protected local server
@@ -354,6 +363,7 @@ The web client uses the index as a fast path and upgrades to live data automatic
354
363
  Exports are now tracked as jobs with:
355
364
 
356
365
  - persistent local history across CLI and web runs
366
+ - explicit scope metadata for all-meetings and folder-scoped runs
357
367
  - running, completed, and failed status
358
368
  - per-export progress counters
359
369
  - rerun support from `granola exports rerun <job-id>` or the web client
package/dist/cli.js CHANGED
@@ -218,16 +218,22 @@ var GranolaServerClient = class GranolaServerClient {
218
218
  async listExportJobs(options = {}) {
219
219
  return await this.requestJson(granolaExportJobsPath(options));
220
220
  }
221
- async exportNotes(format = "markdown") {
221
+ async exportNotes(format = "markdown", options = {}) {
222
222
  return await this.requestJson(granolaTransportPaths.exportNotes, {
223
- body: JSON.stringify({ format }),
223
+ body: JSON.stringify({
224
+ folderId: options.folderId,
225
+ format
226
+ }),
224
227
  headers: { "content-type": "application/json" },
225
228
  method: "POST"
226
229
  });
227
230
  }
228
- async exportTranscripts(format = "text") {
231
+ async exportTranscripts(format = "text", options = {}) {
229
232
  return await this.requestJson(granolaTransportPaths.exportTranscripts, {
230
- body: JSON.stringify({ format }),
233
+ body: JSON.stringify({
234
+ folderId: options.folderId,
235
+ format
236
+ }),
231
237
  headers: { "content-type": "application/json" },
232
238
  method: "POST"
233
239
  });
@@ -2974,6 +2980,42 @@ async function loadOptionalGranolaCache(cacheFile) {
2974
2980
  return parseCacheContents(await readFile(cacheFile, "utf8"));
2975
2981
  }
2976
2982
  //#endregion
2983
+ //#region src/export-scope.ts
2984
+ const FOLDER_EXPORT_DIRECTORY = "_folders";
2985
+ function allExportScope() {
2986
+ return { mode: "all" };
2987
+ }
2988
+ function folderExportScope(folder) {
2989
+ return {
2990
+ folderId: folder.id,
2991
+ folderName: folder.name || folder.id,
2992
+ mode: "folder"
2993
+ };
2994
+ }
2995
+ function cloneExportScope(scope) {
2996
+ return scope.mode === "folder" ? { ...scope } : { mode: "all" };
2997
+ }
2998
+ function normaliseExportScope(value) {
2999
+ const record = asRecord(value);
3000
+ if (!record) return allExportScope();
3001
+ if (record.mode !== "folder") return allExportScope();
3002
+ const folderId = stringValue(record.folderId);
3003
+ const folderName = stringValue(record.folderName) || folderId;
3004
+ if (!folderId) return allExportScope();
3005
+ return {
3006
+ folderId,
3007
+ folderName,
3008
+ mode: "folder"
3009
+ };
3010
+ }
3011
+ function renderExportScopeLabel(scope) {
3012
+ return scope.mode === "folder" ? `folder ${scope.folderName}` : "all meetings";
3013
+ }
3014
+ function resolveExportOutputDir(outputDir, scope, options = {}) {
3015
+ if (scope.mode !== "folder" || options.scopedDirectory === false) return outputDir;
3016
+ return join(outputDir, FOLDER_EXPORT_DIRECTORY, sanitiseFilename(scope.folderId, "folder"));
3017
+ }
3018
+ //#endregion
2977
3019
  //#region src/export-jobs.ts
2978
3020
  const EXPORT_JOBS_VERSION = 1;
2979
3021
  const MAX_EXPORT_JOBS = 100;
@@ -2999,6 +3041,7 @@ function normaliseJob(value) {
2999
3041
  itemCount,
3000
3042
  kind,
3001
3043
  outputDir,
3044
+ scope: normaliseExportScope(record.scope),
3002
3045
  startedAt,
3003
3046
  status,
3004
3047
  written
@@ -3209,10 +3252,16 @@ function transcriptCount(cacheData) {
3209
3252
  return Object.values(cacheData.transcripts).filter((segments) => segments.length > 0).length;
3210
3253
  }
3211
3254
  function cloneExportState(state) {
3212
- return state ? { ...state } : void 0;
3255
+ return state ? {
3256
+ ...state,
3257
+ scope: cloneExportScope(state.scope)
3258
+ } : void 0;
3213
3259
  }
3214
3260
  function cloneExportJob(job) {
3215
- return { ...job };
3261
+ return {
3262
+ ...job,
3263
+ scope: cloneExportScope(job.scope)
3264
+ };
3216
3265
  }
3217
3266
  function cloneFolderSummary(folder) {
3218
3267
  return { ...folder };
@@ -3463,7 +3512,7 @@ var GranolaApp = class {
3463
3512
  this.emitStateUpdate();
3464
3513
  return cloneExportJob(job);
3465
3514
  }
3466
- async startExportJob(kind, format, itemCount, outputDir) {
3515
+ async startExportJob(kind, format, itemCount, outputDir, scope) {
3467
3516
  return await this.updateExportJob({
3468
3517
  completedCount: 0,
3469
3518
  format,
@@ -3471,6 +3520,7 @@ var GranolaApp = class {
3471
3520
  itemCount,
3472
3521
  kind,
3473
3522
  outputDir,
3523
+ scope: cloneExportScope(scope),
3474
3524
  startedAt: this.nowIso(),
3475
3525
  status: "running",
3476
3526
  written: 0
@@ -3740,25 +3790,29 @@ var GranolaApp = class {
3740
3790
  this.setUiState({ view: "exports-history" });
3741
3791
  return { jobs };
3742
3792
  }
3743
- async exportNotes(format = "markdown") {
3793
+ async exportNotes(format = "markdown", options = {}) {
3794
+ const documents = await this.listDocuments();
3795
+ const exportContext = await this.resolveExportContext(options.folderId);
3796
+ const filteredDocuments = exportContext.documentIds ? documents.filter((document) => exportContext.documentIds.has(document.id)) : documents;
3744
3797
  return await this.runNotesExport({
3798
+ documents: filteredDocuments,
3745
3799
  format,
3746
- outputDir: this.config.notes.output
3800
+ outputDir: resolveExportOutputDir(options.outputDir ?? this.config.notes.output, exportContext.scope, { scopedDirectory: options.scopedOutput }),
3801
+ scope: exportContext.scope
3747
3802
  });
3748
3803
  }
3749
3804
  async runNotesExport(options) {
3750
- const documents = await this.listDocuments();
3751
- let job = await this.startExportJob("notes", options.format, documents.length, options.outputDir);
3805
+ let job = await this.startExportJob("notes", options.format, options.documents.length, options.outputDir, options.scope);
3752
3806
  let written = 0;
3753
3807
  try {
3754
- written = await writeNotes(documents, options.outputDir, options.format, { onProgress: async (progress) => {
3808
+ written = await writeNotes(options.documents, options.outputDir, options.format, { onProgress: async (progress) => {
3755
3809
  job = await this.setExportJobProgress(job, {
3756
3810
  completedCount: progress.completed,
3757
3811
  written: progress.written
3758
3812
  });
3759
3813
  } });
3760
3814
  job = await this.completeExportJob(job, {
3761
- completedCount: documents.length,
3815
+ completedCount: options.documents.length,
3762
3816
  written
3763
3817
  });
3764
3818
  } catch (error) {
@@ -3767,37 +3821,49 @@ var GranolaApp = class {
3767
3821
  }
3768
3822
  this.#state.exports.notes = {
3769
3823
  format: options.format,
3770
- itemCount: documents.length,
3824
+ itemCount: options.documents.length,
3771
3825
  jobId: job.id,
3772
3826
  outputDir: options.outputDir,
3773
3827
  ranAt: this.nowIso(),
3828
+ scope: cloneExportScope(options.scope),
3774
3829
  written
3775
3830
  };
3776
3831
  this.emitStateUpdate();
3777
- this.setUiState({ view: "notes-export" });
3832
+ this.setUiState({
3833
+ selectedFolderId: options.scope.mode === "folder" ? options.scope.folderId : void 0,
3834
+ view: "notes-export"
3835
+ });
3778
3836
  return {
3779
- documentCount: documents.length,
3780
- documents,
3837
+ documentCount: options.documents.length,
3838
+ documents: options.documents,
3781
3839
  format: options.format,
3782
3840
  job,
3783
3841
  outputDir: options.outputDir,
3842
+ scope: cloneExportScope(options.scope),
3784
3843
  written
3785
3844
  };
3786
3845
  }
3787
- async exportTranscripts(format = "text") {
3846
+ async exportTranscripts(format = "text", options = {}) {
3847
+ const cacheData = await this.loadCache({ required: true });
3848
+ if (!cacheData) throw this.missingCacheError();
3849
+ const exportContext = await this.resolveExportContext(options.folderId);
3850
+ const scopedCacheData = exportContext.documentIds ? {
3851
+ documents: Object.fromEntries(Object.entries(cacheData.documents).filter(([id]) => exportContext.documentIds.has(id))),
3852
+ transcripts: Object.fromEntries(Object.entries(cacheData.transcripts).filter(([id]) => exportContext.documentIds.has(id)))
3853
+ } : cacheData;
3788
3854
  return await this.runTranscriptsExport({
3855
+ cacheData: scopedCacheData,
3789
3856
  format,
3790
- outputDir: this.config.transcripts.output
3857
+ outputDir: resolveExportOutputDir(options.outputDir ?? this.config.transcripts.output, exportContext.scope, { scopedDirectory: options.scopedOutput }),
3858
+ scope: exportContext.scope
3791
3859
  });
3792
3860
  }
3793
3861
  async runTranscriptsExport(options) {
3794
- const cacheData = await this.loadCache({ required: true });
3795
- if (!cacheData) throw this.missingCacheError();
3796
- const count = transcriptCount(cacheData);
3797
- let job = await this.startExportJob("transcripts", options.format, count, options.outputDir);
3862
+ const count = transcriptCount(options.cacheData);
3863
+ let job = await this.startExportJob("transcripts", options.format, count, options.outputDir, options.scope);
3798
3864
  let written = 0;
3799
3865
  try {
3800
- written = await writeTranscripts(cacheData, options.outputDir, options.format, { onProgress: async (progress) => {
3866
+ written = await writeTranscripts(options.cacheData, options.outputDir, options.format, { onProgress: async (progress) => {
3801
3867
  job = await this.setExportJobProgress(job, {
3802
3868
  completedCount: progress.completed,
3803
3869
  written: progress.written
@@ -3817,15 +3883,20 @@ var GranolaApp = class {
3817
3883
  jobId: job.id,
3818
3884
  outputDir: options.outputDir,
3819
3885
  ranAt: this.nowIso(),
3886
+ scope: cloneExportScope(options.scope),
3820
3887
  written
3821
3888
  };
3822
3889
  this.emitStateUpdate();
3823
- this.setUiState({ view: "transcripts-export" });
3890
+ this.setUiState({
3891
+ selectedFolderId: options.scope.mode === "folder" ? options.scope.folderId : void 0,
3892
+ view: "transcripts-export"
3893
+ });
3824
3894
  return {
3825
- cacheData,
3895
+ cacheData: options.cacheData,
3826
3896
  format: options.format,
3827
3897
  job,
3828
3898
  outputDir: options.outputDir,
3899
+ scope: cloneExportScope(options.scope),
3829
3900
  transcriptCount: count,
3830
3901
  written
3831
3902
  };
@@ -3833,15 +3904,28 @@ var GranolaApp = class {
3833
3904
  async rerunExportJob(id) {
3834
3905
  const job = this.#state.exports.jobs.find((candidate) => candidate.id === id);
3835
3906
  if (!job) throw new Error(`export job not found: ${id}`);
3836
- if (job.kind === "notes") return await this.runNotesExport({
3837
- format: job.format,
3838
- outputDir: job.outputDir
3907
+ if (job.kind === "notes") return await this.exportNotes(job.format, {
3908
+ folderId: job.scope.mode === "folder" ? job.scope.folderId : void 0,
3909
+ outputDir: job.outputDir,
3910
+ scopedOutput: false
3839
3911
  });
3840
- return await this.runTranscriptsExport({
3841
- format: job.format,
3842
- outputDir: job.outputDir
3912
+ return await this.exportTranscripts(job.format, {
3913
+ folderId: job.scope.mode === "folder" ? job.scope.folderId : void 0,
3914
+ outputDir: job.outputDir,
3915
+ scopedOutput: false
3843
3916
  });
3844
3917
  }
3918
+ async resolveExportContext(folderId) {
3919
+ if (!folderId) return { scope: allExportScope() };
3920
+ const folders = await this.loadFolders({ required: true });
3921
+ const summary = resolveFolder((folders ?? []).map((folder) => buildFolderSummary(folder)), folderId);
3922
+ const rawFolder = (folders ?? []).find((candidate) => candidate.id === summary.id);
3923
+ if (!rawFolder) throw new Error(`folder not found: ${folderId}`);
3924
+ return {
3925
+ documentIds: new Set(rawFolder.documentIds),
3926
+ scope: folderExportScope(summary)
3927
+ };
3928
+ }
3845
3929
  };
3846
3930
  async function createGranolaApp(config, options = {}) {
3847
3931
  const auth = await inspectDefaultGranolaAuth(config);
@@ -4132,8 +4216,8 @@ function renderExportJobs(jobs, format) {
4132
4216
  if (format === "json") return toJson({ jobs });
4133
4217
  if (format === "yaml") return toYaml({ jobs });
4134
4218
  if (jobs.length === 0) return "No export jobs\n";
4135
- return `${["ID KIND STATUS FORMAT ITEMS WRITTEN STARTED", ...jobs.map((job) => {
4136
- return `${job.id.padEnd(28).slice(0, 28)} ${job.kind.padEnd(12)} ${job.status.padEnd(11)} ${job.format.padEnd(11)} ${String(job.itemCount).padEnd(7)} ${String(job.written).padEnd(8)} ${job.startedAt.slice(0, 19)}`;
4219
+ return `${["ID KIND STATUS FORMAT SCOPE ITEMS WRITTEN STARTED", ...jobs.map((job) => {
4220
+ return `${job.id.padEnd(28).slice(0, 28)} ${job.kind.padEnd(12)} ${job.status.padEnd(11)} ${job.format.padEnd(11)} ${renderExportScopeLabel(job.scope).padEnd(20).slice(0, 20)} ${String(job.itemCount).padEnd(7)} ${String(job.written).padEnd(8)} ${job.startedAt.slice(0, 19)}`;
4137
4221
  })].join("\n")}\n`;
4138
4222
  }
4139
4223
  const exportsCommand = {
@@ -4183,10 +4267,10 @@ async function rerun(id, commandFlags, globalFlags) {
4183
4267
  debug(config.debug, "cacheFile", config.transcripts.cacheFile || "(none)");
4184
4268
  const result = await (await createGranolaApp(config)).rerunExportJob(id);
4185
4269
  if ("documentCount" in result) {
4186
- console.log(`✓ Reran notes export job ${result.job.id} to ${result.outputDir} (${result.written}/${result.documentCount} written)`);
4270
+ console.log(`✓ Reran notes export job ${result.job.id} from ${renderExportScopeLabel(result.scope)} to ${result.outputDir} (${result.written}/${result.documentCount} written)`);
4187
4271
  return 0;
4188
4272
  }
4189
- console.log(`✓ Reran transcripts export job ${result.job.id} to ${result.outputDir} (${result.written}/${result.transcriptCount} written)`);
4273
+ console.log(`✓ Reran transcripts export job ${result.job.id} from ${renderExportScopeLabel(result.scope)} to ${result.outputDir} (${result.written}/${result.transcriptCount} written)`);
4190
4274
  return 0;
4191
4275
  }
4192
4276
  //#endregion
@@ -4426,6 +4510,12 @@ function escapeHtml(value) {
4426
4510
  .replaceAll('"', "&quot;");
4427
4511
  }
4428
4512
 
4513
+ function exportScopeLabel(scope) {
4514
+ return scope && scope.mode === "folder"
4515
+ ? "Folder: " + (scope.folderName || scope.folderId)
4516
+ : "Scope: All meetings";
4517
+ }
4518
+
4429
4519
  function setStatus(label, tone = "idle") {
4430
4520
  els.stateBadge.textContent = label;
4431
4521
  els.stateBadge.dataset.tone = tone;
@@ -4764,8 +4854,9 @@ function renderExportJobs() {
4764
4854
  "</div>",
4765
4855
  '<div class="job-card__status" data-status="' + escapeHtml(job.status) + '">' + escapeHtml(job.status) + "</div>",
4766
4856
  "</div>",
4767
- '<div class="job-card__meta">Format: ' + escapeHtml(job.format) + " • " + escapeHtml(progress) + " • Written: " + escapeHtml(String(job.written)) + "</div>",
4857
+ '<div class="job-card__meta">Format: ' + escapeHtml(job.format) + " • " + escapeHtml(exportScopeLabel(job.scope)) + " • " + escapeHtml(progress) + " • Written: " + escapeHtml(String(job.written)) + "</div>",
4768
4858
  '<div class="job-card__meta">Started: ' + escapeHtml(job.startedAt.slice(0, 19)) + "</div>",
4859
+ '<div class="job-card__meta">Output: ' + escapeHtml(job.outputDir) + "</div>",
4769
4860
  error,
4770
4861
  '<div class="job-card__actions">' + rerunButton + "</div>",
4771
4862
  "</article>",
@@ -4965,9 +5056,12 @@ async function syncAuthState() {
4965
5056
  }
4966
5057
 
4967
5058
  async function exportNotes() {
4968
- setStatus("Exporting notes…", "busy");
5059
+ setStatus(state.selectedFolderId ? "Exporting folder notes…" : "Exporting notes…", "busy");
4969
5060
  await fetchJson("/exports/notes", {
4970
- body: JSON.stringify({ format: "markdown" }),
5061
+ body: JSON.stringify({
5062
+ folderId: state.selectedFolderId || undefined,
5063
+ format: "markdown",
5064
+ }),
4971
5065
  headers: { "content-type": "application/json" },
4972
5066
  method: "POST",
4973
5067
  });
@@ -4975,9 +5069,15 @@ async function exportNotes() {
4975
5069
  }
4976
5070
 
4977
5071
  async function exportTranscripts() {
4978
- setStatus("Exporting transcripts…", "busy");
5072
+ setStatus(
5073
+ state.selectedFolderId ? "Exporting folder transcripts…" : "Exporting transcripts…",
5074
+ "busy",
5075
+ );
4979
5076
  await fetchJson("/exports/transcripts", {
4980
- body: JSON.stringify({ format: "text" }),
5077
+ body: JSON.stringify({
5078
+ folderId: state.selectedFolderId || undefined,
5079
+ format: "text",
5080
+ }),
4981
5081
  headers: { "content-type": "application/json" },
4982
5082
  method: "POST",
4983
5083
  });
@@ -6059,6 +6159,9 @@ function parseAuthMode(value) {
6059
6159
  default: throw new Error("invalid auth mode: expected stored-session or supabase-file");
6060
6160
  }
6061
6161
  }
6162
+ function folderIdFromBody(value) {
6163
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
6164
+ }
6062
6165
  function sendJson(response, body, init = {}) {
6063
6166
  const payload = `${JSON.stringify(body, null, 2)}\n`;
6064
6167
  response.writeHead(init.status ?? 200, {
@@ -6404,7 +6507,7 @@ async function startGranolaServer(app, options = {}) {
6404
6507
  }
6405
6508
  if (method === "POST" && path === granolaTransportPaths.exportNotes) {
6406
6509
  const body = await readJsonBody(request);
6407
- sendJson(response, await app.exportNotes(noteFormatFromBody(body.format)), {
6510
+ sendJson(response, await app.exportNotes(noteFormatFromBody(body.format), { folderId: folderIdFromBody(body.folderId) }), {
6408
6511
  headers: originHeaders,
6409
6512
  status: 202
6410
6513
  });
@@ -6426,7 +6529,7 @@ async function startGranolaServer(app, options = {}) {
6426
6529
  }
6427
6530
  if (method === "POST" && path === granolaTransportPaths.exportTranscripts) {
6428
6531
  const body = await readJsonBody(request);
6429
- sendJson(response, await app.exportTranscripts(transcriptFormatFromBody(body.format)), {
6532
+ sendJson(response, await app.exportTranscripts(transcriptFormatFromBody(body.format), { folderId: folderIdFromBody(body.folderId) }), {
6430
6533
  headers: originHeaders,
6431
6534
  status: 202
6432
6535
  });
@@ -6806,6 +6909,7 @@ Usage:
6806
6909
  granola notes [options]
6807
6910
 
6808
6911
  Options:
6912
+ --folder <query> Export only meetings inside one folder id or name
6809
6913
  --format <value> Output format: markdown, json, yaml, raw (default: markdown)
6810
6914
  --output <path> Output directory for note files (default: ./notes)
6811
6915
  --timeout <value> Request timeout, e.g. 2m, 30s, 120000 (default: 2m)
@@ -6818,6 +6922,7 @@ Options:
6818
6922
  const notesCommand = {
6819
6923
  description: "Export Granola notes",
6820
6924
  flags: {
6925
+ folder: { type: "string" },
6821
6926
  format: { type: "string" },
6822
6927
  help: { type: "boolean" },
6823
6928
  output: { type: "string" },
@@ -6838,8 +6943,14 @@ const notesCommand = {
6838
6943
  debug(config.debug, "format", format);
6839
6944
  const app = await createGranolaApp(config);
6840
6945
  debug(config.debug, "authMode", app.getState().auth.mode);
6841
- const result = await app.exportNotes(format);
6842
- console.log(`✓ Exported ${result.documentCount} notes to ${result.outputDir} (job ${result.job.id})`);
6946
+ const folderQuery = typeof commandFlags.folder === "string" ? commandFlags.folder : void 0;
6947
+ const folder = folderQuery ? await app.findFolder(folderQuery) : void 0;
6948
+ debug(config.debug, "folder", folder?.id ?? "(all)");
6949
+ const result = await app.exportNotes(format, {
6950
+ folderId: folder?.id,
6951
+ scopedOutput: typeof commandFlags.output !== "string"
6952
+ });
6953
+ console.log(`✓ Exported ${result.documentCount} notes from ${renderExportScopeLabel(result.scope)} to ${result.outputDir} (job ${result.job.id})`);
6843
6954
  debug(config.debug, "notes written", result.written);
6844
6955
  return 0;
6845
6956
  }
@@ -6992,6 +7103,7 @@ Usage:
6992
7103
 
6993
7104
  Options:
6994
7105
  --cache <path> Path to Granola cache JSON
7106
+ --folder <query> Export only meetings inside one folder id or name
6995
7107
  --format <value> Output format: text, json, yaml, raw (default: text)
6996
7108
  --output <path> Output directory for transcript files (default: ./transcripts)
6997
7109
  --debug Enable debug logging
@@ -7003,6 +7115,7 @@ const transcriptsCommand = {
7003
7115
  description: "Export Granola transcripts",
7004
7116
  flags: {
7005
7117
  cache: { type: "string" },
7118
+ folder: { type: "string" },
7006
7119
  format: { type: "string" },
7007
7120
  help: { type: "boolean" },
7008
7121
  output: { type: "string" }
@@ -7021,8 +7134,14 @@ const transcriptsCommand = {
7021
7134
  debug(config.debug, "format", format);
7022
7135
  const app = await createGranolaApp(config);
7023
7136
  debug(config.debug, "authMode", app.getState().auth.mode);
7024
- const result = await app.exportTranscripts(format);
7025
- console.log(`✓ Exported ${result.transcriptCount} transcripts to ${result.outputDir} (job ${result.job.id})`);
7137
+ const folderQuery = typeof commandFlags.folder === "string" ? commandFlags.folder : void 0;
7138
+ const folder = folderQuery ? await app.findFolder(folderQuery) : void 0;
7139
+ debug(config.debug, "folder", folder?.id ?? "(all)");
7140
+ const result = await app.exportTranscripts(format, {
7141
+ folderId: folder?.id,
7142
+ scopedOutput: typeof commandFlags.output !== "string"
7143
+ });
7144
+ console.log(`✓ Exported ${result.transcriptCount} transcripts from ${renderExportScopeLabel(result.scope)} to ${result.outputDir} (job ${result.job.id})`);
7026
7145
  debug(config.debug, "transcripts written", result.written);
7027
7146
  return 0;
7028
7147
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granola-toolkit",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Toolkit for exporting and working with Granola meetings, notes, and transcripts",
5
5
  "keywords": [
6
6
  "cli",