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.
- package/README.md +19 -0
- package/bin/winter.js +23 -1
- package/extensions/vscode/src/extension.js +34 -7
- package/package.json +3 -2
- package/skill.md +4 -1
- package/src/agent/agent-definitions.js +4 -4
- package/src/ai/prompts/system-prompt.js +2 -0
- package/src/cli/commands.js +32 -1
- package/src/cli/config.js +4 -3
- package/src/cli/prompt-builder.js +32 -8
- package/src/cli/repl-commands.js +6 -0
- package/src/cli/repl.js +269 -13
- package/src/cli/slash-commands.js +2 -1
- package/src/mcp/client.js +8 -6
- package/src/mcp/ide-server.js +114 -2
- package/src/mcp/presets.js +114 -0
- package/src/rag/cli.js +195 -0
- package/src/rag/embeddings.js +183 -0
- package/src/rag/rag-engine.js +187 -0
- package/src/rag/retriever.js +112 -0
- package/src/rag/vector-store.js +225 -0
- package/src/tools/executor.js +103 -4
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ❄ RETRIEVER ❄
|
|
3
|
+
* Retrieval logic for RAG - handles similarity search and result formatting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { VectorStore } from './vector-store.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Retriever class - handles document retrieval
|
|
10
|
+
*/
|
|
11
|
+
export class Retriever {
|
|
12
|
+
constructor(config, vectorStore) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.vectorStore = vectorStore;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Retrieve relevant documents for a query
|
|
19
|
+
* @param {string} query - User query
|
|
20
|
+
* @param {object} options - Retrieval options
|
|
21
|
+
* @returns {object} Retrieval results
|
|
22
|
+
*/
|
|
23
|
+
async retrieve(query, options = {}) {
|
|
24
|
+
const {
|
|
25
|
+
topK = 5,
|
|
26
|
+
threshold = 0.5,
|
|
27
|
+
includeMetadata = true,
|
|
28
|
+
} = options;
|
|
29
|
+
|
|
30
|
+
if (!query || query.trim().length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
query,
|
|
33
|
+
results: [],
|
|
34
|
+
count: 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const results = await this.vectorStore.search(query, topK, threshold);
|
|
39
|
+
|
|
40
|
+
const formattedResults = results.map(doc => ({
|
|
41
|
+
id: doc.id,
|
|
42
|
+
text: doc.text,
|
|
43
|
+
source: doc.source,
|
|
44
|
+
score: doc.score,
|
|
45
|
+
metadata: includeMetadata ? doc.metadata : undefined,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
query,
|
|
50
|
+
results: formattedResults,
|
|
51
|
+
count: formattedResults.length,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Format retrieval results as context string
|
|
57
|
+
* @param {object} retrievalResult - Result from retrieve()
|
|
58
|
+
* @param {object} options - Formatting options
|
|
59
|
+
* @returns {string} Formatted context
|
|
60
|
+
*/
|
|
61
|
+
formatContext(retrievalResult, options = {}) {
|
|
62
|
+
const { maxChars = 4000, showScores = true } = options;
|
|
63
|
+
|
|
64
|
+
if (retrievalResult.count === 0) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let context = '## Relevant Context\n\n';
|
|
69
|
+
|
|
70
|
+
for (const doc of retrievalResult.results) {
|
|
71
|
+
const scoreStr = showScores ? ` [score: ${doc.score.toFixed(3)}]` : '';
|
|
72
|
+
const sourceStr = doc.source ? ` (${doc.source})` : '';
|
|
73
|
+
|
|
74
|
+
context += `---${sourceStr}${scoreStr}---\n`;
|
|
75
|
+
context += doc.text + '\n\n';
|
|
76
|
+
|
|
77
|
+
// Truncate if too long
|
|
78
|
+
if (context.length > maxChars) {
|
|
79
|
+
context = context.slice(0, maxChars) + '\n... (truncated)';
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return context;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Augment prompt with retrieved context
|
|
89
|
+
* @param {string} originalPrompt - Original user prompt
|
|
90
|
+
* @param {object} options - Retrieval and formatting options
|
|
91
|
+
* @returns {string} Augmented prompt
|
|
92
|
+
*/
|
|
93
|
+
async augmentPrompt(originalPrompt, options = {}) {
|
|
94
|
+
const retrievalResult = await this.retrieve(originalPrompt, {
|
|
95
|
+
topK: options.topK || 5,
|
|
96
|
+
threshold: options.threshold || 0.5,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (retrievalResult.count === 0) {
|
|
100
|
+
return originalPrompt;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const context = this.formatContext(retrievalResult, {
|
|
104
|
+
maxChars: options.maxChars || 4000,
|
|
105
|
+
showScores: options.showScores || false,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return `${context}\n\n## User Question\n${originalPrompt}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default Retriever;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ❄ VECTOR STORE ❄
|
|
3
|
+
* File-based vector storage with cosine similarity search
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { getEmbeddingConfig, createEmbeddingRequest, parseEmbeddingResponse, getEmbeddingBaseURL, getEmbeddingHeaders } from './embeddings.js';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_VECTOR_STORE_FILE = path.join('.winter', 'rag', 'vector-store.json');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Vector Store class
|
|
14
|
+
*/
|
|
15
|
+
export class VectorStore {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.projectPath = path.resolve(config.projectPath || config.project?.current || process.cwd());
|
|
19
|
+
this.storeFile = path.resolve(this.projectPath, config.rag?.storeFile || DEFAULT_VECTOR_STORE_FILE);
|
|
20
|
+
this.embeddings = [];
|
|
21
|
+
this.meta = {
|
|
22
|
+
provider: config.rag?.provider || 'qwen',
|
|
23
|
+
dimension: 0,
|
|
24
|
+
createdAt: null,
|
|
25
|
+
updatedAt: null,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize vector store (load from disk)
|
|
31
|
+
*/
|
|
32
|
+
async init() {
|
|
33
|
+
try {
|
|
34
|
+
const data = await fs.readFile(this.storeFile, 'utf-8');
|
|
35
|
+
const parsed = JSON.parse(data);
|
|
36
|
+
this.embeddings = parsed.embeddings || [];
|
|
37
|
+
this.meta = parsed.meta || this.meta;
|
|
38
|
+
} catch {
|
|
39
|
+
// No existing store, start fresh
|
|
40
|
+
this.embeddings = [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Save vector store to disk
|
|
46
|
+
*/
|
|
47
|
+
async save() {
|
|
48
|
+
const dir = path.dirname(this.storeFile);
|
|
49
|
+
await fs.mkdir(dir, { recursive: true });
|
|
50
|
+
|
|
51
|
+
this.meta.updatedAt = new Date().toISOString();
|
|
52
|
+
|
|
53
|
+
await fs.writeFile(this.storeFile, JSON.stringify({
|
|
54
|
+
embeddings: this.embeddings,
|
|
55
|
+
meta: this.meta,
|
|
56
|
+
}, null, 2));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Embed text using configured provider
|
|
61
|
+
* @param {string} text - Text to embed
|
|
62
|
+
* @returns {number[]} Embedding vector
|
|
63
|
+
*/
|
|
64
|
+
async embedText(text) {
|
|
65
|
+
const providerName = this.meta.provider;
|
|
66
|
+
const cfg = getEmbeddingConfig(providerName, this.config);
|
|
67
|
+
|
|
68
|
+
const baseURL = getEmbeddingBaseURL(providerName, cfg);
|
|
69
|
+
const headers = getEmbeddingHeaders(providerName, cfg);
|
|
70
|
+
const body = createEmbeddingRequest(providerName, text, cfg);
|
|
71
|
+
|
|
72
|
+
const response = await fetch(baseURL, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers,
|
|
75
|
+
body: JSON.stringify(body),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const errorText = await response.text();
|
|
80
|
+
throw new Error(`Embedding request failed: ${response.status} ${errorText}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
const embedding = parseEmbeddingResponse(providerName, data);
|
|
85
|
+
|
|
86
|
+
if (!embedding || embedding.length === 0) {
|
|
87
|
+
throw new Error('Failed to generate embedding');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return embedding;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Add document to vector store
|
|
95
|
+
* @param {object} doc - Document { id, text, source, metadata }
|
|
96
|
+
*/
|
|
97
|
+
async addDocument(doc) {
|
|
98
|
+
const embedding = await this.embedText(doc.text);
|
|
99
|
+
|
|
100
|
+
const vectorDoc = {
|
|
101
|
+
id: doc.id || `doc_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
|
102
|
+
text: doc.text,
|
|
103
|
+
source: doc.source || 'unknown',
|
|
104
|
+
metadata: doc.metadata || {},
|
|
105
|
+
embedding,
|
|
106
|
+
createdAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.embeddings.push(vectorDoc);
|
|
110
|
+
|
|
111
|
+
if (this.meta.dimension === 0) {
|
|
112
|
+
this.meta.dimension = embedding.length;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await this.save();
|
|
116
|
+
|
|
117
|
+
return vectorDoc.id;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Add multiple documents
|
|
122
|
+
* @param {object[]} docs - Array of documents
|
|
123
|
+
*/
|
|
124
|
+
async addDocuments(docs) {
|
|
125
|
+
const ids = [];
|
|
126
|
+
|
|
127
|
+
for (const doc of docs) {
|
|
128
|
+
const id = await this.addDocument(doc);
|
|
129
|
+
ids.push(id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return ids;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cosine similarity between two vectors
|
|
137
|
+
* @param {number[]} a - Vector A
|
|
138
|
+
* @param {number[]} b - Vector B
|
|
139
|
+
* @returns {number} Similarity score (0-1)
|
|
140
|
+
*/
|
|
141
|
+
cosineSimilarity(a, b) {
|
|
142
|
+
if (a.length !== b.length) return 0;
|
|
143
|
+
|
|
144
|
+
let dotProduct = 0;
|
|
145
|
+
let normA = 0;
|
|
146
|
+
let normB = 0;
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < a.length; i++) {
|
|
149
|
+
dotProduct += a[i] * b[i];
|
|
150
|
+
normA += a[i] * a[i];
|
|
151
|
+
normB += b[i] * b[i];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (normA === 0 || normB === 0) return 0;
|
|
155
|
+
|
|
156
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Search for similar documents
|
|
161
|
+
* @param {string} query - Query text
|
|
162
|
+
* @param {number} topK - Number of results (default: 5)
|
|
163
|
+
* @param {number} threshold - Similarity threshold (default: 0.5)
|
|
164
|
+
* @returns {object[]} Similar documents
|
|
165
|
+
*/
|
|
166
|
+
async search(query, topK = 5, threshold = 0.5) {
|
|
167
|
+
if (this.embeddings.length === 0) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const queryEmbedding = await this.embedText(query);
|
|
172
|
+
|
|
173
|
+
const results = this.embeddings
|
|
174
|
+
.map(doc => ({
|
|
175
|
+
...doc,
|
|
176
|
+
score: this.cosineSimilarity(queryEmbedding, doc.embedding),
|
|
177
|
+
}))
|
|
178
|
+
.filter(doc => doc.score >= threshold)
|
|
179
|
+
.sort((a, b) => b.score - a.score)
|
|
180
|
+
.slice(0, topK);
|
|
181
|
+
|
|
182
|
+
return results;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get all documents
|
|
187
|
+
* @returns {object[]} All documents
|
|
188
|
+
*/
|
|
189
|
+
getAll() {
|
|
190
|
+
return this.embeddings.map(doc => ({
|
|
191
|
+
id: doc.id,
|
|
192
|
+
text: doc.text,
|
|
193
|
+
source: doc.source,
|
|
194
|
+
metadata: doc.metadata,
|
|
195
|
+
createdAt: doc.createdAt,
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Clear all documents
|
|
201
|
+
*/
|
|
202
|
+
async clear() {
|
|
203
|
+
this.embeddings = [];
|
|
204
|
+
this.meta.dimension = 0;
|
|
205
|
+
this.meta.createdAt = null;
|
|
206
|
+
this.meta.updatedAt = null;
|
|
207
|
+
await this.save();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get statistics
|
|
212
|
+
*/
|
|
213
|
+
getStats() {
|
|
214
|
+
return {
|
|
215
|
+
totalDocuments: this.embeddings.length,
|
|
216
|
+
dimension: this.meta.dimension,
|
|
217
|
+
provider: this.meta.provider,
|
|
218
|
+
createdAt: this.meta.createdAt,
|
|
219
|
+
updatedAt: this.meta.updatedAt,
|
|
220
|
+
storeFile: this.storeFile,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export default VectorStore;
|
package/src/tools/executor.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { promises as fs } from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
|
-
import { exec, execFile } from 'child_process';
|
|
8
|
+
import { exec, execFile, spawn } from 'child_process';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { diffLines } from 'diff';
|
|
11
11
|
import { withRetry } from './retry.js';
|
|
@@ -207,11 +207,11 @@ export class ToolExecutor {
|
|
|
207
207
|
{
|
|
208
208
|
type: 'function',
|
|
209
209
|
name: 'MCP',
|
|
210
|
-
description: 'Call a configured MCP server tool by name. Use for external integrations and IDE-like tools. Discover available MCP tools via the MCP tool with server name and tool=list. Also, tools from MCP servers are exposed with mcp__<server>__<tool> naming for direct IDE integration (e.g. mcp__vscode__open_file).',
|
|
210
|
+
description: 'Call a configured MCP server tool by name. Use for external integrations and IDE-like tools. Discover available MCP tools via the MCP tool with server name and tool=list. For live Chrome debugging, use server chrome-devtools with tools such as new_page, navigate_page, take_snapshot, take_screenshot, evaluate_script, list_console_messages, list_network_requests, and performance trace tools. Also, tools from MCP servers are exposed with mcp__<server>__<tool> naming for direct IDE integration (e.g. mcp__vscode__open_file).',
|
|
211
211
|
parameters: {
|
|
212
212
|
type: 'object',
|
|
213
213
|
properties: {
|
|
214
|
-
server: { type: 'string', description: 'Configured MCP server name (e.g. vscode)' },
|
|
214
|
+
server: { type: 'string', description: 'Configured MCP server name (e.g. vscode, chrome-devtools)' },
|
|
215
215
|
tool: { type: 'string', description: 'MCP tool name, or set to "list" to discover all tools from a server' },
|
|
216
216
|
arguments: { type: 'object', description: 'Tool arguments' },
|
|
217
217
|
},
|
|
@@ -388,6 +388,18 @@ export class ToolExecutor {
|
|
|
388
388
|
required: ['url']
|
|
389
389
|
}
|
|
390
390
|
},
|
|
391
|
+
{
|
|
392
|
+
type: 'function',
|
|
393
|
+
name: 'OpenBrowser',
|
|
394
|
+
description: 'Open Chrome or the default browser visibly for the user. Use this for requests like "mở chrome", "open Chrome", or "open this URL in browser". Do not use Bash/Start-Process for this.',
|
|
395
|
+
parameters: {
|
|
396
|
+
type: 'object',
|
|
397
|
+
properties: {
|
|
398
|
+
url: { type: 'string', description: 'URL to open. Defaults to about:blank.' },
|
|
399
|
+
browser: { type: 'string', description: 'chrome or default. Defaults to chrome.' },
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
},
|
|
391
403
|
{
|
|
392
404
|
type: 'function',
|
|
393
405
|
name: 'WebFetch',
|
|
@@ -532,6 +544,8 @@ export class ToolExecutor {
|
|
|
532
544
|
return await this.parallelExecute(input.tools ?? input.calls ?? [], { cwd });
|
|
533
545
|
case 'BrowserDebug':
|
|
534
546
|
return await this.browserDebug(input.url ?? input.uri, input.action);
|
|
547
|
+
case 'OpenBrowser':
|
|
548
|
+
return await this.openBrowser(input.url ?? input.uri ?? input.href, input.browser);
|
|
535
549
|
case 'WebFetch':
|
|
536
550
|
return await this.webFetch(input.url ?? input.uri ?? input.href, input.prompt ?? input.query ?? input.extract);
|
|
537
551
|
case 'WebSearch':
|
|
@@ -572,7 +586,7 @@ export class ToolExecutor {
|
|
|
572
586
|
return {
|
|
573
587
|
success: false,
|
|
574
588
|
error: `Unknown tool: ${toolName}`,
|
|
575
|
-
availableTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList', 'MCP', 'Parallel', 'BrowserDebug', 'WebFetch', 'WebSearch', 'WebArchive', 'HtmlEffectiveness', 'NotebookRead', 'NotebookEdit', 'TodoWrite', 'TodoList', 'ScheduleWakeup', 'AskUserQuestion', 'Agent', 'InsertText', 'StrReplaceAll'],
|
|
589
|
+
availableTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList', 'MCP', 'Parallel', 'OpenBrowser', 'BrowserDebug', 'WebFetch', 'WebSearch', 'WebArchive', 'HtmlEffectiveness', 'NotebookRead', 'NotebookEdit', 'TodoWrite', 'TodoList', 'ScheduleWakeup', 'AskUserQuestion', 'Agent', 'InsertText', 'StrReplaceAll'],
|
|
576
590
|
recovery: 'Call one of the available tools. For file writes use Write with { "file_path": "...", "content": "..." }. For shell commands use Bash with { "command": "..." }.',
|
|
577
591
|
};
|
|
578
592
|
}
|
|
@@ -725,6 +739,12 @@ export class ToolExecutor {
|
|
|
725
739
|
return { success: true, coerced: true, args: next };
|
|
726
740
|
}
|
|
727
741
|
|
|
742
|
+
if (toolName === 'OpenBrowser') {
|
|
743
|
+
const url = pick('url', 'uri', 'href') || 'about:blank';
|
|
744
|
+
const browser = pick('browser', 'app') || 'chrome';
|
|
745
|
+
return { success: true, coerced: true, args: { ...args, url, browser } };
|
|
746
|
+
}
|
|
747
|
+
|
|
728
748
|
if (toolName === 'WebSearch') {
|
|
729
749
|
const query = pick('query', 'q', 'search', 'search_query', 'searchQuery');
|
|
730
750
|
if (!query) {
|
|
@@ -752,6 +772,14 @@ export class ToolExecutor {
|
|
|
752
772
|
const baseCommand = this.getBaseCommand(text);
|
|
753
773
|
if (!baseCommand) return { success: true };
|
|
754
774
|
|
|
775
|
+
if (/^(?:get-command|start-process|start|open|xdg-open)$/i.test(baseCommand) && /\b(chrome|browser|google chrome)\b/i.test(text)) {
|
|
776
|
+
return {
|
|
777
|
+
success: false,
|
|
778
|
+
error: `Use OpenBrowser instead of shell command for browser launch: ${baseCommand}`,
|
|
779
|
+
recovery: 'Call OpenBrowser {"browser":"chrome","url":"about:blank"} for "mở chrome", or OpenBrowser {"browser":"chrome","url":"https://example.com"} for a specific URL.',
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
755
783
|
const cfg = await this.getRuntimeConfig();
|
|
756
784
|
const permissionCommands = cfg.permissions?.allowlist?.commands || [];
|
|
757
785
|
const sandbox = cfg.sandbox || {};
|
|
@@ -974,6 +1002,11 @@ export class ToolExecutor {
|
|
|
974
1002
|
searchweb: 'WebSearch',
|
|
975
1003
|
internetsearch: 'WebSearch',
|
|
976
1004
|
googlesearch: 'WebSearch',
|
|
1005
|
+
openbrowser: 'OpenBrowser',
|
|
1006
|
+
open_browser: 'OpenBrowser',
|
|
1007
|
+
browseropen: 'OpenBrowser',
|
|
1008
|
+
openchrome: 'OpenBrowser',
|
|
1009
|
+
launchchrome: 'OpenBrowser',
|
|
977
1010
|
browserdebug: 'BrowserDebug',
|
|
978
1011
|
browser: 'BrowserDebug',
|
|
979
1012
|
browserinspect: 'BrowserDebug',
|
|
@@ -2076,6 +2109,72 @@ export class ToolExecutor {
|
|
|
2076
2109
|
}
|
|
2077
2110
|
}
|
|
2078
2111
|
|
|
2112
|
+
buildBrowserLaunchCommand(url = 'about:blank', browser = 'chrome', platform = process.platform) {
|
|
2113
|
+
const targetUrl = String(url || 'about:blank');
|
|
2114
|
+
const targetBrowser = String(browser || 'chrome').toLowerCase();
|
|
2115
|
+
|
|
2116
|
+
if (platform === 'win32') {
|
|
2117
|
+
if (targetBrowser === 'default') {
|
|
2118
|
+
return { command: 'cmd', args: ['/c', 'start', '', targetUrl] };
|
|
2119
|
+
}
|
|
2120
|
+
return { command: 'cmd', args: ['/c', 'start', '', 'chrome', targetUrl] };
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
if (platform === 'darwin') {
|
|
2124
|
+
if (targetBrowser === 'default') {
|
|
2125
|
+
return { command: 'open', args: [targetUrl] };
|
|
2126
|
+
}
|
|
2127
|
+
return { command: 'open', args: ['-a', 'Google Chrome', targetUrl] };
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
if (targetBrowser === 'default') {
|
|
2131
|
+
return { command: 'xdg-open', args: [targetUrl] };
|
|
2132
|
+
}
|
|
2133
|
+
return { command: 'google-chrome', args: [targetUrl] };
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
async openBrowser(url = 'about:blank', browser = 'chrome') {
|
|
2137
|
+
const targetUrl = String(url || 'about:blank');
|
|
2138
|
+
const targetBrowser = String(browser || 'chrome').toLowerCase();
|
|
2139
|
+
const launch = this.buildBrowserLaunchCommand(targetUrl, targetBrowser);
|
|
2140
|
+
|
|
2141
|
+
return await new Promise(resolve => {
|
|
2142
|
+
let child;
|
|
2143
|
+
try {
|
|
2144
|
+
child = spawn(launch.command, launch.args, {
|
|
2145
|
+
detached: true,
|
|
2146
|
+
stdio: 'ignore',
|
|
2147
|
+
windowsHide: false,
|
|
2148
|
+
});
|
|
2149
|
+
} catch (error) {
|
|
2150
|
+
resolve({
|
|
2151
|
+
success: false,
|
|
2152
|
+
error: error.message,
|
|
2153
|
+
recovery: 'Install Chrome or retry with OpenBrowser {"browser":"default","url":"about:blank"}.',
|
|
2154
|
+
});
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
child.once('error', error => {
|
|
2159
|
+
resolve({
|
|
2160
|
+
success: false,
|
|
2161
|
+
error: error.message,
|
|
2162
|
+
recovery: 'Install Chrome or retry with OpenBrowser {"browser":"default","url":"about:blank"}.',
|
|
2163
|
+
});
|
|
2164
|
+
});
|
|
2165
|
+
child.once('spawn', () => {
|
|
2166
|
+
child.unref();
|
|
2167
|
+
resolve({
|
|
2168
|
+
success: true,
|
|
2169
|
+
browser: targetBrowser,
|
|
2170
|
+
url: targetUrl,
|
|
2171
|
+
command: launch.command,
|
|
2172
|
+
args: launch.args,
|
|
2173
|
+
});
|
|
2174
|
+
});
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2079
2178
|
async htmlEffectivenessCompile(input, cwd) {
|
|
2080
2179
|
const inputPath = this.resolveInputPath(input.input_path ?? input.inputPath ?? input.input, cwd);
|
|
2081
2180
|
const outputPath = this.resolveInputPath(input.output_path ?? input.outputPath ?? input.output, cwd);
|