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