skill-land 1.3.0 → 1.4.0
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 +25 -0
- package/agents.json +37 -4
- package/package.json +1 -1
- package/skill-land.mjs +75 -9
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ npx skill-land <source> --for <agent> [options]
|
|
|
61
61
|
| `--project` | install into `./` instead of the user-global directory |
|
|
62
62
|
| `--dry-run` | resolve and report, write nothing |
|
|
63
63
|
| `--verify` | check an existing install, write nothing |
|
|
64
|
+
| `--discover` | also ask a fresh agent session to list its skills (claude-code, codex, antigravity) |
|
|
64
65
|
| `--skip-security` | skip the SkillSpector scan (acknowledged, not silent) |
|
|
65
66
|
| `--strict` | refuse to install anything SkillSpector flags |
|
|
66
67
|
| `--list` | show known agents and their directories |
|
|
@@ -133,6 +134,30 @@ Where an agent has several directories it reads, `--verify` counts the skill as
|
|
|
133
134
|
it is **complete in any one of them**. Requiring every fallback to hold a copy reports
|
|
134
135
|
working installs as broken.
|
|
135
136
|
|
|
137
|
+
### Does the agent actually load it? `--discover`
|
|
138
|
+
|
|
139
|
+
Everything above proves the bytes are on disk. It does not prove the agent loads them: it may
|
|
140
|
+
need a restart, reject the frontmatter, or read the directory only in some workspaces.
|
|
141
|
+
`--discover` starts a fresh non-interactive session from a neutral directory and checks that the
|
|
142
|
+
skill name appears in what the agent says it can see.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
skill-land Magithar/SKILLmama --for codex,antigravity --discover
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Agent | Listing used | Kind |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| Antigravity | `agy -p /skills` | a real skill list |
|
|
151
|
+
| Codex | `codex exec "list your skills"` | asks the model |
|
|
152
|
+
| Claude Code | `claude -p "list your skills"` | asks the model |
|
|
153
|
+
|
|
154
|
+
Each was tested by planting a uniquely named canary skill and confirming it appeared. Every other
|
|
155
|
+
agent reports `NOT CHECKED`, never a pass: an unknown is not a success. A missing binary or a
|
|
156
|
+
timeout is also `NOT CHECKED`; only a completed listing that lacks the skill fails the run.
|
|
157
|
+
|
|
158
|
+
The Codex and Claude Code listings go through the model, so they cost an API call and a `FAIL`
|
|
159
|
+
is a lead to investigate, not proof. It is opt-in for that reason, and skipped with `--project`.
|
|
160
|
+
|
|
136
161
|
### What it cannot tell you
|
|
137
162
|
|
|
138
163
|
Verification assumes the installer **copied**. Some installers **rewrite** on install — the
|
package/agents.json
CHANGED
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
"project": ".claude/skills",
|
|
8
8
|
"file": "SKILL.md",
|
|
9
9
|
"restart": false,
|
|
10
|
-
"broken": false
|
|
10
|
+
"broken": false,
|
|
11
|
+
"discover": {
|
|
12
|
+
"cmd": [
|
|
13
|
+
"claude",
|
|
14
|
+
"-p",
|
|
15
|
+
"List the names of all skills available to you, one per line. Output only names."
|
|
16
|
+
],
|
|
17
|
+
"deterministic": false
|
|
18
|
+
}
|
|
11
19
|
},
|
|
12
20
|
"codex": {
|
|
13
21
|
"display": "OpenAI Codex",
|
|
@@ -15,7 +23,16 @@
|
|
|
15
23
|
"project": ".agents/skills",
|
|
16
24
|
"file": "SKILL.md",
|
|
17
25
|
"restart": false,
|
|
18
|
-
"broken": true
|
|
26
|
+
"broken": true,
|
|
27
|
+
"discover": {
|
|
28
|
+
"cmd": [
|
|
29
|
+
"codex",
|
|
30
|
+
"exec",
|
|
31
|
+
"--skip-git-repo-check",
|
|
32
|
+
"List the names of all skills available to you, one per line. Output only names."
|
|
33
|
+
],
|
|
34
|
+
"deterministic": false
|
|
35
|
+
}
|
|
19
36
|
},
|
|
20
37
|
"antigravity": {
|
|
21
38
|
"_paths": "LIVE VERIFIED against agy 1.1.27 on 2026-09-05: a uniquely-named canary skill was planted in each candidate directory and `agy -p /skills` run from a neutral workspace. Read as global: ~/.gemini/config/skills, ~/.gemini/skills, ~/.gemini/antigravity-cli/skills — matching the maintainer statement in google-antigravity/antigravity-cli#103 comment 5547952239. NOT read: ~/.agents/skills (appears only when its parent is the active workspace, i.e. `--add-dir $HOME`, which is project-local behavior, not global) and ~/.gemini/antigravity/skills (app.asar defines ~/.gemini/antigravity as IDE_OLD_DATA_DIR, the legacy IDE data dir migrating to ~/.gemini/antigravity-ide; never a skills root). Project dir .agents/skills is read only when the directory is a registered workspace — cwd alone was not enough; .antigravity/skills and .agy/skills are not read at all. language_server hardcodes no .gemini/*/skills literal: skills_paths is a repeated protobuf field the client fills in.",
|
|
@@ -28,7 +45,15 @@
|
|
|
28
45
|
"project": ".agents/skills",
|
|
29
46
|
"file": "SKILL.md",
|
|
30
47
|
"restart": true,
|
|
31
|
-
"broken": true
|
|
48
|
+
"broken": true,
|
|
49
|
+
"discover": {
|
|
50
|
+
"cmd": [
|
|
51
|
+
"agy",
|
|
52
|
+
"-p",
|
|
53
|
+
"/skills"
|
|
54
|
+
],
|
|
55
|
+
"deterministic": true
|
|
56
|
+
}
|
|
32
57
|
},
|
|
33
58
|
"antigravity-cli": {
|
|
34
59
|
"display": "Antigravity CLI",
|
|
@@ -36,7 +61,15 @@
|
|
|
36
61
|
"project": ".agents/skills",
|
|
37
62
|
"file": "SKILL.md",
|
|
38
63
|
"restart": true,
|
|
39
|
-
"broken": true
|
|
64
|
+
"broken": true,
|
|
65
|
+
"discover": {
|
|
66
|
+
"cmd": [
|
|
67
|
+
"agy",
|
|
68
|
+
"-p",
|
|
69
|
+
"/skills"
|
|
70
|
+
],
|
|
71
|
+
"deterministic": true
|
|
72
|
+
}
|
|
40
73
|
},
|
|
41
74
|
"gemini-cli": {
|
|
42
75
|
"display": "Gemini CLI",
|
package/package.json
CHANGED
package/skill-land.mjs
CHANGED
|
@@ -407,6 +407,7 @@ function runSkill(skillPath, root, quiet) {
|
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
const bad = [];
|
|
410
|
+
const baseSummaries = [];
|
|
410
411
|
let targetCount = 0;
|
|
411
412
|
|
|
412
413
|
for (const agent of targets) {
|
|
@@ -453,7 +454,11 @@ function runSkill(skillPath, root, quiet) {
|
|
|
453
454
|
if (!checks.every((c) => c.pass)) badFiles.push({ target, rel: f.rel, checks });
|
|
454
455
|
}
|
|
455
456
|
|
|
456
|
-
|
|
457
|
+
const absent = badFiles.length === files.length
|
|
458
|
+
&& badFiles.every((b) => b.checks.some((c) => c.name === "file exists" && !c.pass));
|
|
459
|
+
baseResults.push({ destDir, badFiles, passed: badFiles.length === 0, absent });
|
|
460
|
+
baseSummaries.push({ destDir, passed: badFiles.length === 0, absent,
|
|
461
|
+
n: files.length - badFiles.length });
|
|
457
462
|
}
|
|
458
463
|
|
|
459
464
|
if (!dryRun) {
|
|
@@ -475,9 +480,7 @@ function runSkill(skillPath, root, quiet) {
|
|
|
475
480
|
// Nothing present at all is one fact, not N failures. Listing every
|
|
476
481
|
// absent file buries the directories that are partially installed,
|
|
477
482
|
// which are the ones worth looking at.
|
|
478
|
-
|
|
479
|
-
&& r.badFiles.every((b) => b.checks.some((c) => c.name === "file exists" && !c.pass));
|
|
480
|
-
if (absent) {
|
|
483
|
+
if (r.absent) {
|
|
481
484
|
console.log(` FAIL ${r.destDir} not installed${verifyOnly ? " (verify only)" : ""}`);
|
|
482
485
|
continue;
|
|
483
486
|
}
|
|
@@ -505,13 +508,60 @@ function runSkill(skillPath, root, quiet) {
|
|
|
505
508
|
const nf = `${files.length} file${files.length === 1 ? "" : "s"}`;
|
|
506
509
|
if (dryRun) console.log(`-- ${targetCount} target${targetCount === 1 ? "" : "s"}, ${nf} (dry run)`);
|
|
507
510
|
else console.log(`${skillFailed ? "FAIL" : "OK "} ${nf}`);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
+
// Per directory, not per file. Flattening 3 paths x 148 files into one list
|
|
512
|
+
// buries the only thing that is actionable: which directory is wrong, and how.
|
|
513
|
+
for (const b of baseSummaries.filter((x) => !x.passed)) {
|
|
514
|
+
console.log(` x ${b.destDir} ${b.absent ? "not installed" : `${b.n}/${files.length} files`}`);
|
|
511
515
|
}
|
|
512
|
-
if (bad.length > 10) console.log(` ... and ${bad.length - 10} more file(s)`);
|
|
513
516
|
}
|
|
514
|
-
return { name: skillName, failed: skillFailed, skipped: skillSkipped };
|
|
517
|
+
return { name: skillName, fmName: fm.name, failed: skillFailed, skipped: skillSkipped };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ---------------------------------------------------------------- discover
|
|
521
|
+
|
|
522
|
+
// --verify proves the bytes are on disk. It cannot prove the agent loads them:
|
|
523
|
+
// the agent may need a restart, reject the frontmatter, or read the directory
|
|
524
|
+
// only in some workspaces. --discover asks a fresh, non-interactive session what
|
|
525
|
+
// skills it sees. Run from a neutral cwd so a project-local skills directory
|
|
526
|
+
// cannot make a missing global install look found.
|
|
527
|
+
//
|
|
528
|
+
// Only agents with a `discover` entry in agents.json were checked this way, and
|
|
529
|
+
// each was tested with a planted canary. The rest report "not checked" rather
|
|
530
|
+
// than pass: an unknown is not a success.
|
|
531
|
+
const hasName = (out, name) =>
|
|
532
|
+
new RegExp(`(^|[^\\w-])${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}($|[^\\w-])`, "m").test(out);
|
|
533
|
+
|
|
534
|
+
function discover(agent, names) {
|
|
535
|
+
const a = AGENTS[agent];
|
|
536
|
+
console.log(` ${a.display}`);
|
|
537
|
+
if (!a.discover) {
|
|
538
|
+
console.log(` NOT CHECKED no known non-interactive skill listing for ${a.display}\n`);
|
|
539
|
+
return 0;
|
|
540
|
+
}
|
|
541
|
+
const [bin, ...args] = a.discover.cmd;
|
|
542
|
+
const cwd = mkdtempSync(join(tmpdir(), "skill-land-discover-"));
|
|
543
|
+
let out;
|
|
544
|
+
try {
|
|
545
|
+
out = execFileSync(bin, args, { cwd, encoding: "utf-8", timeout: 180000,
|
|
546
|
+
stdio: ["ignore", "pipe", "pipe"] });
|
|
547
|
+
} catch (e) {
|
|
548
|
+
const why = e.code === "ENOENT" ? `${bin} not on PATH` : (e.message.split("\n")[0]);
|
|
549
|
+
console.log(` NOT CHECKED ${why}\n`);
|
|
550
|
+
return 0;
|
|
551
|
+
} finally {
|
|
552
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
553
|
+
}
|
|
554
|
+
let missing = 0;
|
|
555
|
+
for (const n of names) {
|
|
556
|
+
const found = hasName(out, n);
|
|
557
|
+
if (!found) missing++;
|
|
558
|
+
console.log(` ${found ? "OK " : "FAIL"} ${n} ${found ? "listed" : "not in the skill list of a fresh session"}`);
|
|
559
|
+
}
|
|
560
|
+
if (!a.discover.deterministic)
|
|
561
|
+
console.log(` note ${a.display} has no plain skill listing; this asks the model, so treat a FAIL as a lead, not proof`);
|
|
562
|
+
if (missing && a.restart) console.log(` note restart ${a.display} fully; skills load at startup`);
|
|
563
|
+
console.log();
|
|
564
|
+
return missing;
|
|
515
565
|
}
|
|
516
566
|
|
|
517
567
|
// ---------------------------------------------------------------- main
|
|
@@ -531,6 +581,8 @@ if (!argv.length || argv.includes("-h") || argv.includes("--help")) {
|
|
|
531
581
|
--project install into ./ instead of the user-global dir
|
|
532
582
|
--dry-run resolve and report, write nothing
|
|
533
583
|
--verify check an existing install, write nothing
|
|
584
|
+
--discover also ask a fresh agent session to list its skills
|
|
585
|
+
(claude-code, codex, antigravity; others: not checked)
|
|
534
586
|
--skip-security skip the SkillSpector scan (acknowledged, not silent)
|
|
535
587
|
--strict refuse to install anything SkillSpector flags
|
|
536
588
|
--list show known agents and their directories
|
|
@@ -567,6 +619,7 @@ for (const t of targets) if (!AGENTS[t]) die(`unknown agent "${t}"\n kno
|
|
|
567
619
|
const isProject = flag("--project");
|
|
568
620
|
const dryRun = flag("--dry-run");
|
|
569
621
|
const verifyOnly = flag("--verify");
|
|
622
|
+
const discoverFlag = flag("--discover");
|
|
570
623
|
const skipSecurity = flag("--skip-security");
|
|
571
624
|
const strict = flag("--strict");
|
|
572
625
|
const allSkills = flag("--all");
|
|
@@ -581,6 +634,7 @@ const src = resolveSource(source);
|
|
|
581
634
|
const tmp = mkdtempSync(join(tmpdir(), "skill-install-"));
|
|
582
635
|
let failed = 0;
|
|
583
636
|
const skipped = [];
|
|
637
|
+
const landed = [];
|
|
584
638
|
|
|
585
639
|
try {
|
|
586
640
|
const root = fetchSource(src, tmp);
|
|
@@ -595,6 +649,7 @@ try {
|
|
|
595
649
|
const r = runSkill(skillPath, root, quiet);
|
|
596
650
|
failed += r.failed;
|
|
597
651
|
if (r.skipped) skipped.push(r.name);
|
|
652
|
+
else if (!r.failed) landed.push(r.fmName || r.name);
|
|
598
653
|
}
|
|
599
654
|
|
|
600
655
|
if (allSkills) {
|
|
@@ -607,6 +662,17 @@ try {
|
|
|
607
662
|
rmSync(tmp, { recursive: true, force: true });
|
|
608
663
|
}
|
|
609
664
|
|
|
665
|
+
// Once per agent, not per skill: a listing is one session however many skills.
|
|
666
|
+
// Only skills that passed file verification are looked up; a skill that never
|
|
667
|
+
// landed would just produce a second, redundant failure.
|
|
668
|
+
if (discoverFlag && !dryRun) {
|
|
669
|
+
if (isProject) console.log(" --discover is skipped with --project: the listing runs from a neutral directory\n");
|
|
670
|
+
else if (landed.length) {
|
|
671
|
+
console.log(" discovery fresh session, neutral directory\n");
|
|
672
|
+
for (const t of targets) failed += discover(t, landed);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
610
676
|
if (failed) {
|
|
611
677
|
console.error(` ${failed} install(s) failed verification\n`);
|
|
612
678
|
process.exit(1);
|