perplexity-notebooklm 0.2.2__tar.gz → 0.3.0__tar.gz

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 (53) hide show
  1. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/.claude-plugin/marketplace.json +1 -1
  2. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/.claude-plugin/plugin.json +1 -1
  3. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/CHANGELOG.md +14 -0
  4. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/PKG-INFO +1 -1
  5. perplexity_notebooklm-0.3.0/docs/superpowers/specs/2026-06-27-dual-mcp-tools-design.md +69 -0
  6. perplexity_notebooklm-0.3.0/perplexity_mcp/nlm_tools.py +138 -0
  7. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/server.py +11 -0
  8. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/pyproject.toml +1 -1
  9. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/skills/perplexity-notebooklm/SKILL.md +7 -3
  10. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/.github/workflows/publish.yml +0 -0
  11. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/.gitignore +0 -0
  12. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/CLAUDE.md +0 -0
  13. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/LICENSE +0 -0
  14. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/README.md +0 -0
  15. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/README.pt-BR.md +0 -0
  16. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/auth/.env.example +0 -0
  17. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/auth/extract_cookies.md +0 -0
  18. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/commands/dual-research.md +0 -0
  19. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/commands/notebook-enrich.md +0 -0
  20. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/docs/BENCHMARK.md +0 -0
  21. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/docs/INNOVATION.md +0 -0
  22. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/docs/INSTALL.md +0 -0
  23. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/docs/REFINAMENTO_github_study.md +0 -0
  24. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/docs/assets/banner.svg +0 -0
  25. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/docs/benchmark_perplexity_mcp_repos.md +0 -0
  26. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/dual_flow.py +0 -0
  27. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/evals/run.py +0 -0
  28. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/evals/scenarios.jsonl +0 -0
  29. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/evals/score.py +0 -0
  30. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/hooks/hooks.json +0 -0
  31. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/hooks/sessionstart.py +0 -0
  32. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/notebooklm_write/__init__.py +0 -0
  33. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/notebooklm_write/add_source.py +0 -0
  34. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/notebooklm_write/browser.py +0 -0
  35. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/notebooklm_write/embed.py +0 -0
  36. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/notebooklm_write/ledger.py +0 -0
  37. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/notebooklm_write/nlm_http.py +0 -0
  38. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/__init__.py +0 -0
  39. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/adapter.py +0 -0
  40. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/agentic.py +0 -0
  41. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/auth_login.py +0 -0
  42. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/backends/__init__.py +0 -0
  43. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/backends/helallao.py +0 -0
  44. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/cited_search.py +0 -0
  45. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/doctor.py +0 -0
  46. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/resilience.py +0 -0
  47. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/security.py +0 -0
  48. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/validate.py +0 -0
  49. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/perplexity_mcp/verify_citations.py +0 -0
  50. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/rag.py +0 -0
  51. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/scripts/confirm_sources_pwm.py +0 -0
  52. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/uv.lock +0 -0
  53. {perplexity_notebooklm-0.2.2 → perplexity_notebooklm-0.3.0}/watch.py +0 -0
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "perplexity-notebooklm",
10
10
  "description": "Integração dual Perplexity <-> NotebookLM (conta Pro, sem API key)",
11
- "version": "0.2.2",
11
+ "version": "0.3.0",
12
12
  "source": "./",
13
13
  "author": {
14
14
  "name": "wgardim"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perplexity-notebooklm",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Integração dual Perplexity <-> NotebookLM (conta Pro, sem API key): pesquisa citada vira fonte no notebook e vice-versa.",
5
5
  "author": {
6
6
  "name": "wgardim"
@@ -2,6 +2,20 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com). Versionamento semântico.
4
4
 
5
+ ## [0.3.0] — 2026-06-27
6
+
7
+ ### Adicionado
8
+ - **Fluxo dual como tools MCP first-class** (`perplexity_mcp/nlm_tools.py`): o server
9
+ `perplexity-pro` agora expõe 9 tools além das do Perplexity —
10
+ `notebooklm_list`, `notebooklm_read_summary`, `notebooklm_add_text/add_url/add_file`,
11
+ `notebooklm_generate`, `dual_research_to_notebook`, `dual_notebook_to_research`,
12
+ `dual_research_to_media`. O Claude chama tudo direto via MCP (não mais skill rodando Python).
13
+ - **Annotations de segurança**: leitura = `readOnlyHint`; escrita = `destructiveHint`
14
+ (checkpoint humano = permission prompt do Claude Code).
15
+ - **Registro condicional/gracioso**: as tools só sobem se o extra `notebooklm` estiver
16
+ instalado (`find_spec`); install Perplexity-only fica intacto. Erro real de registro
17
+ loga no stderr (não polui o protocolo stdio).
18
+
5
19
  ## [0.2.2] — 2026-06-27
6
20
 
7
21
  ### CI
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: perplexity-notebooklm
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Integração dual NotebookLM <-> Perplexity (conta Pro, sem API key) via MCP + skill
5
5
  Project-URL: Homepage, https://github.com/wgardim-hub/notebooklm2perplexity
6
6
  Project-URL: Repository, https://github.com/wgardim-hub/notebooklm2perplexity
@@ -0,0 +1,69 @@
1
+ # Spec: Fluxo dual como tools MCP first-class
2
+
3
+ ## Contexto
4
+ Hoje o MCP `perplexity-pro` expõe só Perplexity (ask/search/reason/research/healthcheck).
5
+ NotebookLM + o fluxo dual existem em `notebooklm_write/nlm_http.py` e `dual_flow.py`, mas
6
+ são acessados pela **skill rodando Python via venv** — não como tools MCP. Resultado: o
7
+ Claude não chama add_source/list/dual diretamente pela interface MCP. Esta feature expõe
8
+ tudo como tools, uniformizando o produto pela camada MCP.
9
+
10
+ ## Objetivo
11
+ Adicionar NotebookLM (primitivas) + dual como tools no **mesmo** server `perplexity-pro`,
12
+ com registro condicional e annotations de segurança.
13
+
14
+ ## Não-objetivos
15
+ - Não muda os helpers (`nlm_http`/`dual_flow`) — as tools são adaptadores finos.
16
+ - Não remove o caminho da skill (continua válido); só passa a preferir as tools MCP.
17
+ - Sem novas capacidades de NotebookLM além das já implementadas.
18
+
19
+ ## Arquitetura
20
+ - **Novo `perplexity_mcp/nlm_tools.py`** com `register(mcp)`: define e registra as tools.
21
+ Imports de `nlm_http`/`dual_flow` são lazy (dentro das funções) → import do módulo não
22
+ quebra em install Perplexity-only.
23
+ - **`server.py`**: em `main()`/`_register()`, chamar `nlm_tools.register(mcp)` dentro de
24
+ `try/except Exception` — se `notebooklm-py` ausente, loga e segue (Perplexity-only intacto).
25
+ - **Annotations**: usar `ToolAnnotations`/`annotations` do FastMCP — `readOnlyHint=True` nas
26
+ de leitura; escrita marcada como destrutiva (`readOnlyHint=False`). Se a versão do FastMCP
27
+ instalada não suportar annotations, fallback: descrição explícita ("ESCREVE no notebook")
28
+ + permission prompt do Claude Code (checkpoint humano default).
29
+
30
+ ## Tools (retornam string/markdown human-readable)
31
+ Leitura (readOnly):
32
+ - `notebooklm_list()` — notebooks (id + título), via `nlm_http.list_notebooks_sync`.
33
+ - `notebooklm_read_summary(notebook_id)` — resumo, via `read_summary_sync`.
34
+ Escrita (destrutiva — checkpoint via permission prompt):
35
+ - `notebooklm_add_text(notebook_id, title, content)` — `add_text_sync`.
36
+ - `notebooklm_add_url(notebook_id, url)` — `add_url_sync` (web/YouTube auto).
37
+ - `notebooklm_add_file(notebook_id, file_path)` — `add_file_sync`.
38
+ - `notebooklm_generate(notebook_id, kind)` — `generate_artifact_sync` (audio|video|mind_map).
39
+ Dual:
40
+ - `dual_research_to_notebook(query, notebook_id, tool="research", verify=True, dedup=True)` (escrita).
41
+ - `dual_notebook_to_research(notebook_id, tool="search")` (leitura+pesquisa; não escreve).
42
+ - `dual_research_to_media(query, notebook_id, kind="mind_map", tool="search")` (escrita).
43
+
44
+ ## Fluxo de dados
45
+ Claude → tool MCP → helper sync (`nlm_http`/`dual_flow`) → cited_search/notebooklm-py →
46
+ resultado. Cada tool formata o resultado (`.ok`/`.detail`/source_id/answer) em string.
47
+
48
+ ## Erros
49
+ Helpers já retornam objetos de resultado (não levantam em falha esperada). As tools
50
+ formatam: Chrome fechado/cookie stale → mensagem clara ("abra Chrome :9222 logado").
51
+ Exceções inesperadas → capturadas e retornadas como texto de erro (sem stacktrace cru).
52
+
53
+ ## Testes
54
+ - Unit (offline): `register(mcp)` adiciona as 9 tools (`mcp.list_tools()`); sem notebooklm-py
55
+ → `register` é no-op gracioso; annotations de leitura/escrita corretas.
56
+ - Live (Chrome :9222): `notebooklm_list` retorna notebooks; `dual_notebook_to_research` retorna
57
+ análise. (Camada fina; helpers já validados e2e em sessões anteriores.)
58
+
59
+ ## Arquivos
60
+ - Criar: `perplexity_mcp/nlm_tools.py`.
61
+ - Modificar: `perplexity_mcp/server.py` (registro condicional).
62
+ - Docs: `skills/perplexity-notebooklm/SKILL.md` (usar `mcp__perplexity-pro__*` direto),
63
+ `README*.md`, `CHANGELOG.md`; bump `0.3.0` (pyproject + plugin + marketplace).
64
+
65
+ ## Verificação
66
+ - `mcp.list_tools()` lista Perplexity + 9 novas tools.
67
+ - Install Perplexity-only (sem extra notebooklm): server sobe, tools NotebookLM ausentes, sem erro.
68
+ - 1 tool de leitura e 1 dual funcionam live com Chrome :9222.
69
+ - Harness `evals/run.py` segue verde.
@@ -0,0 +1,138 @@
1
+ """Tools MCP de NotebookLM + fluxo dual (registradas no server `perplexity-pro`).
2
+
3
+ Adaptadores FINOS sobre os helpers já validados (`notebooklm_write.nlm_http` e
4
+ `dual_flow`). `register(mcp)` é chamado pelo server dentro de try/except — se o extra
5
+ `notebooklm` (notebooklm-py) não estiver instalado, o import falha e o server segue
6
+ só com as tools do Perplexity (degradação graciosa).
7
+
8
+ Annotations: leitura = readOnlyHint; escrita = destructiveHint (o cliente trata as de
9
+ escrita com cautela; o checkpoint humano é o permission prompt do Claude Code).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from mcp.types import ToolAnnotations
15
+
16
+ _RO = ToolAnnotations(readOnlyHint=True, destructiveHint=False)
17
+ _WRITE = ToolAnnotations(readOnlyHint=False, destructiveHint=True)
18
+
19
+
20
+ def _err(exc: Exception) -> str:
21
+ return f"erro: {type(exc).__name__}: {exc}"
22
+
23
+
24
+ def register(mcp) -> None:
25
+ """Registra as 9 tools de NotebookLM/dual no server MCP. Import lazy dos helpers.
26
+
27
+ No-op se o extra `notebooklm` (notebooklm-py) não estiver instalado — Perplexity-only
28
+ não expõe tools que não conseguiriam rodar.
29
+ """
30
+ import importlib.util
31
+
32
+ if importlib.util.find_spec("notebooklm") is None:
33
+ return # extra notebooklm ausente → não registra as tools
34
+
35
+ # ---- NotebookLM: leitura ----
36
+ def notebooklm_list() -> str:
37
+ """Lista os notebooks do NotebookLM da conta (id — título). Requer Chrome :9222 logado."""
38
+ try:
39
+ from notebooklm_write.nlm_http import list_notebooks_sync
40
+
41
+ nbs = list_notebooks_sync()
42
+ return "\n".join(f"{nb_id} — {title}" for nb_id, title in nbs) or "(nenhum notebook)"
43
+ except Exception as exc:
44
+ return _err(exc)
45
+
46
+ def notebooklm_read_summary(notebook_id: str) -> str:
47
+ """Lê o resumo de um notebook (texto). Útil p/ enriquecer/fact-check no Perplexity."""
48
+ try:
49
+ from notebooklm_write.nlm_http import read_summary_sync
50
+
51
+ return read_summary_sync(notebook_id) or "(resumo vazio)"
52
+ except Exception as exc:
53
+ return _err(exc)
54
+
55
+ # ---- NotebookLM: escrita (ESCREVE na conta — não-reversível) ----
56
+ def notebooklm_add_text(notebook_id: str, title: str, content: str) -> str:
57
+ """ESCREVE: adiciona uma fonte de texto ao notebook."""
58
+ try:
59
+ from notebooklm_write.nlm_http import add_text_sync
60
+
61
+ r = add_text_sync(notebook_id, title, content)
62
+ return f"ok={r.ok} source_id={r.source_id} | {r.detail}"
63
+ except Exception as exc:
64
+ return _err(exc)
65
+
66
+ def notebooklm_add_url(notebook_id: str, url: str) -> str:
67
+ """ESCREVE: adiciona fonte por URL (web ou YouTube, auto-detectado)."""
68
+ try:
69
+ from notebooklm_write.nlm_http import add_url_sync
70
+
71
+ r = add_url_sync(notebook_id, url)
72
+ return f"ok={r.ok} source_id={r.source_id} | {r.detail}"
73
+ except Exception as exc:
74
+ return _err(exc)
75
+
76
+ def notebooklm_add_file(notebook_id: str, file_path: str) -> str:
77
+ """ESCREVE: adiciona fonte por arquivo local (PDF/DOCX/MD/...)."""
78
+ try:
79
+ from notebooklm_write.nlm_http import add_file_sync
80
+
81
+ r = add_file_sync(notebook_id, file_path)
82
+ return f"ok={r.ok} source_id={r.source_id} | {r.detail}"
83
+ except Exception as exc:
84
+ return _err(exc)
85
+
86
+ def notebooklm_generate(notebook_id: str, kind: str) -> str:
87
+ """ESCREVE: gera artefato no notebook. kind: audio | video | mind_map (áudio/vídeo gastam cota)."""
88
+ try:
89
+ from notebooklm_write.nlm_http import generate_artifact_sync
90
+
91
+ r = generate_artifact_sync(notebook_id, kind)
92
+ return f"ok={r.ok} kind={r.kind} | {r.detail}"
93
+ except Exception as exc:
94
+ return _err(exc)
95
+
96
+ # ---- Dual ----
97
+ def dual_research_to_notebook(
98
+ query: str, notebook_id: str, tool: str = "research", verify: bool = True, dedup: bool = True
99
+ ) -> str:
100
+ """ESCREVE: pesquisa no Perplexity e injeta como fonte (verify filtra citações, dedup evita repetir). tool: research|search|reason."""
101
+ try:
102
+ from dual_flow import research_to_notebook
103
+
104
+ r = research_to_notebook(query, notebook_id, tool=tool, verify=verify, dedup=dedup)
105
+ return f"source_added={r.source_added} | {r.detail}"
106
+ except Exception as exc:
107
+ return _err(exc)
108
+
109
+ def dual_notebook_to_research(notebook_id: str, tool: str = "search") -> str:
110
+ """Lê o resumo do notebook e enriquece/fact-check no Perplexity (NÃO escreve). tool: search|reason|research."""
111
+ try:
112
+ from dual_flow import notebook_to_research
113
+
114
+ r = notebook_to_research(notebook_id, tool=tool)
115
+ return r.answer or "(sem resposta)"
116
+ except Exception as exc:
117
+ return _err(exc)
118
+
119
+ def dual_research_to_media(
120
+ query: str, notebook_id: str, kind: str = "mind_map", tool: str = "search"
121
+ ) -> str:
122
+ """ESCREVE: pesquisa → fonte no notebook → gera artefato (kind: audio|video|mind_map)."""
123
+ try:
124
+ from dual_flow import research_to_media
125
+
126
+ r = research_to_media(query, notebook_id, kind=kind, tool=tool)
127
+ return f"source_added={r.source_added} artifact_ok={r.artifact_ok} | {r.detail}"
128
+ except Exception as exc:
129
+ return _err(exc)
130
+
131
+ # Registro com annotations (leitura vs escrita).
132
+ for fn in (notebooklm_list, notebooklm_read_summary, dual_notebook_to_research):
133
+ mcp.tool(annotations=_RO)(fn)
134
+ for fn in (
135
+ notebooklm_add_text, notebooklm_add_url, notebooklm_add_file, notebooklm_generate,
136
+ dual_research_to_notebook, dual_research_to_media,
137
+ ):
138
+ mcp.tool(annotations=_WRITE)(fn)
@@ -13,6 +13,7 @@ autenticada — espelha o comportamento do upstream.
13
13
  from __future__ import annotations
14
14
 
15
15
  import os
16
+ import sys
16
17
 
17
18
  from pathlib import Path
18
19
 
@@ -96,6 +97,16 @@ def _register() -> None:
96
97
  mcp.tool()(perplexity_reason)
97
98
  mcp.tool()(perplexity_research)
98
99
 
100
+ # NotebookLM + dual: só sobem se o extra `notebooklm` estiver instalado (gracioso).
101
+ # `register` já faz no-op se notebooklm-py ausente; aqui logamos qualquer erro REAL
102
+ # de registro no stderr (stdout é o protocolo MCP — não pode poluir) em vez de silenciar.
103
+ try:
104
+ from . import nlm_tools
105
+
106
+ nlm_tools.register(mcp)
107
+ except Exception as exc:
108
+ print(f"[perplexity-pro] aviso: tools NotebookLM/dual não registradas: {exc}", file=sys.stderr)
109
+
99
110
 
100
111
  def main() -> None:
101
112
  global BACKEND
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "perplexity-notebooklm"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "Integração dual NotebookLM <-> Perplexity (conta Pro, sem API key) via MCP + skill"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10,<3.14" # 3.14 quebra wheels do curl_cffi; testado em 3.12/3.13
@@ -5,9 +5,13 @@ description: Use quando o usuário quiser pesquisar/enriquecer/fact-check com Pe
5
5
 
6
6
  # Perplexity ↔ NotebookLM (fluxo dual)
7
7
 
8
- Integra dois lados, ambos sem API key paga:
9
- - **Perplexity (conta Pro)** via MCP `perplexity-pro` (registrado, Connected). Tools: `healthcheck`, `perplexity_ask`, `perplexity_search`, `perplexity_reason`, `perplexity_research`. Auth = cookies em `.env`.
10
- - **NotebookLM** via `notebooklm_write.nlm_http` — **HTTP/RPC** (notebooklm-py), sem dirigir UI. Capaz de `add_text` (escrita), `list_notebooks`, `read_summary` (leitura). Fallback: `add_source` (Selenium) se o HTTP falhar.
8
+ Integra dois lados, ambos sem API key paga. **Preferir as tools MCP** (v0.3.0+) — o Claude chama direto, sem rodar Python:
9
+ - **Perplexity**: `mcp__perplexity-pro__perplexity_ask|search|reason|research`, `healthcheck`.
10
+ - **NotebookLM**: `mcp__perplexity-pro__notebooklm_list|read_summary|add_text|add_url|add_file|generate`.
11
+ - **Dual**: `mcp__perplexity-pro__dual_research_to_notebook` (PPLX→NLM, verify+dedup), `dual_notebook_to_research` (NLM→PPLX), `dual_research_to_media` (research→fonte→artefato).
12
+ - Tools de escrita têm annotation destrutiva → o Claude Code pede aprovação (checkpoint humano). Os helpers Python (`dual_flow`, `nlm_http`) seguem válidos como fallback.
13
+
14
+ Detalhes do backend: Perplexity por cookies no `.env`; NotebookLM por cookie-bridge (HTTP/RPC, fallback Selenium).
11
15
 
12
16
  ### Auth NotebookLM = cookie-bridge (uma vez)
13
17
  O Google bloqueia login sob automação. Solução: abrir um Chrome real logado com porta de debug; o `nlm_http` extrai os cookies dele e gera o `storage_state` (depois as chamadas são HTTP puras, sem browser, até o cookie expirar).