@arcreflex/agent-transcripts 0.1.10 → 0.1.12
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/CLAUDE.md +3 -1
- package/README.md +60 -53
- package/package.json +1 -1
- package/src/adapters/claude-code.ts +1 -0
- package/src/adapters/index.ts +0 -6
- package/src/archive.ts +267 -0
- package/src/cli.ts +96 -63
- package/src/convert.ts +19 -86
- package/src/parse.ts +0 -3
- package/src/render-html.ts +38 -195
- package/src/render-index.ts +15 -178
- package/src/render.ts +25 -88
- package/src/serve.ts +124 -215
- package/src/title.ts +24 -102
- package/src/types.ts +3 -0
- package/src/utils/naming.ts +8 -13
- package/src/utils/summary.ts +1 -4
- package/src/utils/text.ts +5 -0
- package/src/utils/theme.ts +152 -0
- package/src/utils/tree.ts +85 -1
- package/src/watch.ts +111 -0
- package/test/archive.test.ts +264 -0
- package/test/fixtures/claude/branching.input.jsonl +6 -0
- package/test/fixtures/claude/branching.output.md +25 -0
- package/test/naming.test.ts +98 -0
- package/test/summary.test.ts +144 -0
- package/test/tree.test.ts +217 -0
- package/tsconfig.json +1 -1
- package/src/cache.ts +0 -129
- package/src/sync.ts +0 -295
- package/src/utils/provenance.ts +0 -212
package/src/render-html.ts
CHANGED
|
@@ -11,13 +11,15 @@
|
|
|
11
11
|
|
|
12
12
|
import type { Transcript, Message, ToolCall } from "./types.ts";
|
|
13
13
|
import { createHighlighter, type Highlighter } from "shiki";
|
|
14
|
-
import {
|
|
15
|
-
buildTree,
|
|
16
|
-
findLatestLeaf,
|
|
17
|
-
tracePath,
|
|
18
|
-
getFirstLine,
|
|
19
|
-
} from "./utils/tree.ts";
|
|
14
|
+
import { walkTranscriptTree } from "./utils/tree.ts";
|
|
20
15
|
import { escapeHtml } from "./utils/html.ts";
|
|
16
|
+
import {
|
|
17
|
+
THEME_VARS,
|
|
18
|
+
BASE_RESET,
|
|
19
|
+
SCROLLBAR_STYLES,
|
|
20
|
+
accentBar,
|
|
21
|
+
responsiveBase,
|
|
22
|
+
} from "./utils/theme.ts";
|
|
21
23
|
|
|
22
24
|
// Lazy-loaded shiki highlighter
|
|
23
25
|
let highlighter: Highlighter | null = null;
|
|
@@ -39,32 +41,11 @@ async function getHighlighter(): Promise<Highlighter> {
|
|
|
39
41
|
const STYLES = `
|
|
40
42
|
/* ============================================================================
|
|
41
43
|
Agent Transcripts - Terminal Chronicle Theme
|
|
42
|
-
Inspired by the Claude Code TUI: dark, focused, monospace-forward
|
|
43
44
|
============================================================================ */
|
|
44
|
-
|
|
45
|
-
@import url('https://fonts.googleapis.com/css2?family=Berkeley+Mono:wght@400;500&family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&display=swap');
|
|
45
|
+
${THEME_VARS}
|
|
46
46
|
|
|
47
47
|
:root {
|
|
48
|
-
/*
|
|
49
|
-
--font-mono: 'Berkeley Mono', 'IBM Plex Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace;
|
|
50
|
-
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
51
|
-
|
|
52
|
-
/* Dark theme - Terminal aesthetic */
|
|
53
|
-
--bg: #0d0d0d;
|
|
54
|
-
--bg-elevated: #141414;
|
|
55
|
-
--bg-surface: #1a1a1a;
|
|
56
|
-
--fg: #e4e4e4;
|
|
57
|
-
--fg-secondary: #a3a3a3;
|
|
58
|
-
--muted: #666666;
|
|
59
|
-
--border: #2a2a2a;
|
|
60
|
-
--border-subtle: #222222;
|
|
61
|
-
|
|
62
|
-
/* Accent - Amber/Orange (Claude Code cursor vibe) */
|
|
63
|
-
--accent: #f59e0b;
|
|
64
|
-
--accent-dim: #b45309;
|
|
65
|
-
--accent-glow: rgba(245, 158, 11, 0.15);
|
|
66
|
-
|
|
67
|
-
/* Semantic colors */
|
|
48
|
+
/* Semantic colors (transcript-specific) */
|
|
68
49
|
--user-accent: #3b82f6;
|
|
69
50
|
--user-bg: rgba(59, 130, 246, 0.08);
|
|
70
51
|
--user-border: rgba(59, 130, 246, 0.3);
|
|
@@ -87,32 +68,11 @@ const STYLES = `
|
|
|
87
68
|
--thinking-border: #1f1f1f;
|
|
88
69
|
--raw-bg: #0a0a0a;
|
|
89
70
|
|
|
90
|
-
/* Links */
|
|
91
|
-
--link: #60a5fa;
|
|
92
|
-
--link-hover: #93c5fd;
|
|
93
|
-
|
|
94
|
-
/* Shadows & effects */
|
|
95
|
-
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
96
|
-
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
97
71
|
--glow: 0 0 20px var(--accent-glow);
|
|
98
72
|
}
|
|
99
73
|
|
|
100
|
-
/* Light theme - Minimal, paper-like */
|
|
101
74
|
@media (prefers-color-scheme: light) {
|
|
102
75
|
:root {
|
|
103
|
-
--bg: #fafafa;
|
|
104
|
-
--bg-elevated: #ffffff;
|
|
105
|
-
--bg-surface: #f5f5f5;
|
|
106
|
-
--fg: #171717;
|
|
107
|
-
--fg-secondary: #525252;
|
|
108
|
-
--muted: #a3a3a3;
|
|
109
|
-
--border: #e5e5e5;
|
|
110
|
-
--border-subtle: #f0f0f0;
|
|
111
|
-
|
|
112
|
-
--accent: #d97706;
|
|
113
|
-
--accent-dim: #92400e;
|
|
114
|
-
--accent-glow: rgba(217, 119, 6, 0.1);
|
|
115
|
-
|
|
116
76
|
--user-accent: #2563eb;
|
|
117
77
|
--user-bg: rgba(37, 99, 235, 0.04);
|
|
118
78
|
--user-border: rgba(37, 99, 235, 0.2);
|
|
@@ -134,32 +94,10 @@ const STYLES = `
|
|
|
134
94
|
--thinking-border: #e5e5e5;
|
|
135
95
|
--raw-bg: #f0f0f0;
|
|
136
96
|
|
|
137
|
-
--link: #2563eb;
|
|
138
|
-
--link-hover: #1d4ed8;
|
|
139
|
-
|
|
140
|
-
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
141
|
-
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
142
97
|
--glow: none;
|
|
143
98
|
}
|
|
144
99
|
}
|
|
145
|
-
|
|
146
|
-
*, *::before, *::after { box-sizing: border-box; }
|
|
147
|
-
|
|
148
|
-
html {
|
|
149
|
-
font-size: 15px;
|
|
150
|
-
-webkit-font-smoothing: antialiased;
|
|
151
|
-
-moz-osx-font-smoothing: grayscale;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
body {
|
|
155
|
-
font-family: var(--font-body);
|
|
156
|
-
background: var(--bg);
|
|
157
|
-
color: var(--fg);
|
|
158
|
-
line-height: 1.65;
|
|
159
|
-
margin: 0;
|
|
160
|
-
padding: 0;
|
|
161
|
-
min-height: 100vh;
|
|
162
|
-
}
|
|
100
|
+
${BASE_RESET}
|
|
163
101
|
|
|
164
102
|
/* Main container */
|
|
165
103
|
.transcript-container {
|
|
@@ -168,35 +106,7 @@ body {
|
|
|
168
106
|
padding: 2.5rem 2rem 4rem;
|
|
169
107
|
position: relative;
|
|
170
108
|
}
|
|
171
|
-
|
|
172
|
-
/* Subtle left border accent */
|
|
173
|
-
.transcript-container::before {
|
|
174
|
-
content: '';
|
|
175
|
-
position: fixed;
|
|
176
|
-
left: 0;
|
|
177
|
-
top: 0;
|
|
178
|
-
bottom: 0;
|
|
179
|
-
width: 2px;
|
|
180
|
-
background: linear-gradient(
|
|
181
|
-
180deg,
|
|
182
|
-
transparent 0%,
|
|
183
|
-
var(--accent-dim) 15%,
|
|
184
|
-
var(--accent) 50%,
|
|
185
|
-
var(--accent-dim) 85%,
|
|
186
|
-
transparent 100%
|
|
187
|
-
);
|
|
188
|
-
opacity: 0.6;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
a {
|
|
192
|
-
color: var(--link);
|
|
193
|
-
text-decoration: none;
|
|
194
|
-
transition: color 0.15s ease;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
a:hover {
|
|
198
|
-
color: var(--link-hover);
|
|
199
|
-
}
|
|
109
|
+
${accentBar("transcript-container")}
|
|
200
110
|
|
|
201
111
|
/* ============================================================================
|
|
202
112
|
Header - Terminal prompt style
|
|
@@ -678,45 +588,10 @@ details.tool-call[open] > .tool-call-header::before {
|
|
|
678
588
|
display: none;
|
|
679
589
|
}
|
|
680
590
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
============================================================================ */
|
|
684
|
-
|
|
685
|
-
::-webkit-scrollbar {
|
|
686
|
-
width: 6px;
|
|
687
|
-
height: 6px;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
::-webkit-scrollbar-track {
|
|
691
|
-
background: var(--border-subtle);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
::-webkit-scrollbar-thumb {
|
|
695
|
-
background: var(--muted);
|
|
696
|
-
border-radius: 3px;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
::-webkit-scrollbar-thumb:hover {
|
|
700
|
-
background: var(--fg-secondary);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
/* ============================================================================
|
|
704
|
-
Responsive
|
|
705
|
-
============================================================================ */
|
|
591
|
+
${SCROLLBAR_STYLES}
|
|
592
|
+
${responsiveBase("transcript-container")}
|
|
706
593
|
|
|
707
594
|
@media (max-width: 640px) {
|
|
708
|
-
html {
|
|
709
|
-
font-size: 14px;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
.transcript-container {
|
|
713
|
-
padding: 1.5rem 1rem 3rem;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.transcript-container::before {
|
|
717
|
-
display: none;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
595
|
header h1 {
|
|
721
596
|
font-size: 1rem;
|
|
722
597
|
}
|
|
@@ -796,9 +671,6 @@ const SCRIPT = `
|
|
|
796
671
|
// HTML Utilities
|
|
797
672
|
// ============================================================================
|
|
798
673
|
|
|
799
|
-
/**
|
|
800
|
-
* Format JSON for display with indentation.
|
|
801
|
-
*/
|
|
802
674
|
function formatJson(obj: unknown): string {
|
|
803
675
|
try {
|
|
804
676
|
return JSON.stringify(obj, null, 2);
|
|
@@ -865,9 +737,14 @@ async function renderMessage(
|
|
|
865
737
|
msg: Message,
|
|
866
738
|
ctx: RenderContext,
|
|
867
739
|
): Promise<string> {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
740
|
+
let rawJson = "";
|
|
741
|
+
if (msg.rawJson) {
|
|
742
|
+
try {
|
|
743
|
+
rawJson = escapeHtml(formatJson(JSON.parse(msg.rawJson)));
|
|
744
|
+
} catch {
|
|
745
|
+
rawJson = escapeHtml(msg.rawJson);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
871
748
|
|
|
872
749
|
switch (msg.type) {
|
|
873
750
|
case "user":
|
|
@@ -1000,77 +877,43 @@ export async function renderTranscriptHtml(
|
|
|
1000
877
|
|
|
1001
878
|
// Build messages section
|
|
1002
879
|
let messagesHtml = "";
|
|
880
|
+
let inAssistantTurn = false;
|
|
1003
881
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
882
|
+
for (const event of walkTranscriptTree(transcript, { head })) {
|
|
883
|
+
switch (event.type) {
|
|
884
|
+
case "empty":
|
|
885
|
+
messagesHtml = "<p><em>No messages in this transcript.</em></p>";
|
|
886
|
+
break;
|
|
1008
887
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
messagesHtml = `<p class="error">Message ID <code>${escapeHtml(head)}</code> not found</p>`;
|
|
1013
|
-
} else {
|
|
1014
|
-
target = head;
|
|
1015
|
-
}
|
|
1016
|
-
} else {
|
|
1017
|
-
target = findLatestLeaf(bySourceRef, children);
|
|
1018
|
-
}
|
|
888
|
+
case "head_not_found":
|
|
889
|
+
messagesHtml = `<p class="error">Message ID <code>${escapeHtml(event.head)}</code> not found</p>`;
|
|
890
|
+
break;
|
|
1019
891
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const pathSet = new Set(path);
|
|
1023
|
-
let inAssistantTurn = false;
|
|
1024
|
-
|
|
1025
|
-
for (const sourceRef of path) {
|
|
1026
|
-
const msgs = bySourceRef.get(sourceRef);
|
|
1027
|
-
if (!msgs) continue;
|
|
1028
|
-
|
|
1029
|
-
for (const msg of msgs) {
|
|
1030
|
-
// Track when we enter/exit assistant turns
|
|
892
|
+
case "messages":
|
|
893
|
+
for (const msg of event.messages) {
|
|
1031
894
|
const isAssistantContent =
|
|
1032
895
|
msg.type === "assistant" || msg.type === "tool_calls";
|
|
1033
|
-
|
|
1034
|
-
// Show header only at the START of an assistant turn (after user)
|
|
1035
896
|
const showAssistantHeader = isAssistantContent && !inAssistantTurn;
|
|
1036
897
|
|
|
1037
898
|
messagesHtml += await renderMessage(msg, { showAssistantHeader });
|
|
1038
899
|
|
|
1039
|
-
// Update turn state
|
|
1040
900
|
if (msg.type === "user") {
|
|
1041
901
|
inAssistantTurn = false;
|
|
1042
902
|
} else if (isAssistantContent) {
|
|
1043
903
|
inAssistantTurn = true;
|
|
1044
904
|
}
|
|
1045
905
|
}
|
|
906
|
+
break;
|
|
1046
907
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const childSet = children.get(sourceRef);
|
|
1050
|
-
if (childSet && childSet.size > 1) {
|
|
1051
|
-
const otherBranches = [...childSet].filter((c) => !pathSet.has(c));
|
|
1052
|
-
if (otherBranches.length > 0) {
|
|
1053
|
-
messagesHtml += `
|
|
908
|
+
case "branch_note":
|
|
909
|
+
messagesHtml += `
|
|
1054
910
|
<div class="branch-note">
|
|
1055
911
|
<strong>Other branches:</strong>
|
|
1056
912
|
<ul>
|
|
1057
|
-
${
|
|
1058
|
-
.map((branchRef) => {
|
|
1059
|
-
const branchMsgs = bySourceRef.get(branchRef);
|
|
1060
|
-
if (branchMsgs && branchMsgs.length > 0) {
|
|
1061
|
-
const firstLine = getFirstLine(branchMsgs[0]);
|
|
1062
|
-
return `<li><code>${escapeHtml(branchRef)}</code> "${escapeHtml(firstLine)}"</li>`;
|
|
1063
|
-
}
|
|
1064
|
-
return "";
|
|
1065
|
-
})
|
|
1066
|
-
.filter(Boolean)
|
|
1067
|
-
.join("\n ")}
|
|
913
|
+
${event.branches.map((b) => `<li><code>${escapeHtml(b.sourceRef)}</code> "${escapeHtml(b.firstLine)}"</li>`).join("\n ")}
|
|
1068
914
|
</ul>
|
|
1069
915
|
</div>`;
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
916
|
+
break;
|
|
1074
917
|
}
|
|
1075
918
|
}
|
|
1076
919
|
|
package/src/render-index.ts
CHANGED
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
* - Data embedded inline (works with file://)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { TranscriptsIndex } from "./utils/provenance.ts";
|
|
11
10
|
import { escapeHtml } from "./utils/html.ts";
|
|
11
|
+
import { truncate } from "./utils/text.ts";
|
|
12
|
+
import {
|
|
13
|
+
THEME_VARS,
|
|
14
|
+
BASE_RESET,
|
|
15
|
+
SCROLLBAR_STYLES,
|
|
16
|
+
accentBar,
|
|
17
|
+
responsiveBase,
|
|
18
|
+
} from "./utils/theme.ts";
|
|
12
19
|
|
|
13
20
|
// ============================================================================
|
|
14
21
|
// Styles - Terminal Chronicle Theme (Index)
|
|
@@ -18,98 +25,35 @@ const INDEX_STYLES = `
|
|
|
18
25
|
/* ============================================================================
|
|
19
26
|
Agent Transcripts Index - Terminal Chronicle Theme
|
|
20
27
|
============================================================================ */
|
|
21
|
-
|
|
22
|
-
@import url('https://fonts.googleapis.com/css2?family=Berkeley+Mono:wght@400;500&family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&display=swap');
|
|
28
|
+
${THEME_VARS}
|
|
23
29
|
|
|
24
30
|
:root {
|
|
25
|
-
/*
|
|
26
|
-
--font-mono: 'Berkeley Mono', 'IBM Plex Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace;
|
|
27
|
-
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
28
|
-
|
|
29
|
-
/* Dark theme */
|
|
30
|
-
--bg: #0d0d0d;
|
|
31
|
-
--bg-elevated: #141414;
|
|
32
|
-
--bg-surface: #1a1a1a;
|
|
33
|
-
--fg: #e4e4e4;
|
|
34
|
-
--fg-secondary: #a3a3a3;
|
|
35
|
-
--muted: #666666;
|
|
36
|
-
--border: #2a2a2a;
|
|
37
|
-
--border-subtle: #222222;
|
|
38
|
-
|
|
39
|
-
/* Accent */
|
|
40
|
-
--accent: #f59e0b;
|
|
41
|
-
--accent-dim: #b45309;
|
|
42
|
-
--accent-glow: rgba(245, 158, 11, 0.15);
|
|
43
|
-
|
|
44
|
-
/* Cards */
|
|
31
|
+
/* Index-specific tokens */
|
|
45
32
|
--card-bg: var(--bg-elevated);
|
|
46
33
|
--card-hover: var(--bg-surface);
|
|
47
34
|
--card-border: var(--border);
|
|
48
35
|
--card-border-hover: #3a3a3a;
|
|
49
36
|
|
|
50
|
-
/* Links */
|
|
51
|
-
--link: #60a5fa;
|
|
52
|
-
--link-hover: #93c5fd;
|
|
53
|
-
|
|
54
|
-
/* Input */
|
|
55
37
|
--input-bg: var(--bg-elevated);
|
|
56
38
|
--input-border: var(--border);
|
|
57
39
|
--input-focus: var(--accent);
|
|
58
|
-
|
|
59
|
-
/* Effects */
|
|
60
|
-
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
61
|
-
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
62
40
|
}
|
|
63
41
|
|
|
64
42
|
@media (prefers-color-scheme: light) {
|
|
65
43
|
:root {
|
|
66
|
-
--bg: #fafafa;
|
|
67
|
-
--bg-elevated: #ffffff;
|
|
68
|
-
--bg-surface: #f5f5f5;
|
|
69
|
-
--fg: #171717;
|
|
70
|
-
--fg-secondary: #525252;
|
|
71
|
-
--muted: #a3a3a3;
|
|
72
|
-
--border: #e5e5e5;
|
|
73
|
-
--border-subtle: #f0f0f0;
|
|
74
|
-
|
|
75
|
-
--accent: #d97706;
|
|
76
|
-
--accent-dim: #92400e;
|
|
77
|
-
--accent-glow: rgba(217, 119, 6, 0.1);
|
|
78
|
-
|
|
79
44
|
--card-bg: var(--bg-elevated);
|
|
80
45
|
--card-hover: var(--bg-surface);
|
|
81
46
|
--card-border: var(--border);
|
|
82
47
|
--card-border-hover: #d4d4d4;
|
|
83
48
|
|
|
84
|
-
--link: #2563eb;
|
|
85
|
-
--link-hover: #1d4ed8;
|
|
86
|
-
|
|
87
49
|
--input-bg: var(--bg-elevated);
|
|
88
50
|
--input-border: var(--border);
|
|
89
51
|
--input-focus: var(--accent);
|
|
90
|
-
|
|
91
|
-
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
92
|
-
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
93
52
|
}
|
|
94
53
|
}
|
|
54
|
+
${BASE_RESET}
|
|
95
55
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
html {
|
|
99
|
-
font-size: 15px;
|
|
100
|
-
-webkit-font-smoothing: antialiased;
|
|
101
|
-
-moz-osx-font-smoothing: grayscale;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
body {
|
|
105
|
-
font-family: var(--font-body);
|
|
106
|
-
background: var(--bg);
|
|
107
|
-
color: var(--fg);
|
|
108
|
-
line-height: 1.6;
|
|
109
|
-
margin: 0;
|
|
110
|
-
padding: 0;
|
|
111
|
-
min-height: 100vh;
|
|
112
|
-
}
|
|
56
|
+
body { line-height: 1.6; }
|
|
113
57
|
|
|
114
58
|
.index-container {
|
|
115
59
|
max-width: 54rem;
|
|
@@ -117,35 +61,7 @@ body {
|
|
|
117
61
|
padding: 2.5rem 2rem 4rem;
|
|
118
62
|
position: relative;
|
|
119
63
|
}
|
|
120
|
-
|
|
121
|
-
/* Subtle accent bar */
|
|
122
|
-
.index-container::before {
|
|
123
|
-
content: '';
|
|
124
|
-
position: fixed;
|
|
125
|
-
left: 0;
|
|
126
|
-
top: 0;
|
|
127
|
-
bottom: 0;
|
|
128
|
-
width: 2px;
|
|
129
|
-
background: linear-gradient(
|
|
130
|
-
180deg,
|
|
131
|
-
transparent 0%,
|
|
132
|
-
var(--accent-dim) 15%,
|
|
133
|
-
var(--accent) 50%,
|
|
134
|
-
var(--accent-dim) 85%,
|
|
135
|
-
transparent 100%
|
|
136
|
-
);
|
|
137
|
-
opacity: 0.6;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
a {
|
|
141
|
-
color: var(--link);
|
|
142
|
-
text-decoration: none;
|
|
143
|
-
transition: color 0.15s ease;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
a:hover {
|
|
147
|
-
color: var(--link-hover);
|
|
148
|
-
}
|
|
64
|
+
${accentBar("index-container")}
|
|
149
65
|
|
|
150
66
|
/* ============================================================================
|
|
151
67
|
Header
|
|
@@ -314,45 +230,10 @@ header h1::before {
|
|
|
314
230
|
margin: 0;
|
|
315
231
|
}
|
|
316
232
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
============================================================================ */
|
|
320
|
-
|
|
321
|
-
::-webkit-scrollbar {
|
|
322
|
-
width: 6px;
|
|
323
|
-
height: 6px;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
::-webkit-scrollbar-track {
|
|
327
|
-
background: var(--border-subtle);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
::-webkit-scrollbar-thumb {
|
|
331
|
-
background: var(--muted);
|
|
332
|
-
border-radius: 3px;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
::-webkit-scrollbar-thumb:hover {
|
|
336
|
-
background: var(--fg-secondary);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/* ============================================================================
|
|
340
|
-
Responsive
|
|
341
|
-
============================================================================ */
|
|
233
|
+
${SCROLLBAR_STYLES}
|
|
234
|
+
${responsiveBase("index-container")}
|
|
342
235
|
|
|
343
236
|
@media (max-width: 640px) {
|
|
344
|
-
html {
|
|
345
|
-
font-size: 14px;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
.index-container {
|
|
349
|
-
padding: 1.5rem 1rem 3rem;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.index-container::before {
|
|
353
|
-
display: none;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
237
|
header h1 {
|
|
357
238
|
font-size: 1.125rem;
|
|
358
239
|
}
|
|
@@ -411,26 +292,6 @@ const INDEX_SCRIPT = `
|
|
|
411
292
|
// Helpers
|
|
412
293
|
// ============================================================================
|
|
413
294
|
|
|
414
|
-
function truncate(text: string, maxLen: number): string {
|
|
415
|
-
if (text.length <= maxLen) return text;
|
|
416
|
-
return text.slice(0, maxLen).trim() + "...";
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function formatDate(isoString: string): string {
|
|
420
|
-
try {
|
|
421
|
-
const date = new Date(isoString);
|
|
422
|
-
return date.toLocaleDateString("en-US", {
|
|
423
|
-
year: "numeric",
|
|
424
|
-
month: "short",
|
|
425
|
-
day: "numeric",
|
|
426
|
-
hour: "2-digit",
|
|
427
|
-
minute: "2-digit",
|
|
428
|
-
});
|
|
429
|
-
} catch {
|
|
430
|
-
return isoString;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
295
|
/**
|
|
435
296
|
* Format a date range compactly, e.g., "1/25 4:00-6:30"
|
|
436
297
|
*/
|
|
@@ -585,27 +446,3 @@ export function renderIndexFromSessions(
|
|
|
585
446
|
</body>
|
|
586
447
|
</html>`;
|
|
587
448
|
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Render index.html from transcripts.json data.
|
|
591
|
-
* Convenience wrapper around renderIndexFromSessions.
|
|
592
|
-
*/
|
|
593
|
-
export function renderIndex(
|
|
594
|
-
index: TranscriptsIndex,
|
|
595
|
-
options: RenderIndexOptions = {},
|
|
596
|
-
): string {
|
|
597
|
-
const sessions: SessionEntry[] = Object.entries(index.entries)
|
|
598
|
-
.filter(([filename]) => filename.endsWith(".html"))
|
|
599
|
-
.map(([filename, entry]) => ({
|
|
600
|
-
filename,
|
|
601
|
-
title:
|
|
602
|
-
entry.title || truncate(entry.firstUserMessage, 80) || entry.sessionId,
|
|
603
|
-
firstUserMessage: entry.firstUserMessage,
|
|
604
|
-
date: entry.startTime,
|
|
605
|
-
endDate: entry.endTime,
|
|
606
|
-
messageCount: entry.messageCount,
|
|
607
|
-
cwd: entry.cwd,
|
|
608
|
-
}));
|
|
609
|
-
|
|
610
|
-
return renderIndexFromSessions(sessions, options);
|
|
611
|
-
}
|