ccrecall 0.0.4
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.
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/package.json +43 -0
- package/src/cli.ts +113 -0
- package/src/db.ts +450 -0
- package/src/index.ts +6 -0
- package/src/parser.ts +208 -0
- package/src/sync-teams.ts +172 -0
- package/src/sync.ts +191 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Scott Spence
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# ccrecall
|
|
2
|
+
|
|
3
|
+
Sync Claude Code transcripts to SQLite for analytics.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Binary (recommended)
|
|
8
|
+
|
|
9
|
+
| File | Platform |
|
|
10
|
+
| --------------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
|
11
|
+
| [`ccrecall-linux-x64`](https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-linux-x64) | Linux (Intel/AMD) |
|
|
12
|
+
| [`ccrecall-linux-arm64`](https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-linux-arm64) | Linux (ARM, Raspberry Pi) |
|
|
13
|
+
| [`ccrecall-darwin-x64`](https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-darwin-x64) | macOS (Intel) |
|
|
14
|
+
| [`ccrecall-darwin-arm64`](https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-darwin-arm64) | macOS (Apple Silicon) |
|
|
15
|
+
| [`ccrecall-windows-x64.exe`](https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-windows-x64.exe) | Windows |
|
|
16
|
+
|
|
17
|
+
Or use curl:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Linux (x64)
|
|
21
|
+
curl -fsSL https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-linux-x64 -o ~/.local/bin/ccrecall && chmod +x ~/.local/bin/ccrecall
|
|
22
|
+
|
|
23
|
+
# Linux (arm64)
|
|
24
|
+
curl -fsSL https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-linux-arm64 -o ~/.local/bin/ccrecall && chmod +x ~/.local/bin/ccrecall
|
|
25
|
+
|
|
26
|
+
# macOS (Apple Silicon)
|
|
27
|
+
curl -fsSL https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-darwin-arm64 -o /usr/local/bin/ccrecall && chmod +x /usr/local/bin/ccrecall
|
|
28
|
+
|
|
29
|
+
# macOS (Intel)
|
|
30
|
+
curl -fsSL https://github.com/spences10/ccrecall/releases/latest/download/ccrecall-darwin-x64 -o /usr/local/bin/ccrecall && chmod +x /usr/local/bin/ccrecall
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### From source
|
|
34
|
+
|
|
35
|
+
Requires [Bun](https://bun.sh) >= 1.0:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/spences10/ccrecall.git
|
|
39
|
+
cd ccrecall
|
|
40
|
+
bun install
|
|
41
|
+
bun src/index.ts sync
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Sync transcripts from ~/.claude/projects to SQLite
|
|
48
|
+
ccrecall sync
|
|
49
|
+
|
|
50
|
+
# Show stats
|
|
51
|
+
ccrecall stats
|
|
52
|
+
|
|
53
|
+
# Help
|
|
54
|
+
ccrecall --help
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### From source
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bun src/index.ts sync
|
|
61
|
+
bun src/index.ts stats
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Commands
|
|
65
|
+
|
|
66
|
+
| Command | Description |
|
|
67
|
+
| ------- | ------------------------------------------ |
|
|
68
|
+
| `sync` | Import transcripts and teams (incremental) |
|
|
69
|
+
| `stats` | Show session/message/team/token counts |
|
|
70
|
+
|
|
71
|
+
### Options
|
|
72
|
+
|
|
73
|
+
| Flag | Description |
|
|
74
|
+
| ----------------- | ------------------------------------------------------- |
|
|
75
|
+
| `-v, --verbose` | Show files being processed |
|
|
76
|
+
| `-d, --db <path>` | Custom database path (default: `~/.claude/ccrecall.db`) |
|
|
77
|
+
|
|
78
|
+
## Database Schema
|
|
79
|
+
|
|
80
|
+
```mermaid
|
|
81
|
+
erDiagram
|
|
82
|
+
sessions ||--o{ messages : contains
|
|
83
|
+
sessions ||--o{ tool_calls : contains
|
|
84
|
+
sessions ||--o{ tool_results : contains
|
|
85
|
+
sessions ||--o| teams : "lead session"
|
|
86
|
+
messages ||--o{ tool_calls : has
|
|
87
|
+
messages ||--o{ tool_results : has
|
|
88
|
+
tool_calls ||--o{ tool_results : produces
|
|
89
|
+
teams ||--o{ team_members : has
|
|
90
|
+
teams ||--o{ team_tasks : has
|
|
91
|
+
|
|
92
|
+
sessions {
|
|
93
|
+
text id PK
|
|
94
|
+
text project_path
|
|
95
|
+
text git_branch
|
|
96
|
+
text cwd
|
|
97
|
+
int first_timestamp
|
|
98
|
+
int last_timestamp
|
|
99
|
+
text summary
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
messages {
|
|
103
|
+
text uuid PK
|
|
104
|
+
text session_id FK
|
|
105
|
+
text parent_uuid
|
|
106
|
+
text type
|
|
107
|
+
text model
|
|
108
|
+
text content_text
|
|
109
|
+
text content_json
|
|
110
|
+
text thinking
|
|
111
|
+
int timestamp
|
|
112
|
+
int input_tokens
|
|
113
|
+
int output_tokens
|
|
114
|
+
int cache_read_tokens
|
|
115
|
+
int cache_creation_tokens
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
tool_calls {
|
|
119
|
+
text id PK
|
|
120
|
+
text message_uuid FK
|
|
121
|
+
text session_id FK
|
|
122
|
+
text tool_name
|
|
123
|
+
text tool_input
|
|
124
|
+
int timestamp
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
tool_results {
|
|
128
|
+
int id PK
|
|
129
|
+
text tool_call_id FK
|
|
130
|
+
text message_uuid FK
|
|
131
|
+
text session_id FK
|
|
132
|
+
text content
|
|
133
|
+
int is_error
|
|
134
|
+
int timestamp
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
teams {
|
|
138
|
+
text id PK
|
|
139
|
+
text name
|
|
140
|
+
text description
|
|
141
|
+
text lead_session_id FK
|
|
142
|
+
int created_at
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
team_members {
|
|
146
|
+
text id PK
|
|
147
|
+
text team_id FK
|
|
148
|
+
text name
|
|
149
|
+
text agent_type
|
|
150
|
+
text model
|
|
151
|
+
text prompt
|
|
152
|
+
text color
|
|
153
|
+
text cwd
|
|
154
|
+
int joined_at
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
team_tasks {
|
|
158
|
+
text id PK
|
|
159
|
+
text team_id FK
|
|
160
|
+
text owner_name
|
|
161
|
+
text subject
|
|
162
|
+
text description
|
|
163
|
+
text status
|
|
164
|
+
int created_at
|
|
165
|
+
int completed_at
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
sync_state {
|
|
169
|
+
text file_path PK
|
|
170
|
+
int last_modified
|
|
171
|
+
int last_byte_offset
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Team/Swarm Support
|
|
176
|
+
|
|
177
|
+
Syncs team data from `~/.claude/teams/` when Claude Code's swarm mode
|
|
178
|
+
is enabled.
|
|
179
|
+
|
|
180
|
+
**Why track teams?**
|
|
181
|
+
|
|
182
|
+
- Debug runaway agents: compare `prompt` (original instructions) vs
|
|
183
|
+
actual behavior
|
|
184
|
+
- Link swarm runs to sessions and PRs
|
|
185
|
+
- Track task assignments and completion
|
|
186
|
+
|
|
187
|
+
## Example Queries
|
|
188
|
+
|
|
189
|
+
```sql
|
|
190
|
+
-- Token usage by project
|
|
191
|
+
SELECT project_path, SUM(input_tokens + output_tokens) as tokens
|
|
192
|
+
FROM sessions s
|
|
193
|
+
JOIN messages m ON m.session_id = s.id
|
|
194
|
+
GROUP BY project_path
|
|
195
|
+
ORDER BY tokens DESC;
|
|
196
|
+
|
|
197
|
+
-- Daily message count
|
|
198
|
+
SELECT DATE(timestamp/1000, 'unixepoch') as day, COUNT(*) as messages
|
|
199
|
+
FROM messages
|
|
200
|
+
GROUP BY day
|
|
201
|
+
ORDER BY day DESC;
|
|
202
|
+
|
|
203
|
+
-- Most used models
|
|
204
|
+
SELECT model, COUNT(*) as count
|
|
205
|
+
FROM messages
|
|
206
|
+
WHERE model IS NOT NULL
|
|
207
|
+
GROUP BY model
|
|
208
|
+
ORDER BY count DESC;
|
|
209
|
+
|
|
210
|
+
-- Tool usage breakdown
|
|
211
|
+
SELECT tool_name, COUNT(*) as count
|
|
212
|
+
FROM tool_calls
|
|
213
|
+
GROUP BY tool_name
|
|
214
|
+
ORDER BY count DESC;
|
|
215
|
+
|
|
216
|
+
-- Files read in a session
|
|
217
|
+
SELECT tc.tool_name, json_extract(tc.tool_input, '$.file_path') as file
|
|
218
|
+
FROM tool_calls tc
|
|
219
|
+
WHERE tc.tool_name = 'Read' AND tc.session_id = 'your-session-id';
|
|
220
|
+
|
|
221
|
+
-- Code changes (edits) with before/after
|
|
222
|
+
SELECT
|
|
223
|
+
json_extract(tc.tool_input, '$.file_path') as file,
|
|
224
|
+
json_extract(tc.tool_input, '$.old_string') as old,
|
|
225
|
+
json_extract(tc.tool_input, '$.new_string') as new
|
|
226
|
+
FROM tool_calls tc
|
|
227
|
+
WHERE tc.tool_name = 'Edit';
|
|
228
|
+
|
|
229
|
+
-- Session cost estimate (Opus 4.5)
|
|
230
|
+
SELECT
|
|
231
|
+
s.project_path,
|
|
232
|
+
SUM(m.input_tokens) / 1000000.0 * 15 +
|
|
233
|
+
SUM(m.output_tokens) / 1000000.0 * 75 +
|
|
234
|
+
SUM(m.cache_read_tokens) / 1000000.0 * 1.5 +
|
|
235
|
+
SUM(m.cache_creation_tokens) / 1000000.0 * 18.75 as cost_usd
|
|
236
|
+
FROM sessions s
|
|
237
|
+
JOIN messages m ON m.session_id = s.id
|
|
238
|
+
WHERE m.model LIKE '%opus%'
|
|
239
|
+
GROUP BY s.id
|
|
240
|
+
ORDER BY cost_usd DESC;
|
|
241
|
+
|
|
242
|
+
-- Teams with member count
|
|
243
|
+
SELECT t.name, t.description, COUNT(tm.id) as members
|
|
244
|
+
FROM teams t
|
|
245
|
+
LEFT JOIN team_members tm ON tm.team_id = t.id
|
|
246
|
+
GROUP BY t.id;
|
|
247
|
+
|
|
248
|
+
-- Agent prompts for debugging (what were they told to do?)
|
|
249
|
+
SELECT name, prompt FROM team_members WHERE team_id = 'your-team-id';
|
|
250
|
+
|
|
251
|
+
-- Task status by team
|
|
252
|
+
SELECT team_id, status, COUNT(*) as count
|
|
253
|
+
FROM team_tasks
|
|
254
|
+
GROUP BY team_id, status;
|
|
255
|
+
|
|
256
|
+
-- Link Teammate tool calls to team configs
|
|
257
|
+
SELECT tc.timestamp, t.name, t.description
|
|
258
|
+
FROM tool_calls tc
|
|
259
|
+
JOIN teams t ON json_extract(tc.tool_input, '$.team_name') = t.name
|
|
260
|
+
WHERE tc.tool_name = 'Teammate';
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccrecall",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "Sync Claude Code transcripts to SQLite and recall context from past sessions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ccrecall": "./src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "bun src/index.ts",
|
|
11
|
+
"start": "bun src/index.ts",
|
|
12
|
+
"build": "bun build --compile src/index.ts --outfile ccrecall",
|
|
13
|
+
"test": "bun test",
|
|
14
|
+
"format": "prettier --write .",
|
|
15
|
+
"format:check": "prettier --check .",
|
|
16
|
+
"lint": "bun run --bun tsc --noEmit",
|
|
17
|
+
"changeset": "changeset",
|
|
18
|
+
"version": "changeset version",
|
|
19
|
+
"release": "bun run build && changeset publish"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@changesets/cli": "^2.29.8",
|
|
23
|
+
"@types/bun": "latest",
|
|
24
|
+
"prettier": "^3.8.1",
|
|
25
|
+
"typescript": "^5.9.3"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"claude",
|
|
32
|
+
"claude-code",
|
|
33
|
+
"analytics",
|
|
34
|
+
"sqlite",
|
|
35
|
+
"memory",
|
|
36
|
+
"context",
|
|
37
|
+
"recall"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"citty": "^0.2.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_DB_PATH = join(Bun.env.HOME!, '.claude', 'ccrecall.db');
|
|
5
|
+
|
|
6
|
+
const sharedArgs = {
|
|
7
|
+
db: {
|
|
8
|
+
type: 'string' as const,
|
|
9
|
+
alias: 'd',
|
|
10
|
+
description: `Database path (default: ${DEFAULT_DB_PATH})`,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const sync = defineCommand({
|
|
15
|
+
meta: {
|
|
16
|
+
name: 'sync',
|
|
17
|
+
description: 'Sync Claude Code transcripts to database',
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
...sharedArgs,
|
|
21
|
+
verbose: {
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
alias: 'v',
|
|
24
|
+
description: 'Show detailed output',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
async run({ args }) {
|
|
28
|
+
const { Database } = await import('./db.ts');
|
|
29
|
+
const { sync: syncTranscripts } = await import('./sync.ts');
|
|
30
|
+
const { sync_teams } = await import('./sync-teams.ts');
|
|
31
|
+
|
|
32
|
+
const db_path = args.db ?? DEFAULT_DB_PATH;
|
|
33
|
+
const db = new Database(db_path);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
console.log('Syncing transcripts...');
|
|
37
|
+
const result = await syncTranscripts(db, args.verbose);
|
|
38
|
+
console.log('Syncing teams...');
|
|
39
|
+
const team_result = await sync_teams(db, args.verbose);
|
|
40
|
+
console.log(`
|
|
41
|
+
Done!
|
|
42
|
+
Files scanned: ${result.files_scanned}
|
|
43
|
+
Files processed: ${result.files_processed}
|
|
44
|
+
Messages added: ${result.messages_added}
|
|
45
|
+
Sessions found: ${result.sessions_added}
|
|
46
|
+
Tool calls: ${result.tool_calls_added}
|
|
47
|
+
Tool results: ${result.tool_results_added}
|
|
48
|
+
Teams synced: ${team_result.teams_synced}
|
|
49
|
+
Team members: ${team_result.members_synced}
|
|
50
|
+
Team tasks: ${team_result.tasks_synced}
|
|
51
|
+
`);
|
|
52
|
+
} finally {
|
|
53
|
+
db.close();
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const stats = defineCommand({
|
|
59
|
+
meta: {
|
|
60
|
+
name: 'stats',
|
|
61
|
+
description: 'Show database statistics',
|
|
62
|
+
},
|
|
63
|
+
args: {
|
|
64
|
+
...sharedArgs,
|
|
65
|
+
},
|
|
66
|
+
async run({ args }) {
|
|
67
|
+
const { Database } = await import('./db.ts');
|
|
68
|
+
|
|
69
|
+
const db_path = args.db ?? DEFAULT_DB_PATH;
|
|
70
|
+
const db = new Database(db_path);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const s = db.get_stats();
|
|
74
|
+
console.log(`
|
|
75
|
+
Database: ${db_path}
|
|
76
|
+
Sessions: ${s.sessions}
|
|
77
|
+
Messages: ${s.messages}
|
|
78
|
+
Tool calls: ${s.tool_calls}
|
|
79
|
+
Tool results: ${s.tool_results}
|
|
80
|
+
Teams: ${s.teams}
|
|
81
|
+
Team members: ${s.team_members}
|
|
82
|
+
Team tasks: ${s.team_tasks}
|
|
83
|
+
Tokens:
|
|
84
|
+
Input: ${s.tokens.input?.toLocaleString() ?? 0}
|
|
85
|
+
Output: ${s.tokens.output?.toLocaleString() ?? 0}
|
|
86
|
+
Cache read: ${s.tokens.cache_read?.toLocaleString() ?? 0}
|
|
87
|
+
Cache creation: ${s.tokens.cache_creation?.toLocaleString() ?? 0}
|
|
88
|
+
`);
|
|
89
|
+
} finally {
|
|
90
|
+
db.close();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const main = defineCommand({
|
|
96
|
+
meta: {
|
|
97
|
+
name: 'ccrecall',
|
|
98
|
+
version: '0.0.3',
|
|
99
|
+
description:
|
|
100
|
+
'Sync Claude Code transcripts to SQLite and recall context from past sessions',
|
|
101
|
+
},
|
|
102
|
+
args: {
|
|
103
|
+
db: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
alias: 'd',
|
|
106
|
+
description: `Database path (default: ${DEFAULT_DB_PATH})`,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
subCommands: {
|
|
110
|
+
sync,
|
|
111
|
+
stats,
|
|
112
|
+
},
|
|
113
|
+
});
|