@stefafafan/skm 0.1.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 +130 -0
- package/dist/src/cli.js +7 -0
- package/dist/src/commands/add.js +149 -0
- package/dist/src/commands/init.js +33 -0
- package/dist/src/commands/inspect.js +58 -0
- package/dist/src/commands/install.js +114 -0
- package/dist/src/commands/list.js +72 -0
- package/dist/src/commands/remove.js +36 -0
- package/dist/src/commands/rename.js +66 -0
- package/dist/src/commands/update.js +92 -0
- package/dist/src/errors.js +16 -0
- package/dist/src/fs.js +64 -0
- package/dist/src/git.js +30 -0
- package/dist/src/hash.js +20 -0
- package/dist/src/index.js +321 -0
- package/dist/src/manifest.js +93 -0
- package/dist/src/materialize.js +75 -0
- package/dist/src/output.js +32 -0
- package/dist/src/scope.js +72 -0
- package/dist/src/source.js +156 -0
- package/dist/src/store.js +20 -0
- package/dist/src/ui/render.js +83 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 stefafafan
|
|
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
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# skm
|
|
2
|
+
|
|
3
|
+
`skm` is a package manager of [Agent Skills](https://agentskills.io), supporting both project and global-level skills.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> This package is in beta. There may be breaking changes.
|
|
7
|
+
|
|
8
|
+
## Motivation
|
|
9
|
+
|
|
10
|
+
Agent Skills are convenient, but there are 2 points to consider:
|
|
11
|
+
|
|
12
|
+
1. Which version skill did I just download? How should I update them?
|
|
13
|
+
2. People make skills with different naming conventions--should I rename them?
|
|
14
|
+
|
|
15
|
+
`skm` solves these problems by storing metadata of installed skills and having the ability to alias skills with your favorite names.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Globally
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g @stefafafan/skm
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Run Directly
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @stefafafan/skm
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
### For projects
|
|
34
|
+
|
|
35
|
+
Initialize a project with `skills.json` and `skills.lock.json`
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
skm init --project
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Add your favorite skills.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
skm add https://github.com/stefafafan/skills --project
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Rename a skill locally if you prefer a different key name.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
skm rename pin-github-actions gha-pinner --project
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Add and commit your files.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git add skills.json skills.lock.json
|
|
57
|
+
git commit
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### For global settings
|
|
61
|
+
|
|
62
|
+
Basically the same, but with `--global`
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
skm init --global
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
skm add stefafafan/skills --global
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
skm list --global
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Commands
|
|
77
|
+
|
|
78
|
+
Call `help` subcommand for more information.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
skm help
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Show help for specific subcommands.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
skm help add
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Supported source formats
|
|
91
|
+
|
|
92
|
+
Currently this tool assumes the skills are on GitHub.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Installing a skill in a specific path.
|
|
96
|
+
skm add https://github.com/stefafafan/skills/tree/main/skills/pin-github-actions
|
|
97
|
+
|
|
98
|
+
# Installing all skills in a repository (shorthand)
|
|
99
|
+
skm add stefafafan/skills
|
|
100
|
+
|
|
101
|
+
# Installing all skills in a repository (full URL)
|
|
102
|
+
skm add https://github.com/stefafafan/skills
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Repository-wide imports discover every nested directory containing `SKILL.md`.
|
|
106
|
+
|
|
107
|
+
## Metadata files used
|
|
108
|
+
|
|
109
|
+
The following files are used by `skm`
|
|
110
|
+
|
|
111
|
+
- `skills.json`
|
|
112
|
+
- user-facing intent
|
|
113
|
+
- `skills.lock.json`
|
|
114
|
+
- resolved commit hash and integrity data
|
|
115
|
+
- `.skm/`
|
|
116
|
+
- internal state and stored contents
|
|
117
|
+
- `.agents/skills/`
|
|
118
|
+
- the derived skills
|
|
119
|
+
|
|
120
|
+
The intended manifest-first flow is:
|
|
121
|
+
|
|
122
|
+
1. Change `skills.json` with `skm` commands or by editing it directly.
|
|
123
|
+
2. Run `skm install --project`.
|
|
124
|
+
3. Let `skills.lock.json` and `.agents/skills/` reconcile automatically.
|
|
125
|
+
|
|
126
|
+
It is recommended to add the following to `.gitignore`
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
.skm
|
|
130
|
+
```
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runAddCommand = runAddCommand;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const errors_1 = require("../errors");
|
|
11
|
+
const hash_1 = require("../hash");
|
|
12
|
+
const manifest_1 = require("../manifest");
|
|
13
|
+
const scope_1 = require("../scope");
|
|
14
|
+
const source_1 = require("../source");
|
|
15
|
+
const store_1 = require("../store");
|
|
16
|
+
const materialize_1 = require("../materialize");
|
|
17
|
+
const fs_1 = require("../fs");
|
|
18
|
+
async function runAddCommand(options) {
|
|
19
|
+
const scope = await (0, scope_1.resolveScope)({
|
|
20
|
+
cwd: options.cwd,
|
|
21
|
+
homeDir: options.homeDir,
|
|
22
|
+
xdgConfigHome: options.xdgConfigHome,
|
|
23
|
+
explicitScope: options.scope,
|
|
24
|
+
});
|
|
25
|
+
const manifest = await (0, manifest_1.readManifest)(scope.manifestPath);
|
|
26
|
+
const lockfile = await (0, manifest_1.readLockfile)(scope.lockfilePath);
|
|
27
|
+
const parsedSource = (0, source_1.parseSource)(options.source);
|
|
28
|
+
const requestedRef = options.requestedRef ?? (0, source_1.defaultRequestedRef)(parsedSource);
|
|
29
|
+
const tempRoot = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), "skm-add-"));
|
|
30
|
+
try {
|
|
31
|
+
if (parsedSource.kind === "github-repo") {
|
|
32
|
+
if (options.canonicalName) {
|
|
33
|
+
throw new errors_1.SkmError("--as is not supported for repo-wide imports", 2);
|
|
34
|
+
}
|
|
35
|
+
const checkedOut = await (0, source_1.checkoutSourceRepo)({
|
|
36
|
+
source: parsedSource,
|
|
37
|
+
requestedRef,
|
|
38
|
+
githubBaseUrl: options.githubBaseUrl,
|
|
39
|
+
}, tempRoot);
|
|
40
|
+
const discoveredSkills = await (0, source_1.discoverSkillsInRepo)(checkedOut.checkoutDir);
|
|
41
|
+
if (discoveredSkills.length === 0) {
|
|
42
|
+
throw new errors_1.SkmError(`No skills found in repository ${options.source}`, 4);
|
|
43
|
+
}
|
|
44
|
+
const seenCanonicalNames = new Set();
|
|
45
|
+
for (const discoveredSkill of discoveredSkills) {
|
|
46
|
+
if (seenCanonicalNames.has(discoveredSkill.canonicalName)) {
|
|
47
|
+
throw new errors_1.SkmError(`Duplicate canonical name discovered: ${discoveredSkill.canonicalName}`, 5);
|
|
48
|
+
}
|
|
49
|
+
seenCanonicalNames.add(discoveredSkill.canonicalName);
|
|
50
|
+
}
|
|
51
|
+
const strategy = options.strategy ?? "wrap";
|
|
52
|
+
const addedSkills = [];
|
|
53
|
+
for (const discoveredSkill of discoveredSkills) {
|
|
54
|
+
const integrity = await (0, hash_1.hashDirectory)(discoveredSkill.absoluteDir);
|
|
55
|
+
const storeDir = await (0, store_1.storeSkill)(scope.storeDir, discoveredSkill.absoluteDir, integrity);
|
|
56
|
+
const canonicalSource = (0, source_1.canonicalTreeUrl)(parsedSource, requestedRef, discoveredSkill.relativeDir);
|
|
57
|
+
manifest.skills[discoveredSkill.canonicalName] = {
|
|
58
|
+
source: canonicalSource,
|
|
59
|
+
requested: requestedRef,
|
|
60
|
+
strategy,
|
|
61
|
+
};
|
|
62
|
+
lockfile.skills[discoveredSkill.canonicalName] = {
|
|
63
|
+
resolved: checkedOut.resolved,
|
|
64
|
+
integrity,
|
|
65
|
+
};
|
|
66
|
+
await (0, materialize_1.materializeSkill)({
|
|
67
|
+
canonicalName: discoveredSkill.canonicalName,
|
|
68
|
+
sourceDir: storeDir,
|
|
69
|
+
generatedSkillsDir: scope.generatedSkillsDir,
|
|
70
|
+
manifestSource: options.source,
|
|
71
|
+
resolved: checkedOut.resolved,
|
|
72
|
+
strategy,
|
|
73
|
+
});
|
|
74
|
+
addedSkills.push({
|
|
75
|
+
name: discoveredSkill.canonicalName,
|
|
76
|
+
status: "added",
|
|
77
|
+
source: canonicalSource,
|
|
78
|
+
requested: requestedRef,
|
|
79
|
+
resolved: checkedOut.resolved,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
await (0, manifest_1.writeManifest)(scope.manifestPath, manifest);
|
|
83
|
+
await (0, manifest_1.writeLockfile)(scope.lockfilePath, lockfile);
|
|
84
|
+
return {
|
|
85
|
+
kind: "summary",
|
|
86
|
+
command: "add",
|
|
87
|
+
scope: scope.kind,
|
|
88
|
+
summary: `Added ${discoveredSkills.length} skill(s) to ${scope.kind} scope`,
|
|
89
|
+
details: [
|
|
90
|
+
{ label: "source", value: options.source },
|
|
91
|
+
{ label: "requested", value: requestedRef },
|
|
92
|
+
],
|
|
93
|
+
skills: addedSkills,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const fetched = await (0, source_1.fetchSkillToTempDir)({
|
|
97
|
+
source: parsedSource,
|
|
98
|
+
requestedRef,
|
|
99
|
+
githubBaseUrl: options.githubBaseUrl,
|
|
100
|
+
}, tempRoot);
|
|
101
|
+
const integrity = await (0, hash_1.hashDirectory)(fetched.skillDir);
|
|
102
|
+
const storeDir = await (0, store_1.storeSkill)(scope.storeDir, fetched.skillDir, integrity);
|
|
103
|
+
const canonicalName = options.canonicalName ?? parsedSource.defaultName;
|
|
104
|
+
const strategy = options.strategy ?? "wrap";
|
|
105
|
+
manifest.skills[canonicalName] = {
|
|
106
|
+
source: options.source,
|
|
107
|
+
requested: requestedRef,
|
|
108
|
+
strategy,
|
|
109
|
+
};
|
|
110
|
+
lockfile.skills[canonicalName] = {
|
|
111
|
+
resolved: fetched.resolved,
|
|
112
|
+
integrity,
|
|
113
|
+
};
|
|
114
|
+
await (0, manifest_1.writeManifest)(scope.manifestPath, manifest);
|
|
115
|
+
await (0, manifest_1.writeLockfile)(scope.lockfilePath, lockfile);
|
|
116
|
+
await (0, materialize_1.materializeSkill)({
|
|
117
|
+
canonicalName,
|
|
118
|
+
sourceDir: storeDir,
|
|
119
|
+
generatedSkillsDir: scope.generatedSkillsDir,
|
|
120
|
+
manifestSource: options.source,
|
|
121
|
+
resolved: fetched.resolved,
|
|
122
|
+
strategy,
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
kind: "summary",
|
|
126
|
+
command: "add",
|
|
127
|
+
scope: scope.kind,
|
|
128
|
+
summary: `Added ${canonicalName} to ${scope.kind} scope`,
|
|
129
|
+
details: [
|
|
130
|
+
{ label: "source", value: options.source },
|
|
131
|
+
{ label: "requested", value: requestedRef },
|
|
132
|
+
{ label: "strategy", value: strategy },
|
|
133
|
+
],
|
|
134
|
+
skills: [
|
|
135
|
+
{
|
|
136
|
+
name: canonicalName,
|
|
137
|
+
status: "added",
|
|
138
|
+
source: options.source,
|
|
139
|
+
requested: requestedRef,
|
|
140
|
+
resolved: fetched.resolved,
|
|
141
|
+
integrity,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await (0, fs_1.removeIfExists)(tempRoot);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runInitCommand = runInitCommand;
|
|
4
|
+
const manifest_1 = require("../manifest");
|
|
5
|
+
const scope_1 = require("../scope");
|
|
6
|
+
async function runInitCommand(options) {
|
|
7
|
+
const scope = options.scope === "global"
|
|
8
|
+
? await (0, scope_1.resolveScope)({
|
|
9
|
+
cwd: options.cwd,
|
|
10
|
+
homeDir: options.homeDir,
|
|
11
|
+
xdgConfigHome: options.xdgConfigHome,
|
|
12
|
+
explicitScope: "global",
|
|
13
|
+
})
|
|
14
|
+
: await (0, scope_1.resolveScope)({
|
|
15
|
+
cwd: options.cwd,
|
|
16
|
+
homeDir: options.homeDir,
|
|
17
|
+
xdgConfigHome: options.xdgConfigHome,
|
|
18
|
+
explicitScope: "project",
|
|
19
|
+
allowCreateProject: true,
|
|
20
|
+
});
|
|
21
|
+
await (0, manifest_1.initManifest)(scope.manifestPath, options.force);
|
|
22
|
+
await (0, manifest_1.initLockfile)(scope.lockfilePath, options.force);
|
|
23
|
+
return {
|
|
24
|
+
kind: "summary",
|
|
25
|
+
command: "init",
|
|
26
|
+
scope: scope.kind,
|
|
27
|
+
summary: `Initialized ${scope.kind} manifest at ${scope.manifestPath}`,
|
|
28
|
+
details: [
|
|
29
|
+
{ label: "manifest", value: scope.manifestPath },
|
|
30
|
+
{ label: "lockfile", value: scope.lockfilePath },
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInspectCommand = runInspectCommand;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const errors_1 = require("../errors");
|
|
9
|
+
const manifest_1 = require("../manifest");
|
|
10
|
+
const scope_1 = require("../scope");
|
|
11
|
+
async function runInspectCommand(options) {
|
|
12
|
+
const scope = await (0, scope_1.resolveScope)({
|
|
13
|
+
cwd: options.cwd,
|
|
14
|
+
homeDir: options.homeDir,
|
|
15
|
+
xdgConfigHome: options.xdgConfigHome,
|
|
16
|
+
explicitScope: options.scope,
|
|
17
|
+
});
|
|
18
|
+
const manifest = await (0, manifest_1.readManifest)(scope.manifestPath);
|
|
19
|
+
const lockfile = await (0, manifest_1.readLockfile)(scope.lockfilePath);
|
|
20
|
+
const entry = (0, manifest_1.mergeSkillState)(manifest, lockfile)[options.canonicalName];
|
|
21
|
+
if (!entry) {
|
|
22
|
+
throw new errors_1.SkmError(`Skill ${options.canonicalName} not found in ${scope.kind} scope`, 1);
|
|
23
|
+
}
|
|
24
|
+
let overriddenByProjectSkill = "no";
|
|
25
|
+
if (scope.kind === "global") {
|
|
26
|
+
const projectRoot = await (0, scope_1.findProjectRoot)(options.cwd);
|
|
27
|
+
if (projectRoot) {
|
|
28
|
+
try {
|
|
29
|
+
const projectManifest = await (0, manifest_1.readManifest)((0, scope_1.projectScope)(projectRoot).manifestPath);
|
|
30
|
+
if (projectManifest.skills[options.canonicalName]) {
|
|
31
|
+
overriddenByProjectSkill = "yes";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
overriddenByProjectSkill = "no";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
kind: "inspect",
|
|
41
|
+
name: options.canonicalName,
|
|
42
|
+
scope: scope.kind,
|
|
43
|
+
details: [
|
|
44
|
+
{ label: "canonical local name", value: options.canonicalName },
|
|
45
|
+
{ label: "scope", value: scope.kind },
|
|
46
|
+
{ label: "source", value: entry.source },
|
|
47
|
+
{ label: "requested ref", value: entry.requested ?? "" },
|
|
48
|
+
{ label: "resolved commit", value: entry.resolved ?? "" },
|
|
49
|
+
{ label: "integrity hash", value: entry.integrity ?? "" },
|
|
50
|
+
{
|
|
51
|
+
label: "materialized path",
|
|
52
|
+
value: node_path_1.default.join(scope.generatedSkillsDir, options.canonicalName),
|
|
53
|
+
},
|
|
54
|
+
{ label: "strategy", value: entry.strategy ?? "wrap" },
|
|
55
|
+
{ label: "overridden by project skill", value: overriddenByProjectSkill },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInstallCommand = runInstallCommand;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const errors_1 = require("../errors");
|
|
11
|
+
const fs_1 = require("../fs");
|
|
12
|
+
const hash_1 = require("../hash");
|
|
13
|
+
const materialize_1 = require("../materialize");
|
|
14
|
+
const manifest_1 = require("../manifest");
|
|
15
|
+
const scope_1 = require("../scope");
|
|
16
|
+
const source_1 = require("../source");
|
|
17
|
+
const store_1 = require("../store");
|
|
18
|
+
async function runInstallCommand(options) {
|
|
19
|
+
const scope = await (0, scope_1.resolveScope)({
|
|
20
|
+
cwd: options.cwd,
|
|
21
|
+
homeDir: options.homeDir,
|
|
22
|
+
xdgConfigHome: options.xdgConfigHome,
|
|
23
|
+
explicitScope: options.scope,
|
|
24
|
+
});
|
|
25
|
+
const manifest = await (0, manifest_1.readManifest)(scope.manifestPath);
|
|
26
|
+
const lockfile = await (0, manifest_1.readLockfile)(scope.lockfilePath);
|
|
27
|
+
const tempRoot = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), "skm-install-"));
|
|
28
|
+
const reconciledSkills = [];
|
|
29
|
+
try {
|
|
30
|
+
for (const name of Object.keys(lockfile.skills)) {
|
|
31
|
+
if (!(name in manifest.skills)) {
|
|
32
|
+
delete lockfile.skills[name];
|
|
33
|
+
await (0, fs_1.removeIfExists)(node_path_1.default.join(scope.generatedSkillsDir, name));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const [canonicalName, entry] of Object.entries(manifest.skills)) {
|
|
37
|
+
let lockEntry = lockfile.skills[canonicalName];
|
|
38
|
+
const parsedSource = (0, source_1.parseSource)(entry.source);
|
|
39
|
+
const requestedRef = entry.requested ?? (0, source_1.defaultRequestedRef)(parsedSource);
|
|
40
|
+
if (!lockEntry) {
|
|
41
|
+
const fetched = await (0, source_1.fetchSkillToTempDir)({
|
|
42
|
+
source: parsedSource,
|
|
43
|
+
requestedRef,
|
|
44
|
+
githubBaseUrl: options.githubBaseUrl,
|
|
45
|
+
}, tempRoot);
|
|
46
|
+
const integrity = await (0, hash_1.hashDirectory)(fetched.skillDir);
|
|
47
|
+
const storedPath = await (0, store_1.storeSkill)(scope.storeDir, fetched.skillDir, integrity);
|
|
48
|
+
lockEntry = {
|
|
49
|
+
resolved: fetched.resolved,
|
|
50
|
+
integrity,
|
|
51
|
+
};
|
|
52
|
+
lockfile.skills[canonicalName] = lockEntry;
|
|
53
|
+
await (0, materialize_1.materializeSkill)({
|
|
54
|
+
canonicalName,
|
|
55
|
+
sourceDir: storedPath,
|
|
56
|
+
generatedSkillsDir: scope.generatedSkillsDir,
|
|
57
|
+
manifestSource: entry.source,
|
|
58
|
+
resolved: lockEntry.resolved,
|
|
59
|
+
strategy: entry.strategy ?? "wrap",
|
|
60
|
+
});
|
|
61
|
+
reconciledSkills.push({
|
|
62
|
+
name: canonicalName,
|
|
63
|
+
status: "installed",
|
|
64
|
+
source: entry.source,
|
|
65
|
+
requested: requestedRef,
|
|
66
|
+
resolved: lockEntry.resolved,
|
|
67
|
+
integrity,
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const integrity = lockEntry.integrity;
|
|
72
|
+
let materialSource = (0, store_1.storePath)(scope.storeDir, integrity);
|
|
73
|
+
if (!(await (0, fs_1.pathExists)(materialSource))) {
|
|
74
|
+
const fetched = await (0, source_1.fetchSkillToTempDir)({
|
|
75
|
+
source: parsedSource,
|
|
76
|
+
requestedRef: lockEntry.resolved,
|
|
77
|
+
githubBaseUrl: options.githubBaseUrl,
|
|
78
|
+
}, tempRoot);
|
|
79
|
+
const actualIntegrity = await (0, hash_1.hashDirectory)(fetched.skillDir);
|
|
80
|
+
if (actualIntegrity !== integrity) {
|
|
81
|
+
throw new errors_1.SkmError(`Integrity mismatch for ${canonicalName}: expected ${integrity}, got ${actualIntegrity}`, 4);
|
|
82
|
+
}
|
|
83
|
+
materialSource = await (0, store_1.storeSkill)(scope.storeDir, fetched.skillDir, integrity);
|
|
84
|
+
}
|
|
85
|
+
await (0, materialize_1.materializeSkill)({
|
|
86
|
+
canonicalName,
|
|
87
|
+
sourceDir: materialSource,
|
|
88
|
+
generatedSkillsDir: scope.generatedSkillsDir,
|
|
89
|
+
manifestSource: entry.source,
|
|
90
|
+
resolved: lockEntry.resolved,
|
|
91
|
+
strategy: entry.strategy ?? "wrap",
|
|
92
|
+
});
|
|
93
|
+
reconciledSkills.push({
|
|
94
|
+
name: canonicalName,
|
|
95
|
+
status: "installed",
|
|
96
|
+
source: entry.source,
|
|
97
|
+
requested: requestedRef,
|
|
98
|
+
resolved: lockEntry.resolved,
|
|
99
|
+
integrity,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
await (0, manifest_1.writeLockfile)(scope.lockfilePath, lockfile);
|
|
103
|
+
return {
|
|
104
|
+
kind: "summary",
|
|
105
|
+
command: "install",
|
|
106
|
+
scope: scope.kind,
|
|
107
|
+
summary: `Installed ${Object.keys(manifest.skills).length} skill(s) for ${scope.kind} scope`,
|
|
108
|
+
skills: reconciledSkills,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await (0, fs_1.removeIfExists)(tempRoot);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runListCommand = runListCommand;
|
|
4
|
+
const manifest_1 = require("../manifest");
|
|
5
|
+
const scope_1 = require("../scope");
|
|
6
|
+
async function runListCommand(options) {
|
|
7
|
+
const listedSkills = await collectSkills(options);
|
|
8
|
+
const projectNames = new Set(listedSkills.filter((skill) => skill.scope === "project").map((skill) => skill.name));
|
|
9
|
+
const rows = listedSkills.map((skill) => ({
|
|
10
|
+
name: skill.name,
|
|
11
|
+
scope: skill.scope,
|
|
12
|
+
source: skill.entry.source,
|
|
13
|
+
requested: skill.entry.requested,
|
|
14
|
+
resolved: skill.entry.resolved,
|
|
15
|
+
effective: skill.scope === "global" && projectNames.has(skill.name) ? "overridden" : "active",
|
|
16
|
+
}));
|
|
17
|
+
return {
|
|
18
|
+
kind: "list",
|
|
19
|
+
all: options.all,
|
|
20
|
+
rows,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function collectSkills(options) {
|
|
24
|
+
if (!options.all) {
|
|
25
|
+
const scope = await (0, scope_1.resolveScope)({
|
|
26
|
+
cwd: options.cwd,
|
|
27
|
+
homeDir: options.homeDir,
|
|
28
|
+
xdgConfigHome: options.xdgConfigHome,
|
|
29
|
+
explicitScope: options.scope,
|
|
30
|
+
});
|
|
31
|
+
const manifest = await (0, manifest_1.readManifest)(scope.manifestPath);
|
|
32
|
+
const lockfile = await (0, manifest_1.readLockfile)(scope.lockfilePath);
|
|
33
|
+
return Object.entries((0, manifest_1.mergeSkillState)(manifest, lockfile)).map(([name, entry]) => ({
|
|
34
|
+
name,
|
|
35
|
+
scope: scope.kind,
|
|
36
|
+
entry,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
const homeDir = options.homeDir ?? process.env.HOME;
|
|
40
|
+
const projectRoot = await (0, scope_1.findProjectRoot)(options.cwd);
|
|
41
|
+
const manifests = [];
|
|
42
|
+
if (homeDir) {
|
|
43
|
+
const global = (0, scope_1.globalScope)(homeDir, options.xdgConfigHome);
|
|
44
|
+
manifests.push({
|
|
45
|
+
scope: "global",
|
|
46
|
+
manifestPath: global.manifestPath,
|
|
47
|
+
lockfilePath: global.lockfilePath,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (projectRoot) {
|
|
51
|
+
const project = (0, scope_1.projectScope)(projectRoot);
|
|
52
|
+
manifests.push({
|
|
53
|
+
scope: "project",
|
|
54
|
+
manifestPath: project.manifestPath,
|
|
55
|
+
lockfilePath: project.lockfilePath,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const rows = [];
|
|
59
|
+
for (const manifestTarget of manifests) {
|
|
60
|
+
try {
|
|
61
|
+
const manifest = await (0, manifest_1.readManifest)(manifestTarget.manifestPath);
|
|
62
|
+
const lockfile = await (0, manifest_1.readLockfile)(manifestTarget.lockfilePath);
|
|
63
|
+
for (const [name, entry] of Object.entries((0, manifest_1.mergeSkillState)(manifest, lockfile))) {
|
|
64
|
+
rows.push({ name, scope: manifestTarget.scope, entry });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return rows.sort((left, right) => left.name.localeCompare(right.name) || left.scope.localeCompare(right.scope));
|
|
72
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runRemoveCommand = runRemoveCommand;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const errors_1 = require("../errors");
|
|
9
|
+
const fs_1 = require("../fs");
|
|
10
|
+
const manifest_1 = require("../manifest");
|
|
11
|
+
const scope_1 = require("../scope");
|
|
12
|
+
async function runRemoveCommand(options) {
|
|
13
|
+
const scope = await (0, scope_1.resolveScope)({
|
|
14
|
+
cwd: options.cwd,
|
|
15
|
+
homeDir: options.homeDir,
|
|
16
|
+
xdgConfigHome: options.xdgConfigHome,
|
|
17
|
+
explicitScope: options.scope,
|
|
18
|
+
});
|
|
19
|
+
const manifest = await (0, manifest_1.readManifest)(scope.manifestPath);
|
|
20
|
+
const lockfile = await (0, manifest_1.readLockfile)(scope.lockfilePath);
|
|
21
|
+
if (!(options.canonicalName in manifest.skills)) {
|
|
22
|
+
throw new errors_1.SkmError(`Skill ${options.canonicalName} not found in ${scope.kind} scope`, 1);
|
|
23
|
+
}
|
|
24
|
+
delete manifest.skills[options.canonicalName];
|
|
25
|
+
delete lockfile.skills[options.canonicalName];
|
|
26
|
+
await (0, manifest_1.writeManifest)(scope.manifestPath, manifest);
|
|
27
|
+
await (0, manifest_1.writeLockfile)(scope.lockfilePath, lockfile);
|
|
28
|
+
await (0, fs_1.removeIfExists)(node_path_1.default.join(scope.generatedSkillsDir, options.canonicalName));
|
|
29
|
+
return {
|
|
30
|
+
kind: "summary",
|
|
31
|
+
command: "remove",
|
|
32
|
+
scope: scope.kind,
|
|
33
|
+
summary: `Removed ${options.canonicalName} from ${scope.kind} scope`,
|
|
34
|
+
skills: [{ name: options.canonicalName, status: "removed" }],
|
|
35
|
+
};
|
|
36
|
+
}
|