@agentrules/cli 0.3.0 → 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 +2 -1
- package/dist/index.js +489 -408
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -193,8 +193,9 @@ agentrules publish [path] [options]
|
|
|
193
193
|
# Publish current directory
|
|
194
194
|
agentrules publish
|
|
195
195
|
|
|
196
|
-
# Publish a specific
|
|
196
|
+
# Publish a specific directory or file (interactive if no agentrules.json)
|
|
197
197
|
agentrules publish ./my-rule
|
|
198
|
+
agentrules publish .claude/commands/deploy.md
|
|
198
199
|
|
|
199
200
|
# Publish to major version 2
|
|
200
201
|
agentrules publish --version 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
|
|
|
@@ -1989,7 +1990,7 @@ const $ZodUnion = /* @__PURE__ */ $constructor("$ZodUnion", (inst, def) => {
|
|
|
1989
1990
|
defineLazy(inst._zod, "pattern", () => {
|
|
1990
1991
|
if (def.options.every((o) => o._zod.pattern)) {
|
|
1991
1992
|
const patterns = def.options.map((o) => o._zod.pattern);
|
|
1992
|
-
return new RegExp(`^(${patterns.map((p$
|
|
1993
|
+
return new RegExp(`^(${patterns.map((p$3) => cleanRegex(p$3.source)).join("|")})$`);
|
|
1993
1994
|
}
|
|
1994
1995
|
return void 0;
|
|
1995
1996
|
});
|
|
@@ -2368,9 +2369,9 @@ var $ZodRegistry = class {
|
|
|
2368
2369
|
return this;
|
|
2369
2370
|
}
|
|
2370
2371
|
get(schema) {
|
|
2371
|
-
const p$
|
|
2372
|
-
if (p$
|
|
2373
|
-
const pm = { ...this.get(p$
|
|
2372
|
+
const p$3 = schema._zod.parent;
|
|
2373
|
+
if (p$3) {
|
|
2374
|
+
const pm = { ...this.get(p$3) ?? {} };
|
|
2374
2375
|
delete pm.id;
|
|
2375
2376
|
const f = {
|
|
2376
2377
|
...pm,
|
|
@@ -4375,8 +4376,8 @@ function command(cmd) {
|
|
|
4375
4376
|
return theme.command(cmd);
|
|
4376
4377
|
}
|
|
4377
4378
|
/** Format a file path */
|
|
4378
|
-
function path(p$
|
|
4379
|
-
return theme.path(p$
|
|
4379
|
+
function path(p$3) {
|
|
4380
|
+
return theme.path(p$3);
|
|
4380
4381
|
}
|
|
4381
4382
|
/** Format as muted/secondary text */
|
|
4382
4383
|
function muted(text) {
|
|
@@ -4694,6 +4695,22 @@ function fileTree(files, options) {
|
|
|
4694
4695
|
});
|
|
4695
4696
|
return lines.join("\n");
|
|
4696
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
|
+
}
|
|
4697
4714
|
const ui = {
|
|
4698
4715
|
theme,
|
|
4699
4716
|
symbols,
|
|
@@ -4730,6 +4747,7 @@ const ui = {
|
|
|
4730
4747
|
fileCounts,
|
|
4731
4748
|
hint,
|
|
4732
4749
|
link,
|
|
4750
|
+
rulePreview,
|
|
4733
4751
|
stripAnsi,
|
|
4734
4752
|
truncate,
|
|
4735
4753
|
formatBytes: formatBytes$1,
|
|
@@ -5882,6 +5900,20 @@ async function directoryExists(path$1) {
|
|
|
5882
5900
|
}
|
|
5883
5901
|
}
|
|
5884
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
|
+
|
|
5885
5917
|
//#endregion
|
|
5886
5918
|
//#region src/lib/rule-utils.ts
|
|
5887
5919
|
const SKILL_FILENAME = "SKILL.md";
|
|
@@ -6005,7 +6037,7 @@ async function loadConfig(inputPath, overrides) {
|
|
|
6005
6037
|
platforms
|
|
6006
6038
|
};
|
|
6007
6039
|
const configDir = dirname(configPath);
|
|
6008
|
-
const platformNames = platforms.map((
|
|
6040
|
+
const platformNames = platforms.map((plat) => plat.platform).join(", ");
|
|
6009
6041
|
log.debug(`Loaded config: ${config$1.name}, platforms: ${platformNames}`);
|
|
6010
6042
|
return {
|
|
6011
6043
|
configPath,
|
|
@@ -6191,18 +6223,270 @@ async function readFirstMatch(dir, filenames) {
|
|
|
6191
6223
|
}
|
|
6192
6224
|
return;
|
|
6193
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
|
+
}
|
|
6194
6405
|
|
|
6195
6406
|
//#endregion
|
|
6196
|
-
//#region src/
|
|
6407
|
+
//#region src/commands/rule/init.ts
|
|
6408
|
+
/** Default rule name when none specified */
|
|
6409
|
+
const DEFAULT_RULE_NAME = "my-rule";
|
|
6197
6410
|
/**
|
|
6198
|
-
*
|
|
6199
|
-
* Returns error message if invalid, undefined if valid.
|
|
6411
|
+
* Detect if directory contains SKILL.md and extract frontmatter defaults.
|
|
6200
6412
|
*/
|
|
6201
|
-
function
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
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
|
|
6206
6490
|
};
|
|
6207
6491
|
}
|
|
6208
6492
|
|
|
@@ -6210,14 +6494,46 @@ function check(schema) {
|
|
|
6210
6494
|
//#region src/commands/publish.ts
|
|
6211
6495
|
/** Maximum size per variant/platform bundle in bytes (1MB) */
|
|
6212
6496
|
const MAX_VARIANT_SIZE_BYTES = 1 * 1024 * 1024;
|
|
6213
|
-
/** Schema for parsing comma-separated tags input */
|
|
6214
|
-
const tagsInputSchema = string().transform((input) => input.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0)).pipe(tagsSchema);
|
|
6215
6497
|
function formatBytes(bytes) {
|
|
6216
6498
|
if (bytes < 1024) return `${bytes} B`;
|
|
6217
6499
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
6218
6500
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
6219
6501
|
}
|
|
6220
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
|
+
/**
|
|
6221
6537
|
* Publishes a rule to the registry.
|
|
6222
6538
|
*
|
|
6223
6539
|
* Supports:
|
|
@@ -6287,7 +6603,9 @@ async function publish(options = {}) {
|
|
|
6287
6603
|
dryRun,
|
|
6288
6604
|
version: version$2,
|
|
6289
6605
|
spinner: fileSpinner,
|
|
6290
|
-
ctx
|
|
6606
|
+
ctx,
|
|
6607
|
+
quickPublish: resolved,
|
|
6608
|
+
yes
|
|
6291
6609
|
});
|
|
6292
6610
|
}
|
|
6293
6611
|
if (quickDir) {
|
|
@@ -6303,8 +6621,7 @@ async function publish(options = {}) {
|
|
|
6303
6621
|
license
|
|
6304
6622
|
}, {
|
|
6305
6623
|
type: "directory",
|
|
6306
|
-
path: quickDir
|
|
6307
|
-
entryFile: quickDir.entryFile
|
|
6624
|
+
path: quickDir
|
|
6308
6625
|
}, {
|
|
6309
6626
|
dryRun,
|
|
6310
6627
|
yes
|
|
@@ -6338,7 +6655,9 @@ async function publish(options = {}) {
|
|
|
6338
6655
|
dryRun,
|
|
6339
6656
|
version: version$2,
|
|
6340
6657
|
spinner: dirSpinner,
|
|
6341
|
-
ctx
|
|
6658
|
+
ctx,
|
|
6659
|
+
quickPublish: resolved,
|
|
6660
|
+
yes
|
|
6342
6661
|
});
|
|
6343
6662
|
}
|
|
6344
6663
|
const spinner$1 = await log.spinner("Validating rule...");
|
|
@@ -6435,14 +6754,7 @@ async function getQuickPublishDirectory(inputPath) {
|
|
|
6435
6754
|
if (!pathStat?.isDirectory()) return;
|
|
6436
6755
|
const configStat = await stat(`${inputPath}/${RULE_CONFIG_FILENAME}`).catch(() => null);
|
|
6437
6756
|
if (configStat?.isFile()) return;
|
|
6438
|
-
|
|
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;
|
|
6757
|
+
return inputPath;
|
|
6446
6758
|
}
|
|
6447
6759
|
function normalizePathForInference(value) {
|
|
6448
6760
|
return value.replace(/\\/g, "/");
|
|
@@ -6469,17 +6781,6 @@ function inferFileDefaults(filePath) {
|
|
|
6469
6781
|
}
|
|
6470
6782
|
return result;
|
|
6471
6783
|
}
|
|
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
|
-
}
|
|
6483
6784
|
function buildConfigPublishOverrides(options) {
|
|
6484
6785
|
const overrides = {};
|
|
6485
6786
|
if (options.name !== void 0) overrides.name = options.name;
|
|
@@ -6492,37 +6793,23 @@ function buildConfigPublishOverrides(options) {
|
|
|
6492
6793
|
return Object.keys(overrides).length > 0 ? overrides : void 0;
|
|
6493
6794
|
}
|
|
6494
6795
|
async function resolveQuickPublishInputs(options, source, ctx) {
|
|
6495
|
-
const inferred = source.type === "file" ? inferFileDefaults(source.path) : await inferDirectoryDefaults(source.path, source.entryFile, "skill");
|
|
6496
6796
|
const isInteractive = !ctx.yes && process.stdin.isTTY;
|
|
6497
6797
|
const isDirectory = source.type === "directory";
|
|
6498
|
-
const
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
const selection = await p$1.select({
|
|
6509
|
-
message: "Platform",
|
|
6510
|
-
options: PLATFORM_IDS.map((id) => ({
|
|
6511
|
-
value: id,
|
|
6512
|
-
label: id
|
|
6513
|
-
}))
|
|
6514
|
-
});
|
|
6515
|
-
if (p$1.isCancel(selection)) throw new Error("Cancelled");
|
|
6516
|
-
selectedPlatform = selection;
|
|
6517
|
-
}
|
|
6518
|
-
let selectedType = options.type ?? inferred.ruleType;
|
|
6519
|
-
if (!selectedType) {
|
|
6520
|
-
if (!isInteractive) throw new Error("Missing --type");
|
|
6521
|
-
const candidates = getValidTypes(selectedPlatform).filter((t) => supportsInstallPath({
|
|
6522
|
-
platform: selectedPlatform,
|
|
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,
|
|
6523
6808
|
type: t,
|
|
6524
6809
|
scope: "project"
|
|
6525
|
-
}));
|
|
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(", ")}`);
|
|
6526
6813
|
const selection = await p$1.select({
|
|
6527
6814
|
message: "Type",
|
|
6528
6815
|
options: candidates.map((t) => ({
|
|
@@ -6533,64 +6820,48 @@ async function resolveQuickPublishInputs(options, source, ctx) {
|
|
|
6533
6820
|
if (p$1.isCancel(selection)) throw new Error("Cancelled");
|
|
6534
6821
|
selectedType = selection;
|
|
6535
6822
|
}
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
const input = await p$1.text({
|
|
6565
|
-
message: "Description (optional)",
|
|
6566
|
-
placeholder: "Describe what this rule does...",
|
|
6567
|
-
validate: check(descriptionSchema)
|
|
6568
|
-
});
|
|
6569
|
-
if (p$1.isCancel(input)) throw new Error("Cancelled");
|
|
6570
|
-
return input;
|
|
6571
|
-
})();
|
|
6572
|
-
const finalTags = await (async () => {
|
|
6573
|
-
if (options.tags) return tagsSchema.parse(options.tags);
|
|
6574
|
-
if (!isInteractive) return [];
|
|
6575
|
-
const input = await p$1.text({
|
|
6576
|
-
message: "Tags (optional)",
|
|
6577
|
-
placeholder: "comma-separated, e.g. typescript, react",
|
|
6578
|
-
validate: check(tagsInputSchema)
|
|
6579
|
-
});
|
|
6580
|
-
if (p$1.isCancel(input)) throw new Error("Cancelled");
|
|
6581
|
-
return tagsInputSchema.parse(input);
|
|
6582
|
-
})();
|
|
6583
|
-
const finalLicense = options.license ?? inferred.license ?? "MIT";
|
|
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({
|
|
6847
|
+
platform,
|
|
6848
|
+
type: finalRuleType,
|
|
6849
|
+
scope: "project"
|
|
6850
|
+
})) throw new Error(`Type "${finalRuleType}" is not supported for project installs on platform "${platform}".`);
|
|
6584
6851
|
if (isInteractive && !ctx.dryRun) {
|
|
6585
6852
|
log.print("");
|
|
6586
|
-
log.print(ui.
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
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
|
+
}));
|
|
6594
6865
|
log.print("");
|
|
6595
6866
|
const confirm = await p$1.confirm({
|
|
6596
6867
|
message: isDirectory ? "Publish this directory?" : "Publish this file?",
|
|
@@ -6600,16 +6871,24 @@ async function resolveQuickPublishInputs(options, source, ctx) {
|
|
|
6600
6871
|
}
|
|
6601
6872
|
return {
|
|
6602
6873
|
source,
|
|
6603
|
-
name:
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
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
|
|
6610
6882
|
};
|
|
6611
6883
|
}
|
|
6612
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
|
+
});
|
|
6613
6892
|
const config$1 = {
|
|
6614
6893
|
$schema: RULE_SCHEMA_URL,
|
|
6615
6894
|
name: inputs.name,
|
|
@@ -6618,34 +6897,38 @@ async function buildRuleInputFromQuickPublish(inputs) {
|
|
|
6618
6897
|
description: inputs.description,
|
|
6619
6898
|
license: inputs.license,
|
|
6620
6899
|
tags: inputs.tags,
|
|
6621
|
-
platforms:
|
|
6900
|
+
platforms: platformEntries
|
|
6622
6901
|
};
|
|
6623
6902
|
if (inputs.source.type === "file") {
|
|
6624
6903
|
const content = await readFile(inputs.source.path);
|
|
6625
|
-
const
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
platform: inputs.platform,
|
|
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,
|
|
6637
6915
|
files: [{
|
|
6638
6916
|
path: bundlePath,
|
|
6639
6917
|
content
|
|
6640
6918
|
}]
|
|
6641
|
-
}
|
|
6919
|
+
});
|
|
6920
|
+
}
|
|
6921
|
+
return {
|
|
6922
|
+
name: inputs.name,
|
|
6923
|
+
config: config$1,
|
|
6924
|
+
platformFiles: platformFiles$1
|
|
6642
6925
|
};
|
|
6643
6926
|
}
|
|
6644
6927
|
const loadedConfig = {
|
|
6645
6928
|
configPath: `${inputs.source.path}/agentrules.json`,
|
|
6646
6929
|
config: {
|
|
6647
6930
|
...config$1,
|
|
6648
|
-
platforms:
|
|
6931
|
+
platforms: platformEntries
|
|
6649
6932
|
},
|
|
6650
6933
|
configDir: inputs.source.path
|
|
6651
6934
|
};
|
|
@@ -6657,7 +6940,7 @@ async function buildRuleInputFromQuickPublish(inputs) {
|
|
|
6657
6940
|
};
|
|
6658
6941
|
}
|
|
6659
6942
|
async function finalizePublish(options) {
|
|
6660
|
-
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;
|
|
6661
6944
|
const totalFileCount = publishInput.variants.reduce((sum, v) => sum + v.files.length, 0);
|
|
6662
6945
|
const platformList = publishInput.variants.map((v) => v.platform).join(", ");
|
|
6663
6946
|
let totalSize = 0;
|
|
@@ -6696,6 +6979,7 @@ async function finalizePublish(options) {
|
|
|
6696
6979
|
log.print("");
|
|
6697
6980
|
}
|
|
6698
6981
|
log.print(ui.hint("Run without --dry-run to publish."));
|
|
6982
|
+
await promptToCreateConfig(quickPublish, yes);
|
|
6699
6983
|
return {
|
|
6700
6984
|
success: true,
|
|
6701
6985
|
preview: {
|
|
@@ -6743,6 +7027,7 @@ async function finalizePublish(options) {
|
|
|
6743
7027
|
}
|
|
6744
7028
|
log.info("");
|
|
6745
7029
|
log.info(ui.keyValue("Now live at", ui.link(data.url)));
|
|
7030
|
+
await promptToCreateConfig(quickPublish, yes);
|
|
6746
7031
|
return {
|
|
6747
7032
|
success: true,
|
|
6748
7033
|
rule: {
|
|
@@ -6833,196 +7118,15 @@ async function discoverRuleDirs(inputDir) {
|
|
|
6833
7118
|
return ruleDirs.sort();
|
|
6834
7119
|
}
|
|
6835
7120
|
|
|
6836
|
-
//#endregion
|
|
6837
|
-
//#region src/commands/rule/init.ts
|
|
6838
|
-
/** Default rule name when none specified */
|
|
6839
|
-
const DEFAULT_RULE_NAME$1 = "my-rule";
|
|
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
|
-
/**
|
|
6854
|
-
* Initialize a rule in a directory (rule root).
|
|
6855
|
-
*
|
|
6856
|
-
* Structure:
|
|
6857
|
-
* - ruleDir/agentrules.json - rule config
|
|
6858
|
-
* - ruleDir/* - rule files (collected by default)
|
|
6859
|
-
* - ruleDir/README.md, ruleDir/LICENSE.md, ruleDir/INSTALL.txt - optional metadata (not bundled)
|
|
6860
|
-
*/
|
|
6861
|
-
async function initRule(options) {
|
|
6862
|
-
const ruleDir = options.directory ?? process.cwd();
|
|
6863
|
-
log.debug(`Initializing rule in: ${ruleDir}`);
|
|
6864
|
-
const skillInfo = await detectSkillDirectory(ruleDir);
|
|
6865
|
-
const isSkillDirectory = skillInfo !== void 0;
|
|
6866
|
-
const inferredPlatform = getPlatformFromDir(basename(ruleDir));
|
|
6867
|
-
const platformInputs = options.platforms ?? (inferredPlatform ? [inferredPlatform] : []);
|
|
6868
|
-
if (platformInputs.length === 0) throw new Error(`Cannot determine platform. Specify --platform (${PLATFORM_IDS.join(", ")}).`);
|
|
6869
|
-
const platforms = platformInputs.map(normalizePlatformEntryInput);
|
|
6870
|
-
const name = normalizeName(options.name ?? skillInfo?.name ?? DEFAULT_RULE_NAME$1);
|
|
6871
|
-
const title = options.title ?? toTitleCase(name);
|
|
6872
|
-
const description = options.description ?? "";
|
|
6873
|
-
const license = options.license ?? skillInfo?.license ?? "MIT";
|
|
6874
|
-
const ruleType = isSkillDirectory ? "skill" : options.type;
|
|
6875
|
-
const platformLabels = platforms.map((p$2) => typeof p$2 === "string" ? p$2 : p$2.platform).join(", ");
|
|
6876
|
-
log.debug(`Rule name: ${name}, platforms: ${platformLabels}`);
|
|
6877
|
-
const configPath = join(ruleDir, RULE_CONFIG_FILENAME);
|
|
6878
|
-
if (!options.force && await fileExists(configPath)) throw new Error(`${RULE_CONFIG_FILENAME} already exists. Use --force to overwrite.`);
|
|
6879
|
-
const config$1 = {
|
|
6880
|
-
$schema: RULE_SCHEMA_URL,
|
|
6881
|
-
name,
|
|
6882
|
-
...ruleType && { type: ruleType },
|
|
6883
|
-
title,
|
|
6884
|
-
version: 1,
|
|
6885
|
-
description,
|
|
6886
|
-
tags: options.tags ?? [],
|
|
6887
|
-
license,
|
|
6888
|
-
platforms
|
|
6889
|
-
};
|
|
6890
|
-
let createdDir;
|
|
6891
|
-
if (await directoryExists(ruleDir)) log.debug(`Directory exists: ${ruleDir}`);
|
|
6892
|
-
else {
|
|
6893
|
-
await mkdir(ruleDir, { recursive: true });
|
|
6894
|
-
createdDir = ruleDir;
|
|
6895
|
-
log.debug(`Created directory: ${ruleDir}`);
|
|
6896
|
-
}
|
|
6897
|
-
const content = `${JSON.stringify(config$1, null, 2)}\n`;
|
|
6898
|
-
await writeFile(configPath, content, "utf8");
|
|
6899
|
-
log.debug(`Wrote config file: ${configPath}`);
|
|
6900
|
-
log.debug("Rule initialization complete.");
|
|
6901
|
-
return {
|
|
6902
|
-
configPath,
|
|
6903
|
-
rule: config$1,
|
|
6904
|
-
createdDir
|
|
6905
|
-
};
|
|
6906
|
-
}
|
|
6907
|
-
function normalizePlatform(input) {
|
|
6908
|
-
const normalized = input.toLowerCase();
|
|
6909
|
-
if (!isSupportedPlatform(normalized)) throw new Error(`Unknown platform "${input}". Supported: ${PLATFORM_IDS.join(", ")}`);
|
|
6910
|
-
return normalized;
|
|
6911
|
-
}
|
|
6912
|
-
function normalizePlatformEntryInput(input) {
|
|
6913
|
-
if (typeof input === "string") return normalizePlatform(input);
|
|
6914
|
-
const platform = normalizePlatform(input.platform);
|
|
6915
|
-
const path$1 = typeof input.path === "string" ? input.path.trim() : "";
|
|
6916
|
-
if (path$1.length === 0 || path$1 === ".") return platform;
|
|
6917
|
-
return {
|
|
6918
|
-
platform,
|
|
6919
|
-
path: path$1
|
|
6920
|
-
};
|
|
6921
|
-
}
|
|
6922
|
-
|
|
6923
7121
|
//#endregion
|
|
6924
7122
|
//#region src/commands/rule/init-interactive.ts
|
|
6925
|
-
const DEFAULT_RULE_NAME = "my-rule";
|
|
6926
|
-
/**
|
|
6927
|
-
* Parse comma-separated tags string into array.
|
|
6928
|
-
*/
|
|
6929
|
-
function parseTags(input) {
|
|
6930
|
-
if (typeof input !== "string") return [];
|
|
6931
|
-
if (input.trim().length === 0) return [];
|
|
6932
|
-
return input.split(",").map((tag) => tag.trim().toLowerCase()).filter((tag) => tag.length > 0);
|
|
6933
|
-
}
|
|
6934
|
-
/**
|
|
6935
|
-
* Validator for comma-separated tags input.
|
|
6936
|
-
*/
|
|
6937
|
-
function checkTags(value) {
|
|
6938
|
-
const tags = parseTags(value);
|
|
6939
|
-
const result = tagsSchema.safeParse(tags);
|
|
6940
|
-
if (!result.success) return result.error.issues[0]?.message;
|
|
6941
|
-
}
|
|
6942
7123
|
/**
|
|
6943
7124
|
* Run interactive init flow with clack prompts.
|
|
6944
7125
|
*/
|
|
6945
7126
|
async function initInteractive(options) {
|
|
6946
|
-
const { directory,
|
|
7127
|
+
const { directory, platformPaths } = options;
|
|
6947
7128
|
let { force } = options;
|
|
6948
7129
|
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";
|
|
6964
|
-
const validatedPlatforms = [];
|
|
6965
|
-
if (platformsOption) for (const platform of platformsOption) {
|
|
6966
|
-
if (!isSupportedPlatform(platform)) {
|
|
6967
|
-
p.cancel(`Unknown platform "${platform}"`);
|
|
6968
|
-
process.exit(1);
|
|
6969
|
-
}
|
|
6970
|
-
validatedPlatforms.push(platform);
|
|
6971
|
-
}
|
|
6972
|
-
const selectedPlatforms = validatedPlatforms.length > 0 ? validatedPlatforms : await (async () => {
|
|
6973
|
-
const platformChoices = await p.multiselect({
|
|
6974
|
-
message: "Platforms (select one or more)",
|
|
6975
|
-
options: PLATFORM_IDS.map((id) => ({
|
|
6976
|
-
value: id,
|
|
6977
|
-
label: id
|
|
6978
|
-
})),
|
|
6979
|
-
required: true
|
|
6980
|
-
});
|
|
6981
|
-
if (p.isCancel(platformChoices)) {
|
|
6982
|
-
p.cancel("Cancelled");
|
|
6983
|
-
process.exit(0);
|
|
6984
|
-
}
|
|
6985
|
-
return platformChoices;
|
|
6986
|
-
})();
|
|
6987
|
-
const platformEntries = await (async () => {
|
|
6988
|
-
if (selectedPlatforms.length === 0) return [];
|
|
6989
|
-
if (useSkillDefaults) return selectedPlatforms;
|
|
6990
|
-
const hasCompletePathMapping = selectedPlatforms.every((platform) => {
|
|
6991
|
-
const value = platformPaths?.[platform];
|
|
6992
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
6993
|
-
});
|
|
6994
|
-
if (hasCompletePathMapping) return selectedPlatforms.map((platform) => {
|
|
6995
|
-
const path$1 = platformPaths?.[platform]?.trim();
|
|
6996
|
-
if (!path$1 || path$1 === ".") return platform;
|
|
6997
|
-
return {
|
|
6998
|
-
platform,
|
|
6999
|
-
path: path$1
|
|
7000
|
-
};
|
|
7001
|
-
});
|
|
7002
|
-
if (selectedPlatforms.length === 1) return selectedPlatforms;
|
|
7003
|
-
const entries = [];
|
|
7004
|
-
for (const platform of selectedPlatforms) {
|
|
7005
|
-
const mappedPath = platformPaths?.[platform]?.trim();
|
|
7006
|
-
const suggestedPath = mappedPath ?? (await directoryExists(join(directory, platform)) ? platform : ".");
|
|
7007
|
-
const input = await p.text({
|
|
7008
|
-
message: `Folder for ${platform} files ('.' = same folder as agentrules.json)`,
|
|
7009
|
-
placeholder: suggestedPath,
|
|
7010
|
-
defaultValue: suggestedPath
|
|
7011
|
-
});
|
|
7012
|
-
if (p.isCancel(input)) {
|
|
7013
|
-
p.cancel("Cancelled");
|
|
7014
|
-
process.exit(0);
|
|
7015
|
-
}
|
|
7016
|
-
const trimmed = input.trim();
|
|
7017
|
-
const resolvedPath = trimmed.length > 0 ? trimmed : suggestedPath;
|
|
7018
|
-
if (resolvedPath === ".") entries.push(platform);
|
|
7019
|
-
else entries.push({
|
|
7020
|
-
platform,
|
|
7021
|
-
path: resolvedPath
|
|
7022
|
-
});
|
|
7023
|
-
}
|
|
7024
|
-
return entries;
|
|
7025
|
-
})();
|
|
7026
7130
|
const configPath = join(directory, RULE_CONFIG_FILENAME);
|
|
7027
7131
|
if (!force && await fileExists(configPath)) {
|
|
7028
7132
|
const overwrite = await p.confirm({
|
|
@@ -7035,81 +7139,58 @@ async function initInteractive(options) {
|
|
|
7035
7139
|
}
|
|
7036
7140
|
force = true;
|
|
7037
7141
|
}
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
}
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
placeholder: defaultTitle
|
|
7057
|
-
});
|
|
7058
|
-
},
|
|
7059
|
-
description: () => p.text({
|
|
7060
|
-
message: "Description",
|
|
7061
|
-
placeholder: "Describe what this rule does...",
|
|
7062
|
-
defaultValue: descriptionOption,
|
|
7063
|
-
validate: check(descriptionSchema)
|
|
7064
|
-
}),
|
|
7065
|
-
tags: () => p.text({
|
|
7066
|
-
message: "Tags (comma-separated, optional)",
|
|
7067
|
-
placeholder: "e.g., typescript, testing, react",
|
|
7068
|
-
validate: checkTags
|
|
7069
|
-
}),
|
|
7070
|
-
license: async () => {
|
|
7071
|
-
const choice = await p.select({
|
|
7072
|
-
message: "License",
|
|
7073
|
-
options: [...COMMON_LICENSES.map((id) => ({
|
|
7074
|
-
value: id,
|
|
7075
|
-
label: id
|
|
7076
|
-
})), {
|
|
7077
|
-
value: "__other__",
|
|
7078
|
-
label: "Other (enter SPDX identifier)"
|
|
7079
|
-
}],
|
|
7080
|
-
initialValue: defaultLicense
|
|
7081
|
-
});
|
|
7082
|
-
if (p.isCancel(choice)) {
|
|
7083
|
-
p.cancel("Cancelled");
|
|
7084
|
-
process.exit(0);
|
|
7085
|
-
}
|
|
7086
|
-
if (choice === "__other__") {
|
|
7087
|
-
const custom = await p.text({
|
|
7088
|
-
message: "License (SPDX identifier)",
|
|
7089
|
-
placeholder: "e.g., MPL-2.0, AGPL-3.0-only",
|
|
7090
|
-
validate: check(licenseSchema)
|
|
7091
|
-
});
|
|
7092
|
-
if (p.isCancel(custom)) {
|
|
7093
|
-
p.cancel("Cancelled");
|
|
7094
|
-
process.exit(0);
|
|
7095
|
-
}
|
|
7096
|
-
return custom;
|
|
7097
|
-
}
|
|
7098
|
-
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);
|
|
7099
7160
|
}
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
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("");
|
|
7104
7185
|
const initOptions = {
|
|
7105
7186
|
directory,
|
|
7106
|
-
name:
|
|
7107
|
-
type:
|
|
7108
|
-
title:
|
|
7109
|
-
description:
|
|
7110
|
-
tags:
|
|
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,
|
|
7111
7192
|
platforms: platformEntries,
|
|
7112
|
-
license:
|
|
7193
|
+
license: collected.license,
|
|
7113
7194
|
force
|
|
7114
7195
|
};
|
|
7115
7196
|
const initResult = await initRule(initOptions);
|
|
@@ -7582,7 +7663,7 @@ program.command("add <item>").description("Download and install a rule from the
|
|
|
7582
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) => {
|
|
7583
7664
|
const targetDir = directory ?? process.cwd();
|
|
7584
7665
|
const defaultName = directory ? basename(directory) : void 0;
|
|
7585
|
-
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);
|
|
7586
7667
|
const platformIds = [];
|
|
7587
7668
|
const platformPaths = {};
|
|
7588
7669
|
if (platformInputs) for (const input of platformInputs) {
|
|
@@ -7623,7 +7704,7 @@ program.command("init").description("Initialize a new rule").argument("[director
|
|
|
7623
7704
|
}
|
|
7624
7705
|
const nextSteps$1 = [
|
|
7625
7706
|
"Add your rule files in this directory",
|
|
7626
|
-
"Add tags
|
|
7707
|
+
"Add tags and features to agentrules.json",
|
|
7627
7708
|
`Run ${ui.command("agentrules publish")} to publish your rule`
|
|
7628
7709
|
];
|
|
7629
7710
|
log.print(`\n${ui.header("Next steps")}`);
|
|
@@ -7647,7 +7728,7 @@ program.command("init").description("Initialize a new rule").argument("[director
|
|
|
7647
7728
|
}
|
|
7648
7729
|
const nextSteps = [
|
|
7649
7730
|
"Add your rule files in this directory",
|
|
7650
|
-
"Add tags
|
|
7731
|
+
"Add tags and features to agentrules.json",
|
|
7651
7732
|
`Run ${ui.command("agentrules publish")} to publish your rule`
|
|
7652
7733
|
];
|
|
7653
7734
|
log.print(`\n${ui.header("Next steps")}`);
|
|
@@ -7656,13 +7737,13 @@ program.command("init").description("Initialize a new rule").argument("[director
|
|
|
7656
7737
|
program.command("validate").description("Validate an agentrules.json configuration").argument("[path]", "Path to agentrules.json or directory").action(handle(async (path$1) => {
|
|
7657
7738
|
const result = await validateRule({ path: path$1 });
|
|
7658
7739
|
if (result.valid && result.rule) {
|
|
7659
|
-
const p$
|
|
7660
|
-
const platforms = p$
|
|
7661
|
-
log.success(p$
|
|
7662
|
-
if (p$
|
|
7663
|
-
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));
|
|
7664
7745
|
log.print(ui.keyValue("Platforms", platforms));
|
|
7665
|
-
if (p$
|
|
7746
|
+
if (p$3.tags?.length) log.print(ui.keyValue("Tags", p$3.tags.join(", ")));
|
|
7666
7747
|
} else if (!result.valid) log.error(`Invalid: ${ui.path(result.configPath)}`);
|
|
7667
7748
|
if (result.errors.length > 0) {
|
|
7668
7749
|
log.print("");
|