@iinm/plain-agent
Advanced tools
+1
-1
| { | ||
| "name": "@iinm/plain-agent", | ||
| "version": "1.14.4", | ||
| "version": "1.14.5", | ||
| "description": "A lightweight terminal-based coding agent focused on safety and low token cost", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
+1
-1
| # Plain Agent | ||
| [](https://github.com/iinm/plain-agent/actions/workflows/github-code-scanning/codeql) | ||
| [](https://socket.dev/npm/package/@iinm/plain-agent) | ||
| [](https://socket.dev/npm/package/@iinm/plain-agent) | ||
| [](https://packagephobia.com/result?p=@iinm/plain-agent) | ||
@@ -6,0 +6,0 @@ |
@@ -14,4 +14,3 @@ /** | ||
| import { styleText } from "node:util"; | ||
| import { parseBlocks } from "../tools/patchFile.mjs"; | ||
| import { diffLines } from "../utils/diffLines.mjs"; | ||
| import { parseBlocks, renderPatchBlock } from "../tools/patchFile.mjs"; | ||
| import { noThrow } from "../utils/noThrow.mjs"; | ||
@@ -867,3 +866,9 @@ | ||
| return blocks | ||
| .map((block) => renderPatchBlock(block, originalLines, nonce)) | ||
| .map((block) => | ||
| renderPatchBlock(block, originalLines, nonce, { | ||
| header: (text) => styleText("cyan", text), | ||
| del: (text) => styleText("magenta", text), | ||
| add: (text) => styleText("green", text), | ||
| }), | ||
| ) | ||
| .join("\n\n"); | ||
@@ -873,53 +878,2 @@ } | ||
| /** | ||
| * @param {PatchBlock} block | ||
| * @param {string[] | null} originalLines | ||
| * @param {string} nonce | ||
| * @returns {string} | ||
| */ | ||
| function renderPatchBlock(block, originalLines, nonce) { | ||
| /** @type {string[]} */ | ||
| const out = []; | ||
| if (block.op === "replace") { | ||
| out.push( | ||
| styleText( | ||
| "cyan", | ||
| `REPLACE ${nonce} ${block.start}:${block.startHash}-${block.end}:${block.endHash}`, | ||
| ), | ||
| ); | ||
| if (originalLines) { | ||
| const safeStart = Math.max(1, block.start); | ||
| const safeEnd = Math.min(originalLines.length, block.end); | ||
| const oldSlice = originalLines.slice(safeStart - 1, safeEnd); | ||
| // Use a real line diff so unchanged lines render as context | ||
| // (no color, " " prefix) instead of being shown as both "- " and | ||
| // "+ ". | ||
| for (const op of diffLines(oldSlice, block.body)) { | ||
| if (op.type === "-") { | ||
| out.push(styleText("magenta", `- ${op.line}`)); | ||
| } else if (op.type === "+") { | ||
| out.push(styleText("green", `+ ${op.line}`)); | ||
| } else { | ||
| out.push(` ${op.line}`); | ||
| } | ||
| } | ||
| } else { | ||
| // No file context available — fall back to listing the body as | ||
| // additions so the user can still see the new content. | ||
| for (const line of block.body) { | ||
| out.push(styleText("green", `+ ${line}`)); | ||
| } | ||
| } | ||
| } else { | ||
| const afterSuffix = block.afterHash ? `:${block.afterHash}` : ""; | ||
| out.push( | ||
| styleText("cyan", `INSERT_AFTER ${nonce} ${block.after}${afterSuffix}`), | ||
| ); | ||
| for (const line of block.body) { | ||
| out.push(styleText("green", `+ ${line}`)); | ||
| } | ||
| } | ||
| return out.join("\n"); | ||
| } | ||
| /** | ||
| * Verbatim highlighter used as fallback when block-aware rendering is not | ||
@@ -926,0 +880,0 @@ * possible (parse error, missing nonce, etc.). |
@@ -7,2 +7,3 @@ /** | ||
| import fs from "node:fs/promises"; | ||
| import { diffLines } from "../utils/diffLines.mjs"; | ||
| import { lineHash } from "../utils/lineHash.mjs"; | ||
@@ -70,5 +71,10 @@ import { noThrow } from "../utils/noThrow.mjs"; | ||
| const original = await fs.readFile(filePath, "utf8"); | ||
| const originalLines = splitLines(original); | ||
| const newContent = applyBlocks(original, blocks); | ||
| await fs.writeFile(filePath, newContent); | ||
| return `Patched file: ${filePath}`; | ||
| const diff = blocks | ||
| .map((block) => renderPatchBlock(block, originalLines, nonce)) | ||
| .join("\n\n"); | ||
| return `Patched file: ${filePath}\n${diff}`; | ||
| }), | ||
@@ -240,2 +246,67 @@ | ||
| /** | ||
| * @typedef {(text: string) => string} DiffStyler | ||
| */ | ||
| /** | ||
| * Render a single patch block as a unified-style diff of the change against | ||
| * the original file: the block header, then removed lines ("- "), added lines | ||
| * ("+ "), and unchanged context (" ") for its range only. | ||
| * | ||
| * The optional `style` callbacks colorize the header and change lines; they | ||
| * default to identity so the output is plain text suitable for tool results, | ||
| * while the CLI passes styleText-based stylers for colored display. | ||
| * | ||
| * @param {PatchBlock} block | ||
| * @param {string[] | null} originalLines | ||
| * @param {string} nonce | ||
| * @param {{ header?: DiffStyler, del?: DiffStyler, add?: DiffStyler }} [style] | ||
| * @returns {string} | ||
| */ | ||
| export function renderPatchBlock(block, originalLines, nonce, style = {}) { | ||
| const header = style.header ?? ((text) => text); | ||
| const del = style.del ?? ((text) => text); | ||
| const add = style.add ?? ((text) => text); | ||
| /** @type {string[]} */ | ||
| const out = []; | ||
| if (block.op === "replace") { | ||
| out.push( | ||
| header( | ||
| `REPLACE ${nonce} ${block.start}:${block.startHash}-${block.end}:${block.endHash}`, | ||
| ), | ||
| ); | ||
| if (originalLines) { | ||
| const safeStart = Math.max(1, block.start); | ||
| const safeEnd = Math.min(originalLines.length, block.end); | ||
| const oldSlice = originalLines.slice(safeStart - 1, safeEnd); | ||
| // Use a real line diff so unchanged lines render as context | ||
| // (no color, " " prefix) instead of being shown as both "- " and | ||
| // "+ ". | ||
| for (const op of diffLines(oldSlice, block.body)) { | ||
| if (op.type === "-") { | ||
| out.push(del(`- ${op.line}`)); | ||
| } else if (op.type === "+") { | ||
| out.push(add(`+ ${op.line}`)); | ||
| } else { | ||
| out.push(` ${op.line}`); | ||
| } | ||
| } | ||
| } else { | ||
| // No file context available — fall back to listing the body as | ||
| // additions so the new content is still visible. | ||
| for (const line of block.body) { | ||
| out.push(add(`+ ${line}`)); | ||
| } | ||
| } | ||
| } else { | ||
| const afterSuffix = block.afterHash ? `:${block.afterHash}` : ""; | ||
| out.push(header(`INSERT_AFTER ${nonce} ${block.after}${afterSuffix}`)); | ||
| for (const line of block.body) { | ||
| out.push(add(`+ ${line}`)); | ||
| } | ||
| } | ||
| return out.join("\n"); | ||
| } | ||
| /** | ||
| * @param {string} headerArgs | ||
@@ -380,1 +451,16 @@ * @param {"replace" | "insert"} op | ||
| } | ||
| /** | ||
| * Split file content into lines the same way applyBlocks does: drop the | ||
| * trailing empty element produced by split() when the content ends with a | ||
| * newline (or is empty), so line numbers match read_file. | ||
| * @param {string} content | ||
| * @returns {string[]} | ||
| */ | ||
| function splitLines(content) { | ||
| const lines = content.split("\n"); | ||
| if (lines.length > 0 && lines[lines.length - 1] === "") { | ||
| lines.pop(); | ||
| } | ||
| return lines; | ||
| } |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
586214
0.25%16527
0.22%