voyageai-cli 1.23.1 → 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 (46) hide show
  1. package/README.md +64 -0
  2. package/package.json +1 -1
  3. package/src/cli.js +2 -0
  4. package/src/commands/about.js +1 -1
  5. package/src/commands/bug.js +1 -1
  6. package/src/commands/mcp-server.js +74 -0
  7. package/src/commands/playground.js +31 -0
  8. package/src/commands/scaffold.js +23 -1
  9. package/src/commands/workflow.js +336 -0
  10. package/src/lib/explanations.js +53 -0
  11. package/src/lib/scaffold-structure.js +8 -9
  12. package/src/lib/telemetry.js +1 -1
  13. package/src/lib/template-engine.js +240 -0
  14. package/src/lib/templates/nextjs/README.md.tpl +78 -55
  15. package/src/lib/templates/nextjs/favicon.svg.tpl +11 -0
  16. package/src/lib/templates/nextjs/footer.jsx.tpl +49 -0
  17. package/src/lib/templates/nextjs/layout.jsx.tpl +16 -10
  18. package/src/lib/templates/nextjs/lib-mongo.js.tpl +5 -5
  19. package/src/lib/templates/nextjs/lib-voyage.js.tpl +13 -8
  20. package/src/lib/templates/nextjs/navbar.jsx.tpl +98 -0
  21. package/src/lib/templates/nextjs/page-home.jsx.tpl +201 -0
  22. package/src/lib/templates/nextjs/page-search.jsx.tpl +184 -82
  23. package/src/lib/templates/nextjs/theme-registry.jsx.tpl +51 -0
  24. package/src/lib/templates/nextjs/theme.js.tpl +138 -65
  25. package/src/lib/templates/nextjs/vai-logo-256.png +0 -0
  26. package/src/lib/workflow-utils.js +65 -0
  27. package/src/lib/workflow.js +1259 -0
  28. package/src/mcp/install.js +201 -0
  29. package/src/mcp/tools/management.js +1 -60
  30. package/src/playground/icons/dark/128.png +0 -0
  31. package/src/playground/icons/dark/16.png +0 -0
  32. package/src/playground/icons/dark/256.png +0 -0
  33. package/src/playground/icons/dark/32.png +0 -0
  34. package/src/playground/icons/dark/64.png +0 -0
  35. package/src/playground/icons/light/128.png +0 -0
  36. package/src/playground/icons/light/16.png +0 -0
  37. package/src/playground/icons/light/256.png +0 -0
  38. package/src/playground/icons/light/32.png +0 -0
  39. package/src/playground/icons/light/64.png +0 -0
  40. package/src/playground/icons/watermark.png +0 -0
  41. package/src/playground/index.html +125 -73
  42. package/src/workflows/consistency-check.json +64 -0
  43. package/src/workflows/cost-analysis.json +69 -0
  44. package/src/workflows/multi-collection-search.json +80 -0
  45. package/src/workflows/research-and-summarize.json +46 -0
  46. package/src/workflows/smart-ingest.json +63 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Theme Registry — light/dark toggle with localStorage persistence
3
+ * Generated by vai v{{vaiVersion}} on {{generatedAt}}
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import { createContext, useContext, useMemo, useState, useEffect } from 'react';
9
+ import { ThemeProvider } from '@mui/material/styles';
10
+ import CssBaseline from '@mui/material/CssBaseline';
11
+ import { lightTheme, darkTheme } from '@/lib/theme';
12
+
13
+ const ColorModeContext = createContext({ toggle: () => {}, mode: 'light' });
14
+
15
+ export function useColorMode() {
16
+ return useContext(ColorModeContext);
17
+ }
18
+
19
+ export function ThemeRegistry({ children }) {
20
+ const [mode, setMode] = useState('light');
21
+
22
+ useEffect(() => {
23
+ const stored = localStorage.getItem('vai-color-mode');
24
+ if (stored === 'dark' || stored === 'light') {
25
+ setMode(stored);
26
+ } else if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
27
+ setMode('dark');
28
+ }
29
+ }, []);
30
+
31
+ const colorMode = useMemo(() => ({
32
+ mode,
33
+ toggle: () =>
34
+ setMode((prev) => {
35
+ const next = prev === 'light' ? 'dark' : 'light';
36
+ localStorage.setItem('vai-color-mode', next);
37
+ return next;
38
+ }),
39
+ }), [mode]);
40
+
41
+ const theme = mode === 'dark' ? darkTheme : lightTheme;
42
+
43
+ return (
44
+ <ColorModeContext.Provider value={colorMode}>
45
+ <ThemeProvider theme={theme}>
46
+ <CssBaseline />
47
+ {children}
48
+ </ThemeProvider>
49
+ </ColorModeContext.Provider>
50
+ );
51
+ }
@@ -1,84 +1,157 @@
1
1
  /**
2
- * MUI Theme Configuration
2
+ * MUI Theme Configuration — vai branded
3
3
  * Generated by vai v{{vaiVersion}} on {{generatedAt}}
4
4
  */
5
5
 
6
6
  'use client';
7
7
 
8
- import { createTheme } from '@mui/material/styles';
8
+ import { createTheme, responsiveFontSizes } from '@mui/material/styles';
9
9
 
10
- // MongoDB-inspired color palette
11
- const palette = {
12
- primary: {
13
- main: '#00ED64', // MongoDB Green
14
- light: '#4DFF94',
15
- dark: '#00B84A',
16
- contrastText: '#000',
17
- },
18
- secondary: {
19
- main: '#001E2B', // MongoDB Dark
20
- light: '#1C3A4B',
21
- dark: '#000F17',
22
- contrastText: '#fff',
23
- },
24
- background: {
25
- default: '#FAFAFA',
26
- paper: '#FFFFFF',
27
- },
28
- text: {
29
- primary: '#001E2B',
30
- secondary: '#5C6C75',
31
- },
10
+ /* ── colour tokens ─────────────────────────────── */
11
+ const brand = {
12
+ green: '#00ED64',
13
+ greenLight: '#71F6A0',
14
+ greenDark: '#00B84A',
15
+ navy: '#001E2B',
16
+ navyLight: '#1C3A4B',
17
+ slate: '#3D4F58',
18
+ mist: '#E3FCF7',
19
+ white: '#FFFFFF',
20
+ offWhite: '#F9FBFA',
21
+ grey100: '#F5F6F7',
22
+ grey300: '#C1C7C6',
23
+ grey500: '#889397',
24
+ grey700: '#5C6C75',
32
25
  };
33
26
 
34
- export const theme = createTheme({
35
- palette,
36
- typography: {
37
- fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
38
- h1: {
39
- fontWeight: 700,
40
- },
41
- h2: {
42
- fontWeight: 600,
43
- },
44
- h3: {
45
- fontWeight: 600,
46
- },
47
- h4: {
48
- fontWeight: 600,
27
+ /* ── light palette ─────────────────────────────── */
28
+ const lightPalette = {
29
+ mode: 'light',
30
+ primary: { main: brand.green, light: brand.greenLight, dark: brand.greenDark, contrastText: brand.navy },
31
+ secondary: { main: brand.navy, light: brand.navyLight, dark: '#000F17', contrastText: brand.white },
32
+ background: { default: brand.offWhite, paper: brand.white },
33
+ text: { primary: brand.navy, secondary: brand.grey700 },
34
+ divider: brand.grey300,
35
+ success: { main: brand.greenDark },
36
+ };
37
+
38
+ /* ── dark palette ──────────────────────────────── */
39
+ const darkPalette = {
40
+ mode: 'dark',
41
+ primary: { main: brand.green, light: brand.greenLight, dark: brand.greenDark, contrastText: brand.navy },
42
+ secondary: { main: '#90CAF9', light: '#BBDEFB', dark: '#42A5F5', contrastText: brand.navy },
43
+ background: { default: '#0A1117', paper: '#111D27' },
44
+ text: { primary: '#E8EDDF', secondary: '#9AABB8' },
45
+ divider: 'rgba(255,255,255,0.12)',
46
+ success: { main: brand.green },
47
+ };
48
+
49
+ function buildTheme(mode = 'light') {
50
+ let theme = createTheme({
51
+ palette: mode === 'dark' ? darkPalette : lightPalette,
52
+ typography: {
53
+ fontFamily: '"Inter", "IBM Plex Sans", "Roboto", "Helvetica Neue", Arial, sans-serif',
54
+ h1: { fontWeight: 800, letterSpacing: '-0.02em' },
55
+ h2: { fontWeight: 700, letterSpacing: '-0.01em' },
56
+ h3: { fontWeight: 700 },
57
+ h4: { fontWeight: 700 },
58
+ h5: { fontWeight: 600 },
59
+ h6: { fontWeight: 600 },
60
+ subtitle1: { fontWeight: 500, color: mode === 'dark' ? '#9AABB8' : brand.grey700 },
61
+ button: { fontWeight: 600, textTransform: 'none' },
62
+ overline: { fontWeight: 700, letterSpacing: '0.08em' },
49
63
  },
50
- },
51
- shape: {
52
- borderRadius: 8,
53
- },
54
- components: {
55
- MuiButton: {
56
- styleOverrides: {
57
- root: {
58
- textTransform: 'none',
59
- fontWeight: 600,
64
+ shape: { borderRadius: 12 },
65
+ shadows: [
66
+ 'none',
67
+ '0 1px 2px rgba(0,0,0,0.06)',
68
+ '0 1px 4px rgba(0,0,0,0.08)',
69
+ '0 2px 8px rgba(0,0,0,0.10)',
70
+ '0 4px 12px rgba(0,0,0,0.10)',
71
+ '0 6px 16px rgba(0,0,0,0.10)',
72
+ ...Array(19).fill('0 8px 24px rgba(0,0,0,0.12)'),
73
+ ],
74
+ components: {
75
+ MuiCssBaseline: {
76
+ styleOverrides: {
77
+ body: {
78
+ scrollbarWidth: 'thin',
79
+ '&::-webkit-scrollbar': { width: 6 },
80
+ '&::-webkit-scrollbar-thumb': { background: brand.grey300, borderRadius: 3 },
81
+ },
60
82
  },
61
- contained: {
62
- boxShadow: 'none',
63
- '&:hover': {
64
- boxShadow: 'none',
83
+ },
84
+ MuiButton: {
85
+ defaultProps: { disableElevation: true },
86
+ styleOverrides: {
87
+ root: { borderRadius: 8, padding: '8px 20px', fontWeight: 600 },
88
+ containedPrimary: {
89
+ color: brand.navy,
90
+ '&:hover': { background: brand.greenLight },
65
91
  },
92
+ outlined: { borderWidth: 2, '&:hover': { borderWidth: 2 } },
66
93
  },
67
94
  },
68
- },
69
- MuiCard: {
70
- styleOverrides: {
71
- root: {
72
- boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.06)',
95
+ MuiCard: {
96
+ styleOverrides: {
97
+ root: {
98
+ borderRadius: 16,
99
+ border: `1px solid ${mode === 'dark' ? 'rgba(255,255,255,0.08)' : brand.grey100}`,
100
+ transition: 'box-shadow 0.2s ease, transform 0.2s ease',
101
+ '&:hover': {
102
+ boxShadow: mode === 'dark'
103
+ ? '0 8px 24px rgba(0,237,100,0.08)'
104
+ : '0 8px 24px rgba(0,0,0,0.08)',
105
+ transform: 'translateY(-2px)',
106
+ },
107
+ },
73
108
  },
74
109
  },
75
- },
76
- MuiChip: {
77
- styleOverrides: {
78
- root: {
79
- fontWeight: 500,
110
+ MuiChip: {
111
+ styleOverrides: {
112
+ root: { fontWeight: 600, borderRadius: 8 },
113
+ colorSuccess: {
114
+ background: mode === 'dark' ? 'rgba(0,237,100,0.15)' : brand.mist,
115
+ color: mode === 'dark' ? brand.green : brand.greenDark,
116
+ },
117
+ },
118
+ },
119
+ MuiTextField: {
120
+ styleOverrides: {
121
+ root: {
122
+ '& .MuiOutlinedInput-root': {
123
+ borderRadius: 12,
124
+ '&.Mui-focused .MuiOutlinedInput-notchedOutline': {
125
+ borderColor: brand.green,
126
+ borderWidth: 2,
127
+ },
128
+ },
129
+ },
130
+ },
131
+ },
132
+ MuiAppBar: {
133
+ styleOverrides: {
134
+ root: {
135
+ background: mode === 'dark' ? '#0D1820' : brand.white,
136
+ borderBottom: `1px solid ${mode === 'dark' ? 'rgba(255,255,255,0.08)' : brand.grey100}`,
137
+ },
138
+ },
139
+ },
140
+ MuiLinearProgress: {
141
+ styleOverrides: {
142
+ root: { borderRadius: 4, height: 6 },
143
+ bar: { borderRadius: 4 },
80
144
  },
81
145
  },
82
146
  },
83
- },
84
- });
147
+ });
148
+
149
+ theme = responsiveFontSizes(theme);
150
+ return theme;
151
+ }
152
+
153
+ export const lightTheme = buildTheme('light');
154
+ export const darkTheme = buildTheme('dark');
155
+
156
+ /* default export for simple usage */
157
+ export const theme = lightTheme;
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ const { requireMongoUri } = require('./mongo');
4
+
5
+ /**
6
+ * Introspect MongoDB collections: list collections with vector index info.
7
+ * Moved from src/mcp/tools/management.js for reuse by the workflow engine.
8
+ *
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
+ const fields = idx.latestDefinition?.fields || [];
36
+ for (const f of fields) {
37
+ if (f.type === 'vector') {
38
+ hasVectorIndex = true;
39
+ embeddingField = f.path;
40
+ dimensions = f.numDimensions;
41
+ break;
42
+ }
43
+ }
44
+ if (hasVectorIndex) break;
45
+ }
46
+ } catch {
47
+ // listSearchIndexes may not be available on non-Atlas deployments
48
+ }
49
+
50
+ results.push({
51
+ name: collInfo.name,
52
+ documentCount,
53
+ hasVectorIndex,
54
+ ...(embeddingField && { embeddingField }),
55
+ ...(dimensions && { dimensions }),
56
+ });
57
+ }
58
+
59
+ return results;
60
+ } finally {
61
+ await client.close();
62
+ }
63
+ }
64
+
65
+ module.exports = { introspectCollections };