@stelfield/cli 0.12.9 → 0.12.11

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": "@stelfield/cli",
3
- "version": "0.12.9",
3
+ "version": "0.12.11",
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
+ }
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 读
@@ -437,6 +456,11 @@ export function formatTaskDetail(data, { json = false } = {}) {
437
456
  } else if (data.my_application) {
438
457
  lines.push(` 我的申请:${data.my_application.status === 'pending' ? '已报名·等选人' : data.my_application.status}`);
439
458
  }
459
+ // dispatch window still collecting sign-ups: the list is empty on purpose, say so (Echo #187)
460
+ if (data.dispatch && data.dispatch.phase === 'collecting') {
461
+ const n = Number(data.dispatch.reported_count) || 0;
462
+ lines.push(` 📋 报名窗口收集中${n > 0 ? `(已有 ${n} 人报名)` : ''}:现在看不到名单,窗口结束后公布前 5 名`);
463
+ }
440
464
  if (data.tags && data.tags.length > 0) lines.push(` 标签:${data.tags.join(', ')}`);
441
465
  // Prd 22: the price window this seller may quote (whole-order price; lower or higher) and the owner-only "looked, did not apply" record
442
466
  const qr = data.quote_range;
@@ -457,6 +481,7 @@ export function formatTaskDetail(data, { json = false } = {}) {
457
481
  }
458
482
  for (const l of taskDetailSafetyLines(data)) lines.push(l);
459
483
  for (const l of continuationLines(data.continuation)) lines.push(l);
484
+ for (const l of sameOfLines(data.same_of)) lines.push(l);
460
485
  if (Array.isArray(data.continuation_chain) && data.continuation_chain.length > 1) {
461
486
  const seq = data.continuation_chain.map((x) => `第 ${x.index} 张${x.current ? '(本单)' : ''}${x.title ? `「${x.title}」` : ''}`);
462
487
  lines.push(` 接着做链条:${seq.join(' → ')}`);
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';
@@ -424,6 +425,11 @@ 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 可以查看现状,并代用户回应对方的报价。
@@ -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
  }