@yizhuan-cli/cli 0.1.16-beta.1 → 0.1.16

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.16-beta.1",
3
+ "version": "0.1.16",
4
4
  "description": "易撰命令行工具,用于通过 API Key 查询易撰真实数据。",
5
5
  "private": false,
6
6
  "type": "module",
@@ -68,11 +68,13 @@ yizhuan doctor
68
68
  - 微信公众号暂不支持全网搜索作品。用户提出该请求时必须明确说明不支持,不得改用作品库检索并把 `empty` 解释为全网没有相关作品;微信公众号作者全网搜索仍可使用 `author_search`。
69
69
  - 查询指定作者最近作品时,直接使用 `intent=works_search`,同时提供 `platform`、`authorName` 和 `timeRange`;不得先用 `author_search` 的 `workCount` 判断该作者是否有作品。
70
70
  - `author_search` 返回的 `extAuthorId` 是平台外部作者 ID,不是易撰内部 `authorId`。需要按外部 ID 查询作品时只能传 `extAuthorId` / `ext_author_id`(兼容 `author_id`),必须同时提供 `platform`,由服务端完成内部 ID 转换;禁止把它直接作为内部 `authorId` 使用。
71
+ - 关注公开作者时,先用 `author_search` 取得并选定完整作者资料,再调用 `accounts` 的 `mode=follow_public_author`;必须传该结果的 `platform`、`extAuthorId` 和 `nickname`。`nickname` 必须是公开搜索结果中的作者名称,绝不能将 `extAuthorId` 作为名称写入。
71
72
  - 公开作者结果中的 `workCount: 0` 可能是上游未返回作品数后的缺省值,不代表作者没有作品;是否有作品必须以 `works_search` 的结果为准。
72
73
  - 作品复合筛选优先使用统一协议 `intent=works_search`。平台使用 `platform`,分类使用 `categories`,内容类型使用 `content_type`,最低阅读/播放和评论分别使用 `min_views`、`min_comments`;需要固定顺序时显式传 `sort_by` 和 `order`。不得把作品查询改成作者、雷达或热点查询。
73
74
  - 当前 CLI 不支持按单篇作品 URL 或平台侧 `articleId` 精确查询作品详情。用户提供作品链接或文章 ID 时,直接说明该能力暂不支持;不得将完整链接或纯文章 ID 改作 `keyword` 搜索,也不得把这种搜索返回的 `empty` 解读为全网不存在该作品。
74
75
  - 作品查询返回后必须逐项核对用户要求的分类、类型和指标阈值;低于阈值或缺少必要指标的项目不得作为符合条件的结果展示,也不得擅自放宽条件。展示总数必须使用返回的 `data.total`,不能把当前页条数当成总数。
75
76
  - 作者累计指标筛选使用 `author_query`,不得改成作品指标或擅自降低阈值。必须逐项映射用户给出的所有条件:粉丝使用 `min_follower` / `max_follower`,点赞使用 `min_total_likes` / `max_total_likes`,阅读使用 `min_total_views` / `max_total_views`,关注使用 `min_friends` / `max_friends`,作品数使用 `min_work_count` / `max_work_count`;复合条件不得遗漏任何字段。例如“百家号粉丝大于50万,点赞大于10万”必须同时传 `platform_id:2`、`min_follower:500000` 和 `min_total_likes:100000`。
77
+ - 用户按粉丝、关注、阅读、评论或点赞询问“最高作者”时,始终以作者结果的对应累计字段排序或判断,禁止改查作品、雷达作品或单篇作品指标。若返回作者中目标字段均为 `0`,如实说明“查询到的作者该指标均为 0,无法据此排序或判断最高作者”;不得据此断言平台不支持,也不得以作品数据补充或替代结论。
76
78
  - 作者指标查询返回后,必须核对 `data.items` 中所有已请求指标。指标缺失、显示“暂未返回”或低于/高于用户阈值的项目不得作为符合条件的结果展示;一旦发现此类项目,必须先检查实际请求参数是否遗漏对应的 min/max 字段并使用完整参数重新查询,禁止直接报告原总数。
77
79
  - “我的关注作者”使用 `radar` 且 `mode=following`,不是作品收藏。
78
80
  - “我的灵感”“灵感库”“灵感作品”“我的收藏”和“收藏作品”是同一业务概念,均直接使用 `favorites`;不得尝试 `inspiration` 等不存在的 ability,也不要反问用户是不是指收藏作品。
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "yizhuan-cli",
4
- "version": "0.1.16-beta.1",
5
- "compatibleCliVersion": "0.1.16-beta.1",
6
- "source": "npm:@yizhuan-cli/cli@0.1.16-beta.1",
4
+ "version": "0.1.16",
5
+ "compatibleCliVersion": "0.1.16",
6
+ "source": "npm:@yizhuan-cli/cli@0.1.16",
7
7
  "digestAlgorithm": "sha256",
8
8
  "files": [
9
9
  {
10
10
  "path": "SKILL.md",
11
- "sha256": "4c7a2c189e1b86577a8b3ff290d7cc1031a4cf3fb3e3e075560f4b672adf90dc"
11
+ "sha256": "a46185057b4fa09503ba1773d3e8912a9a448c845f16c39759e5e19b246a1e39"
12
12
  }
13
13
  ]
14
14
  }
package/src/doctor.js CHANGED
@@ -210,7 +210,7 @@ export function inspectSkill(
210
210
  }
211
211
  return check('pass', 'skill', `正式 Skill ${metadata.version} 来源和摘要有效:${resolved.target}`)
212
212
  } catch (error) {
213
- return check('fail', 'skill', `正式 Skill 校验失败:${error.message}`, '保留冲突内容并人工确认,或移走后重新运行 skill sync。')
213
+ return check('fail', 'skill', `正式 Skill 校验失败:${error.message}`, '人工确认后运行 yizhuan skill sync --repair,或移走冲突内容后重新同步。')
214
214
  }
215
215
  }
216
216
 
package/src/index.js CHANGED
@@ -59,7 +59,7 @@ Usage:
59
59
  yizhuan config init [--api-key-stdin] [--api-base-url <url>] [--force]
60
60
  yizhuan config path
61
61
  yizhuan config show
62
- yizhuan skill sync [--global]
62
+ yizhuan skill sync [--global] [--host <name>] [--repair]
63
63
  yizhuan doctor [--json]
64
64
 
65
65
  Examples:
@@ -323,6 +323,65 @@ function assignFlagParams(params, args) {
323
323
  return params
324
324
  }
325
325
 
326
+ const AUTHOR_RANKING_METRICS = [
327
+ {
328
+ key: 'friendsCount',
329
+ label: '关注数',
330
+ patterns: [/关注/, /friends(?:Count|_count)?/i]
331
+ },
332
+ {
333
+ key: 'followerCount',
334
+ label: '粉丝数',
335
+ patterns: [/粉丝/, /follower(?:Count|_count)?/i]
336
+ },
337
+ {
338
+ key: 'totalViews',
339
+ label: '阅读数',
340
+ patterns: [/阅读|播放/, /(?:total)?views?/i]
341
+ },
342
+ {
343
+ key: 'totalLikes',
344
+ label: '点赞数',
345
+ patterns: [/点赞/, /(?:total)?likes?/i]
346
+ }
347
+ ]
348
+
349
+ function rankingMetric(query, params) {
350
+ const text = [
351
+ query,
352
+ params?.metric,
353
+ params?.sortBy,
354
+ params?.sort_by,
355
+ params?.order
356
+ ].filter(Boolean).join(' ')
357
+ if (!/(最高|最多|最大|第一|排行|排名|top)/i.test(text)) return undefined
358
+ return AUTHOR_RANKING_METRICS.find((metric) => metric.patterns.some((pattern) => pattern.test(text)))
359
+ }
360
+
361
+ export function annotateUnavailableAuthorRanking(result, { query, params } = {}) {
362
+ const metric = rankingMetric(query, params)
363
+ const items = result?.data?.items
364
+ if (!metric || !Array.isArray(items) || items.length === 0) return result
365
+
366
+ const allValuesAreZero = items.every((item) => Number(item?.[metric.key]) === 0)
367
+ if (!allValuesAreZero) return result
368
+
369
+ const message = `查询到的作者${metric.label}均为 0,无法据此排序或判断最高作者`
370
+ return {
371
+ ...result,
372
+ message: result.message || message,
373
+ data: {
374
+ ...result.data,
375
+ ranking: {
376
+ metric: metric.key,
377
+ status: 'unavailable',
378
+ reason: 'all_returned_values_zero',
379
+ message
380
+ }
381
+ }
382
+ }
383
+ }
384
+
326
385
  export function buildExecuteParams(args) {
327
386
  if (args.jsonFile) {
328
387
  const conflictingKeys = ['params', 'query', ...PARAM_FLAG_KEYS].filter(
@@ -372,6 +431,7 @@ async function execute(args) {
372
431
  throw new Error(`未知 ability:${ability || ''},请执行 yizhuan --help 查看可用能力`)
373
432
  }
374
433
 
434
+ const requestParams = buildExecuteParams(args)
375
435
  const config = readConfig()
376
436
  const response = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
377
437
  method: 'POST',
@@ -384,7 +444,7 @@ async function execute(args) {
384
444
  json: {
385
445
  ability,
386
446
  query: typeof args.query === 'string' ? args.query : undefined,
387
- params: buildExecuteParams(args)
447
+ params: requestParams
388
448
  }
389
449
  })
390
450
  })
@@ -395,7 +455,6 @@ async function execute(args) {
395
455
  }
396
456
 
397
457
  let result = payloadResult(JSON.parse(text))
398
- const requestParams = buildExecuteParams(args)
399
458
  if (
400
459
  ability === 'detection' &&
401
460
  requestParams?.wait === true &&
@@ -426,6 +485,10 @@ async function execute(args) {
426
485
  result = payloadResult(JSON.parse(pollText))
427
486
  }
428
487
  }
488
+ result = annotateUnavailableAuthorRanking(result, {
489
+ query: typeof args.query === 'string' ? args.query : undefined,
490
+ params: requestParams
491
+ })
429
492
  console.log(JSON.stringify(result, null, 2))
430
493
  }
431
494
 
@@ -468,9 +531,11 @@ async function query(args) {
468
531
  })
469
532
  const text = await response.text()
470
533
  if (!response.ok) throw new Error(`请求失败 HTTP ${response.status}:${text}`)
471
- console.log(
472
- renderQuery(JSON.parse(text).result?.data?.json ?? JSON.parse(text), args.format || 'json')
473
- )
534
+ const result = annotateUnavailableAuthorRanking(payloadResult(JSON.parse(text)), {
535
+ query: request.query || request.keyword,
536
+ params: request
537
+ })
538
+ console.log(renderQuery(result, args.format || 'json'))
474
539
  }
475
540
 
476
541
  async function main() {
@@ -510,7 +575,7 @@ async function main() {
510
575
  }
511
576
 
512
577
  if (command === 'skill' && args._[1] === 'sync') {
513
- assertCommandArgs(args, 'skill sync', new Set(['global', 'host', 'help']), 2)
578
+ assertCommandArgs(args, 'skill sync', new Set(['global', 'host', 'repair', 'help']), 2)
514
579
  await runSkillSync(args)
515
580
  return
516
581
  }
package/src/skill.js CHANGED
@@ -90,7 +90,7 @@ export function loadOfficialSkill(resourceDir = DEFAULT_RESOURCE_DIR) {
90
90
  return { manifest, resourceDir }
91
91
  }
92
92
 
93
- export function readSkillMetadata(target) {
93
+ export function readSkillMetadata(target, { allowModified = false } = {}) {
94
94
  const metadataPath = path.join(target, SKILL_METADATA_FILE)
95
95
  if (!fs.existsSync(metadataPath)) {
96
96
  throw new Error(`目标存在未受易撰管理的同名内容,已停止同步:${target}`)
@@ -111,7 +111,7 @@ export function readSkillMetadata(target) {
111
111
  }
112
112
  for (const file of metadata.files) {
113
113
  const installedPath = safeResourcePath(target, file.path)
114
- if (!fs.existsSync(installedPath) || sha256File(installedPath) !== file.sha256) {
114
+ if (!allowModified && (!fs.existsSync(installedPath) || sha256File(installedPath) !== file.sha256)) {
115
115
  throw new Error(`已安装 Skill 包含未知修改,已停止覆盖:${file.path}`)
116
116
  }
117
117
  }
@@ -182,14 +182,15 @@ export function syncOfficialSkill(
182
182
  {
183
183
  loadBundle = loadOfficialSkill,
184
184
  installBundle = installStagedSkill,
185
- now = () => new Date()
185
+ now = () => new Date(),
186
+ repair = false
186
187
  } = {}
187
188
  ) {
188
189
  const bundle = loadBundle()
189
190
  const { manifest } = bundle
190
191
  let installed
191
192
  if (fs.existsSync(resolved.target)) {
192
- installed = readSkillMetadata(resolved.target)
193
+ installed = readSkillMetadata(resolved.target, { allowModified: repair })
193
194
  const comparison = compareVersions(installed.version, manifest.version)
194
195
  if (comparison > 0) {
195
196
  return { action: 'newer', metadata: installed, target: resolved.target }
@@ -257,11 +258,12 @@ export function resolveSkillTarget(
257
258
 
258
259
  export function printSkillSyncHelp() {
259
260
  console.log(`Usage:
260
- yizhuan skill sync [--global] [--host <name>]
261
+ yizhuan skill sync [--global] [--host <name>] [--repair]
261
262
 
262
263
  Options:
263
264
  --global 安装到当前受支持宿主的用户级 Skill 目录
264
265
  --host 明确指定 codex、agents、trae、claude 或 cursor
266
+ --repair 仅覆盖带官方托管元数据但内容校验失败的 Skill
265
267
  --help 显示本帮助`)
266
268
  }
267
269
 
@@ -272,7 +274,7 @@ export async function runSkillSync(args) {
272
274
  }
273
275
 
274
276
  const resolved = resolveSkillTarget({ global: Boolean(args.global), host: args.host })
275
- const result = syncOfficialSkill(resolved)
277
+ const result = syncOfficialSkill(resolved, { repair: Boolean(args.repair) })
276
278
  const actionText = {
277
279
  installed: '已安装',
278
280
  updated: '已更新',