@lark-apaas/miaoda-cli 0.1.3 → 0.1.4-alpha.abaa17f

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.
Files changed (103) hide show
  1. package/dist/api/app/api.js +3 -3
  2. package/dist/api/app/schemas.js +43 -43
  3. package/dist/api/db/api.js +398 -55
  4. package/dist/api/db/client.js +155 -28
  5. package/dist/api/db/index.js +12 -1
  6. package/dist/api/db/parsers.js +20 -20
  7. package/dist/api/db/sql-keywords.js +87 -87
  8. package/dist/api/deploy/api.js +5 -5
  9. package/dist/api/deploy/index.js +12 -1
  10. package/dist/api/deploy/modern-types.js +23 -0
  11. package/dist/api/deploy/modern.js +70 -0
  12. package/dist/api/deploy/plugin-instances-types.js +6 -0
  13. package/dist/api/deploy/plugin-instances.js +30 -0
  14. package/dist/api/deploy/schemas.js +32 -32
  15. package/dist/api/file/api.js +89 -87
  16. package/dist/api/file/client.js +62 -22
  17. package/dist/api/file/detect.js +3 -3
  18. package/dist/api/file/index.js +2 -1
  19. package/dist/api/file/parsers.js +18 -7
  20. package/dist/api/observability/api.js +6 -6
  21. package/dist/api/observability/schemas.js +14 -14
  22. package/dist/api/plugin/api.js +31 -31
  23. package/dist/cli/commands/app/index.js +94 -13
  24. package/dist/cli/commands/db/index.js +602 -54
  25. package/dist/cli/commands/deploy/index.js +28 -28
  26. package/dist/cli/commands/deploy/modern.js +48 -0
  27. package/dist/cli/commands/file/index.js +85 -58
  28. package/dist/cli/commands/index.js +31 -5
  29. package/dist/cli/commands/observability/index.js +69 -69
  30. package/dist/cli/commands/plugin/index.js +27 -27
  31. package/dist/cli/commands/shared.js +10 -10
  32. package/dist/cli/handlers/app/index.js +6 -1
  33. package/dist/cli/handlers/app/init.js +86 -0
  34. package/dist/cli/handlers/app/update.js +2 -2
  35. package/dist/cli/handlers/db/_destructive.js +67 -0
  36. package/dist/cli/handlers/db/_env.js +26 -0
  37. package/dist/cli/handlers/db/_operator.js +35 -0
  38. package/dist/cli/handlers/db/audit.js +383 -0
  39. package/dist/cli/handlers/db/changelog.js +160 -0
  40. package/dist/cli/handlers/db/data.js +32 -31
  41. package/dist/cli/handlers/db/index.js +17 -1
  42. package/dist/cli/handlers/db/migration.js +234 -0
  43. package/dist/cli/handlers/db/quota.js +68 -0
  44. package/dist/cli/handlers/db/recovery.js +413 -0
  45. package/dist/cli/handlers/db/schema.js +33 -33
  46. package/dist/cli/handlers/db/sql.js +69 -69
  47. package/dist/cli/handlers/deploy/deploy.js +4 -4
  48. package/dist/cli/handlers/deploy/error-log.js +1 -1
  49. package/dist/cli/handlers/deploy/get.js +3 -3
  50. package/dist/cli/handlers/deploy/modern.js +32 -0
  51. package/dist/cli/handlers/deploy/polling.js +11 -11
  52. package/dist/cli/handlers/file/cp.js +30 -30
  53. package/dist/cli/handlers/file/index.js +3 -1
  54. package/dist/cli/handlers/file/ls.js +5 -5
  55. package/dist/cli/handlers/file/quota.js +66 -0
  56. package/dist/cli/handlers/file/rm.js +32 -30
  57. package/dist/cli/handlers/file/sign.js +3 -3
  58. package/dist/cli/handlers/file/stat.js +10 -9
  59. package/dist/cli/handlers/observability/analytics.js +47 -47
  60. package/dist/cli/handlers/observability/helpers.js +2 -2
  61. package/dist/cli/handlers/observability/log.js +9 -9
  62. package/dist/cli/handlers/observability/metric.js +26 -26
  63. package/dist/cli/handlers/observability/trace.js +5 -5
  64. package/dist/cli/handlers/plugin/plugin-local.js +53 -53
  65. package/dist/cli/handlers/plugin/plugin.js +15 -15
  66. package/dist/cli/help.js +16 -16
  67. package/dist/main.js +12 -12
  68. package/dist/services/app/init/index.js +14 -0
  69. package/dist/services/app/init/install.js +101 -0
  70. package/dist/services/app/init/steering.js +74 -0
  71. package/dist/services/app/init/template.js +85 -0
  72. package/dist/services/deploy/modern/atoms/build.js +54 -0
  73. package/dist/services/deploy/modern/atoms/context.js +30 -0
  74. package/dist/services/deploy/modern/atoms/index.js +17 -0
  75. package/dist/services/deploy/modern/atoms/local-release.js +27 -0
  76. package/dist/services/deploy/modern/atoms/pre-release.js +13 -0
  77. package/dist/services/deploy/modern/atoms/save-plugin-instances.js +72 -0
  78. package/dist/services/deploy/modern/atoms/upload.js +192 -0
  79. package/dist/services/deploy/modern/check.js +58 -0
  80. package/dist/services/deploy/modern/constants.js +13 -0
  81. package/dist/services/deploy/modern/index.js +16 -0
  82. package/dist/services/deploy/modern/pipelines/index.js +5 -0
  83. package/dist/services/deploy/modern/pipelines/local.js +75 -0
  84. package/dist/services/deploy/modern/protocol.js +43 -0
  85. package/dist/services/deploy/modern/run-types.js +4 -0
  86. package/dist/services/deploy/modern/run.js +13 -0
  87. package/dist/services/deploy/modern/template-key-map.js +29 -0
  88. package/dist/utils/args.js +1 -1
  89. package/dist/utils/colors.js +2 -2
  90. package/dist/utils/config.js +2 -2
  91. package/dist/utils/devops-error.js +9 -9
  92. package/dist/utils/error.js +2 -2
  93. package/dist/utils/git.js +4 -4
  94. package/dist/utils/http.js +27 -21
  95. package/dist/utils/index.js +3 -1
  96. package/dist/utils/npm-pack.js +55 -0
  97. package/dist/utils/output.js +67 -45
  98. package/dist/utils/poll.js +35 -0
  99. package/dist/utils/render.js +27 -27
  100. package/dist/utils/spark-meta.js +48 -0
  101. package/dist/utils/spinner.js +46 -0
  102. package/dist/utils/time.js +47 -42
  103. package/package.json +1 -1
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseAttachment = parseAttachment;
4
4
  exports.parseHead = parseHead;
5
5
  /** 安全读取字符串字段(空字符串回退为默认值) */
6
- function str(value, fallback = "") {
7
- return typeof value === "string" && value.length > 0 ? value : fallback;
6
+ function str(value, fallback = '') {
7
+ return typeof value === 'string' && value.length > 0 ? value : fallback;
8
8
  }
9
9
  /** 安全读取数字字段(非正值回退到 0) */
10
10
  function int(value) {
11
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
11
+ if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
12
12
  return Math.trunc(value);
13
13
  }
14
14
  return 0;
@@ -26,7 +26,7 @@ function readSize(meta) {
26
26
  function toDisplayPath(p) {
27
27
  if (!p)
28
28
  return p;
29
- return p.startsWith("/") ? p : `/${p}`;
29
+ return p.startsWith('/') ? p : `/${p}`;
30
30
  }
31
31
  /** 将后端 attachment 结构映射为 CLI FileInfo(用于 ls) */
32
32
  function parseAttachment(att) {
@@ -41,8 +41,9 @@ function parseAttachment(att) {
41
41
  const downloadUrl = str(att.downloadURL);
42
42
  if (downloadUrl)
43
43
  info.download_url = downloadUrl;
44
- // uploaded_by createdBy.name 映射(后端返 {userID, name, email, ...},空代表匿名)
45
- const uploader = str(att.createdBy?.name);
44
+ // uploaded_by 输出 {id, name} 对象:后端 createdBy: {userID, name, email, ...}
45
+ // 任一非空都建对象;JSON 用对象、pretty 渲染只取 name
46
+ const uploader = toUploaderRef(att.createdBy);
46
47
  if (uploader)
47
48
  info.uploaded_by = uploader;
48
49
  return info;
@@ -61,8 +62,18 @@ function parseHead(data, filePath) {
61
62
  const downloadUrl = str(outer.downloadURL);
62
63
  if (downloadUrl)
63
64
  info.download_url = downloadUrl;
64
- const uploader = str(outer.createdBy?.name);
65
+ const uploader = toUploaderRef(outer.createdBy);
65
66
  if (uploader)
66
67
  info.uploaded_by = uploader;
67
68
  return info;
68
69
  }
70
+ /** 把后端 UserRef 归一成 CLI 暴露的 {id, name};id / name 全空时返 undefined 略过字段。 */
71
+ function toUploaderRef(user) {
72
+ if (!user)
73
+ return undefined;
74
+ const id = str(user.userID);
75
+ const name = str(user.name);
76
+ if (id === '' && name === '')
77
+ return undefined;
78
+ return { id, name };
79
+ }
@@ -6,12 +6,12 @@ exports.getTraces = getTraces;
6
6
  exports.queryMetricsData = queryMetricsData;
7
7
  exports.queryAnalyticsData = queryAnalyticsData;
8
8
  const http_1 = require("../../utils/http");
9
- const DEFAULT_ERR_CODE = "INTERNAL_OB_ERROR";
9
+ const DEFAULT_ERR_CODE = 'INTERNAL_OB_ERROR';
10
10
  // ── 日志 ──
11
11
  async function searchLogs(req) {
12
12
  const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/logs/search`;
13
13
  return (0, http_1.postInnerApi)(url, req, {
14
- errPrefix: "Failed to search logs",
14
+ errPrefix: 'Failed to search logs',
15
15
  defaultErrCode: DEFAULT_ERR_CODE,
16
16
  });
17
17
  }
@@ -19,14 +19,14 @@ async function searchLogs(req) {
19
19
  async function searchTraces(req) {
20
20
  const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/search`;
21
21
  return (0, http_1.postInnerApi)(url, req, {
22
- errPrefix: "Failed to search traces",
22
+ errPrefix: 'Failed to search traces',
23
23
  defaultErrCode: DEFAULT_ERR_CODE,
24
24
  });
25
25
  }
26
26
  async function getTraces(req) {
27
27
  const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/${encodeURIComponent(req.traceID)}`;
28
28
  return (0, http_1.postInnerApi)(url, req, {
29
- errPrefix: "Failed to get trace",
29
+ errPrefix: 'Failed to get trace',
30
30
  defaultErrCode: DEFAULT_ERR_CODE,
31
31
  });
32
32
  }
@@ -34,7 +34,7 @@ async function getTraces(req) {
34
34
  async function queryMetricsData(req) {
35
35
  const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/metrics/data/query`;
36
36
  return (0, http_1.postInnerApi)(url, req, {
37
- errPrefix: "Failed to query metrics",
37
+ errPrefix: 'Failed to query metrics',
38
38
  defaultErrCode: DEFAULT_ERR_CODE,
39
39
  });
40
40
  }
@@ -46,7 +46,7 @@ async function queryMetricsData(req) {
46
46
  async function queryAnalyticsData(req) {
47
47
  const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/analytics/data/batch_query`;
48
48
  return (0, http_1.postInnerApi)(url, req, {
49
- errPrefix: "Failed to query analytics",
49
+ errPrefix: 'Failed to query analytics',
50
50
  defaultErrCode: DEFAULT_ERR_CODE,
51
51
  });
52
52
  }
@@ -5,30 +5,30 @@ const output_1 = require("../../utils/output");
5
5
  /** LogItem 渲染契约(log search) */
6
6
  exports.logItemSchema = {
7
7
  columns: [
8
- { key: "timestampNs", label: "time", format: output_1.fmt.ns("yyyy-MM-dd HH:mm:ss.SSS") },
8
+ { key: 'timestampNs', label: 'time', format: output_1.fmt.ns('yyyy-MM-dd HH:mm:ss.SSS') },
9
9
  {
10
- key: "module",
10
+ key: 'module',
11
11
  derive: (row) => {
12
12
  return row.attributes?.module;
13
13
  },
14
14
  },
15
15
  {
16
- key: "user-id",
16
+ key: 'user-id',
17
17
  derive: (row) => {
18
18
  return row.attributes?.user_id;
19
19
  },
20
20
  },
21
- { key: "severityText", label: "severity-text" },
21
+ { key: 'severityText', label: 'severity-text' },
22
22
  {
23
- key: "duration",
23
+ key: 'duration',
24
24
  format: output_1.fmt.durationMs(),
25
25
  derive: (row) => {
26
26
  return row.attributes?.duration_ms;
27
27
  },
28
28
  },
29
- { key: "traceID", label: "trace-id" },
30
- { key: "id", label: "log-id" },
31
- { key: "body" },
29
+ { key: 'traceID', label: 'trace-id' },
30
+ { key: 'id', label: 'log-id' },
31
+ { key: 'body' },
32
32
  ],
33
33
  strict: true,
34
34
  };
@@ -36,23 +36,23 @@ exports.logItemSchema = {
36
36
  * duration 是 client-derived(BAM 只给 start/end,没有原生 durationNs 字段)。 */
37
37
  exports.spanSchema = {
38
38
  columns: [
39
- { key: "startTimeUnixNano", label: "start-time", format: output_1.fmt.ns("yyyy-MM-dd HH:mm:ss.SSS") },
40
- { key: "name", label: "root-span" },
39
+ { key: 'startTimeUnixNano', label: 'start-time', format: output_1.fmt.ns('yyyy-MM-dd HH:mm:ss.SSS') },
40
+ { key: 'name', label: 'root-span' },
41
41
  {
42
- key: "user-id",
42
+ key: 'user-id',
43
43
  derive: (row) => {
44
44
  return row.attributes?.user_id;
45
45
  },
46
46
  },
47
47
  {
48
- key: "duration",
49
- label: "duration",
48
+ key: 'duration',
49
+ label: 'duration',
50
50
  format: output_1.fmt.durationMs(),
51
51
  derive: (row) => {
52
52
  return row.attributes?.duration_ms;
53
53
  },
54
54
  },
55
- { key: "traceID", label: "trace-id" },
55
+ { key: 'traceID', label: 'trace-id' },
56
56
  ],
57
57
  strict: true,
58
58
  };
@@ -23,23 +23,23 @@ const node_path_1 = __importDefault(require("node:path"));
23
23
  // ── Plugin Version API ──
24
24
  async function getPluginVersions(keys, latestOnly = true) {
25
25
  const client = (0, http_1.getRuntimeHttpClient)();
26
- const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(",")}&latest_only=${String(latestOnly)}`);
26
+ const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(',')}&latest_only=${String(latestOnly)}`);
27
27
  if (!response.ok) {
28
28
  throw new error_1.HttpError(response.status, response.url, `Failed to get plugin versions: ${String(response.status)} ${response.statusText}`);
29
29
  }
30
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"}`);
31
+ if (result.status_code !== '0') {
32
+ throw new error_1.AppError('INTERNAL_API_ERROR', `API error: ${result.message ?? 'unknown'}`);
33
33
  }
34
34
  return result.data.pluginVersions;
35
35
  }
36
36
  async function getPluginVersion(pluginKey, requestedVersion) {
37
- const isLatest = requestedVersion === "latest";
37
+ const isLatest = requestedVersion === 'latest';
38
38
  const versions = await getPluginVersions([pluginKey], isLatest);
39
39
  const pluginVersions = versions[pluginKey];
40
40
  if (!pluginVersions || pluginVersions.length === 0) {
41
- throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin not found: ${pluginKey}`, {
42
- next_actions: ["检查包名拼写,或确认该插件已在插件市场发布"],
41
+ throw new error_1.AppError('PLUGIN_NOT_FOUND', `Plugin not found: ${pluginKey}`, {
42
+ next_actions: ['检查包名拼写,或确认该插件已在插件市场发布'],
43
43
  });
44
44
  }
45
45
  if (isLatest) {
@@ -47,7 +47,7 @@ async function getPluginVersion(pluginKey, requestedVersion) {
47
47
  }
48
48
  const targetVersion = pluginVersions.find((v) => v.version === requestedVersion);
49
49
  if (!targetVersion) {
50
- throw new error_1.AppError("VERSION_NOT_FOUND", `Version ${requestedVersion} not found for plugin ${pluginKey}`, { next_actions: [`可用版本:${pluginVersions.map((v) => v.version).join(", ")}`] });
50
+ throw new error_1.AppError('VERSION_NOT_FOUND', `Version ${requestedVersion} not found for plugin ${pluginKey}`, { next_actions: [`可用版本:${pluginVersions.map((v) => v.version).join(', ')}`] });
51
51
  }
52
52
  return targetVersion;
53
53
  }
@@ -55,8 +55,8 @@ async function getPluginVersion(pluginKey, requestedVersion) {
55
55
  function parsePluginKey(key) {
56
56
  const match = /^(@[^/]+)\/(.+)$/.exec(key);
57
57
  if (!match) {
58
- throw new error_1.AppError("INVALID_PLUGIN_KEY", `Invalid plugin key format: ${key}`, {
59
- next_actions: ["插件 key 必须形如 @scope/name"],
58
+ throw new error_1.AppError('INVALID_PLUGIN_KEY', `Invalid plugin key format: ${key}`, {
59
+ next_actions: ['插件 key 必须形如 @scope/name'],
60
60
  });
61
61
  }
62
62
  return { scope: match[1], name: match[2] };
@@ -91,15 +91,15 @@ async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
91
91
  catch (error) {
92
92
  lastError = error instanceof Error ? error : new Error(String(error));
93
93
  if (attempt < maxRetries) {
94
- (0, logger_1.log)("plugin", `${description} failed, retrying (${String(attempt + 1)}/${String(maxRetries)})...`);
94
+ (0, logger_1.log)('plugin', `${description} failed, retrying (${String(attempt + 1)}/${String(maxRetries)})...`);
95
95
  }
96
96
  }
97
97
  }
98
98
  throw (lastError ??
99
- new error_1.AppError("INTERNAL_RETRY_EXHAUSTED", `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ["检查网络后重试,--verbose 可查看重试日志"] }));
99
+ new error_1.AppError('INTERNAL_RETRY_EXHAUSTED', `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ['检查网络后重试,--verbose 可查看重试日志'] }));
100
100
  }
101
101
  /** 插件缓存目录 */
102
- const PLUGIN_CACHE_DIR = "node_modules/.cache/miaoda-cli/plugins";
102
+ const PLUGIN_CACHE_DIR = 'node_modules/.cache/miaoda-cli/plugins';
103
103
  function getPluginCacheDir() {
104
104
  return node_path_1.default.join(process.cwd(), PLUGIN_CACHE_DIR);
105
105
  }
@@ -111,18 +111,18 @@ function ensureCacheDir() {
111
111
  }
112
112
  function getTempFilePath(pluginKey, version) {
113
113
  ensureCacheDir();
114
- const safeKey = pluginKey.replace(/[/@]/g, "_");
114
+ const safeKey = pluginKey.replace(/[/@]/g, '_');
115
115
  const filename = `${safeKey}@${version}.tgz`;
116
116
  return node_path_1.default.join(getPluginCacheDir(), filename);
117
117
  }
118
118
  async function downloadPlugin(pluginKey, requestedVersion) {
119
119
  const pluginInfo = await getPluginVersion(pluginKey, requestedVersion);
120
120
  let tgzBuffer;
121
- if (pluginInfo.downloadApproach === "inner") {
122
- tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version), "Download");
121
+ if (pluginInfo.downloadApproach === 'inner') {
122
+ tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version), 'Download');
123
123
  }
124
124
  else {
125
- tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL), "Download");
125
+ tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL), 'Download');
126
126
  }
127
127
  const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
128
128
  node_fs_1.default.writeFileSync(tgzPath, tgzBuffer);
@@ -135,7 +135,7 @@ async function downloadPlugin(pluginKey, requestedVersion) {
135
135
  // ── Cache ──
136
136
  function getCachePath(pluginKey, version) {
137
137
  ensureCacheDir();
138
- const safeKey = pluginKey.replace(/[/@]/g, "_");
138
+ const safeKey = pluginKey.replace(/[/@]/g, '_');
139
139
  const filename = `${safeKey}@${version}.tgz`;
140
140
  return node_path_1.default.join(getPluginCacheDir(), filename);
141
141
  }
@@ -151,13 +151,13 @@ function listCachedPlugins() {
151
151
  const files = node_fs_1.default.readdirSync(cacheDir);
152
152
  const result = [];
153
153
  for (const file of files) {
154
- if (!file.endsWith(".tgz"))
154
+ if (!file.endsWith('.tgz'))
155
155
  continue;
156
156
  const match = /^(.+)@(.+)\.tgz$/.exec(file);
157
157
  if (!match)
158
158
  continue;
159
159
  const [, rawName, version] = match;
160
- const name = rawName.replace(/^_/, "@").replace(/_/, "/");
160
+ const name = rawName.replace(/^_/, '@').replace(/_/, '/');
161
161
  const filePath = node_path_1.default.join(cacheDir, file);
162
162
  const stat = node_fs_1.default.statSync(filePath);
163
163
  result.push({ name, version, filePath, size: stat.size, mtime: stat.mtime });
@@ -172,7 +172,7 @@ function cleanAllCache() {
172
172
  const files = node_fs_1.default.readdirSync(cacheDir);
173
173
  let count = 0;
174
174
  for (const file of files) {
175
- if (file.endsWith(".tgz")) {
175
+ if (file.endsWith('.tgz')) {
176
176
  node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
177
177
  count++;
178
178
  }
@@ -184,7 +184,7 @@ function cleanPluginCache(pluginKey, version) {
184
184
  if (!node_fs_1.default.existsSync(cacheDir)) {
185
185
  return 0;
186
186
  }
187
- const safeKey = pluginKey.replace(/[/@]/g, "_");
187
+ const safeKey = pluginKey.replace(/[/@]/g, '_');
188
188
  const files = node_fs_1.default.readdirSync(cacheDir);
189
189
  let count = 0;
190
190
  for (const file of files) {
@@ -195,7 +195,7 @@ function cleanPluginCache(pluginKey, version) {
195
195
  }
196
196
  }
197
197
  else {
198
- if (file.startsWith(`${safeKey}@`) && file.endsWith(".tgz")) {
198
+ if (file.startsWith(`${safeKey}@`) && file.endsWith('.tgz')) {
199
199
  node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
200
200
  count++;
201
201
  }
@@ -209,29 +209,29 @@ async function reportEvents(events) {
209
209
  return true;
210
210
  try {
211
211
  const client = (0, http_1.getRuntimeHttpClient)();
212
- const response = await client.post("/api/v1/studio/innerapi/resource_events", { events });
212
+ const response = await client.post('/api/v1/studio/innerapi/resource_events', { events });
213
213
  if (!response.ok) {
214
- (0, logger_1.log)("telemetry", `Failed to report events: ${String(response.status)} ${response.statusText}`);
214
+ (0, logger_1.log)('telemetry', `Failed to report events: ${String(response.status)} ${response.statusText}`);
215
215
  return false;
216
216
  }
217
217
  const result = (await response.json());
218
- if (result.status_code !== "0") {
219
- (0, logger_1.log)("telemetry", `API error: ${result.message ?? "unknown"}`);
218
+ if (result.status_code !== '0') {
219
+ (0, logger_1.log)('telemetry', `API error: ${result.message ?? 'unknown'}`);
220
220
  return false;
221
221
  }
222
222
  return true;
223
223
  }
224
224
  catch (error) {
225
- (0, logger_1.log)("telemetry", `Failed to report events: ${error instanceof Error ? error.message : String(error)}`);
225
+ (0, logger_1.log)('telemetry', `Failed to report events: ${error instanceof Error ? error.message : String(error)}`);
226
226
  return false;
227
227
  }
228
228
  }
229
229
  async function reportInstallEvent(pluginKey, version) {
230
230
  await reportEvents([
231
231
  {
232
- resourceType: "plugin",
232
+ resourceType: 'plugin',
233
233
  resourceKey: pluginKey,
234
- eventType: "install",
234
+ eventType: 'install',
235
235
  details: { version },
236
236
  },
237
237
  ]);
@@ -239,9 +239,9 @@ async function reportInstallEvent(pluginKey, version) {
239
239
  async function reportCreateInstanceEvent(pluginKey, version) {
240
240
  await reportEvents([
241
241
  {
242
- resourceType: "plugin",
242
+ resourceType: 'plugin',
243
243
  resourceKey: pluginKey,
244
- eventType: "create_instance",
244
+ eventType: 'create_instance',
245
245
  details: { version },
246
246
  },
247
247
  ]);
@@ -3,25 +3,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerAppCommands = registerAppCommands;
4
4
  const shared_1 = require("../../../cli/commands/shared");
5
5
  const index_1 = require("../../../cli/handlers/app/index");
6
- function registerAppCommands(program) {
7
- const appCmd = program.command("app").description("应用元数据管理:查看 / 修改");
6
+ const error_1 = require("../../../utils/error");
7
+ function registerAppCommands(program, opts = {}) {
8
+ const description = opts.includeInit
9
+ ? '应用元数据管理:查看 / 修改 / 初始化'
10
+ : '应用元数据管理:查看 / 修改';
11
+ const appCmd = program.command('app').description(description);
8
12
  appCmd.action(() => {
9
13
  appCmd.outputHelp();
10
14
  });
11
- appCmd.addHelpText("after", `
15
+ appCmd.addHelpText('after', `
12
16
  作用范围
13
17
  仅供 Agent 在妙搭沙箱内调用。
14
18
  应用上下文:沙箱内的默认应用。
15
19
  `);
16
20
  registerAppGet(appCmd);
17
21
  registerAppUpdate(appCmd);
22
+ if (opts.includeInit) {
23
+ registerAppInit(appCmd);
24
+ }
18
25
  }
19
26
  function registerAppGet(parent) {
20
27
  const cmd = parent
21
- .command("get")
22
- .description("查看应用元数据(status / type / mode / branch / 时间戳 等)")
28
+ .command('get')
29
+ .description('查看应用元数据(status / type / mode / branch / 时间戳 等)')
23
30
  .addOption((0, shared_1.appIdOption)().hideHelp())
24
- .addHelpText("after", `
31
+ .addHelpText('after', `
25
32
  JSON 输出
26
33
  {"data": {"appID": "...", "name": "...", ...}}
27
34
 
@@ -30,7 +37,7 @@ JSON 输出
30
37
  $ miaoda app get --json
31
38
  `);
32
39
  cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
33
- (0, shared_1.rejectCliOverride)(cmd, "appId");
40
+ (0, shared_1.rejectCliOverride)(cmd, 'appId');
34
41
  await (0, index_1.handleAppGet)({
35
42
  appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
36
43
  });
@@ -38,12 +45,12 @@ JSON 输出
38
45
  }
39
46
  function registerAppUpdate(parent) {
40
47
  const cmd = parent
41
- .command("update")
42
- .description("修改应用名称 / 描述(至少传 --name 或 --description)")
48
+ .command('update')
49
+ .description('修改应用名称 / 描述(至少传 --name 或 --description)')
43
50
  .addOption((0, shared_1.appIdOption)().hideHelp())
44
- .option("--name <name>", "新应用名称")
45
- .option("--description <desc>", "新应用描述")
46
- .addHelpText("after", `
51
+ .option('--name <name>', '新应用名称')
52
+ .option('--description <desc>', '新应用描述')
53
+ .addHelpText('after', `
47
54
  JSON 输出
48
55
  与 'app get' 一致:{"data": {...新 meta...}}
49
56
 
@@ -52,7 +59,7 @@ JSON 输出
52
59
  $ miaoda app update --description "在线商城应用"
53
60
  `);
54
61
  cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
55
- (0, shared_1.rejectCliOverride)(cmd, "appId");
62
+ (0, shared_1.rejectCliOverride)(cmd, 'appId');
56
63
  await (0, index_1.handleAppUpdate)({
57
64
  appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
58
65
  name: rawOpts.name,
@@ -60,3 +67,77 @@ JSON 输出
60
67
  });
61
68
  }));
62
69
  }
70
+ function registerAppInit(parent) {
71
+ const cmd = parent
72
+ .command('init')
73
+ .description('初始化应用代码:抓 template 渲染、装 .agent/steering/ skills、写 .spark/meta.json。.spark/meta.json 已存在则直接退出')
74
+ .addOption((0, shared_1.appIdOption)().hideHelp())
75
+ .option('--template <stack>', `技术栈短名(${index_1.SUPPORTED_STACKS.join(' / ')})`)
76
+ .option('--conf <json>', 'init 配置 JSON。支持 {"version": "<template 版本>"} / {"steeringVersion": "<steering 版本>"},均默认 latest')
77
+ .option('--skip-install', '跳过依赖安装', false)
78
+ .addHelpText('after', `
79
+ 幂等
80
+ .spark/meta.json 已存在时直接退出(initialized=false, reason=already_initialized),
81
+ 不再触发 template 渲染、不重新拉 skills、不安装依赖。
82
+ meta.json 在 init 流程最后才写,写了 = 已完整成功一次;
83
+ 上一次失败留下半渲染状态时(package.json 在但 meta.json 没在)允许重跑。
84
+
85
+ 依赖安装
86
+ 默认:npm install --no-audit --no-fund
87
+ --skip-install: 跳过
88
+ MIAODA_DEP_CACHE_DIR 环境变量:
89
+ 若设了且 CWD 含 package.json,先算 md5(package.json),
90
+ 命中 \${MIAODA_DEP_CACHE_DIR}/\${hash}.zip 则 unzip 复用缓存;未命中或解压后 node_modules
91
+ 为空都会 fallback npm install。
92
+ JSON 模式下子进程 stdout 重定向到 stderr,避免污染最终 emit 的 JSON。
93
+
94
+ JSON 输出
95
+ 已初始化:{"data": {"initialized": false, "reason": "already_initialized", "targetDir": "..."}}
96
+ 新初始化:{"data": {"initialized": true, "template": "...", "templateVersion": "...", "steeringVersion": "...",
97
+ "installed": true, "installSource": "cache|npm|skipped", "installHash": "...", ...}}
98
+
99
+ 示例
100
+ $ miaoda app init --template vite-react
101
+ $ miaoda app init --template vite-react --conf '{"version": "0.1.0-alpha.1"}'
102
+ $ miaoda app init --template vite-react --conf '{"steeringVersion": "0.1.0"}'
103
+ $ miaoda app init --template vite-react --skip-install
104
+ $ MIAODA_DEP_CACHE_DIR=/tmp/dep-cache miaoda app init --template vite-react
105
+ `);
106
+ cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
107
+ (0, shared_1.rejectCliOverride)(cmd, 'appId');
108
+ const conf = parseInitConf(rawOpts.conf);
109
+ await (0, index_1.handleAppInit)({
110
+ appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
111
+ template: rawOpts.template,
112
+ conf,
113
+ skipInstall: rawOpts.skipInstall,
114
+ });
115
+ }));
116
+ }
117
+ function parseInitConf(raw) {
118
+ if (raw === undefined)
119
+ return undefined;
120
+ let parsed;
121
+ try {
122
+ parsed = JSON.parse(raw);
123
+ }
124
+ catch (err) {
125
+ const msg = err instanceof Error ? err.message : String(err);
126
+ throw new error_1.AppError('ARGS_INVALID', `--conf 必须是合法 JSON:${msg}`);
127
+ }
128
+ if (!isPlainObject(parsed)) {
129
+ throw new error_1.AppError('ARGS_INVALID', '--conf 必须是 JSON 对象');
130
+ }
131
+ const version = parsed.version;
132
+ if (version !== undefined && typeof version !== 'string') {
133
+ throw new error_1.AppError('ARGS_INVALID', '--conf.version 必须是字符串');
134
+ }
135
+ const steeringVersion = parsed.steeringVersion;
136
+ if (steeringVersion !== undefined && typeof steeringVersion !== 'string') {
137
+ throw new error_1.AppError('ARGS_INVALID', '--conf.steeringVersion 必须是字符串');
138
+ }
139
+ return { version, steeringVersion };
140
+ }
141
+ function isPlainObject(v) {
142
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
143
+ }