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 +4 -4
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +42 -0
- package/dist/index.js +2 -2
- package/dist/mcp.js +347 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,11 +12,11 @@ bunx codexslimedit
|
|
|
12
12
|
|
|
13
13
|
The MCP server exposes:
|
|
14
14
|
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
4735
|
-
description: "
|
|
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: {
|
|
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:
|
|
4744
|
-
description: "
|
|
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
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
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 ===
|
|
5070
|
+
if (name === READ_FILE_TOOL_NAME) {
|
|
4763
5071
|
const parsed = ReadArgumentsSchema.safeParse(arguments_);
|
|
4764
|
-
if (!parsed.success) return toolResponse(id,
|
|
4765
|
-
return
|
|
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 ===
|
|
4771
|
-
const parsed =
|
|
4772
|
-
if (!parsed.success) return toolResponse(id,
|
|
4773
|
-
|
|
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
|
-
...
|
|
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
|
|
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 {
|
|
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 };
|