@serviceme/devtools-core 2.0.0 → 2.0.2

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.
Files changed (82) hide show
  1. package/dist/auth-Bzss8zOv.mjs +995 -0
  2. package/dist/auth-Bzss8zOv.mjs.map +1 -0
  3. package/dist/auth-DXam3c4S.js +1078 -0
  4. package/dist/auth-DXam3c4S.js.map +1 -0
  5. package/dist/auth.d.mts +2 -2
  6. package/dist/auth.d.ts +2 -2
  7. package/dist/auth.js +14 -1029
  8. package/dist/auth.mjs +2 -991
  9. package/dist/device-CDDcwDY6.mjs +758 -0
  10. package/dist/device-CDDcwDY6.mjs.map +1 -0
  11. package/dist/device-DuAYH8bk.js +839 -0
  12. package/dist/device-DuAYH8bk.js.map +1 -0
  13. package/dist/device.d.mts +2 -455
  14. package/dist/device.d.ts +2 -455
  15. package/dist/device.js +15 -728
  16. package/dist/device.mjs +2 -679
  17. package/dist/index-2rX86yMq.d.mts +331 -0
  18. package/dist/index-BI11AfMH.d.mts +377 -0
  19. package/dist/index-BI11AfMH.d.ts +377 -0
  20. package/dist/index-CoA6y0MP.d.mts +498 -0
  21. package/dist/index-CoA6y0MP.d.ts +498 -0
  22. package/dist/index-DaW7IYA-.d.mts +185 -0
  23. package/dist/index-DaW7IYA-.d.ts +185 -0
  24. package/dist/index-Dp6FwWKh.d.ts +331 -0
  25. package/dist/index.d.mts +2340 -2101
  26. package/dist/index.d.ts +2340 -2101
  27. package/dist/index.js +8117 -10538
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +7895 -10318
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/rolldown-runtime-VH7oDXx4.js +28 -0
  32. package/dist/skill-linker.d.mts +82 -81
  33. package/dist/skill-linker.d.ts +82 -81
  34. package/dist/skill-linker.js +359 -402
  35. package/dist/skill-linker.js.map +1 -1
  36. package/dist/skill-linker.mjs +345 -357
  37. package/dist/skill-linker.mjs.map +1 -1
  38. package/dist/skill-store-Bym2yinU.mjs +388 -0
  39. package/dist/skill-store-Bym2yinU.mjs.map +1 -0
  40. package/dist/skill-store-CJdYyyxu.js +408 -0
  41. package/dist/skill-store-CJdYyyxu.js.map +1 -0
  42. package/dist/skill-store.d.mts +41 -42
  43. package/dist/skill-store.d.ts +41 -42
  44. package/dist/skill-store.js +5 -338
  45. package/dist/skill-store.mjs +2 -301
  46. package/dist/submit-Cg7wSa3x.mjs +84 -0
  47. package/dist/submit-Cg7wSa3x.mjs.map +1 -0
  48. package/dist/submit-CltE6YOZ.js +98 -0
  49. package/dist/submit-CltE6YOZ.js.map +1 -0
  50. package/dist/submit.d.mts +2 -3
  51. package/dist/submit.d.ts +2 -3
  52. package/dist/submit.js +4 -166
  53. package/dist/submit.mjs +2 -130
  54. package/dist/toolbox-CbfxpNg-.mjs +540 -0
  55. package/dist/toolbox-CbfxpNg-.mjs.map +1 -0
  56. package/dist/toolbox-CxnOr-FY.js +614 -0
  57. package/dist/toolbox-CxnOr-FY.js.map +1 -0
  58. package/dist/toolbox.d.mts +2 -244
  59. package/dist/toolbox.d.ts +2 -244
  60. package/dist/toolbox.js +14 -584
  61. package/dist/toolbox.mjs +2 -536
  62. package/dist/{types-B9gk3dXH.d.ts → types-BCV3IedE.d.mts} +25 -23
  63. package/dist/{types-B9gk3dXH.d.mts → types-BCV3IedE.d.ts} +25 -23
  64. package/dist/userHome-BquDeFVE.js +526 -0
  65. package/dist/userHome-BquDeFVE.js.map +1 -0
  66. package/dist/userHome-CcnAQPG1.mjs +212 -0
  67. package/dist/userHome-CcnAQPG1.mjs.map +1 -0
  68. package/package.json +10 -10
  69. package/dist/auth.js.map +0 -1
  70. package/dist/auth.mjs.map +0 -1
  71. package/dist/device.js.map +0 -1
  72. package/dist/device.mjs.map +0 -1
  73. package/dist/index-BMk4tqIT.d.mts +0 -328
  74. package/dist/index-CymN0x9Z.d.ts +0 -328
  75. package/dist/index-PD135hlB.d.mts +0 -626
  76. package/dist/index-PD135hlB.d.ts +0 -626
  77. package/dist/skill-store.js.map +0 -1
  78. package/dist/skill-store.mjs.map +0 -1
  79. package/dist/submit.js.map +0 -1
  80. package/dist/submit.mjs.map +0 -1
  81. package/dist/toolbox.js.map +0 -1
  82. package/dist/toolbox.mjs.map +0 -1
@@ -1,441 +1,398 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/skill-linker/index.ts
31
- var skill_linker_exports = {};
32
- __export(skill_linker_exports, {
33
- LinkError: () => LinkError,
34
- convertToSymlink: () => convertToSymlink,
35
- getPlatformHint: () => getPlatformHint,
36
- installSkillToWorkspace: () => installSkillToWorkspace,
37
- listLegacyUserScopeSkills: () => listLegacyUserScopeSkills,
38
- listLinkedSkills: () => listLinkedSkills,
39
- pickMode: () => pickMode,
40
- resolveSkillLinkPath: () => resolveSkillLinkPath,
41
- resolveSkillTarget: () => resolveSkillTarget,
42
- resolveUserSkillLinkPath: () => resolveUserSkillLinkPath,
43
- uninstallSkillFromWorkspace: () => uninstallSkillFromWorkspace
44
- });
45
- module.exports = __toCommonJS(skill_linker_exports);
46
- var fs = __toESM(require("fs/promises"));
47
- var path2 = __toESM(require("path"));
48
-
49
- // src/paths/userHome.ts
50
- var os = __toESM(require("os"));
51
- var path = __toESM(require("path"));
52
- var SERVICEME_DIR_NAME = ".serviceme";
53
- var REPOS_SUBDIR = "repos";
54
- var SAFE_REPO_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
55
- var SERVICEME_HOME_ENV = "SERVICEME_HOME";
56
- var activeOverrides = {};
57
- function resolveHomeDir() {
58
- const injected = activeOverrides.homeDir;
59
- if (injected !== void 0) {
60
- return injected;
61
- }
62
- return os.homedir();
63
- }
64
- function resolveServicemeHomeEnv() {
65
- const injected = activeOverrides.servicemeHomeEnv;
66
- if (injected !== void 0) {
67
- return injected.length > 0 ? injected : void 0;
68
- }
69
- const envValue = process.env[SERVICEME_HOME_ENV];
70
- return envValue && envValue.length > 0 ? envValue : void 0;
71
- }
72
- function assertSafeRepoId(repoId) {
73
- if (typeof repoId !== "string" || repoId.length === 0 || !SAFE_REPO_ID_PATTERN.test(repoId)) {
74
- throw new Error(
75
- `Invalid repo id: ${JSON.stringify(repoId)}. Must match ${SAFE_REPO_ID_PATTERN} (alphanumeric start, then alphanumerics / underscores / hyphens, \u2264 64 chars).`
76
- );
77
- }
78
- return repoId;
79
- }
80
- function getHomeDir() {
81
- return resolveHomeDir();
82
- }
83
- function getServicemeHome() {
84
- const override = resolveServicemeHomeEnv();
85
- if (override !== void 0) {
86
- return path.resolve(override);
87
- }
88
- return path.join(resolveHomeDir(), SERVICEME_DIR_NAME);
89
- }
90
- function getReposDir() {
91
- return path.join(getServicemeHome(), REPOS_SUBDIR);
92
- }
93
- function getRepoDir(repoId) {
94
- return path.join(getReposDir(), assertSafeRepoId(repoId));
95
- }
96
-
97
- // src/skill-linker/index.ts
98
- var ENTRY_SUBDIR = {
99
- skill: "skills",
100
- agent: "agents"
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.js");
3
+ const require_userHome = require("./userHome-BquDeFVE.js");
4
+ let node_fs_promises = require("node:fs/promises");
5
+ node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises);
6
+ let node_path = require("node:path");
7
+ node_path = require_rolldown_runtime.__toESM(node_path);
8
+ //#region src/skill-linker/index.ts
9
+ const ENTRY_SUBDIR = {
10
+ skill: "skills",
11
+ agent: "agents"
101
12
  };
13
+ /**
14
+ * Sentinel error for link failures. Carries the chosen mode + platform
15
+ * hint so the UI can render an actionable message.
16
+ */
102
17
  var LinkError = class extends Error {
103
- constructor(opts) {
104
- super(
105
- `failed to ${opts.mode} link ${opts.linkPath} \u2192 ${opts.target} on ${opts.platform}: ${opts.cause instanceof Error ? opts.cause.message : String(opts.cause)}`
106
- );
107
- this.name = "LinkError";
108
- this.mode = opts.mode;
109
- this.target = opts.target;
110
- this.linkPath = opts.linkPath;
111
- this.platform = opts.platform;
112
- this.cause = opts.cause;
113
- }
114
- /** A user-facing hint for the platform that failed. */
115
- hint() {
116
- return getPlatformHint(this.platform);
117
- }
18
+ constructor(opts) {
19
+ super(`failed to ${opts.mode} link ${opts.linkPath} → ${opts.target} on ${opts.platform}: ${opts.cause instanceof Error ? opts.cause.message : String(opts.cause)}`);
20
+ this.name = "LinkError";
21
+ this.mode = opts.mode;
22
+ this.target = opts.target;
23
+ this.linkPath = opts.linkPath;
24
+ this.platform = opts.platform;
25
+ this.cause = opts.cause;
26
+ }
27
+ /** A user-facing hint for the platform that failed. */
28
+ hint() {
29
+ return getPlatformHint(this.platform);
30
+ }
118
31
  };
32
+ /** Choose the platform-optimal link mode. */
119
33
  function pickMode(platform = process.platform) {
120
- return platform === "win32" ? "junction" : "symlink";
34
+ return platform === "win32" ? "junction" : "symlink";
121
35
  }
36
+ /** A short hint string for the platform where the link failed. */
122
37
  function getPlatformHint(platform) {
123
- if (platform === "win32") {
124
- return "On Windows, enable Developer Mode in Settings \u2192 Privacy & Security \u2192 For developers, or run as Administrator. NTFS junction also requires the target to be on a local NTFS volume (OneDrive / network shares are not supported).";
125
- }
126
- return "Symlink creation failed. Check filesystem permissions and that the source directory exists. On some filesystems (FAT32, exFAT, network mounts) symlinks are not supported.";
38
+ if (platform === "win32") return "On Windows, enable Developer Mode in Settings → Privacy & Security → For developers, or run as Administrator. NTFS junction also requires the target to be on a local NTFS volume (OneDrive / network shares are not supported).";
39
+ return "Symlink creation failed. Check filesystem permissions and that the source directory exists. On some filesystems (FAT32, exFAT, network mounts) symlinks are not supported.";
127
40
  }
41
+ /**
42
+ * Resolve the source path inside `~/.serviceme/repos/<repoId>/{skills,agents}/<name>`.
43
+ * Exported for callers that need to inspect the target without installing.
44
+ */
128
45
  function resolveSkillTarget(repoId, skillName, kind = "skill") {
129
- return path2.join(getRepoDir(repoId), ENTRY_SUBDIR[kind], skillName);
46
+ return node_path.join(require_userHome.getRepoDir(repoId), ENTRY_SUBDIR[kind], skillName);
130
47
  }
48
+ /**
49
+ * Resolve the workspace link path — `<workspaceDir>/.github/{skills,agents}/<name>` —
50
+ * the same directory Copilot/Claude actually scan.
51
+ */
131
52
  function resolveSkillLinkPath(workspaceDir, skillName, kind = "skill") {
132
- return path2.join(workspaceDir, ".github", ENTRY_SUBDIR[kind], skillName);
53
+ return node_path.join(workspaceDir, ".github", ENTRY_SUBDIR[kind], skillName);
133
54
  }
55
+ /**
56
+ * Resolve the user-scope link path — `~/.copilot/{skills,agents}/<name>` —
57
+ * visible to every workspace.
58
+ */
134
59
  function resolveUserSkillLinkPath(skillName, kind = "skill") {
135
- return path2.join(getHomeDir(), ".copilot", ENTRY_SUBDIR[kind], skillName);
60
+ return node_path.join(require_userHome.getHomeDir(), ".copilot", ENTRY_SUBDIR[kind], skillName);
136
61
  }
137
62
  function resolveLinkPath(opts) {
138
- if (opts.scope === "workspace" && !opts.workspaceDir) {
139
- throw new Error(
140
- "workspaceDir is required for scope: 'workspace' (pass scope: 'user' for a global install instead)."
141
- );
142
- }
143
- const base = opts.scope === "user" ? resolveUserSkillLinkPath(opts.skillName, opts.kind) : resolveSkillLinkPath(opts.workspaceDir, opts.skillName, opts.kind);
144
- return opts.linkBasename ? path2.join(path2.dirname(base), opts.linkBasename) : base;
63
+ if (opts.scope === "workspace" && !opts.workspaceDir) throw new Error("workspaceDir is required for scope: 'workspace' (pass scope: 'user' for a global install instead).");
64
+ const base = opts.scope === "user" ? resolveUserSkillLinkPath(opts.skillName, opts.kind) : resolveSkillLinkPath(opts.workspaceDir, opts.skillName, opts.kind);
65
+ return opts.linkBasename ? node_path.join(node_path.dirname(base), opts.linkBasename) : base;
145
66
  }
67
+ /**
68
+ * Install a skill into a workspace by creating a link at the standard
69
+ * path. Throws `LinkError` on failure (no silent copy fallback per
70
+ * §11.8).
71
+ *
72
+ * Idempotent: if the correct link already exists, returns the existing
73
+ * record without touching the filesystem. If a *wrong* link exists at
74
+ * the target path, it is replaced.
75
+ */
146
76
  async function installSkillToWorkspace(opts) {
147
- const kind = opts.kind ?? "skill";
148
- const scope = opts.scope ?? "workspace";
149
- const isFile = opts.sourceIsFile ?? false;
150
- const target = opts.sourcePath ?? resolveSkillTarget(opts.repoId, opts.skillName, kind);
151
- const linkPath = resolveLinkPath({
152
- workspaceDir: opts.workspaceDir,
153
- skillName: opts.skillName,
154
- kind,
155
- scope,
156
- linkBasename: isFile ? path2.basename(target) : void 0
157
- });
158
- await fs.access(target).catch(() => {
159
- throw new LinkError({
160
- mode: "symlink",
161
- // placeholder — mode not chosen yet
162
- target,
163
- linkPath,
164
- platform: process.platform,
165
- cause: new Error(`skill source not found: ${target}`)
166
- });
167
- });
168
- if (await isCorrectLink(linkPath, target)) {
169
- return { mode: "symlink", linkPath, targetPath: target };
170
- }
171
- await removeIfExists(linkPath);
172
- const requestedMode = opts.mode === void 0 || opts.mode === "auto" ? pickMode() : opts.mode;
173
- const mode = isFile && requestedMode === "junction" ? "symlink" : requestedMode;
174
- await fs.mkdir(path2.dirname(linkPath), { recursive: true });
175
- try {
176
- if (mode === "junction") {
177
- await fs.symlink(target, linkPath, "junction");
178
- } else if (mode === "symlink") {
179
- await fs.symlink(target, linkPath, isFile ? "file" : "dir");
180
- } else {
181
- throw new LinkError({
182
- mode: "copy",
183
- target,
184
- linkPath,
185
- platform: process.platform,
186
- cause: new Error(
187
- "copy mode is not supported in v1 \u2014 see spec \xA711.8 (throw, not copy fallback)"
188
- )
189
- });
190
- }
191
- } catch (err) {
192
- if (err instanceof LinkError) throw err;
193
- throw new LinkError({
194
- mode,
195
- target,
196
- linkPath,
197
- platform: process.platform,
198
- cause: err
199
- });
200
- }
201
- return { mode, linkPath, targetPath: target };
77
+ const kind = opts.kind ?? "skill";
78
+ const scope = opts.scope ?? "workspace";
79
+ const isFile = opts.sourceIsFile ?? false;
80
+ const target = opts.sourcePath ?? resolveSkillTarget(opts.repoId, opts.skillName, kind);
81
+ const linkPath = resolveLinkPath({
82
+ workspaceDir: opts.workspaceDir,
83
+ skillName: opts.skillName,
84
+ kind,
85
+ scope,
86
+ linkBasename: isFile ? node_path.basename(target) : void 0
87
+ });
88
+ await node_fs_promises.access(target).catch(() => {
89
+ throw new LinkError({
90
+ mode: "symlink",
91
+ target,
92
+ linkPath,
93
+ platform: process.platform,
94
+ cause: /* @__PURE__ */ new Error(`skill source not found: ${target}`)
95
+ });
96
+ });
97
+ if (await isCorrectLink(linkPath, target)) return {
98
+ mode: "symlink",
99
+ linkPath,
100
+ targetPath: target
101
+ };
102
+ await removeIfExists(linkPath);
103
+ const requestedMode = opts.mode === void 0 || opts.mode === "auto" ? pickMode() : opts.mode;
104
+ const mode = isFile && requestedMode === "junction" ? "symlink" : requestedMode;
105
+ await node_fs_promises.mkdir(node_path.dirname(linkPath), { recursive: true });
106
+ try {
107
+ if (mode === "junction") await node_fs_promises.symlink(target, linkPath, "junction");
108
+ else if (mode === "symlink") await node_fs_promises.symlink(target, linkPath, isFile ? "file" : "dir");
109
+ else throw new LinkError({
110
+ mode: "copy",
111
+ target,
112
+ linkPath,
113
+ platform: process.platform,
114
+ cause: /* @__PURE__ */ new Error("copy mode is not supported in v1 — see spec §11.8 (throw, not copy fallback)")
115
+ });
116
+ } catch (err) {
117
+ if (err instanceof LinkError) throw err;
118
+ throw new LinkError({
119
+ mode,
120
+ target,
121
+ linkPath,
122
+ platform: process.platform,
123
+ cause: err
124
+ });
125
+ }
126
+ return {
127
+ mode,
128
+ linkPath,
129
+ targetPath: target
130
+ };
202
131
  }
132
+ /**
133
+ * Convert an existing, real (non-symlink) skill/agent directory at the
134
+ * standard scan path into a symlink pointing at the matching global
135
+ * repo entry. Refuses to run when there's nothing real to convert —
136
+ * callers should only surface this action when a marketplace/scope
137
+ * state check already confirmed a non-symlink install is present.
138
+ */
203
139
  async function convertToSymlink(opts) {
204
- const kind = opts.kind ?? "skill";
205
- const scope = opts.scope ?? "workspace";
206
- const linkPath = resolveLinkPath({
207
- workspaceDir: opts.workspaceDir,
208
- skillName: opts.skillName,
209
- kind,
210
- scope,
211
- linkBasename: opts.sourceIsFile && opts.sourcePath ? path2.basename(opts.sourcePath) : void 0
212
- });
213
- const stat2 = await fs.lstat(linkPath).catch(() => null);
214
- if (!stat2) {
215
- throw new Error(`Nothing installed at ${linkPath} to convert.`);
216
- }
217
- if (stat2.isSymbolicLink()) {
218
- throw new Error(`${linkPath} is already a symlink.`);
219
- }
220
- return installSkillToWorkspace(opts);
140
+ const kind = opts.kind ?? "skill";
141
+ const scope = opts.scope ?? "workspace";
142
+ const linkPath = resolveLinkPath({
143
+ workspaceDir: opts.workspaceDir,
144
+ skillName: opts.skillName,
145
+ kind,
146
+ scope,
147
+ linkBasename: opts.sourceIsFile && opts.sourcePath ? node_path.basename(opts.sourcePath) : void 0
148
+ });
149
+ const stat = await node_fs_promises.lstat(linkPath).catch(() => null);
150
+ if (!stat) throw new Error(`Nothing installed at ${linkPath} to convert.`);
151
+ if (stat.isSymbolicLink()) throw new Error(`${linkPath} is already a symlink.`);
152
+ return installSkillToWorkspace(opts);
221
153
  }
154
+ /**
155
+ * Remove a skill/agent link from a workspace/user scope. Idempotent:
156
+ * succeeds when the link does not exist (no error).
157
+ *
158
+ * Tries the bare `<name>` path first (directory-based entries), then
159
+ * falls back to scanning the scope dir for a `<name>.<ext>` file (flat
160
+ * manifest entries, e.g. `CSharpExpert.agent.md`) — the caller doesn't
161
+ * necessarily still have the source repo entry to know which shape was
162
+ * installed (e.g. the repo may have been removed since).
163
+ */
222
164
  async function uninstallSkillFromWorkspace(opts) {
223
- const kind = opts.kind ?? "skill";
224
- const scope = opts.scope ?? "workspace";
225
- const linkPath = resolveLinkPath({
226
- workspaceDir: opts.workspaceDir,
227
- skillName: opts.skillName,
228
- kind,
229
- scope
230
- });
231
- if (await pathExists(linkPath)) {
232
- await removeIfExists(linkPath);
233
- return;
234
- }
235
- const scopeDir = path2.dirname(linkPath);
236
- let entries;
237
- try {
238
- entries = await fs.readdir(scopeDir, { withFileTypes: true });
239
- } catch {
240
- entries = [];
241
- }
242
- const flatMatch = entries.find(
243
- (e) => e.name === opts.skillName || e.name.startsWith(`${opts.skillName}.`)
244
- );
245
- if (flatMatch) {
246
- await removeIfExists(path2.join(scopeDir, flatMatch.name));
247
- return;
248
- }
249
- if (scope === "user") {
250
- const legacyDir = path2.join(getHomeDir(), LEGACY_USER_SCOPE_ROOT, ENTRY_SUBDIR[kind]);
251
- const legacyEntries = await fs.readdir(legacyDir, { withFileTypes: true }).catch(() => []);
252
- const legacyMatch = legacyEntries.find(
253
- (e) => e.name === opts.skillName || e.name.startsWith(`${opts.skillName}.`)
254
- );
255
- if (legacyMatch) {
256
- await removeIfExists(path2.join(legacyDir, legacyMatch.name));
257
- }
258
- }
165
+ const kind = opts.kind ?? "skill";
166
+ const scope = opts.scope ?? "workspace";
167
+ const linkPath = resolveLinkPath({
168
+ workspaceDir: opts.workspaceDir,
169
+ skillName: opts.skillName,
170
+ kind,
171
+ scope
172
+ });
173
+ if (await pathExists(linkPath)) {
174
+ await removeIfExists(linkPath);
175
+ return;
176
+ }
177
+ const scopeDir = node_path.dirname(linkPath);
178
+ let entries;
179
+ try {
180
+ entries = await node_fs_promises.readdir(scopeDir, { withFileTypes: true });
181
+ } catch {
182
+ entries = [];
183
+ }
184
+ const flatMatch = entries.find((e) => e.name === opts.skillName || e.name.startsWith(`${opts.skillName}.`));
185
+ if (flatMatch) {
186
+ await removeIfExists(node_path.join(scopeDir, flatMatch.name));
187
+ return;
188
+ }
189
+ if (scope === "user") {
190
+ const legacyDir = node_path.join(require_userHome.getHomeDir(), LEGACY_USER_SCOPE_ROOT, ENTRY_SUBDIR[kind]);
191
+ const legacyMatch = (await node_fs_promises.readdir(legacyDir, { withFileTypes: true }).catch(() => [])).find((e) => e.name === opts.skillName || e.name.startsWith(`${opts.skillName}.`));
192
+ if (legacyMatch) await removeIfExists(node_path.join(legacyDir, legacyMatch.name));
193
+ }
259
194
  }
195
+ /** `true` when `p` exists (following symlinks is irrelevant — `lstat` is enough). */
260
196
  async function pathExists(p) {
261
- try {
262
- await fs.lstat(p);
263
- return true;
264
- } catch {
265
- return false;
266
- }
197
+ try {
198
+ await node_fs_promises.lstat(p);
199
+ return true;
200
+ } catch {
201
+ return false;
202
+ }
267
203
  }
268
- var MANIFEST_FILENAME = {
269
- skill: "SKILL.md",
270
- agent: "AGENT.md"
204
+ const MANIFEST_FILENAME = {
205
+ skill: "SKILL.md",
206
+ agent: "AGENT.md"
271
207
  };
272
- var LEGACY_USER_SCOPE_ROOT = ".agents";
208
+ /**
209
+ * Legacy user-scope root used before the migration to `~/.copilot`.
210
+ * Kept readable so user-scope listing can surface old links instead of
211
+ * silently hiding content the user still has installed.
212
+ */
213
+ const LEGACY_USER_SCOPE_ROOT = ".agents";
214
+ /**
215
+ * Enumerate all skill/agent entries currently present in a workspace/user
216
+ * scope — both symlinked installs (`mode: "symlink"`/`"junction"`) AND
217
+ * real, non-symlink directories/files that already carry a valid
218
+ * manifest (`mode: "copy"`). Used by the UI's "Manage installed skills"
219
+ * view so it can show a real (copy-installed) entry as installed too —
220
+ * this is common for the user (global) scope, where many skills are
221
+ * placed by other tools/installers as plain copies rather than via this
222
+ * package's symlink-only install flow. `repoId` is `""` for `"copy"`
223
+ * entries since there's no symlink target to resolve it from; callers
224
+ * match these by name only. Returns an empty array when the scan
225
+ * directory doesn't exist.
226
+ */
273
227
  async function listLinkedSkills(workspaceDir, kind = "skill", scope = "workspace") {
274
- if (scope === "workspace" && !workspaceDir) {
275
- return [];
276
- }
277
- const userRoot = path2.join(getHomeDir(), ".copilot", ENTRY_SUBDIR[kind]);
278
- const root = scope === "user" ? userRoot : path2.join(workspaceDir, ".github", ENTRY_SUBDIR[kind]);
279
- let entries;
280
- try {
281
- entries = await fs.readdir(root, { withFileTypes: true });
282
- } catch (err) {
283
- if (err.code === "ENOENT") return [];
284
- throw err;
285
- }
286
- const results = [];
287
- for (const entry of entries) {
288
- const entryPath = path2.join(root, entry.name);
289
- if (entry.isSymbolicLink()) {
290
- const skillName = entry.name.replace(/\.(agent|skill)\.md$/, "");
291
- const rawTarget = await readLinkTarget(entryPath);
292
- if (!rawTarget) continue;
293
- const resolvedTarget = resolveTargetPath(entryPath, rawTarget);
294
- const targetStat = await fs.stat(resolvedTarget).catch(() => null);
295
- if (!targetStat) continue;
296
- results.push({
297
- repoId: extractRepoIdFromTarget(resolvedTarget) ?? "",
298
- skillName,
299
- linkPath: entryPath,
300
- targetPath: resolvedTarget,
301
- mode: process.platform === "win32" ? "junction" : "symlink",
302
- scope
303
- });
304
- continue;
305
- }
306
- if (entry.isDirectory()) {
307
- const hasManifest = await fs.access(path2.join(entryPath, MANIFEST_FILENAME[kind])).then(() => true).catch(() => false);
308
- if (!hasManifest) continue;
309
- results.push({
310
- repoId: "",
311
- skillName: entry.name,
312
- linkPath: entryPath,
313
- targetPath: entryPath,
314
- mode: "copy",
315
- scope
316
- });
317
- continue;
318
- }
319
- if (entry.isFile() && entry.name.endsWith(`.${kind}.md`)) {
320
- results.push({
321
- repoId: "",
322
- skillName: entry.name.replace(/\.(agent|skill)\.md$/, ""),
323
- linkPath: entryPath,
324
- targetPath: entryPath,
325
- mode: "copy",
326
- scope
327
- });
328
- }
329
- }
330
- if (scope === "user") {
331
- const seen = new Set(results.map((r) => r.skillName));
332
- for (const legacy of await listLegacyUserScopeSkills(kind)) {
333
- if (!seen.has(legacy.skillName)) {
334
- results.push(legacy);
335
- }
336
- }
337
- }
338
- return results;
228
+ if (scope === "workspace" && !workspaceDir) return [];
229
+ const userRoot = node_path.join(require_userHome.getHomeDir(), ".copilot", ENTRY_SUBDIR[kind]);
230
+ const root = scope === "user" ? userRoot : node_path.join(workspaceDir, ".github", ENTRY_SUBDIR[kind]);
231
+ let entries;
232
+ try {
233
+ entries = await node_fs_promises.readdir(root, { withFileTypes: true });
234
+ } catch (err) {
235
+ if (err.code === "ENOENT") return [];
236
+ throw err;
237
+ }
238
+ const results = [];
239
+ for (const entry of entries) {
240
+ const entryPath = node_path.join(root, entry.name);
241
+ if (entry.isSymbolicLink()) {
242
+ const skillName = entry.name.replace(/\.(agent|skill)\.md$/, "");
243
+ const rawTarget = await readLinkTarget(entryPath);
244
+ if (!rawTarget) continue;
245
+ const resolvedTarget = resolveTargetPath(entryPath, rawTarget);
246
+ if (!await node_fs_promises.stat(resolvedTarget).catch(() => null)) continue;
247
+ results.push({
248
+ repoId: extractRepoIdFromTarget(resolvedTarget) ?? "",
249
+ skillName,
250
+ linkPath: entryPath,
251
+ targetPath: resolvedTarget,
252
+ mode: process.platform === "win32" ? "junction" : "symlink",
253
+ scope
254
+ });
255
+ continue;
256
+ }
257
+ if (entry.isDirectory()) {
258
+ if (!await node_fs_promises.access(node_path.join(entryPath, MANIFEST_FILENAME[kind])).then(() => true).catch(() => false)) continue;
259
+ results.push({
260
+ repoId: "",
261
+ skillName: entry.name,
262
+ linkPath: entryPath,
263
+ targetPath: entryPath,
264
+ mode: "copy",
265
+ scope
266
+ });
267
+ continue;
268
+ }
269
+ if (entry.isFile() && entry.name.endsWith(`.${kind}.md`)) results.push({
270
+ repoId: "",
271
+ skillName: entry.name.replace(/\.(agent|skill)\.md$/, ""),
272
+ linkPath: entryPath,
273
+ targetPath: entryPath,
274
+ mode: "copy",
275
+ scope
276
+ });
277
+ }
278
+ if (scope === "user") {
279
+ const seen = new Set(results.map((r) => r.skillName));
280
+ for (const legacy of await listLegacyUserScopeSkills(kind)) if (!seen.has(legacy.skillName)) results.push(legacy);
281
+ }
282
+ return results;
339
283
  }
284
+ /**
285
+ * List entries still living in the legacy `~/.agents` user scope.
286
+ * Read-only: migration is an explicit user action elsewhere.
287
+ */
340
288
  async function listLegacyUserScopeSkills(kind = "skill") {
341
- const root = path2.join(getHomeDir(), LEGACY_USER_SCOPE_ROOT, ENTRY_SUBDIR[kind]);
342
- let entries;
343
- try {
344
- entries = await fs.readdir(root, { withFileTypes: true });
345
- } catch (err) {
346
- if (err.code === "ENOENT") return [];
347
- throw err;
348
- }
349
- const results = [];
350
- for (const entry of entries) {
351
- const entryPath = path2.join(root, entry.name);
352
- if (entry.isSymbolicLink()) {
353
- const skillName = entry.name.replace(/\.(agent|skill)\.md$/, "");
354
- const rawTarget = await readLinkTarget(entryPath);
355
- if (!rawTarget) continue;
356
- const resolvedTarget = resolveTargetPath(entryPath, rawTarget);
357
- const targetStat = await fs.stat(resolvedTarget).catch(() => null);
358
- if (!targetStat) continue;
359
- results.push({
360
- repoId: extractRepoIdFromTarget(resolvedTarget) ?? "",
361
- skillName,
362
- linkPath: entryPath,
363
- targetPath: resolvedTarget,
364
- mode: process.platform === "win32" ? "junction" : "symlink",
365
- scope: "user"
366
- });
367
- } else if (entry.isDirectory()) {
368
- const manifestPath = path2.join(entryPath, MANIFEST_FILENAME[kind]);
369
- const stat2 = await fs.stat(manifestPath).catch(() => null);
370
- if (stat2?.isFile()) {
371
- results.push({
372
- repoId: "",
373
- skillName: entry.name,
374
- linkPath: entryPath,
375
- targetPath: manifestPath,
376
- mode: "copy",
377
- scope: "user"
378
- });
379
- }
380
- }
381
- }
382
- return results;
289
+ const root = node_path.join(require_userHome.getHomeDir(), LEGACY_USER_SCOPE_ROOT, ENTRY_SUBDIR[kind]);
290
+ let entries;
291
+ try {
292
+ entries = await node_fs_promises.readdir(root, { withFileTypes: true });
293
+ } catch (err) {
294
+ if (err.code === "ENOENT") return [];
295
+ throw err;
296
+ }
297
+ const results = [];
298
+ for (const entry of entries) {
299
+ const entryPath = node_path.join(root, entry.name);
300
+ if (entry.isSymbolicLink()) {
301
+ const skillName = entry.name.replace(/\.(agent|skill)\.md$/, "");
302
+ const rawTarget = await readLinkTarget(entryPath);
303
+ if (!rawTarget) continue;
304
+ const resolvedTarget = resolveTargetPath(entryPath, rawTarget);
305
+ if (!await node_fs_promises.stat(resolvedTarget).catch(() => null)) continue;
306
+ results.push({
307
+ repoId: extractRepoIdFromTarget(resolvedTarget) ?? "",
308
+ skillName,
309
+ linkPath: entryPath,
310
+ targetPath: resolvedTarget,
311
+ mode: process.platform === "win32" ? "junction" : "symlink",
312
+ scope: "user"
313
+ });
314
+ } else if (entry.isDirectory()) {
315
+ const manifestPath = node_path.join(entryPath, MANIFEST_FILENAME[kind]);
316
+ if ((await node_fs_promises.stat(manifestPath).catch(() => null))?.isFile()) results.push({
317
+ repoId: "",
318
+ skillName: entry.name,
319
+ linkPath: entryPath,
320
+ targetPath: manifestPath,
321
+ mode: "copy",
322
+ scope: "user"
323
+ });
324
+ }
325
+ }
326
+ return results;
383
327
  }
328
+ /** Returns `true` when `linkPath` is a symlink/junction pointing at `expectedTarget`. */
384
329
  async function isCorrectLink(linkPath, expectedTarget) {
385
- let actual;
386
- try {
387
- actual = await readLinkTarget(linkPath);
388
- } catch {
389
- return false;
390
- }
391
- if (!actual) return false;
392
- return resolveTargetPath(linkPath, actual) === resolveTargetPath(linkPath, expectedTarget);
330
+ let actual;
331
+ try {
332
+ actual = await readLinkTarget(linkPath);
333
+ } catch {
334
+ return false;
335
+ }
336
+ if (!actual) return false;
337
+ return resolveTargetPath(linkPath, actual) === resolveTargetPath(linkPath, expectedTarget);
393
338
  }
339
+ /** Read a link's target without throwing — returns undefined on ENOENT. */
394
340
  async function readLinkTarget(linkPath) {
395
- try {
396
- return await fs.readlink(linkPath);
397
- } catch (err) {
398
- if (err.code === "ENOENT") return void 0;
399
- throw err;
400
- }
341
+ try {
342
+ return await node_fs_promises.readlink(linkPath);
343
+ } catch (err) {
344
+ if (err.code === "ENOENT") return void 0;
345
+ throw err;
346
+ }
401
347
  }
348
+ /**
349
+ * Resolve a relative symlink target against its parent directory so a
350
+ * relative link reads back as an absolute path comparable to another
351
+ * absolute path.
352
+ */
402
353
  function resolveTargetPath(linkPath, target) {
403
- if (path2.isAbsolute(target)) return path2.normalize(target);
404
- return path2.normalize(path2.resolve(path2.dirname(linkPath), target));
354
+ if (node_path.isAbsolute(target)) return node_path.normalize(target);
355
+ return node_path.normalize(node_path.resolve(node_path.dirname(linkPath), target));
405
356
  }
357
+ /**
358
+ * Parse the `<repoId>` segment back out of a resolved link target of
359
+ * the shape `.../.serviceme/repos/<repoId>/{skills,agents}/<name>`.
360
+ * Returns `undefined` when the target doesn't match that shape (e.g. a
361
+ * hand-authored local skill under `~/.agents/skills/<name>` with no
362
+ * backing repo).
363
+ */
406
364
  function extractRepoIdFromTarget(target) {
407
- const marker = `${path2.sep}repos${path2.sep}`;
408
- const idx = target.indexOf(marker);
409
- if (idx === -1) return void 0;
410
- const rest = target.slice(idx + marker.length);
411
- const [repoId] = rest.split(path2.sep);
412
- return repoId || void 0;
365
+ const marker = `${node_path.sep}repos${node_path.sep}`;
366
+ const idx = target.indexOf(marker);
367
+ if (idx === -1) return void 0;
368
+ const [repoId] = target.slice(idx + marker.length).split(node_path.sep);
369
+ return repoId || void 0;
413
370
  }
371
+ /** Best-effort removal of a path. ENOENT is silently ignored. */
414
372
  async function removeIfExists(p) {
415
- try {
416
- const stat2 = await fs.lstat(p);
417
- if (stat2.isDirectory() && !stat2.isSymbolicLink()) {
418
- await fs.rm(p, { recursive: true, force: true });
419
- } else {
420
- await fs.unlink(p);
421
- }
422
- } catch (err) {
423
- if (err.code === "ENOENT") return;
424
- throw err;
425
- }
373
+ try {
374
+ const stat = await node_fs_promises.lstat(p);
375
+ if (stat.isDirectory() && !stat.isSymbolicLink()) await node_fs_promises.rm(p, {
376
+ recursive: true,
377
+ force: true
378
+ });
379
+ else await node_fs_promises.unlink(p);
380
+ } catch (err) {
381
+ if (err.code === "ENOENT") return;
382
+ throw err;
383
+ }
426
384
  }
427
- // Annotate the CommonJS export names for ESM import in node:
428
- 0 && (module.exports = {
429
- LinkError,
430
- convertToSymlink,
431
- getPlatformHint,
432
- installSkillToWorkspace,
433
- listLegacyUserScopeSkills,
434
- listLinkedSkills,
435
- pickMode,
436
- resolveSkillLinkPath,
437
- resolveSkillTarget,
438
- resolveUserSkillLinkPath,
439
- uninstallSkillFromWorkspace
440
- });
385
+ //#endregion
386
+ exports.LinkError = LinkError;
387
+ exports.convertToSymlink = convertToSymlink;
388
+ exports.getPlatformHint = getPlatformHint;
389
+ exports.installSkillToWorkspace = installSkillToWorkspace;
390
+ exports.listLegacyUserScopeSkills = listLegacyUserScopeSkills;
391
+ exports.listLinkedSkills = listLinkedSkills;
392
+ exports.pickMode = pickMode;
393
+ exports.resolveSkillLinkPath = resolveSkillLinkPath;
394
+ exports.resolveSkillTarget = resolveSkillTarget;
395
+ exports.resolveUserSkillLinkPath = resolveUserSkillLinkPath;
396
+ exports.uninstallSkillFromWorkspace = uninstallSkillFromWorkspace;
397
+
441
398
  //# sourceMappingURL=skill-linker.js.map