agentacta 1.3.4 → 1.5.0
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/README.md +100 -101
- package/db.js +1 -0
- package/index.js +90 -3
- package/package.json +1 -1
- package/public/app.js +773 -55
- package/public/index.html +17 -5
- package/public/style.css +519 -3
package/README.md
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/agentacta)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
**Your
|
|
7
|
+
**Your agent did 1000s of things today. Can you find the 1 that broke prod?**
|
|
8
8
|
|
|
9
|
-
AgentActa is
|
|
9
|
+
AgentActa is a local audit trail and search engine for AI agent sessions.
|
|
10
|
+
|
|
11
|
+
It indexes messages, tool calls, file edits, searches, and decisions into a fast UI you can query in seconds.
|
|
10
12
|
|
|
11
13
|
One command. Zero config. Full visibility.
|
|
12
14
|
|
|
@@ -18,95 +20,105 @@ npx agentacta
|
|
|
18
20
|
<img src="screenshots/demo.gif" alt="AgentActa demo" width="800">
|
|
19
21
|
</p>
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Why
|
|
24
|
-
|
|
25
|
-
AI agents are powerful. They write code, send emails, manage infrastructure, make decisions on your behalf. But when you need to know *what happened* — what was changed, when, and why — you're digging through scattered logs or asking the agent to remember (it won't).
|
|
23
|
+
## Why this exists
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
Agents move fast. Your memory of what happened doesn’t.
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
When you need to answer “what changed, when, and why,” you’re usually scraping logs, scrolling transcripts, or asking the same assistant that forgot 20 minutes ago.
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
📋 **Session browser** with summaries, token breakdowns (input/output), and model info
|
|
33
|
-
📅 **Timeline view** — everything that happened on any given day
|
|
34
|
-
📁 **File activity** — every file your agent touched, across all sessions
|
|
35
|
-
📊 **Stats** — sessions, messages, tool usage, token counts
|
|
36
|
-
⚡ **Live indexing** — new sessions appear automatically via file watching
|
|
37
|
-
📱 **Mobile-friendly** — responsive UI with bottom tab navigation
|
|
38
|
-
💡 **Smart suggestions** — quick search chips derived from your actual session data
|
|
29
|
+
AgentActa gives you one place to inspect the full trail.
|
|
39
30
|
|
|
40
|
-
##
|
|
31
|
+
## What you get
|
|
41
32
|
|
|
42
|
-
|
|
33
|
+
- 🔍 Full-text search across messages, tool calls, and results
|
|
34
|
+
- 📋 Session browser with summaries, token breakdowns, and model info
|
|
35
|
+
- 📅 Timeline view with live updates for today
|
|
36
|
+
- 📁 File activity across all indexed sessions
|
|
37
|
+
- 🌗 Light and dark themes
|
|
38
|
+
- 📊 Stats for sessions, messages, tools, and tokens
|
|
39
|
+
- ⚡ Live indexing via file watching
|
|
40
|
+
- 📱 Mobile-friendly UI
|
|
41
|
+
- 💡 Search suggestions based on real data
|
|
42
|
+
- ⌨️ Command palette (⌘K / Ctrl+K) for quick navigation
|
|
43
|
+
- 🎨 Theme settings (system, light, dark, OLED)
|
|
44
|
+
- 🏥 Health endpoint for monitoring (`/api/health`)
|
|
43
45
|
|
|
44
|
-
## Screenshots
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-

|
|
48
|
-

|
|
49
|
-

|
|
50
|
-

|
|
51
|
-

|
|
52
|
-

|
|
53
|
-
|
|
54
|
-
## Quick Start
|
|
47
|
+
## Quick start
|
|
55
48
|
|
|
56
49
|
```bash
|
|
57
|
-
#
|
|
50
|
+
# run directly
|
|
58
51
|
npx agentacta
|
|
59
52
|
|
|
60
|
-
#
|
|
53
|
+
# or install globally
|
|
61
54
|
npm install -g agentacta
|
|
62
55
|
agentacta
|
|
63
56
|
```
|
|
64
57
|
|
|
65
|
-
Open `http://localhost:4003`
|
|
58
|
+
Open: `http://localhost:4003`
|
|
66
59
|
|
|
67
|
-
|
|
60
|
+
Auto-detected session paths:
|
|
68
61
|
- `~/.openclaw/agents/*/sessions/` (OpenClaw)
|
|
69
62
|
- `~/.claude/projects/*/` (Claude Code)
|
|
70
63
|
- `~/.codex/sessions/` (Codex CLI)
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
Custom path:
|
|
73
66
|
|
|
74
67
|
```bash
|
|
75
68
|
AGENTACTA_SESSIONS_PATH=/path/to/sessions agentacta
|
|
76
69
|
```
|
|
77
70
|
|
|
78
|
-
##
|
|
71
|
+
## Core features
|
|
79
72
|
|
|
80
73
|
### Search
|
|
81
|
-
|
|
74
|
+
|
|
75
|
+
SQLite FTS5 full-text search with filters for message type (messages, tool calls, results) and role (user, assistant).
|
|
76
|
+
|
|
77
|
+
Suggestions come from your own dataset: top tools, common topics, frequently touched files.
|
|
82
78
|
|
|
83
79
|
### Sessions
|
|
84
|
-
|
|
80
|
+
|
|
81
|
+
Browse indexed sessions with auto-generated summaries, token splits (input/output), and model details. Click into any session to see the full event history.
|
|
82
|
+
|
|
83
|
+
Session types get tagged so noisy categories are easier to spot (cron, sub-agent, heartbeat).
|
|
85
84
|
|
|
86
85
|
### Timeline
|
|
87
|
-
|
|
86
|
+
|
|
87
|
+
Pick a date, see everything that happened, newest first. Today's view updates live as new events come in.
|
|
88
88
|
|
|
89
89
|
### File Activity
|
|
90
|
-
|
|
90
|
+
|
|
91
|
+
See what files were touched, how often, and by which sessions.
|
|
92
|
+
|
|
93
|
+
Sort by recency, frequency, or session count. Filter by extension. Group by directory. Click any file to see which sessions touched it.
|
|
91
94
|
|
|
92
95
|
### Export
|
|
93
|
-
Download any session or search results as Markdown or JSON. Great for sharing, auditing, or archiving.
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
Export sessions or search results as Markdown or JSON.
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
Useful for handoffs, incident writeups, and audit archives.
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
## How it works
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
AgentActa parses JSONL session files (OpenClaw, Claude Code, Codex CLI), then indexes events into local SQLite with FTS5.
|
|
104
|
+
|
|
105
|
+
The UI is a single-page app served by a lightweight Node HTTP server.
|
|
106
|
+
|
|
107
|
+
No framework build pipeline. Minimal moving parts.
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
Session JSONL files -> SQLite + FTS5 index -> HTTP API -> Web UI
|
|
103
111
|
```
|
|
104
112
|
|
|
105
|
-
|
|
113
|
+
Everything stays on your machine.
|
|
106
114
|
|
|
107
115
|
## Configuration
|
|
108
116
|
|
|
109
|
-
On first run, AgentActa creates
|
|
117
|
+
On first run, AgentActa creates:
|
|
118
|
+
- `~/.config/agentacta/config.json`
|
|
119
|
+
- or `agentacta.config.json` in current directory (if present)
|
|
120
|
+
|
|
121
|
+
Default config:
|
|
110
122
|
|
|
111
123
|
```json
|
|
112
124
|
{
|
|
@@ -118,85 +130,70 @@ On first run, AgentActa creates a config file with sensible defaults at `~/.conf
|
|
|
118
130
|
}
|
|
119
131
|
```
|
|
120
132
|
|
|
121
|
-
### Storage
|
|
133
|
+
### Storage modes
|
|
122
134
|
|
|
123
|
-
-
|
|
124
|
-
-
|
|
135
|
+
- `reference` (default): index parsed events in SQLite, keep source JSONL on disk. Lightweight.
|
|
136
|
+
- `archive`: store full JSONL in SQLite. Sessions survive even if original files are deleted. Uses more disk.
|
|
125
137
|
|
|
126
|
-
### Environment
|
|
138
|
+
### Environment variables
|
|
127
139
|
|
|
128
140
|
| Variable | Default | Description |
|
|
129
141
|
|---|---|---|
|
|
130
142
|
| `PORT` | `4003` | Server port |
|
|
131
|
-
| `AGENTACTA_HOST` | `127.0.0.1` | Bind address
|
|
132
|
-
| `AGENTACTA_SESSIONS_PATH` |
|
|
133
|
-
| `AGENTACTA_DB_PATH` | `./agentacta.db` | Database
|
|
134
|
-
| `AGENTACTA_STORAGE` | `reference` |
|
|
135
|
-
| `AGENTACTA_PROJECT_ALIASES_JSON` | unset |
|
|
143
|
+
| `AGENTACTA_HOST` | `127.0.0.1` | Bind address |
|
|
144
|
+
| `AGENTACTA_SESSIONS_PATH` | auto-detected | Custom sessions directory |
|
|
145
|
+
| `AGENTACTA_DB_PATH` | `./agentacta.db` | Database path |
|
|
146
|
+
| `AGENTACTA_STORAGE` | `reference` | `reference` or `archive` |
|
|
147
|
+
| `AGENTACTA_PROJECT_ALIASES_JSON` | unset | Rename inferred project labels |
|
|
136
148
|
|
|
137
149
|
## API
|
|
138
150
|
|
|
139
|
-
AgentActa exposes a JSON API for programmatic access — useful for integrating search into your agent's workflow.
|
|
140
|
-
|
|
141
151
|
| Endpoint | Description |
|
|
142
152
|
|---|---|
|
|
143
|
-
| `GET /api/stats` |
|
|
144
|
-
| `GET /api/sessions` |
|
|
145
|
-
| `GET /api/sessions/:id` | Full session
|
|
146
|
-
| `GET /api/search?q=<query>` | Full-text search
|
|
147
|
-
| `GET /api/suggestions` |
|
|
148
|
-
| `GET /api/timeline?date=YYYY-MM-DD` |
|
|
149
|
-
| `GET /api/files` |
|
|
150
|
-
| `GET /api/export/session/:id?format=md` | Export session
|
|
153
|
+
| `GET /api/stats` | Session/message/tool/token totals |
|
|
154
|
+
| `GET /api/sessions` | Session list with metadata |
|
|
155
|
+
| `GET /api/sessions/:id` | Full session events |
|
|
156
|
+
| `GET /api/search?q=<query>` | Full-text search + filters |
|
|
157
|
+
| `GET /api/suggestions` | Search suggestions |
|
|
158
|
+
| `GET /api/timeline?date=YYYY-MM-DD` | Events for one day |
|
|
159
|
+
| `GET /api/files` | Touched-file inventory |
|
|
160
|
+
| `GET /api/export/session/:id?format=md` | Export one session |
|
|
161
|
+
| `GET /api/timeline/stream?after=<ts>` | SSE stream for live timeline updates |
|
|
162
|
+
| `POST /api/maintenance` | VACUUM + WAL checkpoint (returns size before/after) |
|
|
163
|
+
| `GET /api/health` | Server status, version, uptime, session count |
|
|
151
164
|
| `GET /api/export/search?q=<query>&format=md` | Export search results |
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Your AI agent can query AgentActa for better recall:
|
|
166
|
+
Agent integration example:
|
|
156
167
|
|
|
157
168
|
```javascript
|
|
158
|
-
const
|
|
159
|
-
const data = await
|
|
160
|
-
// Agent now has context from past sessions
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Demo Mode
|
|
164
|
-
|
|
165
|
-
Want to see what AgentActa looks like with data? Run with demo sessions:
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
# Generate demo data and start in demo mode
|
|
169
|
-
npm run demo
|
|
170
|
-
|
|
171
|
-
# Or separately:
|
|
172
|
-
node scripts/seed-demo.js
|
|
173
|
-
node index.js --demo
|
|
169
|
+
const res = await fetch('http://localhost:4003/api/search?q=deployment+issue&limit=5');
|
|
170
|
+
const data = await res.json();
|
|
174
171
|
```
|
|
175
172
|
|
|
176
|
-
This creates 7 realistic sessions simulating a developer building a weather app — scaffolding, API integration, frontend, debugging, deployment, tests, and a sub-agent task.
|
|
177
|
-
|
|
178
173
|
## Security
|
|
179
174
|
|
|
180
|
-
|
|
175
|
+
AgentActa binds to `127.0.0.1` by default.
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
If you expose it on a network, do it intentionally:
|
|
183
178
|
|
|
184
179
|
```bash
|
|
185
180
|
AGENTACTA_HOST=0.0.0.0 agentacta
|
|
186
181
|
```
|
|
187
182
|
|
|
188
|
-
|
|
183
|
+
**Important:** Session data can contain sensitive content (file snippets, API payloads, personal messages, tool args). There is no built-in auth yet, so only expose on trusted networks.
|
|
189
184
|
|
|
190
|
-
## Tech
|
|
185
|
+
## Tech stack
|
|
191
186
|
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
187
|
+
- Node.js (built-in `http`)
|
|
188
|
+
- `better-sqlite3` + SQLite FTS5
|
|
189
|
+
- Vanilla HTML/CSS/JS
|
|
190
|
+
- PWA support
|
|
196
191
|
|
|
197
192
|
## Privacy
|
|
198
193
|
|
|
199
|
-
|
|
194
|
+
No telemetry. No cloud sync. No external indexing service.
|
|
195
|
+
|
|
196
|
+
Your session history stays local.
|
|
200
197
|
|
|
201
198
|
## Compatibility
|
|
202
199
|
|
|
@@ -207,13 +204,15 @@ All data stays local. AgentActa runs entirely on your machine — no cloud servi
|
|
|
207
204
|
|
|
208
205
|
## Contributing
|
|
209
206
|
|
|
210
|
-
PRs welcome
|
|
207
|
+
PRs welcome.
|
|
208
|
+
|
|
209
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). If you’re adding a new agent format, start in `indexer.js`.
|
|
211
210
|
|
|
212
|
-
##
|
|
211
|
+
## Name
|
|
213
212
|
|
|
214
|
-
*Acta*
|
|
213
|
+
*Acta* is Latin for “things done.”
|
|
215
214
|
|
|
216
|
-
|
|
215
|
+
That’s the job here: keep a readable record of what your agents actually did.
|
|
217
216
|
|
|
218
217
|
## License
|
|
219
218
|
|
|
@@ -221,4 +220,4 @@ MIT
|
|
|
221
220
|
|
|
222
221
|
---
|
|
223
222
|
|
|
224
|
-
Built in Chicago by humans and agents
|
|
223
|
+
Built in Chicago by humans and agents.
|
package/db.js
CHANGED
|
@@ -54,6 +54,7 @@ function init(dbPath) {
|
|
|
54
54
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
55
55
|
);
|
|
56
56
|
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time DESC);
|
|
57
58
|
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
58
59
|
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
59
60
|
CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
|
package/index.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Bridge release for users on ^1.x semver ranges
|
|
4
|
+
if (require('./package.json').version === '1.5.0') {
|
|
5
|
+
console.log('\n\x1b[33m⚠ AgentActa has moved to CalVer versioning (2026.x.x).\x1b[0m');
|
|
6
|
+
console.log('\x1b[33m You are running an old 1.x version.\x1b[0m\n');
|
|
7
|
+
console.log(' Update with: \x1b[36mnpx agentacta@latest\x1b[0m');
|
|
8
|
+
console.log(' Or install: \x1b[36mnpm install -g agentacta@latest\x1b[0m\n');
|
|
9
|
+
}
|
|
10
|
+
|
|
2
11
|
const http = require('http');
|
|
3
12
|
const fs = require('fs');
|
|
4
13
|
const path = require('path');
|
|
5
14
|
const { EventEmitter } = require('events');
|
|
6
15
|
|
|
16
|
+
// --version / -v flag: print version and exit
|
|
17
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
18
|
+
const pkg = require('./package.json');
|
|
19
|
+
console.log(`${pkg.name} v${pkg.version}`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
// --demo flag: use demo session data (must run before config load)
|
|
8
24
|
if (process.argv.includes('--demo')) {
|
|
9
25
|
const demoDir = path.join(__dirname, 'demo');
|
|
@@ -193,6 +209,20 @@ const server = http.createServer((req, res) => {
|
|
|
193
209
|
return json(res, { ok: true, sessions: result.sessions, events: result.events });
|
|
194
210
|
}
|
|
195
211
|
|
|
212
|
+
else if (pathname === '/api/health') {
|
|
213
|
+
const pkg = require('./package.json');
|
|
214
|
+
const sessions = db.prepare('SELECT COUNT(*) as c FROM sessions').get().c;
|
|
215
|
+
const dbSize = getDbSize();
|
|
216
|
+
return json(res, {
|
|
217
|
+
status: 'ok',
|
|
218
|
+
version: pkg.version,
|
|
219
|
+
uptime: Math.round(process.uptime()),
|
|
220
|
+
sessions,
|
|
221
|
+
dbSizeBytes: dbSize.bytes,
|
|
222
|
+
node: process.version
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
196
226
|
else if (pathname === '/api/config') {
|
|
197
227
|
const dbSize = getDbSize();
|
|
198
228
|
const archiveCount = db.prepare('SELECT COUNT(*) as c FROM archive').get().c;
|
|
@@ -428,13 +458,70 @@ const server = http.createServer((req, res) => {
|
|
|
428
458
|
const date = query.date || (() => { const n = new Date(); return `${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,'0')}-${String(n.getDate()).padStart(2,'0')}`; })();
|
|
429
459
|
const from = new Date(date + 'T00:00:00').toISOString();
|
|
430
460
|
const to = new Date(date + 'T23:59:59.999').toISOString();
|
|
461
|
+
const limit = Math.min(parseInt(query.limit || '100', 10) || 100, 500);
|
|
462
|
+
const offset = Math.max(parseInt(query.offset || '0', 10) || 0, 0);
|
|
431
463
|
const events = db.prepare(
|
|
432
464
|
`SELECT e.*, s.summary as session_summary FROM events e
|
|
433
465
|
JOIN sessions s ON s.id = e.session_id
|
|
434
466
|
WHERE e.timestamp >= ? AND e.timestamp <= ?
|
|
435
|
-
ORDER BY e.timestamp DESC
|
|
436
|
-
|
|
437
|
-
|
|
467
|
+
ORDER BY e.timestamp DESC
|
|
468
|
+
LIMIT ? OFFSET ?`
|
|
469
|
+
).all(from, to, limit, offset);
|
|
470
|
+
const total = db.prepare(
|
|
471
|
+
`SELECT COUNT(*) as c FROM events e
|
|
472
|
+
WHERE e.timestamp >= ? AND e.timestamp <= ?`
|
|
473
|
+
).get(from, to).c;
|
|
474
|
+
json(res, { date, events, total, limit, offset, hasMore: offset + events.length < total });
|
|
475
|
+
}
|
|
476
|
+
else if (pathname === '/api/timeline/stream') {
|
|
477
|
+
res.writeHead(200, {
|
|
478
|
+
'Content-Type': 'text/event-stream',
|
|
479
|
+
'Cache-Control': 'no-cache',
|
|
480
|
+
'Connection': 'keep-alive',
|
|
481
|
+
'X-Accel-Buffering': 'no'
|
|
482
|
+
});
|
|
483
|
+
res.write(': connected\n\n');
|
|
484
|
+
|
|
485
|
+
let lastTs = query.after || new Date().toISOString();
|
|
486
|
+
let lastId = query.afterId || '';
|
|
487
|
+
|
|
488
|
+
const onUpdate = () => {
|
|
489
|
+
try {
|
|
490
|
+
const rows = db.prepare(
|
|
491
|
+
`SELECT e.*, s.summary as session_summary FROM events e
|
|
492
|
+
JOIN sessions s ON s.id = e.session_id
|
|
493
|
+
WHERE (e.timestamp > ?) OR (e.timestamp = ? AND e.id > ?)
|
|
494
|
+
ORDER BY e.timestamp ASC, e.id ASC`
|
|
495
|
+
).all(lastTs, lastTs, lastId);
|
|
496
|
+
if (rows.length) {
|
|
497
|
+
const tail = rows[rows.length - 1];
|
|
498
|
+
lastTs = tail.timestamp || lastTs;
|
|
499
|
+
lastId = tail.id || lastId;
|
|
500
|
+
res.write(`id: ${lastTs}:${lastId}\ndata: ${JSON.stringify(rows)}\n\n`);
|
|
501
|
+
}
|
|
502
|
+
} catch (err) {
|
|
503
|
+
console.error('Timeline SSE error:', err.message);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
sseEmitter.on('session-update', onUpdate);
|
|
508
|
+
|
|
509
|
+
const ping = setInterval(() => {
|
|
510
|
+
try { res.write(': ping\n\n'); } catch {}
|
|
511
|
+
}, 30000);
|
|
512
|
+
|
|
513
|
+
req.on('close', () => {
|
|
514
|
+
sseEmitter.off('session-update', onUpdate);
|
|
515
|
+
clearInterval(ping);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
else if (pathname === '/api/maintenance') {
|
|
519
|
+
if (req.method !== 'POST') return json(res, { error: 'Method not allowed' }, 405);
|
|
520
|
+
const sizeBefore = getDbSize();
|
|
521
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
522
|
+
db.exec('VACUUM');
|
|
523
|
+
const sizeAfter = getDbSize();
|
|
524
|
+
json(res, { ok: true, sizeBefore, sizeAfter });
|
|
438
525
|
}
|
|
439
526
|
else if (pathname === '/api/files') {
|
|
440
527
|
const limit = parseInt(query.limit) || 100;
|