granola-toolkit 0.35.0 → 0.36.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 +1 -0
- package/dist/cli.js +192 -21
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1378,7 +1378,7 @@ function renderGranolaTuiMeetingTab(bundle, tab) {
|
|
|
1378
1378
|
}
|
|
1379
1379
|
}
|
|
1380
1380
|
function buildGranolaTuiSummary(state, meetingSource) {
|
|
1381
|
-
return `auth ${state.auth.mode === "stored-session" ? "stored" : "supabase"} | ${state.documents.loaded ? `${state.documents.count} docs` : "docs pending"} | ${state.folders.loaded ? `${state.folders.count} folders` : "folders pending"} | ${state.cache.loaded ? `${state.cache.transcriptCount} transcript sets` : state.cache.configured ? "cache configured" : "cache missing"} | ${state.index.loaded ? `${state.index.meetingCount} indexed` : "index pending"} | list ${meetingSource}`;
|
|
1381
|
+
return `auth ${state.auth.mode === "stored-session" ? "stored" : "supabase"} | ${state.documents.loaded ? `${state.documents.count} docs` : "docs pending"} | ${state.folders.loaded ? `${state.folders.count} folders` : "folders pending"} | ${state.cache.loaded ? `${state.cache.transcriptCount} transcript sets` : state.cache.configured ? "cache configured" : "cache missing"} | ${state.index.loaded ? `${state.index.meetingCount} indexed` : "index pending"} | ${state.sync.running ? "sync running" : state.sync.lastError ? "sync error" : state.sync.lastCompletedAt ? `sync ${state.sync.lastCompletedAt.slice(11, 16)}` : "sync idle"} | list ${meetingSource}`;
|
|
1382
1382
|
}
|
|
1383
1383
|
//#endregion
|
|
1384
1384
|
//#region src/tui/theme.ts
|
|
@@ -1849,6 +1849,10 @@ var GranolaTuiWorkspace = class {
|
|
|
1849
1849
|
}
|
|
1850
1850
|
async refresh(forceRefresh) {
|
|
1851
1851
|
try {
|
|
1852
|
+
if (forceRefresh) {
|
|
1853
|
+
this.setStatus("Syncing…");
|
|
1854
|
+
await this.app.sync();
|
|
1855
|
+
}
|
|
1852
1856
|
await this.loadFolders({
|
|
1853
1857
|
forceRefresh,
|
|
1854
1858
|
setStatus: false
|
|
@@ -1858,7 +1862,9 @@ var GranolaTuiWorkspace = class {
|
|
|
1858
1862
|
preferredMeetingId: this.#selectedMeetingId
|
|
1859
1863
|
});
|
|
1860
1864
|
if (this.#selectedMeetingId) await this.loadMeeting(this.#selectedMeetingId, { ensureMeetingVisible: true });
|
|
1861
|
-
} catch {
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
if (error instanceof Error && error.message) this.setStatus(error.message, "error");
|
|
1867
|
+
}
|
|
1862
1868
|
}
|
|
1863
1869
|
async moveMeetingSelection(delta) {
|
|
1864
1870
|
if (this.#meetings.length === 0) return;
|
|
@@ -2245,7 +2251,7 @@ var GranolaTuiWorkspace = class {
|
|
|
2245
2251
|
const bodyLines = [];
|
|
2246
2252
|
for (let index = 0; index < bodyHeight; index += 1) bodyLines.push(`${padLine(listLines[index] ?? "", listWidth)} | ${padLine(detailLines[index] ?? "", detailWidth)}`);
|
|
2247
2253
|
const footerStatus = padLine(toneText(this.#statusTone, this.#statusMessage), width);
|
|
2248
|
-
const footerHints = padLine(granolaTuiTheme.dim("h/l or Tab pane j/k move / quick open a auth r
|
|
2254
|
+
const footerHints = padLine(granolaTuiTheme.dim("h/l or Tab pane j/k move / quick open a auth r sync 1-4 tabs PgUp/PgDn scroll q quit"), width);
|
|
2249
2255
|
return [
|
|
2250
2256
|
headerTitle,
|
|
2251
2257
|
headerSummary,
|
|
@@ -2263,7 +2269,7 @@ async function runGranolaTui(app, options = {}) {
|
|
|
2263
2269
|
onExit: () => {
|
|
2264
2270
|
workspace.dispose();
|
|
2265
2271
|
tui.stop();
|
|
2266
|
-
Promise.resolve(app.close?.()).catch(() => {}).finally(() => {
|
|
2272
|
+
Promise.resolve(options.onClose?.()).then(() => Promise.resolve(app.close?.())).catch(() => {}).finally(() => {
|
|
2267
2273
|
resolve(0);
|
|
2268
2274
|
});
|
|
2269
2275
|
}
|
|
@@ -2273,7 +2279,7 @@ async function runGranolaTui(app, options = {}) {
|
|
|
2273
2279
|
await workspace.initialise();
|
|
2274
2280
|
} catch (error) {
|
|
2275
2281
|
workspace.dispose();
|
|
2276
|
-
await Promise.resolve(app.close?.()).catch(() => {});
|
|
2282
|
+
await Promise.resolve(options.onClose?.()).then(() => Promise.resolve(app.close?.())).catch(() => {});
|
|
2277
2283
|
reject(error);
|
|
2278
2284
|
return;
|
|
2279
2285
|
}
|
|
@@ -3880,7 +3886,7 @@ var GranolaApp = class {
|
|
|
3880
3886
|
async sync(options = {}) {
|
|
3881
3887
|
return await this.runSync({
|
|
3882
3888
|
forceRefresh: options.forceRefresh,
|
|
3883
|
-
foreground: true
|
|
3889
|
+
foreground: options.foreground ?? true
|
|
3884
3890
|
});
|
|
3885
3891
|
}
|
|
3886
3892
|
async listDocuments(options = {}) {
|
|
@@ -4325,6 +4331,14 @@ function parseTrustedOrigins(value) {
|
|
|
4325
4331
|
if (typeof value !== "string" || !value.trim()) return [];
|
|
4326
4332
|
return value.split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
4327
4333
|
}
|
|
4334
|
+
function parseSyncInterval(value, fallbackMs = 6e4) {
|
|
4335
|
+
if (value === void 0) return fallbackMs;
|
|
4336
|
+
if (typeof value !== "string" || !value.trim()) throw new Error("invalid sync interval: expected a duration like 60s or 5m");
|
|
4337
|
+
return parseDuration(value);
|
|
4338
|
+
}
|
|
4339
|
+
function syncEnabled(commandFlags) {
|
|
4340
|
+
return commandFlags["no-sync"] !== true;
|
|
4341
|
+
}
|
|
4328
4342
|
async function waitForShutdown(close) {
|
|
4329
4343
|
await new Promise((resolve, reject) => {
|
|
4330
4344
|
let closing = false;
|
|
@@ -4903,12 +4917,20 @@ function renderAppState() {
|
|
|
4903
4917
|
const folderStatus = appState.folders.loaded
|
|
4904
4918
|
? appState.folders.count + " folders"
|
|
4905
4919
|
: "not loaded";
|
|
4920
|
+
const syncStatus = appState.sync.running
|
|
4921
|
+
? "running"
|
|
4922
|
+
: appState.sync.lastError
|
|
4923
|
+
? "error"
|
|
4924
|
+
: appState.sync.lastCompletedAt
|
|
4925
|
+
? "last " + appState.sync.lastCompletedAt.slice(11, 19)
|
|
4926
|
+
: "idle";
|
|
4906
4927
|
|
|
4907
4928
|
els.appState.innerHTML = [
|
|
4908
4929
|
'<div class="status-grid">',
|
|
4909
4930
|
'<div><span class="status-label">Surface</span><strong>' + escapeHtml(appState.ui.surface) + "</strong></div>",
|
|
4910
4931
|
'<div><span class="status-label">View</span><strong>' + escapeHtml(appState.ui.view) + "</strong></div>",
|
|
4911
4932
|
'<div><span class="status-label">Auth</span><strong>' + escapeHtml(authMode) + "</strong></div>",
|
|
4933
|
+
'<div><span class="status-label">Sync</span><strong>' + escapeHtml(syncStatus) + "</strong></div>",
|
|
4912
4934
|
'<div><span class="status-label">Documents</span><strong>' + escapeHtml(docs) + "</strong></div>",
|
|
4913
4935
|
'<div><span class="status-label">Folders</span><strong>' + escapeHtml(folderStatus) + "</strong></div>",
|
|
4914
4936
|
'<div><span class="status-label">Cache</span><strong>' + escapeHtml(cache) + "</strong></div>",
|
|
@@ -5055,7 +5077,7 @@ function renderMeetingList() {
|
|
|
5055
5077
|
});
|
|
5056
5078
|
const message = filterSummary
|
|
5057
5079
|
? "No meetings match " + filterSummary + "."
|
|
5058
|
-
: "No meetings yet. Try
|
|
5080
|
+
: "No meetings yet. Try Sync now.";
|
|
5059
5081
|
els.list.innerHTML = '<div class="meeting-empty">' + escapeHtml(message) + "</div>";
|
|
5060
5082
|
renderMeetingDetail();
|
|
5061
5083
|
return;
|
|
@@ -5334,8 +5356,16 @@ async function quickOpenMeeting() {
|
|
|
5334
5356
|
}
|
|
5335
5357
|
|
|
5336
5358
|
async function refreshAll(forceLiveMeetings = false) {
|
|
5337
|
-
setStatus("Refreshing…", "busy");
|
|
5359
|
+
setStatus(forceLiveMeetings ? "Syncing…" : "Refreshing…", "busy");
|
|
5338
5360
|
try {
|
|
5361
|
+
if (forceLiveMeetings) {
|
|
5362
|
+
await fetchJson("/sync", {
|
|
5363
|
+
body: JSON.stringify({ forceRefresh: true }),
|
|
5364
|
+
headers: { "content-type": "application/json" },
|
|
5365
|
+
method: "POST",
|
|
5366
|
+
});
|
|
5367
|
+
}
|
|
5368
|
+
|
|
5339
5369
|
await loadFolders({ refresh: forceLiveMeetings });
|
|
5340
5370
|
const [appState, authState] = await Promise.all([fetchJson("/state"), fetchJson("/auth/status")]);
|
|
5341
5371
|
await loadMeetings({ refresh: forceLiveMeetings });
|
|
@@ -5345,7 +5375,14 @@ async function refreshAll(forceLiveMeetings = false) {
|
|
|
5345
5375
|
auth: authState,
|
|
5346
5376
|
};
|
|
5347
5377
|
renderAppState();
|
|
5348
|
-
setStatus(
|
|
5378
|
+
setStatus(
|
|
5379
|
+
forceLiveMeetings
|
|
5380
|
+
? "Sync complete"
|
|
5381
|
+
: state.meetingSource === "index"
|
|
5382
|
+
? "Loaded from index"
|
|
5383
|
+
: "Connected",
|
|
5384
|
+
"ok",
|
|
5385
|
+
);
|
|
5349
5386
|
} catch (error) {
|
|
5350
5387
|
if (error.authRequired) {
|
|
5351
5388
|
setStatus("Server locked", "error");
|
|
@@ -5790,7 +5827,7 @@ const granolaWebMarkup = String.raw`
|
|
|
5790
5827
|
</section>
|
|
5791
5828
|
<section class="toolbar">
|
|
5792
5829
|
<div class="toolbar-actions">
|
|
5793
|
-
<button class="button button--primary" data-refresh>
|
|
5830
|
+
<button class="button button--primary" data-refresh>Sync now</button>
|
|
5794
5831
|
<button class="button button--secondary" data-export-notes>Export Notes</button>
|
|
5795
5832
|
<button class="button button--secondary" data-export-transcripts>Export Transcripts</button>
|
|
5796
5833
|
</div>
|
|
@@ -6664,7 +6701,10 @@ async function startGranolaServer(app, options = {}) {
|
|
|
6664
6701
|
}
|
|
6665
6702
|
if (method === "POST" && path === granolaTransportPaths.syncRun) {
|
|
6666
6703
|
const body = await readJsonBody(request);
|
|
6667
|
-
sendJson(response, await app.sync({
|
|
6704
|
+
sendJson(response, await app.sync({
|
|
6705
|
+
foreground: typeof body.foreground === "boolean" ? body.foreground : void 0,
|
|
6706
|
+
forceRefresh: typeof body.forceRefresh === "boolean" ? body.forceRefresh : void 0
|
|
6707
|
+
}), { headers: originHeaders });
|
|
6668
6708
|
return;
|
|
6669
6709
|
}
|
|
6670
6710
|
if (method === "POST" && path === granolaTransportPaths.authLock) {
|
|
@@ -6851,6 +6891,59 @@ async function startGranolaServer(app, options = {}) {
|
|
|
6851
6891
|
};
|
|
6852
6892
|
}
|
|
6853
6893
|
//#endregion
|
|
6894
|
+
//#region src/sync-loop.ts
|
|
6895
|
+
function createGranolaSyncLoop(options) {
|
|
6896
|
+
const clearTimeoutImpl = options.clearTimeoutImpl ?? clearTimeout;
|
|
6897
|
+
const setTimeoutImpl = options.setTimeoutImpl ?? setTimeout;
|
|
6898
|
+
let inFlight;
|
|
6899
|
+
let stopped = true;
|
|
6900
|
+
let timer;
|
|
6901
|
+
const schedule = () => {
|
|
6902
|
+
if (stopped) return;
|
|
6903
|
+
timer = setTimeoutImpl(() => {
|
|
6904
|
+
runCycle();
|
|
6905
|
+
}, options.intervalMs);
|
|
6906
|
+
};
|
|
6907
|
+
const runCycle = async () => {
|
|
6908
|
+
if (stopped || inFlight) return;
|
|
6909
|
+
inFlight = (async () => {
|
|
6910
|
+
try {
|
|
6911
|
+
const result = await options.app.sync({
|
|
6912
|
+
forceRefresh: true,
|
|
6913
|
+
foreground: false
|
|
6914
|
+
});
|
|
6915
|
+
await options.onSynced?.(result);
|
|
6916
|
+
} catch (error) {
|
|
6917
|
+
options.logger?.warn?.(`background sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6918
|
+
await options.onError?.(error);
|
|
6919
|
+
} finally {
|
|
6920
|
+
inFlight = void 0;
|
|
6921
|
+
schedule();
|
|
6922
|
+
}
|
|
6923
|
+
})();
|
|
6924
|
+
await inFlight;
|
|
6925
|
+
};
|
|
6926
|
+
return {
|
|
6927
|
+
start(loopOptions = {}) {
|
|
6928
|
+
if (!stopped) return;
|
|
6929
|
+
stopped = false;
|
|
6930
|
+
if (loopOptions.immediate === false) {
|
|
6931
|
+
schedule();
|
|
6932
|
+
return;
|
|
6933
|
+
}
|
|
6934
|
+
runCycle();
|
|
6935
|
+
},
|
|
6936
|
+
async stop() {
|
|
6937
|
+
stopped = true;
|
|
6938
|
+
if (timer !== void 0) {
|
|
6939
|
+
clearTimeoutImpl(timer);
|
|
6940
|
+
timer = void 0;
|
|
6941
|
+
}
|
|
6942
|
+
await inFlight;
|
|
6943
|
+
}
|
|
6944
|
+
};
|
|
6945
|
+
}
|
|
6946
|
+
//#endregion
|
|
6854
6947
|
//#region src/web-url.ts
|
|
6855
6948
|
function buildGranolaMeetingUrl(baseUrl, meetingId) {
|
|
6856
6949
|
const url = new URL(baseUrl);
|
|
@@ -6869,6 +6962,8 @@ function resolveGranolaWebWorkspaceOptions(commandFlags) {
|
|
|
6869
6962
|
openBrowser: commandFlags.open !== false,
|
|
6870
6963
|
password: typeof commandFlags.password === "string" && commandFlags.password.trim() ? commandFlags.password.trim() : void 0,
|
|
6871
6964
|
port,
|
|
6965
|
+
syncEnabled: syncEnabled(commandFlags),
|
|
6966
|
+
syncIntervalMs: parseSyncInterval(commandFlags["sync-interval"]),
|
|
6872
6967
|
trustedOrigins: parseTrustedOrigins(commandFlags["trusted-origins"])
|
|
6873
6968
|
};
|
|
6874
6969
|
}
|
|
@@ -6908,6 +7003,12 @@ async function runGranolaWebWorkspace(app, options) {
|
|
|
6908
7003
|
trustedOrigins: options.trustedOrigins
|
|
6909
7004
|
}
|
|
6910
7005
|
});
|
|
7006
|
+
const syncLoop = options.syncEnabled ? createGranolaSyncLoop({
|
|
7007
|
+
app,
|
|
7008
|
+
intervalMs: options.syncIntervalMs,
|
|
7009
|
+
logger: console
|
|
7010
|
+
}) : void 0;
|
|
7011
|
+
syncLoop?.start();
|
|
6911
7012
|
const targetUrl = options.targetMeetingId ? buildGranolaMeetingUrl(server.url, options.targetMeetingId) : new URL(server.url);
|
|
6912
7013
|
console.log(`Granola Toolkit web workspace listening on ${server.url.href}`);
|
|
6913
7014
|
if (targetUrl.href !== server.url.href) console.log(`Focused meeting URL: ${targetUrl.href}`);
|
|
@@ -6915,6 +7016,7 @@ async function runGranolaWebWorkspace(app, options) {
|
|
|
6915
7016
|
if (options.password) console.log("Server password protection: enabled");
|
|
6916
7017
|
else if (options.networkMode === "lan") console.log("Warning: LAN mode is enabled without a server password");
|
|
6917
7018
|
if (options.trustedOrigins.length > 0) console.log(`Trusted origins: ${options.trustedOrigins.join(", ")}`);
|
|
7019
|
+
console.log(options.syncEnabled ? `Background sync: enabled (${options.syncIntervalMs}ms)` : "Background sync: disabled");
|
|
6918
7020
|
printWebRoutes();
|
|
6919
7021
|
console.log(`Attach: granola attach ${server.url.href}`);
|
|
6920
7022
|
if (options.password) console.log("Attach password: add --password <value>");
|
|
@@ -6925,7 +7027,10 @@ async function runGranolaWebWorkspace(app, options) {
|
|
|
6925
7027
|
console.error(`failed to open browser automatically: ${message}`);
|
|
6926
7028
|
console.error(`open ${targetUrl.href} manually`);
|
|
6927
7029
|
}
|
|
6928
|
-
await waitForShutdown(async () =>
|
|
7030
|
+
await waitForShutdown(async () => {
|
|
7031
|
+
await syncLoop?.stop();
|
|
7032
|
+
await server.close();
|
|
7033
|
+
});
|
|
6929
7034
|
return 0;
|
|
6930
7035
|
}
|
|
6931
7036
|
//#endregion
|
|
@@ -7256,6 +7361,8 @@ Options:
|
|
|
7256
7361
|
--hostname <value> Hostname to bind (overrides network default)
|
|
7257
7362
|
--port <value> Port to bind (default: 0 for any available port)
|
|
7258
7363
|
--password <value> Optional server password for API and browser access
|
|
7364
|
+
--sync-interval <value> Background sync interval, e.g. 60s or 5m (default: 60s)
|
|
7365
|
+
--no-sync Disable the background sync loop
|
|
7259
7366
|
--trusted-origins <v> Comma-separated extra browser origins to trust
|
|
7260
7367
|
--cache <path> Path to Granola cache JSON
|
|
7261
7368
|
--timeout <value> Request timeout, e.g. 2m, 30s, 120000 (default: 2m)
|
|
@@ -7272,8 +7379,10 @@ const serveCommand = {
|
|
|
7272
7379
|
help: { type: "boolean" },
|
|
7273
7380
|
hostname: { type: "string" },
|
|
7274
7381
|
network: { type: "string" },
|
|
7382
|
+
"no-sync": { type: "boolean" },
|
|
7275
7383
|
password: { type: "string" },
|
|
7276
7384
|
port: { type: "string" },
|
|
7385
|
+
"sync-interval": { type: "string" },
|
|
7277
7386
|
timeout: { type: "string" },
|
|
7278
7387
|
"trusted-origins": { type: "string" }
|
|
7279
7388
|
},
|
|
@@ -7293,6 +7402,8 @@ const serveCommand = {
|
|
|
7293
7402
|
const hostname = resolveServerHostname(networkMode, commandFlags.hostname);
|
|
7294
7403
|
const port = parsePort(commandFlags.port);
|
|
7295
7404
|
const password = typeof commandFlags.password === "string" && commandFlags.password.trim() ? commandFlags.password : void 0;
|
|
7405
|
+
const backgroundSyncEnabled = syncEnabled(commandFlags);
|
|
7406
|
+
const syncIntervalMs = parseSyncInterval(commandFlags["sync-interval"]);
|
|
7296
7407
|
const trustedOrigins = parseTrustedOrigins(commandFlags["trusted-origins"]);
|
|
7297
7408
|
const server = await startGranolaServer(app, {
|
|
7298
7409
|
hostname,
|
|
@@ -7302,11 +7413,18 @@ const serveCommand = {
|
|
|
7302
7413
|
trustedOrigins
|
|
7303
7414
|
}
|
|
7304
7415
|
});
|
|
7416
|
+
const syncLoop = backgroundSyncEnabled ? createGranolaSyncLoop({
|
|
7417
|
+
app,
|
|
7418
|
+
intervalMs: syncIntervalMs,
|
|
7419
|
+
logger: console
|
|
7420
|
+
}) : void 0;
|
|
7421
|
+
syncLoop?.start();
|
|
7305
7422
|
console.log(`Granola server listening on ${server.url.href}`);
|
|
7306
7423
|
console.log(`Network mode: ${networkMode}`);
|
|
7307
7424
|
if (password) console.log("Server password protection: enabled");
|
|
7308
7425
|
else if (networkMode === "lan") console.log("Warning: LAN mode is enabled without a server password");
|
|
7309
7426
|
if (trustedOrigins.length > 0) console.log(`Trusted origins: ${trustedOrigins.join(", ")}`);
|
|
7427
|
+
console.log(backgroundSyncEnabled ? `Background sync: enabled (${syncIntervalMs}ms)` : "Background sync: disabled");
|
|
7310
7428
|
console.log("Endpoints:");
|
|
7311
7429
|
console.log(" GET /health");
|
|
7312
7430
|
console.log(" GET /server/info");
|
|
@@ -7328,7 +7446,10 @@ const serveCommand = {
|
|
|
7328
7446
|
console.log(" POST /sync");
|
|
7329
7447
|
console.log(`Attach: granola attach ${server.url.href}`);
|
|
7330
7448
|
if (password) console.log("Attach password: add --password <value>");
|
|
7331
|
-
await waitForShutdown(async () =>
|
|
7449
|
+
await waitForShutdown(async () => {
|
|
7450
|
+
await syncLoop?.stop();
|
|
7451
|
+
await server.close();
|
|
7452
|
+
});
|
|
7332
7453
|
return 0;
|
|
7333
7454
|
}
|
|
7334
7455
|
};
|
|
@@ -7341,6 +7462,8 @@ Usage:
|
|
|
7341
7462
|
granola sync [options]
|
|
7342
7463
|
|
|
7343
7464
|
Options:
|
|
7465
|
+
--watch Keep syncing in the background until interrupted
|
|
7466
|
+
--interval <value> Poll interval for --watch, e.g. 60s or 5m (default: 60s)
|
|
7344
7467
|
--cache <path> Path to Granola cache JSON
|
|
7345
7468
|
--timeout <value> Request timeout, e.g. 2m, 30s, 120000 (default: 2m)
|
|
7346
7469
|
--supabase <path> Path to supabase.json
|
|
@@ -7352,12 +7475,22 @@ Options:
|
|
|
7352
7475
|
function pluralise(count, singular, plural = singular) {
|
|
7353
7476
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
7354
7477
|
}
|
|
7478
|
+
function printSyncResult(result, log = console.log) {
|
|
7479
|
+
log(`✓ Synced ${pluralise(result.summary.meetingCount, "meeting", "meetings")} across ${pluralise(result.summary.folderCount, "folder", "folders")} (${pluralise(result.summary.createdCount, "created")}, ${pluralise(result.summary.changedCount, "updated")}, ${pluralise(result.summary.removedCount, "removed")}, ${pluralise(result.summary.transcriptReadyCount, "transcript ready", "transcripts ready")})`);
|
|
7480
|
+
const lines = result.changes.slice(0, 10).map((change) => {
|
|
7481
|
+
return ` ${change.kind.padEnd(16)} ${change.title} (${change.meetingId})`;
|
|
7482
|
+
});
|
|
7483
|
+
for (const line of lines) log(line);
|
|
7484
|
+
if (result.changes.length > lines.length) log(` ...and ${result.changes.length - lines.length} more change(s)`);
|
|
7485
|
+
}
|
|
7355
7486
|
const syncCommand = {
|
|
7356
7487
|
description: "Refresh the local meeting index and sync state",
|
|
7357
7488
|
flags: {
|
|
7358
7489
|
cache: { type: "string" },
|
|
7359
7490
|
help: { type: "boolean" },
|
|
7360
|
-
|
|
7491
|
+
interval: { type: "string" },
|
|
7492
|
+
timeout: { type: "string" },
|
|
7493
|
+
watch: { type: "boolean" }
|
|
7361
7494
|
},
|
|
7362
7495
|
help: syncHelp,
|
|
7363
7496
|
name: "sync",
|
|
@@ -7373,13 +7506,27 @@ const syncCommand = {
|
|
|
7373
7506
|
const app = await createGranolaApp(config);
|
|
7374
7507
|
debug(config.debug, "authMode", app.getState().auth.mode);
|
|
7375
7508
|
const result = await app.sync();
|
|
7376
|
-
|
|
7377
|
-
const lines = result.changes.slice(0, 10).map((change) => {
|
|
7378
|
-
return ` ${change.kind.padEnd(16)} ${change.title} (${change.meetingId})`;
|
|
7379
|
-
});
|
|
7380
|
-
for (const line of lines) console.log(line);
|
|
7381
|
-
if (result.changes.length > lines.length) console.log(` ...and ${result.changes.length - lines.length} more change(s)`);
|
|
7509
|
+
printSyncResult(result);
|
|
7382
7510
|
if (result.state.lastCompletedAt) debug(config.debug, "syncCompletedAt", result.state.lastCompletedAt);
|
|
7511
|
+
if (commandFlags.watch === true) {
|
|
7512
|
+
const intervalMs = parseSyncInterval(commandFlags.interval);
|
|
7513
|
+
const syncLoop = createGranolaSyncLoop({
|
|
7514
|
+
app,
|
|
7515
|
+
intervalMs,
|
|
7516
|
+
logger: console,
|
|
7517
|
+
onError: async (error) => {
|
|
7518
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
7519
|
+
},
|
|
7520
|
+
onSynced: async (nextResult) => {
|
|
7521
|
+
printSyncResult(nextResult);
|
|
7522
|
+
}
|
|
7523
|
+
});
|
|
7524
|
+
syncLoop.start({ immediate: false });
|
|
7525
|
+
console.log(`Watching for Granola changes every ${intervalMs}ms. Press Ctrl+C to stop.`);
|
|
7526
|
+
await waitForShutdown(async () => {
|
|
7527
|
+
await syncLoop.stop();
|
|
7528
|
+
});
|
|
7529
|
+
}
|
|
7383
7530
|
return 0;
|
|
7384
7531
|
}
|
|
7385
7532
|
};
|
|
@@ -7393,6 +7540,8 @@ Usage:
|
|
|
7393
7540
|
|
|
7394
7541
|
Options:
|
|
7395
7542
|
--meeting <id> Open the workspace focused on a specific meeting
|
|
7543
|
+
--sync-interval <value> Background sync interval, e.g. 60s or 5m (default: 60s)
|
|
7544
|
+
--no-sync Disable the background sync loop
|
|
7396
7545
|
--cache <path> Path to Granola cache JSON
|
|
7397
7546
|
--timeout <value> Request timeout, e.g. 2m, 30s, 120000 (default: 2m)
|
|
7398
7547
|
--supabase <path> Path to supabase.json
|
|
@@ -7407,6 +7556,8 @@ const tuiCommand = {
|
|
|
7407
7556
|
cache: { type: "string" },
|
|
7408
7557
|
help: { type: "boolean" },
|
|
7409
7558
|
meeting: { type: "string" },
|
|
7559
|
+
"no-sync": { type: "boolean" },
|
|
7560
|
+
"sync-interval": { type: "string" },
|
|
7410
7561
|
timeout: { type: "string" }
|
|
7411
7562
|
},
|
|
7412
7563
|
help: tuiHelp,
|
|
@@ -7420,7 +7571,23 @@ const tuiCommand = {
|
|
|
7420
7571
|
debug(config.debug, "supabase", config.supabase);
|
|
7421
7572
|
debug(config.debug, "cacheFile", config.transcripts.cacheFile || "(none)");
|
|
7422
7573
|
debug(config.debug, "timeoutMs", config.notes.timeoutMs);
|
|
7423
|
-
|
|
7574
|
+
const app = await createGranolaApp(config, { surface: "tui" });
|
|
7575
|
+
const initialMeetingId = typeof commandFlags.meeting === "string" && commandFlags.meeting.trim() ? commandFlags.meeting.trim() : void 0;
|
|
7576
|
+
const backgroundSyncEnabled = syncEnabled(commandFlags);
|
|
7577
|
+
const syncIntervalMs = parseSyncInterval(commandFlags["sync-interval"]);
|
|
7578
|
+
const syncLoop = backgroundSyncEnabled ? createGranolaSyncLoop({
|
|
7579
|
+
app,
|
|
7580
|
+
intervalMs: syncIntervalMs,
|
|
7581
|
+
logger: console
|
|
7582
|
+
}) : void 0;
|
|
7583
|
+
syncLoop?.start();
|
|
7584
|
+
debug(config.debug, "backgroundSync", backgroundSyncEnabled ? `${syncIntervalMs}ms` : "disabled");
|
|
7585
|
+
return await runGranolaTui(app, {
|
|
7586
|
+
initialMeetingId,
|
|
7587
|
+
onClose: async () => {
|
|
7588
|
+
await syncLoop?.stop();
|
|
7589
|
+
}
|
|
7590
|
+
});
|
|
7424
7591
|
}
|
|
7425
7592
|
};
|
|
7426
7593
|
//#endregion
|
|
@@ -7500,6 +7667,8 @@ Options:
|
|
|
7500
7667
|
--hostname <value> Hostname to bind (overrides network default)
|
|
7501
7668
|
--port <value> Port to bind (default: 0 for any available port)
|
|
7502
7669
|
--password <value> Optional server password for API and browser access
|
|
7670
|
+
--sync-interval <value> Background sync interval, e.g. 60s or 5m (default: 60s)
|
|
7671
|
+
--no-sync Disable the background sync loop
|
|
7503
7672
|
--trusted-origins <v> Comma-separated extra browser origins to trust
|
|
7504
7673
|
--cache <path> Path to Granola cache JSON
|
|
7505
7674
|
--timeout <value> Request timeout, e.g. 2m, 30s, 120000 (default: 2m)
|
|
@@ -7531,9 +7700,11 @@ const commands = [
|
|
|
7531
7700
|
hostname: { type: "string" },
|
|
7532
7701
|
meeting: { type: "string" },
|
|
7533
7702
|
network: { type: "string" },
|
|
7703
|
+
"no-sync": { type: "boolean" },
|
|
7534
7704
|
open: { type: "boolean" },
|
|
7535
7705
|
password: { type: "string" },
|
|
7536
7706
|
port: { type: "string" },
|
|
7707
|
+
"sync-interval": { type: "string" },
|
|
7537
7708
|
timeout: { type: "string" },
|
|
7538
7709
|
"trusted-origins": { type: "string" }
|
|
7539
7710
|
},
|