@sickr/cli 0.9.2
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/dist/agentAuth.js +76 -0
- package/dist/auth.js +52 -0
- package/dist/cli.js +1109 -0
- package/dist/hookConfig.js +49 -0
- package/dist/live.js +392 -0
- package/dist/recorder.js +154 -0
- package/dist/redact.js +132 -0
- package/dist/render.js +528 -0
- package/dist/share.js +30 -0
- package/dist/ui.js +63 -0
- package/package.json +22 -0
package/dist/render.js
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
// NOTE: this local viewer is deliberately kept in visual + behavioural parity
|
|
2
|
+
// with the public share page (sickr-ui/functions/r/[id].ts). Same atmosphere,
|
|
3
|
+
// sticky rails, find (prev/next), jump controls and bottom bar — only the rail
|
|
4
|
+
// copy differs (local context vs. public "capture your own"). If you change one,
|
|
5
|
+
// change the other.
|
|
6
|
+
const PLASMA = '#34e0ff';
|
|
7
|
+
function esc(s) {
|
|
8
|
+
return s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
9
|
+
}
|
|
10
|
+
// Inline Sickr wordmark (mirrors src/components/SickrLogo.tsx) — static ids.
|
|
11
|
+
function wordmark(idPrefix) {
|
|
12
|
+
return `<svg viewBox="0 0 1105 395" role="img" aria-label="Sickr" class="logo" shape-rendering="geometricPrecision">
|
|
13
|
+
<defs>
|
|
14
|
+
<linearGradient id="${idPrefix}-silver" x1="0" y1="0" x2="1" y2="1">
|
|
15
|
+
<stop offset="0%" stop-color="#ffffff"/><stop offset="36%" stop-color="#f6f8fb"/>
|
|
16
|
+
<stop offset="58%" stop-color="#c7cedb"/><stop offset="78%" stop-color="#ffffff"/><stop offset="100%" stop-color="#dde3ee"/>
|
|
17
|
+
</linearGradient>
|
|
18
|
+
<filter id="${idPrefix}-fx" x="-10%" y="-12%" width="120%" height="124%">
|
|
19
|
+
<feGaussianBlur stdDeviation="5" result="b"/>
|
|
20
|
+
<feColorMatrix in="b" type="matrix" values="0 0 0 0 0.20 0 0 0 0 0.88 0 0 0 0 1 0 0 0 0.5 0" result="g"/>
|
|
21
|
+
<feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
22
|
+
</filter>
|
|
23
|
+
<mask id="${idPrefix}-s" maskUnits="userSpaceOnUse"><rect width="1105" height="395" fill="white"/><polygon points="101,258 270,145 202,204 161,213" fill="black"/></mask>
|
|
24
|
+
<mask id="${idPrefix}-i" maskUnits="userSpaceOnUse"><rect width="1105" height="395" fill="white"/><polygon points="361,260 396,229 396,252 361,283" fill="black"/></mask>
|
|
25
|
+
<mask id="${idPrefix}-c" maskUnits="userSpaceOnUse"><rect width="1105" height="395" fill="white"/><polygon points="458,272 493,244 504,262 469,290" fill="black"/></mask>
|
|
26
|
+
<mask id="${idPrefix}-r" maskUnits="userSpaceOnUse"><rect width="1105" height="395" fill="white"/><polygon points="888,266 920,238 920,261 888,289" fill="black"/></mask>
|
|
27
|
+
</defs>
|
|
28
|
+
<g fill="url(#${idPrefix}-silver)" filter="url(#${idPrefix}-fx)">
|
|
29
|
+
<path mask="url(#${idPrefix}-s)" d="M 70 324 L 110 296 L 221 296 C 243 296 258 282 258 260 C 258 238 247 223 224 207 L 244 188 C 272 204 286 227 286 258 C 286 297 260 324 228 324 Z M 136 213 C 109 199 96 179 96 153 C 96 109 122 77 156 77 L 296 77 L 262 106 L 158 106 C 138 106 124 125 124 153 C 124 171 134 184 157 195 Z"/>
|
|
30
|
+
<polygon points="268,146 200,203 109,256 162,211"/>
|
|
31
|
+
<circle cx="378" cy="116" r="19"/>
|
|
32
|
+
<path mask="url(#${idPrefix}-i)" d="M 366 182 L 395 155 L 395 299 L 366 324 Z"/>
|
|
33
|
+
<path mask="url(#${idPrefix}-c)" d="M 623 153 L 587 182 L 530 182 C 504 182 486 204 486 234 C 486 264 504 285 529 285 L 625 285 L 591 314 L 520 314 C 482 314 457 282 457 237 C 457 191 482 153 518 153 Z"/>
|
|
34
|
+
<path d="M 684 98 L 714 70 L 714 200 L 684 227 Z M 684 254 L 796 151 L 837 151 L 714 265 L 714 294 L 684 320 Z M 764 238 L 841 313 L 802 313 L 744 256 Z"/>
|
|
35
|
+
<path mask="url(#${idPrefix}-r)" d="M 889 321 L 889 193 C 889 168 906 153 927 153 L 1034 153 L 998 182 L 934 182 C 924 182 918 189 918 199 L 918 295 Z"/>
|
|
36
|
+
</g>
|
|
37
|
+
</svg>`;
|
|
38
|
+
}
|
|
39
|
+
const STYLES = `
|
|
40
|
+
:root{--plasma:${PLASMA};--ink:#06080d}
|
|
41
|
+
*{box-sizing:border-box}
|
|
42
|
+
html{scroll-behavior:smooth}
|
|
43
|
+
body{margin:0;background:var(--ink);color:#e7ecf3;font-family:Sora,system-ui,Arial,sans-serif;min-height:100vh;position:relative;overflow-x:hidden}
|
|
44
|
+
.bg{position:fixed;inset:0;z-index:0;pointer-events:none}
|
|
45
|
+
.bg-grid{position:absolute;inset:0;opacity:.55;background-image:linear-gradient(rgba(255,255,255,.035) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.035) 1px,transparent 1px);background-size:46px 46px;mask-image:radial-gradient(ellipse at 50% 0%,#000 40%,transparent 85%)}
|
|
46
|
+
.glow{position:absolute;border-radius:50%;filter:blur(120px)}
|
|
47
|
+
.glow-a{top:-200px;left:50%;transform:translateX(-50%);width:840px;height:520px;background:radial-gradient(closest-side,rgba(52,224,255,.16),transparent 70%)}
|
|
48
|
+
.glow-b{bottom:-220px;right:-140px;width:560px;height:440px;background:radial-gradient(closest-side,rgba(99,102,241,.13),transparent 70%)}
|
|
49
|
+
.wrap{position:relative;z-index:1;max-width:1180px;margin:0 auto;padding:26px 24px 72px}
|
|
50
|
+
a{color:var(--plasma);text-decoration:none}
|
|
51
|
+
.bar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:6px 0 30px}
|
|
52
|
+
.logo{height:32px;width:auto;display:block}
|
|
53
|
+
.bar-cta{font-family:"JetBrains Mono",monospace;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--plasma)}
|
|
54
|
+
.label{font-family:"JetBrains Mono",monospace;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:#5f6b80}
|
|
55
|
+
.layout{display:grid;gap:30px;grid-template-columns:1fr;align-items:start}
|
|
56
|
+
@media(min-width:980px){.layout{grid-template-columns:236px minmax(0,1fr) 290px}.layout aside{position:sticky;top:24px;align-self:start;max-height:calc(100vh - 48px);overflow:auto}}
|
|
57
|
+
.panel{border:1px solid #16202f;border-radius:12px;background:rgba(8,12,20,.55);padding:18px}
|
|
58
|
+
.rail h3{font-family:"Chakra Petch","Sora",sans-serif;font-weight:700;font-size:15px;color:#fff;margin:0 0 8px}
|
|
59
|
+
.rail p{font-size:13px;line-height:1.6;color:#9aa6b6;margin:0 0 10px}
|
|
60
|
+
.meta-row{display:flex;justify-content:space-between;gap:10px;font-family:"JetBrains Mono",monospace;font-size:11px;color:#6b7689;padding:6px 0;border-top:1px solid #111a27}
|
|
61
|
+
.meta-row b{color:#cdd5e1;font-weight:500;word-break:break-all;text-align:right}
|
|
62
|
+
h1.title{font-family:"Chakra Petch","Sora",sans-serif;font-weight:700;font-size:26px;line-height:1.1;letter-spacing:-.01em;color:#fff;margin:0 0 4px}
|
|
63
|
+
.title .hl{color:var(--plasma);text-shadow:0 0 22px rgba(52,224,255,.45)}
|
|
64
|
+
.sub{font-family:"JetBrains Mono",monospace;font-size:12px;color:#5f6b80;margin:0 0 22px}
|
|
65
|
+
ol.tl{list-style:none;margin:0;padding:0;position:relative}
|
|
66
|
+
ol.tl::before{content:"";position:absolute;left:6px;top:8px;bottom:8px;width:1px;background:linear-gradient(var(--plasma),transparent)}
|
|
67
|
+
ol.tl li{position:relative;padding:0 0 20px 30px}
|
|
68
|
+
.dot{position:absolute;left:0;top:3px;width:13px;height:13px;border-radius:50%;box-shadow:0 0 10px rgba(52,224,255,.5)}
|
|
69
|
+
.lbl{font-family:"Chakra Petch","Sora",sans-serif;font-weight:600;color:#fff}
|
|
70
|
+
.kind{font-family:"JetBrains Mono",monospace;font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:var(--plasma);margin-left:6px}
|
|
71
|
+
.detail{font-family:"JetBrains Mono",monospace;font-size:12.5px;color:#cdd5e1;margin-top:5px;white-space:pre-wrap;word-break:break-word}
|
|
72
|
+
.time{font-family:"JetBrains Mono",monospace;font-size:11px;color:#5f6b80;margin-top:4px}
|
|
73
|
+
.cmd{display:flex;align-items:center;gap:8px;font-family:"JetBrains Mono",monospace;font-size:13px;background:#04060b;border:1px solid #1b2435;border-radius:8px;padding:11px 12px;color:#e7ecf3;margin:4px 0 0}
|
|
74
|
+
.cmd .dollar{color:rgba(52,224,255,.6)}
|
|
75
|
+
.btn{display:inline-flex;align-items:center;gap:8px;margin-top:14px;background:var(--plasma);color:#06080d;font-family:"JetBrains Mono",monospace;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;padding:11px 16px;border-radius:6px;text-decoration:none;box-shadow:0 0 24px rgba(52,224,255,.3)}
|
|
76
|
+
.foot{margin-top:40px;border-top:1px solid #16202f;padding-top:18px;font-family:"JetBrains Mono",monospace;font-size:11px;color:#5f6b80}
|
|
77
|
+
.search{position:sticky;top:0;z-index:3;display:flex;align-items:center;gap:12px;padding:10px 0 14px;margin-bottom:6px;background:linear-gradient(var(--ink) 70%,transparent)}
|
|
78
|
+
.search input{flex:1;min-width:0;background:#04060b;border:1px solid #1b2435;border-radius:8px;padding:10px 12px;color:#e7ecf3;font-family:"JetBrains Mono",monospace;font-size:13px;outline:none}
|
|
79
|
+
.search input:focus{border-color:rgba(52,224,255,.5);box-shadow:0 0 0 3px rgba(52,224,255,.12)}
|
|
80
|
+
.search .count{font-family:"JetBrains Mono",monospace;font-size:11px;color:#5f6b80;white-space:nowrap;min-width:78px;text-align:right}
|
|
81
|
+
.search .nav{display:flex;gap:4px}
|
|
82
|
+
.search .nav button{width:30px;height:30px;display:flex;align-items:center;justify-content:center;background:#04060b;border:1px solid #1b2435;border-radius:7px;color:#9aa6b6;font-size:14px;cursor:pointer;line-height:1}
|
|
83
|
+
.search .nav button:hover:not(:disabled){border-color:rgba(52,224,255,.5);color:var(--plasma)}
|
|
84
|
+
.search .nav button:disabled{opacity:.4;cursor:default}
|
|
85
|
+
mark.hit{background:rgba(52,224,255,.18);color:#e7ecf3;border-radius:2px;padding:0 1px}
|
|
86
|
+
mark.hit.cur{background:var(--plasma);color:#06080d;box-shadow:0 0 0 2px rgba(52,224,255,.35)}
|
|
87
|
+
.jumps{display:flex;gap:14px;margin-top:12px}
|
|
88
|
+
.jumps a{font-family:"JetBrains Mono",monospace;font-size:11px;letter-spacing:.08em;color:#9aa6b6}
|
|
89
|
+
.jump{position:fixed;right:18px;bottom:18px;display:flex;flex-direction:column;gap:8px;z-index:6}
|
|
90
|
+
.jump a{width:40px;height:40px;display:flex;align-items:center;justify-content:center;border:1px solid #1b2435;border-radius:10px;background:rgba(8,12,20,.85);color:var(--plasma);text-decoration:none;font-size:16px;backdrop-filter:blur(6px)}
|
|
91
|
+
.jump a:hover{border-color:rgba(52,224,255,.5)}
|
|
92
|
+
.bottombar{margin-top:48px;border-top:1px solid #16202f;padding-top:24px;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:18px}
|
|
93
|
+
.bottombar .logo{height:26px;width:auto}
|
|
94
|
+
.bottombar .pitch{font-size:13px;color:#9aa6b6;max-width:520px}
|
|
95
|
+
.session{font-family:"JetBrains Mono",monospace;font-size:11px;color:#5f6b80;margin-bottom:18px;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
96
|
+
.turn{padding-bottom:10px;margin-bottom:18px;border-bottom:1px solid #0f1722}
|
|
97
|
+
.turn:last-child{border-bottom:none}
|
|
98
|
+
.msg{margin:10px 0}
|
|
99
|
+
.who{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
|
100
|
+
.ts{font-family:"JetBrains Mono",monospace;font-size:10.5px;color:#4a5568}
|
|
101
|
+
.who .ts{margin-left:auto}
|
|
102
|
+
.copy{flex:none;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:1px solid transparent;border-radius:6px;color:#5f6b80;cursor:pointer;padding:0}
|
|
103
|
+
.copy:hover{color:var(--plasma);border-color:#1b2435}
|
|
104
|
+
.copy.done{color:var(--plasma);border-color:rgba(52,224,255,.4)}
|
|
105
|
+
.badge{font-family:"JetBrains Mono",monospace;font-size:10px;text-transform:uppercase;letter-spacing:.12em;padding:2px 7px;border-radius:5px}
|
|
106
|
+
.badge.h{background:rgba(255,207,107,.14);color:#ffcf6b;border:1px solid rgba(255,207,107,.3)}
|
|
107
|
+
.badge.a{background:rgba(52,224,255,.14);color:var(--plasma);border:1px solid rgba(52,224,255,.3)}
|
|
108
|
+
.msg .bubble{padding:10px 14px;border-radius:0 8px 8px 0;white-space:pre-wrap;word-break:break-word;font-size:14px;line-height:1.6}
|
|
109
|
+
.msg.human .bubble{border-left:2px solid rgba(255,207,107,.55);background:rgba(255,207,107,.05);color:#f3ead7}
|
|
110
|
+
.msg.agent .bubble{border-left:2px solid rgba(52,224,255,.55);background:rgba(52,224,255,.05);color:#d7e6ee}
|
|
111
|
+
.bubble.md{white-space:normal}
|
|
112
|
+
.bubble.md p{margin:0 0 8px}.bubble.md p:last-child{margin-bottom:0}
|
|
113
|
+
.bubble.md code{font-family:"JetBrains Mono",monospace;font-size:.92em;background:rgba(255,255,255,.07);padding:1px 5px;border-radius:4px}
|
|
114
|
+
.bubble.md pre{background:#04060b;border:1px solid #1b2435;border-radius:8px;padding:10px 12px;overflow:auto;margin:8px 0}
|
|
115
|
+
.bubble.md pre code{background:none;padding:0;font-size:12.5px;line-height:1.5}
|
|
116
|
+
.bubble.md ul,.bubble.md ol{margin:6px 0;padding-left:20px}.bubble.md li{margin:3px 0}
|
|
117
|
+
.bubble.md a{color:var(--plasma);text-decoration:underline}
|
|
118
|
+
.bubble.md blockquote{margin:8px 0;padding:2px 12px;border-left:2px solid #2a3850;color:#9aa6b6}
|
|
119
|
+
.bubble.md .md-h{font-family:"Chakra Petch","Sora",sans-serif;font-weight:700;color:#fff;font-size:15px;margin:10px 0 6px}
|
|
120
|
+
.bubble.md strong{color:#fff;font-weight:700}
|
|
121
|
+
details.work{margin:10px 0;border:1px solid #16202f;border-radius:10px;background:rgba(8,12,20,.4)}
|
|
122
|
+
details.work>summary{cursor:pointer;list-style:none;padding:10px 14px;display:flex;align-items:center;gap:10px;font-family:"JetBrains Mono",monospace;font-size:12px;color:#9aa6b6}
|
|
123
|
+
details.work>summary::-webkit-details-marker{display:none}
|
|
124
|
+
details.work>summary::before{content:"\\25B8";color:var(--plasma);font-size:11px}
|
|
125
|
+
details.work[open]>summary::before{content:"\\25BE"}
|
|
126
|
+
details.work .peek{color:#5f6b80;font-size:11px}
|
|
127
|
+
details.work ol.tl{padding:6px 16px 14px}
|
|
128
|
+
details.work ol.tl::before{top:14px;bottom:14px}
|
|
129
|
+
.controls{position:sticky;top:0;z-index:3;display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 0 14px;margin-bottom:4px;background:linear-gradient(var(--ink) 72%,transparent)}
|
|
130
|
+
.chip{font-family:"JetBrains Mono",monospace;font-size:11px;padding:4px 11px;border-radius:999px;border:1px solid #1b2435;background:#04060b;color:#9aa6b6;cursor:pointer}
|
|
131
|
+
.chip.on{border-color:rgba(52,224,255,.5);color:var(--plasma)}
|
|
132
|
+
.sortbtn{margin-left:auto;font-family:"JetBrains Mono",monospace;font-size:11px;padding:4px 11px;border-radius:6px;border:1px solid #1b2435;background:#04060b;color:#9aa6b6;cursor:pointer}
|
|
133
|
+
.sortbtn:hover{border-color:rgba(52,224,255,.5);color:var(--plasma)}
|
|
134
|
+
.turnhead{margin:0 0 4px}
|
|
135
|
+
.agentchip{font-family:"JetBrains Mono",monospace;font-size:10px;text-transform:uppercase;letter-spacing:.1em;padding:2px 8px;border-radius:5px;border:1px solid #2a3850;color:#9aa6b6}
|
|
136
|
+
.agentchip.claude{color:var(--plasma);border-color:rgba(52,224,255,.4);background:rgba(52,224,255,.08)}
|
|
137
|
+
.agentchip.codex{color:#9b8cff;border-color:rgba(155,140,255,.4);background:rgba(155,140,255,.08)}
|
|
138
|
+
.turn.hide{display:none}
|
|
139
|
+
`;
|
|
140
|
+
const FIND_SCRIPT = `<script>
|
|
141
|
+
(function(){
|
|
142
|
+
var q=document.getElementById('q'),tl=document.getElementById('tl'),count=document.getElementById('count'),
|
|
143
|
+
prev=document.getElementById('prev'),next=document.getElementById('next');
|
|
144
|
+
if(!q||!tl)return;
|
|
145
|
+
var ORIGINAL=tl.innerHTML,total=tl.getElementsByTagName('li').length,hits=[],cur=-1;
|
|
146
|
+
function render(){
|
|
147
|
+
var on=hits.length>0, term=q.value.trim();
|
|
148
|
+
prev.disabled=!on; next.disabled=!on;
|
|
149
|
+
count.textContent=term?(on?((cur+1)+' / '+hits.length):'0 matches'):(total+' actions');
|
|
150
|
+
}
|
|
151
|
+
function setCur(i){
|
|
152
|
+
if(cur>=0&&hits[cur])hits[cur].className='hit';
|
|
153
|
+
if(!hits.length){cur=-1;render();return;}
|
|
154
|
+
cur=(i+hits.length)%hits.length;
|
|
155
|
+
hits[cur].className='hit cur';
|
|
156
|
+
var p=hits[cur].parentNode; while(p&&p!==tl){ if(p.tagName==='DETAILS')p.open=true; p=p.parentNode; }
|
|
157
|
+
hits[cur].scrollIntoView({block:'center',behavior:'smooth'});
|
|
158
|
+
render();
|
|
159
|
+
}
|
|
160
|
+
function find(term){
|
|
161
|
+
tl.innerHTML=ORIGINAL; hits=[]; cur=-1; term=term.toLowerCase();
|
|
162
|
+
if(term){
|
|
163
|
+
var walker=document.createTreeWalker(tl,NodeFilter.SHOW_TEXT,null),nodes=[];
|
|
164
|
+
while(walker.nextNode())nodes.push(walker.currentNode);
|
|
165
|
+
nodes.forEach(function(node){
|
|
166
|
+
var text=node.nodeValue,low=text.toLowerCase();
|
|
167
|
+
if(low.indexOf(term)<0)return;
|
|
168
|
+
var frag=document.createDocumentFragment(),last=0,idx;
|
|
169
|
+
while((idx=low.indexOf(term,last))>=0){
|
|
170
|
+
if(idx>last)frag.appendChild(document.createTextNode(text.slice(last,idx)));
|
|
171
|
+
var m=document.createElement('mark'); m.className='hit'; m.textContent=text.slice(idx,idx+term.length);
|
|
172
|
+
frag.appendChild(m); hits.push(m); last=idx+term.length;
|
|
173
|
+
}
|
|
174
|
+
if(last<text.length)frag.appendChild(document.createTextNode(text.slice(last)));
|
|
175
|
+
node.parentNode.replaceChild(frag,node);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if(hits.length)setCur(0); else render();
|
|
179
|
+
}
|
|
180
|
+
q.addEventListener('input',function(){find(q.value.trim());});
|
|
181
|
+
q.addEventListener('keydown',function(e){
|
|
182
|
+
if(e.key==='Enter'){e.preventDefault(); if(hits.length)setCur(cur+(e.shiftKey?-1:1));}
|
|
183
|
+
});
|
|
184
|
+
prev.addEventListener('click',function(){setCur(cur-1);});
|
|
185
|
+
next.addEventListener('click',function(){setCur(cur+1);});
|
|
186
|
+
document.addEventListener('click',function(e){
|
|
187
|
+
var btn=e.target.closest&&e.target.closest('.copy'); if(!btn)return;
|
|
188
|
+
var msg=btn.closest('.msg'),b=msg&&msg.querySelector('.bubble');
|
|
189
|
+
if(b&&navigator.clipboard)navigator.clipboard.writeText(b.innerText);
|
|
190
|
+
btn.classList.add('done'); setTimeout(function(){btn.classList.remove('done');},1200);
|
|
191
|
+
});
|
|
192
|
+
})();
|
|
193
|
+
</script>`;
|
|
194
|
+
function dot(kind) {
|
|
195
|
+
return kind === 'tool' ? PLASMA : kind === 'prompt' ? '#8b95a7' : '#5f6b80';
|
|
196
|
+
}
|
|
197
|
+
function row(e) {
|
|
198
|
+
const time = e.at ? esc(e.at.replace('T', ' ').slice(0, 19) + 'Z') : '';
|
|
199
|
+
return `<li><span class="dot" style="background:${dot(e.kind)}"></span><div>
|
|
200
|
+
<span class="lbl">${esc(e.label)}</span><span class="kind">${esc(e.kind)}</span>
|
|
201
|
+
${e.detail ? `<div class="detail">${esc(e.detail)}</div>` : ''}<div class="time">${time}</div></div></li>`;
|
|
202
|
+
}
|
|
203
|
+
function groupTurns(events) {
|
|
204
|
+
const pre = [];
|
|
205
|
+
const turns = [];
|
|
206
|
+
let cur = null;
|
|
207
|
+
for (const e of events) {
|
|
208
|
+
if (e.kind === 'prompt') {
|
|
209
|
+
if (cur)
|
|
210
|
+
turns.push(cur);
|
|
211
|
+
cur = { prompt: e, work: [] };
|
|
212
|
+
}
|
|
213
|
+
else if (e.kind === 'response') {
|
|
214
|
+
if (cur)
|
|
215
|
+
cur.response = e;
|
|
216
|
+
}
|
|
217
|
+
else if (e.kind === 'tool') {
|
|
218
|
+
(cur ? cur.work : pre).push(e);
|
|
219
|
+
}
|
|
220
|
+
else if (e.kind === 'start') {
|
|
221
|
+
pre.push(e);
|
|
222
|
+
}
|
|
223
|
+
// legacy 'stop' events are ignored (superseded by 'response')
|
|
224
|
+
}
|
|
225
|
+
if (cur)
|
|
226
|
+
turns.push(cur);
|
|
227
|
+
return { pre, turns };
|
|
228
|
+
}
|
|
229
|
+
function fmtTime(at) {
|
|
230
|
+
return at ? esc(at.replace('T', ' ').slice(0, 19) + 'Z') : '';
|
|
231
|
+
}
|
|
232
|
+
const COPY_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>';
|
|
233
|
+
// Inline markdown on an ALREADY-ESCAPED string. Code spans are protected from
|
|
234
|
+
// emphasis. Only http(s) links are emitted. No raw HTML can survive (input is
|
|
235
|
+
// pre-escaped), so this is XSS-safe — it only adds a fixed set of safe tags.
|
|
236
|
+
function inlineMd(s) {
|
|
237
|
+
return s.split(/(`[^`]+`)/).map((p) => {
|
|
238
|
+
if (p.length >= 2 && p[0] === '`' && p[p.length - 1] === '`')
|
|
239
|
+
return `<code>${p.slice(1, -1)}</code>`;
|
|
240
|
+
return p
|
|
241
|
+
.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" rel="noopener noreferrer" target="_blank">$1</a>')
|
|
242
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
243
|
+
.replace(/__([^_]+)__/g, '<strong>$1</strong>')
|
|
244
|
+
.replace(/\*([^*\s][^*]*)\*/g, '<em>$1</em>')
|
|
245
|
+
.replace(/(^|[^a-zA-Z0-9_])_([^_\s][^_]*)_/g, '$1<em>$2</em>');
|
|
246
|
+
}).join('');
|
|
247
|
+
}
|
|
248
|
+
/** Minimal, XSS-safe markdown → HTML for chat bubbles (prompt/response). */
|
|
249
|
+
export function mdToHtml(raw) {
|
|
250
|
+
const lines = esc(raw).split('\n');
|
|
251
|
+
const out = [];
|
|
252
|
+
let para = [];
|
|
253
|
+
let code = [];
|
|
254
|
+
let inCode = false;
|
|
255
|
+
let list = null;
|
|
256
|
+
const flushPara = () => { if (para.length) {
|
|
257
|
+
out.push(`<p>${inlineMd(para.join('<br>'))}</p>`);
|
|
258
|
+
para = [];
|
|
259
|
+
} };
|
|
260
|
+
const flushList = () => { if (list) {
|
|
261
|
+
out.push(`</${list}>`);
|
|
262
|
+
list = null;
|
|
263
|
+
} };
|
|
264
|
+
for (const line of lines) {
|
|
265
|
+
const fence = /^```/.test(line.trim());
|
|
266
|
+
if (inCode) {
|
|
267
|
+
if (fence) {
|
|
268
|
+
out.push(`<pre><code>${code.join('\n')}</code></pre>`);
|
|
269
|
+
code = [];
|
|
270
|
+
inCode = false;
|
|
271
|
+
}
|
|
272
|
+
else
|
|
273
|
+
code.push(line);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (fence) {
|
|
277
|
+
flushPara();
|
|
278
|
+
flushList();
|
|
279
|
+
inCode = true;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (/^\s*$/.test(line)) {
|
|
283
|
+
flushPara();
|
|
284
|
+
flushList();
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
let m;
|
|
288
|
+
if ((m = line.match(/^#{1,6}\s+(.*)$/))) {
|
|
289
|
+
flushPara();
|
|
290
|
+
flushList();
|
|
291
|
+
out.push(`<div class="md-h">${inlineMd(m[1])}</div>`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if ((m = line.match(/^\s*[-*]\s+(.*)$/))) {
|
|
295
|
+
flushPara();
|
|
296
|
+
if (list !== 'ul') {
|
|
297
|
+
flushList();
|
|
298
|
+
out.push('<ul>');
|
|
299
|
+
list = 'ul';
|
|
300
|
+
}
|
|
301
|
+
out.push(`<li>${inlineMd(m[1])}</li>`);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if ((m = line.match(/^\s*\d+\.\s+(.*)$/))) {
|
|
305
|
+
flushPara();
|
|
306
|
+
if (list !== 'ol') {
|
|
307
|
+
flushList();
|
|
308
|
+
out.push('<ol>');
|
|
309
|
+
list = 'ol';
|
|
310
|
+
}
|
|
311
|
+
out.push(`<li>${inlineMd(m[1])}</li>`);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if ((m = line.match(/^>\s?(.*)$/))) {
|
|
315
|
+
flushPara();
|
|
316
|
+
flushList();
|
|
317
|
+
out.push(`<blockquote>${inlineMd(m[1])}</blockquote>`);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
para.push(line);
|
|
321
|
+
}
|
|
322
|
+
if (inCode)
|
|
323
|
+
out.push(`<pre><code>${code.join('\n')}</code></pre>`);
|
|
324
|
+
flushPara();
|
|
325
|
+
flushList();
|
|
326
|
+
return out.join('\n');
|
|
327
|
+
}
|
|
328
|
+
// The speaker name travels on the event label; fall back for legacy generic labels.
|
|
329
|
+
function speaker(label, generic, fallback) {
|
|
330
|
+
return label && label !== generic ? label : fallback;
|
|
331
|
+
}
|
|
332
|
+
function bubble(kind, who, e) {
|
|
333
|
+
return `<div class="msg ${kind}">
|
|
334
|
+
<div class="who"><span class="badge ${kind === 'human' ? 'h' : 'a'}">${esc(who)}</span><span class="ts">${fmtTime(e.at)}</span><button class="copy" type="button" aria-label="Copy text" title="Copy">${COPY_ICON}</button></div>
|
|
335
|
+
<div class="bubble md">${mdToHtml(e.detail)}</div>
|
|
336
|
+
</div>`;
|
|
337
|
+
}
|
|
338
|
+
function workBlock(work) {
|
|
339
|
+
if (!work.length)
|
|
340
|
+
return '';
|
|
341
|
+
const peek = work.slice(0, 4).map((e) => esc(e.label)).join(' · ') + (work.length > 4 ? ` +${work.length - 4}` : '');
|
|
342
|
+
return `<details class="work">
|
|
343
|
+
<summary><span class="badge a">Agent</span> ${work.length} action${work.length === 1 ? '' : 's'} <span class="peek">${peek}</span></summary>
|
|
344
|
+
<ol class="tl">${work.map(row).join('\n')}</ol>
|
|
345
|
+
</details>`;
|
|
346
|
+
}
|
|
347
|
+
// Group a run into [Human prompt][Agent work, collapsed][Agent response] turns.
|
|
348
|
+
// Falls back to a flat list for prompt-less (legacy) runs.
|
|
349
|
+
function renderTimeline(events) {
|
|
350
|
+
const { pre, turns } = groupTurns(events);
|
|
351
|
+
if (turns.length === 0) {
|
|
352
|
+
return `<ol class="tl">${events.filter((e) => e.kind !== 'start').map(row).join('\n')}</ol>`;
|
|
353
|
+
}
|
|
354
|
+
const startEv = pre.find((e) => e.kind === 'start');
|
|
355
|
+
const preamble = startEv
|
|
356
|
+
? `<div class="session"><span class="badge a">Session</span><span class="ts">${esc(startEv.detail || '')} · ${fmtTime(startEv.at)}</span></div>`
|
|
357
|
+
: '';
|
|
358
|
+
const preTools = workBlock(pre.filter((e) => e.kind === 'tool'));
|
|
359
|
+
const body = turns.map((t) => `<section class="turn">
|
|
360
|
+
${t.prompt ? bubble('human', speaker(t.prompt.label, 'Prompt', 'Human'), t.prompt) : ''}
|
|
361
|
+
${workBlock(t.work)}
|
|
362
|
+
${t.response && t.response.detail ? bubble('agent', speaker(t.response.label, 'Response', 'Agent'), t.response) : ''}
|
|
363
|
+
</section>`).join('\n');
|
|
364
|
+
return preamble + preTools + body;
|
|
365
|
+
}
|
|
366
|
+
function toolCount(events) {
|
|
367
|
+
return events.filter((e) => e.kind === 'tool').length;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Self-contained, redacted-in audit-timeline HTML for one local run — in full
|
|
371
|
+
* visual + behavioural parity with the public sickr.ai/r/<id> page.
|
|
372
|
+
*/
|
|
373
|
+
export function renderRunHtml(run) {
|
|
374
|
+
return `<!doctype html><html lang="en"><head><meta charset="UTF-8"/>
|
|
375
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
376
|
+
<title>SICKR Replay — ${esc(run.id)}</title>
|
|
377
|
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
|
378
|
+
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@600;700&family=Sora:wght@300;400;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
|
379
|
+
<style>${STYLES}</style></head>
|
|
380
|
+
<body>
|
|
381
|
+
<div class="bg"><div class="bg-grid"></div><div class="glow glow-a"></div><div class="glow glow-b"></div></div>
|
|
382
|
+
<div class="wrap">
|
|
383
|
+
<span id="tl-top"></span>
|
|
384
|
+
<header class="bar">
|
|
385
|
+
<a href="https://sickr.ai">${wordmark('lg')}</a>
|
|
386
|
+
<a class="bar-cta" href="https://sickr.ai">sickr.ai →</a>
|
|
387
|
+
</header>
|
|
388
|
+
<div class="layout">
|
|
389
|
+
<aside class="rail">
|
|
390
|
+
<p class="label">Local replay</p>
|
|
391
|
+
<h3 style="margin-top:10px">What is this?</h3>
|
|
392
|
+
<p>A timeline of every action your AI coding agent took in this session — captured on your machine, secrets redacted.</p>
|
|
393
|
+
<div style="margin-top:14px">
|
|
394
|
+
<div class="meta-row"><span>id</span><b>${esc(run.id)}</b></div>
|
|
395
|
+
<div class="meta-row"><span>workspace</span><b>${esc(run.cwd || '—')}</b></div>
|
|
396
|
+
<div class="meta-row"><span>actions</span><b>${run.events.length}</b></div>
|
|
397
|
+
</div>
|
|
398
|
+
<div class="jumps"><a href="#tl-top">↑ top</a><a href="#tl-bottom">↓ end</a></div>
|
|
399
|
+
</aside>
|
|
400
|
+
<main>
|
|
401
|
+
<h1 class="title">What this agent <span class="hl">actually did</span>.</h1>
|
|
402
|
+
<p class="sub">captured locally with npx @sickr/cli · secrets redacted</p>
|
|
403
|
+
<div class="search">
|
|
404
|
+
<input id="q" type="search" autocomplete="off" placeholder="Search this run — tool, command, file, prompt…" aria-label="Search this run"/>
|
|
405
|
+
<div class="nav"><button id="prev" type="button" aria-label="Previous match" title="Previous (Shift+Enter)" disabled>‹</button><button id="next" type="button" aria-label="Next match" title="Next (Enter)" disabled>›</button></div>
|
|
406
|
+
<span class="count" id="count">${toolCount(run.events)} actions</span>
|
|
407
|
+
</div>
|
|
408
|
+
<div id="tl">${renderTimeline(run.events)}</div>
|
|
409
|
+
</main>
|
|
410
|
+
<aside class="rail panel">
|
|
411
|
+
<p class="label" style="margin-bottom:12px">Beyond this run</p>
|
|
412
|
+
<h3>Govern your whole team</h3>
|
|
413
|
+
<p>This is one agent on your machine. SICKR adds gates, approvals, multi-agent hand-offs and a full, signed-off audit trail across humans and agents.</p>
|
|
414
|
+
<a class="btn" href="https://sickr.ai">Explore SICKR →</a>
|
|
415
|
+
<p style="margin-top:16px;font-size:12px">Share this run as a public link:</p>
|
|
416
|
+
<div class="cmd"><span class="dollar">$</span><code>npx @sickr/cli replay share</code></div>
|
|
417
|
+
</aside>
|
|
418
|
+
</div>
|
|
419
|
+
<span id="tl-bottom"></span>
|
|
420
|
+
<div class="bottombar">
|
|
421
|
+
<a href="https://sickr.ai">${wordmark('lg2')}</a>
|
|
422
|
+
<p class="pitch">One agent, audited. <span style="color:#fff">Govern your whole team</span> — gates, approvals, multi-agent hand-offs and a full audit trail.</p>
|
|
423
|
+
<a class="btn" href="https://sickr.ai">Explore SICKR →</a>
|
|
424
|
+
</div>
|
|
425
|
+
<div class="foot">Captured locally with npx @sickr/cli · <a href="#tl-top">back to top ↑</a> · sickr.ai</div>
|
|
426
|
+
<nav class="jump" aria-label="scroll"><a href="#tl-top" aria-label="Scroll to top" title="Top">↑</a><a href="#tl-bottom" aria-label="Scroll to bottom" title="Bottom">↓</a></nav>
|
|
427
|
+
${FIND_SCRIPT}
|
|
428
|
+
</div></body></html>`;
|
|
429
|
+
}
|
|
430
|
+
// Filter (by agent) + sort (prompt vs response time) + copy. Kept in sync with /r.
|
|
431
|
+
const COMBINED_SCRIPT = `<script>
|
|
432
|
+
(function(){
|
|
433
|
+
var tl=document.getElementById('tl'); if(!tl) return;
|
|
434
|
+
var turns=[].slice.call(tl.getElementsByClassName('turn'));
|
|
435
|
+
var chips=[].slice.call(document.getElementsByClassName('chip'));
|
|
436
|
+
chips.forEach(function(c){c.addEventListener('click',function(){
|
|
437
|
+
chips.forEach(function(x){x.classList.remove('on')}); c.classList.add('on');
|
|
438
|
+
var f=c.getAttribute('data-f');
|
|
439
|
+
turns.forEach(function(t){ t.className='turn'+((f==='All'||t.getAttribute('data-agent')===f)?'':' hide'); });
|
|
440
|
+
});});
|
|
441
|
+
var sb=document.getElementById('sortbtn'), byResp=false;
|
|
442
|
+
if(sb) sb.addEventListener('click',function(){
|
|
443
|
+
byResp=!byResp; sb.textContent=byResp?'sort: response time':'sort: prompt time';
|
|
444
|
+
var key=byResp?'data-rt':'data-pt';
|
|
445
|
+
turns.slice().sort(function(a,b){var x=a.getAttribute(key)||'',y=b.getAttribute(key)||'';return x<y?-1:x>y?1:0;}).forEach(function(t){tl.appendChild(t)});
|
|
446
|
+
});
|
|
447
|
+
document.addEventListener('click',function(e){
|
|
448
|
+
var b=e.target.closest&&e.target.closest('.copy'); if(!b)return;
|
|
449
|
+
var m=b.closest('.msg'),bb=m&&m.querySelector('.bubble');
|
|
450
|
+
if(bb&&navigator.clipboard)navigator.clipboard.writeText(bb.innerText);
|
|
451
|
+
b.classList.add('done'); setTimeout(function(){b.classList.remove('done');},1200);
|
|
452
|
+
});
|
|
453
|
+
})();
|
|
454
|
+
</script>`;
|
|
455
|
+
function combinedSection(agent, t) {
|
|
456
|
+
const pt = t.prompt?.at || t.work[0]?.at || t.response?.at || '';
|
|
457
|
+
const rt = t.response?.at || t.work[t.work.length - 1]?.at || pt;
|
|
458
|
+
const cls = agent === 'Codex' ? 'codex' : agent === 'Claude' ? 'claude' : '';
|
|
459
|
+
return `<section class="turn" data-agent="${esc(agent)}" data-pt="${esc(pt)}" data-rt="${esc(rt)}">
|
|
460
|
+
<div class="turnhead"><span class="agentchip ${cls}">${esc(agent)}</span></div>
|
|
461
|
+
${t.prompt ? bubble('human', speaker(t.prompt.label, 'Prompt', 'Human'), t.prompt) : ''}
|
|
462
|
+
${workBlock(t.work)}
|
|
463
|
+
${t.response && t.response.detail ? bubble('agent', speaker(t.response.label, 'Response', agent), t.response) : ''}
|
|
464
|
+
</section>`;
|
|
465
|
+
}
|
|
466
|
+
/** A combined, interleaved timeline across multiple runs/agents, sorted by prompt time. */
|
|
467
|
+
export function renderCombinedHtml(runs, window) {
|
|
468
|
+
const items = [];
|
|
469
|
+
for (const r of runs) {
|
|
470
|
+
for (const t of groupTurns(r.events).turns) {
|
|
471
|
+
items.push({ agent: r.agent, t, pt: t.prompt?.at || t.work[0]?.at || t.response?.at || '' });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
items.sort((a, b) => (a.pt < b.pt ? -1 : a.pt > b.pt ? 1 : 0));
|
|
475
|
+
const agents = Array.from(new Set(runs.map((r) => r.agent)));
|
|
476
|
+
const chips = ['All', ...agents].map((a) => `<button class="chip${a === 'All' ? ' on' : ''}" type="button" data-f="${esc(a)}">${esc(a)}</button>`).join('');
|
|
477
|
+
const sections = items.length ? items.map((it) => combinedSection(it.agent, it.t)).join('\n') : '<p class="sub">No turns in this window yet.</p>';
|
|
478
|
+
return `<!doctype html><html lang="en"><head><meta charset="UTF-8"/>
|
|
479
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
480
|
+
<title>SICKR Replay — combined (${esc(window)})</title>
|
|
481
|
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
|
482
|
+
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@600;700&family=Sora:wght@300;400;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
|
483
|
+
<style>${STYLES}</style></head>
|
|
484
|
+
<body>
|
|
485
|
+
<div class="bg"><div class="bg-grid"></div><div class="glow glow-a"></div><div class="glow glow-b"></div></div>
|
|
486
|
+
<div class="wrap">
|
|
487
|
+
<span id="tl-top"></span>
|
|
488
|
+
<header class="bar">
|
|
489
|
+
<a href="https://sickr.ai">${wordmark('lg')}</a>
|
|
490
|
+
<a class="bar-cta" href="https://sickr.ai">sickr.ai →</a>
|
|
491
|
+
</header>
|
|
492
|
+
<div class="layout">
|
|
493
|
+
<aside class="rail">
|
|
494
|
+
<p class="label">Combined replay</p>
|
|
495
|
+
<h3 style="margin-top:10px">All your agents, one timeline</h3>
|
|
496
|
+
<p>Every turn from every agent in this window, interleaved by time.</p>
|
|
497
|
+
<div style="margin-top:14px">
|
|
498
|
+
<div class="meta-row"><span>window</span><b>${esc(window)}</b></div>
|
|
499
|
+
<div class="meta-row"><span>runs</span><b>${runs.length}</b></div>
|
|
500
|
+
<div class="meta-row"><span>turns</span><b>${items.length}</b></div>
|
|
501
|
+
<div class="meta-row"><span>agents</span><b>${esc(agents.join(', ') || '—')}</b></div>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="jumps"><a href="#tl-top">↑ top</a><a href="#tl-bottom">↓ end</a></div>
|
|
504
|
+
</aside>
|
|
505
|
+
<main>
|
|
506
|
+
<h1 class="title">What <span class="hl">all your agents</span> did.</h1>
|
|
507
|
+
<p class="sub">${items.length} turns · ${runs.length} runs · ${esc(window)} · captured locally</p>
|
|
508
|
+
<div class="controls">${chips}<button id="sortbtn" type="button" class="sortbtn">sort: prompt time</button></div>
|
|
509
|
+
<div id="tl" class="turns">${sections}</div>
|
|
510
|
+
</main>
|
|
511
|
+
<aside class="rail panel">
|
|
512
|
+
<p class="label" style="margin-bottom:12px">Beyond your machine</p>
|
|
513
|
+
<h3>Govern your whole team</h3>
|
|
514
|
+
<p>This is your agents on one machine. SICKR adds gates, approvals, multi-agent hand-offs and a full, signed-off audit trail across humans and agents.</p>
|
|
515
|
+
<a class="btn" href="https://sickr.ai">Explore SICKR →</a>
|
|
516
|
+
</aside>
|
|
517
|
+
</div>
|
|
518
|
+
<span id="tl-bottom"></span>
|
|
519
|
+
<div class="bottombar">
|
|
520
|
+
<a href="https://sickr.ai">${wordmark('lg2')}</a>
|
|
521
|
+
<p class="pitch">All your agents, audited in one place. <span style="color:#fff">Govern the whole team</span> — gates, approvals, full audit trail.</p>
|
|
522
|
+
<a class="btn" href="https://sickr.ai">Explore SICKR →</a>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="foot">Captured locally with npx @sickr/cli · <a href="#tl-top">back to top ↑</a> · sickr.ai</div>
|
|
525
|
+
<nav class="jump" aria-label="scroll"><a href="#tl-top" aria-label="Scroll to top" title="Top">↑</a><a href="#tl-bottom" aria-label="Scroll to bottom" title="Bottom">↓</a></nav>
|
|
526
|
+
${COMBINED_SCRIPT}
|
|
527
|
+
</div></body></html>`;
|
|
528
|
+
}
|
package/dist/share.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { gzipSync } from 'node:zlib';
|
|
2
|
+
/** Strip the local id; events are already redacted at capture time. */
|
|
3
|
+
export function buildSharePayload(run) {
|
|
4
|
+
return { run: { cwd: run.cwd, startedAt: run.startedAt, events: run.events } };
|
|
5
|
+
}
|
|
6
|
+
/** Combined multi-agent payload: tagged runs + the window label. */
|
|
7
|
+
export function buildCombinedPayload(runs, window) {
|
|
8
|
+
return { window, runs: runs.map((r) => ({ agent: r.agent, events: r.events })) };
|
|
9
|
+
}
|
|
10
|
+
export class PublishError extends Error {
|
|
11
|
+
status;
|
|
12
|
+
constructor(status) {
|
|
13
|
+
super(`publish failed: ${status}`);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.name = 'PublishError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function publish(payload, endpoint, opts = {}) {
|
|
19
|
+
// Gzip the JSON body: ~5-10x smaller for typical sessions, comfortably fits
|
|
20
|
+
// the worker's 4 MB cap even for combined multi-day windows.
|
|
21
|
+
const raw = JSON.stringify(payload);
|
|
22
|
+
const gz = gzipSync(raw);
|
|
23
|
+
const headers = { 'Content-Type': 'application/json', 'Content-Encoding': 'gzip' };
|
|
24
|
+
if (opts.token)
|
|
25
|
+
headers.Authorization = `Bearer ${opts.token}`;
|
|
26
|
+
const res = await fetch(endpoint, { method: 'POST', headers, body: gz });
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
throw new PublishError(res.status);
|
|
29
|
+
return (await res.json());
|
|
30
|
+
}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Tiny inline ANSI + box helper. No deps — chalk/ora would balloon the install
|
|
2
|
+
// and clash with the "npx and forget" wedge. One place that decides when to
|
|
3
|
+
// down-grade to plain `sickr: ...` lines: noColor() — NO_COLOR / CI / non-TTY /
|
|
4
|
+
// dumb terminals / legacy cmd.exe. Every other code path goes through ui.enabled().
|
|
5
|
+
//
|
|
6
|
+
// Brand: Sickr wordmark cyan (51), plasma accent magenta (201), dim chrome
|
|
7
|
+
// gray (240/245), values bright white (255), amber (214) for anon-link
|
|
8
|
+
// urgency, green (42) for the redacted check. Matches the existing /r/<id>
|
|
9
|
+
// dark/plasma palette without hard-coding hex (terminals translate 256-color).
|
|
10
|
+
const ESC = '\x1b[';
|
|
11
|
+
function isStdoutTTY() {
|
|
12
|
+
return process.stdout && typeof process.stdout.isTTY === 'boolean' && process.stdout.isTTY;
|
|
13
|
+
}
|
|
14
|
+
function noColor() {
|
|
15
|
+
if (process.env.NO_COLOR != null || process.env.SICKR_NO_COLOR != null)
|
|
16
|
+
return true;
|
|
17
|
+
if (process.env.CI === 'true')
|
|
18
|
+
return true;
|
|
19
|
+
if (process.env.TERM === 'dumb')
|
|
20
|
+
return true;
|
|
21
|
+
// Legacy cmd.exe: claims TTY but mangles 256-color SGR. Modern Windows
|
|
22
|
+
// Terminal sets WT_SESSION; VS Code sets TERM_PROGRAM; both safe.
|
|
23
|
+
if (process.platform === 'win32' && !process.env.WT_SESSION && !process.env.TERM_PROGRAM)
|
|
24
|
+
return true;
|
|
25
|
+
return !isStdoutTTY();
|
|
26
|
+
}
|
|
27
|
+
const c = (code) => (s) => noColor() ? s : `${ESC}${code}m${s}${ESC}0m`;
|
|
28
|
+
const c256 = (n) => (s) => noColor() ? s : `${ESC}38;5;${n}m${s}${ESC}0m`;
|
|
29
|
+
export const ui = {
|
|
30
|
+
enabled() { return !noColor(); },
|
|
31
|
+
bold: c(1),
|
|
32
|
+
dim: c256(245),
|
|
33
|
+
border: c256(240),
|
|
34
|
+
brand: c256(51), // cyan — Sickr wordmark
|
|
35
|
+
accent: c256(201), // magenta — plasma glyphs / Pro tag
|
|
36
|
+
ok: c256(42), // green — redacted check
|
|
37
|
+
warn: c256(214), // amber — anon-link urgency
|
|
38
|
+
white: c256(255),
|
|
39
|
+
underline(s) { return noColor() ? s : `${ESC}4m${s}${ESC}24m`; },
|
|
40
|
+
glyph: { tip: '▸', check: '✓', tag: '⌗' },
|
|
41
|
+
};
|
|
42
|
+
function stripAnsi(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
43
|
+
/** "label value" with the label dim-gray padded to a fixed column. */
|
|
44
|
+
export function kv(label, value, labelWidth = 10) {
|
|
45
|
+
return ui.dim(label.padEnd(labelWidth)) + value;
|
|
46
|
+
}
|
|
47
|
+
/** Render a Sickr-titled box around pre-formatted rows. Caller should gate on
|
|
48
|
+
* ui.enabled() — in plain mode emit `sickr: ...` lines directly instead. */
|
|
49
|
+
export function card(title, rows) {
|
|
50
|
+
const widths = rows.map((r) => stripAnsi(r).length);
|
|
51
|
+
const inner = Math.max(56, ...widths) + 2; // +2 = one space pad each side inside the border
|
|
52
|
+
const dash = (n) => '─'.repeat(Math.max(1, n));
|
|
53
|
+
// Title bar: "╭─ sickr ─ <title> ─────────╮"
|
|
54
|
+
const titleTextLen = 'sickr '.length + 2 + title.length + 1; // "sickr " + "─ " + title + " "
|
|
55
|
+
const tailLen = Math.max(1, inner - titleTextLen - 1);
|
|
56
|
+
const top = ui.border('╭─ ') + ui.brand(ui.bold('sickr')) + ui.border(' ─ ') + ui.dim(title) + ' ' + ui.border(dash(tailLen) + '╮');
|
|
57
|
+
const bot = ui.border('╰' + dash(inner) + '╯');
|
|
58
|
+
const body = rows.map((r, i) => {
|
|
59
|
+
const pad = ' '.repeat(Math.max(0, inner - widths[i] - 2));
|
|
60
|
+
return ui.border('│ ') + r + pad + ui.border(' │');
|
|
61
|
+
}).join('\n');
|
|
62
|
+
return `${top}\n${body}\n${bot}\n`;
|
|
63
|
+
}
|