@agentrules/cli 0.0.11 → 0.0.13
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 +24 -3
- package/dist/index.js +523 -49
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,7 +34,8 @@ agentrules add <preset> --platform <platform> [options]
|
|
|
34
34
|
| `-g, --global` | Install to global config directory |
|
|
35
35
|
| `--dir <path>` | Install to a custom directory |
|
|
36
36
|
| `-r, --registry <alias>` | Use a specific registry |
|
|
37
|
-
| `-f, --force` | Overwrite existing files |
|
|
37
|
+
| `-f, --force` | Overwrite existing files (backs up originals to `.bak`) |
|
|
38
|
+
| `--no-backup` | Don't backup files before overwriting (use with `--force`) |
|
|
38
39
|
| `--dry-run` | Preview changes without writing |
|
|
39
40
|
| `--skip-conflicts` | Skip files that already exist |
|
|
40
41
|
|
|
@@ -200,12 +201,32 @@ agentrules publish --dry-run
|
|
|
200
201
|
|
|
201
202
|
**Versioning:** Presets use `MAJOR.MINOR` versioning. You set the major version, and the registry auto-increments the minor version on each publish.
|
|
202
203
|
|
|
203
|
-
### `agentrules unpublish <
|
|
204
|
+
### `agentrules unpublish <preset>`
|
|
204
205
|
|
|
205
206
|
Remove a specific version of a preset from the registry. Requires authentication.
|
|
206
207
|
|
|
207
208
|
```bash
|
|
208
|
-
agentrules unpublish
|
|
209
|
+
agentrules unpublish <preset> [options]
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Options:**
|
|
213
|
+
|
|
214
|
+
| Option | Description |
|
|
215
|
+
|--------|-------------|
|
|
216
|
+
| `-p, --platform <platform>` | Target platform (if not in preset string) |
|
|
217
|
+
| `-V, --version <version>` | Version to unpublish (if not in preset string) |
|
|
218
|
+
|
|
219
|
+
**Examples:**
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Full format: slug.platform@version
|
|
223
|
+
agentrules unpublish my-preset.opencode@1.0
|
|
224
|
+
|
|
225
|
+
# With flags
|
|
226
|
+
agentrules unpublish my-preset --platform opencode --version 1.0
|
|
227
|
+
|
|
228
|
+
# Mixed: version in string, platform as flag
|
|
229
|
+
agentrules unpublish my-preset@1.0 --platform opencode
|
|
209
230
|
```
|
|
210
231
|
|
|
211
232
|
**Note:** Unpublished versions cannot be republished with the same version number.
|
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, 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, 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";
|
|
@@ -8,7 +8,7 @@ import { promisify } from "util";
|
|
|
8
8
|
import * as client from "openid-client";
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import { chmod, constants } from "fs";
|
|
11
|
-
import { access, constants as constants$1, mkdir, readFile, readdir, rm, stat, writeFile } from "fs/promises";
|
|
11
|
+
import { access, constants as constants$1, copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "fs/promises";
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import * as p from "@clack/prompts";
|
|
14
14
|
|
|
@@ -221,6 +221,14 @@ function fileStatus(status, filePath, options = {}) {
|
|
|
221
221
|
return `${config.style(config.symbol)} ${config.style(pad(label, 14))} ${filePath}`;
|
|
222
222
|
}
|
|
223
223
|
/**
|
|
224
|
+
* Format a backup status line
|
|
225
|
+
* e.g., "↪ backed up .opencode/AGENT_RULES.md → .opencode/AGENT_RULES.md.bak"
|
|
226
|
+
*/
|
|
227
|
+
function backupStatus(originalPath, backupPath, options = {}) {
|
|
228
|
+
const label = options.dryRun ? "would backup" : "backed up";
|
|
229
|
+
return `${theme.info("↪")} ${theme.info(pad(label, 14))} ${originalPath} ${symbols.arrow} ${backupPath}`;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
224
232
|
* Step indicator for multi-step operations
|
|
225
233
|
* e.g., "[1/3] Fetching registry..."
|
|
226
234
|
*/
|
|
@@ -307,6 +315,49 @@ function relativeTime(date) {
|
|
|
307
315
|
if (minutes > 0) return `${minutes}m ago`;
|
|
308
316
|
return "just now";
|
|
309
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Formats an array of files as a tree structure
|
|
320
|
+
*/
|
|
321
|
+
function fileTree(files) {
|
|
322
|
+
const root = {
|
|
323
|
+
name: "",
|
|
324
|
+
children: new Map()
|
|
325
|
+
};
|
|
326
|
+
for (const file of files) {
|
|
327
|
+
const parts = file.path.split("/");
|
|
328
|
+
let current = root;
|
|
329
|
+
for (let i = 0; i < parts.length; i++) {
|
|
330
|
+
const part = parts[i];
|
|
331
|
+
const isFile = i === parts.length - 1;
|
|
332
|
+
let child = current.children.get(part);
|
|
333
|
+
if (!child) {
|
|
334
|
+
child = {
|
|
335
|
+
name: part,
|
|
336
|
+
size: isFile ? file.size : void 0,
|
|
337
|
+
children: new Map()
|
|
338
|
+
};
|
|
339
|
+
current.children.set(part, child);
|
|
340
|
+
}
|
|
341
|
+
current = child;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const lines = [];
|
|
345
|
+
function renderNode(node, prefix, isLast) {
|
|
346
|
+
const connector = isLast ? "└── " : "├── ";
|
|
347
|
+
const sizeStr = node.size !== void 0 ? muted(` (${formatBytes$1(node.size)})`) : "";
|
|
348
|
+
lines.push(`${prefix}${connector}${node.name}${sizeStr}`);
|
|
349
|
+
const children = Array.from(node.children.values());
|
|
350
|
+
const newPrefix = prefix + (isLast ? " " : "│ ");
|
|
351
|
+
children.forEach((child, index) => {
|
|
352
|
+
renderNode(child, newPrefix, index === children.length - 1);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const topLevel = Array.from(root.children.values());
|
|
356
|
+
topLevel.forEach((child, index) => {
|
|
357
|
+
renderNode(child, "", index === topLevel.length - 1);
|
|
358
|
+
});
|
|
359
|
+
return lines.join("\n");
|
|
360
|
+
}
|
|
310
361
|
const ui = {
|
|
311
362
|
theme,
|
|
312
363
|
symbols,
|
|
@@ -335,6 +386,7 @@ const ui = {
|
|
|
335
386
|
warning,
|
|
336
387
|
info: info$1,
|
|
337
388
|
fileStatus,
|
|
389
|
+
backupStatus,
|
|
338
390
|
step,
|
|
339
391
|
brand,
|
|
340
392
|
banner,
|
|
@@ -345,7 +397,8 @@ const ui = {
|
|
|
345
397
|
stripAnsi,
|
|
346
398
|
truncate,
|
|
347
399
|
formatBytes: formatBytes$1,
|
|
348
|
-
relativeTime
|
|
400
|
+
relativeTime,
|
|
401
|
+
fileTree
|
|
349
402
|
};
|
|
350
403
|
|
|
351
404
|
//#endregion
|
|
@@ -1209,6 +1262,7 @@ async function addPreset(options) {
|
|
|
1209
1262
|
const writeStats = await writeBundleFiles(bundle, target, {
|
|
1210
1263
|
force: Boolean(options.force),
|
|
1211
1264
|
skipConflicts: Boolean(options.skipConflicts),
|
|
1265
|
+
noBackup: Boolean(options.noBackup),
|
|
1212
1266
|
dryRun
|
|
1213
1267
|
});
|
|
1214
1268
|
return {
|
|
@@ -1216,6 +1270,7 @@ async function addPreset(options) {
|
|
|
1216
1270
|
bundle,
|
|
1217
1271
|
files: writeStats.files,
|
|
1218
1272
|
conflicts: writeStats.conflicts,
|
|
1273
|
+
backups: writeStats.backups,
|
|
1219
1274
|
targetRoot: target.root,
|
|
1220
1275
|
targetLabel: target.label,
|
|
1221
1276
|
registryAlias,
|
|
@@ -1298,6 +1353,7 @@ function resolveInstallTarget(platform, options) {
|
|
|
1298
1353
|
async function writeBundleFiles(bundle, target, behavior) {
|
|
1299
1354
|
const files = [];
|
|
1300
1355
|
const conflicts = [];
|
|
1356
|
+
const backups = [];
|
|
1301
1357
|
if (!behavior.dryRun) await mkdir(target.root, { recursive: true });
|
|
1302
1358
|
for (const file of bundle.files) {
|
|
1303
1359
|
const decoded = decodeBundledFile(file);
|
|
@@ -1326,6 +1382,16 @@ async function writeBundleFiles(bundle, target, behavior) {
|
|
|
1326
1382
|
continue;
|
|
1327
1383
|
}
|
|
1328
1384
|
if (behavior.force) {
|
|
1385
|
+
if (!behavior.noBackup) {
|
|
1386
|
+
const backupPath = `${destination}.bak`;
|
|
1387
|
+
const relativeBackupPath = `${relativePath}.bak`;
|
|
1388
|
+
if (!behavior.dryRun) await copyFile(destination, backupPath);
|
|
1389
|
+
backups.push({
|
|
1390
|
+
originalPath: relativePath,
|
|
1391
|
+
backupPath: relativeBackupPath
|
|
1392
|
+
});
|
|
1393
|
+
log.debug(`Backed up: ${relativePath} → ${relativeBackupPath}`);
|
|
1394
|
+
}
|
|
1329
1395
|
if (!behavior.dryRun) await writeFile(destination, data);
|
|
1330
1396
|
files.push({
|
|
1331
1397
|
path: relativePath,
|
|
@@ -1346,7 +1412,8 @@ async function writeBundleFiles(bundle, target, behavior) {
|
|
|
1346
1412
|
}
|
|
1347
1413
|
return {
|
|
1348
1414
|
files,
|
|
1349
|
-
conflicts
|
|
1415
|
+
conflicts,
|
|
1416
|
+
backups
|
|
1350
1417
|
};
|
|
1351
1418
|
}
|
|
1352
1419
|
/**
|
|
@@ -1621,6 +1688,66 @@ async function detectPlatforms(directory) {
|
|
|
1621
1688
|
return detected;
|
|
1622
1689
|
}
|
|
1623
1690
|
/**
|
|
1691
|
+
* Resolve the target platform directory for initialization.
|
|
1692
|
+
*
|
|
1693
|
+
* Detection order (deterministic):
|
|
1694
|
+
* 1. If targetDir itself is a platform directory (e.g., ".claude"), use it directly
|
|
1695
|
+
* 2. Otherwise, detect platform directories inside targetDir
|
|
1696
|
+
*
|
|
1697
|
+
* @param targetDir - The target directory (cwd or user-provided path)
|
|
1698
|
+
* @param platformOverride - Optional platform to use instead of detecting/inferring
|
|
1699
|
+
*/
|
|
1700
|
+
async function resolvePlatformDirectory(targetDir, platformOverride) {
|
|
1701
|
+
const targetDirName = basename(targetDir);
|
|
1702
|
+
const targetPlatform = getPlatformFromDir(targetDirName);
|
|
1703
|
+
if (targetPlatform) {
|
|
1704
|
+
const platform$1 = platformOverride ? normalizePlatform(platformOverride) : targetPlatform;
|
|
1705
|
+
return {
|
|
1706
|
+
platformDir: targetDir,
|
|
1707
|
+
platform: platform$1,
|
|
1708
|
+
isTargetPlatformDir: true,
|
|
1709
|
+
detected: []
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
const detected = await detectPlatforms(targetDir);
|
|
1713
|
+
let platform;
|
|
1714
|
+
let platformDir;
|
|
1715
|
+
if (platformOverride) {
|
|
1716
|
+
platform = normalizePlatform(platformOverride);
|
|
1717
|
+
const detectedPath = detected.find((d) => d.id === platform)?.path;
|
|
1718
|
+
platformDir = detectedPath ? join(targetDir, detectedPath) : join(targetDir, PLATFORMS[platform].projectDir);
|
|
1719
|
+
} else if (detected.length > 0) {
|
|
1720
|
+
platform = detected[0].id;
|
|
1721
|
+
platformDir = join(targetDir, detected[0].path);
|
|
1722
|
+
} else {
|
|
1723
|
+
platform = "opencode";
|
|
1724
|
+
platformDir = join(targetDir, PLATFORMS.opencode.projectDir);
|
|
1725
|
+
}
|
|
1726
|
+
return {
|
|
1727
|
+
platformDir,
|
|
1728
|
+
platform,
|
|
1729
|
+
isTargetPlatformDir: false,
|
|
1730
|
+
detected
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Check if --platform flag is required for non-interactive mode.
|
|
1735
|
+
* Returns the reason if required, so CLI can show appropriate error.
|
|
1736
|
+
*/
|
|
1737
|
+
function requiresPlatformFlag(resolved) {
|
|
1738
|
+
if (resolved.isTargetPlatformDir) return { required: false };
|
|
1739
|
+
if (resolved.detected.length === 0) return {
|
|
1740
|
+
required: true,
|
|
1741
|
+
reason: "no_platforms"
|
|
1742
|
+
};
|
|
1743
|
+
if (resolved.detected.length > 1) return {
|
|
1744
|
+
required: true,
|
|
1745
|
+
reason: "multiple_platforms",
|
|
1746
|
+
platforms: resolved.detected.map((d) => d.id)
|
|
1747
|
+
};
|
|
1748
|
+
return { required: false };
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1624
1751
|
* Initialize a preset in a platform directory.
|
|
1625
1752
|
*
|
|
1626
1753
|
* Structure:
|
|
@@ -1646,6 +1773,7 @@ async function initPreset(options) {
|
|
|
1646
1773
|
title,
|
|
1647
1774
|
version: 1,
|
|
1648
1775
|
description,
|
|
1776
|
+
tags: options.tags ?? [],
|
|
1649
1777
|
license,
|
|
1650
1778
|
platform
|
|
1651
1779
|
};
|
|
@@ -1690,6 +1818,20 @@ function check(schema) {
|
|
|
1690
1818
|
//#region src/commands/preset/init-interactive.ts
|
|
1691
1819
|
const DEFAULT_PRESET_NAME = "my-preset";
|
|
1692
1820
|
/**
|
|
1821
|
+
* Parse comma-separated tags string into array
|
|
1822
|
+
*/
|
|
1823
|
+
function parseTags(input) {
|
|
1824
|
+
return input.split(",").map((tag) => tag.trim().toLowerCase()).filter((tag) => tag.length > 0);
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Validator for comma-separated tags input
|
|
1828
|
+
*/
|
|
1829
|
+
function checkTags(value) {
|
|
1830
|
+
const tags = parseTags(value);
|
|
1831
|
+
const result = tagsSchema.safeParse(tags);
|
|
1832
|
+
if (!result.success) return result.error.issues[0]?.message;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1693
1835
|
* Run interactive init flow with clack prompts.
|
|
1694
1836
|
*
|
|
1695
1837
|
* If platformDir is provided, init directly in that directory.
|
|
@@ -1704,30 +1846,36 @@ async function initInteractive(options) {
|
|
|
1704
1846
|
let selectedPlatform;
|
|
1705
1847
|
if (explicitPlatformDir) {
|
|
1706
1848
|
targetPlatformDir = explicitPlatformDir;
|
|
1707
|
-
const dirName = explicitPlatformDir
|
|
1849
|
+
const dirName = basename(explicitPlatformDir);
|
|
1708
1850
|
selectedPlatform = platformOption ?? getPlatformFromDir(dirName) ?? "opencode";
|
|
1709
1851
|
} else {
|
|
1710
|
-
const
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1852
|
+
const resolved = await resolvePlatformDirectory(baseDir, platformOption);
|
|
1853
|
+
if (resolved.isTargetPlatformDir) {
|
|
1854
|
+
targetPlatformDir = resolved.platformDir;
|
|
1855
|
+
selectedPlatform = resolved.platform;
|
|
1856
|
+
p.note(`Detected platform directory: ${resolved.platform}`, "Using current directory");
|
|
1857
|
+
} else {
|
|
1858
|
+
const detectedMap = new Map(resolved.detected.map((d) => [d.id, d]));
|
|
1859
|
+
if (resolved.detected.length > 0) p.note(resolved.detected.map((d) => `${d.id} → ${d.path}`).join("\n"), "Detected platform directories");
|
|
1860
|
+
const platformChoice = await p.select({
|
|
1861
|
+
message: "Platform",
|
|
1862
|
+
options: PLATFORM_IDS.map((id) => ({
|
|
1863
|
+
value: id,
|
|
1864
|
+
label: detectedMap.has(id) ? `${id} (detected)` : id,
|
|
1865
|
+
hint: detectedMap.get(id)?.path
|
|
1866
|
+
})),
|
|
1867
|
+
initialValue: resolved.platform
|
|
1868
|
+
});
|
|
1869
|
+
if (p.isCancel(platformChoice)) {
|
|
1870
|
+
p.cancel("Cancelled");
|
|
1871
|
+
process.exit(0);
|
|
1872
|
+
}
|
|
1873
|
+
selectedPlatform = platformChoice;
|
|
1874
|
+
if (selectedPlatform !== resolved.platform) {
|
|
1875
|
+
const reResolved = await resolvePlatformDirectory(baseDir, selectedPlatform);
|
|
1876
|
+
targetPlatformDir = reResolved.platformDir;
|
|
1877
|
+
} else targetPlatformDir = resolved.platformDir;
|
|
1726
1878
|
}
|
|
1727
|
-
selectedPlatform = platformChoice;
|
|
1728
|
-
const detectedInfo = detectedMap.get(selectedPlatform);
|
|
1729
|
-
if (detectedInfo) targetPlatformDir = join(baseDir, detectedInfo.path);
|
|
1730
|
-
else targetPlatformDir = join(baseDir, PLATFORMS[selectedPlatform].projectDir);
|
|
1731
1879
|
}
|
|
1732
1880
|
const configPath = join(targetPlatformDir, PRESET_CONFIG_FILENAME);
|
|
1733
1881
|
if (!force && await fileExists(configPath)) {
|
|
@@ -1766,6 +1914,11 @@ async function initInteractive(options) {
|
|
|
1766
1914
|
validate: check(descriptionSchema)
|
|
1767
1915
|
});
|
|
1768
1916
|
},
|
|
1917
|
+
tags: () => p.text({
|
|
1918
|
+
message: "Tags (comma-separated, at least one)",
|
|
1919
|
+
placeholder: "e.g., typescript, testing, react",
|
|
1920
|
+
validate: checkTags
|
|
1921
|
+
}),
|
|
1769
1922
|
license: async () => {
|
|
1770
1923
|
const defaultLicense = licenseOption ?? "MIT";
|
|
1771
1924
|
const choice = await p.select({
|
|
@@ -1806,6 +1959,7 @@ async function initInteractive(options) {
|
|
|
1806
1959
|
name: result.name,
|
|
1807
1960
|
title: result.title,
|
|
1808
1961
|
description: result.description,
|
|
1962
|
+
tags: parseTags(result.tags),
|
|
1809
1963
|
platform: selectedPlatform,
|
|
1810
1964
|
license: result.license,
|
|
1811
1965
|
force
|
|
@@ -2004,6 +2158,9 @@ async function publish(options = {}) {
|
|
|
2004
2158
|
log.print(ui.keyValue("Files", `${fileCount} file${fileCount === 1 ? "" : "s"}`));
|
|
2005
2159
|
log.print(ui.keyValue("Size", formatBytes(inputSize)));
|
|
2006
2160
|
log.print("");
|
|
2161
|
+
log.print(ui.header("Files to publish", fileCount));
|
|
2162
|
+
log.print(ui.fileTree(publishInput.files));
|
|
2163
|
+
log.print("");
|
|
2007
2164
|
log.print(ui.hint("Run without --dry-run to publish."));
|
|
2008
2165
|
return {
|
|
2009
2166
|
success: true,
|
|
@@ -2041,6 +2198,9 @@ async function publish(options = {}) {
|
|
|
2041
2198
|
const { data } = result;
|
|
2042
2199
|
const action = data.isNewPreset ? "Published new preset" : "Published";
|
|
2043
2200
|
spinner$1.success(`${action} ${ui.code(data.slug)} ${ui.version(data.version)} (${data.platform})`);
|
|
2201
|
+
log.print("");
|
|
2202
|
+
log.print(ui.header("Published files", fileCount));
|
|
2203
|
+
log.print(ui.fileTree(publishInput.files));
|
|
2044
2204
|
const presetName$1 = `${data.slug}.${data.platform}`;
|
|
2045
2205
|
const presetRegistryUrl = `${ctx.registry.url}preset/${presetName$1}`;
|
|
2046
2206
|
log.info("");
|
|
@@ -2139,10 +2299,45 @@ async function discoverPresetDirs(inputDir) {
|
|
|
2139
2299
|
//#endregion
|
|
2140
2300
|
//#region src/commands/unpublish.ts
|
|
2141
2301
|
/**
|
|
2302
|
+
* Parses preset input to extract slug, platform, and version.
|
|
2303
|
+
* Supports formats:
|
|
2304
|
+
* - "my-preset.claude@1.0" (platform and version in string)
|
|
2305
|
+
* - "my-preset@1.0" (requires explicit platform)
|
|
2306
|
+
* - "my-preset.claude" (requires explicit version)
|
|
2307
|
+
*
|
|
2308
|
+
* Explicit --platform and --version flags take precedence.
|
|
2309
|
+
*/
|
|
2310
|
+
function parseUnpublishInput(input, explicitPlatform, explicitVersion) {
|
|
2311
|
+
let normalized = input.toLowerCase().trim();
|
|
2312
|
+
let parsedVersion;
|
|
2313
|
+
const atIndex = normalized.lastIndexOf("@");
|
|
2314
|
+
if (atIndex > 0) {
|
|
2315
|
+
parsedVersion = normalized.slice(atIndex + 1);
|
|
2316
|
+
normalized = normalized.slice(0, atIndex);
|
|
2317
|
+
}
|
|
2318
|
+
const version$1 = explicitVersion ?? parsedVersion;
|
|
2319
|
+
const parts = normalized.split(".");
|
|
2320
|
+
const maybePlatform = parts.at(-1);
|
|
2321
|
+
let slug;
|
|
2322
|
+
let platform;
|
|
2323
|
+
if (maybePlatform && isSupportedPlatform(maybePlatform)) {
|
|
2324
|
+
slug = parts.slice(0, -1).join(".");
|
|
2325
|
+
platform = explicitPlatform ?? maybePlatform;
|
|
2326
|
+
} else {
|
|
2327
|
+
slug = normalized;
|
|
2328
|
+
platform = explicitPlatform;
|
|
2329
|
+
}
|
|
2330
|
+
return {
|
|
2331
|
+
slug,
|
|
2332
|
+
platform,
|
|
2333
|
+
version: version$1
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2142
2337
|
* Unpublishes a preset version from the registry
|
|
2143
2338
|
*/
|
|
2144
2339
|
async function unpublish(options) {
|
|
2145
|
-
const { slug, platform, version: version$1 } = options;
|
|
2340
|
+
const { slug, platform, version: version$1 } = parseUnpublishInput(options.preset, options.platform, options.version);
|
|
2146
2341
|
if (!slug) {
|
|
2147
2342
|
log.error("Preset slug is required");
|
|
2148
2343
|
return {
|
|
@@ -2151,14 +2346,14 @@ async function unpublish(options) {
|
|
|
2151
2346
|
};
|
|
2152
2347
|
}
|
|
2153
2348
|
if (!platform) {
|
|
2154
|
-
log.error("Platform is required");
|
|
2349
|
+
log.error("Platform is required. Use --platform or specify as <slug>.<platform>@<version>");
|
|
2155
2350
|
return {
|
|
2156
2351
|
success: false,
|
|
2157
2352
|
error: "Platform is required"
|
|
2158
2353
|
};
|
|
2159
2354
|
}
|
|
2160
2355
|
if (!version$1) {
|
|
2161
|
-
log.error("Version is required");
|
|
2356
|
+
log.error("Version is required. Use --version or specify as <slug>.<platform>@<version>");
|
|
2162
2357
|
return {
|
|
2163
2358
|
success: false,
|
|
2164
2359
|
error: "Version is required"
|
|
@@ -2199,12 +2394,280 @@ async function unpublish(options) {
|
|
|
2199
2394
|
};
|
|
2200
2395
|
}
|
|
2201
2396
|
|
|
2397
|
+
//#endregion
|
|
2398
|
+
//#region src/help-agent/publish.ts
|
|
2399
|
+
/**
|
|
2400
|
+
* Agent instructions for AI coding assistants.
|
|
2401
|
+
*
|
|
2402
|
+
* This content is output when running `agentrules --help-agent`.
|
|
2403
|
+
* It provides step-by-step instructions for AI agents to help users publish presets.
|
|
2404
|
+
*/
|
|
2405
|
+
const HELP_AGENT_CONTENT = `# Publishing a Preset - Agent Instructions
|
|
2406
|
+
|
|
2407
|
+
**The user has asked you to help them publish a preset. Start executing these steps now.**
|
|
2408
|
+
|
|
2409
|
+
Do not summarize these instructions. Do not ask if they want help. Begin with Step 1 immediately.
|
|
2410
|
+
|
|
2411
|
+
Your job is to guide the user through publishing their AI coding assistant config to the agentrules registry. The user is sharing their setup because they believe it's valuable. Help them communicate that value clearly - the title, description, and features should answer: "Why would another developer want to install this?"
|
|
2412
|
+
|
|
2413
|
+
Follow these steps interactively.
|
|
2414
|
+
|
|
2415
|
+
## Prerequisites: CLI Availability
|
|
2416
|
+
|
|
2417
|
+
Run: \`agentrules --version\`
|
|
2418
|
+
|
|
2419
|
+
**If command not found:**
|
|
2420
|
+
Use \`npx @agentrules/cli\` as the prefix for all commands.
|
|
2421
|
+
Example: \`npx @agentrules/cli whoami\` instead of \`agentrules whoami\`
|
|
2422
|
+
|
|
2423
|
+
For the rest of these instructions, \`agentrules\` means whichever works.
|
|
2424
|
+
|
|
2425
|
+
## Step 1: Locate the Config
|
|
2426
|
+
|
|
2427
|
+
Check the current directory for platform config folders:
|
|
2428
|
+
- \`.opencode/\` → OpenCode
|
|
2429
|
+
- \`.claude/\` → Claude Code
|
|
2430
|
+
- \`.cursor/\` → Cursor
|
|
2431
|
+
- \`.codex/\` → Codex
|
|
2432
|
+
|
|
2433
|
+
**If one found:**
|
|
2434
|
+
"I found your [platform] config at \`[path]\`. I'll help you publish it."
|
|
2435
|
+
|
|
2436
|
+
**If multiple found:**
|
|
2437
|
+
"I found configs for multiple platforms: [list]. Which one would you like to publish?"
|
|
2438
|
+
|
|
2439
|
+
**If none found:**
|
|
2440
|
+
"I don't see a config directory here. Where is your config located?"
|
|
2441
|
+
|
|
2442
|
+
## Step 2: Check for Existing Config
|
|
2443
|
+
|
|
2444
|
+
List the files in \`[config-dir]\` first to see what exists.
|
|
2445
|
+
|
|
2446
|
+
If \`agentrules.json\` is in the listing, read it:
|
|
2447
|
+
- If complete (has name, description, tags): "You already have a preset configured: '[name]'. Ready to republish?" → Skip to Step 4 if yes
|
|
2448
|
+
- If missing required fields: Help them add the missing fields
|
|
2449
|
+
|
|
2450
|
+
If \`agentrules.json\` is not in the listing, continue to Step 3.
|
|
2451
|
+
|
|
2452
|
+
### Check for ignorable files
|
|
2453
|
+
|
|
2454
|
+
While reviewing the file listing, look for files/folders that probably shouldn't be published.
|
|
2455
|
+
|
|
2456
|
+
**Already ignored by default** (don't suggest these):
|
|
2457
|
+
- node_modules
|
|
2458
|
+
- .git
|
|
2459
|
+
- .DS_Store
|
|
2460
|
+
- *.lock
|
|
2461
|
+
- package-lock.json
|
|
2462
|
+
- bun.lockb
|
|
2463
|
+
- pnpm-lock.yaml
|
|
2464
|
+
|
|
2465
|
+
**Commonly ignorable** (suggest adding to \`ignore\` field if present):
|
|
2466
|
+
- build/, dist/, out/ (build output)
|
|
2467
|
+
- .env, .env.* (environment files)
|
|
2468
|
+
- *.log (log files)
|
|
2469
|
+
- tmp/, temp/ (temporary files)
|
|
2470
|
+
- coverage/ (test coverage)
|
|
2471
|
+
- .cache/, .turbo/ (cache directories)
|
|
2472
|
+
|
|
2473
|
+
If you see any of these or similar files/folders, ask: "I noticed [files]. These are usually not needed in a preset. Want me to add them to the ignore list?"
|
|
2474
|
+
|
|
2475
|
+
If yes, include them in the \`ignore\` array when creating agentrules.json.
|
|
2476
|
+
|
|
2477
|
+
## Step 3: Create agentrules.json
|
|
2478
|
+
|
|
2479
|
+
The goal is to help potential users understand the **value** of this preset - why should they install it? What problem does it solve? How will it improve their workflow?
|
|
2480
|
+
|
|
2481
|
+
### 3a. Analyze their config
|
|
2482
|
+
|
|
2483
|
+
You already listed files in Step 2. Now read the config files you found (e.g., CLAUDE.md, AGENT_RULES.md, rules/*.md) to understand what the preset does.
|
|
2484
|
+
|
|
2485
|
+
Look for:
|
|
2486
|
+
- Technologies and frameworks mentioned
|
|
2487
|
+
- The main purpose or rules being enforced
|
|
2488
|
+
- Who would benefit from this setup
|
|
2489
|
+
|
|
2490
|
+
### 3b. Generate all suggestions at once
|
|
2491
|
+
|
|
2492
|
+
Based on your analysis, generate suggestions for ALL fields:
|
|
2493
|
+
|
|
2494
|
+
- **Name**: lowercase, hyphens, based on repo/directory/theme (1-64 chars)
|
|
2495
|
+
- **Title**: Title-cased, compelling name
|
|
2496
|
+
- **Description**: Value-focused - who is this for, what problem does it solve? (max 500 chars)
|
|
2497
|
+
- **Tags**: For discovery - technologies, frameworks, use cases (1-10 tags)
|
|
2498
|
+
- **Features**: Key benefits, not just capabilities (optional, up to 5)
|
|
2499
|
+
- **License**: Default to MIT
|
|
2500
|
+
|
|
2501
|
+
### 3c. Present a single summary
|
|
2502
|
+
|
|
2503
|
+
Show everything in one concise output. Put each field name on its own line, followed by the value on the next line:
|
|
2504
|
+
|
|
2505
|
+
"Based on your config, here's what I'd suggest:
|
|
2506
|
+
|
|
2507
|
+
**Name**
|
|
2508
|
+
typescript-strict-rules
|
|
2509
|
+
|
|
2510
|
+
**Title**
|
|
2511
|
+
TypeScript Strict Rules
|
|
2512
|
+
|
|
2513
|
+
**Description**
|
|
2514
|
+
Opinionated TypeScript rules that catch common bugs at dev time and enforce consistent patterns across your team.
|
|
2515
|
+
|
|
2516
|
+
**Tags**
|
|
2517
|
+
typescript, strict, type-safety
|
|
2518
|
+
|
|
2519
|
+
**Features**
|
|
2520
|
+
- Catches null/undefined errors before production
|
|
2521
|
+
- Enforces consistent code style without manual review
|
|
2522
|
+
|
|
2523
|
+
**License**
|
|
2524
|
+
MIT
|
|
2525
|
+
|
|
2526
|
+
Let me know if you'd like to change anything, or say 'looks good' to continue."
|
|
2527
|
+
|
|
2528
|
+
### 3d. Handle feedback
|
|
2529
|
+
|
|
2530
|
+
If the user wants changes (e.g., "change the description" or "add a react tag"), update those fields and show the summary again.
|
|
2531
|
+
|
|
2532
|
+
When they approve, proceed to create the file.
|
|
2533
|
+
|
|
2534
|
+
### Guidelines for good suggestions
|
|
2535
|
+
|
|
2536
|
+
**Description** should answer: What problem does this solve? Who benefits?
|
|
2537
|
+
- Good: "Opinionated TypeScript rules that catch common bugs at dev time and enforce consistent patterns across your team."
|
|
2538
|
+
- Bad: "TypeScript rules with strict settings." (too vague, no value prop)
|
|
2539
|
+
|
|
2540
|
+
**Features** should describe benefits, not capabilities:
|
|
2541
|
+
- Good: "Catches null/undefined errors before they hit production"
|
|
2542
|
+
- Bad: "Strict null checks" (feature, not benefit)
|
|
2543
|
+
|
|
2544
|
+
**Tags** should help with discovery:
|
|
2545
|
+
- Technologies: typescript, python, rust, go
|
|
2546
|
+
- Frameworks: react, nextjs, fastapi, django
|
|
2547
|
+
- Use cases: code-review, testing, security, onboarding
|
|
2548
|
+
|
|
2549
|
+
### 3e. Create the file
|
|
2550
|
+
|
|
2551
|
+
Write \`[config-dir]/agentrules.json\`:
|
|
2552
|
+
|
|
2553
|
+
\`\`\`json
|
|
2554
|
+
{
|
|
2555
|
+
"$schema": "https://agentrules.directory/schema/agentrules.json",
|
|
2556
|
+
"name": "[name]",
|
|
2557
|
+
"title": "[title]",
|
|
2558
|
+
"version": 1,
|
|
2559
|
+
"description": "[description]",
|
|
2560
|
+
"tags": ["tag1", "tag2"],
|
|
2561
|
+
"license": "[license]",
|
|
2562
|
+
"platform": "[detected-platform]"
|
|
2563
|
+
}
|
|
2564
|
+
\`\`\`
|
|
2565
|
+
|
|
2566
|
+
Include \`"features": [...]\` only if provided.
|
|
2567
|
+
Include \`"ignore": ["pattern1", "pattern2"]\` if the user agreed to ignore certain files.
|
|
2568
|
+
|
|
2569
|
+
### 3f. Show the file and get approval
|
|
2570
|
+
|
|
2571
|
+
After writing the file, show the user:
|
|
2572
|
+
|
|
2573
|
+
"I've created the config file at \`[config-dir]/agentrules.json\`:
|
|
2574
|
+
|
|
2575
|
+
\`\`\`json
|
|
2576
|
+
[show the actual file contents]
|
|
2577
|
+
\`\`\`
|
|
2578
|
+
|
|
2579
|
+
Take a look and let me know if you'd like to change anything, or say 'looks good' to continue."
|
|
2580
|
+
|
|
2581
|
+
Wait for approval before proceeding. If they want changes, edit the file and show it again.
|
|
2582
|
+
|
|
2583
|
+
Then validate: \`agentrules validate [config-dir]\`
|
|
2584
|
+
|
|
2585
|
+
If errors, fix and retry.
|
|
2586
|
+
|
|
2587
|
+
## Step 4: Login
|
|
2588
|
+
|
|
2589
|
+
Run: \`agentrules whoami\`
|
|
2590
|
+
|
|
2591
|
+
**If output shows "loggedIn": false or "Not logged in":**
|
|
2592
|
+
"You need to log in to publish."
|
|
2593
|
+
Run: \`agentrules login\`
|
|
2594
|
+
This opens a browser for authentication. Wait for completion.
|
|
2595
|
+
|
|
2596
|
+
## Step 5: Preview with Dry Run
|
|
2597
|
+
|
|
2598
|
+
Run: \`agentrules publish [config-dir] --dry-run\`
|
|
2599
|
+
|
|
2600
|
+
Show the user the preview:
|
|
2601
|
+
"Here's what will be published:
|
|
2602
|
+
- Name: [name]
|
|
2603
|
+
- Platform: [platform]
|
|
2604
|
+
- Files: [count] files ([size])
|
|
2605
|
+
|
|
2606
|
+
Ready to publish?"
|
|
2607
|
+
|
|
2608
|
+
If they want changes, help edit agentrules.json and re-run dry-run.
|
|
2609
|
+
|
|
2610
|
+
## Step 6: Publish
|
|
2611
|
+
|
|
2612
|
+
Run: \`agentrules publish [config-dir]\`
|
|
2613
|
+
|
|
2614
|
+
**If successful:**
|
|
2615
|
+
Show the URL from the output:
|
|
2616
|
+
|
|
2617
|
+
"Published! Your preset is live at: [url]
|
|
2618
|
+
|
|
2619
|
+
Share with others:
|
|
2620
|
+
\`\`\`
|
|
2621
|
+
npx @agentrules/cli add [name]
|
|
2622
|
+
\`\`\`"
|
|
2623
|
+
|
|
2624
|
+
**If "already exists" error:**
|
|
2625
|
+
Ask if they want to increment the \`version\` field in agentrules.json and retry.
|
|
2626
|
+
|
|
2627
|
+
**If other errors:**
|
|
2628
|
+
Show the error and suggest: \`agentrules validate [config-dir]\`
|
|
2629
|
+
|
|
2630
|
+
## Step 7: Tips
|
|
2631
|
+
|
|
2632
|
+
**If you used \`npx @agentrules/cli\`:**
|
|
2633
|
+
"Tip: Install globally to skip the npx download:
|
|
2634
|
+
\`\`\`
|
|
2635
|
+
npm i -g @agentrules/cli
|
|
2636
|
+
\`\`\`"
|
|
2637
|
+
|
|
2638
|
+
## Notes for Agent
|
|
2639
|
+
|
|
2640
|
+
- Be conversational and helpful
|
|
2641
|
+
- Explain what you're doing at each step
|
|
2642
|
+
- Use \`agentrules validate\` to check your work after any config changes
|
|
2643
|
+
- Remember whether you used npx for the tip at the end
|
|
2644
|
+
- If the user seems confused, explain that agentrules is a registry for sharing AI coding configs
|
|
2645
|
+
- The config file must be inside the platform directory (e.g., \`.opencode/agentrules.json\`)
|
|
2646
|
+
|
|
2647
|
+
## Schema Reference
|
|
2648
|
+
|
|
2649
|
+
**Required fields:**
|
|
2650
|
+
- \`name\`: slug format (lowercase, hyphens, 1-64 chars)
|
|
2651
|
+
- \`title\`: 1-80 characters
|
|
2652
|
+
- \`description\`: 1-500 characters
|
|
2653
|
+
- \`tags\`: array, 1-10 items, each lowercase/hyphens, max 35 chars
|
|
2654
|
+
- \`license\`: SPDX identifier (e.g., "MIT")
|
|
2655
|
+
- \`platform\`: one of \`opencode\`, \`claude\`, \`cursor\`, \`codex\`
|
|
2656
|
+
|
|
2657
|
+
**Optional fields:**
|
|
2658
|
+
- \`$schema\`: JSON schema URL for validation
|
|
2659
|
+
- \`version\`: major version number (default: 1)
|
|
2660
|
+
- \`features\`: array, max 5 items, each max 100 chars
|
|
2661
|
+
- \`path\`: custom path to files (advanced use)
|
|
2662
|
+
- \`ignore\`: patterns to exclude from bundle
|
|
2663
|
+
`;
|
|
2664
|
+
|
|
2202
2665
|
//#endregion
|
|
2203
2666
|
//#region src/index.ts
|
|
2204
2667
|
const require = createRequire(import.meta.url);
|
|
2205
2668
|
const packageJson = require("../package.json");
|
|
2206
2669
|
const program = new Command();
|
|
2207
|
-
program.name("agentrules").description("The AI Agent Directory CLI").version(packageJson.version).option("-v, --verbose", "Enable verbose/debug output").configureOutput({ outputError: (str, write) => write(ui.error(str.trim())) }).hook("preAction", async (thisCommand, actionCommand) => {
|
|
2670
|
+
program.name("agentrules").description("The AI Agent Directory CLI").version(packageJson.version).option("-v, --verbose", "Enable verbose/debug output").option("--help-agent", "Output instructions for AI coding assistants").configureOutput({ outputError: (str, write) => write(ui.error(str.trim())) }).hook("preAction", async (thisCommand, actionCommand) => {
|
|
2208
2671
|
const opts = thisCommand.opts();
|
|
2209
2672
|
if (opts.verbose) log.setVerbose(true);
|
|
2210
2673
|
const actionOpts = actionCommand.opts();
|
|
@@ -2217,7 +2680,7 @@ program.name("agentrules").description("The AI Agent Directory CLI").version(pac
|
|
|
2217
2680
|
log.debug(`Failed to init context: ${getErrorMessage(error$2)}`);
|
|
2218
2681
|
}
|
|
2219
2682
|
}).showHelpAfterError();
|
|
2220
|
-
program.command("add <preset>").description("Download and install a preset from the registry").option("-p, --platform <platform>", "Target platform (opencode, codex, claude, cursor)").option("-V, --version <version>", "Install a specific version").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").option("-y, --yes", "Alias for --force").option("--dry-run", "Preview changes without writing").option("--skip-conflicts", "Skip conflicting files").action(handle(async (preset, options) => {
|
|
2683
|
+
program.command("add <preset>").description("Download and install a preset from the registry").option("-p, --platform <platform>", "Target platform (opencode, codex, claude, cursor)").option("-V, --version <version>", "Install a specific version").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").option("--no-backup", "Don't backup files before overwriting (use with --force)").action(handle(async (preset, options) => {
|
|
2221
2684
|
const platform = options.platform ? normalizePlatformInput(options.platform) : void 0;
|
|
2222
2685
|
const dryRun = Boolean(options.dryRun);
|
|
2223
2686
|
const spinner$1 = await log.spinner("Fetching preset...");
|
|
@@ -2231,7 +2694,8 @@ program.command("add <preset>").description("Download and install a preset from
|
|
|
2231
2694
|
directory: options.dir,
|
|
2232
2695
|
force: Boolean(options.force || options.yes),
|
|
2233
2696
|
dryRun,
|
|
2234
|
-
skipConflicts: Boolean(options.skipConflicts)
|
|
2697
|
+
skipConflicts: Boolean(options.skipConflicts),
|
|
2698
|
+
noBackup: options.backup === false
|
|
2235
2699
|
});
|
|
2236
2700
|
} catch (err) {
|
|
2237
2701
|
spinner$1.stop();
|
|
@@ -2241,16 +2705,23 @@ program.command("add <preset>").description("Download and install a preset from
|
|
|
2241
2705
|
const hasBlockingConflicts = result.conflicts.length > 0 && !options.skipConflicts && !dryRun;
|
|
2242
2706
|
if (hasBlockingConflicts) {
|
|
2243
2707
|
const count$1 = result.conflicts.length === 1 ? "1 file has" : `${result.conflicts.length} files have`;
|
|
2244
|
-
|
|
2708
|
+
const forceHint = `Use ${ui.command("--force")} to overwrite ${ui.muted("(--no-backup to skip backups)")}`;
|
|
2709
|
+
log.error(`${count$1} conflicts. ${forceHint}`);
|
|
2245
2710
|
log.print("");
|
|
2246
2711
|
for (const conflict of result.conflicts.slice(0, 3)) {
|
|
2247
2712
|
log.print(` ${ui.muted("•")} ${conflict.path}`);
|
|
2248
2713
|
if (conflict.diff) log.print(conflict.diff.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
2249
2714
|
}
|
|
2250
2715
|
if (result.conflicts.length > 3) log.print(`\n ${ui.muted(`...and ${result.conflicts.length - 3} more`)}`);
|
|
2716
|
+
log.print("");
|
|
2717
|
+
log.print(forceHint);
|
|
2251
2718
|
process.exitCode = 1;
|
|
2252
2719
|
return;
|
|
2253
2720
|
}
|
|
2721
|
+
if (result.backups.length > 0) {
|
|
2722
|
+
log.print("");
|
|
2723
|
+
for (const backup of result.backups) log.print(ui.backupStatus(backup.originalPath, backup.backupPath, { dryRun }));
|
|
2724
|
+
}
|
|
2254
2725
|
log.print("");
|
|
2255
2726
|
for (const file of result.files) {
|
|
2256
2727
|
const status = file.status === "overwritten" ? "updated" : file.status;
|
|
@@ -2291,25 +2762,23 @@ program.command("init").description("Initialize a new preset").argument("[direct
|
|
|
2291
2762
|
log.print(ui.numberedList(nextSteps$1));
|
|
2292
2763
|
return;
|
|
2293
2764
|
}
|
|
2294
|
-
const
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
const detectedPath = detected.find((d) => d.id === platform)?.path;
|
|
2305
|
-
platformDir = detectedPath ? join(targetDir, detectedPath) : join(targetDir, PLATFORMS[platform].projectDir);
|
|
2765
|
+
const resolved = await resolvePlatformDirectory(targetDir, options.platform);
|
|
2766
|
+
if (!options.platform) {
|
|
2767
|
+
const check$1 = requiresPlatformFlag(resolved);
|
|
2768
|
+
if (check$1.required) {
|
|
2769
|
+
if (check$1.reason === "no_platforms") {
|
|
2770
|
+
const targetDirName = basename(targetDir);
|
|
2771
|
+
log.error(`No platform directory found in "${targetDirName}". Specify --platform (${PLATFORM_IDS.join(", ")}) or run from a platform directory.`);
|
|
2772
|
+
} else log.error(`Multiple platform directories found (${check$1.platforms.join(", ")}). Specify --platform to choose one.`);
|
|
2773
|
+
process.exit(1);
|
|
2774
|
+
}
|
|
2306
2775
|
}
|
|
2307
2776
|
const result = await initPreset({
|
|
2308
|
-
directory: platformDir,
|
|
2777
|
+
directory: resolved.platformDir,
|
|
2309
2778
|
name: options.name ?? defaultName,
|
|
2310
2779
|
title: options.title,
|
|
2311
2780
|
description: options.description,
|
|
2312
|
-
platform,
|
|
2781
|
+
platform: resolved.platform,
|
|
2313
2782
|
license: options.license,
|
|
2314
2783
|
force: options.force
|
|
2315
2784
|
});
|
|
@@ -2464,14 +2933,19 @@ program.command("publish").description("Publish a preset to the registry").argum
|
|
|
2464
2933
|
});
|
|
2465
2934
|
if (!result.success) process.exitCode = 1;
|
|
2466
2935
|
}));
|
|
2467
|
-
program.command("unpublish").description("Remove a preset version from the registry").argument("<
|
|
2936
|
+
program.command("unpublish").description("Remove a preset version from the registry").argument("<preset>", "Preset to unpublish (e.g., my-preset.claude@1.0 or my-preset@1.0)").option("-p, --platform <platform>", "Target platform (opencode, codex, claude, cursor)").option("-V, --version <version>", "Version to unpublish").action(handle(async (preset, options) => {
|
|
2937
|
+
const platform = options.platform ? normalizePlatformInput(options.platform) : void 0;
|
|
2468
2938
|
const result = await unpublish({
|
|
2469
|
-
|
|
2939
|
+
preset,
|
|
2470
2940
|
platform,
|
|
2471
|
-
version: version
|
|
2941
|
+
version: options.version
|
|
2472
2942
|
});
|
|
2473
2943
|
if (!result.success) process.exitCode = 1;
|
|
2474
2944
|
}));
|
|
2945
|
+
if (process.argv.includes("--help-agent")) {
|
|
2946
|
+
console.log(HELP_AGENT_CONTENT);
|
|
2947
|
+
process.exit(0);
|
|
2948
|
+
}
|
|
2475
2949
|
program.parseAsync(process.argv).then(() => {
|
|
2476
2950
|
process.exit(process.exitCode ?? 0);
|
|
2477
2951
|
}).catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentrules/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
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.10",
|
|
52
52
|
"@clack/prompts": "^0.11.0",
|
|
53
53
|
"chalk": "^5.4.1",
|
|
54
54
|
"commander": "^12.1.0",
|