Compare commits

...

2 Commits

Author SHA1 Message Date
Peter Steinberger
08f56adae0 test: add zalouser zca parse tests (#1379) (thanks @ptn1411) 2026-01-22 00:00:05 +00:00
Pham Nam
89dd1702b6 Refs #1378: scaffold zalouser extension 2026-01-21 23:20:19 +00:00
3 changed files with 55 additions and 1 deletions

View File

@ -28,6 +28,7 @@ Docs: https://docs.clawd.bot
- Model picker: list the full catalog when no model allowlist is configured.
- Discord: honor wildcard channel configs via shared match helpers. (#1334) Thanks @pvoo.
- BlueBubbles: resolve short message IDs safely and expose full IDs in templates. (#1387) Thanks @tyler6204.
- Zalouser: parse zca JSON output when logs or ANSI codes are present. (#1379) Thanks @ptn1411.
- Infra: preserve fetch helper methods when wrapping abort signals. (#1387)
## 2026.1.20

View File

@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { parseJsonOutput } from "./zca.js";
describe("parseJsonOutput", () => {
it("parses plain JSON output", () => {
expect(parseJsonOutput<{ ok: boolean }>('{"ok":true}')).toEqual({ ok: true });
});
it("parses JSON wrapped in ANSI codes", () => {
const output = "\u001B[32m{\"ok\":true}\u001B[0m";
expect(parseJsonOutput<{ ok: boolean }>(output)).toEqual({ ok: true });
});
it("parses JSON after log prefix lines", () => {
const output = ["INFO starting up", "{\"items\":[1,2]}"].join("\n");
expect(parseJsonOutput<{ items: number[] }>(output)).toEqual({ items: [1, 2] });
});
it("skips invalid JSON blocks and returns the next payload", () => {
const output = ["INFO", "{bad}", "{\"ok\":true}"].join("\n");
expect(parseJsonOutput<{ ok: boolean }>(output)).toEqual({ ok: true });
});
it("returns null when no JSON payload is found", () => {
expect(parseJsonOutput("INFO no payload")).toBeNull();
});
});

View File

@ -114,11 +114,36 @@ export function runZcaInteractive(
});
}
function stripAnsi(str: string): string {
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
}
export function parseJsonOutput<T>(stdout: string): T | null {
try {
return JSON.parse(stdout) as T;
} catch {
return null;
const cleaned = stripAnsi(stdout);
try {
return JSON.parse(cleaned) as T;
} catch {
// zca may prefix output with INFO/log lines, try to find JSON
const lines = cleaned.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith("{") || line.startsWith("[")) {
// Try parsing from this line to the end
const jsonCandidate = lines.slice(i).join("\n").trim();
try {
return JSON.parse(jsonCandidate) as T;
} catch {
continue;
}
}
}
return null;
}
}
}