skillscout-cli 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/PKG-INFO +89 -14
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/README.md +86 -11
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/pyproject.toml +3 -3
- skillscout_cli-0.4.0/skillscout/cli.py +254 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/core.py +40 -12
- skillscout_cli-0.4.0/skillscout/history.py +153 -0
- skillscout_cli-0.4.0/skillscout/persona.py +103 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/profile.example.toml +21 -0
- skillscout_cli-0.4.0/skillscout/switch.py +169 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout_cli.egg-info/PKG-INFO +89 -14
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout_cli.egg-info/SOURCES.txt +4 -0
- skillscout_cli-0.4.0/tests/test_include.py +21 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/tests/test_skillscout.py +178 -1
- skillscout_cli-0.2.0/skillscout/cli.py +0 -119
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/LICENSE +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/setup.cfg +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/__init__.py +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/github.py +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/index.py +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/scan.py +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/schedule.py +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/score.py +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout/skill.py +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout_cli.egg-info/dependency_links.txt +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout_cli.egg-info/entry_points.txt +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout_cli.egg-info/requires.txt +0 -0
- {skillscout_cli-0.2.0 → skillscout_cli-0.4.0}/skillscout_cli.egg-info/top_level.txt +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillscout-cli
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Keep your agent's skills current: weekly
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Keep your agent's skills current for each kind of work you do: weekly index, personas, scans, pinned installs
|
|
5
5
|
Author: Akshit Gupta
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://akshit9162.github.io/skillscout
|
|
8
8
|
Project-URL: Source, https://github.com/akshit9162/skillscout
|
|
9
9
|
Project-URL: Issues, https://github.com/akshit9162/skillscout/issues
|
|
10
10
|
Project-URL: Report, https://akshit9162.github.io/skillscout/report.html
|
|
11
|
-
Keywords: claude,claude-code,agent-skills,security,supply-chain,llm
|
|
11
|
+
Keywords: claude,claude-code,agent-skills,personas,subagents,security,supply-chain,llm
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -27,13 +27,14 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
27
27
|
Dynamic: license-file
|
|
28
28
|
|
|
29
29
|
<p align="center">
|
|
30
|
-
<img src="https://
|
|
30
|
+
<img src="https://akshit9162.github.io/skillscout/assets/header.svg" width="760" alt="skillscout: keep your agent's skills current, for every kind of work you do" />
|
|
31
31
|
</p>
|
|
32
32
|
|
|
33
33
|
<p align="center">
|
|
34
34
|
<a href="https://akshit9162.github.io/skillscout">Website</a>  · 
|
|
35
35
|
<a href="https://akshit9162.github.io/skillscout/report.html">Report: what's inside 6,726 skills</a>  · 
|
|
36
36
|
<a href="#install">Install</a>  · 
|
|
37
|
+
<a href="#personas">Personas</a>  · 
|
|
37
38
|
<a href="#a-week-with-skillscout">A week with it</a>  · 
|
|
38
39
|
<a href="#how-it-works">How it works</a>  · 
|
|
39
40
|
<a href="#what-gets-flagged">What gets flagged</a>
|
|
@@ -47,7 +48,7 @@ Dynamic: license-file
|
|
|
47
48
|
</p>
|
|
48
49
|
|
|
49
50
|
<p align="center">
|
|
50
|
-
<img src="https://
|
|
51
|
+
<img src="https://akshit9162.github.io/skillscout/assets/demo.svg" width="760" alt="Terminal demo: init drafts keywords from one line; persona add creates a brand persona; discover scores 23,865 skills with a section for the brand persona; persona build writes its agent; claude --agent brand starts it" />
|
|
51
52
|
</p>
|
|
52
53
|
|
|
53
54
|
```bash
|
|
@@ -60,28 +61,92 @@ AI tooling moves weekly. New skills, plugins and agent workflows land on GitHub
|
|
|
60
61
|
installed keep changing upstream, and last month's best practice is already stale. Nobody has time to
|
|
61
62
|
watch hundreds of repos.
|
|
62
63
|
|
|
63
|
-
**skillscout keeps your agent current
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
**skillscout keeps your agent current, for each kind of work you do.** Every week a shared index of every
|
|
65
|
+
public skill it can find (32,360 from 1,173 repos at the last build) is rebuilt. skillscout scores it on your
|
|
66
|
+
machine against what *you* work on, tells you what's new and worth having, and shows you exactly what
|
|
67
|
+
changed in the skills you already use.
|
|
68
|
+
|
|
69
|
+
You probably don't do one kind of work. Coding on Monday, a landing page on Tuesday, investor emails on
|
|
70
|
+
Friday. So skillscout keeps **personas**: one per kind of work, each with its own search and its own Claude
|
|
71
|
+
Code agent, loaded with the best skills for that work and kept up to date as better ones appear.
|
|
66
72
|
|
|
67
73
|
Keeping up fast is only useful if it's safe. A skill is a set of instructions your agent follows
|
|
68
74
|
**with your permissions**, so skillscout scans every file for dangerous patterns, installs nothing without
|
|
69
75
|
your approval, pins each install to an exact commit, and re-scans every update before it can replace anything.
|
|
70
76
|
|
|
71
|
-
In
|
|
77
|
+
In a hand-checked run on 27 Oct 2026 over **6,726 skills in 95 repos**, 198 had at least one high-risk flag,
|
|
72
78
|
mostly `sudo` and `curl | sh` install steps, plus prompt-injection-style text and hidden Unicode.
|
|
73
79
|
The [report](https://akshit9162.github.io/skillscout/report.html) reads every one of the scariest flags: they were defenders.
|
|
74
80
|
|
|
75
81
|
It's a zero-dependency Python CLI, and it also ships as a skill, so your agent can run the whole loop for you
|
|
76
82
|
and still ask before every install.
|
|
77
83
|
|
|
84
|
+
## Personas
|
|
85
|
+
|
|
86
|
+
A persona is one side of your work, in your words. skillscout doesn't ship any: yours might be "builder",
|
|
87
|
+
"brand" and "outreach", someone else's "trading" and "thesis".
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
You "set up personas for me" (in Claude Code, with the plugin)
|
|
91
|
+
Agent reads your recent sessions on this machine, with your OK (skillscout history)
|
|
92
|
+
→ proposes: builder · brand · outreach, each with the sessions it came from
|
|
93
|
+
You keep brand and builder, rename outreach to launches
|
|
94
|
+
Agent skillscout persona add brand --about "D2C marketing: ad copy, landing pages, Instagram reels"
|
|
95
|
+
→ keywords drafted from terms skills actually use: instagram reels, ad copy, landing pages...
|
|
96
|
+
skillscout discover → a "For your brand work" section in the report
|
|
97
|
+
skillscout persona build brand → ~/.claude/agents/brand.md, skills: copy, landing-page-design, script...
|
|
98
|
+
You claude --agent brand
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Switch persona, and the rest stays out of the way
|
|
102
|
+
|
|
103
|
+
Every installed skill's description loads into every Claude Code session, so a big collection crowds out
|
|
104
|
+
the work. Switch to the persona you're in and Claude Code lists only its skills:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
skillscout persona use brand # or say "switch to brand" to the agent
|
|
108
|
+
# active: brand
|
|
109
|
+
# hid 31: fastapi, postgres-patterns, seo-audit, ...
|
|
110
|
+
# plugins on 1: marketing@knowledge-work-plugins
|
|
111
|
+
# plugins off 2: sales@knowledge-work-plugins, android-skills@android-skills
|
|
112
|
+
skillscout persona use brand builder # several at once
|
|
113
|
+
skillscout persona start brand # switch, then start Claude Code (with brand's agent if built)
|
|
114
|
+
skillscout persona use --core # no persona: only [switch] core and unclaimed skills
|
|
115
|
+
skillscout persona use --clear # everything back as it was
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
It writes `skillOverrides` and `enabledPlugins` in `~/.claude/settings.json`: nothing moves on disk.
|
|
119
|
+
A persona claims the skills that fit its keywords, plus any you pin with `skills = [...]`, minus `exclude`.
|
|
120
|
+
Plugins switch whole, so you pin them with `plugins = [...]` (`persona add` drafts them from the plugins
|
|
121
|
+
whose skills fit). skillscout restores only what it changed, never overwrites an override you set, and a
|
|
122
|
+
skill you approve later joins whichever personas it fits. A new session picks the switch up; in an open
|
|
123
|
+
one, run `/reload-skills` and `/reload-plugins`.
|
|
124
|
+
|
|
125
|
+
From then on, each weekly report has a section per persona, and `persona build` swaps in a better skill when
|
|
126
|
+
one arrives (after you've read and approved it). `persona build` only ever rewrites the agent's `skills:` list:
|
|
127
|
+
its prompt, tone, tools, model and memory are yours.
|
|
128
|
+
|
|
129
|
+
The CLI works without the agent too:
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
skillscout persona add brand --about "D2C marketing: ad copy, landing pages, Instagram reels"
|
|
133
|
+
skillscout persona list # keywords, best installed skills, what's worth installing
|
|
134
|
+
skillscout persona build --all # write or update ~/.claude/agents/<name>.md
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Your history stays on your machine.** `skillscout history` reads `~/.claude/projects` only when you run it,
|
|
138
|
+
keeps only what you typed (no tool output, no pasted blocks, no links) and sends nothing anywhere. Grouping
|
|
139
|
+
sessions into personas is left to you or the agent in your own session: counting words can't tell short chat
|
|
140
|
+
messages apart reliably, and a wrong persona is worse than none.
|
|
141
|
+
|
|
78
142
|
## A week with skillscout
|
|
79
143
|
|
|
80
144
|
Example session (counts vary week to week):
|
|
81
145
|
|
|
82
146
|
```text
|
|
83
147
|
Monday 10:00 skillscout discover --notify (weekly, via schedule)
|
|
84
|
-
→ "4 new
|
|
148
|
+
→ "23 recommended for you (4 new this run), 11 relevant but flagged high-risk"
|
|
149
|
+
→ plus a section per persona: "For your brand work", "For your builder work"
|
|
85
150
|
You skillscout show github/awesome-copilot:eval-driven-dev
|
|
86
151
|
→ flags with file:line, the full SKILL.md, overlaps with what you have
|
|
87
152
|
You skillscout approve eval-driven-dev → copied, pinned to its commit
|
|
@@ -98,17 +163,18 @@ claude plugin marketplace add akshit9162/skillscout
|
|
|
98
163
|
claude plugin install skillscout@skillscout
|
|
99
164
|
```
|
|
100
165
|
|
|
101
|
-
Then ask: *"find me skills for my Python and React work"*. The agent installs the CLI, runs the search,
|
|
166
|
+
Then ask: *"find me skills for my Python and React work"*, or *"set up personas for me"*. The agent installs the CLI, runs the search,
|
|
102
167
|
summarises the report and shows you each skill's flags before asking to install it.
|
|
103
168
|
|
|
104
169
|
### As a CLI
|
|
105
170
|
|
|
106
171
|
```sh
|
|
107
172
|
pipx install skillscout-cli # or: uv tool install skillscout-cli
|
|
108
|
-
skillscout init
|
|
173
|
+
skillscout init --about "backend in Python, code review, RL evals, Postgres"
|
|
109
174
|
```
|
|
110
175
|
|
|
111
|
-
|
|
176
|
+
`init` writes `~/.skillscout/profile.toml` and drafts `[keywords]` from your line, using only terms that skills
|
|
177
|
+
actually use. Check them, then:
|
|
112
178
|
|
|
113
179
|
```sh
|
|
114
180
|
skillscout discover # score this week's index; writes ~/.skillscout/reports/<date>.md
|
|
@@ -167,6 +233,13 @@ High-risk skills need `--accept-risk`, which the bundled skill tells the agent n
|
|
|
167
233
|
| `skillscout approve <id> [--update] [--accept-risk] [--to DIR]` | Install and pin. `--to`, or `dir` under `[install]` in your profile, installs into another agent's skills folder |
|
|
168
234
|
| `skillscout installed` · `skillscout remove <id>` · `skillscout rollback <id>` | Pinned skills · uninstall · undo the last update |
|
|
169
235
|
| `skillscout updates` | Diff pinned skills against upstream |
|
|
236
|
+
| `skillscout init [--about "..."]` | Write the profile; `--about` drafts your keywords from one line |
|
|
237
|
+
| `skillscout history [--days N]` | A local digest of your recent Claude Code sessions, to draft personas from |
|
|
238
|
+
| `skillscout persona add <name> --about "..." [--keywords "a=3,b=2"]` | Add a persona; keywords drafted from the line unless given |
|
|
239
|
+
| `skillscout persona list` | Each persona: keywords, best installed skills, what's worth installing |
|
|
240
|
+
| `skillscout persona build <name> \| --all [--dry-run]` | Write `~/.claude/agents/<name>.md`, or update only its `skills:` list |
|
|
241
|
+
| `skillscout persona use [<name>...] [--core] [--clear]` | List only these personas' skills and plugins in Claude Code; `--clear` undoes it |
|
|
242
|
+
| `skillscout persona start <name>...` | `persona use`, then start Claude Code |
|
|
170
243
|
| `skillscout schedule` | Set up weekly discovery: launchd on macOS, a systemd timer on Linux (cron line without systemd), a `schtasks` command on Windows. It prints the one command that turns it on |
|
|
171
244
|
|
|
172
245
|
With the index, `discover` makes no GitHub API calls at all. For `--local` and `index`, set `GITHUB_TOKEN`, or just be logged in with the GitHub CLI (`gh auth login`), for higher API rate limits (5,000 requests an hour instead of 60). Following lists costs one lookup per linked repo: up to 1,500 a run with a token, 40 without; links not yet checked go first, so later runs reach the rest. A token is optional: without it, a rate limit means waiting rather than failing. A weekly run with few upstream changes takes about 25 seconds; the first run takes a few minutes. Scheduled jobs don't see your shell's environment, so add the token to the job if you want it there.
|
|
@@ -176,7 +249,9 @@ With the index, `discover` makes no GitHub API calls at all. For `--local` and `
|
|
|
176
249
|
- The scanner catches known patterns. It can't prove a skill is safe, so read `show` before you approve.
|
|
177
250
|
- Relevance is keyword matching against your profile, not semantic search. Tune the weights.
|
|
178
251
|
- The scanner is pattern rules, deliberately: a skill is text written to steer a model, so a model judging it can be steered by it. The rules make you read; they don't decide.
|
|
179
|
-
- Every installed skill's description loads into every agent session.
|
|
252
|
+
- Every installed skill's description loads into every agent session. `persona use` keeps only the active
|
|
253
|
+
personas' skills listed; without it, keep the total to a few dozen.
|
|
254
|
+
- A persona's `skills:` load in full when its agent starts. `max_skills` (default 5) keeps that small.
|
|
180
255
|
|
|
181
256
|
## Development
|
|
182
257
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://
|
|
2
|
+
<img src="https://akshit9162.github.io/skillscout/assets/header.svg" width="760" alt="skillscout: keep your agent's skills current, for every kind of work you do" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://akshit9162.github.io/skillscout">Website</a>  · 
|
|
7
7
|
<a href="https://akshit9162.github.io/skillscout/report.html">Report: what's inside 6,726 skills</a>  · 
|
|
8
8
|
<a href="#install">Install</a>  · 
|
|
9
|
+
<a href="#personas">Personas</a>  · 
|
|
9
10
|
<a href="#a-week-with-skillscout">A week with it</a>  · 
|
|
10
11
|
<a href="#how-it-works">How it works</a>  · 
|
|
11
12
|
<a href="#what-gets-flagged">What gets flagged</a>
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
</p>
|
|
20
21
|
|
|
21
22
|
<p align="center">
|
|
22
|
-
<img src="https://
|
|
23
|
+
<img src="https://akshit9162.github.io/skillscout/assets/demo.svg" width="760" alt="Terminal demo: init drafts keywords from one line; persona add creates a brand persona; discover scores 23,865 skills with a section for the brand persona; persona build writes its agent; claude --agent brand starts it" />
|
|
23
24
|
</p>
|
|
24
25
|
|
|
25
26
|
```bash
|
|
@@ -32,28 +33,92 @@ AI tooling moves weekly. New skills, plugins and agent workflows land on GitHub
|
|
|
32
33
|
installed keep changing upstream, and last month's best practice is already stale. Nobody has time to
|
|
33
34
|
watch hundreds of repos.
|
|
34
35
|
|
|
35
|
-
**skillscout keeps your agent current
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
**skillscout keeps your agent current, for each kind of work you do.** Every week a shared index of every
|
|
37
|
+
public skill it can find (32,360 from 1,173 repos at the last build) is rebuilt. skillscout scores it on your
|
|
38
|
+
machine against what *you* work on, tells you what's new and worth having, and shows you exactly what
|
|
39
|
+
changed in the skills you already use.
|
|
40
|
+
|
|
41
|
+
You probably don't do one kind of work. Coding on Monday, a landing page on Tuesday, investor emails on
|
|
42
|
+
Friday. So skillscout keeps **personas**: one per kind of work, each with its own search and its own Claude
|
|
43
|
+
Code agent, loaded with the best skills for that work and kept up to date as better ones appear.
|
|
38
44
|
|
|
39
45
|
Keeping up fast is only useful if it's safe. A skill is a set of instructions your agent follows
|
|
40
46
|
**with your permissions**, so skillscout scans every file for dangerous patterns, installs nothing without
|
|
41
47
|
your approval, pins each install to an exact commit, and re-scans every update before it can replace anything.
|
|
42
48
|
|
|
43
|
-
In
|
|
49
|
+
In a hand-checked run on 27 Oct 2026 over **6,726 skills in 95 repos**, 198 had at least one high-risk flag,
|
|
44
50
|
mostly `sudo` and `curl | sh` install steps, plus prompt-injection-style text and hidden Unicode.
|
|
45
51
|
The [report](https://akshit9162.github.io/skillscout/report.html) reads every one of the scariest flags: they were defenders.
|
|
46
52
|
|
|
47
53
|
It's a zero-dependency Python CLI, and it also ships as a skill, so your agent can run the whole loop for you
|
|
48
54
|
and still ask before every install.
|
|
49
55
|
|
|
56
|
+
## Personas
|
|
57
|
+
|
|
58
|
+
A persona is one side of your work, in your words. skillscout doesn't ship any: yours might be "builder",
|
|
59
|
+
"brand" and "outreach", someone else's "trading" and "thesis".
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
You "set up personas for me" (in Claude Code, with the plugin)
|
|
63
|
+
Agent reads your recent sessions on this machine, with your OK (skillscout history)
|
|
64
|
+
→ proposes: builder · brand · outreach, each with the sessions it came from
|
|
65
|
+
You keep brand and builder, rename outreach to launches
|
|
66
|
+
Agent skillscout persona add brand --about "D2C marketing: ad copy, landing pages, Instagram reels"
|
|
67
|
+
→ keywords drafted from terms skills actually use: instagram reels, ad copy, landing pages...
|
|
68
|
+
skillscout discover → a "For your brand work" section in the report
|
|
69
|
+
skillscout persona build brand → ~/.claude/agents/brand.md, skills: copy, landing-page-design, script...
|
|
70
|
+
You claude --agent brand
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Switch persona, and the rest stays out of the way
|
|
74
|
+
|
|
75
|
+
Every installed skill's description loads into every Claude Code session, so a big collection crowds out
|
|
76
|
+
the work. Switch to the persona you're in and Claude Code lists only its skills:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
skillscout persona use brand # or say "switch to brand" to the agent
|
|
80
|
+
# active: brand
|
|
81
|
+
# hid 31: fastapi, postgres-patterns, seo-audit, ...
|
|
82
|
+
# plugins on 1: marketing@knowledge-work-plugins
|
|
83
|
+
# plugins off 2: sales@knowledge-work-plugins, android-skills@android-skills
|
|
84
|
+
skillscout persona use brand builder # several at once
|
|
85
|
+
skillscout persona start brand # switch, then start Claude Code (with brand's agent if built)
|
|
86
|
+
skillscout persona use --core # no persona: only [switch] core and unclaimed skills
|
|
87
|
+
skillscout persona use --clear # everything back as it was
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
It writes `skillOverrides` and `enabledPlugins` in `~/.claude/settings.json`: nothing moves on disk.
|
|
91
|
+
A persona claims the skills that fit its keywords, plus any you pin with `skills = [...]`, minus `exclude`.
|
|
92
|
+
Plugins switch whole, so you pin them with `plugins = [...]` (`persona add` drafts them from the plugins
|
|
93
|
+
whose skills fit). skillscout restores only what it changed, never overwrites an override you set, and a
|
|
94
|
+
skill you approve later joins whichever personas it fits. A new session picks the switch up; in an open
|
|
95
|
+
one, run `/reload-skills` and `/reload-plugins`.
|
|
96
|
+
|
|
97
|
+
From then on, each weekly report has a section per persona, and `persona build` swaps in a better skill when
|
|
98
|
+
one arrives (after you've read and approved it). `persona build` only ever rewrites the agent's `skills:` list:
|
|
99
|
+
its prompt, tone, tools, model and memory are yours.
|
|
100
|
+
|
|
101
|
+
The CLI works without the agent too:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
skillscout persona add brand --about "D2C marketing: ad copy, landing pages, Instagram reels"
|
|
105
|
+
skillscout persona list # keywords, best installed skills, what's worth installing
|
|
106
|
+
skillscout persona build --all # write or update ~/.claude/agents/<name>.md
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Your history stays on your machine.** `skillscout history` reads `~/.claude/projects` only when you run it,
|
|
110
|
+
keeps only what you typed (no tool output, no pasted blocks, no links) and sends nothing anywhere. Grouping
|
|
111
|
+
sessions into personas is left to you or the agent in your own session: counting words can't tell short chat
|
|
112
|
+
messages apart reliably, and a wrong persona is worse than none.
|
|
113
|
+
|
|
50
114
|
## A week with skillscout
|
|
51
115
|
|
|
52
116
|
Example session (counts vary week to week):
|
|
53
117
|
|
|
54
118
|
```text
|
|
55
119
|
Monday 10:00 skillscout discover --notify (weekly, via schedule)
|
|
56
|
-
→ "4 new
|
|
120
|
+
→ "23 recommended for you (4 new this run), 11 relevant but flagged high-risk"
|
|
121
|
+
→ plus a section per persona: "For your brand work", "For your builder work"
|
|
57
122
|
You skillscout show github/awesome-copilot:eval-driven-dev
|
|
58
123
|
→ flags with file:line, the full SKILL.md, overlaps with what you have
|
|
59
124
|
You skillscout approve eval-driven-dev → copied, pinned to its commit
|
|
@@ -70,17 +135,18 @@ claude plugin marketplace add akshit9162/skillscout
|
|
|
70
135
|
claude plugin install skillscout@skillscout
|
|
71
136
|
```
|
|
72
137
|
|
|
73
|
-
Then ask: *"find me skills for my Python and React work"*. The agent installs the CLI, runs the search,
|
|
138
|
+
Then ask: *"find me skills for my Python and React work"*, or *"set up personas for me"*. The agent installs the CLI, runs the search,
|
|
74
139
|
summarises the report and shows you each skill's flags before asking to install it.
|
|
75
140
|
|
|
76
141
|
### As a CLI
|
|
77
142
|
|
|
78
143
|
```sh
|
|
79
144
|
pipx install skillscout-cli # or: uv tool install skillscout-cli
|
|
80
|
-
skillscout init
|
|
145
|
+
skillscout init --about "backend in Python, code review, RL evals, Postgres"
|
|
81
146
|
```
|
|
82
147
|
|
|
83
|
-
|
|
148
|
+
`init` writes `~/.skillscout/profile.toml` and drafts `[keywords]` from your line, using only terms that skills
|
|
149
|
+
actually use. Check them, then:
|
|
84
150
|
|
|
85
151
|
```sh
|
|
86
152
|
skillscout discover # score this week's index; writes ~/.skillscout/reports/<date>.md
|
|
@@ -139,6 +205,13 @@ High-risk skills need `--accept-risk`, which the bundled skill tells the agent n
|
|
|
139
205
|
| `skillscout approve <id> [--update] [--accept-risk] [--to DIR]` | Install and pin. `--to`, or `dir` under `[install]` in your profile, installs into another agent's skills folder |
|
|
140
206
|
| `skillscout installed` · `skillscout remove <id>` · `skillscout rollback <id>` | Pinned skills · uninstall · undo the last update |
|
|
141
207
|
| `skillscout updates` | Diff pinned skills against upstream |
|
|
208
|
+
| `skillscout init [--about "..."]` | Write the profile; `--about` drafts your keywords from one line |
|
|
209
|
+
| `skillscout history [--days N]` | A local digest of your recent Claude Code sessions, to draft personas from |
|
|
210
|
+
| `skillscout persona add <name> --about "..." [--keywords "a=3,b=2"]` | Add a persona; keywords drafted from the line unless given |
|
|
211
|
+
| `skillscout persona list` | Each persona: keywords, best installed skills, what's worth installing |
|
|
212
|
+
| `skillscout persona build <name> \| --all [--dry-run]` | Write `~/.claude/agents/<name>.md`, or update only its `skills:` list |
|
|
213
|
+
| `skillscout persona use [<name>...] [--core] [--clear]` | List only these personas' skills and plugins in Claude Code; `--clear` undoes it |
|
|
214
|
+
| `skillscout persona start <name>...` | `persona use`, then start Claude Code |
|
|
142
215
|
| `skillscout schedule` | Set up weekly discovery: launchd on macOS, a systemd timer on Linux (cron line without systemd), a `schtasks` command on Windows. It prints the one command that turns it on |
|
|
143
216
|
|
|
144
217
|
With the index, `discover` makes no GitHub API calls at all. For `--local` and `index`, set `GITHUB_TOKEN`, or just be logged in with the GitHub CLI (`gh auth login`), for higher API rate limits (5,000 requests an hour instead of 60). Following lists costs one lookup per linked repo: up to 1,500 a run with a token, 40 without; links not yet checked go first, so later runs reach the rest. A token is optional: without it, a rate limit means waiting rather than failing. A weekly run with few upstream changes takes about 25 seconds; the first run takes a few minutes. Scheduled jobs don't see your shell's environment, so add the token to the job if you want it there.
|
|
@@ -148,7 +221,9 @@ With the index, `discover` makes no GitHub API calls at all. For `--local` and `
|
|
|
148
221
|
- The scanner catches known patterns. It can't prove a skill is safe, so read `show` before you approve.
|
|
149
222
|
- Relevance is keyword matching against your profile, not semantic search. Tune the weights.
|
|
150
223
|
- The scanner is pattern rules, deliberately: a skill is text written to steer a model, so a model judging it can be steered by it. The rules make you read; they don't decide.
|
|
151
|
-
- Every installed skill's description loads into every agent session.
|
|
224
|
+
- Every installed skill's description loads into every agent session. `persona use` keeps only the active
|
|
225
|
+
personas' skills listed; without it, keep the total to a few dozen.
|
|
226
|
+
- A persona's `skills:` load in full when its agent starts. `max_skills` (default 5) keeps that small.
|
|
152
227
|
|
|
153
228
|
## Development
|
|
154
229
|
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "skillscout-cli"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Keep your agent's skills current: weekly
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "Keep your agent's skills current for each kind of work you do: weekly index, personas, scans, pinned installs"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [{ name = "Akshit Gupta" }]
|
|
14
|
-
keywords = ["claude", "claude-code", "agent-skills", "security", "supply-chain", "llm"]
|
|
14
|
+
keywords = ["claude", "claude-code", "agent-skills", "personas", "subagents", "security", "supply-chain", "llm"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
17
|
"Environment :: Console",
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""skillscout command line."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import __doc__ as ABOUT, core, github, history, index, persona, schedule, switch
|
|
11
|
+
|
|
12
|
+
EXAMPLE = Path(__file__).resolve().parent / "profile.example.toml"
|
|
13
|
+
USER_PROFILE = Path.home() / ".skillscout" / "profile.toml"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def notify(text: str) -> None:
|
|
17
|
+
if sys.platform == "darwin" and shutil.which("osascript"):
|
|
18
|
+
subprocess.run(["osascript", "-e", f'display notification "{text}" with title "skillscout"'], check=False)
|
|
19
|
+
elif shutil.which("notify-send"):
|
|
20
|
+
subprocess.run(["notify-send", "skillscout", text], check=False)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv: list[str] | None = None) -> None:
|
|
24
|
+
p = argparse.ArgumentParser(prog="skillscout", description=ABOUT)
|
|
25
|
+
p.add_argument("--profile", type=Path, default=None, help="default: ~/.skillscout/profile.toml, else the example")
|
|
26
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
27
|
+
it = sub.add_parser("init", help="write ~/.skillscout/profile.toml")
|
|
28
|
+
it.add_argument("--about", help="one line about your work; becomes your keywords (e.g. \"backend in Python, RL evals, Postgres\")")
|
|
29
|
+
d = sub.add_parser("discover", help="score this week's shared index against your profile and write a report")
|
|
30
|
+
d.add_argument("--local", action="store_true", help="crawl and scan GitHub on this machine instead of using the index")
|
|
31
|
+
d.add_argument("--full", action="store_true", help="with --local: ignore caches, fetch every repo and re-scan every skill")
|
|
32
|
+
d.add_argument("--notify", action="store_true", help="desktop notification with the summary (macOS, or Linux with notify-send)")
|
|
33
|
+
ls = sub.add_parser("list", help="show scored candidates")
|
|
34
|
+
ls.add_argument("--all", action="store_true", help="include zero-relevance skills")
|
|
35
|
+
ls.add_argument("-n", type=int, default=30)
|
|
36
|
+
ix = sub.add_parser("index", help="crawl and scan the ecosystem and write the shared index (what the weekly job runs)")
|
|
37
|
+
ix.add_argument("--out", type=Path, default=Path("index.json.gz"))
|
|
38
|
+
ix.add_argument("--full", action="store_true", help="ignore caches: fetch every repo and re-scan every skill")
|
|
39
|
+
sh = sub.add_parser("show", help="fetch a candidate, scan it here, print its flags and full SKILL.md")
|
|
40
|
+
sh.add_argument("id")
|
|
41
|
+
ap = sub.add_parser("approve", help="install one skill (into ~/.claude/skills unless [install] dir says otherwise) and pin it")
|
|
42
|
+
ap.add_argument("id")
|
|
43
|
+
ap.add_argument("--to", type=Path, help="install into this skills folder instead, e.g. another agent's")
|
|
44
|
+
ap.add_argument("--accept-risk", action="store_true", help="install despite high-risk flags")
|
|
45
|
+
ap.add_argument("--update", action="store_true", help="replace a copy skillscout installed earlier")
|
|
46
|
+
rm = sub.add_parser("remove", help="uninstall a skill skillscout installed")
|
|
47
|
+
rm.add_argument("id")
|
|
48
|
+
rb = sub.add_parser("rollback", help="restore the copy the last `approve --update` replaced")
|
|
49
|
+
rb.add_argument("id")
|
|
50
|
+
sub.add_parser("installed", help="show pinned skills")
|
|
51
|
+
sub.add_parser("updates", help="diff pinned skills against upstream; applies nothing")
|
|
52
|
+
sub.add_parser("schedule", help="set up weekly discovery for this OS (you turn it on)")
|
|
53
|
+
hi = sub.add_parser("history", help="summarise your recent Claude Code sessions, read locally, to draft personas from")
|
|
54
|
+
hi.add_argument("--days", type=int, default=60)
|
|
55
|
+
pe = sub.add_parser("persona", help="kinds of work, each with its own keywords and an agent kept current")
|
|
56
|
+
psub = pe.add_subparsers(dest="action", required=True)
|
|
57
|
+
pa = psub.add_parser("add", help="add a persona from one line about that work")
|
|
58
|
+
pa.add_argument("name")
|
|
59
|
+
pa.add_argument("--about", required=True, help="what this side of your work is, in your words")
|
|
60
|
+
pa.add_argument("--keywords", help="instead of drafting them: \"cold email=4,investor update=3\"")
|
|
61
|
+
pa.add_argument("--agent", help="agent file name, if not the persona's name")
|
|
62
|
+
psub.add_parser("list", help="each persona, its keywords, and the installed skills that fit it")
|
|
63
|
+
pb = psub.add_parser("build", help="write or update the persona's agent in ~/.claude/agents (only its skills: list)")
|
|
64
|
+
pb.add_argument("name", nargs="?")
|
|
65
|
+
pb.add_argument("--all", action="store_true")
|
|
66
|
+
pb.add_argument("--dry-run", action="store_true")
|
|
67
|
+
pu = psub.add_parser("use", help="list only these personas' skills and plugins in Claude Code (no names: show what's active)")
|
|
68
|
+
pu.add_argument("names", nargs="*")
|
|
69
|
+
pu.add_argument("--clear", action="store_true", help="undo every change skillscout made: all skills and plugins as before")
|
|
70
|
+
pu.add_argument("--core", action="store_true", help="no persona: hide every persona's skills and plugins, keep [switch] core")
|
|
71
|
+
ps = psub.add_parser("start", help="`persona use` these personas, then start Claude Code")
|
|
72
|
+
ps.add_argument("names", nargs="+")
|
|
73
|
+
a = p.parse_args(argv)
|
|
74
|
+
|
|
75
|
+
paths = core.Paths()
|
|
76
|
+
if a.cmd == "init":
|
|
77
|
+
if USER_PROFILE.exists():
|
|
78
|
+
raise SystemExit(f"{USER_PROFILE} already exists")
|
|
79
|
+
text = EXAMPLE.read_text(encoding="utf-8")
|
|
80
|
+
if a.about:
|
|
81
|
+
kw = history.keywords_from(a.about, vocabulary(core.load_profile(EXAMPLE), paths))
|
|
82
|
+
if not kw:
|
|
83
|
+
raise SystemExit("none of those words appear in skill descriptions; try more specific ones")
|
|
84
|
+
head, rest = text.split("[keywords]\n", 1)
|
|
85
|
+
lines = rest.splitlines(keepends=True)
|
|
86
|
+
keep = next(i for i, line in enumerate(lines) if not line.startswith('"') and line.strip())
|
|
87
|
+
tail = "".join(lines[keep:])
|
|
88
|
+
text = head + "[keywords]\n" + f"# drafted from: {a.about}\n" + "".join(f"{json.dumps(k)} = {v}\n" for k, v in kw.items()) + "\n" + tail
|
|
89
|
+
USER_PROFILE.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
USER_PROFILE.write_text(text, encoding="utf-8")
|
|
91
|
+
print(f"wrote {USER_PROFILE}; " + ("check the drafted [keywords]" if a.about else "edit [keywords] to match your work")
|
|
92
|
+
+ ", then run `skillscout discover`")
|
|
93
|
+
return
|
|
94
|
+
profile = core.load_profile(a.profile or (USER_PROFILE if USER_PROFILE.exists() else EXAMPLE))
|
|
95
|
+
if folder := getattr(a, "to", None) or profile.get("install", {}).get("dir"):
|
|
96
|
+
paths.skills = Path(folder).expanduser()
|
|
97
|
+
cands = core._read(paths.candidates, [])
|
|
98
|
+
|
|
99
|
+
if a.cmd == "discover":
|
|
100
|
+
url = profile.get("index", {}).get("url", index.URL)
|
|
101
|
+
if a.local or not url:
|
|
102
|
+
cands = core.discover(profile, paths, full=a.full)
|
|
103
|
+
else:
|
|
104
|
+
try:
|
|
105
|
+
cands = core.discover_from_index(profile, paths, url, profile.get("index", {}).get("verify", False))
|
|
106
|
+
except index.Unavailable as e:
|
|
107
|
+
raise SystemExit(str(e))
|
|
108
|
+
out, summary = core.report(cands, profile, paths)
|
|
109
|
+
print(f"{summary}\nreport: {out}")
|
|
110
|
+
if a.notify:
|
|
111
|
+
notify(summary)
|
|
112
|
+
elif a.cmd == "index":
|
|
113
|
+
records = core.crawl(profile["search"], paths, full=a.full)
|
|
114
|
+
data = index.build(records, a.out, core.RULES)
|
|
115
|
+
print(f"wrote {a.out}: {len(data['skills'])} skills from {len({r['repo'] for r in records})} repos")
|
|
116
|
+
elif a.cmd == "list":
|
|
117
|
+
rows = [c for c in cands if a.all or c["relevance"] > 0][: a.n]
|
|
118
|
+
for c in rows:
|
|
119
|
+
tag = "installed" if c["installed"] else ("REC" if core.recommended(c, profile) else "")
|
|
120
|
+
print(f"{c['score']:.2f} {c['risk'] or '-':6} {tag:9} {c['id']}")
|
|
121
|
+
elif a.cmd == "show":
|
|
122
|
+
c = core.ensure_local(core.find(cands, a.id), paths)
|
|
123
|
+
print(json.dumps({k: c[k] for k in ("id", "score", "stars", "license", "commit", "risk", "matched", "conflicts")}, indent=2))
|
|
124
|
+
for f in c["flags"]:
|
|
125
|
+
print(f" [{f['level']}] {f['reason']} — {f['where']}")
|
|
126
|
+
src = core.source_dir(c, paths)
|
|
127
|
+
print(f"\nfiles: {', '.join(sorted(str(x.relative_to(src)) for x in src.rglob('*') if x.is_file()))}\n")
|
|
128
|
+
print((src / "SKILL.md").read_text(encoding="utf-8", errors="ignore"))
|
|
129
|
+
elif a.cmd == "approve":
|
|
130
|
+
c = core.find(cands, a.id)
|
|
131
|
+
print(f"installed {c['id']} -> {core.approve(c, paths, a.accept_risk, a.update, fetch=github)}")
|
|
132
|
+
if on := switch.active(paths): # a new skill for an inactive persona stays out of view
|
|
133
|
+
names = [n for n in on if n != switch.CORE]
|
|
134
|
+
say_switch(switch.use(profile, paths, names, core.installed_skills(paths), not names), on, quiet=True)
|
|
135
|
+
elif a.cmd == "remove":
|
|
136
|
+
print(f"removed {core.remove(a.id, paths)}")
|
|
137
|
+
elif a.cmd == "rollback":
|
|
138
|
+
print(f"restored the previous {a.id} -> {core.rollback(a.id, paths)}")
|
|
139
|
+
elif a.cmd == "installed":
|
|
140
|
+
for cid, e in core._read(paths.lock, {}).items():
|
|
141
|
+
print(f"{cid} @{e['commit'][:10]} {e['installed_at']}")
|
|
142
|
+
elif a.cmd == "updates":
|
|
143
|
+
found = core.updates(paths)
|
|
144
|
+
for cid, commit, diff in found:
|
|
145
|
+
print(f"\n=== {cid} -> {commit[:10]}\n{diff}")
|
|
146
|
+
print(f"\n{len(found)} pinned skill(s) changed upstream." + (" Re-run `approve <id> --update` after reading the diff." if found else ""))
|
|
147
|
+
elif a.cmd == "history":
|
|
148
|
+
text = history.digest(history.episodes(days=a.days))
|
|
149
|
+
print(text or "no Claude Code history found in ~/.claude/projects")
|
|
150
|
+
print(f"(read from ~/.claude/projects on this machine, last {a.days} days; nothing is sent anywhere)")
|
|
151
|
+
elif a.cmd == "persona":
|
|
152
|
+
run_persona(a, profile, paths, cands)
|
|
153
|
+
elif a.cmd == "schedule":
|
|
154
|
+
files, message = schedule.here(paths.state)
|
|
155
|
+
schedule.write(files)
|
|
156
|
+
print(message)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def say_switch(done: dict, names: list[str], quiet: bool = False) -> None:
|
|
160
|
+
parts = [f"{label} {len(done[k])}: {', '.join(done[k][:8])}{' ...' if len(done[k]) > 8 else ''}"
|
|
161
|
+
for k, label in (("hidden", "hid"), ("shown", "showed"), ("plugins_off", "plugins off"), ("plugins_on", "plugins on"))
|
|
162
|
+
if done[k]]
|
|
163
|
+
if done["kept_yours"]:
|
|
164
|
+
parts.append(f"left your own skillOverrides alone for {', '.join(done['kept_yours'])}")
|
|
165
|
+
if quiet and not parts:
|
|
166
|
+
return
|
|
167
|
+
print((f"active: {', '.join(names)}" if names else "cleared: every skill and plugin as before skillscout switched them"))
|
|
168
|
+
for line in parts:
|
|
169
|
+
print(f" {line}")
|
|
170
|
+
if parts:
|
|
171
|
+
print("A new Claude Code session picks this up; in an open one, run /reload-skills and /reload-plugins.")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def start_claude(names: list[str], known: dict, paths: core.Paths) -> None:
|
|
175
|
+
claude = shutil.which("claude")
|
|
176
|
+
if not claude:
|
|
177
|
+
raise SystemExit("claude isn't on PATH; the switch is done, start Claude Code yourself")
|
|
178
|
+
cmd = [claude]
|
|
179
|
+
agent = known[names[0]]["agent"] if len(names) == 1 else None
|
|
180
|
+
if agent and (paths.agents / f"{agent}.md").exists():
|
|
181
|
+
cmd += ["--agent", agent]
|
|
182
|
+
sys.exit(subprocess.run(cmd, check=False).returncode)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def vocabulary(profile: dict, paths: core.Paths) -> dict[str, int]:
|
|
186
|
+
"""Terms skills use, from the cached index (downloaded first if there isn't one)."""
|
|
187
|
+
try:
|
|
188
|
+
if not paths.index.exists():
|
|
189
|
+
index.fetch(profile.get("index", {}).get("url") or index.URL, paths.index)
|
|
190
|
+
return history.vocabulary(index.load(paths.index)["skills"])
|
|
191
|
+
except index.Unavailable as e:
|
|
192
|
+
raise SystemExit(f"{e}\n(drafting keywords needs the skill index; or write them by hand)")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def run_persona(a, profile: dict, paths: core.Paths, cands: list[dict]) -> None:
|
|
196
|
+
known = persona.personas(profile)
|
|
197
|
+
if a.action == "add":
|
|
198
|
+
if a.keywords:
|
|
199
|
+
kw = {k.strip(): int(v) for k, v in (pair.rsplit("=", 1) for pair in a.keywords.split(","))}
|
|
200
|
+
else:
|
|
201
|
+
kw = history.keywords_from(a.about, vocabulary(profile, paths))
|
|
202
|
+
path = a.profile or USER_PROFILE
|
|
203
|
+
if not path.exists():
|
|
204
|
+
raise SystemExit(f"no profile at {path}; run `skillscout init` first")
|
|
205
|
+
print(persona.add(path, a.name, a.about, kw, a.agent, switch.draft_plugins(paths, kw)))
|
|
206
|
+
print(f"\nadded to {path}. Next: `skillscout discover`, then `skillscout persona build {a.name}`")
|
|
207
|
+
return
|
|
208
|
+
installed = core.installed_skills(paths)
|
|
209
|
+
if a.action in ("use", "start"):
|
|
210
|
+
if a.action == "use" and not a.names and not a.clear and not a.core:
|
|
211
|
+
on = switch.active(paths)
|
|
212
|
+
print(f"active: {', '.join(on)}" if on else "no persona active: Claude Code lists every skill")
|
|
213
|
+
return
|
|
214
|
+
names = [] if a.action == "use" and a.clear else a.names
|
|
215
|
+
core_only = a.action == "use" and a.core and not a.clear
|
|
216
|
+
say_switch(switch.use(profile, paths, names, installed, core_only), names or ([switch.CORE] if core_only else []))
|
|
217
|
+
if a.action == "start":
|
|
218
|
+
start_claude(names, known, paths)
|
|
219
|
+
return
|
|
220
|
+
if not known:
|
|
221
|
+
raise SystemExit("no personas yet: add one with `skillscout persona add <name> --about \"...\"`")
|
|
222
|
+
if a.action == "list":
|
|
223
|
+
on = switch.active(paths)
|
|
224
|
+
by = switch.claims(profile, switch.own_skills(installed))
|
|
225
|
+
for name, p in known.items():
|
|
226
|
+
fits = persona.fitting(installed, p["keywords"], p["max_skills"])
|
|
227
|
+
agent = paths.agents / f"{p['agent']}.md"
|
|
228
|
+
print(f"{name}: {p['description']}\n keywords: {', '.join(p['keywords'])}\n"
|
|
229
|
+
f" agent: {agent}{'' if agent.exists() else ' (not built yet)'}\n"
|
|
230
|
+
f" best installed skills: {', '.join(n for n, _ in fits) or 'none fit yet'}\n"
|
|
231
|
+
f" switches: {len(by[name])} of your skills, plugins: {', '.join(p['plugins']) or 'none'}"
|
|
232
|
+
+ (" (active)" if name in on else ""))
|
|
233
|
+
pending = [c for c in cands if core.recommended(c, profile) and c.get("personas", {}).get(name, 0) >= 0.3][:3]
|
|
234
|
+
if pending:
|
|
235
|
+
print(f" worth installing: {', '.join(c['id'] for c in pending)}")
|
|
236
|
+
return
|
|
237
|
+
names = list(known) if a.all else [a.name] if a.name in known else None
|
|
238
|
+
if not names:
|
|
239
|
+
raise SystemExit(f"name a persona ({', '.join(known)}) or pass --all")
|
|
240
|
+
for name in names:
|
|
241
|
+
p = known[name]
|
|
242
|
+
skills = [n for n, _ in persona.fitting(installed, p["keywords"], p["max_skills"])]
|
|
243
|
+
path = paths.agents / f"{p['agent']}.md"
|
|
244
|
+
if a.dry_run:
|
|
245
|
+
print(f"{path}: skills would be {', '.join(skills) or '(none fit yet)'}")
|
|
246
|
+
continue
|
|
247
|
+
what, added, dropped = persona.write_agent(path, p["agent"], p["description"], skills)
|
|
248
|
+
print(f"{what} {path}" + (f"; added {', '.join(added)}" if added else "") + (f"; dropped {', '.join(dropped)}" if dropped else "")
|
|
249
|
+
+ ("" if added or dropped or what == "created" else "; skills unchanged"))
|
|
250
|
+
print("Start one with `claude --agent <name>`, or ask for it by name in any session.")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
main()
|