Skip to content

Commit fbb577c

Browse files
committed
feat(notebooklm): add drive sources and infographic support
1 parent 7040a0f commit fbb577c

12 files changed

Lines changed: 1230 additions & 7 deletions

findings.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,61 @@ High-level groups verified from the source repo:
11431143
- `status = ready`
11441144
- `status_code = 2`
11451145

1146+
## 2026-04-01 Source Add-Drive
1147+
1148+
- 这轮范围严格限制在 `source/add-drive`,不碰:
1149+
- `source add-research`
1150+
- notes / notebook CRUD / share / generate / download / artifact / framework
1151+
- 上游取证结论明确:
1152+
- `notebooklm-cdp-cli``source add-drive` 输入形态是:
1153+
- `file_id`
1154+
- `title`
1155+
- `mime_type`
1156+
- `notebooklm-py``client.sources.add_drive(...)` 走的不是 Drive URL 解析,也不是 DOM 上传
1157+
- 实际 RPC 仍然是 `izAoDd`(与 add-text / add-url 共用)
1158+
- 上游稳定 params 形状:
1159+
- `source_data = [[file_id, mime_type, 1, title], null x9, 1]`
1160+
- `params = [[source_data], notebook_id, [2], [1, null x9, [1]]]`
1161+
- 因此 opencli 这轮实现刻意收口为:
1162+
- 只支持 raw Google Drive `file_id`
1163+
- 需要显式 `title`
1164+
- `--mime-type` 允许传原始 MIME string
1165+
- 当前已知稳定值:
1166+
- `application/vnd.google-apps.document`
1167+
- `application/vnd.google-apps.presentation`
1168+
- `application/vnd.google-apps.spreadsheet`
1169+
- `application/pdf`
1170+
- 不支持把 `docs.google.com/...``drive.google.com/...` URL 自动解析成 file id
1171+
- opencli 落地链路:
1172+
- `buildNotebooklmAddDriveParams(...)`
1173+
- `addNotebooklmDriveSourceViaRpc(...)`
1174+
- `source/add-drive` 命令层只负责取当前 notebook、校验输入、调用 RPC helper
1175+
1176+
### Verification
1177+
1178+
- red -> green:
1179+
- `npx vitest run src\\clis\\notebooklm\\source-add-drive.test.ts --reporter=verbose`
1180+
- `npx vitest run src\\clis\\notebooklm\\utils.test.ts -t "builds add-drive rpc params with file id, mime type, and display title in the drive slot" --reporter=verbose`
1181+
- related ingest regression:
1182+
- `npx vitest run src\\clis\\notebooklm\\source-add-drive.test.ts src\\clis\\notebooklm\\source-add-text.test.ts src\\clis\\notebooklm\\source-add-url.test.ts src\\clis\\notebooklm\\source-add-file.test.ts --reporter=verbose`
1183+
- type/build:
1184+
- `npx tsc --noEmit`
1185+
- `npm run build`
1186+
- type/build 当前失败,但根因与 add-drive 无关:
1187+
- 缺失 / 未实现的 infographic 相关文件与导出:
1188+
- `src/clis/notebooklm/download-infographic.test.ts`
1189+
- `src/clis/notebooklm/generate-infographic.test.ts`
1190+
- `buildNotebooklmGenerateInfographicParams`
1191+
- `extractNotebooklmInfographicDownloadUrl`
1192+
- live 尝试结果:
1193+
- `node dist/main.js notebooklm list -f json``[]`
1194+
- `node dist/main.js notebooklm status -f json` → 当前停在 NotebookLM home,`authuser=3`,无 visible notebook tab
1195+
- `npx tsx src/main.ts notebooklm create "opencli add-drive smoke 2026-04-01" -f json` → NotebookLM RPC HTTP 400
1196+
- 因此这轮 live 的真实阻塞点是:
1197+
- 当前浏览器会话没有可绑定的 notebook 上下文
1198+
- 同时也没有稳定、现成可验证的 Drive `file_id`
1199+
- 这不是 `source/add-drive` 的静态链路不清楚,而是运行态前置条件不足
1200+
11461201
## 2026-03-31 From-0 Integration Test Results (Historical Only)
11471202

11481203
### Test Environment
@@ -1423,3 +1478,68 @@ High-level groups verified from the source repo:
14231478
- 新账号当前 live 结果证明:
14241479
- 这套增强至少已经把“原先只看到 failed”的返回,提升成了“明确是 `UserDisplayableError` 但无可读 message,所以归到 `generation_failed_unknown`
14251480
- 若将来服务端重新带出可读 quota / eligibility / content 文案,当前分类器已经能直接映射到更具体的错误类型
1481+
1482+
## Infographic Generate / Download Findings
1483+
1484+
- 上游原仓库和当前 `.venv` client 已确认 infographic 仍走:
1485+
- generate RPC: `R7cb6c`
1486+
- artifact type code: `7`
1487+
- download data source: `gArtLc` raw artifact list,而不是 export RPC
1488+
- 当前最小 generate payload 已确认可按上游稳定形状构造:
1489+
- `[[2], notebook_id, [null, null, 7, source_ids_triple, ..., [[instructions, language, null, orientation_code, detail_code, style_code]]]]`
1490+
- 当前可用的 infographic 选项码位已确认:
1491+
- orientation:
1492+
- `landscape -> 1`
1493+
- `portrait -> 2`
1494+
- `square -> 3`
1495+
- detail:
1496+
- `concise -> 1`
1497+
- `standard -> 2`
1498+
- `detailed -> 3`
1499+
- style:
1500+
- `auto_select -> 1`
1501+
- `sketch_note -> 2`
1502+
- `professional -> 3`
1503+
- `bento_grid -> 4`
1504+
- `editorial -> 5`
1505+
- `instructional -> 6`
1506+
- `bricks -> 7`
1507+
- `clay -> 8`
1508+
- `anime -> 9`
1509+
- `kawaii -> 10`
1510+
- `scientific -> 11`
1511+
- `gArtLc` raw row 里的 infographic 下载 URL 形态也已确认:
1512+
- 不像 slide deck 那样有固定 `pdf/pptx` 槽位
1513+
- 更像深层 metadata / content 列表里嵌的单个 PNG URL
1514+
- 当前 opencli 采用的最小稳定提取策略与上游 `_find_infographic_url(...)` 一致:从 raw row 尾部反向扫描第一个可用 image URL
1515+
1516+
### Live Verification
1517+
1518+
- 已手动把 browser bridge workspace 导航回 rich notebook:
1519+
- `a45591ed-37bd-4038-a131-141a295c024b`
1520+
- live `generate infographic`
1521+
- 返回:
1522+
- `artifact_id: null`
1523+
- `artifact_type: "infographic"`
1524+
- `status: "failed"`
1525+
- `error_type: "generation_failed_unknown"`
1526+
- `message: null`
1527+
- 这说明当前账号/样本上,服务端在提交阶段直接拒绝 infographic,但没有返回可读 message
1528+
- live `download list`
1529+
- 已正确列出历史 infographic artifact:
1530+
- `artifact_id: 6a31b7d3-7b9c-402d-a4dc-fcc396430de4`
1531+
- `artifact_type: "infographic"`
1532+
- `download_variants: ["png"]`
1533+
- live `download infographic --artifact-id 6a31b7d3-7b9c-402d-a4dc-fcc396430de4`
1534+
- 成功写出:
1535+
- `E:\web\opencli\tmp\notebooklm-infographic-smoke.png`
1536+
- 返回 direct PNG URL(`lh3.googleusercontent.com/notebooklm/...`
1537+
1538+
### Practical Conclusion
1539+
1540+
- infographic 的 `type / payload / artifact` 形态已经查清。
1541+
- `download/infographic` 已可独立闭环,不需要 `artifact/*` 作为前置。
1542+
- `generate/infographic` 当前也已接入,但 live 行为与 audio / slide-deck 一样,现阶段最保守的结论仍然是:
1543+
- 服务端拒绝
1544+
- 错误分类只能稳定落到 `generation_failed_unknown`
1545+
- 当前没有证据支持继续扩 artifact 子系统来解决它

progress.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,75 @@
22

33
## 2026-03-31
44

5+
### Infographic Generate / Download
6+
7+
- Scoped this round strictly to NotebookLM infographic generation/download:
8+
- `generate infographic`
9+
- `download infographic`
10+
- Re-read the existing generate/download helpers in:
11+
- `generate-report.ts`
12+
- `generate-audio.ts`
13+
- `generate-slide-deck.ts`
14+
- `download-list.ts`
15+
- `download-report.ts`
16+
- `download-slide-deck.ts`
17+
- `utils.ts`
18+
- `rpc.ts`
19+
- `shared.ts`
20+
- Re-checked upstream command surface and packaged implementation:
21+
- old CLI still exposes `generate infographic` / `download infographic`
22+
- upstream artifact type code is `7`
23+
- generate still uses `R7cb6c`
24+
- download still uses `gArtLc` raw artifact list plus a deep image URL extractor
25+
- Kept the implementation narrow:
26+
- added `generate/infographic`
27+
- added `download/infographic`
28+
- extended shared artifact unions/types with `infographic`
29+
- added infographic payload builder and PNG URL extractor
30+
- extended `download/list` / artifact-id completion to recognize `infographic`
31+
- reused the existing generate failure classification and HTTP download path
32+
- Did not expand into:
33+
- `quiz`
34+
- `flashcards`
35+
- `data-table`
36+
- `artifact/*`
37+
- framework / command-tree changes
38+
39+
### Verification
40+
41+
- red -> green tests:
42+
- `npx vitest run src\\clis\\notebooklm\\generate-infographic.test.ts src\\clis\\notebooklm\\download-infographic.test.ts --reporter=verbose`
43+
- `npx vitest run src\\clis\\notebooklm\\utils.test.ts -t "infographic|supported downloadable artifact rows|filters artifact-id completion candidates" --reporter=verbose`
44+
- broader related tests:
45+
- `npx vitest run src\\clis\\notebooklm\\generate-report.test.ts src\\clis\\notebooklm\\generate-audio.test.ts src\\clis\\notebooklm\\generate-slide-deck.test.ts src\\clis\\notebooklm\\generate-infographic.test.ts src\\clis\\notebooklm\\download-list.test.ts src\\clis\\notebooklm\\download-report.test.ts src\\clis\\notebooklm\\download-slide-deck.test.ts src\\clis\\notebooklm\\download-infographic.test.ts --reporter=verbose`
46+
- type/build:
47+
- `npx tsc --noEmit`
48+
- `npm run build`
49+
- command discovery/help smoke:
50+
- `node dist/main.js list -f json | Select-String 'generate/infographic|download/infographic'`
51+
- `node dist/main.js notebooklm generate infographic --help`
52+
- `node dist/main.js notebooklm download infographic --help`
53+
- live:
54+
- initial `node dist/main.js notebooklm status -f json` showed browser bridge connected but parked on NotebookLM home
55+
- daemon navigate moved workspace `site:notebooklm` to:
56+
- `https://notebooklm.google.com/notebook/a45591ed-37bd-4038-a131-141a295c024b`
57+
- `node dist/main.js notebooklm generate infographic -f json`
58+
- returned `status: "failed"`, `artifact_id: null`, `error_type: "generation_failed_unknown"`
59+
- `node dist/main.js notebooklm download list -f json`
60+
- returned one completed infographic artifact:
61+
- `6a31b7d3-7b9c-402d-a4dc-fcc396430de4`
62+
- `download_variants: ["png"]`
63+
- `node dist/main.js notebooklm download infographic "E:\\web\\opencli\\tmp\\notebooklm-infographic-smoke.png" --artifact-id 6a31b7d3-7b9c-402d-a4dc-fcc396430de4 -f json`
64+
- succeeded
65+
66+
### Outcome
67+
68+
- `generate infographic` is implemented and uses the verified `R7cb6c` payload path.
69+
- `download infographic` is implemented and closes successfully against an existing live artifact.
70+
- Current live boundary is explicit:
71+
- generate is still server-rejected on the current account/notebook sample
72+
- download does not require `artifact/*` as a prerequisite
73+
574
### Session Summary
675

776
- Confirmed `opencli` is the Windows/browser-bridge target repo.
@@ -964,6 +1033,55 @@
9641033
- add-file 创建 source `6143e8b6-cb0d-4b18-9192-fbcd2abbebc1`
9651034
- wait / wait-for-sources 都等到 ready
9661035

1036+
## 2026-04-01 Source Add-Drive
1037+
1038+
- 范围控制:
1039+
- 只做 `source/add-drive`
1040+
- 不扩 `add-research`
1041+
- 不碰 notes / notebook CRUD / share / generate / download / artifact / framework
1042+
- 先取证,再实现:
1043+
- 读取了:
1044+
- `notebooklm-cdp-cli``cli.py` / `notebooklm_ops.py`
1045+
- `notebooklm-py``_sources.py` / `cli/source.py` / `rpc/types.py`
1046+
- 确认上游最小输入是:
1047+
- `file_id`
1048+
- `title`
1049+
- `mime_type`
1050+
- 确认链路是:
1051+
- `izAoDd` RPC
1052+
- 不需要 DOM 点击
1053+
- 不需要 Drive URL 解析
1054+
- TDD:
1055+
1. 新增失败测试:
1056+
- `src/clis/notebooklm/source-add-drive.test.ts`
1057+
- `src/clis/notebooklm/utils.test.ts` 中 add-drive builder 用例
1058+
2. 初次失败点:
1059+
-`source-add-drive.ts`
1060+
-`buildNotebooklmAddDriveParams(...)`
1061+
3. 最小实现:
1062+
- `src/clis/notebooklm/source-add-drive.ts`
1063+
- `src/clis/notebooklm/utils.ts`
1064+
- 当前命令边界:
1065+
- positional:
1066+
- `<file-id>`
1067+
- `<title>`
1068+
- optional:
1069+
- `--mime-type`
1070+
- 默认 MIME:
1071+
- `application/vnd.google-apps.document`
1072+
- 验证结果:
1073+
- targeted vitest:
1074+
- `source-add-drive.test.ts` 通过
1075+
- add-drive builder 过滤测试通过
1076+
- source ingest 相关回归测试 `4 files, 4 tests passed`
1077+
- `npx tsc --noEmit` / `npm run build`
1078+
- 失败
1079+
- 原因是 repo 里已存在的 infographic 相关缺件,不是 add-drive 本身
1080+
- live:
1081+
- 尝试建立 smoke notebook 失败:NotebookLM home RPC 400
1082+
- 当前 browser workspace 停在 home,且没有 visible notebook tab
1083+
- 因此本轮未能完成 add-drive live 闭环
1084+
9671085
## 2026-03-31 From-0 Integration Test Summary (9 Modules, Historical Only)
9681086

9691087
### Test Environment
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const {
4+
mockDownloadNotebooklmInfographicViaRpc,
5+
mockEnsureNotebooklmNotebookBinding,
6+
mockGetNotebooklmPageState,
7+
mockRequireNotebooklmSession,
8+
} = vi.hoisted(() => ({
9+
mockDownloadNotebooklmInfographicViaRpc: vi.fn(),
10+
mockEnsureNotebooklmNotebookBinding: 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+
downloadNotebooklmInfographicViaRpc: mockDownloadNotebooklmInfographicViaRpc,
20+
ensureNotebooklmNotebookBinding: mockEnsureNotebooklmNotebookBinding,
21+
getNotebooklmPageState: mockGetNotebooklmPageState,
22+
requireNotebooklmSession: mockRequireNotebooklmSession,
23+
};
24+
});
25+
26+
import { getRegistry } from '../../registry.js';
27+
import './download-infographic.js';
28+
29+
describe('notebooklm download-infographic', () => {
30+
const command = getRegistry().get('notebooklm/download/infographic');
31+
32+
beforeEach(() => {
33+
mockDownloadNotebooklmInfographicViaRpc.mockReset();
34+
mockEnsureNotebooklmNotebookBinding.mockReset();
35+
mockGetNotebooklmPageState.mockReset();
36+
mockRequireNotebooklmSession.mockReset();
37+
38+
mockEnsureNotebooklmNotebookBinding.mockResolvedValue(false);
39+
mockRequireNotebooklmSession.mockResolvedValue(undefined);
40+
mockGetNotebooklmPageState.mockResolvedValue({
41+
url: 'https://notebooklm.google.com/notebook/nb-demo',
42+
title: 'Browser Automation',
43+
hostname: 'notebooklm.google.com',
44+
kind: 'notebook',
45+
notebookId: 'nb-demo',
46+
loginRequired: false,
47+
notebookCount: 1,
48+
});
49+
});
50+
51+
it('downloads the latest completed infographic artifact when artifact id is omitted', async () => {
52+
mockDownloadNotebooklmInfographicViaRpc.mockResolvedValue({
53+
notebook_id: 'nb-demo',
54+
artifact_id: 'infographic-2',
55+
artifact_type: 'infographic',
56+
title: 'Browser Automation Infographic',
57+
output_path: 'E:\\tmp\\browser-automation.png',
58+
created_at: '2026-03-31T12:00:00.000Z',
59+
url: 'https://notebooklm.google.com/notebook/nb-demo',
60+
download_url: 'https://example.com/latest.png',
61+
source: 'rpc+artifact-url',
62+
});
63+
64+
const result = await command!.func!({} as any, { output_path: 'E:\\tmp\\browser-automation.png' });
65+
66+
expect(mockDownloadNotebooklmInfographicViaRpc).toHaveBeenCalledWith(
67+
expect.anything(),
68+
'E:\\tmp\\browser-automation.png',
69+
undefined,
70+
);
71+
expect(result).toEqual([
72+
expect.objectContaining({
73+
artifact_id: 'infographic-2',
74+
artifact_type: 'infographic',
75+
output_path: 'E:\\tmp\\browser-automation.png',
76+
}),
77+
]);
78+
});
79+
80+
it('passes --artifact-id through to the infographic download helper', async () => {
81+
mockDownloadNotebooklmInfographicViaRpc.mockResolvedValue({
82+
notebook_id: 'nb-demo',
83+
artifact_id: 'infographic-1',
84+
artifact_type: 'infographic',
85+
title: 'Browser Automation Infographic',
86+
output_path: 'E:\\tmp\\browser-automation.png',
87+
created_at: '2026-03-30T10:00:00.000Z',
88+
url: 'https://notebooklm.google.com/notebook/nb-demo',
89+
download_url: 'https://example.com/specific.png',
90+
source: 'rpc+artifact-url',
91+
});
92+
93+
await command!.func!({} as any, {
94+
output_path: 'E:\\tmp\\browser-automation.png',
95+
'artifact-id': 'infographic-1',
96+
});
97+
98+
expect(mockDownloadNotebooklmInfographicViaRpc).toHaveBeenCalledWith(
99+
expect.anything(),
100+
'E:\\tmp\\browser-automation.png',
101+
'infographic-1',
102+
);
103+
});
104+
});

0 commit comments

Comments
 (0)