granola-toolkit 0.28.0 → 0.29.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 +2 -0
  2. package/dist/cli.js +229 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -280,6 +280,7 @@ The initial terminal workspace includes:
280
280
 
281
281
  - a meeting list pane with keyboard navigation
282
282
  - a detail pane with notes, transcript, metadata, and raw views
283
+ - an auth session overlay for import, refresh, source switching, and sign-out
283
284
  - a footer with app state and key hints
284
285
  - a quick-open overlay for jumping by title, id, or tag
285
286
 
@@ -287,6 +288,7 @@ The main keyboard controls are:
287
288
 
288
289
  - `j` / `k` or arrow keys to move between meetings
289
290
  - `/` or `Ctrl+P` to open quick open
291
+ - `a` to open auth session actions
290
292
  - `1`-`4` to switch detail tabs
291
293
  - `PageUp` / `PageDown` to scroll the detail pane
292
294
  - `r` to refresh from live Granola data
package/dist/cli.js CHANGED
@@ -1305,6 +1305,151 @@ const granolaTuiTheme = {
1305
1305
  }
1306
1306
  };
1307
1307
  //#endregion
1308
+ //#region src/tui/auth.ts
1309
+ function padLine$2(text, width) {
1310
+ const clipped = truncateToWidth(text, width, "");
1311
+ return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
1312
+ }
1313
+ function frameLine$1(text, width) {
1314
+ return `| ${padLine$2(text, Math.max(1, width - 4))} |`;
1315
+ }
1316
+ function actionDisabledReason(auth, actionId) {
1317
+ switch (actionId) {
1318
+ case "login": return auth.supabaseAvailable ? "" : "supabase.json unavailable";
1319
+ case "refresh":
1320
+ if (!auth.storedSessionAvailable) return "stored session missing";
1321
+ return auth.refreshAvailable ? "" : "refresh unavailable";
1322
+ case "use-stored":
1323
+ if (!auth.storedSessionAvailable) return "stored session missing";
1324
+ return auth.mode === "stored-session" ? "already active" : "";
1325
+ case "use-supabase":
1326
+ if (!auth.supabaseAvailable) return "supabase.json unavailable";
1327
+ return auth.mode === "supabase-file" ? "already active" : "";
1328
+ case "logout": return auth.storedSessionAvailable ? "" : "stored session missing";
1329
+ }
1330
+ }
1331
+ function buildGranolaTuiAuthActions(auth) {
1332
+ return [
1333
+ {
1334
+ description: "Import the Granola desktop session from supabase.json",
1335
+ id: "login",
1336
+ key: "1",
1337
+ label: "Import desktop session"
1338
+ },
1339
+ {
1340
+ description: "Refresh the stored Granola session",
1341
+ id: "refresh",
1342
+ key: "2",
1343
+ label: "Refresh stored session"
1344
+ },
1345
+ {
1346
+ description: "Switch the active auth source to the stored session",
1347
+ id: "use-stored",
1348
+ key: "3",
1349
+ label: "Use stored session"
1350
+ },
1351
+ {
1352
+ description: "Switch the active auth source to supabase.json",
1353
+ id: "use-supabase",
1354
+ key: "4",
1355
+ label: "Use supabase.json"
1356
+ },
1357
+ {
1358
+ description: "Delete the stored session and fall back to supabase.json",
1359
+ id: "logout",
1360
+ key: "5",
1361
+ label: "Sign out"
1362
+ }
1363
+ ].map((action) => {
1364
+ const disabledReason = actionDisabledReason(auth, action.id);
1365
+ return {
1366
+ ...action,
1367
+ disabled: disabledReason.length > 0,
1368
+ disabledReason: disabledReason || void 0
1369
+ };
1370
+ });
1371
+ }
1372
+ function renderGranolaTuiAuthState(auth) {
1373
+ const lines = [
1374
+ `Active source: ${auth.mode === "stored-session" ? "Stored session" : "supabase.json"}`,
1375
+ `Stored session: ${auth.storedSessionAvailable ? "available" : "missing"}`,
1376
+ `supabase.json: ${auth.supabaseAvailable ? "available" : "missing"}`,
1377
+ `Refresh: ${auth.refreshAvailable ? "available" : "missing"}`
1378
+ ];
1379
+ if (auth.clientId) lines.push(`Client ID: ${auth.clientId}`);
1380
+ if (auth.signInMethod) lines.push(`Sign-in method: ${auth.signInMethod}`);
1381
+ if (auth.supabasePath) lines.push(`supabase path: ${auth.supabasePath}`);
1382
+ if (auth.lastError) lines.push(`Last error: ${auth.lastError}`);
1383
+ return lines.join("\n");
1384
+ }
1385
+ function nextEnabledIndex(actions, startIndex, delta) {
1386
+ if (actions.length === 0) return -1;
1387
+ for (let attempts = 0; attempts < actions.length; attempts += 1) {
1388
+ const nextIndex = (startIndex + delta * (attempts + 1) + actions.length) % actions.length;
1389
+ if (!actions[nextIndex]?.disabled) return nextIndex;
1390
+ }
1391
+ return Math.max(0, Math.min(actions.length - 1, startIndex));
1392
+ }
1393
+ var GranolaTuiAuthOverlay = class {
1394
+ focused = false;
1395
+ #actions;
1396
+ #selectedIndex;
1397
+ constructor(options) {
1398
+ this.options = options;
1399
+ this.#actions = buildGranolaTuiAuthActions(this.options.auth);
1400
+ const firstEnabledIndex = this.#actions.findIndex((action) => !action.disabled);
1401
+ this.#selectedIndex = firstEnabledIndex >= 0 ? firstEnabledIndex : 0;
1402
+ }
1403
+ invalidate() {}
1404
+ async runAction(actionId) {
1405
+ const action = this.#actions.find((candidate) => candidate.id === actionId);
1406
+ if (!action || action.disabled) return;
1407
+ await this.options.onRun(action.id);
1408
+ }
1409
+ handleInput(data) {
1410
+ if (matchesKey(data, "esc")) {
1411
+ this.options.onCancel();
1412
+ return;
1413
+ }
1414
+ if (matchesKey(data, "up")) {
1415
+ this.#selectedIndex = nextEnabledIndex(this.#actions, this.#selectedIndex, -1);
1416
+ return;
1417
+ }
1418
+ if (matchesKey(data, "down")) {
1419
+ this.#selectedIndex = nextEnabledIndex(this.#actions, this.#selectedIndex, 1);
1420
+ return;
1421
+ }
1422
+ const selected = this.#actions[this.#selectedIndex];
1423
+ if (matchesKey(data, "enter")) {
1424
+ if (selected && !selected.disabled) this.runAction(selected.id);
1425
+ return;
1426
+ }
1427
+ const hotkeyAction = this.#actions.find((action) => action.key === data);
1428
+ if (hotkeyAction && !hotkeyAction.disabled) this.runAction(hotkeyAction.id);
1429
+ }
1430
+ render(width) {
1431
+ const lines = [];
1432
+ const bodyWidth = Math.max(48, width);
1433
+ lines.push(`+${"-".repeat(bodyWidth - 2)}+`);
1434
+ lines.push(frameLine$1(granolaTuiTheme.strong("Auth Session"), bodyWidth));
1435
+ lines.push(frameLine$1("", bodyWidth));
1436
+ for (const detailLine of renderGranolaTuiAuthState(this.options.auth).split("\n")) lines.push(frameLine$1(detailLine, bodyWidth));
1437
+ lines.push(frameLine$1("", bodyWidth));
1438
+ for (const action of this.#actions) {
1439
+ const selected = this.#actions[this.#selectedIndex]?.id === action.id;
1440
+ const label = `${action.key}. ${action.label}`;
1441
+ const titleLine = action.disabled ? granolaTuiTheme.dim(label) : selected ? granolaTuiTheme.selected(label) : label;
1442
+ lines.push(frameLine$1(titleLine, bodyWidth));
1443
+ const detail = action.disabled ? granolaTuiTheme.warning(action.disabledReason ?? "Unavailable") : granolaTuiTheme.dim(action.description);
1444
+ for (const wrapped of wrapTextWithAnsi(detail, Math.max(1, bodyWidth - 6))) lines.push(frameLine$1(` ${wrapped}`, bodyWidth));
1445
+ }
1446
+ lines.push(frameLine$1("", bodyWidth));
1447
+ lines.push(frameLine$1(granolaTuiTheme.dim("Enter to run, Esc to cancel, arrows to move"), bodyWidth));
1448
+ lines.push(`+${"-".repeat(bodyWidth - 2)}+`);
1449
+ return lines;
1450
+ }
1451
+ };
1452
+ //#endregion
1308
1453
  //#region src/tui/palette.ts
1309
1454
  function padLine$1(text, width) {
1310
1455
  const clipped = truncateToWidth(text, width, "");
@@ -1597,6 +1742,85 @@ var GranolaTuiWorkspace = class {
1597
1742
  this.#detailScroll = 0;
1598
1743
  this.tui.requestRender();
1599
1744
  }
1745
+ async reloadAfterAuthChange() {
1746
+ const preferredMeetingId = this.#selectedMeeting?.document.id ?? this.#selectedMeetingId;
1747
+ try {
1748
+ await this.loadMeetings({
1749
+ forceRefresh: true,
1750
+ preferredMeetingId,
1751
+ setStatus: false
1752
+ });
1753
+ if (this.#selectedMeetingId) {
1754
+ await this.loadMeeting(this.#selectedMeetingId, { ensureMeetingVisible: true });
1755
+ return;
1756
+ }
1757
+ this.#selectedMeeting = void 0;
1758
+ this.#detailError = "";
1759
+ this.#detailScroll = 0;
1760
+ this.tui.requestRender();
1761
+ } catch {}
1762
+ }
1763
+ async runAuthAction(actionId) {
1764
+ let successMessage = "";
1765
+ try {
1766
+ switch (actionId) {
1767
+ case "login":
1768
+ this.setStatus("Importing desktop session…");
1769
+ await this.app.loginAuth();
1770
+ successMessage = "Stored session imported";
1771
+ break;
1772
+ case "refresh":
1773
+ this.setStatus("Refreshing stored session…");
1774
+ await this.app.refreshAuth();
1775
+ successMessage = "Stored session refreshed";
1776
+ break;
1777
+ case "use-stored":
1778
+ this.setStatus("Switching to stored session…");
1779
+ await this.app.switchAuthMode("stored-session");
1780
+ successMessage = "Using stored session";
1781
+ break;
1782
+ case "use-supabase":
1783
+ this.setStatus("Switching to supabase.json…");
1784
+ await this.app.switchAuthMode("supabase-file");
1785
+ successMessage = "Using supabase.json";
1786
+ break;
1787
+ case "logout":
1788
+ this.setStatus("Signing out…");
1789
+ await this.app.logoutAuth();
1790
+ successMessage = "Stored session removed";
1791
+ break;
1792
+ }
1793
+ await this.reloadAfterAuthChange();
1794
+ this.setStatus(successMessage);
1795
+ } catch (error) {
1796
+ const message = error instanceof Error ? error.message : String(error);
1797
+ this.setStatus(message, "error");
1798
+ }
1799
+ }
1800
+ openAuthPanel(auth = this.#appState.auth) {
1801
+ if (this.#overlay) return;
1802
+ const closeOverlay = () => {
1803
+ this.#overlay?.hide();
1804
+ this.#overlay = void 0;
1805
+ this.tui.setFocus(this);
1806
+ this.tui.requestRender();
1807
+ };
1808
+ const overlay = new GranolaTuiAuthOverlay({
1809
+ auth,
1810
+ onCancel: closeOverlay,
1811
+ onRun: async (actionId) => {
1812
+ closeOverlay();
1813
+ await this.runAuthAction(actionId);
1814
+ }
1815
+ });
1816
+ this.#overlay = this.tui.showOverlay(overlay, {
1817
+ anchor: "center",
1818
+ maxHeight: "70%",
1819
+ minWidth: 52,
1820
+ width: "72%"
1821
+ });
1822
+ this.setStatus("Auth session");
1823
+ }
1600
1824
  openQuickOpen() {
1601
1825
  if (this.#overlay) return;
1602
1826
  const closeOverlay = () => {
@@ -1641,6 +1865,10 @@ var GranolaTuiWorkspace = class {
1641
1865
  this.openQuickOpen();
1642
1866
  return;
1643
1867
  }
1868
+ if (matchesKey(data, "a")) {
1869
+ this.openAuthPanel();
1870
+ return;
1871
+ }
1644
1872
  if (matchesKey(data, "up") || matchesKey(data, "k")) {
1645
1873
  this.moveSelection(-1);
1646
1874
  return;
@@ -1779,7 +2007,7 @@ var GranolaTuiWorkspace = class {
1779
2007
  const bodyLines = [];
1780
2008
  for (let index = 0; index < bodyHeight; index += 1) bodyLines.push(`${padLine(listLines[index] ?? "", listWidth)} | ${padLine(detailLines[index] ?? "", detailWidth)}`);
1781
2009
  const footerStatus = padLine(toneText(this.#statusTone, this.#statusMessage), width);
1782
- const footerHints = padLine(granolaTuiTheme.dim("/ quick open r refresh 1-4 tabs PgUp/PgDn scroll q quit"), width);
2010
+ const footerHints = padLine(granolaTuiTheme.dim("/ quick open a auth r refresh 1-4 tabs PgUp/PgDn scroll q quit"), width);
1783
2011
  return [
1784
2012
  headerTitle,
1785
2013
  headerSummary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granola-toolkit",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Toolkit for exporting and working with Granola meetings, notes, and transcripts",
5
5
  "keywords": [
6
6
  "cli",