@yizhuan-cli/cli 0.1.13 → 0.1.14-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yizhuan-cli/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.14-beta.1",
4
4
  "description": "易撰命令行工具,用于通过 API Key 查询易撰真实数据。",
5
5
  "private": false,
6
6
  "type": "module",
@@ -45,6 +45,8 @@ yizhuan doctor
45
45
  | 作者查询 | `yizhuan execute --ability accounts --query "张三"` |
46
46
  | 作者指标筛选 | `yizhuan execute --ability author_query --params '{"platform_id":2,"min_follower":500000,"min_total_likes":100000}'` |
47
47
  | 作者全网搜索 | `yizhuan query --json '{"intent":"author_search","scope":"public","platform":"douyin","keyword":"张三"}'` |
48
+ | 微信公众号作者 | `yizhuan execute --ability accounts --params '{"mode":"public_search","platform":"wechat","keyword":"科技"}'` |
49
+ | 微信公众号作品 | `yizhuan execute --ability works --params '{"mode":"search","platform":"wechat","platform_id":9,"keyword":"人工智能"}'` |
48
50
  | 热点 | `yizhuan query --json '{"intent":"hot_topics","scope":"platform","platform":"douyin","timeRange":"today"}'` |
49
51
  | 我的关注 | `yizhuan execute --ability radar --params '{"mode":"following"}'` |
50
52
  | 竞品动态 | `yizhuan execute --ability radar --mode timeline --timeRange 7d` |
@@ -63,6 +65,7 @@ yizhuan doctor
63
65
  - 平台热点必须严格按 CLI 返回的 `data.items` / `rank` 顺序展示,该顺序与 Web 端平台榜单一致;`hot` 仅为展示指标,禁止按 `hot` 或格式化后的“万”值二次排序。
64
66
  - 使用 Markdown 表格展示热点时,必须将标题中的 `|` 转义为 `\|`,不得让标题内容错位到热度列。
65
67
  - 全网作者搜索使用 `intent=author_search`、`scope=public`,并提供 `platform` 和 `keyword`。
68
+ - 微信公众号暂不支持全网搜索作品。用户提出该请求时必须明确说明不支持,不得改用作品库检索并把 `empty` 解释为全网没有相关作品;微信公众号作者全网搜索仍可使用 `author_search`。
66
69
  - 查询指定作者最近作品时,直接使用 `intent=works_search`,同时提供 `platform`、`authorName` 和 `timeRange`;不得先用 `author_search` 的 `workCount` 判断该作者是否有作品。
67
70
  - `author_search` 返回的 `extAuthorId` 是平台外部作者 ID,不是易撰内部 `authorId`。需要按外部 ID 查询作品时只能传 `extAuthorId` / `ext_author_id`(兼容 `author_id`),必须同时提供 `platform`,由服务端完成内部 ID 转换;禁止把它直接作为内部 `authorId` 使用。
68
71
  - 公开作者结果中的 `workCount: 0` 可能是上游未返回作品数后的缺省值,不代表作者没有作品;是否有作品必须以 `works_search` 的结果为准。
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "yizhuan-cli",
4
- "version": "0.1.13",
5
- "compatibleCliVersion": "0.1.13",
6
- "source": "npm:@yizhuan-cli/cli@0.1.13",
4
+ "version": "0.1.14-beta.1",
5
+ "compatibleCliVersion": "0.1.14-beta.1",
6
+ "source": "npm:@yizhuan-cli/cli@0.1.14-beta.1",
7
7
  "digestAlgorithm": "sha256",
8
8
  "files": [
9
9
  {
10
10
  "path": "SKILL.md",
11
- "sha256": "4fcca6c6a4bc98ffabab267174bf369e617e58f2bd297b20132d29138fdcad19"
11
+ "sha256": "4c7a2c189e1b86577a8b3ff290d7cc1031a4cf3fb3e3e075560f4b672adf90dc"
12
12
  }
13
13
  ]
14
14
  }
package/src/index.js CHANGED
@@ -17,6 +17,7 @@ const ABILITIES = new Set([
17
17
  'membership',
18
18
  'favorites',
19
19
  'public_author_search',
20
+ 'public_work_search',
20
21
  'author_query',
21
22
  'statistics',
22
23
  'content_search',
@@ -24,8 +25,8 @@ const ABILITIES = new Set([
24
25
  'hot_topic_analysis',
25
26
  'work_query',
26
27
  'membership_info',
27
- 'favorites_query'
28
- ,'detection'
28
+ 'favorites_query',
29
+ 'detection'
29
30
  ])
30
31
 
31
32
  const PARAM_FLAG_KEYS = [
@@ -89,6 +90,7 @@ Abilities:
89
90
  membership 会员查询
90
91
  favorites 收藏查询
91
92
  public_author_search 作者全网搜索,兼容低层能力
93
+ public_work_search 作品全网搜索,兼容低层能力
92
94
  detection 一键检测(原创/风险)
93
95
 
94
96
  Config:
@@ -146,13 +148,31 @@ function readFlagValue(argv, startIndex, key) {
146
148
 
147
149
  if ((key === 'params' || key === 'json') && /[{\[]/.test(raw)) {
148
150
  let joined = raw
149
- while (!isBalancedJsonLike(joined) && index + 1 < argv.length && !String(argv[index + 1]).startsWith('--')) {
151
+ while (
152
+ !isBalancedJsonLike(joined) &&
153
+ index + 1 < argv.length &&
154
+ !String(argv[index + 1]).startsWith('--')
155
+ ) {
150
156
  index += 1
151
157
  joined += argv[index]
152
158
  }
153
159
  return { value: joined, nextIndex: index }
154
160
  }
155
161
 
162
+ // Accept comma-separated key=value fragments commonly pasted into PowerShell.
163
+ if (key === 'json' && /^[A-Za-z_][\w-]*\s*[:=]/.test(String(raw))) {
164
+ let joined = raw
165
+ while (
166
+ index + 1 < argv.length &&
167
+ !String(argv[index + 1]).startsWith('--') &&
168
+ /^[A-Za-z_][\w-]*\s*[:=]/.test(String(argv[index + 1]))
169
+ ) {
170
+ index += 1
171
+ joined += ` ${argv[index]}`
172
+ }
173
+ return { value: joined, nextIndex: index }
174
+ }
175
+
156
176
  return { value: raw, nextIndex: index }
157
177
  }
158
178
 
@@ -215,8 +235,11 @@ function stripWrappingQuotes(text) {
215
235
  return value
216
236
  }
217
237
 
218
- function coerceScalar(value) {
219
- const text = stripWrappingQuotes(value).trim()
238
+ const OPAQUE_ID_KEYS = new Set(['extAuthorId', 'ext_author_id', 'author_id'])
239
+
240
+ function coerceScalar(value, key) {
241
+ const text = stripWrappingQuotes(value).replace(/\\_/g, '_').trim()
242
+ if (OPAQUE_ID_KEYS.has(key)) return text
220
243
  if (text === 'true') return true
221
244
  if (text === 'false') return false
222
245
  if (text === 'null') return null
@@ -241,7 +264,10 @@ function parseLooseObject(text) {
241
264
  let body = String(text).trim()
242
265
  if (!body) return undefined
243
266
 
244
- if ((body.startsWith('{') && body.endsWith('}')) || (body.startsWith('[') && body.endsWith(']'))) {
267
+ if (
268
+ (body.startsWith('{') && body.endsWith('}')) ||
269
+ (body.startsWith('[') && body.endsWith(']'))
270
+ ) {
245
271
  body = body.slice(1, -1).trim()
246
272
  }
247
273
  if (!body) return {}
@@ -254,11 +280,9 @@ function parseLooseObject(text) {
254
280
 
255
281
  const result = {}
256
282
  for (const pair of pairs) {
257
- const matched = pair.match(
258
- /^(?:["']?)([A-Za-z_][\w-]*)(?:["']?)\s*[:=]\s*(.+)$/
259
- )
283
+ const matched = pair.match(/^(?:["']?)([A-Za-z_][\w-]*)(?:["']?)\s*[:=]\s*(.+)$/)
260
284
  if (!matched) return undefined
261
- result[matched[1]] = coerceScalar(matched[2])
285
+ result[matched[1]] = coerceScalar(matched[2], matched[1])
262
286
  }
263
287
  return result
264
288
  }
@@ -294,7 +318,7 @@ function assignFlagParams(params, args) {
294
318
  for (const key of PARAM_FLAG_KEYS) {
295
319
  if (args[key] == null || args[key] === true) continue
296
320
  if (params[key] != null) continue
297
- params[key] = coerceScalar(args[key])
321
+ params[key] = coerceScalar(args[key], key)
298
322
  }
299
323
  return params
300
324
  }
@@ -331,9 +355,7 @@ export function assertCommandArgs(args, command, allowedFlags, positionalCount =
331
355
  )
332
356
  }
333
357
 
334
- const unknownFlags = Object.keys(args).filter(
335
- (key) => key !== '_' && !allowedFlags.has(key)
336
- )
358
+ const unknownFlags = Object.keys(args).filter((key) => key !== '_' && !allowedFlags.has(key))
337
359
  if (unknownFlags.length > 0) {
338
360
  throw new Error(`${command} 存在未知参数:${unknownFlags.map((key) => `--${key}`).join(', ')}`)
339
361
  }
@@ -374,16 +396,30 @@ async function execute(args) {
374
396
 
375
397
  let result = payloadResult(JSON.parse(text))
376
398
  const requestParams = buildExecuteParams(args)
377
- if (ability === 'detection' && requestParams?.wait === true && result?.status === 'pending' && result?.data?.id) {
378
- const timeoutSeconds = Number(requestParams.timeoutSeconds) > 0 ? Number(requestParams.timeoutSeconds) : 120
399
+ if (
400
+ ability === 'detection' &&
401
+ requestParams?.wait === true &&
402
+ result?.status === 'pending' &&
403
+ result?.data?.id
404
+ ) {
405
+ const timeoutSeconds =
406
+ Number(requestParams.timeoutSeconds) > 0 ? Number(requestParams.timeoutSeconds) : 120
379
407
  const deadline = Date.now() + timeoutSeconds * 1000
380
408
  while (Date.now() < deadline && result.status === 'pending') {
381
- await new Promise((resolve) => setTimeout(resolve, Math.max(500, Number(result.data.pollAfterSeconds || 3) * 1000)))
409
+ await new Promise((resolve) =>
410
+ setTimeout(resolve, Math.max(500, Number(result.data.pollAfterSeconds || 3) * 1000))
411
+ )
382
412
  if (Date.now() >= deadline) break
383
413
  const poll = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
384
414
  method: 'POST',
385
- headers: { authorization: config.apiKey, 'content-type': 'application/json', 'x-device-type': 'pc' },
386
- body: JSON.stringify({ json: { ability: 'detection', params: { mode: 'get', id: result.data.id } } })
415
+ headers: {
416
+ authorization: config.apiKey,
417
+ 'content-type': 'application/json',
418
+ 'x-device-type': 'pc'
419
+ },
420
+ body: JSON.stringify({
421
+ json: { ability: 'detection', params: { mode: 'get', id: result.data.id } }
422
+ })
387
423
  })
388
424
  const pollText = await poll.text()
389
425
  if (!poll.ok) throw new Error(`请求失败 HTTP ${poll.status}:${pollText}`)
@@ -402,8 +438,13 @@ function renderQuery(payload, format) {
402
438
  if (payload.status === 'error') return `${payload.error.code}: ${payload.error.message}`
403
439
  const items = payload.data?.items
404
440
  if (!Array.isArray(items)) return JSON.stringify(payload, null, 2)
405
- if (format === 'markdown') return items.map((item, index) => `| ${index + 1} | ${item.title || item.name || item.nickname || ''} |`).join('\n')
406
- return items.map((item, index) => `${index + 1}\t${item.title || item.name || item.nickname || ''}`).join('\n')
441
+ if (format === 'markdown')
442
+ return items
443
+ .map((item, index) => `| ${index + 1} | ${item.title || item.name || item.nickname || ''} |`)
444
+ .join('\n')
445
+ return items
446
+ .map((item, index) => `${index + 1}\t${item.title || item.name || item.nickname || ''}`)
447
+ .join('\n')
407
448
  }
408
449
 
409
450
  async function query(args) {
@@ -417,12 +458,19 @@ async function query(args) {
417
458
  if (!request) throw new Error('query 必须提供 --json 请求对象')
418
459
  const config = readConfig()
419
460
  const response = await fetch(`${config.apiBaseUrl}/api/agent.query`, {
420
- method: 'POST', headers: { authorization: config.apiKey, 'content-type': 'application/json', 'x-device-type': 'pc' },
461
+ method: 'POST',
462
+ headers: {
463
+ authorization: config.apiKey,
464
+ 'content-type': 'application/json',
465
+ 'x-device-type': 'pc'
466
+ },
421
467
  body: JSON.stringify({ json: request })
422
468
  })
423
469
  const text = await response.text()
424
470
  if (!response.ok) throw new Error(`请求失败 HTTP ${response.status}:${text}`)
425
- console.log(renderQuery(JSON.parse(text).result?.data?.json ?? JSON.parse(text), args.format || 'json'))
471
+ console.log(
472
+ renderQuery(JSON.parse(text).result?.data?.json ?? JSON.parse(text), args.format || 'json')
473
+ )
426
474
  }
427
475
 
428
476
  async function main() {
@@ -445,7 +493,12 @@ async function main() {
445
493
  }
446
494
 
447
495
  if (command === 'config' && args._[1] === 'init') {
448
- assertCommandArgs(args, 'config init', new Set(['apiBaseUrl', 'apiKey', 'apiKeyStdin', 'force', 'help']), 2)
496
+ assertCommandArgs(
497
+ args,
498
+ 'config init',
499
+ new Set(['apiBaseUrl', 'apiKey', 'apiKeyStdin', 'force', 'help']),
500
+ 2
501
+ )
449
502
  await runConfigInit(args)
450
503
  return
451
504
  }