@manturhub/cli 0.9.5 → 0.9.7

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
@@ -31,7 +31,7 @@ manturhub quote 电商文案生成
31
31
  manturhub run 电商文案生成 --json '{"product_info":"便携榨汁杯,USB 充电,300ml","scene":"product_title","tone":"lively"}'
32
32
  ```
33
33
 
34
- CLI 会在付费调用前按算子实时 schema 校验未知字段、必填项、类型和枚举,校验失败不会发起 invoke。校验通过后会显示本次预计消耗和计费依据,获得确认才调用;完成后显示实际消耗和退款。批量参数也按整批请求试算。
34
+ CLI 会在付费调用前按算子实时 schema 校验未知字段、必填项、类型、枚举和长度/数值范围,校验失败不会发起 invoke。校验通过后会显示本次预计消耗和计费依据,获得确认才调用;完成后在最终 JSON 的 `_billing` 返回实际消耗和退款,并同步显示到终端。批量参数按整批请求试算;多次调用应逐笔汇总 `charged_dumplings` 和 `refunded_dumplings`,不得自行估算。
35
35
 
36
36
  面向用户的列表、详情、报价、确认和结算统一显示中文算子名,也可直接用中文名调用。英文算子 ID 仅保留在 `--json` 机器输出和内部 API 请求中,供 Agent 与脚本稳定使用。
37
37
 
@@ -44,7 +44,7 @@ CLI 会在付费调用前按算子实时 schema 校验未知字段、必填项
44
44
  | `manturhub ls [--cat image\|video\|audio\|text\|data] [--json]` | 实时列出上线算子 |
45
45
  | `manturhub describe <中文算子名> [--json]` | 查看精确入参与异步属性 |
46
46
  | `manturhub quote <中文算子名> [--json]` | 查询 Java API 返回的实时计费公式 |
47
- | `manturhub run <中文算子名> --json '{}'` | 调用算子;异步任务默认轮询到终态 |
47
+ | `manturhub run <中文算子名> --json '{}' [--confirm <quote_id>] [--no-wait]` | 调用算子;非交互确认传报价 ID,异步任务默认轮询到终态 |
48
48
  | `manturhub run <中文算子名> --json-file params.json` | 从文件读取参数,适合长提示词和自动化 |
49
49
  | `manturhub upload <本地文件>` | 流式上传图片、音频或视频并输出公网 URL |
50
50
  | `manturhub status <poll_url>` | 查询 `run --no-wait` 返回的异步任务 |
package/bin/cli.js CHANGED
@@ -99,14 +99,16 @@ function catLabel(cat) {
99
99
  // Human commands accept either the stable operator ID or its Chinese display name.
100
100
  // Machine-facing --json responses keep IDs unchanged for automation compatibility.
101
101
  async function resolveOperator(ref) {
102
- const direct = await apiFetch(`/api/v1/operators/${encodeURIComponent(ref)}`, { auth: "optional" });
103
- if (direct.ok) return direct.json.operator || direct.json;
104
- if (direct.status !== 404) throw new Error(`算子信息获取失败(HTTP ${direct.status})`);
102
+ if (String(ref).startsWith("op.")) {
103
+ const direct = await apiFetch(`/api/v1/operators/${encodeURIComponent(ref)}`, { auth: "optional" });
104
+ if (direct.ok) return direct.json.operator || direct.json;
105
+ if (direct.status !== 404) throw new Error(`算子信息获取失败(HTTP ${direct.status})`);
106
+ }
105
107
 
106
108
  const listed = await apiFetch("/api/v1/operators?status=online", { auth: "optional" });
107
109
  if (!listed.ok) throw new Error(`算子列表获取失败(HTTP ${listed.status})`);
108
110
  const operators = listed.json.operators || listed.json || [];
109
- const matches = operators.filter((item) => item?.name === ref);
111
+ const matches = operators.filter((item) => item?.name === ref || item?.id === ref);
110
112
  if (matches.length === 0) throw new Error(`未找到算子“${ref}”`);
111
113
  if (matches.length > 1) throw new Error(`算子名称“${ref}”不唯一,请联系平台处理`);
112
114
 
@@ -126,6 +128,8 @@ const HELP = `manturhub — ManturHub 算子广场 CLI v${VERSION}
126
128
  manturhub quote <中文算子名> 查询实时计费公式(不要使用 Skill 内的历史价格)
127
129
  manturhub run <中文算子名> --json '{}' 试算并确认费用后调用(异步算子自动轮询到出结果)
128
130
  manturhub run <中文算子名> --json-file x.json 从文件读参数(prompt 来自配方/用户时更安全)
131
+ manturhub run <中文算子名> ... --confirm <quote_id> 使用非交互试算返回的报价确认 ID 调用
132
+ manturhub run <中文算子名> ... --no-wait 提交异步任务后立即返回 poll_url,不自动轮询
129
133
  manturhub upload <本地文件> 上传图片/音频/视频 → 公网 URL(喂算子前先转换本地文件)
130
134
  manturhub status <poll_url> 查异步任务状态(配合 run --no-wait)
131
135
  manturhub balance 查询馒头余额
@@ -436,6 +440,7 @@ async function main() {
436
440
  `⏳ 异步任务 ${r.json.job_id || ""} 已提交,轮询结果中(预计 ${r.json.estimated_seconds || "?"}s;加 --no-wait 可只拿 job_id)…\n`
437
441
  );
438
442
  const final = await pollJob(pollUrl, {
443
+ initialBilling: r.json._billing,
439
444
  onTick: (j) =>
440
445
  process.stderr.write(
441
446
  ` ${j.status || "?"}${j.elapsed_ms ? " " + Math.round(j.elapsed_ms / 1000) + "s" : ""}\n`
@@ -517,6 +522,7 @@ async function main() {
517
522
  }
518
523
  const r = await apiFetch(pu);
519
524
  console.log(JSON.stringify(r.json, null, 2));
525
+ printBillingResult(r.json);
520
526
  if (!r.ok) process.exit(1);
521
527
  break;
522
528
  }
package/lib/api.js CHANGED
@@ -65,16 +65,31 @@ export async function apiFetch(
65
65
  // 异步算子:轮询 invoke 返回的 poll_url 直到任务出终态(succeeded/failed/…),返回最终 json。
66
66
  // 未知 shape(无 status 字段)按终态处理,直接返回让上层打印。
67
67
  const ACTIVE = new Set(["queued", "running", "pending", "processing", "in_progress"]);
68
- export async function pollJob(pollUrl, { intervalMs = 8000, maxMs = 1200000, onTick } = {}) {
68
+ export function retainBilling(result, fallbackBilling) {
69
+ if (!fallbackBilling || !result || typeof result !== "object" || Array.isArray(result)) return result;
70
+ return result._billing ? result : { ...result, _billing: fallbackBilling };
71
+ }
72
+
73
+ export async function pollJob(
74
+ pollUrl,
75
+ { intervalMs = 8000, maxMs = 1200000, onTick, initialBilling } = {}
76
+ ) {
69
77
  const start = Date.now();
70
78
  let last = null;
79
+ let lastBilling = initialBilling || null;
71
80
  while (Date.now() - start < maxMs) {
72
81
  const r = await apiFetch(pollUrl, { timeoutMs: 30000 });
73
82
  last = r.json;
83
+ if (r.json?._billing) lastBilling = r.json._billing;
74
84
  const s = r.json && r.json.status;
75
85
  if (onTick) onTick(r.json);
76
- if (!r.ok || !s || !ACTIVE.has(s)) return r.json; // 终态(或报错/未知 shape)→ 返回
86
+ if (!r.ok || !s || !ACTIVE.has(s)) {
87
+ return retainBilling(r.json, lastBilling); // 终态(或报错/未知 shape)→ 返回
88
+ }
77
89
  await new Promise((res) => setTimeout(res, intervalMs));
78
90
  }
79
- return { ...(last || {}), status: (last && last.status) || "timeout", _timeout: true };
91
+ return retainBilling(
92
+ { ...(last || {}), status: (last && last.status) || "timeout", _timeout: true },
93
+ lastBilling
94
+ );
80
95
  }
package/lib/params.js CHANGED
@@ -83,6 +83,27 @@ export function validateParams(body, schema, { coerceStrings = false } = {}) {
83
83
  `参数 ${name} 类型错误:需要 ${expectedTypes.join(" 或 ")},收到 ${typeName(checked)}`
84
84
  );
85
85
  }
86
+ if (typeof checked === "string") {
87
+ const length = Array.from(checked).length;
88
+ const minLength = field.minLength == null ? Number.NaN : Number(field.minLength);
89
+ const maxLength = field.maxLength == null ? Number.NaN : Number(field.maxLength);
90
+ if (Number.isFinite(minLength) && length < minLength) {
91
+ throw new Error(`参数 ${name} 长度不能少于 ${minLength} 个字符`);
92
+ }
93
+ if (Number.isFinite(maxLength) && length > maxLength) {
94
+ throw new Error(`参数 ${name} 长度不能超过 ${maxLength} 个字符`);
95
+ }
96
+ }
97
+ if (typeof checked === "number") {
98
+ const minimum = field.minimum == null ? Number.NaN : Number(field.minimum);
99
+ const maximum = field.maximum == null ? Number.NaN : Number(field.maximum);
100
+ if (Number.isFinite(minimum) && checked < minimum) {
101
+ throw new Error(`参数 ${name} 不能小于 ${minimum}`);
102
+ }
103
+ if (Number.isFinite(maximum) && checked > maximum) {
104
+ throw new Error(`参数 ${name} 不能大于 ${maximum}`);
105
+ }
106
+ }
86
107
  if (Array.isArray(field.enum) && !field.enum.includes(checked)) {
87
108
  throw new Error(`参数 ${name} 只能是: ${field.enum.join(" | ")}`);
88
109
  }
package/lib/setup.js CHANGED
@@ -17,6 +17,7 @@ function guideBlock() {
17
17
  - 列出可用算子:\`manturhub ls\`(按类:\`manturhub ls --cat image|video|audio|text|data\`)
18
18
  - 查实时价格:\`manturhub quote <算子ID>\`(不要在 Skill / 文档里写死价格)
19
19
  - 调用算子:优先把参数写入 JSON 文件,再运行 \`manturhub run <算子ID> --json-file params.json\`;简短可信参数也可用 \`--json '{...}'\`(异步算子会轮询到结果,**别重复调用以免重复扣费**)
20
+ - 统计费用:只读取每次最终 JSON 的 \`_billing.charged_dumplings\` / \`refunded_dumplings\`,多次调用逐笔求和;禁止自行估算
20
21
  - 上传本地文件:\`manturhub upload <文件>\` → 公网 URL(算子只接受公网 URL,本地路径非法)
21
22
  - 查余额:\`manturhub balance\`
22
23
  - 查某算子的精确入参字段:\`manturhub describe <算子ID>\`
package/lib/skill.js CHANGED
@@ -31,13 +31,14 @@ description: 调用 ManturHub 算子广场的 AI 能力与行业数据(文生图
31
31
  | \`manturhub suite ls\` | 列出 Agent 套件(多角色团队工作区,如小说→短剧制作团队) |
32
32
  | \`manturhub suite install <slug> [目录]\` | 安装套件为工作目录;Key 仍保存在 CLI 用户级配置中 |
33
33
 
34
- ## 五条铁律(不照做就会踩坑)
34
+ ## 六条铁律(不照做就会踩坑)
35
35
 
36
36
  1. **先 \`manturhub ls\` 摸清能力,再 \`manturhub describe <算子ID>\` 查精确入参** —— CLI 会在付费调用前校验字段、类型和枚举,填参数仍应以实时 schema 为准。
37
37
  2. **本地文件先 \`manturhub upload <文件>\`** 换成公网 URL,再把 URL 填进 run 的参数。算子不接受本地路径,只接受公网 URL。
38
38
  3. **异步算子直接等 \`run\` 返回** —— \`run\` 已自动轮询到 succeeded 才返回最终结果,**不要重复 run(会重复扣费 + 重复出活)**。视频可能要几分钟,耐心等。真想后台拿 \`job_id\` 用 \`--no-wait\`,之后 \`manturhub status <poll_url>\` 查。
39
39
  4. **花钱心里有数** —— 每次 run 前先 \`manturhub quote <算子ID>\`;余额不足时去当前 ManturHub 站点的 \`/pricing\`;异步任务失败平台自动退费。
40
40
  5. **能用平台 Skill 就别手搓流程** —— 完整业务(如「FPV 运镜视频」「短剧改编」)平台常有现成 Skill 模板,优先用,省得自己一步步编排还踩坑。
41
+ 6. **费用只认返回值,禁止估算** —— 每次 run 完成后读取最终 JSON 的 \`_billing.charged_dumplings\` 和 \`_billing.refunded_dumplings\`;多次或批量调用逐笔求和。最终报告必须写真实合计,不能按文件数、时长或余额印象自行估算。
41
42
 
42
43
  ## 典型流程
43
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manturhub/cli",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "ManturHub 算子广场 CLI:通过 REST 发现和调用 AI 算子、浏览配方,并安装 Skill 与 Agent 套件",
5
5
  "type": "module",
6
6
  "bin": {