@jackwener/opencli 1.7.22 → 1.8.1

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 (346) hide show
  1. package/README.md +35 -194
  2. package/README.zh-CN.md +42 -260
  3. package/cli-manifest.json +8160 -4392
  4. package/clis/12306/me.js +73 -0
  5. package/clis/12306/orders.js +96 -0
  6. package/clis/12306/passengers.js +90 -0
  7. package/clis/12306/price.js +166 -0
  8. package/clis/12306/stations.js +66 -0
  9. package/clis/12306/train.js +91 -0
  10. package/clis/12306/trains.js +119 -0
  11. package/clis/12306/utils.js +272 -0
  12. package/clis/12306/utils.test.js +331 -0
  13. package/clis/36kr/article.js +6 -3
  14. package/clis/36kr/article.test.js +46 -0
  15. package/clis/_atlassian/shared.js +577 -0
  16. package/clis/_atlassian/shared.test.js +170 -0
  17. package/clis/apple-podcasts/commands.test.js +20 -0
  18. package/clis/apple-podcasts/search.js +2 -2
  19. package/clis/barchart/greeks.js +144 -56
  20. package/clis/barchart/greeks.test.js +138 -0
  21. package/clis/bilibili/comment.js +125 -0
  22. package/clis/bilibili/comment.test.js +153 -0
  23. package/clis/bilibili/comments.js +116 -21
  24. package/clis/bilibili/comments.test.js +77 -18
  25. package/clis/bilibili/subtitle.js +76 -31
  26. package/clis/bilibili/subtitle.test.js +156 -9
  27. package/clis/bilibili/summary.js +167 -0
  28. package/clis/bilibili/summary.test.js +210 -0
  29. package/clis/bilibili/utils.js +63 -5
  30. package/clis/bilibili/utils.test.js +45 -1
  31. package/clis/booking/booking.test.js +356 -0
  32. package/clis/booking/search.js +351 -0
  33. package/clis/chatgpt/envelope.test.js +108 -0
  34. package/clis/chatgpt/image.js +2 -2
  35. package/clis/chatgpt/image.test.js +6 -0
  36. package/clis/chatgpt/utils.js +148 -41
  37. package/clis/chatgpt/utils.test.js +92 -2
  38. package/clis/chess/analyze.js +35 -0
  39. package/clis/chess/analyze.test.js +79 -0
  40. package/clis/chess/game.js +114 -0
  41. package/clis/chess/game.test.js +178 -0
  42. package/clis/chess/games.js +67 -0
  43. package/clis/chess/games.test.js +164 -0
  44. package/clis/chess/stats.js +32 -0
  45. package/clis/chess/stats.test.js +79 -0
  46. package/clis/chess/utils.js +170 -0
  47. package/clis/chess/utils.test.js +230 -0
  48. package/clis/confluence/commands.test.js +195 -0
  49. package/clis/confluence/create.js +39 -0
  50. package/clis/confluence/page.js +23 -0
  51. package/clis/confluence/search.js +34 -0
  52. package/clis/confluence/shared.js +173 -0
  53. package/clis/confluence/update.js +38 -0
  54. package/clis/douyin/_shared/browser-fetch.js +44 -20
  55. package/clis/douyin/_shared/browser-fetch.test.js +22 -1
  56. package/clis/douyin/_shared/evaluate-result.js +16 -0
  57. package/clis/douyin/_shared/tos-upload.js +105 -69
  58. package/clis/douyin/_shared/vod-upload.js +212 -0
  59. package/clis/douyin/_shared/vod-upload.test.js +38 -0
  60. package/clis/douyin/delete.js +137 -4
  61. package/clis/douyin/delete.test.js +90 -1
  62. package/clis/douyin/hashtag.js +84 -23
  63. package/clis/douyin/hashtag.test.js +113 -0
  64. package/clis/douyin/publish-upload-id.test.js +170 -0
  65. package/clis/douyin/publish.js +88 -42
  66. package/clis/douyin/user-videos.js +9 -2
  67. package/clis/douyin/user-videos.test.js +43 -0
  68. package/clis/flomo/memos.js +228 -0
  69. package/clis/flomo/memos.test.js +144 -0
  70. package/clis/geogebra/add-circle.js +46 -0
  71. package/clis/geogebra/add-line.js +35 -0
  72. package/clis/geogebra/add-point.js +27 -0
  73. package/clis/geogebra/add-polygon.js +25 -0
  74. package/clis/geogebra/eval.js +35 -0
  75. package/clis/geogebra/geogebra.test.js +175 -0
  76. package/clis/geogebra/hexagon.js +62 -0
  77. package/clis/geogebra/info.js +72 -0
  78. package/clis/geogebra/list.js +35 -0
  79. package/clis/geogebra/triangle.js +60 -0
  80. package/clis/geogebra/utils.js +271 -0
  81. package/clis/gitee/search.js +2 -2
  82. package/clis/gitee/search.test.js +65 -0
  83. package/clis/jike/post.js +27 -17
  84. package/clis/jike/read.test.js +86 -0
  85. package/clis/jike/topic.js +32 -19
  86. package/clis/jike/user.js +33 -20
  87. package/clis/jira/attachments.js +28 -0
  88. package/clis/jira/commands.test.js +287 -0
  89. package/clis/jira/comments.js +28 -0
  90. package/clis/jira/issue.js +28 -0
  91. package/clis/jira/links.js +28 -0
  92. package/clis/jira/search.js +47 -0
  93. package/clis/jira/shared.js +256 -0
  94. package/clis/lesswrong/comments.js +1 -1
  95. package/clis/lesswrong/curated.js +1 -1
  96. package/clis/lesswrong/frontpage.js +1 -1
  97. package/clis/lesswrong/frontpage.test.js +37 -0
  98. package/clis/lesswrong/new.js +1 -1
  99. package/clis/lesswrong/read.js +1 -1
  100. package/clis/lesswrong/sequences.js +1 -1
  101. package/clis/lesswrong/shortform.js +1 -1
  102. package/clis/lesswrong/tag.js +1 -1
  103. package/clis/lesswrong/top-month.js +1 -1
  104. package/clis/lesswrong/top-week.js +1 -1
  105. package/clis/lesswrong/top-year.js +1 -1
  106. package/clis/lesswrong/top.js +1 -1
  107. package/clis/linkedin/connect.js +401 -0
  108. package/clis/linkedin/connect.test.js +213 -0
  109. package/clis/linkedin/inbox.js +234 -0
  110. package/clis/linkedin/inbox.test.js +152 -0
  111. package/clis/linkedin/job-detail.js +167 -0
  112. package/clis/linkedin/job-detail.test.js +38 -0
  113. package/clis/linkedin/jobs-preferences.js +113 -0
  114. package/clis/linkedin/jobs-preferences.test.js +43 -0
  115. package/clis/linkedin/people-search.js +262 -0
  116. package/clis/linkedin/people-search.test.js +216 -0
  117. package/clis/linkedin/post-analytics.js +74 -0
  118. package/clis/linkedin/post-analytics.test.js +40 -0
  119. package/clis/linkedin/posts-core.js +241 -0
  120. package/clis/linkedin/posts.js +22 -0
  121. package/clis/linkedin/posts.test.js +40 -0
  122. package/clis/linkedin/profile-analytics.js +104 -0
  123. package/clis/linkedin/profile-analytics.test.js +67 -0
  124. package/clis/linkedin/profile-experience.js +671 -0
  125. package/clis/linkedin/profile-experience.test.js +152 -0
  126. package/clis/linkedin/profile-projects.js +311 -0
  127. package/clis/linkedin/profile-projects.test.js +111 -0
  128. package/clis/linkedin/profile-read.js +148 -0
  129. package/clis/linkedin/profile-read.test.js +77 -0
  130. package/clis/linkedin/safe-send.js +357 -0
  131. package/clis/linkedin/safe-send.test.js +204 -0
  132. package/clis/linkedin/salesnav-inbox.js +210 -0
  133. package/clis/linkedin/salesnav-inbox.test.js +113 -0
  134. package/clis/linkedin/salesnav-message.js +360 -0
  135. package/clis/linkedin/salesnav-message.test.js +172 -0
  136. package/clis/linkedin/salesnav-search.js +186 -0
  137. package/clis/linkedin/salesnav-search.test.js +76 -0
  138. package/clis/linkedin/salesnav-thread.js +212 -0
  139. package/clis/linkedin/salesnav-thread.test.js +79 -0
  140. package/clis/linkedin/sent-invitations.js +92 -0
  141. package/clis/linkedin/sent-invitations.test.js +62 -0
  142. package/clis/linkedin/services-read.js +213 -0
  143. package/clis/linkedin/services-read.test.js +105 -0
  144. package/clis/linkedin/shared.js +124 -0
  145. package/clis/linkedin/thread-snapshot.js +214 -0
  146. package/clis/linkedin/thread-snapshot.test.js +89 -0
  147. package/clis/linkedin/timeline.js +14 -7
  148. package/clis/linkedin-learning/course.js +138 -0
  149. package/clis/linkedin-learning/course.test.js +114 -0
  150. package/clis/linkedin-learning/search.js +155 -0
  151. package/clis/linkedin-learning/search.test.js +144 -0
  152. package/clis/linkedin-learning/trending.js +133 -0
  153. package/clis/linkedin-learning/trending.test.js +123 -0
  154. package/clis/notebooklm/add-source.js +269 -0
  155. package/clis/notebooklm/add-source.test.js +97 -0
  156. package/clis/notebooklm/create.js +76 -0
  157. package/clis/notebooklm/create.test.js +58 -0
  158. package/clis/notebooklm/generate-audio.js +91 -0
  159. package/clis/notebooklm/generate-audio.test.js +63 -0
  160. package/clis/notebooklm/generate-slides.js +106 -0
  161. package/clis/notebooklm/generate-slides.test.js +75 -0
  162. package/clis/notebooklm/open.test.js +10 -10
  163. package/clis/notebooklm/rpc.js +20 -6
  164. package/clis/notebooklm/rpc.test.js +27 -1
  165. package/clis/notebooklm/utils.js +100 -24
  166. package/clis/notebooklm/utils.test.js +60 -1
  167. package/clis/notebooklm/write-note.js +103 -0
  168. package/clis/notebooklm/write-note.test.js +70 -0
  169. package/clis/pixiv/detail.js +41 -34
  170. package/clis/pixiv/detail.test.js +93 -0
  171. package/clis/pixiv/user.js +36 -31
  172. package/clis/pixiv/user.test.js +100 -0
  173. package/clis/pixiv/utils.js +56 -7
  174. package/clis/powerchina/search.js +3 -3
  175. package/clis/powerchina/search.test.js +27 -1
  176. package/clis/reddit/extract-media.test.js +149 -0
  177. package/clis/reddit/frontpage.js +47 -9
  178. package/clis/reddit/frontpage.test.js +34 -0
  179. package/clis/reddit/home.js +31 -1
  180. package/clis/reddit/home.test.js +46 -3
  181. package/clis/reddit/hot.js +32 -1
  182. package/clis/reddit/hot.test.js +15 -1
  183. package/clis/reddit/popular.js +39 -1
  184. package/clis/reddit/popular.test.js +26 -0
  185. package/clis/reddit/saved.js +1 -1
  186. package/clis/reddit/search.js +38 -1
  187. package/clis/reddit/search.test.js +26 -0
  188. package/clis/reddit/subreddit.js +52 -7
  189. package/clis/reddit/subreddit.test.js +31 -0
  190. package/clis/reddit/subscribed.js +165 -0
  191. package/clis/reddit/subscribed.test.js +168 -0
  192. package/clis/reddit/upvoted.js +1 -1
  193. package/clis/suno/commands.test.js +188 -0
  194. package/clis/suno/download.js +140 -0
  195. package/clis/suno/download.test.js +151 -0
  196. package/clis/suno/generate.js +231 -0
  197. package/clis/suno/generate.test.js +252 -0
  198. package/clis/suno/list.js +79 -0
  199. package/clis/suno/status.js +63 -0
  200. package/clis/suno/utils.js +549 -0
  201. package/clis/suno/utils.test.js +329 -0
  202. package/clis/twitter/device-follow.js +193 -0
  203. package/clis/twitter/device-follow.test.js +287 -0
  204. package/clis/twitter/download.js +443 -73
  205. package/clis/twitter/download.test.js +457 -0
  206. package/clis/twitter/followers.js +6 -2
  207. package/clis/twitter/followers.test.js +19 -1
  208. package/clis/twitter/following.js +14 -5
  209. package/clis/twitter/following.test.js +29 -0
  210. package/clis/twitter/likes.js +12 -4
  211. package/clis/twitter/likes.test.js +26 -1
  212. package/clis/twitter/list-add.js +1 -1
  213. package/clis/twitter/list-create.js +155 -0
  214. package/clis/twitter/list-create.test.js +169 -0
  215. package/clis/twitter/list-remove.js +13 -6
  216. package/clis/twitter/list-remove.test.js +74 -0
  217. package/clis/twitter/list-tweets.js +6 -2
  218. package/clis/twitter/list-tweets.test.js +41 -1
  219. package/clis/twitter/lists.js +31 -4
  220. package/clis/twitter/lists.test.js +152 -16
  221. package/clis/twitter/notifications.js +4 -4
  222. package/clis/twitter/post.js +62 -4
  223. package/clis/twitter/post.test.js +35 -3
  224. package/clis/twitter/profile.js +81 -28
  225. package/clis/twitter/profile.test.js +113 -2
  226. package/clis/twitter/quote.js +9 -4
  227. package/clis/twitter/reply.js +13 -10
  228. package/clis/twitter/reply.test.js +41 -0
  229. package/clis/twitter/search.js +7 -3
  230. package/clis/twitter/search.test.js +41 -0
  231. package/clis/twitter/shared.js +155 -0
  232. package/clis/twitter/shared.test.js +465 -1
  233. package/clis/twitter/thread.js +10 -2
  234. package/clis/twitter/thread.test.js +58 -0
  235. package/clis/twitter/timeline.js +6 -2
  236. package/clis/twitter/timeline.test.js +2 -0
  237. package/clis/twitter/tweets.js +3 -2
  238. package/clis/twitter/tweets.test.js +1 -1
  239. package/clis/twitter/utils.js +53 -16
  240. package/clis/upwork/detail.js +132 -0
  241. package/clis/upwork/feed.js +109 -0
  242. package/clis/upwork/search.js +115 -0
  243. package/clis/upwork/upwork.test.js +566 -0
  244. package/clis/upwork/utils.js +323 -0
  245. package/clis/weibo/delete.js +172 -0
  246. package/clis/weibo/delete.test.js +94 -0
  247. package/clis/weibo/publish.js +37 -14
  248. package/clis/weibo/publish.test.js +14 -5
  249. package/clis/weibo/user-posts.js +234 -0
  250. package/clis/weibo/user-posts.test.js +92 -0
  251. package/clis/weread/book-search.js +438 -0
  252. package/clis/weread/book-search.test.js +242 -0
  253. package/clis/weread/search-regression.test.js +98 -11
  254. package/clis/weread/search.js +32 -9
  255. package/clis/weread-official/book.js +135 -0
  256. package/clis/weread-official/commands.test.js +385 -0
  257. package/clis/weread-official/discover.js +107 -0
  258. package/clis/weread-official/list-apis.js +95 -0
  259. package/clis/weread-official/notes.js +171 -0
  260. package/clis/weread-official/readdata.js +158 -0
  261. package/clis/weread-official/review.js +93 -0
  262. package/clis/weread-official/search.js +106 -0
  263. package/clis/weread-official/shelf.js +97 -0
  264. package/clis/weread-official/utils.js +293 -0
  265. package/clis/weread-official/utils.test.js +242 -0
  266. package/clis/wikipedia/trending.js +7 -3
  267. package/clis/wikipedia/trending.test.js +57 -0
  268. package/clis/xianyu/chat.js +24 -109
  269. package/clis/xianyu/chat.test.js +5 -0
  270. package/clis/xianyu/im.js +322 -0
  271. package/clis/xianyu/im.test.js +253 -0
  272. package/clis/xianyu/inbox.js +96 -0
  273. package/clis/xianyu/messages.js +91 -0
  274. package/clis/xianyu/reply.js +82 -0
  275. package/clis/xiaohongshu/creator-note-detail.js +166 -28
  276. package/clis/xiaohongshu/creator-note-detail.test.js +196 -36
  277. package/clis/xiaohongshu/creator-notes-summary.js +2 -1
  278. package/clis/xiaohongshu/creator-notes-summary.test.js +7 -0
  279. package/clis/xiaohongshu/creator-notes.js +252 -2
  280. package/clis/xiaohongshu/creator-notes.test.js +90 -1
  281. package/clis/xiaohongshu/creator-stats.js +2 -1
  282. package/clis/xiaohongshu/creator-stats.test.js +24 -0
  283. package/clis/xiaohongshu/delete-note.js +260 -0
  284. package/clis/xiaohongshu/delete-note.test.js +172 -0
  285. package/clis/xiaohongshu/download.js +97 -39
  286. package/clis/xiaohongshu/download.test.js +201 -0
  287. package/clis/xiaohongshu/publish.js +48 -8
  288. package/clis/xiaohongshu/publish.test.js +65 -10
  289. package/clis/xiaohongshu/user-helpers.test.js +41 -0
  290. package/clis/xiaohongshu/user.js +27 -4
  291. package/clis/xiaoyuzhou/download.js +1 -1
  292. package/clis/xiaoyuzhou/transcript.js +1 -1
  293. package/clis/youdao/note.js +258 -0
  294. package/clis/youdao/note.test.js +99 -0
  295. package/clis/youtube/transcript.js +397 -24
  296. package/clis/youtube/transcript.test.js +196 -6
  297. package/clis/zhihu/answer-comments.js +280 -0
  298. package/clis/zhihu/answer-comments.test.js +287 -0
  299. package/clis/zhihu/answer-detail.js +2 -19
  300. package/clis/zhihu/answer-detail.test.js +8 -0
  301. package/clis/zhihu/collection.js +17 -16
  302. package/clis/zhihu/collection.test.js +50 -3
  303. package/clis/zhihu/download.js +1 -1
  304. package/clis/zhihu/question.js +42 -17
  305. package/clis/zhihu/question.test.js +113 -11
  306. package/clis/zhihu/search.js +195 -43
  307. package/clis/zhihu/search.test.js +198 -0
  308. package/clis/zhihu/text.js +29 -0
  309. package/clis/zhihu/text.test.js +24 -0
  310. package/dist/src/browser/errors.js +4 -2
  311. package/dist/src/browser/errors.test.js +6 -0
  312. package/dist/src/browser/network-cache.js +13 -1
  313. package/dist/src/browser/network-cache.test.js +17 -0
  314. package/dist/src/browser/page.js +30 -4
  315. package/dist/src/browser/page.test.js +42 -0
  316. package/dist/src/browser/utils.d.ts +1 -1
  317. package/dist/src/cli-argv-preprocess.d.ts +26 -0
  318. package/dist/src/cli-argv-preprocess.js +138 -0
  319. package/dist/src/cli-argv-preprocess.test.js +79 -0
  320. package/dist/src/convention-audit.js +15 -8
  321. package/dist/src/convention-audit.test.js +21 -0
  322. package/dist/src/download/index.js +13 -1
  323. package/dist/src/download/index.test.js +23 -1
  324. package/dist/src/download/media-download.js +15 -2
  325. package/dist/src/download/media-download.test.d.ts +1 -0
  326. package/dist/src/download/media-download.test.js +112 -0
  327. package/dist/src/download/progress.js +2 -2
  328. package/dist/src/download/progress.test.js +12 -1
  329. package/dist/src/electron-apps.js +1 -1
  330. package/dist/src/electron-apps.test.js +7 -2
  331. package/dist/src/errors.d.ts +17 -0
  332. package/dist/src/errors.js +22 -0
  333. package/dist/src/external-clis.yaml +8 -0
  334. package/dist/src/main.js +14 -2
  335. package/dist/src/output.js +11 -1
  336. package/dist/src/output.test.js +6 -0
  337. package/dist/src/registry.js +1 -0
  338. package/dist/src/registry.test.js +11 -0
  339. package/dist/src/utils.d.ts +43 -0
  340. package/dist/src/utils.js +97 -0
  341. package/dist/src/utils.test.d.ts +1 -0
  342. package/dist/src/utils.test.js +155 -0
  343. package/package.json +8 -2
  344. package/scripts/silent-column-drop-baseline.json +0 -52
  345. package/scripts/typed-error-lint-baseline.json +28 -380
  346. package/clis/slock/_utils.js +0 -12
@@ -0,0 +1,106 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
3
+ import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
4
+ import { callNotebooklmRpc } from './rpc.js';
5
+ import { buildNotebooklmNotebookUrl, listNotebooklmSourcesViaRpc, parseNotebooklmNotebookTarget, requireNotebooklmExecute, requireNotebooklmSession } from './utils.js';
6
+
7
+ const NOTEBOOKLM_CREATE_ARTIFACT_RPC_ID = 'R7cb6c';
8
+ const SLIDE_DECK_CONFIG_BLOCK = [2, null, null, [1, null, null, null, null, null, null, null, null, null, [1]], [[1, 4, 2, 3, 6]]];
9
+ const ARTIFACT_UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
10
+
11
+ function toExcludedUuidSet(excludedIds) {
12
+ return new Set(excludedIds.map((id) => String(id ?? '').toLowerCase()).filter(Boolean));
13
+ }
14
+
15
+ export function buildCreateSlidesArgs(projectId, sourceIds, options = {}) {
16
+ const sourceTuples = sourceIds.map((id) => [[id]]);
17
+ const language = options.language || 'en';
18
+ const length = Number.isInteger(options.length) ? options.length : 3;
19
+ return [
20
+ SLIDE_DECK_CONFIG_BLOCK,
21
+ projectId,
22
+ [
23
+ null,
24
+ null,
25
+ 8,
26
+ sourceTuples,
27
+ null,
28
+ null,
29
+ null, null, null, null, null, null, null, null, null, null,
30
+ [[null, language, 1, length]],
31
+ ],
32
+ ];
33
+ }
34
+
35
+ export function parseSlidesIdFromResult(result, excludedIds = []) {
36
+ const excluded = toExcludedUuidSet(excludedIds);
37
+ if (typeof result === 'string' && ARTIFACT_UUID_RE.test(result) && !excluded.has(result.toLowerCase())) return result;
38
+ const stack = [result];
39
+ while (stack.length) {
40
+ const node = stack.shift();
41
+ if (typeof node === 'string' && ARTIFACT_UUID_RE.test(node) && !excluded.has(node.toLowerCase())) return node;
42
+ if (Array.isArray(node)) for (const child of node) stack.push(child);
43
+ else if (node && typeof node === 'object') for (const v of Object.values(node)) stack.push(v);
44
+ }
45
+ return '';
46
+ }
47
+
48
+ export function parseSlideDeckLength(value) {
49
+ if (value === undefined || value === '') return 3;
50
+ const length = Number(value);
51
+ if (!Number.isInteger(length) || length <= 0) {
52
+ throw new ArgumentError('--length must be a positive integer');
53
+ }
54
+ return length;
55
+ }
56
+
57
+ cli({
58
+ site: NOTEBOOKLM_SITE,
59
+ name: 'generate-slides',
60
+ access: 'write',
61
+ description: 'Trigger a Slide Deck (AI presentation) generation for a NotebookLM notebook, using all of its sources',
62
+ domain: NOTEBOOKLM_DOMAIN,
63
+ strategy: Strategy.COOKIE,
64
+ browser: true,
65
+ navigateBefore: false,
66
+ args: [
67
+ { name: 'notebook', positional: true, required: true, help: 'Notebook id from `notebooklm list` or full notebook URL' },
68
+ { name: 'length', help: 'Slide deck length: 1=Short, 3=Default (default 3)' },
69
+ { name: 'language', help: 'Language code (default en)' },
70
+ { name: 'execute', type: 'boolean', help: 'Actually trigger remote NotebookLM slide deck generation' },
71
+ ],
72
+ columns: ['notebook_id', 'slides_id', 'source_count', 'status', 'notebook_url'],
73
+ func: async (page, kwargs) => {
74
+ const notebookId = parseNotebooklmNotebookTarget(String(kwargs.notebook ?? ''));
75
+ const length = parseSlideDeckLength(kwargs.length);
76
+ const language = String(kwargs.language ?? 'en').trim() || 'en';
77
+ requireNotebooklmExecute(kwargs.execute, 'generate NotebookLM slides');
78
+ try {
79
+ await page.goto(buildNotebooklmNotebookUrl(notebookId));
80
+ await page.wait(2);
81
+ }
82
+ catch (error) {
83
+ throw new CommandExecutionError(`Failed to open NotebookLM notebook ${notebookId}: ${error?.message || error}`);
84
+ }
85
+ await requireNotebooklmSession(page);
86
+ const sources = await listNotebooklmSourcesViaRpc(page);
87
+ const sourceIds = sources.map((s) => s.id).filter((id) => typeof id === 'string' && id);
88
+ if (sourceIds.length === 0) {
89
+ throw new EmptyResultError('notebooklm generate-slides', 'The notebook has no sources; add a source before generating a slide deck.');
90
+ }
91
+ const rpc = await callNotebooklmRpc(page, NOTEBOOKLM_CREATE_ARTIFACT_RPC_ID, buildCreateSlidesArgs(notebookId, sourceIds, { length, language }));
92
+ const slidesId = parseSlidesIdFromResult(rpc.result, [notebookId, ...sourceIds]);
93
+ if (!slidesId) {
94
+ throw new CommandExecutionError('NotebookLM CreateArtifact (slides) RPC returned no slide-deck id; the server may have rejected the request.');
95
+ }
96
+ return [{
97
+ notebook_id: notebookId,
98
+ slides_id: slidesId,
99
+ source_count: sourceIds.length,
100
+ status: 'pending',
101
+ notebook_url: buildNotebooklmNotebookUrl(notebookId),
102
+ }];
103
+ },
104
+ });
105
+
106
+ export const __test__ = { SLIDE_DECK_CONFIG_BLOCK, buildCreateSlidesArgs, parseSlideDeckLength, parseSlidesIdFromResult };
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { ArgumentError } from '@jackwener/opencli/errors';
3
+ import { getRegistry } from '@jackwener/opencli/registry';
4
+ import { __test__ } from './generate-slides.js';
5
+
6
+ const { SLIDE_DECK_CONFIG_BLOCK, buildCreateSlidesArgs, parseSlideDeckLength, parseSlidesIdFromResult } = __test__;
7
+
8
+ describe('notebooklm generate-slides', () => {
9
+ it('SLIDE_DECK_CONFIG_BLOCK reuses the same R7cb6c config envelope as audio', () => {
10
+ expect(SLIDE_DECK_CONFIG_BLOCK).toEqual([
11
+ 2, null, null,
12
+ [1, null, null, null, null, null, null, null, null, null, [1]],
13
+ [[1, 4, 2, 3, 6]],
14
+ ]);
15
+ });
16
+
17
+ it('buildCreateSlidesArgs matches the live-captured slide-deck wire format', () => {
18
+ const projectId = '17e2b882-6a01-4c6c-9262-0738dfa2abee';
19
+ const sourceA = '493b9ddd-453b-4523-b638-bb560b37723c';
20
+ const sourceB = '182508cf-9afd-4db5-8640-2f8cafcc7111';
21
+ expect(buildCreateSlidesArgs(projectId, [sourceA, sourceB])).toEqual([
22
+ [2, null, null, [1, null, null, null, null, null, null, null, null, null, [1]], [[1, 4, 2, 3, 6]]],
23
+ projectId,
24
+ [
25
+ null, null, 8,
26
+ [[[sourceA]], [[sourceB]]],
27
+ null, null,
28
+ null, null, null, null, null, null, null, null, null, null,
29
+ [[null, 'en', 1, 3]],
30
+ ],
31
+ ]);
32
+ });
33
+
34
+ it('buildCreateSlidesArgs honours --language and --length overrides', () => {
35
+ const args = buildCreateSlidesArgs('pid', ['s1'], { language: 'zh', length: 1 });
36
+ expect(args[2][16]).toEqual([[null, 'zh', 1, 1]]);
37
+ });
38
+
39
+ it('parseSlideDeckLength defaults empty values and rejects invalid input', () => {
40
+ expect(parseSlideDeckLength(undefined)).toBe(3);
41
+ expect(parseSlideDeckLength('')).toBe(3);
42
+ expect(parseSlideDeckLength('1')).toBe(1);
43
+ expect(() => parseSlideDeckLength('many')).toThrow(ArgumentError);
44
+ expect(() => parseSlideDeckLength(0)).toThrow(ArgumentError);
45
+ });
46
+
47
+ it('parseSlidesIdFromResult finds a UUID-shaped slides id anywhere in the tree', () => {
48
+ const id = '1f8ada7d-cb33-49a4-8498-c5b81c1a899d';
49
+ expect(parseSlidesIdFromResult([[id, 'opencli-slides-test']])).toBe(id);
50
+ expect(parseSlidesIdFromResult({ artifactId: id })).toBe(id);
51
+ });
52
+
53
+ it('parseSlidesIdFromResult ignores non-UUID strings and empty inputs', () => {
54
+ expect(parseSlidesIdFromResult([[null, 'opencli-slides-test']])).toBe('');
55
+ expect(parseSlidesIdFromResult({})).toBe('');
56
+ expect(parseSlidesIdFromResult([])).toBe('');
57
+ expect(parseSlidesIdFromResult(null)).toBe('');
58
+ });
59
+
60
+ it('parseSlidesIdFromResult skips notebook/source ids before selecting the generated deck id', () => {
61
+ const notebookId = '17e2b882-6a01-4c6c-9262-0738dfa2abee';
62
+ const sourceId = '493b9ddd-453b-4523-b638-bb560b37723c';
63
+ const slidesId = '1f8ada7d-cb33-49a4-8498-c5b81c1a899d';
64
+ expect(parseSlidesIdFromResult([notebookId, [[sourceId]], [slidesId]], [notebookId, sourceId])).toBe(slidesId);
65
+ });
66
+
67
+ it('refuses to trigger remote slide generation without --execute', async () => {
68
+ const command = getRegistry().get('notebooklm/generate-slides');
69
+ const page = { goto: vi.fn() };
70
+ await expect(command.func(page, {
71
+ notebook: '17e2b882-6a01-4c6c-9262-0738dfa2abee',
72
+ })).rejects.toThrow(ArgumentError);
73
+ expect(page.goto).not.toHaveBeenCalled();
74
+ });
75
+ });
@@ -23,18 +23,18 @@ describe('notebooklm open', () => {
23
23
  mockRequireNotebooklmSession.mockReset();
24
24
  mockRequireNotebooklmSession.mockResolvedValue(undefined);
25
25
  mockGetNotebooklmPageState.mockResolvedValue({
26
- url: 'https://notebooklm.google.com/notebook/nb-demo',
26
+ url: 'https://notebooklm.google.com/notebook/17e2b882-1234-1234-1234-abcdef012345',
27
27
  title: 'Browser Automation',
28
28
  hostname: 'notebooklm.google.com',
29
29
  kind: 'notebook',
30
- notebookId: 'nb-demo',
30
+ notebookId: '17e2b882-1234-1234-1234-abcdef012345',
31
31
  loginRequired: false,
32
32
  notebookCount: 1,
33
33
  });
34
34
  mockReadCurrentNotebooklm.mockResolvedValue({
35
- id: 'nb-demo',
35
+ id: '17e2b882-1234-1234-1234-abcdef012345',
36
36
  title: 'Browser Automation',
37
- url: 'https://notebooklm.google.com/notebook/nb-demo',
37
+ url: 'https://notebooklm.google.com/notebook/17e2b882-1234-1234-1234-abcdef012345',
38
38
  source: 'current-page',
39
39
  });
40
40
  });
@@ -43,12 +43,12 @@ describe('notebooklm open', () => {
43
43
  goto: vi.fn(async () => { }),
44
44
  wait: vi.fn(async () => { }),
45
45
  };
46
- const result = await command.func(page, { notebook: 'nb-demo' });
47
- expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/nb-demo');
46
+ const result = await command.func(page, { notebook: '17e2b882-1234-1234-1234-abcdef012345' });
47
+ expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/17e2b882-1234-1234-1234-abcdef012345');
48
48
  expect(result).toEqual([{
49
- id: 'nb-demo',
49
+ id: '17e2b882-1234-1234-1234-abcdef012345',
50
50
  title: 'Browser Automation',
51
- url: 'https://notebooklm.google.com/notebook/nb-demo',
51
+ url: 'https://notebooklm.google.com/notebook/17e2b882-1234-1234-1234-abcdef012345',
52
52
  source: 'current-page',
53
53
  }]);
54
54
  });
@@ -57,7 +57,7 @@ describe('notebooklm open', () => {
57
57
  goto: vi.fn(async () => { }),
58
58
  wait: vi.fn(async () => { }),
59
59
  };
60
- await command.func(page, { notebook: 'https://notebooklm.google.com/notebook/nb-demo?pli=1' });
61
- expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/nb-demo');
60
+ await command.func(page, { notebook: 'https://notebooklm.google.com/notebook/17e2b882-1234-1234-1234-abcdef012345?pli=1' });
61
+ expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/17e2b882-1234-1234-1234-abcdef012345');
62
62
  });
63
63
  });
@@ -1,5 +1,13 @@
1
1
  import { AuthRequiredError, CliError } from '@jackwener/opencli/errors';
2
2
  import { NOTEBOOKLM_DOMAIN } from './shared.js';
3
+
4
+ export function unwrapNotebooklmEvaluateResult(payload) {
5
+ if (payload && typeof payload === 'object' && !Array.isArray(payload) && 'session' in payload && 'data' in payload) {
6
+ return payload.data;
7
+ }
8
+ return payload;
9
+ }
10
+
3
11
  export function extractNotebooklmPageAuthFromHtml(html, sourcePath = '/', preferredTokens) {
4
12
  const csrfMatch = html.match(/"SNlM0e":"([^"]+)"/);
5
13
  const sessionMatch = html.match(/"FdrFJe":"([^"]+)"/);
@@ -8,26 +16,30 @@ export function extractNotebooklmPageAuthFromHtml(html, sourcePath = '/', prefer
8
16
  if (!csrfToken || !sessionId) {
9
17
  throw new CliError('NOTEBOOKLM_TOKENS', 'NotebookLM page tokens were not found in the current page HTML', 'Open the NotebookLM notebook page in Chrome, wait for it to finish loading, then retry with --verbose if it still fails.');
10
18
  }
11
- return { csrfToken, sessionId, sourcePath: sourcePath || '/' };
19
+ return { csrfToken, sessionId, sourcePath: sourcePath || '/', authuser: preferredTokens?.authuser ?? '' };
12
20
  }
13
21
  async function probeNotebooklmPageAuth(page) {
14
- const raw = await page.evaluate(`(() => {
22
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
15
23
  const wiz = window.WIZ_global_data || {};
16
24
  const html = document.documentElement.innerHTML;
25
+ const authMatch = (location.search || '').match(/[?&]authuser=(\\d+)/);
26
+ const pathMatch = (location.pathname || '').match(/^\\/u\\/(\\d+)\\//);
17
27
  return {
18
28
  html,
19
29
  sourcePath: location.pathname || '/',
20
30
  readyState: document.readyState || '',
21
31
  csrfToken: typeof wiz.SNlM0e === 'string' ? wiz.SNlM0e : '',
22
32
  sessionId: typeof wiz.FdrFJe === 'string' ? wiz.FdrFJe : '',
33
+ authuser: authMatch ? authMatch[1] : (pathMatch ? pathMatch[1] : ''),
23
34
  };
24
- })()`);
35
+ })()`));
25
36
  return {
26
37
  html: String(raw?.html ?? ''),
27
38
  sourcePath: String(raw?.sourcePath ?? '/'),
28
39
  readyState: String(raw?.readyState ?? ''),
29
40
  csrfToken: String(raw?.csrfToken ?? ''),
30
41
  sessionId: String(raw?.sessionId ?? ''),
42
+ authuser: String(raw?.authuser ?? ''),
31
43
  };
32
44
  }
33
45
  export async function getNotebooklmPageAuth(page) {
@@ -35,7 +47,7 @@ export async function getNotebooklmPageAuth(page) {
35
47
  for (let attempt = 0; attempt < 2; attempt += 1) {
36
48
  const probe = await probeNotebooklmPageAuth(page);
37
49
  try {
38
- return extractNotebooklmPageAuthFromHtml(probe.html, probe.sourcePath, { csrfToken: probe.csrfToken, sessionId: probe.sessionId });
50
+ return extractNotebooklmPageAuthFromHtml(probe.html, probe.sourcePath, { csrfToken: probe.csrfToken, sessionId: probe.sessionId, authuser: probe.authuser });
39
51
  }
40
52
  catch (error) {
41
53
  lastError = error;
@@ -130,7 +142,7 @@ export async function fetchNotebooklmInPage(page, url, options = {}) {
130
142
  const method = options.method ?? 'GET';
131
143
  const headers = options.headers ?? {};
132
144
  const body = options.body ?? '';
133
- const raw = await page.evaluate(`(async () => {
145
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(async () => {
134
146
  const request = {
135
147
  url: ${JSON.stringify(url)},
136
148
  method: ${JSON.stringify(method)},
@@ -151,7 +163,7 @@ export async function fetchNotebooklmInPage(page, url, options = {}) {
151
163
  body: await response.text(),
152
164
  finalUrl: response.url,
153
165
  };
154
- })()`);
166
+ })()`));
155
167
  return {
156
168
  ok: Boolean(raw?.ok),
157
169
  status: Number(raw?.status ?? 0),
@@ -162,8 +174,10 @@ export async function fetchNotebooklmInPage(page, url, options = {}) {
162
174
  export async function callNotebooklmRpc(page, rpcId, params, options = {}) {
163
175
  const auth = await getNotebooklmPageAuth(page);
164
176
  const requestBody = buildNotebooklmRpcBody(rpcId, params, auth.csrfToken);
177
+ const authuser = auth.authuser || '';
165
178
  const url = `https://${NOTEBOOKLM_DOMAIN}/_/LabsTailwindUi/data/batchexecute` +
166
179
  `?rpcids=${rpcId}&source-path=${encodeURIComponent(auth.sourcePath)}` +
180
+ (authuser ? `&authuser=${encodeURIComponent(authuser)}` : '') +
167
181
  `&hl=${encodeURIComponent(options.hl ?? 'en')}` +
168
182
  `&f.sid=${encodeURIComponent(auth.sessionId)}&rt=c`;
169
183
  const response = await fetchNotebooklmInPage(page, url, {
@@ -1,7 +1,13 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
  import { AuthRequiredError } from '@jackwener/opencli/errors';
3
- import { buildNotebooklmRpcBody, extractNotebooklmRpcResult, getNotebooklmPageAuth, parseNotebooklmChunkedResponse, } from './rpc.js';
3
+ import { buildNotebooklmRpcBody, extractNotebooklmRpcResult, getNotebooklmPageAuth, parseNotebooklmChunkedResponse, unwrapNotebooklmEvaluateResult, } from './rpc.js';
4
4
  describe('notebooklm rpc transport', () => {
5
+ it('unwraps Browser Bridge evaluate envelopes', () => {
6
+ const data = { ok: true };
7
+ expect(unwrapNotebooklmEvaluateResult({ session: 'site:notebooklm:abc', data })).toBe(data);
8
+ expect(unwrapNotebooklmEvaluateResult(data)).toBe(data);
9
+ });
10
+
5
11
  it('extracts auth tokens from the page html via page evaluation', async () => {
6
12
  const page = {
7
13
  evaluate: vi.fn(async (script) => {
@@ -16,9 +22,27 @@ describe('notebooklm rpc transport', () => {
16
22
  csrfToken: 'csrf-123',
17
23
  sessionId: 'sess-456',
18
24
  sourcePath: '/',
25
+ authuser: '',
19
26
  });
20
27
  expect(page.evaluate).toHaveBeenCalledTimes(1);
21
28
  });
29
+ it('extracts auth tokens when page evaluation is wrapped in a Browser Bridge envelope', async () => {
30
+ const page = {
31
+ evaluate: vi.fn(async () => ({
32
+ session: 'site:notebooklm:abc',
33
+ data: {
34
+ html: '<html>"SNlM0e":"csrf-123","FdrFJe":"sess-456"</html>',
35
+ sourcePath: '/notebook/nb-demo',
36
+ },
37
+ })),
38
+ };
39
+ await expect(getNotebooklmPageAuth(page)).resolves.toEqual({
40
+ csrfToken: 'csrf-123',
41
+ sessionId: 'sess-456',
42
+ sourcePath: '/notebook/nb-demo',
43
+ authuser: '',
44
+ });
45
+ });
22
46
  it('falls back to WIZ_global_data tokens when html regex data is missing', async () => {
23
47
  const page = {
24
48
  evaluate: vi.fn(async () => ({
@@ -33,6 +57,7 @@ describe('notebooklm rpc transport', () => {
33
57
  csrfToken: 'csrf-wiz',
34
58
  sessionId: 'sess-wiz',
35
59
  sourcePath: '/notebook/nb-demo',
60
+ authuser: '',
36
61
  });
37
62
  });
38
63
  it('retries token extraction once when the first probe returns no tokens', async () => {
@@ -58,6 +83,7 @@ describe('notebooklm rpc transport', () => {
58
83
  csrfToken: 'csrf-123',
59
84
  sessionId: 'sess-456',
60
85
  sourcePath: '/notebook/nb-demo',
86
+ authuser: '',
61
87
  });
62
88
  expect(page.evaluate).toHaveBeenCalledTimes(2);
63
89
  });
@@ -1,6 +1,6 @@
1
- import { AuthRequiredError, CliError } from '@jackwener/opencli/errors';
1
+ import { ArgumentError, AuthRequiredError, CliError, CommandExecutionError } from '@jackwener/opencli/errors';
2
2
  import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_HOME_URL, } from './shared.js';
3
- import { callNotebooklmRpc, getNotebooklmPageAuth, } from './rpc.js';
3
+ import { callNotebooklmRpc, getNotebooklmPageAuth, unwrapNotebooklmEvaluateResult, } from './rpc.js';
4
4
  export { buildNotebooklmRpcBody, extractNotebooklmRpcResult, fetchNotebooklmInPage, getNotebooklmPageAuth, parseNotebooklmChunkedResponse, stripNotebooklmAntiXssi, } from './rpc.js';
5
5
  const NOTEBOOKLM_LIST_RPC_ID = 'wXbhsf';
6
6
  const NOTEBOOKLM_NOTEBOOK_DETAIL_RPC_ID = 'rLM1Ne';
@@ -13,28 +13,61 @@ function unwrapNotebooklmSingletonResult(result) {
13
13
  }
14
14
  return current;
15
15
  }
16
+ export function isPlainObject(value) {
17
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
18
+ }
16
19
  export function parseNotebooklmIdFromUrl(url) {
17
20
  const match = url.match(/\/notebook\/([^/?#]+)/);
18
21
  return match?.[1] ?? '';
19
22
  }
23
+ const NOTEBOOK_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24
+ function ensureNotebookUuid(candidate) {
25
+ if (!NOTEBOOK_UUID_RE.test(candidate)) {
26
+ throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', `NotebookLM notebook id "${candidate}" is not a valid UUID`, 'Pass a notebook id from `opencli notebooklm list` or a full notebook URL like https://notebooklm.google.com/notebook/<uuid>.');
27
+ }
28
+ return candidate;
29
+ }
20
30
  export function parseNotebooklmNotebookTarget(value) {
21
31
  const normalized = value.trim();
22
32
  if (!normalized) {
23
33
  throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', 'NotebookLM notebook id is required', 'Pass a notebook id from `opencli notebooklm list` or a full notebook URL.');
24
34
  }
25
35
  if (/^https?:\/\//i.test(normalized)) {
36
+ let parsed;
37
+ try {
38
+ parsed = new URL(normalized);
39
+ }
40
+ catch {
41
+ throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', 'NotebookLM notebook URL is invalid', 'Pass a full NotebookLM notebook URL like https://notebooklm.google.com/notebook/<uuid>.');
42
+ }
43
+ if (parsed.protocol !== 'https:' || parsed.hostname !== NOTEBOOKLM_DOMAIN || parsed.username || parsed.password || parsed.port) {
44
+ throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', 'NotebookLM notebook URL must be a canonical https://notebooklm.google.com URL', 'Pass a notebook id from `opencli notebooklm list` or a full NotebookLM notebook URL.');
45
+ }
26
46
  const notebookId = parseNotebooklmIdFromUrl(normalized);
27
- if (notebookId)
28
- return notebookId;
29
- throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', 'NotebookLM notebook URL is invalid', 'Pass a full NotebookLM notebook URL like https://notebooklm.google.com/notebook/<id>.');
47
+ if (!notebookId) {
48
+ throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', 'NotebookLM notebook URL is invalid', 'Pass a full NotebookLM notebook URL like https://notebooklm.google.com/notebook/<uuid>.');
49
+ }
50
+ return ensureNotebookUuid(notebookId);
30
51
  }
31
52
  const pathMatch = normalized.match(/(?:^|\/)notebook\/([^/?#]+)/);
32
53
  if (pathMatch?.[1])
33
- return pathMatch[1];
34
- return normalized;
54
+ return ensureNotebookUuid(pathMatch[1]);
55
+ return ensureNotebookUuid(normalized);
56
+ }
57
+ export function getNotebooklmAuthuser() {
58
+ const v = process.env.OPENCLI_NOTEBOOKLM_AUTHUSER;
59
+ return typeof v === 'string' && /^\d+$/.test(v) ? v : '';
60
+ }
61
+ export function requireNotebooklmExecute(value, action) {
62
+ if (value !== true) {
63
+ throw new ArgumentError(`Refusing to ${action}: pass --execute to perform this NotebookLM write`);
64
+ }
35
65
  }
36
66
  export function buildNotebooklmNotebookUrl(notebookId) {
37
- return new URL(`/notebook/${encodeURIComponent(notebookId)}`, NOTEBOOKLM_HOME_URL).toString();
67
+ const u = new URL(`/notebook/${encodeURIComponent(notebookId)}`, NOTEBOOKLM_HOME_URL);
68
+ const authuser = getNotebooklmAuthuser();
69
+ if (authuser) u.searchParams.set('authuser', authuser);
70
+ return u.toString();
38
71
  }
39
72
  export function classifyNotebooklmPage(url) {
40
73
  try {
@@ -401,6 +434,42 @@ export async function getNotebooklmDetailViaRpc(page) {
401
434
  const rpc = await callNotebooklmRpc(page, NOTEBOOKLM_NOTEBOOK_DETAIL_RPC_ID, [state.notebookId, null, [2], null, 0]);
402
435
  return parseNotebooklmNotebookDetailResult(rpc.result);
403
436
  }
437
+ export async function getNotebooklmNotebookDetailById(page, notebookId) {
438
+ const rpc = await callNotebooklmRpc(page, NOTEBOOKLM_NOTEBOOK_DETAIL_RPC_ID, [notebookId, null, [2], null, 0]);
439
+ return { detail: parseNotebooklmNotebookDetailResult(rpc.result), sources: parseNotebooklmSourceListResult(rpc.result) };
440
+ }
441
+ export async function verifyNotebooklmNotebookExists(page, notebookId, action) {
442
+ try {
443
+ const { detail } = await getNotebooklmNotebookDetailById(page, notebookId);
444
+ if (!detail || detail.id !== notebookId) {
445
+ throw new CommandExecutionError(`NotebookLM ${action} succeeded but the notebook ${notebookId} was not found in the post-write verification`);
446
+ }
447
+ return detail;
448
+ }
449
+ catch (error) {
450
+ if (error instanceof AuthRequiredError || error instanceof CommandExecutionError)
451
+ throw error;
452
+ throw new CommandExecutionError(`NotebookLM ${action} post-write verification failed: ${error?.message || error}`);
453
+ }
454
+ }
455
+ export async function verifyNotebooklmSourceAdded(page, notebookId, sourceId, action) {
456
+ try {
457
+ const { detail, sources } = await getNotebooklmNotebookDetailById(page, notebookId);
458
+ if (!detail || detail.id !== notebookId) {
459
+ throw new CommandExecutionError(`NotebookLM ${action} succeeded but the notebook ${notebookId} was not found in the post-write verification`);
460
+ }
461
+ const matched = sources.find((s) => s.id === sourceId);
462
+ if (!matched) {
463
+ throw new CommandExecutionError(`NotebookLM ${action} succeeded but source ${sourceId} did not appear in the notebook's source list`);
464
+ }
465
+ return matched;
466
+ }
467
+ catch (error) {
468
+ if (error instanceof AuthRequiredError || error instanceof CommandExecutionError)
469
+ throw error;
470
+ throw new CommandExecutionError(`NotebookLM ${action} post-write verification failed: ${error?.message || error}`);
471
+ }
472
+ }
404
473
  export async function listNotebooklmSourcesViaRpc(page) {
405
474
  const state = await getNotebooklmPageState(page);
406
475
  if (state.kind !== 'notebook' || !state.notebookId)
@@ -434,7 +503,7 @@ export async function listNotebooklmNotesFromPage(page) {
434
503
  const state = await getNotebooklmPageState(page);
435
504
  if (state.kind !== 'notebook' || !state.notebookId)
436
505
  return [];
437
- const raw = await page.evaluate(`(() => {
506
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
438
507
  return Array.from(document.querySelectorAll('artifact-library-note')).map((node) => {
439
508
  const titleNode = node.querySelector('.artifact-title');
440
509
  return {
@@ -442,7 +511,7 @@ export async function listNotebooklmNotesFromPage(page) {
442
511
  text: (node.innerText || node.textContent || '').replace(/\\s+/g, ' ').trim(),
443
512
  };
444
513
  });
445
- })()`);
514
+ })()`));
446
515
  if (!Array.isArray(raw) || raw.length === 0)
447
516
  return [];
448
517
  return parseNotebooklmNoteListRawRows(raw, state.notebookId, state.url || `https://${NOTEBOOKLM_DOMAIN}/notebook/${state.notebookId}`);
@@ -451,13 +520,13 @@ export async function readNotebooklmSummaryFromPage(page) {
451
520
  const state = await getNotebooklmPageState(page);
452
521
  if (state.kind !== 'notebook' || !state.notebookId)
453
522
  return null;
454
- const raw = await page.evaluate(`(() => {
523
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
455
524
  const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
456
525
  const title = normalize(document.querySelector('.notebook-title, h1, [data-testid="notebook-title"]')?.textContent || document.title || '');
457
526
  const summaryNode = document.querySelector('.notebook-summary, .summary-content, [class*="summary"]');
458
527
  const summary = normalize(summaryNode?.textContent || '');
459
528
  return { title, summary };
460
- })()`);
529
+ })()`));
461
530
  return parseNotebooklmSummaryRawRow(raw, state.notebookId, state.url || `https://${NOTEBOOKLM_DOMAIN}/notebook/${state.notebookId}`);
462
531
  }
463
532
  export async function getNotebooklmSummaryViaRpc(page) {
@@ -499,7 +568,7 @@ export async function readNotebooklmVisibleNoteFromPage(page) {
499
568
  const state = await getNotebooklmPageState(page);
500
569
  if (state.kind !== 'notebook' || !state.notebookId)
501
570
  return null;
502
- const raw = await page.evaluate(`(() => {
571
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
503
572
  const normalizeText = (value) => (value || '').replace(/\\u00a0/g, ' ').replace(/\\r\\n/g, '\\n').trim();
504
573
  const titleNode = document.querySelector('.note-header__editable-title');
505
574
  const title = titleNode instanceof HTMLInputElement || titleNode instanceof HTMLTextAreaElement
@@ -516,7 +585,7 @@ export async function readNotebooklmVisibleNoteFromPage(page) {
516
585
  title: normalizeText(title),
517
586
  content: normalizeText(content),
518
587
  };
519
- })()`);
588
+ })()`));
520
589
  return parseNotebooklmVisibleNoteRawRow(raw, state.notebookId, state.url || `https://${NOTEBOOKLM_DOMAIN}/notebook/${state.notebookId}`);
521
590
  }
522
591
  export async function ensureNotebooklmHome(page) {
@@ -526,11 +595,18 @@ export async function ensureNotebooklmHome(page) {
526
595
  const currentKind = currentUrl ? classifyNotebooklmPage(currentUrl) : 'unknown';
527
596
  if (currentKind === 'home')
528
597
  return;
529
- await page.goto(NOTEBOOKLM_HOME_URL);
530
- await page.wait(2);
598
+ const authuser = getNotebooklmAuthuser();
599
+ const target = authuser ? `${NOTEBOOKLM_HOME_URL}?authuser=${encodeURIComponent(authuser)}` : NOTEBOOKLM_HOME_URL;
600
+ try {
601
+ await page.goto(target);
602
+ await page.wait(2);
603
+ }
604
+ catch (error) {
605
+ throw new CommandExecutionError(`Failed to open NotebookLM home: ${error?.message || error}`);
606
+ }
531
607
  }
532
608
  export async function getNotebooklmPageState(page) {
533
- const raw = await page.evaluate(`(() => {
609
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
534
610
  const url = window.location.href;
535
611
  const title = document.title || '';
536
612
  const hostname = window.location.hostname || '';
@@ -557,7 +633,7 @@ export async function getNotebooklmPageState(page) {
557
633
  .reduce((count, href, index, list) => list.indexOf(href) === index ? count + 1 : count, 0);
558
634
 
559
635
  return { url, title, hostname, kind, notebookId, loginRequired, notebookCount, path };
560
- })()`);
636
+ })()`));
561
637
  const state = {
562
638
  url: String(raw?.url ?? ''),
563
639
  title: normalizeNotebooklmTitle(raw?.title, 'NotebookLM'),
@@ -582,7 +658,7 @@ export async function getNotebooklmPageState(page) {
582
658
  return state;
583
659
  }
584
660
  export async function readCurrentNotebooklm(page) {
585
- const raw = await page.evaluate(`(() => {
661
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
586
662
  const url = window.location.href;
587
663
  const match = url.match(/\\/notebook\\/([^/?#]+)/);
588
664
  if (!match) return null;
@@ -595,7 +671,7 @@ export async function readCurrentNotebooklm(page) {
595
671
  url,
596
672
  source: 'current-page',
597
673
  };
598
- })()`);
674
+ })()`));
599
675
  if (!raw)
600
676
  return null;
601
677
  return {
@@ -608,7 +684,7 @@ export async function readCurrentNotebooklm(page) {
608
684
  };
609
685
  }
610
686
  export async function listNotebooklmLinks(page) {
611
- const raw = await page.evaluate(`(() => {
687
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
612
688
  const rows = [];
613
689
  const seen = new Set();
614
690
 
@@ -656,7 +732,7 @@ export async function listNotebooklmLinks(page) {
656
732
  }
657
733
 
658
734
  return rows;
659
- })()`);
735
+ })()`));
660
736
  if (!Array.isArray(raw))
661
737
  return [];
662
738
  return raw
@@ -671,7 +747,7 @@ export async function listNotebooklmLinks(page) {
671
747
  .filter((row) => row.id && row.url);
672
748
  }
673
749
  export async function listNotebooklmSourcesFromPage(page) {
674
- const raw = await page.evaluate(`(() => {
750
+ const raw = unwrapNotebooklmEvaluateResult(await page.evaluate(`(() => {
675
751
  const notebookMatch = window.location.href.match(/\\/notebook\\/([^/?#]+)/);
676
752
  const notebookId = notebookMatch ? notebookMatch[1] : '';
677
753
  if (!notebookId) return [];
@@ -721,7 +797,7 @@ export async function listNotebooklmSourcesFromPage(page) {
721
797
  });
722
798
  }
723
799
  return rows;
724
- })()`);
800
+ })()`));
725
801
  if (!Array.isArray(raw))
726
802
  return [];
727
803
  return raw.filter((row) => row.id && row.title);