@agentrules/cli 0.2.1 → 0.3.1
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 +3 -2
- package/dist/index.js +688 -372
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { chmod, constants as constants$1 } from "fs";
|
|
|
9
9
|
import * as client from "openid-client";
|
|
10
10
|
import { promisify } from "util";
|
|
11
11
|
import { exec } from "child_process";
|
|
12
|
+
import * as p$2 from "@clack/prompts";
|
|
12
13
|
import * as p$1 from "@clack/prompts";
|
|
13
14
|
import * as p from "@clack/prompts";
|
|
14
15
|
|
|
@@ -63,6 +64,11 @@ const PLATFORMS = {
|
|
|
63
64
|
description: "Custom tool",
|
|
64
65
|
project: "{platformDir}/tool/{name}.ts",
|
|
65
66
|
global: "{platformDir}/tool/{name}.ts"
|
|
67
|
+
},
|
|
68
|
+
skill: {
|
|
69
|
+
description: "Agent skill",
|
|
70
|
+
project: "{platformDir}/skill/{name}/SKILL.md",
|
|
71
|
+
global: "{platformDir}/skill/{name}/SKILL.md"
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
74
|
},
|
|
@@ -112,6 +118,11 @@ const PLATFORMS = {
|
|
|
112
118
|
description: "Custom slash command",
|
|
113
119
|
project: "{platformDir}/commands/{name}.md",
|
|
114
120
|
global: "{platformDir}/commands/{name}.md"
|
|
121
|
+
},
|
|
122
|
+
skill: {
|
|
123
|
+
description: "Agent skill",
|
|
124
|
+
project: "{platformDir}/skills/{name}/SKILL.md",
|
|
125
|
+
global: "{platformDir}/skills/{name}/SKILL.md"
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
},
|
|
@@ -129,6 +140,11 @@ const PLATFORMS = {
|
|
|
129
140
|
description: "Custom prompt",
|
|
130
141
|
project: null,
|
|
131
142
|
global: "{platformDir}/prompts/{name}.md"
|
|
143
|
+
},
|
|
144
|
+
skill: {
|
|
145
|
+
description: "Agent skill",
|
|
146
|
+
project: "{platformDir}/skills/{name}/SKILL.md",
|
|
147
|
+
global: "{platformDir}/skills/{name}/SKILL.md"
|
|
132
148
|
}
|
|
133
149
|
}
|
|
134
150
|
}
|
|
@@ -265,6 +281,41 @@ function inferTypeFromPath(platform, filePath) {
|
|
|
265
281
|
if (!nextDir) return;
|
|
266
282
|
return getProjectTypeDirMap(platform).get(nextDir);
|
|
267
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Get the install directory for a type (parent directory of the install path).
|
|
286
|
+
* For skills, this is the directory containing SKILL.md.
|
|
287
|
+
*/
|
|
288
|
+
function getInstallDir({ platform, type, name }) {
|
|
289
|
+
const installPath = getInstallPath({
|
|
290
|
+
platform,
|
|
291
|
+
type,
|
|
292
|
+
name,
|
|
293
|
+
scope: "project"
|
|
294
|
+
});
|
|
295
|
+
if (!installPath) return null;
|
|
296
|
+
const lastSlash = installPath.lastIndexOf("/");
|
|
297
|
+
if (lastSlash === -1) return null;
|
|
298
|
+
return installPath.slice(0, lastSlash);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Normalize skill files by finding SKILL.md anchor and adjusting all paths.
|
|
302
|
+
* Strips any existing path prefix to prevent duplication.
|
|
303
|
+
*/
|
|
304
|
+
function normalizeSkillFiles({ files, installDir }) {
|
|
305
|
+
const marker = files.find((f) => f.path === "SKILL.md" || f.path.endsWith("/SKILL.md"));
|
|
306
|
+
if (!marker) throw new Error("SKILL.md not found in files");
|
|
307
|
+
const skillRoot = marker.path === "SKILL.md" ? "." : marker.path.slice(0, marker.path.lastIndexOf("/"));
|
|
308
|
+
return files.map((f) => {
|
|
309
|
+
let relative$1;
|
|
310
|
+
if (skillRoot === ".") relative$1 = f.path;
|
|
311
|
+
else if (f.path.startsWith(`${skillRoot}/`)) relative$1 = f.path.slice(skillRoot.length + 1);
|
|
312
|
+
else relative$1 = f.path;
|
|
313
|
+
return {
|
|
314
|
+
...f,
|
|
315
|
+
path: `${installDir}/${relative$1}`
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
}
|
|
268
319
|
|
|
269
320
|
//#endregion
|
|
270
321
|
//#region ../core/src/utils/encoding.ts
|
|
@@ -1939,7 +1990,7 @@ const $ZodUnion = /* @__PURE__ */ $constructor("$ZodUnion", (inst, def) => {
|
|
|
1939
1990
|
defineLazy(inst._zod, "pattern", () => {
|
|
1940
1991
|
if (def.options.every((o) => o._zod.pattern)) {
|
|
1941
1992
|
const patterns = def.options.map((o) => o._zod.pattern);
|
|
1942
|
-
return new RegExp(`^(${patterns.map((p$
|
|
1993
|
+
return new RegExp(`^(${patterns.map((p$3) => cleanRegex(p$3.source)).join("|")})$`);
|
|
1943
1994
|
}
|
|
1944
1995
|
return void 0;
|
|
1945
1996
|
});
|
|
@@ -2318,9 +2369,9 @@ var $ZodRegistry = class {
|
|
|
2318
2369
|
return this;
|
|
2319
2370
|
}
|
|
2320
2371
|
get(schema) {
|
|
2321
|
-
const p$
|
|
2322
|
-
if (p$
|
|
2323
|
-
const pm = { ...this.get(p$
|
|
2372
|
+
const p$3 = schema._zod.parent;
|
|
2373
|
+
if (p$3) {
|
|
2374
|
+
const pm = { ...this.get(p$3) ?? {} };
|
|
2324
2375
|
delete pm.id;
|
|
2325
2376
|
const f = {
|
|
2326
2377
|
...pm,
|
|
@@ -4325,8 +4376,8 @@ function command(cmd) {
|
|
|
4325
4376
|
return theme.command(cmd);
|
|
4326
4377
|
}
|
|
4327
4378
|
/** Format a file path */
|
|
4328
|
-
function path(p$
|
|
4329
|
-
return theme.path(p$
|
|
4379
|
+
function path(p$3) {
|
|
4380
|
+
return theme.path(p$3);
|
|
4330
4381
|
}
|
|
4331
4382
|
/** Format as muted/secondary text */
|
|
4332
4383
|
function muted(text) {
|
|
@@ -4644,6 +4695,22 @@ function fileTree(files, options) {
|
|
|
4644
4695
|
});
|
|
4645
4696
|
return lines.join("\n");
|
|
4646
4697
|
}
|
|
4698
|
+
function rulePreview(options) {
|
|
4699
|
+
const lines = [];
|
|
4700
|
+
lines.push(header(options.header));
|
|
4701
|
+
if (options.path) lines.push(keyValue(options.pathLabel ?? "Path", path(options.path)));
|
|
4702
|
+
lines.push(keyValue("Name", code(options.name)));
|
|
4703
|
+
lines.push(keyValue("Title", options.title));
|
|
4704
|
+
lines.push(keyValue("Description", options.description || dim("—")));
|
|
4705
|
+
lines.push(keyValue("Platforms", options.platforms.join(", ")));
|
|
4706
|
+
if (options.type) lines.push(keyValue("Type", options.type));
|
|
4707
|
+
if (options.tags && options.tags.length > 0) lines.push(keyValue("Tags", options.tags.join(", ")));
|
|
4708
|
+
else if (options.showHints) lines.push(keyValue("Tags", dim("— (add to improve discoverability)")));
|
|
4709
|
+
if (options.features && options.features.length > 0) lines.push(keyValue("Features", options.features.join(", ")));
|
|
4710
|
+
else if (options.showHints) lines.push(keyValue("Features", dim("— (highlight what this rule does)")));
|
|
4711
|
+
if (options.license) lines.push(keyValue("License", options.license));
|
|
4712
|
+
return lines.join("\n");
|
|
4713
|
+
}
|
|
4647
4714
|
const ui = {
|
|
4648
4715
|
theme,
|
|
4649
4716
|
symbols,
|
|
@@ -4680,6 +4747,7 @@ const ui = {
|
|
|
4680
4747
|
fileCounts,
|
|
4681
4748
|
hint,
|
|
4682
4749
|
link,
|
|
4750
|
+
rulePreview,
|
|
4683
4751
|
stripAnsi,
|
|
4684
4752
|
truncate,
|
|
4685
4753
|
formatBytes: formatBytes$1,
|
|
@@ -5832,11 +5900,43 @@ async function directoryExists(path$1) {
|
|
|
5832
5900
|
}
|
|
5833
5901
|
}
|
|
5834
5902
|
|
|
5903
|
+
//#endregion
|
|
5904
|
+
//#region src/lib/zod-validator.ts
|
|
5905
|
+
/**
|
|
5906
|
+
* Creates a validator function from a Zod schema.
|
|
5907
|
+
* Returns error message if invalid, undefined if valid.
|
|
5908
|
+
*/
|
|
5909
|
+
function check(schema) {
|
|
5910
|
+
return (value) => {
|
|
5911
|
+
const result = schema.safeParse(value);
|
|
5912
|
+
if (!result.success) return result.error.issues[0]?.message;
|
|
5913
|
+
return;
|
|
5914
|
+
};
|
|
5915
|
+
}
|
|
5916
|
+
|
|
5835
5917
|
//#endregion
|
|
5836
5918
|
//#region src/lib/rule-utils.ts
|
|
5837
|
-
const
|
|
5838
|
-
|
|
5839
|
-
|
|
5919
|
+
const SKILL_FILENAME = "SKILL.md";
|
|
5920
|
+
/**
|
|
5921
|
+
* Parse SKILL.md frontmatter for name and license.
|
|
5922
|
+
* Only extracts simple key: value pairs we need for quick publish defaults.
|
|
5923
|
+
*/
|
|
5924
|
+
function parseSkillFrontmatter(content) {
|
|
5925
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
5926
|
+
if (!match?.[1]) return {};
|
|
5927
|
+
const frontmatter = match[1];
|
|
5928
|
+
const result = {};
|
|
5929
|
+
const nameMatch = frontmatter.match(/^name:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
5930
|
+
if (nameMatch?.[1]) result.name = nameMatch[1].trim();
|
|
5931
|
+
const licenseMatch = frontmatter.match(/^license:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
5932
|
+
if (licenseMatch?.[1]) result.license = licenseMatch[1].trim();
|
|
5933
|
+
return result;
|
|
5934
|
+
}
|
|
5935
|
+
const METADATA_FILES = {
|
|
5936
|
+
install: ["INSTALL.txt"],
|
|
5937
|
+
readme: ["README.md"],
|
|
5938
|
+
license: ["LICENSE.md", "LICENSE.txt"]
|
|
5939
|
+
};
|
|
5840
5940
|
/**
|
|
5841
5941
|
* Files/directories that are always excluded from rules.
|
|
5842
5942
|
* These are never useful in a rule bundle.
|
|
@@ -5937,7 +6037,7 @@ async function loadConfig(inputPath, overrides) {
|
|
|
5937
6037
|
platforms
|
|
5938
6038
|
};
|
|
5939
6039
|
const configDir = dirname(configPath);
|
|
5940
|
-
const platformNames = platforms.map((
|
|
6040
|
+
const platformNames = platforms.map((plat) => plat.platform).join(", ");
|
|
5941
6041
|
log.debug(`Loaded config: ${config$1.name}, platforms: ${platformNames}`);
|
|
5942
6042
|
return {
|
|
5943
6043
|
configPath,
|
|
@@ -5970,9 +6070,9 @@ async function loadRule(ruleDir, overrides) {
|
|
|
5970
6070
|
}
|
|
5971
6071
|
async function collectMetadata(loaded) {
|
|
5972
6072
|
const { configDir } = loaded;
|
|
5973
|
-
const installMessage = await
|
|
5974
|
-
const readmeContent = await
|
|
5975
|
-
const licenseContent = await
|
|
6073
|
+
const installMessage = await readFirstMatch(configDir, METADATA_FILES.install);
|
|
6074
|
+
const readmeContent = await readFirstMatch(configDir, METADATA_FILES.readme);
|
|
6075
|
+
const licenseContent = await readFirstMatch(configDir, METADATA_FILES.license);
|
|
5976
6076
|
return {
|
|
5977
6077
|
installMessage,
|
|
5978
6078
|
readmeContent,
|
|
@@ -5990,6 +6090,34 @@ async function collectPlatformFiles(loaded) {
|
|
|
5990
6090
|
const resolvedSourcePath = sourcePath ?? ".";
|
|
5991
6091
|
const filesDir = join(configDir, resolvedSourcePath);
|
|
5992
6092
|
log.debug(`Files for ${platform}: source=${resolvedSourcePath}, dir=${filesDir}`);
|
|
6093
|
+
const filesDirExists = await directoryExists(filesDir);
|
|
6094
|
+
const rootExclude = [RULE_CONFIG_FILENAME];
|
|
6095
|
+
if (filesDir === configDir) rootExclude.push(...METADATA_FILES.readme, ...METADATA_FILES.license, ...METADATA_FILES.install);
|
|
6096
|
+
const collectedFiles = filesDirExists ? await collectFiles(filesDir, rootExclude, ignorePatterns) : [];
|
|
6097
|
+
if (collectedFiles.length === 0) {
|
|
6098
|
+
if (!filesDirExists) throw new Error(`Files directory not found: ${filesDir}. Create the directory or set "path" in the platform entry.`);
|
|
6099
|
+
throw new Error(`No files found in ${filesDir}. Rules must include at least one file.`);
|
|
6100
|
+
}
|
|
6101
|
+
if (config$1.type === "skill") {
|
|
6102
|
+
const installDir = getInstallDir({
|
|
6103
|
+
platform,
|
|
6104
|
+
type: "skill",
|
|
6105
|
+
name: config$1.name
|
|
6106
|
+
});
|
|
6107
|
+
if (!installDir) throw new Error(`Platform "${platform}" does not support skill type.`);
|
|
6108
|
+
const normalizedFiles = normalizeSkillFiles({
|
|
6109
|
+
files: collectedFiles,
|
|
6110
|
+
installDir
|
|
6111
|
+
});
|
|
6112
|
+
platformFiles.push({
|
|
6113
|
+
platform,
|
|
6114
|
+
files: normalizedFiles.map((f) => ({
|
|
6115
|
+
path: f.path,
|
|
6116
|
+
content: typeof f.content === "string" ? f.content : new TextDecoder().decode(f.content)
|
|
6117
|
+
}))
|
|
6118
|
+
});
|
|
6119
|
+
continue;
|
|
6120
|
+
}
|
|
5993
6121
|
const treatInstructionAsRoot = config$1.type === void 0 || config$1.type === "instruction";
|
|
5994
6122
|
const instructionProjectPath = treatInstructionAsRoot ? getInstallPath({
|
|
5995
6123
|
platform,
|
|
@@ -5997,10 +6125,6 @@ async function collectPlatformFiles(loaded) {
|
|
|
5997
6125
|
scope: "project"
|
|
5998
6126
|
}) : null;
|
|
5999
6127
|
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
6128
|
const publishFiles = [];
|
|
6005
6129
|
const seenPublishPaths = new Set();
|
|
6006
6130
|
for (const file of collectedFiles) {
|
|
@@ -6020,10 +6144,7 @@ async function collectPlatformFiles(loaded) {
|
|
|
6020
6144
|
content: instructionContent
|
|
6021
6145
|
});
|
|
6022
6146
|
}
|
|
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
|
-
}
|
|
6147
|
+
if (publishFiles.length === 0) throw new Error(`No files found in ${filesDir}. Rules must include at least one file.`);
|
|
6027
6148
|
platformFiles.push({
|
|
6028
6149
|
platform,
|
|
6029
6150
|
files: publishFiles
|
|
@@ -6092,18 +6213,280 @@ async function readFileIfExists(path$1) {
|
|
|
6092
6213
|
if (await fileExists(path$1)) return await readFile(path$1, "utf8");
|
|
6093
6214
|
return;
|
|
6094
6215
|
}
|
|
6216
|
+
/**
|
|
6217
|
+
* Try reading files from a list of candidates, return first match.
|
|
6218
|
+
*/
|
|
6219
|
+
async function readFirstMatch(dir, filenames) {
|
|
6220
|
+
for (const filename of filenames) {
|
|
6221
|
+
const content = await readFileIfExists(join(dir, filename));
|
|
6222
|
+
if (content !== void 0) return content;
|
|
6223
|
+
}
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6226
|
+
/**
|
|
6227
|
+
* Detect if directory contains SKILL.md and extract frontmatter defaults.
|
|
6228
|
+
*/
|
|
6229
|
+
async function detectSkillDirectory$1(directory) {
|
|
6230
|
+
const skillPath = join(directory, SKILL_FILENAME);
|
|
6231
|
+
if (!await fileExists(skillPath)) return;
|
|
6232
|
+
const content = await readFile(skillPath, "utf8");
|
|
6233
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
6234
|
+
return {
|
|
6235
|
+
name: frontmatter.name,
|
|
6236
|
+
license: frontmatter.license
|
|
6237
|
+
};
|
|
6238
|
+
}
|
|
6239
|
+
function parseTags(input) {
|
|
6240
|
+
if (typeof input !== "string") return [];
|
|
6241
|
+
if (input.trim().length === 0) return [];
|
|
6242
|
+
return input.split(",").map((tag) => tag.trim().toLowerCase()).filter((tag) => tag.length > 0);
|
|
6243
|
+
}
|
|
6244
|
+
function checkTags(value) {
|
|
6245
|
+
const tags = parseTags(value);
|
|
6246
|
+
const result = tagsSchema.safeParse(tags);
|
|
6247
|
+
if (!result.success) return result.error.issues[0]?.message;
|
|
6248
|
+
}
|
|
6249
|
+
/**
|
|
6250
|
+
* Collect rule inputs via interactive prompts or defaults.
|
|
6251
|
+
*
|
|
6252
|
+
* Handles:
|
|
6253
|
+
* - Skill detection with SKILL.md frontmatter
|
|
6254
|
+
* - Platform multiselect with keyboard hints
|
|
6255
|
+
* - Per-platform path prompting (for non-skill multi-platform)
|
|
6256
|
+
* - Name, title, description, tags, license prompts
|
|
6257
|
+
*/
|
|
6258
|
+
async function collectRuleInputs(options) {
|
|
6259
|
+
const { directory, defaults = {}, nonInteractive = false } = options;
|
|
6260
|
+
const skillInfo = await detectSkillDirectory$1(directory);
|
|
6261
|
+
let isSkill = false;
|
|
6262
|
+
if (skillInfo && !nonInteractive) {
|
|
6263
|
+
const confirm = await p$2.confirm({
|
|
6264
|
+
message: `Detected SKILL.md${skillInfo.name ? ` (${skillInfo.name})` : ""}. Initialize as skill?`,
|
|
6265
|
+
initialValue: true
|
|
6266
|
+
});
|
|
6267
|
+
if (p$2.isCancel(confirm)) throw new Error("Cancelled");
|
|
6268
|
+
isSkill = confirm;
|
|
6269
|
+
} else if (skillInfo && nonInteractive) isSkill = true;
|
|
6270
|
+
const defaultName = isSkill && skillInfo?.name ? normalizeName(skillInfo.name) : defaults.name ?? "my-rule";
|
|
6271
|
+
const defaultLicense = isSkill && skillInfo?.license ? skillInfo.license : defaults.license ?? "MIT";
|
|
6272
|
+
const validatedPlatforms = [];
|
|
6273
|
+
if (defaults.platforms) for (const platform of defaults.platforms) {
|
|
6274
|
+
if (!isSupportedPlatform(platform)) throw new Error(`Unknown platform "${platform}"`);
|
|
6275
|
+
validatedPlatforms.push(platform);
|
|
6276
|
+
}
|
|
6277
|
+
const selectedPlatforms = validatedPlatforms.length > 0 ? validatedPlatforms : await (async () => {
|
|
6278
|
+
if (nonInteractive) throw new Error("Missing --platform in non-interactive mode");
|
|
6279
|
+
const platformChoices = await p$2.multiselect({
|
|
6280
|
+
message: `Platforms ${ui.dim("(space to toggle, 'a' to select all)")}`,
|
|
6281
|
+
options: PLATFORM_IDS.map((id) => ({
|
|
6282
|
+
value: id,
|
|
6283
|
+
label: id
|
|
6284
|
+
})),
|
|
6285
|
+
required: true
|
|
6286
|
+
});
|
|
6287
|
+
if (p$2.isCancel(platformChoices)) throw new Error("Cancelled");
|
|
6288
|
+
return platformChoices;
|
|
6289
|
+
})();
|
|
6290
|
+
const platformPaths = {};
|
|
6291
|
+
if (selectedPlatforms.length > 1 && !isSkill && !nonInteractive) {
|
|
6292
|
+
const hasCompletePathMapping = selectedPlatforms.every((platform) => {
|
|
6293
|
+
const value = defaults.platformPaths?.[platform];
|
|
6294
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
6295
|
+
});
|
|
6296
|
+
if (hasCompletePathMapping && defaults.platformPaths) for (const platform of selectedPlatforms) {
|
|
6297
|
+
const pathVal = defaults.platformPaths[platform]?.trim();
|
|
6298
|
+
if (pathVal && pathVal !== ".") platformPaths[platform] = pathVal;
|
|
6299
|
+
}
|
|
6300
|
+
else for (const platform of selectedPlatforms) {
|
|
6301
|
+
const mappedPath = defaults.platformPaths?.[platform]?.trim();
|
|
6302
|
+
const suggestedPath = mappedPath ?? (await directoryExists(join(directory, platform)) ? platform : ".");
|
|
6303
|
+
const input = await p$2.text({
|
|
6304
|
+
message: `Folder for ${platform} files ('.' = same folder as agentrules.json)`,
|
|
6305
|
+
placeholder: suggestedPath,
|
|
6306
|
+
defaultValue: suggestedPath
|
|
6307
|
+
});
|
|
6308
|
+
if (p$2.isCancel(input)) throw new Error("Cancelled");
|
|
6309
|
+
const trimmed = input.trim();
|
|
6310
|
+
const resolvedPath = trimmed.length > 0 ? trimmed : suggestedPath;
|
|
6311
|
+
if (resolvedPath !== ".") platformPaths[platform] = resolvedPath;
|
|
6312
|
+
}
|
|
6313
|
+
} else if (defaults.platformPaths) for (const platform of selectedPlatforms) {
|
|
6314
|
+
const pathVal = defaults.platformPaths[platform]?.trim();
|
|
6315
|
+
if (pathVal && pathVal !== ".") platformPaths[platform] = pathVal;
|
|
6316
|
+
}
|
|
6317
|
+
if (nonInteractive) {
|
|
6318
|
+
const name = normalizeName(defaults.name ?? defaultName);
|
|
6319
|
+
const nameCheck = nameSchema.safeParse(name);
|
|
6320
|
+
if (!nameCheck.success) throw new Error(nameCheck.error.issues[0]?.message ?? "Invalid name");
|
|
6321
|
+
return {
|
|
6322
|
+
platforms: selectedPlatforms,
|
|
6323
|
+
platformPaths,
|
|
6324
|
+
name,
|
|
6325
|
+
title: defaults.title ?? toTitleCase(name),
|
|
6326
|
+
description: defaults.description ?? "",
|
|
6327
|
+
tags: defaults.tags ?? [],
|
|
6328
|
+
license: defaultLicense,
|
|
6329
|
+
isSkill,
|
|
6330
|
+
ruleType: isSkill ? "skill" : defaults.ruleType
|
|
6331
|
+
};
|
|
6332
|
+
}
|
|
6333
|
+
const result = await p$2.group({
|
|
6334
|
+
name: () => {
|
|
6335
|
+
const normalizedDefault = normalizeName(defaultName);
|
|
6336
|
+
return p$2.text({
|
|
6337
|
+
message: "Rule name",
|
|
6338
|
+
placeholder: normalizedDefault,
|
|
6339
|
+
defaultValue: normalizedDefault,
|
|
6340
|
+
validate: (value) => {
|
|
6341
|
+
if (!value || value.trim() === "") return;
|
|
6342
|
+
return check(nameSchema)(value);
|
|
6343
|
+
}
|
|
6344
|
+
});
|
|
6345
|
+
},
|
|
6346
|
+
title: ({ results }) => {
|
|
6347
|
+
const defaultTitle = defaults.title ?? toTitleCase(results.name ?? defaultName);
|
|
6348
|
+
return p$2.text({
|
|
6349
|
+
message: "Title",
|
|
6350
|
+
defaultValue: defaultTitle,
|
|
6351
|
+
placeholder: defaultTitle
|
|
6352
|
+
});
|
|
6353
|
+
},
|
|
6354
|
+
description: () => p$2.text({
|
|
6355
|
+
message: "Description",
|
|
6356
|
+
placeholder: "Describe what this rule does...",
|
|
6357
|
+
defaultValue: defaults.description,
|
|
6358
|
+
validate: check(descriptionSchema)
|
|
6359
|
+
}),
|
|
6360
|
+
tags: () => p$2.text({
|
|
6361
|
+
message: "Tags (comma-separated, optional)",
|
|
6362
|
+
placeholder: "e.g., typescript, testing, react",
|
|
6363
|
+
validate: checkTags
|
|
6364
|
+
}),
|
|
6365
|
+
license: async () => {
|
|
6366
|
+
const choice = await p$2.select({
|
|
6367
|
+
message: "License",
|
|
6368
|
+
options: [...COMMON_LICENSES.map((id) => ({
|
|
6369
|
+
value: id,
|
|
6370
|
+
label: id
|
|
6371
|
+
})), {
|
|
6372
|
+
value: "__other__",
|
|
6373
|
+
label: "Other (enter SPDX identifier)"
|
|
6374
|
+
}],
|
|
6375
|
+
initialValue: defaultLicense
|
|
6376
|
+
});
|
|
6377
|
+
if (p$2.isCancel(choice)) throw new Error("Cancelled");
|
|
6378
|
+
if (choice === "__other__") {
|
|
6379
|
+
const custom = await p$2.text({
|
|
6380
|
+
message: "License (SPDX identifier)",
|
|
6381
|
+
placeholder: "e.g., MPL-2.0, AGPL-3.0-only",
|
|
6382
|
+
validate: check(licenseSchema)
|
|
6383
|
+
});
|
|
6384
|
+
if (p$2.isCancel(custom)) throw new Error("Cancelled");
|
|
6385
|
+
return custom;
|
|
6386
|
+
}
|
|
6387
|
+
return choice;
|
|
6388
|
+
}
|
|
6389
|
+
}, { onCancel: () => {
|
|
6390
|
+
throw new Error("Cancelled");
|
|
6391
|
+
} });
|
|
6392
|
+
const tags = parseTags(result.tags);
|
|
6393
|
+
return {
|
|
6394
|
+
platforms: selectedPlatforms,
|
|
6395
|
+
platformPaths,
|
|
6396
|
+
name: result.name,
|
|
6397
|
+
title: result.title.trim() || toTitleCase(result.name),
|
|
6398
|
+
description: result.description ?? "",
|
|
6399
|
+
tags,
|
|
6400
|
+
license: result.license,
|
|
6401
|
+
isSkill,
|
|
6402
|
+
ruleType: isSkill ? "skill" : defaults.ruleType
|
|
6403
|
+
};
|
|
6404
|
+
}
|
|
6095
6405
|
|
|
6096
6406
|
//#endregion
|
|
6097
|
-
//#region src/
|
|
6407
|
+
//#region src/commands/rule/init.ts
|
|
6408
|
+
/** Default rule name when none specified */
|
|
6409
|
+
const DEFAULT_RULE_NAME = "my-rule";
|
|
6098
6410
|
/**
|
|
6099
|
-
*
|
|
6100
|
-
* Returns error message if invalid, undefined if valid.
|
|
6411
|
+
* Detect if directory contains SKILL.md and extract frontmatter defaults.
|
|
6101
6412
|
*/
|
|
6102
|
-
function
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6413
|
+
async function detectSkillDirectory(directory) {
|
|
6414
|
+
const skillPath = join(directory, SKILL_FILENAME);
|
|
6415
|
+
if (!await fileExists(skillPath)) return;
|
|
6416
|
+
const content = await readFile(skillPath, "utf8");
|
|
6417
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
6418
|
+
return {
|
|
6419
|
+
name: frontmatter.name,
|
|
6420
|
+
license: frontmatter.license
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
/**
|
|
6424
|
+
* Initialize a rule in a directory (rule root).
|
|
6425
|
+
*
|
|
6426
|
+
* Structure:
|
|
6427
|
+
* - ruleDir/agentrules.json - rule config
|
|
6428
|
+
* - ruleDir/* - rule files (collected by default)
|
|
6429
|
+
* - ruleDir/README.md, ruleDir/LICENSE.md, ruleDir/INSTALL.txt - optional metadata (not bundled)
|
|
6430
|
+
*/
|
|
6431
|
+
async function initRule(options) {
|
|
6432
|
+
const ruleDir = options.directory ?? process.cwd();
|
|
6433
|
+
log.debug(`Initializing rule in: ${ruleDir}`);
|
|
6434
|
+
const skillInfo = await detectSkillDirectory(ruleDir);
|
|
6435
|
+
const isSkillDirectory = skillInfo !== void 0;
|
|
6436
|
+
const inferredPlatform = getPlatformFromDir(basename(ruleDir));
|
|
6437
|
+
const platformInputs = options.platforms ?? (inferredPlatform ? [inferredPlatform] : []);
|
|
6438
|
+
if (platformInputs.length === 0) throw new Error(`Cannot determine platform. Specify --platform (${PLATFORM_IDS.join(", ")}).`);
|
|
6439
|
+
const platforms = platformInputs.map(normalizePlatformEntryInput);
|
|
6440
|
+
const name = normalizeName(options.name ?? skillInfo?.name ?? DEFAULT_RULE_NAME);
|
|
6441
|
+
const title = options.title ?? toTitleCase(name);
|
|
6442
|
+
const description = options.description ?? "";
|
|
6443
|
+
const license = options.license ?? skillInfo?.license ?? "MIT";
|
|
6444
|
+
const ruleType = isSkillDirectory ? "skill" : options.type;
|
|
6445
|
+
const platformLabels = platforms.map((p$3) => typeof p$3 === "string" ? p$3 : p$3.platform).join(", ");
|
|
6446
|
+
log.debug(`Rule name: ${name}, platforms: ${platformLabels}`);
|
|
6447
|
+
const configPath = join(ruleDir, RULE_CONFIG_FILENAME);
|
|
6448
|
+
if (!options.force && await fileExists(configPath)) throw new Error(`${RULE_CONFIG_FILENAME} already exists. Use --force to overwrite.`);
|
|
6449
|
+
const config$1 = {
|
|
6450
|
+
$schema: RULE_SCHEMA_URL,
|
|
6451
|
+
name,
|
|
6452
|
+
...ruleType && { type: ruleType },
|
|
6453
|
+
title,
|
|
6454
|
+
version: 1,
|
|
6455
|
+
description,
|
|
6456
|
+
tags: options.tags ?? [],
|
|
6457
|
+
license,
|
|
6458
|
+
platforms
|
|
6459
|
+
};
|
|
6460
|
+
let createdDir;
|
|
6461
|
+
if (await directoryExists(ruleDir)) log.debug(`Directory exists: ${ruleDir}`);
|
|
6462
|
+
else {
|
|
6463
|
+
await mkdir(ruleDir, { recursive: true });
|
|
6464
|
+
createdDir = ruleDir;
|
|
6465
|
+
log.debug(`Created directory: ${ruleDir}`);
|
|
6466
|
+
}
|
|
6467
|
+
const content = `${JSON.stringify(config$1, null, 2)}\n`;
|
|
6468
|
+
await writeFile(configPath, content, "utf8");
|
|
6469
|
+
log.debug(`Wrote config file: ${configPath}`);
|
|
6470
|
+
log.debug("Rule initialization complete.");
|
|
6471
|
+
return {
|
|
6472
|
+
configPath,
|
|
6473
|
+
rule: config$1,
|
|
6474
|
+
createdDir
|
|
6475
|
+
};
|
|
6476
|
+
}
|
|
6477
|
+
function normalizePlatform(input) {
|
|
6478
|
+
const normalized = input.toLowerCase();
|
|
6479
|
+
if (!isSupportedPlatform(normalized)) throw new Error(`Unknown platform "${input}". Supported: ${PLATFORM_IDS.join(", ")}`);
|
|
6480
|
+
return normalized;
|
|
6481
|
+
}
|
|
6482
|
+
function normalizePlatformEntryInput(input) {
|
|
6483
|
+
if (typeof input === "string") return normalizePlatform(input);
|
|
6484
|
+
const platform = normalizePlatform(input.platform);
|
|
6485
|
+
const path$1 = typeof input.path === "string" ? input.path.trim() : "";
|
|
6486
|
+
if (path$1.length === 0 || path$1 === ".") return platform;
|
|
6487
|
+
return {
|
|
6488
|
+
platform,
|
|
6489
|
+
path: path$1
|
|
6107
6490
|
};
|
|
6108
6491
|
}
|
|
6109
6492
|
|
|
@@ -6111,14 +6494,46 @@ function check(schema) {
|
|
|
6111
6494
|
//#region src/commands/publish.ts
|
|
6112
6495
|
/** Maximum size per variant/platform bundle in bytes (1MB) */
|
|
6113
6496
|
const MAX_VARIANT_SIZE_BYTES = 1 * 1024 * 1024;
|
|
6114
|
-
/** Schema for parsing comma-separated tags input */
|
|
6115
|
-
const tagsInputSchema = string().transform((input) => input.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0)).pipe(tagsSchema);
|
|
6116
6497
|
function formatBytes(bytes) {
|
|
6117
6498
|
if (bytes < 1024) return `${bytes} B`;
|
|
6118
6499
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
6119
6500
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
6120
6501
|
}
|
|
6121
6502
|
/**
|
|
6503
|
+
* Prompts to create agentrules.json after quick publish (or dry run).
|
|
6504
|
+
* With --yes, creates without prompting.
|
|
6505
|
+
*/
|
|
6506
|
+
async function promptToCreateConfig(quickPublish, yes) {
|
|
6507
|
+
if (!quickPublish) return false;
|
|
6508
|
+
let createConfig = yes ?? false;
|
|
6509
|
+
if (!yes && process.stdin.isTTY) {
|
|
6510
|
+
log.print("");
|
|
6511
|
+
const answer = await p$1.confirm({
|
|
6512
|
+
message: "Create agentrules.json for future publishes?",
|
|
6513
|
+
initialValue: true
|
|
6514
|
+
});
|
|
6515
|
+
createConfig = !p$1.isCancel(answer) && answer;
|
|
6516
|
+
}
|
|
6517
|
+
if (createConfig) {
|
|
6518
|
+
const sourceDir = quickPublish.source.type === "directory" ? quickPublish.source.path : void 0;
|
|
6519
|
+
const configDir = sourceDir ?? (quickPublish.source.path.replace(/[/\\][^/\\]+$/, "") || ".");
|
|
6520
|
+
await initRule({
|
|
6521
|
+
directory: configDir,
|
|
6522
|
+
name: quickPublish.name,
|
|
6523
|
+
title: quickPublish.title,
|
|
6524
|
+
description: quickPublish.description || void 0,
|
|
6525
|
+
platforms: quickPublish.platforms,
|
|
6526
|
+
type: quickPublish.ruleType,
|
|
6527
|
+
tags: quickPublish.tags,
|
|
6528
|
+
license: quickPublish.license,
|
|
6529
|
+
force: false
|
|
6530
|
+
});
|
|
6531
|
+
log.success(`Created ${ui.path(join(configDir, "agentrules.json"))}`);
|
|
6532
|
+
return true;
|
|
6533
|
+
}
|
|
6534
|
+
return false;
|
|
6535
|
+
}
|
|
6536
|
+
/**
|
|
6122
6537
|
* Publishes a rule to the registry.
|
|
6123
6538
|
*
|
|
6124
6539
|
* Supports:
|
|
@@ -6139,11 +6554,12 @@ async function publish(options = {}) {
|
|
|
6139
6554
|
}
|
|
6140
6555
|
if (!dryRun) log.debug(`Authenticated as user, publishing to ${ctx.registry.url}`);
|
|
6141
6556
|
const filePath = await getSingleFilePath(path$1);
|
|
6557
|
+
const quickDir = await getQuickPublishDirectory(path$1);
|
|
6142
6558
|
let publishInput;
|
|
6143
6559
|
if (filePath) {
|
|
6144
6560
|
let resolved;
|
|
6145
6561
|
try {
|
|
6146
|
-
resolved = await
|
|
6562
|
+
resolved = await resolveQuickPublishInputs({
|
|
6147
6563
|
name,
|
|
6148
6564
|
platform,
|
|
6149
6565
|
type,
|
|
@@ -6151,7 +6567,10 @@ async function publish(options = {}) {
|
|
|
6151
6567
|
description,
|
|
6152
6568
|
tags,
|
|
6153
6569
|
license
|
|
6154
|
-
},
|
|
6570
|
+
}, {
|
|
6571
|
+
type: "file",
|
|
6572
|
+
path: filePath
|
|
6573
|
+
}, {
|
|
6155
6574
|
dryRun,
|
|
6156
6575
|
yes
|
|
6157
6576
|
});
|
|
@@ -6166,7 +6585,7 @@ async function publish(options = {}) {
|
|
|
6166
6585
|
const fileSpinner = await log.spinner("Building bundle...");
|
|
6167
6586
|
try {
|
|
6168
6587
|
publishInput = await buildPublishInput({
|
|
6169
|
-
rule: await
|
|
6588
|
+
rule: await buildRuleInputFromQuickPublish(resolved),
|
|
6170
6589
|
version: version$2
|
|
6171
6590
|
});
|
|
6172
6591
|
log.debug(`Built publish input for platforms: ${publishInput.variants.map((v) => v.platform).join(", ")}`);
|
|
@@ -6184,7 +6603,61 @@ async function publish(options = {}) {
|
|
|
6184
6603
|
dryRun,
|
|
6185
6604
|
version: version$2,
|
|
6186
6605
|
spinner: fileSpinner,
|
|
6187
|
-
ctx
|
|
6606
|
+
ctx,
|
|
6607
|
+
quickPublish: resolved,
|
|
6608
|
+
yes
|
|
6609
|
+
});
|
|
6610
|
+
}
|
|
6611
|
+
if (quickDir) {
|
|
6612
|
+
let resolved;
|
|
6613
|
+
try {
|
|
6614
|
+
resolved = await resolveQuickPublishInputs({
|
|
6615
|
+
name,
|
|
6616
|
+
platform,
|
|
6617
|
+
type,
|
|
6618
|
+
title,
|
|
6619
|
+
description,
|
|
6620
|
+
tags,
|
|
6621
|
+
license
|
|
6622
|
+
}, {
|
|
6623
|
+
type: "directory",
|
|
6624
|
+
path: quickDir
|
|
6625
|
+
}, {
|
|
6626
|
+
dryRun,
|
|
6627
|
+
yes
|
|
6628
|
+
});
|
|
6629
|
+
} catch (error$2) {
|
|
6630
|
+
const message = getErrorMessage(error$2);
|
|
6631
|
+
log.error(message);
|
|
6632
|
+
return {
|
|
6633
|
+
success: false,
|
|
6634
|
+
error: message
|
|
6635
|
+
};
|
|
6636
|
+
}
|
|
6637
|
+
const dirSpinner = await log.spinner("Building bundle...");
|
|
6638
|
+
try {
|
|
6639
|
+
publishInput = await buildPublishInput({
|
|
6640
|
+
rule: await buildRuleInputFromQuickPublish(resolved),
|
|
6641
|
+
version: version$2
|
|
6642
|
+
});
|
|
6643
|
+
log.debug(`Built publish input for platforms: ${publishInput.variants.map((v) => v.platform).join(", ")}`);
|
|
6644
|
+
} catch (error$2) {
|
|
6645
|
+
const message = getErrorMessage(error$2);
|
|
6646
|
+
dirSpinner.fail("Failed to build bundle");
|
|
6647
|
+
log.error(message);
|
|
6648
|
+
return {
|
|
6649
|
+
success: false,
|
|
6650
|
+
error: message
|
|
6651
|
+
};
|
|
6652
|
+
}
|
|
6653
|
+
return await finalizePublish({
|
|
6654
|
+
publishInput,
|
|
6655
|
+
dryRun,
|
|
6656
|
+
version: version$2,
|
|
6657
|
+
spinner: dirSpinner,
|
|
6658
|
+
ctx,
|
|
6659
|
+
quickPublish: resolved,
|
|
6660
|
+
yes
|
|
6188
6661
|
});
|
|
6189
6662
|
}
|
|
6190
6663
|
const spinner$1 = await log.spinner("Validating rule...");
|
|
@@ -6275,19 +6748,27 @@ async function getSingleFilePath(inputPath) {
|
|
|
6275
6748
|
if (basename(inputPath) === RULE_CONFIG_FILENAME) return;
|
|
6276
6749
|
return inputPath;
|
|
6277
6750
|
}
|
|
6751
|
+
async function getQuickPublishDirectory(inputPath) {
|
|
6752
|
+
if (!inputPath) return;
|
|
6753
|
+
const pathStat = await stat(inputPath).catch(() => null);
|
|
6754
|
+
if (!pathStat?.isDirectory()) return;
|
|
6755
|
+
const configStat = await stat(`${inputPath}/${RULE_CONFIG_FILENAME}`).catch(() => null);
|
|
6756
|
+
if (configStat?.isFile()) return;
|
|
6757
|
+
return inputPath;
|
|
6758
|
+
}
|
|
6278
6759
|
function normalizePathForInference(value) {
|
|
6279
6760
|
return value.replace(/\\/g, "/");
|
|
6280
6761
|
}
|
|
6281
6762
|
function stripExtension(value) {
|
|
6282
6763
|
return value.replace(/\.[^/.]+$/, "");
|
|
6283
6764
|
}
|
|
6284
|
-
function
|
|
6765
|
+
function inferFileDefaults(filePath) {
|
|
6285
6766
|
const normalized = normalizePathForInference(filePath);
|
|
6286
6767
|
const segments = normalized.split("/").filter(Boolean);
|
|
6287
6768
|
const fileName = segments.at(-1) ?? "";
|
|
6288
6769
|
const instructionPlatforms = inferInstructionPlatformsFromFileName(fileName);
|
|
6289
6770
|
if (instructionPlatforms.length > 0) return {
|
|
6290
|
-
|
|
6771
|
+
ruleType: "instruction",
|
|
6291
6772
|
...instructionPlatforms.length === 1 ? { platform: instructionPlatforms[0] } : {}
|
|
6292
6773
|
};
|
|
6293
6774
|
const platform = inferPlatformFromPath(filePath);
|
|
@@ -6295,7 +6776,7 @@ function inferSingleFileDefaults(filePath) {
|
|
|
6295
6776
|
const inferredType = inferTypeFromPath(platform, filePath);
|
|
6296
6777
|
const result = { platform };
|
|
6297
6778
|
if (inferredType) {
|
|
6298
|
-
result.
|
|
6779
|
+
result.ruleType = inferredType;
|
|
6299
6780
|
if (inferredType !== "instruction") result.name = normalizeName(stripExtension(fileName));
|
|
6300
6781
|
}
|
|
6301
6782
|
return result;
|
|
@@ -6311,34 +6792,24 @@ function buildConfigPublishOverrides(options) {
|
|
|
6311
6792
|
if (options.tags !== void 0) overrides.tags = options.tags;
|
|
6312
6793
|
return Object.keys(overrides).length > 0 ? overrides : void 0;
|
|
6313
6794
|
}
|
|
6314
|
-
async function
|
|
6315
|
-
const inferred = inferSingleFileDefaults(filePath);
|
|
6795
|
+
async function resolveQuickPublishInputs(options, source, ctx) {
|
|
6316
6796
|
const isInteractive = !ctx.yes && process.stdin.isTTY;
|
|
6317
|
-
const
|
|
6318
|
-
|
|
6319
|
-
const
|
|
6320
|
-
|
|
6321
|
-
let
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
label: id
|
|
6329
|
-
}))
|
|
6330
|
-
});
|
|
6331
|
-
if (p$1.isCancel(selection)) throw new Error("Cancelled");
|
|
6332
|
-
selectedPlatform = selection;
|
|
6333
|
-
}
|
|
6334
|
-
let selectedType = options.type ?? inferred.type;
|
|
6335
|
-
if (!selectedType) {
|
|
6336
|
-
if (!isInteractive) throw new Error("Missing --type");
|
|
6337
|
-
const candidates = getValidTypes(selectedPlatform).filter((t) => supportsInstallPath({
|
|
6338
|
-
platform: selectedPlatform,
|
|
6797
|
+
const isDirectory = source.type === "directory";
|
|
6798
|
+
const isFile = source.type === "file";
|
|
6799
|
+
const fileInferred = isFile ? inferFileDefaults(source.path) : {};
|
|
6800
|
+
const parsedPlatforms = options.platform ? parsePlatformSelection(options.platform).map(normalizePlatformInput) : void 0;
|
|
6801
|
+
let selectedType = options.type ?? fileInferred.ruleType;
|
|
6802
|
+
const platformsForTypeCheck = parsedPlatforms && parsedPlatforms.length > 0 ? parsedPlatforms : fileInferred.platform ? [fileInferred.platform] : [];
|
|
6803
|
+
if (isFile && !selectedType) {
|
|
6804
|
+
if (!isInteractive) throw new Error("Publishing a single file in non-interactive mode requires --name, --platform, and --type.");
|
|
6805
|
+
if (platformsForTypeCheck.length === 0) throw new Error("Missing --platform");
|
|
6806
|
+
const candidateSets = platformsForTypeCheck.map((plat) => getValidTypes(plat).filter((t) => supportsInstallPath({
|
|
6807
|
+
platform: plat,
|
|
6339
6808
|
type: t,
|
|
6340
6809
|
scope: "project"
|
|
6341
|
-
}));
|
|
6810
|
+
})));
|
|
6811
|
+
const candidates = candidateSets.length === 1 ? candidateSets[0] : candidateSets.reduce((acc, set) => acc.filter((t) => set.includes(t)));
|
|
6812
|
+
if (candidates.length === 0) throw new Error(`No common type supports all selected platforms: ${platformsForTypeCheck.join(", ")}`);
|
|
6342
6813
|
const selection = await p$1.select({
|
|
6343
6814
|
message: "Type",
|
|
6344
6815
|
options: candidates.map((t) => ({
|
|
@@ -6349,117 +6820,127 @@ async function resolveSingleFileInputs(options, filePath, ctx) {
|
|
|
6349
6820
|
if (p$1.isCancel(selection)) throw new Error("Cancelled");
|
|
6350
6821
|
selectedType = selection;
|
|
6351
6822
|
}
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6823
|
+
if (options.type && platformsForTypeCheck.length > 0) {
|
|
6824
|
+
const ruleType = selectedType;
|
|
6825
|
+
for (const platform of platformsForTypeCheck) if (!supportsInstallPath({
|
|
6826
|
+
platform,
|
|
6827
|
+
type: ruleType,
|
|
6828
|
+
scope: "project"
|
|
6829
|
+
})) throw new Error(`Type "${ruleType}" is not supported for project installs on platform "${platform}".`);
|
|
6830
|
+
}
|
|
6831
|
+
const collected = await collectRuleInputs({
|
|
6832
|
+
directory: isDirectory ? source.path : dirname(source.path),
|
|
6833
|
+
defaults: {
|
|
6834
|
+
name: options.name ?? fileInferred.name,
|
|
6835
|
+
title: options.title,
|
|
6836
|
+
description: options.description,
|
|
6837
|
+
platforms: parsedPlatforms,
|
|
6838
|
+
license: options.license ?? fileInferred.license,
|
|
6839
|
+
tags: options.tags,
|
|
6840
|
+
ruleType: selectedType
|
|
6841
|
+
},
|
|
6842
|
+
nonInteractive: !isInteractive,
|
|
6843
|
+
detectType: isDirectory
|
|
6844
|
+
});
|
|
6845
|
+
const finalRuleType = isFile ? selectedType : collected.ruleType ?? (collected.isSkill ? "skill" : "instruction");
|
|
6846
|
+
for (const platform of collected.platforms) if (!supportsInstallPath({
|
|
6368
6847
|
platform,
|
|
6369
|
-
type,
|
|
6370
|
-
name: normalizedName,
|
|
6848
|
+
type: finalRuleType,
|
|
6371
6849
|
scope: "project"
|
|
6372
|
-
});
|
|
6373
|
-
if (!bundlePath) throw new Error(`Type "${type}" is not supported for project installs on platform "${platform}".`);
|
|
6374
|
-
const defaultTitle = toTitleCase(normalizedName);
|
|
6375
|
-
const finalTitle = options.title ?? await (async () => {
|
|
6376
|
-
if (!isInteractive) return defaultTitle;
|
|
6377
|
-
const input = await p$1.text({
|
|
6378
|
-
message: "Title",
|
|
6379
|
-
defaultValue: defaultTitle,
|
|
6380
|
-
placeholder: defaultTitle
|
|
6381
|
-
});
|
|
6382
|
-
if (p$1.isCancel(input)) throw new Error("Cancelled");
|
|
6383
|
-
return input;
|
|
6384
|
-
})();
|
|
6385
|
-
const finalDescription = options.description ?? await (async () => {
|
|
6386
|
-
if (!isInteractive) return "";
|
|
6387
|
-
const input = await p$1.text({
|
|
6388
|
-
message: "Description (optional)",
|
|
6389
|
-
placeholder: "Describe what this rule does...",
|
|
6390
|
-
validate: check(descriptionSchema)
|
|
6391
|
-
});
|
|
6392
|
-
if (p$1.isCancel(input)) throw new Error("Cancelled");
|
|
6393
|
-
return input;
|
|
6394
|
-
})();
|
|
6395
|
-
const finalTags = await (async () => {
|
|
6396
|
-
if (options.tags) return tagsSchema.parse(options.tags);
|
|
6397
|
-
if (!isInteractive) return [];
|
|
6398
|
-
const input = await p$1.text({
|
|
6399
|
-
message: "Tags (optional)",
|
|
6400
|
-
placeholder: "comma-separated, e.g. typescript, react",
|
|
6401
|
-
validate: check(tagsInputSchema)
|
|
6402
|
-
});
|
|
6403
|
-
if (p$1.isCancel(input)) throw new Error("Cancelled");
|
|
6404
|
-
return tagsInputSchema.parse(input);
|
|
6405
|
-
})();
|
|
6406
|
-
const finalLicense = options.license ?? "MIT";
|
|
6850
|
+
})) throw new Error(`Type "${finalRuleType}" is not supported for project installs on platform "${platform}".`);
|
|
6407
6851
|
if (isInteractive && !ctx.dryRun) {
|
|
6408
6852
|
log.print("");
|
|
6409
|
-
log.print(ui.
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6853
|
+
log.print(ui.rulePreview({
|
|
6854
|
+
header: "Quick publish",
|
|
6855
|
+
path: source.path,
|
|
6856
|
+
pathLabel: isDirectory ? "Directory" : "File",
|
|
6857
|
+
name: collected.name,
|
|
6858
|
+
title: collected.title,
|
|
6859
|
+
description: collected.description,
|
|
6860
|
+
platforms: collected.platforms,
|
|
6861
|
+
type: finalRuleType,
|
|
6862
|
+
tags: collected.tags,
|
|
6863
|
+
showHints: true
|
|
6864
|
+
}));
|
|
6418
6865
|
log.print("");
|
|
6419
6866
|
const confirm = await p$1.confirm({
|
|
6420
|
-
message: "Publish this file?",
|
|
6867
|
+
message: isDirectory ? "Publish this directory?" : "Publish this file?",
|
|
6421
6868
|
initialValue: true
|
|
6422
6869
|
});
|
|
6423
6870
|
if (p$1.isCancel(confirm) || !confirm) throw new Error("Cancelled");
|
|
6424
6871
|
}
|
|
6425
6872
|
return {
|
|
6426
|
-
|
|
6427
|
-
name:
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6873
|
+
source,
|
|
6874
|
+
name: collected.name,
|
|
6875
|
+
platforms: collected.platforms,
|
|
6876
|
+
platformPaths: isFile ? {} : collected.platformPaths,
|
|
6877
|
+
ruleType: finalRuleType,
|
|
6878
|
+
title: collected.title,
|
|
6879
|
+
description: collected.description,
|
|
6880
|
+
tags: collected.tags,
|
|
6881
|
+
license: collected.license
|
|
6435
6882
|
};
|
|
6436
6883
|
}
|
|
6437
|
-
async function
|
|
6438
|
-
const
|
|
6884
|
+
async function buildRuleInputFromQuickPublish(inputs) {
|
|
6885
|
+
const platformEntries = inputs.platforms.map((platform) => {
|
|
6886
|
+
const path$1 = inputs.platformPaths[platform];
|
|
6887
|
+
return path$1 ? {
|
|
6888
|
+
platform,
|
|
6889
|
+
path: path$1
|
|
6890
|
+
} : { platform };
|
|
6891
|
+
});
|
|
6439
6892
|
const config$1 = {
|
|
6440
6893
|
$schema: RULE_SCHEMA_URL,
|
|
6441
6894
|
name: inputs.name,
|
|
6442
|
-
type: inputs.
|
|
6895
|
+
type: inputs.ruleType,
|
|
6443
6896
|
title: inputs.title,
|
|
6444
6897
|
description: inputs.description,
|
|
6445
6898
|
license: inputs.license,
|
|
6446
6899
|
tags: inputs.tags,
|
|
6447
|
-
platforms:
|
|
6900
|
+
platforms: platformEntries
|
|
6901
|
+
};
|
|
6902
|
+
if (inputs.source.type === "file") {
|
|
6903
|
+
const content = await readFile(inputs.source.path);
|
|
6904
|
+
const platformFiles$1 = [];
|
|
6905
|
+
for (const platform of inputs.platforms) {
|
|
6906
|
+
const bundlePath = getInstallPath({
|
|
6907
|
+
platform,
|
|
6908
|
+
type: inputs.ruleType,
|
|
6909
|
+
name: inputs.name,
|
|
6910
|
+
scope: "project"
|
|
6911
|
+
});
|
|
6912
|
+
if (!bundlePath) throw new Error(`Type "${inputs.ruleType}" is not supported for project installs on platform "${platform}".`);
|
|
6913
|
+
platformFiles$1.push({
|
|
6914
|
+
platform,
|
|
6915
|
+
files: [{
|
|
6916
|
+
path: bundlePath,
|
|
6917
|
+
content
|
|
6918
|
+
}]
|
|
6919
|
+
});
|
|
6920
|
+
}
|
|
6921
|
+
return {
|
|
6922
|
+
name: inputs.name,
|
|
6923
|
+
config: config$1,
|
|
6924
|
+
platformFiles: platformFiles$1
|
|
6925
|
+
};
|
|
6926
|
+
}
|
|
6927
|
+
const loadedConfig = {
|
|
6928
|
+
configPath: `${inputs.source.path}/agentrules.json`,
|
|
6929
|
+
config: {
|
|
6930
|
+
...config$1,
|
|
6931
|
+
platforms: platformEntries
|
|
6932
|
+
},
|
|
6933
|
+
configDir: inputs.source.path
|
|
6448
6934
|
};
|
|
6935
|
+
const platformFiles = await collectPlatformFiles(loadedConfig);
|
|
6449
6936
|
return {
|
|
6450
6937
|
name: inputs.name,
|
|
6451
6938
|
config: config$1,
|
|
6452
|
-
platformFiles
|
|
6453
|
-
platform: inputs.platform,
|
|
6454
|
-
files: [{
|
|
6455
|
-
path: inputs.bundlePath,
|
|
6456
|
-
content
|
|
6457
|
-
}]
|
|
6458
|
-
}]
|
|
6939
|
+
platformFiles
|
|
6459
6940
|
};
|
|
6460
6941
|
}
|
|
6461
6942
|
async function finalizePublish(options) {
|
|
6462
|
-
const { publishInput, dryRun, version: version$2, spinner: spinner$1, ctx } = options;
|
|
6943
|
+
const { publishInput, dryRun, version: version$2, spinner: spinner$1, ctx, quickPublish, yes } = options;
|
|
6463
6944
|
const totalFileCount = publishInput.variants.reduce((sum, v) => sum + v.files.length, 0);
|
|
6464
6945
|
const platformList = publishInput.variants.map((v) => v.platform).join(", ");
|
|
6465
6946
|
let totalSize = 0;
|
|
@@ -6498,6 +6979,7 @@ async function finalizePublish(options) {
|
|
|
6498
6979
|
log.print("");
|
|
6499
6980
|
}
|
|
6500
6981
|
log.print(ui.hint("Run without --dry-run to publish."));
|
|
6982
|
+
await promptToCreateConfig(quickPublish, yes);
|
|
6501
6983
|
return {
|
|
6502
6984
|
success: true,
|
|
6503
6985
|
preview: {
|
|
@@ -6545,6 +7027,7 @@ async function finalizePublish(options) {
|
|
|
6545
7027
|
}
|
|
6546
7028
|
log.info("");
|
|
6547
7029
|
log.info(ui.keyValue("Now live at", ui.link(data.url)));
|
|
7030
|
+
await promptToCreateConfig(quickPublish, yes);
|
|
6548
7031
|
return {
|
|
6549
7032
|
success: true,
|
|
6550
7033
|
rule: {
|
|
@@ -6635,165 +7118,15 @@ async function discoverRuleDirs(inputDir) {
|
|
|
6635
7118
|
return ruleDirs.sort();
|
|
6636
7119
|
}
|
|
6637
7120
|
|
|
6638
|
-
//#endregion
|
|
6639
|
-
//#region src/commands/rule/init.ts
|
|
6640
|
-
/** Default rule name when none specified */
|
|
6641
|
-
const DEFAULT_RULE_NAME$1 = "my-rule";
|
|
6642
|
-
/**
|
|
6643
|
-
* Initialize a rule in a directory (rule root).
|
|
6644
|
-
*
|
|
6645
|
-
* Structure:
|
|
6646
|
-
* - ruleDir/agentrules.json - rule config
|
|
6647
|
-
* - ruleDir/* - rule files (collected by default)
|
|
6648
|
-
* - ruleDir/README.md, ruleDir/LICENSE.md, ruleDir/INSTALL.txt - optional metadata (not bundled)
|
|
6649
|
-
*/
|
|
6650
|
-
async function initRule(options) {
|
|
6651
|
-
const ruleDir = options.directory ?? process.cwd();
|
|
6652
|
-
log.debug(`Initializing rule in: ${ruleDir}`);
|
|
6653
|
-
const inferredPlatform = getPlatformFromDir(basename(ruleDir));
|
|
6654
|
-
const platformInputs = options.platforms ?? (inferredPlatform ? [inferredPlatform] : []);
|
|
6655
|
-
if (platformInputs.length === 0) throw new Error(`Cannot determine platform. Specify --platform (${PLATFORM_IDS.join(", ")}).`);
|
|
6656
|
-
const platforms = platformInputs.map(normalizePlatformEntryInput);
|
|
6657
|
-
const name = normalizeName(options.name ?? DEFAULT_RULE_NAME$1);
|
|
6658
|
-
const title = options.title ?? toTitleCase(name);
|
|
6659
|
-
const description = options.description ?? "";
|
|
6660
|
-
const license = options.license ?? "MIT";
|
|
6661
|
-
const platformLabels = platforms.map((p$2) => typeof p$2 === "string" ? p$2 : p$2.platform).join(", ");
|
|
6662
|
-
log.debug(`Rule name: ${name}, platforms: ${platformLabels}`);
|
|
6663
|
-
const configPath = join(ruleDir, RULE_CONFIG_FILENAME);
|
|
6664
|
-
if (!options.force && await fileExists(configPath)) throw new Error(`${RULE_CONFIG_FILENAME} already exists. Use --force to overwrite.`);
|
|
6665
|
-
const config$1 = {
|
|
6666
|
-
$schema: RULE_SCHEMA_URL,
|
|
6667
|
-
name,
|
|
6668
|
-
...options.type && { type: options.type },
|
|
6669
|
-
title,
|
|
6670
|
-
version: 1,
|
|
6671
|
-
description,
|
|
6672
|
-
tags: options.tags ?? [],
|
|
6673
|
-
license,
|
|
6674
|
-
platforms
|
|
6675
|
-
};
|
|
6676
|
-
let createdDir;
|
|
6677
|
-
if (await directoryExists(ruleDir)) log.debug(`Directory exists: ${ruleDir}`);
|
|
6678
|
-
else {
|
|
6679
|
-
await mkdir(ruleDir, { recursive: true });
|
|
6680
|
-
createdDir = ruleDir;
|
|
6681
|
-
log.debug(`Created directory: ${ruleDir}`);
|
|
6682
|
-
}
|
|
6683
|
-
const content = `${JSON.stringify(config$1, null, 2)}\n`;
|
|
6684
|
-
await writeFile(configPath, content, "utf8");
|
|
6685
|
-
log.debug(`Wrote config file: ${configPath}`);
|
|
6686
|
-
log.debug("Rule initialization complete.");
|
|
6687
|
-
return {
|
|
6688
|
-
configPath,
|
|
6689
|
-
rule: config$1,
|
|
6690
|
-
createdDir
|
|
6691
|
-
};
|
|
6692
|
-
}
|
|
6693
|
-
function normalizePlatform(input) {
|
|
6694
|
-
const normalized = input.toLowerCase();
|
|
6695
|
-
if (!isSupportedPlatform(normalized)) throw new Error(`Unknown platform "${input}". Supported: ${PLATFORM_IDS.join(", ")}`);
|
|
6696
|
-
return normalized;
|
|
6697
|
-
}
|
|
6698
|
-
function normalizePlatformEntryInput(input) {
|
|
6699
|
-
if (typeof input === "string") return normalizePlatform(input);
|
|
6700
|
-
const platform = normalizePlatform(input.platform);
|
|
6701
|
-
const path$1 = typeof input.path === "string" ? input.path.trim() : "";
|
|
6702
|
-
if (path$1.length === 0 || path$1 === ".") return platform;
|
|
6703
|
-
return {
|
|
6704
|
-
platform,
|
|
6705
|
-
path: path$1
|
|
6706
|
-
};
|
|
6707
|
-
}
|
|
6708
|
-
|
|
6709
7121
|
//#endregion
|
|
6710
7122
|
//#region src/commands/rule/init-interactive.ts
|
|
6711
|
-
const DEFAULT_RULE_NAME = "my-rule";
|
|
6712
|
-
/**
|
|
6713
|
-
* Parse comma-separated tags string into array.
|
|
6714
|
-
*/
|
|
6715
|
-
function parseTags(input) {
|
|
6716
|
-
if (typeof input !== "string") return [];
|
|
6717
|
-
if (input.trim().length === 0) return [];
|
|
6718
|
-
return input.split(",").map((tag) => tag.trim().toLowerCase()).filter((tag) => tag.length > 0);
|
|
6719
|
-
}
|
|
6720
|
-
/**
|
|
6721
|
-
* Validator for comma-separated tags input.
|
|
6722
|
-
*/
|
|
6723
|
-
function checkTags(value) {
|
|
6724
|
-
const tags = parseTags(value);
|
|
6725
|
-
const result = tagsSchema.safeParse(tags);
|
|
6726
|
-
if (!result.success) return result.error.issues[0]?.message;
|
|
6727
|
-
}
|
|
6728
7123
|
/**
|
|
6729
7124
|
* Run interactive init flow with clack prompts.
|
|
6730
7125
|
*/
|
|
6731
7126
|
async function initInteractive(options) {
|
|
6732
|
-
const { directory,
|
|
7127
|
+
const { directory, platformPaths } = options;
|
|
6733
7128
|
let { force } = options;
|
|
6734
|
-
const defaultName = nameOption ?? DEFAULT_RULE_NAME;
|
|
6735
7129
|
p.intro("Create a new rule");
|
|
6736
|
-
const validatedPlatforms = [];
|
|
6737
|
-
if (platformsOption) for (const platform of platformsOption) {
|
|
6738
|
-
if (!isSupportedPlatform(platform)) {
|
|
6739
|
-
p.cancel(`Unknown platform "${platform}"`);
|
|
6740
|
-
process.exit(1);
|
|
6741
|
-
}
|
|
6742
|
-
validatedPlatforms.push(platform);
|
|
6743
|
-
}
|
|
6744
|
-
const selectedPlatforms = validatedPlatforms.length > 0 ? validatedPlatforms : await (async () => {
|
|
6745
|
-
const platformChoices = await p.multiselect({
|
|
6746
|
-
message: "Platforms (select one or more)",
|
|
6747
|
-
options: PLATFORM_IDS.map((id) => ({
|
|
6748
|
-
value: id,
|
|
6749
|
-
label: id
|
|
6750
|
-
})),
|
|
6751
|
-
required: true
|
|
6752
|
-
});
|
|
6753
|
-
if (p.isCancel(platformChoices)) {
|
|
6754
|
-
p.cancel("Cancelled");
|
|
6755
|
-
process.exit(0);
|
|
6756
|
-
}
|
|
6757
|
-
return platformChoices;
|
|
6758
|
-
})();
|
|
6759
|
-
const platformEntries = await (async () => {
|
|
6760
|
-
if (selectedPlatforms.length === 0) return [];
|
|
6761
|
-
const hasCompletePathMapping = selectedPlatforms.every((platform) => {
|
|
6762
|
-
const value = platformPaths?.[platform];
|
|
6763
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
6764
|
-
});
|
|
6765
|
-
if (hasCompletePathMapping) return selectedPlatforms.map((platform) => {
|
|
6766
|
-
const path$1 = platformPaths?.[platform]?.trim();
|
|
6767
|
-
if (!path$1 || path$1 === ".") return platform;
|
|
6768
|
-
return {
|
|
6769
|
-
platform,
|
|
6770
|
-
path: path$1
|
|
6771
|
-
};
|
|
6772
|
-
});
|
|
6773
|
-
if (selectedPlatforms.length === 1) return selectedPlatforms;
|
|
6774
|
-
const entries = [];
|
|
6775
|
-
for (const platform of selectedPlatforms) {
|
|
6776
|
-
const mappedPath = platformPaths?.[platform]?.trim();
|
|
6777
|
-
const suggestedPath = mappedPath ?? (await directoryExists(join(directory, platform)) ? platform : ".");
|
|
6778
|
-
const input = await p.text({
|
|
6779
|
-
message: `Folder for ${platform} files ('.' = same folder as agentrules.json)`,
|
|
6780
|
-
placeholder: suggestedPath,
|
|
6781
|
-
defaultValue: suggestedPath
|
|
6782
|
-
});
|
|
6783
|
-
if (p.isCancel(input)) {
|
|
6784
|
-
p.cancel("Cancelled");
|
|
6785
|
-
process.exit(0);
|
|
6786
|
-
}
|
|
6787
|
-
const trimmed = input.trim();
|
|
6788
|
-
const resolvedPath = trimmed.length > 0 ? trimmed : suggestedPath;
|
|
6789
|
-
if (resolvedPath === ".") entries.push(platform);
|
|
6790
|
-
else entries.push({
|
|
6791
|
-
platform,
|
|
6792
|
-
path: resolvedPath
|
|
6793
|
-
});
|
|
6794
|
-
}
|
|
6795
|
-
return entries;
|
|
6796
|
-
})();
|
|
6797
7130
|
const configPath = join(directory, RULE_CONFIG_FILENAME);
|
|
6798
7131
|
if (!force && await fileExists(configPath)) {
|
|
6799
7132
|
const overwrite = await p.confirm({
|
|
@@ -6806,75 +7139,58 @@ async function initInteractive(options) {
|
|
|
6806
7139
|
}
|
|
6807
7140
|
force = true;
|
|
6808
7141
|
}
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
defaultValue: descriptionOption,
|
|
6828
|
-
validate: check(descriptionSchema)
|
|
6829
|
-
}),
|
|
6830
|
-
tags: () => p.text({
|
|
6831
|
-
message: "Tags (comma-separated, optional)",
|
|
6832
|
-
placeholder: "e.g., typescript, testing, react",
|
|
6833
|
-
validate: checkTags
|
|
6834
|
-
}),
|
|
6835
|
-
license: async () => {
|
|
6836
|
-
const defaultLicense = licenseOption ?? "MIT";
|
|
6837
|
-
const choice = await p.select({
|
|
6838
|
-
message: "License",
|
|
6839
|
-
options: [...COMMON_LICENSES.map((id) => ({
|
|
6840
|
-
value: id,
|
|
6841
|
-
label: id
|
|
6842
|
-
})), {
|
|
6843
|
-
value: "__other__",
|
|
6844
|
-
label: "Other (enter SPDX identifier)"
|
|
6845
|
-
}],
|
|
6846
|
-
initialValue: defaultLicense
|
|
6847
|
-
});
|
|
6848
|
-
if (p.isCancel(choice)) {
|
|
6849
|
-
p.cancel("Cancelled");
|
|
6850
|
-
process.exit(0);
|
|
6851
|
-
}
|
|
6852
|
-
if (choice === "__other__") {
|
|
6853
|
-
const custom = await p.text({
|
|
6854
|
-
message: "License (SPDX identifier)",
|
|
6855
|
-
placeholder: "e.g., MPL-2.0, AGPL-3.0-only",
|
|
6856
|
-
validate: check(licenseSchema)
|
|
6857
|
-
});
|
|
6858
|
-
if (p.isCancel(custom)) {
|
|
6859
|
-
p.cancel("Cancelled");
|
|
6860
|
-
process.exit(0);
|
|
6861
|
-
}
|
|
6862
|
-
return custom;
|
|
6863
|
-
}
|
|
6864
|
-
return choice;
|
|
7142
|
+
let collected;
|
|
7143
|
+
try {
|
|
7144
|
+
collected = await collectRuleInputs({
|
|
7145
|
+
directory,
|
|
7146
|
+
defaults: {
|
|
7147
|
+
name: options.name,
|
|
7148
|
+
title: options.title,
|
|
7149
|
+
description: options.description,
|
|
7150
|
+
platforms: options.platforms,
|
|
7151
|
+
platformPaths,
|
|
7152
|
+
license: options.license
|
|
7153
|
+
},
|
|
7154
|
+
nonInteractive: false
|
|
7155
|
+
});
|
|
7156
|
+
} catch (error$2) {
|
|
7157
|
+
if (error$2 instanceof Error && error$2.message === "Cancelled") {
|
|
7158
|
+
p.cancel("Cancelled");
|
|
7159
|
+
process.exit(0);
|
|
6865
7160
|
}
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
7161
|
+
throw error$2;
|
|
7162
|
+
}
|
|
7163
|
+
const platformEntries = collected.platforms.map((platform) => {
|
|
7164
|
+
const path$1 = collected.platformPaths[platform];
|
|
7165
|
+
return path$1 ? {
|
|
7166
|
+
platform,
|
|
7167
|
+
path: path$1
|
|
7168
|
+
} : platform;
|
|
7169
|
+
});
|
|
7170
|
+
log.print("");
|
|
7171
|
+
log.print(ui.rulePreview({
|
|
7172
|
+
header: "Rule configuration",
|
|
7173
|
+
path: directory,
|
|
7174
|
+
pathLabel: "Directory",
|
|
7175
|
+
name: collected.name,
|
|
7176
|
+
title: collected.title || toTitleCase(collected.name),
|
|
7177
|
+
description: collected.description,
|
|
7178
|
+
platforms: collected.platforms,
|
|
7179
|
+
type: collected.isSkill ? "skill" : void 0,
|
|
7180
|
+
tags: collected.tags,
|
|
7181
|
+
license: collected.license,
|
|
7182
|
+
showHints: true
|
|
7183
|
+
}));
|
|
7184
|
+
log.print("");
|
|
6870
7185
|
const initOptions = {
|
|
6871
7186
|
directory,
|
|
6872
|
-
name:
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
7187
|
+
name: collected.name,
|
|
7188
|
+
type: collected.isSkill ? "skill" : void 0,
|
|
7189
|
+
title: collected.title || void 0,
|
|
7190
|
+
description: collected.description,
|
|
7191
|
+
tags: collected.tags,
|
|
6876
7192
|
platforms: platformEntries,
|
|
6877
|
-
license:
|
|
7193
|
+
license: collected.license,
|
|
6878
7194
|
force
|
|
6879
7195
|
};
|
|
6880
7196
|
const initResult = await initRule(initOptions);
|
|
@@ -7347,7 +7663,7 @@ program.command("add <item>").description("Download and install a rule from the
|
|
|
7347
7663
|
program.command("init").description("Initialize a new rule").argument("[directory]", "Directory to initialize (created if it doesn't exist)").option("-y, --yes", "Accept defaults without prompting").option("-n, --name <name>", "Rule name").option("-t, --title <title>", "Display title").option("--description <text>", "Rule description").option("-p, --platform <platform>", "Target platform(s). Repeatable, accepts comma-separated. Supports <platform>=<path> mappings.", (value, previous) => previous ? [...previous, value] : [value]).option("-l, --license <license>", "License (e.g., MIT)").option("-f, --force", "Overwrite existing agentrules.json").action(handle(async (directory, options) => {
|
|
7348
7664
|
const targetDir = directory ?? process.cwd();
|
|
7349
7665
|
const defaultName = directory ? basename(directory) : void 0;
|
|
7350
|
-
const platformInputs = options.platform?.flatMap((p$
|
|
7666
|
+
const platformInputs = options.platform?.flatMap((p$3) => p$3.split(",").map((s) => s.trim())).filter((p$3) => p$3.length > 0);
|
|
7351
7667
|
const platformIds = [];
|
|
7352
7668
|
const platformPaths = {};
|
|
7353
7669
|
if (platformInputs) for (const input of platformInputs) {
|
|
@@ -7388,7 +7704,7 @@ program.command("init").description("Initialize a new rule").argument("[director
|
|
|
7388
7704
|
}
|
|
7389
7705
|
const nextSteps$1 = [
|
|
7390
7706
|
"Add your rule files in this directory",
|
|
7391
|
-
"Add tags
|
|
7707
|
+
"Add tags and features to agentrules.json",
|
|
7392
7708
|
`Run ${ui.command("agentrules publish")} to publish your rule`
|
|
7393
7709
|
];
|
|
7394
7710
|
log.print(`\n${ui.header("Next steps")}`);
|
|
@@ -7412,7 +7728,7 @@ program.command("init").description("Initialize a new rule").argument("[director
|
|
|
7412
7728
|
}
|
|
7413
7729
|
const nextSteps = [
|
|
7414
7730
|
"Add your rule files in this directory",
|
|
7415
|
-
"Add tags
|
|
7731
|
+
"Add tags and features to agentrules.json",
|
|
7416
7732
|
`Run ${ui.command("agentrules publish")} to publish your rule`
|
|
7417
7733
|
];
|
|
7418
7734
|
log.print(`\n${ui.header("Next steps")}`);
|
|
@@ -7421,13 +7737,13 @@ program.command("init").description("Initialize a new rule").argument("[director
|
|
|
7421
7737
|
program.command("validate").description("Validate an agentrules.json configuration").argument("[path]", "Path to agentrules.json or directory").action(handle(async (path$1) => {
|
|
7422
7738
|
const result = await validateRule({ path: path$1 });
|
|
7423
7739
|
if (result.valid && result.rule) {
|
|
7424
|
-
const p$
|
|
7425
|
-
const platforms = p$
|
|
7426
|
-
log.success(p$
|
|
7427
|
-
if (p$
|
|
7428
|
-
log.print(ui.keyValue("License", p$
|
|
7740
|
+
const p$3 = result.rule;
|
|
7741
|
+
const platforms = p$3.platforms.map((entry) => entry.platform).join(", ");
|
|
7742
|
+
log.success(p$3.title);
|
|
7743
|
+
if (p$3.description) log.print(ui.keyValue("Description", p$3.description));
|
|
7744
|
+
log.print(ui.keyValue("License", p$3.license));
|
|
7429
7745
|
log.print(ui.keyValue("Platforms", platforms));
|
|
7430
|
-
if (p$
|
|
7746
|
+
if (p$3.tags?.length) log.print(ui.keyValue("Tags", p$3.tags.join(", ")));
|
|
7431
7747
|
} else if (!result.valid) log.error(`Invalid: ${ui.path(result.configPath)}`);
|
|
7432
7748
|
if (result.errors.length > 0) {
|
|
7433
7749
|
log.print("");
|