@cef-ai/cli 0.2.7 → 0.4.0

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 (153) hide show
  1. package/README.md +75 -1247
  2. package/dist/build/duration.d.ts +22 -0
  3. package/dist/build/duration.d.ts.map +1 -0
  4. package/dist/build/duration.js +44 -0
  5. package/dist/build/duration.js.map +1 -0
  6. package/dist/build/esbuild-plugin.d.ts +32 -0
  7. package/dist/build/esbuild-plugin.d.ts.map +1 -0
  8. package/dist/build/esbuild-plugin.js +53 -0
  9. package/dist/build/esbuild-plugin.js.map +1 -0
  10. package/dist/build/lints.d.ts +55 -0
  11. package/dist/build/lints.d.ts.map +1 -0
  12. package/dist/build/lints.js +214 -0
  13. package/dist/build/lints.js.map +1 -0
  14. package/dist/build/load-config.d.ts +114 -0
  15. package/dist/build/load-config.d.ts.map +1 -0
  16. package/dist/build/load-config.js +150 -0
  17. package/dist/build/load-config.js.map +1 -0
  18. package/dist/build/manifest.d.ts +156 -0
  19. package/dist/build/manifest.d.ts.map +1 -0
  20. package/dist/build/manifest.js +111 -0
  21. package/dist/build/manifest.js.map +1 -0
  22. package/dist/build/targeting.d.ts +15 -0
  23. package/dist/build/targeting.d.ts.map +1 -0
  24. package/dist/build/targeting.js +69 -0
  25. package/dist/build/targeting.js.map +1 -0
  26. package/dist/cli-program.d.ts +14 -0
  27. package/dist/cli-program.d.ts.map +1 -0
  28. package/dist/cli-program.js +171 -0
  29. package/dist/cli-program.js.map +1 -0
  30. package/dist/cli.d.ts +2 -2
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +25 -65
  33. package/dist/cli.js.map +1 -0
  34. package/dist/commands/build.d.ts +41 -0
  35. package/dist/commands/build.d.ts.map +1 -0
  36. package/dist/commands/build.js +252 -0
  37. package/dist/commands/build.js.map +1 -0
  38. package/dist/commands/inspect.d.ts +49 -0
  39. package/dist/commands/inspect.d.ts.map +1 -0
  40. package/dist/commands/inspect.js +135 -0
  41. package/dist/commands/inspect.js.map +1 -0
  42. package/dist/commands/keypair.d.ts +21 -0
  43. package/dist/commands/keypair.d.ts.map +1 -0
  44. package/dist/commands/keypair.js +91 -0
  45. package/dist/commands/keypair.js.map +1 -0
  46. package/dist/commands/publish.d.ts +40 -0
  47. package/dist/commands/publish.d.ts.map +1 -0
  48. package/dist/commands/publish.js +187 -0
  49. package/dist/commands/publish.js.map +1 -0
  50. package/dist/commands/typegen.d.ts +36 -0
  51. package/dist/commands/typegen.d.ts.map +1 -0
  52. package/dist/commands/typegen.js +210 -0
  53. package/dist/commands/typegen.js.map +1 -0
  54. package/dist/index.d.ts +6 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +6 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/lib/cid.d.ts +3 -0
  59. package/dist/lib/cid.d.ts.map +1 -0
  60. package/dist/lib/cid.js +43 -0
  61. package/dist/lib/cid.js.map +1 -0
  62. package/dist/lib/credentials.d.ts +40 -16
  63. package/dist/lib/credentials.d.ts.map +1 -0
  64. package/dist/lib/credentials.js +222 -49
  65. package/dist/lib/credentials.js.map +1 -0
  66. package/dist/lib/jcs.d.ts +3 -0
  67. package/dist/lib/jcs.d.ts.map +1 -0
  68. package/dist/lib/jcs.js +48 -0
  69. package/dist/lib/jcs.js.map +1 -0
  70. package/dist/lib/signing.d.ts +13 -0
  71. package/dist/lib/signing.d.ts.map +1 -0
  72. package/dist/lib/signing.js +34 -0
  73. package/dist/lib/signing.js.map +1 -0
  74. package/dist/lib.d.ts +18 -0
  75. package/dist/lib.d.ts.map +1 -0
  76. package/dist/lib.js +12 -0
  77. package/dist/lib.js.map +1 -0
  78. package/dist/typegen/augment.d.ts +57 -0
  79. package/dist/typegen/augment.d.ts.map +1 -0
  80. package/dist/typegen/augment.js +87 -0
  81. package/dist/typegen/augment.js.map +1 -0
  82. package/dist/typegen/fetch.d.ts +30 -0
  83. package/dist/typegen/fetch.d.ts.map +1 -0
  84. package/dist/typegen/fetch.js +38 -0
  85. package/dist/typegen/fetch.js.map +1 -0
  86. package/dist/typegen/lockfile.d.ts +42 -0
  87. package/dist/typegen/lockfile.d.ts.map +1 -0
  88. package/dist/typegen/lockfile.js +58 -0
  89. package/dist/typegen/lockfile.js.map +1 -0
  90. package/dist/typegen/schema-to-ts.d.ts +21 -0
  91. package/dist/typegen/schema-to-ts.d.ts.map +1 -0
  92. package/dist/typegen/schema-to-ts.js +29 -0
  93. package/dist/typegen/schema-to-ts.js.map +1 -0
  94. package/package.json +23 -58
  95. package/dist/commands/auth/index.d.ts +0 -2
  96. package/dist/commands/auth/index.js +0 -162
  97. package/dist/commands/deploy/clone.d.ts +0 -13
  98. package/dist/commands/deploy/clone.js +0 -143
  99. package/dist/commands/deploy/delete.d.ts +0 -14
  100. package/dist/commands/deploy/delete.js +0 -134
  101. package/dist/commands/deploy/deploy.d.ts +0 -24
  102. package/dist/commands/deploy/deploy.js +0 -654
  103. package/dist/commands/deploy/index.d.ts +0 -2
  104. package/dist/commands/deploy/index.js +0 -216
  105. package/dist/commands/deploy/status.d.ts +0 -14
  106. package/dist/commands/deploy/status.js +0 -61
  107. package/dist/commands/dev/index.d.ts +0 -2
  108. package/dist/commands/dev/index.js +0 -134
  109. package/dist/commands/dev/server.d.ts +0 -14
  110. package/dist/commands/dev/server.js +0 -195
  111. package/dist/commands/dev/static/app.js +0 -633
  112. package/dist/commands/dev/static/index.html +0 -205
  113. package/dist/commands/dev/static/static/app.js +0 -633
  114. package/dist/commands/dev/static/static/index.html +0 -205
  115. package/dist/commands/dev/static/static/style.css +0 -184
  116. package/dist/commands/dev/static/style.css +0 -184
  117. package/dist/commands/playground/index.d.ts +0 -2
  118. package/dist/commands/playground/index.js +0 -75
  119. package/dist/commands/playground/test.d.ts +0 -15
  120. package/dist/commands/playground/test.js +0 -166
  121. package/dist/commands/service/index.d.ts +0 -2
  122. package/dist/commands/service/index.js +0 -89
  123. package/dist/lib/api.d.ts +0 -146
  124. package/dist/lib/api.js +0 -330
  125. package/dist/lib/config.d.ts +0 -12
  126. package/dist/lib/config.js +0 -145
  127. package/dist/lib/runtime/agent-router.d.ts +0 -12
  128. package/dist/lib/runtime/agent-router.js +0 -67
  129. package/dist/lib/runtime/context-builder.d.ts +0 -27
  130. package/dist/lib/runtime/context-builder.js +0 -166
  131. package/dist/lib/runtime/cubby-manager.d.ts +0 -20
  132. package/dist/lib/runtime/cubby-manager.js +0 -162
  133. package/dist/lib/runtime/index.d.ts +0 -51
  134. package/dist/lib/runtime/index.js +0 -181
  135. package/dist/lib/runtime/log-collector.d.ts +0 -17
  136. package/dist/lib/runtime/log-collector.js +0 -61
  137. package/dist/lib/runtime/stream-manager.d.ts +0 -10
  138. package/dist/lib/runtime/stream-manager.js +0 -77
  139. package/dist/lib/runtime/types.d.ts +0 -76
  140. package/dist/lib/runtime/types.js +0 -2
  141. package/dist/lib/transpile.d.ts +0 -1
  142. package/dist/lib/transpile.js +0 -9
  143. package/dist/lib/typecheck.d.ts +0 -31
  144. package/dist/lib/typecheck.js +0 -175
  145. package/dist/lib/types.d.ts +0 -246
  146. package/dist/lib/types.js +0 -2
  147. package/dist/lib/utils.d.ts +0 -4
  148. package/dist/lib/utils.js +0 -33
  149. package/example/.env.example +0 -6
  150. package/example/agents/object-detection/tasks/yolo.ts +0 -28
  151. package/example/agents/parking-violation-detector/tasks/detect.ts +0 -326
  152. package/example/cef.config.yaml +0 -91
  153. package/example/engagements/engagement.ts +0 -902
@@ -1,633 +0,0 @@
1
- (function(){
2
- const API='';
3
- let topology=null,activeEngagement=null,seqNum=0,ws=null,logFilter='',blastPayloads=null,blastAbort=false,batchRunning=false,batchAbort=false;
4
-
5
- async function api(method,path,body){
6
- const opts={method,headers:{'Content-Type':'application/json'}};
7
- if(body!==undefined)opts.body=JSON.stringify(body);
8
- const res=await fetch(API+path,opts);return res.json();
9
- }
10
-
11
- // WebSocket
12
- function connectWs(){
13
- const proto=location.protocol==='https:'?'wss:':'ws:';
14
- ws=new WebSocket(proto+'//'+location.host+'/ws');
15
- ws.onopen=()=>{$('status-dot').className='status-dot ok';$('status-pill').innerHTML='<span class="status-dot ok" id="status-dot"></span>'+location.host};
16
- ws.onclose=()=>{$('status-dot').className='status-dot err';$('status-pill').innerHTML='<span class="status-dot err" id="status-dot"></span>reconnecting';setTimeout(connectWs,2000)};
17
- ws.onmessage=(e)=>{const m=JSON.parse(e.data);if(m.type==='log')appendLog(m.data);if(m.type==='trace')appendTrace(m.data)};
18
- }
19
-
20
- // Logs
21
- function appendLog(entry){
22
- const c=$('log-container'),d=document.createElement('div');
23
- d.className='log-line'+(entry.level!=='log'?' '+entry.level:'');
24
- const ts=new Date(entry.timestamp).toISOString().slice(11,23);
25
- const ag=entry.agentAlias?(entry.agentAlias+(entry.taskAlias?'.'+entry.taskAlias:'')):'';
26
- const pf=ag?'<span class="agent">['+ag+']</span> ':'';
27
- const args=entry.args.map(a=>typeof a==='string'?a:JSON.stringify(a)).join(' ');
28
- d.innerHTML='<span class="ts">'+ts+'</span>'+pf+esc(args);
29
- if(logFilter&&!args.toLowerCase().includes(logFilter))d.style.display='none';
30
- c.appendChild(d);while(c.children.length>500)c.removeChild(c.firstChild);
31
- c.scrollTop=c.scrollHeight;
32
- }
33
-
34
- // Flow
35
- function appendTrace(ev){
36
- const c=$('flow-container'),d=document.createElement('div');
37
- d.className='flow-entry';
38
- const ts=new Date(ev.timestamp).toISOString().slice(11,23);
39
- let ind='',det='',cls='';
40
- switch(ev.type){
41
- case'engagement:start':cls='fe-eng';det=ev.callee||'';break;
42
- case'agent:call':ind=' ';cls='fe-agent';det=ev.callee+(ev.caller?' <span class="fe-dim">\u2190 '+esc(ev.caller)+'</span>':'');break;
43
- case'agent:return':ind=' ';cls=ev.success?'fe-ok':'fe-err';det=ev.callee+' <span class="fe-dim">'+(ev.durationMs||0).toFixed(1)+'ms</span>'+(ev.success?'':' <span class="fe-err">'+esc(ev.error||'FAILED')+'</span>');break;
44
- case'cubby:query':ind=' ';cls='fe-cubby';det=(ev.cubbyAlias||'')+' <span class="fe-dim">'+esc(ev.sql||'')+'</span>';break;
45
- case'cubby:exec':ind=' ';cls='fe-cubby';det=(ev.cubbyAlias||'')+' <span class="fe-dim">'+esc(ev.sql||'')+'</span>'+(ev.rowsAffected!=null?' <span class="fe-dim">('+ev.rowsAffected+')</span>':'');break;
46
- }
47
- d.innerHTML=ind+'<span class="fe-time">'+ts+'</span> <span class="fe-type '+cls+'">'+esc(ev.type)+'</span> '+det;
48
- c.appendChild(d);c.scrollTop=c.scrollHeight;
49
- }
50
-
51
- // Sidebar
52
- async function loadTopology(){topology=await api('GET','/api/topology');renderSidebar();updateEmptyStats()}
53
- function updateEmptyStats(){
54
- if(!topology)return;
55
- var el=$('empty-stats');if(!el)return;
56
- el.innerHTML=
57
- '<div class="stat-card"><div class="stat-num">'+topology.engagements.length+'</div><div class="stat-label">Engagements</div></div>'+
58
- '<div class="stat-card"><div class="stat-num">'+topology.agents.reduce(function(s,a){return s+a.tasks.length},0)+'</div><div class="stat-label">Agent Tasks</div></div>'+
59
- '<div class="stat-card"><div class="stat-num">'+topology.cubbies.length+'</div><div class="stat-label">Cubbies</div></div>';
60
- }
61
- function renderSidebar(){
62
- const el=$('sidebar');let h='';
63
- if(topology.engagements.length){
64
- h+='<div class="s-label">&#9654; Engagements</div>';
65
- for(const e of topology.engagements)h+='<div class="s-item" data-type="engagement" data-name="'+ea(e.name)+'"><span class="s-dot s-dot-eng"></span>'+esc(e.name)+'</div>';
66
- }
67
- if(topology.agents.length){
68
- h+='<div class="s-label">&#9881; Agents</div>';
69
- for(const a of topology.agents)for(const t of a.tasks)
70
- h+='<div class="s-item" data-type="agent" data-alias="'+ea(a.alias)+'" data-task="'+ea(t.alias)+'"><span class="s-dot s-dot-agent"></span>'+esc(a.alias)+'.<span class="s-task">'+esc(t.alias)+'</span></div>';
71
- }
72
- if(topology.cubbies.length){
73
- h+='<div class="s-label">&#9636; Cubbies</div>';
74
- for(const c of topology.cubbies)h+='<div class="s-item" data-type="cubby" data-alias="'+ea(c.alias)+'"><span class="s-dot s-dot-cubby"></span>'+esc(c.alias)+'</div>';
75
- const graphCubbies=topology.cubbies.filter(c=>c.alias);
76
- if(graphCubbies.length)h+='<div class="s-item" data-type="graph-viz" style="margin-top:4px"><span class="s-dot" style="background:#22c55e"></span> Graph Visualizer</div>';
77
- }
78
- el.innerHTML=h;
79
- el.querySelectorAll('.s-item').forEach(item=>{
80
- item.addEventListener('click',()=>{
81
- el.querySelectorAll('.s-item').forEach(i=>i.classList.remove('active'));
82
- item.classList.add('active');
83
- const t=item.dataset.type;
84
- if(t==='engagement')showEngagement(item.dataset.name);
85
- else if(t==='agent')showAgent(item.dataset.alias,item.dataset.task);
86
- else if(t==='cubby')showCubby(item.dataset.alias);
87
- else if(t==='graph-viz'){const gc=topology.cubbies[0];if(gc){showCubby(gc.alias);setTimeout(()=>{const tab=document.querySelector('#panel-cubby .tab[data-tab="graph"]');if(tab)tab.click();},50);}}
88
- });
89
- });
90
- }
91
-
92
- // Panels
93
- function hideAll(){document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));$('empty-state').style.display='none'}
94
-
95
- function showEngagement(name){
96
- hideAll();$('panel-engagement').classList.add('active');
97
- $('eng-name').textContent=name;$('eng-result').innerHTML='';
98
- const ec=topology.engagements.find(e=>e.name===name),params=ec?.parameters;
99
- $('eng-desc').textContent=ec?.description||'';
100
- if(params&&params.properties){
101
- const s=schemaToSample(params);
102
- if('streamId' in s&&!s.streamId)s.streamId=name.toLowerCase().replace(/\s+/g,'-')+'-stream';
103
- $('eng-payload').value=JSON.stringify(s,null,2);
104
- }else{$('eng-payload').value=JSON.stringify({streamId:name.toLowerCase().replace(/\s+/g,'-')+'-stream'},null,2)}
105
- $('eng-schema-hints').innerHTML=renderSchemaHints(params);
106
- if(activeEngagement&&activeEngagement.name===name){$('eng-stream-area').style.display='';$('eng-stop').style.display='';$('eng-start').style.display='none'}
107
- else{$('eng-stream-area').style.display='none';$('eng-stop').style.display='none';$('eng-start').style.display=''}
108
- }
109
-
110
- function showAgent(alias,task){
111
- hideAll();$('panel-agent').classList.add('active');
112
- const lbl=$('agent-label');lbl.textContent=alias+'.'+task;lbl.dataset.alias=alias;lbl.dataset.task=task;
113
- $('agent-result').innerHTML='';
114
- const ac=topology.agents.find(a=>a.alias===alias),tc=ac?.tasks.find(t=>t.alias===task);
115
- const params=tc?.parameters,returns=tc?.returns;
116
- $('agent-desc').textContent=tc?.description||ac?.description||'';
117
- $('agent-payload').value=params&&params.properties?JSON.stringify(schemaToSample(params),null,2):'{}';
118
- $('agent-schema-hints').innerHTML=renderSchemaHints(params,returns);
119
- var bb=$('agent-batch');
120
- if(tc&&tc.batchSql){bb.style.display='';bb.dataset.batchSql=tc.batchSql;bb.dataset.batchCubby=tc.batchCubby||'';}
121
- else{bb.style.display='none';}
122
- }
123
-
124
- function showCubby(alias){
125
- hideAll();$('panel-cubby').classList.add('active');
126
- const lbl=$('cubby-label');lbl.textContent=alias;lbl.dataset.alias=alias;
127
- document.querySelectorAll('#panel-cubby .tab').forEach(t=>t.classList.remove('active'));
128
- document.querySelector('#panel-cubby .tab[data-tab="browse"]').classList.add('active');
129
- $('cubby-browse').style.display='';$('cubby-sql').style.display='none';$('cubby-graph').style.display='none';
130
- $('graph-tab').style.display='';
131
- $('panel-cubby').classList.add('panel-wide');
132
- qtRefreshSelect(alias);
133
- loadCubbyBrowse(alias);
134
- }
135
-
136
- function renderSchemaHints(params,returns){
137
- const hp=params&&params.properties&&Object.keys(params.properties).length>0;
138
- const hr=returns&&returns.properties&&Object.keys(returns.properties).length>0;
139
- if(!hp&&!hr)return'';
140
- let h='<div class="schema-pair">';
141
- if(hp){
142
- h+='<div class="schema-card"><div class="st-title">Input</div>';
143
- for(const[k,v]of Object.entries(params.properties)){
144
- const req=(params.required||[]).includes(k)?'<span class="sr">required</span>':'';
145
- h+='<div class="schema-row"><span class="sk">'+esc(k)+'</span><span class="st">'+esc(v.type||'any')+'</span>'+req;
146
- if(v.description)h+=' <span class="sd">'+esc(v.description)+'</span>';
147
- h+='</div>';
148
- }
149
- h+='</div>';
150
- }
151
- if(hr){
152
- h+='<div class="schema-card"><div class="st-title">Output</div>';
153
- for(const[k,v]of Object.entries(returns.properties))
154
- h+='<div class="schema-row"><span class="sk">'+esc(k)+'</span><span class="st">'+esc(v.type||'any')+'</span></div>';
155
- h+='</div>';
156
- }
157
- return h+'</div>';
158
- }
159
-
160
- function schemaToSample(s){
161
- if(!s||!s.properties)return{};const o={};
162
- for(const[k,v]of Object.entries(s.properties)){
163
- if(v.type==='string')o[k]='';else if(v.type==='number')o[k]=0;
164
- else if(v.type==='boolean')o[k]=false;else if(v.type==='array')o[k]=[];
165
- else if(v.type==='object')o[k]={};else o[k]=null;
166
- }return o;
167
- }
168
-
169
- // Engagement actions
170
- $('eng-file-blast').addEventListener('change',function(){
171
- if(!this.files||!this.files[0]){blastPayloads=null;return;}
172
- var reader=new FileReader();
173
- reader.onload=function(e){
174
- try{
175
- var parsed=JSON.parse(e.target.result);
176
- blastPayloads=Array.isArray(parsed)?parsed:[parsed];
177
- $('eng-result').innerHTML='<div class="success-banner">Loaded '+blastPayloads.length+' payloads from file</div>';
178
- $('eng-repeat').value=''+blastPayloads.length;
179
- }catch(err){blastPayloads=null;$('eng-result').innerHTML='<div class="error-banner">Invalid JSON: '+esc(err.message||String(err))+'</div>';}
180
- };
181
- reader.readAsText(this.files[0]);
182
- });
183
- $('eng-start').addEventListener('click',async function(){
184
- const name=$('eng-name').textContent;
185
- const delay=parseInt($('eng-delay').value)||0;
186
- let payloads;
187
- if(blastPayloads){
188
- payloads=blastPayloads;
189
- }else{
190
- let base;try{base=JSON.parse($('eng-payload').value)}catch{base={}}
191
- const n=Math.max(1,parseInt($('eng-repeat').value)||1);
192
- payloads=Array.from({length:n},function(){return Object.assign({},base);});
193
- }
194
- blastAbort=false;
195
- this.style.display='none';$('eng-stop').style.display='';
196
- let ok=0,errors=0;
197
- for(var i=0;i<payloads.length;i++){
198
- if(blastAbort)break;
199
- var payload=Object.assign({},payloads[i]);
200
- payload.streamId=name.toLowerCase().replace(/\s+/g,'-')+'-'+i+'-'+Date.now();
201
- $('eng-result').innerHTML='<div class="success-banner">Firing '+(i+1)+'/'+payloads.length+'...</div>';
202
- try{
203
- const r=await api('POST','/api/trigger',{engagement:name,payload});
204
- if(r.error){errors++;}else{ok++;if(payloads.length===1){activeEngagement={executionId:r.executionId,streamId:sid,name};seqNum=0;$('eng-stream-area').style.display='';}}
205
- }catch(e){errors++;}
206
- if(delay&&i<payloads.length-1)await new Promise(function(r){setTimeout(r,delay);});
207
- }
208
- var msg=blastAbort?'Aborted after '+(i)+'/'+payloads.length+' \u2014 '+ok+' ok':'Done \u2014 '+ok+'/'+payloads.length+' ok'+(errors?' ('+errors+' errors)':'');
209
- $('eng-result').innerHTML='<div class="'+(errors||blastAbort?'error-banner':'success-banner')+'">'+esc(msg)+'</div>';
210
- $('eng-stop').style.display='none';this.style.display='';
211
- });
212
- $('eng-stop').addEventListener('click',function(){
213
- blastAbort=true;activeEngagement=null;$('eng-stream-area').style.display='none';this.style.display='none';
214
- $('eng-start').style.display='';
215
- });
216
- // Stream push tab switching
217
- document.querySelectorAll('[data-stab]').forEach(tab=>{
218
- tab.addEventListener('click',()=>{
219
- document.querySelectorAll('[data-stab]').forEach(t=>t.classList.remove('active'));tab.classList.add('active');
220
- $('stream-single').style.display=tab.dataset.stab==='single'?'':'none';
221
- $('stream-bulk').style.display=tab.dataset.stab==='bulk'?'':'none';
222
- $('stream-file').style.display=tab.dataset.stab==='file'?'':'none';
223
- });
224
- });
225
-
226
- // Helper: push one packet to active stream
227
- async function pushOne(payloadStr){
228
- if(!activeEngagement)return;
229
- seqNum++;
230
- await api('POST','/api/streams/'+encodeURIComponent(activeEngagement.streamId)+'/push',{data:{sequenceNum:seqNum,payload:payloadStr}});
231
- }
232
-
233
- // Helper: parse lines from text (JSON Lines or JSON array)
234
- function parseLines(text){
235
- const trimmed=text.trim();
236
- if(trimmed.startsWith('[')){try{const arr=JSON.parse(trimmed);if(Array.isArray(arr))return arr.map(o=>JSON.stringify(o))}catch{}}
237
- return trimmed.split('\n').map(l=>l.trim()).filter(l=>l&&!l.startsWith('//'));
238
- }
239
-
240
- let bulkAbort=false;
241
-
242
- // Helper: send lines with delay and progress
243
- async function sendLines(lines,delayMs,progressEl,stopBtn,sendBtn){
244
- bulkAbort=false;stopBtn.style.display='';sendBtn.style.display='none';
245
- progressEl.innerHTML='<div class="progress-bar"><div class="fill" style="width:0%"></div></div><div class="progress-text">0 / '+lines.length+'</div>';
246
- const bar=progressEl.querySelector('.fill'),txt=progressEl.querySelector('.progress-text');
247
- let sent=0;
248
- for(const line of lines){
249
- if(bulkAbort)break;
250
- try{await pushOne(line);sent++}catch(e){progressEl.innerHTML+='<div class="error-banner">Failed at '+sent+': '+esc(e.message)+'</div>';break}
251
- const pct=Math.round(sent/lines.length*100);
252
- bar.style.width=pct+'%';txt.textContent=sent+' / '+lines.length;
253
- if(delayMs>0&&sent<lines.length)await new Promise(r=>setTimeout(r,delayMs));
254
- }
255
- stopBtn.style.display='none';sendBtn.style.display='';
256
- if(!bulkAbort)$('eng-push-result').innerHTML='<div class="success-banner">Sent '+sent+' packets</div>';
257
- else $('eng-push-result').innerHTML='<div class="success-banner">Stopped after '+sent+' / '+lines.length+'</div>';
258
- }
259
-
260
- // Single push
261
- $('eng-push').addEventListener('click',async function(){
262
- if(!activeEngagement)return;this.textContent='...';
263
- try{await pushOne($('eng-stream-payload').value);
264
- $('eng-push-result').innerHTML='<div class="success-banner">Pushed <span class="fe-dim">seq '+seqNum+'</span></div>';
265
- }catch(e){$('eng-push-result').innerHTML='<div class="error-banner">'+esc(e.message)+'</div>'}
266
- this.textContent='Push';
267
- });
268
-
269
- // Bulk send
270
- $('eng-bulk-send').addEventListener('click',async function(){
271
- if(!activeEngagement)return;
272
- const lines=parseLines($('eng-bulk-payload').value);
273
- if(!lines.length)return;
274
- const delay=parseInt($('eng-bulk-delay').value)||0;
275
- await sendLines(lines,delay,$('eng-bulk-progress'),$('eng-bulk-stop'),this);
276
- });
277
- $('eng-bulk-stop').addEventListener('click',()=>{bulkAbort=true});
278
-
279
- // File upload
280
- let fileLines=[];
281
- $('file-drop-zone').addEventListener('click',()=>$('eng-file-input').click());
282
- $('file-drop-zone').addEventListener('dragover',(e)=>{e.preventDefault();e.currentTarget.classList.add('drag-over')});
283
- $('file-drop-zone').addEventListener('dragleave',(e)=>{e.currentTarget.classList.remove('drag-over')});
284
- $('file-drop-zone').addEventListener('drop',(e)=>{e.preventDefault();e.currentTarget.classList.remove('drag-over');if(e.dataTransfer.files.length)handleFile(e.dataTransfer.files[0])});
285
- $('eng-file-input').addEventListener('change',(e)=>{if(e.target.files.length)handleFile(e.target.files[0])});
286
-
287
- function handleFile(file){
288
- const reader=new FileReader();
289
- reader.onload=(e)=>{
290
- fileLines=parseLines(e.target.result);
291
- $('file-info').textContent=file.name+' ('+fileLines.length+' events)';
292
- $('eng-file-send').disabled=false;
293
- };
294
- reader.readAsText(file);
295
- }
296
-
297
- $('eng-file-send').addEventListener('click',async function(){
298
- if(!activeEngagement||!fileLines.length)return;
299
- const delay=parseInt($('eng-file-delay').value)||0;
300
- await sendLines(fileLines,delay,$('eng-file-progress'),$('eng-file-stop'),this);
301
- });
302
- $('eng-file-stop').addEventListener('click',()=>{bulkAbort=true});
303
-
304
- // Agent actions
305
- $('agent-call').addEventListener('click',async function(){
306
- const lbl=$('agent-label'),alias=lbl.dataset.alias,task=lbl.dataset.task;
307
- let payload;try{payload=JSON.parse($('agent-payload').value)}catch{payload={}}
308
- this.textContent='...';
309
- try{
310
- const r=await api('POST','/api/agents/'+alias+'/'+task,{payload});
311
- if(r.error&&!r.success){$('agent-result').innerHTML='<div class="error-banner">'+esc(r.error)+'</div>'}
312
- else{$('agent-result').innerHTML='<div class="result"><pre>'+esc(JSON.stringify(r.result,null,2))+'</pre></div><div class="result-meta"><span>'+(r.durationMs||0).toFixed(1)+'ms</span><span class="badge '+(r.success?'badge-ok':'badge-err')+'">'+(r.success?'ok':'error')+'</span></div>'}
313
- }catch(e){$('agent-result').innerHTML='<div class="error-banner">'+esc(e.message)+'</div>'}
314
- this.textContent='Call';
315
- });
316
-
317
- $('agent-batch').addEventListener('click',async function(){
318
- if(batchRunning){batchAbort=true;return;}
319
- const lbl=$('agent-label'),alias=lbl.dataset.alias,task=lbl.dataset.task;
320
- const batchSql=this.dataset.batchSql,batchCubby=this.dataset.batchCubby;
321
- if(!batchSql||!batchCubby){$('agent-result').innerHTML='<div class="error-banner">No batch config on this task</div>';return;}
322
- $('agent-result').innerHTML='<div class="success-banner">Querying '+esc(batchCubby)+'...</div>';
323
- var r=await api('POST','/api/cubbies/'+batchCubby+'/query',{sql:batchSql,params:[]});
324
- if(r.error){$('agent-result').innerHTML='<div class="error-banner">Query failed: '+esc(r.error)+'</div>';return;}
325
- var rows=r.rows||[];
326
- if(!rows.length){$('agent-result').innerHTML='<div class="success-banner">No rows matched \u2014 nothing to run</div>';return;}
327
- const ac=topology.agents.find(function(a){return a.alias===alias;}),tc=ac&&ac.tasks.find(function(t){return t.alias===task;});
328
- const paramKeys=tc&&tc.parameters&&tc.parameters.properties?Object.keys(tc.parameters.properties):[];
329
- batchRunning=true;batchAbort=false;
330
- this.textContent='Stop';this.classList.replace('btn-secondary','btn-danger');
331
- var ok=0,errors=0,i=0;
332
- for(;i<rows.length;i++){
333
- if(batchAbort)break;
334
- $('agent-result').innerHTML='<div class="success-banner">Running '+(i+1)+'/'+rows.length+' \u2014 '+ok+' ok'+( errors?' '+errors+' err':'')+'</div>';
335
- var payload={};
336
- for(var j=0;j<paramKeys.length&&j<rows[i].length;j++)payload[paramKeys[j]]=rows[i][j];
337
- try{
338
- var res=await api('POST','/api/agents/'+alias+'/'+task,{payload});
339
- if(res.error&&!res.success)errors++;else ok++;
340
- }catch(e){errors++;}
341
- await new Promise(function(r){setTimeout(r,300);});
342
- }
343
- batchRunning=false;
344
- this.textContent='Run Batch';this.classList.replace('btn-danger','btn-secondary');
345
- var msg=batchAbort?'Stopped after '+i+'/'+rows.length+' \u2014 '+ok+' ok':'Done \u2014 '+ok+'/'+rows.length+' ok'+(errors?' ('+errors+' errors)':'');
346
- $('agent-result').innerHTML='<div class="'+(errors||batchAbort?'error-banner':'success-banner')+'">'+esc(msg)+'</div>';
347
- });
348
-
349
- // Cubby tabs
350
- document.querySelectorAll('#panel-cubby .tab').forEach(tab=>{
351
- tab.addEventListener('click',()=>{
352
- document.querySelectorAll('#panel-cubby .tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');
353
- $('cubby-browse').style.display=tab.dataset.tab==='browse'?'':'none';
354
- $('cubby-sql').style.display=tab.dataset.tab==='sql'?'':'none';
355
- $('cubby-graph').style.display=tab.dataset.tab==='graph'?'':'none';
356
- if(tab.dataset.tab==='graph'){$('panel-cubby').classList.add('panel-wide');loadCubbyGraph($('cubby-label').dataset.alias);}
357
- else if(tab.dataset.tab==='sql'){$('panel-cubby').classList.add('panel-wide');}
358
- else $('panel-cubby').classList.remove('panel-wide');
359
- });
360
- });
361
-
362
- // Graph tab
363
- const GRAPH_NODE_COLORS={
364
- artifact:{bg:'#6366f1',border:'#818cf8'},
365
- work:{bg:'#f59e0b',border:'#fbbf24'},
366
- operator:{bg:'#22c55e',border:'#4ade80'},
367
- eval:{bg:'#ec4899',border:'#f472b6'},
368
- artifact_map:{bg:'#52525b',border:'#71717a'},
369
- };
370
- let graphNet=null,graphVisNodes=null,hiddenTypes=new Set();
371
-
372
- async function loadCubbyGraph(alias){
373
- const canvas=$('graph-canvas');
374
- canvas.innerHTML='<div class="empty">Loading graph...</div>';
375
- $('graph-detail').style.display='none';
376
- $('graph-stats').textContent='';
377
- try{
378
- const [nr,er]=await Promise.all([
379
- api('POST','/api/cubbies/'+alias+'/query',{sql:"SELECT id,version,type,ref,created_at,attribute FROM nodes"}),
380
- api('POST','/api/cubbies/'+alias+'/query',{sql:"SELECT id,src,type,dst FROM edges"}),
381
- ]);
382
- if(nr.error){canvas.innerHTML='<div class="empty">This cubby has no nodes table \u2014 graph view requires nodes + edges tables</div>';return}
383
- const rows=nr.rows||[],erows=er.rows||[];
384
- if(!rows.length){canvas.innerHTML='<div class="empty">No nodes yet \u2014 run an ingest first</div>';return}
385
-
386
- // Build vis nodes
387
- const visNodes=[],versionedNode={},latestVer={},nodeVisId={};
388
- let idx=1;
389
- for(const r of rows){
390
- const[id,ver,type,ref,created_at,attr]=r;
391
- const col=GRAPH_NODE_COLORS[type]||GRAPH_NODE_COLORS.artifact_map;
392
- const a=safeJ(attr);
393
- const label=type==='artifact'?(a.title||ref||id).slice(0,22)+'\nv'+ver
394
- :type==='operator'?(a.name||ref||'operator').slice(0,18)
395
- :type==='work'?(a.type||'work')+'\nv'+ver
396
- :(type+' v'+ver);
397
- const shape=type==='operator'?'diamond':type==='work'?'box':'ellipse';
398
- visNodes.push({id:idx,label,
399
- color:{background:col.bg,border:col.border,highlight:{background:col.border,border:'#fff'}},
400
- font:{color:'#fff',size:11,face:"'SF Mono','Fira Code',monospace",multi:false},
401
- shape,borderWidth:2,
402
- _raw:{id,ver,type,ref,created_at,attr}});
403
- nodeVisId[id+'|'+ver]=idx;
404
- if(!versionedNode[id])versionedNode[id]={};
405
- versionedNode[id][ver]=idx;
406
- if(!latestVer[id]||parseInt(ver)>parseInt(latestVer[id].ver))latestVer[id]={ver,visId:idx};
407
- idx++;
408
- }
409
-
410
- // Build vis edges
411
- const nodeVer={};
412
- for(const r of rows)nodeVer[r[0]]=r[1];
413
-
414
- const visEdges=[];
415
- for(const r of erows){
416
- const[eid,src,type,dst]=r;
417
- const sVisId=latestVer[src]?.visId;
418
- const srcVersion=nodeVer[src];
419
- const dVisId=(versionedNode[dst]&&srcVersion&&versionedNode[dst][srcVersion])
420
- ?versionedNode[dst][srcVersion]
421
- :latestVer[dst]?.visId;
422
- if(!sVisId||!dVisId)continue;
423
- visEdges.push({id:eid,from:sVisId,to:dVisId,label:type,
424
- font:{size:9,color:'#52525b',align:'middle'},
425
- color:{color:'#3f3f46',highlight:'#818cf8'},
426
- arrows:{to:{enabled:true,scaleFactor:0.5}},
427
- smooth:{type:'curvedCW',roundness:0.2}});
428
- }
429
-
430
- // Synthetic PREV_VERSION dashed edges
431
- for(const[nodeId,verMap]of Object.entries(versionedNode)){
432
- const ntype=rows.find(function(r){return r[0]===nodeId;})?.[2];
433
- if(ntype!=='artifact')continue;
434
- const versions=Object.keys(verMap).map(Number).sort(function(a,b){return a-b;});
435
- for(var vi=1;vi<versions.length;vi++){
436
- const fromVisId=verMap[String(versions[vi-1])],toVisId=verMap[String(versions[vi])];
437
- if(!fromVisId||!toVisId)continue;
438
- visEdges.push({id:nodeId+'|PREV|v'+versions[vi],from:fromVisId,to:toVisId,
439
- label:'',dashes:true,
440
- font:{size:8,color:'#3f3f46',align:'middle'},
441
- color:{color:'#3f3f46',highlight:'#52525b'},
442
- arrows:{to:{enabled:true,scaleFactor:0.3}},
443
- smooth:{type:'continuous'}});
444
- }
445
- }
446
-
447
- // Count node types for legend
448
- const typeCounts={};
449
- for(const n of visNodes)typeCounts[n._raw.type]=(typeCounts[n._raw.type]||0)+1;
450
-
451
- $('graph-stats').innerHTML=visNodes.length+' nodes \u00b7 '+visEdges.length+' edges';
452
-
453
- // Legend toggles
454
- var legendHtml='<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px">';
455
- for(const[type,col]of Object.entries(GRAPH_NODE_COLORS)){
456
- if(!typeCounts[type])continue;
457
- const isHidden=hiddenTypes.has(type);
458
- legendHtml+='<button onclick="window.graphToggleType(\''+type+'\')" style="display:flex;align-items:center;gap:5px;background:'+(isHidden?'var(--surface2)':col.bg+'22')+';border:1px solid '+(isHidden?'var(--border)':col.bg+'66')+';color:'+(isHidden?'var(--text3)':col.bg)+';border-radius:4px;padding:2px 8px;font-size:10px;cursor:pointer">'
459
- +'<span style="width:8px;height:8px;border-radius:50%;background:'+(isHidden?'var(--border)':col.bg)+';display:inline-block"></span>'+esc(type)+' <span style="opacity:.6">'+typeCounts[type]+'</span></button>';
460
- }
461
- legendHtml+='</div>';
462
- $('graph-stats').innerHTML+= legendHtml;
463
-
464
- canvas.innerHTML='';
465
- if(graphNet)graphNet.destroy();
466
- graphVisNodes=new window.vis.DataSet(visNodes.map(function(n){return Object.assign({},n,{hidden:hiddenTypes.has(n._raw.type)});}));
467
- graphNet=new window.vis.Network(canvas,
468
- {nodes:graphVisNodes,edges:new window.vis.DataSet(visEdges)},
469
- {physics:{barnesHut:{gravitationalConstant:-12000,springLength:200,springConstant:0.02,damping:0.15,centralGravity:0.05},stabilization:{iterations:200}},
470
- interaction:{hover:true},layout:{improvedLayout:rows.length<80}});
471
-
472
- graphNet.on('click',(p)=>{
473
- if(!p.nodes.length){$('graph-detail').style.display='none';return}
474
- const n=visNodes.find(v=>v.id===p.nodes[0]);if(!n)return;
475
- const{id,ver,type,ref,created_at,attr}=n._raw,a=safeJ(attr);
476
- const col=GRAPH_NODE_COLORS[type]||GRAPH_NODE_COLORS.artifact_map;
477
- $('gd-type-badge').textContent=type;
478
- $('gd-type-badge').style.cssText='background:'+col.bg+'22;color:'+col.bg+';border:1px solid '+col.bg+'44';
479
- const rows=[['id',id],['version',ver],['ref',ref],['created_at',created_at],
480
- ...Object.entries(a).filter(([k])=>k!=='segments'&&k!=='embedding').map(([k,v])=>[k,typeof v==='object'?JSON.stringify(v):String(v??'')])
481
- ].filter(([,v])=>v&&v!=='null');
482
- if(a.segments?.length)rows.push(['segments',a.segments.length+' blocks']);
483
- $('gd-attrs').innerHTML=rows.map(([k,v])=>'<div class="gd-row"><span class="gd-key">'+esc(k)+'</span><span class="gd-val">'+esc(String(v))+'</span></div>').join('');
484
- $('graph-detail').style.display='';
485
- });
486
- }catch(e){canvas.innerHTML='<div class="error-banner">'+esc(e.message||String(e))+'</div>'}
487
- }
488
-
489
- window.loadCubbyGraph=loadCubbyGraph;
490
- window.graphToggleType=function(type){
491
- if(hiddenTypes.has(type))hiddenTypes.delete(type);else hiddenTypes.add(type);
492
- if(!graphVisNodes)return;
493
- graphVisNodes.update(graphVisNodes.get().filter(function(n){return n._raw&&n._raw.type===type;}).map(function(n){return{id:n.id,hidden:hiddenTypes.has(type)};}));
494
- document.querySelectorAll('#graph-stats button').forEach(function(btn){
495
- var t=btn.textContent.trim().split(' ')[0];
496
- var col=GRAPH_NODE_COLORS[t];if(!col)return;
497
- var hidden=hiddenTypes.has(t);
498
- btn.style.background=hidden?'var(--surface2)':col.bg+'22';
499
- btn.style.borderColor=hidden?'var(--border)':col.bg+'66';
500
- btn.style.color=hidden?'var(--text3)':col.bg;
501
- var dot=btn.querySelector('span');if(dot)dot.style.background=hidden?'var(--border)':col.bg;
502
- });
503
- };
504
- function safeJ(s){try{return JSON.parse(s)}catch{return{}}}
505
- async function loadCubbyBrowse(alias){
506
- const el=$('cubby-tables');el.innerHTML='<div class="empty">Loading...</div>';
507
- try{
508
- const t=await api('POST','/api/cubbies/'+alias+'/query',{sql:"SELECT name FROM sqlite_master WHERE type='table' AND name != 'schema_migrations'"});
509
- if(!t.rows||!t.rows.length){el.innerHTML='<div class="empty">No tables</div>';return}
510
- let h='';
511
- for(const row of t.rows){
512
- const tn=row[0],cr=await api('POST','/api/cubbies/'+alias+'/query',{sql:'SELECT COUNT(*) FROM "'+tn+'"'});
513
- h+='<div class="cubby-table-item" onclick="browseCubbyTable(\''+ea(alias)+'\',\''+ea(tn)+'\')">';
514
- h+='<span class="cti-name">'+esc(tn)+'</span><span class="cti-count">'+(cr.rows?.[0]?.[0]??'?')+' rows</span></div>';
515
- }
516
- el.innerHTML=h;
517
- }catch(e){el.innerHTML='<div class="error-banner">'+esc(e.message)+'</div>'}
518
- }
519
- window.browseCubbyTable=async function(alias,table){
520
- const el=$('cubby-tables');
521
- try{const r=await api('POST','/api/cubbies/'+alias+'/query',{sql:'SELECT * FROM "'+table+'" LIMIT 100'});
522
- el.innerHTML='<div style="display:flex;gap:8px;justify-content:flex-end;margin-bottom:8px">'
523
- +'<button class="btn-ghost" onclick="loadCubbyBrowse(\''+ea(alias)+'\')">\u2190 Back</button>'
524
- +'<button class="btn-danger" style="font-size:11px;padding:5px 12px" onclick="clearCubbyTable(\''+ea(alias)+'\',\''+ea(table)+'\')">Clear table</button>'
525
- +'</div>'+renderTable(r);
526
- }catch(e){el.innerHTML='<div class="error-banner">'+esc(e.message)+'</div>'}
527
- };
528
- window.clearCubbyTable=async function(alias,table){
529
- if(!confirm('Delete all rows from "'+table+'"?'))return;
530
- try{
531
- await api('POST','/api/cubbies/'+alias+'/exec',{sql:'DELETE FROM "'+table+'"'});
532
- window.browseCubbyTable(alias,table);
533
- }catch(e){alert('Error: '+e.message);}
534
- };
535
- window.loadCubbyBrowse=loadCubbyBrowse;
536
-
537
- $('cubby-run').addEventListener('click',async function(){
538
- const alias=$('cubby-label').dataset.alias,sql=$('cubby-sql-input').value.trim();if(!sql)return;
539
- const isW=/^\s*(INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)/i.test(sql),ep=isW?'exec':'query';
540
- this.textContent='...';
541
- try{const r=await api('POST','/api/cubbies/'+alias+'/'+ep,{sql});
542
- if(r.error){$('cubby-sql-result').innerHTML='<div class="error-banner">'+esc(r.error)+'</div>'}
543
- else if(r.columns){$('cubby-sql-result').innerHTML=renderTable(r)+'<div class="result-meta"><span>'+r.meta.rowsRead+' rows</span><span>'+(r.meta.duration*1000).toFixed(2)+'ms</span></div>'}
544
- else{$('cubby-sql-result').innerHTML='<div class="success-banner">'+(r.rowsAffected??0)+' rows affected</div>'}
545
- }catch(e){$('cubby-sql-result').innerHTML='<div class="error-banner">'+esc(e.message)+'</div>'}
546
- this.textContent='Run';
547
- });
548
-
549
- // Query template manager (stored in cubby saved_queries table)
550
- async function qtRefreshSelect(alias){
551
- var sel=$('query-select'),prev=sel.value;
552
- try{
553
- var r=await api('POST','/api/cubbies/'+alias+'/query',{sql:'SELECT name FROM saved_queries ORDER BY name',params:[]});
554
- sel.innerHTML='<option value="">\u2014 select a saved query \u2014</option>';
555
- (r.rows||[]).forEach(function(row){var o=document.createElement('option');o.value=row[0];o.textContent=row[0];sel.appendChild(o);});
556
- sel.value=prev||'';
557
- $('query-delete').style.display=sel.value?'':'none';
558
- }catch(e){}
559
- }
560
- $('query-select').addEventListener('change',async function(){
561
- var alias=$('cubby-label').dataset.alias,name=this.value;
562
- $('query-delete').style.display=name?'':'none';
563
- if(!name)return;
564
- try{
565
- var r=await api('POST','/api/cubbies/'+alias+'/query',{sql:'SELECT sql_text FROM saved_queries WHERE name=?',params:[name]});
566
- if(r.rows&&r.rows[0]){$('cubby-sql-input').value=r.rows[0][0];$('query-name').value=name;}
567
- }catch(e){}
568
- });
569
- $('query-save').addEventListener('click',async function(){
570
- var alias=$('cubby-label').dataset.alias,name=$('query-name').value.trim(),sql=$('cubby-sql-input').value.trim();
571
- if(!name||!sql)return;
572
- try{
573
- await api('POST','/api/cubbies/'+alias+'/exec',{sql:'INSERT OR REPLACE INTO saved_queries(name,sql_text) VALUES(?,?)',params:[name,sql]});
574
- await qtRefreshSelect(alias);$('query-select').value=name;$('query-delete').style.display='';
575
- }catch(e){}
576
- });
577
- $('query-delete').addEventListener('click',async function(){
578
- var alias=$('cubby-label').dataset.alias,name=$('query-select').value;if(!name)return;
579
- try{
580
- await api('POST','/api/cubbies/'+alias+'/exec',{sql:'DELETE FROM saved_queries WHERE name=?',params:[name]});
581
- await qtRefreshSelect(alias);$('query-name').value='';
582
- }catch(e){}
583
- });
584
-
585
- // Bottom panel: tabs, resize, collapse
586
- document.querySelectorAll('.btab').forEach(tab=>{
587
- tab.addEventListener('click',()=>{
588
- document.querySelectorAll('.btab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');
589
- $('log-container').style.display=tab.dataset.btab==='logs'?'':'none';
590
- $('flow-container').style.display=tab.dataset.btab==='flow'?'':'none';
591
- });
592
- });
593
- $('log-filter').addEventListener('input',function(){
594
- logFilter=this.value.toLowerCase();
595
- $('log-container').querySelectorAll('.log-line').forEach(l=>{l.style.display=(!logFilter||l.textContent.toLowerCase().includes(logFilter))?'':'none'});
596
- });
597
- $('btn-clear').addEventListener('click',()=>{$('log-container').innerHTML='';$('flow-container').innerHTML=''});
598
- $('btn-collapse').addEventListener('click',function(){
599
- const b=$('bottom');
600
- if(b.classList.toggle('collapsed')){this.innerHTML='&#9650;';this.title='Expand'}
601
- else{this.innerHTML='&#9660;';this.title='Collapse'}
602
- });
603
-
604
- // Resize handle drag
605
- (function(){
606
- const handle=$('resize-handle'),bottom=$('bottom');
607
- let startY,startH;
608
- handle.addEventListener('mousedown',(e)=>{
609
- e.preventDefault();startY=e.clientY;startH=bottom.offsetHeight;
610
- const onMove=(e)=>{const h=Math.max(40,Math.min(window.innerHeight*0.7,startH-(e.clientY-startY)));bottom.style.height=h+'px';bottom.classList.remove('collapsed')};
611
- const onUp=()=>{document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp)};
612
- document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);
613
- });
614
- })();
615
-
616
- // Tab in textareas
617
- document.querySelectorAll('textarea').forEach(ta=>{
618
- ta.addEventListener('keydown',function(e){if(e.key==='Tab'){e.preventDefault();const s=this.selectionStart;this.value=this.value.substring(0,s)+' '+this.value.substring(this.selectionEnd);this.selectionStart=this.selectionEnd=s+2}});
619
- });
620
-
621
- // Helpers
622
- function $(id){return document.getElementById(id)}
623
- function renderTable(r){
624
- if(!r.columns||!r.rows)return'';let h='<div class="table-wrap"><table><thead><tr>';
625
- for(const c of r.columns)h+='<th>'+esc(c)+'</th>';h+='</tr></thead><tbody>';
626
- for(const row of r.rows){h+='<tr>';for(const cell of row)h+='<td>'+esc(cell==null?'NULL':String(cell))+'</td>';h+='</tr>'}
627
- return h+'</tbody></table></div>';
628
- }
629
- function esc(s){if(s==null)return'';return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
630
- function ea(s){return String(s).replace(/'/g,"\\'").replace(/"/g,'&quot;')}
631
-
632
- loadTopology();connectWs();
633
- })();