@yivan-lab/pretty-please 1.0.0 → 1.2.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 (89) hide show
  1. package/README.md +381 -28
  2. package/bin/pls.tsx +1138 -109
  3. package/dist/bin/pls.d.ts +1 -1
  4. package/dist/bin/pls.js +994 -91
  5. package/dist/package.json +80 -0
  6. package/dist/src/ai.d.ts +1 -41
  7. package/dist/src/ai.js +9 -190
  8. package/dist/src/alias.d.ts +41 -0
  9. package/dist/src/alias.js +240 -0
  10. package/dist/src/builtin-detector.d.ts +14 -8
  11. package/dist/src/builtin-detector.js +36 -16
  12. package/dist/src/chat-history.d.ts +16 -11
  13. package/dist/src/chat-history.js +35 -4
  14. package/dist/src/components/Chat.js +5 -4
  15. package/dist/src/components/CodeColorizer.js +26 -20
  16. package/dist/src/components/CommandBox.js +3 -17
  17. package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
  18. package/dist/src/components/ConfirmationPrompt.js +9 -4
  19. package/dist/src/components/Duration.js +2 -1
  20. package/dist/src/components/InlineRenderer.js +2 -1
  21. package/dist/src/components/MarkdownDisplay.js +2 -1
  22. package/dist/src/components/MultiStepCommandGenerator.d.ts +5 -1
  23. package/dist/src/components/MultiStepCommandGenerator.js +127 -14
  24. package/dist/src/components/TableRenderer.js +2 -1
  25. package/dist/src/config.d.ts +59 -9
  26. package/dist/src/config.js +147 -48
  27. package/dist/src/history.d.ts +19 -5
  28. package/dist/src/history.js +26 -11
  29. package/dist/src/mastra-agent.d.ts +0 -1
  30. package/dist/src/mastra-agent.js +3 -4
  31. package/dist/src/mastra-chat.d.ts +28 -0
  32. package/dist/src/mastra-chat.js +93 -0
  33. package/dist/src/multi-step.d.ts +23 -7
  34. package/dist/src/multi-step.js +29 -6
  35. package/dist/src/prompts.d.ts +11 -0
  36. package/dist/src/prompts.js +140 -0
  37. package/dist/src/remote-history.d.ts +63 -0
  38. package/dist/src/remote-history.js +315 -0
  39. package/dist/src/remote.d.ts +113 -0
  40. package/dist/src/remote.js +634 -0
  41. package/dist/src/shell-hook.d.ts +87 -12
  42. package/dist/src/shell-hook.js +315 -17
  43. package/dist/src/sysinfo.d.ts +9 -5
  44. package/dist/src/sysinfo.js +2 -2
  45. package/dist/src/ui/theme.d.ts +27 -24
  46. package/dist/src/ui/theme.js +71 -21
  47. package/dist/src/upgrade.d.ts +41 -0
  48. package/dist/src/upgrade.js +348 -0
  49. package/dist/src/utils/console.d.ts +11 -11
  50. package/dist/src/utils/console.js +26 -17
  51. package/package.json +11 -9
  52. package/src/alias.ts +301 -0
  53. package/src/builtin-detector.ts +126 -0
  54. package/src/chat-history.ts +140 -0
  55. package/src/components/Chat.tsx +6 -5
  56. package/src/components/CodeColorizer.tsx +27 -19
  57. package/src/components/CommandBox.tsx +3 -17
  58. package/src/components/ConfirmationPrompt.tsx +11 -3
  59. package/src/components/Duration.tsx +2 -1
  60. package/src/components/InlineRenderer.tsx +2 -1
  61. package/src/components/MarkdownDisplay.tsx +2 -1
  62. package/src/components/MultiStepCommandGenerator.tsx +167 -16
  63. package/src/components/TableRenderer.tsx +2 -1
  64. package/src/config.ts +394 -0
  65. package/src/history.ts +160 -0
  66. package/src/mastra-agent.ts +3 -4
  67. package/src/mastra-chat.ts +124 -0
  68. package/src/multi-step.ts +45 -8
  69. package/src/prompts.ts +154 -0
  70. package/src/remote-history.ts +390 -0
  71. package/src/remote.ts +800 -0
  72. package/src/shell-hook.ts +754 -0
  73. package/src/{sysinfo.js → sysinfo.ts} +28 -16
  74. package/src/ui/theme.ts +101 -24
  75. package/src/upgrade.ts +397 -0
  76. package/src/utils/{console.js → console.ts} +36 -27
  77. package/bin/pls.js +0 -681
  78. package/src/ai.js +0 -324
  79. package/src/builtin-detector.js +0 -98
  80. package/src/chat-history.js +0 -94
  81. package/src/components/ChatStatus.tsx +0 -53
  82. package/src/components/CommandGenerator.tsx +0 -184
  83. package/src/components/ConfigDisplay.tsx +0 -64
  84. package/src/components/ConfigWizard.tsx +0 -101
  85. package/src/components/HistoryDisplay.tsx +0 -69
  86. package/src/components/HookManager.tsx +0 -150
  87. package/src/config.js +0 -221
  88. package/src/history.js +0 -131
  89. package/src/shell-hook.js +0 -393
package/bin/pls.js DELETED
@@ -1,681 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import { exec } from 'child_process';
7
- import { fileURLToPath } from 'url';
8
- import { dirname, join } from 'path';
9
- import fs from 'fs';
10
-
11
- import {
12
- getConfig,
13
- setConfigValue,
14
- isConfigValid,
15
- displayConfig,
16
- runConfigWizard
17
- } from '../src/config.js';
18
- import { generateCommand } from '../src/ai.js';
19
- import { chatWithAI } from '../src/ai.js';
20
- import {
21
- addHistory,
22
- getHistory,
23
- clearHistory,
24
- getHistoryFilePath
25
- } from '../src/history.js';
26
- import {
27
- clearChatHistory,
28
- getChatRoundCount,
29
- getChatHistoryFilePath
30
- } from '../src/chat-history.js';
31
- import {
32
- installShellHook,
33
- uninstallShellHook,
34
- getHookStatus,
35
- detectShell
36
- } from '../src/shell-hook.js';
37
- import { detectBuiltin, formatBuiltins } from '../src/builtin-detector.js';
38
-
39
- // 获取 package.json 版本
40
- const __filename = fileURLToPath(import.meta.url);
41
- const __dirname = dirname(__filename);
42
- const packageJson = JSON.parse(fs.readFileSync(join(__dirname, '../package.json'), 'utf-8'));
43
-
44
- const program = new Command();
45
-
46
- /**
47
- * 计算字符串的显示宽度(中文占2个宽度)
48
- */
49
- function getDisplayWidth(str) {
50
- let width = 0;
51
- for (const char of str) {
52
- // 中文、日文、韩文等宽字符占 2 个宽度
53
- if (char.match(/[\u4e00-\u9fff\u3400-\u4dbf\uff00-\uffef\u3000-\u303f]/)) {
54
- width += 2;
55
- } else {
56
- width += 1;
57
- }
58
- }
59
- return width;
60
- }
61
-
62
- /**
63
- * 绘制命令框
64
- * @param {string} command - 要显示的命令
65
- * @param {string} title - 框框标题
66
- */
67
- function drawCommandBox(command, title = '生成命令') {
68
- const lines = command.split('\n');
69
- const titleWidth = getDisplayWidth(title);
70
- const maxContentWidth = Math.max(...lines.map(l => getDisplayWidth(l)));
71
- const boxWidth = Math.max(maxContentWidth + 4, titleWidth + 6, 20);
72
-
73
- // 顶部边框:┌─ 生成命令 ─────┐
74
- const topPadding = boxWidth - titleWidth - 5;
75
- const topBorder = '┌─ ' + title + ' ' + '─'.repeat(topPadding) + '┐';
76
-
77
- // 底部边框
78
- const bottomBorder = '└' + '─'.repeat(boxWidth - 2) + '┘';
79
-
80
- console.log(chalk.yellow(topBorder));
81
- for (const line of lines) {
82
- const lineWidth = getDisplayWidth(line);
83
- const padding = ' '.repeat(boxWidth - lineWidth - 4);
84
- console.log(chalk.yellow('│ ') + chalk.cyan(line) + padding + chalk.yellow(' │'));
85
- }
86
- console.log(chalk.yellow(bottomBorder));
87
- }
88
-
89
- /**
90
- * 格式化耗时
91
- * @param {number} ms - 毫秒数
92
- */
93
- function formatDuration(ms) {
94
- if (ms < 1000) {
95
- return `${ms}ms`;
96
- }
97
- return `${(ms / 1000).toFixed(2)}s`;
98
- }
99
-
100
- /**
101
- * 询问用户确认(单键模式)
102
- * 回车 = 确认执行,Esc = 取消
103
- */
104
- function askConfirmation(prompt) {
105
- return new Promise((resolve) => {
106
- process.stdout.write(prompt);
107
-
108
- // 启用原始模式以捕获单个按键
109
- if (process.stdin.isTTY) {
110
- process.stdin.setRawMode(true);
111
- }
112
- process.stdin.resume();
113
-
114
- const onKeyPress = (key) => {
115
- // 恢复正常模式
116
- if (process.stdin.isTTY) {
117
- process.stdin.setRawMode(false);
118
- }
119
- process.stdin.pause();
120
- process.stdin.removeListener('data', onKeyPress);
121
-
122
- // 换行,让后续输出在新行显示
123
- process.stdout.write('\n');
124
-
125
- // 检测按键
126
- if (key[0] === 0x0d || key[0] === 0x0a) {
127
- // Enter 键 (回车)
128
- resolve(true);
129
- } else if (key[0] === 0x1b) {
130
- // Esc 键
131
- resolve(false);
132
- } else if (key[0] === 0x03) {
133
- // Ctrl+C
134
- process.exit(0);
135
- } else {
136
- // 其他键,忽略,继续等待
137
- process.stdout.write(prompt);
138
- if (process.stdin.isTTY) {
139
- process.stdin.setRawMode(true);
140
- }
141
- process.stdin.resume();
142
- process.stdin.once('data', onKeyPress);
143
- }
144
- };
145
-
146
- process.stdin.once('data', onKeyPress);
147
- });
148
- }
149
-
150
- /**
151
- * 执行命令并返回结果
152
- */
153
- function executeCommand(command) {
154
- return new Promise((resolve) => {
155
- let output = '';
156
-
157
- const child = exec(command, { shell: true });
158
-
159
- child.stdout?.on('data', (data) => {
160
- output += data;
161
- process.stdout.write(data);
162
- });
163
-
164
- child.stderr?.on('data', (data) => {
165
- output += data;
166
- process.stderr.write(data);
167
- });
168
-
169
- child.on('close', (code) => {
170
- resolve({ exitCode: code, output });
171
- });
172
-
173
- child.on('error', (err) => {
174
- resolve({ exitCode: 1, output: err.message });
175
- });
176
- });
177
- }
178
-
179
- /**
180
- * 执行命令(配合 spinner 使用)
181
- * 先停止 spinner,显示输出,执行完成后再更新 spinner 状态
182
- */
183
- function executeCommandWithSpinner(command, spinner) {
184
- return new Promise((resolve) => {
185
- let output = '';
186
- let hasOutput = false; // 标记是否有输出
187
- let topSeparatorShown = false; // 标记是否已显示顶部分隔线
188
-
189
- // 停止 spinner 动画,但不改变状态
190
- spinner.stop();
191
-
192
- const child = exec(command, { shell: true });
193
-
194
- child.stdout?.on('data', (data) => {
195
- output += data;
196
- hasOutput = true;
197
-
198
- // 第一次有输出时显示顶部分隔线
199
- if (!topSeparatorShown) {
200
- console.log(chalk.gray('\n─── 输出 ' + '─'.repeat(30)));
201
- topSeparatorShown = true;
202
- }
203
-
204
- process.stdout.write(data);
205
- });
206
-
207
- child.stderr?.on('data', (data) => {
208
- output += data;
209
- hasOutput = true;
210
-
211
- // 第一次有输出时显示顶部分隔线
212
- if (!topSeparatorShown) {
213
- console.log(chalk.gray('\n─── 输出 ' + '─'.repeat(30)));
214
- topSeparatorShown = true;
215
- }
216
-
217
- process.stderr.write(data);
218
- });
219
-
220
- child.on('close', (code) => {
221
- // 只在有输出时显示底部分隔线
222
- if (hasOutput) {
223
- console.log(chalk.gray('─'.repeat(38)));
224
- }
225
- resolve({ exitCode: code, output });
226
- });
227
-
228
- child.on('error', (err) => {
229
- // error 也算有输出
230
- if (!topSeparatorShown) {
231
- console.log(chalk.gray('\n─── 输出 ' + '─'.repeat(30)));
232
- }
233
- console.log(chalk.gray('─'.repeat(38)));
234
- resolve({ exitCode: 1, output: err.message });
235
- });
236
- });
237
- }
238
-
239
- /**
240
- * 显示调试信息
241
- */
242
- function displayDebugInfo(debug) {
243
- console.log(chalk.magenta('\n━━━ 调试信息 ━━━'));
244
- console.log(chalk.gray('系统信息: ') + debug.sysinfo);
245
- console.log(chalk.gray('模型: ') + debug.model);
246
- console.log(chalk.gray('System Prompt:'));
247
- console.log(chalk.dim(debug.systemPrompt));
248
- console.log(chalk.gray('User Prompt: ') + debug.userPrompt);
249
- console.log(chalk.magenta('━'.repeat(16)));
250
- }
251
-
252
- /**
253
- * 主要的命令执行流程
254
- */
255
- async function runPrompt(promptArgs, options = {}) {
256
- const prompt = promptArgs.join(' ');
257
- const debug = options.debug || false;
258
-
259
- if (!prompt.trim()) {
260
- console.log(chalk.red('请提供你想执行的操作描述'));
261
- console.log(chalk.gray('示例: pls 安装 git'));
262
- process.exit(1);
263
- }
264
-
265
- // 检查配置
266
- if (!isConfigValid()) {
267
- console.log(chalk.yellow('\n⚠️ 检测到尚未配置 API Key'));
268
- console.log(chalk.gray('请运行 ') + chalk.cyan('pls config') + chalk.gray(' 进行配置\n'));
269
- process.exit(1);
270
- }
271
-
272
- try {
273
- // 思考中 spinner
274
- const thinkingSpinner = ora({
275
- text: '正在思考...',
276
- spinner: 'dots'
277
- }).start();
278
-
279
- const thinkStartTime = Date.now();
280
- const result = await generateCommand(prompt, { debug });
281
- const thinkDuration = Date.now() - thinkStartTime;
282
-
283
- // 根据是否调试模式,解构结果
284
- const command = debug ? result.command : result;
285
-
286
- thinkingSpinner.succeed(chalk.gray(`思考完成 (${formatDuration(thinkDuration)})`));
287
-
288
- // 调试模式下显示调试信息
289
- if (debug) {
290
- displayDebugInfo(result.debug);
291
- }
292
-
293
- // 显示生成的命令(框框样式)
294
- console.log('');
295
- drawCommandBox(command);
296
-
297
- // 检测是否包含 builtin 命令
298
- const { hasBuiltin, builtins } = detectBuiltin(command);
299
-
300
- if (hasBuiltin) {
301
- // 包含 builtin,不执行,只提示
302
- console.log(chalk.red('\n⚠️ 此命令包含 shell 内置命令(' + formatBuiltins(builtins) + '),无法在子进程中生效'));
303
- console.log(chalk.yellow('💡 请手动复制到终端执行\n'));
304
-
305
- // 记录历史(标记为未执行,原因是 builtin)
306
- addHistory({
307
- userPrompt: prompt,
308
- command,
309
- executed: false,
310
- exitCode: null,
311
- output: '',
312
- reason: 'builtin'
313
- });
314
-
315
- return;
316
- }
317
-
318
- // 询问确认
319
- const confirmed = await askConfirmation(
320
- chalk.bold.yellow('执行?') + chalk.gray(' [回车执行 / Esc 取消] ')
321
- );
322
-
323
- if (confirmed) {
324
- // 执行中 spinner
325
- const execSpinner = ora({
326
- text: '执行中...',
327
- spinner: 'dots'
328
- }).start();
329
-
330
- const execStartTime = Date.now();
331
- const { exitCode, output } = await executeCommandWithSpinner(command, execSpinner);
332
- const execDuration = Date.now() - execStartTime;
333
-
334
- // 记录历史
335
- addHistory({
336
- userPrompt: prompt,
337
- command,
338
- executed: true,
339
- exitCode,
340
- output
341
- });
342
-
343
- if (exitCode === 0) {
344
- execSpinner.succeed(chalk.green(`执行完成 (${formatDuration(execDuration)})`));
345
- } else {
346
- execSpinner.fail(chalk.red(`执行失败,退出码: ${exitCode} (${formatDuration(execDuration)})`));
347
- }
348
- } else {
349
- // 记录未执行的历史
350
- addHistory({
351
- userPrompt: prompt,
352
- command,
353
- executed: false,
354
- exitCode: null,
355
- output: ''
356
- });
357
-
358
- console.log(chalk.gray('\n已取消执行\n'));
359
- }
360
- } catch (error) {
361
- console.error(chalk.red('\n❌ 错误: ') + error.message);
362
- process.exit(1);
363
- }
364
- }
365
-
366
- // 设置程序
367
- program
368
- .name('pls')
369
- .description('AI 驱动的命令行工具,将自然语言转换为可执行的 Shell 命令')
370
- .version(packageJson.version, '-v, --version', '显示版本号')
371
- .helpOption('-h, --help', '显示帮助信息');
372
-
373
- // config 子命令
374
- const configCmd = program
375
- .command('config')
376
- .description('管理配置');
377
-
378
- configCmd
379
- .command('list')
380
- .description('查看当前配置')
381
- .action(() => {
382
- displayConfig();
383
- });
384
-
385
- configCmd
386
- .command('show')
387
- .description('查看当前配置')
388
- .action(() => {
389
- displayConfig();
390
- });
391
-
392
- configCmd
393
- .command('set <key> <value>')
394
- .description('设置配置项 (apiKey, baseUrl, model, shellHook, chatHistoryLimit)')
395
- .action((key, value) => {
396
- try {
397
- setConfigValue(key, value);
398
- console.log(chalk.green(`✅ 已设置 ${key}`));
399
- } catch (error) {
400
- console.error(chalk.red(`❌ ${error.message}`));
401
- process.exit(1);
402
- }
403
- });
404
-
405
- // 默认 config 命令(交互式配置)
406
- configCmd
407
- .action(async () => {
408
- await runConfigWizard();
409
- });
410
-
411
- // history 子命令
412
- const historyCmd = program
413
- .command('history')
414
- .description('查看或管理命令历史');
415
-
416
- historyCmd
417
- .command('show')
418
- .description('显示历史记录')
419
- .action(() => {
420
- const history = getHistory();
421
- if (history.length === 0) {
422
- console.log(chalk.gray('\n暂无历史记录\n'));
423
- return;
424
- }
425
-
426
- console.log(chalk.bold('\n📜 命令历史:'));
427
- console.log(chalk.gray('━'.repeat(50)));
428
-
429
- history.forEach((item, index) => {
430
- const status = item.executed
431
- ? (item.exitCode === 0 ? chalk.green('✓') : chalk.red(`✗ 退出码:${item.exitCode}`))
432
- : chalk.gray('(未执行)');
433
-
434
- console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(item.userPrompt)}`);
435
- console.log(` ${chalk.dim('→')} ${item.command} ${status}`);
436
- console.log(` ${chalk.gray(item.timestamp)}`);
437
- console.log();
438
- });
439
-
440
- console.log(chalk.gray(`历史文件: ${getHistoryFilePath()}\n`));
441
- });
442
-
443
- historyCmd
444
- .command('clear')
445
- .description('清空历史记录')
446
- .action(() => {
447
- clearHistory();
448
- console.log(chalk.green('✅ 历史记录已清空'));
449
- });
450
-
451
- // 默认 history 命令(显示历史)
452
- historyCmd
453
- .action(() => {
454
- const history = getHistory();
455
- if (history.length === 0) {
456
- console.log(chalk.gray('\n暂无历史记录\n'));
457
- return;
458
- }
459
-
460
- console.log(chalk.bold('\n📜 命令历史:'));
461
- console.log(chalk.gray('━'.repeat(50)));
462
-
463
- history.forEach((item, index) => {
464
- const status = item.executed
465
- ? (item.exitCode === 0 ? chalk.green('✓') : chalk.red(`✗ 退出码:${item.exitCode}`))
466
- : chalk.gray('(未执行)');
467
-
468
- console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(item.userPrompt)}`);
469
- console.log(` ${chalk.dim('→')} ${item.command} ${status}`);
470
- console.log(` ${chalk.gray(item.timestamp)}`);
471
- console.log();
472
- });
473
-
474
- console.log(chalk.gray(`历史文件: ${getHistoryFilePath()}\n`));
475
- });
476
-
477
- // hook 子命令 - 安装/卸载 shell hook
478
- const hookCmd = program
479
- .command('hook')
480
- .description('管理 shell hook(增强功能:记录终端命令历史)');
481
-
482
- hookCmd
483
- .command('install')
484
- .description('安装 shell hook')
485
- .action(async () => {
486
- const status = getHookStatus();
487
- console.log(chalk.bold('\n🔧 Shell Hook 安装向导'));
488
- console.log(chalk.gray('━'.repeat(40)));
489
- console.log(chalk.gray(`检测到 Shell: ${status.shellType}`));
490
- console.log(chalk.gray(`配置文件: ${status.configPath || '未知'}`));
491
- console.log();
492
-
493
- if (status.shellType === 'unknown') {
494
- console.log(chalk.red('❌ 不支持的 shell 类型'));
495
- console.log(chalk.gray('支持的 shell: zsh, bash, powershell'));
496
- return;
497
- }
498
-
499
- console.log(chalk.yellow('此功能会在你的 shell 配置文件中添加 hook,'));
500
- console.log(chalk.yellow('用于记录你在终端执行的每条命令,让 AI 更智能。'));
501
- console.log();
502
-
503
- await installShellHook();
504
- });
505
-
506
- hookCmd
507
- .command('uninstall')
508
- .description('卸载 shell hook')
509
- .action(() => {
510
- uninstallShellHook();
511
- });
512
-
513
- hookCmd
514
- .command('status')
515
- .description('查看 shell hook 状态')
516
- .action(() => {
517
- const status = getHookStatus();
518
- console.log(chalk.bold('\n📊 Shell Hook 状态'));
519
- console.log(chalk.gray('━'.repeat(40)));
520
- console.log(` ${chalk.cyan('Shell 类型')}: ${status.shellType}`);
521
- console.log(` ${chalk.cyan('配置文件')}: ${status.configPath || '未知'}`);
522
- console.log(` ${chalk.cyan('已安装')}: ${status.installed ? chalk.green('是') : chalk.gray('否')}`);
523
- console.log(` ${chalk.cyan('已启用')}: ${status.enabled ? chalk.green('是') : chalk.gray('否')}`);
524
- console.log(` ${chalk.cyan('历史文件')}: ${status.historyFile}`);
525
- console.log(chalk.gray('━'.repeat(40)));
526
-
527
- if (!status.installed) {
528
- console.log(chalk.gray('\n提示: 运行 ') + chalk.cyan('pls hook install') + chalk.gray(' 安装 shell hook'));
529
- }
530
- console.log();
531
- });
532
-
533
- // 默认 hook 命令(显示状态)
534
- hookCmd
535
- .action(() => {
536
- const status = getHookStatus();
537
- console.log(chalk.bold('\n📊 Shell Hook 状态'));
538
- console.log(chalk.gray('━'.repeat(40)));
539
- console.log(` ${chalk.cyan('Shell 类型')}: ${status.shellType}`);
540
- console.log(` ${chalk.cyan('配置文件')}: ${status.configPath || '未知'}`);
541
- console.log(` ${chalk.cyan('已安装')}: ${status.installed ? chalk.green('是') : chalk.gray('否')}`);
542
- console.log(` ${chalk.cyan('已启用')}: ${status.enabled ? chalk.green('是') : chalk.gray('否')}`);
543
- console.log(chalk.gray('━'.repeat(40)));
544
-
545
- if (!status.installed) {
546
- console.log(chalk.gray('\n提示: 运行 ') + chalk.cyan('pls hook install') + chalk.gray(' 安装 shell hook'));
547
- console.log(chalk.gray(' 运行 ') + chalk.cyan('pls hook uninstall') + chalk.gray(' 卸载 shell hook'));
548
- }
549
- console.log();
550
- });
551
-
552
- // chat 子命令 - AI 对话模式
553
- const chatCmd = program
554
- .command('chat')
555
- .description('AI 对话模式,问答、讲解命令');
556
-
557
- chatCmd
558
- .command('clear')
559
- .description('清空对话历史')
560
- .action(() => {
561
- clearChatHistory();
562
- console.log(chalk.green('✅ 对话历史已清空'));
563
- });
564
-
565
- // 默认 chat 命令(进行对话)
566
- chatCmd
567
- .argument('[prompt...]', '你的问题')
568
- .option('-d, --debug', '显示调试信息')
569
- .action(async (promptArgs, options) => {
570
- const prompt = promptArgs.join(' ');
571
-
572
- if (!prompt.trim()) {
573
- // 没有输入,显示对话状态
574
- const roundCount = getChatRoundCount();
575
- console.log(chalk.bold('\n💬 AI 对话模式'));
576
- console.log(chalk.gray('━'.repeat(40)));
577
- console.log(` ${chalk.cyan('当前对话轮数')}: ${roundCount}`);
578
- console.log(` ${chalk.cyan('历史文件')}: ${getChatHistoryFilePath()}`);
579
- console.log(chalk.gray('━'.repeat(40)));
580
- console.log(chalk.gray('\n用法:'));
581
- console.log(chalk.cyan(' pls chat <问题>') + chalk.gray(' 与 AI 对话'));
582
- console.log(chalk.cyan(' pls chat clear') + chalk.gray(' 清空对话历史'));
583
- console.log();
584
- return;
585
- }
586
-
587
- // 检查配置
588
- if (!isConfigValid()) {
589
- console.log(chalk.yellow('\n⚠️ 检测到尚未配置 API Key'));
590
- console.log(chalk.gray('请运行 ') + chalk.cyan('pls config') + chalk.gray(' 进行配置\n'));
591
- process.exit(1);
592
- }
593
-
594
- try {
595
- // 显示对话轮数
596
- const roundCount = getChatRoundCount();
597
- if (roundCount > 0) {
598
- console.log(chalk.gray(`(对话轮数: ${roundCount})`));
599
- }
600
-
601
- // 思考中 spinner
602
- const spinner = ora({
603
- text: '思考中...',
604
- spinner: 'dots'
605
- }).start();
606
-
607
- const startTime = Date.now();
608
- let firstChunk = true;
609
-
610
- // 流式输出回调 - 逐字符输出原始 markdown
611
- const onChunk = (content) => {
612
- if (firstChunk) {
613
- // 第一个 chunk 到来,清理 spinner
614
- spinner.stop();
615
- process.stdout.write('\r\x1b[K'); // 清除当前行
616
- firstChunk = false;
617
- }
618
- // 直接输出原始内容(逐字符)
619
- process.stdout.write(content);
620
- };
621
-
622
- const result = await chatWithAI(prompt, {
623
- debug: options.debug,
624
- onChunk
625
- });
626
- const duration = Date.now() - startTime;
627
-
628
- // 输出完成后换行
629
- console.log();
630
- console.log(chalk.gray(`(${formatDuration(duration)})`));
631
-
632
- // 调试模式下显示调试信息
633
- if (options.debug) {
634
- console.log(chalk.magenta('\n━━━ 调试信息 ━━━'));
635
- console.log(chalk.gray('系统信息: ') + result.debug.sysinfo);
636
- console.log(chalk.gray('模型: ') + result.debug.model);
637
- console.log(chalk.gray('对话历史轮数: ') + Math.floor(result.debug.chatHistory.length / 2));
638
- console.log(chalk.gray('System Prompt:'));
639
- console.log(chalk.dim(result.debug.systemPrompt));
640
- console.log(chalk.gray('User Prompt: ') + result.debug.userPrompt);
641
- console.log(chalk.magenta('━'.repeat(16)));
642
- }
643
-
644
- } catch (error) {
645
- console.error(chalk.red('\n❌ 错误: ') + error.message);
646
- process.exit(1);
647
- }
648
- });
649
-
650
- // 默认命令(执行 prompt)
651
- program
652
- .argument('[prompt...]', '自然语言描述你想执行的操作')
653
- .option('-d, --debug', '显示调试信息(系统信息、完整 prompt 等)')
654
- .action(async (promptArgs, options) => {
655
- if (promptArgs.length === 0) {
656
- program.help();
657
- return;
658
- }
659
- await runPrompt(promptArgs, { debug: options.debug });
660
- });
661
-
662
- // 自定义帮助信息
663
- program.addHelpText('after', `
664
-
665
- ${chalk.bold('示例:')}
666
- ${chalk.cyan('pls 安装 git')} 让 AI 生成安装 git 的命令
667
- ${chalk.cyan('pls 查找大于 100MB 的文件')} 查找大文件
668
- ${chalk.cyan('pls 删除刚才创建的文件')} AI 会参考历史记录
669
- ${chalk.cyan('pls --debug 压缩 logs 目录')} 显示调试信息
670
- ${chalk.cyan('pls chat tar 命令怎么用')} AI 对话模式
671
- ${chalk.cyan('pls chat clear')} 清空对话历史
672
- ${chalk.cyan('pls history')} 查看 pls 命令历史
673
- ${chalk.cyan('pls history clear')} 清空历史记录
674
- ${chalk.cyan('pls hook')} 查看 shell hook 状态
675
- ${chalk.cyan('pls hook install')} 安装 shell hook(增强功能)
676
- ${chalk.cyan('pls hook uninstall')} 卸载 shell hook
677
- ${chalk.cyan('pls config')} 交互式配置
678
- ${chalk.cyan('pls config list')} 查看当前配置
679
- `);
680
-
681
- program.parse();