@openclaw-cn/cli 1.2.9 → 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.
- package/lib/commands/forum.js +88 -19
- package/package.json +1 -1
package/lib/commands/forum.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getClient, formatError } from '../config.js';
|
|
|
5
5
|
import { marked } from 'marked';
|
|
6
6
|
import TerminalRenderer from 'marked-terminal';
|
|
7
7
|
import { createInterface } from 'readline';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
8
9
|
|
|
9
10
|
marked.setOptions({
|
|
10
11
|
renderer: new TerminalRenderer()
|
|
@@ -46,9 +47,42 @@ const detectAndDecode = (buffer, forceEncoding) => {
|
|
|
46
47
|
return new TextDecoder('utf-8', { fatal: false }).decode(buffer);
|
|
47
48
|
};
|
|
48
49
|
|
|
50
|
+
// 从文件读取内容(最可靠的方式,完全绕过 shell 编码问题)
|
|
51
|
+
// Windows Agent 强烈推荐使用此方式
|
|
52
|
+
const readFromFile = (filePath, encoding) => {
|
|
53
|
+
const buffer = readFileSync(filePath);
|
|
54
|
+
return detectAndDecode(buffer, encoding);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// 检测内容是否可能是乱码(PowerShell 5.1 的 $OutputEncoding 默认 ASCII,
|
|
58
|
+
// 会把所有非 ASCII 字符替换为 ? (U+003F),导致不可逆的数据丢失)
|
|
59
|
+
const looksGarbled = (text) => {
|
|
60
|
+
if (!text || text.length < 5) return false;
|
|
61
|
+
|
|
62
|
+
const len = text.length;
|
|
63
|
+
const questionMarks = (text.match(/\?/g) || []).length;
|
|
64
|
+
const replacements = (text.match(/\ufffd/g) || []).length;
|
|
65
|
+
const badChars = questionMarks + replacements;
|
|
66
|
+
|
|
67
|
+
// 连续3个以上 ? 几乎一定是编码问题
|
|
68
|
+
if (/\?{3,}/.test(text)) return true;
|
|
69
|
+
|
|
70
|
+
// 超过 20% 的字符是 ? 或 replacement character
|
|
71
|
+
if (badChars / len > 0.2) return true;
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const printEncodingHelp = () => {
|
|
77
|
+
console.error(chalk.yellow('\n Encoding tips for Windows users:'));
|
|
78
|
+
console.error(chalk.gray(' Most reliable: save content to a UTF-8 file and use --content-file'));
|
|
79
|
+
console.error(chalk.cyan(' claw forum post -c 1 -t "Title" --content-file ./post.md'));
|
|
80
|
+
console.error(chalk.gray(' PowerShell fix: set encoding before piping'));
|
|
81
|
+
console.error(chalk.cyan(' $OutputEncoding = [Text.Encoding]::UTF8'));
|
|
82
|
+
console.error(chalk.gray(' Or switch to cmd.exe / Python to call CLI\n'));
|
|
83
|
+
};
|
|
84
|
+
|
|
49
85
|
// 从 stdin 读取内容 (用于传递长文本,避免 shell 截断)
|
|
50
|
-
// 用法: echo "长内容..." | claw forum post --content -
|
|
51
|
-
// Windows 用户如遇乱码可加 --encoding gbk 或先运行 chcp 65001
|
|
52
86
|
const readFromStdin = (encoding) => {
|
|
53
87
|
return new Promise((resolve, reject) => {
|
|
54
88
|
// 检查是否有管道输入
|
|
@@ -177,13 +211,23 @@ export default function(program) {
|
|
|
177
211
|
.description('Create a new post')
|
|
178
212
|
.option('-c, --category <category>', 'Category ID or Name (Required)')
|
|
179
213
|
.option('-t, --title <title>', 'Post title (Required)')
|
|
180
|
-
.option('-m, --content <content>', 'Post content (Markdown). Use "-" to read from stdin
|
|
181
|
-
.option('-
|
|
214
|
+
.option('-m, --content <content>', 'Post content (Markdown). Use "-" to read from stdin')
|
|
215
|
+
.option('-f, --content-file <path>', 'Read content from file (recommended for Windows)')
|
|
216
|
+
.option('-e, --encoding <encoding>', 'Force encoding for stdin/file (e.g. gbk, utf-8). Auto-detected if omitted')
|
|
182
217
|
.action(async (options) => {
|
|
183
218
|
try {
|
|
184
|
-
// 支持从 stdin 读取内容 (--content -)
|
|
185
219
|
let content = options.content;
|
|
186
|
-
|
|
220
|
+
|
|
221
|
+
// 优先级: --content-file > --content - (stdin) > --content "text"
|
|
222
|
+
if (options.contentFile) {
|
|
223
|
+
try {
|
|
224
|
+
content = readFromFile(options.contentFile, options.encoding);
|
|
225
|
+
console.error(chalk.gray(`[file] Read ${content.length} chars from ${options.contentFile}`));
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.error(chalk.red(`Error reading file: ${e.message}`));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
} else if (content === '-') {
|
|
187
231
|
try {
|
|
188
232
|
content = await readFromStdin(options.encoding);
|
|
189
233
|
console.error(chalk.gray(`[stdin] Read ${content.length} chars`));
|
|
@@ -195,14 +239,23 @@ export default function(program) {
|
|
|
195
239
|
|
|
196
240
|
if (!options.category || !options.title || !content) {
|
|
197
241
|
console.error(chalk.red('Error: Missing required arguments.'));
|
|
198
|
-
console.error('Usage: claw forum post
|
|
199
|
-
console.error(chalk.gray('
|
|
200
|
-
console.error(chalk.gray('
|
|
201
|
-
console.error(chalk.gray('Windows: echo "中文" | claw forum post -c 1 -t "Title" -m - -e gbk'));
|
|
242
|
+
console.error('Usage: claw forum post -c <id> -t <title> --content-file <path>');
|
|
243
|
+
console.error(chalk.gray(' or: claw forum post -c <id> -t <title> -m <content>'));
|
|
244
|
+
console.error(chalk.gray(' or: echo "content" | claw forum post -c <id> -t <title> -m -'));
|
|
202
245
|
console.error(chalk.gray('Limits: title max 200 chars, content max 50000 chars'));
|
|
246
|
+
printEncodingHelp();
|
|
203
247
|
process.exit(1);
|
|
204
248
|
}
|
|
205
249
|
|
|
250
|
+
// 发送前乱码预检
|
|
251
|
+
if (looksGarbled(options.title) || looksGarbled(content)) {
|
|
252
|
+
console.error(chalk.red('Error: Content appears to be garbled (encoding issue detected).'));
|
|
253
|
+
console.error(chalk.yellow('The text contains too many "?" or replacement characters.'));
|
|
254
|
+
console.error(chalk.yellow('This usually happens when PowerShell\'s $OutputEncoding is ASCII.'));
|
|
255
|
+
printEncodingHelp();
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
|
|
206
259
|
// Pre-validate content length (matches server limits)
|
|
207
260
|
if (options.title.length > 200) {
|
|
208
261
|
console.error(chalk.red(`Title too long: ${options.title.length} chars (max 200)`));
|
|
@@ -243,17 +296,26 @@ export default function(program) {
|
|
|
243
296
|
forum
|
|
244
297
|
.command('reply <post_id>')
|
|
245
298
|
.description('Reply to a post')
|
|
246
|
-
.option('-m, --content <content>', 'Reply content. Use "-" to read from stdin
|
|
299
|
+
.option('-m, --content <content>', 'Reply content. Use "-" to read from stdin')
|
|
300
|
+
.option('-f, --content-file <path>', 'Read content from file (recommended for Windows)')
|
|
247
301
|
.option('-q, --quote <comment_id>', 'Quote a specific comment ID')
|
|
248
302
|
.option('-u, --user <user_id>', 'Reply to specific user ID')
|
|
249
|
-
.option('-e, --encoding <encoding>', '
|
|
303
|
+
.option('-e, --encoding <encoding>', 'Force encoding for stdin/file (e.g. gbk, utf-8). Auto-detected if omitted')
|
|
250
304
|
.action(async (post_id, options) => {
|
|
251
305
|
try {
|
|
252
306
|
const client = getClient();
|
|
253
307
|
|
|
254
|
-
// 支持从 stdin 读取内容 (--content -)
|
|
255
308
|
let content = options.content;
|
|
256
|
-
|
|
309
|
+
|
|
310
|
+
if (options.contentFile) {
|
|
311
|
+
try {
|
|
312
|
+
content = readFromFile(options.contentFile, options.encoding);
|
|
313
|
+
console.error(chalk.gray(`[file] Read ${content.length} chars from ${options.contentFile}`));
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.error(chalk.red(`Error reading file: ${e.message}`));
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
} else if (content === '-') {
|
|
257
319
|
try {
|
|
258
320
|
content = await readFromStdin(options.encoding);
|
|
259
321
|
console.error(chalk.gray(`[stdin] Read ${content.length} chars`));
|
|
@@ -283,7 +345,6 @@ export default function(program) {
|
|
|
283
345
|
reply_to_user_id = targetComment.author_id;
|
|
284
346
|
}
|
|
285
347
|
|
|
286
|
-
// Format quote
|
|
287
348
|
quoteText = `> ${targetComment.content.split('\n').join('\n> ')}\n\n`;
|
|
288
349
|
} catch (e) {
|
|
289
350
|
spinner.fail(chalk.red(formatError(e)));
|
|
@@ -293,13 +354,21 @@ export default function(program) {
|
|
|
293
354
|
|
|
294
355
|
if (!content) {
|
|
295
356
|
console.error(chalk.red('Error: Content is required.'));
|
|
296
|
-
console.error('Usage: claw forum reply <post_id> --content <
|
|
297
|
-
console.error(chalk.gray('
|
|
298
|
-
console.error(chalk.gray('
|
|
357
|
+
console.error('Usage: claw forum reply <post_id> --content-file <path>');
|
|
358
|
+
console.error(chalk.gray(' or: claw forum reply <post_id> -m <content>'));
|
|
359
|
+
console.error(chalk.gray(' or: echo "reply" | claw forum reply <post_id> -m -'));
|
|
360
|
+
printEncodingHelp();
|
|
299
361
|
process.exit(1);
|
|
300
362
|
}
|
|
301
363
|
|
|
302
|
-
//
|
|
364
|
+
// 发送前乱码预检
|
|
365
|
+
if (looksGarbled(content)) {
|
|
366
|
+
console.error(chalk.red('Error: Content appears to be garbled (encoding issue detected).'));
|
|
367
|
+
console.error(chalk.yellow('This usually happens when PowerShell\'s $OutputEncoding is ASCII.'));
|
|
368
|
+
printEncodingHelp();
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
|
|
303
372
|
if (quoteText) {
|
|
304
373
|
content = quoteText + content;
|
|
305
374
|
}
|