toolfunnel 0.3.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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +324 -0
  3. package/bin/toolfunnel.js +112 -0
  4. package/docs/admin-ui.md +404 -0
  5. package/docs/configuration.md +402 -0
  6. package/docs/hooks-and-gating.md +428 -0
  7. package/docs/logging.md +223 -0
  8. package/docs/management.md +429 -0
  9. package/docs/oauth.md +103 -0
  10. package/docs/setup.md +352 -0
  11. package/docs/tools-and-upstreams.md +469 -0
  12. package/hooks/hooks.manifest.json +27 -0
  13. package/hooks/scripts/.gitkeep +3 -0
  14. package/hooks/scripts/example-audit.js +21 -0
  15. package/hooks/scripts/example-deny-dangerous.js +31 -0
  16. package/mcp/expose.example.json +30 -0
  17. package/mcp/servers/mock-upstream/server.js +170 -0
  18. package/package.json +63 -0
  19. package/src/auth/config.js +172 -0
  20. package/src/auth/install.js +181 -0
  21. package/src/auth/resource-server.js +319 -0
  22. package/src/core/events.js +105 -0
  23. package/src/core/hook-engine.js +275 -0
  24. package/src/core/hook-loader.js +791 -0
  25. package/src/core/hook-runner.js +346 -0
  26. package/src/core/logger.js +169 -0
  27. package/src/core/matcher.js +51 -0
  28. package/src/core/metrics.js +75 -0
  29. package/src/core/tool-registry.js +180 -0
  30. package/src/extend/add-hook.md +142 -0
  31. package/src/extend/add-mcp.md +121 -0
  32. package/src/extend/create-tool.md +132 -0
  33. package/src/extend/howto.js +98 -0
  34. package/src/extend/package.md +154 -0
  35. package/src/mcp/aggregator.js +802 -0
  36. package/src/mcp/expose-store.js +430 -0
  37. package/src/mcp/gated-run.js +194 -0
  38. package/src/mcp/http-transport.js +962 -0
  39. package/src/mcp/mcp-client.js +524 -0
  40. package/src/mcp/protocol.js +466 -0
  41. package/src/mcp/server.js +1240 -0
  42. package/src/packages/packager.js +713 -0
  43. package/src/tools/drift.js +138 -0
  44. package/src/tools/registry.js +468 -0
  45. package/src/tools/tool-state.js +104 -0
  46. package/src/ui/public/app.js +1055 -0
  47. package/src/ui/public/index.html +249 -0
  48. package/src/ui/public/logo.jpg +0 -0
  49. package/src/ui/public/styles.css +535 -0
  50. package/src/ui/server.js +1144 -0
  51. package/tools/scripts/base64.js +139 -0
  52. package/tools/scripts/danger.js +61 -0
  53. package/tools/scripts/echo.js +39 -0
  54. package/tools/scripts/hash.js +101 -0
  55. package/tools/scripts/json.js +109 -0
  56. package/tools/scripts/text-stats.js +131 -0
  57. package/tools/scripts/tf-hook-add.js +152 -0
  58. package/tools/scripts/tf-hook-set.js +113 -0
  59. package/tools/scripts/tf-list.js +195 -0
  60. package/tools/scripts/tf-log.js +83 -0
  61. package/tools/scripts/tf-mcp-add.js +119 -0
  62. package/tools/scripts/tf-mcp-set.js +104 -0
  63. package/tools/scripts/tf-tool-add.js +129 -0
  64. package/tools/scripts/tf-tool-set.js +148 -0
  65. package/tools/scripts/uuid.js +102 -0
  66. package/tools/tools.register.json +171 -0
@@ -0,0 +1,535 @@
1
+ /* ToolFunnel management console — dark, modern, technical. Vanilla CSS, zero external assets. */
2
+
3
+ :root {
4
+ --bg: #0c0e13;
5
+ --bg-elev: #13161d;
6
+ --bg-card: #161a22;
7
+ --bg-card-hover: #1b202a;
8
+ --bg-input: #0f1218;
9
+ --border: #242a36;
10
+ --border-strong: #313a48;
11
+ --text: #e6e9ef;
12
+ --text-dim: #9aa3b2;
13
+ --text-faint: #6a7384;
14
+ --accent: #5cc8ff;
15
+ --accent-dim: #2b6f93;
16
+ --accent-glow: rgba(92, 200, 255, 0.18);
17
+ --good: #54d6a0;
18
+ --warn: #f5b860;
19
+ --danger: #ff6b78;
20
+ --mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", monospace;
21
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
22
+ --radius: 12px;
23
+ --radius-sm: 8px;
24
+ }
25
+
26
+ * { box-sizing: border-box; }
27
+
28
+ html, body {
29
+ margin: 0;
30
+ padding: 0;
31
+ background: var(--bg);
32
+ color: var(--text);
33
+ font-family: var(--sans);
34
+ font-size: 15px;
35
+ line-height: 1.5;
36
+ -webkit-font-smoothing: antialiased;
37
+ }
38
+
39
+ body {
40
+ background:
41
+ radial-gradient(900px 500px at 80% -10%, rgba(92, 200, 255, 0.06), transparent 60%),
42
+ radial-gradient(700px 400px at 0% 0%, rgba(84, 214, 160, 0.04), transparent 55%),
43
+ var(--bg);
44
+ min-height: 100vh;
45
+ }
46
+
47
+ .app {
48
+ max-width: 980px;
49
+ margin: 0 auto;
50
+ padding: 28px 24px 80px;
51
+ }
52
+
53
+ /* ── Top bar ─────────────────────────────────────────────────────────────── */
54
+ .topbar {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: space-between;
58
+ gap: 20px;
59
+ flex-wrap: wrap;
60
+ padding-bottom: 22px;
61
+ border-bottom: 1px solid var(--border);
62
+ }
63
+
64
+ .brand { display: flex; align-items: center; gap: 14px; }
65
+
66
+ .logo {
67
+ display: grid;
68
+ place-items: center;
69
+ width: 42px;
70
+ height: 42px;
71
+ border-radius: var(--radius-sm);
72
+ color: var(--accent);
73
+ background: linear-gradient(160deg, rgba(92, 200, 255, 0.16), rgba(92, 200, 255, 0.03));
74
+ border: 1px solid var(--border-strong);
75
+ box-shadow: 0 0 24px var(--accent-glow);
76
+ }
77
+
78
+ .brand-text h1 { margin: 0; font-size: 22px; font-weight: 650; letter-spacing: -0.02em; }
79
+ .tagline { margin: 1px 0 0; font-size: 12.5px; color: var(--text-faint); font-family: var(--mono); }
80
+
81
+ .stats { display: flex; gap: 10px; }
82
+ .stat {
83
+ display: flex;
84
+ flex-direction: column;
85
+ align-items: center;
86
+ min-width: 74px;
87
+ padding: 9px 14px;
88
+ background: var(--bg-elev);
89
+ border: 1px solid var(--border);
90
+ border-radius: var(--radius-sm);
91
+ }
92
+ .stat-num { font-family: var(--mono); font-size: 19px; font-weight: 600; color: var(--accent); line-height: 1.1; }
93
+ .stat-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-faint); }
94
+
95
+ /* ── Tabs ────────────────────────────────────────────────────────────────── */
96
+ .tabs { display: flex; gap: 4px; margin: 22px 0 18px; }
97
+ .tab {
98
+ appearance: none;
99
+ border: none;
100
+ background: transparent;
101
+ color: var(--text-dim);
102
+ font-family: var(--sans);
103
+ font-size: 14px;
104
+ font-weight: 550;
105
+ padding: 8px 18px;
106
+ border-radius: 999px;
107
+ cursor: pointer;
108
+ transition: background 0.15s, color 0.15s;
109
+ }
110
+ .tab:hover { color: var(--text); background: var(--bg-elev); }
111
+ .tab.is-active { color: var(--bg); background: var(--accent); }
112
+
113
+ .view { display: none; }
114
+ .view.is-active { display: block; animation: fade 0.2s ease; }
115
+ @keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
116
+
117
+ /* ── Toolbar (search + add button) ─────────────────────────────────────────── */
118
+ .toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
119
+
120
+ .searchbar { position: relative; display: flex; align-items: center; flex: 1; }
121
+ .search-icon { position: absolute; left: 14px; color: var(--text-faint); pointer-events: none; }
122
+ .searchbar input {
123
+ width: 100%;
124
+ padding: 12px 16px 12px 42px;
125
+ background: var(--bg-elev);
126
+ border: 1px solid var(--border-strong);
127
+ border-radius: var(--radius);
128
+ color: var(--text);
129
+ font-size: 15px;
130
+ font-family: var(--sans);
131
+ outline: none;
132
+ transition: border-color 0.15s, box-shadow 0.15s;
133
+ }
134
+ .searchbar input::placeholder { color: var(--text-faint); }
135
+ .searchbar input:focus { border-color: var(--accent-dim); box-shadow: 0 0 0 3px var(--accent-glow); }
136
+ .result-count {
137
+ position: absolute;
138
+ right: 16px;
139
+ font-family: var(--mono);
140
+ font-size: 12px;
141
+ color: var(--text-faint);
142
+ pointer-events: none;
143
+ }
144
+
145
+ /* ── Buttons ─────────────────────────────────────────────────────────────── */
146
+ .btn {
147
+ appearance: none;
148
+ font-family: var(--sans);
149
+ font-size: 14px;
150
+ font-weight: 550;
151
+ padding: 11px 18px;
152
+ border-radius: var(--radius-sm);
153
+ border: 1px solid var(--border-strong);
154
+ background: var(--bg-elev);
155
+ color: var(--text);
156
+ cursor: pointer;
157
+ white-space: nowrap;
158
+ transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.05s;
159
+ }
160
+ .btn:hover { background: var(--bg-card-hover); border-color: var(--accent-dim); }
161
+ .btn:active { transform: translateY(1px); }
162
+ .btn:disabled { opacity: 0.5; cursor: progress; }
163
+ .btn-add { color: var(--accent); }
164
+ .btn-add.is-open { background: var(--accent); color: var(--bg); border-color: var(--accent); }
165
+ .btn-primary { background: var(--accent); color: var(--bg); border-color: var(--accent); }
166
+ .btn-primary:hover { background: #7ad3ff; border-color: #7ad3ff; }
167
+ .btn-ghost { background: transparent; color: var(--text-dim); }
168
+ .btn-ghost:hover { background: var(--bg-elev); color: var(--text); }
169
+ .btn-sm { padding: 6px 12px; font-size: 12.5px; }
170
+
171
+ .btn-remove {
172
+ appearance: none;
173
+ font-family: var(--sans);
174
+ font-size: 12.5px;
175
+ font-weight: 550;
176
+ padding: 7px 12px;
177
+ border-radius: var(--radius-sm);
178
+ border: 1px solid var(--border-strong);
179
+ background: transparent;
180
+ color: var(--text-faint);
181
+ cursor: pointer;
182
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
183
+ }
184
+ .btn-remove:hover { color: var(--danger); border-color: rgba(255, 107, 120, 0.4); background: rgba(255, 107, 120, 0.08); }
185
+ .btn-remove.btn-sm { padding: 4px 9px; line-height: 1; }
186
+
187
+ /* ── Add forms ───────────────────────────────────────────────────────────── */
188
+ .addform {
189
+ background: var(--bg-card);
190
+ border: 1px solid var(--border-strong);
191
+ border-radius: var(--radius);
192
+ padding: 18px;
193
+ margin-bottom: 18px;
194
+ animation: fade 0.18s ease;
195
+ }
196
+ .field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
197
+ .field { display: flex; flex-direction: column; gap: 5px; }
198
+ .field.field-wide { grid-column: 1 / -1; }
199
+ .field > span { font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
200
+ .field > span em { color: var(--accent); font-style: normal; }
201
+ .field > span em.hint { color: var(--text-faint); text-transform: none; letter-spacing: 0; font-size: 11.5px; }
202
+ .field input, .field select, .field textarea {
203
+ width: 100%;
204
+ padding: 10px 12px;
205
+ background: var(--bg-input);
206
+ border: 1px solid var(--border-strong);
207
+ border-radius: var(--radius-sm);
208
+ color: var(--text);
209
+ font-size: 14px;
210
+ font-family: var(--sans);
211
+ outline: none;
212
+ transition: border-color 0.15s, box-shadow 0.15s;
213
+ }
214
+ .field input[id$="path"], .field input[id$="command"], .field #t-id, .field #m-id, .field #h-id,
215
+ .field #m-command, .field #h-command, .field #h-matcher, .field textarea { font-family: var(--mono); font-size: 13px; }
216
+ .field textarea { resize: vertical; line-height: 1.5; }
217
+ .field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent-dim); box-shadow: 0 0 0 3px var(--accent-glow); }
218
+ .field select { cursor: pointer; }
219
+
220
+ .subsection { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 14px; }
221
+ .subsection-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
222
+ .subsection-title { font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
223
+ .subsection-title em.hint { color: var(--text-faint); text-transform: none; letter-spacing: 0; font-size: 11.5px; }
224
+ .expose-rows { display: flex; flex-direction: column; gap: 8px; }
225
+ .expose-input-row { display: grid; grid-template-columns: 1fr 1fr 1fr auto; gap: 8px; align-items: center; }
226
+ .expose-input-row input {
227
+ padding: 8px 10px;
228
+ background: var(--bg-input);
229
+ border: 1px solid var(--border-strong);
230
+ border-radius: var(--radius-sm);
231
+ color: var(--text);
232
+ font-family: var(--mono);
233
+ font-size: 13px;
234
+ outline: none;
235
+ }
236
+ .expose-input-row input:focus { border-color: var(--accent-dim); box-shadow: 0 0 0 3px var(--accent-glow); }
237
+
238
+ .addform-actions { display: flex; gap: 10px; margin-top: 18px; }
239
+
240
+ /* ── Rows (tools / mcps / hooks share the card layout) ─────────────────────── */
241
+ .card-list { display: flex; flex-direction: column; gap: 10px; }
242
+
243
+ .row {
244
+ display: grid;
245
+ grid-template-columns: 1fr auto;
246
+ gap: 6px 18px;
247
+ padding: 16px 18px;
248
+ background: var(--bg-card);
249
+ border: 1px solid var(--border);
250
+ border-radius: var(--radius);
251
+ transition: border-color 0.15s, background 0.15s;
252
+ }
253
+ .row:hover { background: var(--bg-card-hover); border-color: var(--border-strong); }
254
+ .row.is-disabled { opacity: 0.55; }
255
+
256
+ .row-head { grid-column: 1; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
257
+ .row-name { font-size: 15.5px; font-weight: 600; letter-spacing: -0.01em; }
258
+ .row-name.mono { font-family: var(--mono); font-size: 14.5px; }
259
+ .row-id { font-family: var(--mono); font-size: 12px; color: var(--text-faint); }
260
+ .row-summary { grid-column: 1; color: var(--text-dim); font-size: 13.5px; margin: 0; }
261
+ .row-cmd { grid-column: 1; margin: 2px 0 0; font-family: var(--mono); font-size: 12px; color: var(--text-faint); word-break: break-all; }
262
+
263
+ .chip {
264
+ font-family: var(--mono);
265
+ font-size: 11px;
266
+ letter-spacing: 0.02em;
267
+ color: var(--accent);
268
+ background: rgba(92, 200, 255, 0.1);
269
+ border: 1px solid rgba(92, 200, 255, 0.22);
270
+ padding: 2px 9px;
271
+ border-radius: 999px;
272
+ }
273
+
274
+ /* Controls cluster (right side, spans the card rows) */
275
+ .row-controls {
276
+ grid-column: 2;
277
+ grid-row: 1 / span 3;
278
+ display: flex;
279
+ align-items: center;
280
+ gap: 16px;
281
+ flex-wrap: wrap;
282
+ justify-content: flex-end;
283
+ }
284
+ .hook-toggles { display: flex; gap: 14px; }
285
+ .toggle-group { display: flex; flex-direction: column; align-items: center; gap: 5px; }
286
+ .toggle-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-faint); }
287
+ .toggle-group.enable .toggle-label { color: var(--text-dim); }
288
+ .toggle-group.mode .mode-label { color: var(--accent); font-family: var(--mono); text-transform: none; letter-spacing: 0; }
289
+ .managed-note { font-size: 11.5px; color: var(--text-faint); font-style: italic; }
290
+
291
+ /* Switch */
292
+ .switch { position: relative; display: inline-block; width: 40px; height: 22px; }
293
+ .switch input { opacity: 0; width: 0; height: 0; }
294
+ .slider {
295
+ position: absolute;
296
+ inset: 0;
297
+ cursor: pointer;
298
+ background: #2a313d;
299
+ border: 1px solid var(--border-strong);
300
+ border-radius: 999px;
301
+ transition: background 0.18s, border-color 0.18s;
302
+ }
303
+ .slider::before {
304
+ content: "";
305
+ position: absolute;
306
+ height: 16px;
307
+ width: 16px;
308
+ left: 2px;
309
+ top: 2px;
310
+ background: #c2c9d6;
311
+ border-radius: 50%;
312
+ transition: transform 0.18s, background 0.18s;
313
+ }
314
+ .switch input:checked + .slider { background: var(--accent); border-color: var(--accent); }
315
+ .switch input:checked + .slider::before { transform: translateX(18px); background: #06222e; }
316
+ .switch.small { width: 34px; height: 19px; }
317
+ .switch.small .slider::before { height: 13px; width: 13px; }
318
+ .switch.small input:checked + .slider::before { transform: translateX(15px); }
319
+ .switch input:focus-visible + .slider { box-shadow: 0 0 0 3px var(--accent-glow); }
320
+ .switch input:disabled + .slider { opacity: 0.5; cursor: progress; }
321
+
322
+ /* ── Badges ──────────────────────────────────────────────────────────────── */
323
+ .badge {
324
+ font-size: 11px;
325
+ padding: 2px 8px;
326
+ border-radius: 999px;
327
+ border: 1px solid var(--border-strong);
328
+ color: var(--text-dim);
329
+ font-family: var(--mono);
330
+ }
331
+ .badge.on { color: var(--good); border-color: rgba(84, 214, 160, 0.35); background: rgba(84, 214, 160, 0.08); }
332
+ .badge.off { color: var(--text-faint); }
333
+ .badge.bad { color: var(--danger); border-color: rgba(255, 107, 120, 0.4); background: rgba(255, 107, 120, 0.08); }
334
+
335
+ /* ── Exposed tools (MCP rows) ──────────────────────────────────────────────── */
336
+ .exposed { grid-column: 1; margin-top: 10px; border-top: 1px solid var(--border); padding-top: 10px; }
337
+ .exposed-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-faint); margin: 0 0 8px; }
338
+ .exposed-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; font-family: var(--mono); font-size: 12.5px; }
339
+ .exposed-row .arrow { color: var(--text-faint); }
340
+ .exposed-row .as { color: var(--accent); }
341
+ .exposed-row .none { color: var(--text-faint); font-style: italic; font-family: var(--sans); }
342
+
343
+ /* ── Logs ──────────────────────────────────────────────────────────────────── */
344
+ .logs-toolbar { justify-content: space-between; }
345
+ .log-config { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }
346
+ .log-status { font-size: 13px; color: var(--text-dim); }
347
+ .log-status.on { color: var(--good); }
348
+ .log-path { font-size: 12px; color: var(--text-faint); word-break: break-all; }
349
+ .log-count { position: static; }
350
+
351
+ .log-list { max-height: 62vh; overflow-y: auto; padding-right: 4px; }
352
+ .log-row { display: flex; flex-direction: column; gap: 6px; padding: 12px 16px; }
353
+ .log-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
354
+ .log-head .log-name { font-size: 14px; font-weight: 600; }
355
+ .log-ts { margin-left: auto; font-family: var(--mono); font-size: 11.5px; color: var(--text-faint); }
356
+ .log-detail { margin: 0; font-family: var(--mono); font-size: 12px; color: var(--text-dim); word-break: break-word; }
357
+
358
+ .log-badge {
359
+ font-family: var(--mono);
360
+ font-size: 10.5px;
361
+ text-transform: uppercase;
362
+ letter-spacing: 0.06em;
363
+ padding: 2px 8px;
364
+ border-radius: 999px;
365
+ border: 1px solid var(--border-strong);
366
+ }
367
+ .log-badge.gate { color: var(--warn); border-color: rgba(245, 184, 96, 0.35); background: rgba(245, 184, 96, 0.08); }
368
+ .log-badge.tool { color: var(--accent); border-color: rgba(92, 200, 255, 0.3); background: rgba(92, 200, 255, 0.08); }
369
+
370
+ /* ── Empty states ────────────────────────────────────────────────────────── */
371
+ .empty {
372
+ text-align: center;
373
+ padding: 48px 20px;
374
+ color: var(--text-dim);
375
+ background: var(--bg-elev);
376
+ border: 1px dashed var(--border-strong);
377
+ border-radius: var(--radius);
378
+ }
379
+ .empty p { margin: 4px 0; }
380
+ .empty .muted { color: var(--text-faint); font-size: 13px; }
381
+ code { font-family: var(--mono); color: var(--accent); background: rgba(92, 200, 255, 0.08); padding: 1px 5px; border-radius: 4px; }
382
+ .muted { color: var(--text-faint); }
383
+
384
+ /* ── Toast ───────────────────────────────────────────────────────────────── */
385
+ .toast {
386
+ position: fixed;
387
+ left: 50%;
388
+ bottom: 26px;
389
+ transform: translateX(-50%) translateY(20px);
390
+ max-width: 560px;
391
+ background: var(--bg-card);
392
+ border: 1px solid var(--border-strong);
393
+ border-left: 3px solid var(--accent);
394
+ border-radius: var(--radius-sm);
395
+ padding: 12px 16px;
396
+ font-size: 13.5px;
397
+ color: var(--text);
398
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
399
+ opacity: 0;
400
+ pointer-events: none;
401
+ transition: opacity 0.2s, transform 0.2s;
402
+ z-index: 50;
403
+ }
404
+ .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
405
+ .toast.good { border-left-color: var(--good); }
406
+ .toast.error { border-left-color: var(--danger); }
407
+ .toast code { display: inline-block; margin-top: 2px; word-break: break-all; }
408
+
409
+ /* ── Top-level surface panel (Tools tab — the visibility matrix's every-turn axis) ── */
410
+ .surface-panel {
411
+ background: var(--bg-elev);
412
+ border: 1px solid var(--border);
413
+ border-radius: var(--radius);
414
+ padding: 14px 16px;
415
+ margin-bottom: 18px;
416
+ }
417
+ .surface-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
418
+ .surface-title { font-size: 12.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text); }
419
+ .surface-sub { font-size: 12px; color: var(--text-faint); }
420
+ .surface-count { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--text-faint); }
421
+ .surface-count.is-some { color: var(--accent); }
422
+ .surface-count.is-bloat { color: var(--warn); }
423
+ .surface-meta { display: flex; flex-wrap: wrap; gap: 12px 20px; margin-top: 12px; }
424
+ .surface-meta-item { display: flex; align-items: center; gap: 8px; }
425
+ .surface-meta-item code { font-family: var(--mono); font-size: 12px; color: var(--accent); }
426
+ .surface-meta-item.is-off code { color: var(--text-faint); text-decoration: line-through; }
427
+ .surface-warnings { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
428
+ .surface-warning {
429
+ margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--warn);
430
+ background: rgba(245, 184, 96, 0.08); border: 1px solid rgba(245, 184, 96, 0.28);
431
+ border-radius: var(--radius-sm); padding: 8px 11px;
432
+ }
433
+
434
+ /* Hot row accent + the per-row Hot toggle */
435
+ .row.is-hot { border-color: var(--accent-dim); box-shadow: inset 3px 0 0 var(--accent); }
436
+ /* A DISABLED tool is never actually on the every-turn surface — drop the hot accent so a dimmed row
437
+ * doesn't read as "still promoted" (the Hot switch stays on, reflecting the stored intent). */
438
+ .row.is-disabled.is-hot { box-shadow: none; border-color: var(--border); }
439
+ .toggle-group.hot .toggle-label { color: var(--accent); }
440
+ .toggle-group.hidden .toggle-label { color: var(--text-faint); }
441
+
442
+ /* "show hidden" filter in the Tools toolbar */
443
+ .show-hidden { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-dim); white-space: nowrap; cursor: pointer; }
444
+ .show-hidden input { accent-color: var(--accent); cursor: pointer; margin: 0; }
445
+
446
+ /* ── Discovered upstream tools (MCPs tab — the live Discover button) ───────────── */
447
+ .discovered { grid-column: 1 / -1; margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
448
+ .discovered-row {
449
+ display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
450
+ padding: 7px 0; font-family: var(--mono); font-size: 12.5px;
451
+ }
452
+ .discovered-row .tool { color: var(--text); }
453
+ .discovered-row .arrow { color: var(--text-faint); }
454
+ .discovered-row .as { color: var(--accent); }
455
+ .discovered-row .disc-desc { font-family: var(--sans); color: var(--text-dim); font-size: 12px; flex: 1 1 160px; min-width: 0; }
456
+ .discovered-row .toggle-group { margin-left: 6px; }
457
+ .discovered-row .toggle-group.lean .toggle-label { color: var(--text-dim); }
458
+ .discovered .none { color: var(--text-faint); font-style: italic; }
459
+
460
+ /* ── Responsive ──────────────────────────────────────────────────────────── */
461
+ @media (max-width: 720px) {
462
+ .field-grid { grid-template-columns: 1fr; }
463
+ .row { grid-template-columns: 1fr; }
464
+ .row-controls { grid-column: 1; grid-row: auto; justify-content: flex-start; margin-top: 10px; padding-top: 12px; border-top: 1px solid var(--border); }
465
+ .toolbar { flex-wrap: wrap; }
466
+ .expose-input-row { grid-template-columns: 1fr 1fr; }
467
+ .discovered-row { gap: 6px 10px; }
468
+ }
469
+
470
+ /* ── Auth (OAuth 2.1) view ─────────────────────────────────────────────────── */
471
+ .auth-intro {
472
+ background: var(--bg-elev);
473
+ border: 1px solid var(--border);
474
+ border-radius: var(--radius);
475
+ padding: 14px 18px;
476
+ margin-bottom: 16px;
477
+ }
478
+ .auth-intro p { margin: 0; color: var(--text-dim); font-size: 14px; }
479
+ .auth-intro code { font-family: var(--mono); color: var(--accent); font-size: 13px; }
480
+ .auth-intro strong { color: var(--text); }
481
+
482
+ .auth-card {
483
+ background: var(--bg-card);
484
+ border: 1px solid var(--border);
485
+ border-radius: var(--radius);
486
+ padding: 16px 18px;
487
+ margin-bottom: 16px;
488
+ }
489
+ .auth-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
490
+ .auth-dep-state { font-size: 14px; font-weight: 500; }
491
+ .auth-dep-state.is-good { color: var(--good); }
492
+ .auth-dep-state.is-off { color: var(--text-dim); }
493
+ .auth-install-log {
494
+ margin: 14px 0 0;
495
+ padding: 12px 14px;
496
+ background: var(--bg-input);
497
+ border: 1px solid var(--border);
498
+ border-radius: var(--radius-sm);
499
+ color: var(--text-dim);
500
+ font-family: var(--mono);
501
+ font-size: 12px;
502
+ white-space: pre-wrap;
503
+ word-break: break-word;
504
+ max-height: 240px;
505
+ overflow: auto;
506
+ }
507
+
508
+ .auth-enable-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
509
+ .auth-enable-label { font-weight: 500; }
510
+ .auth-ready-badge { font-size: 13px; padding: 2px 10px; border-radius: 999px; }
511
+ .auth-ready-badge.is-good { color: var(--good); background: rgba(84, 214, 160, 0.12); }
512
+ .auth-ready-badge.is-bad { color: var(--warn); background: rgba(245, 184, 96, 0.12); }
513
+ .auth-config-note { font-size: 13px; }
514
+
515
+ /* ── Activity-log badges for the full set of entry types ───────────────────── */
516
+ .log-badge.config { background: rgba(92, 200, 255, 0.16); color: var(--accent); }
517
+ .log-badge.auth { background: rgba(245, 184, 96, 0.16); color: var(--warn); }
518
+ .log-badge.client { background: rgba(84, 214, 160, 0.14); color: var(--good); }
519
+ .log-badge.mcp { background: rgba(154, 163, 178, 0.16); color: var(--text-dim); }
520
+ .log-badge.event { background: rgba(154, 163, 178, 0.16); color: var(--text-dim); }
521
+
522
+ /* ── Tool detail / edit panel ──────────────────────────────────────────────── */
523
+ .tool-detail {
524
+ margin-top: 12px;
525
+ padding: 14px 16px;
526
+ background: var(--bg-input);
527
+ border: 1px solid var(--border);
528
+ border-radius: var(--radius-sm);
529
+ }
530
+ .tool-detail-id { margin-bottom: 10px; }
531
+ .tool-details-btn.is-open { color: var(--accent); border-color: var(--accent-dim); }
532
+
533
+ /* ── Brand logo image + blue "Tool" wordmark ───────────────────────────────── */
534
+ .logo-img { width: 100%; height: 100%; object-fit: contain; border-radius: var(--radius-sm); display: block; }
535
+ .brand-tool { color: var(--accent); }