skillfed 0.1.0__py3-none-any.whl
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.
- skillfed/__init__.py +3 -0
- skillfed/cli.py +147 -0
- skillfed/payload/SKILL.md +185 -0
- skillfed/payload/plan_nudge.json +1 -0
- skillfed/payload/skillfed.md +34 -0
- skillfed-0.1.0.dist-info/METADATA +37 -0
- skillfed-0.1.0.dist-info/RECORD +9 -0
- skillfed-0.1.0.dist-info/WHEEL +4 -0
- skillfed-0.1.0.dist-info/entry_points.txt +2 -0
skillfed/__init__.py
ADDED
skillfed/cli.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Skill Federation installer — Python tier (`uvx skillfed` / `pipx run skillfed`).
|
|
2
|
+
|
|
3
|
+
Same curl-tier install as install.sh / install.ps1 / `npx skillfed`, packaged for PyPI so the
|
|
4
|
+
smallest-dependency audience (CI, Python shops) can install with no clone and no Node:
|
|
5
|
+
|
|
6
|
+
uvx skillfed # curl tier, user scope (~/.claude)
|
|
7
|
+
uvx skillfed --with-hook # + plan-approval nudge (safe settings.json merge)
|
|
8
|
+
uvx skillfed --with-npx # + register the npx -y skillfed-mcp MCP server
|
|
9
|
+
uvx skillfed --scope project # install into ./.claude instead of ~/.claude
|
|
10
|
+
|
|
11
|
+
The 3 payload files are vendored into this package (src/skillfed/payload/) by
|
|
12
|
+
scripts/vendor-payload.mjs and shipped inside the wheel. When run from a source checkout before
|
|
13
|
+
vendoring, we fall back to the canonical copy under integrations/claude-code/. Stdlib only.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import shutil
|
|
22
|
+
import sys
|
|
23
|
+
from importlib.resources import files
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
ENDPOINT_DEFAULT = "https://qurini-skill-federation.hf.space"
|
|
27
|
+
|
|
28
|
+
# vendored filename -> (dest path under .claude, repo-relative clone-fallback path)
|
|
29
|
+
PAYLOAD = [
|
|
30
|
+
("SKILL.md", ("skills", "skill-federation", "SKILL.md"),
|
|
31
|
+
("skills", "skill-federation", "SKILL.md")),
|
|
32
|
+
("plan_nudge.json", ("skills", "skill-federation", "plan_nudge.json"),
|
|
33
|
+
("hooks", "plan_nudge.json")),
|
|
34
|
+
("skillfed.md", ("commands", "skillfed.md"),
|
|
35
|
+
("commands", "skillfed.md")),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _read_payload(name: str, repo_rel: tuple[str, ...]) -> bytes:
|
|
40
|
+
"""Bytes of a payload file: the bundled copy if present, else the clone fallback."""
|
|
41
|
+
try:
|
|
42
|
+
# chained single-arg joinpath: multi-arg joinpath is 3.11+, this stays 3.9-safe
|
|
43
|
+
res = files("skillfed").joinpath("payload").joinpath(name)
|
|
44
|
+
if res.is_file():
|
|
45
|
+
return res.read_bytes()
|
|
46
|
+
except (FileNotFoundError, ModuleNotFoundError, OSError):
|
|
47
|
+
pass
|
|
48
|
+
# Clone fallback: <repo>/integrations/claude-code/<repo_rel>
|
|
49
|
+
# cli.py -> skillfed -> src -> python-installer -> <repo root>
|
|
50
|
+
repo = Path(__file__).resolve().parents[3]
|
|
51
|
+
cand = repo.joinpath("integrations", "claude-code", *repo_rel)
|
|
52
|
+
if cand.is_file():
|
|
53
|
+
return cand.read_bytes()
|
|
54
|
+
sys.exit(
|
|
55
|
+
f"error: payload '{name}' not found (bundled or in a clone). "
|
|
56
|
+
"Run scripts/vendor-payload.mjs, or install from the published package."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _backup(path: Path) -> None:
|
|
61
|
+
if path.exists():
|
|
62
|
+
shutil.copyfile(path, path.with_name(path.name + ".bak"))
|
|
63
|
+
print(f" backed up -> {path}.bak")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _read_json(path: Path) -> dict:
|
|
67
|
+
if path.exists() and path.stat().st_size > 0:
|
|
68
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
69
|
+
return {}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _write_json(obj: dict, path: Path) -> None:
|
|
73
|
+
path.write_text(json.dumps(obj, indent=2) + "\n", encoding="utf-8")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main(argv: list[str] | None = None) -> int:
|
|
77
|
+
ap = argparse.ArgumentParser(prog="skillfed", description="Install the Skill Federation finder.")
|
|
78
|
+
ap.add_argument("--scope", choices=("user", "project"), default="user")
|
|
79
|
+
ap.add_argument("--target")
|
|
80
|
+
ap.add_argument("--with-hook", action="store_true")
|
|
81
|
+
ap.add_argument("--with-npx", action="store_true")
|
|
82
|
+
ap.add_argument("--endpoint", default=ENDPOINT_DEFAULT)
|
|
83
|
+
args = ap.parse_args(argv)
|
|
84
|
+
|
|
85
|
+
if args.target:
|
|
86
|
+
target = Path(args.target).resolve()
|
|
87
|
+
elif args.scope == "user":
|
|
88
|
+
target = Path.home() / ".claude"
|
|
89
|
+
else:
|
|
90
|
+
target = Path.cwd() / ".claude"
|
|
91
|
+
|
|
92
|
+
print("Skill Federation installer (uvx skillfed)")
|
|
93
|
+
print(f" target : {target} (scope={args.scope})")
|
|
94
|
+
print()
|
|
95
|
+
|
|
96
|
+
# ALWAYS: curl tier (skill + command) — plain file writes, works immediately.
|
|
97
|
+
skill_dir = target / "skills" / "skill-federation"
|
|
98
|
+
cmd_dir = target / "commands"
|
|
99
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
cmd_dir.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
for name, dest_parts, repo_rel in PAYLOAD:
|
|
102
|
+
(target.joinpath(*dest_parts)).write_bytes(_read_payload(name, repo_rel))
|
|
103
|
+
print("[curl] installed finder skill + /skillfed command (zero runtime)")
|
|
104
|
+
|
|
105
|
+
# --with-hook: register the plan-approval nudge (safe merge + backup, idempotent).
|
|
106
|
+
if args.with_hook:
|
|
107
|
+
nudge_abs = str(skill_dir / "plan_nudge.json").replace("\\", "/")
|
|
108
|
+
cmd = f'curl -s "file://{nudge_abs}"'
|
|
109
|
+
settings = target / "settings.json"
|
|
110
|
+
s = _read_json(settings)
|
|
111
|
+
ptu = s.setdefault("hooks", {}).setdefault("PostToolUse", [])
|
|
112
|
+
already = any(
|
|
113
|
+
"plan_nudge.json" in str(h.get("command", ""))
|
|
114
|
+
for e in ptu for h in e.get("hooks", [])
|
|
115
|
+
)
|
|
116
|
+
if already:
|
|
117
|
+
print("[hook] already registered; skipped")
|
|
118
|
+
else:
|
|
119
|
+
_backup(settings)
|
|
120
|
+
ptu.append({
|
|
121
|
+
"matcher": "ExitPlanMode",
|
|
122
|
+
"hooks": [{"type": "command", "command": cmd, "timeout": 20}],
|
|
123
|
+
})
|
|
124
|
+
_write_json(s, settings)
|
|
125
|
+
print("[hook] registered plan-approval nudge in settings.json")
|
|
126
|
+
|
|
127
|
+
# --with-npx: register the published Node MCP server (project-scoped .mcp.json).
|
|
128
|
+
if args.with_npx:
|
|
129
|
+
mcp = Path.cwd() / ".mcp.json"
|
|
130
|
+
m = _read_json(mcp)
|
|
131
|
+
_backup(mcp)
|
|
132
|
+
m.setdefault("mcpServers", {})["skillfed-mcp"] = {
|
|
133
|
+
"command": "npx",
|
|
134
|
+
"args": ["-y", "skillfed-mcp"],
|
|
135
|
+
"env": {"SKILLFED_ENDPOINT": args.endpoint},
|
|
136
|
+
}
|
|
137
|
+
_write_json(m, mcp)
|
|
138
|
+
print(f"[npx] registered Node MCP server -> {mcp} (npx -y skillfed-mcp)")
|
|
139
|
+
|
|
140
|
+
print()
|
|
141
|
+
print("Done. Restart Claude Code, then run: /skillfed <what you're trying to do>")
|
|
142
|
+
print(f"Endpoint: {args.endpoint} (override with $SKILLFED_ENDPOINT)")
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-federation
|
|
3
|
+
description: Find vetted agent skills for the current task from a federated catalog, the privacy-preserving way. Use right after a plan is approved, when you hit a capability gap mid-task, or when the user asks to find/discover a skill ("/skillfed …", "is there a skill that…"). You generate an abstract wish-list (never the plan) and the federation matches it.
|
|
4
|
+
allowed-tools: Bash, Read, Write, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill Federation — wish-list finder
|
|
8
|
+
|
|
9
|
+
Discover vetted agent skills **without ever sending the user's plan, brief, or work
|
|
10
|
+
across the boundary**. You reason about the *ideal* skills for the task, emit an
|
|
11
|
+
abstract wish-list, and the federation matches those wishes against its catalog.
|
|
12
|
+
|
|
13
|
+
> **Privacy floor (constitution Principle IV) — non-negotiable.** What leaves the machine is only
|
|
14
|
+
> the abstract wish — its one-line `description`, its ~4 paraphrased `formulations`, 1–5
|
|
15
|
+
> `keywords`, and its structured capability `sketch` (per [demand-sketch.md](demand-sketch.md)).
|
|
16
|
+
> The sketch's flattened terms ride **inside the search query on every search** (and on a miss the
|
|
17
|
+
> same sketch is the demand pointer). (The wish `name` is display-only and stays local; the search
|
|
18
|
+
> payload is the concatenated description + formulations + flattened sketch, plus keywords.) Every
|
|
19
|
+
> field stays at the "what skill should exist" abstraction. The plan, brief, outputs, file
|
|
20
|
+
> contents, and your reasoning trace MUST NOT appear in any description, formulation, keyword,
|
|
21
|
+
> sketch, or search payload. If you can't phrase a need without quoting the user's content,
|
|
22
|
+
> abstract it until you can.
|
|
23
|
+
|
|
24
|
+
## When to use
|
|
25
|
+
|
|
26
|
+
- A plan was just approved (the plan-approval hook nudges you here).
|
|
27
|
+
- You're about to build something a packaged skill likely already does (PDF
|
|
28
|
+
extraction, market sizing, PR review, SQL reporting, resume tailoring, …).
|
|
29
|
+
- The user says "find a skill for…", "is there a skill that…", or runs `/skillfed`.
|
|
30
|
+
|
|
31
|
+
> **Don't skip silently.** If you conclude a plan needs no skill search (e.g. bespoke work on your
|
|
32
|
+
> own codebase), say so **explicitly** — name the capabilities you considered and why none needs a
|
|
33
|
+
> federated skill — and **confirm with the user** before proceeding without a search.
|
|
34
|
+
|
|
35
|
+
## Backend — MCP tools if present, else `curl`
|
|
36
|
+
|
|
37
|
+
**If the `skillfed-mcp` MCP tools are available this session** (`find_skills`,
|
|
38
|
+
`get_skill_bundle`, `report_selection`, `emit_demand_pointer` — the optional Node/npx tier),
|
|
39
|
+
**use them**: they hit the same federation with typed, validated I/O and no shell-out. The
|
|
40
|
+
wish-list, selection, trust, and reporting logic below are identical — just call the tool
|
|
41
|
+
instead of the matching `curl` POST (`find_skills` ≙ `/search`, `get_skill_bundle` ≙ `/fetch`,
|
|
42
|
+
`report_selection` ≙ `/report_selection`, `emit_demand_pointer` ≙ `/report_demand`).
|
|
43
|
+
|
|
44
|
+
**Otherwise (the default), use `curl`** — it ships with Windows 10+ (`curl.exe`) and macOS
|
|
45
|
+
(`/usr/bin/curl`), so the finder needs **no Python, no Node, no install**. You run `curl`
|
|
46
|
+
through your shell (Bash) tool.
|
|
47
|
+
|
|
48
|
+
- **Endpoint**: use `$SKILLFED_ENDPOINT` if it's set, else default
|
|
49
|
+
`https://qurini-skill-federation.hf.space` (the keyless demo). Point it at our own
|
|
50
|
+
federation core later — the request/response shapes are unchanged.
|
|
51
|
+
- **Defaults**: `top_n` = 3 candidates per wish; ~4 paraphrases per wish.
|
|
52
|
+
- **Windows note**: in PowerShell, `curl` is an alias for `Invoke-WebRequest` — call
|
|
53
|
+
**`curl.exe`** explicitly. On macOS/Linux plain `curl` is fine.
|
|
54
|
+
- **Quoting-safe pattern**: write each JSON request body to a temp file and send it with
|
|
55
|
+
`--data-binary "@<file>"`, so no shell has to escape braces or quotes.
|
|
56
|
+
|
|
57
|
+
The federation operations below are one `curl` POST each — `/search`, `/fetch`,
|
|
58
|
+
`/report_selection`, `/report_demand`. (The endpoint also exposes `/report_outcome` for
|
|
59
|
+
post-use signals; that's out of scope for the finder.)
|
|
60
|
+
|
|
61
|
+
## The flow (two hops; always user-approved before install)
|
|
62
|
+
|
|
63
|
+
### Hop 1 — search
|
|
64
|
+
|
|
65
|
+
1. **Form an expected-response sketch, then a wish-list.** For the task, imagine the
|
|
66
|
+
*ideal* skill(s): what each would do, its inputs/outputs, the key operations, and the
|
|
67
|
+
discriminative vocabulary its SKILL.md would contain. Emit that sketch as a real
|
|
68
|
+
`sketch` field on each wish (it powers the search query *and* becomes the demand
|
|
69
|
+
pointer on a miss — author it once, per [demand-sketch.md](demand-sketch.md)). Then
|
|
70
|
+
write **up to 10 wishes** — fewer is fine — each:
|
|
71
|
+
- `name`: short hypothetical skill name (display-only, stays local),
|
|
72
|
+
- `description`: **one line** for display only (the wish→match table) — abstract, no
|
|
73
|
+
plan specifics,
|
|
74
|
+
- `keywords`: **1–5 required** evidence terms the description omits but the target
|
|
75
|
+
skill's docs would contain (the discriminative subset of `sketch.domain_vocab`),
|
|
76
|
+
- `formulations`: **~4 paraphrases** of the description with *deliberately varied
|
|
77
|
+
vocabulary* (synonyms, alternate framings). The load-bearing recall field — a single
|
|
78
|
+
phrasing misses ~20% of the time; 4 concatenated paraphrases erase that (BM25 is
|
|
79
|
+
bag-of-words, so they form a robust term-union query). Keep each abstract; never
|
|
80
|
+
quote the plan/brief.
|
|
81
|
+
- `sketch`: the structured expected-response sketch — `purpose / inputs / outputs /
|
|
82
|
+
operations / domain_vocab / section_sketch / tags` (demand-sketch.md schema). Its
|
|
83
|
+
flattened term values are appended to the search query, so the single BM25 call sees
|
|
84
|
+
the full discriminative vocabulary a matching SKILL.md would contain (SIRA step iii),
|
|
85
|
+
not just the 1–5 keywords. Keep it terse and capability-level — never task data.
|
|
86
|
+
|
|
87
|
+
2. **Search each wish with `curl` (`/search`).** For each wish, concatenate its
|
|
88
|
+
`description` + `formulations` + the flattened `sketch` term values into ONE
|
|
89
|
+
bag-of-words query string (BM25 is bag-of-words, so the concatenation is a robust
|
|
90
|
+
term-union — matches a K-request ensemble at 1/K the cost; the sketch supplies the rare,
|
|
91
|
+
discriminative vocabulary SIRA rewards). Flatten the sketch to its *values* only
|
|
92
|
+
(`domain_vocab`, `operations`, `inputs`, `outputs`, `purpose`, `section_sketch`, `tags`)
|
|
93
|
+
— never JSON keys or punctuation. Write the request body to a temp file and POST it:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# body.json → { "tenant":"local",
|
|
97
|
+
# "wish":"<description + formulations + flattened sketch, space-joined>",
|
|
98
|
+
# "keywords":["1-5","evidence","terms"], "top_n":3 }
|
|
99
|
+
curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/search" \
|
|
100
|
+
-H "Content-Type: application/json" --data-binary "@body.json"
|
|
101
|
+
```
|
|
102
|
+
Response per wish:
|
|
103
|
+
```json
|
|
104
|
+
{ "query_id":"q_…",
|
|
105
|
+
"candidates":[ { "skill_id":"…","name":"…","description":"…","score":0.27,
|
|
106
|
+
"trust":{"license":"MIT","license_class":"permissive","provenance":"verified","stars":null},
|
|
107
|
+
"source_url":"https://…" } ],
|
|
108
|
+
"confidence":0.59, "recommendation":"…" }
|
|
109
|
+
```
|
|
110
|
+
Keep each wish's `query_id` (needed for selection reporting). Empty `candidates` →
|
|
111
|
+
demand case. Run the wishes in turn (≤10; each is <300 ms) — or issue them in parallel.
|
|
112
|
+
|
|
113
|
+
3. **Drop already-installed skills.** Before showing matches, `Glob`
|
|
114
|
+
`~/.claude/skills/*/SKILL.md` and `./.claude/skills/*/SKILL.md`, read each skill's
|
|
115
|
+
frontmatter `name`, and remove any candidate whose name matches (normalize: lowercase,
|
|
116
|
+
non-alphanumerics → `-`). Don't re-recommend something the user already has.
|
|
117
|
+
|
|
118
|
+
### Selection (your job — precision)
|
|
119
|
+
|
|
120
|
+
4. **Agentic selection, per wish.** Each `/search` returns ≤k recall candidates; *you*
|
|
121
|
+
decide. For each wish: pick the single best candidate, or reject all. A high score
|
|
122
|
+
is not approval — judge fit against the actual need.
|
|
123
|
+
5. **Surface trust BEFORE approval.** Show the user a wish→match table with each
|
|
124
|
+
candidate's `license_class` (permissive / copyleft / proprietary / review),
|
|
125
|
+
`provenance` (verified / unverified), `stars`, `source_url`, and a ⚠ for any
|
|
126
|
+
`security_flags`. Prefer permissive + verified; call out review/unverified ones.
|
|
127
|
+
**Never install without explicit user approval.**
|
|
128
|
+
|
|
129
|
+
### Hop 2 — local resolution (local-first)
|
|
130
|
+
|
|
131
|
+
6. For each approved match, check whether it's already installed at
|
|
132
|
+
`.claude/skills/<id>/` (existence check — that *is* "local search"). If present,
|
|
133
|
+
use the local copy as-is (local-first rule; a drifted local copy is personalization,
|
|
134
|
+
not corruption). If absent, fetch the bundle with `curl` (`/fetch`):
|
|
135
|
+
```bash
|
|
136
|
+
# body.json → { "tenant":"local", "skill_id":"<skill_id>" }
|
|
137
|
+
curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/fetch" \
|
|
138
|
+
-H "Content-Type: application/json" --data-binary "@body.json"
|
|
139
|
+
# → { "skill_id","name","license","source_url", "body":"<full SKILL.md content>" }
|
|
140
|
+
# (an in-house bundle may instead return "files":{ "SKILL.md":…, … } — handle both)
|
|
141
|
+
```
|
|
142
|
+
Write the returned `body` (or each `files` entry) to `.claude/skills/<id>/SKILL.md`
|
|
143
|
+
and record a `.federation.json` manifest (`skill_id`, `installed_at`, `source_url`/
|
|
144
|
+
`license` for OSS). Surface attribution at install.
|
|
145
|
+
7. **Use or revise.** Run the installed skill. If it needs local adaptation for this
|
|
146
|
+
task, stage the change as a LOCAL update on the installed copy (drift) — never push
|
|
147
|
+
local edits back. A general improvement that isn't tenant-specific is a FEDERATED
|
|
148
|
+
suggestion instead. (Full reflection/suggestion chain is a later task; keep it light.)
|
|
149
|
+
|
|
150
|
+
### Report outcomes — two complementary reports
|
|
151
|
+
|
|
152
|
+
8. For every wish that **had candidates**, report the selection with `curl`
|
|
153
|
+
(`/report_selection`, per wish, with its `query_id`). `chosen` must be a **non-empty
|
|
154
|
+
string** — the selected id, or the literal `"None"` if you rejected every candidate:
|
|
155
|
+
```bash
|
|
156
|
+
# body.json → { "tenant":"local", "query_id":"<query_id>",
|
|
157
|
+
# "chosen":"<id-or-'None'>", "rejected":["<id>","…"] }
|
|
158
|
+
curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/report_selection" \
|
|
159
|
+
-H "Content-Type: application/json" --data-binary "@body.json"
|
|
160
|
+
```
|
|
161
|
+
`"chosen":"None"` records that the shown candidates were all wrong — a retrieval-quality
|
|
162
|
+
signal (not a substitute for the demand pointer below).
|
|
163
|
+
9. For every wish that ended with **no skill installed** — search came back **empty**, OR you
|
|
164
|
+
**rejected every candidate** — record a demand pointer with `curl` (`/report_demand`).
|
|
165
|
+
`wish` is REQUIRED (the wish string you searched). Build the `sketch` **string** exactly per
|
|
166
|
+
**[demand-sketch.md](demand-sketch.md)** — a `"<query_id>: <minified-json>"` build spec
|
|
167
|
+
(it is a STRING, not an object; the endpoint's `sketch` field is a string):
|
|
168
|
+
```bash
|
|
169
|
+
# body.json → { "tenant":"local",
|
|
170
|
+
# "wish":"<the description + formulations + flattened sketch you searched>",
|
|
171
|
+
# "sketch":"<query_id>: {\"purpose\":\"…\",\"inputs\":[…],\"outputs\":[…],\"operations\":[…],\"domain_vocab\":[…],\"section_sketch\":\"…\",\"tags\":[…],\"source\":\"unmatched_wish|all_rejected\"}" }
|
|
172
|
+
curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/report_demand" \
|
|
173
|
+
-H "Content-Type: application/json" --data-binary "@body.json"
|
|
174
|
+
```
|
|
175
|
+
The two reports are **complementary**, not conflated: `report_selection` labels retrieval
|
|
176
|
+
quality (these candidates were shown); `report_demand` captures the capability gap (what was
|
|
177
|
+
actually needed). On **all-rejected** send BOTH; on **empty** send only the demand pointer.
|
|
178
|
+
|
|
179
|
+
## Don't
|
|
180
|
+
|
|
181
|
+
- Don't put plan/brief/output text into any wish, keyword, sketch, or payload.
|
|
182
|
+
- Don't install without user approval, or re-recommend an already-installed skill.
|
|
183
|
+
- Don't author a demand sketch for a wish whose candidate you **accepted** (only on empty or all-rejected).
|
|
184
|
+
- Don't send `sketch` as a JSON object or `chosen` as null/empty — both are strings (see step 8–9).
|
|
185
|
+
- Don't treat candidates as authoritative — they're recall; you and the user decide.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"A plan was just approved. Before executing it, run the **Skill Federation finder** (the skill-federation skill) to see whether vetted skills already exist:\n1. From the plan in your context, write a wish-list of UP TO 10 wishes — each a {name, description, keywords, formulations} for an ideal skill this plan would use (\"if every skill existed, which ≤10 would I reach for?\"), with 1–5 evidence keywords and ~4 vocabulary-varied paraphrases per wish. Do NOT send the plan anywhere — only the wishes cross.\n2. Search each wish with curl (POST $SKILLFED_ENDPOINT/search; default https://qurini-skill-federation.hf.space) and drop anything already in ~/.claude/skills or ./.claude/skills.\n3. Review the candidates per wish; select the best match or reject all; present matches with license/provenance/trust metadata and get the user's approval BEFORE installing anything (fetch via POST /fetch).\n4. Report two complementary outcomes: for a wish that had candidates → POST /report_selection with chosen = the picked id or the literal \"None\" if you rejected all; for a wish with NO skill installed (empty OR all-rejected) → POST /report_demand with the wish string + a sketch string built per demand-sketch.md.\nDo NOT skip the finder silently. If you judge that a skill search is unnecessary for this plan (e.g. bespoke work on your own codebase), state that explicitly and verbosely — list the capabilities you considered and why none needs an external skill — and ask the user to confirm before proceeding without a search."}}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Find vetted agent skills for a task via the Skill Federation wish-list finder
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run the **Skill Federation wish-list finder** (the `skill-federation` skill) for the
|
|
6
|
+
task below. Follow that skill's flow exactly:
|
|
7
|
+
|
|
8
|
+
1. Sketch the ideal skill(s), then write a wish-list of **up to 10 wishes**, each
|
|
9
|
+
`{name, description, keywords, formulations, sketch}`: a one-line `description` (display),
|
|
10
|
+
**1–5 evidence keywords**, **~4 vocabulary-varied paraphrases** in `formulations` (the
|
|
11
|
+
load-bearing recall field), and a structured `sketch` (`purpose / inputs / outputs /
|
|
12
|
+
operations / domain_vocab / section_sketch / tags`, per `demand-sketch.md`) — author the
|
|
13
|
+
sketch once; it powers the search and, on a miss, becomes the demand pointer. Abstract
|
|
14
|
+
capability only — **never put the task's raw content, data, or outputs into any field**
|
|
15
|
+
(constitution Principle IV).
|
|
16
|
+
2. Search each wish with `curl` (POST `$SKILLFED_ENDPOINT/search`, default endpoint
|
|
17
|
+
`https://qurini-skill-federation.hf.space`): concatenate each wish's
|
|
18
|
+
description + formulations + the flattened `sketch` values into the `wish` string, send
|
|
19
|
+
`keywords` and `top_n`. No Python, no Node — just `curl` (use `curl.exe` on Windows).
|
|
20
|
+
Then drop any candidate whose name matches a skill already in `~/.claude/skills` or
|
|
21
|
+
`./.claude/skills`.
|
|
22
|
+
3. Per wish, select the best candidate or reject all. Present matches with trust
|
|
23
|
+
metadata (license class, provenance, stars, source, ⚠ flags) and get approval
|
|
24
|
+
**before** installing anything.
|
|
25
|
+
4. On approval, fetch with `curl` (POST `/fetch`) + install the returned `body`/`files` under
|
|
26
|
+
`.claude/skills/<id>/` (local-first: use an existing local copy if present). Then report two
|
|
27
|
+
complementary outcomes: for any wish that had candidates → `curl` POST `/report_selection`
|
|
28
|
+
with `chosen` = the picked id, or the literal `"None"` if you rejected all; for any wish with
|
|
29
|
+
**no skill installed** (empty OR all-rejected) → `curl` POST `/report_demand` with the `wish`
|
|
30
|
+
string + a `sketch` string built per `demand-sketch.md`.
|
|
31
|
+
|
|
32
|
+
If the task below is empty, ask the user what capability they're looking for.
|
|
33
|
+
|
|
34
|
+
Task to search for: $ARGUMENTS
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skillfed
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: No-clone installer for Skill Federation — drops the curl-tier finder skill + /skillfed command into ~/.claude. Run via `uvx skillfed` or `pipx run skillfed`. Stdlib only, zero deps.
|
|
5
|
+
Project-URL: Homepage, https://github.com/skill-federation/skill-federation
|
|
6
|
+
Project-URL: Repository, https://github.com/skill-federation/skill-federation
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: agent-skills,claude-code,installer,skill-federation,skillfed
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# skillfed (Python installer)
|
|
13
|
+
|
|
14
|
+
No-clone installer for [Skill Federation](https://github.com/skill-federation/skill-federation).
|
|
15
|
+
Drops the curl-tier finder skill + `/skillfed` command into `~/.claude` (or `./.claude`).
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uvx skillfed # curl tier, user scope (~/.claude)
|
|
19
|
+
uvx skillfed --with-hook # + plan-approval nudge
|
|
20
|
+
uvx skillfed --with-npx # + register the npx -y skillfed-mcp MCP server
|
|
21
|
+
uvx skillfed --scope project # install into ./.claude
|
|
22
|
+
|
|
23
|
+
# or, with pipx:
|
|
24
|
+
pipx run skillfed
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then **restart Claude Code** and run `/skillfed <what you're trying to do>`.
|
|
28
|
+
|
|
29
|
+
Stdlib only, zero dependencies. The runtime finder itself needs just `curl`. This package is one
|
|
30
|
+
of three install paths — see the project README for the curl bootstrap and `npx skillfed`.
|
|
31
|
+
|
|
32
|
+
## Build (maintainers)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node ../scripts/vendor-payload.mjs # vendor the 3 payload files into src/skillfed/payload/
|
|
36
|
+
python -m build # sdist + wheel in dist/
|
|
37
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
skillfed/__init__.py,sha256=d6XpxiokvvGj_9mC9th258xBBg04GaZnOLqKG4YK-2w,122
|
|
2
|
+
skillfed/cli.py,sha256=vygPifPqrtyweTa3L63DFoV9AVHoqueid39CDldGmZk,5794
|
|
3
|
+
skillfed/payload/SKILL.md,sha256=1UzxfTFIMRtlhvkeuM7N-bjUbfCON9plShdpuRNhQEU,11931
|
|
4
|
+
skillfed/payload/plan_nudge.json,sha256=fBNtfOjvt3EArD6r3fQJSpNYIc4ED4W__KBNoSZujxA,1623
|
|
5
|
+
skillfed/payload/skillfed.md,sha256=bu-1Dx8OGTfU1UYpFu-pGH74NLrNOaWFF9GakuYiPWY,2197
|
|
6
|
+
skillfed-0.1.0.dist-info/METADATA,sha256=-Q2bhsuu7wNpWvoLlCXBKU0OaTTokP0Qly-3whlZFLE,1539
|
|
7
|
+
skillfed-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
8
|
+
skillfed-0.1.0.dist-info/entry_points.txt,sha256=HZMfFaWgRvV1jGemjXOr9a94SxrIzQibEkIwHHGjJYk,47
|
|
9
|
+
skillfed-0.1.0.dist-info/RECORD,,
|