@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 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
+ ```
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_1 = require("./index");
5
+ void (0, index_1.main)(process.argv.slice(2)).then((code) => {
6
+ process.exitCode = code;
7
+ });
@@ -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
+ }