rubyrlm 0.1.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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/LICENSE +21 -0
  4. data/README.md +300 -0
  5. data/bin/rubyrlm +168 -0
  6. data/lib/rubyrlm/backends/base.rb +9 -0
  7. data/lib/rubyrlm/backends/gemini_rest.rb +317 -0
  8. data/lib/rubyrlm/client.rb +643 -0
  9. data/lib/rubyrlm/completion.rb +71 -0
  10. data/lib/rubyrlm/errors.rb +9 -0
  11. data/lib/rubyrlm/logger/jsonl_logger.rb +27 -0
  12. data/lib/rubyrlm/pricing.rb +88 -0
  13. data/lib/rubyrlm/prompts/system_prompt.rb +108 -0
  14. data/lib/rubyrlm/protocol/action_parser.rb +84 -0
  15. data/lib/rubyrlm/repl/code_validator.rb +113 -0
  16. data/lib/rubyrlm/repl/docker_repl/container_manager.rb +158 -0
  17. data/lib/rubyrlm/repl/docker_repl/host_rpc_server.rb +164 -0
  18. data/lib/rubyrlm/repl/docker_repl/protocol.rb +26 -0
  19. data/lib/rubyrlm/repl/docker_repl.rb +190 -0
  20. data/lib/rubyrlm/repl/execution_result.rb +41 -0
  21. data/lib/rubyrlm/repl/local_repl.rb +476 -0
  22. data/lib/rubyrlm/sub_call_cache.rb +47 -0
  23. data/lib/rubyrlm/version.rb +3 -0
  24. data/lib/rubyrlm/web/app.rb +41 -0
  25. data/lib/rubyrlm/web/public/css/components.css +649 -0
  26. data/lib/rubyrlm/web/public/css/design-system.css +1396 -0
  27. data/lib/rubyrlm/web/public/js/app.js +1016 -0
  28. data/lib/rubyrlm/web/public/js/components/charts.js +68 -0
  29. data/lib/rubyrlm/web/public/js/components/context-inspector.js +94 -0
  30. data/lib/rubyrlm/web/public/js/components/exec-chain.js +105 -0
  31. data/lib/rubyrlm/web/public/js/components/kpi-dashboard.js +187 -0
  32. data/lib/rubyrlm/web/public/js/components/query-panel.js +335 -0
  33. data/lib/rubyrlm/web/public/js/components/recursion-tree.js +83 -0
  34. data/lib/rubyrlm/web/public/js/components/session-list.js +160 -0
  35. data/lib/rubyrlm/web/public/js/components/step-navigator.js +129 -0
  36. data/lib/rubyrlm/web/public/js/components/timeline.js +281 -0
  37. data/lib/rubyrlm/web/public/js/lib/animation.js +46 -0
  38. data/lib/rubyrlm/web/public/js/lib/chart-renderer.js +116 -0
  39. data/lib/rubyrlm/web/public/js/lib/diagram-renderer.js +233 -0
  40. data/lib/rubyrlm/web/public/js/lib/sse-client.js +94 -0
  41. data/lib/rubyrlm/web/public/js/lib/theme-manager.js +39 -0
  42. data/lib/rubyrlm/web/public/js/utils.js +57 -0
  43. data/lib/rubyrlm/web/routes/api.rb +129 -0
  44. data/lib/rubyrlm/web/routes/pages.rb +365 -0
  45. data/lib/rubyrlm/web/routes/sse.rb +95 -0
  46. data/lib/rubyrlm/web/services/event_broadcaster.rb +36 -0
  47. data/lib/rubyrlm/web/services/export_service.rb +903 -0
  48. data/lib/rubyrlm/web/services/query_service.rb +221 -0
  49. data/lib/rubyrlm/web/services/session_loader.rb +356 -0
  50. data/lib/rubyrlm/web/services/streaming_logger.rb +22 -0
  51. data/lib/rubyrlm.rb +18 -0
  52. metadata +208 -0
@@ -0,0 +1,649 @@
1
+ /* Timeline */
2
+ .timeline {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 0.65rem;
6
+ }
7
+
8
+ .timeline-card {
9
+ border: 1px solid var(--color-border);
10
+ border-radius: var(--radius-lg);
11
+ background: var(--color-surface-1);
12
+ overflow: hidden;
13
+ box-shadow: var(--shadow-node);
14
+ transition: border-color var(--transition-fast), background var(--transition-fast), transform var(--transition-fast);
15
+ }
16
+
17
+ .timeline-card:hover {
18
+ border-color: var(--color-accent);
19
+ transform: translateX(2px);
20
+ }
21
+
22
+ .timeline-card--error {
23
+ border-left: 3px solid var(--color-error);
24
+ }
25
+
26
+ .timeline-card--exec {
27
+ border-left: 3px solid var(--color-accent);
28
+ }
29
+
30
+ .timeline-card--user {
31
+ border-left: 3px solid #a855f7;
32
+ }
33
+
34
+ .timeline-card--final {
35
+ border-left: 3px solid var(--color-info);
36
+ }
37
+
38
+ .timeline-card__header {
39
+ padding: 0.55rem 0.8rem;
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 0.5rem;
43
+ cursor: pointer;
44
+ user-select: none;
45
+ border-bottom: 1px solid transparent;
46
+ }
47
+
48
+ .timeline-card__header:hover {
49
+ background: var(--color-surface-2);
50
+ }
51
+
52
+ .timeline-card__step-badge {
53
+ border: 1px solid var(--color-border);
54
+ border-radius: var(--radius-sm);
55
+ background: var(--color-surface-3);
56
+ color: var(--color-text-muted);
57
+ font-family: var(--font-mono);
58
+ font-size: 0.62rem;
59
+ font-weight: 700;
60
+ letter-spacing: 0.06em;
61
+ text-transform: uppercase;
62
+ padding: 0.1rem 0.35rem;
63
+ }
64
+
65
+ .timeline-card__type-badge {
66
+ border: 1px solid var(--color-border);
67
+ border-radius: var(--radius-sm);
68
+ font-family: var(--font-mono);
69
+ font-size: 0.59rem;
70
+ font-weight: 700;
71
+ letter-spacing: 0.08em;
72
+ text-transform: uppercase;
73
+ padding: 0.1rem 0.35rem;
74
+ }
75
+
76
+ .timeline-card__type-badge--exec {
77
+ border-color: var(--color-accent);
78
+ color: var(--color-accent);
79
+ background: var(--color-accent-soft);
80
+ }
81
+
82
+ .timeline-card__type-badge--user {
83
+ border-color: rgba(168, 85, 247, 0.5);
84
+ color: #d8b4fe;
85
+ background: rgba(168, 85, 247, 0.15);
86
+ }
87
+
88
+ .timeline-card__type-badge--final {
89
+ border-color: rgba(56, 189, 248, 0.38);
90
+ color: var(--color-info);
91
+ background: var(--color-info-bg);
92
+ }
93
+
94
+ .timeline-card__preview {
95
+ flex: 1;
96
+ min-width: 0;
97
+ font-family: var(--font-mono);
98
+ font-size: 0.71rem;
99
+ color: var(--color-text-muted);
100
+ white-space: nowrap;
101
+ overflow: hidden;
102
+ text-overflow: ellipsis;
103
+ }
104
+
105
+ .timeline-card__latency {
106
+ font-family: var(--font-mono);
107
+ font-size: 0.65rem;
108
+ color: var(--color-text-dim);
109
+ }
110
+
111
+ .timeline-card__chevron {
112
+ color: var(--color-text-dim);
113
+ font-size: 0.63rem;
114
+ transition: transform var(--transition-fast);
115
+ }
116
+
117
+ .timeline-card--expanded .timeline-card__chevron {
118
+ transform: rotate(90deg);
119
+ }
120
+
121
+ .timeline-card__body {
122
+ max-height: 0;
123
+ overflow: hidden;
124
+ transition: max-height 260ms ease;
125
+ }
126
+
127
+ .timeline-card--expanded .timeline-card__body {
128
+ max-height: 2200px;
129
+ }
130
+
131
+ .timeline-card__content {
132
+ padding: 0 0.8rem 0.8rem;
133
+ }
134
+
135
+ .timeline-card__section-label {
136
+ margin-top: 0.7rem;
137
+ margin-bottom: 0.35rem;
138
+ display: inline-flex;
139
+ align-items: center;
140
+ gap: 0.35rem;
141
+ font-family: var(--font-mono);
142
+ font-size: 0.61rem;
143
+ font-weight: 700;
144
+ letter-spacing: 0.08em;
145
+ text-transform: uppercase;
146
+ color: var(--color-text-muted);
147
+ }
148
+
149
+ .user-prompt-text {
150
+ color: var(--color-text);
151
+ font-size: 0.8rem;
152
+ line-height: 1.5;
153
+ padding: 0.6rem 0.8rem;
154
+ background: var(--color-surface-3);
155
+ border-radius: var(--radius-md);
156
+ margin-top: 0.4rem;
157
+ white-space: pre-wrap;
158
+ word-break: break-word;
159
+ }
160
+
161
+ .code-block {
162
+ position: relative;
163
+ }
164
+
165
+ .code-block__copy {
166
+ position: absolute;
167
+ top: 0.42rem;
168
+ right: 0.42rem;
169
+ border: 1px solid var(--color-border);
170
+ border-radius: var(--radius-sm);
171
+ background: var(--color-surface-2);
172
+ color: var(--color-text-dim);
173
+ font-family: var(--font-mono);
174
+ font-size: 0.57rem;
175
+ letter-spacing: 0.06em;
176
+ text-transform: uppercase;
177
+ padding: 0.15rem 0.35rem;
178
+ opacity: 0;
179
+ cursor: pointer;
180
+ transition: all var(--transition-fast);
181
+ }
182
+
183
+ .code-block:hover .code-block__copy {
184
+ opacity: 1;
185
+ }
186
+
187
+ .code-block__copy:hover {
188
+ color: var(--color-accent);
189
+ border-color: var(--color-accent);
190
+ }
191
+
192
+ .exec-output {
193
+ border: 1px solid var(--color-border);
194
+ border-radius: var(--radius-md);
195
+ background: #05080f;
196
+ color: #e2e8f0;
197
+ padding: 0.7rem;
198
+ position: relative;
199
+ overflow: auto;
200
+ }
201
+
202
+ .exec-output::after {
203
+ content: '';
204
+ position: absolute;
205
+ inset: 0;
206
+ pointer-events: none;
207
+ opacity: 0.22;
208
+ background: linear-gradient(to bottom, transparent 50%, rgba(15, 23, 42, 0.55) 50%);
209
+ background-size: 100% 2px;
210
+ }
211
+
212
+ .exec-output--error {
213
+ border-color: rgba(239, 68, 68, 0.45);
214
+ }
215
+
216
+ .exec-output__class {
217
+ margin-bottom: 0.35rem;
218
+ font-family: var(--font-mono);
219
+ font-size: 0.66rem;
220
+ color: var(--color-error);
221
+ text-transform: uppercase;
222
+ letter-spacing: 0.07em;
223
+ }
224
+
225
+ .exec-output__message {
226
+ font-family: var(--font-mono);
227
+ font-size: 0.73rem;
228
+ white-space: pre-wrap;
229
+ word-break: break-word;
230
+ line-height: 1.5;
231
+ }
232
+
233
+ .exec-output__message--error {
234
+ color: #fca5a5;
235
+ }
236
+
237
+ .exec-output__message--stdout {
238
+ color: #cbd5e1;
239
+ }
240
+
241
+ .exec-output__message--value {
242
+ color: #93c5fd;
243
+ }
244
+
245
+ .exec-output__message--empty {
246
+ color: #64748b;
247
+ font-style: italic;
248
+ }
249
+
250
+ .final-answer {
251
+ border: 1px solid var(--color-border);
252
+ border-radius: var(--radius-md);
253
+ background: var(--color-surface-2);
254
+ padding: 0.7rem;
255
+ }
256
+
257
+ .final-answer__text {
258
+ color: var(--color-text);
259
+ font-size: 0.82rem;
260
+ line-height: 1.62;
261
+ word-break: break-word;
262
+ }
263
+
264
+ .final-answer__text>*:first-child {
265
+ margin-top: 0;
266
+ }
267
+
268
+ .final-answer__text>*:last-child {
269
+ margin-bottom: 0;
270
+ }
271
+
272
+ .final-answer__text h1,
273
+ .final-answer__text h2,
274
+ .final-answer__text h3,
275
+ .final-answer__text h4 {
276
+ margin: 0.85rem 0 0.4rem;
277
+ line-height: 1.35;
278
+ font-weight: 700;
279
+ }
280
+
281
+ .final-answer__text h1 {
282
+ font-size: 1.06rem;
283
+ }
284
+
285
+ .final-answer__text h2 {
286
+ font-size: 0.98rem;
287
+ }
288
+
289
+ .final-answer__text h3 {
290
+ font-size: 0.9rem;
291
+ }
292
+
293
+ .final-answer__text h4 {
294
+ font-size: 0.84rem;
295
+ }
296
+
297
+ .final-answer__text p {
298
+ margin: 0.4rem 0;
299
+ }
300
+
301
+ .final-answer__text ul,
302
+ .final-answer__text ol {
303
+ margin: 0.45rem 0;
304
+ padding-left: 1.3rem;
305
+ }
306
+
307
+ .final-answer__text code {
308
+ border: 1px solid var(--color-border);
309
+ border-radius: var(--radius-sm);
310
+ background: var(--color-surface-3);
311
+ font-family: var(--font-mono);
312
+ font-size: 0.73rem;
313
+ padding: 0.1rem 0.3rem;
314
+ }
315
+
316
+ .final-answer__text pre {
317
+ border: 1px solid var(--color-border);
318
+ border-radius: var(--radius-md);
319
+ background: var(--color-surface-3);
320
+ padding: 0.6rem;
321
+ overflow: auto;
322
+ margin: 0.45rem 0;
323
+ }
324
+
325
+ .final-answer__text pre code {
326
+ border: 0;
327
+ background: transparent;
328
+ padding: 0;
329
+ }
330
+
331
+ .final-answer__text table {
332
+ width: 100%;
333
+ border-collapse: collapse;
334
+ margin: 0.45rem 0;
335
+ }
336
+
337
+ .final-answer__text th,
338
+ .final-answer__text td {
339
+ border: 1px solid var(--color-border);
340
+ padding: 0.3rem 0.45rem;
341
+ text-align: left;
342
+ }
343
+
344
+ .final-answer__text th {
345
+ background: var(--color-surface-3);
346
+ font-family: var(--font-mono);
347
+ font-size: 0.64rem;
348
+ letter-spacing: 0.07em;
349
+ text-transform: uppercase;
350
+ color: var(--color-text-dim);
351
+ }
352
+
353
+ .final-answer__text blockquote {
354
+ margin: 0.45rem 0;
355
+ border-left: 3px solid var(--color-accent);
356
+ padding: 0.25rem 0.55rem;
357
+ color: var(--color-text-muted);
358
+ }
359
+
360
+ .final-answer__mermaid {
361
+ margin: 0.6rem 0;
362
+ }
363
+
364
+ /* Context tree */
365
+ .context-tree {
366
+ font-family: var(--font-mono);
367
+ font-size: 0.72rem;
368
+ }
369
+
370
+ .context-tree__item {
371
+ padding: 0.18rem 0;
372
+ padding-left: calc(var(--depth, 0) * 0.95rem);
373
+ }
374
+
375
+ .context-tree__key {
376
+ color: var(--color-accent);
377
+ font-weight: 600;
378
+ }
379
+
380
+ .context-tree__type {
381
+ color: var(--color-text-dim);
382
+ margin-left: 0.22rem;
383
+ }
384
+
385
+ .context-tree__value {
386
+ color: var(--color-text-muted);
387
+ }
388
+
389
+ .context-tree__toggle {
390
+ color: var(--color-text-dim);
391
+ margin-right: 0.2rem;
392
+ cursor: pointer;
393
+ }
394
+
395
+ /* Comparison */
396
+ .comparison-table {
397
+ width: 100%;
398
+ border-collapse: collapse;
399
+ margin-bottom: 0.8rem;
400
+ }
401
+
402
+ .comparison-table th,
403
+ .comparison-table td {
404
+ border-bottom: 1px solid var(--color-border-subtle);
405
+ padding: 0.4rem 0.6rem;
406
+ }
407
+
408
+ .comparison-table th {
409
+ font-family: var(--font-mono);
410
+ font-size: 0.63rem;
411
+ letter-spacing: 0.08em;
412
+ text-transform: uppercase;
413
+ color: var(--color-text-dim);
414
+ background: var(--color-surface-2);
415
+ }
416
+
417
+ .comparison-table td {
418
+ font-family: var(--font-mono);
419
+ font-size: 0.72rem;
420
+ }
421
+
422
+ .comparison-table td:first-child {
423
+ color: var(--color-text-muted);
424
+ font-family: var(--font-sans);
425
+ }
426
+
427
+ .comparison-split {
428
+ display: grid;
429
+ grid-template-columns: 1fr 1fr;
430
+ gap: 0.8rem;
431
+ }
432
+
433
+ .comparison-pane {
434
+ padding: 0;
435
+ display: flex;
436
+ flex-direction: column;
437
+ min-height: 520px;
438
+ }
439
+
440
+ .comparison-pane__header {
441
+ border-bottom: 1px solid var(--color-border);
442
+ background: var(--color-surface-2);
443
+ padding: 0.55rem 0.7rem;
444
+ }
445
+
446
+ .comparison-pane__title {
447
+ font-family: var(--font-mono);
448
+ font-size: 0.65rem;
449
+ color: var(--color-text-muted);
450
+ letter-spacing: 0.09em;
451
+ text-transform: uppercase;
452
+ }
453
+
454
+ .comparison-pane__meta {
455
+ margin-top: 0.2rem;
456
+ font-family: var(--font-mono);
457
+ font-size: 0.7rem;
458
+ color: var(--color-text);
459
+ }
460
+
461
+ .comparison-pane__timeline {
462
+ flex: 1;
463
+ overflow-y: auto;
464
+ padding: 0.65rem;
465
+ display: flex;
466
+ flex-direction: column;
467
+ gap: 0.5rem;
468
+ }
469
+
470
+ .comparison-pane__empty {
471
+ text-align: center;
472
+ color: var(--color-text-dim);
473
+ font-size: 0.78rem;
474
+ padding: 1.3rem;
475
+ }
476
+
477
+ .comparison-step {
478
+ border: 1px solid var(--color-border);
479
+ border-radius: var(--radius-md);
480
+ background: var(--color-surface-2);
481
+ overflow: hidden;
482
+ }
483
+
484
+ .comparison-step--error {
485
+ border-left: 3px solid var(--color-error);
486
+ }
487
+
488
+ .comparison-step--final {
489
+ border-left: 3px solid var(--color-info);
490
+ }
491
+
492
+ .comparison-step__header {
493
+ border-bottom: 1px solid var(--color-border-subtle);
494
+ background: var(--color-surface-3);
495
+ padding: 0.35rem 0.55rem;
496
+ font-family: var(--font-mono);
497
+ font-size: 0.64rem;
498
+ color: var(--color-text-muted);
499
+ }
500
+
501
+ .comparison-step__body {
502
+ padding: 0.52rem;
503
+ }
504
+
505
+ .comparison-step__label {
506
+ margin-bottom: 0.25rem;
507
+ font-family: var(--font-mono);
508
+ font-size: 0.59rem;
509
+ color: var(--color-text-dim);
510
+ letter-spacing: 0.08em;
511
+ text-transform: uppercase;
512
+ }
513
+
514
+ .comparison-step__content {
515
+ margin: 0 0 0.4rem;
516
+ border: 1px solid var(--color-border);
517
+ border-radius: var(--radius-sm);
518
+ background: var(--color-surface-3);
519
+ font-family: var(--font-mono);
520
+ font-size: 0.7rem;
521
+ line-height: 1.5;
522
+ color: var(--color-text);
523
+ white-space: pre-wrap;
524
+ word-break: break-word;
525
+ max-height: 180px;
526
+ overflow-y: auto;
527
+ padding: 0.4rem 0.45rem;
528
+ }
529
+
530
+ .comparison-step__content:last-child {
531
+ margin-bottom: 0;
532
+ }
533
+
534
+ /* Streaming */
535
+ .streaming-card {
536
+ border-left: 3px solid var(--color-accent);
537
+ }
538
+
539
+ .streaming-card__badge {
540
+ animation: pulseBadge 1.4s ease-in-out infinite;
541
+ }
542
+
543
+ .streaming-card__dots {
544
+ display: inline-flex;
545
+ gap: 3px;
546
+ margin-left: 0.45rem;
547
+ }
548
+
549
+ .streaming-card__dots span {
550
+ width: 4px;
551
+ height: 4px;
552
+ border-radius: 999px;
553
+ background: var(--color-accent);
554
+ animation: dotPulse 1.35s ease-in-out infinite;
555
+ }
556
+
557
+ .streaming-card__dots span:nth-child(2) {
558
+ animation-delay: 0.14s;
559
+ }
560
+
561
+ .streaming-card__dots span:nth-child(3) {
562
+ animation-delay: 0.28s;
563
+ }
564
+
565
+ .streaming-card__text {
566
+ margin: 0;
567
+ max-height: 300px;
568
+ overflow-y: auto;
569
+ font-family: var(--font-mono);
570
+ font-size: 0.72rem;
571
+ color: var(--color-text-muted);
572
+ white-space: pre-wrap;
573
+ word-break: break-word;
574
+ }
575
+
576
+ .retry-notice {
577
+ border: 1px solid var(--color-warning);
578
+ border-radius: var(--radius-md);
579
+ background: var(--color-warning-bg);
580
+ color: var(--color-warning);
581
+ padding: 0.58rem 0.72rem;
582
+ font-family: var(--font-mono);
583
+ font-size: 0.68rem;
584
+ letter-spacing: 0.03em;
585
+ }
586
+
587
+ @keyframes pulseBadge {
588
+
589
+ 0%,
590
+ 100% {
591
+ opacity: 1;
592
+ }
593
+
594
+ 50% {
595
+ opacity: 0.52;
596
+ }
597
+ }
598
+
599
+ @keyframes dotPulse {
600
+
601
+ 0%,
602
+ 80%,
603
+ 100% {
604
+ opacity: 0.35;
605
+ transform: scale(0.85);
606
+ }
607
+
608
+ 40% {
609
+ opacity: 1;
610
+ transform: scale(1.15);
611
+ }
612
+ }
613
+
614
+ /* Continue prompt */
615
+ .continue-prompt {
616
+ border: 1px dashed var(--color-border);
617
+ background: var(--color-surface-2);
618
+ margin-top: 0.35rem;
619
+ }
620
+
621
+ .continue-prompt .query-textarea {
622
+ min-height: 3rem;
623
+ margin-bottom: 0.45rem;
624
+ }
625
+
626
+ .continue-prompt__actions {
627
+ display: flex;
628
+ gap: 0.42rem;
629
+ flex-wrap: wrap;
630
+ }
631
+
632
+ .continue-divider {
633
+ margin-top: 0.2rem;
634
+ border: 1px dashed rgba(25, 127, 230, 0.45);
635
+ border-radius: var(--radius-md);
636
+ background: var(--color-accent-soft);
637
+ color: var(--color-accent);
638
+ text-align: center;
639
+ padding: 0.62rem 0.7rem;
640
+ font-family: var(--font-mono);
641
+ font-size: 0.67rem;
642
+ letter-spacing: 0.06em;
643
+ }
644
+
645
+ @media (max-width: 1100px) {
646
+ .comparison-split {
647
+ grid-template-columns: 1fr;
648
+ }
649
+ }