aiblueprint-cli 1.4.72 → 1.4.74
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/cli.js +415 -25
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -33628,6 +33628,8 @@ async function mergeCodexConfigFile(sourceConfigPath, codexDir) {
|
|
|
33628
33628
|
// src/lib/configs-store.ts
|
|
33629
33629
|
var import_fs_extra7 = __toESM(require_lib4(), 1);
|
|
33630
33630
|
import path11 from "path";
|
|
33631
|
+
var RETENTION_MANAGED_BACKUP_TRIGGERS = new Set(["setup", "sync", "load", "backup-load"]);
|
|
33632
|
+
var DEFAULT_BACKUP_RETENTION_DAYS = 30;
|
|
33631
33633
|
function getConfigStorePaths(rootDir) {
|
|
33632
33634
|
const baseDir = path11.join(rootDir, ".aiblueprint");
|
|
33633
33635
|
return {
|
|
@@ -33866,6 +33868,117 @@ async function listConfigBackups(options = {}) {
|
|
|
33866
33868
|
const paths = getConfigStorePaths(folders.rootDir);
|
|
33867
33869
|
return listSnapshots(paths.backupsDir, "backup");
|
|
33868
33870
|
}
|
|
33871
|
+
function normalizeBackupRetentionDays(value = DEFAULT_BACKUP_RETENTION_DAYS) {
|
|
33872
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
33873
|
+
throw new Error("Backup retention days must be a positive integer.");
|
|
33874
|
+
}
|
|
33875
|
+
return value;
|
|
33876
|
+
}
|
|
33877
|
+
function isRetentionManagedBackup(metadata, includeManual = false) {
|
|
33878
|
+
return RETENTION_MANAGED_BACKUP_TRIGGERS.has(metadata.trigger) || includeManual && metadata.trigger === "manual-backup";
|
|
33879
|
+
}
|
|
33880
|
+
function isPathInside(childPath, parentPath) {
|
|
33881
|
+
const relativePath = path11.relative(parentPath, childPath);
|
|
33882
|
+
return relativePath !== "" && !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
33883
|
+
}
|
|
33884
|
+
async function realBackupStorePath(backupsDir) {
|
|
33885
|
+
if (!await import_fs_extra7.default.pathExists(backupsDir))
|
|
33886
|
+
return null;
|
|
33887
|
+
const stats = await import_fs_extra7.default.lstat(backupsDir);
|
|
33888
|
+
if (stats.isSymbolicLink()) {
|
|
33889
|
+
throw new Error("Backup directory cannot be a symlink.");
|
|
33890
|
+
}
|
|
33891
|
+
if (!stats.isDirectory()) {
|
|
33892
|
+
throw new Error("Backup path is not a directory.");
|
|
33893
|
+
}
|
|
33894
|
+
return import_fs_extra7.default.realpath(backupsDir);
|
|
33895
|
+
}
|
|
33896
|
+
function cleanupDateForSnapshot(snapshot) {
|
|
33897
|
+
if (snapshot.metadata.type !== "backup")
|
|
33898
|
+
return null;
|
|
33899
|
+
const createdAt = new Date(snapshot.metadata.createdAt);
|
|
33900
|
+
if (Number.isNaN(createdAt.getTime()))
|
|
33901
|
+
return null;
|
|
33902
|
+
return createdAt;
|
|
33903
|
+
}
|
|
33904
|
+
async function removeBackupSnapshot(snapshot, backupsRealPath) {
|
|
33905
|
+
const snapshotRealPath = await import_fs_extra7.default.realpath(snapshot.path);
|
|
33906
|
+
if (!isPathInside(snapshotRealPath, backupsRealPath)) {
|
|
33907
|
+
throw new Error(`Refusing to delete backup outside store: ${snapshot.name}`);
|
|
33908
|
+
}
|
|
33909
|
+
await import_fs_extra7.default.remove(snapshot.path);
|
|
33910
|
+
}
|
|
33911
|
+
async function cleanConfigBackups(options = {}) {
|
|
33912
|
+
const days = normalizeBackupRetentionDays(options.days);
|
|
33913
|
+
const folders = resolveConfigStoreFolders(options);
|
|
33914
|
+
const paths = getConfigStorePaths(folders.rootDir);
|
|
33915
|
+
const backupsRealPath = await realBackupStorePath(paths.backupsDir);
|
|
33916
|
+
const now = options.now ?? new Date;
|
|
33917
|
+
const cutoffDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
|
33918
|
+
if (!backupsRealPath) {
|
|
33919
|
+
return {
|
|
33920
|
+
cutoff: cutoffDate.toISOString(),
|
|
33921
|
+
deleted: [],
|
|
33922
|
+
failed: [],
|
|
33923
|
+
kept: [],
|
|
33924
|
+
skipped: []
|
|
33925
|
+
};
|
|
33926
|
+
}
|
|
33927
|
+
const backups = await listSnapshots(paths.backupsDir, "backup");
|
|
33928
|
+
const deleted = [];
|
|
33929
|
+
const failed = [];
|
|
33930
|
+
const kept = [];
|
|
33931
|
+
const skipped = [];
|
|
33932
|
+
const deletionCandidates = [];
|
|
33933
|
+
for (const backup of backups) {
|
|
33934
|
+
if (!isRetentionManagedBackup(backup.metadata, options.includeManual)) {
|
|
33935
|
+
skipped.push({ snapshot: backup, reason: "not retention-managed" });
|
|
33936
|
+
continue;
|
|
33937
|
+
}
|
|
33938
|
+
const cleanupDate = cleanupDateForSnapshot(backup);
|
|
33939
|
+
if (!cleanupDate) {
|
|
33940
|
+
skipped.push({ snapshot: backup, reason: "missing valid backup metadata date" });
|
|
33941
|
+
continue;
|
|
33942
|
+
}
|
|
33943
|
+
if (cleanupDate < cutoffDate) {
|
|
33944
|
+
deletionCandidates.push(backup);
|
|
33945
|
+
} else {
|
|
33946
|
+
kept.push(backup);
|
|
33947
|
+
}
|
|
33948
|
+
}
|
|
33949
|
+
if (options.dryRun) {
|
|
33950
|
+
deleted.push(...deletionCandidates);
|
|
33951
|
+
} else {
|
|
33952
|
+
for (const backup of deletionCandidates) {
|
|
33953
|
+
try {
|
|
33954
|
+
await removeBackupSnapshot(backup, backupsRealPath);
|
|
33955
|
+
deleted.push(backup);
|
|
33956
|
+
} catch (error) {
|
|
33957
|
+
failed.push({
|
|
33958
|
+
snapshot: backup,
|
|
33959
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33960
|
+
});
|
|
33961
|
+
}
|
|
33962
|
+
}
|
|
33963
|
+
if (deleted.length > 0 || failed.length > 0) {
|
|
33964
|
+
await appendHistory(paths, {
|
|
33965
|
+
action: "backup-clean",
|
|
33966
|
+
days,
|
|
33967
|
+
cutoff: cutoffDate.toISOString(),
|
|
33968
|
+
deleted: deleted.map((backup) => backup.name),
|
|
33969
|
+
failed: failed.map((entry) => ({ name: entry.snapshot.name, error: entry.error })),
|
|
33970
|
+
skipped: skipped.map((entry) => ({ name: entry.snapshot.name, reason: entry.reason }))
|
|
33971
|
+
});
|
|
33972
|
+
}
|
|
33973
|
+
}
|
|
33974
|
+
return {
|
|
33975
|
+
cutoff: cutoffDate.toISOString(),
|
|
33976
|
+
deleted,
|
|
33977
|
+
failed,
|
|
33978
|
+
kept,
|
|
33979
|
+
skipped
|
|
33980
|
+
};
|
|
33981
|
+
}
|
|
33869
33982
|
|
|
33870
33983
|
// src/lib/codex-agents-renderer.ts
|
|
33871
33984
|
var import_fs_extra8 = __toESM(require_lib4(), 1);
|
|
@@ -34083,6 +34196,17 @@ async function resolveClaudeAssetPath(sourceDir, name) {
|
|
|
34083
34196
|
}
|
|
34084
34197
|
return null;
|
|
34085
34198
|
}
|
|
34199
|
+
async function resolveDirectoryCopyDestination(destination) {
|
|
34200
|
+
const stat = await import_fs_extra9.default.lstat(destination).catch(() => null);
|
|
34201
|
+
if (!stat?.isSymbolicLink())
|
|
34202
|
+
return destination;
|
|
34203
|
+
const targetStat = await import_fs_extra9.default.stat(destination).catch(() => null);
|
|
34204
|
+
if (targetStat?.isDirectory()) {
|
|
34205
|
+
return import_fs_extra9.default.realpath(destination);
|
|
34206
|
+
}
|
|
34207
|
+
await import_fs_extra9.default.remove(destination);
|
|
34208
|
+
return destination;
|
|
34209
|
+
}
|
|
34086
34210
|
async function setupCommand(params = {}) {
|
|
34087
34211
|
const {
|
|
34088
34212
|
folder,
|
|
@@ -34195,11 +34319,10 @@ async function setupCommand(params = {}) {
|
|
|
34195
34319
|
s.start("Setting up scripts");
|
|
34196
34320
|
const scriptsSource = await resolveClaudeAssetPath(sourceDir, "scripts");
|
|
34197
34321
|
if (scriptsSource) {
|
|
34198
|
-
|
|
34199
|
-
|
|
34200
|
-
|
|
34201
|
-
await
|
|
34202
|
-
await import_fs_extra9.default.ensureDir(path13.join(claudeDir, "scripts/statusline/data"));
|
|
34322
|
+
const scriptsDestination = path13.join(claudeDir, "scripts");
|
|
34323
|
+
await import_fs_extra9.default.copy(scriptsSource, await resolveDirectoryCopyDestination(scriptsDestination), { overwrite: true });
|
|
34324
|
+
await replacePathPlaceholdersInDir(scriptsDestination, claudeDir);
|
|
34325
|
+
await import_fs_extra9.default.ensureDir(path13.join(scriptsDestination, "statusline/data"));
|
|
34203
34326
|
s.stop("Scripts installed");
|
|
34204
34327
|
} else {
|
|
34205
34328
|
s.stop("Scripts not available in repository");
|
|
@@ -35738,6 +35861,7 @@ var IGNORED_ENTRY_NAMES2 = new Set([
|
|
|
35738
35861
|
".git",
|
|
35739
35862
|
"node_modules"
|
|
35740
35863
|
]);
|
|
35864
|
+
var RULE_EXTENSION_RENAME_REASON = "Rule file extension normalized from .mdc to .md";
|
|
35741
35865
|
function uniqueByPath(candidates) {
|
|
35742
35866
|
const seen = new Set;
|
|
35743
35867
|
const unique = [];
|
|
@@ -35971,6 +36095,60 @@ async function hashPath(targetPath) {
|
|
|
35971
36095
|
}
|
|
35972
36096
|
return hashString(`other:${stat.mode}:${stat.size}`);
|
|
35973
36097
|
}
|
|
36098
|
+
async function hashRulePath(targetPath) {
|
|
36099
|
+
const stat = await import_fs_extra13.default.lstat(targetPath);
|
|
36100
|
+
if (stat.isSymbolicLink()) {
|
|
36101
|
+
const linkTarget = await import_fs_extra13.default.readlink(targetPath);
|
|
36102
|
+
return hashString(`symlink:${linkTarget}`);
|
|
36103
|
+
}
|
|
36104
|
+
if (stat.isFile()) {
|
|
36105
|
+
const fileHash = crypto.createHash("sha256");
|
|
36106
|
+
fileHash.update("file:");
|
|
36107
|
+
fileHash.update(await import_fs_extra13.default.readFile(targetPath));
|
|
36108
|
+
return fileHash.digest("hex");
|
|
36109
|
+
}
|
|
36110
|
+
if (stat.isDirectory()) {
|
|
36111
|
+
const entries = (await import_fs_extra13.default.readdir(targetPath, { withFileTypes: true })).filter((entry) => !IGNORED_ENTRY_NAMES2.has(entry.name)).sort(compareRuleEntryNames);
|
|
36112
|
+
const canonicalEntries = [];
|
|
36113
|
+
const usedNames = new Set;
|
|
36114
|
+
const hashesByName = new Map;
|
|
36115
|
+
const dirHash = crypto.createHash("sha256");
|
|
36116
|
+
dirHash.update("dir:");
|
|
36117
|
+
for (const entry of entries) {
|
|
36118
|
+
const entryHash = await hashRulePath(path17.join(targetPath, entry.name));
|
|
36119
|
+
let entryName = entry.isDirectory() ? entry.name : normalizeRuleFileName(entry.name);
|
|
36120
|
+
const existingHash = hashesByName.get(entryName);
|
|
36121
|
+
if (existingHash) {
|
|
36122
|
+
if (existingHash === entryHash)
|
|
36123
|
+
continue;
|
|
36124
|
+
entryName = findAvailableRuleName(entryName, usedNames);
|
|
36125
|
+
}
|
|
36126
|
+
usedNames.add(entryName);
|
|
36127
|
+
hashesByName.set(entryName, entryHash);
|
|
36128
|
+
canonicalEntries.push({ name: entryName, hash: entryHash });
|
|
36129
|
+
}
|
|
36130
|
+
for (const entry of canonicalEntries.sort((a, b3) => a.name.localeCompare(b3.name))) {
|
|
36131
|
+
dirHash.update(entry.name);
|
|
36132
|
+
dirHash.update("\x00");
|
|
36133
|
+
dirHash.update(entry.hash);
|
|
36134
|
+
dirHash.update("\x00");
|
|
36135
|
+
}
|
|
36136
|
+
return dirHash.digest("hex");
|
|
36137
|
+
}
|
|
36138
|
+
return hashString(`other:${stat.mode}:${stat.size}`);
|
|
36139
|
+
}
|
|
36140
|
+
async function hashPathForCategory(category, targetPath) {
|
|
36141
|
+
return category === "rules" ? hashRulePath(targetPath) : hashPath(targetPath);
|
|
36142
|
+
}
|
|
36143
|
+
function findAvailableRuleName(originalName, usedNames) {
|
|
36144
|
+
let index = 1;
|
|
36145
|
+
while (true) {
|
|
36146
|
+
const candidate = nameWithSuffix(originalName, "agents-rules", index);
|
|
36147
|
+
if (!usedNames.has(candidate))
|
|
36148
|
+
return candidate;
|
|
36149
|
+
index++;
|
|
36150
|
+
}
|
|
36151
|
+
}
|
|
35974
36152
|
var TEXT_EXTENSIONS = new Set([
|
|
35975
36153
|
".cjs",
|
|
35976
36154
|
".js",
|
|
@@ -36049,15 +36227,40 @@ function nameWithSuffix(name, suffix, index) {
|
|
|
36049
36227
|
}
|
|
36050
36228
|
return `${name}--${numberedSuffix}`;
|
|
36051
36229
|
}
|
|
36052
|
-
|
|
36230
|
+
function normalizeRuleFileName(name) {
|
|
36231
|
+
return path17.extname(name).toLowerCase() === ".mdc" ? `${path17.basename(name, path17.extname(name))}.md` : name;
|
|
36232
|
+
}
|
|
36233
|
+
function ruleExtensionPriority(name) {
|
|
36234
|
+
const ext = path17.extname(name).toLowerCase();
|
|
36235
|
+
if (ext === ".md")
|
|
36236
|
+
return 0;
|
|
36237
|
+
if (ext === ".mdc")
|
|
36238
|
+
return 1;
|
|
36239
|
+
return 2;
|
|
36240
|
+
}
|
|
36241
|
+
function compareRuleEntryNames(a, b3) {
|
|
36242
|
+
const normalizedCompare = normalizeRuleFileName(a.name).localeCompare(normalizeRuleFileName(b3.name));
|
|
36243
|
+
if (normalizedCompare !== 0)
|
|
36244
|
+
return normalizedCompare;
|
|
36245
|
+
const priorityCompare = ruleExtensionPriority(a.name) - ruleExtensionPriority(b3.name);
|
|
36246
|
+
if (priorityCompare !== 0)
|
|
36247
|
+
return priorityCompare;
|
|
36248
|
+
return a.name.localeCompare(b3.name);
|
|
36249
|
+
}
|
|
36250
|
+
async function addExistingDestinationHashes(category, destinationDir, knownHashes, result) {
|
|
36053
36251
|
if (!await import_fs_extra13.default.pathExists(destinationDir))
|
|
36054
36252
|
return;
|
|
36253
|
+
const plannedRuleNames = new Map(result.renamed.filter((entry) => entry.category === "rules").map((entry) => [path17.resolve(entry.from), path17.basename(entry.to)]));
|
|
36254
|
+
const removedRulePaths = new Set(result.duplicates.filter((entry) => entry.category === "rules").map((entry) => path17.resolve(entry.from)));
|
|
36055
36255
|
const entries = await import_fs_extra13.default.readdir(destinationDir, { withFileTypes: true });
|
|
36056
36256
|
for (const entry of entries) {
|
|
36057
36257
|
if (IGNORED_ENTRY_NAMES2.has(entry.name))
|
|
36058
36258
|
continue;
|
|
36059
36259
|
const entryPath = path17.join(destinationDir, entry.name);
|
|
36060
|
-
|
|
36260
|
+
if (category === "rules" && removedRulePaths.has(path17.resolve(entryPath)))
|
|
36261
|
+
continue;
|
|
36262
|
+
const entryName = category === "rules" ? plannedRuleNames.get(path17.resolve(entryPath)) ?? normalizeRuleFileName(entry.name) : entry.name;
|
|
36263
|
+
knownHashes.set(await hashPathForCategory(category, entryPath), entryName);
|
|
36061
36264
|
}
|
|
36062
36265
|
}
|
|
36063
36266
|
async function importCategoryEntries(category, candidates, destinationDir, result, dryRun = false) {
|
|
@@ -36074,7 +36277,7 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
36074
36277
|
if (destinationRealPath && candidateRealPath && samePath(destinationRealPath, candidateRealPath)) {
|
|
36075
36278
|
continue;
|
|
36076
36279
|
}
|
|
36077
|
-
const entries = await collectCandidateEntries(candidate).catch(() => null);
|
|
36280
|
+
const entries = await collectCandidateEntries(candidate, category).catch(() => null);
|
|
36078
36281
|
if (!entries) {
|
|
36079
36282
|
result.skipped.push({
|
|
36080
36283
|
category,
|
|
@@ -36100,12 +36303,12 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
36100
36303
|
await import_fs_extra13.default.ensureDir(destinationDir);
|
|
36101
36304
|
}
|
|
36102
36305
|
const knownHashes = new Map;
|
|
36103
|
-
await addExistingDestinationHashes(destinationDir, knownHashes);
|
|
36306
|
+
await addExistingDestinationHashes(category, destinationDir, knownHashes, result);
|
|
36104
36307
|
const knownNames = new Set(knownHashes.values());
|
|
36105
36308
|
for (const { candidate, entries } of sourceEntries) {
|
|
36106
36309
|
for (const entry of entries) {
|
|
36107
36310
|
const sourcePath = entry.path;
|
|
36108
|
-
const sourceHash = await
|
|
36311
|
+
const sourceHash = await hashPathForCategory(category, sourcePath);
|
|
36109
36312
|
const existingName = knownHashes.get(sourceHash);
|
|
36110
36313
|
if (existingName) {
|
|
36111
36314
|
result.duplicates.push({
|
|
@@ -36116,10 +36319,10 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
36116
36319
|
});
|
|
36117
36320
|
continue;
|
|
36118
36321
|
}
|
|
36119
|
-
let targetName = entry.name;
|
|
36322
|
+
let targetName = category === "rules" ? normalizeRuleFileName(entry.name) : entry.name;
|
|
36120
36323
|
let targetPath = path17.join(destinationDir, targetName);
|
|
36121
36324
|
if (await pathExistsOrSymlink(targetPath) || knownNames.has(targetName)) {
|
|
36122
|
-
targetName = await findAvailableTargetName(destinationDir,
|
|
36325
|
+
targetName = await findAvailableTargetName(destinationDir, targetName, candidate.label, knownNames);
|
|
36123
36326
|
targetPath = path17.join(destinationDir, targetName);
|
|
36124
36327
|
result.renamed.push({
|
|
36125
36328
|
category,
|
|
@@ -36137,7 +36340,7 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
36137
36340
|
await normalizePortableContent(targetPath);
|
|
36138
36341
|
}
|
|
36139
36342
|
knownNames.add(targetName);
|
|
36140
|
-
knownHashes.set(dryRun ? sourceHash : await
|
|
36343
|
+
knownHashes.set(dryRun ? sourceHash : await hashPathForCategory(category, targetPath), targetName);
|
|
36141
36344
|
result.imported.push({
|
|
36142
36345
|
category,
|
|
36143
36346
|
name: entry.name,
|
|
@@ -36148,6 +36351,84 @@ async function importCategoryEntries(category, candidates, destinationDir, resul
|
|
|
36148
36351
|
}
|
|
36149
36352
|
return true;
|
|
36150
36353
|
}
|
|
36354
|
+
function recordRuleExtensionRename(result, from3, to) {
|
|
36355
|
+
result.renamed.push({
|
|
36356
|
+
category: "rules",
|
|
36357
|
+
name: path17.basename(from3),
|
|
36358
|
+
from: from3,
|
|
36359
|
+
to,
|
|
36360
|
+
reason: RULE_EXTENSION_RENAME_REASON
|
|
36361
|
+
});
|
|
36362
|
+
}
|
|
36363
|
+
async function migrateExistingRuleExtensions(rulesDir, result, dryRun = false) {
|
|
36364
|
+
const usedNamesByDir = new Map;
|
|
36365
|
+
async function usedNamesForDir(currentDir) {
|
|
36366
|
+
const existing = usedNamesByDir.get(currentDir);
|
|
36367
|
+
if (existing)
|
|
36368
|
+
return existing;
|
|
36369
|
+
const names = new Set(await import_fs_extra13.default.readdir(currentDir).catch(() => []));
|
|
36370
|
+
usedNamesByDir.set(currentDir, names);
|
|
36371
|
+
return names;
|
|
36372
|
+
}
|
|
36373
|
+
async function walk(currentDir) {
|
|
36374
|
+
const entries = sortSourceEntries(await import_fs_extra13.default.readdir(currentDir, { withFileTypes: true }).catch(() => []), "rules");
|
|
36375
|
+
for (const entry of entries) {
|
|
36376
|
+
if (IGNORED_ENTRY_NAMES2.has(entry.name))
|
|
36377
|
+
continue;
|
|
36378
|
+
const sourcePath = path17.join(currentDir, entry.name);
|
|
36379
|
+
if (entry.isDirectory()) {
|
|
36380
|
+
await walk(sourcePath);
|
|
36381
|
+
continue;
|
|
36382
|
+
}
|
|
36383
|
+
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
36384
|
+
continue;
|
|
36385
|
+
if (path17.extname(entry.name).toLowerCase() !== ".mdc")
|
|
36386
|
+
continue;
|
|
36387
|
+
const targetPath = path17.join(currentDir, normalizeRuleFileName(entry.name));
|
|
36388
|
+
if (samePath(sourcePath, targetPath))
|
|
36389
|
+
continue;
|
|
36390
|
+
if (await pathExistsOrSymlink(targetPath)) {
|
|
36391
|
+
const sourceHash = await hashPath(sourcePath);
|
|
36392
|
+
const targetHash = await hashPath(targetPath);
|
|
36393
|
+
if (sourceHash === targetHash) {
|
|
36394
|
+
const backupRoot = await ensureBackupPath(result, dryRun);
|
|
36395
|
+
const backupTarget = path17.join(backupRoot, safeRelativePath(result.rootDir, sourcePath));
|
|
36396
|
+
if (!dryRun) {
|
|
36397
|
+
await import_fs_extra13.default.ensureDir(path17.dirname(backupTarget));
|
|
36398
|
+
await import_fs_extra13.default.move(sourcePath, backupTarget, { overwrite: false });
|
|
36399
|
+
}
|
|
36400
|
+
result.duplicates.push({
|
|
36401
|
+
category: "rules",
|
|
36402
|
+
name: entry.name,
|
|
36403
|
+
from: sourcePath,
|
|
36404
|
+
keptAs: targetPath
|
|
36405
|
+
});
|
|
36406
|
+
const usedNames3 = await usedNamesForDir(currentDir);
|
|
36407
|
+
usedNames3.delete(entry.name);
|
|
36408
|
+
continue;
|
|
36409
|
+
}
|
|
36410
|
+
const usedNames2 = await usedNamesForDir(currentDir);
|
|
36411
|
+
const targetName = await findAvailableTargetName(currentDir, path17.basename(targetPath), "agents-rules", usedNames2);
|
|
36412
|
+
const renamedTargetPath = path17.join(currentDir, targetName);
|
|
36413
|
+
if (!dryRun) {
|
|
36414
|
+
await import_fs_extra13.default.move(sourcePath, renamedTargetPath, { overwrite: false });
|
|
36415
|
+
}
|
|
36416
|
+
usedNames2.delete(entry.name);
|
|
36417
|
+
usedNames2.add(targetName);
|
|
36418
|
+
recordRuleExtensionRename(result, sourcePath, renamedTargetPath);
|
|
36419
|
+
continue;
|
|
36420
|
+
}
|
|
36421
|
+
const usedNames = await usedNamesForDir(currentDir);
|
|
36422
|
+
if (!dryRun) {
|
|
36423
|
+
await import_fs_extra13.default.move(sourcePath, targetPath, { overwrite: false });
|
|
36424
|
+
}
|
|
36425
|
+
usedNames.delete(entry.name);
|
|
36426
|
+
usedNames.add(path17.basename(targetPath));
|
|
36427
|
+
recordRuleExtensionRename(result, sourcePath, targetPath);
|
|
36428
|
+
}
|
|
36429
|
+
}
|
|
36430
|
+
await walk(rulesDir);
|
|
36431
|
+
}
|
|
36151
36432
|
async function findAvailableTargetName(destinationDir, originalName, label, knownNames) {
|
|
36152
36433
|
const suffix = suffixFromLabel(label);
|
|
36153
36434
|
let index = 1;
|
|
@@ -36159,11 +36440,18 @@ async function findAvailableTargetName(destinationDir, originalName, label, know
|
|
|
36159
36440
|
index++;
|
|
36160
36441
|
}
|
|
36161
36442
|
}
|
|
36162
|
-
|
|
36443
|
+
function sortSourceEntries(entries, category) {
|
|
36444
|
+
return [...entries].sort((a, b3) => {
|
|
36445
|
+
if (category === "rules")
|
|
36446
|
+
return compareRuleEntryNames(a, b3);
|
|
36447
|
+
return a.name.localeCompare(b3.name);
|
|
36448
|
+
});
|
|
36449
|
+
}
|
|
36450
|
+
async function collectCandidateEntries(candidate, category) {
|
|
36163
36451
|
const stat = await import_fs_extra13.default.lstat(candidate.path);
|
|
36164
36452
|
if (stat.isDirectory()) {
|
|
36165
36453
|
const entries = await import_fs_extra13.default.readdir(candidate.path, { withFileTypes: true });
|
|
36166
|
-
return entries.map((entry) => ({
|
|
36454
|
+
return sortSourceEntries(entries, category).map((entry) => ({
|
|
36167
36455
|
name: entry.name,
|
|
36168
36456
|
path: path17.join(candidate.path, entry.name)
|
|
36169
36457
|
}));
|
|
@@ -36172,7 +36460,7 @@ async function collectCandidateEntries(candidate) {
|
|
|
36172
36460
|
const targetStat = await import_fs_extra13.default.stat(candidate.path).catch(() => null);
|
|
36173
36461
|
if (targetStat?.isDirectory()) {
|
|
36174
36462
|
const entries = await import_fs_extra13.default.readdir(candidate.path, { withFileTypes: true });
|
|
36175
|
-
return entries.map((entry) => ({
|
|
36463
|
+
return sortSourceEntries(entries, category).map((entry) => ({
|
|
36176
36464
|
name: entry.name,
|
|
36177
36465
|
path: path17.join(candidate.path, entry.name)
|
|
36178
36466
|
}));
|
|
@@ -36450,7 +36738,7 @@ async function collectRuleIndexEntries(rulesDir, rootDir, currentDir = rulesDir)
|
|
|
36450
36738
|
}
|
|
36451
36739
|
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
36452
36740
|
continue;
|
|
36453
|
-
if (
|
|
36741
|
+
if (path17.extname(entry.name).toLowerCase() !== ".md")
|
|
36454
36742
|
continue;
|
|
36455
36743
|
rules.push({
|
|
36456
36744
|
title: await readRuleTitle(entryPath),
|
|
@@ -36601,21 +36889,20 @@ ${renderRulesIndexBlock(rules)}
|
|
|
36601
36889
|
}
|
|
36602
36890
|
async function collectPlannedRuleIndexEntries(result, rootDir) {
|
|
36603
36891
|
const rules = [];
|
|
36892
|
+
const removedRulePaths = new Set([
|
|
36893
|
+
...result.renamed.filter((entry) => entry.category === "rules").map((entry) => path17.relative(rootDir, entry.from)),
|
|
36894
|
+
...result.duplicates.filter((entry) => entry.category === "rules").map((entry) => path17.relative(rootDir, entry.from))
|
|
36895
|
+
]);
|
|
36604
36896
|
const plannedRuleEntries = [
|
|
36605
36897
|
...result.imported.filter((entry) => entry.category === "rules"),
|
|
36606
36898
|
...result.renamed.filter((entry) => entry.category === "rules")
|
|
36607
36899
|
];
|
|
36608
36900
|
for (const entry of plannedRuleEntries) {
|
|
36609
|
-
|
|
36610
|
-
if (![".md", ".mdc"].includes(ext))
|
|
36611
|
-
continue;
|
|
36612
|
-
rules.push({
|
|
36613
|
-
title: await readRuleTitle(entry.from),
|
|
36614
|
-
relativePath: path17.relative(rootDir, entry.to)
|
|
36615
|
-
});
|
|
36901
|
+
rules.push(...await collectPlannedRuleEntryFiles(entry, rootDir));
|
|
36616
36902
|
}
|
|
36617
36903
|
if (await pathExistsOrSymlink(path17.join(rootDir, ".agents", "rules"))) {
|
|
36618
|
-
|
|
36904
|
+
const existingRules = await collectRuleIndexEntries(path17.join(rootDir, ".agents", "rules"), rootDir);
|
|
36905
|
+
rules.push(...existingRules.filter((rule) => !removedRulePaths.has(rule.relativePath)));
|
|
36619
36906
|
}
|
|
36620
36907
|
const byPath = new Map;
|
|
36621
36908
|
for (const rule of rules) {
|
|
@@ -36623,6 +36910,58 @@ async function collectPlannedRuleIndexEntries(result, rootDir) {
|
|
|
36623
36910
|
}
|
|
36624
36911
|
return [...byPath.values()].sort((a, b3) => a.relativePath.localeCompare(b3.relativePath));
|
|
36625
36912
|
}
|
|
36913
|
+
async function collectPlannedRuleEntryFiles(entry, rootDir) {
|
|
36914
|
+
const sourceStat = await import_fs_extra13.default.lstat(entry.from).catch(() => null);
|
|
36915
|
+
if (!sourceStat?.isDirectory()) {
|
|
36916
|
+
const ext = path17.extname(entry.to).toLowerCase();
|
|
36917
|
+
if (ext !== ".md")
|
|
36918
|
+
return [];
|
|
36919
|
+
return [{
|
|
36920
|
+
title: await readRuleTitle(entry.from),
|
|
36921
|
+
relativePath: path17.relative(rootDir, entry.to)
|
|
36922
|
+
}];
|
|
36923
|
+
}
|
|
36924
|
+
return collectPlannedRuleDirectoryFiles(entry.from, entry.to, rootDir);
|
|
36925
|
+
}
|
|
36926
|
+
async function collectPlannedRuleDirectoryFiles(sourceDir, targetDir, rootDir) {
|
|
36927
|
+
const entries = sortSourceEntries(await import_fs_extra13.default.readdir(sourceDir, { withFileTypes: true }).catch(() => []), "rules");
|
|
36928
|
+
const rules = [];
|
|
36929
|
+
const usedNames = new Set;
|
|
36930
|
+
const plannedSourcesByName = new Map;
|
|
36931
|
+
for (const entry of entries) {
|
|
36932
|
+
if (IGNORED_ENTRY_NAMES2.has(entry.name))
|
|
36933
|
+
continue;
|
|
36934
|
+
const sourcePath = path17.join(sourceDir, entry.name);
|
|
36935
|
+
if (entry.isDirectory()) {
|
|
36936
|
+
const targetPath2 = path17.join(targetDir, entry.name);
|
|
36937
|
+
usedNames.add(entry.name);
|
|
36938
|
+
rules.push(...await collectPlannedRuleDirectoryFiles(sourcePath, targetPath2, rootDir));
|
|
36939
|
+
continue;
|
|
36940
|
+
}
|
|
36941
|
+
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
36942
|
+
continue;
|
|
36943
|
+
const ext = path17.extname(entry.name).toLowerCase();
|
|
36944
|
+
if (![".md", ".mdc"].includes(ext))
|
|
36945
|
+
continue;
|
|
36946
|
+
let targetName = normalizeRuleFileName(entry.name);
|
|
36947
|
+
const existingSourcePath = plannedSourcesByName.get(targetName);
|
|
36948
|
+
if (existingSourcePath) {
|
|
36949
|
+
const sourceHash = await hashRulePath(sourcePath);
|
|
36950
|
+
const existingHash = await hashRulePath(existingSourcePath);
|
|
36951
|
+
if (sourceHash === existingHash)
|
|
36952
|
+
continue;
|
|
36953
|
+
targetName = await findAvailableTargetName(targetDir, targetName, "agents-rules", usedNames);
|
|
36954
|
+
}
|
|
36955
|
+
usedNames.add(targetName);
|
|
36956
|
+
plannedSourcesByName.set(targetName, sourcePath);
|
|
36957
|
+
const targetPath = path17.join(targetDir, targetName);
|
|
36958
|
+
rules.push({
|
|
36959
|
+
title: await readRuleTitle(sourcePath),
|
|
36960
|
+
relativePath: path17.relative(rootDir, targetPath)
|
|
36961
|
+
});
|
|
36962
|
+
}
|
|
36963
|
+
return rules;
|
|
36964
|
+
}
|
|
36626
36965
|
async function unifyAgentsConfiguration(options = {}) {
|
|
36627
36966
|
const scope = options.scope ?? "global";
|
|
36628
36967
|
const dryRun = Boolean(options.dryRun);
|
|
@@ -36651,6 +36990,9 @@ async function unifyAgentsConfiguration(options = {}) {
|
|
|
36651
36990
|
};
|
|
36652
36991
|
await importInstructionFiles(instructionCandidates, destinationByCategory.instructions, result, dryRun);
|
|
36653
36992
|
const categories = scope === "repository" ? ["skills", "agents", "rules"] : ["skills", "agents"];
|
|
36993
|
+
if (scope === "repository" && await pathExistsOrSymlink(destinationByCategory.rules)) {
|
|
36994
|
+
await migrateExistingRuleExtensions(destinationByCategory.rules, result, dryRun);
|
|
36995
|
+
}
|
|
36654
36996
|
const activeCategories = new Set;
|
|
36655
36997
|
for (const category of categories) {
|
|
36656
36998
|
const isActive = await importCategoryEntries(category, candidates, destinationByCategory[category], result, dryRun);
|
|
@@ -36667,6 +37009,9 @@ async function unifyAgentsConfiguration(options = {}) {
|
|
|
36667
37009
|
await linkInstructionFile(candidate, destinationByCategory.instructions, result, dryRun);
|
|
36668
37010
|
}
|
|
36669
37011
|
if (scope === "repository") {
|
|
37012
|
+
if (!dryRun && await pathExistsOrSymlink(destinationByCategory.rules)) {
|
|
37013
|
+
await migrateExistingRuleExtensions(destinationByCategory.rules, result, dryRun);
|
|
37014
|
+
}
|
|
36670
37015
|
await writeRepositoryInstructionIndex(result, folders, dryRun);
|
|
36671
37016
|
}
|
|
36672
37017
|
return result;
|
|
@@ -38454,6 +38799,35 @@ async function configsBackupsCreateCommand(reason, options = {}) {
|
|
|
38454
38799
|
process.exit(1);
|
|
38455
38800
|
}
|
|
38456
38801
|
}
|
|
38802
|
+
async function configsBackupsCleanCommand(options = {}) {
|
|
38803
|
+
try {
|
|
38804
|
+
const days = options.days ?? DEFAULT_BACKUP_RETENTION_DAYS;
|
|
38805
|
+
console.log(source_default.gray(`Cleaning config backups older than ${days} day${days !== 1 ? "s" : ""}...`));
|
|
38806
|
+
const result = await cleanConfigBackups(options);
|
|
38807
|
+
if (options.dryRun) {
|
|
38808
|
+
console.log(source_default.yellow(`Dry run: ${result.deleted.length} backup${result.deleted.length !== 1 ? "s" : ""} would be deleted.`));
|
|
38809
|
+
} else {
|
|
38810
|
+
console.log(source_default.green(`Deleted ${result.deleted.length} old backup${result.deleted.length !== 1 ? "s" : ""}.`));
|
|
38811
|
+
}
|
|
38812
|
+
console.log(source_default.gray(`Cutoff: ${formatDate2(result.cutoff)}`));
|
|
38813
|
+
for (const backup of result.deleted) {
|
|
38814
|
+
console.log(source_default.gray(` ${backup.name}`));
|
|
38815
|
+
}
|
|
38816
|
+
if (result.skipped.length > 0) {
|
|
38817
|
+
console.log(source_default.gray(`Skipped ${result.skipped.length} backup${result.skipped.length !== 1 ? "s" : ""}.`));
|
|
38818
|
+
}
|
|
38819
|
+
if (result.failed.length > 0) {
|
|
38820
|
+
console.error(source_default.red(`Failed to delete ${result.failed.length} backup${result.failed.length !== 1 ? "s" : ""}.`));
|
|
38821
|
+
for (const failure of result.failed) {
|
|
38822
|
+
console.error(source_default.red(` ${failure.snapshot.name}: ${failure.error}`));
|
|
38823
|
+
}
|
|
38824
|
+
process.exit(1);
|
|
38825
|
+
}
|
|
38826
|
+
} catch (error) {
|
|
38827
|
+
console.error(source_default.red(error instanceof Error ? error.message : String(error)));
|
|
38828
|
+
process.exit(1);
|
|
38829
|
+
}
|
|
38830
|
+
}
|
|
38457
38831
|
|
|
38458
38832
|
// src/commands/openclaw-pro.ts
|
|
38459
38833
|
import os20 from "os";
|
|
@@ -39068,6 +39442,13 @@ function readConfigOptions(command, options = {}) {
|
|
|
39068
39442
|
agentsFolder: findOption("agentsFolder")
|
|
39069
39443
|
};
|
|
39070
39444
|
}
|
|
39445
|
+
function parseRetentionDays(value) {
|
|
39446
|
+
try {
|
|
39447
|
+
return normalizeBackupRetentionDays(Number(value));
|
|
39448
|
+
} catch (error) {
|
|
39449
|
+
throw new InvalidArgumentError(error instanceof Error ? error.message : String(error));
|
|
39450
|
+
}
|
|
39451
|
+
}
|
|
39071
39452
|
var agentsCmd = program2.command("agents").description("AI coding configuration commands");
|
|
39072
39453
|
registerAgentsCommands(agentsCmd);
|
|
39073
39454
|
var aiCodingCmd = program2.command("ai-coding").description("Legacy alias for agents configuration commands");
|
|
@@ -39119,6 +39500,15 @@ addConfigFolderOptions(configsBackupsCmd.command("create [reason]").description(
|
|
|
39119
39500
|
...folderOptions
|
|
39120
39501
|
});
|
|
39121
39502
|
});
|
|
39503
|
+
addConfigFolderOptions(configsBackupsCmd.command("clean").description("Delete old config backups managed by retention").option("-d, --days <days>", "Delete backups older than this many days", parseRetentionDays, DEFAULT_BACKUP_RETENTION_DAYS).option("--dry-run", "Show backups that would be deleted without removing them").option("--include-manual", "Also delete manual backups created with configs backups create")).action((options, command) => {
|
|
39504
|
+
const folderOptions = readConfigOptions(command, options);
|
|
39505
|
+
configsBackupsCleanCommand({
|
|
39506
|
+
...folderOptions,
|
|
39507
|
+
days: options.days,
|
|
39508
|
+
dryRun: options.dryRun,
|
|
39509
|
+
includeManual: options.includeManual
|
|
39510
|
+
});
|
|
39511
|
+
});
|
|
39122
39512
|
var openclawCmd = program2.command("openclaw").description("OpenClaw configuration commands").option("-f, --folder <path>", "Specify custom OpenClaw folder path (default: ~/.openclaw)");
|
|
39123
39513
|
var openclawProCmd = openclawCmd.command("pro").description("Manage OpenClaw Pro features");
|
|
39124
39514
|
openclawProCmd.command("activate [token]").description("Activate OpenClaw Pro with your access token").action((token) => {
|