closer-code 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 (100) hide show
  1. package/.env.example +83 -0
  2. package/API_GUIDE.md +1411 -0
  3. package/AUTO_MKDIR_IMPROVEMENT.md +354 -0
  4. package/CLAUDE.md +55 -0
  5. package/CTRL_C_EXPERIMENT.md +90 -0
  6. package/PROJECT_CLEANUP_SUMMARY.md +121 -0
  7. package/README.md +686 -0
  8. package/cloco.md +51 -0
  9. package/config.example.json +116 -0
  10. package/dist/bash-runner.js +128 -0
  11. package/dist/batch-cli.js +20736 -0
  12. package/dist/closer-cli.js +21190 -0
  13. package/dist/index.js +31228 -0
  14. package/docs/EXPORT_COMMAND.md +152 -0
  15. package/docs/FILE_NAMING_IMPROVEMENT.md +168 -0
  16. package/docs/GLOBAL_CONFIG.md +128 -0
  17. package/docs/LONG_MESSAGE_DISPLAY_FIX.md +202 -0
  18. package/docs/PROJECT_HISTORY_ISOLATION.md +315 -0
  19. package/docs/QUICK_START_HISTORY.md +207 -0
  20. package/docs/TASK_PROGRESS_FEATURE.md +190 -0
  21. package/docs/THINKING_CONTENT_RESEARCH.md +267 -0
  22. package/docs/THINKING_FEATURE.md +187 -0
  23. package/docs/THINKING_IMPROVEMENT_COMPARISON.md +193 -0
  24. package/docs/THINKING_OPTIMIZATION_SUMMARY.md +242 -0
  25. package/docs/UI_IMPROVEMENTS_2025-01-18.md +256 -0
  26. package/docs/WHY_THINKING_SHORT.md +201 -0
  27. package/package.json +49 -0
  28. package/scenarios/README.md +234 -0
  29. package/scenarios/run-all-scenarios.js +342 -0
  30. package/scenarios/scenario1-batch-converter.js +247 -0
  31. package/scenarios/scenario2-code-analyzer.js +375 -0
  32. package/scenarios/scenario3-doc-generator.js +371 -0
  33. package/scenarios/scenario4-log-analyzer.js +496 -0
  34. package/scenarios/scenario5-tdd-helper.js +681 -0
  35. package/src/ai-client-legacy.js +171 -0
  36. package/src/ai-client.js +221 -0
  37. package/src/bash-runner.js +148 -0
  38. package/src/batch-cli.js +327 -0
  39. package/src/cli.jsx +166 -0
  40. package/src/closer-cli.jsx +1103 -0
  41. package/src/closer-cli.jsx.backup +948 -0
  42. package/src/commands/batch.js +62 -0
  43. package/src/commands/chat.js +10 -0
  44. package/src/commands/config.js +154 -0
  45. package/src/commands/help.js +76 -0
  46. package/src/commands/history.js +192 -0
  47. package/src/commands/setup.js +17 -0
  48. package/src/commands/upgrade.js +101 -0
  49. package/src/commands/workflow-tests.js +125 -0
  50. package/src/config.js +343 -0
  51. package/src/conversation.js +962 -0
  52. package/src/git-helper.js +349 -0
  53. package/src/index.js +88 -0
  54. package/src/logger.js +347 -0
  55. package/src/plan.js +193 -0
  56. package/src/planner.js +397 -0
  57. package/src/search.js +195 -0
  58. package/src/setup.js +147 -0
  59. package/src/shortcuts.js +269 -0
  60. package/src/snippets.js +430 -0
  61. package/src/test-modules.js +118 -0
  62. package/src/tools.js +398 -0
  63. package/src/utils/cli.js +124 -0
  64. package/src/utils/validator.js +184 -0
  65. package/src/utils/version.js +33 -0
  66. package/src/utils/workflow-test.js +271 -0
  67. package/src/utils/workflow.js +268 -0
  68. package/test/demo-file-naming.js +92 -0
  69. package/test/demo-thinking.js +124 -0
  70. package/test/final-verification-report.md +303 -0
  71. package/test/research-thinking.js +130 -0
  72. package/test/test-auto-mkdir.js +123 -0
  73. package/test/test-e2e-empty-dir.md +108 -0
  74. package/test/test-export-logic.js +119 -0
  75. package/test/test-global-cloco.js +126 -0
  76. package/test/test-history-isolation.js +291 -0
  77. package/test/test-improved-thinking.js +43 -0
  78. package/test/test-long-message.js +65 -0
  79. package/test/test-plan-functionality.js +95 -0
  80. package/test/test-real-scenario.js +216 -0
  81. package/test/test-thinking-display.js +65 -0
  82. package/test/ui-verification-test.js +203 -0
  83. package/test/verify-history-isolation.sh +71 -0
  84. package/test/verify-thinking.js +339 -0
  85. package/test/workflows/empty-dir-creation.md +51 -0
  86. package/test/workflows/inventor/ascii-teacup.js +199 -0
  87. package/test/workflows/inventor/ascii-teacup.mjs +199 -0
  88. package/test/workflows/inventor/ascii_apple.hs +84 -0
  89. package/test/workflows/inventor/ascii_apple.py +91 -0
  90. package/test/workflows/inventor/cloco.md +3 -0
  91. package/test/workflows/longtalk/cloco.md +19 -0
  92. package/test/workflows/longtalk/emoji_500.txt +63 -0
  93. package/test/workflows/longtalk/emoji_list.txt +20 -0
  94. package/test/workflows/programmer/adder.md +33 -0
  95. package/test/workflows/programmer/expect.md +2 -0
  96. package/test/workflows/programmer/prompt.md +3 -0
  97. package/test/workflows/test-empty-dir-creation.js +113 -0
  98. package/test-ctrl-c.jsx +126 -0
  99. package/test-manual-file-creation.js +151 -0
  100. package/winfix.md +3 -0
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Closer Code - Batch Mode (非交互模式)
4
+ *
5
+ * 用于批处理场景,直接输出结果而不启动交互式 UI
6
+ *
7
+ * 使用方式:
8
+ * closer-batch "你的问题"
9
+ * echo "你的问题" | closer-batch
10
+ * closer-batch --file prompt.txt
11
+ * closer-batch --json "你的问题"
12
+ */
13
+
14
+ import { createConversation } from './conversation.js';
15
+ import { getConfig } from './config.js';
16
+ import {
17
+ initLogger,
18
+ closeLogger,
19
+ logSessionSummary
20
+ } from './logger.js';
21
+ import fs from 'fs/promises';
22
+ import readline from 'readline';
23
+
24
+ /**
25
+ * 解析命令行参数
26
+ */
27
+ function parseArgs() {
28
+ const args = process.argv.slice(2);
29
+ const options = {
30
+ prompt: null,
31
+ file: null,
32
+ output: 'text', // text, json, verbose
33
+ debug: false,
34
+ help: false
35
+ };
36
+
37
+ for (let i = 0; i < args.length; i++) {
38
+ const arg = args[i];
39
+
40
+ if (arg === '--help' || arg === '-h') {
41
+ options.help = true;
42
+ } else if (arg === '--file' || arg === '-f') {
43
+ options.file = args[++i];
44
+ } else if (arg === '--json' || arg === '-j') {
45
+ options.output = 'json';
46
+ } else if (arg === '--verbose' || arg === '-v') {
47
+ options.output = 'verbose';
48
+ } else if (arg === '--debug' || arg === '-d') {
49
+ options.debug = true;
50
+ } else if (!arg.startsWith('-')) {
51
+ options.prompt = arg;
52
+ }
53
+ }
54
+
55
+ return options;
56
+ }
57
+
58
+ /**
59
+ * 显示帮助信息
60
+ */
61
+ function showHelp() {
62
+ console.log(`
63
+ Closer Code - Batch Mode (非交互模式)
64
+
65
+ 使用方式:
66
+ closer-batch [选项] "你的问题"
67
+ echo "你的问题" | closer-batch
68
+ closer-batch --file prompt.txt
69
+
70
+ 选项:
71
+ -f, --file <文件> 从文件读取提示词
72
+ -j, --json 以 JSON 格式输出
73
+ -v, --verbose 详细输出(包含工具调用)
74
+ -d, --debug 启用调试日志
75
+ -h, --help 显示帮助信息
76
+
77
+ 环境变量:
78
+ CLOSER_DEBUG_LOG=1 启用调试日志
79
+ CLOSER_AI_PROVIDER AI 提供商 (anthropic, openai, ollama)
80
+ CLOSER_ANTHROPIC_API_KEY Anthropic API Key
81
+
82
+ 输出格式:
83
+ text 纯文本输出(默认)
84
+ json JSON 格式,包含完整响应信息
85
+ verbose 详细输出,包含工具调用详情
86
+
87
+ 退出码:
88
+ 0 成功
89
+ 1 错误
90
+ 2 参数错误
91
+
92
+ 示例:
93
+ closer-batch "列出当前目录的文件"
94
+ closer-batch --json "分析 package.json"
95
+ cat prompt.txt | closer-batch
96
+ closer-batch --file prompt.txt --verbose
97
+ `);
98
+ }
99
+
100
+ /**
101
+ * 从标准输入读取提示词
102
+ */
103
+ async function readStdin() {
104
+ return new Promise((resolve, reject) => {
105
+ if (process.stdin.isTTY) {
106
+ resolve('');
107
+ return;
108
+ }
109
+
110
+ const rl = readline.createInterface({
111
+ input: process.stdin,
112
+ terminal: false
113
+ });
114
+
115
+ let content = '';
116
+ rl.on('line', (line) => {
117
+ content += line + '\n';
118
+ });
119
+ rl.on('close', () => {
120
+ resolve(content.trim());
121
+ });
122
+ rl.on('error', reject);
123
+ });
124
+ }
125
+
126
+ /**
127
+ * 格式化输出
128
+ */
129
+ class OutputFormatter {
130
+ constructor(mode) {
131
+ this.mode = mode;
132
+ this.toolCalls = [];
133
+ this.startTime = Date.now();
134
+ }
135
+
136
+ /**
137
+ * 输出进度信息
138
+ */
139
+ progress(message) {
140
+ if (this.mode === 'verbose') {
141
+ console.error(`[INFO] ${message}`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 记录工具调用
147
+ */
148
+ recordToolCall(toolName, input, result) {
149
+ this.toolCalls.push({
150
+ tool: toolName,
151
+ input,
152
+ success: result.success,
153
+ result: result.success ? result.data : result.error,
154
+ timestamp: new Date().toISOString()
155
+ });
156
+
157
+ if (this.mode === 'verbose') {
158
+ console.error(`[TOOL] ${toolName}`);
159
+ if (result.success) {
160
+ console.error(` ✓ Success`);
161
+ } else {
162
+ console.error(` ✗ Failed: ${result.error}`);
163
+ }
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 格式化最终输出
169
+ */
170
+ format(content) {
171
+ const duration = Date.now() - this.startTime;
172
+
173
+ if (this.mode === 'json') {
174
+ return JSON.stringify({
175
+ success: true,
176
+ content,
177
+ toolCalls: this.toolCalls,
178
+ metadata: {
179
+ duration,
180
+ toolCount: this.toolCalls.length,
181
+ timestamp: new Date().toISOString()
182
+ }
183
+ }, null, 2);
184
+ }
185
+
186
+ if (this.mode === 'verbose') {
187
+ return `
188
+ ${content}
189
+
190
+ ---
191
+ 工具调用: ${this.toolCalls.length} 次
192
+ 耗时: ${duration}ms
193
+ `;
194
+ }
195
+
196
+ // text 模式:只输出内容
197
+ return content;
198
+ }
199
+
200
+ /**
201
+ * 格式化错误
202
+ */
203
+ error(message) {
204
+ if (this.mode === 'json') {
205
+ return JSON.stringify({
206
+ success: false,
207
+ error: message,
208
+ timestamp: new Date().toISOString()
209
+ }, null, 2);
210
+ }
211
+
212
+ return `Error: ${message}`;
213
+ }
214
+ }
215
+
216
+ /**
217
+ * 批处理模式主函数
218
+ */
219
+ async function runBatch() {
220
+ const options = parseArgs();
221
+
222
+ // 显示帮助
223
+ if (options.help) {
224
+ showHelp();
225
+ process.exit(0);
226
+ }
227
+
228
+ // 启用调试日志
229
+ if (options.debug) {
230
+ process.env.CLOSER_DEBUG_LOG = '1';
231
+ }
232
+
233
+ // 初始化输出格式化器
234
+ const formatter = new OutputFormatter(options.output);
235
+
236
+ try {
237
+ // 获取提示词
238
+ let prompt = options.prompt;
239
+
240
+ if (options.file) {
241
+ formatter.progress(`从文件读取: ${options.file}`);
242
+ prompt = await fs.readFile(options.file, 'utf-8');
243
+ prompt = prompt.trim();
244
+ } else if (!prompt) {
245
+ formatter.progress('从标准输入读取...');
246
+ prompt = await readStdin();
247
+ }
248
+
249
+ if (!prompt) {
250
+ console.error(formatter.error('未提供提示词。使用 --help 查看使用说明。'));
251
+ process.exit(2);
252
+ }
253
+
254
+ formatter.progress('初始化配置...');
255
+ const config = getConfig();
256
+
257
+ formatter.progress('创建对话会话...');
258
+ const conversation = await createConversation(config);
259
+
260
+ formatter.progress('发送消息到 AI...');
261
+ const response = await conversation.sendMessage(
262
+ prompt,
263
+ (progress) => {
264
+ if (progress.type === 'tool_start') {
265
+ formatter.progress(`执行工具: ${progress.tool}`);
266
+ } else if (progress.type === 'tool_complete') {
267
+ formatter.recordToolCall(
268
+ progress.tool,
269
+ null, // input 已经在 tool_start 中记录
270
+ progress.result
271
+ );
272
+ } else if (progress.type === 'token') {
273
+ // 流式输出 token(仅 text 模式)
274
+ if (options.output === 'text') {
275
+ process.stdout.write(progress.content);
276
+ }
277
+ }
278
+ }
279
+ );
280
+
281
+ // 输出最终结果(非 text 模式或没有流式输出时)
282
+ if (options.output !== 'text') {
283
+ console.log(formatter.format(response.content));
284
+ } else if (!response.content) {
285
+ // 如果 content 为空,至少输出一个换行
286
+ console.log();
287
+ }
288
+
289
+ formatter.progress('完成!');
290
+
291
+ // 记录会话摘要到日志
292
+ await logSessionSummary(conversation);
293
+
294
+ // 关闭日志
295
+ await closeLogger();
296
+
297
+ process.exit(0);
298
+
299
+ } catch (error) {
300
+ console.error(formatter.error(error.message));
301
+ console.error('');
302
+ console.error('详细错误信息:');
303
+ console.error(error);
304
+
305
+ // 关闭日志
306
+ await closeLogger();
307
+
308
+ process.exit(1);
309
+ }
310
+ }
311
+
312
+ // 导出运行函数,供 cloco 命令使用
313
+ export { runBatch };
314
+
315
+ // 只在直接运行时执行
316
+ // 检查当前模块是否是主入口模块
317
+ const modulePath = new URL(import.meta.url).pathname;
318
+ const argvPath = process.argv[1];
319
+ // 在 Windows 和 Unix 系统上都能正常工作的路径比较
320
+ const isMainModule = argvPath === modulePath ||
321
+ argvPath === modulePath.replace(/^\//, '') ||
322
+ argvPath.endsWith('batch-cli.js') ||
323
+ argvPath.endsWith('batch-cli');
324
+
325
+ if (isMainModule) {
326
+ runBatch();
327
+ }
package/src/cli.jsx ADDED
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import React, { useState, useCallback } from 'react';
3
+ import { render, Box, Text } from 'ink';
4
+ import TextInput from 'ink-text-input';
5
+ import { executeBashCommand } from './bash-runner.js';
6
+
7
+ // 历史记录管理
8
+ let commandHistory = [];
9
+ let resultHistory = [];
10
+
11
+ // 命令输入组件
12
+ function CommandInput({ onSubmit }) {
13
+ const [value, setValue] = useState('');
14
+
15
+ const handleSubmit = () => {
16
+ if (value.trim()) {
17
+ onSubmit(value);
18
+ setValue('');
19
+ }
20
+ };
21
+
22
+ return (
23
+ <Box borderStyle="double" borderColor="cyan" paddingX={1} marginBottom={1}>
24
+ <Box marginRight={1}>
25
+ <Text bold color="cyan">$</Text>
26
+ </Box>
27
+ <TextInput
28
+ value={value}
29
+ onChange={setValue}
30
+ onSubmit={handleSubmit}
31
+ placeholder="输入 bash 命令..."
32
+ />
33
+ </Box>
34
+ );
35
+ }
36
+
37
+ // 输出区域组件
38
+ function OutputArea({ label, content, color, borderColor }) {
39
+ return (
40
+ <Box
41
+ borderStyle="round"
42
+ borderColor={borderColor}
43
+ paddingX={1}
44
+ marginBottom={1}
45
+ flexDirection="column"
46
+ width="100%"
47
+ >
48
+ <Text bold color={color}>
49
+ {label}
50
+ </Text>
51
+ <Box flexGrow={1}>
52
+ {content || <Text dim>(空)</Text>}
53
+ </Box>
54
+ </Box>
55
+ );
56
+ }
57
+
58
+ // 信息区域组件
59
+ function InfoArea({ result }) {
60
+ if (!result) {
61
+ return (
62
+ <OutputArea
63
+ label="[INFO] 执行信息"
64
+ content={<Text>等待命令执行...</Text>}
65
+ color="yellow"
66
+ borderColor="yellow"
67
+ />
68
+ );
69
+ }
70
+
71
+ const status = result.success ? '✓ 成功' : '✗ 失败';
72
+ const statusColor = result.success ? 'green' : 'red';
73
+ const time = result.timestamp.split('T')[1].split('.')[0];
74
+
75
+ const infoText = [
76
+ <Text key="line1">状态: <Text color={statusColor} bold>{status}</Text></Text>,
77
+ <Text key="line2">退出码: <Text bold>{result.exitCode ?? 'N/A'}</Text></Text>,
78
+ <Text key="line3">时间: {time}</Text>,
79
+ <Text key="line4" dim>命令: {result.command.slice(0, 60)}{result.command.length > 60 ? '...' : ''}</Text>,
80
+ result.signal && (
81
+ <Text key="line5" color="red">信号: {result.signal}</Text>
82
+ ),
83
+ result.error && (
84
+ <Text key="line6" color="red">错误: {result.error}</Text>
85
+ )
86
+ ].filter(Boolean);
87
+
88
+ return (
89
+ <OutputArea
90
+ label="[INFO] 执行信息"
91
+ content={<Box flexDirection="column">{infoText}</Box>}
92
+ color="yellow"
93
+ borderColor="yellow"
94
+ />
95
+ );
96
+ }
97
+
98
+ // 主应用组件
99
+ function App() {
100
+ const [lastResult, setLastResult] = useState(null);
101
+ const [isExecuting, setIsExecuting] = useState(false);
102
+
103
+ const handleCommand = useCallback(async (command) => {
104
+ setIsExecuting(true);
105
+ commandHistory.push(command);
106
+
107
+ try {
108
+ const result = await executeBashCommand(command, {
109
+ timeout: 30000
110
+ });
111
+ resultHistory.push(result);
112
+ setLastResult(result);
113
+ } catch (errorResult) {
114
+ resultHistory.push(errorResult);
115
+ setLastResult(errorResult);
116
+ } finally {
117
+ setIsExecuting(false);
118
+ }
119
+ }, []);
120
+
121
+ return (
122
+ <Box flexDirection="column" padding={1}>
123
+ <Box
124
+ borderStyle="bold"
125
+ borderColor="magenta"
126
+ paddingX={1}
127
+ marginBottom={1}
128
+ >
129
+ <Box flexDirection="column">
130
+ <Text bold color="magenta">Bash 调用工具</Text>
131
+ <Text dim>按 Ctrl+C 退出</Text>
132
+ </Box>
133
+ </Box>
134
+
135
+ <InfoArea result={lastResult} />
136
+
137
+ <OutputArea
138
+ label="[STDOUT] 标准输出"
139
+ content={<Text>{lastResult?.stdout || ''}</Text>}
140
+ color="green"
141
+ borderColor="green"
142
+ />
143
+
144
+ <OutputArea
145
+ label="[STDERR] 错误输出"
146
+ content={<Text>{lastResult?.stderr || ''}</Text>}
147
+ color="red"
148
+ borderColor="red"
149
+ />
150
+
151
+ {isExecuting && (
152
+ <Text color="cyan" dim>⏳ 执行中...</Text>
153
+ )}
154
+
155
+ <CommandInput onSubmit={handleCommand} />
156
+ </Box>
157
+ );
158
+ }
159
+
160
+ // 启动应用
161
+ render(<App />);
162
+
163
+ // 优雅退出
164
+ process.on('SIGINT', () => {
165
+ process.exit(0);
166
+ });