Skip to content
Development
Command

/notebook

Local NotebookLM over a FOLDER of documents using Antigravity (agy). Sweeps each document (PDF with text, scanned PDF, image, docx) into an objective-driven Markdown summary, then builds a relevance INDEX and a cited master summary. Incremental cache (re-runs only re-summarize

From plugin
marcosnahuel-antigravity-plugin-cc
2622 skills1 agent22 commands
Install
$ npx -y skills add MarcosNahuel/antigravity-plugin-cc --agent claude-code

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/notebook

Context preview

What this command does when you run it.

Local NotebookLM over a FOLDER of documents using Antigravity (agy). Sweeps each document (PDF with text, scanned PDF, image, docx) into an objective-driven Markdown summary, then builds a relevance INDEX and a cited master summary. Incremental cache (re-runs only re-summarize

Command definition

notebook.md
description: Local NotebookLM over a FOLDER of documents using Antigravity (agy). Sweeps each document (PDF with text, scanned PDF, image, docx) into an objective-driven Markdown summary, then builds a relevance INDEX and a cited master summary. Incremental cache (re-runs only re-summarize changed docs / changed objective) and automatic model routing (Flash for the sweep, Pro for the synthesis). Offloads all heavy reading to agy. Saves to docs/agy/notebook/.
argument-hint: "<folder> | <objective> [--semantic] [--background]"
context: fork
allowed-tools: Bash, Read, Write, Agent

Local replacement for NotebookLM. Given a **folder of documents** and an **objective**, agy reads every document and produces one objective-driven summary per document, plus a relevance `INDEX.md` and a cited `RESUMEN_MAESTRO.md`. The point is to **keep Claude's context cheap**: agy does all the document reading; you only read the two small final files.

Raw user request: $ARGUMENTS

Phase 0 — Parse + list + classify + cache (ONE Bash call)

Parse `$ARGUMENTS`: split on the first `|`. Left side = folder, right side = objective. If there is no `|`, the longest leading token that resolves to an existing directory is the folder and the rest is the objective. If the folder is missing, ask once: "¿Qué carpeta querés analizar?" and stop.

Run ONE Bash call (a Python helper). It lists supported files, classifies each as `text` (PDF with a real text layer → pre-extract) or `vision` (scanned/image → agy OCR), and applies an **incremental cache**: a document is marked `cached` (skipped) when its summary already exists AND its size+mtime AND the objective are unchanged since the last run. The cache key includes a hash of the objective, so changing the objective re-summarizes everything.

python - "$FOLDER_ABS" "$OUTDIR" "$OBJETIVO" <<'PYEOF'
import sys, os, re, glob, hashlib
import fitz  # PyMuPDF
folder, outdir, objetivo = sys.argv[1], sys.argv[2], (sys.argv[3] if len(sys.argv) > 3 else "")
os.makedirs(os.path.join(outdir, "_text"), exist_ok=True)
objhash = hashlib.sha1(objetivo.strip().encode("utf-8")).hexdigest()[:8]
MAXV, CHUNK, GROUP_MAX, CHAR_BUDGET = 20, 15, 4, 24000   # scans >MAXV pages -> CHUNK-page subs; uncached text docs -> groups of <=GROUP_MAX docs and <=CHAR_BUDGET chars (1 agy call -> many summaries)
cache_path = os.path.join(outdir, "_cache.tsv"); prev = {}
if os.path.exists(cache_path):
    for ln in open(cache_path, encoding="utf-8"):
        pp = ln.rstrip("\n").split("\t")
        if len(pp) == 2: prev[pp[0]] = pp[1]
exts = (".pdf",".docx",".doc",".png",".jpg",".jpeg",".webp",".gif")
files = sorted(f for f in glob.glob(os.path.join(folder,"*")) if f.lower().endswith(exts))
def slug(s):
    s = re.sub(r"[^a-z0-9]+","-", os.path.splitext(os.path.basename(s))[0].lower()).strip("-")
    return s[:60] or "doc"
def mkrow(nn, mode, src, tpath, summ, key):     # incremental cache per output file
    if os.path.exists(os.path.join(outdir, summ)) and prev.get(summ) == key:
        return (nn, "cached", src, "-", summ, key)
    return (nn, mode, src, tpath, summ, key)
rows=[]; small=[]   # small = UNCACHED text docs to pack into groups: (nn, sl, tpath, srcabs, key, nchars)
for i,f in enumerate(files,1):
    nn=f"{i:03d}"; sl=slug(f); st=os.stat(f); key=f"{st.st_size}:{int(st.st_mtime)}:{objhash}"
    is_pdf=f.lower().endswith(".pdf"); mode="vision"; tpath="-"; pages=0; d=None; nchars=0
    if is_pdf:
        try:
            d=fitz.open(f); pages=d.page_count; txt="\n".join(p.get_text() for p in d)
            if pages and len(txt.strip())/pages >= 200:
                mode="text"; nchars=len(txt.strip()); tpath=os.path.join(outdir,"_text",f"{nn}-{sl}.txt")
                open(tpath,"w",encoding="utf-8").write(txt)
        except Exception:
            mode,pages,d="vision",0,None
    if mode=="vision" and is_pdf and pages>MAXV and d is not None:
        os.makedirs(os.path.join(outdir,"_chunks"),exist_ok=True)   # oversized scan -> page-range chunks
        for ci,startp in enumerate(range(0,pages,CHUNK),1):
            endp=min(startp+CHUNK,pages)
            cpath=os.path.join(outdir,"_chunks",f"{nn}-{sl}-p{startp+1:03d}-{endp:03d}.pdf")
            sub=fitz.open(); sub.insert_pdf(d,from_page=startp,to_page=endp-1); sub.save(cpath); sub.close()
            summ=f"{nn}-{sl}-p{startp+1:03d}-{endp:03d}.resumen.md"
            rows.append(mkrow(nn,"vision",cpath,"-",summ,f"{key}:c{ci}"))
    elif mode=="text":
        summ=f"{nn}-{sl}.resumen.md"
        if os.path.exists(os.path.join(outdir,summ)) and prev.get(summ)==key:
            rows.append((nn,"cached",os.path.abspath(f),tpath,summ,key))     # already summarized -> skip
        else:
            small.append((nn, sl, tpath, os.path.abspath(f), key, nchars))   # pack into a group below
    else:
        rows.append(mkrow(nn,mode,os.path.abspath(f),tpath,f"{nn}-{sl}.resumen.md",key))
    if d is not None: d.close()
# greedy-pack UNCACHED text docs into groups (<=GROUP_MAX docs, <=CHAR_BUDGET chars) to cut agy calls.
# Each group = ONE agy call that writes one summary file PER member (see Mode: notebook-group).
batches=[]; cur=[]; cc=0
for it in small:                                    # it = (nn, sl, tpath, srcabs, key, nchars)
    if cur and (len(cur)>=GROUP_MAX or cc+it[5]>CHAR_BUDGET):
        batches.append(cur); cur=[]; cc=0
    cur.append(it); cc+=it[5]
if cur: batches.append(cur)
for gi,b in enumerate(batches,1):
    if len(b)==1:                                   # lone text doc -> 1-per-call (no group overhead)
        nn,sl,tpath,srcabs,key,_=b[0]
        rows.append((nn,"text",srcabs,tpath,f"{nn}-{sl}.resumen.md",key)); continue
    g=f"G{gi:02d}"
    texts="|".join(x[2] for x in b)                 # member text paths
    names="|".join(f"{x[0]}-{x[1]}" for x in b)     # member display names
    summs="|".join(f"{x[0]}-{x[1]}.resumen.md" for x in b)  # one output file PER member
    gkey="|".join(x[4] for x in b)
Read more
Ships withmarcosnahuel-antigravity-plugin-cc

A local NotebookLM, multi-agent deep research, and 21 more commands — for Claude Code, powered by Google Antigravity (agy / Gemini 3.x), the official CLI that replaces the now-deprecated gemini-cli.

Get the whole plugin, auto-invoked
Stats
26
Stars
0
Views
6
Forks
Active
Maintenance
Python
Language
MIT
License
7d ago
Last commit
2mo ago
Created

Repo: MarcosNahuel/antigravity-plugin-cc