qazen-cli 0.2.0 → 0.2.1

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.
@@ -10,6 +10,76 @@ function firstTextBlock(content) {
10
10
  const block = content[0];
11
11
  return block && block.type === "text" ? block.text : "";
12
12
  }
13
+ function salvageJson(text) {
14
+ const cleaned = stripCodeFences(text);
15
+ // First try clean parse
16
+ try {
17
+ return JSON.parse(cleaned);
18
+ }
19
+ catch {
20
+ // Fall through to salvage
21
+ }
22
+ // Salvage truncated JSON: extract metadata + every complete {...} element block.
23
+ try {
24
+ const descMatch = cleaned.match(/"pageDescription"\s*:\s*"([^"]+)"/);
25
+ const workflowMatch = cleaned.match(/"workflow"\s*:\s*"([^"]+)"/);
26
+ const pageTypeMatch = cleaned.match(/"pageType"\s*:\s*"([^"]+)"/);
27
+ const elements = [];
28
+ let depth = 0;
29
+ let start = -1;
30
+ let inString = false;
31
+ let escape = false;
32
+ for (let i = 0; i < cleaned.length; i++) {
33
+ const ch = cleaned[i];
34
+ if (escape) {
35
+ escape = false;
36
+ continue;
37
+ }
38
+ if (ch === "\\") {
39
+ escape = true;
40
+ continue;
41
+ }
42
+ if (ch === '"') {
43
+ inString = !inString;
44
+ continue;
45
+ }
46
+ if (inString)
47
+ continue;
48
+ if (ch === "{") {
49
+ if (depth === 0)
50
+ start = i;
51
+ depth++;
52
+ }
53
+ else if (ch === "}") {
54
+ depth--;
55
+ if (depth === 0 && start !== -1) {
56
+ try {
57
+ const obj = JSON.parse(cleaned.substring(start, i + 1));
58
+ if (typeof obj.description === "string" &&
59
+ typeof obj.elementType === "string") {
60
+ elements.push(obj);
61
+ }
62
+ }
63
+ catch {
64
+ // skip incomplete object
65
+ }
66
+ start = -1;
67
+ }
68
+ }
69
+ }
70
+ if (elements.length === 0)
71
+ return null;
72
+ return {
73
+ pageDescription: descMatch?.[1] ?? "Page analysis (partial)",
74
+ workflow: workflowMatch?.[1] ?? "Unknown",
75
+ pageType: pageTypeMatch?.[1] ?? "other",
76
+ elements,
77
+ };
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
13
83
  export class VisionNavigator {
14
84
  anthropic;
15
85
  baseUrl;
@@ -221,7 +291,7 @@ export class VisionNavigator {
221
291
  try {
222
292
  const response = await this.anthropic.messages.create({
223
293
  model: MODEL,
224
- max_tokens: 2000,
294
+ max_tokens: 4000,
225
295
  messages: [
226
296
  {
227
297
  role: "user",
@@ -342,7 +412,14 @@ Return ONLY valid JSON — no markdown, no explanation:
342
412
  ],
343
413
  });
344
414
  const text = firstTextBlock(response.content);
345
- const parsed = JSON.parse(stripCodeFences(text));
415
+ const parsed = salvageJson(text);
416
+ if (!parsed) {
417
+ onEvent({
418
+ type: "error",
419
+ message: "Vision analysis failed: could not parse or salvage JSON response",
420
+ });
421
+ return null;
422
+ }
346
423
  onEvent({
347
424
  type: "vision_analysis",
348
425
  message: parsed.pageDescription + (parsed.workflow ? ` (${parsed.workflow})` : ""),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qazen-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "QAZen CLI — capture authenticated browser sessions for enterprise SSO testing",
5
5
  "license": "MIT",
6
6
  "author": "QAZen",