ai-cli-toolkit 0.2.0__py3-none-any.whl
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.
- ai_cli/__init__.py +3 -0
- ai_cli/__main__.py +6 -0
- ai_cli/bin/ai-mux-linux-x86_64 +0 -0
- ai_cli/bin/remote-tty-wrapper +153 -0
- ai_cli/ca.py +175 -0
- ai_cli/completion_gen.py +680 -0
- ai_cli/config.py +185 -0
- ai_cli/credentials.py +341 -0
- ai_cli/detached_cleanup.py +135 -0
- ai_cli/housekeeping.py +50 -0
- ai_cli/instructions.py +308 -0
- ai_cli/log.py +53 -0
- ai_cli/main.py +1516 -0
- ai_cli/main_helpers.py +553 -0
- ai_cli/prompt_editor_launcher.py +324 -0
- ai_cli/proxy.py +627 -0
- ai_cli/remote.py +669 -0
- ai_cli/remote_package.py +1111 -0
- ai_cli/session.py +1344 -0
- ai_cli/session_store.py +236 -0
- ai_cli/traffic.py +1510 -0
- ai_cli/traffic_db.py +118 -0
- ai_cli/tui.py +525 -0
- ai_cli/update.py +200 -0
- ai_cli_toolkit-0.2.0.dist-info/METADATA +17 -0
- ai_cli_toolkit-0.2.0.dist-info/RECORD +30 -0
- ai_cli_toolkit-0.2.0.dist-info/WHEEL +5 -0
- ai_cli_toolkit-0.2.0.dist-info/entry_points.txt +2 -0
- ai_cli_toolkit-0.2.0.dist-info/licenses/LICENSE +21 -0
- ai_cli_toolkit-0.2.0.dist-info/top_level.txt +1 -0
ai_cli/session_store.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Copilot session-store SQLite access helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sqlite3
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
# Default location of the Copilot CLI session store database.
|
|
11
|
+
SESSION_STORE_PATHS = (
|
|
12
|
+
Path.home() / ".copilot" / "session-store.db",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _normalize_cwd(value: str) -> str:
|
|
17
|
+
text = value.strip()
|
|
18
|
+
if not text:
|
|
19
|
+
return ""
|
|
20
|
+
try:
|
|
21
|
+
path = Path(text).expanduser()
|
|
22
|
+
if path.exists():
|
|
23
|
+
return str(path.resolve())
|
|
24
|
+
return str(path)
|
|
25
|
+
except OSError:
|
|
26
|
+
return text
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def find_session_store_db(path: str = "") -> Path | None:
|
|
30
|
+
"""Locate the Copilot CLI session store database."""
|
|
31
|
+
if path:
|
|
32
|
+
p = Path(path).expanduser()
|
|
33
|
+
if p.is_file():
|
|
34
|
+
return p
|
|
35
|
+
for candidate in SESSION_STORE_PATHS:
|
|
36
|
+
if candidate.is_file():
|
|
37
|
+
return candidate
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _connect_store(db_path: Path) -> sqlite3.Connection:
|
|
42
|
+
"""Open a read-only connection to the session store."""
|
|
43
|
+
uri = f"file:{db_path}?mode=ro"
|
|
44
|
+
conn = sqlite3.connect(uri, uri=True)
|
|
45
|
+
conn.row_factory = sqlite3.Row
|
|
46
|
+
return conn
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class StoreSession:
|
|
51
|
+
"""A session record from the session store database."""
|
|
52
|
+
|
|
53
|
+
id: str
|
|
54
|
+
cwd: str
|
|
55
|
+
repository: str
|
|
56
|
+
branch: str
|
|
57
|
+
summary: str
|
|
58
|
+
created_at: str
|
|
59
|
+
updated_at: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def list_store_sessions(
|
|
63
|
+
db_path: Path,
|
|
64
|
+
cwd: str = "",
|
|
65
|
+
branch: str = "",
|
|
66
|
+
limit: int = 50,
|
|
67
|
+
) -> list[StoreSession]:
|
|
68
|
+
"""List sessions from the store, optionally filtered by cwd or branch."""
|
|
69
|
+
conn = _connect_store(db_path)
|
|
70
|
+
try:
|
|
71
|
+
clauses: list[str] = []
|
|
72
|
+
params: list[Any] = []
|
|
73
|
+
if cwd:
|
|
74
|
+
norm = _normalize_cwd(cwd)
|
|
75
|
+
clauses.append("(cwd = ? OR cwd LIKE ?)")
|
|
76
|
+
params.extend([norm, norm + "/%"])
|
|
77
|
+
if branch:
|
|
78
|
+
clauses.append("branch = ?")
|
|
79
|
+
params.append(branch)
|
|
80
|
+
where = (" WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
81
|
+
query = (
|
|
82
|
+
"SELECT id, cwd, repository, branch, summary, created_at, updated_at "
|
|
83
|
+
f"FROM sessions{where} ORDER BY created_at DESC LIMIT ?"
|
|
84
|
+
)
|
|
85
|
+
params.append(limit)
|
|
86
|
+
rows = conn.execute(query, params).fetchall()
|
|
87
|
+
return [
|
|
88
|
+
StoreSession(
|
|
89
|
+
id=r["id"],
|
|
90
|
+
cwd=r["cwd"] or "",
|
|
91
|
+
repository=r["repository"] or "",
|
|
92
|
+
branch=r["branch"] or "",
|
|
93
|
+
summary=r["summary"] or "",
|
|
94
|
+
created_at=r["created_at"] or "",
|
|
95
|
+
updated_at=r["updated_at"] or "",
|
|
96
|
+
)
|
|
97
|
+
for r in rows
|
|
98
|
+
]
|
|
99
|
+
finally:
|
|
100
|
+
conn.close()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def query_store_turns(
|
|
104
|
+
db_path: Path,
|
|
105
|
+
session_id: str = "",
|
|
106
|
+
grep: str = "",
|
|
107
|
+
limit: int = 200,
|
|
108
|
+
) -> list[dict[str, Any]]:
|
|
109
|
+
"""Retrieve conversation turns from the session store."""
|
|
110
|
+
conn = _connect_store(db_path)
|
|
111
|
+
try:
|
|
112
|
+
clauses: list[str] = []
|
|
113
|
+
params: list[Any] = []
|
|
114
|
+
if session_id:
|
|
115
|
+
clauses.append("t.session_id = ?")
|
|
116
|
+
params.append(session_id)
|
|
117
|
+
if grep:
|
|
118
|
+
clauses.append("(t.user_message LIKE ? OR t.assistant_response LIKE ?)")
|
|
119
|
+
needle = f"%{grep}%"
|
|
120
|
+
params.extend([needle, needle])
|
|
121
|
+
where = (" WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
122
|
+
query = (
|
|
123
|
+
"SELECT t.session_id, t.turn_index, t.user_message, t.assistant_response, "
|
|
124
|
+
"t.timestamp, s.cwd, s.branch "
|
|
125
|
+
"FROM turns t JOIN sessions s ON t.session_id = s.id"
|
|
126
|
+
f"{where} ORDER BY t.timestamp, t.turn_index LIMIT ?"
|
|
127
|
+
)
|
|
128
|
+
params.append(limit)
|
|
129
|
+
rows = conn.execute(query, params).fetchall()
|
|
130
|
+
|
|
131
|
+
messages: list[dict[str, Any]] = []
|
|
132
|
+
for r in rows:
|
|
133
|
+
sid = r["session_id"]
|
|
134
|
+
ts = r["timestamp"] or ""
|
|
135
|
+
if r["user_message"]:
|
|
136
|
+
messages.append({
|
|
137
|
+
"agent": "copilot",
|
|
138
|
+
"role": "user",
|
|
139
|
+
"type": "text",
|
|
140
|
+
"content": r["user_message"],
|
|
141
|
+
"line": r["turn_index"],
|
|
142
|
+
"timestamp": ts,
|
|
143
|
+
"file": f"session-store:{sid}",
|
|
144
|
+
"session_id": sid,
|
|
145
|
+
})
|
|
146
|
+
if r["assistant_response"]:
|
|
147
|
+
messages.append({
|
|
148
|
+
"agent": "copilot",
|
|
149
|
+
"role": "assistant",
|
|
150
|
+
"type": "text",
|
|
151
|
+
"content": r["assistant_response"],
|
|
152
|
+
"line": r["turn_index"],
|
|
153
|
+
"timestamp": ts,
|
|
154
|
+
"file": f"session-store:{sid}",
|
|
155
|
+
"session_id": sid,
|
|
156
|
+
})
|
|
157
|
+
return messages
|
|
158
|
+
finally:
|
|
159
|
+
conn.close()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def search_store(
|
|
163
|
+
db_path: Path,
|
|
164
|
+
query: str,
|
|
165
|
+
limit: int = 30,
|
|
166
|
+
) -> list[dict[str, Any]]:
|
|
167
|
+
"""Full-text search across the session store using FTS5."""
|
|
168
|
+
conn = _connect_store(db_path)
|
|
169
|
+
try:
|
|
170
|
+
rows = conn.execute(
|
|
171
|
+
"SELECT content, session_id, source_type, source_id "
|
|
172
|
+
"FROM search_index WHERE search_index MATCH ? "
|
|
173
|
+
"ORDER BY rank LIMIT ?",
|
|
174
|
+
[query, limit],
|
|
175
|
+
).fetchall()
|
|
176
|
+
|
|
177
|
+
results: list[dict[str, Any]] = []
|
|
178
|
+
for r in rows:
|
|
179
|
+
results.append({
|
|
180
|
+
"agent": "copilot",
|
|
181
|
+
"role": "assistant",
|
|
182
|
+
"type": "text",
|
|
183
|
+
"content": r["content"][:2000] if r["content"] else "",
|
|
184
|
+
"line": 0,
|
|
185
|
+
"timestamp": "",
|
|
186
|
+
"file": f"session-store:{r['session_id']}",
|
|
187
|
+
"session_id": r["session_id"],
|
|
188
|
+
"source_type": r["source_type"],
|
|
189
|
+
})
|
|
190
|
+
return results
|
|
191
|
+
finally:
|
|
192
|
+
conn.close()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def query_store_checkpoints(
|
|
196
|
+
db_path: Path,
|
|
197
|
+
session_id: str,
|
|
198
|
+
) -> list[dict[str, Any]]:
|
|
199
|
+
"""Retrieve checkpoints for a session from the store."""
|
|
200
|
+
conn = _connect_store(db_path)
|
|
201
|
+
try:
|
|
202
|
+
rows = conn.execute(
|
|
203
|
+
"SELECT checkpoint_number, title, overview, work_done, next_steps "
|
|
204
|
+
"FROM checkpoints WHERE session_id = ? ORDER BY checkpoint_number",
|
|
205
|
+
[session_id],
|
|
206
|
+
).fetchall()
|
|
207
|
+
return [dict(r) for r in rows]
|
|
208
|
+
finally:
|
|
209
|
+
conn.close()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def query_store_files(
|
|
213
|
+
db_path: Path,
|
|
214
|
+
session_id: str = "",
|
|
215
|
+
file_pattern: str = "",
|
|
216
|
+
) -> list[dict[str, Any]]:
|
|
217
|
+
"""Retrieve file records from the session store."""
|
|
218
|
+
conn = _connect_store(db_path)
|
|
219
|
+
try:
|
|
220
|
+
clauses: list[str] = []
|
|
221
|
+
params: list[Any] = []
|
|
222
|
+
if session_id:
|
|
223
|
+
clauses.append("sf.session_id = ?")
|
|
224
|
+
params.append(session_id)
|
|
225
|
+
if file_pattern:
|
|
226
|
+
clauses.append("sf.file_path LIKE ?")
|
|
227
|
+
params.append(f"%{file_pattern}%")
|
|
228
|
+
where = (" WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
229
|
+
rows = conn.execute(
|
|
230
|
+
"SELECT sf.session_id, sf.file_path, sf.tool_name, sf.first_seen_at "
|
|
231
|
+
f"FROM session_files sf{where} ORDER BY sf.first_seen_at DESC LIMIT 100",
|
|
232
|
+
params,
|
|
233
|
+
).fetchall()
|
|
234
|
+
return [dict(r) for r in rows]
|
|
235
|
+
finally:
|
|
236
|
+
conn.close()
|