@yonpark/skillhub-cli 0.1.2 → 0.2.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 CHANGED
@@ -1,167 +1,121 @@
1
- # SkillHub CLI (Gist Edition)
2
-
3
- SkillHub is a small CLI that syncs your local AI agent skills (managed by `npx skills`)
4
- with a single GitHub Gist. The Gist acts as a free, serverless backup so you can
5
- recreate your skill setup on any machine.
6
-
7
- ## Features
8
-
9
- - **Login** with a GitHub Personal Access Token (classic) that has `gist` scope
10
- - **Sync** local skills with a remote Gist:
11
- - Push new local skills up to Gist
12
- - Pull missing skills down from Gist and install them globally
13
- - Simple merge strategies (`union` and `latest`)
14
-
15
- ## Installation
16
-
17
- From the project root:
18
-
19
- ```bash
20
- npm install
21
- npm run build
22
- npm link # optional, to get a global `skillhub` command
23
- ```
24
-
25
- Alternatively, you can use `npx`:
26
-
27
- ```bash
28
- npm run build
29
- npx skillhub-cli <command>
30
- ```
31
-
32
- ## Commands
33
-
34
- ### `skillhub login`
35
-
36
- Registers a GitHub token locally so the CLI can talk to the Gist API.
37
-
38
- The command will:
39
-
40
- 1. Prompt for a token:
41
- - Create a **Personal Access Token (classic)** in GitHub
42
- - Grant at least the **`gist`** scope
43
- 2. Verify that the token is valid and can access Gists
44
- 3. Store the token locally using [`conf`](https://www.npmjs.com/package/conf)
45
-
46
- If login fails, the CLI explains that the token is invalid or missing gist
47
- permissions, and you can paste a new one.
48
-
49
- ### `skillhub sync`
50
-
51
- Synchronizes your local skills with the remote Gist.
52
-
53
- ```bash
54
- skillhub sync
55
- skillhub sync --strategy union
56
- skillhub sync --strategy latest
57
- ```
58
-
59
- #### What “sync” actually does
60
-
61
- 1. **Read local skills**
62
- - Runs `npx skills list -g` and parses the global skills:
63
- - For example:
64
- ```text
65
- Global Skills
66
-
67
- vercel-composition-patterns ~\.agents\skills\vercel-composition-patterns
68
- vercel-react-best-practices ~\.agents\skills\vercel-react-best-practices
69
- ```
70
- - Falls back to `npx skills generate-lock` + searching for a
71
- `skills-lock.json` file when necessary.
72
-
73
- 2. **Read remote skills (from Gist)**
74
- - Looks for a private Gist that contains `skillhub.json`
75
- - If not found, creates a new Gist
76
-
77
- 3. **Merge local vs remote**
78
- - **`union` (default)**:
79
- - Takes the set union of local and remote skill names
80
- - Any skills only on Gist are installed locally
81
- - Any skills only on local are written to Gist
82
- - **`latest`**:
83
- - Compares `updatedAt` timestamps in the payloads
84
- - If remote is newer:
85
- - Installs skills that are missing locally
86
- - Gist is treated as the source of truth
87
- - If local is newer (or timestamps are invalid):
88
- - Overwrites the Gist with local data
89
-
90
- 4. **Apply changes**
91
- - For skills that exist remotely but not locally, SkillHub runs:
92
- ```bash
93
- npx skills add vercel-labs/agent-skills --skill "<skill-name>" -g -y
94
- ```
95
- - For skills that exist locally but not in the Gist, the CLI updates
96
- `skillhub.json` in the Gist to reflect the union
97
-
98
- 5. **Summary output**
99
-
100
- After a run you’ll see a short summary like:
101
-
102
- ```text
103
- Uploaded 1 change, installed 0 skills
104
- Uploaded 0 changes, installed 4 skills (1 install failed – check logs)
105
- ```
106
-
107
- ## Gist Payload
108
-
109
- The Gist file `skillhub.json` currently uses a simple payload:
110
-
111
- ```json
112
- {
113
- "skills": ["vercel-composition-patterns", "vercel-react-best-practices"],
114
- "updatedAt": "2026-01-29T07:27:53.844Z"
115
- }
116
- ```
117
-
118
- - `skills`: list of skill names as reported by `skills list -g`
119
- - `updatedAt`: ISO timestamp when the payload was last written
120
-
121
- This keeps the format easy to inspect and edit directly in GitHub if needed.
122
-
123
- ## Typical Workflows
124
-
125
- ### First machine (create backup)
126
-
127
- ```bash
128
- # 1) Install skills using the official CLI
129
- npx skills add vercel-labs/agent-skills --all -g -y
130
-
131
- # 2) Login once
132
- skillhub login
133
-
134
- # 3) Push your current skills to Gist
135
- skillhub sync
136
- ```
137
-
138
- ### New machine (restore from backup)
139
-
140
- ```bash
141
- # 1) Install and build SkillHub CLI
142
- npm install
143
- npm run build
144
- npm link
145
-
146
- # 2) Login with the same GitHub account/token
147
- skillhub login
148
-
149
- # 3) Pull skills from Gist and install missing ones
150
- skillhub sync
151
- ```
152
-
153
- ## Notes and Limitations
154
-
155
- - The installer assumes your skills come from
156
- `vercel-labs/agent-skills` and calls:
157
- ```bash
158
- npx skills add vercel-labs/agent-skills --skill "<skill-name>" -g -y
159
- ```
160
- If a skill name doesn’t exist in that repo, the install will fail but
161
- the overall sync will continue.
162
- - The CLI currently focuses on **global** skills (`skills list -g`),
163
- not project-scoped skills.
164
- - Error messages try to surface both:
165
- - Which step failed (local list / lock file / install / Gist)
166
- - The underlying CLI output for easier debugging
167
-
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
+ ### Login
28
+
29
+ ```bash
30
+ skillhub login
31
+ ```
32
+
33
+ - Prompts for a GitHub PAT (classic, `gist` scope)
34
+ - Verifies token + Gist API access
35
+ - Stores token locally via `conf`
36
+
37
+ ### Sync
38
+
39
+ ```bash
40
+ skillhub sync
41
+ skillhub sync --strategy union
42
+ skillhub sync --strategy latest
43
+ skillhub sync --dry-run
44
+ skillhub sync --json
45
+ ```
46
+
47
+ - `union`: merge local and remote skills, install missing local skills, upload only when changed
48
+ - `latest`: compare `remote.updatedAt` and local `lastSyncAt`
49
+ - `--dry-run`: compute plan only (no install, no Gist update, no config write)
50
+ - `--json`: single JSON output object
51
+
52
+ ### Status
53
+
54
+ ```bash
55
+ skillhub status
56
+ skillhub status --json
57
+ ```
58
+
59
+ Shows:
60
+
61
+ - login state
62
+ - stored gist id
63
+ - last successful sync timestamp
64
+ - local skill count (if available)
65
+ - remote Gist API accessibility
66
+
67
+ ### Logout
68
+
69
+ ```bash
70
+ skillhub logout
71
+ skillhub logout --yes
72
+ skillhub logout --json
73
+ ```
74
+
75
+ Clears stored session keys: `githubToken`, `gistId`, `lastSyncAt`.
76
+
77
+ ## Payload Format
78
+
79
+ `skillhub.json` in Gist:
80
+
81
+ ```json
82
+ {
83
+ "skills": [
84
+ { "name": "vercel-composition-patterns", "source": "vercel-labs/agent-skills" }
85
+ ],
86
+ "updatedAt": "2026-01-29T07:27:53.844Z"
87
+ }
88
+ ```
89
+
90
+ Backward compatibility for legacy `skills: string[]` is preserved.
91
+
92
+ ## Local npm Credentials
93
+
94
+ If you need npm auth, copy `.npmrc.example` to `.npmrc` and keep it local only.
95
+
96
+ ## Release (Changesets)
97
+
98
+ Create release note:
99
+
100
+ ```bash
101
+ npm run changeset
102
+ ```
103
+
104
+ Version packages:
105
+
106
+ ```bash
107
+ npm run version-packages
108
+ ```
109
+
110
+ Publish:
111
+
112
+ ```bash
113
+ npm run release
114
+ ```
115
+
116
+ CI release workflow requires:
117
+
118
+ - `NPM_TOKEN` repository secret for npmjs publish of `@yonpark/skillhub-cli`.
119
+ - GitHub Packages publish uses `GITHUB_TOKEN` by default.
120
+ - If needed, add `GH_PACKAGES_TOKEN` (PAT with `write:packages`) for GitHub Packages.
121
+ - GitHub Packages target package is `@yw9142/skillhub-cli`.
@@ -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
+ }