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

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.17-beta.1",
3
+ "version": "0.1.17",
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
  ],
@@ -124,3 +124,13 @@ try {
124
124
  ```
125
125
 
126
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.17-beta.1",
5
- "compatibleCliVersion": "0.1.17-beta.1",
6
- "source": "npm:@yizhuan-cli/cli@0.1.17-beta.1",
4
+ "version": "0.1.17",
5
+ "compatibleCliVersion": "0.1.17",
6
+ "source": "npm:@yizhuan-cli/cli@0.1.17",
7
7
  "digestAlgorithm": "sha256",
8
8
  "files": [
9
9
  {
10
10
  "path": "SKILL.md",
11
- "sha256": "f5d8858b7377e3afabc394bf2aab72ffce7b7cee60864d0d97d86549d68fd70b"
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 "百家号昨天新增了哪些作品"
@@ -105,6 +108,10 @@ Config:
105
108
  可通过环境变量 YIZHUAN_CONFIG_PATH 指定独立配置文件
106
109
  YIZHUAN_TRIGGER=user_initiated 标识用户主动请求,automated 标识自动任务;未设置为 unknown
107
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
108
115
  `)
109
116
  }
110
117
 
@@ -455,7 +462,7 @@ async function execute(args) {
455
462
  assertCommandArgs(
456
463
  args,
457
464
  'execute',
458
- new Set(['ability', 'query', 'params', 'jsonFile', ...PARAM_FLAG_KEYS])
465
+ new Set(['ability', 'query', 'params', 'jsonFile', ...PARAM_FLAG_KEYS, ...USAGE_FLAGS])
459
466
  )
460
467
  const ability = args.ability
461
468
  if (!ABILITIES.has(ability)) {
@@ -464,9 +471,10 @@ async function execute(args) {
464
471
 
465
472
  const requestParams = buildExecuteParams(args)
466
473
  const config = readConfig()
474
+ const requestHeaders = { ...buildRequestHeaders(config), ...usageHeaders(args) }
467
475
  const response = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
468
476
  method: 'POST',
469
- headers: buildRequestHeaders(config),
477
+ headers: requestHeaders,
470
478
  body: JSON.stringify({
471
479
  json: {
472
480
  ability,
@@ -498,7 +506,7 @@ async function execute(args) {
498
506
  if (Date.now() >= deadline) break
499
507
  const poll = await fetch(`${config.apiBaseUrl}/api/cli.execute`, {
500
508
  method: 'POST',
501
- headers: { ...buildRequestHeaders(config), 'x-yizhuan-trigger': 'polling' },
509
+ headers: { ...requestHeaders, 'x-yizhuan-trigger': 'polling' },
502
510
  body: JSON.stringify({
503
511
  json: { ability: 'detection', params: { mode: 'get', id: result.data.id } }
504
512
  })
@@ -534,7 +542,7 @@ function renderQuery(payload, format) {
534
542
  }
535
543
 
536
544
  async function query(args) {
537
- assertCommandArgs(args, 'query', new Set(['json', 'jsonFile', 'params', 'format']))
545
+ assertCommandArgs(args, 'query', new Set(['json', 'jsonFile', 'params', 'format', ...USAGE_FLAGS]))
538
546
  const jsonText = args.jsonFile
539
547
  ? fs.readFileSync(path.resolve(String(args.jsonFile)), 'utf8')
540
548
  : args.json === '-'
@@ -545,7 +553,7 @@ async function query(args) {
545
553
  const config = readConfig()
546
554
  const response = await fetch(`${config.apiBaseUrl}/api/agent.query`, {
547
555
  method: 'POST',
548
- headers: buildRequestHeaders(config),
556
+ headers: { ...buildRequestHeaders(config), ...usageHeaders(args) },
549
557
  body: JSON.stringify({ json: request })
550
558
  })
551
559
  const text = await response.text()
@@ -610,6 +618,12 @@ async function main() {
610
618
  await execute(args)
611
619
  return
612
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
+ }
613
627
  if (command === 'query') {
614
628
  await query(args)
615
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
+ }