codexslimedit 0.7.4-dev.29698722339.1 → 0.7.4-dev.29702371652.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,11 +12,11 @@ bunx codexslimedit
12
12
 
13
13
  The MCP server exposes:
14
14
 
15
- - `read`: returns a root-relative path and file content without footer boilerplate.
16
- - `edit`: replaces one unique exact string or a 1-based inclusive line range such as `55-64`, then returns `OK <path>`.
15
+ - `read_file`: returns a root-relative path and file content without footer boilerplate.
16
+ - `apply_patch`: accepts Codex `*** Begin Patch` envelopes for `Add File`, `Update File`, and `Delete File`. It also retains a compact `filePath`/`oldString`/`newString` form for one unique exact replacement or inclusive line range such as `55-64`.
17
17
 
18
- Both tools constrain access to the server working directory. Reads reject non-UTF-8, NUL-containing, missing, non-file, traversal, and symlink-escape targets. Edits validate before writing, reject ambiguous matches and invalid ranges, preserve line endings and file mode, and use same-directory atomic replacement.
18
+ Both tools constrain access to the server working directory. Reads reject non-UTF-8, NUL-containing, missing, non-file, traversal, and symlink-escape targets. Patches create, update, and delete regular UTF-8 files; exact edits reject ambiguous matches and invalid ranges, preserve line endings and file mode, and use same-directory atomic replacement.
19
19
 
20
20
  ## Codex adaptation
21
21
 
22
- This package adapts ideas from OpenSlimEdit for supported Codex extension surfaces. Codex plugins cannot replace descriptions of native tools or replace model-facing `PostToolUse` output, and Codex native patch editing has no OpenCode `oldString` contract. `codexslimedit` therefore provides separate concise MCP tools and makes no upstream token-saving claim.
22
+ This package adapts ideas from OpenSlimEdit for supported Codex extension surfaces. Codex qualifies MCP tool names with their server namespace, so the model-facing names are `mcp__codexslimedit__read_file` and `mcp__codexslimedit__apply_patch`; MCP cannot overwrite Codex's built-in `apply_patch` registration. HolyCodex instructions require these tools for workspace reads and writes. Native patch envelopes preserve familiar patch ergonomics, while the compact exact-replacement form reduces simple-edit arguments and results. No upstream token-saving percentage is claimed without Codex-specific benchmarks.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as CODEX_SLIM_EDIT_VERSION, n as runCodexSlimEditMcpStdioServer, o as isVersionRequest, s as stackOrMessageFromError } from "./mcp.js";
2
+ import { d as stackOrMessageFromError, i as isVersionRequest, n as runCodexSlimEditMcpStdioServer, r as CODEX_SLIM_EDIT_VERSION } from "./mcp.js";
3
3
  import { stderr } from "node:process";
4
4
  //#region src/cli.ts
5
5
  /** Starts the CodexSlimEdit MCP stdio entrypoint. */
package/dist/index.d.ts CHANGED
@@ -8,8 +8,10 @@ export declare function isVersionRequest(args: readonly string[]): boolean;
8
8
 
9
9
  /** Identifies a workspace file operation failure. */
10
10
  export type WorkspaceFileErrorCode =
11
+ | "ALREADY_EXISTS"
11
12
  | "EXACT_MATCH_NOT_FOUND"
12
13
  | "DUPLICATE_MATCH"
14
+ | "INVALID_PATCH"
13
15
  | "INVALID_RANGE"
14
16
  | "NOT_A_FILE"
15
17
  | "NOT_FOUND"
@@ -49,6 +51,26 @@ export interface WorkspaceFileResult {
49
51
  readonly content: string;
50
52
  }
51
53
 
54
+ /** Input for writing complete UTF-8 content to a workspace file. */
55
+ export interface WriteWorkspaceFileInput extends WorkspaceFileInput {
56
+ /** Complete replacement content. */
57
+ readonly content: string;
58
+ }
59
+
60
+ /** Input for a Codex-compatible workspace patch envelope. */
61
+ export interface ApplyWorkspacePatchInput {
62
+ /** Workspace root used to constrain file access. */
63
+ readonly root: string;
64
+ /** Codex `*** Begin Patch` envelope. */
65
+ readonly patch: string;
66
+ }
67
+
68
+ /** Paths changed by a workspace patch. */
69
+ export interface WorkspacePatchResult {
70
+ /** Canonical root-relative paths in patch order. */
71
+ readonly paths: readonly string[];
72
+ }
73
+
52
74
  /** Options for the CodexSlimEdit MCP server. */
53
75
  export interface CodexSlimEditMcpOptions {
54
76
  /** Workspace root; defaults to the server process current directory. */
@@ -63,6 +85,26 @@ export declare function editWorkspaceFile(
63
85
  input: EditWorkspaceFileInput,
64
86
  ): Promise<WorkspaceFileResult>;
65
87
 
88
+ /** Replaces an existing workspace file with complete UTF-8 content. */
89
+ export declare function writeWorkspaceFile(
90
+ input: WriteWorkspaceFileInput,
91
+ ): Promise<WorkspaceFileResult>;
92
+
93
+ /** Creates a new workspace file in an existing directory. */
94
+ export declare function createWorkspaceFile(
95
+ input: WriteWorkspaceFileInput,
96
+ ): Promise<WorkspaceFileResult>;
97
+
98
+ /** Deletes one existing regular workspace file. */
99
+ export declare function deleteWorkspaceFile(
100
+ input: WorkspaceFileInput,
101
+ ): Promise<WorkspaceFileResult>;
102
+
103
+ /** Applies Codex add, update, and delete patch operations inside a workspace. */
104
+ export declare function applyWorkspacePatch(
105
+ input: ApplyWorkspacePatchInput,
106
+ ): Promise<WorkspacePatchResult>;
107
+
66
108
  /** Handles one CodexSlimEdit MCP JSON-RPC request. */
67
109
  export declare function handleCodexSlimEditMcpRequest(
68
110
  input: unknown,
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as CODEX_SLIM_EDIT_VERSION, c as WorkspaceFileError, i as readWorkspaceFile, n as runCodexSlimEditMcpStdioServer, o as isVersionRequest, r as editWorkspaceFile, t as handleCodexSlimEditMcpRequest } from "./mcp.js";
2
- export { CODEX_SLIM_EDIT_VERSION, WorkspaceFileError, editWorkspaceFile, handleCodexSlimEditMcpRequest, isVersionRequest, readWorkspaceFile, runCodexSlimEditMcpStdioServer };
1
+ import { a as applyWorkspacePatch, c as editWorkspaceFile, f as WorkspaceFileError, i as isVersionRequest, l as readWorkspaceFile, n as runCodexSlimEditMcpStdioServer, o as createWorkspaceFile, r as CODEX_SLIM_EDIT_VERSION, s as deleteWorkspaceFile, t as handleCodexSlimEditMcpRequest, u as writeWorkspaceFile } from "./mcp.js";
2
+ export { CODEX_SLIM_EDIT_VERSION, WorkspaceFileError, applyWorkspacePatch, createWorkspaceFile, deleteWorkspaceFile, editWorkspaceFile, handleCodexSlimEditMcpRequest, isVersionRequest, readWorkspaceFile, runCodexSlimEditMcpStdioServer, writeWorkspaceFile };
package/dist/mcp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "node:child_process";
2
- import { chmod, open, readFile, realpath, rename, rm, stat } from "node:fs/promises";
2
+ import { chmod, lstat, mkdir, open, readFile, realpath, rename, rm, stat } from "node:fs/promises";
3
3
  import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
4
4
  //#region src/errors.ts
5
5
  /** Reports a typed, actionable workspace file operation failure. */
@@ -4508,14 +4508,6 @@ function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
4508
4508
  };
4509
4509
  }
4510
4510
  //#endregion
4511
- //#region src/version.ts
4512
- /** Current independent codexslimedit package version. */
4513
- var CODEX_SLIM_EDIT_VERSION = "0.7.4-dev.29698722339.1";
4514
- /** Returns whether command-line arguments request the package version. */
4515
- function isVersionRequest(args) {
4516
- return args.includes("--version");
4517
- }
4518
- //#endregion
4519
4511
  //#region src/workspace.ts
4520
4512
  var RANGE_PATTERN = /^\s*(?<start>[1-9]\d*)\s*(?:-\s*(?<end>[1-9]\d*)\s*)?$/;
4521
4513
  var TEMPORARY_FILE_PREFIX = ".codexslimedit-";
@@ -4531,12 +4523,69 @@ async function readWorkspaceFile(input) {
4531
4523
  async function editWorkspaceFile(input) {
4532
4524
  const target = await resolveWorkspaceFile(input);
4533
4525
  const nextContent = replaceContent(await readUtf8Text(target.absolutePath), input.oldString, input.newString);
4526
+ validateText(nextContent);
4534
4527
  await atomicWrite(target.absolutePath, nextContent);
4535
4528
  return {
4536
4529
  path: target.relativePath,
4537
4530
  content: nextContent
4538
4531
  };
4539
4532
  }
4533
+ /** Replaces an existing workspace file with complete UTF-8 content. */
4534
+ async function writeWorkspaceFile(input) {
4535
+ const target = await resolveWorkspaceFile(input);
4536
+ validateText(input.content);
4537
+ await atomicWrite(target.absolutePath, input.content);
4538
+ return {
4539
+ path: target.relativePath,
4540
+ content: input.content
4541
+ };
4542
+ }
4543
+ /** Creates a new workspace file in an existing directory. */
4544
+ async function createWorkspaceFile(input) {
4545
+ const target = await resolveNewWorkspaceFile(input);
4546
+ validateText(input.content);
4547
+ let file;
4548
+ let created = false;
4549
+ try {
4550
+ file = await open(target.absolutePath, "wx", 420);
4551
+ created = true;
4552
+ await file.writeFile(input.content, "utf8");
4553
+ await file.sync();
4554
+ await file.close();
4555
+ file = void 0;
4556
+ } catch (error) {
4557
+ await file?.close();
4558
+ if (created) await rm(target.absolutePath, { force: true });
4559
+ throw new WorkspaceFileError("WRITE_FAILED", `Could not create filePath: ${message(error)}`);
4560
+ }
4561
+ return {
4562
+ path: target.relativePath,
4563
+ content: input.content
4564
+ };
4565
+ }
4566
+ /** Validates a new workspace file without modifying the workspace. */
4567
+ async function prepareWorkspaceFileCreation(input) {
4568
+ const target = await resolveNewWorkspaceFile(input, false);
4569
+ validateText(input.content);
4570
+ return {
4571
+ path: target.relativePath,
4572
+ content: input.content
4573
+ };
4574
+ }
4575
+ /** Deletes one existing regular workspace file. */
4576
+ async function deleteWorkspaceFile(input) {
4577
+ const target = await resolveWorkspaceFile(input);
4578
+ const content = await readUtf8Text(target.absolutePath);
4579
+ try {
4580
+ await rm(target.absolutePath);
4581
+ } catch (error) {
4582
+ throw new WorkspaceFileError("WRITE_FAILED", `Could not delete filePath: ${message(error)}`);
4583
+ }
4584
+ return {
4585
+ path: target.relativePath,
4586
+ content
4587
+ };
4588
+ }
4540
4589
  async function resolveWorkspaceFile(input) {
4541
4590
  const rootPath = await existingDirectory(input.root);
4542
4591
  const candidate = resolve(rootPath, normalizeSeparators(input.filePath));
@@ -4548,6 +4597,38 @@ async function resolveWorkspaceFile(input) {
4548
4597
  relativePath: relative(rootPath, resolvedCandidate).split(sep).join("/")
4549
4598
  };
4550
4599
  }
4600
+ async function resolveNewWorkspaceFile(input, createParent = true) {
4601
+ const rootPath = await existingDirectory(input.root);
4602
+ const candidate = resolve(rootPath, normalizeSeparators(input.filePath));
4603
+ if (!isInside(rootPath, candidate)) throw new WorkspaceFileError("PATH_OUTSIDE_ROOT", "filePath must remain inside the workspace root.");
4604
+ try {
4605
+ await lstat(candidate);
4606
+ throw new WorkspaceFileError("ALREADY_EXISTS", "filePath already exists.");
4607
+ } catch (error) {
4608
+ if (error instanceof WorkspaceFileError) throw error;
4609
+ }
4610
+ const absolutePath = resolve(await safeParent(rootPath, dirname(candidate), createParent), basename(candidate));
4611
+ return {
4612
+ absolutePath,
4613
+ relativePath: relative(rootPath, absolutePath).split(sep).join("/")
4614
+ };
4615
+ }
4616
+ async function safeParent(rootPath, requestedParent, createParent) {
4617
+ const missingDirectories = [];
4618
+ let existingParent = requestedParent;
4619
+ while (true) try {
4620
+ existingParent = await realpath(existingParent);
4621
+ break;
4622
+ } catch {
4623
+ if (existingParent === rootPath) throw new WorkspaceFileError("NOT_FOUND", "Workspace root does not exist.");
4624
+ missingDirectories.push(basename(existingParent));
4625
+ existingParent = dirname(existingParent);
4626
+ }
4627
+ if (existingParent !== rootPath && !isInside(rootPath, existingParent)) throw new WorkspaceFileError("PATH_OUTSIDE_ROOT", "filePath parent resolves outside the workspace root through a symlink.");
4628
+ const parentPath = resolve(existingParent, ...missingDirectories.reverse());
4629
+ if (createParent) await mkdir(parentPath, { recursive: true });
4630
+ return parentPath;
4631
+ }
4551
4632
  async function existingDirectory(root) {
4552
4633
  try {
4553
4634
  const resolvedRoot = await realpath(resolve(root));
@@ -4584,12 +4665,15 @@ async function readUtf8Text(path) {
4584
4665
  }
4585
4666
  try {
4586
4667
  const content = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
4587
- if (content.includes("\0")) throw new TypeError("NUL byte");
4668
+ validateText(content);
4588
4669
  return content;
4589
4670
  } catch {
4590
4671
  throw new WorkspaceFileError("UNSUPPORTED_TEXT", "filePath must contain UTF-8 text without NUL bytes.");
4591
4672
  }
4592
4673
  }
4674
+ function validateText(content) {
4675
+ if (content.includes("\0")) throw new WorkspaceFileError("UNSUPPORTED_TEXT", "Text must not contain NUL bytes.");
4676
+ }
4593
4677
  function replaceContent(content, oldString, newString) {
4594
4678
  const range = parseRange(oldString, lineCount(content));
4595
4679
  if (range !== null) return replaceLineRange(content, range.start, range.end, newString);
@@ -4687,14 +4771,210 @@ function message(error) {
4687
4771
  return error instanceof Error ? error.message : String(error);
4688
4772
  }
4689
4773
  //#endregion
4774
+ //#region src/patch.ts
4775
+ /** Applies Codex add, update, and delete patch operations inside a workspace. */
4776
+ async function applyWorkspacePatch(input) {
4777
+ const operations = parsePatch(input.patch);
4778
+ const planned = await planOperations(input.root, operations);
4779
+ const paths = [];
4780
+ for (const { operation, path, content } of planned) {
4781
+ if (operation.kind === "add") {
4782
+ await createWorkspaceFile({
4783
+ root: input.root,
4784
+ filePath: operation.filePath,
4785
+ content: operation.content
4786
+ });
4787
+ paths.push(path);
4788
+ continue;
4789
+ }
4790
+ if (operation.kind === "delete") {
4791
+ await deleteWorkspaceFile({
4792
+ root: input.root,
4793
+ filePath: operation.filePath
4794
+ });
4795
+ paths.push(path);
4796
+ continue;
4797
+ }
4798
+ await writeWorkspaceFile({
4799
+ root: input.root,
4800
+ filePath: operation.filePath,
4801
+ content: content ?? ""
4802
+ });
4803
+ paths.push(path);
4804
+ }
4805
+ return { paths };
4806
+ }
4807
+ async function planOperations(root, operations) {
4808
+ const planned = [];
4809
+ const paths = /* @__PURE__ */ new Set();
4810
+ for (const operation of operations) {
4811
+ if (operation.kind === "add") {
4812
+ const result = await prepareWorkspaceFileCreation({
4813
+ root,
4814
+ filePath: operation.filePath,
4815
+ content: operation.content
4816
+ });
4817
+ assertUniquePath(paths, result.path);
4818
+ planned.push({
4819
+ operation,
4820
+ path: result.path
4821
+ });
4822
+ continue;
4823
+ }
4824
+ const current = await readWorkspaceFile({
4825
+ root,
4826
+ filePath: operation.filePath
4827
+ });
4828
+ assertUniquePath(paths, current.path);
4829
+ planned.push({
4830
+ operation,
4831
+ path: current.path,
4832
+ content: operation.kind === "update" ? applyUpdateHunks(current.content, operation.hunks) : void 0
4833
+ });
4834
+ }
4835
+ return planned;
4836
+ }
4837
+ function assertUniquePath(paths, path) {
4838
+ if (paths.has(path)) invalidPatch(`Patch contains multiple operations for ${path}.`);
4839
+ paths.add(path);
4840
+ }
4841
+ function parsePatch(patch) {
4842
+ const lines = patch.replace(/\r\n|\r/g, "\n").split("\n");
4843
+ while (lines.at(-1) === "") lines.pop();
4844
+ if (lines[0] !== "*** Begin Patch" || lines.at(-1) !== "*** End Patch") invalidPatch("Patch must start with `*** Begin Patch` and end with `*** End Patch`.");
4845
+ const operations = [];
4846
+ let index = 1;
4847
+ while (index < lines.length - 1) {
4848
+ const header = lines[index];
4849
+ const addPath = header?.match(/^\*\*\* Add File: (.+)$/)?.[1];
4850
+ const updatePath = header?.match(/^\*\*\* Update File: (.+)$/)?.[1];
4851
+ const deletePath = header?.match(/^\*\*\* Delete File: (.+)$/)?.[1];
4852
+ index += 1;
4853
+ if (addPath !== void 0) {
4854
+ const contentLines = [];
4855
+ while (index < lines.length - 1 && !isOperationHeader(lines[index])) {
4856
+ const line = lines[index];
4857
+ if (line === "*** End of File") {
4858
+ index += 1;
4859
+ break;
4860
+ }
4861
+ if (!line?.startsWith("+")) invalidPatch("Add File lines must start with `+`.");
4862
+ contentLines.push(line.slice(1));
4863
+ index += 1;
4864
+ }
4865
+ operations.push({
4866
+ kind: "add",
4867
+ filePath: filePath(addPath),
4868
+ content: contentLines.length === 0 ? "" : `${contentLines.join("\n")}\n`
4869
+ });
4870
+ continue;
4871
+ }
4872
+ if (deletePath !== void 0) {
4873
+ operations.push({
4874
+ kind: "delete",
4875
+ filePath: filePath(deletePath)
4876
+ });
4877
+ continue;
4878
+ }
4879
+ if (updatePath !== void 0) {
4880
+ const hunks = [];
4881
+ let oldLines;
4882
+ let newLines;
4883
+ while (index < lines.length - 1 && !isOperationHeader(lines[index])) {
4884
+ const line = lines[index];
4885
+ if (line === "*** End of File") {
4886
+ index += 1;
4887
+ break;
4888
+ }
4889
+ if (line?.startsWith("@@")) {
4890
+ if (oldLines !== void 0 && newLines !== void 0) hunks.push(validHunk(oldLines, newLines));
4891
+ oldLines = [];
4892
+ newLines = [];
4893
+ index += 1;
4894
+ continue;
4895
+ }
4896
+ if (oldLines === void 0 || newLines === void 0 || line === void 0) invalidPatch("Update File content must begin with an `@@` hunk header.");
4897
+ const prefix = line[0];
4898
+ const content = line.slice(1);
4899
+ if (prefix === " " || prefix === "-") oldLines.push(content);
4900
+ if (prefix === " " || prefix === "+") newLines.push(content);
4901
+ if (prefix !== " " && prefix !== "-" && prefix !== "+") invalidPatch("Update hunk lines must start with a space, `-`, or `+`.");
4902
+ index += 1;
4903
+ }
4904
+ if (oldLines !== void 0 && newLines !== void 0) hunks.push(validHunk(oldLines, newLines));
4905
+ if (hunks.length === 0) invalidPatch("Update File requires at least one hunk.");
4906
+ operations.push({
4907
+ kind: "update",
4908
+ filePath: filePath(updatePath),
4909
+ hunks
4910
+ });
4911
+ continue;
4912
+ }
4913
+ invalidPatch(`Unknown patch operation: ${header ?? "<missing>"}`);
4914
+ }
4915
+ if (operations.length === 0) invalidPatch("Patch must contain at least one file operation.");
4916
+ return operations;
4917
+ }
4918
+ function isOperationHeader(line) {
4919
+ return /^\*\*\* (?:Add|Update|Delete) File: /.test(line ?? "");
4920
+ }
4921
+ function validHunk(oldLines, newLines) {
4922
+ if (oldLines.length === 0) invalidPatch("Update hunks require old or context lines for deterministic placement.");
4923
+ return {
4924
+ oldLines,
4925
+ newLines
4926
+ };
4927
+ }
4928
+ function applyUpdateHunks(content, hunks) {
4929
+ const lineEnding = content.includes("\r\n") ? "\r\n" : "\n";
4930
+ const normalized = content.replace(/\r\n|\r/g, "\n");
4931
+ const hasFinalNewline = normalized.endsWith("\n");
4932
+ const lines = normalized.split("\n");
4933
+ if (hasFinalNewline) lines.pop();
4934
+ let cursor = 0;
4935
+ for (const hunk of hunks) {
4936
+ const offset = uniqueSequenceOffset(lines, hunk.oldLines, cursor);
4937
+ lines.splice(offset, hunk.oldLines.length, ...hunk.newLines);
4938
+ cursor = offset + hunk.newLines.length;
4939
+ }
4940
+ if (lines.length === 0) return "";
4941
+ return `${lines.join(lineEnding)}${hasFinalNewline ? lineEnding : ""}`;
4942
+ }
4943
+ function uniqueSequenceOffset(lines, expected, cursor) {
4944
+ const offsets = [];
4945
+ for (let offset = cursor; offset <= lines.length - expected.length; offset += 1) if (expected.every((line, index) => lines[offset + index] === line)) offsets.push(offset);
4946
+ if (offsets.length === 1) return offsets[0] ?? 0;
4947
+ if (offsets.length > 1) throw new WorkspaceFileError("DUPLICATE_MATCH", "Patch hunk matches more than once; include more unchanged context.");
4948
+ throw new WorkspaceFileError("EXACT_MATCH_NOT_FOUND", "Patch hunk context was not found exactly.");
4949
+ }
4950
+ function filePath(value) {
4951
+ const trimmed = value.trim();
4952
+ if (trimmed === "") invalidPatch("Patch file paths must not be empty.");
4953
+ return trimmed;
4954
+ }
4955
+ function invalidPatch(message) {
4956
+ throw new WorkspaceFileError("INVALID_PATCH", message);
4957
+ }
4958
+ //#endregion
4959
+ //#region src/version.ts
4960
+ /** Current independent codexslimedit package version. */
4961
+ var CODEX_SLIM_EDIT_VERSION = "0.7.4-dev.29702371652.1";
4962
+ /** Returns whether command-line arguments request the package version. */
4963
+ function isVersionRequest(args) {
4964
+ return args.includes("--version");
4965
+ }
4966
+ //#endregion
4690
4967
  //#region src/mcp.ts
4691
4968
  var InitializeParamsSchema = looseObject({ protocolVersion: string() });
4692
4969
  var ReadArgumentsSchema = strictObject({ filePath: string().min(1) });
4693
- var EditArgumentsSchema = strictObject({
4970
+ var ExactEditArgumentsSchema = strictObject({
4694
4971
  filePath: string().min(1),
4695
4972
  oldString: string().min(1),
4696
4973
  newString: string()
4697
4974
  });
4975
+ var ApplyPatchArgumentsSchema = union([strictObject({ patch: string().min(1) }), ExactEditArgumentsSchema]);
4976
+ var READ_FILE_TOOL_NAME = "read_file";
4977
+ var APPLY_PATCH_TOOL_NAME = "apply_patch";
4698
4978
  /** Handles one CodexSlimEdit MCP JSON-RPC request. */
4699
4979
  async function handleCodexSlimEditMcpRequest(input, options = {}) {
4700
4980
  const request = JsonRpcRequestSchema.safeParse(input);
@@ -4731,53 +5011,89 @@ async function runCodexSlimEditMcpStdioServer(input, output, options = {}) {
4731
5011
  });
4732
5012
  }
4733
5013
  var TOOL_DEFINITIONS = [{
4734
- name: "read",
4735
- description: "Read one UTF-8 workspace file concisely.",
5014
+ name: READ_FILE_TOOL_NAME,
5015
+ description: "Required tool for reading one complete UTF-8 workspace file. Returns the workspace-relative path and exact content without metadata or footer boilerplate.",
4736
5016
  inputSchema: {
4737
5017
  type: "object",
4738
- properties: { filePath: { type: "string" } },
5018
+ properties: { filePath: {
5019
+ type: "string",
5020
+ description: "The workspace-relative path of the file to read."
5021
+ } },
4739
5022
  required: ["filePath"],
4740
5023
  additionalProperties: false
5024
+ },
5025
+ annotations: {
5026
+ readOnlyHint: true,
5027
+ destructiveHint: false,
5028
+ idempotentHint: true,
5029
+ openWorldHint: false
4741
5030
  }
4742
5031
  }, {
4743
- name: "edit",
4744
- description: "Atomically replace unique content or an inclusive line range.",
5032
+ name: APPLY_PATCH_TOOL_NAME,
5033
+ description: "Required workspace write tool. Use a native `*** Begin Patch` envelope to add, update, or delete files; for a smaller single replacement, pass filePath, oldString, and newString.",
4745
5034
  inputSchema: {
4746
5035
  type: "object",
4747
5036
  properties: {
4748
- filePath: { type: "string" },
4749
- oldString: { type: "string" },
4750
- newString: { type: "string" }
5037
+ patch: {
5038
+ type: "string",
5039
+ description: "Native Codex patch envelope with Add File, Update File, or Delete File operations."
5040
+ },
5041
+ filePath: {
5042
+ type: "string",
5043
+ description: "The workspace-relative path for a compact single-file replacement."
5044
+ },
5045
+ oldString: {
5046
+ type: "string",
5047
+ description: "The unique exact text, or inclusive 1-based line number or N-M range to replace."
5048
+ },
5049
+ newString: {
5050
+ type: "string",
5051
+ description: "Replacement text; may be empty."
5052
+ }
4751
5053
  },
4752
- required: [
5054
+ oneOf: [{ required: ["patch"] }, { required: [
4753
5055
  "filePath",
4754
5056
  "oldString",
4755
5057
  "newString"
4756
- ],
5058
+ ] }],
4757
5059
  additionalProperties: false
5060
+ },
5061
+ annotations: {
5062
+ readOnlyHint: false,
5063
+ destructiveHint: true,
5064
+ idempotentHint: false,
5065
+ openWorldHint: false
4758
5066
  }
4759
5067
  }];
4760
5068
  async function callTool(id, name, arguments_, options) {
4761
5069
  const root = options.root ?? process.cwd();
4762
- if (name === "read") {
5070
+ if (name === READ_FILE_TOOL_NAME) {
4763
5071
  const parsed = ReadArgumentsSchema.safeParse(arguments_);
4764
- if (!parsed.success) return toolResponse(id, "read.filePath must be a non-empty string.", true);
4765
- return fileOperationResponse(id, () => readWorkspaceFile({
5072
+ if (!parsed.success) return toolResponse(id, `${READ_FILE_TOOL_NAME}.filePath must be a non-empty string.`, true);
5073
+ return operationResponse(id, () => readWorkspaceFile({
4766
5074
  root,
4767
5075
  ...parsed.data
4768
5076
  }), (result) => `${result.path}\n${result.content}`);
4769
5077
  }
4770
- if (name === "edit") {
4771
- const parsed = EditArgumentsSchema.safeParse(arguments_);
4772
- if (!parsed.success) return toolResponse(id, "edit requires non-empty filePath and oldString plus string newString.", true);
4773
- return fileOperationResponse(id, () => editWorkspaceFile({
5078
+ if (name === APPLY_PATCH_TOOL_NAME) {
5079
+ const parsed = ApplyPatchArgumentsSchema.safeParse(arguments_);
5080
+ if (!parsed.success) return toolResponse(id, `${APPLY_PATCH_TOOL_NAME} requires a non-empty patch envelope or non-empty filePath and oldString plus string newString.`, true);
5081
+ const parsedArguments = parsed.data;
5082
+ if ("patch" in parsedArguments) {
5083
+ const patch = parsedArguments.patch;
5084
+ return operationResponse(id, () => applyWorkspacePatch({
5085
+ root,
5086
+ patch
5087
+ }), () => "Done!");
5088
+ }
5089
+ return operationResponse(id, () => editWorkspaceFile({
4774
5090
  root,
4775
- ...parsed.data
5091
+ ...parsedArguments
4776
5092
  }), (result) => `OK ${result.path}`);
4777
5093
  }
4778
5094
  return toolResponse(id, `Unknown codexslimedit tool: ${name}`, true);
4779
5095
  }
4780
- async function fileOperationResponse(id, operation, successText) {
5096
+ async function operationResponse(id, operation, successText) {
4781
5097
  try {
4782
5098
  return toolResponse(id, successText(await operation()));
4783
5099
  } catch (error) {
@@ -4794,4 +5110,4 @@ function toolResponse(id, text, isError = false) {
4794
5110
  });
4795
5111
  }
4796
5112
  //#endregion
4797
- export { CODEX_SLIM_EDIT_VERSION as a, WorkspaceFileError as c, readWorkspaceFile as i, runCodexSlimEditMcpStdioServer as n, isVersionRequest as o, editWorkspaceFile as r, stackOrMessageFromError as s, handleCodexSlimEditMcpRequest as t };
5113
+ export { applyWorkspacePatch as a, editWorkspaceFile as c, stackOrMessageFromError as d, WorkspaceFileError as f, isVersionRequest as i, readWorkspaceFile as l, runCodexSlimEditMcpStdioServer as n, createWorkspaceFile as o, CODEX_SLIM_EDIT_VERSION as r, deleteWorkspaceFile as s, handleCodexSlimEditMcpRequest as t, writeWorkspaceFile as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexslimedit",
3
- "version": "0.7.4-dev.29698722339.1",
3
+ "version": "0.7.4-dev.29702371652.1",
4
4
  "description": "Concise, workspace-safe MCP file reading and editing.",
5
5
  "license": "SUL-1.0",
6
6
  "repository": {