encoding-aware-fs 0.1.2 → 0.1.4

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/index.js CHANGED
@@ -25813,6 +25813,36 @@ var init_read_file = __esm({
25813
25813
  }
25814
25814
  });
25815
25815
 
25816
+ // src/encoding/line-endings.ts
25817
+ function detectLineEnding(buffer) {
25818
+ let crlf = 0;
25819
+ let lf = 0;
25820
+ for (let i = 0; i < buffer.length; i++) {
25821
+ if (buffer[i] === 13 && i + 1 < buffer.length && buffer[i + 1] === 10) {
25822
+ crlf++;
25823
+ i++;
25824
+ } else if (buffer[i] === 10) {
25825
+ lf++;
25826
+ }
25827
+ }
25828
+ if (crlf === 0 && lf === 0) {
25829
+ return process.platform === "win32" ? "CRLF" : "LF";
25830
+ }
25831
+ return crlf >= lf ? "CRLF" : "LF";
25832
+ }
25833
+ function restoreLineEndings(text, style) {
25834
+ const normalized = text.replace(/\r\n/g, "\n");
25835
+ if (style === "CRLF") {
25836
+ return normalized.replace(/\n/g, "\r\n");
25837
+ }
25838
+ return normalized;
25839
+ }
25840
+ var init_line_endings = __esm({
25841
+ "src/encoding/line-endings.ts"() {
25842
+ "use strict";
25843
+ }
25844
+ });
25845
+
25816
25846
  // src/tools/write-file.ts
25817
25847
  function registerWriteFile(server2, config3, allowedDirectories) {
25818
25848
  server2.registerTool(
@@ -25829,8 +25859,10 @@ function registerWriteFile(server2, config3, allowedDirectories) {
25829
25859
  async (args) => {
25830
25860
  const validPath = await validatePath(args.path, allowedDirectories);
25831
25861
  let targetEncoding = config3.sourceEncoding || "GB18030";
25862
+ let lineEndingStyle = process.platform === "win32" ? "CRLF" : "LF";
25832
25863
  try {
25833
- await fs5.access(validPath);
25864
+ const existingBuffer = await fs5.readFile(validPath);
25865
+ lineEndingStyle = detectLineEnding(existingBuffer);
25834
25866
  const detection = await detectEncoding(validPath);
25835
25867
  if (detection.encoding && isUtf8Encoding(detection.encoding)) {
25836
25868
  targetEncoding = "UTF-8";
@@ -25839,7 +25871,8 @@ function registerWriteFile(server2, config3, allowedDirectories) {
25839
25871
  }
25840
25872
  } catch {
25841
25873
  }
25842
- const encoded = encodeFromUtf8(args.content, targetEncoding);
25874
+ const finalContent = restoreLineEndings(args.content, lineEndingStyle);
25875
+ const encoded = encodeFromUtf8(finalContent, targetEncoding);
25843
25876
  try {
25844
25877
  await fs5.writeFile(validPath, encoded, { flag: "wx" });
25845
25878
  } catch (error2) {
@@ -25875,6 +25908,7 @@ var init_write_file = __esm({
25875
25908
  init_path_validation();
25876
25909
  init_detector();
25877
25910
  init_converter();
25911
+ init_line_endings();
25878
25912
  }
25879
25913
  });
25880
25914
 
@@ -26400,6 +26434,7 @@ function registerEditFile(server2, config3, allowedDirectories) {
26400
26434
  async (args) => {
26401
26435
  const validPath = await validatePath(args.path, allowedDirectories);
26402
26436
  const buffer = await fs6.readFile(validPath);
26437
+ const originalLineEnding = detectLineEnding(buffer);
26403
26438
  const detection = await detectEncoding(validPath);
26404
26439
  let originalEncoding = "UTF-8";
26405
26440
  if (detection.encoding && detection.confidence >= config3.confidenceThreshold && isGBEncoding(detection.encoding)) {
@@ -26411,7 +26446,8 @@ function registerEditFile(server2, config3, allowedDirectories) {
26411
26446
  const modifiedContent = applyEditsToContent(utf8Content, args.edits);
26412
26447
  const diff = createUnifiedDiff(utf8Content, modifiedContent, args.path);
26413
26448
  if (!args.dryRun) {
26414
- const encoded = isGBEncoding(originalEncoding) ? encodeFromUtf8(modifiedContent, originalEncoding) : Buffer.from(modifiedContent, "utf-8");
26449
+ const finalContent = restoreLineEndings(modifiedContent, originalLineEnding);
26450
+ const encoded = isGBEncoding(originalEncoding) ? encodeFromUtf8(finalContent, originalEncoding) : Buffer.from(finalContent, "utf-8");
26415
26451
  const tempPath = `${validPath}.${(0, import_crypto2.randomBytes)(16).toString("hex")}.tmp`;
26416
26452
  try {
26417
26453
  await fs6.writeFile(tempPath, encoded);
@@ -26441,6 +26477,7 @@ var init_edit_file = __esm({
26441
26477
  init_path_validation();
26442
26478
  init_detector();
26443
26479
  init_converter();
26480
+ init_line_endings();
26444
26481
  }
26445
26482
  });
26446
26483
 
@@ -26539,6 +26576,10 @@ var init_config_io = __esm({
26539
26576
  // src/uninstaller.ts
26540
26577
  var uninstaller_exports = {};
26541
26578
  __export(uninstaller_exports, {
26579
+ detectInstalled: () => detectInstalled,
26580
+ isOpenCodeJsoncEmpty: () => isOpenCodeJsoncEmpty,
26581
+ removeOpenCodeConfig: () => removeOpenCodeConfig,
26582
+ removeRuleFile: () => removeRuleFile,
26542
26583
  runUninstaller: () => runUninstaller
26543
26584
  });
26544
26585
  async function fileExists(filePath) {
@@ -26561,8 +26602,18 @@ async function removeDirIfEmpty(dirPath) {
26561
26602
  function isEmptyMcpJson(data) {
26562
26603
  return data && typeof data === "object" && data.mcpServers && typeof data.mcpServers === "object" && Object.keys(data.mcpServers).length === 0 && Object.keys(data).length === 1;
26563
26604
  }
26564
- function isEmptyOpenCodeJsonc(data) {
26565
- return data && typeof data === "object" && data.mcp && typeof data.mcp === "object" && Object.keys(data.mcp).length === 0 && Object.keys(data).length === 1;
26605
+ function isOpenCodeJsoncEmpty(data) {
26606
+ if (!data || typeof data !== "object")
26607
+ return false;
26608
+ const keys = Object.keys(data);
26609
+ return keys.every((key) => {
26610
+ const val = data[key];
26611
+ if (Array.isArray(val))
26612
+ return val.length === 0;
26613
+ if (typeof val === "object" && val !== null)
26614
+ return Object.keys(val).length === 0;
26615
+ return false;
26616
+ });
26566
26617
  }
26567
26618
  async function detectInstalled(cwd) {
26568
26619
  const found = [];
@@ -26570,24 +26621,31 @@ async function detectInstalled(cwd) {
26570
26621
  const mcpJson = await readJsonFile(mcpJsonPath);
26571
26622
  const claudeSkillPath = path5.join(cwd, ".claude", "skills", "encoding-aware-fs", "SKILL.md");
26572
26623
  const hasClaudeSkill = await fileExists(claudeSkillPath);
26573
- if (mcpJson?.mcpServers?.["encoding-aware-fs"] || hasClaudeSkill) {
26624
+ const claudeRulePath = path5.join(cwd, ".claude", "rules", "encoding-aware-fs.md");
26625
+ const hasClaudeRule = await fileExists(claudeRulePath);
26626
+ if (mcpJson?.mcpServers?.["encoding-aware-fs"] || hasClaudeSkill || hasClaudeRule) {
26574
26627
  found.push({
26575
26628
  type: "claude-code",
26576
26629
  name: "Claude Code (.mcp.json)",
26577
26630
  configPath: mcpJsonPath,
26578
- skillPath: hasClaudeSkill ? claudeSkillPath : void 0
26631
+ skillPath: hasClaudeSkill ? claudeSkillPath : void 0,
26632
+ rulePath: hasClaudeRule ? claudeRulePath : void 0
26579
26633
  });
26580
26634
  }
26581
26635
  const openCodePath = path5.join(cwd, "opencode.jsonc");
26582
26636
  const openCode = await readJsonFile(openCodePath);
26583
26637
  const openCodeSkillPath = path5.join(cwd, ".agents", "skills", "encoding-aware-fs", "SKILL.md");
26584
26638
  const hasOpenCodeSkill = await fileExists(openCodeSkillPath);
26585
- if (openCode?.mcp?.["encoding-aware-fs"] || hasOpenCodeSkill) {
26639
+ const openCodeRulePath = path5.join(cwd, ".agents", "rules", "encoding-aware-fs.md");
26640
+ const hasOpenCodeRule = await fileExists(openCodeRulePath);
26641
+ const hasOpenCodeInstructions = Array.isArray(openCode?.instructions) && openCode.instructions.includes(".agents/rules/encoding-aware-fs.md");
26642
+ if (openCode?.mcp?.["encoding-aware-fs"] || hasOpenCodeSkill || hasOpenCodeRule || hasOpenCodeInstructions) {
26586
26643
  found.push({
26587
26644
  type: "opencode",
26588
26645
  name: "OpenCode (opencode.jsonc)",
26589
26646
  configPath: openCodePath,
26590
- skillPath: hasOpenCodeSkill ? openCodeSkillPath : void 0
26647
+ skillPath: hasOpenCodeSkill ? openCodeSkillPath : void 0,
26648
+ rulePath: hasOpenCodeRule ? openCodeRulePath : void 0
26591
26649
  });
26592
26650
  }
26593
26651
  return found;
@@ -26605,17 +26663,28 @@ async function removeClaudeCodeEntry(configPath) {
26605
26663
  console.log(` \u2713 Removed encoding-aware-fs entry from ${configPath}`);
26606
26664
  }
26607
26665
  }
26608
- async function removeOpenCodeEntry(configPath) {
26666
+ async function removeOpenCodeConfig(configPath) {
26609
26667
  const data = await readJsonFile(configPath);
26610
- if (!data?.mcp?.["encoding-aware-fs"])
26668
+ if (!data)
26611
26669
  return;
26612
- delete data.mcp["encoding-aware-fs"];
26613
- if (isEmptyOpenCodeJsonc(data)) {
26670
+ if (data.mcp?.["encoding-aware-fs"]) {
26671
+ delete data.mcp["encoding-aware-fs"];
26672
+ if (Object.keys(data.mcp).length === 0)
26673
+ delete data.mcp;
26674
+ }
26675
+ if (Array.isArray(data.instructions)) {
26676
+ data.instructions = data.instructions.filter(
26677
+ (i) => i !== ".agents/rules/encoding-aware-fs.md"
26678
+ );
26679
+ if (data.instructions.length === 0)
26680
+ delete data.instructions;
26681
+ }
26682
+ if (isOpenCodeJsoncEmpty(data) || Object.keys(data).length === 0) {
26614
26683
  await fs9.unlink(configPath);
26615
26684
  console.log(` \u2713 Removed ${configPath} (was empty after cleanup)`);
26616
26685
  } else {
26617
26686
  await writeJsonFile(configPath, data);
26618
- console.log(` \u2713 Removed encoding-aware-fs entry from ${configPath}`);
26687
+ console.log(` \u2713 Removed encoding-aware-fs entries from ${configPath}`);
26619
26688
  }
26620
26689
  }
26621
26690
  async function removeSkillFiles(platform) {
@@ -26636,6 +26705,22 @@ async function removeSkillFiles(platform) {
26636
26705
  const parentDir = path5.dirname(skillsDir);
26637
26706
  await removeDirIfEmpty(parentDir);
26638
26707
  }
26708
+ async function removeRuleFile(platform) {
26709
+ if (!platform.rulePath)
26710
+ return;
26711
+ try {
26712
+ await fs9.unlink(platform.rulePath);
26713
+ console.log(` \u2713 Removed ${platform.rulePath}`);
26714
+ } catch (err) {
26715
+ if (err.code !== "ENOENT")
26716
+ throw err;
26717
+ return;
26718
+ }
26719
+ const ruleDir = path5.dirname(platform.rulePath);
26720
+ await removeDirIfEmpty(ruleDir);
26721
+ const parentDir = path5.dirname(ruleDir);
26722
+ await removeDirIfEmpty(parentDir);
26723
+ }
26639
26724
  async function runUninstaller() {
26640
26725
  const cwd = process.cwd();
26641
26726
  console.log();
@@ -26655,10 +26740,13 @@ async function runUninstaller() {
26655
26740
  if (p.skillPath) {
26656
26741
  console.log(` + Skill file: ${p.skillPath}`);
26657
26742
  }
26743
+ if (p.rulePath) {
26744
+ console.log(` + Rule file: ${p.rulePath}`);
26745
+ }
26658
26746
  }
26659
26747
  console.log();
26660
26748
  const proceed = await (0, import_prompts.confirm)({
26661
- message: "Remove encoding-aware-fs MCP config entries and skill files?",
26749
+ message: "Remove encoding-aware-fs MCP config entries, skill files, and rule files?",
26662
26750
  default: false
26663
26751
  });
26664
26752
  if (!proceed) {
@@ -26670,12 +26758,15 @@ async function runUninstaller() {
26670
26758
  if (p.type === "claude-code") {
26671
26759
  await removeClaudeCodeEntry(p.configPath);
26672
26760
  } else if (p.type === "opencode") {
26673
- await removeOpenCodeEntry(p.configPath);
26761
+ await removeOpenCodeConfig(p.configPath);
26674
26762
  }
26675
26763
  }
26676
26764
  for (const p of installed) {
26677
26765
  await removeSkillFiles(p);
26678
26766
  }
26767
+ for (const p of installed) {
26768
+ await removeRuleFile(p);
26769
+ }
26679
26770
  const encodingConfigPath = path5.join(cwd, ".encoding-converter.json");
26680
26771
  try {
26681
26772
  await fs9.access(encodingConfigPath);
@@ -26700,6 +26791,8 @@ var init_uninstaller = __esm({
26700
26791
  // src/installer.ts
26701
26792
  var installer_exports = {};
26702
26793
  __export(installer_exports, {
26794
+ addOpenCodeInstructions: () => addOpenCodeInstructions,
26795
+ copyRuleFile: () => copyRuleFile,
26703
26796
  runInstaller: () => runInstaller
26704
26797
  });
26705
26798
  function checkNodeVersion() {
@@ -26765,6 +26858,33 @@ async function copySkillFile(cwd, platform) {
26765
26858
  await fs10.copyFile(sourcePath, targetPath);
26766
26859
  console.log(` \u2713 Installed skill \u2192 ${targetPath}`);
26767
26860
  }
26861
+ async function copyRuleFile(cwd, platform) {
26862
+ const sourcePath = path6.join(path6.resolve(__dirname, ".."), "skills", platform, "RULE.md");
26863
+ try {
26864
+ await fs10.access(sourcePath);
26865
+ } catch {
26866
+ console.log(` \u26A0 Rule file template not found, skipping`);
26867
+ return;
26868
+ }
26869
+ const ruleDir = platform === "claude-code" ? path6.join(cwd, ".claude", "rules") : path6.join(cwd, ".agents", "rules");
26870
+ const rulePath = path6.join(ruleDir, "encoding-aware-fs.md");
26871
+ await fs10.mkdir(ruleDir, { recursive: true });
26872
+ await fs10.copyFile(sourcePath, rulePath);
26873
+ console.log(` \u2713 Installed rule \u2192 ${rulePath}`);
26874
+ }
26875
+ async function addOpenCodeInstructions(cwd) {
26876
+ const configPath = path6.join(cwd, "opencode.jsonc");
26877
+ const existing = await readJsonFile(configPath);
26878
+ const merged = existing ?? {};
26879
+ if (!merged.instructions) {
26880
+ merged.instructions = [];
26881
+ }
26882
+ const rulePath = ".agents/rules/encoding-aware-fs.md";
26883
+ if (!merged.instructions.includes(rulePath)) {
26884
+ merged.instructions.push(rulePath);
26885
+ }
26886
+ await writeJsonFile(configPath, merged);
26887
+ }
26768
26888
  async function ensureEncodingConfig(cwd) {
26769
26889
  const configPath = path6.join(cwd, ".encoding-converter.json");
26770
26890
  try {
@@ -26828,6 +26948,15 @@ async function runInstaller() {
26828
26948
  for (const platform of platforms) {
26829
26949
  await copySkillFile(cwd, platform);
26830
26950
  }
26951
+ console.log();
26952
+ console.log("Rule files:");
26953
+ for (const platform of platforms) {
26954
+ await copyRuleFile(cwd, platform);
26955
+ }
26956
+ if (platforms.includes("opencode")) {
26957
+ await addOpenCodeInstructions(cwd);
26958
+ console.log(" \u2713 Added rule to opencode.jsonc instructions");
26959
+ }
26831
26960
  await ensureEncodingConfig(cwd);
26832
26961
  console.log();
26833
26962
  console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
@@ -26840,7 +26969,7 @@ async function runInstaller() {
26840
26969
  console.log("\u2502 OpenCode: restart opencode \u2502");
26841
26970
  }
26842
26971
  console.log("\u2502 \u2502");
26843
- console.log("\u2502 Skill files installed for AI discovery. \u2502");
26972
+ console.log("\u2502 Rule + Skill files installed for AI. \u2502");
26844
26973
  console.log("\u2502 The MCP server will start automatically \u2502");
26845
26974
  console.log("\u2502 when your AI tool connects. \u2502");
26846
26975
  console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
package/dist/server.js CHANGED
@@ -25050,6 +25050,33 @@ function registerReadFile(server2, config3, allowedDirectories) {
25050
25050
  // src/tools/write-file.ts
25051
25051
  var fs5 = __toESM(require("fs/promises"));
25052
25052
  var import_crypto = require("crypto");
25053
+
25054
+ // src/encoding/line-endings.ts
25055
+ function detectLineEnding(buffer) {
25056
+ let crlf = 0;
25057
+ let lf = 0;
25058
+ for (let i = 0; i < buffer.length; i++) {
25059
+ if (buffer[i] === 13 && i + 1 < buffer.length && buffer[i + 1] === 10) {
25060
+ crlf++;
25061
+ i++;
25062
+ } else if (buffer[i] === 10) {
25063
+ lf++;
25064
+ }
25065
+ }
25066
+ if (crlf === 0 && lf === 0) {
25067
+ return process.platform === "win32" ? "CRLF" : "LF";
25068
+ }
25069
+ return crlf >= lf ? "CRLF" : "LF";
25070
+ }
25071
+ function restoreLineEndings(text, style) {
25072
+ const normalized = text.replace(/\r\n/g, "\n");
25073
+ if (style === "CRLF") {
25074
+ return normalized.replace(/\n/g, "\r\n");
25075
+ }
25076
+ return normalized;
25077
+ }
25078
+
25079
+ // src/tools/write-file.ts
25053
25080
  function registerWriteFile(server2, config3, allowedDirectories) {
25054
25081
  server2.registerTool(
25055
25082
  "write_file",
@@ -25065,8 +25092,10 @@ function registerWriteFile(server2, config3, allowedDirectories) {
25065
25092
  async (args) => {
25066
25093
  const validPath = await validatePath(args.path, allowedDirectories);
25067
25094
  let targetEncoding = config3.sourceEncoding || "GB18030";
25095
+ let lineEndingStyle = process.platform === "win32" ? "CRLF" : "LF";
25068
25096
  try {
25069
- await fs5.access(validPath);
25097
+ const existingBuffer = await fs5.readFile(validPath);
25098
+ lineEndingStyle = detectLineEnding(existingBuffer);
25070
25099
  const detection = await detectEncoding(validPath);
25071
25100
  if (detection.encoding && isUtf8Encoding(detection.encoding)) {
25072
25101
  targetEncoding = "UTF-8";
@@ -25075,7 +25104,8 @@ function registerWriteFile(server2, config3, allowedDirectories) {
25075
25104
  }
25076
25105
  } catch {
25077
25106
  }
25078
- const encoded = encodeFromUtf8(args.content, targetEncoding);
25107
+ const finalContent = restoreLineEndings(args.content, lineEndingStyle);
25108
+ const encoded = encodeFromUtf8(finalContent, targetEncoding);
25079
25109
  try {
25080
25110
  await fs5.writeFile(validPath, encoded, { flag: "wx" });
25081
25111
  } catch (error2) {
@@ -25604,6 +25634,7 @@ function registerEditFile(server2, config3, allowedDirectories) {
25604
25634
  async (args) => {
25605
25635
  const validPath = await validatePath(args.path, allowedDirectories);
25606
25636
  const buffer = await fs6.readFile(validPath);
25637
+ const originalLineEnding = detectLineEnding(buffer);
25607
25638
  const detection = await detectEncoding(validPath);
25608
25639
  let originalEncoding = "UTF-8";
25609
25640
  if (detection.encoding && detection.confidence >= config3.confidenceThreshold && isGBEncoding(detection.encoding)) {
@@ -25615,7 +25646,8 @@ function registerEditFile(server2, config3, allowedDirectories) {
25615
25646
  const modifiedContent = applyEditsToContent(utf8Content, args.edits);
25616
25647
  const diff = createUnifiedDiff(utf8Content, modifiedContent, args.path);
25617
25648
  if (!args.dryRun) {
25618
- const encoded = isGBEncoding(originalEncoding) ? encodeFromUtf8(modifiedContent, originalEncoding) : Buffer.from(modifiedContent, "utf-8");
25649
+ const finalContent = restoreLineEndings(modifiedContent, originalLineEnding);
25650
+ const encoded = isGBEncoding(originalEncoding) ? encodeFromUtf8(finalContent, originalEncoding) : Buffer.from(finalContent, "utf-8");
25619
25651
  const tempPath = `${validPath}.${(0, import_crypto2.randomBytes)(16).toString("hex")}.tmp`;
25620
25652
  try {
25621
25653
  await fs6.writeFile(tempPath, encoded);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "encoding-aware-fs",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Encoding-aware file operations MCP Server for AI tools working with GB18030 projects",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
@@ -0,0 +1,3 @@
1
+ MUST use encoding-aware-fs MCP tools (read_file, write_file, edit_file)
2
+ for ALL file operations in this project. NEVER use the built-in Read, Write,
3
+ or Edit tools for any file inside the project directory.
@@ -0,0 +1,3 @@
1
+ MUST use encoding-aware-fs MCP tools (read_file, write_file, edit_file)
2
+ for ALL file operations in this project. NEVER use the built-in Read, Write,
3
+ or Edit tools for any file inside the project directory.
@@ -1,5 +0,0 @@
1
- {
2
- "sourceEncoding": "GB18030",
3
- "targetEncoding": "UTF-8",
4
- "confidenceThreshold": 0.8
5
- }
package/.mcp.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "encoding-aware-fs": {
4
- "command": "cmd",
5
- "args": [
6
- "/c",
7
- "npx",
8
- "-y",
9
- "encoding-aware-fs",
10
- "serve"
11
- ],
12
- "env": {}
13
- }
14
- }
15
- }
package/opencode.jsonc DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "mcp": {
3
- "encoding-aware-fs": {
4
- "type": "local",
5
- "command": [
6
- "npx",
7
- "-y",
8
- "encoding-aware-fs",
9
- "serve"
10
- ],
11
- "enabled": true,
12
- "timeout": 30000
13
- }
14
- }
15
- }