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