@musashishao/agent-kit 1.9.0 → 1.9.1

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 (125) hide show
  1. package/.agent/agents/ai-asset-factory.md +700 -0
  2. package/.agent/agents/ai-audio-factory.md +503 -0
  3. package/.agent/agents/game-developer.md +4 -4
  4. package/.agent/agents/orchestrator.md +113 -3
  5. package/.agent/agents/project-planner.md +67 -0
  6. package/.agent/agents/unity-mobile-master.md +949 -0
  7. package/.agent/mcp/config/registry.json +65 -51
  8. package/.agent/mcp/servers/notebooklm/README.md +114 -0
  9. package/.agent/mcp/servers/notebooklm/package.json +35 -0
  10. package/.agent/mcp/servers/notebooklm/src/auth/chrome.ts +225 -0
  11. package/.agent/mcp/servers/notebooklm/src/auth/index.ts +1 -0
  12. package/.agent/mcp/servers/notebooklm/src/index.ts +516 -0
  13. package/.agent/mcp/servers/notebooklm/src/services/index.ts +3 -0
  14. package/.agent/mcp/servers/notebooklm/src/services/library.ts +217 -0
  15. package/.agent/mcp/servers/notebooklm/src/services/notebooklm.ts +380 -0
  16. package/.agent/mcp/servers/notebooklm/tsconfig.json +15 -0
  17. package/.agent/mcp-gateway/README.md +169 -20
  18. package/.agent/mcp-gateway/package.json +22 -7
  19. package/.agent/mcp-gateway/src/auth/index.ts +55 -0
  20. package/.agent/mcp-gateway/src/auth/middleware.ts +242 -0
  21. package/.agent/mcp-gateway/src/auth/oauth.ts +462 -0
  22. package/.agent/mcp-gateway/src/auth/scopes.ts +227 -0
  23. package/.agent/mcp-gateway/src/index.ts +252 -105
  24. package/.agent/mcp-gateway/src/observability/index.ts +5 -0
  25. package/.agent/mcp-gateway/src/observability/otel.ts +405 -0
  26. package/.agent/mcp-gateway/src/transports/index.ts +5 -0
  27. package/.agent/mcp-gateway/src/transports/streamableHttp.ts +235 -0
  28. package/.agent/rules/CODEX.md +89 -0
  29. package/.agent/rules/CODE_RULES.md +73 -0
  30. package/.agent/rules/GEMINI.md +25 -0
  31. package/.agent/rules/MEMORY_STATE.md +110 -0
  32. package/.agent/rules/REFERENCE.md +33 -141
  33. package/.agent/rules/REF_SKILLS.md +116 -0
  34. package/.agent/rules/REF_WORKFLOWS.md +81 -0
  35. package/.agent/scripts/ak_cli.py +106 -5
  36. package/.agent/scripts/memory_manager.py +48 -9
  37. package/.agent/skills/anti-hallucination/SKILL.md +295 -0
  38. package/.agent/skills/anti-hallucination/scripts/check_hallucination.py +299 -0
  39. package/.agent/skills/bifurcation-analysis/SKILL.md +56 -0
  40. package/.agent/skills/brainstorming/SKILL.md +80 -6
  41. package/.agent/skills/decision-memory/SKILL.md +317 -0
  42. package/.agent/skills/emergence-detector/SKILL.md +230 -0
  43. package/.agent/skills/emergence-detector/scripts/check_emergence.py +265 -0
  44. package/.agent/skills/explained-qa/SKILL.md +142 -0
  45. package/.agent/skills/explained-qa/game-terminology.md +214 -0
  46. package/.agent/skills/game-development/ai-dialogue-engine/SKILL.md +442 -0
  47. package/.agent/skills/game-development/ai-graphics-generator/SKILL.md +463 -0
  48. package/.agent/skills/game-development/ai-playtest-framework/SKILL.md +570 -0
  49. package/.agent/skills/game-development/camera-systems/SKILL.md +607 -0
  50. package/.agent/skills/game-development/card-battle-engine/SKILL.md +618 -0
  51. package/.agent/skills/game-development/character-controller-3d/SKILL.md +908 -0
  52. package/.agent/skills/game-development/cloud-save-sync/SKILL.md +527 -0
  53. package/.agent/skills/game-development/combat-system/SKILL.md +748 -0
  54. package/.agent/skills/game-development/compliance-rating/SKILL.md +277 -0
  55. package/.agent/skills/game-development/crossplatform-build/SKILL.md +386 -0
  56. package/.agent/skills/game-development/cultivation-progression/SKILL.md +520 -0
  57. package/.agent/skills/game-development/data-driven-balance/SKILL.md +535 -0
  58. package/.agent/skills/game-development/game-analytics-integrator/SKILL.md +410 -0
  59. package/.agent/skills/game-development/game-audio-advanced/SKILL.md +646 -0
  60. package/.agent/skills/game-development/game-economy-designer/SKILL.md +375 -0
  61. package/.agent/skills/game-development/game-marketing/SKILL.md +85 -0
  62. package/.agent/skills/game-development/game-state-manager/SKILL.md +883 -0
  63. package/.agent/skills/game-development/hybrid-game-spec/SKILL.md +220 -0
  64. package/.agent/skills/game-development/inventory-quest/SKILL.md +747 -0
  65. package/.agent/skills/game-development/liveops/SKILL.md +308 -0
  66. package/.agent/skills/game-development/localization/SKILL.md +286 -0
  67. package/.agent/skills/game-development/mobile-input-patterns/SKILL.md +343 -0
  68. package/.agent/skills/game-development/monetization-strategy/SKILL.md +94 -0
  69. package/.agent/skills/game-development/multiplayer-master/SKILL.md +727 -0
  70. package/.agent/skills/game-development/narrative-branching/SKILL.md +593 -0
  71. package/.agent/skills/game-development/procedural-level-ai/SKILL.md +367 -0
  72. package/.agent/skills/game-development/prototyping-rapid/SKILL.md +205 -0
  73. package/.agent/skills/game-development/spec-ecosystem/SKILL.md +155 -0
  74. package/.agent/skills/game-development/spec-ecosystem/decision-log-format.md +129 -0
  75. package/.agent/skills/game-development/spec-ecosystem/templates/PLAN-template.md +178 -0
  76. package/.agent/skills/game-development/spec-ecosystem/templates/SPEC-template.md +110 -0
  77. package/.agent/skills/game-development/spec-ecosystem/templates/TASKS-template.md +156 -0
  78. package/.agent/skills/game-development/survival-systems/SKILL.md +493 -0
  79. package/.agent/skills/game-development/testing-qa/SKILL.md +270 -0
  80. package/.agent/skills/game-development/unity-mobile-optimization/SKILL.md +271 -0
  81. package/.agent/skills/intent-capture/SKILL.md +65 -0
  82. package/.agent/skills/mcp-composition/SKILL.md +362 -0
  83. package/.agent/skills/mcp-observability/SKILL.md +323 -0
  84. package/.agent/skills/mcp-security/SKILL.md +314 -0
  85. package/.agent/skills/trust-spectrum/SKILL.md +291 -0
  86. package/.agent/skills/vibe-coding-guard/SKILL.md +328 -0
  87. package/.agent/templates/AGENTS.game.md +63 -0
  88. package/.agent/templates/docs/WORKFLOW_GUIDE.en.md +100 -0
  89. package/.agent/templates/docs/WORKFLOW_GUIDE.vi.md +100 -0
  90. package/.agent/workflows/ai-agent.md +2 -0
  91. package/.agent/workflows/autofix.md +1 -0
  92. package/.agent/workflows/brainstorm.md +1 -0
  93. package/.agent/workflows/context.md +1 -0
  94. package/.agent/workflows/create.md +39 -8
  95. package/.agent/workflows/dashboard.md +1 -0
  96. package/.agent/workflows/debug.md +14 -0
  97. package/.agent/workflows/deploy.md +14 -0
  98. package/.agent/workflows/enhance.md +44 -0
  99. package/.agent/workflows/gamekit-init.md +177 -0
  100. package/.agent/workflows/gamekit-launch.md +338 -0
  101. package/.agent/workflows/gamekit-plan.md +204 -0
  102. package/.agent/workflows/gamekit-qa.md +153 -0
  103. package/.agent/workflows/gamekit-spec.md +243 -0
  104. package/.agent/workflows/gamekit-tasks.md +208 -0
  105. package/.agent/workflows/marketing.md +2 -0
  106. package/.agent/workflows/next.md +1 -0
  107. package/.agent/workflows/orchestrate.md +12 -0
  108. package/.agent/workflows/pentest.md +2 -0
  109. package/.agent/workflows/plan.md +42 -0
  110. package/.agent/workflows/preview.md +1 -0
  111. package/.agent/workflows/quality.md +1 -0
  112. package/.agent/workflows/saas.md +2 -0
  113. package/.agent/workflows/spec.md +42 -0
  114. package/.agent/workflows/status.md +1 -0
  115. package/.agent/workflows/test.md +14 -0
  116. package/.agent/workflows/ui-ux-pro-max.md +1 -0
  117. package/bin/cli.js +411 -111
  118. package/package.json +1 -2
  119. package/.agent/agents/game-asset-curator.md +0 -317
  120. package/.agent/agents/game-narrative-designer.md +0 -310
  121. package/.agent/agents/game-qa-agent.md +0 -441
  122. package/.agent/workflows/game-prototype.md +0 -154
  123. package/docs/AI_DATA_INFRASTRUCTURE.md +0 -288
  124. package/docs/CHANGELOG_AI_INFRA.md +0 -141
  125. package/docs/MIGRATION_GUIDE_V1.9.md +0 -55
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Library Service
3
+ *
4
+ * Local storage for bookmark notebooks with tags and descriptions.
5
+ * Enables skill-aware matching and cross-tool sharing.
6
+ */
7
+
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import * as os from "os";
11
+
12
+ const LIBRARY_DIR = process.env.NOTEBOOKLM_LIBRARY_DIR ||
13
+ path.join(os.homedir(), ".agent-kit", "notebooklm", "library");
14
+
15
+ const LIBRARY_FILE = path.join(LIBRARY_DIR, "notebooks.json");
16
+
17
+ export interface LibraryEntry {
18
+ id: string;
19
+ notebookId: string;
20
+ notebookUrl: string;
21
+ title: string;
22
+ description?: string;
23
+ tags: string[];
24
+ skills?: string[]; // Linked Agent Kit skills
25
+ addedAt: string;
26
+ lastUsed?: string;
27
+ }
28
+
29
+ export interface Library {
30
+ entries: LibraryEntry[];
31
+ }
32
+
33
+ function ensureLibraryDir(): void {
34
+ if (!fs.existsSync(LIBRARY_DIR)) {
35
+ fs.mkdirSync(LIBRARY_DIR, { recursive: true });
36
+ }
37
+ }
38
+
39
+ function loadLibrary(): Library {
40
+ ensureLibraryDir();
41
+
42
+ if (fs.existsSync(LIBRARY_FILE)) {
43
+ try {
44
+ return JSON.parse(fs.readFileSync(LIBRARY_FILE, "utf-8"));
45
+ } catch {
46
+ return { entries: [] };
47
+ }
48
+ }
49
+
50
+ return { entries: [] };
51
+ }
52
+
53
+ function saveLibrary(library: Library): void {
54
+ ensureLibraryDir();
55
+ fs.writeFileSync(LIBRARY_FILE, JSON.stringify(library, null, 2));
56
+ }
57
+
58
+ function generateId(): string {
59
+ return Date.now().toString(36) + Math.random().toString(36).slice(2);
60
+ }
61
+
62
+ /**
63
+ * Add a notebook to the library
64
+ */
65
+ export function addToLibrary(
66
+ notebookId: string,
67
+ notebookUrl: string,
68
+ title: string,
69
+ options?: {
70
+ description?: string;
71
+ tags?: string[];
72
+ skills?: string[];
73
+ }
74
+ ): LibraryEntry {
75
+ const library = loadLibrary();
76
+
77
+ // Check if already exists
78
+ const existing = library.entries.find(e => e.notebookId === notebookId);
79
+ if (existing) {
80
+ // Update existing entry
81
+ existing.title = title;
82
+ existing.description = options?.description || existing.description;
83
+ existing.tags = options?.tags || existing.tags;
84
+ existing.skills = options?.skills || existing.skills;
85
+ saveLibrary(library);
86
+ return existing;
87
+ }
88
+
89
+ // Create new entry
90
+ const entry: LibraryEntry = {
91
+ id: generateId(),
92
+ notebookId,
93
+ notebookUrl,
94
+ title,
95
+ description: options?.description,
96
+ tags: options?.tags || [],
97
+ skills: options?.skills,
98
+ addedAt: new Date().toISOString(),
99
+ };
100
+
101
+ library.entries.push(entry);
102
+ saveLibrary(library);
103
+
104
+ return entry;
105
+ }
106
+
107
+ /**
108
+ * Search library by tags or skills
109
+ */
110
+ export function searchLibrary(options?: {
111
+ tags?: string[];
112
+ skills?: string[];
113
+ query?: string;
114
+ }): LibraryEntry[] {
115
+ const library = loadLibrary();
116
+
117
+ if (!options) {
118
+ return library.entries;
119
+ }
120
+
121
+ return library.entries.filter(entry => {
122
+ // Match by tags
123
+ if (options.tags && options.tags.length > 0) {
124
+ const hasMatchingTag = options.tags.some(tag =>
125
+ entry.tags.some(t => t.toLowerCase().includes(tag.toLowerCase()))
126
+ );
127
+ if (!hasMatchingTag) return false;
128
+ }
129
+
130
+ // Match by skills
131
+ if (options.skills && options.skills.length > 0) {
132
+ const hasMatchingSkill = options.skills.some(skill =>
133
+ entry.skills?.some(s => s.toLowerCase().includes(skill.toLowerCase()))
134
+ );
135
+ if (!hasMatchingSkill) return false;
136
+ }
137
+
138
+ // Match by query (title or description)
139
+ if (options.query) {
140
+ const q = options.query.toLowerCase();
141
+ const matchesTitle = entry.title.toLowerCase().includes(q);
142
+ const matchesDesc = entry.description?.toLowerCase().includes(q);
143
+ if (!matchesTitle && !matchesDesc) return false;
144
+ }
145
+
146
+ return true;
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Remove from library
152
+ */
153
+ export function removeFromLibrary(entryId: string): boolean {
154
+ const library = loadLibrary();
155
+ const index = library.entries.findIndex(e => e.id === entryId);
156
+
157
+ if (index >= 0) {
158
+ library.entries.splice(index, 1);
159
+ saveLibrary(library);
160
+ return true;
161
+ }
162
+
163
+ return false;
164
+ }
165
+
166
+ /**
167
+ * Link a notebook to an Agent Kit skill
168
+ */
169
+ export function linkToSkill(entryId: string, skillName: string): LibraryEntry | null {
170
+ const library = loadLibrary();
171
+ const entry = library.entries.find(e => e.id === entryId);
172
+
173
+ if (!entry) return null;
174
+
175
+ if (!entry.skills) entry.skills = [];
176
+ if (!entry.skills.includes(skillName)) {
177
+ entry.skills.push(skillName);
178
+ }
179
+
180
+ saveLibrary(library);
181
+ return entry;
182
+ }
183
+
184
+ /**
185
+ * Get library entry by notebook ID
186
+ */
187
+ export function getByNotebookId(notebookId: string): LibraryEntry | undefined {
188
+ const library = loadLibrary();
189
+ return library.entries.find(e => e.notebookId === notebookId);
190
+ }
191
+
192
+ /**
193
+ * Update last used timestamp
194
+ */
195
+ export function markAsUsed(notebookId: string): void {
196
+ const library = loadLibrary();
197
+ const entry = library.entries.find(e => e.notebookId === notebookId);
198
+
199
+ if (entry) {
200
+ entry.lastUsed = new Date().toISOString();
201
+ saveLibrary(library);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Get recently used notebooks
207
+ */
208
+ export function getRecentlyUsed(limit: number = 5): LibraryEntry[] {
209
+ const library = loadLibrary();
210
+
211
+ return library.entries
212
+ .filter(e => e.lastUsed)
213
+ .sort((a, b) => new Date(b.lastUsed!).getTime() - new Date(a.lastUsed!).getTime())
214
+ .slice(0, limit);
215
+ }
216
+
217
+ export { LIBRARY_DIR };
@@ -0,0 +1,380 @@
1
+ /**
2
+ * NotebookLM Service
3
+ *
4
+ * Browser automation for interacting with NotebookLM.
5
+ * Uses Playwright to navigate and extract data.
6
+ */
7
+
8
+ import { Page, BrowserContext } from "playwright";
9
+ import { getAuthenticatedContext } from "../auth/index.js";
10
+
11
+ const NOTEBOOKLM_URL = "https://notebooklm.google.com";
12
+
13
+ export interface Notebook {
14
+ id: string;
15
+ title: string;
16
+ sourceCount?: number;
17
+ lastModified?: string;
18
+ url: string;
19
+ }
20
+
21
+ export interface Source {
22
+ id: string;
23
+ title: string;
24
+ type: "pdf" | "url" | "text" | "gdoc" | "youtube" | "unknown";
25
+ }
26
+
27
+ export interface QueryResult {
28
+ answer: string;
29
+ citations: Array<{
30
+ sourceTitle: string;
31
+ excerpt: string;
32
+ }>;
33
+ }
34
+
35
+ /**
36
+ * Get a page ready for NotebookLM interactions
37
+ */
38
+ async function getReadyPage(context: BrowserContext): Promise<Page> {
39
+ const pages = context.pages();
40
+ let page = pages.find(p => p.url().includes("notebooklm.google.com"));
41
+
42
+ if (!page) {
43
+ page = await context.newPage();
44
+ await page.goto(NOTEBOOKLM_URL, { waitUntil: "networkidle", timeout: 30000 });
45
+ }
46
+
47
+ return page;
48
+ }
49
+
50
+ /**
51
+ * List all notebooks
52
+ */
53
+ export async function listNotebooks(): Promise<Notebook[]> {
54
+ const context = await getAuthenticatedContext();
55
+
56
+ try {
57
+ const page = await getReadyPage(context);
58
+
59
+ // Wait for notebooks to load
60
+ await page.waitForTimeout(2000);
61
+
62
+ // Try to find notebook elements
63
+ const notebooks: Notebook[] = [];
64
+
65
+ // NotebookLM uses different selectors, try multiple approaches
66
+ const notebookElements = await page.$$('[class*="notebook"], [role="listitem"], a[href*="notebook"]');
67
+
68
+ for (const el of notebookElements) {
69
+ try {
70
+ const href = await el.getAttribute("href");
71
+ const title = await el.innerText();
72
+
73
+ if (href && title && href.includes("notebook")) {
74
+ const id = href.split("/").pop() || "";
75
+ notebooks.push({
76
+ id,
77
+ title: title.trim().split("\n")[0], // First line only
78
+ url: `${NOTEBOOKLM_URL}${href}`,
79
+ });
80
+ }
81
+ } catch {
82
+ // Skip problematic elements
83
+ }
84
+ }
85
+
86
+ // If no notebooks found via selectors, try evaluating page content
87
+ if (notebooks.length === 0) {
88
+ const pageContent = await page.content();
89
+
90
+ // Parse notebook links from page content
91
+ const notebookRegex = /href="(\/notebook\/[^"]+)"[^>]*>([^<]+)/g;
92
+ let match;
93
+ while ((match = notebookRegex.exec(pageContent)) !== null) {
94
+ const [, href, title] = match;
95
+ const id = href.split("/").pop() || "";
96
+ notebooks.push({
97
+ id,
98
+ title: title.trim(),
99
+ url: `${NOTEBOOKLM_URL}${href}`,
100
+ });
101
+ }
102
+ }
103
+
104
+ await context.close();
105
+ return notebooks;
106
+
107
+ } catch (error) {
108
+ await context.close();
109
+ throw error;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Query a notebook
115
+ */
116
+ export async function queryNotebook(
117
+ notebookId: string,
118
+ question: string,
119
+ options?: { includeCitations?: boolean }
120
+ ): Promise<QueryResult> {
121
+ const context = await getAuthenticatedContext();
122
+
123
+ try {
124
+ const page = await context.newPage();
125
+
126
+ // Navigate to specific notebook
127
+ await page.goto(`${NOTEBOOKLM_URL}/notebook/${notebookId}`, {
128
+ waitUntil: "networkidle",
129
+ timeout: 30000,
130
+ });
131
+
132
+ // Wait for chat interface to load
133
+ await page.waitForTimeout(2000);
134
+
135
+ // Find and fill the chat input
136
+ const chatInput = await page.$('textarea, [contenteditable="true"], input[type="text"]');
137
+
138
+ if (!chatInput) {
139
+ throw new Error("Could not find chat input");
140
+ }
141
+
142
+ // Type the question
143
+ await chatInput.fill(question);
144
+
145
+ // Submit (press Enter or click send button)
146
+ await chatInput.press("Enter");
147
+
148
+ // Wait for response
149
+ await page.waitForTimeout(5000);
150
+
151
+ // Try to extract the response
152
+ // NotebookLM renders responses in various ways
153
+ const responseElements = await page.$$('[class*="response"], [class*="message"], [class*="answer"]');
154
+
155
+ let answer = "";
156
+ const citations: QueryResult["citations"] = [];
157
+
158
+ // Get the last response element (most recent answer)
159
+ if (responseElements.length > 0) {
160
+ const lastResponse = responseElements[responseElements.length - 1];
161
+ answer = await lastResponse.innerText();
162
+ }
163
+
164
+ // If no structured response found, try to get page content
165
+ if (!answer) {
166
+ // Wait more for response to render
167
+ await page.waitForTimeout(5000);
168
+
169
+ // Try again
170
+ const allText = await page.$$('[class*="chat"], [class*="conversation"]');
171
+ if (allText.length > 0) {
172
+ const lastEl = allText[allText.length - 1];
173
+ answer = await lastEl.innerText();
174
+ }
175
+ }
176
+
177
+ // Extract citations if requested
178
+ if (options?.includeCitations) {
179
+ const citationElements = await page.$$('[class*="citation"], [class*="source"]');
180
+
181
+ for (const el of citationElements) {
182
+ try {
183
+ const text = await el.innerText();
184
+ citations.push({
185
+ sourceTitle: "Source",
186
+ excerpt: text.trim(),
187
+ });
188
+ } catch {
189
+ // Skip
190
+ }
191
+ }
192
+ }
193
+
194
+ await context.close();
195
+
196
+ return {
197
+ answer: answer.trim() || "Could not extract response. The query was sent but response parsing failed.",
198
+ citations,
199
+ };
200
+
201
+ } catch (error) {
202
+ await context.close();
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Create a new notebook
209
+ */
210
+ export async function createNotebook(title: string): Promise<Notebook> {
211
+ const context = await getAuthenticatedContext();
212
+
213
+ try {
214
+ const page = await getReadyPage(context);
215
+
216
+ // Look for "Create" or "+" button
217
+ const createButton = await page.$('button:has-text("Create"), button:has-text("New"), [aria-label*="Create"]');
218
+
219
+ if (!createButton) {
220
+ throw new Error("Could not find create notebook button");
221
+ }
222
+
223
+ await createButton.click();
224
+ await page.waitForTimeout(2000);
225
+
226
+ // Find title input and fill it
227
+ const titleInput = await page.$('input[type="text"], textarea, [contenteditable="true"]');
228
+ if (titleInput) {
229
+ await titleInput.fill(title);
230
+ }
231
+
232
+ // Look for confirm/save button
233
+ const saveButton = await page.$('button:has-text("Create"), button:has-text("Save"), button[type="submit"]');
234
+ if (saveButton) {
235
+ await saveButton.click();
236
+ }
237
+
238
+ await page.waitForTimeout(3000);
239
+
240
+ // Get the new notebook URL
241
+ const currentUrl = page.url();
242
+ const id = currentUrl.split("/notebook/")[1]?.split("?")[0] || "";
243
+
244
+ await context.close();
245
+
246
+ return {
247
+ id,
248
+ title,
249
+ url: currentUrl,
250
+ };
251
+
252
+ } catch (error) {
253
+ await context.close();
254
+ throw error;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Add a source to a notebook
260
+ */
261
+ export async function addSource(
262
+ notebookId: string,
263
+ sourceType: "url" | "text",
264
+ content: string
265
+ ): Promise<Source> {
266
+ const context = await getAuthenticatedContext();
267
+
268
+ try {
269
+ const page = await context.newPage();
270
+
271
+ // Navigate to notebook
272
+ await page.goto(`${NOTEBOOKLM_URL}/notebook/${notebookId}`, {
273
+ waitUntil: "networkidle",
274
+ timeout: 30000,
275
+ });
276
+
277
+ await page.waitForTimeout(2000);
278
+
279
+ // Find add source button
280
+ const addButton = await page.$('button:has-text("Add"), button:has-text("Source"), [aria-label*="Add source"]');
281
+
282
+ if (!addButton) {
283
+ throw new Error("Could not find add source button");
284
+ }
285
+
286
+ await addButton.click();
287
+ await page.waitForTimeout(1000);
288
+
289
+ if (sourceType === "url") {
290
+ // Look for URL input option
291
+ const urlOption = await page.$('button:has-text("Website"), button:has-text("URL"), [aria-label*="URL"]');
292
+ if (urlOption) {
293
+ await urlOption.click();
294
+ await page.waitForTimeout(500);
295
+ }
296
+
297
+ const urlInput = await page.$('input[type="url"], input[type="text"], input[placeholder*="URL"]');
298
+ if (urlInput) {
299
+ await urlInput.fill(content);
300
+ }
301
+ } else {
302
+ // Text/paste option
303
+ const textOption = await page.$('button:has-text("Text"), button:has-text("Paste"), [aria-label*="Text"]');
304
+ if (textOption) {
305
+ await textOption.click();
306
+ await page.waitForTimeout(500);
307
+ }
308
+
309
+ const textInput = await page.$('textarea, [contenteditable="true"]');
310
+ if (textInput) {
311
+ await textInput.fill(content);
312
+ }
313
+ }
314
+
315
+ // Submit
316
+ const submitButton = await page.$('button:has-text("Add"), button:has-text("Submit"), button[type="submit"]');
317
+ if (submitButton) {
318
+ await submitButton.click();
319
+ }
320
+
321
+ await page.waitForTimeout(3000);
322
+
323
+ await context.close();
324
+
325
+ return {
326
+ id: Date.now().toString(),
327
+ title: sourceType === "url" ? content : content.slice(0, 50) + "...",
328
+ type: sourceType === "url" ? "url" : "text",
329
+ };
330
+
331
+ } catch (error) {
332
+ await context.close();
333
+ throw error;
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Get sources from a notebook
339
+ */
340
+ export async function getSources(notebookId: string): Promise<Source[]> {
341
+ const context = await getAuthenticatedContext();
342
+
343
+ try {
344
+ const page = await context.newPage();
345
+
346
+ await page.goto(`${NOTEBOOKLM_URL}/notebook/${notebookId}`, {
347
+ waitUntil: "networkidle",
348
+ timeout: 30000,
349
+ });
350
+
351
+ await page.waitForTimeout(2000);
352
+
353
+ const sources: Source[] = [];
354
+
355
+ // Find source elements
356
+ const sourceElements = await page.$$('[class*="source"], [role="listitem"]');
357
+
358
+ for (const el of sourceElements) {
359
+ try {
360
+ const title = await el.innerText();
361
+ if (title) {
362
+ sources.push({
363
+ id: Date.now().toString(),
364
+ title: title.trim().split("\n")[0],
365
+ type: "unknown",
366
+ });
367
+ }
368
+ } catch {
369
+ // Skip
370
+ }
371
+ }
372
+
373
+ await context.close();
374
+ return sources;
375
+
376
+ } catch (error) {
377
+ await context.close();
378
+ throw error;
379
+ }
380
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "declaration": true
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }