Add open-notebook skill: self-hosted NotebookLM alternative (issue #56)

Implements the open-notebook skill as a comprehensive integration for the
open-source, self-hosted alternative to Google NotebookLM. Addresses the
gap created by Google not providing a public NotebookLM API.

Developed using TDD with 44 tests covering skill structure, SKILL.md
frontmatter/content, reference documentation, example scripts, API
endpoint coverage, and marketplace.json registration.

Includes:
- SKILL.md with full documentation, code examples, and provider matrix
- references/api_reference.md covering all 20+ REST API endpoint groups
- references/examples.md with complete research workflow examples
- references/configuration.md with Docker, env vars, and security setup
- references/architecture.md with system design and data flow diagrams
- scripts/ with 3 example scripts (notebook, source, chat) + test suite
- marketplace.json updated to register the new skill

Closes #56

https://claude.ai/code/session_015CqcNWNYmDF9sqxKxziXcz
This commit is contained in:
Claude
2026-02-23 00:18:19 +00:00
parent f7585b7624
commit 259e01f7fd
10 changed files with 2599 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
"""
Open Notebook - Notebook Management Example
Demonstrates creating, listing, updating, and deleting notebooks
using the Open Notebook REST API.
Prerequisites:
pip install requests
Usage:
export OPEN_NOTEBOOK_URL="http://localhost:5055"
python notebook_management.py
"""
import os
import requests
BASE_URL = os.getenv("OPEN_NOTEBOOK_URL", "http://localhost:5055") + "/api"
def create_notebook(name, description=""):
"""Create a new notebook."""
response = requests.post(f"{BASE_URL}/notebooks", json={
"name": name,
"description": description,
})
response.raise_for_status()
notebook = response.json()
print(f"Created notebook: {notebook['id']} - {notebook['name']}")
return notebook
def list_notebooks(archived=False):
"""List all notebooks, optionally filtering by archived status."""
response = requests.get(f"{BASE_URL}/notebooks", params={
"archived": archived,
})
response.raise_for_status()
notebooks = response.json()
print(f"Found {len(notebooks)} notebook(s):")
for nb in notebooks:
print(f" - {nb['id']}: {nb['name']} "
f"(sources: {nb.get('source_count', 0)}, "
f"notes: {nb.get('note_count', 0)})")
return notebooks
def get_notebook(notebook_id):
"""Retrieve a single notebook by ID."""
response = requests.get(f"{BASE_URL}/notebooks/{notebook_id}")
response.raise_for_status()
return response.json()
def update_notebook(notebook_id, name=None, description=None, archived=None):
"""Update notebook fields."""
payload = {}
if name is not None:
payload["name"] = name
if description is not None:
payload["description"] = description
if archived is not None:
payload["archived"] = archived
response = requests.put(
f"{BASE_URL}/notebooks/{notebook_id}", json=payload
)
response.raise_for_status()
updated = response.json()
print(f"Updated notebook: {updated['id']} - {updated['name']}")
return updated
def delete_notebook(notebook_id, delete_sources=False):
"""Delete a notebook and optionally its exclusive sources."""
# Preview what will be deleted
preview = requests.get(
f"{BASE_URL}/notebooks/{notebook_id}/delete-preview"
).json()
print(f"Deletion will affect {preview.get('note_count', 0)} notes "
f"and {preview.get('source_count', 0)} sources")
response = requests.delete(
f"{BASE_URL}/notebooks/{notebook_id}",
params={"delete_sources": delete_sources},
)
response.raise_for_status()
print(f"Deleted notebook: {notebook_id}")
def link_source_to_notebook(notebook_id, source_id):
"""Associate an existing source with a notebook."""
response = requests.post(
f"{BASE_URL}/notebooks/{notebook_id}/sources/{source_id}"
)
response.raise_for_status()
print(f"Linked source {source_id} to notebook {notebook_id}")
def unlink_source_from_notebook(notebook_id, source_id):
"""Remove the association between a source and a notebook."""
response = requests.delete(
f"{BASE_URL}/notebooks/{notebook_id}/sources/{source_id}"
)
response.raise_for_status()
print(f"Unlinked source {source_id} from notebook {notebook_id}")
if __name__ == "__main__":
# Demo workflow
print("=== Notebook Management Demo ===\n")
# Create notebooks
nb1 = create_notebook(
"Protein Folding Research",
"Literature review on AlphaFold and related methods"
)
nb2 = create_notebook(
"CRISPR Gene Editing",
"Survey of CRISPR-Cas9 applications in therapeutics"
)
# List all notebooks
print()
list_notebooks()
# Update a notebook
print()
update_notebook(nb1["id"], description="Updated: Including ESMFold comparisons")
# Archive a notebook
print()
update_notebook(nb2["id"], archived=True)
print("\nActive notebooks:")
list_notebooks(archived=False)
print("\nArchived notebooks:")
list_notebooks(archived=True)
# Clean up
print()
delete_notebook(nb1["id"])
delete_notebook(nb2["id"])