@pan-sec/notebooklm-mcp 2026.4.0 → 2026.4.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/dist/notebook-creation/audio-manager.d.ts +7 -15
- package/dist/notebook-creation/audio-manager.js +4 -38
- package/dist/notebook-creation/data-table-manager.d.ts +3 -15
- package/dist/notebook-creation/data-table-manager.js +9 -38
- package/dist/notebook-creation/source-manager-shared.d.ts +75 -0
- package/dist/notebook-creation/source-manager-shared.js +86 -0
- package/dist/notebook-creation/source-manager.js +0 -0
- package/dist/notebook-creation/studio-manager-base.d.ts +57 -0
- package/dist/notebook-creation/studio-manager-base.js +67 -0
- package/dist/notebook-creation/video-manager.d.ts +3 -15
- package/dist/notebook-creation/video-manager.js +3 -38
- package/package.json +1 -1
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* Manages audio overview generation in NotebookLM notebooks.
|
|
5
5
|
* Audio overviews are AI-generated podcast-style summaries of notebook content.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import { SharedContextManager } from "../session/shared-context-manager.js";
|
|
7
|
+
import { StudioManagerBase } from "./studio-manager-base.js";
|
|
9
8
|
export interface AudioStatus {
|
|
10
9
|
status: "not_started" | "generating" | "ready" | "failed" | "unknown";
|
|
11
10
|
progress?: number;
|
|
@@ -23,15 +22,12 @@ export interface DownloadAudioResult {
|
|
|
23
22
|
size?: number;
|
|
24
23
|
error?: string;
|
|
25
24
|
}
|
|
26
|
-
export declare class AudioManager {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* Navigate to a notebook and ensure we're on the right page
|
|
33
|
-
*/
|
|
34
|
-
private navigateToNotebook;
|
|
25
|
+
export declare class AudioManager extends StudioManagerBase {
|
|
26
|
+
protected readonly logName = "audio-manager";
|
|
27
|
+
protected readonly navigateDelay: {
|
|
28
|
+
min: number;
|
|
29
|
+
max: number;
|
|
30
|
+
};
|
|
35
31
|
/**
|
|
36
32
|
* Generate an audio overview for a notebook
|
|
37
33
|
*/
|
|
@@ -48,8 +44,4 @@ export declare class AudioManager {
|
|
|
48
44
|
* Download the generated audio file
|
|
49
45
|
*/
|
|
50
46
|
downloadAudio(notebookUrl: string, outputPath?: string): Promise<DownloadAudioResult>;
|
|
51
|
-
/**
|
|
52
|
-
* Close the page if open
|
|
53
|
-
*/
|
|
54
|
-
private closePage;
|
|
55
47
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { log } from "../utils/logger.js";
|
|
8
8
|
import { randomDelay } from "../utils/stealth-utils.js";
|
|
9
|
+
import { StudioManagerBase, } from "./studio-manager-base.js";
|
|
9
10
|
import fs from "fs";
|
|
10
11
|
import path from "path";
|
|
11
12
|
import os from "os";
|
|
@@ -100,29 +101,9 @@ const AUDIO_SELECTORS = {
|
|
|
100
101
|
text: '[class*="progress-text"], [class*="eta"]',
|
|
101
102
|
},
|
|
102
103
|
};
|
|
103
|
-
export class AudioManager {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
page = null;
|
|
107
|
-
constructor(authManager, contextManager) {
|
|
108
|
-
this.authManager = authManager;
|
|
109
|
-
this.contextManager = contextManager;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Navigate to a notebook and ensure we're on the right page
|
|
113
|
-
*/
|
|
114
|
-
async navigateToNotebook(notebookUrl) {
|
|
115
|
-
const context = await this.contextManager.getOrCreateContext();
|
|
116
|
-
const isAuth = await this.authManager.validateWithRetry(context);
|
|
117
|
-
if (!isAuth) {
|
|
118
|
-
throw new Error("Not authenticated. Run setup_auth first.");
|
|
119
|
-
}
|
|
120
|
-
this.page = await context.newPage();
|
|
121
|
-
await this.page.goto(notebookUrl, { waitUntil: "domcontentloaded" });
|
|
122
|
-
await this.page.waitForLoadState("networkidle").catch(() => { });
|
|
123
|
-
await randomDelay(1500, 2500);
|
|
124
|
-
return this.page;
|
|
125
|
-
}
|
|
104
|
+
export class AudioManager extends StudioManagerBase {
|
|
105
|
+
logName = "audio-manager";
|
|
106
|
+
navigateDelay = { min: 1500, max: 2500 };
|
|
126
107
|
/**
|
|
127
108
|
* Generate an audio overview for a notebook
|
|
128
109
|
*/
|
|
@@ -403,19 +384,4 @@ export class AudioManager {
|
|
|
403
384
|
await this.closePage();
|
|
404
385
|
}
|
|
405
386
|
}
|
|
406
|
-
/**
|
|
407
|
-
* Close the page if open
|
|
408
|
-
*/
|
|
409
|
-
async closePage() {
|
|
410
|
-
if (this.page) {
|
|
411
|
-
try {
|
|
412
|
-
await this.page.close();
|
|
413
|
-
}
|
|
414
|
-
catch (err) {
|
|
415
|
-
log.debug(`audio-manager: closing page: ${err instanceof Error ? err.message : String(err)}`);
|
|
416
|
-
// Ignore close errors
|
|
417
|
-
}
|
|
418
|
-
this.page = null;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
387
|
}
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
* - Artifact title during generation: "Generating data table…"
|
|
15
15
|
* - Chat-embedded tables use standard <table><tr><th>/<td> (no <tbody>)
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
18
|
-
import { SharedContextManager } from "../session/shared-context-manager.js";
|
|
17
|
+
import { StudioManagerBase } from "./studio-manager-base.js";
|
|
19
18
|
export interface DataTable {
|
|
20
19
|
headers: string[];
|
|
21
20
|
rows: string[][];
|
|
@@ -36,15 +35,8 @@ export interface GetDataTableResult {
|
|
|
36
35
|
table?: DataTable;
|
|
37
36
|
error?: string;
|
|
38
37
|
}
|
|
39
|
-
export declare class DataTableManager {
|
|
40
|
-
|
|
41
|
-
private contextManager;
|
|
42
|
-
private page;
|
|
43
|
-
constructor(authManager: AuthManager, contextManager: SharedContextManager);
|
|
44
|
-
/**
|
|
45
|
-
* Navigate to a notebook and ensure we're on the right page
|
|
46
|
-
*/
|
|
47
|
-
private navigateToNotebook;
|
|
38
|
+
export declare class DataTableManager extends StudioManagerBase {
|
|
39
|
+
protected readonly logName = "data-table-manager";
|
|
48
40
|
/**
|
|
49
41
|
* Ensure the Studio panel is visible (expand if collapsed).
|
|
50
42
|
*
|
|
@@ -94,8 +86,4 @@ export declare class DataTableManager {
|
|
|
94
86
|
* to the calling model.
|
|
95
87
|
*/
|
|
96
88
|
private sanitizeTable;
|
|
97
|
-
/**
|
|
98
|
-
* Close the page if open
|
|
99
|
-
*/
|
|
100
|
-
private closePage;
|
|
101
89
|
}
|
|
@@ -17,29 +17,9 @@
|
|
|
17
17
|
import { log } from "../utils/logger.js";
|
|
18
18
|
import { randomDelay } from "../utils/stealth-utils.js";
|
|
19
19
|
import { validateResponse } from "../utils/response-validator.js";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
page = null;
|
|
24
|
-
constructor(authManager, contextManager) {
|
|
25
|
-
this.authManager = authManager;
|
|
26
|
-
this.contextManager = contextManager;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Navigate to a notebook and ensure we're on the right page
|
|
30
|
-
*/
|
|
31
|
-
async navigateToNotebook(notebookUrl) {
|
|
32
|
-
const context = await this.contextManager.getOrCreateContext();
|
|
33
|
-
const isAuth = await this.authManager.validateWithRetry(context);
|
|
34
|
-
if (!isAuth) {
|
|
35
|
-
throw new Error("Not authenticated. Run setup_auth first.");
|
|
36
|
-
}
|
|
37
|
-
this.page = await context.newPage();
|
|
38
|
-
await this.page.goto(notebookUrl, { waitUntil: "domcontentloaded" });
|
|
39
|
-
await this.page.waitForLoadState("networkidle").catch(() => { });
|
|
40
|
-
await randomDelay(2000, 3000);
|
|
41
|
-
return this.page;
|
|
42
|
-
}
|
|
20
|
+
import { StudioManagerBase, } from "./studio-manager-base.js";
|
|
21
|
+
export class DataTableManager extends StudioManagerBase {
|
|
22
|
+
logName = "data-table-manager";
|
|
43
23
|
/**
|
|
44
24
|
* Ensure the Studio panel is visible (expand if collapsed).
|
|
45
25
|
*
|
|
@@ -288,6 +268,12 @@ export class DataTableManager {
|
|
|
288
268
|
error: "Could not click on data table artifact.",
|
|
289
269
|
};
|
|
290
270
|
}
|
|
271
|
+
// Status is already confirmed "ready" above, so a table is expected to
|
|
272
|
+
// render after opening the artifact. Wait for the concrete <table> element
|
|
273
|
+
// that extractTableData() reads, instead of relying on a fixed sleep as the
|
|
274
|
+
// only readiness gate (M30). Bounded + .catch so a miss degrades to the
|
|
275
|
+
// prior behavior (fall through to extractTableData, which returns null).
|
|
276
|
+
await page.waitForSelector("table", { timeout: 10000 }).catch(() => { });
|
|
291
277
|
await randomDelay(2000, 3000);
|
|
292
278
|
// Extract table data from the page
|
|
293
279
|
const table = await this.extractTableData(page);
|
|
@@ -392,19 +378,4 @@ export class DataTableManager {
|
|
|
392
378
|
totalColumns: table.totalColumns,
|
|
393
379
|
};
|
|
394
380
|
}
|
|
395
|
-
/**
|
|
396
|
-
* Close the page if open
|
|
397
|
-
*/
|
|
398
|
-
async closePage() {
|
|
399
|
-
if (this.page) {
|
|
400
|
-
try {
|
|
401
|
-
await this.page.close();
|
|
402
|
-
}
|
|
403
|
-
catch (err) {
|
|
404
|
-
log.debug(`data-table-manager: closing page: ${err instanceof Error ? err.message : String(err)}`);
|
|
405
|
-
// Ignore close errors
|
|
406
|
-
}
|
|
407
|
-
this.page = null;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
381
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source Manager — shared primitives
|
|
3
|
+
*
|
|
4
|
+
* Helpers genuinely shared between the two source managers in
|
|
5
|
+
* `source-manager.ts`:
|
|
6
|
+
* - SourceManager (legacy: list/add/remove on an existing
|
|
7
|
+
* notebook)
|
|
8
|
+
* - NotebookCreationSourceManager (the creation flow)
|
|
9
|
+
*
|
|
10
|
+
* Only logic that is byte-equivalent between the two classes lives here; the
|
|
11
|
+
* managers' distinct flows (source-type selection, submit/insert clicks, error
|
|
12
|
+
* alert handling, waitForSourceProcessing) intentionally differ and stay in
|
|
13
|
+
* place. These helpers are a pure de-duplication and are behavior-preserving.
|
|
14
|
+
*/
|
|
15
|
+
import type { Page } from "patchright";
|
|
16
|
+
export type BrowserDomElement = unknown;
|
|
17
|
+
export type BrowserSourceItem = {
|
|
18
|
+
textContent?: string | null;
|
|
19
|
+
className?: string;
|
|
20
|
+
id?: string;
|
|
21
|
+
getAttribute(name: string): string | null;
|
|
22
|
+
querySelector(selector: string): BrowserDomElement | null;
|
|
23
|
+
click(): void;
|
|
24
|
+
};
|
|
25
|
+
export type BrowserVisibleElement = {
|
|
26
|
+
textContent?: string | null;
|
|
27
|
+
className?: string;
|
|
28
|
+
offsetParent?: unknown;
|
|
29
|
+
parentElement?: BrowserVisibleElement | null;
|
|
30
|
+
getAttribute(name: string): string | null;
|
|
31
|
+
click(): void;
|
|
32
|
+
};
|
|
33
|
+
export type BrowserTextVisibleElement = BrowserVisibleElement & {
|
|
34
|
+
className?: string;
|
|
35
|
+
};
|
|
36
|
+
export type BrowserTextAreaCandidate = BrowserVisibleElement & {
|
|
37
|
+
value?: string;
|
|
38
|
+
};
|
|
39
|
+
export type BrowserInnerTextHandle = {
|
|
40
|
+
innerText(): Promise<string>;
|
|
41
|
+
};
|
|
42
|
+
export type BrowserDocumentContext = {
|
|
43
|
+
document: {
|
|
44
|
+
querySelector(selector: string): BrowserDomElement | null;
|
|
45
|
+
querySelectorAll(selector: string): Iterable<BrowserDomElement>;
|
|
46
|
+
};
|
|
47
|
+
window: {
|
|
48
|
+
innerWidth: number;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Find the first `textInput` selector that resolves to a visible textarea which
|
|
53
|
+
* is NOT the "discover sources" / "search the web" / query-box control.
|
|
54
|
+
*
|
|
55
|
+
* This is the exact, equivalent logic that both SourceManager and
|
|
56
|
+
* NotebookCreationSourceManager used (their two private copies differed only in
|
|
57
|
+
* an unused `rejected` field and the debug-log wording — callers read only the
|
|
58
|
+
* boolean match). The three exclusion substrings are kept byte-identical.
|
|
59
|
+
*
|
|
60
|
+
* @param debugLabel preserves each caller's original debug log line verbatim.
|
|
61
|
+
*/
|
|
62
|
+
export declare function findValidTextInputSelector(page: Page, debugLabel: string): Promise<string | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Set the text-source content via the direct DOM-value path only.
|
|
65
|
+
* The value is passed as a serialized page.evaluate argument (never
|
|
66
|
+
* string-interpolated). We deliberately do NOT use the OS copy/paste
|
|
67
|
+
* buffer: writing to it leaks/overwrites the host's copied data and, when
|
|
68
|
+
* the relevant browser permission is denied (common in headless), a paste
|
|
69
|
+
* keystroke would inject STALE buffered contents and corrupt the source.
|
|
70
|
+
*
|
|
71
|
+
* This is the byte-identical evaluate block both classes used. The differing
|
|
72
|
+
* focus/click step that precedes it (textArea.focus() in the legacy manager,
|
|
73
|
+
* textarea.click() in the creation manager) stays with each caller.
|
|
74
|
+
*/
|
|
75
|
+
export declare function setTextSourceValue(page: Page, selector: string, value: string): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source Manager — shared primitives
|
|
3
|
+
*
|
|
4
|
+
* Helpers genuinely shared between the two source managers in
|
|
5
|
+
* `source-manager.ts`:
|
|
6
|
+
* - SourceManager (legacy: list/add/remove on an existing
|
|
7
|
+
* notebook)
|
|
8
|
+
* - NotebookCreationSourceManager (the creation flow)
|
|
9
|
+
*
|
|
10
|
+
* Only logic that is byte-equivalent between the two classes lives here; the
|
|
11
|
+
* managers' distinct flows (source-type selection, submit/insert clicks, error
|
|
12
|
+
* alert handling, waitForSourceProcessing) intentionally differ and stay in
|
|
13
|
+
* place. These helpers are a pure de-duplication and are behavior-preserving.
|
|
14
|
+
*/
|
|
15
|
+
import { log } from "../utils/logger.js";
|
|
16
|
+
import { getSelectors } from "./selectors.js";
|
|
17
|
+
/**
|
|
18
|
+
* Find the first `textInput` selector that resolves to a visible textarea which
|
|
19
|
+
* is NOT the "discover sources" / "search the web" / query-box control.
|
|
20
|
+
*
|
|
21
|
+
* This is the exact, equivalent logic that both SourceManager and
|
|
22
|
+
* NotebookCreationSourceManager used (their two private copies differed only in
|
|
23
|
+
* an unused `rejected` field and the debug-log wording — callers read only the
|
|
24
|
+
* boolean match). The three exclusion substrings are kept byte-identical.
|
|
25
|
+
*
|
|
26
|
+
* @param debugLabel preserves each caller's original debug log line verbatim.
|
|
27
|
+
*/
|
|
28
|
+
export async function findValidTextInputSelector(page, debugLabel) {
|
|
29
|
+
for (const selector of getSelectors("textInput")) {
|
|
30
|
+
try {
|
|
31
|
+
const state = await page.evaluate((targetSelector) => {
|
|
32
|
+
const browser = globalThis;
|
|
33
|
+
const textarea = browser.document.querySelector(targetSelector);
|
|
34
|
+
if (!textarea || textarea.offsetParent === null) {
|
|
35
|
+
return { matches: false };
|
|
36
|
+
}
|
|
37
|
+
const aria = textarea.getAttribute("aria-label")?.toLowerCase() || "";
|
|
38
|
+
const placeholder = textarea.getAttribute("placeholder")?.toLowerCase() || "";
|
|
39
|
+
const className = textarea.className?.toLowerCase() || "";
|
|
40
|
+
const haystack = `${aria} ${placeholder} ${className}`;
|
|
41
|
+
return {
|
|
42
|
+
matches: !haystack.includes("discover sources") &&
|
|
43
|
+
!haystack.includes("search the web") &&
|
|
44
|
+
!haystack.includes("query-box"),
|
|
45
|
+
};
|
|
46
|
+
}, selector);
|
|
47
|
+
if (state.matches) {
|
|
48
|
+
return selector;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
log.debug(`${debugLabel}: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Set the text-source content via the direct DOM-value path only.
|
|
59
|
+
* The value is passed as a serialized page.evaluate argument (never
|
|
60
|
+
* string-interpolated). We deliberately do NOT use the OS copy/paste
|
|
61
|
+
* buffer: writing to it leaks/overwrites the host's copied data and, when
|
|
62
|
+
* the relevant browser permission is denied (common in headless), a paste
|
|
63
|
+
* keystroke would inject STALE buffered contents and corrupt the source.
|
|
64
|
+
*
|
|
65
|
+
* This is the byte-identical evaluate block both classes used. The differing
|
|
66
|
+
* focus/click step that precedes it (textArea.focus() in the legacy manager,
|
|
67
|
+
* textarea.click() in the creation manager) stays with each caller.
|
|
68
|
+
*/
|
|
69
|
+
export async function setTextSourceValue(page, selector, value) {
|
|
70
|
+
await page.evaluate(({ selector, value }) => {
|
|
71
|
+
const browser = globalThis;
|
|
72
|
+
const textarea = browser.document.querySelector(selector);
|
|
73
|
+
if (!textarea)
|
|
74
|
+
return false;
|
|
75
|
+
textarea.focus();
|
|
76
|
+
if (typeof textarea.value === "string") {
|
|
77
|
+
textarea.value = value;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
textarea.textContent = value;
|
|
81
|
+
}
|
|
82
|
+
textarea.dispatchEvent(new browser.Event("input", { bubbles: true }));
|
|
83
|
+
textarea.dispatchEvent(new browser.Event("change", { bubbles: true }));
|
|
84
|
+
return true;
|
|
85
|
+
}, { selector, value });
|
|
86
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Manager Base
|
|
3
|
+
*
|
|
4
|
+
* Shared page-lifecycle plumbing for the NotebookLM "Studio" managers
|
|
5
|
+
* (audio, video, data table). These managers all open a notebook page,
|
|
6
|
+
* validate authentication, drive a Studio-panel artifact flow, then close
|
|
7
|
+
* the page. The navigation and teardown logic is identical across them and
|
|
8
|
+
* lives here; the artifact-specific flows stay in each subclass.
|
|
9
|
+
*
|
|
10
|
+
* Behavior is preserved exactly:
|
|
11
|
+
* - navigateToNotebook: getOrCreateContext → validateWithRetry (throws
|
|
12
|
+
* "Not authenticated. Run setup_auth first." when false) → newPage →
|
|
13
|
+
* goto(domcontentloaded) → waitForLoadState("networkidle") (errors
|
|
14
|
+
* swallowed) → per-subclass randomDelay → return page.
|
|
15
|
+
* - closePage: close the page, swallowing errors, then null the handle.
|
|
16
|
+
*
|
|
17
|
+
* Per-subclass differences are expressed as protected properties so that no
|
|
18
|
+
* call site needs to thread arguments:
|
|
19
|
+
* - navigateDelay: video & data table use 2000–3000ms, audio uses 1500–2500ms.
|
|
20
|
+
* - logName: the log-prefix used in the swallowed close-error debug line.
|
|
21
|
+
*/
|
|
22
|
+
import type { Page } from "patchright";
|
|
23
|
+
import { AuthManager } from "../auth/auth-manager.js";
|
|
24
|
+
import { SharedContextManager } from "../session/shared-context-manager.js";
|
|
25
|
+
/** Opaque DOM element handle used only in `as`-casts inside page.evaluate bodies. */
|
|
26
|
+
export type BrowserDomElement = unknown;
|
|
27
|
+
/** Minimal `document` shape used inside page.evaluate bodies. Common to all managers. */
|
|
28
|
+
export interface BrowserDocumentContext {
|
|
29
|
+
document: {
|
|
30
|
+
querySelector(selector: string): BrowserDomElement | null;
|
|
31
|
+
querySelectorAll(selector: string): Iterable<BrowserDomElement>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare abstract class StudioManagerBase {
|
|
35
|
+
protected authManager: AuthManager;
|
|
36
|
+
protected contextManager: SharedContextManager;
|
|
37
|
+
protected page: Page | null;
|
|
38
|
+
/**
|
|
39
|
+
* Trailing delay applied after navigation settles. Video & data table use the
|
|
40
|
+
* default (2000–3000ms); audio overrides to 1500–2500ms.
|
|
41
|
+
*/
|
|
42
|
+
protected readonly navigateDelay: {
|
|
43
|
+
min: number;
|
|
44
|
+
max: number;
|
|
45
|
+
};
|
|
46
|
+
/** Log prefix used in the swallowed close-error debug line (kept per-manager). */
|
|
47
|
+
protected abstract readonly logName: string;
|
|
48
|
+
constructor(authManager: AuthManager, contextManager: SharedContextManager);
|
|
49
|
+
/**
|
|
50
|
+
* Navigate to a notebook and ensure we're on the right page
|
|
51
|
+
*/
|
|
52
|
+
protected navigateToNotebook(notebookUrl: string): Promise<Page>;
|
|
53
|
+
/**
|
|
54
|
+
* Close the page if open
|
|
55
|
+
*/
|
|
56
|
+
protected closePage(): Promise<void>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Manager Base
|
|
3
|
+
*
|
|
4
|
+
* Shared page-lifecycle plumbing for the NotebookLM "Studio" managers
|
|
5
|
+
* (audio, video, data table). These managers all open a notebook page,
|
|
6
|
+
* validate authentication, drive a Studio-panel artifact flow, then close
|
|
7
|
+
* the page. The navigation and teardown logic is identical across them and
|
|
8
|
+
* lives here; the artifact-specific flows stay in each subclass.
|
|
9
|
+
*
|
|
10
|
+
* Behavior is preserved exactly:
|
|
11
|
+
* - navigateToNotebook: getOrCreateContext → validateWithRetry (throws
|
|
12
|
+
* "Not authenticated. Run setup_auth first." when false) → newPage →
|
|
13
|
+
* goto(domcontentloaded) → waitForLoadState("networkidle") (errors
|
|
14
|
+
* swallowed) → per-subclass randomDelay → return page.
|
|
15
|
+
* - closePage: close the page, swallowing errors, then null the handle.
|
|
16
|
+
*
|
|
17
|
+
* Per-subclass differences are expressed as protected properties so that no
|
|
18
|
+
* call site needs to thread arguments:
|
|
19
|
+
* - navigateDelay: video & data table use 2000–3000ms, audio uses 1500–2500ms.
|
|
20
|
+
* - logName: the log-prefix used in the swallowed close-error debug line.
|
|
21
|
+
*/
|
|
22
|
+
import { log } from "../utils/logger.js";
|
|
23
|
+
import { randomDelay } from "../utils/stealth-utils.js";
|
|
24
|
+
export class StudioManagerBase {
|
|
25
|
+
authManager;
|
|
26
|
+
contextManager;
|
|
27
|
+
page = null;
|
|
28
|
+
/**
|
|
29
|
+
* Trailing delay applied after navigation settles. Video & data table use the
|
|
30
|
+
* default (2000–3000ms); audio overrides to 1500–2500ms.
|
|
31
|
+
*/
|
|
32
|
+
navigateDelay = { min: 2000, max: 3000 };
|
|
33
|
+
constructor(authManager, contextManager) {
|
|
34
|
+
this.authManager = authManager;
|
|
35
|
+
this.contextManager = contextManager;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Navigate to a notebook and ensure we're on the right page
|
|
39
|
+
*/
|
|
40
|
+
async navigateToNotebook(notebookUrl) {
|
|
41
|
+
const context = await this.contextManager.getOrCreateContext();
|
|
42
|
+
const isAuth = await this.authManager.validateWithRetry(context);
|
|
43
|
+
if (!isAuth) {
|
|
44
|
+
throw new Error("Not authenticated. Run setup_auth first.");
|
|
45
|
+
}
|
|
46
|
+
this.page = await context.newPage();
|
|
47
|
+
await this.page.goto(notebookUrl, { waitUntil: "domcontentloaded" });
|
|
48
|
+
await this.page.waitForLoadState("networkidle").catch(() => { });
|
|
49
|
+
await randomDelay(this.navigateDelay.min, this.navigateDelay.max);
|
|
50
|
+
return this.page;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Close the page if open
|
|
54
|
+
*/
|
|
55
|
+
async closePage() {
|
|
56
|
+
if (this.page) {
|
|
57
|
+
try {
|
|
58
|
+
await this.page.close();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
log.debug(`${this.logName}: closing page: ${err instanceof Error ? err.message : String(err)}`);
|
|
62
|
+
// Ignore close errors
|
|
63
|
+
}
|
|
64
|
+
this.page = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
* - Artifacts appear in .artifact-library-container with .artifact-item-button
|
|
15
15
|
* - Generating state: .shimmer-blue class + .rotate icon + "Generating" title
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
18
|
-
import { SharedContextManager } from "../session/shared-context-manager.js";
|
|
17
|
+
import { StudioManagerBase } from "./studio-manager-base.js";
|
|
19
18
|
/**
|
|
20
19
|
* Visual styles for video overviews (matches actual NotebookLM UI)
|
|
21
20
|
*/
|
|
@@ -34,15 +33,8 @@ export interface GenerateVideoResult {
|
|
|
34
33
|
status: VideoStatus;
|
|
35
34
|
error?: string;
|
|
36
35
|
}
|
|
37
|
-
export declare class VideoManager {
|
|
38
|
-
|
|
39
|
-
private contextManager;
|
|
40
|
-
private page;
|
|
41
|
-
constructor(authManager: AuthManager, contextManager: SharedContextManager);
|
|
42
|
-
/**
|
|
43
|
-
* Navigate to a notebook and ensure we're on the right page
|
|
44
|
-
*/
|
|
45
|
-
private navigateToNotebook;
|
|
36
|
+
export declare class VideoManager extends StudioManagerBase {
|
|
37
|
+
protected readonly logName = "video-manager";
|
|
46
38
|
/**
|
|
47
39
|
* Ensure the Studio panel is visible (expand if collapsed).
|
|
48
40
|
*
|
|
@@ -93,8 +85,4 @@ export declare class VideoManager {
|
|
|
93
85
|
* Check the current video status for a notebook
|
|
94
86
|
*/
|
|
95
87
|
getVideoStatus(notebookUrl: string): Promise<VideoStatus>;
|
|
96
|
-
/**
|
|
97
|
-
* Close the page if open
|
|
98
|
-
*/
|
|
99
|
-
private closePage;
|
|
100
88
|
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { log } from "../utils/logger.js";
|
|
18
18
|
import { randomDelay } from "../utils/stealth-utils.js";
|
|
19
|
+
import { StudioManagerBase, } from "./studio-manager-base.js";
|
|
19
20
|
/**
|
|
20
21
|
* Allowlisted style values. Single source of truth used to validate untrusted
|
|
21
22
|
* input before it is passed into the page context (prevents attribute-selector
|
|
@@ -39,29 +40,8 @@ const VALID_VIDEO_STYLES = [
|
|
|
39
40
|
* injection in selectFormat).
|
|
40
41
|
*/
|
|
41
42
|
const VALID_VIDEO_FORMATS = ["explainer", "brief"];
|
|
42
|
-
export class VideoManager {
|
|
43
|
-
|
|
44
|
-
contextManager;
|
|
45
|
-
page = null;
|
|
46
|
-
constructor(authManager, contextManager) {
|
|
47
|
-
this.authManager = authManager;
|
|
48
|
-
this.contextManager = contextManager;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Navigate to a notebook and ensure we're on the right page
|
|
52
|
-
*/
|
|
53
|
-
async navigateToNotebook(notebookUrl) {
|
|
54
|
-
const context = await this.contextManager.getOrCreateContext();
|
|
55
|
-
const isAuth = await this.authManager.validateWithRetry(context);
|
|
56
|
-
if (!isAuth) {
|
|
57
|
-
throw new Error("Not authenticated. Run setup_auth first.");
|
|
58
|
-
}
|
|
59
|
-
this.page = await context.newPage();
|
|
60
|
-
await this.page.goto(notebookUrl, { waitUntil: "domcontentloaded" });
|
|
61
|
-
await this.page.waitForLoadState("networkidle").catch(() => { });
|
|
62
|
-
await randomDelay(2000, 3000);
|
|
63
|
-
return this.page;
|
|
64
|
-
}
|
|
43
|
+
export class VideoManager extends StudioManagerBase {
|
|
44
|
+
logName = "video-manager";
|
|
65
45
|
/**
|
|
66
46
|
* Ensure the Studio panel is visible (expand if collapsed).
|
|
67
47
|
*
|
|
@@ -449,19 +429,4 @@ export class VideoManager {
|
|
|
449
429
|
await this.closePage();
|
|
450
430
|
}
|
|
451
431
|
}
|
|
452
|
-
/**
|
|
453
|
-
* Close the page if open
|
|
454
|
-
*/
|
|
455
|
-
async closePage() {
|
|
456
|
-
if (this.page) {
|
|
457
|
-
try {
|
|
458
|
-
await this.page.close();
|
|
459
|
-
}
|
|
460
|
-
catch (err) {
|
|
461
|
-
log.debug(`video-manager: closing page: ${err instanceof Error ? err.message : String(err)}`);
|
|
462
|
-
// Ignore close errors
|
|
463
|
-
}
|
|
464
|
-
this.page = null;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
432
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pan-sec/notebooklm-mcp",
|
|
3
|
-
"version": "2026.4.
|
|
3
|
+
"version": "2026.4.1",
|
|
4
4
|
"mcpName": "io.github.Pantheon-Security/notebooklm-mcp-secure",
|
|
5
5
|
"description": "Security-hardened MCP server for NotebookLM API with compliance-ready architecture (GDPR, SOC2, CSSF controls implemented)",
|
|
6
6
|
"type": "module",
|