@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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +489 -408
  3. 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 rule
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$2) => cleanRegex(p$2.source)).join("|")})$`);
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$2 = schema._zod.parent;
2372
- if (p$2) {
2373
- const pm = { ...this.get(p$2) ?? {} };
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$2) {
4379
- return theme.path(p$2);
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((p$2) => p$2.platform).join(", ");
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/lib/zod-validator.ts
6407
+ //#region src/commands/rule/init.ts
6408
+ /** Default rule name when none specified */
6409
+ const DEFAULT_RULE_NAME = "my-rule";
6197
6410
  /**
6198
- * Creates a validator function from a Zod schema.
6199
- * Returns error message if invalid, undefined if valid.
6411
+ * Detect if directory contains SKILL.md and extract frontmatter defaults.
6200
6412
  */
6201
- function check(schema) {
6202
- return (value) => {
6203
- const result = schema.safeParse(value);
6204
- if (!result.success) return result.error.issues[0]?.message;
6205
- return;
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.path,
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
- const skillPath = `${inputPath}/${SKILL_FILENAME}`;
6439
- const skillStat = await stat(skillPath).catch(() => null);
6440
- if (skillStat?.isFile()) return {
6441
- path: inputPath,
6442
- type: "skill",
6443
- entryFile: skillPath
6444
- };
6445
- return;
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 hasRequiredArgs = isDirectory ? options.platform !== void 0 && (options.name !== void 0 || inferred.name !== void 0) : options.name !== void 0 && options.platform !== void 0 && options.type !== void 0;
6499
- if (!(isInteractive || hasRequiredArgs)) {
6500
- if (isDirectory) throw new Error("Publishing a directory in non-interactive mode requires --platform (and --name if not in frontmatter).");
6501
- throw new Error("Publishing a single file in non-interactive mode requires --name, --platform, and --type.");
6502
- }
6503
- const selectedPlatforms = options.platform ? parsePlatformSelection(options.platform) : void 0;
6504
- if (selectedPlatforms && selectedPlatforms.length > 1) throw new Error("Quick publish requires exactly one --platform value.");
6505
- let selectedPlatform = selectedPlatforms?.[0] ? normalizePlatformInput(selectedPlatforms[0]) : inferred.platform;
6506
- if (!selectedPlatform) {
6507
- if (!isInteractive) throw new Error("Missing --platform");
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
- const platform = selectedPlatform;
6537
- const ruleType = selectedType;
6538
- const nameValue = options.name ?? inferred.name ?? await (async () => {
6539
- if (!isInteractive) throw new Error("Missing --name");
6540
- const input = await p$1.text({
6541
- message: "Rule name",
6542
- placeholder: "my-rule",
6543
- validate: check(nameSchema)
6544
- });
6545
- if (p$1.isCancel(input)) throw new Error("Cancelled");
6546
- return input;
6547
- })();
6548
- const normalizedName = normalizeName(nameValue);
6549
- const nameCheck = nameSchema.safeParse(normalizedName);
6550
- if (!nameCheck.success) throw new Error(nameCheck.error.issues[0]?.message ?? "Invalid name");
6551
- const defaultTitle = toTitleCase(normalizedName);
6552
- const finalTitle = options.title ?? await (async () => {
6553
- if (!isInteractive) return defaultTitle;
6554
- const input = await p$1.text({
6555
- message: "Title",
6556
- defaultValue: defaultTitle,
6557
- placeholder: defaultTitle
6558
- });
6559
- if (p$1.isCancel(input)) throw new Error("Cancelled");
6560
- return input;
6561
- })();
6562
- const finalDescription = options.description ?? await (async () => {
6563
- if (!isInteractive) return "";
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.header("Quick publish"));
6587
- log.print(ui.keyValue(isDirectory ? "Directory" : "File", ui.path(source.path)));
6588
- log.print(ui.keyValue("Name", ui.code(normalizedName)));
6589
- log.print(ui.keyValue("Title", finalTitle));
6590
- log.print(ui.keyValue("Description", finalDescription));
6591
- log.print(ui.keyValue("Platform", platform));
6592
- log.print(ui.keyValue("Type", ruleType));
6593
- if (finalTags.length > 0) log.print(ui.keyValue("Tags", finalTags.join(", ")));
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: normalizedName,
6604
- platform,
6605
- ruleType,
6606
- title: finalTitle,
6607
- description: finalDescription,
6608
- tags: finalTags,
6609
- license: finalLicense
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: [{ platform: inputs.platform }]
6900
+ platforms: platformEntries
6622
6901
  };
6623
6902
  if (inputs.source.type === "file") {
6624
6903
  const content = await readFile(inputs.source.path);
6625
- const bundlePath = getInstallPath({
6626
- platform: inputs.platform,
6627
- type: inputs.ruleType,
6628
- name: inputs.name,
6629
- scope: "project"
6630
- });
6631
- if (!bundlePath) throw new Error(`Type "${inputs.ruleType}" is not supported for project installs on platform "${inputs.platform}".`);
6632
- return {
6633
- name: inputs.name,
6634
- config: config$1,
6635
- platformFiles: [{
6636
- platform: inputs.platform,
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: [{ platform: inputs.platform }]
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, name: nameOption, title: titleOption, description: descriptionOption, platforms: platformsOption, platformPaths, license: licenseOption } = options;
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
- const result = await p.group({
7039
- name: () => {
7040
- const normalizedDefault = normalizeName(defaultName);
7041
- return p.text({
7042
- message: "Rule name",
7043
- placeholder: normalizedDefault,
7044
- defaultValue: normalizedDefault,
7045
- validate: (value) => {
7046
- if (!value || value.trim() === "") return;
7047
- return check(nameSchema)(value);
7048
- }
7049
- });
7050
- },
7051
- title: ({ results }) => {
7052
- const defaultTitle = titleOption ?? toTitleCase(results.name ?? defaultName);
7053
- return p.text({
7054
- message: "Title",
7055
- defaultValue: defaultTitle,
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
- }, { onCancel: () => {
7101
- p.cancel("Cancelled");
7102
- return process.exit(0);
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: result.name,
7107
- type: useSkillDefaults ? "skill" : void 0,
7108
- title: result.title.trim() || void 0,
7109
- description: result.description,
7110
- tags: parseTags(result.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: result.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$2) => p$2.split(",").map((s) => s.trim())).filter((p$2) => p$2.length > 0);
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 (recommended) and features (recommended) to agentrules.json",
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 (recommended) and features (recommended) to agentrules.json",
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$2 = result.rule;
7660
- const platforms = p$2.platforms.map((entry) => entry.platform).join(", ");
7661
- log.success(p$2.title);
7662
- if (p$2.description) log.print(ui.keyValue("Description", p$2.description));
7663
- log.print(ui.keyValue("License", p$2.license));
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$2.tags?.length) log.print(ui.keyValue("Tags", p$2.tags.join(", ")));
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("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentrules/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://agentrules.directory",