cueme 0.1.15 → 0.1.17

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
@@ -14,10 +14,10 @@
14
14
  [![npm downloads](https://img.shields.io/npm/dm/cueme?color=0B7285)](https://www.npmjs.com/package/cueme)
15
15
 
16
16
  [![Repo: cue-stack](https://img.shields.io/badge/repo-cue--stack-111827)](https://github.com/nmhjklnm/cue-stack)
17
- [![Repo: cue-console](https://img.shields.io/badge/repo-cue--console-111827)](https://github.com/nmhjklnm/cue-console)
18
- [![Repo: cue-command](https://img.shields.io/badge/repo-cue--command-111827)](https://github.com/nmhjklnm/cue-command)
19
17
  ![License](https://img.shields.io/badge/license-Apache--2.0-1E40AF)
20
18
 
19
+ Source: <https://github.com/nmhjklnm/cue-stack/tree/main/cue-command>
20
+
21
21
  [Contributing](./CONTRIBUTING.md) · [Trademark](./TRADEMARK.md)
22
22
 
23
23
  A command protocol adapter for Cue, compatible with the existing SQLite mailbox (`~/.cue/cue.db`).
@@ -36,7 +36,7 @@ npm install -g cueme
36
36
 
37
37
  Copy the contents of `protocol.md` into your runtime's system prompt / persistent rules:
38
38
 
39
- - [`protocol.md`](https://github.com/nmhjklnm/cue-command/blob/main/protocol.md)
39
+ - [`protocol.md`](https://github.com/nmhjklnm/cue-stack/blob/main/cue-command/protocol.md)
40
40
 
41
41
  If you installed via npm, `protocol.md` is also included in the package.
42
42
 
package/README.zh-CN.md CHANGED
@@ -14,10 +14,10 @@
14
14
  [![npm downloads](https://img.shields.io/npm/dm/cueme?color=0B7285)](https://www.npmjs.com/package/cueme)
15
15
 
16
16
  [![Repo: cue-stack](https://img.shields.io/badge/repo-cue--stack-111827)](https://github.com/nmhjklnm/cue-stack)
17
- [![Repo: cue-console](https://img.shields.io/badge/repo-cue--console-111827)](https://github.com/nmhjklnm/cue-console)
18
- [![Repo: cue-command](https://img.shields.io/badge/repo-cue--command-111827)](https://github.com/nmhjklnm/cue-command)
19
17
  ![License](https://img.shields.io/badge/license-Apache--2.0-1E40AF)
20
18
 
19
+ 源码: <https://github.com/nmhjklnm/cue-stack/tree/main/cue-command>
20
+
21
21
  [Contributing](./CONTRIBUTING.md) · [Trademark](./TRADEMARK.md)
22
22
 
23
23
  这是 Cue 的命令行协议适配器,兼容现有的 SQLite mailbox(`~/.cue/cue.db`)。
@@ -36,7 +36,7 @@ npm install -g cueme
36
36
 
37
37
  把 `protocol.md` 的内容复制到你正在使用的 runtime 的系统提示词 / 持久规则里:
38
38
 
39
- - [`protocol.md`](https://github.com/nmhjklnm/cue-command/blob/main/protocol.md)
39
+ - [`protocol.md`](https://github.com/nmhjklnm/cue-stack/blob/main/cue-command/protocol.md)
40
40
 
41
41
  如果你是通过 npm 安装的,`protocol.md` 也会包含在安装包里。
42
42
 
package/docs/proto.md CHANGED
@@ -6,7 +6,19 @@ This document describes the `cueme proto` command family.
6
6
 
7
7
  `cueme proto` injects the shared `protocol.md` into a specific agent file by composing:
8
8
 
9
- `final_proto = prefix(agent) + "\n\n" + protocol.md`
9
+ `final_proto = prefix(agent) + "\n\n" + proto_block + runtime_block`
10
+
11
+ `proto_block` and `runtime_block` are separate managed blocks:
12
+
13
+ ```
14
+ <!-- HUMAN_AGENT_PROTO_BEGIN -->
15
+ ... protocol.md ...
16
+ <!-- HUMAN_AGENT_PROTO_END -->
17
+
18
+ <!-- HUMAN_AGENT_RUNTIME_BEGIN -->
19
+ ... runtime-specific guidance ...
20
+ <!-- HUMAN_AGENT_RUNTIME_END -->
21
+ ```
10
22
 
11
23
  The injected content is managed between sentinel markers and may be overwritten by `cueme proto apply`.
12
24
 
@@ -23,6 +35,8 @@ Required keys:
23
35
  - supports `~` and `%ENV%` expansions (e.g. `%APPDATA%`, `%USERPROFILE%`)
24
36
  - `cueme.proto.prefix`: map of prefix by `<agent>`
25
37
  - can be a string or string array (joined with `\n`)
38
+ - `cueme.proto.runtime`: map of runtime-specific guidance by `<agent>`
39
+ - can be a string or string array (joined with `\n`)
26
40
  - `cueme.proto.protocol_path`: absolute or relative path to the shared `protocol.md`
27
41
  - supports `~` and `%ENV%` expansions
28
42
 
@@ -38,7 +52,15 @@ Example:
38
52
  "windows.windsurf": "%USERPROFILE%\\.codeium\\windsurf\\memories\\global_rules.md",
39
53
 
40
54
  "linux.vscode": "~/.config/Code/User/prompts/human_proto.md",
41
- "linux.windsurf": "~/.codeium/windsurf/memories/global_rules.md"
55
+ "linux.windsurf": "~/.codeium/windsurf/memories/global_rules.md",
56
+
57
+ "macos.codex": "~/.codex/AGENTS.md",
58
+ "windows.codex": "%USERPROFILE%\\.codex\\AGENTS.md",
59
+ "linux.codex": "~/.codex/AGENTS.md",
60
+
61
+ "macos.claude": "~/.claude/CLAUDE.md",
62
+ "windows.claude": "%USERPROFILE%\\.claude\\CLAUDE.md",
63
+ "linux.claude": "~/.claude/CLAUDE.md"
42
64
  },
43
65
  "cueme.proto.prefix": {
44
66
  "vscode": [
@@ -46,8 +68,19 @@ Example:
46
68
  "applyTo: '**'",
47
69
  "---"
48
70
  ],
71
+ "codex": [],
72
+ "claude": [],
49
73
  "windsurf": []
50
74
  },
75
+ "cueme.proto.runtime": {
76
+ "codex": [
77
+ "When calling cueme cue/pause, set the tool wait timeout to 24h to avoid premature tool timeouts.",
78
+ "不要误解“更新 todo_list”的指令,因为你是 codex,你需要做的是:把 update_plan 当作 todo_list 来维护任务,而不是创建本地 todo_list 文件。"
79
+ ],
80
+ "claude": [
81
+ "When calling cueme cue/pause, set the tool wait timeout to 24h to avoid premature tool timeouts."
82
+ ]
83
+ },
51
84
  "cueme.proto.protocol_path": "~/path/to/protocol.md"
52
85
  }
53
86
  ```
@@ -60,6 +93,10 @@ Injected content is managed between these markers:
60
93
  <!-- HUMAN_AGENT_PROTO_BEGIN -->
61
94
  ... managed content ...
62
95
  <!-- HUMAN_AGENT_PROTO_END -->
96
+
97
+ <!-- HUMAN_AGENT_RUNTIME_BEGIN -->
98
+ ... managed content ...
99
+ <!-- HUMAN_AGENT_RUNTIME_END -->
63
100
  ```
64
101
 
65
102
  Notes:
@@ -101,6 +138,8 @@ Auto-detect (current platform only):
101
138
 
102
139
  - `vscode`: `.vscode/prompts/human_proto.md` (workspace) then platform user path
103
140
  - `windsurf`: `.codeium/windsurf/memories/global_rules.md` (workspace) then platform user path
141
+ - `codex`: uses `~/.codex/AGENTS.md` by default (not auto-detected)
142
+ - `claude`: uses `~/.claude/CLAUDE.md` by default (not auto-detected)
104
143
 
105
144
  ### Helpers
106
145
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueme",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Cue command protocol adapter (stdin/stdout JSON)",
5
5
  "license": "Apache-2.0",
6
6
  "files": [
@@ -18,12 +18,10 @@
18
18
  "engines": {
19
19
  "node": ">=20"
20
20
  },
21
- "scripts": {
22
- "prepare": "node -c src/cli.js && node -c src/handler.js"
23
- },
24
21
  "dependencies": {
25
22
  "better-sqlite3": "^12.6.0",
26
23
  "chardet": "^2.1.1",
27
24
  "iconv-lite": "^0.7.2"
28
- }
29
- }
25
+ },
26
+ "scripts": {}
27
+ }
package/src/envelope.js CHANGED
@@ -3,10 +3,14 @@ function parseTagBlocksEnvelope(raw, opts = {}) {
3
3
  const text = String(raw == null ? '' : raw);
4
4
  const trimmed = text.trim();
5
5
  if (!trimmed) {
6
- // Fallback: use default prompt based on command type
7
- // allowPayload === true means cue command, false means pause command
8
- const defaultPrompt = allowPayload ? 'hi' : 'pause';
9
- return { ok: true, prompt: defaultPrompt, payload: null };
6
+ return {
7
+ ok: false,
8
+ error:
9
+ 'error: stdin cannot be empty. You MUST provide input using tag-block envelope:\n' +
10
+ '<cueme_prompt>\n...\n</cueme_prompt>\n' +
11
+ (allowPayload ? '<cueme_payload>\n...\n</cueme_payload>\n' : '') +
12
+ 'This is critical for proper interaction flow.\n',
13
+ };
10
14
  }
11
15
 
12
16
  if (trimmed.startsWith('{')) {
package/src/handler.js CHANGED
@@ -135,6 +135,7 @@ function detectAgentTerminal() {
135
135
  }
136
136
 
137
137
  async function handleJoin(db, agent_runtime) {
138
+ await initSchema(db);
138
139
  const agent_id = generateName();
139
140
  const project_dir = process.cwd();
140
141
  const agent_terminal = detectAgentTerminal();
package/src/proto.js CHANGED
@@ -4,9 +4,13 @@ const path = require('path');
4
4
 
5
5
  const BEGIN_MARKER = '<!-- HUMAN_AGENT_PROTO_BEGIN -->';
6
6
  const END_MARKER = '<!-- HUMAN_AGENT_PROTO_END -->';
7
+ const RUNTIME_BEGIN_MARKER = '<!-- HUMAN_AGENT_RUNTIME_BEGIN -->';
8
+ const RUNTIME_END_MARKER = '<!-- HUMAN_AGENT_RUNTIME_END -->';
7
9
 
8
10
  const BEGIN_MARKER_RE = /<!--\s*(?:HUMAN|HUAMN)_AGENT_PROTO_BEGIN\s*-->/;
9
11
  const END_MARKER_RE = /<!--\s*(?:HUMAN|HUAMN)_AGENT_PROTO_END\s*-->/;
12
+ const RUNTIME_BEGIN_MARKER_RE = /<!--\s*(?:HUMAN|HUAMN)_AGENT_RUNTIME_BEGIN\s*-->/;
13
+ const RUNTIME_END_MARKER_RE = /<!--\s*(?:HUMAN|HUAMN)_AGENT_RUNTIME_END\s*-->/;
10
14
 
11
15
  function getPlatformKey() {
12
16
  const p = process.platform;
@@ -146,6 +150,16 @@ function defaultPathMapTemplate() {
146
150
  out['windows.kiro'] = path.join(userProfile, '.kiro', 'steering', 'cueme_proto.md');
147
151
  out['linux.kiro'] = path.join(home, '.kiro', 'steering', 'cueme_proto.md');
148
152
 
153
+ // Codex (global)
154
+ out['macos.codex'] = path.join(home, '.codex', 'AGENTS.md');
155
+ out['windows.codex'] = path.join(userProfile, '.codex', 'AGENTS.md');
156
+ out['linux.codex'] = path.join(home, '.codex', 'AGENTS.md');
157
+
158
+ // Claude Code (global)
159
+ out['macos.claude'] = path.join(home, '.claude', 'CLAUDE.md');
160
+ out['windows.claude'] = path.join(userProfile, '.claude', 'CLAUDE.md');
161
+ out['linux.claude'] = path.join(home, '.claude', 'CLAUDE.md');
162
+
149
163
  return out;
150
164
  }
151
165
 
@@ -154,10 +168,21 @@ function defaultConfigTemplate() {
154
168
  return {
155
169
  'cueme.proto.path': defaultPathMapTemplate(),
156
170
  'cueme.proto.prefix': {
171
+ codex: [],
172
+ claude: [],
157
173
  windsurf: [],
158
174
  vscode: ['---', 'applyTo: "**"', '---'],
159
175
  kiro: [],
160
176
  },
177
+ 'cueme.proto.runtime': {
178
+ codex: [
179
+ 'When calling cueme cue/pause, set the tool wait timeout to 24h to avoid premature tool timeouts.',
180
+ '不要误解“更新 todo_list”的指令,因为你是 codex,你需要做的是:把 update_plan 当作 todo_list 来维护任务,而不是创建本地 todo_list 文件。',
181
+ ],
182
+ claude: [
183
+ 'When calling cueme cue/pause, set the tool wait timeout to 24h to avoid premature tool timeouts.',
184
+ ],
185
+ },
161
186
  'cueme.proto.protocol_path': protocolPath,
162
187
  };
163
188
  }
@@ -259,7 +284,11 @@ function ensureDirForFile(filePath) {
259
284
 
260
285
  function buildFinalProto({ cfg, agent }) {
261
286
  const prefixMap = cfg['cueme.proto.prefix'] || {};
262
- const protocolPath = cfg['cueme.proto.protocol_path'];
287
+ const runtimeMap = cfg['cueme.proto.runtime'] || {};
288
+ let protocolPath = cfg['cueme.proto.protocol_path'];
289
+ if (typeof protocolPath !== 'string' || protocolPath.trim().length === 0) {
290
+ protocolPath = path.join(__dirname, '..', 'protocol.md');
291
+ }
263
292
 
264
293
  const prefixRaw = prefixMap[agent];
265
294
  let prefix;
@@ -271,8 +300,16 @@ function buildFinalProto({ cfg, agent }) {
271
300
  throw new Error(`error: prefix not configured: cueme.proto.prefix["${agent}"]`);
272
301
  }
273
302
 
274
- if (typeof protocolPath !== 'string' || protocolPath.trim().length === 0) {
275
- throw new Error('error: cannot read protocol.md');
303
+ const runtimeRaw = runtimeMap[agent];
304
+ let runtime = '';
305
+ if (runtimeRaw == null || runtimeRaw === '') {
306
+ runtime = '';
307
+ } else if (typeof runtimeRaw === 'string') {
308
+ runtime = runtimeRaw;
309
+ } else if (Array.isArray(runtimeRaw) && runtimeRaw.every((x) => typeof x === 'string')) {
310
+ runtime = runtimeRaw.join('\n');
311
+ } else {
312
+ throw new Error(`error: runtime not configured: cueme.proto.runtime["${agent}"]`);
276
313
  }
277
314
 
278
315
  const protocolPathExpanded = expandPath(protocolPath);
@@ -284,10 +321,10 @@ function buildFinalProto({ cfg, agent }) {
284
321
  try {
285
322
  protocol = fs.readFileSync(resolvedProtocolPath, 'utf8');
286
323
  } catch {
287
- throw new Error('error: cannot read protocol.md');
324
+ throw new Error(`error: cannot read protocol file: ${resolvedProtocolPath}`);
288
325
  }
289
326
 
290
- return { prefix, protocol };
327
+ return { prefix, protocol, runtime };
291
328
  }
292
329
 
293
330
  function resolveTargetPath({ cfg, agent }) {
@@ -302,41 +339,114 @@ function resolveTargetPath({ cfg, agent }) {
302
339
  return path.isAbsolute(expanded) ? expanded : path.resolve(process.cwd(), expanded);
303
340
  }
304
341
 
305
- function makeManagedBlock({ prefix, protocol, eol }) {
342
+ function makeProtoBlock({ protocol, eol }) {
306
343
  const normalizedProto = String(protocol || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
307
344
  const protoLines = normalizedProto.split('\n');
308
- const managedBlock = [BEGIN_MARKER, ...protoLines, END_MARKER].join(eol) + eol;
309
-
345
+ return [BEGIN_MARKER, ...protoLines, END_MARKER].join(eol) + eol;
346
+ }
347
+
348
+ function makeRuntimeBlock({ runtime, eol }) {
349
+ const runtimeText = String(runtime || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
350
+ if (!runtimeText) return '';
351
+ const runtimeLines = runtimeText.split('\n');
352
+ return [RUNTIME_BEGIN_MARKER, ...runtimeLines, RUNTIME_END_MARKER].join(eol) + eol;
353
+ }
354
+
355
+ function findBlockRange(text, beginRe, endRe) {
356
+ const beginMatch = text.match(beginRe);
357
+ const endMatch = text.match(endRe);
358
+ if (!beginMatch || !endMatch || endMatch.index <= beginMatch.index) return null;
359
+ return { beginIdx: beginMatch.index, endIdx: endMatch.index, endLen: endMatch[0].length };
360
+ }
361
+
362
+ function extractBlock({ text, range, eol }) {
363
+ let block = text.slice(range.beginIdx, range.endIdx + range.endLen);
364
+ const after = text.slice(range.endIdx + range.endLen);
365
+ if (after.startsWith(eol)) block += eol;
366
+ return block;
367
+ }
368
+
369
+ function replaceBlock({ text, range, block, eol }) {
370
+ const before = text.slice(0, range.beginIdx);
371
+ const after = text.slice(range.endIdx + range.endLen);
372
+ const afterTrim = after.startsWith(eol) ? after.slice(eol.length) : after;
373
+ if (!block) return before + afterTrim;
374
+ return before + block + afterTrim;
375
+ }
376
+
377
+ function appendBlock({ text, block, eol }) {
378
+ if (!block) return text;
379
+ let out = text || '';
380
+ if (out && !out.endsWith(eol)) out += eol;
381
+ out += block;
382
+ return out;
383
+ }
384
+
385
+ function makeCombinedBlock({ prefix, protoBlock, runtimeBlock, eol }) {
386
+ let block = protoBlock + (runtimeBlock || '');
310
387
  if (prefix && prefix.trim()) {
311
388
  const normalizedPrefix = String(prefix).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
312
- return normalizedPrefix + eol + eol + managedBlock;
389
+ return normalizedPrefix + eol + eol + block;
313
390
  }
314
-
315
- return managedBlock;
391
+ return block;
316
392
  }
317
393
 
318
- function applyManagedBlock({ existing, prefix, protocol }) {
319
- const eol = detectEol(existing);
320
- const block = makeManagedBlock({ prefix, protocol, eol });
321
-
322
- const beginMatch = existing.match(BEGIN_MARKER_RE);
323
- const endMatch = existing.match(END_MARKER_RE);
324
- if (beginMatch && endMatch && endMatch.index > beginMatch.index) {
325
- const beginIdx = beginMatch.index;
326
- const endIdx = endMatch.index;
327
- const endLen = endMatch[0].length;
394
+ function applyManagedBlocks({ existing, prefix, protocol, runtime }) {
395
+ const eol = existing ? detectEol(existing) : os.EOL;
396
+ const protoBlock = makeProtoBlock({ protocol, eol });
397
+ const runtimeBlock = makeRuntimeBlock({ runtime, eol });
398
+ const hasProto = findBlockRange(existing, BEGIN_MARKER_RE, END_MARKER_RE);
399
+ const hasRuntime = findBlockRange(existing, RUNTIME_BEGIN_MARKER_RE, RUNTIME_END_MARKER_RE);
400
+ const hasAny = Boolean(hasProto || hasRuntime);
401
+
402
+ if (!hasAny) {
403
+ const combinedBlock = makeCombinedBlock({ prefix, protoBlock, runtimeBlock, eol });
404
+ return {
405
+ out: appendBlock({ text: existing, block: combinedBlock, eol }),
406
+ prefix_added: Boolean(prefix && prefix.trim()),
407
+ proto_action: 'added',
408
+ runtime_action: runtimeBlock ? 'added' : 'skipped',
409
+ };
410
+ }
328
411
 
329
- const before = existing.slice(0, beginIdx);
330
- const after = existing.slice(endIdx + endLen);
412
+ let out = existing;
413
+ let proto_action = 'skipped';
414
+ let runtime_action = 'skipped';
415
+
416
+ const protoRange = findBlockRange(out, BEGIN_MARKER_RE, END_MARKER_RE);
417
+ if (protoRange) {
418
+ const existingBlock = extractBlock({ text: out, range: protoRange, eol });
419
+ if (existingBlock === protoBlock) {
420
+ proto_action = 'unchanged';
421
+ } else {
422
+ out = replaceBlock({ text: out, range: protoRange, block: protoBlock, eol });
423
+ proto_action = 'updated';
424
+ }
425
+ } else {
426
+ out = appendBlock({ text: out, block: protoBlock, eol });
427
+ proto_action = 'added';
428
+ }
331
429
 
332
- const afterTrim = after.startsWith(eol) ? after.slice(eol.length) : after;
333
- return before + block + afterTrim;
430
+ const runtimeRange = findBlockRange(out, RUNTIME_BEGIN_MARKER_RE, RUNTIME_END_MARKER_RE);
431
+ if (runtimeRange) {
432
+ if (runtimeBlock) {
433
+ const existingBlock = extractBlock({ text: out, range: runtimeRange, eol });
434
+ if (existingBlock === runtimeBlock) {
435
+ runtime_action = 'unchanged';
436
+ } else {
437
+ out = replaceBlock({ text: out, range: runtimeRange, block: runtimeBlock, eol });
438
+ runtime_action = 'updated';
439
+ }
440
+ } else {
441
+ out = replaceBlock({ text: out, range: runtimeRange, block: '', eol });
442
+ runtime_action = 'removed';
443
+ }
444
+ } else if (runtimeBlock) {
445
+ out = appendBlock({ text: out, block: runtimeBlock, eol });
446
+ runtime_action = 'added';
334
447
  }
335
448
 
336
- let out = existing;
337
- if (!out.endsWith(eol)) out += eol;
338
- out += block;
339
- return out;
449
+ return { out, prefix_added: false, proto_action, runtime_action };
340
450
  }
341
451
 
342
452
  function listAgents({ cfg }) {
@@ -371,7 +481,7 @@ function protoLs() {
371
481
  function protoApply(agent) {
372
482
  const cfg = readConfigOrThrow({ auto_init: true });
373
483
  const targetPath = resolveTargetPath({ cfg, agent });
374
- const { prefix, protocol } = buildFinalProto({ cfg, agent });
484
+ const { prefix, protocol, runtime } = buildFinalProto({ cfg, agent });
375
485
 
376
486
  let existing = '';
377
487
  let exists = false;
@@ -383,20 +493,22 @@ function protoApply(agent) {
383
493
  exists = false;
384
494
  }
385
495
 
386
- const eol = exists ? detectEol(existing) : os.EOL;
387
- const managedBlock = makeManagedBlock({ prefix, protocol, eol });
388
-
389
- let out;
390
- if (!exists) {
391
- out = managedBlock;
392
- } else {
393
- out = applyManagedBlock({ existing, prefix, protocol });
394
- }
496
+ const { out, prefix_added, proto_action, runtime_action } = applyManagedBlocks({
497
+ existing,
498
+ prefix,
499
+ protocol,
500
+ runtime,
501
+ });
395
502
 
396
503
  ensureDirForFile(targetPath);
397
504
  fs.writeFileSync(targetPath, out, 'utf8');
398
505
 
399
- return `ok: applied to ${targetPath}`;
506
+ const parts = [
507
+ `prefix=${prefix_added ? 'added' : 'skipped'}`,
508
+ `proto=${proto_action}`,
509
+ `runtime=${runtime_action}`,
510
+ ];
511
+ return `ok: applied to ${targetPath} (${parts.join(', ')})`;
400
512
  }
401
513
 
402
514
  function protoRemove(agent) {
@@ -410,23 +522,27 @@ function protoRemove(agent) {
410
522
  return `ok: file does not exist: ${targetPath}`;
411
523
  }
412
524
 
413
- const beginMatch = existing.match(BEGIN_MARKER_RE);
414
- const endMatch = existing.match(END_MARKER_RE);
525
+ const eol = detectEol(existing);
526
+ let out = existing;
527
+ let removed = false;
415
528
 
416
- if (!beginMatch || !endMatch || endMatch.index <= beginMatch.index) {
417
- return `ok: no managed block found in: ${targetPath}`;
418
- }
529
+ const removeBlock = (text, beginRe, endRe) => {
530
+ const range = findBlockRange(text, beginRe, endRe);
531
+ if (!range) return { text, removed: false };
532
+ return { text: replaceBlock({ text, range, block: '', eol }), removed: true };
533
+ };
419
534
 
420
- const beginIdx = beginMatch.index;
421
- const endIdx = endMatch.index;
422
- const endLen = endMatch[0].length;
535
+ const protoResult = removeBlock(out, BEGIN_MARKER_RE, END_MARKER_RE);
536
+ out = protoResult.text;
537
+ removed = removed || protoResult.removed;
423
538
 
424
- const before = existing.slice(0, beginIdx);
425
- const after = existing.slice(endIdx + endLen);
539
+ const runtimeResult = removeBlock(out, RUNTIME_BEGIN_MARKER_RE, RUNTIME_END_MARKER_RE);
540
+ out = runtimeResult.text;
541
+ removed = removed || runtimeResult.removed;
426
542
 
427
- const eol = detectEol(existing);
428
- const afterTrim = after.startsWith(eol) ? after.slice(eol.length) : after;
429
- const out = before + afterTrim;
543
+ if (!removed) {
544
+ return `ok: no managed block found in: ${targetPath}`;
545
+ }
430
546
 
431
547
  if (out.trim().length === 0) {
432
548
  try {