hubskillz 0.1.0 → 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/LICENSE +21 -0
- package/README.md +72 -12
- package/dist/index.js +639 -135
- package/package.json +27 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jérôme Desmares
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,41 +1,69 @@
|
|
|
1
1
|
# hubskillz
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/hubskillz)
|
|
4
|
+
[](https://github.com/JejeDurden2/hubskillz/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/JejeDurden2/hubskillz/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Keep the Claude Code skills on your machine in sync with your organization's approved directory.
|
|
8
|
+
|
|
9
|
+
`hubskillz` inventories `~/.claude/skills` and `<repo>/.claude/skills`, reports the state of every skill against the directory, and installs the approved versions after showing you the plan. Skills you customized are never overwritten unless you ask.
|
|
4
10
|
|
|
5
11
|
## Install
|
|
6
12
|
|
|
7
13
|
```sh
|
|
8
14
|
npm i -g hubskillz
|
|
9
|
-
# or
|
|
10
|
-
npx hubskillz
|
|
15
|
+
# or without installing
|
|
16
|
+
npx hubskillz help
|
|
11
17
|
```
|
|
12
18
|
|
|
13
19
|
Requires Node 22 or newer.
|
|
14
20
|
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
hubskillz login # paste a device token from Settings, Device tokens
|
|
25
|
+
hubskillz status # what is installed, and how it compares
|
|
26
|
+
hubskillz sync --all # install the approved set, adopt what the directory lacks
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The bare `hubskillz` command prints this quickstart, with your progress, until the first sync completes.
|
|
30
|
+
|
|
15
31
|
## Commands
|
|
16
32
|
|
|
17
33
|
```
|
|
18
|
-
hubskillz login
|
|
19
|
-
hubskillz
|
|
20
|
-
hubskillz
|
|
21
|
-
hubskillz
|
|
34
|
+
hubskillz login [--token TOKEN] [--base-url URL]
|
|
35
|
+
hubskillz logout
|
|
36
|
+
hubskillz status [--path DIR] [--yes]
|
|
37
|
+
hubskillz sync [--path DIR] [--all] [--adopt] [--yes] [--dry-run] [--force]
|
|
38
|
+
hubskillz push <skill-dir> [-m MESSAGE]
|
|
39
|
+
hubskillz projects [add|remove [DIR] | discover [--yes] | list]
|
|
40
|
+
hubskillz help [command]
|
|
22
41
|
hubskillz --version
|
|
23
42
|
```
|
|
24
43
|
|
|
44
|
+
Every command accepts `--help`.
|
|
45
|
+
|
|
25
46
|
### login
|
|
26
47
|
|
|
27
|
-
Prompts for a device token (create one in the web app at `/app/settings/tokens`), verifies it against the server and writes it to the config file.
|
|
48
|
+
Prompts for a device token (create one in the web app at `/app/settings/tokens`), verifies it against the server and writes it to the config file. `--token` skips the prompt. When stdin is a pipe, the token is read from it.
|
|
49
|
+
|
|
50
|
+
### logout
|
|
51
|
+
|
|
52
|
+
Removes the config file, so the token and the registered projects.
|
|
28
53
|
|
|
29
54
|
### status
|
|
30
55
|
|
|
31
|
-
Scans the
|
|
56
|
+
Scans the global root and every registered project (running `projects discover` first when none is registered), sends each inventory and prints the state of every skill: `synced`, `drifted`, `customized`, `missing` or `unmanaged`. Reads only.
|
|
57
|
+
|
|
58
|
+
What the inventory contains: for every skill, its name, content hash and per-file hashes and sizes. Skills installed from skills.sh (found in the lock file) travel as hashes only, the server fetches their content upstream. Private skills (no upstream) also ship the content of their files, up to 200,000 characters per skill, so the web app can adopt them into the directory. The server keeps that snapshot with the installation and replaces it on every new inventory.
|
|
32
59
|
|
|
33
60
|
### sync
|
|
34
61
|
|
|
35
62
|
Installs missing skills and updates drifted ones to the approved version. Shows the plan first and asks for confirmation. Skills you changed locally are left untouched unless you pass `--force`.
|
|
36
63
|
|
|
37
64
|
- `--path DIR`: project directory to act on. Default: the current directory.
|
|
38
|
-
- `--all`: act on the global root
|
|
65
|
+
- `--all`: act on the global root, the project root and every registered project. With nothing registered yet, it first scans your home directory for repos with `.claude/skills` and asks which ones to register (see `projects discover`). Default: the project root when `DIR/.claude/skills` exists, else the global root.
|
|
66
|
+
- `--adopt`: add every importable skill found on this surface to the directory as an approved version, without asking. Importable means installed here and absent from the directory: an upstream skill is pinned at its installed hash, a private skill is created from its snapshot. Maintainer only, other roles get a notice and keep syncing. Without the flag, an interactive run asks the same question once; `--yes` alone answers no and `--dry-run` skips it.
|
|
39
67
|
- `--yes`, `-y`: apply without asking.
|
|
40
68
|
- `--dry-run`: print the plan and stop.
|
|
41
69
|
- `--force`: overwrite locally customized skills.
|
|
@@ -44,13 +72,45 @@ Installs missing skills and updates drifted ones to the approved version. Shows
|
|
|
44
72
|
|
|
45
73
|
Uploads a skill directory as a draft version for review. `-m`, `--message` attaches a message to the draft.
|
|
46
74
|
|
|
75
|
+
### projects
|
|
76
|
+
|
|
77
|
+
Registers repos so `sync --all` and `status` cover them from anywhere, in one run. `add` records the current directory (or `DIR`), which must contain `.claude/skills`; `remove` forgets it; `list` prints them; `discover` scans your home directory (three levels deep, skipping `node_modules`, `.git`, `Library` and hidden folders) for repos with `.claude/skills` and lets you pick which ones to register, `--yes` registers them all. Without a TTY the prompt selects everything. A registered path that no longer has a skills root is skipped with a note.
|
|
78
|
+
|
|
47
79
|
## Surfaces
|
|
48
80
|
|
|
49
81
|
- Global root: `~/.claude/skills`
|
|
50
82
|
- Project root: `<project>/.claude/skills`
|
|
51
83
|
|
|
84
|
+
A symlinked skill directory (the skills.sh canonical copy under `~/.agents/skills`) is written through, so every agent on the machine sees the approved version. Links that leave your home directory are refused.
|
|
85
|
+
|
|
86
|
+
## CI and containers
|
|
87
|
+
|
|
88
|
+
Set `HUBSKILLZ_TOKEN` and no config file is needed:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
HUBSKILLZ_TOKEN=... npx hubskillz sync --all --yes
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Prompts are skipped when stdin is not a TTY: `sync` needs `--yes` to apply, `projects discover` registers everything found.
|
|
95
|
+
|
|
52
96
|
## Configuration
|
|
53
97
|
|
|
54
|
-
|
|
98
|
+
| Source | What |
|
|
99
|
+
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| `~/.hubskillz/config.json` | `{ "baseUrl", "token", "machineId", "projects", "firstSyncAt" }`, mode `0600`. `login` writes it, `hubskillz projects` edits `projects`, the first completed `sync` sets `firstSyncAt`. |
|
|
101
|
+
| `HUBSKILLZ_TOKEN` | Device token. Takes precedence over the file. |
|
|
102
|
+
| `HUBSKILLZ_BASE_URL` | Server URL for self-hosted instances. |
|
|
103
|
+
| `NO_COLOR` | Disables colours. Colours are also off when stdout is not a TTY. |
|
|
104
|
+
|
|
105
|
+
The server URL resolves in this order: `--base-url` flag, `HUBSKILLZ_BASE_URL`, the config file, then `https://hubskillz.com`.
|
|
106
|
+
|
|
107
|
+
## Exit codes
|
|
108
|
+
|
|
109
|
+
`0` on success, `1` on any failure. The failure is one line on stderr, prefixed with `hubskillz:`.
|
|
110
|
+
|
|
111
|
+
## Links
|
|
55
112
|
|
|
56
|
-
|
|
113
|
+
- Docs: https://hubskillz.com/docs/cli
|
|
114
|
+
- Changelog: https://github.com/JejeDurden2/hubskillz/blob/main/CHANGELOG.md
|
|
115
|
+
- Issues: https://github.com/JejeDurden2/hubskillz/issues
|
|
116
|
+
- License: MIT
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,14 @@ function contentHash(files) {
|
|
|
19
19
|
return hash2.digest("hex");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// ../shared/src/cli/skill-file.ts
|
|
23
|
+
var BINARY = /\.(png|jpe?g|gif|webp|svg|ico|pdf|zip|gz|tar|woff2?|ttf|otf|mp[34]|wav|bin|exe|dylib|so)$/i;
|
|
24
|
+
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
25
|
+
function isSkillFile(path) {
|
|
26
|
+
const segments = path.split("/");
|
|
27
|
+
return !segments.some((s) => s.startsWith(".") || SKIPPED.has(s)) && !BINARY.test(path);
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
23
31
|
var external_exports = {};
|
|
24
32
|
__export(external_exports, {
|
|
@@ -14550,10 +14558,12 @@ var skillFilePathSchema = external_exports.string().min(1).max(200).refine(
|
|
|
14550
14558
|
var MAX_FILES_PER_SKILL = 100;
|
|
14551
14559
|
var MAX_FILE_CONTENT_CHARS = 2e5;
|
|
14552
14560
|
var MAX_SKILLS_PER_REQUEST = 500;
|
|
14561
|
+
var MAX_SNAPSHOT_CHARS = MAX_FILE_CONTENT_CHARS;
|
|
14553
14562
|
var inventoryFileSchema = external_exports.object({
|
|
14554
14563
|
path: skillFilePathSchema,
|
|
14555
14564
|
hash: external_exports.string().min(1),
|
|
14556
|
-
size: external_exports.number().int().nonnegative()
|
|
14565
|
+
size: external_exports.number().int().nonnegative(),
|
|
14566
|
+
content: external_exports.string().max(MAX_FILE_CONTENT_CHARS).optional()
|
|
14557
14567
|
});
|
|
14558
14568
|
var skillFileSchema = external_exports.object({
|
|
14559
14569
|
path: skillFilePathSchema,
|
|
@@ -14631,6 +14641,11 @@ var approvedSkillSchema = external_exports.object({
|
|
|
14631
14641
|
var approvedResponseSchema = external_exports.object({
|
|
14632
14642
|
skills: external_exports.array(approvedSkillSchema)
|
|
14633
14643
|
});
|
|
14644
|
+
var adoptRequestSchema = external_exports.object({ surfaceId: external_exports.string().min(1) });
|
|
14645
|
+
var adoptResponseSchema = external_exports.object({
|
|
14646
|
+
adopted: external_exports.array(external_exports.string()),
|
|
14647
|
+
skipped: external_exports.array(external_exports.object({ name: external_exports.string(), code: external_exports.string() }))
|
|
14648
|
+
});
|
|
14634
14649
|
var draftRequestSchema = external_exports.object({
|
|
14635
14650
|
name: skillNameSchema,
|
|
14636
14651
|
files: external_exports.array(skillFileSchema).min(1).max(MAX_FILES_PER_SKILL),
|
|
@@ -14744,7 +14759,7 @@ async function apiRequest(request) {
|
|
|
14744
14759
|
const message = decodeBody(text, apiErrorSchema)?.message ?? `${response.status} ${response.statusText}`;
|
|
14745
14760
|
return Result.fail(
|
|
14746
14761
|
new CliError(
|
|
14747
|
-
response.status === 401 ? "UNAUTHORIZED" : "HTTP",
|
|
14762
|
+
response.status === 401 ? "UNAUTHORIZED" : response.status === 403 ? "FORBIDDEN" : "HTTP",
|
|
14748
14763
|
`${request.method} ${request.path} failed: ${message}`
|
|
14749
14764
|
)
|
|
14750
14765
|
);
|
|
@@ -14767,14 +14782,18 @@ function decodeBody(text, schema) {
|
|
|
14767
14782
|
|
|
14768
14783
|
// src/config.ts
|
|
14769
14784
|
import { randomUUID } from "node:crypto";
|
|
14770
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
14771
|
-
import { homedir } from "node:os";
|
|
14785
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
14786
|
+
import { homedir, hostname as hostname3 } from "node:os";
|
|
14772
14787
|
import { join } from "node:path";
|
|
14773
14788
|
var DEFAULT_BASE_URL = "https://hubskillz.com";
|
|
14774
14789
|
var configSchema = external_exports.object({
|
|
14775
14790
|
baseUrl: external_exports.string().min(1),
|
|
14776
14791
|
token: external_exports.string().min(1),
|
|
14777
|
-
machineId: external_exports.string().min(1)
|
|
14792
|
+
machineId: external_exports.string().min(1),
|
|
14793
|
+
/** Absolute repo paths that `--all` includes next to the global root. */
|
|
14794
|
+
projects: external_exports.array(external_exports.string()).default([]),
|
|
14795
|
+
/** ISO date of the first completed `sync`. Absent until then: the quickstart shows. */
|
|
14796
|
+
firstSyncAt: external_exports.string().optional()
|
|
14778
14797
|
});
|
|
14779
14798
|
function configDir() {
|
|
14780
14799
|
return join(homedir(), ".hubskillz");
|
|
@@ -14783,16 +14802,30 @@ function configPath() {
|
|
|
14783
14802
|
return join(configDir(), "config.json");
|
|
14784
14803
|
}
|
|
14785
14804
|
async function readConfig() {
|
|
14805
|
+
const envToken = process.env["HUBSKILLZ_TOKEN"];
|
|
14786
14806
|
let raw;
|
|
14787
14807
|
try {
|
|
14788
14808
|
raw = await readFile(configPath(), "utf8");
|
|
14789
14809
|
} catch {
|
|
14810
|
+
if (envToken !== void 0 && envToken !== "") {
|
|
14811
|
+
return Result.ok({
|
|
14812
|
+
baseUrl: resolveBaseUrl(void 0),
|
|
14813
|
+
token: envToken,
|
|
14814
|
+
machineId: hostname3(),
|
|
14815
|
+
projects: []
|
|
14816
|
+
});
|
|
14817
|
+
}
|
|
14790
14818
|
return Result.fail(
|
|
14791
14819
|
new CliError("NOT_SIGNED_IN", "Not signed in. Run `hubskillz login`.")
|
|
14792
14820
|
);
|
|
14793
14821
|
}
|
|
14794
|
-
|
|
14795
|
-
|
|
14822
|
+
let parsed = null;
|
|
14823
|
+
try {
|
|
14824
|
+
parsed = configSchema.safeParse(JSON.parse(raw));
|
|
14825
|
+
} catch {
|
|
14826
|
+
parsed = null;
|
|
14827
|
+
}
|
|
14828
|
+
if (parsed === null || !parsed.success) {
|
|
14796
14829
|
return Result.fail(
|
|
14797
14830
|
new CliError(
|
|
14798
14831
|
"BAD_CONFIG",
|
|
@@ -14800,7 +14833,17 @@ async function readConfig() {
|
|
|
14800
14833
|
)
|
|
14801
14834
|
);
|
|
14802
14835
|
}
|
|
14803
|
-
return Result.ok(
|
|
14836
|
+
return Result.ok(
|
|
14837
|
+
envToken === void 0 || envToken === "" ? parsed.data : { ...parsed.data, token: envToken }
|
|
14838
|
+
);
|
|
14839
|
+
}
|
|
14840
|
+
async function deleteConfig() {
|
|
14841
|
+
try {
|
|
14842
|
+
await rm(configPath());
|
|
14843
|
+
return true;
|
|
14844
|
+
} catch {
|
|
14845
|
+
return false;
|
|
14846
|
+
}
|
|
14804
14847
|
}
|
|
14805
14848
|
async function writeConfig(config2) {
|
|
14806
14849
|
await mkdir(configDir(), { recursive: true, mode: 448 });
|
|
@@ -14913,6 +14956,66 @@ async function readAll(stream) {
|
|
|
14913
14956
|
}
|
|
14914
14957
|
return Buffer.concat(chunks).toString("utf8");
|
|
14915
14958
|
}
|
|
14959
|
+
function parseSelection(answer, count) {
|
|
14960
|
+
const text = answer.trim().toLowerCase();
|
|
14961
|
+
if (text === "" || text === "all" || text === "a") {
|
|
14962
|
+
return Array.from({ length: count }, (_, index) => index);
|
|
14963
|
+
}
|
|
14964
|
+
if (text === "none" || text === "n") return [];
|
|
14965
|
+
const picked = /* @__PURE__ */ new Set();
|
|
14966
|
+
for (const token of text.split(/[\s,]+/u)) {
|
|
14967
|
+
const index = Number(token) - 1;
|
|
14968
|
+
if (Number.isInteger(index) && index >= 0 && index < count) {
|
|
14969
|
+
picked.add(index);
|
|
14970
|
+
}
|
|
14971
|
+
}
|
|
14972
|
+
return [...picked].sort((left, right) => left - right);
|
|
14973
|
+
}
|
|
14974
|
+
async function selectMany(question, choices) {
|
|
14975
|
+
if (process.stdin.isTTY !== true) return choices;
|
|
14976
|
+
process.stdout.write(`${question}
|
|
14977
|
+
`);
|
|
14978
|
+
choices.forEach((choice, index) => {
|
|
14979
|
+
process.stdout.write(` ${index + 1}. ${choice}
|
|
14980
|
+
`);
|
|
14981
|
+
});
|
|
14982
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
14983
|
+
try {
|
|
14984
|
+
const answer = await rl.question("Select [all]: ");
|
|
14985
|
+
const picked = new Set(parseSelection(answer, choices.length));
|
|
14986
|
+
return choices.filter((_, index) => picked.has(index));
|
|
14987
|
+
} finally {
|
|
14988
|
+
rl.close();
|
|
14989
|
+
}
|
|
14990
|
+
}
|
|
14991
|
+
|
|
14992
|
+
// src/quickstart.ts
|
|
14993
|
+
function quickstart(config2) {
|
|
14994
|
+
const signedIn = config2.isSuccess;
|
|
14995
|
+
const synced = signedIn && config2.value.firstSyncAt !== void 0;
|
|
14996
|
+
const step = (done, text, command) => ` ${done ? dim("[x]") : "[ ]"} ${done ? dim(text) : text}
|
|
14997
|
+
${dim("$")} ${done ? dim(command) : bold(command)}
|
|
14998
|
+
`;
|
|
14999
|
+
return `${bold("Quickstart")}
|
|
15000
|
+
` + step(
|
|
15001
|
+
signedIn,
|
|
15002
|
+
"Sign in with a device token (create one under Settings, Device tokens)",
|
|
15003
|
+
"hubskillz login"
|
|
15004
|
+
) + step(
|
|
15005
|
+
synced,
|
|
15006
|
+
"See what is installed on this machine and how it compares to the directory",
|
|
15007
|
+
"hubskillz status"
|
|
15008
|
+
) + step(
|
|
15009
|
+
synced,
|
|
15010
|
+
"Install the approved set everywhere, adopt what the directory lacks",
|
|
15011
|
+
"hubskillz sync --all"
|
|
15012
|
+
) + `
|
|
15013
|
+
${dim("Run `hubskillz help` for every command, `hubskillz help <command>` for its flags.")}
|
|
15014
|
+
`;
|
|
15015
|
+
}
|
|
15016
|
+
function quickstartPending(config2) {
|
|
15017
|
+
return config2.isFailure || config2.value.firstSyncAt === void 0;
|
|
15018
|
+
}
|
|
14916
15019
|
|
|
14917
15020
|
// src/commands/login.ts
|
|
14918
15021
|
async function login(options) {
|
|
@@ -14921,9 +15024,12 @@ async function login(options) {
|
|
|
14921
15024
|
options.baseUrl,
|
|
14922
15025
|
existing.isSuccess ? existing.value.baseUrl : void 0
|
|
14923
15026
|
);
|
|
14924
|
-
process.stdout.write(
|
|
14925
|
-
`)
|
|
14926
|
-
|
|
15027
|
+
process.stdout.write(
|
|
15028
|
+
`${dim(`Signing in to ${baseUrl}`)}
|
|
15029
|
+
${dim(`Create a device token at ${baseUrl}/app/settings/tokens`)}
|
|
15030
|
+
`
|
|
15031
|
+
);
|
|
15032
|
+
const token = options.token ?? await promptSecret("Device token: ");
|
|
14927
15033
|
if (token === "") {
|
|
14928
15034
|
return Result.fail(new CliError("EMPTY_TOKEN", "No device token given."));
|
|
14929
15035
|
}
|
|
@@ -14934,19 +15040,51 @@ async function login(options) {
|
|
|
14934
15040
|
schema: meResponseSchema
|
|
14935
15041
|
});
|
|
14936
15042
|
if (me.isFailure) return Result.fail(me.error);
|
|
14937
|
-
|
|
15043
|
+
const config2 = {
|
|
15044
|
+
baseUrl,
|
|
15045
|
+
token,
|
|
15046
|
+
machineId: await machineId(),
|
|
15047
|
+
projects: existing.isSuccess ? existing.value.projects : []
|
|
15048
|
+
};
|
|
15049
|
+
if (existing.isSuccess && existing.value.firstSyncAt !== void 0) {
|
|
15050
|
+
config2.firstSyncAt = existing.value.firstSyncAt;
|
|
15051
|
+
}
|
|
15052
|
+
await writeConfig(config2);
|
|
14938
15053
|
process.stdout.write(
|
|
14939
15054
|
`Signed in as ${me.value.user.email} on ${me.value.org.name}.
|
|
14940
15055
|
${dim(`Token written to ${configPath()}`)}
|
|
15056
|
+
|
|
14941
15057
|
`
|
|
14942
15058
|
);
|
|
15059
|
+
if (config2.firstSyncAt === void 0) {
|
|
15060
|
+
process.stdout.write(quickstart(Result.ok(config2)));
|
|
15061
|
+
}
|
|
14943
15062
|
return Result.ok(void 0);
|
|
14944
15063
|
}
|
|
14945
15064
|
|
|
15065
|
+
// src/commands/logout.ts
|
|
15066
|
+
async function logout() {
|
|
15067
|
+
const removed = await deleteConfig();
|
|
15068
|
+
process.stdout.write(
|
|
15069
|
+
removed ? `Signed out. ${dim(`Removed ${configPath()}`)}
|
|
15070
|
+
` : `${dim("Not signed in.")}
|
|
15071
|
+
`
|
|
15072
|
+
);
|
|
15073
|
+
return Result.ok(void 0);
|
|
15074
|
+
}
|
|
15075
|
+
|
|
15076
|
+
// src/commands/projects.ts
|
|
15077
|
+
import { resolve as resolve4 } from "node:path";
|
|
15078
|
+
|
|
15079
|
+
// src/discover.ts
|
|
15080
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
15081
|
+
import { homedir as homedir4 } from "node:os";
|
|
15082
|
+
import { join as join4, resolve as resolve3 } from "node:path";
|
|
15083
|
+
|
|
14946
15084
|
// src/scan.ts
|
|
14947
15085
|
import { createHash as createHash2 } from "node:crypto";
|
|
14948
15086
|
import { readdir, readFile as readFile3, realpath, stat } from "node:fs/promises";
|
|
14949
|
-
import { homedir as homedir3, hostname as
|
|
15087
|
+
import { homedir as homedir3, hostname as hostname4 } from "node:os";
|
|
14950
15088
|
import { basename, dirname, join as join3, resolve as resolve2 } from "node:path";
|
|
14951
15089
|
|
|
14952
15090
|
// src/lock.ts
|
|
@@ -14992,7 +15130,6 @@ async function readLock(path) {
|
|
|
14992
15130
|
}
|
|
14993
15131
|
|
|
14994
15132
|
// src/scan.ts
|
|
14995
|
-
var SKIPPED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
14996
15133
|
function globalSkillsRoot() {
|
|
14997
15134
|
return join3(homedir3(), ".claude", "skills");
|
|
14998
15135
|
}
|
|
@@ -15000,10 +15137,10 @@ function projectSkillsRoot(dir) {
|
|
|
15000
15137
|
return join3(resolve2(dir), ".claude", "skills");
|
|
15001
15138
|
}
|
|
15002
15139
|
function globalSurfaceLabel() {
|
|
15003
|
-
return
|
|
15140
|
+
return hostname4();
|
|
15004
15141
|
}
|
|
15005
15142
|
function projectSurfaceLabel(dir) {
|
|
15006
|
-
return `${
|
|
15143
|
+
return `${hostname4()}:${basename(resolve2(dir))}`;
|
|
15007
15144
|
}
|
|
15008
15145
|
async function exists(path) {
|
|
15009
15146
|
try {
|
|
@@ -15050,7 +15187,7 @@ async function scanSkills(root) {
|
|
|
15050
15187
|
}
|
|
15051
15188
|
const skills = [];
|
|
15052
15189
|
for (const entry of entries) {
|
|
15053
|
-
if (
|
|
15190
|
+
if (!isSkillFile(entry.name)) continue;
|
|
15054
15191
|
const dir = join3(root, entry.name);
|
|
15055
15192
|
const symlink = entry.isSymbolicLink();
|
|
15056
15193
|
if (!entry.isDirectory() && !(symlink && await isDir(dir))) continue;
|
|
@@ -15080,9 +15217,9 @@ async function walk(dir, prefix, visited) {
|
|
|
15080
15217
|
let link = false;
|
|
15081
15218
|
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15082
15219
|
for (const entry of entries) {
|
|
15083
|
-
if (entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
|
|
15084
15220
|
const full = join3(dir, entry.name);
|
|
15085
15221
|
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
15222
|
+
if (!isSkillFile(relative)) continue;
|
|
15086
15223
|
const symlink = entry.isSymbolicLink();
|
|
15087
15224
|
if (entry.isDirectory() || symlink && await isDir(full)) {
|
|
15088
15225
|
const nested = await walk(full, relative, visited);
|
|
@@ -15092,7 +15229,9 @@ async function walk(dir, prefix, visited) {
|
|
|
15092
15229
|
}
|
|
15093
15230
|
if (!entry.isFile() && !symlink) continue;
|
|
15094
15231
|
const content = await readFile3(full, "utf8").catch(() => null);
|
|
15095
|
-
if (content === null)
|
|
15232
|
+
if (content === null || content.includes("\0") || content.length > MAX_FILE_CONTENT_CHARS) {
|
|
15233
|
+
continue;
|
|
15234
|
+
}
|
|
15096
15235
|
files.push({
|
|
15097
15236
|
path: relative,
|
|
15098
15237
|
content,
|
|
@@ -15109,14 +15248,19 @@ function inventoryRequestOf(surface) {
|
|
|
15109
15248
|
return {
|
|
15110
15249
|
surface: surface.descriptor,
|
|
15111
15250
|
skills: surface.skills.map((skill) => {
|
|
15251
|
+
const snapshot = skill.upstream === void 0 && skill.files.reduce((sum, file2) => sum + file2.content.length, 0) <= MAX_SNAPSHOT_CHARS;
|
|
15112
15252
|
const item = {
|
|
15113
15253
|
name: skill.name,
|
|
15114
15254
|
contentHash: skill.contentHash,
|
|
15115
|
-
files: skill.files.map((file2) =>
|
|
15116
|
-
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15255
|
+
files: skill.files.map((file2) => {
|
|
15256
|
+
const entry = {
|
|
15257
|
+
path: file2.path,
|
|
15258
|
+
hash: file2.hash,
|
|
15259
|
+
size: file2.size
|
|
15260
|
+
};
|
|
15261
|
+
if (snapshot) entry.content = file2.content;
|
|
15262
|
+
return entry;
|
|
15263
|
+
})
|
|
15120
15264
|
};
|
|
15121
15265
|
if (skill.upstream !== void 0) item.upstream = skill.upstream;
|
|
15122
15266
|
return item;
|
|
@@ -15124,6 +15268,120 @@ function inventoryRequestOf(surface) {
|
|
|
15124
15268
|
};
|
|
15125
15269
|
}
|
|
15126
15270
|
|
|
15271
|
+
// src/discover.ts
|
|
15272
|
+
var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "Library", ".Trash", ".cache"]);
|
|
15273
|
+
async function discoverProjects(roots = [homedir4()], maxDepth = 3) {
|
|
15274
|
+
const found = /* @__PURE__ */ new Set();
|
|
15275
|
+
const walk2 = async (dir, depth) => {
|
|
15276
|
+
if (depth > maxDepth) return;
|
|
15277
|
+
let entries;
|
|
15278
|
+
try {
|
|
15279
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
15280
|
+
} catch {
|
|
15281
|
+
return;
|
|
15282
|
+
}
|
|
15283
|
+
for (const entry of entries) {
|
|
15284
|
+
const name = entry.name;
|
|
15285
|
+
if (!entry.isDirectory() || SKIP.has(name)) continue;
|
|
15286
|
+
if (name.startsWith(".") && name !== ".claude") continue;
|
|
15287
|
+
const child = join4(dir, name);
|
|
15288
|
+
if (name === ".claude" && depth > 0) {
|
|
15289
|
+
if (await exists(projectSkillsRoot(dir))) found.add(dir);
|
|
15290
|
+
continue;
|
|
15291
|
+
}
|
|
15292
|
+
await walk2(child, depth + 1);
|
|
15293
|
+
}
|
|
15294
|
+
};
|
|
15295
|
+
for (const root of roots) await walk2(resolve3(root), 0);
|
|
15296
|
+
return [...found].sort();
|
|
15297
|
+
}
|
|
15298
|
+
|
|
15299
|
+
// src/commands/projects.ts
|
|
15300
|
+
async function discoverAndRegister(config2, yes) {
|
|
15301
|
+
const known = new Set(config2.projects);
|
|
15302
|
+
const found = (await discoverProjects()).filter((dir) => !known.has(dir));
|
|
15303
|
+
if (found.length === 0) {
|
|
15304
|
+
process.stdout.write(dim("No new project with .claude/skills found.\n"));
|
|
15305
|
+
return config2.projects;
|
|
15306
|
+
}
|
|
15307
|
+
const chosen = yes ? found : await selectMany("Projects with skills found. Register:", found);
|
|
15308
|
+
if (chosen.length === 0) return config2.projects;
|
|
15309
|
+
const projects2 = [...config2.projects, ...chosen];
|
|
15310
|
+
await writeConfig({ ...config2, projects: projects2 });
|
|
15311
|
+
process.stdout.write(
|
|
15312
|
+
`Registered ${chosen.length} project${chosen.length === 1 ? "" : "s"}. They are now part of \`hubskillz sync --all\`.
|
|
15313
|
+
`
|
|
15314
|
+
);
|
|
15315
|
+
return projects2;
|
|
15316
|
+
}
|
|
15317
|
+
async function projects(options) {
|
|
15318
|
+
const config2 = await readConfig();
|
|
15319
|
+
if (config2.isFailure) return Result.fail(config2.error);
|
|
15320
|
+
const current = config2.value.projects;
|
|
15321
|
+
const dir = resolve4(options.dir ?? process.cwd());
|
|
15322
|
+
switch (options.action ?? "list") {
|
|
15323
|
+
case "list": {
|
|
15324
|
+
if (current.length === 0) {
|
|
15325
|
+
process.stdout.write(
|
|
15326
|
+
dim(
|
|
15327
|
+
"No registered projects. Run `hubskillz projects add` inside a repo.\n"
|
|
15328
|
+
)
|
|
15329
|
+
);
|
|
15330
|
+
return Result.ok(void 0);
|
|
15331
|
+
}
|
|
15332
|
+
for (const project of current) process.stdout.write(`${project}
|
|
15333
|
+
`);
|
|
15334
|
+
return Result.ok(void 0);
|
|
15335
|
+
}
|
|
15336
|
+
case "add": {
|
|
15337
|
+
if (!await exists(projectSkillsRoot(dir))) {
|
|
15338
|
+
return Result.fail(
|
|
15339
|
+
new CliError(
|
|
15340
|
+
"NO_SKILLS_ROOT",
|
|
15341
|
+
`${dir} has no .claude/skills directory.`
|
|
15342
|
+
)
|
|
15343
|
+
);
|
|
15344
|
+
}
|
|
15345
|
+
if (current.includes(dir)) {
|
|
15346
|
+
process.stdout.write(dim(`${dir} is already registered.
|
|
15347
|
+
`));
|
|
15348
|
+
return Result.ok(void 0);
|
|
15349
|
+
}
|
|
15350
|
+
await writeConfig({ ...config2.value, projects: [...current, dir] });
|
|
15351
|
+
process.stdout.write(
|
|
15352
|
+
`Registered ${dir}. It is now part of \`hubskillz sync --all\`.
|
|
15353
|
+
`
|
|
15354
|
+
);
|
|
15355
|
+
return Result.ok(void 0);
|
|
15356
|
+
}
|
|
15357
|
+
case "discover": {
|
|
15358
|
+
await discoverAndRegister(config2.value, options.yes);
|
|
15359
|
+
return Result.ok(void 0);
|
|
15360
|
+
}
|
|
15361
|
+
case "remove": {
|
|
15362
|
+
if (!current.includes(dir)) {
|
|
15363
|
+
return Result.fail(
|
|
15364
|
+
new CliError("NOT_REGISTERED", `${dir} is not registered.`)
|
|
15365
|
+
);
|
|
15366
|
+
}
|
|
15367
|
+
await writeConfig({
|
|
15368
|
+
...config2.value,
|
|
15369
|
+
projects: current.filter((project) => project !== dir)
|
|
15370
|
+
});
|
|
15371
|
+
process.stdout.write(`Forgot ${dir}.
|
|
15372
|
+
`);
|
|
15373
|
+
return Result.ok(void 0);
|
|
15374
|
+
}
|
|
15375
|
+
default:
|
|
15376
|
+
return Result.fail(
|
|
15377
|
+
new CliError(
|
|
15378
|
+
"USAGE",
|
|
15379
|
+
`hubskillz projects: unknown action "${options.action}". Use add, remove, discover or list.`
|
|
15380
|
+
)
|
|
15381
|
+
);
|
|
15382
|
+
}
|
|
15383
|
+
}
|
|
15384
|
+
|
|
15127
15385
|
// src/commands/push.ts
|
|
15128
15386
|
async function push(options) {
|
|
15129
15387
|
const config2 = await readConfig();
|
|
@@ -15163,18 +15421,44 @@ ${dim(`skill ${draft.value.skillId} version ${draft.value.versionId}`)}
|
|
|
15163
15421
|
}
|
|
15164
15422
|
|
|
15165
15423
|
// src/surfaces.ts
|
|
15166
|
-
|
|
15167
|
-
|
|
15424
|
+
import { resolve as resolve5 } from "node:path";
|
|
15425
|
+
async function localSurfaces(path, both, machineId2, registered = []) {
|
|
15426
|
+
const projectDir = resolve5(path ?? process.cwd());
|
|
15168
15427
|
const projectRoot = projectSkillsRoot(projectDir);
|
|
15169
15428
|
const hasProject = await exists(projectRoot);
|
|
15170
|
-
const project = async () => scanSurface(
|
|
15429
|
+
const project = async (dir) => scanSurface(projectSkillsRoot(dir), projectSurfaceLabel(dir), machineId2);
|
|
15171
15430
|
const global = async () => scanSurface(globalSkillsRoot(), globalSurfaceLabel(), machineId2);
|
|
15172
|
-
if (both)
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15431
|
+
if (!both) return [hasProject ? await project(projectDir) : await global()];
|
|
15432
|
+
const surfaces = [await global()];
|
|
15433
|
+
const dirs = projectDirs(
|
|
15434
|
+
hasProject || path !== void 0 ? projectDir : void 0,
|
|
15435
|
+
registered
|
|
15436
|
+
);
|
|
15437
|
+
for (const dir of dirs) {
|
|
15438
|
+
if (await exists(projectSkillsRoot(dir))) {
|
|
15439
|
+
surfaces.push(await project(dir));
|
|
15440
|
+
continue;
|
|
15441
|
+
}
|
|
15442
|
+
process.stdout.write(
|
|
15443
|
+
dim(
|
|
15444
|
+
`skipping ${dir}: no .claude/skills here, run \`hubskillz projects remove ${dir}\` to forget it
|
|
15445
|
+
`
|
|
15446
|
+
)
|
|
15447
|
+
);
|
|
15176
15448
|
}
|
|
15177
|
-
return
|
|
15449
|
+
return surfaces;
|
|
15450
|
+
}
|
|
15451
|
+
function projectDirs(current, registered) {
|
|
15452
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15453
|
+
const out = [];
|
|
15454
|
+
for (const dir of [current, ...registered]) {
|
|
15455
|
+
if (dir === void 0) continue;
|
|
15456
|
+
const abs = resolve5(dir);
|
|
15457
|
+
if (seen.has(abs)) continue;
|
|
15458
|
+
seen.add(abs);
|
|
15459
|
+
out.push(abs);
|
|
15460
|
+
}
|
|
15461
|
+
return out;
|
|
15178
15462
|
}
|
|
15179
15463
|
|
|
15180
15464
|
// src/commands/status.ts
|
|
@@ -15185,16 +15469,22 @@ async function status(options) {
|
|
|
15185
15469
|
baseUrl: resolveBaseUrl(options.baseUrl, config2.value.baseUrl),
|
|
15186
15470
|
token: config2.value.token
|
|
15187
15471
|
};
|
|
15472
|
+
const projects2 = config2.value.projects.length === 0 ? await discoverAndRegister(config2.value, options.yes) : config2.value.projects;
|
|
15188
15473
|
const surfaces = await localSurfaces(
|
|
15189
15474
|
options.path,
|
|
15190
15475
|
true,
|
|
15191
|
-
config2.value.machineId
|
|
15476
|
+
config2.value.machineId,
|
|
15477
|
+
projects2
|
|
15192
15478
|
);
|
|
15193
15479
|
for (const surface of surfaces) {
|
|
15194
15480
|
const inventory = await postInventory(session, surface);
|
|
15195
15481
|
if (inventory.isFailure) return Result.fail(inventory.error);
|
|
15196
15482
|
printSurface(surface, inventory.value);
|
|
15197
15483
|
}
|
|
15484
|
+
if (quickstartPending(config2)) {
|
|
15485
|
+
process.stdout.write(`
|
|
15486
|
+
${quickstart(config2)}`);
|
|
15487
|
+
}
|
|
15198
15488
|
return Result.ok(void 0);
|
|
15199
15489
|
}
|
|
15200
15490
|
async function postInventory(session, surface) {
|
|
@@ -15212,57 +15502,58 @@ function originOf(surface, name) {
|
|
|
15212
15502
|
return skill.upstream === void 0 ? "private" : `skills.sh ${skill.upstream.source}`;
|
|
15213
15503
|
}
|
|
15214
15504
|
function printNotes(surface, items) {
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15505
|
+
const importable = items.filter((item) => item.importable).map((item) => item.name);
|
|
15506
|
+
if (importable.length > 0) {
|
|
15507
|
+
process.stdout.write(
|
|
15508
|
+
`${dim(`${importable.length} not in the directory yet, run npx hubskillz sync to adopt them:`)} ${importable.join(", ")}
|
|
15219
15509
|
`
|
|
15220
|
-
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15510
|
+
);
|
|
15511
|
+
}
|
|
15512
|
+
const ahead = items.filter((item) => item.upstreamAhead === true).map((item) => `${item.name} (${originOf(surface, item.name)})`);
|
|
15513
|
+
if (ahead.length > 0) {
|
|
15514
|
+
process.stdout.write(
|
|
15515
|
+
`${dim("upstream ahead of approved version, waiting for review:")} ${ahead.join(", ")}
|
|
15225
15516
|
`
|
|
15226
|
-
|
|
15227
|
-
}
|
|
15517
|
+
);
|
|
15228
15518
|
}
|
|
15229
15519
|
}
|
|
15230
|
-
function
|
|
15231
|
-
|
|
15232
|
-
|
|
15520
|
+
function printHeader(surface) {
|
|
15521
|
+
process.stdout.write(
|
|
15522
|
+
`
|
|
15523
|
+
${bold(surface.descriptor.label)} ${dim(surface.descriptor.path)}
|
|
15524
|
+
`
|
|
15233
15525
|
);
|
|
15526
|
+
}
|
|
15527
|
+
function printSurface(surface, inventory) {
|
|
15234
15528
|
const rows = inventory.items.map((item) => [
|
|
15235
|
-
|
|
15529
|
+
item.name,
|
|
15236
15530
|
originOf(surface, item.name),
|
|
15237
15531
|
item.required ? `${item.state} ${dim("(required)")}` : item.state,
|
|
15238
15532
|
shortHash(item.installedHash),
|
|
15239
15533
|
item.approvedVersion === void 0 ? "-" : `v${item.approvedVersion}`
|
|
15240
15534
|
]);
|
|
15241
|
-
|
|
15242
|
-
`
|
|
15243
|
-
${bold(surface.descriptor.label)} ${dim(surface.descriptor.path)}
|
|
15244
|
-
`
|
|
15245
|
-
);
|
|
15535
|
+
printHeader(surface);
|
|
15246
15536
|
if (rows.length === 0) {
|
|
15247
15537
|
process.stdout.write(`${dim("no skills")}
|
|
15248
15538
|
`);
|
|
15249
15539
|
return;
|
|
15250
15540
|
}
|
|
15251
15541
|
process.stdout.write(
|
|
15252
|
-
`${table(["SKILL", "ORIGIN", "STATE", "
|
|
15542
|
+
`${table(["SKILL", "ORIGIN", "STATE", "LOCAL", "APPROVED"], rows)}
|
|
15543
|
+
|
|
15253
15544
|
`
|
|
15254
15545
|
);
|
|
15255
15546
|
printNotes(surface, inventory.items);
|
|
15256
15547
|
}
|
|
15257
15548
|
|
|
15258
15549
|
// src/commands/sync.ts
|
|
15259
|
-
import { lstat, readdir as
|
|
15260
|
-
import { homedir as
|
|
15261
|
-
import { join as
|
|
15550
|
+
import { lstat, readdir as readdir3, realpath as realpath2 } from "node:fs/promises";
|
|
15551
|
+
import { homedir as homedir5 } from "node:os";
|
|
15552
|
+
import { join as join6, resolve as resolve6, sep as sep2 } from "node:path";
|
|
15262
15553
|
|
|
15263
15554
|
// src/apply.ts
|
|
15264
|
-
import { mkdir as mkdir2, mkdtemp, rename, rm, rmdir, writeFile as writeFile2 } from "node:fs/promises";
|
|
15265
|
-
import { dirname as dirname2, join as
|
|
15555
|
+
import { mkdir as mkdir2, mkdtemp, rename, rm as rm2, rmdir, writeFile as writeFile2 } from "node:fs/promises";
|
|
15556
|
+
import { dirname as dirname2, join as join5, sep } from "node:path";
|
|
15266
15557
|
async function applySkill(input) {
|
|
15267
15558
|
for (const path of [...input.files.map((f) => f.path), ...input.remove]) {
|
|
15268
15559
|
if (!isSafeRelativePath(path)) {
|
|
@@ -15275,25 +15566,25 @@ async function applySkill(input) {
|
|
|
15275
15566
|
}
|
|
15276
15567
|
}
|
|
15277
15568
|
await mkdir2(input.dir, { recursive: true });
|
|
15278
|
-
const staging = await mkdtemp(
|
|
15569
|
+
const staging = await mkdtemp(join5(dirname2(input.dir), ".hubskillz-"));
|
|
15279
15570
|
try {
|
|
15280
15571
|
for (const file2 of input.files) {
|
|
15281
|
-
const staged =
|
|
15572
|
+
const staged = join5(staging, ...file2.path.split("/"));
|
|
15282
15573
|
await mkdir2(dirname2(staged), { recursive: true });
|
|
15283
15574
|
await writeFile2(staged, file2.content, "utf8");
|
|
15284
15575
|
}
|
|
15285
15576
|
for (const file2 of input.files) {
|
|
15286
|
-
const target =
|
|
15577
|
+
const target = join5(input.dir, ...file2.path.split("/"));
|
|
15287
15578
|
await mkdir2(dirname2(target), { recursive: true });
|
|
15288
|
-
await rename(
|
|
15579
|
+
await rename(join5(staging, ...file2.path.split("/")), target);
|
|
15289
15580
|
}
|
|
15290
15581
|
for (const path of input.remove) {
|
|
15291
|
-
const target =
|
|
15292
|
-
await
|
|
15582
|
+
const target = join5(input.dir, ...path.split("/"));
|
|
15583
|
+
await rm2(target, { force: true });
|
|
15293
15584
|
await pruneEmptyDirs(dirname2(target), input.dir);
|
|
15294
15585
|
}
|
|
15295
15586
|
} finally {
|
|
15296
|
-
await
|
|
15587
|
+
await rm2(staging, { recursive: true, force: true });
|
|
15297
15588
|
}
|
|
15298
15589
|
return Result.ok(void 0);
|
|
15299
15590
|
}
|
|
@@ -15372,21 +15663,35 @@ async function sync(options) {
|
|
|
15372
15663
|
baseUrl: resolveBaseUrl(options.baseUrl, config2.value.baseUrl),
|
|
15373
15664
|
token: config2.value.token
|
|
15374
15665
|
};
|
|
15666
|
+
const projects2 = options.all && config2.value.projects.length === 0 ? await discoverAndRegister(config2.value, options.yes) : config2.value.projects;
|
|
15375
15667
|
const surfaces = await localSurfaces(
|
|
15376
15668
|
options.path,
|
|
15377
15669
|
options.all,
|
|
15378
|
-
config2.value.machineId
|
|
15670
|
+
config2.value.machineId,
|
|
15671
|
+
projects2
|
|
15379
15672
|
);
|
|
15380
15673
|
for (const surface of surfaces) {
|
|
15381
15674
|
const result = await syncSurface(session, surface, options);
|
|
15382
15675
|
if (result.isFailure) return result;
|
|
15383
15676
|
}
|
|
15677
|
+
if (!options.dryRun && config2.value.firstSyncAt === void 0 && process.env["HUBSKILLZ_TOKEN"] === void 0) {
|
|
15678
|
+
const stored = await readConfig();
|
|
15679
|
+
if (stored.isSuccess && stored.value.firstSyncAt === void 0) {
|
|
15680
|
+
await writeConfig({
|
|
15681
|
+
...stored.value,
|
|
15682
|
+
projects: [...projects2],
|
|
15683
|
+
firstSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15684
|
+
});
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15384
15687
|
return Result.ok(void 0);
|
|
15385
15688
|
}
|
|
15386
15689
|
async function syncSurface(session, surface, options) {
|
|
15387
|
-
const
|
|
15690
|
+
const first = await postInventory(session, surface);
|
|
15691
|
+
if (first.isFailure) return Result.fail(first.error);
|
|
15692
|
+
const surfaceId = first.value.surfaceId;
|
|
15693
|
+
const inventory = await maybeAdopt(session, surface, first.value, options);
|
|
15388
15694
|
if (inventory.isFailure) return Result.fail(inventory.error);
|
|
15389
|
-
const surfaceId = inventory.value.surfaceId;
|
|
15390
15695
|
const approved = await apiRequest({
|
|
15391
15696
|
session,
|
|
15392
15697
|
method: "GET",
|
|
@@ -15401,18 +15706,8 @@ async function syncSurface(session, surface, options) {
|
|
|
15401
15706
|
local: surface.skills,
|
|
15402
15707
|
force: options.force
|
|
15403
15708
|
});
|
|
15404
|
-
|
|
15405
|
-
|
|
15406
|
-
${bold(surface.descriptor.label)} ${dim(surface.descriptor.path)}
|
|
15407
|
-
`
|
|
15408
|
-
);
|
|
15409
|
-
printPlan(plans, surface);
|
|
15410
|
-
for (const skill of blocked) {
|
|
15411
|
-
process.stdout.write(
|
|
15412
|
-
`${skill.name}: blocked by org policy (${skill.blockedReason ?? "no reason given"})
|
|
15413
|
-
`
|
|
15414
|
-
);
|
|
15415
|
-
}
|
|
15709
|
+
printHeader(surface);
|
|
15710
|
+
printPlan(plans, blocked, surface);
|
|
15416
15711
|
printNotes(surface, inventory.value.items);
|
|
15417
15712
|
if (options.dryRun || !planHasWrites(plans)) return Result.ok(void 0);
|
|
15418
15713
|
if (!options.yes && !await confirm("Apply?")) {
|
|
@@ -15430,6 +15725,44 @@ ${bold(surface.descriptor.label)} ${dim(surface.descriptor.path)}
|
|
|
15430
15725
|
if (reposted.isFailure) return Result.fail(reposted.error);
|
|
15431
15726
|
return clearPending(session, surfaceId);
|
|
15432
15727
|
}
|
|
15728
|
+
async function maybeAdopt(session, surface, inventory, options) {
|
|
15729
|
+
const importable = inventory.items.filter((item) => item.importable);
|
|
15730
|
+
if (importable.length === 0 || options.dryRun) return Result.ok(inventory);
|
|
15731
|
+
const wanted = options.adopt || !options.yes && await confirm(
|
|
15732
|
+
`Adopt ${plural(importable.length)} found here as approved in your directory?`
|
|
15733
|
+
);
|
|
15734
|
+
if (!wanted) return Result.ok(inventory);
|
|
15735
|
+
const adopted = await apiRequest({
|
|
15736
|
+
session,
|
|
15737
|
+
method: "POST",
|
|
15738
|
+
path: "/api/cli/adopt",
|
|
15739
|
+
schema: adoptResponseSchema,
|
|
15740
|
+
body: { surfaceId: inventory.surfaceId }
|
|
15741
|
+
});
|
|
15742
|
+
if (adopted.isFailure && adopted.error.code === "FORBIDDEN") {
|
|
15743
|
+
process.stdout.write(
|
|
15744
|
+
`${dim("not adopted: ask a maintainer to adopt these skills")}
|
|
15745
|
+
`
|
|
15746
|
+
);
|
|
15747
|
+
return Result.ok(inventory);
|
|
15748
|
+
}
|
|
15749
|
+
if (adopted.isFailure) return Result.fail(adopted.error);
|
|
15750
|
+
const names = adopted.value.adopted;
|
|
15751
|
+
process.stdout.write(
|
|
15752
|
+
names.length === 0 ? `${dim("nothing adopted")}
|
|
15753
|
+
` : `adopted ${plural(names.length)} as approved: ${names.join(", ")}
|
|
15754
|
+
`
|
|
15755
|
+
);
|
|
15756
|
+
for (const skip of adopted.value.skipped) {
|
|
15757
|
+
process.stdout.write(dim(`skipped ${skip.name} (${skip.code})
|
|
15758
|
+
`));
|
|
15759
|
+
}
|
|
15760
|
+
if (adopted.value.adopted.length === 0) return Result.ok(inventory);
|
|
15761
|
+
return postInventory(session, surface);
|
|
15762
|
+
}
|
|
15763
|
+
function plural(n) {
|
|
15764
|
+
return `${n} skill${n === 1 ? "" : "s"}`;
|
|
15765
|
+
}
|
|
15433
15766
|
async function applyPlan(surface, plans, approved) {
|
|
15434
15767
|
for (const plan of plans) {
|
|
15435
15768
|
if (plan.action !== "install" && plan.action !== "update") continue;
|
|
@@ -15458,12 +15791,12 @@ async function applyPlan(surface, plans, approved) {
|
|
|
15458
15791
|
return Result.ok(void 0);
|
|
15459
15792
|
}
|
|
15460
15793
|
async function writeTarget(root, name) {
|
|
15461
|
-
const dir =
|
|
15794
|
+
const dir = join6(root, name);
|
|
15462
15795
|
const stats = await lstat(dir).catch(() => null);
|
|
15463
15796
|
if (stats === null || !stats.isSymbolicLink()) return Result.ok(dir);
|
|
15464
15797
|
const target = await realpath2(dir).catch(() => null);
|
|
15465
|
-
const
|
|
15466
|
-
if (target === null || !isInside(target,
|
|
15798
|
+
const home2 = await realpath2(homedir5()).catch(() => homedir5());
|
|
15799
|
+
if (target === null || !isInside(target, home2) || isInside(target, await realpath2(root).catch(() => resolve6(root)))) {
|
|
15467
15800
|
return Result.fail(
|
|
15468
15801
|
new CliError(
|
|
15469
15802
|
"UNSAFE_LINK",
|
|
@@ -15474,7 +15807,7 @@ async function writeTarget(root, name) {
|
|
|
15474
15807
|
return Result.ok(target);
|
|
15475
15808
|
}
|
|
15476
15809
|
async function containsSymlink(dir) {
|
|
15477
|
-
const entries = await
|
|
15810
|
+
const entries = await readdir3(dir, {
|
|
15478
15811
|
recursive: true,
|
|
15479
15812
|
withFileTypes: true
|
|
15480
15813
|
}).catch(() => []);
|
|
@@ -15502,104 +15835,273 @@ async function clearPending(session, surfaceId) {
|
|
|
15502
15835
|
}
|
|
15503
15836
|
return Result.ok(void 0);
|
|
15504
15837
|
}
|
|
15505
|
-
function printPlan(plans, surface) {
|
|
15506
|
-
|
|
15838
|
+
function printPlan(plans, blocked, surface) {
|
|
15839
|
+
const rows = [
|
|
15840
|
+
...plans.map((plan) => [
|
|
15841
|
+
plan.action,
|
|
15842
|
+
plan.name,
|
|
15843
|
+
originOf(surface, plan.name),
|
|
15844
|
+
`v${plan.version}`,
|
|
15845
|
+
detailOf(plan)
|
|
15846
|
+
]),
|
|
15847
|
+
...blocked.map((skill) => [
|
|
15848
|
+
accent("blocked"),
|
|
15849
|
+
skill.name,
|
|
15850
|
+
originOf(surface, skill.name),
|
|
15851
|
+
`v${skill.version}`,
|
|
15852
|
+
`org policy: ${skill.blockedReason ?? "no reason given"}`
|
|
15853
|
+
])
|
|
15854
|
+
];
|
|
15855
|
+
if (rows.length === 0) {
|
|
15507
15856
|
process.stdout.write(`${dim("nothing to sync")}
|
|
15857
|
+
|
|
15508
15858
|
`);
|
|
15509
15859
|
return;
|
|
15510
15860
|
}
|
|
15511
|
-
const rows = plans.map((plan) => [
|
|
15512
|
-
plan.action,
|
|
15513
|
-
plan.name,
|
|
15514
|
-
originOf(surface, plan.name),
|
|
15515
|
-
`v${plan.version}`,
|
|
15516
|
-
diffSummary(plan)
|
|
15517
|
-
]);
|
|
15518
15861
|
process.stdout.write(
|
|
15519
15862
|
`${table(["ACTION", "SKILL", "ORIGIN", "VERSION", "FILES"], rows)}
|
|
15520
15863
|
`
|
|
15521
15864
|
);
|
|
15522
15865
|
for (const plan of plans) {
|
|
15866
|
+
if (plan.action !== "update") continue;
|
|
15523
15867
|
for (const [marker, paths] of [
|
|
15524
15868
|
["+", plan.added],
|
|
15525
15869
|
["~", plan.changed],
|
|
15526
15870
|
["-", plan.removed]
|
|
15527
15871
|
]) {
|
|
15528
|
-
if (plan.action === "keep" || plan.action === "skip") continue;
|
|
15529
15872
|
for (const path of paths) {
|
|
15530
|
-
process.stdout.write(` ${marker} ${plan.name}/${path}
|
|
15531
|
-
`);
|
|
15873
|
+
process.stdout.write(dim(` ${marker} ${plan.name}/${path}
|
|
15874
|
+
`));
|
|
15532
15875
|
}
|
|
15533
15876
|
}
|
|
15534
15877
|
}
|
|
15535
|
-
const
|
|
15536
|
-
|
|
15537
|
-
|
|
15538
|
-
|
|
15878
|
+
const counts = [
|
|
15879
|
+
[plans.filter((plan) => plan.action === "install").length, "to install"],
|
|
15880
|
+
[plans.filter((plan) => plan.action === "update").length, "to update"],
|
|
15881
|
+
[plans.filter((plan) => plan.action === "keep").length, "up to date"],
|
|
15882
|
+
[plans.filter((plan) => plan.action === "skip").length, "skipped"],
|
|
15883
|
+
[blocked.length, "blocked"]
|
|
15884
|
+
];
|
|
15885
|
+
process.stdout.write(
|
|
15886
|
+
`${counts.filter(([n]) => n > 0).map(([n, label]) => `${n} ${label}`).join(", ")}
|
|
15887
|
+
|
|
15539
15888
|
`
|
|
15540
|
-
|
|
15541
|
-
}
|
|
15889
|
+
);
|
|
15542
15890
|
}
|
|
15543
|
-
function
|
|
15891
|
+
function detailOf(plan) {
|
|
15544
15892
|
if (plan.action === "keep") return dim("up to date");
|
|
15545
|
-
if (plan.action === "skip")
|
|
15893
|
+
if (plan.action === "skip") {
|
|
15894
|
+
return `${accent("customized locally")}, use --force to overwrite`;
|
|
15895
|
+
}
|
|
15896
|
+
if (plan.action === "install") return `+${plan.added.length}`;
|
|
15546
15897
|
return `+${plan.added.length} ~${plan.changed.length} -${plan.removed.length}`;
|
|
15547
15898
|
}
|
|
15548
15899
|
|
|
15549
15900
|
// src/index.ts
|
|
15550
|
-
var
|
|
15901
|
+
var GLOBAL_FLAGS = [
|
|
15902
|
+
{
|
|
15903
|
+
spec: "--base-url URL",
|
|
15904
|
+
help: "Server to talk to (default: config file, then https://hubskillz.com)"
|
|
15905
|
+
},
|
|
15906
|
+
{ spec: "-h, --help", help: "Show help" },
|
|
15907
|
+
{ spec: "-v, --version", help: "Show the version" }
|
|
15908
|
+
];
|
|
15909
|
+
var COMMANDS = [
|
|
15910
|
+
{
|
|
15911
|
+
name: "login",
|
|
15912
|
+
usage: "hubskillz login [--token TOKEN] [--base-url URL]",
|
|
15913
|
+
summary: "Sign in with a device token",
|
|
15914
|
+
flags: [
|
|
15915
|
+
{ spec: "--token TOKEN", help: "Device token, instead of the prompt" }
|
|
15916
|
+
]
|
|
15917
|
+
},
|
|
15918
|
+
{
|
|
15919
|
+
name: "logout",
|
|
15920
|
+
usage: "hubskillz logout",
|
|
15921
|
+
summary: "Forget the device token on this machine",
|
|
15922
|
+
flags: []
|
|
15923
|
+
},
|
|
15924
|
+
{
|
|
15925
|
+
name: "status",
|
|
15926
|
+
usage: "hubskillz status [--path DIR] [--yes]",
|
|
15927
|
+
summary: "Report the state of every installed skill",
|
|
15928
|
+
flags: [
|
|
15929
|
+
{
|
|
15930
|
+
spec: "--path DIR",
|
|
15931
|
+
help: "Project directory (default: current directory)"
|
|
15932
|
+
},
|
|
15933
|
+
{
|
|
15934
|
+
spec: "-y, --yes",
|
|
15935
|
+
help: "Register every discovered project without asking"
|
|
15936
|
+
}
|
|
15937
|
+
]
|
|
15938
|
+
},
|
|
15939
|
+
{
|
|
15940
|
+
name: "sync",
|
|
15941
|
+
usage: "hubskillz sync [--path DIR] [--all] [--adopt] [--yes] [--dry-run] [--force]",
|
|
15942
|
+
summary: "Install and update skills to the approved versions",
|
|
15943
|
+
flags: [
|
|
15944
|
+
{
|
|
15945
|
+
spec: "--path DIR",
|
|
15946
|
+
help: "Project directory (default: current directory)"
|
|
15947
|
+
},
|
|
15948
|
+
{
|
|
15949
|
+
spec: "--all",
|
|
15950
|
+
help: "Global root, this project and every registered project"
|
|
15951
|
+
},
|
|
15952
|
+
{
|
|
15953
|
+
spec: "--adopt",
|
|
15954
|
+
help: "Add importable skills to the directory as approved (maintainers)"
|
|
15955
|
+
},
|
|
15956
|
+
{ spec: "-y, --yes", help: "Apply without asking" },
|
|
15957
|
+
{ spec: "--dry-run", help: "Print the plan and stop" },
|
|
15958
|
+
{ spec: "--force", help: "Overwrite skills you customized locally" }
|
|
15959
|
+
]
|
|
15960
|
+
},
|
|
15961
|
+
{
|
|
15962
|
+
name: "push",
|
|
15963
|
+
usage: "hubskillz push <skill-dir> [-m MESSAGE]",
|
|
15964
|
+
summary: "Upload a skill directory as a draft for review",
|
|
15965
|
+
flags: [{ spec: "-m, --message TEXT", help: "Note attached to the draft" }]
|
|
15966
|
+
},
|
|
15967
|
+
{
|
|
15968
|
+
name: "projects",
|
|
15969
|
+
usage: "hubskillz projects [add|remove [DIR] | discover [--yes] | list]",
|
|
15970
|
+
summary: "Repos that `sync --all` and `status` cover",
|
|
15971
|
+
flags: [{ spec: "-y, --yes", help: "discover: register everything found" }]
|
|
15972
|
+
}
|
|
15973
|
+
];
|
|
15974
|
+
function flagLines(flags) {
|
|
15975
|
+
const width = Math.max(...flags.map((flag) => flag.spec.length));
|
|
15976
|
+
return flags.map((flag) => ` ${flag.spec.padEnd(width)} ${dim(flag.help)}
|
|
15977
|
+
`).join("");
|
|
15978
|
+
}
|
|
15979
|
+
function usage() {
|
|
15980
|
+
const width = Math.max(...COMMANDS.map((command) => command.name.length));
|
|
15981
|
+
return `${bold("hubskillz")} ${dim(`v${"0.2.0"}`)} keep your agent skills in sync
|
|
15982
|
+
|
|
15983
|
+
${bold("Usage")}
|
|
15984
|
+
hubskillz <command> [flags]
|
|
15551
15985
|
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15986
|
+
${bold("Commands")}
|
|
15987
|
+
` + COMMANDS.map(
|
|
15988
|
+
(command) => ` ${command.name.padEnd(width)} ${dim(command.summary)}
|
|
15989
|
+
`
|
|
15990
|
+
).join("") + `
|
|
15991
|
+
${bold("Flags")}
|
|
15992
|
+
${flagLines(GLOBAL_FLAGS)}
|
|
15993
|
+
${dim("Environment: HUBSKILLZ_TOKEN, HUBSKILLZ_BASE_URL, NO_COLOR. Docs: https://hubskillz.com/docs/cli")}
|
|
15557
15994
|
`;
|
|
15995
|
+
}
|
|
15996
|
+
function commandHelp(command) {
|
|
15997
|
+
const flags = [...command.flags, ...GLOBAL_FLAGS];
|
|
15998
|
+
return `${bold("Usage")}
|
|
15999
|
+
${command.usage}
|
|
16000
|
+
|
|
16001
|
+
${command.summary}.
|
|
16002
|
+
|
|
16003
|
+
${bold("Flags")}
|
|
16004
|
+
${flagLines(flags)}`;
|
|
16005
|
+
}
|
|
16006
|
+
function help(name) {
|
|
16007
|
+
if (name === void 0) {
|
|
16008
|
+
process.stdout.write(usage());
|
|
16009
|
+
return Result.ok(void 0);
|
|
16010
|
+
}
|
|
16011
|
+
const command = COMMANDS.find((entry) => entry.name === name);
|
|
16012
|
+
if (command === void 0) {
|
|
16013
|
+
return Result.fail(
|
|
16014
|
+
new CliError(
|
|
16015
|
+
"USAGE",
|
|
16016
|
+
`Unknown command "${name}". Run \`hubskillz help\`.`
|
|
16017
|
+
)
|
|
16018
|
+
);
|
|
16019
|
+
}
|
|
16020
|
+
process.stdout.write(commandHelp(command));
|
|
16021
|
+
return Result.ok(void 0);
|
|
16022
|
+
}
|
|
16023
|
+
async function home() {
|
|
16024
|
+
const config2 = await readConfig();
|
|
16025
|
+
process.stdout.write(
|
|
16026
|
+
quickstartPending(config2) ? `${usage()}
|
|
16027
|
+
${quickstart(config2)}` : usage()
|
|
16028
|
+
);
|
|
16029
|
+
return Result.ok(void 0);
|
|
16030
|
+
}
|
|
15558
16031
|
async function run() {
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
16032
|
+
let values;
|
|
16033
|
+
let positionals;
|
|
16034
|
+
try {
|
|
16035
|
+
({ values, positionals } = parseArgs({
|
|
16036
|
+
args: process.argv.slice(2),
|
|
16037
|
+
allowPositionals: true,
|
|
16038
|
+
options: {
|
|
16039
|
+
"base-url": { type: "string" },
|
|
16040
|
+
token: { type: "string" },
|
|
16041
|
+
path: { type: "string" },
|
|
16042
|
+
all: { type: "boolean", default: false },
|
|
16043
|
+
adopt: { type: "boolean", default: false },
|
|
16044
|
+
yes: { type: "boolean", short: "y", default: false },
|
|
16045
|
+
"dry-run": { type: "boolean", default: false },
|
|
16046
|
+
force: { type: "boolean", default: false },
|
|
16047
|
+
message: { type: "string", short: "m" },
|
|
16048
|
+
help: { type: "boolean", short: "h", default: false },
|
|
16049
|
+
version: { type: "boolean", short: "v", default: false }
|
|
16050
|
+
}
|
|
16051
|
+
}));
|
|
16052
|
+
} catch (cause) {
|
|
16053
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
16054
|
+
return Result.fail(
|
|
16055
|
+
new CliError("USAGE", `${detail}
|
|
16056
|
+
Run \`hubskillz help\` for usage.`)
|
|
16057
|
+
);
|
|
16058
|
+
}
|
|
15574
16059
|
if (values.version === true) {
|
|
15575
|
-
process.stdout.write(`${"0.
|
|
16060
|
+
process.stdout.write(`${"0.2.0"}
|
|
15576
16061
|
`);
|
|
15577
16062
|
return Result.ok(void 0);
|
|
15578
16063
|
}
|
|
15579
16064
|
const command = positionals[0];
|
|
15580
|
-
if (
|
|
15581
|
-
|
|
15582
|
-
|
|
16065
|
+
if (command === void 0)
|
|
16066
|
+
return values.help === true ? help(void 0) : home();
|
|
16067
|
+
if (values.help === true || command === "help") {
|
|
16068
|
+
return help(command === "help" ? positionals[1] : command);
|
|
15583
16069
|
}
|
|
15584
16070
|
switch (command) {
|
|
15585
16071
|
case "login":
|
|
15586
|
-
return login({ baseUrl: values["base-url"] });
|
|
16072
|
+
return login({ baseUrl: values["base-url"], token: values.token });
|
|
16073
|
+
case "logout":
|
|
16074
|
+
return logout();
|
|
15587
16075
|
case "status":
|
|
15588
|
-
return status({
|
|
16076
|
+
return status({
|
|
16077
|
+
baseUrl: values["base-url"],
|
|
16078
|
+
path: values.path,
|
|
16079
|
+
yes: values.yes === true
|
|
16080
|
+
});
|
|
15589
16081
|
case "sync":
|
|
15590
16082
|
return sync({
|
|
15591
16083
|
baseUrl: values["base-url"],
|
|
15592
16084
|
path: values.path,
|
|
15593
16085
|
all: values.all === true,
|
|
16086
|
+
adopt: values.adopt === true,
|
|
15594
16087
|
yes: values.yes === true,
|
|
15595
16088
|
dryRun: values["dry-run"] === true,
|
|
15596
16089
|
force: values.force === true
|
|
15597
16090
|
});
|
|
16091
|
+
case "projects":
|
|
16092
|
+
return projects({
|
|
16093
|
+
action: positionals[1],
|
|
16094
|
+
dir: positionals[2],
|
|
16095
|
+
yes: values.yes === true
|
|
16096
|
+
});
|
|
15598
16097
|
case "push": {
|
|
15599
16098
|
const dir = positionals[1];
|
|
15600
16099
|
if (dir === void 0) {
|
|
15601
16100
|
return Result.fail(
|
|
15602
|
-
new CliError(
|
|
16101
|
+
new CliError(
|
|
16102
|
+
"USAGE",
|
|
16103
|
+
"hubskillz push needs a skill directory. Run `hubskillz help push`."
|
|
16104
|
+
)
|
|
15603
16105
|
);
|
|
15604
16106
|
}
|
|
15605
16107
|
return push({
|
|
@@ -15610,8 +16112,10 @@ async function run() {
|
|
|
15610
16112
|
}
|
|
15611
16113
|
default:
|
|
15612
16114
|
return Result.fail(
|
|
15613
|
-
new CliError(
|
|
15614
|
-
|
|
16115
|
+
new CliError(
|
|
16116
|
+
"USAGE",
|
|
16117
|
+
`Unknown command "${command}". Run \`hubskillz help\`.`
|
|
16118
|
+
)
|
|
15615
16119
|
);
|
|
15616
16120
|
}
|
|
15617
16121
|
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hubskillz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Keep your Claude Code skills in sync with your organization's approved catalog.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": "Jérôme Desmares",
|
|
7
|
+
"homepage": "https://hubskillz.com/docs/cli",
|
|
6
8
|
"repository": {
|
|
7
9
|
"type": "git",
|
|
8
10
|
"url": "https://github.com/JejeDurden2/hubskillz",
|
|
9
11
|
"directory": "packages/cli"
|
|
10
12
|
},
|
|
11
|
-
"
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/JejeDurden2/hubskillz/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"claude-code",
|
|
18
|
+
"claude",
|
|
19
|
+
"skills",
|
|
20
|
+
"agent-skills",
|
|
21
|
+
"cli",
|
|
22
|
+
"sync",
|
|
23
|
+
"skills.sh"
|
|
24
|
+
],
|
|
12
25
|
"type": "module",
|
|
13
26
|
"bin": {
|
|
14
27
|
"hubskillz": "./dist/index.js"
|
|
15
28
|
},
|
|
16
29
|
"main": "./dist/index.js",
|
|
17
30
|
"files": [
|
|
18
|
-
"dist"
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
19
34
|
],
|
|
20
35
|
"engines": {
|
|
21
36
|
"node": ">=22"
|
|
@@ -23,19 +38,19 @@
|
|
|
23
38
|
"publishConfig": {
|
|
24
39
|
"access": "public"
|
|
25
40
|
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^26.3.0",
|
|
43
|
+
"esbuild": "0.28.2",
|
|
44
|
+
"typescript": "^7.0.2",
|
|
45
|
+
"vitest": "^4.1.11",
|
|
46
|
+
"zod": "^4.4.3",
|
|
47
|
+
"@hubskillz/shared": "0.0.1"
|
|
48
|
+
},
|
|
26
49
|
"scripts": {
|
|
27
50
|
"dev": "pnpm build --watch",
|
|
28
51
|
"build": "esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --alias:@hubskillz/shared=../shared/src/index.ts --outfile=dist/index.js --define:__VERSION__=\"'$npm_package_version'\" && chmod +x dist/index.js",
|
|
29
52
|
"lint": "oxlint src/",
|
|
30
53
|
"type-check": "tsc --noEmit",
|
|
31
54
|
"test": "vitest run"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@hubskillz/shared": "workspace:*",
|
|
35
|
-
"@types/node": "^26.3.0",
|
|
36
|
-
"esbuild": "0.28.2",
|
|
37
|
-
"typescript": "^7.0.2",
|
|
38
|
-
"vitest": "^4.1.11",
|
|
39
|
-
"zod": "^4.4.3"
|
|
40
55
|
}
|
|
41
|
-
}
|
|
56
|
+
}
|