@test-lab-ai/cli 0.2.15 → 0.2.17

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.
package/bin/testlab.mjs CHANGED
@@ -13,7 +13,6 @@
13
13
  * testlab import <path> [--dry-run] Import a plan file or a directory of *.json
14
14
  *
15
15
  * Auth: --key -> $TESTLAB_API_KEY -> ~/.test-lab/config.json
16
- * API: https://www.test-lab.ai (fixed)
17
16
  */
18
17
  import { parseArgs } from "node:util"
19
18
  import fs from "node:fs"
@@ -41,11 +40,14 @@ Usage:
41
40
  (credentials, labels, fixtures, plans)
42
41
  testlab plans list List your test plans
43
42
  testlab plans create -f plan.json Create one plan from JSON
43
+ testlab plans update <id> [-f patch.json] [--prompt P] [--name N]
44
+ Update an existing plan; only the
45
+ fields you pass change
44
46
  testlab projects list List your projects
45
47
  testlab credentials set <key> --value <value> Set a credential ({{credentials.<key>}})
46
48
  testlab credentials list List credential keys (values never shown)
47
49
  testlab labels list List your labels
48
- testlab data list List your data fixtures
50
+ testlab data list List data fixtures + built-in file fixtures
49
51
  testlab data create -f fixture.json Create a data fixture ({{data.<fixture>.<field>}})
50
52
  testlab scripts upload <file> --plan <id> Upload a local Playwright script for a plan
51
53
  (skips paid AI generation; --device optional)
@@ -217,6 +219,34 @@ async function cmdPlansCreate(flags) {
217
219
  log(`✓ Created plan #${r.json.testPlan.id}: ${r.json.testPlan.name}`)
218
220
  }
219
221
 
222
+ async function cmdPlansUpdate(flags, args) {
223
+ const { apiKey, apiUrl } = requireAuth(flags)
224
+ const planId = parsePlanIdArg(args[2] ?? flags.plan)
225
+ if (!Number.isInteger(planId)) {
226
+ errExit("usage: testlab plans update <id> [-f patch.json] [--prompt P] [--name N] (numeric plan id; see `testlab plans list`)")
227
+ }
228
+ // Build a partial patch. A JSON file is the full surface (name, prompt,
229
+ // testType, agentType, devices, paused, projectId); --prompt / --name are
230
+ // quick overrides for the two common edits. Only the fields present are sent,
231
+ // and the server changes only those.
232
+ let patch = {}
233
+ if (flags.file) {
234
+ try {
235
+ patch = JSON.parse(fs.readFileSync(flags.file, "utf8"))
236
+ } catch (e) {
237
+ errExit(`could not read ${flags.file}: ${e.message}`)
238
+ }
239
+ }
240
+ if (flags.prompt !== undefined) patch.prompt = flags.prompt
241
+ if (flags.name !== undefined) patch.name = flags.name
242
+ if (Object.keys(patch).length === 0) {
243
+ errExit("nothing to update: pass -f <patch.json> and/or --prompt / --name. Editable fields: name, prompt, testType, agentType, devices, paused, projectId")
244
+ }
245
+ const r = await apiFetch(apiUrl, apiKey, "PUT", `/api/v1/test-plans/${planId}`, patch)
246
+ if (!r.ok) errExit(`${r.status}: ${r.json?.error || ""}`)
247
+ log(`✓ Updated plan #${r.json.testPlan.id}: ${r.json.testPlan.name}`)
248
+ }
249
+
220
250
  async function cmdCredentialsSet(flags, args) {
221
251
  const { apiKey, apiUrl } = requireAuth(flags)
222
252
  const key = args[2]
@@ -338,13 +368,27 @@ async function cmdDataList(flags) {
338
368
  const fixtures = r.json?.fixtures || []
339
369
  if (fixtures.length === 0) {
340
370
  log("No data fixtures yet.")
341
- return
342
- }
343
- for (const fx of fixtures) {
344
- const fields = (fx.fields || []).map((f) => f.key).join(", ")
345
- log(` ${fx.key}${fx.label ? ` (${fx.label})` : ""}${fields ? ` - ${fields}` : ""}`)
371
+ } else {
372
+ log("Data fixtures (reference as {{data.<fixture>.<field>}}):")
373
+ for (const fx of fixtures) {
374
+ const fields = (fx.fields || []).map((f) => f.key).join(", ")
375
+ log(` ${fx.key}${fx.label ? ` (${fx.label})` : ""}${fields ? ` - ${fields}` : ""}`)
376
+ }
377
+ log(` ${fixtures.length} data fixture(s)`)
378
+ }
379
+ // Built-in upload-file fixtures (reference in an upload step as {{file.<name>}}).
380
+ const files = r.json?.files || []
381
+ if (files.length > 0) {
382
+ log("\nFile fixtures (reference in an upload step as {{file.<name>}}):")
383
+ const byCat = {}
384
+ for (const f of files) (byCat[f.category] ||= []).push(f)
385
+ for (const cat of Object.keys(byCat).sort()) {
386
+ const defaults = byCat[cat].filter((f) => f.default).map((f) => `{{${f.token}}}`)
387
+ const shown = defaults.length ? defaults : byCat[cat].map((f) => `{{${f.token}}}`)
388
+ log(` ${cat}: ${shown.join(" ")}`)
389
+ }
390
+ log(` ${files.length} file fixture(s) (append a size, e.g. {{file.pdf.25mb}})`)
346
391
  }
347
- log(`\n${fixtures.length} fixture(s)`)
348
392
  }
349
393
 
350
394
  async function cmdDataCreate(flags) {
@@ -626,7 +670,8 @@ async function main() {
626
670
  case "plans":
627
671
  if (args[1] === "list") return cmdPlansList(flags)
628
672
  if (args[1] === "create") return cmdPlansCreate(flags)
629
- return errExit("usage: testlab plans <list|create>")
673
+ if (args[1] === "update") return cmdPlansUpdate(flags, args)
674
+ return errExit("usage: testlab plans <list|create|update>")
630
675
  case "projects":
631
676
  if (args[1] === "list") return cmdProjectsList(flags)
632
677
  return errExit("usage: testlab projects list")
package/lib/config.mjs CHANGED
@@ -2,7 +2,6 @@
2
2
  * Config + auth resolution for the testlab CLI.
3
3
  *
4
4
  * API key: --key flag -> $TESTLAB_API_KEY -> ~/.test-lab/config.json
5
- * API base: always https://www.test-lab.ai (not configurable)
6
5
  */
7
6
  import fs from "node:fs"
8
7
  import os from "node:os"
@@ -35,8 +34,7 @@ export function saveConfig(cfg) {
35
34
  export function resolveAuth(flags) {
36
35
  const cfg = loadConfig()
37
36
  const apiKey = flags.key || process.env.TESTLAB_API_KEY || cfg.apiKey || null
38
- // API base is fixed to production. It used to be overridable via --api-url /
39
- // TESTLAB_API_URL / a stored config value; that was removed so an API key is
40
- // never sent to an arbitrary host.
37
+ // The base URL used to be overridable (--api-url / TESTLAB_API_URL / config);
38
+ // removed so an API key can't be sent to an arbitrary host.
41
39
  return { apiKey, apiUrl: DEFAULT_API_URL }
42
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@test-lab-ai/cli",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "Import existing test plans into test-lab.ai from the command line (or an AI agent).",
5
5
  "type": "module",
6
6
  "bin": {