@ats-cx/cx-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.
- package/dist/init.js +12 -3
- package/dist/skills.js +20 -6
- package/package.json +2 -2
- package/skills/cx-cli-setup/SKILL.md +4 -4
package/dist/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { isApmConfigured,
|
|
3
|
+
import { isApmConfigured, loadApmProviderState, SENTENCE } from "@ats-cx/cx-core";
|
|
4
4
|
import { UserFacingError } from "./errors.js";
|
|
5
5
|
import { formatOut, jsonOut } from "./output.js";
|
|
6
6
|
import { REQUIRED_ENV_KEYS } from "./project-pull.js";
|
|
@@ -56,7 +56,7 @@ export function runInit(options) {
|
|
|
56
56
|
(existsSync(path) ? skipped : created).push(`${dir}/`);
|
|
57
57
|
mkdirSync(path, { recursive: true });
|
|
58
58
|
}
|
|
59
|
-
//
|
|
59
|
+
// 唯一无条件重写的已有文件:老工作区里内容为 `*` 的 .gitignore 会把语义表挡在 git 外,
|
|
60
60
|
// 必须换成选择性模板,否则组件 D 的「宿主 git 承载语义历史」不成立。
|
|
61
61
|
writeFileSync(join(workspaceDir, ".gitignore"), WORKSPACE_GITIGNORE);
|
|
62
62
|
rewritten.push(".gitignore");
|
|
@@ -73,11 +73,15 @@ export function runInit(options) {
|
|
|
73
73
|
else {
|
|
74
74
|
let existingConfig = null;
|
|
75
75
|
let invalidConfigJson = false;
|
|
76
|
+
let notObjectJson = false;
|
|
76
77
|
try {
|
|
77
78
|
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
78
79
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
79
80
|
existingConfig = parsed;
|
|
80
81
|
}
|
|
82
|
+
else {
|
|
83
|
+
notObjectJson = true;
|
|
84
|
+
}
|
|
81
85
|
}
|
|
82
86
|
catch {
|
|
83
87
|
invalidConfigJson = true;
|
|
@@ -90,6 +94,11 @@ export function runInit(options) {
|
|
|
90
94
|
skipped.push("config.json");
|
|
91
95
|
warnings.push("config.json 不是合法 JSON,未修改;请手工修复或删除后重跑 cx-cli init。");
|
|
92
96
|
}
|
|
97
|
+
else if (notObjectJson) {
|
|
98
|
+
// 与坏 JSON 同待遇:不是对象就没有可定点修的键,删了等于重建,由人决定。
|
|
99
|
+
skipped.push("config.json");
|
|
100
|
+
warnings.push("config.json 顶层不是 JSON 对象,未修改;请手工修复或删除后重跑 cx-cli init。");
|
|
101
|
+
}
|
|
93
102
|
else if (existingConfig && hasPointer && (typeof pointer !== "string" || pointer.length === 0)) {
|
|
94
103
|
delete existingConfig.toolkitConfigDir;
|
|
95
104
|
writeFileSync(configPath, `${JSON.stringify(existingConfig, null, 2)}\n`);
|
|
@@ -133,7 +142,7 @@ export function runInit(options) {
|
|
|
133
142
|
warnings.push(`工具箱缺少 ${seedSemantics},未生成工作区 event-semantics.json;诊断命令会因缺语义表报错。`);
|
|
134
143
|
}
|
|
135
144
|
const toolkitConfigDir = join(resolve(options.toolkitRoot), "config");
|
|
136
|
-
const apmState =
|
|
145
|
+
const apmState = loadApmProviderState(toolkitConfigDir, { source: "toolkit" }).apmProviderState;
|
|
137
146
|
if (apmState?.kind === "invalid") {
|
|
138
147
|
warnings.push(`apm 配置无效:${apmState.error}`);
|
|
139
148
|
}
|
package/dist/skills.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, realpathSync, symlinkSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { isApmConfigured,
|
|
4
|
+
import { isApmConfigured, loadApmProviderState, machineConfigDir, SENTENCE } from "@ats-cx/cx-core";
|
|
5
5
|
import { jsonOut } from "./output.js";
|
|
6
6
|
export const SKILLS_INSTALL_HELP = `Usage: cx-cli skills install
|
|
7
7
|
|
|
@@ -123,8 +123,9 @@ function findOrphans(packageRoot, skillNames, home) {
|
|
|
123
123
|
}
|
|
124
124
|
return [...mountOrphans, ...centralOrphans.values()];
|
|
125
125
|
}
|
|
126
|
+
/** 只读 apm-provider 叠层:toolkit.local.json / 语义表 / 规则表出错不该让 skills 组报 io。 */
|
|
126
127
|
function machineApmStatus(packageRoot) {
|
|
127
|
-
const state =
|
|
128
|
+
const state = loadApmProviderState(join(packageRoot, "config"), { source: "toolkit" }).apmProviderState;
|
|
128
129
|
return {
|
|
129
130
|
apmConfigured: isApmConfigured(state),
|
|
130
131
|
...(state?.kind === "invalid" ? { apmError: state.error } : {}),
|
|
@@ -301,7 +302,8 @@ export function runSkillsStatus(options) {
|
|
|
301
302
|
claude: existsSync(join(home, ".claude")),
|
|
302
303
|
codex: existsSync(join(home, ".codex")),
|
|
303
304
|
};
|
|
304
|
-
|
|
305
|
+
// issues 只放让 ok 为 false 的原因;缺 SKILL.md 的目录是包内容问题,install 修不了,进 hint 提醒。
|
|
306
|
+
const issues = [];
|
|
305
307
|
const orphans = findOrphans(packageRoot, inventory.names, home);
|
|
306
308
|
const skills = inventory.names.map(name => {
|
|
307
309
|
const central = inspectLink(join(centralDir, name), join(packageRoot, "skills", name), "realpath");
|
|
@@ -327,7 +329,7 @@ export function runSkillsStatus(options) {
|
|
|
327
329
|
const configDir = resolve(machineConfigDir());
|
|
328
330
|
const configPath = join(configDir, "apm-provider.local.json");
|
|
329
331
|
const apm = machineApmStatus(packageRoot);
|
|
330
|
-
const hintParts = [ok ? "skills 链接正常。" : "运行 cx-cli skills install 修复。"];
|
|
332
|
+
const hintParts = [ok ? "skills 链接正常。" : "运行 cx-cli skills install 修复。", ...inventory.warnings];
|
|
331
333
|
if (apm.apmError) {
|
|
332
334
|
hintParts.push(ensurePeriod(`apm 配置无效:${apm.apmError}`));
|
|
333
335
|
}
|
|
@@ -369,14 +371,26 @@ export function runSkillsUninstall(options) {
|
|
|
369
371
|
const orphans = findOrphans(packageRoot, inventory.names, home);
|
|
370
372
|
const removed = [];
|
|
371
373
|
const skipped = [];
|
|
374
|
+
// 中央段归属先算好:挂载链只在中央链是本包的(或已悬空)时才跟着删。
|
|
375
|
+
// 否则 nvm 双装场景下卸载旧版本会拆掉新版本的 Claude 入口(中央链早已重指到新版本)。
|
|
376
|
+
const centralOwned = new Map();
|
|
377
|
+
for (const name of inventory.names) {
|
|
378
|
+
const path = join(centralDir, name);
|
|
379
|
+
const target = symlinkTarget(path);
|
|
380
|
+
centralOwned.set(name, target !== null && (!existsSync(path) || isWithin(realpathSync(path), packageRoot)));
|
|
381
|
+
}
|
|
372
382
|
for (const mountDir of mountDirs) {
|
|
373
383
|
for (const name of inventory.names) {
|
|
374
384
|
const path = join(mountDir, name);
|
|
375
385
|
const target = symlinkTarget(path);
|
|
376
|
-
|
|
386
|
+
const centralPath = join(centralDir, name);
|
|
387
|
+
if (target === centralPath && centralOwned.get(name)) {
|
|
377
388
|
unlinkSync(path);
|
|
378
389
|
removed.push(path);
|
|
379
390
|
}
|
|
391
|
+
else if (target === centralPath) {
|
|
392
|
+
skipped.push({ path, reason: `中央链指向别处:${symlinkTarget(centralPath) ?? centralPath}` });
|
|
393
|
+
}
|
|
380
394
|
else if (target !== null) {
|
|
381
395
|
skipped.push({ path, reason: `指向别处:${target}` });
|
|
382
396
|
}
|
|
@@ -398,7 +412,7 @@ export function runSkillsUninstall(options) {
|
|
|
398
412
|
}
|
|
399
413
|
continue;
|
|
400
414
|
}
|
|
401
|
-
if (
|
|
415
|
+
if (centralOwned.get(name)) {
|
|
402
416
|
unlinkSync(path);
|
|
403
417
|
removed.push(path);
|
|
404
418
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ats-cx/cx-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "编辑器诊断工具箱命令行:串联埋点日志、project 快照与 APM,输出结构化诊断 run 供 Agent 消费;附带 Claude Code / Codex skills",
|
|
5
5
|
"author": "heyg",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"commander": "^15.0.0",
|
|
29
29
|
"zod": "^3.24.0",
|
|
30
|
-
"@ats-cx/cx-core": "0.1.
|
|
30
|
+
"@ats-cx/cx-core": "0.1.1"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc -p tsconfig.json",
|
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
|
|
9
9
|
# cx-cli Setup
|
|
10
10
|
|
|
11
|
-
两级安装的统一入口:**机器级**(全局命令 `cx-cli` 可用 + 包内 skills 链进本机 Agent + 机器级覆盖目录 `~/.config/cx-cli/`,一台机器一次)+ **项目级**(当前仓库建 `.cx-cli/`
|
|
11
|
+
两级安装的统一入口:**机器级**(全局命令 `cx-cli` 可用 + 包内 skills 链进本机 Agent + 机器级覆盖目录 `~/.config/cx-cli/`,一台机器一次)+ **项目级**(当前仓库建 `.cx-cli/` 诊断工作区,每个仓库一次)。全部幂等:重复触发只补缺的、修坏的,已有配置与语义表不动(仅为空、非字符串、指向不存在或非目录路径的 `config.json.toolkitConfigDir` 会被定点移除);`.cx-cli/.gitignore` 是唯一每次都按当前模板重写的已有文件。
|
|
12
12
|
|
|
13
13
|
本 skill 只调用现成命令并解读 `--json` 输出:机器级喊 `cx-cli skills status` / `cx-cli skills install`,项目级喊 `cx-cli init`。不要绕过它们手工建软链、手工建目录。本 skill 随 `@ats-cx/cx-cli` 包分发,**不需要知道包装在哪**——需要路径时全部从命令输出里取。
|
|
14
14
|
|
|
@@ -78,9 +78,9 @@ cx-cli init --json
|
|
|
78
78
|
| 字段 | 含义与动作 |
|
|
79
79
|
|------|-----------|
|
|
80
80
|
| `workspaceDir` | 工作区位置(宿主仓库根 `.cx-cli/`) |
|
|
81
|
-
| `created` / `skipped` | 本次建了什么 / 已存在没动什么。首次 init 从工具箱种子复制 `event-semantics.json
|
|
82
|
-
| `rewritten` | 本次被 init 改写的已有文件:`.gitignore` 每次按当前模板重写;`config.json` 仅在其 `toolkitConfigDir`
|
|
83
|
-
| `warnings` | 四类:「宿主不像编辑器仓库」→ 转述给用户确认是否跑错目录,不阻塞;「skills 未安装到本机 Agent(…)」→ 回 §2(§1–2 走完后不该出现);「apm 未配置:…」→ §4 处理;「config.json 的 toolkitConfigDir
|
|
81
|
+
| `created` / `skipped` | 本次建了什么 / 已存在没动什么。首次 init 从工具箱种子复制 `event-semantics.json`;已有语义表与 `.env` 不覆盖;`config.json` 除下行所述的失效指针修复外不覆盖 |
|
|
82
|
+
| `rewritten` | 本次被 init 改写的已有文件:`.gitignore` 每次按当前模板重写;`config.json` 仅在其 `toolkitConfigDir` 为空、非字符串、指向不存在或非目录路径时被修(只删这一键,`warnings` 会说明) |
|
|
83
|
+
| `warnings` | 四类:「宿主不像编辑器仓库」→ 转述给用户确认是否跑错目录,不阻塞;「skills 未安装到本机 Agent(…)」→ 回 §2(§1–2 走完后不该出现);「apm 未配置:…」→ §4 处理;「config.json 的 toolkitConfigDir … 已移除」→ 转述即可,无需动作 |
|
|
84
84
|
| `missingEnvKeys` | `.cx-cli/.env` 里还空着的 DB 凭据键 |
|
|
85
85
|
| `hint` | 汇总提示,可直接转述;apm 未配置时它会说「填好 apm 后可运行 run new / apm query」 |
|
|
86
86
|
|