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 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
- ![Search](screenshots/search.png)
50
- ![Sessions](screenshots/sessions.png)
51
- ![Session Detail](screenshots/session-detail.png)
52
- ![Timeline](screenshots/timeline.png)
53
- ![Files](screenshots/files.png)
54
- ![Stats](screenshots/stats.png)
55
- ![Search Results](screenshots/search-results.png)
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": null,
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 in XDG location
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(DEFAULTS, null, 2) + '\n');
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
- for (const p of sessionsOverride.split(':')) {
36
- if (fs.existsSync(p)) dirs.push({ path: p, agent: path.basename(path.dirname(p)) });
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
- // Scan ~/.openclaw/agents/*/sessions/
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
- // Scan ~/.claude/projects/*/ (Claude Code stores JSONL directly in project dirs)
53
- const claudeProjects = path.join(home, '.claude/projects');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentacta",
3
- "version": "2026.3.5",
3
+ "version": "2026.3.7",
4
4
  "description": "Audit trail and search engine for AI agent sessions",
5
5
  "main": "index.js",
6
6
  "bin": {
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() {