@jackwener/opencli 1.5.6 → 1.5.8

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 (338) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +4 -2
  3. package/README.zh-CN.md +4 -1
  4. package/SKILL.md +879 -0
  5. package/dist/browser/cdp.d.ts +1 -0
  6. package/dist/browser/cdp.js +30 -27
  7. package/dist/browser/daemon-client.d.ts +7 -1
  8. package/dist/browser/daemon-client.js +3 -0
  9. package/dist/browser/dom-helpers.js +1 -0
  10. package/dist/browser/dom-helpers.test.js +14 -1
  11. package/dist/browser/mcp.js +18 -13
  12. package/dist/browser/page.js +22 -2
  13. package/dist/browser/page.test.d.ts +1 -0
  14. package/dist/browser/page.test.js +44 -0
  15. package/dist/browser/stealth.js +198 -0
  16. package/dist/browser/stealth.test.d.ts +1 -0
  17. package/dist/browser/stealth.test.js +134 -0
  18. package/dist/browser.test.js +1 -1
  19. package/dist/build-manifest.d.ts +1 -0
  20. package/dist/build-manifest.js +5 -1
  21. package/dist/build-manifest.test.js +2 -0
  22. package/dist/cli-manifest.json +544 -137
  23. package/dist/cli.js +20 -3
  24. package/dist/clis/antigravity/serve.d.ts +1 -1
  25. package/dist/clis/antigravity/serve.js +5 -8
  26. package/dist/clis/bilibili/subtitle.js +4 -0
  27. package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
  28. package/dist/clis/bilibili/subtitle.test.js +48 -0
  29. package/dist/clis/chatwise/ask.js +0 -2
  30. package/dist/clis/chatwise/export.js +0 -2
  31. package/dist/clis/chatwise/history.js +0 -2
  32. package/dist/clis/chatwise/model.js +0 -2
  33. package/dist/clis/chatwise/new.js +1 -2
  34. package/dist/clis/chatwise/read.js +0 -2
  35. package/dist/clis/chatwise/screenshot.js +1 -2
  36. package/dist/clis/chatwise/send.js +0 -2
  37. package/dist/clis/chatwise/status.js +1 -2
  38. package/dist/clis/ctrip/search.d.ts +13 -0
  39. package/dist/clis/ctrip/search.js +73 -48
  40. package/dist/clis/ctrip/search.test.d.ts +1 -0
  41. package/dist/clis/ctrip/search.test.js +64 -0
  42. package/dist/clis/douyin/_shared/sts2.js +8 -2
  43. package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
  44. package/dist/clis/douyin/_shared/sts2.test.js +27 -0
  45. package/dist/clis/douyin/activities.js +4 -2
  46. package/dist/clis/douyin/activities.test.js +34 -1
  47. package/dist/clis/douyin/collections.js +1 -1
  48. package/dist/clis/douyin/collections.test.js +24 -2
  49. package/dist/clis/douyin/draft.d.ts +8 -11
  50. package/dist/clis/douyin/draft.js +302 -185
  51. package/dist/clis/douyin/draft.test.d.ts +1 -1
  52. package/dist/clis/douyin/draft.test.js +357 -2
  53. package/dist/clis/douyin/hashtag.js +9 -2
  54. package/dist/clis/douyin/hashtag.test.js +35 -2
  55. package/dist/clis/douyin/profile.js +1 -1
  56. package/dist/clis/douyin/profile.test.js +36 -1
  57. package/dist/clis/douyin/videos.js +22 -5
  58. package/dist/clis/douyin/videos.test.js +45 -2
  59. package/dist/clis/facebook/search.test.d.ts +5 -0
  60. package/dist/clis/facebook/search.test.js +60 -0
  61. package/dist/clis/facebook/search.yaml +4 -3
  62. package/dist/clis/instagram/download.d.ts +16 -0
  63. package/dist/clis/instagram/download.js +225 -0
  64. package/dist/clis/instagram/download.test.d.ts +1 -0
  65. package/dist/clis/instagram/download.test.js +118 -0
  66. package/dist/clis/notebooklm/bind-current.d.ts +1 -0
  67. package/dist/clis/notebooklm/bind-current.js +29 -0
  68. package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
  69. package/dist/clis/notebooklm/bind-current.test.js +35 -0
  70. package/dist/clis/notebooklm/binding.test.d.ts +1 -0
  71. package/dist/clis/notebooklm/binding.test.js +44 -0
  72. package/dist/clis/notebooklm/compat.test.d.ts +3 -0
  73. package/dist/clis/notebooklm/compat.test.js +16 -0
  74. package/dist/clis/notebooklm/current.d.ts +1 -0
  75. package/dist/clis/notebooklm/current.js +28 -0
  76. package/dist/clis/notebooklm/get.d.ts +1 -0
  77. package/dist/clis/notebooklm/get.js +37 -0
  78. package/dist/clis/notebooklm/history.d.ts +1 -0
  79. package/dist/clis/notebooklm/history.js +25 -0
  80. package/dist/clis/notebooklm/history.test.d.ts +1 -0
  81. package/dist/clis/notebooklm/history.test.js +58 -0
  82. package/dist/clis/notebooklm/list.d.ts +1 -0
  83. package/dist/clis/notebooklm/list.js +35 -0
  84. package/dist/clis/notebooklm/note-list.d.ts +1 -0
  85. package/dist/clis/notebooklm/note-list.js +28 -0
  86. package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
  87. package/dist/clis/notebooklm/note-list.test.js +56 -0
  88. package/dist/clis/notebooklm/notes-get.d.ts +1 -0
  89. package/dist/clis/notebooklm/notes-get.js +47 -0
  90. package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
  91. package/dist/clis/notebooklm/notes-get.test.js +72 -0
  92. package/dist/clis/notebooklm/rpc.d.ts +36 -0
  93. package/dist/clis/notebooklm/rpc.js +189 -0
  94. package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
  95. package/dist/clis/notebooklm/rpc.test.js +105 -0
  96. package/dist/clis/notebooklm/shared.d.ts +87 -0
  97. package/dist/clis/notebooklm/shared.js +3 -0
  98. package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
  99. package/dist/clis/notebooklm/source-fulltext.js +44 -0
  100. package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
  101. package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
  102. package/dist/clis/notebooklm/source-get.d.ts +1 -0
  103. package/dist/clis/notebooklm/source-get.js +40 -0
  104. package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
  105. package/dist/clis/notebooklm/source-get.test.js +84 -0
  106. package/dist/clis/notebooklm/source-guide.d.ts +1 -0
  107. package/dist/clis/notebooklm/source-guide.js +44 -0
  108. package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
  109. package/dist/clis/notebooklm/source-guide.test.js +104 -0
  110. package/dist/clis/notebooklm/source-list.d.ts +1 -0
  111. package/dist/clis/notebooklm/source-list.js +30 -0
  112. package/dist/clis/notebooklm/status.d.ts +1 -0
  113. package/dist/clis/notebooklm/status.js +31 -0
  114. package/dist/clis/notebooklm/summary.d.ts +1 -0
  115. package/dist/clis/notebooklm/summary.js +30 -0
  116. package/dist/clis/notebooklm/summary.test.d.ts +1 -0
  117. package/dist/clis/notebooklm/summary.test.js +78 -0
  118. package/dist/clis/notebooklm/utils.d.ts +37 -0
  119. package/dist/clis/notebooklm/utils.js +739 -0
  120. package/dist/clis/notebooklm/utils.test.d.ts +1 -0
  121. package/dist/clis/notebooklm/utils.test.js +390 -0
  122. package/dist/clis/substack/utils.d.ts +4 -0
  123. package/dist/clis/substack/utils.js +8 -2
  124. package/dist/clis/substack/utils.test.d.ts +1 -0
  125. package/dist/clis/substack/utils.test.js +46 -0
  126. package/dist/clis/v2ex/hot.yaml +4 -1
  127. package/dist/clis/v2ex/latest.yaml +4 -1
  128. package/dist/clis/v2ex/topic.yaml +6 -1
  129. package/dist/clis/weixin/download.d.ts +9 -0
  130. package/dist/clis/weixin/download.js +76 -6
  131. package/dist/clis/weread/book.js +108 -2
  132. package/dist/clis/weread/commands.test.js +262 -152
  133. package/dist/clis/weread/utils.d.ts +10 -0
  134. package/dist/clis/weread/utils.js +27 -7
  135. package/dist/clis/xiaohongshu/comments.d.ts +3 -0
  136. package/dist/clis/xiaohongshu/comments.js +76 -17
  137. package/dist/clis/xiaohongshu/comments.test.js +70 -9
  138. package/dist/clis/xiaohongshu/download.d.ts +4 -1
  139. package/dist/clis/xiaohongshu/download.js +83 -22
  140. package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
  141. package/dist/clis/xiaohongshu/download.test.js +75 -0
  142. package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
  143. package/dist/clis/xiaohongshu/note-helpers.js +23 -0
  144. package/dist/clis/xiaohongshu/note.d.ts +7 -0
  145. package/dist/clis/xiaohongshu/note.js +76 -0
  146. package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
  147. package/dist/clis/xiaohongshu/note.test.js +136 -0
  148. package/dist/clis/xiaohongshu/search.js +9 -0
  149. package/dist/clis/xiaohongshu/search.test.js +10 -4
  150. package/dist/clis/youtube/search.js +57 -17
  151. package/dist/clis/zhihu/question.js +19 -17
  152. package/dist/clis/zhihu/question.test.d.ts +1 -0
  153. package/dist/clis/zhihu/question.test.js +54 -0
  154. package/dist/commanderAdapter.js +9 -0
  155. package/dist/commanderAdapter.test.js +25 -0
  156. package/dist/commands/daemon.d.ts +9 -0
  157. package/dist/commands/daemon.js +124 -0
  158. package/dist/commands/daemon.test.d.ts +1 -0
  159. package/dist/commands/daemon.test.js +185 -0
  160. package/dist/completion.js +3 -1
  161. package/dist/constants.d.ts +2 -0
  162. package/dist/constants.js +2 -0
  163. package/dist/daemon.d.ts +1 -1
  164. package/dist/daemon.js +25 -14
  165. package/dist/daemon.test.d.ts +1 -0
  166. package/dist/daemon.test.js +65 -0
  167. package/dist/discovery.d.ts +9 -0
  168. package/dist/discovery.js +47 -2
  169. package/dist/electron-apps.d.ts +29 -0
  170. package/dist/electron-apps.js +65 -0
  171. package/dist/electron-apps.test.d.ts +1 -0
  172. package/dist/electron-apps.test.js +43 -0
  173. package/dist/engine.test.js +41 -9
  174. package/dist/execution.js +20 -16
  175. package/dist/extension-manifest-regression.test.js +1 -0
  176. package/dist/idle-manager.d.ts +19 -0
  177. package/dist/idle-manager.js +54 -0
  178. package/dist/launcher.d.ts +36 -0
  179. package/dist/launcher.js +152 -0
  180. package/dist/launcher.test.d.ts +1 -0
  181. package/dist/launcher.test.js +57 -0
  182. package/dist/main.js +3 -3
  183. package/dist/registry.d.ts +1 -0
  184. package/dist/registry.js +31 -3
  185. package/dist/registry.test.js +13 -0
  186. package/dist/runtime.d.ts +5 -3
  187. package/dist/runtime.js +12 -5
  188. package/dist/serialization.d.ts +1 -0
  189. package/dist/serialization.js +3 -0
  190. package/dist/serialization.test.js +17 -1
  191. package/dist/tui.d.ts +7 -0
  192. package/dist/tui.js +52 -0
  193. package/dist/tui.test.d.ts +1 -0
  194. package/dist/tui.test.js +19 -0
  195. package/dist/weixin-download.test.js +14 -0
  196. package/docs/.vitepress/config.mts +1 -0
  197. package/docs/adapters/browser/notebooklm.md +69 -0
  198. package/docs/adapters/browser/xiaohongshu.md +19 -10
  199. package/docs/adapters/index.md +67 -66
  200. package/docs/guide/browser-bridge.md +12 -0
  201. package/docs/guide/troubleshooting.md +9 -4
  202. package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
  203. package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
  204. package/docs/zh/guide/browser-bridge.md +12 -0
  205. package/extension/dist/background.js +250 -11
  206. package/extension/manifest.json +2 -1
  207. package/extension/src/background.test.ts +202 -2
  208. package/extension/src/background.ts +175 -10
  209. package/extension/src/cdp.test.ts +75 -0
  210. package/extension/src/cdp.ts +89 -3
  211. package/extension/src/protocol.ts +7 -5
  212. package/package.json +1 -1
  213. package/src/browser/cdp.ts +24 -17
  214. package/src/browser/daemon-client.ts +7 -1
  215. package/src/browser/dom-helpers.test.ts +15 -1
  216. package/src/browser/dom-helpers.ts +1 -0
  217. package/src/browser/mcp.ts +18 -13
  218. package/src/browser/page.test.ts +58 -0
  219. package/src/browser/page.ts +18 -2
  220. package/src/browser/stealth.test.ts +153 -0
  221. package/src/browser/stealth.ts +198 -0
  222. package/src/browser.test.ts +1 -1
  223. package/src/build-manifest.test.ts +2 -0
  224. package/src/build-manifest.ts +6 -1
  225. package/src/cli.ts +21 -3
  226. package/src/clis/antigravity/SKILL.md +3 -12
  227. package/src/clis/antigravity/serve.ts +5 -10
  228. package/src/clis/bilibili/subtitle.test.ts +60 -0
  229. package/src/clis/bilibili/subtitle.ts +4 -0
  230. package/src/clis/chatwise/ask.ts +0 -2
  231. package/src/clis/chatwise/export.ts +0 -2
  232. package/src/clis/chatwise/history.ts +0 -2
  233. package/src/clis/chatwise/model.ts +0 -2
  234. package/src/clis/chatwise/new.ts +1 -2
  235. package/src/clis/chatwise/read.ts +0 -2
  236. package/src/clis/chatwise/screenshot.ts +1 -2
  237. package/src/clis/chatwise/send.ts +0 -2
  238. package/src/clis/chatwise/status.ts +1 -2
  239. package/src/clis/ctrip/search.test.ts +73 -0
  240. package/src/clis/ctrip/search.ts +97 -47
  241. package/src/clis/douyin/_shared/sts2.test.ts +31 -0
  242. package/src/clis/douyin/_shared/sts2.ts +11 -3
  243. package/src/clis/douyin/activities.test.ts +41 -1
  244. package/src/clis/douyin/activities.ts +12 -3
  245. package/src/clis/douyin/collections.test.ts +35 -2
  246. package/src/clis/douyin/collections.ts +1 -1
  247. package/src/clis/douyin/draft.test.ts +444 -2
  248. package/src/clis/douyin/draft.ts +382 -218
  249. package/src/clis/douyin/hashtag.test.ts +42 -2
  250. package/src/clis/douyin/hashtag.ts +11 -3
  251. package/src/clis/douyin/profile.test.ts +43 -1
  252. package/src/clis/douyin/profile.ts +9 -2
  253. package/src/clis/douyin/videos.test.ts +52 -2
  254. package/src/clis/douyin/videos.ts +49 -15
  255. package/src/clis/facebook/search.test.ts +70 -0
  256. package/src/clis/facebook/search.yaml +4 -3
  257. package/src/clis/instagram/download.test.ts +159 -0
  258. package/src/clis/instagram/download.ts +286 -0
  259. package/src/clis/notebooklm/bind-current.test.ts +43 -0
  260. package/src/clis/notebooklm/bind-current.ts +36 -0
  261. package/src/clis/notebooklm/binding.test.ts +53 -0
  262. package/src/clis/notebooklm/compat.test.ts +19 -0
  263. package/src/clis/notebooklm/current.ts +38 -0
  264. package/src/clis/notebooklm/get.ts +53 -0
  265. package/src/clis/notebooklm/history.test.ts +70 -0
  266. package/src/clis/notebooklm/history.ts +36 -0
  267. package/src/clis/notebooklm/list.ts +40 -0
  268. package/src/clis/notebooklm/note-list.test.ts +64 -0
  269. package/src/clis/notebooklm/note-list.ts +42 -0
  270. package/src/clis/notebooklm/notes-get.test.ts +88 -0
  271. package/src/clis/notebooklm/notes-get.ts +67 -0
  272. package/src/clis/notebooklm/rpc.test.ts +126 -0
  273. package/src/clis/notebooklm/rpc.ts +286 -0
  274. package/src/clis/notebooklm/shared.ts +98 -0
  275. package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
  276. package/src/clis/notebooklm/source-fulltext.ts +69 -0
  277. package/src/clis/notebooklm/source-get.test.ts +100 -0
  278. package/src/clis/notebooklm/source-get.ts +60 -0
  279. package/src/clis/notebooklm/source-guide.test.ts +121 -0
  280. package/src/clis/notebooklm/source-guide.ts +69 -0
  281. package/src/clis/notebooklm/source-list.ts +45 -0
  282. package/src/clis/notebooklm/status.ts +34 -0
  283. package/src/clis/notebooklm/summary.test.ts +94 -0
  284. package/src/clis/notebooklm/summary.ts +45 -0
  285. package/src/clis/notebooklm/utils.test.ts +446 -0
  286. package/src/clis/notebooklm/utils.ts +893 -0
  287. package/src/clis/substack/utils.test.ts +54 -0
  288. package/src/clis/substack/utils.ts +10 -2
  289. package/src/clis/v2ex/hot.yaml +4 -1
  290. package/src/clis/v2ex/latest.yaml +4 -1
  291. package/src/clis/v2ex/topic.yaml +6 -1
  292. package/src/clis/weixin/download.ts +95 -6
  293. package/src/clis/weread/book.ts +142 -2
  294. package/src/clis/weread/commands.test.ts +314 -154
  295. package/src/clis/weread/utils.ts +33 -4
  296. package/src/clis/xiaohongshu/comments.test.ts +85 -9
  297. package/src/clis/xiaohongshu/comments.ts +76 -17
  298. package/src/clis/xiaohongshu/download.test.ts +96 -0
  299. package/src/clis/xiaohongshu/download.ts +83 -22
  300. package/src/clis/xiaohongshu/note-helpers.ts +25 -0
  301. package/src/clis/xiaohongshu/note.test.ts +164 -0
  302. package/src/clis/xiaohongshu/note.ts +86 -0
  303. package/src/clis/xiaohongshu/search.test.ts +11 -4
  304. package/src/clis/xiaohongshu/search.ts +13 -0
  305. package/src/clis/youtube/search.ts +57 -17
  306. package/src/clis/zhihu/question.test.ts +71 -0
  307. package/src/clis/zhihu/question.ts +27 -15
  308. package/src/commanderAdapter.test.ts +30 -0
  309. package/src/commanderAdapter.ts +7 -0
  310. package/src/commands/daemon.test.ts +238 -0
  311. package/src/commands/daemon.ts +135 -0
  312. package/src/completion.ts +2 -1
  313. package/src/constants.ts +3 -0
  314. package/src/daemon.test.ts +88 -0
  315. package/src/daemon.ts +26 -14
  316. package/src/discovery.ts +52 -2
  317. package/src/electron-apps.test.ts +50 -0
  318. package/src/electron-apps.ts +89 -0
  319. package/src/engine.test.ts +45 -9
  320. package/src/execution.ts +24 -19
  321. package/src/extension-manifest-regression.test.ts +1 -0
  322. package/src/idle-manager.ts +60 -0
  323. package/src/launcher.test.ts +67 -0
  324. package/src/launcher.ts +185 -0
  325. package/src/main.ts +3 -2
  326. package/src/registry.test.ts +15 -0
  327. package/src/registry.ts +32 -3
  328. package/src/runtime.ts +13 -7
  329. package/src/serialization.test.ts +19 -1
  330. package/src/serialization.ts +2 -0
  331. package/src/tui.test.ts +23 -0
  332. package/src/tui.ts +65 -0
  333. package/src/weixin-download.test.ts +27 -0
  334. package/tests/e2e/browser-public-extended.test.ts +6 -2
  335. package/chatwise-opencli.ps1 +0 -82
  336. package/dist/clis/chatwise/shared.d.ts +0 -2
  337. package/dist/clis/chatwise/shared.js +0 -6
  338. package/src/clis/chatwise/shared.ts +0 -8
@@ -0,0 +1,64 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const { mockListNotebooklmNotesFromPage, mockGetNotebooklmPageState, mockRequireNotebooklmSession } = vi.hoisted(() => ({
4
+ mockListNotebooklmNotesFromPage: vi.fn(),
5
+ mockGetNotebooklmPageState: vi.fn(),
6
+ mockRequireNotebooklmSession: vi.fn(),
7
+ }));
8
+
9
+ vi.mock('./utils.js', async () => {
10
+ const actual = await vi.importActual<typeof import('./utils.js')>('./utils.js');
11
+ return {
12
+ ...actual,
13
+ getNotebooklmPageState: mockGetNotebooklmPageState,
14
+ listNotebooklmNotesFromPage: mockListNotebooklmNotesFromPage,
15
+ requireNotebooklmSession: mockRequireNotebooklmSession,
16
+ };
17
+ });
18
+
19
+ import { getRegistry } from '../../registry.js';
20
+ import './note-list.js';
21
+
22
+ describe('notebooklm note-list', () => {
23
+ const command = getRegistry().get('notebooklm/note-list');
24
+
25
+ beforeEach(() => {
26
+ mockListNotebooklmNotesFromPage.mockReset();
27
+ mockGetNotebooklmPageState.mockReset();
28
+ mockRequireNotebooklmSession.mockReset();
29
+ mockRequireNotebooklmSession.mockResolvedValue(undefined);
30
+ mockGetNotebooklmPageState.mockResolvedValue({
31
+ url: 'https://notebooklm.google.com/notebook/nb-demo',
32
+ title: 'Browser Automation',
33
+ hostname: 'notebooklm.google.com',
34
+ kind: 'notebook',
35
+ notebookId: 'nb-demo',
36
+ loginRequired: false,
37
+ notebookCount: 1,
38
+ });
39
+ });
40
+
41
+ it('lists notebook notes from the Studio panel', async () => {
42
+ mockListNotebooklmNotesFromPage.mockResolvedValue([
43
+ {
44
+ notebook_id: 'nb-demo',
45
+ title: '新建笔记',
46
+ created_at: '6 分钟前',
47
+ url: 'https://notebooklm.google.com/notebook/nb-demo',
48
+ source: 'studio-list',
49
+ },
50
+ ]);
51
+
52
+ const result = await command!.func!({} as any, {});
53
+
54
+ expect(result).toEqual([
55
+ {
56
+ notebook_id: 'nb-demo',
57
+ title: '新建笔记',
58
+ created_at: '6 分钟前',
59
+ url: 'https://notebooklm.google.com/notebook/nb-demo',
60
+ source: 'studio-list',
61
+ },
62
+ ]);
63
+ });
64
+ });
@@ -0,0 +1,42 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+ import { EmptyResultError } from '../../errors.js';
4
+ import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
5
+ import {
6
+ ensureNotebooklmNotebookBinding,
7
+ getNotebooklmPageState,
8
+ listNotebooklmNotesFromPage,
9
+ requireNotebooklmSession,
10
+ } from './utils.js';
11
+
12
+ cli({
13
+ site: NOTEBOOKLM_SITE,
14
+ name: 'note-list',
15
+ aliases: ['notes-list'],
16
+ description: 'List saved notes from the Studio panel of the current NotebookLM notebook',
17
+ domain: NOTEBOOKLM_DOMAIN,
18
+ strategy: Strategy.COOKIE,
19
+ browser: true,
20
+ navigateBefore: false,
21
+ args: [],
22
+ columns: ['title', 'created_at', 'source', 'url'],
23
+ func: async (page: IPage) => {
24
+ await ensureNotebooklmNotebookBinding(page);
25
+ await requireNotebooklmSession(page);
26
+ const state = await getNotebooklmPageState(page);
27
+ if (state.kind !== 'notebook') {
28
+ throw new EmptyResultError(
29
+ 'opencli notebooklm note-list',
30
+ 'Open a specific NotebookLM notebook tab first, then retry.',
31
+ );
32
+ }
33
+
34
+ const rows = await listNotebooklmNotesFromPage(page);
35
+ if (rows.length > 0) return rows;
36
+
37
+ throw new EmptyResultError(
38
+ 'opencli notebooklm note-list',
39
+ 'No NotebookLM notes are visible in the Studio panel. Reload the notebook page or close the note editor and retry.',
40
+ );
41
+ },
42
+ });
@@ -0,0 +1,88 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const {
4
+ mockListNotebooklmNotesFromPage,
5
+ mockReadNotebooklmVisibleNoteFromPage,
6
+ mockGetNotebooklmPageState,
7
+ mockRequireNotebooklmSession,
8
+ } = vi.hoisted(() => ({
9
+ mockListNotebooklmNotesFromPage: vi.fn(),
10
+ mockReadNotebooklmVisibleNoteFromPage: vi.fn(),
11
+ mockGetNotebooklmPageState: vi.fn(),
12
+ mockRequireNotebooklmSession: vi.fn(),
13
+ }));
14
+
15
+ vi.mock('./utils.js', async () => {
16
+ const actual = await vi.importActual<typeof import('./utils.js')>('./utils.js');
17
+ return {
18
+ ...actual,
19
+ listNotebooklmNotesFromPage: mockListNotebooklmNotesFromPage,
20
+ readNotebooklmVisibleNoteFromPage: mockReadNotebooklmVisibleNoteFromPage,
21
+ getNotebooklmPageState: mockGetNotebooklmPageState,
22
+ requireNotebooklmSession: mockRequireNotebooklmSession,
23
+ };
24
+ });
25
+
26
+ import { getRegistry } from '../../registry.js';
27
+ import { CliError } from '../../errors.js';
28
+ import './notes-get.js';
29
+
30
+ describe('notebooklm notes-get', () => {
31
+ const command = getRegistry().get('notebooklm/notes-get');
32
+
33
+ beforeEach(() => {
34
+ mockListNotebooklmNotesFromPage.mockReset();
35
+ mockReadNotebooklmVisibleNoteFromPage.mockReset();
36
+ mockGetNotebooklmPageState.mockReset();
37
+ mockRequireNotebooklmSession.mockReset();
38
+ mockRequireNotebooklmSession.mockResolvedValue(undefined);
39
+ mockGetNotebooklmPageState.mockResolvedValue({
40
+ url: 'https://notebooklm.google.com/notebook/nb-demo',
41
+ title: 'Browser Automation',
42
+ hostname: 'notebooklm.google.com',
43
+ kind: 'notebook',
44
+ notebookId: 'nb-demo',
45
+ loginRequired: false,
46
+ notebookCount: 1,
47
+ });
48
+ });
49
+
50
+ it('returns the currently visible note editor content when the title matches', async () => {
51
+ mockReadNotebooklmVisibleNoteFromPage.mockResolvedValue({
52
+ notebook_id: 'nb-demo',
53
+ title: '新建笔记',
54
+ content: '第一段\\n第二段',
55
+ url: 'https://notebooklm.google.com/notebook/nb-demo',
56
+ source: 'studio-editor',
57
+ });
58
+
59
+ const result = await command!.func!({} as any, { note: '新建笔记' });
60
+
61
+ expect(result).toEqual([
62
+ {
63
+ notebook_id: 'nb-demo',
64
+ title: '新建笔记',
65
+ content: '第一段\\n第二段',
66
+ url: 'https://notebooklm.google.com/notebook/nb-demo',
67
+ source: 'studio-editor',
68
+ },
69
+ ]);
70
+ });
71
+
72
+ it('explains the current visible-note limitation when the target note is listed but not open', async () => {
73
+ mockReadNotebooklmVisibleNoteFromPage.mockResolvedValue(null);
74
+ mockListNotebooklmNotesFromPage.mockResolvedValue([
75
+ {
76
+ notebook_id: 'nb-demo',
77
+ title: '新建笔记',
78
+ created_at: '6 分钟前',
79
+ url: 'https://notebooklm.google.com/notebook/nb-demo',
80
+ source: 'studio-list',
81
+ },
82
+ ]);
83
+
84
+ await expect(command!.func!({} as any, { note: '新建笔记' })).rejects.toMatchObject({
85
+ hint: expect.stringMatching(/currently reads note content only from the visible note editor/i),
86
+ } satisfies Partial<CliError>);
87
+ });
88
+ });
@@ -0,0 +1,67 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+ import { EmptyResultError } from '../../errors.js';
4
+ import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
5
+ import {
6
+ ensureNotebooklmNotebookBinding,
7
+ findNotebooklmNoteRow,
8
+ getNotebooklmPageState,
9
+ listNotebooklmNotesFromPage,
10
+ readNotebooklmVisibleNoteFromPage,
11
+ requireNotebooklmSession,
12
+ } from './utils.js';
13
+
14
+ function matchesNoteTitle(title: string, query: string): boolean {
15
+ const needle = query.trim().toLowerCase();
16
+ if (!needle) return false;
17
+ const normalized = title.trim().toLowerCase();
18
+ return normalized === needle || normalized.includes(needle);
19
+ }
20
+
21
+ cli({
22
+ site: NOTEBOOKLM_SITE,
23
+ name: 'notes-get',
24
+ description: 'Get one note from the current NotebookLM notebook by title from the visible note editor',
25
+ domain: NOTEBOOKLM_DOMAIN,
26
+ strategy: Strategy.COOKIE,
27
+ browser: true,
28
+ navigateBefore: false,
29
+ args: [
30
+ {
31
+ name: 'note',
32
+ positional: true,
33
+ required: true,
34
+ help: 'Note title or id from the current notebook',
35
+ },
36
+ ],
37
+ columns: ['title', 'content', 'source', 'url'],
38
+ func: async (page: IPage, kwargs) => {
39
+ await ensureNotebooklmNotebookBinding(page);
40
+ await requireNotebooklmSession(page);
41
+ const state = await getNotebooklmPageState(page);
42
+ if (state.kind !== 'notebook') {
43
+ throw new EmptyResultError(
44
+ 'opencli notebooklm notes-get',
45
+ 'Open a specific NotebookLM notebook tab first, then retry.',
46
+ );
47
+ }
48
+
49
+ const query = typeof kwargs.note === 'string' ? kwargs.note : String(kwargs.note ?? '');
50
+ const visible = await readNotebooklmVisibleNoteFromPage(page);
51
+ if (visible && matchesNoteTitle(visible.title, query)) return [visible];
52
+
53
+ const rows = await listNotebooklmNotesFromPage(page);
54
+ const listed = findNotebooklmNoteRow(rows, query);
55
+ if (listed) {
56
+ throw new EmptyResultError(
57
+ 'opencli notebooklm notes-get',
58
+ `Note "${query}" is listed in Studio, but opencli currently reads note content only from the visible note editor. Open that note in NotebookLM, then retry.`,
59
+ );
60
+ }
61
+
62
+ throw new EmptyResultError(
63
+ 'opencli notebooklm notes-get',
64
+ `Note "${query}" was not found in the current notebook.`,
65
+ );
66
+ },
67
+ });
@@ -0,0 +1,126 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { AuthRequiredError } from '../../errors.js';
3
+ import type { IPage } from '../../types.js';
4
+ import {
5
+ buildNotebooklmRpcBody,
6
+ extractNotebooklmRpcResult,
7
+ getNotebooklmPageAuth,
8
+ parseNotebooklmChunkedResponse,
9
+ } from './rpc.js';
10
+
11
+ describe('notebooklm rpc transport', () => {
12
+ it('extracts auth tokens from the page html via page evaluation', async () => {
13
+ const page = {
14
+ evaluate: vi.fn(async (script: string) => {
15
+ expect(script).toContain('document.documentElement.innerHTML');
16
+ return {
17
+ html: '<html>"SNlM0e":"csrf-123","FdrFJe":"sess-456"</html>',
18
+ sourcePath: '/',
19
+ };
20
+ }),
21
+ } as unknown as IPage;
22
+
23
+ await expect(getNotebooklmPageAuth(page)).resolves.toEqual({
24
+ csrfToken: 'csrf-123',
25
+ sessionId: 'sess-456',
26
+ sourcePath: '/',
27
+ });
28
+ expect(page.evaluate).toHaveBeenCalledTimes(1);
29
+ });
30
+
31
+ it('falls back to WIZ_global_data tokens when html regex data is missing', async () => {
32
+ const page = {
33
+ evaluate: vi.fn(async () => ({
34
+ html: '<html><body>NotebookLM</body></html>',
35
+ sourcePath: '/notebook/nb-demo',
36
+ readyState: 'complete',
37
+ csrfToken: 'csrf-wiz',
38
+ sessionId: 'sess-wiz',
39
+ })),
40
+ } as unknown as IPage;
41
+
42
+ await expect(getNotebooklmPageAuth(page)).resolves.toEqual({
43
+ csrfToken: 'csrf-wiz',
44
+ sessionId: 'sess-wiz',
45
+ sourcePath: '/notebook/nb-demo',
46
+ });
47
+ });
48
+
49
+ it('retries token extraction once when the first probe returns no tokens', async () => {
50
+ const page = {
51
+ evaluate: vi.fn()
52
+ .mockResolvedValueOnce({
53
+ html: '<html><body>Loading…</body></html>',
54
+ sourcePath: '/notebook/nb-demo',
55
+ readyState: 'interactive',
56
+ csrfToken: '',
57
+ sessionId: '',
58
+ })
59
+ .mockResolvedValueOnce({
60
+ html: '<html>"SNlM0e":"csrf-123","FdrFJe":"sess-456"</html>',
61
+ sourcePath: '/notebook/nb-demo',
62
+ readyState: 'complete',
63
+ csrfToken: '',
64
+ sessionId: '',
65
+ }),
66
+ wait: vi.fn(async () => undefined),
67
+ } as unknown as IPage;
68
+
69
+ await expect(getNotebooklmPageAuth(page)).resolves.toEqual({
70
+ csrfToken: 'csrf-123',
71
+ sessionId: 'sess-456',
72
+ sourcePath: '/notebook/nb-demo',
73
+ });
74
+ expect(page.evaluate).toHaveBeenCalledTimes(2);
75
+ });
76
+
77
+ it('builds the rpc body with the expected notebooklm payload shape', () => {
78
+ const body = buildNotebooklmRpcBody('wXbhsf', [null, 1, null, [2]], 'csrf-123');
79
+
80
+ expect(body).toContain('f.req=');
81
+ expect(body).toContain('at=csrf-123');
82
+ expect(body.endsWith('&')).toBe(true);
83
+ expect(decodeURIComponent(body)).toContain('"[null,1,null,[2]]"');
84
+ });
85
+
86
+ it('parses chunked batchexecute responses into json chunks', () => {
87
+ const raw = `)]}'\n107\n[["wrb.fr","wXbhsf","[[[\\\"Notebook One\\\",null,\\\"nb1\\\",null,null,[null,false,null,null,null,[1704067200]]]]]"]]`;
88
+ const chunks = parseNotebooklmChunkedResponse(raw);
89
+
90
+ expect(chunks).toHaveLength(1);
91
+ expect(Array.isArray(chunks[0])).toBe(true);
92
+ expect(chunks[0]).toEqual([
93
+ [
94
+ 'wrb.fr',
95
+ 'wXbhsf',
96
+ '[[["Notebook One",null,"nb1",null,null,[null,false,null,null,null,[1704067200]]]]]',
97
+ ],
98
+ ]);
99
+ });
100
+
101
+ it('extracts the rpc payload from wrb.fr responses', () => {
102
+ const raw = `)]}'\n107\n[["wrb.fr","wXbhsf","[[[\\\"Notebook One\\\",null,\\\"nb1\\\",null,null,[null,false,null,null,null,[1704067200]]]]]"]]`;
103
+
104
+ const result = extractNotebooklmRpcResult(raw, 'wXbhsf');
105
+
106
+ expect(result).toEqual([
107
+ [
108
+ ['Notebook One', null, 'nb1', null, null, [null, false, null, null, null, [1704067200]]],
109
+ ],
110
+ ]);
111
+ });
112
+
113
+ it('classifies auth errors as AuthRequiredError', () => {
114
+ const raw = `)]}'\n25\n[["er",null,null,null,null,401,"generic"]]`;
115
+
116
+ expect(() => extractNotebooklmRpcResult(raw, 'wXbhsf')).toThrow(AuthRequiredError);
117
+
118
+ try {
119
+ extractNotebooklmRpcResult(raw, 'wXbhsf');
120
+ } catch (error) {
121
+ expect(error).toBeInstanceOf(AuthRequiredError);
122
+ expect((error as AuthRequiredError).domain).toBe('notebooklm.google.com');
123
+ expect((error as AuthRequiredError).code).toBe('AUTH_REQUIRED');
124
+ }
125
+ });
126
+ });
@@ -0,0 +1,286 @@
1
+ import { AuthRequiredError, CliError } from '../../errors.js';
2
+ import type { IPage } from '../../types.js';
3
+ import { NOTEBOOKLM_DOMAIN } from './shared.js';
4
+
5
+ export type NotebooklmPageAuth = {
6
+ csrfToken: string;
7
+ sessionId: string;
8
+ sourcePath: string;
9
+ };
10
+
11
+ type NotebooklmAuthProbe = {
12
+ html: string;
13
+ sourcePath: string;
14
+ readyState: string;
15
+ csrfToken: string;
16
+ sessionId: string;
17
+ };
18
+
19
+ export type NotebooklmFetchResponse = {
20
+ ok: boolean;
21
+ status: number;
22
+ body: string;
23
+ finalUrl: string;
24
+ };
25
+
26
+ export type NotebooklmRpcCallResult = {
27
+ auth: NotebooklmPageAuth;
28
+ url: string;
29
+ requestBody: string;
30
+ response: NotebooklmFetchResponse;
31
+ result: unknown;
32
+ };
33
+
34
+ export function extractNotebooklmPageAuthFromHtml(
35
+ html: string,
36
+ sourcePath: string = '/',
37
+ preferredTokens?: { csrfToken?: string; sessionId?: string },
38
+ ): NotebooklmPageAuth {
39
+ const csrfMatch = html.match(/"SNlM0e":"([^"]+)"/);
40
+ const sessionMatch = html.match(/"FdrFJe":"([^"]+)"/);
41
+ const csrfToken = preferredTokens?.csrfToken?.trim() || (csrfMatch ? csrfMatch[1] : '');
42
+ const sessionId = preferredTokens?.sessionId?.trim() || (sessionMatch ? sessionMatch[1] : '');
43
+
44
+ if (!csrfToken || !sessionId) {
45
+ throw new CliError(
46
+ 'NOTEBOOKLM_TOKENS',
47
+ 'NotebookLM page tokens were not found in the current page HTML',
48
+ 'Open the NotebookLM notebook page in Chrome, wait for it to finish loading, then retry with --verbose if it still fails.',
49
+ );
50
+ }
51
+
52
+ return { csrfToken, sessionId, sourcePath: sourcePath || '/' };
53
+ }
54
+
55
+ async function probeNotebooklmPageAuth(page: IPage): Promise<NotebooklmAuthProbe> {
56
+ const raw = await page.evaluate(`(() => {
57
+ const wiz = window.WIZ_global_data || {};
58
+ const html = document.documentElement.innerHTML;
59
+ return {
60
+ html,
61
+ sourcePath: location.pathname || '/',
62
+ readyState: document.readyState || '',
63
+ csrfToken: typeof wiz.SNlM0e === 'string' ? wiz.SNlM0e : '',
64
+ sessionId: typeof wiz.FdrFJe === 'string' ? wiz.FdrFJe : '',
65
+ };
66
+ })()`) as Partial<NotebooklmAuthProbe> | null;
67
+
68
+ return {
69
+ html: String(raw?.html ?? ''),
70
+ sourcePath: String(raw?.sourcePath ?? '/'),
71
+ readyState: String(raw?.readyState ?? ''),
72
+ csrfToken: String(raw?.csrfToken ?? ''),
73
+ sessionId: String(raw?.sessionId ?? ''),
74
+ };
75
+ }
76
+
77
+ export async function getNotebooklmPageAuth(page: IPage): Promise<NotebooklmPageAuth> {
78
+ let lastError: unknown;
79
+ for (let attempt = 0; attempt < 2; attempt += 1) {
80
+ const probe = await probeNotebooklmPageAuth(page);
81
+ try {
82
+ return extractNotebooklmPageAuthFromHtml(
83
+ probe.html,
84
+ probe.sourcePath,
85
+ { csrfToken: probe.csrfToken, sessionId: probe.sessionId },
86
+ );
87
+ } catch (error) {
88
+ lastError = error;
89
+ if (attempt === 0 && typeof page.wait === 'function') {
90
+ await page.wait(0.5).catch(() => undefined);
91
+ continue;
92
+ }
93
+ }
94
+ }
95
+
96
+ throw lastError;
97
+ }
98
+
99
+ export function buildNotebooklmRpcBody(
100
+ rpcId: string,
101
+ params: unknown[] | Record<string, unknown> | null,
102
+ csrfToken: string,
103
+ ): string {
104
+ const rpcRequest = [[[rpcId, JSON.stringify(params), null, 'generic']]];
105
+ return `f.req=${encodeURIComponent(JSON.stringify(rpcRequest))}&at=${encodeURIComponent(csrfToken)}&`;
106
+ }
107
+
108
+ export function stripNotebooklmAntiXssi(rawBody: string): string {
109
+ if (!rawBody.startsWith(")]}'")) return rawBody;
110
+ return rawBody.replace(/^\)\]\}'\r?\n/, '');
111
+ }
112
+
113
+ export function parseNotebooklmChunkedResponse(rawBody: string): unknown[] {
114
+ const cleaned = stripNotebooklmAntiXssi(rawBody).trim();
115
+ if (!cleaned) return [];
116
+
117
+ const lines = cleaned.split('\n');
118
+ const chunks: unknown[] = [];
119
+
120
+ for (let i = 0; i < lines.length; i += 1) {
121
+ const line = lines[i].trim();
122
+ if (!line) continue;
123
+
124
+ if (/^\d+$/.test(line)) {
125
+ const nextLine = lines[i + 1];
126
+ if (!nextLine) continue;
127
+ try {
128
+ chunks.push(JSON.parse(nextLine));
129
+ } catch {
130
+ // Ignore malformed chunks and keep scanning.
131
+ }
132
+ i += 1;
133
+ continue;
134
+ }
135
+
136
+ if (line.startsWith('[')) {
137
+ try {
138
+ chunks.push(JSON.parse(line));
139
+ } catch {
140
+ // Ignore malformed chunks and keep scanning.
141
+ }
142
+ }
143
+ }
144
+
145
+ return chunks;
146
+ }
147
+
148
+ export function extractNotebooklmRpcResult(rawBody: string, rpcId: string): unknown {
149
+ const chunks = parseNotebooklmChunkedResponse(rawBody);
150
+
151
+ for (const chunk of chunks) {
152
+ if (!Array.isArray(chunk)) continue;
153
+ const items = Array.isArray(chunk[0]) ? chunk : [chunk];
154
+
155
+ for (const item of items) {
156
+ if (!Array.isArray(item) || item.length < 1) continue;
157
+
158
+ if (item[0] === 'er') {
159
+ const errorCode = typeof item[2] === 'number'
160
+ ? item[2]
161
+ : typeof item[5] === 'number'
162
+ ? item[5]
163
+ : null;
164
+
165
+ if (errorCode === 401 || errorCode === 403) {
166
+ throw new AuthRequiredError(
167
+ NOTEBOOKLM_DOMAIN,
168
+ `NotebookLM RPC returned auth error (${errorCode})`,
169
+ );
170
+ }
171
+
172
+ throw new CliError(
173
+ 'NOTEBOOKLM_RPC',
174
+ `NotebookLM RPC failed${errorCode ? ` (code=${errorCode})` : ''}`,
175
+ 'Retry from an already logged-in NotebookLM session, or inspect the raw response with debug logging.',
176
+ );
177
+ }
178
+
179
+ if (item[0] === 'wrb.fr' && item[1] === rpcId) {
180
+ const payload = item[2];
181
+ if (typeof payload === 'string') {
182
+ try {
183
+ return JSON.parse(payload);
184
+ } catch {
185
+ return payload;
186
+ }
187
+ }
188
+ return payload;
189
+ }
190
+ }
191
+ }
192
+
193
+ return null;
194
+ }
195
+
196
+ export async function fetchNotebooklmInPage(
197
+ page: IPage,
198
+ url: string,
199
+ options: {
200
+ method?: 'GET' | 'POST';
201
+ headers?: Record<string, string>;
202
+ body?: string;
203
+ } = {},
204
+ ): Promise<NotebooklmFetchResponse> {
205
+ const method = options.method ?? 'GET';
206
+ const headers = options.headers ?? {};
207
+ const body = options.body ?? '';
208
+
209
+ const raw = await page.evaluate(`(async () => {
210
+ const request = {
211
+ url: ${JSON.stringify(url)},
212
+ method: ${JSON.stringify(method)},
213
+ headers: ${JSON.stringify(headers)},
214
+ body: ${JSON.stringify(body)},
215
+ };
216
+
217
+ const response = await fetch(request.url, {
218
+ method: request.method,
219
+ headers: request.headers,
220
+ body: request.method === 'GET' ? undefined : request.body,
221
+ credentials: 'include',
222
+ });
223
+
224
+ return {
225
+ ok: response.ok,
226
+ status: response.status,
227
+ body: await response.text(),
228
+ finalUrl: response.url,
229
+ };
230
+ })()`) as Partial<NotebooklmFetchResponse> | null;
231
+
232
+ return {
233
+ ok: Boolean(raw?.ok),
234
+ status: Number(raw?.status ?? 0),
235
+ body: String(raw?.body ?? ''),
236
+ finalUrl: String(raw?.finalUrl ?? url),
237
+ };
238
+ }
239
+
240
+ export async function callNotebooklmRpc(
241
+ page: IPage,
242
+ rpcId: string,
243
+ params: unknown[] | Record<string, unknown> | null,
244
+ options: {
245
+ hl?: string;
246
+ } = {},
247
+ ): Promise<NotebooklmRpcCallResult> {
248
+ const auth = await getNotebooklmPageAuth(page);
249
+ const requestBody = buildNotebooklmRpcBody(rpcId, params, auth.csrfToken);
250
+ const url =
251
+ `https://${NOTEBOOKLM_DOMAIN}/_/LabsTailwindUi/data/batchexecute` +
252
+ `?rpcids=${rpcId}&source-path=${encodeURIComponent(auth.sourcePath)}` +
253
+ `&hl=${encodeURIComponent(options.hl ?? 'en')}` +
254
+ `&f.sid=${encodeURIComponent(auth.sessionId)}&rt=c`;
255
+
256
+ const response = await fetchNotebooklmInPage(page, url, {
257
+ method: 'POST',
258
+ headers: {
259
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
260
+ },
261
+ body: requestBody,
262
+ });
263
+
264
+ if (response.status === 401 || response.status === 403) {
265
+ throw new AuthRequiredError(
266
+ NOTEBOOKLM_DOMAIN,
267
+ `NotebookLM RPC returned auth error (${response.status})`,
268
+ );
269
+ }
270
+
271
+ if (!response.ok) {
272
+ throw new CliError(
273
+ 'NOTEBOOKLM_RPC',
274
+ `NotebookLM RPC request failed with HTTP ${response.status}`,
275
+ 'Retry from the NotebookLM home page in an already logged-in Chrome session.',
276
+ );
277
+ }
278
+
279
+ return {
280
+ auth,
281
+ url,
282
+ requestBody,
283
+ response,
284
+ result: extractNotebooklmRpcResult(response.body, rpcId),
285
+ };
286
+ }