@yizhuan-cli/cli 0.1.16-beta.2 → 0.1.17-beta.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/README.md CHANGED
@@ -36,6 +36,8 @@ yizhuan config path
36
36
  yizhuan config show
37
37
  yizhuan execute --ability membership
38
38
  yizhuan execute --ability works --query "百家号昨天新增了哪些作品"
39
+ yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","workId":"123456","count":20}'
40
+ yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","title":"作品标题","count":20}'
39
41
  yizhuan execute --ability hot_topics --platform douyin
40
42
  yizhuan execute --ability hot_topics --params '{"platform":"douyin"}'
41
43
  yizhuan execute --ability accounts --params '{"mode":"public_search","platform":"toutiao","keyword":"张三"}'
@@ -72,6 +74,9 @@ yizhuan execute --ability hot_topics --params "{\"platform\":\"douyin\"}"
72
74
  ## 支持的典型能力
73
75
 
74
76
  - 作品查询:`yizhuan execute --ability works --query "AI 相关的热门作品有哪些"`
77
+ - 作品评论:`yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","workId":"123456","count":20}'`
78
+ - 按标题查询评论:`yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","title":"作品标题"}'`;标题必须唯一匹配,多个候选时 CLI 会要求补充条件。
79
+ - 微信公众号/视频号评论:`yizhuan execute --ability works --params '{"mode":"comments","platform":"weixin","workId":"https://mp.weixin.qq.com/s/文章链接","count":20}'`;这两个平台的 `workId` 使用作品链接。
75
80
  - 作者查询:`yizhuan execute --ability accounts --query "查询张三这个作者"`
76
81
  - 作者指标筛选:`yizhuan execute --ability author_query --params '{"platform_id":2,"min_follower":500000,"min_total_likes":100000}'`
77
82
  - 作者全网搜索:`yizhuan execute --ability accounts --params '{"mode":"public_search","platform":"toutiao","keyword":"张三"}'`
@@ -82,6 +87,8 @@ yizhuan execute --ability hot_topics --params "{\"platform\":\"douyin\"}"
82
87
  - 竞品动态筛选:`yizhuan execute --ability radar --params '{"mode":"timeline","timeRange":"7d","platform":"baijiahao","keyword":"AI"}'`
83
88
  - 取消关注雷达作者:先查询关注列表拿到 `articleId`,再执行 `yizhuan execute --ability radar --params '{"mode":"unfollow","articleId":"205169870"}'`
84
89
  - 会员查询:`yizhuan execute --ability membership --query "我的会员什么时候到期"`
90
+ - 内容提取:`yizhuan execute --ability content_extract --url "https://example.com/article"`
91
+ - 易撰作品内容提取:`yizhuan execute --ability content_extract --params '{"contentId":12345}'`
85
92
 
86
93
  如果你是通过 Codex、Cursor、Claude Code 这类 Agent 使用 CLI,也可以直接用自然语言提问,例如:
87
94
 
@@ -90,3 +97,5 @@ yizhuan execute --ability hot_topics --params "{\"platform\":\"douyin\"}"
90
97
  - `查询 AI 相关的热门文章`
91
98
  - `取消关注陈某某先生`
92
99
  - `我的会员什么时候到期`
100
+
101
+ 纯创作请求(例如“写一篇中秋文章”)由当前 Agent 完成,不调用 `content_extract`;CLI 只提供真实内容素材,不负责生成、改写或发布。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yizhuan-cli/cli",
3
- "version": "0.1.16-beta.2",
3
+ "version": "0.1.17-beta.0",
4
4
  "description": "易撰命令行工具,用于通过 API Key 查询易撰真实数据。",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yizhuan-cli
3
3
  description: |
4
- 安装、配置并使用易撰 CLI(yizhuan 命令)查询真实数据的全量 Skill,处理作品、作者、全网搜索、热点、竞品动态、会员、灵感、收藏和一键检测;若已安装用户明确指定平台对应的 yizhuan-<platform> Skill,必须优先使用对应的拆分平台 Skill。业务数据禁止臆造。
4
+ 安装、配置并使用易撰 CLI(yizhuan 命令)查询真实数据、提取文章/视频/图文内容的全量 Skill;若已安装用户明确指定平台对应的 yizhuan-<platform> Skill,必须优先使用对应的拆分平台 Skill。业务数据禁止臆造,文章创作由当前 Agent 完成。
5
5
  ---
6
6
 
7
7
  # 易撰 CLI
@@ -40,6 +40,9 @@ yizhuan doctor
40
40
  | 配置状态 | `yizhuan config show` |
41
41
  | 作品 | `yizhuan execute --ability works --query "百家号昨天新增了哪些作品"` |
42
42
  | 作品检索 | `yizhuan execute --ability works --params '{"mode":"search","keyword":"AI"}'` |
43
+ | 作品评论(明确 ID) | `yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","workId":"123456","count":20}'` |
44
+ | 作品评论(按标题定位) | `yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","title":"作品标题"}'` |
45
+ | 微信公众号/视频号评论 | `yizhuan execute --ability works --params '{"mode":"comments","platform":"weixin","workId":"https://mp.weixin.qq.com/s/文章链接","count":20}'` |
43
46
  | 指定作者作品 | `yizhuan query --json '{"intent":"works_search","scope":"public","platform":"toutiao","authorName":"大名府名人","timeRange":"3d"}'` |
44
47
  | 高级作品筛选 | `yizhuan query --json '{"intent":"works_search","scope":"public","platform":"weibo","categories":["娱乐","情感"],"content_type":"video","min_views":100000,"min_comments":10000,"sort_by":"views","order":"desc"}'` |
45
48
  | 作者查询 | `yizhuan execute --ability accounts --query "张三"` |
@@ -53,10 +56,14 @@ yizhuan doctor
53
56
  | 会员 | `yizhuan execute --ability membership` |
54
57
  | 我的灵感 / 灵感库 / 收藏作品 | `yizhuan execute --ability favorites` |
55
58
  | 一键检测 | 将标题和正文写入临时 JSON 文件后执行 `yizhuan execute --ability detection --json-file <file>` |
59
+ | 提取文章/视频/图文 | `yizhuan execute --ability content_extract --params '{"url":"https://..."}'` |
56
60
 
57
61
  ## 调用规则
58
62
 
59
63
  - 需要真实易撰数据时必须调用 CLI,不直接猜测结果。
64
+ - 用户只说“写一篇中秋文章”等纯创作请求时,不调用 CLI,由当前 Agent 直接创作。
65
+ - 用户要求提取文章正文、视频口播文字或图文素材时,使用 `content_extract`;来源可以是 `url` 或易撰 `contentId`。
66
+ - 用户要求“根据易撰热门内容创作”时,先调用查询/提取能力获取真实素材,再由当前 Agent 完成创作;CLI 不生成、改写、生成标题或发布内容。
60
67
  - 一键检测的原创度检测当前只有百度引擎。展示检测结果时使用 `data.originalScore` 和 `data.originalEngine`;不得把历史兼容字段 `_360`、`sogou`、`zonghe` 展示为检测引擎或额外得分。
61
68
  - 一键检测支持 `submit`、`get` 和 `wait` 模式。`wait` 必须按照服务端 `pollAfterSeconds` 轮询;pending、failed、timeout 状态必须如实报告,成功、失败或超时后均清理包含标题和正文的临时 JSON 文件。
62
69
  - PowerShell 使用单引号包裹 JSON。终端可能改写引号时,将不含密钥的请求对象写入文件,再使用 `--json-file`。
@@ -71,6 +78,10 @@ yizhuan doctor
71
78
  - 关注公开作者时,先用 `author_search` 取得并选定完整作者资料,再调用 `accounts` 的 `mode=follow_public_author`;必须传该结果的 `platform`、`extAuthorId` 和 `nickname`。`nickname` 必须是公开搜索结果中的作者名称,绝不能将 `extAuthorId` 作为名称写入。
72
79
  - 公开作者结果中的 `workCount: 0` 可能是上游未返回作品数后的缺省值,不代表作者没有作品;是否有作品必须以 `works_search` 的结果为准。
73
80
  - 作品复合筛选优先使用统一协议 `intent=works_search`。平台使用 `platform`,分类使用 `categories`,内容类型使用 `content_type`,最低阅读/播放和评论分别使用 `min_views`、`min_comments`;需要固定顺序时显式传 `sort_by` 和 `order`。不得把作品查询改成作者、雷达或热点查询。
81
+ - 查询作品评论使用 `works` 的 `mode=comments`。有明确平台作品 ID 时传 `platform` + `workId`;只有作品标题时传 `platform` + `title`,CLI 会先检索作品,只有标题唯一匹配且能解析平台作品 ID 才继续查询。多个候选返回 `AMBIGUOUS_WORK`,不得自行猜测。
82
+ - 微信公众号和微信视频号评论通过登录采集机获取,平台分别传 `weixin`/`wxgzh` 和 `wxsph`/`视频号`,`workId` 必须是作品链接;这两个平台暂不支持 `cursor` 和 `parentId`。
83
+ - 作品评论默认返回一页、最多 50 条,未传 `count` 时按 20 条处理。需要继续翻页时,必须将上一页返回的 `data.cursor` 原样传回,依据 `data.hasMore` 停止;不得把评论数 `metrics.commentCount` 当作评论正文,也不得声称结果是“最新”或“热门”。
84
+ - 作品评论返回 `data.items[].text`、`user`、`likeCount`、`replyCount`、`ipLabel`、`createdAt` 等字段。查询评论回复时可额外传 `parentId`。评论发布、删除、点赞不属于本 Skill 能力。
74
85
  - 当前 CLI 不支持按单篇作品 URL 或平台侧 `articleId` 精确查询作品详情。用户提供作品链接或文章 ID 时,直接说明该能力暂不支持;不得将完整链接或纯文章 ID 改作 `keyword` 搜索,也不得把这种搜索返回的 `empty` 解读为全网不存在该作品。
75
86
  - 作品查询返回后必须逐项核对用户要求的分类、类型和指标阈值;低于阈值或缺少必要指标的项目不得作为符合条件的结果展示,也不得擅自放宽条件。展示总数必须使用返回的 `data.total`,不能把当前页条数当成总数。
76
87
  - 作者累计指标筛选使用 `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`。
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "yizhuan-cli",
4
- "version": "0.1.16-beta.2",
5
- "compatibleCliVersion": "0.1.16-beta.2",
6
- "source": "npm:@yizhuan-cli/cli@0.1.16-beta.2",
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": "a46185057b4fa09503ba1773d3e8912a9a448c845f16c39759e5e19b246a1e39"
11
+ "sha256": "b58b68be1bd53fb3c9c572e8ccefcf000ad837c4f54d5269959d23f2c12150d4"
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
@@ -26,7 +26,8 @@ const ABILITIES = new Set([
26
26
  'work_query',
27
27
  'membership_info',
28
28
  'favorites_query',
29
- 'detection'
29
+ 'detection',
30
+ 'content_extract'
30
31
  ])
31
32
 
32
33
  const PARAM_FLAG_KEYS = [
@@ -43,7 +44,9 @@ const PARAM_FLAG_KEYS = [
43
44
  'authorId',
44
45
  'extAuthorId',
45
46
  'collectionType',
46
- 'metric'
47
+ 'metric',
48
+ 'url', 'contentType', 'contentId', 'content_id', 'platformCode', 'platform_code',
49
+ 'outputFormat', 'output_format', 'includeImages', 'includeTranscriptSegments', 'source'
47
50
  ]
48
51
 
49
52
  function printHelp() {
@@ -59,17 +62,20 @@ Usage:
59
62
  yizhuan config init [--api-key-stdin] [--api-base-url <url>] [--force]
60
63
  yizhuan config path
61
64
  yizhuan config show
62
- yizhuan skill sync [--global]
65
+ yizhuan skill sync [--global] [--host <name>] [--repair]
63
66
  yizhuan doctor [--json]
64
67
 
65
68
  Examples:
66
69
  yizhuan execute --ability works --query "百家号昨天新增了哪些作品"
70
+ yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","workId":"123456","count":20}'
71
+ yizhuan execute --ability works --params '{"mode":"comments","platform":"douyin","title":"作品标题","count":20}'
67
72
  yizhuan execute --ability hot_topics --platform douyin
68
73
  yizhuan execute --ability hot_topics --params '{"platform":"douyin"}'
69
74
  yizhuan execute --ability accounts --params '{"mode":"public_search","platform":"toutiao","keyword":"张三"}'
70
75
  yizhuan execute --ability radar --params '{"mode":"following"}'
71
76
  yizhuan execute --ability radar --mode timeline --timeRange 7d
72
77
  yizhuan execute --ability detection --json-file detection.json
78
+ yizhuan execute --ability content_extract --url "https://example.com/article"
73
79
  yizhuan execute --ability radar --params '{"mode":"unfollow","articleId":"205169870"}'
74
80
 
75
81
  Notes:
@@ -92,6 +98,7 @@ Abilities:
92
98
  public_author_search 作者全网搜索,兼容低层能力
93
99
  public_work_search 作品全网搜索,兼容低层能力
94
100
  detection 一键检测(原创/风险)
101
+ content_extract 提取文章、视频和图文内容
95
102
 
96
103
  Config:
97
104
  ${CONFIG_PATH}
@@ -220,7 +227,18 @@ function readConfig() {
220
227
 
221
228
  return {
222
229
  apiBaseUrl: String(config.apiBaseUrl).replace(/\/+$/, ''),
223
- apiKey: String(config.apiKey)
230
+ apiKey: String(config.apiKey),
231
+ telemetryEnabled: process.env.YIZHUAN_TELEMETRY !== '0'
232
+ }
233
+ }
234
+
235
+ function buildRequestHeaders(config) {
236
+ return {
237
+ authorization: config.apiKey,
238
+ 'content-type': 'application/json',
239
+ 'x-device-type': 'pc',
240
+ 'x-yizhuan-cli-version': readPackageVersion(),
241
+ 'x-yizhuan-telemetry': config.telemetryEnabled ? '1' : '0'
224
242
  }
225
243
  }
226
244
 
@@ -235,7 +253,16 @@ function stripWrappingQuotes(text) {
235
253
  return value
236
254
  }
237
255
 
238
- const OPAQUE_ID_KEYS = new Set(['extAuthorId', 'ext_author_id', 'author_id'])
256
+ // Platform work IDs can exceed Number.MAX_SAFE_INTEGER (Douyin aweme IDs are
257
+ // commonly 19 digits). Keep them opaque even when params use loose key=value
258
+ // parsing; JSON input already preserves quoted strings.
259
+ const OPAQUE_ID_KEYS = new Set([
260
+ 'extAuthorId',
261
+ 'ext_author_id',
262
+ 'author_id',
263
+ 'workId',
264
+ 'work_id'
265
+ ])
239
266
 
240
267
  function coerceScalar(value, key) {
241
268
  const text = stripWrappingQuotes(value).replace(/\\_/g, '_').trim()
@@ -323,6 +350,65 @@ function assignFlagParams(params, args) {
323
350
  return params
324
351
  }
325
352
 
353
+ const AUTHOR_RANKING_METRICS = [
354
+ {
355
+ key: 'friendsCount',
356
+ label: '关注数',
357
+ patterns: [/关注/, /friends(?:Count|_count)?/i]
358
+ },
359
+ {
360
+ key: 'followerCount',
361
+ label: '粉丝数',
362
+ patterns: [/粉丝/, /follower(?:Count|_count)?/i]
363
+ },
364
+ {
365
+ key: 'totalViews',
366
+ label: '阅读数',
367
+ patterns: [/阅读|播放/, /(?:total)?views?/i]
368
+ },
369
+ {
370
+ key: 'totalLikes',
371
+ label: '点赞数',
372
+ patterns: [/点赞/, /(?:total)?likes?/i]
373
+ }
374
+ ]
375
+
376
+ function rankingMetric(query, params) {
377
+ const text = [
378
+ query,
379
+ params?.metric,
380
+ params?.sortBy,
381
+ params?.sort_by,
382
+ params?.order
383
+ ].filter(Boolean).join(' ')
384
+ if (!/(最高|最多|最大|第一|排行|排名|top)/i.test(text)) return undefined
385
+ return AUTHOR_RANKING_METRICS.find((metric) => metric.patterns.some((pattern) => pattern.test(text)))
386
+ }
387
+
388
+ export function annotateUnavailableAuthorRanking(result, { query, params } = {}) {
389
+ const metric = rankingMetric(query, params)
390
+ const items = result?.data?.items
391
+ if (!metric || !Array.isArray(items) || items.length === 0) return result
392
+
393
+ const allValuesAreZero = items.every((item) => Number(item?.[metric.key]) === 0)
394
+ if (!allValuesAreZero) return result
395
+
396
+ const message = `查询到的作者${metric.label}均为 0,无法据此排序或判断最高作者`
397
+ return {
398
+ ...result,
399
+ message: result.message || message,
400
+ data: {
401
+ ...result.data,
402
+ ranking: {
403
+ metric: metric.key,
404
+ status: 'unavailable',
405
+ reason: 'all_returned_values_zero',
406
+ message
407
+ }
408
+ }
409
+ }
410
+ }
411
+
326
412
  export function buildExecuteParams(args) {
327
413
  if (args.jsonFile) {
328
414
  const conflictingKeys = ['params', 'query', ...PARAM_FLAG_KEYS].filter(
@@ -372,19 +458,16 @@ async function execute(args) {
372
458
  throw new Error(`未知 ability:${ability || ''},请执行 yizhuan --help 查看可用能力`)
373
459
  }
374
460
 
461
+ const requestParams = buildExecuteParams(args)
375
462
  const config = readConfig()
376
463
  const response = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
377
464
  method: 'POST',
378
- headers: {
379
- authorization: config.apiKey,
380
- 'content-type': 'application/json',
381
- 'x-device-type': 'pc'
382
- },
465
+ headers: buildRequestHeaders(config),
383
466
  body: JSON.stringify({
384
467
  json: {
385
468
  ability,
386
469
  query: typeof args.query === 'string' ? args.query : undefined,
387
- params: buildExecuteParams(args)
470
+ params: requestParams
388
471
  }
389
472
  })
390
473
  })
@@ -395,7 +478,6 @@ async function execute(args) {
395
478
  }
396
479
 
397
480
  let result = payloadResult(JSON.parse(text))
398
- const requestParams = buildExecuteParams(args)
399
481
  if (
400
482
  ability === 'detection' &&
401
483
  requestParams?.wait === true &&
@@ -412,11 +494,7 @@ async function execute(args) {
412
494
  if (Date.now() >= deadline) break
413
495
  const poll = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
414
496
  method: 'POST',
415
- headers: {
416
- authorization: config.apiKey,
417
- 'content-type': 'application/json',
418
- 'x-device-type': 'pc'
419
- },
497
+ headers: buildRequestHeaders(config),
420
498
  body: JSON.stringify({
421
499
  json: { ability: 'detection', params: { mode: 'get', id: result.data.id } }
422
500
  })
@@ -426,6 +504,10 @@ async function execute(args) {
426
504
  result = payloadResult(JSON.parse(pollText))
427
505
  }
428
506
  }
507
+ result = annotateUnavailableAuthorRanking(result, {
508
+ query: typeof args.query === 'string' ? args.query : undefined,
509
+ params: requestParams
510
+ })
429
511
  console.log(JSON.stringify(result, null, 2))
430
512
  }
431
513
 
@@ -459,18 +541,16 @@ async function query(args) {
459
541
  const config = readConfig()
460
542
  const response = await fetch(`${config.apiBaseUrl}/api/agent.query`, {
461
543
  method: 'POST',
462
- headers: {
463
- authorization: config.apiKey,
464
- 'content-type': 'application/json',
465
- 'x-device-type': 'pc'
466
- },
544
+ headers: buildRequestHeaders(config),
467
545
  body: JSON.stringify({ json: request })
468
546
  })
469
547
  const text = await response.text()
470
548
  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
- )
549
+ const result = annotateUnavailableAuthorRanking(payloadResult(JSON.parse(text)), {
550
+ query: request.query || request.keyword,
551
+ params: request
552
+ })
553
+ console.log(renderQuery(result, args.format || 'json'))
474
554
  }
475
555
 
476
556
  async function main() {
@@ -510,7 +590,7 @@ async function main() {
510
590
  }
511
591
 
512
592
  if (command === 'skill' && args._[1] === 'sync') {
513
- assertCommandArgs(args, 'skill sync', new Set(['global', 'host', 'help']), 2)
593
+ assertCommandArgs(args, 'skill sync', new Set(['global', 'host', 'repair', 'help']), 2)
514
594
  await runSkillSync(args)
515
595
  return
516
596
  }
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: '已更新',