@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.
@@ -0,0 +1,66 @@
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.runRenameCommand = runRenameCommand;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const errors_1 = require("../errors");
9
+ const materialize_1 = require("../materialize");
10
+ const manifest_1 = require("../manifest");
11
+ const scope_1 = require("../scope");
12
+ const store_1 = require("../store");
13
+ const fs_1 = require("../fs");
14
+ async function runRenameCommand(options) {
15
+ const scope = await (0, scope_1.resolveScope)({
16
+ cwd: options.cwd,
17
+ homeDir: options.homeDir,
18
+ xdgConfigHome: options.xdgConfigHome,
19
+ explicitScope: options.scope,
20
+ });
21
+ const manifest = await (0, manifest_1.readManifest)(scope.manifestPath);
22
+ const lockfile = await (0, manifest_1.readLockfile)(scope.lockfilePath);
23
+ const manifestEntry = manifest.skills[options.oldName];
24
+ const lockEntry = lockfile.skills[options.oldName];
25
+ if (!manifestEntry) {
26
+ throw new errors_1.SkmError(`Skill ${options.oldName} not found in ${scope.kind} scope`, 1);
27
+ }
28
+ if (manifest.skills[options.newName]) {
29
+ throw new errors_1.SkmError(`Skill ${options.newName} already exists in ${scope.kind} scope`, 5);
30
+ }
31
+ if (!lockEntry) {
32
+ throw new errors_1.SkmError(`Skill ${options.oldName} is missing lockfile state`, 2);
33
+ }
34
+ delete manifest.skills[options.oldName];
35
+ manifest.skills[options.newName] = manifestEntry;
36
+ delete lockfile.skills[options.oldName];
37
+ lockfile.skills[options.newName] = lockEntry;
38
+ await (0, manifest_1.writeManifest)(scope.manifestPath, manifest);
39
+ await (0, manifest_1.writeLockfile)(scope.lockfilePath, lockfile);
40
+ await (0, materialize_1.materializeSkill)({
41
+ canonicalName: options.newName,
42
+ sourceDir: (0, store_1.storePath)(scope.storeDir, lockEntry.integrity),
43
+ generatedSkillsDir: scope.generatedSkillsDir,
44
+ manifestSource: manifestEntry.source,
45
+ resolved: lockEntry.resolved,
46
+ strategy: manifestEntry.strategy ?? "wrap",
47
+ });
48
+ await (0, fs_1.removeIfExists)(node_path_1.default.join(scope.generatedSkillsDir, options.oldName));
49
+ return {
50
+ kind: "summary",
51
+ command: "rename",
52
+ scope: scope.kind,
53
+ summary: `Renamed ${options.oldName} to ${options.newName} in ${scope.kind} scope`,
54
+ skills: [
55
+ {
56
+ name: options.newName,
57
+ previousName: options.oldName,
58
+ status: "renamed",
59
+ source: manifestEntry.source,
60
+ requested: manifestEntry.requested,
61
+ resolved: lockEntry.resolved,
62
+ integrity: lockEntry.integrity,
63
+ },
64
+ ],
65
+ };
66
+ }
@@ -0,0 +1,92 @@
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.runUpdateCommand = runUpdateCommand;
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 runUpdateCommand(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 names = options.canonicalName ? [options.canonicalName] : Object.keys(manifest.skills);
28
+ const tempRoot = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), "skm-update-"));
29
+ const updatedSkills = [];
30
+ try {
31
+ let updatedCount = 0;
32
+ for (const name of names) {
33
+ const entry = manifest.skills[name];
34
+ if (!entry) {
35
+ continue;
36
+ }
37
+ const lockEntry = lockfile.skills[name];
38
+ if (!lockEntry) {
39
+ throw new errors_1.SkmError(`Skill ${name} is missing lockfile state`, 2);
40
+ }
41
+ const requestedRef = entry.requested ?? (0, source_1.defaultRequestedRef)((0, source_1.parseSource)(entry.source));
42
+ if ((0, source_1.isFixedRef)(requestedRef) && !options.force) {
43
+ updatedSkills.push({
44
+ name,
45
+ status: "skipped",
46
+ source: entry.source,
47
+ requested: requestedRef,
48
+ resolved: lockEntry.resolved,
49
+ integrity: lockEntry.integrity,
50
+ });
51
+ continue;
52
+ }
53
+ const fetched = await (0, source_1.fetchSkillToTempDir)({
54
+ source: (0, source_1.parseSource)(entry.source),
55
+ requestedRef,
56
+ githubBaseUrl: options.githubBaseUrl,
57
+ }, tempRoot);
58
+ const integrity = await (0, hash_1.hashDirectory)(fetched.skillDir);
59
+ const storeDir = await (0, store_1.storeSkill)(scope.storeDir, fetched.skillDir, integrity);
60
+ lockEntry.resolved = fetched.resolved;
61
+ lockEntry.integrity = integrity;
62
+ await (0, materialize_1.materializeSkill)({
63
+ canonicalName: name,
64
+ sourceDir: storeDir,
65
+ generatedSkillsDir: scope.generatedSkillsDir,
66
+ manifestSource: entry.source,
67
+ resolved: lockEntry.resolved,
68
+ strategy: entry.strategy ?? "wrap",
69
+ });
70
+ updatedSkills.push({
71
+ name,
72
+ status: "updated",
73
+ source: entry.source,
74
+ requested: requestedRef,
75
+ resolved: lockEntry.resolved,
76
+ integrity,
77
+ });
78
+ updatedCount += 1;
79
+ }
80
+ await (0, manifest_1.writeLockfile)(scope.lockfilePath, lockfile);
81
+ return {
82
+ kind: "summary",
83
+ command: "update",
84
+ scope: scope.kind,
85
+ summary: `Updated ${updatedCount} skill(s) in ${scope.kind} scope`,
86
+ skills: updatedSkills,
87
+ };
88
+ }
89
+ finally {
90
+ await (0, fs_1.removeIfExists)(tempRoot);
91
+ }
92
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SkmError = void 0;
4
+ exports.isSkmError = isSkmError;
5
+ class SkmError extends Error {
6
+ exitCode;
7
+ constructor(message, exitCode = 1) {
8
+ super(message);
9
+ this.name = "SkmError";
10
+ this.exitCode = exitCode;
11
+ }
12
+ }
13
+ exports.SkmError = SkmError;
14
+ function isSkmError(error) {
15
+ return error instanceof SkmError;
16
+ }
package/dist/src/fs.js ADDED
@@ -0,0 +1,64 @@
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.pathExists = pathExists;
7
+ exports.ensureDir = ensureDir;
8
+ exports.removeIfExists = removeIfExists;
9
+ exports.copyDirectory = copyDirectory;
10
+ exports.listFilesRecursive = listFilesRecursive;
11
+ exports.isDirectory = isDirectory;
12
+ const promises_1 = require("node:fs/promises");
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ async function pathExists(targetPath) {
15
+ try {
16
+ await (0, promises_1.stat)(targetPath);
17
+ return true;
18
+ }
19
+ catch (error) {
20
+ if (error.code === "ENOENT") {
21
+ return false;
22
+ }
23
+ throw error;
24
+ }
25
+ }
26
+ async function ensureDir(dirPath) {
27
+ await (0, promises_1.mkdir)(dirPath, { recursive: true });
28
+ }
29
+ async function removeIfExists(targetPath) {
30
+ await (0, promises_1.rm)(targetPath, { recursive: true, force: true });
31
+ }
32
+ async function copyDirectory(sourceDir, destinationDir) {
33
+ await removeIfExists(destinationDir);
34
+ await ensureDir(node_path_1.default.dirname(destinationDir));
35
+ await (0, promises_1.cp)(sourceDir, destinationDir, { recursive: true, preserveTimestamps: true });
36
+ }
37
+ async function listFilesRecursive(rootDir) {
38
+ const entries = await (0, promises_1.readdir)(rootDir, { withFileTypes: true });
39
+ const files = [];
40
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
41
+ const fullPath = node_path_1.default.join(rootDir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ for (const nested of await listFilesRecursive(fullPath)) {
44
+ files.push(node_path_1.default.join(entry.name, nested));
45
+ }
46
+ continue;
47
+ }
48
+ if (entry.isFile() || entry.isSymbolicLink()) {
49
+ files.push(entry.name);
50
+ }
51
+ }
52
+ return files;
53
+ }
54
+ async function isDirectory(targetPath) {
55
+ try {
56
+ return (await (0, promises_1.lstat)(targetPath)).isDirectory();
57
+ }
58
+ catch (error) {
59
+ if (error.code === "ENOENT") {
60
+ return false;
61
+ }
62
+ throw error;
63
+ }
64
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runGit = runGit;
4
+ exports.cloneAndCheckout = cloneAndCheckout;
5
+ exports.readHeadCommit = readHeadCommit;
6
+ const node_child_process_1 = require("node:child_process");
7
+ const node_util_1 = require("node:util");
8
+ const errors_1 = require("./errors");
9
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
10
+ async function runGit(args, cwd) {
11
+ try {
12
+ const { stdout } = await execFileAsync("git", args, {
13
+ cwd,
14
+ maxBuffer: 10 * 1024 * 1024,
15
+ });
16
+ return stdout;
17
+ }
18
+ catch (error) {
19
+ const execError = error;
20
+ const detail = execError.stderr?.trim() || execError.message;
21
+ throw new errors_1.SkmError(`git ${args.join(" ")} failed: ${detail}`, 3);
22
+ }
23
+ }
24
+ async function cloneAndCheckout(repoUrl, ref, targetDir) {
25
+ await runGit(["clone", "--quiet", repoUrl, targetDir]);
26
+ await runGit(["checkout", "--quiet", ref], targetDir);
27
+ }
28
+ async function readHeadCommit(repoDir) {
29
+ return (await runGit(["rev-parse", "HEAD"], repoDir)).trim();
30
+ }
@@ -0,0 +1,20 @@
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.hashDirectory = hashDirectory;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const fs_1 = require("./fs");
11
+ async function hashDirectory(dirPath) {
12
+ const hash = (0, node_crypto_1.createHash)("sha256");
13
+ const files = await (0, fs_1.listFilesRecursive)(dirPath);
14
+ for (const relativePath of files) {
15
+ hash.update(`path:${relativePath}\n`);
16
+ hash.update(await (0, promises_1.readFile)(node_path_1.default.join(dirPath, relativePath)));
17
+ hash.update("\n");
18
+ }
19
+ return `sha256-${hash.digest("base64")}`;
20
+ }
@@ -0,0 +1,321 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const add_1 = require("./commands/add");
5
+ const init_1 = require("./commands/init");
6
+ const inspect_1 = require("./commands/inspect");
7
+ const install_1 = require("./commands/install");
8
+ const list_1 = require("./commands/list");
9
+ const rename_1 = require("./commands/rename");
10
+ const remove_1 = require("./commands/remove");
11
+ const update_1 = require("./commands/update");
12
+ const errors_1 = require("./errors");
13
+ const output_1 = require("./output");
14
+ const render_1 = require("./ui/render");
15
+ async function main(argv, context) {
16
+ const parsed = parseArgv(argv);
17
+ const cwd = context?.cwd ?? process.cwd();
18
+ const env = context?.env ?? process.env;
19
+ const stdoutIsTTY = context?.stdoutIsTTY ?? process.stdout.isTTY ?? false;
20
+ const stdoutColumns = context?.stdoutColumns ?? process.stdout.columns;
21
+ try {
22
+ const output = await dispatch(parsed, cwd, env);
23
+ if (output) {
24
+ const renderedOutput = stdoutIsTTY
25
+ ? await (0, render_1.renderCliResultWithInk)(output, { columns: stdoutColumns })
26
+ : (0, output_1.renderCliResultAsText)(output);
27
+ process.stdout.write(renderedOutput.endsWith("\n") ? renderedOutput : `${renderedOutput}\n`);
28
+ }
29
+ return 0;
30
+ }
31
+ catch (error) {
32
+ if ((0, errors_1.isSkmError)(error)) {
33
+ process.stderr.write(`${error.message}\n`);
34
+ return error.exitCode;
35
+ }
36
+ const unknown = error;
37
+ process.stderr.write(`${unknown.message}\n`);
38
+ return 1;
39
+ }
40
+ }
41
+ function parseArgv(argv) {
42
+ const parsed = {
43
+ positional: [],
44
+ all: false,
45
+ force: false,
46
+ help: false,
47
+ };
48
+ for (let index = 0; index < argv.length; index += 1) {
49
+ const token = argv[index];
50
+ if (token === undefined) {
51
+ continue;
52
+ }
53
+ if (token === "--global") {
54
+ parsed.scope = "global";
55
+ continue;
56
+ }
57
+ if (token === "--help" || token === "-h") {
58
+ parsed.help = true;
59
+ continue;
60
+ }
61
+ if (token === "--project") {
62
+ parsed.scope = "project";
63
+ continue;
64
+ }
65
+ if (token === "--all") {
66
+ parsed.all = true;
67
+ continue;
68
+ }
69
+ if (token === "--force") {
70
+ parsed.force = true;
71
+ continue;
72
+ }
73
+ if (token === "--as") {
74
+ parsed.alias = argv[index + 1];
75
+ index += 1;
76
+ continue;
77
+ }
78
+ if (token === "--ref") {
79
+ parsed.ref = argv[index + 1];
80
+ index += 1;
81
+ continue;
82
+ }
83
+ if (!parsed.command) {
84
+ parsed.command = token;
85
+ continue;
86
+ }
87
+ parsed.positional.push(token);
88
+ }
89
+ return parsed;
90
+ }
91
+ async function dispatch(parsed, cwd, env) {
92
+ if (parsed.command === "help") {
93
+ return buildHelpResult(parsed.positional[0]);
94
+ }
95
+ if (parsed.help) {
96
+ return buildHelpResult(parsed.command);
97
+ }
98
+ if (!parsed.command) {
99
+ return buildHelpResult();
100
+ }
101
+ const homeDir = env.HOME;
102
+ const githubBaseUrl = env.SKM_GITHUB_BASE_URL;
103
+ const xdgConfigHome = env.XDG_CONFIG_HOME;
104
+ switch (parsed.command) {
105
+ case "init":
106
+ return (0, init_1.runInitCommand)({
107
+ cwd,
108
+ homeDir,
109
+ xdgConfigHome,
110
+ scope: parsed.scope,
111
+ force: parsed.force,
112
+ });
113
+ case "add":
114
+ if (!parsed.positional[0]) {
115
+ throw new errors_1.SkmError("Usage: skm add <source>", 2);
116
+ }
117
+ return (0, add_1.runAddCommand)({
118
+ cwd,
119
+ homeDir,
120
+ xdgConfigHome,
121
+ scope: parsed.scope,
122
+ source: parsed.positional[0],
123
+ canonicalName: parsed.alias,
124
+ requestedRef: parsed.ref,
125
+ githubBaseUrl,
126
+ });
127
+ case "remove":
128
+ if (!parsed.positional[0]) {
129
+ throw new errors_1.SkmError("Usage: skm remove <name>", 2);
130
+ }
131
+ return (0, remove_1.runRemoveCommand)({
132
+ cwd,
133
+ homeDir,
134
+ xdgConfigHome,
135
+ scope: parsed.scope,
136
+ canonicalName: parsed.positional[0],
137
+ });
138
+ case "rename":
139
+ if (!parsed.positional[0] || !parsed.positional[1]) {
140
+ throw new errors_1.SkmError("Usage: skm rename <old-name> <new-name>", 2);
141
+ }
142
+ return (0, rename_1.runRenameCommand)({
143
+ cwd,
144
+ homeDir,
145
+ xdgConfigHome,
146
+ scope: parsed.scope,
147
+ oldName: parsed.positional[0],
148
+ newName: parsed.positional[1],
149
+ });
150
+ case "install":
151
+ return (0, install_1.runInstallCommand)({
152
+ cwd,
153
+ homeDir,
154
+ xdgConfigHome,
155
+ scope: parsed.scope,
156
+ githubBaseUrl,
157
+ });
158
+ case "update":
159
+ return (0, update_1.runUpdateCommand)({
160
+ cwd,
161
+ homeDir,
162
+ xdgConfigHome,
163
+ scope: parsed.scope,
164
+ canonicalName: parsed.positional[0],
165
+ force: parsed.force,
166
+ githubBaseUrl,
167
+ });
168
+ case "list":
169
+ return (0, list_1.runListCommand)({
170
+ cwd,
171
+ homeDir,
172
+ xdgConfigHome,
173
+ scope: parsed.scope,
174
+ all: parsed.all,
175
+ });
176
+ case "inspect":
177
+ if (!parsed.positional[0]) {
178
+ throw new errors_1.SkmError("Usage: skm inspect <name>", 2);
179
+ }
180
+ return (0, inspect_1.runInspectCommand)({
181
+ cwd,
182
+ homeDir,
183
+ xdgConfigHome,
184
+ scope: parsed.scope,
185
+ canonicalName: parsed.positional[0],
186
+ });
187
+ default:
188
+ throw new errors_1.SkmError(`Unknown command: ${parsed.command}`, 2);
189
+ }
190
+ }
191
+ function buildHelpResult(command) {
192
+ switch (command) {
193
+ case "add":
194
+ return {
195
+ kind: "help",
196
+ title: "skm add",
197
+ usage: "skm add <source> [--project|--global] [--as <name>] [--ref <ref>]",
198
+ sections: [
199
+ {
200
+ title: "Sources",
201
+ lines: [
202
+ "- GitHub tree URL: https://github.com/<owner>/<repo>/tree/<ref>/<path>",
203
+ "- GitHub repository shorthand: <owner>/<repo>",
204
+ "- GitHub repository URL: https://github.com/<owner>/<repo>",
205
+ ],
206
+ },
207
+ {
208
+ title: "Options",
209
+ lines: [
210
+ "- --as <name> Set the local skill name for single-skill imports",
211
+ "- --ref <ref> Override the requested branch, tag, or commit",
212
+ "- --project Use project scope",
213
+ "- --global Use global scope",
214
+ ],
215
+ },
216
+ ],
217
+ };
218
+ case "init":
219
+ return {
220
+ kind: "help",
221
+ title: "skm init",
222
+ usage: "skm init [--project|--global] [--force]",
223
+ sections: [
224
+ {
225
+ title: "Options",
226
+ lines: [
227
+ "- --project Initialize project scope",
228
+ "- --global Initialize global scope",
229
+ "- --force Rewrite existing manifest and lockfile",
230
+ ],
231
+ },
232
+ ],
233
+ };
234
+ case "remove":
235
+ return {
236
+ kind: "help",
237
+ title: "skm remove",
238
+ usage: "skm remove <name> [--project|--global]",
239
+ sections: [{ title: "Options", lines: ["- --project", "- --global"] }],
240
+ };
241
+ case "rename":
242
+ return {
243
+ kind: "help",
244
+ title: "skm rename",
245
+ usage: "skm rename <old-name> <new-name> [--project|--global]",
246
+ sections: [{ title: "Options", lines: ["- --project", "- --global"] }],
247
+ };
248
+ case "install":
249
+ return {
250
+ kind: "help",
251
+ title: "skm install",
252
+ usage: "skm install [--project|--global]",
253
+ sections: [{ title: "Options", lines: ["- --project", "- --global"] }],
254
+ };
255
+ case "update":
256
+ return {
257
+ kind: "help",
258
+ title: "skm update",
259
+ usage: "skm update [name] [--project|--global] [--force]",
260
+ sections: [
261
+ {
262
+ title: "Options",
263
+ lines: [
264
+ "- --project",
265
+ "- --global",
266
+ "- --force Refresh even when the requested ref is already a fixed commit",
267
+ ],
268
+ },
269
+ ],
270
+ };
271
+ case "list":
272
+ return {
273
+ kind: "help",
274
+ title: "skm list",
275
+ usage: "skm list [--project|--global] [--all]",
276
+ sections: [
277
+ {
278
+ title: "Options",
279
+ lines: [
280
+ "- --all Show both project and global entries when available",
281
+ "- --project",
282
+ "- --global",
283
+ ],
284
+ },
285
+ ],
286
+ };
287
+ case "inspect":
288
+ return {
289
+ kind: "help",
290
+ title: "skm inspect",
291
+ usage: "skm inspect <name> [--project|--global]",
292
+ sections: [{ title: "Options", lines: ["- --project", "- --global"] }],
293
+ };
294
+ default:
295
+ return {
296
+ kind: "help",
297
+ title: "skm",
298
+ usage: "skm <command>",
299
+ sections: [
300
+ {
301
+ title: "Commands",
302
+ lines: [
303
+ "- init",
304
+ "- add <source>",
305
+ "- remove <name>",
306
+ "- rename <old-name> <new-name>",
307
+ "- install",
308
+ "- update [name]",
309
+ "- list",
310
+ "- inspect <name>",
311
+ "- help [command]",
312
+ ],
313
+ },
314
+ {
315
+ title: "Global options",
316
+ lines: ["- --help, -h", "- --project", "- --global"],
317
+ },
318
+ ],
319
+ };
320
+ }
321
+ }