diff --git a/src/parser.ts b/src/parser.ts index 845adfc..039a041 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -60,10 +60,20 @@ function tokenizeCommand(input) { const ch = input[i]; if (quote) { + // In single-quoted strings, everything is literal (no escape processing) + if (quote === "'") { + if (ch === "'") { + quote = null; + continue; + } + current += ch; + continue; + } + // In double-quoted strings, preserve escape sequences (backslash + next char) if (ch === '\\') { const next = input[i + 1]; if (next) { - current += next; + current += ch + next; i++; continue; } diff --git a/test/parser.test.ts b/test/parser.test.ts index 2db7cbf..0209316 100644 --- a/test/parser.test.ts +++ b/test/parser.test.ts @@ -18,3 +18,19 @@ test('parsePipeline keeps quoted pipes', () => { assert.equal(p.length, 2); assert.deepEqual(p[0].args._, ['echo', 'a|b']); }); + +test('single-quoted strings are fully literal (no escape processing)', () => { + const p = parsePipeline("exec echo 'hello\\nworld'"); + assert.deepEqual(p[0].args._, ['echo', 'hello\\nworld']); +}); + +test('double-quoted strings preserve escape sequences', () => { + const p = parsePipeline('exec echo "line1\\nline2"'); + assert.deepEqual(p[0].args._, ['echo', 'line1\\nline2']); +}); + +test('--args-json with escaped JSON in double quotes', () => { + const p = parsePipeline('invoke --args-json "{\\"prompt\\":\\"line1\\\\nline2\\"}"'); + // Backslashes are preserved in double-quoted strings: \" stays as \" + assert.equal(p[0].args['args-json'], '{\\"prompt\\":\\"line1\\\\nline2\\"}'); +});