@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.
- package/.agent/agents/ai-asset-factory.md +700 -0
- package/.agent/agents/ai-audio-factory.md +503 -0
- package/.agent/agents/game-developer.md +4 -4
- package/.agent/agents/orchestrator.md +113 -3
- package/.agent/agents/project-planner.md +67 -0
- package/.agent/agents/unity-mobile-master.md +949 -0
- package/.agent/mcp/config/registry.json +65 -51
- package/.agent/mcp/servers/notebooklm/README.md +114 -0
- package/.agent/mcp/servers/notebooklm/package.json +35 -0
- package/.agent/mcp/servers/notebooklm/src/auth/chrome.ts +225 -0
- package/.agent/mcp/servers/notebooklm/src/auth/index.ts +1 -0
- package/.agent/mcp/servers/notebooklm/src/index.ts +516 -0
- package/.agent/mcp/servers/notebooklm/src/services/index.ts +3 -0
- package/.agent/mcp/servers/notebooklm/src/services/library.ts +217 -0
- package/.agent/mcp/servers/notebooklm/src/services/notebooklm.ts +380 -0
- package/.agent/mcp/servers/notebooklm/tsconfig.json +15 -0
- package/.agent/mcp-gateway/README.md +169 -20
- package/.agent/mcp-gateway/package.json +22 -7
- package/.agent/mcp-gateway/src/auth/index.ts +55 -0
- package/.agent/mcp-gateway/src/auth/middleware.ts +242 -0
- package/.agent/mcp-gateway/src/auth/oauth.ts +462 -0
- package/.agent/mcp-gateway/src/auth/scopes.ts +227 -0
- package/.agent/mcp-gateway/src/index.ts +252 -105
- package/.agent/mcp-gateway/src/observability/index.ts +5 -0
- package/.agent/mcp-gateway/src/observability/otel.ts +405 -0
- package/.agent/mcp-gateway/src/transports/index.ts +5 -0
- package/.agent/mcp-gateway/src/transports/streamableHttp.ts +235 -0
- package/.agent/rules/CODEX.md +89 -0
- package/.agent/rules/CODE_RULES.md +73 -0
- package/.agent/rules/GEMINI.md +25 -0
- package/.agent/rules/MEMORY_STATE.md +110 -0
- package/.agent/rules/REFERENCE.md +33 -141
- package/.agent/rules/REF_SKILLS.md +116 -0
- package/.agent/rules/REF_WORKFLOWS.md +81 -0
- package/.agent/scripts/ak_cli.py +106 -5
- package/.agent/scripts/memory_manager.py +48 -9
- package/.agent/skills/anti-hallucination/SKILL.md +295 -0
- package/.agent/skills/anti-hallucination/scripts/check_hallucination.py +299 -0
- package/.agent/skills/bifurcation-analysis/SKILL.md +56 -0
- package/.agent/skills/brainstorming/SKILL.md +80 -6
- package/.agent/skills/decision-memory/SKILL.md +317 -0
- package/.agent/skills/emergence-detector/SKILL.md +230 -0
- package/.agent/skills/emergence-detector/scripts/check_emergence.py +265 -0
- package/.agent/skills/explained-qa/SKILL.md +142 -0
- package/.agent/skills/explained-qa/game-terminology.md +214 -0
- package/.agent/skills/game-development/ai-dialogue-engine/SKILL.md +442 -0
- package/.agent/skills/game-development/ai-graphics-generator/SKILL.md +463 -0
- package/.agent/skills/game-development/ai-playtest-framework/SKILL.md +570 -0
- package/.agent/skills/game-development/camera-systems/SKILL.md +607 -0
- package/.agent/skills/game-development/card-battle-engine/SKILL.md +618 -0
- package/.agent/skills/game-development/character-controller-3d/SKILL.md +908 -0
- package/.agent/skills/game-development/cloud-save-sync/SKILL.md +527 -0
- package/.agent/skills/game-development/combat-system/SKILL.md +748 -0
- package/.agent/skills/game-development/compliance-rating/SKILL.md +277 -0
- package/.agent/skills/game-development/crossplatform-build/SKILL.md +386 -0
- package/.agent/skills/game-development/cultivation-progression/SKILL.md +520 -0
- package/.agent/skills/game-development/data-driven-balance/SKILL.md +535 -0
- package/.agent/skills/game-development/game-analytics-integrator/SKILL.md +410 -0
- package/.agent/skills/game-development/game-audio-advanced/SKILL.md +646 -0
- package/.agent/skills/game-development/game-economy-designer/SKILL.md +375 -0
- package/.agent/skills/game-development/game-marketing/SKILL.md +85 -0
- package/.agent/skills/game-development/game-state-manager/SKILL.md +883 -0
- package/.agent/skills/game-development/hybrid-game-spec/SKILL.md +220 -0
- package/.agent/skills/game-development/inventory-quest/SKILL.md +747 -0
- package/.agent/skills/game-development/liveops/SKILL.md +308 -0
- package/.agent/skills/game-development/localization/SKILL.md +286 -0
- package/.agent/skills/game-development/mobile-input-patterns/SKILL.md +343 -0
- package/.agent/skills/game-development/monetization-strategy/SKILL.md +94 -0
- package/.agent/skills/game-development/multiplayer-master/SKILL.md +727 -0
- package/.agent/skills/game-development/narrative-branching/SKILL.md +593 -0
- package/.agent/skills/game-development/procedural-level-ai/SKILL.md +367 -0
- package/.agent/skills/game-development/prototyping-rapid/SKILL.md +205 -0
- package/.agent/skills/game-development/spec-ecosystem/SKILL.md +155 -0
- package/.agent/skills/game-development/spec-ecosystem/decision-log-format.md +129 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/PLAN-template.md +178 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/SPEC-template.md +110 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/TASKS-template.md +156 -0
- package/.agent/skills/game-development/survival-systems/SKILL.md +493 -0
- package/.agent/skills/game-development/testing-qa/SKILL.md +270 -0
- package/.agent/skills/game-development/unity-mobile-optimization/SKILL.md +271 -0
- package/.agent/skills/intent-capture/SKILL.md +65 -0
- package/.agent/skills/mcp-composition/SKILL.md +362 -0
- package/.agent/skills/mcp-observability/SKILL.md +323 -0
- package/.agent/skills/mcp-security/SKILL.md +314 -0
- package/.agent/skills/trust-spectrum/SKILL.md +291 -0
- package/.agent/skills/vibe-coding-guard/SKILL.md +328 -0
- package/.agent/templates/AGENTS.game.md +63 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.en.md +100 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.vi.md +100 -0
- package/.agent/workflows/ai-agent.md +2 -0
- package/.agent/workflows/autofix.md +1 -0
- package/.agent/workflows/brainstorm.md +1 -0
- package/.agent/workflows/context.md +1 -0
- package/.agent/workflows/create.md +39 -8
- package/.agent/workflows/dashboard.md +1 -0
- package/.agent/workflows/debug.md +14 -0
- package/.agent/workflows/deploy.md +14 -0
- package/.agent/workflows/enhance.md +44 -0
- package/.agent/workflows/gamekit-init.md +177 -0
- package/.agent/workflows/gamekit-launch.md +338 -0
- package/.agent/workflows/gamekit-plan.md +204 -0
- package/.agent/workflows/gamekit-qa.md +153 -0
- package/.agent/workflows/gamekit-spec.md +243 -0
- package/.agent/workflows/gamekit-tasks.md +208 -0
- package/.agent/workflows/marketing.md +2 -0
- package/.agent/workflows/next.md +1 -0
- package/.agent/workflows/orchestrate.md +12 -0
- package/.agent/workflows/pentest.md +2 -0
- package/.agent/workflows/plan.md +42 -0
- package/.agent/workflows/preview.md +1 -0
- package/.agent/workflows/quality.md +1 -0
- package/.agent/workflows/saas.md +2 -0
- package/.agent/workflows/spec.md +42 -0
- package/.agent/workflows/status.md +1 -0
- package/.agent/workflows/test.md +14 -0
- package/.agent/workflows/ui-ux-pro-max.md +1 -0
- package/bin/cli.js +411 -111
- package/package.json +1 -2
- package/.agent/agents/game-asset-curator.md +0 -317
- package/.agent/agents/game-narrative-designer.md +0 -310
- package/.agent/agents/game-qa-agent.md +0 -441
- package/.agent/workflows/game-prototype.md +0 -154
- package/docs/AI_DATA_INFRASTRUCTURE.md +0 -288
- package/docs/CHANGELOG_AI_INFRA.md +0 -141
- 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
|
+
}
|