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.
@@ -1,8 +1,103 @@
1
- // src/core/scopes.ts
2
- import { accessSync } from "fs";
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 ?? os.homedir();
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 path.join(".agents", "skills");
148
+ return path2.join(".agents", "skills");
51
149
  case "claude-code":
52
- return path.join(".claude", "skills");
150
+ return path2.join(".claude", "skills");
53
151
  case "cursor":
54
- return path.join(".cursor", "skills");
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 path.join(roots.userRoot, providerSubpath(provider));
161
+ return path2.join(roots.userRoot, providerSubpath(provider));
64
162
  case "project":
65
- return path.join(roots.projectRoot, providerSubpath(provider));
163
+ return path2.join(roots.projectRoot, providerSubpath(provider));
66
164
  case "local":
67
- return path.join(roots.localRoot, providerSubpath(provider));
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 = path.resolve(resolveSkillsRoot(provider, scope, cwd));
75
- const resolved = path.resolve(root, normalizedSkillName);
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 readDirSafe(root);
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 = path.join(root, entry.name);
94
- if (!await pathExists(path.join(skillDir, "SKILL.md"))) {
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 pathExists(path.join(explicitPath, "SKILL.md"))) {
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 pathExists(path.join(candidatePath, "SKILL.md"))) {
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 = [path.sep, path.posix.sep, path.win32.sep];
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 (path.isAbsolute(normalized) || separators.some((sep) => sep && normalized.includes(sep))) {
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 readDirSafe(dirPath) {
269
+ async function readDirSafe2(dirPath) {
164
270
  try {
165
- return await readdir(dirPath, { withFileTypes: true });
271
+ return await readdir2(dirPath, { withFileTypes: true });
166
272
  } catch {
167
273
  return [];
168
274
  }
169
275
  }
170
- async function pathExists(filePath) {
276
+ async function pathExists2(filePath) {
171
277
  try {
172
- await access(filePath);
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 path.resolve(override);
287
+ return path2.resolve(override);
182
288
  }
183
- let current = path.resolve(start);
289
+ let current = path2.resolve(start);
184
290
  while (true) {
185
- const gitMarker = path.join(current, ".git");
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 = path.dirname(current);
297
+ const parent = path2.dirname(current);
192
298
  if (parent === current) {
193
- return path.resolve(start);
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 = path.relative(root, candidate);
203
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
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 access3, mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
208
- import path7 from "path";
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 path2 from "path";
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 = path2.join(skillDir, "SKILL.md");
301
- const raw = await readFile(skillPath, "utf8");
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 = path2.join(skillDir, "SKILL.md");
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 path3 from "path";
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 = path3.basename(skillDir);
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 path5 from "path";
562
+ import path6 from "path";
437
563
 
438
564
  // src/core/files.ts
439
- import { cp, mkdir, readFile as readFile2, rm, stat, unlink, writeFile as writeFile2 } from "fs/promises";
440
- import path4 from "path";
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 = path4.resolve(outputDir);
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 readFile2(filePath, "utf8");
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(path4.dirname(filePath), { recursive: true });
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 = path4.parse(targetPath);
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 = path5.join(skillDir, "agents", "openai.yaml");
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 access2 } from "fs/promises";
681
- import path6 from "path";
806
+ import { access as access3 } from "fs/promises";
807
+ import path7 from "path";
682
808
  async function exists(filePath) {
683
809
  try {
684
- await access2(filePath);
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 = path6.join(skillDir, "agents", "openai.yaml");
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(path6.sep).join("/").toLowerCase();
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 = path7.resolve(options.input);
899
+ const inputDir = path8.resolve(options.input);
774
900
  const targetProvider = options.to;
775
- const outputDir = path7.resolve(
776
- options.out ?? path7.join(path7.dirname(inputDir), `${path7.basename(inputDir)}-${targetProvider}`)
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 (path7.resolve(outputDir) === path7.resolve(inputDir)) {
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 = path7.join(outputDir, "agents", "openai.yaml");
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 = path7.resolve(options.report ?? path7.join(outputDir, "skill-port.report.json"));
850
- await mkdir2(path7.dirname(reportPath), { recursive: true });
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 ? path7.resolve(options.out) : resolveDefaultOutputPath(options.to, targetScope, options.skill);
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 = path7.join(inputDir, "SKILL.md");
1018
+ const skillFile = path8.join(inputDir, "SKILL.md");
888
1019
  try {
889
- await access3(skillFile);
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-OZ2PBTWL.js";
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
- export { type CanonicalSkill, type ConversionReport, type ConvertOptions, type ConvertResult, type ListedSkill, type Provider, type ReportIssue, type ReportMapping, type Scope, type ScopedConvertOptions, StrictModeError, convertScopedSkill, convertSkill, formatSkillsText, formatTextReport, listSkillsByScope };
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
- listSkillsByScope
8
- } from "./chunk-OZ2PBTWL.js";
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
- listSkillsByScope
17
+ listPluginSkills,
18
+ listSkillsByScope,
19
+ readInstalledPlugins
16
20
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-port",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Convert AI coding skills across OpenAI/Codex, Claude Code, and Cursor Skills formats.",
5
5
  "license": "MIT",
6
6
  "type": "module",