@test-lab-ai/cli 0.2.7 → 0.2.8
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/README.md +3 -0
- package/bin/testlab.mjs +47 -3
- package/lib/skills.mjs +17 -0
- package/lib/update-check.mjs +17 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,9 @@ Cursor user rules are set in Cursor's Settings. Restart your agent to load it.
|
|
|
123
123
|
It installs from the public [`Test-Lab-ai/skills`](https://github.com/Test-Lab-ai/skills)
|
|
124
124
|
mirror, so you always get the latest version.
|
|
125
125
|
|
|
126
|
+
`testlab skills update` refreshes every installed copy to the latest, and the CLI
|
|
127
|
+
also refreshes them automatically the first time you run it after an upgrade.
|
|
128
|
+
|
|
126
129
|
## For AI agents
|
|
127
130
|
|
|
128
131
|
Run **`testlab examples`** for the complete JSON reference, or read `AGENTS.md`
|
package/bin/testlab.mjs
CHANGED
|
@@ -23,8 +23,8 @@ import { apiFetch } from "../lib/api.mjs"
|
|
|
23
23
|
import { loadImportFile, runImport } from "../lib/import.mjs"
|
|
24
24
|
import { browserLogin } from "../lib/login.mjs"
|
|
25
25
|
import { EXAMPLES_TEXT } from "../lib/examples.mjs"
|
|
26
|
-
import { TESTLAB_SKILLS, AGENTS, detectAgents, installSkill } from "../lib/skills.mjs"
|
|
27
|
-
import { checkForUpdate, currentVersion } from "../lib/update-check.mjs"
|
|
26
|
+
import { TESTLAB_SKILLS, AGENTS, detectAgents, installSkill, installedSkillLocations } from "../lib/skills.mjs"
|
|
27
|
+
import { checkForUpdate, currentVersion, previousRunVersion } from "../lib/update-check.mjs"
|
|
28
28
|
|
|
29
29
|
const log = (...a) => console.log(...a)
|
|
30
30
|
function errExit(msg) {
|
|
@@ -51,6 +51,7 @@ Usage:
|
|
|
51
51
|
resource (designed for AI agents)
|
|
52
52
|
testlab skills install [--agent ...] Install the test-lab-plan skill (auto-detects
|
|
53
53
|
Claude/Codex/Cursor; --agent claude|codex|cursor|all)
|
|
54
|
+
testlab skills update Refresh installed skills (also auto-runs after a CLI upgrade)
|
|
54
55
|
|
|
55
56
|
Options:
|
|
56
57
|
--key <tl_…> API key (else $TESTLAB_API_KEY or ~/.test-lab/config.json)
|
|
@@ -392,6 +393,46 @@ function cmdSkillsList() {
|
|
|
392
393
|
log(`\nInstall with: testlab skills install [name] [--agent ${AGENTS.join("|")}|all]`)
|
|
393
394
|
}
|
|
394
395
|
|
|
396
|
+
async function cmdSkillsUpdate() {
|
|
397
|
+
let refreshed = 0
|
|
398
|
+
for (const name of TESTLAB_SKILLS) {
|
|
399
|
+
for (const loc of installedSkillLocations(name)) {
|
|
400
|
+
try {
|
|
401
|
+
const res = await installSkill(name, loc.agent, { global: loc.global })
|
|
402
|
+
refreshed++
|
|
403
|
+
log(`✓ ${name} → ${loc.agent}${loc.global ? " (global)" : ""}: ${res.dest}`)
|
|
404
|
+
} catch (e) {
|
|
405
|
+
log(` ✗ ${name} (${loc.agent}): ${e.message}`)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (refreshed === 0) log("No installed skills found here. Run `testlab skills install` first.")
|
|
410
|
+
else log(`\nRefreshed ${refreshed} location(s). Restart your agent to load the changes.`)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// After a CLI upgrade, refresh any already-installed skills in place — runs once
|
|
414
|
+
// per version bump (best effort). Skipped in CI / when NO_UPDATE_NOTIFIER is set.
|
|
415
|
+
async function maybeRefreshSkillsOnUpgrade() {
|
|
416
|
+
if (process.env.CI || process.env.NO_UPDATE_NOTIFIER) return
|
|
417
|
+
const ver = currentVersion()
|
|
418
|
+
const prev = previousRunVersion(ver)
|
|
419
|
+
if (!prev || prev === ver) return
|
|
420
|
+
let refreshed = 0
|
|
421
|
+
for (const name of TESTLAB_SKILLS) {
|
|
422
|
+
for (const loc of installedSkillLocations(name)) {
|
|
423
|
+
try {
|
|
424
|
+
await installSkill(name, loc.agent, { global: loc.global })
|
|
425
|
+
refreshed++
|
|
426
|
+
} catch {
|
|
427
|
+
/* best effort — never break the actual command */
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (refreshed > 0) {
|
|
432
|
+
process.stderr.write(`↻ CLI upgraded ${prev} → ${ver}; refreshed ${refreshed} installed skill location(s).\n`)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
395
436
|
async function main() {
|
|
396
437
|
let parsed
|
|
397
438
|
try {
|
|
@@ -414,6 +455,8 @@ async function main() {
|
|
|
414
455
|
return
|
|
415
456
|
}
|
|
416
457
|
|
|
458
|
+
await maybeRefreshSkillsOnUpgrade()
|
|
459
|
+
|
|
417
460
|
switch (args[0]) {
|
|
418
461
|
case "login":
|
|
419
462
|
return cmdLogin(flags)
|
|
@@ -441,8 +484,9 @@ async function main() {
|
|
|
441
484
|
return cmdExamples()
|
|
442
485
|
case "skills":
|
|
443
486
|
if (args[1] === "install") return cmdSkillsInstall(flags, args)
|
|
487
|
+
if (args[1] === "update") return cmdSkillsUpdate()
|
|
444
488
|
if (args[1] === "list") return cmdSkillsList()
|
|
445
|
-
return errExit("usage: testlab skills <install|list> [name] [--global]")
|
|
489
|
+
return errExit("usage: testlab skills <install|update|list> [name] [--global]")
|
|
446
490
|
case "import":
|
|
447
491
|
return cmdImport(flags, args)
|
|
448
492
|
default:
|
package/lib/skills.mjs
CHANGED
|
@@ -51,6 +51,23 @@ export function detectAgents() {
|
|
|
51
51
|
return [...found]
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Where skill `name` is already installed — project (cwd) and global locations
|
|
56
|
+
* across all agents — filtered to the paths that actually exist. Used to
|
|
57
|
+
* refresh installed skills (on `skills update` and after a CLI upgrade).
|
|
58
|
+
*/
|
|
59
|
+
export function installedSkillLocations(name) {
|
|
60
|
+
const home = os.homedir()
|
|
61
|
+
const cwd = process.cwd()
|
|
62
|
+
return [
|
|
63
|
+
{ agent: "claude", global: false, path: path.join(cwd, ".claude", "skills", name) },
|
|
64
|
+
{ agent: "codex", global: false, path: path.join(cwd, ".agents", "skills", name) },
|
|
65
|
+
{ agent: "cursor", global: false, path: path.join(cwd, ".cursor", "rules", `${name}.mdc`) },
|
|
66
|
+
{ agent: "claude", global: true, path: path.join(home, ".claude", "skills", name) },
|
|
67
|
+
{ agent: "codex", global: true, path: path.join(home, ".agents", "skills", name) },
|
|
68
|
+
].filter((c) => fs.existsSync(c.path))
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
/** Resolve the install target (base dir + format) for one agent. */
|
|
55
72
|
export function agentTarget(agent, { global } = {}) {
|
|
56
73
|
const home = os.homedir()
|
package/lib/update-check.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Best-effort "update available" notice.
|
|
3
3
|
*
|
|
4
4
|
* Design constraints: never block, slow, or break a command. It reads a cached
|
|
5
|
-
* latest-version (checked against the npm registry at most
|
|
5
|
+
* latest-version (checked against the npm registry at most every few hours), prints a
|
|
6
6
|
* one-line notice to STDERR when the running version is behind, and kicks off a
|
|
7
7
|
* background refresh for next time. Any failure (offline, slow/forbidden
|
|
8
8
|
* registry) is swallowed. Suppressed when stderr isn't a TTY (pipes / CI /
|
|
@@ -15,7 +15,7 @@ import path from "node:path"
|
|
|
15
15
|
const PKG = "@test-lab-ai/cli"
|
|
16
16
|
const REGISTRY = "https://registry.npmjs.org/@test-lab-ai%2Fcli"
|
|
17
17
|
const CACHE = path.join(os.homedir(), ".test-lab", "update-check.json")
|
|
18
|
-
const
|
|
18
|
+
const CHECK_TTL = 3 * 60 * 60 * 1000 // re-check npm at most every 3h
|
|
19
19
|
|
|
20
20
|
export function currentVersion() {
|
|
21
21
|
try {
|
|
@@ -70,16 +70,29 @@ export function checkForUpdate() {
|
|
|
70
70
|
const notice = updateNotice(current, cache.latest)
|
|
71
71
|
if (notice) process.stderr.write(notice + "\n")
|
|
72
72
|
|
|
73
|
-
if (!cache.lastCheck || Date.now() - cache.lastCheck >
|
|
73
|
+
if (!cache.lastCheck || Date.now() - cache.lastCheck > CHECK_TTL) {
|
|
74
74
|
const ctrl = new AbortController()
|
|
75
75
|
const timer = setTimeout(() => ctrl.abort(), 2000)
|
|
76
76
|
fetch(REGISTRY, { signal: ctrl.signal, headers: { Accept: "application/vnd.npm.install-v1+json" } })
|
|
77
77
|
.then((r) => (r.ok ? r.json() : null))
|
|
78
78
|
.then((j) => {
|
|
79
79
|
const latest = j && j["dist-tags"] && j["dist-tags"].latest
|
|
80
|
-
if (latest) writeCache({ lastCheck: Date.now(), latest })
|
|
80
|
+
if (latest) writeCache({ ...cache, lastCheck: Date.now(), latest })
|
|
81
81
|
})
|
|
82
82
|
.catch(() => {})
|
|
83
83
|
.finally(() => clearTimeout(timer))
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Record the running CLI version and return the version seen on the PREVIOUS
|
|
89
|
+
* run (null on first run). Lets the CLI notice "I was just upgraded" so it can
|
|
90
|
+
* refresh installed skills. Merges into the shared cache so it doesn't clobber
|
|
91
|
+
* the update-check state.
|
|
92
|
+
*/
|
|
93
|
+
export function previousRunVersion(current) {
|
|
94
|
+
const cache = readCache()
|
|
95
|
+
const prev = cache.cliVersion || null
|
|
96
|
+
if (prev !== current) writeCache({ ...cache, cliVersion: current })
|
|
97
|
+
return prev
|
|
98
|
+
}
|