From 198412b4c12eb2e3b0fa0baa4d4275140a710669 Mon Sep 17 00:00:00 2001 From: XiangqianHuang <2332671@stu.zyufl.edu.cn> Date: Sat, 17 Jan 2026 19:42:30 +0800 Subject: [PATCH 1/2] feat: add proto rm/remove command to uninstall protocol Implement cueme proto rm/remove command as the reverse operation of cueme proto apply. This allows users to cleanly remove the HAP protocol from agent configuration files. Features: - Add protoRemove() function to remove managed blocks - Support both 'rm' and 'remove' aliases - Automatically delete file if it becomes empty after removal - Preserve file content outside managed blocks - Handle cases where file or managed block doesn't exist Usage: cueme proto rm cueme proto remove Examples: cueme proto rm windsurf cueme proto remove kiro The command removes content between BEGIN_MARKER and END_MARKER, preserving any other content in the file. If the file only contains the managed block, it will be deleted entirely. --- src/cli.js | 14 +++++++++++++- src/proto.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/cli.js b/src/cli.js index 97c272d..45dd5ea 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,7 +1,7 @@ const { readAllStdin } = require('./io'); const { handleCommand } = require('./handler'); const { parseTagBlocksEnvelope } = require('./envelope'); -const { protoApply, protoInit, protoLs, protoPath, protoRender } = require('./proto'); +const { protoApply, protoRemove, protoInit, protoLs, protoPath, protoRender } = require('./proto'); const pkg = require('../package.json'); const fs = require('fs'); const path = require('path'); @@ -99,6 +99,7 @@ async function main() { ' cueme -p|--protocol', ' cueme proto ', ' cueme proto apply ', + ' cueme proto rm|remove ', ' cueme proto init', ' cueme proto ls', ' cueme proto path ', @@ -258,6 +259,17 @@ async function main() { return; } + if (action === 'rm' || action === 'remove') { + const agent = pos[1]; + if (!agent) { + process.stderr.write('error: missing \n'); + process.exitCode = 2; + return; + } + process.stdout.write(protoRemove(String(agent)) + '\n'); + return; + } + if (action === 'path') { const agent = pos[1]; if (!agent) { diff --git a/src/proto.js b/src/proto.js index aaeefd2..57356b9 100644 --- a/src/proto.js +++ b/src/proto.js @@ -399,6 +399,55 @@ function protoApply(agent) { return `ok: applied to ${targetPath}`; } +function protoRemove(agent) { + const cfg = readConfigOrThrow({ auto_init: true }); + const targetPath = resolveTargetPath({ cfg, agent }); + + let existing = ''; + let exists = false; + try { + existing = fs.readFileSync(targetPath, 'utf8'); + exists = true; + } catch { + return `ok: file does not exist: ${targetPath}`; + } + + const beginMatch = existing.match(BEGIN_MARKER_RE); + const endMatch = existing.match(END_MARKER_RE); + + if (!beginMatch || !endMatch || endMatch.index <= beginMatch.index) { + return `ok: no managed block found in: ${targetPath}`; + } + + const eol = detectEol(existing); + const beginIdx = beginMatch.index; + const endIdx = endMatch.index; + const endLen = endMatch[0].length; + + const before = existing.slice(0, beginIdx); + const after = existing.slice(endIdx + endLen); + + const afterTrim = after.startsWith(eol) ? after.slice(eol.length) : after; + let out = before + afterTrim; + + out = out.trimEnd(); + if (out.length > 0) { + out += eol; + } + + if (out.trim().length === 0) { + try { + fs.unlinkSync(targetPath); + return `ok: removed managed block and deleted empty file: ${targetPath}`; + } catch (err) { + return `ok: removed managed block but failed to delete file: ${targetPath}`; + } + } + + fs.writeFileSync(targetPath, out, 'utf8'); + return `ok: removed managed block from: ${targetPath}`; +} + function protoInit() { const { created, path: p, detected } = initConfigIfMissing(); if (!created) return `ok: exists ${p}`; @@ -417,6 +466,7 @@ module.exports = { END_MARKER, getPlatformKey, protoApply, + protoRemove, protoInit, protoLs, protoPath, From e496b0cd44237607fbe2a4c1693554cde2207b50 Mon Sep 17 00:00:00 2001 From: XiangqianHuang <2332671@stu.zyufl.edu.cn> Date: Sat, 17 Jan 2026 21:05:05 +0800 Subject: [PATCH 2/2] fix: address code review feedback Address all feedback from code review: Must-fix items: 1. Remove unused 'exists' variable in protoRemove() 2. Throw error when file deletion fails instead of returning ok - Changed from 'ok: ... but failed to delete' to throwing Error - This ensures non-zero exit code and proper error handling Improvements: 3. Removed trimEnd() to preserve original file formatting - Keep trailing whitespace/newlines as they were - Only remove the managed block itself The function now: - Cleanly removes managed blocks - Preserves original file formatting - Properly reports errors when file deletion fails - Has no unused variables --- src/proto.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/proto.js b/src/proto.js index 57356b9..eaddc98 100644 --- a/src/proto.js +++ b/src/proto.js @@ -404,10 +404,8 @@ function protoRemove(agent) { const targetPath = resolveTargetPath({ cfg, agent }); let existing = ''; - let exists = false; try { existing = fs.readFileSync(targetPath, 'utf8'); - exists = true; } catch { return `ok: file does not exist: ${targetPath}`; } @@ -419,7 +417,6 @@ function protoRemove(agent) { return `ok: no managed block found in: ${targetPath}`; } - const eol = detectEol(existing); const beginIdx = beginMatch.index; const endIdx = endMatch.index; const endLen = endMatch[0].length; @@ -427,20 +424,16 @@ function protoRemove(agent) { const before = existing.slice(0, beginIdx); const after = existing.slice(endIdx + endLen); + const eol = detectEol(existing); const afterTrim = after.startsWith(eol) ? after.slice(eol.length) : after; - let out = before + afterTrim; - - out = out.trimEnd(); - if (out.length > 0) { - out += eol; - } + const out = before + afterTrim; if (out.trim().length === 0) { try { fs.unlinkSync(targetPath); return `ok: removed managed block and deleted empty file: ${targetPath}`; } catch (err) { - return `ok: removed managed block but failed to delete file: ${targetPath}`; + throw new Error(`error: failed to delete file after removing managed block: ${targetPath}: ${err.message}`); } }