@yivan-lab/pretty-please 1.0.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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -0
  3. package/bin/pls.js +681 -0
  4. package/bin/pls.tsx +541 -0
  5. package/dist/bin/pls.d.ts +2 -0
  6. package/dist/bin/pls.js +429 -0
  7. package/dist/src/ai.d.ts +48 -0
  8. package/dist/src/ai.js +295 -0
  9. package/dist/src/builtin-detector.d.ts +15 -0
  10. package/dist/src/builtin-detector.js +83 -0
  11. package/dist/src/chat-history.d.ts +26 -0
  12. package/dist/src/chat-history.js +81 -0
  13. package/dist/src/components/Chat.d.ts +13 -0
  14. package/dist/src/components/Chat.js +80 -0
  15. package/dist/src/components/ChatStatus.d.ts +9 -0
  16. package/dist/src/components/ChatStatus.js +34 -0
  17. package/dist/src/components/CodeColorizer.d.ts +12 -0
  18. package/dist/src/components/CodeColorizer.js +82 -0
  19. package/dist/src/components/CommandBox.d.ts +10 -0
  20. package/dist/src/components/CommandBox.js +45 -0
  21. package/dist/src/components/CommandGenerator.d.ts +20 -0
  22. package/dist/src/components/CommandGenerator.js +116 -0
  23. package/dist/src/components/ConfigDisplay.d.ts +9 -0
  24. package/dist/src/components/ConfigDisplay.js +42 -0
  25. package/dist/src/components/ConfigWizard.d.ts +9 -0
  26. package/dist/src/components/ConfigWizard.js +72 -0
  27. package/dist/src/components/ConfirmationPrompt.d.ts +12 -0
  28. package/dist/src/components/ConfirmationPrompt.js +26 -0
  29. package/dist/src/components/Duration.d.ts +9 -0
  30. package/dist/src/components/Duration.js +21 -0
  31. package/dist/src/components/HistoryDisplay.d.ts +9 -0
  32. package/dist/src/components/HistoryDisplay.js +51 -0
  33. package/dist/src/components/HookManager.d.ts +10 -0
  34. package/dist/src/components/HookManager.js +88 -0
  35. package/dist/src/components/InlineRenderer.d.ts +12 -0
  36. package/dist/src/components/InlineRenderer.js +75 -0
  37. package/dist/src/components/MarkdownDisplay.d.ts +13 -0
  38. package/dist/src/components/MarkdownDisplay.js +197 -0
  39. package/dist/src/components/MultiStepCommandGenerator.d.ts +25 -0
  40. package/dist/src/components/MultiStepCommandGenerator.js +142 -0
  41. package/dist/src/components/TableRenderer.d.ts +12 -0
  42. package/dist/src/components/TableRenderer.js +66 -0
  43. package/dist/src/config.d.ts +29 -0
  44. package/dist/src/config.js +203 -0
  45. package/dist/src/history.d.ts +20 -0
  46. package/dist/src/history.js +113 -0
  47. package/dist/src/mastra-agent.d.ts +7 -0
  48. package/dist/src/mastra-agent.js +31 -0
  49. package/dist/src/multi-step.d.ts +41 -0
  50. package/dist/src/multi-step.js +67 -0
  51. package/dist/src/shell-hook.d.ts +35 -0
  52. package/dist/src/shell-hook.js +348 -0
  53. package/dist/src/sysinfo.d.ts +15 -0
  54. package/dist/src/sysinfo.js +52 -0
  55. package/dist/src/ui/theme.d.ts +26 -0
  56. package/dist/src/ui/theme.js +31 -0
  57. package/dist/src/utils/console.d.ts +44 -0
  58. package/dist/src/utils/console.js +114 -0
  59. package/package.json +78 -0
  60. package/src/ai.js +324 -0
  61. package/src/builtin-detector.js +98 -0
  62. package/src/chat-history.js +94 -0
  63. package/src/components/Chat.tsx +122 -0
  64. package/src/components/ChatStatus.tsx +53 -0
  65. package/src/components/CodeColorizer.tsx +128 -0
  66. package/src/components/CommandBox.tsx +60 -0
  67. package/src/components/CommandGenerator.tsx +184 -0
  68. package/src/components/ConfigDisplay.tsx +64 -0
  69. package/src/components/ConfigWizard.tsx +101 -0
  70. package/src/components/ConfirmationPrompt.tsx +41 -0
  71. package/src/components/Duration.tsx +24 -0
  72. package/src/components/HistoryDisplay.tsx +69 -0
  73. package/src/components/HookManager.tsx +150 -0
  74. package/src/components/InlineRenderer.tsx +123 -0
  75. package/src/components/MarkdownDisplay.tsx +288 -0
  76. package/src/components/MultiStepCommandGenerator.tsx +229 -0
  77. package/src/components/TableRenderer.tsx +110 -0
  78. package/src/config.js +221 -0
  79. package/src/history.js +131 -0
  80. package/src/mastra-agent.ts +35 -0
  81. package/src/multi-step.ts +93 -0
  82. package/src/shell-hook.js +393 -0
  83. package/src/sysinfo.js +57 -0
  84. package/src/ui/theme.ts +37 -0
  85. package/src/utils/console.js +130 -0
  86. package/tsconfig.json +23 -0
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import { theme } from '../ui/theme.js';
4
+ /**
5
+ * 行内 Markdown 渲染器
6
+ * 处理 **粗体**、*斜体*、`代码`、~~删除线~~、<u>下划线</u>、链接
7
+ */
8
+ function RenderInlineInternal({ text, defaultColor }) {
9
+ const baseColor = defaultColor || theme.text.primary;
10
+ // 快速路径:纯文本无 markdown
11
+ if (!/[*_~`<[https?:]/.test(text)) {
12
+ return React.createElement(Text, { color: baseColor }, text);
13
+ }
14
+ const nodes = [];
15
+ let lastIndex = 0;
16
+ // 匹配所有行内 markdown 语法(添加删除线、下划线、链接)
17
+ const inlineRegex = /(~~.+?~~|\*\*[^*]+?\*\*|\*[^*]+?\*|_[^_]+?_|`+[^`]+?`+|\[.*?\]\(.*?\)|<u>.*?<\/u>|https?:\/\/\S+)/g;
18
+ let match;
19
+ while ((match = inlineRegex.exec(text)) !== null) {
20
+ // 添加匹配之前的普通文本
21
+ if (match.index > lastIndex) {
22
+ nodes.push(React.createElement(Text, { key: `t-${lastIndex}`, color: baseColor }, text.slice(lastIndex, match.index)));
23
+ }
24
+ const fullMatch = match[0];
25
+ let renderedNode = null;
26
+ const key = `m-${match.index}`;
27
+ // **粗体**
28
+ if (fullMatch.startsWith('**') && fullMatch.endsWith('**') && fullMatch.length > 4) {
29
+ renderedNode = (React.createElement(Text, { key: key, bold: true, color: baseColor }, fullMatch.slice(2, -2)));
30
+ }
31
+ // ~~删除线~~
32
+ else if (fullMatch.startsWith('~~') && fullMatch.endsWith('~~') && fullMatch.length > 4) {
33
+ renderedNode = (React.createElement(Text, { key: key, strikethrough: true, color: baseColor }, fullMatch.slice(2, -2)));
34
+ }
35
+ // *斜体*
36
+ else if (fullMatch.startsWith('*') && fullMatch.endsWith('*') && fullMatch.length > 2 && !fullMatch.startsWith('**')) {
37
+ renderedNode = (React.createElement(Text, { key: key, italic: true, color: baseColor }, fullMatch.slice(1, -1)));
38
+ }
39
+ // `行内代码`
40
+ else if (fullMatch.startsWith('`') && fullMatch.endsWith('`')) {
41
+ const codeText = fullMatch.slice(1, -1);
42
+ renderedNode = (React.createElement(Text, { key: key, color: theme.primary }, codeText));
43
+ }
44
+ // [链接文本](URL)
45
+ else if (fullMatch.startsWith('[') && fullMatch.includes('](') && fullMatch.endsWith(')')) {
46
+ const linkMatch = fullMatch.match(/\[(.*?)\]\((.*?)\)/);
47
+ if (linkMatch) {
48
+ const linkText = linkMatch[1];
49
+ const url = linkMatch[2];
50
+ renderedNode = (React.createElement(Text, { key: key, color: baseColor },
51
+ linkText,
52
+ React.createElement(Text, { color: theme.info },
53
+ " (",
54
+ url,
55
+ ")")));
56
+ }
57
+ }
58
+ // <u>下划线</u>
59
+ else if (fullMatch.startsWith('<u>') && fullMatch.endsWith('</u>') && fullMatch.length > 7) {
60
+ renderedNode = (React.createElement(Text, { key: key, underline: true, color: baseColor }, fullMatch.slice(3, -4)));
61
+ }
62
+ // 裸 URL
63
+ else if (fullMatch.match(/^https?:\/\//)) {
64
+ renderedNode = (React.createElement(Text, { key: key, color: theme.info }, fullMatch));
65
+ }
66
+ nodes.push(renderedNode || React.createElement(Text, { key: key, color: baseColor }, fullMatch));
67
+ lastIndex = inlineRegex.lastIndex;
68
+ }
69
+ // 添加剩余的普通文本
70
+ if (lastIndex < text.length) {
71
+ nodes.push(React.createElement(Text, { key: `t-${lastIndex}`, color: baseColor }, text.slice(lastIndex)));
72
+ }
73
+ return React.createElement(React.Fragment, null, nodes);
74
+ }
75
+ export const RenderInline = React.memo(RenderInlineInternal);
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ interface MarkdownDisplayProps {
3
+ text: string;
4
+ terminalWidth?: number;
5
+ }
6
+ /**
7
+ * Markdown 主渲染组件
8
+ * 参考 gemini-cli 的实现
9
+ * 支持:标题、列表、代码块、表格、粗体、斜体、链接
10
+ */
11
+ declare function MarkdownDisplayInternal({ text, terminalWidth }: MarkdownDisplayProps): React.JSX.Element;
12
+ export declare const MarkdownDisplay: React.MemoExoticComponent<typeof MarkdownDisplayInternal>;
13
+ export {};
@@ -0,0 +1,197 @@
1
+ import React from 'react';
2
+ import { Text, Box } from 'ink';
3
+ import { theme } from '../ui/theme.js';
4
+ import { ColorizeCode } from './CodeColorizer.js';
5
+ import { TableRenderer } from './TableRenderer.js';
6
+ import { RenderInline } from './InlineRenderer.js';
7
+ /**
8
+ * Markdown 主渲染组件
9
+ * 参考 gemini-cli 的实现
10
+ * 支持:标题、列表、代码块、表格、粗体、斜体、链接
11
+ */
12
+ function MarkdownDisplayInternal({ text, terminalWidth = 80 }) {
13
+ if (!text)
14
+ return React.createElement(React.Fragment, null);
15
+ const lines = text.split(/\r?\n/);
16
+ // 正则表达式
17
+ const headerRegex = /^ *(#{1,4}) +(.*)/;
18
+ const codeFenceRegex = /^ *(`{3,}|~{3,}) *(\w*?) *$/;
19
+ const ulItemRegex = /^([ \t]*)([-*+]) +(.*)/;
20
+ const olItemRegex = /^([ \t]*)(\d+)\. +(.*)/;
21
+ const hrRegex = /^ *([-*_] *){3,} *$/;
22
+ // 表格行:支持有无尾部 | 的情况
23
+ const tableRowRegex = /^\s*\|(.+?)(?:\|)?\s*$/;
24
+ const tableSeparatorRegex = /^\s*\|?\s*(:?-+:?)\s*(\|\s*(:?-+:?)\s*)+\|?\s*$/;
25
+ const contentBlocks = [];
26
+ let inCodeBlock = false;
27
+ let lastLineEmpty = true;
28
+ let codeBlockContent = [];
29
+ let codeBlockLang = null;
30
+ let codeBlockFence = '';
31
+ let inTable = false;
32
+ let tableRows = [];
33
+ let tableHeaders = [];
34
+ function addContentBlock(block) {
35
+ if (block) {
36
+ contentBlocks.push(block);
37
+ lastLineEmpty = false;
38
+ }
39
+ }
40
+ lines.forEach((line, index) => {
41
+ const key = `line-${index}`;
42
+ // 代码块内部
43
+ if (inCodeBlock) {
44
+ const fenceMatch = line.match(codeFenceRegex);
45
+ if (fenceMatch &&
46
+ fenceMatch[1].startsWith(codeBlockFence[0]) &&
47
+ fenceMatch[1].length >= codeBlockFence.length) {
48
+ // 代码块结束
49
+ addContentBlock(React.createElement(ColorizeCode, { key: key, code: codeBlockContent.join('\n'), language: codeBlockLang, showLineNumbers: false }));
50
+ inCodeBlock = false;
51
+ codeBlockContent = [];
52
+ codeBlockLang = null;
53
+ codeBlockFence = '';
54
+ }
55
+ else {
56
+ codeBlockContent.push(line);
57
+ }
58
+ return;
59
+ }
60
+ const codeFenceMatch = line.match(codeFenceRegex);
61
+ const headerMatch = line.match(headerRegex);
62
+ const ulMatch = line.match(ulItemRegex);
63
+ const olMatch = line.match(olItemRegex);
64
+ const hrMatch = line.match(hrRegex);
65
+ const tableRowMatch = line.match(tableRowRegex);
66
+ const tableSeparatorMatch = line.match(tableSeparatorRegex);
67
+ // 代码块开始
68
+ if (codeFenceMatch) {
69
+ inCodeBlock = true;
70
+ codeBlockFence = codeFenceMatch[1];
71
+ codeBlockLang = codeFenceMatch[2] || null;
72
+ }
73
+ // 表格开始
74
+ else if (tableRowMatch && !inTable) {
75
+ if (index + 1 < lines.length && lines[index + 1].match(tableSeparatorRegex)) {
76
+ inTable = true;
77
+ tableHeaders = tableRowMatch[1].split('|').map(cell => cell.trim());
78
+ tableRows = [];
79
+ }
80
+ else {
81
+ addContentBlock(React.createElement(Box, { key: key },
82
+ React.createElement(Text, { wrap: "wrap", color: theme.text.primary },
83
+ React.createElement(RenderInline, { text: line, defaultColor: theme.text.primary }))));
84
+ }
85
+ }
86
+ // 表格分隔符
87
+ else if (inTable && tableSeparatorMatch) {
88
+ // 跳过分隔符行
89
+ }
90
+ // 表格行
91
+ else if (inTable && tableRowMatch) {
92
+ const cells = tableRowMatch[1].split('|').map(cell => cell.trim());
93
+ while (cells.length < tableHeaders.length)
94
+ cells.push('');
95
+ if (cells.length > tableHeaders.length)
96
+ cells.length = tableHeaders.length;
97
+ tableRows.push(cells);
98
+ }
99
+ // 表格结束
100
+ else if (inTable && !tableRowMatch) {
101
+ if (tableHeaders.length > 0 && tableRows.length > 0) {
102
+ addContentBlock(React.createElement(TableRenderer, { key: `table-${contentBlocks.length}`, headers: tableHeaders, rows: tableRows, terminalWidth: terminalWidth }));
103
+ }
104
+ inTable = false;
105
+ tableRows = [];
106
+ tableHeaders = [];
107
+ // 处理当前行
108
+ if (line.trim().length > 0) {
109
+ addContentBlock(React.createElement(Box, { key: key },
110
+ React.createElement(Text, { wrap: "wrap", color: theme.text.primary },
111
+ React.createElement(RenderInline, { text: line, defaultColor: theme.text.primary }))));
112
+ }
113
+ }
114
+ // 横线
115
+ else if (hrMatch) {
116
+ addContentBlock(React.createElement(Box, { key: key },
117
+ React.createElement(Text, { color: theme.border }, '─'.repeat(40))));
118
+ }
119
+ // 标题
120
+ else if (headerMatch) {
121
+ const level = headerMatch[1].length;
122
+ const headerText = headerMatch[2];
123
+ let headerNode = null;
124
+ switch (level) {
125
+ case 1:
126
+ headerNode = (React.createElement(Text, { bold: true, underline: true, color: theme.primary },
127
+ React.createElement(RenderInline, { text: headerText, defaultColor: theme.primary })));
128
+ break;
129
+ case 2:
130
+ headerNode = (React.createElement(Text, { bold: true, color: theme.secondary },
131
+ React.createElement(RenderInline, { text: headerText, defaultColor: theme.secondary })));
132
+ break;
133
+ case 3:
134
+ headerNode = (React.createElement(Text, { bold: true, color: theme.info },
135
+ React.createElement(RenderInline, { text: headerText, defaultColor: theme.info })));
136
+ break;
137
+ default:
138
+ headerNode = (React.createElement(Text, { bold: true, color: theme.text.primary },
139
+ React.createElement(RenderInline, { text: headerText, defaultColor: theme.text.primary })));
140
+ break;
141
+ }
142
+ if (headerNode) {
143
+ addContentBlock(React.createElement(Box, { key: key, marginTop: lastLineEmpty ? 0 : 1 }, headerNode));
144
+ }
145
+ }
146
+ // 无序列表
147
+ else if (ulMatch) {
148
+ const leadingWhitespace = ulMatch[1];
149
+ const itemText = ulMatch[3];
150
+ const indentation = leadingWhitespace.length;
151
+ addContentBlock(React.createElement(Box, { key: key, paddingLeft: indentation + 1, flexDirection: "row" },
152
+ React.createElement(Box, { width: 2 },
153
+ React.createElement(Text, { color: theme.text.primary }, "\u2022 ")),
154
+ React.createElement(Box, { flexGrow: 1 },
155
+ React.createElement(Text, { wrap: "wrap", color: theme.text.primary },
156
+ React.createElement(RenderInline, { text: itemText, defaultColor: theme.text.primary })))));
157
+ }
158
+ // 有序列表
159
+ else if (olMatch) {
160
+ const leadingWhitespace = olMatch[1];
161
+ const marker = olMatch[2];
162
+ const itemText = olMatch[3];
163
+ const indentation = leadingWhitespace.length;
164
+ const prefix = `${marker}. `;
165
+ addContentBlock(React.createElement(Box, { key: key, paddingLeft: indentation + 1, flexDirection: "row" },
166
+ React.createElement(Box, { width: prefix.length },
167
+ React.createElement(Text, { color: theme.text.primary }, prefix)),
168
+ React.createElement(Box, { flexGrow: 1 },
169
+ React.createElement(Text, { wrap: "wrap", color: theme.text.primary },
170
+ React.createElement(RenderInline, { text: itemText, defaultColor: theme.text.primary })))));
171
+ }
172
+ // 空行或普通文本
173
+ else {
174
+ if (line.trim().length === 0 && !inCodeBlock) {
175
+ // 空行:不添加额外的 Box,让段落自然分隔
176
+ if (!lastLineEmpty) {
177
+ lastLineEmpty = true;
178
+ }
179
+ }
180
+ else {
181
+ const inlineContent = React.createElement(RenderInline, { text: line, defaultColor: theme.text.primary });
182
+ addContentBlock(React.createElement(Box, { key: key },
183
+ React.createElement(Text, { wrap: "wrap", color: theme.text.primary }, inlineContent)));
184
+ }
185
+ }
186
+ });
187
+ // 处理未闭合的代码块
188
+ if (inCodeBlock) {
189
+ addContentBlock(React.createElement(ColorizeCode, { key: "line-eof", code: codeBlockContent.join('\n'), language: codeBlockLang, showLineNumbers: false }));
190
+ }
191
+ // 处理未闭合的表格
192
+ if (inTable && tableHeaders.length > 0 && tableRows.length > 0) {
193
+ addContentBlock(React.createElement(TableRenderer, { key: `table-${contentBlocks.length}`, headers: tableHeaders, rows: tableRows, terminalWidth: terminalWidth }));
194
+ }
195
+ return React.createElement(Box, { flexDirection: "column" }, contentBlocks);
196
+ }
197
+ export const MarkdownDisplay = React.memo(MarkdownDisplayInternal);
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { type ExecutedStep } from '../multi-step.js';
3
+ interface MultiStepCommandGeneratorProps {
4
+ prompt: string;
5
+ debug?: boolean;
6
+ onStepComplete: (step: {
7
+ command: string;
8
+ confirmed: boolean;
9
+ cancelled?: boolean;
10
+ hasBuiltin?: boolean;
11
+ builtins?: string[];
12
+ reasoning?: string;
13
+ needsContinue?: boolean;
14
+ nextStepHint?: string;
15
+ debugInfo?: any;
16
+ }) => void;
17
+ previousSteps?: ExecutedStep[];
18
+ currentStepNumber?: number;
19
+ }
20
+ /**
21
+ * MultiStepCommandGenerator 组件 - 多步骤命令生成
22
+ * 每次只生成一个命令,支持 continue 机制
23
+ */
24
+ export declare const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>;
25
+ export {};
@@ -0,0 +1,142 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import Spinner from 'ink-spinner';
4
+ import { generateMultiStepCommand } from '../multi-step.js';
5
+ import { detectBuiltin, formatBuiltins } from '../builtin-detector.js';
6
+ import { CommandBox } from './CommandBox.js';
7
+ import { ConfirmationPrompt } from './ConfirmationPrompt.js';
8
+ import { Duration } from './Duration.js';
9
+ import { theme } from '../ui/theme.js';
10
+ /**
11
+ * MultiStepCommandGenerator 组件 - 多步骤命令生成
12
+ * 每次只生成一个命令,支持 continue 机制
13
+ */
14
+ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], currentStepNumber = 1, onStepComplete, }) => {
15
+ const [state, setState] = useState({ type: 'thinking' });
16
+ const [thinkDuration, setThinkDuration] = useState(0);
17
+ const [debugInfo, setDebugInfo] = useState(null);
18
+ // 初始化:调用 Mastra 生成命令
19
+ useEffect(() => {
20
+ const thinkStart = Date.now();
21
+ generateMultiStepCommand(prompt, previousSteps, { debug })
22
+ .then((result) => {
23
+ const thinkEnd = Date.now();
24
+ setThinkDuration(thinkEnd - thinkStart);
25
+ // 保存调试信息
26
+ if (debug && result.debugInfo) {
27
+ setDebugInfo(result.debugInfo);
28
+ }
29
+ setState({
30
+ type: 'showing_command',
31
+ stepData: result.stepData,
32
+ });
33
+ // 检测 builtin
34
+ const { hasBuiltin, builtins } = detectBuiltin(result.stepData.command);
35
+ if (hasBuiltin) {
36
+ setTimeout(() => {
37
+ onStepComplete({
38
+ command: result.stepData.command,
39
+ confirmed: false,
40
+ hasBuiltin: true,
41
+ builtins,
42
+ reasoning: result.stepData.reasoning,
43
+ needsContinue: result.stepData.continue,
44
+ });
45
+ }, 100);
46
+ }
47
+ })
48
+ .catch((error) => {
49
+ setState({ type: 'error', error: error.message });
50
+ setTimeout(() => {
51
+ onStepComplete({
52
+ command: '',
53
+ confirmed: false,
54
+ cancelled: true,
55
+ });
56
+ }, 100);
57
+ });
58
+ }, [prompt, previousSteps, debug]);
59
+ // 处理确认
60
+ const handleConfirm = () => {
61
+ if (state.type === 'showing_command') {
62
+ onStepComplete({
63
+ command: state.stepData.command,
64
+ confirmed: true,
65
+ reasoning: state.stepData.reasoning,
66
+ needsContinue: state.stepData.continue,
67
+ nextStepHint: state.stepData.nextStepHint,
68
+ debugInfo: debugInfo,
69
+ });
70
+ }
71
+ };
72
+ // 处理取消
73
+ const handleCancel = () => {
74
+ if (state.type === 'showing_command') {
75
+ setState({ type: 'cancelled', command: state.stepData.command });
76
+ setTimeout(() => {
77
+ onStepComplete({
78
+ command: state.stepData.command,
79
+ confirmed: false,
80
+ cancelled: true,
81
+ });
82
+ }, 100);
83
+ }
84
+ };
85
+ return (React.createElement(Box, { flexDirection: "column" },
86
+ state.type === 'thinking' && (React.createElement(Box, null,
87
+ React.createElement(Text, { color: theme.info },
88
+ React.createElement(Spinner, { type: "dots" }),
89
+ ' ',
90
+ currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`))),
91
+ state.type !== 'thinking' && thinkDuration > 0 && (React.createElement(Box, null,
92
+ React.createElement(Text, { color: theme.success }, "\u2713 \u601D\u8003\u5B8C\u6210 "),
93
+ React.createElement(Duration, { ms: thinkDuration }))),
94
+ state.type === 'showing_command' && (React.createElement(React.Fragment, null,
95
+ debug && debugInfo && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
96
+ React.createElement(Text, { color: theme.accent }, "\u2501\u2501\u2501 \u8C03\u8BD5\u4FE1\u606F \u2501\u2501\u2501"),
97
+ React.createElement(Text, { color: theme.text.secondary }, "\u5B8C\u6574\u7CFB\u7EDF\u63D0\u793A\u8BCD:"),
98
+ React.createElement(Text, { color: theme.text.dim }, debugInfo.fullPrompt),
99
+ React.createElement(Box, { marginTop: 1 },
100
+ React.createElement(Text, { color: theme.text.secondary },
101
+ "\u7528\u6237 Prompt: ",
102
+ debugInfo.userPrompt)),
103
+ debugInfo.previousStepsCount > 0 && (React.createElement(Box, { marginTop: 1 },
104
+ React.createElement(Text, { color: theme.text.secondary },
105
+ "\u5DF2\u6267\u884C\u6B65\u9AA4\u6570: ",
106
+ debugInfo.previousStepsCount))),
107
+ React.createElement(Box, { marginTop: 1 },
108
+ React.createElement(Text, { color: theme.text.secondary }, "AI \u8FD4\u56DE\u7684 JSON:")),
109
+ React.createElement(Text, { color: theme.text.dim }, JSON.stringify(debugInfo.response, null, 2)),
110
+ React.createElement(Text, { color: theme.accent }, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"))),
111
+ state.stepData.continue === true && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
112
+ React.createElement(Text, { color: theme.text.secondary },
113
+ "\u6B65\u9AA4 ",
114
+ currentStepNumber,
115
+ "/?"),
116
+ state.stepData.reasoning && (React.createElement(Text, { color: theme.text.muted },
117
+ "\u539F\u56E0: ",
118
+ state.stepData.reasoning)),
119
+ state.stepData.nextStepHint && (React.createElement(Text, { color: theme.text.muted },
120
+ "\u4E0B\u4E00\u6B65: ",
121
+ state.stepData.nextStepHint)))),
122
+ React.createElement(CommandBox, { command: state.stepData.command }),
123
+ (() => {
124
+ const { hasBuiltin, builtins } = detectBuiltin(state.stepData.command);
125
+ if (hasBuiltin) {
126
+ return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
127
+ React.createElement(Text, { color: theme.error },
128
+ "\u26A0\uFE0F \u6B64\u547D\u4EE4\u5305\u542B shell \u5185\u7F6E\u547D\u4EE4\uFF08",
129
+ formatBuiltins(builtins),
130
+ "\uFF09\uFF0C\u65E0\u6CD5\u5728\u5B50\u8FDB\u7A0B\u4E2D\u751F\u6548"),
131
+ React.createElement(Text, { color: theme.warning }, "\uD83D\uDCA1 \u8BF7\u624B\u52A8\u590D\u5236\u5230\u7EC8\u7AEF\u6267\u884C")));
132
+ }
133
+ return null;
134
+ })(),
135
+ !detectBuiltin(state.stepData.command).hasBuiltin && (React.createElement(ConfirmationPrompt, { prompt: "\u6267\u884C\uFF1F", onConfirm: handleConfirm, onCancel: handleCancel })))),
136
+ state.type === 'cancelled' && (React.createElement(Box, { marginTop: 1 },
137
+ React.createElement(Text, { color: theme.text.secondary }, "\u5DF2\u53D6\u6D88\u6267\u884C"))),
138
+ state.type === 'error' && (React.createElement(Box, { marginTop: 1 },
139
+ React.createElement(Text, { color: theme.error },
140
+ "\u274C \u9519\u8BEF: ",
141
+ state.error)))));
142
+ };
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ interface TableRendererProps {
3
+ headers: string[];
4
+ rows: string[][];
5
+ terminalWidth: number;
6
+ }
7
+ /**
8
+ * 表格渲染组件
9
+ */
10
+ declare function TableRendererInternal({ headers, rows, terminalWidth }: TableRendererProps): React.JSX.Element;
11
+ export declare const TableRenderer: React.MemoExoticComponent<typeof TableRendererInternal>;
12
+ export {};
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import stringWidth from 'string-width';
4
+ import { theme } from '../ui/theme.js';
5
+ import { RenderInline } from './InlineRenderer.js';
6
+ /**
7
+ * 计算纯文本长度(去除 markdown 标记)
8
+ */
9
+ function getPlainTextLength(text) {
10
+ const cleanText = text
11
+ .replace(/\*\*(.*?)\*\*/g, '$1')
12
+ .replace(/\*(.*?)\*/g, '$1')
13
+ .replace(/_(.*?)_/g, '$1')
14
+ .replace(/~~(.*?)~~/g, '$1')
15
+ .replace(/`(.*?)`/g, '$1')
16
+ .replace(/<u>(.*?)<\/u>/g, '$1')
17
+ .replace(/\[(.*?)\]\(.*?\)/g, '$1');
18
+ return stringWidth(cleanText);
19
+ }
20
+ /**
21
+ * 计算每列的最佳宽度
22
+ */
23
+ function calculateColumnWidths(headers, rows, terminalWidth) {
24
+ const columnCount = headers.length;
25
+ const minWidths = headers.map((h, i) => {
26
+ const headerWidth = getPlainTextLength(h);
27
+ const maxRowWidth = Math.max(...rows.map(row => getPlainTextLength(row[i] || '')));
28
+ return Math.max(headerWidth, maxRowWidth, 3); // 最小宽度 3
29
+ });
30
+ const totalMinWidth = minWidths.reduce((a, b) => a + b, 0) + (columnCount + 1) * 3; // 加边框和间距
31
+ if (totalMinWidth <= terminalWidth) {
32
+ return minWidths;
33
+ }
34
+ // 如果超宽,平均分配
35
+ const availableWidth = terminalWidth - (columnCount + 1) * 3;
36
+ const avgWidth = Math.floor(availableWidth / columnCount);
37
+ return headers.map(() => Math.max(avgWidth, 5));
38
+ }
39
+ /**
40
+ * 表格渲染组件
41
+ */
42
+ function TableRendererInternal({ headers, rows, terminalWidth }) {
43
+ const columnWidths = calculateColumnWidths(headers, rows, terminalWidth);
44
+ const baseColor = theme.text.primary;
45
+ return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
46
+ React.createElement(Box, null,
47
+ React.createElement(Text, { color: theme.border }, "\u2502 "),
48
+ headers.map((header, i) => (React.createElement(React.Fragment, { key: i },
49
+ React.createElement(Box, { width: columnWidths[i] },
50
+ React.createElement(Text, { bold: true, color: theme.primary },
51
+ React.createElement(RenderInline, { text: header, defaultColor: theme.primary }))),
52
+ React.createElement(Text, { color: theme.border }, " \u2502 "))))),
53
+ React.createElement(Box, null,
54
+ React.createElement(Text, { color: theme.border }, "\u251C\u2500"),
55
+ columnWidths.map((width, i) => (React.createElement(React.Fragment, { key: i },
56
+ React.createElement(Text, { color: theme.border }, '─'.repeat(width)),
57
+ React.createElement(Text, { color: theme.border }, "\u2500\u253C\u2500"))))),
58
+ rows.map((row, rowIndex) => (React.createElement(Box, { key: rowIndex },
59
+ React.createElement(Text, { color: theme.border }, "\u2502 "),
60
+ row.map((cell, cellIndex) => (React.createElement(React.Fragment, { key: cellIndex },
61
+ React.createElement(Box, { width: columnWidths[cellIndex] },
62
+ React.createElement(Text, { color: baseColor },
63
+ React.createElement(RenderInline, { text: cell || '', defaultColor: baseColor }))),
64
+ React.createElement(Text, { color: theme.border }, " \u2502 ")))))))));
65
+ }
66
+ export const TableRenderer = React.memo(TableRendererInternal);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 读取配置
3
+ */
4
+ export function getConfig(): any;
5
+ /**
6
+ * 保存配置
7
+ */
8
+ export function saveConfig(config: any): void;
9
+ /**
10
+ * 设置单个配置项
11
+ */
12
+ export function setConfigValue(key: any, value: any): any;
13
+ /**
14
+ * 检查配置是否有效
15
+ */
16
+ export function isConfigValid(): any;
17
+ /**
18
+ * 隐藏 API Key 中间部分
19
+ */
20
+ export function maskApiKey(apiKey: any): any;
21
+ /**
22
+ * 显示当前配置
23
+ */
24
+ export function displayConfig(): void;
25
+ /**
26
+ * 交互式配置向导
27
+ */
28
+ export function runConfigWizard(): Promise<void>;
29
+ export const CONFIG_DIR: string;