sumulige-claude 1.5.1 → 1.5.2

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 (219) hide show
  1. package/.claude/hooks/hook-registry.json +0 -15
  2. package/.claude/rules/coding-style.md +18 -7
  3. package/.claude/rules/hooks.md +15 -4
  4. package/.claude/rules/performance.md +15 -5
  5. package/.claude/rules/security.md +140 -4
  6. package/.claude/rules/testing.md +138 -9
  7. package/.claude/rules/web-design-standard.md +16 -5
  8. package/.claude/skills/algorithmic-art/metadata.yaml +28 -0
  9. package/.claude/skills/api-tester/SKILL.md +61 -0
  10. package/.claude/skills/api-tester/examples/basic.md +3 -0
  11. package/.claude/skills/api-tester/metadata.yaml +30 -0
  12. package/.claude/skills/api-tester/templates/default.md +3 -0
  13. package/.claude/skills/brand-guidelines/metadata.yaml +26 -0
  14. package/.claude/skills/canvas-design/metadata.yaml +27 -0
  15. package/.claude/skills/code-reviewer-123/SKILL.md +61 -0
  16. package/.claude/skills/code-reviewer-123/examples/basic.md +3 -0
  17. package/.claude/skills/code-reviewer-123/metadata.yaml +30 -0
  18. package/.claude/skills/code-reviewer-123/templates/default.md +3 -0
  19. package/.claude/skills/doc-coauthoring/metadata.yaml +27 -0
  20. package/.claude/skills/docx/metadata.yaml +30 -0
  21. package/.claude/skills/frontend-design/metadata.yaml +28 -0
  22. package/.claude/skills/internal-comms/metadata.yaml +28 -0
  23. package/.claude/skills/mcp-builder/metadata.yaml +26 -0
  24. package/.claude/skills/my-skill/SKILL.md +61 -0
  25. package/.claude/skills/my-skill/examples/basic.md +3 -0
  26. package/.claude/skills/my-skill/metadata.yaml +30 -0
  27. package/.claude/skills/my-skill/templates/default.md +3 -0
  28. package/.claude/skills/pdf/metadata.yaml +29 -0
  29. package/.claude/skills/pptx/metadata.yaml +29 -0
  30. package/.claude/skills/react-best-practices/metadata.yaml +26 -0
  31. package/.claude/skills/react-node-practices/SKILL.md +409 -0
  32. package/.claude/skills/react-node-practices/metadata.yaml +56 -0
  33. package/.claude/skills/skill-creator/metadata.yaml +25 -0
  34. package/.claude/skills/slack-gif-creator/metadata.yaml +28 -0
  35. package/.claude/skills/test-skill-name/SKILL.md +61 -0
  36. package/.claude/skills/test-skill-name/examples/basic.md +3 -0
  37. package/.claude/skills/test-skill-name/metadata.yaml +30 -0
  38. package/.claude/skills/test-skill-name/templates/default.md +3 -0
  39. package/.claude/skills/test-workflow/metadata.yaml +32 -0
  40. package/.claude/skills/theme-factory/metadata.yaml +26 -0
  41. package/.claude/skills/threejs-fundamentals/metadata.yaml +27 -0
  42. package/.claude/skills/web-artifacts-builder/metadata.yaml +30 -0
  43. package/.claude/skills/web-design-guidelines/metadata.yaml +26 -0
  44. package/.claude/skills/webapp-testing/metadata.yaml +26 -0
  45. package/.claude/skills/xlsx/metadata.yaml +29 -0
  46. package/LICENSE +21 -0
  47. package/cli.js +1 -1
  48. package/package.json +25 -3
  49. package/.claude/.kickoff-hint.txt +0 -52
  50. package/.claude/.sumulige-claude-version +0 -1
  51. package/.claude/.version +0 -1
  52. package/.claude/AGENTS.md +0 -42
  53. package/.claude/ANCHORS.md +0 -40
  54. package/.claude/CLAUDE.md +0 -138
  55. package/.claude/MEMORY.md +0 -69
  56. package/.claude/PROJECT_LOG.md +0 -101
  57. package/.claude/THINKING_CHAIN_GUIDE.md +0 -287
  58. package/.claude/USAGE.md +0 -175
  59. package/.claude/boris-optimizations.md +0 -167
  60. package/.claude/handoffs/INDEX.md +0 -21
  61. package/.claude/handoffs/LATEST.md +0 -76
  62. package/.claude/handoffs/handoff_2026-01-22T13-07-04-757Z.md +0 -76
  63. package/.claude/quality-gate.json +0 -82
  64. package/.claude/rag/skill-index.json +0 -135
  65. package/.claude/settings.json +0 -99
  66. package/.claude/settings.local.json +0 -175
  67. package/.claude/templates/PROJECT_KICKOFF.md +0 -89
  68. package/.claude/templates/PROJECT_PROPOSAL.md +0 -227
  69. package/.claude/templates/TASK_PLAN.md +0 -121
  70. package/.claude/templates/hooks/README.md +0 -302
  71. package/.claude/templates/hooks/hook.sh.template +0 -94
  72. package/.claude/templates/hooks/user-prompt-submit.cjs.template +0 -116
  73. package/.claude/templates/hooks/user-response-submit.cjs.template +0 -94
  74. package/.claude/templates/hooks/validate.js +0 -173
  75. package/.claude/templates/tasks/develop.md +0 -69
  76. package/.claude/templates/tasks/research.md +0 -64
  77. package/.claude/templates/tasks/test.md +0 -96
  78. package/.claude/thinking-routes/.last-sync +0 -1
  79. package/.claude/thinking-routes/QUICKREF.md +0 -98
  80. package/.claude/workflow/document-scanner.js +0 -426
  81. package/.claude/workflow/knowledge-engine.js +0 -941
  82. package/.claude/workflow/notebooklm/browser.js +0 -1028
  83. package/.claude/workflow/phases/phase1-research.js +0 -578
  84. package/.claude/workflow/phases/phase1-research.ts +0 -465
  85. package/.claude/workflow/phases/phase2-approve.js +0 -722
  86. package/.claude/workflow/phases/phase3-plan.js +0 -1200
  87. package/.claude/workflow/phases/phase4-develop.js +0 -894
  88. package/.claude/workflow/search-cache.js +0 -230
  89. package/.claude/workflow/templates/approval.md +0 -315
  90. package/.claude/workflow/templates/development.md +0 -377
  91. package/.claude/workflow/templates/planning.md +0 -328
  92. package/.claude/workflow/templates/research.md +0 -250
  93. package/.claude/workflow/types.js +0 -37
  94. package/.claude/workflow/web-search.js +0 -278
  95. package/.claude-plugin/marketplace.json +0 -71
  96. package/.github/workflows/sync-skills.yml +0 -74
  97. package/.versionrc +0 -25
  98. package/AGENTS.md +0 -580
  99. package/CHANGELOG.md +0 -481
  100. package/CLAUDE-template.md +0 -114
  101. package/DEV_TOOLS_GUIDE.md +0 -190
  102. package/PROJECT_STRUCTURE.md +0 -266
  103. package/Q&A.md +0 -325
  104. package/config/defaults.json +0 -34
  105. package/config/official-skills.json +0 -183
  106. package/config/quality-gate.json +0 -67
  107. package/config/skill-categories.json +0 -40
  108. package/config/version-manifest.json +0 -85
  109. package/demos/power-3d-scatter.html +0 -683
  110. package/development/cache/web-search/search_1193d605f8eb364651fc2f2041b58a31.json +0 -36
  111. package/development/cache/web-search/search_3798bf06960edc125f744a1abb5b72c5.json +0 -36
  112. package/development/cache/web-search/search_37c7d4843a53f0d83f1122a6f908a2a3.json +0 -36
  113. package/development/cache/web-search/search_44166fa0153709ee168485a22aa0ab40.json +0 -36
  114. package/development/cache/web-search/search_4deaebb1f77e86a8ca066dc5a49c59fd.json +0 -36
  115. package/development/cache/web-search/search_94da91789466070a7f545612e73c7372.json +0 -36
  116. package/development/cache/web-search/search_dd5de8491b8b803a3cb01339cd210fb0.json +0 -36
  117. package/development/knowledge-base/.index.clean.json +0 -1
  118. package/development/knowledge-base/.index.json +0 -486
  119. package/development/knowledge-base/test-best-practices.md +0 -29
  120. package/development/projects/proj_mkh1pazz_ixmt1/phase1/feasibility-report.md +0 -160
  121. package/development/projects/proj_mkh4jvnb_z7rwf/phase1/feasibility-report.md +0 -160
  122. package/development/projects/proj_mkh4jxkd_ewz5a/phase1/feasibility-report.md +0 -160
  123. package/development/projects/proj_mkh4k84n_ni73k/phase1/feasibility-report.md +0 -160
  124. package/development/projects/proj_mkh4wfyd_u9w88/phase1/feasibility-report.md +0 -160
  125. package/development/projects/proj_mkh4wsbo_iahvf/development/projects/proj_mkh4xbpg_4na5w/phase1/feasibility-report.md +0 -160
  126. package/development/projects/proj_mkh4wsbo_iahvf/phase1/feasibility-report.md +0 -160
  127. package/development/projects/proj_mkh4xulg_1ka8x/phase1/feasibility-report.md +0 -160
  128. package/development/projects/proj_mkh4xwhj_gch8j/phase1/feasibility-report.md +0 -160
  129. package/development/projects/proj_mkh4y2qk_9lm8z/phase1/feasibility-report.md +0 -160
  130. package/development/projects/proj_mkh4y2qk_9lm8z/phase2/requirements.md +0 -226
  131. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/PRD.md +0 -345
  132. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/TASK_PLAN.md +0 -284
  133. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/prototype/README.md +0 -14
  134. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/DEVELOPMENT_LOG.md +0 -35
  135. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/TASKS.md +0 -34
  136. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/.env.example +0 -5
  137. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/README.md +0 -60
  138. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/package.json +0 -25
  139. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/index.js +0 -70
  140. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/routes/index.js +0 -48
  141. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/health.test.js +0 -20
  142. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/jest.config.js +0 -21
  143. package/development/projects/proj_mkh7veqg_3lypc/phase1/feasibility-report.md +0 -160
  144. package/development/projects/proj_mkh7veqg_3lypc/phase2/requirements.md +0 -226
  145. package/development/projects/proj_mkh7veqg_3lypc/phase3/PRD.md +0 -345
  146. package/development/projects/proj_mkh7veqg_3lypc/phase3/TASK_PLAN.md +0 -284
  147. package/development/projects/proj_mkh7veqg_3lypc/phase3/prototype/README.md +0 -14
  148. package/development/projects/proj_mkh8k8fo_rmqn5/phase1/feasibility-report.md +0 -160
  149. package/development/projects/proj_mkh8xyhy_1vshq/phase1/feasibility-report.md +0 -178
  150. package/development/projects/proj_mkh8zddd_dhamf/phase1/feasibility-report.md +0 -377
  151. package/development/projects/proj_mkh8zddd_dhamf/phase2/requirements.md +0 -442
  152. package/development/projects/proj_mkh8zddd_dhamf/phase3/api-design.md +0 -800
  153. package/development/projects/proj_mkh8zddd_dhamf/phase3/architecture.md +0 -625
  154. package/development/projects/proj_mkh8zddd_dhamf/phase3/data-model.md +0 -830
  155. package/development/projects/proj_mkh8zddd_dhamf/phase3/risks.md +0 -957
  156. package/development/projects/proj_mkh8zddd_dhamf/phase3/wbs.md +0 -381
  157. package/development/todos/.state.json +0 -19
  158. package/development/todos/INDEX.md +0 -63
  159. package/development/todos/active/_README.md +0 -49
  160. package/development/todos/archived/_README.md +0 -11
  161. package/development/todos/backlog/_README.md +0 -11
  162. package/development/todos/backlog/mcp-integration.md +0 -35
  163. package/development/todos/completed/_README.md +0 -11
  164. package/development/todos/completed/boris-optimizations.md +0 -39
  165. package/development/todos/completed/develop/local-knowledge-index.md +0 -85
  166. package/development/todos/completed/develop/todo-system.md +0 -47
  167. package/development/todos/completed/develop/web-search-integration.md +0 -83
  168. package/development/todos/completed/test/phase1-e2e-test.md +0 -103
  169. package/docs/DEVELOPMENT.md +0 -461
  170. package/docs/MARKETPLACE.md +0 -352
  171. package/docs/RELEASE.md +0 -93
  172. package/jest.config.js +0 -63
  173. package/lib/commands.js +0 -3588
  174. package/lib/config-manager.js +0 -441
  175. package/lib/config-schema.js +0 -408
  176. package/lib/config-validator.js +0 -330
  177. package/lib/config.js +0 -122
  178. package/lib/errors.js +0 -305
  179. package/lib/incremental-sync.js +0 -274
  180. package/lib/marketplace.js +0 -487
  181. package/lib/migrations.js +0 -154
  182. package/lib/permission-audit.js +0 -255
  183. package/lib/quality-gate.js +0 -431
  184. package/lib/quality-rules.js +0 -373
  185. package/lib/utils.js +0 -150
  186. package/lib/version-check.js +0 -169
  187. package/lib/version-manifest.js +0 -171
  188. package/project-paradigm.md +0 -313
  189. package/prompts/how-to-find.md +0 -163
  190. package/prompts/linus-architect.md +0 -71
  191. package/prompts/software-architect.md +0 -173
  192. package/prompts/web-designer.md +0 -249
  193. package/scripts/fix-hooks.mjs +0 -97
  194. package/scripts/sync-external.mjs +0 -298
  195. package/scripts/sync-to-home.sh +0 -108
  196. package/scripts/update-registry.mjs +0 -325
  197. package/sources.yaml +0 -83
  198. package/tests/README.md +0 -263
  199. package/tests/commands.test.js +0 -1086
  200. package/tests/config-manager.test.js +0 -677
  201. package/tests/config-schema.test.js +0 -425
  202. package/tests/config-validator.test.js +0 -436
  203. package/tests/config.test.js +0 -100
  204. package/tests/errors.test.js +0 -477
  205. package/tests/manual/phase1-e2e.sh +0 -389
  206. package/tests/manual/phase2-test-cases.md +0 -311
  207. package/tests/manual/phase3-test-cases.md +0 -309
  208. package/tests/manual/phase4-test-cases.md +0 -414
  209. package/tests/manual/test-cases.md +0 -417
  210. package/tests/marketplace.test.js +0 -420
  211. package/tests/migrations.test.js +0 -187
  212. package/tests/quality-gate.test.js +0 -679
  213. package/tests/quality-rules.test.js +0 -619
  214. package/tests/sync-external.test.js +0 -214
  215. package/tests/update-registry.test.js +0 -251
  216. package/tests/utils.test.js +0 -171
  217. package/tests/version-check.test.js +0 -75
  218. package/tests/web-search.test.js +0 -392
  219. package/thinkinglens-silent.md +0 -138
@@ -1,1028 +0,0 @@
1
- /**
2
- * NotebookLM Browser Automation Module (Simplified)
3
- *
4
- * Provides browser automation for NotebookLM interactions:
5
- * - Authentication management
6
- * - Session management
7
- * - Question/answer with streaming detection
8
- * - Human-like behavior simulation
9
- *
10
- * This is a simplified version of notebooklm-mcp core modules
11
- * adapted for integration into sumulige-claude.
12
- */
13
-
14
- const fs = require('fs');
15
- const path = require('path');
16
-
17
- // ============================================================================
18
- // Configuration
19
- // ============================================================================
20
-
21
- const DEFAULT_CONFIG = {
22
- // Browser settings
23
- headless: true,
24
- viewport: { width: 1024, height: 768 },
25
-
26
- // Timeout settings
27
- browserTimeout: 120000, // 2 minutes
28
- responseTimeout: 60000, // 1 minute
29
- sessionTimeout: 900000, // 15 minutes
30
-
31
- // Stealth settings
32
- stealthEnabled: true,
33
- humanTyping: true,
34
- typingWpmMin: 90,
35
- typingWpmMax: 120,
36
- randomDelays: true,
37
- minDelayMs: 100,
38
- maxDelayMs: 300,
39
-
40
- // URLs
41
- notebookUrl: 'https://notebooklm.google.com',
42
- authUrl: 'https://accounts.google.com'
43
- };
44
-
45
- // ============================================================================
46
- // Path Management
47
- // ============================================================================
48
-
49
- class PathManager {
50
- constructor() {
51
- // Use cross-platform home directory
52
- const homeDir = require('os').homedir();
53
- const appName = 'sumulige-claude';
54
-
55
- // Platform-specific data directory
56
- const platform = require('os').platform();
57
- let baseDir;
58
-
59
- if (platform === 'darwin') {
60
- baseDir = path.join(homeDir, 'Library', 'Application Support', appName);
61
- } else if (platform === 'win32') {
62
- baseDir = path.join(homeDir, 'AppData', 'Roaming', appName);
63
- } else {
64
- baseDir = path.join(homeDir, '.local', 'share', appName);
65
- }
66
-
67
- // Fallback to home directory if platform-specific path fails
68
- this.dataDir = baseDir || path.join(homeDir, `.${appName}`);
69
- this.browserStateDir = path.join(this.dataDir, 'notebooklm-state');
70
- this.stateFilePath = path.join(this.browserStateDir, 'state.json');
71
- this.sessionFilePath = path.join(this.browserStateDir, 'session.json');
72
- this.ensureDirectories();
73
- }
74
-
75
- ensureDirectories() {
76
- if (!fs.existsSync(this.browserStateDir)) {
77
- fs.mkdirSync(this.browserStateDir, { recursive: true });
78
- }
79
- }
80
-
81
- getStatePath() {
82
- return fs.existsSync(this.stateFilePath) ? this.stateFilePath : null;
83
- }
84
-
85
- getSessionPath() {
86
- return this.sessionFilePath;
87
- }
88
-
89
- getDataDir() {
90
- return this.dataDir;
91
- }
92
- }
93
-
94
- // ============================================================================
95
- // Stealth Utilities (Human-like behavior)
96
- // ============================================================================
97
-
98
- class StealthUtils {
99
- static sleep(ms) {
100
- return new Promise(resolve => setTimeout(resolve, ms));
101
- }
102
-
103
- static randomInt(min, max) {
104
- return Math.floor(Math.random() * (max - min + 1)) + min;
105
- }
106
-
107
- static randomFloat(min, max) {
108
- return Math.random() * (max - min) + min;
109
- }
110
-
111
- // Gaussian distribution (Box-Muller transform)
112
- static gaussian(mean, stdDev) {
113
- const u1 = Math.random();
114
- const u2 = Math.random();
115
- const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
116
- return z0 * stdDev + mean;
117
- }
118
-
119
- static async randomDelay(minMs, maxMs) {
120
- minMs = minMs ?? DEFAULT_CONFIG.minDelayMs;
121
- maxMs = maxMs ?? DEFAULT_CONFIG.maxDelayMs;
122
-
123
- if (!DEFAULT_CONFIG.stealthEnabled || !DEFAULT_CONFIG.randomDelays) {
124
- const target = (minMs + maxMs) / 2;
125
- if (target > 0) await this.sleep(target);
126
- return;
127
- }
128
-
129
- const mean = minMs + (maxMs - minMs) * 0.6;
130
- const stdDev = (maxMs - minMs) * 0.2;
131
- let delay = this.gaussian(mean, stdDev);
132
- delay = Math.max(minMs, Math.min(maxMs, delay));
133
-
134
- await this.sleep(delay);
135
- }
136
-
137
- // Calculate delay per character based on WPM
138
- static getTypingDelay(wpm) {
139
- const charsPerMinute = wpm * 5;
140
- return (60 * 1000) / charsPerMinute;
141
- }
142
-
143
- // Get pause duration for punctuation
144
- static getPunctuationPause(char) {
145
- const pauses = {
146
- '.': 300,
147
- '!': 350,
148
- '?': 300,
149
- ',': 150,
150
- ';': 200,
151
- ':': 200,
152
- ' ': 50,
153
- '\n': 400
154
- };
155
- return pauses[char] || 50;
156
- }
157
- }
158
-
159
- // ============================================================================
160
- // Page Utilities (NotebookLM specific)
161
- // ============================================================================
162
-
163
- class PageUtils {
164
- // CSS selectors for NotebookLM responses
165
- static RESPONSE_SELECTORS = [
166
- // Primary NotebookLM selectors
167
- '.to-user-container .message-text-content',
168
- '.to-user-container .markdown-content',
169
- '.to-user-container',
170
- // Generic bot/assistant selectors
171
- '[data-message-author="bot"]',
172
- '[data-message-author="assistant"]',
173
- '[data-message-role="assistant"]',
174
- // Content containers
175
- 'markdown-response',
176
- '.response-text',
177
- '.message-text-content',
178
- // Data attributes
179
- '[data-automation-id="response-text"]',
180
- '[data-automation-id="assistant-response"]',
181
- // Last resort
182
- '.message-text',
183
- '[role="log"]'
184
- ];
185
-
186
- // Placeholder texts to ignore
187
- static PLACEHOLDERS = new Set([
188
- '',
189
- '...',
190
- 'Thinking...',
191
- 'Generating response...',
192
- 'Please wait...',
193
- 'Finding relevant info...',
194
- 'Searching',
195
- 'Loading'
196
- ]);
197
-
198
- /**
199
- * Extract latest response from NotebookLM page
200
- */
201
- static async extractLatestResponse(page, existingHashes = new Set()) {
202
- for (const selector of this.RESPONSE_SELECTORS) {
203
- try {
204
- const elements = await page.$$(selector);
205
- if (elements.length > 0) {
206
- // Get the last (latest) element
207
- const latest = elements[elements.length - 1];
208
- const text = await latest.evaluate(el => el.textContent?.trim() || '');
209
- const hash = this.hashString(text);
210
-
211
- // Only return if it's a new response (not in existing hashes)
212
- if (text && !this.PLACEHOLDERS.has(text) && !existingHashes.has(hash)) {
213
- return { text, hash, selector };
214
- }
215
- }
216
- } catch (e) {
217
- continue;
218
- }
219
- }
220
- return null;
221
- }
222
-
223
- /**
224
- * Snapshot all existing responses before asking a question
225
- */
226
- static async snapshotAllResponses(page) {
227
- const hashes = new Set();
228
- const texts = [];
229
-
230
- try {
231
- const containers = await page.$$('.to-user-container');
232
- for (const container of containers) {
233
- try {
234
- const textEl = await container.$('.message-text-content');
235
- if (textEl) {
236
- const text = await textEl.evaluate(el => el.textContent?.trim() || '');
237
- if (text) {
238
- hashes.add(this.hashString(text));
239
- texts.push(text);
240
- }
241
- }
242
- } catch (e) {
243
- continue;
244
- }
245
- }
246
- } catch (e) {
247
- // Ignore errors
248
- }
249
-
250
- return { hashes, texts };
251
- }
252
-
253
- /**
254
- * Wait for new response with streaming detection
255
- */
256
- static async waitForNewResponse(page, existingHashes, timeout = 120000) {
257
- const startTime = Date.now();
258
- let stableCount = 0;
259
- const STABLE_THRESHOLD = 5; // Increased for better detection
260
- const POLL_INTERVAL = 1000; // Increased to reduce CPU usage
261
- let lastResponse = null;
262
- let lastLength = 0;
263
-
264
- console.log(`ā³ Waiting for response (timeout: ${timeout/1000}s)...`);
265
-
266
- while (Date.now() - startTime < timeout) {
267
- await StealthUtils.sleep(POLL_INTERVAL);
268
-
269
- const response = await this.extractLatestResponse(page, existingHashes);
270
-
271
- if (response) {
272
- const currentLength = response.text.length;
273
-
274
- // Check if response is growing (streaming) or stable
275
- if (lastResponse && lastResponse.text === response.text) {
276
- stableCount++;
277
- if (stableCount >= STABLE_THRESHOLD) {
278
- console.log(`āœ… Response complete (${response.text.length} chars)`);
279
- return response.text;
280
- }
281
- } else if (currentLength > lastLength) {
282
- // Response is still growing
283
- stableCount = 0;
284
- lastResponse = response;
285
- lastLength = currentLength;
286
- console.log(`ā³ Receiving... (${currentLength} chars)`);
287
- } else {
288
- // Different response (unlikely but handle it)
289
- stableCount = 0;
290
- lastResponse = response;
291
- lastLength = currentLength;
292
- }
293
- } else {
294
- // No new response yet, check existing ones
295
- const allTexts = await this.snapshotAllResponses(page);
296
- if (allTexts.texts.length > 0) {
297
- const latest = allTexts.texts[allTexts.texts.length - 1];
298
- if (latest && latest.length > 0 && !this.PLACEHOLDERS.has(latest)) {
299
- lastResponse = { text: latest };
300
- lastLength = latest.length;
301
- }
302
- }
303
- }
304
-
305
- // Progress indicator
306
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
307
- process.stdout.write(`\rā³ ${elapsed}s / ${timeout/1000}s`);
308
- }
309
-
310
- process.stdout.write('\n');
311
-
312
- if (lastResponse) {
313
- console.log(`āœ… Got response (${lastResponse.text.length} chars)`);
314
- return lastResponse.text;
315
- }
316
-
317
- return null;
318
- }
319
-
320
- static hashString(str) {
321
- let hash = 0;
322
- for (let i = 0; i < str.length; i++) {
323
- const char = str.charCodeAt(i);
324
- hash = ((hash << 5) - hash + char) & 0xffffffff;
325
- }
326
- return hash.toString(36);
327
- }
328
- }
329
-
330
- // ============================================================================
331
- // Authentication Manager
332
- // ============================================================================
333
-
334
- class AuthManager {
335
- constructor() {
336
- this.paths = new PathManager();
337
- }
338
-
339
- /**
340
- * Check if saved state exists and is valid
341
- */
342
- hasValidState() {
343
- const statePath = this.paths.getStatePath();
344
- if (!statePath) return false;
345
-
346
- // Check if state is less than 24 hours old
347
- const stats = fs.statSync(statePath);
348
- const age = Date.now() - stats.mtime.getTime();
349
- const maxAge = 24 * 60 * 60 * 1000; // 24 hours
350
-
351
- return age < maxAge;
352
- }
353
-
354
- /**
355
- * Get the state file path for loading
356
- */
357
- getStatePath() {
358
- return this.paths.getStatePath();
359
- }
360
-
361
- /**
362
- * Save browser state after authentication
363
- */
364
- async saveState(context, page) {
365
- try {
366
- // Save cookies and localStorage
367
- await context.storageState({ path: this.paths.stateFilePath });
368
-
369
- // Save sessionStorage separately
370
- if (page) {
371
- const sessionStorage = await page.evaluate(() => {
372
- const storage = {};
373
- for (let i = 0; i < sessionStorage.length; i++) {
374
- const key = sessionStorage.key(i);
375
- if (key) {
376
- storage[key] = sessionStorage.getItem(key) || '';
377
- }
378
- }
379
- return JSON.stringify(storage);
380
- });
381
-
382
- await fs.promises.writeFile(
383
- this.paths.sessionFilePath,
384
- sessionStorage,
385
- 'utf-8'
386
- );
387
- }
388
-
389
- return true;
390
- } catch (error) {
391
- console.error('Failed to save state:', error.message);
392
- return false;
393
- }
394
- }
395
-
396
- /**
397
- * Clear all authentication data
398
- */
399
- async clearState() {
400
- try {
401
- if (fs.existsSync(this.paths.stateFilePath)) {
402
- fs.unlinkSync(this.paths.stateFilePath);
403
- }
404
- if (fs.existsSync(this.paths.sessionFilePath)) {
405
- fs.unlinkSync(this.paths.sessionFilePath);
406
- }
407
- return true;
408
- } catch (error) {
409
- console.error('Failed to clear state:', error.message);
410
- return false;
411
- }
412
- }
413
-
414
- /**
415
- * Load sessionStorage data
416
- */
417
- async loadSessionStorage() {
418
- try {
419
- const data = await fs.promises.readFile(this.paths.sessionFilePath, 'utf-8');
420
- return JSON.parse(data);
421
- } catch (error) {
422
- return null;
423
- }
424
- }
425
- }
426
-
427
- // ============================================================================
428
- // NotebookLM Session
429
- // ============================================================================
430
-
431
- class NotebookLMSession {
432
- constructor(notebookUrl, options = {}) {
433
- this.notebookUrl = notebookUrl || DEFAULT_CONFIG.notebookUrl;
434
- this.options = { ...DEFAULT_CONFIG, ...options };
435
- this.authManager = new AuthManager();
436
- this.browser = null;
437
- this.context = null;
438
- this.page = null;
439
- this.isActive = false;
440
- this.createdAt = Date.now();
441
- this.lastActivity = Date.now();
442
- }
443
-
444
- /**
445
- * Initialize the browser session
446
- */
447
- async init() {
448
- // Check if patchright is available
449
- let patchright;
450
- try {
451
- patchright = require('patchright');
452
- } catch (e) {
453
- throw new Error('patchright is not installed. Install with: npm install patchright');
454
- }
455
-
456
- const paths = new PathManager();
457
- const statePath = this.authManager.getStatePath();
458
- const needsAuth = !this.authManager.hasValidState();
459
-
460
- console.log(`šŸ”‘ Authentication needed: ${needsAuth ? 'YES' : 'NO (using saved state)'}`);
461
-
462
- // Launch browser
463
- this.browser = await patchright.chromium.launchPersistentContext(
464
- paths.getDataDir(),
465
- {
466
- headless: this.options.headless && !needsAuth, // Show browser for auth
467
- channel: 'chrome',
468
- viewport: this.options.viewport,
469
- args: [
470
- '--disable-blink-features=AutomationControlled'
471
- ]
472
- }
473
- );
474
-
475
- // Get or create page
476
- const pages = this.browser.pages();
477
- this.page = pages[0] || await this.browser.newPage();
478
- this.context = this.browser;
479
-
480
- // Load existing state if available
481
- if (statePath && !needsAuth) {
482
- // State is loaded automatically by launchPersistentContext
483
-
484
- // Restore sessionStorage
485
- const sessionData = await this.authManager.loadSessionStorage();
486
- if (sessionData) {
487
- await this.page.evaluate((data) => {
488
- for (const [key, value] of Object.entries(data)) {
489
- sessionStorage.setItem(key, String(value));
490
- }
491
- }, sessionData);
492
- }
493
- }
494
-
495
- // Navigate to NotebookLM with longer timeout
496
- await this.page.goto(this.notebookUrl, { waitUntil: 'domcontentloaded', timeout: 120000 });
497
-
498
- // Wait for page to be fully loaded
499
- await StealthUtils.sleep(2000);
500
-
501
- // Check if we need to create/select a notebook
502
- const currentUrl = this.page.url();
503
- console.log(`Current URL: ${currentUrl}`);
504
-
505
- // Check if authentication is needed
506
- if (needsAuth) {
507
- console.log('šŸ” Please authenticate in the browser window...');
508
- console.log('āœ‹ Sign in with your Google account, then close the browser when done.');
509
-
510
- // Wait for authentication (user will close browser when done)
511
- await this.waitForAuthentication();
512
-
513
- // Save the authenticated state
514
- await this.authManager.saveState(this.context, this.page);
515
- console.log('āœ… Authentication state saved!');
516
- console.log('');
517
- console.log('šŸ’” Next: Create a notebook in NotebookLM and save its URL for asking questions.');
518
- }
519
-
520
- this.isActive = true;
521
- this.lastActivity = Date.now();
522
-
523
- return true;
524
- }
525
-
526
- /**
527
- * Wait for user to complete authentication
528
- */
529
- async waitForAuthentication() {
530
- // Wait for navigation to authenticated state
531
- // This is a simple implementation - in production you'd poll for auth cookies
532
- return new Promise((resolve) => {
533
- const checkAuth = async () => {
534
- const cookies = await this.context.cookies();
535
- const hasAuthCookie = cookies.some(c =>
536
- c.name === 'SID' || c.name === 'HSID' || c.name === 'SSID'
537
- );
538
-
539
- if (hasAuthCookie) {
540
- // Wait a bit more for page to load
541
- await StealthUtils.sleep(2000);
542
- resolve();
543
- } else {
544
- setTimeout(checkAuth, 2000);
545
- }
546
- };
547
-
548
- // Start checking after initial page load
549
- setTimeout(checkAuth, 3000);
550
- });
551
- }
552
-
553
- /**
554
- * Ask a question to NotebookLM
555
- */
556
- async ask(question, progressCallback) {
557
- if (!this.isActive) {
558
- throw new Error('Session is not active. Call init() first.');
559
- }
560
-
561
- await progressCallback?.('Asking NotebookLM...');
562
-
563
- // Snapshot existing responses
564
- const { hashes: existingHashes } = await PageUtils.snapshotAllResponses(this.page);
565
-
566
- // Find and click the input field - expanded selectors for NotebookLM
567
- const inputSelectors = [
568
- // Google-specific selectors
569
- 'textarea[aria-label*="ask" i]',
570
- 'textarea[aria-label*="message" i]',
571
- 'textarea[aria-label*="chat" i]',
572
- 'textarea[placeholder*="ask" i]',
573
- 'textarea[placeholder*="Ask" i]',
574
- 'textarea[placeholder*="Chat" i]',
575
- // Generic selectors
576
- 'textarea.contenteditable',
577
- 'rich-textarea',
578
- 'div[contenteditable="true"] textarea',
579
- 'div[contenteditable="true"]',
580
- 'textarea',
581
- // Data attributes
582
- '[data-test-id="chat-input"]',
583
- '[data-testid="chat-input"]',
584
- '[data-test-id="prompt-input"]',
585
- '[data-testid="prompt-input"]',
586
- '[data-automation-id="chat-input"]',
587
- // Specific classes
588
- '.ql-editor',
589
- '.ProseMirror',
590
- 'markdown-textarea',
591
- // If all else fails, get all textareas
592
- 'textarea'
593
- ];
594
-
595
- let inputField = null;
596
- let usedSelector = null;
597
-
598
- for (const selector of inputSelectors) {
599
- try {
600
- const elements = await this.page.$$(selector);
601
- for (const el of elements) {
602
- try {
603
- const isVisible = await el.isVisible();
604
- const isEnabled = await el.isEnabled().catch(() => true);
605
- if (isVisible && isEnabled) {
606
- inputField = el;
607
- usedSelector = selector;
608
- break;
609
- }
610
- } catch (e) {
611
- continue;
612
- }
613
- }
614
- if (inputField) break;
615
- } catch (e) {
616
- continue;
617
- }
618
- }
619
-
620
- if (!inputField) {
621
- // Debug: list all potential input elements on page
622
- const allTextareas = await this.page.$$('textarea, [contenteditable="true"]');
623
- console.log(`Found ${allTextareas.length} potential input elements on page`);
624
-
625
- // Try to get page title for debugging
626
- const title = await this.page.title().catch(() => 'Unknown');
627
- const url = this.page.url();
628
- console.log(`Page title: ${title}`);
629
- console.log(`Current URL: ${url}`);
630
-
631
- // Check if we're on the NotebookLM homepage
632
- const isHomepage = url.includes('notebooklm.google') &&
633
- (url === 'https://notebooklm.google.com/' ||
634
- url === 'https://notebooklm.google.com' ||
635
- url.includes('notebooklm.google/?'));
636
-
637
- if (isHomepage) {
638
- throw new Error('You are on the NotebookLM homepage. Please:\n' +
639
- '1. Create a new notebook or open an existing one\n' +
640
- '2. Copy the notebook URL\n' +
641
- '3. Use that URL with the command: smc notebooklm ask "<question>" <notebook-url>');
642
- }
643
-
644
- throw new Error('Could not find input field on the page. Make sure you are on a NotebookLM notebook page.');
645
- }
646
-
647
- console.log(`āœ… Found input field using selector: ${usedSelector}`);
648
-
649
- // Type the question with human-like behavior
650
- await progressCallback?.('Typing question...');
651
-
652
- if (this.options.humanTyping) {
653
- // Use the selector we found, not inputField.selector()
654
- await this.humanType(usedSelector, question);
655
- } else {
656
- await inputField.fill(question);
657
- }
658
-
659
- await StealthUtils.randomDelay(200, 500);
660
-
661
- // Submit (press Enter)
662
- await this.page.keyboard.press('Enter');
663
-
664
- await progressCallback?.('Waiting for response...');
665
-
666
- // Wait for new response
667
- const response = await PageUtils.waitForNewResponse(
668
- this.page,
669
- existingHashes,
670
- this.options.responseTimeout
671
- );
672
-
673
- this.lastActivity = Date.now();
674
-
675
- if (!response) {
676
- throw new Error('No response received within timeout period');
677
- }
678
-
679
- await progressCallback?.('Response received!');
680
-
681
- return response;
682
- }
683
-
684
- /**
685
- * Type text with human-like behavior
686
- */
687
- async humanType(selector, text) {
688
- const avgDelay = StealthUtils.getTypingDelay(
689
- StealthUtils.randomInt(this.options.typingWpmMin, this.options.typingWpmMax)
690
- );
691
-
692
- await this.page.fill(selector, '');
693
- await this.page.click(selector);
694
- await StealthUtils.randomDelay(50, 150);
695
-
696
- for (const char of text) {
697
- await this.page.keyboard.type(char);
698
- const pause = StealthUtils.getPunctuationPause(char);
699
- const variance = StealthUtils.randomFloat(-30, 30);
700
- await StealthUtils.sleep(Math.max(20, avgDelay + pause + variance));
701
- }
702
- }
703
-
704
- /**
705
- * Check if session has expired
706
- */
707
- isExpired() {
708
- const age = Date.now() - this.lastActivity;
709
- return age > this.options.sessionTimeout;
710
- }
711
-
712
- /**
713
- * Close the session
714
- */
715
- async close() {
716
- this.isActive = false;
717
-
718
- if (this.page) {
719
- await this.page.close().catch(() => {});
720
- }
721
-
722
- if (this.context) {
723
- await this.context.close().catch(() => {});
724
- }
725
-
726
- if (this.browser) {
727
- await this.browser.close().catch(() => {});
728
- }
729
-
730
- this.page = null;
731
- this.context = null;
732
- this.browser = null;
733
- }
734
-
735
- /**
736
- * Get session info
737
- */
738
- getInfo() {
739
- return {
740
- notebookUrl: this.notebookUrl,
741
- isActive: this.isActive,
742
- createdAt: this.createdAt,
743
- lastActivity: this.lastActivity,
744
- age: Date.now() - this.createdAt,
745
- inactiveTime: Date.now() - this.lastActivity
746
- };
747
- }
748
- }
749
-
750
- // ============================================================================
751
- // Session Manager
752
- // ============================================================================
753
-
754
- class SessionManager {
755
- constructor() {
756
- this.sessions = new Map();
757
- this.maxSessions = 5;
758
- }
759
-
760
- /**
761
- * Get or create a session for a notebook
762
- */
763
- async getSession(notebookUrl, options = {}) {
764
- // Clean up expired sessions first
765
- this.cleanupExpired();
766
-
767
- // Find existing session for this notebook
768
- for (const [id, session] of this.sessions) {
769
- if (session.notebookUrl === notebookUrl && session.isActive && !session.isExpired()) {
770
- session.lastActivity = Date.now();
771
- return session;
772
- }
773
- }
774
-
775
- // Create new session
776
- if (this.sessions.size >= this.maxSessions) {
777
- // Close oldest inactive session
778
- const oldest = [...this.sessions.entries()].sort((a, b) => a[1].lastActivity - b[1].lastActivity)[0];
779
- await this.sessions.get(oldest)?.close();
780
- this.sessions.delete(oldest);
781
- }
782
-
783
- const session = new NotebookLMSession(notebookUrl, options);
784
- await session.init();
785
- const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
786
- this.sessions.set(sessionId, session);
787
-
788
- return session;
789
- }
790
-
791
- /**
792
- * Close a specific session
793
- */
794
- async closeSession(sessionId) {
795
- const session = this.sessions.get(sessionId);
796
- if (session) {
797
- await session.close();
798
- this.sessions.delete(sessionId);
799
- return true;
800
- }
801
- return false;
802
- }
803
-
804
- /**
805
- * Close all sessions
806
- */
807
- async closeAll() {
808
- const promises = [...this.sessions.values()].map(s => s.close());
809
- await Promise.all(promises);
810
- this.sessions.clear();
811
- }
812
-
813
- /**
814
- * Clean up expired sessions
815
- */
816
- cleanupExpired() {
817
- for (const [id, session] of this.sessions) {
818
- if (session.isExpired()) {
819
- session.close().catch(() => {});
820
- this.sessions.delete(id);
821
- }
822
- }
823
- }
824
-
825
- /**
826
- * Get session statistics
827
- */
828
- getStats() {
829
- const active = [...this.sessions.values()].filter(s => s.isActive).length;
830
- return {
831
- total: this.sessions.size,
832
- active,
833
- maxSessions: this.maxSessions
834
- };
835
- }
836
- }
837
-
838
- // ============================================================================
839
- // Main NotebookLM Client
840
- // ============================================================================
841
-
842
- class NotebookLMClient {
843
- constructor() {
844
- this.sessionManager = new SessionManager();
845
- this.authManager = new AuthManager();
846
- }
847
-
848
- /**
849
- * Check if authenticated
850
- */
851
- isAuthenticated() {
852
- return this.authManager.hasValidState();
853
- }
854
-
855
- /**
856
- * Setup authentication (opens browser for manual login)
857
- */
858
- async setup(progressCallback) {
859
- await progressCallback?.('Launching browser for authentication...');
860
-
861
- const session = new NotebookLMSession(null, { headless: false });
862
- await session.init();
863
- await session.close();
864
-
865
- await progressCallback?.('Authentication complete!');
866
-
867
- return true;
868
- }
869
-
870
- /**
871
- * Ask a question to NotebookLM
872
- */
873
- async ask(notebookUrl, question, progressCallback) {
874
- const session = await this.sessionManager.getSession(notebookUrl);
875
- return await session.ask(question, progressCallback);
876
- }
877
-
878
- /**
879
- * Close all sessions
880
- */
881
- async closeAll() {
882
- await this.sessionManager.closeAll();
883
- }
884
-
885
- /**
886
- * Get statistics
887
- */
888
- getStats() {
889
- return {
890
- authenticated: this.isAuthenticated(),
891
- sessions: this.sessionManager.getStats()
892
- };
893
- }
894
- }
895
-
896
- // ============================================================================
897
- // Module Singleton
898
- // ============================================================================
899
-
900
- let clientInstance = null;
901
-
902
- function getClient() {
903
- if (!clientInstance) {
904
- clientInstance = new NotebookLMClient();
905
- }
906
- return clientInstance;
907
- }
908
-
909
- // ============================================================================
910
- // CLI Command Handlers
911
- // ============================================================================
912
-
913
- async function handleNotebookLMCommand(args) {
914
- const client = getClient();
915
- const [action, ...rest] = args;
916
-
917
- switch (action) {
918
- case 'auth':
919
- case 'setup': {
920
- console.log('\nšŸ” NotebookLM Authentication\n');
921
-
922
- if (client.isAuthenticated()) {
923
- console.log('āœ… Already authenticated!');
924
- console.log('šŸ’” To re-authenticate, run: smc notebooklm clear && smc notebooklm auth');
925
- return;
926
- }
927
-
928
- try {
929
- await client.setup((msg) => console.log(` ${msg}`));
930
- console.log('\nāœ… Authentication successful!');
931
- } catch (error) {
932
- console.error(`\nāŒ Authentication failed: ${error.message}`);
933
- console.log('\nšŸ’” Make sure you have Google Chrome installed');
934
- console.log('šŸ’” Install patchright: npm install patchright');
935
- process.exit(1);
936
- }
937
- break;
938
- }
939
-
940
- case 'ask': {
941
- const [question, ...urlParts] = rest;
942
- const notebookUrl = urlParts.join(' ') || null;
943
-
944
- if (!question) {
945
- console.error('Usage: smc notebooklm ask "<question>" [notebook-url]');
946
- console.error('');
947
- console.error('Examples:');
948
- console.error(' smc notebooklm ask "What are best practices for API design?"');
949
- console.error(' smc notebooklm ask "Summarize this document" https://notebooklm.google.com/notebook/...');
950
- process.exit(1);
951
- }
952
-
953
- console.log(`\nšŸ““ Asking: ${question}\n`);
954
-
955
- if (!notebookUrl) {
956
- console.log('āš ļø No notebook URL provided. You need to:');
957
- console.log(' 1. Go to https://notebooklm.google.com');
958
- console.log(' 2. Create a new notebook (or open an existing one)');
959
- console.log(' 3. Add sources to your notebook (PDFs, docs, etc.)');
960
- console.log(' 4. Copy the notebook URL from the address bar');
961
- console.log(' 5. Run: smc notebooklm ask "' + question + '" <your-notebook-url>');
962
- console.log('');
963
- console.log('šŸ’” The notebook URL looks like:');
964
- console.log(' https://notebooklm.google.com/notebook/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
965
- console.log('');
966
- process.exit(1);
967
- }
968
-
969
- try {
970
- const response = await client.ask(notebookUrl, question, (msg) => {
971
- console.log(` ${msg}`);
972
- });
973
-
974
- console.log('\nšŸ“ Response:\n');
975
- console.log(response);
976
- console.log('\n');
977
- } catch (error) {
978
- console.error(`\nāŒ Error: ${error.message}`);
979
- process.exit(1);
980
- }
981
- break;
982
- }
983
-
984
- case 'status': {
985
- const stats = client.getStats();
986
-
987
- console.log('\nšŸ“Š NotebookLM Status\n');
988
- console.log(` Authenticated: ${stats.authenticated ? 'āœ… Yes' : 'āŒ No'}`);
989
- console.log(` Active Sessions: ${stats.sessions.active}/${stats.sessions.maxSessions}`);
990
- console.log('');
991
- break;
992
- }
993
-
994
- case 'clear': {
995
- const authManager = new AuthManager();
996
- await authManager.clearState();
997
- await client.closeAll();
998
- console.log('\nāœ… Cleared authentication data and closed all sessions\n');
999
- break;
1000
- }
1001
-
1002
- default:
1003
- console.log(`
1004
- NotebookLM Commands:
1005
-
1006
- smc notebooklm auth Authenticate with NotebookLM
1007
- smc notebooklm ask "<question>" Ask a question
1008
- smc notebooklm status Show authentication status
1009
- smc notebooklm clear Clear authentication data
1010
-
1011
- Examples:
1012
- smc notebooklm auth
1013
- smc notebooklm ask "What are the best practices for REST API design?"
1014
- smc notebooklm status
1015
- `);
1016
- }
1017
- }
1018
-
1019
- module.exports = {
1020
- NotebookLMClient,
1021
- NotebookLMSession,
1022
- AuthManager,
1023
- SessionManager,
1024
- getClient,
1025
- handleNotebookLMCommand,
1026
- PageUtils,
1027
- StealthUtils
1028
- };