surf-cli 0.7.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.
- surf_cli-0.7.0/.agents/skills/surf/SKILL.md +60 -0
- surf_cli-0.7.0/.agents/skills/surf/references/about.md +18 -0
- surf_cli-0.7.0/.agents/skills/surf/references/surf.pdf +0 -0
- surf_cli-0.7.0/.agents/skills/surf/references/surf.tex +298 -0
- surf_cli-0.7.0/.agents/skills/surf/references/usage.md +136 -0
- surf_cli-0.7.0/.claude-plugin/marketplace.json +18 -0
- surf_cli-0.7.0/.github/workflows/publish.yml +47 -0
- surf_cli-0.7.0/.gitignore +16 -0
- surf_cli-0.7.0/.grok-plugin/marketplace.json +15 -0
- surf_cli-0.7.0/LICENSE +21 -0
- surf_cli-0.7.0/PKG-INFO +177 -0
- surf_cli-0.7.0/README.md +142 -0
- surf_cli-0.7.0/RELEASING.md +56 -0
- surf_cli-0.7.0/flake.lock +61 -0
- surf_cli-0.7.0/flake.nix +160 -0
- surf_cli-0.7.0/plugins/surf/.claude-plugin/plugin.json +10 -0
- surf_cli-0.7.0/plugins/surf/plugin.json +11 -0
- surf_cli-0.7.0/pyproject.toml +87 -0
- surf_cli-0.7.0/scripts/deploy.sh +112 -0
- surf_cli-0.7.0/scripts/lint.sh +9 -0
- surf_cli-0.7.0/scripts/test.sh +4 -0
- surf_cli-0.7.0/src/surf/__init__.py +5 -0
- surf_cli-0.7.0/src/surf/__main__.py +7 -0
- surf_cli-0.7.0/src/surf/adapters.py +174 -0
- surf_cli-0.7.0/src/surf/logic.py +557 -0
- surf_cli-0.7.0/src/surf/models.py +148 -0
- surf_cli-0.7.0/src/surf/orchestrator.py +300 -0
- surf_cli-0.7.0/src/surf/test_adapters.py +218 -0
- surf_cli-0.7.0/src/surf/test_extract_section_perf_smoke.py +73 -0
- surf_cli-0.7.0/src/surf/test_logic.py +539 -0
- surf_cli-0.7.0/src/surf/test_models.py +30 -0
- surf_cli-0.7.0/src/surf/test_orchestrator.py +692 -0
- surf_cli-0.7.0/src/surf/test_tex_logic.py +535 -0
- surf_cli-0.7.0/uv.lock +419 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use instead of Read to skim markdown, TeX, or PDF files. Progressive
|
|
3
|
+
context disclosure by heading or outline. See `surf --help` for usage.
|
|
4
|
+
name: surf
|
|
5
|
+
metadata:
|
|
6
|
+
author:
|
|
7
|
+
name: Alexander R. Saint Croix
|
|
8
|
+
github_username: saintx
|
|
9
|
+
email: alex@saintx.us
|
|
10
|
+
twitter: alexsaintx
|
|
11
|
+
surf-version: "0.7.0"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Surf: Progressive Context Disclosure
|
|
15
|
+
|
|
16
|
+
Extract a markdown heading, TeX section, or PDF outline item via `surf` without loading the rest of the file. Default `surf ~/path/to/file.md` (no heading) is the file map, not the body.
|
|
17
|
+
|
|
18
|
+
## Load units (section routing)
|
|
19
|
+
|
|
20
|
+
Resolve with `surf` against the section heading; do not bulk-ingest a whole reference for one fact.
|
|
21
|
+
|
|
22
|
+
### About / orientation
|
|
23
|
+
|
|
24
|
+
| Intent | Section extraction |
|
|
25
|
+
|--------|--------------------|
|
|
26
|
+
| Overview | `surf references/about.md "Overview"` |
|
|
27
|
+
| When to use | `surf references/about.md "When to use"` |
|
|
28
|
+
|
|
29
|
+
### Usage
|
|
30
|
+
|
|
31
|
+
| Intent | Section extraction |
|
|
32
|
+
|--------|--------------------|
|
|
33
|
+
| Default: file map | `surf references/usage.md "Default: file map"` |
|
|
34
|
+
| Frontmatter only | `surf references/usage.md "Frontmatter only"` |
|
|
35
|
+
| Heading tree only | `surf references/usage.md "Heading tree only"` |
|
|
36
|
+
| Targeted section | `surf references/usage.md "Targeted section"` |
|
|
37
|
+
| TeX | `surf references/usage.md "TeX"` |
|
|
38
|
+
| PDF | `surf references/usage.md "PDF"` |
|
|
39
|
+
| Level | `surf references/usage.md "Level"` |
|
|
40
|
+
| Wikilink and link input | `surf references/usage.md "Wikilink and link input"` |
|
|
41
|
+
| Batch scanning | `surf references/usage.md "Batch scanning"` |
|
|
42
|
+
| Platform aggregation (batch section extraction) | `surf references/usage.md "Platform aggregation (batch section extraction)"` |
|
|
43
|
+
| Additional options | `surf references/usage.md "Additional options"` |
|
|
44
|
+
|
|
45
|
+
### Whitepaper (TeX and PDF examples)
|
|
46
|
+
|
|
47
|
+
The same paper ships as `references/surf.tex` and `references/surf.pdf`; both list the same tree, so any row below works against either file.
|
|
48
|
+
|
|
49
|
+
| Intent | Section extraction |
|
|
50
|
+
|--------|--------------------|
|
|
51
|
+
| List a TeX paper's sections | `surf references/surf.tex --list` |
|
|
52
|
+
| List a PDF's outline | `surf references/surf.pdf --list` |
|
|
53
|
+
| Read the abstract | `surf references/surf.tex "abstract"` |
|
|
54
|
+
| Why surf exists | `surf references/surf.pdf "Background"` |
|
|
55
|
+
| What a thin index looks like | `surf references/surf.tex "A Thin Index Shaped by Intent"` |
|
|
56
|
+
| Who composes context | `surf references/surf.pdf "Agentic Context Composition"` |
|
|
57
|
+
| Check a skill mechanically | `surf references/surf.tex "Skills You Can Check"` |
|
|
58
|
+
| Compose indexes across skills | `surf references/surf.pdf "Indexes over Indexes"` |
|
|
59
|
+
| Formats and corpora surf addresses | `surf references/surf.tex "Markdown, TeX, and PDF"` |
|
|
60
|
+
| How the paper checks itself | `surf references/surf.pdf "This Paper"` |
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
metadata:
|
|
3
|
+
author:
|
|
4
|
+
name: Alexander R. Saint Croix
|
|
5
|
+
github_username: saintx
|
|
6
|
+
email: alex@saintx.us
|
|
7
|
+
twitter: alexsaintx
|
|
8
|
+
surf-version: "0.7.0"
|
|
9
|
+
---
|
|
10
|
+
# Surf — About
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Progressive context disclosure — extract a heading, TeX section, or PDF outline item without loading the rest of the file. Default `surf ~/path/to/file.md` (no heading) is the file map: YAML (if present) plus the heading list, not the body. Same default on `.tex` and `.pdf`: the heading or outline list, or `no structural index` when there is none. Then `-f`, `--list`, or a heading for one index or one section. Wikilink resolution, batch metadata scanning, and batch section extraction across a directory. Use instead of Read to skim markdown, TeX, or PDF files.
|
|
15
|
+
|
|
16
|
+
## When to use
|
|
17
|
+
|
|
18
|
+
Invoke when skimming markdown, TeX, or PDF files, checking what a file contains, listing structure, extracting a named address, or batch-scanning metadata across a directory. See `surf --help` for CLI flags. Skip when the full body is already needed. Do not skip `.tex` or `.pdf`. Other existing UTF-8 files are addressed as ATX markdown. Do not use `surf ~/path/to/file.md` with no heading as a substitute for Read.
|
|
Binary file
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
\documentclass[11pt]{article}
|
|
2
|
+
|
|
3
|
+
\usepackage[margin=1.1in]{geometry}
|
|
4
|
+
\usepackage[T1]{fontenc}
|
|
5
|
+
\usepackage{lmodern}
|
|
6
|
+
\usepackage{microtype}
|
|
7
|
+
\usepackage{booktabs}
|
|
8
|
+
\usepackage{fancyvrb}
|
|
9
|
+
\usepackage{upquote}
|
|
10
|
+
\usepackage[hidelinks,bookmarks=true,bookmarksopen=true,bookmarksdepth=3]{hyperref}
|
|
11
|
+
|
|
12
|
+
\fvset{fontsize=\small,frame=none,xleftmargin=1.5em}
|
|
13
|
+
|
|
14
|
+
\title{\normalsize\textsc{Whitepaper}\\[0.6em]
|
|
15
|
+
\LARGE surf: Context Traversal without Ingestion\thanks{Marc McCahill is
|
|
16
|
+
credited with coining the phrase ``surfing the Internet.'' This tool lets agents
|
|
17
|
+
surf the seas of context.}}
|
|
18
|
+
\author{Alexander R. Saint Croix\\\texttt{alex@saintx.us}}
|
|
19
|
+
\date{September 2026}
|
|
20
|
+
|
|
21
|
+
\begin{document}
|
|
22
|
+
|
|
23
|
+
\maketitle
|
|
24
|
+
|
|
25
|
+
\pdfbookmark[1]{abstract}{abstract}
|
|
26
|
+
\begin{abstract}
|
|
27
|
+
surf is a command-line tool that returns one section of a markdown, TeX, or PDF
|
|
28
|
+
document by heading, and returns the document's heading tree when no heading is
|
|
29
|
+
given. I built it so that agent skills can be written as thin indexes: a
|
|
30
|
+
\texttt{SKILL.md} that lists the intents an agent might arrive with and, for each
|
|
31
|
+
one, a single command that pulls the exact section of reference material that
|
|
32
|
+
intent needs. The reference corpus can then grow without the index changing, an
|
|
33
|
+
agent loads only what its task requires, and the relationship between index and
|
|
34
|
+
corpus can be checked by machine. The surf skill ships this paper as its TeX and
|
|
35
|
+
PDF example, so every command it describes can be run against it.
|
|
36
|
+
\end{abstract}
|
|
37
|
+
|
|
38
|
+
\section{Background}
|
|
39
|
+
|
|
40
|
+
Three approaches to giving a language model context have a defect in common.
|
|
41
|
+
Each tends to hand the model more than the task needs and leave it to sort out
|
|
42
|
+
which part applies.
|
|
43
|
+
|
|
44
|
+
Retrieval-augmented generation (RAG) selects chunks of a corpus by their
|
|
45
|
+
resemblance to a query and places them in the prompt. Resemblance to a query and
|
|
46
|
+
relevance to the agent's task are different measures, and RAG can compute only
|
|
47
|
+
the first. The model receives passages that look like the question and then
|
|
48
|
+
spends attention deciding which of them bear on the work.
|
|
49
|
+
|
|
50
|
+
Long instruction files are the second approach. A \texttt{CLAUDE.md} or
|
|
51
|
+
\texttt{AGENTS.md} loads on every turn, and a file that has grown to cover every
|
|
52
|
+
situation a project might present is, on any given turn, mostly about other
|
|
53
|
+
situations. The model attends over all of it to find the part that applies.
|
|
54
|
+
|
|
55
|
+
Skills are the third, and they improve on this by adding agency. A skill loads
|
|
56
|
+
when the agent decides it applies, or when a person invokes it directly, so the
|
|
57
|
+
decision to bring material into context moves from a RAG pipeline or a static
|
|
58
|
+
file to the agent itself. But the decision stops at the skill boundary. Once
|
|
59
|
+
invoked, the entire \texttt{SKILL.md} lands in context, and a long one carries
|
|
60
|
+
the same situational content a long instruction file does. The agency costs the
|
|
61
|
+
whole file.
|
|
62
|
+
|
|
63
|
+
In January 2026 I kept some skill reference material as JSON and wrote the rows
|
|
64
|
+
of a \texttt{SKILL.md} as \texttt{jq} queries against those files. The agent ran
|
|
65
|
+
the query it needed and received the value. The file itself never entered
|
|
66
|
+
context. I call this context traversal without ingestion.
|
|
67
|
+
|
|
68
|
+
Most of my reference material is structured prose, though. A markdown file
|
|
69
|
+
carries a heading tree the way a JSON file carries keys, and what it lacked was a
|
|
70
|
+
command-line utility that would list that tree for an agent or return the prose
|
|
71
|
+
under one path through it. I wrote surf in March 2026 to be that utility. It
|
|
72
|
+
addresses a section by its heading and returns the section. Run against the
|
|
73
|
+
usage reference that ships with the surf skill:
|
|
74
|
+
|
|
75
|
+
\begin{Verbatim}
|
|
76
|
+
$ surf references/usage.md --list
|
|
77
|
+
- Surf Usage
|
|
78
|
+
- Default: file map
|
|
79
|
+
- Frontmatter only
|
|
80
|
+
- Heading tree only
|
|
81
|
+
- Targeted section
|
|
82
|
+
- TeX
|
|
83
|
+
- PDF
|
|
84
|
+
- Level
|
|
85
|
+
- Wikilink and link input
|
|
86
|
+
- Batch scanning
|
|
87
|
+
- Platform aggregation (batch section extraction)
|
|
88
|
+
- Additional options
|
|
89
|
+
|
|
90
|
+
$ surf references/usage.md "Level"
|
|
91
|
+
## Level
|
|
92
|
+
|
|
93
|
+
`--level 1` is the top rank. On markdown, rank 1 is `#`. A file whose
|
|
94
|
+
first heading is `##` needs `--level 2` to list it. On TeX, rank 1 is
|
|
95
|
+
the shallowest command in that file. In an article that starts at
|
|
96
|
+
`\section`, `--level 1` is `\section`. On PDF, rank 1 is the top
|
|
97
|
+
outline rank.
|
|
98
|
+
|
|
99
|
+
`--list --level N` prints ranks 1 through N. A named heading with
|
|
100
|
+
`--level N` matches only a heading at rank N.
|
|
101
|
+
\end{Verbatim}
|
|
102
|
+
|
|
103
|
+
The agent has seen twelve lines of headings and one section from the middle of
|
|
104
|
+
the file, and it has not opened the file. By June I was building skill indexes
|
|
105
|
+
shaped by intent on top of this, and in September surf gained TeX sectioning
|
|
106
|
+
commands and PDF outlines so that the same skills could index academic papers.
|
|
107
|
+
|
|
108
|
+
\section{A Thin Index Shaped by Intent}
|
|
109
|
+
|
|
110
|
+
A skill built on surf has two parts. The \texttt{SKILL.md} is a short file: one
|
|
111
|
+
sentence stating what the skill does, followed by a table grouped by the intents
|
|
112
|
+
an agent might have on arrival. Each row pairs an intent with one command that
|
|
113
|
+
resolves to a section of a reference file by its heading.
|
|
114
|
+
|
|
115
|
+
\begin{Verbatim}
|
|
116
|
+
| Intent | Section extraction |
|
|
117
|
+
|-----------------------------|-----------------------------------------------|
|
|
118
|
+
| See what a file contains | surf references/usage.md "Default: file map" |
|
|
119
|
+
| Pull one section | surf references/usage.md "Targeted section" |
|
|
120
|
+
| Address a TeX paper | surf references/usage.md "TeX" |
|
|
121
|
+
\end{Verbatim}
|
|
122
|
+
|
|
123
|
+
Those three rows come from the surf skill's own index, and each resolves
|
|
124
|
+
against the \texttt{usage.md} that ships beside this paper.
|
|
125
|
+
|
|
126
|
+
The \texttt{references/} directory holds the corpus. I break reference files
|
|
127
|
+
into sections of twenty to eighty lines and write each so that an agent arriving
|
|
128
|
+
at it with no surrounding context can act from it alone.
|
|
129
|
+
|
|
130
|
+
The two parts have different jobs and change for different reasons. The index
|
|
131
|
+
is intensional. It defines, by intent, which sections matter. The corpus is
|
|
132
|
+
extensional. It holds the sections themselves. Datalog makes the same split
|
|
133
|
+
between an intensional database of rules and an extensional database of ground
|
|
134
|
+
facts \cite{ceri1989}, and the properties carry over. A row in the index holds a
|
|
135
|
+
query, never a copy of the content, and the query resolves against the corpus at
|
|
136
|
+
the moment the agent runs it. So the corpus can be rewritten or extended without
|
|
137
|
+
touching the index, and the index can be regrouped by intent without touching
|
|
138
|
+
the corpus. The only thing both sides depend on is the heading tree. The index
|
|
139
|
+
changes when the structure of the corpus changes, and at no other time.
|
|
140
|
+
|
|
141
|
+
A surf call with no heading returns that structure. \texttt{surf file.md}
|
|
142
|
+
prints the file's frontmatter and then its heading tree, and nothing from the
|
|
143
|
+
body. An agent's first move on an unfamiliar file is to look at the map, and its
|
|
144
|
+
second is to select a section. Neither move loads the file.
|
|
145
|
+
|
|
146
|
+
\section{Agentic Context Composition}
|
|
147
|
+
|
|
148
|
+
The question that separates this pattern from RAG is who decides what enters
|
|
149
|
+
the context window. In RAG a component outside the model decides, before the
|
|
150
|
+
model has seen anything. In a skill built on surf the model reads the intent
|
|
151
|
+
index, judges what its current task needs, and issues the pulls itself. Agent
|
|
152
|
+
inference, rather than a similarity score, composes the context.
|
|
153
|
+
|
|
154
|
+
Invocation loads the index and nothing else. This holds whether the agent chose
|
|
155
|
+
the skill or a person invoked it by name. When I tell an agent to surf my
|
|
156
|
+
holdings for papers on a topic, I have forced the skill to load, and I have
|
|
157
|
+
forced only the index. Which papers it lists, which sections it pulls, and in
|
|
158
|
+
what order remain the agent's decisions against the task I gave it.
|
|
159
|
+
|
|
160
|
+
The cost structure follows from this. In a long \texttt{SKILL.md}, presenting an
|
|
161
|
+
option to the agent costs the full text behind that option, whether or not the
|
|
162
|
+
agent takes it. In a thin index, presenting an option costs one row, and taking
|
|
163
|
+
it costs one section. A skill can therefore lay out every route it supports, and
|
|
164
|
+
an agent that needs one route pays for one section while an agent that needs
|
|
165
|
+
none pays for a table.
|
|
166
|
+
|
|
167
|
+
Models perform worse as the volume of irrelevant material in the context window
|
|
168
|
+
grows, even when the material they need is present \cite{liu2024,hong2025}.
|
|
169
|
+
Every token the model attends to that has no bearing on the task is a token of
|
|
170
|
+
inference spent on sorting rather than on working. A skill that hands the agent
|
|
171
|
+
only the sections it asked for spends that inference on the work.
|
|
172
|
+
|
|
173
|
+
\section{Skills You Can Check}
|
|
174
|
+
\label{sec:check}
|
|
175
|
+
|
|
176
|
+
Because the index is data, a script can verify the relationship between index
|
|
177
|
+
and corpus.
|
|
178
|
+
|
|
179
|
+
Every row is an executable command. After any edit to a skill, a script can run
|
|
180
|
+
every listed command and confirm that each resolves to a section. A row that
|
|
181
|
+
fails is a defect, and the most common cause is a renamed heading the index did
|
|
182
|
+
not follow. Renaming a heading is a schema change under this pattern, and the
|
|
183
|
+
check treats it as one.
|
|
184
|
+
|
|
185
|
+
The reverse check is more interesting. A script can list every section in the
|
|
186
|
+
corpus and count how many index rows reach each one. A section reached zero
|
|
187
|
+
times is a finding, and the count says which questions to ask. The index may
|
|
188
|
+
have fallen behind the corpus and be missing a row. The section may be content
|
|
189
|
+
nobody routes to, and belong trimmed or folded into a neighbor. The corpus may
|
|
190
|
+
have outgrown one index and need a second. Or the file may already be
|
|
191
|
+
structured for navigation by intent, so that an agent told to surf its heading
|
|
192
|
+
tree directly needs no row for it at all. Skills written as prose cannot be
|
|
193
|
+
audited this way, because there is nothing to count.
|
|
194
|
+
|
|
195
|
+
Two conventions make the checks reliable. The index quotes headings literally
|
|
196
|
+
from \texttt{surf -{}-list}, macros and punctuation included, so it never guesses
|
|
197
|
+
at an address. And structural divisions use real headings rather than bold text,
|
|
198
|
+
because surf sees the first and cannot see the second.
|
|
199
|
+
|
|
200
|
+
\section{Indexes over Indexes}
|
|
201
|
+
|
|
202
|
+
An index lists what a corpus contains. A procedure, a numbered list of steps,
|
|
203
|
+
says what to do with it. Most of what a skill carries is the first kind, and a
|
|
204
|
+
thin index hands the agent that inventory without ordering it. The order comes
|
|
205
|
+
from the task.
|
|
206
|
+
|
|
207
|
+
Indexes compose, and composing them is cheaper than enlarging one. A family of
|
|
208
|
+
twenty skills with forty sections each is eight hundred rows in a single index,
|
|
209
|
+
all of them loaded on every invocation to use one. Split into a top index that
|
|
210
|
+
routes by broad intent to member skills and a member index in each that routes
|
|
211
|
+
by narrower intent to its sections, the same destination costs twenty rows and
|
|
212
|
+
then forty, and the agent sees no others. A row in one skill's index may also
|
|
213
|
+
point at a section in another skill's corpus, so shared reference material lives
|
|
214
|
+
in one place and every skill that needs it routes there. The agent descends as
|
|
215
|
+
far as the task requires and at each level pays for one small table, never for
|
|
216
|
+
the corpora beneath it. A family grows by adding a member index and one row
|
|
217
|
+
above it.
|
|
218
|
+
|
|
219
|
+
\section{Markdown, TeX, and PDF}
|
|
220
|
+
|
|
221
|
+
surf addresses a markdown file by its ATX headings, a TeX file by its sectioning
|
|
222
|
+
commands and its \texttt{abstract} environment, and a PDF by its outline. The
|
|
223
|
+
agent's two moves are the same in each case: list the section titles, then
|
|
224
|
+
select the one to read.
|
|
225
|
+
|
|
226
|
+
\begin{Verbatim}
|
|
227
|
+
surf paper.tex --list --level 2
|
|
228
|
+
surf paper.tex "abstract"
|
|
229
|
+
surf paper.pdf "Experiments#Programming"
|
|
230
|
+
\end{Verbatim}
|
|
231
|
+
|
|
232
|
+
A markdown wiki adds a second kind of structure. In an Obsidian vault, a
|
|
233
|
+
section's body names other sections by link, \texttt{[[note\#Heading]]} or
|
|
234
|
+
\texttt{[[note\#Parent\#Child]]}, and surf accepts that link text as a target.
|
|
235
|
+
An agent reading one section follows a link with the same command it used to
|
|
236
|
+
arrive, so the vault is traversed one section at a time along the links its
|
|
237
|
+
authors wrote, and no note is loaded whole.
|
|
238
|
+
|
|
239
|
+
\begin{Verbatim}
|
|
240
|
+
surf "[[agents/memory#Episodic buffer]]"
|
|
241
|
+
\end{Verbatim}
|
|
242
|
+
|
|
243
|
+
A paper already has the structure the pattern needs, maintained by its authors.
|
|
244
|
+
One skill can therefore index a family of related papers at subsection
|
|
245
|
+
granularity, with rows that resolve to one experiment section of one paper. An
|
|
246
|
+
agent working the topic reads the sections its question touches across the
|
|
247
|
+
family and nothing else.
|
|
248
|
+
|
|
249
|
+
Shared structure across a corpus goes further. Most papers have an abstract, so
|
|
250
|
+
a loop over a directory of papers that runs \texttt{surf \$f "abstract"}
|
|
251
|
+
projects that one section out of most of the collection. If every skill in a
|
|
252
|
+
library keeps a \texttt{references/about.md} with the headings
|
|
253
|
+
\texttt{Overview} and \texttt{When to use}, two commands over the library return
|
|
254
|
+
a digest of every skill for a few hundred tokens each. Mine does, and the digest
|
|
255
|
+
is how I orient an agent to some eighty skills without loading one of them.
|
|
256
|
+
Frontmatter, read with \texttt{surf -f}, filters the collection to the files
|
|
257
|
+
that share a structure before the projection runs. A heading convention, seen
|
|
258
|
+
this way, is a schema declaration, and a corpus that follows one can be queried
|
|
259
|
+
like a table. In every format the address is the text that
|
|
260
|
+
\texttt{surf -{}-list} prints for the heading.
|
|
261
|
+
|
|
262
|
+
\section{This Paper}
|
|
263
|
+
|
|
264
|
+
The surf skill needs a TeX file and a PDF that it may ship, and this paper is
|
|
265
|
+
both. The commands below run against it as distributed.
|
|
266
|
+
|
|
267
|
+
\begin{Verbatim}
|
|
268
|
+
surf surf.tex --list
|
|
269
|
+
surf surf.pdf --list
|
|
270
|
+
surf surf.tex "abstract"
|
|
271
|
+
surf surf.pdf "Skills You Can Check"
|
|
272
|
+
\end{Verbatim}
|
|
273
|
+
|
|
274
|
+
The two listings return the same tree. The skill's build checks that claim
|
|
275
|
+
mechanically, in the way Section~\ref{sec:check} describes, each time this file
|
|
276
|
+
changes.
|
|
277
|
+
|
|
278
|
+
\begin{thebibliography}{9}
|
|
279
|
+
|
|
280
|
+
\bibitem{ceri1989}
|
|
281
|
+
S.~Ceri, G.~Gottlob, and L.~Tanca.
|
|
282
|
+
What you always wanted to know about Datalog (and never dared to ask).
|
|
283
|
+
\emph{IEEE Transactions on Knowledge and Data Engineering}, 1(1):146--166, 1989.
|
|
284
|
+
|
|
285
|
+
\bibitem{liu2024}
|
|
286
|
+
N.~F. Liu, K.~Lin, J.~Hewitt, A.~Paranjape, M.~Bevilacqua, F.~Petroni, and
|
|
287
|
+
P.~Liang.
|
|
288
|
+
Lost in the middle: How language models use long contexts.
|
|
289
|
+
\emph{Transactions of the Association for Computational Linguistics}, 12:157--173, 2024.
|
|
290
|
+
|
|
291
|
+
\bibitem{hong2025}
|
|
292
|
+
K.~Hong, A.~Troynikov, and J.~Huber.
|
|
293
|
+
Context rot: How increasing input tokens impacts LLM performance.
|
|
294
|
+
Chroma technical report, July 2025.
|
|
295
|
+
|
|
296
|
+
\end{thebibliography}
|
|
297
|
+
|
|
298
|
+
\end{document}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
metadata:
|
|
3
|
+
author:
|
|
4
|
+
name: Alexander R. Saint Croix
|
|
5
|
+
github_username: saintx
|
|
6
|
+
email: alex@saintx.us
|
|
7
|
+
twitter: alexsaintx
|
|
8
|
+
surf-version: "0.7.0"
|
|
9
|
+
---
|
|
10
|
+
# Surf Usage
|
|
11
|
+
|
|
12
|
+
`surf` extracts a markdown heading, a TeX section, or a PDF outline item.
|
|
13
|
+
|
|
14
|
+
Name a heading to get that section. `--full` with a named heading prints YAML frontmatter, then that section. `--full` without a heading is the file map. Neither form prints the whole file.
|
|
15
|
+
|
|
16
|
+
## Default: file map
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
surf ~/path/to/file.md
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
No heading. This is the file map: YAML frontmatter if present, then the heading list. It does not print the body.
|
|
23
|
+
|
|
24
|
+
If there is no frontmatter, you get the heading list only. If there are no headings and no YAML, you get `no structural index` and a size line. Markdown and TeX print lines and bytes. PDF prints pages and bytes. The same command on a `.tex` or `.pdf` file prints that file's heading or outline list, not the body.
|
|
25
|
+
|
|
26
|
+
`surf` with no file path exits 2. It prints `Error: no target specified. Use surf --help for usage.`
|
|
27
|
+
|
|
28
|
+
## Frontmatter only
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
surf -f ~/path/to/file.md
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Returns YAML frontmatter. No body. No heading list. Use this to read YAML metadata. PDF has no YAML, so this prints nothing.
|
|
35
|
+
|
|
36
|
+
## Heading tree only
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
surf --list ~/path/to/file.md
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Returns the heading hierarchy. No YAML. Use this when you want headings and not frontmatter.
|
|
43
|
+
|
|
44
|
+
## Targeted section
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
surf ~/path/to/file.md "<heading text>"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Returns content under the matched heading, down to the next same-or-higher-level heading. Nested paths (`Parent#Child`) select a child inside that parent.
|
|
51
|
+
|
|
52
|
+
On markdown, the address is the heading text after the `#` marks. On TeX, the address is the title in braces. The TeX `abstract` environment is addressed as `abstract`. On PDF, the address is the outline bookmark title.
|
|
53
|
+
|
|
54
|
+
Heading match ignores case.
|
|
55
|
+
|
|
56
|
+
## TeX
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
surf ~/path/to/file.tex
|
|
60
|
+
surf --list ~/path/to/file.tex
|
|
61
|
+
surf ~/path/to/file.tex "Heading"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Address is the brace title. The `abstract` environment is addressed as `abstract`. `\input` and `\include` expand `.tex` files relative to the file you named. They do not expand graphics, comments, shell pipes, or paths built by macros. They also skip absolute paths, `..` path segments, and files that are not `.tex`.
|
|
65
|
+
|
|
66
|
+
## PDF
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
surf ~/path/to/file.pdf
|
|
70
|
+
surf --list ~/path/to/file.pdf
|
|
71
|
+
surf ~/path/to/file.pdf "Heading"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Address is the outline bookmark title. The extract is the page text from this bookmark to the next bookmark. If the next bookmark is on the same page, that page is included. Listing the outline does not print page text.
|
|
75
|
+
|
|
76
|
+
## Level
|
|
77
|
+
|
|
78
|
+
`--level 1` is the top rank. On markdown, rank 1 is `#`. A file whose first heading is `##` needs `--level 2` to list it. On TeX, rank 1 is the shallowest command in that file. In an article that starts at `\section`, `--level 1` is `\section`. On PDF, rank 1 is the top outline rank.
|
|
79
|
+
|
|
80
|
+
`--list --level N` prints ranks 1 through N. A named heading with `--level N` matches only a heading at rank N.
|
|
81
|
+
|
|
82
|
+
## Wikilink and link input
|
|
83
|
+
|
|
84
|
+
`surf` accepts wikilinks and markdown links as targets:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
surf "[[path/to/file]]"
|
|
88
|
+
surf "[[path/to/file#Heading]]"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
surf "[Text](path/to/file.md#Heading)"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A wikilink with no `#Heading` is the file map, not the full page. A wikilink with `#Heading` extracts that section. A markdown link with a `#Heading` extracts that section. A markdown link with no `#Heading` is the file map.
|
|
96
|
+
|
|
97
|
+
The `.md` extension is implicit. `surf ~/path/to/file` resolves to `~/path/to/file.md`. `.tex` and `.pdf` need the suffix.
|
|
98
|
+
|
|
99
|
+
## Batch scanning
|
|
100
|
+
|
|
101
|
+
Scan YAML across a directory without loading any body:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
ls ~/path/to/*.md | xargs -I {} surf -f {}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Same pattern on nested trees:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
ls ~/path/to/*/file.md | xargs -I {} surf -f {}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Platform aggregation (batch section extraction)
|
|
114
|
+
|
|
115
|
+
Print the path, then one heading, for each matching file:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
find ~/path/to -path '*/subdir/file.md' | sort \
|
|
119
|
+
| xargs -I{} sh -c 'echo "### {}" && surf {} "Some Heading" && echo'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Change the `find` root, the path glob, and the heading text to match the files you have.
|
|
123
|
+
|
|
124
|
+
## Additional options
|
|
125
|
+
|
|
126
|
+
| Flag | Effect |
|
|
127
|
+
|------|--------|
|
|
128
|
+
| no heading | File map (YAML if any, then headings or outline). Not the body. |
|
|
129
|
+
| heading, no flag | Section content without frontmatter |
|
|
130
|
+
| `--list` | Heading tree only |
|
|
131
|
+
| `-f` / `--frontmatter-only` | YAML only |
|
|
132
|
+
| `--full` | With a named heading: YAML frontmatter, then that section. Without a heading: the file map. |
|
|
133
|
+
| `--content-only` / `--body-only` | Same as a named heading with no flag. This is the default extract. |
|
|
134
|
+
| `--level N` | List: ranks 1 through N. Extract: exact rank N. |
|
|
135
|
+
| `--no-heading` | Omit the heading line from section output |
|
|
136
|
+
| `-o <file>` | Write output to a file. |
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "surf-cli",
|
|
3
|
+
"owner": { "name": "Alexander R. Saint Croix", "email": "alex@saintx.us" },
|
|
4
|
+
"metadata": {
|
|
5
|
+
"description": "Plugins shipped with the surf CLI.",
|
|
6
|
+
"version": "0.7.0"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "surf",
|
|
11
|
+
"source": "./plugins/surf",
|
|
12
|
+
"description": "Extract a markdown heading, TeX section, or PDF outline item without loading the rest of the file.",
|
|
13
|
+
"version": "0.7.0",
|
|
14
|
+
"category": "tooling",
|
|
15
|
+
"keywords": ["surf", "markdown", "tex", "pdf", "context", "skills"]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "*.*.*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
with:
|
|
17
|
+
persist-credentials: false
|
|
18
|
+
- uses: astral-sh/setup-uv@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
enable-cache: true
|
|
22
|
+
- name: Test
|
|
23
|
+
run: uv run --frozen pytest src/surf -q
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build --no-sources
|
|
26
|
+
- uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: dist
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
name: Publish
|
|
33
|
+
needs: build
|
|
34
|
+
if: github.ref_type == 'tag' && !startsWith(github.ref_name, 'v')
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment:
|
|
37
|
+
name: pypi
|
|
38
|
+
url: https://pypi.org/p/surf-cli
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write
|
|
41
|
+
contents: read
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/download-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "surf-cli",
|
|
3
|
+
"description": "Plugins shipped with the surf CLI.",
|
|
4
|
+
"owner": { "name": "Alexander R. Saint Croix" },
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "surf",
|
|
8
|
+
"description": "Extract a markdown heading, TeX section, or PDF outline item without loading the rest of the file.",
|
|
9
|
+
"category": "tooling",
|
|
10
|
+
"source": "./plugins/surf",
|
|
11
|
+
"homepage": "https://github.com/saintx/surf-cli",
|
|
12
|
+
"keywords": ["surf", "markdown", "tex", "pdf", "context", "skills"]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
surf_cli-0.7.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander R. Saint Croix
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|