checkpoint-cli 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/index.js +370 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -79,31 +79,360 @@ function requireAuth() {
79
79
  }
80
80
  }
81
81
  /* ── Checkpoint Tracking Script (injected into HTML responses) ── */
82
- function trackingScript(checkpointOrigin) {
82
+ function trackingScript() {
83
83
  return `
84
84
  <script data-checkpoint>
85
85
  (function(){
86
- var origin=${JSON.stringify(checkpointOrigin)};
87
- function report(){
86
+ var lastPath='';
87
+ var pickMode=false;
88
+ var pickClickHandler=null;
89
+ var pickEscHandler=null;
90
+
91
+ function getMetrics(){
92
+ var de=document.documentElement;
93
+ return {
94
+ scrollX:window.scrollX||window.pageXOffset||0,
95
+ scrollY:window.scrollY||window.pageYOffset||0,
96
+ docWidth:de.scrollWidth||0,
97
+ docHeight:de.scrollHeight||0,
98
+ viewWidth:de.clientWidth||window.innerWidth||0,
99
+ viewHeight:de.clientHeight||window.innerHeight||0
100
+ };
101
+ }
102
+
103
+ function clampPercent(v){
104
+ if(v<0) return 0;
105
+ if(v>100) return 100;
106
+ return v;
107
+ }
108
+
109
+ function cssEscape(value){
110
+ try{
111
+ if(window.CSS&&typeof window.CSS.escape==='function'){
112
+ return window.CSS.escape(value);
113
+ }
114
+ }catch(e){}
115
+ return String(value).replace(/[^a-zA-Z0-9_-]/g,'\\\\$&');
116
+ }
117
+
118
+ function shortText(value){
119
+ if(!value) return '';
120
+ return String(value).replace(/\\s+/g,' ').trim().slice(0,120);
121
+ }
122
+
123
+ function elementSelector(el){
124
+ if(!el||!el.tagName) return '';
125
+ var tag=el.tagName.toLowerCase();
126
+ if(el.id) return '#'+cssEscape(el.id);
127
+ var cls=(el.className&&typeof el.className==='string')
128
+ ? el.className.trim().split(/\\s+/).filter(Boolean).slice(0,3)
129
+ : [];
130
+ if(cls.length>0) return tag+'.'+cls.map(cssEscape).join('.');
131
+ return tag;
132
+ }
133
+
134
+ function getSelectorChain(el){
135
+ var out=[];
136
+ var seen={};
137
+ function push(selector,score){
138
+ if(!selector||seen[selector]) return;
139
+ seen[selector]=true;
140
+ out.push({selector:selector,score:score});
141
+ }
142
+ var current=el;
143
+ var depth=0;
144
+ while(current&&current.nodeType===1&&depth<5){
145
+ var sel=elementSelector(current);
146
+ if(sel){
147
+ var score=Math.max(0.2,1-(depth*0.18));
148
+ push(sel,score);
149
+ }
150
+ if(current.parentElement){
151
+ var parentSel=elementSelector(current.parentElement);
152
+ if(parentSel&&sel){
153
+ push(parentSel+' > '+sel,Math.max(0.2,0.88-(depth*0.15)));
154
+ }
155
+ }
156
+ current=current.parentElement;
157
+ depth++;
158
+ }
159
+ return out.slice(0,10);
160
+ }
161
+
162
+ function getSiblingPath(el){
163
+ var path=[];
164
+ var current=el;
165
+ var steps=0;
166
+ while(current&&current.parentElement&&steps<8){
167
+ var parent=current.parentElement;
168
+ var index=0;
169
+ var children=parent.children;
170
+ for(var i=0;i<children.length;i++){
171
+ if(children[i]===current){
172
+ index=i;
173
+ break;
174
+ }
175
+ }
176
+ path.unshift(index);
177
+ current=parent;
178
+ steps++;
179
+ }
180
+ return path;
181
+ }
182
+
183
+ function getDomFingerprint(el){
184
+ if(!el||!el.tagName) return null;
185
+ var classes=(el.className&&typeof el.className==='string')
186
+ ? el.className.trim().split(/\\s+/).filter(Boolean).slice(0,6)
187
+ : [];
188
+ return {
189
+ tag:el.tagName.toLowerCase(),
190
+ id:el.id||null,
191
+ classes:classes,
192
+ sibling_path:getSiblingPath(el)
193
+ };
194
+ }
195
+
196
+ function getContainerHint(el){
197
+ if(!el||typeof el.closest!=='function') return null;
198
+ var container=el.closest('main,article,section,form,nav,[role="main"],[data-testid],[id]');
199
+ if(!container||!container.tagName) return null;
200
+ var classes=(container.className&&typeof container.className==='string')
201
+ ? container.className.trim().split(/\\s+/).filter(Boolean).slice(0,4)
202
+ : [];
203
+ return {
204
+ selector:elementSelector(container),
205
+ tag:container.tagName.toLowerCase(),
206
+ id:container.id||null,
207
+ classes:classes
208
+ };
209
+ }
210
+
211
+ function buildCoordFallback(clientX,clientY,metrics){
212
+ var safeViewW=metrics.viewWidth||1;
213
+ var safeViewH=metrics.viewHeight||1;
214
+ var docX=clientX+metrics.scrollX;
215
+ var docY=clientY+metrics.scrollY;
216
+ return {
217
+ x_percent:clampPercent((clientX/safeViewW)*100),
218
+ y_percent:clampPercent((clientY/safeViewH)*100),
219
+ scroll_x:metrics.scrollX,
220
+ scroll_y:metrics.scrollY,
221
+ viewport_width:safeViewW,
222
+ viewport_height:safeViewH,
223
+ doc_width:metrics.docWidth||safeViewW,
224
+ doc_height:metrics.docHeight||safeViewH,
225
+ doc_x_percent:clampPercent((docX/(metrics.docWidth||safeViewW))*100),
226
+ doc_y_percent:clampPercent((docY/(metrics.docHeight||safeViewH))*100)
227
+ };
228
+ }
229
+
230
+ function buildAnchorPayload(el,clientX,clientY){
231
+ var metrics=getMetrics();
232
+ var textSelf='';
233
+ var textParent='';
88
234
  try{
235
+ textSelf=shortText(el&&el.textContent);
236
+ textParent=shortText(el&&el.parentElement&&el.parentElement.textContent);
237
+ }catch(e){}
238
+ return {
239
+ selector_chain:getSelectorChain(el),
240
+ dom_fingerprint:getDomFingerprint(el),
241
+ text_context:{
242
+ self:textSelf,
243
+ parent:textParent
244
+ },
245
+ container_hint:getContainerHint(el),
246
+ coord_fallback:buildCoordFallback(clientX,clientY,metrics)
247
+ };
248
+ }
249
+
250
+ function resolveFromFingerprint(fingerprint){
251
+ if(!fingerprint) return null;
252
+ if(fingerprint.id){
253
+ var byId=document.getElementById(fingerprint.id);
254
+ if(byId) return byId;
255
+ }
256
+ var tag=fingerprint.tag||'*';
257
+ var selector=tag;
258
+ if(Array.isArray(fingerprint.classes)&&fingerprint.classes.length>0){
259
+ selector+= '.'+fingerprint.classes.map(cssEscape).join('.');
260
+ }
261
+ try{
262
+ var nodes=document.querySelectorAll(selector);
263
+ if(nodes&&nodes.length>0) return nodes[0];
264
+ }catch(e){}
265
+ return null;
266
+ }
267
+
268
+ function resolveAnchor(payload){
269
+ if(!payload||typeof payload!=='object') return null;
270
+ var element=null;
271
+ if(Array.isArray(payload.selector_chain)){
272
+ for(var i=0;i<payload.selector_chain.length;i++){
273
+ var candidate=payload.selector_chain[i];
274
+ if(!candidate||typeof candidate.selector!=='string') continue;
275
+ try{
276
+ var found=document.querySelector(candidate.selector);
277
+ if(found){
278
+ element=found;
279
+ break;
280
+ }
281
+ }catch(e){}
282
+ }
283
+ }
284
+ if(!element&&payload.container_hint&&typeof payload.container_hint.selector==='string'){
285
+ try{
286
+ element=document.querySelector(payload.container_hint.selector);
287
+ }catch(e){}
288
+ }
289
+ if(!element){
290
+ element=resolveFromFingerprint(payload.dom_fingerprint);
291
+ }
292
+ if(!element) return null;
293
+ var rect=element.getBoundingClientRect();
294
+ var clientX=rect.left+Math.max(1,Math.min(rect.width-1,rect.width*0.5));
295
+ var clientY=rect.top+Math.max(1,Math.min(rect.height-1,rect.height*0.5));
296
+ var metrics=getMetrics();
297
+ return buildCoordFallback(clientX,clientY,metrics);
298
+ }
299
+
300
+ function setPickMode(enabled){
301
+ var next=!!enabled;
302
+ if(pickMode===next) return;
303
+ pickMode=next;
304
+ document.documentElement.style.cursor=pickMode?'crosshair':'';
305
+ if(document.body){
306
+ document.body.style.cursor=pickMode?'crosshair':'';
307
+ }
308
+ if(!pickMode){
309
+ if(pickClickHandler){
310
+ window.removeEventListener('click',pickClickHandler,true);
311
+ pickClickHandler=null;
312
+ }
313
+ if(pickEscHandler){
314
+ window.removeEventListener('keydown',pickEscHandler,true);
315
+ pickEscHandler=null;
316
+ }
317
+ return;
318
+ }
319
+ pickClickHandler=function(ev){
320
+ try{
321
+ if(!pickMode) return;
322
+ ev.preventDefault();
323
+ ev.stopPropagation();
324
+ if(typeof ev.stopImmediatePropagation==='function'){
325
+ ev.stopImmediatePropagation();
326
+ }
327
+ var target=ev.target&&ev.target.nodeType===1?ev.target:null;
328
+ if(!target) return;
329
+ var payload=buildAnchorPayload(target,ev.clientX,ev.clientY);
330
+ window.parent.postMessage({
331
+ type:'checkpoint:pickResult',
332
+ path:location.pathname+location.search+location.hash,
333
+ anchorPayload:payload,
334
+ point:{
335
+ xPercent:payload.coord_fallback&&typeof payload.coord_fallback.x_percent==='number'?payload.coord_fallback.x_percent:50,
336
+ yPercent:payload.coord_fallback&&typeof payload.coord_fallback.y_percent==='number'?payload.coord_fallback.y_percent:50,
337
+ scrollY:payload.coord_fallback&&typeof payload.coord_fallback.scroll_y==='number'?payload.coord_fallback.scroll_y:0,
338
+ viewHeight:payload.coord_fallback&&typeof payload.coord_fallback.viewport_height==='number'?payload.coord_fallback.viewport_height:0
339
+ }
340
+ },'*');
341
+ }catch(e){}
342
+ setPickMode(false);
343
+ };
344
+ pickEscHandler=function(ev){
345
+ if(ev.key==='Escape'){
346
+ setPickMode(false);
347
+ }
348
+ };
349
+ window.addEventListener('click',pickClickHandler,true);
350
+ window.addEventListener('keydown',pickEscHandler,true);
351
+ }
352
+
353
+ function reportNav(){
354
+ try{
355
+ var p=location.pathname+location.search+location.hash;
356
+ if(p!==lastPath){
357
+ lastPath=p;
358
+ window.parent.postMessage({
359
+ type:'checkpoint:navigate',
360
+ path:p
361
+ },'*');
362
+ reportScroll();
363
+ }
364
+ }catch(e){}
365
+ }
366
+ var scrollTick=false;
367
+ function reportScroll(){
368
+ try{
369
+ var metrics=getMetrics();
89
370
  window.parent.postMessage({
90
- type:'checkpoint:navigate',
91
- path:location.pathname+location.search
92
- },origin);
371
+ type:'checkpoint:scroll',
372
+ scrollX:metrics.scrollX,
373
+ scrollY:metrics.scrollY,
374
+ docWidth:metrics.docWidth,
375
+ docHeight:metrics.docHeight,
376
+ viewWidth:metrics.viewWidth,
377
+ viewHeight:metrics.viewHeight
378
+ },'*');
93
379
  }catch(e){}
94
380
  }
95
- report();
381
+ function onScroll(){
382
+ if(!scrollTick){
383
+ scrollTick=true;
384
+ requestAnimationFrame(function(){
385
+ reportScroll();
386
+ scrollTick=false;
387
+ });
388
+ }
389
+ }
390
+ reportNav();
391
+ reportScroll();
96
392
  var _ps=history.pushState,_rs=history.replaceState;
97
- history.pushState=function(){_ps.apply(this,arguments);report();};
98
- history.replaceState=function(){_rs.apply(this,arguments);report();};
99
- window.addEventListener('popstate',report);
393
+ history.pushState=function(){_ps.apply(this,arguments);reportNav();};
394
+ history.replaceState=function(){_rs.apply(this,arguments);reportNav();};
395
+ window.addEventListener('popstate',reportNav);
396
+ window.addEventListener('hashchange',reportNav);
397
+ window.addEventListener('scroll',onScroll,{passive:true});
398
+ window.addEventListener('resize',reportScroll);
399
+ window.addEventListener('message',function(e){
400
+ try{
401
+ if(e.data&&e.data.type==='checkpoint:scrollTo'&&typeof e.data.y==='number'){
402
+ window.scrollTo({top:e.data.y,behavior:'smooth'});
403
+ }
404
+ if(e.data&&e.data.type==='checkpoint:pickMode'){
405
+ setPickMode(!!e.data.enabled);
406
+ }
407
+ if(e.data&&e.data.type==='checkpoint:resolveAnchors'&&Array.isArray(e.data.items)){
408
+ var positions=[];
409
+ for(var i=0;i<e.data.items.length;i++){
410
+ var item=e.data.items[i];
411
+ if(!item||typeof item.id!=='string') continue;
412
+ var coordFallback=resolveAnchor(item.anchorPayload);
413
+ if(coordFallback){
414
+ positions.push({
415
+ id:item.id,
416
+ found:true,
417
+ coordFallback:coordFallback
418
+ });
419
+ }
420
+ }
421
+ window.parent.postMessage({
422
+ type:'checkpoint:anchorsResolved',
423
+ path:location.pathname+location.search+location.hash,
424
+ positions:positions
425
+ },'*');
426
+ }
427
+ }catch(ex){}
428
+ });
429
+ setInterval(reportNav,500);
100
430
  })();
101
431
  </script>`.trim();
102
432
  }
103
433
  /* ── Injection Proxy ── */
104
434
  function startInjectionProxy(targetPort) {
105
- const APP_URL = (0, config_js_1.getAppUrl)();
106
- const SCRIPT = trackingScript(APP_URL);
435
+ const SCRIPT = trackingScript();
107
436
  return new Promise((resolve, reject) => {
108
437
  const server = http_1.default.createServer((req, res) => {
109
438
  const fwdHeaders = { ...req.headers, host: `localhost:${targetPort}` };
@@ -361,13 +690,22 @@ function loginWithBrowser() {
361
690
  const refresh_token = url.searchParams.get('refresh_token');
362
691
  if (access_token && refresh_token) {
363
692
  // Success page
364
- res.writeHead(200, { 'Content-Type': 'text/html' });
693
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
365
694
  res.end(`
366
695
  <html>
367
- <body style="font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #0a0e17; color: #f1f3f5;">
368
- <div style="text-align: center;">
369
- <h1 style="font-size: 24px;">✓ Logged in to Checkpoint</h1>
370
- <p style="color: #9ca3af;">You can close this tab and return to your terminal.</p>
696
+ <body style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #13120A; color: #f0ede6;">
697
+ <div style="text-align: center; display: flex; flex-direction: column; align-items: center; gap: 16px;">
698
+ <svg width="32" height="32" viewBox="0 0 256 256" fill="none">
699
+ <circle cx="128" cy="128" r="112" fill="#22c55e"/>
700
+ <polyline points="88,136 112,160 168,104" stroke="#13120A" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
701
+ </svg>
702
+ <h1 style="font-size: 20px; font-weight: 500; margin: 0; color: #f0ede6;">Logged in to Checkpoint</h1>
703
+ <p style="color: #9e9889; margin: 0; font-size: 14px;">You can close this tab and return to your terminal.</p>
704
+ <div style="margin-top: 8px; padding-top: 24px; border-top: 1px dashed rgba(73,67,58,0.3);">
705
+ <p style="color: #9e9889; margin: 0px 0 24px; font-size: 13px;">Start sharing your localhost for feedback:</p>
706
+ <code style="padding: 8px 12px; background: #1D1B15; border: 1px solid rgba(73,67,58,0.4); border-radius: 6px; font-family: 'JetBrains Mono','Fira Code','SF Mono',monospace; font-size: 13px; color: #9e9889;">checkpoint start -p <span style="color: #ff5700;">&lt;port&gt;</span></code>
707
+ <p style="text-align: center; margin-top: 24px; font-size: 13px; color: #9e9889;">or <a href="${(0, config_js_1.getAppUrl)()}" style="color: #ff5700; text-decoration: none;">Go to Dashboard</a></p>
708
+ </div>
371
709
  </div>
372
710
  </body>
373
711
  </html>
@@ -376,13 +714,18 @@ function loginWithBrowser() {
376
714
  resolve({ access_token, refresh_token });
377
715
  }
378
716
  else {
379
- res.writeHead(400, { 'Content-Type': 'text/html' });
717
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
380
718
  res.end(`
381
719
  <html>
382
- <body style="font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #0a0e17; color: #f1f3f5;">
383
- <div style="text-align: center;">
384
- <h1 style="font-size: 24px; color: #ef4444;">Login failed</h1>
385
- <p style="color: #9ca3af;">Missing tokens. Please try again.</p>
720
+ <body style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #13120A; color: #f0ede6;">
721
+ <div style="text-align: center; display: flex; flex-direction: column; align-items: center; gap: 16px;">
722
+ <svg width="48" height="48" viewBox="0 0 256 256" fill="none">
723
+ <circle cx="128" cy="128" r="112" fill="#ef4444"/>
724
+ <line x1="96" y1="96" x2="160" y2="160" stroke="#13120A" stroke-width="16" stroke-linecap="round"/>
725
+ <line x1="160" y1="96" x2="96" y2="160" stroke="#13120A" stroke-width="16" stroke-linecap="round"/>
726
+ </svg>
727
+ <h1 style="font-size: 20px; font-weight: 500; margin: 0; color: #f0ede6;">Login failed</h1>
728
+ <p style="color: #9e9889; margin: 0; font-size: 14px;">Missing tokens. Please try again.</p>
386
729
  </div>
387
730
  </body>
388
731
  </html>
@@ -405,10 +748,13 @@ function loginWithBrowser() {
405
748
  const callbackUrl = `http://localhost:${addr.port}/callback`;
406
749
  const APP_URL = (0, config_js_1.getAppUrl)();
407
750
  const loginUrl = `${APP_URL}/cli/auth?callback=${encodeURIComponent(callbackUrl)}`;
408
- console.log(chalk_1.default.gray(` Opening browser...`));
409
751
  console.log(` ${chalk_1.default.cyan(loginUrl)}`);
410
752
  console.log('');
411
- openBrowser(loginUrl);
753
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
754
+ rl.question(chalk_1.default.gray(' Press ENTER to open the browser...'), () => {
755
+ rl.close();
756
+ openBrowser(loginUrl);
757
+ });
412
758
  });
413
759
  // Timeout after 5 minutes
414
760
  setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "checkpoint-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Share your localhost with reviewers — get visual feedback directly on the page",
5
5
  "keywords": [
6
6
  "checkpoint",