@agentrules/cli 0.0.13 → 0.0.14
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/index.js +187 -15
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "module";
|
|
3
|
-
import { AGENT_RULES_DIR, API_ENDPOINTS, COMMON_LICENSES, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, createDiffPreview, decodeBundledFile, descriptionSchema, fetchBundle, getPlatformFromDir, isLikelyText, isPlatformDir, isSupportedPlatform, licenseSchema, normalizeBundlePath, normalizePlatformInput, resolvePreset, slugSchema, tagsSchema, titleSchema, toUtf8String, validatePresetConfig, verifyBundledFileChecksum } from "@agentrules/core";
|
|
3
|
+
import { AGENT_RULES_DIR, API_ENDPOINTS, COMMON_LICENSES, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, createDiffPreview, decodeBundledFile, descriptionSchema, fetchBundle, getInstallPath, getPlatformFromDir, isLikelyText, isPlatformDir, isSupportedPlatform, licenseSchema, normalizeBundlePath, normalizePlatformInput, resolvePreset, slugSchema, tagsSchema, titleSchema, toUtf8String, validatePresetConfig, verifyBundledFileChecksum } from "@agentrules/core";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { basename, dirname, join, relative, resolve, sep } from "path";
|
|
6
6
|
import { exec } from "child_process";
|
|
@@ -712,6 +712,34 @@ async function unpublishPreset(baseUrl, token, slug, platform, version$1) {
|
|
|
712
712
|
}
|
|
713
713
|
}
|
|
714
714
|
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/lib/api/rule.ts
|
|
717
|
+
async function getRule(baseUrl, slug) {
|
|
718
|
+
const url = `${baseUrl}${API_ENDPOINTS.rule.get(slug)}`;
|
|
719
|
+
log.debug(`GET ${url}`);
|
|
720
|
+
try {
|
|
721
|
+
const response = await fetch(url);
|
|
722
|
+
log.debug(`Response status: ${response.status}`);
|
|
723
|
+
if (!response.ok) {
|
|
724
|
+
const errorData = await response.json();
|
|
725
|
+
return {
|
|
726
|
+
success: false,
|
|
727
|
+
error: errorData.error || `HTTP ${response.status}`
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const data = await response.json();
|
|
731
|
+
return {
|
|
732
|
+
success: true,
|
|
733
|
+
data
|
|
734
|
+
};
|
|
735
|
+
} catch (error$2) {
|
|
736
|
+
return {
|
|
737
|
+
success: false,
|
|
738
|
+
error: `Failed to connect to registry: ${getErrorMessage(error$2)}`
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
715
743
|
//#endregion
|
|
716
744
|
//#region src/lib/api/session.ts
|
|
717
745
|
/**
|
|
@@ -1322,7 +1350,7 @@ function parsePresetInput(input, explicitPlatform, explicitVersion) {
|
|
|
1322
1350
|
function resolveInstallTarget(platform, options) {
|
|
1323
1351
|
const { projectDir, globalDir } = PLATFORMS[platform];
|
|
1324
1352
|
if (options.directory) {
|
|
1325
|
-
const customRoot = resolve(expandHome(options.directory));
|
|
1353
|
+
const customRoot = resolve(expandHome$1(options.directory));
|
|
1326
1354
|
return {
|
|
1327
1355
|
root: customRoot,
|
|
1328
1356
|
mode: "custom",
|
|
@@ -1332,7 +1360,8 @@ function resolveInstallTarget(platform, options) {
|
|
|
1332
1360
|
};
|
|
1333
1361
|
}
|
|
1334
1362
|
if (options.global) {
|
|
1335
|
-
|
|
1363
|
+
if (!globalDir) throw new Error(`Platform "${platform}" does not support global installation`);
|
|
1364
|
+
const globalRoot = resolve(expandHome$1(globalDir));
|
|
1336
1365
|
return {
|
|
1337
1366
|
root: globalRoot,
|
|
1338
1367
|
mode: "global",
|
|
@@ -1362,7 +1391,7 @@ async function writeBundleFiles(bundle, target, behavior) {
|
|
|
1362
1391
|
const destResult = computeDestinationPath(file.path, target);
|
|
1363
1392
|
const destination = destResult.path;
|
|
1364
1393
|
if (!behavior.dryRun) await mkdir(dirname(destination), { recursive: true });
|
|
1365
|
-
const existing = await readExistingFile(destination);
|
|
1394
|
+
const existing = await readExistingFile$1(destination);
|
|
1366
1395
|
const relativePath = relativize(destination, target.root);
|
|
1367
1396
|
if (!existing) {
|
|
1368
1397
|
if (!behavior.dryRun) await writeFile(destination, data);
|
|
@@ -1434,7 +1463,7 @@ function computeDestinationPath(pathInput, target) {
|
|
|
1434
1463
|
ensureWithinRoot(destination, target.root);
|
|
1435
1464
|
return { path: destination };
|
|
1436
1465
|
}
|
|
1437
|
-
async function readExistingFile(pathname) {
|
|
1466
|
+
async function readExistingFile$1(pathname) {
|
|
1438
1467
|
try {
|
|
1439
1468
|
return await readFile(pathname);
|
|
1440
1469
|
} catch (error$2) {
|
|
@@ -1463,7 +1492,7 @@ function ensureWithinRoot(candidate, root) {
|
|
|
1463
1492
|
if (candidate === root) return;
|
|
1464
1493
|
if (!candidate.startsWith(normalizedRoot)) throw new Error(`Refusing to write outside of ${root}. Derived path: ${candidate}`);
|
|
1465
1494
|
}
|
|
1466
|
-
function expandHome(value) {
|
|
1495
|
+
function expandHome$1(value) {
|
|
1467
1496
|
if (value.startsWith("~")) {
|
|
1468
1497
|
const remainder = value.slice(1);
|
|
1469
1498
|
if (!remainder) return homedir();
|
|
@@ -1557,7 +1586,7 @@ async function loadPreset(presetDir) {
|
|
|
1557
1586
|
const configObj = configJson;
|
|
1558
1587
|
const identifier = typeof configObj?.name === "string" ? configObj.name : configPath;
|
|
1559
1588
|
const config = validatePresetConfig(configJson, identifier);
|
|
1560
|
-
const
|
|
1589
|
+
const name = config.name;
|
|
1561
1590
|
const dirName = basename(presetDir);
|
|
1562
1591
|
const isConfigInPlatformDir = isPlatformDir(dirName);
|
|
1563
1592
|
let filesDir;
|
|
@@ -1586,7 +1615,7 @@ async function loadPreset(presetDir) {
|
|
|
1586
1615
|
const files = await collectFiles(filesDir, rootExclude, ignorePatterns);
|
|
1587
1616
|
if (files.length === 0) throw new Error(`No files found in ${filesDir}. Presets must include at least one file.`);
|
|
1588
1617
|
return {
|
|
1589
|
-
|
|
1618
|
+
name,
|
|
1590
1619
|
config,
|
|
1591
1620
|
files,
|
|
1592
1621
|
installMessage,
|
|
@@ -2019,7 +2048,7 @@ async function validatePreset(options) {
|
|
|
2019
2048
|
warnings
|
|
2020
2049
|
};
|
|
2021
2050
|
}
|
|
2022
|
-
log.debug(`Preset
|
|
2051
|
+
log.debug(`Preset name: ${preset.name}`);
|
|
2023
2052
|
const presetDir = dirname(configPath);
|
|
2024
2053
|
const platform = preset.platform;
|
|
2025
2054
|
log.debug(`Checking platform: ${platform}`);
|
|
@@ -2107,7 +2136,7 @@ async function publish(options = {}) {
|
|
|
2107
2136
|
let presetInput;
|
|
2108
2137
|
try {
|
|
2109
2138
|
presetInput = await loadPreset(presetDir);
|
|
2110
|
-
log.debug(`Loaded preset "${presetInput.
|
|
2139
|
+
log.debug(`Loaded preset "${presetInput.name}" for platform ${presetInput.config.platform}`);
|
|
2111
2140
|
} catch (error$2) {
|
|
2112
2141
|
const message = getErrorMessage(error$2);
|
|
2113
2142
|
spinner$1.fail("Failed to load preset");
|
|
@@ -2152,7 +2181,7 @@ async function publish(options = {}) {
|
|
|
2152
2181
|
log.print("");
|
|
2153
2182
|
log.print(ui.header("Publish Preview"));
|
|
2154
2183
|
log.print(ui.keyValue("Preset", publishInput.title));
|
|
2155
|
-
log.print(ui.keyValue("
|
|
2184
|
+
log.print(ui.keyValue("Name", publishInput.name));
|
|
2156
2185
|
log.print(ui.keyValue("Platform", publishInput.platform));
|
|
2157
2186
|
log.print(ui.keyValue("Version", version$1 ? `${version$1}.x (auto-assigned minor)` : "1.x (auto-assigned)"));
|
|
2158
2187
|
log.print(ui.keyValue("Files", `${fileCount} file${fileCount === 1 ? "" : "s"}`));
|
|
@@ -2165,7 +2194,7 @@ async function publish(options = {}) {
|
|
|
2165
2194
|
return {
|
|
2166
2195
|
success: true,
|
|
2167
2196
|
preview: {
|
|
2168
|
-
slug: publishInput.
|
|
2197
|
+
slug: publishInput.name,
|
|
2169
2198
|
platform: publishInput.platform,
|
|
2170
2199
|
title: publishInput.title,
|
|
2171
2200
|
totalSize: inputSize,
|
|
@@ -2296,6 +2325,116 @@ async function discoverPresetDirs(inputDir) {
|
|
|
2296
2325
|
return presetDirs.sort();
|
|
2297
2326
|
}
|
|
2298
2327
|
|
|
2328
|
+
//#endregion
|
|
2329
|
+
//#region src/commands/rule/add.ts
|
|
2330
|
+
async function addRule(options) {
|
|
2331
|
+
const ctx = useAppContext();
|
|
2332
|
+
const dryRun = Boolean(options.dryRun);
|
|
2333
|
+
log.debug(`Fetching rule: ${options.slug}`);
|
|
2334
|
+
const result = await getRule(ctx.registry.url, options.slug);
|
|
2335
|
+
if (!result.success) throw new Error(result.error);
|
|
2336
|
+
const rule = result.data;
|
|
2337
|
+
const platform = rule.platform;
|
|
2338
|
+
const targetPath = resolveTargetPath(platform, rule.type, rule.slug, {
|
|
2339
|
+
global: options.global,
|
|
2340
|
+
directory: options.directory
|
|
2341
|
+
});
|
|
2342
|
+
log.debug(`Target path: ${targetPath}`);
|
|
2343
|
+
const existing = await readExistingFile(targetPath);
|
|
2344
|
+
if (existing !== null) {
|
|
2345
|
+
if (existing === rule.content) return {
|
|
2346
|
+
slug: rule.slug,
|
|
2347
|
+
platform: rule.platform,
|
|
2348
|
+
type: rule.type,
|
|
2349
|
+
title: rule.title,
|
|
2350
|
+
targetPath,
|
|
2351
|
+
status: "unchanged",
|
|
2352
|
+
dryRun
|
|
2353
|
+
};
|
|
2354
|
+
if (!options.force) return {
|
|
2355
|
+
slug: rule.slug,
|
|
2356
|
+
platform: rule.platform,
|
|
2357
|
+
type: rule.type,
|
|
2358
|
+
title: rule.title,
|
|
2359
|
+
targetPath,
|
|
2360
|
+
status: "conflict",
|
|
2361
|
+
dryRun
|
|
2362
|
+
};
|
|
2363
|
+
if (!dryRun) {
|
|
2364
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
2365
|
+
await writeFile(targetPath, rule.content, "utf-8");
|
|
2366
|
+
}
|
|
2367
|
+
return {
|
|
2368
|
+
slug: rule.slug,
|
|
2369
|
+
platform: rule.platform,
|
|
2370
|
+
type: rule.type,
|
|
2371
|
+
title: rule.title,
|
|
2372
|
+
targetPath,
|
|
2373
|
+
status: "overwritten",
|
|
2374
|
+
dryRun
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
if (!dryRun) {
|
|
2378
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
2379
|
+
await writeFile(targetPath, rule.content, "utf-8");
|
|
2380
|
+
}
|
|
2381
|
+
return {
|
|
2382
|
+
slug: rule.slug,
|
|
2383
|
+
platform: rule.platform,
|
|
2384
|
+
type: rule.type,
|
|
2385
|
+
title: rule.title,
|
|
2386
|
+
targetPath,
|
|
2387
|
+
status: "created",
|
|
2388
|
+
dryRun
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
function resolveTargetPath(platform, type, slug, options) {
|
|
2392
|
+
const location = options.global ? "global" : "project";
|
|
2393
|
+
const pathTemplate = getInstallPath(platform, type, slug, location);
|
|
2394
|
+
if (!pathTemplate) {
|
|
2395
|
+
const locationLabel = options.global ? "globally" : "to a project";
|
|
2396
|
+
throw new Error(`Rule type "${type}" cannot be installed ${locationLabel} for platform "${platform}"`);
|
|
2397
|
+
}
|
|
2398
|
+
if (options.directory) {
|
|
2399
|
+
const resolvedTemplate = pathTemplate.replace("{name}", slug);
|
|
2400
|
+
const filename = resolvedTemplate.split("/").pop() ?? `${slug}.md`;
|
|
2401
|
+
return resolve(expandHome(options.directory), filename);
|
|
2402
|
+
}
|
|
2403
|
+
const expanded = expandHome(pathTemplate);
|
|
2404
|
+
if (expanded.startsWith("/")) return expanded;
|
|
2405
|
+
return resolve(process.cwd(), expanded);
|
|
2406
|
+
}
|
|
2407
|
+
async function readExistingFile(pathname) {
|
|
2408
|
+
try {
|
|
2409
|
+
return await readFile(pathname, "utf-8");
|
|
2410
|
+
} catch (error$2) {
|
|
2411
|
+
if (error$2.code === "ENOENT") return null;
|
|
2412
|
+
throw error$2;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
function expandHome(value) {
|
|
2416
|
+
if (value.startsWith("~")) {
|
|
2417
|
+
const remainder = value.slice(1);
|
|
2418
|
+
if (!remainder) return homedir();
|
|
2419
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) return `${homedir()}${remainder}`;
|
|
2420
|
+
return `${homedir()}/${remainder}`;
|
|
2421
|
+
}
|
|
2422
|
+
return value;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Check if input looks like a rule reference (r/ prefix)
|
|
2426
|
+
*/
|
|
2427
|
+
function isRuleReference(input) {
|
|
2428
|
+
return input.toLowerCase().startsWith("r/");
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Extract slug from rule reference (removes r/ prefix)
|
|
2432
|
+
*/
|
|
2433
|
+
function extractRuleSlug(input) {
|
|
2434
|
+
if (isRuleReference(input)) return input.slice(2);
|
|
2435
|
+
return input;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2299
2438
|
//#endregion
|
|
2300
2439
|
//#region src/commands/unpublish.ts
|
|
2301
2440
|
/**
|
|
@@ -2680,14 +2819,47 @@ program.name("agentrules").description("The AI Agent Directory CLI").version(pac
|
|
|
2680
2819
|
log.debug(`Failed to init context: ${getErrorMessage(error$2)}`);
|
|
2681
2820
|
}
|
|
2682
2821
|
}).showHelpAfterError();
|
|
2683
|
-
program.command("add <
|
|
2684
|
-
const platform = options.platform ? normalizePlatformInput(options.platform) : void 0;
|
|
2822
|
+
program.command("add <item>").description("Download and install a preset or rule from the registry (use r/<slug> for rules)").option("-p, --platform <platform>", "Target platform (opencode, codex, claude, cursor)").option("-V, --version <version>", "Install a specific version (presets only)").option("-r, --registry <alias>", "Use a specific registry alias").option("-g, --global", "Install to global directory").option("--dir <path>", "Install to a custom directory").option("-f, --force", "Overwrite existing files (backs up originals)").option("-y, --yes", "Alias for --force").option("--dry-run", "Preview changes without writing").option("--skip-conflicts", "Skip conflicting files (presets only)").option("--no-backup", "Don't backup files before overwriting (use with --force)").action(handle(async (item, options) => {
|
|
2685
2823
|
const dryRun = Boolean(options.dryRun);
|
|
2824
|
+
if (isRuleReference(item)) {
|
|
2825
|
+
const slug = extractRuleSlug(item);
|
|
2826
|
+
const spinner$2 = await log.spinner(`Fetching rule "${slug}"...`);
|
|
2827
|
+
try {
|
|
2828
|
+
const result$1 = await addRule({
|
|
2829
|
+
slug,
|
|
2830
|
+
global: Boolean(options.global),
|
|
2831
|
+
directory: options.dir,
|
|
2832
|
+
force: Boolean(options.force || options.yes),
|
|
2833
|
+
dryRun
|
|
2834
|
+
});
|
|
2835
|
+
spinner$2.stop();
|
|
2836
|
+
if (result$1.status === "conflict") {
|
|
2837
|
+
log.error(`File already exists: ${result$1.targetPath}. Use ${ui.command("--force")} to overwrite.`);
|
|
2838
|
+
process.exitCode = 1;
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
if (result$1.status === "unchanged") {
|
|
2842
|
+
log.info(`Already up to date: ${ui.path(result$1.targetPath)}`);
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
const verb$1 = dryRun ? "Would install" : "Installed";
|
|
2846
|
+
const action = result$1.status === "overwritten" ? "updated" : "created";
|
|
2847
|
+
log.print(ui.fileStatus(action, result$1.targetPath, { dryRun }));
|
|
2848
|
+
log.print("");
|
|
2849
|
+
log.success(`${verb$1} ${ui.bold(result$1.title)} ${ui.muted(`(${result$1.platform}/${result$1.type})`)}`);
|
|
2850
|
+
if (dryRun) log.print(ui.hint("\nDry run complete. No files were written."));
|
|
2851
|
+
} catch (err) {
|
|
2852
|
+
spinner$2.stop();
|
|
2853
|
+
throw err;
|
|
2854
|
+
}
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
const platform = options.platform ? normalizePlatformInput(options.platform) : void 0;
|
|
2686
2858
|
const spinner$1 = await log.spinner("Fetching preset...");
|
|
2687
2859
|
let result;
|
|
2688
2860
|
try {
|
|
2689
2861
|
result = await addPreset({
|
|
2690
|
-
preset,
|
|
2862
|
+
preset: item,
|
|
2691
2863
|
platform,
|
|
2692
2864
|
version: options.version,
|
|
2693
2865
|
global: Boolean(options.global),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentrules/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://agentrules.directory",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"clean": "rm -rf node_modules dist .turbo"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@agentrules/core": "0.0.
|
|
51
|
+
"@agentrules/core": "0.0.11",
|
|
52
52
|
"@clack/prompts": "^0.11.0",
|
|
53
53
|
"chalk": "^5.4.1",
|
|
54
54
|
"commander": "^12.1.0",
|