voyageai-cli 1.24.0 → 1.26.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 (43) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +2 -0
  3. package/src/commands/about.js +1 -1
  4. package/src/commands/bug.js +1 -1
  5. package/src/commands/playground.js +31 -0
  6. package/src/commands/scaffold.js +23 -1
  7. package/src/commands/workflow.js +336 -0
  8. package/src/lib/explanations.js +53 -0
  9. package/src/lib/scaffold-structure.js +8 -9
  10. package/src/lib/telemetry.js +1 -1
  11. package/src/lib/template-engine.js +240 -0
  12. package/src/lib/templates/nextjs/README.md.tpl +78 -55
  13. package/src/lib/templates/nextjs/favicon.svg.tpl +11 -0
  14. package/src/lib/templates/nextjs/footer.jsx.tpl +49 -0
  15. package/src/lib/templates/nextjs/layout.jsx.tpl +16 -10
  16. package/src/lib/templates/nextjs/lib-mongo.js.tpl +5 -5
  17. package/src/lib/templates/nextjs/lib-voyage.js.tpl +13 -8
  18. package/src/lib/templates/nextjs/navbar.jsx.tpl +98 -0
  19. package/src/lib/templates/nextjs/page-home.jsx.tpl +201 -0
  20. package/src/lib/templates/nextjs/page-search.jsx.tpl +184 -82
  21. package/src/lib/templates/nextjs/theme-registry.jsx.tpl +51 -0
  22. package/src/lib/templates/nextjs/theme.js.tpl +138 -65
  23. package/src/lib/templates/nextjs/vai-logo-256.png +0 -0
  24. package/src/lib/workflow-utils.js +65 -0
  25. package/src/lib/workflow.js +1259 -0
  26. package/src/mcp/tools/management.js +1 -60
  27. package/src/playground/icons/dark/128.png +0 -0
  28. package/src/playground/icons/dark/16.png +0 -0
  29. package/src/playground/icons/dark/256.png +0 -0
  30. package/src/playground/icons/dark/32.png +0 -0
  31. package/src/playground/icons/dark/64.png +0 -0
  32. package/src/playground/icons/light/128.png +0 -0
  33. package/src/playground/icons/light/16.png +0 -0
  34. package/src/playground/icons/light/256.png +0 -0
  35. package/src/playground/icons/light/32.png +0 -0
  36. package/src/playground/icons/light/64.png +0 -0
  37. package/src/playground/icons/watermark.png +0 -0
  38. package/src/playground/index.html +125 -73
  39. package/src/workflows/consistency-check.json +64 -0
  40. package/src/workflows/cost-analysis.json +69 -0
  41. package/src/workflows/multi-collection-search.json +80 -0
  42. package/src/workflows/research-and-summarize.json +46 -0
  43. package/src/workflows/smart-ingest.json +63 -0
@@ -2,66 +2,7 @@
2
2
 
3
3
  const { MODEL_CATALOG } = require('../../lib/catalog');
4
4
  const { loadProject } = require('../../lib/project');
5
- const { requireMongoUri } = require('../../lib/mongo');
6
-
7
- /**
8
- * Introspect MongoDB collections — list collections with vector index info.
9
- * @param {string} dbName
10
- * @returns {Promise<Array<{ name: string, documentCount: number, hasVectorIndex: boolean, embeddingField?: string, dimensions?: number }>>}
11
- */
12
- async function introspectCollections(dbName) {
13
- const { MongoClient } = require('mongodb');
14
- const uri = requireMongoUri();
15
- const client = new MongoClient(uri);
16
- await client.connect();
17
-
18
- try {
19
- const db = client.db(dbName);
20
- const collections = await db.listCollections().toArray();
21
- const results = [];
22
-
23
- for (const collInfo of collections) {
24
- if (collInfo.name.startsWith('system.')) continue;
25
- const coll = db.collection(collInfo.name);
26
- const documentCount = await coll.estimatedDocumentCount();
27
-
28
- let hasVectorIndex = false;
29
- let embeddingField;
30
- let dimensions;
31
-
32
- try {
33
- const indexes = await coll.listSearchIndexes().toArray();
34
- for (const idx of indexes) {
35
- // Atlas Search index definitions vary; look for vector type
36
- const fields = idx.latestDefinition?.fields || [];
37
- for (const f of fields) {
38
- if (f.type === 'vector') {
39
- hasVectorIndex = true;
40
- embeddingField = f.path;
41
- dimensions = f.numDimensions;
42
- break;
43
- }
44
- }
45
- if (hasVectorIndex) break;
46
- }
47
- } catch {
48
- // listSearchIndexes may not be available on non-Atlas deployments
49
- }
50
-
51
- results.push({
52
- name: collInfo.name,
53
- documentCount,
54
- hasVectorIndex,
55
- ...(embeddingField && { embeddingField }),
56
- ...(dimensions && { dimensions }),
57
- });
58
- }
59
-
60
- return results;
61
- } finally {
62
- await client.close();
63
- }
64
- }
5
+ const { introspectCollections } = require('../../lib/workflow-utils');
65
6
 
66
7
  /**
67
8
  * Register management tools: vai_collections, vai_models
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -10,56 +10,56 @@
10
10
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
11
11
 
12
12
  :root {
13
- /* MongoDB Design System — Dark Mode Palette (default) */
13
+ /* Vai Dark Mode Palette (default) — Teal/Cyan brand */
14
14
  --bg: #001E2B; /* MDB Black */
15
15
  --bg-surface: #112733; /* Gray Dark 4 */
16
16
  --bg-card: #1C2D38; /* Gray Dark 3 */
17
17
  --bg-input: #112733; /* Gray Dark 4 */
18
- --accent: #00ED64; /* Green Base — interactive elements only */
18
+ --accent: #00D4AA; /* Teal — interactive elements */
19
19
  --accent-text: #FFFFFF; /* Bright white — headings/labels in dark mode */
20
- --accent-dim: #00A35C; /* Green Dark 1 */
21
- --accent-glow: rgba(0, 237, 100, 0.12);
20
+ --accent-dim: #009E80; /* Teal Dark */
21
+ --accent-glow: rgba(0, 212, 170, 0.10);
22
22
  --text: #E8EDEB; /* Gray Light 2 */
23
23
  --text-dim: #C1C7C6; /* Gray Light 1 */
24
24
  --text-muted: #889397; /* Gray Base */
25
25
  --border: #3D4F58; /* Gray Dark 2 */
26
26
  --error: #FF6960; /* Red Light 1 */
27
27
  --warning: #FFC010; /* Yellow Base */
28
- --success: #00ED64; /* Green Base */
28
+ --success: #00D4AA; /* Teal */
29
29
  --red: #FF6960; /* Red Light 1 */
30
30
  --yellow: #FFC010; /* Yellow Base */
31
- --green: #00ED64; /* Green Base */
32
- --blue: #0498EC; /* Blue Light 1 (links) */
31
+ --green: #00D4AA; /* Teal */
32
+ --blue: #40E0FF; /* Cyan (links) */
33
33
  --purple: #B45AF2; /* Purple Base */
34
- --green-dark: #023430; /* Green Dark 3 */
34
+ --green-dark: #00241E; /* Teal Dark 3 */
35
35
  --radius: 8px;
36
36
  --font: 'Euclid Circular A', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
37
37
  --mono: 'Source Code Pro', 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
38
38
  }
39
39
 
40
- /* MongoDB Design SystemLight Mode Palette */
40
+ /* Vai Light Mode Palette Teal/Cyan brand */
41
41
  [data-theme="light"] {
42
42
  --bg: #FFFFFF; /* White */
43
43
  --bg-surface: #F9FBFA; /* Gray Light 3 */
44
44
  --bg-card: #FFFFFF; /* White */
45
45
  --bg-input: #F9FBFA; /* Gray Light 3 */
46
- --accent: #00A35C; /* Green Dark 1 — interactive elements */
46
+ --accent: #009E80; /* Teal Dark — interactive elements */
47
47
  --accent-text: #001E2B; /* MDB Black — headings/labels in light mode */
48
- --accent-dim: #00684A; /* Green Dark 2 */
49
- --accent-glow: rgba(0, 163, 92, 0.08);
48
+ --accent-dim: #007A63; /* Teal Darker */
49
+ --accent-glow: rgba(0, 158, 128, 0.08);
50
50
  --text: #001E2B; /* MDB Black */
51
51
  --text-dim: #5C6C75; /* Gray Dark 1 */
52
52
  --text-muted: #889397; /* Gray Base */
53
53
  --border: #E8EDEB; /* Gray Light 2 */
54
54
  --error: #DB3030; /* Red Base */
55
55
  --warning: #944F01; /* Yellow Dark 2 */
56
- --success: #00684A; /* Green Dark 2 */
56
+ --success: #009E80; /* Teal Dark */
57
57
  --red: #DB3030; /* Red Base */
58
58
  --yellow: #944F01; /* Yellow Dark 2 */
59
- --green: #00684A; /* Green Dark 2 */
60
- --blue: #016BF8; /* Blue Base */
59
+ --green: #009E80; /* Teal Dark */
60
+ --blue: #0088CC; /* Cyan Dark (links) */
61
61
  --purple: #5E0C9E; /* Purple Dark 2 */
62
- --green-dark: #023430; /* Green Dark 3 */
62
+ --green-dark: #00241E; /* Teal Dark 3 */
63
63
  }
64
64
  /* Light mode shadow + card adjustments */
65
65
  [data-theme="light"] .explore-card,
@@ -69,7 +69,7 @@
69
69
  box-shadow: 0 1px 4px rgba(0, 30, 43, 0.08);
70
70
  }
71
71
  [data-theme="light"] .explore-card:hover {
72
- box-shadow: 0 4px 16px rgba(0, 163, 92, 0.12);
72
+ box-shadow: 0 4px 16px rgba(0, 158, 128, 0.12);
73
73
  }
74
74
  [data-theme="light"] .cost-modal,
75
75
  [data-theme="light"] .explore-modal {
@@ -83,14 +83,14 @@
83
83
  box-shadow: 1px 0 3px rgba(0, 30, 43, 0.06);
84
84
  }
85
85
  /* Light mode gradient overrides */
86
- [data-theme="light"] .quant-bar-fill.storage { background: linear-gradient(90deg, #00A35C, #00ED64); }
87
- [data-theme="light"] .quant-bar-fill.latency { background: linear-gradient(90deg, #016BF8, #0498EC); }
88
- [data-theme="light"] .quant-meter-fill.perfect { background: linear-gradient(90deg, #00A35C, #00ED64); }
86
+ [data-theme="light"] .quant-bar-fill.storage { background: linear-gradient(90deg, #009E80, #00D4AA); }
87
+ [data-theme="light"] .quant-bar-fill.latency { background: linear-gradient(90deg, #0088CC, #40E0FF); }
88
+ [data-theme="light"] .quant-meter-fill.perfect { background: linear-gradient(90deg, #009E80, #00D4AA); }
89
89
  [data-theme="light"] .quant-meter-fill.good { background: linear-gradient(90deg, #944F01, #FFC010); }
90
90
  [data-theme="light"] .quant-meter-fill.degraded { background: linear-gradient(90deg, #DB3030, #FF6960); }
91
91
  /* Light mode button text */
92
92
  [data-theme="light"] .btn { color: #FFFFFF; }
93
- [data-theme="light"] .btn:hover { background: #00684A; }
93
+ [data-theme="light"] .btn:hover { background: #007A63; }
94
94
 
95
95
  html, body { height: 100%; }
96
96
 
@@ -108,8 +108,8 @@ body {
108
108
  align-items: center;
109
109
  gap: 10px;
110
110
  padding: 8px 16px;
111
- background: linear-gradient(135deg, rgba(0,163,92,0.12), rgba(4,152,236,0.12));
112
- border-bottom: 1px solid rgba(0,237,100,0.2);
111
+ background: linear-gradient(135deg, rgba(0,158,128,0.12), rgba(4,152,236,0.12));
112
+ border-bottom: 1px solid rgba(0,212,170,0.2);
113
113
  font-size: 13px;
114
114
  color: var(--text);
115
115
  flex-shrink: 0;
@@ -178,7 +178,7 @@ body {
178
178
  padding: 24px 28px 20px;
179
179
  width: 380px;
180
180
  max-width: 90vw;
181
- box-shadow: 0 12px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(0,237,100,0.15);
181
+ box-shadow: 0 12px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(0,212,170,0.15);
182
182
  transition: all 0.4s cubic-bezier(0.4,0,0.2,1);
183
183
  opacity: 0;
184
184
  transform: translateY(10px);
@@ -296,7 +296,7 @@ body {
296
296
  width: 460px;
297
297
  max-width: 90vw;
298
298
  text-align: center;
299
- box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 80px rgba(0,237,100,0.08);
299
+ box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 80px rgba(0,212,170,0.08);
300
300
  pointer-events: auto;
301
301
  opacity: 0;
302
302
  transform: scale(0.95);
@@ -324,10 +324,10 @@ body {
324
324
  margin-bottom: 28px;
325
325
  }
326
326
  [data-theme="light"] .onboarding-tooltip {
327
- box-shadow: 0 12px 40px rgba(0,30,43,0.15), 0 0 0 1px rgba(0,163,92,0.2);
327
+ box-shadow: 0 12px 40px rgba(0,30,43,0.15), 0 0 0 1px rgba(0,158,128,0.2);
328
328
  }
329
329
  [data-theme="light"] .onboarding-welcome-card {
330
- box-shadow: 0 20px 60px rgba(0,30,43,0.18), 0 0 80px rgba(0,163,92,0.06);
330
+ box-shadow: 0 20px 60px rgba(0,30,43,0.18), 0 0 80px rgba(0,158,128,0.06);
331
331
  }
332
332
  [data-theme="light"] .onboarding-backdrop {
333
333
  background: rgba(0,30,43,0.45);
@@ -480,7 +480,7 @@ body:not(.is-electron) .sidebar-drag-region {
480
480
  height: 16px;
481
481
  }
482
482
  [data-theme="light"] .tab-btn:hover { background: rgba(0,30,43,0.04); }
483
- [data-theme="light"] .tab-btn.active { background: rgba(0,163,92,0.08); }
483
+ [data-theme="light"] .tab-btn.active { background: rgba(0,158,128,0.08); }
484
484
 
485
485
  .sidebar-footer {
486
486
  padding: 12px 16px;
@@ -558,6 +558,27 @@ body:not(.is-electron) .sidebar-drag-region {
558
558
  overflow-y: auto;
559
559
  display: flex;
560
560
  flex-direction: column;
561
+ position: relative;
562
+ }
563
+
564
+ .content-area::after {
565
+ content: '';
566
+ position: fixed;
567
+ bottom: 2rem;
568
+ right: 2rem;
569
+ width: 200px;
570
+ height: 200px;
571
+ background-image: url('/icons/watermark.png');
572
+ background-size: contain;
573
+ background-repeat: no-repeat;
574
+ opacity: 0.05;
575
+ pointer-events: none;
576
+ z-index: 0;
577
+ filter: invert(1);
578
+ }
579
+
580
+ [data-theme="light"] .content-area::after {
581
+ filter: none;
561
582
  }
562
583
 
563
584
  .content-drag-region {
@@ -644,7 +665,7 @@ select:focus { outline: none; border-color: var(--accent); }
644
665
  align-items: center;
645
666
  gap: 8px;
646
667
  }
647
- .btn:hover { background: #71F6BA; transform: translateY(-1px); }
668
+ .btn:hover { background: #5CE8CC; transform: translateY(-1px); }
648
669
  .btn:active { transform: translateY(0); }
649
670
  .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
650
671
 
@@ -689,7 +710,7 @@ select:focus { outline: none; border-color: var(--accent); }
689
710
  }
690
711
  .subtab:hover {
691
712
  color: var(--text);
692
- background: rgba(0, 237, 100, 0.05);
713
+ background: rgba(0, 212, 170, 0.05);
693
714
  }
694
715
  .subtab.active {
695
716
  background: var(--accent);
@@ -964,7 +985,7 @@ select:focus { outline: none; border-color: var(--accent); }
964
985
  .explore-card:hover {
965
986
  border-color: var(--accent);
966
987
  transform: translateY(-2px);
967
- box-shadow: 0 4px 20px rgba(0, 237, 100, 0.1);
988
+ box-shadow: 0 4px 20px rgba(0, 212, 170, 0.1);
968
989
  }
969
990
  .explore-card.expanded {
970
991
  border-color: var(--accent);
@@ -1252,8 +1273,8 @@ select:focus { outline: none; border-color: var(--accent); }
1252
1273
  font-family: var(--mono); font-size: 12px; font-weight: 600;
1253
1274
  color: var(--green-dark); white-space: nowrap; min-width: fit-content;
1254
1275
  }
1255
- .quant-bar-fill.storage { background: linear-gradient(90deg, #00ED64, #71F6BA); }
1256
- .quant-bar-fill.latency { background: linear-gradient(90deg, #0498EC, #016BF8); }
1276
+ .quant-bar-fill.storage { background: linear-gradient(90deg, #00D4AA, #5CE8CC); }
1277
+ .quant-bar-fill.latency { background: linear-gradient(90deg, #40E0FF, #0088CC); }
1257
1278
  .quant-bar-badge {
1258
1279
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
1259
1280
  font-size: 12px; color: var(--text-dim); font-family: var(--mono);
@@ -1278,7 +1299,7 @@ select:focus { outline: none; border-color: var(--accent); }
1278
1299
  height: 100%; border-radius: 5px;
1279
1300
  transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
1280
1301
  }
1281
- .quant-meter-fill.perfect { background: linear-gradient(90deg, #00ED64, #71F6BA); }
1302
+ .quant-meter-fill.perfect { background: linear-gradient(90deg, #00D4AA, #5CE8CC); }
1282
1303
  .quant-meter-fill.good { background: linear-gradient(90deg, #FFC010, #FFEC9E); }
1283
1304
  .quant-meter-fill.degraded { background: linear-gradient(90deg, #FF6960, #FFCDC7); }
1284
1305
  .quant-meter-detail { font-size: 11px; color: var(--text-muted); margin-top: 4px; font-family: var(--mono); }
@@ -1374,7 +1395,7 @@ select:focus { outline: none; border-color: var(--accent); }
1374
1395
  font-weight: 600;
1375
1396
  }
1376
1397
  .cost-mode-btn:hover:not(.active) {
1377
- background: rgba(0, 237, 100, 0.1);
1398
+ background: rgba(0, 212, 170, 0.1);
1378
1399
  color: var(--text);
1379
1400
  }
1380
1401
  .cost-select {
@@ -1514,7 +1535,7 @@ select:focus { outline: none; border-color: var(--accent); }
1514
1535
  border-bottom: 1px solid rgba(42, 53, 80, 0.3);
1515
1536
  font-family: var(--mono);
1516
1537
  }
1517
- .cost-table tr:hover { background: rgba(0, 237, 100, 0.03); }
1538
+ .cost-table tr:hover { background: rgba(0, 212, 170, 0.03); }
1518
1539
  .cost-highlight {
1519
1540
  color: var(--accent-text);
1520
1541
  font-weight: 600;
@@ -1541,7 +1562,7 @@ select:focus { outline: none; border-color: var(--accent); }
1541
1562
  .cost-tip {
1542
1563
  font-size: 12px;
1543
1564
  color: var(--text-muted);
1544
- background: rgba(0, 237, 100, 0.05);
1565
+ background: rgba(0, 212, 170, 0.05);
1545
1566
  border-left: 3px solid var(--accent);
1546
1567
  padding: 10px 14px;
1547
1568
  border-radius: 0 6px 6px 0;
@@ -1661,7 +1682,7 @@ select:focus { outline: none; border-color: var(--accent); }
1661
1682
  }
1662
1683
  .cost-modal .formula .accent { color: var(--accent); font-weight: 600; }
1663
1684
  .cost-modal .example {
1664
- background: rgba(0, 237, 100, 0.05);
1685
+ background: rgba(0, 212, 170, 0.05);
1665
1686
  border-left: 3px solid var(--accent);
1666
1687
  border-radius: 0 8px 8px 0;
1667
1688
  padding: 12px 16px;
@@ -1843,8 +1864,8 @@ select:focus { outline: none; border-color: var(--accent); }
1843
1864
  line-height: 1.5;
1844
1865
  }
1845
1866
  .settings-api-banner.success {
1846
- background: rgba(0,237,100,0.06);
1847
- border-color: rgba(0,237,100,0.2);
1867
+ background: rgba(0,212,170,0.06);
1868
+ border-color: rgba(0,212,170,0.2);
1848
1869
  color: var(--success);
1849
1870
  }
1850
1871
  [data-theme="light"] .settings-api-banner {
@@ -1932,8 +1953,8 @@ select:focus { outline: none; border-color: var(--accent); }
1932
1953
  font-size: 13px;
1933
1954
  font-weight: 600;
1934
1955
  color: var(--accent);
1935
- background: rgba(0,237,100,0.08);
1936
- border: 1px solid rgba(0,237,100,0.2);
1956
+ background: rgba(0,212,170,0.08);
1957
+ border: 1px solid rgba(0,212,170,0.2);
1937
1958
  border-radius: 6px;
1938
1959
  padding: 4px 12px;
1939
1960
  letter-spacing: 0.3px;
@@ -1941,7 +1962,7 @@ select:focus { outline: none; border-color: var(--accent); }
1941
1962
  [data-theme="light"] .version-badge {
1942
1963
  background: rgba(0,104,74,0.06);
1943
1964
  border-color: rgba(0,104,74,0.2);
1944
- color: #00684A;
1965
+ color: #007A63;
1945
1966
  }
1946
1967
  .settings-api-field .save-btn {
1947
1968
  color: var(--accent);
@@ -2174,7 +2195,7 @@ select:focus { outline: none; border-color: var(--accent); }
2174
2195
  }
2175
2196
  [data-theme="light"] .settings-origin.origin-env {
2176
2197
  background: rgba(4,120,190,0.10);
2177
- color: #016BF8;
2198
+ color: #0088CC;
2178
2199
  }
2179
2200
  .settings-origin.origin-config {
2180
2201
  background: rgba(180,90,242,0.12);
@@ -2185,12 +2206,12 @@ select:focus { outline: none; border-color: var(--accent); }
2185
2206
  color: #7B3DB5;
2186
2207
  }
2187
2208
  .settings-origin.origin-project {
2188
- background: rgba(0,237,100,0.12);
2209
+ background: rgba(0,212,170,0.12);
2189
2210
  color: var(--accent);
2190
2211
  }
2191
2212
  [data-theme="light"] .settings-origin.origin-project {
2192
- background: rgba(0,163,92,0.10);
2193
- color: #00A35C;
2213
+ background: rgba(0,158,128,0.10);
2214
+ color: #009E80;
2194
2215
  }
2195
2216
  .settings-origin.origin-default {
2196
2217
  background: rgba(136,147,151,0.12);
@@ -4280,7 +4301,7 @@ let lastEmbedding = null;
4280
4301
 
4281
4302
  // ── Init ──
4282
4303
  // ── Anonymous Telemetry ──
4283
- const TELEMETRY_URL = 'https://vai.mlynn.org/api/telemetry';
4304
+ const TELEMETRY_URL = 'https://vaicli.com/api/telemetry';
4284
4305
  const TELEMETRY_KEY = 'vai-telemetry-enabled';
4285
4306
 
4286
4307
  function isTelemetryEnabled() {
@@ -5318,8 +5339,8 @@ const BENCH_SAMPLE_TEXTS = [
5318
5339
  ];
5319
5340
 
5320
5341
  const MODEL_COLORS = [
5321
- '#00ED64', '#71F6BA', '#0498EC', '#B45AF2', '#FFC010',
5322
- '#FF6960', '#B45AF2', '#FFC010', '#016BF8', '#C0FAE6',
5342
+ '#00D4AA', '#5CE8CC', '#40E0FF', '#B45AF2', '#FFC010',
5343
+ '#FF6960', '#B45AF2', '#FFC010', '#0088CC', '#A8F0E0',
5323
5344
  ];
5324
5345
 
5325
5346
  window.doBenchLatency = async function() {
@@ -5660,7 +5681,7 @@ window.doBenchQuantization = async function() {
5660
5681
  const baseline = completed.find(r => r.dtype === 'float') || completed[0];
5661
5682
  const maxBytes = Math.max(...completed.map(r => r.bytesPerVec));
5662
5683
  const maxLatency = Math.max(...completed.map(r => r.latency));
5663
- const DTYPE_COLORS = { float: '#00ED64', int8: '#71F6BA', uint8: '#0498EC', ubinary: '#FFC010', binary: '#FF6960' };
5684
+ const DTYPE_COLORS = { float: '#00D4AA', int8: '#5CE8CC', uint8: '#40E0FF', ubinary: '#FFC010', binary: '#FF6960' };
5664
5685
 
5665
5686
  // ── Storage Bar Chart ──
5666
5687
  let storageHTML = '';
@@ -5671,7 +5692,7 @@ window.doBenchQuantization = async function() {
5671
5692
  const savings = r.bytesPerVec < baseline.bytesPerVec
5672
5693
  ? `${(baseline.bytesPerVec / r.bytesPerVec).toFixed(0)}× smaller`
5673
5694
  : 'baseline';
5674
- const color = DTYPE_COLORS[r.dtype] || '#0498EC';
5695
+ const color = DTYPE_COLORS[r.dtype] || '#40E0FF';
5675
5696
  storageHTML += `<div class="quant-bar-group">
5676
5697
  <div class="quant-bar-label">
5677
5698
  <span class="dtype-name">${r.dtype}</span>
@@ -5689,7 +5710,7 @@ window.doBenchQuantization = async function() {
5689
5710
  const minLatency = Math.min(...completed.map(r => r.latency));
5690
5711
  for (const r of completed) {
5691
5712
  const pct = Math.max(8, (r.latency / maxLatency) * 100);
5692
- const color = DTYPE_COLORS[r.dtype] || '#0498EC';
5713
+ const color = DTYPE_COLORS[r.dtype] || '#40E0FF';
5693
5714
  const badge = r.latency === minLatency ? ' ⚡' : '';
5694
5715
  latencyHTML += `<div class="quant-bar-group">
5695
5716
  <div class="quant-bar-label">
@@ -7229,7 +7250,22 @@ init = async function() {
7229
7250
  // ── Start ──
7230
7251
  init();
7231
7252
 
7232
- // ── Vector Space Invaders (Easter Egg) ──
7253
+ // ── Vector Space Invaders (Easter Egg) ──
7254
+
7255
+ // VSI Telemetry helper
7256
+ function sendVSITelemetry(event, extra) {
7257
+ try {
7258
+ fetch('https://vaicli.com/api/telemetry', {
7259
+ method: 'POST',
7260
+ headers: { 'Content-Type': 'application/json' },
7261
+ body: JSON.stringify({ event, ...extra, sentAt: new Date().toISOString() }),
7262
+ }).catch(() => {});
7263
+ } catch (_) {}
7264
+ }
7265
+
7266
+ let _vsiGameStartTime = 0;
7267
+ let _vsiGameTrigger = 'unknown';
7268
+
7233
7269
  (function initEasterEgg() {
7234
7270
  const KONAMI = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];
7235
7271
  let konamiIdx = 0;
@@ -7237,7 +7273,7 @@ init();
7237
7273
 
7238
7274
  document.addEventListener('keydown', (e) => {
7239
7275
  if (document.getElementById('vsiOverlay')?.style.display === 'flex') return;
7240
- if (e.key === KONAMI[konamiIdx]) { konamiIdx++; if (konamiIdx === KONAMI.length) { konamiIdx = 0; launchVSI(); } }
7276
+ if (e.key === KONAMI[konamiIdx]) { konamiIdx++; if (konamiIdx === KONAMI.length) { konamiIdx = 0; _vsiGameTrigger = 'konami'; launchVSI(); } }
7241
7277
  else { konamiIdx = 0; }
7242
7278
  });
7243
7279
 
@@ -7247,7 +7283,7 @@ init();
7247
7283
  const now = Date.now();
7248
7284
  logoClicks.push(now);
7249
7285
  logoClicks = logoClicks.filter(t => now - t < 2000);
7250
- if (logoClicks.length >= 7) { logoClicks = []; launchVSI(); }
7286
+ if (logoClicks.length >= 7) { logoClicks = []; _vsiGameTrigger = 'logo_click'; launchVSI(); }
7251
7287
  });
7252
7288
  }
7253
7289
 
@@ -7256,6 +7292,22 @@ init();
7256
7292
  const canvas = document.getElementById('vsiCanvas');
7257
7293
  if (!overlay || !canvas) return;
7258
7294
  overlay.style.display = 'flex';
7295
+ _vsiGameStartTime = Date.now();
7296
+
7297
+ // Detect version from page
7298
+ const versionEl = document.querySelector('.version-label, [data-version]');
7299
+ const version = versionEl ? (versionEl.dataset?.version || versionEl.textContent?.trim()) : 'unknown';
7300
+ const ua = navigator.userAgent;
7301
+ const platform = /Mac/.test(ua) ? 'macOS' : /Win/.test(ua) ? 'Windows' : /Linux/.test(ua) ? 'Linux' : /Android/.test(ua) ? 'Android' : /iPhone|iPad/.test(ua) ? 'iOS' : 'unknown';
7302
+
7303
+ sendVSITelemetry('vsi_game_start', {
7304
+ version,
7305
+ context: 'playground',
7306
+ platform,
7307
+ locale: navigator.language || 'unknown',
7308
+ trigger: _vsiGameTrigger,
7309
+ });
7310
+
7259
7311
  startVSI(canvas, () => { overlay.style.display = 'none'; });
7260
7312
  }
7261
7313
  })();
@@ -7277,7 +7329,7 @@ function startVSI(canvas, onExit) {
7277
7329
  ctx.quadraticCurveTo(x - size * 0.6, y - size * 0.3, x, y - size);
7278
7330
  ctx.fill();
7279
7331
  // Central vein
7280
- ctx.strokeStyle = color === '#00ED64' ? '#00A35C' : color;
7332
+ ctx.strokeStyle = color === '#00D4AA' ? '#009E80' : color;
7281
7333
  ctx.lineWidth = 1;
7282
7334
  ctx.beginPath();
7283
7335
  ctx.moveTo(x, y - size);
@@ -7312,7 +7364,7 @@ function startVSI(canvas, onExit) {
7312
7364
  let waveQueued = false;
7313
7365
 
7314
7366
  const keys = {};
7315
- const keyDown = (e) => { keys[e.key] = true; if (['ArrowLeft','ArrowRight',' ','Escape'].includes(e.key)) e.preventDefault(); if (e.key === 'Escape') { cleanup(); onExit(); } };
7367
+ const keyDown = (e) => { keys[e.key] = true; if (['ArrowLeft','ArrowRight',' ','Escape'].includes(e.key)) e.preventDefault(); if (e.key === 'Escape') { sendVSITelemetry('vsi_game_over', { score, wave, durationMs: Date.now() - _vsiGameStartTime, livesRemaining: lives, exitReason: 'esc' }); cleanup(); onExit(); } };
7316
7368
  const keyUp = (e) => { keys[e.key] = false; };
7317
7369
  document.addEventListener('keydown', keyDown);
7318
7370
  document.addEventListener('keyup', keyUp);
@@ -7353,7 +7405,7 @@ function startVSI(canvas, onExit) {
7353
7405
  }
7354
7406
 
7355
7407
  function addExplosion(x, y, sim) {
7356
- const color = sim > 0.7 ? '#00ED64' : sim > 0.4 ? '#FFC010' : '#FF6960';
7408
+ const color = sim > 0.7 ? '#00D4AA' : sim > 0.4 ? '#FFC010' : '#FF6960';
7357
7409
  for (let i = 0; i < 8; i++) {
7358
7410
  const angle = (Math.PI * 2 / 8) * i + Math.random() * 0.5;
7359
7411
  const speed = 1.5 + Math.random() * 2;
@@ -7445,7 +7497,7 @@ function startVSI(canvas, onExit) {
7445
7497
  if (enemies[i].y > H - 50) {
7446
7498
  enemies.splice(i, 1);
7447
7499
  lives--;
7448
- if (lives <= 0) gameOver = true;
7500
+ if (lives <= 0) { gameOver = true; sendVSITelemetry('vsi_game_over', { score, wave, durationMs: Date.now() - _vsiGameStartTime, livesRemaining: 0, exitReason: 'death' }); }
7449
7501
  }
7450
7502
  }
7451
7503
 
@@ -7482,16 +7534,16 @@ function startVSI(canvas, onExit) {
7482
7534
  }
7483
7535
 
7484
7536
  // Ship (Voyage AI leaf)
7485
- drawLeaf(ctx, ship.x, H - 32, 12, '#00ED64');
7537
+ drawLeaf(ctx, ship.x, H - 32, 12, '#00D4AA');
7486
7538
  // Ship label
7487
- ctx.fillStyle = '#00ED64';
7539
+ ctx.fillStyle = '#00D4AA';
7488
7540
  ctx.font = '9px monospace';
7489
7541
  ctx.textAlign = 'center';
7490
7542
  ctx.fillText('voyage', ship.x, H - 14);
7491
7543
 
7492
7544
  // Bullets (small leaf shapes)
7493
7545
  for (const b of bullets) {
7494
- drawLeaf(ctx, b.x, b.y, 3, '#00ED64');
7546
+ drawLeaf(ctx, b.x, b.y, 3, '#00D4AA');
7495
7547
  }
7496
7548
 
7497
7549
  // Boss
@@ -7516,7 +7568,7 @@ function startVSI(canvas, onExit) {
7516
7568
  for (const e of enemies) {
7517
7569
  ctx.fillStyle = 'rgba(61,79,88,0.6)';
7518
7570
  ctx.fillRect(e.x - e.w / 2, e.y - e.h / 2, e.w, e.h);
7519
- ctx.strokeStyle = '#0498EC';
7571
+ ctx.strokeStyle = '#40E0FF';
7520
7572
  ctx.lineWidth = 1;
7521
7573
  ctx.strokeRect(e.x - e.w / 2, e.y - e.h / 2, e.w, e.h);
7522
7574
  ctx.fillStyle = '#E8EDEB';
@@ -7556,19 +7608,19 @@ function startVSI(canvas, onExit) {
7556
7608
 
7557
7609
  // Lives (leaf icons)
7558
7610
  for (let i = 0; i < lives; i++) {
7559
- drawLeaf(ctx, W - 20 - (i * 18), 15, 6, '#00ED64');
7611
+ drawLeaf(ctx, W - 20 - (i * 18), 15, 6, '#00D4AA');
7560
7612
  }
7561
7613
 
7562
7614
  // Title with Voyage AI branding
7563
- ctx.fillStyle = '#00ED64';
7615
+ ctx.fillStyle = '#00D4AA';
7564
7616
  ctx.font = 'bold 10px monospace';
7565
7617
  ctx.textAlign = 'center';
7566
7618
  ctx.fillText('VECTOR SPACE INVADERS', W / 2, H - 4);
7567
7619
 
7568
7620
  // Voyage AI watermark (top right corner)
7569
7621
  ctx.globalAlpha = 0.3;
7570
- drawLeaf(ctx, W - 25, 25, 8, '#00ED64');
7571
- ctx.fillStyle = '#00ED64';
7622
+ drawLeaf(ctx, W - 25, 25, 8, '#00D4AA');
7623
+ ctx.fillStyle = '#00D4AA';
7572
7624
  ctx.font = 'bold 9px monospace';
7573
7625
  ctx.textAlign = 'right';
7574
7626
  ctx.fillText('VOYAGE AI', W - 38, 28);
@@ -7578,7 +7630,7 @@ function startVSI(canvas, onExit) {
7578
7630
  if (gameOver) {
7579
7631
  ctx.fillStyle = 'rgba(0,30,43,0.8)';
7580
7632
  ctx.fillRect(0, 0, W, H);
7581
- ctx.fillStyle = '#00ED64';
7633
+ ctx.fillStyle = '#00D4AA';
7582
7634
  ctx.font = 'bold 28px monospace';
7583
7635
  ctx.textAlign = 'center';
7584
7636
  ctx.fillText('GAME OVER', W / 2, H / 2 - 30);
@@ -8141,13 +8193,13 @@ init();
8141
8193
  .bug-error{padding:10px 12px;background:rgba(255,107,107,0.15);border:1px solid rgba(255,107,107,0.3);border-radius:8px;color:var(--error);font-size:13px;margin-bottom:16px}
8142
8194
  .bug-actions{display:flex;gap:12px}
8143
8195
  .bug-actions button{flex:1;padding:12px 16px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;border:none}
8144
- .bug-actions .primary{background:linear-gradient(135deg,var(--accent),#00c853);color:#000}
8145
- .bug-actions .primary:hover{box-shadow:0 4px 12px rgba(0,237,100,0.4)}
8196
+ .bug-actions .primary{background:linear-gradient(135deg,var(--accent),#5CE8CC);color:#000}
8197
+ .bug-actions .primary:hover{box-shadow:0 4px 12px rgba(0,212,170,0.4)}
8146
8198
  .bug-actions .primary:disabled{opacity:0.6;cursor:not-allowed}
8147
8199
  .bug-actions .secondary{background:rgba(255,255,255,0.1);color:var(--text);border:1px solid var(--border)}
8148
8200
  .bug-actions .secondary:hover{background:rgba(255,255,255,0.15)}
8149
8201
  .bug-success{padding:40px 20px;text-align:center}
8150
- .bug-success .icon{width:60px;height:60px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#00c853);color:#000;font-size:32px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px}
8202
+ .bug-success .icon{width:60px;height:60px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#5CE8CC);color:#000;font-size:32px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px}
8151
8203
  .bug-success h3{margin:0 0 12px;color:var(--accent-text);font-size:20px}
8152
8204
  .bug-success p{margin:8px 0;color:var(--text-muted)}
8153
8205
  .bug-success code{background:rgba(255,255,255,0.1);padding:4px 8px;border-radius:4px;font-size:12px;color:var(--accent)}
@@ -8203,7 +8255,7 @@ init();
8203
8255
 
8204
8256
  <script>
8205
8257
  (function() {
8206
- const BUG_API = 'https://vai.mlynn.org/api/bugs';
8258
+ const BUG_API = 'https://vaicli.com/api/bugs';
8207
8259
  const GITHUB_URL = 'https://github.com/mrlynn/voyageai-cli/issues/new';
8208
8260
 
8209
8261
  function getEnv() {