@jackwener/opencli 1.5.8 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +35 -1
  3. package/README.zh-CN.md +17 -1
  4. package/SKILL.md +31 -851
  5. package/autoresearch/baseline-browse.txt +1 -0
  6. package/autoresearch/baseline-skill.txt +1 -0
  7. package/autoresearch/browse-tasks.json +688 -0
  8. package/autoresearch/eval-browse.ts +185 -0
  9. package/autoresearch/eval-skill.ts +248 -0
  10. package/autoresearch/run-browse.sh +9 -0
  11. package/autoresearch/run-skill.sh +9 -0
  12. package/dist/browser/base-page.d.ts +48 -0
  13. package/dist/browser/base-page.js +160 -0
  14. package/dist/browser/cdp.js +4 -106
  15. package/dist/browser/daemon-client.d.ts +20 -7
  16. package/dist/browser/daemon-client.js +39 -39
  17. package/dist/browser/daemon-client.test.js +77 -0
  18. package/dist/browser/discover.d.ts +1 -4
  19. package/dist/browser/discover.js +9 -23
  20. package/dist/browser/errors.d.ts +4 -0
  21. package/dist/browser/errors.js +20 -0
  22. package/dist/browser/index.d.ts +1 -1
  23. package/dist/browser/index.js +1 -1
  24. package/dist/browser/page.d.ts +10 -35
  25. package/dist/browser/page.js +55 -187
  26. package/dist/browser/tabs.js +5 -5
  27. package/dist/browser.test.js +15 -15
  28. package/dist/cli-manifest.json +294 -22
  29. package/dist/cli.js +392 -0
  30. package/dist/clis/amazon/bestsellers.d.ts +21 -0
  31. package/dist/clis/amazon/bestsellers.js +130 -0
  32. package/dist/clis/amazon/bestsellers.test.js +20 -0
  33. package/dist/clis/amazon/discussion.d.ts +20 -0
  34. package/dist/clis/amazon/discussion.js +91 -0
  35. package/dist/clis/amazon/discussion.test.d.ts +1 -0
  36. package/dist/clis/amazon/discussion.test.js +36 -0
  37. package/dist/clis/amazon/offer.d.ts +23 -0
  38. package/dist/clis/amazon/offer.js +140 -0
  39. package/dist/clis/amazon/offer.test.d.ts +1 -0
  40. package/dist/clis/amazon/offer.test.js +29 -0
  41. package/dist/clis/amazon/product.d.ts +18 -0
  42. package/dist/clis/amazon/product.js +92 -0
  43. package/dist/clis/amazon/product.test.d.ts +1 -0
  44. package/dist/clis/amazon/product.test.js +24 -0
  45. package/dist/clis/amazon/search.d.ts +18 -0
  46. package/dist/clis/amazon/search.js +87 -0
  47. package/dist/clis/amazon/search.test.d.ts +1 -0
  48. package/dist/clis/amazon/search.test.js +22 -0
  49. package/dist/clis/amazon/shared.d.ts +64 -0
  50. package/dist/clis/amazon/shared.js +255 -0
  51. package/dist/clis/amazon/shared.test.d.ts +1 -0
  52. package/dist/clis/amazon/shared.test.js +33 -0
  53. package/dist/clis/gemini/ask.d.ts +1 -0
  54. package/dist/clis/gemini/ask.js +40 -0
  55. package/dist/clis/gemini/image.d.ts +1 -0
  56. package/dist/clis/gemini/image.js +105 -0
  57. package/dist/clis/gemini/new.d.ts +1 -0
  58. package/dist/clis/gemini/new.js +20 -0
  59. package/dist/clis/gemini/utils.d.ts +34 -0
  60. package/dist/clis/gemini/utils.js +463 -0
  61. package/dist/clis/gemini/utils.test.d.ts +1 -0
  62. package/dist/clis/gemini/utils.test.js +31 -0
  63. package/dist/clis/notebooklm/compat.test.d.ts +1 -1
  64. package/dist/clis/notebooklm/compat.test.js +3 -3
  65. package/dist/clis/notebooklm/current.js +2 -3
  66. package/dist/clis/notebooklm/get.js +2 -3
  67. package/dist/clis/notebooklm/history.js +2 -3
  68. package/dist/clis/notebooklm/note-list.js +2 -3
  69. package/dist/clis/notebooklm/notes-get.js +2 -3
  70. package/dist/clis/notebooklm/open.d.ts +1 -0
  71. package/dist/clis/notebooklm/open.js +41 -0
  72. package/dist/clis/notebooklm/open.test.d.ts +1 -0
  73. package/dist/clis/notebooklm/open.test.js +63 -0
  74. package/dist/clis/notebooklm/source-fulltext.js +2 -3
  75. package/dist/clis/notebooklm/source-get.js +2 -3
  76. package/dist/clis/notebooklm/source-guide.js +2 -3
  77. package/dist/clis/notebooklm/source-list.js +2 -3
  78. package/dist/clis/notebooklm/status.js +1 -2
  79. package/dist/clis/notebooklm/summary.js +2 -3
  80. package/dist/clis/notebooklm/utils.d.ts +2 -1
  81. package/dist/clis/notebooklm/utils.js +20 -21
  82. package/dist/clis/twitter/article.js +28 -1
  83. package/dist/clis/xiaohongshu/creator-note-detail.test.js +11 -11
  84. package/dist/clis/xiaohongshu/creator-notes-summary.test.js +6 -6
  85. package/dist/clis/xiaohongshu/creator-notes.test.js +22 -22
  86. package/dist/clis/xiaohongshu/note.js +11 -0
  87. package/dist/clis/xiaohongshu/note.test.js +49 -0
  88. package/dist/commanderAdapter.js +7 -4
  89. package/dist/commanderAdapter.test.js +76 -0
  90. package/dist/commands/daemon.js +8 -47
  91. package/dist/commands/daemon.test.js +45 -70
  92. package/dist/discovery.js +27 -0
  93. package/dist/doctor.d.ts +1 -2
  94. package/dist/doctor.js +7 -8
  95. package/dist/explore.js +1 -1
  96. package/dist/output.js +28 -0
  97. package/dist/output.test.js +15 -0
  98. package/dist/pipeline/executor.js +2 -7
  99. package/dist/pipeline/steps/browser.js +1 -1
  100. package/dist/pipeline/template.js +25 -3
  101. package/dist/record.d.ts +50 -0
  102. package/dist/record.js +298 -57
  103. package/dist/record.test.d.ts +1 -0
  104. package/dist/record.test.js +293 -0
  105. package/dist/registry.d.ts +2 -0
  106. package/dist/registry.js +1 -0
  107. package/dist/registry.test.js +10 -0
  108. package/dist/runtime.js +3 -3
  109. package/dist/snapshotFormatter.d.ts +1 -1
  110. package/dist/snapshotFormatter.js +4 -4
  111. package/dist/snapshotFormatter.test.d.ts +1 -1
  112. package/dist/snapshotFormatter.test.js +2 -2
  113. package/dist/types.d.ts +11 -1
  114. package/dist/types.js +1 -1
  115. package/docs/.vitepress/config.mts +2 -0
  116. package/docs/adapters/browser/amazon.md +53 -0
  117. package/docs/adapters/browser/gemini.md +72 -0
  118. package/docs/adapters/browser/notebooklm.md +5 -5
  119. package/docs/adapters/index.md +3 -1
  120. package/docs/guide/getting-started.md +21 -0
  121. package/docs/superpowers/specs/2026-04-02-browse-skill-testing-design.md +144 -0
  122. package/docs/zh/guide/getting-started.md +21 -0
  123. package/extension/package-lock.json +2 -2
  124. package/extension/src/background.test.ts +7 -163
  125. package/extension/src/background.ts +58 -161
  126. package/extension/src/cdp.ts +77 -124
  127. package/extension/src/protocol.ts +5 -5
  128. package/package.json +1 -1
  129. package/skills/opencli-explorer/SKILL.md +853 -0
  130. package/skills/opencli-oneshot/SKILL.md +222 -0
  131. package/skills/opencli-operate/SKILL.md +213 -0
  132. package/skills/opencli-usage/SKILL.md +152 -0
  133. package/skills/opencli-usage/browser.md +429 -0
  134. package/skills/opencli-usage/desktop.md +118 -0
  135. package/skills/opencli-usage/plugins.md +82 -0
  136. package/skills/opencli-usage/public-api.md +149 -0
  137. package/src/browser/base-page.ts +197 -0
  138. package/src/browser/cdp.ts +7 -131
  139. package/src/browser/daemon-client.test.ts +103 -0
  140. package/src/browser/daemon-client.ts +55 -43
  141. package/src/browser/discover.ts +9 -21
  142. package/src/browser/errors.ts +22 -0
  143. package/src/browser/index.ts +1 -1
  144. package/src/browser/page.ts +57 -209
  145. package/src/browser/tabs.ts +5 -5
  146. package/src/browser.test.ts +15 -15
  147. package/src/cli.ts +392 -0
  148. package/src/clis/amazon/bestsellers.test.ts +22 -0
  149. package/src/clis/amazon/bestsellers.ts +180 -0
  150. package/src/clis/amazon/discussion.test.ts +38 -0
  151. package/src/clis/amazon/discussion.ts +131 -0
  152. package/src/clis/amazon/offer.test.ts +35 -0
  153. package/src/clis/amazon/offer.ts +185 -0
  154. package/src/clis/amazon/product.test.ts +26 -0
  155. package/src/clis/amazon/product.ts +131 -0
  156. package/src/clis/amazon/search.test.ts +24 -0
  157. package/src/clis/amazon/search.ts +128 -0
  158. package/src/clis/amazon/shared.test.ts +37 -0
  159. package/src/clis/amazon/shared.ts +316 -0
  160. package/src/clis/gemini/ask.ts +46 -0
  161. package/src/clis/gemini/image.ts +115 -0
  162. package/src/clis/gemini/new.ts +22 -0
  163. package/src/clis/gemini/utils.test.ts +36 -0
  164. package/src/clis/gemini/utils.ts +523 -0
  165. package/src/clis/notebooklm/compat.test.ts +3 -3
  166. package/src/clis/notebooklm/current.ts +2 -3
  167. package/src/clis/notebooklm/get.ts +1 -3
  168. package/src/clis/notebooklm/history.ts +1 -3
  169. package/src/clis/notebooklm/note-list.ts +1 -3
  170. package/src/clis/notebooklm/notes-get.ts +1 -3
  171. package/src/clis/notebooklm/open.test.ts +78 -0
  172. package/src/clis/notebooklm/open.ts +61 -0
  173. package/src/clis/notebooklm/source-fulltext.ts +1 -3
  174. package/src/clis/notebooklm/source-get.ts +1 -3
  175. package/src/clis/notebooklm/source-guide.ts +1 -3
  176. package/src/clis/notebooklm/source-list.ts +1 -3
  177. package/src/clis/notebooklm/status.ts +1 -2
  178. package/src/clis/notebooklm/summary.ts +1 -3
  179. package/src/clis/notebooklm/utils.ts +29 -20
  180. package/src/clis/twitter/article.ts +31 -1
  181. package/src/clis/xiaohongshu/creator-note-detail.test.ts +11 -11
  182. package/src/clis/xiaohongshu/creator-notes-summary.test.ts +6 -6
  183. package/src/clis/xiaohongshu/creator-notes.test.ts +22 -22
  184. package/src/clis/xiaohongshu/note.test.ts +51 -0
  185. package/src/clis/xiaohongshu/note.ts +18 -0
  186. package/src/commanderAdapter.test.ts +109 -0
  187. package/src/commanderAdapter.ts +8 -4
  188. package/src/commands/daemon.test.ts +50 -84
  189. package/src/commands/daemon.ts +8 -56
  190. package/src/discovery.ts +22 -0
  191. package/src/doctor.ts +8 -9
  192. package/src/explore.ts +1 -1
  193. package/src/output.test.ts +17 -0
  194. package/src/output.ts +27 -0
  195. package/src/pipeline/executor.ts +2 -7
  196. package/src/pipeline/steps/browser.ts +1 -1
  197. package/src/pipeline/template.ts +27 -4
  198. package/src/record.test.ts +362 -0
  199. package/src/record.ts +341 -62
  200. package/src/registry.test.ts +12 -0
  201. package/src/registry.ts +3 -0
  202. package/src/runtime.ts +3 -3
  203. package/src/snapshotFormatter.test.ts +2 -2
  204. package/src/snapshotFormatter.ts +4 -4
  205. package/src/types.ts +11 -1
  206. package/.agents/skills/cross-project-adapter-migration/SKILL.md +0 -249
  207. package/.agents/workflows/cross-project-adapter-migration.md +0 -54
  208. package/dist/clis/notebooklm/bind-current.js +0 -29
  209. package/dist/clis/notebooklm/bind-current.test.d.ts +0 -1
  210. package/dist/clis/notebooklm/bind-current.test.js +0 -35
  211. package/dist/clis/notebooklm/binding.test.js +0 -44
  212. package/extension/dist/background.js +0 -819
  213. package/src/clis/notebooklm/bind-current.test.ts +0 -43
  214. package/src/clis/notebooklm/bind-current.ts +0 -36
  215. package/src/clis/notebooklm/binding.test.ts +0 -53
  216. /package/dist/browser/{mcp.d.ts → bridge.d.ts} +0 -0
  217. /package/dist/browser/{mcp.js → bridge.js} +0 -0
  218. /package/dist/{clis/notebooklm/bind-current.d.ts → browser/daemon-client.test.d.ts} +0 -0
  219. /package/dist/clis/{notebooklm/binding.test.d.ts → amazon/bestsellers.test.d.ts} +0 -0
  220. /package/src/browser/{mcp.ts → bridge.ts} +0 -0
@@ -48,6 +48,27 @@ opencli bilibili hot -f csv # CSV
48
48
  opencli bilibili hot -v # Verbose: show pipeline debug
49
49
  ```
50
50
 
51
+ ### Tab Completion
52
+
53
+ OpenCLI supports intelligent tab completion to speed up command input:
54
+
55
+ ```bash
56
+ # Add shell completion to your startup config
57
+ echo 'eval "$(opencli completion zsh)"' >> ~/.zshrc # Zsh
58
+ echo 'eval "$(opencli completion bash)"' >> ~/.bashrc # Bash
59
+ echo 'opencli completion fish | source' >> ~/.config/fish/config.fish # Fish
60
+
61
+ # Restart your shell, then press Tab to complete:
62
+ opencli [Tab] # Complete site names (bilibili, zhihu, twitter...)
63
+ opencli bilibili [Tab] # Complete commands (hot, search, me, download...)
64
+ ```
65
+
66
+ The completion includes:
67
+ - All available sites and adapters
68
+ - Built-in commands (list, explore, validate...)
69
+ - Command aliases
70
+ - Real-time updates as you add new adapters
71
+
51
72
  ## Next Steps
52
73
 
53
74
  - [Installation details](/guide/installation)
@@ -0,0 +1,144 @@
1
+ # Browse Skill Testing Design
2
+
3
+ Two-layer testing framework for `opencli browse` commands and the
4
+ Claude Code skill integration.
5
+
6
+ ## Goal
7
+
8
+ Verify that `opencli browse` works reliably on real websites and that
9
+ Claude Code can use the skill to complete browser tasks end-to-end.
10
+
11
+ ## Architecture
12
+
13
+ ```
14
+ autoresearch/
15
+ ├── browse-tasks.json ← 59 task definitions with browse command sequences
16
+ ├── eval-browse.ts ← Layer 1: deterministic browse command testing
17
+ ├── eval-skill.ts ← Layer 2: Claude Code skill E2E testing
18
+ ├── run-browse.sh ← Launch Layer 1
19
+ ├── run-skill.sh ← Launch Layer 2
20
+ ├── baseline-browse.txt ← Layer 1 best score
21
+ ├── baseline-skill.txt ← Layer 2 best score
22
+ └── results/ ← Per-run results (gitignored)
23
+ ```
24
+
25
+ ## Layer 1: Deterministic Browse Command Testing
26
+
27
+ Tests `opencli browse` commands directly on real websites. No LLM
28
+ involved — pure command reliability testing.
29
+
30
+ ### How It Works
31
+
32
+ Each task defines a sequence of browse commands and a judge for the
33
+ last command's output:
34
+
35
+ ```json
36
+ {
37
+ "name": "hn-top-stories",
38
+ "steps": [
39
+ "opencli browse open https://news.ycombinator.com",
40
+ "opencli browse eval \"JSON.stringify([...document.querySelectorAll('.titleline a')].slice(0,5).map(a=>({title:a.textContent,url:a.href})))\""
41
+ ],
42
+ "judge": { "type": "arrayMinLength", "minLength": 5 }
43
+ }
44
+ ```
45
+
46
+ ### Execution
47
+
48
+ ```bash
49
+ ./autoresearch/run-browse.sh
50
+ ```
51
+
52
+ - Runs all 59 tasks serially
53
+ - Each task: execute steps → judge last step output → pass/fail
54
+ - `opencli browse close` between tasks for clean state
55
+ - Expected: ~2 minutes, $0 cost
56
+
57
+ ### Task Categories
58
+
59
+ | Category | Count | Example |
60
+ |----------|-------|---------|
61
+ | extract | 9 | Open page, eval JS to extract data |
62
+ | list | 10 | Open page, eval JS to extract array |
63
+ | search | 6 | Open, type query, keys Enter, eval results |
64
+ | nav | 7 | Open, click link, eval new page title |
65
+ | scroll | 5 | Open, scroll, eval footer/hidden content |
66
+ | form | 6 | Open, type into fields, eval field values |
67
+ | complex | 6 | Multi-step: open → click → navigate → extract |
68
+ | bench | 10 | Test set (various) |
69
+
70
+ ## Layer 2: Claude Code Skill E2E Testing
71
+
72
+ Spawns Claude Code with the opencli-operate skill to complete tasks
73
+ autonomously using browse commands.
74
+
75
+ ### How It Works
76
+
77
+ ```bash
78
+ claude -p \
79
+ --system-prompt "$(cat skills/opencli-operate/SKILL.md)" \
80
+ --dangerously-skip-permissions \
81
+ --allowedTools "Bash(opencli:*)" \
82
+ --output-format json \
83
+ "用 opencli browse 完成任务:Extract the top 5 stories from Hacker News with title and score. Start URL: https://news.ycombinator.com"
84
+ ```
85
+
86
+ ### Execution
87
+
88
+ ```bash
89
+ ./autoresearch/run-skill.sh
90
+ ```
91
+
92
+ - Runs all 59 tasks serially
93
+ - Each task: spawn Claude Code → it uses browse commands autonomously → judge output
94
+ - Expected: ~20 minutes, ~$5-10
95
+
96
+ ### Judge
97
+
98
+ Both layers use the same judge types:
99
+
100
+ | Type | Description |
101
+ |------|-------------|
102
+ | `contains` | Output contains a substring |
103
+ | `arrayMinLength` | Output is an array with ≥ N items |
104
+ | `arrayFieldsPresent` | Array items have required fields |
105
+ | `nonEmpty` | Output is non-empty |
106
+ | `matchesPattern` | Output matches a regex |
107
+
108
+ ## Output Format
109
+
110
+ ```
111
+ 🔬 Layer 1: Browse Commands — 59 tasks
112
+
113
+ [1/59] extract-title-example... ✓ (0.5s)
114
+ [2/59] hn-top-stories... ✓ (1.2s)
115
+ ...
116
+
117
+ Score: 55/59 (93%)
118
+ Time: 2min
119
+ Cost: $0
120
+
121
+ 🔬 Layer 2: Skill E2E — 59 tasks
122
+
123
+ [1/59] extract-title-example... ✓ (8s, $0.01)
124
+ [2/59] hn-top-stories... ✓ (15s, $0.08)
125
+ ...
126
+
127
+ Score: 52/59 (88%)
128
+ Time: 20min
129
+ Cost: $6.50
130
+ ```
131
+
132
+ ## Constraints
133
+
134
+ - All 59 tasks run on real websites (no mocks)
135
+ - Layer 1: zero LLM cost, ~2 min
136
+ - Layer 2: ~$5-10 LLM cost, ~20 min
137
+ - Results saved to `autoresearch/results/` (gitignored)
138
+ - Baselines tracked in `baseline-browse.txt` and `baseline-skill.txt`
139
+
140
+ ## Success Criteria
141
+
142
+ - Layer 1 ≥ 90% (browse commands work on real sites)
143
+ - Layer 2 ≥ 85% (Claude Code can use skill effectively)
144
+ - Both layers cover all 8 task categories
@@ -32,6 +32,27 @@ opencli bilibili hot -f md # Markdown
32
32
  opencli bilibili hot -f csv # CSV
33
33
  ```
34
34
 
35
+ ## 终端自动补全
36
+
37
+ OpenCLI 支持智能的 Tab 自动补全,加快命令输入:
38
+
39
+ ```bash
40
+ # 把自动补全加入 shell 启动配置
41
+ echo 'eval "$(opencli completion zsh)"' >> ~/.zshrc # Zsh
42
+ echo 'eval "$(opencli completion bash)"' >> ~/.bashrc # Bash
43
+ echo 'opencli completion fish | source' >> ~/.config/fish/config.fish # Fish
44
+
45
+ # 重启 shell 后,按 Tab 键补全:
46
+ opencli [Tab] # 补全站点名称(bilibili、zhihu、twitter...)
47
+ opencli bilibili [Tab] # 补全命令(hot、search、me、download...)
48
+ ```
49
+
50
+ 补全功能包含:
51
+ - 所有可用的站点和适配器
52
+ - 内置命令(list、explore、validate...)
53
+ - 命令别名
54
+ - 新增适配器时的实时更新
55
+
35
56
  ## 下一步
36
57
 
37
58
  - [安装详情](/zh/guide/installation)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "opencli-extension",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "opencli-extension",
9
- "version": "1.5.4",
9
+ "version": "1.5.5",
10
10
  "devDependencies": {
11
11
  "@types/chrome": "^0.0.287",
12
12
  "typescript": "^5.7.0",
@@ -200,7 +200,7 @@ describe('background tab isolation', () => {
200
200
  ]));
201
201
  });
202
202
 
203
- it('rebinds site:notebooklm to the active notebook tab instead of a home tab', async () => {
203
+ it('keeps site:notebooklm inside its owned automation window instead of rebinding to a user tab', async () => {
204
204
  const { chrome, tabs } = createChromeMock();
205
205
  tabs[0].url = 'https://notebooklm.google.com/';
206
206
  tabs[0].title = 'NotebookLM Home';
@@ -213,184 +213,28 @@ describe('background tab isolation', () => {
213
213
 
214
214
  const tabId = await mod.__test__.resolveTabId(undefined, 'site:notebooklm');
215
215
 
216
- expect(tabId).toBe(2);
216
+ expect(tabId).toBe(1);
217
217
  expect(mod.__test__.getSession('site:notebooklm')).toEqual(expect.objectContaining({
218
- windowId: 2,
219
- preferredTabId: 2,
220
- owned: false,
218
+ windowId: 1,
221
219
  }));
222
220
  });
223
221
 
224
- it('prefers a notebook tab over an active home tab for site:notebooklm', async () => {
222
+ it('idle timeout closes the automation window for site:notebooklm', async () => {
225
223
  const { chrome, tabs } = createChromeMock();
226
224
  tabs[0].url = 'https://notebooklm.google.com/';
227
225
  tabs[0].title = 'NotebookLM Home';
228
226
  tabs[0].active = true;
229
- tabs[1].url = 'https://notebooklm.google.com/notebook/nb-passive';
230
- tabs[1].title = 'Notebook';
231
- tabs[1].active = false;
232
- vi.stubGlobal('chrome', chrome);
233
-
234
- const mod = await import('./background');
235
- mod.__test__.setAutomationWindowId('site:notebooklm', 1);
236
-
237
- const tabId = await mod.__test__.resolveTabId(undefined, 'site:notebooklm');
238
-
239
- expect(tabId).toBe(2);
240
- expect(mod.__test__.getSession('site:notebooklm')).toEqual(expect.objectContaining({
241
- windowId: 2,
242
- preferredTabId: 2,
243
- owned: false,
244
- }));
245
- });
246
227
 
247
- it('detaches an adopted workspace session on idle instead of closing the user window', async () => {
248
- const { chrome } = createChromeMock();
249
- vi.stubGlobal('chrome', chrome);
250
228
  vi.useFakeTimers();
229
+ vi.stubGlobal('chrome', chrome);
251
230
 
252
231
  const mod = await import('./background');
253
- mod.__test__.setSession('site:notebooklm', {
254
- windowId: 2,
255
- preferredTabId: 2,
256
- owned: false,
257
- });
232
+ mod.__test__.setAutomationWindowId('site:notebooklm', 1);
258
233
 
259
234
  mod.__test__.resetWindowIdleTimer('site:notebooklm');
260
235
  await vi.advanceTimersByTimeAsync(30001);
261
236
 
262
- expect(chrome.windows.remove).not.toHaveBeenCalled();
237
+ expect(chrome.windows.remove).toHaveBeenCalledWith(1);
263
238
  expect(mod.__test__.getSession('site:notebooklm')).toBeNull();
264
239
  });
265
-
266
- it('binds the active NotebookLM tab into the workspace explicitly', async () => {
267
- const { chrome, tabs } = createChromeMock();
268
- tabs[1].url = 'https://notebooklm.google.com/notebook/nb-active';
269
- tabs[1].title = 'Bound Notebook';
270
- tabs[1].active = true;
271
- vi.stubGlobal('chrome', chrome);
272
-
273
- const mod = await import('./background');
274
- const result = await mod.__test__.handleBindCurrent(
275
- {
276
- id: 'bind-current',
277
- action: 'bind-current',
278
- workspace: 'site:notebooklm',
279
- matchDomain: 'notebooklm.google.com',
280
- matchPathPrefix: '/notebook/',
281
- },
282
- 'site:notebooklm',
283
- );
284
-
285
- expect(result).toEqual({
286
- id: 'bind-current',
287
- ok: true,
288
- data: expect.objectContaining({
289
- tabId: 2,
290
- windowId: 2,
291
- url: 'https://notebooklm.google.com/notebook/nb-active',
292
- title: 'Bound Notebook',
293
- workspace: 'site:notebooklm',
294
- }),
295
- });
296
- expect(mod.__test__.getSession('site:notebooklm')).toEqual(expect.objectContaining({
297
- windowId: 2,
298
- preferredTabId: 2,
299
- owned: false,
300
- }));
301
- });
302
-
303
- it('bind-current falls back to another matching notebook tab in the current window', async () => {
304
- const { chrome, tabs } = createChromeMock();
305
- tabs[0].windowId = 2;
306
- tabs[0].url = 'https://notebooklm.google.com/';
307
- tabs[0].title = 'NotebookLM Home';
308
- tabs[0].active = true;
309
- tabs[1].url = 'https://notebooklm.google.com/notebook/nb-passive';
310
- tabs[1].title = 'Passive Notebook';
311
- tabs[1].active = false;
312
- vi.stubGlobal('chrome', chrome);
313
-
314
- const mod = await import('./background');
315
- const result = await mod.__test__.handleBindCurrent(
316
- {
317
- id: 'bind-fallback',
318
- action: 'bind-current',
319
- workspace: 'site:notebooklm',
320
- matchDomain: 'notebooklm.google.com',
321
- matchPathPrefix: '/notebook/',
322
- },
323
- 'site:notebooklm',
324
- );
325
-
326
- expect(result).toEqual({
327
- id: 'bind-fallback',
328
- ok: true,
329
- data: expect.objectContaining({
330
- tabId: 2,
331
- windowId: 2,
332
- url: 'https://notebooklm.google.com/notebook/nb-passive',
333
- title: 'Passive Notebook',
334
- }),
335
- });
336
- });
337
-
338
- it('bind-current falls back to a matching notebook tab in another window of the same profile', async () => {
339
- const { chrome, tabs } = createChromeMock();
340
- tabs[0].windowId = 3;
341
- tabs[0].url = 'https://notebooklm.google.com/';
342
- tabs[0].title = 'NotebookLM Home';
343
- tabs[0].active = true;
344
- tabs[1].windowId = 2;
345
- tabs[1].url = 'https://notebooklm.google.com/notebook/nb-other-window';
346
- tabs[1].title = 'Notebook In Other Window';
347
- tabs[1].active = false;
348
- vi.stubGlobal('chrome', chrome);
349
-
350
- const mod = await import('./background');
351
- const result = await mod.__test__.handleBindCurrent(
352
- {
353
- id: 'bind-cross-window',
354
- action: 'bind-current',
355
- workspace: 'site:notebooklm',
356
- matchDomain: 'notebooklm.google.com',
357
- matchPathPrefix: '/notebook/',
358
- },
359
- 'site:notebooklm',
360
- );
361
-
362
- expect(result).toEqual({
363
- id: 'bind-cross-window',
364
- ok: true,
365
- data: expect.objectContaining({
366
- tabId: 2,
367
- windowId: 2,
368
- url: 'https://notebooklm.google.com/notebook/nb-other-window',
369
- title: 'Notebook In Other Window',
370
- }),
371
- });
372
- });
373
-
374
- it('rejects bind-current when the active tab is not NotebookLM', async () => {
375
- const { chrome } = createChromeMock();
376
- vi.stubGlobal('chrome', chrome);
377
-
378
- const mod = await import('./background');
379
- const result = await mod.__test__.handleBindCurrent(
380
- {
381
- id: 'bind-miss',
382
- action: 'bind-current',
383
- workspace: 'site:notebooklm',
384
- matchDomain: 'notebooklm.google.com',
385
- matchPathPrefix: '/notebook/',
386
- },
387
- 'site:notebooklm',
388
- );
389
-
390
- expect(result).toEqual({
391
- id: 'bind-miss',
392
- ok: false,
393
- error: 'No visible tab matching notebooklm.google.com /notebook/',
394
- });
395
- });
396
240
  });