@triflux/remote 10.0.0-alpha.1

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 (68) hide show
  1. package/hub/pipe.mjs +579 -0
  2. package/hub/public/dashboard.html +355 -0
  3. package/hub/public/tray-icon.ico +0 -0
  4. package/hub/public/tray-icon.png +0 -0
  5. package/hub/server.mjs +1124 -0
  6. package/hub/store-adapter.mjs +851 -0
  7. package/hub/store.mjs +897 -0
  8. package/hub/team/agent-map.json +11 -0
  9. package/hub/team/ansi.mjs +379 -0
  10. package/hub/team/backend.mjs +90 -0
  11. package/hub/team/cli/commands/attach.mjs +37 -0
  12. package/hub/team/cli/commands/control.mjs +43 -0
  13. package/hub/team/cli/commands/debug.mjs +74 -0
  14. package/hub/team/cli/commands/focus.mjs +53 -0
  15. package/hub/team/cli/commands/interrupt.mjs +36 -0
  16. package/hub/team/cli/commands/kill.mjs +37 -0
  17. package/hub/team/cli/commands/list.mjs +24 -0
  18. package/hub/team/cli/commands/send.mjs +37 -0
  19. package/hub/team/cli/commands/start/index.mjs +106 -0
  20. package/hub/team/cli/commands/start/parse-args.mjs +130 -0
  21. package/hub/team/cli/commands/start/start-headless.mjs +109 -0
  22. package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
  23. package/hub/team/cli/commands/start/start-mux.mjs +73 -0
  24. package/hub/team/cli/commands/start/start-wt.mjs +69 -0
  25. package/hub/team/cli/commands/status.mjs +87 -0
  26. package/hub/team/cli/commands/stop.mjs +31 -0
  27. package/hub/team/cli/commands/task.mjs +30 -0
  28. package/hub/team/cli/commands/tasks.mjs +13 -0
  29. package/hub/team/cli/help.mjs +42 -0
  30. package/hub/team/cli/index.mjs +41 -0
  31. package/hub/team/cli/manifest.mjs +29 -0
  32. package/hub/team/cli/render.mjs +30 -0
  33. package/hub/team/cli/services/attach-fallback.mjs +54 -0
  34. package/hub/team/cli/services/hub-client.mjs +208 -0
  35. package/hub/team/cli/services/member-selector.mjs +30 -0
  36. package/hub/team/cli/services/native-control.mjs +117 -0
  37. package/hub/team/cli/services/runtime-mode.mjs +62 -0
  38. package/hub/team/cli/services/state-store.mjs +48 -0
  39. package/hub/team/cli/services/task-model.mjs +30 -0
  40. package/hub/team/dashboard-anchor.mjs +14 -0
  41. package/hub/team/dashboard-layout.mjs +33 -0
  42. package/hub/team/dashboard-open.mjs +153 -0
  43. package/hub/team/dashboard.mjs +274 -0
  44. package/hub/team/handoff.mjs +303 -0
  45. package/hub/team/headless.mjs +1149 -0
  46. package/hub/team/native-supervisor.mjs +392 -0
  47. package/hub/team/native.mjs +649 -0
  48. package/hub/team/nativeProxy.mjs +681 -0
  49. package/hub/team/orchestrator.mjs +161 -0
  50. package/hub/team/pane.mjs +153 -0
  51. package/hub/team/psmux.mjs +1354 -0
  52. package/hub/team/routing.mjs +223 -0
  53. package/hub/team/session.mjs +611 -0
  54. package/hub/team/shared.mjs +13 -0
  55. package/hub/team/staleState.mjs +361 -0
  56. package/hub/team/tui-lite.mjs +380 -0
  57. package/hub/team/tui-viewer.mjs +463 -0
  58. package/hub/team/tui.mjs +1245 -0
  59. package/hub/tools.mjs +554 -0
  60. package/hub/tray.mjs +376 -0
  61. package/hub/workers/claude-worker.mjs +475 -0
  62. package/hub/workers/codex-mcp.mjs +504 -0
  63. package/hub/workers/delegator-mcp.mjs +1076 -0
  64. package/hub/workers/factory.mjs +21 -0
  65. package/hub/workers/gemini-worker.mjs +373 -0
  66. package/hub/workers/interface.mjs +52 -0
  67. package/hub/workers/worker-utils.mjs +104 -0
  68. package/package.json +31 -0
@@ -0,0 +1,355 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>triflux QoS 대시보드</title>
7
+ <style>
8
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
9
+ body{background:#0d1117;color:#c9d1d9;font-family:system-ui,-apple-system,sans-serif;min-height:100vh;padding:16px}
10
+ .header{display:flex;align-items:center;justify-content:space-between;padding:12px 0;margin-bottom:16px;border-bottom:1px solid #30363d;max-width:1200px;margin-left:auto;margin-right:auto}
11
+ .header h1{font-size:1.4rem;font-weight:600}
12
+ .status-bar{display:flex;align-items:center;gap:12px;font-size:.85rem;color:#8b949e}
13
+ .status-dot{width:10px;height:10px;border-radius:50%;display:inline-block;transition:background .3s}
14
+ .status-dot.ok{background:#3fb950}
15
+ .status-dot.err{background:#f85149}
16
+ .grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:1200px;margin:0 auto}
17
+ @media(max-width:768px){.grid{grid-template-columns:1fr}}
18
+ .card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px}
19
+ .card-title{font-size:.95rem;font-weight:600;margin-bottom:12px;color:#c9d1d9}
20
+ .sub{color:#8b949e;font-size:.8rem}
21
+
22
+ .gauge-wrap{display:flex;flex-direction:column;align-items:center;gap:8px}
23
+ .gauge-value{font-size:2.2rem;font-weight:700;margin-top:-36px}
24
+ .gauge-label{font-size:.85rem;color:#8b949e}
25
+
26
+ .quota-row{margin-bottom:14px}
27
+ .quota-row:last-child{margin-bottom:0}
28
+ .quota-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;font-size:.85rem}
29
+ .quota-header .cli-name{font-weight:600}
30
+ .quota-header .meta{color:#8b949e;font-size:.78rem}
31
+ .bar-row{display:flex;gap:6px;align-items:center;margin-bottom:4px}
32
+ .bar-row:last-child{margin-bottom:0}
33
+ .bar-row .bar-label{font-size:.72rem;color:#8b949e;width:56px;text-align:right;flex-shrink:0}
34
+ .bar-track{height:18px;background:#21262d;border-radius:4px;overflow:hidden;position:relative;flex:1}
35
+ .bar-fill{height:100%;border-radius:4px;transition:width .4s ease;display:flex;align-items:center;justify-content:flex-end;padding-right:6px;font-size:.72rem;font-weight:600;color:#0d1117;min-width:0}
36
+ .bar-pct-outer{font-size:.72rem;color:#8b949e;margin-left:6px;width:32px;flex-shrink:0}
37
+
38
+ .token-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
39
+ .token-item{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:12px;text-align:center}
40
+ .token-item .label{font-size:.78rem;color:#8b949e;margin-bottom:4px}
41
+ .token-item .value{font-size:1.5rem;font-weight:700}
42
+ .token-item .calls{font-size:.78rem;color:#8b949e;margin-top:2px}
43
+
44
+ .chart-wrap{position:relative;width:100%;height:160px}
45
+ .chart-wrap canvas{width:100%!important;height:100%!important}
46
+ .chart-legend{display:flex;gap:14px;margin-top:8px;font-size:.78rem;color:#8b949e}
47
+ .legend-dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:4px;vertical-align:middle}
48
+
49
+ .placeholder{text-align:center;padding:24px;color:#8b949e;font-size:.85rem}
50
+ </style>
51
+ </head>
52
+ <body>
53
+
54
+ <div class="header">
55
+ <h1>&#9889; triflux QoS 대시보드</h1>
56
+ <div class="status-bar">
57
+ <span class="status-dot" id="statusDot"></span>
58
+ <span id="statusText">연결 중...</span>
59
+ <span id="lastUpdate" class="sub"></span>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="grid">
64
+ <!-- AIMD 게이지 -->
65
+ <div class="card">
66
+ <div class="card-title">AIMD 동시 워커</div>
67
+ <div class="gauge-wrap">
68
+ <svg width="220" height="130" viewBox="0 0 220 130">
69
+ <path d="M20 120 A90 90 0 0 1 200 120" fill="none" stroke="#21262d" stroke-width="18" stroke-linecap="round"/>
70
+ <path id="gaugeArc" d="M20 120 A90 90 0 0 1 200 120" fill="none" stroke="#3fb950" stroke-width="18" stroke-linecap="round" stroke-dasharray="0 283"/>
71
+ <!-- 눈금 -->
72
+ <g id="gaugeTicks" fill="#8b949e" font-size="10" text-anchor="middle"></g>
73
+ </svg>
74
+ <div class="gauge-value" id="gaugeVal">-</div>
75
+ <div class="gauge-label" id="gaugeLabel">- / 10</div>
76
+ </div>
77
+ </div>
78
+
79
+ <!-- CLI 쿼터 바 -->
80
+ <div class="card">
81
+ <div class="card-title">CLI 쿼터 사용률</div>
82
+ <div id="quotaBars">
83
+ <div class="placeholder">데이터 대기 중...</div>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- 호출 이력 차트 -->
88
+ <div class="card" style="grid-column:1/-1">
89
+ <div class="card-title">호출 이력 타임라인</div>
90
+ <div class="chart-wrap">
91
+ <canvas id="eventsCanvas"></canvas>
92
+ </div>
93
+ <div class="chart-legend">
94
+ <span><span class="legend-dot" style="background:#3fb950"></span>성공</span>
95
+ <span><span class="legend-dot" style="background:#f85149"></span>실패</span>
96
+ <span><span class="legend-dot" style="background:#d29922"></span>타임아웃</span>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- 토큰 누적 -->
101
+ <div class="card" style="grid-column:1/-1">
102
+ <div class="card-title">토큰 누적 현황</div>
103
+ <div class="token-grid" id="tokenCards">
104
+ <div class="placeholder" style="grid-column:1/-1">데이터 대기 중...</div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ <script>
110
+ (function() {
111
+ function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
112
+
113
+ var POLL = 5000;
114
+ var MAX_W = 10;
115
+ var ARC_TOTAL = 283;
116
+
117
+ var $dot = document.getElementById('statusDot');
118
+ var $stxt = document.getElementById('statusText');
119
+ var $time = document.getElementById('lastUpdate');
120
+ var $arc = document.getElementById('gaugeArc');
121
+ var $gval = document.getElementById('gaugeVal');
122
+ var $glab = document.getElementById('gaugeLabel');
123
+ var $quota = document.getElementById('quotaBars');
124
+ var $tokens = document.getElementById('tokenCards');
125
+ var canvas = document.getElementById('eventsCanvas');
126
+ var cx = canvas.getContext('2d');
127
+ var cachedEvents = [];
128
+
129
+ function status(ok) {
130
+ $dot.className = 'status-dot ' + (ok ? 'ok' : 'err');
131
+ $stxt.textContent = ok ? '연결됨' : '연결 실패';
132
+ }
133
+
134
+ function ts(d) {
135
+ return d.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
136
+ }
137
+
138
+ function fmtTok(n) {
139
+ if (n == null) return '-';
140
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
141
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
142
+ return String(n);
143
+ }
144
+
145
+ function barCol(p) {
146
+ if (p >= 80) return '#f85149';
147
+ if (p >= 50) return '#d29922';
148
+ return '#3fb950';
149
+ }
150
+
151
+ // rt: ISO 문자열 또는 Unix 초(정수). Unix 초는 1e10 미만으로 구분
152
+ function remain(rt) {
153
+ if (!rt) return '';
154
+ var ts = typeof rt === 'number' && rt < 1e10 ? rt * 1000 : new Date(rt).getTime();
155
+ if (!isFinite(ts)) return '';
156
+ var ms = ts - Date.now();
157
+ if (ms <= 0) return '만료됨';
158
+ var h = Math.floor(ms / 3600000);
159
+ var m = Math.floor((ms % 3600000) / 60000);
160
+ return h > 0 ? h + '시간 ' + m + '분' : m + '분';
161
+ }
162
+
163
+ /* ── 게이지 ── */
164
+ function gauge(val) {
165
+ val = Math.max(0, Math.min(MAX_W, val || 0));
166
+ var r = val / MAX_W;
167
+ $arc.setAttribute('stroke-dasharray', (r * ARC_TOTAL) + ' ' + ARC_TOTAL);
168
+ $arc.setAttribute('stroke', r > 0.8 ? '#f85149' : r > 0.5 ? '#d29922' : '#3fb950');
169
+ $gval.textContent = val;
170
+ $glab.textContent = val + ' / ' + MAX_W;
171
+ }
172
+
173
+ /* ── 쿼터 ── */
174
+ function makeBar(label, pct) {
175
+ pct = Math.min(100, Math.max(0, Math.round(pct)));
176
+ var c = barCol(pct);
177
+ var inner = pct > 10 ? pct + '%' : '';
178
+ var outer = pct <= 10 ? pct + '%' : '';
179
+ return '<div class="bar-row">' +
180
+ '<span class="bar-label">' + esc(label) + '</span>' +
181
+ '<div class="bar-track"><div class="bar-fill" style="width:' + pct + '%;background:' + c + '">' + inner + '</div></div>' +
182
+ '<span class="bar-pct-outer">' + outer + '</span>' +
183
+ '</div>';
184
+ }
185
+
186
+ function quotaBlock(name, color, bars, resetTime) {
187
+ var rem = remain(resetTime);
188
+ var expired = rem === '만료됨';
189
+ var h = '<div class="quota-row"><div class="quota-header">' +
190
+ '<span class="cli-name" style="color:' + color + '">' + esc(name) + '</span>';
191
+ if (rem) h += '<span class="meta" style="' + (expired ? 'color:#f85149' : '') + '">리셋: ' + rem + '</span>';
192
+ h += '</div>';
193
+ for (var i = 0; i < bars.length; i++) h += makeBar(bars[i][0], bars[i][1]);
194
+ h += '</div>';
195
+ return h;
196
+ }
197
+
198
+ function renderQuota(d) {
199
+ var s = '';
200
+
201
+ // Claude
202
+ var cl = d.claude && d.claude.data;
203
+ if (cl) {
204
+ s += quotaBlock('Claude (c)', '#d19a66', [
205
+ ['5시간', cl.fiveHourPercent || 0],
206
+ ['주간', cl.weeklyPercent || 0]
207
+ ], cl.fiveHourResetsAt);
208
+ }
209
+
210
+ // Codex
211
+ var cx2 = d.codex && d.codex.buckets && d.codex.buckets.codex;
212
+ if (cx2) {
213
+ s += quotaBlock('Codex (x)', '#ffffff', [
214
+ ['Primary', cx2.primary ? cx2.primary.used_percent || 0 : 0],
215
+ ['Secondary', cx2.secondary ? cx2.secondary.used_percent || 0 : 0]
216
+ ], cx2.primary && cx2.primary.resets_at);
217
+ }
218
+
219
+ // Gemini
220
+ var gm = d.gemini && d.gemini.buckets;
221
+ if (gm && gm.length > 0) {
222
+ var frac = gm[0].remainingFraction;
223
+ s += quotaBlock('Gemini (g)', '#61afef', [
224
+ ['사용률', frac != null ? Math.round((1 - frac) * 100) : 0]
225
+ ], gm[0].resetTime);
226
+ }
227
+
228
+ $quota.innerHTML = s || '<div class="placeholder">쿼터 데이터 없음</div>';
229
+ }
230
+
231
+ /* ── 토큰 ── */
232
+ function renderTokens(acc) {
233
+ if (!acc || Object.keys(acc).length === 0) {
234
+ $tokens.innerHTML = '<div class="placeholder" style="grid-column:1/-1">누적 데이터 없음</div>';
235
+ return;
236
+ }
237
+ var h = '';
238
+ var keys = Object.keys(acc);
239
+ for (var i = 0; i < keys.length; i++) {
240
+ var k = keys[i], v = acc[k];
241
+ if (!v) continue;
242
+ h += '<div class="token-item">' +
243
+ '<div class="label">' + esc(k.toUpperCase()) + '</div>' +
244
+ '<div class="value">' + esc(fmtTok(v.tokens)) + '</div>' +
245
+ '<div class="calls">' + (v.calls || 0) + '회 호출</div>' +
246
+ '</div>';
247
+ }
248
+ $tokens.innerHTML = h || '<div class="placeholder" style="grid-column:1/-1">누적 데이터 없음</div>';
249
+ }
250
+
251
+ /* ── 이벤트 차트 ── */
252
+ var colorMap = { success: '#3fb950', failed: '#f85149', fail: '#f85149', error: '#f85149', timeout: '#d29922' };
253
+
254
+ function drawEvents(evts) {
255
+ var dpr = window.devicePixelRatio || 1;
256
+ var rect = canvas.parentElement.getBoundingClientRect();
257
+ var w = rect.width, h = rect.height;
258
+ canvas.width = w * dpr;
259
+ canvas.height = h * dpr;
260
+ cx.setTransform(dpr, 0, 0, dpr, 0, 0);
261
+ cx.clearRect(0, 0, w, h);
262
+
263
+ if (!evts || evts.length === 0) {
264
+ cx.fillStyle = '#8b949e';
265
+ cx.font = '13px system-ui';
266
+ cx.textAlign = 'center';
267
+ cx.fillText('이벤트 데이터 없음', w / 2, h / 2);
268
+ return;
269
+ }
270
+
271
+ var pad = { l: 50, r: 16, t: 12, b: 28 };
272
+ var cw = w - pad.l - pad.r;
273
+ var ch = h - pad.t - pad.b;
274
+
275
+ var times = [];
276
+ for (var i = 0; i < evts.length; i++) {
277
+ times.push(new Date(evts[i].timestamp || evts[i].time || evts[i].t).getTime());
278
+ }
279
+ var tMin = Math.min.apply(null, times);
280
+ var tMax = Math.max.apply(null, times);
281
+ if (tMax === tMin) tMax = tMin + 60000;
282
+
283
+ // X축 라인
284
+ cx.strokeStyle = '#30363d';
285
+ cx.lineWidth = 1;
286
+ cx.beginPath();
287
+ cx.moveTo(pad.l, pad.t + ch);
288
+ cx.lineTo(pad.l + cw, pad.t + ch);
289
+ cx.stroke();
290
+
291
+ // X축 라벨
292
+ cx.fillStyle = '#8b949e';
293
+ cx.font = '11px system-ui';
294
+ cx.textAlign = 'center';
295
+ var nL = Math.min(6, evts.length);
296
+ for (var j = 0; j < nL; j++) {
297
+ var tp = tMin + (tMax - tMin) * j / (nL - 1 || 1);
298
+ var xp = pad.l + cw * (tp - tMin) / (tMax - tMin);
299
+ var dd = new Date(tp);
300
+ cx.fillText(dd.getHours() + ':' + String(dd.getMinutes()).padStart(2, '0'), xp, h - 6);
301
+ }
302
+
303
+ // 도트
304
+ var dr = Math.max(3, Math.min(6, cw / evts.length * 0.4));
305
+ for (var k = 0; k < evts.length; k++) {
306
+ var ev = evts[k];
307
+ var t = times[k];
308
+ var ex = pad.l + cw * (t - tMin) / (tMax - tMin);
309
+ // Y: 해시 분산 (겹침 방지)
310
+ var ey = pad.t + ch * 0.15 + (ch * 0.65) * ((k * 7 + 3) % 13) / 13;
311
+ var st = (ev.status || ev.result || 'success').toLowerCase();
312
+ cx.beginPath();
313
+ cx.arc(ex, ey, dr, 0, Math.PI * 2);
314
+ cx.fillStyle = colorMap[st] || '#8b949e';
315
+ cx.fill();
316
+ }
317
+ }
318
+
319
+ /* ── 폴링 ── */
320
+ function poll() {
321
+ fetch('/api/qos-stats')
322
+ .then(function(r) {
323
+ if (!r.ok) throw new Error('HTTP ' + r.status);
324
+ return r.json();
325
+ })
326
+ .then(function(data) {
327
+ status(true);
328
+ $time.textContent = ts(new Date());
329
+
330
+ gauge(data.aimd ? data.aimd.batchSize : 0);
331
+ renderQuota(data);
332
+
333
+ cachedEvents = (data.aimd && data.aimd.events) || [];
334
+ drawEvents(cachedEvents);
335
+
336
+ renderTokens(data.accumulator);
337
+ })
338
+ .catch(function(e) {
339
+ status(false);
340
+ $time.textContent = ts(new Date()) + ' (실패)';
341
+ });
342
+ }
343
+
344
+ var rTimer;
345
+ window.addEventListener('resize', function() {
346
+ clearTimeout(rTimer);
347
+ rTimer = setTimeout(function() { drawEvents(cachedEvents); }, 150);
348
+ });
349
+
350
+ poll();
351
+ setInterval(poll, POLL);
352
+ })();
353
+ </script>
354
+ </body>
355
+ </html>
Binary file
Binary file