@lark-apaas/miaoda-cli 0.1.0-alpha.c783fb5

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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Lark Technologies Pte. Ltd. and/or its affiliates
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
8
+ IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
9
+ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
10
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
11
+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
12
+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # miaoda-cli
2
+
3
+ 妙搭开发平台 Code Agent 命令行工具,为 Agent 提供原子能力。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add -g @lark-apaas/miaoda-cli
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ```bash
14
+ # 设置默认应用(需要应用上下文的命令可用 --app-id 覆盖)
15
+ export MIAODA_APP_ID=app_demo_xxx
16
+
17
+ # 插件管理
18
+ miaoda plugin list-packages
19
+ miaoda plugin install @demo/example-plugin
20
+ ```
21
+
22
+ JSON 结构化输出(Agent 推荐):
23
+
24
+ ```bash
25
+ # 输出全部字段
26
+ miaoda plugin list --json
27
+
28
+ # 可选字段级选择
29
+ miaoda plugin list --json id,name
30
+
31
+ # 或通过 --output 指定格式
32
+ miaoda plugin list --output json
33
+ ```
34
+
35
+ ## 命令树
36
+
37
+ 命令以"域"作为第一级命名空间:
38
+
39
+ | 域 | 用途 |
40
+ |---|---|
41
+ | `miaoda plugin ...` | 插件管理:安装、更新、移除、插件实例查询 |
42
+
43
+ 完整命令通过 `miaoda --help` 或 `miaoda <domain> --help` 查看。
44
+
45
+ ## 全局参数
46
+
47
+ - `--json [fields]`:输出结构化 JSON,可选字段级选择(如 `--json id,name`)。
48
+ - `--output <pretty|json>`:输出格式,默认 `pretty`。
49
+ - `--verbose`:输出调试日志到 stderr。
50
+
51
+ > `--app-id <id>` 非全局参数,需要应用上下文的命令会各自声明,默认读取 `MIAODA_APP_ID` 环境变量。
52
+
53
+ ## 环境变量
54
+
55
+ - `MIAODA_APP_ID`:默认应用 ID,等价于 `--app-id`。
56
+ - `MIAODA_CANARY_HEADER`:设置后所有 HTTP 请求自动带上 `x-tt-env: <值>` 小流量头(例:`export MIAODA_CANARY_HEADER=boe_xxx`)。
57
+
58
+ ## 输出契约
59
+
60
+ - 默认文本输出(pretty),`--json` 或 `--output json` 切换为 JSON。
61
+ - 错误始终写 stderr,业务数据写 stdout。
62
+ - 完整的输出格式、JSON envelope、错误 shape、错误码前缀、退出码分类等见 [Agent 友好 CLI 设计规范](./docs/agent-friendly-cli.md)。
63
+
64
+ ## 贡献
65
+
66
+ 详见 [AGENTS.md](./AGENTS.md)。
package/bin/miaoda.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("../dist/main.js");
@@ -0,0 +1,38 @@
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.plugin = void 0;
37
+ const _plugin = __importStar(require("../api/plugin/index"));
38
+ exports.plugin = { ..._plugin };
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getPluginVersions = getPluginVersions;
7
+ exports.getPluginVersion = getPluginVersion;
8
+ exports.downloadPlugin = downloadPlugin;
9
+ exports.getCachePath = getCachePath;
10
+ exports.hasCachedPlugin = hasCachedPlugin;
11
+ exports.listCachedPlugins = listCachedPlugins;
12
+ exports.cleanAllCache = cleanAllCache;
13
+ exports.cleanPluginCache = cleanPluginCache;
14
+ exports.reportEvents = reportEvents;
15
+ exports.reportInstallEvent = reportInstallEvent;
16
+ exports.reportCreateInstanceEvent = reportCreateInstanceEvent;
17
+ const http_client_1 = require("@lark-apaas/http-client");
18
+ const http_1 = require("../../utils/http");
19
+ const error_1 = require("../../utils/error");
20
+ const logger_1 = require("../../utils/logger");
21
+ const node_fs_1 = __importDefault(require("node:fs"));
22
+ const node_path_1 = __importDefault(require("node:path"));
23
+ // ── Plugin Version API ──
24
+ async function getPluginVersions(keys, latestOnly = true) {
25
+ const client = (0, http_1.getHttpClient)();
26
+ const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(",")}&latest_only=${String(latestOnly)}`);
27
+ if (!response.ok) {
28
+ throw new error_1.HttpError(response.status, response.url, `Failed to get plugin versions: ${String(response.status)} ${response.statusText}`);
29
+ }
30
+ const result = (await response.json());
31
+ if (result.status_code !== "0") {
32
+ throw new error_1.AppError("INTERNAL_API_ERROR", `API error: ${result.message ?? "unknown"}`);
33
+ }
34
+ return result.data.pluginVersions;
35
+ }
36
+ async function getPluginVersion(pluginKey, requestedVersion) {
37
+ const isLatest = requestedVersion === "latest";
38
+ const versions = await getPluginVersions([pluginKey], isLatest);
39
+ const pluginVersions = versions[pluginKey];
40
+ if (!pluginVersions || pluginVersions.length === 0) {
41
+ throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin not found: ${pluginKey}`, { next_actions: ["检查包名拼写,或确认该插件已在插件市场发布"] });
42
+ }
43
+ if (isLatest) {
44
+ return pluginVersions[0];
45
+ }
46
+ const targetVersion = pluginVersions.find((v) => v.version === requestedVersion);
47
+ if (!targetVersion) {
48
+ throw new error_1.AppError("VERSION_NOT_FOUND", `Version ${requestedVersion} not found for plugin ${pluginKey}`, { next_actions: [`可用版本:${pluginVersions.map((v) => v.version).join(", ")}`] });
49
+ }
50
+ return targetVersion;
51
+ }
52
+ // ── Plugin Download ──
53
+ function parsePluginKey(key) {
54
+ const match = /^(@[^/]+)\/(.+)$/.exec(key);
55
+ if (!match) {
56
+ throw new error_1.AppError("INVALID_PLUGIN_KEY", `Invalid plugin key format: ${key}`, { next_actions: ["插件 key 必须形如 @scope/name"] });
57
+ }
58
+ return { scope: match[1], name: match[2] };
59
+ }
60
+ async function downloadFromInner(pluginKey, version) {
61
+ const client = (0, http_1.getHttpClient)();
62
+ const { scope, name } = parsePluginKey(pluginKey);
63
+ const url = `/api/v1/studio/innerapi/plugins/${scope}/${name}/versions/${version}/package`;
64
+ const response = await client.get(url);
65
+ if (!response.ok) {
66
+ throw new error_1.HttpError(response.status, url, `Failed to download plugin: ${String(response.status)} ${response.statusText}`);
67
+ }
68
+ const arrayBuffer = await response.arrayBuffer();
69
+ return Buffer.from(arrayBuffer);
70
+ }
71
+ async function downloadFromPublic(downloadURL) {
72
+ const client = new http_client_1.HttpClient({ timeout: 60000 });
73
+ const response = await client.get(downloadURL);
74
+ if (!response.ok) {
75
+ throw new error_1.HttpError(response.status, downloadURL, `Failed to download from public URL: ${String(response.status)} ${response.statusText}`);
76
+ }
77
+ const arrayBuffer = await response.arrayBuffer();
78
+ return Buffer.from(arrayBuffer);
79
+ }
80
+ const MAX_RETRIES = 2;
81
+ async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
82
+ let lastError;
83
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
84
+ try {
85
+ return await operation();
86
+ }
87
+ catch (error) {
88
+ lastError = error instanceof Error ? error : new Error(String(error));
89
+ if (attempt < maxRetries) {
90
+ (0, logger_1.log)("plugin", `${description} failed, retrying (${String(attempt + 1)}/${String(maxRetries)})...`);
91
+ }
92
+ }
93
+ }
94
+ throw lastError ?? new error_1.AppError("INTERNAL_RETRY_EXHAUSTED", `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ["检查网络后重试,--verbose 可查看重试日志"] });
95
+ }
96
+ /** 插件缓存目录 */
97
+ const PLUGIN_CACHE_DIR = "node_modules/.cache/miaoda-cli/plugins";
98
+ function getPluginCacheDir() {
99
+ return node_path_1.default.join(process.cwd(), PLUGIN_CACHE_DIR);
100
+ }
101
+ function ensureCacheDir() {
102
+ const cacheDir = getPluginCacheDir();
103
+ if (!node_fs_1.default.existsSync(cacheDir)) {
104
+ node_fs_1.default.mkdirSync(cacheDir, { recursive: true });
105
+ }
106
+ }
107
+ function getTempFilePath(pluginKey, version) {
108
+ ensureCacheDir();
109
+ const safeKey = pluginKey.replace(/[/@]/g, "_");
110
+ const filename = `${safeKey}@${version}.tgz`;
111
+ return node_path_1.default.join(getPluginCacheDir(), filename);
112
+ }
113
+ async function downloadPlugin(pluginKey, requestedVersion) {
114
+ const pluginInfo = await getPluginVersion(pluginKey, requestedVersion);
115
+ let tgzBuffer;
116
+ if (pluginInfo.downloadApproach === "inner") {
117
+ tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version), "Download");
118
+ }
119
+ else {
120
+ tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL), "Download");
121
+ }
122
+ const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
123
+ node_fs_1.default.writeFileSync(tgzPath, tgzBuffer);
124
+ return {
125
+ tgzPath,
126
+ version: pluginInfo.version,
127
+ pluginInfo,
128
+ };
129
+ }
130
+ // ── Cache ──
131
+ function getCachePath(pluginKey, version) {
132
+ ensureCacheDir();
133
+ const safeKey = pluginKey.replace(/[/@]/g, "_");
134
+ const filename = `${safeKey}@${version}.tgz`;
135
+ return node_path_1.default.join(getPluginCacheDir(), filename);
136
+ }
137
+ function hasCachedPlugin(pluginKey, version) {
138
+ const cachePath = getCachePath(pluginKey, version);
139
+ return node_fs_1.default.existsSync(cachePath);
140
+ }
141
+ function listCachedPlugins() {
142
+ const cacheDir = getPluginCacheDir();
143
+ if (!node_fs_1.default.existsSync(cacheDir)) {
144
+ return [];
145
+ }
146
+ const files = node_fs_1.default.readdirSync(cacheDir);
147
+ const result = [];
148
+ for (const file of files) {
149
+ if (!file.endsWith(".tgz"))
150
+ continue;
151
+ const match = /^(.+)@(.+)\.tgz$/.exec(file);
152
+ if (!match)
153
+ continue;
154
+ const [, rawName, version] = match;
155
+ const name = rawName.replace(/^_/, "@").replace(/_/, "/");
156
+ const filePath = node_path_1.default.join(cacheDir, file);
157
+ const stat = node_fs_1.default.statSync(filePath);
158
+ result.push({ name, version, filePath, size: stat.size, mtime: stat.mtime });
159
+ }
160
+ return result;
161
+ }
162
+ function cleanAllCache() {
163
+ const cacheDir = getPluginCacheDir();
164
+ if (!node_fs_1.default.existsSync(cacheDir)) {
165
+ return 0;
166
+ }
167
+ const files = node_fs_1.default.readdirSync(cacheDir);
168
+ let count = 0;
169
+ for (const file of files) {
170
+ if (file.endsWith(".tgz")) {
171
+ node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
172
+ count++;
173
+ }
174
+ }
175
+ return count;
176
+ }
177
+ function cleanPluginCache(pluginKey, version) {
178
+ const cacheDir = getPluginCacheDir();
179
+ if (!node_fs_1.default.existsSync(cacheDir)) {
180
+ return 0;
181
+ }
182
+ const safeKey = pluginKey.replace(/[/@]/g, "_");
183
+ const files = node_fs_1.default.readdirSync(cacheDir);
184
+ let count = 0;
185
+ for (const file of files) {
186
+ if (version) {
187
+ if (file === `${safeKey}@${version}.tgz`) {
188
+ node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
189
+ count++;
190
+ }
191
+ }
192
+ else {
193
+ if (file.startsWith(`${safeKey}@`) && file.endsWith(".tgz")) {
194
+ node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
195
+ count++;
196
+ }
197
+ }
198
+ }
199
+ return count;
200
+ }
201
+ // ── Telemetry ──
202
+ async function reportEvents(events) {
203
+ if (events.length === 0)
204
+ return true;
205
+ try {
206
+ const client = (0, http_1.getHttpClient)();
207
+ const response = await client.post("/api/v1/studio/innerapi/resource_events", { events });
208
+ if (!response.ok) {
209
+ (0, logger_1.log)("telemetry", `Failed to report events: ${String(response.status)} ${response.statusText}`);
210
+ return false;
211
+ }
212
+ const result = (await response.json());
213
+ if (result.status_code !== "0") {
214
+ (0, logger_1.log)("telemetry", `API error: ${result.message ?? "unknown"}`);
215
+ return false;
216
+ }
217
+ return true;
218
+ }
219
+ catch (error) {
220
+ (0, logger_1.log)("telemetry", `Failed to report events: ${error instanceof Error ? error.message : String(error)}`);
221
+ return false;
222
+ }
223
+ }
224
+ async function reportInstallEvent(pluginKey, version) {
225
+ await reportEvents([
226
+ {
227
+ resourceType: "plugin",
228
+ resourceKey: pluginKey,
229
+ eventType: "install",
230
+ details: { version },
231
+ },
232
+ ]);
233
+ }
234
+ async function reportCreateInstanceEvent(pluginKey, version) {
235
+ await reportEvents([
236
+ {
237
+ resourceType: "plugin",
238
+ resourceKey: pluginKey,
239
+ eventType: "create_instance",
240
+ details: { version },
241
+ },
242
+ ]);
243
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportCreateInstanceEvent = exports.reportInstallEvent = exports.reportEvents = exports.hasCachedPlugin = exports.getCachePath = exports.downloadPlugin = exports.getPluginVersion = exports.getPluginVersions = void 0;
4
+ var api_1 = require("./api");
5
+ Object.defineProperty(exports, "getPluginVersions", { enumerable: true, get: function () { return api_1.getPluginVersions; } });
6
+ Object.defineProperty(exports, "getPluginVersion", { enumerable: true, get: function () { return api_1.getPluginVersion; } });
7
+ Object.defineProperty(exports, "downloadPlugin", { enumerable: true, get: function () { return api_1.downloadPlugin; } });
8
+ Object.defineProperty(exports, "getCachePath", { enumerable: true, get: function () { return api_1.getCachePath; } });
9
+ Object.defineProperty(exports, "hasCachedPlugin", { enumerable: true, get: function () { return api_1.hasCachedPlugin; } });
10
+ Object.defineProperty(exports, "reportEvents", { enumerable: true, get: function () { return api_1.reportEvents; } });
11
+ Object.defineProperty(exports, "reportInstallEvent", { enumerable: true, get: function () { return api_1.reportInstallEvent; } });
12
+ Object.defineProperty(exports, "reportCreateInstanceEvent", { enumerable: true, get: function () { return api_1.reportCreateInstanceEvent; } });
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // ── Plugin (原 action-plugin) ──
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerCommands = registerCommands;
4
+ const index_1 = require("../../cli/commands/plugin/index");
5
+ function registerCommands(program) {
6
+ (0, index_1.registerPluginCommands)(program);
7
+ }
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerPluginCommands = registerPluginCommands;
4
+ const index_1 = require("../../../cli/handlers/plugin/index");
5
+ function registerPluginCommands(program) {
6
+ const pluginCmd = program
7
+ .command("plugin")
8
+ .description("插件管理:安装/更新/移除插件包,查询 capability 实例");
9
+ pluginCmd.action(() => {
10
+ pluginCmd.outputHelp();
11
+ });
12
+ pluginCmd.addHelpText("after", `
13
+ 概念
14
+ - 插件包 (plugin package):.tgz 包,释放到 ./node_modules/<name>
15
+ - actionPlugins:./package.json 里的字段,记录 { "@scope/name": "version" },作为
16
+ 当前应用项目依赖的插件清单;install/update/remove/init 读写此字段
17
+ - capability 实例:./server/capabilities/<id>.json,描述该应用下具体使用的
18
+ 插件实例(pluginKey + formValue + paramsSchema)
19
+ - 充血 (hydrate):读取插件 manifest.json,把 capability 与 manifest 的
20
+ inputSchema/outputSchema 合并输出完整 action 定义
21
+
22
+ 作用范围
23
+ 本地落点统一为 cwd 下的 package.json / node_modules / server/capabilities。
24
+ - install / update / init:通过 platform HTTP 拉取插件版本与 .tgz
25
+ - remove / list / list-packages:纯本地 FS,不走网络
26
+ `);
27
+ pluginCmd
28
+ .command("install")
29
+ .description("拉取插件包并安装到当前应用项目")
30
+ .argument("<names...>", "插件名,格式 @scope/name 或 @scope/name@version;未带版本默认 latest")
31
+ .addHelpText("after", `
32
+ 行为
33
+ - 拉取 .tgz 到本地缓存,解压到 ./node_modules/<name>
34
+ - 在 ./package.json 的 actionPlugins 字段记录 <name>: <version>
35
+ - 自动补齐插件声明的 peerDependencies
36
+ - 同版本已安装则跳过(幂等)
37
+
38
+ 前置
39
+ - 当前工作目录必须包含 package.json
40
+
41
+ JSON 输出
42
+ {
43
+ "installed": ["@demo/example-plugin@1.2.3", ...],
44
+ "skipped": ["@demo/existing-plugin", ...],
45
+ "failed": [{"name": "@demo/bad", "error": "..."}]
46
+ }
47
+
48
+ 退出码
49
+ 0:全部成功;1:至少一个失败(其余仍会尝试)
50
+
51
+ 示例
52
+ $ miaoda plugin install @demo/example-plugin
53
+ $ miaoda plugin install @demo/example-plugin@1.2.3 @demo/other-plugin --json
54
+
55
+ # 报错:包名格式不合法
56
+ $ miaoda plugin install bad-name
57
+ Error: Invalid plugin name format: bad-name. Expected: @scope/name or @scope/name@version
58
+ hint: 示例:@demo/example-plugin 或 @demo/example-plugin@1.2.3
59
+ `)
60
+ .action(async (names) => { await (0, index_1.handlePluginInstall)({ names }); });
61
+ pluginCmd
62
+ .command("update")
63
+ .description("把已安装插件升级到 latest 版本")
64
+ .argument("<names...>", "插件名,格式 @scope/name;带 @version 也只取 name 部分")
65
+ .addHelpText("after", `
66
+ 行为
67
+ - 仅对已在 actionPlugins 里的插件生效;未安装的插件返回 notInstalled
68
+ - 拉取 latest 版本 .tgz,覆盖安装到 ./node_modules/<name>
69
+ - 版本一致则跳过(幂等);版本变化时同步写回 actionPlugins
70
+
71
+ 前置
72
+ - 当前工作目录必须包含 package.json
73
+
74
+ JSON 输出
75
+ {
76
+ "updated": [{"name": "...", "from": "1.0.0", "to": "1.2.3"}],
77
+ "skipped": ["..."], // 已是 latest
78
+ "notInstalled": ["..."], // 未安装,未尝试
79
+ "failed": [{"name": "...", "error": "..."}]
80
+ }
81
+
82
+ 退出码
83
+ 0:全部成功或仅有 notInstalled;1:至少一个 failed
84
+
85
+ 示例
86
+ $ miaoda plugin update @demo/example-plugin
87
+ $ miaoda plugin update @demo/a @demo/b --json
88
+
89
+ # 场景:插件未登记(非抛错,出现在返回的 notInstalled)
90
+ $ miaoda plugin update @demo/not-installed --json
91
+ {"updated":[],"skipped":[],"notInstalled":["@demo/not-installed"],"failed":[]}
92
+ `)
93
+ .action(async (names) => { await (0, index_1.handlePluginUpdate)({ names }); });
94
+ pluginCmd
95
+ .command("remove")
96
+ .description("从当前项目移除一个已安装的插件")
97
+ .argument("<name>", "插件名,格式 @scope/name")
98
+ .addHelpText("after", `
99
+ 行为
100
+ - 删除 ./node_modules/<name> 目录
101
+ - 从 ./package.json 的 actionPlugins 字段移除对应条目
102
+
103
+ 前置
104
+ - 插件必须已登记在 actionPlugins 中,否则抛 NOT_FOUND
105
+
106
+ JSON 输出
107
+ {"removed": "@demo/example-plugin"}
108
+
109
+ 示例
110
+ $ miaoda plugin remove @demo/example-plugin
111
+
112
+ # 报错:插件未登记
113
+ $ miaoda plugin remove @demo/not-installed
114
+ Error: Plugin @demo/not-installed is not installed
115
+ hint: 运行 miaoda plugin list-packages 查看已安装插件
116
+ `)
117
+ .action((name) => { (0, index_1.handlePluginRemove)({ name }); });
118
+ pluginCmd
119
+ .command("init")
120
+ .description("按 package.json 的 actionPlugins 批量安装所有插件")
121
+ .addHelpText("after", `
122
+ 行为
123
+ - 读取 ./package.json 的 actionPlugins,对每个条目按固定版本安装
124
+ - 已经是目标版本的跳过(幂等),用于 clone 项目后首次同步 / CI
125
+
126
+ 前置
127
+ - 当前工作目录必须包含 package.json
128
+
129
+ JSON 输出
130
+ {
131
+ "installed": ["@demo/a@1.0.0", ...],
132
+ "skipped": ["@demo/b", ...],
133
+ "failed": [{"name": "...", "error": "..."}]
134
+ }
135
+ 当 actionPlugins 为空时输出 {"message": "No plugins found in package.json", "installed": [], "skipped": []}
136
+
137
+ 退出码
138
+ 0:全部成功;1:至少一个 failed
139
+
140
+ 示例
141
+ $ miaoda plugin init --json
142
+
143
+ # 报错:当前目录没有 package.json
144
+ $ miaoda plugin init
145
+ Error: package.json not found in current directory
146
+ hint: 在应用项目根目录运行
147
+ `)
148
+ .action(async () => { await (0, index_1.handlePluginInit)(); });
149
+ pluginCmd
150
+ .command("list")
151
+ .description("列出当前项目的 capability 实例(./server/capabilities/*.json)")
152
+ .option("--summary", "不 hydrate(不充血),直接返回 capability 文件的原始 JSON")
153
+ .option("--id <id>", "只查询指定 id 的单个 capability")
154
+ .addHelpText("after", `
155
+ 行为
156
+ - 扫描 ./server/capabilities/*.json(排除 capabilities.json)
157
+ - 默认对每条 capability 做 hydrate:读取 ./node_modules/<pluginKey>/manifest.json,
158
+ 合并得到每个 action 的最终 inputSchema / outputSchema
159
+ - hydrate 失败不抛错,每条结果上带 _hydrateError 字段
160
+
161
+ 前置
162
+ - ./server/capabilities 目录必须存在,否则抛 NOT_FOUND
163
+ - 充血模式下需要对应插件已安装在 ./node_modules,否则该条 _hydrateError
164
+
165
+ JSON 输出
166
+ - 无 --id:数组,元素为 hydrate 后的 capability,含 actions[].inputSchema 等
167
+ - 带 --id:单个对象
168
+ - 带 --summary:不做 hydrate,返回原始 RawCapability 结构
169
+
170
+ 示例
171
+ $ miaoda plugin list --json
172
+ $ miaoda plugin list --id cap_demo_xxx --json
173
+ $ miaoda plugin list --summary --json
174
+
175
+ # 报错:capability 目录不存在
176
+ $ miaoda plugin list
177
+ Error: server/capabilities directory not found
178
+ hint: 当前目录必须是含 server/capabilities/ 的应用项目
179
+ `)
180
+ .action(async (opts) => { await (0, index_1.handlePluginList)(opts); });
181
+ pluginCmd
182
+ .command("list-packages")
183
+ .description("列出 package.json actionPlugins 里已声明的插件包")
184
+ .addHelpText("after", `
185
+ 行为
186
+ - 读取 ./package.json 的 actionPlugins 字段
187
+ - 只看声明,不校验 ./node_modules 下是否实际存在
188
+
189
+ JSON 输出
190
+ {
191
+ "plugins": [{"name": "@demo/example-plugin", "version": "1.2.3"}, ...],
192
+ "total": <number>
193
+ }
194
+
195
+ 示例
196
+ $ miaoda plugin list-packages --json
197
+
198
+ # 报错:当前目录没有 package.json
199
+ $ miaoda plugin list-packages
200
+ Error: package.json not found in current directory
201
+ hint: 在应用项目根目录运行
202
+ `)
203
+ .action(() => { (0, index_1.handlePluginListPlugins)(); });
204
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.appIdOption = appIdOption;
4
+ exports.softRequiredOption = softRequiredOption;
5
+ exports.resolveAppId = resolveAppId;
6
+ exports.withHelp = withHelp;
7
+ exports.failArgs = failArgs;
8
+ const commander_1 = require("commander");
9
+ const error_1 = require("../../utils/error");
10
+ /** --app-id option,需要应用上下文的命令自行 .addOption(appIdOption()) */
11
+ function appIdOption() {
12
+ return new commander_1.Option("--app-id <id>", "指定目标应用").env("MIAODA_APP_ID");
13
+ }
14
+ /**
15
+ * soft-required: Commander 类型上 optional,runtime 校验必填。
16
+ */
17
+ function softRequiredOption(name, desc) {
18
+ return new commander_1.Option(name, desc);
19
+ }
20
+ /** 解析 appId,CLI flag > env > 抛错 */
21
+ function resolveAppId(opts) {
22
+ const id = opts.appId ?? process.env.MIAODA_APP_ID;
23
+ if (!id) {
24
+ throw new error_1.AppError("APP_ID_MISSING", "未指定应用", {
25
+ next_actions: [
26
+ "传入 --app-id <id>",
27
+ "设置 export MIAODA_APP_ID=<id>",
28
+ ],
29
+ });
30
+ }
31
+ return id;
32
+ }
33
+ /** 包裹 handler,缺 soft-required 参数时打 help 并退出 */
34
+ function withHelp(cmd, handler) {
35
+ return async (opts) => {
36
+ try {
37
+ await handler(opts);
38
+ }
39
+ catch (err) {
40
+ if (err instanceof error_1.AppError && err.code === "ARGS_INVALID") {
41
+ process.stderr.write(`Error: ${err.message}\n`);
42
+ cmd.outputHelp();
43
+ process.exitCode = 2;
44
+ return;
45
+ }
46
+ throw err;
47
+ }
48
+ };
49
+ }
50
+ /** 参数校验失败时抛出,配合 withHelp 自动打 help */
51
+ function failArgs(message) {
52
+ throw new error_1.AppError("ARGS_INVALID", message);
53
+ }
@@ -0,0 +1,17 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../../cli/handlers/plugin/index"), exports);