@test-lab-ai/cli 0.2.12 → 0.2.13
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 +54 -25
- package/lib/skills.mjs +17 -0
- package/package.json +1 -1
package/bin/testlab.mjs
CHANGED
|
@@ -23,7 +23,7 @@ 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, installedSkillLocations } from "../lib/skills.mjs"
|
|
26
|
+
import { TESTLAB_SKILLS, AGENTS, detectAgents, installSkill, installedSkillLocations, installedSkillTargets } from "../lib/skills.mjs"
|
|
27
27
|
import { checkForUpdate, currentVersion, previousRunVersion } from "../lib/update-check.mjs"
|
|
28
28
|
|
|
29
29
|
const log = (...a) => console.log(...a)
|
|
@@ -462,44 +462,73 @@ function cmdSkillsList() {
|
|
|
462
462
|
log(`\nInstall with: testlab skills install [name] [--agent ${AGENTS.join("|")}|all]`)
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
-
|
|
466
|
-
|
|
465
|
+
// Install EVERY current skill at every location where any test-lab skill is
|
|
466
|
+
// already present — refreshing existing copies AND adding skills shipped since
|
|
467
|
+
// the user last installed, so an update delivers the latest skill SET (not just
|
|
468
|
+
// fresher copies of the old set). `installedSkillTargets()` is the union of
|
|
469
|
+
// (agent, scope) homes; a brand-new skill has no install locations of its own
|
|
470
|
+
// yet, which is exactly why the old per-skill loop never picked it up. Returns
|
|
471
|
+
// one result per (skill, location), with `isNew` flagging a first-time install.
|
|
472
|
+
async function refreshInstalledSkills() {
|
|
473
|
+
const had = new Set()
|
|
467
474
|
for (const name of TESTLAB_SKILLS) {
|
|
468
|
-
for (const loc of installedSkillLocations(name)) {
|
|
475
|
+
for (const loc of installedSkillLocations(name)) had.add(`${name}:${loc.agent}:${loc.global}`)
|
|
476
|
+
}
|
|
477
|
+
const results = []
|
|
478
|
+
for (const { agent, global } of installedSkillTargets()) {
|
|
479
|
+
for (const name of TESTLAB_SKILLS) {
|
|
480
|
+
const isNew = !had.has(`${name}:${agent}:${global}`)
|
|
469
481
|
try {
|
|
470
|
-
const res = await installSkill(name,
|
|
471
|
-
|
|
472
|
-
log(`✓ ${name} → ${loc.agent}${loc.global ? " (global)" : ""}: ${res.dest}`)
|
|
482
|
+
const res = await installSkill(name, agent, { global })
|
|
483
|
+
results.push({ name, agent, global, isNew, ok: true, dest: res.dest })
|
|
473
484
|
} catch (e) {
|
|
474
|
-
|
|
485
|
+
results.push({ name, agent, global, isNew, ok: false, error: e.message })
|
|
475
486
|
}
|
|
476
487
|
}
|
|
477
488
|
}
|
|
478
|
-
|
|
479
|
-
|
|
489
|
+
return results
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function cmdSkillsUpdate() {
|
|
493
|
+
const results = await refreshInstalledSkills()
|
|
494
|
+
if (results.length === 0) {
|
|
495
|
+
log("No installed skills found here. Run `testlab skills install` first.")
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
for (const r of results) {
|
|
499
|
+
const where = `${r.agent}${r.global ? " (global)" : ""}`
|
|
500
|
+
if (!r.ok) log(` ✗ ${r.name} (${where}): ${r.error}`)
|
|
501
|
+
else log(`${r.isNew ? "+ installed" : "✓ refreshed"} ${r.name} → ${where}: ${r.dest}`)
|
|
502
|
+
}
|
|
503
|
+
const ok = results.filter((r) => r.ok)
|
|
504
|
+
const added = ok.filter((r) => r.isNew).length
|
|
505
|
+
const bits = []
|
|
506
|
+
if (ok.length - added) bits.push(`${ok.length - added} refreshed`)
|
|
507
|
+
if (added) bits.push(`${added} newly installed`)
|
|
508
|
+
log(`\n${bits.join(", ") || "Nothing to update"}. Restart your agent to load the changes.`)
|
|
480
509
|
}
|
|
481
510
|
|
|
482
|
-
// After a CLI upgrade, refresh
|
|
483
|
-
// per version bump (best effort). Skipped in CI / when
|
|
511
|
+
// After a CLI upgrade, refresh installed skills in place AND add any newly
|
|
512
|
+
// shipped ones — runs once per version bump (best effort). Skipped in CI / when
|
|
513
|
+
// NO_UPDATE_NOTIFIER is set.
|
|
484
514
|
async function maybeRefreshSkillsOnUpgrade() {
|
|
485
515
|
if (process.env.CI || process.env.NO_UPDATE_NOTIFIER) return
|
|
486
516
|
const ver = currentVersion()
|
|
487
517
|
const prev = previousRunVersion(ver)
|
|
488
518
|
if (!prev || prev === ver) return
|
|
489
|
-
let
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
refreshed++
|
|
495
|
-
} catch {
|
|
496
|
-
/* best effort — never break the actual command */
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
if (refreshed > 0) {
|
|
501
|
-
process.stderr.write(`↻ CLI upgraded ${prev} → ${ver}; refreshed ${refreshed} installed skill location(s).\n`)
|
|
519
|
+
let ok
|
|
520
|
+
try {
|
|
521
|
+
ok = (await refreshInstalledSkills()).filter((r) => r.ok)
|
|
522
|
+
} catch {
|
|
523
|
+
return // best effort — never break the actual command
|
|
502
524
|
}
|
|
525
|
+
if (ok.length === 0) return
|
|
526
|
+
const added = ok.filter((r) => r.isNew).length
|
|
527
|
+
const refreshed = ok.length - added
|
|
528
|
+
const bits = []
|
|
529
|
+
if (refreshed) bits.push(`refreshed ${refreshed}`)
|
|
530
|
+
if (added) bits.push(`installed ${added} new`)
|
|
531
|
+
process.stderr.write(`↻ CLI upgraded ${prev} → ${ver}; ${bits.join(", ")} skill location(s).\n`)
|
|
503
532
|
}
|
|
504
533
|
|
|
505
534
|
async function main() {
|
package/lib/skills.mjs
CHANGED
|
@@ -70,6 +70,23 @@ export function installedSkillLocations(name) {
|
|
|
70
70
|
].filter((c) => fs.existsSync(c.path))
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The distinct (agent, scope) locations where AT LEAST ONE test-lab skill is
|
|
75
|
+
* currently installed. `skills update` and the on-upgrade refresh install EVERY
|
|
76
|
+
* current skill at each of these, so a skill added in a later CLI release lands
|
|
77
|
+
* wherever the user already keeps test-lab skills — not just fresher copies of
|
|
78
|
+
* the skills that happened to be installed before.
|
|
79
|
+
*/
|
|
80
|
+
export function installedSkillTargets() {
|
|
81
|
+
const seen = new Map()
|
|
82
|
+
for (const name of TESTLAB_SKILLS) {
|
|
83
|
+
for (const loc of installedSkillLocations(name)) {
|
|
84
|
+
seen.set(`${loc.agent}:${loc.global}`, { agent: loc.agent, global: loc.global })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return [...seen.values()]
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
/** Resolve the install target (base dir + format) for one agent. */
|
|
74
91
|
export function agentTarget(agent, { global } = {}) {
|
|
75
92
|
const home = os.homedir()
|