agentacta 2026.3.5 → 2026.3.7
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 +8 -28
- package/config.js +20 -2
- package/index.js +21 -0
- package/indexer.js +33 -23
- package/package.json +1 -1
- package/public/app.js +33 -0
package/README.md
CHANGED
|
@@ -39,20 +39,10 @@ AgentActa gives you one place to inspect the full trail.
|
|
|
39
39
|
- ⚡ Live indexing via file watching
|
|
40
40
|
- 📱 Mobile-friendly UI
|
|
41
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`)
|
|
42
45
|
|
|
43
|
-
## Demo
|
|
44
|
-
|
|
45
|
-
https://github.com/mirajchokshi/agentacta/raw/main/screenshots/demo-final.mp4
|
|
46
|
-
|
|
47
|
-
## Screenshots
|
|
48
|
-
|
|
49
|
-

|
|
50
|
-

|
|
51
|
-

|
|
52
|
-

|
|
53
|
-

|
|
54
|
-

|
|
55
|
-

|
|
56
46
|
|
|
57
47
|
## Quick start
|
|
58
48
|
|
|
@@ -128,18 +118,20 @@ On first run, AgentActa creates:
|
|
|
128
118
|
- `~/.config/agentacta/config.json`
|
|
129
119
|
- or `agentacta.config.json` in current directory (if present)
|
|
130
120
|
|
|
131
|
-
Default config:
|
|
121
|
+
Default config (auto-generated on first run — session directories are detected automatically):
|
|
132
122
|
|
|
133
123
|
```json
|
|
134
124
|
{
|
|
135
125
|
"port": 4003,
|
|
136
126
|
"storage": "reference",
|
|
137
|
-
"sessionsPath":
|
|
127
|
+
"sessionsPath": ["~/.claude/projects", "~/.openclaw/sessions"],
|
|
138
128
|
"dbPath": "./agentacta.db",
|
|
139
129
|
"projectAliases": {}
|
|
140
130
|
}
|
|
141
131
|
```
|
|
142
132
|
|
|
133
|
+
`sessionsPath` accepts a string, a colon-delimited string, or a JSON array.
|
|
134
|
+
|
|
143
135
|
### Storage modes
|
|
144
136
|
|
|
145
137
|
- `reference` (default): index parsed events in SQLite, keep source JSONL on disk. Lightweight.
|
|
@@ -170,6 +162,7 @@ Default config:
|
|
|
170
162
|
| `GET /api/export/session/:id?format=md` | Export one session |
|
|
171
163
|
| `GET /api/timeline/stream?after=<ts>` | SSE stream for live timeline updates |
|
|
172
164
|
| `POST /api/maintenance` | VACUUM + WAL checkpoint (returns size before/after) |
|
|
165
|
+
| `GET /api/health` | Server status, version, uptime, session count |
|
|
173
166
|
| `GET /api/export/search?q=<query>&format=md` | Export search results |
|
|
174
167
|
|
|
175
168
|
Agent integration example:
|
|
@@ -179,19 +172,6 @@ const res = await fetch('http://localhost:4003/api/search?q=deployment+issue&lim
|
|
|
179
172
|
const data = await res.json();
|
|
180
173
|
```
|
|
181
174
|
|
|
182
|
-
## Demo mode
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
# seed demo data + run
|
|
186
|
-
npm run demo
|
|
187
|
-
|
|
188
|
-
# or split steps
|
|
189
|
-
node scripts/seed-demo.js
|
|
190
|
-
node index.js --demo
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Demo mode creates 7 realistic sessions (weather app build path: scaffolding, API, frontend, debugging, deployment, tests, sub-agent task).
|
|
194
|
-
|
|
195
175
|
## Security
|
|
196
176
|
|
|
197
177
|
AgentActa binds to `127.0.0.1` by default.
|
package/config.js
CHANGED
|
@@ -14,6 +14,12 @@ function resolveConfigFile() {
|
|
|
14
14
|
|
|
15
15
|
const CONFIG_FILE = resolveConfigFile();
|
|
16
16
|
|
|
17
|
+
const KNOWN_SESSION_DIRS = [
|
|
18
|
+
path.join(os.homedir(), '.claude', 'projects'), // Claude Code
|
|
19
|
+
path.join(os.homedir(), '.codex', 'sessions'), // Codex CLI
|
|
20
|
+
path.join(os.homedir(), '.openclaw', 'sessions'), // OpenClaw
|
|
21
|
+
];
|
|
22
|
+
|
|
17
23
|
const DEFAULTS = {
|
|
18
24
|
port: 4003,
|
|
19
25
|
storage: 'reference',
|
|
@@ -22,6 +28,11 @@ const DEFAULTS = {
|
|
|
22
28
|
projectAliases: {}
|
|
23
29
|
};
|
|
24
30
|
|
|
31
|
+
function detectSessionDirs() {
|
|
32
|
+
const found = KNOWN_SESSION_DIRS.filter(d => fs.existsSync(d));
|
|
33
|
+
return found.length > 0 ? found : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
function loadConfig() {
|
|
26
37
|
let fileConfig = {};
|
|
27
38
|
|
|
@@ -32,11 +43,18 @@ function loadConfig() {
|
|
|
32
43
|
console.error(`Warning: Could not parse ${CONFIG_FILE}:`, err.message);
|
|
33
44
|
}
|
|
34
45
|
} else {
|
|
35
|
-
// First-run: create default config
|
|
46
|
+
// First-run: create default config with auto-detected session dirs
|
|
47
|
+
const detected = detectSessionDirs();
|
|
48
|
+
const firstRunDefaults = { ...DEFAULTS, sessionsPath: detected };
|
|
36
49
|
const dir = path.dirname(CONFIG_FILE);
|
|
37
50
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
38
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(
|
|
51
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(firstRunDefaults, null, 2) + '\n');
|
|
52
|
+
// Apply to in-memory config so this run also benefits
|
|
53
|
+
fileConfig = firstRunDefaults;
|
|
39
54
|
console.log(`Created default config: ${CONFIG_FILE}`);
|
|
55
|
+
if (detected) {
|
|
56
|
+
console.log(`Auto-detected session directories:\n${detected.map(d => ` - ${d}`).join('\n')}`);
|
|
57
|
+
}
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
const config = { ...DEFAULTS, ...fileConfig };
|
package/index.js
CHANGED
|
@@ -4,6 +4,13 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { EventEmitter } = require('events');
|
|
6
6
|
|
|
7
|
+
// --version / -v flag: print version and exit
|
|
8
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
9
|
+
const pkg = require('./package.json');
|
|
10
|
+
console.log(`${pkg.name} v${pkg.version}`);
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
// --demo flag: use demo session data (must run before config load)
|
|
8
15
|
if (process.argv.includes('--demo')) {
|
|
9
16
|
const demoDir = path.join(__dirname, 'demo');
|
|
@@ -193,6 +200,20 @@ const server = http.createServer((req, res) => {
|
|
|
193
200
|
return json(res, { ok: true, sessions: result.sessions, events: result.events });
|
|
194
201
|
}
|
|
195
202
|
|
|
203
|
+
else if (pathname === '/api/health') {
|
|
204
|
+
const pkg = require('./package.json');
|
|
205
|
+
const sessions = db.prepare('SELECT COUNT(*) as c FROM sessions').get().c;
|
|
206
|
+
const dbSize = getDbSize();
|
|
207
|
+
return json(res, {
|
|
208
|
+
status: 'ok',
|
|
209
|
+
version: pkg.version,
|
|
210
|
+
uptime: Math.round(process.uptime()),
|
|
211
|
+
sessions,
|
|
212
|
+
dbSizeBytes: dbSize.bytes,
|
|
213
|
+
node: process.version
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
196
217
|
else if (pathname === '/api/config') {
|
|
197
218
|
const dbSize = getDbSize();
|
|
198
219
|
const archiveCount = db.prepare('SELECT COUNT(*) as c FROM archive').get().c;
|
package/indexer.js
CHANGED
|
@@ -29,16 +29,36 @@ function discoverSessionDirs(config) {
|
|
|
29
29
|
const dirs = [];
|
|
30
30
|
const home = process.env.HOME;
|
|
31
31
|
|
|
32
|
+
// Expand a single path into session dirs, handling Claude Code's per-project structure
|
|
33
|
+
function expandPath(p) {
|
|
34
|
+
if (!fs.existsSync(p)) return;
|
|
35
|
+
const stat = fs.statSync(p);
|
|
36
|
+
if (!stat.isDirectory()) return;
|
|
37
|
+
// Claude Code: ~/.claude/projects contains per-project subdirs with JSONL files
|
|
38
|
+
if (p.replace(/\/$/, '').endsWith('/.claude/projects')) {
|
|
39
|
+
for (const proj of fs.readdirSync(p)) {
|
|
40
|
+
const projDir = path.join(p, proj);
|
|
41
|
+
if (fs.statSync(projDir).isDirectory()) {
|
|
42
|
+
const hasJsonl = fs.readdirSync(projDir).some(f => f.endsWith('.jsonl'));
|
|
43
|
+
if (hasJsonl) dirs.push({ path: projDir, agent: 'claude-code' });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
dirs.push({ path: p, agent: path.basename(path.dirname(p)) });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
32
51
|
// Config sessionsPath or env var override
|
|
33
52
|
const sessionsOverride = process.env.AGENTACTA_SESSIONS_PATH || (config && config.sessionsPath);
|
|
34
53
|
if (sessionsOverride) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
const overridePaths = Array.isArray(sessionsOverride)
|
|
55
|
+
? sessionsOverride
|
|
56
|
+
: sessionsOverride.split(':');
|
|
57
|
+
overridePaths.forEach(expandPath);
|
|
38
58
|
if (dirs.length) return dirs;
|
|
39
59
|
}
|
|
40
60
|
|
|
41
|
-
//
|
|
61
|
+
// Auto-discover: ~/.openclaw/agents/*/sessions/
|
|
42
62
|
const oclawAgents = path.join(home, '.openclaw/agents');
|
|
43
63
|
if (fs.existsSync(oclawAgents)) {
|
|
44
64
|
for (const agent of fs.readdirSync(oclawAgents)) {
|
|
@@ -49,23 +69,8 @@ function discoverSessionDirs(config) {
|
|
|
49
69
|
}
|
|
50
70
|
}
|
|
51
71
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
if (fs.existsSync(claudeProjects)) {
|
|
55
|
-
for (const proj of fs.readdirSync(claudeProjects)) {
|
|
56
|
-
const projDir = path.join(claudeProjects, proj);
|
|
57
|
-
// Claude Code: JSONL files directly in project dir
|
|
58
|
-
if (fs.existsSync(projDir) && fs.statSync(projDir).isDirectory()) {
|
|
59
|
-
const hasJsonl = fs.readdirSync(projDir).some(f => f.endsWith('.jsonl'));
|
|
60
|
-
if (hasJsonl) dirs.push({ path: projDir, agent: 'claude-code' });
|
|
61
|
-
}
|
|
62
|
-
// Also check sessions/ subdirectory (future-proofing)
|
|
63
|
-
const sp = path.join(projDir, 'sessions');
|
|
64
|
-
if (fs.existsSync(sp) && fs.statSync(sp).isDirectory()) {
|
|
65
|
-
dirs.push({ path: sp, agent: 'claude-code' });
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
72
|
+
// Auto-discover: ~/.claude/projects/
|
|
73
|
+
expandPath(path.join(home, '.claude/projects'));
|
|
69
74
|
|
|
70
75
|
// Scan ~/.codex/sessions recursively (Codex CLI stores nested YYYY/MM/DD/*.jsonl)
|
|
71
76
|
const codexSessions = path.join(home, '.codex/sessions');
|
|
@@ -289,11 +294,16 @@ function indexFile(db, filePath, agentName, stmts, archiveMode, config) {
|
|
|
289
294
|
if (firstLine.agent) agent = firstLine.agent;
|
|
290
295
|
if (firstLine.sessionType) sessionType = firstLine.sessionType;
|
|
291
296
|
if (sessionId.includes('subagent')) sessionType = 'subagent';
|
|
292
|
-
} else if (firstLine.type === 'user' || firstLine.type === 'assistant' || firstLine.type === 'file-history-snapshot') {
|
|
293
|
-
// Claude Code format — no session header, extract from first message line
|
|
297
|
+
} else if (firstLine.type === 'user' || firstLine.type === 'assistant' || firstLine.type === 'file-history-snapshot' || firstLine.type === 'queue-operation') {
|
|
298
|
+
// Claude Code format — no session header, extract from first message or queue-operation line
|
|
294
299
|
isClaudeCode = true;
|
|
295
300
|
for (const line of lines) {
|
|
296
301
|
let obj; try { obj = JSON.parse(line); } catch { continue; }
|
|
302
|
+
if (obj.sessionId && obj.timestamp) {
|
|
303
|
+
sessionId = obj.sessionId;
|
|
304
|
+
sessionStart = obj.timestamp;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
297
307
|
if ((obj.type === 'user' || obj.type === 'assistant') && obj.sessionId) {
|
|
298
308
|
sessionId = obj.sessionId;
|
|
299
309
|
sessionStart = obj.timestamp;
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -1148,6 +1148,14 @@ async function viewStats() {
|
|
|
1148
1148
|
${data.agents && data.agents.length > 1 ? `<div class="section-label">Agents</div><div class="filters" style="margin-bottom:var(--space-xl)">${data.agents.map(a => `<span class="filter-chip">${escHtml(a)}</span>`).join('')}</div>` : ''}
|
|
1149
1149
|
<div class="section-label">Tools Used</div>
|
|
1150
1150
|
<div class="tools-grid">${[...new Set((data.tools||[]).filter(t => t).map(t => fmtToolGroup(t)))].sort().map(t => `<span class="tool-chip">${escHtml(t)}</span>`).join('')}</div>
|
|
1151
|
+
|
|
1152
|
+
<div class="section-label">System Info</div>
|
|
1153
|
+
<div id="systemInfoContainer" style="display:grid;grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));gap:var(--space-md);margin-bottom:var(--space-md)">
|
|
1154
|
+
<div class="config-card"><div class="config-label">Version</div><div class="config-value" id="sysVersion">…</div></div>
|
|
1155
|
+
<div class="config-card"><div class="config-label">Uptime</div><div class="config-value" id="sysUptime">…</div></div>
|
|
1156
|
+
<div class="config-card"><div class="config-label">Indexed Sessions</div><div class="config-value" id="sysSessions">…</div></div>
|
|
1157
|
+
<div class="config-card"><div class="config-label">Node.js</div><div class="config-value" id="sysNode">…</div></div>
|
|
1158
|
+
</div>
|
|
1151
1159
|
</div>`;
|
|
1152
1160
|
|
|
1153
1161
|
content.innerHTML = html;
|
|
@@ -1187,6 +1195,31 @@ async function viewStats() {
|
|
|
1187
1195
|
optimizeBtn.disabled = false;
|
|
1188
1196
|
});
|
|
1189
1197
|
}
|
|
1198
|
+
|
|
1199
|
+
// Fetch system info
|
|
1200
|
+
api('/health').then(h => {
|
|
1201
|
+
if (h._error) return;
|
|
1202
|
+
const fmtUptime = (s) => {
|
|
1203
|
+
const d = Math.floor(s / 86400);
|
|
1204
|
+
const hr = Math.floor((s % 86400) / 3600);
|
|
1205
|
+
const m = Math.floor((s % 3600) / 60);
|
|
1206
|
+
if (d > 0) return `${d}d ${hr}h`;
|
|
1207
|
+
if (hr > 0) return `${hr}h ${m}m`;
|
|
1208
|
+
return `${m}m`;
|
|
1209
|
+
};
|
|
1210
|
+
const fmtBytes = (b) => {
|
|
1211
|
+
if (b >= 1024 * 1024) return `${(b / (1024 * 1024)).toFixed(1)} MB`;
|
|
1212
|
+
return `${(b / 1024).toFixed(1)} KB`;
|
|
1213
|
+
};
|
|
1214
|
+
const v = $('#sysVersion');
|
|
1215
|
+
const u = $('#sysUptime');
|
|
1216
|
+
const sc = $('#sysSessions');
|
|
1217
|
+
const n = $('#sysNode');
|
|
1218
|
+
if (v) v.textContent = `v${h.version}`;
|
|
1219
|
+
if (u) u.textContent = fmtUptime(h.uptime);
|
|
1220
|
+
if (sc) sc.textContent = String(h.sessions);
|
|
1221
|
+
if (n) n.textContent = h.node;
|
|
1222
|
+
});
|
|
1190
1223
|
}
|
|
1191
1224
|
|
|
1192
1225
|
async function viewFiles() {
|