@jefuriiij/synthra 0.1.0 → 0.1.2

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.
package/dist/cli/index.js CHANGED
@@ -18,7 +18,7 @@ var init_package = __esm({
18
18
  "package.json"() {
19
19
  package_default = {
20
20
  name: "@jefuriiij/synthra",
21
- version: "0.1.0",
21
+ version: "0.1.2",
22
22
  publishConfig: {
23
23
  access: "public"
24
24
  },
@@ -83,6 +83,7 @@ var init_package = __esm({
83
83
  });
84
84
 
85
85
  // src/cli/index.ts
86
+ init_package();
86
87
  import sade from "sade";
87
88
  import { resolve as resolve4 } from "path";
88
89
 
@@ -455,28 +456,39 @@ var public_default = `<!doctype html>
455
456
 
456
457
  <main>
457
458
  <section>
458
- <h2>Global totals <span class="muted">(all projects)</span></h2>
459
+ <h2>
460
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M7 14l4-4 4 4 5-5"/></svg>
461
+ Global totals
462
+ <span class="muted">(all projects)</span>
463
+ </h2>
459
464
  <div class="cards" id="cards"></div>
460
465
  </section>
461
466
 
462
467
  <section>
463
- <h2>Projects</h2>
468
+ <h2>
469
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
470
+ Projects
471
+ </h2>
464
472
  <div class="projects" id="projects"></div>
465
473
  <p class="empty hidden" id="projects-empty">No projects registered yet. Run <code>syn .</code> in any project to add it.</p>
466
474
  </section>
467
475
 
468
476
  <section>
469
- <h2>Recent calls <span class="muted">(across all projects)</span></h2>
477
+ <h2>
478
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
479
+ Recent calls
480
+ <span class="muted">(across all projects)</span>
481
+ </h2>
470
482
  <table id="turns">
471
483
  <thead>
472
484
  <tr>
473
485
  <th>Time</th>
474
486
  <th>Project</th>
475
487
  <th>Model</th>
476
- <th class="num">Input</th>
477
- <th class="num">Output</th>
478
- <th class="num">Cache R / W</th>
479
- <th class="num">Cost</th>
488
+ <th class="num"><span class="has-tooltip" data-tooltip="New (uncached) tokens you sent to Claude this turn. Usually small \u2014 most of the conversation comes from cache.">Input <span class="help-icon">i</span></span></th>
489
+ <th class="num"><span class="has-tooltip" data-tooltip="Tokens Claude generated in its response. The most expensive line item \u2014 ~5\xD7 the input rate on Opus.">Output <span class="help-icon">i</span></span></th>
490
+ <th class="num"><span class="has-tooltip" data-tooltip="Cache read / cache write. Reads (~10% of input rate) reuse prior context; writes (~125% of input rate) save new context for future turns.">Cache R / W <span class="help-icon">i</span></span></th>
491
+ <th class="num"><span class="has-tooltip" data-tooltip="Approximate USD cost for this turn \u2014 input \xD7 rate + output \xD7 5\xD7rate + cache_read \xD7 0.1\xD7rate + cache_write \xD7 1.25\xD7rate, using the turn's model.">Cost <span class="help-icon">i</span></span></th>
480
492
  </tr>
481
493
  </thead>
482
494
  <tbody></tbody>
@@ -485,14 +497,17 @@ var public_default = `<!doctype html>
485
497
  </section>
486
498
 
487
499
  <section>
488
- <h2>Recent gate decisions</h2>
500
+ <h2>
501
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
502
+ Recent gate decisions
503
+ </h2>
489
504
  <table id="gates">
490
505
  <thead>
491
506
  <tr>
492
507
  <th>Time</th>
493
508
  <th>Project</th>
494
509
  <th>Tool</th>
495
- <th>Decision</th>
510
+ <th><span class="has-tooltip" data-tooltip="ALLOW = Synthra let the tool call through. BLOCK = Synthra intercepted it because the graph already had high-confidence context \u2014 Claude should use graph_continue instead.">Decision <span class="help-icon">i</span></span></th>
496
511
  <th>Query</th>
497
512
  </tr>
498
513
  </thead>
@@ -503,11 +518,42 @@ var public_default = `<!doctype html>
503
518
  </main>
504
519
 
505
520
  <footer>
506
- <span>Token Counter MCP \xB7 live polling every 2s</span>
507
- <span class="muted">Cost figures are approximate \u2014 see /docs/PROTOCOL.md</span>
521
+ <span>Synthra Token Dashboard \xB7 live polling every 2s</span>
522
+ <span class="muted">Cost figures are approximate \u2014 based on published Anthropic rates.</span>
508
523
  </footer>
509
524
 
510
525
  <script>
526
+ // Inline SVG icons. Each is a stroke-based icon, currentColor for the
527
+ // stroke, designed to inherit color from the parent's CSS.
528
+ const ICONS = {
529
+ dollar: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>',
530
+ chat: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
531
+ arrowDown: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>',
532
+ arrowUp: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>',
533
+ refresh: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>',
534
+ save: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>',
535
+ folder: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>',
536
+ shield: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>',
537
+ trending: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>',
538
+ ban: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>',
539
+ };
540
+
541
+ // Classify a model name into a family for color-coding.
542
+ function modelFamily(model) {
543
+ if (!model) return 'unknown';
544
+ const m = model.toLowerCase();
545
+ if (m === '<synthetic>') return 'unknown';
546
+ if (m.includes('opus')) return 'opus';
547
+ if (m.includes('sonnet')) return 'sonnet';
548
+ if (m.includes('haiku')) return 'haiku';
549
+ return 'unknown';
550
+ }
551
+
552
+ function modelLabel(model) {
553
+ if (!model || model === '<synthetic>') return model === '<synthetic>' ? 'synthetic' : 'unknown';
554
+ return model;
555
+ }
556
+
511
557
  const $ = (sel) => document.querySelector(sel);
512
558
  const cardsEl = $("#cards");
513
559
  const projectsEl = $("#projects");
@@ -550,23 +596,84 @@ var public_default = `<!doctype html>
550
596
  }
551
597
  }
552
598
 
599
+ // Definitions for the global-totals cards. Each: label, value-source key,
600
+ // icon, optional class (accent | money), tooltip text.
601
+ function cardConfigs(g) {
602
+ return [
603
+ {
604
+ label: "Total cost",
605
+ value: fmtCost(g.estimated_cost_usd),
606
+ icon: ICONS.dollar,
607
+ cls: "money",
608
+ tooltip: "Approximate USD cost across all projects. Computed from per-model pricing (Opus, Sonnet, Haiku) applied to each turn's input/output/cache. Tilde everywhere \u2014 real cost depends on Anthropic's current rates.",
609
+ },
610
+ {
611
+ label: "Turns",
612
+ value: fmt(g.total_turns),
613
+ icon: ICONS.chat,
614
+ tooltip: "Total number of back-and-forth exchanges with Claude across all projects. One turn = you send a message, Claude responds.",
615
+ },
616
+ {
617
+ label: "Input",
618
+ value: fmt(g.total_input_tokens),
619
+ icon: ICONS.arrowDown,
620
+ tooltip: "New (uncached) tokens sent to Claude across all turns. Usually small \u2014 most of the conversation comes from cache.",
621
+ },
622
+ {
623
+ label: "Output",
624
+ value: fmt(g.total_output_tokens),
625
+ icon: ICONS.arrowUp,
626
+ tooltip: "Tokens Claude generated in responses. The most expensive line item per turn (~5\xD7 input rate on Opus).",
627
+ },
628
+ {
629
+ label: "Cache read",
630
+ value: fmt(g.total_cache_read),
631
+ icon: ICONS.refresh,
632
+ tooltip: "Tokens read from Claude's prompt cache \u2014 conversation history, system prompt, Synthra's pack. Cheap: ~10% of the input rate. The bulk of every long session.",
633
+ },
634
+ {
635
+ label: "Cache write",
636
+ value: fmt(g.total_cache_create),
637
+ icon: ICONS.save,
638
+ tooltip: "Tokens newly added to the prompt cache so future turns can read them cheaply. Premium-priced (~125% of input) but pays back over the rest of the session.",
639
+ },
640
+ {
641
+ label: "Projects",
642
+ value: fmt(g.project_count),
643
+ icon: ICONS.folder,
644
+ tooltip: "Projects that have ever run \`syn .\` on this machine. Tracked in ~/.synthra/projects.json.",
645
+ },
646
+ {
647
+ label: "Blocked Grep / Glob",
648
+ value: fmt(g.blocked_count),
649
+ icon: ICONS.shield,
650
+ cls: "accent",
651
+ tooltip: "PreToolUse hook intercepts: Synthra blocked these Grep/Glob calls because the graph already had high-confidence context for the query. Claude pivots to graph_continue or graph_read instead.",
652
+ },
653
+ {
654
+ label: "Tokens saved",
655
+ value: fmt(g.estimated_tokens_saved),
656
+ icon: ICONS.trending,
657
+ cls: "accent",
658
+ tooltip: "Estimated tokens avoided by blocking exploratory Grep/Glob calls. Calculated as blocks \xD7 500 \u2014 conservative; under-counts the cache thrash you also avoid.",
659
+ },
660
+ ];
661
+ }
662
+
553
663
  function renderCards(g) {
554
664
  cardsEl.innerHTML = "";
555
- const cards = [
556
- { label: "Total cost", value: fmtCost(g.estimated_cost_usd), accent: true },
557
- { label: "Turns", value: fmt(g.total_turns) },
558
- { label: "Input", value: fmt(g.total_input_tokens) },
559
- { label: "Output", value: fmt(g.total_output_tokens) },
560
- { label: "Cache read", value: fmt(g.total_cache_read) },
561
- { label: "Cache write", value: fmt(g.total_cache_create) },
562
- { label: "Projects", value: fmt(g.project_count) },
563
- { label: "Blocked Grep / Glob", value: fmt(g.blocked_count), accent: true },
564
- { label: "Tokens saved", value: fmt(g.estimated_tokens_saved), accent: true },
565
- ];
566
- for (const c of cards) {
665
+ for (const c of cardConfigs(g)) {
567
666
  const el = document.createElement("div");
568
- el.className = "card" + (c.accent ? " accent" : "");
569
- el.innerHTML = '<div class="card-label">' + c.label + '</div><div class="card-value">' + c.value + '</div>';
667
+ el.className = "card" + (c.cls ? " " + c.cls : "");
668
+ el.innerHTML =
669
+ '<div class="card-head">' +
670
+ '<div class="card-label has-tooltip" data-tooltip="' + c.tooltip.replace(/"/g, '&quot;') + '">' +
671
+ c.label +
672
+ ' <span class="help-icon">i</span>' +
673
+ '</div>' +
674
+ '<span class="card-icon">' + c.icon + '</span>' +
675
+ '</div>' +
676
+ '<div class="card-value">' + c.value + '</div>';
570
677
  cardsEl.appendChild(el);
571
678
  }
572
679
  }
@@ -587,7 +694,7 @@ var public_default = `<!doctype html>
587
694
  row.className = "project-row";
588
695
  row.innerHTML =
589
696
  '<div class="project-name">' +
590
- '<strong>' + p.name + '</strong>' +
697
+ '<strong>' + ICONS.folder + p.name + '</strong>' +
591
698
  '<code class="project-path">' + p.path + '</code>' +
592
699
  '</div>' +
593
700
  '<div class="project-stats">' +
@@ -609,15 +716,12 @@ var public_default = `<!doctype html>
609
716
  }
610
717
  turnsEmpty.classList.add("hidden");
611
718
  for (const t of turns) {
719
+ const family = modelFamily(t.model);
612
720
  const tr = document.createElement("tr");
613
- const modelCell =
614
- t.model && t.model !== "<synthetic>"
615
- ? "<code>" + t.model + "</code>"
616
- : '<span class="muted">' + (t.model === "<synthetic>" ? "synthetic" : "unknown") + "</span>";
617
721
  tr.innerHTML =
618
722
  "<td>" + fmtTs(t.ts) + "</td>" +
619
723
  "<td><code>" + t.project_name + "</code></td>" +
620
- "<td>" + modelCell + "</td>" +
724
+ '<td><span class="model-pill ' + family + '">' + modelLabel(t.model) + "</span></td>" +
621
725
  '<td class="num">' + fmtFull(t.input) + "</td>" +
622
726
  '<td class="num">' + fmtFull(t.output) + "</td>" +
623
727
  '<td class="num">' + fmt(t.cache_read) + " / " + fmt(t.cache_create) + "</td>" +
@@ -635,12 +739,16 @@ var public_default = `<!doctype html>
635
739
  gatesEmpty.classList.add("hidden");
636
740
  for (const g of gates) {
637
741
  const tr = document.createElement("tr");
638
- const cls = g.decision === "block" ? "decision-block" : "decision-allow";
742
+ const isBlock = g.decision === "block";
743
+ const cls = isBlock ? "decision-block" : "decision-allow";
744
+ const label = isBlock
745
+ ? '<span class="' + cls + '">' + ICONS.ban + ' BLOCK</span>'
746
+ : '<span class="' + cls + '">ALLOW</span>';
639
747
  tr.innerHTML =
640
748
  "<td>" + fmtTs(g.ts) + "</td>" +
641
749
  "<td><code>" + g.project_name + "</code></td>" +
642
750
  "<td><code>" + g.tool + "</code></td>" +
643
- '<td class="' + cls + '">' + g.decision + "</td>" +
751
+ "<td>" + label + "</td>" +
644
752
  "<td><code>" + (g.query || "") + "</code></td>";
645
753
  gatesBody.appendChild(tr);
646
754
  }
@@ -674,7 +782,628 @@ var public_default = `<!doctype html>
674
782
  `;
675
783
 
676
784
  // src/dashboard/public/style.css
677
- var style_default = '/* Synthra token dashboard \u2014 palette per project brief. */\n\n:root {\n --color-heading: #ECEBD8;\n --color-body: #EDECD9;\n --color-bg: #000000;\n --color-surface: #140009;\n --color-surface-raised: #1E000D;\n --color-border: #4D0020;\n --color-accent: #FF0073;\n --color-accent-darker: #EB006A;\n --color-form-bg: #2E0014;\n --color-muted: rgba(237, 236, 217, 0.55);\n --color-very-muted: rgba(237, 236, 217, 0.35);\n --color-block: #FF0073;\n --color-allow: #ECEBD8;\n}\n\n* {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nhtml, body {\n background: var(--color-bg);\n color: var(--color-body);\n font-family:\n ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",\n system-ui, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n min-height: 100vh;\n}\n\ncode, .num, table, .project-path {\n font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;\n}\n\nheader {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n padding: 1.1rem 2rem;\n background: var(--color-surface);\n border-bottom: 1px solid var(--color-border);\n}\n\nheader .brand {\n display: flex;\n align-items: baseline;\n gap: 0.75rem;\n}\n\nheader h1 {\n color: var(--color-heading);\n font-size: 1.35rem;\n font-weight: 700;\n letter-spacing: 0.02em;\n}\n\nheader .tag {\n color: var(--color-muted);\n font-size: 0.78rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n}\n\nheader .meta {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 0.78rem;\n font-family: ui-monospace, monospace;\n color: var(--color-muted);\n}\n\nheader .active-project {\n max-width: 480px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmain {\n padding: 2rem;\n display: flex;\n flex-direction: column;\n gap: 2rem;\n max-width: 1400px;\n margin: 0 auto;\n}\n\nsection {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n}\n\nh2 {\n color: var(--color-heading);\n font-size: 0.85rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\nh2 .muted {\n color: var(--color-very-muted);\n font-size: 0.78rem;\n font-weight: 400;\n letter-spacing: 0.06em;\n text-transform: none;\n margin-left: 0.5rem;\n}\n\n/* Cards row */\n.cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n gap: 0.7rem;\n}\n\n.card {\n background: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 6px;\n padding: 0.85rem 1rem;\n}\n\n.card.accent {\n background: var(--color-surface-raised);\n border-color: var(--color-accent);\n}\n\n.card-label {\n color: var(--color-muted);\n font-size: 0.66rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n margin-bottom: 0.35rem;\n}\n\n.card-value {\n color: var(--color-heading);\n font-family: ui-monospace, monospace;\n font-size: 1.5rem;\n font-weight: 600;\n}\n\n.card.accent .card-value {\n color: var(--color-accent);\n}\n\n/* Projects list */\n.projects {\n display: flex;\n flex-direction: column;\n gap: 0.6rem;\n}\n\n.project-row {\n display: grid;\n grid-template-columns: minmax(220px, 1fr) auto;\n grid-template-rows: auto auto;\n gap: 0.6rem 1.25rem;\n align-items: center;\n background: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 6px;\n padding: 0.9rem 1.2rem;\n}\n\n.project-row:hover {\n background: var(--color-surface-raised);\n}\n\n.project-name {\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n overflow: hidden;\n}\n\n.project-name strong {\n color: var(--color-heading);\n font-size: 0.95rem;\n font-weight: 600;\n}\n\n.project-name .project-path {\n color: var(--color-very-muted);\n font-size: 0.72rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.project-stats {\n display: flex;\n gap: 1.6rem;\n justify-self: end;\n text-align: right;\n}\n\n.stat {\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n min-width: 70px;\n}\n\n.stat-value {\n color: var(--color-heading);\n font-family: ui-monospace, monospace;\n font-size: 0.95rem;\n font-weight: 600;\n}\n\n.stat-value.cost {\n color: var(--color-accent);\n}\n\n.stat-label {\n color: var(--color-muted);\n font-size: 0.66rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n}\n\n.bar {\n grid-column: 1 / -1;\n height: 3px;\n background: var(--color-form-bg);\n border-radius: 2px;\n overflow: hidden;\n}\n\n.bar-fill {\n height: 100%;\n background: linear-gradient(90deg, var(--color-accent-darker), var(--color-accent));\n border-radius: 2px;\n transition: width 400ms ease;\n}\n\n/* Tables */\ntable {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.83rem;\n background: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 6px;\n overflow: hidden;\n}\n\ntable thead th {\n text-align: left;\n color: var(--color-muted);\n text-transform: uppercase;\n font-size: 0.66rem;\n letter-spacing: 0.08em;\n font-weight: 600;\n padding: 0.65rem 0.85rem;\n border-bottom: 1px solid var(--color-border);\n background: var(--color-surface);\n}\n\ntable thead th.num {\n text-align: right;\n}\n\ntable tbody td {\n padding: 0.55rem 0.85rem;\n border-bottom: 1px solid rgba(77, 0, 32, 0.4);\n color: var(--color-body);\n}\n\ntable tbody td.num {\n text-align: right;\n}\n\ntable tbody td.cost {\n color: var(--color-accent);\n font-weight: 600;\n}\n\ntable tbody tr:last-child td {\n border-bottom: none;\n}\n\ntable tbody tr:hover {\n background: var(--color-surface-raised);\n}\n\ncode {\n color: var(--color-heading);\n background: var(--color-form-bg);\n padding: 0.1rem 0.4rem;\n border-radius: 3px;\n font-size: 0.85em;\n}\n\n.decision-block {\n color: var(--color-block);\n font-weight: 700;\n text-transform: uppercase;\n font-size: 0.72rem;\n letter-spacing: 0.06em;\n}\n\n.decision-allow {\n color: var(--color-allow);\n opacity: 0.7;\n text-transform: uppercase;\n font-size: 0.72rem;\n letter-spacing: 0.06em;\n}\n\n.empty {\n color: var(--color-muted);\n font-style: italic;\n padding: 1rem;\n background: var(--color-surface);\n border: 1px dashed var(--color-border);\n border-radius: 6px;\n}\n\n.hidden {\n display: none;\n}\n\nfooter {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0.85rem 2rem;\n background: var(--color-surface);\n border-top: 1px solid var(--color-border);\n color: var(--color-muted);\n font-size: 0.72rem;\n font-family: ui-monospace, monospace;\n}\n\nfooter .muted {\n color: var(--color-very-muted);\n}\n\n.dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--color-muted);\n transition: background 200ms ease, box-shadow 200ms ease;\n}\n\n.dot.live {\n background: var(--color-accent);\n box-shadow: 0 0 8px var(--color-accent-darker);\n}\n\n.dot.dead {\n background: var(--color-border);\n box-shadow: none;\n}\n';
785
+ var style_default = `/* Synthra token dashboard \u2014 palette per project brief.
786
+ Base: cream-on-near-black, dark-red borders, hot-pink highlights.
787
+ Plus: money green for dollar amounts, per-family model colors,
788
+ subtle dot grid + top glow background, tooltip system, icons. */
789
+
790
+ :root {
791
+ /* Brand */
792
+ --color-heading: #ECEBD8;
793
+ --color-body: #EDECD9;
794
+ --color-bg: #000000;
795
+ --color-surface: #140009;
796
+ --color-surface-raised: #1E000D;
797
+ --color-border: #4D0020;
798
+ --color-accent: #FF0073;
799
+ --color-accent-darker: #EB006A;
800
+ --color-form-bg: #2E0014;
801
+ --color-muted: rgba(237, 236, 217, 0.55);
802
+ --color-very-muted: rgba(237, 236, 217, 0.35);
803
+ --color-block: #FF0073;
804
+ --color-allow: #ECEBD8;
805
+
806
+ /* Money green \u2014 used everywhere a $ amount appears */
807
+ --color-money: #36E596;
808
+ --color-money-darker: #00B85F;
809
+ --color-money-bg: rgba(54, 229, 150, 0.08);
810
+
811
+ /* Per-family model colors */
812
+ --color-model-opus: #C9A2FF;
813
+ --color-model-sonnet: #6BD0FF;
814
+ --color-model-haiku: #7BFFC7;
815
+
816
+ /* Background layering */
817
+ --bg-dot-color: rgba(237, 236, 217, 0.045);
818
+ --bg-glow-color: rgba(255, 0, 115, 0.07);
819
+ }
820
+
821
+ * {
822
+ box-sizing: border-box;
823
+ margin: 0;
824
+ padding: 0;
825
+ }
826
+
827
+ html, body {
828
+ background-color: var(--color-bg);
829
+ color: var(--color-body);
830
+ font-family:
831
+ ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
832
+ system-ui, sans-serif;
833
+ font-size: 14px;
834
+ line-height: 1.5;
835
+ min-height: 100vh;
836
+ }
837
+
838
+ /* Body becomes a flex column so main can grow + header/footer stay sticky
839
+ at the natural top/bottom of the viewport. */
840
+ body {
841
+ display: flex;
842
+ flex-direction: column;
843
+ min-height: 100vh;
844
+ }
845
+
846
+ main {
847
+ flex: 1;
848
+ }
849
+
850
+ /* Layered backdrop: dot grid + soft pink glow at the top.
851
+ Both are fixed so they don't repeat as you scroll. */
852
+ body {
853
+ background-image:
854
+ radial-gradient(ellipse 70% 40% at 50% 0%, var(--bg-glow-color), transparent 70%),
855
+ radial-gradient(circle at 1px 1px, var(--bg-dot-color) 1px, transparent 0);
856
+ background-size: 100% 100%, 22px 22px;
857
+ background-attachment: fixed;
858
+ }
859
+
860
+ code, .num, table, .project-path {
861
+ font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
862
+ }
863
+
864
+ header {
865
+ display: flex;
866
+ align-items: center;
867
+ gap: 1.5rem;
868
+ padding: 1.1rem 2rem;
869
+ background: linear-gradient(180deg, rgba(20, 0, 9, 0.7), rgba(20, 0, 9, 0.4));
870
+ backdrop-filter: blur(8px);
871
+ border-bottom: 1px solid var(--color-border);
872
+ position: sticky;
873
+ top: 0;
874
+ z-index: 5;
875
+ }
876
+
877
+ header .brand {
878
+ display: flex;
879
+ align-items: baseline;
880
+ gap: 0.75rem;
881
+ }
882
+
883
+ header h1 {
884
+ color: var(--color-heading);
885
+ font-size: 1.35rem;
886
+ font-weight: 700;
887
+ letter-spacing: 0.02em;
888
+ }
889
+
890
+ header .tag {
891
+ color: var(--color-muted);
892
+ font-size: 0.78rem;
893
+ text-transform: uppercase;
894
+ letter-spacing: 0.1em;
895
+ }
896
+
897
+ header .meta {
898
+ margin-left: auto;
899
+ display: flex;
900
+ align-items: center;
901
+ gap: 0.75rem;
902
+ font-size: 0.78rem;
903
+ font-family: ui-monospace, monospace;
904
+ color: var(--color-muted);
905
+ }
906
+
907
+ header .active-project {
908
+ max-width: 480px;
909
+ white-space: nowrap;
910
+ overflow: hidden;
911
+ text-overflow: ellipsis;
912
+ }
913
+
914
+ main {
915
+ padding: 2rem;
916
+ display: flex;
917
+ flex-direction: column;
918
+ gap: 2rem;
919
+ max-width: 1400px;
920
+ margin: 0 auto;
921
+ width: 100%;
922
+ }
923
+
924
+ section {
925
+ display: flex;
926
+ flex-direction: column;
927
+ gap: 0.85rem;
928
+ }
929
+
930
+ h2 {
931
+ color: var(--color-heading);
932
+ font-size: 0.85rem;
933
+ font-weight: 600;
934
+ letter-spacing: 0.08em;
935
+ text-transform: uppercase;
936
+ display: flex;
937
+ align-items: center;
938
+ gap: 0.5rem;
939
+ }
940
+
941
+ h2 svg {
942
+ width: 14px;
943
+ height: 14px;
944
+ opacity: 0.5;
945
+ }
946
+
947
+ h2 .muted {
948
+ color: var(--color-very-muted);
949
+ font-size: 0.78rem;
950
+ font-weight: 400;
951
+ letter-spacing: 0.06em;
952
+ text-transform: none;
953
+ margin-left: 0.5rem;
954
+ }
955
+
956
+ /* ============================================================
957
+ Cards row (Global totals)
958
+ ============================================================ */
959
+
960
+ .cards {
961
+ display: grid;
962
+ grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
963
+ gap: 0.7rem;
964
+ }
965
+
966
+ .card {
967
+ position: relative;
968
+ background: var(--color-surface);
969
+ border: 1px solid var(--color-border);
970
+ border-radius: 8px;
971
+ padding: 0.95rem 1rem 0.85rem;
972
+ transition: transform 120ms ease, border-color 120ms ease;
973
+ }
974
+
975
+ .card:hover {
976
+ transform: translateY(-1px);
977
+ border-color: rgba(77, 0, 32, 0.85);
978
+ }
979
+
980
+ .card.accent {
981
+ background: var(--color-surface-raised);
982
+ border-color: var(--color-accent);
983
+ }
984
+
985
+ .card.money {
986
+ background: linear-gradient(180deg, var(--color-money-bg), transparent 80%), var(--color-surface);
987
+ border-color: var(--color-money-darker);
988
+ }
989
+
990
+ .card.money:hover {
991
+ border-color: var(--color-money);
992
+ }
993
+
994
+ .card-head {
995
+ display: flex;
996
+ align-items: center;
997
+ justify-content: space-between;
998
+ gap: 0.5rem;
999
+ margin-bottom: 0.4rem;
1000
+ }
1001
+
1002
+ .card-label {
1003
+ color: var(--color-muted);
1004
+ font-size: 0.66rem;
1005
+ text-transform: uppercase;
1006
+ letter-spacing: 0.09em;
1007
+ display: flex;
1008
+ align-items: center;
1009
+ gap: 0.35rem;
1010
+ }
1011
+
1012
+ .card-icon {
1013
+ width: 14px;
1014
+ height: 14px;
1015
+ color: var(--color-muted);
1016
+ opacity: 0.65;
1017
+ flex-shrink: 0;
1018
+ }
1019
+
1020
+ .card.accent .card-icon { color: var(--color-accent); opacity: 0.85; }
1021
+ .card.money .card-icon { color: var(--color-money); opacity: 0.85; }
1022
+
1023
+ .card-value {
1024
+ color: var(--color-heading);
1025
+ font-family: ui-monospace, monospace;
1026
+ font-size: 1.5rem;
1027
+ font-weight: 600;
1028
+ letter-spacing: -0.01em;
1029
+ }
1030
+
1031
+ .card.accent .card-value { color: var(--color-accent); }
1032
+ .card.money .card-value { color: var(--color-money); }
1033
+
1034
+ /* ============================================================
1035
+ Tooltip (data-tooltip on .has-tooltip)
1036
+ ============================================================ */
1037
+
1038
+ .has-tooltip {
1039
+ position: relative;
1040
+ cursor: help;
1041
+ }
1042
+
1043
+ .has-tooltip::before,
1044
+ .has-tooltip::after {
1045
+ position: absolute;
1046
+ pointer-events: none;
1047
+ opacity: 0;
1048
+ transition: opacity 150ms ease, transform 150ms ease;
1049
+ z-index: 100;
1050
+ }
1051
+
1052
+ .has-tooltip::after {
1053
+ content: attr(data-tooltip);
1054
+ bottom: calc(100% + 10px);
1055
+ left: 50%;
1056
+ transform: translate(-50%, 4px);
1057
+ background: var(--color-surface-raised);
1058
+ color: var(--color-body);
1059
+ border: 1px solid var(--color-border);
1060
+ border-radius: 6px;
1061
+ padding: 0.6rem 0.75rem;
1062
+ font-size: 0.74rem;
1063
+ font-weight: 400;
1064
+ text-transform: none;
1065
+ letter-spacing: 0;
1066
+ white-space: normal;
1067
+ width: 260px;
1068
+ text-align: left;
1069
+ line-height: 1.45;
1070
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.7);
1071
+ }
1072
+
1073
+ .has-tooltip::before {
1074
+ content: "";
1075
+ bottom: calc(100% + 4px);
1076
+ left: 50%;
1077
+ transform: translate(-50%, 4px);
1078
+ border: 6px solid transparent;
1079
+ border-top-color: var(--color-border);
1080
+ }
1081
+
1082
+ .has-tooltip:hover::after,
1083
+ .has-tooltip:hover::before {
1084
+ opacity: 1;
1085
+ transform: translate(-50%, 0);
1086
+ }
1087
+
1088
+ /* Small \u24D8 icon used to indicate tooltips */
1089
+ .help-icon {
1090
+ display: inline-flex;
1091
+ align-items: center;
1092
+ justify-content: center;
1093
+ width: 13px;
1094
+ height: 13px;
1095
+ border-radius: 50%;
1096
+ border: 1px solid var(--color-muted);
1097
+ color: var(--color-muted);
1098
+ font-size: 9px;
1099
+ font-weight: 600;
1100
+ font-family: ui-sans-serif, sans-serif;
1101
+ line-height: 1;
1102
+ cursor: help;
1103
+ user-select: none;
1104
+ transition: color 120ms, border-color 120ms;
1105
+ }
1106
+
1107
+ .has-tooltip:hover .help-icon {
1108
+ border-color: var(--color-accent);
1109
+ color: var(--color-accent);
1110
+ }
1111
+
1112
+ .card.money .has-tooltip:hover .help-icon { border-color: var(--color-money); color: var(--color-money); }
1113
+
1114
+ /* ============================================================
1115
+ Projects list
1116
+ ============================================================ */
1117
+
1118
+ .projects {
1119
+ display: flex;
1120
+ flex-direction: column;
1121
+ gap: 0.6rem;
1122
+ }
1123
+
1124
+ .project-row {
1125
+ display: grid;
1126
+ grid-template-columns: minmax(220px, 1fr) auto;
1127
+ grid-template-rows: auto auto;
1128
+ gap: 0.6rem 1.25rem;
1129
+ align-items: center;
1130
+ background: var(--color-surface);
1131
+ border: 1px solid var(--color-border);
1132
+ border-radius: 8px;
1133
+ padding: 0.9rem 1.2rem;
1134
+ transition: background 120ms ease;
1135
+ }
1136
+
1137
+ .project-row:hover {
1138
+ background: var(--color-surface-raised);
1139
+ }
1140
+
1141
+ .project-name {
1142
+ display: flex;
1143
+ flex-direction: column;
1144
+ gap: 0.15rem;
1145
+ overflow: hidden;
1146
+ }
1147
+
1148
+ .project-name strong {
1149
+ color: var(--color-heading);
1150
+ font-size: 0.95rem;
1151
+ font-weight: 600;
1152
+ display: flex;
1153
+ align-items: center;
1154
+ gap: 0.4rem;
1155
+ }
1156
+
1157
+ .project-name strong svg {
1158
+ width: 13px;
1159
+ height: 13px;
1160
+ opacity: 0.65;
1161
+ color: var(--color-muted);
1162
+ flex-shrink: 0;
1163
+ }
1164
+
1165
+ .project-name .project-path {
1166
+ color: var(--color-very-muted);
1167
+ font-size: 0.72rem;
1168
+ white-space: nowrap;
1169
+ overflow: hidden;
1170
+ text-overflow: ellipsis;
1171
+ }
1172
+
1173
+ .project-stats {
1174
+ display: flex;
1175
+ gap: 1.6rem;
1176
+ justify-self: end;
1177
+ text-align: right;
1178
+ }
1179
+
1180
+ .stat {
1181
+ display: flex;
1182
+ flex-direction: column;
1183
+ gap: 0.15rem;
1184
+ min-width: 70px;
1185
+ }
1186
+
1187
+ .stat-value {
1188
+ color: var(--color-heading);
1189
+ font-family: ui-monospace, monospace;
1190
+ font-size: 0.95rem;
1191
+ font-weight: 600;
1192
+ }
1193
+
1194
+ .stat-value.cost {
1195
+ color: var(--color-money);
1196
+ }
1197
+
1198
+ .stat-label {
1199
+ color: var(--color-muted);
1200
+ font-size: 0.66rem;
1201
+ text-transform: uppercase;
1202
+ letter-spacing: 0.08em;
1203
+ }
1204
+
1205
+ .bar {
1206
+ grid-column: 1 / -1;
1207
+ height: 3px;
1208
+ background: var(--color-form-bg);
1209
+ border-radius: 2px;
1210
+ overflow: hidden;
1211
+ }
1212
+
1213
+ .bar-fill {
1214
+ height: 100%;
1215
+ background: linear-gradient(90deg, var(--color-accent-darker), var(--color-accent));
1216
+ border-radius: 2px;
1217
+ transition: width 400ms ease;
1218
+ }
1219
+
1220
+ /* ============================================================
1221
+ Tables
1222
+ ============================================================ */
1223
+
1224
+ table {
1225
+ width: 100%;
1226
+ border-collapse: collapse;
1227
+ font-size: 0.83rem;
1228
+ background: var(--color-surface);
1229
+ border: 1px solid var(--color-border);
1230
+ border-radius: 8px;
1231
+ overflow: hidden;
1232
+ }
1233
+
1234
+ table thead th {
1235
+ text-align: left;
1236
+ color: var(--color-muted);
1237
+ text-transform: uppercase;
1238
+ font-size: 0.66rem;
1239
+ letter-spacing: 0.08em;
1240
+ font-weight: 600;
1241
+ padding: 0.65rem 0.85rem;
1242
+ border-bottom: 1px solid var(--color-border);
1243
+ background: var(--color-surface);
1244
+ }
1245
+
1246
+ table thead th.num {
1247
+ text-align: right;
1248
+ }
1249
+
1250
+ table thead th .has-tooltip {
1251
+ display: inline-flex;
1252
+ align-items: center;
1253
+ gap: 0.3rem;
1254
+ }
1255
+
1256
+ table tbody td {
1257
+ padding: 0.55rem 0.85rem;
1258
+ border-bottom: 1px solid rgba(77, 0, 32, 0.4);
1259
+ color: var(--color-body);
1260
+ }
1261
+
1262
+ table tbody td.num {
1263
+ text-align: right;
1264
+ }
1265
+
1266
+ table tbody td.cost {
1267
+ color: var(--color-money);
1268
+ font-weight: 600;
1269
+ }
1270
+
1271
+ table tbody tr:last-child td {
1272
+ border-bottom: none;
1273
+ }
1274
+
1275
+ table tbody tr:hover {
1276
+ background: var(--color-surface-raised);
1277
+ }
1278
+
1279
+ /* ============================================================
1280
+ Model pills \u2014 color-coded by family
1281
+ ============================================================ */
1282
+
1283
+ .model-pill {
1284
+ display: inline-block;
1285
+ padding: 0.1rem 0.5rem;
1286
+ border-radius: 3px;
1287
+ background: var(--color-form-bg);
1288
+ font-family: ui-monospace, monospace;
1289
+ font-size: 0.82em;
1290
+ border: 1px solid transparent;
1291
+ color: var(--color-body);
1292
+ white-space: nowrap;
1293
+ }
1294
+
1295
+ .model-pill.opus {
1296
+ color: var(--color-model-opus);
1297
+ border-color: rgba(201, 162, 255, 0.3);
1298
+ background: rgba(201, 162, 255, 0.08);
1299
+ }
1300
+
1301
+ .model-pill.sonnet {
1302
+ color: var(--color-model-sonnet);
1303
+ border-color: rgba(107, 208, 255, 0.3);
1304
+ background: rgba(107, 208, 255, 0.08);
1305
+ }
1306
+
1307
+ .model-pill.haiku {
1308
+ color: var(--color-model-haiku);
1309
+ border-color: rgba(123, 255, 199, 0.3);
1310
+ background: rgba(123, 255, 199, 0.08);
1311
+ }
1312
+
1313
+ .model-pill.unknown {
1314
+ color: var(--color-muted);
1315
+ font-style: italic;
1316
+ border-color: rgba(237, 236, 217, 0.15);
1317
+ }
1318
+
1319
+ /* Existing inline code in tables (project name, etc.) */
1320
+ code {
1321
+ color: var(--color-heading);
1322
+ background: var(--color-form-bg);
1323
+ padding: 0.1rem 0.4rem;
1324
+ border-radius: 3px;
1325
+ font-size: 0.85em;
1326
+ }
1327
+
1328
+ /* ============================================================
1329
+ Decisions (allow / block)
1330
+ ============================================================ */
1331
+
1332
+ .decision-block {
1333
+ color: var(--color-block);
1334
+ font-weight: 700;
1335
+ text-transform: uppercase;
1336
+ font-size: 0.72rem;
1337
+ letter-spacing: 0.06em;
1338
+ display: inline-flex;
1339
+ align-items: center;
1340
+ gap: 0.3rem;
1341
+ }
1342
+
1343
+ .decision-block svg {
1344
+ width: 11px;
1345
+ height: 11px;
1346
+ }
1347
+
1348
+ .decision-allow {
1349
+ color: var(--color-allow);
1350
+ opacity: 0.7;
1351
+ text-transform: uppercase;
1352
+ font-size: 0.72rem;
1353
+ letter-spacing: 0.06em;
1354
+ }
1355
+
1356
+ .empty {
1357
+ color: var(--color-muted);
1358
+ font-style: italic;
1359
+ padding: 1rem;
1360
+ background: var(--color-surface);
1361
+ border: 1px dashed var(--color-border);
1362
+ border-radius: 8px;
1363
+ }
1364
+
1365
+ .hidden {
1366
+ display: none;
1367
+ }
1368
+
1369
+ footer {
1370
+ display: flex;
1371
+ justify-content: space-between;
1372
+ align-items: center;
1373
+ padding: 0.85rem 2rem;
1374
+ background: linear-gradient(0deg, rgba(20, 0, 9, 0.7), rgba(20, 0, 9, 0.4));
1375
+ backdrop-filter: blur(8px);
1376
+ border-top: 1px solid var(--color-border);
1377
+ color: var(--color-muted);
1378
+ font-size: 0.72rem;
1379
+ font-family: ui-monospace, monospace;
1380
+ position: sticky;
1381
+ bottom: 0;
1382
+ z-index: 5;
1383
+ }
1384
+
1385
+ footer .muted {
1386
+ color: var(--color-very-muted);
1387
+ }
1388
+
1389
+ .dot {
1390
+ width: 8px;
1391
+ height: 8px;
1392
+ border-radius: 50%;
1393
+ background: var(--color-muted);
1394
+ transition: background 200ms ease, box-shadow 200ms ease;
1395
+ }
1396
+
1397
+ .dot.live {
1398
+ background: var(--color-money);
1399
+ box-shadow: 0 0 8px var(--color-money-darker);
1400
+ }
1401
+
1402
+ .dot.dead {
1403
+ background: var(--color-border);
1404
+ box-shadow: none;
1405
+ }
1406
+ `;
678
1407
 
679
1408
  // src/dashboard/server.ts
680
1409
  var FALLBACK_RANGE = 9;
@@ -4170,7 +4899,7 @@ async function spawnClaude(bin, opts) {
4170
4899
  }
4171
4900
 
4172
4901
  // src/cli/index.ts
4173
- var VERSION = "0.0.1";
4902
+ var VERSION = package_default.version;
4174
4903
  function printReadyBanner(info) {
4175
4904
  log.info("");
4176
4905
  log.info(` \u2705 scanned ${info.scan.parsed} files \xB7 ${info.scan.symbolCount} symbols \xB7 ${info.scan.edgeCount} edges`);