clauderipple 0.2.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 (71) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/LICENSE +674 -0
  3. package/README.ko.md +328 -0
  4. package/README.md +372 -0
  5. package/bin/clauderipple.js +12 -0
  6. package/dist/app/assets/trayDownTemplate.png +0 -0
  7. package/dist/app/assets/trayDownTemplate@2x.png +0 -0
  8. package/dist/app/assets/trayTemplate.png +0 -0
  9. package/dist/app/assets/trayTemplate@2x.png +0 -0
  10. package/dist/app/assets/trayWarnTemplate.png +0 -0
  11. package/dist/app/assets/trayWarnTemplate@2x.png +0 -0
  12. package/dist/app/assets/trayWin.png +0 -0
  13. package/dist/app/assets/trayWin@2x.png +0 -0
  14. package/dist/app/assets/trayWinDown.png +0 -0
  15. package/dist/app/assets/trayWinDown@2x.png +0 -0
  16. package/dist/app/assets/trayWinWarn.png +0 -0
  17. package/dist/app/assets/trayWinWarn@2x.png +0 -0
  18. package/dist/app/dist/main.js +518 -0
  19. package/dist/cli/src/browser.js +21 -0
  20. package/dist/cli/src/bundle.js +51 -0
  21. package/dist/cli/src/certs.js +33 -0
  22. package/dist/cli/src/claude-auth.js +112 -0
  23. package/dist/cli/src/codex.js +172 -0
  24. package/dist/cli/src/gen-certs.js +7 -0
  25. package/dist/cli/src/hooks/agent-title.js +160 -0
  26. package/dist/cli/src/index.js +489 -0
  27. package/dist/cli/src/launchd.js +183 -0
  28. package/dist/cli/src/picker.js +166 -0
  29. package/dist/cli/src/probe.js +55 -0
  30. package/dist/cli/src/runtime.js +62 -0
  31. package/dist/cli/src/schtasks.js +134 -0
  32. package/dist/cli/src/settings.js +142 -0
  33. package/dist/cli/src/supervisor.js +100 -0
  34. package/dist/cli/src/tray.js +85 -0
  35. package/dist/router/src/admin.js +945 -0
  36. package/dist/router/src/bootstrap.js +80 -0
  37. package/dist/router/src/certs.js +65 -0
  38. package/dist/router/src/compat.js +172 -0
  39. package/dist/router/src/config.js +179 -0
  40. package/dist/router/src/health.js +45 -0
  41. package/dist/router/src/identity.js +51 -0
  42. package/dist/router/src/index.js +144 -0
  43. package/dist/router/src/ingress/models.js +29 -0
  44. package/dist/router/src/ingress/server.js +400 -0
  45. package/dist/router/src/ingress/translate.js +457 -0
  46. package/dist/router/src/log.js +81 -0
  47. package/dist/router/src/picker.js +74 -0
  48. package/dist/router/src/presets.js +267 -0
  49. package/dist/router/src/providers/anthropic-observed.js +88 -0
  50. package/dist/router/src/providers/anthropic-token-file.js +48 -0
  51. package/dist/router/src/providers/anthropic.js +203 -0
  52. package/dist/router/src/providers/chatgpt/auth.js +226 -0
  53. package/dist/router/src/providers/chatgpt/index.js +274 -0
  54. package/dist/router/src/providers/chatgpt/sse.js +28 -0
  55. package/dist/router/src/providers/chatgpt/translate.js +393 -0
  56. package/dist/router/src/providers/claude-oauth.js +252 -0
  57. package/dist/router/src/providers/openai/index.js +193 -0
  58. package/dist/router/src/providers/openai/translate.js +504 -0
  59. package/dist/router/src/proxy.js +724 -0
  60. package/dist/router/src/redact.js +43 -0
  61. package/dist/router/src/requestlog.js +346 -0
  62. package/dist/router/src/routing.js +113 -0
  63. package/dist/router/src/version.js +8 -0
  64. package/dist/router/src/x509.js +203 -0
  65. package/dist/ui/app.js +1228 -0
  66. package/dist/ui/i18n.js +95 -0
  67. package/dist/ui/index.html +104 -0
  68. package/dist/ui/presets-fallback.js +61 -0
  69. package/dist/ui/style.css +347 -0
  70. package/docs/ARCHITECTURE.md +441 -0
  71. package/package.json +66 -0
@@ -0,0 +1,347 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --bg: #f6f6f7;
4
+ --panel: #ffffff;
5
+ --border: #dcdde0;
6
+ --text: #1b1c1f;
7
+ --muted: #4f545c;
8
+ --accent: #6a56d6;
9
+ --accent-text: #ffffff;
10
+ --ok: #1a8a4a;
11
+ --warn: #b5820a;
12
+ --bad: #c73434;
13
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
14
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
15
+ }
16
+
17
+ @media (prefers-color-scheme: dark) {
18
+ :root {
19
+ --bg: #17181a;
20
+ --panel: #1f2023;
21
+ --border: #33353a;
22
+ --text: #e8e9eb;
23
+ --muted: #b3b7bf;
24
+ --accent: #9f8cf2;
25
+ --accent-text: #0c1220;
26
+ --ok: #3ecb7d;
27
+ --warn: #e0b436;
28
+ --bad: #ef5a5a;
29
+ }
30
+ }
31
+
32
+ * { box-sizing: border-box; }
33
+
34
+ body {
35
+ margin: 0;
36
+ background: var(--bg);
37
+ color: var(--text);
38
+ font-family: var(--sans);
39
+ font-size: 16px;
40
+ display: flex;
41
+ min-height: 100vh;
42
+ }
43
+
44
+ nav {
45
+ width: 168px;
46
+ flex: 0 0 auto;
47
+ border-right: 1px solid var(--border);
48
+ padding: 14px 10px;
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: 2px;
52
+ }
53
+
54
+ nav .brand {
55
+ font-weight: 600;
56
+ font-size: 18px;
57
+ padding: 4px 8px 14px;
58
+ letter-spacing: 0.2px;
59
+ }
60
+
61
+ nav button {
62
+ all: unset;
63
+ cursor: pointer;
64
+ padding: 7px 8px;
65
+ border-radius: 6px;
66
+ color: var(--muted);
67
+ font-size: 15px;
68
+ }
69
+
70
+ nav button:hover { background: var(--border); color: var(--text); }
71
+ nav button.active { background: var(--accent); color: var(--accent-text); font-weight: 600; }
72
+
73
+ nav .nav-spacer { flex: 1 1 auto; }
74
+
75
+ .lang-toggle {
76
+ all: unset;
77
+ cursor: pointer;
78
+ padding: 6px 8px;
79
+ border-radius: 6px;
80
+ color: var(--muted);
81
+ font-size: 14px;
82
+ border: 1px solid var(--border);
83
+ text-align: center;
84
+ }
85
+ .lang-toggle:hover { background: var(--border); color: var(--text); }
86
+
87
+ main {
88
+ flex: 1 1 auto;
89
+ padding: 22px 28px;
90
+ max-width: 980px;
91
+ overflow-x: auto;
92
+ }
93
+
94
+ h1 {
95
+ font-size: 22px;
96
+ margin: 0 0 4px;
97
+ }
98
+
99
+ .subtitle {
100
+ color: var(--muted);
101
+ margin: 0 0 18px;
102
+ font-size: 14px;
103
+ }
104
+
105
+ .card {
106
+ background: var(--panel);
107
+ border: 1px solid var(--border);
108
+ border-radius: 8px;
109
+ padding: 14px 16px;
110
+ margin-bottom: 14px;
111
+ }
112
+
113
+ .card h2 {
114
+ font-size: 15px;
115
+ letter-spacing: 0.2px;
116
+ color: var(--muted);
117
+ margin: 0 0 10px;
118
+ font-weight: 600;
119
+ }
120
+
121
+ .rows { display: flex; flex-direction: column; gap: 7px; }
122
+ .row { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
123
+ .row .k { color: var(--muted); }
124
+ .row .v { font-family: var(--mono); font-size: 15px; text-align: right; }
125
+
126
+ .badge {
127
+ display: inline-flex;
128
+ align-items: center;
129
+ gap: 5px;
130
+ padding: 2px 8px;
131
+ border-radius: 100px;
132
+ font-size: 14px;
133
+ font-weight: 600;
134
+ }
135
+ .badge.ok { background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok); }
136
+ .badge.bad { background: color-mix(in srgb, var(--bad) 18%, transparent); color: var(--bad); }
137
+ .badge.warn { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
138
+ .badge.dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
139
+
140
+ table { width: 100%; border-collapse: collapse; }
141
+ th { text-align: left; font-size: 14px; letter-spacing: 0.2px; color: var(--muted); font-weight: 600; padding: 6px 8px; border-bottom: 1px solid var(--border); }
142
+ td { padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: middle; }
143
+ tr:last-child td { border-bottom: none; }
144
+
145
+ input, select, textarea {
146
+ background: var(--bg);
147
+ color: var(--text);
148
+ border: 1px solid var(--border);
149
+ border-radius: 5px;
150
+ padding: 5px 7px;
151
+ font-size: 15px;
152
+ font-family: inherit;
153
+ width: 100%;
154
+ }
155
+ input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
156
+
157
+ button.btn {
158
+ all: unset;
159
+ cursor: pointer;
160
+ background: var(--accent);
161
+ color: var(--accent-text);
162
+ padding: 6px 14px;
163
+ border-radius: 6px;
164
+ font-size: 15px;
165
+ font-weight: 600;
166
+ }
167
+ button.btn:hover { filter: brightness(1.06); }
168
+ button.btn.secondary { background: transparent; color: var(--text); border: 1px solid var(--border); }
169
+ button.btn.danger { background: transparent; color: var(--bad); border: 1px solid var(--bad); }
170
+ button.btn:disabled { opacity: 0.5; cursor: default; }
171
+
172
+ .icon-btn {
173
+ all: unset;
174
+ cursor: pointer;
175
+ color: var(--muted);
176
+ padding: 3px 7px;
177
+ border-radius: 5px;
178
+ font-size: 15px;
179
+ }
180
+ .icon-btn:hover { background: var(--border); color: var(--bad); }
181
+
182
+ .toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; gap: 10px; flex-wrap: wrap; }
183
+ .toolbar .right { display: flex; gap: 8px; align-items: center; }
184
+
185
+ .toast {
186
+ position: fixed;
187
+ bottom: 20px;
188
+ right: 20px;
189
+ background: var(--panel);
190
+ border: 1px solid var(--border);
191
+ border-left: 3px solid var(--ok);
192
+ padding: 10px 16px;
193
+ border-radius: 6px;
194
+ font-size: 15px;
195
+ box-shadow: 0 6px 20px rgba(0,0,0,0.15);
196
+ opacity: 0;
197
+ transform: translateY(6px);
198
+ transition: opacity 0.15s, transform 0.15s;
199
+ pointer-events: none;
200
+ max-width: 360px;
201
+ }
202
+ .toast.show { opacity: 1; transform: translateY(0); }
203
+ .toast.error { border-left-color: var(--bad); }
204
+
205
+ pre#logbox {
206
+ background: var(--panel);
207
+ border: 1px solid var(--border);
208
+ border-radius: 8px;
209
+ padding: 12px;
210
+ font-family: var(--mono);
211
+ font-size: 14px;
212
+ line-height: 1.5;
213
+ height: 60vh;
214
+ overflow: auto;
215
+ white-space: pre-wrap;
216
+ word-break: break-all;
217
+ }
218
+ .tag-PASS { color: var(--muted); }
219
+ .tag-ERR { color: var(--bad); }
220
+ .tag-WARN { color: var(--warn); }
221
+ .tag-PROVIDER { color: var(--accent); }
222
+
223
+ label.check { display: inline-flex; align-items: center; gap: 7px; color: var(--text); font-size: 15px; width: auto; cursor: pointer; }
224
+ label.check input { width: auto; }
225
+ .hint { color: var(--muted); font-size: 14px; line-height: 1.45; margin: 4px 0 0; }
226
+ .small { font-size: 14px; color: var(--muted); }
227
+
228
+ .view { display: none; }
229
+ .view.active { display: block; }
230
+ .actions { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
231
+ .actions.end { justify-content: flex-end; }
232
+ .details { color: var(--muted); font-size: 14px; margin-top: 12px; }
233
+ .advanced-content { margin-top: 10px; display: grid; gap: 12px; }
234
+ .details summary { cursor: pointer; user-select: none; }
235
+ .detail-rows { margin-top: 10px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 7px; background: var(--panel); }
236
+ .picker-setting { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
237
+ .switch { width: 40px; height: 23px; flex: 0 0 auto; position: relative; cursor: pointer; }
238
+ .switch input { position: absolute; opacity: 0; pointer-events: none; }
239
+ .switch span { display: block; height: 100%; border-radius: 99px; background: var(--border); transition: background .15s; }
240
+ .switch span::after { content: ""; position: absolute; width: 17px; height: 17px; top: 3px; left: 3px; border-radius: 50%; background: var(--panel); box-shadow: 0 1px 3px rgba(0,0,0,.22); transition: transform .15s; }
241
+ .switch input:checked + span { background: var(--accent); }
242
+ .switch input:checked + span::after { transform: translateX(17px); }
243
+ .table-card { padding: 0; overflow-x: auto; }
244
+ .table-card table { min-width: 670px; }
245
+ .icon-cell { width: 36px; text-align: right; }
246
+ .muted-cell { color: var(--muted); position: relative; }
247
+ .muted-cell::after { content: attr(data-empty); position: absolute; inset: 0 8px; display: grid; place-items: center; pointer-events: none; color: var(--muted); }
248
+ .muted-cell select:disabled { opacity: 0; }
249
+ .model-checklist { display: grid; grid-template-columns: repeat(auto-fill, minmax(185px, 1fr)); gap: 7px; margin-top: 9px; }
250
+ .model-check { display: flex; gap: 7px; align-items: center; min-width: 0; padding: 8px 9px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); cursor: pointer; }
251
+ .model-check input { width: auto; flex: 0 0 auto; }
252
+ .model-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
253
+ .model-check small { margin-left: auto; color: var(--muted); font-size: 13px; white-space: nowrap; }
254
+ .provider-card h2 { color: var(--text); font-size: 14px; text-transform: none; letter-spacing: 0; margin: 0; }
255
+ .provider-state { display: flex; gap: 8px; align-items: center; margin: 8px 0 2px; }
256
+ .provider-status { display: grid; grid-template-columns: minmax(120px, 1fr) auto; gap: 8px; align-items: center; padding: 7px 0; border-bottom: 1px solid var(--border); }
257
+ .provider-status:last-child { border-bottom: none; }
258
+ .empty-card { color: var(--muted); padding: 28px 16px; border: 1px dashed var(--border); border-radius: 8px; text-align: center; }
259
+ .toast-raw { margin-top: 5px; color: var(--muted); font: 10.5px/1.35 var(--mono); word-break: break-word; }
260
+ .modal-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 18px; background: color-mix(in srgb, #000 38%, transparent); }
261
+ #modal-backdrop[hidden] { display: none !important; }
262
+ .modal { width: min(650px, 100%); max-height: min(780px, calc(100vh - 36px)); overflow: auto; position: relative; padding: 22px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 18px 50px rgba(0,0,0,.3); }
263
+ .modal h1 { margin-bottom: 5px; }
264
+ .modal-close { position: absolute; top: 11px; right: 11px; font-size: 20px; }
265
+ .chooser-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 9px; margin-top: 16px; }
266
+ .chooser-group { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 3px; padding: 12px 13px; border: 1px solid var(--border); border-radius: 8px; background: color-mix(in srgb, var(--accent) 7%, var(--panel)); }
267
+ .chooser-group strong { font-size: 15px; }
268
+ .chooser-group span { color: var(--muted); font-size: 14px; line-height: 1.35; }
269
+ .chooser-tile { appearance: none; cursor: pointer; min-height: 106px; padding: 13px; text-align: left; color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: 8px; }
270
+ .chooser-tile:hover { border-color: var(--accent); }
271
+ .chooser-tile strong, .chooser-tile span { display: block; }
272
+ .chooser-tile strong { margin-bottom: 6px; }
273
+ .chooser-tile span { color: var(--muted); font-size: 14px; line-height: 1.35; }
274
+ .form-field { display: flex; flex-direction: column; gap: 5px; margin-top: 14px; color: var(--text); font-size: 15px; }
275
+ .form-field > small { color: var(--muted); font-size: 14px; line-height: 1.4; }
276
+ .key-control { display: flex; gap: 6px; }
277
+ .eye-button { all: unset; flex: 0 0 auto; cursor: pointer; padding: 0 9px; border: 1px solid var(--border); border-radius: 5px; font-size: 14px; color: var(--muted); }
278
+ .probe-result { min-height: 18px; margin-top: 9px; font-size: 14px; }
279
+ .ok-text { color: var(--ok); }
280
+ .warn-text { color: var(--warn); }.bad-text { color: var(--bad); }
281
+ .advanced-content { padding-top: 3px; }
282
+ .picker-check { margin-top: 15px; }
283
+ .client-models { margin-top: 15px; }
284
+ .client-models h3, .card h3 { color: var(--muted); font-size: 15px; letter-spacing: 0.2px; margin: 0 0 8px; font-weight: 600; }
285
+ .client-divider { border-top: 1px solid var(--border); margin: 18px 0 14px; }
286
+
287
+ @media (max-width: 680px) {
288
+ body { display: block; }
289
+ nav { width: 100%; border-right: 0; border-bottom: 1px solid var(--border); padding: 9px; flex-direction: row; overflow-x: auto; align-items: center; }
290
+ nav .brand { display: none; }
291
+ nav .nav-spacer { display: none; }
292
+ main { padding: 18px 16px; overflow-x: visible; }
293
+ .row { align-items: flex-start; flex-direction: column; gap: 4px; }
294
+ .row .v { text-align: left; }
295
+ .provider-status { grid-template-columns: 1fr; }
296
+ .modal { padding: 19px 16px; }
297
+ }
298
+
299
+ .model-search { width: 100%; margin-top: 8px; }
300
+ .model-picker .small { margin-top: 6px; }
301
+ .model-effort-tag { display: inline-block; margin-left: 5px; padding: 1px 5px; border-radius: 999px; font-size: 12px; line-height: 1.35; vertical-align: middle; white-space: nowrap; }
302
+ .model-effort-tag.has-effort { color: var(--ok); background: color-mix(in srgb, var(--ok) 13%, transparent); }
303
+ .model-effort-tag.no-effort { color: var(--muted); background: color-mix(in srgb, var(--muted) 12%, transparent); }
304
+ .model-check .model-effort-tag { margin-left: auto; }
305
+ #slots-status { margin-left: 12px; }
306
+ #slots-summary { margin: 0 0 10px; }
307
+
308
+ /* Readability: body copy is 16px; hints and secondary text stay one step down but never below 14px. */
309
+ .hint, .small, small, .subtitle { font-size: 14px; }
310
+ .subtitle { font-size: 15px; }
311
+ input, select, textarea, button { font-size: 15px; }
312
+
313
+ .logs-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 0 0 16px; }
314
+ .logs-tab { all: unset; cursor: pointer; padding: 8px 11px; color: var(--muted); font-size: 15px; border-bottom: 2px solid transparent; }
315
+ .logs-tab:hover { color: var(--text); }
316
+ .logs-tab.active { color: var(--accent); font-weight: 600; border-bottom-color: var(--accent); }
317
+ .logs-summary-title { display: flex; align-items: baseline; gap: 9px; margin: 0 0 8px; }
318
+ .logs-summary { display: grid; grid-template-columns: repeat(5, minmax(126px, 1fr)); gap: 8px; margin-bottom: 14px; }
319
+ .logs-chip { min-width: 0; border: 1px solid var(--border); border-radius: 7px; padding: 10px 11px; background: var(--panel); }
320
+ .logs-chip .label { display: block; color: var(--muted); font-size: 14px; margin-bottom: 4px; }
321
+ .logs-chip .value { display: block; font-size: 16px; font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
322
+ .logs-controls { justify-content: flex-start; }
323
+ .logs-filter { display: inline-flex; align-items: center; gap: 7px; color: var(--text); font-size: 15px; }
324
+ .logs-filter select { width: auto; min-width: 120px; }
325
+ .logs-table-card { margin-bottom: 0; }
326
+ .logs-table-card table { min-width: 920px; }
327
+ #requests-table tbody tr.request-row { cursor: pointer; }
328
+ #requests-table tbody tr.request-row:hover td { background: color-mix(in srgb, var(--accent) 7%, transparent); }
329
+ #requests-table td { font-variant-numeric: tabular-nums; }
330
+ .request-models { display: flex; align-items: center; gap: 5px; min-width: 185px; }
331
+ .request-models .model { max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
332
+ .provider-badge { display: inline-flex; align-items: center; padding: 2px 7px; border-radius: 99px; color: var(--provider, var(--accent)); background: color-mix(in srgb, var(--provider, var(--accent)) 16%, transparent); font-size: 14px; font-weight: 600; white-space: nowrap; }
333
+ .provider-anthropic { --provider: #8b5cf6; }.provider-chatgpt { --provider: #16856b; }.provider-default { --provider: var(--accent); }
334
+ .token-cell { white-space: nowrap; }
335
+ .cache-pill { display: inline-block; margin-left: 4px; padding: 1px 5px; border-radius: 99px; color: var(--muted); background: var(--bg); font-size: 14px; }
336
+ .no-usage { color: var(--muted); }
337
+ .status-text { font-weight: 600; white-space: nowrap; }.status-text.ok { color: var(--ok); }.status-text.bad { color: var(--bad); }
338
+ .request-detail td { padding: 0; background: color-mix(in srgb, var(--accent) 4%, var(--panel)); }
339
+ .request-detail-grid { display: grid; grid-template-columns: repeat(4, minmax(120px, 1fr)); gap: 10px; padding: 12px 14px; }
340
+ .request-detail-grid .detail-label { display: block; color: var(--muted); font-size: 14px; margin-bottom: 3px; }
341
+ .request-detail-grid .detail-value { word-break: break-word; font-family: var(--mono); font-size: 14px; }
342
+ .request-note { grid-column: 1 / -1; }
343
+ @media (max-width: 980px) { .logs-summary { grid-template-columns: repeat(3, minmax(130px, 1fr)); } }
344
+ @media (max-width: 680px) { .logs-summary { grid-template-columns: repeat(2, minmax(130px, 1fr)); }.request-detail-grid { grid-template-columns: repeat(2, minmax(120px, 1fr)); } }
345
+
346
+ nav .brand { display: flex; align-items: center; gap: 8px; }
347
+ nav .brand svg { width: 22px; height: 22px; flex: 0 0 auto; }