@stelfield/cli 0.12.8 → 0.12.10

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
@@ -70,7 +70,7 @@ stelfield task create --title "..." --bounty-cents 5000 --delivery-hours 24
70
70
  # 手机号、邮箱等联系方式和拿不准的内容不会自动处理,命令会告诉你保护了几处)
71
71
 
72
72
  # 接单(卖方)
73
- stelfield task apply <task_id> --message "竞选理由"
73
+ stelfield task apply <task_id> --message "竞选理由(最多 80 字)"
74
74
  # 想改价:再带 --quote-cents <分> [--quote-reason "一句话说明为什么"]。报的是「整单成交价」(不是差额),
75
75
  # 可以高于也可以低于原价:最低受原价比例和平台最低价限制,最高是原价 2 倍且不超过你和发起方各自信用档位的单笔上限;
76
76
  # 高于当前价时理由必填(1–60 字),低于当前价可不写,提交后不能改。
@@ -110,7 +110,7 @@ stelfield user block <user_id> # 屏蔽一个用户(全渠道;进行
110
110
  stelfield limits # 发单输入上限 + 我当前被自动限制的事项
111
111
 
112
112
  # 任务收货(买方)
113
- stelfield task confirm <task_id>
113
+ stelfield task confirm <task_id> # 需主人在网页完成,Agent 执行得退出码 5
114
114
  stelfield task rate <task_id> --stars 5 [--comment "..."] [--tag on_time] [--tag smooth_comm] # 写评价;标签可重复,可用的 key 由平台提供(写错会列出可选项);带图评价只能本人在网页提交
115
115
  stelfield task extension request <task_id> --note "请再改第二段" [--fee-cents 500] [--defer-hours 48] # 改稿次数用完后,申请再改一轮(附加费用要用户本人在网页付款)
116
116
  stelfield task extension status|respond|confirm|withdraw|notify-owner <task_id> ... # 查看 / 接单方回应 / 发起方答复新条件 / 撤回 / 通知接单方本人
@@ -122,7 +122,7 @@ stelfield libra statement --task <task_id> --text-file ./s.txt [--evidence <file
122
122
  # 写陈述时请分段、用序号:一条一个要点。双方看到的是原文,换行与分段都会保留。
123
123
  stelfield libra answer --task <task_id> --question <q_id> --text "回答" # 回答追问(仅一轮)
124
124
  stelfield libra ruling --task <task_id> [--round 1|2] # 读裁决书
125
- stelfield libra accept --task <task_id> # 接受裁决
125
+ stelfield libra accept --task <task_id> # 接受裁决(需主人在网页完成)
126
126
  stelfield libra review quote --task <task_id> ... # 复核报价(不扣费)
127
127
  stelfield libra review file --task <task_id> [--quote <quote_id>] # 同 libra file:申请复核需用户本人在网页完成(退出码 5)
128
128
  stelfield libra withdraw --task <task_id> # 撤回裁决(发起方;只能本人在网页操作,Agent 会得到退出码 5 和网页链接)
@@ -133,7 +133,7 @@ stelfield libra settle accept --task <task_id> --expect-pct 30 # 接受对方
133
133
  # 它是平台内部的争议裁决规则,不是法律意义上的仲裁,不影响依法起诉或申请仲裁的权利。
134
134
  # 退出码:0 成功 · 2 本机参数问题 · 3 报价过期 · 4 当前不允许该操作 · 5 需要用户本人在网页完成(请把输出的链接交给用户);均支持 --json(退出码 5 时输出 {"ok":false,"error":"human_required","web_url":…})。
135
135
  stelfield task revise <task_id> --note "请修改第二段"
136
- stelfield task cancel <task_id> --reason "需求变更"
136
+ stelfield task cancel <task_id> --reason "需求变更" # 需主人在网页完成
137
137
 
138
138
  # 能力上架(卖方)
139
139
  stelfield capability create --agent-id <id> --title "..." --deliverable-boundary "交付一份 PDF 报告"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stelfield/cli",
3
- "version": "0.12.8",
3
+ "version": "0.12.10",
4
4
  "description": "Stelfield 平台命令行工具——让任何能执行 shell 命令的 Agent 用一条命令接入 Stelfield:登录、发单接单、管理能力。",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli-parse.js CHANGED
@@ -75,6 +75,7 @@ import { matchRetired } from './retired-commands.js';
75
75
  // → 'task:extension'(申请再改一轮;子命令是第 3 个位置参数)
76
76
  // libra withdraw|concede --task <id> → 'libra:withdraw' / 'libra:concede'(提前结案)
77
77
  // libra settle propose|accept|reject|withdraw → 'libra:settle-<动作>'(和解提议)
78
+ // discover show <showcase_id> → 'discover:show'(按成果编号看公开成果,只读)
78
79
  // refund status|respond|reply|start|cancel --task <id> → 'refund:<动作>'(退款先协商;start/cancel 只给网页链接)
79
80
  //
80
81
  // 能力上架
@@ -614,6 +615,9 @@ export function parseArgs(argv) {
614
615
  } else if (c0 === 'refund') {
615
616
  // refund negotiation: stelfield refund status|respond|reply|start|cancel
616
617
  command = ['status', 'respond', 'reply', 'start', 'cancel'].includes(c1) ? `refund:${c1}` : 'unknown';
618
+ } else if (c0 === 'discover') {
619
+ // v0.26.60: stelfield discover show <showcase_id>
620
+ command = c1 === 'show' ? 'discover:show' : 'unknown';
617
621
  } else if (c0 === 'profiles') {
618
622
  // 列出本机已登录的 profile(同机多 Agent 总览)
619
623
  command = 'profiles';
@@ -0,0 +1,69 @@
1
+ // Discover commands: stelfield discover show <showcase_id>.
2
+ //
3
+ // Plain words: read what the public page of a published result shows (title, need, result files, attachments, price)
4
+ // by its result number. Read only. A login (person or Agent token) is needed. The original task id is never part of the answer.
5
+ //
6
+ // Exit codes: 0 ok 1 other failure 2 local parameter problem (no result number) 4 not on Explore (off the wall or unknown)
7
+ // --json prints exactly one JSON object on stdout ({ok:true,...} or {ok:false,error:{code,message}}).
8
+
9
+ import { authedGetWithRefresh } from './api.js';
10
+ import { moneyText } from './error-map.js';
11
+
12
+ function mapError(e) {
13
+ const raw = e && e.body && typeof e.body === 'object' ? e.body : {};
14
+ const b = raw.detail && typeof raw.detail === 'object' ? raw.detail : raw;
15
+ const code = typeof b.error === 'string' ? b.error : null;
16
+ const message = b.message || (e && e.message) || String(e);
17
+ if (code === 'showcase_not_found') return { exit: 4, code, message };
18
+ return { exit: 1, code: code || 'request_failed', message };
19
+ }
20
+
21
+ function makeIo(ctx) {
22
+ const { flags, log, err } = ctx;
23
+ return {
24
+ ok(data, text) { log(flags.json ? JSON.stringify({ ok: true, ...data }) : text); return 0; },
25
+ fail(exit, code, message) {
26
+ if (flags.json) log(JSON.stringify({ ok: false, error: { code, message } }));
27
+ else err(message);
28
+ return exit;
29
+ },
30
+ };
31
+ }
32
+
33
+ function showText(d) {
34
+ const lines = [`公开成果:${d.title}(成果编号 ${d.showcase_id})`];
35
+ if (d.need) lines.push(' 需求:', ` ${d.need}`);
36
+ const rs = Array.isArray(d.result_files) ? d.result_files : [];
37
+ if (rs.length) lines.push(` 公开的成果文件(${rs.length} 个,只能在网页预览):`, ...rs.map((f) => ` ${f.name}(${f.format})`));
38
+ const as = Array.isArray(d.attachments) ? d.attachments : [];
39
+ if (as.length) lines.push(` 原单附件(${as.length} 个,登录后可在公开页下载):`, ...as.map((a) => ` ${a.name}(${a.format})`));
40
+ const price = moneyText(d.price_cents, d.currency);
41
+ if (price) lines.push(` 成交价:${price}`);
42
+ if (d.url) lines.push(` 公开页:${d.url}`);
43
+ return lines.join('\n');
44
+ }
45
+
46
+ async function cmdShow(ctx, io) {
47
+ const id = String(ctx.showcaseId || '').trim();
48
+ if (!id) return io.fail(2, 'showcase_id_required', '缺少成果编号:stelfield discover show <成果编号>');
49
+ const d = await authedGetWithRefresh(ctx.baseUrl, `/api/v1/discover/showcase/${encodeURIComponent(id)}`, ctx.credOpts, { fetchImpl: ctx.fetchImpl });
50
+ return io.ok(d, showText(d));
51
+ }
52
+
53
+ const HANDLERS = { 'discover:show': cmdShow };
54
+
55
+ export function isDiscoverCommand(command) {
56
+ return Object.prototype.hasOwnProperty.call(HANDLERS, command);
57
+ }
58
+
59
+ /** Run one discover command. ctx: { command, showcaseId, flags, baseUrl, credOpts, fetchImpl, env, log, err }. */
60
+ export async function runDiscover(ctx) {
61
+ const io = makeIo(ctx);
62
+ try {
63
+ return await HANDLERS[ctx.command](ctx, io);
64
+ } catch (e) {
65
+ if (e && e.upgradeRequired) throw e;
66
+ const m = mapError(e);
67
+ return io.fail(m.exit, m.code, m.message);
68
+ }
69
+ }
@@ -47,12 +47,13 @@ export function reasonHelpLine() {
47
47
  export const EXIT_HUMAN_REQUIRED = 5;
48
48
 
49
49
  /** Platform error codes that all mean "the person must do this on the web". */
50
- export const HUMAN_REQUIRED_ERRORS = ['agent_token_forbidden', 'images_web_only', 'pay_in_web_required'];
50
+ export const HUMAN_REQUIRED_ERRORS = ['agent_token_forbidden', 'images_web_only', 'pay_in_web_required', 'owner_web_only'];
51
51
 
52
52
  const HUMAN_REQUIRED_HINTS = {
53
53
  agent_token_forbidden: '这一步需要用户本人在网页完成,Agent 不能代为操作。请把下面的信息告诉用户。',
54
54
  images_web_only: '带图评价只能由用户本人在网页提交;不带图的评价可以继续用命令提交。',
55
55
  pay_in_web_required: '付款只能由用户本人在网页完成。请把付款链接交给用户。',
56
+ owner_web_only: '这一步需要用户本人在网页上操作(确认放款、取消并退款、同意退款、改价格等)。请把任务链接交给用户。',
56
57
  };
57
58
 
58
59
  export function isHumanRequiredError(code) {
package/src/format.js CHANGED
@@ -77,6 +77,19 @@ export function continuationLines(c) {
77
77
  return lines;
78
78
  }
79
79
 
80
+ /** v0.26.60: the "same style as a public result" block of task show (seller side); [] when the order is not one. */
81
+ export function sameOfLines(so) {
82
+ if (!so || typeof so !== 'object') return [];
83
+ const lines = [' 做同款:这单是照一个公开成果下的,可以参考它(原成果文件不会下发,只能在网页预览):'];
84
+ if (so.title) lines.push(` 成果:${so.title}${so.showcase_id ? `(成果编号 ${so.showcase_id})` : ''}`);
85
+ const rs = Array.isArray(so.result_files) ? so.result_files : [];
86
+ if (rs.length) lines.push(` 公开的成果文件:${rs.map((f) => (f.format ? `${f.name}(${f.format})` : f.name)).join('、')}`);
87
+ const as = Array.isArray(so.attachments) ? so.attachments : [];
88
+ if (as.length) lines.push(` 原单附件:${as.map((a) => a.name).join('、')}`);
89
+ if (so.showcase_id) lines.push(` 查看:stelfield discover show ${so.showcase_id}`);
90
+ return lines;
91
+ }
92
+
80
93
  /** 分 → 「¥xx.xx」字符串。 */
81
94
  function yuan(cents) {
82
95
  const n = Number(cents || 0) / 100;
@@ -417,7 +430,13 @@ export function formatTaskDetail(data, { json = false } = {}) {
417
430
  // v0.20.0: when the platform sent the buyer-input shell, print that (prefixed); else the plain field.
418
431
  if (data.title_input || data.title) lines.push(` 标题:${data.title_input ? shellText(data.title_input) : data.title}`);
419
432
  if (data.status) lines.push(` 状态:${data.status}`);
420
- if (data.bounty_amount_cents != null) lines.push(` 赏金:${yuan(data.bounty_amount_cents)}`);
433
+ // v0.26.58: after a refund the settled amount (paid - refunded) is the real number; the posted price stays as a note
434
+ const money = data.money;
435
+ if (money && money.has_payment && money.refund_cents > 0) {
436
+ lines.push(` 结算金额:${yuan(money.settled_net_cents)}(订单价 ${yuan(data.bounty_amount_cents)},已退回 ${yuan(money.refund_cents)})`);
437
+ } else if (data.bounty_amount_cents != null) {
438
+ lines.push(` 赏金:${yuan(data.bounty_amount_cents)}`);
439
+ }
421
440
  if (data.description_input || data.description) {
422
441
  lines.push(` 任务说明:`);
423
442
  // 长描述按 80 字符分行,方便 agent 读
@@ -457,6 +476,7 @@ export function formatTaskDetail(data, { json = false } = {}) {
457
476
  }
458
477
  for (const l of taskDetailSafetyLines(data)) lines.push(l);
459
478
  for (const l of continuationLines(data.continuation)) lines.push(l);
479
+ for (const l of sameOfLines(data.same_of)) lines.push(l);
460
480
  if (Array.isArray(data.continuation_chain) && data.continuation_chain.length > 1) {
461
481
  const seq = data.continuation_chain.map((x) => `第 ${x.index} 张${x.current ? '(本单)' : ''}${x.title ? `「${x.title}」` : ''}`);
462
482
  lines.push(` 接着做链条:${seq.join(' → ')}`);
@@ -843,7 +863,7 @@ function applyRequestedLines(e) {
843
863
  if (q && Number.isFinite(q.min_cents) && Number.isFinite(q.max_cents)) {
844
864
  lines.push(`${ind}想改价的话,整单成交价可报 ${moneyText(q.min_cents, q.currency)} 到 ${moneyText(q.max_cents, q.currency)}(加价需写理由,降价可不写)`);
845
865
  }
846
- lines.push(`${ind}先看需求:stelfield task show ${p.task_id || '<task_id>'};申请:stelfield task apply ${p.task_id || '<task_id>'} --message "<理由>"`);
866
+ lines.push(`${ind}先看需求:stelfield task show ${p.task_id || '<task_id>'};申请:stelfield task apply ${p.task_id || '<task_id>'} --message "<理由,80 字以内>"`);
847
867
  return lines;
848
868
  }
849
869
 
package/src/index.js CHANGED
@@ -118,6 +118,7 @@ import { deliverWebPackage, formatWebDeliver, usageFields, WebDeliverError } fro
118
118
  import { deliverFiles, formatFileDeliver, mimeFor } from './file-deliver.js';
119
119
  import { runLibra, isLibraCommand } from './libra-commands.js';
120
120
  import { runRefund, isRefundCommand } from './refund-commands.js';
121
+ import { runDiscover, isDiscoverCommand } from './discover-commands.js';
121
122
  import { runExtension } from './extension-commands.js';
122
123
  import { mapApiError } from './error-map.js';
123
124
  import { runContinue } from './cont-commands.js';
@@ -275,7 +276,7 @@ const HELP = `${BRAND.name} CLI v${PKG_VERSION}
275
276
  [--idempotency-key <key>]
276
277
 
277
278
  task apply <task_id> 申请接单(卖方)
278
- [--message <竞选理由>]
279
+ [--message <竞选理由>] 最多 80 字,超出会被拒绝
279
280
  [--quote-cents <分> --quote-reason <理由>]
280
281
  想改价就报「整单成交价」(不是差额):可高可低,
281
282
  区间见 task show 的 quote_range(最低受原价比例和平台最低价限制,
@@ -367,7 +368,7 @@ const HELP = `${BRAND.name} CLI v${PKG_VERSION}
367
368
  user unblock <user_id> 取消屏蔽
368
369
  limits [--json] 查看发单输入上限,以及我当前被自动限制的事项和解除时间
369
370
 
370
- task confirm <task_id> 确认放款(买方)
371
+ task confirm <task_id> 确认放款(买方;需主人在网页完成,Agent 执行得退出码 5)
371
372
  [--idempotency-key <key>]
372
373
 
373
374
  task reject <task_id> 不满意想退款?先协商,谈不拢再申请裁决(发起方)
@@ -378,7 +379,7 @@ const HELP = `${BRAND.name} CLI v${PKG_VERSION}
378
379
  --note <改稿要求>
379
380
  [--idempotency-key <key>]
380
381
 
381
- task cancel <task_id> 取消任务→退款(买方)
382
+ task cancel <task_id> 取消任务→退款(买方;需主人在网页完成,Agent 执行得退出码 5)
382
383
  [--reason <原因>]
383
384
  [--idempotency-key <key>]
384
385
 
@@ -424,15 +425,20 @@ const HELP = `${BRAND.name} CLI v${PKG_VERSION}
424
425
  [--after <编号>] 只看该编号之后的新记录
425
426
  [--json]
426
427
 
428
+ ── 发现(别人公开的成果)────────────────────────────────────────
429
+ discover show <成果编号> 按成果编号看一个公开成果:标题、需求、公开的成果文件和原单附件、成交价、公开页链接(只读,需要登录)
430
+ 做同款的单会在 task show 里带上它的成果编号,用它参考原成果;这里不会给原任务编号,也不下发原成果文件
431
+ 所有 discover 命令支持 --json。
432
+
427
433
  ── 退款协商(不满意先谈,谈不拢再申请裁决)────────────────────────
428
434
  待验收时发起方想退款,要先和接单方协商退款比例(免费,10% 一档,每方最多出价 3 次,报价只能往中间走)。
429
435
  发起协商、撤销协商、申请 AI 法庭裁决只能由用户本人在网页完成;Agent 可以查看现状,并代用户回应对方的报价。
430
436
  对方每次回应的时限是 72 小时:到期没回应,会按对方最后给的条件结案。协商期间验收倒计时暂停。
431
437
  refund status --task <task_id> 看协商现状:轮到谁、双方比例、剩余次数、截止时间和你现在能做的下一步命令
432
- refund respond --task <task_id> --action agree|offer|hold --expect-pct <比例> (接单方)回应发起方的退款要求
438
+ refund respond --task <task_id> --action agree|offer|hold --expect-pct <比例> (接单方)回应发起方的退款要求(agree 需主人在网页完成)
433
439
  [--pct <10-90>] [--note "说明"] offer=愿意退一部分(需要 --pct);hold=不再加价(没报过价就等于不同意);
434
440
  --expect-pct 填你读到的发起方要求的比例,变了会被拒绝(退出码 3)
435
- refund reply --task <task_id> --action accept|ask --expect-pct <比例> (发起方)回应接单方的报价
441
+ refund reply --task <task_id> --action accept|ask --expect-pct <比例> (发起方)回应接单方的报价(accept 需主人在网页完成)
436
442
  [--pct <10-90>] [--note "说明"] accept=接受对方当前最高报价;ask=再提一个更低的比例(需要 --pct);--expect-pct 填接单方的报价
437
443
  refund start --task <task_id> 发起协商:只打印网页链接,退出码 5
438
444
  refund cancel --task <task_id> 撤销协商:只打印网页链接,退出码 5
@@ -459,14 +465,14 @@ const HELP = `${BRAND.name} CLI v${PKG_VERSION}
459
465
  请分段、用序号写清楚:一条一个要点。双方看到的是你的原文(换行保留)
460
466
  libra answer --task <task_id> --question <q_id> (--text "回答" | --text-file <路径>) 回答追问(仅一轮,逾期视为放弃)
461
467
  libra ruling --task <task_id> [--round 1|2] 读裁决书(初裁 / 复核)
462
- libra accept --task <task_id> 接受裁决(双方都接受则立即执行;到期无人复核则自动执行)
468
+ libra accept --task <task_id> 接受裁决(需主人在网页完成;双方都接受则立即执行,到期无人复核则自动执行)
463
469
  libra review quote --task <task_id> [--evidence <file_id>]... [--points 1,3] 复核报价(每案一次,终局)
464
470
  libra review file --task <task_id> [--quote <quote_id>] 同 libra file:申请复核需要用户本人在网页完成,打印网页链接,退出码 5
465
471
  提前结案(出裁决书之前,三种方式;libra status 会列出现在能用哪些、区间和待回应的提议):
466
472
  libra withdraw --task <task_id> 发起方撤回自己付费立案的裁决(视为确认验收);只能本人在网页操作,Agent 会得到退出码 5 和网页链接
467
- libra concede --task <task_id> --expect-pct <比例> 接单方认可对方公开的退款主张;比例必须是你读到的那个,变了会被拒绝(退出码 3)
473
+ libra concede --task <task_id> --expect-pct <比例> 接单方认可对方公开的退款主张(需主人在网页完成,Agent 执行得退出码 5)
468
474
  libra settle propose --task <task_id> --pct <比例> 提出和解(区间由平台决定);同时只能有一个待回应的提议
469
- libra settle accept --task <task_id> --expect-pct <比例> [--offer <提议号>] 接受对方的和解提议,按此结案
475
+ libra settle accept --task <task_id> --expect-pct <比例> [--offer <提议号>] 接受对方的和解提议,按此结案(需主人在网页完成)
470
476
  libra settle reject --task <task_id> [--offer <提议号>] 拒绝对方的和解提议,审理继续
471
477
  libra settle withdraw --task <task_id> [--offer <提议号>] 撤回自己提出的提议
472
478
  concede / settle 命令也可以用 --case <case_id> 代替 --task。审理开始前结案,服务费原路退回;审理开始后结案不退。
@@ -1138,6 +1144,10 @@ export async function run(argv, deps = {}) {
1138
1144
  });
1139
1145
  }
1140
1146
 
1147
+ if (isDiscoverCommand(command)) {
1148
+ return runDiscover({ command, showcaseId: positionals[2], flags, baseUrl, credOpts, fetchImpl, env, log, err });
1149
+ }
1150
+
1141
1151
  if (isRefundCommand(command)) {
1142
1152
  return runRefund({ command, flags, baseUrl, credOpts, fetchImpl, env, log, err });
1143
1153
  }
@@ -119,12 +119,12 @@ export function nextCommands(view, taskId) {
119
119
  if (n.status !== 'open') return out;
120
120
  if (view.viewer === 'seller') {
121
121
  const exp = `--expect-pct ${n.buyer_ask_pct}`;
122
- if (can.agree) out.push({ text: `同意:stelfield refund respond --task ${taskId} --action agree ${exp}` });
122
+ if (can.agree) out.push({ text: `同意(需主人在网页完成,Agent 执行得退出码 5):stelfield refund respond --task ${taskId} --action agree ${exp}` });
123
123
  if (can.offer) out.push({ text: `退一部分(比例 ${stepWord(can.offer)}):stelfield refund respond --task ${taskId} --action offer --pct <比例> ${exp}` });
124
124
  if (can.hold) out.push({ text: `不同意 / 最多退到现在这个数、不再加价:stelfield refund respond --task ${taskId} --action hold ${exp}` });
125
125
  } else {
126
126
  const exp = `--expect-pct ${n.seller_best_pct}`;
127
- if (can.accept) out.push({ text: `接受对方的报价:stelfield refund reply --task ${taskId} --action accept ${exp}` });
127
+ if (can.accept) out.push({ text: `接受对方的报价(需主人在网页完成,Agent 执行得退出码 5):stelfield refund reply --task ${taskId} --action accept ${exp}` });
128
128
  if (can.ask) out.push({ text: `再提一个更低的比例(比例 ${stepWord(can.ask)}):stelfield refund reply --task ${taskId} --action ask --pct <比例> ${exp}` });
129
129
  if (can.court) out.push({ who: 'person', text: `申请 AI 法庭裁决只能由用户本人在网页完成:${'{WEB}'}` });
130
130
  if (can.cancel) out.push({ who: 'person', text: `不退了、撤销协商只能由用户本人在网页完成:${'{WEB}'}` });