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.
Files changed (159) hide show
  1. llm_api_scope-0.9.0/PKG-INFO +211 -0
  2. llm_api_scope-0.9.0/README.md +162 -0
  3. llm_api_scope-0.9.0/apiscope/add/__init__.py +1 -0
  4. llm_api_scope-0.9.0/apiscope/add/app.py +119 -0
  5. llm_api_scope-0.9.0/apiscope/add/constants.py +20 -0
  6. llm_api_scope-0.9.0/apiscope/add/context.py +12 -0
  7. llm_api_scope-0.9.0/apiscope/add/preflight.py +10 -0
  8. llm_api_scope-0.9.0/apiscope/add/schema.py +12 -0
  9. llm_api_scope-0.9.0/apiscope/app.py +89 -0
  10. llm_api_scope-0.9.0/apiscope/cache.py +267 -0
  11. llm_api_scope-0.9.0/apiscope/config.py +326 -0
  12. llm_api_scope-0.9.0/apiscope/constants.py +136 -0
  13. llm_api_scope-0.9.0/apiscope/content.py +42 -0
  14. llm_api_scope-0.9.0/apiscope/context.py +73 -0
  15. llm_api_scope-0.9.0/apiscope/errors.py +29 -0
  16. llm_api_scope-0.9.0/apiscope/gitignore.py +53 -0
  17. llm_api_scope-0.9.0/apiscope/list/__init__.py +1 -0
  18. llm_api_scope-0.9.0/apiscope/list/app.py +177 -0
  19. llm_api_scope-0.9.0/apiscope/list/constants.py +18 -0
  20. llm_api_scope-0.9.0/apiscope/list/context.py +12 -0
  21. llm_api_scope-0.9.0/apiscope/list/preflight.py +8 -0
  22. llm_api_scope-0.9.0/apiscope/list/schema.py +11 -0
  23. llm_api_scope-0.9.0/apiscope/main.py +9 -0
  24. llm_api_scope-0.9.0/apiscope/output.py +303 -0
  25. llm_api_scope-0.9.0/apiscope/preflight.py +212 -0
  26. llm_api_scope-0.9.0/apiscope/read/__init__.py +1 -0
  27. llm_api_scope-0.9.0/apiscope/read/app.py +233 -0
  28. llm_api_scope-0.9.0/apiscope/read/constants.py +29 -0
  29. llm_api_scope-0.9.0/apiscope/read/context.py +12 -0
  30. llm_api_scope-0.9.0/apiscope/read/preflight.py +8 -0
  31. llm_api_scope-0.9.0/apiscope/read/schema.py +9 -0
  32. llm_api_scope-0.9.0/apiscope/read_lib/__init__.py +1 -0
  33. llm_api_scope-0.9.0/apiscope/read_lib/constants.py +49 -0
  34. llm_api_scope-0.9.0/apiscope/read_lib/errors.py +14 -0
  35. llm_api_scope-0.9.0/apiscope/read_lib/filesystem/__init__.py +1 -0
  36. llm_api_scope-0.9.0/apiscope/read_lib/filesystem/reader.py +69 -0
  37. llm_api_scope-0.9.0/apiscope/read_lib/llmstxt/__init__.py +1 -0
  38. llm_api_scope-0.9.0/apiscope/read_lib/llmstxt/reader.py +22 -0
  39. llm_api_scope-0.9.0/apiscope/read_lib/openapi/__init__.py +1 -0
  40. llm_api_scope-0.9.0/apiscope/read_lib/openapi/constants.py +16 -0
  41. llm_api_scope-0.9.0/apiscope/read_lib/openapi/reader.py +108 -0
  42. llm_api_scope-0.9.0/apiscope/read_lib/protocols.py +18 -0
  43. llm_api_scope-0.9.0/apiscope/read_lib/registry.py +44 -0
  44. llm_api_scope-0.9.0/apiscope/read_lib/repo/__init__.py +1 -0
  45. llm_api_scope-0.9.0/apiscope/read_lib/repo/reader.py +22 -0
  46. llm_api_scope-0.9.0/apiscope/read_lib/rfc/__init__.py +1 -0
  47. llm_api_scope-0.9.0/apiscope/read_lib/rfc/reader.py +94 -0
  48. llm_api_scope-0.9.0/apiscope/read_lib/schema.py +52 -0
  49. llm_api_scope-0.9.0/apiscope/remove/__init__.py +1 -0
  50. llm_api_scope-0.9.0/apiscope/remove/app.py +110 -0
  51. llm_api_scope-0.9.0/apiscope/remove/constants.py +17 -0
  52. llm_api_scope-0.9.0/apiscope/remove/context.py +12 -0
  53. llm_api_scope-0.9.0/apiscope/remove/preflight.py +10 -0
  54. llm_api_scope-0.9.0/apiscope/remove/schema.py +9 -0
  55. llm_api_scope-0.9.0/apiscope/schema.py +139 -0
  56. llm_api_scope-0.9.0/apiscope/skill/__init__.py +1 -0
  57. llm_api_scope-0.9.0/apiscope/skill/app.py +21 -0
  58. llm_api_scope-0.9.0/apiscope/skill/constants.py +24 -0
  59. llm_api_scope-0.9.0/apiscope/skill/install/__init__.py +1 -0
  60. llm_api_scope-0.9.0/apiscope/skill/install/app.py +121 -0
  61. llm_api_scope-0.9.0/apiscope/skill/install/constants.py +40 -0
  62. llm_api_scope-0.9.0/apiscope/skill/install/context.py +12 -0
  63. llm_api_scope-0.9.0/apiscope/skill/install/preflight.py +8 -0
  64. llm_api_scope-0.9.0/apiscope/skill/install/schema.py +9 -0
  65. llm_api_scope-0.9.0/apiscope/skill/show/__init__.py +1 -0
  66. llm_api_scope-0.9.0/apiscope/skill/show/app.py +90 -0
  67. llm_api_scope-0.9.0/apiscope/skill/show/constants.py +46 -0
  68. llm_api_scope-0.9.0/apiscope/skill/show/context.py +12 -0
  69. llm_api_scope-0.9.0/apiscope/skill/show/preflight.py +8 -0
  70. llm_api_scope-0.9.0/apiscope/skill/show/schema.py +7 -0
  71. llm_api_scope-0.9.0/apiscope/source.py +154 -0
  72. llm_api_scope-0.9.0/apiscope/sync/__init__.py +2 -0
  73. llm_api_scope-0.9.0/apiscope/sync/_lib/__init__.py +2 -0
  74. llm_api_scope-0.9.0/apiscope/sync/_lib/errors.py +32 -0
  75. llm_api_scope-0.9.0/apiscope/sync/_lib/filesystem/__init__.py +2 -0
  76. llm_api_scope-0.9.0/apiscope/sync/_lib/filesystem/constants.py +9 -0
  77. llm_api_scope-0.9.0/apiscope/sync/_lib/filesystem/fetcher.py +54 -0
  78. llm_api_scope-0.9.0/apiscope/sync/_lib/llmstxt/__init__.py +2 -0
  79. llm_api_scope-0.9.0/apiscope/sync/_lib/llmstxt/fetcher.py +112 -0
  80. llm_api_scope-0.9.0/apiscope/sync/_lib/openapi/__init__.py +2 -0
  81. llm_api_scope-0.9.0/apiscope/sync/_lib/openapi/fetcher.py +32 -0
  82. llm_api_scope-0.9.0/apiscope/sync/_lib/protocols.py +15 -0
  83. llm_api_scope-0.9.0/apiscope/sync/_lib/registry.py +38 -0
  84. llm_api_scope-0.9.0/apiscope/sync/_lib/repo/__init__.py +2 -0
  85. llm_api_scope-0.9.0/apiscope/sync/_lib/repo/constants.py +22 -0
  86. llm_api_scope-0.9.0/apiscope/sync/_lib/repo/fetcher.py +115 -0
  87. llm_api_scope-0.9.0/apiscope/sync/_lib/rfc/__init__.py +2 -0
  88. llm_api_scope-0.9.0/apiscope/sync/_lib/rfc/fetcher.py +32 -0
  89. llm_api_scope-0.9.0/apiscope/sync/_lib/schema.py +21 -0
  90. llm_api_scope-0.9.0/apiscope/sync/_lib/transport.py +79 -0
  91. llm_api_scope-0.9.0/apiscope/sync/app.py +315 -0
  92. llm_api_scope-0.9.0/apiscope/sync/constants.py +72 -0
  93. llm_api_scope-0.9.0/apiscope/sync/context.py +11 -0
  94. llm_api_scope-0.9.0/apiscope/sync/preflight.py +32 -0
  95. llm_api_scope-0.9.0/apiscope/sync/schema.py +10 -0
  96. llm_api_scope-0.9.0/apiscope/usage.py +75 -0
  97. llm_api_scope-0.9.0/apiscope/view/__init__.py +1 -0
  98. llm_api_scope-0.9.0/apiscope/view/app.py +220 -0
  99. llm_api_scope-0.9.0/apiscope/view/constants.py +28 -0
  100. llm_api_scope-0.9.0/apiscope/view/context.py +12 -0
  101. llm_api_scope-0.9.0/apiscope/view/preflight.py +8 -0
  102. llm_api_scope-0.9.0/apiscope/view/schema.py +9 -0
  103. llm_api_scope-0.9.0/apiscope/view_lib/__init__.py +2 -0
  104. llm_api_scope-0.9.0/apiscope/view_lib/address.py +18 -0
  105. llm_api_scope-0.9.0/apiscope/view_lib/constants.py +47 -0
  106. llm_api_scope-0.9.0/apiscope/view_lib/errors.py +31 -0
  107. llm_api_scope-0.9.0/apiscope/view_lib/filesystem/viewer.py +114 -0
  108. llm_api_scope-0.9.0/apiscope/view_lib/hint.py +14 -0
  109. llm_api_scope-0.9.0/apiscope/view_lib/llmstxt/viewer.py +11 -0
  110. llm_api_scope-0.9.0/apiscope/view_lib/openapi/constants.py +19 -0
  111. llm_api_scope-0.9.0/apiscope/view_lib/openapi/viewer.py +194 -0
  112. llm_api_scope-0.9.0/apiscope/view_lib/protocols.py +10 -0
  113. llm_api_scope-0.9.0/apiscope/view_lib/registry.py +27 -0
  114. llm_api_scope-0.9.0/apiscope/view_lib/repo/viewer.py +11 -0
  115. llm_api_scope-0.9.0/apiscope/view_lib/rfc/text.py +3 -0
  116. llm_api_scope-0.9.0/apiscope/view_lib/rfc/viewer.py +59 -0
  117. llm_api_scope-0.9.0/apiscope/view_lib/rfc/xml.py +138 -0
  118. llm_api_scope-0.9.0/apiscope/view_lib/schema.py +56 -0
  119. llm_api_scope-0.9.0/apiscope/view_lib/tree.py +215 -0
  120. llm_api_scope-0.9.0/llm_api_scope.egg-info/PKG-INFO +211 -0
  121. llm_api_scope-0.9.0/llm_api_scope.egg-info/SOURCES.txt +127 -0
  122. {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/pyproject.toml +8 -2
  123. llm_api_scope-0.7.0/PKG-INFO +0 -95
  124. llm_api_scope-0.7.0/README.md +0 -46
  125. llm_api_scope-0.7.0/apiscope/config.py +0 -165
  126. llm_api_scope-0.7.0/apiscope/main.py +0 -117
  127. llm_api_scope-0.7.0/apiscope/openapi/__init__.py +0 -5
  128. llm_api_scope-0.7.0/apiscope/openapi/app.py +0 -161
  129. llm_api_scope-0.7.0/apiscope/openapi/fetch.py +0 -58
  130. llm_api_scope-0.7.0/apiscope/openapi/reader.py +0 -85
  131. llm_api_scope-0.7.0/apiscope/openapi/schema.py +0 -15
  132. llm_api_scope-0.7.0/apiscope/openapi/spec/__init__.py +0 -5
  133. llm_api_scope-0.7.0/apiscope/openapi/spec/app.py +0 -95
  134. llm_api_scope-0.7.0/apiscope/openapi/spec/schema.py +0 -7
  135. llm_api_scope-0.7.0/apiscope/repo/__init__.py +0 -6
  136. llm_api_scope-0.7.0/apiscope/repo/app.py +0 -152
  137. llm_api_scope-0.7.0/apiscope/repo/fetch.py +0 -132
  138. llm_api_scope-0.7.0/apiscope/repo/schema.py +0 -53
  139. llm_api_scope-0.7.0/apiscope/rfc/__init__.py +0 -6
  140. llm_api_scope-0.7.0/apiscope/rfc/app.py +0 -403
  141. llm_api_scope-0.7.0/apiscope/rfc/fetch.py +0 -31
  142. llm_api_scope-0.7.0/apiscope/rfc/parse_txt.py +0 -54
  143. llm_api_scope-0.7.0/apiscope/rfc/parse_xml.py +0 -93
  144. llm_api_scope-0.7.0/apiscope/rfc/schema.py +0 -130
  145. llm_api_scope-0.7.0/apiscope/rfc/search.py +0 -60
  146. llm_api_scope-0.7.0/apiscope/schema.py +0 -15
  147. llm_api_scope-0.7.0/apiscope/skill/__init__.py +0 -5
  148. llm_api_scope-0.7.0/apiscope/skill/_usage.py +0 -72
  149. llm_api_scope-0.7.0/apiscope/skill/app.py +0 -19
  150. llm_api_scope-0.7.0/apiscope/skill/docs.py +0 -111
  151. llm_api_scope-0.7.0/llm_api_scope.egg-info/PKG-INFO +0 -95
  152. llm_api_scope-0.7.0/llm_api_scope.egg-info/SOURCES.txt +0 -36
  153. {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/LICENSE +0 -0
  154. {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/apiscope/__init__.py +0 -0
  155. {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/dependency_links.txt +0 -0
  156. {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/entry_points.txt +0 -0
  157. {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/requires.txt +0 -0
  158. {llm_api_scope-0.7.0 → llm_api_scope-0.9.0}/llm_api_scope.egg-info/top_level.txt +0 -0
  159. {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())