@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.
- package/dist/auth-Bzss8zOv.mjs +995 -0
- package/dist/auth-Bzss8zOv.mjs.map +1 -0
- package/dist/auth-DXam3c4S.js +1078 -0
- package/dist/auth-DXam3c4S.js.map +1 -0
- package/dist/auth.d.mts +2 -2
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +14 -1029
- package/dist/auth.mjs +2 -991
- package/dist/device-CDDcwDY6.mjs +758 -0
- package/dist/device-CDDcwDY6.mjs.map +1 -0
- package/dist/device-DuAYH8bk.js +839 -0
- package/dist/device-DuAYH8bk.js.map +1 -0
- package/dist/device.d.mts +2 -455
- package/dist/device.d.ts +2 -455
- package/dist/device.js +15 -728
- package/dist/device.mjs +2 -679
- package/dist/index-2rX86yMq.d.mts +331 -0
- package/dist/index-BI11AfMH.d.mts +377 -0
- package/dist/index-BI11AfMH.d.ts +377 -0
- package/dist/index-CoA6y0MP.d.mts +498 -0
- package/dist/index-CoA6y0MP.d.ts +498 -0
- package/dist/index-DaW7IYA-.d.mts +185 -0
- package/dist/index-DaW7IYA-.d.ts +185 -0
- package/dist/index-Dp6FwWKh.d.ts +331 -0
- package/dist/index.d.mts +2340 -2101
- package/dist/index.d.ts +2340 -2101
- package/dist/index.js +8117 -10538
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7895 -10318
- package/dist/index.mjs.map +1 -1
- package/dist/rolldown-runtime-VH7oDXx4.js +28 -0
- package/dist/skill-linker.d.mts +82 -81
- package/dist/skill-linker.d.ts +82 -81
- package/dist/skill-linker.js +359 -402
- package/dist/skill-linker.js.map +1 -1
- package/dist/skill-linker.mjs +345 -357
- package/dist/skill-linker.mjs.map +1 -1
- package/dist/skill-store-Bym2yinU.mjs +388 -0
- package/dist/skill-store-Bym2yinU.mjs.map +1 -0
- package/dist/skill-store-CJdYyyxu.js +408 -0
- package/dist/skill-store-CJdYyyxu.js.map +1 -0
- package/dist/skill-store.d.mts +41 -42
- package/dist/skill-store.d.ts +41 -42
- package/dist/skill-store.js +5 -338
- package/dist/skill-store.mjs +2 -301
- package/dist/submit-Cg7wSa3x.mjs +84 -0
- package/dist/submit-Cg7wSa3x.mjs.map +1 -0
- package/dist/submit-CltE6YOZ.js +98 -0
- package/dist/submit-CltE6YOZ.js.map +1 -0
- package/dist/submit.d.mts +2 -3
- package/dist/submit.d.ts +2 -3
- package/dist/submit.js +4 -166
- package/dist/submit.mjs +2 -130
- package/dist/toolbox-CbfxpNg-.mjs +540 -0
- package/dist/toolbox-CbfxpNg-.mjs.map +1 -0
- package/dist/toolbox-CxnOr-FY.js +614 -0
- package/dist/toolbox-CxnOr-FY.js.map +1 -0
- package/dist/toolbox.d.mts +2 -244
- package/dist/toolbox.d.ts +2 -244
- package/dist/toolbox.js +14 -584
- package/dist/toolbox.mjs +2 -536
- package/dist/{types-B9gk3dXH.d.ts → types-BCV3IedE.d.mts} +25 -23
- package/dist/{types-B9gk3dXH.d.mts → types-BCV3IedE.d.ts} +25 -23
- package/dist/userHome-BquDeFVE.js +526 -0
- package/dist/userHome-BquDeFVE.js.map +1 -0
- package/dist/userHome-CcnAQPG1.mjs +212 -0
- package/dist/userHome-CcnAQPG1.mjs.map +1 -0
- package/package.json +10 -10
- package/dist/auth.js.map +0 -1
- package/dist/auth.mjs.map +0 -1
- package/dist/device.js.map +0 -1
- package/dist/device.mjs.map +0 -1
- package/dist/index-BMk4tqIT.d.mts +0 -328
- package/dist/index-CymN0x9Z.d.ts +0 -328
- package/dist/index-PD135hlB.d.mts +0 -626
- package/dist/index-PD135hlB.d.ts +0 -626
- package/dist/skill-store.js.map +0 -1
- package/dist/skill-store.mjs.map +0 -1
- package/dist/submit.js.map +0 -1
- package/dist/submit.mjs.map +0 -1
- package/dist/toolbox.js.map +0 -1
- package/dist/toolbox.mjs.map +0 -1
package/dist/skill-linker.js
CHANGED
|
@@ -1,441 +1,398 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
return node_path.join(require_userHome.getHomeDir(), ".copilot", ENTRY_SUBDIR[kind], skillName);
|
|
136
61
|
}
|
|
137
62
|
function resolveLinkPath(opts) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
197
|
+
try {
|
|
198
|
+
await node_fs_promises.lstat(p);
|
|
199
|
+
return true;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
267
203
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
204
|
+
const MANIFEST_FILENAME = {
|
|
205
|
+
skill: "SKILL.md",
|
|
206
|
+
agent: "AGENT.md"
|
|
271
207
|
};
|
|
272
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
404
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|