granola-toolkit 0.29.0 → 0.30.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 +11 -0
  2. package/dist/cli.js +145 -62
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -208,6 +208,7 @@ The machine-readable `export` command includes:
208
208
  The initial server API includes:
209
209
 
210
210
  - `GET /health`
211
+ - `GET /server/info`
211
212
  - `POST /auth/unlock` for password-protected servers
212
213
  - `POST /auth/lock` to clear the browser/API unlock cookie
213
214
  - `GET /auth/status`
@@ -272,6 +273,16 @@ Use it when you want:
272
273
 
273
274
  The attach flow uses the existing local HTTP API plus `GET /events` for live state updates.
274
275
 
276
+ ### Runtime Boundaries
277
+
278
+ The toolkit now keeps its local persistence and transport contracts explicit:
279
+
280
+ - one shared local data directory for export jobs, meeting index data, and any file-backed session state
281
+ - one versioned local HTTP transport contract, exposed by `GET /server/info`
282
+ - one remote client handshake that validates the transport protocol before attaching
283
+
284
+ That keeps the current single-package repo simple, while making a future split into separate server/client packages or remote-hosted clients much less invasive.
285
+
275
286
  ### TUI
276
287
 
277
288
  `tui` starts a full-screen terminal workspace on the shared app core, without requiring the local server or browser client. Use `attach` when you want the same workspace against an existing shared server instance instead.
package/dist/cli.js CHANGED
@@ -9,6 +9,60 @@ import { NodeHtmlMarkdown } from "node-html-markdown";
9
9
  import { execFile } from "node:child_process";
10
10
  import { promisify } from "node:util";
11
11
  import { createServer } from "node:http";
12
+ //#region src/transport.ts
13
+ const granolaTransportPaths = {
14
+ authLock: "/auth/lock",
15
+ authLogin: "/auth/login",
16
+ authLogout: "/auth/logout",
17
+ authMode: "/auth/mode",
18
+ authRefresh: "/auth/refresh",
19
+ authStatus: "/auth/status",
20
+ authUnlock: "/auth/unlock",
21
+ events: "/events",
22
+ exportJobs: "/exports/jobs",
23
+ exportNotes: "/exports/notes",
24
+ exportTranscripts: "/exports/transcripts",
25
+ health: "/health",
26
+ meetingResolve: "/meetings/resolve",
27
+ meetings: "/meetings",
28
+ root: "/",
29
+ serverInfo: "/server/info",
30
+ state: "/state"
31
+ };
32
+ function appendSearchParams(path, params) {
33
+ const url = new URL(path, "http://localhost");
34
+ for (const [key, value] of Object.entries(params)) {
35
+ if (value === void 0 || value === false || value === "") continue;
36
+ url.searchParams.set(key, String(value));
37
+ }
38
+ return `${url.pathname}${url.search}`;
39
+ }
40
+ function granolaMeetingPath(id) {
41
+ return `${granolaTransportPaths.meetings}/${encodeURIComponent(id)}`;
42
+ }
43
+ function granolaMeetingResolvePath(query, options = {}) {
44
+ return appendSearchParams(granolaTransportPaths.meetingResolve, {
45
+ includeTranscript: options.includeTranscript ? "true" : void 0,
46
+ q: query
47
+ });
48
+ }
49
+ function granolaMeetingsPath(options = {}) {
50
+ return appendSearchParams(granolaTransportPaths.meetings, {
51
+ limit: options.limit,
52
+ refresh: options.forceRefresh ? "true" : void 0,
53
+ search: options.search,
54
+ sort: options.sort,
55
+ updatedFrom: options.updatedFrom,
56
+ updatedTo: options.updatedTo
57
+ });
58
+ }
59
+ function granolaExportJobsPath(options = {}) {
60
+ return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });
61
+ }
62
+ function granolaExportJobRerunPath(id) {
63
+ return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;
64
+ }
65
+ //#endregion
12
66
  //#region src/server/client.ts
13
67
  function cloneValue(value) {
14
68
  return structuredClone(value);
@@ -24,14 +78,6 @@ function normaliseServerUrl(serverUrl) {
24
78
  parsed.hash = "";
25
79
  return parsed;
26
80
  }
27
- function appendSearchParams(path, params) {
28
- const url = new URL(path, "http://localhost");
29
- for (const [key, value] of Object.entries(params)) {
30
- if (value === void 0 || value === false || value === "") continue;
31
- url.searchParams.set(key, String(value));
32
- }
33
- return `${url.pathname}${url.search}`;
34
- }
35
81
  function mergeHeaders(...values) {
36
82
  const headers = new Headers();
37
83
  for (const value of values) {
@@ -66,22 +112,32 @@ var GranolaServerClient = class GranolaServerClient {
66
112
  #fetchImpl;
67
113
  #password;
68
114
  #reconnectDelayMs;
115
+ info;
69
116
  #streamAbortController;
70
- constructor(url, initialState, options = {}) {
117
+ constructor(info, url, initialState, options = {}) {
71
118
  this.url = url;
72
119
  this.#fetchImpl = options.fetchImpl ?? fetch;
120
+ this.info = cloneValue(info);
73
121
  this.#password = options.password?.trim() || void 0;
74
122
  this.#reconnectDelayMs = options.reconnectDelayMs ?? 1e3;
75
123
  this.#state = cloneValue(initialState);
76
124
  }
77
125
  static async connect(serverUrl, options = {}) {
78
126
  const url = normaliseServerUrl(serverUrl);
79
- const response = await (options.fetchImpl ?? fetch)(new URL("/state", url), { headers: mergeHeaders({
127
+ const fetchImpl = options.fetchImpl ?? fetch;
128
+ const infoResponse = await fetchImpl(new URL(granolaTransportPaths.serverInfo, url), { headers: mergeHeaders({
129
+ ...options.password?.trim() ? { "x-granola-password": options.password.trim() } : {},
130
+ accept: "application/json"
131
+ }) });
132
+ if (!infoResponse.ok) throw await responseError(infoResponse);
133
+ const info = await infoResponse.json();
134
+ if (info.protocolVersion !== 1) throw new Error(`unsupported Granola transport protocol: expected 1, got ${info.protocolVersion}`);
135
+ const response = await fetchImpl(new URL(granolaTransportPaths.state, url), { headers: mergeHeaders({
80
136
  ...options.password?.trim() ? { "x-granola-password": options.password.trim() } : {},
81
137
  accept: "application/json"
82
138
  }) });
83
139
  if (!response.ok) throw await responseError(response);
84
- const client = new GranolaServerClient(url, await response.json(), options);
140
+ const client = new GranolaServerClient(info, url, await response.json(), options);
85
141
  client.startEvents();
86
142
  return client;
87
143
  }
@@ -103,66 +159,56 @@ var GranolaServerClient = class GranolaServerClient {
103
159
  };
104
160
  }
105
161
  async inspectAuth() {
106
- return await this.requestJson("/auth/status");
162
+ return await this.requestJson(granolaTransportPaths.authStatus);
107
163
  }
108
164
  async loginAuth(options = {}) {
109
- return await this.requestJson("/auth/login", {
165
+ return await this.requestJson(granolaTransportPaths.authLogin, {
110
166
  body: JSON.stringify(options),
111
167
  headers: { "content-type": "application/json" },
112
168
  method: "POST"
113
169
  });
114
170
  }
115
171
  async logoutAuth() {
116
- return await this.requestJson("/auth/logout", { method: "POST" });
172
+ return await this.requestJson(granolaTransportPaths.authLogout, { method: "POST" });
117
173
  }
118
174
  async refreshAuth() {
119
- return await this.requestJson("/auth/refresh", { method: "POST" });
175
+ return await this.requestJson(granolaTransportPaths.authRefresh, { method: "POST" });
120
176
  }
121
177
  async switchAuthMode(mode) {
122
- return await this.requestJson("/auth/mode", {
178
+ return await this.requestJson(granolaTransportPaths.authMode, {
123
179
  body: JSON.stringify({ mode }),
124
180
  headers: { "content-type": "application/json" },
125
181
  method: "POST"
126
182
  });
127
183
  }
128
184
  async listMeetings(options = {}) {
129
- return await this.requestJson(appendSearchParams("/meetings", {
130
- limit: options.limit,
131
- refresh: options.forceRefresh ? "true" : void 0,
132
- search: options.search,
133
- sort: options.sort,
134
- updatedFrom: options.updatedFrom,
135
- updatedTo: options.updatedTo
136
- }));
185
+ return await this.requestJson(granolaMeetingsPath(options));
137
186
  }
138
187
  async getMeeting(id, options = {}) {
139
- return await this.requestJson(appendSearchParams(`/meetings/${encodeURIComponent(id)}`, { includeTranscript: options.requireCache ? "true" : void 0 }));
188
+ return await this.requestJson(`${granolaMeetingPath(id)}${options.requireCache ? "?includeTranscript=true" : ""}`);
140
189
  }
141
190
  async findMeeting(query, options = {}) {
142
- return await this.requestJson(appendSearchParams("/meetings/resolve", {
143
- includeTranscript: options.requireCache ? "true" : void 0,
144
- q: query
145
- }));
191
+ return await this.requestJson(granolaMeetingResolvePath(query, { includeTranscript: options.requireCache }));
146
192
  }
147
193
  async listExportJobs(options = {}) {
148
- return await this.requestJson(appendSearchParams("/exports/jobs", { limit: options.limit }));
194
+ return await this.requestJson(granolaExportJobsPath(options));
149
195
  }
150
196
  async exportNotes(format = "markdown") {
151
- return await this.requestJson("/exports/notes", {
197
+ return await this.requestJson(granolaTransportPaths.exportNotes, {
152
198
  body: JSON.stringify({ format }),
153
199
  headers: { "content-type": "application/json" },
154
200
  method: "POST"
155
201
  });
156
202
  }
157
203
  async exportTranscripts(format = "text") {
158
- return await this.requestJson("/exports/transcripts", {
204
+ return await this.requestJson(granolaTransportPaths.exportTranscripts, {
159
205
  body: JSON.stringify({ format }),
160
206
  headers: { "content-type": "application/json" },
161
207
  method: "POST"
162
208
  });
163
209
  }
164
210
  async rerunExportJob(id) {
165
- return await this.requestJson(`/exports/jobs/${encodeURIComponent(id)}/rerun`, { method: "POST" });
211
+ return await this.requestJson(granolaExportJobRerunPath(id), { method: "POST" });
166
212
  }
167
213
  async request(path, init = {}) {
168
214
  const response = await this.#fetchImpl(new URL(path, this.url), {
@@ -192,7 +238,7 @@ var GranolaServerClient = class GranolaServerClient {
192
238
  const controller = new AbortController();
193
239
  this.#streamAbortController = controller;
194
240
  try {
195
- const response = await this.request("/events", {
241
+ const response = await this.request(granolaTransportPaths.events, {
196
242
  headers: { accept: "text/event-stream" },
197
243
  signal: controller.signal
198
244
  });
@@ -2131,6 +2177,22 @@ function parseCacheContents(contents) {
2131
2177
  };
2132
2178
  }
2133
2179
  //#endregion
2180
+ //#region src/persistence/layout.ts
2181
+ function defaultGranolaToolkitDataDirectory(targetPlatform = platform(), homeDirectory = homedir()) {
2182
+ return targetPlatform === "darwin" ? join(homeDirectory, "Library", "Application Support", "granola-toolkit") : join(homeDirectory, ".config", "granola-toolkit");
2183
+ }
2184
+ function defaultGranolaToolkitPersistenceLayout(options = {}) {
2185
+ const targetPlatform = options.platform ?? platform();
2186
+ const dataDirectory = defaultGranolaToolkitDataDirectory(targetPlatform, options.homeDirectory ?? homedir());
2187
+ return {
2188
+ dataDirectory,
2189
+ exportJobsFile: join(dataDirectory, "export-jobs.json"),
2190
+ meetingIndexFile: join(dataDirectory, "meeting-index.json"),
2191
+ sessionFile: join(dataDirectory, "session.json"),
2192
+ sessionStoreKind: targetPlatform === "darwin" ? "keychain" : "file"
2193
+ };
2194
+ }
2195
+ //#endregion
2134
2196
  //#region src/client/auth.ts
2135
2197
  const execFileAsync$1 = promisify(execFile);
2136
2198
  const DEFAULT_CLIENT_ID = "client_GranolaMac";
@@ -2351,11 +2413,10 @@ async function refreshGranolaSession(session, fetchImpl = fetch) {
2351
2413
  };
2352
2414
  }
2353
2415
  function defaultSessionFilePath() {
2354
- const home = homedir();
2355
- return platform() === "darwin" ? join(home, "Library", "Application Support", "granola-toolkit", "session.json") : join(home, ".config", "granola-toolkit", "session.json");
2416
+ return defaultGranolaToolkitPersistenceLayout().sessionFile;
2356
2417
  }
2357
2418
  function createDefaultSessionStore() {
2358
- return platform() === "darwin" ? new KeychainSessionStore() : new FileSessionStore();
2419
+ return defaultGranolaToolkitPersistenceLayout().sessionStoreKind === "keychain" ? new KeychainSessionStore() : new FileSessionStore();
2359
2420
  }
2360
2421
  //#endregion
2361
2422
  //#region src/client/default-auth.ts
@@ -2736,8 +2797,7 @@ function createExportJobId(kind) {
2736
2797
  return `${kind}-${randomUUID()}`;
2737
2798
  }
2738
2799
  function defaultExportJobsFilePath() {
2739
- const home = homedir();
2740
- return platform() === "darwin" ? join(home, "Library", "Application Support", "granola-toolkit", "export-jobs.json") : join(home, ".config", "granola-toolkit", "export-jobs.json");
2800
+ return defaultGranolaToolkitPersistenceLayout().exportJobsFile;
2741
2801
  }
2742
2802
  var FileExportJobStore = class {
2743
2803
  constructor(filePath = defaultExportJobsFilePath()) {
@@ -2801,8 +2861,7 @@ var FileMeetingIndexStore = class {
2801
2861
  }
2802
2862
  };
2803
2863
  function defaultMeetingIndexFilePath() {
2804
- const home = homedir();
2805
- return platform() === "darwin" ? join(home, "Library", "Application Support", "granola-toolkit", "meeting-index.json") : join(home, ".config", "granola-toolkit", "meeting-index.json");
2864
+ return defaultGranolaToolkitPersistenceLayout().meetingIndexFile;
2806
2865
  }
2807
2866
  function createDefaultMeetingIndexStore() {
2808
2867
  return new FileMeetingIndexStore();
@@ -5355,7 +5414,7 @@ function isPasswordAuthenticated(request, password) {
5355
5414
  return parseCookies(request)[PASSWORD_COOKIE_NAME] === password;
5356
5415
  }
5357
5416
  function publicRoute(path, enableWebClient) {
5358
- return path === "/health" || path === "/auth/unlock" || enableWebClient && path === "/";
5417
+ return path === granolaTransportPaths.health || path === granolaTransportPaths.serverInfo || path === granolaTransportPaths.authUnlock || enableWebClient && path === granolaTransportPaths.root;
5359
5418
  }
5360
5419
  async function startGranolaServer(app, options = {}) {
5361
5420
  const enableWebClient = options.enableWebClient ?? false;
@@ -5365,6 +5424,24 @@ async function startGranolaServer(app, options = {}) {
5365
5424
  password: options.security?.password?.trim() || void 0,
5366
5425
  trustedOrigins: (options.security?.trustedOrigins ?? []).map((origin) => origin.trim()).filter(Boolean)
5367
5426
  };
5427
+ const serverInfo = {
5428
+ capabilities: {
5429
+ attach: true,
5430
+ auth: true,
5431
+ events: true,
5432
+ exports: true,
5433
+ meetingOpen: true,
5434
+ webClient: enableWebClient
5435
+ },
5436
+ persistence: {
5437
+ exportJobs: true,
5438
+ meetingIndex: true,
5439
+ sessionStore: defaultGranolaToolkitPersistenceLayout().sessionStoreKind
5440
+ },
5441
+ product: "granola-toolkit",
5442
+ protocolVersion: 1,
5443
+ transport: "local-http"
5444
+ };
5368
5445
  const server = createServer(async (request, response) => {
5369
5446
  const method = request.method ?? "GET";
5370
5447
  const url = new URL(request.url ?? "/", `http://${hostname}`);
@@ -5392,11 +5469,11 @@ async function startGranolaServer(app, options = {}) {
5392
5469
  sendNoContent(response, 204, originHeaders);
5393
5470
  return;
5394
5471
  }
5395
- if (method === "GET" && path === "/" && enableWebClient) {
5472
+ if (method === "GET" && path === granolaTransportPaths.root && enableWebClient) {
5396
5473
  sendHtml(response, renderGranolaWebPage({ serverPasswordRequired: Boolean(security.password) }), 200, originHeaders);
5397
5474
  return;
5398
5475
  }
5399
- if (method === "GET" && path === "/health") {
5476
+ if (method === "GET" && path === granolaTransportPaths.health) {
5400
5477
  sendJson(response, {
5401
5478
  ok: true,
5402
5479
  service: "granola-toolkit",
@@ -5404,7 +5481,11 @@ async function startGranolaServer(app, options = {}) {
5404
5481
  }, { headers: originHeaders });
5405
5482
  return;
5406
5483
  }
5407
- if (method === "POST" && path === "/auth/unlock") {
5484
+ if (method === "GET" && path === granolaTransportPaths.serverInfo) {
5485
+ sendJson(response, serverInfo, { headers: originHeaders });
5486
+ return;
5487
+ }
5488
+ if (method === "POST" && path === granolaTransportPaths.authUnlock) {
5408
5489
  if (!security.password) {
5409
5490
  sendJson(response, {
5410
5491
  ok: true,
@@ -5443,22 +5524,22 @@ async function startGranolaServer(app, options = {}) {
5443
5524
  });
5444
5525
  return;
5445
5526
  }
5446
- if (method === "GET" && path === "/state") {
5527
+ if (method === "GET" && path === granolaTransportPaths.state) {
5447
5528
  sendJson(response, app.getState(), { headers: originHeaders });
5448
5529
  return;
5449
5530
  }
5450
- if (method === "GET" && path === "/auth/status") {
5531
+ if (method === "GET" && path === granolaTransportPaths.authStatus) {
5451
5532
  sendJson(response, await app.inspectAuth(), { headers: originHeaders });
5452
5533
  return;
5453
5534
  }
5454
- if (method === "POST" && path === "/auth/lock") {
5535
+ if (method === "POST" && path === granolaTransportPaths.authLock) {
5455
5536
  sendJson(response, { ok: true }, { headers: {
5456
5537
  ...originHeaders,
5457
5538
  "set-cookie": clearPasswordCookieHeader()
5458
5539
  } });
5459
5540
  return;
5460
5541
  }
5461
- if (method === "GET" && path === "/events") {
5542
+ if (method === "GET" && path === granolaTransportPaths.events) {
5462
5543
  response.writeHead(200, {
5463
5544
  "cache-control": "no-cache, no-transform",
5464
5545
  connection: "keep-alive",
@@ -5479,7 +5560,7 @@ async function startGranolaServer(app, options = {}) {
5479
5560
  });
5480
5561
  return;
5481
5562
  }
5482
- if (method === "GET" && path === "/meetings") {
5563
+ if (method === "GET" && path === granolaTransportPaths.meetings) {
5483
5564
  const limit = parseInteger(url.searchParams.get("limit"));
5484
5565
  const refresh = url.searchParams.get("refresh") === "true";
5485
5566
  const search = url.searchParams.get("search")?.trim() || void 0;
@@ -5505,38 +5586,38 @@ async function startGranolaServer(app, options = {}) {
5505
5586
  }, { headers: originHeaders });
5506
5587
  return;
5507
5588
  }
5508
- if (method === "GET" && path === "/meetings/resolve") {
5589
+ if (method === "GET" && path === granolaTransportPaths.meetingResolve) {
5509
5590
  const query = url.searchParams.get("q")?.trim();
5510
5591
  if (!query) throw new Error("meeting query is required");
5511
5592
  sendJson(response, await app.findMeeting(query, { requireCache: url.searchParams.get("includeTranscript") === "true" }), { headers: originHeaders });
5512
5593
  return;
5513
5594
  }
5514
- if (method === "GET" && path.startsWith("/meetings/")) {
5515
- const id = decodeURIComponent(path.slice(10));
5595
+ if (method === "GET" && path.startsWith(`${granolaTransportPaths.meetings}/`) && path !== granolaTransportPaths.meetingResolve) {
5596
+ const id = decodeURIComponent(path.slice(`${granolaTransportPaths.meetings}/`.length));
5516
5597
  if (!id) throw new Error("meeting id is required");
5517
5598
  sendJson(response, await app.getMeeting(id, { requireCache: url.searchParams.get("includeTranscript") === "true" }), { headers: originHeaders });
5518
5599
  return;
5519
5600
  }
5520
- if (method === "POST" && path === "/auth/login") {
5601
+ if (method === "POST" && path === granolaTransportPaths.authLogin) {
5521
5602
  const body = await readJsonBody(request);
5522
5603
  const supabasePath = typeof body.supabasePath === "string" && body.supabasePath.trim() ? body.supabasePath.trim() : void 0;
5523
5604
  sendJson(response, await app.loginAuth({ supabasePath }), { headers: originHeaders });
5524
5605
  return;
5525
5606
  }
5526
- if (method === "POST" && path === "/auth/logout") {
5607
+ if (method === "POST" && path === granolaTransportPaths.authLogout) {
5527
5608
  sendJson(response, await app.logoutAuth(), { headers: originHeaders });
5528
5609
  return;
5529
5610
  }
5530
- if (method === "POST" && path === "/auth/refresh") {
5611
+ if (method === "POST" && path === granolaTransportPaths.authRefresh) {
5531
5612
  sendJson(response, await app.refreshAuth(), { headers: originHeaders });
5532
5613
  return;
5533
5614
  }
5534
- if (method === "POST" && path === "/auth/mode") {
5615
+ if (method === "POST" && path === granolaTransportPaths.authMode) {
5535
5616
  const body = await readJsonBody(request);
5536
5617
  sendJson(response, await app.switchAuthMode(parseAuthMode(body.mode)), { headers: originHeaders });
5537
5618
  return;
5538
5619
  }
5539
- if (method === "POST" && path === "/exports/notes") {
5620
+ if (method === "POST" && path === granolaTransportPaths.exportNotes) {
5540
5621
  const body = await readJsonBody(request);
5541
5622
  sendJson(response, await app.exportNotes(noteFormatFromBody(body.format)), {
5542
5623
  headers: originHeaders,
@@ -5544,13 +5625,13 @@ async function startGranolaServer(app, options = {}) {
5544
5625
  });
5545
5626
  return;
5546
5627
  }
5547
- if (method === "GET" && path === "/exports/jobs") {
5628
+ if (method === "GET" && path === granolaTransportPaths.exportJobs) {
5548
5629
  const limit = parseInteger(url.searchParams.get("limit"));
5549
5630
  sendJson(response, await app.listExportJobs({ limit }), { headers: originHeaders });
5550
5631
  return;
5551
5632
  }
5552
- if (method === "POST" && path.startsWith("/exports/jobs/") && path.endsWith("/rerun")) {
5553
- const id = decodeURIComponent(path.slice(14, -6));
5633
+ if (method === "POST" && path.startsWith(`${granolaTransportPaths.exportJobs}/`) && path.endsWith("/rerun")) {
5634
+ const id = decodeURIComponent(path.slice(`${granolaTransportPaths.exportJobs}/`.length, -6));
5554
5635
  if (!id) throw new Error("export job id is required");
5555
5636
  sendJson(response, await app.rerunExportJob(id), {
5556
5637
  headers: originHeaders,
@@ -5558,7 +5639,7 @@ async function startGranolaServer(app, options = {}) {
5558
5639
  });
5559
5640
  return;
5560
5641
  }
5561
- if (method === "POST" && path === "/exports/transcripts") {
5642
+ if (method === "POST" && path === granolaTransportPaths.exportTranscripts) {
5562
5643
  const body = await readJsonBody(request);
5563
5644
  sendJson(response, await app.exportTranscripts(transcriptFormatFromBody(body.format)), {
5564
5645
  headers: originHeaders,
@@ -5630,6 +5711,7 @@ function printWebRoutes() {
5630
5711
  console.log("Routes:");
5631
5712
  console.log(" GET /");
5632
5713
  console.log(" GET /health");
5714
+ console.log(" GET /server/info");
5633
5715
  console.log(" POST /auth/unlock");
5634
5716
  console.log(" POST /auth/lock");
5635
5717
  console.log(" GET /auth/status");
@@ -6042,6 +6124,7 @@ const serveCommand = {
6042
6124
  if (trustedOrigins.length > 0) console.log(`Trusted origins: ${trustedOrigins.join(", ")}`);
6043
6125
  console.log("Endpoints:");
6044
6126
  console.log(" GET /health");
6127
+ console.log(" GET /server/info");
6045
6128
  console.log(" POST /auth/unlock");
6046
6129
  console.log(" POST /auth/lock");
6047
6130
  console.log(" GET /auth/status");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granola-toolkit",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Toolkit for exporting and working with Granola meetings, notes, and transcripts",
5
5
  "keywords": [
6
6
  "cli",