Compare commits
2 Commits
main
...
feature/13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08f56adae0 | ||
|
|
89dd1702b6 |
@ -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
|
||||
|
||||
28
extensions/zalouser/src/zca.test.ts
Normal file
28
extensions/zalouser/src/zca.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user