aiblueprint-cli 1.4.66 → 1.4.68
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/README.md +1 -1
- package/dist/cli.js +311 -292
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ npx aiblueprint-cli@latest agents symlink
|
|
|
77
77
|
# Centralize global skills and agents in ~/.agents
|
|
78
78
|
npx aiblueprint-cli@latest agents unify
|
|
79
79
|
|
|
80
|
-
# Unify project-local .claude/.
|
|
80
|
+
# Unify project-local .claude/.cursor config into .agents
|
|
81
81
|
npx aiblueprint-cli@latest agents unify projects
|
|
82
82
|
|
|
83
83
|
# Recover sessions from saved configs and backups
|
package/dist/cli.js
CHANGED
|
@@ -34892,10 +34892,132 @@ async function symlinkCommand(params = {}) {
|
|
|
34892
34892
|
}
|
|
34893
34893
|
|
|
34894
34894
|
// src/lib/agents-unifier.ts
|
|
34895
|
-
var
|
|
34895
|
+
var import_fs_extra13 = __toESM(require_lib4(), 1);
|
|
34896
34896
|
import crypto from "crypto";
|
|
34897
|
-
import
|
|
34897
|
+
import os14 from "os";
|
|
34898
|
+
import path17 from "path";
|
|
34899
|
+
|
|
34900
|
+
// src/lib/backup-utils.ts
|
|
34901
|
+
var import_fs_extra12 = __toESM(require_lib4(), 1);
|
|
34898
34902
|
import path16 from "path";
|
|
34903
|
+
import os13 from "os";
|
|
34904
|
+
var BACKUP_BASE_DIR = path16.join(os13.homedir(), ".config", "aiblueprint", "backup");
|
|
34905
|
+
function getBackupDir() {
|
|
34906
|
+
return process.env.AIBLUEPRINT_BACKUP_DIR || BACKUP_BASE_DIR;
|
|
34907
|
+
}
|
|
34908
|
+
function formatDate(date) {
|
|
34909
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
34910
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
|
|
34911
|
+
}
|
|
34912
|
+
function createBackupNameSuffix(value) {
|
|
34913
|
+
return value.trim().replace(/^[a-zA-Z]:/, (drive) => drive.replace(":", "")).replace(/[\\/]+/g, "--").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "root";
|
|
34914
|
+
}
|
|
34915
|
+
function createTimestampedBackupName(suffix, date = new Date) {
|
|
34916
|
+
const base = formatDate(date);
|
|
34917
|
+
if (!suffix)
|
|
34918
|
+
return base;
|
|
34919
|
+
return `${base}--${createBackupNameSuffix(suffix)}`;
|
|
34920
|
+
}
|
|
34921
|
+
async function listBackups() {
|
|
34922
|
+
const backupBaseDir = getBackupDir();
|
|
34923
|
+
const exists = await import_fs_extra12.default.pathExists(backupBaseDir);
|
|
34924
|
+
if (!exists) {
|
|
34925
|
+
return [];
|
|
34926
|
+
}
|
|
34927
|
+
const entries = await import_fs_extra12.default.readdir(backupBaseDir, { withFileTypes: true });
|
|
34928
|
+
const backups = [];
|
|
34929
|
+
for (const entry of entries) {
|
|
34930
|
+
if (!entry.isDirectory())
|
|
34931
|
+
continue;
|
|
34932
|
+
const match = entry.name.match(/^(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})(?:--.+)?$/);
|
|
34933
|
+
if (!match)
|
|
34934
|
+
continue;
|
|
34935
|
+
const [, year, month, day, hour, minute, second] = match;
|
|
34936
|
+
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
|
|
34937
|
+
backups.push({
|
|
34938
|
+
name: entry.name,
|
|
34939
|
+
path: path16.join(backupBaseDir, entry.name),
|
|
34940
|
+
date
|
|
34941
|
+
});
|
|
34942
|
+
}
|
|
34943
|
+
return backups.sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
34944
|
+
}
|
|
34945
|
+
var AGENTS_BACKUP_SUBDIR = ".agents";
|
|
34946
|
+
var CLAUDE_ITEMS = ["commands", "agents", "skills", "scripts", "settings.json"];
|
|
34947
|
+
var MANAGED_FOLDERS = [".claude", ".codex", ".agents"];
|
|
34948
|
+
async function copyForBackup(sourcePath, destPath) {
|
|
34949
|
+
await import_fs_extra12.default.copy(sourcePath, destPath, {
|
|
34950
|
+
overwrite: true,
|
|
34951
|
+
dereference: false
|
|
34952
|
+
});
|
|
34953
|
+
}
|
|
34954
|
+
async function hasMeaningfulContent(dir) {
|
|
34955
|
+
if (!await import_fs_extra12.default.pathExists(dir))
|
|
34956
|
+
return false;
|
|
34957
|
+
const files = await import_fs_extra12.default.readdir(dir);
|
|
34958
|
+
return files.some((f) => f !== ".DS_Store");
|
|
34959
|
+
}
|
|
34960
|
+
async function loadBackup(backupPath, claudeDir, codexDir, agentsDir) {
|
|
34961
|
+
const exists = await import_fs_extra12.default.pathExists(backupPath);
|
|
34962
|
+
if (!exists) {
|
|
34963
|
+
throw new Error(`Backup not found: ${backupPath}`);
|
|
34964
|
+
}
|
|
34965
|
+
const managedDestinations = {
|
|
34966
|
+
".claude": claudeDir,
|
|
34967
|
+
".codex": codexDir,
|
|
34968
|
+
".agents": agentsDir
|
|
34969
|
+
};
|
|
34970
|
+
let restoredManagedFolder = false;
|
|
34971
|
+
for (const folderName of MANAGED_FOLDERS) {
|
|
34972
|
+
const sourcePath = path16.join(backupPath, folderName);
|
|
34973
|
+
const destPath = managedDestinations[folderName];
|
|
34974
|
+
if (!destPath || !await import_fs_extra12.default.pathExists(sourcePath))
|
|
34975
|
+
continue;
|
|
34976
|
+
await import_fs_extra12.default.ensureDir(destPath);
|
|
34977
|
+
await copyForBackup(sourcePath, destPath);
|
|
34978
|
+
restoredManagedFolder = true;
|
|
34979
|
+
}
|
|
34980
|
+
if (!restoredManagedFolder) {
|
|
34981
|
+
await import_fs_extra12.default.ensureDir(claudeDir);
|
|
34982
|
+
for (const item of CLAUDE_ITEMS) {
|
|
34983
|
+
const sourcePath = path16.join(backupPath, item);
|
|
34984
|
+
const destPath = path16.join(claudeDir, item);
|
|
34985
|
+
if (await import_fs_extra12.default.pathExists(sourcePath)) {
|
|
34986
|
+
await copyForBackup(sourcePath, destPath);
|
|
34987
|
+
}
|
|
34988
|
+
}
|
|
34989
|
+
if (agentsDir) {
|
|
34990
|
+
const agentsBackupPath = path16.join(backupPath, AGENTS_BACKUP_SUBDIR);
|
|
34991
|
+
if (await import_fs_extra12.default.pathExists(agentsBackupPath)) {
|
|
34992
|
+
await import_fs_extra12.default.ensureDir(agentsDir);
|
|
34993
|
+
await copyForBackup(agentsBackupPath, agentsDir);
|
|
34994
|
+
}
|
|
34995
|
+
}
|
|
34996
|
+
}
|
|
34997
|
+
}
|
|
34998
|
+
async function createBackup(claudeDir, codexDir, agentsDir) {
|
|
34999
|
+
const claudeHasContent = await hasMeaningfulContent(claudeDir);
|
|
35000
|
+
const codexHasContent = codexDir ? await hasMeaningfulContent(codexDir) : false;
|
|
35001
|
+
const agentsHasContent = agentsDir ? await hasMeaningfulContent(agentsDir) : false;
|
|
35002
|
+
if (!claudeHasContent && !codexHasContent && !agentsHasContent) {
|
|
35003
|
+
return null;
|
|
35004
|
+
}
|
|
35005
|
+
const backupPath = path16.join(getBackupDir(), createTimestampedBackupName());
|
|
35006
|
+
await import_fs_extra12.default.ensureDir(backupPath);
|
|
35007
|
+
if (claudeHasContent) {
|
|
35008
|
+
await copyForBackup(claudeDir, path16.join(backupPath, ".claude"));
|
|
35009
|
+
}
|
|
35010
|
+
if (codexHasContent && codexDir) {
|
|
35011
|
+
await copyForBackup(codexDir, path16.join(backupPath, ".codex"));
|
|
35012
|
+
}
|
|
35013
|
+
if (agentsHasContent && agentsDir) {
|
|
35014
|
+
const destPath = path16.join(backupPath, AGENTS_BACKUP_SUBDIR);
|
|
35015
|
+
await copyForBackup(agentsDir, destPath);
|
|
35016
|
+
}
|
|
35017
|
+
return backupPath;
|
|
35018
|
+
}
|
|
35019
|
+
|
|
35020
|
+
// src/lib/agents-unifier.ts
|
|
34899
35021
|
var IGNORED_ENTRY_NAMES2 = new Set([
|
|
34900
35022
|
".DS_Store",
|
|
34901
35023
|
".git",
|
|
@@ -34905,7 +35027,7 @@ function uniqueByPath(candidates) {
|
|
|
34905
35027
|
const seen = new Set;
|
|
34906
35028
|
const unique = [];
|
|
34907
35029
|
for (const candidate of candidates) {
|
|
34908
|
-
const resolved =
|
|
35030
|
+
const resolved = path17.resolve(candidate.path);
|
|
34909
35031
|
if (seen.has(resolved))
|
|
34910
35032
|
continue;
|
|
34911
35033
|
seen.add(resolved);
|
|
@@ -34913,245 +35035,221 @@ function uniqueByPath(candidates) {
|
|
|
34913
35035
|
}
|
|
34914
35036
|
return unique;
|
|
34915
35037
|
}
|
|
34916
|
-
function getContainerCandidates(options) {
|
|
35038
|
+
function getContainerCandidates(options, includeCodex = true) {
|
|
34917
35039
|
const folders = resolveFolders(options);
|
|
34918
|
-
const cursorDir =
|
|
34919
|
-
const factoryDir =
|
|
34920
|
-
const opencodeDir =
|
|
35040
|
+
const cursorDir = path17.join(folders.rootDir, ".cursor");
|
|
35041
|
+
const factoryDir = path17.join(folders.rootDir, ".factory");
|
|
35042
|
+
const opencodeDir = path17.join(folders.rootDir, ".config", "opencode");
|
|
34921
35043
|
return uniqueByPath([
|
|
34922
35044
|
{
|
|
34923
35045
|
category: "skills",
|
|
34924
35046
|
label: "agents-skills",
|
|
34925
|
-
path:
|
|
35047
|
+
path: path17.join(folders.agentsDir, "skills"),
|
|
34926
35048
|
isDestination: true
|
|
34927
35049
|
},
|
|
34928
35050
|
{
|
|
34929
35051
|
category: "skills",
|
|
34930
35052
|
label: "claude-skills",
|
|
34931
|
-
path:
|
|
35053
|
+
path: path17.join(folders.claudeDir, "skills"),
|
|
34932
35054
|
linkWhenMissing: true
|
|
34933
35055
|
},
|
|
34934
|
-
{
|
|
35056
|
+
...includeCodex ? [{
|
|
34935
35057
|
category: "skills",
|
|
34936
35058
|
label: "codex-skills",
|
|
34937
|
-
path:
|
|
35059
|
+
path: path17.join(folders.codexDir, "skills"),
|
|
34938
35060
|
linkWhenMissing: true
|
|
34939
|
-
},
|
|
35061
|
+
}] : [],
|
|
34940
35062
|
{
|
|
34941
35063
|
category: "skills",
|
|
34942
35064
|
label: "cursor-skills",
|
|
34943
|
-
path:
|
|
35065
|
+
path: path17.join(cursorDir, "skills"),
|
|
34944
35066
|
linkWhenParentExists: true
|
|
34945
35067
|
},
|
|
34946
35068
|
{
|
|
34947
35069
|
category: "skills",
|
|
34948
35070
|
label: "cursor-skills-cursor",
|
|
34949
|
-
path:
|
|
35071
|
+
path: path17.join(cursorDir, "skills-cursor"),
|
|
34950
35072
|
linkWhenParentExists: true
|
|
34951
35073
|
},
|
|
34952
35074
|
{
|
|
34953
35075
|
category: "skills",
|
|
34954
35076
|
label: "factory-skills",
|
|
34955
|
-
path:
|
|
35077
|
+
path: path17.join(factoryDir, "skills"),
|
|
34956
35078
|
linkWhenParentExists: true
|
|
34957
35079
|
},
|
|
34958
35080
|
{
|
|
34959
35081
|
category: "skills",
|
|
34960
35082
|
label: "opencode-skill",
|
|
34961
|
-
path:
|
|
35083
|
+
path: path17.join(opencodeDir, "skill"),
|
|
34962
35084
|
linkWhenParentExists: true
|
|
34963
35085
|
},
|
|
34964
35086
|
{
|
|
34965
35087
|
category: "skills",
|
|
34966
35088
|
label: "opencode-skills",
|
|
34967
|
-
path:
|
|
35089
|
+
path: path17.join(opencodeDir, "skills"),
|
|
34968
35090
|
linkWhenParentExists: true
|
|
34969
35091
|
},
|
|
34970
35092
|
{
|
|
34971
35093
|
category: "agents",
|
|
34972
35094
|
label: "agents-agents",
|
|
34973
|
-
path:
|
|
35095
|
+
path: path17.join(folders.agentsDir, "agents"),
|
|
34974
35096
|
isDestination: true
|
|
34975
35097
|
},
|
|
34976
35098
|
{
|
|
34977
35099
|
category: "agents",
|
|
34978
35100
|
label: "claude-agents",
|
|
34979
|
-
path:
|
|
35101
|
+
path: path17.join(folders.claudeDir, "agents"),
|
|
34980
35102
|
linkWhenMissing: true
|
|
34981
35103
|
},
|
|
34982
35104
|
{
|
|
34983
35105
|
category: "agents",
|
|
34984
35106
|
label: "claude-agnets",
|
|
34985
|
-
path:
|
|
35107
|
+
path: path17.join(folders.claudeDir, "agnets")
|
|
34986
35108
|
},
|
|
34987
35109
|
{
|
|
34988
35110
|
category: "agents",
|
|
34989
35111
|
label: "cursor-agents",
|
|
34990
|
-
path:
|
|
35112
|
+
path: path17.join(cursorDir, "agents"),
|
|
34991
35113
|
linkWhenParentExists: true
|
|
34992
35114
|
},
|
|
34993
35115
|
{
|
|
34994
35116
|
category: "agents",
|
|
34995
35117
|
label: "factory-droids",
|
|
34996
|
-
path:
|
|
35118
|
+
path: path17.join(factoryDir, "droids"),
|
|
34997
35119
|
linkWhenParentExists: true
|
|
34998
35120
|
},
|
|
34999
35121
|
{
|
|
35000
35122
|
category: "agents",
|
|
35001
35123
|
label: "opencode-agent",
|
|
35002
|
-
path:
|
|
35124
|
+
path: path17.join(opencodeDir, "agent"),
|
|
35003
35125
|
linkWhenParentExists: true
|
|
35004
35126
|
},
|
|
35005
35127
|
{
|
|
35006
35128
|
category: "agents",
|
|
35007
35129
|
label: "opencode-agents",
|
|
35008
|
-
path:
|
|
35130
|
+
path: path17.join(opencodeDir, "agents"),
|
|
35009
35131
|
linkWhenParentExists: true
|
|
35010
35132
|
}
|
|
35011
35133
|
]);
|
|
35012
35134
|
}
|
|
35013
|
-
function getInstructionFileCandidates(options) {
|
|
35135
|
+
function getInstructionFileCandidates(options, includeCodex = true) {
|
|
35014
35136
|
const folders = resolveFolders(options);
|
|
35015
35137
|
return uniqueByPath([
|
|
35016
35138
|
{
|
|
35017
35139
|
label: "agents-instructions",
|
|
35018
|
-
path:
|
|
35140
|
+
path: path17.join(folders.agentsDir, "AGENTS.md"),
|
|
35019
35141
|
isDestination: true
|
|
35020
35142
|
},
|
|
35021
35143
|
{
|
|
35022
35144
|
label: "claude-instructions",
|
|
35023
|
-
path:
|
|
35145
|
+
path: path17.join(folders.claudeDir, "CLAUDE.md"),
|
|
35024
35146
|
linkWhenMissing: true
|
|
35025
35147
|
},
|
|
35026
|
-
{
|
|
35148
|
+
...includeCodex ? [{
|
|
35027
35149
|
label: "codex-instructions",
|
|
35028
|
-
path:
|
|
35150
|
+
path: path17.join(folders.codexDir, "AGENTS.md"),
|
|
35029
35151
|
linkWhenMissing: true
|
|
35030
|
-
}
|
|
35152
|
+
}] : []
|
|
35031
35153
|
]);
|
|
35032
35154
|
}
|
|
35033
35155
|
function getRepositoryContainerCandidates(options) {
|
|
35034
35156
|
const folders = resolveFolders(options);
|
|
35035
|
-
const cursorDir =
|
|
35157
|
+
const cursorDir = path17.join(folders.rootDir, ".cursor");
|
|
35036
35158
|
return uniqueByPath([
|
|
35037
|
-
...getContainerCandidates(options),
|
|
35159
|
+
...getContainerCandidates(options, false),
|
|
35038
35160
|
{
|
|
35039
35161
|
category: "rules",
|
|
35040
35162
|
label: "agents-rules",
|
|
35041
|
-
path:
|
|
35163
|
+
path: path17.join(folders.agentsDir, "rules"),
|
|
35042
35164
|
isDestination: true
|
|
35043
35165
|
},
|
|
35044
35166
|
{
|
|
35045
35167
|
category: "rules",
|
|
35046
35168
|
label: "claude-rules",
|
|
35047
|
-
path:
|
|
35169
|
+
path: path17.join(folders.claudeDir, "rules"),
|
|
35048
35170
|
linkWhenMissing: true
|
|
35049
35171
|
},
|
|
35050
|
-
{
|
|
35051
|
-
category: "rules",
|
|
35052
|
-
label: "codex-rules",
|
|
35053
|
-
path: path16.join(folders.codexDir, "rules"),
|
|
35054
|
-
linkWhenParentExists: true
|
|
35055
|
-
},
|
|
35056
35172
|
{
|
|
35057
35173
|
category: "rules",
|
|
35058
35174
|
label: "cursor-rules",
|
|
35059
|
-
path:
|
|
35175
|
+
path: path17.join(cursorDir, "rules"),
|
|
35060
35176
|
linkWhenParentExists: true
|
|
35061
35177
|
},
|
|
35062
35178
|
{
|
|
35063
35179
|
category: "rules",
|
|
35064
35180
|
label: "claude-memories",
|
|
35065
|
-
path:
|
|
35066
|
-
linkSource: false
|
|
35067
|
-
},
|
|
35068
|
-
{
|
|
35069
|
-
category: "rules",
|
|
35070
|
-
label: "codex-memories",
|
|
35071
|
-
path: path16.join(folders.codexDir, "memories"),
|
|
35181
|
+
path: path17.join(folders.claudeDir, "memories"),
|
|
35072
35182
|
linkSource: false
|
|
35073
35183
|
},
|
|
35074
35184
|
{
|
|
35075
35185
|
category: "rules",
|
|
35076
35186
|
label: "cursor-memories",
|
|
35077
|
-
path:
|
|
35187
|
+
path: path17.join(cursorDir, "memories"),
|
|
35078
35188
|
linkSource: false
|
|
35079
35189
|
},
|
|
35080
35190
|
{
|
|
35081
35191
|
category: "rules",
|
|
35082
35192
|
label: "claude-memory",
|
|
35083
|
-
path:
|
|
35084
|
-
linkSource: false
|
|
35085
|
-
},
|
|
35086
|
-
{
|
|
35087
|
-
category: "rules",
|
|
35088
|
-
label: "codex-memory",
|
|
35089
|
-
path: path16.join(folders.codexDir, "memory.md"),
|
|
35193
|
+
path: path17.join(folders.claudeDir, "memory.md"),
|
|
35090
35194
|
linkSource: false
|
|
35091
35195
|
},
|
|
35092
35196
|
{
|
|
35093
35197
|
category: "rules",
|
|
35094
35198
|
label: "cursor-memory",
|
|
35095
|
-
path:
|
|
35199
|
+
path: path17.join(cursorDir, "memory.md"),
|
|
35096
35200
|
linkSource: false
|
|
35097
35201
|
},
|
|
35098
35202
|
{
|
|
35099
35203
|
category: "rules",
|
|
35100
35204
|
label: "claude-memory-uppercase",
|
|
35101
|
-
path:
|
|
35102
|
-
linkSource: false
|
|
35103
|
-
},
|
|
35104
|
-
{
|
|
35105
|
-
category: "rules",
|
|
35106
|
-
label: "codex-memory-uppercase",
|
|
35107
|
-
path: path16.join(folders.codexDir, "MEMORY.md"),
|
|
35205
|
+
path: path17.join(folders.claudeDir, "MEMORY.md"),
|
|
35108
35206
|
linkSource: false
|
|
35109
35207
|
},
|
|
35110
35208
|
{
|
|
35111
35209
|
category: "rules",
|
|
35112
35210
|
label: "cursor-memory-uppercase",
|
|
35113
|
-
path:
|
|
35211
|
+
path: path17.join(cursorDir, "MEMORY.md"),
|
|
35114
35212
|
linkSource: false
|
|
35115
35213
|
}
|
|
35116
35214
|
]);
|
|
35117
35215
|
}
|
|
35118
35216
|
async function pathExistsOrSymlink(targetPath) {
|
|
35119
|
-
const stat = await
|
|
35217
|
+
const stat = await import_fs_extra13.default.lstat(targetPath).catch(() => null);
|
|
35120
35218
|
return Boolean(stat);
|
|
35121
35219
|
}
|
|
35122
35220
|
async function realPathIfPossible(targetPath) {
|
|
35123
35221
|
try {
|
|
35124
|
-
return await
|
|
35222
|
+
return await import_fs_extra13.default.realpath(targetPath);
|
|
35125
35223
|
} catch {
|
|
35126
35224
|
return null;
|
|
35127
35225
|
}
|
|
35128
35226
|
}
|
|
35129
35227
|
function samePath(a, b) {
|
|
35130
|
-
return
|
|
35228
|
+
return path17.resolve(a) === path17.resolve(b);
|
|
35131
35229
|
}
|
|
35132
35230
|
function hashString(value) {
|
|
35133
35231
|
return crypto.createHash("sha256").update(value).digest("hex");
|
|
35134
35232
|
}
|
|
35135
35233
|
async function hashPath(targetPath) {
|
|
35136
|
-
const stat = await
|
|
35234
|
+
const stat = await import_fs_extra13.default.lstat(targetPath);
|
|
35137
35235
|
if (stat.isSymbolicLink()) {
|
|
35138
|
-
const linkTarget = await
|
|
35236
|
+
const linkTarget = await import_fs_extra13.default.readlink(targetPath);
|
|
35139
35237
|
return hashString(`symlink:${linkTarget}`);
|
|
35140
35238
|
}
|
|
35141
35239
|
if (stat.isFile()) {
|
|
35142
35240
|
const fileHash = crypto.createHash("sha256");
|
|
35143
35241
|
fileHash.update("file:");
|
|
35144
|
-
fileHash.update(await
|
|
35242
|
+
fileHash.update(await import_fs_extra13.default.readFile(targetPath));
|
|
35145
35243
|
return fileHash.digest("hex");
|
|
35146
35244
|
}
|
|
35147
35245
|
if (stat.isDirectory()) {
|
|
35148
|
-
const entries = (await
|
|
35246
|
+
const entries = (await import_fs_extra13.default.readdir(targetPath, { withFileTypes: true })).filter((entry) => !IGNORED_ENTRY_NAMES2.has(entry.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
35149
35247
|
const dirHash = crypto.createHash("sha256");
|
|
35150
35248
|
dirHash.update("dir:");
|
|
35151
35249
|
for (const entry of entries) {
|
|
35152
35250
|
dirHash.update(entry.name);
|
|
35153
35251
|
dirHash.update("\x00");
|
|
35154
|
-
dirHash.update(await hashPath(
|
|
35252
|
+
dirHash.update(await hashPath(path17.join(targetPath, entry.name)));
|
|
35155
35253
|
dirHash.update("\x00");
|
|
35156
35254
|
}
|
|
35157
35255
|
return dirHash.digest("hex");
|
|
@@ -35197,39 +35295,39 @@ function normalizePortableText(content) {
|
|
|
35197
35295
|
return normalized;
|
|
35198
35296
|
}
|
|
35199
35297
|
function isLikelyTextFile(filePath) {
|
|
35200
|
-
const ext =
|
|
35298
|
+
const ext = path17.extname(filePath).toLowerCase();
|
|
35201
35299
|
if (TEXT_EXTENSIONS.has(ext))
|
|
35202
35300
|
return true;
|
|
35203
|
-
return
|
|
35301
|
+
return path17.basename(filePath) === "SKILL.md";
|
|
35204
35302
|
}
|
|
35205
35303
|
async function normalizePortableContent(targetPath) {
|
|
35206
|
-
const stat = await
|
|
35304
|
+
const stat = await import_fs_extra13.default.lstat(targetPath).catch(() => null);
|
|
35207
35305
|
if (!stat || stat.isSymbolicLink())
|
|
35208
35306
|
return;
|
|
35209
35307
|
if (stat.isDirectory()) {
|
|
35210
|
-
const entries = await
|
|
35308
|
+
const entries = await import_fs_extra13.default.readdir(targetPath);
|
|
35211
35309
|
for (const entry of entries) {
|
|
35212
35310
|
if (IGNORED_ENTRY_NAMES2.has(entry))
|
|
35213
35311
|
continue;
|
|
35214
|
-
await normalizePortableContent(
|
|
35312
|
+
await normalizePortableContent(path17.join(targetPath, entry));
|
|
35215
35313
|
}
|
|
35216
35314
|
return;
|
|
35217
35315
|
}
|
|
35218
35316
|
if (!stat.isFile() || !isLikelyTextFile(targetPath))
|
|
35219
35317
|
return;
|
|
35220
|
-
const content = await
|
|
35318
|
+
const content = await import_fs_extra13.default.readFile(targetPath, "utf-8").catch(() => null);
|
|
35221
35319
|
if (content === null || content.includes("\x00"))
|
|
35222
35320
|
return;
|
|
35223
35321
|
const normalized = normalizePortableText(content);
|
|
35224
35322
|
if (normalized !== content) {
|
|
35225
|
-
await
|
|
35323
|
+
await import_fs_extra13.default.writeFile(targetPath, normalized, "utf-8");
|
|
35226
35324
|
}
|
|
35227
35325
|
}
|
|
35228
35326
|
function suffixFromLabel(label) {
|
|
35229
35327
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "source";
|
|
35230
35328
|
}
|
|
35231
35329
|
function nameWithSuffix(name, suffix, index) {
|
|
35232
|
-
const parsed =
|
|
35330
|
+
const parsed = path17.parse(name);
|
|
35233
35331
|
const numberedSuffix = index === 1 ? suffix : `${suffix}-${index}`;
|
|
35234
35332
|
if (parsed.ext && parsed.name) {
|
|
35235
35333
|
return `${parsed.name}--${numberedSuffix}${parsed.ext}`;
|
|
@@ -35241,32 +35339,31 @@ async function findTargetName(destinationDir, originalName, label) {
|
|
|
35241
35339
|
let index = 1;
|
|
35242
35340
|
while (true) {
|
|
35243
35341
|
const candidate = nameWithSuffix(originalName, suffix, index);
|
|
35244
|
-
if (!await pathExistsOrSymlink(
|
|
35342
|
+
if (!await pathExistsOrSymlink(path17.join(destinationDir, candidate))) {
|
|
35245
35343
|
return candidate;
|
|
35246
35344
|
}
|
|
35247
35345
|
index++;
|
|
35248
35346
|
}
|
|
35249
35347
|
}
|
|
35250
35348
|
async function addExistingDestinationHashes(destinationDir, knownHashes) {
|
|
35251
|
-
if (!await
|
|
35349
|
+
if (!await import_fs_extra13.default.pathExists(destinationDir))
|
|
35252
35350
|
return;
|
|
35253
|
-
const entries = await
|
|
35351
|
+
const entries = await import_fs_extra13.default.readdir(destinationDir, { withFileTypes: true });
|
|
35254
35352
|
for (const entry of entries) {
|
|
35255
35353
|
if (IGNORED_ENTRY_NAMES2.has(entry.name))
|
|
35256
35354
|
continue;
|
|
35257
|
-
const entryPath =
|
|
35355
|
+
const entryPath = path17.join(destinationDir, entry.name);
|
|
35258
35356
|
knownHashes.set(await hashPath(entryPath), entry.name);
|
|
35259
35357
|
}
|
|
35260
35358
|
}
|
|
35261
35359
|
async function importCategoryEntries(category, candidates, destinationDir, result) {
|
|
35262
|
-
|
|
35263
|
-
const
|
|
35264
|
-
const
|
|
35265
|
-
await addExistingDestinationHashes(destinationDir, knownHashes);
|
|
35360
|
+
const sourceEntries = [];
|
|
35361
|
+
const destinationExists = await pathExistsOrSymlink(destinationDir);
|
|
35362
|
+
const destinationRealPath = destinationExists ? await realPathIfPossible(destinationDir) : null;
|
|
35266
35363
|
for (const candidate of candidates) {
|
|
35267
35364
|
if (candidate.category !== category || candidate.isDestination)
|
|
35268
35365
|
continue;
|
|
35269
|
-
const candidateStat = await
|
|
35366
|
+
const candidateStat = await import_fs_extra13.default.lstat(candidate.path).catch(() => null);
|
|
35270
35367
|
if (!candidateStat)
|
|
35271
35368
|
continue;
|
|
35272
35369
|
const candidateRealPath = await realPathIfPossible(candidate.path);
|
|
@@ -35282,9 +35379,24 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
35282
35379
|
});
|
|
35283
35380
|
continue;
|
|
35284
35381
|
}
|
|
35382
|
+
const collectableEntries = [];
|
|
35285
35383
|
for (const entry of entries) {
|
|
35286
|
-
if (!shouldCollectPath(category, entry.name, entry.path))
|
|
35384
|
+
if (!await shouldCollectPath(category, entry.name, entry.path))
|
|
35287
35385
|
continue;
|
|
35386
|
+
collectableEntries.push(entry);
|
|
35387
|
+
}
|
|
35388
|
+
if (collectableEntries.length > 0) {
|
|
35389
|
+
sourceEntries.push({ candidate, entries: collectableEntries });
|
|
35390
|
+
}
|
|
35391
|
+
}
|
|
35392
|
+
if (!destinationExists && sourceEntries.length === 0) {
|
|
35393
|
+
return false;
|
|
35394
|
+
}
|
|
35395
|
+
await import_fs_extra13.default.ensureDir(destinationDir);
|
|
35396
|
+
const knownHashes = new Map;
|
|
35397
|
+
await addExistingDestinationHashes(destinationDir, knownHashes);
|
|
35398
|
+
for (const { candidate, entries } of sourceEntries) {
|
|
35399
|
+
for (const entry of entries) {
|
|
35288
35400
|
const sourcePath = entry.path;
|
|
35289
35401
|
const sourceHash = await hashPath(sourcePath);
|
|
35290
35402
|
const existingName = knownHashes.get(sourceHash);
|
|
@@ -35293,15 +35405,15 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
35293
35405
|
category,
|
|
35294
35406
|
name: entry.name,
|
|
35295
35407
|
from: sourcePath,
|
|
35296
|
-
keptAs:
|
|
35408
|
+
keptAs: path17.join(destinationDir, existingName)
|
|
35297
35409
|
});
|
|
35298
35410
|
continue;
|
|
35299
35411
|
}
|
|
35300
35412
|
let targetName = entry.name;
|
|
35301
|
-
let targetPath =
|
|
35413
|
+
let targetPath = path17.join(destinationDir, targetName);
|
|
35302
35414
|
if (await pathExistsOrSymlink(targetPath)) {
|
|
35303
35415
|
targetName = await findTargetName(destinationDir, entry.name, candidate.label);
|
|
35304
|
-
targetPath =
|
|
35416
|
+
targetPath = path17.join(destinationDir, targetName);
|
|
35305
35417
|
result.renamed.push({
|
|
35306
35418
|
category,
|
|
35307
35419
|
name: entry.name,
|
|
@@ -35310,12 +35422,12 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
35310
35422
|
reason: "Same name with different content"
|
|
35311
35423
|
});
|
|
35312
35424
|
}
|
|
35313
|
-
await
|
|
35425
|
+
await import_fs_extra13.default.copy(sourcePath, targetPath, {
|
|
35314
35426
|
dereference: false,
|
|
35315
35427
|
overwrite: false
|
|
35316
35428
|
});
|
|
35317
35429
|
await normalizePortableContent(targetPath);
|
|
35318
|
-
knownHashes.set(
|
|
35430
|
+
knownHashes.set(await hashPath(targetPath), targetName);
|
|
35319
35431
|
result.imported.push({
|
|
35320
35432
|
category,
|
|
35321
35433
|
name: entry.name,
|
|
@@ -35324,28 +35436,29 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
35324
35436
|
});
|
|
35325
35437
|
}
|
|
35326
35438
|
}
|
|
35439
|
+
return true;
|
|
35327
35440
|
}
|
|
35328
35441
|
async function collectCandidateEntries(candidate) {
|
|
35329
|
-
const stat = await
|
|
35442
|
+
const stat = await import_fs_extra13.default.lstat(candidate.path);
|
|
35330
35443
|
if (stat.isDirectory()) {
|
|
35331
|
-
const entries = await
|
|
35444
|
+
const entries = await import_fs_extra13.default.readdir(candidate.path, { withFileTypes: true });
|
|
35332
35445
|
return entries.map((entry) => ({
|
|
35333
35446
|
name: entry.name,
|
|
35334
|
-
path:
|
|
35447
|
+
path: path17.join(candidate.path, entry.name)
|
|
35335
35448
|
}));
|
|
35336
35449
|
}
|
|
35337
35450
|
if (stat.isSymbolicLink()) {
|
|
35338
|
-
const targetStat = await
|
|
35451
|
+
const targetStat = await import_fs_extra13.default.stat(candidate.path).catch(() => null);
|
|
35339
35452
|
if (targetStat?.isDirectory()) {
|
|
35340
|
-
const entries = await
|
|
35453
|
+
const entries = await import_fs_extra13.default.readdir(candidate.path, { withFileTypes: true });
|
|
35341
35454
|
return entries.map((entry) => ({
|
|
35342
35455
|
name: entry.name,
|
|
35343
|
-
path:
|
|
35456
|
+
path: path17.join(candidate.path, entry.name)
|
|
35344
35457
|
}));
|
|
35345
35458
|
}
|
|
35346
35459
|
}
|
|
35347
35460
|
return [{
|
|
35348
|
-
name:
|
|
35461
|
+
name: path17.basename(candidate.path),
|
|
35349
35462
|
path: candidate.path
|
|
35350
35463
|
}];
|
|
35351
35464
|
}
|
|
@@ -35355,53 +35468,51 @@ async function shouldCollectPath(category, name, sourcePath) {
|
|
|
35355
35468
|
if (category === "skills" && name === ".cursor-managed-skills-manifest.json") {
|
|
35356
35469
|
return true;
|
|
35357
35470
|
}
|
|
35358
|
-
const stat = await
|
|
35471
|
+
const stat = await import_fs_extra13.default.lstat(sourcePath).catch(() => null);
|
|
35359
35472
|
if (!stat)
|
|
35360
35473
|
return false;
|
|
35361
35474
|
return stat.isFile() || stat.isDirectory() || stat.isSymbolicLink();
|
|
35362
35475
|
}
|
|
35363
|
-
function timestamp2(date = new Date) {
|
|
35364
|
-
return date.toISOString().replace(/\.\d{3}Z$/, "").replace(/[:T]/g, "-");
|
|
35365
|
-
}
|
|
35366
35476
|
function safeRelativePath(rootDir, targetPath) {
|
|
35367
|
-
const relativePath =
|
|
35368
|
-
if (!relativePath || relativePath.startsWith("..") ||
|
|
35369
|
-
return
|
|
35477
|
+
const relativePath = path17.relative(rootDir, targetPath);
|
|
35478
|
+
if (!relativePath || relativePath.startsWith("..") || path17.isAbsolute(relativePath)) {
|
|
35479
|
+
return path17.join("external", targetPath.replace(/[^a-zA-Z0-9._-]+/g, "-"));
|
|
35370
35480
|
}
|
|
35371
35481
|
return relativePath;
|
|
35372
35482
|
}
|
|
35373
35483
|
function createBackupPath(rootDir) {
|
|
35374
|
-
|
|
35484
|
+
const projectKey = createBackupNameSuffix(path17.resolve(rootDir));
|
|
35485
|
+
return path17.join(getBackupDir(), createTimestampedBackupName(`project-${projectKey}-agents-unify-sources`));
|
|
35375
35486
|
}
|
|
35376
35487
|
async function ensureBackupPath(result) {
|
|
35377
35488
|
if (!result.backupPath) {
|
|
35378
35489
|
result.backupPath = createBackupPath(result.rootDir);
|
|
35379
|
-
await
|
|
35490
|
+
await import_fs_extra13.default.ensureDir(result.backupPath);
|
|
35380
35491
|
}
|
|
35381
35492
|
return result.backupPath;
|
|
35382
35493
|
}
|
|
35383
35494
|
async function createDirectorySymlink(source, target) {
|
|
35384
|
-
await
|
|
35385
|
-
if (
|
|
35386
|
-
await
|
|
35495
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(target));
|
|
35496
|
+
if (os14.platform() === "win32") {
|
|
35497
|
+
await import_fs_extra13.default.symlink(source, target, "junction");
|
|
35387
35498
|
return;
|
|
35388
35499
|
}
|
|
35389
|
-
await
|
|
35500
|
+
await import_fs_extra13.default.symlink(source, target, "dir");
|
|
35390
35501
|
}
|
|
35391
35502
|
async function createFileSymlink(source, target) {
|
|
35392
|
-
await
|
|
35393
|
-
if (
|
|
35394
|
-
await
|
|
35503
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(target));
|
|
35504
|
+
if (os14.platform() === "win32") {
|
|
35505
|
+
await import_fs_extra13.default.symlink(source, target, "file");
|
|
35395
35506
|
return;
|
|
35396
35507
|
}
|
|
35397
|
-
await
|
|
35508
|
+
await import_fs_extra13.default.symlink(source, target);
|
|
35398
35509
|
}
|
|
35399
35510
|
async function shouldLinkMissingContainer(candidate) {
|
|
35400
35511
|
if (candidate.linkWhenMissing)
|
|
35401
35512
|
return true;
|
|
35402
35513
|
if (!candidate.linkWhenParentExists)
|
|
35403
35514
|
return false;
|
|
35404
|
-
return
|
|
35515
|
+
return import_fs_extra13.default.pathExists(path17.dirname(candidate.path));
|
|
35405
35516
|
}
|
|
35406
35517
|
async function linkContainer(candidate, destinationDir, result) {
|
|
35407
35518
|
if (candidate.linkSource === false) {
|
|
@@ -35411,7 +35522,7 @@ async function linkContainer(candidate, destinationDir, result) {
|
|
|
35411
35522
|
return;
|
|
35412
35523
|
}
|
|
35413
35524
|
const destinationRealPath = await realPathIfPossible(destinationDir);
|
|
35414
|
-
const stat = await
|
|
35525
|
+
const stat = await import_fs_extra13.default.lstat(candidate.path).catch(() => null);
|
|
35415
35526
|
if (!stat) {
|
|
35416
35527
|
if (!await shouldLinkMissingContainer(candidate))
|
|
35417
35528
|
return;
|
|
@@ -35433,7 +35544,7 @@ async function linkContainer(candidate, destinationDir, result) {
|
|
|
35433
35544
|
});
|
|
35434
35545
|
return;
|
|
35435
35546
|
}
|
|
35436
|
-
await
|
|
35547
|
+
await import_fs_extra13.default.remove(candidate.path);
|
|
35437
35548
|
await createDirectorySymlink(destinationDir, candidate.path);
|
|
35438
35549
|
result.linked.push({
|
|
35439
35550
|
category: candidate.category,
|
|
@@ -35443,9 +35554,9 @@ async function linkContainer(candidate, destinationDir, result) {
|
|
|
35443
35554
|
return;
|
|
35444
35555
|
}
|
|
35445
35556
|
const backupRoot = await ensureBackupPath(result);
|
|
35446
|
-
const backupTarget =
|
|
35447
|
-
await
|
|
35448
|
-
await
|
|
35557
|
+
const backupTarget = path17.join(backupRoot, safeRelativePath(result.rootDir, candidate.path));
|
|
35558
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(backupTarget));
|
|
35559
|
+
await import_fs_extra13.default.move(candidate.path, backupTarget, { overwrite: false });
|
|
35449
35560
|
await createDirectorySymlink(destinationDir, candidate.path);
|
|
35450
35561
|
result.linked.push({
|
|
35451
35562
|
category: candidate.category,
|
|
@@ -35455,29 +35566,37 @@ async function linkContainer(candidate, destinationDir, result) {
|
|
|
35455
35566
|
});
|
|
35456
35567
|
}
|
|
35457
35568
|
async function importInstructionFiles(candidates, destinationPath, result) {
|
|
35458
|
-
await
|
|
35459
|
-
const destinationRealPath = await realPathIfPossible(destinationPath);
|
|
35460
|
-
|
|
35569
|
+
const destinationExists = await pathExistsOrSymlink(destinationPath);
|
|
35570
|
+
const destinationRealPath = destinationExists ? await realPathIfPossible(destinationPath) : null;
|
|
35571
|
+
const sourceCandidates = [];
|
|
35461
35572
|
for (const candidate of candidates) {
|
|
35462
35573
|
if (candidate.isDestination)
|
|
35463
35574
|
continue;
|
|
35464
|
-
const sourceStat = await
|
|
35575
|
+
const sourceStat = await import_fs_extra13.default.lstat(candidate.path).catch(() => null);
|
|
35465
35576
|
if (!sourceStat)
|
|
35466
35577
|
continue;
|
|
35467
35578
|
const sourceRealPath = await realPathIfPossible(candidate.path);
|
|
35468
35579
|
if (destinationRealPath && sourceRealPath && samePath(destinationRealPath, sourceRealPath)) {
|
|
35469
35580
|
continue;
|
|
35470
35581
|
}
|
|
35582
|
+
sourceCandidates.push(candidate);
|
|
35583
|
+
}
|
|
35584
|
+
if (!destinationExists && sourceCandidates.length === 0) {
|
|
35585
|
+
return false;
|
|
35586
|
+
}
|
|
35587
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(destinationPath));
|
|
35588
|
+
let destinationHash = destinationExists ? await hashPath(destinationPath) : null;
|
|
35589
|
+
for (const candidate of sourceCandidates) {
|
|
35471
35590
|
const sourceHash = await hashPath(candidate.path);
|
|
35472
35591
|
if (!destinationHash) {
|
|
35473
|
-
await
|
|
35592
|
+
await import_fs_extra13.default.copy(candidate.path, destinationPath, {
|
|
35474
35593
|
dereference: false,
|
|
35475
35594
|
overwrite: false
|
|
35476
35595
|
});
|
|
35477
35596
|
destinationHash = sourceHash;
|
|
35478
35597
|
result.imported.push({
|
|
35479
35598
|
category: "instructions",
|
|
35480
|
-
name:
|
|
35599
|
+
name: path17.basename(candidate.path),
|
|
35481
35600
|
from: candidate.path,
|
|
35482
35601
|
to: destinationPath
|
|
35483
35602
|
});
|
|
@@ -35486,26 +35605,27 @@ async function importInstructionFiles(candidates, destinationPath, result) {
|
|
|
35486
35605
|
if (sourceHash === destinationHash) {
|
|
35487
35606
|
result.duplicates.push({
|
|
35488
35607
|
category: "instructions",
|
|
35489
|
-
name:
|
|
35608
|
+
name: path17.basename(candidate.path),
|
|
35490
35609
|
from: candidate.path,
|
|
35491
35610
|
keptAs: destinationPath
|
|
35492
35611
|
});
|
|
35493
35612
|
continue;
|
|
35494
35613
|
}
|
|
35495
|
-
const targetName = await findTargetName(
|
|
35496
|
-
const targetPath =
|
|
35497
|
-
await
|
|
35614
|
+
const targetName = await findTargetName(path17.dirname(destinationPath), path17.basename(destinationPath), candidate.label);
|
|
35615
|
+
const targetPath = path17.join(path17.dirname(destinationPath), targetName);
|
|
35616
|
+
await import_fs_extra13.default.copy(candidate.path, targetPath, {
|
|
35498
35617
|
dereference: false,
|
|
35499
35618
|
overwrite: false
|
|
35500
35619
|
});
|
|
35501
35620
|
result.renamed.push({
|
|
35502
35621
|
category: "instructions",
|
|
35503
|
-
name:
|
|
35622
|
+
name: path17.basename(candidate.path),
|
|
35504
35623
|
from: candidate.path,
|
|
35505
35624
|
to: targetPath,
|
|
35506
35625
|
reason: "Shared instruction file already exists with different content"
|
|
35507
35626
|
});
|
|
35508
35627
|
}
|
|
35628
|
+
return true;
|
|
35509
35629
|
}
|
|
35510
35630
|
async function shouldLinkMissingInstruction(candidate) {
|
|
35511
35631
|
if (candidate.linkWhenMissing)
|
|
@@ -35520,7 +35640,7 @@ async function linkInstructionFile(candidate, destinationPath, result) {
|
|
|
35520
35640
|
return;
|
|
35521
35641
|
}
|
|
35522
35642
|
const destinationRealPath = await realPathIfPossible(destinationPath);
|
|
35523
|
-
const stat = await
|
|
35643
|
+
const stat = await import_fs_extra13.default.lstat(candidate.path).catch(() => null);
|
|
35524
35644
|
if (!stat) {
|
|
35525
35645
|
if (!await shouldLinkMissingInstruction(candidate))
|
|
35526
35646
|
return;
|
|
@@ -35542,7 +35662,7 @@ async function linkInstructionFile(candidate, destinationPath, result) {
|
|
|
35542
35662
|
});
|
|
35543
35663
|
return;
|
|
35544
35664
|
}
|
|
35545
|
-
await
|
|
35665
|
+
await import_fs_extra13.default.remove(candidate.path);
|
|
35546
35666
|
await createFileSymlink(destinationPath, candidate.path);
|
|
35547
35667
|
result.linked.push({
|
|
35548
35668
|
category: "instructions",
|
|
@@ -35552,9 +35672,9 @@ async function linkInstructionFile(candidate, destinationPath, result) {
|
|
|
35552
35672
|
return;
|
|
35553
35673
|
}
|
|
35554
35674
|
const backupRoot = await ensureBackupPath(result);
|
|
35555
|
-
const backupTarget =
|
|
35556
|
-
await
|
|
35557
|
-
await
|
|
35675
|
+
const backupTarget = path17.join(backupRoot, safeRelativePath(result.rootDir, candidate.path));
|
|
35676
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(backupTarget));
|
|
35677
|
+
await import_fs_extra13.default.move(candidate.path, backupTarget, { overwrite: false });
|
|
35558
35678
|
await createFileSymlink(destinationPath, candidate.path);
|
|
35559
35679
|
result.linked.push({
|
|
35560
35680
|
category: "instructions",
|
|
@@ -35566,31 +35686,31 @@ async function linkInstructionFile(candidate, destinationPath, result) {
|
|
|
35566
35686
|
var RULES_INDEX_START = "<!-- AIBLUEPRINT:RULES:START -->";
|
|
35567
35687
|
var RULES_INDEX_END = "<!-- AIBLUEPRINT:RULES:END -->";
|
|
35568
35688
|
function titleFromRulePath(rulePath) {
|
|
35569
|
-
return
|
|
35689
|
+
return path17.basename(rulePath, path17.extname(rulePath)).replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
35570
35690
|
}
|
|
35571
35691
|
async function readRuleTitle(rulePath) {
|
|
35572
|
-
const content = await
|
|
35692
|
+
const content = await import_fs_extra13.default.readFile(rulePath, "utf-8").catch(() => "");
|
|
35573
35693
|
const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
35574
35694
|
return heading || titleFromRulePath(rulePath);
|
|
35575
35695
|
}
|
|
35576
35696
|
async function collectRuleIndexEntries(rulesDir, rootDir, currentDir = rulesDir) {
|
|
35577
|
-
const entries = await
|
|
35697
|
+
const entries = await import_fs_extra13.default.readdir(currentDir, { withFileTypes: true }).catch(() => []);
|
|
35578
35698
|
const rules = [];
|
|
35579
35699
|
for (const entry of entries) {
|
|
35580
35700
|
if (IGNORED_ENTRY_NAMES2.has(entry.name))
|
|
35581
35701
|
continue;
|
|
35582
|
-
const entryPath =
|
|
35702
|
+
const entryPath = path17.join(currentDir, entry.name);
|
|
35583
35703
|
if (entry.isDirectory()) {
|
|
35584
35704
|
rules.push(...await collectRuleIndexEntries(rulesDir, rootDir, entryPath));
|
|
35585
35705
|
continue;
|
|
35586
35706
|
}
|
|
35587
35707
|
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
35588
35708
|
continue;
|
|
35589
|
-
if (![".md", ".mdc"].includes(
|
|
35709
|
+
if (![".md", ".mdc"].includes(path17.extname(entry.name).toLowerCase()))
|
|
35590
35710
|
continue;
|
|
35591
35711
|
rules.push({
|
|
35592
35712
|
title: await readRuleTitle(entryPath),
|
|
35593
|
-
relativePath:
|
|
35713
|
+
relativePath: path17.relative(rootDir, entryPath)
|
|
35594
35714
|
});
|
|
35595
35715
|
}
|
|
35596
35716
|
return rules.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
@@ -35628,10 +35748,10 @@ function renderRulesIndexBlock(rules) {
|
|
|
35628
35748
|
`);
|
|
35629
35749
|
}
|
|
35630
35750
|
async function readExistingInstructions(rootDir) {
|
|
35631
|
-
const agentsPath =
|
|
35632
|
-
const claudePath =
|
|
35633
|
-
const agentsContent = await
|
|
35634
|
-
const claudeContent = await
|
|
35751
|
+
const agentsPath = path17.join(rootDir, "AGENTS.md");
|
|
35752
|
+
const claudePath = path17.join(rootDir, "CLAUDE.md");
|
|
35753
|
+
const agentsContent = await import_fs_extra13.default.readFile(agentsPath, "utf-8").catch(() => null);
|
|
35754
|
+
const claudeContent = await import_fs_extra13.default.readFile(claudePath, "utf-8").catch(() => null);
|
|
35635
35755
|
if (agentsContent !== null && claudeContent !== null && agentsContent.trim() !== claudeContent.trim()) {
|
|
35636
35756
|
return `${agentsContent.trimEnd()}
|
|
35637
35757
|
|
|
@@ -35647,26 +35767,26 @@ ${claudeContent.trimStart()}`;
|
|
|
35647
35767
|
`;
|
|
35648
35768
|
}
|
|
35649
35769
|
async function copyPathToBackup(result, targetPath) {
|
|
35650
|
-
const stat = await
|
|
35770
|
+
const stat = await import_fs_extra13.default.lstat(targetPath).catch(() => null);
|
|
35651
35771
|
if (!stat)
|
|
35652
35772
|
return null;
|
|
35653
35773
|
const backupRoot = await ensureBackupPath(result);
|
|
35654
|
-
const backupTarget =
|
|
35655
|
-
await
|
|
35656
|
-
await
|
|
35774
|
+
const backupTarget = path17.join(backupRoot, safeRelativePath(result.rootDir, targetPath));
|
|
35775
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(backupTarget));
|
|
35776
|
+
await import_fs_extra13.default.copy(targetPath, backupTarget, {
|
|
35657
35777
|
dereference: false,
|
|
35658
35778
|
overwrite: false
|
|
35659
35779
|
});
|
|
35660
35780
|
return backupTarget;
|
|
35661
35781
|
}
|
|
35662
35782
|
async function replaceWithFileSymlink(sourcePath, targetPath) {
|
|
35663
|
-
await
|
|
35664
|
-
const relativeSource =
|
|
35665
|
-
await
|
|
35783
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(targetPath));
|
|
35784
|
+
const relativeSource = path17.relative(path17.dirname(targetPath), sourcePath) || path17.basename(sourcePath);
|
|
35785
|
+
await import_fs_extra13.default.symlink(relativeSource, targetPath, "file");
|
|
35666
35786
|
}
|
|
35667
35787
|
async function ensureClaudeInstructionSymlink(result, agentsPath, claudePath) {
|
|
35668
35788
|
const agentsRealPath = await realPathIfPossible(agentsPath);
|
|
35669
|
-
const claudeStat = await
|
|
35789
|
+
const claudeStat = await import_fs_extra13.default.lstat(claudePath).catch(() => null);
|
|
35670
35790
|
if (claudeStat?.isSymbolicLink()) {
|
|
35671
35791
|
const claudeRealPath = await realPathIfPossible(claudePath);
|
|
35672
35792
|
if (agentsRealPath && claudeRealPath && samePath(agentsRealPath, claudeRealPath)) {
|
|
@@ -35675,29 +35795,34 @@ async function ensureClaudeInstructionSymlink(result, agentsPath, claudePath) {
|
|
|
35675
35795
|
}
|
|
35676
35796
|
if (claudeStat) {
|
|
35677
35797
|
await copyPathToBackup(result, claudePath);
|
|
35678
|
-
await
|
|
35798
|
+
await import_fs_extra13.default.remove(claudePath);
|
|
35679
35799
|
}
|
|
35680
35800
|
await replaceWithFileSymlink(agentsPath, claudePath);
|
|
35681
35801
|
}
|
|
35682
35802
|
async function writeRepositoryInstructionIndex(result, folders) {
|
|
35683
|
-
const rulesDir =
|
|
35684
|
-
await
|
|
35803
|
+
const rulesDir = path17.join(folders.agentsDir, "rules");
|
|
35804
|
+
if (!await pathExistsOrSymlink(rulesDir)) {
|
|
35805
|
+
return;
|
|
35806
|
+
}
|
|
35685
35807
|
const rules = await collectRuleIndexEntries(rulesDir, folders.rootDir);
|
|
35808
|
+
if (rules.length === 0) {
|
|
35809
|
+
return;
|
|
35810
|
+
}
|
|
35686
35811
|
const existing = stripGeneratedRulesIndex(await readExistingInstructions(folders.rootDir));
|
|
35687
35812
|
const content = `${existing}
|
|
35688
35813
|
|
|
35689
35814
|
${renderRulesIndexBlock(rules)}
|
|
35690
35815
|
`;
|
|
35691
|
-
const agentsPath =
|
|
35692
|
-
const claudePath =
|
|
35693
|
-
const agentsStat = await
|
|
35816
|
+
const agentsPath = path17.join(folders.rootDir, "AGENTS.md");
|
|
35817
|
+
const claudePath = path17.join(folders.rootDir, "CLAUDE.md");
|
|
35818
|
+
const agentsStat = await import_fs_extra13.default.lstat(agentsPath).catch(() => null);
|
|
35694
35819
|
if (agentsStat) {
|
|
35695
35820
|
await copyPathToBackup(result, agentsPath);
|
|
35696
35821
|
if (agentsStat.isSymbolicLink()) {
|
|
35697
|
-
await
|
|
35822
|
+
await import_fs_extra13.default.remove(agentsPath);
|
|
35698
35823
|
}
|
|
35699
35824
|
}
|
|
35700
|
-
await
|
|
35825
|
+
await import_fs_extra13.default.writeFile(agentsPath, content, "utf-8");
|
|
35701
35826
|
await ensureClaudeInstructionSymlink(result, agentsPath, claudePath);
|
|
35702
35827
|
result.instructionIndex = {
|
|
35703
35828
|
agentsPath,
|
|
@@ -35708,7 +35833,8 @@ ${renderRulesIndexBlock(rules)}
|
|
|
35708
35833
|
async function unifyAgentsConfiguration(options = {}) {
|
|
35709
35834
|
const scope = options.scope ?? "global";
|
|
35710
35835
|
const folders = resolveFolders(options);
|
|
35711
|
-
const
|
|
35836
|
+
const includeCodex = scope !== "repository";
|
|
35837
|
+
const instructionCandidates = getInstructionFileCandidates(options, includeCodex);
|
|
35712
35838
|
const candidates = scope === "repository" ? getRepositoryContainerCandidates(options) : getContainerCandidates(options);
|
|
35713
35839
|
const result = {
|
|
35714
35840
|
rootDir: folders.rootDir,
|
|
@@ -35724,18 +35850,23 @@ async function unifyAgentsConfiguration(options = {}) {
|
|
|
35724
35850
|
instructionIndex: null
|
|
35725
35851
|
};
|
|
35726
35852
|
const destinationByCategory = {
|
|
35727
|
-
skills:
|
|
35728
|
-
agents:
|
|
35729
|
-
instructions:
|
|
35730
|
-
rules:
|
|
35853
|
+
skills: path17.join(folders.agentsDir, "skills"),
|
|
35854
|
+
agents: path17.join(folders.agentsDir, "agents"),
|
|
35855
|
+
instructions: path17.join(folders.agentsDir, "AGENTS.md"),
|
|
35856
|
+
rules: path17.join(folders.agentsDir, "rules")
|
|
35731
35857
|
};
|
|
35732
|
-
await import_fs_extra12.default.ensureDir(folders.agentsDir);
|
|
35733
35858
|
await importInstructionFiles(instructionCandidates, destinationByCategory.instructions, result);
|
|
35734
35859
|
const categories = scope === "repository" ? ["skills", "agents", "rules"] : ["skills", "agents"];
|
|
35860
|
+
const activeCategories = new Set;
|
|
35735
35861
|
for (const category of categories) {
|
|
35736
|
-
await importCategoryEntries(category, candidates, destinationByCategory[category], result);
|
|
35862
|
+
const isActive = await importCategoryEntries(category, candidates, destinationByCategory[category], result);
|
|
35863
|
+
if (isActive) {
|
|
35864
|
+
activeCategories.add(category);
|
|
35865
|
+
}
|
|
35737
35866
|
}
|
|
35738
35867
|
for (const candidate of candidates) {
|
|
35868
|
+
if (!activeCategories.has(candidate.category))
|
|
35869
|
+
continue;
|
|
35739
35870
|
await linkContainer(candidate, destinationByCategory[candidate.category], result);
|
|
35740
35871
|
}
|
|
35741
35872
|
for (const candidate of instructionCandidates) {
|
|
@@ -35765,9 +35896,9 @@ async function agentsUnifyCommand(params = {}) {
|
|
|
35765
35896
|
AIBlueprint agents unify ${source_default.gray(`v${getVersion()}`)}
|
|
35766
35897
|
`));
|
|
35767
35898
|
console.log(source_default.gray(`Scope: ${params.scope ?? "global"}`));
|
|
35768
|
-
console.log(source_default.gray("Centralizing reusable agent configuration into .agents, then rendering Codex agents"));
|
|
35899
|
+
console.log(source_default.gray(params.scope === "repository" ? "Centralizing project agent configuration into .agents" : "Centralizing reusable agent configuration into .agents, then rendering Codex agents"));
|
|
35769
35900
|
const result = await unifyAgentsConfiguration(params);
|
|
35770
|
-
const codexResult = await renderCodexAgentsFromMarkdown(params);
|
|
35901
|
+
const codexResult = params.scope === "repository" ? null : await renderCodexAgentsFromMarkdown(params);
|
|
35771
35902
|
console.log(source_default.green(`
|
|
35772
35903
|
Unify complete`));
|
|
35773
35904
|
console.log(source_default.gray(` Shared folder: ${result.agentsDir}`));
|
|
@@ -35777,7 +35908,9 @@ Unify complete`));
|
|
|
35777
35908
|
if (result.scope === "repository") {
|
|
35778
35909
|
printCategorySummary(result, "rules");
|
|
35779
35910
|
}
|
|
35780
|
-
|
|
35911
|
+
if (codexResult) {
|
|
35912
|
+
console.log(source_default.gray(` codex agents: ${codexResult.rendered.length} rendered, ${codexResult.skipped.length} skipped`));
|
|
35913
|
+
}
|
|
35781
35914
|
if (result.instructionIndex) {
|
|
35782
35915
|
console.log(source_default.gray(` rules index: ${result.instructionIndex.indexedRules.length} rules indexed in ${result.instructionIndex.agentsPath}`));
|
|
35783
35916
|
console.log(source_default.gray(` Claude instructions: ${result.instructionIndex.claudePath}`));
|
|
@@ -35832,120 +35965,6 @@ var import_fs_extra14 = __toESM(require_lib4(), 1);
|
|
|
35832
35965
|
import crypto2 from "crypto";
|
|
35833
35966
|
import os15 from "os";
|
|
35834
35967
|
import path18 from "path";
|
|
35835
|
-
|
|
35836
|
-
// src/lib/backup-utils.ts
|
|
35837
|
-
var import_fs_extra13 = __toESM(require_lib4(), 1);
|
|
35838
|
-
import path17 from "path";
|
|
35839
|
-
import os14 from "os";
|
|
35840
|
-
var BACKUP_BASE_DIR = path17.join(os14.homedir(), ".config", "aiblueprint", "backup");
|
|
35841
|
-
function getBackupDir() {
|
|
35842
|
-
return process.env.AIBLUEPRINT_BACKUP_DIR || BACKUP_BASE_DIR;
|
|
35843
|
-
}
|
|
35844
|
-
function formatDate(date) {
|
|
35845
|
-
const pad = (n) => n.toString().padStart(2, "0");
|
|
35846
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
|
|
35847
|
-
}
|
|
35848
|
-
async function listBackups() {
|
|
35849
|
-
const backupBaseDir = getBackupDir();
|
|
35850
|
-
const exists = await import_fs_extra13.default.pathExists(backupBaseDir);
|
|
35851
|
-
if (!exists) {
|
|
35852
|
-
return [];
|
|
35853
|
-
}
|
|
35854
|
-
const entries = await import_fs_extra13.default.readdir(backupBaseDir, { withFileTypes: true });
|
|
35855
|
-
const backups = [];
|
|
35856
|
-
for (const entry of entries) {
|
|
35857
|
-
if (!entry.isDirectory())
|
|
35858
|
-
continue;
|
|
35859
|
-
const match = entry.name.match(/^(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})$/);
|
|
35860
|
-
if (!match)
|
|
35861
|
-
continue;
|
|
35862
|
-
const [, year, month, day, hour, minute, second] = match;
|
|
35863
|
-
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
|
|
35864
|
-
backups.push({
|
|
35865
|
-
name: entry.name,
|
|
35866
|
-
path: path17.join(backupBaseDir, entry.name),
|
|
35867
|
-
date
|
|
35868
|
-
});
|
|
35869
|
-
}
|
|
35870
|
-
return backups.sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
35871
|
-
}
|
|
35872
|
-
var AGENTS_BACKUP_SUBDIR = ".agents";
|
|
35873
|
-
var CLAUDE_ITEMS = ["commands", "agents", "skills", "scripts", "settings.json"];
|
|
35874
|
-
var MANAGED_FOLDERS = [".claude", ".codex", ".agents"];
|
|
35875
|
-
async function copyForBackup(sourcePath, destPath) {
|
|
35876
|
-
await import_fs_extra13.default.copy(sourcePath, destPath, {
|
|
35877
|
-
overwrite: true,
|
|
35878
|
-
dereference: false
|
|
35879
|
-
});
|
|
35880
|
-
}
|
|
35881
|
-
async function hasMeaningfulContent(dir) {
|
|
35882
|
-
if (!await import_fs_extra13.default.pathExists(dir))
|
|
35883
|
-
return false;
|
|
35884
|
-
const files = await import_fs_extra13.default.readdir(dir);
|
|
35885
|
-
return files.some((f) => f !== ".DS_Store");
|
|
35886
|
-
}
|
|
35887
|
-
async function loadBackup(backupPath, claudeDir, codexDir, agentsDir) {
|
|
35888
|
-
const exists = await import_fs_extra13.default.pathExists(backupPath);
|
|
35889
|
-
if (!exists) {
|
|
35890
|
-
throw new Error(`Backup not found: ${backupPath}`);
|
|
35891
|
-
}
|
|
35892
|
-
const managedDestinations = {
|
|
35893
|
-
".claude": claudeDir,
|
|
35894
|
-
".codex": codexDir,
|
|
35895
|
-
".agents": agentsDir
|
|
35896
|
-
};
|
|
35897
|
-
let restoredManagedFolder = false;
|
|
35898
|
-
for (const folderName of MANAGED_FOLDERS) {
|
|
35899
|
-
const sourcePath = path17.join(backupPath, folderName);
|
|
35900
|
-
const destPath = managedDestinations[folderName];
|
|
35901
|
-
if (!destPath || !await import_fs_extra13.default.pathExists(sourcePath))
|
|
35902
|
-
continue;
|
|
35903
|
-
await import_fs_extra13.default.ensureDir(destPath);
|
|
35904
|
-
await copyForBackup(sourcePath, destPath);
|
|
35905
|
-
restoredManagedFolder = true;
|
|
35906
|
-
}
|
|
35907
|
-
if (!restoredManagedFolder) {
|
|
35908
|
-
await import_fs_extra13.default.ensureDir(claudeDir);
|
|
35909
|
-
for (const item of CLAUDE_ITEMS) {
|
|
35910
|
-
const sourcePath = path17.join(backupPath, item);
|
|
35911
|
-
const destPath = path17.join(claudeDir, item);
|
|
35912
|
-
if (await import_fs_extra13.default.pathExists(sourcePath)) {
|
|
35913
|
-
await copyForBackup(sourcePath, destPath);
|
|
35914
|
-
}
|
|
35915
|
-
}
|
|
35916
|
-
if (agentsDir) {
|
|
35917
|
-
const agentsBackupPath = path17.join(backupPath, AGENTS_BACKUP_SUBDIR);
|
|
35918
|
-
if (await import_fs_extra13.default.pathExists(agentsBackupPath)) {
|
|
35919
|
-
await import_fs_extra13.default.ensureDir(agentsDir);
|
|
35920
|
-
await copyForBackup(agentsBackupPath, agentsDir);
|
|
35921
|
-
}
|
|
35922
|
-
}
|
|
35923
|
-
}
|
|
35924
|
-
}
|
|
35925
|
-
async function createBackup(claudeDir, codexDir, agentsDir) {
|
|
35926
|
-
const claudeHasContent = await hasMeaningfulContent(claudeDir);
|
|
35927
|
-
const codexHasContent = codexDir ? await hasMeaningfulContent(codexDir) : false;
|
|
35928
|
-
const agentsHasContent = agentsDir ? await hasMeaningfulContent(agentsDir) : false;
|
|
35929
|
-
if (!claudeHasContent && !codexHasContent && !agentsHasContent) {
|
|
35930
|
-
return null;
|
|
35931
|
-
}
|
|
35932
|
-
const timestamp3 = formatDate(new Date);
|
|
35933
|
-
const backupPath = path17.join(getBackupDir(), timestamp3);
|
|
35934
|
-
await import_fs_extra13.default.ensureDir(backupPath);
|
|
35935
|
-
if (claudeHasContent) {
|
|
35936
|
-
await copyForBackup(claudeDir, path17.join(backupPath, ".claude"));
|
|
35937
|
-
}
|
|
35938
|
-
if (codexHasContent && codexDir) {
|
|
35939
|
-
await copyForBackup(codexDir, path17.join(backupPath, ".codex"));
|
|
35940
|
-
}
|
|
35941
|
-
if (agentsHasContent && agentsDir) {
|
|
35942
|
-
const destPath = path17.join(backupPath, AGENTS_BACKUP_SUBDIR);
|
|
35943
|
-
await copyForBackup(agentsDir, destPath);
|
|
35944
|
-
}
|
|
35945
|
-
return backupPath;
|
|
35946
|
-
}
|
|
35947
|
-
|
|
35948
|
-
// src/lib/session-unifier.ts
|
|
35949
35968
|
var MANAGED_FOLDERS2 = [".claude", ".codex", ".agents"];
|
|
35950
35969
|
var SESSION_PATHS = {
|
|
35951
35970
|
".claude": ["projects", "sessions"],
|