notebooklm-sdk 0.1.8 → 0.2.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
@@ -455,10 +455,8 @@ function loadCookiesFromFile(filePath) {
455
455
  try {
456
456
  raw = fs.readFileSync(filePath, "utf-8");
457
457
  } catch {
458
- throw new exports.AuthError(
459
- `Session file not found: ${filePath}
460
- Run: npx notebooklm-sdk login`
461
- );
458
+ throw new exports.AuthError(`Session file not found: ${filePath}
459
+ Run: npx notebooklm-sdk login`);
462
460
  }
463
461
  return extractCookiesFromStorageState(JSON.parse(raw));
464
462
  }
@@ -592,9 +590,7 @@ async function connect(opts = {}) {
592
590
  } else if (envCookies) {
593
591
  cookieMap = loadCookiesFromString(envCookies);
594
592
  } else {
595
- throw new exports.AuthError(
596
- "No session found. Run: npx notebooklm-sdk login"
597
- );
593
+ throw new exports.AuthError("No session found. Run: npx notebooklm-sdk login");
598
594
  }
599
595
  }
600
596
  const { csrfToken, sessionId } = await fetchTokens(cookieMap);
@@ -729,26 +725,6 @@ function parseArtifact(data, notebookId) {
729
725
  _raw: Array.isArray(data) ? data : []
730
726
  };
731
727
  }
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
728
 
753
729
  // src/api/artifacts.ts
754
730
  function tripleNest(ids) {
@@ -758,18 +734,13 @@ function doubleNest(ids) {
758
734
  return ids.map((id) => [id]);
759
735
  }
760
736
  var ArtifactsAPI = class {
761
- constructor(rpc, auth) {
737
+ constructor(rpc, auth, notes) {
762
738
  this.rpc = rpc;
763
739
  this.auth = auth;
740
+ this.notes = notes;
764
741
  }
765
742
  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;
743
+ const rawList = await this._listRaw(notebookId);
773
744
  const artifacts = [];
774
745
  for (const item of rawList) {
775
746
  if (Array.isArray(item)) {
@@ -781,6 +752,15 @@ var ArtifactsAPI = class {
781
752
  }
782
753
  return artifacts;
783
754
  }
755
+ async _listRaw(notebookId) {
756
+ const params = [[2], notebookId, 'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"'];
757
+ const result = await this.rpc.call(exports.RPCMethod.LIST_ARTIFACTS, params, {
758
+ sourcePath: `/notebook/${notebookId}`,
759
+ allowNull: true
760
+ });
761
+ if (!Array.isArray(result) || !result.length) return [];
762
+ return Array.isArray(result[0]) ? result[0] : result;
763
+ }
784
764
  async get(notebookId, artifactId) {
785
765
  const artifacts = await this.list(notebookId);
786
766
  return artifacts.find((a) => a.id === artifactId) ?? null;
@@ -960,6 +940,37 @@ var ArtifactsAPI = class {
960
940
  ];
961
941
  return this._callGenerate(notebookId, params);
962
942
  }
943
+ async createDataTable(notebookId, opts = {}) {
944
+ const language = opts.language ?? "en";
945
+ const sourceIds = opts.sourceIds ?? await this.rpc.getSourceIds(notebookId);
946
+ const triple = tripleNest(sourceIds);
947
+ const params = [
948
+ [2],
949
+ notebookId,
950
+ [
951
+ null,
952
+ null,
953
+ exports.ArtifactTypeCode.DATA_TABLE,
954
+ triple,
955
+ null,
956
+ null,
957
+ null,
958
+ null,
959
+ null,
960
+ null,
961
+ null,
962
+ null,
963
+ null,
964
+ null,
965
+ null,
966
+ null,
967
+ null,
968
+ null,
969
+ [null, [opts.instructions ?? null, language]]
970
+ ]
971
+ ];
972
+ return this._callGenerate(notebookId, params);
973
+ }
963
974
  async createReport(notebookId, opts = {}) {
964
975
  const format = opts.format ?? "briefing_doc";
965
976
  const language = opts.language ?? "en";
@@ -1025,7 +1036,15 @@ ${opts.extraInstructions}` : cfg.prompt;
1025
1036
  sourcePath: `/notebook/${notebookId}`,
1026
1037
  allowNull: true
1027
1038
  });
1028
- return this._parseGenerationResult(result);
1039
+ const mindMapJson = Array.isArray(result) && Array.isArray(result[0]) && typeof result[0][0] === "string" ? result[0][0] : null;
1040
+ if (!mindMapJson) throw new Error("Mind map generation returned no content");
1041
+ let title = "Mind Map";
1042
+ try {
1043
+ const parsed = JSON.parse(mindMapJson);
1044
+ if (typeof parsed["name"] === "string") title = parsed["name"];
1045
+ } catch {
1046
+ }
1047
+ return this.notes.create(notebookId, mindMapJson, title);
1029
1048
  }
1030
1049
  // ---------------------------------------------------------------------------
1031
1050
  // Polling / download
@@ -1081,6 +1100,48 @@ ${opts.extraInstructions}` : cfg.prompt;
1081
1100
  }
1082
1101
  return null;
1083
1102
  }
1103
+ /** Download a completed slide deck as PDF or PPTX. Returns a Buffer. */
1104
+ async downloadSlideDeck(notebookId, artifactId, format = "pdf") {
1105
+ const rawList = await this._listRaw(notebookId);
1106
+ const raw = rawList.find(
1107
+ (a) => a[0] === artifactId && a[2] === exports.ArtifactTypeCode.SLIDE_DECK
1108
+ );
1109
+ if (!raw) throw new exports.ArtifactNotReadyError("slide_deck", { artifactId });
1110
+ const metadata = raw[16];
1111
+ if (!Array.isArray(metadata)) throw new exports.ArtifactNotReadyError("slide_deck", { artifactId });
1112
+ const url = format === "pptx" ? metadata[4] : metadata[3];
1113
+ if (typeof url !== "string" || !url.startsWith("http")) {
1114
+ throw new exports.ArtifactNotReadyError("slide_deck", { artifactId, status: `no ${format} url` });
1115
+ }
1116
+ return this._fetchMediaWithCookies(url);
1117
+ }
1118
+ /** Download a completed infographic as PNG. Returns a Buffer. */
1119
+ async downloadInfographic(notebookId, artifactId) {
1120
+ const rawList = await this._listRaw(notebookId);
1121
+ const raw = rawList.find(
1122
+ (a) => a[0] === artifactId && a[2] === exports.ArtifactTypeCode.INFOGRAPHIC
1123
+ );
1124
+ if (!raw) throw new exports.ArtifactNotReadyError("infographic", { artifactId });
1125
+ let url = null;
1126
+ for (let i = raw.length - 1; i >= 0; i--) {
1127
+ const item = raw[i];
1128
+ 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")) {
1129
+ url = item[2][0][1][0];
1130
+ break;
1131
+ }
1132
+ }
1133
+ if (!url) throw new exports.ArtifactNotReadyError("infographic", { artifactId });
1134
+ return this._fetchMediaWithCookies(url);
1135
+ }
1136
+ /** Get parsed headers and rows from a completed data table artifact. */
1137
+ async getDataTableContent(notebookId, artifactId) {
1138
+ const artifacts = await this._listRaw(notebookId);
1139
+ const raw = artifacts.find(
1140
+ (a) => Array.isArray(a) && a[0] === artifactId && a[2] === exports.ArtifactTypeCode.DATA_TABLE
1141
+ );
1142
+ if (!raw || !Array.isArray(raw) || !Array.isArray(raw[18])) return null;
1143
+ return parseDataTable(raw[18]);
1144
+ }
1084
1145
  // ---------------------------------------------------------------------------
1085
1146
  // Internal
1086
1147
  // ---------------------------------------------------------------------------
@@ -1137,6 +1198,34 @@ ${opts.extraInstructions}` : cfg.prompt;
1137
1198
  return { artifactId: null, status: "failed" };
1138
1199
  }
1139
1200
  };
1201
+ function extractCellText(cell) {
1202
+ if (typeof cell === "string") return cell;
1203
+ if (typeof cell === "number") return "";
1204
+ if (Array.isArray(cell)) return cell.map(extractCellText).join("");
1205
+ return "";
1206
+ }
1207
+ function parseDataTable(rawData) {
1208
+ try {
1209
+ const nav = rawData;
1210
+ const rowsArray = nav[0][0][0][0][4][2];
1211
+ if (!rowsArray?.length) throw new Error("Empty data table");
1212
+ const headers = [];
1213
+ const rows = [];
1214
+ for (let i = 0; i < rowsArray.length; i++) {
1215
+ const rowSection = rowsArray[i];
1216
+ if (!Array.isArray(rowSection) || rowSection.length < 3) continue;
1217
+ const cellArray = rowSection[2];
1218
+ if (!Array.isArray(cellArray)) continue;
1219
+ const values = cellArray.map(extractCellText);
1220
+ if (i === 0) headers.push(...values);
1221
+ else rows.push(values);
1222
+ }
1223
+ if (!headers.length) throw new Error("No headers found");
1224
+ return { headers, rows };
1225
+ } catch (e) {
1226
+ throw new Error(`Failed to parse data table: ${e}`);
1227
+ }
1228
+ }
1140
1229
  function sleep(ms) {
1141
1230
  return new Promise((resolve) => setTimeout(resolve, ms));
1142
1231
  }
@@ -1473,61 +1562,69 @@ var NotesAPI = class {
1473
1562
  this.rpc = rpc;
1474
1563
  }
1475
1564
  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 };
1565
+ const all = await this._fetchAll(notebookId);
1566
+ return all.filter((n) => !this._isMindMap(n.content));
1567
+ }
1568
+ async listMindMaps(notebookId) {
1569
+ const all = await this._fetchAll(notebookId);
1570
+ return all.filter((n) => this._isMindMap(n.content));
1506
1571
  }
1507
1572
  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}`
1573
+ const createParams = [notebookId, "", [1], null, "New Note"];
1574
+ const result = await this.rpc.call(exports.RPCMethod.CREATE_NOTE, createParams, {
1575
+ sourcePath: `/notebook/${notebookId}`,
1576
+ allowNull: true
1511
1577
  });
1512
- if (Array.isArray(result)) return parseNote(result);
1513
- throw new Error("Could not parse note creation response");
1578
+ 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;
1579
+ if (!noteId) throw new Error("CREATE_NOTE did not return a note ID");
1580
+ await this.update(notebookId, noteId, content, title ?? "New Note");
1581
+ return { id: noteId, title: title ?? null, content, createdAt: null, updatedAt: /* @__PURE__ */ new Date() };
1514
1582
  }
1515
1583
  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}`
1584
+ const params = [notebookId, noteId, [[[content, title ?? "New Note", [], 0]]]];
1585
+ await this.rpc.call(exports.RPCMethod.UPDATE_NOTE, params, {
1586
+ sourcePath: `/notebook/${notebookId}`,
1587
+ allowNull: true
1519
1588
  });
1520
- if (Array.isArray(result)) return parseNote(result);
1521
1589
  return { id: noteId, title: title ?? null, content, createdAt: null, updatedAt: /* @__PURE__ */ new Date() };
1522
1590
  }
1523
1591
  async delete(notebookId, noteId) {
1524
- const params = [notebookId, noteId, [2]];
1592
+ const params = [notebookId, null, [noteId]];
1525
1593
  await this.rpc.call(exports.RPCMethod.DELETE_NOTE, params, {
1526
1594
  sourcePath: `/notebook/${notebookId}`,
1527
1595
  allowNull: true
1528
1596
  });
1529
1597
  return true;
1530
1598
  }
1599
+ async _fetchAll(notebookId) {
1600
+ const result = await this.rpc.call(exports.RPCMethod.GET_NOTES_AND_MIND_MAPS, [notebookId], {
1601
+ sourcePath: `/notebook/${notebookId}`,
1602
+ allowNull: true
1603
+ });
1604
+ if (!Array.isArray(result) || !Array.isArray(result[0])) return [];
1605
+ const notes = [];
1606
+ for (const item of result[0]) {
1607
+ if (!Array.isArray(item) || typeof item[0] !== "string") continue;
1608
+ if (item[1] === null && item[2] === 2) continue;
1609
+ const content = this._extractContent(item);
1610
+ notes.push(this._parseItem(item, notebookId, content));
1611
+ }
1612
+ return notes;
1613
+ }
1614
+ _isMindMap(content) {
1615
+ return content.includes('"children":') || content.includes('"nodes":');
1616
+ }
1617
+ _extractContent(item) {
1618
+ if (typeof item[1] === "string") return item[1];
1619
+ if (Array.isArray(item[1]) && typeof item[1][1] === "string") return item[1][1];
1620
+ return "";
1621
+ }
1622
+ _parseItem(item, _notebookId, content) {
1623
+ const inner = Array.isArray(item[1]) ? item[1] : null;
1624
+ const title = inner && typeof inner[4] === "string" && inner[4] ? inner[4] : null;
1625
+ const createdAt = Array.isArray(item[3]) && typeof item[3][0] === "number" ? new Date(item[3][0] * 1e3) : null;
1626
+ return { id: item[0], title, content, createdAt, updatedAt: null };
1627
+ }
1531
1628
  };
1532
1629
 
1533
1630
  // src/api/research.ts
@@ -1664,7 +1761,7 @@ var ResearchAPI = class {
1664
1761
  const webSources = sources.filter((s) => s.url && !reportSourceSet.has(s));
1665
1762
  if (!webSources.length && !reportSources.length) return [];
1666
1763
  const sourceArray = [
1667
- ...reportSources.map((s) => buildReportEntry(s.title, s.reportMarkdown)),
1764
+ ...reportSources.filter((s) => s.reportMarkdown).map((s) => buildReportEntry(s.title, s.reportMarkdown)),
1668
1765
  ...webSources.map((s) => buildWebEntry(s.url, s.title))
1669
1766
  ];
1670
1767
  const params = [null, [1], effectiveTaskId, notebookId, sourceArray];
@@ -2394,9 +2491,9 @@ var NotebookLMClient = class _NotebookLMClient {
2394
2491
  const rpc = new RPCCore(auth, opts.timeoutMs);
2395
2492
  this.notebooks = new NotebooksAPI(rpc);
2396
2493
  this.sources = new SourcesAPI(rpc, auth);
2397
- this.artifacts = new ArtifactsAPI(rpc, auth);
2398
- this.chat = new ChatAPI(rpc, auth);
2399
2494
  this.notes = new NotesAPI(rpc);
2495
+ this.artifacts = new ArtifactsAPI(rpc, auth, this.notes);
2496
+ this.chat = new ChatAPI(rpc, auth);
2400
2497
  this.research = new ResearchAPI(rpc);
2401
2498
  this.settings = new SettingsAPI(rpc);
2402
2499
  this.sharing = new SharingAPI(rpc);