skillscout-cli 0.1.0__tar.gz → 0.2.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.1.0 → skillscout_cli-0.2.0}/PKG-INFO +20 -13
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/README.md +19 -12
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/pyproject.toml +1 -1
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/cli.py +32 -10
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/core.py +203 -54
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/github.py +6 -5
- skillscout_cli-0.2.0/skillscout/index.py +89 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/profile.example.toml +11 -2
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/scan.py +5 -4
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/schedule.py +1 -1
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/score.py +26 -3
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/skill.py +16 -6
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout_cli.egg-info/PKG-INFO +20 -13
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout_cli.egg-info/SOURCES.txt +1 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/tests/test_skillscout.py +169 -25
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/LICENSE +0 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/setup.cfg +0 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout/__init__.py +0 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout_cli.egg-info/dependency_links.txt +0 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout_cli.egg-info/entry_points.txt +0 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout_cli.egg-info/requires.txt +0 -0
- {skillscout_cli-0.1.0 → skillscout_cli-0.2.0}/skillscout_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillscout-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Keep your agent's skills current: weekly discovery, upstream diffs, safety scans, pinned installs
|
|
5
5
|
Author: Akshit Gupta
|
|
6
6
|
License-Expression: MIT
|
|
@@ -53,7 +53,7 @@ Dynamic: license-file
|
|
|
53
53
|
```bash
|
|
54
54
|
claude plugin marketplace add akshit9162/skillscout && claude plugin install skillscout@skillscout
|
|
55
55
|
# or the CLI on its own
|
|
56
|
-
pipx install
|
|
56
|
+
pipx install skillscout-cli
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
AI tooling moves weekly. New skills, plugins and agent workflows land on GitHub every day, the ones you
|
|
@@ -104,33 +104,37 @@ summarises the report and shows you each skill's flags before asking to install
|
|
|
104
104
|
### As a CLI
|
|
105
105
|
|
|
106
106
|
```sh
|
|
107
|
-
pipx install
|
|
107
|
+
pipx install skillscout-cli # or: uv tool install skillscout-cli
|
|
108
108
|
skillscout init # writes ~/.skillscout/profile.toml
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
Edit `[keywords]` in `~/.skillscout/profile.toml` with the words from your own work, then:
|
|
112
112
|
|
|
113
113
|
```sh
|
|
114
|
-
skillscout discover #
|
|
114
|
+
skillscout discover # score this week's index; writes ~/.skillscout/reports/<date>.md
|
|
115
115
|
skillscout list # ranked; REC = recommended
|
|
116
116
|
skillscout show <id> # every flag plus the full SKILL.md, before you decide
|
|
117
117
|
skillscout approve <id> # copy into ~/.claude/skills and pin the commit
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
`<id>` is `owner/repo:skill-name`. The bare skill name works when it's unique.
|
|
120
|
+
`<id>` is `owner/repo:skill-name`. The bare skill name works when it's unique. If one repo has two skills with the same name, the second is `owner/repo:path/to/skill`.
|
|
121
121
|
|
|
122
122
|
## How it works
|
|
123
123
|
|
|
124
|
+
The crawl is the same for everyone, so it runs once a week in this repo's [index workflow](.github/workflows/index.yml) and is published as `index.json.gz`. `skillscout discover` downloads that one file (seconds, no GitHub token, your keywords never leave your machine) and scores it locally. `show` and `approve` fetch the skill and scan it again on your machine, so nothing is installed on the index's word. `discover --local` runs the whole crawl yourself instead.
|
|
125
|
+
|
|
126
|
+
Each published index carries a signed [build attestation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations): proof that this repo's index workflow built that exact file. Check one with `gh attestation verify ~/.skillscout/index.json.gz --repo akshit9162/skillscout`, or set `verify = true` under `[index]` and `discover` checks every download and refuses one that fails.
|
|
127
|
+
|
|
124
128
|
| Step | What happens |
|
|
125
129
|
|---|---|
|
|
126
|
-
| **Discover** | Runs your GitHub searches (topics like `claude-skills`, `agent-skills`) plus your trusted repos, then follows every repo linked from curated lists (your `lists`, plus any `awesome-*` repo it finds). Drops archived, stale (no push in a year), unpopular and huge repos. |
|
|
130
|
+
| **Discover** (weekly job, or `--local`) | Runs your GitHub searches (topics like `claude-skills`, `agent-skills`) plus your trusted repos, then follows every repo linked from curated lists (your `lists`, plus any `awesome-*` repo it finds). Drops archived, stale (no push in a year), unpopular and huge repos. |
|
|
127
131
|
| **Clone** | Sparse, shallow clones: only folders that contain a `SKILL.md`, minus media and archives, 16 repos at a time. Repos with no push since the last run aren't fetched again. Repo code is never run. |
|
|
128
132
|
| **Scan** | Static checks on every file in the skill, reported with file and line ([below](#what-gets-flagged)). A skill whose folder (git tree hash) and the scanner's rules are both unchanged keeps its last result instead of being read again. |
|
|
129
|
-
| **Score** | 65% relevance to your keywords, 20% stars, 15% freshness, plus a bonus for trusted repos. |
|
|
133
|
+
| **Score** (your machine) | 65% relevance to your keywords (phrases match in order, singular or plural), 20% stars, 15% freshness, plus a bonus for trusted repos. |
|
|
130
134
|
| **Conflicts** | Compared against your `~/.claude/skills` and plugin skills: same name, or a description close enough that both would fire on the same prompt. |
|
|
131
135
|
| **Report** | What's new since your last run, recommended skills, and relevant ones that are blocked and why. |
|
|
132
|
-
| **Approve** | You pick one. It's copied (symlinks dropped) and pinned in `~/.skillscout/skills.lock.json` with its commit and a content hash. |
|
|
133
|
-
| **Update** | `skillscout updates` diffs each pinned skill against upstream and applies nothing. Taking an update re-runs the scan. |
|
|
136
|
+
| **Approve** | You pick one. It's fetched and scanned again on your machine (that scan decides), then copied (symlinks dropped) and pinned in `~/.skillscout/skills.lock.json` with its commit and a content hash. |
|
|
137
|
+
| **Update** | `skillscout updates` diffs each pinned skill against upstream and applies nothing. Taking an update re-runs the scan, builds the new copy beside the old one and swaps them; if any step fails, the old copy and its pin stay. `skillscout rollback <id>` puts back the version an update replaced. |
|
|
134
138
|
|
|
135
139
|
## What gets flagged
|
|
136
140
|
|
|
@@ -146,6 +150,7 @@ High-risk skills need `--accept-risk`, which the bundled skill tells the agent n
|
|
|
146
150
|
|
|
147
151
|
- Nothing installs without an explicit `approve`.
|
|
148
152
|
- `approve` never overwrites a folder skillscout didn't install, and never follows symlinks.
|
|
153
|
+
- A skill can't name its way out of the skills folder: a `name:` like `../../x` falls back to the folder's own name, and a path that leads outside its repo is refused. The install name comes from the `SKILL.md` fetched on your machine, not from the index.
|
|
149
154
|
- Every install is pinned to a commit and a content hash.
|
|
150
155
|
- Updates are a diff first, and re-scanned before they replace anything. The test suite includes an upstream
|
|
151
156
|
update that slips in `sudo rm -rf /`, and checks that re-approval refuses it.
|
|
@@ -155,20 +160,22 @@ High-risk skills need `--accept-risk`, which the bundled skill tells the agent n
|
|
|
155
160
|
| Command | Does |
|
|
156
161
|
|---|---|
|
|
157
162
|
| `skillscout init` | Copy the example profile to `~/.skillscout/profile.toml` |
|
|
158
|
-
| `skillscout discover [--full] [--notify]` |
|
|
163
|
+
| `skillscout discover [--local [--full]] [--notify]` | Score the weekly index against your profile and write the report. `--local` crawls and scans GitHub on your machine instead; `--full` then ignores the caches; `--notify` sends a desktop notification (macOS, or Linux with `notify-send`) |
|
|
164
|
+
| `skillscout index [--out FILE]` | Crawl and scan the ecosystem and write the shared index (what the weekly job runs) |
|
|
159
165
|
| `skillscout list [--all] [-n N]` | Ranked candidates |
|
|
160
166
|
| `skillscout show <id>` | Flags, conflicts and the full SKILL.md |
|
|
161
|
-
| `skillscout approve <id> [--update] [--accept-risk]` | Install and pin |
|
|
162
|
-
| `skillscout installed` · `skillscout remove <id>` | Pinned skills · uninstall |
|
|
167
|
+
| `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
|
+
| `skillscout installed` · `skillscout remove <id>` · `skillscout rollback <id>` | Pinned skills · uninstall · undo the last update |
|
|
163
169
|
| `skillscout updates` | Diff pinned skills against upstream |
|
|
164
170
|
| `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 |
|
|
165
171
|
|
|
166
|
-
|
|
172
|
+
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.
|
|
167
173
|
|
|
168
174
|
## Limits
|
|
169
175
|
|
|
170
176
|
- The scanner catches known patterns. It can't prove a skill is safe, so read `show` before you approve.
|
|
171
177
|
- Relevance is keyword matching against your profile, not semantic search. Tune the weights.
|
|
178
|
+
- 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.
|
|
172
179
|
- Every installed skill's description loads into every agent session. Keep the total to a few dozen.
|
|
173
180
|
|
|
174
181
|
## Development
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
```bash
|
|
26
26
|
claude plugin marketplace add akshit9162/skillscout && claude plugin install skillscout@skillscout
|
|
27
27
|
# or the CLI on its own
|
|
28
|
-
pipx install
|
|
28
|
+
pipx install skillscout-cli
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
AI tooling moves weekly. New skills, plugins and agent workflows land on GitHub every day, the ones you
|
|
@@ -76,33 +76,37 @@ summarises the report and shows you each skill's flags before asking to install
|
|
|
76
76
|
### As a CLI
|
|
77
77
|
|
|
78
78
|
```sh
|
|
79
|
-
pipx install
|
|
79
|
+
pipx install skillscout-cli # or: uv tool install skillscout-cli
|
|
80
80
|
skillscout init # writes ~/.skillscout/profile.toml
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
Edit `[keywords]` in `~/.skillscout/profile.toml` with the words from your own work, then:
|
|
84
84
|
|
|
85
85
|
```sh
|
|
86
|
-
skillscout discover #
|
|
86
|
+
skillscout discover # score this week's index; writes ~/.skillscout/reports/<date>.md
|
|
87
87
|
skillscout list # ranked; REC = recommended
|
|
88
88
|
skillscout show <id> # every flag plus the full SKILL.md, before you decide
|
|
89
89
|
skillscout approve <id> # copy into ~/.claude/skills and pin the commit
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
`<id>` is `owner/repo:skill-name`. The bare skill name works when it's unique.
|
|
92
|
+
`<id>` is `owner/repo:skill-name`. The bare skill name works when it's unique. If one repo has two skills with the same name, the second is `owner/repo:path/to/skill`.
|
|
93
93
|
|
|
94
94
|
## How it works
|
|
95
95
|
|
|
96
|
+
The crawl is the same for everyone, so it runs once a week in this repo's [index workflow](.github/workflows/index.yml) and is published as `index.json.gz`. `skillscout discover` downloads that one file (seconds, no GitHub token, your keywords never leave your machine) and scores it locally. `show` and `approve` fetch the skill and scan it again on your machine, so nothing is installed on the index's word. `discover --local` runs the whole crawl yourself instead.
|
|
97
|
+
|
|
98
|
+
Each published index carries a signed [build attestation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations): proof that this repo's index workflow built that exact file. Check one with `gh attestation verify ~/.skillscout/index.json.gz --repo akshit9162/skillscout`, or set `verify = true` under `[index]` and `discover` checks every download and refuses one that fails.
|
|
99
|
+
|
|
96
100
|
| Step | What happens |
|
|
97
101
|
|---|---|
|
|
98
|
-
| **Discover** | Runs your GitHub searches (topics like `claude-skills`, `agent-skills`) plus your trusted repos, then follows every repo linked from curated lists (your `lists`, plus any `awesome-*` repo it finds). Drops archived, stale (no push in a year), unpopular and huge repos. |
|
|
102
|
+
| **Discover** (weekly job, or `--local`) | Runs your GitHub searches (topics like `claude-skills`, `agent-skills`) plus your trusted repos, then follows every repo linked from curated lists (your `lists`, plus any `awesome-*` repo it finds). Drops archived, stale (no push in a year), unpopular and huge repos. |
|
|
99
103
|
| **Clone** | Sparse, shallow clones: only folders that contain a `SKILL.md`, minus media and archives, 16 repos at a time. Repos with no push since the last run aren't fetched again. Repo code is never run. |
|
|
100
104
|
| **Scan** | Static checks on every file in the skill, reported with file and line ([below](#what-gets-flagged)). A skill whose folder (git tree hash) and the scanner's rules are both unchanged keeps its last result instead of being read again. |
|
|
101
|
-
| **Score** | 65% relevance to your keywords, 20% stars, 15% freshness, plus a bonus for trusted repos. |
|
|
105
|
+
| **Score** (your machine) | 65% relevance to your keywords (phrases match in order, singular or plural), 20% stars, 15% freshness, plus a bonus for trusted repos. |
|
|
102
106
|
| **Conflicts** | Compared against your `~/.claude/skills` and plugin skills: same name, or a description close enough that both would fire on the same prompt. |
|
|
103
107
|
| **Report** | What's new since your last run, recommended skills, and relevant ones that are blocked and why. |
|
|
104
|
-
| **Approve** | You pick one. It's copied (symlinks dropped) and pinned in `~/.skillscout/skills.lock.json` with its commit and a content hash. |
|
|
105
|
-
| **Update** | `skillscout updates` diffs each pinned skill against upstream and applies nothing. Taking an update re-runs the scan. |
|
|
108
|
+
| **Approve** | You pick one. It's fetched and scanned again on your machine (that scan decides), then copied (symlinks dropped) and pinned in `~/.skillscout/skills.lock.json` with its commit and a content hash. |
|
|
109
|
+
| **Update** | `skillscout updates` diffs each pinned skill against upstream and applies nothing. Taking an update re-runs the scan, builds the new copy beside the old one and swaps them; if any step fails, the old copy and its pin stay. `skillscout rollback <id>` puts back the version an update replaced. |
|
|
106
110
|
|
|
107
111
|
## What gets flagged
|
|
108
112
|
|
|
@@ -118,6 +122,7 @@ High-risk skills need `--accept-risk`, which the bundled skill tells the agent n
|
|
|
118
122
|
|
|
119
123
|
- Nothing installs without an explicit `approve`.
|
|
120
124
|
- `approve` never overwrites a folder skillscout didn't install, and never follows symlinks.
|
|
125
|
+
- A skill can't name its way out of the skills folder: a `name:` like `../../x` falls back to the folder's own name, and a path that leads outside its repo is refused. The install name comes from the `SKILL.md` fetched on your machine, not from the index.
|
|
121
126
|
- Every install is pinned to a commit and a content hash.
|
|
122
127
|
- Updates are a diff first, and re-scanned before they replace anything. The test suite includes an upstream
|
|
123
128
|
update that slips in `sudo rm -rf /`, and checks that re-approval refuses it.
|
|
@@ -127,20 +132,22 @@ High-risk skills need `--accept-risk`, which the bundled skill tells the agent n
|
|
|
127
132
|
| Command | Does |
|
|
128
133
|
|---|---|
|
|
129
134
|
| `skillscout init` | Copy the example profile to `~/.skillscout/profile.toml` |
|
|
130
|
-
| `skillscout discover [--full] [--notify]` |
|
|
135
|
+
| `skillscout discover [--local [--full]] [--notify]` | Score the weekly index against your profile and write the report. `--local` crawls and scans GitHub on your machine instead; `--full` then ignores the caches; `--notify` sends a desktop notification (macOS, or Linux with `notify-send`) |
|
|
136
|
+
| `skillscout index [--out FILE]` | Crawl and scan the ecosystem and write the shared index (what the weekly job runs) |
|
|
131
137
|
| `skillscout list [--all] [-n N]` | Ranked candidates |
|
|
132
138
|
| `skillscout show <id>` | Flags, conflicts and the full SKILL.md |
|
|
133
|
-
| `skillscout approve <id> [--update] [--accept-risk]` | Install and pin |
|
|
134
|
-
| `skillscout installed` · `skillscout remove <id>` | Pinned skills · uninstall |
|
|
139
|
+
| `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
|
+
| `skillscout installed` · `skillscout remove <id>` · `skillscout rollback <id>` | Pinned skills · uninstall · undo the last update |
|
|
135
141
|
| `skillscout updates` | Diff pinned skills against upstream |
|
|
136
142
|
| `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 |
|
|
137
143
|
|
|
138
|
-
|
|
144
|
+
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.
|
|
139
145
|
|
|
140
146
|
## Limits
|
|
141
147
|
|
|
142
148
|
- The scanner catches known patterns. It can't prove a skill is safe, so read `show` before you approve.
|
|
143
149
|
- Relevance is keyword matching against your profile, not semantic search. Tune the weights.
|
|
150
|
+
- 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.
|
|
144
151
|
- Every installed skill's description loads into every agent session. Keep the total to a few dozen.
|
|
145
152
|
|
|
146
153
|
## Development
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "skillscout-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Keep your agent's skills current: weekly discovery, upstream diffs, safety scans, pinned installs"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
readme = "README.md"
|
|
@@ -7,7 +7,7 @@ import subprocess
|
|
|
7
7
|
import sys
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from . import __doc__ as ABOUT, core, schedule
|
|
10
|
+
from . import __doc__ as ABOUT, core, github, index, schedule
|
|
11
11
|
|
|
12
12
|
EXAMPLE = Path(__file__).resolve().parent / "profile.example.toml"
|
|
13
13
|
USER_PROFILE = Path.home() / ".skillscout" / "profile.toml"
|
|
@@ -25,20 +25,27 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
25
25
|
p.add_argument("--profile", type=Path, default=None, help="default: ~/.skillscout/profile.toml, else the example")
|
|
26
26
|
sub = p.add_subparsers(dest="cmd", required=True)
|
|
27
27
|
sub.add_parser("init", help="copy the example profile to ~/.skillscout/profile.toml")
|
|
28
|
-
d = sub.add_parser("discover", help="
|
|
29
|
-
d.add_argument("--
|
|
28
|
+
d = sub.add_parser("discover", help="score this week's shared index against your profile and write a report")
|
|
29
|
+
d.add_argument("--local", action="store_true", help="crawl and scan GitHub on this machine instead of using the index")
|
|
30
|
+
d.add_argument("--full", action="store_true", help="with --local: ignore caches, fetch every repo and re-scan every skill")
|
|
30
31
|
d.add_argument("--notify", action="store_true", help="desktop notification with the summary (macOS, or Linux with notify-send)")
|
|
31
32
|
ls = sub.add_parser("list", help="show scored candidates")
|
|
32
33
|
ls.add_argument("--all", action="store_true", help="include zero-relevance skills")
|
|
33
34
|
ls.add_argument("-n", type=int, default=30)
|
|
34
|
-
|
|
35
|
+
ix = sub.add_parser("index", help="crawl and scan the ecosystem and write the shared index (what the weekly job runs)")
|
|
36
|
+
ix.add_argument("--out", type=Path, default=Path("index.json.gz"))
|
|
37
|
+
ix.add_argument("--full", action="store_true", help="ignore caches: fetch every repo and re-scan every skill")
|
|
38
|
+
sh = sub.add_parser("show", help="fetch a candidate, scan it here, print its flags and full SKILL.md")
|
|
35
39
|
sh.add_argument("id")
|
|
36
|
-
ap = sub.add_parser("approve", help="install one skill into ~/.claude/skills and pin it")
|
|
40
|
+
ap = sub.add_parser("approve", help="install one skill (into ~/.claude/skills unless [install] dir says otherwise) and pin it")
|
|
37
41
|
ap.add_argument("id")
|
|
42
|
+
ap.add_argument("--to", type=Path, help="install into this skills folder instead, e.g. another agent's")
|
|
38
43
|
ap.add_argument("--accept-risk", action="store_true", help="install despite high-risk flags")
|
|
39
44
|
ap.add_argument("--update", action="store_true", help="replace a copy skillscout installed earlier")
|
|
40
45
|
rm = sub.add_parser("remove", help="uninstall a skill skillscout installed")
|
|
41
46
|
rm.add_argument("id")
|
|
47
|
+
rb = sub.add_parser("rollback", help="restore the copy the last `approve --update` replaced")
|
|
48
|
+
rb.add_argument("id")
|
|
42
49
|
sub.add_parser("installed", help="show pinned skills")
|
|
43
50
|
sub.add_parser("updates", help="diff pinned skills against upstream; applies nothing")
|
|
44
51
|
sub.add_parser("schedule", help="set up weekly discovery for this OS (you turn it on)")
|
|
@@ -49,36 +56,51 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
49
56
|
if USER_PROFILE.exists():
|
|
50
57
|
raise SystemExit(f"{USER_PROFILE} already exists")
|
|
51
58
|
USER_PROFILE.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
-
USER_PROFILE.write_text(EXAMPLE.read_text())
|
|
59
|
+
USER_PROFILE.write_text(EXAMPLE.read_text(encoding="utf-8"), encoding="utf-8")
|
|
53
60
|
print(f"wrote {USER_PROFILE}; edit [keywords] to match your work, then run `skillscout discover`")
|
|
54
61
|
return
|
|
55
62
|
profile = core.load_profile(a.profile or (USER_PROFILE if USER_PROFILE.exists() else EXAMPLE))
|
|
63
|
+
if folder := getattr(a, "to", None) or profile.get("install", {}).get("dir"):
|
|
64
|
+
paths.skills = Path(folder).expanduser()
|
|
56
65
|
cands = core._read(paths.candidates, [])
|
|
57
66
|
|
|
58
67
|
if a.cmd == "discover":
|
|
59
|
-
|
|
68
|
+
url = profile.get("index", {}).get("url", index.URL)
|
|
69
|
+
if a.local or not url:
|
|
70
|
+
cands = core.discover(profile, paths, full=a.full)
|
|
71
|
+
else:
|
|
72
|
+
try:
|
|
73
|
+
cands = core.discover_from_index(profile, paths, url, profile.get("index", {}).get("verify", False))
|
|
74
|
+
except index.Unavailable as e:
|
|
75
|
+
raise SystemExit(str(e))
|
|
60
76
|
out, summary = core.report(cands, profile, paths)
|
|
61
77
|
print(f"{summary}\nreport: {out}")
|
|
62
78
|
if a.notify:
|
|
63
79
|
notify(summary)
|
|
80
|
+
elif a.cmd == "index":
|
|
81
|
+
records = core.crawl(profile["search"], paths, full=a.full)
|
|
82
|
+
data = index.build(records, a.out, core.RULES)
|
|
83
|
+
print(f"wrote {a.out}: {len(data['skills'])} skills from {len({r['repo'] for r in records})} repos")
|
|
64
84
|
elif a.cmd == "list":
|
|
65
85
|
rows = [c for c in cands if a.all or c["relevance"] > 0][: a.n]
|
|
66
86
|
for c in rows:
|
|
67
87
|
tag = "installed" if c["installed"] else ("REC" if core.recommended(c, profile) else "")
|
|
68
88
|
print(f"{c['score']:.2f} {c['risk'] or '-':6} {tag:9} {c['id']}")
|
|
69
89
|
elif a.cmd == "show":
|
|
70
|
-
c = core.find(cands, a.id)
|
|
90
|
+
c = core.ensure_local(core.find(cands, a.id), paths)
|
|
71
91
|
print(json.dumps({k: c[k] for k in ("id", "score", "stars", "license", "commit", "risk", "matched", "conflicts")}, indent=2))
|
|
72
92
|
for f in c["flags"]:
|
|
73
93
|
print(f" [{f['level']}] {f['reason']} — {f['where']}")
|
|
74
94
|
src = core.source_dir(c, paths)
|
|
75
95
|
print(f"\nfiles: {', '.join(sorted(str(x.relative_to(src)) for x in src.rglob('*') if x.is_file()))}\n")
|
|
76
|
-
print((src / "SKILL.md").read_text(errors="ignore"))
|
|
96
|
+
print((src / "SKILL.md").read_text(encoding="utf-8", errors="ignore"))
|
|
77
97
|
elif a.cmd == "approve":
|
|
78
98
|
c = core.find(cands, a.id)
|
|
79
|
-
print(f"installed {c['id']} -> {core.approve(c, paths, a.accept_risk, a.update)}")
|
|
99
|
+
print(f"installed {c['id']} -> {core.approve(c, paths, a.accept_risk, a.update, fetch=github)}")
|
|
80
100
|
elif a.cmd == "remove":
|
|
81
101
|
print(f"removed {core.remove(a.id, paths)}")
|
|
102
|
+
elif a.cmd == "rollback":
|
|
103
|
+
print(f"restored the previous {a.id} -> {core.rollback(a.id, paths)}")
|
|
82
104
|
elif a.cmd == "installed":
|
|
83
105
|
for cid, e in core._read(paths.lock, {}).items():
|
|
84
106
|
print(f"{cid} @{e['commit'][:10]} {e['installed_at']}")
|