curldb 0.1.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.
- curldb-0.1.0/LICENSE +21 -0
- curldb-0.1.0/MANIFEST.in +3 -0
- curldb-0.1.0/PKG-INFO +478 -0
- curldb-0.1.0/README.md +457 -0
- curldb-0.1.0/SYSTEM.md +43 -0
- curldb-0.1.0/curldb.egg-info/PKG-INFO +478 -0
- curldb-0.1.0/curldb.egg-info/SOURCES.txt +13 -0
- curldb-0.1.0/curldb.egg-info/dependency_links.txt +1 -0
- curldb-0.1.0/curldb.egg-info/entry_points.txt +2 -0
- curldb-0.1.0/curldb.egg-info/top_level.txt +1 -0
- curldb-0.1.0/curldb.py +822 -0
- curldb-0.1.0/pyproject.toml +34 -0
- curldb-0.1.0/scripts/check_dist.py +91 -0
- curldb-0.1.0/setup.cfg +4 -0
- curldb-0.1.0/tests/test_curldb.py +309 -0
curldb-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ranger Chen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
curldb-0.1.0/MANIFEST.in
ADDED
curldb-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: curldb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HTTP exchange datastore: raw requests and responses in, structured queries out. One SQLite file per session, zero daemon, zero dependencies.
|
|
5
|
+
Author-email: Ranger Chen <ranger@rangerfoxsolutions.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/rangersui/curldb
|
|
8
|
+
Keywords: http,sqlite,archive,llm,front-matter,fts5
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Database
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
16
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# curldb
|
|
23
|
+
|
|
24
|
+
HTTP exchange datastore. One SQLite file per session; the requests and responses of an AI conversation stored as-is, queried by envelope fields.
|
|
25
|
+
|
|
26
|
+
curl is the reference client, hence the name: every operation the server accepts is one curl line, and the stored record is byte for byte what curl sent.
|
|
27
|
+
|
|
28
|
+
[中文说明在后面](#中文)
|
|
29
|
+
|
|
30
|
+
## What it does
|
|
31
|
+
|
|
32
|
+
Every message in an LLM conversation is an HTTP envelope: what you type is a request, what the model answers is a response, a tool call is a request plus a response. curldb stores each envelope verbatim in SQLite, parses status / method / path / headers into an index, and full-text indexes the body.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
what you type ---------> curldb wrap 'POST /chat' -H X-Topic:x ---+
|
|
36
|
+
model reply (HTTP/1.1 200 OK ...) --------------------------------+--> curldb add --> session.sqlite
|
|
37
|
+
tool call (POST /tool/Read ... / HTTP/1.1 200 OK ...) ------------+
|
|
38
|
+
|
|
|
39
|
+
curldb query 'status=409 header:X-Scope=design'
|
|
40
|
+
curldb tags
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
raw is the source of truth. Every index is parsed from raw and can be rebuilt.
|
|
44
|
+
|
|
45
|
+
## Three principles
|
|
46
|
+
|
|
47
|
+
**One session, one file.** The file name is the session identity: `curldb --db 2026-09-11-design.sqlite` or `CURLDB_PATH`, default `curldb.sqlite` in the current directory. Searching across sessions is querying each file in turn.
|
|
48
|
+
|
|
49
|
+
**Headers are tags, not a schema.** Names and values need no agreement up front. `X-Verdict: shaky` is unreadable to a conventional program and readable to an LLM. The program stores, counts and lists; `curldb tags` prints every header name and value this file has seen, so you look first, then query. The vocabulary grows out of the data.
|
|
50
|
+
|
|
51
|
+
**The body is never touched.** Whoever adds headers (you, the model, an annotating model, a hook) adds headers only. Annotators sign with `Via:` so records tagged by different models stay distinguishable.
|
|
52
|
+
|
|
53
|
+
## How messages get in
|
|
54
|
+
|
|
55
|
+
### Model replies
|
|
56
|
+
|
|
57
|
+
Give the model a system prompt like [SYSTEM.md](SYSTEM.md) so its output carries its own envelope: technical output starts with `HTTP/1.1 200 OK` and headers such as `X-Verdict` or `X-Scope`; small talk starts with `PUBLISH topic/path`. The first line decides the pipe:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
HTTP/ -> curldb add
|
|
61
|
+
PUBLISH -> mosquitto_pub, or append to a log file; never stored
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### What you type
|
|
65
|
+
|
|
66
|
+
Type as usual, no HTTP. An adapter wraps it:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
echo 'what about tool calls' | curldb wrap 'POST /chat' -H X-Topic:tool-call | curldb add
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`wrap` adds only the start line, a `Date:` and the headers you pass; the body is untouched. To tag your own words, tee a copy to a cheap annotating model that emits header lines only, merge them back into the envelope, store. You stay out of the loop.
|
|
73
|
+
|
|
74
|
+
### Tool calls
|
|
75
|
+
|
|
76
|
+
A tool call is already a request and its result already a response. The translation is mechanical, no model involved:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
tool name -> POST /tool/Read
|
|
80
|
+
args (JSON) -> body, Content-Type: application/json
|
|
81
|
+
result -> response body
|
|
82
|
+
success/error -> status 200 / 4xx / 5xx
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
It lives in the harness hook (Claude Code's PreToolUse / PostToolUse):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# PreToolUse
|
|
89
|
+
echo "$ARGS_JSON" | curldb wrap "POST /tool/$TOOL" -H Content-Type:application/json -H X-Tool:$TOOL | curldb add
|
|
90
|
+
# PostToolUse
|
|
91
|
+
echo "$RESULT" | curldb wrap 200 -H X-Tool:$TOOL | curldb add
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A turn that mixes prose with several tool calls is split at protocol boundaries and each piece is stored on its own.
|
|
95
|
+
|
|
96
|
+
Envelopes are stored as UTF-8. Tool args produced by Python's `json.dumps` with default settings escape non-ASCII to `\u4e2d`; they are stored escaped and full-text search will not find the characters. Use `json.dumps(args, ensure_ascii=False)` in the hook.
|
|
97
|
+
|
|
98
|
+
### Notes and files
|
|
99
|
+
|
|
100
|
+
The `---` YAML front matter at the top of a markdown file is header + body too. `curldb add note.md` reads a local file: `kind=note`, the file path becomes `path`, raw is the whole file. The front matter is flattened into the headers index:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
title: hello -> title: hello
|
|
104
|
+
tags: [rust, iot] -> tags: rust / tags: iot (list = repeated name)
|
|
105
|
+
metadata: -> metadata.type: feedback (nesting = dotted name)
|
|
106
|
+
type: feedback
|
|
107
|
+
desc: | -> desc: first line second line (block scalar = lines joined)
|
|
108
|
+
first line
|
|
109
|
+
second line
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Supported is the common front matter subset: `key: value`, indentation nesting, `- item` and `[a, b]` lists, `|` / `>` blocks, quotes. Anchors and inline maps are stored as text. A file without front matter is `kind=raw`, the whole file is the body, and it is searchable all the same.
|
|
113
|
+
|
|
114
|
+
One file per call; a whole directory is a shell loop:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
for f in vault/*.md; do curldb add "$f"; done
|
|
118
|
+
curldb tags # the tag panel of this vault
|
|
119
|
+
curldb query 'header:tags=iot'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Network door
|
|
123
|
+
|
|
124
|
+
`curldb serve` opens an HTTP door onto the same file. It is a peer of the CLI, operation for operation:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
POST /<anything> stored as received (request line, all headers, body), 201 + Location: /<id>
|
|
128
|
+
GET /<id> the whole stored message, Content-Type: message/http
|
|
129
|
+
HEAD /<id> standard HEAD
|
|
130
|
+
GET /?q=<expr> same as curldb query
|
|
131
|
+
GET /tags[/<name>] same as curldb tags
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The received request is the envelope; nothing to wrap:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
curldb serve # 127.0.0.1:200, --db picks the file
|
|
138
|
+
curl -X POST localhost:200/chat -H 'X-Topic: fork' -d 'what about fork?'
|
|
139
|
+
curl localhost:200/42
|
|
140
|
+
curl 'localhost:200/?q=status=409'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The default port is 200. Ports below 1024 need root on Linux/macOS; `curldb serve 8200` avoids sudo.
|
|
144
|
+
|
|
145
|
+
An AI can curl straight in. A Codex review sent as `PUT /review` is stored as that PUT request with the review in the body; to store it as a response, use `curldb add` from the CLI.
|
|
146
|
+
|
|
147
|
+
Bound to localhost, no token. The trust model is the CLI's: whoever can run curl on this machine.
|
|
148
|
+
|
|
149
|
+
## Querying afterwards
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
curldb tags # which headers exist in this file, with values
|
|
153
|
+
curldb tags X-Verdict # every value of one header
|
|
154
|
+
|
|
155
|
+
curldb query 'status=400' # the model said you were wrong
|
|
156
|
+
curldb query 'status=409' # conflicts with something established
|
|
157
|
+
curldb query 'header:X-Verdict=shaky' # conclusions that did not hold
|
|
158
|
+
curldb query 'kind=request path=/chat' # things you said
|
|
159
|
+
curldb query 'header:X-Tool status=500' # failed tool calls
|
|
160
|
+
curldb query 'path~/tool/ body~timeout' # tool calls that timed out
|
|
161
|
+
curldb query 'status=200 header:X-Scope=design body~protocol'
|
|
162
|
+
|
|
163
|
+
curldb get 42 # the raw envelope
|
|
164
|
+
curldb ls 50 # the latest 50
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Usage
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
curldb add [file] store one envelope from stdin or a file: HTTP request/response, or markdown with front matter
|
|
171
|
+
curldb wrap START [-H N:V] wrap the stdin body in an envelope; START is a status code (200) or a request line (POST /chat)
|
|
172
|
+
curldb get <id> print the raw envelope by id
|
|
173
|
+
curldb headers <id> print the headers of a record
|
|
174
|
+
curldb query '<expr>' search
|
|
175
|
+
curldb tags [name] header names and values, with counts
|
|
176
|
+
curldb ls [n] the latest n records (default 20)
|
|
177
|
+
curldb stats database status
|
|
178
|
+
curldb serve [port] HTTP door on 127.0.0.1, default 200
|
|
179
|
+
|
|
180
|
+
--db PATH or CURLDB_PATH picks the file, default ./curldb.sqlite
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Query DSL
|
|
184
|
+
|
|
185
|
+
All conditions are ANDed, separated by spaces:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
kind=request|response|note|raw
|
|
189
|
+
status=200 status code
|
|
190
|
+
status=200,201 status in set
|
|
191
|
+
method=POST request method
|
|
192
|
+
path=/chat request path equals
|
|
193
|
+
path~/tool/ request path contains
|
|
194
|
+
header:X-Verdict header exists
|
|
195
|
+
header:X-Verdict=solid header value equals
|
|
196
|
+
body~fork body full-text search
|
|
197
|
+
body~"exact phrase" body phrase search
|
|
198
|
+
anyword bare word, body search
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Full-text search uses FTS5 with the trigram tokenizer (SQLite 3.34+), so CJK substrings match directly; older SQLite falls back to unicode61 + LIKE, and `curldb stats` shows which one is in use.
|
|
202
|
+
|
|
203
|
+
## Install
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
pip install curldb
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or copy the one file:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cp curldb.py ~/.local/bin/curldb
|
|
213
|
+
chmod +x ~/.local/bin/curldb
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Zero dependencies. Python 3.10+, standard-library sqlite3.
|
|
217
|
+
|
|
218
|
+
## Development and verification
|
|
219
|
+
|
|
220
|
+
Run the standard-library tests from the repository root, no test dependencies needed:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
python -m unittest discover -s tests -v
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The tests cover raw round trips (UTF-8, LF / CRLF / CR), combined queries, CJK search and the tokenizer fallback, front matter, legacy layout migration, the CLI and the local HTTP door. Databases live in temporary directories; the HTTP tests use a system-assigned port.
|
|
227
|
+
|
|
228
|
+
Build and check a release:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
python -m pip install build twine
|
|
232
|
+
python -m build
|
|
233
|
+
python -m twine check --strict dist/*
|
|
234
|
+
python scripts/check_dist.py dist
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`dist` should hold exactly one wheel and one sdist from this build. The check script verifies license, version, zero runtime dependencies and sdist contents, then installs the wheel offline into a temporary virtual environment and exercises the installed command line. The sdist contains `SYSTEM.md`, the tests and the check script.
|
|
238
|
+
|
|
239
|
+
GitHub Actions runs the tests on Windows and Linux with Python 3.10 and 3.14, plus a build-and-install check of the release artifacts.
|
|
240
|
+
|
|
241
|
+
## Design choices
|
|
242
|
+
|
|
243
|
+
- **Verbatim storage** -- the raw envelope goes into a SQLite TEXT column as-is.
|
|
244
|
+
- **Rebuildable index** -- kind / status / method / path / headers / body_fts are all parsed from raw.
|
|
245
|
+
- **Lenient parsing** -- accepts `\n` and `\r\n`, ignores malformed headers, Content-Length optional. It is a document parser.
|
|
246
|
+
- **One session, one file** -- backup is `cp`, sync is rsync.
|
|
247
|
+
- **One CLI** -- in, out, exit. `serve` is a second door onto the same cabinet, opened when wanted.
|
|
248
|
+
- **Append only** -- no update, no delete.
|
|
249
|
+
- **Stores HTTP messages, queries HTTP messages** -- `GET /<id>` returns the stored message itself (`message/http`). A stored message is always data; it is never replayed as the server's own reply. To view HTML an AI wrote: `curldb get 42 > x.html` and open it locally.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
# 中文
|
|
254
|
+
|
|
255
|
+
HTTP exchange 原生存储。一个 session 一个 SQLite 文件,AI 对话里的 request 和 response 原样存,按信封字段查。
|
|
256
|
+
|
|
257
|
+
curl 是参考客户端,名字由此而来:server 接受的每个操作都是一行 curl,存下来的记录就是 curl 发出去的那条消息,一个字节不差。
|
|
258
|
+
|
|
259
|
+
## 干嘛的
|
|
260
|
+
|
|
261
|
+
LLM 对话里的每条消息都是一个 HTTP 信封:你的话是 request,模型的回答是 response,工具调用是 request + response。curldb 把信封原样存进 SQLite,解析出 status / method / path / headers 建索引,body 做全文索引。
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
你的话 -----------> curldb wrap 'POST /chat' -H X-Topic:x ---+
|
|
265
|
+
模型的回答 (HTTP/1.1 200 OK ...) ---------------------------+--> curldb add --> session.sqlite
|
|
266
|
+
工具调用 (POST /tool/Read ... / HTTP/1.1 200 OK ...) --------+
|
|
267
|
+
|
|
|
268
|
+
curldb query 'status=409 header:X-Scope=design'
|
|
269
|
+
curldb tags
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
raw 是源数据,所有索引从 raw 解析出来,丢了可以重建。
|
|
273
|
+
|
|
274
|
+
## 三条原则
|
|
275
|
+
|
|
276
|
+
**一个 session 一个文件。** session 的身份就是文件名,`curldb --db 2026-09-11-design.sqlite` 或 `CURLDB_PATH`,默认当前目录的 `curldb.sqlite`。跨 session 查就是对多个文件各查一遍。
|
|
277
|
+
|
|
278
|
+
**header 是 tag,不是 schema。** 名字和值都不用事先约定。`X-Verdict: shaky` 这种传统程序读不懂的东西,读它的是 LLM。程序只管存、数、列;`curldb tags` 把这个库里出现过的 header 名和值全列出来,看一眼再查。词表是从数据里长出来的。
|
|
279
|
+
|
|
280
|
+
**body 永远原样。** 加 header 的人(你、模型、标注模型、hook)只加 header,谁都不改 body。标注者用 `Via:` 留名,以后换模型重标,新旧记录能区分。
|
|
281
|
+
|
|
282
|
+
## 三种消息怎么进来
|
|
283
|
+
|
|
284
|
+
### 模型的回答
|
|
285
|
+
|
|
286
|
+
给模型配 [SYSTEM.md](SYSTEM.md) 那样的 system prompt,让输出自带信封:技术产出以 `HTTP/1.1 200 OK` 开头,带 `X-Verdict`、`X-Scope` 这类 header;闲聊以 `PUBLISH topic/path` 开头。第一行决定这条消息走哪条管道:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
HTTP/ -> curldb add
|
|
290
|
+
PUBLISH -> mosquitto_pub,或 append 到日志文件;不进库
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 你的话
|
|
294
|
+
|
|
295
|
+
你照常打字,不用写 HTTP。adapter 套信封:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
echo '那 tool call 呢' | curldb wrap 'POST /chat' -H X-Topic:tool-call | curldb add
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`wrap` 只加 start line、`Date:` 和你给的 header,body 一个字不动。要给你的话打 tag,tee 一份给一个便宜的标注模型,它只出 header 行,合回信封再入库。你不在环里。
|
|
302
|
+
|
|
303
|
+
### 工具调用
|
|
304
|
+
|
|
305
|
+
tool call 本来就是 request,结果本来就是 response,机械翻译,不需要模型:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
tool name -> POST /tool/Read
|
|
309
|
+
args (JSON) -> body, Content-Type: application/json
|
|
310
|
+
result -> response body
|
|
311
|
+
success/error -> status 200 / 4xx / 5xx
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
放在 harness 的 hook 里(Claude Code 的 PreToolUse / PostToolUse):
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# PreToolUse
|
|
318
|
+
echo "$ARGS_JSON" | curldb wrap "POST /tool/$TOOL" -H Content-Type:application/json -H X-Tool:$TOOL | curldb add
|
|
319
|
+
# PostToolUse
|
|
320
|
+
echo "$RESULT" | curldb wrap 200 -H X-Tool:$TOOL | curldb add
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
一个 turn 里 prose 和多个 tool call 混着,按协议边界切开各存各的。
|
|
324
|
+
|
|
325
|
+
信封一律按 UTF-8 存。tool args 如果是用 Python 的 `json.dumps` 默认参数生成的,中文会变成 `\u4e2d` 这种转义,存进去就是转义,全文搜索搜不到中文;hook 里用 `json.dumps(args, ensure_ascii=False)`。
|
|
326
|
+
|
|
327
|
+
### 笔记和文件
|
|
328
|
+
|
|
329
|
+
markdown 顶上那段 `---` 夹着的 YAML front matter 也是 header + body。`curldb add note.md` 直接读本地文件,`kind=note`,文件路径当 `path`,raw 是整个文件原文。front matter 打平进 headers 索引:
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
title: hello -> title: hello
|
|
333
|
+
tags: [rust, iot] -> tags: rust / tags: iot (列表 = 同名重复)
|
|
334
|
+
metadata: -> metadata.type: feedback (嵌套 = 点号)
|
|
335
|
+
type: feedback
|
|
336
|
+
desc: | -> desc: first line second line (多行块按空格拼)
|
|
337
|
+
first line
|
|
338
|
+
second line
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
支持的是 front matter 常见子集:`key: value`、缩进嵌套、`- item` 和 `[a, b]` 列表、`|` / `>` 块、引号。锚点和行内 map 当文本存。没有 front matter 的文件 `kind=raw`,整个文件是 body,一样能搜。
|
|
342
|
+
|
|
343
|
+
一次一个文件,整个目录用 shell 循环:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
for f in vault/*.md; do curldb add "$f"; done
|
|
347
|
+
curldb tags # 这个 vault 的 tag 面板
|
|
348
|
+
curldb query 'header:tags=iot'
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 网络入口
|
|
352
|
+
|
|
353
|
+
`curldb serve` 给同一个文件开一个 HTTP 门,和 CLI 平级,功能一一对应:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
POST /<anything> 收到什么存什么(请求行、所有 header、body),201 + Location: /<id>
|
|
357
|
+
GET /<id> 整条存的消息,Content-Type: message/http
|
|
358
|
+
HEAD /<id> 标准 HEAD
|
|
359
|
+
GET /?q=<expr> 同 curldb query
|
|
360
|
+
GET /tags[/<name>] 同 curldb tags
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
收到的请求本身就是信封,不用 wrap:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
curldb serve # 127.0.0.1:200,--db 选文件
|
|
367
|
+
curl -X POST localhost:200/chat -H 'X-Topic: fork' -d 'what about fork?'
|
|
368
|
+
curl localhost:200/42
|
|
369
|
+
curl 'localhost:200/?q=status=409'
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
端口默认 200。1024 以下在 Linux/macOS 要 root,不想 sudo 就 `curldb serve 8200`。
|
|
373
|
+
|
|
374
|
+
AI 可以直接 curl 进来。Codex 的 review 以 `PUT /review` 发过来,存的是这个 PUT 请求,review 原文在 body 里;要把它当一条 response 存,走 CLI 的 `curldb add`。
|
|
375
|
+
|
|
376
|
+
只绑 localhost,没有 token:信任模型和 CLI 一样,能在这台机器上跑 curl 的人。
|
|
377
|
+
|
|
378
|
+
## 事后查
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
curldb tags # 这个库里有哪些 header,值是什么
|
|
382
|
+
curldb tags X-Verdict # 一个 header 的所有值
|
|
383
|
+
|
|
384
|
+
curldb query 'status=400' # 模型说你说错了
|
|
385
|
+
curldb query 'status=409' # 和已定的东西冲突
|
|
386
|
+
curldb query 'header:X-Verdict=shaky' # 站不住的结论
|
|
387
|
+
curldb query 'kind=request path=/chat' # 你说过的话
|
|
388
|
+
curldb query 'header:X-Tool status=500' # 失败的工具调用
|
|
389
|
+
curldb query 'path~/tool/ body~timeout' # 超时的工具调用
|
|
390
|
+
curldb query 'status=200 header:X-Scope=design body~协议'
|
|
391
|
+
|
|
392
|
+
curldb get 42 # 原始信封
|
|
393
|
+
curldb ls 50 # 最近 50 条
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## 用法
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
curldb add [file] 从 stdin 或文件存一个信封:HTTP request/response,或带 front matter 的 markdown
|
|
400
|
+
curldb wrap START [-H N:V] 给 stdin 的 body 套信封;START 是 status code (200) 或 request line (POST /chat)
|
|
401
|
+
curldb get <id> 按 id 取原始信封
|
|
402
|
+
curldb headers <id> 看某条的 headers
|
|
403
|
+
curldb query '<expr>' 查询
|
|
404
|
+
curldb tags [name] header 名和值,带计数
|
|
405
|
+
curldb ls [n] 最近 n 条(默认 20)
|
|
406
|
+
curldb stats 数据库状态
|
|
407
|
+
curldb serve [port] HTTP 门,127.0.0.1,默认 200
|
|
408
|
+
|
|
409
|
+
--db PATH 或 CURLDB_PATH 选文件,默认 ./curldb.sqlite
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### query DSL
|
|
413
|
+
|
|
414
|
+
所有条件 AND 连接,空格分隔:
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
kind=request|response|note|raw
|
|
418
|
+
status=200 status code
|
|
419
|
+
status=200,201 status in set
|
|
420
|
+
method=POST request method
|
|
421
|
+
path=/chat request path 等于
|
|
422
|
+
path~/tool/ request path 包含
|
|
423
|
+
header:X-Verdict header 存在
|
|
424
|
+
header:X-Verdict=solid header 值等于
|
|
425
|
+
body~fork body 全文搜索
|
|
426
|
+
body~"exact phrase" body 短语搜索
|
|
427
|
+
anyword 裸词,body 搜索
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
全文索引用 FTS5 trigram(SQLite 3.34+),中文子串直接搜;老 SQLite 自动退到 unicode61 + LIKE,`curldb stats` 里能看到用的哪个。
|
|
431
|
+
|
|
432
|
+
## 安装
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
pip install curldb
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
或者就一个文件,复制走:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
cp curldb.py ~/.local/bin/curldb
|
|
442
|
+
chmod +x ~/.local/bin/curldb
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
零依赖。Python 3.10+,标准库 sqlite3。
|
|
446
|
+
|
|
447
|
+
## 开发与验证
|
|
448
|
+
|
|
449
|
+
在仓库根目录运行标准库测试,不需要安装测试依赖:
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
python -m unittest discover -s tests -v
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
测试覆盖原文存取(UTF-8、LF / CRLF / CR)、组合查询、中文搜索及 tokenizer 回退、front matter、旧库迁移、CLI 和本地 HTTP 入口。数据库都放在临时目录,HTTP 测试使用系统分配的临时端口。
|
|
456
|
+
|
|
457
|
+
构建和检查发行包:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
python -m pip install build twine
|
|
461
|
+
python -m build
|
|
462
|
+
python -m twine check --strict dist/*
|
|
463
|
+
python scripts/check_dist.py dist
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
`dist` 中应只有本次构建的一份 wheel 和一份源码包。检查脚本核对许可证、版本、零运行时依赖和源码包内容,再在临时虚拟环境中离线安装 wheel,验证实际安装后的命令行存取。源码包包含 `SYSTEM.md`、测试和检查脚本。
|
|
467
|
+
|
|
468
|
+
GitHub Actions 在 Windows / Linux 的 Python 3.10 / 3.14 上运行测试,另有发行包构建与安装检查。
|
|
469
|
+
|
|
470
|
+
## 设计选择
|
|
471
|
+
|
|
472
|
+
- **原文存储** -- raw 信封原样进 SQLite TEXT 字段。
|
|
473
|
+
- **索引可重建** -- kind / status / method / path / headers / body_fts 都从 raw 解析出来。
|
|
474
|
+
- **宽松解析** -- 接受 `\n` 和 `\r\n`,忽略畸形 header,Content-Length 可选。这是文档解析器。
|
|
475
|
+
- **一个 session 一个文件** -- 备份是 `cp`,同步是 rsync。
|
|
476
|
+
- **一个 CLI** -- 进出,用完退出。`serve` 是同一个档案柜的第二扇门,想开就开。
|
|
477
|
+
- **只追加** -- 没有 update,没有 delete。
|
|
478
|
+
- **存 HTTP 消息,查 HTTP 消息** -- `GET /<id>` 回的是存的那条消息本身(`message/http`),存的消息永远是数据,不当 server 自己的回复回放。想看 AI 写的 HTML:`curldb get 42 > x.html`,本地打开。
|