@yonpark/skillhub-cli 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -185
- package/dist/commands/logout.js +44 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/sync.js +467 -279
- package/dist/core/syncCore.js +145 -0
- package/dist/index.js +106 -9
- package/dist/service/config.js +26 -0
- package/dist/service/gistService.js +25 -15
- package/dist/service/skillsService.js +227 -0
- package/dist/utils/output.js +10 -0
- package/dist/utils/retry.js +69 -0
- package/package.json +55 -48
package/README.md
CHANGED
|
@@ -1,185 +1,136 @@
|
|
|
1
|
-
# SkillHub CLI
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
## Typical Workflows
|
|
138
|
-
|
|
139
|
-
### First machine (create backup)
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
# 1) Install skills using the official CLI
|
|
143
|
-
npx skills add vercel-labs/agent-skills --all -g -y
|
|
144
|
-
|
|
145
|
-
# 2) Login once
|
|
146
|
-
skillhub login
|
|
147
|
-
|
|
148
|
-
# 3) Push your current skills to Gist
|
|
149
|
-
skillhub sync
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### New machine (restore from backup)
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
# 1) Install and build SkillHub CLI
|
|
156
|
-
npm install
|
|
157
|
-
npm run build
|
|
158
|
-
npm link
|
|
159
|
-
|
|
160
|
-
# 2) Login with the same GitHub account/token
|
|
161
|
-
skillhub login
|
|
162
|
-
|
|
163
|
-
# 3) Pull skills from Gist and install missing ones
|
|
164
|
-
skillhub sync
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## Notes and Limitations
|
|
168
|
-
|
|
169
|
-
- Local discovery uses `npx skills list -g` as the primary source. That output
|
|
170
|
-
usually does **not** include `owner/repo`, so SkillHub may fall back to a
|
|
171
|
-
default source (`vercel-labs/agent-skills`) unless it can infer a source from
|
|
172
|
-
`skills-lock.json`.
|
|
173
|
-
- For installs, SkillHub uses the stored `source` per skill:
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
npx skills add "<owner>/<repo>" --skill "<skill-name>" -g -y
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
If a skill name doesn’t exist in that repo, the install will fail but the
|
|
180
|
-
overall sync will continue.
|
|
181
|
-
- The CLI currently focuses on **global** skills (`skills list -g`),
|
|
182
|
-
not project-scoped skills.
|
|
183
|
-
- Error messages try to surface both:
|
|
184
|
-
- Which step failed (local list / lock file / install / Gist)
|
|
185
|
-
- The underlying CLI output for easier debugging
|
|
1
|
+
# SkillHub CLI
|
|
2
|
+
|
|
3
|
+
Sync local AI agent skills (`npx skills`) with a private GitHub Gist.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run build
|
|
10
|
+
npm link
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run without linking:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node bin/skillhub.js <command>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Published package:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @yw9142/skillhub-cli <command>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
### Auth
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
skillhub auth login
|
|
31
|
+
skillhub auth status
|
|
32
|
+
skillhub auth status --json
|
|
33
|
+
skillhub auth logout
|
|
34
|
+
skillhub auth logout --yes
|
|
35
|
+
skillhub auth logout --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `auth login`: prompts for a GitHub PAT (classic, `gist` scope), verifies access, and stores it via `conf`
|
|
39
|
+
- `auth status`: shows login state, gist id, last successful sync timestamp, local skill count, and Gist API accessibility
|
|
40
|
+
- `auth logout`: clears stored session keys (`githubToken`, `gistId`, `lastSyncAt`)
|
|
41
|
+
|
|
42
|
+
### Sync
|
|
43
|
+
|
|
44
|
+
`skillhub sync` requires a subcommand.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
skillhub sync pull
|
|
48
|
+
skillhub sync push
|
|
49
|
+
skillhub sync merge
|
|
50
|
+
skillhub sync auto
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Common options:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
--dry-run # compute plan only (no install/remove/upload/config write)
|
|
57
|
+
--json # single JSON output object
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`pull` only:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
--yes # skip deletion confirmation prompt
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Mode behavior:
|
|
67
|
+
|
|
68
|
+
- `pull`: mirror remote to local (remote -> local). Installs missing local skills and removes extra local skills.
|
|
69
|
+
- `push`: mirror local to remote (local -> remote). Updates/creates remote Gist payload from local skills.
|
|
70
|
+
- `merge`: union local + remote, installs missing local skills, uploads when remote differs.
|
|
71
|
+
- `auto`: compare `remote.updatedAt` and local `lastSyncAt`; install from remote when remote is newer, otherwise upload local when needed.
|
|
72
|
+
|
|
73
|
+
## Migration (Breaking Changes in 0.3.0)
|
|
74
|
+
|
|
75
|
+
Removed commands:
|
|
76
|
+
|
|
77
|
+
- `skillhub login`
|
|
78
|
+
- `skillhub status`
|
|
79
|
+
- `skillhub logout`
|
|
80
|
+
- `skillhub sync --strategy ...`
|
|
81
|
+
- `skillhub sync` (without a subcommand)
|
|
82
|
+
|
|
83
|
+
New mapping:
|
|
84
|
+
|
|
85
|
+
- `skillhub login` -> `skillhub auth login`
|
|
86
|
+
- `skillhub status` -> `skillhub auth status`
|
|
87
|
+
- `skillhub logout` -> `skillhub auth logout`
|
|
88
|
+
- `skillhub sync --strategy union` -> `skillhub sync merge`
|
|
89
|
+
- `skillhub sync --strategy latest` -> `skillhub sync auto`
|
|
90
|
+
|
|
91
|
+
## Payload Format
|
|
92
|
+
|
|
93
|
+
`skillhub.json` in Gist:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"skills": [
|
|
98
|
+
{ "name": "vercel-composition-patterns", "source": "vercel-labs/agent-skills" }
|
|
99
|
+
],
|
|
100
|
+
"updatedAt": "2026-01-29T07:27:53.844Z"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Backward compatibility for legacy `skills: string[]` payloads is preserved.
|
|
105
|
+
|
|
106
|
+
## Local npm Credentials
|
|
107
|
+
|
|
108
|
+
If you need npm auth, copy `.npmrc.example` to `.npmrc` and keep it local only.
|
|
109
|
+
|
|
110
|
+
## Release (Changesets)
|
|
111
|
+
|
|
112
|
+
Create release note:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm run changeset
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Version packages:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm run version-packages
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Publish:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run release
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
CI release workflow publishes to GitHub Packages only:
|
|
131
|
+
|
|
132
|
+
- GitHub Packages publish uses `GITHUB_TOKEN` by default.
|
|
133
|
+
- If needed, add `GH_PACKAGES_TOKEN` (PAT with `write:packages`) for GitHub Packages.
|
|
134
|
+
- GitHub Packages target package is `@yw9142/skillhub-cli`.
|
|
135
|
+
|
|
136
|
+
npmjs publish is manual.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearStoredSession = clearStoredSession;
|
|
4
|
+
exports.runLogout = runLogout;
|
|
5
|
+
const config_1 = require("../service/config");
|
|
6
|
+
const output_1 = require("../utils/output");
|
|
7
|
+
const REMOVED_KEYS = ["githubToken", "gistId", "lastSyncAt"];
|
|
8
|
+
function formatLogoutSummary(summary) {
|
|
9
|
+
if (!summary.cleared) {
|
|
10
|
+
return "Logout cancelled.";
|
|
11
|
+
}
|
|
12
|
+
return `Logout completed. Removed keys: ${summary.removedKeys.join(", ")}`;
|
|
13
|
+
}
|
|
14
|
+
async function clearStoredSession(store = config_1.configStore) {
|
|
15
|
+
await store.clearSession();
|
|
16
|
+
return [...REMOVED_KEYS];
|
|
17
|
+
}
|
|
18
|
+
async function runLogout(options = {}) {
|
|
19
|
+
const asJson = options.json === true;
|
|
20
|
+
const force = options.yes === true;
|
|
21
|
+
if (!force) {
|
|
22
|
+
const { default: inquirer } = await import("inquirer");
|
|
23
|
+
const { confirm } = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: "confirm",
|
|
26
|
+
name: "confirm",
|
|
27
|
+
default: false,
|
|
28
|
+
message: "Delete stored GitHub session data (token, gistId, lastSyncAt)?",
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
if (!confirm) {
|
|
32
|
+
const cancelled = { cleared: false, removedKeys: [] };
|
|
33
|
+
(0, output_1.emitOutput)(cancelled, asJson, formatLogoutSummary);
|
|
34
|
+
return cancelled;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const removedKeys = await clearStoredSession();
|
|
38
|
+
const summary = {
|
|
39
|
+
cleared: true,
|
|
40
|
+
removedKeys,
|
|
41
|
+
};
|
|
42
|
+
(0, output_1.emitOutput)(summary, asJson, formatLogoutSummary);
|
|
43
|
+
return summary;
|
|
44
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runStatus = runStatus;
|
|
4
|
+
const config_1 = require("../service/config");
|
|
5
|
+
const gistService_1 = require("../service/gistService");
|
|
6
|
+
const skillsService_1 = require("../service/skillsService");
|
|
7
|
+
const output_1 = require("../utils/output");
|
|
8
|
+
function formatStatusSummary(summary) {
|
|
9
|
+
const lines = [
|
|
10
|
+
`loggedIn=${summary.loggedIn}`,
|
|
11
|
+
`gistId=${summary.gistId ?? "none"}`,
|
|
12
|
+
`lastSyncAt=${summary.lastSyncAt ?? "none"}`,
|
|
13
|
+
`localSkillCount=${summary.localSkillCount === null ? "unavailable" : String(summary.localSkillCount)}`,
|
|
14
|
+
`remoteAccessible=${summary.remoteAccessible === null
|
|
15
|
+
? "unknown"
|
|
16
|
+
: String(summary.remoteAccessible)}`,
|
|
17
|
+
];
|
|
18
|
+
if (summary.errors.length > 0) {
|
|
19
|
+
lines.push(`errors=${summary.errors.length}`);
|
|
20
|
+
for (const error of summary.errors) {
|
|
21
|
+
lines.push(`- ${error}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return lines.join("\n");
|
|
25
|
+
}
|
|
26
|
+
async function runStatus(options = {}) {
|
|
27
|
+
const asJson = options.json === true;
|
|
28
|
+
const token = await config_1.configStore.getToken();
|
|
29
|
+
const gistId = await config_1.configStore.getGistId();
|
|
30
|
+
const lastSyncAt = await config_1.configStore.getLastSyncAt();
|
|
31
|
+
const summary = {
|
|
32
|
+
loggedIn: Boolean(token),
|
|
33
|
+
gistId: gistId ?? null,
|
|
34
|
+
lastSyncAt: lastSyncAt ?? null,
|
|
35
|
+
localSkillCount: null,
|
|
36
|
+
remoteAccessible: token ? null : false,
|
|
37
|
+
errors: [],
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const localSkills = await (0, skillsService_1.getLocalSkills)();
|
|
41
|
+
summary.localSkillCount = localSkills.length;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
summary.errors.push(`Failed to read local skills: ${message}`);
|
|
46
|
+
}
|
|
47
|
+
if (token) {
|
|
48
|
+
try {
|
|
49
|
+
const octokit = (0, gistService_1.createOctokit)(token);
|
|
50
|
+
await (0, gistService_1.checkGistAccess)(octokit);
|
|
51
|
+
summary.remoteAccessible = true;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
summary.remoteAccessible = false;
|
|
56
|
+
summary.errors.push(`Failed to access GitHub Gist API: ${message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
(0, output_1.emitOutput)(summary, asJson, formatStatusSummary);
|
|
60
|
+
return summary;
|
|
61
|
+
}
|