@techwavedev/agi-agent-kit 1.1.7 → 1.2.7
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.
- package/CHANGELOG.md +142 -1
- package/README.md +195 -15
- package/bin/init.js +154 -5
- package/package.json +6 -3
- package/templates/base/AGENTS.md +54 -23
- package/templates/base/README.md +327 -0
- package/templates/base/directives/memory_integration.md +95 -0
- package/templates/base/execution/memory_manager.py +309 -0
- package/templates/base/execution/session_boot.py +218 -0
- package/templates/base/execution/session_init.py +320 -0
- package/templates/base/requirements.txt +45 -6
- package/templates/base/skill-creator/SKILL_skillcreator.md +3 -3
- package/templates/skills/knowledge/design-md/README.md +0 -0
- package/templates/skills/knowledge/design-md/SKILL.md +0 -0
- package/templates/skills/knowledge/design-md/examples/DESIGN.md +0 -0
- package/templates/skills/knowledge/intelligent-routing/SKILL.md +237 -164
- package/templates/skills/knowledge/notebooklm-rag/SKILL.md +216 -0
- package/templates/skills/knowledge/notebooklm-rag/requirements.txt +9 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/ask_question.py +237 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/auth_manager.py +307 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/browser_utils.py +101 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/cleanup_manager.py +87 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/config.py +45 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/notebook_manager.py +334 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/run.py +92 -0
- package/templates/skills/knowledge/notebooklm-rag/scripts/setup_environment.py +68 -0
- package/templates/skills/knowledge/parallel-agents/SKILL.md +345 -73
- package/templates/skills/knowledge/plugin-discovery/SKILL.md +581 -0
- package/templates/skills/knowledge/plugin-discovery/scripts/platform_setup.py +1083 -0
- package/templates/skills/knowledge/react-components/README.md +0 -0
- package/templates/skills/knowledge/react-components/SKILL.md +0 -0
- package/templates/skills/knowledge/react-components/examples/gold-standard-card.tsx +0 -0
- package/templates/skills/knowledge/react-components/package-lock.json +0 -0
- package/templates/skills/knowledge/react-components/package.json +0 -0
- package/templates/skills/knowledge/react-components/resources/architecture-checklist.md +0 -0
- package/templates/skills/knowledge/react-components/resources/component-template.tsx +0 -0
- package/templates/skills/knowledge/react-components/resources/stitch-api-reference.md +0 -0
- package/templates/skills/knowledge/react-components/resources/style-guide.json +0 -0
- package/templates/skills/knowledge/react-components/scripts/validate.js +0 -0
- package/templates/skills/knowledge/self-update/SKILL.md +0 -0
- package/templates/skills/knowledge/self-update/scripts/update_kit.py +0 -0
- package/templates/skills/knowledge/stitch-loop/README.md +0 -0
- package/templates/skills/knowledge/stitch-loop/SKILL.md +3 -3
- package/templates/skills/knowledge/stitch-loop/examples/SITE.md +0 -0
- package/templates/skills/knowledge/stitch-loop/examples/next-prompt.md +0 -0
- package/templates/skills/knowledge/stitch-loop/resources/baton-schema.md +0 -0
- package/templates/skills/knowledge/stitch-loop/resources/site-template.md +0 -0
- package/templates/skills/stitch-loop/SKILL.md +3 -3
- package/templates/skills/core/qdrant-memory/scripts/__pycache__/embedding_utils.cpython-314.pyc +0 -0
- package/templates/skills/core/qdrant-memory/scripts/__pycache__/init_collection.cpython-314.pyc +0 -0
- package/templates/skills/knowledge/SKILLS_CATALOG.md +0 -796
- package/templates/skills/knowledge/jira/scripts/__pycache__/jira_client.cpython-314.pyc +0 -0
- package/templates/skills/knowledge/notebooklm-mcp/SKILL.md +0 -71
- package/templates/skills/knowledge/notebooklm-mcp/assets/example_asset.txt +0 -24
- package/templates/skills/knowledge/notebooklm-mcp/references/api_reference.md +0 -34
- package/templates/skills/knowledge/notebooklm-mcp/scripts/example.py +0 -19
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: notebooklm-rag
|
|
3
|
+
description: "Deep RAG layer powered by Google NotebookLM + Gemini. The agent autonomously manages notebooks via MCP tools — authentication, library management, querying, follow-ups, and caching. Opt-in for users with a Google account. Default RAG is qdrant-memory. Triggers on: '@notebooklm', 'research my docs', 'deep search', 'query my notebook', 'check my notebooks'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# NotebookLM Deep RAG
|
|
7
|
+
|
|
8
|
+
> **This is a Deep RAG tool.** The agent uses NotebookLM as an autonomous knowledge backend via MCP tools. It handles auth, notebooks, queries, follow-ups, and caching — fully hands-free.
|
|
9
|
+
>
|
|
10
|
+
> **Opt-in:** Requires a Google account with NotebookLM. Default RAG uses `qdrant-memory` (local, offline, no account needed).
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
User question
|
|
16
|
+
↓
|
|
17
|
+
Agent checks Qdrant cache → hit? → return cached answer (0 cost)
|
|
18
|
+
↓ miss
|
|
19
|
+
Agent checks NotebookLM auth → not authenticated? → setup_auth (opens browser)
|
|
20
|
+
↓ authenticated
|
|
21
|
+
Agent resolves notebook → list_notebooks / search_notebooks / select_notebook
|
|
22
|
+
↓
|
|
23
|
+
Agent asks question → ask_question (browser automation, Gemini-grounded answer)
|
|
24
|
+
↓
|
|
25
|
+
Agent evaluates answer → gaps? → ask follow-up automatically
|
|
26
|
+
↓ complete
|
|
27
|
+
Agent stores in Qdrant → cache for future use
|
|
28
|
+
↓
|
|
29
|
+
Agent responds to user with synthesized answer
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## MCP Tools Reference
|
|
33
|
+
|
|
34
|
+
The agent has direct access to these tools. Use them autonomously.
|
|
35
|
+
|
|
36
|
+
### Authentication
|
|
37
|
+
|
|
38
|
+
| Tool | When |
|
|
39
|
+
| ------------ | --------------------------------------------- |
|
|
40
|
+
| `get_health` | First — always check auth status |
|
|
41
|
+
| `setup_auth` | One-time Google login (opens visible browser) |
|
|
42
|
+
| `re_auth` | Switch account or fix expired session |
|
|
43
|
+
|
|
44
|
+
### Library Management
|
|
45
|
+
|
|
46
|
+
| Tool | When |
|
|
47
|
+
| ------------------- | ----------------------------------------------------------------- |
|
|
48
|
+
| `list_notebooks` | See all registered notebooks |
|
|
49
|
+
| `add_notebook` | Register a new notebook (url, name, description, topics required) |
|
|
50
|
+
| `remove_notebook` | Remove a notebook from library |
|
|
51
|
+
| `update_notebook` | Update notebook metadata |
|
|
52
|
+
| `search_notebooks` | Find notebooks by topic/keyword |
|
|
53
|
+
| `select_notebook` | Set active notebook (used as default for queries) |
|
|
54
|
+
| `get_notebook` | Get details of a specific notebook |
|
|
55
|
+
| `get_library_stats` | Library overview |
|
|
56
|
+
|
|
57
|
+
### Querying
|
|
58
|
+
|
|
59
|
+
| Tool | When |
|
|
60
|
+
| --------------- | ------------------------------------- |
|
|
61
|
+
| `ask_question` | Query a notebook — core research tool |
|
|
62
|
+
| `list_sessions` | Check active browser sessions |
|
|
63
|
+
| `close_session` | Close a session when done |
|
|
64
|
+
| `reset_session` | Clear session history |
|
|
65
|
+
|
|
66
|
+
### Maintenance
|
|
67
|
+
|
|
68
|
+
| Tool | When |
|
|
69
|
+
| -------------- | ------------------------------ |
|
|
70
|
+
| `cleanup_data` | Clean browser data, fix issues |
|
|
71
|
+
|
|
72
|
+
## Autonomous Workflow
|
|
73
|
+
|
|
74
|
+
### On Any Research Request:
|
|
75
|
+
|
|
76
|
+
1. **Check Qdrant first** — `memory_manager.py auto --query "..."`. If cache hit, return immediately.
|
|
77
|
+
|
|
78
|
+
2. **Check auth** — `get_health`. If not authenticated, run `setup_auth` and tell user a browser will open.
|
|
79
|
+
|
|
80
|
+
3. **Resolve notebook** — `list_notebooks`. If user mentions a topic, `search_notebooks`. If no notebooks exist, ask user for a NotebookLM URL and `add_notebook`.
|
|
81
|
+
|
|
82
|
+
4. **Ask the question** — `ask_question` with the resolved notebook. Can pass `notebook_id` (from library) or `notebook_url` (direct URL).
|
|
83
|
+
|
|
84
|
+
5. **Follow up** — Every answer ends with "Is that ALL you need to know?" The agent MUST:
|
|
85
|
+
- Compare answer to original request
|
|
86
|
+
- Identify gaps
|
|
87
|
+
- Ask follow-up questions automatically (include full context — each question is a new browser session)
|
|
88
|
+
- Repeat until information is complete
|
|
89
|
+
|
|
90
|
+
6. **Cache in Qdrant** — Store result with `memory_manager.py store` and `cache-store`.
|
|
91
|
+
|
|
92
|
+
7. **Respond** — Synthesize all answers into a cohesive response.
|
|
93
|
+
|
|
94
|
+
### On "Add a notebook":
|
|
95
|
+
|
|
96
|
+
**Smart Add** — If user provides a URL but no details:
|
|
97
|
+
|
|
98
|
+
1. `ask_question` with `notebook_url` and question: "What is the content of this notebook? What topics are covered? Provide a brief overview."
|
|
99
|
+
2. Use the answer to fill in `name`, `description`, `topics`
|
|
100
|
+
3. `add_notebook` with discovered metadata
|
|
101
|
+
|
|
102
|
+
**Manual Add** — If user provides all details directly, just `add_notebook`.
|
|
103
|
+
|
|
104
|
+
### On Notebook Management:
|
|
105
|
+
|
|
106
|
+
- "List my notebooks" → `list_notebooks`
|
|
107
|
+
- "Remove X" → Confirm with user → `remove_notebook`
|
|
108
|
+
- "Switch to X" → `select_notebook`
|
|
109
|
+
- "Update X description" → `update_notebook`
|
|
110
|
+
- "Search for X" → `search_notebooks`
|
|
111
|
+
|
|
112
|
+
## Qdrant Integration (Context Keeping)
|
|
113
|
+
|
|
114
|
+
NotebookLM answers are cached in Qdrant. Prior research is recalled automatically.
|
|
115
|
+
|
|
116
|
+
### Before Query
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python3 execution/memory_manager.py auto --query "<research question>"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `cache_hit: true` → Skip browser, return cached answer
|
|
123
|
+
- `source: memory` → Inject prior context into the question
|
|
124
|
+
- `source: none` → Proceed with NotebookLM query
|
|
125
|
+
|
|
126
|
+
### After Query
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python3 execution/memory_manager.py store \
|
|
130
|
+
--content "Q: [question] A: [answer]" \
|
|
131
|
+
--type technical \
|
|
132
|
+
--project notebooklm-research \
|
|
133
|
+
--tags notebooklm [notebook-name] [topic]
|
|
134
|
+
|
|
135
|
+
python3 execution/memory_manager.py cache-store \
|
|
136
|
+
--query "[question]" \
|
|
137
|
+
--response "[synthesized answer]"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Context Keeping
|
|
141
|
+
|
|
142
|
+
- Prior research on same topic is auto-recalled
|
|
143
|
+
- Previous findings enrich follow-up questions
|
|
144
|
+
- Each session builds compound knowledge
|
|
145
|
+
|
|
146
|
+
## MCP Server Setup
|
|
147
|
+
|
|
148
|
+
The NotebookLM MCP server must be configured in the AI host:
|
|
149
|
+
|
|
150
|
+
### Claude Desktop / Claude Code
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"mcpServers": {
|
|
155
|
+
"notebooklm": {
|
|
156
|
+
"command": "npx",
|
|
157
|
+
"args": ["-y", "@anthropic/notebooklm-mcp"]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Opencode
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"notebooklm": {
|
|
169
|
+
"command": "npx",
|
|
170
|
+
"args": ["-y", "@anthropic/notebooklm-mcp"]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If MCP is not configured, fall back to the Python scripts in `scripts/` (see Fallback section below).
|
|
177
|
+
|
|
178
|
+
## Fallback: Python Scripts
|
|
179
|
+
|
|
180
|
+
When MCP is not available, use the bundled scripts via `run.py`:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
python scripts/run.py auth_manager.py status # Check auth
|
|
184
|
+
python scripts/run.py auth_manager.py setup # Authenticate
|
|
185
|
+
python scripts/run.py notebook_manager.py list # List notebooks
|
|
186
|
+
python scripts/run.py notebook_manager.py add --url URL --name NAME --description DESC --topics TOPICS
|
|
187
|
+
python scripts/run.py ask_question.py --question "..." # Query
|
|
188
|
+
python scripts/run.py ask_question.py --question "..." --notebook-url "https://..."
|
|
189
|
+
python scripts/run.py cleanup_manager.py --confirm # Cleanup
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The `run.py` wrapper auto-creates `.venv`, installs dependencies (patchright, python-dotenv), and installs Chrome.
|
|
193
|
+
|
|
194
|
+
## Troubleshooting
|
|
195
|
+
|
|
196
|
+
| Problem | Solution |
|
|
197
|
+
| ------------------------ | ------------------------------------------------------- |
|
|
198
|
+
| Not authenticated | `setup_auth` (browser opens for Google login) |
|
|
199
|
+
| Rate limit (50/day free) | Wait 24h or `re_auth` with different Google account |
|
|
200
|
+
| Browser crashes | `cleanup_data(preserve_library=true)` then `setup_auth` |
|
|
201
|
+
| Stale cached answer | Re-query or clear Qdrant cache |
|
|
202
|
+
| Notebook not found | `list_notebooks`, then `add_notebook` if missing |
|
|
203
|
+
| MCP not available | Use fallback Python scripts via `run.py` |
|
|
204
|
+
|
|
205
|
+
## Limitations
|
|
206
|
+
|
|
207
|
+
- Rate limits: 50 queries/day (free), 250/day (Google AI Pro)
|
|
208
|
+
- Manual upload: User must add documents to NotebookLM first
|
|
209
|
+
- Browser overhead: Few seconds per query
|
|
210
|
+
- No live notebook discovery: User must provide URLs to register notebooks
|
|
211
|
+
|
|
212
|
+
## Credits
|
|
213
|
+
|
|
214
|
+
MCP server: [PleasePrompto/notebooklm-mcp](https://github.com/PleasePrompto/notebooklm-mcp)
|
|
215
|
+
Browser automation: [PleasePrompto/notebooklm-skill](https://github.com/PleasePrompto/notebooklm-skill) (MIT License)
|
|
216
|
+
Adapted for the Agi Agent Framework with Qdrant memory integration.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# NotebookLM RAG Skill Dependencies
|
|
2
|
+
# These will be installed in the skill's local .venv
|
|
3
|
+
|
|
4
|
+
# Core browser automation with anti-detection
|
|
5
|
+
# Note: After installation, run: patchright install chrome
|
|
6
|
+
patchright==1.55.2
|
|
7
|
+
|
|
8
|
+
# Environment management
|
|
9
|
+
python-dotenv==1.0.0
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Simple NotebookLM Question Interface
|
|
4
|
+
Adapted from PleasePrompto/notebooklm-skill (MIT License)
|
|
5
|
+
|
|
6
|
+
Implements hybrid auth approach:
|
|
7
|
+
- Persistent browser profile (user_data_dir) for fingerprint consistency
|
|
8
|
+
- Manual cookie injection from state.json for session cookies (Playwright bug workaround)
|
|
9
|
+
See: https://github.com/microsoft/playwright/issues/36139
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
import re
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from patchright.sync_api import sync_playwright
|
|
19
|
+
|
|
20
|
+
# Add parent directory to path
|
|
21
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
22
|
+
|
|
23
|
+
from auth_manager import AuthManager
|
|
24
|
+
from notebook_manager import NotebookLibrary
|
|
25
|
+
from config import QUERY_INPUT_SELECTORS, RESPONSE_SELECTORS
|
|
26
|
+
from browser_utils import BrowserFactory, StealthUtils
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Since we don't have persistent sessions, we encourage comprehensive questions
|
|
30
|
+
FOLLOW_UP_REMINDER = (
|
|
31
|
+
"\n\nEXTREMELY IMPORTANT: Is that ALL you need to know? "
|
|
32
|
+
"You can always ask another question! Think about it carefully: "
|
|
33
|
+
"before you reply to the user, review their original request and this answer. "
|
|
34
|
+
"If anything is still unclear or missing, ask me another comprehensive question "
|
|
35
|
+
"that includes all necessary context (since each question opens a new browser session)."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ask_notebooklm(question: str, notebook_url: str, headless: bool = True) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Ask a question to NotebookLM
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
question: Question to ask
|
|
45
|
+
notebook_url: NotebookLM notebook URL
|
|
46
|
+
headless: Run browser in headless mode
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Answer text from NotebookLM
|
|
50
|
+
"""
|
|
51
|
+
auth = AuthManager()
|
|
52
|
+
|
|
53
|
+
if not auth.is_authenticated():
|
|
54
|
+
print("⚠️ Not authenticated. Run: python scripts/run.py auth_manager.py setup")
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
print(f"💬 Asking: {question}")
|
|
58
|
+
print(f"📚 Notebook: {notebook_url}")
|
|
59
|
+
|
|
60
|
+
playwright = None
|
|
61
|
+
context = None
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
playwright = sync_playwright().start()
|
|
65
|
+
|
|
66
|
+
context = BrowserFactory.launch_persistent_context(
|
|
67
|
+
playwright,
|
|
68
|
+
headless=headless
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
page = context.new_page()
|
|
72
|
+
print(" 🌐 Opening notebook...")
|
|
73
|
+
page.goto(notebook_url, wait_until="domcontentloaded")
|
|
74
|
+
|
|
75
|
+
page.wait_for_url(re.compile(r"^https://notebooklm\.google\.com/"), timeout=10000)
|
|
76
|
+
|
|
77
|
+
print(" ⏳ Waiting for query input...")
|
|
78
|
+
query_element = None
|
|
79
|
+
|
|
80
|
+
for selector in QUERY_INPUT_SELECTORS:
|
|
81
|
+
try:
|
|
82
|
+
query_element = page.wait_for_selector(
|
|
83
|
+
selector,
|
|
84
|
+
timeout=10000,
|
|
85
|
+
state="visible"
|
|
86
|
+
)
|
|
87
|
+
if query_element:
|
|
88
|
+
print(f" ✓ Found input: {selector}")
|
|
89
|
+
break
|
|
90
|
+
except:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if not query_element:
|
|
94
|
+
print(" ❌ Could not find query input")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
print(" ⏳ Typing question...")
|
|
98
|
+
|
|
99
|
+
input_selector = QUERY_INPUT_SELECTORS[0]
|
|
100
|
+
StealthUtils.human_type(page, input_selector, question)
|
|
101
|
+
|
|
102
|
+
print(" 📤 Submitting...")
|
|
103
|
+
page.keyboard.press("Enter")
|
|
104
|
+
|
|
105
|
+
StealthUtils.random_delay(500, 1500)
|
|
106
|
+
|
|
107
|
+
print(" ⏳ Waiting for answer...")
|
|
108
|
+
|
|
109
|
+
answer = None
|
|
110
|
+
stable_count = 0
|
|
111
|
+
last_text = None
|
|
112
|
+
deadline = time.time() + 120 # 2 minutes timeout
|
|
113
|
+
|
|
114
|
+
while time.time() < deadline:
|
|
115
|
+
try:
|
|
116
|
+
thinking_element = page.query_selector('div.thinking-message')
|
|
117
|
+
if thinking_element and thinking_element.is_visible():
|
|
118
|
+
time.sleep(1)
|
|
119
|
+
continue
|
|
120
|
+
except:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
for selector in RESPONSE_SELECTORS:
|
|
124
|
+
try:
|
|
125
|
+
elements = page.query_selector_all(selector)
|
|
126
|
+
if elements:
|
|
127
|
+
latest = elements[-1]
|
|
128
|
+
text = latest.inner_text().strip()
|
|
129
|
+
|
|
130
|
+
if text:
|
|
131
|
+
if text == last_text:
|
|
132
|
+
stable_count += 1
|
|
133
|
+
if stable_count >= 3:
|
|
134
|
+
answer = text
|
|
135
|
+
break
|
|
136
|
+
else:
|
|
137
|
+
stable_count = 0
|
|
138
|
+
last_text = text
|
|
139
|
+
except:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
if answer:
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
time.sleep(1)
|
|
146
|
+
|
|
147
|
+
if not answer:
|
|
148
|
+
print(" ❌ Timeout waiting for answer")
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
print(" ✅ Got answer!")
|
|
152
|
+
return answer + FOLLOW_UP_REMINDER
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(f" ❌ Error: {e}")
|
|
156
|
+
import traceback
|
|
157
|
+
traceback.print_exc()
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
finally:
|
|
161
|
+
if context:
|
|
162
|
+
try:
|
|
163
|
+
context.close()
|
|
164
|
+
except:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
if playwright:
|
|
168
|
+
try:
|
|
169
|
+
playwright.stop()
|
|
170
|
+
except:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def main():
|
|
175
|
+
parser = argparse.ArgumentParser(description='Ask NotebookLM a question')
|
|
176
|
+
|
|
177
|
+
parser.add_argument('--question', required=True, help='Question to ask')
|
|
178
|
+
parser.add_argument('--notebook-url', help='NotebookLM notebook URL')
|
|
179
|
+
parser.add_argument('--notebook-id', help='Notebook ID from library')
|
|
180
|
+
parser.add_argument('--show-browser', action='store_true', help='Show browser')
|
|
181
|
+
|
|
182
|
+
args = parser.parse_args()
|
|
183
|
+
|
|
184
|
+
# Resolve notebook URL
|
|
185
|
+
notebook_url = args.notebook_url
|
|
186
|
+
|
|
187
|
+
if not notebook_url and args.notebook_id:
|
|
188
|
+
library = NotebookLibrary()
|
|
189
|
+
notebook = library.get_notebook(args.notebook_id)
|
|
190
|
+
if notebook:
|
|
191
|
+
notebook_url = notebook['url']
|
|
192
|
+
else:
|
|
193
|
+
print(f"❌ Notebook '{args.notebook_id}' not found")
|
|
194
|
+
return 1
|
|
195
|
+
|
|
196
|
+
if not notebook_url:
|
|
197
|
+
library = NotebookLibrary()
|
|
198
|
+
active = library.get_active_notebook()
|
|
199
|
+
if active:
|
|
200
|
+
notebook_url = active['url']
|
|
201
|
+
print(f"📚 Using active notebook: {active['name']}")
|
|
202
|
+
else:
|
|
203
|
+
notebooks = library.list_notebooks()
|
|
204
|
+
if notebooks:
|
|
205
|
+
print("\n📚 Available notebooks:")
|
|
206
|
+
for nb in notebooks:
|
|
207
|
+
mark = " [ACTIVE]" if nb.get('id') == library.active_notebook_id else ""
|
|
208
|
+
print(f" {nb['id']}: {nb['name']}{mark}")
|
|
209
|
+
print("\nSpecify with --notebook-id or set active:")
|
|
210
|
+
print("python scripts/run.py notebook_manager.py activate --id ID")
|
|
211
|
+
else:
|
|
212
|
+
print("❌ No notebooks in library. Add one first:")
|
|
213
|
+
print("python scripts/run.py notebook_manager.py add --url URL --name NAME --description DESC --topics TOPICS")
|
|
214
|
+
return 1
|
|
215
|
+
|
|
216
|
+
answer = ask_notebooklm(
|
|
217
|
+
question=args.question,
|
|
218
|
+
notebook_url=notebook_url,
|
|
219
|
+
headless=not args.show_browser
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if answer:
|
|
223
|
+
print("\n" + "=" * 60)
|
|
224
|
+
print(f"Question: {args.question}")
|
|
225
|
+
print("=" * 60)
|
|
226
|
+
print()
|
|
227
|
+
print(answer)
|
|
228
|
+
print()
|
|
229
|
+
print("=" * 60)
|
|
230
|
+
return 0
|
|
231
|
+
else:
|
|
232
|
+
print("\n❌ Failed to get answer")
|
|
233
|
+
return 1
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
sys.exit(main())
|