@lingerai/cli 0.1.0 → 0.1.1
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 +1 -1
- package/src/api.js +58 -0
- package/src/cli-parse.js +40 -3
- package/src/format.js +78 -0
- package/src/index.js +132 -7
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -218,3 +218,61 @@ export async function getComments(baseUrl, token, taskId, opts = {}) {
|
|
|
218
218
|
export async function getCruise(baseUrl, token, opts = {}) {
|
|
219
219
|
return authedGet(baseUrl, '/api/v1/agent/cruise', token, opts);
|
|
220
220
|
}
|
|
221
|
+
|
|
222
|
+
// ============================================================
|
|
223
|
+
// M7 新增:任务信息查询 + 文件能力(薄 REST 包装·零业务逻辑)
|
|
224
|
+
// ============================================================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 读取任务详情(标题 + 正文 + 状态等)。
|
|
228
|
+
*
|
|
229
|
+
* 对应端点:GET /api/v1/tasks/{task_id}
|
|
230
|
+
* 鉴权:Bearer JWT(OAuth access_token 可达·_require_auth 只校签名+exp)
|
|
231
|
+
*
|
|
232
|
+
* @param {string} taskId 任务 ID
|
|
233
|
+
*/
|
|
234
|
+
export async function getTaskDetail(baseUrl, token, taskId, opts = {}) {
|
|
235
|
+
return authedGet(baseUrl, `/api/v1/tasks/${taskId}`, token, opts);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 查看任务附件列表(买方上传的需求原始材料)。
|
|
240
|
+
*
|
|
241
|
+
* 对应端点:GET /api/v1/tasks/{task_id}/attachments
|
|
242
|
+
* 返回:{ attachments: [{ file_id, mime_type, size_bytes, locked, ... }] }
|
|
243
|
+
*
|
|
244
|
+
* @param {string} taskId 任务 ID
|
|
245
|
+
*/
|
|
246
|
+
export async function getTaskFiles(baseUrl, token, taskId, opts = {}) {
|
|
247
|
+
return authedGet(baseUrl, `/api/v1/tasks/${taskId}/attachments`, token, opts);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 拿到某个文件的下载地址(临时 signed URL,5 分钟内有效)。
|
|
252
|
+
*
|
|
253
|
+
* 对应端点:GET /api/v1/downloads/{file_id}/url
|
|
254
|
+
* 返回:{ file_id, signed_get_url }
|
|
255
|
+
* 薄包装铁律:只返回 signed_get_url,HTTP GET 由调用方(agent)自己做。
|
|
256
|
+
*
|
|
257
|
+
* @param {string} fileId 文件 ID
|
|
258
|
+
*/
|
|
259
|
+
export async function getDownloadUrl(baseUrl, token, fileId, opts = {}) {
|
|
260
|
+
return authedGet(baseUrl, `/api/v1/downloads/${fileId}/url`, token, opts);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 申请上传文件(附件或交付物)。
|
|
265
|
+
*
|
|
266
|
+
* 对应端点:POST /api/v1/uploads
|
|
267
|
+
* 返回:{ file_id, signed_put_url, oss_object_id }
|
|
268
|
+
* 薄包装铁律:只返回 signed_put_url + file_id,HTTP PUT 由调用方(agent)自己做。
|
|
269
|
+
*
|
|
270
|
+
* @param {object} uploadData { task_id, kind, mime_type, size_bytes }
|
|
271
|
+
* kind = 'attachment'(需求附件)| 'deliverable'(交付物)
|
|
272
|
+
*/
|
|
273
|
+
export async function requestUpload(baseUrl, token, uploadData, opts = {}) {
|
|
274
|
+
// 上传申请走随机 UUID 幂等键(uploadData 里的字段相同可幂等,后端会返回相同 file_id)
|
|
275
|
+
const { idempotencyKey, fetchImpl } = opts;
|
|
276
|
+
// 把 opts 其余字段传给 authedPost
|
|
277
|
+
return authedPost(baseUrl, '/api/v1/uploads', token, uploadData, idempotencyKey || null, { fetchImpl });
|
|
278
|
+
}
|
package/src/cli-parse.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
// 命令解析:把 argv 解析成 { command, flags, raw, positionals }。纯函数、无副作用。
|
|
2
2
|
//
|
|
3
|
-
// 支持命令(M4 全量 + M5 deliverable-boundary
|
|
3
|
+
// 支持命令(M4 全量 + M5 deliverable-boundary + M7 任务信息/文件能力):
|
|
4
4
|
//
|
|
5
5
|
// 已实现(M3 · 不动)
|
|
6
6
|
// auth login [--no-wait] → 'auth:login'
|
|
7
7
|
// wallet [--json] → 'wallet'
|
|
8
8
|
// hall browse [--json] → 'hall:browse'
|
|
9
9
|
//
|
|
10
|
+
// 任务信息查询(M7 · 只读)
|
|
11
|
+
// task show <task_id> → 'task:show'
|
|
12
|
+
// task files <task_id> → 'task:files'
|
|
13
|
+
// task download <file_id> → 'task:download'
|
|
14
|
+
// task request-upload <task_id> → 'task:request-upload'
|
|
15
|
+
// --kind <attachment|deliverable>
|
|
16
|
+
// --mime-type <mime>
|
|
17
|
+
// --size-bytes <n>
|
|
18
|
+
//
|
|
10
19
|
// 任务生命周期前段(独立 REST 端点)
|
|
11
20
|
// task create ... → 'task:create'
|
|
12
21
|
// task apply <task_id> → 'task:apply'
|
|
@@ -16,7 +25,7 @@
|
|
|
16
25
|
// task accept <task_id> → 'task:accept'
|
|
17
26
|
// task start <task_id> → 'task:start'
|
|
18
27
|
// task heartbeat <task_id> → 'task:heartbeat'
|
|
19
|
-
// task deliver <task_id> → 'task:deliver'
|
|
28
|
+
// task deliver <task_id> → 'task:deliver'(M7 扩:--file-id 与 --result 互斥)
|
|
20
29
|
// task fail <task_id> → 'task:fail'
|
|
21
30
|
// task confirm <task_id> → 'task:confirm'
|
|
22
31
|
// task reject <task_id> → 'task:reject'
|
|
@@ -38,7 +47,11 @@
|
|
|
38
47
|
// --json 机器输出(给 agent 读)
|
|
39
48
|
// --no-wait auth login 时不阻塞等浏览器(只输出授权 URL)
|
|
40
49
|
// --message <str> task apply 竞选理由 / qa post 消息内容
|
|
41
|
-
// --result <str> task deliver
|
|
50
|
+
// --result <str> task deliver 文本交付(与 --file-id 互斥)
|
|
51
|
+
// --file-id <str> task deliver 文件交付(与 --result 互斥)· M7 新增
|
|
52
|
+
// --kind <str> task request-upload 附件类型(attachment|deliverable)· M7 新增
|
|
53
|
+
// --mime-type <str> task request-upload 文件 MIME 类型 · M7 新增
|
|
54
|
+
// --size-bytes <int> task request-upload 文件大小(字节)· M7 新增
|
|
42
55
|
// --progress <int> task heartbeat 进度 0-100
|
|
43
56
|
// --note <str> task heartbeat 进度说明 / task revise 改稿备注
|
|
44
57
|
// --reason <str> task reject / task fail / task cancel 原因
|
|
@@ -71,6 +84,11 @@ export function parseArgs(argv) {
|
|
|
71
84
|
errorMessage: null, // task fail --error-message
|
|
72
85
|
agentId: null, // capability create --agent-id
|
|
73
86
|
deliverableBoundary: null, // capability create --deliverable-boundary(M5)
|
|
87
|
+
// M7 新增 flag
|
|
88
|
+
fileId: null, // task deliver --file-id(文件交付·与 --result 互斥)
|
|
89
|
+
kind: null, // task request-upload --kind(attachment|deliverable)
|
|
90
|
+
mimeType: null, // task request-upload --mime-type
|
|
91
|
+
sizeBytes: null, // task request-upload --size-bytes(整数·字节数)
|
|
74
92
|
description: null, // capability create --description
|
|
75
93
|
type: null, // capability create --type
|
|
76
94
|
category: null, // capability create --category
|
|
@@ -119,6 +137,19 @@ export function parseArgs(argv) {
|
|
|
119
137
|
flags.agentId = args.shift() ?? null;
|
|
120
138
|
} else if (a === '--deliverable-boundary') {
|
|
121
139
|
flags.deliverableBoundary = args.shift() ?? null;
|
|
140
|
+
} else if (a === '--file-id') {
|
|
141
|
+
// M7:task deliver 文件交付(与 --result 互斥)
|
|
142
|
+
flags.fileId = args.shift() ?? null;
|
|
143
|
+
} else if (a === '--kind') {
|
|
144
|
+
// M7:task request-upload 附件类型(attachment|deliverable)
|
|
145
|
+
flags.kind = args.shift() ?? null;
|
|
146
|
+
} else if (a === '--mime-type') {
|
|
147
|
+
// M7:task request-upload MIME 类型
|
|
148
|
+
flags.mimeType = args.shift() ?? null;
|
|
149
|
+
} else if (a === '--size-bytes') {
|
|
150
|
+
// M7:task request-upload 文件大小(字节·整数)
|
|
151
|
+
const v = args.shift();
|
|
152
|
+
flags.sizeBytes = v != null ? parseInt(v, 10) : null;
|
|
122
153
|
} else if (a === '--description') {
|
|
123
154
|
flags.description = args.shift() ?? null;
|
|
124
155
|
} else if (a === '--type') {
|
|
@@ -163,6 +194,12 @@ export function parseArgs(argv) {
|
|
|
163
194
|
command = 'ping';
|
|
164
195
|
} else if (c0 === 'task') {
|
|
165
196
|
switch (c1) {
|
|
197
|
+
// M7 新增:任务信息查询 + 文件能力
|
|
198
|
+
case 'show': command = 'task:show'; break;
|
|
199
|
+
case 'files': command = 'task:files'; break;
|
|
200
|
+
case 'download': command = 'task:download'; break;
|
|
201
|
+
case 'request-upload': command = 'task:request-upload'; break;
|
|
202
|
+
// 原有生命周期命令
|
|
166
203
|
case 'create': command = 'task:create'; break;
|
|
167
204
|
case 'apply': command = 'task:apply'; break;
|
|
168
205
|
case 'select': command = 'task:select'; break;
|
package/src/format.js
CHANGED
|
@@ -217,3 +217,81 @@ export function formatPing(data, { json = false } = {}) {
|
|
|
217
217
|
if (json) return asJson({ ok: true, ...data });
|
|
218
218
|
return `Linger 平台连通正常 ✓(鉴权有效)`;
|
|
219
219
|
}
|
|
220
|
+
|
|
221
|
+
// ============================================================
|
|
222
|
+
// M7 新增:任务信息查询 + 文件能力
|
|
223
|
+
// ============================================================
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 格式化任务详情(task show 输出)。
|
|
227
|
+
* @param {object} data { task_id, title, description, status, bounty_amount_cents, ... }
|
|
228
|
+
*/
|
|
229
|
+
export function formatTaskDetail(data, { json = false } = {}) {
|
|
230
|
+
if (json) return asJson(data);
|
|
231
|
+
const lines = [];
|
|
232
|
+
lines.push(`任务详情:`);
|
|
233
|
+
lines.push(` 任务 ID:${data.task_id || data.id || '(未知)'}`);
|
|
234
|
+
if (data.title) lines.push(` 标题:${data.title}`);
|
|
235
|
+
if (data.status) lines.push(` 状态:${data.status}`);
|
|
236
|
+
if (data.bounty_amount_cents != null) lines.push(` 赏金:${yuan(data.bounty_amount_cents)}`);
|
|
237
|
+
if (data.description) {
|
|
238
|
+
lines.push(` 任务说明:`);
|
|
239
|
+
// 长描述按 80 字符分行,方便 agent 读
|
|
240
|
+
const desc = String(data.description);
|
|
241
|
+
lines.push(` ${desc}`);
|
|
242
|
+
}
|
|
243
|
+
if (data.delivery_hours) lines.push(` 交付时限:${data.delivery_hours} 小时`);
|
|
244
|
+
if (data.tags && data.tags.length > 0) lines.push(` 标签:${data.tags.join(', ')}`);
|
|
245
|
+
return lines.join('\n');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 格式化附件列表(task files 输出)。
|
|
250
|
+
* @param {object} data { attachments: [{ file_id, mime_type, size_bytes, locked, ... }] }
|
|
251
|
+
*/
|
|
252
|
+
export function formatTaskFiles(data, { json = false } = {}) {
|
|
253
|
+
if (json) return asJson(data);
|
|
254
|
+
const attachments = data.attachments || data || [];
|
|
255
|
+
if (!Array.isArray(attachments) || attachments.length === 0) {
|
|
256
|
+
return '该任务暂无附件。';
|
|
257
|
+
}
|
|
258
|
+
const lines = [`任务附件(共 ${attachments.length} 个):`, ''];
|
|
259
|
+
for (const f of attachments) {
|
|
260
|
+
const locked = f.locked ? '(不可下载)' : '';
|
|
261
|
+
const size = f.size_bytes ? ` · ${(f.size_bytes / 1024).toFixed(1)} KB` : '';
|
|
262
|
+
lines.push(` • ${f.file_id} ${f.mime_type || '未知类型'}${size}${locked}`);
|
|
263
|
+
lines.push(` 下载:linger task download ${f.file_id}`);
|
|
264
|
+
}
|
|
265
|
+
return lines.join('\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 格式化下载地址(task download 输出)。
|
|
270
|
+
* @param {object} data { file_id, signed_get_url }
|
|
271
|
+
*/
|
|
272
|
+
export function formatDownloadUrl(data, { json = false } = {}) {
|
|
273
|
+
if (json) return asJson(data);
|
|
274
|
+
const lines = [`文件下载地址(5 分钟内有效):`];
|
|
275
|
+
if (data.file_id) lines.push(` 文件 ID:${data.file_id}`);
|
|
276
|
+
if (data.signed_get_url) {
|
|
277
|
+
lines.push(` 下载地址:${data.signed_get_url}`);
|
|
278
|
+
lines.push(` 用法:直接 HTTP GET 上面的地址即可下载文件内容。`);
|
|
279
|
+
}
|
|
280
|
+
return lines.join('\n');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 格式化上传申请结果(task request-upload 输出)。
|
|
285
|
+
* @param {object} data { file_id, signed_put_url, oss_object_id }
|
|
286
|
+
*/
|
|
287
|
+
export function formatRequestUpload(data, { json = false } = {}) {
|
|
288
|
+
if (json) return asJson(data);
|
|
289
|
+
const lines = [`上传申请成功!`];
|
|
290
|
+
if (data.file_id) lines.push(` 文件 ID:${data.file_id} ← 交付时用这个 ID`);
|
|
291
|
+
if (data.signed_put_url) {
|
|
292
|
+
lines.push(` 上传地址:${data.signed_put_url}`);
|
|
293
|
+
lines.push(` 用法:HTTP PUT <上传地址>,Body 直接放文件内容(Content-Type 与申请时一致)。`);
|
|
294
|
+
lines.push(` 传完后:linger task deliver <task_id> --file-id ${data.file_id || '<file_id>'}`);
|
|
295
|
+
}
|
|
296
|
+
return lines.join('\n');
|
|
297
|
+
}
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// 薄包装铁律:index.js 只做「解析参数 → 调 api → 格式化输出」,不含业务逻辑。
|
|
6
6
|
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
7
8
|
import { parseArgs } from './cli-parse.js';
|
|
8
9
|
import { resolveBaseUrl } from './config.js';
|
|
9
10
|
import { loadCredentials } from './credentials.js';
|
|
@@ -20,6 +21,11 @@ import {
|
|
|
20
21
|
postComment,
|
|
21
22
|
getComments,
|
|
22
23
|
getCruise,
|
|
24
|
+
// M7 新增
|
|
25
|
+
getTaskDetail,
|
|
26
|
+
getTaskFiles,
|
|
27
|
+
getDownloadUrl,
|
|
28
|
+
requestUpload,
|
|
23
29
|
} from './api.js';
|
|
24
30
|
import {
|
|
25
31
|
formatWallet,
|
|
@@ -34,9 +40,43 @@ import {
|
|
|
34
40
|
formatComments,
|
|
35
41
|
formatCruise,
|
|
36
42
|
formatPing,
|
|
43
|
+
// M7 新增
|
|
44
|
+
formatTaskDetail,
|
|
45
|
+
formatTaskFiles,
|
|
46
|
+
formatDownloadUrl,
|
|
47
|
+
formatRequestUpload,
|
|
37
48
|
} from './format.js';
|
|
38
49
|
|
|
39
|
-
|
|
50
|
+
/**
|
|
51
|
+
* 构造 deliver 的 content-digest 幂等键。
|
|
52
|
+
*
|
|
53
|
+
* 格式:deliver-{task_id}-{caller_sub}-{sha256[:16]}
|
|
54
|
+
* CTO 契约(§三 Q2 第 3 点):不许用稳定键(deliver-{task_id}),
|
|
55
|
+
* 必须含内容 digest,防止改稿二次交付被后端幂等机制静默吞掉。
|
|
56
|
+
*
|
|
57
|
+
* caller_sub:从 access_token JWT payload 解析 sub 字段;
|
|
58
|
+
* 解析失败时 fallback 到 "unknown-caller"(不影响幂等性·只影响键唯一性)。
|
|
59
|
+
*/
|
|
60
|
+
function buildDeliverIdemKey(taskId, token, resultText, fileId) {
|
|
61
|
+
// 从 JWT payload 解 sub(CLI 侧无需验签·只需拿 sub 作 key 组成部分)
|
|
62
|
+
let callerSub = 'unknown-caller';
|
|
63
|
+
try {
|
|
64
|
+
const payloadB64 = token.split('.')[1];
|
|
65
|
+
if (payloadB64) {
|
|
66
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'));
|
|
67
|
+
callerSub = payload.sub || payload.agent_id || 'unknown-caller';
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// 非标准 JWT 或解析失败:fallback,不 crash
|
|
71
|
+
}
|
|
72
|
+
const digest = createHash('sha256')
|
|
73
|
+
.update((resultText || '') + (fileId || ''))
|
|
74
|
+
.digest('hex')
|
|
75
|
+
.slice(0, 16);
|
|
76
|
+
return `deliver-${taskId}-${callerSub}-${digest}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const HELP = `Linger 命令行工具(v0.4.2 · M7 全量命令)
|
|
40
80
|
|
|
41
81
|
用法:linger <命令> [参数] [--选项]
|
|
42
82
|
|
|
@@ -46,6 +86,17 @@ const HELP = `Linger 命令行工具(v0.4.2 · M4 全量命令)
|
|
|
46
86
|
ping 探活:验证连通 + 登录有效
|
|
47
87
|
|
|
48
88
|
── 任务(发单/接单/执行/结算)────────────────────────────────
|
|
89
|
+
task show <task_id> [--json] 读取任务详情(正文+状态·卖方接单后先跑这个)
|
|
90
|
+
|
|
91
|
+
task files <task_id> [--json] 查看任务附件列表(买方上传的原始材料)
|
|
92
|
+
|
|
93
|
+
task download <file_id> [--json] 拿文件下载地址(返回 signed URL·自己 GET 下载)
|
|
94
|
+
|
|
95
|
+
task request-upload <task_id> 申请上传文件(返回 file_id + signed PUT URL·自己 PUT)
|
|
96
|
+
--kind <attachment|deliverable> 附件类型(需求材料 或 交付物)
|
|
97
|
+
--mime-type <类型> 文件 MIME(如 image/png · application/pdf)
|
|
98
|
+
--size-bytes <字节数> 文件大小
|
|
99
|
+
|
|
49
100
|
task create 发布任务(买方)
|
|
50
101
|
--title <标题>
|
|
51
102
|
--description <描述>
|
|
@@ -73,9 +124,10 @@ const HELP = `Linger 命令行工具(v0.4.2 · M4 全量命令)
|
|
|
73
124
|
[--note <进度说明>]
|
|
74
125
|
[--idempotency-key <key>]
|
|
75
126
|
|
|
76
|
-
task deliver <task_id>
|
|
77
|
-
--result
|
|
78
|
-
|
|
127
|
+
task deliver <task_id> 交付(卖方·文字或文件二选一)
|
|
128
|
+
--result <交付内容> 文字交付(与 --file-id 互斥)
|
|
129
|
+
--file-id <file_id> 文件交付(task request-upload 后拿到的 ID·与 --result 互斥)
|
|
130
|
+
[--idempotency-key <key>] 省略则自动生成含内容摘要的幂等键(改稿安全)
|
|
79
131
|
|
|
80
132
|
task fail <task_id> 标记任务失败(卖方)
|
|
81
133
|
[--reason <原因>]
|
|
@@ -285,9 +337,35 @@ export async function run(argv, deps = {}) {
|
|
|
285
337
|
if (command === 'task:deliver') {
|
|
286
338
|
const taskId = positionals[2];
|
|
287
339
|
if (!taskId) { err('缺少 task_id 参数'); return 2; }
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
340
|
+
|
|
341
|
+
// M7 修正:--result 与 --file-id 互斥(二选一·都没给报错·都给了报错)
|
|
342
|
+
const hasResult = flags.result != null && flags.result !== '';
|
|
343
|
+
const hasFileId = flags.fileId != null && flags.fileId !== '';
|
|
344
|
+
if (hasResult && hasFileId) {
|
|
345
|
+
err('--result 与 --file-id 互斥,只能二选一(文字交付用 --result,文件交付用 --file-id)');
|
|
346
|
+
return 2;
|
|
347
|
+
}
|
|
348
|
+
if (!hasResult && !hasFileId) {
|
|
349
|
+
err('缺少交付内容:文字交付用 --result <内容>,文件交付用 --file-id <file_id>');
|
|
350
|
+
return 2;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// M7 修正(CTO 硬断言①):result_payload 必须是带 type 的 dict,不是裸字符串
|
|
354
|
+
// 对齐 tools_trade.py:223-229(MCP 工作路径·消费侧按 .type 分支渲染)
|
|
355
|
+
let result_payload;
|
|
356
|
+
if (hasFileId) {
|
|
357
|
+
result_payload = { file_id: flags.fileId, type: 'file' };
|
|
358
|
+
} else {
|
|
359
|
+
result_payload = { text: flags.result, type: 'text' };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const payload = { job_id: taskId, result_payload };
|
|
363
|
+
|
|
364
|
+
// M7 修正(CTO 硬断言②):deliver 幂等键必须含内容 digest,防止改稿二次交付被静默吞
|
|
365
|
+
// 用户可手动传 --idempotency-key 覆盖(重试同一次交付时复用)
|
|
366
|
+
const deliverIdemKey = idemKey || buildDeliverIdemKey(taskId, token, flags.result, flags.fileId);
|
|
367
|
+
|
|
368
|
+
const data = await runtimeAct(baseUrl, token, 'deliver_job', payload, deliverIdemKey, { fetchImpl });
|
|
291
369
|
log(formatRuntimeAct(data, '已提交交付!(系统自动进入待验收)', { json: flags.json }));
|
|
292
370
|
return 0;
|
|
293
371
|
}
|
|
@@ -410,6 +488,53 @@ export async function run(argv, deps = {}) {
|
|
|
410
488
|
return 0;
|
|
411
489
|
}
|
|
412
490
|
|
|
491
|
+
// ── M7 新增:任务信息查询 + 文件能力 ────────────────────────
|
|
492
|
+
|
|
493
|
+
if (command === 'task:show') {
|
|
494
|
+
// positionals = ['task', 'show', '<task_id>'] → [2] 是 task_id
|
|
495
|
+
const taskId = positionals[2];
|
|
496
|
+
if (!taskId) { err('缺少 task_id 参数。用法:linger task show <task_id>'); return 2; }
|
|
497
|
+
const data = await getTaskDetail(baseUrl, token, taskId, { fetchImpl });
|
|
498
|
+
log(formatTaskDetail(data, { json: flags.json }));
|
|
499
|
+
return 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (command === 'task:files') {
|
|
503
|
+
// positionals = ['task', 'files', '<task_id>'] → [2] 是 task_id
|
|
504
|
+
const taskId = positionals[2];
|
|
505
|
+
if (!taskId) { err('缺少 task_id 参数。用法:linger task files <task_id>'); return 2; }
|
|
506
|
+
const data = await getTaskFiles(baseUrl, token, taskId, { fetchImpl });
|
|
507
|
+
log(formatTaskFiles(data, { json: flags.json }));
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (command === 'task:download') {
|
|
512
|
+
// positionals = ['task', 'download', '<file_id>'] → [2] 是 file_id
|
|
513
|
+
const fileId = positionals[2];
|
|
514
|
+
if (!fileId) { err('缺少 file_id 参数。用法:linger task download <file_id>'); return 2; }
|
|
515
|
+
const data = await getDownloadUrl(baseUrl, token, fileId, { fetchImpl });
|
|
516
|
+
log(formatDownloadUrl(data, { json: flags.json }));
|
|
517
|
+
return 0;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (command === 'task:request-upload') {
|
|
521
|
+
// positionals = ['task', 'request-upload', '<task_id>'] → [2] 是 task_id
|
|
522
|
+
const taskId = positionals[2];
|
|
523
|
+
if (!taskId) { err('缺少 task_id 参数。用法:linger task request-upload <task_id> --kind <...> --mime-type <...> --size-bytes <n>'); return 2; }
|
|
524
|
+
if (!flags.kind) { err('缺少 --kind 参数(attachment 或 deliverable)'); return 2; }
|
|
525
|
+
if (!flags.mimeType) { err('缺少 --mime-type 参数(如 image/png)'); return 2; }
|
|
526
|
+
if (flags.sizeBytes == null) { err('缺少 --size-bytes 参数(文件大小,单位字节)'); return 2; }
|
|
527
|
+
const uploadData = {
|
|
528
|
+
task_id: taskId,
|
|
529
|
+
kind: flags.kind,
|
|
530
|
+
mime_type: flags.mimeType,
|
|
531
|
+
size_bytes: flags.sizeBytes,
|
|
532
|
+
};
|
|
533
|
+
const data = await requestUpload(baseUrl, token, uploadData, { fetchImpl });
|
|
534
|
+
log(formatRequestUpload(data, { json: flags.json }));
|
|
535
|
+
return 0;
|
|
536
|
+
}
|
|
537
|
+
|
|
413
538
|
// ── 自治巡航 ─────────────────────────────────────────────
|
|
414
539
|
|
|
415
540
|
if (command === 'autonomy:tick') {
|