foldkeep 0.9.2__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.
- foldkeep-0.9.2/LICENSE +21 -0
- foldkeep-0.9.2/PKG-INFO +117 -0
- foldkeep-0.9.2/README.md +80 -0
- foldkeep-0.9.2/foldkeep/__init__.py +16 -0
- foldkeep-0.9.2/foldkeep/__main__.py +3 -0
- foldkeep-0.9.2/foldkeep/cli.py +246 -0
- foldkeep-0.9.2/foldkeep/core/__init__.py +0 -0
- foldkeep-0.9.2/foldkeep/core/compressor.py +346 -0
- foldkeep-0.9.2/foldkeep/diff.py +105 -0
- foldkeep-0.9.2/foldkeep/estimate.py +28 -0
- foldkeep-0.9.2/foldkeep/mcp_server.py +189 -0
- foldkeep-0.9.2/foldkeep/memory/__init__.py +0 -0
- foldkeep-0.9.2/foldkeep/memory/store.py +92 -0
- foldkeep-0.9.2/foldkeep/packing.py +63 -0
- foldkeep-0.9.2/foldkeep/selftest.py +154 -0
- foldkeep-0.9.2/foldkeep/session.py +423 -0
- foldkeep-0.9.2/foldkeep.egg-info/PKG-INFO +117 -0
- foldkeep-0.9.2/foldkeep.egg-info/SOURCES.txt +20 -0
- foldkeep-0.9.2/foldkeep.egg-info/dependency_links.txt +1 -0
- foldkeep-0.9.2/foldkeep.egg-info/top_level.txt +1 -0
- foldkeep-0.9.2/pyproject.toml +23 -0
- foldkeep-0.9.2/setup.cfg +4 -0
foldkeep-0.9.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 foldkeep contributors
|
|
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.
|
foldkeep-0.9.2/PKG-INFO
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foldkeep
|
|
3
|
+
Version: 0.9.2
|
|
4
|
+
Summary: Zero-dependency lossless context folding for agent sessions: pinned facts, tolerance routing, byte-for-byte expand.
|
|
5
|
+
Author: foldkeep contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 foldkeep contributors
|
|
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
|
+
Keywords: llm,agent,context-window,memory,compression
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Intended Audience :: Developers
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
33
|
+
Requires-Python: >=3.10
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# foldkeep
|
|
39
|
+
|
|
40
|
+
**Fold the context. Keep the facts.**
|
|
41
|
+
|
|
42
|
+
foldkeep is a zero-dependency, deterministic context-compression engine for
|
|
43
|
+
long agent conversations. It rolls old turns into compressed form while a
|
|
44
|
+
pinned fact layer keeps every decision reachable — and a lossless base lets
|
|
45
|
+
you expand ANY turn back byte-for-byte, even after rolling and merging.
|
|
46
|
+
|
|
47
|
+
Pure Python stdlib. No LLM calls. Millisecond latency. SQLite-backed.
|
|
48
|
+
|
|
49
|
+
## Why
|
|
50
|
+
|
|
51
|
+
Agent transcripts grow until the model drowns. Naive fixes lose the past:
|
|
52
|
+
tail-truncation drops early decisions; naive compression mangles numbers,
|
|
53
|
+
code and tool payloads. foldkeep keeps three guarantees:
|
|
54
|
+
|
|
55
|
+
1. **Pinned facts survive** — every turn pins a salient evidence line;
|
|
56
|
+
a set-cover knapsack picks which lines fill the leftover budget so
|
|
57
|
+
entity coverage is maximized, not clustered.
|
|
58
|
+
2. **Zero-tolerance content is never mangled** — code fences, JSON payloads
|
|
59
|
+
and tool-call shapes are routed away from lossy compression.
|
|
60
|
+
3. **Nothing is ever lost** — originals persist on disk; `expand(name, seq)`
|
|
61
|
+
recovers any turn byte-for-byte after rollup, consolidation or render
|
|
62
|
+
drops.
|
|
63
|
+
|
|
64
|
+
## Quickstart
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from foldkeep import session as sess
|
|
68
|
+
|
|
69
|
+
sess.new("my-session")
|
|
70
|
+
for role, text in conversation: # push turns as they happen
|
|
71
|
+
sess.push("my-session", role, text) # auto-rolls when long
|
|
72
|
+
|
|
73
|
+
out, used, dropped = sess.render("my-session", budget=800)
|
|
74
|
+
# -> compressed body + key-facts registry + pinned-fact block, <= 800 tok
|
|
75
|
+
|
|
76
|
+
orig = sess.expand("my-session", 3) # byte-for-byte original of turn 3
|
|
77
|
+
hits = sess.search("my-session", "TKT-9001") # recall across rolled turns
|
|
78
|
+
md = sess.export_md("my-session", "vault.md") # inspectable Markdown vault
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
CLI:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python -m foldkeep selftest # 16-case correctness battery
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Proven, not promised
|
|
88
|
+
|
|
89
|
+
| Suite | Result |
|
|
90
|
+
|---|---|
|
|
91
|
+
| selftest (correctness) | 16/16 |
|
|
92
|
+
| stress_audit (adversarial probes) | 22/22 |
|
|
93
|
+
| marathon (long-session checkpoints) | 9/9, push ≤ 12 ms, render ≤ 80 ms |
|
|
94
|
+
| agent_selftest (real build-session replay, equal budgets) | wins 5/6 cells vs tail-only & head-trunc |
|
|
95
|
+
| chat_selftest (this project's own chat replay) | 12/12, recall 9/9 under forced rolling |
|
|
96
|
+
|
|
97
|
+
Benchmarks live in this repo (`agent_selftest.py`, `chat_selftest.py`,
|
|
98
|
+
`stress_audit.py`, `long_session_test.py`) — every number reproducible.
|
|
99
|
+
|
|
100
|
+
## Design
|
|
101
|
+
|
|
102
|
+
- **Rolling compression**: newest stays verbatim, oldest compresses into a
|
|
103
|
+
head; hierarchical re-merge keeps the head shrinking.
|
|
104
|
+
- **Three-layer render**: compressed body + one-line entity registry +
|
|
105
|
+
set-cover pinned-fact block, all inside your token budget.
|
|
106
|
+
- **Loss-tolerance routing**: fences / JSON / tool-call shapes bypass
|
|
107
|
+
lossy paths entirely.
|
|
108
|
+
- **Lossless base**: `orig_text` column + `originals` table + `expand()`.
|
|
109
|
+
- **Operator knobs**: `OC_REGISTRY_MAX` env caps registry injection.
|
|
110
|
+
|
|
111
|
+
## Status
|
|
112
|
+
|
|
113
|
+
v0.9.2. Research notes (`RESEARCH.md`) document the competitive landscape
|
|
114
|
+
(LLMLingua, leanctx, mnesis, FoldAgent, pi-fold, ECC, hermes-agent) and what
|
|
115
|
+
we learned from each.
|
|
116
|
+
|
|
117
|
+
MIT licensed. Windows/macOS/Linux, Python 3.10+.
|
foldkeep-0.9.2/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# foldkeep
|
|
2
|
+
|
|
3
|
+
**Fold the context. Keep the facts.**
|
|
4
|
+
|
|
5
|
+
foldkeep is a zero-dependency, deterministic context-compression engine for
|
|
6
|
+
long agent conversations. It rolls old turns into compressed form while a
|
|
7
|
+
pinned fact layer keeps every decision reachable — and a lossless base lets
|
|
8
|
+
you expand ANY turn back byte-for-byte, even after rolling and merging.
|
|
9
|
+
|
|
10
|
+
Pure Python stdlib. No LLM calls. Millisecond latency. SQLite-backed.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Agent transcripts grow until the model drowns. Naive fixes lose the past:
|
|
15
|
+
tail-truncation drops early decisions; naive compression mangles numbers,
|
|
16
|
+
code and tool payloads. foldkeep keeps three guarantees:
|
|
17
|
+
|
|
18
|
+
1. **Pinned facts survive** — every turn pins a salient evidence line;
|
|
19
|
+
a set-cover knapsack picks which lines fill the leftover budget so
|
|
20
|
+
entity coverage is maximized, not clustered.
|
|
21
|
+
2. **Zero-tolerance content is never mangled** — code fences, JSON payloads
|
|
22
|
+
and tool-call shapes are routed away from lossy compression.
|
|
23
|
+
3. **Nothing is ever lost** — originals persist on disk; `expand(name, seq)`
|
|
24
|
+
recovers any turn byte-for-byte after rollup, consolidation or render
|
|
25
|
+
drops.
|
|
26
|
+
|
|
27
|
+
## Quickstart
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from foldkeep import session as sess
|
|
31
|
+
|
|
32
|
+
sess.new("my-session")
|
|
33
|
+
for role, text in conversation: # push turns as they happen
|
|
34
|
+
sess.push("my-session", role, text) # auto-rolls when long
|
|
35
|
+
|
|
36
|
+
out, used, dropped = sess.render("my-session", budget=800)
|
|
37
|
+
# -> compressed body + key-facts registry + pinned-fact block, <= 800 tok
|
|
38
|
+
|
|
39
|
+
orig = sess.expand("my-session", 3) # byte-for-byte original of turn 3
|
|
40
|
+
hits = sess.search("my-session", "TKT-9001") # recall across rolled turns
|
|
41
|
+
md = sess.export_md("my-session", "vault.md") # inspectable Markdown vault
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
CLI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m foldkeep selftest # 16-case correctness battery
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Proven, not promised
|
|
51
|
+
|
|
52
|
+
| Suite | Result |
|
|
53
|
+
|---|---|
|
|
54
|
+
| selftest (correctness) | 16/16 |
|
|
55
|
+
| stress_audit (adversarial probes) | 22/22 |
|
|
56
|
+
| marathon (long-session checkpoints) | 9/9, push ≤ 12 ms, render ≤ 80 ms |
|
|
57
|
+
| agent_selftest (real build-session replay, equal budgets) | wins 5/6 cells vs tail-only & head-trunc |
|
|
58
|
+
| chat_selftest (this project's own chat replay) | 12/12, recall 9/9 under forced rolling |
|
|
59
|
+
|
|
60
|
+
Benchmarks live in this repo (`agent_selftest.py`, `chat_selftest.py`,
|
|
61
|
+
`stress_audit.py`, `long_session_test.py`) — every number reproducible.
|
|
62
|
+
|
|
63
|
+
## Design
|
|
64
|
+
|
|
65
|
+
- **Rolling compression**: newest stays verbatim, oldest compresses into a
|
|
66
|
+
head; hierarchical re-merge keeps the head shrinking.
|
|
67
|
+
- **Three-layer render**: compressed body + one-line entity registry +
|
|
68
|
+
set-cover pinned-fact block, all inside your token budget.
|
|
69
|
+
- **Loss-tolerance routing**: fences / JSON / tool-call shapes bypass
|
|
70
|
+
lossy paths entirely.
|
|
71
|
+
- **Lossless base**: `orig_text` column + `originals` table + `expand()`.
|
|
72
|
+
- **Operator knobs**: `OC_REGISTRY_MAX` env caps registry injection.
|
|
73
|
+
|
|
74
|
+
## Status
|
|
75
|
+
|
|
76
|
+
v0.9.2. Research notes (`RESEARCH.md`) document the competitive landscape
|
|
77
|
+
(LLMLingua, leanctx, mnesis, FoldAgent, pi-fold, ECC, hermes-agent) and what
|
|
78
|
+
we learned from each.
|
|
79
|
+
|
|
80
|
+
MIT licensed. Windows/macOS/Linux, Python 3.10+.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""FoldKeep -- Agent Context Infrastructure.
|
|
2
|
+
|
|
3
|
+
Layers:
|
|
4
|
+
compressor : FastLingua engine (entity-preserving heuristic compression)
|
|
5
|
+
estimate : token & cost estimation across popular models
|
|
6
|
+
store : named compressed-context snapshots for agent workflows
|
|
7
|
+
|
|
8
|
+
Layers: core.compressor | estimate | store(SQLite memory bank) | packing | mcp_server.
|
|
9
|
+
"""
|
|
10
|
+
__version__ = "0.6.0"
|
|
11
|
+
|
|
12
|
+
from .core.compressor import FastLingua # noqa: F401
|
|
13
|
+
from .benchmark.suite import run_bench, evolve # noqa: F401
|
|
14
|
+
from .estimate import estimate_tokens, estimate_cost, savings # noqa: F401
|
|
15
|
+
from .memory import store # noqa: F401
|
|
16
|
+
from .packing import pack, render # noqa: F401
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""FoldKeep unified CLI: oc compress / estimate / store / bench / evolve."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .core.compressor import FastLingua, GENES
|
|
9
|
+
from .benchmark.suite import run_bench, evolve, EVOLVE_FILE
|
|
10
|
+
from .memory import store
|
|
11
|
+
from .estimate import estimate_cost, savings
|
|
12
|
+
from .packing import pack, render
|
|
13
|
+
from .diff import commit as ctx_commit, log as ctx_log, show as ctx_show
|
|
14
|
+
from . import session as sess
|
|
15
|
+
|
|
16
|
+
def _read(src):
|
|
17
|
+
if src == "-":
|
|
18
|
+
raw = sys.stdin.buffer.read()
|
|
19
|
+
try:
|
|
20
|
+
return raw.decode("utf-8")
|
|
21
|
+
except UnicodeDecodeError:
|
|
22
|
+
return raw.decode("gbk", "replace")
|
|
23
|
+
with open(src, encoding="utf-8-sig") as f:
|
|
24
|
+
return f.read().lstrip("\ufeff")
|
|
25
|
+
|
|
26
|
+
def _adapt(fl, stats, target):
|
|
27
|
+
try:
|
|
28
|
+
cur = {}
|
|
29
|
+
if os.path.exists(EVOLVE_FILE):
|
|
30
|
+
with open(EVOLVE_FILE, encoding="utf-8") as f:
|
|
31
|
+
cur = json.load(f)
|
|
32
|
+
err = stats["ratio"] - target
|
|
33
|
+
if abs(err) > 0.05:
|
|
34
|
+
lo, hi = GENES["dup_th"]
|
|
35
|
+
d = float(cur.get("dup_th", 0.72)) - (0.01 if err > 0 else -0.008)
|
|
36
|
+
cur["dup_th"] = round(min(hi, max(lo, d)), 4)
|
|
37
|
+
lo, hi = GENES["ent_w"]
|
|
38
|
+
d = float(cur.get("ent_w", 3.0)) - (0.08 if err > 0 else -0.10)
|
|
39
|
+
cur["ent_w"] = round(min(hi, max(lo, d)), 4)
|
|
40
|
+
lo, hi = GENES["cont_w"]
|
|
41
|
+
d = float(cur.get("cont_w", 1.0)) - (0.05 if err > 0 else -0.04)
|
|
42
|
+
cur["cont_w"] = round(min(hi, max(lo, d)), 4)
|
|
43
|
+
with open(EVOLVE_FILE, "w", encoding="utf-8") as f:
|
|
44
|
+
json.dump(cur, f, indent=1)
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def main(argv=None):
|
|
49
|
+
ap = argparse.ArgumentParser(
|
|
50
|
+
prog="foldkeep",
|
|
51
|
+
description="FoldKeep: Agent Context Infrastructure (compress | estimate | store | bench | evolve)")
|
|
52
|
+
ap.add_argument("--version", action="version", version="foldkeep " + __version__)
|
|
53
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
54
|
+
|
|
55
|
+
c = sub.add_parser("compress", help="compress text via FastLingua engine")
|
|
56
|
+
c.add_argument("input", help="file path or '-' for stdin")
|
|
57
|
+
c.add_argument("--ratio", type=float, default=0.5)
|
|
58
|
+
c.add_argument("--mode", choices=FastLingua.MODES, default="balanced")
|
|
59
|
+
c.add_argument("--anchors", action="store_true")
|
|
60
|
+
c.add_argument("--collapse", action="store_true")
|
|
61
|
+
c.add_argument("--adapt", action="store_true")
|
|
62
|
+
c.add_argument("--no-auto", action="store_true")
|
|
63
|
+
c.add_argument("--save", metavar="NAME", help="also store snapshot for later reuse")
|
|
64
|
+
c.add_argument("-o", "--output")
|
|
65
|
+
|
|
66
|
+
e = sub.add_parser("estimate", help="token & cost estimate")
|
|
67
|
+
e.add_argument("input")
|
|
68
|
+
e.add_argument("--model", default="claude-sonnet")
|
|
69
|
+
e.add_argument("--compress", action="store_true", help="also show post-compression savings")
|
|
70
|
+
|
|
71
|
+
st = sub.add_parser("store", help="snapshot management")
|
|
72
|
+
st.add_argument("op", choices=["list", "get", "delete"])
|
|
73
|
+
st.add_argument("name", nargs="?")
|
|
74
|
+
|
|
75
|
+
b = sub.add_parser("bench", help="run benchmark")
|
|
76
|
+
b.add_argument("--full", action="store_true", help="1000-sample synthetic suite")
|
|
77
|
+
|
|
78
|
+
pk = sub.add_parser("pack", help="fit multiple files into a token budget")
|
|
79
|
+
pk.add_argument("inputs", nargs="+")
|
|
80
|
+
pk.add_argument("--budget", type=int, default=8000)
|
|
81
|
+
pk.add_argument("--ratio", type=float, default=0.5)
|
|
82
|
+
pk.add_argument("--model", default=None, help="also show cost for this model")
|
|
83
|
+
pk.add_argument("--save", metavar="NAME")
|
|
84
|
+
pk.add_argument("-o", "--output")
|
|
85
|
+
sub.add_parser("selftest", help="run the 16-case edge battery")
|
|
86
|
+
|
|
87
|
+
ss = sub.add_parser("session", help="long-conversation rolling compression")
|
|
88
|
+
ss.add_argument("op", choices=["new", "push", "show", "stat"])
|
|
89
|
+
ss.add_argument("name")
|
|
90
|
+
ss.add_argument("-r", "--role", default="user", choices=["user", "assistant", "system"])
|
|
91
|
+
ss.add_argument("-t", "--text")
|
|
92
|
+
ss.add_argument("-f", "--file")
|
|
93
|
+
ss.add_argument("--budget", type=int, default=None)
|
|
94
|
+
ss.add_argument("--roll-threshold", type=int, default=None,
|
|
95
|
+
help="override auto-rollup token threshold")
|
|
96
|
+
cx = sub.add_parser("ctx", help="context version chain: commit/log/show/restore")
|
|
97
|
+
cx.add_argument("op", choices=["commit", "log", "show", "restore"])
|
|
98
|
+
cx.add_argument("target", nargs="?", help="file path (commit) or version id")
|
|
99
|
+
cx.add_argument("-p", "--project", default="default")
|
|
100
|
+
cx.add_argument("-l", "--label", default="")
|
|
101
|
+
cx.add_argument("-o", "--output")
|
|
102
|
+
ev = sub.add_parser("evolve", help="self-evolve compressor genes")
|
|
103
|
+
ev.add_argument("--gens", type=int, default=8)
|
|
104
|
+
ev.add_argument("--pop", type=int, default=8)
|
|
105
|
+
ev.add_argument("--seed", type=int, default=42)
|
|
106
|
+
|
|
107
|
+
a = ap.parse_args(argv)
|
|
108
|
+
|
|
109
|
+
if a.cmd == "compress":
|
|
110
|
+
text = _read(a.input)
|
|
111
|
+
fl = FastLingua(a.mode, a.ratio, a.anchors,
|
|
112
|
+
auto=not a.no_auto, collapse=a.collapse)
|
|
113
|
+
comp, stats = fl.compress(text)
|
|
114
|
+
if a.adapt:
|
|
115
|
+
_adapt(fl, stats, a.ratio)
|
|
116
|
+
out = comp # engine already renders [F#] anchors when enabled
|
|
117
|
+
if a.output:
|
|
118
|
+
with open(a.output, "w", encoding="utf-8") as f:
|
|
119
|
+
f.write(out)
|
|
120
|
+
else:
|
|
121
|
+
print(out)
|
|
122
|
+
if a.save:
|
|
123
|
+
p = store.save(a.save, text, comp,
|
|
124
|
+
{"ratio": stats["ratio"], "mode": a.mode})
|
|
125
|
+
print("[stored] %s" % p, file=sys.stderr)
|
|
126
|
+
|
|
127
|
+
elif a.cmd == "estimate":
|
|
128
|
+
text = _read(a.input)
|
|
129
|
+
info = estimate_cost(text, a.model)
|
|
130
|
+
print("input : %6d tokens | $%.6f (%s)" % (info["tokens"], info["usd"], a.model))
|
|
131
|
+
if a.compress:
|
|
132
|
+
comp, _st = FastLingua("balanced", 0.5).compress(text)
|
|
133
|
+
after = estimate_cost(comp, a.model)
|
|
134
|
+
sv = savings(text, comp, a.model)
|
|
135
|
+
print("after : %6d tokens | $%.6f" % (after["tokens"], after["usd"]))
|
|
136
|
+
print("saved : %6d tokens | $%.6f | compressed to %.1f%%"
|
|
137
|
+
% (sv["tokens_saved"], sv["usd_saved"], sv["ratio"] * 100))
|
|
138
|
+
|
|
139
|
+
elif a.cmd == "store":
|
|
140
|
+
if a.op == "list":
|
|
141
|
+
rows = store.list_all()
|
|
142
|
+
if not rows:
|
|
143
|
+
print("(empty)")
|
|
144
|
+
for r in rows:
|
|
145
|
+
print("%-24s %7d -> %7d chars" % (r["name"], r["orig"], r["comp"]))
|
|
146
|
+
elif a.op == "get":
|
|
147
|
+
r = store.get(a.name or "")
|
|
148
|
+
print(r["text"] if r else "(not found)")
|
|
149
|
+
else:
|
|
150
|
+
print("deleted" if store.delete(a.name or "") else "(not found)")
|
|
151
|
+
|
|
152
|
+
elif a.cmd == "bench":
|
|
153
|
+
run_bench(getattr(a, "full", False))
|
|
154
|
+
|
|
155
|
+
elif a.cmd == "pack":
|
|
156
|
+
items = []
|
|
157
|
+
for src in a.inputs:
|
|
158
|
+
try:
|
|
159
|
+
items.append((os.path.basename(src), _read(src)))
|
|
160
|
+
except Exception as ex:
|
|
161
|
+
print("[skip] %s: %s" % (src, ex), file=sys.stderr)
|
|
162
|
+
if not items:
|
|
163
|
+
print("(no readable inputs)", file=sys.stderr)
|
|
164
|
+
return
|
|
165
|
+
res = pack(items, a.budget, a.ratio)
|
|
166
|
+
out = render(res)
|
|
167
|
+
if a.model:
|
|
168
|
+
from .estimate import estimate_cost
|
|
169
|
+
info = estimate_cost(out, a.model)
|
|
170
|
+
out += "\n=== cost: %d tokens | $%.6f (%s) ===" % (
|
|
171
|
+
info["tokens"], info["usd"], a.model)
|
|
172
|
+
if a.output:
|
|
173
|
+
with open(a.output, "w", encoding="utf-8") as f:
|
|
174
|
+
f.write(out)
|
|
175
|
+
else:
|
|
176
|
+
print(out)
|
|
177
|
+
if a.save:
|
|
178
|
+
pth = store.save(a.save, "".join(t for _, t in items), out,
|
|
179
|
+
{"budget": a.budget, "used": res["used"]})
|
|
180
|
+
print("[stored] %s" % pth, file=sys.stderr)
|
|
181
|
+
|
|
182
|
+
elif a.cmd == "selftest":
|
|
183
|
+
from .selftest import run
|
|
184
|
+
R, fails = run()
|
|
185
|
+
print("---")
|
|
186
|
+
print("TOTAL %d | PASS %d | FAIL %d" % (len(R), len(R) - len(fails), len(fails)))
|
|
187
|
+
if fails:
|
|
188
|
+
raise SystemExit(1)
|
|
189
|
+
|
|
190
|
+
elif a.cmd == "session":
|
|
191
|
+
if a.op == "new":
|
|
192
|
+
sess.new(a.name)
|
|
193
|
+
print("session '%s' ready" % a.name)
|
|
194
|
+
elif a.op == "push":
|
|
195
|
+
text = a.text if a.text is not None else (_read(a.file) if a.file else None)
|
|
196
|
+
if not text:
|
|
197
|
+
print("need -t TEXT or -f FILE", file=sys.stderr)
|
|
198
|
+
return
|
|
199
|
+
kw = {} if a.roll_threshold is None else {"roll_threshold": a.roll_threshold}
|
|
200
|
+
r = sess.push(a.name, a.role, text, **kw)
|
|
201
|
+
st = sess.stat(a.name)
|
|
202
|
+
print("pushed #%d (%s) | rolled %d | session: %d turns, ~%d tok"
|
|
203
|
+
% (r["seq"], a.role, r["rolled"], st["turns"], st["est_tokens"]))
|
|
204
|
+
elif a.op == "show":
|
|
205
|
+
out, used, dropped = sess.render(a.name, a.budget)
|
|
206
|
+
print(out)
|
|
207
|
+
else:
|
|
208
|
+
print(sess.stat(a.name))
|
|
209
|
+
|
|
210
|
+
elif a.cmd == "ctx":
|
|
211
|
+
if a.op == "commit":
|
|
212
|
+
if not a.target:
|
|
213
|
+
print("need file path", file=sys.stderr)
|
|
214
|
+
return
|
|
215
|
+
text = _read(a.target)
|
|
216
|
+
r = ctx_commit(a.project, text, a.label)
|
|
217
|
+
print("committed v%d [%s] %d -> %d chars (%.1f%%), stored %s"
|
|
218
|
+
% (r["id"], a.project, r["orig"], r["comp"],
|
|
219
|
+
r["ratio"] * 100, r["stored"]))
|
|
220
|
+
elif a.op == "log":
|
|
221
|
+
rows = ctx_log(a.project)
|
|
222
|
+
if not rows:
|
|
223
|
+
print("(empty)")
|
|
224
|
+
for r in rows:
|
|
225
|
+
print("v%-4d %-16s parent=%-4s %6d->%6d %s"
|
|
226
|
+
% (r["id"], r["label"] or "-", r["parent"] or "-",
|
|
227
|
+
r["orig"], r["comp"], "FULL" if r["full"] else "delta"))
|
|
228
|
+
else:
|
|
229
|
+
if not a.target or not a.target.isdigit():
|
|
230
|
+
print("need version id", file=sys.stderr)
|
|
231
|
+
return
|
|
232
|
+
text = ctx_show(int(a.target))
|
|
233
|
+
if text is None:
|
|
234
|
+
print("(version not found)", file=sys.stderr)
|
|
235
|
+
return
|
|
236
|
+
if a.output:
|
|
237
|
+
with open(a.output, "w", encoding="utf-8") as f:
|
|
238
|
+
f.write(text)
|
|
239
|
+
else:
|
|
240
|
+
print(text)
|
|
241
|
+
|
|
242
|
+
elif a.cmd == "evolve":
|
|
243
|
+
evolve(a.gens, a.pop, a.seed)
|
|
244
|
+
|
|
245
|
+
if __name__ == "__main__":
|
|
246
|
+
main()
|
|
File without changes
|