skill-port 0.1.3 → 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/dist/{chunk-OZ2PBTWL.js → chunk-ZRQAK25B.js} +195 -62
- package/dist/cli.js +5 -3
- package/dist/index.d.ts +20 -2
- package/dist/index.js +7 -3
- package/package.json +1 -1
|
@@ -1,8 +1,103 @@
|
|
|
1
|
-
// src/core/
|
|
2
|
-
import {
|
|
3
|
-
import { access, readdir } from "fs/promises";
|
|
1
|
+
// src/core/plugins.ts
|
|
2
|
+
import { access, readdir, readFile } from "fs/promises";
|
|
4
3
|
import os from "os";
|
|
5
4
|
import path from "path";
|
|
5
|
+
var PROVIDER_SKILL_DIRS = [
|
|
6
|
+
{ subpath: "skills", provider: "claude-code" },
|
|
7
|
+
{ subpath: path.join(".claude", "skills"), provider: "claude-code" },
|
|
8
|
+
{ subpath: path.join(".agents", "skills"), provider: "codex" },
|
|
9
|
+
{ subpath: path.join(".cursor", "skills"), provider: "cursor" }
|
|
10
|
+
];
|
|
11
|
+
async function readInstalledPlugins(homeDir) {
|
|
12
|
+
const home = homeDir ?? (process.env.SKILL_PORT_HOME ?? os.homedir());
|
|
13
|
+
const filePath = path.join(home, ".claude", "plugins", "installed_plugins.json");
|
|
14
|
+
let raw;
|
|
15
|
+
try {
|
|
16
|
+
raw = await readFile(filePath, "utf8");
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
let parsed;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
if (!parsed.plugins || typeof parsed.plugins !== "object") {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const entries = [];
|
|
30
|
+
for (const installs of Object.values(parsed.plugins)) {
|
|
31
|
+
if (!Array.isArray(installs)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
for (const install of installs) {
|
|
35
|
+
if (install && typeof install.installPath === "string") {
|
|
36
|
+
entries.push({
|
|
37
|
+
scope: String(install.scope ?? "user"),
|
|
38
|
+
installPath: install.installPath,
|
|
39
|
+
version: String(install.version ?? "unknown")
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return entries;
|
|
45
|
+
}
|
|
46
|
+
async function listPluginSkills(provider, homeDir) {
|
|
47
|
+
const installs = await readInstalledPlugins(homeDir);
|
|
48
|
+
const output = [];
|
|
49
|
+
for (const install of installs) {
|
|
50
|
+
for (const { subpath, provider: dirProvider } of PROVIDER_SKILL_DIRS) {
|
|
51
|
+
if (provider !== "all" && provider !== dirProvider) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const skillsRoot = path.join(install.installPath, subpath);
|
|
55
|
+
const entries = await readDirSafe(skillsRoot);
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (!entry.isDirectory()) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const skillDir = path.join(skillsRoot, entry.name);
|
|
61
|
+
if (!await pathExists(path.join(skillDir, "SKILL.md"))) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
output.push({
|
|
65
|
+
provider: dirProvider,
|
|
66
|
+
scope: "plugin",
|
|
67
|
+
name: entry.name,
|
|
68
|
+
path: skillDir
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return output.sort((a, b) => {
|
|
74
|
+
if (a.provider !== b.provider) {
|
|
75
|
+
return a.provider.localeCompare(b.provider);
|
|
76
|
+
}
|
|
77
|
+
return a.name.localeCompare(b.name);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function readDirSafe(dirPath) {
|
|
81
|
+
try {
|
|
82
|
+
return await readdir(dirPath, { withFileTypes: true });
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function pathExists(filePath) {
|
|
88
|
+
try {
|
|
89
|
+
await access(filePath);
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/core/scopes.ts
|
|
97
|
+
import { accessSync } from "fs";
|
|
98
|
+
import { access as access2, readdir as readdir2 } from "fs/promises";
|
|
99
|
+
import os2 from "os";
|
|
100
|
+
import path2 from "path";
|
|
6
101
|
|
|
7
102
|
// src/constants.ts
|
|
8
103
|
var ISSUE_CODES = {
|
|
@@ -14,6 +109,9 @@ var ISSUE_CODES = {
|
|
|
14
109
|
OPENAI_FIELD_DROPPED: "SP202",
|
|
15
110
|
NAME_DERIVED: "SP301",
|
|
16
111
|
DESCRIPTION_DERIVED: "SP302",
|
|
112
|
+
PLUGIN_FILE_MISSING: "SP401",
|
|
113
|
+
PLUGIN_FILE_INVALID: "SP402",
|
|
114
|
+
PLUGIN_WRITE_BLOCKED: "SP403",
|
|
17
115
|
STRICT_BLOCKED: "SP900"
|
|
18
116
|
};
|
|
19
117
|
var FRONTMATTER_KEY_ORDER = [
|
|
@@ -37,7 +135,7 @@ var PROVIDER_ORDER = ["codex", "claude-code", "cursor"];
|
|
|
37
135
|
function getScopeRoots(cwd) {
|
|
38
136
|
const localRoot = cwd;
|
|
39
137
|
const projectRoot = findProjectRoot(cwd);
|
|
40
|
-
const userRoot = process.env.SKILL_PORT_HOME ??
|
|
138
|
+
const userRoot = process.env.SKILL_PORT_HOME ?? os2.homedir();
|
|
41
139
|
return {
|
|
42
140
|
userRoot,
|
|
43
141
|
projectRoot,
|
|
@@ -47,11 +145,11 @@ function getScopeRoots(cwd) {
|
|
|
47
145
|
function providerSubpath(provider) {
|
|
48
146
|
switch (provider) {
|
|
49
147
|
case "codex":
|
|
50
|
-
return
|
|
148
|
+
return path2.join(".agents", "skills");
|
|
51
149
|
case "claude-code":
|
|
52
|
-
return
|
|
150
|
+
return path2.join(".claude", "skills");
|
|
53
151
|
case "cursor":
|
|
54
|
-
return
|
|
152
|
+
return path2.join(".cursor", "skills");
|
|
55
153
|
default:
|
|
56
154
|
return assertNever(provider);
|
|
57
155
|
}
|
|
@@ -60,19 +158,21 @@ function resolveSkillsRoot(provider, scope, cwd = process.cwd()) {
|
|
|
60
158
|
const roots = getScopeRoots(cwd);
|
|
61
159
|
switch (scope) {
|
|
62
160
|
case "user":
|
|
63
|
-
return
|
|
161
|
+
return path2.join(roots.userRoot, providerSubpath(provider));
|
|
64
162
|
case "project":
|
|
65
|
-
return
|
|
163
|
+
return path2.join(roots.projectRoot, providerSubpath(provider));
|
|
66
164
|
case "local":
|
|
67
|
-
return
|
|
165
|
+
return path2.join(roots.localRoot, providerSubpath(provider));
|
|
166
|
+
case "plugin":
|
|
167
|
+
throw new Error("Plugin skills are resolved via installed_plugins.json, not a fixed root path.");
|
|
68
168
|
default:
|
|
69
169
|
return assertNever(scope);
|
|
70
170
|
}
|
|
71
171
|
}
|
|
72
172
|
function resolveSkillPath(provider, scope, skillName, cwd = process.cwd()) {
|
|
73
173
|
const normalizedSkillName = validateSkillName(skillName);
|
|
74
|
-
const root =
|
|
75
|
-
const resolved =
|
|
174
|
+
const root = path2.resolve(resolveSkillsRoot(provider, scope, cwd));
|
|
175
|
+
const resolved = path2.resolve(root, normalizedSkillName);
|
|
76
176
|
if (!isWithinRoot(root, resolved)) {
|
|
77
177
|
throw new Error(
|
|
78
178
|
`[${ISSUE_CODES.INVALID_SKILL_NAME}] Invalid skill name '${skillName}'. Skill names must resolve under the provider root.`
|
|
@@ -81,17 +181,20 @@ function resolveSkillPath(provider, scope, skillName, cwd = process.cwd()) {
|
|
|
81
181
|
return resolved;
|
|
82
182
|
}
|
|
83
183
|
async function listSkills(scope, provider, cwd = process.cwd()) {
|
|
184
|
+
if (scope === "plugin") {
|
|
185
|
+
return listPluginSkills(provider);
|
|
186
|
+
}
|
|
84
187
|
const providers = provider === "all" ? PROVIDER_ORDER : [provider];
|
|
85
188
|
const output = [];
|
|
86
189
|
for (const candidateProvider of providers) {
|
|
87
190
|
const root = resolveSkillsRoot(candidateProvider, scope, cwd);
|
|
88
|
-
const entries = await
|
|
191
|
+
const entries = await readDirSafe2(root);
|
|
89
192
|
for (const entry of entries) {
|
|
90
193
|
if (!entry.isDirectory()) {
|
|
91
194
|
continue;
|
|
92
195
|
}
|
|
93
|
-
const skillDir =
|
|
94
|
-
if (!await
|
|
196
|
+
const skillDir = path2.join(root, entry.name);
|
|
197
|
+
if (!await pathExists2(path2.join(skillDir, "SKILL.md"))) {
|
|
95
198
|
continue;
|
|
96
199
|
}
|
|
97
200
|
output.push({
|
|
@@ -110,9 +213,12 @@ async function listSkills(scope, provider, cwd = process.cwd()) {
|
|
|
110
213
|
});
|
|
111
214
|
}
|
|
112
215
|
async function resolveSkillForConvert(skillName, scope, from, cwd = process.cwd()) {
|
|
216
|
+
if (scope === "plugin") {
|
|
217
|
+
return resolvePluginSkill(skillName, from);
|
|
218
|
+
}
|
|
113
219
|
if (from !== "auto") {
|
|
114
220
|
const explicitPath = resolveSkillPath(from, scope, skillName, cwd);
|
|
115
|
-
if (!await
|
|
221
|
+
if (!await pathExists2(path2.join(explicitPath, "SKILL.md"))) {
|
|
116
222
|
throw new Error(
|
|
117
223
|
`Skill '${skillName}' was not found for provider '${from}' in scope '${scope}' (${explicitPath}).`
|
|
118
224
|
);
|
|
@@ -122,7 +228,7 @@ async function resolveSkillForConvert(skillName, scope, from, cwd = process.cwd(
|
|
|
122
228
|
const matches = [];
|
|
123
229
|
for (const provider of PROVIDER_ORDER) {
|
|
124
230
|
const candidatePath = resolveSkillPath(provider, scope, skillName, cwd);
|
|
125
|
-
if (await
|
|
231
|
+
if (await pathExists2(path2.join(candidatePath, "SKILL.md"))) {
|
|
126
232
|
matches.push({ provider, path: candidatePath });
|
|
127
233
|
}
|
|
128
234
|
}
|
|
@@ -142,7 +248,7 @@ function resolveDefaultOutputPath(to, targetScope, skillName, cwd = process.cwd(
|
|
|
142
248
|
}
|
|
143
249
|
function validateSkillName(skillName) {
|
|
144
250
|
const normalized = skillName.trim();
|
|
145
|
-
const separators = [
|
|
251
|
+
const separators = [path2.sep, path2.posix.sep, path2.win32.sep];
|
|
146
252
|
if (!normalized) {
|
|
147
253
|
throw new Error(
|
|
148
254
|
`[${ISSUE_CODES.INVALID_SKILL_NAME}] Invalid skill name '${skillName}'. Skill name cannot be empty.`
|
|
@@ -153,23 +259,23 @@ function validateSkillName(skillName) {
|
|
|
153
259
|
`[${ISSUE_CODES.INVALID_SKILL_NAME}] Invalid skill name '${skillName}'. Dot path segments are not allowed.`
|
|
154
260
|
);
|
|
155
261
|
}
|
|
156
|
-
if (
|
|
262
|
+
if (path2.isAbsolute(normalized) || separators.some((sep) => sep && normalized.includes(sep))) {
|
|
157
263
|
throw new Error(
|
|
158
264
|
`[${ISSUE_CODES.INVALID_SKILL_NAME}] Invalid skill name '${skillName}'. Path separators are not allowed.`
|
|
159
265
|
);
|
|
160
266
|
}
|
|
161
267
|
return normalized;
|
|
162
268
|
}
|
|
163
|
-
async function
|
|
269
|
+
async function readDirSafe2(dirPath) {
|
|
164
270
|
try {
|
|
165
|
-
return await
|
|
271
|
+
return await readdir2(dirPath, { withFileTypes: true });
|
|
166
272
|
} catch {
|
|
167
273
|
return [];
|
|
168
274
|
}
|
|
169
275
|
}
|
|
170
|
-
async function
|
|
276
|
+
async function pathExists2(filePath) {
|
|
171
277
|
try {
|
|
172
|
-
await
|
|
278
|
+
await access2(filePath);
|
|
173
279
|
return true;
|
|
174
280
|
} catch {
|
|
175
281
|
return false;
|
|
@@ -178,38 +284,58 @@ async function pathExists(filePath) {
|
|
|
178
284
|
function findProjectRoot(start) {
|
|
179
285
|
const override = process.env.SKILL_PORT_PROJECT_ROOT;
|
|
180
286
|
if (override) {
|
|
181
|
-
return
|
|
287
|
+
return path2.resolve(override);
|
|
182
288
|
}
|
|
183
|
-
let current =
|
|
289
|
+
let current = path2.resolve(start);
|
|
184
290
|
while (true) {
|
|
185
|
-
const gitMarker =
|
|
291
|
+
const gitMarker = path2.join(current, ".git");
|
|
186
292
|
try {
|
|
187
293
|
accessSync(gitMarker);
|
|
188
294
|
return current;
|
|
189
295
|
} catch {
|
|
190
296
|
}
|
|
191
|
-
const parent =
|
|
297
|
+
const parent = path2.dirname(current);
|
|
192
298
|
if (parent === current) {
|
|
193
|
-
return
|
|
299
|
+
return path2.resolve(start);
|
|
194
300
|
}
|
|
195
301
|
current = parent;
|
|
196
302
|
}
|
|
197
303
|
}
|
|
304
|
+
async function resolvePluginSkill(skillName, from) {
|
|
305
|
+
const normalizedName = validateSkillName(skillName);
|
|
306
|
+
const provider = from === "auto" ? "all" : from;
|
|
307
|
+
const skills = await listPluginSkills(provider);
|
|
308
|
+
const matches = skills.filter((s) => s.name === normalizedName);
|
|
309
|
+
if (matches.length === 0) {
|
|
310
|
+
throw new Error(`Skill '${skillName}' was not found in scope 'plugin'.`);
|
|
311
|
+
}
|
|
312
|
+
if (from === "auto" && matches.length > 1) {
|
|
313
|
+
const uniqueProviders = new Set(matches.map((m) => m.provider));
|
|
314
|
+
if (uniqueProviders.size > 1) {
|
|
315
|
+
const providers = Array.from(uniqueProviders).join(", ");
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Skill '${skillName}' exists for multiple providers in scope 'plugin' (${providers}). Specify --from.`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const match = matches[0];
|
|
322
|
+
return { provider: match.provider, path: match.path };
|
|
323
|
+
}
|
|
198
324
|
function assertNever(value) {
|
|
199
325
|
throw new Error(`Unsupported value: ${String(value)}`);
|
|
200
326
|
}
|
|
201
327
|
function isWithinRoot(root, candidate) {
|
|
202
|
-
const relative =
|
|
203
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
328
|
+
const relative = path2.relative(root, candidate);
|
|
329
|
+
return relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative);
|
|
204
330
|
}
|
|
205
331
|
|
|
206
332
|
// src/core/convert.ts
|
|
207
|
-
import { access as
|
|
208
|
-
import
|
|
333
|
+
import { access as access4, mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
334
|
+
import path8 from "path";
|
|
209
335
|
|
|
210
336
|
// src/core/skill-file.ts
|
|
211
|
-
import { readFile, writeFile } from "fs/promises";
|
|
212
|
-
import
|
|
337
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
338
|
+
import path3 from "path";
|
|
213
339
|
|
|
214
340
|
// src/core/frontmatter.ts
|
|
215
341
|
import yaml from "js-yaml";
|
|
@@ -297,24 +423,24 @@ function orderFrontmatter(frontmatter) {
|
|
|
297
423
|
|
|
298
424
|
// src/core/skill-file.ts
|
|
299
425
|
async function readSkillDocument(skillDir) {
|
|
300
|
-
const skillPath =
|
|
301
|
-
const raw = await
|
|
426
|
+
const skillPath = path3.join(skillDir, "SKILL.md");
|
|
427
|
+
const raw = await readFile2(skillPath, "utf8");
|
|
302
428
|
return parseSkillMarkdown(raw);
|
|
303
429
|
}
|
|
304
430
|
async function writeSkillDocument(skillDir, skill) {
|
|
305
|
-
const skillPath =
|
|
431
|
+
const skillPath = path3.join(skillDir, "SKILL.md");
|
|
306
432
|
const serialized = serializeSkillMarkdown(skill);
|
|
307
433
|
await writeFile(skillPath, serialized, "utf8");
|
|
308
434
|
}
|
|
309
435
|
|
|
310
436
|
// src/adapters/shared.ts
|
|
311
|
-
import
|
|
437
|
+
import path4 from "path";
|
|
312
438
|
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
313
439
|
function deriveNameAndDescription(skillDir, frontmatter, body) {
|
|
314
440
|
const issues = [];
|
|
315
441
|
let name = readString(frontmatter.name);
|
|
316
442
|
if (!name) {
|
|
317
|
-
name =
|
|
443
|
+
name = path4.basename(skillDir);
|
|
318
444
|
issues.push({
|
|
319
445
|
code: ISSUE_CODES.NAME_DERIVED,
|
|
320
446
|
field: "name",
|
|
@@ -433,14 +559,14 @@ function writeCursorSkill(canonical) {
|
|
|
433
559
|
}
|
|
434
560
|
|
|
435
561
|
// src/adapters/openai.ts
|
|
436
|
-
import
|
|
562
|
+
import path6 from "path";
|
|
437
563
|
|
|
438
564
|
// src/core/files.ts
|
|
439
|
-
import { cp, mkdir, readFile as
|
|
440
|
-
import
|
|
565
|
+
import { cp, mkdir, readFile as readFile3, rm, stat, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
566
|
+
import path5 from "path";
|
|
441
567
|
import yaml2 from "js-yaml";
|
|
442
568
|
async function ensureOutputDirectory(outputDir, overwrite) {
|
|
443
|
-
const resolvedOutput =
|
|
569
|
+
const resolvedOutput = path5.resolve(outputDir);
|
|
444
570
|
let outputExists = false;
|
|
445
571
|
try {
|
|
446
572
|
await stat(resolvedOutput);
|
|
@@ -470,7 +596,7 @@ async function copySkillDirectory(sourceDir, outputDir) {
|
|
|
470
596
|
async function readYamlFile(filePath) {
|
|
471
597
|
let raw;
|
|
472
598
|
try {
|
|
473
|
-
raw = await
|
|
599
|
+
raw = await readFile3(filePath, "utf8");
|
|
474
600
|
} catch (error) {
|
|
475
601
|
const nodeError = error;
|
|
476
602
|
if (nodeError?.code === "ENOENT") {
|
|
@@ -496,7 +622,7 @@ async function readYamlFile(filePath) {
|
|
|
496
622
|
}
|
|
497
623
|
}
|
|
498
624
|
async function writeYamlFile(filePath, data) {
|
|
499
|
-
await mkdir(
|
|
625
|
+
await mkdir(path5.dirname(filePath), { recursive: true });
|
|
500
626
|
const text = yaml2.dump(data, {
|
|
501
627
|
lineWidth: -1,
|
|
502
628
|
noRefs: true,
|
|
@@ -513,7 +639,7 @@ async function removeFileIfExists(filePath) {
|
|
|
513
639
|
}
|
|
514
640
|
}
|
|
515
641
|
function isFilesystemRoot(targetPath) {
|
|
516
|
-
const parsed =
|
|
642
|
+
const parsed = path5.parse(targetPath);
|
|
517
643
|
return parsed.root === targetPath;
|
|
518
644
|
}
|
|
519
645
|
|
|
@@ -529,7 +655,7 @@ async function readOpenAiSkill(skillDir) {
|
|
|
529
655
|
}
|
|
530
656
|
];
|
|
531
657
|
const { name, description, issues } = deriveNameAndDescription(skillDir, frontmatter, doc.body);
|
|
532
|
-
const openaiPath =
|
|
658
|
+
const openaiPath = path6.join(skillDir, "agents", "openai.yaml");
|
|
533
659
|
const openaiRead = await readYamlFile(openaiPath);
|
|
534
660
|
const openaiConfig = openaiRead.data;
|
|
535
661
|
const warnings = [...issues];
|
|
@@ -677,11 +803,11 @@ function assertNever2(value) {
|
|
|
677
803
|
}
|
|
678
804
|
|
|
679
805
|
// src/core/detect.ts
|
|
680
|
-
import { access as
|
|
681
|
-
import
|
|
806
|
+
import { access as access3 } from "fs/promises";
|
|
807
|
+
import path7 from "path";
|
|
682
808
|
async function exists(filePath) {
|
|
683
809
|
try {
|
|
684
|
-
await
|
|
810
|
+
await access3(filePath);
|
|
685
811
|
return true;
|
|
686
812
|
} catch {
|
|
687
813
|
return false;
|
|
@@ -689,11 +815,11 @@ async function exists(filePath) {
|
|
|
689
815
|
}
|
|
690
816
|
async function detectProvider(skillDir) {
|
|
691
817
|
const issues = [];
|
|
692
|
-
const openaiConfigPath =
|
|
818
|
+
const openaiConfigPath = path7.join(skillDir, "agents", "openai.yaml");
|
|
693
819
|
if (await exists(openaiConfigPath)) {
|
|
694
820
|
return { provider: "codex", issues };
|
|
695
821
|
}
|
|
696
|
-
const normalized = skillDir.split(
|
|
822
|
+
const normalized = skillDir.split(path7.sep).join("/").toLowerCase();
|
|
697
823
|
if (normalized.includes("/.claude/skills/")) {
|
|
698
824
|
return { provider: "claude-code", issues };
|
|
699
825
|
}
|
|
@@ -770,10 +896,10 @@ var StrictModeError = class extends Error {
|
|
|
770
896
|
}
|
|
771
897
|
};
|
|
772
898
|
async function convertSkill(options) {
|
|
773
|
-
const inputDir =
|
|
899
|
+
const inputDir = path8.resolve(options.input);
|
|
774
900
|
const targetProvider = options.to;
|
|
775
|
-
const outputDir =
|
|
776
|
-
options.out ??
|
|
901
|
+
const outputDir = path8.resolve(
|
|
902
|
+
options.out ?? path8.join(path8.dirname(inputDir), `${path8.basename(inputDir)}-${targetProvider}`)
|
|
777
903
|
);
|
|
778
904
|
await assertSkillDirectory(inputDir);
|
|
779
905
|
const sourceProvider = await resolveSourceProvider(inputDir, options.from);
|
|
@@ -816,7 +942,7 @@ async function convertSkill(options) {
|
|
|
816
942
|
}
|
|
817
943
|
let wroteFiles = false;
|
|
818
944
|
if (!options.dryRun) {
|
|
819
|
-
if (
|
|
945
|
+
if (path8.resolve(outputDir) === path8.resolve(inputDir)) {
|
|
820
946
|
if (sourceProvider === targetProvider) {
|
|
821
947
|
throw new Error(
|
|
822
948
|
`Detected source provider '${sourceProvider}' and target provider '${targetProvider}' for the same skill path (${outputDir}). This is a no-op conversion to the same provider. Choose a different --to provider, or set --target-scope/--out to write elsewhere.`
|
|
@@ -832,7 +958,7 @@ async function convertSkill(options) {
|
|
|
832
958
|
frontmatter: writeResult.frontmatter,
|
|
833
959
|
body: writeResult.body
|
|
834
960
|
});
|
|
835
|
-
const outputOpenAiPath =
|
|
961
|
+
const outputOpenAiPath = path8.join(outputDir, "agents", "openai.yaml");
|
|
836
962
|
if (targetProvider === "codex" && writeResult.openaiYaml) {
|
|
837
963
|
await writeYamlFile(outputOpenAiPath, writeResult.openaiYaml);
|
|
838
964
|
} else {
|
|
@@ -846,8 +972,8 @@ async function convertSkill(options) {
|
|
|
846
972
|
});
|
|
847
973
|
}
|
|
848
974
|
}
|
|
849
|
-
const reportPath =
|
|
850
|
-
await mkdir2(
|
|
975
|
+
const reportPath = path8.resolve(options.report ?? path8.join(outputDir, "skill-port.report.json"));
|
|
976
|
+
await mkdir2(path8.dirname(reportPath), { recursive: true });
|
|
851
977
|
await writeFile3(reportPath, `${JSON.stringify(report, null, 2)}
|
|
852
978
|
`, "utf8");
|
|
853
979
|
wroteFiles = true;
|
|
@@ -863,8 +989,13 @@ async function convertSkill(options) {
|
|
|
863
989
|
async function convertScopedSkill(options) {
|
|
864
990
|
const scope = options.scope ?? "user";
|
|
865
991
|
const targetScope = options.targetScope ?? scope;
|
|
992
|
+
if (targetScope === "plugin") {
|
|
993
|
+
throw new Error(
|
|
994
|
+
`[${ISSUE_CODES.PLUGIN_WRITE_BLOCKED}] Cannot write to plugin scope. Plugin skills are managed by the plugin system. Use --target-scope user|project|local instead.`
|
|
995
|
+
);
|
|
996
|
+
}
|
|
866
997
|
const source = await resolveSkillForConvert(options.skill, scope, options.from ?? "auto");
|
|
867
|
-
const output = options.out ?
|
|
998
|
+
const output = options.out ? path8.resolve(options.out) : resolveDefaultOutputPath(options.to, targetScope, options.skill);
|
|
868
999
|
return convertSkill({
|
|
869
1000
|
input: source.path,
|
|
870
1001
|
to: options.to,
|
|
@@ -884,9 +1015,9 @@ async function resolveSourceProvider(inputDir, from) {
|
|
|
884
1015
|
return detection.provider;
|
|
885
1016
|
}
|
|
886
1017
|
async function assertSkillDirectory(inputDir) {
|
|
887
|
-
const skillFile =
|
|
1018
|
+
const skillFile = path8.join(inputDir, "SKILL.md");
|
|
888
1019
|
try {
|
|
889
|
-
await
|
|
1020
|
+
await access4(skillFile);
|
|
890
1021
|
} catch {
|
|
891
1022
|
throw new Error(`Input path is not a skill directory. Missing SKILL.md: ${skillFile}`);
|
|
892
1023
|
}
|
|
@@ -938,6 +1069,8 @@ function formatSkillsText(skills, scope, provider, showPaths = false) {
|
|
|
938
1069
|
}
|
|
939
1070
|
|
|
940
1071
|
export {
|
|
1072
|
+
readInstalledPlugins,
|
|
1073
|
+
listPluginSkills,
|
|
941
1074
|
validateSkillName,
|
|
942
1075
|
StrictModeError,
|
|
943
1076
|
convertSkill,
|
package/dist/cli.js
CHANGED
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
formatTextReport,
|
|
7
7
|
listSkillsByScope,
|
|
8
8
|
validateSkillName
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ZRQAK25B.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
12
12
|
import path from "path";
|
|
13
13
|
import process from "process";
|
|
14
14
|
var PROVIDERS = ["codex", "claude-code", "cursor"];
|
|
15
|
-
var SCOPES = ["user", "project", "local"];
|
|
15
|
+
var SCOPES = ["user", "project", "local", "plugin"];
|
|
16
16
|
function printUsage() {
|
|
17
17
|
const usage = `skill-port v0.1.0
|
|
18
18
|
|
|
@@ -24,7 +24,7 @@ Providers:
|
|
|
24
24
|
codex | claude-code | cursor
|
|
25
25
|
|
|
26
26
|
Scopes:
|
|
27
|
-
user | project | local
|
|
27
|
+
user | project | local | plugin
|
|
28
28
|
|
|
29
29
|
list options:
|
|
30
30
|
--scope <scope> Scope to scan (default: user)
|
|
@@ -38,7 +38,9 @@ convert options:
|
|
|
38
38
|
--all Convert all skills in scope (cannot be used with <skill-name>)
|
|
39
39
|
Continues per-skill on errors; exits non-zero if any fail
|
|
40
40
|
--scope <scope> Source scope (default: user)
|
|
41
|
+
Use 'plugin' for skills from installed Claude Code plugins
|
|
41
42
|
--target-scope <scope> Target scope (default: same as --scope)
|
|
43
|
+
Note: 'plugin' is read-only and cannot be used here
|
|
42
44
|
--out <dir> Explicit output directory (overrides scope path)
|
|
43
45
|
--report <path> Report output path (default: <out>/skill-port.report.json)
|
|
44
46
|
--strict Fail on lossy conversion or conflicts
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
type Provider = "codex" | "claude-code" | "cursor";
|
|
2
|
-
type Scope = "user" | "project" | "local";
|
|
2
|
+
type Scope = "user" | "project" | "local" | "plugin";
|
|
3
3
|
interface OpenAiSkillConfig {
|
|
4
4
|
interface?: Record<string, unknown>;
|
|
5
5
|
policy?: {
|
|
@@ -76,6 +76,21 @@ interface ListedSkill {
|
|
|
76
76
|
name: string;
|
|
77
77
|
path: string;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Represents one installed plugin entry from installed_plugins.json.
|
|
81
|
+
* `scope` is typed as `string` (not `Scope`) because it represents the
|
|
82
|
+
* plugin install scope from the JSON file, which may not map 1:1 to
|
|
83
|
+
* skill-port's `Scope` type.
|
|
84
|
+
*/
|
|
85
|
+
interface PluginInstall {
|
|
86
|
+
readonly scope: string;
|
|
87
|
+
readonly installPath: string;
|
|
88
|
+
readonly version: string;
|
|
89
|
+
}
|
|
90
|
+
interface InstalledPluginsFile {
|
|
91
|
+
readonly version: number;
|
|
92
|
+
readonly plugins: Record<string, readonly PluginInstall[]>;
|
|
93
|
+
}
|
|
79
94
|
interface ConvertResult {
|
|
80
95
|
outputDir: string;
|
|
81
96
|
report: ConversionReport;
|
|
@@ -95,4 +110,7 @@ declare function formatTextReport(result: ConvertResult, strictError?: boolean):
|
|
|
95
110
|
declare function listSkillsByScope(scope: Scope, provider: Provider | "all", cwd?: string): Promise<ListedSkill[]>;
|
|
96
111
|
declare function formatSkillsText(skills: ListedSkill[], scope: Scope, provider: Provider | "all", showPaths?: boolean): string;
|
|
97
112
|
|
|
98
|
-
|
|
113
|
+
declare function readInstalledPlugins(homeDir?: string): Promise<readonly PluginInstall[]>;
|
|
114
|
+
declare function listPluginSkills(provider: Provider | "all", homeDir?: string): Promise<ListedSkill[]>;
|
|
115
|
+
|
|
116
|
+
export { type CanonicalSkill, type ConversionReport, type ConvertOptions, type ConvertResult, type InstalledPluginsFile, type ListedSkill, type PluginInstall, type Provider, type ReportIssue, type ReportMapping, type Scope, type ScopedConvertOptions, StrictModeError, convertScopedSkill, convertSkill, formatSkillsText, formatTextReport, listPluginSkills, listSkillsByScope, readInstalledPlugins };
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,17 @@ import {
|
|
|
4
4
|
convertSkill,
|
|
5
5
|
formatSkillsText,
|
|
6
6
|
formatTextReport,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
listPluginSkills,
|
|
8
|
+
listSkillsByScope,
|
|
9
|
+
readInstalledPlugins
|
|
10
|
+
} from "./chunk-ZRQAK25B.js";
|
|
9
11
|
export {
|
|
10
12
|
StrictModeError,
|
|
11
13
|
convertScopedSkill,
|
|
12
14
|
convertSkill,
|
|
13
15
|
formatSkillsText,
|
|
14
16
|
formatTextReport,
|
|
15
|
-
|
|
17
|
+
listPluginSkills,
|
|
18
|
+
listSkillsByScope,
|
|
19
|
+
readInstalledPlugins
|
|
16
20
|
};
|