qualty 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/bin/qualty.js +104 -1
  2. package/package.json +1 -1
package/bin/qualty.js CHANGED
@@ -39,7 +39,10 @@ function usage() {
39
39
  "Usage:",
40
40
  " qualty connect --project <project-id> [--port 3000] [--api https://your-api] [--token <bearer-token>]",
41
41
  " qualty run --project <project-id> [--suite-id <suite-id>] [--ids <id1,id2>] [--api https://your-api] [--token <bearer-token>]",
42
- " [--poll-interval 5] [--timeout 30] [--fail-on-failure true]",
42
+ " [--poll-interval 5] [--timeout 30] [--fail-on-failure true] [--no-view-logs]",
43
+ "",
44
+ " After each run, logs include step results, explanation, and final evaluator output (dashboard \"View logs\" data).",
45
+ " Pass --no-view-logs for a short log only (e.g. very large evaluator text).",
43
46
  " qualty resolve --project <project-id> [--suite-id <suite-id>] [--ids <id1,id2>] [--json] [--api https://your-api] [--token <bearer-token>]",
44
47
  "",
45
48
  "Env vars:",
@@ -171,6 +174,97 @@ function sleep(ms) {
171
174
  return new Promise((resolve) => setTimeout(resolve, ms));
172
175
  }
173
176
 
177
+ function truncateForCiLog(text, maxLen) {
178
+ const s = String(text ?? "");
179
+ if (s.length <= maxLen) return s;
180
+ return `${s.slice(0, maxLen)}\n[qualty] … (truncated, ${s.length - maxLen} more chars)`;
181
+ }
182
+
183
+ function logPrefixedLines(prefix, text) {
184
+ const body = String(text ?? "").trimEnd();
185
+ if (!body) return;
186
+ for (const line of body.split("\n")) {
187
+ // eslint-disable-next-line no-console
188
+ console.log(`${prefix}${line}`);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Print the same kind of detail as Qualty "View logs": steps, explanation, evaluator (agent) output.
194
+ * Data comes from GET status payload fields on each combination (steps_json, explanation, agent_output).
195
+ */
196
+ function printQualtyViewLogsReport(executionId, status) {
197
+ // eslint-disable-next-line no-console
198
+ console.log(`\n[qualty] ━━━ View logs: ${executionId} (${status.episode_name || "run"}) ━━━`);
199
+ if (status.url) {
200
+ // eslint-disable-next-line no-console
201
+ console.log(`[qualty] URL: ${status.url}`);
202
+ }
203
+ if (status.error) {
204
+ // eslint-disable-next-line no-console
205
+ console.log(`[qualty] Run error: ${truncateForCiLog(status.error, 4000)}`);
206
+ }
207
+ if (status.expected_behavior) {
208
+ // eslint-disable-next-line no-console
209
+ console.log(`[qualty] Expected behavior:\n[qualty] ${truncateForCiLog(status.expected_behavior, 6000).split("\n").join("\n[qualty] ")}`);
210
+ }
211
+ const combos = Array.isArray(status.combinations) ? status.combinations : [];
212
+ if (combos.length === 0) {
213
+ // eslint-disable-next-line no-console
214
+ console.log(
215
+ "[qualty] (No per-device breakdown in API response yet — open this run in the Qualty dashboard for full logs.)"
216
+ );
217
+ // eslint-disable-next-line no-console
218
+ console.log(`[qualty] ━━━ End view logs: ${executionId} ━━━\n`);
219
+ return;
220
+ }
221
+ for (let i = 0; i < combos.length; i += 1) {
222
+ const c = combos[i];
223
+ const device = c.device ?? "?";
224
+ const comboStatus = c.status ?? (c.success === true ? "passed" : c.success === false ? "failed" : "?");
225
+ // eslint-disable-next-line no-console
226
+ console.log(`\n[qualty] --- Combination ${i + 1}/${combos.length} (${device} · ${comboStatus}) ---`);
227
+ const steps = Array.isArray(c.steps_json) ? c.steps_json : [];
228
+ if (steps.length > 0) {
229
+ // eslint-disable-next-line no-console
230
+ console.log(`[qualty] Steps (${steps.length}):`);
231
+ for (let j = 0; j < steps.length; j += 1) {
232
+ const s = steps[j];
233
+ const label =
234
+ (s.name && String(s.name).trim()) ||
235
+ (s.description && String(s.description).trim().slice(0, 100)) ||
236
+ `Step ${j + 1}`;
237
+ const st = s.status != null ? s.status : "?";
238
+ // eslint-disable-next-line no-console
239
+ console.log(`[qualty] ${j + 1}. [${st}] ${label}`);
240
+ if (s.description && String(s.description).trim() && String(s.description) !== String(s.name)) {
241
+ logPrefixedLines("[qualty] ", truncateForCiLog(s.description, 4000));
242
+ }
243
+ if (s.action) {
244
+ // eslint-disable-next-line no-console
245
+ console.log(`[qualty] action: ${truncateForCiLog(s.action, 2000)}`);
246
+ }
247
+ }
248
+ } else if (c.total_steps) {
249
+ // eslint-disable-next-line no-console
250
+ console.log(`[qualty] (Step list not available yet; ${c.total_steps} step(s) reported.)`);
251
+ }
252
+ if (c.explanation) {
253
+ // eslint-disable-next-line no-console
254
+ console.log(`[qualty] Explanation:\n[qualty] ${truncateForCiLog(c.explanation, 12000).split("\n").join("\n[qualty] ")}`);
255
+ }
256
+ const evaluator = c.agent_output ?? c.gpt_output;
257
+ if (evaluator) {
258
+ // eslint-disable-next-line no-console
259
+ console.log(
260
+ `[qualty] Final evaluator output:\n[qualty] ${truncateForCiLog(evaluator, 16000).split("\n").join("\n[qualty] ")}`
261
+ );
262
+ }
263
+ }
264
+ // eslint-disable-next-line no-console
265
+ console.log(`\n[qualty] ━━━ End view logs: ${executionId} ━━━\n`);
266
+ }
267
+
174
268
  async function runCi(args) {
175
269
  const apiUrl = resolveApiUrl(args);
176
270
  const token = args.token || process.env.QUALTY_API_TOKEN;
@@ -183,6 +277,7 @@ async function runCi(args) {
183
277
  const pollIntervalSec = Number(args["poll-interval"] || 5);
184
278
  const timeoutMin = Number(args.timeout || 30);
185
279
  const failOnFailure = parseBoolean(args["fail-on-failure"], true);
280
+ const noViewLogs = parseBoolean(args["no-view-logs"], false);
186
281
 
187
282
  if (!token) {
188
283
  throw new Error("Missing auth token. Pass --token or set QUALTY_API_TOKEN.");
@@ -282,6 +377,14 @@ async function runCi(args) {
282
377
  );
283
378
  }
284
379
 
380
+ if (!noViewLogs) {
381
+ // eslint-disable-next-line no-console
382
+ console.log(`[qualty] Detailed run output (same fields as dashboard "View logs"):`);
383
+ for (const executionId of executionJobIds) {
384
+ printQualtyViewLogsReport(executionId, finalStatuses[executionId] || {});
385
+ }
386
+ }
387
+
285
388
  // eslint-disable-next-line no-console
286
389
  console.log(`[qualty] Summary: ${passed} passed, ${failed} failed.`);
287
390
  if (failOnFailure && failed > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualty",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Qualty CLI for localhost and CI test runs",
5
5
  "bin": {
6
6
  "qualty": "bin/qualty.js"