happenin 0.2.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 +45 -5
- package/assets/help.md +10 -3
- package/dist/bin.js +527 -118
- package/package.json +7 -5
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`]),
|
|
3
|
-
`,`utf8`),i(n,e)},
|
|
4
|
-
`),t&&c.stdout.write(` backup: ${t}\n`)}async function
|
|
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}}},be=e=>{let t=
|
|
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}},
|
|
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,
|
|
@@ -75,6 +75,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
|
|
|
75
75
|
project_path TEXT,
|
|
76
76
|
file_path TEXT,
|
|
77
77
|
tool_name TEXT,
|
|
78
|
+
skill_name TEXT,
|
|
78
79
|
payload TEXT NOT NULL,
|
|
79
80
|
source_path TEXT,
|
|
80
81
|
subagent_id TEXT,
|
|
@@ -86,7 +87,7 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
|
|
|
86
87
|
mtime INTEGER NOT NULL,
|
|
87
88
|
imported_at INTEGER NOT NULL
|
|
88
89
|
);
|
|
89
|
-
`),
|
|
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(`
|
|
90
91
|
INSERT INTO events (
|
|
91
92
|
source,
|
|
92
93
|
client,
|
|
@@ -97,13 +98,14 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
|
|
|
97
98
|
project_path,
|
|
98
99
|
file_path,
|
|
99
100
|
tool_name,
|
|
101
|
+
skill_name,
|
|
100
102
|
payload,
|
|
101
103
|
source_path,
|
|
102
104
|
subagent_id,
|
|
103
105
|
subagent_type,
|
|
104
106
|
transcript_path
|
|
105
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
106
|
-
`),r=Date.now(),i=
|
|
107
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
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=`
|
|
107
109
|
SELECT
|
|
108
110
|
id,
|
|
109
111
|
source,
|
|
@@ -115,13 +117,14 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
|
|
|
115
117
|
project_path AS projectPath,
|
|
116
118
|
file_path AS filePath,
|
|
117
119
|
tool_name AS toolName,
|
|
120
|
+
skill_name AS skillName,
|
|
118
121
|
payload,
|
|
119
122
|
source_path AS sourcePath,
|
|
120
123
|
subagent_id AS subagentId,
|
|
121
124
|
subagent_type AS subagentType,
|
|
122
125
|
transcript_path AS transcriptPath
|
|
123
126
|
FROM events
|
|
124
|
-
${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}=
|
|
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=`
|
|
125
128
|
SELECT
|
|
126
129
|
session_id AS sessionId,
|
|
127
130
|
COUNT(*) AS eventCount,
|
|
@@ -131,13 +134,38 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
|
|
|
131
134
|
MAX(happened_at) AS lastAt,
|
|
132
135
|
COALESCE(json_group_array(DISTINCT project_path ORDER BY project_path) FILTER (WHERE project_path IS NOT NULL AND project_path <> ''), '[]') AS projectPaths,
|
|
133
136
|
COALESCE(json_group_array(DISTINCT tool_name ORDER BY tool_name) FILTER (WHERE tool_name IS NOT NULL AND tool_name <> ''), '[]') AS toolNames,
|
|
137
|
+
COALESCE(json_group_array(DISTINCT skill_name ORDER BY skill_name) FILTER (WHERE skill_name IS NOT NULL AND skill_name <> ''), '[]') AS skillNames,
|
|
138
|
+
COALESCE(json_group_array(DISTINCT file_path ORDER BY file_path) FILTER (WHERE file_path IS NOT NULL AND file_path <> ''), '[]') AS filePaths,
|
|
134
139
|
SUM(CASE WHEN event LIKE '%Failure%' THEN 1 ELSE 0 END) AS failureCount
|
|
135
140
|
FROM events
|
|
136
141
|
${r}
|
|
137
142
|
GROUP BY session_id
|
|
138
143
|
ORDER BY lastReceivedAt DESC, sessionId
|
|
139
144
|
LIMIT ?${o>0?` OFFSET ?`:``}
|
|
140
|
-
`;return i.push(a),o>0&&i.push(o),e.prepare(s).all(...i).map(
|
|
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
|
+
CASE
|
|
147
|
+
WHEN tool_name LIKE 'mcp\\_\\_%' ESCAPE '\\' THEN 'mcpServers'
|
|
148
|
+
WHEN file_path IS NOT NULL AND file_path <> '' AND LOWER(file_path) LIKE '%.md' THEN 'mdFiles'
|
|
149
|
+
WHEN skill_name IS NOT NULL AND skill_name <> '' THEN 'bloatware'
|
|
150
|
+
WHEN tool_name IS NOT NULL AND tool_name <> '' THEN 'actualValue'
|
|
151
|
+
WHEN event IN ('user', 'assistant', 'prompt') THEN 'actualValue'
|
|
152
|
+
ELSE 'bloatware'
|
|
153
|
+
END
|
|
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
|
+
SELECT
|
|
156
|
+
${Pe} AS bucket,
|
|
157
|
+
SUM(LENGTH(payload)) AS bytes,
|
|
158
|
+
SUM(
|
|
159
|
+
CASE WHEN json_valid(payload) THEN
|
|
160
|
+
COALESCE(json_extract(payload, '\$.message.usage.input_tokens'), 0) +
|
|
161
|
+
COALESCE(json_extract(payload, '\$.message.usage.cache_creation_input_tokens'), 0) +
|
|
162
|
+
COALESCE(json_extract(payload, '\$.message.usage.cache_read_input_tokens'), 0)
|
|
163
|
+
ELSE 0 END
|
|
164
|
+
) AS tokens
|
|
165
|
+
FROM events
|
|
166
|
+
${r}
|
|
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=`
|
|
141
169
|
SELECT
|
|
142
170
|
session_id AS sessionId,
|
|
143
171
|
subagent_id AS subagentId,
|
|
@@ -149,18 +177,29 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
|
|
|
149
177
|
MAX(happened_at) AS lastAt,
|
|
150
178
|
COALESCE(json_group_array(DISTINCT project_path ORDER BY project_path) FILTER (WHERE project_path IS NOT NULL AND project_path <> ''), '[]') AS projectPaths,
|
|
151
179
|
COALESCE(json_group_array(DISTINCT tool_name ORDER BY tool_name) FILTER (WHERE tool_name IS NOT NULL AND tool_name <> ''), '[]') AS toolNames,
|
|
180
|
+
COALESCE(json_group_array(DISTINCT skill_name ORDER BY skill_name) FILTER (WHERE skill_name IS NOT NULL AND skill_name <> ''), '[]') AS skillNames,
|
|
181
|
+
COALESCE(json_group_array(DISTINCT file_path ORDER BY file_path) FILTER (WHERE file_path IS NOT NULL AND file_path <> ''), '[]') AS filePaths,
|
|
152
182
|
SUM(CASE WHEN event LIKE '%Failure%' THEN 1 ELSE 0 END) AS failureCount
|
|
153
183
|
FROM events
|
|
154
184
|
WHERE subagent_id IS NOT NULL AND subagent_id <> '' AND session_id IN (${n.map(()=>`?`).join(`,`)})${o}
|
|
155
185
|
GROUP BY session_id, subagent_id
|
|
156
186
|
ORDER BY lastReceivedAt DESC, subagent_id
|
|
157
|
-
`,c=e.prepare(i);s.push(...c.all(...n,...a))}return s.map(
|
|
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(`
|
|
158
197
|
INSERT INTO imports (path, mtime, imported_at)
|
|
159
198
|
VALUES (?, ?, ?)
|
|
160
199
|
ON CONFLICT(path) DO UPDATE SET
|
|
161
200
|
mtime = excluded.mtime,
|
|
162
201
|
imported_at = excluded.imported_at
|
|
163
|
-
`).run(t,n,Date.now())},
|
|
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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`)}function B(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}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">
|
|
164
203
|
<div class="metric-card metric-sessions">
|
|
165
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>
|
|
166
205
|
<div class="metric-label">Total Sessions</div>
|
|
@@ -174,81 +213,100 @@ import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,renameS
|
|
|
174
213
|
<div class="metric-card metric-duration">
|
|
175
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>
|
|
176
215
|
<div class="metric-label">Average Duration</div>
|
|
177
|
-
<div class="metric-value">${
|
|
216
|
+
<div class="metric-value">${_t(e.averageDurationMs)}</div>
|
|
178
217
|
</div>
|
|
179
218
|
<div class="metric-card metric-success">
|
|
180
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>
|
|
181
220
|
<div class="metric-label">Success Rate</div>
|
|
182
221
|
<div class="metric-value">${t}</div>
|
|
183
222
|
</div>
|
|
184
|
-
</div>`}function
|
|
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">
|
|
224
|
+
<h3 class="context-bar-title">${z(e)}</h3>
|
|
225
|
+
<div class="chart-empty">${z(r)}</div>
|
|
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">
|
|
227
|
+
<span class="context-legend-dot" style="background: ${a}"></span>
|
|
228
|
+
<span class="context-legend-label">${z(r)}</span>
|
|
229
|
+
<span class="context-legend-value">${z(n(o))} · ${s.toFixed(1)}%</span>
|
|
230
|
+
</div>`}).join(``);return`<div class="context-bar-block">
|
|
231
|
+
<h3 class="context-bar-title">${z(e)}</h3>
|
|
232
|
+
<div class="context-bar">${a}</div>
|
|
233
|
+
<div class="context-legend">${o}</div>
|
|
234
|
+
</div>`}function kt(e){return`<div class="chart-panel context-breakdown">
|
|
235
|
+
<h2 class="chart-title">Context Breakdown</h2>
|
|
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}${_}>
|
|
185
238
|
${o}
|
|
186
|
-
<div class="session-main"${
|
|
239
|
+
<div class="session-main"${v}>
|
|
187
240
|
<div class="session-row">
|
|
188
|
-
<span class="session-id" title="${
|
|
241
|
+
<span class="session-id" title="${B(l)}">${u}${te}</span>
|
|
189
242
|
<span class="status-badge status-${c}">${c}</span>
|
|
190
243
|
</div>
|
|
191
244
|
<div class="session-meta"><span>${f}</span><span>${e.eventCount} events</span><span>${d}</span></div>
|
|
192
245
|
</div>
|
|
193
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>
|
|
194
247
|
${s}
|
|
195
|
-
</li>`}function
|
|
196
|
-
<ul class="session-list sessions-table">${e.map(e=>e.children?.length?G(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=>G(e,t,n,r,i,!0)).join(``)}</ul>`):G(e,t,n,r,i)).join(``)}</ul>`}function at(e){if(e)try{return JSON.parse(e)}catch{return}}function ot(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,sourcePath:e.sourcePath,subagentId:e.subagentId,subagentType:e.subagentType,transcriptPath:e.transcriptPath,payload:at(e.payload??``)??e.payload}}function st(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 ct(e){return`<span class="ts">${H(W(e.happenedAt??e.receivedAt))}</span><span class="source">${H(e.source??``)}</span><span class="event">${H(e.event??``)}</span><span class="tool">${H(e.toolName??``)}</span>`}function lt(e){let t=H(W(e.happenedAt??e.receivedAt)),n=H(e.source??``),r=H(e.client??``),i=H(e.event??``),a=H(e.toolName??``),o=H(e.subagentId??``),s=H(e.subagentType??``),c=H(e.projectPath??``),l=H(e.filePath??``),u=o?`<div><span>Subagent</span><span>${s||`-`} (${o})</span></div>`:``,d=l?`<div><span>File</span><span>${l}</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">
|
|
197
249
|
<div><span>Time</span><span>${t}</span></div>
|
|
198
250
|
<div><span>Source</span><span>${n}</span></div>
|
|
199
251
|
<div><span>Client</span><span>${r||`-`}</span></div>
|
|
200
252
|
<div><span>Event</span><span>${i}</span></div>
|
|
201
253
|
<div><span>Tool</span><span>${a||`-`}</span></div>
|
|
202
254
|
<div><span>Project</span><span>${c||`-`}</span></div>
|
|
203
|
-
${u}
|
|
204
255
|
${d}
|
|
205
|
-
|
|
256
|
+
${f}
|
|
257
|
+
${p}
|
|
258
|
+
</div>`}function Rt(e){return`<div class="detail-json">
|
|
206
259
|
<details class="json-toggle">
|
|
207
260
|
<summary>JSON</summary>
|
|
208
|
-
<pre>${
|
|
261
|
+
<pre>${z(JSON.stringify(Nt(e),null,2))}</pre>
|
|
209
262
|
</details>
|
|
210
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>
|
|
211
|
-
</div>`}function
|
|
212
|
-
<summary>${
|
|
213
|
-
<div class="detail-body">${
|
|
214
|
-
${
|
|
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)}
|
|
215
268
|
</div>
|
|
216
|
-
</details>`}function
|
|
217
|
-
<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>
|
|
218
271
|
<div class="detail-body">
|
|
219
|
-
${
|
|
220
|
-
${
|
|
272
|
+
${Lt(n)}
|
|
273
|
+
${Rt(n)}
|
|
221
274
|
<div class="detail-children">${r}</div>
|
|
222
275
|
</div>
|
|
223
|
-
</details>`}function
|
|
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??``)}">
|
|
224
277
|
<div class="detail-header">
|
|
225
|
-
|
|
226
|
-
<h2 class="detail-title">Session Details - ${
|
|
227
|
-
<span class="detail-count">${n??
|
|
278
|
+
${Pt()}
|
|
279
|
+
<h2 class="detail-title">Session Details - ${z(s)}</h2>
|
|
280
|
+
<span class="detail-count">${n??o.length} events</span>
|
|
228
281
|
</div>
|
|
229
|
-
${
|
|
282
|
+
${zt(i)}
|
|
283
|
+
${a?kt(a):``}
|
|
284
|
+
${u}
|
|
230
285
|
<div class="detail-toolbar">
|
|
231
286
|
<input type="search" class="detail-search" placeholder="Search attributes..." oninput="filterSessionDetails(this.value)">
|
|
232
287
|
<button type="button" onclick="toggleSessionDetails(false)">Collapse All</button>
|
|
233
288
|
<button type="button" onclick="toggleSessionDetails(true)">Expand All</button>
|
|
234
289
|
</div>
|
|
235
|
-
<div class="detail-events">${
|
|
290
|
+
<div class="detail-events">${c}</div>
|
|
236
291
|
<div class="detail-actions">
|
|
237
292
|
<button type="button" onclick="copySessionJson()">Copy JSON</button>
|
|
238
293
|
</div>
|
|
239
|
-
<pre id="session-json" class="session-json">${
|
|
240
|
-
</section>`}function
|
|
294
|
+
<pre id="session-json" class="session-json">${z(l)}</pre>
|
|
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>
|
|
300
|
+
${l}
|
|
241
301
|
<div class="session-list-wrapper">
|
|
242
|
-
${
|
|
243
|
-
${
|
|
302
|
+
${jt(o,n,void 0,void 0,t)}
|
|
303
|
+
${Jt(t,r.length)}
|
|
244
304
|
</div>
|
|
245
|
-
</
|
|
246
|
-
<div class="main-
|
|
247
|
-
<div class="
|
|
248
|
-
|
|
249
|
-
</div>`}function
|
|
250
|
-
<div class="main-content"><div class="detail-area"><div class="empty">No events.</div></div></div>`;let 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});return`${r}
|
|
251
|
-
<div class="main-content">${mt(t.sessionId,i,a,t.subagentId)}</div>`}function St(e,t,n,r){let i=xt(r,K(n));t.writeHead(200,{"Content-Type":`text/html; charset=utf-8`}),t.end(i)}function Ct(e,t,n,r){let i=k(r,K(n));t.writeHead(200,{"Content-Type":`application/json`}),t.end(JSON.stringify(i))}const wt=[{value:`24h`,label:`Last 24h`},{value:`7d`,label:`Last 7 days`},{value:`30d`,label:`Last 30 days`},{value:`all`,label:`All time`}];function Tt(e){let t=H(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">
|
|
252
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>
|
|
253
311
|
<h1 class="page-title">Session Overview Analytics</h1>
|
|
254
312
|
<div class="header-controls">
|
|
@@ -256,17 +314,32 @@ ${vt(t,e.length)}
|
|
|
256
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>
|
|
257
315
|
<input type="text" name="q" class="search" placeholder="session or payload" value="${t}" autocomplete="off">
|
|
258
316
|
</div>
|
|
259
|
-
<label>range <select name="range">${
|
|
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>
|
|
260
318
|
<button type="button" class="theme" data-theme-toggle title="Toggle light/dark theme" aria-label="toggle theme">🌙</button>
|
|
261
319
|
</div>
|
|
262
|
-
</header>`}const
|
|
263
|
-
<
|
|
264
|
-
|
|
265
|
-
<
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
<
|
|
269
|
-
|
|
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>
|
|
270
343
|
<html lang="en" hx-ext="sse">
|
|
271
344
|
<head>
|
|
272
345
|
<meta charset="utf-8">
|
|
@@ -364,7 +437,14 @@ ${vt(t,e.length)}
|
|
|
364
437
|
if (!form) return false;
|
|
365
438
|
for (const [name, value] of Object.entries(values)) {
|
|
366
439
|
const field = form.elements[name];
|
|
367
|
-
if (field
|
|
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 !== '') {
|
|
368
448
|
field.value = String(value);
|
|
369
449
|
}
|
|
370
450
|
}
|
|
@@ -373,23 +453,270 @@ ${vt(t,e.length)}
|
|
|
373
453
|
return false;
|
|
374
454
|
}
|
|
375
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
|
+
|
|
376
701
|
function updateContentUrl() {
|
|
377
702
|
const form = document.querySelector('[data-filter-form]');
|
|
378
703
|
const content = document.getElementById('dashboard-content');
|
|
379
704
|
if (!form || !content) return;
|
|
380
|
-
const data = new FormData(form);
|
|
381
705
|
const params = new URLSearchParams();
|
|
382
|
-
for (const [key, value] of
|
|
383
|
-
|
|
706
|
+
for (const [key, value] of collectFormEntries(form)) {
|
|
707
|
+
params.append(key, value);
|
|
384
708
|
}
|
|
385
709
|
content.setAttribute('hx-get', '/fragments/sessions' + (params.size > 0 ? '?' + params.toString() : ''));
|
|
386
710
|
}
|
|
387
711
|
|
|
388
712
|
function saveFilters(form) {
|
|
389
|
-
const data = new FormData(form);
|
|
390
713
|
const filters = {};
|
|
391
|
-
for (const [key, value] of
|
|
392
|
-
if (
|
|
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
|
+
}
|
|
393
720
|
}
|
|
394
721
|
setItem(FILTER_KEY, JSON.stringify(filters));
|
|
395
722
|
}
|
|
@@ -398,15 +725,23 @@ ${vt(t,e.length)}
|
|
|
398
725
|
const form = document.querySelector('[data-filter-form]');
|
|
399
726
|
const content = document.getElementById('dashboard-content');
|
|
400
727
|
if (!form || !content) return;
|
|
401
|
-
|
|
402
|
-
|
|
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
|
+
});
|
|
403
737
|
form.addEventListener('input', (event) => {
|
|
404
738
|
if (event.target.classList.contains('search')) saveFilters(form);
|
|
405
739
|
});
|
|
406
740
|
updateContentUrl();
|
|
741
|
+
initFilterPanel();
|
|
407
742
|
if (hadSaved) {
|
|
408
743
|
setTimeout(() => {
|
|
409
|
-
|
|
744
|
+
requestSessionsFragment();
|
|
410
745
|
}, 0);
|
|
411
746
|
}
|
|
412
747
|
}
|
|
@@ -441,8 +776,7 @@ ${vt(t,e.length)}
|
|
|
441
776
|
}
|
|
442
777
|
|
|
443
778
|
function backToDashboard() {
|
|
444
|
-
|
|
445
|
-
if (form && typeof htmx !== 'undefined') htmx.trigger(form, 'change');
|
|
779
|
+
requestSessionsFragment();
|
|
446
780
|
}
|
|
447
781
|
|
|
448
782
|
function syncDetailState() {
|
|
@@ -450,28 +784,21 @@ ${vt(t,e.length)}
|
|
|
450
784
|
if (!content) return;
|
|
451
785
|
const detail = content.querySelector('.session-detail-view');
|
|
452
786
|
document.body.classList.toggle('session-detail-open', !!detail);
|
|
453
|
-
const sessionId = detail ? detail.getAttribute('data-session') : null;
|
|
454
|
-
const subagentId = detail ? detail.getAttribute('data-subagent') || null : null;
|
|
455
|
-
for (const item of content.querySelectorAll('.session-item')) {
|
|
456
|
-
const sessionMatch = !!sessionId && item.dataset.session === sessionId;
|
|
457
|
-
const itemSubagent = item.dataset.subagent || null;
|
|
458
|
-
const subagentMatch = itemSubagent === null || itemSubagent === subagentId;
|
|
459
|
-
item.classList.toggle('active', sessionMatch && subagentMatch);
|
|
460
|
-
}
|
|
461
|
-
const list = content.querySelector('.session-list');
|
|
462
|
-
if (list) {
|
|
463
|
-
list.scrollTop = sidebarScroll;
|
|
464
|
-
const active = content.querySelector('.session-item.active');
|
|
465
|
-
if (active) active.scrollIntoView({ block: 'nearest' });
|
|
466
|
-
}
|
|
467
787
|
}
|
|
468
788
|
|
|
469
|
-
|
|
789
|
+
function syncSessionsListState() {
|
|
790
|
+
const content = document.getElementById('dashboard-content');
|
|
791
|
+
if (!content) return;
|
|
792
|
+
const isList = !!content.querySelector('.sessions-list-view');
|
|
793
|
+
document.body.classList.toggle('sessions-list-open', isList);
|
|
794
|
+
}
|
|
795
|
+
|
|
470
796
|
document.addEventListener('htmx:beforeSwap', (e) => {
|
|
471
|
-
const list = document.querySelector('.session-list');
|
|
472
|
-
if (list) sidebarScroll = list.scrollTop;
|
|
473
797
|
const elt = e.detail.requestConfig ? e.detail.requestConfig.elt : null;
|
|
474
|
-
|
|
798
|
+
const blocked =
|
|
799
|
+
document.body.classList.contains('session-detail-open') ||
|
|
800
|
+
document.body.classList.contains('sessions-list-open');
|
|
801
|
+
if (elt && elt.id === 'dashboard-content' && blocked) {
|
|
475
802
|
e.detail.shouldSwap = false;
|
|
476
803
|
}
|
|
477
804
|
});
|
|
@@ -481,20 +808,34 @@ ${vt(t,e.length)}
|
|
|
481
808
|
window.copySessionJson = copySessionJson;
|
|
482
809
|
window.copyEventJson = copyEventJson;
|
|
483
810
|
window.backToDashboard = backToDashboard;
|
|
811
|
+
window.switchView = switchView;
|
|
812
|
+
window.toggleFilters = toggleFilters;
|
|
484
813
|
|
|
485
814
|
document.addEventListener('htmx:afterSettle', () => {
|
|
486
815
|
updateContentUrl();
|
|
487
816
|
syncDetailState();
|
|
817
|
+
syncSessionsListState();
|
|
818
|
+
updateViewTabs();
|
|
819
|
+
syncViewCounts();
|
|
488
820
|
});
|
|
489
821
|
|
|
822
|
+
function initHistoryState() {
|
|
823
|
+
syncDetailState();
|
|
824
|
+
syncSessionsListState();
|
|
825
|
+
updateViewTabs();
|
|
826
|
+
syncViewCounts();
|
|
827
|
+
}
|
|
828
|
+
|
|
490
829
|
applyTheme(getTheme());
|
|
491
830
|
|
|
492
831
|
if (document.readyState === 'loading') {
|
|
493
832
|
document.addEventListener('DOMContentLoaded', initTheme);
|
|
494
833
|
document.addEventListener('DOMContentLoaded', initFilters);
|
|
834
|
+
document.addEventListener('DOMContentLoaded', initHistoryState);
|
|
495
835
|
} else {
|
|
496
836
|
initTheme();
|
|
497
837
|
initFilters();
|
|
838
|
+
initHistoryState();
|
|
498
839
|
}
|
|
499
840
|
})();
|
|
500
841
|
<\/script>
|
|
@@ -518,6 +859,8 @@ ${vt(t,e.length)}
|
|
|
518
859
|
--success: #22c55e;
|
|
519
860
|
--warning: #eab308;
|
|
520
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);
|
|
521
864
|
}
|
|
522
865
|
[data-theme="light"] {
|
|
523
866
|
color-scheme: light;
|
|
@@ -538,34 +881,78 @@ ${vt(t,e.length)}
|
|
|
538
881
|
--success: #16a34a;
|
|
539
882
|
--warning: #ca8a04;
|
|
540
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);
|
|
541
886
|
}
|
|
542
887
|
* { box-sizing: border-box; }
|
|
543
|
-
html, body { height: 100%; }
|
|
888
|
+
html, body { min-height: 100%; }
|
|
544
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; }
|
|
545
|
-
.app { display: flex; flex-direction: column; height: 100%; max-width: 1400px; margin: 0 auto; padding: 1.
|
|
546
|
-
.sticky-bar { flex: 0 0 auto; display: flex; flex-direction: column; gap:
|
|
547
|
-
.app-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; padding: 0.
|
|
548
|
-
.brand { font-weight: 800; font-size: 1.
|
|
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; }
|
|
549
894
|
.brand-icon { color: var(--accent); }
|
|
550
|
-
.page-title { font-size: 1.
|
|
895
|
+
.page-title { font-size: 1.05rem; font-weight: 600; margin: 0; color: var(--text-2); }
|
|
551
896
|
.header-controls { display: flex; gap: 0.75rem; align-items: end; }
|
|
552
897
|
.search-wrap { position: relative; display: flex; align-items: center; }
|
|
553
898
|
.search-icon { position: absolute; left: 0.6rem; color: var(--text-4); pointer-events: none; }
|
|
554
899
|
input.search { padding-left: 2rem; min-width: 14rem; }
|
|
555
|
-
|
|
556
|
-
form { margin: 0; display: flex; flex-direction: column; gap: 1rem; }
|
|
900
|
+
form { margin: 0; display: flex; flex-direction: column; gap: 0.85rem; }
|
|
557
901
|
label { display: flex; flex-direction: column; font-size: 0.75rem; gap: 0.25rem; color: var(--text-2); }
|
|
558
|
-
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; }
|
|
559
903
|
input:focus, select:focus, button:focus { outline: none; border-color: var(--accent); }
|
|
560
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); }
|
|
561
908
|
button { cursor: pointer; background: var(--surface-4); font-weight: 500; }
|
|
562
909
|
button:hover { background: var(--surface-5); }
|
|
910
|
+
button:active { transform: scale(0.97); }
|
|
563
911
|
.theme { display: inline-flex; align-items: center; justify-content: center; font-size: 1.25rem; padding: 0.35rem 0.6rem; line-height: 1; }
|
|
564
|
-
.
|
|
565
|
-
.
|
|
566
|
-
.
|
|
567
|
-
.
|
|
568
|
-
.
|
|
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); }
|
|
948
|
+
.dashboard-content-area { flex: 1 1 auto; }
|
|
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; }
|
|
950
|
+
.session-collapse-count { color: var(--text-4); font-weight: 500; }
|
|
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; }
|
|
952
|
+
.sessions-list-filter a { color: var(--accent); text-decoration: none; }
|
|
953
|
+
.sessions-list-filter a:hover { text-decoration: underline; }
|
|
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); }
|
|
955
|
+
.session-list { list-style: none; padding: 0; margin: 0; }
|
|
569
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; }
|
|
570
957
|
.status-active { background: rgba(34,197,94,0.15); color: var(--success); }
|
|
571
958
|
.status-completed { background: rgba(96,165,250,0.15); color: var(--accent); }
|
|
@@ -599,13 +986,13 @@ button:hover { background: var(--surface-5); }
|
|
|
599
986
|
.session-children .session-subagent .session-chevron { display: none; }
|
|
600
987
|
.session-item.expanded > .session-children { display: block; }
|
|
601
988
|
.subagent-type-badge { font-size: 0.65rem; color: var(--text-3); margin-left: 0.35rem; padding: 0.08rem 0.3rem; background: var(--surface-2); border-radius: 0.2rem; }
|
|
602
|
-
.main-content { display:
|
|
603
|
-
.main-content:has(.session-detail-view) { grid-template-rows: 1fr; }
|
|
989
|
+
.main-content { display: flex; flex-direction: column; gap: 1.25rem; }
|
|
604
990
|
.main-metrics .metric-grid { grid-template-columns: repeat(4, 1fr); gap: 1rem; }
|
|
605
991
|
.main-metrics .metric-card { min-height: 5rem; }
|
|
606
992
|
.main-metrics .metric-value { font-size: 1.65rem; }
|
|
607
|
-
.top-charts { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem;
|
|
608
|
-
.chart-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 0.
|
|
993
|
+
.top-charts { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
|
|
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); }
|
|
609
996
|
.chart-title { font-size: 0.9rem; font-weight: 600; color: var(--text-2); margin: 0 0 0.75rem; flex: 0 0 auto; }
|
|
610
997
|
.chart-empty { color: var(--text-4); text-align: center; padding: 4rem 0; }
|
|
611
998
|
.chart-max { text-align: right; color: var(--text-4); font-size: 0.7rem; margin-top: 0.25rem; flex: 0 0 auto; }
|
|
@@ -616,15 +1003,19 @@ button:hover { background: var(--surface-5); }
|
|
|
616
1003
|
.event-chart text { fill: var(--text-3); font-size: 10px; }
|
|
617
1004
|
.event-chart .y-label { font-size: 9px; fill: var(--text-4); }
|
|
618
1005
|
.event-chart .grid { stroke: var(--surface-4); stroke-width: 1; }
|
|
619
|
-
.tool-chart { display: flex; flex-direction: column; gap: 0.65rem;
|
|
1006
|
+
.tool-chart { display: flex; flex-direction: column; gap: 0.65rem; }
|
|
620
1007
|
.tool-row { display: grid; grid-template-columns: 8rem 1fr 2.5rem; align-items: center; gap: 0.75rem; font-size: 0.8rem; }
|
|
1008
|
+
.chart-panel--wide-label .tool-row { grid-template-columns: 60% 1fr 2.5rem; }
|
|
1009
|
+
a.tool-row { text-decoration: none; color: inherit; border-radius: 0.3rem; padding: 0.15rem 0.3rem; margin: -0.15rem -0.3rem; transition: background 0.12s; }
|
|
1010
|
+
a.tool-row:hover { background: var(--surface-3); }
|
|
1011
|
+
a.tool-row:hover .tool-name { color: var(--accent); }
|
|
621
1012
|
.tool-name { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
622
1013
|
.tool-bar-bg { background: var(--surface-4); border-radius: 0.4rem; height: 0.85rem; overflow: hidden; }
|
|
623
1014
|
.tool-bar { background: linear-gradient(90deg, var(--accent), var(--accent-2)); height: 100%; border-radius: 0.4rem; }
|
|
624
1015
|
.tool-count { color: var(--text-3); text-align: right; font-weight: 600; }
|
|
625
|
-
.detail-area { background: var(--surface); border: 1px solid var(--border); border-radius: 0.
|
|
1016
|
+
.detail-area { background: var(--surface); border: 1px solid var(--border); border-radius: 0.85rem; padding: 1rem; box-shadow: var(--shadow-sm); }
|
|
626
1017
|
.detail-area > .empty { color: var(--text-4); text-align: center; padding: 4rem 0; }
|
|
627
|
-
.session-detail-view {
|
|
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); }
|
|
628
1019
|
.detail-header { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; flex: 0 0 auto; }
|
|
629
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; }
|
|
630
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; }
|
|
@@ -665,6 +1056,11 @@ button:hover { background: var(--surface-5); }
|
|
|
665
1056
|
.detail-attrs > div { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
|
|
666
1057
|
.detail-attrs > div span:first-child { color: var(--text-4); font-size: 0.7rem; text-transform: capitalize; }
|
|
667
1058
|
.detail-attrs > div span:last-child { color: var(--text); font-weight: 500; overflow: hidden; text-overflow: ellipsis; word-break: break-all; }
|
|
1059
|
+
.detail-summary { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.6rem; background: var(--surface-2); border-radius: 0.4rem; }
|
|
1060
|
+
.summary-group { display: flex; flex-direction: column; gap: 0.3rem; }
|
|
1061
|
+
.summary-label { color: var(--text-4); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.02em; }
|
|
1062
|
+
.summary-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
|
1063
|
+
.chip { font-size: 0.75rem; color: var(--text-2); background: var(--surface-4); border: 1px solid var(--border); padding: 0.1rem 0.45rem; border-radius: 0.25rem; max-width: 22rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
668
1064
|
.session-json { display: none; }
|
|
669
1065
|
.pager { display: flex; justify-content: center; align-items: center; gap: 0.75rem; padding: 0.75rem 1rem; border-top: 1px solid var(--border); background: var(--surface-2); flex: 0 0 auto; }
|
|
670
1066
|
.pager a, .pager button { background: var(--surface-4); color: var(--text); border: 1px solid var(--border); border-radius: 0.35rem; padding: 0.4rem 0.75rem; font-size: 0.85rem; text-decoration: none; }
|
|
@@ -672,7 +1068,8 @@ button:hover { background: var(--surface-5); }
|
|
|
672
1068
|
.pager button:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
673
1069
|
.pager-info { color: var(--text-3); font-size: 0.85rem; }
|
|
674
1070
|
.metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
|
|
675
|
-
.metric-card { position: relative; overflow: hidden; background: var(--surface-2); border: 1px solid var(--border); border-radius: 0.
|
|
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); }
|
|
676
1073
|
.metric-sessions .metric-icon { color: var(--accent); }
|
|
677
1074
|
.metric-events .metric-icon { color: var(--accent-2); }
|
|
678
1075
|
.metric-duration .metric-icon { color: var(--warning); }
|
|
@@ -680,9 +1077,20 @@ button:hover { background: var(--surface-5); }
|
|
|
680
1077
|
.metric-icon { position: absolute; top: 0.75rem; right: 0.75rem; opacity: 0.8; }
|
|
681
1078
|
.metric-label { font-size: 0.72rem; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }
|
|
682
1079
|
.metric-value { font-size: 1.75rem; font-weight: 800; color: var(--text); margin-top: 0.25rem; letter-spacing: -0.02em; }
|
|
1080
|
+
.context-breakdown { min-height: 0; }
|
|
1081
|
+
.context-bars { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
|
|
1082
|
+
.context-bar-block { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1083
|
+
.context-bar-title { font-size: 0.78rem; font-weight: 600; color: var(--text-3); margin: 0; }
|
|
1084
|
+
.context-bar { display: flex; height: 0.85rem; border-radius: 0.4rem; overflow: hidden; background: var(--surface-4); }
|
|
1085
|
+
.context-segment { height: 100%; }
|
|
1086
|
+
.context-legend { display: flex; flex-direction: column; gap: 0.3rem; }
|
|
1087
|
+
.context-legend-row { display: flex; align-items: center; gap: 0.4rem; font-size: 0.78rem; color: var(--text-2); }
|
|
1088
|
+
.context-legend-dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; flex: 0 0 auto; }
|
|
1089
|
+
.context-legend-label { flex: 1 1 auto; }
|
|
1090
|
+
.context-legend-value { color: var(--text-3); font-weight: 600; }
|
|
683
1091
|
.empty { color: var(--text-4); text-align: center; padding: 2rem; }
|
|
684
|
-
@media (max-width: 960px) { .
|
|
685
|
-
@media (max-width: 640px) { .app { padding: 0.75rem; } .app-header { flex-direction: column; align-items: flex-start; } .header-controls, .filter-
|
|
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; } }
|
|
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%; } }
|
|
686
1094
|
</style>
|
|
687
1095
|
<script src="https://unpkg.com/htmx.org@2.0.10/dist/htmx.min.js" integrity="sha256-cepnGFv6jJjDnTFxfG/OXYUjcPzf0SnbRUN3TTFFwN4=" crossorigin="anonymous"><\/script>
|
|
688
1096
|
<script src="https://unpkg.com/htmx-ext-sse@2.2.4/dist/sse.js" integrity="sha256-O1mSpUFhm6vvxMFpUFr0dN9cMDnaUeWblsz5JB7NYdI=" crossorigin="anonymous"><\/script>
|
|
@@ -691,20 +1099,21 @@ button:hover { background: var(--surface-5); }
|
|
|
691
1099
|
<div class="app">
|
|
692
1100
|
<div class="sticky-bar">
|
|
693
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">
|
|
694
|
-
${
|
|
1102
|
+
<input type="hidden" name="view" value="${t.view===`list`?`list`:``}">
|
|
1103
|
+
${i}${a}
|
|
695
1104
|
</form>
|
|
696
1105
|
</div>
|
|
697
|
-
<div id="dashboard-content" class="dashboard-
|
|
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">
|
|
698
1107
|
${r}
|
|
699
1108
|
</div>
|
|
700
1109
|
</div>
|
|
701
1110
|
</body>
|
|
702
|
-
</html>`}function
|
|
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
|
|
703
1112
|
|
|
704
|
-
`);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)?
|
|
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
|
|
705
1114
|
data: ping
|
|
706
1115
|
|
|
707
|
-
`),a.lastId=e)}catch(e){console.error(`SSE poll error:`,e),clearInterval(a.timer),
|
|
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]
|
|
708
1117
|
|
|
709
1118
|
Options:
|
|
710
1119
|
--source <source> filter by source (cursor, claude, ...)
|
|
@@ -719,7 +1128,7 @@ Options:
|
|
|
719
1128
|
--format <format> output format: json (default), jsonl, summary
|
|
720
1129
|
--db <path> database path (default: HAPPENIN_DB or ~/.happenin/happenin.db)
|
|
721
1130
|
-h, --help show this help
|
|
722
|
-
`)}function
|
|
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]
|
|
723
1132
|
|
|
724
1133
|
Options:
|
|
725
1134
|
--source <source> filter by source (cursor, claude, ...)
|
|
@@ -737,8 +1146,8 @@ Options:
|
|
|
737
1146
|
--format <format> output format: json (default), jsonl, summary
|
|
738
1147
|
--db <path> database path (default: HAPPENIN_DB or ~/.happenin/happenin.db)
|
|
739
1148
|
-h, --help show this help
|
|
740
|
-
`)}function
|
|
741
|
-
`)}async function
|
|
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
|
|
742
1151
|
|
|
743
1152
|
Run \`happenin <command>\`.
|
|
744
|
-
`}c.stdout.write(`\n${
|
|
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{};
|