Skip to content

Commit 86c6f60

Browse files
committed
fix(cli): align global sync with real consumer paths
1 parent a5a3a10 commit 86c6f60

9 files changed

Lines changed: 242 additions & 85 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ npm install -g .
5454
- 项目安装:`ag-kit init` / `ag-kit update`(功能最完整)
5555
- 全局安装:`ag-kit global sync`(仅同步 Skills,跨项目复用)
5656
- 默认行为:`ag-kit global sync` 未指定 `--target/--targets` 时,同步 `codex + gemini`
57+
- 真实落盘:
58+
- `codex` -> `~/.codex/skills/`
59+
- `gemini` -> 同时写入 `~/.gemini/skills/``~/.gemini/antigravity/skills/`
5760

5861
示例:
5962

@@ -165,7 +168,7 @@ CLI(命令行界面)工具:
165168
| `ag-kit update` | 更新当前项目已安装目标 |
166169
| `ag-kit update-all` | 批量更新所有已登记工作区 |
167170
| `ag-kit doctor` | 诊断安装完整性(可 `--fix` 自愈) |
168-
| `ag-kit global sync` | 全局同步 Skills(默认同步 codex+gemini) |
171+
| `ag-kit global sync` | 全局同步 Skills(默认同步 codex + gemini;其中 gemini 同步到 gemini-cli 与 antigravity|
169172
| `ag-kit global status` | 查看全局 Skills 安装状态 |
170173
| `ag-kit exclude` | 管理全局索引排除清单 |
171174
| `ag-kit status` | 检查安装状态 |
@@ -227,7 +230,7 @@ bun install --cwd web
227230
bun run lint --cwd web
228231
```
229232

230-
> 说明:若你通过 `bun install -g` 安装 CLI,Bun 默认会阻止本包 `postinstall`。上游同名包冲突提示会在首次执行 `ag-kit init/update/global sync` 时给出。
233+
> 说明:若你通过 `bun install -g` 安装 CLI,Bun 默认会阻止本包 `postinstall`。上游同名包冲突提示会在首次执行 `ag-kit init/update/update-all/global sync` 时给出。
231234
232235
## 卸载
233236

bin/ag-kit.js

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@ const WORKSPACE_INDEX_VERSION = 2;
1818
const UPSTREAM_GLOBAL_PACKAGE = "@vudovn/ag-kit";
1919
const TOOLKIT_PACKAGE_NAMES = new Set(["@mison/ag-kit-cn", "antigravity-kit-cn", "antigravity-kit"]);
2020
const SUPPORTED_TARGETS = ["gemini", "codex"];
21+
const LEGACY_INDEX_TARGET_ALIASES = {
22+
full: "gemini",
23+
};
24+
const GLOBAL_TARGET_DESTINATIONS = {
25+
codex: [
26+
{
27+
id: "codex",
28+
rootParts: [".codex"],
29+
skillsParts: [".codex", "skills"],
30+
},
31+
],
32+
gemini: [
33+
{
34+
id: "gemini-cli",
35+
rootParts: [".gemini", "skills"],
36+
skillsParts: [".gemini", "skills"],
37+
},
38+
{
39+
id: "antigravity",
40+
rootParts: [".gemini", "antigravity"],
41+
skillsParts: [".gemini", "antigravity", "skills"],
42+
},
43+
],
44+
};
2145
const INDEX_LOCK_RETRY_MS = 50;
2246
const INDEX_LOCK_TIMEOUT_MS = 3000;
2347
const INDEX_LOCK_STALE_MS = 30000;
@@ -56,15 +80,21 @@ function resolveGlobalRootDir() {
5680
return os.homedir();
5781
}
5882

59-
function resolveGlobalSkillRoot(targetName) {
60-
const globalRoot = resolveGlobalRootDir();
61-
if (targetName === "codex") {
62-
return path.join(globalRoot, ".agents", "skills");
83+
function getGlobalDestinations(targetName, globalRoot = resolveGlobalRootDir()) {
84+
const config = GLOBAL_TARGET_DESTINATIONS[targetName];
85+
if (!config) {
86+
throw new Error(`未知目标: ${targetName}`);
6387
}
64-
if (targetName === "gemini") {
65-
return path.join(globalRoot, ".gemini", "antigravity", "skills");
66-
}
67-
throw new Error(`未知目标: ${targetName}`);
88+
return config.map((item) => ({
89+
...item,
90+
targetName,
91+
rootDir: path.join(globalRoot, ...item.rootParts),
92+
skillsRoot: path.join(globalRoot, ...item.skillsParts),
93+
}));
94+
}
95+
96+
function listGlobalDestinations(globalRoot = resolveGlobalRootDir()) {
97+
return Object.keys(GLOBAL_TARGET_DESTINATIONS).flatMap((targetName) => getGlobalDestinations(targetName, globalRoot));
6898
}
6999

70100
function resolveGlobalBackupRoot(timestamp) {
@@ -108,7 +138,7 @@ function printUsage() {
108138
console.log(" ag-kit update [--path <dir>] [--branch <name>] [--target <name>|--targets <a,b>] [--no-index] [--quiet] [--dry-run]");
109139
console.log(" ag-kit update-all [--branch <name>] [--targets <a,b>] [--prune-missing] [--quiet] [--dry-run]");
110140
console.log(" ag-kit doctor [--path <dir>] [--target <name>|--targets <a,b>] [--fix] [--quiet]");
111-
console.log(" ag-kit global sync [--target <name>|--targets <a,b>] [--branch <name>] [--quiet] [--dry-run] # 默认同步 codex+gemini");
141+
console.log(" ag-kit global sync [--target <name>|--targets <a,b>] [--branch <name>] [--quiet] [--dry-run] # 默认同步 codex + gemini(cli+antigravity)");
112142
console.log(" ag-kit global status [--quiet]");
113143
console.log(" ag-kit exclude list [--quiet]");
114144
console.log(" ag-kit exclude add --path <dir> [--dry-run] [--quiet]");
@@ -416,13 +446,34 @@ function normalizeTargetState(value) {
416446
};
417447
}
418448

449+
function normalizeIndexTargetName(targetName) {
450+
if (typeof targetName !== "string") {
451+
return null;
452+
}
453+
const normalized = targetName.trim().toLowerCase();
454+
if (!normalized) {
455+
return null;
456+
}
457+
if (Object.prototype.hasOwnProperty.call(LEGACY_INDEX_TARGET_ALIASES, normalized)) {
458+
return LEGACY_INDEX_TARGET_ALIASES[normalized];
459+
}
460+
if (SUPPORTED_TARGETS.includes(normalized)) {
461+
return normalized;
462+
}
463+
return null;
464+
}
465+
419466
function normalizeWorkspaceRecordV2(item, normalizedPath) {
420467
const targets = {};
421468
if (item && item.targets && typeof item.targets === "object") {
422469
for (const [targetName, state] of Object.entries(item.targets)) {
470+
const normalizedTargetName = normalizeIndexTargetName(targetName);
471+
if (!normalizedTargetName) {
472+
continue;
473+
}
423474
const normalizedState = normalizeTargetState(state);
424475
if (normalizedState) {
425-
targets[targetName] = normalizedState;
476+
targets[normalizedTargetName] = normalizedState;
426477
}
427478
}
428479
}
@@ -862,33 +913,16 @@ function evaluateWorkspaceState(workspaceRoot, options) {
862913
};
863914
}
864915

865-
function getGlobalTargetPaths(globalRoot, targetName) {
866-
if (targetName === "codex") {
867-
return {
868-
markerDir: path.join(globalRoot, ".agents"),
869-
skillsRoot: path.join(globalRoot, ".agents", "skills"),
870-
};
871-
}
872-
if (targetName === "gemini") {
873-
return {
874-
markerDir: path.join(globalRoot, ".gemini", "antigravity"),
875-
skillsRoot: path.join(globalRoot, ".gemini", "antigravity", "skills"),
876-
};
877-
}
878-
throw new Error(`未知全局目标: ${targetName}`);
879-
}
880-
881916
function evaluateGlobalState() {
882917
const globalRoot = resolveGlobalRootDir();
883-
const targetStates = SUPPORTED_TARGETS.map((targetName) => {
884-
const paths = getGlobalTargetPaths(globalRoot, targetName);
885-
const markerExists = fs.existsSync(paths.markerDir);
886-
const skillsExists = fs.existsSync(paths.skillsRoot);
887-
const skillsCount = skillsExists ? countSkillsRecursive(paths.skillsRoot) : 0;
918+
const targetStates = listGlobalDestinations(globalRoot).map((destination) => {
919+
const rootExists = fs.existsSync(destination.rootDir);
920+
const skillsExists = fs.existsSync(destination.skillsRoot);
921+
const skillsCount = skillsExists ? countSkillsRecursive(destination.skillsRoot) : 0;
888922
let state = "missing";
889923
const issues = [];
890924

891-
if (markerExists || skillsExists) {
925+
if (rootExists || skillsExists) {
892926
if (!skillsExists) {
893927
state = "broken";
894928
issues.push("Skills 根目录缺失");
@@ -901,10 +935,11 @@ function evaluateGlobalState() {
901935
}
902936

903937
return {
904-
targetName,
938+
targetName: destination.id,
939+
family: destination.targetName,
905940
state,
906-
markerDir: paths.markerDir,
907-
skillsRoot: paths.skillsRoot,
941+
rootDir: destination.rootDir,
942+
skillsRoot: destination.skillsRoot,
908943
skillsCount,
909944
issues,
910945
};
@@ -972,7 +1007,7 @@ function resolveTargetsForGlobalSync(options) {
9721007
if (requested.length > 0) {
9731008
return requested;
9741009
}
975-
// 保持 global sync 简洁:默认同步两个目标
1010+
// 保持 global sync 简洁:默认同步 codex + gemini;其中 gemini 会展开为 gemini-cli 与 antigravity
9761011
return ["codex", "gemini"];
9771012
}
9781013

@@ -1025,58 +1060,87 @@ function backupSkillDirectory(targetName, skillName, sourceDir, timestamp, optio
10251060
log(options, `📦 已备份 ${targetName} 全局 Skill: ${skillName} -> ${backupDir}`);
10261061
}
10271062

1028-
function syncSkillDirectory(targetName, srcDir, destDir, timestamp, options) {
1063+
function syncSkillDirectory(destination, srcDir, destDir, timestamp, options) {
10291064
const exists = fs.existsSync(destDir);
10301065
if (exists) {
10311066
if (areDirectoriesEqual(srcDir, destDir)) {
1032-
log(options, `⏭️ 全局 Skill 已最新,无需同步: ${targetName}/${path.basename(destDir)}`);
1067+
log(options, `⏭️ 全局 Skill 已最新,无需同步: ${destination.id}/${path.basename(destDir)}`);
10331068
return { skipped: 1, synced: 0, backedUp: 0 };
10341069
}
10351070
}
10361071

10371072
if (options.dryRun) {
1038-
log(options, `[dry-run] 将同步全局 Skill: ${targetName}/${path.basename(destDir)}`);
1073+
log(options, `[dry-run] 将同步全局 Skill: ${destination.id}/${path.basename(destDir)}`);
10391074
return { skipped: 0, synced: 0, backedUp: exists ? 1 : 0 };
10401075
}
10411076

10421077
let backedUp = 0;
10431078
if (exists) {
1044-
backupSkillDirectory(targetName, path.basename(destDir), destDir, timestamp, options);
1079+
backupSkillDirectory(destination.id, path.basename(destDir), destDir, timestamp, options);
10451080
backedUp = 1;
10461081
}
10471082

10481083
const logger = options.quiet ? (() => {}) : log.bind(null, options);
10491084
AtomicWriter.atomicCopyDir(srcDir, destDir, { logger });
1050-
log(options, `✅ 已同步全局 Skill: ${targetName}/${path.basename(destDir)}`);
1085+
log(options, `✅ 已同步全局 Skill: ${destination.id}/${path.basename(destDir)}`);
10511086

10521087
return { skipped: 0, synced: 1, backedUp };
10531088
}
10541089

10551090
function syncGlobalSkillsFromRoot(targetName, skillsRoot, timestamp, options) {
1056-
const destRoot = resolveGlobalSkillRoot(targetName);
1091+
const destinations = getGlobalDestinations(targetName);
10571092
const skillNames = listSkillDirectories(skillsRoot);
10581093
if (skillNames.length === 0) {
10591094
throw new Error(`未检测到可同步的 Skills: ${skillsRoot}`);
10601095
}
10611096

10621097
if (options.dryRun) {
1063-
log(options, `[dry-run] 将同步 ${skillNames.length} 个全局 Skills -> ${destRoot}`);
1098+
for (const destination of destinations) {
1099+
log(options, `[dry-run] 将同步 ${skillNames.length} 个全局 Skills -> ${destination.skillsRoot}`);
1100+
}
10641101
}
10651102

10661103
let synced = 0;
10671104
let skipped = 0;
10681105
let backedUp = 0;
1106+
const destinationResults = [];
1107+
1108+
for (const destination of destinations) {
1109+
let destinationSynced = 0;
1110+
let destinationSkipped = 0;
1111+
let destinationBackedUp = 0;
1112+
1113+
for (const skillName of skillNames) {
1114+
const srcDir = path.join(skillsRoot, skillName);
1115+
const destDir = path.join(destination.skillsRoot, skillName);
1116+
const result = syncSkillDirectory(destination, srcDir, destDir, timestamp, options);
1117+
synced += result.synced;
1118+
skipped += result.skipped;
1119+
backedUp += result.backedUp;
1120+
destinationSynced += result.synced;
1121+
destinationSkipped += result.skipped;
1122+
destinationBackedUp += result.backedUp;
1123+
}
10691124

1070-
for (const skillName of skillNames) {
1071-
const srcDir = path.join(skillsRoot, skillName);
1072-
const destDir = path.join(destRoot, skillName);
1073-
const result = syncSkillDirectory(targetName, srcDir, destDir, timestamp, options);
1074-
synced += result.synced;
1075-
skipped += result.skipped;
1076-
backedUp += result.backedUp;
1125+
destinationResults.push({
1126+
targetName: destination.id,
1127+
family: destination.targetName,
1128+
destRoot: destination.skillsRoot,
1129+
total: skillNames.length,
1130+
synced: destinationSynced,
1131+
skipped: destinationSkipped,
1132+
backedUp: destinationBackedUp,
1133+
});
10771134
}
10781135

1079-
return { total: skillNames.length, synced, skipped, backedUp, destRoot };
1136+
return {
1137+
total: skillNames.length * destinations.length,
1138+
skillsPerDestination: skillNames.length,
1139+
synced,
1140+
skipped,
1141+
backedUp,
1142+
destinations: destinationResults,
1143+
};
10801144
}
10811145

10821146
function applyGlobalSync(targetName, agentDir, timestamp, options) {
@@ -1116,7 +1180,9 @@ async function commandGlobalSync(options) {
11161180
const result = applyGlobalSync(target, agentDir, timestamp, options);
11171181
if (!options.dryRun) {
11181182
log(options, `📊 全局同步完成 [${target}]:总计 ${result.total},新增/覆盖 ${result.synced},跳过 ${result.skipped},备份 ${result.backedUp}`);
1119-
log(options, ` 目标路径: ${result.destRoot}`);
1183+
for (const item of result.destinations) {
1184+
log(options, ` - ${item.targetName}: ${item.destRoot}(每目标 ${item.total} 个 Skills)`);
1185+
}
11201186
}
11211187
}
11221188
} finally {
@@ -1152,6 +1218,7 @@ function commandGlobalStatus(options) {
11521218

11531219
for (const item of summary.targets) {
11541220
console.log(`\n[${item.targetName}:global]`);
1221+
console.log(` 家族: ${item.family}`);
11551222
console.log(` 状态: ${item.state}`);
11561223
console.log(` 路径: ${item.skillsRoot}`);
11571224
if (item.state === "installed") {

docs/PLAN.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ Ag-Kit 只做一件事:把仓库内统一的 `.agents/` 模板,优雅地投
1919
- 命令:`ag-kit global sync` / `ag-kit global status`
2020
- 默认行为:`ag-kit global sync` 未指定 `--target/--targets` 时,同步 `codex + gemini`
2121
- 目标路径:
22-
- `codex` -> `$HOME/.agents/skills/`
23-
- `gemini` -> `$HOME/.gemini/antigravity/skills/`
22+
- `codex` -> `$HOME/.codex/skills/`
23+
- `gemini` -> 同时写入 `$HOME/.gemini/skills/``$HOME/.gemini/antigravity/skills/`
2424
- 安全边界:全局只同步 Skills,不写入全局 Rules/Agents/Workflows。
2525

2626
## 覆盖与回滚(全局同步)
@@ -31,8 +31,9 @@ Ag-Kit 只做一件事:把仓库内统一的 `.agents/` 模板,优雅地投
3131
## 兼容策略
3232
- Gemini/Antigravity:输出 `.agent/`,保持与官方工作区机制一致。
3333
- Codex:受管目录为 `.agents/`,并使用 `manifest.json` 做完整性与漂移检测;识别并迁移遗留 `.codex/`
34+
- 全局同步遵循真实消费端目录,而不是仓库模板源目录;仓库内仍以 `.agents/` 作为唯一 Canonical。
3435

3536
## 成功标准
36-
- `ag-kit global sync` 一条命令即可完成全局 Skills 安装/更新(默认 codex+gemini)。
37+
- `ag-kit global sync` 一条命令即可完成全局 Skills 安装/更新(默认 codex + gemini,其中 gemini 同步到 gemini-cli 与 antigravity)。
3738
- 覆盖可回滚:每次覆盖同名 Skill 都有可用备份。
3839
- 跨平台 CI(Linux/macOS/Windows)验证主链路通过。

0 commit comments

Comments
 (0)