surf-cli 0.7.0__tar.gz → 0.7.1__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 → surf_cli-0.7.1}/.agents/skills/surf/SKILL.md +5 -13
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.agents/skills/surf/references/about.md +1 -1
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.agents/skills/surf/references/usage.md +1 -1
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.claude-plugin/marketplace.json +2 -2
- surf_cli-0.7.1/CONTRIBUTING.md +53 -0
- surf_cli-0.7.1/PKG-INFO +272 -0
- surf_cli-0.7.1/README.md +237 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/flake.nix +1 -1
- {surf_cli-0.7.0 → surf_cli-0.7.1}/plugins/surf/.claude-plugin/plugin.json +1 -1
- {surf_cli-0.7.0 → surf_cli-0.7.1}/plugins/surf/plugin.json +1 -1
- {surf_cli-0.7.0 → surf_cli-0.7.1}/pyproject.toml +2 -2
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/__init__.py +1 -1
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/logic.py +35 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/test_logic.py +57 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/uv.lock +1 -1
- surf_cli-0.7.0/PKG-INFO +0 -177
- surf_cli-0.7.0/README.md +0 -142
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.agents/skills/surf/references/surf.pdf +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.agents/skills/surf/references/surf.tex +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.github/workflows/publish.yml +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.gitignore +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/.grok-plugin/marketplace.json +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/LICENSE +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/RELEASING.md +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/flake.lock +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/scripts/deploy.sh +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/scripts/lint.sh +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/scripts/test.sh +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/__main__.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/adapters.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/models.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/orchestrator.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/test_adapters.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/test_extract_section_perf_smoke.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/test_models.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/test_orchestrator.py +0 -0
- {surf_cli-0.7.0 → surf_cli-0.7.1}/src/surf/test_tex_logic.py +0 -0
|
@@ -8,7 +8,7 @@ metadata:
|
|
|
8
8
|
github_username: saintx
|
|
9
9
|
email: alex@saintx.us
|
|
10
10
|
twitter: alexsaintx
|
|
11
|
-
surf-version: "0.7.
|
|
11
|
+
surf-version: "0.7.1"
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Surf: Progressive Context Disclosure
|
|
@@ -42,19 +42,11 @@ Resolve with `surf` against the section heading; do not bulk-ingest a whole refe
|
|
|
42
42
|
| Platform aggregation (batch section extraction) | `surf references/usage.md "Platform aggregation (batch section extraction)"` |
|
|
43
43
|
| Additional options | `surf references/usage.md "Additional options"` |
|
|
44
44
|
|
|
45
|
-
###
|
|
45
|
+
### TeX and PDF
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
`references/surf.tex` and `references/surf.pdf` are one document in two forms. The same address works on either.
|
|
48
48
|
|
|
49
49
|
| Intent | Section extraction |
|
|
50
50
|
|--------|--------------------|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
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"` |
|
|
51
|
+
| Extract a section from TeX | `surf references/surf.tex "Markdown, TeX, and PDF"` |
|
|
52
|
+
| Extract a section from PDF | `surf references/surf.pdf "Markdown, TeX, and PDF"` |
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"owner": { "name": "Alexander R. Saint Croix", "email": "alex@saintx.us" },
|
|
4
4
|
"metadata": {
|
|
5
5
|
"description": "Plugins shipped with the surf CLI.",
|
|
6
|
-
"version": "0.7.
|
|
6
|
+
"version": "0.7.1"
|
|
7
7
|
},
|
|
8
8
|
"plugins": [
|
|
9
9
|
{
|
|
10
10
|
"name": "surf",
|
|
11
11
|
"source": "./plugins/surf",
|
|
12
12
|
"description": "Extract a markdown heading, TeX section, or PDF outline item without loading the rest of the file.",
|
|
13
|
-
"version": "0.7.
|
|
13
|
+
"version": "0.7.1",
|
|
14
14
|
"category": "tooling",
|
|
15
15
|
"keywords": ["surf", "markdown", "tex", "pdf", "context", "skills"]
|
|
16
16
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing to surf
|
|
2
|
+
|
|
3
|
+
How to get a working tree, run the tests, and pass lint. The release procedure is in [RELEASING.md](https://github.com/saintx/surf-cli/blob/main/RELEASING.md).
|
|
4
|
+
|
|
5
|
+
## Environment
|
|
6
|
+
|
|
7
|
+
Python 3.12+ and [uv](https://docs.astral.sh/uv/). Either of these gives a shell with the dev dependencies installed:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
nix develop # python312 + uv, then uv sync
|
|
11
|
+
uv sync # without Nix; the dev group is installed by default
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Run the checkout's `surf` without installing it:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv run surf --version
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Layout
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/surf/
|
|
24
|
+
models.py data types
|
|
25
|
+
logic.py heading trees, section extraction; pure, no I/O
|
|
26
|
+
adapters.py file reading, TeX expansion, pypdf
|
|
27
|
+
orchestrator.py CLI: argument parsing, dispatch, exit codes
|
|
28
|
+
test_*.py tests, beside the modules they cover
|
|
29
|
+
plugins/surf/ the agent skill and its manifests
|
|
30
|
+
scripts/ test.sh, lint.sh, deploy.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The layers only depend downward, and `pypdf` is confined to `adapters.py`. Four `import-linter` contracts in `pyproject.toml` enforce this, and `scripts/lint.sh` runs them.
|
|
34
|
+
|
|
35
|
+
## Tests
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
scripts/test.sh # uv run pytest src/surf -q
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Tests live in `src/surf/test_*.py` and are excluded from the wheel. The root `tests/` directory is unused.
|
|
42
|
+
|
|
43
|
+
## Lint
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
scripts/lint.sh
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Runs, in order: `isort`, `black` (line length 100), `autoflake` (unused imports), `pyright` (basic mode, unused imports are errors), and `lint-imports` against the layer contracts. The first three rewrite files in place; run it before committing.
|
|
50
|
+
|
|
51
|
+
## Version
|
|
52
|
+
|
|
53
|
+
`surf --version` must agree with the version in every location listed in [RELEASING.md](https://github.com/saintx/surf-cli/blob/main/RELEASING.md). Changes that are not a release do not bump it.
|
surf_cli-0.7.1/PKG-INFO
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: surf-cli
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Summary: List headings, or extract one section by name, from Markdown, TeX, or PDF.
|
|
5
|
+
Project-URL: Homepage, https://github.com/saintx/surf-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/saintx/surf-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/saintx/surf-cli/issues
|
|
8
|
+
Project-URL: Author, https://github.com/saintx
|
|
9
|
+
Author-email: "Alexander R. Saint Croix" <alex@saintx.us>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Alexander R. Saint Croix
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Requires-Python: >=3.12
|
|
33
|
+
Requires-Dist: pypdf>=6.17.0
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# surf
|
|
37
|
+
|
|
38
|
+
List headings, or extract one section by name, from Markdown, TeX, or PDF.
|
|
39
|
+
|
|
40
|
+
Distribution `surf-cli`. Command `surf`. Python 3.12+; pypdf is the one runtime dependency, for PDF outlines. Only the section you name is printed, so the rest of the file never enters an agent's context.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv tool install surf-cli # or: pipx install surf-cli, or: pip install surf-cli
|
|
46
|
+
surf --version
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With Nix:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
nix profile install github:saintx/surf-cli # add ?ref=X.Y.Z to pin a release
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
The examples run against the reference files shipped with the agent skill:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd plugins/surf/skills/surf/references
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A file with no heading named returns the map: frontmatter, then the heading tree, none of the body.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
$ surf about.md
|
|
67
|
+
---
|
|
68
|
+
metadata:
|
|
69
|
+
author:
|
|
70
|
+
name: Alexander R. Saint Croix
|
|
71
|
+
github_username: saintx
|
|
72
|
+
email: alex@saintx.us
|
|
73
|
+
twitter: alexsaintx
|
|
74
|
+
surf-version: "0.7.1"
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
- Surf — About
|
|
78
|
+
- Overview
|
|
79
|
+
- When to use
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A heading returns that section and stops at the next heading of the same or higher level.
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
$ surf about.md "When to use"
|
|
86
|
+
## When to use
|
|
87
|
+
|
|
88
|
+
Invoke when skimming markdown, TeX, or PDF files, checking what a file contains, ...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The same two commands work on TeX, addressed by sectioning commands and the `abstract` environment, and on PDF, addressed by outline bookmarks. `surf.tex` and `surf.pdf` are one paper in two forms and list the same tree from either.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
$ surf surf.tex --list
|
|
95
|
+
- abstract
|
|
96
|
+
- Background
|
|
97
|
+
- A Thin Index Shaped by Intent
|
|
98
|
+
- Agentic Context Composition
|
|
99
|
+
- Skills You Can Check
|
|
100
|
+
- Indexes over Indexes
|
|
101
|
+
- Markdown, TeX, and PDF
|
|
102
|
+
- This Paper
|
|
103
|
+
|
|
104
|
+
$ surf surf.tex "Markdown, TeX, and PDF"
|
|
105
|
+
\section{Markdown, TeX, and PDF}
|
|
106
|
+
|
|
107
|
+
surf addresses a markdown file by its ATX headings, a TeX file by its sectioning
|
|
108
|
+
commands and its \texttt{abstract} environment, and a PDF by its outline. ...
|
|
109
|
+
|
|
110
|
+
$ surf surf.pdf "Markdown, TeX, and PDF"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Addressing
|
|
114
|
+
|
|
115
|
+
Suppose `guide.md` is:
|
|
116
|
+
|
|
117
|
+
```markdown
|
|
118
|
+
---
|
|
119
|
+
title: Guide
|
|
120
|
+
tags: [cli, docs]
|
|
121
|
+
---
|
|
122
|
+
# Guide
|
|
123
|
+
|
|
124
|
+
Intro paragraph.
|
|
125
|
+
|
|
126
|
+
## Install
|
|
127
|
+
|
|
128
|
+
### Usage
|
|
129
|
+
|
|
130
|
+
Install usage text.
|
|
131
|
+
|
|
132
|
+
## API
|
|
133
|
+
|
|
134
|
+
### Usage
|
|
135
|
+
|
|
136
|
+
API usage text.
|
|
137
|
+
|
|
138
|
+
#### Examples
|
|
139
|
+
|
|
140
|
+
Deep example.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Its map is:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
$ surf guide.md
|
|
147
|
+
---
|
|
148
|
+
title: Guide
|
|
149
|
+
tags: [cli, docs]
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
- Guide
|
|
153
|
+
- Install
|
|
154
|
+
- Usage
|
|
155
|
+
- API
|
|
156
|
+
- Usage
|
|
157
|
+
- Examples
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Get one section.** Name the heading. The section runs to the next heading of the same or higher level, so a parent brings its children.
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
$ surf guide.md "API"
|
|
164
|
+
## API
|
|
165
|
+
|
|
166
|
+
### Usage
|
|
167
|
+
|
|
168
|
+
API usage text.
|
|
169
|
+
|
|
170
|
+
#### Examples
|
|
171
|
+
|
|
172
|
+
Deep example.
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Pick between two headings with the same name.** Write the path from the parent. A bare name matches the first occurrence in the file.
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
$ surf guide.md "API#Usage"
|
|
179
|
+
### Usage
|
|
180
|
+
|
|
181
|
+
API usage text.
|
|
182
|
+
|
|
183
|
+
#### Examples
|
|
184
|
+
|
|
185
|
+
Deep example.
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**List only the top N levels.** `--level N` on a listing shows ranks 1 through N.
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
$ surf guide.md --list --level 2
|
|
192
|
+
- Guide
|
|
193
|
+
- Install
|
|
194
|
+
- API
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Extract only at an exact level.** `--level N` on an extract matches rank N and nothing else. `Usage` exists only at rank 3, so rank 2 is not found.
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
$ surf guide.md "Examples" --level 4
|
|
201
|
+
#### Examples
|
|
202
|
+
|
|
203
|
+
Deep example.
|
|
204
|
+
|
|
205
|
+
$ surf guide.md "Usage" --level 2
|
|
206
|
+
Error: heading "Usage" not found in guide.md.
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Use a link as the address.** Obsidian wikilinks and markdown links resolve directly, `#` segments included.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
surf "[[guide#API#Usage]]"
|
|
213
|
+
surf "[API](guide.md#API)"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Frontmatter only, or headings only.** `-f` prints the YAML block and nothing else. `--list` prints the tree and no YAML. `--full` prints the YAML and then the named section.
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
surf -f guide.md
|
|
220
|
+
surf --list guide.md
|
|
221
|
+
surf guide.md "Install" --full
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Drop the heading line, or write to a file.**
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
surf guide.md "API" --no-heading
|
|
228
|
+
surf guide.md "API" -o api.md
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**What counts as a heading.** On markdown, the ATX heading text. On TeX, the brace title of a sectioning command, plus `abstract` for the abstract environment; `\input` and `\include` of `.tex` files expand relative to the file you name, while graphics, comments, shell pipes, absolute paths, `..` segments, and macro-built paths do not. On PDF, an outline bookmark title; the extract is the page text from that bookmark to the next, and when the next bookmark shares a page, that page is included. Rank 1 is `#` on markdown, the shallowest sectioning command in the TeX file (`\section` in an article), and the outline's top rank on PDF.
|
|
232
|
+
|
|
233
|
+
Matching ignores case. `surf guide` resolves to `guide.md`; `.tex` and `.pdf` need their suffix. Any other UTF-8 file is addressed as ATX markdown.
|
|
234
|
+
|
|
235
|
+
**When there is nothing to address.** A file with no headings, or a PDF with no outline, prints `no structural index` and line and byte counts (page and byte counts on PDF), and exits 0. A heading that is not there exits 1 with `Error: heading "X" not found in guide.md.` A missing file or bad usage exits 2.
|
|
236
|
+
|
|
237
|
+
**Scan a directory.** Frontmatter across many files, no body loaded:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
ls docs/*.md | xargs -I {} surf -f {}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`surf --help` lists every flag.
|
|
244
|
+
|
|
245
|
+
## Agent skill
|
|
246
|
+
|
|
247
|
+
`plugins/surf` packages a skill that teaches an agent how to use surf. It carries a Claude Code manifest, an Agent Plugins 1.0.0 manifest, and the skill itself at `plugins/surf/skills/surf`, in the Agent Skills format that Claude Code, Codex, Gemini CLI, Cursor, and Grok Build read. The skill shells out to `surf`, so install the CLI first.
|
|
248
|
+
|
|
249
|
+
Claude Code installs it from the marketplace in this repo:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
claude plugin marketplace add saintx/surf-cli
|
|
253
|
+
claude plugin install surf@surf-cli
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Any harness that reads a skills directory takes a copy:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
cp -R plugins/surf/skills/surf ~/.agents/skills/surf
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
This checkout links `.agents/skills/surf`, `.claude/skills/surf`, and `.grok/skills/surf` into the plugin, so an agent working in the repo already has it.
|
|
263
|
+
|
|
264
|
+
The paper in `plugins/surf/skills/surf/references/surf.pdf` (also `surf.tex`) is about what surf can be used to build. surf itself is the primitive.
|
|
265
|
+
|
|
266
|
+
## Contributing
|
|
267
|
+
|
|
268
|
+
Development setup, tests, and lint are in [CONTRIBUTING.md](https://github.com/saintx/surf-cli/blob/main/CONTRIBUTING.md). The release procedure is in [RELEASING.md](https://github.com/saintx/surf-cli/blob/main/RELEASING.md).
|
|
269
|
+
|
|
270
|
+
## License
|
|
271
|
+
|
|
272
|
+
[MIT](https://github.com/saintx/surf-cli/blob/main/LICENSE).
|
surf_cli-0.7.1/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# surf
|
|
2
|
+
|
|
3
|
+
List headings, or extract one section by name, from Markdown, TeX, or PDF.
|
|
4
|
+
|
|
5
|
+
Distribution `surf-cli`. Command `surf`. Python 3.12+; pypdf is the one runtime dependency, for PDF outlines. Only the section you name is printed, so the rest of the file never enters an agent's context.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv tool install surf-cli # or: pipx install surf-cli, or: pip install surf-cli
|
|
11
|
+
surf --version
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
With Nix:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
nix profile install github:saintx/surf-cli # add ?ref=X.Y.Z to pin a release
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
The examples run against the reference files shipped with the agent skill:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd plugins/surf/skills/surf/references
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A file with no heading named returns the map: frontmatter, then the heading tree, none of the body.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
$ surf about.md
|
|
32
|
+
---
|
|
33
|
+
metadata:
|
|
34
|
+
author:
|
|
35
|
+
name: Alexander R. Saint Croix
|
|
36
|
+
github_username: saintx
|
|
37
|
+
email: alex@saintx.us
|
|
38
|
+
twitter: alexsaintx
|
|
39
|
+
surf-version: "0.7.1"
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
- Surf — About
|
|
43
|
+
- Overview
|
|
44
|
+
- When to use
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A heading returns that section and stops at the next heading of the same or higher level.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
$ surf about.md "When to use"
|
|
51
|
+
## When to use
|
|
52
|
+
|
|
53
|
+
Invoke when skimming markdown, TeX, or PDF files, checking what a file contains, ...
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The same two commands work on TeX, addressed by sectioning commands and the `abstract` environment, and on PDF, addressed by outline bookmarks. `surf.tex` and `surf.pdf` are one paper in two forms and list the same tree from either.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ surf surf.tex --list
|
|
60
|
+
- abstract
|
|
61
|
+
- Background
|
|
62
|
+
- A Thin Index Shaped by Intent
|
|
63
|
+
- Agentic Context Composition
|
|
64
|
+
- Skills You Can Check
|
|
65
|
+
- Indexes over Indexes
|
|
66
|
+
- Markdown, TeX, and PDF
|
|
67
|
+
- This Paper
|
|
68
|
+
|
|
69
|
+
$ surf surf.tex "Markdown, TeX, and PDF"
|
|
70
|
+
\section{Markdown, TeX, and PDF}
|
|
71
|
+
|
|
72
|
+
surf addresses a markdown file by its ATX headings, a TeX file by its sectioning
|
|
73
|
+
commands and its \texttt{abstract} environment, and a PDF by its outline. ...
|
|
74
|
+
|
|
75
|
+
$ surf surf.pdf "Markdown, TeX, and PDF"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Addressing
|
|
79
|
+
|
|
80
|
+
Suppose `guide.md` is:
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
---
|
|
84
|
+
title: Guide
|
|
85
|
+
tags: [cli, docs]
|
|
86
|
+
---
|
|
87
|
+
# Guide
|
|
88
|
+
|
|
89
|
+
Intro paragraph.
|
|
90
|
+
|
|
91
|
+
## Install
|
|
92
|
+
|
|
93
|
+
### Usage
|
|
94
|
+
|
|
95
|
+
Install usage text.
|
|
96
|
+
|
|
97
|
+
## API
|
|
98
|
+
|
|
99
|
+
### Usage
|
|
100
|
+
|
|
101
|
+
API usage text.
|
|
102
|
+
|
|
103
|
+
#### Examples
|
|
104
|
+
|
|
105
|
+
Deep example.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Its map is:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
$ surf guide.md
|
|
112
|
+
---
|
|
113
|
+
title: Guide
|
|
114
|
+
tags: [cli, docs]
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
- Guide
|
|
118
|
+
- Install
|
|
119
|
+
- Usage
|
|
120
|
+
- API
|
|
121
|
+
- Usage
|
|
122
|
+
- Examples
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Get one section.** Name the heading. The section runs to the next heading of the same or higher level, so a parent brings its children.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
$ surf guide.md "API"
|
|
129
|
+
## API
|
|
130
|
+
|
|
131
|
+
### Usage
|
|
132
|
+
|
|
133
|
+
API usage text.
|
|
134
|
+
|
|
135
|
+
#### Examples
|
|
136
|
+
|
|
137
|
+
Deep example.
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Pick between two headings with the same name.** Write the path from the parent. A bare name matches the first occurrence in the file.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
$ surf guide.md "API#Usage"
|
|
144
|
+
### Usage
|
|
145
|
+
|
|
146
|
+
API usage text.
|
|
147
|
+
|
|
148
|
+
#### Examples
|
|
149
|
+
|
|
150
|
+
Deep example.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**List only the top N levels.** `--level N` on a listing shows ranks 1 through N.
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
$ surf guide.md --list --level 2
|
|
157
|
+
- Guide
|
|
158
|
+
- Install
|
|
159
|
+
- API
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Extract only at an exact level.** `--level N` on an extract matches rank N and nothing else. `Usage` exists only at rank 3, so rank 2 is not found.
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
$ surf guide.md "Examples" --level 4
|
|
166
|
+
#### Examples
|
|
167
|
+
|
|
168
|
+
Deep example.
|
|
169
|
+
|
|
170
|
+
$ surf guide.md "Usage" --level 2
|
|
171
|
+
Error: heading "Usage" not found in guide.md.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Use a link as the address.** Obsidian wikilinks and markdown links resolve directly, `#` segments included.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
surf "[[guide#API#Usage]]"
|
|
178
|
+
surf "[API](guide.md#API)"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Frontmatter only, or headings only.** `-f` prints the YAML block and nothing else. `--list` prints the tree and no YAML. `--full` prints the YAML and then the named section.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
surf -f guide.md
|
|
185
|
+
surf --list guide.md
|
|
186
|
+
surf guide.md "Install" --full
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Drop the heading line, or write to a file.**
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
surf guide.md "API" --no-heading
|
|
193
|
+
surf guide.md "API" -o api.md
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**What counts as a heading.** On markdown, the ATX heading text. On TeX, the brace title of a sectioning command, plus `abstract` for the abstract environment; `\input` and `\include` of `.tex` files expand relative to the file you name, while graphics, comments, shell pipes, absolute paths, `..` segments, and macro-built paths do not. On PDF, an outline bookmark title; the extract is the page text from that bookmark to the next, and when the next bookmark shares a page, that page is included. Rank 1 is `#` on markdown, the shallowest sectioning command in the TeX file (`\section` in an article), and the outline's top rank on PDF.
|
|
197
|
+
|
|
198
|
+
Matching ignores case. `surf guide` resolves to `guide.md`; `.tex` and `.pdf` need their suffix. Any other UTF-8 file is addressed as ATX markdown.
|
|
199
|
+
|
|
200
|
+
**When there is nothing to address.** A file with no headings, or a PDF with no outline, prints `no structural index` and line and byte counts (page and byte counts on PDF), and exits 0. A heading that is not there exits 1 with `Error: heading "X" not found in guide.md.` A missing file or bad usage exits 2.
|
|
201
|
+
|
|
202
|
+
**Scan a directory.** Frontmatter across many files, no body loaded:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
ls docs/*.md | xargs -I {} surf -f {}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`surf --help` lists every flag.
|
|
209
|
+
|
|
210
|
+
## Agent skill
|
|
211
|
+
|
|
212
|
+
`plugins/surf` packages a skill that teaches an agent how to use surf. It carries a Claude Code manifest, an Agent Plugins 1.0.0 manifest, and the skill itself at `plugins/surf/skills/surf`, in the Agent Skills format that Claude Code, Codex, Gemini CLI, Cursor, and Grok Build read. The skill shells out to `surf`, so install the CLI first.
|
|
213
|
+
|
|
214
|
+
Claude Code installs it from the marketplace in this repo:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
claude plugin marketplace add saintx/surf-cli
|
|
218
|
+
claude plugin install surf@surf-cli
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Any harness that reads a skills directory takes a copy:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
cp -R plugins/surf/skills/surf ~/.agents/skills/surf
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This checkout links `.agents/skills/surf`, `.claude/skills/surf`, and `.grok/skills/surf` into the plugin, so an agent working in the repo already has it.
|
|
228
|
+
|
|
229
|
+
The paper in `plugins/surf/skills/surf/references/surf.pdf` (also `surf.tex`) is about what surf can be used to build. surf itself is the primitive.
|
|
230
|
+
|
|
231
|
+
## Contributing
|
|
232
|
+
|
|
233
|
+
Development setup, tests, and lint are in [CONTRIBUTING.md](https://github.com/saintx/surf-cli/blob/main/CONTRIBUTING.md). The release procedure is in [RELEASING.md](https://github.com/saintx/surf-cli/blob/main/RELEASING.md).
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
[MIT](https://github.com/saintx/surf-cli/blob/main/LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "surf",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Extract a markdown heading, TeX section, or PDF outline item without loading the rest of the file. Skill for agents using the surf CLI.",
|
|
5
5
|
"author": { "name": "Alexander R. Saint Croix", "email": "alex@saintx.us" },
|
|
6
6
|
"homepage": "https://github.com/saintx/surf-cli",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "surf",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.1",
|
|
5
5
|
"description": "Extract a markdown heading, TeX section, or PDF outline item without loading the rest of the file. Skill for agents using the surf CLI.",
|
|
6
6
|
"author": { "name": "Alexander R. Saint Croix", "email": "alex@saintx.us", "url": "https://github.com/saintx" },
|
|
7
7
|
"homepage": "https://github.com/saintx/surf-cli",
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "surf-cli"
|
|
7
|
-
version = "0.7.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.7.1"
|
|
8
|
+
description = "List headings, or extract one section by name, from Markdown, TeX, or PDF."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
11
11
|
authors = [{ name = "Alexander R. Saint Croix", email = "alex@saintx.us" }]
|
|
@@ -41,6 +41,8 @@ from surf.models import (
|
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$")
|
|
44
|
+
_FENCE_OPEN_RE = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$")
|
|
45
|
+
_FENCE_CLOSE_RE = re.compile(r"^ {0,3}(`{3,}|~{3,})\s*$")
|
|
44
46
|
_TEX_LEVEL: Mapping[TexCommand, HeadingLevel] = {
|
|
45
47
|
TexCommand.PART: HeadingLevel(1),
|
|
46
48
|
TexCommand.CHAPTER: HeadingLevel(2),
|
|
@@ -108,9 +110,42 @@ def parse_link(target: CliTarget) -> ParsedLink:
|
|
|
108
110
|
return ParsedLink(file_ref=FileRef(stripped) if stripped else None, heading_path=None)
|
|
109
111
|
|
|
110
112
|
|
|
113
|
+
def _fence_open(line: str) -> str | None:
|
|
114
|
+
"""Return the fence string when line opens a fenced code block, else None.
|
|
115
|
+
|
|
116
|
+
CommonMark: up to three spaces of indent, then three or more backticks or
|
|
117
|
+
tildes. A backtick fence's info string may not contain a backtick.
|
|
118
|
+
"""
|
|
119
|
+
m = _FENCE_OPEN_RE.match(line)
|
|
120
|
+
if m is None:
|
|
121
|
+
return None
|
|
122
|
+
fence, info = m.group(1), m.group(2)
|
|
123
|
+
if fence[0] == "`" and "`" in info:
|
|
124
|
+
return None
|
|
125
|
+
return fence
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _fence_closes(line: str, open_fence: str) -> bool:
|
|
129
|
+
"""A closing fence uses the same character and is at least as long."""
|
|
130
|
+
m = _FENCE_CLOSE_RE.match(line)
|
|
131
|
+
if m is None:
|
|
132
|
+
return False
|
|
133
|
+
fence = m.group(1)
|
|
134
|
+
return fence[0] == open_fence[0] and len(fence) >= len(open_fence)
|
|
135
|
+
|
|
136
|
+
|
|
111
137
|
def parse_headings(lines: Sequence[str]) -> tuple[HeadingRecord, ...]:
|
|
138
|
+
"""ATX headings outside fenced code blocks. A `#` line inside a fence is code."""
|
|
112
139
|
records: list[HeadingRecord] = []
|
|
140
|
+
open_fence: str | None = None
|
|
113
141
|
for i, line in enumerate(lines):
|
|
142
|
+
if open_fence is not None:
|
|
143
|
+
if _fence_closes(line, open_fence):
|
|
144
|
+
open_fence = None
|
|
145
|
+
continue
|
|
146
|
+
open_fence = _fence_open(line)
|
|
147
|
+
if open_fence is not None:
|
|
148
|
+
continue
|
|
114
149
|
m = _HEADING_RE.match(line)
|
|
115
150
|
if m:
|
|
116
151
|
records.append(
|
|
@@ -537,3 +537,60 @@ def test_format_empty_pdf_index_reports_pages_and_bytes() -> None:
|
|
|
537
537
|
"pages: 18",
|
|
538
538
|
"bytes: 13153200",
|
|
539
539
|
]
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
FENCED_MD = """# Doc
|
|
543
|
+
|
|
544
|
+
## Real
|
|
545
|
+
|
|
546
|
+
```markdown
|
|
547
|
+
# Not a heading
|
|
548
|
+
## Also not
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
~~~
|
|
552
|
+
### Tilde fenced
|
|
553
|
+
~~~
|
|
554
|
+
|
|
555
|
+
````md
|
|
556
|
+
```
|
|
557
|
+
# Nested shorter fence stays open
|
|
558
|
+
```
|
|
559
|
+
````
|
|
560
|
+
|
|
561
|
+
## After
|
|
562
|
+
|
|
563
|
+
# Indented code, not a heading
|
|
564
|
+
|
|
565
|
+
```
|
|
566
|
+
# Unclosed fence runs to the end
|
|
567
|
+
## Still code
|
|
568
|
+
"""
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def test_parse_headings_skips_fenced_code_blocks() -> None:
|
|
572
|
+
headings = parse_headings(FENCED_MD.splitlines())
|
|
573
|
+
assert [str(record.text) for record in headings] == ["Doc", "Real", "After"]
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def test_extract_section_keeps_fenced_hash_lines_in_body() -> None:
|
|
577
|
+
lines = FENCED_MD.splitlines()
|
|
578
|
+
result = extract_section(lines, hp("Real"))
|
|
579
|
+
assert result is not None
|
|
580
|
+
body = "\n".join(result.lines)
|
|
581
|
+
assert "# Not a heading" in body
|
|
582
|
+
assert "### Tilde fenced" in body
|
|
583
|
+
assert "# Nested shorter fence stays open" in body
|
|
584
|
+
assert "## After" not in body
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def test_backtick_fence_info_string_with_backtick_is_not_a_fence() -> None:
|
|
588
|
+
lines = ["# Top", "``` not `a` fence", "## Heading", "```"]
|
|
589
|
+
headings = parse_headings(lines)
|
|
590
|
+
assert [str(record.text) for record in headings] == ["Top", "Heading"]
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def test_closing_fence_must_match_char_and_length() -> None:
|
|
594
|
+
lines = ["# Top", "````", "~~~~", "```", "## Inside", "````", "## Outside"]
|
|
595
|
+
headings = parse_headings(lines)
|
|
596
|
+
assert [str(record.text) for record in headings] == ["Top", "Outside"]
|
surf_cli-0.7.0/PKG-INFO
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: surf-cli
|
|
3
|
-
Version: 0.7.0
|
|
4
|
-
Summary: Extract markdown, TeX, or PDF outline sections by heading with Obsidian link support
|
|
5
|
-
Project-URL: Homepage, https://github.com/saintx/surf-cli
|
|
6
|
-
Project-URL: Repository, https://github.com/saintx/surf-cli
|
|
7
|
-
Project-URL: Issues, https://github.com/saintx/surf-cli/issues
|
|
8
|
-
Project-URL: Author, https://github.com/saintx
|
|
9
|
-
Author-email: "Alexander R. Saint Croix" <alex@saintx.us>
|
|
10
|
-
License: MIT License
|
|
11
|
-
|
|
12
|
-
Copyright (c) 2026 Alexander R. Saint Croix
|
|
13
|
-
|
|
14
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
-
in the Software without restriction, including without limitation the rights
|
|
17
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
-
furnished to do so, subject to the following conditions:
|
|
20
|
-
|
|
21
|
-
The above copyright notice and this permission notice shall be included in all
|
|
22
|
-
copies or substantial portions of the Software.
|
|
23
|
-
|
|
24
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
-
SOFTWARE.
|
|
31
|
-
License-File: LICENSE
|
|
32
|
-
Requires-Python: >=3.12
|
|
33
|
-
Requires-Dist: pypdf>=6.17.0
|
|
34
|
-
Description-Content-Type: text/markdown
|
|
35
|
-
|
|
36
|
-
# surf
|
|
37
|
-
|
|
38
|
-
Extract one section of a markdown, TeX, or PDF file by its heading. The rest of the file never loads.
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
uv tool install surf-cli
|
|
42
|
-
cd plugins/surf/skills/surf/references
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
A file with no heading named returns the map, frontmatter and then headings, and none of the body:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
$ surf about.md
|
|
49
|
-
---
|
|
50
|
-
metadata:
|
|
51
|
-
author:
|
|
52
|
-
name: Alexander R. Saint Croix
|
|
53
|
-
github_username: saintx
|
|
54
|
-
email: alex@saintx.us
|
|
55
|
-
twitter: alexsaintx
|
|
56
|
-
surf-version: "0.7.0"
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
- Surf — About
|
|
60
|
-
- Overview
|
|
61
|
-
- When to use
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
A heading returns that section and stops at the next heading of the same or higher level:
|
|
65
|
-
|
|
66
|
-
```
|
|
67
|
-
$ surf about.md "When to use"
|
|
68
|
-
## When to use
|
|
69
|
-
|
|
70
|
-
Invoke when skimming markdown, TeX, or PDF files, checking what a file contains,
|
|
71
|
-
listing structure, extracting a named address, or batch-scanning metadata across
|
|
72
|
-
a directory. See `surf --help` for CLI flags. Skip when the full body is already
|
|
73
|
-
needed. ...
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
The same two commands work on TeX, addressed by sectioning commands and the `abstract` environment, and on PDF, addressed by outline bookmarks. The whitepaper in this directory ships in both forms and lists the same tree from either:
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
$ surf surf.tex --list
|
|
80
|
-
- abstract
|
|
81
|
-
- Background
|
|
82
|
-
- A Thin Index Shaped by Intent
|
|
83
|
-
- Agentic Context Composition
|
|
84
|
-
- Skills You Can Check
|
|
85
|
-
- Indexes over Indexes
|
|
86
|
-
- Markdown, TeX, and PDF
|
|
87
|
-
- This Paper
|
|
88
|
-
|
|
89
|
-
$ surf surf.pdf "Skills You Can Check"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
surf exists so that agent skills can be thin. The `SKILL.md` beside these files is a table of intents, each resolving to `surf path "Heading"`, over reference material the agent never reads whole. `surf.pdf` explains why the tool was built and what that pattern makes possible.
|
|
93
|
-
|
|
94
|
-
## Addressing
|
|
95
|
-
|
|
96
|
-
`surf --list file.md` is the heading tree without YAML. `surf -f file.md` is YAML only. Nested paths (`Overview#Usage`) select a child when the same name appears under different parents.
|
|
97
|
-
|
|
98
|
-
A heading is an address: ATX display text on markdown, brace title on TeX, the TeX `abstract` environment (addressed as `abstract`), or an outline bookmark title on PDF. On markdown and TeX the return value is that section through the next heading of the same or higher level. On PDF it is dest-to-next-dest page text; when the next dest is on the same page, that dest page is included.
|
|
99
|
-
|
|
100
|
-
`--level` is 1 at the top of the heading tree. On markdown that is `#`. On TeX it is the shallowest command in the file, so `--level 1` is `\section` in an article. On PDF it is the outline's native rank. Listing with `--level N` includes ranks 1 through N. Named extract uses N as an exact match.
|
|
101
|
-
|
|
102
|
-
TeX `\input` and `\include` of `.tex` files are expanded relative to the file you name. Graphics, comments, shell pipes, and macro-constructed paths are not.
|
|
103
|
-
|
|
104
|
-
If the file has no headings, or a PDF has no outline, surf prints that it has no structural index, with line and byte counts on markdown and TeX, or page and byte counts on PDF. It does not dump the body.
|
|
105
|
-
|
|
106
|
-
Python 3.12+. pypdf is the runtime dependency for PDF outline addressing.
|
|
107
|
-
|
|
108
|
-
## Agent skill
|
|
109
|
-
|
|
110
|
-
`plugins/surf` packages the skill for agents. It carries a Claude Code manifest, an Agent Plugins 1.0.0 manifest, and the skill itself at `plugins/surf/skills/surf`, in the Agent Skills format that Claude Code, Codex, Gemini CLI, Cursor, and Grok Build read.
|
|
111
|
-
|
|
112
|
-
Claude Code installs it from the marketplace in this repo:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
claude plugin marketplace add saintx/surf-cli
|
|
116
|
-
claude plugin install surf@surf-cli
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Any harness that reads a skills directory takes a copy of the skill:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
cp -R plugins/surf/skills/surf ~/.agents/skills/surf
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
The repo also carries `.agents/skills/surf`, `.claude/skills/surf`, and `.grok/skills/surf` as symlinks into the plugin, so an agent working in this checkout has the skill available.
|
|
126
|
-
|
|
127
|
-
## Install
|
|
128
|
-
|
|
129
|
-
The distribution name is `surf-cli`. The command is `surf`. Python 3.12+.
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
uv tool install surf-cli
|
|
133
|
-
surf --version
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
pipx install surf-cli
|
|
138
|
-
surf --version
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
pip install surf-cli
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Nix
|
|
146
|
-
|
|
147
|
-
From a local clone, install the flake into the nix profile:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
nix profile install "git+file://${PWD}"
|
|
151
|
-
surf --version
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Pin a tagged release with `?ref=X.Y.Z`. If `surf` is already in the profile under a different flake URL, remove it first:
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
nix profile remove surf
|
|
158
|
-
nix profile install "git+file://${PWD}?ref=X.Y.Z"
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
`scripts/deploy.sh` syncs, tests, and retargets the profile at the latest local semver tag.
|
|
162
|
-
|
|
163
|
-
## Releasing
|
|
164
|
-
|
|
165
|
-
Version locations, the bump checklist, tagging, and nix profile deployment are in [RELEASING.md](RELEASING.md).
|
|
166
|
-
|
|
167
|
-
## Development
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
nix develop
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
## Tests
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
scripts/test.sh # uv run pytest src/surf
|
|
177
|
-
```
|
surf_cli-0.7.0/README.md
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# surf
|
|
2
|
-
|
|
3
|
-
Extract one section of a markdown, TeX, or PDF file by its heading. The rest of the file never loads.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
uv tool install surf-cli
|
|
7
|
-
cd plugins/surf/skills/surf/references
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
A file with no heading named returns the map, frontmatter and then headings, and none of the body:
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
$ surf about.md
|
|
14
|
-
---
|
|
15
|
-
metadata:
|
|
16
|
-
author:
|
|
17
|
-
name: Alexander R. Saint Croix
|
|
18
|
-
github_username: saintx
|
|
19
|
-
email: alex@saintx.us
|
|
20
|
-
twitter: alexsaintx
|
|
21
|
-
surf-version: "0.7.0"
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
- Surf — About
|
|
25
|
-
- Overview
|
|
26
|
-
- When to use
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
A heading returns that section and stops at the next heading of the same or higher level:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
$ surf about.md "When to use"
|
|
33
|
-
## When to use
|
|
34
|
-
|
|
35
|
-
Invoke when skimming markdown, TeX, or PDF files, checking what a file contains,
|
|
36
|
-
listing structure, extracting a named address, or batch-scanning metadata across
|
|
37
|
-
a directory. See `surf --help` for CLI flags. Skip when the full body is already
|
|
38
|
-
needed. ...
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
The same two commands work on TeX, addressed by sectioning commands and the `abstract` environment, and on PDF, addressed by outline bookmarks. The whitepaper in this directory ships in both forms and lists the same tree from either:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
$ surf surf.tex --list
|
|
45
|
-
- abstract
|
|
46
|
-
- Background
|
|
47
|
-
- A Thin Index Shaped by Intent
|
|
48
|
-
- Agentic Context Composition
|
|
49
|
-
- Skills You Can Check
|
|
50
|
-
- Indexes over Indexes
|
|
51
|
-
- Markdown, TeX, and PDF
|
|
52
|
-
- This Paper
|
|
53
|
-
|
|
54
|
-
$ surf surf.pdf "Skills You Can Check"
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
surf exists so that agent skills can be thin. The `SKILL.md` beside these files is a table of intents, each resolving to `surf path "Heading"`, over reference material the agent never reads whole. `surf.pdf` explains why the tool was built and what that pattern makes possible.
|
|
58
|
-
|
|
59
|
-
## Addressing
|
|
60
|
-
|
|
61
|
-
`surf --list file.md` is the heading tree without YAML. `surf -f file.md` is YAML only. Nested paths (`Overview#Usage`) select a child when the same name appears under different parents.
|
|
62
|
-
|
|
63
|
-
A heading is an address: ATX display text on markdown, brace title on TeX, the TeX `abstract` environment (addressed as `abstract`), or an outline bookmark title on PDF. On markdown and TeX the return value is that section through the next heading of the same or higher level. On PDF it is dest-to-next-dest page text; when the next dest is on the same page, that dest page is included.
|
|
64
|
-
|
|
65
|
-
`--level` is 1 at the top of the heading tree. On markdown that is `#`. On TeX it is the shallowest command in the file, so `--level 1` is `\section` in an article. On PDF it is the outline's native rank. Listing with `--level N` includes ranks 1 through N. Named extract uses N as an exact match.
|
|
66
|
-
|
|
67
|
-
TeX `\input` and `\include` of `.tex` files are expanded relative to the file you name. Graphics, comments, shell pipes, and macro-constructed paths are not.
|
|
68
|
-
|
|
69
|
-
If the file has no headings, or a PDF has no outline, surf prints that it has no structural index, with line and byte counts on markdown and TeX, or page and byte counts on PDF. It does not dump the body.
|
|
70
|
-
|
|
71
|
-
Python 3.12+. pypdf is the runtime dependency for PDF outline addressing.
|
|
72
|
-
|
|
73
|
-
## Agent skill
|
|
74
|
-
|
|
75
|
-
`plugins/surf` packages the skill for agents. It carries a Claude Code manifest, an Agent Plugins 1.0.0 manifest, and the skill itself at `plugins/surf/skills/surf`, in the Agent Skills format that Claude Code, Codex, Gemini CLI, Cursor, and Grok Build read.
|
|
76
|
-
|
|
77
|
-
Claude Code installs it from the marketplace in this repo:
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
claude plugin marketplace add saintx/surf-cli
|
|
81
|
-
claude plugin install surf@surf-cli
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Any harness that reads a skills directory takes a copy of the skill:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
cp -R plugins/surf/skills/surf ~/.agents/skills/surf
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
The repo also carries `.agents/skills/surf`, `.claude/skills/surf`, and `.grok/skills/surf` as symlinks into the plugin, so an agent working in this checkout has the skill available.
|
|
91
|
-
|
|
92
|
-
## Install
|
|
93
|
-
|
|
94
|
-
The distribution name is `surf-cli`. The command is `surf`. Python 3.12+.
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
uv tool install surf-cli
|
|
98
|
-
surf --version
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
pipx install surf-cli
|
|
103
|
-
surf --version
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
pip install surf-cli
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Nix
|
|
111
|
-
|
|
112
|
-
From a local clone, install the flake into the nix profile:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
nix profile install "git+file://${PWD}"
|
|
116
|
-
surf --version
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Pin a tagged release with `?ref=X.Y.Z`. If `surf` is already in the profile under a different flake URL, remove it first:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
nix profile remove surf
|
|
123
|
-
nix profile install "git+file://${PWD}?ref=X.Y.Z"
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
`scripts/deploy.sh` syncs, tests, and retargets the profile at the latest local semver tag.
|
|
127
|
-
|
|
128
|
-
## Releasing
|
|
129
|
-
|
|
130
|
-
Version locations, the bump checklist, tagging, and nix profile deployment are in [RELEASING.md](RELEASING.md).
|
|
131
|
-
|
|
132
|
-
## Development
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
nix develop
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Tests
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
scripts/test.sh # uv run pytest src/surf
|
|
142
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|