yuangs 1.3.23 → 1.3.29

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 CHANGED
@@ -63,6 +63,60 @@ yuangs ai
63
63
 
64
64
  Pong 游戏: https://wealth.want.biz/pages/pong.html
65
65
 
66
+ ## 配置自定义应用 (v1.3.24)
67
+
68
+ 从 v1.3.24 版本开始,您可以自定义应用列表,而无需修改源代码。
69
+
70
+ ### 配置文件格式
71
+
72
+ 创建一个 JSON 或 YAML 配置文件,支持以下格式之一:
73
+
74
+ 1. `yuangs.config.json` - 项目级别的 JSON 配置文件
75
+ 2. `.yuangs.json` - 项目级或用户主目录的 JSON 隐藏配置文件
76
+ 3. `yuangs.config.yaml` - 项目级别的 YAML 配置文件
77
+ 4. `yuangs.config.yml` - 项目级别的 YAML 配置文件
78
+ 5. `.yuangs.yaml` - 项目级或用户主目录的 YAML 隐藏配置文件
79
+ 6. `.yuangs.yml` - 项目级或用户主目录的 YAML 隐藏配置文件
80
+ 7. `~/.yuangs.json` - 全局用户 JSON 配置文件
81
+ 8. `~/.yuangs.yaml` - 全局用户 YAML 配置文件
82
+ 9. `~/.yuangs.yml` - 全局用户 YAML 配置文件
83
+
84
+ 配置文件示例:
85
+
86
+ ```json
87
+ {
88
+ "shici": "https://wealth.want.biz/shici/index.html",
89
+ "dict": "https://wealth.want.biz/pages/dict.html",
90
+ "pong": "https://wealth.want.biz/pages/pong.html",
91
+ "github": "https://github.com",
92
+ "calendar": "https://calendar.google.com",
93
+ "mail": "https://mail.google.com"
94
+ }
95
+ ```
96
+
97
+ ### 配置文件优先级
98
+
99
+ 配置文件按以下优先级顺序查找(高到低):
100
+
101
+ 1. 当前工作目录下的 `yuangs.config.json`
102
+ 2. 当前工作目录下的 `.yuangs.json`
103
+ 3. 用户主目录下的 `.yuangs.json`
104
+ 4. 项目目录下的 `yuangs.config.json`
105
+ 5. 项目目录下的 `.yuangs.json`
106
+ 6. 如果没有配置文件,则使用默认应用列表
107
+
108
+ ### 使用自定义应用
109
+
110
+ 创建配置文件后,您可以使用任何定义的应用名称作为命令:
111
+
112
+ ```bash
113
+ yuangs github # 打开 GitHub
114
+ yuangs calendar # 打开日历
115
+ yuangs mail # 打开邮箱
116
+ ```
117
+
118
+ 使用 `yuangs list` 命令查看当前加载的所有应用。
119
+
66
120
  ## 近期主要更新日志
67
121
 
68
122
  ### v1.3.22 (2025-11-30)
package/cli.js CHANGED
@@ -13,10 +13,24 @@ function printHelp() {
13
13
  console.log(chalk.gray('仓库地址: https://www.npmjs.com/package/yuangs?activeTab=readme\n'));
14
14
  console.log(chalk.white('使用方法:') + chalk.gray(' yuangs <命令> [参数]\n'));
15
15
  console.log(chalk.bold('命令列表:'));
16
+
17
+ // Show default commands
16
18
  console.log(` ${chalk.green('shici')} 打开古诗词 PWA`);
17
19
  console.log(` ${chalk.green('dict')} 打开英语词典`);
18
20
  console.log(` ${chalk.green('pong')} 打开 Pong 游戏`);
19
21
  console.log(` ${chalk.green('list')} 列出所有应用链接`);
22
+
23
+ // Show dynamically configured apps
24
+ const dynamicApps = Object.keys(yuangs.urls).filter(key =>
25
+ !['shici', 'dict', 'pong'].includes(key)
26
+ );
27
+ if (dynamicApps.length > 0) {
28
+ console.log(chalk.bold('\n自定义应用:'));
29
+ dynamicApps.forEach(app => {
30
+ console.log(` ${chalk.green(app)} 打开 ${app} 应用`);
31
+ });
32
+ }
33
+
20
34
  console.log(` ${chalk.green('ai')} "<问题>" 向 AI 提问(不写问题进入交互模式)`);
21
35
  console.log(` ${chalk.gray('--model, -m <模型名称>')} 指定 AI 模型 (可选)`);
22
36
  console.log(` ${chalk.gray('-p -f -l')} 指定 pro,flash,lite 模型 (可选)`);
@@ -26,6 +40,7 @@ function printHelp() {
26
40
  console.log(` ${chalk.gray('/history')} 查看对话历史\n`);
27
41
  console.log(chalk.gray('AI 示例: yuangs ai "你好" --model gemini-pro-latest'));
28
42
  console.log(chalk.gray('普通示例: yuangs shici\n'));
43
+ console.log(chalk.gray('配置文件: 您可以通过创建 yuangs.config.json 或 ~/.yuangs.json 来自定义应用列表\n'));
29
44
  }
30
45
 
31
46
  function printSuccess(app, url) {
@@ -185,17 +200,20 @@ async function handleAICommand() {
185
200
  await askOnce(question, model);
186
201
  }
187
202
 
203
+ // Check if the command matches one of the configured apps
204
+ const isAppCommand = Object.keys(yuangs.urls).includes(command);
205
+
188
206
  switch (command) {
189
207
  case 'shici':
190
- printSuccess('古诗词应用', yuangs.urls.shici);
208
+ printSuccess('古诗词应用', yuangs.urls.shici || 'N/A');
191
209
  yuangs.openShici();
192
210
  break;
193
211
  case 'dict':
194
- printSuccess('英语词典', yuangs.urls.dict);
212
+ printSuccess('英语词典', yuangs.urls.dict || 'N/A');
195
213
  yuangs.openDict();
196
214
  break;
197
215
  case 'pong':
198
- printSuccess('Pong 游戏', yuangs.urls.pong);
216
+ printSuccess('Pong 游戏', yuangs.urls.pong || 'N/A');
199
217
  yuangs.openPong();
200
218
  break;
201
219
  case 'list':
@@ -212,7 +230,18 @@ switch (command) {
212
230
  case 'help':
213
231
  case '--help':
214
232
  case '-h':
215
- default:
216
233
  printHelp();
217
234
  break;
235
+ default:
236
+ // If it's an app command but not one of the named ones, handle it with the dynamic function
237
+ if (isAppCommand) {
238
+ printSuccess(command, yuangs.urls[command]);
239
+ yuangs.openApp(command);
240
+ } else if (command) {
241
+ console.log(chalk.red(`\n错误: 未知命令 '${command}'\n`));
242
+ printHelp();
243
+ } else {
244
+ printHelp();
245
+ }
246
+ break;
218
247
  }
package/index.js CHANGED
@@ -1,15 +1,75 @@
1
1
  const { exec } = require('child_process');
2
2
  const axios = require('axios');
3
+ const fs = require('fs');
4
+ const path = require('path');
3
5
 
4
6
  // Store conversation history
7
+ // 存储结构标准为: [{ role: 'user', content: '...' }, { role: 'assistant', content: '...' }]
5
8
  let conversationHistory = [];
6
9
 
7
- const APPS = {
10
+ // Default apps (fallback if no config file exists)
11
+ const DEFAULT_APPS = {
8
12
  shici: 'https://wealth.want.biz/shici/index.html',
9
13
  dict: 'https://wealth.want.biz/pages/dict.html',
10
14
  pong: 'https://wealth.want.biz/pages/pong.html'
11
15
  };
12
16
 
17
+ // Load apps from configuration file
18
+ function loadAppsConfig() {
19
+ // Define possible config file locations (JSON and YAML)
20
+ const configPaths = [
21
+ path.join(process.cwd(), 'yuangs.config.json'), // Current working directory
22
+ path.join(process.cwd(), '.yuangs.json'), // Current working directory dot file
23
+ path.join(process.cwd(), 'yuangs.config.yaml'), // Current working directory YAML
24
+ path.join(process.cwd(), 'yuangs.config.yml'), // Current working directory YAML
25
+ path.join(process.cwd(), '.yuangs.yaml'), // Current working directory dot YAML
26
+ path.join(process.cwd(), '.yuangs.yml'), // Current working directory dot YAML
27
+ path.join(require('os').homedir(), '.yuangs.json'), // User home directory
28
+ path.join(require('os').homedir(), '.yuangs.yaml'), // User home directory YAML
29
+ path.join(require('os').homedir(), '.yuangs.yml'), // User home directory YAML
30
+ path.join(__dirname, 'yuangs.config.json'), // Project directory
31
+ path.join(__dirname, '.yuangs.json'), // Project directory dot file
32
+ path.join(__dirname, 'yuangs.config.yaml'), // Project directory YAML
33
+ path.join(__dirname, 'yuangs.config.yml') // Project directory YAML
34
+ ];
35
+
36
+ for (const configPath of configPaths) {
37
+ if (fs.existsSync(configPath)) {
38
+ try {
39
+ const configContent = fs.readFileSync(configPath, 'utf8');
40
+
41
+ // Determine if it's JSON or YAML based on file extension
42
+ let config;
43
+ if (configPath.endsWith('.json')) {
44
+ config = JSON.parse(configContent);
45
+ } else {
46
+ // For YAML files, we need to require the yaml parser
47
+ let yaml;
48
+ try {
49
+ yaml = require('js-yaml');
50
+ } catch (yamlError) {
51
+ console.warn(`Warning: js-yaml not installed, skipping YAML file ${configPath}`);
52
+ console.warn('Install js-yaml with: npm install js-yaml');
53
+ continue; // Skip this file and try the next config file
54
+ }
55
+ config = yaml.load(configContent);
56
+ }
57
+
58
+ // If config has an 'apps' property, use it, otherwise use the whole config as apps
59
+ return config.apps || config;
60
+ } catch (error) {
61
+ console.warn(`Warning: Could not parse config file at ${configPath}:`, error.message);
62
+ // Continue to next config file
63
+ }
64
+ }
65
+ }
66
+
67
+ // If no config file is found, use default apps
68
+ return DEFAULT_APPS;
69
+ }
70
+
71
+ const APPS = loadAppsConfig();
72
+
13
73
  function openUrl(url) {
14
74
  let command;
15
75
  switch (process.platform) {
@@ -40,60 +100,87 @@ function getConversationHistory() {
40
100
  return conversationHistory;
41
101
  }
42
102
 
103
+ /**
104
+ * 获取 AI 回复 (OpenAI 兼容接口版)
105
+ */
43
106
  async function getAIAnswer(question, model, includeHistory = true) {
44
- const url = 'https://aiproxy.want.biz/ai/explain';
45
-
46
- // Prepare the prompt with conversation history if enabled
47
- let prompt;
48
- if (includeHistory) {
49
- prompt = JSON.stringify({
50
- history: conversationHistory,
51
- query: question
52
- }, null, 2);
53
- } else {
54
- // If not including history, still use JSON format for consistency but with empty history
55
- prompt = JSON.stringify({
56
- history: [],
57
- query: question
58
- }, null, 2);
59
- }
107
+ // 1. 修改接口地址为 OpenAI 标准兼容路径
108
+ const url = 'https://aiproxy.want.biz/v1/chat/completions';
60
109
 
110
+ // 2. 准备 Headers (包含客户端标识)
61
111
  const headers = {
112
+ 'Content-Type': 'application/json',
113
+ 'X-Client-ID': 'npm_yuangs', // 客户端 标识
114
+ 'Origin': 'https://cli.want.biz', // 配合后端白名单
62
115
  'Referer': 'https://cli.want.biz/',
63
116
  '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',
64
- 'Accept': 'application/json',
65
- 'Content-Type': 'application/json'
117
+ 'Accept': 'application/json'
66
118
  };
67
119
 
120
+ // 3. 构建 messages 数组 (上下文 + 当前问题)
121
+ let messages = [];
122
+ if (includeHistory) {
123
+ // history 已经是 [{role, content}, ...] 格式,直接展开
124
+ messages = [...conversationHistory];
125
+ }
126
+
127
+ // 添加当前用户提问
128
+ messages.push({ role: 'user', content: question });
129
+
130
+ // 4. 构建 OpenAI 标准请求体
68
131
  const data = {
69
- text: prompt,
70
- model: model || "gemini-flash-lite-latest"
132
+ model: model || "gemini-flash-lite-latest",
133
+ messages: messages,
134
+ stream: false
71
135
  };
72
136
 
73
137
  try {
138
+ // 发送请求
74
139
  const response = await axios.post(url, data, { headers });
75
- const answer = response.data;
140
+
141
+ // 5. 解析 OpenAI 格式响应 (choices[0].message.content)
142
+ const aiContent = response.data?.choices?.[0]?.message?.content;
143
+
144
+ if (!aiContent) {
145
+ throw new Error('Invalid response structure from AI API');
146
+ }
76
147
 
77
- // Add the user's question to the conversation history
148
+ // 6. 更新历史记录
149
+ // 只有请求成功才记录
78
150
  addToConversationHistory('user', question);
151
+ addToConversationHistory('assistant', aiContent);
79
152
 
80
- // Add the AI response to the conversation history
81
- if (answer && answer.explanation) {
82
- addToConversationHistory('assistant', answer.explanation);
83
- }
153
+ // 返回结果
154
+ // 为了兼容旧代码可能使用的 .explanation 属性,这里做一层包装
155
+ return {
156
+ explanation: aiContent, // 兼容字段
157
+ content: aiContent, // 标准字段建议
158
+ raw: response.data // 原始响应
159
+ };
84
160
 
85
- return answer;
86
161
  } catch (error) {
87
- console.error('AI 请求失败:', error.response?.data?.message || error.message || '未知错误');
162
+ const errorMsg = error.response?.data?.error?.message || error.response?.data?.message || error.message || '未知错误';
163
+ console.error('AI 请求失败:', errorMsg);
88
164
  return null;
89
165
  }
90
166
  }
91
167
 
92
168
  module.exports = {
93
169
  urls: APPS,
94
- openShici: () => openUrl(APPS.shici),
95
- openDict: () => openUrl(APPS.dict),
96
- openPong: () => openUrl(APPS.pong),
170
+ // Dynamic function to open any app by key
171
+ openApp: (appKey) => {
172
+ const url = APPS[appKey];
173
+ if (url) {
174
+ openUrl(url);
175
+ return true;
176
+ }
177
+ console.error(`App '${appKey}' not found`);
178
+ return false;
179
+ },
180
+ // Specific functions for default apps (for backward compatibility)
181
+ openShici: () => openUrl(APPS.shici || DEFAULT_APPS.shici),
182
+ openDict: () => openUrl(APPS.dict || DEFAULT_APPS.dict),
183
+ openPong: () => openUrl(APPS.pong || DEFAULT_APPS.pong),
97
184
  listApps: () => {
98
185
  console.log('--- YGS Apps ---');
99
186
  Object.entries(APPS).forEach(([key, url]) => console.log(`${key}: ${url}`));
@@ -102,4 +189,4 @@ module.exports = {
102
189
  addToConversationHistory,
103
190
  clearConversationHistory,
104
191
  getConversationHistory
105
- };
192
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yuangs",
3
- "version": "1.3.23",
3
+ "version": "1.3.29",
4
4
  "description": "苑广山的个人应用集合 CLI(彩色版)",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "tools",
16
16
  "colorful"
17
17
  ],
18
- "homepage": "https://github.com/yuanguangshan/npm_yuangs#readme",
18
+ "homepage": "https://github.com/yauangshan/npm_yuangs#readme",
19
19
  "repository": {
20
20
  "type": "git",
21
21
  "url": "git+https://github.com/yuanguangshan/npm_yuangs.git"
@@ -27,7 +27,8 @@
27
27
  "license": "ISC",
28
28
  "dependencies": {
29
29
  "axios": "^1.13.2",
30
- "chalk": "^4.1.2"
30
+ "chalk": "^4.1.2",
31
+ "js-yaml": "^4.1.0"
31
32
  },
32
33
  "devDependencies": {
33
34
  "jest": "^29.7.0"
@@ -14,6 +14,16 @@ describe('Module: index.js', () => {
14
14
  expect(yuangs.urls.shici).toContain('shici/index.html');
15
15
  });
16
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
+
17
27
  test('should manage conversation history correctly', () => {
18
28
  yuangs.addToConversationHistory('user', 'hello');
19
29
  let history = yuangs.getConversationHistory();
@@ -0,0 +1,8 @@
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
+ }
@@ -0,0 +1,18 @@
1
+ # Example configuration file for yuangs CLI
2
+ # Add your custom applications here
3
+
4
+ shici: "https://wealth.want.biz/shici/index.html"
5
+ dict: "https://wealth.want.biz/pages/dict.html"
6
+ pong: "https://wealth.want.biz/pages/pong.html"
7
+ github: "https://github.com"
8
+ calendar: "https://calendar.google.com"
9
+ mail: "https://mail.google.com"
10
+
11
+ # You can also use the apps property if you prefer to group them
12
+ # apps:
13
+ # shici: "https://wealth.want.biz/shici/index.html"
14
+ # dict: "https://wealth.want.biz/pages/dict.html"
15
+ # pong: "https://wealth.want.biz/pages/pong.html"
16
+ # github: "https://github.com"
17
+ # calendar: "https://calendar.google.com"
18
+ # mail: "https://mail.google.com"
@@ -0,0 +1,9 @@
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
+ "mail": "https://mail.google.com",
6
+ "github": "https://github.com",
7
+ "calendar": "https://calendar.google.com",
8
+ "homepage": "https://i.want.biz"
9
+ }