@lark-apaas/miaoda-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.
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleFileLs = handleFileLs;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const output_1 = require("../../../utils/output");
39
+ const render_1 = require("../../../utils/render");
40
+ const error_1 = require("../../../utils/error");
41
+ const shared_1 = require("../../../cli/commands/shared");
42
+ const index_1 = require("../../../api/file/index");
43
+ /**
44
+ * 把位置参数 `query` 路由到 `--path` 或 `--name`:
45
+ * - `looksLikePath(query)` → path 精确匹配
46
+ * - 否则 → file_name 精确匹配
47
+ *
48
+ * 显式 `--path` / `--name` 传入时跳过 query 自动识别。
49
+ * 同时传 query 又传 --path/--name 视为歧义,直接报错。
50
+ */
51
+ function resolveQueryRouting(opts) {
52
+ const explicit = {
53
+ path: opts.path,
54
+ name: opts.name,
55
+ };
56
+ if (!opts.query)
57
+ return explicit;
58
+ if (explicit.path || explicit.name) {
59
+ throw new error_1.AppError("ARGS_INVALID", "Do not mix positional <query> with --path / --name; pick one.");
60
+ }
61
+ if ((0, index_1.looksLikePath)(opts.query)) {
62
+ // 补齐前导 `/`,对齐 ls sidecar 比较逻辑(info.path 永远带前导 `/`)
63
+ return { path: (0, index_1.toAbsolutePath)(opts.query) };
64
+ }
65
+ return { name: opts.query };
66
+ }
67
+ async function handleFileLs(opts) {
68
+ const appId = (0, shared_1.resolveAppId)(opts);
69
+ // commander 已经把 --limit 解析为 number;保留 ?? undefined 兼容老调用
70
+ const limit = opts.limit;
71
+ const sizeGt = opts.sizeGt ? (0, render_1.parseSize)(opts.sizeGt) : undefined;
72
+ const sizeLt = opts.sizeLt ? (0, render_1.parseSize)(opts.sizeLt) : undefined;
73
+ const { path, name } = resolveQueryRouting(opts);
74
+ const result = await api.file.listFiles({
75
+ appId,
76
+ limit,
77
+ cursor: opts.cursor,
78
+ all: opts.all,
79
+ path,
80
+ name,
81
+ type: opts.type,
82
+ sizeGt,
83
+ sizeLt,
84
+ uploadedSince: opts.uploadedSince,
85
+ uploadedUntil: opts.uploadedUntil,
86
+ });
87
+ if ((0, output_1.isJsonMode)()) {
88
+ (0, output_1.emitPaged)(result.items, result.next_cursor, result.has_more);
89
+ return;
90
+ }
91
+ if (result.items.length === 0) {
92
+ (0, output_1.emit)("No files found.");
93
+ return;
94
+ }
95
+ const tty = (0, render_1.isStdoutTty)();
96
+ const headers = ["file_name", "path", "size", "type", "uploaded_at"];
97
+ const rows = result.items.map((info) => [
98
+ info.file_name || "—",
99
+ info.path,
100
+ tty ? (0, render_1.formatSize)(info.size_bytes) : String(info.size_bytes),
101
+ info.type,
102
+ (0, render_1.formatTime)(info.uploaded_at, tty),
103
+ ]);
104
+ const table = tty
105
+ ? (0, render_1.renderAlignedTable)(headers, rows)
106
+ : (0, render_1.renderTsv)(headers, rows);
107
+ const hint = result.has_more && result.next_cursor
108
+ ? `\n— ${String(result.items.length)} results. Next: --cursor ${result.next_cursor}`
109
+ : "";
110
+ (0, output_1.emit)(table + hint);
111
+ }
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.handleFileRm = handleFileRm;
40
+ const api = __importStar(require("../../../api/index"));
41
+ const output_1 = require("../../../utils/output");
42
+ const error_1 = require("../../../utils/error");
43
+ const shared_1 = require("../../../cli/commands/shared");
44
+ const index_1 = require("../../../api/file/index");
45
+ const render_1 = require("../../../utils/render");
46
+ const node_readline_1 = __importDefault(require("node:readline"));
47
+ const MAX_BATCH = 100;
48
+ /**
49
+ * 解析位置参数(自动识别 path / file_name)与 `--name`(强制 file_name)两类输入。
50
+ *
51
+ * 位置参数:
52
+ * - `looksLikePath(p)` 为真(`/` 开头 / 含 `/` / 16+ 位 fileKey 模式)→ 视为 path,直接删
53
+ * - 否则视为 file_name → 走 FilterExpression 精确查找,命中唯一后再删
54
+ *
55
+ * `--name` 选项:
56
+ * - 强制按 file_name 处理,跳过 looksLikePath 判断
57
+ * - 走 FilterExpression `name eq X` 查找;0 命中 → FILE_NOT_FOUND;多匹配 → AMBIGUOUS_FILE_NAME
58
+ */
59
+ async function resolveDeleteInputs(appId, paths, names) {
60
+ const resolved = [];
61
+ const errors = [];
62
+ // 1. 位置参数:按 looksLikePath 分流
63
+ const positionalAsPath = [];
64
+ const positionalAsName = [];
65
+ for (const p of paths) {
66
+ if ((0, index_1.looksLikePath)(p)) {
67
+ positionalAsPath.push(p);
68
+ }
69
+ else {
70
+ positionalAsName.push(p);
71
+ }
72
+ }
73
+ // 1a. path 分支:直接进删除队列,file_name 取 basename 先占位
74
+ for (const p of positionalAsPath) {
75
+ const abs = (0, index_1.toAbsolutePath)(p);
76
+ const basename = abs.split("/").pop() ?? abs;
77
+ resolved.push({ input: p, path: abs, file_name: basename });
78
+ }
79
+ // 1b. name 分支(来自位置参数的非 path):和 --name 合并走 resolveInputs
80
+ const allNames = [...positionalAsName, ...names];
81
+ if (allNames.length > 0) {
82
+ const results = await api.file.resolveInputs({
83
+ appId,
84
+ inputs: allNames,
85
+ forceAs: "name",
86
+ });
87
+ for (const r of results) {
88
+ if (r.status === "ok") {
89
+ resolved.push({
90
+ input: r.input,
91
+ path: r.file.path,
92
+ file_name: r.file.file_name,
93
+ });
94
+ }
95
+ else {
96
+ errors.push({
97
+ status: "error",
98
+ input: r.input,
99
+ error: r.error,
100
+ });
101
+ }
102
+ }
103
+ }
104
+ return { resolved, errors };
105
+ }
106
+ /**
107
+ * 删除前 TTY 二次确认。
108
+ * PRD 单文件场景下提示带具体路径:`? Delete '/path'? (y/N)`,
109
+ * 多文件场景汇总条数:`? Delete N files? (y/N)`。
110
+ * `firstInput` 是用户传入的第一个值(可能是 path 或 file_name),
111
+ * 单文件时直接展示给用户,方便核对目标。
112
+ */
113
+ async function confirm(count, firstInput) {
114
+ const rl = node_readline_1.default.createInterface({
115
+ input: process.stdin,
116
+ output: process.stderr,
117
+ });
118
+ return new Promise((resolve) => {
119
+ const prompt = count === 1 ? `? Delete '${firstInput}'? (y/N) ` : `? Delete ${String(count)} files? (y/N) `;
120
+ rl.question(prompt, (answer) => {
121
+ rl.close();
122
+ resolve(answer.trim().toLowerCase() === "y");
123
+ });
124
+ });
125
+ }
126
+ async function handleFileRm(paths, opts) {
127
+ const names = opts.name ?? [];
128
+ const totalCount = paths.length + names.length;
129
+ if (totalCount === 0) {
130
+ throw new error_1.AppError("ARGS_INVALID", "No file specified (give a /path or --name <name>)");
131
+ }
132
+ if (totalCount > MAX_BATCH) {
133
+ throw new error_1.AppError("FILE_BATCH_TOO_MANY", `Batch size ${String(totalCount)} exceeds the 100 limit`);
134
+ }
135
+ const appId = (0, shared_1.resolveAppId)(opts);
136
+ // destructive guardrail
137
+ const tty = (0, render_1.isStdoutTty)();
138
+ if (tty && !opts.yes) {
139
+ // 单文件提示带具体目标方便用户核对;多文件传第一个 input 占位(实际只显示 N files)
140
+ // 上面已校验 totalCount > 0,paths/names 至少有一个非空
141
+ const firstInput = paths.length > 0 ? paths[0] : names[0];
142
+ const ok = await confirm(totalCount, firstInput);
143
+ if (!ok) {
144
+ throw new error_1.AppError("DESTRUCTIVE_CANCELLED", "Cancelled by user");
145
+ }
146
+ }
147
+ else if (!tty && !opts.yes) {
148
+ throw new error_1.AppError("DESTRUCTIVE_REQUIRES_CONFIRM", "This operation is destructive. Rerun with --yes to confirm.");
149
+ }
150
+ const { resolved, errors } = await resolveDeleteInputs(appId, paths, names);
151
+ // path → entry 映射(用于 delete 响应归档回原始 input 和 file_name)
152
+ const pathToEntry = new Map();
153
+ for (const e of resolved) {
154
+ pathToEntry.set(e.path, e);
155
+ }
156
+ const uniquePaths = Array.from(new Set(resolved.map((e) => e.path)));
157
+ let results = [...errors];
158
+ if (uniquePaths.length > 0) {
159
+ // 后端 delete 幂等语义,只返成功删除的 attachments。对比请求 vs 响应推断失败项;
160
+ // 失败项再发一次 head 探测具体原因(文件不存在 / 服务端错误)。
161
+ // TODO(storage): 推动后端 InnerBatchDeleteAttachmentsResponse 增加 failed 字段,
162
+ // 避免靠 HEAD 二次探测(多 N 次请求,仅失败场景才触发)。
163
+ const { deleted, failed } = await api.file.deleteFiles({
164
+ appId,
165
+ filePaths: uniquePaths,
166
+ });
167
+ for (const p of deleted) {
168
+ const entry = pathToEntry.get(p);
169
+ results.push({
170
+ status: "ok",
171
+ input: entry?.input ?? p,
172
+ file_name: entry?.file_name ?? (p.split("/").pop() ?? p),
173
+ path: p,
174
+ });
175
+ }
176
+ // 失败项:并发 HEAD 探测分类
177
+ const classified = await Promise.all(failed.map(async (f) => {
178
+ const entry = pathToEntry.get(f.path);
179
+ const input = entry?.input ?? f.path;
180
+ try {
181
+ await api.file.statFile({ appId, filePath: f.path });
182
+ return {
183
+ input,
184
+ code: "INTERNAL_ERROR",
185
+ message: "Delete request returned success but file still exists (server-side issue)",
186
+ hint: undefined,
187
+ };
188
+ }
189
+ catch (err) {
190
+ if (err instanceof error_1.AppError && err.code === "FILE_NOT_FOUND") {
191
+ return {
192
+ input,
193
+ code: "FILE_NOT_FOUND",
194
+ message: `File '${input}' does not exist at delete time`,
195
+ hint: "Run `miaoda file ls` to see available files.",
196
+ };
197
+ }
198
+ return {
199
+ input,
200
+ code: "INTERNAL_ERROR",
201
+ message: err instanceof Error ? err.message : "verification failed",
202
+ hint: undefined,
203
+ };
204
+ }
205
+ }));
206
+ for (const c of classified) {
207
+ results.push({
208
+ status: "error",
209
+ input: c.input,
210
+ error: c.hint
211
+ ? { code: c.code, message: c.message, hint: c.hint }
212
+ : { code: c.code, message: c.message },
213
+ });
214
+ }
215
+ }
216
+ // 按输入顺序重排(paths 优先,然后 names)
217
+ const orderIndex = new Map();
218
+ paths.forEach((p, i) => orderIndex.set(p, i));
219
+ names.forEach((n, i) => orderIndex.set(n, paths.length + i));
220
+ results = results.sort((a, b) => (orderIndex.get(a.input) ?? Number.MAX_SAFE_INTEGER) -
221
+ (orderIndex.get(b.input) ?? Number.MAX_SAFE_INTEGER));
222
+ const okCount = results.filter((r) => r.status === "ok").length;
223
+ const failCount = results.length - okCount;
224
+ if ((0, output_1.isJsonMode)()) {
225
+ (0, output_1.emit)({ data: results });
226
+ }
227
+ else if (tty) {
228
+ const lines = [];
229
+ for (const r of results) {
230
+ if (r.status === "ok")
231
+ lines.push(`✓ Deleted ${r.input}`);
232
+ else
233
+ lines.push(`✗ ${r.input}: ${r.error.message}`);
234
+ }
235
+ if (failCount === 0) {
236
+ lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files`);
237
+ }
238
+ else {
239
+ lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
240
+ }
241
+ (0, output_1.emit)(lines.join("\n"));
242
+ }
243
+ else {
244
+ const lines = [];
245
+ for (const r of results) {
246
+ if (r.status === "ok")
247
+ lines.push(`OK\t${r.input}`);
248
+ else
249
+ lines.push(`FAIL\t${r.input}\t${r.error.message}`);
250
+ }
251
+ if (failCount === 0) {
252
+ lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files`);
253
+ }
254
+ else {
255
+ lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
256
+ }
257
+ (0, output_1.emit)(lines.join("\n"));
258
+ }
259
+ // 退出码:任一失败 → 1;全成功 → 0
260
+ if (failCount > 0) {
261
+ process.exitCode = 1;
262
+ }
263
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleFileSign = handleFileSign;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const output_1 = require("../../../utils/output");
39
+ const render_1 = require("../../../utils/render");
40
+ const error_1 = require("../../../utils/error");
41
+ const shared_1 = require("../../../cli/commands/shared");
42
+ const index_1 = require("../../../api/file/index");
43
+ const MAX_EXPIRES_SECONDS = 30 * 86400;
44
+ const DEFAULT_EXPIRES_SECONDS = 86400; // 1d(PRD 规定)
45
+ /**
46
+ * 把 <file> 解析为后端 sign 可用的 filePath。
47
+ *
48
+ * - `looksLikePath(input)`(`/path`、含 `/` 的 key、后端生成的 fileKey)→ 直接走 path,不反查
49
+ * - 其他输入按 file_name 走 FilterExpression 精确查询(多匹配 → AMBIGUOUS_FILE_NAME)
50
+ */
51
+ async function resolveFilePath(appId, input) {
52
+ if ((0, index_1.looksLikePath)(input))
53
+ return (0, index_1.toAbsolutePath)(input);
54
+ const [resolved] = await api.file.resolveInputs({
55
+ appId,
56
+ inputs: [input],
57
+ forceAs: "name",
58
+ });
59
+ if (resolved.status === "error") {
60
+ throw new error_1.AppError(resolved.error.code, resolved.error.message, {
61
+ next_actions: resolved.error.hint ? [resolved.error.hint] : undefined,
62
+ });
63
+ }
64
+ return resolved.file.path;
65
+ }
66
+ async function handleFileSign(file, opts) {
67
+ const appId = (0, shared_1.resolveAppId)(opts);
68
+ let expiresSec = DEFAULT_EXPIRES_SECONDS;
69
+ if (opts.expires) {
70
+ expiresSec = (0, render_1.parseDuration)(opts.expires);
71
+ if (expiresSec > MAX_EXPIRES_SECONDS) {
72
+ throw new error_1.AppError("FILE_SIGN_DURATION_EXCEEDED", `Expires duration '${opts.expires}' exceeds the maximum of 30d`, {
73
+ next_actions: [
74
+ `Maximum allowed value is 30d. Use \`--expires 30d\` for the longest link.`,
75
+ ],
76
+ });
77
+ }
78
+ }
79
+ const filePath = await resolveFilePath(appId, file);
80
+ const result = await api.file.signDownload({
81
+ appId,
82
+ filePath,
83
+ expiresIn: expiresSec,
84
+ });
85
+ if ((0, output_1.isJsonMode)()) {
86
+ (0, output_1.emitOk)({
87
+ file_name: result.file_name,
88
+ path: result.path,
89
+ signed_url: result.signed_url,
90
+ expires_at: result.expires_at,
91
+ });
92
+ return;
93
+ }
94
+ // 默认输出 URL 一行(pretty 与 non-TTY 一致)
95
+ (0, output_1.emit)(result.signed_url);
96
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleFileStat = handleFileStat;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const output_1 = require("../../../utils/output");
39
+ const render_1 = require("../../../utils/render");
40
+ const error_1 = require("../../../utils/error");
41
+ const shared_1 = require("../../../cli/commands/shared");
42
+ const index_1 = require("../../../api/file/index");
43
+ /**
44
+ * 解析 `<file>` 为后端 head 可用的 filePath。
45
+ *
46
+ * - `looksLikePath(input)`(`/path`、含 `/` 的 key、后端生成的 fileKey)→ 直接 HEAD
47
+ * - 其他输入按 file_name 走 FilterExpression 精确查询(多匹配 → AMBIGUOUS_FILE_NAME)
48
+ */
49
+ async function resolveFile(appId, input) {
50
+ if ((0, index_1.looksLikePath)(input)) {
51
+ return api.file.statFile({ appId, filePath: (0, index_1.toAbsolutePath)(input) });
52
+ }
53
+ const [resolved] = await api.file.resolveInputs({
54
+ appId,
55
+ inputs: [input],
56
+ forceAs: "name",
57
+ });
58
+ if (resolved.status === "error") {
59
+ throw new error_1.AppError(resolved.error.code, resolved.error.message, {
60
+ next_actions: resolved.error.hint ? [resolved.error.hint] : undefined,
61
+ });
62
+ }
63
+ const head = await api.file.statFile({ appId, filePath: resolved.file.path });
64
+ // head 响应的 file_name 有时为空,用 list 响应补齐
65
+ if (!head.file_name)
66
+ head.file_name = resolved.file.file_name;
67
+ return head;
68
+ }
69
+ async function handleFileStat(file, opts) {
70
+ const appId = (0, shared_1.resolveAppId)(opts);
71
+ const info = await resolveFile(appId, file);
72
+ if ((0, output_1.isJsonMode)()) {
73
+ (0, output_1.emitOk)(info);
74
+ return;
75
+ }
76
+ const tty = (0, render_1.isStdoutTty)();
77
+ const pairs = [
78
+ ["file_name", info.file_name || "—"],
79
+ ["path", info.path],
80
+ [
81
+ "size",
82
+ tty
83
+ ? `${(0, render_1.formatSize)(info.size_bytes)} (${String(info.size_bytes)} bytes)`
84
+ : String(info.size_bytes),
85
+ ],
86
+ ["type", info.type || "—"],
87
+ ["uploaded_at", (0, render_1.formatTime)(info.uploaded_at, tty)],
88
+ ];
89
+ if (info.uploaded_by) {
90
+ // uploaded_by 紧跟 uploaded_at 前插入(index = "uploaded_at" 之前)
91
+ pairs.splice(pairs.length - 1, 0, ["uploaded_by", info.uploaded_by]);
92
+ }
93
+ if (info.download_url) {
94
+ pairs.push(["download_url", info.download_url]);
95
+ }
96
+ (0, output_1.emit)((0, render_1.renderKeyValue)(pairs, tty));
97
+ }
@@ -15,3 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("../../cli/handlers/plugin/index"), exports);
18
+ __exportStar(require("../../cli/handlers/file/index"), exports);
19
+ __exportStar(require("../../cli/handlers/db/index"), exports);