winter-super-cli 2026.6.23 → 2026.6.26

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.
@@ -0,0 +1,114 @@
1
+ export const CHROME_DEVTOOLS_MCP_NAME = 'chrome-devtools';
2
+
3
+ const CHROME_DEVTOOLS_PACKAGE = 'chrome-devtools-mcp@latest';
4
+ const CHROME_DEVTOOLS_SOURCE = 'https://github.com/ChromeDevTools/chrome-devtools-mcp';
5
+
6
+ const CHROME_DEVTOOLS_FLAGS_WITH_VALUES = new Set([
7
+ '--browser-url',
8
+ '--channel',
9
+ '--executablePath',
10
+ '--logFile',
11
+ '--viewport',
12
+ '--proxy-server',
13
+ ]);
14
+
15
+ const CHROME_DEVTOOLS_BOOLEAN_FLAGS = new Set([
16
+ '--headless',
17
+ '--isolated',
18
+ '--acceptInsecureCerts',
19
+ '--help',
20
+ ]);
21
+
22
+ export function normalizeMcpPresetName(name = '') {
23
+ return String(name || '').trim().toLowerCase();
24
+ }
25
+
26
+ export function isChromeDevtoolsPreset(name = '') {
27
+ return ['chrome-devtools', 'chromedevtools', 'chrome', 'devtools', 'cdp'].includes(normalizeMcpPresetName(name));
28
+ }
29
+
30
+ export function buildChromeDevtoolsArgs(options = []) {
31
+ const input = Array.isArray(options) ? [...options] : [];
32
+ const args = ['-y', CHROME_DEVTOOLS_PACKAGE];
33
+
34
+ for (let index = 0; index < input.length; index += 1) {
35
+ const flag = input[index];
36
+ if (!String(flag || '').startsWith('--')) continue;
37
+
38
+ if (CHROME_DEVTOOLS_BOOLEAN_FLAGS.has(flag)) {
39
+ args.push(flag);
40
+ continue;
41
+ }
42
+
43
+ if (CHROME_DEVTOOLS_FLAGS_WITH_VALUES.has(flag)) {
44
+ const value = input[index + 1];
45
+ if (value === undefined || String(value).startsWith('--')) {
46
+ throw new Error(`Missing value for ${flag}`);
47
+ }
48
+ args.push(flag, String(value));
49
+ index += 1;
50
+ }
51
+ }
52
+
53
+ return args;
54
+ }
55
+
56
+ export function createChromeDevtoolsMcpServer(options = [], platform = process.platform, env = process.env) {
57
+ const npxArgs = buildChromeDevtoolsArgs(options);
58
+ const common = {
59
+ name: CHROME_DEVTOOLS_MCP_NAME,
60
+ enabled: true,
61
+ requestTimeoutMs: 60000,
62
+ metadata: {
63
+ preset: CHROME_DEVTOOLS_MCP_NAME,
64
+ source: CHROME_DEVTOOLS_SOURCE,
65
+ purpose: 'Chrome DevTools MCP for live browser automation, debugging, screenshots, console, network, and performance traces.',
66
+ },
67
+ };
68
+
69
+ if (platform === 'win32') {
70
+ return {
71
+ ...common,
72
+ command: 'cmd',
73
+ args: ['/c', 'npx', ...npxArgs],
74
+ env: {
75
+ SystemRoot: env.SystemRoot || 'C:\\Windows',
76
+ PROGRAMFILES: env.PROGRAMFILES || 'C:\\Program Files',
77
+ },
78
+ };
79
+ }
80
+
81
+ return {
82
+ ...common,
83
+ command: 'npx',
84
+ args: npxArgs,
85
+ };
86
+ }
87
+
88
+ export function getMcpPreset(name, options = []) {
89
+ if (isChromeDevtoolsPreset(name)) {
90
+ return createChromeDevtoolsMcpServer(options);
91
+ }
92
+ throw new Error(`Unknown MCP preset: ${name}`);
93
+ }
94
+
95
+ export function ensureMcpConfigShape(config = {}) {
96
+ config.mcp = config.mcp || { servers: [] };
97
+ config.mcp.servers = Array.isArray(config.mcp.servers) ? config.mcp.servers : [];
98
+ config.permissions = config.permissions || { allowlist: {} };
99
+ config.permissions.allowlist = config.permissions.allowlist || {};
100
+ config.permissions.allowlist.tools = config.permissions.allowlist.tools || [];
101
+ config.permissions.allowlist.commands = config.permissions.allowlist.commands || [];
102
+ config.permissions.allowlist.mcpServers = config.permissions.allowlist.mcpServers || [];
103
+ return config;
104
+ }
105
+
106
+ export function upsertMcpServer(config, server) {
107
+ ensureMcpConfigShape(config);
108
+ config.mcp.servers = config.mcp.servers.filter(item => item.name !== server.name);
109
+ config.mcp.servers.push(server);
110
+ config.permissions.allowlist.mcpServers = [
111
+ ...new Set([...(config.permissions.allowlist.mcpServers || []), server.name]),
112
+ ];
113
+ return config;
114
+ }
package/src/rag/cli.js ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * RAG CLI commands: /rag query, /rag index, /rag status, /rag reset.
3
+ */
4
+
5
+ import { RAGEngine } from './rag-engine.js';
6
+ import { colors, statusIcons } from '../cli/snowflake-logo.js';
7
+
8
+ let ragEngine = null;
9
+
10
+ export function initRAG(config) {
11
+ ragEngine = new RAGEngine(config);
12
+ return ragEngine;
13
+ }
14
+
15
+ export function getRAGEngine() {
16
+ return ragEngine;
17
+ }
18
+
19
+ async function getEngine(config) {
20
+ if (!ragEngine) {
21
+ ragEngine = new RAGEngine(config);
22
+ await ragEngine.init();
23
+ }
24
+ return ragEngine;
25
+ }
26
+
27
+ export async function handleRagCommand(query, config) {
28
+ const engine = await getEngine(config);
29
+ const results = await engine.search(query, { topK: 5, threshold: 0.5 });
30
+
31
+ if (results.count === 0) {
32
+ return {
33
+ success: true,
34
+ message: 'No relevant documents found. Try running /rag index first.',
35
+ results: [],
36
+ };
37
+ }
38
+
39
+ return {
40
+ success: true,
41
+ query: results.query,
42
+ count: results.count,
43
+ results: results.results.map(doc => ({
44
+ id: doc.id,
45
+ source: doc.source,
46
+ score: doc.score.toFixed(3),
47
+ text: doc.text.slice(0, 500) + (doc.text.length > 500 ? '...' : ''),
48
+ })),
49
+ };
50
+ }
51
+
52
+ export async function handleRagCommandFromRepl(repl, args = []) {
53
+ const config = repl.config ? await repl.config.load() : {};
54
+ config.projectPath = repl.projectPath;
55
+ await printRagCommand(args, config);
56
+ }
57
+
58
+ export async function handleRagCommandFromParser(parser, args = []) {
59
+ const config = parser.config ? await parser.config.load() : {};
60
+ config.projectPath = parser.projectPath || process.cwd();
61
+ await printRagCommand(args, config);
62
+ }
63
+
64
+ async function printRagCommand(args, config) {
65
+ const [action = 'query', ...rest] = args;
66
+ const normalizedAction = String(action || 'query').toLowerCase();
67
+
68
+ switch (normalizedAction) {
69
+ case 'query':
70
+ case 'search': {
71
+ const query = rest.join(' ');
72
+ if (!query) {
73
+ console.log(`${colors.yellow}Usage: /rag query <search-term>${colors.reset}`);
74
+ return;
75
+ }
76
+
77
+ const result = await handleRagCommand(query, config);
78
+ console.log(`${colors.cyan}RAG Search: "${query}"${colors.reset}`);
79
+ console.log(`${colors.dim}Found ${result.count || 0} results${colors.reset}`);
80
+ result.results?.forEach((r, i) => {
81
+ console.log(`\n${colors.green}[${i + 1}] ${r.source}${colors.reset} ${colors.dim}(score: ${r.score})${colors.reset}`);
82
+ console.log(`${r.text}\n`);
83
+ });
84
+ if (result.message) console.log(`${colors.dim}${result.message}${colors.reset}`);
85
+ return;
86
+ }
87
+
88
+ case 'index': {
89
+ console.log(`${colors.cyan}RAG Indexing...${colors.reset}`);
90
+ const force = rest.includes('--force');
91
+ const result = await handleRagIndexCommand(config, { force });
92
+ const color = result.success ? colors.green : colors.yellow;
93
+ const icon = result.success ? `${statusIcons.success} ` : '';
94
+ console.log(`${color}${icon}${result.message}${colors.reset}`);
95
+ return;
96
+ }
97
+
98
+ case 'status': {
99
+ const result = await handleRagStatusCommand(config);
100
+ console.log(`${colors.cyan}RAG Status:${colors.reset}`);
101
+ console.log(` Enabled: ${result.enabled}`);
102
+ console.log(` Documents: ${result.totalDocuments}`);
103
+ console.log(` Dimension: ${result.dimension}`);
104
+ console.log(` Provider: ${result.provider}`);
105
+ console.log(` Store: ${result.storeFile || 'default'}`);
106
+ return;
107
+ }
108
+
109
+ case 'reset': {
110
+ console.log(`${colors.cyan}RAG Resetting...${colors.reset}`);
111
+ const result = await handleRagResetCommand(config);
112
+ console.log(`${colors.green}${statusIcons.success} ${result.message}${colors.reset}`);
113
+ return;
114
+ }
115
+
116
+ case 'help':
117
+ default:
118
+ console.log(`${colors.cyan}RAG Commands:${colors.reset}`);
119
+ console.log(' /rag query <term> - Semantic search');
120
+ console.log(' /rag index [--force] - Index codebase');
121
+ console.log(' /rag status - Show RAG status');
122
+ console.log(' /rag reset - Clear vector store');
123
+ }
124
+ }
125
+
126
+ export async function handleRagIndexCommand(config, options = {}) {
127
+ const engine = await getEngine(config);
128
+ const stats = engine.getStatus();
129
+
130
+ if (stats.totalDocuments > 0 && !options.force) {
131
+ return {
132
+ success: false,
133
+ message: `Already indexed ${stats.totalDocuments} documents. Use /rag index --force to re-index.`,
134
+ };
135
+ }
136
+
137
+ if (options.force) {
138
+ await engine.reset();
139
+ }
140
+
141
+ const result = await engine.indexCodebase(options);
142
+ return {
143
+ success: true,
144
+ message: `Indexed ${result.totalDocuments} documents from ${result.totalFiles} files.`,
145
+ ...result,
146
+ };
147
+ }
148
+
149
+ export async function handleRagResetCommand(config) {
150
+ const engine = await getEngine(config);
151
+ await engine.reset();
152
+ return {
153
+ success: true,
154
+ message: 'Vector store cleared.',
155
+ };
156
+ }
157
+
158
+ export async function handleRagStatusCommand(config) {
159
+ const engine = await getEngine(config);
160
+ const status = engine.getStatus();
161
+
162
+ return {
163
+ success: true,
164
+ enabled: status.enabled,
165
+ totalDocuments: status.totalDocuments,
166
+ dimension: status.dimension,
167
+ provider: status.provider,
168
+ createdAt: status.createdAt,
169
+ updatedAt: status.updatedAt,
170
+ storeFile: status.storeFile,
171
+ };
172
+ }
173
+
174
+ export function handleRagEnableCommand(enabled) {
175
+ if (ragEngine) {
176
+ ragEngine.setEnabled(enabled);
177
+ }
178
+
179
+ return {
180
+ success: true,
181
+ message: `RAG ${enabled ? 'enabled' : 'disabled'}.`,
182
+ };
183
+ }
184
+
185
+ export default {
186
+ initRAG,
187
+ getRAGEngine,
188
+ handleRagCommand,
189
+ handleRagCommandFromRepl,
190
+ handleRagCommandFromParser,
191
+ handleRagIndexCommand,
192
+ handleRagResetCommand,
193
+ handleRagStatusCommand,
194
+ handleRagEnableCommand,
195
+ };
@@ -0,0 +1,183 @@
1
+ /**
2
+ * ❄ EMBEDDING PROVIDERS ❄
3
+ * Abstraction layer for embedding models (Qwen, MiniMax, Ollama, OpenAI, custom)
4
+ */
5
+
6
+ import { getProviderPreset } from '../ai/provider-adapters.js';
7
+
8
+ const DEFAULT_EMBED_MODEL = {
9
+ qwen: 'text-embedding-v3',
10
+ minimax: 'embedding-2',
11
+ ollama: 'nomic-embed-text',
12
+ openai: 'text-embedding-3-small',
13
+ };
14
+
15
+ /**
16
+ * Get embedding provider config
17
+ * @param {string} providerName - Provider name (qwen, minimax, ollama, openai, custom)
18
+ * @param {object} config - Full Winter config
19
+ * @returns {object} Embedding config
20
+ */
21
+ export function getEmbeddingConfig(providerName, config) {
22
+ const preset = getProviderPreset(providerName);
23
+ const configuredProvider = config?.providers?.[providerName] || config?.[providerName] || {};
24
+
25
+ if (!preset) {
26
+ // For custom providers, use the config directly
27
+ const customProvider = configuredProvider;
28
+ if (customProvider) {
29
+ return {
30
+ ...customProvider,
31
+ embedModel: customProvider.embedModel || DEFAULT_EMBED_MODEL[providerName] || 'text-embedding-3-small',
32
+ embedProvider: providerName,
33
+ };
34
+ }
35
+ throw new Error(`Provider '${providerName}' not found in config`);
36
+ }
37
+
38
+ const model = configuredProvider.embedModel
39
+ || DEFAULT_EMBED_MODEL[providerName]
40
+ || preset.embedModel
41
+ || preset.model;
42
+
43
+ return {
44
+ ...preset,
45
+ ...configuredProvider,
46
+ embedModel: model,
47
+ embedProvider: providerName,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Create embedding request body for a provider
53
+ * @param {string} providerName - Provider name
54
+ * @param {string} text - Text to embed
55
+ * @param {object} cfg - Provider config
56
+ * @returns {object} Request body
57
+ */
58
+ export function createEmbeddingRequest(providerName, text, cfg) {
59
+ const normalizedText = text.replace(/\n/g, ' ').trim();
60
+
61
+ switch (providerName) {
62
+ case 'qwen':
63
+ return {
64
+ model: cfg.embedModel,
65
+ input: normalizedText,
66
+ };
67
+
68
+ case 'minimax':
69
+ return {
70
+ model: cfg.embedModel,
71
+ text: normalizedText,
72
+ };
73
+
74
+ case 'ollama':
75
+ return {
76
+ model: cfg.embedModel,
77
+ prompt: normalizedText,
78
+ };
79
+
80
+ case 'openai':
81
+ case 'custom':
82
+ default:
83
+ return {
84
+ model: cfg.embedModel,
85
+ input: normalizedText,
86
+ };
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Parse embedding response from provider
92
+ * @param {string} providerName - Provider name
93
+ * @param {object} data - Response data
94
+ * @returns {number[]} Embedding vector
95
+ */
96
+ export function parseEmbeddingResponse(providerName, data) {
97
+ switch (providerName) {
98
+ case 'qwen':
99
+ return data.output?.embeddings?.[0]?.embedding || data.data?.[0]?.embedding;
100
+
101
+ case 'minimax':
102
+ return data.data?.embedding || data.data?.[0]?.embedding;
103
+
104
+ case 'ollama':
105
+ return data.embedding;
106
+
107
+ case 'openai':
108
+ case 'custom':
109
+ default:
110
+ return data.data?.[0]?.embedding;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Get base URL for embedding provider
116
+ * @param {string} providerName - Provider name
117
+ * @param {object} cfg - Provider config
118
+ * @returns {string} Base URL
119
+ */
120
+ export function getEmbeddingBaseURL(providerName, cfg) {
121
+ const baseURL = cfg.baseURL;
122
+
123
+ if (baseURL) {
124
+ const trimmed = String(baseURL).replace(/\/+$/, '');
125
+ if (/\/embeddings$/i.test(trimmed)) return trimmed;
126
+ if (providerName === 'ollama' && !/\/v1$/i.test(trimmed)) return `${trimmed}/api/embeddings`;
127
+ return `${trimmed}/embeddings`;
128
+ }
129
+
130
+ const EMBED_ENDPOINTS = {
131
+ qwen: 'https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings',
132
+ minimax: 'https://api.minimax.chat/v1/text/embeddings',
133
+ ollama: 'http://localhost:11434/api/embeddings',
134
+ openai: 'https://api.openai.com/v1/embeddings',
135
+ };
136
+
137
+ return EMBED_ENDPOINTS[providerName] || EMBED_ENDPOINTS.openai;
138
+ }
139
+
140
+ /**
141
+ * Get headers for embedding request
142
+ * @param {string} providerName - Provider name
143
+ * @param {object} cfg - Provider config
144
+ * @returns {object} Headers
145
+ */
146
+ export function getEmbeddingHeaders(providerName, cfg) {
147
+ const headers = {
148
+ 'Content-Type': 'application/json',
149
+ };
150
+
151
+ switch (providerName) {
152
+ case 'qwen':
153
+ headers['Authorization'] = `Bearer ${cfg.apiKey}`;
154
+ break;
155
+
156
+ case 'minimax':
157
+ headers['Authorization'] = `Bearer ${cfg.apiKey}`;
158
+ break;
159
+
160
+ case 'ollama':
161
+ // No auth needed for local
162
+ break;
163
+
164
+ case 'openai':
165
+ headers['Authorization'] = `Bearer ${cfg.apiKey}`;
166
+ break;
167
+
168
+ case 'custom':
169
+ if (cfg.apiKey) headers['Authorization'] = `Bearer ${cfg.apiKey}`;
170
+ if (cfg.authToken) headers['Authorization'] = `Bearer ${cfg.authToken}`;
171
+ break;
172
+ }
173
+
174
+ return headers;
175
+ }
176
+
177
+ export default {
178
+ getEmbeddingConfig,
179
+ createEmbeddingRequest,
180
+ parseEmbeddingResponse,
181
+ getEmbeddingBaseURL,
182
+ getEmbeddingHeaders,
183
+ };
@@ -0,0 +1,187 @@
1
+ /**
2
+ * ❄ RAG ENGINE ❄
3
+ * Core RAG engine - orchestrates retrieval + augmentation
4
+ */
5
+
6
+ import { VectorStore } from './vector-store.js';
7
+ import { Retriever } from './retriever.js';
8
+ import { getEmbeddingConfig } from './embeddings.js';
9
+ import { glob } from 'glob';
10
+ import { promises as fs } from 'fs';
11
+ import path from 'path';
12
+
13
+ /**
14
+ * RAG Engine class
15
+ */
16
+ export class RAGEngine {
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.projectPath = path.resolve(config.projectPath || config.project?.current || process.cwd());
20
+ this.vectorStore = new VectorStore(config);
21
+ this.retriever = new Retriever(config, this.vectorStore);
22
+ this.enabled = config.rag?.enabled !== false;
23
+ }
24
+
25
+ /**
26
+ * Initialize RAG engine
27
+ */
28
+ async init() {
29
+ await this.vectorStore.init();
30
+ }
31
+
32
+ /**
33
+ * Check if RAG is enabled
34
+ */
35
+ isEnabled() {
36
+ return this.enabled;
37
+ }
38
+
39
+ /**
40
+ * Enable/disable RAG
41
+ */
42
+ setEnabled(enabled) {
43
+ this.enabled = enabled;
44
+ }
45
+
46
+ /**
47
+ * Index codebase
48
+ * @param {object} options - Indexing options
49
+ */
50
+ async indexCodebase(options = {}) {
51
+ const {
52
+ patterns = ['src/**/*.js', 'src/**/*.ts', '*.md', 'docs/**/*.md'],
53
+ exclude = ['node_modules/**', 'dist/**', 'build/**', '.git/**', '.winter/**', 'resources/local/**', '**/*.test.js', '**/*.min.js'],
54
+ chunkSize = 1000,
55
+ chunkOverlap = 200,
56
+ } = options;
57
+
58
+ const files = await glob(patterns, {
59
+ cwd: this.projectPath,
60
+ ignore: exclude,
61
+ absolute: true,
62
+ nodir: true,
63
+ });
64
+
65
+ const docs = [];
66
+
67
+ for (const file of files) {
68
+ try {
69
+ const content = await fs.readFile(file, 'utf-8');
70
+
71
+ // Chunk large files
72
+ if (content.length <= chunkSize) {
73
+ docs.push({
74
+ id: `file_${path.basename(file)}`,
75
+ text: content,
76
+ source: path.relative(this.projectPath, file).replace(/\\/g, '/'),
77
+ metadata: {
78
+ type: 'file',
79
+ name: path.basename(file),
80
+ ext: path.extname(file),
81
+ },
82
+ });
83
+ } else {
84
+ // Split into chunks
85
+ const chunks = this.chunkText(content, chunkSize, chunkOverlap);
86
+
87
+ chunks.forEach((chunk, idx) => {
88
+ docs.push({
89
+ id: `chunk_${path.basename(file)}_${idx}`,
90
+ text: chunk,
91
+ source: path.relative(this.projectPath, file).replace(/\\/g, '/'),
92
+ metadata: {
93
+ type: 'chunk',
94
+ name: path.basename(file),
95
+ ext: path.extname(file),
96
+ chunkIndex: idx,
97
+ totalChunks: chunks.length,
98
+ },
99
+ });
100
+ });
101
+ }
102
+ } catch (err) {
103
+ console.warn(`Failed to read ${file}: ${err.message}`);
104
+ }
105
+ }
106
+
107
+ const ids = await this.vectorStore.addDocuments(docs);
108
+
109
+ return {
110
+ totalFiles: files.length,
111
+ totalDocuments: ids.length,
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Chunk text with overlap
117
+ * @param {string} text - Text to chunk
118
+ * @param {number} chunkSize - Chunk size in chars
119
+ * @param {number} overlap - Overlap between chunks
120
+ * @returns {string[]} Chunks
121
+ */
122
+ chunkText(text, chunkSize, overlap) {
123
+ const chunks = [];
124
+ const lines = text.split('\n');
125
+ let currentChunk = '';
126
+
127
+ for (const line of lines) {
128
+ if (currentChunk.length + line.length > chunkSize && currentChunk.length > 0) {
129
+ chunks.push(currentChunk.trim());
130
+
131
+ // Keep overlap
132
+ const overlapLines = currentChunk.split('\n').slice(-Math.floor(overlap / 50));
133
+ currentChunk = overlapLines.join('\n') + '\n' + line;
134
+ } else {
135
+ currentChunk += line + '\n';
136
+ }
137
+ }
138
+
139
+ if (currentChunk.trim()) {
140
+ chunks.push(currentChunk.trim());
141
+ }
142
+
143
+ return chunks;
144
+ }
145
+
146
+ /**
147
+ * Search
148
+ * @param {string} query - Query
149
+ * @param {object} options - Search options
150
+ */
151
+ async search(query, options = {}) {
152
+ return this.retriever.retrieve(query, options);
153
+ }
154
+
155
+ /**
156
+ * Augment prompt with context
157
+ * @param {string} prompt - Original prompt
158
+ * @param {object} options - Augmentation options
159
+ */
160
+ async augmentPrompt(prompt, options = {}) {
161
+ if (!this.enabled) {
162
+ return prompt;
163
+ }
164
+
165
+ return this.retriever.augmentPrompt(prompt, options);
166
+ }
167
+
168
+ /**
169
+ * Get RAG status
170
+ */
171
+ getStatus() {
172
+ const stats = this.vectorStore.getStats();
173
+ return {
174
+ enabled: this.enabled,
175
+ ...stats,
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Reset vector store
181
+ */
182
+ async reset() {
183
+ await this.vectorStore.clear();
184
+ }
185
+ }
186
+
187
+ export default RAGEngine;