@lark-apaas/miaoda-cli 0.1.24-alpha.bc2625d → 0.1.24-alpha.cb67974

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.
@@ -70,7 +70,6 @@ exports.SQL_KEYWORDS = [
70
70
  // DDL
71
71
  'CREATE',
72
72
  'ALTER',
73
- 'ENABLE',
74
73
  'DROP',
75
74
  'TRUNCATE',
76
75
  'RENAME',
@@ -21,11 +21,15 @@ function registerSkillsCommands(program) {
21
21
  function registerSkillsSync(parent) {
22
22
  const cmd = parent
23
23
  .command('sync')
24
- .description('同步 coding-steering(latest 或指定版本)到 user app')
24
+ .description('同步 skills 到 user app(默认 coding-steering;带 --type 时走 sandbox-skills)')
25
25
  .option('--dir <path>', '项目目录,默认当前目录', '.')
26
26
  .option('--version <ver>', 'coding-steering 包版本或 dist-tag,缺省 latest')
27
27
  .option('--local', '走本地 dev / agent 输出形态:拷到 .agents/skills 平铺 + 创建 .claude/skills 软链。' +
28
28
  '不传时退回老形态:拷到 .agent/skills/steering/<stack>/skills/(跟沙箱端 update-skills.sh 对齐)', false)
29
+ .option('--type <type>', '走 sandbox-skills 分支:miaoda | miaoda-modern | miaoda-design | openclaw。' +
30
+ '有值时忽略 --version/--local,改从 @lark-apaas/coding-<pkg>-sandbox 拉对应包写入 .agent/skills/')
31
+ .option('--tgz-key <key>', '配合 --type 使用;老 TCC skillsUpdateTgzKey 值透传,' +
32
+ '从 skills_versions/all_<X>.tgz 反推出 dist-tag lane-<X>;缺省走 @latest')
29
33
  .addHelpText('after', `
30
34
  前置要求
31
35
  当前目录(或 --dir)已走过 'miaoda app init'(.spark/meta.json 含 stack)
@@ -56,6 +60,8 @@ JSON 输出
56
60
  dir: rawOpts.dir,
57
61
  version: rawOpts.version,
58
62
  local: rawOpts.local,
63
+ type: rawOpts.type,
64
+ tgzKey: rawOpts.tgzKey,
59
65
  });
60
66
  }));
61
67
  }
@@ -411,19 +411,6 @@ const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?
411
411
  const SYNTAX_RE = /syntax error at or near "([^"]+)"/i;
412
412
  const RELATION_RE = /relation "([^"]+)" does not exist/i;
413
413
  const COLUMN_RE = /column "([^"]+)"(?: of relation "([^"]+)")? does not exist/i;
414
- const KEYWORD_TYPO_IGNORE_WORDS = new Set([
415
- // Legal PostgreSQL terms that are not useful keyword typo candidates.
416
- 'ROW',
417
- 'LEVEL',
418
- 'SECURITY',
419
- // Common index access methods used after `USING`.
420
- 'BTREE',
421
- 'HASH',
422
- 'GIST',
423
- 'SPGIST',
424
- 'GIN',
425
- 'BRIN',
426
- ]);
427
414
  /**
428
415
  * 在错误抛出前富化 next_actions(最多 1 条 hint),按 PG 报错形态分发:
429
416
  *
@@ -553,8 +540,6 @@ function findKeywordTypo(sql) {
553
540
  const upper = tok.toUpperCase();
554
541
  if (sql_keywords_1.SQL_KEYWORDS.includes(upper))
555
542
  continue; // 写对的关键字
556
- if (KEYWORD_TYPO_IGNORE_WORDS.has(upper))
557
- continue;
558
543
  const guess = (0, fuzzy_match_1.suggest)(upper, sql_keywords_1.SQL_KEYWORDS);
559
544
  if (guess && guess !== upper) {
560
545
  return { input: tok, suggest: guess };
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.handleSkillsSync = handleSkillsSync;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  const coding_steering_1 = require("../../../utils/coding-steering");
9
+ const sandbox_skills_1 = require("../../../utils/sandbox-skills");
9
10
  const spark_meta_1 = require("../../../utils/spark-meta");
10
11
  const error_1 = require("../../../utils/error");
11
12
  const output_1 = require("../../../utils/output");
@@ -26,6 +27,25 @@ const output_1 = require("../../../utils/output");
26
27
  async function handleSkillsSync(opts) {
27
28
  await Promise.resolve();
28
29
  const targetDir = node_path_1.default.resolve(opts.dir ?? process.cwd());
30
+ // --type 走 sandbox-skills 分支(迁仓后沙箱内 update-skills.sh 调用),跟本地 dev 的
31
+ // steering 同步逻辑并列——两条路径的目标 / 拉包 / 目录结构完全不同,用 --type 显式路由。
32
+ if (opts.type !== undefined && opts.type !== '') {
33
+ const { type, tgzKey } = (0, sandbox_skills_1.validateSandboxSyncOpts)({ type: opts.type, tgzKey: opts.tgzKey });
34
+ const result = (0, sandbox_skills_1.syncSandboxSkills)({ type, tgzKey, targetDir });
35
+ (0, output_1.emit)({
36
+ data: {
37
+ mode: 'sandbox',
38
+ type,
39
+ packageName: result.packageName,
40
+ version: result.version,
41
+ lane: result.lane,
42
+ syncedTop: result.syncedTop,
43
+ targetDir: result.targetDir,
44
+ },
45
+ });
46
+ return;
47
+ }
48
+ // 原有 coding-steering 同步逻辑(本地 dev / miaoda app init 用)
29
49
  const meta = (0, spark_meta_1.readSparkMeta)(targetDir);
30
50
  if (meta.stack === undefined || meta.stack === '') {
31
51
  throw new error_1.AppError('SKILLS_META_INCOMPLETE', '.spark/meta.json missing stack — run `miaoda app init` first');
@@ -19,32 +19,14 @@ exports.EXCLUDES = new Set([
19
19
  'yarn.lock',
20
20
  '.gitignore',
21
21
  '.npmrc',
22
- // agent / 编辑器配置目录与指令文件:只服务本地开发,不属于应用资产。
23
- // 注意 .claude/skills 在 flat layout 下是指向 ../.agents/skills 的软链,而 listSourceFiles
24
- // 会 follow 软链,所以必须按 basename 把 .claude 整个排掉,否则 skills 会从软链侧被带进产物。
25
22
  '.agent',
26
23
  '.agents',
27
- '.claude',
28
- '.codex',
29
- '.cursor',
30
- '.gemini',
31
- '.trae',
32
- '.windsurf',
33
- '.vscode',
34
- '.idea',
35
- 'AGENTS.md',
36
- 'CLAUDE.md',
37
- 'GEMINI.md',
38
24
  'skills',
39
25
  '.env',
40
26
  '.env.local',
41
27
  'README.md',
42
28
  '.DS_Store',
43
29
  '.spark',
44
- // 本地开发的临时/运行时目录:tmp 里的 .html 是草稿或中间产物,logs 是本地日志,
45
- // 两者既不进产物也不该出现在 routes.json。
46
- 'tmp',
47
- 'logs',
48
30
  ]);
49
31
  /**
50
32
  * 递归列出 root 下所有文件的相对 posix 路径,跳过 EXCLUDES(任意层级 basename),
@@ -0,0 +1,193 @@
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.extractLaneFromTgzKey = extractLaneFromTgzKey;
7
+ exports.syncSandboxSkills = syncSandboxSkills;
8
+ exports.validateSandboxSyncOpts = validateSandboxSyncOpts;
9
+ const node_child_process_1 = require("node:child_process");
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const npm_pack_1 = require("../utils/npm-pack");
13
+ const error_1 = require("../utils/error");
14
+ const logger_1 = require("../utils/logger");
15
+ /**
16
+ * 覆盖式清空目录:先 shell `rm -rf`,挂了再 fallback 到 fs.rmSync。
17
+ *
18
+ * 为什么不直接 fs.rmSync:老沙箱残留的文件可能是**别的用户**写的(例如老 update.sh
19
+ * 以 root 用户跑,写下 root owned 的 `.last-update`)。`fs.rmSync {force:true}` 只
20
+ * 吞 ENOENT,遇到 EACCES 直接 throw;而 shell `rm -rf` 对权限的处理更宽松(只要
21
+ * 对**目录**有写权限就能删掉里面的文件,无视文件本身的 owner/权限)。
22
+ */
23
+ function forceRemoveDir(dir) {
24
+ if (!node_fs_1.default.existsSync(dir))
25
+ return;
26
+ try {
27
+ (0, node_child_process_1.execFileSync)('rm', ['-rf', dir], { stdio: ['ignore', 'ignore', 'pipe'] });
28
+ if (!node_fs_1.default.existsSync(dir))
29
+ return;
30
+ }
31
+ catch (err) {
32
+ (0, logger_1.debug)(`sandbox-skills: shell rm -rf ${dir} failed: ${err.message}, fallback to fs.rmSync`);
33
+ }
34
+ // Fallback(比如沙箱没 rm、或权限受限):尽力用 Node fs.rmSync
35
+ node_fs_1.default.rmSync(dir, { recursive: true, force: true });
36
+ }
37
+ const PACKAGE_BY_TYPE = {
38
+ miaoda: '@lark-apaas/coding-miaoda-sandbox',
39
+ 'miaoda-modern': '@lark-apaas/coding-miaoda-sandbox',
40
+ 'miaoda-design': '@lark-apaas/coding-miaoda-sandbox',
41
+ openclaw: '@lark-apaas/coding-openclaw-sandbox',
42
+ };
43
+ // 合并包内的业务线子目录(miaoda / miaoda-modern / miaoda-design);shared 单独处理。
44
+ const BUSINESS_LINE_BY_TYPE = {
45
+ miaoda: 'miaoda',
46
+ 'miaoda-modern': 'miaoda-modern',
47
+ 'miaoda-design': 'miaoda-design',
48
+ openclaw: null, // openclaw 独立包,直接展平
49
+ };
50
+ /**
51
+ * 从 TCC skillsUpdateTgzKey 反推 lane 名。
52
+ * skills_versions/all_feat-xxx.tgz → feat-xxx
53
+ * skills_versions/all_latest.tgz → null(走 @latest)
54
+ * 任何 all_<X>.tgz → <X>
55
+ *
56
+ * 用 nullable 而不是空字符串 —— 让调用方能判断"没 lane 就走 latest"。
57
+ */
58
+ function extractLaneFromTgzKey(tgzKey) {
59
+ if (tgzKey === undefined || tgzKey === '') {
60
+ return null;
61
+ }
62
+ const match = /(?:^|\/)all_([A-Za-z0-9][A-Za-z0-9_-]*)\.tgz$/.exec(tgzKey);
63
+ if (match === null) {
64
+ return null;
65
+ }
66
+ const lane = match[1];
67
+ if (lane === 'latest') {
68
+ return null;
69
+ }
70
+ return lane;
71
+ }
72
+ /**
73
+ * 主流程:按 type 拉包 + sync 到目标目录。
74
+ *
75
+ * sync 策略:**覆盖式写入 + 老文件清理**,对齐老 cp-skills.sh 行为
76
+ * (`rm -rf $TARGET; mkdir -p $TARGET; cp -r ...`)。
77
+ */
78
+ function syncSandboxSkills(opts) {
79
+ const targetDir = node_path_1.default.resolve(opts.targetDir ?? process.cwd());
80
+ const packageName = PACKAGE_BY_TYPE[opts.type];
81
+ const businessLine = BUSINESS_LINE_BY_TYPE[opts.type];
82
+ const lane = extractLaneFromTgzKey(opts.tgzKey);
83
+ // 优先拉 lane dist-tag;失败 fallback 到 latest(防灰度切换期间 dist-tag 缺失 404)
84
+ const primaryVersion = lane !== null ? `lane-${lane}` : 'latest';
85
+ let fetched;
86
+ try {
87
+ (0, logger_1.debug)(`sandbox-skills: fetching ${packageName}@${primaryVersion}`);
88
+ fetched = (0, npm_pack_1.fetchNpmPackage)({
89
+ packageName,
90
+ version: primaryVersion,
91
+ registry: opts.registry,
92
+ });
93
+ }
94
+ catch (err) {
95
+ if (lane === null) {
96
+ throw err;
97
+ }
98
+ (0, logger_1.debug)(`sandbox-skills: lane ${primaryVersion} not available, fallback to @latest (err: ${err.message})`);
99
+ fetched = (0, npm_pack_1.fetchNpmPackage)({
100
+ packageName,
101
+ version: 'latest',
102
+ registry: opts.registry,
103
+ });
104
+ }
105
+ try {
106
+ // 计算 skill 目标根目录:
107
+ // - openclaw → <targetDir>/skills/(跟老 cp-skills.sh 的 openclaw 分支对齐,落 $WORKSPACE_DIR/agent/skills)
108
+ // - 其它 → <targetDir>/.agent/skills/
109
+ const skillsRoot = opts.type === 'openclaw'
110
+ ? node_path_1.default.join(targetDir, 'skills')
111
+ : node_path_1.default.join(targetDir, '.agent', 'skills');
112
+ // 覆盖式:清空目标 skill 根目录(对齐 cp-skills.sh 行为,避免遗留老 skill)
113
+ // 用 forceRemoveDir 兜底:老沙箱可能残留其他用户写的 stale 文件(例如老 update.sh
114
+ // 以 root 跑,写下 root owned 的 `.last-update`),fs.rmSync {force:true} 遇到 EACCES
115
+ // 会 throw。见 forceRemoveDir 注释。
116
+ forceRemoveDir(skillsRoot);
117
+ node_fs_1.default.mkdirSync(skillsRoot, { recursive: true });
118
+ const syncedTop = [];
119
+ if (businessLine !== null) {
120
+ // 合并包 miaoda/miaoda-modern/miaoda-design:cp <extractDir>/<business-line>/* 到 target
121
+ const sourceDir = node_path_1.default.join(fetched.extractDir, businessLine);
122
+ if (node_fs_1.default.existsSync(sourceDir)) {
123
+ for (const entry of node_fs_1.default.readdirSync(sourceDir)) {
124
+ const from = node_path_1.default.join(sourceDir, entry);
125
+ const to = node_path_1.default.join(skillsRoot, entry);
126
+ node_fs_1.default.cpSync(from, to, { recursive: true });
127
+ syncedTop.push(entry);
128
+ }
129
+ }
130
+ else {
131
+ (0, logger_1.debug)(`sandbox-skills: source dir ${sourceDir} not found in package (skipped)`);
132
+ }
133
+ // shared:sync 合并包内 shared/* 到 target(对齐老 build.sh 的 shared 分发行为)
134
+ const sharedDir = node_path_1.default.join(fetched.extractDir, 'shared');
135
+ if (node_fs_1.default.existsSync(sharedDir)) {
136
+ for (const entry of node_fs_1.default.readdirSync(sharedDir)) {
137
+ const from = node_path_1.default.join(sharedDir, entry);
138
+ const to = node_path_1.default.join(skillsRoot, entry);
139
+ if (node_fs_1.default.existsSync(to)) {
140
+ // shared 命中已存在的 skill(如同名)— 保留业务线版本,跳过 shared(对齐老 build.sh:shared 是补丁,同名不覆盖)
141
+ (0, logger_1.debug)(`sandbox-skills: shared/${entry} conflicts with business-line skill, skipped`);
142
+ continue;
143
+ }
144
+ node_fs_1.default.cpSync(from, to, { recursive: true });
145
+ syncedTop.push(entry);
146
+ }
147
+ }
148
+ }
149
+ else {
150
+ // openclaw 独立包:直接把包内所有 skill 目录(除 package.json 等元文件外)平铺到 skillsRoot
151
+ for (const entry of node_fs_1.default.readdirSync(fetched.extractDir)) {
152
+ if (entry === 'package.json' || entry === 'README.md' || entry === 'LICENSE')
153
+ continue;
154
+ const from = node_path_1.default.join(fetched.extractDir, entry);
155
+ const stat = node_fs_1.default.statSync(from);
156
+ if (!stat.isDirectory())
157
+ continue;
158
+ const to = node_path_1.default.join(skillsRoot, entry);
159
+ node_fs_1.default.cpSync(from, to, { recursive: true });
160
+ syncedTop.push(entry);
161
+ }
162
+ }
163
+ // 写 .last-update 时间戳(对齐老 update-skills.sh;供 feida-ai freshness 检查用)
164
+ const lastUpdatePath = node_path_1.default.join(skillsRoot, '.last-update');
165
+ node_fs_1.default.writeFileSync(lastUpdatePath, String(Date.now()));
166
+ return {
167
+ packageName,
168
+ version: fetched.version,
169
+ lane,
170
+ syncedTop,
171
+ targetDir: skillsRoot,
172
+ };
173
+ }
174
+ finally {
175
+ fetched.cleanup();
176
+ }
177
+ }
178
+ /**
179
+ * 参数校验:type 白名单 + tgzKey 格式(避免非法值透传到 npm 包名)。
180
+ */
181
+ function validateSandboxSyncOpts(opts) {
182
+ const validTypes = ['miaoda', 'miaoda-modern', 'miaoda-design', 'openclaw'];
183
+ if (opts.type === undefined || !validTypes.includes(opts.type)) {
184
+ throw new error_1.AppError('SKILLS_INVALID_TYPE', `--type must be one of: ${validTypes.join(', ')} (got: ${opts.type ?? 'undefined'})`);
185
+ }
186
+ if (opts.tgzKey !== undefined && opts.tgzKey !== '') {
187
+ // 校验 tgz key 大致格式(前缀 skills_versions/ + all_ + 允许字符 + .tgz);避免非法值影响 lane 反推
188
+ if (!/^[A-Za-z0-9/_\-.]+\.tgz$/.test(opts.tgzKey)) {
189
+ throw new error_1.AppError('SKILLS_INVALID_TGZ_KEY', `--tgz-key value looks invalid: ${opts.tgzKey}`);
190
+ }
191
+ }
192
+ return { type: opts.type, tgzKey: opts.tgzKey };
193
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.24-alpha.bc2625d",
3
+ "version": "0.1.24-alpha.cb67974",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {