opencodekit 0.15.18 → 0.15.19

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/dist/index.js CHANGED
@@ -750,7 +750,7 @@ var cac = (name = "") => new CAC(name);
750
750
  // package.json
751
751
  var package_default = {
752
752
  name: "opencodekit",
753
- version: "0.15.18",
753
+ version: "0.15.19",
754
754
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
755
755
  keywords: ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
756
756
  license: "MIT",
@@ -3857,18 +3857,18 @@ async function copyOpenCodeOnly(templateRoot, targetDir) {
3857
3857
  }
3858
3858
  var MODEL_PRESETS = {
3859
3859
  free: {
3860
- model: "opencode/big-pickle",
3860
+ model: "opencode/kimi-k2.5-free",
3861
3861
  agents: {
3862
- build: "opencode/big-pickle",
3863
- plan: "opencode/big-pickle",
3864
- review: "opencode/big-pickle",
3865
- explore: "opencode/grok-code",
3866
- general: "opencode/grok-code",
3867
- looker: "opencode/gpt-5-nano",
3868
- vision: "opencode/gpt-5-nano",
3869
- scout: "opencode/big-pickle",
3870
- painter: "opencode/gpt-5-nano",
3871
- compaction: "opencode/big-pickle"
3862
+ build: "opencode/kimi-k2.5-free",
3863
+ plan: "opencode/kimi-k2.5-free",
3864
+ review: "opencode/minimax-m2.1-free",
3865
+ explore: "opencode/glm-4.7-free",
3866
+ general: "opencode/kimi-k2.5-free",
3867
+ looker: "opencode/kimi-k2.5-free",
3868
+ vision: "opencode/kimi-k2.5-free",
3869
+ scout: "opencode/minimax-m2.1-free",
3870
+ painter: "opencode/kimi-k2.5-free",
3871
+ compaction: "opencode/kimi-k2.5-free"
3872
3872
  }
3873
3873
  },
3874
3874
  recommend: {
@@ -3878,12 +3878,12 @@ var MODEL_PRESETS = {
3878
3878
  plan: "openai/gpt-5.2",
3879
3879
  review: "openai/gpt-5.2-codex",
3880
3880
  explore: "proxypal/gemini-3-flash-preview",
3881
- general: "github-copilot/claude-haiku-4.5",
3881
+ general: "opencode/kimi-k2.5-free",
3882
3882
  looker: "proxypal/gemini-3-flash-preview",
3883
3883
  vision: "proxypal/gemini-3-pro-preview",
3884
- scout: "proxypal/gemini-3-flash-preview",
3885
- painter: "proxypal/gemini-3-pro-preview",
3886
- compaction: "proxypal/gemini-2.5-flash"
3884
+ scout: "opencode/kimi-k2.5-free",
3885
+ painter: "proxypal/gemini-3-pro-image-preview",
3886
+ compaction: "opencode/kimi-k2.5-free"
3887
3887
  }
3888
3888
  }
3889
3889
  };
@@ -0,0 +1,32 @@
1
+ ---
2
+ type: decision
3
+ created: 2026-01-30T15:07:59.081Z
4
+ confidence: high
5
+ valid_until: null
6
+ superseded_by: null
7
+ concepts: ["github-copilot", "claude", "anthropic-sdk", "reasoning", "api-routing"]
8
+ files: [".opencode/plugins/copilot-auth.ts"]
9
+ ---
10
+
11
+ # 🎯 GitHub Copilot Claude routing: Keep disabled for stability
12
+
13
+ 🟢 **Confidence:** high
14
+
15
+ Claude routing through Anthropic SDK is currently disabled in copilot-auth.ts (commit f7aabde). This decision was made because:
16
+
17
+ 1. **Problem**: Enabling Claude routing to use `@ai-sdk/anthropic` with `/v1` endpoint causes "Not Found: 404" errors
18
+ 2. **Root cause**: GitHub Copilot's API likely doesn't support the native Anthropic `/v1/messages` endpoint for Claude models through this integration
19
+ 3. **Solution**: Keep all models using `@ai-sdk/github-copilot` (OpenAI-compatible `/v1/chat/completions`)
20
+ 4. **Trade-off**: All models work, but Claude models don't get native Anthropic SDK optimizations or reasoning features through the `/v1` gateway
21
+
22
+ To enable reasoning in the future, would need to:
23
+ - Research if GitHub Copilot supports a different endpoint for reasoning
24
+ - Check if there's a separate gateway URL for reasoning models
25
+ - Verify with GitHub Copilot API documentation
26
+
27
+ Current working configuration (commit f7aabde):
28
+ - All models use `@ai-sdk/github-copilot`
29
+ - All models use `/v1/chat/completions` (OpenAI-compatible)
30
+ - Claude models work but don't have reasoning enabled
31
+
32
+ File: .opencode/plugins/copilot-auth.ts (lines 93-100)
@@ -78,11 +78,6 @@ export const CopilotAuthPlugin: Plugin = async ({ client: _client }) => {
78
78
  const info = await getAuth();
79
79
  if (!info || info.type !== "oauth") return {};
80
80
 
81
- const enterpriseUrl = info.enterpriseUrl;
82
- const baseURL = enterpriseUrl
83
- ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}`
84
- : undefined;
85
-
86
81
  if (provider && provider.models) {
87
82
  for (const model of Object.values(provider.models)) {
88
83
  model.cost = {
@@ -94,22 +89,19 @@ export const CopilotAuthPlugin: Plugin = async ({ client: _client }) => {
94
89
  },
95
90
  };
96
91
 
97
- // Sync with official: Handle Claude routing and SDK mapping
98
- const base =
99
- baseURL ?? model.api.url ?? "https://api.githubcopilot.com";
100
- const isClaude = model.id.includes("claude");
101
-
102
- let url = base;
103
- if (isClaude) {
104
- if (!url.endsWith("/v1")) {
105
- url = url.endsWith("/") ? `${url}v1` : `${url}/v1`;
106
- }
107
- }
108
-
109
- model.api.url = url;
110
- model.api.npm = isClaude
111
- ? "@ai-sdk/anthropic"
112
- : "@ai-sdk/github-copilot";
92
+ // TODO: re-enable once messages api has higher rate limits
93
+ // Claude routing is disabled to avoid "Not Found" errors and rate limits
94
+ // const base = baseURL ?? model.api.url ?? "https://api.githubcopilot.com";
95
+ // const isClaude = model.id.includes("claude");
96
+ // let url = base;
97
+ // if (isClaude) {
98
+ // if (!url.endsWith("/v1")) {
99
+ // url = url.endsWith("/") ? `${url}v1` : `${url}/v1`;
100
+ // }
101
+ // }
102
+ // model.api.url = url;
103
+ // model.api.npm = isClaude ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot";
104
+ model.api.npm = "@ai-sdk/github-copilot";
113
105
  }
114
106
  }
115
107
 
@@ -0,0 +1,272 @@
1
+ ---
2
+ name: notebooklm
3
+ description: >
4
+ Query Google NotebookLM notebooks directly from OpenCode for source-grounded, citation-backed answers.
5
+ Use when user mentions NotebookLM, shares notebook URLs, or wants to query their uploaded documents.
6
+ Provides document-only responses with drastically reduced hallucinations.
7
+ version: "1.0.0"
8
+ license: MIT
9
+ ---
10
+
11
+ # NotebookLM Skill
12
+
13
+ Interact with Google NotebookLM to query documentation with Gemini's source-grounded answers. Each question opens a fresh browser session, retrieves the answer exclusively from uploaded documents, and closes.
14
+
15
+ ## When to Use This Skill
16
+
17
+ Trigger when user:
18
+
19
+ - Mentions NotebookLM explicitly
20
+ - Shares NotebookLM URL (`https://notebooklm.google.com/notebook/...`)
21
+ - Asks to query their notebooks/documentation
22
+ - Wants to add documentation to NotebookLM library
23
+ - Uses phrases like "ask my NotebookLM", "check my docs", "query my notebook"
24
+
25
+ ## Prerequisites
26
+
27
+ Before using this skill, ensure:
28
+
29
+ 1. **Python 3.10+** is installed
30
+ 2. **Google Chrome** browser is installed
31
+ 3. **NotebookLM account** with uploaded documents
32
+ 4. **Google authentication** (one-time setup)
33
+
34
+ ## Quick Start
35
+
36
+ ```typescript
37
+ // Check authentication status
38
+ await notebooklm({ operation: "auth", subOperation: "status" });
39
+
40
+ // Query a notebook
41
+ await notebooklm({
42
+ operation: "query",
43
+ question: "What are the key findings in this document?",
44
+ notebookUrl: "https://notebooklm.google.com/notebook/...",
45
+ });
46
+ ```
47
+
48
+ ## Core Workflow
49
+
50
+ ### Step 1: Check Authentication
51
+
52
+ ```typescript
53
+ const authStatus = await notebooklm({
54
+ operation: "auth",
55
+ subOperation: "status",
56
+ });
57
+ ```
58
+
59
+ If not authenticated, proceed to setup.
60
+
61
+ ### Step 2: Authenticate (One-Time Setup)
62
+
63
+ ```typescript
64
+ await notebooklm({
65
+ operation: "auth",
66
+ subOperation: "setup",
67
+ showBrowser: true, // Browser must be visible for manual login
68
+ });
69
+ ```
70
+
71
+ **Important:**
72
+
73
+ - Browser is VISIBLE for authentication
74
+ - Browser window opens automatically
75
+ - User must manually log in to Google
76
+ - Tell user: "A browser window will open for Google login"
77
+
78
+ ### Step 3: Manage Notebook Library
79
+
80
+ ```typescript
81
+ // List all notebooks
82
+ await notebooklm({ operation: "library", subOperation: "list" });
83
+
84
+ // Add notebook to library
85
+ await notebooklm({
86
+ operation: "library",
87
+ subOperation: "add",
88
+ url: "https://notebooklm.google.com/notebook/...",
89
+ name: "API Documentation",
90
+ description: "REST API reference and examples",
91
+ topics: ["api", "rest", "documentation"],
92
+ });
93
+
94
+ // Search notebooks
95
+ await notebooklm({
96
+ operation: "library",
97
+ subOperation: "search",
98
+ query: "api",
99
+ });
100
+
101
+ // Set active notebook
102
+ await notebooklm({
103
+ operation: "library",
104
+ subOperation: "activate",
105
+ notebookId: "api-docs",
106
+ });
107
+ ```
108
+
109
+ ### Step 4: Ask Questions
110
+
111
+ ```typescript
112
+ // Basic query (uses active notebook if set)
113
+ await notebooklm({
114
+ operation: "query",
115
+ question: "What are the authentication requirements?",
116
+ });
117
+
118
+ // Query specific notebook by ID
119
+ await notebooklm({
120
+ operation: "query",
121
+ question: "What are the authentication requirements?",
122
+ notebookId: "api-docs",
123
+ });
124
+
125
+ // Query with notebook URL directly
126
+ await notebooklm({
127
+ operation: "query",
128
+ question: "What are the authentication requirements?",
129
+ notebookUrl: "https://notebooklm.google.com/notebook/...",
130
+ });
131
+
132
+ // Show browser for debugging
133
+ await notebooklm({
134
+ operation: "query",
135
+ question: "What are the authentication requirements?",
136
+ showBrowser: true,
137
+ });
138
+ ```
139
+
140
+ ## Follow-Up Mechanism (CRITICAL)
141
+
142
+ Every NotebookLM answer ends with: **"EXTREMELY IMPORTANT: Is that ALL you need to know?"**
143
+
144
+ **Required Behavior:**
145
+
146
+ 1. **STOP** - Do not immediately respond to user
147
+ 2. **ANALYZE** - Compare answer to user's original request
148
+ 3. **IDENTIFY GAPS** - Determine if more information needed
149
+ 4. **ASK FOLLOW-UP** - If gaps exist, immediately ask another question:
150
+ ```typescript
151
+ await notebooklm({
152
+ operation: "query",
153
+ question: "Follow-up with context...",
154
+ });
155
+ ```
156
+ 5. **REPEAT** - Continue until information is complete
157
+ 6. **SYNTHESIZE** - Combine all answers before responding to user
158
+
159
+ ## Tool Reference
160
+
161
+ ### Authentication Operations
162
+
163
+ | Operation | Sub-Operation | Description |
164
+ | --------- | ------------- | --------------------------------- |
165
+ | `auth` | `setup` | Initial setup (browser visible) |
166
+ | `auth` | `status` | Check authentication |
167
+ | `auth` | `reauth` | Re-authenticate (browser visible) |
168
+ | `auth` | `clear` | Clear authentication |
169
+ | `auth` | `validate` | Validate stored auth |
170
+
171
+ ### Library Operations
172
+
173
+ | Operation | Sub-Operation | Parameters |
174
+ | --------- | ------------- | -------------------------------------- |
175
+ | `library` | `add` | `url`, `name`, `description`, `topics` |
176
+ | `library` | `list` | - |
177
+ | `library` | `search` | `query` |
178
+ | `library` | `activate` | `notebookId` |
179
+ | `library` | `remove` | `notebookId` |
180
+ | `library` | `stats` | - |
181
+
182
+ ### Query Operations
183
+
184
+ | Operation | Parameters |
185
+ | --------- | -------------------------------------------------------------------------------------------------- |
186
+ | `query` | `question` (required), `notebookId` (optional), `notebookUrl` (optional), `showBrowser` (optional) |
187
+
188
+ ## Smart Discovery Pattern
189
+
190
+ When user wants to add a notebook without providing details:
191
+
192
+ ```typescript
193
+ // Step 1: Query the notebook to discover its content
194
+ const discovery = await notebooklm({
195
+ operation: "query",
196
+ question:
197
+ "What is the content of this notebook? What topics are covered? Provide a complete overview briefly and concisely",
198
+ notebookUrl: "https://notebooklm.google.com/notebook/...",
199
+ });
200
+
201
+ // Step 2: Use discovered information to add it
202
+ await notebooklm({
203
+ operation: "library",
204
+ subOperation: "add",
205
+ url: "https://notebooklm.google.com/notebook/...",
206
+ name: "[Based on content]",
207
+ description: "[Based on content]",
208
+ topics: ["[Based on content]"],
209
+ });
210
+ ```
211
+
212
+ ## Decision Flow
213
+
214
+ ```
215
+ User mentions NotebookLM
216
+
217
+ Check auth → notebooklm({ operation: "auth", subOperation: "status" })
218
+
219
+ If not authenticated → notebooklm({ operation: "auth", subOperation: "setup", showBrowser: true })
220
+
221
+ Check/Add notebook → notebooklm({ operation: "library", subOperation: "list/add" })
222
+
223
+ Activate notebook → notebooklm({ operation: "library", subOperation: "activate", notebookId: "..." })
224
+
225
+ Ask question → notebooklm({ operation: "query", question: "..." })
226
+
227
+ See "Is that ALL you need?" → Ask follow-ups until complete
228
+
229
+ Synthesize and respond to user
230
+ ```
231
+
232
+ ## Troubleshooting
233
+
234
+ | Problem | Solution |
235
+ | -------------------- | --------------------------------------- |
236
+ | Not authenticated | Run auth setup with `showBrowser: true` |
237
+ | Authentication fails | Browser must be visible for setup |
238
+ | Rate limit (50/day) | Wait or switch Google account |
239
+ | Notebook not found | Check with `library` → `list` operation |
240
+ | ModuleNotFoundError | Tool auto-installs dependencies |
241
+
242
+ ## Best Practices
243
+
244
+ 1. **Always check auth first** - Before any operations
245
+ 2. **Follow-up questions** - Don't stop at first answer
246
+ 3. **Browser visible for auth** - Required for manual login
247
+ 4. **Include context** - Each question is independent
248
+ 5. **Synthesize answers** - Combine multiple responses
249
+
250
+ ## Limitations
251
+
252
+ - No session persistence (each question = new browser)
253
+ - Rate limits on free Google accounts (50 queries/day)
254
+ - Manual upload required (user must add docs to NotebookLM)
255
+ - Browser overhead (few seconds per question)
256
+ - Requires Python and Chrome installed
257
+
258
+ ## Data Storage
259
+
260
+ All data stored in `~/.opencode/skills/notebooklm/data/`:
261
+
262
+ - `library.json` - Notebook metadata
263
+ - `auth_info.json` - Authentication status
264
+ - `browser_state/` - Browser cookies and session
265
+
266
+ **Security:** Protected by `.gitignore`, never commit to git.
267
+
268
+ ## Resources
269
+
270
+ - **Original Skill**: https://github.com/PleasePrompto/notebooklm-skill
271
+ - **NotebookLM**: https://notebooklm.google.com
272
+ - **Setup Guide**: See `references/setup.md`
@@ -0,0 +1,353 @@
1
+ # NotebookLM Skill Setup Guide
2
+
3
+ Complete setup instructions for the NotebookLM skill integration.
4
+
5
+ ## Prerequisites
6
+
7
+ ### 1. Python 3.10+
8
+
9
+ Check if Python is installed:
10
+
11
+ ```bash
12
+ python --version
13
+ # or
14
+ python3 --version
15
+ ```
16
+
17
+ **Install if missing:**
18
+
19
+ **macOS (using Homebrew):**
20
+
21
+ ```bash
22
+ brew install python@3.11
23
+ ```
24
+
25
+ **Ubuntu/Debian:**
26
+
27
+ ```bash
28
+ sudo apt update
29
+ sudo apt install python3 python3-pip python3-venv
30
+ ```
31
+
32
+ **Windows:**
33
+ Download from [python.org](https://python.org) and install with "Add to PATH" checked.
34
+
35
+ ### 2. Google Chrome
36
+
37
+ Check if Chrome is installed:
38
+
39
+ **macOS:**
40
+
41
+ ```bash
42
+ ls /Applications/Google\ Chrome.app
43
+ ```
44
+
45
+ **Linux:**
46
+
47
+ ```bash
48
+ which google-chrome
49
+ ```
50
+
51
+ **Windows:**
52
+ Check Start Menu for Google Chrome.
53
+
54
+ **Install if missing:**
55
+ Download from [google.com/chrome](https://google.com/chrome)
56
+
57
+ ### 3. NotebookLM Account
58
+
59
+ 1. Visit [notebooklm.google.com](https://notebooklm.google.com)
60
+ 2. Sign in with Google account
61
+ 3. Upload documents to create notebooks
62
+
63
+ ## First-Time Setup
64
+
65
+ ### Step 1: Verify Tool Installation
66
+
67
+ The tool auto-installs the Python skill on first use. Verify it's working:
68
+
69
+ ```typescript
70
+ const setup = await notebooklm({ operation: "setup" });
71
+ console.log(setup.output);
72
+ ```
73
+
74
+ Expected output:
75
+
76
+ ```
77
+ 📦 NotebookLM skill not found. Installing...
78
+ Cloning from https://github.com/PleasePrompto/notebooklm-skill...
79
+ ✓ Skill installed successfully
80
+ 📍 Location: ~/.opencode/skills/notebooklm
81
+ ```
82
+
83
+ ### Step 2: Authenticate with Google
84
+
85
+ **CRITICAL:** Browser must be visible for manual login.
86
+
87
+ ```typescript
88
+ await notebooklm({
89
+ operation: "auth",
90
+ subOperation: "setup",
91
+ showBrowser: true,
92
+ });
93
+ ```
94
+
95
+ **What happens:**
96
+
97
+ 1. Browser window opens automatically
98
+ 2. Navigate to notebooklm.google.com
99
+ 3. **You must manually log in** to your Google account
100
+ 4. Authentication state is saved for future use
101
+
102
+ **Authentication lasts:** ~7 days (then requires re-auth)
103
+
104
+ ### Step 3: Verify Authentication
105
+
106
+ ```typescript
107
+ const status = await notebooklm({
108
+ operation: "auth",
109
+ subOperation: "status",
110
+ });
111
+
112
+ console.log(status.output);
113
+ ```
114
+
115
+ Expected output:
116
+
117
+ ```
118
+ 🔐 Authentication Status:
119
+ Authenticated: Yes
120
+ State age: 0.5 hours
121
+ Last auth: 2025-01-30 14:32:15
122
+ ```
123
+
124
+ ## Adding Notebooks
125
+
126
+ ### Method 1: Smart Discovery (Recommended)
127
+
128
+ If you don't know the notebook content:
129
+
130
+ ```typescript
131
+ // Step 1: Query to discover content
132
+ const discovery = await notebooklm({
133
+ operation: "query",
134
+ question:
135
+ "What is the content of this notebook? What topics are covered? Provide a complete overview briefly and concisely",
136
+ notebookUrl: "https://notebooklm.google.com/notebook/abc123...",
137
+ });
138
+
139
+ // Step 2: Add with discovered info
140
+ await notebooklm({
141
+ operation: "library",
142
+ subOperation: "add",
143
+ url: "https://notebooklm.google.com/notebook/abc123...",
144
+ name: "API Documentation",
145
+ description: "REST API reference with authentication examples",
146
+ topics: ["api", "rest", "authentication"],
147
+ });
148
+ ```
149
+
150
+ ### Method 2: Manual Entry
151
+
152
+ If you already know the details:
153
+
154
+ ```typescript
155
+ await notebooklm({
156
+ operation: "library",
157
+ subOperation: "add",
158
+ url: "https://notebooklm.google.com/notebook/abc123...",
159
+ name: "Project Documentation",
160
+ description: "Architecture and design decisions",
161
+ topics: ["architecture", "design", "documentation"],
162
+ });
163
+ ```
164
+
165
+ ## Usage Examples
166
+
167
+ ### Query Active Notebook
168
+
169
+ ```typescript
170
+ // Set active notebook first
171
+ await notebooklm({
172
+ operation: "library",
173
+ subOperation: "activate",
174
+ notebookId: "api-docs",
175
+ });
176
+
177
+ // Query without specifying notebook
178
+ const answer = await notebooklm({
179
+ operation: "query",
180
+ question: "What are the rate limits?",
181
+ });
182
+
183
+ console.log(answer.output);
184
+ ```
185
+
186
+ ### Query Specific Notebook
187
+
188
+ ```typescript
189
+ const answer = await notebooklm({
190
+ operation: "query",
191
+ question: "What are the rate limits?",
192
+ notebookId: "api-docs",
193
+ });
194
+ ```
195
+
196
+ ### Query by URL
197
+
198
+ ```typescript
199
+ const answer = await notebooklm({
200
+ operation: "query",
201
+ question: "What are the rate limits?",
202
+ notebookUrl: "https://notebooklm.google.com/notebook/abc123...",
203
+ });
204
+ ```
205
+
206
+ ### List All Notebooks
207
+
208
+ ```typescript
209
+ const library = await notebooklm({
210
+ operation: "library",
211
+ subOperation: "list",
212
+ });
213
+
214
+ console.log(library.output);
215
+ ```
216
+
217
+ ### Search Notebooks
218
+
219
+ ```typescript
220
+ const results = await notebooklm({
221
+ operation: "library",
222
+ subOperation: "search",
223
+ query: "authentication",
224
+ });
225
+
226
+ console.log(results.output);
227
+ ```
228
+
229
+ ## Troubleshooting
230
+
231
+ ### "Not authenticated" Error
232
+
233
+ **Cause:** Authentication expired or never completed.
234
+
235
+ **Fix:**
236
+
237
+ ```typescript
238
+ await notebooklm({
239
+ operation: "auth",
240
+ subOperation: "setup",
241
+ showBrowser: true,
242
+ });
243
+ ```
244
+
245
+ ### "ModuleNotFoundError" Error
246
+
247
+ **Cause:** Python dependencies not installed.
248
+
249
+ **Fix:** The tool auto-installs on first use. If it fails:
250
+
251
+ ```bash
252
+ cd ~/.opencode/skills/notebooklm
253
+ python -m venv .venv
254
+ source .venv/bin/activate # Linux/Mac
255
+ # or .venv\Scripts\activate # Windows
256
+ pip install -r requirements.txt
257
+ python -m patchright install chromium
258
+ ```
259
+
260
+ ### "Notebook not found" Error
261
+
262
+ **Cause:** Notebook ID doesn't exist in library.
263
+
264
+ **Fix:**
265
+
266
+ ```typescript
267
+ // List available notebooks
268
+ await notebooklm({ operation: "library", subOperation: "list" });
269
+
270
+ // Or add the notebook first
271
+ await notebooklm({
272
+ operation: "library",
273
+ subOperation: "add",
274
+ url: "...",
275
+ name: "...",
276
+ description: "...",
277
+ topics: ["..."],
278
+ });
279
+ ```
280
+
281
+ ### Rate Limit (50 queries/day)
282
+
283
+ **Cause:** Free Google account limit reached.
284
+
285
+ **Fix:**
286
+
287
+ - Wait 24 hours
288
+ - Use a different Google account
289
+ - Upgrade to Google One AI Premium (if available)
290
+
291
+ ### Browser Not Opening
292
+
293
+ **Cause:** Chrome not found or headless mode enabled.
294
+
295
+ **Fix:**
296
+
297
+ ```typescript
298
+ // Always use showBrowser: true for auth
299
+ await notebooklm({
300
+ operation: "auth",
301
+ subOperation: "setup",
302
+ showBrowser: true, // Required!
303
+ });
304
+ ```
305
+
306
+ ## Data Locations
307
+
308
+ All data stored in `~/.opencode/skills/notebooklm/`:
309
+
310
+ ```
311
+ ~/.opencode/skills/notebooklm/
312
+ ├── data/
313
+ │ ├── library.json # Your notebook library
314
+ │ ├── auth_info.json # Authentication metadata
315
+ │ └── browser_state/ # Browser cookies/session
316
+ │ ├── state.json
317
+ │ └── browser_profile/
318
+ ├── scripts/ # Python automation scripts
319
+ │ ├── run.py # Virtual environment wrapper
320
+ │ ├── ask_question.py # Query interface
321
+ │ ├── notebook_manager.py # Library management
322
+ │ ├── auth_manager.py # Authentication
323
+ │ ├── browser_utils.py # Browser utilities
324
+ │ └── config.py # Configuration
325
+ ├── .venv/ # Python virtual environment
326
+ ├── requirements.txt # Python dependencies
327
+ └── .gitignore # Protects sensitive data
328
+ ```
329
+
330
+ **Security Note:** The `data/` directory contains authentication cookies. Never commit this to git.
331
+
332
+ ## Updating the Skill
333
+
334
+ To update to the latest version:
335
+
336
+ ```bash
337
+ cd ~/.opencode/skills/notebooklm
338
+ git pull origin master
339
+ ```
340
+
341
+ ## Uninstalling
342
+
343
+ To completely remove the skill:
344
+
345
+ ```bash
346
+ rm -rf ~/.opencode/skills/notebooklm
347
+ ```
348
+
349
+ ## Getting Help
350
+
351
+ - **Original Repository:** https://github.com/PleasePrompto/notebooklm-skill
352
+ - **NotebookLM Help:** https://support.google.com/notebooklm
353
+ - **OpenCodeKit Issues:** File an issue in the opencodekit-template repository
@@ -0,0 +1,488 @@
1
+ /**
2
+ * NotebookLM Tool for OpenCodeKit
3
+ *
4
+ * Wraps the Python NotebookLM skill for querying Google NotebookLM notebooks.
5
+ * Auto-clones the skill repository and manages the Python virtual environment.
6
+ *
7
+ * @module notebooklm
8
+ */
9
+
10
+ import { homedir } from "os";
11
+ import { join } from "path";
12
+ import { $ } from "bun";
13
+
14
+ /**
15
+ * Logger interface for OpenCode client app logging
16
+ * Falls back to console if client logger not available
17
+ */
18
+ interface Logger {
19
+ debug: (message: string, extra?: Record<string, unknown>) => void;
20
+ info: (message: string, extra?: Record<string, unknown>) => void;
21
+ warn: (message: string, extra?: Record<string, unknown>) => void;
22
+ error: (message: string, extra?: Record<string, unknown>) => void;
23
+ }
24
+
25
+ /**
26
+ * Create a logger that uses OpenCode client.app.log() when available,
27
+ * otherwise falls back to console
28
+ */
29
+ function createLogger(client?: {
30
+ app: {
31
+ log: (log: {
32
+ service: string;
33
+ level: "debug" | "info" | "warn" | "error";
34
+ message: string;
35
+ extra?: Record<string, unknown>;
36
+ }) => Promise<void>;
37
+ };
38
+ }): Logger {
39
+ const service = "notebooklm";
40
+
41
+ if (client?.app?.log) {
42
+ // Use OpenCode client logging
43
+ return {
44
+ debug: (message: string, extra?: Record<string, unknown>) => {
45
+ client.app
46
+ .log({ service, level: "debug", message, extra })
47
+ .catch(() => {});
48
+ },
49
+ info: (message: string, extra?: Record<string, unknown>) => {
50
+ client.app
51
+ .log({ service, level: "info", message, extra })
52
+ .catch(() => {});
53
+ },
54
+ warn: (message: string, extra?: Record<string, unknown>) => {
55
+ client.app
56
+ .log({ service, level: "warn", message, extra })
57
+ .catch(() => {});
58
+ },
59
+ error: (message: string, extra?: Record<string, unknown>) => {
60
+ client.app
61
+ .log({ service, level: "error", message, extra })
62
+ .catch(() => {});
63
+ },
64
+ };
65
+ }
66
+
67
+ // Fallback to console
68
+ return {
69
+ debug: (message: string, extra?: Record<string, unknown>) => {
70
+ console.log(`[DEBUG] ${message}`, extra ? JSON.stringify(extra) : "");
71
+ },
72
+ info: (message: string, extra?: Record<string, unknown>) => {
73
+ console.log(`[INFO] ${message}`, extra ? JSON.stringify(extra) : "");
74
+ },
75
+ warn: (message: string, extra?: Record<string, unknown>) => {
76
+ console.warn(`[WARN] ${message}`, extra ? JSON.stringify(extra) : "");
77
+ },
78
+ error: (message: string, extra?: Record<string, unknown>) => {
79
+ console.error(`[ERROR] ${message}`, extra ? JSON.stringify(extra) : "");
80
+ },
81
+ };
82
+ }
83
+
84
+ // Global logger instance (will be initialized with client if available)
85
+ let globalLogger: Logger = createLogger();
86
+
87
+ /**
88
+ * Set the logger instance (called by plugin initialization)
89
+ */
90
+ export function setNotebookLMLogger(client?: {
91
+ app: {
92
+ log: (log: {
93
+ service: string;
94
+ level: "debug" | "info" | "warn" | "error";
95
+ message: string;
96
+ extra?: Record<string, unknown>;
97
+ }) => Promise<void>;
98
+ };
99
+ }): void {
100
+ globalLogger = createLogger(client);
101
+ }
102
+
103
+ // Detect Python command (python or python3)
104
+ async function getPythonCommand(): Promise<string> {
105
+ try {
106
+ await $`python --version`.quiet();
107
+ return "python";
108
+ } catch {
109
+ try {
110
+ await $`python3 --version`.quiet();
111
+ return "python3";
112
+ } catch {
113
+ throw new Error("Python not found. Please install Python 3.10+");
114
+ }
115
+ }
116
+ }
117
+
118
+ // Skill repository URL
119
+ const SKILL_REPO = "https://github.com/PleasePrompto/notebooklm-skill";
120
+
121
+ // Skill installation directory
122
+ const SKILL_DIR = join(homedir(), ".opencode", "skills", "notebooklm");
123
+
124
+ // Scripts directory
125
+ const SCRIPTS_DIR = join(SKILL_DIR, "scripts");
126
+
127
+ /**
128
+ * Input parameters for notebooklm tool
129
+ */
130
+ export interface NotebookLMInput {
131
+ /** Operation to perform */
132
+ operation: "auth" | "query" | "library" | "setup";
133
+
134
+ /** Sub-operation for auth and library operations */
135
+ subOperation?:
136
+ | "setup"
137
+ | "status"
138
+ | "reauth"
139
+ | "clear"
140
+ | "validate"
141
+ | "add"
142
+ | "list"
143
+ | "search"
144
+ | "activate"
145
+ | "remove"
146
+ | "stats";
147
+
148
+ /** Question to ask (for query operation) */
149
+ question?: string;
150
+
151
+ /** Notebook URL (for add or query operations) */
152
+ notebookUrl?: string;
153
+
154
+ /** Notebook ID from library (for query or activate operations) */
155
+ notebookId?: string;
156
+
157
+ /** Notebook name (for add operation) */
158
+ name?: string;
159
+
160
+ /** Notebook description (for add operation) */
161
+ description?: string;
162
+
163
+ /** Topics/tags (for add operation) - array or comma-separated string */
164
+ topics?: string[] | string;
165
+
166
+ /** Search query (for library search) */
167
+ query?: string;
168
+
169
+ /** Show browser window (for auth setup or debugging) */
170
+ showBrowser?: boolean;
171
+
172
+ /** Timeout in minutes for authentication */
173
+ timeout?: number;
174
+ }
175
+
176
+ /**
177
+ * Result from notebooklm operation
178
+ */
179
+ export interface NotebookLMResult {
180
+ /** Whether the operation succeeded */
181
+ success: boolean;
182
+
183
+ /** Output from the operation */
184
+ output: string;
185
+
186
+ /** Error message if failed */
187
+ error?: string;
188
+
189
+ /** Parsed data (for library operations) */
190
+ data?: unknown;
191
+ }
192
+
193
+ /**
194
+ * Ensure Python virtual environment is set up with dependencies
195
+ */
196
+ async function ensureVirtualEnv(): Promise<void> {
197
+ const venvPath = join(SKILL_DIR, ".venv");
198
+ const venvPython = join(venvPath, "bin", "python");
199
+ const venvPip = join(venvPath, "bin", "pip");
200
+
201
+ // Check if venv exists with pip
202
+ const venvExists = await Bun.file(venvPython).exists();
203
+ const pipExists = await Bun.file(venvPip).exists();
204
+
205
+ if (venvExists && pipExists) {
206
+ // Check if patchright is installed
207
+ const checkResult =
208
+ await $`cd ${SKILL_DIR} && .venv/bin/python -c "import patchright" 2>&1`.nothrow();
209
+ if (checkResult.exitCode === 0) {
210
+ return;
211
+ }
212
+ }
213
+
214
+ globalLogger.info("Setting up Python virtual environment...");
215
+
216
+ const pythonCmd = await getPythonCommand();
217
+
218
+ // Remove broken venv if exists
219
+ if (venvExists && !pipExists) {
220
+ globalLogger.warn("Removing incomplete virtual environment...");
221
+ await $`cd ${SKILL_DIR} && rm -rf .venv`;
222
+ }
223
+
224
+ // Create virtual environment with pip
225
+ globalLogger.info("Creating virtual environment...");
226
+ const venvResult =
227
+ await $`cd ${SKILL_DIR} && ${pythonCmd} -m venv .venv --without-pip`.nothrow();
228
+ if (venvResult.exitCode !== 0) {
229
+ throw new Error(
230
+ `Failed to create virtual environment: ${venvResult.stderr}`,
231
+ );
232
+ }
233
+
234
+ // Install pip manually
235
+ globalLogger.info("Installing pip...");
236
+ const pipInstallResult =
237
+ await $`cd ${SKILL_DIR} && .venv/bin/python -m ensurepip --upgrade`.nothrow();
238
+ if (pipInstallResult.exitCode !== 0) {
239
+ // Try alternative method
240
+ await $`cd ${SKILL_DIR} && curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && .venv/bin/python get-pip.py && rm get-pip.py`.nothrow();
241
+ }
242
+
243
+ // Install dependencies
244
+ globalLogger.info("Installing dependencies (this may take a minute)...");
245
+ const pipResult =
246
+ await $`cd ${SKILL_DIR} && .venv/bin/python -m pip install -r requirements.txt`.nothrow();
247
+ if (pipResult.exitCode !== 0) {
248
+ throw new Error(`Failed to install dependencies: ${pipResult.stderr}`);
249
+ }
250
+
251
+ // Install patchright browser
252
+ globalLogger.info("Installing browser automation...");
253
+ const browserResult =
254
+ await $`cd ${SKILL_DIR} && .venv/bin/python -m patchright install chromium`.nothrow();
255
+ if (browserResult.exitCode !== 0) {
256
+ globalLogger.warn("Browser install may have issues, but continuing...");
257
+ }
258
+
259
+ globalLogger.info("Virtual environment ready");
260
+ }
261
+
262
+ /**
263
+ * Ensure the NotebookLM skill is installed
264
+ */
265
+ async function ensureSkillInstalled(): Promise<void> {
266
+ const runPyPath = join(SCRIPTS_DIR, "run.py");
267
+
268
+ // Check if skill is already installed
269
+ const exists = await Bun.file(runPyPath).exists();
270
+ if (exists) {
271
+ return;
272
+ }
273
+
274
+ globalLogger.info("NotebookLM skill not found. Installing...");
275
+
276
+ // Create parent directory
277
+ const parentDir = join(homedir(), ".opencode", "skills");
278
+ await $`mkdir -p ${parentDir}`;
279
+
280
+ // Clone the repository
281
+ globalLogger.info(`Cloning from ${SKILL_REPO}...`);
282
+ const result =
283
+ await $`cd ${parentDir} && git clone ${SKILL_REPO} notebooklm`.quiet();
284
+
285
+ if (result.exitCode !== 0) {
286
+ throw new Error(`Failed to clone NotebookLM skill: ${result.stderr}`);
287
+ }
288
+
289
+ globalLogger.info("Skill installed successfully", { location: SKILL_DIR });
290
+
291
+ // Set up virtual environment
292
+ await ensureVirtualEnv();
293
+ }
294
+
295
+ /**
296
+ * Build command arguments for run.py wrapper
297
+ */
298
+ function buildCommandArgs(input: NotebookLMInput): string[] {
299
+ const args: string[] = [];
300
+
301
+ switch (input.operation) {
302
+ case "auth":
303
+ args.push(input.subOperation || "status");
304
+
305
+ if (input.subOperation === "setup" || input.subOperation === "reauth") {
306
+ if (!input.showBrowser) {
307
+ args.push("--headless");
308
+ }
309
+ if (input.timeout) {
310
+ args.push("--timeout", String(input.timeout));
311
+ }
312
+ }
313
+ break;
314
+
315
+ case "library":
316
+ args.push(input.subOperation || "list");
317
+
318
+ if (input.subOperation === "add") {
319
+ if (input.notebookUrl) args.push("--url", input.notebookUrl);
320
+ if (input.name) args.push("--name", input.name);
321
+ if (input.description) args.push("--description", input.description);
322
+ if (input.topics) {
323
+ const topicsStr = Array.isArray(input.topics)
324
+ ? input.topics.join(",")
325
+ : input.topics;
326
+ args.push("--topics", topicsStr);
327
+ }
328
+ } else if (input.subOperation === "search") {
329
+ if (input.query) args.push("--query", input.query);
330
+ } else if (
331
+ input.subOperation === "activate" ||
332
+ input.subOperation === "remove"
333
+ ) {
334
+ if (input.notebookId) args.push("--id", input.notebookId);
335
+ }
336
+ break;
337
+
338
+ case "query":
339
+ // ask_question.py is passed as script parameter
340
+
341
+ if (input.question) {
342
+ args.push("--question", input.question);
343
+ }
344
+
345
+ if (input.notebookUrl) {
346
+ args.push("--notebook-url", input.notebookUrl);
347
+ } else if (input.notebookId) {
348
+ args.push("--notebook-id", input.notebookId);
349
+ }
350
+
351
+ if (input.showBrowser) {
352
+ args.push("--show-browser");
353
+ }
354
+ break;
355
+
356
+ case "setup":
357
+ // Setup is handled by ensureSkillInstalled
358
+ return [];
359
+ }
360
+
361
+ return args;
362
+ }
363
+
364
+ /**
365
+ * Execute a NotebookLM operation
366
+ */
367
+ async function executeOperation(
368
+ script: string,
369
+ args: string[],
370
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
371
+ const runPyPath = join(SCRIPTS_DIR, "run.py");
372
+ const venvPython = join(SKILL_DIR, ".venv", "bin", "python");
373
+
374
+ // Use venv python if available, otherwise fall back to system
375
+ const pythonCmd = (await Bun.file(venvPython).exists())
376
+ ? venvPython
377
+ : await getPythonCommand();
378
+
379
+ // Build full command
380
+ const cmd = [pythonCmd, runPyPath, script, ...args];
381
+
382
+ globalLogger.debug(`Executing: ${cmd.join(" ")}`);
383
+
384
+ // Execute with Bun
385
+ const result = await $`cd ${SKILL_DIR} && ${cmd}`.nothrow();
386
+
387
+ return {
388
+ stdout: result.stdout.toString(),
389
+ stderr: result.stderr.toString(),
390
+ exitCode: result.exitCode,
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Main notebooklm tool function
396
+ *
397
+ * @param input - Operation parameters
398
+ * @returns Result of the operation
399
+ *
400
+ * @example
401
+ * // Check authentication status
402
+ * const status = await notebooklm({ operation: "auth", subOperation: "status" });
403
+ *
404
+ * @example
405
+ * // Query a notebook
406
+ * const answer = await notebooklm({
407
+ * operation: "query",
408
+ * question: "What are the key findings?",
409
+ * notebookId: "my-notebook"
410
+ * });
411
+ */
412
+ export default async function notebooklm(
413
+ input: NotebookLMInput,
414
+ ): Promise<NotebookLMResult> {
415
+ try {
416
+ // Ensure skill is installed
417
+ await ensureSkillInstalled();
418
+
419
+ // Ensure virtual environment is set up (for non-setup operations)
420
+ if (input.operation !== "setup") {
421
+ await ensureVirtualEnv();
422
+ }
423
+
424
+ // Handle setup operation (just verify installation)
425
+ if (input.operation === "setup") {
426
+ return {
427
+ success: true,
428
+ output: `NotebookLM skill installed at: ${SKILL_DIR}\n\nPrerequisites:\n- Python 3.10+ must be installed\n- Google Chrome must be installed\n- Run auth setup before querying`,
429
+ data: { skillDir: SKILL_DIR },
430
+ };
431
+ }
432
+
433
+ // Build command arguments
434
+ const args = buildCommandArgs(input);
435
+
436
+ // Determine which script to run
437
+ let script: string;
438
+ switch (input.operation) {
439
+ case "auth":
440
+ script = "auth_manager.py";
441
+ break;
442
+ case "library":
443
+ script = "notebook_manager.py";
444
+ break;
445
+ case "query":
446
+ script = "ask_question.py";
447
+ break;
448
+ default:
449
+ throw new Error(`Unknown operation: ${input.operation}`);
450
+ }
451
+
452
+ // Execute the operation
453
+ globalLogger.info(
454
+ `NotebookLM: ${input.operation}${input.subOperation ? `/${input.subOperation}` : ""}`,
455
+ );
456
+ const result = await executeOperation(script, args);
457
+
458
+ // Parse result
459
+ const success = result.exitCode === 0;
460
+
461
+ // Try to parse JSON data from output (for library operations)
462
+ let data: unknown = undefined;
463
+ try {
464
+ // Look for JSON in the output
465
+ const jsonMatch = result.stdout.match(/\{[\s\S]*\}/);
466
+ if (jsonMatch) {
467
+ data = JSON.parse(jsonMatch[0]);
468
+ }
469
+ } catch {
470
+ // Not JSON, that's fine
471
+ }
472
+
473
+ return {
474
+ success,
475
+ output: result.stdout || result.stderr,
476
+ error: success ? undefined : result.stderr,
477
+ data,
478
+ };
479
+ } catch (error) {
480
+ const errorMessage = error instanceof Error ? error.message : String(error);
481
+
482
+ return {
483
+ success: false,
484
+ output: "",
485
+ error: errorMessage,
486
+ };
487
+ }
488
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.15.18",
3
+ "version": "0.15.19",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "keywords": ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
6
6
  "license": "MIT",