rulesync 7.18.2 → 7.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -3,8 +3,11 @@ import {
3
3
  ALL_FEATURES,
4
4
  ALL_FEATURES_WITH_WILDCARD,
5
5
  ALL_TOOL_TARGETS,
6
+ ALL_TOOL_TARGETS_WITH_WILDCARD,
7
+ CLIError,
6
8
  CommandsProcessor,
7
9
  ConfigResolver,
10
+ ErrorCodes,
8
11
  FETCH_CONCURRENCY_LIMIT,
9
12
  HooksProcessor,
10
13
  IgnoreProcessor,
@@ -45,6 +48,7 @@ import {
45
48
  SubagentsProcessor,
46
49
  checkPathTraversal,
47
50
  checkRulesyncDirExists,
51
+ createLogger,
48
52
  createTempDirectory,
49
53
  directoryExists,
50
54
  ensureDir,
@@ -65,14 +69,11 @@ import {
65
69
  removeTempDirectory,
66
70
  stringifyFrontmatter,
67
71
  writeFileContent
68
- } from "../chunk-JP3BFD7L.js";
72
+ } from "../chunk-J2ZF4SHC.js";
69
73
 
70
74
  // src/cli/index.ts
71
75
  import { Command } from "commander";
72
76
 
73
- // src/constants/announcements.ts
74
- var ANNOUNCEMENT = "".trim();
75
-
76
77
  // src/lib/fetch.ts
77
78
  import { join } from "path";
78
79
  import { Semaphore } from "es-toolkit/promise";
@@ -979,30 +980,36 @@ function formatFetchSummary(summary) {
979
980
  }
980
981
 
981
982
  // src/cli/commands/fetch.ts
982
- async function fetchCommand(options) {
983
+ async function fetchCommand(logger2, options) {
983
984
  const { source, ...fetchOptions } = options;
984
- logger.configure({
985
- verbose: fetchOptions.verbose ?? false,
986
- silent: fetchOptions.silent ?? false
987
- });
988
- logger.debug(`Fetching files from ${source}...`);
985
+ logger2.debug(`Fetching files from ${source}...`);
989
986
  try {
990
987
  const summary = await fetchFiles({
991
988
  source,
992
989
  options: fetchOptions
993
990
  });
991
+ if (logger2.jsonMode) {
992
+ const createdFiles = summary.files.filter((f) => f.status === "created").map((f) => f.relativePath);
993
+ const overwrittenFiles = summary.files.filter((f) => f.status === "overwritten").map((f) => f.relativePath);
994
+ const skippedFiles = summary.files.filter((f) => f.status === "skipped").map((f) => f.relativePath);
995
+ logger2.captureData("source", source);
996
+ logger2.captureData("path", fetchOptions.path);
997
+ logger2.captureData("created", createdFiles);
998
+ logger2.captureData("overwritten", overwrittenFiles);
999
+ logger2.captureData("skipped", skippedFiles);
1000
+ logger2.captureData("totalFetched", summary.created + summary.overwritten + summary.skipped);
1001
+ }
994
1002
  const output = formatFetchSummary(summary);
995
- logger.success(output);
1003
+ logger2.success(output);
996
1004
  if (summary.created + summary.overwritten === 0 && summary.skipped === 0) {
997
- logger.warn("No files were fetched.");
1005
+ logger2.warn("No files were fetched.");
998
1006
  }
999
1007
  } catch (error) {
1000
1008
  if (error instanceof GitHubClientError) {
1001
- logGitHubAuthHints(error);
1002
- } else {
1003
- logger.error(formatError(error));
1009
+ const authHint = error.statusCode === 401 || error.statusCode === 403 ? " Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable, or use `GITHUB_TOKEN=$(gh auth token) rulesync fetch ...`" : "";
1010
+ throw new CLIError(`GitHub API Error: ${error.message}.${authHint}`, ErrorCodes.FETCH_FAILED);
1004
1011
  }
1005
- process.exit(1);
1012
+ throw error;
1006
1013
  }
1007
1014
  }
1008
1015
 
@@ -1012,110 +1019,92 @@ function calculateTotalCount(result) {
1012
1019
  }
1013
1020
 
1014
1021
  // src/cli/commands/generate.ts
1015
- function logFeatureResult(params) {
1022
+ function logFeatureResult(logger2, params) {
1016
1023
  const { count, paths, featureName, isPreview, modePrefix } = params;
1017
1024
  if (count > 0) {
1018
1025
  if (isPreview) {
1019
- logger.info(`${modePrefix} Would write ${count} ${featureName}`);
1026
+ logger2.info(`${modePrefix} Would write ${count} ${featureName}`);
1020
1027
  } else {
1021
- logger.success(`Written ${count} ${featureName}`);
1028
+ logger2.success(`Written ${count} ${featureName}`);
1022
1029
  }
1023
1030
  for (const p of paths) {
1024
- logger.info(` ${p}`);
1031
+ logger2.info(` ${p}`);
1025
1032
  }
1026
1033
  }
1027
1034
  }
1028
- async function generateCommand(options) {
1035
+ async function generateCommand(logger2, options) {
1029
1036
  const config = await ConfigResolver.resolve(options);
1030
- logger.configure({
1031
- verbose: config.getVerbose(),
1032
- silent: config.getSilent()
1033
- });
1034
1037
  const check = config.getCheck();
1035
1038
  const isPreview = config.isPreviewMode();
1036
1039
  const modePrefix = isPreview ? "[DRY RUN]" : "";
1037
- logger.debug("Generating files...");
1040
+ logger2.debug("Generating files...");
1038
1041
  if (!await checkRulesyncDirExists({ baseDir: process.cwd() })) {
1039
- logger.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
1040
- process.exit(1);
1042
+ throw new CLIError(
1043
+ ".rulesync directory not found. Run 'rulesync init' first.",
1044
+ ErrorCodes.RULESYNC_DIR_NOT_FOUND
1045
+ );
1041
1046
  }
1042
- logger.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
1047
+ logger2.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
1043
1048
  const features = config.getFeatures();
1044
1049
  if (features.includes("ignore")) {
1045
- logger.debug("Generating ignore files...");
1050
+ logger2.debug("Generating ignore files...");
1046
1051
  }
1047
1052
  if (features.includes("mcp")) {
1048
- logger.debug("Generating MCP files...");
1053
+ logger2.debug("Generating MCP files...");
1049
1054
  }
1050
1055
  if (features.includes("commands")) {
1051
- logger.debug("Generating command files...");
1056
+ logger2.debug("Generating command files...");
1052
1057
  }
1053
1058
  if (features.includes("subagents")) {
1054
- logger.debug("Generating subagent files...");
1059
+ logger2.debug("Generating subagent files...");
1055
1060
  }
1056
1061
  if (features.includes("skills")) {
1057
- logger.debug("Generating skill files...");
1062
+ logger2.debug("Generating skill files...");
1058
1063
  }
1059
1064
  if (features.includes("hooks")) {
1060
- logger.debug("Generating hooks...");
1065
+ logger2.debug("Generating hooks...");
1061
1066
  }
1062
1067
  if (features.includes("rules")) {
1063
- logger.debug("Generating rule files...");
1068
+ logger2.debug("Generating rule files...");
1064
1069
  }
1065
1070
  const result = await generate({ config });
1066
- logFeatureResult({
1067
- count: result.ignoreCount,
1068
- paths: result.ignorePaths,
1069
- featureName: "ignore file(s)",
1070
- isPreview,
1071
- modePrefix
1072
- });
1073
- logFeatureResult({
1074
- count: result.mcpCount,
1075
- paths: result.mcpPaths,
1076
- featureName: "MCP configuration(s)",
1077
- isPreview,
1078
- modePrefix
1079
- });
1080
- logFeatureResult({
1081
- count: result.commandsCount,
1082
- paths: result.commandsPaths,
1083
- featureName: "command(s)",
1084
- isPreview,
1085
- modePrefix
1086
- });
1087
- logFeatureResult({
1088
- count: result.subagentsCount,
1089
- paths: result.subagentsPaths,
1090
- featureName: "subagent(s)",
1091
- isPreview,
1092
- modePrefix
1093
- });
1094
- logFeatureResult({
1095
- count: result.skillsCount,
1096
- paths: result.skillsPaths,
1097
- featureName: "skill(s)",
1098
- isPreview,
1099
- modePrefix
1100
- });
1101
- logFeatureResult({
1102
- count: result.hooksCount,
1103
- paths: result.hooksPaths,
1104
- featureName: "hooks file(s)",
1105
- isPreview,
1106
- modePrefix
1107
- });
1108
- logFeatureResult({
1109
- count: result.rulesCount,
1110
- paths: result.rulesPaths,
1111
- featureName: "rule(s)",
1112
- isPreview,
1113
- modePrefix
1114
- });
1115
1071
  const totalGenerated = calculateTotalCount(result);
1072
+ const featureResults = {
1073
+ ignore: { count: result.ignoreCount, paths: result.ignorePaths },
1074
+ mcp: { count: result.mcpCount, paths: result.mcpPaths },
1075
+ commands: { count: result.commandsCount, paths: result.commandsPaths },
1076
+ subagents: { count: result.subagentsCount, paths: result.subagentsPaths },
1077
+ skills: { count: result.skillsCount, paths: result.skillsPaths },
1078
+ hooks: { count: result.hooksCount, paths: result.hooksPaths },
1079
+ rules: { count: result.rulesCount, paths: result.rulesPaths }
1080
+ };
1081
+ const featureLabels = {
1082
+ rules: (count) => `${count === 1 ? "rule" : "rules"}`,
1083
+ ignore: (count) => `${count === 1 ? "ignore file" : "ignore files"}`,
1084
+ mcp: (count) => `${count === 1 ? "MCP file" : "MCP files"}`,
1085
+ commands: (count) => `${count === 1 ? "command" : "commands"}`,
1086
+ subagents: (count) => `${count === 1 ? "subagent" : "subagents"}`,
1087
+ skills: (count) => `${count === 1 ? "skill" : "skills"}`,
1088
+ hooks: (count) => `${count === 1 ? "hooks file" : "hooks files"}`
1089
+ };
1090
+ for (const [feature, data] of Object.entries(featureResults)) {
1091
+ logFeatureResult(logger2, {
1092
+ count: data.count,
1093
+ paths: data.paths,
1094
+ featureName: featureLabels[feature]?.(data.count) ?? feature,
1095
+ isPreview,
1096
+ modePrefix
1097
+ });
1098
+ }
1099
+ if (logger2.jsonMode) {
1100
+ logger2.captureData("features", featureResults);
1101
+ logger2.captureData("totalFiles", totalGenerated);
1102
+ logger2.captureData("hasDiff", result.hasDiff);
1103
+ logger2.captureData("skills", result.skills ?? []);
1104
+ }
1116
1105
  if (totalGenerated === 0) {
1117
1106
  const enabledFeatures = features.join(", ");
1118
- logger.info(`\u2713 All files are up to date (${enabledFeatures})`);
1107
+ logger2.info(`\u2713 All files are up to date (${enabledFeatures})`);
1119
1108
  return;
1120
1109
  }
1121
1110
  const parts = [];
@@ -1127,129 +1116,209 @@ async function generateCommand(options) {
1127
1116
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
1128
1117
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
1129
1118
  if (isPreview) {
1130
- logger.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1119
+ logger2.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1131
1120
  } else {
1132
- logger.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1121
+ logger2.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1133
1122
  }
1134
1123
  if (check) {
1135
1124
  if (result.hasDiff) {
1136
- logger.error("\u274C Files are not up to date. Run 'rulesync generate' to update.");
1137
- process.exit(1);
1125
+ throw new CLIError(
1126
+ "Files are not up to date. Run 'rulesync generate' to update.",
1127
+ ErrorCodes.GENERATION_FAILED
1128
+ );
1138
1129
  } else {
1139
- logger.success("\u2713 All files are up to date.");
1130
+ logger2.success("\u2713 All files are up to date.");
1140
1131
  }
1141
1132
  }
1142
1133
  }
1143
1134
 
1144
1135
  // src/cli/commands/gitignore.ts
1145
1136
  import { join as join2 } from "path";
1146
- var RULESYNC_HEADER = "# Generated by Rulesync";
1147
- var LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
1148
- var RULESYNC_IGNORE_ENTRIES = [
1149
- // Rulesync curated (fetched) skills
1150
- `${RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH}/`,
1137
+
1138
+ // src/cli/commands/gitignore-entries.ts
1139
+ var GITIGNORE_ENTRY_REGISTRY = [
1140
+ // Common / general
1141
+ { target: "common", feature: "general", entry: `${RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH}/` },
1142
+ { target: "common", feature: "general", entry: ".rulesync/rules/*.local.md" },
1143
+ { target: "common", feature: "general", entry: "rulesync.local.jsonc" },
1144
+ { target: "common", feature: "general", entry: "!.rulesync/.aiignore" },
1151
1145
  // AGENTS.md
1152
- "**/AGENTS.md",
1153
- "**/.agents/",
1154
- // Augment
1155
- "**/.augmentignore",
1156
- "**/.augment/rules/",
1157
- "**/.augment-guidelines",
1146
+ { target: "agentsmd", feature: "rules", entry: "**/AGENTS.md" },
1147
+ { target: "agentsmd", feature: "rules", entry: "**/.agents/" },
1148
+ // Augment Code
1149
+ { target: "augmentcode", feature: "rules", entry: "**/.augment/rules/" },
1150
+ { target: "augmentcode", feature: "rules", entry: "**/.augment-guidelines" },
1151
+ { target: "augmentcode", feature: "ignore", entry: "**/.augmentignore" },
1158
1152
  // Claude Code
1159
- "**/CLAUDE.md",
1160
- "**/CLAUDE.local.md",
1161
- "**/.claude/CLAUDE.md",
1162
- "**/.claude/CLAUDE.local.md",
1163
- "**/.claude/memories/",
1164
- "**/.claude/rules/",
1165
- "**/.claude/commands/",
1166
- "**/.claude/agents/",
1167
- "**/.claude/skills/",
1168
- "**/.claude/settings.local.json",
1169
- "**/.mcp.json",
1153
+ { target: "claudecode", feature: "rules", entry: "**/CLAUDE.md" },
1154
+ { target: "claudecode", feature: "rules", entry: "**/CLAUDE.local.md" },
1155
+ { target: "claudecode", feature: "rules", entry: "**/.claude/CLAUDE.md" },
1156
+ { target: "claudecode", feature: "rules", entry: "**/.claude/CLAUDE.local.md" },
1157
+ { target: "claudecode", feature: "rules", entry: "**/.claude/rules/" },
1158
+ { target: "claudecode", feature: "commands", entry: "**/.claude/commands/" },
1159
+ { target: "claudecode", feature: "subagents", entry: "**/.claude/agents/" },
1160
+ { target: "claudecode", feature: "skills", entry: "**/.claude/skills/" },
1161
+ { target: "claudecode", feature: "mcp", entry: "**/.mcp.json" },
1162
+ { target: "claudecode", feature: "general", entry: "**/.claude/memories/" },
1163
+ { target: "claudecode", feature: "general", entry: "**/.claude/settings.local.json" },
1170
1164
  // Cline
1171
- "**/.clinerules/",
1172
- "**/.clinerules/workflows/",
1173
- "**/.clineignore",
1174
- "**/.cline/mcp.json",
1175
- // Codex
1176
- "**/.codexignore",
1177
- "**/.codex/memories/",
1178
- "**/.codex/skills/",
1179
- "**/.codex/agents/",
1180
- "**/.codex/config.toml",
1165
+ { target: "cline", feature: "rules", entry: "**/.clinerules/" },
1166
+ { target: "cline", feature: "commands", entry: "**/.clinerules/workflows/" },
1167
+ { target: "cline", feature: "ignore", entry: "**/.clineignore" },
1168
+ { target: "cline", feature: "mcp", entry: "**/.cline/mcp.json" },
1169
+ // Codex CLI
1170
+ { target: "codexcli", feature: "ignore", entry: "**/.codexignore" },
1171
+ { target: "codexcli", feature: "skills", entry: "**/.codex/skills/" },
1172
+ { target: "codexcli", feature: "subagents", entry: "**/.codex/agents/" },
1173
+ { target: "codexcli", feature: "general", entry: "**/.codex/memories/" },
1174
+ { target: "codexcli", feature: "general", entry: "**/.codex/config.toml" },
1181
1175
  // Cursor
1182
- "**/.cursor/",
1183
- "**/.cursorignore",
1176
+ { target: "cursor", feature: "rules", entry: "**/.cursor/" },
1177
+ { target: "cursor", feature: "ignore", entry: "**/.cursorignore" },
1184
1178
  // Factory Droid
1185
- "**/.factory/rules/",
1186
- "**/.factory/commands/",
1187
- "**/.factory/droids/",
1188
- "**/.factory/skills/",
1189
- "**/.factory/mcp.json",
1190
- "**/.factory/settings.json",
1191
- // Gemini
1192
- "**/GEMINI.md",
1193
- "**/.gemini/memories/",
1194
- "**/.gemini/commands/",
1195
- "**/.gemini/subagents/",
1196
- "**/.gemini/skills/",
1197
- "**/.geminiignore",
1179
+ { target: "factorydroid", feature: "rules", entry: "**/.factory/rules/" },
1180
+ { target: "factorydroid", feature: "commands", entry: "**/.factory/commands/" },
1181
+ { target: "factorydroid", feature: "subagents", entry: "**/.factory/droids/" },
1182
+ { target: "factorydroid", feature: "skills", entry: "**/.factory/skills/" },
1183
+ { target: "factorydroid", feature: "mcp", entry: "**/.factory/mcp.json" },
1184
+ { target: "factorydroid", feature: "general", entry: "**/.factory/settings.json" },
1185
+ // Gemini CLI
1186
+ { target: "geminicli", feature: "rules", entry: "**/GEMINI.md" },
1187
+ { target: "geminicli", feature: "commands", entry: "**/.gemini/commands/" },
1188
+ { target: "geminicli", feature: "subagents", entry: "**/.gemini/subagents/" },
1189
+ { target: "geminicli", feature: "skills", entry: "**/.gemini/skills/" },
1190
+ { target: "geminicli", feature: "ignore", entry: "**/.geminiignore" },
1191
+ { target: "geminicli", feature: "general", entry: "**/.gemini/memories/" },
1198
1192
  // Goose
1199
- "**/.goosehints",
1200
- "**/.goose/",
1201
- "**/.gooseignore",
1193
+ { target: "goose", feature: "rules", entry: "**/.goosehints" },
1194
+ { target: "goose", feature: "rules", entry: "**/.goose/" },
1195
+ { target: "goose", feature: "ignore", entry: "**/.gooseignore" },
1202
1196
  // GitHub Copilot
1203
- "**/.github/copilot-instructions.md",
1204
- "**/.github/instructions/",
1205
- "**/.github/prompts/",
1206
- "**/.github/agents/",
1207
- "**/.github/skills/",
1208
- "**/.github/hooks/",
1209
- "**/.vscode/mcp.json",
1197
+ { target: "copilot", feature: "rules", entry: "**/.github/copilot-instructions.md" },
1198
+ { target: "copilot", feature: "rules", entry: "**/.github/instructions/" },
1199
+ { target: "copilot", feature: "commands", entry: "**/.github/prompts/" },
1200
+ { target: "copilot", feature: "subagents", entry: "**/.github/agents/" },
1201
+ { target: "copilot", feature: "skills", entry: "**/.github/skills/" },
1202
+ { target: "copilot", feature: "hooks", entry: "**/.github/hooks/" },
1203
+ { target: "copilot", feature: "mcp", entry: "**/.vscode/mcp.json" },
1210
1204
  // Junie
1211
- "**/.junie/guidelines.md",
1212
- "**/.junie/mcp.json",
1213
- "**/.junie/skills/",
1214
- "**/.junie/agents/",
1205
+ { target: "junie", feature: "rules", entry: "**/.junie/guidelines.md" },
1206
+ { target: "junie", feature: "mcp", entry: "**/.junie/mcp.json" },
1207
+ { target: "junie", feature: "skills", entry: "**/.junie/skills/" },
1208
+ { target: "junie", feature: "subagents", entry: "**/.junie/agents/" },
1215
1209
  // Kilo Code
1216
- "**/.kilocode/rules/",
1217
- "**/.kilocode/skills/",
1218
- "**/.kilocode/workflows/",
1219
- "**/.kilocode/mcp.json",
1220
- "**/.kilocodeignore",
1210
+ { target: "kilo", feature: "rules", entry: "**/.kilocode/rules/" },
1211
+ { target: "kilo", feature: "skills", entry: "**/.kilocode/skills/" },
1212
+ { target: "kilo", feature: "commands", entry: "**/.kilocode/workflows/" },
1213
+ { target: "kilo", feature: "mcp", entry: "**/.kilocode/mcp.json" },
1214
+ { target: "kilo", feature: "ignore", entry: "**/.kilocodeignore" },
1221
1215
  // Kiro
1222
- "**/.kiro/steering/",
1223
- "**/.kiro/prompts/",
1224
- "**/.kiro/skills/",
1225
- "**/.kiro/agents/",
1226
- "**/.kiro/settings/mcp.json",
1227
- "**/.aiignore",
1216
+ { target: "kiro", feature: "rules", entry: "**/.kiro/steering/" },
1217
+ { target: "kiro", feature: "commands", entry: "**/.kiro/prompts/" },
1218
+ { target: "kiro", feature: "skills", entry: "**/.kiro/skills/" },
1219
+ { target: "kiro", feature: "subagents", entry: "**/.kiro/agents/" },
1220
+ { target: "kiro", feature: "mcp", entry: "**/.kiro/settings/mcp.json" },
1221
+ { target: "kiro", feature: "ignore", entry: "**/.aiignore" },
1228
1222
  // OpenCode
1229
- "**/.opencode/memories/",
1230
- "**/.opencode/command/",
1231
- "**/.opencode/agent/",
1232
- "**/.opencode/skill/",
1233
- "**/.opencode/plugins/",
1234
- // Qwen
1235
- "**/QWEN.md",
1236
- "**/.qwen/memories/",
1223
+ { target: "opencode", feature: "commands", entry: "**/.opencode/command/" },
1224
+ { target: "opencode", feature: "subagents", entry: "**/.opencode/agent/" },
1225
+ { target: "opencode", feature: "skills", entry: "**/.opencode/skill/" },
1226
+ { target: "opencode", feature: "mcp", entry: "**/.opencode/plugins/" },
1227
+ { target: "opencode", feature: "general", entry: "**/.opencode/memories/" },
1228
+ // Qwen Code
1229
+ { target: "qwencode", feature: "rules", entry: "**/QWEN.md" },
1230
+ { target: "qwencode", feature: "general", entry: "**/.qwen/memories/" },
1237
1231
  // Replit
1238
- "**/replit.md",
1232
+ { target: "replit", feature: "rules", entry: "**/replit.md" },
1239
1233
  // Roo
1240
- "**/.roo/rules/",
1241
- "**/.roo/skills/",
1242
- "**/.rooignore",
1243
- "**/.roo/mcp.json",
1244
- "**/.roo/subagents/",
1234
+ { target: "roo", feature: "rules", entry: "**/.roo/rules/" },
1235
+ { target: "roo", feature: "skills", entry: "**/.roo/skills/" },
1236
+ { target: "roo", feature: "ignore", entry: "**/.rooignore" },
1237
+ { target: "roo", feature: "mcp", entry: "**/.roo/mcp.json" },
1238
+ { target: "roo", feature: "subagents", entry: "**/.roo/subagents/" },
1245
1239
  // Warp
1246
- "**/.warp/",
1247
- "**/WARP.md",
1248
- // Others
1249
- ".rulesync/rules/*.local.md",
1250
- "rulesync.local.jsonc",
1251
- "!.rulesync/.aiignore"
1240
+ { target: "warp", feature: "rules", entry: "**/.warp/" },
1241
+ { target: "warp", feature: "rules", entry: "**/WARP.md" }
1252
1242
  ];
1243
+ var ALL_GITIGNORE_ENTRIES = GITIGNORE_ENTRY_REGISTRY.map(
1244
+ (tag) => tag.entry
1245
+ );
1246
+ var isTargetSelected = (target, selectedTargets) => {
1247
+ if (target === "common") return true;
1248
+ if (!selectedTargets || selectedTargets.length === 0) return true;
1249
+ if (selectedTargets.includes("*")) return true;
1250
+ return selectedTargets.includes(target);
1251
+ };
1252
+ var isFeatureSelected = (feature, target, features) => {
1253
+ if (feature === "general") return true;
1254
+ if (!features) return true;
1255
+ if (Array.isArray(features)) {
1256
+ if (features.length === 0) return true;
1257
+ if (features.includes("*")) return true;
1258
+ return features.includes(feature);
1259
+ }
1260
+ if (target === "common") return true;
1261
+ const targetFeatures = features[target];
1262
+ if (!targetFeatures) return true;
1263
+ if (targetFeatures.includes("*")) return true;
1264
+ return targetFeatures.includes(feature);
1265
+ };
1266
+ var warnInvalidTargets = (targets) => {
1267
+ const validTargets = new Set(ALL_TOOL_TARGETS_WITH_WILDCARD);
1268
+ for (const target of targets) {
1269
+ if (!validTargets.has(target)) {
1270
+ logger.warn(
1271
+ `Unknown target '${target}'. Valid targets: ${ALL_TOOL_TARGETS_WITH_WILDCARD.join(", ")}`
1272
+ );
1273
+ }
1274
+ }
1275
+ };
1276
+ var warnInvalidFeatures = (features) => {
1277
+ const validFeatures = new Set(ALL_FEATURES_WITH_WILDCARD);
1278
+ if (Array.isArray(features)) {
1279
+ for (const feature of features) {
1280
+ if (!validFeatures.has(feature)) {
1281
+ logger.warn(
1282
+ `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
1283
+ );
1284
+ }
1285
+ }
1286
+ } else {
1287
+ for (const targetFeatures of Object.values(features)) {
1288
+ if (!targetFeatures) continue;
1289
+ for (const feature of targetFeatures) {
1290
+ if (!validFeatures.has(feature)) {
1291
+ logger.warn(
1292
+ `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
1293
+ );
1294
+ }
1295
+ }
1296
+ }
1297
+ }
1298
+ };
1299
+ var filterGitignoreEntries = (params) => {
1300
+ const { targets, features } = params ?? {};
1301
+ if (targets && targets.length > 0) {
1302
+ warnInvalidTargets(targets);
1303
+ }
1304
+ if (features) {
1305
+ warnInvalidFeatures(features);
1306
+ }
1307
+ const seen = /* @__PURE__ */ new Set();
1308
+ const result = [];
1309
+ for (const tag of GITIGNORE_ENTRY_REGISTRY) {
1310
+ if (!isTargetSelected(tag.target, targets)) continue;
1311
+ if (!isFeatureSelected(tag.feature, tag.target, features)) continue;
1312
+ if (seen.has(tag.entry)) continue;
1313
+ seen.add(tag.entry);
1314
+ result.push(tag.entry);
1315
+ }
1316
+ return result;
1317
+ };
1318
+
1319
+ // src/cli/commands/gitignore.ts
1320
+ var RULESYNC_HEADER = "# Generated by Rulesync";
1321
+ var LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
1253
1322
  var isRulesyncHeader = (line) => {
1254
1323
  const trimmed = line.trim();
1255
1324
  return trimmed === RULESYNC_HEADER || trimmed === LEGACY_RULESYNC_HEADER;
@@ -1259,7 +1328,7 @@ var isRulesyncEntry = (line) => {
1259
1328
  if (trimmed === "" || isRulesyncHeader(line)) {
1260
1329
  return false;
1261
1330
  }
1262
- return RULESYNC_IGNORE_ENTRIES.includes(trimmed);
1331
+ return ALL_GITIGNORE_ENTRIES.includes(trimmed);
1263
1332
  };
1264
1333
  var removeExistingRulesyncEntries = (content) => {
1265
1334
  const lines = content.split("\n");
@@ -1299,63 +1368,89 @@ var removeExistingRulesyncEntries = (content) => {
1299
1368
  }
1300
1369
  return result;
1301
1370
  };
1302
- var gitignoreCommand = async () => {
1371
+ var gitignoreCommand = async (logger2, options) => {
1303
1372
  const gitignorePath = join2(process.cwd(), ".gitignore");
1304
1373
  let gitignoreContent = "";
1305
1374
  if (await fileExists(gitignorePath)) {
1306
1375
  gitignoreContent = await readFileContent(gitignorePath);
1307
1376
  }
1308
1377
  const cleanedContent = removeExistingRulesyncEntries(gitignoreContent);
1309
- const rulesyncBlock = [RULESYNC_HEADER, ...RULESYNC_IGNORE_ENTRIES].join("\n");
1378
+ const filteredEntries = filterGitignoreEntries({
1379
+ targets: options?.targets,
1380
+ features: options?.features
1381
+ });
1382
+ const existingEntries = new Set(
1383
+ gitignoreContent.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line))
1384
+ );
1385
+ const alreadyExistedEntries = filteredEntries.filter((entry) => existingEntries.has(entry));
1386
+ const entriesToAdd = filteredEntries.filter((entry) => !existingEntries.has(entry));
1387
+ const rulesyncBlock = [RULESYNC_HEADER, ...filteredEntries].join("\n");
1310
1388
  const newContent = cleanedContent.trim() ? `${cleanedContent.trimEnd()}
1311
1389
 
1312
1390
  ${rulesyncBlock}
1313
1391
  ` : `${rulesyncBlock}
1314
1392
  `;
1315
1393
  if (gitignoreContent === newContent) {
1316
- logger.success(".gitignore is already up to date");
1394
+ if (logger2.jsonMode) {
1395
+ logger2.captureData("entriesAdded", []);
1396
+ logger2.captureData("gitignorePath", gitignorePath);
1397
+ logger2.captureData("alreadyExisted", filteredEntries);
1398
+ }
1399
+ logger2.success(".gitignore is already up to date");
1317
1400
  return;
1318
1401
  }
1319
1402
  await writeFileContent(gitignorePath, newContent);
1320
- logger.success("Updated .gitignore with rulesync entries:");
1321
- for (const entry of RULESYNC_IGNORE_ENTRIES) {
1322
- logger.info(` ${entry}`);
1403
+ if (logger2.jsonMode) {
1404
+ logger2.captureData("entriesAdded", entriesToAdd);
1405
+ logger2.captureData("gitignorePath", gitignorePath);
1406
+ logger2.captureData("alreadyExisted", alreadyExistedEntries);
1323
1407
  }
1324
- logger.info("");
1325
- logger.info(
1408
+ logger2.success("Updated .gitignore with rulesync entries:");
1409
+ for (const entry of filteredEntries) {
1410
+ logger2.info(` ${entry}`);
1411
+ }
1412
+ logger2.info("");
1413
+ logger2.info(
1326
1414
  "\u{1F4A1} If you're using Google Antigravity, note that rules, workflows, and skills won't load if they're gitignored."
1327
1415
  );
1328
- logger.info(" You can add the following to .git/info/exclude instead:");
1329
- logger.info(" **/.agent/rules/");
1330
- logger.info(" **/.agent/workflows/");
1331
- logger.info(" **/.agent/skills/");
1332
- logger.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
1416
+ logger2.info(" You can add the following to .git/info/exclude instead:");
1417
+ logger2.info(" **/.agent/rules/");
1418
+ logger2.info(" **/.agent/workflows/");
1419
+ logger2.info(" **/.agent/skills/");
1420
+ logger2.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
1333
1421
  };
1334
1422
 
1335
1423
  // src/cli/commands/import.ts
1336
- async function importCommand(options) {
1424
+ async function importCommand(logger2, options) {
1337
1425
  if (!options.targets) {
1338
- logger.error("No tools found in --targets");
1339
- process.exit(1);
1426
+ throw new CLIError("No tools found in --targets", ErrorCodes.IMPORT_FAILED);
1340
1427
  }
1341
1428
  if (options.targets.length > 1) {
1342
- logger.error("Only one tool can be imported at a time");
1343
- process.exit(1);
1429
+ throw new CLIError("Only one tool can be imported at a time", ErrorCodes.IMPORT_FAILED);
1344
1430
  }
1345
1431
  const config = await ConfigResolver.resolve(options);
1346
- logger.configure({
1347
- verbose: config.getVerbose(),
1348
- silent: config.getSilent()
1349
- });
1350
1432
  const tool = config.getTargets()[0];
1351
- logger.debug(`Importing files from ${tool}...`);
1433
+ logger2.debug(`Importing files from ${tool}...`);
1352
1434
  const result = await importFromTool({ config, tool });
1353
1435
  const totalImported = calculateTotalCount(result);
1354
1436
  if (totalImported === 0) {
1355
1437
  const enabledFeatures = config.getFeatures().join(", ");
1356
- logger.warn(`No files imported for enabled features: ${enabledFeatures}`);
1438
+ logger2.warn(`No files imported for enabled features: ${enabledFeatures}`);
1357
1439
  return;
1358
1440
  }
1441
+ if (logger2.jsonMode) {
1442
+ logger2.captureData("tool", tool);
1443
+ logger2.captureData("features", {
1444
+ rules: { count: result.rulesCount },
1445
+ ignore: { count: result.ignoreCount },
1446
+ mcp: { count: result.mcpCount },
1447
+ commands: { count: result.commandsCount },
1448
+ subagents: { count: result.subagentsCount },
1449
+ skills: { count: result.skillsCount },
1450
+ hooks: { count: result.hooksCount }
1451
+ });
1452
+ logger2.captureData("totalFiles", totalImported);
1453
+ }
1359
1454
  const parts = [];
1360
1455
  if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
1361
1456
  if (result.ignoreCount > 0) parts.push(`${result.ignoreCount} ignore files`);
@@ -1364,7 +1459,7 @@ async function importCommand(options) {
1364
1459
  if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
1365
1460
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
1366
1461
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
1367
- logger.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
1462
+ logger2.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
1368
1463
  }
1369
1464
 
1370
1465
  // src/lib/init.ts
@@ -1593,28 +1688,38 @@ async function writeIfNotExists(path2, content) {
1593
1688
  }
1594
1689
 
1595
1690
  // src/cli/commands/init.ts
1596
- async function initCommand() {
1597
- logger.debug("Initializing rulesync...");
1691
+ async function initCommand(logger2) {
1692
+ logger2.debug("Initializing rulesync...");
1598
1693
  await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
1599
1694
  const result = await init();
1695
+ const createdFiles = [];
1696
+ const skippedFiles = [];
1600
1697
  for (const file of result.sampleFiles) {
1601
1698
  if (file.created) {
1602
- logger.success(`Created ${file.path}`);
1699
+ createdFiles.push(file.path);
1700
+ logger2.success(`Created ${file.path}`);
1603
1701
  } else {
1604
- logger.info(`Skipped ${file.path} (already exists)`);
1702
+ skippedFiles.push(file.path);
1703
+ logger2.info(`Skipped ${file.path} (already exists)`);
1605
1704
  }
1606
1705
  }
1607
1706
  if (result.configFile.created) {
1608
- logger.success(`Created ${result.configFile.path}`);
1707
+ createdFiles.push(result.configFile.path);
1708
+ logger2.success(`Created ${result.configFile.path}`);
1609
1709
  } else {
1610
- logger.info(`Skipped ${result.configFile.path} (already exists)`);
1710
+ skippedFiles.push(result.configFile.path);
1711
+ logger2.info(`Skipped ${result.configFile.path} (already exists)`);
1611
1712
  }
1612
- logger.success("rulesync initialized successfully!");
1613
- logger.info("Next steps:");
1614
- logger.info(
1713
+ if (logger2.jsonMode) {
1714
+ logger2.captureData("created", createdFiles);
1715
+ logger2.captureData("skipped", skippedFiles);
1716
+ }
1717
+ logger2.success("rulesync initialized successfully!");
1718
+ logger2.info("Next steps:");
1719
+ logger2.info(
1615
1720
  `1. Edit ${RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${RULESYNC_RELATIVE_DIR_PATH}/skills/*/${SKILL_FILE_NAME}, ${RULESYNC_MCP_RELATIVE_FILE_PATH}, ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} and ${RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`
1616
1721
  );
1617
- logger.info("2. Run 'rulesync generate' to create configuration files");
1722
+ logger2.info("2. Run 'rulesync generate' to create configuration files");
1618
1723
  }
1619
1724
 
1620
1725
  // src/lib/sources.ts
@@ -2340,11 +2445,7 @@ async function fetchSourceViaGit(params) {
2340
2445
  }
2341
2446
 
2342
2447
  // src/cli/commands/install.ts
2343
- async function installCommand(options) {
2344
- logger.configure({
2345
- verbose: options.verbose ?? false,
2346
- silent: options.silent ?? false
2347
- });
2448
+ async function installCommand(logger2, options) {
2348
2449
  const config = await ConfigResolver.resolve({
2349
2450
  configPath: options.configPath,
2350
2451
  verbose: options.verbose,
@@ -2352,10 +2453,10 @@ async function installCommand(options) {
2352
2453
  });
2353
2454
  const sources = config.getSources();
2354
2455
  if (sources.length === 0) {
2355
- logger.warn("No sources defined in configuration. Nothing to install.");
2456
+ logger2.warn("No sources defined in configuration. Nothing to install.");
2356
2457
  return;
2357
2458
  }
2358
- logger.debug(`Installing skills from ${sources.length} source(s)...`);
2459
+ logger2.debug(`Installing skills from ${sources.length} source(s)...`);
2359
2460
  const result = await resolveAndFetchSources({
2360
2461
  sources,
2361
2462
  baseDir: process.cwd(),
@@ -2365,12 +2466,16 @@ async function installCommand(options) {
2365
2466
  token: options.token
2366
2467
  }
2367
2468
  });
2469
+ if (logger2.jsonMode) {
2470
+ logger2.captureData("sourcesProcessed", result.sourcesProcessed);
2471
+ logger2.captureData("skillsFetched", result.fetchedSkillCount);
2472
+ }
2368
2473
  if (result.fetchedSkillCount > 0) {
2369
- logger.success(
2474
+ logger2.success(
2370
2475
  `Installed ${result.fetchedSkillCount} skill(s) from ${result.sourcesProcessed} source(s).`
2371
2476
  );
2372
2477
  } else {
2373
- logger.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
2478
+ logger2.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
2374
2479
  }
2375
2480
  }
2376
2481
 
@@ -3785,7 +3890,7 @@ var rulesyncTool = {
3785
3890
  };
3786
3891
 
3787
3892
  // src/cli/commands/mcp.ts
3788
- async function mcpCommand({ version }) {
3893
+ async function mcpCommand(logger2, { version }) {
3789
3894
  const server = new FastMCP({
3790
3895
  name: "Rulesync MCP Server",
3791
3896
  // eslint-disable-next-line no-type-assertion/no-type-assertion
@@ -3793,7 +3898,7 @@ async function mcpCommand({ version }) {
3793
3898
  instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, subagents and skills for any AI agents. It should be used when you need those files."
3794
3899
  });
3795
3900
  server.addTool(rulesyncTool);
3796
- logger.info("Rulesync MCP server started via stdio");
3901
+ logger2.info("Rulesync MCP server started via stdio");
3797
3902
  void server.start({
3798
3903
  transportType: "stdio"
3799
3904
  });
@@ -4109,158 +4214,185 @@ To upgrade, run:
4109
4214
  }
4110
4215
 
4111
4216
  // src/cli/commands/update.ts
4112
- async function updateCommand(currentVersion, options) {
4113
- const { check = false, force = false, verbose = false, silent = false, token } = options;
4114
- logger.configure({ verbose, silent });
4217
+ async function updateCommand(logger2, currentVersion, options) {
4218
+ const { check = false, force = false, token } = options;
4115
4219
  try {
4116
4220
  const environment = detectExecutionEnvironment();
4117
- logger.debug(`Detected environment: ${environment}`);
4221
+ logger2.debug(`Detected environment: ${environment}`);
4118
4222
  if (environment === "npm") {
4119
- logger.info(getNpmUpgradeInstructions());
4223
+ logger2.info(getNpmUpgradeInstructions());
4120
4224
  return;
4121
4225
  }
4122
4226
  if (environment === "homebrew") {
4123
- logger.info(getHomebrewUpgradeInstructions());
4227
+ logger2.info(getHomebrewUpgradeInstructions());
4124
4228
  return;
4125
4229
  }
4126
4230
  if (check) {
4127
- logger.info("Checking for updates...");
4231
+ logger2.info("Checking for updates...");
4128
4232
  const updateCheck = await checkForUpdate(currentVersion, token);
4233
+ if (logger2.jsonMode) {
4234
+ logger2.captureData("currentVersion", updateCheck.currentVersion);
4235
+ logger2.captureData("latestVersion", updateCheck.latestVersion);
4236
+ logger2.captureData("updateAvailable", updateCheck.hasUpdate);
4237
+ logger2.captureData(
4238
+ "message",
4239
+ updateCheck.hasUpdate ? `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}` : `Already at the latest version (${updateCheck.currentVersion})`
4240
+ );
4241
+ }
4129
4242
  if (updateCheck.hasUpdate) {
4130
- logger.success(
4243
+ logger2.success(
4131
4244
  `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}`
4132
4245
  );
4133
4246
  } else {
4134
- logger.info(`Already at the latest version (${updateCheck.currentVersion})`);
4247
+ logger2.info(`Already at the latest version (${updateCheck.currentVersion})`);
4135
4248
  }
4136
4249
  return;
4137
4250
  }
4138
- logger.info("Checking for updates...");
4251
+ logger2.info("Checking for updates...");
4139
4252
  const message = await performBinaryUpdate(currentVersion, { force, token });
4140
- logger.success(message);
4253
+ logger2.success(message);
4141
4254
  } catch (error) {
4142
4255
  if (error instanceof GitHubClientError) {
4143
- logGitHubAuthHints(error);
4256
+ const authHint = error.statusCode === 401 || error.statusCode === 403 ? " Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable, or use `GITHUB_TOKEN=$(gh auth token) rulesync update ...`" : "";
4257
+ throw new CLIError(
4258
+ `GitHub API Error: ${error.message}.${authHint}`,
4259
+ ErrorCodes.UPDATE_FAILED
4260
+ );
4144
4261
  } else if (error instanceof UpdatePermissionError) {
4145
- logger.error(error.message);
4146
- logger.info("Tip: Run with elevated privileges (e.g., sudo rulesync update)");
4147
- } else {
4148
- logger.error(formatError(error));
4262
+ throw new CLIError(
4263
+ `${error.message} Tip: Run with elevated privileges (e.g., sudo rulesync update)`,
4264
+ ErrorCodes.UPDATE_FAILED
4265
+ );
4149
4266
  }
4150
- process.exit(1);
4267
+ throw error;
4151
4268
  }
4152
4269
  }
4153
4270
 
4154
4271
  // src/cli/index.ts
4155
- var getVersion = () => "7.18.2";
4272
+ var getVersion = () => "7.19.0";
4273
+ function wrapCommand(name, errorCode, handler) {
4274
+ return async (...args) => {
4275
+ const command = args[args.length - 1];
4276
+ const options = args[args.length - 2];
4277
+ const positionalArgs = args.slice(0, -2);
4278
+ const globalOpts = command.parent?.opts() ?? {};
4279
+ const logger2 = createLogger(getVersion());
4280
+ logger2.setJsonMode(Boolean(globalOpts.json), name);
4281
+ logger2.configure({
4282
+ verbose: Boolean(globalOpts.verbose) || Boolean(options.verbose),
4283
+ silent: Boolean(globalOpts.silent) || Boolean(options.silent)
4284
+ });
4285
+ try {
4286
+ await handler(logger2, options, globalOpts, positionalArgs);
4287
+ if (globalOpts.json) {
4288
+ logger2.outputJson(true);
4289
+ }
4290
+ } catch (error) {
4291
+ const code = error instanceof CLIError ? error.code : errorCode;
4292
+ const errorArg = error instanceof Error ? error : formatError(error);
4293
+ logger2.error(errorArg, code);
4294
+ process.exit(error instanceof CLIError ? error.exitCode : 1);
4295
+ }
4296
+ };
4297
+ }
4156
4298
  var main = async () => {
4157
4299
  const program = new Command();
4158
4300
  const version = getVersion();
4159
- program.hook("postAction", () => {
4160
- if (ANNOUNCEMENT.length > 0) {
4161
- logger.info(ANNOUNCEMENT);
4301
+ program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version").option("-j, --json", "Output results as JSON");
4302
+ program.command("init").description("Initialize rulesync in current directory").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4303
+ wrapCommand("init", "INIT_FAILED", async (logger2) => {
4304
+ await initCommand(logger2);
4305
+ })
4306
+ );
4307
+ program.command("gitignore").description("Add generated files to .gitignore").option(
4308
+ "-t, --targets <tools>",
4309
+ "Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)",
4310
+ (value) => {
4311
+ return value.split(",").map((t) => t.trim()).filter(Boolean);
4162
4312
  }
4163
- });
4164
- program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version");
4165
- program.command("init").description("Initialize rulesync in current directory").action(initCommand);
4166
- program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
4313
+ ).option(
4314
+ "-f, --features <features>",
4315
+ `Comma-separated list of features to include (${ALL_FEATURES.join(",")}) or '*' for all`,
4316
+ (value) => {
4317
+ return value.split(",").map((f) => f.trim()).filter(Boolean);
4318
+ }
4319
+ ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4320
+ wrapCommand("gitignore", "GITIGNORE_FAILED", async (logger2, options) => {
4321
+ await gitignoreCommand(logger2, {
4322
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4323
+ targets: options.targets,
4324
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4325
+ features: options.features
4326
+ });
4327
+ })
4328
+ );
4167
4329
  program.command("fetch <source>").description("Fetch files from a Git repository (GitHub/GitLab)").option(
4168
4330
  "-t, --target <target>",
4169
4331
  "Target format to interpret files as (e.g., 'rulesync', 'claudecode'). Default: rulesync"
4170
4332
  ).option(
4171
4333
  "-f, --features <features>",
4172
4334
  `Comma-separated list of features to fetch (${ALL_FEATURES.join(",")}) or '*' for all`,
4173
- (value) => {
4174
- return value.split(",").map((f) => f.trim());
4175
- }
4335
+ (value) => value.split(",").map((f) => f.trim())
4176
4336
  ).option("-r, --ref <ref>", "Branch, tag, or commit SHA to fetch from").option("-p, --path <path>", "Subdirectory path within the repository").option("-o, --output <dir>", "Output directory (default: .rulesync)").option(
4177
4337
  "-c, --conflict <strategy>",
4178
4338
  "Conflict resolution strategy: skip, overwrite (default: overwrite)"
4179
- ).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(async (source, options) => {
4180
- await fetchCommand({
4181
- source,
4182
- target: options.target,
4183
- features: options.features,
4184
- ref: options.ref,
4185
- path: options.path,
4186
- output: options.output,
4187
- conflict: options.conflict,
4188
- token: options.token,
4189
- verbose: options.verbose,
4190
- silent: options.silent
4191
- });
4192
- });
4339
+ ).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4340
+ wrapCommand("fetch", "FETCH_FAILED", async (logger2, options, _globalOpts, positionalArgs) => {
4341
+ const source = positionalArgs[0];
4342
+ await fetchCommand(logger2, { ...options, source });
4343
+ })
4344
+ );
4193
4345
  program.command("import").description("Import configurations from AI tools to rulesync format").option(
4194
4346
  "-t, --targets <tool>",
4195
4347
  "Tool to import from (e.g., 'copilot', 'cursor', 'cline')",
4196
- (value) => {
4197
- return value.split(",").map((t) => t.trim());
4198
- }
4348
+ (value) => value.split(",").map((t) => t.trim())
4199
4349
  ).option(
4200
4350
  "-f, --features <features>",
4201
4351
  `Comma-separated list of features to import (${ALL_FEATURES.join(",")}) or '*' for all`,
4202
- (value) => {
4203
- return value.split(",").map((f) => f.trim());
4204
- }
4205
- ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(async (options) => {
4206
- try {
4207
- await importCommand({
4208
- targets: options.targets,
4209
- features: options.features,
4210
- verbose: options.verbose,
4211
- silent: options.silent,
4212
- configPath: options.config,
4213
- global: options.global
4214
- });
4215
- } catch (error) {
4216
- logger.error(formatError(error));
4217
- process.exit(1);
4218
- }
4219
- });
4220
- program.command("mcp").description("Start MCP server for rulesync").action(async () => {
4221
- try {
4222
- await mcpCommand({ version });
4223
- } catch (error) {
4224
- logger.error(formatError(error));
4225
- process.exit(1);
4226
- }
4227
- });
4352
+ (value) => value.split(",").map((f) => f.trim())
4353
+ ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(
4354
+ wrapCommand("import", "IMPORT_FAILED", async (logger2, options) => {
4355
+ await importCommand(logger2, options);
4356
+ })
4357
+ );
4358
+ program.command("mcp").description("Start MCP server for rulesync").action(
4359
+ wrapCommand("mcp", "MCP_FAILED", async (logger2, _options) => {
4360
+ await mcpCommand(logger2, { version });
4361
+ })
4362
+ );
4228
4363
  program.command("install").description("Install skills from declarative sources in rulesync.jsonc").option("--update", "Force re-resolve all source refs, ignoring lockfile").option(
4229
4364
  "--frozen",
4230
4365
  "Fail if lockfile is missing or out of sync (for CI); fetches missing skills using locked refs"
4231
- ).option("--token <token>", "GitHub token for private repos").option("-c, --config <path>", "Path to configuration file").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(async (options) => {
4232
- try {
4233
- await installCommand({
4366
+ ).option("--token <token>", "GitHub token for private repos").option("-c, --config <path>", "Path to configuration file").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4367
+ wrapCommand("install", "INSTALL_FAILED", async (logger2, options) => {
4368
+ await installCommand(logger2, {
4369
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4234
4370
  update: options.update,
4371
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4235
4372
  frozen: options.frozen,
4373
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4236
4374
  token: options.token,
4375
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4237
4376
  configPath: options.config,
4377
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4238
4378
  verbose: options.verbose,
4379
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
4239
4380
  silent: options.silent
4240
4381
  });
4241
- } catch (error) {
4242
- logger.error(formatError(error));
4243
- process.exit(1);
4244
- }
4245
- });
4382
+ })
4383
+ );
4246
4384
  program.command("generate").description("Generate configuration files for AI tools").option(
4247
4385
  "-t, --targets <tools>",
4248
4386
  "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)",
4249
- (value) => {
4250
- return value.split(",").map((t) => t.trim());
4251
- }
4387
+ (value) => value.split(",").map((t) => t.trim())
4252
4388
  ).option(
4253
4389
  "-f, --features <features>",
4254
4390
  `Comma-separated list of features to generate (${ALL_FEATURES.join(",")}) or '*' for all`,
4255
- (value) => {
4256
- return value.split(",").map((f) => f.trim());
4257
- }
4391
+ (value) => value.split(",").map((f) => f.trim())
4258
4392
  ).option("--delete", "Delete all existing files in output directories before generating").option(
4259
4393
  "-b, --base-dir <paths>",
4260
4394
  "Base directories to generate files (comma-separated for multiple paths)",
4261
- (value) => {
4262
- return value.split(",").map((p) => p.trim());
4263
- }
4395
+ (value) => value.split(",").map((p) => p.trim())
4264
4396
  ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-c, --config <path>", "Path to configuration file").option("-g, --global", "Generate for global(user scope) configuration files").option(
4265
4397
  "--simulate-commands",
4266
4398
  "Generate simulated commands. This feature is only available for copilot, cursor and codexcli."
@@ -4270,40 +4402,19 @@ var main = async () => {
4270
4402
  ).option(
4271
4403
  "--simulate-skills",
4272
4404
  "Generate simulated skills. This feature is only available for copilot, cursor and codexcli."
4273
- ).option("--dry-run", "Dry run: show changes without writing files").option("--check", "Check if files are up to date (exits with code 1 if changes needed)").action(async (options) => {
4274
- try {
4275
- await generateCommand({
4276
- targets: options.targets,
4277
- features: options.features,
4278
- verbose: options.verbose,
4279
- silent: options.silent,
4280
- delete: options.delete,
4281
- baseDirs: options.baseDir,
4282
- configPath: options.config,
4283
- global: options.global,
4284
- simulateCommands: options.simulateCommands,
4285
- simulateSubagents: options.simulateSubagents,
4286
- simulateSkills: options.simulateSkills,
4287
- dryRun: options.dryRun,
4288
- check: options.check
4289
- });
4290
- } catch (error) {
4291
- logger.error(formatError(error));
4292
- process.exit(1);
4293
- }
4294
- });
4295
- program.command("update").description("Update rulesync to the latest version").option("--check", "Check for updates without installing").option("--force", "Force update even if already at latest version").option("--token <token>", "GitHub token for API access").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(async (options) => {
4296
- await updateCommand(version, {
4297
- check: options.check,
4298
- force: options.force,
4299
- token: options.token,
4300
- verbose: options.verbose,
4301
- silent: options.silent
4302
- });
4303
- });
4405
+ ).option("--dry-run", "Dry run: show changes without writing files").option("--check", "Check if files are up to date (exits with code 1 if changes needed)").action(
4406
+ wrapCommand("generate", "GENERATION_FAILED", async (logger2, options) => {
4407
+ await generateCommand(logger2, options);
4408
+ })
4409
+ );
4410
+ program.command("update").description("Update rulesync to the latest version").option("--check", "Check for updates without installing").option("--force", "Force update even if already at latest version").option("--token <token>", "GitHub token for API access").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4411
+ wrapCommand("update", "UPDATE_FAILED", async (logger2, options) => {
4412
+ await updateCommand(logger2, version, options);
4413
+ })
4414
+ );
4304
4415
  program.parse();
4305
4416
  };
4306
4417
  main().catch((error) => {
4307
- logger.error(formatError(error));
4418
+ console.error(formatError(error));
4308
4419
  process.exit(1);
4309
4420
  });