agentacta 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miraj Chokshi
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,216 @@
1
+ # AgentActa
2
+
3
+ [![CI](https://github.com/mirajchokshi/agentacta/actions/workflows/ci.yml/badge.svg)](https://github.com/mirajchokshi/agentacta/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/agentacta)](https://www.npmjs.com/package/agentacta)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ **Your AI agent does hundreds of things. Can you find them?**
8
+
9
+ AgentActa is an audit trail and search engine for AI agent sessions. It indexes everything your agent did — every message, tool call, file edit, web search, and decision — into a fast, searchable local interface.
10
+
11
+ One command. Zero config. Full visibility.
12
+
13
+ ```bash
14
+ npx agentacta
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Why
20
+
21
+ 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).
22
+
23
+ AgentActa gives you a single, searchable view of everything.
24
+
25
+ ## What You Get
26
+
27
+ 🔍 **Full-text search** across all messages, tool calls, and results
28
+ 📋 **Session browser** with summaries, token breakdowns (input/output), and model info
29
+ 📅 **Timeline view** — everything that happened on any given day
30
+ 📁 **File activity** — every file your agent touched, across all sessions
31
+ 📊 **Stats** — sessions, messages, tool usage, token counts
32
+ ⚡ **Live indexing** — new sessions appear automatically via file watching
33
+ 📱 **Mobile-friendly** — responsive UI with bottom tab navigation
34
+ 💡 **Smart suggestions** — quick search chips derived from your actual session data
35
+
36
+ ## Demo
37
+
38
+ https://github.com/mirajchokshi/agentacta/raw/main/screenshots/demo-final.mp4
39
+
40
+ ## Screenshots
41
+
42
+ ![Search](screenshots/search.png)
43
+ ![Sessions](screenshots/sessions.png)
44
+ ![Session Detail](screenshots/session-detail.png)
45
+ ![Timeline](screenshots/timeline.png)
46
+ ![Files](screenshots/files.png)
47
+ ![Stats](screenshots/stats.png)
48
+ ![Search Results](screenshots/search-results.png)
49
+
50
+ ## Quick Start
51
+
52
+ ```bash
53
+ # Run directly (no install needed)
54
+ npx agentacta
55
+
56
+ # Or install globally
57
+ npm install -g agentacta
58
+ agentacta
59
+ ```
60
+
61
+ Open `http://localhost:4003` in your browser.
62
+
63
+ AgentActa automatically finds your sessions in:
64
+ - `~/.openclaw/agents/*/sessions/` (OpenClaw)
65
+ - `~/.claude/projects/*/sessions/` (Claude Code)
66
+
67
+ Or point it at a custom path:
68
+
69
+ ```bash
70
+ AGENTACTA_SESSIONS_PATH=/path/to/sessions agentacta
71
+ ```
72
+
73
+ ## Features
74
+
75
+ ### Search
76
+ Full-text search powered by SQLite FTS5. Filter by message type (messages, tool calls, results) and role (user, assistant). Quick search suggestions are generated from your actual data — most-used tools, common topics, frequently touched files.
77
+
78
+ ### Sessions
79
+ Browse all indexed sessions with auto-generated summaries, token breakdowns (output vs input), and model info. Click into any session to see the full event history, most recent first.
80
+
81
+ ### Timeline
82
+ Pick a date, see everything that happened. Messages, tool invocations, file changes — most recent first.
83
+
84
+ ### File Activity
85
+ See every file your agent read, wrote, or edited. Sort by most touched, most recent, or most sessions. Filter by extension, group by directory. Click any file to see which sessions touched it and what was done.
86
+
87
+ ### Export
88
+ Download any session or search results as Markdown or JSON. Great for sharing, auditing, or archiving.
89
+
90
+ ## How It Works
91
+
92
+ AgentActa reads JSONL session files (the standard format used by OpenClaw and Claude Code), parses every message and tool call, and indexes them into a local SQLite database with FTS5 full-text search.
93
+
94
+ The web UI is a single-page app served by a lightweight Node.js HTTP server. No frameworks, no build step, no external dependencies beyond `better-sqlite3`.
95
+
96
+ ```
97
+ Session JSONL files → SQLite + FTS5 index → HTTP API → Web UI
98
+ ```
99
+
100
+ Data never leaves your machine.
101
+
102
+ ## Configuration
103
+
104
+ On first run, AgentActa creates `agentacta.config.json` with sensible defaults:
105
+
106
+ ```json
107
+ {
108
+ "port": 4003,
109
+ "storage": "reference",
110
+ "sessionDirs": []
111
+ }
112
+ ```
113
+
114
+ ### Storage Modes
115
+
116
+ - **`reference`** (default) — Lightweight index. Stores parsed events in SQLite but not the raw JSONL. Source files must remain on disk.
117
+ - **`archive`** — Full JSONL stored in SQLite. Sessions survive even if the original files are deleted. Uses more disk space.
118
+
119
+ ### Environment Variables
120
+
121
+ | Variable | Default | Description |
122
+ |---|---|---|
123
+ | `PORT` | `4003` | Server port |
124
+ | `AGENTACTA_HOST` | `127.0.0.1` | Bind address (see [Security](#security)) |
125
+ | `AGENTACTA_SESSIONS_PATH` | Auto-detected | Custom sessions directory |
126
+ | `AGENTACTA_DB_PATH` | `./agentacta.db` | Database file location |
127
+ | `AGENTACTA_STORAGE` | `reference` | Storage mode (`reference` or `archive`) |
128
+
129
+ ## API
130
+
131
+ AgentActa exposes a JSON API for programmatic access — useful for integrating search into your agent's workflow.
132
+
133
+ | Endpoint | Description |
134
+ |---|---|
135
+ | `GET /api/stats` | Overview: session count, messages, tools, tokens |
136
+ | `GET /api/sessions` | List sessions with metadata and token breakdowns |
137
+ | `GET /api/sessions/:id` | Full session with all events |
138
+ | `GET /api/search?q=<query>` | Full-text search with type/role/date filters |
139
+ | `GET /api/suggestions` | Data-driven search suggestions |
140
+ | `GET /api/timeline?date=YYYY-MM-DD` | All events for a given day |
141
+ | `GET /api/files` | All files touched across sessions |
142
+ | `GET /api/export/session/:id?format=md` | Export session as Markdown or JSON |
143
+ | `GET /api/export/search?q=<query>&format=md` | Export search results |
144
+
145
+ ### Agent Integration
146
+
147
+ Your AI agent can query AgentActa for better recall:
148
+
149
+ ```javascript
150
+ const results = await fetch('http://localhost:4003/api/search?q=deployment+issue&limit=5');
151
+ const data = await results.json();
152
+ // Agent now has context from past sessions
153
+ ```
154
+
155
+ ### Demo Mode
156
+
157
+ Want to see what AgentActa looks like with data? Run with demo sessions:
158
+
159
+ ```bash
160
+ # Generate demo data and start in demo mode
161
+ npm run demo
162
+
163
+ # Or separately:
164
+ node scripts/seed-demo.js
165
+ node index.js --demo
166
+ ```
167
+
168
+ This creates 7 realistic sessions simulating a developer building a weather app — scaffolding, API integration, frontend, debugging, deployment, tests, and a sub-agent task.
169
+
170
+ ## Security
171
+
172
+ **AgentActa is a local tool.** It binds to `127.0.0.1` by default — only accessible from your machine.
173
+
174
+ To expose on your network (e.g., for Tailscale access):
175
+
176
+ ```bash
177
+ AGENTACTA_HOST=0.0.0.0 agentacta
178
+ ```
179
+
180
+ ⚠️ **Important:** Agent session data can contain sensitive information — file contents, API responses, personal messages, tool call arguments. If you expose AgentActa on a network, ensure it's a trusted one. There is no built-in authentication.
181
+
182
+ ## Tech Stack
183
+
184
+ - **Node.js** — HTTP server (built-in `http`, no Express)
185
+ - **better-sqlite3** — Fast SQLite with FTS5 full-text search
186
+ - **Vanilla HTML/CSS/JS** — No framework, no build step
187
+ - **PWA** — Installable as a home screen app
188
+
189
+ ## Privacy
190
+
191
+ All data stays local. AgentActa runs entirely on your machine — no cloud services, no telemetry, no external requests. Your agent history is yours.
192
+
193
+ ## Compatibility
194
+
195
+ - ✅ [OpenClaw](https://github.com/openclaw/openclaw)
196
+ - ✅ Claude Code
197
+ - 🔜 Codex CLI
198
+ - 🔜 Custom JSONL formats
199
+
200
+ ## Contributing
201
+
202
+ PRs welcome. If you're adding support for a new agent format, add a parser in `indexer.js` and open a PR.
203
+
204
+ ## Etymology
205
+
206
+ *Acta* (Latin) — "things done." In ancient Rome, the *acta diurna* were daily public records of official proceedings — senate decisions, military victories, births and deaths — posted in public spaces for all citizens to read.
207
+
208
+ AgentActa is the same idea: a complete, searchable record of everything your AI agent did.
209
+
210
+ ## License
211
+
212
+ MIT
213
+
214
+ ---
215
+
216
+ Built in Chicago by humans and agents working together.
package/config.js ADDED
@@ -0,0 +1,55 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const CWD_CONFIG_FILE = path.join(process.cwd(), 'agentacta.config.json');
6
+ const XDG_CONFIG_DIR = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'agentacta');
7
+ const XDG_CONFIG_FILE = path.join(XDG_CONFIG_DIR, 'config.json');
8
+
9
+ // Resolve config file: CWD first (backward compat), then XDG default
10
+ function resolveConfigFile() {
11
+ if (fs.existsSync(CWD_CONFIG_FILE)) return CWD_CONFIG_FILE;
12
+ return XDG_CONFIG_FILE;
13
+ }
14
+
15
+ const CONFIG_FILE = resolveConfigFile();
16
+
17
+ const DEFAULTS = {
18
+ port: 4003,
19
+ storage: 'reference',
20
+ sessionsPath: null,
21
+ dbPath: './agentacta.db'
22
+ };
23
+
24
+ function loadConfig() {
25
+ let fileConfig = {};
26
+
27
+ if (fs.existsSync(CONFIG_FILE)) {
28
+ try {
29
+ fileConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
30
+ } catch (err) {
31
+ console.error(`Warning: Could not parse ${CONFIG_FILE}:`, err.message);
32
+ }
33
+ } else {
34
+ // First-run: create default config in XDG location
35
+ const dir = path.dirname(CONFIG_FILE);
36
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
37
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULTS, null, 2) + '\n');
38
+ console.log(`Created default config: ${CONFIG_FILE}`);
39
+ }
40
+
41
+ const config = { ...DEFAULTS, ...fileConfig };
42
+
43
+ // Env var overrides (highest priority)
44
+ if (process.env.PORT) config.port = parseInt(process.env.PORT);
45
+ if (process.env.AGENTACTA_STORAGE) config.storage = process.env.AGENTACTA_STORAGE;
46
+ if (process.env.AGENTACTA_SESSIONS_PATH) config.sessionsPath = process.env.AGENTACTA_SESSIONS_PATH;
47
+ if (process.env.AGENTACTA_DB_PATH) config.dbPath = process.env.AGENTACTA_DB_PATH;
48
+
49
+ // Resolve dbPath relative to cwd
50
+ config.dbPath = path.resolve(config.dbPath);
51
+
52
+ return config;
53
+ }
54
+
55
+ module.exports = { loadConfig, CONFIG_FILE };
package/db.js ADDED
@@ -0,0 +1,137 @@
1
+ const Database = require('better-sqlite3');
2
+ const path = require('path');
3
+ const { loadConfig } = require('./config');
4
+
5
+ let _config = null;
6
+ function getConfig() {
7
+ if (!_config) _config = loadConfig();
8
+ return _config;
9
+ }
10
+
11
+ function open(dbPath) {
12
+ const p = dbPath || getConfig().dbPath;
13
+ const db = new Database(p);
14
+ db.pragma('journal_mode = WAL');
15
+ db.pragma('foreign_keys = ON');
16
+ return db;
17
+ }
18
+
19
+ function init(dbPath) {
20
+ const db = open(dbPath);
21
+
22
+ db.exec(`
23
+ CREATE TABLE IF NOT EXISTS sessions (
24
+ id TEXT PRIMARY KEY,
25
+ start_time TEXT NOT NULL,
26
+ end_time TEXT,
27
+ message_count INTEGER DEFAULT 0,
28
+ tool_count INTEGER DEFAULT 0,
29
+ model TEXT,
30
+ summary TEXT,
31
+ agent TEXT,
32
+ session_type TEXT,
33
+ total_cost REAL DEFAULT 0,
34
+ total_tokens INTEGER DEFAULT 0,
35
+ input_tokens INTEGER DEFAULT 0,
36
+ output_tokens INTEGER DEFAULT 0,
37
+ cache_read_tokens INTEGER DEFAULT 0,
38
+ cache_write_tokens INTEGER DEFAULT 0,
39
+ initial_prompt TEXT,
40
+ first_message_id TEXT,
41
+ first_message_timestamp TEXT
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS events (
45
+ id TEXT PRIMARY KEY,
46
+ session_id TEXT NOT NULL,
47
+ timestamp TEXT NOT NULL,
48
+ type TEXT NOT NULL,
49
+ role TEXT,
50
+ content TEXT,
51
+ tool_name TEXT,
52
+ tool_args TEXT,
53
+ tool_result TEXT,
54
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
55
+ );
56
+
57
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
58
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
59
+ CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
60
+ CREATE INDEX IF NOT EXISTS idx_events_tool_name ON events(tool_name);
61
+
62
+ CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
63
+ content, tool_name, tool_args,
64
+ content='events',
65
+ content_rowid='rowid'
66
+ );
67
+
68
+ CREATE TRIGGER IF NOT EXISTS events_ai AFTER INSERT ON events BEGIN
69
+ INSERT INTO events_fts(rowid, content, tool_name, tool_args)
70
+ VALUES (new.rowid, new.content, new.tool_name, new.tool_args);
71
+ END;
72
+
73
+ CREATE TRIGGER IF NOT EXISTS events_ad AFTER DELETE ON events BEGIN
74
+ INSERT INTO events_fts(events_fts, rowid, content, tool_name, tool_args)
75
+ VALUES ('delete', old.rowid, old.content, old.tool_name, old.tool_args);
76
+ END;
77
+
78
+ CREATE TABLE IF NOT EXISTS index_state (
79
+ file_path TEXT PRIMARY KEY,
80
+ last_offset INTEGER DEFAULT 0,
81
+ last_modified TEXT
82
+ );
83
+
84
+ CREATE TABLE IF NOT EXISTS file_activity (
85
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
86
+ session_id TEXT NOT NULL,
87
+ file_path TEXT NOT NULL,
88
+ operation TEXT NOT NULL,
89
+ timestamp TEXT,
90
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
91
+ );
92
+
93
+ CREATE INDEX IF NOT EXISTS idx_file_activity_path ON file_activity(file_path);
94
+ CREATE INDEX IF NOT EXISTS idx_file_activity_session ON file_activity(session_id);
95
+
96
+ CREATE TABLE IF NOT EXISTS archive (
97
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
98
+ session_id TEXT NOT NULL,
99
+ line_number INTEGER NOT NULL,
100
+ raw_json TEXT NOT NULL,
101
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
102
+ );
103
+
104
+ CREATE INDEX IF NOT EXISTS idx_archive_session ON archive(session_id);
105
+ `);
106
+
107
+ // Add columns if missing (migration)
108
+ const cols = db.prepare("PRAGMA table_info(sessions)").all().map(c => c.name);
109
+ if (!cols.includes('agent')) db.exec("ALTER TABLE sessions ADD COLUMN agent TEXT");
110
+ if (!cols.includes('session_type')) db.exec("ALTER TABLE sessions ADD COLUMN session_type TEXT");
111
+ if (!cols.includes('total_cost')) db.exec("ALTER TABLE sessions ADD COLUMN total_cost REAL DEFAULT 0");
112
+ if (!cols.includes('total_tokens')) db.exec("ALTER TABLE sessions ADD COLUMN total_tokens INTEGER DEFAULT 0");
113
+ if (!cols.includes('input_tokens')) db.exec("ALTER TABLE sessions ADD COLUMN input_tokens INTEGER DEFAULT 0");
114
+ if (!cols.includes('output_tokens')) db.exec("ALTER TABLE sessions ADD COLUMN output_tokens INTEGER DEFAULT 0");
115
+ if (!cols.includes('cache_read_tokens')) db.exec("ALTER TABLE sessions ADD COLUMN cache_read_tokens INTEGER DEFAULT 0");
116
+ if (!cols.includes('cache_write_tokens')) db.exec("ALTER TABLE sessions ADD COLUMN cache_write_tokens INTEGER DEFAULT 0");
117
+
118
+ db.close();
119
+ }
120
+
121
+ function createStmts(db) {
122
+ return {
123
+ getState: db.prepare('SELECT * FROM index_state WHERE file_path = ?'),
124
+ getSession: db.prepare('SELECT id FROM sessions WHERE id = ?'),
125
+ deleteEvents: db.prepare('DELETE FROM events WHERE session_id = ?'),
126
+ deleteSession: db.prepare('DELETE FROM sessions WHERE id = ?'),
127
+ deleteFileActivity: db.prepare('DELETE FROM file_activity WHERE session_id = ?'),
128
+ insertEvent: db.prepare(`INSERT OR REPLACE INTO events (id, session_id, timestamp, type, role, content, tool_name, tool_args, tool_result) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`),
129
+ upsertSession: db.prepare(`INSERT OR REPLACE INTO sessions (id, start_time, end_time, message_count, tool_count, model, summary, agent, session_type, total_cost, total_tokens, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, initial_prompt, first_message_id, first_message_timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
130
+ upsertState: db.prepare(`INSERT OR REPLACE INTO index_state (file_path, last_offset, last_modified) VALUES (?, ?, ?)`),
131
+ insertFileActivity: db.prepare(`INSERT INTO file_activity (session_id, file_path, operation, timestamp) VALUES (?, ?, ?, ?)`),
132
+ deleteArchive: db.prepare('DELETE FROM archive WHERE session_id = ?'),
133
+ insertArchive: db.prepare('INSERT INTO archive (session_id, line_number, raw_json) VALUES (?, ?, ?)')
134
+ };
135
+ }
136
+
137
+ module.exports = { open, init, createStmts };