/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
$ npx -y skills add MarcosNahuel/antigravity-plugin-cc --agent claude-codeHow 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.mddescription: 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
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)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.
Repo: MarcosNahuel/antigravity-plugin-cc
Other commands on marcosnahuel-antigravity-plugin-cc.
- /ask
One-shot prompt to Antigravity (agy) — quick question, returns response verbatim. No file persistence to docs/.
Open command - /deep-research
Deep, multi-source, fact-checked web research with agy — reach for it when a decision or design depends on getting it right and a single-shot answer is not enough (architecture / tool / vendor choices, thorough landscape scans, anything you will act on). Builds an evidence
Open command - /design-review
Run a UX/visual design audit of a URL using Antigravity (agy). Captures desktop + mobile screenshots, scores 10 dimensions (hierarchy, typography, color, spacing, a11y, etc.), benchmarks against industry. Saves to docs/agy/design-reviews/.
Open command - /doc-to-md
Convert a PDF, docx, image, or other document to clean Markdown using Antigravity (agy) multimodal Gemini. Saves to docs/agy/converted/.
Open command - /graph
Build a knowledge GRAPH of a folder (code + docs) with Graphify — tree-sitter ASTs + NetworkX + Leiden communities + an interactive graph.html. The code graph is built LOCALLY and costs zero tokens on any assistant; Gemini via agy only names the communities. Then Claude reads
Open command - /media
Ask a question about an AUDIO, VIDEO or IMAGE file (or a YouTube/remote URL) with Antigravity (agy / Gemini 3.x multimodal). Beyond transcription — "what decisions were made in this meeting?", "what happens at 2:30 in the video?", "what's the tone of this voice note?". Claude
Open command

