happenin 0.3.0 → 0.4.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 CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v3.0%20adopted-ff69b4.svg)](docs/CODE_OF_CONDUCT.md)
6
6
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
7
7
 
8
- A macOS CLI that records Cursor and Claude Code agent events to a local SQLite database and serves a live browser dashboard.
8
+ A CLI that records Cursor and Claude Code agent events to a local SQLite database and serves a live browser dashboard.
9
9
 
10
10
  ![happenin dashboard](docs/dashboard.gif)
11
11
 
@@ -29,7 +29,7 @@ Cursor and Claude Code can emit local hooks for each session, tool use, prompt,
29
29
 
30
30
  ## Requirements
31
31
 
32
- - macOS
32
+ - macOS, Linux, or Windows
33
33
  - Node.js `>= 22.13.0` (uses the built-in `node:sqlite` module, available since Node 22.13)
34
34
  - Zero runtime dependencies
35
35
  - The dashboard loads htmx and htmx-ext-sse from a CDN
@@ -142,6 +142,13 @@ Clicking a tool, skill, or file jumps to a list of every session that used it. T
142
142
  chart can be scoped to one directory with the "md files directory" filter (the common path prefix
143
143
  is stripped from the dropdown labels, and paths under `/private` are excluded).
144
144
 
145
+ The persistent filter bar also has multi-select "directories", "skills", and "integrations (MCP)"
146
+ controls. Each lets you pick several values at once — matching sessions with _any_ of the selected
147
+ values in that category (OR) — and the categories combine with every other filter, including each
148
+ other, with AND (e.g. directory X or Y, AND skill A or B). The "integrations (MCP)" list is derived
149
+ from the data itself: it parses the `mcp__<server>__<tool>` naming convention on recorded tool
150
+ calls and lists every distinct MCP server actually seen, with no hardcoded set of servers.
151
+
145
152
  A context breakdown widget splits recorded payload size (and, for imported Claude sessions, token
146
153
  usage) into four buckets: MCP server calls, markdown file reads, bloatware (skill loads and
147
154
  lifecycle/hook noise), and actual value (real tool calls and conversation turns). It appears on the
package/assets/help.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # happenin
2
2
 
3
- A macOS CLI that records Cursor and Claude Code agent events to a local SQLite database and serves a live browser dashboard.
3
+ A CLI that records Cursor and Claude Code agent events to a local SQLite database and serves a live browser dashboard.
4
4
 
5
5
  ## Commands
6
6
 
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameSync as i,statSync as a,writeFileSync as o}from"node:fs";import s from"node:path";import c from"node:process";import{fileURLToPath as l}from"node:url";import{homedir as u}from"node:os";import{DatabaseSync as d}from"node:sqlite";import f from"node:http";import{execFile as p}from"node:child_process";const m=[`sessionStart`,`sessionEnd`,`beforeSubmitPrompt`,`preToolUse`,`postToolUse`,`postToolUseFailure`,`subagentStart`,`subagentStop`,`beforeShellExecution`,`afterShellExecution`,`beforeMCPExecution`,`afterMCPExecution`,`beforeReadFile`,`afterFileEdit`,`afterAgentResponse`,`afterAgentThought`,`preCompact`,`stop`],h=`SessionStart.SessionEnd.Setup.UserPromptSubmit.UserPromptExpansion.PreToolUse.PermissionRequest.PermissionDenied.PostToolUse.PostToolUseFailure.PostToolBatch.Notification.MessageDisplay.SubagentStart.SubagentStop.TaskCreated.TaskCompleted.Stop.StopFailure.TeammateIdle.InstructionsLoaded.ConfigChange.CwdChanged.DirectoryAdded.FileChanged.WorktreeCreate.WorktreeRemove.PreCompact.PostCompact.Elicitation.ElicitationResult`.split(`.`),g=new Set([`beforeSubmitPrompt`]),_=new Set([`preToolUse`,`beforeShellExecution`,`beforeMCPExecution`,`beforeReadFile`,`subagentStart`]),v=new Set([`UserPromptSubmit`,`UserPromptExpansion`]),y=new Set([`PreToolUse`,`PermissionRequest`,`SubagentStart`,`SubagentStop`,`TaskCreated`,`TaskCompleted`,`Stop`,`WorktreeCreate`,`WorktreeRemove`]),ee=(e,t)=>{if(e===`cursor`)return g.has(t)?JSON.stringify({continue:!0}):_.has(t)?JSON.stringify({permission:`allow`}):void 0;if(e===`claude`)return v.has(t)?JSON.stringify({continue:!0}):y.has(t)?JSON.stringify({decision:`approve`}):void 0},b=e=>typeof e==`object`&&!!e&&!Array.isArray(e),te=e=>e instanceof Error?e.message:String(e),x=()=>c.env.HOME||u(),ne=()=>{let e=c.argv[1];return e&&s.isAbsolute(e)?e.split(s.sep).includes(`_npx`)?`npx -y happenin`:e:`happenin`},re=(e,t)=>e.includes(t),ie=e=>{t(e,{recursive:!0})},S=t=>e(t),ae=e=>{let t=n(e,`utf8`);return JSON.parse(t)},oe=(e,t)=>{let n=`${e}.${Date.now()}.tmp`;ie(s.dirname(e)),o(n,JSON.stringify(t,null,2)+`
3
- `,`utf8`),i(n,e)},se=(e,t)=>{if(ie(t),!S(e))return;let r=s.join(t,`${s.basename(e)}.${Date.now()}.bak`);return o(r,n(e,`utf8`),`utf8`),r},ce=e=>{if(!S(e))return{version:1,hooks:{}};let t=ae(e);if(!b(t))throw Error(`Invalid JSON object in ${e}`);let n=b(t.hooks)?t.hooks:{};return{...t,version:typeof t.version==`number`?t.version:1,hooks:n}},le=e=>{if(!S(e))return{hooks:{}};let t=ae(e);if(!b(t))throw Error(`Invalid JSON object in ${e}`);let n=b(t.hooks)?t.hooks:{};return{...t,hooks:n}},ue=(e,t,n)=>{e[t]=[...(Array.isArray(e[t])?e[t]:[]).filter(e=>!b(e)||typeof e.command!=`string`||!re(e.command,`happenin record cursor`)),{command:`${n} record cursor`}]},de=(e,t,n)=>{let r=`happenin record claude ${t}`;e[t]=[...(Array.isArray(e[t])?e[t]:[]).map(e=>b(e)&&Array.isArray(e.hooks)?{...e,hooks:e.hooks.filter(e=>!b(e)||typeof e.command!=`string`||!re(e.command,r))}:e).filter(e=>!(b(e)&&Array.isArray(e.hooks)&&e.hooks.length===0)),{matcher:``,hooks:[{type:`command`,command:`${n} record claude ${t}`}]}]},fe=e=>{let t=x(),n=s.join(t,`.cursor`,`hooks.json`),r=s.join(t,`.happenin`,`backups`,`cursor`),i=se(n,r),a=ce(n);for(let t of m)ue(a.hooks,t,e);return oe(n,a),{target:n,backup:i}},pe=e=>{let t=x(),n=s.join(t,`.claude`,`settings.json`),r=s.join(t,`.happenin`,`backups`,`claude`),i=se(n,r),a=le(n);for(let t of h)de(a.hooks,t,e);return oe(n,a),{target:n,backup:i}},me=e=>{let t=!1,n=!1;for(let r of e)r===`--cursor`&&(t=!0),r===`--claude`&&(n=!0);return!t&&!n&&(t=!0,n=!0),{cursor:t,claude:n}};async function he(e={}){let{cursor:t=!0,claude:n=!0}=e,r=ne(),i=[];t&&i.push(fe(r)),n&&i.push(pe(r));for(let{target:e,backup:t}of i)c.stdout.write(`${e} written
4
- `),t&&c.stdout.write(` backup: ${t}\n`)}async function ge(e=[]){try{let{cursor:t,claude:n}=me(e);await he({cursor:t,claude:n})}catch(e){c.stderr.write(`install failed: ${te(e)}\n`),c.exitCode=1}}const _e=[`CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);`,`CREATE INDEX IF NOT EXISTS idx_events_event ON events(event);`,`CREATE INDEX IF NOT EXISTS idx_events_session_id ON events(session_id);`,`CREATE INDEX IF NOT EXISTS idx_events_happened_at ON events(happened_at);`,`CREATE INDEX IF NOT EXISTS idx_events_received_at ON events(received_at);`,`CREATE INDEX IF NOT EXISTS idx_events_source_path ON events(source_path);`,`CREATE INDEX IF NOT EXISTS idx_events_subagent_id ON events(subagent_id);`];function C(e){let t=e.prepare(`PRAGMA user_version`).get();return Number(t?.user_version??0)}function ve(e){let t=C(e);if(!(t>=1)){if(e.exec(`BEGIN IMMEDIATE;`),t=C(e),t>=1){e.exec(`ROLLBACK;`);return}try{let t=e.prepare(`PRAGMA table_info(events)`).all(),n=new Set(t.map(e=>e.name)),r=t=>{n.has(t)||e.exec(`ALTER TABLE events ADD COLUMN ${t} TEXT;`)};r(`subagent_id`),r(`subagent_type`),r(`transcript_path`),e.exec(`PRAGMA user_version = 1;`),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}}}function ye(e){let t=C(e);if(!(t>=3)){if(e.exec(`BEGIN IMMEDIATE;`),t=C(e),t>=3){e.exec(`ROLLBACK;`);return}try{e.prepare(`PRAGMA table_info(events)`).all().some(e=>e.name===`skill_name`)||e.exec(`ALTER TABLE events ADD COLUMN skill_name TEXT;`),e.exec(`PRAGMA user_version = 3;`),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}}}const be=e=>{let t=C(e);if(!(t>=2)){if(e.exec(`BEGIN IMMEDIATE;`),t=C(e),t>=2){e.exec(`ROLLBACK;`);return}try{let t=e.prepare(`SELECT id, payload FROM events WHERE happened_at IS NULL AND json_valid(payload) = 1`).all(),n=e.prepare(`UPDATE events SET happened_at = ? WHERE id = ?`);for(let e of t){let t=we(JSON.parse(e.payload));t!==void 0&&n.run(t,e.id)}e.exec(`
2
+ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameSync as i,statSync as a,writeFileSync as o}from"node:fs";import s from"node:path";import c from"node:process";import{fileURLToPath as l}from"node:url";import{homedir as u}from"node:os";import{DatabaseSync as d}from"node:sqlite";import f from"node:http";import{execFile as p}from"node:child_process";const m=[`sessionStart`,`sessionEnd`,`beforeSubmitPrompt`,`preToolUse`,`postToolUse`,`postToolUseFailure`,`subagentStart`,`subagentStop`,`beforeShellExecution`,`afterShellExecution`,`beforeMCPExecution`,`afterMCPExecution`,`beforeReadFile`,`afterFileEdit`,`afterAgentResponse`,`afterAgentThought`,`preCompact`,`stop`],h=`SessionStart.SessionEnd.Setup.UserPromptSubmit.UserPromptExpansion.PreToolUse.PermissionRequest.PermissionDenied.PostToolUse.PostToolUseFailure.PostToolBatch.Notification.MessageDisplay.SubagentStart.SubagentStop.TaskCreated.TaskCompleted.Stop.StopFailure.TeammateIdle.InstructionsLoaded.ConfigChange.CwdChanged.DirectoryAdded.FileChanged.WorktreeCreate.WorktreeRemove.PreCompact.PostCompact.Elicitation.ElicitationResult`.split(`.`),g=new Set([`beforeSubmitPrompt`]),_=new Set([`preToolUse`,`beforeShellExecution`,`beforeMCPExecution`,`beforeReadFile`,`subagentStart`]),v=new Set([`UserPromptSubmit`,`UserPromptExpansion`]),y=new Set([`PreToolUse`,`PermissionRequest`,`SubagentStart`,`SubagentStop`,`TaskCreated`,`TaskCompleted`,`Stop`,`WorktreeCreate`,`WorktreeRemove`]),b=(e,t)=>{if(e===`cursor`)return g.has(t)?JSON.stringify({continue:!0}):_.has(t)?JSON.stringify({permission:`allow`}):void 0;if(e===`claude`)return v.has(t)?JSON.stringify({continue:!0}):y.has(t)?JSON.stringify({decision:`approve`}):void 0},x=()=>c.env.HOME||u(),S=e=>typeof e==`object`&&!!e&&!Array.isArray(e),ee=e=>e instanceof Error?e.message:String(e),te=()=>{let e=c.argv[1];if(e&&s.isAbsolute(e)){if(e.split(s.sep).includes(`_npx`))return`npx -y happenin`;if(c.platform!==`win32`)return e}return`happenin`},ne=(e,t)=>e.includes(t),re=e=>{t(e,{recursive:!0})},C=t=>e(t),ie=e=>{let t=n(e,`utf8`);return JSON.parse(t)},ae=(e,t)=>{let n=`${e}.${Date.now()}.tmp`;re(s.dirname(e)),o(n,JSON.stringify(t,null,2)+`
3
+ `,`utf8`),i(n,e)},oe=(e,t)=>{if(re(t),!C(e))return;let r=s.join(t,`${s.basename(e)}.${Date.now()}.bak`);return o(r,n(e,`utf8`),`utf8`),r},se=e=>{if(!C(e))return{version:1,hooks:{}};let t=ie(e);if(!S(t))throw Error(`Invalid JSON object in ${e}`);let n=S(t.hooks)?t.hooks:{};return{...t,version:typeof t.version==`number`?t.version:1,hooks:n}},ce=e=>{if(!C(e))return{hooks:{}};let t=ie(e);if(!S(t))throw Error(`Invalid JSON object in ${e}`);let n=S(t.hooks)?t.hooks:{};return{...t,hooks:n}},le=(e,t,n)=>{e[t]=[...(Array.isArray(e[t])?e[t]:[]).filter(e=>!S(e)||typeof e.command!=`string`||!ne(e.command,`happenin record cursor`)),{command:`${n} record cursor`}]},ue=(e,t,n)=>{let r=`happenin record claude ${t}`;e[t]=[...(Array.isArray(e[t])?e[t]:[]).map(e=>S(e)&&Array.isArray(e.hooks)?{...e,hooks:e.hooks.filter(e=>!S(e)||typeof e.command!=`string`||!ne(e.command,r))}:e).filter(e=>!(S(e)&&Array.isArray(e.hooks)&&e.hooks.length===0)),{matcher:``,hooks:[{type:`command`,command:`${n} record claude ${t}`}]}]},de=e=>{let t=x(),n=s.join(t,`.cursor`,`hooks.json`),r=s.join(t,`.happenin`,`backups`,`cursor`),i=oe(n,r),a=se(n);for(let t of m)le(a.hooks,t,e);return ae(n,a),{target:n,backup:i}},fe=e=>{let t=x(),n=s.join(t,`.claude`,`settings.json`),r=s.join(t,`.happenin`,`backups`,`claude`),i=oe(n,r),a=ce(n);for(let t of h)ue(a.hooks,t,e);return ae(n,a),{target:n,backup:i}},pe=e=>{let t=!1,n=!1;for(let r of e)r===`--cursor`&&(t=!0),r===`--claude`&&(n=!0);return!t&&!n&&(t=!0,n=!0),{cursor:t,claude:n}};async function me(e={}){let{cursor:t=!0,claude:n=!0}=e,r=te(),i=[];t&&i.push(de(r)),n&&i.push(fe(r));for(let{target:e,backup:t}of i)c.stdout.write(`${e} written
4
+ `),t&&c.stdout.write(` backup: ${t}\n`)}async function he(e=[]){try{let{cursor:t,claude:n}=pe(e);await me({cursor:t,claude:n})}catch(e){c.stderr.write(`install failed: ${ee(e)}\n`),c.exitCode=1}}const ge=[`CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);`,`CREATE INDEX IF NOT EXISTS idx_events_event ON events(event);`,`CREATE INDEX IF NOT EXISTS idx_events_session_id ON events(session_id);`,`CREATE INDEX IF NOT EXISTS idx_events_happened_at ON events(happened_at);`,`CREATE INDEX IF NOT EXISTS idx_events_received_at ON events(received_at);`,`CREATE INDEX IF NOT EXISTS idx_events_source_path ON events(source_path);`,`CREATE INDEX IF NOT EXISTS idx_events_subagent_id ON events(subagent_id);`];function w(e){let t=e.prepare(`PRAGMA user_version`).get();return Number(t?.user_version??0)}function _e(e){let t=w(e);if(!(t>=1)){if(e.exec(`BEGIN IMMEDIATE;`),t=w(e),t>=1){e.exec(`ROLLBACK;`);return}try{let t=e.prepare(`PRAGMA table_info(events)`).all(),n=new Set(t.map(e=>e.name)),r=t=>{n.has(t)||e.exec(`ALTER TABLE events ADD COLUMN ${t} TEXT;`)};r(`subagent_id`),r(`subagent_type`),r(`transcript_path`),e.exec(`PRAGMA user_version = 1;`),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}}}function ve(e){let t=w(e);if(!(t>=3)){if(e.exec(`BEGIN IMMEDIATE;`),t=w(e),t>=3){e.exec(`ROLLBACK;`);return}try{e.prepare(`PRAGMA table_info(events)`).all().some(e=>e.name===`skill_name`)||e.exec(`ALTER TABLE events ADD COLUMN skill_name TEXT;`),e.exec(`PRAGMA user_version = 3;`),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}}}const ye=e=>{let t=w(e);if(!(t>=2)){if(e.exec(`BEGIN IMMEDIATE;`),t=w(e),t>=2){e.exec(`ROLLBACK;`);return}try{let t=e.prepare(`SELECT id, payload FROM events WHERE happened_at IS NULL AND json_valid(payload) = 1`).all(),n=e.prepare(`UPDATE events SET happened_at = ? WHERE id = ?`);for(let e of t){let t=Ce(JSON.parse(e.payload));t!==void 0&&n.run(t,e.id)}e.exec(`
5
5
  UPDATE events
6
6
  SET happened_at = strftime('%Y-%m-%dT%H:%M:%fZ', received_at / 1000.0, 'unixepoch')
7
7
  WHERE happened_at IS NULL;
@@ -24,7 +24,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
24
24
  WHERE
25
25
  project_path IS NULL
26
26
  AND json_valid(payload) = 1;
27
- `),e.exec(`PRAGMA user_version = 2;`),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}}},xe=e=>{let t=C(e);if(t<1)throw Error(`Database schema must be migrated before backfilling subagent metadata`);if(e.exec(`BEGIN IMMEDIATE;`),t=C(e),t<1){e.exec(`ROLLBACK;`);return}try{e.exec(`
27
+ `),e.exec(`PRAGMA user_version = 2;`),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}}},be=e=>{let t=w(e);if(t<1)throw Error(`Database schema must be migrated before backfilling subagent metadata`);if(e.exec(`BEGIN IMMEDIATE;`),t=w(e),t<1){e.exec(`ROLLBACK;`);return}try{e.exec(`
28
28
  UPDATE events
29
29
  SET
30
30
  subagent_id = NULLIF(trim(json_extract(payload, '$.subagent_id')), ''),
@@ -64,7 +64,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
64
64
  WHERE s.event = 'subagentStart'
65
65
  AND s.subagent_id = json_extract(events.payload, '$.tool_use_id')
66
66
  );
67
- `),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}},w=()=>c.env.HAPPENIN_DB?c.env.HAPPENIN_DB:s.join(u(),`.happenin`,`happenin.db`),T=(e,n=5e3)=>{let r=e??w();t(s.dirname(r),{recursive:!0});let i=new d(r);try{i.exec(`PRAGMA busy_timeout = ${n};`),i.exec(`PRAGMA journal_mode = WAL;`),i.exec(`CREATE TABLE IF NOT EXISTS events (
67
+ `),e.exec(`COMMIT;`)}catch(t){try{e.exec(`ROLLBACK;`)}catch{}throw t}},T=()=>c.env.HAPPENIN_DB?c.env.HAPPENIN_DB:s.join(x(),`.happenin`,`happenin.db`),E=(e,n=5e3)=>{let r=e??T();t(s.dirname(r),{recursive:!0});let i=new d(r);try{i.exec(`PRAGMA busy_timeout = ${n};`),i.exec(`PRAGMA journal_mode = WAL;`),i.exec(`CREATE TABLE IF NOT EXISTS events (
68
68
  id INTEGER PRIMARY KEY AUTOINCREMENT,
69
69
  source TEXT NOT NULL,
70
70
  client TEXT,
@@ -87,7 +87,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
87
87
  mtime INTEGER NOT NULL,
88
88
  imported_at INTEGER NOT NULL
89
89
  );
90
- `),ve(i),be(i),ye(i);for(let e of _e)i.exec(e);return i}catch(e){try{i.close()}catch{}throw e}},Se=[`happenedAt`,`timestamp`,`happened_at`,`time`,`ts`,`createdAt`,`created_at`];function Ce(e){if(e!=null){if(typeof e==`number`){if(!Number.isFinite(e))return;try{return new Date(e).toISOString()}catch{return}}if(typeof e==`string`){let t=e.trim();if(t.length===0)return;if(/^\d+$/.test(t)){let e=Number(t);if(!Number.isFinite(e))return;try{return new Date(e).toISOString()}catch{return}}return Number.isNaN(Date.parse(t))?void 0:t}}}function we(e){if(typeof e!=`object`||!e||Array.isArray(e))return;let t=e;for(let e of Se){let n=Ce(Reflect.get(t,e));if(n!==void 0)return n}}const E=(e,t)=>{let n=e.prepare(`
90
+ `),_e(i),ye(i),ve(i);for(let e of ge)i.exec(e);return i}catch(e){try{i.close()}catch{}throw e}},xe=[`happenedAt`,`timestamp`,`happened_at`,`time`,`ts`,`createdAt`,`created_at`];function Se(e){if(e!=null){if(typeof e==`number`){if(!Number.isFinite(e))return;try{return new Date(e).toISOString()}catch{return}}if(typeof e==`string`){let t=e.trim();if(t.length===0)return;if(/^\d+$/.test(t)){let e=Number(t);if(!Number.isFinite(e))return;try{return new Date(e).toISOString()}catch{return}}return Number.isNaN(Date.parse(t))?void 0:t}}}function Ce(e){if(typeof e!=`object`||!e||Array.isArray(e))return;let t=e;for(let e of xe){let n=Se(Reflect.get(t,e));if(n!==void 0)return n}}const D=(e,t)=>{let n=e.prepare(`
91
91
  INSERT INTO events (
92
92
  source,
93
93
  client,
@@ -105,7 +105,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
105
105
  subagent_type,
106
106
  transcript_path
107
107
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
108
- `),r=Date.now(),i=Ce(t.happenedAt)??new Date(r).toISOString();n.run(t.source,t.client??null,t.event??null,t.sessionId??null,i,r,t.projectPath??null,t.filePath??null,t.toolName??null,t.skillName??null,t.payload,t.sourcePath??null,t.subagentId??null,t.subagentType??null,t.transcriptPath??null)},D=`COALESCE(strftime('%Y-%m-%dT%H:%M:%fZ', happened_at), strftime('%Y-%m-%dT%H:%M:%fZ', received_at / 1000.0, 'unixepoch'))`;function Te(e){return(e===`7d`?168:e===`30d`?720:24)*60*60*1e3}function O(e,t=Date.now()){let n=[],r=[];if(e.since!==void 0&&(n.push(`id > ?`),r.push(e.since)),e.source!==void 0&&(n.push(`source = ?`),r.push(e.source)),e.event!==void 0&&(n.push(`event = ?`),r.push(e.event)),e.tool!==void 0&&e.tool!==``&&(n.push(`tool_name = ?`),r.push(e.tool)),e.skill!==void 0&&e.skill!==``&&(n.push(`skill_name = ?`),r.push(e.skill)),e.file!==void 0&&e.file!==``&&(n.push(`file_path = ?`),r.push(e.file)),e.sessionId!==void 0&&e.sessionId!==``&&(e.sessionIdExact?(n.push(`session_id = ?`),r.push(e.sessionId)):(n.push(`session_id LIKE ?`),r.push(`%${e.sessionId}%`))),e.sessionIds!==void 0&&e.sessionIds.length>0){let t=e.sessionIds.filter(e=>e!==null),i=e.sessionIds.some(e=>e===null),a=[];if(t.length>0){let e=t.map(()=>`?`).join(`,`);a.push(`session_id IN (${e})`);for(let e of t)r.push(e)}i&&a.push(`session_id IS NULL`),n.push(a.length===1?a[0]:`(${a.join(` OR `)})`)}return e.subagentId!==void 0&&e.subagentId!==``&&(n.push(`subagent_id = ?`),r.push(e.subagentId)),e.q!==void 0&&e.q!==``&&(n.push(`(payload LIKE ? OR session_id LIKE ?)`),r.push(`%${e.q}%`),r.push(`%${e.q}%`)),e.range!==void 0&&e.range!==`all`&&(n.push(`${D} >= ?`),r.push(new Date(t-Te(e.range)).toISOString())),{clause:n.length>0?`WHERE ${n.join(` AND `)}`:``,params:r}}const k=(e,t)=>{let{clause:n,params:r}=O(t),i=t.limit??100,a=t.offset??0,o=`
108
+ `),r=Date.now(),i=Se(t.happenedAt)??new Date(r).toISOString();n.run(t.source,t.client??null,t.event??null,t.sessionId??null,i,r,t.projectPath??null,t.filePath??null,t.toolName??null,t.skillName??null,t.payload,t.sourcePath??null,t.subagentId??null,t.subagentType??null,t.transcriptPath??null)},O=`COALESCE(strftime('%Y-%m-%dT%H:%M:%fZ', happened_at), strftime('%Y-%m-%dT%H:%M:%fZ', received_at / 1000.0, 'unixepoch'))`;function we(e){return(e===`7d`?168:e===`30d`?720:24)*60*60*1e3}function Te(e){return e.replace(/\\/g,`\\\\`).replace(/%/g,`\\%`).replace(/_/g,`\\_`)}function Ee(e){return`mcp\\_\\_${Te(e)}\\_\\_%`}function k(e,t=Date.now()){let n=[],r=[];if(e.since!==void 0&&(n.push(`id > ?`),r.push(e.since)),e.source!==void 0&&(n.push(`source = ?`),r.push(e.source)),e.event!==void 0&&(n.push(`event = ?`),r.push(e.event)),e.tool!==void 0&&e.tool!==``&&(n.push(`tool_name = ?`),r.push(e.tool)),e.skill!==void 0&&e.skill!==``&&(n.push(`skill_name = ?`),r.push(e.skill)),e.file!==void 0&&e.file!==``&&(n.push(`file_path = ?`),r.push(e.file)),e.skills!==void 0&&e.skills.length>0){let t=e.skills.map(()=>`?`).join(`,`);n.push(`skill_name IN (${t})`);for(let t of e.skills)r.push(t)}if(e.projectPaths!==void 0&&e.projectPaths.length>0){let t=e.projectPaths.map(()=>`?`).join(`,`);n.push(`project_path IN (${t})`);for(let t of e.projectPaths)r.push(t)}if(e.mcpServers!==void 0&&e.mcpServers.length>0){let t=e.mcpServers.map(()=>`tool_name LIKE ? ESCAPE '\\'`);n.push(t.length===1?t[0]:`(${t.join(` OR `)})`);for(let t of e.mcpServers)r.push(Ee(t))}if(e.sessionId!==void 0&&e.sessionId!==``&&(e.sessionIdExact?(n.push(`session_id = ?`),r.push(e.sessionId)):(n.push(`session_id LIKE ?`),r.push(`%${e.sessionId}%`))),e.sessionIds!==void 0&&e.sessionIds.length>0){let t=e.sessionIds.filter(e=>e!==null),i=e.sessionIds.some(e=>e===null),a=[];if(t.length>0){let e=t.map(()=>`?`).join(`,`);a.push(`session_id IN (${e})`);for(let e of t)r.push(e)}i&&a.push(`session_id IS NULL`),n.push(a.length===1?a[0]:`(${a.join(` OR `)})`)}return e.subagentId!==void 0&&e.subagentId!==``&&(n.push(`subagent_id = ?`),r.push(e.subagentId)),e.q!==void 0&&e.q!==``&&(n.push(`(payload LIKE ? OR session_id LIKE ?)`),r.push(`%${e.q}%`),r.push(`%${e.q}%`)),e.range!==void 0&&e.range!==`all`&&(n.push(`${O} >= ?`),r.push(new Date(t-we(e.range)).toISOString())),{clause:n.length>0?`WHERE ${n.join(` AND `)}`:``,params:r}}const De=(e,t)=>{let{clause:n,params:r}=k(t),i=t.limit??100,a=t.offset??0,o=`
109
109
  SELECT
110
110
  id,
111
111
  source,
@@ -124,7 +124,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
124
124
  subagent_type AS subagentType,
125
125
  transcript_path AS transcriptPath
126
126
  FROM events
127
- ${n} ORDER BY id DESC LIMIT ?${a>0?` OFFSET ?`:``}`;return r.push(i),a>0&&r.push(a),e.prepare(o).all(...r)},A=(e,t)=>{let{clause:n,params:r}=O(t),i=`SELECT COUNT(*) AS count FROM events ${n}`,a=e.prepare(i).get(...r);return a?Number(a.count):0},Ee=(e,t)=>{let{clause:n,params:r}=O(t);return{total:A(e,t),bySource:e.prepare(`SELECT source, COUNT(*) AS count FROM events ${n} GROUP BY source ORDER BY count DESC`).all(...r),byEvent:e.prepare(`SELECT event, COUNT(*) AS count FROM events ${n} GROUP BY event ORDER BY count DESC`).all(...r),bySession:e.prepare(`SELECT session_id AS sessionId, COUNT(*) AS count, MIN(happened_at) AS firstAt, MAX(happened_at) AS lastAt FROM events ${n} GROUP BY session_id ORDER BY count DESC`).all(...r)}};function De(e){let t=Number(e.firstReceivedAt),n=Number(e.lastReceivedAt),r=JSON.parse(e.projectPaths),i=JSON.parse(e.toolNames),a=JSON.parse(e.skillNames),o=JSON.parse(e.filePaths),s=e.firstAt?Date.parse(e.firstAt):NaN,c=e.lastAt?Date.parse(e.lastAt):NaN,l=!Number.isNaN(s)&&!Number.isNaN(c)&&c>=s?c-s:n-t;return{sessionId:e.sessionId,subagentId:e.subagentId,subagentType:e.subagentType,firstAt:e.firstAt,lastAt:e.lastAt,firstReceivedAt:t,lastReceivedAt:n,durationMs:l,eventCount:Number(e.eventCount),projectPath:r[0]??null,projectPaths:r,tools:i,skills:a,files:o,failureCount:Number(e.failureCount)}}const Oe=(e,t,n=Date.now())=>{let{clause:r,params:i}=O(t,n),a=t.limit??100,o=t.offset??0,s=`
127
+ ${n} ORDER BY id DESC LIMIT ?${a>0?` OFFSET ?`:``}`;return r.push(i),a>0&&r.push(a),e.prepare(o).all(...r)},A=(e,t)=>{let{clause:n,params:r}=k(t),i=`SELECT COUNT(*) AS count FROM events ${n}`,a=e.prepare(i).get(...r);return a?Number(a.count):0},Oe=(e,t)=>{let{clause:n,params:r}=k(t);return{total:A(e,t),bySource:e.prepare(`SELECT source, COUNT(*) AS count FROM events ${n} GROUP BY source ORDER BY count DESC`).all(...r),byEvent:e.prepare(`SELECT event, COUNT(*) AS count FROM events ${n} GROUP BY event ORDER BY count DESC`).all(...r),bySession:e.prepare(`SELECT session_id AS sessionId, COUNT(*) AS count, MIN(happened_at) AS firstAt, MAX(happened_at) AS lastAt FROM events ${n} GROUP BY session_id ORDER BY count DESC`).all(...r)}};function ke(e){let t=Number(e.firstReceivedAt),n=Number(e.lastReceivedAt),r=JSON.parse(e.projectPaths),i=JSON.parse(e.toolNames),a=JSON.parse(e.skillNames),o=JSON.parse(e.filePaths),s=e.firstAt?Date.parse(e.firstAt):NaN,c=e.lastAt?Date.parse(e.lastAt):NaN,l=!Number.isNaN(s)&&!Number.isNaN(c)&&c>=s?c-s:n-t;return{sessionId:e.sessionId,subagentId:e.subagentId,subagentType:e.subagentType,firstAt:e.firstAt,lastAt:e.lastAt,firstReceivedAt:t,lastReceivedAt:n,durationMs:l,eventCount:Number(e.eventCount),projectPath:r[0]??null,projectPaths:r,tools:i,skills:a,files:o,failureCount:Number(e.failureCount)}}const Ae=(e,t,n=Date.now())=>{let{clause:r,params:i}=k(t,n),a=t.limit??100,o=t.offset??0,s=`
128
128
  SELECT
129
129
  session_id AS sessionId,
130
130
  COUNT(*) AS eventCount,
@@ -142,7 +142,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
142
142
  GROUP BY session_id
143
143
  ORDER BY lastReceivedAt DESC, sessionId
144
144
  LIMIT ?${o>0?` OFFSET ?`:``}
145
- `;return i.push(a),o>0&&i.push(o),e.prepare(s).all(...i).map(De)},ke=(e,t={},n=10,r=Date.now())=>{let{clause:i,params:a}=O(t,r),o=`tool_name IS NOT NULL AND tool_name <> ''`,s=`SELECT tool_name AS tool, COUNT(*) AS count FROM events ${i?`${i} AND ${o}`:`WHERE ${o}`} GROUP BY tool_name ORDER BY count DESC, tool_name ASC LIMIT ?`;return e.prepare(s).all(...a,n)},Ae=(e,t={},n=10,r=Date.now())=>{let{clause:i,params:a}=O(t,r),o=`skill_name IS NOT NULL AND skill_name <> ''`,s=`SELECT skill_name AS skill, COUNT(*) AS count FROM events ${i?`${i} AND ${o}`:`WHERE ${o}`} GROUP BY skill_name ORDER BY count DESC, skill_name ASC LIMIT ?`;return e.prepare(s).all(...a,n)},je=(e,t={},n=10,r=Date.now())=>{let{clause:i,params:a}=O(t,r),o=[`file_path IS NOT NULL`,`file_path <> ''`,`LOWER(file_path) LIKE '%.md'`];t.mdDir&&o.push(`project_path = ?`);let s=o.join(` AND `),c=`SELECT file_path AS file, COUNT(*) AS count FROM events ${i?`${i} AND ${s}`:`WHERE ${s}`} GROUP BY file_path ORDER BY count DESC, file_path ASC LIMIT ?`,l=e.prepare(c),u=t.mdDir?[...a,t.mdDir,n]:[...a,n];return l.all(...u)},Me=`
145
+ `;return i.push(a),o>0&&i.push(o),e.prepare(s).all(...i).map(ke)},je=(e,t={},n=10,r=Date.now())=>{let{clause:i,params:a}=k(t,r),o=`tool_name IS NOT NULL AND tool_name <> ''`,s=`SELECT tool_name AS tool, COUNT(*) AS count FROM events ${i?`${i} AND ${o}`:`WHERE ${o}`} GROUP BY tool_name ORDER BY count DESC, tool_name ASC LIMIT ?`;return e.prepare(s).all(...a,n)},Me=(e,t={},n=10,r=Date.now())=>{let{clause:i,params:a}=k(t,r),o=`skill_name IS NOT NULL AND skill_name <> ''`,s=`SELECT skill_name AS skill, COUNT(*) AS count FROM events ${i?`${i} AND ${o}`:`WHERE ${o}`} GROUP BY skill_name ORDER BY count DESC, skill_name ASC LIMIT ?`;return e.prepare(s).all(...a,n)},Ne=(e,t={},n=10,r=Date.now())=>{let{clause:i,params:a}=k(t,r),o=[`file_path IS NOT NULL`,`file_path <> ''`,`LOWER(file_path) LIKE '%.md'`];t.mdDir&&o.push(`project_path = ?`);let s=o.join(` AND `),c=`SELECT file_path AS file, COUNT(*) AS count FROM events ${i?`${i} AND ${s}`:`WHERE ${s}`} GROUP BY file_path ORDER BY count DESC, file_path ASC LIMIT ?`,l=e.prepare(c),u=t.mdDir?[...a,t.mdDir,n]:[...a,n];return l.all(...u)},Pe=`
146
146
  CASE
147
147
  WHEN tool_name LIKE 'mcp\\_\\_%' ESCAPE '\\' THEN 'mcpServers'
148
148
  WHEN file_path IS NOT NULL AND file_path <> '' AND LOWER(file_path) LIKE '%.md' THEN 'mdFiles'
@@ -151,9 +151,9 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
151
151
  WHEN event IN ('user', 'assistant', 'prompt') THEN 'actualValue'
152
152
  ELSE 'bloatware'
153
153
  END
154
- `;function Ne(){return{bytes:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0},tokens:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0}}}const Pe=(e,t={},n=Date.now())=>{let{clause:r,params:i}=O(t,n),a=`
154
+ `;function Fe(){return{bytes:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0},tokens:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0}}}const Ie=(e,t={},n=Date.now())=>{let{clause:r,params:i}=k(t,n),a=`
155
155
  SELECT
156
- ${Me} AS bucket,
156
+ ${Pe} AS bucket,
157
157
  SUM(LENGTH(payload)) AS bytes,
158
158
  SUM(
159
159
  CASE WHEN json_valid(payload) THEN
@@ -164,8 +164,8 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
164
164
  ) AS tokens
165
165
  FROM events
166
166
  ${r}
167
- GROUP BY ${Me}
168
- `,o=e.prepare(a).all(...i),s=Ne();for(let e of o)s.bytes[e.bucket]=Number(e.bytes),s.tokens[e.bucket]=Number(e.tokens);return s};function Fe(e){return e===`day`?`strftime('%Y-%m-%dT00:00:00.000Z', ${D})`:`strftime('%Y-%m-%dT%H:00:00.000Z', ${D})`}function Ie(e,t=Date.now()){let n=new Date(t);return e===`day`?n.setUTCHours(0,0,0,0):(n.setUTCMinutes(0,0,0),n.setUTCMilliseconds(0)),n}function Le(e){return e===`day`?1440*60*1e3:3600*1e3}const Re=(e,t={},n=24,r=`hour`,i=Date.now())=>{if(n<=0){let n=Fe(r),{clause:a,params:o}=O({...t,range:void 0},i),s=`SELECT ${n} AS bucket, COUNT(*) AS count FROM events ${a} GROUP BY bucket ORDER BY bucket`;return e.prepare(s).all(...o)}let a=Le(r),o=Ie(r,i),s=Ie(r,i-n*60*60*1e3),c=Math.max(1,Math.ceil((o.getTime()-s.getTime())/a)+1),l=s.toISOString(),u=Fe(r),{clause:d,params:f}=O({...t,range:void 0},i),p=`${u} >= ?`,m=`SELECT ${u} AS bucket, COUNT(*) AS count FROM events ${d?`${d} AND ${p}`:`WHERE ${p}`} GROUP BY bucket ORDER BY bucket`,h=e.prepare(m).all(...f,l),g=[],_=new Map;for(let e of h)_.set(e.bucket,e.count);for(let e=0;e<c;e++){let e=r===`day`?`${s.toISOString().slice(0,10)}T00:00:00.000Z`:s.toISOString();g.push({bucket:e,count:_.get(e)??0}),s.setTime(s.getTime()+a)}return g},ze=e=>{let t=e.prepare(`SELECT id FROM events ORDER BY id DESC LIMIT 1`).get();return t?Number(t.id):0},Be=(e,t)=>{if(e.failureCount>0)return`failed`;let n=e.lastAt?Date.parse(e.lastAt):NaN;return t-(Number.isNaN(n)?e.lastReceivedAt:Math.min(n,e.lastReceivedAt))<=300*1e3?`active`:`completed`};function Ve(e,t,n){return!(t.status!==void 0&&Be(e,n)!==t.status||t.minDuration!==void 0&&e.durationMs<t.minDuration*60*1e3||t.maxDuration!==void 0&&e.durationMs>t.maxDuration*60*1e3)}const He=(e,t,n={})=>{let r=[...new Set(t.filter(e=>e!==null))];if(r.length===0)return[];let{clause:i,params:a}=O({...n,sessionId:void 0,sessionIds:void 0,subagentId:void 0}),o=i?` ${i.replace(/^WHERE/,`AND`)}`:``,s=[];for(let t=0;t<r.length;t+=500){let n=r.slice(t,t+500),i=`
167
+ GROUP BY ${Pe}
168
+ `,o=e.prepare(a).all(...i),s=Fe();for(let e of o)s.bytes[e.bucket]=Number(e.bytes),s.tokens[e.bucket]=Number(e.tokens);return s};function Le(e){return e===`day`?`strftime('%Y-%m-%dT00:00:00.000Z', ${O})`:`strftime('%Y-%m-%dT%H:00:00.000Z', ${O})`}function Re(e,t=Date.now()){let n=new Date(t);return e===`day`?n.setUTCHours(0,0,0,0):(n.setUTCMinutes(0,0,0),n.setUTCMilliseconds(0)),n}function ze(e){return e===`day`?1440*60*1e3:3600*1e3}const Be=(e,t={},n=24,r=`hour`,i=Date.now())=>{if(n<=0){let n=Le(r),{clause:a,params:o}=k({...t,range:void 0},i),s=`SELECT ${n} AS bucket, COUNT(*) AS count FROM events ${a} GROUP BY bucket ORDER BY bucket`;return e.prepare(s).all(...o)}let a=ze(r),o=Re(r,i),s=Re(r,i-n*60*60*1e3),c=Math.max(1,Math.ceil((o.getTime()-s.getTime())/a)+1),l=s.toISOString(),u=Le(r),{clause:d,params:f}=k({...t,range:void 0},i),p=`${u} >= ?`,m=`SELECT ${u} AS bucket, COUNT(*) AS count FROM events ${d?`${d} AND ${p}`:`WHERE ${p}`} GROUP BY bucket ORDER BY bucket`,h=e.prepare(m).all(...f,l),g=[],_=new Map;for(let e of h)_.set(e.bucket,e.count);for(let e=0;e<c;e++){let e=r===`day`?`${s.toISOString().slice(0,10)}T00:00:00.000Z`:s.toISOString();g.push({bucket:e,count:_.get(e)??0}),s.setTime(s.getTime()+a)}return g},Ve=e=>{let t=e.prepare(`SELECT id FROM events ORDER BY id DESC LIMIT 1`).get();return t?Number(t.id):0},He=(e,t)=>{if(e.failureCount>0)return`failed`;let n=e.lastAt?Date.parse(e.lastAt):NaN;return t-(Number.isNaN(n)?e.lastReceivedAt:Math.min(n,e.lastReceivedAt))<=300*1e3?`active`:`completed`};function Ue(e,t,n){return!(t.status!==void 0&&He(e,n)!==t.status||t.minDuration!==void 0&&e.durationMs<t.minDuration*60*1e3||t.maxDuration!==void 0&&e.durationMs>t.maxDuration*60*1e3)}const We=(e,t,n={})=>{let r=[...new Set(t.filter(e=>e!==null))];if(r.length===0)return[];let{clause:i,params:a}=k({...n,sessionId:void 0,sessionIds:void 0,subagentId:void 0}),o=i?` ${i.replace(/^WHERE/,`AND`)}`:``,s=[];for(let t=0;t<r.length;t+=500){let n=r.slice(t,t+500),i=`
169
169
  SELECT
170
170
  session_id AS sessionId,
171
171
  subagent_id AS subagentId,
@@ -184,13 +184,22 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
184
184
  WHERE subagent_id IS NOT NULL AND subagent_id <> '' AND session_id IN (${n.map(()=>`?`).join(`,`)})${o}
185
185
  GROUP BY session_id, subagent_id
186
186
  ORDER BY lastReceivedAt DESC, subagent_id
187
- `,c=e.prepare(i);s.push(...c.all(...n,...a))}return s.map(De)},j=(e,t={},n=Date.now())=>{let r=Oe(e,{...t,limit:2**53-1,offset:0},n).filter(e=>Ve(e,t,n)),i=He(e,r.map(e=>e.sessionId),t),a=new Map;for(let e of i){let t=a.get(e.sessionId)??[];t.push(e),a.set(e.sessionId,t)}let o=r.map(e=>({...e,children:a.get(e.sessionId)})),s=t.offset??0,c=t.limit;return s===0&&c===void 0?o:c===void 0?o.slice(s):o.slice(s,s+c)},Ue=e=>{let t=e.prepare(`SELECT DISTINCT source FROM events WHERE source IS NOT NULL AND source <> '' ORDER BY source`).all(),n=e.prepare(`SELECT DISTINCT event FROM events WHERE event IS NOT NULL AND event <> '' ORDER BY event`).all(),r=e.prepare(`SELECT DISTINCT tool_name AS tool FROM events WHERE tool_name IS NOT NULL AND tool_name <> '' ORDER BY tool_name`).all(),i=e.prepare(`SELECT DISTINCT project_path AS dir FROM events WHERE project_path IS NOT NULL AND project_path <> '' AND project_path NOT LIKE '/private/%' ORDER BY project_path`).all();return{sources:t.map(e=>e.source),events:n.map(e=>e.event),tools:r.map(e=>e.tool),directories:i.map(e=>e.dir)}},M=(e,t,n)=>{e.prepare(`
187
+ `,c=e.prepare(i);s.push(...c.all(...n,...a))}return s.map(ke)},j=(e,t={},n=Date.now())=>{let r=Ae(e,{...t,limit:2**53-1,offset:0},n).filter(e=>Ue(e,t,n)),i=We(e,r.map(e=>e.sessionId),t),a=new Map;for(let e of i){let t=a.get(e.sessionId)??[];t.push(e),a.set(e.sessionId,t)}let o=r.map(e=>({...e,children:a.get(e.sessionId)})),s=t.offset??0,c=t.limit;return s===0&&c===void 0?o:c===void 0?o.slice(s):o.slice(s,s+c)},Ge=e=>{let t=e.prepare(`SELECT DISTINCT source FROM events WHERE source IS NOT NULL AND source <> '' ORDER BY source`).all(),n=e.prepare(`SELECT DISTINCT event FROM events WHERE event IS NOT NULL AND event <> '' ORDER BY event`).all(),r=e.prepare(`SELECT DISTINCT tool_name AS tool FROM events WHERE tool_name IS NOT NULL AND tool_name <> '' ORDER BY tool_name`).all(),i=e.prepare(`SELECT DISTINCT project_path AS dir FROM events WHERE project_path IS NOT NULL AND project_path <> '' AND project_path NOT LIKE '/private/%' ORDER BY project_path`).all(),a=e.prepare(`SELECT DISTINCT skill_name AS skill FROM events WHERE skill_name IS NOT NULL AND skill_name <> '' ORDER BY skill_name`).all(),o=e.prepare(`
188
+ SELECT DISTINCT server FROM (
189
+ SELECT substr(tool_name, 6, instr(substr(tool_name, 6), '__') - 1) AS server
190
+ FROM events
191
+ WHERE tool_name LIKE 'mcp\\_\\_%' ESCAPE '\\'
192
+ AND instr(substr(tool_name, 6), '__') > 0
193
+ )
194
+ WHERE server <> ''
195
+ ORDER BY server
196
+ `).all();return{sources:t.map(e=>e.source),events:n.map(e=>e.event),tools:r.map(e=>e.tool),directories:i.map(e=>e.dir),skills:a.map(e=>e.skill),mcpServers:o.map(e=>e.server)}},M=(e,t,n)=>{e.prepare(`
188
197
  INSERT INTO imports (path, mtime, imported_at)
189
198
  VALUES (?, ?, ?)
190
199
  ON CONFLICT(path) DO UPDATE SET
191
200
  mtime = excluded.mtime,
192
201
  imported_at = excluded.imported_at
193
- `).run(t,n,Date.now())},We=(e,t)=>{let n=e.prepare(`SELECT mtime FROM imports WHERE path = ?`).get(t);return n?Number(n.mtime):void 0},Ge=e=>{e.exec(`DELETE FROM imports`)},Ke=new Set([`Read`,`Edit`,`Write`,`MultiEdit`,`NotebookEdit`]);function N(e){return typeof e==`string`&&e.length>0?e:void 0}function qe(e,t){if(!e||!Ke.has(e)||typeof t!=`object`||!t)return;let n=t;return N(n.file_path)??N(n.notebook_path)??N(n.path)}function Je(e,t){if(!(e!==`Skill`||typeof t!=`object`||!t))return N(t.skill)}function P(e){if(e==null||typeof e==`object`)return;let t=String(e).trim();return t.length>0?t:void 0}function F(e){let t=P(e);if(t!==void 0)return t;if(Array.isArray(e))for(let t of e){let e=P(t);if(e!==void 0)return e}}function Ye(e){if(typeof e!=`object`||!e)return!1;let t=e,n=t.errcode??0;return t.code===`ERR_SQLITE_ERROR`&&((n&255)==5||(n&255)==6)}function Xe(e,t,n){if(t.length===0)return;let r;try{r=JSON.parse(t)}catch{return}let i=e[0];if(i!==`cursor`&&i!==`claude`)return;let a;a=i===`cursor`?P(r.hook_event_name):e[1];let o=i===`cursor`&&a===`subagentStart`?P(r.parent_conversation_id)??P(r.conversation_id)??P(r.sessionId)??P(r.session_id):P(r.sessionId)??P(r.session_id),s=we(r),c=P(r.projectPath)??P(r.cwd)??P(r.project_path)??F(r.workspaceRoot)??F(r.workspaceRoots)??F(r.workspace_roots)??F(r.workspace_root)??P(r.workspace_path),l=P(r.toolName??r.tool_name??r.tool),u=P(r.filePath??r.file_path??r.path)??qe(l,r.tool_input),d=Je(l,r.tool_input),f=P(r.client)??(i===`cursor`?`cursor`:`claude_code`),p=P(r.subagent_id)??void 0,m=P(r.subagent_type)??void 0,h=P(r.transcript_path)??void 0,g={source:i,client:f,event:a,sessionId:o,happenedAt:s,projectPath:c,filePath:u,toolName:l,skillName:d,payload:t,subagentId:p,subagentType:m,transcriptPath:h},_=ee(i,a??``),v;try{if(v=T(n,500),!g.subagentId){let e=P(r.tool_use_id);if(e){let t=v.prepare(`SELECT subagent_id FROM events WHERE event = 'subagentStart' AND subagent_id = ? LIMIT 1`).get(e);t&&(g.subagentId=t.subagent_id)}}E(v,g)}catch(e){if(Ye(e))console.warn(`happenin: dropping event due to database lock`,e);else throw e}finally{try{v?.close()}catch{}}return _}async function Ze(e){let t=[];for await(let e of c.stdin)t.push(Buffer.isBuffer(e)?e:Buffer.from(e,`utf8`));let n=Xe(e,Buffer.concat(t).toString(`utf8`).trim());n!==void 0&&c.stdout.write(`${n}\n`)}const Qe=`claude-transcript`,I=`cursor-transcript`,$e=`cursor`;function et(e){if(e.type!==`assistant`||typeof e.message!=`object`||e.message===null)return[];let t=e.message.content;if(!Array.isArray(t))return[];let n=[];for(let e of t)if(typeof e==`object`&&e&&e.type===`tool_use`&&typeof e.name==`string`){let t=e.name,r=e.input;n.push({name:t,filePath:qe(t,r),skillName:Je(t,r)})}return n}const L=e=>{try{return r(e,{withFileTypes:!0})}catch{return[]}},tt=e=>{try{return n(e,`utf8`)}catch{return}},nt=e=>{try{return Math.floor(a(e).mtimeMs)}catch{return}},R=(e,t)=>{let n=nt(t);if(n===void 0||We(e,t)===n)return;let r=tt(t);if(r!==void 0)return{content:r,mtime:n}};function rt(e){let t=[];for(let n of L(e)){if(n.isSymbolicLink())continue;let r=s.join(e,n.name);n.isDirectory()?t.push(...rt(r)):n.isFile()&&n.name.endsWith(`.jsonl`)&&n.name!==`store.db`&&t.push(r)}return t}function it(e){let t=[];for(let n of L(e)){if(!n.isDirectory()||n.isSymbolicLink())continue;let r=s.join(e,n.name);for(let e of L(r))e.isDirectory()&&!e.isSymbolicLink()&&t.push(s.join(r,e.name))}return t}async function at(e,t){let n=R(e,t);if(n===void 0)return;let{content:r,mtime:i}=n,a=s.basename(t,`.jsonl`),o=[];for(let e of r.split(/\r?\n/)){if(e.trim().length===0)continue;let t;try{t=JSON.parse(e)}catch{continue}typeof t==`object`&&t&&o.push({obj:t,line:e})}e.prepare(`DELETE FROM events WHERE source = ? AND source_path = ?`).run(Qe,t);for(let{obj:n,line:r}of o){let i={source:Qe,client:`claude_code`,event:typeof n.type==`string`?n.type:`transcript`,sessionId:typeof n.sessionId==`string`?n.sessionId:a,happenedAt:typeof n.timestamp==`string`?n.timestamp:typeof n.timestamp==`number`?String(n.timestamp):void 0,projectPath:typeof n.cwd==`string`?n.cwd:void 0,payload:r,sourcePath:t},o=et(n);if(o.length===0){E(e,i);continue}for(let t of o)E(e,{...i,toolName:t.name,filePath:t.filePath,skillName:t.skillName})}M(e,t,i)}async function ot(e){let t=s.resolve(u(),`.claude/projects`);for(let n of rt(t))await at(e,n)}async function st(e,t,n){let r=R(e,t);if(r===void 0)return;let{content:i,mtime:a}=r,o=[],s;try{s=JSON.parse(i)}catch{return}Array.isArray(s)&&(o=s),e.prepare(`DELETE FROM events WHERE source = ? AND source_path = ?`).run(I,t);for(let r of o)typeof r==`string`&&E(e,{source:I,client:$e,event:`prompt`,sessionId:n,payload:JSON.stringify({prompt:r}),sourcePath:t});M(e,t,a)}async function ct(e,t,n){let r=R(e,t);if(r===void 0)return;let{content:i,mtime:a}=r;e.prepare(`DELETE FROM events WHERE source = ? AND source_path = ?`).run(I,t),E(e,{source:I,client:$e,event:`session_meta`,sessionId:n,payload:i,sourcePath:t}),M(e,t,a)}async function lt(e,t){let n=s.basename(t);await st(e,s.join(t,`prompt_history.json`),n),await ct(e,s.join(t,`meta.json`),n)}async function ut(e){let t=s.resolve(u(),`.cursor/chats`);for(let n of it(t))await lt(e,n)}async function dt(e){let t=T(w());try{xe(t),e?.includes(`--force`)&&Ge(t),await ft(t)}finally{t.close()}}async function ft(e){await ot(e),await ut(e)}function z(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#39;`)}function B(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}function V(e,t=24){return e.length<=t?e:`${e.slice(0,t-1)}…`}function pt(e){if(e.length===0)return``;let t=e[0];for(let n of e.slice(1)){let e=0;for(;e<t.length&&e<n.length&&t.charAt(e)===n.charAt(e);)e++;if(t=t.slice(0,e),t===``)return``}let n=t.lastIndexOf(`/`);return n>0?t.slice(0,n+1):``}function mt(e,t){return e.split(`/`).filter(e=>e.length>0).slice(-t).join(`/`)}function ht(e){let t=Math.max(0,Math.round(e/1e3)),n=Math.floor(t/60),r=t%60;return n>0?`${n}m ${r}s`:`${r}s`}function H(e){if(e==null||e===``)return`-`;let t=new Date(e);return Number.isNaN(t.getTime())?`-`:t.toLocaleString()}function gt(e,t={}){let n={...e,...t},r=new URLSearchParams;return n.source&&r.set(`source`,n.source),n.event&&r.set(`event`,n.event),n.sessionId&&r.set(`session`,n.sessionId),n.q&&r.set(`q`,n.q),n.since!==void 0&&r.set(`since`,String(n.since)),n.range&&r.set(`range`,n.range),n.status&&r.set(`status`,n.status),n.tool&&r.set(`tool`,n.tool),n.skill&&r.set(`skill`,n.skill),n.file&&r.set(`file`,n.file),n.mdDir&&r.set(`mdDir`,n.mdDir),n.view&&r.set(`view`,n.view),n.minDuration!==void 0&&r.set(`minDuration`,String(n.minDuration)),n.maxDuration!==void 0&&r.set(`maxDuration`,String(n.maxDuration)),n.limit!==void 0&&r.set(`limit`,String(n.limit)),n.offset!==void 0&&r.set(`offset`,String(n.offset)),r.toString()}function U(e,t={}){return`/fragments/sessions?${gt(e,t)}`}function _t(e){let t=e.totalSessions===0?`-`:`${Math.round(e.successRate)}%`;return`<div class="metric-grid">
202
+ `).run(t,n,Date.now())},Ke=(e,t)=>{let n=e.prepare(`SELECT mtime FROM imports WHERE path = ?`).get(t);return n?Number(n.mtime):void 0},qe=e=>{e.exec(`DELETE FROM imports`)},Je=new Set([`Read`,`Edit`,`Write`,`MultiEdit`,`NotebookEdit`]);function N(e){return typeof e==`string`&&e.length>0?e:void 0}function Ye(e,t){if(!e||!Je.has(e)||typeof t!=`object`||!t)return;let n=t;return N(n.file_path)??N(n.notebook_path)??N(n.path)}function Xe(e,t){if(!(e!==`Skill`||typeof t!=`object`||!t))return N(t.skill)}function P(e){if(e==null||typeof e==`object`)return;let t=String(e).trim();return t.length>0?t:void 0}function F(e){let t=P(e);if(t!==void 0)return t;if(Array.isArray(e))for(let t of e){let e=P(t);if(e!==void 0)return e}}function Ze(e){if(typeof e!=`object`||!e)return!1;let t=e,n=t.errcode??0;return t.code===`ERR_SQLITE_ERROR`&&((n&255)==5||(n&255)==6)}function Qe(e,t,n){if(t.length===0)return;let r;try{r=JSON.parse(t)}catch{return}let i=e[0];if(i!==`cursor`&&i!==`claude`)return;let a;a=i===`cursor`?P(r.hook_event_name):e[1];let o=i===`cursor`&&a===`subagentStart`?P(r.parent_conversation_id)??P(r.conversation_id)??P(r.sessionId)??P(r.session_id):P(r.sessionId)??P(r.session_id),s=Ce(r),c=P(r.projectPath)??P(r.cwd)??P(r.project_path)??F(r.workspaceRoot)??F(r.workspaceRoots)??F(r.workspace_roots)??F(r.workspace_root)??P(r.workspace_path),l=P(r.toolName??r.tool_name??r.tool),u=P(r.filePath??r.file_path??r.path)??Ye(l,r.tool_input),d=Xe(l,r.tool_input),f=P(r.client)??(i===`cursor`?`cursor`:`claude_code`),p=P(r.subagent_id)??void 0,m=P(r.subagent_type)??void 0,h=P(r.transcript_path)??void 0,g={source:i,client:f,event:a,sessionId:o,happenedAt:s,projectPath:c,filePath:u,toolName:l,skillName:d,payload:t,subagentId:p,subagentType:m,transcriptPath:h},_=b(i,a??``),v;try{if(v=E(n,500),!g.subagentId){let e=P(r.tool_use_id);if(e){let t=v.prepare(`SELECT subagent_id FROM events WHERE event = 'subagentStart' AND subagent_id = ? LIMIT 1`).get(e);t&&(g.subagentId=t.subagent_id)}}D(v,g)}catch(e){if(Ze(e))console.warn(`happenin: dropping event due to database lock`,e);else throw e}finally{try{v?.close()}catch{}}return _}async function $e(e){let t=[];for await(let e of c.stdin)t.push(Buffer.isBuffer(e)?e:Buffer.from(e,`utf8`));let n=Qe(e,Buffer.concat(t).toString(`utf8`).trim());n!==void 0&&c.stdout.write(`${n}\n`)}const et=`claude-transcript`,I=`cursor-transcript`,tt=`cursor`;function nt(e){if(e.type!==`assistant`||typeof e.message!=`object`||e.message===null)return[];let t=e.message.content;if(!Array.isArray(t))return[];let n=[];for(let e of t)if(typeof e==`object`&&e&&e.type===`tool_use`&&typeof e.name==`string`){let t=e.name,r=e.input;n.push({name:t,filePath:Ye(t,r),skillName:Xe(t,r)})}return n}const L=e=>{try{return r(e,{withFileTypes:!0})}catch{return[]}},rt=e=>{try{return n(e,`utf8`)}catch{return}},it=e=>{try{return Math.floor(a(e).mtimeMs)}catch{return}},R=(e,t)=>{let n=it(t);if(n===void 0||Ke(e,t)===n)return;let r=rt(t);if(r!==void 0)return{content:r,mtime:n}};function at(e){let t=[];for(let n of L(e)){if(n.isSymbolicLink())continue;let r=s.join(e,n.name);n.isDirectory()?t.push(...at(r)):n.isFile()&&n.name.endsWith(`.jsonl`)&&n.name!==`store.db`&&t.push(r)}return t}function ot(e){let t=[];for(let n of L(e)){if(!n.isDirectory()||n.isSymbolicLink())continue;let r=s.join(e,n.name);for(let e of L(r))e.isDirectory()&&!e.isSymbolicLink()&&t.push(s.join(r,e.name))}return t}async function st(e,t){let n=R(e,t);if(n===void 0)return;let{content:r,mtime:i}=n,a=s.basename(t,`.jsonl`),o=[];for(let e of r.split(/\r?\n/)){if(e.trim().length===0)continue;let t;try{t=JSON.parse(e)}catch{continue}typeof t==`object`&&t&&o.push({obj:t,line:e})}e.prepare(`DELETE FROM events WHERE source = ? AND source_path = ?`).run(et,t);for(let{obj:n,line:r}of o){let i={source:et,client:`claude_code`,event:typeof n.type==`string`?n.type:`transcript`,sessionId:typeof n.sessionId==`string`?n.sessionId:a,happenedAt:typeof n.timestamp==`string`?n.timestamp:typeof n.timestamp==`number`?String(n.timestamp):void 0,projectPath:typeof n.cwd==`string`?n.cwd:void 0,payload:r,sourcePath:t},o=nt(n);if(o.length===0){D(e,i);continue}for(let t of o)D(e,{...i,toolName:t.name,filePath:t.filePath,skillName:t.skillName})}M(e,t,i)}async function ct(e){let t=s.resolve(x(),`.claude/projects`);for(let n of at(t))await st(e,n)}async function lt(e,t,n){let r=R(e,t);if(r===void 0)return;let{content:i,mtime:a}=r,o=[],s;try{s=JSON.parse(i)}catch{return}Array.isArray(s)&&(o=s),e.prepare(`DELETE FROM events WHERE source = ? AND source_path = ?`).run(I,t);for(let r of o)typeof r==`string`&&D(e,{source:I,client:tt,event:`prompt`,sessionId:n,payload:JSON.stringify({prompt:r}),sourcePath:t});M(e,t,a)}async function ut(e,t,n){let r=R(e,t);if(r===void 0)return;let{content:i,mtime:a}=r;e.prepare(`DELETE FROM events WHERE source = ? AND source_path = ?`).run(I,t),D(e,{source:I,client:tt,event:`session_meta`,sessionId:n,payload:i,sourcePath:t}),M(e,t,a)}async function dt(e,t){let n=s.basename(t);await lt(e,s.join(t,`prompt_history.json`),n),await ut(e,s.join(t,`meta.json`),n)}async function ft(e){let t=s.resolve(x(),`.cursor/chats`);for(let n of ot(t))await dt(e,n)}async function pt(e){let t=E(T());try{be(t),e?.includes(`--force`)&&qe(t),await mt(t)}finally{t.close()}}async function mt(e){await ct(e),await ft(e)}function z(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#39;`)}function B(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}function V(e,t=24){return e.length<=t?e:`${e.slice(0,t-1)}…`}function ht(e){if(e.length===0)return``;let t=e[0];for(let n of e.slice(1)){let e=0;for(;e<t.length&&e<n.length&&t.charAt(e)===n.charAt(e);)e++;if(t=t.slice(0,e),t===``)return``}let n=process.platform===`win32`?Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`)):t.lastIndexOf(`/`);return n>0?t.slice(0,n+1):``}function gt(e,t){return e.split(process.platform===`win32`?/[\\/]/:/\//).filter(e=>e.length>0).slice(-t).join(`/`)}function _t(e){let t=Math.max(0,Math.round(e/1e3)),n=Math.floor(t/60),r=t%60;return n>0?`${n}m ${r}s`:`${r}s`}function H(e){if(e==null||e===``)return`-`;let t=new Date(e);return Number.isNaN(t.getTime())?`-`:t.toLocaleString()}function U(e,t,n){if(n!==void 0&&n.length!==0)for(let r of n)e.append(t,r)}function vt(e,t={}){let n={...e,...t},r=new URLSearchParams;return n.source&&r.set(`source`,n.source),n.event&&r.set(`event`,n.event),n.sessionId&&r.set(`session`,n.sessionId),n.q&&r.set(`q`,n.q),n.since!==void 0&&r.set(`since`,String(n.since)),n.range&&r.set(`range`,n.range),n.status&&r.set(`status`,n.status),n.tool&&r.set(`tool`,n.tool),n.skill&&r.set(`skill`,n.skill),n.file&&r.set(`file`,n.file),n.mdDir&&r.set(`mdDir`,n.mdDir),U(r,`dirs`,n.projectPaths),U(r,`skills`,n.skills),U(r,`mcp`,n.mcpServers),n.view&&r.set(`view`,n.view),n.minDuration!==void 0&&r.set(`minDuration`,String(n.minDuration)),n.maxDuration!==void 0&&r.set(`maxDuration`,String(n.maxDuration)),n.limit!==void 0&&r.set(`limit`,String(n.limit)),n.offset!==void 0&&r.set(`offset`,String(n.offset)),r.toString()}function W(e,t={}){return`/fragments/sessions?${vt(e,t)}`}function yt(e){let t=e.totalSessions===0?`-`:`${Math.round(e.successRate)}%`;return`<div class="metric-grid">
194
203
  <div class="metric-card metric-sessions">
195
204
  <div class="metric-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 18 9 12 13 16 21 6"/><circle cx="21" cy="6" r="2"/></svg></div>
196
205
  <div class="metric-label">Total Sessions</div>
@@ -204,17 +213,17 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
204
213
  <div class="metric-card metric-duration">
205
214
  <div class="metric-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
206
215
  <div class="metric-label">Average Duration</div>
207
- <div class="metric-value">${ht(e.averageDurationMs)}</div>
216
+ <div class="metric-value">${_t(e.averageDurationMs)}</div>
208
217
  </div>
209
218
  <div class="metric-card metric-success">
210
219
  <div class="metric-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 12 15 16 10"/></svg></div>
211
220
  <div class="metric-label">Success Rate</div>
212
221
  <div class="metric-value">${t}</div>
213
222
  </div>
214
- </div>`}function vt(e,t){return t===`day`?e.slice(5,10):e.slice(11,16)}function yt(e,t=`hour`){if(e.length===0)return`<div class="chart-panel"><h2 class="chart-title">Event Frequency Over Time</h2><div class="chart-empty">No data</div></div>`;let n=Math.max(0,...e.map(e=>e.count)),r=e.length>1?552/(e.length-1):0,i=n>0?172/n:1,a=e.map((e,n)=>({x:44+n*r,y:216-e.count*i,count:e.count,label:vt(e.bucket,t)})),o=a.map(e=>`${e.x},${e.y}`).join(` `),s=`M ${a[0].x} 216 ${a.map(e=>`L ${e.x} ${e.y}`).join(` `)} L ${a[a.length-1].x} 216 Z`,c=e.length<=12?1:Math.ceil(e.length/8),l=a.filter((e,t)=>t%c===0).map(e=>`<text x="${e.x}" y="248" text-anchor="middle">${z(e.label)}</text>`).join(``),u=[0,.5,1].map(e=>{let t=n>0?Math.round(n*e):0;return`<text x="36" y="${216-172*e+4}" text-anchor="end" class="y-label">${t}</text>`}).join(``);return`<div class="chart-panel"><h2 class="chart-title">Event Frequency Over Time</h2>${`<svg class="event-chart" viewBox="0 0 640 260" preserveAspectRatio="xMidYMid meet"><defs><linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="var(--accent)" stop-opacity="0.5"/><stop offset="100%" stop-color="var(--accent-2)" stop-opacity="0.05"/></linearGradient></defs>${[.25,.5,.75].map(e=>{let t=216-172*e;return`<line class="grid" x1="44" y1="${t}" x2="596" y2="${t}"/>`}).join(``)}<line class="axis" x1="44" y1="44" x2="44" y2="216"/><line class="axis" x1="44" y1="216" x2="596" y2="216"/>${u}<path class="area" fill="url(#areaGradient)" d="${s}"/><polyline points="${o}"/>${l}</svg>`}${`<div class="chart-max">max ${n.toLocaleString()}</div>`}</div>`}function bt(e,t,n){return e.length<=n?e:t===`start`?`…${e.slice(-(n-1))}`:`${e.slice(0,n-1)}…`}function W(e,t,n={}){let{truncate:r=`end`,labelLimit:i=20,wideLabel:a=!1}=n,o=a?`chart-panel chart-panel--wide-label`:`chart-panel`;if(t.length===0)return`<div class="${o}"><h2 class="chart-title">${z(e)}</h2><div class="chart-empty">No data</div></div>`;let s=Math.max(...t.map(e=>e.count)),c=t.map(e=>{let t=s>0?e.count/s*100:0,n=z(bt(e.label,r,i)),a=`<span class="tool-name" title="${B(e.title??e.label)}">${n}</span><div class="tool-bar-bg"><div class="tool-bar" style="width: ${t}%"></div></div><span class="tool-count">${e.count}</span>`;return e.href?`<a class="tool-row" href="${B(e.href)}" hx-get="${B(e.href)}" hx-target="#dashboard-content" hx-swap="innerHTML">${a}</a>`:`<div class="tool-row">${a}</div>`}).join(``);return`<div class="${o}"><h2 class="chart-title">${z(e)}</h2><div class="tool-chart">${c}</div></div>`}function xt(e,t){return W(`Most Used Tools`,e.map(e=>({label:e.tool,count:e.count,href:t&&U(t,{tool:e.tool,view:`list`,offset:0})})),{labelLimit:36,wideLabel:!0})}function St(e,t){return W(`Most Used Skills`,e.map(e=>({label:e.skill,count:e.count,href:t&&U(t,{skill:e.skill,view:`list`,offset:0})})),{labelLimit:36,wideLabel:!0})}function Ct(e,t){return W(`Top Markdown Files`,e.map(e=>({label:mt(e.file,2),title:e.file,count:e.count,href:t&&U(t,{file:e.file,view:`list`,offset:0})})),{truncate:`start`,labelLimit:40,wideLabel:!0})}const G=[{key:`actualValue`,label:`Actual value`,color:`var(--success)`},{key:`mdFiles`,label:`Markdown files`,color:`var(--accent-2)`},{key:`mcpServers`,label:`MCP servers`,color:`var(--accent)`},{key:`bloatware`,label:`Bloatware`,color:`var(--warning)`}];function wt(e){return e>=1e6?`${(e/1e6).toFixed(1)}MB`:e>=1e3?`${(e/1e3).toFixed(1)}KB`:`${e}B`}function Tt(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:`${e}`}function Et(e,t,n,r){let i=G.reduce((e,{key:n})=>e+t[n],0);if(i===0)return`<div class="context-bar-block">
223
+ </div>`}function bt(e,t){return t===`day`?e.slice(5,10):e.slice(11,16)}function xt(e,t=`hour`){if(e.length===0)return`<div class="chart-panel"><h2 class="chart-title">Event Frequency Over Time</h2><div class="chart-empty">No data</div></div>`;let n=Math.max(0,...e.map(e=>e.count)),r=e.length>1?552/(e.length-1):0,i=n>0?172/n:1,a=e.map((e,n)=>({x:44+n*r,y:216-e.count*i,count:e.count,label:bt(e.bucket,t)})),o=a.map(e=>`${e.x},${e.y}`).join(` `),s=`M ${a[0].x} 216 ${a.map(e=>`L ${e.x} ${e.y}`).join(` `)} L ${a[a.length-1].x} 216 Z`,c=e.length<=12?1:Math.ceil(e.length/8),l=a.filter((e,t)=>t%c===0).map(e=>`<text x="${e.x}" y="248" text-anchor="middle">${z(e.label)}</text>`).join(``),u=[0,.5,1].map(e=>{let t=n>0?Math.round(n*e):0;return`<text x="36" y="${216-172*e+4}" text-anchor="end" class="y-label">${t}</text>`}).join(``);return`<div class="chart-panel"><h2 class="chart-title">Event Frequency Over Time</h2>${`<svg class="event-chart" viewBox="0 0 640 260" preserveAspectRatio="xMidYMid meet"><defs><linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="var(--accent)" stop-opacity="0.5"/><stop offset="100%" stop-color="var(--accent-2)" stop-opacity="0.05"/></linearGradient></defs>${[.25,.5,.75].map(e=>{let t=216-172*e;return`<line class="grid" x1="44" y1="${t}" x2="596" y2="${t}"/>`}).join(``)}<line class="axis" x1="44" y1="44" x2="44" y2="216"/><line class="axis" x1="44" y1="216" x2="596" y2="216"/>${u}<path class="area" fill="url(#areaGradient)" d="${s}"/><polyline points="${o}"/>${l}</svg>`}${`<div class="chart-max">max ${n.toLocaleString()}</div>`}</div>`}function St(e,t,n){return e.length<=n?e:t===`start`?`…${e.slice(-(n-1))}`:`${e.slice(0,n-1)}…`}function G(e,t,n={}){let{truncate:r=`end`,labelLimit:i=20,wideLabel:a=!1}=n,o=a?`chart-panel chart-panel--wide-label`:`chart-panel`;if(t.length===0)return`<div class="${o}"><h2 class="chart-title">${z(e)}</h2><div class="chart-empty">No data</div></div>`;let s=Math.max(...t.map(e=>e.count)),c=t.map(e=>{let t=s>0?e.count/s*100:0,n=z(St(e.label,r,i)),a=`<span class="tool-name" title="${B(e.title??e.label)}">${n}</span><div class="tool-bar-bg"><div class="tool-bar" style="width: ${t}%"></div></div><span class="tool-count">${e.count}</span>`;return e.href?`<a class="tool-row" href="${B(e.href)}" hx-get="${B(e.href)}" hx-target="#dashboard-content" hx-swap="innerHTML">${a}</a>`:`<div class="tool-row">${a}</div>`}).join(``);return`<div class="${o}"><h2 class="chart-title">${z(e)}</h2><div class="tool-chart">${c}</div></div>`}function Ct(e,t){return G(`Most Used Tools`,e.map(e=>({label:e.tool,count:e.count,href:t&&W(t,{tool:e.tool,view:`list`,offset:0})})),{labelLimit:36,wideLabel:!0})}function wt(e,t){return G(`Most Used Skills`,e.map(e=>({label:e.skill,count:e.count,href:t&&W(t,{skill:e.skill,view:`list`,offset:0})})),{labelLimit:36,wideLabel:!0})}function Tt(e,t){return G(`Top Markdown Files`,e.map(e=>({label:gt(e.file,2),title:e.file,count:e.count,href:t&&W(t,{file:e.file,view:`list`,offset:0})})),{truncate:`start`,labelLimit:40,wideLabel:!0})}const K=[{key:`actualValue`,label:`Actual value`,color:`var(--success)`},{key:`mdFiles`,label:`Markdown files`,color:`var(--accent-2)`},{key:`mcpServers`,label:`MCP servers`,color:`var(--accent)`},{key:`bloatware`,label:`Bloatware`,color:`var(--warning)`}];function Et(e){return e>=1e6?`${(e/1e6).toFixed(1)}MB`:e>=1e3?`${(e/1e3).toFixed(1)}KB`:`${e}B`}function Dt(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:`${e}`}function Ot(e,t,n,r){let i=K.reduce((e,{key:n})=>e+t[n],0);if(i===0)return`<div class="context-bar-block">
215
224
  <h3 class="context-bar-title">${z(e)}</h3>
216
225
  <div class="chart-empty">${z(r)}</div>
217
- </div>`;let a=G.filter(({key:e})=>t[e]>0).map(({key:e,label:r,color:a})=>{let o=t[e],s=o/i*100;return`<div class="context-segment" style="width: ${s}%; background: ${a}" title="${z(`${r}: ${n(o)} (${s.toFixed(1)}%)`)}"></div>`}).join(``),o=G.map(({key:e,label:r,color:a})=>{let o=t[e],s=o/i*100;return`<div class="context-legend-row">
226
+ </div>`;let a=K.filter(({key:e})=>t[e]>0).map(({key:e,label:r,color:a})=>{let o=t[e],s=o/i*100;return`<div class="context-segment" style="width: ${s}%; background: ${a}" title="${z(`${r}: ${n(o)} (${s.toFixed(1)}%)`)}"></div>`}).join(``),o=K.map(({key:e,label:r,color:a})=>{let o=t[e],s=o/i*100;return`<div class="context-legend-row">
218
227
  <span class="context-legend-dot" style="background: ${a}"></span>
219
228
  <span class="context-legend-label">${z(r)}</span>
220
229
  <span class="context-legend-value">${z(n(o))} · ${s.toFixed(1)}%</span>
@@ -222,21 +231,21 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
222
231
  <h3 class="context-bar-title">${z(e)}</h3>
223
232
  <div class="context-bar">${a}</div>
224
233
  <div class="context-legend">${o}</div>
225
- </div>`}function Dt(e){return`<div class="chart-panel context-breakdown">
234
+ </div>`}function kt(e){return`<div class="chart-panel context-breakdown">
226
235
  <h2 class="chart-title">Context Breakdown</h2>
227
- <div class="context-bars">${Et(`By payload size (all sources)`,e.bytes,wt,`No data`)}${Et(`By tokens (imported Claude sessions)`,e.tokens,Tt,"Run `happenin import` to see token-based numbers")}</div>
228
- </div>`}function Ot(e,t,n){let r=new URLSearchParams({session:t});return n&&r.set(`subagent`,n),e?.source&&r.set(`source`,e.source),e?.event&&r.set(`event`,e.event),e?.q&&r.set(`q`,e.q),e?.range&&r.set(`range`,e.range),e?.status&&r.set(`status`,e.status),e?.tool&&r.set(`tool`,e.tool),e?.minDuration!==void 0&&r.set(`minDuration`,String(e.minDuration)),e?.maxDuration!==void 0&&r.set(`maxDuration`,String(e.maxDuration)),e?.limit!==void 0&&r.set(`limit`,String(e.limit)),e?.offset!==void 0&&r.set(`offset`,String(e.offset)),`/fragments/detail?${r.toString()}`}function K(e,t,n,r,i,a=!1,o=``,s=``){let c=Be(e,t),l=a?e.subagentId??`no subagent`:e.sessionId??`no session`,u=z(V(l,28)),d=z(H(e.firstAt??e.firstReceivedAt)),f=z(ht(e.durationMs)),p=(a?e.subagentId===r&&e.sessionId===n:e.sessionId===n)?` active`:``,m=e.sessionId??``,h=a?e.subagentId:void 0,g=!a&&!!e.children?.length,_=m&&!g?` hx-get="${B(Ot(i,m,h))}" hx-target="#dashboard-content" hx-swap="innerHTML"`:``,v=m&&g?` hx-get="${B(Ot(i,m))}" hx-target="#dashboard-content" hx-swap="innerHTML"`:``,y=a?` session-subagent`:``,ee=!a&&e.children?.length&&e.sessionId===n?` expanded`:``,b=!a&&e.children?.length?` session-parent`:``,te=!a&&!e.sessionId?` session-item-static`:``,x=a?` data-session="${B(m)}" data-subagent="${B(e.subagentId??``)}"`:` data-session="${B(e.sessionId??``)}"`,ne=a&&e.subagentType?` <span class="subagent-type-badge">${z(e.subagentType)}</span>`:``;return`<li class="session-item${b}${ee}${y}${p}${te}"${x}${_}>
236
+ <div class="context-bars">${Ot(`By payload size (all sources)`,e.bytes,Et,`No data`)}${Ot(`By tokens (imported Claude sessions)`,e.tokens,Dt,"Run `happenin import` to see token-based numbers")}</div>
237
+ </div>`}function At(e,t,n){let r=new URLSearchParams({session:t});if(n&&r.set(`subagent`,n),e?.source&&r.set(`source`,e.source),e?.event&&r.set(`event`,e.event),e?.q&&r.set(`q`,e.q),e?.range&&r.set(`range`,e.range),e?.status&&r.set(`status`,e.status),e?.tool&&r.set(`tool`,e.tool),e?.projectPaths&&e.projectPaths.length>0)for(let t of e.projectPaths)r.append(`dirs`,t);if(e?.skills&&e.skills.length>0)for(let t of e.skills)r.append(`skills`,t);if(e?.mcpServers&&e.mcpServers.length>0)for(let t of e.mcpServers)r.append(`mcp`,t);return e?.minDuration!==void 0&&r.set(`minDuration`,String(e.minDuration)),e?.maxDuration!==void 0&&r.set(`maxDuration`,String(e.maxDuration)),e?.limit!==void 0&&r.set(`limit`,String(e.limit)),e?.offset!==void 0&&r.set(`offset`,String(e.offset)),`/fragments/detail?${r.toString()}`}function q(e,t,n,r,i,a=!1,o=``,s=``){let c=He(e,t),l=a?e.subagentId??`no subagent`:e.sessionId??`no session`,u=z(V(l,28)),d=z(H(e.firstAt??e.firstReceivedAt)),f=z(_t(e.durationMs)),p=(a?e.subagentId===r&&e.sessionId===n:e.sessionId===n)?` active`:``,m=e.sessionId??``,h=a?e.subagentId:void 0,g=!a&&!!e.children?.length,_=m&&!g?` hx-get="${B(At(i,m,h))}" hx-target="#dashboard-content" hx-swap="innerHTML"`:``,v=m&&g?` hx-get="${B(At(i,m))}" hx-target="#dashboard-content" hx-swap="innerHTML"`:``,y=a?` session-subagent`:``,b=!a&&e.children?.length&&e.sessionId===n?` expanded`:``,x=!a&&e.children?.length?` session-parent`:``,S=!a&&!e.sessionId?` session-item-static`:``,ee=a?` data-session="${B(m)}" data-subagent="${B(e.subagentId??``)}"`:` data-session="${B(e.sessionId??``)}"`,te=a&&e.subagentType?` <span class="subagent-type-badge">${z(e.subagentType)}</span>`:``;return`<li class="session-item${x}${b}${y}${p}${S}"${ee}${_}>
229
238
  ${o}
230
239
  <div class="session-main"${v}>
231
240
  <div class="session-row">
232
- <span class="session-id" title="${B(l)}">${u}${ne}</span>
241
+ <span class="session-id" title="${B(l)}">${u}${te}</span>
233
242
  <span class="status-badge status-${c}">${c}</span>
234
243
  </div>
235
244
  <div class="session-meta"><span>${f}</span><span>${e.eventCount} events</span><span>${d}</span></div>
236
245
  </div>
237
246
  <span class="session-chevron" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg></span>
238
247
  ${s}
239
- </li>`}function kt(e,t=Date.now(),n,r,i){return e.length===0?`<ul class="session-list sessions-table"><li class="empty">No sessions found.</li></ul>`:`<ul class="session-list sessions-table">${e.map(e=>e.children?.length?K(e,t,n,r,i,!1,`<button type="button" class="session-toggle" aria-label="toggle subagents" onclick="event.stopPropagation(); const li = this.closest('.session-item'); li.classList.toggle('expanded'); this.textContent = li.classList.contains('expanded') ? '▾' : '▸'">${e.sessionId===n?`▾`:`▸`}</button>`,`<ul class="session-children">${e.children.map(e=>K(e,t,n,r,i,!0)).join(``)}</ul>`):K(e,t,n,r,i)).join(``)}</ul>`}function At(e){if(e)try{return JSON.parse(e)}catch{return}}function jt(e){return{id:e.id,source:e.source,client:e.client,event:e.event,sessionId:e.sessionId,happenedAt:e.happenedAt,receivedAt:e.receivedAt,projectPath:e.projectPath,filePath:e.filePath,toolName:e.toolName,skillName:e.skillName,sourcePath:e.sourcePath,subagentId:e.subagentId,subagentType:e.subagentType,transcriptPath:e.transcriptPath,payload:At(e.payload??``)??e.payload}}function Mt(e){let t=new Map;for(let n of e){let e=n.subagentId??null;t.has(e)||t.set(e,[]),t.get(e).push(n)}let n=[];for(let[e,r]of t){let t=r.toSorted((e,t)=>t.id-e.id),i=t.findIndex(e=>e.event===`subagentStart`),a=i>=0?t.at(i):t.at(0),o=t.filter(e=>e!==a);n.push({subagentId:e,parent:a,children:o,maxId:t.at(0).id})}return n.toSorted((e,t)=>t.maxId-e.maxId)}function Nt(e){return`<span class="ts">${z(H(e.happenedAt??e.receivedAt))}</span><span class="source">${z(e.source??``)}</span><span class="event">${z(e.event??``)}</span><span class="tool">${z(e.toolName??``)}</span>`}function Pt(e){let t=z(H(e.happenedAt??e.receivedAt)),n=z(e.source??``),r=z(e.client??``),i=z(e.event??``),a=z(e.toolName??``),o=z(e.subagentId??``),s=z(e.subagentType??``),c=z(e.projectPath??``),l=z(e.filePath??``),u=z(e.skillName??``),d=o?`<div><span>Subagent</span><span>${s||`-`} (${o})</span></div>`:``,f=l?`<div><span>File</span><span>${l}</span></div>`:``,p=u?`<div><span>Skill</span><span>${u}</span></div>`:``;return`<div class="detail-attrs">
248
+ </li>`}function jt(e,t=Date.now(),n,r,i){return e.length===0?`<ul class="session-list sessions-table"><li class="empty">No sessions found.</li></ul>`:`<ul class="session-list sessions-table">${e.map(e=>e.children?.length?q(e,t,n,r,i,!1,`<button type="button" class="session-toggle" aria-label="toggle subagents" onclick="event.stopPropagation(); const li = this.closest('.session-item'); li.classList.toggle('expanded'); this.textContent = li.classList.contains('expanded') ? '▾' : '▸'">${e.sessionId===n?`▾`:`▸`}</button>`,`<ul class="session-children">${e.children.map(e=>q(e,t,n,r,i,!0)).join(``)}</ul>`):q(e,t,n,r,i)).join(``)}</ul>`}function Mt(e){if(e)try{return JSON.parse(e)}catch{return}}function Nt(e){return{id:e.id,source:e.source,client:e.client,event:e.event,sessionId:e.sessionId,happenedAt:e.happenedAt,receivedAt:e.receivedAt,projectPath:e.projectPath,filePath:e.filePath,toolName:e.toolName,skillName:e.skillName,sourcePath:e.sourcePath,subagentId:e.subagentId,subagentType:e.subagentType,transcriptPath:e.transcriptPath,payload:Mt(e.payload??``)??e.payload}}function Pt(e=`Back`){return`<button type="button" class="detail-back" onclick="backToDashboard()" aria-label="back to dashboard"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg><span>${e}</span></button>`}function Ft(e){let t=new Map;for(let n of e){let e=n.subagentId??null;t.has(e)||t.set(e,[]),t.get(e).push(n)}let n=[];for(let[e,r]of t){let t=r.toSorted((e,t)=>t.id-e.id),i=t.findIndex(e=>e.event===`subagentStart`),a=i>=0?t.at(i):t.at(0),o=t.filter(e=>e!==a);n.push({subagentId:e,parent:a,children:o,maxId:t.at(0).id})}return n.toSorted((e,t)=>t.maxId-e.maxId)}function It(e){return`<span class="ts">${z(H(e.happenedAt??e.receivedAt))}</span><span class="source">${z(e.source??``)}</span><span class="event">${z(e.event??``)}</span><span class="tool">${z(e.toolName??``)}</span>`}function Lt(e){let t=z(H(e.happenedAt??e.receivedAt)),n=z(e.source??``),r=z(e.client??``),i=z(e.event??``),a=z(e.toolName??``),o=z(e.subagentId??``),s=z(e.subagentType??``),c=z(e.projectPath??``),l=z(e.filePath??``),u=z(e.skillName??``),d=o?`<div><span>Subagent</span><span>${s||`-`} (${o})</span></div>`:``,f=l?`<div><span>File</span><span>${l}</span></div>`:``,p=u?`<div><span>Skill</span><span>${u}</span></div>`:``;return`<div class="detail-attrs">
240
249
  <div><span>Time</span><span>${t}</span></div>
241
250
  <div><span>Source</span><span>${n}</span></div>
242
251
  <div><span>Client</span><span>${r||`-`}</span></div>
@@ -246,32 +255,32 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
246
255
  ${d}
247
256
  ${f}
248
257
  ${p}
249
- </div>`}function Ft(e){return`<div class="detail-json">
258
+ </div>`}function Rt(e){return`<div class="detail-json">
250
259
  <details class="json-toggle">
251
260
  <summary>JSON</summary>
252
- <pre>${z(JSON.stringify(jt(e),null,2))}</pre>
261
+ <pre>${z(JSON.stringify(Nt(e),null,2))}</pre>
253
262
  </details>
254
263
  <button type="button" class="json-copy" onclick="copyEventJson(this)" title="Copy JSON" aria-label="Copy JSON"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
255
- </div>`}function q(e,t){if(t.length===0)return``;let n=t.map(e=>`<span class="chip" title="${B(e)}">${z(V(e,40))}</span>`).join(``);return`<div class="summary-group"><span class="summary-label">${e} (${t.length})</span><div class="summary-chips">${n}</div></div>`}function It(e){if(!e)return``;let t=[q(`Tools`,e.tools),q(`Skills`,e.skills),q(`Files`,e.files)].join(``);return t?`<div class="detail-summary">${t}</div>`:``}function Lt(e){return`<details class="detail-event" data-event-id="${e.id}" open>
256
- <summary>${Nt(e)}</summary>
257
- <div class="detail-body">${Pt(e)}
258
- ${Ft(e)}
264
+ </div>`}function J(e,t){if(t.length===0)return``;let n=t.map(e=>`<span class="chip" title="${B(e)}">${z(V(e,40))}</span>`).join(``);return`<div class="summary-group"><span class="summary-label">${e} (${t.length})</span><div class="summary-chips">${n}</div></div>`}function zt(e){if(!e)return``;let t=[J(`Tools`,e.tools),J(`Skills`,e.skills),J(`Files`,e.files)].join(``);return t?`<div class="detail-summary">${t}</div>`:``}function Bt(e){return`<details class="detail-event" data-event-id="${e.id}" open>
265
+ <summary>${It(e)}</summary>
266
+ <div class="detail-body">${Lt(e)}
267
+ ${Rt(e)}
259
268
  </div>
260
- </details>`}function Rt(e,t){let n=e.parent,r=e.children.map(Lt).join(``),i=`<span class="subagent-badge">${z(n.subagentType||`subagent`)}${` · ${z(V(t,18))}`} · ${e.children.length}</span>`;return`<details class="detail-event detail-subagent" data-event-id="${n.id}" open>
261
- <summary>${Nt(n)}${i}</summary>
269
+ </details>`}function Vt(e,t){let n=e.parent,r=e.children.map(Bt).join(``),i=`<span class="subagent-badge">${z(n.subagentType||`subagent`)}${` · ${z(V(t,18))}`} · ${e.children.length}</span>`;return`<details class="detail-event detail-subagent" data-event-id="${n.id}" open>
270
+ <summary>${It(n)}${i}</summary>
262
271
  <div class="detail-body">
263
- ${Pt(n)}
264
- ${Ft(n)}
272
+ ${Lt(n)}
273
+ ${Rt(n)}
265
274
  <div class="detail-children">${r}</div>
266
275
  </div>
267
- </details>`}function zt(e){return Mt(e).map(e=>e.subagentId?Rt(e,e.subagentId):[e.parent,...e.children].map(Lt).join(``)).join(``)}function Bt(e,t,n,r,i,a){if(!e)return`<div class="detail-header"><h2 class="detail-title">Session Details</h2></div><div class="empty">No events.</div>`;let o=r?t.filter(e=>e.subagentId===r):t,s=r?`${e} · ${r}`:e;if(o.length===0)return`<div class="detail-header"><h2 class="detail-title">Session Details - ${z(s)}</h2></div><div class="empty">No events for this subagent.</div>`;let c=zt(o),l=JSON.stringify(o,null,2),u=n!==void 0&&n>o.length?`<div class="detail-truncated">Showing the ${o.length} most recent of ${n} events — older events are hidden and the JSON copy is partial.</div>`:``;return`<section class="session-detail-view" data-session="${B(e)}" data-subagent="${B(r??``)}">
276
+ </details>`}function Ht(e){return Ft(e).map(e=>e.subagentId?Vt(e,e.subagentId):[e.parent,...e.children].map(Bt).join(``)).join(``)}function Ut(e,t,n,r,i,a){if(!e)return`<div class="detail-header"><h2 class="detail-title">Session Details</h2></div><div class="empty">No events.</div>`;let o=r?t.filter(e=>e.subagentId===r):t,s=r?`${e} · ${r}`:e;if(o.length===0)return`<div class="detail-header"><h2 class="detail-title">Session Details - ${z(s)}</h2></div><div class="empty">No events for this subagent.</div>`;let c=Ht(o),l=JSON.stringify(o,null,2),u=n!==void 0&&n>o.length?`<div class="detail-truncated">Showing the ${o.length} most recent of ${n} events — older events are hidden and the JSON copy is partial.</div>`:``;return`<section class="session-detail-view" data-session="${B(e)}" data-subagent="${B(r??``)}">
268
277
  <div class="detail-header">
269
- <button type="button" class="detail-back" onclick="backToDashboard()" aria-label="back to dashboard"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg><span>Back</span></button>
278
+ ${Pt()}
270
279
  <h2 class="detail-title">Session Details - ${z(s)}</h2>
271
280
  <span class="detail-count">${n??o.length} events</span>
272
281
  </div>
273
- ${It(i)}
274
- ${a?Dt(a):``}
282
+ ${zt(i)}
283
+ ${a?kt(a):``}
275
284
  ${u}
276
285
  <div class="detail-toolbar">
277
286
  <input type="search" class="detail-search" placeholder="Search attributes..." oninput="filterSessionDetails(this.value)">
@@ -283,18 +292,21 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
283
292
  <button type="button" onclick="copySessionJson()">Copy JSON</button>
284
293
  </div>
285
294
  <pre id="session-json" class="session-json">${z(l)}</pre>
286
- </section>`}const Vt={bytes:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0},tokens:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0}};function Ht(e){if(!e)return;let t=Number(e);return Number.isFinite(t)&&t>=0?t:void 0}function Ut(e){if(!e)return;let t=Number(e);return Number.isInteger(t)&&t>=0?t:void 0}function Wt(e){return e===`7d`?{hours:168,groupBy:`day`}:e===`30d`?{hours:720,groupBy:`day`}:e===`all`?{hours:0,groupBy:`day`}:{hours:24,groupBy:`hour`}}function J(e){let t=e.searchParams.get(`since`),n=e.searchParams.get(`source`)||void 0,r=e.searchParams.get(`event`)||void 0,i=e.searchParams.get(`session`)||void 0,a=e.searchParams.get(`subagent`)||void 0,o=e.searchParams.get(`q`)||void 0,s=e.searchParams.get(`status`)||void 0,c=e.searchParams.get(`tool`)||void 0,l=e.searchParams.get(`skill`)||void 0,u=e.searchParams.get(`file`)||void 0,d=e.searchParams.get(`mdDir`)||void 0,f=(e.searchParams.get(`view`)||void 0)===`list`?`list`:void 0,p=e.searchParams.get(`minDuration`),m=e.searchParams.get(`maxDuration`),h=e.searchParams.get(`range`)||`24h`,g=[`24h`,`7d`,`30d`,`all`].includes(h)?h:`24h`,_=e.searchParams.get(`limit`),v=e.searchParams.get(`offset`),y;if(t){let e=parseInt(t,10);Number.isNaN(e)||(y=e)}return{since:y,source:n,event:r,sessionId:i,subagentId:a,q:o,status:s===`active`||s===`completed`||s===`failed`?s:void 0,tool:c,skill:l,file:u,mdDir:d,view:f,minDuration:Ht(p),maxDuration:Ht(m),range:g,limit:Ut(_),offset:Ut(v)}}function Gt(e,t){let n=e.limit&&e.limit>0?e.limit:25,r=e.offset??0;if(t<=n)return``;let i=Math.floor(r/n)+1,a=Math.ceil(t/n),o=r-n,s=r+n,c=U(e,{offset:Math.max(o,0),limit:n}),l=U(e,{offset:s,limit:n});return`<div class="pager">${o>=0?`<a href="${c}" hx-get="${c}" hx-target="#dashboard-content" hx-swap="innerHTML">Previous</a>`:`<button type="button" disabled>Previous</button>`}<span class="pager-info">Page ${i} of ${a}</span>${s<t?`<a href="${l}" hx-get="${l}" hx-target="#dashboard-content" hx-swap="innerHTML">Next</a>`:`<button type="button" disabled>Next</button>`}</div>`}function Kt(e){if(e.skill)return{kind:`skill`,value:e.skill};if(e.file)return{kind:`file`,value:e.file};if(e.tool)return{kind:`tool`,value:e.tool}}function qt(e,t,n){let r=j(e,{...t,subagentId:void 0,limit:void 0,offset:void 0},n),i=t.limit&&t.limit>0?t.limit:25,a=t.offset??0,o=r.slice(a,a+i),s=Kt(t),c=U(t,{tool:void 0,skill:void 0,file:void 0,offset:0}),l=s?`<div class="sessions-list-filter">Sessions using ${z(s.kind)}: <strong>${z(s.value)}</strong> <a href="${c}" hx-get="${c}" hx-target="#dashboard-content" hx-swap="innerHTML">Clear</a></div>`:``;return`<div class="main-content sessions-list-view">
287
- <h2 class="session-list-title">Recent Sessions <span class="session-collapse-count">${r.length}</span></h2>
295
+ </section>`}const Wt={bytes:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0},tokens:{mcpServers:0,mdFiles:0,bloatware:0,actualValue:0}};function Gt(e){if(!e)return;let t=Number(e);return Number.isFinite(t)&&t>=0?t:void 0}function Kt(e){if(!e)return;let t=Number(e);return Number.isInteger(t)&&t>=0?t:void 0}function qt(e){return e===`7d`?{hours:168,groupBy:`day`}:e===`30d`?{hours:720,groupBy:`day`}:e===`all`?{hours:0,groupBy:`day`}:{hours:24,groupBy:`hour`}}function Y(e){let t=e.searchParams.get(`since`),n=e.searchParams.get(`source`)||void 0,r=e.searchParams.get(`event`)||void 0,i=e.searchParams.get(`session`)||void 0,a=e.searchParams.get(`subagent`)||void 0,o=e.searchParams.get(`q`)||void 0,s=e.searchParams.get(`status`)||void 0,c=e.searchParams.get(`tool`)||void 0,l=e.searchParams.get(`skill`)||void 0,u=e.searchParams.get(`file`)||void 0,d=e.searchParams.get(`mdDir`)||void 0,f=e.searchParams.getAll(`dirs`).filter(e=>e!==``),p=e.searchParams.getAll(`skills`).filter(e=>e!==``),m=e.searchParams.getAll(`mcp`).filter(e=>e!==``),h=(e.searchParams.get(`view`)||void 0)===`list`?`list`:void 0,g=e.searchParams.get(`minDuration`),_=e.searchParams.get(`maxDuration`),v=e.searchParams.get(`range`)||`24h`,y=[`24h`,`7d`,`30d`,`all`].includes(v)?v:`24h`,b=e.searchParams.get(`limit`),x=e.searchParams.get(`offset`),S;if(t){let e=parseInt(t,10);Number.isNaN(e)||(S=e)}return{since:S,source:n,event:r,sessionId:i,subagentId:a,q:o,status:s===`active`||s===`completed`||s===`failed`?s:void 0,tool:c,skill:l,file:u,mdDir:d,view:h,projectPaths:f.length>0?f:void 0,skills:p.length>0?p:void 0,mcpServers:m.length>0?m:void 0,minDuration:Gt(g),maxDuration:Gt(_),range:y,limit:Kt(b),offset:Kt(x)}}function Jt(e,t){let n=e.limit&&e.limit>0?e.limit:25,r=e.offset??0;if(t<=n)return``;let i=Math.floor(r/n)+1,a=Math.ceil(t/n),o=r-n,s=r+n,c=W(e,{offset:Math.max(o,0),limit:n}),l=W(e,{offset:s,limit:n});return`<div class="pager">${o>=0?`<a href="${c}" hx-get="${c}" hx-target="#dashboard-content" hx-swap="innerHTML">Previous</a>`:`<button type="button" disabled>Previous</button>`}<span class="pager-info">Page ${i} of ${a}</span>${s<t?`<a href="${l}" hx-get="${l}" hx-target="#dashboard-content" hx-swap="innerHTML">Next</a>`:`<button type="button" disabled>Next</button>`}</div>`}function Yt(e){if(e.skill)return{kind:`skill`,value:e.skill};if(e.file)return{kind:`file`,value:e.file};if(e.tool)return{kind:`tool`,value:e.tool}}function Xt(e,t,n){let r=j(e,{...t,subagentId:void 0,limit:void 0,offset:void 0},n),i=t.limit&&t.limit>0?t.limit:25,a=t.offset??0,o=r.slice(a,a+i),s=Yt(t),c=W(t,{tool:void 0,skill:void 0,file:void 0,offset:0}),l=s?`<div class="sessions-list-filter">Sessions using ${z(s.kind)}: <strong>${z(s.value)}</strong> <a href="${c}" hx-get="${c}" hx-target="#dashboard-content" hx-swap="innerHTML">Clear</a></div>`:``;return`<div class="main-content sessions-list-view">
296
+ <div class="detail-header">
297
+ ${Pt()}
298
+ <h2 class="session-list-title">Sessions <span class="session-collapse-count">${r.length}</span></h2>
299
+ </div>
288
300
  ${l}
289
301
  <div class="session-list-wrapper">
290
- ${kt(o,n,void 0,void 0,t)}
291
- ${Gt(t,r.length)}
302
+ ${jt(o,n,void 0,void 0,t)}
303
+ ${Jt(t,r.length)}
292
304
  </div>
293
- </div>`}function Jt(e,t){let n=Date.now();if(t.view===`list`)return qt(e,t,n);let{hours:r,groupBy:i}=Wt(t.range),a=j(e,{...t,subagentId:void 0,limit:void 0,offset:void 0},n),o=a.map(e=>e.sessionId),s=[],c=[],l=[],u=[],d=Vt;if(o.length>0){let a={...t,subagentId:void 0,sessionIds:o};s=Re(e,a,r,i,n),c=ke(e,a,10,n),l=Ae(e,a,10,n),u=je(e,a,10,n),d=Pe(e,a,n)}let f=a.length,p=a.reduce((e,t)=>e+t.eventCount,0),m=f>0?a.reduce((e,t)=>e+t.durationMs,0)/f:0,h=a.filter(e=>e.failureCount===0).length;return`<div class="main-content">
294
- <div class="main-metrics">${_t({totalSessions:f,totalEvents:p,averageDurationMs:m,successRate:f>0?h/f*100:0})}</div>
295
- <div class="top-charts">${yt(s,i)}${xt(c,t)}${St(l,t)}${Ct(u,t)}</div>
296
- ${Dt(d)}
297
- </div>`}function Yt(e,t,n,r){let i=Jt(r,J(n));t.writeHead(200,{"Content-Type":`text/html; charset=utf-8`}),t.end(i)}function Xt(e,t){let n=Date.now();if(!t.sessionId)return`<div class="main-content"><div class="detail-area"><div class="empty">No events.</div></div></div>`;let r=j(e,{...t,sessionId:void 0,subagentId:void 0,limit:void 0,offset:void 0},n),i=k(e,{...t,range:void 0,sessionId:t.sessionId,sessionIdExact:!0,subagentId:t.subagentId,limit:1e3,offset:0}),a=A(e,{...t,range:void 0,sessionId:t.sessionId,sessionIdExact:!0,subagentId:t.subagentId}),o=Pe(e,{...t,range:void 0,sessionId:t.sessionId,sessionIdExact:!0,subagentId:t.subagentId}),s=r.find(e=>e.sessionId===t.sessionId),c=t.subagentId?s?.children?.find(e=>e.subagentId===t.subagentId):s;return`<div class="main-content">${Bt(t.sessionId,i,a,t.subagentId,c,o)}</div>`}function Zt(e,t,n,r){let i=Xt(r,J(n));t.writeHead(200,{"Content-Type":`text/html; charset=utf-8`}),t.end(i)}function Qt(e,t,n,r){let i=k(r,J(n));t.writeHead(200,{"Content-Type":`application/json`}),t.end(JSON.stringify(i))}const $t=[{value:`24h`,label:`Last 24h`},{value:`7d`,label:`Last 7 days`},{value:`30d`,label:`Last 30 days`},{value:`all`,label:`All time`}];function en(e){let t=z(e.q??``),n=e.range??`24h`;return`<header class="app-header">
305
+ </div>`}function Zt(e,t){let n=Date.now();if(t.view===`list`)return Xt(e,t,n);let{hours:r,groupBy:i}=qt(t.range),a=j(e,{...t,subagentId:void 0,limit:void 0,offset:void 0},n),o=a.map(e=>e.sessionId),s=[],c=[],l=[],u=[],d=Wt;if(o.length>0){let a={...t,subagentId:void 0,sessionIds:o};s=Be(e,a,r,i,n),c=je(e,a,10,n),l=Me(e,a,10,n),u=Ne(e,a,10,n),d=Ie(e,a,n)}let f=a.length,p=a.reduce((e,t)=>e+t.eventCount,0),m=f>0?a.reduce((e,t)=>e+t.durationMs,0)/f:0,h=a.filter(e=>e.failureCount===0).length;return`<div class="main-content">
306
+ <div class="main-metrics">${yt({totalSessions:f,totalEvents:p,averageDurationMs:m,successRate:f>0?h/f*100:0})}</div>
307
+ <div class="top-charts">${xt(s,i)}${Ct(c,t)}${wt(l,t)}${Tt(u,t)}</div>
308
+ ${kt(d)}
309
+ </div>`}function Qt(e){return e.headers[`hx-trigger`]===`dashboard-content`}function $t(e,t){let n={"Content-Type":`text/html; charset=utf-8`};return Qt(e)||(n[`HX-Push-Url`]=`/${t.search}`),n}function en(e,t,n,r){let i=Zt(r,Y(n));t.writeHead(200,$t(e,n)),t.end(i)}function tn(e,t){let n=Date.now();if(!t.sessionId)return`<div class="main-content"><div class="detail-area"><div class="empty">No events.</div></div></div>`;let r=j(e,{...t,sessionId:void 0,subagentId:void 0,limit:void 0,offset:void 0},n),i=De(e,{...t,range:void 0,sessionId:t.sessionId,sessionIdExact:!0,subagentId:t.subagentId,limit:1e3,offset:0}),a=A(e,{...t,range:void 0,sessionId:t.sessionId,sessionIdExact:!0,subagentId:t.subagentId}),o=Ie(e,{...t,range:void 0,sessionId:t.sessionId,sessionIdExact:!0,subagentId:t.subagentId}),s=r.find(e=>e.sessionId===t.sessionId),c=t.subagentId?s?.children?.find(e=>e.subagentId===t.subagentId):s;return`<div class="main-content">${Ut(t.sessionId,i,a,t.subagentId,c,o)}</div>`}function nn(e,t,n,r){let i=tn(r,Y(n));t.writeHead(200,$t(e,n)),t.end(i)}function rn(e,t,n,r){let i=De(r,Y(n));t.writeHead(200,{"Content-Type":`application/json`}),t.end(JSON.stringify(i))}const an=[{value:`24h`,label:`Last 24h`},{value:`7d`,label:`Last 7 days`},{value:`30d`,label:`Last 30 days`},{value:`all`,label:`All time`}];function on(e){let t=z(e.q??``),n=e.range??`24h`;return`<header class="app-header">
298
310
  <div class="brand"><svg class="brand-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="4"/><path d="M8 7v10"/><path d="M16 7v10"/><path d="M8 12h8"/></svg> happenin</div>
299
311
  <h1 class="page-title">Session Overview Analytics</h1>
300
312
  <div class="header-controls">
@@ -302,18 +314,32 @@ ${Dt(d)}
302
314
  <svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
303
315
  <input type="text" name="q" class="search" placeholder="session or payload" value="${t}" autocomplete="off">
304
316
  </div>
305
- <label>range <select name="range">${$t.map(e=>`<option value="${B(e.value)}"${e.value===n?` selected`:``}>${z(e.label)}</option>`).join(``)}</select></label>
317
+ <label>range <select name="range">${an.map(e=>`<option value="${B(e.value)}"${e.value===n?` selected`:``}>${z(e.label)}</option>`).join(``)}</select></label>
306
318
  <button type="button" class="theme" data-theme-toggle title="Toggle light/dark theme" aria-label="toggle theme">🌙</button>
307
319
  </div>
308
- </header>`}const tn=[`active`,`completed`,`failed`];function Y(e,t,n,r=`all`){let i=n??``,a=B(i);return`<select name="${e}"><option value=""${i===``?` selected`:``}>${r}</option>${t.map(e=>{let t=B(e);return`<option value="${t}"${t===a?` selected`:``}>${z(e)}</option>`}).join(``)}</select>`}function nn(e,t,n){let r=pt(t),i=n??``,a=B(i),o=t.map(e=>{let t=B(e);return`<option value="${t}"${t===a?` selected`:``}>${z(e.slice(r.length))}</option>`}).join(``);return`<select name="${e}"><option value=""${i===``?` selected`:``}>all directories</option>${o}</select>`}function rn(e,t){let n=t.status,r=t.source,i=t.tool,a=t.event,o=t.minDuration===void 0?``:String(t.minDuration),s=t.maxDuration===void 0?``:String(t.maxDuration),c=t.mdDir;return`<div class="filter-bar">
309
- <label>status ${Y(`status`,tn,n)}</label>
310
- <label>source ${Y(`source`,e.sources,r)}</label>
311
- <label>tool ${Y(`tool`,e.tools,i)}</label>
312
- <label>event ${Y(`event`,e.events,a)}</label>
313
- <label>md files directory ${nn(`mdDir`,e.directories,c)}</label>
314
- <label>min duration (m) <input type="number" name="minDuration" min="0" step="any" placeholder="min" value="${z(o)}"></label>
315
- <label>max duration (m) <input type="number" name="maxDuration" min="0" step="any" placeholder="max" value="${z(s)}"></label>
316
- </div>`}function an(e,t){let n=Ue(e),r=Jt(e,t);return`<!DOCTYPE html>
320
+ </header>`}const sn=[`active`,`completed`,`failed`];function X(e,t,n,r=`all`){let i=n??``,a=B(i);return`<select name="${e}"><option value=""${i===``?` selected`:``}>${r}</option>${t.map(e=>{let t=B(e);return`<option value="${t}"${t===a?` selected`:``}>${z(e)}</option>`}).join(``)}</select>`}function cn(e,t,n){let r=ht(t),i=n??``,a=B(i),o=t.map(e=>{let t=B(e);return`<option value="${t}"${t===a?` selected`:``}>${z(e.slice(r.length))}</option>`}).join(``);return`<select name="${e}"><option value=""${i===``?` selected`:``}>all directories</option>${o}</select>`}function ln(e,t,n,r){let i=new Set(n),a=t.map(({value:e,label:t})=>{let n=B(e),r=i.has(e)?` selected`:``;return`<option value="${n}" data-label="${B(t)}"${r}>${z(t)}</option>`}).join(``);return`<div class="ms" data-ms-all="${B(r)}">
321
+ <select name="${e}" multiple class="ms-native">${a}</select>
322
+ </div>`}function un(e,t,n){let r=ht(t);return ln(e,t.map(e=>({value:e,label:e.slice(r.length)})),n,`All directories`)}function dn(e,t,n,r){return ln(e,t.map(e=>({value:e,label:e})),n,r)}function fn(e,t){let n=t.status,r=t.source,i=t.tool,a=t.event,o=t.minDuration===void 0?``:String(t.minDuration),s=t.maxDuration===void 0?``:String(t.maxDuration),c=t.mdDir,l=t.projectPaths??[],u=t.skills??[],d=t.mcpServers??[],f=t.view===`list`;return`<div class="toolbar-row">
323
+ <button type="button" id="filters-toggle" class="toggle-pill" aria-expanded="false" aria-controls="filter-panel" onclick="toggleFilters()">
324
+ <svg class="toggle-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg><span>Filters</span><span class="toggle-badge" id="filters-badge" hidden></span>
325
+ </button>
326
+ <div class="view-tabs" role="tablist">
327
+ <button type="button" id="tab-overview" class="view-tab" role="tab" aria-selected="${f?`false`:`true`}" onclick="switchView('overview')">Overview</button>
328
+ <button type="button" id="tab-sessions" class="view-tab" role="tab" aria-selected="${f?`true`:`false`}" onclick="switchView('list')">Sessions<span class="toggle-badge" id="sessions-tab-count" hidden></span></button>
329
+ </div>
330
+ </div>
331
+ <div class="filter-panel" id="filter-panel">
332
+ <div class="field"><label>Status</label>${X(`status`,sn,n)}</div>
333
+ <div class="field"><label>Source</label>${X(`source`,e.sources,r)}</div>
334
+ <div class="field"><label>Tool</label>${X(`tool`,e.tools,i)}</div>
335
+ <div class="field"><label>Event</label>${X(`event`,e.events,a)}</div>
336
+ <div class="field field-wide"><label>Directories</label>${un(`dirs`,e.directories,l)}</div>
337
+ <div class="field field-wide"><label>Skills</label>${dn(`skills`,e.skills,u,`All skills`)}</div>
338
+ <div class="field field-wide"><label>Integrations (MCP)</label>${dn(`mcp`,e.mcpServers,d,`All integrations`)}</div>
339
+ <div class="field"><label>Md files directory</label>${cn(`mdDir`,e.directories,c)}</div>
340
+ <div class="field"><label>Min duration (m)</label><input type="number" name="minDuration" min="0" step="any" placeholder="min" value="${z(o)}"></div>
341
+ <div class="field"><label>Max duration (m)</label><input type="number" name="maxDuration" min="0" step="any" placeholder="max" value="${z(s)}"></div>
342
+ </div>`}function pn(e,t){let n=Ge(e),r=t.sessionId?tn(e,t):Zt(e,t),i=on(t),a=fn(n,t);return`<!DOCTYPE html>
317
343
  <html lang="en" hx-ext="sse">
318
344
  <head>
319
345
  <meta charset="utf-8">
@@ -411,7 +437,14 @@ ${Dt(d)}
411
437
  if (!form) return false;
412
438
  for (const [name, value] of Object.entries(values)) {
413
439
  const field = form.elements[name];
414
- if (field && value != null && value !== '') {
440
+ if (!field) continue;
441
+ if (Array.isArray(value)) {
442
+ if (field.multiple && field.options) {
443
+ for (const option of field.options) {
444
+ option.selected = value.includes(option.value);
445
+ }
446
+ }
447
+ } else if (value != null && value !== '') {
415
448
  field.value = String(value);
416
449
  }
417
450
  }
@@ -420,23 +453,270 @@ ${Dt(d)}
420
453
  return false;
421
454
  }
422
455
 
456
+ function buildMultiSelect(wrapper) {
457
+ const select = wrapper.querySelector('select.ms-native');
458
+ if (!select) return;
459
+ const allLabel = wrapper.getAttribute('data-ms-all') || 'All';
460
+ const noun = allLabel.replace(/^All /i, '').toLowerCase();
461
+
462
+ const pills = document.createElement('div');
463
+ pills.className = 'ms-pills';
464
+ const searchWrap = document.createElement('div');
465
+ searchWrap.className = 'ms-search-wrap';
466
+ const input = document.createElement('input');
467
+ input.type = 'text';
468
+ input.className = 'ms-search';
469
+ input.placeholder = 'Search ' + noun + '...';
470
+ input.autocomplete = 'off';
471
+ const dropdown = document.createElement('div');
472
+ dropdown.className = 'ms-dropdown';
473
+ dropdown.hidden = true;
474
+ searchWrap.appendChild(input);
475
+ searchWrap.appendChild(dropdown);
476
+ wrapper.insertBefore(pills, select);
477
+ wrapper.insertBefore(searchWrap, select);
478
+ wrapper.classList.add('enhanced');
479
+
480
+ function optionsList() {
481
+ return Array.from(select.options);
482
+ }
483
+
484
+ function renderPills() {
485
+ pills.innerHTML = '';
486
+ const selected = optionsList().filter((o) => o.selected);
487
+ if (selected.length === 0) {
488
+ const span = document.createElement('span');
489
+ span.className = 'ms-pill ms-pill--all';
490
+ span.textContent = allLabel;
491
+ pills.appendChild(span);
492
+ return;
493
+ }
494
+ for (const opt of selected) {
495
+ const label = opt.dataset.label || opt.textContent;
496
+ const pill = document.createElement('span');
497
+ pill.className = 'ms-pill';
498
+ const text = document.createElement('span');
499
+ text.textContent = label;
500
+ text.title = label;
501
+ const remove = document.createElement('button');
502
+ remove.type = 'button';
503
+ remove.setAttribute('aria-label', 'Remove ' + label);
504
+ remove.textContent = String.fromCharCode(215);
505
+ remove.addEventListener('click', (e) => {
506
+ e.stopPropagation();
507
+ opt.selected = false;
508
+ select.dispatchEvent(new Event('change', { bubbles: true }));
509
+ renderPills();
510
+ renderDropdown();
511
+ });
512
+ pill.appendChild(text);
513
+ pill.appendChild(remove);
514
+ pills.appendChild(pill);
515
+ }
516
+ }
517
+
518
+ function renderDropdown() {
519
+ const term = input.value.trim().toLowerCase();
520
+ const unselected = optionsList().filter((o) => !o.selected);
521
+ const matches = term
522
+ ? unselected.filter((o) => (o.dataset.label || o.textContent).toLowerCase().includes(term))
523
+ : unselected;
524
+ dropdown.innerHTML = '';
525
+ if (matches.length === 0) {
526
+ const empty = document.createElement('div');
527
+ empty.className = 'ms-empty';
528
+ empty.textContent = unselected.length === 0 ? 'All options selected' : 'No matches';
529
+ dropdown.appendChild(empty);
530
+ return;
531
+ }
532
+ for (const opt of matches.slice(0, 50)) {
533
+ const item = document.createElement('div');
534
+ item.className = 'ms-option';
535
+ item.textContent = opt.dataset.label || opt.textContent;
536
+ item.addEventListener('mousedown', (e) => {
537
+ e.preventDefault();
538
+ opt.selected = true;
539
+ select.dispatchEvent(new Event('change', { bubbles: true }));
540
+ input.value = '';
541
+ renderPills();
542
+ renderDropdown();
543
+ });
544
+ dropdown.appendChild(item);
545
+ }
546
+ }
547
+
548
+ input.addEventListener('focus', () => {
549
+ renderDropdown();
550
+ dropdown.hidden = false;
551
+ });
552
+ input.addEventListener('input', () => {
553
+ renderDropdown();
554
+ dropdown.hidden = false;
555
+ });
556
+ input.addEventListener('blur', () => {
557
+ setTimeout(() => {
558
+ dropdown.hidden = true;
559
+ }, 150);
560
+ });
561
+ input.addEventListener('keydown', (e) => {
562
+ if (e.key === 'Escape') {
563
+ dropdown.hidden = true;
564
+ input.blur();
565
+ }
566
+ if (e.key === 'Enter') {
567
+ e.preventDefault();
568
+ const first = dropdown.querySelector('.ms-option');
569
+ if (first) first.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
570
+ }
571
+ });
572
+
573
+ select.msRefresh = () => {
574
+ renderPills();
575
+ renderDropdown();
576
+ };
577
+ renderPills();
578
+ renderDropdown();
579
+ }
580
+
581
+ function initMultiSelects() {
582
+ for (const wrapper of document.querySelectorAll('.ms')) {
583
+ buildMultiSelect(wrapper);
584
+ }
585
+ }
586
+
587
+ function refreshMultiSelects() {
588
+ for (const select of document.querySelectorAll('select.ms-native')) {
589
+ if (select.msRefresh) select.msRefresh();
590
+ }
591
+ }
592
+
593
+ function getViewField() {
594
+ const form = document.querySelector('[data-filter-form]');
595
+ return form ? form.elements['view'] : null;
596
+ }
597
+
598
+ function getActiveView() {
599
+ const field = getViewField();
600
+ return field && field.value === 'list' ? 'list' : 'overview';
601
+ }
602
+
603
+ function updateViewTabs() {
604
+ const view = getActiveView();
605
+ const overviewTab = document.getElementById('tab-overview');
606
+ const sessionsTab = document.getElementById('tab-sessions');
607
+ if (overviewTab) overviewTab.setAttribute('aria-selected', String(view === 'overview'));
608
+ if (sessionsTab) sessionsTab.setAttribute('aria-selected', String(view === 'list'));
609
+ }
610
+
611
+ function requestSessionsFragment() {
612
+ const form = document.querySelector('[data-filter-form]');
613
+ const content = document.getElementById('dashboard-content');
614
+ if (!form || !content || typeof htmx === 'undefined') return;
615
+ const params = new URLSearchParams();
616
+ for (const [key, value] of collectFormEntries(form)) {
617
+ params.append(key, value);
618
+ }
619
+ const url = '/fragments/sessions' + (params.size > 0 ? '?' + params.toString() : '');
620
+ content.setAttribute('hx-get', url);
621
+ htmx.ajax('GET', url, { target: content, swap: 'innerHTML' });
622
+ }
623
+
624
+ function switchView(view) {
625
+ const form = document.querySelector('[data-filter-form]');
626
+ const field = getViewField();
627
+ if (field) field.value = view === 'list' ? 'list' : '';
628
+ updateViewTabs();
629
+ if (form) {
630
+ saveFilters(form);
631
+ updateFiltersBadge();
632
+ }
633
+ requestSessionsFragment();
634
+ }
635
+
636
+ function syncViewCounts() {
637
+ const content = document.getElementById('dashboard-content');
638
+ const badge = document.getElementById('sessions-tab-count');
639
+ if (!content || !badge) return;
640
+ const countEl =
641
+ content.querySelector('.metric-sessions .metric-value') ||
642
+ content.querySelector('.session-collapse-count');
643
+ if (countEl) {
644
+ badge.textContent = countEl.textContent;
645
+ badge.hidden = false;
646
+ } else {
647
+ badge.hidden = true;
648
+ }
649
+ }
650
+
651
+ function countActiveFilters() {
652
+ const panel = document.getElementById('filter-panel');
653
+ if (!panel) return 0;
654
+ let count = 0;
655
+ for (const el of panel.querySelectorAll('select, input')) {
656
+ if (el.multiple) count += el.selectedOptions.length;
657
+ else if (el.value) count += 1;
658
+ }
659
+ return count;
660
+ }
661
+
662
+ function updateFiltersBadge() {
663
+ const badge = document.getElementById('filters-badge');
664
+ if (!badge) return;
665
+ const count = countActiveFilters();
666
+ badge.textContent = String(count);
667
+ badge.hidden = count === 0;
668
+ }
669
+
670
+ const FILTERS_OPEN_KEY = 'happenin-filters-open';
671
+
672
+ function toggleFilters() {
673
+ const btn = document.getElementById('filters-toggle');
674
+ const panel = document.getElementById('filter-panel');
675
+ if (!btn || !panel) return;
676
+ const next = btn.getAttribute('aria-expanded') !== 'true';
677
+ btn.setAttribute('aria-expanded', String(next));
678
+ panel.classList.toggle('open', next);
679
+ setItem(FILTERS_OPEN_KEY, next ? '1' : '');
680
+ }
681
+
682
+ function initFilterPanel() {
683
+ const btn = document.getElementById('filters-toggle');
684
+ const panel = document.getElementById('filter-panel');
685
+ if (!btn || !panel) return;
686
+ const open = getItem(FILTERS_OPEN_KEY) === '1';
687
+ btn.setAttribute('aria-expanded', String(open));
688
+ panel.classList.toggle('open', open);
689
+ updateFiltersBadge();
690
+ }
691
+
692
+ function collectFormEntries(form) {
693
+ const data = new FormData(form);
694
+ const entries = [];
695
+ for (const [key, value] of data.entries()) {
696
+ if (value) entries.push([key, String(value)]);
697
+ }
698
+ return entries;
699
+ }
700
+
423
701
  function updateContentUrl() {
424
702
  const form = document.querySelector('[data-filter-form]');
425
703
  const content = document.getElementById('dashboard-content');
426
704
  if (!form || !content) return;
427
- const data = new FormData(form);
428
705
  const params = new URLSearchParams();
429
- for (const [key, value] of data.entries()) {
430
- if (value) params.set(key, String(value));
706
+ for (const [key, value] of collectFormEntries(form)) {
707
+ params.append(key, value);
431
708
  }
432
709
  content.setAttribute('hx-get', '/fragments/sessions' + (params.size > 0 ? '?' + params.toString() : ''));
433
710
  }
434
711
 
435
712
  function saveFilters(form) {
436
- const data = new FormData(form);
437
713
  const filters = {};
438
- for (const [key, value] of data.entries()) {
439
- if (value) filters[key] = String(value);
714
+ for (const [key, value] of collectFormEntries(form)) {
715
+ if (key in filters) {
716
+ filters[key] = Array.isArray(filters[key]) ? [...filters[key], value] : [filters[key], value];
717
+ } else {
718
+ filters[key] = value;
719
+ }
440
720
  }
441
721
  setItem(FILTER_KEY, JSON.stringify(filters));
442
722
  }
@@ -445,15 +725,23 @@ ${Dt(d)}
445
725
  const form = document.querySelector('[data-filter-form]');
446
726
  const content = document.getElementById('dashboard-content');
447
727
  if (!form || !content) return;
448
- const hadSaved = loadFilters();
449
- form.addEventListener('change', () => saveFilters(form));
728
+ initMultiSelects();
729
+ const hadSaved = window.location.search ? false : loadFilters();
730
+ refreshMultiSelects();
731
+ form.addEventListener('change', () => {
732
+ saveFilters(form);
733
+ updateFiltersBadge();
734
+ updateViewTabs();
735
+ refreshMultiSelects();
736
+ });
450
737
  form.addEventListener('input', (event) => {
451
738
  if (event.target.classList.contains('search')) saveFilters(form);
452
739
  });
453
740
  updateContentUrl();
741
+ initFilterPanel();
454
742
  if (hadSaved) {
455
743
  setTimeout(() => {
456
- if (typeof htmx !== 'undefined') htmx.trigger(form, 'change');
744
+ requestSessionsFragment();
457
745
  }, 0);
458
746
  }
459
747
  }
@@ -488,8 +776,7 @@ ${Dt(d)}
488
776
  }
489
777
 
490
778
  function backToDashboard() {
491
- const form = document.querySelector('[data-filter-form]');
492
- if (form && typeof htmx !== 'undefined') htmx.trigger(form, 'change');
779
+ requestSessionsFragment();
493
780
  }
494
781
 
495
782
  function syncDetailState() {
@@ -499,34 +786,13 @@ ${Dt(d)}
499
786
  document.body.classList.toggle('session-detail-open', !!detail);
500
787
  }
501
788
 
502
- function syncSessionsToggle() {
789
+ function syncSessionsListState() {
503
790
  const content = document.getElementById('dashboard-content');
504
- const btn = document.getElementById('sessions-toggle');
505
- if (!content || !btn) return;
791
+ if (!content) return;
506
792
  const isList = !!content.querySelector('.sessions-list-view');
507
- btn.setAttribute('aria-expanded', isList ? 'true' : 'false');
508
793
  document.body.classList.toggle('sessions-list-open', isList);
509
794
  }
510
795
 
511
- function toggleSessionsList() {
512
- const btn = document.getElementById('sessions-toggle');
513
- const form = document.querySelector('[data-filter-form]');
514
- if (!btn) return;
515
- const isOpen = btn.getAttribute('aria-expanded') === 'true';
516
- const params = new URLSearchParams();
517
- if (form) {
518
- const data = new FormData(form);
519
- for (const [key, value] of data.entries()) {
520
- if (value) params.set(key, String(value));
521
- }
522
- }
523
- if (!isOpen) params.set('view', 'list');
524
- const url = '/fragments/sessions' + (params.size > 0 ? '?' + params.toString() : '');
525
- if (typeof htmx !== 'undefined') {
526
- htmx.ajax('GET', url, { target: '#dashboard-content', swap: 'innerHTML', source: btn });
527
- }
528
- }
529
-
530
796
  document.addEventListener('htmx:beforeSwap', (e) => {
531
797
  const elt = e.detail.requestConfig ? e.detail.requestConfig.elt : null;
532
798
  const blocked =
@@ -542,22 +808,34 @@ ${Dt(d)}
542
808
  window.copySessionJson = copySessionJson;
543
809
  window.copyEventJson = copyEventJson;
544
810
  window.backToDashboard = backToDashboard;
545
- window.toggleSessionsList = toggleSessionsList;
811
+ window.switchView = switchView;
812
+ window.toggleFilters = toggleFilters;
546
813
 
547
814
  document.addEventListener('htmx:afterSettle', () => {
548
815
  updateContentUrl();
549
816
  syncDetailState();
550
- syncSessionsToggle();
817
+ syncSessionsListState();
818
+ updateViewTabs();
819
+ syncViewCounts();
551
820
  });
552
821
 
822
+ function initHistoryState() {
823
+ syncDetailState();
824
+ syncSessionsListState();
825
+ updateViewTabs();
826
+ syncViewCounts();
827
+ }
828
+
553
829
  applyTheme(getTheme());
554
830
 
555
831
  if (document.readyState === 'loading') {
556
832
  document.addEventListener('DOMContentLoaded', initTheme);
557
833
  document.addEventListener('DOMContentLoaded', initFilters);
834
+ document.addEventListener('DOMContentLoaded', initHistoryState);
558
835
  } else {
559
836
  initTheme();
560
837
  initFilters();
838
+ initHistoryState();
561
839
  }
562
840
  })();
563
841
  <\/script>
@@ -581,6 +859,8 @@ ${Dt(d)}
581
859
  --success: #22c55e;
582
860
  --warning: #eab308;
583
861
  --danger: #ef4444;
862
+ --shadow: 0 1px 2px rgba(0,0,0,0.3), 0 4px 12px rgba(0,0,0,0.22);
863
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.28);
584
864
  }
585
865
  [data-theme="light"] {
586
866
  color-scheme: light;
@@ -601,39 +881,77 @@ ${Dt(d)}
601
881
  --success: #16a34a;
602
882
  --warning: #ca8a04;
603
883
  --danger: #dc2626;
884
+ --shadow: 0 1px 2px rgba(15,23,42,0.04), 0 4px 12px rgba(15,23,42,0.06);
885
+ --shadow-sm: 0 1px 2px rgba(15,23,42,0.05);
604
886
  }
605
887
  * { box-sizing: border-box; }
606
888
  html, body { min-height: 100%; }
607
889
  body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 0; line-height: 1.5; }
608
- .app { display: flex; flex-direction: column; min-height: 100%; max-width: 1400px; margin: 0 auto; padding: 1.5rem; gap: 1.25rem; }
609
- .sticky-bar { flex: 0 0 auto; display: flex; flex-direction: column; gap: 1rem; background: var(--bg); position: sticky; top: 0; z-index: 10; }
610
- .app-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; padding: 0.75rem 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; }
611
- .brand { font-weight: 800; font-size: 1.25rem; display: flex; align-items: center; gap: 0.5rem; color: var(--text); }
890
+ .app { display: flex; flex-direction: column; min-height: 100%; max-width: 1400px; margin: 0 auto; padding: 1.75rem; gap: 1.5rem; }
891
+ .sticky-bar { flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.85rem; background: var(--bg); position: sticky; top: 0; z-index: 10; padding-bottom: 0.25rem; }
892
+ .app-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; padding: 0.85rem 1.1rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.85rem; box-shadow: var(--shadow-sm); }
893
+ .brand { font-weight: 800; font-size: 1.3rem; display: flex; align-items: center; gap: 0.5rem; color: var(--text); background: linear-gradient(135deg, var(--text), var(--text) 40%, var(--accent)); -webkit-background-clip: text; background-clip: text; }
612
894
  .brand-icon { color: var(--accent); }
613
- .page-title { font-size: 1.1rem; font-weight: 600; margin: 0; color: var(--text); }
895
+ .page-title { font-size: 1.05rem; font-weight: 600; margin: 0; color: var(--text-2); }
614
896
  .header-controls { display: flex; gap: 0.75rem; align-items: end; }
615
897
  .search-wrap { position: relative; display: flex; align-items: center; }
616
898
  .search-icon { position: absolute; left: 0.6rem; color: var(--text-4); pointer-events: none; }
617
899
  input.search { padding-left: 2rem; min-width: 14rem; }
618
- .filter-bar { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: end; padding: 0.75rem 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; }
619
- form { margin: 0; display: flex; flex-direction: column; gap: 1rem; }
900
+ form { margin: 0; display: flex; flex-direction: column; gap: 0.85rem; }
620
901
  label { display: flex; flex-direction: column; font-size: 0.75rem; gap: 0.25rem; color: var(--text-2); }
621
- input, select, button { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); border-radius: 0.4rem; padding: 0.45rem 0.7rem; font-size: 0.85rem; transition: border-color 0.15s, background 0.15s; }
902
+ input, select, button { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); border-radius: 0.4rem; padding: 0.45rem 0.7rem; font-size: 0.85rem; transition: border-color 0.15s, background 0.15s, transform 0.1s; }
622
903
  input:focus, select:focus, button:focus { outline: none; border-color: var(--accent); }
623
904
  select { min-width: 7.5rem; cursor: pointer; }
905
+ select[multiple] { min-width: 10rem; padding: 0.3rem; cursor: default; }
906
+ select[multiple] option { padding: 0.15rem 0.4rem; border-radius: 0.25rem; }
907
+ select[multiple] option:checked { background: var(--accent); color: var(--bg); }
624
908
  button { cursor: pointer; background: var(--surface-4); font-weight: 500; }
625
909
  button:hover { background: var(--surface-5); }
910
+ button:active { transform: scale(0.97); }
626
911
  .theme { display: inline-flex; align-items: center; justify-content: center; font-size: 1.25rem; padding: 0.35rem 0.6rem; line-height: 1; }
627
- .sessions-toggle-bar { flex: 0 0 auto; display: flex; align-items: center; gap: 0.4rem; width: 100%; text-align: left; background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; padding: 0.65rem 1rem; font-size: 0.85rem; font-weight: 600; color: var(--text-2); }
628
- .sessions-toggle-bar:hover { background: var(--surface-3); }
629
- .sessions-toggle-bar[aria-expanded="true"] { color: var(--text); }
912
+ .toolbar-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
913
+ .toggle-pill { display: inline-flex; align-items: center; gap: 0.4rem; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 0.5rem 1rem; font-size: 0.85rem; font-weight: 600; color: var(--text-2); box-shadow: var(--shadow-sm); }
914
+ .toggle-pill:hover { background: var(--surface-3); color: var(--text); }
915
+ .toggle-pill[aria-expanded="true"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
916
+ .toggle-chevron { transition: transform 0.18s ease; flex: 0 0 auto; }
917
+ .toggle-pill[aria-expanded="true"] .toggle-chevron { transform: rotate(90deg); }
918
+ .toggle-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 1.15rem; height: 1.15rem; padding: 0 0.3rem; border-radius: 999px; background: var(--accent); color: var(--bg); font-size: 0.68rem; font-weight: 700; }
919
+ .view-tabs { display: flex; align-items: center; gap: 0.15rem; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 0.25rem; box-shadow: var(--shadow-sm); }
920
+ .view-tab { border: none; background: transparent; color: var(--text-2); border-radius: 999px; padding: 0.42rem 1rem; font-size: 0.85rem; font-weight: 600; display: inline-flex; align-items: center; gap: 0.4rem; }
921
+ .view-tab:hover { color: var(--text); background: var(--surface-3); }
922
+ .view-tab[aria-selected="true"] { background: var(--accent); color: var(--bg); }
923
+ .view-tab[aria-selected="true"]:hover { background: var(--accent); }
924
+ .view-tab .toggle-badge { background: color-mix(in srgb, var(--text) 15%, transparent); color: var(--text-2); }
925
+ .view-tab[aria-selected="true"] .toggle-badge { background: rgba(0,0,0,0.22); color: var(--bg); }
926
+ .filter-panel { display: none; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: 1.1rem 1.25rem; align-items: start; padding: 1.15rem 1.25rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.85rem; box-shadow: var(--shadow-sm); animation: filterPanelIn 0.15s ease; }
927
+ .filter-panel.open { display: grid; }
928
+ .filter-panel .field { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; }
929
+ .filter-panel .field-wide { grid-column: span 2; min-width: 15rem; }
930
+ .filter-panel .field label { display: block; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-3); }
931
+ .filter-panel .field select, .filter-panel .field input { width: 100%; }
932
+ @keyframes filterPanelIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
933
+ .ms { position: relative; display: flex; flex-direction: column; gap: 0.4rem; }
934
+ .ms select.ms-native { display: block; }
935
+ .ms.enhanced select.ms-native { display: none; }
936
+ .ms-pills { display: flex; flex-wrap: wrap; gap: 0.35rem; min-height: 2.1rem; padding: 0.3rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: 0.5rem; align-items: center; }
937
+ .ms-pill { display: inline-flex; align-items: center; gap: 0.3rem; max-width: 100%; background: color-mix(in srgb, var(--accent) 16%, var(--surface-4)); border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border)); color: var(--text); border-radius: 999px; padding: 0.2rem 0.35rem 0.2rem 0.65rem; font-size: 0.75rem; font-weight: 600; line-height: 1.4; }
938
+ .ms-pill span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 10rem; }
939
+ .ms-pill button { all: unset; display: inline-flex; align-items: center; justify-content: center; width: 1.1rem; height: 1.1rem; border-radius: 50%; color: var(--text-2); font-size: 0.85rem; line-height: 1; cursor: pointer; }
940
+ .ms-pill button:hover { background: rgba(0,0,0,0.18); color: var(--text); }
941
+ .ms-pill--all { background: transparent; border-style: dashed; border-color: var(--border); color: var(--text-4); font-weight: 500; font-style: italic; }
942
+ .ms-search-wrap { position: relative; }
943
+ .ms-search { width: 100%; font-size: 0.78rem; padding: 0.4rem 0.6rem; }
944
+ .ms-dropdown { position: absolute; top: calc(100% + 0.3rem); left: 0; right: 0; z-index: 30; max-height: 12rem; overflow-y: auto; background: var(--surface-3); border: 1px solid var(--border); border-radius: 0.5rem; box-shadow: var(--shadow); padding: 0.3rem; }
945
+ .ms-option { padding: 0.4rem 0.6rem; border-radius: 0.3rem; font-size: 0.8rem; color: var(--text-2); cursor: pointer; }
946
+ .ms-option:hover { background: var(--surface-5); color: var(--text); }
947
+ .ms-empty { padding: 0.4rem 0.6rem; font-size: 0.75rem; color: var(--text-4); }
630
948
  .dashboard-content-area { flex: 1 1 auto; }
631
949
  .session-list-title { display: flex; align-items: center; gap: 0.5rem; font-size: 0.95rem; font-weight: 600; color: var(--text); margin: 0 0 0.5rem; }
632
950
  .session-collapse-count { color: var(--text-4); font-weight: 500; }
633
951
  .sessions-list-filter { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-2); background: var(--surface-2); border: 1px solid var(--border); border-radius: 0.4rem; padding: 0.5rem 0.75rem; margin-bottom: 0.75rem; }
634
952
  .sessions-list-filter a { color: var(--accent); text-decoration: none; }
635
953
  .sessions-list-filter a:hover { text-decoration: underline; }
636
- .session-list-wrapper { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; overflow: hidden; }
954
+ .session-list-wrapper { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: 0.85rem; overflow: hidden; box-shadow: var(--shadow-sm); }
637
955
  .session-list { list-style: none; padding: 0; margin: 0; }
638
956
  .status-badge { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.15rem 0.45rem; border-radius: 0.25rem; }
639
957
  .status-active { background: rgba(34,197,94,0.15); color: var(--success); }
@@ -673,7 +991,8 @@ button:hover { background: var(--surface-5); }
673
991
  .main-metrics .metric-card { min-height: 5rem; }
674
992
  .main-metrics .metric-value { font-size: 1.65rem; }
675
993
  .top-charts { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
676
- .chart-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; display: flex; flex-direction: column; min-height: 18rem; }
994
+ .chart-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 0.85rem; padding: 1.1rem; display: flex; flex-direction: column; min-height: 18rem; box-shadow: var(--shadow-sm); transition: box-shadow 0.15s, transform 0.15s; }
995
+ .chart-panel:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
677
996
  .chart-title { font-size: 0.9rem; font-weight: 600; color: var(--text-2); margin: 0 0 0.75rem; flex: 0 0 auto; }
678
997
  .chart-empty { color: var(--text-4); text-align: center; padding: 4rem 0; }
679
998
  .chart-max { text-align: right; color: var(--text-4); font-size: 0.7rem; margin-top: 0.25rem; flex: 0 0 auto; }
@@ -694,9 +1013,9 @@ a.tool-row:hover .tool-name { color: var(--accent); }
694
1013
  .tool-bar-bg { background: var(--surface-4); border-radius: 0.4rem; height: 0.85rem; overflow: hidden; }
695
1014
  .tool-bar { background: linear-gradient(90deg, var(--accent), var(--accent-2)); height: 100%; border-radius: 0.4rem; }
696
1015
  .tool-count { color: var(--text-3); text-align: right; font-weight: 600; }
697
- .detail-area { background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; }
1016
+ .detail-area { background: var(--surface); border: 1px solid var(--border); border-radius: 0.85rem; padding: 1rem; box-shadow: var(--shadow-sm); }
698
1017
  .detail-area > .empty { color: var(--text-4); text-align: center; padding: 4rem 0; }
699
- .session-detail-view { display: flex; flex-direction: column; gap: 0.75rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; }
1018
+ .session-detail-view { display: flex; flex-direction: column; gap: 0.75rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.85rem; padding: 1.1rem; box-shadow: var(--shadow-sm); }
700
1019
  .detail-header { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; flex: 0 0 auto; }
701
1020
  .detail-back { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.8rem; padding: 0.35rem 0.6rem; flex: 0 0 auto; }
702
1021
  .detail-title { font-size: 1rem; font-weight: 600; color: var(--text); word-break: break-all; flex: 1 1 auto; min-width: 0; margin: 0; }
@@ -749,7 +1068,8 @@ a.tool-row:hover .tool-name { color: var(--accent); }
749
1068
  .pager button:disabled { opacity: 0.45; cursor: not-allowed; }
750
1069
  .pager-info { color: var(--text-3); font-size: 0.85rem; }
751
1070
  .metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
752
- .metric-card { position: relative; overflow: hidden; background: var(--surface-2); border: 1px solid var(--border); border-radius: 0.5rem; padding: 0.85rem 1rem; display: flex; flex-direction: column; justify-content: center; }
1071
+ .metric-card { position: relative; overflow: hidden; background: var(--surface-2); border: 1px solid var(--border); border-radius: 0.75rem; padding: 0.9rem 1.1rem; display: flex; flex-direction: column; justify-content: center; box-shadow: var(--shadow-sm); transition: box-shadow 0.15s, transform 0.15s; }
1072
+ .metric-card:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
753
1073
  .metric-sessions .metric-icon { color: var(--accent); }
754
1074
  .metric-events .metric-icon { color: var(--accent-2); }
755
1075
  .metric-duration .metric-icon { color: var(--warning); }
@@ -770,7 +1090,7 @@ a.tool-row:hover .tool-name { color: var(--accent); }
770
1090
  .context-legend-value { color: var(--text-3); font-weight: 600; }
771
1091
  .empty { color: var(--text-4); text-align: center; padding: 2rem; }
772
1092
  @media (max-width: 960px) { .top-charts { grid-template-columns: 1fr; } .metric-grid { grid-template-columns: repeat(2, 1fr); } .main-metrics .metric-grid { grid-template-columns: repeat(2, 1fr); } .detail-attrs { grid-template-columns: repeat(2, 1fr); } .context-bars { grid-template-columns: 1fr; } }
773
- @media (max-width: 640px) { .app { padding: 0.75rem; } .app-header { flex-direction: column; align-items: flex-start; } .header-controls, .filter-bar { width: 100%; } input.search { min-width: 0; width: 100%; } .search-wrap { width: 100%; } .metric-grid { grid-template-columns: 1fr; } .main-metrics .metric-grid { grid-template-columns: 1fr; } .detail-attrs { grid-template-columns: 1fr; } .detail-toolbar input[type="search"] { min-width: 0; width: 100%; } }
1093
+ @media (max-width: 640px) { .app { padding: 0.75rem; } .app-header { flex-direction: column; align-items: flex-start; } .header-controls, .filter-panel, .toolbar-row { width: 100%; } .filter-panel { grid-template-columns: 1fr; } .filter-panel .field-wide { grid-column: span 1; } .view-tabs { flex: 1 1 auto; justify-content: center; } input.search { min-width: 0; width: 100%; } .search-wrap { width: 100%; } .metric-grid { grid-template-columns: 1fr; } .main-metrics .metric-grid { grid-template-columns: 1fr; } .detail-attrs { grid-template-columns: 1fr; } .detail-toolbar input[type="search"] { min-width: 0; width: 100%; } }
774
1094
  </style>
775
1095
  <script src="https://unpkg.com/htmx.org@2.0.10/dist/htmx.min.js" integrity="sha256-cepnGFv6jJjDnTFxfG/OXYUjcPzf0SnbRUN3TTFFwN4=" crossorigin="anonymous"><\/script>
776
1096
  <script src="https://unpkg.com/htmx-ext-sse@2.2.4/dist/sse.js" integrity="sha256-O1mSpUFhm6vvxMFpUFr0dN9cMDnaUeWblsz5JB7NYdI=" crossorigin="anonymous"><\/script>
@@ -779,23 +1099,21 @@ a.tool-row:hover .tool-name { color: var(--accent); }
779
1099
  <div class="app">
780
1100
  <div class="sticky-bar">
781
1101
  <form data-filter-form hx-get="/fragments/sessions" hx-target="#dashboard-content" hx-swap="innerHTML" hx-trigger="change, input changed delay:300ms from:.search">
782
- ${en(t)}${rn(n,t)}
1102
+ <input type="hidden" name="view" value="${t.view===`list`?`list`:``}">
1103
+ ${i}${a}
783
1104
  </form>
784
1105
  </div>
785
- <button type="button" id="sessions-toggle" class="sessions-toggle-bar" aria-expanded="false" onclick="toggleSessionsList()">
786
- <span class="sessions-toggle-arrow" aria-hidden="true">▸</span> Recent Sessions
787
- </button>
788
1106
  <div id="dashboard-content" class="dashboard-content-area" sse-connect="/events/stream" hx-get="/fragments/sessions" hx-target="#dashboard-content" hx-swap="innerHTML" hx-trigger="sse:message">
789
1107
  ${r}
790
1108
  </div>
791
1109
  </div>
792
1110
  </body>
793
- </html>`}function on(e,t,n,r){let i=J(n);t.writeHead(200,{"Content-Type":`text/html; charset=utf-8`}),t.end(an(r,i))}let X;const Z=new Set;function sn(e,t){t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.setTimeout(0),t.write(`retry: 500
1111
+ </html>`}function mn(e,t,n,r){let i=Y(n);t.writeHead(200,{"Content-Type":`text/html; charset=utf-8`}),t.end(pn(r,i))}let Z;const hn=new Set;function gn(e,t){t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.setTimeout(0),t.write(`retry: 500
794
1112
 
795
- `);let n=e.headers[`last-event-id`],r=Array.isArray(n)?n[0]:n,i=r?parseInt(r,10):NaN,a={res:t,lastId:Number.isNaN(i)?ze(X):i,timer:null};a.timer=setInterval(()=>{try{let e=ze(X);e>a.lastId&&(t.write(`event: message
1113
+ `);let n=e.headers[`last-event-id`],r=Array.isArray(n)?n[0]:n,i=r?parseInt(r,10):NaN,a={res:t,lastId:Number.isNaN(i)?Ve(Z):i,timer:null};a.timer=setInterval(()=>{try{let e=Ve(Z);e>a.lastId&&(t.write(`event: message
796
1114
  data: ping
797
1115
 
798
- `),a.lastId=e)}catch(e){console.error(`SSE poll error:`,e),clearInterval(a.timer),Z.delete(a)}},300),Z.add(a),e.on(`close`,()=>{clearInterval(a.timer),Z.delete(a)})}function cn(e,t){let n=new URL(e.url??`/`,`http://localhost`);try{if(e.method===`GET`){if(n.pathname===`/`){on(e,t,n,X);return}if(n.pathname===`/fragments/sessions`){Yt(e,t,n,X);return}if(n.pathname===`/fragments/detail`){Zt(e,t,n,X);return}if(n.pathname===`/events`){Qt(e,t,n,X);return}if(n.pathname===`/events/stream`){sn(e,t);return}}t.writeHead(404,{"Content-Type":`text/plain`}),t.end(`Not found`)}catch(e){console.error(`Dashboard request error:`,e),t.headersSent?t.writableEnded||t.destroy?.():(t.writeHead(500,{"Content-Type":`text/plain`}),t.end(`Internal server error`))}}async function ln(e,t,n=!1){let r=f.createServer((e,t)=>{cn(e,t)});await new Promise((i,a)=>{let o=e=>{function s(t){r.off(`error`,s),r.off(`listening`,l),t.code===`EADDRINUSE`&&e<65535?o(e+1):a(t)}function l(){r.off(`error`,s);let a=`http://localhost:${e}`;n||console.log(`Dashboard: ${a}`),t&&c.platform===`darwin`&&p(`open`,[a],e=>{e&&console.error(`open failed:`,e.message)}),i()}r.once(`error`,s),r.once(`listening`,l),r.listen(e,`127.0.0.1`)};o(e)})}async function un(e){let t=8765,n=!0,r=!1,i=!1;for(let a of e){if(i){let e=parseInt(a,10);(Number.isNaN(e)||e<1||e>65535)&&(console.error(`Invalid --port`),c.exit(1)),t=e,i=!1;continue}if(a===`--port`){i=!0;continue}if(a.startsWith(`--port=`)){let e=parseInt(a.slice(7),10);(Number.isNaN(e)||e<1||e>65535)&&(console.error(`Invalid --port`),c.exit(1)),t=e}else a===`--no-open`?n=!1:a===`--silent`&&(r=!0,n=!1)}i&&(console.error(`Missing value for --port`),c.exit(1)),await dt(),X=T(w()),await ln(t,n,r)}const dn=[`json`,`jsonl`,`summary`];function Q(e){let t=e.trim();if(t===``)return;let n=Number(t);if(!(!Number.isFinite(n)||!Number.isInteger(n)||n<0))return n}function $(e){let t=e.trim();if(t===``)return;let n=Number(t);if(!(!Number.isFinite(n)||n<0))return n}const fn=[`24h`,`7d`,`30d`,`all`];function pn(){c.stdout.write(`Usage: happenin query [options]
1116
+ `),a.lastId=e)}catch(e){console.error(`SSE poll error:`,e),clearInterval(a.timer),hn.delete(a)}},300),hn.add(a),e.on(`close`,()=>{clearInterval(a.timer),hn.delete(a)})}function _n(e,t){let n=new URL(e.url??`/`,`http://localhost`);try{if(e.method===`GET`){if(n.pathname===`/`){mn(e,t,n,Z);return}if(n.pathname===`/fragments/sessions`){en(e,t,n,Z);return}if(n.pathname===`/fragments/detail`){nn(e,t,n,Z);return}if(n.pathname===`/events`){rn(e,t,n,Z);return}if(n.pathname===`/events/stream`){gn(e,t);return}}t.writeHead(404,{"Content-Type":`text/plain`}),t.end(`Not found`)}catch(e){console.error(`Dashboard request error:`,e),t.headersSent?t.writableEnded||t.destroy?.():(t.writeHead(500,{"Content-Type":`text/plain`}),t.end(`Internal server error`))}}function vn(e){p(c.platform===`win32`?`cmd`:c.platform===`darwin`?`open`:`xdg-open`,c.platform===`win32`?[`/c`,`start`,``,e]:[e],e=>{e&&console.error(`open failed:`,e.message,`(use --no-open to suppress)`)})}async function yn(e,t,n=!1){let r=f.createServer((e,t)=>{_n(e,t)});await new Promise((i,a)=>{let o=e=>{function s(t){r.off(`error`,s),r.off(`listening`,c),t.code===`EADDRINUSE`&&e<65535?o(e+1):a(t)}function c(){r.off(`error`,s);let a=`http://localhost:${e}`;n||console.log(`Dashboard: ${a}`),t&&vn(a),i()}r.once(`error`,s),r.once(`listening`,c),r.listen(e,`127.0.0.1`)};o(e)})}async function bn(e){let t=8765,n=!0,r=!1,i=!1;for(let a of e){if(i){let e=parseInt(a,10);(Number.isNaN(e)||e<1||e>65535)&&(console.error(`Invalid --port`),c.exit(1)),t=e,i=!1;continue}if(a===`--port`){i=!0;continue}if(a.startsWith(`--port=`)){let e=parseInt(a.slice(7),10);(Number.isNaN(e)||e<1||e>65535)&&(console.error(`Invalid --port`),c.exit(1)),t=e}else a===`--no-open`?n=!1:a===`--silent`&&(r=!0,n=!1)}i&&(console.error(`Missing value for --port`),c.exit(1)),await pt(),Z=E(T()),await yn(t,n,r)}const xn=[`json`,`jsonl`,`summary`];function Q(e){let t=e.trim();if(t===``)return;let n=Number(t);if(!(!Number.isFinite(n)||!Number.isInteger(n)||n<0))return n}function $(e){let t=e.trim();if(t===``)return;let n=Number(t);if(!(!Number.isFinite(n)||n<0))return n}const Sn=[`24h`,`7d`,`30d`,`all`];function Cn(){c.stdout.write(`Usage: happenin query [options]
799
1117
 
800
1118
  Options:
801
1119
  --source <source> filter by source (cursor, claude, ...)
@@ -810,7 +1128,7 @@ Options:
810
1128
  --format <format> output format: json (default), jsonl, summary
811
1129
  --db <path> database path (default: HAPPENIN_DB or ~/.happenin/happenin.db)
812
1130
  -h, --help show this help
813
- `)}function mn(e){let t={},n=`json`,r,i=!1,a;for(let o of e){if(a){switch(a){case`source`:t.source=o;break;case`event`:t.event=o;break;case`session`:t.sessionId=o;break;case`q`:t.q=o;break;case`since`:{let e=Q(o);e!==void 0&&(t.since=e);break}case`range`:fn.includes(o)&&(t.range=o);break;case`status`:(o===`active`||o===`completed`||o===`failed`)&&(t.status=o);break;case`tool`:t.tool=o;break;case`minDuration`:{let e=$(o);e!==void 0&&(t.minDuration=e);break}case`maxDuration`:{let e=$(o);e!==void 0&&(t.maxDuration=e);break}case`limit`:{let e=Q(o);e!==void 0&&(t.limit=e);break}case`offset`:{let e=Q(o);e!==void 0&&(t.offset=e);break}case`format`:dn.includes(o)&&(n=o);break;case`db`:r=o;break}a=void 0;continue}if(o===`-h`||o===`--help`){i=!0;continue}if(o===`--source`||o===`--event`||o===`--session`||o===`--q`||o===`--since`||o===`--range`||o===`--status`||o===`--tool`||o===`--minDuration`||o===`--maxDuration`||o===`--limit`||o===`--offset`||o===`--format`||o===`--db`){a=o.slice(2);continue}if(o.startsWith(`--`)){let e=o.indexOf(`=`);if(e!==-1){let i=o.slice(2,e),a=o.slice(e+1);switch(i){case`source`:t.source=a;break;case`event`:t.event=a;break;case`session`:t.sessionId=a;break;case`q`:t.q=a;break;case`since`:{let e=Q(a);e!==void 0&&(t.since=e);break}case`range`:fn.includes(a)&&(t.range=a);break;case`status`:(a===`active`||a===`completed`||a===`failed`)&&(t.status=a);break;case`tool`:t.tool=a;break;case`minDuration`:{let e=$(a);e!==void 0&&(t.minDuration=e);break}case`maxDuration`:{let e=$(a);e!==void 0&&(t.maxDuration=e);break}case`limit`:{let e=Q(a);e!==void 0&&(t.limit=e);break}case`offset`:{let e=Q(a);e!==void 0&&(t.offset=e);break}case`format`:dn.includes(a)&&(n=a);break;case`db`:r=a;break}}}}return{options:{range:`24h`,...t},format:n,dbPath:r,help:i}}async function hn(e,t,n,r=!1){let{options:i,format:a,dbPath:o,help:s}=mn(e);if(s){t();return}r||(delete i.status,delete i.minDuration,delete i.maxDuration);let c=T(o);try{await n(c,i,a)}finally{c.close()}}async function gn(e){await hn(e,pn,(e,t,n)=>{if(n===`summary`){let n=Ee(e,t);c.stdout.write(`${JSON.stringify(n,null,2)}\n`);return}let r={total:A(e,t),rows:k(e,t).map(jt)};if(n===`jsonl`)for(let e of r.rows)c.stdout.write(`${JSON.stringify(e)}\n`);else c.stdout.write(`${JSON.stringify(r,null,2)}\n`)})}function _n(){c.stdout.write(`Usage: happenin sessions [options]
1131
+ `)}function wn(e){let t={},n=`json`,r,i=!1,a;for(let o of e){if(a){switch(a){case`source`:t.source=o;break;case`event`:t.event=o;break;case`session`:t.sessionId=o;break;case`q`:t.q=o;break;case`since`:{let e=Q(o);e!==void 0&&(t.since=e);break}case`range`:Sn.includes(o)&&(t.range=o);break;case`status`:(o===`active`||o===`completed`||o===`failed`)&&(t.status=o);break;case`tool`:t.tool=o;break;case`minDuration`:{let e=$(o);e!==void 0&&(t.minDuration=e);break}case`maxDuration`:{let e=$(o);e!==void 0&&(t.maxDuration=e);break}case`limit`:{let e=Q(o);e!==void 0&&(t.limit=e);break}case`offset`:{let e=Q(o);e!==void 0&&(t.offset=e);break}case`format`:xn.includes(o)&&(n=o);break;case`db`:r=o;break}a=void 0;continue}if(o===`-h`||o===`--help`){i=!0;continue}if(o===`--source`||o===`--event`||o===`--session`||o===`--q`||o===`--since`||o===`--range`||o===`--status`||o===`--tool`||o===`--minDuration`||o===`--maxDuration`||o===`--limit`||o===`--offset`||o===`--format`||o===`--db`){a=o.slice(2);continue}if(o.startsWith(`--`)){let e=o.indexOf(`=`);if(e!==-1){let i=o.slice(2,e),a=o.slice(e+1);switch(i){case`source`:t.source=a;break;case`event`:t.event=a;break;case`session`:t.sessionId=a;break;case`q`:t.q=a;break;case`since`:{let e=Q(a);e!==void 0&&(t.since=e);break}case`range`:Sn.includes(a)&&(t.range=a);break;case`status`:(a===`active`||a===`completed`||a===`failed`)&&(t.status=a);break;case`tool`:t.tool=a;break;case`minDuration`:{let e=$(a);e!==void 0&&(t.minDuration=e);break}case`maxDuration`:{let e=$(a);e!==void 0&&(t.maxDuration=e);break}case`limit`:{let e=Q(a);e!==void 0&&(t.limit=e);break}case`offset`:{let e=Q(a);e!==void 0&&(t.offset=e);break}case`format`:xn.includes(a)&&(n=a);break;case`db`:r=a;break}}}}return{options:{range:`24h`,...t},format:n,dbPath:r,help:i}}async function Tn(e,t,n,r=!1){let{options:i,format:a,dbPath:o,help:s}=wn(e);if(s){t();return}r||(delete i.status,delete i.minDuration,delete i.maxDuration);let c=E(o);try{await n(c,i,a)}finally{c.close()}}async function En(e){await Tn(e,Cn,(e,t,n)=>{if(n===`summary`){let n=Oe(e,t);c.stdout.write(`${JSON.stringify(n,null,2)}\n`);return}let r={total:A(e,t),rows:De(e,t).map(Nt)};if(n===`jsonl`)for(let e of r.rows)c.stdout.write(`${JSON.stringify(e)}\n`);else c.stdout.write(`${JSON.stringify(r,null,2)}\n`)})}function Dn(){c.stdout.write(`Usage: happenin sessions [options]
814
1132
 
815
1133
  Options:
816
1134
  --source <source> filter by source (cursor, claude, ...)
@@ -828,8 +1146,8 @@ Options:
828
1146
  --format <format> output format: json (default), jsonl, summary
829
1147
  --db <path> database path (default: HAPPENIN_DB or ~/.happenin/happenin.db)
830
1148
  -h, --help show this help
831
- `)}function vn(e){return e.map(e=>{let t=`${Math.floor(e.durationMs/1e3)}s`,n=e.sessionId??`no-session`,r=e.firstAt??`?`,i=e.lastAt??`?`,a=e.projectPath??`-`,o=e.tools.join(`,`)||`-`,s=e.skills.join(`,`)||`-`,c=e.files.join(`,`)||`-`;return`${n}\t${r}\t${i}\t${t}\t${e.eventCount} events\tproject:${a}\ttools:${o}\tskills:${s}\tfiles:${c}\tfailures:${e.failureCount}`}).join(`
832
- `)}async function yn(e){await hn(e,_n,(e,t,n)=>{let r=j(e,t);if(n===`jsonl`){for(let e of r)c.stdout.write(`${JSON.stringify(e)}\n`);return}if(n===`summary`){c.stdout.write(`${vn(r)}\n`);return}c.stdout.write(`${JSON.stringify(r,null,2)}\n`)},!0)}function bn(){let t=s.dirname(l(import.meta.url)),n=t;for(;;){let t=s.dirname(n);if(t===n)break;if(e(s.join(n,`package.json`)))return n;n=t}return t}const xn=bn(),Sn=s.join(xn,`assets`,`help.md`),Cn=s.join(xn,`package.json`);function wn(e){let t=!!c.stdout.isTTY&&!c.env.NO_COLOR,n=t?`\x1B[1m`:``,r=t?`\x1B[2m`:``,i=t?`\x1B[36m`:``,a=t?`\x1B[0m`:``;return e.replace(/^# (.+)$/gm,`${n}$1${a}`).replace(/^## (.+)$/gm,`${n}$1${a}`).replace(/^### `(.+)`$/gm,`${n}$1${a}`).replace(/^### (.+)$/gm,`${n}$1${a}`).replace(/^#### (.+)$/gm,`${r}$1${a}`).replace(/^- /gm,` • `).replace(/`([^`]+)`/g,`${i}$1${a}`)}function Tn(){let e;try{e=n(Sn,`utf8`)}catch{e=`# happenin
1149
+ `)}function On(e){return e.map(e=>{let t=`${Math.floor(e.durationMs/1e3)}s`,n=e.sessionId??`no-session`,r=e.firstAt??`?`,i=e.lastAt??`?`,a=e.projectPath??`-`,o=e.tools.join(`,`)||`-`,s=e.skills.join(`,`)||`-`,c=e.files.join(`,`)||`-`;return`${n}\t${r}\t${i}\t${t}\t${e.eventCount} events\tproject:${a}\ttools:${o}\tskills:${s}\tfiles:${c}\tfailures:${e.failureCount}`}).join(`
1150
+ `)}async function kn(e){await Tn(e,Dn,(e,t,n)=>{let r=j(e,t);if(n===`jsonl`){for(let e of r)c.stdout.write(`${JSON.stringify(e)}\n`);return}if(n===`summary`){c.stdout.write(`${On(r)}\n`);return}c.stdout.write(`${JSON.stringify(r,null,2)}\n`)},!0)}function An(){let t=s.dirname(l(import.meta.url)),n=t;for(;;){let t=s.dirname(n);if(t===n)break;if(e(s.join(n,`package.json`)))return n;n=t}return t}const jn=An(),Mn=s.join(jn,`assets`,`help.md`),Nn=s.join(jn,`package.json`);function Pn(e){let t=!!c.stdout.isTTY&&!c.env.NO_COLOR,n=t?`\x1B[1m`:``,r=t?`\x1B[2m`:``,i=t?`\x1B[36m`:``,a=t?`\x1B[0m`:``;return e.replace(/^# (.+)$/gm,`${n}$1${a}`).replace(/^## (.+)$/gm,`${n}$1${a}`).replace(/^### `(.+)`$/gm,`${n}$1${a}`).replace(/^### (.+)$/gm,`${n}$1${a}`).replace(/^#### (.+)$/gm,`${r}$1${a}`).replace(/^- /gm,` • `).replace(/`([^`]+)`/g,`${i}$1${a}`)}function Fn(){let e;try{e=n(Mn,`utf8`)}catch{e=`# happenin
833
1151
 
834
1152
  Run \`happenin <command>\`.
835
- `}c.stdout.write(`\n${wn(e)}\n`)}function En(){let e=`0.0.0`;try{let t=n(Cn,`utf8`);e=JSON.parse(t).version??e}catch{}c.stdout.write(`${e}\n`)}async function Dn(){let[e,...t]=c.argv.slice(2);if(!e||e===`--help`||e===`-h`){Tn();return}if(e===`--version`||e===`-v`){En();return}if(e===`install`){await ge(t);return}if(e===`record`){await Ze(t);return}if(e===`import`){await dt(t);return}if(e===`dashboard`){await un(t);return}if(e===`query`){await gn(t);return}if(e===`sessions`){await yn(t);return}c.stderr.write(`Unknown command: ${e}\n`),Tn(),c.exitCode=1}await Dn();export{};
1153
+ `}c.stdout.write(`\n${Pn(e)}\n`)}function In(){let e=`0.0.0`;try{let t=n(Nn,`utf8`);e=JSON.parse(t).version??e}catch{}c.stdout.write(`${e}\n`)}async function Ln(){let[e,...t]=c.argv.slice(2);if(!e||e===`--help`||e===`-h`){Fn();return}if(e===`--version`||e===`-v`){In();return}if(e===`install`){await he(t);return}if(e===`record`){await $e(t);return}if(e===`import`){await pt(t);return}if(e===`dashboard`){await bn(t);return}if(e===`query`){await En(t);return}if(e===`sessions`){await kn(t);return}c.stderr.write(`Unknown command: ${e}\n`),Fn(),c.exitCode=1}await Ln();export{};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "happenin",
3
- "version": "0.3.0",
4
- "description": "A macOS CLI that records Cursor and Claude Code agent events to a local SQLite database and serves a live browser dashboard.",
3
+ "version": "0.4.0",
4
+ "description": "A CLI that records Cursor and Claude Code agent events to a local SQLite database and serves a live browser dashboard.",
5
5
  "keywords": [
6
6
  "agents",
7
7
  "ai-agents",
@@ -10,9 +10,11 @@
10
10
  "cli",
11
11
  "cursor",
12
12
  "dashboard",
13
+ "linux",
13
14
  "logging",
14
15
  "macos",
15
- "sqlite"
16
+ "sqlite",
17
+ "windows"
16
18
  ],
17
19
  "homepage": "https://github.com/YogliB/happenin#readme",
18
20
  "bugs": {