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.
- package/README.md +2 -0
- package/dist/cli.js +229 -1
- 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,
|