@yizhuan-cli/cli 0.1.17-beta.0 → 0.1.17-beta.2

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
@@ -99,3 +99,9 @@ yizhuan execute --ability hot_topics --params "{\"platform\":\"douyin\"}"
99
99
  - `我的会员什么时候到期`
100
100
 
101
101
  纯创作请求(例如“写一篇中秋文章”)由当前 Agent 完成,不调用 `content_extract`;CLI 只提供真实内容素材,不负责生成、改写或发布。
102
+
103
+ ## 使用统计
104
+
105
+ 仅采集业务调用元数据,不采集问题、关键词、URL、正文或返回内容。
106
+ 调用方可设置 `YIZHUAN_TRIGGER=user_initiated` 表示用户当前提问或手动触发(含 AI 代执行),设置 `YIZHUAN_TRIGGER=automated` 表示定时或无人值守任务。
107
+ 未设置时归为 `unknown`,不会猜测来源。检测轮询不计活跃。通过 `YIZHUAN_TELEMETRY=0` 可关闭使用埋点。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yizhuan-cli/cli",
3
- "version": "0.1.17-beta.0",
3
+ "version": "0.1.17-beta.2",
4
4
  "description": "易撰命令行工具,用于通过 API Key 查询易撰真实数据。",
5
5
  "private": false,
6
6
  "type": "module",
@@ -12,6 +12,7 @@
12
12
  "src/config.js",
13
13
  "src/skill.js",
14
14
  "src/doctor.js",
15
+ "src/usage.js",
15
16
  "resources",
16
17
  "README.md"
17
18
  ],
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yizhuan-cli
3
3
  description: |
4
- 安装、配置并使用易撰 CLI(yizhuan 命令)查询真实数据、提取文章/视频/图文内容的全量 Skill;若已安装用户明确指定平台对应的 yizhuan-<platform> Skill,必须优先使用对应的拆分平台 Skill。业务数据禁止臆造,文章创作由当前 Agent 完成。
4
+ 安装、配置并使用易撰 CLI(yizhuan 命令)查询真实数据、提取文章/视频/图文内容的全量 Skill;若已安装用户明确指定平台对应的易撰平台 Skill,必须优先使用对应的拆分平台 Skill。业务数据禁止臆造,文章创作由当前 Agent 完成。
5
5
  ---
6
6
 
7
7
  # 易撰 CLI
@@ -94,3 +94,43 @@ yizhuan doctor
94
94
  - 取消关注前先查询关注列表,使用匹配项的 `articleId`,不得因结果不符合预期自行取消。
95
95
  - `unsupported` 表示当前口径不支持,`empty` 表示没有匹配数据;如实转述,不编造补充结果。
96
96
  - CLI 报错时保留脱敏后的错误信息,引导检查 `yizhuan doctor`,不得输出完整 Key。
97
+
98
+ ## CLI 调用来源
99
+
100
+ 在每次 `execute` 或 `query` 业务调用前,根据本次任务的触发原因设置进程环境变量 `YIZHUAN_TRIGGER`:
101
+
102
+ - `user_initiated`:用户当前提问、手动指令及为完成该请求而执行的分页、后续能力调用。AI 代用户执行仍属于主动使用。
103
+ - `automated`:定时执行、无人值守批处理、后台监控或自动唤醒。用户曾要求建立定时任务,不代表每次定时运行都是主动使用。
104
+ - `unknown`:无法确定触发原因;不得默认标为主动,也不得仅因使用 AI 工具就标为自动。
105
+
106
+ 每次按当前触发原因显式覆盖,避免继承上一任务的来源。来源只通过子进程环境传递,不写入业务 JSON、API Key 配置或系统全局环境。调用结束后恢复原环境;一个任务中的分页保持同一来源,CLI 内置检测轮询由 CLI/服务端自动排除,不手工伪造 `polling`。
107
+
108
+ Bash/zsh 的单次主动查询示例(自动任务把值改为 `automated`):
109
+
110
+ ```bash
111
+ YIZHUAN_TRIGGER=user_initiated yizhuan query --json-file request.json
112
+ ```
113
+
114
+ PowerShell 示例,使用当前任务选择的来源并恢复原值:
115
+
116
+ ```powershell
117
+ $cliPreviousTrigger = $env:YIZHUAN_TRIGGER
118
+ try {
119
+ $env:YIZHUAN_TRIGGER = 'user_initiated'
120
+ yizhuan query --json-file request.json
121
+ } finally {
122
+ $env:YIZHUAN_TRIGGER = $cliPreviousTrigger
123
+ }
124
+ ```
125
+
126
+ 只有元数据参与使用统计,不额外上传用户问题、关键词、链接或正文。尊重用户现有的 `YIZHUAN_TELEMETRY=0` 设置,不启用或覆盖它。安装、配置、doctor、help 和 version 不作为业务使用;来源设置无需额外询问用户。来源标签不是触发新业务调用或定时任务的授权。
127
+
128
+ ## 任务关联与中文功能统计
129
+
130
+ 使用支持任务关联的新版本 CLI 时,每次明确用户目标或自动任务运行生成一个随机 UUID 作为 taskId。同一目标内的 execute/query 共用 `--task-id`,新的独立目标更换 ID;不要把整个聊天会话当作一次任务。使用 `yizhuan task start --task-id <uuid>` 建立任务,任务结束使用 `yizhuan task finish --task-id <uuid> --status completed|failed|cancelled`。自由任务不必声明目标路径;已明确固定流程时,可在 start 使用 `--goal-feature-ids works_search,content_extract,content_detection` 声明内部功能标识。
131
+
132
+ 每次业务操作生成独立 UUID 并传入 `--operation-id`;同一操作的技术重试复用该 ID,用户重新执行则换 ID。仅在后一步实际使用了前一步的输出时,传入 `--parent-operation-ids <前序操作UUID,...>`;不能仅因先后执行就声明连接。串行任务可传 `--sequence 0`、`--sequence 1` 等顺序,并行步骤不要伪造成串行顺序。分页通常是新的业务查询操作,CLI 内置检测轮询自动复用操作标识且不重复计数。
133
+
134
+ 也可通过当前任务子进程的 `YIZHUAN_TASK_ID`、`YIZHUAN_OPERATION_ID`、`YIZHUAN_PARENT_OPERATION_IDS`、`YIZHUAN_SEQUENCE` 传递;调用结束恢复旧值,避免继承到新任务。关联标识只能是随机 UUID,不包含用户提问、关键词或任何业务内容。关闭 telemetry 时不要额外执行统计任务 start/finish;旧版本 CLI 不支持这些参数时正常使用业务命令,关联标为未知,不捏造路径。
135
+
136
+ 统计和用户解释统一使用“作品查询”“作者查询”“内容提取”“一键检测”等中文功能名称。命令名和内部标识只用于实现,不作为面向用户的功能名称。
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "yizhuan-cli",
4
- "version": "0.1.16",
5
- "compatibleCliVersion": "0.1.16",
6
- "source": "npm:@yizhuan-cli/cli@0.1.16",
4
+ "version": "0.1.17-beta.2",
5
+ "compatibleCliVersion": "0.1.17-beta.2",
6
+ "source": "npm:@yizhuan-cli/cli@0.1.17-beta.2",
7
7
  "digestAlgorithm": "sha256",
8
8
  "files": [
9
9
  {
10
10
  "path": "SKILL.md",
11
- "sha256": "b58b68be1bd53fb3c9c572e8ccefcf000ad837c4f54d5269959d23f2c12150d4"
11
+ "sha256": "c19151583a93ae02008f1ff30b13d1ae2ed71ec143f9ef7b0ed47ac2648a18e6"
12
12
  }
13
13
  ]
14
14
  }
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'
5
5
  import { getConfigPath, maskApiKey, runConfigInit } from './config.js'
6
6
  import { runSkillSync } from './skill.js'
7
7
  import { runDoctor } from './doctor.js'
8
+ import { USAGE_FLAGS, usageHeaders, runUsageTask } from './usage.js'
8
9
 
9
10
  const CLI_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
10
11
  const PACKAGE_PATH = path.join(CLI_DIR, 'package.json')
@@ -64,6 +65,8 @@ Usage:
64
65
  yizhuan config show
65
66
  yizhuan skill sync [--global] [--host <name>] [--repair]
66
67
  yizhuan doctor [--json]
68
+ yizhuan task start [--task-id <uuid>] [--goal-feature-ids <功能标识列表>]
69
+ yizhuan task finish --task-id <uuid> [--status completed|failed|cancelled]
67
70
 
68
71
  Examples:
69
72
  yizhuan execute --ability works --query "百家号昨天新增了哪些作品"
@@ -103,6 +106,12 @@ Abilities:
103
106
  Config:
104
107
  ${CONFIG_PATH}
105
108
  可通过环境变量 YIZHUAN_CONFIG_PATH 指定独立配置文件
109
+ YIZHUAN_TRIGGER=user_initiated 标识用户主动请求,automated 标识自动任务;未设置为 unknown
110
+ YIZHUAN_TELEMETRY=0 关闭使用元数据埋点
111
+ YIZHUAN_TASK_ID=<uuid> 关联同一次用户任务,新的目标使用新 ID
112
+ YIZHUAN_OPERATION_ID=<uuid> 标识一次操作,技术重试复用,新操作更换
113
+ YIZHUAN_PARENT_OPERATION_IDS=<uuid,...> 声明实际使用的前序输出,不能只按时间猜测
114
+ YIZHUAN_SEQUENCE=<整数> 声明任务顺序;也支持 --task-id / --operation-id / --parent-operation-ids / --sequence
106
115
  `)
107
116
  }
108
117
 
@@ -238,6 +247,8 @@ function buildRequestHeaders(config) {
238
247
  'content-type': 'application/json',
239
248
  'x-device-type': 'pc',
240
249
  'x-yizhuan-cli-version': readPackageVersion(),
250
+ 'x-yizhuan-trigger': ['user_initiated', 'automated'].includes(process.env.YIZHUAN_TRIGGER)
251
+ ? process.env.YIZHUAN_TRIGGER : 'unknown',
241
252
  'x-yizhuan-telemetry': config.telemetryEnabled ? '1' : '0'
242
253
  }
243
254
  }
@@ -451,7 +462,7 @@ async function execute(args) {
451
462
  assertCommandArgs(
452
463
  args,
453
464
  'execute',
454
- new Set(['ability', 'query', 'params', 'jsonFile', ...PARAM_FLAG_KEYS])
465
+ new Set(['ability', 'query', 'params', 'jsonFile', ...PARAM_FLAG_KEYS, ...USAGE_FLAGS])
455
466
  )
456
467
  const ability = args.ability
457
468
  if (!ABILITIES.has(ability)) {
@@ -460,9 +471,10 @@ async function execute(args) {
460
471
 
461
472
  const requestParams = buildExecuteParams(args)
462
473
  const config = readConfig()
474
+ const requestHeaders = { ...buildRequestHeaders(config), ...usageHeaders(args) }
463
475
  const response = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
464
476
  method: 'POST',
465
- headers: buildRequestHeaders(config),
477
+ headers: requestHeaders,
466
478
  body: JSON.stringify({
467
479
  json: {
468
480
  ability,
@@ -494,7 +506,7 @@ async function execute(args) {
494
506
  if (Date.now() >= deadline) break
495
507
  const poll = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
496
508
  method: 'POST',
497
- headers: buildRequestHeaders(config),
509
+ headers: { ...requestHeaders, 'x-yizhuan-trigger': 'polling' },
498
510
  body: JSON.stringify({
499
511
  json: { ability: 'detection', params: { mode: 'get', id: result.data.id } }
500
512
  })
@@ -530,7 +542,7 @@ function renderQuery(payload, format) {
530
542
  }
531
543
 
532
544
  async function query(args) {
533
- assertCommandArgs(args, 'query', new Set(['json', 'jsonFile', 'params', 'format']))
545
+ assertCommandArgs(args, 'query', new Set(['json', 'jsonFile', 'params', 'format', ...USAGE_FLAGS]))
534
546
  const jsonText = args.jsonFile
535
547
  ? fs.readFileSync(path.resolve(String(args.jsonFile)), 'utf8')
536
548
  : args.json === '-'
@@ -541,7 +553,7 @@ async function query(args) {
541
553
  const config = readConfig()
542
554
  const response = await fetch(`${config.apiBaseUrl}/api/agent.query`, {
543
555
  method: 'POST',
544
- headers: buildRequestHeaders(config),
556
+ headers: { ...buildRequestHeaders(config), ...usageHeaders(args) },
545
557
  body: JSON.stringify({ json: request })
546
558
  })
547
559
  const text = await response.text()
@@ -606,6 +618,12 @@ async function main() {
606
618
  await execute(args)
607
619
  return
608
620
  }
621
+ if (command === 'task') {
622
+ assertCommandArgs(args, 'task', new Set(['taskId', 'status', 'goalFeatureIds']), 2)
623
+ const config = readConfig()
624
+ await runUsageTask(args, config, buildRequestHeaders(config))
625
+ return
626
+ }
609
627
  if (command === 'query') {
610
628
  await query(args)
611
629
  return
package/src/usage.js ADDED
@@ -0,0 +1,60 @@
1
+ import { randomUUID } from 'node:crypto'
2
+
3
+ export const USAGE_FLAGS = ['taskId', 'operationId', 'parentOperationIds', 'sequence']
4
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
5
+ export function usageHeaders(args = {}, env = process.env) {
6
+ const values = {
7
+ taskId: args.taskId ?? env.YIZHUAN_TASK_ID,
8
+ operationId: args.operationId ?? env.YIZHUAN_OPERATION_ID ?? randomUUID(),
9
+ parentOperationIds: args.parentOperationIds ?? env.YIZHUAN_PARENT_OPERATION_IDS,
10
+ sequence: args.sequence ?? env.YIZHUAN_SEQUENCE
11
+ }
12
+ for (const field of ['taskId', 'operationId'])
13
+ if (values[field] !== undefined && !uuid.test(String(values[field])))
14
+ throw new Error(`${field} 必须是 UUID,不能使用用户提问或业务内容`)
15
+ const parents = values.parentOperationIds
16
+ ? String(values.parentOperationIds).split(',').filter(Boolean)
17
+ : []
18
+ if (parents.length > 20 || parents.some((id) => !uuid.test(id)))
19
+ throw new Error('parentOperationIds 必须是最多20个逗号分隔的 UUID')
20
+ if (parents.length && !values.taskId) throw new Error('关联前序操作必须同时提供 taskId')
21
+ if (
22
+ values.sequence !== undefined &&
23
+ (!/^\d+$/.test(String(values.sequence)) || Number(values.sequence) > 1000000)
24
+ )
25
+ throw new Error('sequence 必须是0到1000000之间的整数')
26
+ return {
27
+ 'x-yizhuan-operation-id': String(values.operationId),
28
+ ...(values.taskId ? { 'x-yizhuan-task-id': String(values.taskId) } : {}),
29
+ ...(parents.length
30
+ ? { 'x-yizhuan-parent-operation-ids': [...new Set(parents)].join(',') }
31
+ : {}),
32
+ ...(values.sequence !== undefined ? { 'x-yizhuan-sequence': String(values.sequence) } : {})
33
+ }
34
+ }
35
+
36
+ export async function runUsageTask(args, config, headers) {
37
+ const action = args._[1]
38
+ const taskId =
39
+ args.taskId ?? process.env.YIZHUAN_TASK_ID ?? (action === 'start' ? randomUUID() : undefined)
40
+ if (!taskId || !uuid.test(taskId))
41
+ throw new Error('需要 UUID 格式的 --task-id 或 YIZHUAN_TASK_ID')
42
+ if (!['start', 'finish'].includes(action)) throw new Error('task 支持 start 或 finish')
43
+ const status = args.status ?? 'completed'
44
+ if (action === 'finish' && !['completed', 'failed', 'cancelled'].includes(status))
45
+ throw new Error('status 支持 completed、failed、cancelled')
46
+ const goalFeatureIds = args.goalFeatureIds ? String(args.goalFeatureIds).split(',') : undefined
47
+ const response = await fetch(
48
+ `${config.apiBaseUrl}/api/cli.${action === 'start' ? 'startUsageTask' : 'finishUsageTask'}`,
49
+ {
50
+ method: 'POST',
51
+ headers,
52
+ body: JSON.stringify({
53
+ json: action === 'start' ? { taskId, goalFeatureIds } : { taskId, status }
54
+ })
55
+ }
56
+ )
57
+ const payload = await response.json()
58
+ if (!response.ok) throw new Error(`任务统计请求失败 HTTP ${response.status}`)
59
+ console.log(JSON.stringify({ taskId, ...(payload.result?.data?.json ?? payload) }, null, 2))
60
+ }