@yivan-lab/pretty-please 1.2.0 → 1.3.1
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 +112 -5
- package/bin/pls.tsx +265 -51
- package/dist/bin/pls.js +234 -49
- package/dist/package.json +1 -1
- package/dist/src/components/Chat.js +52 -25
- package/dist/src/components/CommandBox.js +17 -7
- package/dist/src/config.d.ts +1 -2
- package/dist/src/config.js +18 -9
- package/dist/src/mastra-agent.d.ts +1 -0
- package/dist/src/mastra-agent.js +3 -11
- package/dist/src/mastra-chat.d.ts +13 -6
- package/dist/src/mastra-chat.js +31 -31
- package/dist/src/multi-step.d.ts +2 -2
- package/dist/src/multi-step.js +35 -39
- package/dist/src/prompts.d.ts +30 -4
- package/dist/src/prompts.js +218 -70
- package/dist/src/shell-hook.d.ts +5 -0
- package/dist/src/shell-hook.js +56 -10
- package/dist/src/ui/theme.d.ts +35 -1
- package/dist/src/ui/theme.js +480 -8
- package/dist/src/utils/console.d.ts +9 -0
- package/dist/src/utils/console.js +69 -6
- package/package.json +1 -1
- package/src/components/Chat.tsx +69 -25
- package/src/components/CommandBox.tsx +24 -7
- package/src/config.ts +21 -15
- package/src/mastra-agent.ts +3 -12
- package/src/mastra-chat.ts +40 -34
- package/src/multi-step.ts +40 -45
- package/src/prompts.ts +236 -78
- package/src/shell-hook.ts +71 -10
- package/src/ui/theme.ts +542 -10
- package/src/utils/console.ts +80 -6
package/README.md
CHANGED
|
@@ -464,25 +464,126 @@ pls hook uninstall # 卸载 hook
|
|
|
464
464
|
|
|
465
465
|
## 🌗 主题系统
|
|
466
466
|
|
|
467
|
-
|
|
467
|
+
支持多种内置主题和自定义主题,适配不同终端背景和个人喜好:
|
|
468
|
+
|
|
469
|
+
### 内置主题
|
|
468
470
|
|
|
469
471
|
```bash
|
|
470
472
|
pls theme # 查看当前主题
|
|
471
473
|
pls theme list # 查看所有可用主题
|
|
472
474
|
pls theme dark # 切换到深色主题
|
|
473
|
-
pls theme
|
|
475
|
+
pls theme nord # 切换到 Nord 主题
|
|
474
476
|
```
|
|
475
477
|
|
|
476
|
-
|
|
478
|
+
**可用的内置主题:**
|
|
477
479
|
- **dark(深色)** - 明亮的颜色,适合深色终端背景(默认)
|
|
478
480
|
- **light(浅色)** - 较深的颜色,适合浅色终端背景
|
|
481
|
+
- **nord(北欧冷色)** - 冷色调护眼主题,适合长时间使用
|
|
482
|
+
- **dracula(德古拉暗色)** - 高对比暗色主题,色彩丰富但不刺眼
|
|
483
|
+
- **retro(复古终端绿)** - 经典终端荧光绿,致敬老派 hacker 风格
|
|
484
|
+
- **contrast(高对比度)** - 极高对比度,适合视力辅助和强光环境
|
|
485
|
+
- **monokai(经典编辑器)** - Monokai 经典配色,开发者熟悉的选择
|
|
479
486
|
|
|
480
487
|
也可以通过配置命令切换:
|
|
481
488
|
|
|
482
489
|
```bash
|
|
483
|
-
pls config set theme
|
|
490
|
+
pls config set theme nord
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### 自定义主题
|
|
494
|
+
|
|
495
|
+
创建你自己的主题配色方案:
|
|
496
|
+
|
|
497
|
+
#### 1. 创建主题模板
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
# 创建深色主题
|
|
501
|
+
pls theme create my-ocean --display-name "我的海洋主题"
|
|
502
|
+
|
|
503
|
+
# 创建浅色主题
|
|
504
|
+
pls theme create my-light --display-name "我的浅色" --category light
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
这会在 `~/.please/themes/` 目录下生成一个 JSON 模板文件。
|
|
508
|
+
|
|
509
|
+
#### 2. 编辑主题文件
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
vim ~/.please/themes/my-ocean.json
|
|
484
513
|
```
|
|
485
514
|
|
|
515
|
+
主题文件格式:
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"metadata": {
|
|
520
|
+
"name": "my-ocean",
|
|
521
|
+
"displayName": "我的海洋主题",
|
|
522
|
+
"description": "深邃的海洋蓝配色",
|
|
523
|
+
"category": "dark",
|
|
524
|
+
"author": "Your Name"
|
|
525
|
+
},
|
|
526
|
+
"colors": {
|
|
527
|
+
"primary": "#0077BE",
|
|
528
|
+
"secondary": "#5DADE2",
|
|
529
|
+
"accent": "#48C9B0",
|
|
530
|
+
"success": "#2ECC71",
|
|
531
|
+
"error": "#E74C3C",
|
|
532
|
+
"warning": "#F39C12",
|
|
533
|
+
"info": "#3498DB",
|
|
534
|
+
"text": {
|
|
535
|
+
"primary": "#ECF0F1",
|
|
536
|
+
"secondary": "#BDC3C7",
|
|
537
|
+
"muted": "#95A5A6",
|
|
538
|
+
"dim": "#7F8C8D"
|
|
539
|
+
},
|
|
540
|
+
"border": "#34495E",
|
|
541
|
+
"divider": "#2C3E50",
|
|
542
|
+
"code": {
|
|
543
|
+
"background": "#1C2833",
|
|
544
|
+
"text": "#ECF0F1",
|
|
545
|
+
"keyword": "#C678DD",
|
|
546
|
+
"string": "#98C379",
|
|
547
|
+
"function": "#61AFEF",
|
|
548
|
+
"comment": "#5C6370"
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
#### 3. 验证主题
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
pls theme validate ~/.please/themes/my-ocean.json
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
验证通过后会显示主题信息,失败会给出详细的错误提示。
|
|
561
|
+
|
|
562
|
+
#### 4. 应用自定义主题
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
pls theme my-ocean
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### 5. 管理自定义主题
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
# 查看所有主题(内置 + 自定义)
|
|
572
|
+
pls theme list
|
|
573
|
+
|
|
574
|
+
# 只查看自定义主题
|
|
575
|
+
pls theme list --custom
|
|
576
|
+
|
|
577
|
+
# 只查看内置主题
|
|
578
|
+
pls theme list --builtin
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
自定义主题在列表中会显示 ✨ 标记。
|
|
582
|
+
|
|
583
|
+
**主题文件位置:** `~/.please/themes/`
|
|
584
|
+
|
|
585
|
+
**颜色格式:** 所有颜色必须使用十六进制格式(如 `#00D9FF`)
|
|
586
|
+
|
|
486
587
|
## 🏷️ 命令别名
|
|
487
588
|
|
|
488
589
|
将常用的操作保存为别名,一键触发:
|
|
@@ -594,7 +695,13 @@ pls hook status
|
|
|
594
695
|
# 主题
|
|
595
696
|
pls theme # 查看当前主题
|
|
596
697
|
pls theme list # 查看所有主题
|
|
597
|
-
pls theme <
|
|
698
|
+
pls theme <name> # 切换主题(dark/light/nord/dracula/retro/contrast/monokai)
|
|
699
|
+
|
|
700
|
+
# 自定义主题
|
|
701
|
+
pls theme create <name> --display-name "名称" # 创建主题模板
|
|
702
|
+
pls theme validate <file> # 验证主题文件
|
|
703
|
+
pls theme list --custom # 只显示自定义主题
|
|
704
|
+
pls theme list --builtin # 只显示内置主题
|
|
598
705
|
|
|
599
706
|
# 别名
|
|
600
707
|
pls alias # 查看所有别名
|
package/bin/pls.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Command } from 'commander'
|
|
3
3
|
import { fileURLToPath } from 'url'
|
|
4
4
|
import { dirname, join } from 'path'
|
|
5
|
+
import path from 'path'
|
|
5
6
|
import { exec } from 'child_process'
|
|
6
7
|
import fs from 'fs'
|
|
7
8
|
import os from 'os'
|
|
@@ -126,10 +127,19 @@ function executeCommand(command: string): Promise<{ exitCode: number; output: st
|
|
|
126
127
|
|
|
127
128
|
console.log('') // 空行
|
|
128
129
|
|
|
129
|
-
//
|
|
130
|
+
// 计算命令框宽度,让分隔线长度一致(限制终端宽度)
|
|
131
|
+
const termWidth = process.stdout.columns || 80
|
|
132
|
+
const maxContentWidth = termWidth - 6
|
|
130
133
|
const lines = command.split('\n')
|
|
131
|
-
const
|
|
132
|
-
|
|
134
|
+
const wrappedLines: string[] = []
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
wrappedLines.push(...console2.wrapText(line, maxContentWidth))
|
|
137
|
+
}
|
|
138
|
+
const actualMaxWidth = Math.max(
|
|
139
|
+
...wrappedLines.map((l) => console2.getDisplayWidth(l)),
|
|
140
|
+
console2.getDisplayWidth('生成命令')
|
|
141
|
+
)
|
|
142
|
+
const boxWidth = Math.max(console2.MIN_COMMAND_BOX_WIDTH, Math.min(actualMaxWidth + 4, termWidth - 2))
|
|
133
143
|
console2.printSeparator('输出', boxWidth)
|
|
134
144
|
|
|
135
145
|
// 使用 bash 并启用 pipefail,确保管道中任何命令失败都能正确返回非零退出码
|
|
@@ -217,11 +227,21 @@ configCmd
|
|
|
217
227
|
configCmd
|
|
218
228
|
.command('set <key> <value>')
|
|
219
229
|
.description('设置配置项 (apiKey, baseUrl, provider, model, shellHook, chatHistoryLimit)')
|
|
220
|
-
.action((key, value) => {
|
|
230
|
+
.action(async (key, value) => {
|
|
221
231
|
try {
|
|
232
|
+
const oldConfig = getConfig()
|
|
233
|
+
const oldShellHistoryLimit = oldConfig.shellHistoryLimit
|
|
234
|
+
|
|
222
235
|
setConfigValue(key, value)
|
|
223
236
|
console.log('')
|
|
224
237
|
console2.success(`已设置 ${key}`)
|
|
238
|
+
|
|
239
|
+
// 如果修改了 shellHistoryLimit,自动重装 hook
|
|
240
|
+
if (key === 'shellHistoryLimit') {
|
|
241
|
+
const { reinstallHookForLimitChange } = await import('../src/shell-hook.js')
|
|
242
|
+
await reinstallHookForLimitChange(oldShellHistoryLimit, Number(value))
|
|
243
|
+
}
|
|
244
|
+
|
|
225
245
|
console.log('')
|
|
226
246
|
} catch (error: any) {
|
|
227
247
|
console.log('')
|
|
@@ -243,8 +263,10 @@ const themeCmd = program.command('theme').description('管理主题')
|
|
|
243
263
|
themeCmd
|
|
244
264
|
.command('list')
|
|
245
265
|
.description('查看所有可用主题')
|
|
246
|
-
.
|
|
247
|
-
|
|
266
|
+
.option('--custom', '只显示自定义主题')
|
|
267
|
+
.option('--builtin', '只显示内置主题')
|
|
268
|
+
.action(async (options: { custom?: boolean; builtin?: boolean }) => {
|
|
269
|
+
const { getAllThemeMetadata, isBuiltinTheme } = await import('../src/ui/theme.js')
|
|
248
270
|
const config = getConfig()
|
|
249
271
|
const currentTheme = config.theme || 'dark'
|
|
250
272
|
|
|
@@ -252,37 +274,82 @@ themeCmd
|
|
|
252
274
|
console2.title('🎨 可用主题:')
|
|
253
275
|
console2.muted('━'.repeat(50))
|
|
254
276
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const prefix = isCurrent ? '●' : '○'
|
|
258
|
-
const label = themeName === 'dark' ? 'dark (深色)' : 'light (浅色)'
|
|
259
|
-
const color = themeName === 'dark' ? '#00D9FF' : '#0284C7'
|
|
277
|
+
// 动态获取所有主题元数据
|
|
278
|
+
const allThemes = getAllThemeMetadata()
|
|
260
279
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
280
|
+
// 根据选项过滤主题
|
|
281
|
+
const builtinThemes = allThemes.filter((meta) => isBuiltinTheme(meta.name))
|
|
282
|
+
const customThemes = allThemes.filter((meta) => !isBuiltinTheme(meta.name))
|
|
283
|
+
|
|
284
|
+
// 显示内置主题
|
|
285
|
+
if (!options.custom) {
|
|
286
|
+
if (builtinThemes.length > 0) {
|
|
287
|
+
console.log('')
|
|
288
|
+
console2.info('内置主题:')
|
|
289
|
+
builtinThemes.forEach((meta) => {
|
|
290
|
+
const isCurrent = meta.name === currentTheme
|
|
291
|
+
const prefix = isCurrent ? '●' : '○'
|
|
292
|
+
const label = `${meta.name} (${meta.displayName})`
|
|
293
|
+
|
|
294
|
+
if (isCurrent) {
|
|
295
|
+
console.log(` ${chalk.hex(meta.previewColor)(prefix)} ${chalk.hex(meta.previewColor).bold(label)} ${chalk.gray('(当前)')}`)
|
|
296
|
+
} else {
|
|
297
|
+
console.log(` ${chalk.gray(prefix)} ${label}`)
|
|
298
|
+
}
|
|
299
|
+
})
|
|
265
300
|
}
|
|
266
|
-
}
|
|
301
|
+
}
|
|
267
302
|
|
|
303
|
+
// 显示自定义主题
|
|
304
|
+
if (!options.builtin) {
|
|
305
|
+
if (customThemes.length > 0) {
|
|
306
|
+
console.log('')
|
|
307
|
+
console2.info('自定义主题:')
|
|
308
|
+
customThemes.forEach((meta) => {
|
|
309
|
+
const isCurrent = meta.name === currentTheme
|
|
310
|
+
const prefix = isCurrent ? '●' : '○'
|
|
311
|
+
const label = `${meta.name} (${meta.displayName})`
|
|
312
|
+
const emoji = ' ✨'
|
|
313
|
+
|
|
314
|
+
if (isCurrent) {
|
|
315
|
+
console.log(` ${chalk.hex(meta.previewColor)(prefix)} ${chalk.hex(meta.previewColor).bold(label)}${emoji} ${chalk.gray('(当前)')}`)
|
|
316
|
+
} else {
|
|
317
|
+
console.log(` ${chalk.gray(prefix)} ${label}${emoji}`)
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
} else if (options.custom) {
|
|
321
|
+
console.log('')
|
|
322
|
+
console2.muted(' 还没有自定义主题')
|
|
323
|
+
console2.muted(' 使用 pls theme create <name> 创建')
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log('')
|
|
268
328
|
console2.muted('━'.repeat(50))
|
|
269
329
|
console.log('')
|
|
270
330
|
})
|
|
271
331
|
|
|
272
332
|
themeCmd
|
|
273
|
-
.argument('[name]', '主题名称
|
|
333
|
+
.argument('[name]', '主题名称')
|
|
274
334
|
.description('切换主题')
|
|
275
|
-
.action((name?: string) => {
|
|
335
|
+
.action(async (name?: string) => {
|
|
336
|
+
const { getThemeMetadata, getAllThemeMetadata, isValidTheme } = await import('../src/ui/theme.js')
|
|
337
|
+
|
|
276
338
|
if (!name) {
|
|
277
339
|
// 显示当前主题
|
|
278
340
|
const config = getConfig()
|
|
279
341
|
const currentTheme = config.theme || 'dark'
|
|
280
|
-
const
|
|
281
|
-
|
|
342
|
+
const meta = getThemeMetadata(currentTheme as any)
|
|
343
|
+
|
|
344
|
+
if (meta) {
|
|
345
|
+
console.log('')
|
|
346
|
+
console.log(`当前主题: ${chalk.hex(meta.previewColor).bold(`${meta.name} (${meta.displayName})`)}`)
|
|
347
|
+
if (meta.description) {
|
|
348
|
+
console2.muted(` ${meta.description}`)
|
|
349
|
+
}
|
|
350
|
+
console.log('')
|
|
351
|
+
}
|
|
282
352
|
|
|
283
|
-
console.log('')
|
|
284
|
-
console.log(`当前主题: ${chalk.hex(color).bold(label)}`)
|
|
285
|
-
console.log('')
|
|
286
353
|
console2.muted('使用 pls theme list 查看所有主题')
|
|
287
354
|
console2.muted('使用 pls theme <name> 切换主题')
|
|
288
355
|
console.log('')
|
|
@@ -291,12 +358,91 @@ themeCmd
|
|
|
291
358
|
|
|
292
359
|
// 切换主题
|
|
293
360
|
try {
|
|
361
|
+
// 验证主题是否存在
|
|
362
|
+
if (!isValidTheme(name)) {
|
|
363
|
+
const allThemes = getAllThemeMetadata()
|
|
364
|
+
const themeNames = allThemes.map((m) => m.name).join(', ')
|
|
365
|
+
throw new Error(`未知主题 "${name}",可用主题: ${themeNames}`)
|
|
366
|
+
}
|
|
367
|
+
|
|
294
368
|
setConfigValue('theme', name)
|
|
295
|
-
const
|
|
296
|
-
const color = name === 'dark' ? '#00D9FF' : '#0284C7'
|
|
369
|
+
const meta = getThemeMetadata(name)
|
|
297
370
|
|
|
371
|
+
if (meta) {
|
|
372
|
+
console.log('')
|
|
373
|
+
console2.success(`已切换到 ${chalk.hex(meta.previewColor).bold(`${meta.name} (${meta.displayName})`)} 主题`)
|
|
374
|
+
if (meta.description) {
|
|
375
|
+
console2.muted(` ${meta.description}`)
|
|
376
|
+
}
|
|
377
|
+
console.log('')
|
|
378
|
+
}
|
|
379
|
+
} catch (error: any) {
|
|
380
|
+
console.log('')
|
|
381
|
+
console2.error(error.message)
|
|
298
382
|
console.log('')
|
|
299
|
-
|
|
383
|
+
process.exit(1)
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
// theme create - 创建主题模板
|
|
388
|
+
themeCmd
|
|
389
|
+
.command('create <name>')
|
|
390
|
+
.description('创建自定义主题模板')
|
|
391
|
+
.option('-d, --display-name <name>', '显示名称')
|
|
392
|
+
.option('-c, --category <type>', '主题类别 (dark 或 light)', 'dark')
|
|
393
|
+
.action(async (name: string, options: { displayName?: string; category?: string }) => {
|
|
394
|
+
const { createThemeTemplate } = await import('../src/ui/theme.js')
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
// 验证主题名称格式
|
|
398
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
399
|
+
throw new Error('主题名称只能包含小写字母、数字和连字符')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 验证类别
|
|
403
|
+
const category = options.category as 'dark' | 'light'
|
|
404
|
+
if (category !== 'dark' && category !== 'light') {
|
|
405
|
+
throw new Error('主题类别必须是 dark 或 light')
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 创建主题目录
|
|
409
|
+
const themesDir = path.join(os.homedir(), '.please', 'themes')
|
|
410
|
+
if (!fs.existsSync(themesDir)) {
|
|
411
|
+
fs.mkdirSync(themesDir, { recursive: true })
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 检查主题文件是否已存在
|
|
415
|
+
const themePath = path.join(themesDir, `${name}.json`)
|
|
416
|
+
if (fs.existsSync(themePath)) {
|
|
417
|
+
throw new Error(`主题 "${name}" 已存在`)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 创建主题模板
|
|
421
|
+
const displayName = options.displayName || name
|
|
422
|
+
const template = createThemeTemplate(name, displayName, category)
|
|
423
|
+
|
|
424
|
+
// 保存到文件
|
|
425
|
+
fs.writeFileSync(themePath, JSON.stringify(template, null, 2), 'utf-8')
|
|
426
|
+
|
|
427
|
+
// 显示成功信息
|
|
428
|
+
console.log('')
|
|
429
|
+
console2.success(`已创建主题模板: ${themePath}`)
|
|
430
|
+
console.log('')
|
|
431
|
+
|
|
432
|
+
console2.info('📝 下一步:')
|
|
433
|
+
console.log(` 1. 编辑主题文件修改颜色配置`)
|
|
434
|
+
console2.muted(` vim ${themePath}`)
|
|
435
|
+
console.log('')
|
|
436
|
+
console.log(` 2. 验证主题格式`)
|
|
437
|
+
console2.muted(` pls theme validate ${themePath}`)
|
|
438
|
+
console.log('')
|
|
439
|
+
console.log(` 3. 应用主题查看效果`)
|
|
440
|
+
console2.muted(` pls theme ${name}`)
|
|
441
|
+
console.log('')
|
|
442
|
+
|
|
443
|
+
console2.info('💡 提示:')
|
|
444
|
+
console2.muted(' - 使用在线工具选择颜色: https://colorhunt.co')
|
|
445
|
+
console2.muted(' - 参考内置主题: pls theme list')
|
|
300
446
|
console.log('')
|
|
301
447
|
} catch (error: any) {
|
|
302
448
|
console.log('')
|
|
@@ -306,6 +452,75 @@ themeCmd
|
|
|
306
452
|
}
|
|
307
453
|
})
|
|
308
454
|
|
|
455
|
+
// theme validate - 验证主题文件
|
|
456
|
+
themeCmd
|
|
457
|
+
.command('validate <file>')
|
|
458
|
+
.description('验证主题文件格式')
|
|
459
|
+
.action(async (file: string) => {
|
|
460
|
+
const { validateThemeWithDetails } = await import('../src/ui/theme.js')
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
// 读取主题文件
|
|
464
|
+
const themePath = path.isAbsolute(file) ? file : path.join(process.cwd(), file)
|
|
465
|
+
|
|
466
|
+
if (!fs.existsSync(themePath)) {
|
|
467
|
+
throw new Error(`文件不存在: ${themePath}`)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const content = fs.readFileSync(themePath, 'utf-8')
|
|
471
|
+
const theme = JSON.parse(content)
|
|
472
|
+
|
|
473
|
+
// 验证主题
|
|
474
|
+
const result = validateThemeWithDetails(theme)
|
|
475
|
+
|
|
476
|
+
console.log('')
|
|
477
|
+
|
|
478
|
+
if (result.valid) {
|
|
479
|
+
console2.success('✓ 主题验证通过')
|
|
480
|
+
console.log('')
|
|
481
|
+
|
|
482
|
+
if (theme.metadata) {
|
|
483
|
+
console2.info('主题信息:')
|
|
484
|
+
console.log(` 名称: ${theme.metadata.name} (${theme.metadata.displayName})`)
|
|
485
|
+
console.log(` 类别: ${theme.metadata.category}`)
|
|
486
|
+
if (theme.metadata.description) {
|
|
487
|
+
console.log(` 描述: ${theme.metadata.description}`)
|
|
488
|
+
}
|
|
489
|
+
if (theme.metadata.author) {
|
|
490
|
+
console.log(` 作者: ${theme.metadata.author}`)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log('')
|
|
495
|
+
} else {
|
|
496
|
+
console2.error('✗ 主题验证失败')
|
|
497
|
+
console.log('')
|
|
498
|
+
console2.info('错误列表:')
|
|
499
|
+
result.errors.forEach((err, idx) => {
|
|
500
|
+
console.log(` ${idx + 1}. ${err}`)
|
|
501
|
+
})
|
|
502
|
+
console.log('')
|
|
503
|
+
|
|
504
|
+
console2.info('修复建议:')
|
|
505
|
+
console2.muted(` 1. 编辑主题文件: vim ${themePath}`)
|
|
506
|
+
console2.muted(' 2. 参考内置主题格式')
|
|
507
|
+
console2.muted(' 3. 确保所有颜色使用 #RRGGBB 格式')
|
|
508
|
+
console.log('')
|
|
509
|
+
|
|
510
|
+
process.exit(1)
|
|
511
|
+
}
|
|
512
|
+
} catch (error: any) {
|
|
513
|
+
console.log('')
|
|
514
|
+
if (error.message.includes('Unexpected token')) {
|
|
515
|
+
console2.error('JSON 格式错误,请检查文件语法')
|
|
516
|
+
} else {
|
|
517
|
+
console2.error(error.message)
|
|
518
|
+
}
|
|
519
|
+
console.log('')
|
|
520
|
+
process.exit(1)
|
|
521
|
+
}
|
|
522
|
+
})
|
|
523
|
+
|
|
309
524
|
// history 子命令
|
|
310
525
|
const historyCmd = program.command('history').description('查看或管理命令历史')
|
|
311
526
|
|
|
@@ -962,24 +1177,17 @@ remoteCmd.action(() => {
|
|
|
962
1177
|
displayRemotes()
|
|
963
1178
|
})
|
|
964
1179
|
|
|
965
|
-
// chat
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
.
|
|
970
|
-
.description('清空对话历史')
|
|
971
|
-
.action(() => {
|
|
972
|
-
clearChatHistory()
|
|
973
|
-
console.log('')
|
|
974
|
-
console2.success('对话历史已清空')
|
|
975
|
-
console.log('')
|
|
976
|
-
})
|
|
977
|
-
|
|
978
|
-
// 默认 chat 命令(进行对话)
|
|
979
|
-
chatCmd
|
|
980
|
-
.argument('[prompt...]', '你的问题')
|
|
1180
|
+
// chat 命令(AI 对话)
|
|
1181
|
+
program
|
|
1182
|
+
.command('chat')
|
|
1183
|
+
.description('AI 对话模式,问答、讲解命令')
|
|
1184
|
+
.argument('[prompt...]', '你的问题(不提供则显示状态)')
|
|
981
1185
|
.option('-d, --debug', '显示调试信息')
|
|
982
1186
|
.action((promptArgs, options) => {
|
|
1187
|
+
// Workaround: Commander.js 14.x 的子命令 option 解析有 bug
|
|
1188
|
+
// 直接从 process.argv 检查 --debug
|
|
1189
|
+
const debug = process.argv.includes('--debug') || process.argv.includes('-d')
|
|
1190
|
+
|
|
983
1191
|
const prompt = promptArgs.join(' ')
|
|
984
1192
|
|
|
985
1193
|
if (!prompt.trim()) {
|
|
@@ -995,8 +1203,8 @@ chatCmd
|
|
|
995
1203
|
console2.muted('━'.repeat(40))
|
|
996
1204
|
console.log('')
|
|
997
1205
|
console2.muted('用法:')
|
|
998
|
-
console2.info(' pls chat <问题>
|
|
999
|
-
console2.info(' pls chat clear
|
|
1206
|
+
console2.info(' pls chat <问题> 与 AI 对话')
|
|
1207
|
+
console2.info(' pls history chat clear 清空对话历史')
|
|
1000
1208
|
console.log('')
|
|
1001
1209
|
return
|
|
1002
1210
|
}
|
|
@@ -1019,7 +1227,7 @@ chatCmd
|
|
|
1019
1227
|
render(
|
|
1020
1228
|
React.createElement(Chat, {
|
|
1021
1229
|
prompt,
|
|
1022
|
-
debug:
|
|
1230
|
+
debug: debug, // 使用 debug 变量
|
|
1023
1231
|
showRoundCount: true,
|
|
1024
1232
|
onComplete: () => process.exit(0),
|
|
1025
1233
|
})
|
|
@@ -1297,9 +1505,6 @@ program
|
|
|
1297
1505
|
|
|
1298
1506
|
// 处理步骤结果
|
|
1299
1507
|
if (!stepResult || stepResult.cancelled) {
|
|
1300
|
-
console.log('')
|
|
1301
|
-
console2.muted('已取消执行')
|
|
1302
|
-
console.log('')
|
|
1303
1508
|
process.exit(0)
|
|
1304
1509
|
}
|
|
1305
1510
|
|
|
@@ -1495,10 +1700,19 @@ async function executeRemoteCommand(
|
|
|
1495
1700
|
|
|
1496
1701
|
console.log('') // 空行
|
|
1497
1702
|
|
|
1498
|
-
//
|
|
1703
|
+
// 计算命令框宽度,让分隔线长度一致(限制终端宽度)
|
|
1704
|
+
const termWidth = process.stdout.columns || 80
|
|
1705
|
+
const maxContentWidth = termWidth - 6
|
|
1499
1706
|
const lines = command.split('\n')
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1707
|
+
const wrappedLines: string[] = []
|
|
1708
|
+
for (const line of lines) {
|
|
1709
|
+
wrappedLines.push(...console2.wrapText(line, maxContentWidth))
|
|
1710
|
+
}
|
|
1711
|
+
const actualMaxWidth = Math.max(
|
|
1712
|
+
...wrappedLines.map((l) => console2.getDisplayWidth(l)),
|
|
1713
|
+
console2.getDisplayWidth('生成命令')
|
|
1714
|
+
)
|
|
1715
|
+
const boxWidth = Math.max(console2.MIN_COMMAND_BOX_WIDTH, Math.min(actualMaxWidth + 4, termWidth - 2))
|
|
1502
1716
|
console2.printSeparator(`远程输出 (${remoteName})`, boxWidth)
|
|
1503
1717
|
|
|
1504
1718
|
try {
|