@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,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MiaodaHelp = void 0;
4
+ const commander_1 = require("commander");
5
+ /**
6
+ * MiaodaHelp 重写 commander 默认的 --help 输出,使之对齐 CLI 文档规范:
7
+ *
8
+ * 1. 描述放在最前(commander 默认是 Usage 在前、描述在后)
9
+ * 2. "Options:" 重命名为 "Flags:","Global Options:" 重命名为 "Global Flags:"
10
+ * 3. Usage 段独占一行 Heading + 缩进展示 usage 行
11
+ * 4. 段落顺序:描述 → Usage → Arguments → Commands → Flags → Global Flags
12
+ * → Notes(addHelpText) → Examples(addHelpText)
13
+ * (父命令不带 Examples,由 formatHelp 末尾自动追加 "Use ... --help" 提示)
14
+ * 5. Root 命令的 local options 视作 Global Flags(root 无 command-specific flag)
15
+ * 6. 子命令隐藏 -v / --version(仅 root 暴露)和自动生成的 `help` 子命令
16
+ *
17
+ * Notes / Examples 段由各命令通过 addHelpText('after', ...) 自行追加,
18
+ * 本类不直接生成 —— 框架与文案分层。
19
+ */
20
+ class MiaodaHelp extends commander_1.Help {
21
+ // 全局默认开启:所有子命令 --help 都展示 Global Flags 段
22
+ showGlobalOptions = true;
23
+ /**
24
+ * 父级 --help 的 Commands 列表里展示子命令调用形态。spec 要求只展示
25
+ * `name <args>` 不带 `[flags]` 尾巴:
26
+ *
27
+ * - 子命令是分组(含下级 subcommand)→ 只显示 name
28
+ * - 子命令是 leaf 且配置了 usage() → "name <args>"(usage 末尾的 [flags] 去掉)
29
+ * - leaf 没配置 usage → 退回 commander 默认行为
30
+ */
31
+ subcommandTerm(cmd) {
32
+ if (cmd.commands.length > 0) {
33
+ return cmd.name();
34
+ }
35
+ const usage = cmd.usage();
36
+ if (usage) {
37
+ // 去掉末尾的 [flags] / [options],对齐 spec 的 "name <args>" 形态
38
+ const argsOnly = usage.replace(/\s*\[(?:flags|options)\]\s*$/i, "").trim();
39
+ return argsOnly ? `${cmd.name()} ${argsOnly}` : cmd.name();
40
+ }
41
+ return super.subcommandTerm(cmd);
42
+ }
43
+ /**
44
+ * 父命令的 Commands 列表里:
45
+ * - 优先用子命令的 .summary()(短摘要,对齐 spec 父级列表用的简短描述)
46
+ * - 否则取 description 首行,避免多行 description 把列表撑乱
47
+ * 叶子命令自身 --help 仍展示完整 description。
48
+ */
49
+ subcommandDescription(cmd) {
50
+ const summary = cmd.summary();
51
+ if (summary)
52
+ return summary;
53
+ const desc = super.subcommandDescription(cmd);
54
+ const idx = desc.indexOf("\n");
55
+ return idx === -1 ? desc : desc.slice(0, idx);
56
+ }
57
+ /**
58
+ * Flags 段里去掉那些已经在父命令注册过的选项(避免在 Flags 与 Global Flags
59
+ * 里重复展示,例如 --env 在 db 父级注册后,leaf 即使本地也注册一份也只在
60
+ * Global Flags 里出现一次)。
61
+ */
62
+ visibleOptions(cmd) {
63
+ const opts = super.visibleOptions(cmd);
64
+ if (!cmd.parent)
65
+ return opts;
66
+ const parentLongs = new Set();
67
+ let p = cmd.parent;
68
+ while (p) {
69
+ for (const o of p.options) {
70
+ if (o.long)
71
+ parentLongs.add(o.long);
72
+ }
73
+ p = p.parent;
74
+ }
75
+ return opts.filter((o) => !o.long || !parentLongs.has(o.long));
76
+ }
77
+ /**
78
+ * 子命令 --help 默认会从父级继承 -v, --version;spec 只在 root 列这条。
79
+ * 非 root 命令把 --version 从 Global Flags 列表里过滤掉。
80
+ */
81
+ visibleGlobalOptions(cmd) {
82
+ const opts = super.visibleGlobalOptions(cmd);
83
+ if (cmd.parent) {
84
+ return opts.filter((o) => o.long !== "--version" && o.short !== "-v");
85
+ }
86
+ return opts;
87
+ }
88
+ /**
89
+ * Root 命令默认会列 commander 自动生成的 `help [command]` 子命令;
90
+ * spec 不展示这一条,过滤掉。
91
+ */
92
+ visibleCommands(cmd) {
93
+ return super.visibleCommands(cmd).filter((c) => c.name() !== "help");
94
+ }
95
+ formatHelp(cmd, helper) {
96
+ const isRoot = cmd.parent == null;
97
+ const termWidth = helper.padWidth(cmd, helper);
98
+ const helpWidth = helper.helpWidth ?? 80;
99
+ const formatItem = (term, description) => {
100
+ if (description) {
101
+ const padding = " ".repeat(Math.max(termWidth - term.length, 0) + 2);
102
+ return `${term}${padding}${description}`;
103
+ }
104
+ return term;
105
+ };
106
+ const formatList = (lines) => lines.map((l) => " " + l).join("\n");
107
+ void helpWidth; // 保留以备后续按宽度自动 wrap,当前直接透传 description
108
+ const out = [];
109
+ // 1. 描述
110
+ const desc = helper.commandDescription(cmd);
111
+ if (desc) {
112
+ out.push(desc, "");
113
+ }
114
+ // 2. Usage:独立 heading + 缩进
115
+ out.push("Usage:", ` ${helper.commandUsage(cmd)}`, "");
116
+ // 3. Commands(仅父级命令组有,spec 要求 Commands 在 Flags 前)
117
+ // spec 不展示 Arguments 段,参数说明放在 description 文本里
118
+ const subs = helper.visibleCommands(cmd).map((c) => formatItem(helper.subcommandTerm(c), helper.subcommandDescription(c)));
119
+ if (subs.length) {
120
+ out.push("Commands:", formatList(subs), "");
121
+ }
122
+ // 5. Flags(叶子命令专属 options)
123
+ // - Root / 父命令组:local options 都视作"会被子命令继承的 globals",渲染到 Global Flags 段
124
+ // - 叶子命令(无子命令):local options 渲染到 Flags 段(如 db data export 的 --format)
125
+ // - `-h, --help` 永远不放 Flags 段,统一放 Global Flags(spec 约定)
126
+ const isParent = subs.length > 0;
127
+ if (!isRoot && !isParent) {
128
+ const opts = helper.visibleOptions(cmd)
129
+ .filter((o) => !isHelpOption(o))
130
+ .map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
131
+ if (opts.length) {
132
+ out.push("Flags:", formatList(opts), "");
133
+ }
134
+ }
135
+ // 6. Global Flags
136
+ // - Root:local options 当 globals 渲染(root 自己就是 global 来源)
137
+ // - 父命令组:继承自祖先的 globals + 自己的 local options(也会被子命令继承)
138
+ // - 叶子命令:继承自祖先的 globals + 当前的 -h, --help
139
+ // - 末尾确保 -h, --help 一条
140
+ const localOpts = helper.visibleOptions(cmd);
141
+ let globals = [];
142
+ if (isRoot) {
143
+ globals = localOpts;
144
+ }
145
+ else if (isParent) {
146
+ const inherited = helper.visibleGlobalOptions(cmd);
147
+ const localNonHelp = localOpts.filter((o) => !isHelpOption(o));
148
+ const helpOpt = localOpts.find(isHelpOption);
149
+ globals = [...inherited, ...localNonHelp];
150
+ if (helpOpt && !globals.includes(helpOpt))
151
+ globals.push(helpOpt);
152
+ }
153
+ else if (this.showGlobalOptions) {
154
+ const inherited = helper.visibleGlobalOptions(cmd);
155
+ const helpOpt = localOpts.find(isHelpOption);
156
+ globals = [...inherited];
157
+ if (helpOpt && !globals.includes(helpOpt))
158
+ globals.push(helpOpt);
159
+ }
160
+ if (globals.length) {
161
+ const lines = globals.map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
162
+ out.push("Global Flags:", formatList(lines), "");
163
+ }
164
+ // 7. 父命令底部追加 "Use <cmd> <subcommand> --help" 提示,对齐 spec
165
+ if (subs.length > 0) {
166
+ const path = cmd.name() === "miaoda" ? "miaoda" : commandPath(cmd);
167
+ out.push(`Use "${path} <command> --help" for more information about a command.`, "");
168
+ }
169
+ // 保留末尾换行:commander 用 join('\n') 拼 addHelpText('after') 段,
170
+ // 这里多留一个 \n,让 Notes / Examples 段与上面段落之间空一行。
171
+ return out.join("\n").replace(/\n+$/, "\n");
172
+ }
173
+ }
174
+ exports.MiaodaHelp = MiaodaHelp;
175
+ /** 判断是否为 -h / --help 选项。 */
176
+ function isHelpOption(o) {
177
+ return o.long === "--help" || o.short === "-h";
178
+ }
179
+ /** 拼接命令完整路径,例如 db schema -> "miaoda db schema"。 */
180
+ function commandPath(cmd) {
181
+ const names = [];
182
+ let cur = cmd;
183
+ while (cur) {
184
+ names.unshift(cur.name());
185
+ cur = cur.parent;
186
+ }
187
+ return names.join(" ");
188
+ }
package/dist/main.js CHANGED
@@ -5,15 +5,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const commander_1 = require("commander");
7
7
  const index_1 = require("./cli/commands/index");
8
+ const help_1 = require("./cli/help");
8
9
  const config_1 = require("./utils/config");
9
10
  const log_id_1 = require("./utils/log_id");
10
11
  const output_1 = require("./utils/output");
11
12
  const package_json_1 = __importDefault(require("../package.json"));
13
+ // MiaodaHelp 对齐 CLI 规范(描述置顶 / Flags / Global Flags / 段顺序):
14
+ // 在 Command.prototype 上 patch createHelp,让所有命令实例(含动态注册的
15
+ // 子命令)统一走 MiaodaHelp 渲染,避免在每个子命令上重复 configureHelp。
16
+ commander_1.Command.prototype.createHelp = function () {
17
+ return Object.assign(new help_1.MiaodaHelp(), this.configureHelp());
18
+ };
12
19
  const program = new commander_1.Command();
13
20
  const { version } = package_json_1.default;
14
21
  program
15
22
  .name("miaoda")
16
- .description("妙搭平台命令行工具")
23
+ .description("妙搭平台 CLI,提供数据服务、文件存储、插件管理等命令行操作。")
24
+ .usage("<command> [flags]")
17
25
  .version(version, "-v, --version", "显示版本号")
18
26
  .option("--json [fields]", "JSON 输出,可选字段级选择")
19
27
  .option("--output <format>", "输出格式(pretty|json)", "pretty")
@@ -5,12 +5,14 @@ class AppError extends Error {
5
5
  code;
6
6
  retryable;
7
7
  next_actions;
8
+ statement_index;
8
9
  constructor(code, message, opts) {
9
10
  super(message);
10
11
  this.name = "AppError";
11
12
  this.code = code;
12
13
  this.retryable = opts?.retryable ?? false;
13
14
  this.next_actions = opts?.next_actions ?? [];
15
+ this.statement_index = opts?.statement_index;
14
16
  }
15
17
  toJSON() {
16
18
  return {
@@ -18,6 +20,7 @@ class AppError extends Error {
18
20
  message: this.message,
19
21
  retryable: this.retryable,
20
22
  next_actions: this.next_actions,
23
+ statement_index: this.statement_index,
21
24
  };
22
25
  }
23
26
  }
@@ -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 client;
9
- /** 获取单例 HttpClient,首次调用时初始化 */
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
- client ??= createClient();
12
- return client;
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
- client = undefined;
32
+ adminClient = undefined;
33
+ runtimeClient = undefined;
17
34
  }
18
- /** 替换为自定义 HttpClient 实例(用于测试 mock */
35
+ /** 替换管理端 HttpClient 实例(用于测试 mock)。 */
19
36
  function setHttpClient(custom) {
20
- client = custom;
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
- tokenProvider: { type: "file" },
48
+ adminInnerApi: opts.adminInnerApi,
28
49
  },
29
50
  security: {
30
51
  strictMode: true,
@@ -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; } });
@@ -3,6 +3,8 @@ 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");
8
10
  function isJsonMode() {
@@ -30,19 +32,22 @@ function emit(data) {
30
32
  /**
31
33
  * 输出错误(写入 stderr)
32
34
  * - pretty 模式:Error: message\n hint: ...
33
- * - json 模式:{"error_code": "...", "message": "...", "hint": "..."}
35
+ * - json 模式:PRD 约定 envelope 为 `{error: {code, message, hint?}}`
34
36
  */
35
37
  function emitError(err) {
36
38
  const info = toErrorInfo(err);
37
39
  if (isJsonMode()) {
38
- const jsonErr = {
39
- error_code: info.code,
40
+ const errObj = {
41
+ code: info.code,
40
42
  message: info.message,
41
43
  };
42
44
  if (info.next_actions && info.next_actions.length > 0) {
43
- jsonErr.hint = info.next_actions.join(" ");
45
+ errObj.hint = info.next_actions.join(" ");
44
46
  }
45
- process.stderr.write(JSON.stringify(jsonErr) + "\n");
47
+ if (typeof info.statement_index === "number") {
48
+ errObj.statement_index = info.statement_index;
49
+ }
50
+ process.stderr.write(JSON.stringify({ error: errObj }) + "\n");
46
51
  }
47
52
  else {
48
53
  process.stderr.write(`Error: ${info.message}\n`);
@@ -51,6 +56,14 @@ function emitError(err) {
51
56
  }
52
57
  }
53
58
  }
59
+ /** 发送成功 JSON envelope(非分页):`{ data }`。 */
60
+ function emitOk(data) {
61
+ emit({ data });
62
+ }
63
+ /** 发送成功 JSON envelope(分页信封:`{ data, next_cursor, has_more }`)。 */
64
+ function emitPaged(items, nextCursor, hasMore) {
65
+ emit({ data: items, next_cursor: nextCursor, has_more: hasMore });
66
+ }
54
67
  function toErrorInfo(err) {
55
68
  if (err instanceof error_1.AppError)
56
69
  return err.toJSON();
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /**
3
+ * CLI 渲染 / 解析工具:跨域共用的格式化、表格渲染、字符串解析。
4
+ *
5
+ * 按 AGENTS.md §2.2,跨域共享逻辑统一下沉到 `src/utils/*`。
6
+ * 这里聚合 db / file 域 handler 共用的纯函数:
7
+ * - 格式化:formatSize / formatTime
8
+ * - 表格渲染:renderAlignedTable / renderTsv / renderKeyValue
9
+ * - 终端探测:isStdoutTty
10
+ * - 字符串解析:parseDuration / parseSize
11
+ *
12
+ * JSON envelope 输出(emit / emitOk / emitPaged / emitError)见 ./output.ts。
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.formatSize = formatSize;
16
+ exports.formatTime = formatTime;
17
+ exports.visibleWidth = visibleWidth;
18
+ exports.renderAlignedTable = renderAlignedTable;
19
+ exports.renderTsv = renderTsv;
20
+ exports.renderKeyValue = renderKeyValue;
21
+ exports.isStdoutTty = isStdoutTty;
22
+ exports.parseDuration = parseDuration;
23
+ exports.parseSize = parseSize;
24
+ /** 将字节数格式化为人类可读(`24 KB` / `2.1 MB` / `1.5 GB`)。 */
25
+ function formatSize(bytes) {
26
+ if (!Number.isFinite(bytes) || bytes < 0)
27
+ return "—";
28
+ if (bytes >= 1 << 30)
29
+ return `${(bytes / (1 << 30)).toFixed(1)} GB`;
30
+ if (bytes >= 1 << 20)
31
+ return `${(bytes / (1 << 20)).toFixed(1)} MB`;
32
+ if (bytes >= 1 << 10)
33
+ return `${(bytes / (1 << 10)).toFixed(0)} KB`;
34
+ return `${String(bytes)} bytes`;
35
+ }
36
+ /** 将 ISO 时间转为 TTY 下更友好的相对时间;non-TTY 保持 ISO 原值。 */
37
+ function formatTime(iso, isTty) {
38
+ if (!iso)
39
+ return "—";
40
+ if (!isTty)
41
+ return iso;
42
+ const ts = Date.parse(iso);
43
+ if (Number.isNaN(ts))
44
+ return iso;
45
+ const diff = Date.now() - ts;
46
+ const minute = 60_000;
47
+ const hour = 60 * minute;
48
+ const day = 24 * hour;
49
+ if (diff < hour) {
50
+ const m = Math.max(1, Math.round(diff / minute));
51
+ return `${String(m)}m ago`;
52
+ }
53
+ if (diff < day) {
54
+ const h = Math.round(diff / hour);
55
+ return `${String(h)}h ago`;
56
+ }
57
+ if (diff < 7 * day) {
58
+ const d = Math.round(diff / day);
59
+ return `${String(d)}d ago`;
60
+ }
61
+ // 超过 7 天,用 YYYY-MM-DD
62
+ const date = new Date(ts);
63
+ const y = String(date.getFullYear());
64
+ const mo = String(date.getMonth() + 1).padStart(2, "0");
65
+ const da = String(date.getDate()).padStart(2, "0");
66
+ return `${y}-${mo}-${da}`;
67
+ }
68
+ /**
69
+ * SGR (Select Graphic Rendition) ANSI 转义匹配:`ESC[<digits;...>m`。
70
+ * 用于在列宽计算时剥离不显示的控制字符——否则带 ANSI 颜色的 cell 会按 .length
71
+ * 算进列宽(比如 dim "NULL" 实际占 4 字符但 .length=11),导致表格对齐错位。
72
+ */
73
+ const ANSI_SGR_RE = /\[[0-9;]*m/g;
74
+ /**
75
+ * 单字符的终端列宽:CJK 全角字符(汉字/假名/全角符号等)占 2 列,其它占 1 列。
76
+ * 对齐 Unicode East Asian Width 的 W/F 类,等价于 wcwidth 简化版。
77
+ * 不实现合字 / 零宽字符(ZWJ / 变体选择符)等极端情况,CLI 表格场景够用。
78
+ */
79
+ function charWidth(cp) {
80
+ if ((cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
81
+ cp === 0x2329 || cp === 0x232A ||
82
+ (cp >= 0x2E80 && cp <= 0x303E) || // CJK Radicals / Punctuation
83
+ (cp >= 0x3041 && cp <= 0x33FF) || // Hiragana / Katakana / CJK Symbols
84
+ (cp >= 0x3400 && cp <= 0x4DBF) || // CJK Ext A
85
+ (cp >= 0x4E00 && cp <= 0x9FFF) || // CJK Unified
86
+ (cp >= 0xA000 && cp <= 0xA4CF) || // Yi
87
+ (cp >= 0xAC00 && cp <= 0xD7A3) || // Hangul Syllables
88
+ (cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compat Ideographs
89
+ (cp >= 0xFE30 && cp <= 0xFE4F) || // CJK Compat Forms
90
+ (cp >= 0xFF00 && cp <= 0xFF60) || // Fullwidth Forms
91
+ (cp >= 0xFFE0 && cp <= 0xFFE6) ||
92
+ (cp >= 0x20000 && cp <= 0x2FFFD) || // CJK Ext B-F
93
+ (cp >= 0x30000 && cp <= 0x3FFFD) // CJK Ext G-H
94
+ ) {
95
+ return 2;
96
+ }
97
+ return 1;
98
+ }
99
+ /** cell 可见字符宽度:剥离 ANSI 转义后按终端列宽逐字符累加(CJK 算 2 列)。 */
100
+ function visibleWidth(s) {
101
+ const stripped = s.replace(ANSI_SGR_RE, "");
102
+ let w = 0;
103
+ for (const c of stripped) {
104
+ w += charWidth(c.codePointAt(0) ?? 0);
105
+ }
106
+ return w;
107
+ }
108
+ function padVisibleEnd(s, targetWidth) {
109
+ const w = visibleWidth(s);
110
+ return w >= targetWidth ? s : s + " ".repeat(targetWidth - w);
111
+ }
112
+ /** 渲染 TTY 对齐表格(多行,列宽按最长内容;ANSI 转义不计入列宽)。 */
113
+ function renderAlignedTable(headers, rows) {
114
+ const colWidths = headers.map((h, i) => {
115
+ let w = visibleWidth(h);
116
+ for (const row of rows) {
117
+ const cw = visibleWidth(row[i] || "");
118
+ if (cw > w)
119
+ w = cw;
120
+ }
121
+ return w;
122
+ });
123
+ const lines = [];
124
+ lines.push(headers.map((h, i) => padVisibleEnd(h, colWidths[i])).join(" ").trimEnd());
125
+ for (const row of rows) {
126
+ lines.push(row.map((cell, i) => padVisibleEnd(cell || "", colWidths[i])).join(" ").trimEnd());
127
+ }
128
+ return lines.join("\n");
129
+ }
130
+ /** 渲染 non-TTY tab 分隔(字段原值,ISO 时间)。 */
131
+ function renderTsv(headers, rows) {
132
+ const lines = [];
133
+ lines.push(headers.join("\t"));
134
+ for (const row of rows) {
135
+ lines.push(row.join("\t"));
136
+ }
137
+ return lines.join("\n");
138
+ }
139
+ /** 渲染 key-value 多行(用于 stat 等单条详情)。key 右对齐。 */
140
+ function renderKeyValue(pairs, isTty) {
141
+ if (pairs.length === 0)
142
+ return "";
143
+ if (!isTty) {
144
+ return pairs.map(([k, v]) => `${k}\t${v}`).join("\n");
145
+ }
146
+ const keyWidth = Math.max(...pairs.map(([k]) => k.length));
147
+ return pairs
148
+ .map(([k, v]) => `${k.padStart(keyWidth)}: ${v}`)
149
+ .join("\n");
150
+ }
151
+ /** 通用 isTTY 判定(stdout 是否交互终端)。Node 运行时 isTTY 为 true 或 undefined;TS 类型上 tty.WriteStream 定义为固定 true,绕开做运行时判断。 */
152
+ function isStdoutTty() {
153
+ const isTTY = process.stdout.isTTY;
154
+ return isTTY === true;
155
+ }
156
+ /** 解析时长字符串 `7d` / `24h` / `30m` → 秒。无后缀按秒。 */
157
+ function parseDuration(input) {
158
+ const m = /^(\d+)([smhd]?)$/.exec(input.trim());
159
+ if (!m) {
160
+ throw new Error(`Invalid duration: ${input}`);
161
+ }
162
+ const n = Number(m[1]);
163
+ const unit = m[2] || "s";
164
+ switch (unit) {
165
+ case "s": return n;
166
+ case "m": return n * 60;
167
+ case "h": return n * 3600;
168
+ case "d": return n * 86400;
169
+ default: return n;
170
+ }
171
+ }
172
+ /** 解析 size 字符串 `1MB` / `500KB` / `1GB` → 字节。 */
173
+ function parseSize(input) {
174
+ const m = /^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)?$/i.exec(input.trim());
175
+ if (!m) {
176
+ throw new Error(`Invalid size: ${input}`);
177
+ }
178
+ const n = Number(m[1]);
179
+ const unit = (m[2] || "B").toUpperCase();
180
+ switch (unit) {
181
+ case "B": return Math.round(n);
182
+ case "KB": return Math.round(n * 1024);
183
+ case "MB": return Math.round(n * 1024 * 1024);
184
+ case "GB": return Math.round(n * 1024 * 1024 * 1024);
185
+ default: return Math.round(n);
186
+ }
187
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "node": ">=20"
26
26
  },
27
27
  "dependencies": {
28
- "@lark-apaas/http-client": "^0.1.3",
28
+ "@lark-apaas/http-client": "^0.1.5",
29
29
  "commander": "^13.1.0"
30
30
  },
31
31
  "devDependencies": {