llm-api-scope 0.7.0__tar.gz → 0.9.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.
- llm_api_scope-0.9.0/PKG-INFO +211 -0
- llm_api_scope-0.9.0/README.md +162 -0
- llm_api_scope-0.9.0/apiscope/add/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/add/app.py +119 -0
- llm_api_scope-0.9.0/apiscope/add/constants.py +20 -0
- llm_api_scope-0.9.0/apiscope/add/context.py +12 -0
- llm_api_scope-0.9.0/apiscope/add/preflight.py +10 -0
- llm_api_scope-0.9.0/apiscope/add/schema.py +12 -0
- llm_api_scope-0.9.0/apiscope/app.py +89 -0
- llm_api_scope-0.9.0/apiscope/cache.py +267 -0
- llm_api_scope-0.9.0/apiscope/config.py +326 -0
- llm_api_scope-0.9.0/apiscope/constants.py +136 -0
- llm_api_scope-0.9.0/apiscope/content.py +42 -0
- llm_api_scope-0.9.0/apiscope/context.py +73 -0
- llm_api_scope-0.9.0/apiscope/errors.py +29 -0
- llm_api_scope-0.9.0/apiscope/gitignore.py +53 -0
- llm_api_scope-0.9.0/apiscope/list/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/list/app.py +177 -0
- llm_api_scope-0.9.0/apiscope/list/constants.py +18 -0
- llm_api_scope-0.9.0/apiscope/list/context.py +12 -0
- llm_api_scope-0.9.0/apiscope/list/preflight.py +8 -0
- llm_api_scope-0.9.0/apiscope/list/schema.py +11 -0
- llm_api_scope-0.9.0/apiscope/main.py +9 -0
- llm_api_scope-0.9.0/apiscope/output.py +303 -0
- llm_api_scope-0.9.0/apiscope/preflight.py +212 -0
- llm_api_scope-0.9.0/apiscope/read/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/read/app.py +233 -0
- llm_api_scope-0.9.0/apiscope/read/constants.py +29 -0
- llm_api_scope-0.9.0/apiscope/read/context.py +12 -0
- llm_api_scope-0.9.0/apiscope/read/preflight.py +8 -0
- llm_api_scope-0.9.0/apiscope/read/schema.py +9 -0
- llm_api_scope-0.9.0/apiscope/read_lib/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/read_lib/constants.py +49 -0
- llm_api_scope-0.9.0/apiscope/read_lib/errors.py +14 -0
- llm_api_scope-0.9.0/apiscope/read_lib/filesystem/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/read_lib/filesystem/reader.py +69 -0
- llm_api_scope-0.9.0/apiscope/read_lib/llmstxt/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/read_lib/llmstxt/reader.py +22 -0
- llm_api_scope-0.9.0/apiscope/read_lib/openapi/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/read_lib/openapi/constants.py +16 -0
- llm_api_scope-0.9.0/apiscope/read_lib/openapi/reader.py +108 -0
- llm_api_scope-0.9.0/apiscope/read_lib/protocols.py +18 -0
- llm_api_scope-0.9.0/apiscope/read_lib/registry.py +44 -0
- llm_api_scope-0.9.0/apiscope/read_lib/repo/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/read_lib/repo/reader.py +22 -0
- llm_api_scope-0.9.0/apiscope/read_lib/rfc/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/read_lib/rfc/reader.py +94 -0
- llm_api_scope-0.9.0/apiscope/read_lib/schema.py +52 -0
- llm_api_scope-0.9.0/apiscope/remove/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/remove/app.py +110 -0
- llm_api_scope-0.9.0/apiscope/remove/constants.py +17 -0
- llm_api_scope-0.9.0/apiscope/remove/context.py +12 -0
- llm_api_scope-0.9.0/apiscope/remove/preflight.py +10 -0
- llm_api_scope-0.9.0/apiscope/remove/schema.py +9 -0
- llm_api_scope-0.9.0/apiscope/schema.py +139 -0
- llm_api_scope-0.9.0/apiscope/skill/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/skill/app.py +21 -0
- llm_api_scope-0.9.0/apiscope/skill/constants.py +24 -0
- llm_api_scope-0.9.0/apiscope/skill/install/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/skill/install/app.py +121 -0
- llm_api_scope-0.9.0/apiscope/skill/install/constants.py +40 -0
- llm_api_scope-0.9.0/apiscope/skill/install/context.py +12 -0
- llm_api_scope-0.9.0/apiscope/skill/install/preflight.py +8 -0
- llm_api_scope-0.9.0/apiscope/skill/install/schema.py +9 -0
- llm_api_scope-0.9.0/apiscope/skill/show/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/skill/show/app.py +90 -0
- llm_api_scope-0.9.0/apiscope/skill/show/constants.py +46 -0
- llm_api_scope-0.9.0/apiscope/skill/show/context.py +12 -0
- llm_api_scope-0.9.0/apiscope/skill/show/preflight.py +8 -0
- llm_api_scope-0.9.0/apiscope/skill/show/schema.py +7 -0
- llm_api_scope-0.9.0/apiscope/source.py +154 -0
- llm_api_scope-0.9.0/apiscope/sync/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/errors.py +32 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/filesystem/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/filesystem/constants.py +9 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/filesystem/fetcher.py +54 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/llmstxt/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/llmstxt/fetcher.py +112 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/openapi/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/openapi/fetcher.py +32 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/protocols.py +15 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/registry.py +38 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/repo/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/repo/constants.py +22 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/repo/fetcher.py +115 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/rfc/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/rfc/fetcher.py +32 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/schema.py +21 -0
- llm_api_scope-0.9.0/apiscope/sync/_lib/transport.py +79 -0
- llm_api_scope-0.9.0/apiscope/sync/app.py +315 -0
- llm_api_scope-0.9.0/apiscope/sync/constants.py +72 -0
- llm_api_scope-0.9.0/apiscope/sync/context.py +11 -0
- llm_api_scope-0.9.0/apiscope/sync/preflight.py +32 -0
- llm_api_scope-0.9.0/apiscope/sync/schema.py +10 -0
- llm_api_scope-0.9.0/apiscope/usage.py +75 -0
- llm_api_scope-0.9.0/apiscope/view/__init__.py +1 -0
- llm_api_scope-0.9.0/apiscope/view/app.py +220 -0
- llm_api_scope-0.9.0/apiscope/view/constants.py +28 -0
- llm_api_scope-0.9.0/apiscope/view/context.py +12 -0
- llm_api_scope-0.9.0/apiscope/view/preflight.py +8 -0
- llm_api_scope-0.9.0/apiscope/view/schema.py +9 -0
- llm_api_scope-0.9.0/apiscope/view_lib/__init__.py +2 -0
- llm_api_scope-0.9.0/apiscope/view_lib/address.py +18 -0
- llm_api_scope-0.9.0/apiscope/view_lib/constants.py +47 -0
- llm_api_scope-0.9.0/apiscope/view_lib/errors.py +31 -0
- llm_api_scope-0.9.0/apiscope/view_lib/filesystem/viewer.py +114 -0
- llm_api_scope-0.9.0/apiscope/view_lib/hint.py +14 -0
- llm_api_scope-0.9.0/apiscope/view_lib/llmstxt/viewer.py +11 -0
- llm_api_scope-0.9.0/apiscope/view_lib/openapi/constants.py +19 -0
- llm_api_scope-0.9.0/apiscope/view_lib/openapi/viewer.py +194 -0
- llm_api_scope-0.9.0/apiscope/view_lib/protocols.py +10 -0
- llm_api_scope-0.9.0/apiscope/view_lib/registry.py +27 -0
- llm_api_scope-0.9.0/apiscope/view_lib/repo/viewer.py +11 -0
- llm_api_scope-0.9.0/apiscope/view_lib/rfc/text.py +3 -0
- llm_api_scope-0.9.0/apiscope/view_lib/rfc/viewer.py +59 -0
- llm_api_scope-0.9.0/apiscope/view_lib/rfc/xml.py +138 -0
- llm_api_scope-0.9.0/apiscope/view_lib/schema.py +56 -0
- llm_api_scope-0.9.0/apiscope/view_lib/tree.py +215 -0
- llm_api_scope-0.9.0/llm_api_scope.egg-info/PKG-INFO +211 -0
- llm_api_scope-0.9.0/llm_api_scope.egg-info/SOURCES.txt +127 -0
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/pyproject.toml +8 -2
- llm_api_scope-0.7.0/PKG-INFO +0 -95
- llm_api_scope-0.7.0/README.md +0 -46
- llm_api_scope-0.7.0/apiscope/config.py +0 -165
- llm_api_scope-0.7.0/apiscope/main.py +0 -117
- llm_api_scope-0.7.0/apiscope/openapi/__init__.py +0 -5
- llm_api_scope-0.7.0/apiscope/openapi/app.py +0 -161
- llm_api_scope-0.7.0/apiscope/openapi/fetch.py +0 -58
- llm_api_scope-0.7.0/apiscope/openapi/reader.py +0 -85
- llm_api_scope-0.7.0/apiscope/openapi/schema.py +0 -15
- llm_api_scope-0.7.0/apiscope/openapi/spec/__init__.py +0 -5
- llm_api_scope-0.7.0/apiscope/openapi/spec/app.py +0 -95
- llm_api_scope-0.7.0/apiscope/openapi/spec/schema.py +0 -7
- llm_api_scope-0.7.0/apiscope/repo/__init__.py +0 -6
- llm_api_scope-0.7.0/apiscope/repo/app.py +0 -152
- llm_api_scope-0.7.0/apiscope/repo/fetch.py +0 -132
- llm_api_scope-0.7.0/apiscope/repo/schema.py +0 -53
- llm_api_scope-0.7.0/apiscope/rfc/__init__.py +0 -6
- llm_api_scope-0.7.0/apiscope/rfc/app.py +0 -403
- llm_api_scope-0.7.0/apiscope/rfc/fetch.py +0 -31
- llm_api_scope-0.7.0/apiscope/rfc/parse_txt.py +0 -54
- llm_api_scope-0.7.0/apiscope/rfc/parse_xml.py +0 -93
- llm_api_scope-0.7.0/apiscope/rfc/schema.py +0 -130
- llm_api_scope-0.7.0/apiscope/rfc/search.py +0 -60
- llm_api_scope-0.7.0/apiscope/schema.py +0 -15
- llm_api_scope-0.7.0/apiscope/skill/__init__.py +0 -5
- llm_api_scope-0.7.0/apiscope/skill/_usage.py +0 -72
- llm_api_scope-0.7.0/apiscope/skill/app.py +0 -19
- llm_api_scope-0.7.0/apiscope/skill/docs.py +0 -111
- llm_api_scope-0.7.0/llm_api_scope.egg-info/PKG-INFO +0 -95
- llm_api_scope-0.7.0/llm_api_scope.egg-info/SOURCES.txt +0 -36
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/LICENSE +0 -0
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/apiscope/__init__.py +0 -0
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/dependency_links.txt +0 -0
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/entry_points.txt +0 -0
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/requires.txt +0 -0
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/top_level.txt +0 -0
- {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/setup.cfg +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-api-scope
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: read and cache structured documents from remote for LLM agents
|
|
5
|
+
Author-email: D7x7z49 <85430783+D7x7z49@users.noreply.github.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 D7x7z49
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/D7x7z49/llm-api-scope
|
|
29
|
+
Project-URL: Repository, https://github.com/D7x7z49/llm-api-scope.git
|
|
30
|
+
Project-URL: Issues, https://github.com/D7x7z49/llm-api-scope/issues
|
|
31
|
+
Keywords: agent-tool,document-reader,openapi,specification
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
37
|
+
Classifier: Topic :: Utilities
|
|
38
|
+
Classifier: Development Status :: 3 - Alpha
|
|
39
|
+
Classifier: Intended Audience :: Developers
|
|
40
|
+
Requires-Python: <4.0,>=3.12
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: typer>=0.26.3
|
|
44
|
+
Requires-Dist: pydantic>=2.13.4
|
|
45
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
46
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
47
|
+
Requires-Dist: httpx>=0.28.1
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# LLM API Scope (apiscope)
|
|
51
|
+
|
|
52
|
+
a tool for LLM agents to read and cache structured documents from remote.
|
|
53
|
+
|
|
54
|
+
## install
|
|
55
|
+
|
|
56
|
+
use [pipx](https://github.com/pypa/pipx) for isolated installation:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pipx install llm-api-scope
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## how it works
|
|
63
|
+
|
|
64
|
+
apiscope has two ideas: a registered source, and a tree you navigate by address.
|
|
65
|
+
text is the default output; `--json` prints the data layer instead.
|
|
66
|
+
|
|
67
|
+
### manage
|
|
68
|
+
|
|
69
|
+
`apiscope add` registers a source with a name, a location, and a type.
|
|
70
|
+
`apiscope remove` deletes it.
|
|
71
|
+
`apiscope list` shows what is registered, filtered by type.
|
|
72
|
+
|
|
73
|
+
five document types share one command surface:
|
|
74
|
+
|
|
75
|
+
- `filesystem` reads a local file or directory
|
|
76
|
+
- `repo` reads a directory inside a [git](https://git-scm.com) repository
|
|
77
|
+
- `openapi` reads an [OpenAPI](https://www.openapis.org) specification
|
|
78
|
+
- `rfc` reads an [IETF](https://www.ietf.org) document
|
|
79
|
+
- `llmstxt` reads a [site index](https://llmstxt.org) that lists documentation pages
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
apiscope add docs https://example.test/docs/llms.txt --type llmstxt
|
|
83
|
+
apiscope list all
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### use
|
|
87
|
+
|
|
88
|
+
`apiscope sync` fetches sources into a local cache.
|
|
89
|
+
`apiscope view` shows the cached structure, and `apiscope read` reads one node.
|
|
90
|
+
|
|
91
|
+
view and read share the same address: a source name plus an optional route.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
apiscope sync all
|
|
95
|
+
apiscope view docs
|
|
96
|
+
apiscope read docs 1.2
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
every source projects into one tree.
|
|
100
|
+
an ordinary node has children; a leaf has none.
|
|
101
|
+
a view line is `- [index] key: description`, where the index is a tree address.
|
|
102
|
+
an index from view always works with read.
|
|
103
|
+
|
|
104
|
+
### skill
|
|
105
|
+
|
|
106
|
+
`apiscope skill` prints or installs a combined command reference and strategy guide for AI agents.
|
|
107
|
+
run `apiscope skill show` to print it, or `apiscope skill install` to install it under `~/.agents/skills/apiscope`.
|
|
108
|
+
|
|
109
|
+
agents read this once at onboarding instead of running `--help` repeatedly.
|
|
110
|
+
|
|
111
|
+
### configuration
|
|
112
|
+
|
|
113
|
+
apiscope reads three configuration layers in order.
|
|
114
|
+
the global file is `~/.apiscope/config.json`.
|
|
115
|
+
the project file is `.apiscope/config.json`, and the local file is `.apiscope/local.json`.
|
|
116
|
+
`--global` uses the global layer only and skips project discovery.
|
|
117
|
+
|
|
118
|
+
the public setting holds the default source ttl in days.
|
|
119
|
+
the local setting holds the proxy.
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"setting": {
|
|
124
|
+
"public": {"doc_ttl": 7},
|
|
125
|
+
"local": {"proxy": "http://proxy.example.test:8080"}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
the cache lives in the app directory of the selected layer, and the rest of its rules sit under `## commands`.
|
|
131
|
+
|
|
132
|
+
## commands
|
|
133
|
+
|
|
134
|
+
### add
|
|
135
|
+
|
|
136
|
+
register a source:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
apiscope add <name> <source> --type <type> [--ttl <days>]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### remove
|
|
143
|
+
|
|
144
|
+
delete a source:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
apiscope remove <name>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### list
|
|
151
|
+
|
|
152
|
+
list registered sources:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
apiscope list <selector> [--limit <n>] [--offset <n>]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
the selector is one of all, filesystem, repo, openapi, rfc, or llmstxt.
|
|
159
|
+
|
|
160
|
+
### sync
|
|
161
|
+
|
|
162
|
+
fetch sources into the cache:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
apiscope sync <selector> [<name>] [--force]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
the selector is the same as in list, and the optional name narrows the range to one source.
|
|
169
|
+
a source refreshes when its cache is older than its ttl, and `--force` ignores the ttl.
|
|
170
|
+
`repo` clones with shallow depth, a blobless filter, and sparse checkout.
|
|
171
|
+
`llmstxt` reads the index page, downloads the pages it lists, and skips a failed page.
|
|
172
|
+
|
|
173
|
+
### view
|
|
174
|
+
|
|
175
|
+
show the cached structure of an address:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
apiscope view <address> [--depth <n>]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
depth caps the levels below the scope; the default is unlimited.
|
|
182
|
+
|
|
183
|
+
### read
|
|
184
|
+
|
|
185
|
+
read one node:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
apiscope read <address> [<index>]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
the index is optional when the address already points at a leaf.
|
|
192
|
+
|
|
193
|
+
### skill
|
|
194
|
+
|
|
195
|
+
print or install the agent skill:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
apiscope skill show
|
|
199
|
+
apiscope skill install [<target>]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
install defaults to `~/.agents/skills/apiscope`.
|
|
203
|
+
|
|
204
|
+
## future
|
|
205
|
+
|
|
206
|
+
- read academic papers from arxiv
|
|
207
|
+
- more formal document formats as the need arises
|
|
208
|
+
|
|
209
|
+
## license
|
|
210
|
+
|
|
211
|
+
MIT
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# LLM API Scope (apiscope)
|
|
2
|
+
|
|
3
|
+
a tool for LLM agents to read and cache structured documents from remote.
|
|
4
|
+
|
|
5
|
+
## install
|
|
6
|
+
|
|
7
|
+
use [pipx](https://github.com/pypa/pipx) for isolated installation:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pipx install llm-api-scope
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## how it works
|
|
14
|
+
|
|
15
|
+
apiscope has two ideas: a registered source, and a tree you navigate by address.
|
|
16
|
+
text is the default output; `--json` prints the data layer instead.
|
|
17
|
+
|
|
18
|
+
### manage
|
|
19
|
+
|
|
20
|
+
`apiscope add` registers a source with a name, a location, and a type.
|
|
21
|
+
`apiscope remove` deletes it.
|
|
22
|
+
`apiscope list` shows what is registered, filtered by type.
|
|
23
|
+
|
|
24
|
+
five document types share one command surface:
|
|
25
|
+
|
|
26
|
+
- `filesystem` reads a local file or directory
|
|
27
|
+
- `repo` reads a directory inside a [git](https://git-scm.com) repository
|
|
28
|
+
- `openapi` reads an [OpenAPI](https://www.openapis.org) specification
|
|
29
|
+
- `rfc` reads an [IETF](https://www.ietf.org) document
|
|
30
|
+
- `llmstxt` reads a [site index](https://llmstxt.org) that lists documentation pages
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
apiscope add docs https://example.test/docs/llms.txt --type llmstxt
|
|
34
|
+
apiscope list all
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### use
|
|
38
|
+
|
|
39
|
+
`apiscope sync` fetches sources into a local cache.
|
|
40
|
+
`apiscope view` shows the cached structure, and `apiscope read` reads one node.
|
|
41
|
+
|
|
42
|
+
view and read share the same address: a source name plus an optional route.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
apiscope sync all
|
|
46
|
+
apiscope view docs
|
|
47
|
+
apiscope read docs 1.2
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
every source projects into one tree.
|
|
51
|
+
an ordinary node has children; a leaf has none.
|
|
52
|
+
a view line is `- [index] key: description`, where the index is a tree address.
|
|
53
|
+
an index from view always works with read.
|
|
54
|
+
|
|
55
|
+
### skill
|
|
56
|
+
|
|
57
|
+
`apiscope skill` prints or installs a combined command reference and strategy guide for AI agents.
|
|
58
|
+
run `apiscope skill show` to print it, or `apiscope skill install` to install it under `~/.agents/skills/apiscope`.
|
|
59
|
+
|
|
60
|
+
agents read this once at onboarding instead of running `--help` repeatedly.
|
|
61
|
+
|
|
62
|
+
### configuration
|
|
63
|
+
|
|
64
|
+
apiscope reads three configuration layers in order.
|
|
65
|
+
the global file is `~/.apiscope/config.json`.
|
|
66
|
+
the project file is `.apiscope/config.json`, and the local file is `.apiscope/local.json`.
|
|
67
|
+
`--global` uses the global layer only and skips project discovery.
|
|
68
|
+
|
|
69
|
+
the public setting holds the default source ttl in days.
|
|
70
|
+
the local setting holds the proxy.
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"setting": {
|
|
75
|
+
"public": {"doc_ttl": 7},
|
|
76
|
+
"local": {"proxy": "http://proxy.example.test:8080"}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
the cache lives in the app directory of the selected layer, and the rest of its rules sit under `## commands`.
|
|
82
|
+
|
|
83
|
+
## commands
|
|
84
|
+
|
|
85
|
+
### add
|
|
86
|
+
|
|
87
|
+
register a source:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
apiscope add <name> <source> --type <type> [--ttl <days>]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### remove
|
|
94
|
+
|
|
95
|
+
delete a source:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
apiscope remove <name>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### list
|
|
102
|
+
|
|
103
|
+
list registered sources:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
apiscope list <selector> [--limit <n>] [--offset <n>]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
the selector is one of all, filesystem, repo, openapi, rfc, or llmstxt.
|
|
110
|
+
|
|
111
|
+
### sync
|
|
112
|
+
|
|
113
|
+
fetch sources into the cache:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
apiscope sync <selector> [<name>] [--force]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
the selector is the same as in list, and the optional name narrows the range to one source.
|
|
120
|
+
a source refreshes when its cache is older than its ttl, and `--force` ignores the ttl.
|
|
121
|
+
`repo` clones with shallow depth, a blobless filter, and sparse checkout.
|
|
122
|
+
`llmstxt` reads the index page, downloads the pages it lists, and skips a failed page.
|
|
123
|
+
|
|
124
|
+
### view
|
|
125
|
+
|
|
126
|
+
show the cached structure of an address:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
apiscope view <address> [--depth <n>]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
depth caps the levels below the scope; the default is unlimited.
|
|
133
|
+
|
|
134
|
+
### read
|
|
135
|
+
|
|
136
|
+
read one node:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
apiscope read <address> [<index>]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
the index is optional when the address already points at a leaf.
|
|
143
|
+
|
|
144
|
+
### skill
|
|
145
|
+
|
|
146
|
+
print or install the agent skill:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
apiscope skill show
|
|
150
|
+
apiscope skill install [<target>]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
install defaults to `~/.agents/skills/apiscope`.
|
|
154
|
+
|
|
155
|
+
## future
|
|
156
|
+
|
|
157
|
+
- read academic papers from arxiv
|
|
158
|
+
- more formal document formats as the need arises
|
|
159
|
+
|
|
160
|
+
## license
|
|
161
|
+
|
|
162
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# apiscope/add/__init__.py
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# apiscope/add/app.py
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from pydantic import BaseModel, ValidationError
|
|
8
|
+
|
|
9
|
+
from apiscope.add.constants import COMMAND_NAME, MESSAGE_TEMPLATES
|
|
10
|
+
from apiscope.add.context import AddCommandContext
|
|
11
|
+
from apiscope.add.preflight import run_preflight
|
|
12
|
+
from apiscope.add.schema import AddOptions
|
|
13
|
+
from apiscope.config import (
|
|
14
|
+
ConfigError,
|
|
15
|
+
ensure_config_file,
|
|
16
|
+
extract_config_sources,
|
|
17
|
+
save_config_file,
|
|
18
|
+
with_config_sources,
|
|
19
|
+
)
|
|
20
|
+
from apiscope.constants import CONFIG_SCHEMA_REF
|
|
21
|
+
from apiscope.constants import MESSAGE_TEMPLATES as ROOT_MESSAGE_TEMPLATES
|
|
22
|
+
from apiscope.context import RuntimeContext
|
|
23
|
+
from apiscope.errors import MessageError
|
|
24
|
+
from apiscope.output import Report, emit_report
|
|
25
|
+
from apiscope.schema import SOURCE_SELECTOR_ALL, DocumentType, GlobalConfigFile, ProjectConfigFile, RuntimeSource
|
|
26
|
+
|
|
27
|
+
_MESSAGE_TEMPLATES = {**ROOT_MESSAGE_TEMPLATES, **MESSAGE_TEMPLATES}
|
|
28
|
+
|
|
29
|
+
app = typer.Typer(
|
|
30
|
+
name=COMMAND_NAME,
|
|
31
|
+
help=MESSAGE_TEMPLATES["add.help.command"],
|
|
32
|
+
subcommand_metavar="",
|
|
33
|
+
context_settings={"allow_interspersed_args": True},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.callback(invoke_without_command=True)
|
|
38
|
+
def main_callback(
|
|
39
|
+
ctx: typer.Context,
|
|
40
|
+
name: str = typer.Argument(..., help=MESSAGE_TEMPLATES["add.help.argument.name"]),
|
|
41
|
+
source: str = typer.Argument(..., help=MESSAGE_TEMPLATES["add.help.argument.source"]),
|
|
42
|
+
doc_type: str = typer.Option(..., "--type", help=MESSAGE_TEMPLATES["add.help.option.type"]),
|
|
43
|
+
ttl: int | None = typer.Option(None, "--ttl", help=MESSAGE_TEMPLATES["add.help.option.ttl"]),
|
|
44
|
+
) -> None:
|
|
45
|
+
runtime_context = ctx.find_object(RuntimeContext)
|
|
46
|
+
if runtime_context is None:
|
|
47
|
+
raise MessageError("add.error.runtime_context_unavailable")
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
if name == SOURCE_SELECTOR_ALL:
|
|
51
|
+
raise MessageError("add.error.reserved_name", {"name": name})
|
|
52
|
+
options = AddOptions(name=name, doc_type=cast(DocumentType, doc_type), doc_src=source, doc_ttl=ttl)
|
|
53
|
+
command_context = AddCommandContext(runtime=runtime_context, options=options)
|
|
54
|
+
run_preflight(command_context)
|
|
55
|
+
_add_source(command_context)
|
|
56
|
+
except ValidationError:
|
|
57
|
+
error = MessageError("add.error.invalid_options", {"name": name})
|
|
58
|
+
_emit_error(runtime_context, error)
|
|
59
|
+
raise typer.Exit(code=1) from error
|
|
60
|
+
except MessageError as error:
|
|
61
|
+
_emit_error(runtime_context, error)
|
|
62
|
+
raise typer.Exit(code=1) from error
|
|
63
|
+
|
|
64
|
+
emit_report(
|
|
65
|
+
Report(
|
|
66
|
+
status="ok",
|
|
67
|
+
scope=runtime_context.scope,
|
|
68
|
+
action=COMMAND_NAME,
|
|
69
|
+
meta={"name": options.name, "type": options.doc_type},
|
|
70
|
+
),
|
|
71
|
+
output_format=runtime_context.options.output_format,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _add_source(command_context: AddCommandContext) -> None:
|
|
76
|
+
runtime = command_context.runtime
|
|
77
|
+
path, model = _config_target(runtime)
|
|
78
|
+
try:
|
|
79
|
+
config_file = ensure_config_file(path, model, schema_ref=CONFIG_SCHEMA_REF)
|
|
80
|
+
except ConfigError as error:
|
|
81
|
+
raise MessageError("add.error.persistence.read_failed", {"path": str(path)}) from error
|
|
82
|
+
|
|
83
|
+
sources = extract_config_sources(config_file)
|
|
84
|
+
if command_context.options.name in sources:
|
|
85
|
+
raise MessageError("add.error.duplicate_name", {"name": command_context.options.name})
|
|
86
|
+
|
|
87
|
+
source = RuntimeSource(
|
|
88
|
+
doc_type=command_context.options.doc_type,
|
|
89
|
+
doc_src=command_context.options.doc_src,
|
|
90
|
+
doc_ttl=command_context.options.doc_ttl,
|
|
91
|
+
)
|
|
92
|
+
sources[command_context.options.name] = source
|
|
93
|
+
updated_config = with_config_sources(config_file, sources)
|
|
94
|
+
try:
|
|
95
|
+
save_config_file(path, updated_config)
|
|
96
|
+
except ConfigError as error:
|
|
97
|
+
raise MessageError("add.error.persistence.write_failed", {"path": str(path)}) from error
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _config_target(runtime: RuntimeContext) -> tuple[Path, type[BaseModel]]:
|
|
101
|
+
if runtime.options.global_only:
|
|
102
|
+
return runtime.paths.home.config, GlobalConfigFile
|
|
103
|
+
if runtime.paths.project is None:
|
|
104
|
+
raise MessageError("add.error.project_required")
|
|
105
|
+
return runtime.paths.project.config, ProjectConfigFile
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _emit_error(runtime: RuntimeContext, error: MessageError) -> None:
|
|
109
|
+
emit_report(
|
|
110
|
+
Report(
|
|
111
|
+
status="error",
|
|
112
|
+
scope=runtime.scope,
|
|
113
|
+
action=COMMAND_NAME,
|
|
114
|
+
code=error.code,
|
|
115
|
+
meta=error.values,
|
|
116
|
+
),
|
|
117
|
+
output_format=runtime.options.output_format,
|
|
118
|
+
message_templates=_MESSAGE_TEMPLATES,
|
|
119
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# apiscope/add/constants.py
|
|
2
|
+
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
COMMAND_NAME: Final = "add"
|
|
6
|
+
|
|
7
|
+
MESSAGE_TEMPLATES: Final[dict[str, str]] = {
|
|
8
|
+
"add.help.command": "add a source to the selected configuration",
|
|
9
|
+
"add.help.argument.name": "the source name",
|
|
10
|
+
"add.help.argument.source": "the source location",
|
|
11
|
+
"add.help.option.type": "the source document type",
|
|
12
|
+
"add.help.option.ttl": "the source cache TTL in days",
|
|
13
|
+
"add.error.runtime_context_unavailable": "runtime context is unavailable",
|
|
14
|
+
"add.error.invalid_options": ("source {name} has an invalid definition. check its type, location, and TTL"),
|
|
15
|
+
"add.error.project_required": "a Git project is required unless --global is used",
|
|
16
|
+
"add.error.duplicate_name": "source {name} already exists",
|
|
17
|
+
"add.error.reserved_name": "source name {name} is reserved",
|
|
18
|
+
"add.error.persistence.read_failed": "cannot read the configuration at {path}",
|
|
19
|
+
"add.error.persistence.write_failed": "cannot update the configuration at {path}",
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# apiscope/add/context.py
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from apiscope.add.schema import AddOptions
|
|
6
|
+
from apiscope.context import RuntimeContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class AddCommandContext:
|
|
11
|
+
runtime: RuntimeContext
|
|
12
|
+
options: AddOptions
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# apiscope/add/preflight.py
|
|
2
|
+
|
|
3
|
+
from apiscope.add.context import AddCommandContext
|
|
4
|
+
from apiscope.errors import MessageError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run_preflight(command_context: AddCommandContext) -> None:
|
|
8
|
+
runtime = command_context.runtime
|
|
9
|
+
if not runtime.options.global_only and runtime.paths.project is None:
|
|
10
|
+
raise MessageError("add.error.project_required")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# apiscope/add/schema.py
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, PositiveInt
|
|
4
|
+
|
|
5
|
+
from apiscope.schema import DocumentType, StrictSchemaModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AddOptions(StrictSchemaModel):
|
|
9
|
+
name: str = Field(min_length=1)
|
|
10
|
+
doc_type: DocumentType
|
|
11
|
+
doc_src: str = Field(min_length=1)
|
|
12
|
+
doc_ttl: PositiveInt | None = None
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# apiscope/app.py
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from apiscope.add.app import app as add_app
|
|
6
|
+
from apiscope.add.constants import COMMAND_NAME as ADD_COMMAND_NAME
|
|
7
|
+
from apiscope.constants import MESSAGE_TEMPLATES
|
|
8
|
+
from apiscope.context import RootOptions
|
|
9
|
+
from apiscope.list.app import app as list_app
|
|
10
|
+
from apiscope.list.constants import COMMAND_NAME as LIST_COMMAND_NAME
|
|
11
|
+
from apiscope.output import OutputFormat, Report, emit_report
|
|
12
|
+
from apiscope.preflight import PreflightError, run_preflight, run_read_preflight
|
|
13
|
+
from apiscope.read.app import app as read_app
|
|
14
|
+
from apiscope.read.constants import COMMAND_NAME as READ_COMMAND_NAME
|
|
15
|
+
from apiscope.remove.app import app as remove_app
|
|
16
|
+
from apiscope.remove.constants import COMMAND_NAME as REMOVE_COMMAND_NAME
|
|
17
|
+
from apiscope.schema import ConfigScope
|
|
18
|
+
from apiscope.skill.app import app as skill_app
|
|
19
|
+
from apiscope.skill.constants import COMMAND_NAME as SKILL_COMMAND_NAME
|
|
20
|
+
from apiscope.sync.app import app as sync_app
|
|
21
|
+
from apiscope.sync.constants import COMMAND_NAME as SYNC_COMMAND_NAME
|
|
22
|
+
from apiscope.view.app import app as view_app
|
|
23
|
+
from apiscope.view.constants import COMMAND_NAME as VIEW_COMMAND_NAME
|
|
24
|
+
|
|
25
|
+
# ==============================================================================
|
|
26
|
+
# app
|
|
27
|
+
# ==============================================================================
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
app = typer.Typer(
|
|
31
|
+
rich_markup_mode=None,
|
|
32
|
+
pretty_exceptions_enable=False,
|
|
33
|
+
help=MESSAGE_TEMPLATES["root.help.app"],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
app.add_typer(add_app, name=ADD_COMMAND_NAME)
|
|
37
|
+
app.add_typer(remove_app, name=REMOVE_COMMAND_NAME)
|
|
38
|
+
app.add_typer(read_app, name=READ_COMMAND_NAME)
|
|
39
|
+
app.add_typer(list_app, name=LIST_COMMAND_NAME)
|
|
40
|
+
app.add_typer(sync_app, name=SYNC_COMMAND_NAME)
|
|
41
|
+
app.add_typer(view_app, name=VIEW_COMMAND_NAME)
|
|
42
|
+
app.add_typer(skill_app, name=SKILL_COMMAND_NAME)
|
|
43
|
+
|
|
44
|
+
# write commands prepare assets; read commands only load and project
|
|
45
|
+
_WRITE_COMMANDS = {ADD_COMMAND_NAME, REMOVE_COMMAND_NAME, SYNC_COMMAND_NAME}
|
|
46
|
+
|
|
47
|
+
# ==============================================================================
|
|
48
|
+
# callback
|
|
49
|
+
# ==============================================================================
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.callback(invoke_without_command=True)
|
|
53
|
+
def main_callback(
|
|
54
|
+
ctx: typer.Context,
|
|
55
|
+
global_only: bool = typer.Option(
|
|
56
|
+
False,
|
|
57
|
+
"--global",
|
|
58
|
+
"-g",
|
|
59
|
+
help=MESSAGE_TEMPLATES["root.help.option.global"],
|
|
60
|
+
),
|
|
61
|
+
json_output: bool = typer.Option(
|
|
62
|
+
False,
|
|
63
|
+
"--json",
|
|
64
|
+
help=MESSAGE_TEMPLATES["root.help.option.json"],
|
|
65
|
+
),
|
|
66
|
+
) -> None:
|
|
67
|
+
output_format = OutputFormat.JSON if json_output else OutputFormat.TEXT
|
|
68
|
+
root_options = RootOptions(global_only=global_only, output_format=output_format)
|
|
69
|
+
try:
|
|
70
|
+
if ctx.invoked_subcommand in _WRITE_COMMANDS:
|
|
71
|
+
runtime_context = run_preflight(options=root_options)
|
|
72
|
+
else:
|
|
73
|
+
runtime_context = run_read_preflight(options=root_options)
|
|
74
|
+
except PreflightError as error:
|
|
75
|
+
emit_report(
|
|
76
|
+
Report(
|
|
77
|
+
status="error",
|
|
78
|
+
scope=ConfigScope.HOME if global_only else ConfigScope.PROJECT,
|
|
79
|
+
action="preflight",
|
|
80
|
+
code=error.code,
|
|
81
|
+
meta=error.values,
|
|
82
|
+
),
|
|
83
|
+
output_format=output_format,
|
|
84
|
+
message_templates=MESSAGE_TEMPLATES,
|
|
85
|
+
)
|
|
86
|
+
raise typer.Exit(code=1) from error
|
|
87
|
+
ctx.obj = runtime_context
|
|
88
|
+
if ctx.invoked_subcommand is None:
|
|
89
|
+
typer.echo(ctx.get_help())
|