@agentrules/cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +300 -65
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ A typical single-platform rule structure is:
|
|
|
105
105
|
.
|
|
106
106
|
├── agentrules.json # Rule config (created by init)
|
|
107
107
|
├── README.md # Shown on registry page (optional, not bundled)
|
|
108
|
-
├── LICENSE.md # Full license text (optional, not bundled)
|
|
108
|
+
├── LICENSE.md # Full license text (optional, not bundled; LICENSE.txt also supported)
|
|
109
109
|
├── INSTALL.txt # Shown after install (optional, not bundled)
|
|
110
110
|
├── AGENTS.md # Instruction file (optional)
|
|
111
111
|
└── command/
|
package/dist/index.js
CHANGED
|
@@ -63,6 +63,11 @@ const PLATFORMS = {
|
|
|
63
63
|
description: "Custom tool",
|
|
64
64
|
project: "{platformDir}/tool/{name}.ts",
|
|
65
65
|
global: "{platformDir}/tool/{name}.ts"
|
|
66
|
+
},
|
|
67
|
+
skill: {
|
|
68
|
+
description: "Agent skill",
|
|
69
|
+
project: "{platformDir}/skill/{name}/SKILL.md",
|
|
70
|
+
global: "{platformDir}/skill/{name}/SKILL.md"
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
},
|
|
@@ -112,6 +117,11 @@ const PLATFORMS = {
|
|
|
112
117
|
description: "Custom slash command",
|
|
113
118
|
project: "{platformDir}/commands/{name}.md",
|
|
114
119
|
global: "{platformDir}/commands/{name}.md"
|
|
120
|
+
},
|
|
121
|
+
skill: {
|
|
122
|
+
description: "Agent skill",
|
|
123
|
+
project: "{platformDir}/skills/{name}/SKILL.md",
|
|
124
|
+
global: "{platformDir}/skills/{name}/SKILL.md"
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
},
|
|
@@ -129,6 +139,11 @@ const PLATFORMS = {
|
|
|
129
139
|
description: "Custom prompt",
|
|
130
140
|
project: null,
|
|
131
141
|
global: "{platformDir}/prompts/{name}.md"
|
|
142
|
+
},
|
|
143
|
+
skill: {
|
|
144
|
+
description: "Agent skill",
|
|
145
|
+
project: "{platformDir}/skills/{name}/SKILL.md",
|
|
146
|
+
global: "{platformDir}/skills/{name}/SKILL.md"
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
}
|
|
@@ -265,6 +280,41 @@ function inferTypeFromPath(platform, filePath) {
|
|
|
265
280
|
if (!nextDir) return;
|
|
266
281
|
return getProjectTypeDirMap(platform).get(nextDir);
|
|
267
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Get the install directory for a type (parent directory of the install path).
|
|
285
|
+
* For skills, this is the directory containing SKILL.md.
|
|
286
|
+
*/
|
|
287
|
+
function getInstallDir({ platform, type, name }) {
|
|
288
|
+
const installPath = getInstallPath({
|
|
289
|
+
platform,
|
|
290
|
+
type,
|
|
291
|
+
name,
|
|
292
|
+
scope: "project"
|
|
293
|
+
});
|
|
294
|
+
if (!installPath) return null;
|
|
295
|
+
const lastSlash = installPath.lastIndexOf("/");
|
|
296
|
+
if (lastSlash === -1) return null;
|
|
297
|
+
return installPath.slice(0, lastSlash);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Normalize skill files by finding SKILL.md anchor and adjusting all paths.
|
|
301
|
+
* Strips any existing path prefix to prevent duplication.
|
|
302
|
+
*/
|
|
303
|
+
function normalizeSkillFiles({ files, installDir }) {
|
|
304
|
+
const marker = files.find((f) => f.path === "SKILL.md" || f.path.endsWith("/SKILL.md"));
|
|
305
|
+
if (!marker) throw new Error("SKILL.md not found in files");
|
|
306
|
+
const skillRoot = marker.path === "SKILL.md" ? "." : marker.path.slice(0, marker.path.lastIndexOf("/"));
|
|
307
|
+
return files.map((f) => {
|
|
308
|
+
let relative$1;
|
|
309
|
+
if (skillRoot === ".") relative$1 = f.path;
|
|
310
|
+
else if (f.path.startsWith(`${skillRoot}/`)) relative$1 = f.path.slice(skillRoot.length + 1);
|
|
311
|
+
else relative$1 = f.path;
|
|
312
|
+
return {
|
|
313
|
+
...f,
|
|
314
|
+
path: `${installDir}/${relative$1}`
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
}
|
|
268
318
|
|
|
269
319
|
//#endregion
|
|
270
320
|
//#region ../core/src/utils/encoding.ts
|
|
@@ -5834,9 +5884,27 @@ async function directoryExists(path$1) {
|
|
|
5834
5884
|
|
|
5835
5885
|
//#endregion
|
|
5836
5886
|
//#region src/lib/rule-utils.ts
|
|
5837
|
-
const
|
|
5838
|
-
|
|
5839
|
-
|
|
5887
|
+
const SKILL_FILENAME = "SKILL.md";
|
|
5888
|
+
/**
|
|
5889
|
+
* Parse SKILL.md frontmatter for name and license.
|
|
5890
|
+
* Only extracts simple key: value pairs we need for quick publish defaults.
|
|
5891
|
+
*/
|
|
5892
|
+
function parseSkillFrontmatter(content) {
|
|
5893
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
5894
|
+
if (!match?.[1]) return {};
|
|
5895
|
+
const frontmatter = match[1];
|
|
5896
|
+
const result = {};
|
|
5897
|
+
const nameMatch = frontmatter.match(/^name:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
5898
|
+
if (nameMatch?.[1]) result.name = nameMatch[1].trim();
|
|
5899
|
+
const licenseMatch = frontmatter.match(/^license:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
5900
|
+
if (licenseMatch?.[1]) result.license = licenseMatch[1].trim();
|
|
5901
|
+
return result;
|
|
5902
|
+
}
|
|
5903
|
+
const METADATA_FILES = {
|
|
5904
|
+
install: ["INSTALL.txt"],
|
|
5905
|
+
readme: ["README.md"],
|
|
5906
|
+
license: ["LICENSE.md", "LICENSE.txt"]
|
|
5907
|
+
};
|
|
5840
5908
|
/**
|
|
5841
5909
|
* Files/directories that are always excluded from rules.
|
|
5842
5910
|
* These are never useful in a rule bundle.
|
|
@@ -5970,9 +6038,9 @@ async function loadRule(ruleDir, overrides) {
|
|
|
5970
6038
|
}
|
|
5971
6039
|
async function collectMetadata(loaded) {
|
|
5972
6040
|
const { configDir } = loaded;
|
|
5973
|
-
const installMessage = await
|
|
5974
|
-
const readmeContent = await
|
|
5975
|
-
const licenseContent = await
|
|
6041
|
+
const installMessage = await readFirstMatch(configDir, METADATA_FILES.install);
|
|
6042
|
+
const readmeContent = await readFirstMatch(configDir, METADATA_FILES.readme);
|
|
6043
|
+
const licenseContent = await readFirstMatch(configDir, METADATA_FILES.license);
|
|
5976
6044
|
return {
|
|
5977
6045
|
installMessage,
|
|
5978
6046
|
readmeContent,
|
|
@@ -5990,6 +6058,34 @@ async function collectPlatformFiles(loaded) {
|
|
|
5990
6058
|
const resolvedSourcePath = sourcePath ?? ".";
|
|
5991
6059
|
const filesDir = join(configDir, resolvedSourcePath);
|
|
5992
6060
|
log.debug(`Files for ${platform}: source=${resolvedSourcePath}, dir=${filesDir}`);
|
|
6061
|
+
const filesDirExists = await directoryExists(filesDir);
|
|
6062
|
+
const rootExclude = [RULE_CONFIG_FILENAME];
|
|
6063
|
+
if (filesDir === configDir) rootExclude.push(...METADATA_FILES.readme, ...METADATA_FILES.license, ...METADATA_FILES.install);
|
|
6064
|
+
const collectedFiles = filesDirExists ? await collectFiles(filesDir, rootExclude, ignorePatterns) : [];
|
|
6065
|
+
if (collectedFiles.length === 0) {
|
|
6066
|
+
if (!filesDirExists) throw new Error(`Files directory not found: ${filesDir}. Create the directory or set "path" in the platform entry.`);
|
|
6067
|
+
throw new Error(`No files found in ${filesDir}. Rules must include at least one file.`);
|
|
6068
|
+
}
|
|
6069
|
+
if (config$1.type === "skill") {
|
|
6070
|
+
const installDir = getInstallDir({
|
|
6071
|
+
platform,
|
|
6072
|
+
type: "skill",
|
|
6073
|
+
name: config$1.name
|
|
6074
|
+
});
|
|
6075
|
+
if (!installDir) throw new Error(`Platform "${platform}" does not support skill type.`);
|
|
6076
|
+
const normalizedFiles = normalizeSkillFiles({
|
|
6077
|
+
files: collectedFiles,
|
|
6078
|
+
installDir
|
|
6079
|
+
});
|
|
6080
|
+
platformFiles.push({
|
|
6081
|
+
platform,
|
|
6082
|
+
files: normalizedFiles.map((f) => ({
|
|
6083
|
+
path: f.path,
|
|
6084
|
+
content: typeof f.content === "string" ? f.content : new TextDecoder().decode(f.content)
|
|
6085
|
+
}))
|
|
6086
|
+
});
|
|
6087
|
+
continue;
|
|
6088
|
+
}
|
|
5993
6089
|
const treatInstructionAsRoot = config$1.type === void 0 || config$1.type === "instruction";
|
|
5994
6090
|
const instructionProjectPath = treatInstructionAsRoot ? getInstallPath({
|
|
5995
6091
|
platform,
|
|
@@ -5997,10 +6093,6 @@ async function collectPlatformFiles(loaded) {
|
|
|
5997
6093
|
scope: "project"
|
|
5998
6094
|
}) : null;
|
|
5999
6095
|
const instructionContent = instructionProjectPath ? await readFileIfExists(join(configDir, instructionProjectPath)) : void 0;
|
|
6000
|
-
const filesDirExists = await directoryExists(filesDir);
|
|
6001
|
-
const rootExclude = [RULE_CONFIG_FILENAME];
|
|
6002
|
-
if (filesDir === configDir) rootExclude.push(README_FILENAME, LICENSE_FILENAME, INSTALL_FILENAME);
|
|
6003
|
-
const collectedFiles = filesDirExists ? await collectFiles(filesDir, rootExclude, ignorePatterns) : [];
|
|
6004
6096
|
const publishFiles = [];
|
|
6005
6097
|
const seenPublishPaths = new Set();
|
|
6006
6098
|
for (const file of collectedFiles) {
|
|
@@ -6020,10 +6112,7 @@ async function collectPlatformFiles(loaded) {
|
|
|
6020
6112
|
content: instructionContent
|
|
6021
6113
|
});
|
|
6022
6114
|
}
|
|
6023
|
-
if (publishFiles.length === 0) {
|
|
6024
|
-
if (!filesDirExists) throw new Error(`Files directory not found: ${filesDir}. Create the directory or set "path" in the platform entry.`);
|
|
6025
|
-
throw new Error(`No files found in ${filesDir}. Rules must include at least one file.`);
|
|
6026
|
-
}
|
|
6115
|
+
if (publishFiles.length === 0) throw new Error(`No files found in ${filesDir}. Rules must include at least one file.`);
|
|
6027
6116
|
platformFiles.push({
|
|
6028
6117
|
platform,
|
|
6029
6118
|
files: publishFiles
|
|
@@ -6092,6 +6181,16 @@ async function readFileIfExists(path$1) {
|
|
|
6092
6181
|
if (await fileExists(path$1)) return await readFile(path$1, "utf8");
|
|
6093
6182
|
return;
|
|
6094
6183
|
}
|
|
6184
|
+
/**
|
|
6185
|
+
* Try reading files from a list of candidates, return first match.
|
|
6186
|
+
*/
|
|
6187
|
+
async function readFirstMatch(dir, filenames) {
|
|
6188
|
+
for (const filename of filenames) {
|
|
6189
|
+
const content = await readFileIfExists(join(dir, filename));
|
|
6190
|
+
if (content !== void 0) return content;
|
|
6191
|
+
}
|
|
6192
|
+
return;
|
|
6193
|
+
}
|
|
6095
6194
|
|
|
6096
6195
|
//#endregion
|
|
6097
6196
|
//#region src/lib/zod-validator.ts
|
|
@@ -6139,11 +6238,12 @@ async function publish(options = {}) {
|
|
|
6139
6238
|
}
|
|
6140
6239
|
if (!dryRun) log.debug(`Authenticated as user, publishing to ${ctx.registry.url}`);
|
|
6141
6240
|
const filePath = await getSingleFilePath(path$1);
|
|
6241
|
+
const quickDir = await getQuickPublishDirectory(path$1);
|
|
6142
6242
|
let publishInput;
|
|
6143
6243
|
if (filePath) {
|
|
6144
6244
|
let resolved;
|
|
6145
6245
|
try {
|
|
6146
|
-
resolved = await
|
|
6246
|
+
resolved = await resolveQuickPublishInputs({
|
|
6147
6247
|
name,
|
|
6148
6248
|
platform,
|
|
6149
6249
|
type,
|
|
@@ -6151,7 +6251,10 @@ async function publish(options = {}) {
|
|
|
6151
6251
|
description,
|
|
6152
6252
|
tags,
|
|
6153
6253
|
license
|
|
6154
|
-
},
|
|
6254
|
+
}, {
|
|
6255
|
+
type: "file",
|
|
6256
|
+
path: filePath
|
|
6257
|
+
}, {
|
|
6155
6258
|
dryRun,
|
|
6156
6259
|
yes
|
|
6157
6260
|
});
|
|
@@ -6166,7 +6269,7 @@ async function publish(options = {}) {
|
|
|
6166
6269
|
const fileSpinner = await log.spinner("Building bundle...");
|
|
6167
6270
|
try {
|
|
6168
6271
|
publishInput = await buildPublishInput({
|
|
6169
|
-
rule: await
|
|
6272
|
+
rule: await buildRuleInputFromQuickPublish(resolved),
|
|
6170
6273
|
version: version$2
|
|
6171
6274
|
});
|
|
6172
6275
|
log.debug(`Built publish input for platforms: ${publishInput.variants.map((v) => v.platform).join(", ")}`);
|
|
@@ -6187,6 +6290,57 @@ async function publish(options = {}) {
|
|
|
6187
6290
|
ctx
|
|
6188
6291
|
});
|
|
6189
6292
|
}
|
|
6293
|
+
if (quickDir) {
|
|
6294
|
+
let resolved;
|
|
6295
|
+
try {
|
|
6296
|
+
resolved = await resolveQuickPublishInputs({
|
|
6297
|
+
name,
|
|
6298
|
+
platform,
|
|
6299
|
+
type,
|
|
6300
|
+
title,
|
|
6301
|
+
description,
|
|
6302
|
+
tags,
|
|
6303
|
+
license
|
|
6304
|
+
}, {
|
|
6305
|
+
type: "directory",
|
|
6306
|
+
path: quickDir.path,
|
|
6307
|
+
entryFile: quickDir.entryFile
|
|
6308
|
+
}, {
|
|
6309
|
+
dryRun,
|
|
6310
|
+
yes
|
|
6311
|
+
});
|
|
6312
|
+
} catch (error$2) {
|
|
6313
|
+
const message = getErrorMessage(error$2);
|
|
6314
|
+
log.error(message);
|
|
6315
|
+
return {
|
|
6316
|
+
success: false,
|
|
6317
|
+
error: message
|
|
6318
|
+
};
|
|
6319
|
+
}
|
|
6320
|
+
const dirSpinner = await log.spinner("Building bundle...");
|
|
6321
|
+
try {
|
|
6322
|
+
publishInput = await buildPublishInput({
|
|
6323
|
+
rule: await buildRuleInputFromQuickPublish(resolved),
|
|
6324
|
+
version: version$2
|
|
6325
|
+
});
|
|
6326
|
+
log.debug(`Built publish input for platforms: ${publishInput.variants.map((v) => v.platform).join(", ")}`);
|
|
6327
|
+
} catch (error$2) {
|
|
6328
|
+
const message = getErrorMessage(error$2);
|
|
6329
|
+
dirSpinner.fail("Failed to build bundle");
|
|
6330
|
+
log.error(message);
|
|
6331
|
+
return {
|
|
6332
|
+
success: false,
|
|
6333
|
+
error: message
|
|
6334
|
+
};
|
|
6335
|
+
}
|
|
6336
|
+
return await finalizePublish({
|
|
6337
|
+
publishInput,
|
|
6338
|
+
dryRun,
|
|
6339
|
+
version: version$2,
|
|
6340
|
+
spinner: dirSpinner,
|
|
6341
|
+
ctx
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6190
6344
|
const spinner$1 = await log.spinner("Validating rule...");
|
|
6191
6345
|
const configPath = await resolveConfigPath(path$1);
|
|
6192
6346
|
log.debug(`Resolved config path: ${configPath}`);
|
|
@@ -6275,19 +6429,34 @@ async function getSingleFilePath(inputPath) {
|
|
|
6275
6429
|
if (basename(inputPath) === RULE_CONFIG_FILENAME) return;
|
|
6276
6430
|
return inputPath;
|
|
6277
6431
|
}
|
|
6432
|
+
async function getQuickPublishDirectory(inputPath) {
|
|
6433
|
+
if (!inputPath) return;
|
|
6434
|
+
const pathStat = await stat(inputPath).catch(() => null);
|
|
6435
|
+
if (!pathStat?.isDirectory()) return;
|
|
6436
|
+
const configStat = await stat(`${inputPath}/${RULE_CONFIG_FILENAME}`).catch(() => null);
|
|
6437
|
+
if (configStat?.isFile()) return;
|
|
6438
|
+
const skillPath = `${inputPath}/${SKILL_FILENAME}`;
|
|
6439
|
+
const skillStat = await stat(skillPath).catch(() => null);
|
|
6440
|
+
if (skillStat?.isFile()) return {
|
|
6441
|
+
path: inputPath,
|
|
6442
|
+
type: "skill",
|
|
6443
|
+
entryFile: skillPath
|
|
6444
|
+
};
|
|
6445
|
+
return;
|
|
6446
|
+
}
|
|
6278
6447
|
function normalizePathForInference(value) {
|
|
6279
6448
|
return value.replace(/\\/g, "/");
|
|
6280
6449
|
}
|
|
6281
6450
|
function stripExtension(value) {
|
|
6282
6451
|
return value.replace(/\.[^/.]+$/, "");
|
|
6283
6452
|
}
|
|
6284
|
-
function
|
|
6453
|
+
function inferFileDefaults(filePath) {
|
|
6285
6454
|
const normalized = normalizePathForInference(filePath);
|
|
6286
6455
|
const segments = normalized.split("/").filter(Boolean);
|
|
6287
6456
|
const fileName = segments.at(-1) ?? "";
|
|
6288
6457
|
const instructionPlatforms = inferInstructionPlatformsFromFileName(fileName);
|
|
6289
6458
|
if (instructionPlatforms.length > 0) return {
|
|
6290
|
-
|
|
6459
|
+
ruleType: "instruction",
|
|
6291
6460
|
...instructionPlatforms.length === 1 ? { platform: instructionPlatforms[0] } : {}
|
|
6292
6461
|
};
|
|
6293
6462
|
const platform = inferPlatformFromPath(filePath);
|
|
@@ -6295,11 +6464,22 @@ function inferSingleFileDefaults(filePath) {
|
|
|
6295
6464
|
const inferredType = inferTypeFromPath(platform, filePath);
|
|
6296
6465
|
const result = { platform };
|
|
6297
6466
|
if (inferredType) {
|
|
6298
|
-
result.
|
|
6467
|
+
result.ruleType = inferredType;
|
|
6299
6468
|
if (inferredType !== "instruction") result.name = normalizeName(stripExtension(fileName));
|
|
6300
6469
|
}
|
|
6301
6470
|
return result;
|
|
6302
6471
|
}
|
|
6472
|
+
async function inferDirectoryDefaults(_dirPath, entryFile, dirType) {
|
|
6473
|
+
const result = {};
|
|
6474
|
+
if (dirType === "skill") {
|
|
6475
|
+
result.ruleType = "skill";
|
|
6476
|
+
const content = await readFile(entryFile, "utf8");
|
|
6477
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
6478
|
+
if (frontmatter.name) result.name = normalizeName(frontmatter.name);
|
|
6479
|
+
if (frontmatter.license) result.license = frontmatter.license;
|
|
6480
|
+
}
|
|
6481
|
+
return result;
|
|
6482
|
+
}
|
|
6303
6483
|
function buildConfigPublishOverrides(options) {
|
|
6304
6484
|
const overrides = {};
|
|
6305
6485
|
if (options.name !== void 0) overrides.name = options.name;
|
|
@@ -6311,13 +6491,17 @@ function buildConfigPublishOverrides(options) {
|
|
|
6311
6491
|
if (options.tags !== void 0) overrides.tags = options.tags;
|
|
6312
6492
|
return Object.keys(overrides).length > 0 ? overrides : void 0;
|
|
6313
6493
|
}
|
|
6314
|
-
async function
|
|
6315
|
-
const inferred =
|
|
6494
|
+
async function resolveQuickPublishInputs(options, source, ctx) {
|
|
6495
|
+
const inferred = source.type === "file" ? inferFileDefaults(source.path) : await inferDirectoryDefaults(source.path, source.entryFile, "skill");
|
|
6316
6496
|
const isInteractive = !ctx.yes && process.stdin.isTTY;
|
|
6317
|
-
const
|
|
6318
|
-
|
|
6497
|
+
const isDirectory = source.type === "directory";
|
|
6498
|
+
const hasRequiredArgs = isDirectory ? options.platform !== void 0 && (options.name !== void 0 || inferred.name !== void 0) : options.name !== void 0 && options.platform !== void 0 && options.type !== void 0;
|
|
6499
|
+
if (!(isInteractive || hasRequiredArgs)) {
|
|
6500
|
+
if (isDirectory) throw new Error("Publishing a directory in non-interactive mode requires --platform (and --name if not in frontmatter).");
|
|
6501
|
+
throw new Error("Publishing a single file in non-interactive mode requires --name, --platform, and --type.");
|
|
6502
|
+
}
|
|
6319
6503
|
const selectedPlatforms = options.platform ? parsePlatformSelection(options.platform) : void 0;
|
|
6320
|
-
if (selectedPlatforms && selectedPlatforms.length > 1) throw new Error("
|
|
6504
|
+
if (selectedPlatforms && selectedPlatforms.length > 1) throw new Error("Quick publish requires exactly one --platform value.");
|
|
6321
6505
|
let selectedPlatform = selectedPlatforms?.[0] ? normalizePlatformInput(selectedPlatforms[0]) : inferred.platform;
|
|
6322
6506
|
if (!selectedPlatform) {
|
|
6323
6507
|
if (!isInteractive) throw new Error("Missing --platform");
|
|
@@ -6331,7 +6515,7 @@ async function resolveSingleFileInputs(options, filePath, ctx) {
|
|
|
6331
6515
|
if (p$1.isCancel(selection)) throw new Error("Cancelled");
|
|
6332
6516
|
selectedPlatform = selection;
|
|
6333
6517
|
}
|
|
6334
|
-
let selectedType = options.type ?? inferred.
|
|
6518
|
+
let selectedType = options.type ?? inferred.ruleType;
|
|
6335
6519
|
if (!selectedType) {
|
|
6336
6520
|
if (!isInteractive) throw new Error("Missing --type");
|
|
6337
6521
|
const candidates = getValidTypes(selectedPlatform).filter((t) => supportsInstallPath({
|
|
@@ -6350,8 +6534,8 @@ async function resolveSingleFileInputs(options, filePath, ctx) {
|
|
|
6350
6534
|
selectedType = selection;
|
|
6351
6535
|
}
|
|
6352
6536
|
const platform = selectedPlatform;
|
|
6353
|
-
const
|
|
6354
|
-
const nameValue = options.name ?? await (async () => {
|
|
6537
|
+
const ruleType = selectedType;
|
|
6538
|
+
const nameValue = options.name ?? inferred.name ?? await (async () => {
|
|
6355
6539
|
if (!isInteractive) throw new Error("Missing --name");
|
|
6356
6540
|
const input = await p$1.text({
|
|
6357
6541
|
message: "Rule name",
|
|
@@ -6364,13 +6548,6 @@ async function resolveSingleFileInputs(options, filePath, ctx) {
|
|
|
6364
6548
|
const normalizedName = normalizeName(nameValue);
|
|
6365
6549
|
const nameCheck = nameSchema.safeParse(normalizedName);
|
|
6366
6550
|
if (!nameCheck.success) throw new Error(nameCheck.error.issues[0]?.message ?? "Invalid name");
|
|
6367
|
-
const bundlePath = getInstallPath({
|
|
6368
|
-
platform,
|
|
6369
|
-
type,
|
|
6370
|
-
name: normalizedName,
|
|
6371
|
-
scope: "project"
|
|
6372
|
-
});
|
|
6373
|
-
if (!bundlePath) throw new Error(`Type "${type}" is not supported for project installs on platform "${platform}".`);
|
|
6374
6551
|
const defaultTitle = toTitleCase(normalizedName);
|
|
6375
6552
|
const finalTitle = options.title ?? await (async () => {
|
|
6376
6553
|
if (!isInteractive) return defaultTitle;
|
|
@@ -6403,59 +6580,80 @@ async function resolveSingleFileInputs(options, filePath, ctx) {
|
|
|
6403
6580
|
if (p$1.isCancel(input)) throw new Error("Cancelled");
|
|
6404
6581
|
return tagsInputSchema.parse(input);
|
|
6405
6582
|
})();
|
|
6406
|
-
const finalLicense = options.license ?? "MIT";
|
|
6583
|
+
const finalLicense = options.license ?? inferred.license ?? "MIT";
|
|
6407
6584
|
if (isInteractive && !ctx.dryRun) {
|
|
6408
6585
|
log.print("");
|
|
6409
6586
|
log.print(ui.header("Quick publish"));
|
|
6410
|
-
log.print(ui.keyValue("File", ui.path(
|
|
6587
|
+
log.print(ui.keyValue(isDirectory ? "Directory" : "File", ui.path(source.path)));
|
|
6411
6588
|
log.print(ui.keyValue("Name", ui.code(normalizedName)));
|
|
6412
6589
|
log.print(ui.keyValue("Title", finalTitle));
|
|
6413
6590
|
log.print(ui.keyValue("Description", finalDescription));
|
|
6414
6591
|
log.print(ui.keyValue("Platform", platform));
|
|
6415
|
-
log.print(ui.keyValue("Type",
|
|
6416
|
-
log.print(ui.keyValue("Installs to", ui.path(bundlePath)));
|
|
6592
|
+
log.print(ui.keyValue("Type", ruleType));
|
|
6417
6593
|
if (finalTags.length > 0) log.print(ui.keyValue("Tags", finalTags.join(", ")));
|
|
6418
6594
|
log.print("");
|
|
6419
6595
|
const confirm = await p$1.confirm({
|
|
6420
|
-
message: "Publish this file?",
|
|
6596
|
+
message: isDirectory ? "Publish this directory?" : "Publish this file?",
|
|
6421
6597
|
initialValue: true
|
|
6422
6598
|
});
|
|
6423
6599
|
if (p$1.isCancel(confirm) || !confirm) throw new Error("Cancelled");
|
|
6424
6600
|
}
|
|
6425
6601
|
return {
|
|
6426
|
-
|
|
6602
|
+
source,
|
|
6427
6603
|
name: normalizedName,
|
|
6428
6604
|
platform,
|
|
6429
|
-
|
|
6605
|
+
ruleType,
|
|
6430
6606
|
title: finalTitle,
|
|
6431
6607
|
description: finalDescription,
|
|
6432
6608
|
tags: finalTags,
|
|
6433
|
-
license: finalLicense
|
|
6434
|
-
bundlePath
|
|
6609
|
+
license: finalLicense
|
|
6435
6610
|
};
|
|
6436
6611
|
}
|
|
6437
|
-
async function
|
|
6438
|
-
const content = await readFile(inputs.filePath);
|
|
6612
|
+
async function buildRuleInputFromQuickPublish(inputs) {
|
|
6439
6613
|
const config$1 = {
|
|
6440
6614
|
$schema: RULE_SCHEMA_URL,
|
|
6441
6615
|
name: inputs.name,
|
|
6442
|
-
type: inputs.
|
|
6616
|
+
type: inputs.ruleType,
|
|
6443
6617
|
title: inputs.title,
|
|
6444
6618
|
description: inputs.description,
|
|
6445
6619
|
license: inputs.license,
|
|
6446
6620
|
tags: inputs.tags,
|
|
6447
6621
|
platforms: [{ platform: inputs.platform }]
|
|
6448
6622
|
};
|
|
6623
|
+
if (inputs.source.type === "file") {
|
|
6624
|
+
const content = await readFile(inputs.source.path);
|
|
6625
|
+
const bundlePath = getInstallPath({
|
|
6626
|
+
platform: inputs.platform,
|
|
6627
|
+
type: inputs.ruleType,
|
|
6628
|
+
name: inputs.name,
|
|
6629
|
+
scope: "project"
|
|
6630
|
+
});
|
|
6631
|
+
if (!bundlePath) throw new Error(`Type "${inputs.ruleType}" is not supported for project installs on platform "${inputs.platform}".`);
|
|
6632
|
+
return {
|
|
6633
|
+
name: inputs.name,
|
|
6634
|
+
config: config$1,
|
|
6635
|
+
platformFiles: [{
|
|
6636
|
+
platform: inputs.platform,
|
|
6637
|
+
files: [{
|
|
6638
|
+
path: bundlePath,
|
|
6639
|
+
content
|
|
6640
|
+
}]
|
|
6641
|
+
}]
|
|
6642
|
+
};
|
|
6643
|
+
}
|
|
6644
|
+
const loadedConfig = {
|
|
6645
|
+
configPath: `${inputs.source.path}/agentrules.json`,
|
|
6646
|
+
config: {
|
|
6647
|
+
...config$1,
|
|
6648
|
+
platforms: [{ platform: inputs.platform }]
|
|
6649
|
+
},
|
|
6650
|
+
configDir: inputs.source.path
|
|
6651
|
+
};
|
|
6652
|
+
const platformFiles = await collectPlatformFiles(loadedConfig);
|
|
6449
6653
|
return {
|
|
6450
6654
|
name: inputs.name,
|
|
6451
6655
|
config: config$1,
|
|
6452
|
-
platformFiles
|
|
6453
|
-
platform: inputs.platform,
|
|
6454
|
-
files: [{
|
|
6455
|
-
path: inputs.bundlePath,
|
|
6456
|
-
content
|
|
6457
|
-
}]
|
|
6458
|
-
}]
|
|
6656
|
+
platformFiles
|
|
6459
6657
|
};
|
|
6460
6658
|
}
|
|
6461
6659
|
async function finalizePublish(options) {
|
|
@@ -6640,6 +6838,19 @@ async function discoverRuleDirs(inputDir) {
|
|
|
6640
6838
|
/** Default rule name when none specified */
|
|
6641
6839
|
const DEFAULT_RULE_NAME$1 = "my-rule";
|
|
6642
6840
|
/**
|
|
6841
|
+
* Detect if directory contains SKILL.md and extract frontmatter defaults.
|
|
6842
|
+
*/
|
|
6843
|
+
async function detectSkillDirectory(directory) {
|
|
6844
|
+
const skillPath = join(directory, SKILL_FILENAME);
|
|
6845
|
+
if (!await fileExists(skillPath)) return;
|
|
6846
|
+
const content = await readFile(skillPath, "utf8");
|
|
6847
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
6848
|
+
return {
|
|
6849
|
+
name: frontmatter.name,
|
|
6850
|
+
license: frontmatter.license
|
|
6851
|
+
};
|
|
6852
|
+
}
|
|
6853
|
+
/**
|
|
6643
6854
|
* Initialize a rule in a directory (rule root).
|
|
6644
6855
|
*
|
|
6645
6856
|
* Structure:
|
|
@@ -6650,14 +6861,17 @@ const DEFAULT_RULE_NAME$1 = "my-rule";
|
|
|
6650
6861
|
async function initRule(options) {
|
|
6651
6862
|
const ruleDir = options.directory ?? process.cwd();
|
|
6652
6863
|
log.debug(`Initializing rule in: ${ruleDir}`);
|
|
6864
|
+
const skillInfo = await detectSkillDirectory(ruleDir);
|
|
6865
|
+
const isSkillDirectory = skillInfo !== void 0;
|
|
6653
6866
|
const inferredPlatform = getPlatformFromDir(basename(ruleDir));
|
|
6654
6867
|
const platformInputs = options.platforms ?? (inferredPlatform ? [inferredPlatform] : []);
|
|
6655
6868
|
if (platformInputs.length === 0) throw new Error(`Cannot determine platform. Specify --platform (${PLATFORM_IDS.join(", ")}).`);
|
|
6656
6869
|
const platforms = platformInputs.map(normalizePlatformEntryInput);
|
|
6657
|
-
const name = normalizeName(options.name ?? DEFAULT_RULE_NAME$1);
|
|
6870
|
+
const name = normalizeName(options.name ?? skillInfo?.name ?? DEFAULT_RULE_NAME$1);
|
|
6658
6871
|
const title = options.title ?? toTitleCase(name);
|
|
6659
6872
|
const description = options.description ?? "";
|
|
6660
|
-
const license = options.license ?? "MIT";
|
|
6873
|
+
const license = options.license ?? skillInfo?.license ?? "MIT";
|
|
6874
|
+
const ruleType = isSkillDirectory ? "skill" : options.type;
|
|
6661
6875
|
const platformLabels = platforms.map((p$2) => typeof p$2 === "string" ? p$2 : p$2.platform).join(", ");
|
|
6662
6876
|
log.debug(`Rule name: ${name}, platforms: ${platformLabels}`);
|
|
6663
6877
|
const configPath = join(ruleDir, RULE_CONFIG_FILENAME);
|
|
@@ -6665,7 +6879,7 @@ async function initRule(options) {
|
|
|
6665
6879
|
const config$1 = {
|
|
6666
6880
|
$schema: RULE_SCHEMA_URL,
|
|
6667
6881
|
name,
|
|
6668
|
-
...
|
|
6882
|
+
...ruleType && { type: ruleType },
|
|
6669
6883
|
title,
|
|
6670
6884
|
version: 1,
|
|
6671
6885
|
description,
|
|
@@ -6731,8 +6945,22 @@ function checkTags(value) {
|
|
|
6731
6945
|
async function initInteractive(options) {
|
|
6732
6946
|
const { directory, name: nameOption, title: titleOption, description: descriptionOption, platforms: platformsOption, platformPaths, license: licenseOption } = options;
|
|
6733
6947
|
let { force } = options;
|
|
6734
|
-
const defaultName = nameOption ?? DEFAULT_RULE_NAME;
|
|
6735
6948
|
p.intro("Create a new rule");
|
|
6949
|
+
const skillInfo = await detectSkillDirectory(directory);
|
|
6950
|
+
let useSkillDefaults = false;
|
|
6951
|
+
if (skillInfo) {
|
|
6952
|
+
const confirm = await p.confirm({
|
|
6953
|
+
message: `Detected SKILL.md${skillInfo.name ? ` (${skillInfo.name})` : ""}. Initialize as skill?`,
|
|
6954
|
+
initialValue: true
|
|
6955
|
+
});
|
|
6956
|
+
if (p.isCancel(confirm)) {
|
|
6957
|
+
p.cancel("Cancelled");
|
|
6958
|
+
process.exit(0);
|
|
6959
|
+
}
|
|
6960
|
+
useSkillDefaults = confirm;
|
|
6961
|
+
}
|
|
6962
|
+
const defaultName = useSkillDefaults && skillInfo?.name ? skillInfo.name : nameOption ?? DEFAULT_RULE_NAME;
|
|
6963
|
+
const defaultLicense = useSkillDefaults && skillInfo?.license ? skillInfo.license : licenseOption ?? "MIT";
|
|
6736
6964
|
const validatedPlatforms = [];
|
|
6737
6965
|
if (platformsOption) for (const platform of platformsOption) {
|
|
6738
6966
|
if (!isSupportedPlatform(platform)) {
|
|
@@ -6758,6 +6986,7 @@ async function initInteractive(options) {
|
|
|
6758
6986
|
})();
|
|
6759
6987
|
const platformEntries = await (async () => {
|
|
6760
6988
|
if (selectedPlatforms.length === 0) return [];
|
|
6989
|
+
if (useSkillDefaults) return selectedPlatforms;
|
|
6761
6990
|
const hasCompletePathMapping = selectedPlatforms.every((platform) => {
|
|
6762
6991
|
const value = platformPaths?.[platform];
|
|
6763
6992
|
return typeof value === "string" && value.trim().length > 0;
|
|
@@ -6807,12 +7036,18 @@ async function initInteractive(options) {
|
|
|
6807
7036
|
force = true;
|
|
6808
7037
|
}
|
|
6809
7038
|
const result = await p.group({
|
|
6810
|
-
name: () =>
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
7039
|
+
name: () => {
|
|
7040
|
+
const normalizedDefault = normalizeName(defaultName);
|
|
7041
|
+
return p.text({
|
|
7042
|
+
message: "Rule name",
|
|
7043
|
+
placeholder: normalizedDefault,
|
|
7044
|
+
defaultValue: normalizedDefault,
|
|
7045
|
+
validate: (value) => {
|
|
7046
|
+
if (!value || value.trim() === "") return;
|
|
7047
|
+
return check(nameSchema)(value);
|
|
7048
|
+
}
|
|
7049
|
+
});
|
|
7050
|
+
},
|
|
6816
7051
|
title: ({ results }) => {
|
|
6817
7052
|
const defaultTitle = titleOption ?? toTitleCase(results.name ?? defaultName);
|
|
6818
7053
|
return p.text({
|
|
@@ -6833,7 +7068,6 @@ async function initInteractive(options) {
|
|
|
6833
7068
|
validate: checkTags
|
|
6834
7069
|
}),
|
|
6835
7070
|
license: async () => {
|
|
6836
|
-
const defaultLicense = licenseOption ?? "MIT";
|
|
6837
7071
|
const choice = await p.select({
|
|
6838
7072
|
message: "License",
|
|
6839
7073
|
options: [...COMMON_LICENSES.map((id) => ({
|
|
@@ -6870,6 +7104,7 @@ async function initInteractive(options) {
|
|
|
6870
7104
|
const initOptions = {
|
|
6871
7105
|
directory,
|
|
6872
7106
|
name: result.name,
|
|
7107
|
+
type: useSkillDefaults ? "skill" : void 0,
|
|
6873
7108
|
title: result.title.trim() || void 0,
|
|
6874
7109
|
description: result.description,
|
|
6875
7110
|
tags: parseTags(result.tags),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentrules/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://agentrules.directory",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"clean": "rm -rf node_modules dist .turbo"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@agentrules/core": "0.
|
|
56
|
+
"@agentrules/core": "0.3.0",
|
|
57
57
|
"@clack/prompts": "^0.11.0",
|
|
58
58
|
"chalk": "^5.4.1",
|
|
59
59
|
"commander": "^12.1.0",
|