@rynfar/meridian 1.37.8 → 1.39.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 (83) hide show
  1. package/README.md +54 -3
  2. package/dist/{cli-pr79d7nw.js → cli-4rqtm83g.js} +33 -2
  3. package/dist/{cli-z5r7ptsm.js → cli-jbdchsr4.js} +1109 -231
  4. package/dist/cli-sry5aqdj.js +54 -0
  5. package/dist/cli.js +5 -4
  6. package/dist/pluginPage-85s6t6k8.js +405 -0
  7. package/dist/{profilePage-g5t5t6av.js → profilePage-77z05e0r.js} +4 -8
  8. package/dist/proxy/adapter.d.ts +45 -12
  9. package/dist/proxy/adapter.d.ts.map +1 -1
  10. package/dist/proxy/adapters/claudecode.d.ts +21 -0
  11. package/dist/proxy/adapters/claudecode.d.ts.map +1 -0
  12. package/dist/proxy/adapters/crush.d.ts +2 -0
  13. package/dist/proxy/adapters/crush.d.ts.map +1 -1
  14. package/dist/proxy/adapters/detect.d.ts +3 -2
  15. package/dist/proxy/adapters/detect.d.ts.map +1 -1
  16. package/dist/proxy/adapters/droid.d.ts +2 -0
  17. package/dist/proxy/adapters/droid.d.ts.map +1 -1
  18. package/dist/proxy/adapters/forgecode.d.ts +2 -0
  19. package/dist/proxy/adapters/forgecode.d.ts.map +1 -1
  20. package/dist/proxy/adapters/opencode.d.ts +2 -0
  21. package/dist/proxy/adapters/opencode.d.ts.map +1 -1
  22. package/dist/proxy/adapters/passthrough.d.ts +2 -0
  23. package/dist/proxy/adapters/passthrough.d.ts.map +1 -1
  24. package/dist/proxy/adapters/pi.d.ts +2 -0
  25. package/dist/proxy/adapters/pi.d.ts.map +1 -1
  26. package/dist/proxy/agentDefs.d.ts +2 -0
  27. package/dist/proxy/agentDefs.d.ts.map +1 -1
  28. package/dist/proxy/agentMatch.d.ts +11 -1
  29. package/dist/proxy/agentMatch.d.ts.map +1 -1
  30. package/dist/proxy/messages.d.ts +11 -0
  31. package/dist/proxy/messages.d.ts.map +1 -1
  32. package/dist/proxy/models.d.ts +25 -0
  33. package/dist/proxy/models.d.ts.map +1 -1
  34. package/dist/proxy/openai.d.ts.map +1 -1
  35. package/dist/proxy/passthroughTools.d.ts +18 -0
  36. package/dist/proxy/passthroughTools.d.ts.map +1 -1
  37. package/dist/proxy/plugins/loader.d.ts +6 -0
  38. package/dist/proxy/plugins/loader.d.ts.map +1 -0
  39. package/dist/proxy/plugins/pluginPage.d.ts +7 -0
  40. package/dist/proxy/plugins/pluginPage.d.ts.map +1 -0
  41. package/dist/proxy/plugins/stats.d.ts +61 -0
  42. package/dist/proxy/plugins/stats.d.ts.map +1 -0
  43. package/dist/proxy/plugins/types.d.ts +21 -0
  44. package/dist/proxy/plugins/types.d.ts.map +1 -0
  45. package/dist/proxy/plugins/validation.d.ts +8 -0
  46. package/dist/proxy/plugins/validation.d.ts.map +1 -0
  47. package/dist/proxy/query.d.ts +27 -4
  48. package/dist/proxy/query.d.ts.map +1 -1
  49. package/dist/proxy/server.d.ts +2 -0
  50. package/dist/proxy/server.d.ts.map +1 -1
  51. package/dist/proxy/session/lineage.d.ts +10 -1
  52. package/dist/proxy/session/lineage.d.ts.map +1 -1
  53. package/dist/proxy/transform.d.ts +137 -0
  54. package/dist/proxy/transform.d.ts.map +1 -0
  55. package/dist/proxy/transforms/crush.d.ts +3 -0
  56. package/dist/proxy/transforms/crush.d.ts.map +1 -0
  57. package/dist/proxy/transforms/droid.d.ts +3 -0
  58. package/dist/proxy/transforms/droid.d.ts.map +1 -0
  59. package/dist/proxy/transforms/forgecode.d.ts +3 -0
  60. package/dist/proxy/transforms/forgecode.d.ts.map +1 -0
  61. package/dist/proxy/transforms/opencode.d.ts +3 -0
  62. package/dist/proxy/transforms/opencode.d.ts.map +1 -0
  63. package/dist/proxy/transforms/passthrough.d.ts +3 -0
  64. package/dist/proxy/transforms/passthrough.d.ts.map +1 -0
  65. package/dist/proxy/transforms/pi.d.ts +3 -0
  66. package/dist/proxy/transforms/pi.d.ts.map +1 -0
  67. package/dist/proxy/transforms/registry.d.ts +3 -0
  68. package/dist/proxy/transforms/registry.d.ts.map +1 -0
  69. package/dist/proxy/types.d.ts +6 -0
  70. package/dist/proxy/types.d.ts.map +1 -1
  71. package/dist/server.js +14 -5
  72. package/dist/stats-4c4ewmdh.js +17 -0
  73. package/dist/telemetry/dashboard.d.ts.map +1 -1
  74. package/dist/telemetry/landing.d.ts.map +1 -1
  75. package/dist/telemetry/profileBar.d.ts +13 -1
  76. package/dist/telemetry/profileBar.d.ts.map +1 -1
  77. package/dist/telemetry/profilePage.d.ts.map +1 -1
  78. package/dist/telemetry/settingsPage.d.ts +1 -1
  79. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  80. package/dist/telemetry/sqlite.d.ts.map +1 -1
  81. package/dist/telemetry/types.d.ts +4 -0
  82. package/dist/telemetry/types.d.ts.map +1 -1
  83. package/package.json +4 -2
@@ -0,0 +1,54 @@
1
+ // src/proxy/plugins/stats.ts
2
+ var stats = new Map;
3
+ function emptyStats() {
4
+ return { hooks: {} };
5
+ }
6
+ function emptyHook() {
7
+ return { invocations: 0, errors: 0, totalMs: 0 };
8
+ }
9
+ function registerPluginStats(name) {
10
+ stats.set(name, emptyStats());
11
+ }
12
+ function resetAllPluginStats() {
13
+ stats.clear();
14
+ }
15
+ function isTrackedPlugin(name) {
16
+ return stats.has(name);
17
+ }
18
+ function recordInvocation(name, hook, durationMs) {
19
+ const entry = stats.get(name);
20
+ if (!entry)
21
+ return;
22
+ const h = entry.hooks[hook] ?? emptyHook();
23
+ h.invocations += 1;
24
+ h.totalMs += durationMs;
25
+ entry.hooks[hook] = h;
26
+ entry.lastInvokedAt = Date.now();
27
+ }
28
+ function recordError(name, hook, err) {
29
+ const entry = stats.get(name);
30
+ if (!entry)
31
+ return;
32
+ const h = entry.hooks[hook] ?? emptyHook();
33
+ h.errors += 1;
34
+ entry.hooks[hook] = h;
35
+ const at = Date.now();
36
+ entry.lastInvokedAt = at;
37
+ entry.lastError = {
38
+ hook,
39
+ message: err instanceof Error ? err.message : String(err),
40
+ at
41
+ };
42
+ }
43
+ function getPluginStats(name) {
44
+ const entry = stats.get(name);
45
+ if (!entry)
46
+ return;
47
+ return {
48
+ hooks: Object.fromEntries(Object.entries(entry.hooks).map(([k, v]) => [k, { ...v }])),
49
+ lastInvokedAt: entry.lastInvokedAt,
50
+ ...entry.lastError ? { lastError: { ...entry.lastError } } : {}
51
+ };
52
+ }
53
+
54
+ export { registerPluginStats, resetAllPluginStats, isTrackedPlugin, recordInvocation, recordError, getPluginStats };
package/dist/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-z5r7ptsm.js";
5
- import"./cli-pr79d7nw.js";
6
- import"./cli-rtab0qa6.js";
7
- import"./cli-m9pfb7h9.js";
4
+ } from "./cli-jbdchsr4.js";
8
5
  import"./cli-vdp9s10c.js";
6
+ import"./cli-sry5aqdj.js";
7
+ import"./cli-4rqtm83g.js";
9
8
  import"./cli-340h1chz.js";
9
+ import"./cli-rtab0qa6.js";
10
+ import"./cli-m9pfb7h9.js";
10
11
  import {
11
12
  __require
12
13
  } from "./cli-p9swy5t3.js";
@@ -0,0 +1,405 @@
1
+ import {
2
+ init_profileBar,
3
+ profileBarCss,
4
+ profileBarHtml,
5
+ profileBarJs,
6
+ themeCss
7
+ } from "./cli-4rqtm83g.js";
8
+ import"./cli-p9swy5t3.js";
9
+
10
+ // src/proxy/plugins/pluginPage.ts
11
+ init_profileBar();
12
+ var pluginPageHtml = `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="utf-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1">
17
+ <title>Meridian — Plugins</title>
18
+ <style>
19
+ ${themeCss}
20
+ * { box-sizing: border-box; margin: 0; padding: 0; }
21
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
22
+ background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }
23
+ .container { max-width: 960px; margin: 0 auto; padding: 32px 24px; }
24
+
25
+ .back-link { display: inline-flex; align-items: center; gap: 6px; color: var(--muted);
26
+ text-decoration: none; font-size: 13px; margin-bottom: 24px; transition: color 0.15s; }
27
+ .back-link:hover { color: var(--text); }
28
+
29
+ .page-header { display: flex; align-items: flex-start; justify-content: space-between;
30
+ gap: 16px; margin-bottom: 6px; flex-wrap: wrap; }
31
+ .page-header h1 { font-size: 24px; font-weight: 700; letter-spacing: 0.5px; }
32
+ .tagline { color: var(--muted); font-size: 14px; margin-bottom: 28px; }
33
+
34
+ .reload-btn { padding: 8px 18px; font-size: 13px; font-weight: 500;
35
+ background: var(--surface2); color: var(--accent); border: 1px solid var(--accent);
36
+ border-radius: 8px; cursor: pointer; transition: all 0.15s; white-space: nowrap; }
37
+ .reload-btn:hover { background: rgba(139,92,246,0.15); }
38
+ .reload-btn:disabled { opacity: 0.5; cursor: default; }
39
+ .reload-btn.loading { opacity: 0.7; }
40
+
41
+ .reload-status { font-size: 12px; color: var(--green); opacity: 0; transition: opacity 0.3s;
42
+ margin-left: 8px; }
43
+ .reload-status.show { opacity: 1; }
44
+ .reload-status.error { color: var(--red); }
45
+
46
+ .header-actions { display: flex; align-items: center; gap: 0; }
47
+
48
+ .plugin-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
49
+ padding: 20px; margin-bottom: 12px; transition: border-color 0.2s; }
50
+ .plugin-card.status-error { border-color: rgba(248,81,73,0.4); }
51
+
52
+ .plugin-card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px;
53
+ flex-wrap: wrap; }
54
+ .plugin-name { font-size: 16px; font-weight: 600; }
55
+ .plugin-version { font-size: 11px; color: var(--muted);
56
+ font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; }
57
+
58
+ .status-badge { font-size: 10px; padding: 2px 9px; border-radius: 4px;
59
+ text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
60
+ .badge-active { background: rgba(63,185,80,0.15); color: var(--green);
61
+ border: 1px solid rgba(63,185,80,0.3); }
62
+ .badge-disabled { background: var(--surface2); color: var(--muted);
63
+ border: 1px solid var(--border); }
64
+ .badge-error { background: rgba(248,81,73,0.12); color: var(--red);
65
+ border: 1px solid rgba(248,81,73,0.3); }
66
+
67
+ .plugin-description { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
68
+
69
+ .plugin-meta { display: flex; gap: 20px; flex-wrap: wrap; font-size: 12px; }
70
+ .meta-item { display: flex; align-items: center; gap: 6px; }
71
+ .meta-label { color: var(--muted); text-transform: uppercase; font-size: 10px;
72
+ letter-spacing: 0.5px; font-weight: 500; }
73
+ .meta-value { color: var(--text);
74
+ font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 11px; }
75
+
76
+ .plugin-error-box { margin-top: 12px; padding: 10px 14px;
77
+ background: rgba(248,81,73,0.08); border: 1px solid rgba(248,81,73,0.3);
78
+ border-radius: 8px; font-size: 12px; color: var(--red);
79
+ font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; word-break: break-word; }
80
+
81
+ .empty-state { text-align: center; padding: 56px 24px; color: var(--muted);
82
+ background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
83
+ .empty-state h2 { font-size: 16px; font-weight: 600; margin-bottom: 10px; color: var(--text); }
84
+ .empty-state p { font-size: 13px; line-height: 1.7; }
85
+ .empty-state code { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
86
+ font-size: 12px; background: var(--surface2); padding: 2px 7px;
87
+ border-radius: 4px; color: #a78bfa; }
88
+
89
+ /* ── Hero panel: aggregate stats at a glance ── */
90
+ .hero-panel { background: linear-gradient(135deg, var(--surface) 0%, var(--surface2) 100%);
91
+ border: 1px solid var(--border); border-radius: 14px;
92
+ padding: 20px 24px; margin-bottom: 24px; }
93
+ .hero-row { display: flex; align-items: center; }
94
+ .hero-row-top { gap: 28px; flex-wrap: wrap; padding-bottom: 16px;
95
+ border-bottom: 1px solid var(--border); margin-bottom: 14px; }
96
+ .hero-metric { flex: 1 1 0; min-width: 110px; }
97
+ .hero-num { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
98
+ font-size: 28px; font-weight: 600; color: var(--text);
99
+ line-height: 1.1; font-variant-numeric: tabular-nums; }
100
+ .hero-num.hero-err { color: var(--red); }
101
+ .hero-sub { font-size: 15px; font-weight: 400; color: var(--muted); margin-left: 4px; }
102
+ .hero-unit { font-size: 13px; font-weight: 400; color: var(--muted); margin-left: 3px; }
103
+ .hero-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 0.6px;
104
+ color: var(--muted); margin-top: 6px; font-weight: 500; }
105
+ .hero-row-bottom { gap: 18px; justify-content: space-between; flex-wrap: wrap;
106
+ font-size: 12px; color: var(--muted); }
107
+ .hero-status { display: flex; align-items: center; gap: 6px; }
108
+ .hero-status strong { color: var(--text); font-weight: 600; }
109
+ .chip-sep { width: 1px; height: 14px; background: var(--border); margin: 0 6px; }
110
+ .status-chip { font-size: 10px; line-height: 1; }
111
+ .status-chip-active { color: var(--green); }
112
+ .status-chip-disabled { color: var(--muted); }
113
+ .status-chip-error { color: var(--red); }
114
+ .hero-busiest { color: var(--muted); font-size: 12px; }
115
+ .hero-busiest strong { color: var(--accent); font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; }
116
+ .hero-busiest-count { color: var(--muted); font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 11px; }
117
+
118
+ .plugin-stats { margin-top: 14px; padding-top: 14px;
119
+ border-top: 1px solid var(--border); }
120
+ .plugin-stats-empty { margin-top: 14px; padding: 10px 14px;
121
+ background: var(--surface2); border-radius: 6px; font-size: 12px;
122
+ color: var(--muted); font-style: italic; }
123
+ .stats-header { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px;
124
+ color: var(--muted); font-weight: 500; margin-bottom: 8px; }
125
+ .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
126
+ margin-bottom: 12px; }
127
+ .stat-cell { background: var(--surface2); border: 1px solid var(--border);
128
+ border-radius: 8px; padding: 10px 12px; }
129
+ .stat-num { font-size: 18px; font-weight: 600; color: var(--text);
130
+ font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
131
+ font-variant-numeric: tabular-nums; line-height: 1.2; }
132
+ .stat-num.stat-err { color: var(--red); }
133
+ .stat-unit { font-size: 11px; color: var(--muted); font-weight: 400; margin-left: 2px; }
134
+ .stat-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 0.4px;
135
+ color: var(--muted); margin-top: 4px; }
136
+ .stats-breakdown { display: flex; gap: 8px; flex-wrap: wrap; }
137
+ .hook-pill { font-size: 11px; padding: 3px 8px; border-radius: 4px;
138
+ background: var(--surface2); color: var(--muted);
139
+ font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; }
140
+ .hook-pill strong { color: var(--text); font-weight: 600; }
141
+ .hook-err { color: var(--red); font-weight: 500; }
142
+ ` + profileBarCss + `
143
+ </style>
144
+ </head>
145
+ <body>
146
+ ` + profileBarHtml + `
147
+ <div class="container">
148
+ <a href="/" class="back-link">&#8592; Back to Meridian</a>
149
+
150
+ <div class="page-header">
151
+ <div>
152
+ <h1>Plugins</h1>
153
+ </div>
154
+ <div class="header-actions">
155
+ <button class="reload-btn" id="reloadBtn" onclick="reloadPlugins()">Reload Plugins</button>
156
+ <span class="reload-status" id="reloadStatus"></span>
157
+ </div>
158
+ </div>
159
+ <div class="tagline">Transform request and response behavior with composable plugins</div>
160
+
161
+ <div id="content"><div style="color:var(--muted);padding:40px;text-align:center">Loading…</div></div>
162
+ </div>
163
+
164
+ <script>
165
+ function esc(s) {
166
+ if (s == null) return '';
167
+ var d = document.createElement('div');
168
+ d.textContent = String(s);
169
+ return d.innerHTML;
170
+ }
171
+
172
+ async function loadPlugins() {
173
+ try {
174
+ var res = await fetch('/plugins/list');
175
+ var data = await res.json();
176
+ render(data.plugins || []);
177
+ } catch {
178
+ document.getElementById('content').innerHTML =
179
+ '<div class="empty-state"><h2>Could not load plugins</h2><p>Is Meridian running?</p></div>';
180
+ }
181
+ }
182
+
183
+ async function reloadPlugins() {
184
+ var btn = document.getElementById('reloadBtn');
185
+ var status = document.getElementById('reloadStatus');
186
+ btn.disabled = true;
187
+ btn.classList.add('loading');
188
+ btn.textContent = 'Reloading…';
189
+ status.className = 'reload-status';
190
+ status.textContent = '';
191
+ try {
192
+ var res = await fetch('/plugins/reload', { method: 'POST' });
193
+ var data = await res.json();
194
+ if (data.success) {
195
+ status.textContent = '✓ Reloaded';
196
+ status.className = 'reload-status show';
197
+ } else {
198
+ status.textContent = data.error || 'Reload failed';
199
+ status.className = 'reload-status show error';
200
+ }
201
+ await loadPlugins();
202
+ } catch {
203
+ status.textContent = 'Reload failed';
204
+ status.className = 'reload-status show error';
205
+ } finally {
206
+ btn.disabled = false;
207
+ btn.classList.remove('loading');
208
+ btn.textContent = 'Reload Plugins';
209
+ setTimeout(function() { status.className = 'reload-status'; }, 3000);
210
+ }
211
+ }
212
+
213
+ function render(plugins) {
214
+ if (!plugins.length) {
215
+ document.getElementById('content').innerHTML =
216
+ '<div class="empty-state">'
217
+ + '<h2>No plugins found</h2>'
218
+ + '<p>Drop <code>.ts</code> or <code>.js</code> files in <code>~/.config/meridian/plugins/</code> and reload.</p>'
219
+ + '</div>';
220
+ return;
221
+ }
222
+
223
+ var active = plugins.filter(function(p) { return p.status === 'active'; }).length;
224
+ var disabled = plugins.filter(function(p) { return p.status === 'disabled'; }).length;
225
+ var errors = plugins.filter(function(p) { return p.status === 'error'; }).length;
226
+
227
+ // ── Aggregate stats across all active plugins ──
228
+ var totalCalls = 0, totalErrors = 0, totalMs = 0, lastSeen = 0;
229
+ var busiestName = null, busiestCount = 0;
230
+ for (var i = 0; i < plugins.length; i++) {
231
+ var p = plugins[i];
232
+ if (p.status !== 'active' || !p.stats) continue;
233
+ var pluginCalls = 0, pluginErrors = 0, pluginMs = 0;
234
+ var hookNames = Object.keys(p.stats.hooks || {});
235
+ for (var j = 0; j < hookNames.length; j++) {
236
+ var h = p.stats.hooks[hookNames[j]];
237
+ pluginCalls += h.invocations || 0;
238
+ pluginErrors += h.errors || 0;
239
+ pluginMs += h.totalMs || 0;
240
+ }
241
+ totalCalls += pluginCalls;
242
+ totalErrors += pluginErrors;
243
+ totalMs += pluginMs;
244
+ if (p.stats.lastInvokedAt && p.stats.lastInvokedAt > lastSeen) {
245
+ lastSeen = p.stats.lastInvokedAt;
246
+ }
247
+ if (pluginCalls > busiestCount) {
248
+ busiestCount = pluginCalls;
249
+ busiestName = p.name;
250
+ }
251
+ }
252
+ var aggAvg = totalCalls > 0 ? (totalMs / totalCalls).toFixed(2) : '0.00';
253
+
254
+ var html = '<div class="hero-panel">';
255
+ html += '<div class="hero-row hero-row-top">';
256
+ html += '<div class="hero-metric">'
257
+ + '<div class="hero-num">' + active + '<span class="hero-sub">/ ' + plugins.length + '</span></div>'
258
+ + '<div class="hero-lbl">Active plugins</div>'
259
+ + '</div>';
260
+ html += '<div class="hero-metric">'
261
+ + '<div class="hero-num">' + totalCalls.toLocaleString() + '</div>'
262
+ + '<div class="hero-lbl">Total invocations</div>'
263
+ + '</div>';
264
+ html += '<div class="hero-metric">'
265
+ + '<div class="hero-num ' + (totalErrors > 0 ? 'hero-err' : '') + '">' + totalErrors.toLocaleString() + '</div>'
266
+ + '<div class="hero-lbl">Errors</div>'
267
+ + '</div>';
268
+ html += '<div class="hero-metric">'
269
+ + '<div class="hero-num">' + aggAvg + '<span class="hero-unit">ms</span></div>'
270
+ + '<div class="hero-lbl">Avg latency</div>'
271
+ + '</div>';
272
+ html += '<div class="hero-metric">'
273
+ + '<div class="hero-num">' + (lastSeen ? formatRelative(lastSeen) : '—') + '</div>'
274
+ + '<div class="hero-lbl">Last request</div>'
275
+ + '</div>';
276
+ html += '</div>';
277
+
278
+ // Status breakdown + busiest plugin row
279
+ html += '<div class="hero-row hero-row-bottom">';
280
+ html += '<div class="hero-status">';
281
+ html += '<span class="status-chip status-chip-active">●</span> <strong>' + active + '</strong> active';
282
+ if (disabled) html += '<span class="chip-sep"></span><span class="status-chip status-chip-disabled">●</span> <strong>' + disabled + '</strong> disabled';
283
+ if (errors) html += '<span class="chip-sep"></span><span class="status-chip status-chip-error">●</span> <strong>' + errors + '</strong> error' + (errors !== 1 ? 's' : '');
284
+ html += '</div>';
285
+ if (busiestName) {
286
+ html += '<div class="hero-busiest">Busiest: <strong>' + esc(busiestName) + '</strong> <span class="hero-busiest-count">(' + busiestCount.toLocaleString() + ' calls)</span></div>';
287
+ }
288
+ html += '</div>';
289
+ html += '</div>';
290
+
291
+ for (var i = 0; i < plugins.length; i++) {
292
+ var p = plugins[i];
293
+ var statusClass = p.status === 'active' ? 'badge-active' : p.status === 'error' ? 'badge-error' : 'badge-disabled';
294
+ html += '<div class="plugin-card' + (p.status === 'error' ? ' status-error' : '') + '">';
295
+
296
+ html += '<div class="plugin-card-header">';
297
+ html += '<span class="plugin-name">' + esc(p.name) + '</span>';
298
+ if (p.version) html += '<span class="plugin-version">v' + esc(p.version) + '</span>';
299
+ html += '<span class="status-badge ' + statusClass + '">' + esc(p.status) + '</span>';
300
+ html += '</div>';
301
+
302
+ if (p.description) {
303
+ html += '<div class="plugin-description">' + esc(p.description) + '</div>';
304
+ }
305
+
306
+ html += '<div class="plugin-meta">';
307
+ var hooks = (p.hooks && p.hooks.length) ? p.hooks.join(', ') : '—';
308
+ html += '<div class="meta-item"><span class="meta-label">Hooks</span><span class="meta-value">' + esc(hooks) + '</span></div>';
309
+ var adapters = (p.adapters && p.adapters.length) ? p.adapters.join(', ') : 'All adapters';
310
+ html += '<div class="meta-item"><span class="meta-label">Adapters</span><span class="meta-value">' + esc(adapters) + '</span></div>';
311
+ html += '</div>';
312
+
313
+ if (p.status === 'active' && p.stats) {
314
+ html += renderStats(p.stats);
315
+ }
316
+
317
+ if (p.status === 'error' && p.error) {
318
+ html += '<div class="plugin-error-box">' + esc(p.error) + '</div>';
319
+ }
320
+
321
+ html += '</div>';
322
+ }
323
+
324
+ document.getElementById('content').innerHTML = html;
325
+ }
326
+
327
+ function renderStats(s) {
328
+ var hooks = s.hooks || {};
329
+ var hookNames = Object.keys(hooks);
330
+ if (hookNames.length === 0 && !s.lastInvokedAt && !s.lastError) {
331
+ return '<div class="plugin-stats-empty">No invocations yet — send a request to see counters.</div>';
332
+ }
333
+
334
+ var totalInvocations = 0, totalErrors = 0, totalMs = 0;
335
+ for (var i = 0; i < hookNames.length; i++) {
336
+ var h = hooks[hookNames[i]];
337
+ totalInvocations += h.invocations || 0;
338
+ totalErrors += h.errors || 0;
339
+ totalMs += h.totalMs || 0;
340
+ }
341
+
342
+ var html = '<div class="plugin-stats">';
343
+ html += '<div class="stats-header">Invocations</div>';
344
+ html += '<div class="stats-grid">';
345
+ html += '<div class="stat-cell"><div class="stat-num">' + totalInvocations + '</div><div class="stat-lbl">calls</div></div>';
346
+ html += '<div class="stat-cell"><div class="stat-num ' + (totalErrors > 0 ? 'stat-err' : '') + '">' + totalErrors + '</div><div class="stat-lbl">errors</div></div>';
347
+ var avgMs = totalInvocations > 0 ? (totalMs / totalInvocations).toFixed(2) : '0.00';
348
+ html += '<div class="stat-cell"><div class="stat-num">' + avgMs + '<span class="stat-unit">ms</span></div><div class="stat-lbl">avg</div></div>';
349
+ if (s.lastInvokedAt) {
350
+ html += '<div class="stat-cell"><div class="stat-num">' + formatRelative(s.lastInvokedAt) + '</div><div class="stat-lbl">last seen</div></div>';
351
+ }
352
+ html += '</div>';
353
+
354
+ if (hookNames.length > 0) {
355
+ html += '<div class="stats-breakdown">';
356
+ for (var j = 0; j < hookNames.length; j++) {
357
+ var name = hookNames[j];
358
+ var hd = hooks[name];
359
+ html += '<span class="hook-pill">'
360
+ + esc(name) + ': <strong>' + (hd.invocations || 0) + '</strong>'
361
+ + (hd.errors > 0 ? ' <span class="hook-err">(' + hd.errors + ' err)</span>' : '')
362
+ + '</span>';
363
+ }
364
+ html += '</div>';
365
+ }
366
+
367
+ if (s.lastError) {
368
+ html += '<div class="plugin-error-box">'
369
+ + 'Last error in <strong>' + esc(s.lastError.hook) + '</strong> '
370
+ + formatRelative(s.lastError.at)
371
+ + ': ' + esc(s.lastError.message)
372
+ + '</div>';
373
+ }
374
+
375
+ html += '</div>';
376
+ return html;
377
+ }
378
+
379
+ function formatRelative(ts) {
380
+ var diffMs = Date.now() - ts;
381
+ if (diffMs < 0) return 'just now';
382
+ var s = Math.floor(diffMs / 1000);
383
+ if (s < 5) return 'just now';
384
+ if (s < 60) return s + 's ago';
385
+ var m = Math.floor(s / 60);
386
+ if (m < 60) return m + 'm ago';
387
+ var h = Math.floor(m / 60);
388
+ if (h < 24) return h + 'h ago';
389
+ return Math.floor(h / 24) + 'd ago';
390
+ }
391
+
392
+ // Periodic refresh so you can watch invocation counters climb while
393
+ // you drive traffic through the proxy. Stops when the tab is hidden.
394
+ setInterval(function() {
395
+ if (document.visibilityState === 'visible') loadPlugins();
396
+ }, 3000);
397
+
398
+ loadPlugins();
399
+ ` + profileBarJs + `
400
+ </script>
401
+ </body>
402
+ </html>`;
403
+ export {
404
+ pluginPageHtml
405
+ };
@@ -2,8 +2,9 @@ import {
2
2
  init_profileBar,
3
3
  profileBarCss,
4
4
  profileBarHtml,
5
- profileBarJs
6
- } from "./cli-pr79d7nw.js";
5
+ profileBarJs,
6
+ themeCss
7
+ } from "./cli-4rqtm83g.js";
7
8
  import"./cli-p9swy5t3.js";
8
9
 
9
10
  // src/telemetry/profilePage.ts
@@ -15,12 +16,7 @@ var profilePageHtml = `<!DOCTYPE html>
15
16
  <meta name="viewport" content="width=device-width, initial-scale=1">
16
17
  <title>Meridian — Profiles</title>
17
18
  <style>
18
- :root {
19
- --bg: #0d1117; --surface: #161b22; --border: #30363d;
20
- --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
21
- --green: #3fb950; --yellow: #d29922; --red: #f85149;
22
- --purple: #bc8cff;
23
- }
19
+ ${themeCss}
24
20
  * { box-sizing: border-box; margin: 0; padding: 0; }
25
21
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
26
22
  background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
@@ -7,12 +7,12 @@
7
7
  import type { Context } from "hono";
8
8
  import type { SettingSource } from "@anthropic-ai/claude-agent-sdk";
9
9
  /**
10
- * An agent adapter provides agent-specific configuration to the proxy.
11
- * The proxy calls these methods during request handling to determine
12
- * how to interact with the calling agent.
10
+ * Core identity of an agent detection, session tracking, CWD extraction.
11
+ * This is the minimal interface for agent recognition. Behavioral customization
12
+ * (tool filtering, system prompt modifications, hooks) lives in Transform objects.
13
13
  */
14
- export interface AgentAdapter {
15
- /** Human-readable name for logging */
14
+ export interface AgentIdentity {
15
+ /** Human-readable name for logging and transform scoping */
16
16
  readonly name: string;
17
17
  /**
18
18
  * Extract a session ID from the request.
@@ -20,15 +20,53 @@ export interface AgentAdapter {
20
20
  */
21
21
  getSessionId(c: Context): string | undefined;
22
22
  /**
23
- * Extract the client's working directory from the request body.
24
- * Returns undefined to fall back to CLAUDE_PROXY_WORKDIR or process.cwd().
23
+ * Extract the SDK subprocess working directory from the request body.
24
+ *
25
+ * This path must exist on the proxy host because it becomes the SDK
26
+ * child_process cwd (passed through as the `cwd` option to the Claude
27
+ * Agent SDK's query() call). If it doesn't exist, the subprocess spawn
28
+ * fails or chdirs misbehave.
29
+ *
30
+ * Adapters that assume the client runs on the same host as the proxy
31
+ * (OpenCode, Crush) can return the client-local path here.
32
+ * Adapters for remote clients pointing at a network-exposed proxy
33
+ * (Claude Code) should return undefined and override
34
+ * extractClientWorkingDirectory instead.
35
+ *
36
+ * Returns undefined to fall back to MERIDIAN_WORKDIR / CLAUDE_PROXY_WORKDIR
37
+ * env vars, then process.cwd().
25
38
  */
26
39
  extractWorkingDirectory(body: any): string | undefined;
40
+ /**
41
+ * Optional: extract the client-local working directory (which may not
42
+ * exist on the proxy host). This is used for:
43
+ * - Conversation fingerprinting (per-client-project bucketing so two
44
+ * unrelated projects don't collide on identical first-message hashes).
45
+ * - A system prompt addendum so the model reports the correct path
46
+ * when asked and uses it for file path references.
47
+ *
48
+ * Return undefined to default to the SDK working directory (same machine
49
+ * assumption). Adapters for remote clients should implement this to parse
50
+ * the client's own "working directory" hint from the request body.
51
+ */
52
+ extractClientWorkingDirectory?(body: any): string | undefined;
27
53
  /**
28
54
  * Content normalization — convert message content to a stable string
29
55
  * for hashing. Agents may send content in different formats.
30
56
  */
31
57
  normalizeContent(content: any): string;
58
+ /**
59
+ * The MCP server name used by this agent.
60
+ * Tools are registered as `mcp__{name}__{tool}`.
61
+ */
62
+ getMcpServerName(): string;
63
+ }
64
+ /**
65
+ * An agent adapter provides agent-specific configuration to the proxy.
66
+ * The proxy calls these methods during request handling to determine
67
+ * how to interact with the calling agent.
68
+ */
69
+ export interface AgentAdapter extends AgentIdentity {
32
70
  /**
33
71
  * SDK built-in tools to block (replaced by MCP equivalents).
34
72
  * These are tools where the agent provides its own implementation.
@@ -40,11 +78,6 @@ export interface AgentAdapter {
40
78
  * can't handle.
41
79
  */
42
80
  getAgentIncompatibleTools(): readonly string[];
43
- /**
44
- * The MCP server name used by this agent.
45
- * Tools are registered as `mcp__{name}__{tool}`.
46
- */
47
- getMcpServerName(): string;
48
81
  /**
49
82
  * MCP tools that are allowed through the proxy's tool filter.
50
83
  */
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAEnE;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5C;;;OAGG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;IAE1B;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAAE,CAAA;IAEtC;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,IAAI,aAAa,EAAE,CAAA;IAErC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,OAAO,CAAA;IAE5B;;;;;;OAMG;IACH,sBAAsB,CAAC,IAAI,OAAO,CAAA;IAElC;;;;;;;;OAQG;IACH,yBAAyB,CAAC,IAAI,OAAO,CAAA;IAErC;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAEnE;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5C;;;;;;;;;;;;;;;;OAgBG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;;;;;;;;;OAWG;IACH,6BAA6B,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAE7D;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAAE,CAAA;IAEtC;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,IAAI,aAAa,EAAE,CAAA;IAErC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,OAAO,CAAA;IAE5B;;;;;;OAMG;IACH,sBAAsB,CAAC,IAAI,OAAO,CAAA;IAElC;;;;;;;;OAQG;IACH,yBAAyB,CAAC,IAAI,OAAO,CAAA;IAErC;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Claude Code agent adapter.
3
+ *
4
+ * Claude Code (claude-cli) is unusual among meridian clients in two ways:
5
+ * 1. It typically runs on a different machine than the proxy (pointing at
6
+ * ANTHROPIC_BASE_URL over the network), so its CWD doesn't exist on the
7
+ * proxy host.
8
+ * 2. Its system prompt embeds working-directory info using the
9
+ * `Primary working directory: <path>` format inside a `# Environment`
10
+ * block — different from OpenCode's `<env>Working directory: <path></env>`.
11
+ *
12
+ * Consequently this adapter:
13
+ * - Returns `undefined` from extractWorkingDirectory so the SDK subprocess
14
+ * chdirs into `process.cwd()` (a valid server path) rather than the
15
+ * client's local filesystem layout.
16
+ * - Parses the client's local CWD via extractClientWorkingDirectory for
17
+ * fingerprinting and a system-prompt hint (see server.ts + query.ts).
18
+ */
19
+ import type { AgentAdapter } from "../adapter";
20
+ export declare const claudeCodeAdapter: AgentAdapter;
21
+ //# sourceMappingURL=claudecode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudecode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/claudecode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAgC9C,eAAO,MAAM,iBAAiB,EAAE,YA8F/B,CAAA"}
@@ -19,4 +19,6 @@
19
19
  */
20
20
  import type { AgentAdapter } from "../adapter";
21
21
  export declare const crushAdapter: AgentAdapter;
22
+ import { crushTransforms } from "../transforms/crush";
23
+ export { crushTransforms };
22
24
  //# sourceMappingURL=crush.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crush.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/crush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAgB9C,eAAO,MAAM,YAAY,EAAE,YAyF1B,CAAA"}
1
+ {"version":3,"file":"crush.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/crush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAgB9C,eAAO,MAAM,YAAY,EAAE,YAyF1B,CAAA;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,CAAA"}
@@ -15,8 +15,9 @@ import type { AgentAdapter } from "../adapter";
15
15
  * 3. User-Agent starts with "opencode/" → OpenCode adapter
16
16
  * 4. User-Agent starts with "factory-cli/" → Droid adapter
17
17
  * 5. User-Agent starts with "Charm-Crush/" → Crush adapter
18
- * 6. litellm/* UA or x-litellm-* headers LiteLLM passthrough adapter
19
- * 7. Default → MERIDIAN_DEFAULT_AGENT env var, or OpenCode
18
+ * 6. User-Agent starts with "claude-cli/" Claude Code adapter
19
+ * 7. litellm/* UA or x-litellm-* headers → LiteLLM passthrough adapter
20
+ * 8. Default → MERIDIAN_DEFAULT_AGENT env var, or OpenCode
20
21
  */
21
22
  export declare function detectAdapter(c: Context): AgentAdapter;
22
23
  //# sourceMappingURL=detect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/detect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAuC9C;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CA+BtD"}
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/detect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AA0C9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CAsCtD"}