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.
- package/.claude-plugin/hooks/hooks.json +161 -0
- package/.claude-plugin/marketplace.json +27 -0
- package/.claude-plugin/plugin.json +14 -0
- package/README.md +82 -0
- package/bin/cli.js +18 -0
- package/bin/hook.js +24 -0
- package/client/dist/assets/BufferResource-B3YcFk1L.js +185 -0
- package/client/dist/assets/CanvasRenderer-B7cP3KcG.js +1 -0
- package/client/dist/assets/Filter-BXkJkOCD.js +1 -0
- package/client/dist/assets/RenderTargetSystem-DkV5EZ2H.js +172 -0
- package/client/dist/assets/WebGLRenderer-Cgmusykq.js +156 -0
- package/client/dist/assets/WebGPURenderer-B_Gw9-ml.js +41 -0
- package/client/dist/assets/browserAll-wXmCMyRg.js +14 -0
- package/client/dist/assets/index-BGs_09Jl.js +318 -0
- package/client/dist/assets/index-DjcKbX6b.css +1 -0
- package/client/dist/assets/webworkerAll-Hyzs6HuJ.js +83 -0
- package/client/dist/chords/chord_01.ogg +0 -0
- package/client/dist/chords/chord_02.ogg +0 -0
- package/client/dist/chords/chord_03.ogg +0 -0
- package/client/dist/chords/chord_04.ogg +0 -0
- package/client/dist/chords/chord_05.ogg +0 -0
- package/client/dist/chords/chord_06.ogg +0 -0
- package/client/dist/chords/chord_07.ogg +0 -0
- package/client/dist/chords/chord_08.ogg +0 -0
- package/client/dist/chords/chord_09.ogg +0 -0
- package/client/dist/chords/chord_10.ogg +0 -0
- package/client/dist/chords/chord_11.ogg +0 -0
- package/client/dist/chords/chord_12.ogg +0 -0
- package/client/dist/chords/chord_13.ogg +0 -0
- package/client/dist/chords/chord_14.ogg +0 -0
- package/client/dist/chords/chord_15.ogg +0 -0
- package/client/dist/chords/chord_16.ogg +0 -0
- package/client/dist/index.html +20 -0
- package/commands/claude-live.md +145 -0
- package/package.json +27 -11
- package/server/index.js +93 -0
- 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": "
|
|
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/
|
|
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
|
-
"
|
|
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
|
}
|
package/server/index.js
ADDED
|
@@ -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
|
+
})
|
package/bin/claude-live.js
DELETED
|
@@ -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
|
-
}
|