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 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.
@@ -0,0 +1,3 @@
1
+ include SYSTEM.md
2
+ recursive-include tests *.py
3
+ include scripts/check_dist.py
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`,本地打开。