granola-toolkit 0.34.4 → 0.34.6
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 +9 -9
- package/dist/cli.js +140 -144
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,8 +30,8 @@ granola tui
|
|
|
30
30
|
|
|
31
31
|
## Documentation
|
|
32
32
|
|
|
33
|
-
The detailed documentation now lives
|
|
34
|
-
[`
|
|
33
|
+
The detailed documentation now lives at
|
|
34
|
+
[`kkarimi.github.io/granola-toolkit`](https://kkarimi.github.io/granola-toolkit/).
|
|
35
35
|
|
|
36
36
|
Local docs development:
|
|
37
37
|
|
|
@@ -42,13 +42,13 @@ npm run docs:check
|
|
|
42
42
|
|
|
43
43
|
Key docs entry points:
|
|
44
44
|
|
|
45
|
-
- [`Overview`](https://github.
|
|
46
|
-
- [`Getting Started`](https://github.
|
|
47
|
-
- [`Exporting`](https://github.
|
|
48
|
-
- [`Meetings and Folders`](https://github.
|
|
49
|
-
- [`Server, Web, and TUI`](https://github.
|
|
50
|
-
- [`Auth and Configuration`](https://github.
|
|
51
|
-
- [`Development`](https://github.
|
|
45
|
+
- [`Overview`](https://kkarimi.github.io/granola-toolkit/docs/)
|
|
46
|
+
- [`Getting Started`](https://kkarimi.github.io/granola-toolkit/docs/getting-started/)
|
|
47
|
+
- [`Exporting`](https://kkarimi.github.io/granola-toolkit/docs/exporting/)
|
|
48
|
+
- [`Meetings and Folders`](https://kkarimi.github.io/granola-toolkit/docs/meetings-and-folders/)
|
|
49
|
+
- [`Server, Web, and TUI`](https://kkarimi.github.io/granola-toolkit/docs/server-web-and-tui/)
|
|
50
|
+
- [`Auth and Configuration`](https://kkarimi.github.io/granola-toolkit/docs/auth-and-configuration/)
|
|
51
|
+
- [`Development`](https://kkarimi.github.io/granola-toolkit/docs/development/)
|
|
52
52
|
|
|
53
53
|
## Local Development
|
|
54
54
|
|
package/dist/cli.js
CHANGED
|
@@ -4414,10 +4414,115 @@ async function openExternalUrl(url, options = {}) {
|
|
|
4414
4414
|
}))(command.file, command.args);
|
|
4415
4415
|
}
|
|
4416
4416
|
//#endregion
|
|
4417
|
+
//#region src/web/client-state.ts
|
|
4418
|
+
function parseWorkspaceTab(value) {
|
|
4419
|
+
switch (value) {
|
|
4420
|
+
case "metadata":
|
|
4421
|
+
case "raw":
|
|
4422
|
+
case "transcript": return value;
|
|
4423
|
+
default: return "notes";
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
function startupSelectionFromSearch(search) {
|
|
4427
|
+
const params = new URLSearchParams(search);
|
|
4428
|
+
return {
|
|
4429
|
+
folderId: params.get("folder")?.trim() || "",
|
|
4430
|
+
meetingId: params.get("meeting")?.trim() || "",
|
|
4431
|
+
workspaceTab: parseWorkspaceTab(params.get("tab"))
|
|
4432
|
+
};
|
|
4433
|
+
}
|
|
4434
|
+
function buildBrowserUrlPath(currentHref, selection) {
|
|
4435
|
+
const url = new URL(currentHref);
|
|
4436
|
+
if (selection.selectedFolderId) url.searchParams.set("folder", selection.selectedFolderId);
|
|
4437
|
+
else url.searchParams.delete("folder");
|
|
4438
|
+
if (selection.selectedMeetingId) url.searchParams.set("meeting", selection.selectedMeetingId);
|
|
4439
|
+
else url.searchParams.delete("meeting");
|
|
4440
|
+
if (parseWorkspaceTab(selection.workspaceTab) !== "notes") url.searchParams.set("tab", parseWorkspaceTab(selection.workspaceTab));
|
|
4441
|
+
else url.searchParams.delete("tab");
|
|
4442
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
4443
|
+
}
|
|
4444
|
+
function exportScopeLabel(scope) {
|
|
4445
|
+
return scope && scope.mode === "folder" ? `Folder: ${scope.folderName || scope.folderId}` : "Scope: All meetings";
|
|
4446
|
+
}
|
|
4447
|
+
function currentFilterSummary(filters) {
|
|
4448
|
+
const parts = [];
|
|
4449
|
+
if (filters.selectedFolderId) {
|
|
4450
|
+
const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);
|
|
4451
|
+
parts.push(`folder "${folder ? folder.name : filters.selectedFolderId}"`);
|
|
4452
|
+
}
|
|
4453
|
+
if (filters.search) parts.push(`search "${filters.search}"`);
|
|
4454
|
+
if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);
|
|
4455
|
+
if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);
|
|
4456
|
+
return parts.join(", ");
|
|
4457
|
+
}
|
|
4458
|
+
function selectMeetingId(meetings, selectedMeetingId) {
|
|
4459
|
+
if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;
|
|
4460
|
+
return meetings[0]?.id ?? null;
|
|
4461
|
+
}
|
|
4462
|
+
function buildMeetingsQuery(filters, options = {}) {
|
|
4463
|
+
const params = new URLSearchParams();
|
|
4464
|
+
params.set("limit", String(options.limit ?? 100));
|
|
4465
|
+
params.set("sort", filters.sort || "updated-desc");
|
|
4466
|
+
if (filters.search) params.set("search", filters.search);
|
|
4467
|
+
if (filters.updatedFrom) params.set("updatedFrom", filters.updatedFrom);
|
|
4468
|
+
if (filters.updatedTo) params.set("updatedTo", filters.updatedTo);
|
|
4469
|
+
if (filters.selectedFolderId) params.set("folderId", filters.selectedFolderId);
|
|
4470
|
+
if (options.refresh) params.set("refresh", "true");
|
|
4471
|
+
return `?${params.toString()}`;
|
|
4472
|
+
}
|
|
4473
|
+
function buildNotesExportRequest(selectedFolderId) {
|
|
4474
|
+
return {
|
|
4475
|
+
folderId: selectedFolderId || void 0,
|
|
4476
|
+
format: "markdown"
|
|
4477
|
+
};
|
|
4478
|
+
}
|
|
4479
|
+
function buildTranscriptsExportRequest(selectedFolderId) {
|
|
4480
|
+
return {
|
|
4481
|
+
folderId: selectedFolderId || void 0,
|
|
4482
|
+
format: "text"
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
function nextWorkspaceTab(currentTab, key) {
|
|
4486
|
+
const current = parseWorkspaceTab(currentTab);
|
|
4487
|
+
switch (key) {
|
|
4488
|
+
case "1": return "notes";
|
|
4489
|
+
case "2": return "transcript";
|
|
4490
|
+
case "3": return "metadata";
|
|
4491
|
+
case "4": return "raw";
|
|
4492
|
+
case "]":
|
|
4493
|
+
switch (current) {
|
|
4494
|
+
case "notes": return "transcript";
|
|
4495
|
+
case "transcript": return "metadata";
|
|
4496
|
+
case "metadata": return "raw";
|
|
4497
|
+
case "raw": return "notes";
|
|
4498
|
+
}
|
|
4499
|
+
break;
|
|
4500
|
+
case "[":
|
|
4501
|
+
switch (current) {
|
|
4502
|
+
case "notes": return "raw";
|
|
4503
|
+
case "transcript": return "notes";
|
|
4504
|
+
case "metadata": return "transcript";
|
|
4505
|
+
case "raw": return "metadata";
|
|
4506
|
+
}
|
|
4507
|
+
break;
|
|
4508
|
+
default: return;
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
//#endregion
|
|
4417
4512
|
//#region src/web/client-script.ts
|
|
4418
4513
|
const granolaWebClientScript = String.raw`
|
|
4419
4514
|
const serverConfig = window.__GRANOLA_SERVER__ || { passwordRequired: false };
|
|
4420
4515
|
const workspaceTabs = ["notes", "transcript", "metadata", "raw"];
|
|
4516
|
+
${parseWorkspaceTab.toString()}
|
|
4517
|
+
${startupSelectionFromSearch.toString()}
|
|
4518
|
+
${buildBrowserUrlPath.toString()}
|
|
4519
|
+
${exportScopeLabel.toString()}
|
|
4520
|
+
${currentFilterSummary.toString()}
|
|
4521
|
+
${selectMeetingId.toString()}
|
|
4522
|
+
${buildMeetingsQuery.toString()}
|
|
4523
|
+
${buildNotesExportRequest.toString()}
|
|
4524
|
+
${buildTranscriptsExportRequest.toString()}
|
|
4525
|
+
${nextWorkspaceTab.toString()}
|
|
4421
4526
|
|
|
4422
4527
|
const state = {
|
|
4423
4528
|
appState: null,
|
|
@@ -4466,41 +4571,12 @@ const els = {
|
|
|
4466
4571
|
workspaceTabs: document.querySelectorAll("[data-workspace-tab]"),
|
|
4467
4572
|
};
|
|
4468
4573
|
|
|
4469
|
-
function parseWorkspaceTab(value) {
|
|
4470
|
-
return workspaceTabs.includes(value) ? value : "notes";
|
|
4471
|
-
}
|
|
4472
|
-
|
|
4473
|
-
function startupSelection() {
|
|
4474
|
-
const params = new URLSearchParams(window.location.search);
|
|
4475
|
-
return {
|
|
4476
|
-
folderId: params.get("folder")?.trim() || "",
|
|
4477
|
-
meetingId: params.get("meeting")?.trim() || "",
|
|
4478
|
-
workspaceTab: parseWorkspaceTab(params.get("tab")),
|
|
4479
|
-
};
|
|
4480
|
-
}
|
|
4481
|
-
|
|
4482
4574
|
function syncBrowserUrl() {
|
|
4483
|
-
const
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
}
|
|
4488
|
-
url.searchParams.delete("folder");
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
|
-
if (state.selectedMeetingId) {
|
|
4492
|
-
url.searchParams.set("meeting", state.selectedMeetingId);
|
|
4493
|
-
} else {
|
|
4494
|
-
url.searchParams.delete("meeting");
|
|
4495
|
-
}
|
|
4496
|
-
|
|
4497
|
-
if (state.workspaceTab !== "notes") {
|
|
4498
|
-
url.searchParams.set("tab", state.workspaceTab);
|
|
4499
|
-
} else {
|
|
4500
|
-
url.searchParams.delete("tab");
|
|
4501
|
-
}
|
|
4502
|
-
|
|
4503
|
-
const nextPath = url.pathname + url.search + url.hash;
|
|
4575
|
+
const nextPath = buildBrowserUrlPath(window.location.href, {
|
|
4576
|
+
selectedFolderId: state.selectedFolderId,
|
|
4577
|
+
selectedMeetingId: state.selectedMeetingId,
|
|
4578
|
+
workspaceTab: state.workspaceTab,
|
|
4579
|
+
});
|
|
4504
4580
|
const currentPath = window.location.pathname + window.location.search + window.location.hash;
|
|
4505
4581
|
if (nextPath !== currentPath) {
|
|
4506
4582
|
history.replaceState(null, "", nextPath);
|
|
@@ -4515,12 +4591,6 @@ function escapeHtml(value) {
|
|
|
4515
4591
|
.replaceAll('"', """);
|
|
4516
4592
|
}
|
|
4517
4593
|
|
|
4518
|
-
function exportScopeLabel(scope) {
|
|
4519
|
-
return scope && scope.mode === "folder"
|
|
4520
|
-
? "Folder: " + (scope.folderName || scope.folderId)
|
|
4521
|
-
: "Scope: All meetings";
|
|
4522
|
-
}
|
|
4523
|
-
|
|
4524
4594
|
function setStatus(label, tone = "idle") {
|
|
4525
4595
|
els.stateBadge.textContent = label;
|
|
4526
4596
|
els.stateBadge.dataset.tone = tone;
|
|
@@ -4534,29 +4604,6 @@ function syncFilterInputs() {
|
|
|
4534
4604
|
els.updatedTo.value = state.updatedTo;
|
|
4535
4605
|
}
|
|
4536
4606
|
|
|
4537
|
-
function currentFilterSummary() {
|
|
4538
|
-
const parts = [];
|
|
4539
|
-
|
|
4540
|
-
if (state.selectedFolderId) {
|
|
4541
|
-
const folder = state.folders.find((candidate) => candidate.id === state.selectedFolderId);
|
|
4542
|
-
parts.push("folder " + (folder ? '"' + folder.name + '"' : '"' + state.selectedFolderId + '"'));
|
|
4543
|
-
}
|
|
4544
|
-
|
|
4545
|
-
if (state.search) {
|
|
4546
|
-
parts.push('search "' + state.search + '"');
|
|
4547
|
-
}
|
|
4548
|
-
|
|
4549
|
-
if (state.updatedFrom) {
|
|
4550
|
-
parts.push("from " + state.updatedFrom);
|
|
4551
|
-
}
|
|
4552
|
-
|
|
4553
|
-
if (state.updatedTo) {
|
|
4554
|
-
parts.push("to " + state.updatedTo);
|
|
4555
|
-
}
|
|
4556
|
-
|
|
4557
|
-
return parts.join(", ");
|
|
4558
|
-
}
|
|
4559
|
-
|
|
4560
4607
|
function renderWorkspaceTabs() {
|
|
4561
4608
|
for (const button of els.workspaceTabs) {
|
|
4562
4609
|
button.dataset.selected = button.dataset.workspaceTab === state.workspaceTab ? "true" : "false";
|
|
@@ -4730,7 +4777,13 @@ function renderMeetingList() {
|
|
|
4730
4777
|
state.selectedMeeting = null;
|
|
4731
4778
|
state.selectedMeetingBundle = null;
|
|
4732
4779
|
syncBrowserUrl();
|
|
4733
|
-
const filterSummary = currentFilterSummary(
|
|
4780
|
+
const filterSummary = currentFilterSummary({
|
|
4781
|
+
folders: state.folders,
|
|
4782
|
+
search: state.search,
|
|
4783
|
+
selectedFolderId: state.selectedFolderId,
|
|
4784
|
+
updatedFrom: state.updatedFrom,
|
|
4785
|
+
updatedTo: state.updatedTo,
|
|
4786
|
+
});
|
|
4734
4787
|
const message = filterSummary
|
|
4735
4788
|
? "No meetings match " + filterSummary + "."
|
|
4736
4789
|
: "No meetings yet. Try Refresh.";
|
|
@@ -4739,10 +4792,7 @@ function renderMeetingList() {
|
|
|
4739
4792
|
return;
|
|
4740
4793
|
}
|
|
4741
4794
|
|
|
4742
|
-
|
|
4743
|
-
if (!state.selectedMeetingId || !visibleIds.has(state.selectedMeetingId)) {
|
|
4744
|
-
state.selectedMeetingId = state.meetings[0]?.id || null;
|
|
4745
|
-
}
|
|
4795
|
+
state.selectedMeetingId = selectMeetingId(state.meetings, state.selectedMeetingId);
|
|
4746
4796
|
syncBrowserUrl();
|
|
4747
4797
|
|
|
4748
4798
|
els.list.innerHTML = state.meetings
|
|
@@ -4886,34 +4936,6 @@ async function fetchJson(path, init) {
|
|
|
4886
4936
|
return payload;
|
|
4887
4937
|
}
|
|
4888
4938
|
|
|
4889
|
-
function buildMeetingsQuery(limit = 100, refresh = false) {
|
|
4890
|
-
const params = new URLSearchParams();
|
|
4891
|
-
params.set("limit", String(limit));
|
|
4892
|
-
params.set("sort", state.sort);
|
|
4893
|
-
|
|
4894
|
-
if (state.search) {
|
|
4895
|
-
params.set("search", state.search);
|
|
4896
|
-
}
|
|
4897
|
-
|
|
4898
|
-
if (state.updatedFrom) {
|
|
4899
|
-
params.set("updatedFrom", state.updatedFrom);
|
|
4900
|
-
}
|
|
4901
|
-
|
|
4902
|
-
if (state.updatedTo) {
|
|
4903
|
-
params.set("updatedTo", state.updatedTo);
|
|
4904
|
-
}
|
|
4905
|
-
|
|
4906
|
-
if (state.selectedFolderId) {
|
|
4907
|
-
params.set("folderId", state.selectedFolderId);
|
|
4908
|
-
}
|
|
4909
|
-
|
|
4910
|
-
if (refresh) {
|
|
4911
|
-
params.set("refresh", "true");
|
|
4912
|
-
}
|
|
4913
|
-
|
|
4914
|
-
return "?" + params.toString();
|
|
4915
|
-
}
|
|
4916
|
-
|
|
4917
4939
|
async function loadFolders(options = {}) {
|
|
4918
4940
|
const refresh = options.refresh === true;
|
|
4919
4941
|
|
|
@@ -4953,7 +4975,22 @@ async function loadMeetings(options = {}) {
|
|
|
4953
4975
|
|
|
4954
4976
|
try {
|
|
4955
4977
|
state.listError = "";
|
|
4956
|
-
const payload = await fetchJson(
|
|
4978
|
+
const payload = await fetchJson(
|
|
4979
|
+
"/meetings" +
|
|
4980
|
+
buildMeetingsQuery(
|
|
4981
|
+
{
|
|
4982
|
+
search: state.search,
|
|
4983
|
+
selectedFolderId: state.selectedFolderId,
|
|
4984
|
+
sort: state.sort,
|
|
4985
|
+
updatedFrom: state.updatedFrom,
|
|
4986
|
+
updatedTo: state.updatedTo,
|
|
4987
|
+
},
|
|
4988
|
+
{
|
|
4989
|
+
limit: 100,
|
|
4990
|
+
refresh,
|
|
4991
|
+
},
|
|
4992
|
+
),
|
|
4993
|
+
);
|
|
4957
4994
|
state.meetings = payload.meetings || [];
|
|
4958
4995
|
state.meetingSource = payload.source || "live";
|
|
4959
4996
|
|
|
@@ -5063,10 +5100,7 @@ async function syncAuthState() {
|
|
|
5063
5100
|
async function exportNotes() {
|
|
5064
5101
|
setStatus(state.selectedFolderId ? "Exporting folder notes…" : "Exporting notes…", "busy");
|
|
5065
5102
|
await fetchJson("/exports/notes", {
|
|
5066
|
-
body: JSON.stringify(
|
|
5067
|
-
folderId: state.selectedFolderId || undefined,
|
|
5068
|
-
format: "markdown",
|
|
5069
|
-
}),
|
|
5103
|
+
body: JSON.stringify(buildNotesExportRequest(state.selectedFolderId)),
|
|
5070
5104
|
headers: { "content-type": "application/json" },
|
|
5071
5105
|
method: "POST",
|
|
5072
5106
|
});
|
|
@@ -5079,10 +5113,7 @@ async function exportTranscripts() {
|
|
|
5079
5113
|
"busy",
|
|
5080
5114
|
);
|
|
5081
5115
|
await fetchJson("/exports/transcripts", {
|
|
5082
|
-
body: JSON.stringify(
|
|
5083
|
-
folderId: state.selectedFolderId || undefined,
|
|
5084
|
-
format: "text",
|
|
5085
|
-
}),
|
|
5116
|
+
body: JSON.stringify(buildTranscriptsExportRequest(state.selectedFolderId)),
|
|
5086
5117
|
headers: { "content-type": "application/json" },
|
|
5087
5118
|
method: "POST",
|
|
5088
5119
|
});
|
|
@@ -5386,50 +5417,15 @@ document.addEventListener("keydown", (event) => {
|
|
|
5386
5417
|
return;
|
|
5387
5418
|
}
|
|
5388
5419
|
|
|
5389
|
-
const
|
|
5390
|
-
if (
|
|
5391
|
-
state.workspaceTab =
|
|
5392
|
-
syncBrowserUrl();
|
|
5393
|
-
renderMeetingDetail();
|
|
5394
|
-
return;
|
|
5395
|
-
}
|
|
5396
|
-
|
|
5397
|
-
if (event.key === "2") {
|
|
5398
|
-
state.workspaceTab = "transcript";
|
|
5399
|
-
syncBrowserUrl();
|
|
5400
|
-
renderMeetingDetail();
|
|
5401
|
-
return;
|
|
5402
|
-
}
|
|
5403
|
-
|
|
5404
|
-
if (event.key === "3") {
|
|
5405
|
-
state.workspaceTab = "metadata";
|
|
5406
|
-
syncBrowserUrl();
|
|
5407
|
-
renderMeetingDetail();
|
|
5408
|
-
return;
|
|
5409
|
-
}
|
|
5410
|
-
|
|
5411
|
-
if (event.key === "4") {
|
|
5412
|
-
state.workspaceTab = "raw";
|
|
5413
|
-
syncBrowserUrl();
|
|
5414
|
-
renderMeetingDetail();
|
|
5415
|
-
return;
|
|
5416
|
-
}
|
|
5417
|
-
|
|
5418
|
-
const currentIndex = tabs.indexOf(state.workspaceTab);
|
|
5419
|
-
if (event.key === "]") {
|
|
5420
|
-
state.workspaceTab = tabs[(currentIndex + 1) % tabs.length];
|
|
5421
|
-
syncBrowserUrl();
|
|
5422
|
-
renderMeetingDetail();
|
|
5423
|
-
}
|
|
5424
|
-
|
|
5425
|
-
if (event.key === "[") {
|
|
5426
|
-
state.workspaceTab = tabs[(currentIndex + tabs.length - 1) % tabs.length];
|
|
5420
|
+
const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);
|
|
5421
|
+
if (nextTab) {
|
|
5422
|
+
state.workspaceTab = nextTab;
|
|
5427
5423
|
syncBrowserUrl();
|
|
5428
5424
|
renderMeetingDetail();
|
|
5429
5425
|
}
|
|
5430
5426
|
});
|
|
5431
5427
|
|
|
5432
|
-
const initialSelection =
|
|
5428
|
+
const initialSelection = startupSelectionFromSearch(window.location.search);
|
|
5433
5429
|
state.selectedFolderId = initialSelection.folderId || null;
|
|
5434
5430
|
state.selectedMeetingId = initialSelection.meetingId || null;
|
|
5435
5431
|
state.workspaceTab = initialSelection.workspaceTab;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "granola-toolkit",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.6",
|
|
4
4
|
"description": "Toolkit for exporting and working with Granola meetings, notes, and transcripts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"notes",
|
|
9
9
|
"transcripts"
|
|
10
10
|
],
|
|
11
|
-
"homepage": "https://github.
|
|
11
|
+
"homepage": "https://kkarimi.github.io/granola-toolkit/",
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/kkarimi/granola-toolkit/issues"
|
|
14
14
|
},
|