@lark-apaas/miaoda-cli 0.1.0-alpha.08508f4 → 0.1.0-alpha.0d863af

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.
@@ -6,7 +6,36 @@ exports.importData = importData;
6
6
  exports.exportData = exportData;
7
7
  const http_1 = require("../../utils/http");
8
8
  const error_1 = require("../../utils/error");
9
+ const http_client_1 = require("@lark-apaas/http-client");
9
10
  const client_1 = require("./client");
11
+ /**
12
+ * 把 SDK 抛出的 HttpError 统一映射成 CLI 层错误:
13
+ * 1. 先尝试从 response body 解 envelope,命中 dataloom 业务 code → AppError
14
+ * 2. 兜底返 HttpError,保留真实 status 码与上下文
15
+ *
16
+ * 配合调用点的 try/catch + traceHttp,让 --verbose 在错误路径上也能拿到
17
+ * x-tt-logid 与 status,方便定位线上问题。
18
+ */
19
+ async function mapDbHttpError(err, url, ctx) {
20
+ if (err instanceof error_1.AppError)
21
+ throw err;
22
+ if (err instanceof http_client_1.HttpError) {
23
+ const status = err.response?.status ?? 0;
24
+ const statusText = err.response?.statusText ?? "";
25
+ try {
26
+ const body = (await err.response?.json());
27
+ if (body)
28
+ (0, client_1.extractData)(body); // 业务 code 命中 → 抛 AppError;不命中走兜底
29
+ }
30
+ catch (innerErr) {
31
+ if (innerErr instanceof error_1.AppError)
32
+ throw innerErr;
33
+ // body 解析失败 → 当成无 envelope 的纯 HTTP 错误处理
34
+ }
35
+ throw new error_1.HttpError(status, url, `${ctx}: ${String(status)} ${statusText}`.trim());
36
+ }
37
+ throw err;
38
+ }
10
39
  // CLI 不再为 dbBranch 设默认值:
11
40
  // 用户没传 --env 就完全不携带 dbBranch query 参数,由后端 admin-inner 中间件
12
41
  // 按 workspace 多环境状态决定(多环境 → dev / 单环境 → main)。
@@ -25,20 +54,15 @@ async function execSql(opts) {
25
54
  dbBranch: opts.dbBranch,
26
55
  });
27
56
  const start = Date.now();
28
- const response = await client.post(url, { sql: opts.sql });
29
- (0, client_1.traceHttp)("POST", url, start, response);
30
- if (!response.ok) {
31
- // 4xx / 5xx:尝试解析 body 取 status_code 映射业务错误
32
- let body = null;
33
- try {
34
- body = (await response.json());
35
- }
36
- catch {
37
- // ignore
38
- }
39
- if (body)
40
- (0, client_1.extractData)(body);
41
- throw new error_1.HttpError(response.status, url, `Failed to execute SQL: ${String(response.status)} ${response.statusText}`);
57
+ let response;
58
+ try {
59
+ response = await client.post(url, { sql: opts.sql });
60
+ (0, client_1.traceHttp)("POST", url, start, response);
61
+ }
62
+ catch (err) {
63
+ (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
64
+ await mapDbHttpError(err, url, "Failed to execute SQL");
65
+ throw err; // 不可达
42
66
  }
43
67
  const body = (await response.json());
44
68
  const data = (0, client_1.extractData)(body);
@@ -60,19 +84,15 @@ async function getSchema(opts) {
60
84
  dbBranch: opts.dbBranch,
61
85
  });
62
86
  const start = Date.now();
63
- const response = await client.get(url);
64
- (0, client_1.traceHttp)("GET", url, start, response);
65
- if (!response.ok) {
66
- let body = null;
67
- try {
68
- body = (await response.json());
69
- }
70
- catch {
71
- // ignore
72
- }
73
- if (body)
74
- (0, client_1.extractData)(body);
75
- throw new error_1.HttpError(response.status, url, `Failed to get schema: ${String(response.status)} ${response.statusText}`);
87
+ let response;
88
+ try {
89
+ response = await client.get(url);
90
+ (0, client_1.traceHttp)("GET", url, start, response);
91
+ }
92
+ catch (err) {
93
+ (0, client_1.traceHttp)("GET", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
94
+ await mapDbHttpError(err, url, "Failed to get schema");
95
+ throw err; // 不可达
76
96
  }
77
97
  const body = (await response.json());
78
98
  return (0, client_1.extractData)(body);
@@ -101,19 +121,15 @@ async function importData(opts) {
101
121
  reqBody.dbBranch = opts.dbBranch;
102
122
  }
103
123
  const start = Date.now();
104
- const response = await client.post(url, reqBody);
105
- (0, client_1.traceHttp)("POST", url, start, response);
106
- if (!response.ok) {
107
- let body = null;
108
- try {
109
- body = (await response.json());
110
- }
111
- catch {
112
- // ignore
113
- }
114
- if (body)
115
- (0, client_1.extractData)(body);
116
- throw new error_1.HttpError(response.status, url, `Failed to import data: ${String(response.status)} ${response.statusText}`);
124
+ let response;
125
+ try {
126
+ response = await client.post(url, reqBody);
127
+ (0, client_1.traceHttp)("POST", url, start, response);
128
+ }
129
+ catch (err) {
130
+ (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
131
+ await mapDbHttpError(err, url, "Failed to import data");
132
+ throw err; // 不可达
117
133
  }
118
134
  // 后端 InnerAdminImportData 响应里 data 直接返 {tableName, recordCount, durationMs}
119
135
  const body = (await response.json());
@@ -144,20 +160,15 @@ async function exportData(opts) {
144
160
  });
145
161
  // POST + 空 body:所有业务参数都在 query 里
146
162
  const start = Date.now();
147
- const response = await client.request({ method: "POST", url });
148
- (0, client_1.traceHttp)("POST", url, start, response);
149
- if (!response.ok) {
150
- // 错误路径:body JSON envelope
151
- let body = null;
152
- try {
153
- body = (await response.json());
154
- }
155
- catch {
156
- // ignore
157
- }
158
- if (body)
159
- (0, client_1.extractData)(body);
160
- throw new error_1.HttpError(response.status, url, `Failed to export data: ${String(response.status)} ${response.statusText}`);
163
+ let response;
164
+ try {
165
+ response = await client.request({ method: "POST", url });
166
+ (0, client_1.traceHttp)("POST", url, start, response);
167
+ }
168
+ catch (err) {
169
+ (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
170
+ await mapDbHttpError(err, url, "Failed to export data");
171
+ throw err; // 不可达
161
172
  }
162
173
  // 成功路径:响应 body 通常是原始 CSV/JSON 字节,但部分错误场景下网关会返
163
174
  // HTTP 200 + JSON envelope(status_code != "0"),需要在这里嗅探兜底。
@@ -5,65 +5,174 @@ const index_1 = require("../../../cli/handlers/db/index");
5
5
  function registerDbCommands(program) {
6
6
  const dbCmd = program
7
7
  .command("db")
8
- .description("数据库操作:执行 SQL、查看表结构、导入导出数据");
8
+ .description("数据库操作:执行 SQL、查看表结构、导入导出数据")
9
+ .usage("<command> [flags]");
9
10
  dbCmd.action(() => {
10
11
  dbCmd.outputHelp();
11
12
  });
13
+ dbCmd.addHelpText("after", `
14
+ Examples:
15
+ $ miaoda db sql "SELECT * FROM users LIMIT 5"
16
+ $ miaoda db schema list
17
+ $ miaoda db schema get users
18
+ $ miaoda db data import users.csv
19
+ $ miaoda db data export users
20
+ `);
12
21
  dbCmd
13
22
  .command("sql")
14
- .description("执行 SQL 语句")
15
- .argument("[query]", "要执行的 SQL 语句")
16
- .option("--env <env>", "目标环境(main / dev)")
23
+ .description("执行 SQL 语句(DDL/DML/SELECT 任意 PG 语句)")
24
+ .usage("[query] [flags]")
25
+ .argument("[query]", "SQL 语句;省略时从 stdin 读取")
26
+ .option("--env <env>", "目标环境(main / dev),缺省按多环境状态兜底")
17
27
  .action(async (query, opts) => {
18
28
  await (0, index_1.handleDbSql)(query, opts);
19
- });
29
+ })
30
+ .addHelpText("after", `
31
+ Notes:
32
+ - DML 单语句 affectedRows 与 SELECT 单语句返回行数上限均为 1000,超过即拒绝
33
+ - 多语句以 ; 分隔,失败时错误响应携带 statement_index 定位失败位置
34
+ - 支持用户自管事务 BEGIN / COMMIT / ROLLBACK;事务中途失败服务端会自动 ROLLBACK
35
+
36
+ Examples:
37
+ $ miaoda db sql "SELECT * FROM users LIMIT 3"
38
+ ✓ 3 rows
39
+
40
+ $ miaoda db sql "CREATE TABLE t(id int)"
41
+ ✓ Statement executed
42
+
43
+ $ cat migration.sql | miaoda db sql
44
+ ✓ 5 statements executed
45
+
46
+ $ miaoda db sql "SELECT count(*) FROM orders" --json
47
+ [{"count": 1234}]
48
+
49
+ # 报错:多语句中第 1 条失败
50
+ $ miaoda db sql "CREATE TABLE t(id int); SELECT * FROM no_such"
51
+ Error: TABLE_NOT_FOUND at statement 1
52
+ hint: Run \`miaoda db schema list\` to see existing tables.
53
+ `);
20
54
  // schema 二级资源分组
21
55
  const schemaCmd = dbCmd
22
56
  .command("schema")
23
- .description("查看表结构");
57
+ .description("查看表结构(list / get)")
58
+ .usage("<command> [flags]");
24
59
  schemaCmd.action(() => {
25
60
  schemaCmd.outputHelp();
26
61
  });
27
62
  schemaCmd
28
63
  .command("list")
29
64
  .description("列出当前应用的所有表(含行数估算、占用大小、列数)")
30
- .option("--env <env>", "目标环境(main / dev)")
65
+ .usage("[flags]")
66
+ .option("--env <env>", "目标环境(main / dev),缺省按多环境状态兜底")
31
67
  .action(async (opts) => {
32
68
  await (0, index_1.handleDbSchemaList)(opts);
33
- });
69
+ })
70
+ .addHelpText("after", `
71
+ Examples:
72
+ $ miaoda db schema list
73
+ name rows size_bytes columns
74
+ users 120 65536 6
75
+ orders 5400 327680 9
76
+
77
+ $ miaoda db schema list --json | jq '.[].name'
78
+ "users"
79
+ "orders"
80
+
81
+ $ miaoda db schema list --env main
82
+ `);
34
83
  schemaCmd
35
84
  .command("get")
36
- .description("查看单张表的字段、索引与建表语句")
37
- .argument("<table>", "表名")
38
- .option("--ddl", "输出完整建表语句而非概要")
39
- .option("--env <env>", "目标环境(main / dev)")
85
+ .description("查看单张表的字段、索引与建表语句;判断表是否存在也用这条")
86
+ .usage("<table> [flags]")
87
+ .argument("<table>", "表名(不带 schema 前缀)")
88
+ .option("--ddl", "只输出 CREATE TABLE 语句,便于复制 / 备份")
89
+ .option("--env <env>", "目标环境(main / dev),缺省按多环境状态兜底")
40
90
  .action(async (table, opts) => {
41
91
  await (0, index_1.handleDbSchemaGet)(table, opts);
42
- });
92
+ })
93
+ .addHelpText("after", `
94
+ Notes:
95
+ - 用作"判断表是否存在"探针时:退出码 0 → 表存在;错误码 TABLE_NOT_FOUND → 表不存在
96
+
97
+ Examples:
98
+ $ miaoda db schema get users
99
+ CREATE TABLE users (
100
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
101
+ ...
102
+ );
103
+
104
+ $ miaoda db schema get users --ddl
105
+ CREATE TABLE users ( ... );
106
+
107
+ $ miaoda db schema get users --json
108
+ {"name":"users","columns":[...],"indexes":[...]}
109
+
110
+ # 报错:表不存在
111
+ $ miaoda db schema get no_such
112
+ Error: TABLE_NOT_FOUND
113
+ hint: Run \`miaoda db schema list\` to see all tables.
114
+ `);
43
115
  // data 二级资源分组
44
116
  const dataCmd = dbCmd
45
117
  .command("data")
46
- .description("批量导入与导出数据");
118
+ .description("批量导入与导出数据(import / export)")
119
+ .usage("<command> [flags]");
47
120
  dataCmd.action(() => {
48
121
  dataCmd.outputHelp();
49
122
  });
50
123
  dataCmd
51
124
  .command("import")
52
125
  .description("把本地 CSV / JSON 文件导入到表(单次最多 5000 行 / 1 MB,全部成功或全部回滚)")
53
- .argument("<file>", "本地文件路径")
54
- .option("--table <name>", "目标表名(缺省按文件名推断)")
55
- .option("--format <fmt>", "文件格式 csv / json(缺省按扩展名推断)")
126
+ .usage("<file> [flags]")
127
+ .argument("<file>", "本地文件路径(CSV 或 JSON)")
128
+ .option("--table <name>", "目标表名;缺省按文件名(去扩展名)推断")
129
+ .option("--format <fmt>", "文件格式 csv / json;缺省按文件扩展名推断")
56
130
  .action(async (file, opts) => {
57
131
  await (0, index_1.handleDbDataImport)(file, opts);
58
- });
132
+ })
133
+ .addHelpText("after", `
134
+ Notes:
135
+ - 单次最多 5000 行 / 1 MB;超过请拆批
136
+ - 任一行失败 → 整批回滚,错误响应给出失败行号 + 原因
137
+
138
+ Examples:
139
+ $ miaoda db data import users.csv
140
+ ✓ Imported 120 rows into 'users'
141
+
142
+ $ miaoda db data import data.json --table customers
143
+ ✓ Imported 240 rows into 'customers'
144
+
145
+ $ miaoda db data import dump --table orders --format csv
146
+ ✓ Imported 80 rows into 'orders'
147
+
148
+ # 报错:第 5 行格式错误,整批回滚
149
+ $ miaoda db data import broken.csv
150
+ Error: ROW_PARSE_FAILED at row 5
151
+ hint: Check column count and value types match the table schema.
152
+ `);
59
153
  dataCmd
60
154
  .command("export")
61
155
  .description("把整张表导出为 CSV / JSON 文件(单次最多 5000 行 / 1 MB)")
62
- .argument("<table>", "表名")
63
- .option("--format <fmt>", "导出格式 csv / json(默认 csv)")
64
- .option("-f, --file <path>", "输出文件路径(默认 <table>.<format>)")
156
+ .usage("<table> [flags]")
157
+ .argument("<table>", "表名(不带 schema 前缀)")
158
+ .option("--format <fmt>", "导出格式 csv / json,默认 csv")
159
+ .option("-f, --file <path>", "输出文件路径,默认 <table>.<format>")
65
160
  .option("--limit <n>", "最多导出行数(不超过 5000)")
66
161
  .action(async (table, opts) => {
67
162
  await (0, index_1.handleDbDataExport)(table, opts);
68
- });
163
+ })
164
+ .addHelpText("after", `
165
+ Examples:
166
+ $ miaoda db data export users
167
+ ✓ Exported 120 rows to ./users.csv
168
+
169
+ $ miaoda db data export users --format json
170
+ ✓ Exported 120 rows to ./users.json
171
+
172
+ $ miaoda db data export users -f /tmp/u.csv
173
+ ✓ Exported 120 rows to /tmp/u.csv
174
+
175
+ $ miaoda db data export users --limit 1000
176
+ ✓ Exported 1000 rows to ./users.csv
177
+ `);
69
178
  }
@@ -19,13 +19,24 @@ function parsePositiveInt(raw) {
19
19
  function registerFileCommands(program) {
20
20
  const fileCmd = program
21
21
  .command("file")
22
- .description("文件操作:上传、下载、删除、查询");
22
+ .description("文件操作:上传、下载、删除、查询")
23
+ .usage("<command> [flags]");
23
24
  fileCmd.action(() => {
24
25
  fileCmd.outputHelp();
25
26
  });
27
+ fileCmd.addHelpText("after", `
28
+ Examples:
29
+ $ miaoda file ls
30
+ $ miaoda file stat report.pdf
31
+ $ miaoda file cp report.pdf /uploads/
32
+ $ miaoda file cp /uploads/report.pdf .
33
+ $ miaoda file sign report.pdf
34
+ $ miaoda file rm a.txt b.txt -y
35
+ `);
26
36
  fileCmd
27
37
  .command("ls")
28
- .description("列出应用下的文件")
38
+ .description("列出应用下的文件,支持名称 / 路径 / MIME / 大小 / 时间多维筛选")
39
+ .usage("[query] [flags]")
29
40
  .argument("[query]", "可选筛选值:以 / 开头视为路径(精确匹配),否则按文件名匹配")
30
41
  .option("--path <path>", "按路径精确匹配")
31
42
  .option("--name <name>", "按文件名精确匹配")
@@ -38,38 +49,116 @@ function registerFileCommands(program) {
38
49
  .option("--all", "自动翻页返回全部结果")
39
50
  .action(async (query, opts) => {
40
51
  await (0, index_1.handleFileLs)({ ...opts, query });
41
- });
52
+ })
53
+ .addHelpText("after", `
54
+ Examples:
55
+ $ miaoda file ls
56
+ path size uploaded_at
57
+ /uploads/report.pdf 1.2 MB 2026-04-20 10:00
58
+ /uploads/photo.png 800 KB 2026-04-21 14:30
59
+
60
+ $ miaoda file ls report.pdf
61
+ $ miaoda file ls /uploads/report.pdf
62
+ $ miaoda file ls --type image/png --size-gt 1MB
63
+ $ miaoda file ls --uploaded-since 2026-04-01
64
+
65
+ $ miaoda file ls --all --json | jq '.[].name'
66
+ "report.pdf"
67
+ "photo.png"
68
+ `);
42
69
  fileCmd
43
70
  .command("stat")
44
- .description("查看文件的元数据(不含下载链接,需要链接请用 sign)")
45
- .argument("<file>", "文件的路径或文件名")
71
+ .description("查看文件元数据(不含下载链接,需要链接用 sign)")
72
+ .usage("<file> [flags]")
73
+ .argument("<file>", "文件的路径或文件名(自动识别)")
46
74
  .action(async (file, opts) => {
47
75
  await (0, index_1.handleFileStat)(file, opts);
48
- });
76
+ })
77
+ .addHelpText("after", `
78
+ Examples:
79
+ $ miaoda file stat report.pdf
80
+ path: /uploads/report.pdf
81
+ size: 1.2 MB
82
+ type: application/pdf
83
+ uploaded_at: 2026-04-20 10:00
84
+
85
+ $ miaoda file stat /uploads/report.pdf
86
+ $ miaoda file stat report.pdf --json
87
+ {"path":"/uploads/report.pdf","size":1258291,...}
88
+
89
+ # 报错:文件不存在
90
+ $ miaoda file stat no_such.pdf
91
+ Error: FILE_NOT_FOUND
92
+ hint: Run \`miaoda file ls\` to see available files.
93
+ `);
49
94
  fileCmd
50
95
  .command("cp")
51
- .description("上传或下载文件(按 src/dst 自动判断方向)")
96
+ .description("上传或下载文件,按 src/dst 自动判断方向")
97
+ .usage("<src> <dst> [flags]")
52
98
  .argument("<src>", "源:本地文件路径 或 远程文件路径/名")
53
99
  .argument("<dst>", "目标:本地路径 或 远程路径")
54
100
  .option("--rename <name>", "上传后在远端使用的新文件名")
55
101
  .action(async (src, dst, opts) => {
56
102
  await (0, index_1.handleFileCp)(src, dst, opts);
57
- });
103
+ })
104
+ .addHelpText("after", `
105
+ Notes:
106
+ - src 是本地存在的文件 → 上传
107
+ - src 不是本地文件而 dst 看起来是本地路径 → 下载
108
+
109
+ Examples:
110
+ $ miaoda file cp ./report.pdf /uploads/
111
+ ✓ Uploaded report.pdf → /uploads/report.pdf
112
+
113
+ $ miaoda file cp ./report.pdf /uploads/ --rename r.pdf
114
+ ✓ Uploaded report.pdf → /uploads/r.pdf
115
+
116
+ $ miaoda file cp /uploads/report.pdf .
117
+ ✓ Downloaded /uploads/report.pdf → ./report.pdf
118
+
119
+ $ miaoda file cp /uploads/report.pdf ./local.pdf
120
+ ✓ Downloaded /uploads/report.pdf → ./local.pdf
121
+ `);
58
122
  fileCmd
59
123
  .command("rm")
60
124
  .description("批量删除文件(单次最多 100 个,部分失败不影响其他)")
125
+ .usage("[paths...] [flags]")
61
126
  .argument("[paths...]", "要删除的文件,每项可填路径或文件名(自动识别)")
62
- .option("-n, --name <name>", "按文件名删除(可多次指定)", (value, prev) => [...(prev ?? []), value])
127
+ .option("-n, --name <name>", "按文件名删除(可重复指定)", (value, prev) => [...(prev ?? []), value])
63
128
  .option("-y, --yes", "跳过交互确认(脚本 / agent 调用必加)")
64
129
  .action(async (paths, opts) => {
65
130
  await (0, index_1.handleFileRm)(paths, opts);
66
- });
131
+ })
132
+ .addHelpText("after", `
133
+ Notes:
134
+ - 单次最多 100 个;超过请拆批
135
+ - 部分失败响应里会列出每条 success / failed 状态,整批仍 exit 0
136
+
137
+ Examples:
138
+ $ miaoda file rm /uploads/a.pdf /uploads/b.pdf -y
139
+ ✓ Deleted 2 files
140
+
141
+ $ miaoda file rm a.pdf b.pdf -y
142
+ ✓ Deleted 2 files
143
+
144
+ $ miaoda file rm -n a.pdf -n b.pdf -y
145
+ ✓ Deleted 2 files
146
+ `);
67
147
  fileCmd
68
148
  .command("sign")
69
149
  .description("生成可分享的临时下载链接")
150
+ .usage("<file> [flags]")
70
151
  .argument("<file>", "文件的路径或文件名")
71
152
  .option("--expires <duration>", "链接有效期,支持 30m / 24h / 7d 单位(默认 7d,最长 30d)")
72
153
  .action(async (file, opts) => {
73
154
  await (0, index_1.handleFileSign)(file, opts);
74
- });
155
+ })
156
+ .addHelpText("after", `
157
+ Examples:
158
+ $ miaoda file sign report.pdf
159
+ https://...?expires=... (valid 7d)
160
+
161
+ $ miaoda file sign report.pdf --expires 30m
162
+ $ miaoda file sign report.pdf --expires 24h
163
+ `);
75
164
  }
@@ -5,7 +5,8 @@ const index_1 = require("../../../cli/handlers/plugin/index");
5
5
  function registerPluginCommands(program) {
6
6
  const pluginCmd = program
7
7
  .command("plugin")
8
- .description("插件管理:安装/更新/移除插件包,查询 capability 实例");
8
+ .description("插件管理:安装/更新/移除插件包,查询 capability 实例")
9
+ .usage("<command> [flags]");
9
10
  pluginCmd.action(() => {
10
11
  pluginCmd.outputHelp();
11
12
  });
@@ -0,0 +1,84 @@
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 → Flags → Global Flags → Commands → addHelpText('after')
12
+ *
13
+ * Notes / Examples 段由各命令通过 addHelpText('after', ...) 自行追加,
14
+ * 本类不直接生成 —— 框架与文案分层。
15
+ */
16
+ class MiaodaHelp extends commander_1.Help {
17
+ // 全局默认开启:所有子命令 --help 都展示 Global Flags 段
18
+ showGlobalOptions = true;
19
+ /**
20
+ * 父级 --help 的 Commands 列表里展示子命令调用形态。规范要求 "<args> [flags]"
21
+ * 顺序,但 commander 默认 subcommandTerm 是 "name [options] <args>"。
22
+ *
23
+ * - 子命令是分组(含下级 subcommand)→ 只显示 name,不带 args/flags
24
+ * - 子命令是 leaf 且配置了 usage() → "name <usage>",对齐 args 在前 / flags 在后
25
+ * - leaf 没配置 usage → 退回 commander 默认行为
26
+ */
27
+ subcommandTerm(cmd) {
28
+ if (cmd.commands.length > 0) {
29
+ return cmd.name();
30
+ }
31
+ const usage = cmd.usage();
32
+ if (usage) {
33
+ return `${cmd.name()} ${usage}`.trim();
34
+ }
35
+ return super.subcommandTerm(cmd);
36
+ }
37
+ formatHelp(cmd, helper) {
38
+ const termWidth = helper.padWidth(cmd, helper);
39
+ const helpWidth = helper.helpWidth ?? 80;
40
+ const formatItem = (term, description) => {
41
+ if (description) {
42
+ const padding = " ".repeat(Math.max(termWidth - term.length, 0) + 2);
43
+ return `${term}${padding}${description}`;
44
+ }
45
+ return term;
46
+ };
47
+ const formatList = (lines) => lines.map((l) => " " + l).join("\n");
48
+ void helpWidth; // 保留以备后续按宽度自动 wrap,当前直接透传 description
49
+ const out = [];
50
+ // 1. 描述
51
+ const desc = helper.commandDescription(cmd);
52
+ if (desc) {
53
+ out.push(desc, "");
54
+ }
55
+ // 2. Usage:独立 heading + 缩进
56
+ out.push("Usage:", ` ${helper.commandUsage(cmd)}`, "");
57
+ // 3. Arguments
58
+ const args = helper.visibleArguments(cmd).map((a) => formatItem(helper.argumentTerm(a), helper.argumentDescription(a)));
59
+ if (args.length) {
60
+ out.push("Arguments:", formatList(args), "");
61
+ }
62
+ // 4. Flags(原 Options)
63
+ const opts = helper.visibleOptions(cmd).map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
64
+ if (opts.length) {
65
+ out.push("Flags:", formatList(opts), "");
66
+ }
67
+ // 5. Global Flags(原 Global Options,showGlobalOptions=true 时启用)
68
+ if (this.showGlobalOptions) {
69
+ const globals = helper.visibleGlobalOptions(cmd).map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
70
+ if (globals.length) {
71
+ out.push("Global Flags:", formatList(globals), "");
72
+ }
73
+ }
74
+ // 6. Commands(仅父级命令组有)
75
+ const subs = helper.visibleCommands(cmd).map((c) => formatItem(helper.subcommandTerm(c), helper.subcommandDescription(c)));
76
+ if (subs.length) {
77
+ out.push("Commands:", formatList(subs), "");
78
+ }
79
+ // 保留末尾换行:commander 用 join('\n') 拼 addHelpText('after') 段,
80
+ // 这里多留一个 \n,让 Notes / Examples 段与 Flags / Global Flags 段之间空一行。
81
+ return out.join("\n").replace(/\n+$/, "\n");
82
+ }
83
+ }
84
+ exports.MiaodaHelp = MiaodaHelp;
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
23
  .description("妙搭平台命令行工具")
24
+ .usage("<command> [flags]")
17
25
  .version(version, "-v, --version", "显示版本号")
18
26
  .option("--json [fields]", "JSON 输出,可选字段级选择")
19
27
  .option("--output <format>", "输出格式(pretty|json)", "pretty")
@@ -12,7 +12,7 @@ let runtimeClient;
12
12
  /**
13
13
  * 获取单例 HttpClient(默认:管理端 innerapi)。
14
14
  *
15
- * 管理端链路仅适用于妙搭开发态——baseURL 从 `MIAODA_DEV_INNER_DOMAIN` 读取,
15
+ * 管理端链路仅适用于妙搭开发态——baseURL 从 `MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX` 读取,
16
16
  * 每次请求自动从 `MIAODA_AUTHN_CODE` 读取用户凭证并注入 `X-Miaoda-Client-Token`。
17
17
  * AK/SK 的 `Authorization` / `x-api-key` 照旧叠加。
18
18
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.0-alpha.08508f4",
3
+ "version": "0.1.0-alpha.0d863af",
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.4",
28
+ "@lark-apaas/http-client": "^0.1.5",
29
29
  "commander": "^13.1.0"
30
30
  },
31
31
  "devDependencies": {