yuangs 1.3.39 → 1.3.45

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 (59) hide show
  1. package/README.md +44 -0
  2. package/dist/ai/client.d.ts +9 -0
  3. package/dist/ai/client.js +118 -0
  4. package/dist/ai/client.js.map +1 -0
  5. package/dist/ai/prompt.d.ts +3 -0
  6. package/dist/ai/prompt.js +56 -0
  7. package/dist/ai/prompt.js.map +1 -0
  8. package/dist/ai/types.d.ts +5 -0
  9. package/dist/ai/types.js +3 -0
  10. package/dist/ai/types.js.map +1 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +142 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/handleAIChat.d.ts +1 -0
  15. package/dist/commands/handleAIChat.js +111 -0
  16. package/dist/commands/handleAIChat.js.map +1 -0
  17. package/dist/commands/handleAICommand.d.ts +4 -0
  18. package/dist/commands/handleAICommand.js +97 -0
  19. package/dist/commands/handleAICommand.js.map +1 -0
  20. package/dist/commands/handleConfig.d.ts +1 -0
  21. package/dist/commands/handleConfig.js +73 -0
  22. package/dist/commands/handleConfig.js.map +1 -0
  23. package/dist/core/apps.d.ts +7 -0
  24. package/dist/core/apps.js +64 -0
  25. package/dist/core/apps.js.map +1 -0
  26. package/dist/core/autofix.d.ts +3 -0
  27. package/dist/core/autofix.js +24 -0
  28. package/dist/core/autofix.js.map +1 -0
  29. package/dist/core/executor.d.ts +6 -0
  30. package/dist/core/executor.js +28 -0
  31. package/dist/core/executor.js.map +1 -0
  32. package/dist/core/macros.d.ts +9 -0
  33. package/dist/core/macros.js +51 -0
  34. package/dist/core/macros.js.map +1 -0
  35. package/dist/core/os.d.ts +7 -0
  36. package/dist/core/os.js +36 -0
  37. package/dist/core/os.js.map +1 -0
  38. package/dist/core/risk.d.ts +1 -0
  39. package/dist/core/risk.js +11 -0
  40. package/dist/core/risk.js.map +1 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.js +3 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/utils/confirm.d.ts +1 -0
  45. package/dist/utils/confirm.js +55 -0
  46. package/dist/utils/confirm.js.map +1 -0
  47. package/dist/utils/history.d.ts +10 -0
  48. package/dist/utils/history.js +31 -0
  49. package/dist/utils/history.js.map +1 -0
  50. package/package.json +24 -8
  51. package/cli.js +0 -597
  52. package/index.js +0 -361
  53. package/test/index.test.js +0 -78
  54. package/test/macros.test.js +0 -91
  55. package/test_interactive_history.js +0 -66
  56. package/test_interactive_history_multi.js +0 -67
  57. package/yuangs.config.example.json +0 -11
  58. package/yuangs.config.example.yaml +0 -23
  59. package/yuangs.config.json +0 -9
package/index.js DELETED
@@ -1,361 +0,0 @@
1
- const { exec } = require('child_process');
2
- const axios = require('axios');
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
-
7
- // Store conversation history
8
- // 存储结构标准为: [{ role: 'user', content: '...' }, { role: 'assistant', content: '...' }]
9
- let conversationHistory = [];
10
-
11
- const HISTORY_FILE = path.join(os.homedir(), '.yuangs_cmd_history.json');
12
- const CONFIG_FILE = path.join(os.homedir(), '.yuangs.json');
13
- const MACROS_FILE = path.join(os.homedir(), '.yuangs_macros.json');
14
-
15
- function getUserConfig() {
16
- if (fs.existsSync(CONFIG_FILE)) {
17
- try {
18
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
19
- } catch (e) {}
20
- }
21
- return {};
22
- }
23
-
24
- function getCommandHistory() {
25
- if (fs.existsSync(HISTORY_FILE)) {
26
- try {
27
- return JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf8'));
28
- } catch (e) {}
29
- }
30
- return [];
31
- }
32
-
33
- function saveSuccessfulCommand(question, command) {
34
- if (!command) return;
35
- let history = getCommandHistory();
36
- const newEntry = { question, command, time: new Date().toLocaleString() };
37
- history = [newEntry, ...history.filter(item => item.command !== command)].slice(0, 5);
38
- fs.writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2));
39
- }
40
-
41
- function getMacros() {
42
- if (fs.existsSync(MACROS_FILE)) {
43
- try {
44
- return JSON.parse(fs.readFileSync(MACROS_FILE, 'utf8'));
45
- } catch (e) {}
46
- }
47
- return {};
48
- }
49
-
50
- function saveMacro(name, commands, description = '') {
51
- const macros = getMacros();
52
- macros[name] = {
53
- commands,
54
- description,
55
- createdAt: new Date().toISOString()
56
- };
57
- fs.writeFileSync(MACROS_FILE, JSON.stringify(macros, null, 2));
58
- return true;
59
- }
60
-
61
- function deleteMacro(name) {
62
- const macros = getMacros();
63
- if (macros[name]) {
64
- delete macros[name];
65
- fs.writeFileSync(MACROS_FILE, JSON.stringify(macros, null, 2));
66
- return true;
67
- }
68
- return false;
69
- }
70
-
71
- // Default apps (fallback if no config file exists)
72
- const DEFAULT_APPS = {
73
- shici: 'https://wealth.want.biz/shici/index.html',
74
- dict: 'https://wealth.want.biz/pages/dict.html',
75
- pong: 'https://wealth.want.biz/pages/pong.html'
76
- };
77
-
78
- // Load apps from configuration file
79
- function loadAppsConfig() {
80
- // Define possible config file locations (JSON and YAML)
81
- const configPaths = [
82
- path.join(process.cwd(), 'yuangs.config.json'), // Current working directory
83
- path.join(process.cwd(), '.yuangs.json'), // Current working directory dot file
84
- path.join(process.cwd(), 'yuangs.config.yaml'), // Current working directory YAML
85
- path.join(process.cwd(), 'yuangs.config.yml'), // Current working directory YAML
86
- path.join(process.cwd(), '.yuangs.yaml'), // Current working directory dot YAML
87
- path.join(process.cwd(), '.yuangs.yml'), // Current working directory dot YAML
88
- path.join(require('os').homedir(), '.yuangs.json'), // User home directory
89
- path.join(require('os').homedir(), '.yuangs.yaml'), // User home directory YAML
90
- path.join(require('os').homedir(), '.yuangs.yml'), // User home directory YAML
91
- path.join(__dirname, 'yuangs.config.json'), // Project directory
92
- path.join(__dirname, '.yuangs.json'), // Project directory dot file
93
- path.join(__dirname, 'yuangs.config.yaml'), // Project directory YAML
94
- path.join(__dirname, 'yuangs.config.yml') // Project directory YAML
95
- ];
96
-
97
- for (const configPath of configPaths) {
98
- if (fs.existsSync(configPath)) {
99
- try {
100
- const configContent = fs.readFileSync(configPath, 'utf8');
101
-
102
- // Determine if it's JSON or YAML based on file extension
103
- let config;
104
- if (configPath.endsWith('.json')) {
105
- config = JSON.parse(configContent);
106
- } else {
107
- // For YAML files, we need to require the yaml parser
108
- let yaml;
109
- try {
110
- yaml = require('js-yaml');
111
- } catch (yamlError) {
112
- console.warn(`Warning: js-yaml not installed, skipping YAML file ${configPath}`);
113
- console.warn('Install js-yaml with: npm install js-yaml');
114
- continue; // Skip this file and try the next config file
115
- }
116
- config = yaml.load(configContent);
117
- }
118
-
119
- // If config has an 'apps' property, use it, otherwise use the whole config as apps
120
- return config.apps || config;
121
- } catch (error) {
122
- console.warn(`Warning: Could not parse config file at ${configPath}:`, error.message);
123
- // Continue to next config file
124
- }
125
- }
126
- }
127
-
128
- // If no config file is found, use default apps
129
- return DEFAULT_APPS;
130
- }
131
-
132
- const APPS = loadAppsConfig();
133
-
134
- function openUrl(url) {
135
- let command;
136
- switch (process.platform) {
137
- case 'darwin': command = `open "${url}"`; break;
138
- case 'win32': command = `start "${url}"`; break;
139
- default: command = `xdg-open "${url}"`; break;
140
- }
141
- exec(command);
142
- }
143
-
144
- // Function to add a message to the conversation history
145
- function addToConversationHistory(role, content) {
146
- conversationHistory.push({ role, content });
147
-
148
- // Keep only the last 20 messages to prevent history from growing too large
149
- if (conversationHistory.length > 20) {
150
- conversationHistory = conversationHistory.slice(-20);
151
- }
152
- }
153
-
154
- // Function to clear conversation history
155
- function clearConversationHistory() {
156
- conversationHistory = [];
157
- }
158
-
159
- // Function to get conversation history
160
- function getConversationHistory() {
161
- return conversationHistory;
162
- }
163
-
164
- /**
165
- * 通用 AI 调用函数 (OpenAI 兼容接口)
166
- */
167
- async function callAI_Stream(messages, model, onChunk) {
168
- const config = getUserConfig();
169
- const url = config.aiProxyUrl || 'https://aiproxy.want.biz/v1/chat/completions';
170
-
171
- const response = await axios({
172
- method: 'post',
173
- url: url,
174
- data: {
175
- model: model || config.defaultModel || 'Assistant',
176
- messages: messages,
177
- stream: true
178
- },
179
- responseType: 'stream',
180
- headers: {
181
- 'Content-Type': 'application/json',
182
- 'X-Client-ID': 'npm_yuangs',
183
- 'Origin': 'https://cli.want.biz',
184
- 'Referer': 'https://cli.want.biz/',
185
- 'account': config.accountType || 'free',
186
- 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
187
- 'Accept': 'application/json'
188
- }
189
- });
190
-
191
- return new Promise((resolve, reject) => {
192
- let buffer = '';
193
- response.data.on('data', chunk => {
194
- buffer += chunk.toString();
195
- let lines = buffer.split('\n');
196
- // 数组的最后一个元素可能是不完整的行,留到下一次处理
197
- buffer = lines.pop();
198
-
199
- for (const line of lines) {
200
- const trimmedLine = line.trim();
201
- if (trimmedLine.startsWith('data: ')) {
202
- const data = trimmedLine.slice(6);
203
- if (data === '[DONE]') {
204
- resolve();
205
- return;
206
- }
207
- try {
208
- const parsed = JSON.parse(data);
209
- const content = parsed.choices[0]?.delta?.content || '';
210
- if (content) onChunk(content);
211
- } catch (e) {
212
- // 如果这一行真的有问题,忽略它
213
- }
214
- }
215
- }
216
- });
217
- response.data.on('error', reject);
218
- response.data.on('end', () => {
219
- // 处理缓冲区中剩余的内容(如果有)
220
- if (buffer.trim().startsWith('data: ')) {
221
- try {
222
- const data = buffer.trim().slice(6);
223
- if (data !== '[DONE]') {
224
- const parsed = JSON.parse(data);
225
- const content = parsed.choices[0]?.delta?.content || '';
226
- if (content) onChunk(content);
227
- }
228
- } catch (e) {}
229
- }
230
- resolve();
231
- });
232
- });
233
- }
234
-
235
- async function callAI_OpenAI(messages, model) {
236
- const config = getUserConfig();
237
- const url = config.aiProxyUrl || 'https://aiproxy.want.biz/v1/chat/completions';
238
-
239
- const headers = {
240
- 'Content-Type': 'application/json',
241
- 'X-Client-ID': 'npm_yuangs',
242
- 'Origin': 'https://cli.want.biz',
243
- 'Referer': 'https://cli.want.biz/',
244
- 'account': config.accountType || 'free',
245
- 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
246
- 'Accept': 'application/json'
247
- };
248
-
249
- const data = {
250
- model: model || config.defaultModel || 'Assistant',
251
- messages: messages,
252
- stream: false
253
- };
254
-
255
- return await axios.post(url, data, { headers });
256
- }
257
-
258
- /**
259
- * 获取 AI 回复
260
- */
261
- async function getAIAnswer(question, model, includeHistory = true) {
262
- // 构建 messages 数组 (上下文 + 当前问题)
263
- let messages = [];
264
- if (includeHistory) {
265
- messages = [...conversationHistory];
266
- }
267
- messages.push({ role: 'user', content: question });
268
-
269
- try {
270
- const response = await callAI_OpenAI(messages, model);
271
- const aiContent = response.data?.choices?.[0]?.message?.content;
272
-
273
- if (!aiContent) {
274
- throw new Error('Invalid response structure from AI API');
275
- }
276
-
277
- // 只有请求成功才记录历史
278
- addToConversationHistory('user', question);
279
- addToConversationHistory('assistant', aiContent);
280
-
281
- return {
282
- explanation: aiContent, // 兼容字段
283
- content: aiContent, // 标准字段建议
284
- raw: response.data // 原始响应
285
- };
286
-
287
- } catch (error) {
288
- const errorMsg = error.response?.data?.error?.message || error.response?.data?.message || error.message || '未知错误';
289
- console.error('AI 请求失败:', errorMsg);
290
- return null;
291
- }
292
- }
293
-
294
- async function generateCommand(instruction, model) {
295
- const config = getUserConfig();
296
- const messages = [
297
- {
298
- role: 'system',
299
- content: `You are a Linux command generator. Convert the user's natural language request into a single, executable Linux command.
300
- IMPORTANT: Output ONLY the command. Do not check for safety. Do not output markdown code blocks (no backticks). Do not explain.`
301
- },
302
- {
303
- role: 'user',
304
- content: `Request: ${instruction}`
305
- }
306
- ];
307
-
308
- try {
309
- const response = await callAI_OpenAI(messages, model || config.defaultModel);
310
- const aiContent = response.data?.choices?.[0]?.message?.content;
311
-
312
- if (aiContent) {
313
- let command = aiContent.trim();
314
- if (command.startsWith('`') && command.endsWith('`')) {
315
- command = command.slice(1, -1);
316
- }
317
- if (command.startsWith('```') && command.endsWith('```')) {
318
- command = command.split('\n').filter(line => !line.startsWith('```')).join('\n').trim();
319
- }
320
- if (command.startsWith('$ ')) command = command.slice(2);
321
- if (command.startsWith('> ')) command = command.slice(2);
322
-
323
- return command;
324
- }
325
- return null;
326
- } catch (error) {
327
- return null;
328
- }
329
- }
330
-
331
- module.exports = {
332
- urls: APPS,
333
- openApp: (appKey) => {
334
- const url = APPS[appKey];
335
- if (url) {
336
- openUrl(url);
337
- return true;
338
- }
339
- console.error(`App '${appKey}' not found`);
340
- return false;
341
- },
342
- openShici: () => openUrl(APPS.shici || DEFAULT_APPS.shici),
343
- openDict: () => openUrl(APPS.dict || DEFAULT_APPS.dict),
344
- openPong: () => openUrl(APPS.pong || DEFAULT_APPS.pong),
345
- listApps: () => {
346
- console.log('--- YGS Apps ---');
347
- Object.entries(APPS).forEach(([key, url]) => console.log(`${key}: ${url}`));
348
- },
349
- getAIAnswer,
350
- addToConversationHistory,
351
- clearConversationHistory,
352
- getConversationHistory,
353
- generateCommand,
354
- getUserConfig,
355
- getCommandHistory,
356
- saveSuccessfulCommand,
357
- callAI_Stream,
358
- getMacros,
359
- saveMacro,
360
- deleteMacro
361
- };
@@ -1,78 +0,0 @@
1
- const { exec } = require('child_process');
2
- const path = require('path');
3
- const yuangs = require('../index.js');
4
-
5
- describe('Module: index.js', () => {
6
- beforeEach(() => {
7
- yuangs.clearConversationHistory();
8
- });
9
-
10
- test('should export correct app URLs', () => {
11
- expect(yuangs.urls).toHaveProperty('shici');
12
- expect(yuangs.urls).toHaveProperty('dict');
13
- expect(yuangs.urls).toHaveProperty('pong');
14
- expect(yuangs.urls.shici).toContain('shici/index.html');
15
- });
16
-
17
- test('should have openApp function', () => {
18
- expect(typeof yuangs.openApp).toBe('function');
19
- });
20
-
21
- test('should have backward compatibility functions', () => {
22
- expect(typeof yuangs.openShici).toBe('function');
23
- expect(typeof yuangs.openDict).toBe('function');
24
- expect(typeof yuangs.openPong).toBe('function');
25
- });
26
-
27
- test('should manage conversation history correctly', () => {
28
- yuangs.addToConversationHistory('user', 'hello');
29
- let history = yuangs.getConversationHistory();
30
- expect(history).toHaveLength(1);
31
- expect(history[0]).toEqual({ role: 'user', content: 'hello' });
32
-
33
- yuangs.addToConversationHistory('assistant', 'hi');
34
- history = yuangs.getConversationHistory();
35
- expect(history).toHaveLength(2);
36
- expect(history[1]).toEqual({ role: 'assistant', content: 'hi' });
37
- });
38
-
39
- test('should limit conversation history to 20 items', () => {
40
- for (let i = 0; i < 25; i++) {
41
- yuangs.addToConversationHistory('user', `msg ${i}`);
42
- }
43
- const history = yuangs.getConversationHistory();
44
- expect(history).toHaveLength(20);
45
- expect(history[history.length - 1].content).toBe('msg 24');
46
- // The first 5 should be dropped, so the first one in history should be 'msg 5'
47
- expect(history[0].content).toBe('msg 5');
48
- });
49
-
50
- test('should clear conversation history', () => {
51
- yuangs.addToConversationHistory('user', 'test');
52
- yuangs.clearConversationHistory();
53
- expect(yuangs.getConversationHistory()).toHaveLength(0);
54
- });
55
- });
56
-
57
- describe('CLI Integration', () => {
58
- const cliPath = path.join(__dirname, '../cli.js');
59
-
60
- test('should print help message', (done) => {
61
- exec(`node ${cliPath} --help`, (error, stdout, stderr) => {
62
- expect(error).toBeNull();
63
- expect(stdout).toContain('苑广山的个人应用启动器');
64
- expect(stdout).toContain('使用方法:');
65
- done();
66
- });
67
- });
68
-
69
- test('should list apps', (done) => {
70
- exec(`node ${cliPath} list`, (error, stdout, stderr) => {
71
- expect(error).toBeNull();
72
- expect(stdout).toContain('苑广山的应用列表');
73
- expect(stdout).toContain('shici');
74
- expect(stdout).toContain('dict');
75
- done();
76
- });
77
- });
78
- });
@@ -1,91 +0,0 @@
1
- const fs = require('fs');
2
- const yuangs = require('../index.js');
3
- const path = require('path');
4
- const os = require('os');
5
-
6
- jest.mock('fs');
7
-
8
- describe('Module: Macros', () => {
9
- const mockMacrosFile = path.join(os.homedir(), '.yuangs_macros.json');
10
-
11
- beforeEach(() => {
12
- jest.clearAllMocks();
13
- // Setup default mock implementation
14
- fs.existsSync.mockReturnValue(false);
15
- fs.readFileSync.mockReturnValue('{}');
16
- fs.writeFileSync.mockReturnValue(undefined);
17
- // We need to unmock path and os if they were mocked, but we only mocked fs
18
- });
19
-
20
- test('should get empty macros when file does not exist', () => {
21
- fs.existsSync.mockReturnValue(false);
22
- const macros = yuangs.getMacros();
23
- expect(macros).toEqual({});
24
- expect(fs.existsSync).toHaveBeenCalledWith(mockMacrosFile);
25
- });
26
-
27
- test('should save a new macro', () => {
28
- fs.existsSync.mockReturnValue(false); // File doesn't exist yet
29
-
30
- const result = yuangs.saveMacro('test', 'echo hello', 'description');
31
-
32
- expect(result).toBe(true);
33
- expect(fs.writeFileSync).toHaveBeenCalled();
34
-
35
- const [filePath, content] = fs.writeFileSync.mock.calls[0];
36
- expect(filePath).toBe(mockMacrosFile);
37
-
38
- const data = JSON.parse(content);
39
- expect(data).toHaveProperty('test');
40
- expect(data.test.commands).toBe('echo hello');
41
- expect(data.test.description).toBe('description');
42
- expect(data.test).toHaveProperty('createdAt');
43
- });
44
-
45
- test('should retrieve existing macros', () => {
46
- const mockData = {
47
- "demo": {
48
- "commands": "ls -la",
49
- "description": "list files"
50
- }
51
- };
52
- fs.existsSync.mockReturnValue(true);
53
- fs.readFileSync.mockReturnValue(JSON.stringify(mockData));
54
-
55
- const macros = yuangs.getMacros();
56
- expect(macros).toEqual(mockData);
57
- });
58
-
59
- test('should delete a macro', () => {
60
- const mockData = {
61
- "todelete": { "commands": "rm -rf /" },
62
- "keep": { "commands": "echo safe" }
63
- };
64
- fs.existsSync.mockReturnValue(true);
65
- fs.readFileSync.mockReturnValue(JSON.stringify(mockData));
66
-
67
- const result = yuangs.deleteMacro('todelete');
68
-
69
- expect(result).toBe(true);
70
- expect(fs.writeFileSync).toHaveBeenCalled();
71
-
72
- const [filePath, content] = fs.writeFileSync.mock.calls[0];
73
- const savedData = JSON.parse(content);
74
- expect(savedData).not.toHaveProperty('todelete');
75
- expect(savedData).toHaveProperty('keep');
76
- });
77
-
78
- test('should return false when deleting non-existent macro', () => {
79
- fs.existsSync.mockReturnValue(false); // Or true with empty object
80
-
81
- const result = yuangs.deleteMacro('nonexistent');
82
- expect(result).toBe(false);
83
- // Should not write to disk if nothing changed (optional optimization, but current implementation reads first)
84
- // Actually current implementation:
85
- // const macros = getMacros();
86
- // if (macros[name]) { ... }
87
- // getMacros returns {} if file not exists. macros['nonexistent'] is undefined.
88
- // So it returns false and does NOT call writeFileSync.
89
- expect(fs.writeFileSync).not.toHaveBeenCalled();
90
- });
91
- });
@@ -1,66 +0,0 @@
1
- const { spawn } = require('child_process');
2
- const path = require('path');
3
- const fs = require('fs');
4
- const os = require('os');
5
-
6
- // Ensure there is some history to test with
7
- const historyFile = path.join(os.homedir(), '.yuangs_cmd_history.json');
8
- const dummyHistory = [
9
- { question: "Test Question", command: "echo 'History Test Success'", time: "Now" }
10
- ];
11
-
12
- // Backup existing history if any
13
- let backupHistory = null;
14
- if (fs.existsSync(historyFile)) {
15
- backupHistory = fs.readFileSync(historyFile);
16
- }
17
- fs.writeFileSync(historyFile, JSON.stringify(dummyHistory));
18
-
19
- const cliPath = path.join(__dirname, 'cli.js');
20
- const child = spawn('node', [cliPath, 'history'], { stdio: 'pipe' });
21
-
22
- let output = '';
23
- let step = 0;
24
-
25
- child.stdout.on('data', (data) => {
26
- const str = data.toString();
27
- output += str;
28
- console.log(`[CLI Output] ${str}`);
29
-
30
- // Step 1: Wait for prompt and send index '1'
31
- if (step === 0 && str.includes('输入序号')) {
32
- console.log('[Test] Sending "1"...');
33
- child.stdin.write('1\n');
34
- step++;
35
- }
36
-
37
- // Step 2: Wait for pre-fill confirmation (readline write)
38
- // Note: Since we are in a non-TTY pipe, readline.write might behave differently or just output the text.
39
- // In our code: rlHistory.write(targetCommand) -> outputs to stdout.
40
- if (step === 1 && str.includes('echo \'History Test Success\'')) {
41
- console.log('[Test] Command pre-filled. Sending Enter to execute...');
42
- child.stdin.write('\n');
43
- step++;
44
- }
45
- });
46
-
47
- child.stderr.on('data', (data) => {
48
- console.error(`[CLI Error] ${data}`);
49
- });
50
-
51
- child.on('close', (code) => {
52
- console.log(`[Test] Process exited with code ${code}`);
53
-
54
- // Restore history
55
- if (backupHistory) {
56
- fs.writeFileSync(historyFile, backupHistory);
57
- } else {
58
- fs.unlinkSync(historyFile);
59
- }
60
-
61
- if (output.includes('History Test Success')) {
62
- console.log('✅ Interactive History Test PASSED');
63
- } else {
64
- console.log('❌ Interactive History Test FAILED');
65
- }
66
- });
@@ -1,67 +0,0 @@
1
- const { spawn } = require('child_process');
2
- const path = require('path');
3
- const fs = require('fs');
4
- const os = require('os');
5
-
6
- // Ensure there is some history to test with
7
- const historyFile = path.join(os.homedir(), '.yuangs_cmd_history.json');
8
- const dummyHistory = [
9
- { question: "First command", command: "echo 'I am command 1'", time: "Now" },
10
- { question: "Second command", command: "echo 'I am command 2'", time: "Now" },
11
- { question: "Third command", command: "echo 'I am command 3'", time: "Now" }
12
- ];
13
-
14
- // Backup existing history if any
15
- let backupHistory = null;
16
- if (fs.existsSync(historyFile)) {
17
- backupHistory = fs.readFileSync(historyFile);
18
- }
19
- fs.writeFileSync(historyFile, JSON.stringify(dummyHistory));
20
-
21
- const cliPath = path.join(__dirname, 'cli.js');
22
- // Run: yuangs history
23
- const child = spawn('node', [cliPath, 'history'], { stdio: 'pipe' });
24
-
25
- let output = '';
26
- let step = 0;
27
-
28
- child.stdout.on('data', (data) => {
29
- const str = data.toString();
30
- output += str;
31
- console.log(`[CLI Output] ${str}`);
32
-
33
- // Step 1: Wait for prompt and send index '2' (Select standard middleware item)
34
- if (step === 0 && str.includes('输入序号')) {
35
- console.log('[Test] Sending "2"... (Selecting second item)');
36
- child.stdin.write('2\n');
37
- step++;
38
- }
39
-
40
- // Step 2: Wait for pre-fill confirmation
41
- if (step === 1 && str.includes('echo \'I am command 2\'')) {
42
- console.log('[Test] Command 2 pre-filled. Sending Enter to execute...');
43
- child.stdin.write('\n');
44
- step++;
45
- }
46
- });
47
-
48
- child.stderr.on('data', (data) => {
49
- console.error(`[CLI Error] ${data}`);
50
- });
51
-
52
- child.on('close', (code) => {
53
- console.log(`[Test] Process exited with code ${code}`);
54
-
55
- // Restore history
56
- if (backupHistory) {
57
- fs.writeFileSync(historyFile, backupHistory);
58
- } else {
59
- fs.unlinkSync(historyFile);
60
- }
61
-
62
- if (output.includes('I am command 2')) {
63
- console.log('✅ Interactive Multi-Item History Test PASSED');
64
- } else {
65
- console.log('❌ Interactive Multi-Item History Test FAILED');
66
- }
67
- });
@@ -1,11 +0,0 @@
1
- {
2
- "shici": "https://wealth.want.biz/shici/index.html",
3
- "dict": "https://wealth.want.biz/pages/dict.html",
4
- "pong": "https://wealth.want.biz/pages/pong.html",
5
- "github": "https://github.com",
6
- "calendar": "https://calendar.google.com",
7
- "mail": "https://mail.google.com",
8
- "aiProxyUrl": "https://aiproxy.want.biz/v1/chat/completions",
9
- "defaultModel": "Assistant",
10
- "accountType": "free"
11
- }