claude-live 1.1.1 → 2.0.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.
Files changed (37) hide show
  1. package/.claude-plugin/hooks/hooks.json +161 -0
  2. package/.claude-plugin/marketplace.json +27 -0
  3. package/.claude-plugin/plugin.json +14 -0
  4. package/README.md +82 -0
  5. package/bin/cli.js +18 -0
  6. package/bin/hook.js +24 -0
  7. package/client/dist/assets/BufferResource-B3YcFk1L.js +185 -0
  8. package/client/dist/assets/CanvasRenderer-B7cP3KcG.js +1 -0
  9. package/client/dist/assets/Filter-BXkJkOCD.js +1 -0
  10. package/client/dist/assets/RenderTargetSystem-DkV5EZ2H.js +172 -0
  11. package/client/dist/assets/WebGLRenderer-Cgmusykq.js +156 -0
  12. package/client/dist/assets/WebGPURenderer-B_Gw9-ml.js +41 -0
  13. package/client/dist/assets/browserAll-wXmCMyRg.js +14 -0
  14. package/client/dist/assets/index-BGs_09Jl.js +318 -0
  15. package/client/dist/assets/index-DjcKbX6b.css +1 -0
  16. package/client/dist/assets/webworkerAll-Hyzs6HuJ.js +83 -0
  17. package/client/dist/chords/chord_01.ogg +0 -0
  18. package/client/dist/chords/chord_02.ogg +0 -0
  19. package/client/dist/chords/chord_03.ogg +0 -0
  20. package/client/dist/chords/chord_04.ogg +0 -0
  21. package/client/dist/chords/chord_05.ogg +0 -0
  22. package/client/dist/chords/chord_06.ogg +0 -0
  23. package/client/dist/chords/chord_07.ogg +0 -0
  24. package/client/dist/chords/chord_08.ogg +0 -0
  25. package/client/dist/chords/chord_09.ogg +0 -0
  26. package/client/dist/chords/chord_10.ogg +0 -0
  27. package/client/dist/chords/chord_11.ogg +0 -0
  28. package/client/dist/chords/chord_12.ogg +0 -0
  29. package/client/dist/chords/chord_13.ogg +0 -0
  30. package/client/dist/chords/chord_14.ogg +0 -0
  31. package/client/dist/chords/chord_15.ogg +0 -0
  32. package/client/dist/chords/chord_16.ogg +0 -0
  33. package/client/dist/index.html +20 -0
  34. package/commands/claude-live.md +145 -0
  35. package/package.json +27 -11
  36. package/server/index.js +93 -0
  37. package/bin/claude-live.js +0 -42
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500&family=Space+Mono:wght@400;700&display=swap";*{margin:0;padding:0;box-sizing:border-box}html,body,#root{width:100%;height:100%;overflow:hidden;background:#050507}#root{position:relative}#root:before{content:"";position:fixed;top:0;right:0;bottom:0;left:0;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E");background-size:150px;opacity:.035;pointer-events:none;z-index:10}.hud{position:fixed;top:24px;left:24px;z-index:20;font-family:Space Mono,monospace;pointer-events:none}.hud-title{font-size:17px;font-weight:700;color:#fff;letter-spacing:-.5px;margin-bottom:14px;line-height:1}.hud-title span{color:#4ade80}.hud-stat{display:flex;align-items:baseline;gap:8px;margin-bottom:5px}.hud-label{font-size:9px;color:#666;text-transform:uppercase;letter-spacing:1px;width:52px}.hud-value{font-size:13px;color:#ccc}.hud-tool{font-weight:700}.tooltip{position:fixed;z-index:30;background:#08080ef0;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:10px 14px;pointer-events:none;transition:opacity .1s ease;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);min-width:160px;box-shadow:0 8px 32px #000000b3}.tooltip-label{font-family:Fira Code,monospace;font-size:12px;color:#fff;font-weight:500;margin-bottom:2px;white-space:nowrap;max-width:260px;overflow:hidden;text-overflow:ellipsis}.tooltip-type{font-family:Space Mono,monospace;font-size:9px;color:#888;text-transform:uppercase;letter-spacing:1px;margin-bottom:7px}.tooltip-meta{font-family:Space Mono,monospace;font-size:10px;color:#aaa;margin-bottom:3px}.tooltip-count{font-family:Space Mono,monospace;font-size:9px;color:#666}.sidebar{position:fixed;right:0;top:0;height:100%;width:280px;background:#06060bf5;border-left:1px solid rgba(255,255,255,.08);z-index:25;transform:translate(100%);transition:transform .25s cubic-bezier(.16,1,.3,1);-webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);display:flex;flex-direction:column;overflow:hidden}.sidebar--open{transform:translate(0)}.sidebar-header{padding:24px 20px 16px;border-bottom:1px solid rgba(255,255,255,.06);position:relative}.sidebar-close{position:absolute;top:20px;right:16px;width:26px;height:26px;display:flex;align-items:center;justify-content:center;color:#666;cursor:pointer;font-size:18px;border-radius:4px;transition:color .15s,background .15s;pointer-events:all;line-height:1}.sidebar-close:hover{color:#fff;background:#ffffff14}.sidebar-title{font-family:Fira Code,monospace;font-size:13px;color:#fff;font-weight:500;margin-bottom:4px;padding-right:32px;word-break:break-all}.sidebar-type{font-family:Space Mono,monospace;font-size:9px;color:#666;text-transform:uppercase;letter-spacing:1px}.sidebar-section{padding:16px 20px;border-bottom:1px solid rgba(255,255,255,.05)}.sidebar-section-label{font-family:Space Mono,monospace;font-size:9px;color:#555;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}.sidebar-action{font-family:Fira Code,monospace;font-size:15px;font-weight:500;margin-bottom:3px}.sidebar-time{font-family:Space Mono,monospace;font-size:10px;color:#777}.sidebar-count{font-family:Space Mono,monospace;font-size:24px;color:#ccc;font-weight:700}.sidebar-session{font-family:Fira Code,monospace;font-size:11px;color:#4ade80}.perm-badge{display:inline-flex;align-items:center;gap:5px;font-family:Space Mono,monospace;font-size:9px;color:#fbbf24;background:#fbbf241a;border:1px solid rgba(251,191,36,.3);border-radius:3px;padding:2px 6px;margin-top:6px}.perm-dot{width:5px;height:5px;border-radius:50%;background:#fbbf24;animation:perm-pulse 1s ease-in-out infinite}@keyframes perm-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.7)}}.bottom-left-panel{position:fixed;bottom:24px;left:24px;z-index:20;display:flex;flex-direction:column;gap:10px;pointer-events:none}.bottom-left-panel>*{pointer-events:all}.event-log{display:flex;flex-direction:column;gap:5px;max-width:250px}.event-log-entry{display:flex;align-items:center;gap:5px;position:relative;overflow:hidden;animation:log-entry-in .38s cubic-bezier(.16,1,.3,1)}.event-log-entry:after{content:"";position:absolute;top:0;bottom:0;left:-60%;width:60%;background:linear-gradient(90deg,transparent 0%,var(--entry-color, #888) 60%,transparent 100%);opacity:.22;animation:log-scan .55s ease-out forwards;pointer-events:none}@keyframes log-entry-in{0%{opacity:0;transform:translate(-16px)}55%{opacity:1;transform:translate(3px)}78%{transform:translate(-1px)}to{opacity:1;transform:translate(0)}}@keyframes log-scan{0%{left:-60%;opacity:.22}to{left:130%;opacity:0}}.event-log-dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;animation:log-dot-pop .42s cubic-bezier(.34,1.56,.64,1)}@keyframes log-dot-pop{0%{transform:scale(0) rotate(-90deg)}70%{transform:scale(1.5) rotate(8deg)}to{transform:scale(1) rotate(0)}}.event-log-tool{font-family:Space Mono,monospace;font-size:8px;font-weight:700;min-width:38px;text-transform:uppercase;letter-spacing:.04em;animation:log-tool-in .3s ease-out}@keyframes log-tool-in{0%{opacity:0;letter-spacing:.28em}to{opacity:1;letter-spacing:.04em}}.event-log-file{font-family:Fira Code,monospace;font-size:8px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100px}.event-log-session{font-family:Fira Code,monospace;font-size:7px;color:#3a3a3a;white-space:nowrap;margin-left:auto}.event-log-entry--static,.event-log-entry--static .event-log-dot,.event-log-entry--static .event-log-tool{animation:none}.event-log-entry--static:after{display:none}.event-log-history{width:280px;height:calc(100vh - 80px);overflow-y:scroll;display:flex;flex-direction:column;gap:5px;background:#020209eb;border:1px solid rgba(255,255,255,.08);border-radius:4px;padding:6px;box-sizing:border-box}.event-log-history-back{font-family:Space Mono,monospace;font-size:8px;color:#555;background:none;border:none;cursor:pointer;text-align:left;padding:0 0 4px}.event-log-history-back:hover{color:#aaa}.event-log-history-btn{font-family:Space Mono,monospace;font-size:7px;color:#333;background:none;border:none;cursor:pointer;text-align:left;padding:2px 0 0 10px}.event-log-history-btn:hover{color:#777}.help-btn{background:none;border:1px solid rgba(255,255,255,.1);border-radius:4px;color:#444;font-family:Space Mono,monospace;font-size:9px;padding:3px 8px;cursor:pointer;width:fit-content;transition:color .15s,border-color .15s;letter-spacing:.05em;text-transform:uppercase}.help-btn:hover{color:#999;border-color:#ffffff40}.top-right-buttons{position:fixed;top:24px;right:24px;z-index:50;display:flex;gap:8px;align-items:center;pointer-events:auto}.audio-toggle{background:none;border:1px solid rgba(255,255,255,.1);border-radius:4px;color:#666;cursor:pointer;transition:color .15s,border-color .15s,background-color .15s;display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;font-size:0}.audio-toggle svg{width:16px;height:16px;stroke:currentColor}.audio-toggle:hover{color:#999;border-color:#ffffff40;background-color:#ffffff0d}.audio-toggle:active{background-color:#ffffff1a}.autofit-toggle{background:none;border:1px solid rgba(255,255,255,.1);color:#ccc;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border-radius:4px;transition:all .2s ease;width:32px;height:32px;padding:0;font-size:0}.autofit-toggle svg{width:16px;height:16px;stroke:currentColor}.autofit-toggle:hover{color:#999;border-color:#ffffff40;background-color:#ffffff0d}.autofit-toggle:active{background-color:#ffffff1a}.hud-button{width:32px;height:32px;background:#ffffff14;border:1px solid rgba(255,255,255,.12);border-radius:4px;color:#666;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;transition:color .2s}.hud-button:hover{color:#aaa}.help-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:60;background:#0000008c;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.help-panel{background:#08080efa;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:20px 24px;min-width:200px;box-shadow:0 16px 48px #000c}.help-panel-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.help-panel-title{font-family:Space Mono,monospace;font-size:9px;color:#555;text-transform:uppercase;letter-spacing:1.5px}.legend-title{font-size:8px;color:#444;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px}.legend-items{display:flex;flex-direction:column;gap:5px}.legend-item{display:flex;align-items:center;gap:8px}.legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.legend-badge{width:14px;height:14px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:7px;font-weight:700;color:#000;flex-shrink:0}.legend-name{font-size:9px;color:#888}.legend-key{font-size:8px;color:#555;margin-left:auto;padding-left:8px}.legend-perm{display:flex;align-items:center;gap:8px;margin-top:4px;padding-top:6px;border-top:1px solid rgba(255,255,255,.04)}.legend-perm-ring{width:14px;height:14px;border-radius:50%;border:2px dashed #fbbf24;flex-shrink:0}.legend-perm-name{font-size:9px;color:#fbbf24}.debug-toggle{position:fixed;bottom:24px;right:24px;z-index:40;background:#fbbf241f;border:1px solid rgba(251,191,36,.4);border-radius:5px;color:#fbbf24e6;font-family:Space Mono,monospace;font-size:10px;font-weight:700;letter-spacing:.15em;padding:6px 12px;cursor:pointer;transition:color .15s,background .15s,border-color .15s}.debug-toggle:hover{background:#fbbf2438;border-color:#fbbf24b3;color:#fbbf24}.debug-panel{position:fixed;bottom:56px;right:24px;width:260px;background:#06060cf7;border:1px solid rgba(255,255,255,.1);border-radius:8px;z-index:40;-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);box-shadow:0 16px 48px #000c;transform:translateY(8px) scale(.97);opacity:0;pointer-events:none;transition:opacity .18s ease,transform .18s ease}.debug-panel--open{opacity:1;transform:translateY(0) scale(1);pointer-events:all}.debug-panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 14px 10px;border-bottom:1px solid rgba(255,255,255,.07)}.debug-panel-title{font-family:Space Mono,monospace;font-size:9px;color:#555;text-transform:uppercase;letter-spacing:1.5px}.debug-close{background:none;border:none;color:#555;font-size:16px;cursor:pointer;padding:0 2px;line-height:1;transition:color .15s}.debug-close:hover{color:#ccc}.debug-section{padding:10px 14px;border-bottom:1px solid rgba(255,255,255,.05)}.debug-section:last-child{border-bottom:none}.debug-section-label{font-family:Space Mono,monospace;font-size:8px;color:#444;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px}.debug-session-row{display:flex;gap:6px}.debug-select{flex:1;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:4px;color:#aaa;font-family:Fira Code,monospace;font-size:10px;padding:4px 7px;outline:none;cursor:pointer}.debug-new-btn{background:#ffffff0f;border:1px solid rgba(255,255,255,.12);border-radius:4px;color:#888;font-family:Space Mono,monospace;font-size:9px;padding:4px 8px;cursor:pointer;white-space:nowrap;transition:color .15s,background .15s}.debug-new-btn:hover{color:#ccc;background:#ffffff1a}.debug-tool-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:5px}.debug-tool-btn{background:#ffffff0a;border:1px solid rgba(255,255,255,.08);border-radius:4px;color:var(--tool-color, #888);font-family:Space Mono,monospace;font-size:9px;padding:6px 4px;cursor:pointer;transition:background .15s,border-color .15s,opacity .15s;text-align:center}.debug-tool-btn:hover{background:color-mix(in srgb,var(--tool-color, #888) 15%,transparent);border-color:color-mix(in srgb,var(--tool-color, #888) 40%,transparent)}.debug-tool-btn:active{opacity:.6}.perm-notifications{position:fixed;left:24px;top:130px;display:flex;flex-direction:column;gap:6px;z-index:100;pointer-events:none;max-width:280px}.perm-notifications-title{font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:#fbbf24;text-align:left;margin-bottom:2px;opacity:.7}.perm-notification-item{display:flex;align-items:flex-start;gap:10px;background:#fbbf240f;border:1px solid rgba(251,191,36,.25);border-radius:3px;padding:8px 14px;animation:perm-fadein .3s ease}@keyframes perm-fadein{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.perm-notification-dot{width:6px;height:6px;border-radius:50%;background:#fbbf24;margin-top:3px;flex-shrink:0;animation:perm-pulse 1s ease-in-out infinite}.perm-notification-body{display:flex;flex-direction:column;gap:2px}.perm-notification-session{font-size:9px;letter-spacing:.15em;color:#fbbf2499;text-transform:uppercase}.perm-notification-msg{font-size:11px;color:#fbbf24e6;max-width:320px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-overlay{position:fixed;top:24px;right:24px;max-width:400px;max-height:calc(100vh - 48px);background:#020209f2;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:16px;overflow-y:auto;z-index:100;font-size:12px;line-height:1.6;color:#ccc}.panel-close,.panel-close-btn{position:absolute;top:8px;right:8px;background:none;border:none;color:#666;cursor:pointer;font-size:16px;padding:0;width:24px;height:24px}.panel-close:hover,.panel-close-btn:hover{color:#aaa}.panel-overlay h3{margin:0 0 12px;font-size:14px;font-weight:600;color:#fff}.panel-overlay h4{margin:8px 0 6px;font-size:12px;font-weight:600;color:#aaa;text-transform:uppercase;letter-spacing:.05em}.panel-section{margin-bottom:16px}.panel-section:last-child{margin-bottom:0}.node-types-grid{display:flex;flex-direction:column;gap:8px}.node-type-item{display:flex;align-items:center;gap:8px}.node-type-badge{width:28px;height:28px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;color:#fff;flex-shrink:0}.node-type-info{display:flex;flex-direction:column;gap:2px}.node-type-name{font-size:12px;font-weight:500;color:#fff}.node-type-description{font-size:10px;color:#888}.animations-list{display:flex;flex-direction:column;gap:10px}.animation-item{font-size:11px}.animation-name{font-weight:500;color:#fff;margin-bottom:2px}.animation-description{font-size:10px;color:#888;padding-left:8px;border-left:2px solid rgba(255,255,255,.1)}
@@ -0,0 +1,83 @@
1
+ import{G,b as I,i as _,M as k,K as B,z as O,$ as m,T as v,ai as A,R as C,w as z,_ as E,p as w}from"./index-BGs_09Jl.js";import{F as U}from"./Filter-BXkJkOCD.js";var M=`in vec2 aPosition;
2
+ out vec2 vTextureCoord;
3
+
4
+ uniform vec4 uInputSize;
5
+ uniform vec4 uOutputFrame;
6
+ uniform vec4 uOutputTexture;
7
+
8
+ vec4 filterVertexPosition( void )
9
+ {
10
+ vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
11
+
12
+ position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
13
+ position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
14
+
15
+ return vec4(position, 0.0, 1.0);
16
+ }
17
+
18
+ vec2 filterTextureCoord( void )
19
+ {
20
+ return aPosition * (uOutputFrame.zw * uInputSize.zw);
21
+ }
22
+
23
+ void main(void)
24
+ {
25
+ gl_Position = filterVertexPosition();
26
+ vTextureCoord = filterTextureCoord();
27
+ }
28
+ `,V=`in vec2 vTextureCoord;
29
+ out vec4 finalColor;
30
+ uniform sampler2D uTexture;
31
+ void main() {
32
+ finalColor = texture(uTexture, vTextureCoord);
33
+ }
34
+ `,y=`struct GlobalFilterUniforms {
35
+ uInputSize: vec4<f32>,
36
+ uInputPixel: vec4<f32>,
37
+ uInputClamp: vec4<f32>,
38
+ uOutputFrame: vec4<f32>,
39
+ uGlobalFrame: vec4<f32>,
40
+ uOutputTexture: vec4<f32>,
41
+ };
42
+
43
+ @group(0) @binding(0) var <uniform> gfu: GlobalFilterUniforms;
44
+ @group(0) @binding(1) var uTexture: texture_2d<f32>;
45
+ @group(0) @binding(2) var uSampler: sampler;
46
+
47
+ struct VSOutput {
48
+ @builtin(position) position: vec4<f32>,
49
+ @location(0) uv: vec2<f32>
50
+ };
51
+
52
+ fn filterVertexPosition(aPosition: vec2<f32>) -> vec4<f32>
53
+ {
54
+ var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;
55
+
56
+ position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;
57
+ position.y = position.y * (2.0 * gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;
58
+
59
+ return vec4(position, 0.0, 1.0);
60
+ }
61
+
62
+ fn filterTextureCoord(aPosition: vec2<f32>) -> vec2<f32>
63
+ {
64
+ return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);
65
+ }
66
+
67
+ @vertex
68
+ fn mainVertex(
69
+ @location(0) aPosition: vec2<f32>,
70
+ ) -> VSOutput {
71
+ return VSOutput(
72
+ filterVertexPosition(aPosition),
73
+ filterTextureCoord(aPosition)
74
+ );
75
+ }
76
+
77
+ @fragment
78
+ fn mainFragment(
79
+ @location(0) uv: vec2<f32>,
80
+ ) -> @location(0) vec4<f32> {
81
+ return textureSample(uTexture, uSampler, uv);
82
+ }
83
+ `;class Y extends U{constructor(){const e=G.from({vertex:{source:y,entryPoint:"mainVertex"},fragment:{source:y,entryPoint:"mainFragment"},name:"passthrough-filter"}),t=I.from({vertex:M,fragment:V,name:"passthrough-filter"});super({gpuProgram:e,glProgram:t})}}class R{constructor(e){this._renderer=e}push(e,t,r){this._renderer.renderPipes.batch.break(r),r.add({renderPipeId:"filter",canBundle:!1,action:"pushFilter",container:t,filterEffect:e})}pop(e,t,r){this._renderer.renderPipes.batch.break(r),r.add({renderPipeId:"filter",action:"popFilter",canBundle:!1})}execute(e){e.action==="pushFilter"?this._renderer.filter.push(e):e.action==="popFilter"&&this._renderer.filter.pop()}destroy(){this._renderer=null}}R.extension={type:[_.WebGLPipes,_.WebGPUPipes,_.CanvasPipes],name:"filter"};const P=new k;function X(T,e){e.clear();const t=e.matrix;for(let r=0;r<T.length;r++){const n=T[r];if(n.globalDisplayStatus<7)continue;const i=n.renderGroup??n.parentRenderGroup;i!=null&&i.isCachedAsTexture?e.matrix=P.copyFrom(i.textureOffsetInverseTransform).append(n.worldTransform):i!=null&&i._parentCacheAsTextureRenderGroup?e.matrix=P.copyFrom(i._parentCacheAsTextureRenderGroup.inverseWorldTransform).append(n.groupTransform):e.matrix=n.worldTransform,e.addBounds(n.bounds)}return e.matrix=t,e}const q=new A({attributes:{aPosition:{buffer:new Float32Array([0,0,1,0,1,1,0,1]),format:"float32x2",stride:2*4,offset:0}},indexBuffer:new Uint32Array([0,1,2,0,2,3])});class W{constructor(){this.skip=!1,this.inputTexture=null,this.backTexture=null,this.filters=null,this.bounds=new E,this.container=null,this.blendRequired=!1,this.outputRenderSurface=null,this.globalFrame={x:0,y:0,width:0,height:0},this.firstEnabledIndex=-1,this.lastEnabledIndex=-1}}class S{constructor(e){this._filterStackIndex=0,this._filterStack=[],this._filterGlobalUniforms=new B({uInputSize:{value:new Float32Array(4),type:"vec4<f32>"},uInputPixel:{value:new Float32Array(4),type:"vec4<f32>"},uInputClamp:{value:new Float32Array(4),type:"vec4<f32>"},uOutputFrame:{value:new Float32Array(4),type:"vec4<f32>"},uGlobalFrame:{value:new Float32Array(4),type:"vec4<f32>"},uOutputTexture:{value:new Float32Array(4),type:"vec4<f32>"}}),this._globalFilterBindGroup=new O({}),this.renderer=e}get activeBackTexture(){var e;return(e=this._activeFilterData)==null?void 0:e.backTexture}push(e){const t=this.renderer,r=e.filterEffect.filters,n=this._pushFilterData();n.skip=!1,n.filters=r,n.container=e.container,n.outputRenderSurface=t.renderTarget.renderSurface;const i=t.renderTarget.renderTarget.colorTexture.source,o=i.resolution,s=i.antialias;if(r.every(d=>!d.enabled)){n.skip=!0;return}const c=n.bounds;if(this._calculateFilterArea(e,c),this._calculateFilterBounds(n,t.renderTarget.rootViewPort,s,o,1),n.skip)return;const u=this._getPreviousFilterData(),f=this._findFilterResolution(o);let l=0,a=0;u&&(l=u.bounds.minX,a=u.bounds.minY),this._calculateGlobalFrame(n,l,a,f,i.width,i.height),this._setupFilterTextures(n,c,t,u)}generateFilteredTexture({texture:e,filters:t}){const r=this._pushFilterData();this._activeFilterData=r,r.skip=!1,r.filters=t;const n=e.source,i=n.resolution,o=n.antialias;if(t.every(d=>!d.enabled))return r.skip=!0,e;const s=r.bounds;if(s.addRect(e.frame),this._calculateFilterBounds(r,s.rectangle,o,i,0),r.skip)return e;const c=i;this._calculateGlobalFrame(r,0,0,c,n.width,n.height),r.outputRenderSurface=m.getOptimalTexture(s.width,s.height,r.resolution,r.antialias),r.backTexture=v.EMPTY,r.inputTexture=e,this.renderer.renderTarget.finishRenderPass(),this._applyFiltersToTexture(r,!0);const a=r.outputRenderSurface;return a.source.alphaMode="premultiplied-alpha",a}pop(){const e=this.renderer,t=this._popFilterData();t.skip||(e.globalUniforms.pop(),e.renderTarget.finishRenderPass(),this._activeFilterData=t,this._applyFiltersToTexture(t,!1),t.blendRequired&&m.returnTexture(t.backTexture),m.returnTexture(t.inputTexture))}getBackTexture(e,t,r){const n=e.colorTexture.source._resolution,i=m.getOptimalTexture(t.width,t.height,n,!1);let o=t.minX,s=t.minY;r&&(o-=r.minX,s-=r.minY),o=Math.floor(o*n),s=Math.floor(s*n);const c=Math.ceil(t.width*n),u=Math.ceil(t.height*n);return this.renderer.renderTarget.copyToTexture(e,i,{x:o,y:s},{width:c,height:u},{x:0,y:0}),i}applyFilter(e,t,r,n){const i=this.renderer,o=this._activeFilterData,c=o.outputRenderSurface===r,u=i.renderTarget.rootRenderTarget.colorTexture.source._resolution,f=this._findFilterResolution(u);let l=0,a=0;if(c){const p=this._findPreviousFilterOffset();l=p.x,a=p.y}this._updateFilterUniforms(t,r,o,l,a,f,c,n);const d=e.enabled?e:this._getPassthroughFilter();this._setupBindGroupsAndRender(d,t,i)}calculateSpriteMatrix(e,t){const r=this._activeFilterData,n=e.set(r.inputTexture._source.width,0,0,r.inputTexture._source.height,r.bounds.minX,r.bounds.minY),i=t.worldTransform.copyTo(k.shared),o=t.renderGroup||t.parentRenderGroup;return o&&o.cacheToLocalTransform&&i.prepend(o.cacheToLocalTransform),i.invert(),n.prepend(i),n.scale(1/t.texture.orig.width,1/t.texture.orig.height),n.translate(t.anchor.x,t.anchor.y),n}destroy(){var e;(e=this._passthroughFilter)==null||e.destroy(!0),this._passthroughFilter=null}_getPassthroughFilter(){return this._passthroughFilter??(this._passthroughFilter=new Y),this._passthroughFilter}_setupBindGroupsAndRender(e,t,r){if(r.renderPipes.uniformBatch){const n=r.renderPipes.uniformBatch.getUboResource(this._filterGlobalUniforms);this._globalFilterBindGroup.setResource(n,0)}else this._globalFilterBindGroup.setResource(this._filterGlobalUniforms,0);this._globalFilterBindGroup.setResource(t.source,1),this._globalFilterBindGroup.setResource(t.source.style,2),e.groups[0]=this._globalFilterBindGroup,r.encoder.draw({geometry:q,shader:e,state:e._state,topology:"triangle-list"}),r.type===C.WEBGL&&r.renderTarget.finishRenderPass()}_setupFilterTextures(e,t,r,n){if(e.backTexture=v.EMPTY,e.inputTexture=m.getOptimalTexture(t.width,t.height,e.resolution,e.antialias),e.blendRequired){r.renderTarget.finishRenderPass();const i=r.renderTarget.getRenderTarget(e.outputRenderSurface);e.backTexture=this.getBackTexture(i,t,n==null?void 0:n.bounds)}r.renderTarget.bind(e.inputTexture,!0),r.globalUniforms.push({offset:t})}_calculateGlobalFrame(e,t,r,n,i,o){const s=e.globalFrame;s.x=t*n,s.y=r*n,s.width=i*n,s.height=o*n}_updateFilterUniforms(e,t,r,n,i,o,s,c){const u=this._filterGlobalUniforms.uniforms,f=u.uOutputFrame,l=u.uInputSize,a=u.uInputPixel,d=u.uInputClamp,p=u.uGlobalFrame,x=u.uOutputTexture;s?(f[0]=r.bounds.minX-n,f[1]=r.bounds.minY-i):(f[0]=0,f[1]=0),f[2]=e.frame.width,f[3]=e.frame.height,l[0]=e.source.width,l[1]=e.source.height,l[2]=1/l[0],l[3]=1/l[1],a[0]=e.source.pixelWidth,a[1]=e.source.pixelHeight,a[2]=1/a[0],a[3]=1/a[1],d[0]=.5*a[2],d[1]=.5*a[3],d[2]=e.frame.width*l[2]-.5*a[2],d[3]=e.frame.height*l[3]-.5*a[3];const b=this.renderer.renderTarget.rootRenderTarget.colorTexture;p[0]=n*o,p[1]=i*o,p[2]=b.source.width*o,p[3]=b.source.height*o,t instanceof v&&(t.source.resource=null);const g=this.renderer.renderTarget.getRenderTarget(t);this.renderer.renderTarget.bind(t,!!c),t instanceof v?(x[0]=t.frame.width,x[1]=t.frame.height):(x[0]=g.width,x[1]=g.height),x[2]=g.isRoot?-1:1,this._filterGlobalUniforms.update()}_findFilterResolution(e){let t=this._filterStackIndex-1;for(;t>0&&this._filterStack[t].skip;)--t;return t>0&&this._filterStack[t].inputTexture?this._filterStack[t].inputTexture.source._resolution:e}_findPreviousFilterOffset(){let e=0,t=0,r=this._filterStackIndex;for(;r>0;){r--;const n=this._filterStack[r];if(!n.skip){e=n.bounds.minX,t=n.bounds.minY;break}}return{x:e,y:t}}_calculateFilterArea(e,t){if(e.renderables?X(e.renderables,t):e.filterEffect.filterArea?(t.clear(),t.addRect(e.filterEffect.filterArea),t.applyMatrix(e.container.worldTransform)):e.container.getFastGlobalBounds(!0,t),e.container){const n=(e.container.renderGroup||e.container.parentRenderGroup).cacheToLocalTransform;n&&t.applyMatrix(n)}}_applyFiltersToTexture(e,t){const r=e.inputTexture,n=e.bounds,i=e.filters,o=e.firstEnabledIndex,s=e.lastEnabledIndex;if(this._globalFilterBindGroup.setResource(r.source.style,2),this._globalFilterBindGroup.setResource(e.backTexture.source,3),o===s)i[o].apply(this,r,e.outputRenderSurface,t);else{let c=e.inputTexture;const u=m.getOptimalTexture(n.width,n.height,c.source._resolution,!1);let f=u;for(let l=o;l<s;l++){const a=i[l];if(!a.enabled)continue;a.apply(this,c,f,!0);const d=c;c=f,f=d}i[s].apply(this,c,e.outputRenderSurface,t),m.returnTexture(u)}}_calculateFilterBounds(e,t,r,n,i){var g;const o=this.renderer,s=e.bounds,c=e.filters;let u=1/0,f=0,l=!0,a=!1,d=!1,p=!0,x=-1,b=-1;for(let F=0;F<c.length;F++){const h=c[F];if(!h.enabled)continue;if(x===-1&&(x=F),b=F,u=Math.min(u,h.resolution==="inherit"?n:h.resolution),f+=h.padding,h.antialias==="off"?l=!1:h.antialias==="inherit"&&l&&(l=r),h.clipToViewport||(p=!1),!!!(h.compatibleRenderers&o.type)){d=!1;break}if(h.blendRequired&&!(((g=o.backBuffer)==null?void 0:g.useBackBuffer)??!0)){z("Blend filter requires backBuffer on WebGL renderer to be enabled. Set `useBackBuffer: true` in the renderer options."),d=!1;break}d=!0,a||(a=h.blendRequired)}if(!d){e.skip=!0;return}if(p&&s.fitBounds(0,t.width/n,0,t.height/n),s.scale(u).ceil().scale(1/u).pad((f|0)*i),!s.isPositive){e.skip=!0;return}e.antialias=l,e.resolution=u,e.blendRequired=a,e.firstEnabledIndex=x,e.lastEnabledIndex=b}_popFilterData(){return this._filterStackIndex--,this._filterStack[this._filterStackIndex]}_getPreviousFilterData(){let e,t=this._filterStackIndex-1;for(;t>0&&(t--,e=this._filterStack[t],!!e.skip););return e}_pushFilterData(){let e=this._filterStack[this._filterStackIndex];return e||(e=this._filterStack[this._filterStackIndex]=new W),this._filterStackIndex++,e}}S.extension={type:[_.WebGLSystem,_.WebGPUSystem],name:"filter"};w.add(S);w.add(R);
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>claude-live</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <style>
10
+ * { margin: 0; padding: 0; box-sizing: border-box; }
11
+ body { background: #080808; overflow: hidden; }
12
+ #root { width: 100vw; height: 100vh; }
13
+ </style>
14
+ <script type="module" crossorigin src="/assets/index-BGs_09Jl.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-DjcKbX6b.css">
16
+ </head>
17
+ <body>
18
+ <div id="root"></div>
19
+ </body>
20
+ </html>
@@ -0,0 +1,145 @@
1
+ ---
2
+ allowed-tools: Bash(pkill:*), Bash(curl:*), Bash(npx:*), Bash(node:*), Bash(ps:*), Bash(tail:*), Bash(grep:*), Bash(sed:*), Bash(mkdir:*), Bash(rm:*), Bash(cat:*)
3
+ description: Control and inspect the claude-live visualizer server
4
+ ---
5
+
6
+ Manage the claude-live server. The server always runs on port 43451.
7
+
8
+ User's argument: $ARGUMENTS
9
+
10
+ ## Subcommands
11
+
12
+ - **no argument / status**: Run the status checks below and display results
13
+ - **stop**: Run `pkill -f "node.*server/index.js"` and confirm stopped
14
+ - **start**: Start with `node "${CLAUDE_PLUGIN_ROOT}/server/index.js" >/tmp/claude-live.log 2>&1 &`, wait 1s, then show status
15
+ - **restart**: Stop then start
16
+ - **logs**: Show `tail -30 /tmp/claude-live.log`
17
+ - **config**: Display current endpoint URL (shows which config source is active)
18
+ - **config `<url>`**: Set global endpoint URL, auto-create `~/.config/claude-live/` if needed
19
+ - **config reset**: Reset to default endpoint (`http://localhost:43451`), delete global config file
20
+
21
+ ## Status output (default and after start)
22
+
23
+ Run these and display in a compact summary:
24
+
25
+ ```
26
+ curl -sf http://localhost:43451/events -o /dev/null -m 1 2>&1 && echo "● running on port 43451 — http://localhost:43451" || echo "○ stopped"
27
+ ps -eo pid,etime,cmd | grep "node.*server/index.js" | grep -v grep
28
+ tail -5 /tmp/claude-live.log 2>/dev/null
29
+ ```
30
+
31
+ Show: running/stopped, port (always 43451), PID and uptime if running, last 5 log lines.
32
+
33
+ ## Config Management
34
+
35
+ The `/claude-live config` subcommands manage where hooks send events.
36
+
37
+ ### Display current URL
38
+ ```
39
+ /claude-live config
40
+ ```
41
+ Output example: `● Configured: http://192.168.1.50:43451 (from global config)`
42
+
43
+ Possible sources shown:
44
+ - "from environment (CLAUDE_LIVE_URL)"
45
+ - "from project config (.claude/claude-live.json)"
46
+ - "from global config (~/.config/claude-live/config.json)"
47
+ - "default (localhost:43451)"
48
+
49
+ ### Set global URL
50
+ ```
51
+ /claude-live config http://192.168.1.50:43451
52
+ ```
53
+ Output: `✓ URL set to http://192.168.1.50:43451`
54
+
55
+ Behavior:
56
+ - Validates URL format (must start with http:// or https://)
57
+ - Auto-creates `~/.config/claude-live/` directory if needed
58
+ - Writes to `~/.config/claude-live/config.json`
59
+ - Shows error if validation fails or write fails
60
+
61
+ Valid URLs:
62
+ - `http://example.com`
63
+ - `http://192.168.1.50:9000`
64
+ - `https://api.example.com:443`
65
+
66
+ Invalid URLs:
67
+ - `localhost:43451` (no http://)
68
+ - `http://` (no host)
69
+ - `ftp://example.com` (not http/https)
70
+
71
+ ### Reset to default
72
+ ```
73
+ /claude-live config reset
74
+ ```
75
+ Output: `✓ Reset to default (localhost:43451)`
76
+
77
+ Behavior:
78
+ - Deletes `~/.config/claude-live/config.json` if it exists
79
+ - Doesn't affect project config or env var (those still take precedence if set)
80
+
81
+ ## Implementation
82
+
83
+ Parse $ARGUMENTS and execute the appropriate subcommand:
84
+
85
+ ```bash
86
+ # Handle config subcommands
87
+ if [[ "$ARGUMENTS" == "config" ]] || [[ "$ARGUMENTS" == config* ]]; then
88
+ if [[ "$ARGUMENTS" == "config reset" ]]; then
89
+ # Reset to default
90
+ rm -f "$HOME/.config/claude-live/config.json"
91
+ echo "✓ Reset to default (localhost:43451)"
92
+ elif [[ "$ARGUMENTS" == "config" ]]; then
93
+ # Display current URL
94
+ URL_SOURCE="default"
95
+ URL="http://localhost:43451"
96
+
97
+ if [ -n "$CLAUDE_LIVE_URL" ]; then
98
+ URL="$CLAUDE_LIVE_URL"
99
+ URL_SOURCE="from environment (CLAUDE_LIVE_URL)"
100
+ elif [ -f ".claude/claude-live.json" ]; then
101
+ PROJECT_URL=$(grep -o '"url" *: *"[^"]*"' .claude/claude-live.json 2>/dev/null | head -1 | sed 's/.*: *"//;s/"//')
102
+ if [ -n "$PROJECT_URL" ]; then
103
+ URL="$PROJECT_URL"
104
+ URL_SOURCE="from project config (.claude/claude-live.json)"
105
+ fi
106
+ elif [ -f "$HOME/.config/claude-live/config.json" ]; then
107
+ GLOBAL_URL=$(grep -o '"url" *: *"[^"]*"' "$HOME/.config/claude-live/config.json" 2>/dev/null | head -1 | sed 's/.*: *"//;s/"//')
108
+ if [ -n "$GLOBAL_URL" ]; then
109
+ URL="$GLOBAL_URL"
110
+ URL_SOURCE="from global config (~/.config/claude-live/config.json)"
111
+ fi
112
+ fi
113
+
114
+ echo "● Configured: $URL ($URL_SOURCE)"
115
+ else
116
+ # Set URL
117
+ NEW_URL="${ARGUMENTS#config }"
118
+
119
+ # Validate URL format
120
+ if ! echo "$NEW_URL" | grep -q '^https\?://[^/]'; then
121
+ echo "✗ Invalid URL format: must start with http:// or https://"
122
+ exit 1
123
+ fi
124
+
125
+ # Create directory
126
+ mkdir -p "$HOME/.config/claude-live/" || {
127
+ echo "✗ Failed to create ~/.config/claude-live/ directory"
128
+ exit 1
129
+ }
130
+
131
+ # Write config
132
+ if ! cat > "$HOME/.config/claude-live/config.json" <<EOF
133
+ {
134
+ "url": "$NEW_URL"
135
+ }
136
+ EOF
137
+ then
138
+ echo "✗ Failed to write config file"
139
+ exit 1
140
+ fi
141
+
142
+ echo "✓ URL set to $NEW_URL"
143
+ fi
144
+ fi
145
+ ```
package/package.json CHANGED
@@ -1,24 +1,40 @@
1
1
  {
2
2
  "name": "claude-live",
3
- "version": "1.1.1",
3
+ "version": "2.0.2",
4
4
  "description": "Realtime Claude Code activity visualizer",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/marisancans/claude-live",
7
+ "type": "module",
7
8
  "bin": {
8
- "claude-live": "bin/claude-live.js"
9
+ "claude-live": "bin/cli.js"
10
+ },
11
+ "scripts": {
12
+ "start": "node server/index.js",
13
+ "dev": "cd client && vite dev",
14
+ "build": "cd client && vite build",
15
+ "test": "cd client && npx tsc --noEmit"
9
16
  },
10
17
  "files": [
11
- "bin/"
18
+ "server/",
19
+ "bin/",
20
+ "client/dist/",
21
+ ".claude-plugin/",
22
+ "commands/"
12
23
  ],
13
- "optionalDependencies": {
14
- "@claude-live/linux-x64": "1.1.1",
15
- "@claude-live/linux-arm64": "1.1.1",
16
- "@claude-live/darwin-x64": "1.1.1",
17
- "@claude-live/darwin-arm64": "1.1.1",
18
- "@claude-live/win32-x64": "1.1.1"
19
- },
20
- "type": "module",
21
24
  "engines": {
22
25
  "node": ">=18"
26
+ },
27
+ "devDependencies": {
28
+ "howler": "^2.2.4",
29
+ "@types/howler": "^2.2.12",
30
+ "@types/react": "^18.2.0",
31
+ "@types/react-dom": "^18.2.0",
32
+ "@vitejs/plugin-react": "^4.2.0",
33
+ "pixi-filters": "^6.0.0",
34
+ "pixi.js": "^8.0.0",
35
+ "react": "^18.2.0",
36
+ "react-dom": "^18.2.0",
37
+ "typescript": "^5.3.0",
38
+ "vite": "^5.1.0"
23
39
  }
24
40
  }
@@ -0,0 +1,93 @@
1
+ import { createServer } from 'http'
2
+ import { readFileSync, existsSync, statSync } from 'fs'
3
+ import { join, extname, resolve, sep } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import { dirname } from 'path'
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url))
8
+ const PORT = parseInt(process.env.PORT || '43451', 10)
9
+ const DIST = process.env.CLAUDE_LIVE_STATIC_DIR
10
+ || join(__dirname, '..', 'client', 'dist')
11
+
12
+ const MIME = {
13
+ '.html': 'text/html',
14
+ '.js': 'application/javascript',
15
+ '.css': 'text/css',
16
+ '.json': 'application/json',
17
+ '.png': 'image/png',
18
+ '.svg': 'image/svg+xml',
19
+ '.ico': 'image/x-icon',
20
+ '.woff': 'font/woff',
21
+ '.woff2': 'font/woff2',
22
+ '.ogg': 'audio/ogg',
23
+ '.wav': 'audio/wav',
24
+ '.mp3': 'audio/mpeg',
25
+ }
26
+
27
+ const clients = new Set()
28
+
29
+ function broadcast(data) {
30
+ const msg = `data: ${JSON.stringify(data)}\n\n`
31
+ for (const res of clients) {
32
+ try { res.write(msg) } catch { clients.delete(res) }
33
+ }
34
+ }
35
+
36
+ const server = createServer((req, res) => {
37
+ // POST /hook — receive event, broadcast to SSE clients
38
+ if (req.method === 'POST' && req.url === '/hook') {
39
+ let body = ''
40
+ req.on('data', c => body += c)
41
+ req.on('end', () => {
42
+ try {
43
+ const event = JSON.parse(body)
44
+ broadcast({ type: 'event', data: event })
45
+ res.writeHead(200, { 'Content-Type': 'application/json' })
46
+ res.end('{"ok":true}')
47
+ } catch {
48
+ res.writeHead(400, { 'Content-Type': 'application/json' })
49
+ res.end('{"error":"invalid json"}')
50
+ }
51
+ })
52
+ return
53
+ }
54
+
55
+ // GET /events — SSE stream
56
+ if (req.method === 'GET' && req.url === '/events') {
57
+ res.writeHead(200, {
58
+ 'Content-Type': 'text/event-stream',
59
+ 'Cache-Control': 'no-cache',
60
+ 'Connection': 'keep-alive',
61
+ })
62
+ res.write(': connected\n\n')
63
+ clients.add(res)
64
+ const heartbeat = setInterval(() => {
65
+ try { res.write(': heartbeat\n\n') } catch { clearInterval(heartbeat); clients.delete(res) }
66
+ }, 15000)
67
+ req.on('close', () => { clearInterval(heartbeat); clients.delete(res) })
68
+ return
69
+ }
70
+
71
+ // Static files
72
+ const urlPath = new URL(req.url, 'http://localhost').pathname
73
+ let filePath = join(DIST, urlPath === '/' ? 'index.html' : urlPath)
74
+ if (!resolve(filePath).startsWith(resolve(DIST) + sep) && resolve(filePath) !== resolve(DIST)) {
75
+ filePath = join(DIST, 'index.html')
76
+ }
77
+ if (!existsSync(filePath) || !statSync(filePath).isFile()) {
78
+ filePath = join(DIST, 'index.html') // SPA fallback
79
+ }
80
+ if (!existsSync(filePath)) {
81
+ res.writeHead(404)
82
+ res.end('Not found')
83
+ return
84
+ }
85
+ const ext = extname(filePath)
86
+ const mime = MIME[ext] || 'application/octet-stream'
87
+ res.writeHead(200, { 'Content-Type': mime })
88
+ res.end(readFileSync(filePath))
89
+ })
90
+
91
+ server.listen(PORT, () => {
92
+ console.log(`claude-live running at http://localhost:${PORT}`)
93
+ })
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env node
2
- import { execFileSync } from "child_process";
3
- import { createRequire } from "module";
4
- import { join } from "path";
5
-
6
- const PLATFORMS = {
7
- "linux-x64": "@claude-live/linux-x64",
8
- "linux-arm64": "@claude-live/linux-arm64",
9
- "darwin-x64": "@claude-live/darwin-x64",
10
- "darwin-arm64": "@claude-live/darwin-arm64",
11
- "win32-x64": "@claude-live/win32-x64",
12
- };
13
-
14
- const key = `${process.platform}-${process.arch}`;
15
- const pkg = PLATFORMS[key];
16
-
17
- if (!pkg) {
18
- console.error(`Unsupported platform: ${key}`);
19
- console.error(`Supported: ${Object.keys(PLATFORMS).join(", ")}`);
20
- process.exit(1);
21
- }
22
-
23
- const require = createRequire(import.meta.url);
24
- let binPath;
25
- try {
26
- const pkgDir = join(require.resolve(`${pkg}/package.json`), "..");
27
- const ext = process.platform === "win32" ? ".exe" : "";
28
- binPath = join(pkgDir, `bin/claude-live${ext}`);
29
- } catch {
30
- console.error(`Platform package ${pkg} not installed.`);
31
- console.error(`Try: npm install ${pkg}`);
32
- process.exit(1);
33
- }
34
-
35
- try {
36
- const result = execFileSync(binPath, process.argv.slice(2), {
37
- stdio: "inherit",
38
- env: { ...process.env, CLAUDE_LIVE_STATIC_DIR: join(binPath, "../../client/dist") },
39
- });
40
- } catch (e) {
41
- process.exit(e.status ?? 1);
42
- }