yuangs 1.3.30 → 1.3.32

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/cli.js CHANGED
@@ -34,11 +34,13 @@ function printHelp() {
34
34
  console.log(` ${chalk.green('ai')} "<问题>" 向 AI 提问(不写问题进入交互模式)`);
35
35
  console.log(` ${chalk.gray('--model, -m <模型名称>')} 指定 AI 模型 (可选)`);
36
36
  console.log(` ${chalk.gray('-p -f -l')} 指定 pro,flash,lite 模型 (可选)`);
37
+ console.log(` ${chalk.gray('-e <描述>')} 生成 Linux 命令`);
37
38
  console.log(` ${chalk.green('help')} 显示帮助信息\n`);
38
39
  console.log(chalk.bold('AI 交互模式命令:'));
39
40
  console.log(` ${chalk.gray('/clear')} 清空对话历史`);
40
41
  console.log(` ${chalk.gray('/history')} 查看对话历史\n`);
41
42
  console.log(chalk.gray('AI 示例: yuangs ai "你好" --model gemini-pro-latest'));
43
+ console.log(chalk.gray('AI 生成命令: yuangs ai -e "查看当前目录"'));
42
44
  console.log(chalk.gray('普通示例: yuangs shici\n'));
43
45
  console.log(chalk.gray('配置文件: 您可以通过创建 yuangs.config.json 或 ~/.yuangs.json 来自定义应用列表\n'));
44
46
  }
@@ -99,23 +101,34 @@ async function askOnce(question, model) {
99
101
  async function handleAICommand() {
100
102
  const commandArgs = args.slice(1);
101
103
 
102
- let model = null; // Default model will be handled in index.js
104
+ let model = null;
103
105
  let questionParts = commandArgs;
106
+ let isExecMode = false;
107
+
108
+ // Check for -e flag
109
+ const execIndex = commandArgs.indexOf('-e');
110
+ if (execIndex !== -1) {
111
+ isExecMode = true;
112
+ // removing -e from args
113
+ const before = commandArgs.slice(0, execIndex);
114
+ const after = commandArgs.slice(execIndex + 1);
115
+ questionParts = [...before, ...after];
116
+ }
104
117
 
105
118
  // Check for shorthand model flags first
106
- const proIndex = commandArgs.indexOf('-p');
107
- const flashIndex = commandArgs.indexOf('-f');
108
- const liteIndex = commandArgs.indexOf('-l');
119
+ const proIndex = questionParts.indexOf('-p');
120
+ const flashIndex = questionParts.indexOf('-f');
121
+ const liteIndex = questionParts.indexOf('-l');
109
122
 
110
123
  if (proIndex !== -1) {
111
124
  model = 'gemini-pro-latest';
112
- questionParts = commandArgs.filter((_, index) => index !== proIndex);
125
+ questionParts = questionParts.filter((_, index) => index !== proIndex);
113
126
  } else if (flashIndex !== -1) {
114
127
  model = 'gemini-flash-latest';
115
- questionParts = commandArgs.filter((_, index) => index !== flashIndex);
128
+ questionParts = questionParts.filter((_, index) => index !== flashIndex);
116
129
  } else if (liteIndex !== -1) {
117
130
  model = 'gemini-flash-lite-latest';
118
- questionParts = commandArgs.filter((_, index) => index !== liteIndex);
131
+ questionParts = questionParts.filter((_, index) => index !== liteIndex);
119
132
  }
120
133
 
121
134
  // If shorthand flags are not used, check for --model or -m
@@ -133,6 +146,39 @@ async function handleAICommand() {
133
146
 
134
147
  const question = questionParts.join(' ').trim();
135
148
 
149
+ // Special handling for execution mode
150
+ if (isExecMode) {
151
+ if (!question) {
152
+ console.log(chalk.red('错误: 使用 -e 参数时必需提供描述。'));
153
+ return;
154
+ }
155
+
156
+ const startTime = Date.now();
157
+ let i = 0;
158
+ const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
159
+ const interval = setInterval(() => {
160
+ process.stdout.write(chalk.cyan(`\r${spinner[i++ % spinner.length]} 正在生成命令...`));
161
+ }, 100);
162
+
163
+ const command = await yuangs.generateCommand(question, model);
164
+ clearInterval(interval);
165
+
166
+ if (process.stdout.clearLine) {
167
+ process.stdout.clearLine(0);
168
+ process.stdout.cursorTo(0);
169
+ } else {
170
+ process.stdout.write('\r');
171
+ }
172
+
173
+ if (command) {
174
+ console.log(chalk.gray('生成命令:'));
175
+ console.log(chalk.bold.green(`> ${command}`));
176
+ } else {
177
+ console.log(chalk.yellow('未能生成有效的命令。'));
178
+ }
179
+ return;
180
+ }
181
+
136
182
  // 如果用户直接输入 `yuangs ai`,进入交互式模式
137
183
  if (!question) {
138
184
  console.log(chalk.bold.cyan('\n🤖 进入 AI 交互模式 (输入 exit 退出)\n'));
package/index.js CHANGED
@@ -101,58 +101,53 @@ function getConversationHistory() {
101
101
  }
102
102
 
103
103
  /**
104
- * 获取 AI 回复 (OpenAI 兼容接口版)
104
+ * 通用 AI 调用函数 (OpenAI 兼容接口)
105
105
  */
106
- async function getAIAnswer(question, model, includeHistory = true) {
107
- // 1. 修改接口地址为 OpenAI 标准兼容路径
106
+ async function callAI_OpenAI(messages, model) {
108
107
  const url = 'https://aiproxy.want.biz/v1/chat/completions';
109
108
 
110
- // 2. 准备 Headers (包含客户端标识)
111
109
  const headers = {
112
110
  'Content-Type': 'application/json',
113
111
  'X-Client-ID': 'npm_yuangs', // 客户端 标识
114
112
  'Origin': 'https://cli.want.biz', // 配合后端白名单
115
113
  'Referer': 'https://cli.want.biz/',
116
- "account" : "free",
114
+ "account": "free",
117
115
  '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',
118
116
  'Accept': 'application/json'
119
117
  };
120
118
 
121
- // 3. 构建 messages 数组 (上下文 + 当前问题)
122
- let messages = [];
123
- if (includeHistory) {
124
- // history 已经是 [{role, content}, ...] 格式,直接展开
125
- messages = [...conversationHistory];
126
- }
127
-
128
- // 添加当前用户提问
129
- messages.push({ role: 'user', content: question });
130
-
131
- // 4. 构建 OpenAI 标准请求体
132
119
  const data = {
133
120
  model: model || "gemini-flash-lite-latest",
134
121
  messages: messages,
135
122
  stream: false
136
123
  };
137
124
 
125
+ return await axios.post(url, data, { headers });
126
+ }
127
+
128
+ /**
129
+ * 获取 AI 回复
130
+ */
131
+ async function getAIAnswer(question, model, includeHistory = true) {
132
+ // 构建 messages 数组 (上下文 + 当前问题)
133
+ let messages = [];
134
+ if (includeHistory) {
135
+ messages = [...conversationHistory];
136
+ }
137
+ messages.push({ role: 'user', content: question });
138
+
138
139
  try {
139
- // 发送请求
140
- const response = await axios.post(url, data, { headers });
141
-
142
- // 5. 解析 OpenAI 格式响应 (choices[0].message.content)
140
+ const response = await callAI_OpenAI(messages, model);
143
141
  const aiContent = response.data?.choices?.[0]?.message?.content;
144
142
 
145
143
  if (!aiContent) {
146
144
  throw new Error('Invalid response structure from AI API');
147
145
  }
148
146
 
149
- // 6. 更新历史记录
150
- // 只有请求成功才记录
147
+ // 只有请求成功才记录历史
151
148
  addToConversationHistory('user', question);
152
149
  addToConversationHistory('assistant', aiContent);
153
150
 
154
- // 返回结果
155
- // 为了兼容旧代码可能使用的 .explanation 属性,这里做一层包装
156
151
  return {
157
152
  explanation: aiContent, // 兼容字段
158
153
  content: aiContent, // 标准字段建议
@@ -166,6 +161,49 @@ async function getAIAnswer(question, model, includeHistory = true) {
166
161
  }
167
162
  }
168
163
 
164
+ async function generateCommand(instruction, model) {
165
+ // 构造 System Prompt (通过 system role 或者直接在 user message 中强调)
166
+ // Gemini 有时对 system role 支持不同,但在 OpenAI 兼容接口下通常支持 system
167
+ const messages = [
168
+ {
169
+ role: 'system',
170
+ content: `You are a Linux command generator. Convert the user's natural language request into a single, executable Linux command.
171
+ IMPORTANT: Output ONLY the command. Do not check for safety. Do not output markdown code blocks (no backticks). Do not explain.`
172
+ },
173
+ {
174
+ role: 'user',
175
+ content: `Request: ${instruction}`
176
+ }
177
+ ];
178
+
179
+ try {
180
+ const response = await callAI_OpenAI(messages, model);
181
+ const aiContent = response.data?.choices?.[0]?.message?.content;
182
+
183
+ if (aiContent) {
184
+ // Clean up the output just in case the AI adds markdown or whitespace
185
+ let command = aiContent.trim();
186
+ // Remove wrapping backticks if present
187
+ if (command.startsWith('`') && command.endsWith('`')) {
188
+ command = command.slice(1, -1);
189
+ }
190
+ if (command.startsWith('```') && command.endsWith('```')) {
191
+ command = command.split('\n').filter(line => !line.startsWith('```')).join('\n').trim();
192
+ }
193
+ // If it starts with a shell prefix like "$ " or "> ", remove it
194
+ if (command.startsWith('$ ')) command = command.slice(2);
195
+ if (command.startsWith('> ')) command = command.slice(2);
196
+
197
+ return command;
198
+ }
199
+ return null;
200
+ } catch (error) {
201
+ // 命令生成失败不打印过多错误,返回 null 即可
202
+ // console.error('AI 生成命令失败:', error.message);
203
+ return null;
204
+ }
205
+ }
206
+
169
207
  module.exports = {
170
208
  urls: APPS,
171
209
  // Dynamic function to open any app by key
@@ -189,5 +227,6 @@ module.exports = {
189
227
  getAIAnswer,
190
228
  addToConversationHistory,
191
229
  clearConversationHistory,
192
- getConversationHistory
193
- };
230
+ getConversationHistory,
231
+ generateCommand
232
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yuangs",
3
- "version": "1.3.30",
3
+ "version": "1.3.32",
4
4
  "description": "苑广山的个人应用集合 CLI(彩色版)",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,9 +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
- "mail": "https://mail.google.com",
6
- "github": "https://github.com",
7
- "calendar": "https://calendar.google.com",
8
- "homepage": "https://i.want.biz"
9
- }