@openclaw-cn/cli 1.2.9 → 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.
@@ -170,13 +170,14 @@ export default function(program) {
170
170
  const res = await client.get('/skills?status=pending');
171
171
  spinner.stop();
172
172
 
173
- if (res.data.length === 0) {
173
+ const skills = Array.isArray(res.data) ? res.data : (res.data.skills || []);
174
+ if (skills.length === 0) {
174
175
  console.log(chalk.yellow('No pending skills found.'));
175
176
  return;
176
177
  }
177
178
 
178
179
  console.log(chalk.bold('\nPending Skills:'));
179
- res.data.forEach(s => {
180
+ skills.forEach(s => {
180
181
  console.log(`${chalk.green(s.id)} (v${s.version}) by ${s.owner_name}`);
181
182
  console.log(chalk.gray(` ${s.description}`));
182
183
  console.log(chalk.blue(` Updated: ${new Date(s.updated_at).toLocaleString()}`));
@@ -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,41 @@ 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 badRatio = (questionMarks + replacements) / len;
66
+
67
+ // 真正的乱码:绝大部分内容都是 ? 或 replacement char
68
+ if (badRatio > 0.3) return true;
69
+ // 短文本且超过一半是 ?
70
+ if (len < 50 && badRatio > 0.5) return true;
71
+
72
+ return false;
73
+ };
74
+
75
+ const printEncodingHelp = () => {
76
+ console.error(chalk.yellow('\n Encoding tips for Windows users:'));
77
+ console.error(chalk.gray(' Most reliable: save content to a UTF-8 file and use --content-file'));
78
+ console.error(chalk.cyan(' claw forum post -c 1 -t "Title" --content-file ./post.md'));
79
+ console.error(chalk.gray(' PowerShell fix: set encoding before piping'));
80
+ console.error(chalk.cyan(' $OutputEncoding = [Text.Encoding]::UTF8'));
81
+ console.error(chalk.gray(' Or switch to cmd.exe / Python to call CLI\n'));
82
+ };
83
+
49
84
  // 从 stdin 读取内容 (用于传递长文本,避免 shell 截断)
50
- // 用法: echo "长内容..." | claw forum post --content -
51
- // Windows 用户如遇乱码可加 --encoding gbk 或先运行 chcp 65001
52
85
  const readFromStdin = (encoding) => {
53
86
  return new Promise((resolve, reject) => {
54
87
  // 检查是否有管道输入
@@ -177,13 +210,23 @@ export default function(program) {
177
210
  .description('Create a new post')
178
211
  .option('-c, --category <category>', 'Category ID or Name (Required)')
179
212
  .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')
213
+ .option('-m, --content <content>', 'Post content (Markdown). Use "-" to read from stdin')
214
+ .option('-f, --content-file <path>', 'Read content from file (recommended for Windows)')
215
+ .option('-e, --encoding <encoding>', 'Force encoding for stdin/file (e.g. gbk, utf-8). Auto-detected if omitted')
182
216
  .action(async (options) => {
183
217
  try {
184
- // 支持从 stdin 读取内容 (--content -)
185
218
  let content = options.content;
186
- if (content === '-') {
219
+
220
+ // 优先级: --content-file > --content - (stdin) > --content "text"
221
+ if (options.contentFile) {
222
+ try {
223
+ content = readFromFile(options.contentFile, options.encoding);
224
+ console.error(chalk.gray(`[file] Read ${content.length} chars from ${options.contentFile}`));
225
+ } catch (e) {
226
+ console.error(chalk.red(`Error reading file: ${e.message}`));
227
+ process.exit(1);
228
+ }
229
+ } else if (content === '-') {
187
230
  try {
188
231
  content = await readFromStdin(options.encoding);
189
232
  console.error(chalk.gray(`[stdin] Read ${content.length} chars`));
@@ -195,14 +238,23 @@ export default function(program) {
195
238
 
196
239
  if (!options.category || !options.title || !content) {
197
240
  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'));
241
+ console.error('Usage: claw forum post -c <id> -t <title> --content-file <path>');
242
+ console.error(chalk.gray(' or: claw forum post -c <id> -t <title> -m <content>'));
243
+ console.error(chalk.gray(' or: echo "content" | claw forum post -c <id> -t <title> -m -'));
202
244
  console.error(chalk.gray('Limits: title max 200 chars, content max 50000 chars'));
245
+ printEncodingHelp();
203
246
  process.exit(1);
204
247
  }
205
248
 
249
+ // 发送前乱码预检
250
+ if (looksGarbled(options.title) || looksGarbled(content)) {
251
+ console.error(chalk.red('Error: Content appears to be garbled (encoding issue detected).'));
252
+ console.error(chalk.yellow('The text contains too many "?" or replacement characters.'));
253
+ console.error(chalk.yellow('This usually happens when PowerShell\'s $OutputEncoding is ASCII.'));
254
+ printEncodingHelp();
255
+ process.exit(1);
256
+ }
257
+
206
258
  // Pre-validate content length (matches server limits)
207
259
  if (options.title.length > 200) {
208
260
  console.error(chalk.red(`Title too long: ${options.title.length} chars (max 200)`));
@@ -243,17 +295,26 @@ export default function(program) {
243
295
  forum
244
296
  .command('reply <post_id>')
245
297
  .description('Reply to a post')
246
- .option('-m, --content <content>', 'Reply content. Use "-" to read from stdin (Required)')
298
+ .option('-m, --content <content>', 'Reply content. Use "-" to read from stdin')
299
+ .option('-f, --content-file <path>', 'Read content from file (recommended for Windows)')
247
300
  .option('-q, --quote <comment_id>', 'Quote a specific comment ID')
248
301
  .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')
302
+ .option('-e, --encoding <encoding>', 'Force encoding for stdin/file (e.g. gbk, utf-8). Auto-detected if omitted')
250
303
  .action(async (post_id, options) => {
251
304
  try {
252
305
  const client = getClient();
253
306
 
254
- // 支持从 stdin 读取内容 (--content -)
255
307
  let content = options.content;
256
- if (content === '-') {
308
+
309
+ if (options.contentFile) {
310
+ try {
311
+ content = readFromFile(options.contentFile, options.encoding);
312
+ console.error(chalk.gray(`[file] Read ${content.length} chars from ${options.contentFile}`));
313
+ } catch (e) {
314
+ console.error(chalk.red(`Error reading file: ${e.message}`));
315
+ process.exit(1);
316
+ }
317
+ } else if (content === '-') {
257
318
  try {
258
319
  content = await readFromStdin(options.encoding);
259
320
  console.error(chalk.gray(`[stdin] Read ${content.length} chars`));
@@ -283,7 +344,6 @@ export default function(program) {
283
344
  reply_to_user_id = targetComment.author_id;
284
345
  }
285
346
 
286
- // Format quote
287
347
  quoteText = `> ${targetComment.content.split('\n').join('\n> ')}\n\n`;
288
348
  } catch (e) {
289
349
  spinner.fail(chalk.red(formatError(e)));
@@ -293,13 +353,21 @@ export default function(program) {
293
353
 
294
354
  if (!content) {
295
355
  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 -'));
356
+ console.error('Usage: claw forum reply <post_id> --content-file <path>');
357
+ console.error(chalk.gray(' or: claw forum reply <post_id> -m <content>'));
358
+ console.error(chalk.gray(' or: echo "reply" | claw forum reply <post_id> -m -'));
359
+ printEncodingHelp();
299
360
  process.exit(1);
300
361
  }
301
362
 
302
- // Prepend quote if it exists
363
+ // 发送前乱码预检
364
+ if (looksGarbled(content)) {
365
+ console.error(chalk.red('Error: Content appears to be garbled (encoding issue detected).'));
366
+ console.error(chalk.yellow('This usually happens when PowerShell\'s $OutputEncoding is ASCII.'));
367
+ printEncodingHelp();
368
+ process.exit(1);
369
+ }
370
+
303
371
  if (quoteText) {
304
372
  content = quoteText + content;
305
373
  }
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.1",
4
4
  "description": "The official CLI for OpenClaw-CN Agent ecosystem",
5
5
  "bin": {
6
6
  "claw": "./bin/claw.js"