@yivan-lab/pretty-please 1.1.0 → 1.3.0

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 (62) hide show
  1. package/README.md +390 -1
  2. package/bin/pls.tsx +1255 -123
  3. package/dist/bin/pls.js +1098 -103
  4. package/dist/package.json +4 -4
  5. package/dist/src/alias.d.ts +41 -0
  6. package/dist/src/alias.js +240 -0
  7. package/dist/src/chat-history.js +10 -1
  8. package/dist/src/components/Chat.js +54 -26
  9. package/dist/src/components/CodeColorizer.js +26 -20
  10. package/dist/src/components/CommandBox.js +19 -8
  11. package/dist/src/components/ConfirmationPrompt.js +2 -1
  12. package/dist/src/components/Duration.js +2 -1
  13. package/dist/src/components/InlineRenderer.js +2 -1
  14. package/dist/src/components/MarkdownDisplay.js +2 -1
  15. package/dist/src/components/MultiStepCommandGenerator.d.ts +3 -1
  16. package/dist/src/components/MultiStepCommandGenerator.js +20 -10
  17. package/dist/src/components/TableRenderer.js +2 -1
  18. package/dist/src/config.d.ts +33 -3
  19. package/dist/src/config.js +83 -34
  20. package/dist/src/mastra-agent.d.ts +1 -0
  21. package/dist/src/mastra-agent.js +3 -11
  22. package/dist/src/mastra-chat.d.ts +13 -6
  23. package/dist/src/mastra-chat.js +31 -31
  24. package/dist/src/multi-step.d.ts +23 -7
  25. package/dist/src/multi-step.js +45 -26
  26. package/dist/src/prompts.d.ts +30 -4
  27. package/dist/src/prompts.js +218 -70
  28. package/dist/src/remote-history.d.ts +63 -0
  29. package/dist/src/remote-history.js +315 -0
  30. package/dist/src/remote.d.ts +113 -0
  31. package/dist/src/remote.js +634 -0
  32. package/dist/src/shell-hook.d.ts +58 -0
  33. package/dist/src/shell-hook.js +295 -26
  34. package/dist/src/ui/theme.d.ts +60 -23
  35. package/dist/src/ui/theme.js +544 -22
  36. package/dist/src/upgrade.d.ts +41 -0
  37. package/dist/src/upgrade.js +348 -0
  38. package/dist/src/utils/console.d.ts +4 -0
  39. package/dist/src/utils/console.js +89 -17
  40. package/package.json +4 -4
  41. package/src/alias.ts +301 -0
  42. package/src/chat-history.ts +11 -1
  43. package/src/components/Chat.tsx +71 -26
  44. package/src/components/CodeColorizer.tsx +27 -19
  45. package/src/components/CommandBox.tsx +26 -8
  46. package/src/components/ConfirmationPrompt.tsx +2 -1
  47. package/src/components/Duration.tsx +2 -1
  48. package/src/components/InlineRenderer.tsx +2 -1
  49. package/src/components/MarkdownDisplay.tsx +2 -1
  50. package/src/components/MultiStepCommandGenerator.tsx +25 -11
  51. package/src/components/TableRenderer.tsx +2 -1
  52. package/src/config.ts +126 -35
  53. package/src/mastra-agent.ts +3 -12
  54. package/src/mastra-chat.ts +40 -34
  55. package/src/multi-step.ts +62 -30
  56. package/src/prompts.ts +236 -78
  57. package/src/remote-history.ts +390 -0
  58. package/src/remote.ts +800 -0
  59. package/src/shell-hook.ts +339 -26
  60. package/src/ui/theme.ts +632 -23
  61. package/src/upgrade.ts +397 -0
  62. package/src/utils/console.ts +99 -17
@@ -7,13 +7,14 @@ import { detectBuiltin, formatBuiltins } from '../builtin-detector.js';
7
7
  import { CommandBox } from './CommandBox.js';
8
8
  import { ConfirmationPrompt } from './ConfirmationPrompt.js';
9
9
  import { Duration } from './Duration.js';
10
- import { theme } from '../ui/theme.js';
10
+ import { getCurrentTheme } from '../ui/theme.js';
11
11
  import { getConfig } from '../config.js';
12
12
  /**
13
13
  * MultiStepCommandGenerator 组件 - 多步骤命令生成
14
14
  * 每次只生成一个命令,支持 continue 机制
15
15
  */
16
- export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], currentStepNumber = 1, onStepComplete, }) => {
16
+ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], currentStepNumber = 1, remoteContext, isRemote = false, onStepComplete, }) => {
17
+ const theme = getCurrentTheme();
17
18
  const [state, setState] = useState({ type: 'thinking' });
18
19
  const [thinkDuration, setThinkDuration] = useState(0);
19
20
  const [debugInfo, setDebugInfo] = useState(null);
@@ -27,7 +28,7 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
27
28
  // 初始化:调用 Mastra 生成命令
28
29
  useEffect(() => {
29
30
  const thinkStart = Date.now();
30
- generateMultiStepCommand(prompt, previousSteps, { debug })
31
+ generateMultiStepCommand(prompt, previousSteps, { debug, remoteContext })
31
32
  .then((result) => {
32
33
  const thinkEnd = Date.now();
33
34
  setThinkDuration(thinkEnd - thinkStart);
@@ -48,10 +49,10 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
48
49
  }, 100);
49
50
  return;
50
51
  }
51
- // 检测 builtin(优先检测)
52
+ // 检测 builtin(优先检测,但远程执行时跳过)
52
53
  const { hasBuiltin, builtins } = detectBuiltin(result.stepData.command);
53
- if (hasBuiltin) {
54
- // 有 builtin,不管什么模式都不编辑,直接提示
54
+ if (hasBuiltin && !isRemote) {
55
+ // 有 builtin 且是本地执行,不管什么模式都不编辑,直接提示
55
56
  setState({
56
57
  type: 'showing_command',
57
58
  stepData: result.stepData,
@@ -97,7 +98,7 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
97
98
  });
98
99
  }, 100);
99
100
  });
100
- }, [prompt, previousSteps, debug]);
101
+ }, [prompt, previousSteps, debug, remoteContext]);
101
102
  // 处理确认
102
103
  const handleConfirm = () => {
103
104
  if (state.type === 'showing_command') {
@@ -175,7 +176,9 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
175
176
  React.createElement(Text, { color: theme.info },
176
177
  React.createElement(Spinner, { type: "dots" }),
177
178
  ' ',
178
- currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`))),
179
+ remoteContext
180
+ ? (currentStepNumber === 1 ? `正在为 ${remoteContext.name} 思考...` : `正在规划步骤 ${currentStepNumber} (${remoteContext.name})...`)
181
+ : (currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`)))),
179
182
  state.type !== 'thinking' && thinkDuration > 0 && (React.createElement(Box, null,
180
183
  React.createElement(Text, { color: theme.success }, "\u2713 \u601D\u8003\u5B8C\u6210 "),
181
184
  React.createElement(Duration, { ms: thinkDuration }))),
@@ -192,6 +195,13 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
192
195
  React.createElement(Text, { color: theme.text.secondary },
193
196
  "\u5DF2\u6267\u884C\u6B65\u9AA4\u6570: ",
194
197
  debugInfo.previousStepsCount))),
198
+ debugInfo.remoteContext && (React.createElement(Box, { marginTop: 1 },
199
+ React.createElement(Text, { color: theme.text.secondary },
200
+ "\u8FDC\u7A0B\u670D\u52A1\u5668: ",
201
+ debugInfo.remoteContext.name,
202
+ " (",
203
+ debugInfo.remoteContext.sysInfo.os,
204
+ ")"))),
195
205
  React.createElement(Box, { marginTop: 1 },
196
206
  React.createElement(Text, { color: theme.text.secondary }, "AI \u8FD4\u56DE\u7684 JSON:")),
197
207
  React.createElement(Text, { color: theme.text.dim }, JSON.stringify(debugInfo.response, null, 2)),
@@ -208,7 +218,7 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
208
218
  "\u4E0B\u4E00\u6B65: ",
209
219
  state.stepData.nextStepHint)))),
210
220
  React.createElement(CommandBox, { command: state.stepData.command }),
211
- (() => {
221
+ !isRemote && (() => {
212
222
  const { hasBuiltin, builtins } = detectBuiltin(state.stepData.command);
213
223
  if (hasBuiltin) {
214
224
  return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
@@ -220,7 +230,7 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
220
230
  }
221
231
  return null;
222
232
  })(),
223
- !detectBuiltin(state.stepData.command).hasBuiltin && (React.createElement(ConfirmationPrompt, { prompt: "\u6267\u884C\uFF1F", onConfirm: handleConfirm, onCancel: handleCancel, onEdit: handleEdit })))),
233
+ (isRemote || !detectBuiltin(state.stepData.command).hasBuiltin) && (React.createElement(ConfirmationPrompt, { prompt: "\u6267\u884C\uFF1F", onConfirm: handleConfirm, onCancel: handleCancel, onEdit: handleEdit })))),
224
234
  state.type === 'editing' && (React.createElement(React.Fragment, null,
225
235
  state.stepData.continue === true && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
226
236
  React.createElement(Text, { color: theme.text.secondary },
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import stringWidth from 'string-width';
4
- import { theme } from '../ui/theme.js';
4
+ import { getCurrentTheme } from '../ui/theme.js';
5
5
  import { RenderInline } from './InlineRenderer.js';
6
6
  /**
7
7
  * 计算纯文本长度(去除 markdown 标记)
@@ -40,6 +40,7 @@ function calculateColumnWidths(headers, rows, terminalWidth) {
40
40
  * 表格渲染组件
41
41
  */
42
42
  function TableRendererInternal({ headers, rows, terminalWidth }) {
43
+ const theme = getCurrentTheme();
43
44
  const columnWidths = calculateColumnWidths(headers, rows, terminalWidth);
44
45
  const baseColor = theme.text.primary;
45
46
  return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
@@ -1,8 +1,37 @@
1
+ import { type ThemeName } from './ui/theme.js';
1
2
  export declare const CONFIG_DIR: string;
2
3
  declare const VALID_PROVIDERS: readonly ["openai", "anthropic", "deepseek", "google", "groq", "mistral", "cohere", "fireworks", "together"];
3
4
  type Provider = (typeof VALID_PROVIDERS)[number];
4
5
  declare const VALID_EDIT_MODES: readonly ["manual", "auto"];
5
6
  type EditMode = (typeof VALID_EDIT_MODES)[number];
7
+ /**
8
+ * 别名配置接口
9
+ */
10
+ export interface AliasConfig {
11
+ prompt: string;
12
+ description?: string;
13
+ }
14
+ /**
15
+ * 远程服务器配置接口
16
+ */
17
+ export interface RemoteConfig {
18
+ host: string;
19
+ user: string;
20
+ port: number;
21
+ key?: string;
22
+ password?: boolean;
23
+ workDir?: string;
24
+ }
25
+ /**
26
+ * 远程服务器系统信息缓存
27
+ */
28
+ export interface RemoteSysInfo {
29
+ os: string;
30
+ osVersion: string;
31
+ shell: string;
32
+ hostname: string;
33
+ cachedAt: string;
34
+ }
6
35
  /**
7
36
  * 配置接口
8
37
  */
@@ -16,10 +45,11 @@ export interface Config {
16
45
  commandHistoryLimit: number;
17
46
  shellHistoryLimit: number;
18
47
  editMode: EditMode;
48
+ theme: ThemeName;
49
+ aliases: Record<string, AliasConfig>;
50
+ remotes: Record<string, RemoteConfig>;
51
+ defaultRemote?: string;
19
52
  }
20
- /**
21
- * 读取配置
22
- */
23
53
  export declare function getConfig(): Config;
24
54
  /**
25
55
  * 保存配置
@@ -3,6 +3,17 @@ import path from 'path';
3
3
  import os from 'os';
4
4
  import readline from 'readline';
5
5
  import chalk from 'chalk';
6
+ import { getCurrentTheme, isValidTheme, getAllThemeMetadata } from './ui/theme.js';
7
+ // 获取主题颜色
8
+ function getColors() {
9
+ const theme = getCurrentTheme();
10
+ return {
11
+ primary: theme.primary,
12
+ secondary: theme.secondary,
13
+ success: theme.success,
14
+ error: theme.error
15
+ };
16
+ }
6
17
  // 配置文件路径
7
18
  export const CONFIG_DIR = path.join(os.homedir(), '.please');
8
19
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
@@ -29,10 +40,14 @@ const DEFAULT_CONFIG = {
29
40
  model: 'gpt-4-turbo',
30
41
  provider: 'openai',
31
42
  shellHook: false,
32
- chatHistoryLimit: 10,
33
- commandHistoryLimit: 10,
34
- shellHistoryLimit: 15,
43
+ chatHistoryLimit: 5,
44
+ commandHistoryLimit: 5,
45
+ shellHistoryLimit: 10,
35
46
  editMode: 'manual',
47
+ theme: 'dark',
48
+ aliases: {},
49
+ remotes: {},
50
+ defaultRemote: '',
36
51
  };
37
52
  /**
38
53
  * 确保配置目录存在
@@ -44,19 +59,30 @@ function ensureConfigDir() {
44
59
  }
45
60
  /**
46
61
  * 读取配置
62
+ * 优化:添加缓存,避免重复读取文件
47
63
  */
64
+ let cachedConfig = null;
48
65
  export function getConfig() {
66
+ // 如果已有缓存,直接返回
67
+ if (cachedConfig !== null) {
68
+ return cachedConfig;
69
+ }
49
70
  ensureConfigDir();
71
+ let config;
50
72
  if (!fs.existsSync(CONFIG_FILE)) {
51
- return { ...DEFAULT_CONFIG };
52
- }
53
- try {
54
- const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
55
- return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
73
+ config = { ...DEFAULT_CONFIG };
56
74
  }
57
- catch {
58
- return { ...DEFAULT_CONFIG };
75
+ else {
76
+ try {
77
+ const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
78
+ config = { ...DEFAULT_CONFIG, ...JSON.parse(content) };
79
+ }
80
+ catch {
81
+ config = { ...DEFAULT_CONFIG };
82
+ }
59
83
  }
84
+ cachedConfig = config;
85
+ return config;
60
86
  }
61
87
  /**
62
88
  * 保存配置
@@ -98,10 +124,21 @@ export function setConfigValue(key, value) {
98
124
  }
99
125
  config.editMode = strValue;
100
126
  }
101
- else if (key === 'apiKey' || key === 'baseUrl' || key === 'model') {
127
+ else if (key === 'theme') {
128
+ const strValue = String(value);
129
+ if (!isValidTheme(strValue)) {
130
+ const allThemes = getAllThemeMetadata();
131
+ const themeNames = allThemes.map((m) => m.name).join(', ');
132
+ throw new Error(`theme 必须是以下之一: ${themeNames}`);
133
+ }
134
+ config.theme = strValue;
135
+ }
136
+ else if (key === 'apiKey' || key === 'baseUrl' || key === 'model' || key === 'defaultRemote') {
102
137
  config[key] = String(value);
103
138
  }
104
139
  saveConfig(config);
140
+ // 清除缓存,下次读取时会重新加载
141
+ cachedConfig = null;
105
142
  return config;
106
143
  }
107
144
  /**
@@ -124,17 +161,22 @@ export function maskApiKey(apiKey) {
124
161
  */
125
162
  export function displayConfig() {
126
163
  const config = getConfig();
164
+ const colors = getColors();
127
165
  console.log(chalk.bold('\n当前配置:'));
128
166
  console.log(chalk.gray('━'.repeat(50)));
129
- console.log(` ${chalk.cyan('apiKey')}: ${maskApiKey(config.apiKey)}`);
130
- console.log(` ${chalk.cyan('baseUrl')}: ${config.baseUrl}`);
131
- console.log(` ${chalk.cyan('provider')}: ${config.provider}`);
132
- console.log(` ${chalk.cyan('model')}: ${config.model}`);
133
- console.log(` ${chalk.cyan('shellHook')}: ${config.shellHook ? chalk.green('已启用') : chalk.gray('未启用')}`);
134
- console.log(` ${chalk.cyan('editMode')}: ${config.editMode === 'auto' ? chalk.hex('#00D9FF')('auto (自动编辑)') : chalk.gray('manual (按E编辑)')}`);
135
- console.log(` ${chalk.cyan('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`);
136
- console.log(` ${chalk.cyan('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`);
137
- console.log(` ${chalk.cyan('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`);
167
+ console.log(` ${chalk.hex(colors.primary)('apiKey')}: ${maskApiKey(config.apiKey)}`);
168
+ console.log(` ${chalk.hex(colors.primary)('baseUrl')}: ${config.baseUrl}`);
169
+ console.log(` ${chalk.hex(colors.primary)('provider')}: ${config.provider}`);
170
+ console.log(` ${chalk.hex(colors.primary)('model')}: ${config.model}`);
171
+ console.log(` ${chalk.hex(colors.primary)('shellHook')}: ${config.shellHook ? chalk.hex(colors.success)('已启用') : chalk.gray('未启用')}`);
172
+ console.log(` ${chalk.hex(colors.primary)('editMode')}: ${config.editMode === 'auto' ? chalk.hex(colors.primary)('auto (自动编辑)') : chalk.gray('manual (按E编辑)')}`);
173
+ console.log(` ${chalk.hex(colors.primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`);
174
+ console.log(` ${chalk.hex(colors.primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`);
175
+ console.log(` ${chalk.hex(colors.primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`);
176
+ // 动态显示主题信息
177
+ const themeMetadata = getAllThemeMetadata().find((m) => m.name === config.theme);
178
+ const themeLabel = themeMetadata ? `${themeMetadata.name} (${themeMetadata.displayName})` : config.theme;
179
+ console.log(` ${chalk.hex(colors.primary)('theme')}: ${chalk.hex(colors.primary)(themeLabel)}`);
138
180
  console.log(chalk.gray('━'.repeat(50)));
139
181
  console.log(chalk.gray(`配置文件: ${CONFIG_FILE}\n`));
140
182
  }
@@ -163,17 +205,18 @@ function question(rl, prompt) {
163
205
  export async function runConfigWizard() {
164
206
  const rl = createReadlineInterface();
165
207
  const config = getConfig();
166
- console.log(chalk.bold.hex('#00D9FF')('\n🔧 Pretty Please 配置向导'));
208
+ const colors = getColors();
209
+ console.log(chalk.bold.hex(colors.primary)('\n🔧 Pretty Please 配置向导'));
167
210
  console.log(chalk.gray('━'.repeat(50)));
168
211
  console.log(chalk.gray('直接回车使用默认值,输入值后回车确认\n'));
169
212
  try {
170
213
  // 1. Provider
171
214
  const providerHint = chalk.gray(`(可选: ${VALID_PROVIDERS.join(', ')})`);
172
- const providerPrompt = `${chalk.cyan('Provider')} ${providerHint}\n${chalk.gray('默认:')} ${chalk.yellow(config.provider)} ${chalk.gray('→')} `;
215
+ const providerPrompt = `${chalk.hex(colors.primary)('Provider')} ${providerHint}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.provider)} ${chalk.gray('→')} `;
173
216
  const provider = await question(rl, providerPrompt);
174
217
  if (provider.trim()) {
175
218
  if (!VALID_PROVIDERS.includes(provider.trim())) {
176
- console.log(chalk.hex('#EF4444')(`\n✗ 无效的 provider,必须是以下之一: ${VALID_PROVIDERS.join(', ')}`));
219
+ console.log(chalk.hex(colors.error)(`\n✗ 无效的 provider,必须是以下之一: ${VALID_PROVIDERS.join(', ')}`));
177
220
  console.log();
178
221
  rl.close();
179
222
  return;
@@ -181,37 +224,37 @@ export async function runConfigWizard() {
181
224
  config.provider = provider.trim();
182
225
  }
183
226
  // 2. Base URL
184
- const baseUrlPrompt = `${chalk.cyan('API Base URL')}\n${chalk.gray('默认:')} ${chalk.yellow(config.baseUrl)} ${chalk.gray('→')} `;
227
+ const baseUrlPrompt = `${chalk.hex(colors.primary)('API Base URL')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.baseUrl)} ${chalk.gray('→')} `;
185
228
  const baseUrl = await question(rl, baseUrlPrompt);
186
229
  if (baseUrl.trim()) {
187
230
  config.baseUrl = baseUrl.trim();
188
231
  }
189
232
  // 3. API Key
190
233
  const currentKeyDisplay = config.apiKey ? maskApiKey(config.apiKey) : '(未设置)';
191
- const apiKeyPrompt = `${chalk.cyan('API Key')} ${chalk.gray(`(当前: ${currentKeyDisplay})`)}\n${chalk.gray('→')} `;
234
+ const apiKeyPrompt = `${chalk.hex(colors.primary)('API Key')} ${chalk.gray(`(当前: ${currentKeyDisplay})`)}\n${chalk.gray('→')} `;
192
235
  const apiKey = await question(rl, apiKeyPrompt);
193
236
  if (apiKey.trim()) {
194
237
  config.apiKey = apiKey.trim();
195
238
  }
196
239
  // 4. Model
197
- const modelPrompt = `${chalk.cyan('Model')}\n${chalk.gray('默认:')} ${chalk.yellow(config.model)} ${chalk.gray('→')} `;
240
+ const modelPrompt = `${chalk.hex(colors.primary)('Model')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.model)} ${chalk.gray('→')} `;
198
241
  const model = await question(rl, modelPrompt);
199
242
  if (model.trim()) {
200
243
  config.model = model.trim();
201
244
  }
202
245
  // 5. Shell Hook
203
- const shellHookPrompt = `${chalk.cyan('启用 Shell Hook')} ${chalk.gray('(记录终端命令历史)')}\n${chalk.gray('默认:')} ${chalk.yellow(config.shellHook ? 'true' : 'false')} ${chalk.gray('→')} `;
246
+ const shellHookPrompt = `${chalk.hex(colors.primary)('启用 Shell Hook')} ${chalk.gray('(记录终端命令历史)')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.shellHook ? 'true' : 'false')} ${chalk.gray('→')} `;
204
247
  const shellHook = await question(rl, shellHookPrompt);
205
248
  if (shellHook.trim()) {
206
249
  config.shellHook = shellHook.trim() === 'true';
207
250
  }
208
251
  // 6. Edit Mode
209
252
  const editModeHint = chalk.gray('(manual=按E编辑, auto=自动编辑)');
210
- const editModePrompt = `${chalk.cyan('编辑模式')} ${editModeHint}\n${chalk.gray('默认:')} ${chalk.yellow(config.editMode)} ${chalk.gray('→')} `;
253
+ const editModePrompt = `${chalk.hex(colors.primary)('编辑模式')} ${editModeHint}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.editMode)} ${chalk.gray('→')} `;
211
254
  const editMode = await question(rl, editModePrompt);
212
255
  if (editMode.trim()) {
213
256
  if (!VALID_EDIT_MODES.includes(editMode.trim())) {
214
- console.log(chalk.hex('#EF4444')(`\n✗ 无效的 editMode,必须是: manual 或 auto`));
257
+ console.log(chalk.hex(colors.error)(`\n✗ 无效的 editMode,必须是: manual 或 auto`));
215
258
  console.log();
216
259
  rl.close();
217
260
  return;
@@ -219,7 +262,7 @@ export async function runConfigWizard() {
219
262
  config.editMode = editMode.trim();
220
263
  }
221
264
  // 7. Chat History Limit
222
- const chatHistoryPrompt = `${chalk.cyan('Chat 历史保留轮数')}\n${chalk.gray('默认:')} ${chalk.yellow(config.chatHistoryLimit)} ${chalk.gray('→')} `;
265
+ const chatHistoryPrompt = `${chalk.hex(colors.primary)('Chat 历史保留轮数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.chatHistoryLimit)} ${chalk.gray('→')} `;
223
266
  const chatHistoryLimit = await question(rl, chatHistoryPrompt);
224
267
  if (chatHistoryLimit.trim()) {
225
268
  const num = parseInt(chatHistoryLimit.trim(), 10);
@@ -228,7 +271,7 @@ export async function runConfigWizard() {
228
271
  }
229
272
  }
230
273
  // 8. Command History Limit
231
- const commandHistoryPrompt = `${chalk.cyan('命令历史保留条数')}\n${chalk.gray('默认:')} ${chalk.yellow(config.commandHistoryLimit)} ${chalk.gray('→')} `;
274
+ const commandHistoryPrompt = `${chalk.hex(colors.primary)('命令历史保留条数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.commandHistoryLimit)} ${chalk.gray('→')} `;
232
275
  const commandHistoryLimit = await question(rl, commandHistoryPrompt);
233
276
  if (commandHistoryLimit.trim()) {
234
277
  const num = parseInt(commandHistoryLimit.trim(), 10);
@@ -237,7 +280,8 @@ export async function runConfigWizard() {
237
280
  }
238
281
  }
239
282
  // 9. Shell History Limit
240
- const shellHistoryPrompt = `${chalk.cyan('Shell 历史保留条数')}\n${chalk.gray('默认:')} ${chalk.yellow(config.shellHistoryLimit)} ${chalk.gray('→')} `;
283
+ const oldShellHistoryLimit = config.shellHistoryLimit; // 保存旧值
284
+ const shellHistoryPrompt = `${chalk.hex(colors.primary)('Shell 历史保留条数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.shellHistoryLimit)} ${chalk.gray('→')} `;
241
285
  const shellHistoryLimit = await question(rl, shellHistoryPrompt);
242
286
  if (shellHistoryLimit.trim()) {
243
287
  const num = parseInt(shellHistoryLimit.trim(), 10);
@@ -247,13 +291,18 @@ export async function runConfigWizard() {
247
291
  }
248
292
  saveConfig(config);
249
293
  console.log('\n' + chalk.gray('━'.repeat(50)));
250
- console.log(chalk.hex('#10B981')('✅ 配置已保存'));
294
+ console.log(chalk.hex(getColors().success)('✅ 配置已保存'));
251
295
  console.log(chalk.gray(` ${CONFIG_FILE}`));
252
296
  console.log();
297
+ // 如果修改了 shellHistoryLimit,自动重装 hook
298
+ if (oldShellHistoryLimit !== config.shellHistoryLimit) {
299
+ const { reinstallHookForLimitChange } = await import('./shell-hook.js');
300
+ await reinstallHookForLimitChange(oldShellHistoryLimit, config.shellHistoryLimit);
301
+ }
253
302
  }
254
303
  catch (error) {
255
304
  const message = error instanceof Error ? error.message : String(error);
256
- console.log(chalk.hex('#EF4444')(`\n✗ 配置失败: ${message}`));
305
+ console.log(chalk.hex(getColors().error)(`\n✗ 配置失败: ${message}`));
257
306
  console.log();
258
307
  }
259
308
  finally {
@@ -2,5 +2,6 @@ import { Agent } from '@mastra/core';
2
2
  /**
3
3
  * 创建 Mastra Shell Agent
4
4
  * 根据用户配置的 API Key、Base URL、Provider 和 Model
5
+ * 使用静态的 System Prompt(不包含动态数据)
5
6
  */
6
7
  export declare function createShellAgent(): Agent<"shell-commander", Record<string, import("@mastra/core").ToolAction<any, any, any, any, import("@mastra/core").ToolExecutionContext<any, any, any>>>, Record<string, import("@mastra/core").Metric>>;
@@ -1,26 +1,18 @@
1
1
  import { Agent } from '@mastra/core';
2
2
  import { getConfig } from './config.js';
3
- import { buildCommandSystemPrompt } from './prompts.js';
4
- import { formatSystemInfo } from './sysinfo.js';
5
- import { formatHistoryForAI } from './history.js';
6
- import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js';
3
+ import { SHELL_COMMAND_SYSTEM_PROMPT } from './prompts.js';
7
4
  /**
8
5
  * 创建 Mastra Shell Agent
9
6
  * 根据用户配置的 API Key、Base URL、Provider 和 Model
7
+ * 使用静态的 System Prompt(不包含动态数据)
10
8
  */
11
9
  export function createShellAgent() {
12
10
  const config = getConfig();
13
11
  // 组合 provider/model 格式(Mastra 要求)
14
12
  const modelId = `${config.provider}/${config.model}`;
15
- // 构建系统提示词
16
- const sysinfo = formatSystemInfo();
17
- const plsHistory = formatHistoryForAI();
18
- const shellHistory = formatShellHistoryForAI();
19
- const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
20
- const systemPrompt = buildCommandSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled);
21
13
  return new Agent({
22
14
  name: 'shell-commander',
23
- instructions: systemPrompt,
15
+ instructions: SHELL_COMMAND_SYSTEM_PROMPT,
24
16
  model: {
25
17
  url: config.baseUrl,
26
18
  id: modelId,
@@ -1,14 +1,21 @@
1
1
  import { Agent } from '@mastra/core';
2
2
  /**
3
- * 创建 Mastra Chat Agent
3
+ * 创建 Mastra Chat Agent(使用静态系统提示词)
4
4
  */
5
5
  export declare function createChatAgent(): Agent<"chat-assistant", Record<string, import("@mastra/core").ToolAction<any, any, any, any, import("@mastra/core").ToolExecutionContext<any, any, any>>>, Record<string, import("@mastra/core").Metric>>;
6
- /**
7
- * 获取完整的系统提示词(用于调试)
8
- */
9
- export declare function getChatSystemPrompt(): string;
10
6
  /**
11
7
  * 使用 Mastra 进行 AI 对话(支持流式输出)
8
+ *
9
+ * 消息结构:
10
+ * [
11
+ * "历史问题1", // user (纯粹的问题)
12
+ * "历史回答1", // assistant
13
+ * "历史问题2", // user
14
+ * "历史回答2", // assistant
15
+ * "<system_info>...\n // user (最新消息,包含完整上下文)
16
+ * <command_history>...\n
17
+ * <user_question>最新问题</user_question>"
18
+ * ]
12
19
  */
13
20
  export declare function chatWithMastra(prompt: string, options?: {
14
21
  debug?: boolean;
@@ -23,6 +30,6 @@ export declare function chatWithMastra(prompt: string, options?: {
23
30
  role: string;
24
31
  content: string;
25
32
  }>;
26
- userPrompt: string;
33
+ userContext: string;
27
34
  };
28
35
  }>;
@@ -1,26 +1,20 @@
1
1
  import { Agent } from '@mastra/core';
2
2
  import { getConfig } from './config.js';
3
- import { buildChatSystemPrompt } from './prompts.js';
3
+ import { CHAT_SYSTEM_PROMPT, buildChatUserContext } from './prompts.js';
4
4
  import { formatSystemInfo } from './sysinfo.js';
5
5
  import { formatHistoryForAI } from './history.js';
6
6
  import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js';
7
7
  import { getChatHistory, addChatMessage } from './chat-history.js';
8
8
  /**
9
- * 创建 Mastra Chat Agent
9
+ * 创建 Mastra Chat Agent(使用静态系统提示词)
10
10
  */
11
11
  export function createChatAgent() {
12
12
  const config = getConfig();
13
13
  // 组合 provider/model 格式(Mastra 要求)
14
14
  const modelId = `${config.provider}/${config.model}`;
15
- // 构建系统提示词
16
- const sysinfo = formatSystemInfo();
17
- const plsHistory = formatHistoryForAI();
18
- const shellHistory = formatShellHistoryForAI();
19
- const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
20
- const systemPrompt = buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled);
21
15
  return new Agent({
22
16
  name: 'chat-assistant',
23
- instructions: systemPrompt,
17
+ instructions: CHAT_SYSTEM_PROMPT, // 只包含静态规则
24
18
  model: {
25
19
  url: config.baseUrl,
26
20
  id: modelId,
@@ -28,36 +22,42 @@ export function createChatAgent() {
28
22
  },
29
23
  });
30
24
  }
31
- /**
32
- * 获取完整的系统提示词(用于调试)
33
- */
34
- export function getChatSystemPrompt() {
35
- const config = getConfig();
36
- const sysinfo = formatSystemInfo();
37
- const plsHistory = formatHistoryForAI();
38
- const shellHistory = formatShellHistoryForAI();
39
- const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
40
- return buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled);
41
- }
42
25
  /**
43
26
  * 使用 Mastra 进行 AI 对话(支持流式输出)
27
+ *
28
+ * 消息结构:
29
+ * [
30
+ * "历史问题1", // user (纯粹的问题)
31
+ * "历史回答1", // assistant
32
+ * "历史问题2", // user
33
+ * "历史回答2", // assistant
34
+ * "<system_info>...\n // user (最新消息,包含完整上下文)
35
+ * <command_history>...\n
36
+ * <user_question>最新问题</user_question>"
37
+ * ]
44
38
  */
45
39
  export async function chatWithMastra(prompt, options = {}) {
46
40
  const config = getConfig();
47
41
  const agent = createChatAgent();
48
- // 获取对话历史
42
+ // 1. 获取历史对话(纯粹的问答)
49
43
  const chatHistory = getChatHistory();
50
- // 构建消息数组(将历史和新消息合并)
44
+ // 2. 构建消息数组
51
45
  const messages = [];
52
- // 添加历史对话
46
+ // 加载历史对话
53
47
  for (const msg of chatHistory) {
54
48
  messages.push(msg.content);
55
49
  }
56
- // 添加当前用户消息
57
- messages.push(prompt);
50
+ // 3. 构建最新消息(动态上下文 + 用户问题)
51
+ const sysinfo = formatSystemInfo();
52
+ const plsHistory = formatHistoryForAI();
53
+ const shellHistory = formatShellHistoryForAI();
54
+ const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
55
+ const latestUserContext = buildChatUserContext(prompt, sysinfo, plsHistory, shellHistory, shellHookEnabled);
56
+ messages.push(latestUserContext);
57
+ // 4. 发送给 AI(流式或非流式)
58
58
  let fullContent = '';
59
- // 流式输出模式
60
59
  if (options.onChunk) {
60
+ // 流式输出模式
61
61
  const stream = await agent.stream(messages);
62
62
  for await (const chunk of stream.textStream) {
63
63
  if (chunk) {
@@ -74,18 +74,18 @@ export async function chatWithMastra(prompt, options = {}) {
74
74
  if (!fullContent) {
75
75
  throw new Error('AI 返回了空的响应');
76
76
  }
77
- // 保存对话历史
77
+ // 5. 保存对话历史(只保存纯粹的问题和回答,不保存 XML)
78
78
  addChatMessage(prompt, fullContent);
79
- // 返回结果
79
+ // 6. 返回结果
80
80
  if (options.debug) {
81
81
  return {
82
82
  reply: fullContent,
83
83
  debug: {
84
- sysinfo: formatSystemInfo(),
84
+ sysinfo,
85
85
  model: config.model,
86
- systemPrompt: getChatSystemPrompt(),
86
+ systemPrompt: CHAT_SYSTEM_PROMPT,
87
87
  chatHistory,
88
- userPrompt: prompt,
88
+ userContext: latestUserContext,
89
89
  },
90
90
  };
91
91
  }
@@ -1,17 +1,20 @@
1
1
  import { z } from 'zod';
2
+ import { type RemoteSysInfo } from './config.js';
3
+ import { type RemoteShellHistoryItem } from './remote-history.js';
2
4
  /**
3
5
  * 多步骤命令的 Zod Schema
6
+ * 注意:optional 字段使用 .default() 是为了绕过 Mastra 0.24.8 对 optional 字段的验证 bug
4
7
  */
5
8
  export declare const CommandStepSchema: z.ZodObject<{
6
9
  command: z.ZodString;
7
- continue: z.ZodOptional<z.ZodBoolean>;
8
- reasoning: z.ZodOptional<z.ZodString>;
9
- nextStepHint: z.ZodOptional<z.ZodString>;
10
+ continue: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
11
+ reasoning: z.ZodDefault<z.ZodOptional<z.ZodString>>;
12
+ nextStepHint: z.ZodDefault<z.ZodOptional<z.ZodString>>;
10
13
  }, "strip", z.ZodTypeAny, {
11
14
  command: string;
12
- reasoning?: string | undefined;
13
- continue?: boolean | undefined;
14
- nextStepHint?: string | undefined;
15
+ reasoning: string;
16
+ continue: boolean;
17
+ nextStepHint: string;
15
18
  }, {
16
19
  command: string;
17
20
  reasoning?: string | undefined;
@@ -27,14 +30,27 @@ export interface ExecutedStep extends CommandStep {
27
30
  output: string;
28
31
  }
29
32
  /**
30
- * 生成系统上下文信息(供 Mastra 使用)
33
+ * 远程执行上下文
34
+ */
35
+ export interface RemoteContext {
36
+ name: string;
37
+ sysInfo: RemoteSysInfo;
38
+ shellHistory: RemoteShellHistoryItem[];
39
+ }
40
+ /**
41
+ * 获取静态 System Prompt(供 Mastra 使用)
31
42
  */
32
43
  export declare function getFullSystemPrompt(): string;
44
+ /**
45
+ * 获取静态 System Prompt(远程执行也使用相同的 System Prompt)
46
+ */
47
+ export declare function getRemoteFullSystemPrompt(remoteContext: RemoteContext): string;
33
48
  /**
34
49
  * 使用 Mastra 生成多步骤命令
35
50
  */
36
51
  export declare function generateMultiStepCommand(userPrompt: string, previousSteps?: ExecutedStep[], options?: {
37
52
  debug?: boolean;
53
+ remoteContext?: RemoteContext;
38
54
  }): Promise<{
39
55
  stepData: CommandStep;
40
56
  debugInfo?: any;