cli

The fossick shell command

Installing fossick adds a fossick command. A shell script, or an agent with only a terminal, can use the library through it. The command takes a command name and then that command’s options:

fossick search "sqlite wal mode" --n 5
fossick fetch https://example.com --auto
fossick read-arxiv 1706.03762 --source
fossick research "what is the raft consensus algorithm" --n 3

Most commands wrap the Python function of the same name. Their options are that function’s arguments, with underscores written as hyphens: max_pages becomes --max-pages. A command offers only some of the function’s arguments. Output is readable markdown by default. --as-json prints JSON instead, for piping into other programs. paginate-api always prints JSON. fossick <command> --help lists a command’s options. fossick alone lists the commands.

Two defaults differ from Python. fossick search reranks with BM25 (--rerank lexical), and search in Python keeps the merged order. fossick search uses --region us-en, and search in Python uses region='auto'.


source

fetch

def fetch(
    url:str, # URL to fetch
    sel:str=None, # CSS selector to extract (None = full page)
    heavy:bool=False, # JS rendering via headless browser
    stealthy:bool=False, # Anti-bot stealth fetcher
    session:bool=False, # Route through the persistent debug Chrome (reuses its logged-in cookies)
    auto:bool=False, # Auto-escalate plain->heavy->stealthy->session on bot-block detection
    as_json:bool=False, # Output JSON instead of markdown
):

Fetch a URL and print it as markdown.


source

crawl

def crawl(
    url:str, # start URL
    sel:str=None, # CSS selector to extract per page
    follow_sel:str='a[href]', # CSS selector for links to follow
    max_pages:int=10, # max pages to visit
    same_domain:bool=True, # only follow same-domain links
    heavy:bool=False, # JS rendering via headless browser
    stealthy:bool=False, # anti-bot stealth fetcher
    as_json:bool=False, # output JSON
):

Crawl from a URL, following links, and print each page as markdown.


source


source

read_arxiv

def read_arxiv(
    url:str, # arXiv URL or paper ID
    source:bool=False, # include full paper text
    chars:int=4000, # max source chars to include
    force:bool=False, # re-download even if cached
    as_json:bool=False, # output JSON
):

Print an arXiv paper’s title, authors, date and summary, and optionally its full text.


source

lookup_doi

def lookup_doi(
    title:str, # paper title to look up
    as_json:bool=False, # output JSON
):

Print the doi.org URL of the first Crossref match for a paper title.


source

read_yt

def read_yt(
    url:str, # YouTube URL or video ID
    force:bool=False, # re-fetch even if cached
    as_json:bool=False, # output JSON
):

Print a YouTube video’s title, channel, duration and transcript.


source

search_yt

def search_yt(
    query:str, # search query
    n:int=10, # number of results
    as_json:bool=False, # output JSON
):

Search YouTube and print each video’s title, URL, channel and duration.


source

read_gh_file

def read_gh_file(
    url:str, # GitHub blob URL of the file
    as_json:bool=False, # output JSON
):

Print the contents of one file on GitHub.


source

read_gh_repo

def read_gh_repo(
    url:str, # GitHub repo URL, SSH address, or local path
    globs:str=None, # comma-sep glob patterns (default: README*,pyproject.toml,*.py)
    limit:int=None, # max files to return
    as_json:bool=False, # output JSON
):

Print the files of a GitHub repository that match the glob patterns.


source

calls

def calls(
    url:str, # URL to navigate to
    pattern:str='.*', # regex/glob to filter request URLs
    tail:int=3, # seconds to wait after navigation
    as_json:bool=False, # output JSON
):

Open a page in the debug Chrome and print the network requests it sends.


source

find_xhr

def find_xhr(
    url:str, # URL to visit with a headless browser
    pattern:str='*', # glob/regex to filter captured XHR URLs
    as_json:bool=False, # output JSON
):

Load a page in a headless browser and print the JSON and XHR requests it makes.


source

paginate_api

def paginate_api(
    url:str, # API endpoint URL
    payload:str=None, # request body/params as a JSON string
    page_field:str='pageNumber', # payload key to increment per page
    results_field:str=None, # response key with the items list (auto-detect if None)
    method:str='POST', # HTTP method
    max_pages:int=10, # max pages to fetch
):

Read every page of a JSON API and print all the items as JSON.


source

collect

def collect(
    url:str, # URL to open in Chrome
    save_dir:str='.', # directory to save screenshots
    tout:int=None, # stop after this many seconds
    count:int=None, # stop after this many screenshots
    every_n:int=None, # auto-capture every N seconds
):

Open a URL in the debug Chrome and save a screenshot each time you press the capture button.


source

annotate

def annotate(
    url:str, # URL to open in Chrome
    save_dir:str='.', # directory to save annotated screenshot
    as_json:bool=False, # output JSON list of selected elements
):

Open a URL in the debug Chrome, let you click elements to number them, and save a labelled screenshot.


source

install

def install():

Copy fossick’s SKILL.md into .agents/skills/fossick/ and .claude/skills/fossick/.


source

url2nb

def url2nb(
    url:str, # URL to convert (HTML page, PDF, or arXiv)
    path:str=None, # output notebook path (default: derived from URL)
    as_json:bool=False, # output JSON with notebook path
):

Save a web page, PDF or arXiv paper as a Jupyter notebook and print its path.


source

pdf2nb

def pdf2nb(
    url:str, # PDF URL or local path
    path:str=None, # output notebook path (default: derived from PDF)
    ocr:str='auto', # OCR mode: auto | on | off
    force:bool=False, # re-convert even if notebook exists
    as_json:bool=False, # output JSON with notebook path
):

Save a PDF, from a URL or a local path, as a Jupyter notebook and print its path.


source

download_yt

def download_yt(
    url:str, # YouTube URL or video ID
    format:str='audio', # audio | video | yt-dlp format string
    save_dir:str='.', # directory to save to
    as_json:bool=False, # output JSON with saved path
):

Download a YouTube video’s audio or video and print the saved path.


source

videos

def videos(
    query:str, n:int=20, region:str='us-en', as_json:bool=False
):

Search YouTube and print each title and video URL.


source

news

def news(
    query:str, n:int=20, region:str='us-en', as_json:bool=False
):

Search news and print each date, title and URL.


source

images

def images(
    query:str, n:int=20, region:str='us-en', as_json:bool=False
):

Search for images and print each title and image URL.


source

ax

def ax(
    url:str, # URL to open in the debug Chrome
    port:int=9223, # debug Chrome remote-debugging port
    full:bool=False, # show the full accessibility tree instead of interactive-only
):

Open a URL in the debug Chrome and print a short snapshot of the controls on the page.


source

research

def research(
    query:str, # search query
    n:int=5, # number of top results to read
    google:bool=False, # use Google's own ranking through the stealth browser instead of ddgs metasearch
    region:str='auto', # ddgs 'country-lang' region, or 'auto' to read the country off the query
    timelimit:str=None, # d | w | m | y, only sources from the last day, week, month or year
    sel:str=None, # CSS selector to narrow each page before markdown
    chars:int=4000, # max markdown chars per source
    pages:int=1, # result pages to pull from each backend when searching
    as_json:bool=False, # output JSON
):

Search, read the top results, and print them as one markdown digest that cites each source.


source

shop

def shop(
    url:str, # store URL; the command reuses the tab the last call left open on that site
    search:str=None, # search the store for this term and list the products found
    add:str=None, # add a product by index (from a previous --search) or by title
    qty:int=1, # quantity to add
    variant:str=None, # variant/size to pick, for products that need one
    cart:bool=False, # print the cart and its lines
    fields:bool=False, # print the form fields on this page
    port:int=9223, # debug Chrome remote-debugging port
    as_json:bool=False, # output JSON
):

Search a store, add a product, or read the cart in the debug Chrome, keeping the page open between calls.

The command table

CMDS maps each command name to its function. main is the entry point of the fossick script. It removes the command name from the command line, parses the remaining options against that function’s signature, and runs the function. An unknown command prints the list of commands and exits with status 1. When you supply no command, main prints the command list and exits with status 0.


source

main

def main():

Run the fossick command named on the command line.

Tests

The first cell checks that every command is registered and that the options agents rely on exist. The second runs research against live services and is skipped by the tests.

# end-to-end: the research CLI runs against live services and prints a cited corpus
import io
from contextlib import redirect_stdout
buf = io.StringIO()
with redirect_stdout(buf): research.__wrapped__('what is the raft consensus algorithm', n=2)
out = buf.getvalue()
assert out.startswith('# Research:') and 'http' in out
print(out[:300])
from pathlib import Path
# The CLI's `search`, `fetch`, `crawl` and `research` share names with the library's, and nbdev's doc links resolve a
# bare name to the CLI page first, so index.ipynb links them by hand; the README must not send readers to cli.html.
assert 'cli.html#' not in Path('../README.md').read_text()