notebooklm-sdk 0.1.8 → 0.3.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/dist/index.js CHANGED
@@ -19,6 +19,7 @@ __export(enums_exports, {
19
19
  ArtifactTypeCode: () => ArtifactTypeCode,
20
20
  AudioFormat: () => AudioFormat,
21
21
  AudioLength: () => AudioLength,
22
+ ChatMode: () => ChatMode,
22
23
  ExportType: () => ExportType,
23
24
  InfographicDetail: () => InfographicDetail,
24
25
  InfographicOrientation: () => InfographicOrientation,
@@ -36,6 +37,7 @@ __export(enums_exports, {
36
37
  VideoStyle: () => VideoStyle,
37
38
  artifactStatusFromCode: () => artifactStatusFromCode,
38
39
  artifactTypeFromCode: () => artifactTypeFromCode,
40
+ chatModeToParams: () => chatModeToParams,
39
41
  sourceStatusFromCode: () => sourceStatusFromCode,
40
42
  sourceTypeFromCode: () => sourceTypeFromCode
41
43
  });
@@ -57,7 +59,10 @@ function artifactStatusFromCode(code) {
57
59
  function sourceStatusFromCode(code) {
58
60
  return SOURCE_STATUS_MAP[code] ?? "unknown";
59
61
  }
60
- var RPCMethod, ArtifactTypeCode, ArtifactStatusCode, SourceStatusCode, AudioFormat, AudioLength, VideoFormat, VideoStyle, QuizQuantity, QuizDifficulty, InfographicOrientation, InfographicDetail, InfographicStyle, SlideDeckFormat, SlideDeckLength, ExportType, SOURCE_TYPE_MAP, ARTIFACT_TYPE_MAP, ARTIFACT_STATUS_MAP, SOURCE_STATUS_MAP, ShareAccess, ShareViewLevel, SharePermission;
62
+ function chatModeToParams(mode) {
63
+ return CHAT_MODE_PARAMS[mode];
64
+ }
65
+ var RPCMethod, ArtifactTypeCode, ArtifactStatusCode, SourceStatusCode, AudioFormat, AudioLength, VideoFormat, VideoStyle, QuizQuantity, QuizDifficulty, InfographicOrientation, InfographicDetail, InfographicStyle, SlideDeckFormat, SlideDeckLength, ExportType, SOURCE_TYPE_MAP, ARTIFACT_TYPE_MAP, ARTIFACT_STATUS_MAP, SOURCE_STATUS_MAP, ChatMode, CHAT_MODE_PARAMS, ShareAccess, ShareViewLevel, SharePermission;
61
66
  var init_enums = __esm({
62
67
  "src/types/enums.ts"() {
63
68
  RPCMethod = {
@@ -241,6 +246,22 @@ var init_enums = __esm({
241
246
  3: "error",
242
247
  5: "preparing"
243
248
  };
249
+ ChatMode = {
250
+ /** General purpose — balanced length and style. */
251
+ DEFAULT: "default",
252
+ /** Educational focus with longer, learning-oriented responses. */
253
+ LEARNING_GUIDE: "learning_guide",
254
+ /** Short, concise answers. */
255
+ CONCISE: "concise",
256
+ /** Verbose, detailed answers. */
257
+ DETAILED: "detailed"
258
+ };
259
+ CHAT_MODE_PARAMS = {
260
+ default: [1, 1],
261
+ learning_guide: [3, 4],
262
+ concise: [1, 5],
263
+ detailed: [1, 4]
264
+ };
244
265
  ShareAccess = {
245
266
  /** Only explicitly shared users can access */
246
267
  RESTRICTED: 0,
@@ -453,10 +474,8 @@ function loadCookiesFromFile(filePath) {
453
474
  try {
454
475
  raw = readFileSync(filePath, "utf-8");
455
476
  } catch {
456
- throw new AuthError(
457
- `Session file not found: ${filePath}
458
- Run: npx notebooklm-sdk login`
459
- );
477
+ throw new AuthError(`Session file not found: ${filePath}
478
+ Run: npx notebooklm-sdk login`);
460
479
  }
461
480
  return extractCookiesFromStorageState(JSON.parse(raw));
462
481
  }
@@ -590,9 +609,7 @@ async function connect(opts = {}) {
590
609
  } else if (envCookies) {
591
610
  cookieMap = loadCookiesFromString(envCookies);
592
611
  } else {
593
- throw new AuthError(
594
- "No session found. Run: npx notebooklm-sdk login"
595
- );
612
+ throw new AuthError("No session found. Run: npx notebooklm-sdk login");
596
613
  }
597
614
  }
598
615
  const { csrfToken, sessionId } = await fetchTokens(cookieMap);
@@ -727,26 +744,6 @@ function parseArtifact(data, notebookId) {
727
744
  _raw: Array.isArray(data) ? data : []
728
745
  };
729
746
  }
730
- function parseNote(data) {
731
- const id = typeof data[0] === "string" ? data[0] : "";
732
- const content = typeof data[1] === "string" ? data[1] : "";
733
- const title = typeof data[2] === "string" ? data[2] : null;
734
- let createdAt = null;
735
- let updatedAt = null;
736
- if (Array.isArray(data[3]) && typeof data[3][0] === "number") {
737
- try {
738
- createdAt = new Date(data[3][0] * 1e3);
739
- } catch {
740
- }
741
- }
742
- if (Array.isArray(data[4]) && typeof data[4][0] === "number") {
743
- try {
744
- updatedAt = new Date(data[4][0] * 1e3);
745
- } catch {
746
- }
747
- }
748
- return { id, title, content, createdAt, updatedAt };
749
- }
750
747
 
751
748
  // src/api/artifacts.ts
752
749
  function tripleNest(ids) {
@@ -756,18 +753,13 @@ function doubleNest(ids) {
756
753
  return ids.map((id) => [id]);
757
754
  }
758
755
  var ArtifactsAPI = class {
759
- constructor(rpc, auth) {
756
+ constructor(rpc, auth, notes) {
760
757
  this.rpc = rpc;
761
758
  this.auth = auth;
759
+ this.notes = notes;
762
760
  }
763
761
  async list(notebookId) {
764
- const params = [[2], notebookId, 'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"'];
765
- const result = await this.rpc.call(RPCMethod.LIST_ARTIFACTS, params, {
766
- sourcePath: `/notebook/${notebookId}`,
767
- allowNull: true
768
- });
769
- if (!Array.isArray(result) || !result.length) return [];
770
- const rawList = Array.isArray(result[0]) ? result[0] : result;
762
+ const rawList = await this._listRaw(notebookId);
771
763
  const artifacts = [];
772
764
  for (const item of rawList) {
773
765
  if (Array.isArray(item)) {
@@ -779,6 +771,15 @@ var ArtifactsAPI = class {
779
771
  }
780
772
  return artifacts;
781
773
  }
774
+ async _listRaw(notebookId) {
775
+ const params = [[2], notebookId, 'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"'];
776
+ const result = await this.rpc.call(RPCMethod.LIST_ARTIFACTS, params, {
777
+ sourcePath: `/notebook/${notebookId}`,
778
+ allowNull: true
779
+ });
780
+ if (!Array.isArray(result) || !result.length) return [];
781
+ return Array.isArray(result[0]) ? result[0] : result;
782
+ }
782
783
  async get(notebookId, artifactId) {
783
784
  const artifacts = await this.list(notebookId);
784
785
  return artifacts.find((a) => a.id === artifactId) ?? null;
@@ -958,6 +959,37 @@ var ArtifactsAPI = class {
958
959
  ];
959
960
  return this._callGenerate(notebookId, params);
960
961
  }
962
+ async createDataTable(notebookId, opts = {}) {
963
+ const language = opts.language ?? "en";
964
+ const sourceIds = opts.sourceIds ?? await this.rpc.getSourceIds(notebookId);
965
+ const triple = tripleNest(sourceIds);
966
+ const params = [
967
+ [2],
968
+ notebookId,
969
+ [
970
+ null,
971
+ null,
972
+ ArtifactTypeCode.DATA_TABLE,
973
+ triple,
974
+ null,
975
+ null,
976
+ null,
977
+ null,
978
+ null,
979
+ null,
980
+ null,
981
+ null,
982
+ null,
983
+ null,
984
+ null,
985
+ null,
986
+ null,
987
+ null,
988
+ [null, [opts.instructions ?? null, language]]
989
+ ]
990
+ ];
991
+ return this._callGenerate(notebookId, params);
992
+ }
961
993
  async createReport(notebookId, opts = {}) {
962
994
  const format = opts.format ?? "briefing_doc";
963
995
  const language = opts.language ?? "en";
@@ -1023,7 +1055,15 @@ ${opts.extraInstructions}` : cfg.prompt;
1023
1055
  sourcePath: `/notebook/${notebookId}`,
1024
1056
  allowNull: true
1025
1057
  });
1026
- return this._parseGenerationResult(result);
1058
+ const mindMapJson = Array.isArray(result) && Array.isArray(result[0]) && typeof result[0][0] === "string" ? result[0][0] : null;
1059
+ if (!mindMapJson) throw new Error("Mind map generation returned no content");
1060
+ let title = "Mind Map";
1061
+ try {
1062
+ const parsed = JSON.parse(mindMapJson);
1063
+ if (typeof parsed["name"] === "string") title = parsed["name"];
1064
+ } catch {
1065
+ }
1066
+ return this.notes.create(notebookId, mindMapJson, title);
1027
1067
  }
1028
1068
  // ---------------------------------------------------------------------------
1029
1069
  // Polling / download
@@ -1079,6 +1119,81 @@ ${opts.extraInstructions}` : cfg.prompt;
1079
1119
  }
1080
1120
  return null;
1081
1121
  }
1122
+ /** Download a completed slide deck as PDF or PPTX. Returns a Buffer. */
1123
+ async downloadSlideDeck(notebookId, artifactId, format = "pdf") {
1124
+ const rawList = await this._listRaw(notebookId);
1125
+ const raw = rawList.find(
1126
+ (a) => a[0] === artifactId && a[2] === ArtifactTypeCode.SLIDE_DECK
1127
+ );
1128
+ if (!raw) throw new ArtifactNotReadyError("slide_deck", { artifactId });
1129
+ const metadata = raw[16];
1130
+ if (!Array.isArray(metadata)) throw new ArtifactNotReadyError("slide_deck", { artifactId });
1131
+ const url = format === "pptx" ? metadata[4] : metadata[3];
1132
+ if (typeof url !== "string" || !url.startsWith("http")) {
1133
+ throw new ArtifactNotReadyError("slide_deck", { artifactId, status: `no ${format} url` });
1134
+ }
1135
+ return this._fetchMediaWithCookies(url);
1136
+ }
1137
+ /** Download a completed infographic as PNG. Returns a Buffer. */
1138
+ async downloadInfographic(notebookId, artifactId) {
1139
+ const rawList = await this._listRaw(notebookId);
1140
+ const raw = rawList.find(
1141
+ (a) => a[0] === artifactId && a[2] === ArtifactTypeCode.INFOGRAPHIC
1142
+ );
1143
+ if (!raw) throw new ArtifactNotReadyError("infographic", { artifactId });
1144
+ let url = null;
1145
+ for (let i = raw.length - 1; i >= 0; i--) {
1146
+ const item = raw[i];
1147
+ if (Array.isArray(item) && Array.isArray(item[2]) && Array.isArray(item[2][0]) && Array.isArray(item[2][0][1]) && typeof item[2][0][1][0] === "string" && item[2][0][1][0].startsWith("http")) {
1148
+ url = item[2][0][1][0];
1149
+ break;
1150
+ }
1151
+ }
1152
+ if (!url) throw new ArtifactNotReadyError("infographic", { artifactId });
1153
+ return this._fetchMediaWithCookies(url);
1154
+ }
1155
+ /** Get AI-suggested report formats based on notebook content. */
1156
+ async suggestReports(notebookId) {
1157
+ const params = [[2], notebookId];
1158
+ const result = await this.rpc.call(RPCMethod.GET_SUGGESTED_REPORTS, params, {
1159
+ sourcePath: `/notebook/${notebookId}`,
1160
+ allowNull: true,
1161
+ timeoutMs: 12e4
1162
+ });
1163
+ if (!Array.isArray(result) || !result.length) return [];
1164
+ const items = Array.isArray(result[0]) ? result[0] : result;
1165
+ const suggestions = [];
1166
+ for (const item of items) {
1167
+ if (Array.isArray(item) && item.length >= 5) {
1168
+ suggestions.push({
1169
+ title: typeof item[0] === "string" ? item[0] : "",
1170
+ description: typeof item[1] === "string" ? item[1] : "",
1171
+ prompt: typeof item[4] === "string" ? item[4] : "",
1172
+ audienceLevel: typeof item[5] === "number" ? item[5] : 2
1173
+ });
1174
+ }
1175
+ }
1176
+ return suggestions;
1177
+ }
1178
+ /** Revise an individual slide in a completed slide deck using a prompt. */
1179
+ async reviseSlide(notebookId, artifactId, slideIndex, prompt) {
1180
+ if (slideIndex < 0) throw new Error("slideIndex must be >= 0");
1181
+ const params = [[2], artifactId, [[[slideIndex, prompt]]]];
1182
+ const result = await this.rpc.call(RPCMethod.REVISE_SLIDE, params, {
1183
+ sourcePath: `/notebook/${notebookId}`,
1184
+ allowNull: true
1185
+ });
1186
+ return this._parseGenerationResult(result);
1187
+ }
1188
+ /** Get parsed headers and rows from a completed data table artifact. */
1189
+ async getDataTableContent(notebookId, artifactId) {
1190
+ const artifacts = await this._listRaw(notebookId);
1191
+ const raw = artifacts.find(
1192
+ (a) => Array.isArray(a) && a[0] === artifactId && a[2] === ArtifactTypeCode.DATA_TABLE
1193
+ );
1194
+ if (!raw || !Array.isArray(raw) || !Array.isArray(raw[18])) return null;
1195
+ return parseDataTable(raw[18]);
1196
+ }
1082
1197
  // ---------------------------------------------------------------------------
1083
1198
  // Internal
1084
1199
  // ---------------------------------------------------------------------------
@@ -1135,6 +1250,34 @@ ${opts.extraInstructions}` : cfg.prompt;
1135
1250
  return { artifactId: null, status: "failed" };
1136
1251
  }
1137
1252
  };
1253
+ function extractCellText(cell) {
1254
+ if (typeof cell === "string") return cell;
1255
+ if (typeof cell === "number") return "";
1256
+ if (Array.isArray(cell)) return cell.map(extractCellText).join("");
1257
+ return "";
1258
+ }
1259
+ function parseDataTable(rawData) {
1260
+ try {
1261
+ const nav = rawData;
1262
+ const rowsArray = nav[0][0][0][0][4][2];
1263
+ if (!rowsArray?.length) throw new Error("Empty data table");
1264
+ const headers = [];
1265
+ const rows = [];
1266
+ for (let i = 0; i < rowsArray.length; i++) {
1267
+ const rowSection = rowsArray[i];
1268
+ if (!Array.isArray(rowSection) || rowSection.length < 3) continue;
1269
+ const cellArray = rowSection[2];
1270
+ if (!Array.isArray(cellArray)) continue;
1271
+ const values = cellArray.map(extractCellText);
1272
+ if (i === 0) headers.push(...values);
1273
+ else rows.push(values);
1274
+ }
1275
+ if (!headers.length) throw new Error("No headers found");
1276
+ return { headers, rows };
1277
+ } catch (e) {
1278
+ throw new Error(`Failed to parse data table: ${e}`);
1279
+ }
1280
+ }
1138
1281
  function sleep(ms) {
1139
1282
  return new Promise((resolve) => setTimeout(resolve, ms));
1140
1283
  }
@@ -1265,6 +1408,19 @@ var ChatAPI = class {
1265
1408
  }
1266
1409
  return null;
1267
1410
  }
1411
+ /**
1412
+ * Set the chat mode for a notebook. Persists on the server — affects all
1413
+ * subsequent `ask()` calls until changed.
1414
+ */
1415
+ async setMode(notebookId, mode) {
1416
+ const [goal, length] = chatModeToParams(mode);
1417
+ const chatSettings = [[goal], [length]];
1418
+ const params = [notebookId, [[null, null, null, null, null, null, null, chatSettings]]];
1419
+ await this.rpc.call(RPCMethod.RENAME_NOTEBOOK, params, {
1420
+ sourcePath: `/notebook/${notebookId}`,
1421
+ allowNull: true
1422
+ });
1423
+ }
1268
1424
  clearCache(conversationId) {
1269
1425
  if (conversationId) {
1270
1426
  this.conversationCache.delete(conversationId);
@@ -1435,6 +1591,9 @@ var NotebooksAPI = class {
1435
1591
  }
1436
1592
  return "";
1437
1593
  }
1594
+ async removeFromRecent(notebookId) {
1595
+ await this.rpc.call(RPCMethod.REMOVE_RECENTLY_VIEWED, [notebookId], { allowNull: true });
1596
+ }
1438
1597
  async getDescription(notebookId) {
1439
1598
  const params = [notebookId, [2]];
1440
1599
  const result = await this.rpc.call(RPCMethod.SUMMARIZE, params, {
@@ -1471,61 +1630,69 @@ var NotesAPI = class {
1471
1630
  this.rpc = rpc;
1472
1631
  }
1473
1632
  async list(notebookId) {
1474
- const params = [notebookId, [2]];
1475
- const result = await this.rpc.call(RPCMethod.GET_NOTES_AND_MIND_MAPS, params, {
1476
- sourcePath: `/notebook/${notebookId}`
1477
- });
1478
- const notes = [];
1479
- const mindMaps = [];
1480
- if (!Array.isArray(result)) return { notes, mindMaps };
1481
- try {
1482
- const notesData = result[0];
1483
- if (Array.isArray(notesData)) {
1484
- for (const n of notesData) {
1485
- if (Array.isArray(n)) notes.push(parseNote(n));
1486
- }
1487
- }
1488
- const mapsData = result[1];
1489
- if (Array.isArray(mapsData)) {
1490
- for (const m of mapsData) {
1491
- if (Array.isArray(m)) {
1492
- mindMaps.push({
1493
- id: typeof m[0] === "string" ? m[0] : "",
1494
- title: typeof m[2] === "string" ? m[2] : null,
1495
- content: typeof m[1] === "string" ? m[1] : "",
1496
- createdAt: Array.isArray(m[3]) && typeof m[3][0] === "number" ? new Date(m[3][0] * 1e3) : null
1497
- });
1498
- }
1499
- }
1500
- }
1501
- } catch {
1502
- }
1503
- return { notes, mindMaps };
1633
+ const all = await this._fetchAll(notebookId);
1634
+ return all.filter((n) => !this._isMindMap(n.content));
1635
+ }
1636
+ async listMindMaps(notebookId) {
1637
+ const all = await this._fetchAll(notebookId);
1638
+ return all.filter((n) => this._isMindMap(n.content));
1504
1639
  }
1505
1640
  async create(notebookId, content, title) {
1506
- const params = [notebookId, content, title ?? null, [2]];
1507
- const result = await this.rpc.call(RPCMethod.CREATE_NOTE, params, {
1508
- sourcePath: `/notebook/${notebookId}`
1641
+ const createParams = [notebookId, "", [1], null, "New Note"];
1642
+ const result = await this.rpc.call(RPCMethod.CREATE_NOTE, createParams, {
1643
+ sourcePath: `/notebook/${notebookId}`,
1644
+ allowNull: true
1509
1645
  });
1510
- if (Array.isArray(result)) return parseNote(result);
1511
- throw new Error("Could not parse note creation response");
1646
+ const noteId = Array.isArray(result) && Array.isArray(result[0]) && typeof result[0][0] === "string" ? result[0][0] : Array.isArray(result) && typeof result[0] === "string" ? result[0] : null;
1647
+ if (!noteId) throw new Error("CREATE_NOTE did not return a note ID");
1648
+ await this.update(notebookId, noteId, content, title ?? "New Note");
1649
+ return { id: noteId, title: title ?? null, content, createdAt: null, updatedAt: /* @__PURE__ */ new Date() };
1512
1650
  }
1513
1651
  async update(notebookId, noteId, content, title) {
1514
- const params = [notebookId, noteId, content, title ?? null, [2]];
1515
- const result = await this.rpc.call(RPCMethod.UPDATE_NOTE, params, {
1516
- sourcePath: `/notebook/${notebookId}`
1652
+ const params = [notebookId, noteId, [[[content, title ?? "New Note", [], 0]]]];
1653
+ await this.rpc.call(RPCMethod.UPDATE_NOTE, params, {
1654
+ sourcePath: `/notebook/${notebookId}`,
1655
+ allowNull: true
1517
1656
  });
1518
- if (Array.isArray(result)) return parseNote(result);
1519
1657
  return { id: noteId, title: title ?? null, content, createdAt: null, updatedAt: /* @__PURE__ */ new Date() };
1520
1658
  }
1521
1659
  async delete(notebookId, noteId) {
1522
- const params = [notebookId, noteId, [2]];
1660
+ const params = [notebookId, null, [noteId]];
1523
1661
  await this.rpc.call(RPCMethod.DELETE_NOTE, params, {
1524
1662
  sourcePath: `/notebook/${notebookId}`,
1525
1663
  allowNull: true
1526
1664
  });
1527
1665
  return true;
1528
1666
  }
1667
+ async _fetchAll(notebookId) {
1668
+ const result = await this.rpc.call(RPCMethod.GET_NOTES_AND_MIND_MAPS, [notebookId], {
1669
+ sourcePath: `/notebook/${notebookId}`,
1670
+ allowNull: true
1671
+ });
1672
+ if (!Array.isArray(result) || !Array.isArray(result[0])) return [];
1673
+ const notes = [];
1674
+ for (const item of result[0]) {
1675
+ if (!Array.isArray(item) || typeof item[0] !== "string") continue;
1676
+ if (item[1] === null && item[2] === 2) continue;
1677
+ const content = this._extractContent(item);
1678
+ notes.push(this._parseItem(item, notebookId, content));
1679
+ }
1680
+ return notes;
1681
+ }
1682
+ _isMindMap(content) {
1683
+ return content.includes('"children":') || content.includes('"nodes":');
1684
+ }
1685
+ _extractContent(item) {
1686
+ if (typeof item[1] === "string") return item[1];
1687
+ if (Array.isArray(item[1]) && typeof item[1][1] === "string") return item[1][1];
1688
+ return "";
1689
+ }
1690
+ _parseItem(item, _notebookId, content) {
1691
+ const inner = Array.isArray(item[1]) ? item[1] : null;
1692
+ const title = inner && typeof inner[4] === "string" && inner[4] ? inner[4] : null;
1693
+ const createdAt = Array.isArray(item[3]) && typeof item[3][0] === "number" ? new Date(item[3][0] * 1e3) : null;
1694
+ return { id: item[0], title, content, createdAt, updatedAt: null };
1695
+ }
1529
1696
  };
1530
1697
 
1531
1698
  // src/api/research.ts
@@ -1662,7 +1829,7 @@ var ResearchAPI = class {
1662
1829
  const webSources = sources.filter((s) => s.url && !reportSourceSet.has(s));
1663
1830
  if (!webSources.length && !reportSources.length) return [];
1664
1831
  const sourceArray = [
1665
- ...reportSources.map((s) => buildReportEntry(s.title, s.reportMarkdown)),
1832
+ ...reportSources.filter((s) => s.reportMarkdown).map((s) => buildReportEntry(s.title, s.reportMarkdown)),
1666
1833
  ...webSources.map((s) => buildWebEntry(s.url, s.title))
1667
1834
  ];
1668
1835
  const params = [null, [1], effectiveTaskId, notebookId, sourceArray];
@@ -2034,6 +2201,74 @@ var SourcesAPI = class {
2034
2201
  const uploadResult = await uploadResp.text();
2035
2202
  return uploadResult.trim();
2036
2203
  }
2204
+ /** Get the AI-generated Source Guide (summary + keywords) for a source. */
2205
+ async getGuide(notebookId, sourceId) {
2206
+ const params = [[[[sourceId]]]];
2207
+ const result = await this.rpc.call(RPCMethod.GET_SOURCE_GUIDE, params, {
2208
+ sourcePath: `/notebook/${notebookId}`,
2209
+ allowNull: true,
2210
+ timeoutMs: 12e4
2211
+ });
2212
+ let summary = "";
2213
+ let keywords = [];
2214
+ if (Array.isArray(result) && result.length > 0) {
2215
+ const outer = result[0];
2216
+ if (Array.isArray(outer) && outer.length > 0) {
2217
+ const inner = outer[0];
2218
+ if (Array.isArray(inner)) {
2219
+ if (inner.length > 1 && Array.isArray(inner[1]) && typeof inner[1][0] === "string") {
2220
+ summary = inner[1][0];
2221
+ }
2222
+ if (inner.length > 2 && Array.isArray(inner[2]) && Array.isArray(inner[2][0])) {
2223
+ keywords = inner[2][0].filter((k) => typeof k === "string");
2224
+ }
2225
+ }
2226
+ }
2227
+ }
2228
+ return { summary, keywords };
2229
+ }
2230
+ /** Get the full indexed text content of a source. */
2231
+ async getFulltext(notebookId, sourceId) {
2232
+ const params = [[sourceId], [2], [2]];
2233
+ const result = await this.rpc.call(RPCMethod.GET_SOURCE, params, {
2234
+ sourcePath: `/notebook/${notebookId}`,
2235
+ allowNull: true
2236
+ });
2237
+ if (!Array.isArray(result) || !result.length) {
2238
+ throw new Error(`Source ${sourceId} not found in notebook ${notebookId}`);
2239
+ }
2240
+ let title = "";
2241
+ let url = null;
2242
+ if (Array.isArray(result[0]) && result[0].length > 1) {
2243
+ title = typeof result[0][1] === "string" ? result[0][1] : "";
2244
+ const meta = result[0][2];
2245
+ if (Array.isArray(meta) && meta.length > 7 && Array.isArray(meta[7]) && typeof meta[7][0] === "string") {
2246
+ url = meta[7][0];
2247
+ }
2248
+ }
2249
+ let content = "";
2250
+ if (Array.isArray(result[3]) && result[3].length > 0) {
2251
+ const texts = extractAllText(result[3][0]);
2252
+ content = texts.join("\n");
2253
+ }
2254
+ return { sourceId, title, content, url, charCount: content.length };
2255
+ }
2256
+ /** Check if a source has newer content available. Returns true if fresh, false if stale. */
2257
+ async checkFreshness(notebookId, sourceId) {
2258
+ const params = [null, [sourceId], [2]];
2259
+ const result = await this.rpc.call(RPCMethod.CHECK_SOURCE_FRESHNESS, params, {
2260
+ sourcePath: `/notebook/${notebookId}`,
2261
+ allowNull: true
2262
+ });
2263
+ if (result === true) return true;
2264
+ if (result === false) return false;
2265
+ if (Array.isArray(result)) {
2266
+ if (result.length === 0) return true;
2267
+ const first = result[0];
2268
+ if (Array.isArray(first) && first.length > 1 && first[1] === true) return true;
2269
+ }
2270
+ return false;
2271
+ }
2037
2272
  async delete(notebookId, sourceId) {
2038
2273
  const params = [notebookId, [sourceId], [2]];
2039
2274
  await this.rpc.call(RPCMethod.DELETE_SOURCE, params, {
@@ -2088,6 +2323,15 @@ function extractSourceId(result) {
2088
2323
  console.log("extractSourceId debug info: could not parse:", JSON.stringify(result, null, 2));
2089
2324
  throw new Error("Could not extract source ID from API response");
2090
2325
  }
2326
+ function extractAllText(data, maxDepth = 100) {
2327
+ if (maxDepth <= 0) return [];
2328
+ const texts = [];
2329
+ for (const item of data) {
2330
+ if (typeof item === "string" && item.length > 0) texts.push(item);
2331
+ else if (Array.isArray(item)) texts.push(...extractAllText(item, maxDepth - 1));
2332
+ }
2333
+ return texts;
2334
+ }
2091
2335
  function sleep2(ms) {
2092
2336
  return new Promise((resolve) => setTimeout(resolve, ms));
2093
2337
  }
@@ -2392,9 +2636,9 @@ var NotebookLMClient = class _NotebookLMClient {
2392
2636
  const rpc = new RPCCore(auth, opts.timeoutMs);
2393
2637
  this.notebooks = new NotebooksAPI(rpc);
2394
2638
  this.sources = new SourcesAPI(rpc, auth);
2395
- this.artifacts = new ArtifactsAPI(rpc, auth);
2396
- this.chat = new ChatAPI(rpc, auth);
2397
2639
  this.notes = new NotesAPI(rpc);
2640
+ this.artifacts = new ArtifactsAPI(rpc, auth, this.notes);
2641
+ this.chat = new ChatAPI(rpc, auth);
2398
2642
  this.research = new ResearchAPI(rpc);
2399
2643
  this.settings = new SettingsAPI(rpc);
2400
2644
  this.sharing = new SharingAPI(rpc);
@@ -2430,6 +2674,6 @@ var NotebookLMClient = class _NotebookLMClient {
2430
2674
  init_enums();
2431
2675
  init_errors();
2432
2676
 
2433
- export { ArtifactDownloadError, ArtifactError, ArtifactNotFoundError, ArtifactNotReadyError, ArtifactParseError, ArtifactTypeCode, ArtifactsAPI, AudioFormat, AudioLength, AuthError, ChatAPI, ChatError, ClientError, ExportType, InfographicDetail, InfographicOrientation, InfographicStyle, NetworkError, NotebookError, NotebookLMClient, NotebookLMError, NotebookNotFoundError, NotebooksAPI, NotesAPI, QuizDifficulty, QuizQuantity, RPCError, RPCMethod, RPCTimeoutError, RateLimitError, ResearchAPI, ServerError, SettingsAPI, ShareAccess, SharePermission, ShareViewLevel, SharingAPI, SlideDeckFormat, SlideDeckLength, SourceAddError, SourceError, SourceNotFoundError, SourceProcessingError, SourceTimeoutError, SourcesAPI, VideoFormat, VideoStyle, connect };
2677
+ export { ArtifactDownloadError, ArtifactError, ArtifactNotFoundError, ArtifactNotReadyError, ArtifactParseError, ArtifactTypeCode, ArtifactsAPI, AudioFormat, AudioLength, AuthError, ChatAPI, ChatError, ChatMode, ClientError, ExportType, InfographicDetail, InfographicOrientation, InfographicStyle, NetworkError, NotebookError, NotebookLMClient, NotebookLMError, NotebookNotFoundError, NotebooksAPI, NotesAPI, QuizDifficulty, QuizQuantity, RPCError, RPCMethod, RPCTimeoutError, RateLimitError, ResearchAPI, ServerError, SettingsAPI, ShareAccess, SharePermission, ShareViewLevel, SharingAPI, SlideDeckFormat, SlideDeckLength, SourceAddError, SourceError, SourceNotFoundError, SourceProcessingError, SourceTimeoutError, SourcesAPI, VideoFormat, VideoStyle, connect };
2434
2678
  //# sourceMappingURL=index.js.map
2435
2679
  //# sourceMappingURL=index.js.map