@lark-apaas/miaoda-cli 0.1.0 → 0.1.1-alpha.07b4fd5
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/dist/api/db/api.js +278 -0
- package/dist/api/db/client.js +169 -0
- package/dist/api/db/index.js +16 -0
- package/dist/api/db/parsers.js +174 -0
- package/dist/api/db/sql-keywords.js +52 -0
- package/dist/api/db/types.js +10 -0
- package/dist/api/file/api.js +509 -0
- package/dist/api/file/client.js +199 -0
- package/dist/api/file/detect.js +56 -0
- package/dist/api/file/index.js +18 -0
- package/dist/api/file/parsers.js +72 -0
- package/dist/api/file/types.js +3 -0
- package/dist/api/index.js +5 -1
- package/dist/api/plugin/api.js +3 -3
- package/dist/cli/commands/db/index.js +209 -0
- package/dist/cli/commands/file/index.js +212 -0
- package/dist/cli/commands/index.js +4 -0
- package/dist/cli/commands/plugin/index.js +2 -1
- package/dist/cli/commands/shared.js +7 -8
- package/dist/cli/handlers/db/data.js +183 -0
- package/dist/cli/handlers/db/index.js +11 -0
- package/dist/cli/handlers/db/schema.js +174 -0
- package/dist/cli/handlers/db/sql.js +647 -0
- package/dist/cli/handlers/file/cp.js +221 -0
- package/dist/cli/handlers/file/index.js +13 -0
- package/dist/cli/handlers/file/ls.js +111 -0
- package/dist/cli/handlers/file/rm.js +264 -0
- package/dist/cli/handlers/file/sign.js +96 -0
- package/dist/cli/handlers/file/stat.js +97 -0
- package/dist/cli/handlers/index.js +2 -0
- package/dist/cli/help.js +188 -0
- package/dist/main.js +9 -1
- package/dist/utils/colors.js +98 -0
- package/dist/utils/error.js +14 -0
- package/dist/utils/fuzzy-match.js +91 -0
- package/dist/utils/http.js +31 -10
- package/dist/utils/index.js +3 -1
- package/dist/utils/output.js +75 -9
- package/dist/utils/render.js +192 -0
- package/package.json +4 -3
package/dist/utils/http.js
CHANGED
|
@@ -1,30 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getHttpClient = getHttpClient;
|
|
4
|
+
exports.getRuntimeHttpClient = getRuntimeHttpClient;
|
|
4
5
|
exports.resetHttpClient = resetHttpClient;
|
|
5
6
|
exports.setHttpClient = setHttpClient;
|
|
7
|
+
exports.setRuntimeHttpClient = setRuntimeHttpClient;
|
|
6
8
|
exports.applyCanaryHeader = applyCanaryHeader;
|
|
7
9
|
const http_client_1 = require("@lark-apaas/http-client");
|
|
8
|
-
let
|
|
9
|
-
|
|
10
|
+
let adminClient;
|
|
11
|
+
let runtimeClient;
|
|
12
|
+
/**
|
|
13
|
+
* 获取单例 HttpClient(默认:管理端 innerapi)。
|
|
14
|
+
*
|
|
15
|
+
* 管理端链路仅适用于妙搭开发态——baseURL 从 `MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX` 读取,
|
|
16
|
+
* 每次请求自动从 `MIAODA_AUTHN_CODE` 读取用户凭证并注入 `X-Miaoda-Client-Token`。
|
|
17
|
+
* AK/SK 的 `Authorization` / `x-api-key` 照旧叠加。
|
|
18
|
+
*
|
|
19
|
+
* 访问运行端 innerapi(`/api/v1/studio/innerapi/...`)请使用 {@link getRuntimeHttpClient}。
|
|
20
|
+
*/
|
|
10
21
|
function getHttpClient() {
|
|
11
|
-
|
|
12
|
-
return
|
|
22
|
+
adminClient ??= createClient({ adminInnerApi: true });
|
|
23
|
+
return adminClient;
|
|
13
24
|
}
|
|
14
|
-
/**
|
|
25
|
+
/** 运行端 innerapi 单例。baseURL 来自 `FORCE_AUTHN_INNERAPI_DOMAIN`。 */
|
|
26
|
+
function getRuntimeHttpClient() {
|
|
27
|
+
runtimeClient ??= createClient({ adminInnerApi: false });
|
|
28
|
+
return runtimeClient;
|
|
29
|
+
}
|
|
30
|
+
/** 重建管理端与运行端客户端(测试/配置变更后调用)。 */
|
|
15
31
|
function resetHttpClient() {
|
|
16
|
-
|
|
32
|
+
adminClient = undefined;
|
|
33
|
+
runtimeClient = undefined;
|
|
17
34
|
}
|
|
18
|
-
/**
|
|
35
|
+
/** 替换管理端 HttpClient 实例(用于测试 mock)。 */
|
|
19
36
|
function setHttpClient(custom) {
|
|
20
|
-
|
|
37
|
+
adminClient = custom;
|
|
38
|
+
}
|
|
39
|
+
/** 替换运行端 HttpClient 实例(用于测试 mock)。 */
|
|
40
|
+
function setRuntimeHttpClient(custom) {
|
|
41
|
+
runtimeClient = custom;
|
|
21
42
|
}
|
|
22
|
-
function createClient() {
|
|
43
|
+
function createClient(opts) {
|
|
23
44
|
const config = {
|
|
24
45
|
timeout: 30_000,
|
|
25
46
|
platform: {
|
|
26
47
|
enabled: true,
|
|
27
|
-
|
|
48
|
+
adminInnerApi: opts.adminInnerApi,
|
|
28
49
|
},
|
|
29
50
|
security: {
|
|
30
51
|
strictMode: true,
|
package/dist/utils/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setHttpClient = exports.resetHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.isJsonMode = exports.emitError = exports.emit = exports.getLogId = exports.generateLogId = exports.initConfigFromOpts = exports.resetConfig = exports.setConfig = exports.getConfig = exports.HttpError = exports.AppError = void 0;
|
|
3
|
+
exports.setRuntimeHttpClient = exports.setHttpClient = exports.resetHttpClient = exports.getRuntimeHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.isJsonMode = exports.emitError = exports.emit = exports.getLogId = exports.generateLogId = exports.initConfigFromOpts = exports.resetConfig = exports.setConfig = exports.getConfig = exports.HttpError = exports.AppError = void 0;
|
|
4
4
|
var error_1 = require("./error");
|
|
5
5
|
Object.defineProperty(exports, "AppError", { enumerable: true, get: function () { return error_1.AppError; } });
|
|
6
6
|
Object.defineProperty(exports, "HttpError", { enumerable: true, get: function () { return error_1.HttpError; } });
|
|
@@ -21,5 +21,7 @@ Object.defineProperty(exports, "debug", { enumerable: true, get: function () { r
|
|
|
21
21
|
Object.defineProperty(exports, "log", { enumerable: true, get: function () { return logger_1.log; } });
|
|
22
22
|
var http_1 = require("./http");
|
|
23
23
|
Object.defineProperty(exports, "getHttpClient", { enumerable: true, get: function () { return http_1.getHttpClient; } });
|
|
24
|
+
Object.defineProperty(exports, "getRuntimeHttpClient", { enumerable: true, get: function () { return http_1.getRuntimeHttpClient; } });
|
|
24
25
|
Object.defineProperty(exports, "resetHttpClient", { enumerable: true, get: function () { return http_1.resetHttpClient; } });
|
|
25
26
|
Object.defineProperty(exports, "setHttpClient", { enumerable: true, get: function () { return http_1.setHttpClient; } });
|
|
27
|
+
Object.defineProperty(exports, "setRuntimeHttpClient", { enumerable: true, get: function () { return http_1.setRuntimeHttpClient; } });
|
package/dist/utils/output.js
CHANGED
|
@@ -3,8 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isJsonMode = isJsonMode;
|
|
4
4
|
exports.emit = emit;
|
|
5
5
|
exports.emitError = emitError;
|
|
6
|
+
exports.emitOk = emitOk;
|
|
7
|
+
exports.emitPaged = emitPaged;
|
|
6
8
|
const config_1 = require("./config");
|
|
7
9
|
const error_1 = require("./error");
|
|
10
|
+
const colors_1 = require("./colors");
|
|
11
|
+
/**
|
|
12
|
+
* 服务端错误码 → CLI 兜底 hint 文案。
|
|
13
|
+
*
|
|
14
|
+
* 服务端错误协议只有 `{code, msg}` 两个字段(dataloom InnerExecuteSQL 等 IDL
|
|
15
|
+
* 没有 hint / next_actions 通道),所以服务端给的错误本身永远没有 hint。
|
|
16
|
+
* 这里是 CLI 展示层为常见错误码补一份 spec 一致的 actionable 引导,按错误码
|
|
17
|
+
* 落到 next_actions。
|
|
18
|
+
*
|
|
19
|
+
* 仅在 next_actions 已经为空时介入——保留 handler / enrichSqlError 自己塞过的
|
|
20
|
+
* 具体 hint(如 did-you-mean、shared.resolveAppId 等),它们优先级更高。
|
|
21
|
+
*/
|
|
22
|
+
const SERVER_ERROR_HINTS = {
|
|
23
|
+
// SELECT 结果集超过 1000 行硬拦:spec 多行引导。
|
|
24
|
+
// key 用 BIZ_ERR_MAP 映射后的语义 code(不是原始服务端 k_dl_xxx),保持
|
|
25
|
+
// 与 help / 文档里宣传的 RESULT_SET_TOO_LARGE 一致。
|
|
26
|
+
"RESULT_SET_TOO_LARGE": [
|
|
27
|
+
"Add `LIMIT <n>` to your SQL to narrow the result.",
|
|
28
|
+
"For counts, use `SELECT count(*) FROM ...`; for aggregation, use GROUP BY with filters.",
|
|
29
|
+
],
|
|
30
|
+
};
|
|
8
31
|
function isJsonMode() {
|
|
9
32
|
const cfg = (0, config_1.getConfig)();
|
|
10
33
|
return cfg.output === "json" || Boolean(cfg.json);
|
|
@@ -30,27 +53,70 @@ function emit(data) {
|
|
|
30
53
|
/**
|
|
31
54
|
* 输出错误(写入 stderr)
|
|
32
55
|
* - pretty 模式:Error: message\n hint: ...
|
|
33
|
-
* - json 模式:{
|
|
56
|
+
* - json 模式:PRD 约定 envelope 为 `{error: {code, message, hint?}}`
|
|
34
57
|
*/
|
|
35
58
|
function emitError(err) {
|
|
36
59
|
const info = toErrorInfo(err);
|
|
60
|
+
// 没人给过 next_actions 才走错误码兜底;handler 已经塞过具体 hint 时不覆盖
|
|
61
|
+
const hints = info.next_actions && info.next_actions.length > 0
|
|
62
|
+
? info.next_actions
|
|
63
|
+
: (SERVER_ERROR_HINTS[info.code] ?? []);
|
|
37
64
|
if (isJsonMode()) {
|
|
38
|
-
const
|
|
39
|
-
|
|
65
|
+
const errObj = {
|
|
66
|
+
code: info.code,
|
|
40
67
|
message: info.message,
|
|
41
68
|
};
|
|
42
|
-
if (
|
|
43
|
-
|
|
69
|
+
if (hints.length > 0) {
|
|
70
|
+
// JSON 输出压平成单行,更便于机器消费(脚本 / agent 拼字符串)
|
|
71
|
+
errObj.hint = hints.join(" ");
|
|
72
|
+
}
|
|
73
|
+
if (typeof info.statement_index === "number") {
|
|
74
|
+
errObj.statement_index = info.statement_index;
|
|
75
|
+
}
|
|
76
|
+
// PRD 多语句失败 envelope 额外字段:completed / rolled_back
|
|
77
|
+
if (Array.isArray(info.completed)) {
|
|
78
|
+
errObj.completed = info.completed;
|
|
44
79
|
}
|
|
45
|
-
|
|
80
|
+
if (typeof info.rolled_back === "boolean") {
|
|
81
|
+
errObj.rolled_back = info.rolled_back;
|
|
82
|
+
}
|
|
83
|
+
process.stderr.write(JSON.stringify({ error: errObj }) + "\n");
|
|
46
84
|
}
|
|
47
85
|
else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
86
|
+
// stderr 染色:picocolors 默认按 stdout.isTTY 判断;stderr 通常也是 tty,
|
|
87
|
+
// 这里复用 stdout 探测保持简单(stderr only-pipe 的极端场景留作后续优化)
|
|
88
|
+
// 多语句失败时 Error 行末尾追加 "(at statement K of N)",与 PRD spec 对齐
|
|
89
|
+
let errorLine = `${colors_1.c.fail("Error:")} ${info.message}`;
|
|
90
|
+
if (typeof info.statement_index === "number") {
|
|
91
|
+
const k = info.statement_index + 1;
|
|
92
|
+
const n = info.total_statements;
|
|
93
|
+
errorLine += typeof n === "number" && n > 0
|
|
94
|
+
? ` (at statement ${String(k)} of ${String(n)})`
|
|
95
|
+
: ` (at statement ${String(k)})`;
|
|
96
|
+
}
|
|
97
|
+
process.stderr.write(errorLine + "\n");
|
|
98
|
+
// 多行 hint:第一行带 "hint:" 标签,后续行用 8 空格缩进对齐 " hint: " 之后的列。
|
|
99
|
+
// 对应 spec 期望的格式:
|
|
100
|
+
// Error: ...
|
|
101
|
+
// hint: 第一条建议
|
|
102
|
+
// 第二条建议
|
|
103
|
+
if (hints.length > 0) {
|
|
104
|
+
const [first, ...rest] = hints;
|
|
105
|
+
process.stderr.write(` ${colors_1.c.muted("hint:")} ${first}\n`);
|
|
106
|
+
for (const line of rest) {
|
|
107
|
+
process.stderr.write(` ${line}\n`);
|
|
108
|
+
}
|
|
51
109
|
}
|
|
52
110
|
}
|
|
53
111
|
}
|
|
112
|
+
/** 发送成功 JSON envelope(非分页):`{ data }`。 */
|
|
113
|
+
function emitOk(data) {
|
|
114
|
+
emit({ data });
|
|
115
|
+
}
|
|
116
|
+
/** 发送成功 JSON envelope(分页信封:`{ data, next_cursor, has_more }`)。 */
|
|
117
|
+
function emitPaged(items, nextCursor, hasMore) {
|
|
118
|
+
emit({ data: items, next_cursor: nextCursor, has_more: hasMore });
|
|
119
|
+
}
|
|
54
120
|
function toErrorInfo(err) {
|
|
55
121
|
if (err instanceof error_1.AppError)
|
|
56
122
|
return err.toJSON();
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatSize = formatSize;
|
|
4
|
+
exports.formatTime = formatTime;
|
|
5
|
+
exports.visibleWidth = visibleWidth;
|
|
6
|
+
exports.renderAlignedTable = renderAlignedTable;
|
|
7
|
+
exports.renderTsv = renderTsv;
|
|
8
|
+
exports.renderKeyValue = renderKeyValue;
|
|
9
|
+
exports.isStdoutTty = isStdoutTty;
|
|
10
|
+
exports.parseDuration = parseDuration;
|
|
11
|
+
exports.parseSize = parseSize;
|
|
12
|
+
/**
|
|
13
|
+
* CLI 渲染 / 解析工具:跨域共用的格式化、表格渲染、字符串解析。
|
|
14
|
+
*
|
|
15
|
+
* 按 AGENTS.md §2.2,跨域共享逻辑统一下沉到 `src/utils/*`。
|
|
16
|
+
* 这里聚合 db / file 域 handler 共用的纯函数:
|
|
17
|
+
* - 格式化:formatSize / formatTime
|
|
18
|
+
* - 表格渲染:renderAlignedTable / renderTsv / renderKeyValue
|
|
19
|
+
* - 终端探测:isStdoutTty
|
|
20
|
+
* - 字符串解析:parseDuration / parseSize
|
|
21
|
+
*
|
|
22
|
+
* 彩色高亮的语义层封装见 ./colors.ts。表头 / key 标签等结构性元素由本文件
|
|
23
|
+
* 主动调用 colors.c 染色;业务文案的染色(成功/失败 prefix 等)由 handler 自治。
|
|
24
|
+
*
|
|
25
|
+
* JSON envelope 输出(emit / emitOk / emitPaged / emitError)见 ./output.ts。
|
|
26
|
+
*/
|
|
27
|
+
const colors_1 = require("./colors");
|
|
28
|
+
/** 将字节数格式化为人类可读(`24 KB` / `2.1 MB` / `1.5 GB`)。 */
|
|
29
|
+
function formatSize(bytes) {
|
|
30
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
31
|
+
return "—";
|
|
32
|
+
if (bytes >= 1 << 30)
|
|
33
|
+
return `${(bytes / (1 << 30)).toFixed(1)} GB`;
|
|
34
|
+
if (bytes >= 1 << 20)
|
|
35
|
+
return `${(bytes / (1 << 20)).toFixed(1)} MB`;
|
|
36
|
+
if (bytes >= 1 << 10)
|
|
37
|
+
return `${(bytes / (1 << 10)).toFixed(0)} KB`;
|
|
38
|
+
return `${String(bytes)} bytes`;
|
|
39
|
+
}
|
|
40
|
+
/** 将 ISO 时间转为 TTY 下更友好的相对时间;non-TTY 保持 ISO 原值。 */
|
|
41
|
+
function formatTime(iso, isTty) {
|
|
42
|
+
if (!iso)
|
|
43
|
+
return "—";
|
|
44
|
+
if (!isTty)
|
|
45
|
+
return iso;
|
|
46
|
+
const ts = Date.parse(iso);
|
|
47
|
+
if (Number.isNaN(ts))
|
|
48
|
+
return iso;
|
|
49
|
+
const diff = Date.now() - ts;
|
|
50
|
+
const minute = 60_000;
|
|
51
|
+
const hour = 60 * minute;
|
|
52
|
+
const day = 24 * hour;
|
|
53
|
+
if (diff < hour) {
|
|
54
|
+
const m = Math.max(1, Math.round(diff / minute));
|
|
55
|
+
return `${String(m)}m ago`;
|
|
56
|
+
}
|
|
57
|
+
if (diff < day) {
|
|
58
|
+
const h = Math.round(diff / hour);
|
|
59
|
+
return `${String(h)}h ago`;
|
|
60
|
+
}
|
|
61
|
+
if (diff < 7 * day) {
|
|
62
|
+
const d = Math.round(diff / day);
|
|
63
|
+
return `${String(d)}d ago`;
|
|
64
|
+
}
|
|
65
|
+
// 超过 7 天,用 YYYY-MM-DD
|
|
66
|
+
const date = new Date(ts);
|
|
67
|
+
const y = String(date.getFullYear());
|
|
68
|
+
const mo = String(date.getMonth() + 1).padStart(2, "0");
|
|
69
|
+
const da = String(date.getDate()).padStart(2, "0");
|
|
70
|
+
return `${y}-${mo}-${da}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* SGR (Select Graphic Rendition) ANSI 转义匹配:`ESC[<digits;...>m`。
|
|
74
|
+
* 用于在列宽计算时剥离不显示的控制字符——否则带 ANSI 颜色的 cell 会按 .length
|
|
75
|
+
* 算进列宽(比如 dim "NULL" 实际占 4 字符但 .length=11),导致表格对齐错位。
|
|
76
|
+
*/
|
|
77
|
+
const ANSI_SGR_RE = /\[[0-9;]*m/g;
|
|
78
|
+
/**
|
|
79
|
+
* 单字符的终端列宽:CJK 全角字符(汉字/假名/全角符号等)占 2 列,其它占 1 列。
|
|
80
|
+
* 对齐 Unicode East Asian Width 的 W/F 类,等价于 wcwidth 简化版。
|
|
81
|
+
* 不实现合字 / 零宽字符(ZWJ / 变体选择符)等极端情况,CLI 表格场景够用。
|
|
82
|
+
*/
|
|
83
|
+
function charWidth(cp) {
|
|
84
|
+
if ((cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
|
|
85
|
+
cp === 0x2329 || cp === 0x232A ||
|
|
86
|
+
(cp >= 0x2E80 && cp <= 0x303E) || // CJK Radicals / Punctuation
|
|
87
|
+
(cp >= 0x3041 && cp <= 0x33FF) || // Hiragana / Katakana / CJK Symbols
|
|
88
|
+
(cp >= 0x3400 && cp <= 0x4DBF) || // CJK Ext A
|
|
89
|
+
(cp >= 0x4E00 && cp <= 0x9FFF) || // CJK Unified
|
|
90
|
+
(cp >= 0xA000 && cp <= 0xA4CF) || // Yi
|
|
91
|
+
(cp >= 0xAC00 && cp <= 0xD7A3) || // Hangul Syllables
|
|
92
|
+
(cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compat Ideographs
|
|
93
|
+
(cp >= 0xFE30 && cp <= 0xFE4F) || // CJK Compat Forms
|
|
94
|
+
(cp >= 0xFF00 && cp <= 0xFF60) || // Fullwidth Forms
|
|
95
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) ||
|
|
96
|
+
(cp >= 0x20000 && cp <= 0x2FFFD) || // CJK Ext B-F
|
|
97
|
+
(cp >= 0x30000 && cp <= 0x3FFFD) // CJK Ext G-H
|
|
98
|
+
) {
|
|
99
|
+
return 2;
|
|
100
|
+
}
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
/** cell 可见字符宽度:剥离 ANSI 转义后按终端列宽逐字符累加(CJK 算 2 列)。 */
|
|
104
|
+
function visibleWidth(s) {
|
|
105
|
+
const stripped = s.replace(ANSI_SGR_RE, "");
|
|
106
|
+
let w = 0;
|
|
107
|
+
for (const c of stripped) {
|
|
108
|
+
w += charWidth(c.codePointAt(0) ?? 0);
|
|
109
|
+
}
|
|
110
|
+
return w;
|
|
111
|
+
}
|
|
112
|
+
function padVisibleEnd(s, targetWidth) {
|
|
113
|
+
const w = visibleWidth(s);
|
|
114
|
+
return w >= targetWidth ? s : s + " ".repeat(targetWidth - w);
|
|
115
|
+
}
|
|
116
|
+
/** 渲染 TTY 对齐表格(多行,列宽按最长内容;ANSI 转义不计入列宽)。
|
|
117
|
+
* 表头按 spec 用 bold + cyan 染色;ANSI 序列由 visibleWidth 剥离不影响列宽。 */
|
|
118
|
+
function renderAlignedTable(headers, rows) {
|
|
119
|
+
const colWidths = headers.map((h, i) => {
|
|
120
|
+
let w = visibleWidth(h);
|
|
121
|
+
for (const row of rows) {
|
|
122
|
+
const cw = visibleWidth(row[i] || "");
|
|
123
|
+
if (cw > w)
|
|
124
|
+
w = cw;
|
|
125
|
+
}
|
|
126
|
+
return w;
|
|
127
|
+
});
|
|
128
|
+
const lines = [];
|
|
129
|
+
lines.push(headers.map((h, i) => colors_1.c.header(padVisibleEnd(h, colWidths[i]))).join(" ").trimEnd());
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
lines.push(row.map((cell, i) => padVisibleEnd(cell || "", colWidths[i])).join(" ").trimEnd());
|
|
132
|
+
}
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
/** 渲染 non-TTY tab 分隔(字段原值,ISO 时间)。 */
|
|
136
|
+
function renderTsv(headers, rows) {
|
|
137
|
+
const lines = [];
|
|
138
|
+
lines.push(headers.join("\t"));
|
|
139
|
+
for (const row of rows) {
|
|
140
|
+
lines.push(row.join("\t"));
|
|
141
|
+
}
|
|
142
|
+
return lines.join("\n");
|
|
143
|
+
}
|
|
144
|
+
/** 渲染 key-value 多行(用于 stat 等单条详情)。key 右对齐 + bold cyan 染色。 */
|
|
145
|
+
function renderKeyValue(pairs, isTty) {
|
|
146
|
+
if (pairs.length === 0)
|
|
147
|
+
return "";
|
|
148
|
+
if (!isTty) {
|
|
149
|
+
return pairs.map(([k, v]) => `${k}\t${v}`).join("\n");
|
|
150
|
+
}
|
|
151
|
+
const keyWidth = Math.max(...pairs.map(([k]) => k.length));
|
|
152
|
+
return pairs
|
|
153
|
+
.map(([k, v]) => `${colors_1.c.header(k.padStart(keyWidth))}: ${v}`)
|
|
154
|
+
.join("\n");
|
|
155
|
+
}
|
|
156
|
+
/** 通用 isTTY 判定(stdout 是否交互终端)。Node 运行时 isTTY 为 true 或 undefined;TS 类型上 tty.WriteStream 定义为固定 true,绕开做运行时判断。 */
|
|
157
|
+
function isStdoutTty() {
|
|
158
|
+
const isTTY = process.stdout.isTTY;
|
|
159
|
+
return isTTY === true;
|
|
160
|
+
}
|
|
161
|
+
/** 解析时长字符串 `7d` / `24h` / `30m` → 秒。无后缀按秒。 */
|
|
162
|
+
function parseDuration(input) {
|
|
163
|
+
const m = /^(\d+)([smhd]?)$/.exec(input.trim());
|
|
164
|
+
if (!m) {
|
|
165
|
+
throw new Error(`Invalid duration: ${input}`);
|
|
166
|
+
}
|
|
167
|
+
const n = Number(m[1]);
|
|
168
|
+
const unit = m[2] || "s";
|
|
169
|
+
switch (unit) {
|
|
170
|
+
case "s": return n;
|
|
171
|
+
case "m": return n * 60;
|
|
172
|
+
case "h": return n * 3600;
|
|
173
|
+
case "d": return n * 86400;
|
|
174
|
+
default: return n;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** 解析 size 字符串 `1MB` / `500KB` / `1GB` → 字节。 */
|
|
178
|
+
function parseSize(input) {
|
|
179
|
+
const m = /^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)?$/i.exec(input.trim());
|
|
180
|
+
if (!m) {
|
|
181
|
+
throw new Error(`Invalid size: ${input}`);
|
|
182
|
+
}
|
|
183
|
+
const n = Number(m[1]);
|
|
184
|
+
const unit = (m[2] || "B").toUpperCase();
|
|
185
|
+
switch (unit) {
|
|
186
|
+
case "B": return Math.round(n);
|
|
187
|
+
case "KB": return Math.round(n * 1024);
|
|
188
|
+
case "MB": return Math.round(n * 1024 * 1024);
|
|
189
|
+
case "GB": return Math.round(n * 1024 * 1024 * 1024);
|
|
190
|
+
default: return Math.round(n);
|
|
191
|
+
}
|
|
192
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/miaoda-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-alpha.07b4fd5",
|
|
4
4
|
"description": "Miaoda 平台命令行工具,面向 Agent 调用",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"node": ">=20"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@lark-apaas/http-client": "^0.1.
|
|
29
|
-
"commander": "^13.1.0"
|
|
28
|
+
"@lark-apaas/http-client": "^0.1.5",
|
|
29
|
+
"commander": "^13.1.0",
|
|
30
|
+
"picocolors": "^1.1.1"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@types/node": "^22.15.3",
|