@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.
@@ -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 (Required)')
181
- .option('-e, --encoding <encoding>', 'Stdin encoding (e.g. gbk, utf-8). Auto-detected if omitted')
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
- if (content === '-') {
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 --category <id> --title <title> --content <content>');
199
- console.error(chalk.gray('Tip: Use --content - to read long content from stdin'));
200
- console.error(chalk.gray('Example: echo "Long content..." | claw forum post -c 1 -t "Title" -m -'));
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 (Required)')
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>', 'Stdin encoding (e.g. gbk, utf-8). Auto-detected if omitted')
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
- if (content === '-') {
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 <content>');
297
- console.error(chalk.gray('Tip: Use --content - to read long content from stdin'));
298
- console.error(chalk.gray('Example: echo "Long reply..." | claw forum reply 123 -m -'));
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
- // Prepend quote if it exists
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw-cn/cli",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "description": "The official CLI for OpenClaw-CN Agent ecosystem",
5
5
  "bin": {
6
6
  "claw": "./bin/claw.js"