ctxttools 0.3.0__tar.gz → 0.3.1__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.
- {ctxttools-0.3.0/ctxttools.egg-info → ctxttools-0.3.1}/PKG-INFO +73 -13
- {ctxttools-0.3.0 → ctxttools-0.3.1}/README.md +71 -11
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/backup.py +10 -7
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/ccat.py +2 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/ccopy.py +2 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/cdir.py +135 -54
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/cdu.py +45 -8
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/cextract.py +74 -5
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/cgrep.py +2 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/crecover.py +3 -3
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/crm.py +2 -0
- ctxttools-0.3.1/ctools/ctools-web.py +30 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/ctools_mcp.py +162 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/filterlib.py +31 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/lib.py +25 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/strategy.py +19 -2
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/webui.py +1307 -211
- {ctxttools-0.3.0 → ctxttools-0.3.1/ctxttools.egg-info}/PKG-INFO +73 -13
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctxttools.egg-info/SOURCES.txt +1 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctxttools.egg-info/requires.txt +1 -1
- {ctxttools-0.3.0 → ctxttools-0.3.1}/pyproject.toml +2 -2
- {ctxttools-0.3.0 → ctxttools-0.3.1}/LICENSE.MIT +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/__init__.py +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/agents.py +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/cconnect.py +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/cli.py +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/log.py +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctools/testing.py +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctxttools.egg-info/dependency_links.txt +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctxttools.egg-info/entry_points.txt +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/ctxttools.egg-info/top_level.txt +0 -0
- {ctxttools-0.3.0 → ctxttools-0.3.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctxttools
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: CLI tools for LLM conversations
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -9,7 +9,7 @@ Requires-Dist: rich>=13.0.0
|
|
|
9
9
|
Requires-Dist: tiktoken>=0.5.0
|
|
10
10
|
Requires-Dist: structlog>=24.0
|
|
11
11
|
Provides-Extra: mcp
|
|
12
|
-
Requires-Dist: mcp
|
|
12
|
+
Requires-Dist: mcp<2.0,>=1.2.0; extra == "mcp"
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
15
15
|
Dynamic: license-file
|
|
@@ -65,6 +65,7 @@ As you can see, these are unix-friendly tools using simple paradigms that you ar
|
|
|
65
65
|
| `cdir` | List sessions (ls for your history) | [docs/cdir.md](docs/cdir.md) |
|
|
66
66
|
| `ccat` | Print one or more conversations (cat for your history) | [docs/ccat.md](docs/ccat.md) |
|
|
67
67
|
| `ccopy` | Copy a whole conversation from one agent to a new session in another | [docs/ccopy.md](docs/ccopy.md) |
|
|
68
|
+
| `crecover` | List, verify, and restore the storage backups ccopy takes before each write | [docs/crecover.md](docs/crecover.md) |
|
|
68
69
|
| `cextract` | Extract concepts from a session into files, and inject them back | [docs/cextract.md](docs/cextract.md) |
|
|
69
70
|
| `cconnect` | Live concept pipelines between sessions | [docs/cconnect.md](docs/cconnect.md) |
|
|
70
71
|
| `cdu` | Token usage per session (du for context windows) | [docs/cdu.md](docs/cdu.md) |
|
|
@@ -104,6 +105,32 @@ graph LR
|
|
|
104
105
|
|
|
105
106
|
Context windows are endpoints: opencode, Claude Code, Codex, Pi. They all speak different protocols, but they all consume the same packets. See [cextract](docs/cextract.md) for the concept packet format, strategies, and the filter configs (decision model and JSON-RPC).
|
|
106
107
|
|
|
108
|
+
## Write safety
|
|
109
|
+
|
|
110
|
+
The session-mover tools write into your agents' **live** storage — for the
|
|
111
|
+
SQLite-backed agents (opencode, goose, kilo, hermes) that's the database that
|
|
112
|
+
holds every session of that agent. A write that fails or is interrupted
|
|
113
|
+
part-way can corrupt it, and losing an agent's whole history would be
|
|
114
|
+
unforgivable.
|
|
115
|
+
|
|
116
|
+
So every local write is guarded:
|
|
117
|
+
|
|
118
|
+
1. ctools **snapshots the agent's storage first** (SQLite via `VACUUM INTO` —
|
|
119
|
+
a consistent copy even mid-write; file agents via a tree copy), kept under
|
|
120
|
+
`~/.local/share/ctools/backups/<agent>/` (20 newest per agent).
|
|
121
|
+
2. After the write it **verifies** the storage: the DB opens,
|
|
122
|
+
`PRAGMA integrity_check` passes, the session table still exists, and the
|
|
123
|
+
session count did not decrease. A database that is structurally valid but
|
|
124
|
+
emptied of data is caught too.
|
|
125
|
+
3. If anything looks wrong, the snapshot is **restored automatically** — you
|
|
126
|
+
get a clean error, not a broken database.
|
|
127
|
+
|
|
128
|
+
And because a restore is itself a write, it snapshots what it overwrites, so
|
|
129
|
+
**every restore is undoable**. For the out-of-band case (you notice days later
|
|
130
|
+
that an agent's history vanished), `crecover list` / `verify` / `restore` are
|
|
131
|
+
there. Details: [ccopy — safety](docs/ccopy.md#safety-automatic-backups) and
|
|
132
|
+
[crecover](docs/crecover.md).
|
|
133
|
+
|
|
107
134
|
## Supported endpoints
|
|
108
135
|
|
|
109
136
|
| Agent | Storage |
|
|
@@ -150,23 +177,47 @@ Anything the storage cannot do — pi's session trees cannot be rewritten in pla
|
|
|
150
177
|
|
|
151
178
|
## MCP server
|
|
152
179
|
|
|
153
|
-
There is an MCP server for use from Claude, opencode, Cursor, or anything else that speaks MCP.
|
|
180
|
+
There is an MCP server for use from pi, Claude, opencode, Cursor, or anything else that speaks MCP.
|
|
154
181
|
|
|
155
182
|
Tools: `list_agents`, `list_sessions`, `search_sessions`, `export_session`, `extract_concepts`, `copy_concepts`, `get_session_concepts`.
|
|
156
183
|
|
|
157
|
-
|
|
184
|
+
### Quick install (pi)
|
|
185
|
+
|
|
186
|
+
One command does the whole job — install into an isolated venv (if needed) and register with pi:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
pipx run 'ctxttools[mcp]' ctools-mcp install
|
|
190
|
+
# or, with uv:
|
|
191
|
+
uv tool run 'ctxttools[mcp]' ctools-mcp install
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
It detects an existing `uv tool` or `pipx` install of `ctxttools` and uses it instead. Verify with:
|
|
195
|
+
|
|
196
|
+
```sh
|
|
197
|
+
pi mcp list # expect: ctools: connected, 7 tools
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Then use it inside pi — run `/mcp` to inspect, or just ask the agent (the tools are also reachable through codemode / tool search).
|
|
201
|
+
|
|
202
|
+
Uninstall with `ctools-mcp uninstall` (removes the pi entry; pass `--all` to also remove Claude Code and opencode entries).
|
|
203
|
+
|
|
204
|
+
### Other clients
|
|
205
|
+
|
|
206
|
+
Run `ctools-mcp print-config` to get the JSON snippet for any MCP host, e.g. for opencode (`opencode.json` under `"mcp"`) or Claude (`.mcp.json`):
|
|
158
207
|
|
|
159
208
|
```json
|
|
160
209
|
{
|
|
161
210
|
"mcpServers": {
|
|
162
211
|
"ctools": {
|
|
163
212
|
"command": "python",
|
|
164
|
-
"args": ["
|
|
213
|
+
"args": ["-m", "ctools.ctools_mcp"]
|
|
165
214
|
}
|
|
166
215
|
}
|
|
167
216
|
}
|
|
168
217
|
```
|
|
169
218
|
|
|
219
|
+
(`command` should point at the interpreter of a venv that has `ctxttools[mcp]` installed — `ctools-mcp install` prints the exact one for your machine.)
|
|
220
|
+
|
|
170
221
|
## Web dashboard
|
|
171
222
|
|
|
172
223
|
Prefer a mouse to a man page? ctools ships a browser dashboard — a small local
|
|
@@ -174,26 +225,35 @@ web server (Python stdlib only, no web dependencies) with the same library
|
|
|
174
225
|
underneath. Same on macOS, Windows, and Linux:
|
|
175
226
|
|
|
176
227
|
```sh
|
|
177
|
-
$ ctools-web #
|
|
228
|
+
$ ctools-web # installed command
|
|
229
|
+
# or, straight from a checkout, no install needed:
|
|
230
|
+
$ python3 ctools/ctools-web.py # or: python -m ctools.webui
|
|
178
231
|
ctools 0.2.1 dashboard: http://127.0.0.1:8765
|
|
179
232
|
```
|
|
180
233
|
|
|
181
|
-
Open the URL.
|
|
234
|
+
Open the URL. The navbar has **Overview**, **Sessions**, and **Concepts**, plus
|
|
235
|
+
a search box (press <b>/</b>) and a power button to stop the server. Clicking a
|
|
236
|
+
session anywhere opens its **Conversation** view:
|
|
182
237
|
|
|
183
238
|
- **Overview** — every supported agent, installed or not, with session counts
|
|
184
239
|
and total tokens, and a jump into each agent's sessions.
|
|
185
|
-
- **Search** — `cgrep` in the browser: one
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
- **
|
|
189
|
-
|
|
190
|
-
|
|
240
|
+
- **Search** — `cgrep` in the browser: one box, searches session ids, names,
|
|
241
|
+
paths <b>and</b> the content of every conversation at once; click a match to
|
|
242
|
+
open that conversation.
|
|
243
|
+
- **Sessions** — `cdir` for one agent (or all of them): newest, largest, or by
|
|
244
|
+
token usage, filterable.
|
|
245
|
+
- **Conversation** — `ccat` with the session's messages, token usage, the
|
|
246
|
+
resume command, concept extraction (`cextract`), a markdown export, and
|
|
247
|
+
**Copy** — `ccopy` to any other installed agent, which creates a fresh
|
|
191
248
|
session there (source untouched) and tells you how to resume it.
|
|
192
249
|
|
|
193
250
|
The server binds to `127.0.0.1` by default, so nothing is exposed to your
|
|
194
251
|
network unless you pass `--host 0.0.0.0`. There is one write operation:
|
|
195
252
|
Copy. Everything else is read-only against the agents' storage.
|
|
196
253
|
|
|
254
|
+
Stop it with **Ctrl-C**, or with the **power button** (⏻) in the top-right of
|
|
255
|
+
the page — it asks for confirmation, then shuts the server down cleanly.
|
|
256
|
+
|
|
197
257
|
```sh
|
|
198
258
|
ctools-web --port 9000
|
|
199
259
|
```
|
|
@@ -49,6 +49,7 @@ As you can see, these are unix-friendly tools using simple paradigms that you ar
|
|
|
49
49
|
| `cdir` | List sessions (ls for your history) | [docs/cdir.md](docs/cdir.md) |
|
|
50
50
|
| `ccat` | Print one or more conversations (cat for your history) | [docs/ccat.md](docs/ccat.md) |
|
|
51
51
|
| `ccopy` | Copy a whole conversation from one agent to a new session in another | [docs/ccopy.md](docs/ccopy.md) |
|
|
52
|
+
| `crecover` | List, verify, and restore the storage backups ccopy takes before each write | [docs/crecover.md](docs/crecover.md) |
|
|
52
53
|
| `cextract` | Extract concepts from a session into files, and inject them back | [docs/cextract.md](docs/cextract.md) |
|
|
53
54
|
| `cconnect` | Live concept pipelines between sessions | [docs/cconnect.md](docs/cconnect.md) |
|
|
54
55
|
| `cdu` | Token usage per session (du for context windows) | [docs/cdu.md](docs/cdu.md) |
|
|
@@ -88,6 +89,32 @@ graph LR
|
|
|
88
89
|
|
|
89
90
|
Context windows are endpoints: opencode, Claude Code, Codex, Pi. They all speak different protocols, but they all consume the same packets. See [cextract](docs/cextract.md) for the concept packet format, strategies, and the filter configs (decision model and JSON-RPC).
|
|
90
91
|
|
|
92
|
+
## Write safety
|
|
93
|
+
|
|
94
|
+
The session-mover tools write into your agents' **live** storage — for the
|
|
95
|
+
SQLite-backed agents (opencode, goose, kilo, hermes) that's the database that
|
|
96
|
+
holds every session of that agent. A write that fails or is interrupted
|
|
97
|
+
part-way can corrupt it, and losing an agent's whole history would be
|
|
98
|
+
unforgivable.
|
|
99
|
+
|
|
100
|
+
So every local write is guarded:
|
|
101
|
+
|
|
102
|
+
1. ctools **snapshots the agent's storage first** (SQLite via `VACUUM INTO` —
|
|
103
|
+
a consistent copy even mid-write; file agents via a tree copy), kept under
|
|
104
|
+
`~/.local/share/ctools/backups/<agent>/` (20 newest per agent).
|
|
105
|
+
2. After the write it **verifies** the storage: the DB opens,
|
|
106
|
+
`PRAGMA integrity_check` passes, the session table still exists, and the
|
|
107
|
+
session count did not decrease. A database that is structurally valid but
|
|
108
|
+
emptied of data is caught too.
|
|
109
|
+
3. If anything looks wrong, the snapshot is **restored automatically** — you
|
|
110
|
+
get a clean error, not a broken database.
|
|
111
|
+
|
|
112
|
+
And because a restore is itself a write, it snapshots what it overwrites, so
|
|
113
|
+
**every restore is undoable**. For the out-of-band case (you notice days later
|
|
114
|
+
that an agent's history vanished), `crecover list` / `verify` / `restore` are
|
|
115
|
+
there. Details: [ccopy — safety](docs/ccopy.md#safety-automatic-backups) and
|
|
116
|
+
[crecover](docs/crecover.md).
|
|
117
|
+
|
|
91
118
|
## Supported endpoints
|
|
92
119
|
|
|
93
120
|
| Agent | Storage |
|
|
@@ -134,23 +161,47 @@ Anything the storage cannot do — pi's session trees cannot be rewritten in pla
|
|
|
134
161
|
|
|
135
162
|
## MCP server
|
|
136
163
|
|
|
137
|
-
There is an MCP server for use from Claude, opencode, Cursor, or anything else that speaks MCP.
|
|
164
|
+
There is an MCP server for use from pi, Claude, opencode, Cursor, or anything else that speaks MCP.
|
|
138
165
|
|
|
139
166
|
Tools: `list_agents`, `list_sessions`, `search_sessions`, `export_session`, `extract_concepts`, `copy_concepts`, `get_session_concepts`.
|
|
140
167
|
|
|
141
|
-
|
|
168
|
+
### Quick install (pi)
|
|
169
|
+
|
|
170
|
+
One command does the whole job — install into an isolated venv (if needed) and register with pi:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
pipx run 'ctxttools[mcp]' ctools-mcp install
|
|
174
|
+
# or, with uv:
|
|
175
|
+
uv tool run 'ctxttools[mcp]' ctools-mcp install
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
It detects an existing `uv tool` or `pipx` install of `ctxttools` and uses it instead. Verify with:
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
pi mcp list # expect: ctools: connected, 7 tools
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Then use it inside pi — run `/mcp` to inspect, or just ask the agent (the tools are also reachable through codemode / tool search).
|
|
185
|
+
|
|
186
|
+
Uninstall with `ctools-mcp uninstall` (removes the pi entry; pass `--all` to also remove Claude Code and opencode entries).
|
|
187
|
+
|
|
188
|
+
### Other clients
|
|
189
|
+
|
|
190
|
+
Run `ctools-mcp print-config` to get the JSON snippet for any MCP host, e.g. for opencode (`opencode.json` under `"mcp"`) or Claude (`.mcp.json`):
|
|
142
191
|
|
|
143
192
|
```json
|
|
144
193
|
{
|
|
145
194
|
"mcpServers": {
|
|
146
195
|
"ctools": {
|
|
147
196
|
"command": "python",
|
|
148
|
-
"args": ["
|
|
197
|
+
"args": ["-m", "ctools.ctools_mcp"]
|
|
149
198
|
}
|
|
150
199
|
}
|
|
151
200
|
}
|
|
152
201
|
```
|
|
153
202
|
|
|
203
|
+
(`command` should point at the interpreter of a venv that has `ctxttools[mcp]` installed — `ctools-mcp install` prints the exact one for your machine.)
|
|
204
|
+
|
|
154
205
|
## Web dashboard
|
|
155
206
|
|
|
156
207
|
Prefer a mouse to a man page? ctools ships a browser dashboard — a small local
|
|
@@ -158,26 +209,35 @@ web server (Python stdlib only, no web dependencies) with the same library
|
|
|
158
209
|
underneath. Same on macOS, Windows, and Linux:
|
|
159
210
|
|
|
160
211
|
```sh
|
|
161
|
-
$ ctools-web #
|
|
212
|
+
$ ctools-web # installed command
|
|
213
|
+
# or, straight from a checkout, no install needed:
|
|
214
|
+
$ python3 ctools/ctools-web.py # or: python -m ctools.webui
|
|
162
215
|
ctools 0.2.1 dashboard: http://127.0.0.1:8765
|
|
163
216
|
```
|
|
164
217
|
|
|
165
|
-
Open the URL.
|
|
218
|
+
Open the URL. The navbar has **Overview**, **Sessions**, and **Concepts**, plus
|
|
219
|
+
a search box (press <b>/</b>) and a power button to stop the server. Clicking a
|
|
220
|
+
session anywhere opens its **Conversation** view:
|
|
166
221
|
|
|
167
222
|
- **Overview** — every supported agent, installed or not, with session counts
|
|
168
223
|
and total tokens, and a jump into each agent's sessions.
|
|
169
|
-
- **Search** — `cgrep` in the browser: one
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- **
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
- **Search** — `cgrep` in the browser: one box, searches session ids, names,
|
|
225
|
+
paths <b>and</b> the content of every conversation at once; click a match to
|
|
226
|
+
open that conversation.
|
|
227
|
+
- **Sessions** — `cdir` for one agent (or all of them): newest, largest, or by
|
|
228
|
+
token usage, filterable.
|
|
229
|
+
- **Conversation** — `ccat` with the session's messages, token usage, the
|
|
230
|
+
resume command, concept extraction (`cextract`), a markdown export, and
|
|
231
|
+
**Copy** — `ccopy` to any other installed agent, which creates a fresh
|
|
175
232
|
session there (source untouched) and tells you how to resume it.
|
|
176
233
|
|
|
177
234
|
The server binds to `127.0.0.1` by default, so nothing is exposed to your
|
|
178
235
|
network unless you pass `--host 0.0.0.0`. There is one write operation:
|
|
179
236
|
Copy. Everything else is read-only against the agents' storage.
|
|
180
237
|
|
|
238
|
+
Stop it with **Ctrl-C**, or with the **power button** (⏻) in the top-right of
|
|
239
|
+
the page — it asks for confirmation, then shuts the server down cleanly.
|
|
240
|
+
|
|
181
241
|
```sh
|
|
182
242
|
ctools-web --port 9000
|
|
183
243
|
```
|
|
@@ -27,12 +27,12 @@ import json
|
|
|
27
27
|
import shutil
|
|
28
28
|
import sqlite3
|
|
29
29
|
import time
|
|
30
|
-
from dataclasses import dataclass
|
|
30
|
+
from dataclasses import dataclass
|
|
31
31
|
from datetime import datetime
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
from typing import List, Optional
|
|
34
34
|
|
|
35
|
-
from ctools.agents import Agent
|
|
35
|
+
from ctools.agents import Agent
|
|
36
36
|
|
|
37
37
|
__all__ = [
|
|
38
38
|
"BackupError", "Backup", "Backups",
|
|
@@ -194,14 +194,15 @@ def _session_table(agent: Agent) -> Optional[str]:
|
|
|
194
194
|
try:
|
|
195
195
|
conn = sqlite3.connect(str(agent.storage_path))
|
|
196
196
|
try:
|
|
197
|
-
conn.execute(
|
|
197
|
+
row = conn.execute(
|
|
198
198
|
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
199
199
|
(name,)).fetchone()
|
|
200
|
-
return name
|
|
201
200
|
finally:
|
|
202
201
|
conn.close()
|
|
203
202
|
except sqlite3.Error:
|
|
204
|
-
|
|
203
|
+
row = None
|
|
204
|
+
if row is not None:
|
|
205
|
+
return name
|
|
205
206
|
return None
|
|
206
207
|
|
|
207
208
|
|
|
@@ -342,7 +343,9 @@ class Backups:
|
|
|
342
343
|
out.append(Backup.load(child))
|
|
343
344
|
except (OSError, json.JSONDecodeError, KeyError):
|
|
344
345
|
continue # skip a torn/corrupt manifest
|
|
345
|
-
|
|
346
|
+
# `when` has second precision; the dir name encodes milliseconds and
|
|
347
|
+
# a collision counter, so it breaks same-second ties in creation order.
|
|
348
|
+
out.sort(key=lambda b: (b.when, b.path.name), reverse=True)
|
|
346
349
|
return out
|
|
347
350
|
|
|
348
351
|
def all(self) -> List[Backup]:
|
|
@@ -351,7 +354,7 @@ class Backups:
|
|
|
351
354
|
for child in self.root.iterdir():
|
|
352
355
|
if child.is_dir():
|
|
353
356
|
out.extend(self.for_agent(child.name))
|
|
354
|
-
out.sort(key=lambda b: b.when, reverse=True)
|
|
357
|
+
out.sort(key=lambda b: (b.when, b.path.name), reverse=True)
|
|
355
358
|
return out
|
|
356
359
|
|
|
357
360
|
def prune(self, agent_name: str, keep: int) -> int:
|
|
@@ -37,6 +37,7 @@ from rich.console import Console
|
|
|
37
37
|
from rich.text import Text
|
|
38
38
|
|
|
39
39
|
from ctools.agents import REGISTRY as AGENTS, SessionNotFound, get_agent
|
|
40
|
+
from ctools.lib import install_clean_sigint
|
|
40
41
|
from ctools.cli import (handle_version, parse_ref, parse_remote_ref, reporting,
|
|
41
42
|
version_option)
|
|
42
43
|
from ctools.ccopy import _local_agent, _pull, _remote_common_doc, _remote_has_ctools
|
|
@@ -210,6 +211,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
def main(argv=None) -> int:
|
|
214
|
+
install_clean_sigint()
|
|
213
215
|
args = build_parser().parse_args(argv)
|
|
214
216
|
if (v := handle_version(args)) is not None:
|
|
215
217
|
return v
|
|
@@ -38,6 +38,7 @@ from ctools.agents import (
|
|
|
38
38
|
Agent, Message, REGISTRY,
|
|
39
39
|
get_agent, get_resume_command, SessionNotFound, UnsupportedOperation,
|
|
40
40
|
)
|
|
41
|
+
from ctools.lib import install_clean_sigint
|
|
41
42
|
from ctools.cli import (Remote, handle_version, parse_ref, parse_remote_ref,
|
|
42
43
|
version_option)
|
|
43
44
|
from ctools.backup import guard_write, BackupError
|
|
@@ -475,6 +476,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
475
476
|
|
|
476
477
|
|
|
477
478
|
def main(argv=None) -> int:
|
|
479
|
+
install_clean_sigint()
|
|
478
480
|
args = build_parser().parse_args(argv)
|
|
479
481
|
if (v := handle_version(args)) is not None:
|
|
480
482
|
return v
|