@kwonye/mcpx 0.1.104 → 0.1.105

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.
@@ -3,7 +3,7 @@ import {
3
3
  getConfigPath,
4
4
  readJsonFile,
5
5
  writeJsonAtomic
6
- } from "./chunk-cqenqnxk.js";
6
+ } from "./chunk-4y0t0tfj.js";
7
7
  import {
8
8
  normalizeServerSpecEnabled
9
9
  } from "./chunk-bjw71y54.js";
@@ -508,6 +508,8 @@ var managedPluginSchema = z.object({
508
508
  sourceFingerprint: z.string()
509
509
  }).optional(),
510
510
  updateError: z.string().optional(),
511
+ pinned: z.boolean().default(false),
512
+ previous: z.object({ version: z.string(), resolvedSha: z.string(), root: z.string() }).optional(),
511
513
  projectOverrides: z.record(z.string(), z.object({
512
514
  enabled: z.boolean().optional(),
513
515
  components: z.record(pluginComponentSchema, z.boolean()).optional(),
@@ -735,4 +737,4 @@ function resolveActiveConfig(options = {}) {
735
737
 
736
738
  export { tokenizeCommandLine, parseCliAddCommand, ConfigLoadError, defaultConfig, loadConfig, saveConfig, loadProjectConfig, saveProjectConfig, loadMergedConfig, migrateProjectServers, resolveActiveConfig };
737
739
 
738
- //# debugId=83D38E071988634664756E2164756E21
740
+ //# debugId=5F19AEF9E1E9E38564756E2164756E21
@@ -30,6 +30,12 @@ function getTelemetryPath() {
30
30
  function getSkillsDir() {
31
31
  return path.join(getConfigRoot(), "mcpx", "skills");
32
32
  }
33
+ function getSkillPackagesRoot() {
34
+ return path.join(getDataRoot(), "mcpx", "skills", "packages");
35
+ }
36
+ function getSkillStatePath() {
37
+ return path.join(getDataRoot(), "mcpx", "skills", "state.json");
38
+ }
33
39
  function getManagedIndexPath() {
34
40
  return path.join(getDataRoot(), "mcpx", "managed-index.json");
35
41
  }
@@ -132,6 +138,6 @@ function sha256(value) {
132
138
  return crypto2.createHash("sha256").update(value).digest("hex");
133
139
  }
134
140
 
135
- export { homeDir, getConfigPath, getTelemetryPath, getSkillsDir, getManagedIndexPath, getSecretsStorePath, getSecretsKeyPath, getPidPath, getLogPath, getGatewayTokenPath, getUpdatesDir, getStagedVersionPath, getUpdateLockPath, getOAuthLockPath, getPluginCacheRoot, getPluginDataRoot, getMarketplaceCacheRoot, getMarketplaceUpdateStatePath, getMarketplaceUpdateLockPath, ensureDir, ensureParentDir, findProjectConfigPath, readJsonFile, writeJsonAtomic, sha256 };
141
+ export { homeDir, getConfigPath, getTelemetryPath, getSkillsDir, getSkillPackagesRoot, getSkillStatePath, getManagedIndexPath, getSecretsStorePath, getSecretsKeyPath, getPidPath, getLogPath, getGatewayTokenPath, getUpdatesDir, getStagedVersionPath, getUpdateLockPath, getOAuthLockPath, getPluginCacheRoot, getPluginDataRoot, getMarketplaceCacheRoot, getMarketplaceUpdateStatePath, getMarketplaceUpdateLockPath, ensureDir, ensureParentDir, findProjectConfigPath, readJsonFile, writeJsonAtomic, sha256 };
136
142
 
137
- //# debugId=96DE5993BD5A6D3864756E2164756E21
143
+ //# debugId=BCB55F79AB94B71A64756E2164756E21
@@ -3,11 +3,11 @@ import {
3
3
  loadProjectConfig,
4
4
  saveConfig,
5
5
  saveProjectConfig
6
- } from "./chunk-wq4zd3t7.js";
6
+ } from "./chunk-01s7rhx3.js";
7
7
  import {
8
8
  SecretsManager,
9
9
  ensureGatewayToken
10
- } from "./chunk-cz8cdmw1.js";
10
+ } from "./chunk-fw8dyt89.js";
11
11
  import {
12
12
  ensureDir,
13
13
  findProjectConfigPath,
@@ -15,12 +15,14 @@ import {
15
15
  getManagedIndexPath,
16
16
  getPluginCacheRoot,
17
17
  getPluginDataRoot,
18
+ getSkillPackagesRoot,
19
+ getSkillStatePath,
18
20
  getSkillsDir,
19
21
  homeDir,
20
22
  readJsonFile,
21
23
  sha256,
22
24
  writeJsonAtomic
23
- } from "./chunk-cqenqnxk.js";
25
+ } from "./chunk-4y0t0tfj.js";
24
26
  import {
25
27
  isServerEnabled
26
28
  } from "./chunk-bjw71y54.js";
@@ -35,6 +37,7 @@ import path21 from "node:path";
35
37
  // src/core/plugin-cache.ts
36
38
  import fs from "node:fs";
37
39
  import path from "node:path";
40
+ import crypto from "node:crypto";
38
41
  import { execFile } from "node:child_process";
39
42
  import { promisify } from "node:util";
40
43
  var TMP_PREFIX = "mcpx-acquire-";
@@ -83,7 +86,13 @@ class PluginCache {
83
86
  return computeTreeHash(resolved);
84
87
  }
85
88
  if (source.type === "npm") {
86
- throw new Error("npm source SHA resolution not yet implemented");
89
+ const pkg = source.original.replace(/^npm:/, "");
90
+ const spec = source.ref ? `${pkg}@${source.ref}` : pkg;
91
+ const { stdout } = await execFileAsync("npm", ["view", spec, "version", "dist.integrity", "--json"], { timeout: 30000 });
92
+ const metadata = JSON.parse(stdout);
93
+ if (!metadata.version)
94
+ throw new Error(`Could not resolve npm package ${spec}`);
95
+ return `npm-${metadata.version}-${metadata.dist?.integrity ?? metadata["dist.integrity"] ?? "unverified"}`;
87
96
  }
88
97
  throw new Error(`Cannot resolve SHA for source type: ${source.type}`);
89
98
  }
@@ -161,7 +170,27 @@ class PluginCache {
161
170
  return { source: source.original, name: pluginName, sha, root: destDir };
162
171
  }
163
172
  if (source.type === "npm") {
164
- throw new Error("npm source fetch not yet implemented");
173
+ const pkg = source.original.replace(/^npm:/, "");
174
+ const spec = source.ref ? `${pkg}@${source.ref}` : pkg;
175
+ const tmpDir = fs.mkdtempSync(path.join(this.cacheRoot, TMP_PREFIX));
176
+ try {
177
+ const { stdout } = await execFileAsync("npm", ["pack", spec, "--ignore-scripts", "--json", "--pack-destination", tmpDir], { timeout: 120000 });
178
+ const packed = JSON.parse(stdout);
179
+ const filename = packed[0]?.filename;
180
+ if (!filename)
181
+ throw new Error(`npm did not produce an archive for ${spec}`);
182
+ const archive = path.join(tmpDir, filename);
183
+ const actualSha = `npm-${packed[0]?.version ?? source.ref ?? "latest"}-${crypto.createHash("sha256").update(fs.readFileSync(archive)).digest("hex")}`;
184
+ const finalDest = this.shaDir(srcDir, actualSha);
185
+ if (!fs.existsSync(finalDest)) {
186
+ ensureDir(finalDest);
187
+ await execFileAsync("tar", ["-xzf", archive, "--strip-components=1", "-C", finalDest], { timeout: 30000 });
188
+ }
189
+ this.updateRef(srcDir, actualSha);
190
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
191
+ } finally {
192
+ fs.rmSync(tmpDir, { recursive: true, force: true });
193
+ }
165
194
  }
166
195
  throw new Error(`Cannot fetch source type: ${source.type}`);
167
196
  }
@@ -224,7 +253,6 @@ class PluginCache {
224
253
  }
225
254
  function computeTreeHash(dir) {
226
255
  const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
227
- const crypto = __require("node:crypto");
228
256
  const hash = crypto.createHash("sha256");
229
257
  for (const entry of entries) {
230
258
  if (entry.name.startsWith(".git"))
@@ -1280,6 +1308,29 @@ function copyPluginDir(src, dest, exclude = [], errors) {
1280
1308
  }
1281
1309
  function copyFileOrDir(src, dest, errors) {
1282
1310
  try {
1311
+ if (path6.basename(src).toLowerCase() === "skill.md") {
1312
+ const sourceRoot = path6.dirname(src);
1313
+ const targetRoot = path6.dirname(dest);
1314
+ ensureDir(path6.dirname(targetRoot));
1315
+ if (fs6.existsSync(targetRoot)) {
1316
+ const stat = fs6.lstatSync(targetRoot);
1317
+ if (stat.isSymbolicLink())
1318
+ fs6.rmSync(targetRoot, { force: true });
1319
+ else if (stat.isDirectory() && fs6.readdirSync(targetRoot).length === 0)
1320
+ fs6.rmSync(targetRoot, { recursive: true, force: true });
1321
+ }
1322
+ if (!fs6.existsSync(targetRoot)) {
1323
+ const relativeTarget = path6.relative(path6.dirname(targetRoot), path6.resolve(sourceRoot));
1324
+ try {
1325
+ fs6.symlinkSync(relativeTarget, targetRoot, process.platform === "win32" ? "junction" : "dir");
1326
+ } catch {
1327
+ fs6.cpSync(sourceRoot, targetRoot, { recursive: true, dereference: true });
1328
+ }
1329
+ } else {
1330
+ fs6.cpSync(sourceRoot, targetRoot, { recursive: true, dereference: true });
1331
+ }
1332
+ return;
1333
+ }
1283
1334
  ensureDir(path6.dirname(dest));
1284
1335
  if (fs6.statSync(src).isDirectory()) {
1285
1336
  copyPluginDir(src, dest, [], errors);
@@ -1334,8 +1385,9 @@ function loadOwnership2(targetDir) {
1334
1385
  }
1335
1386
  try {
1336
1387
  const parsed = ownershipSchema.safeParse(JSON.parse(fs7.readFileSync(manifestPath2, "utf8")));
1337
- if (parsed.success)
1338
- return parsed.data;
1388
+ if (parsed.success) {
1389
+ return { ...parsed.data, paths: parsed.data.paths.map((ownedPath) => ownedPath.endsWith("/SKILL.md") ? path7.dirname(ownedPath) : ownedPath) };
1390
+ }
1339
1391
  } catch {
1340
1392
  console.error(`[mcpx] Warning: corrupt ownership manifest at ${manifestPath2}, resetting`);
1341
1393
  return { version: 1, paths: [] };
@@ -1348,7 +1400,7 @@ function saveOwnership2(targetDir, manifest) {
1348
1400
  }
1349
1401
  function projectSkillsToDir(targetDir, skills, layout) {
1350
1402
  const owned = loadOwnership2(targetDir);
1351
- const newPaths = new Set(skills.map((s) => layout === "dir" ? `${s.id}/SKILL.md` : `${s.id}.md`));
1403
+ const newPaths = new Set(skills.map((s) => layout === "dir" ? s.id : `${s.id}.md`));
1352
1404
  for (const ownedPath of owned.paths) {
1353
1405
  if (!newPaths.has(ownedPath)) {
1354
1406
  const fullPath = path7.join(targetDir, ownedPath);
@@ -1374,10 +1426,46 @@ function projectSkillsToDir(targetDir, skills, layout) {
1374
1426
  }
1375
1427
  for (const skill of skills) {
1376
1428
  validateSkillId(skill.id);
1377
- const filePath = layout === "dir" ? path7.join(targetDir, skill.id, "SKILL.md") : path7.join(targetDir, `${skill.id}.md`);
1378
- assertWithinBase2(targetDir, filePath);
1379
- ensureDir(path7.dirname(filePath));
1380
- fs7.writeFileSync(filePath, skill.content, "utf-8");
1429
+ const ownedTarget = layout === "dir" ? skill.id : `${skill.id}.md`;
1430
+ if ((fs7.existsSync(path7.join(targetDir, ownedTarget)) || (() => {
1431
+ try {
1432
+ fs7.lstatSync(path7.join(targetDir, ownedTarget));
1433
+ return true;
1434
+ } catch {
1435
+ return false;
1436
+ }
1437
+ })()) && !owned.paths.includes(ownedTarget)) {
1438
+ console.error(`[mcpx] Warning: preserving unowned skill projection: ${path7.join(targetDir, ownedTarget)}`);
1439
+ continue;
1440
+ }
1441
+ if (layout === "dir" && skill.root && fs7.existsSync(skill.root)) {
1442
+ const targetPath = path7.join(targetDir, skill.id);
1443
+ assertWithinBase2(targetDir, targetPath);
1444
+ if (path7.resolve(targetPath) !== path7.resolve(skill.root)) {
1445
+ if (fs7.existsSync(targetPath) || (() => {
1446
+ try {
1447
+ fs7.lstatSync(targetPath);
1448
+ return true;
1449
+ } catch {
1450
+ return false;
1451
+ }
1452
+ })()) {
1453
+ fs7.rmSync(targetPath, { recursive: true, force: true });
1454
+ }
1455
+ ensureDir(path7.dirname(targetPath));
1456
+ const relativeTarget = path7.relative(path7.dirname(targetPath), path7.resolve(skill.root));
1457
+ try {
1458
+ fs7.symlinkSync(relativeTarget, targetPath, process.platform === "win32" ? "junction" : "dir");
1459
+ } catch {
1460
+ fs7.cpSync(skill.root, targetPath, { recursive: true, dereference: true });
1461
+ }
1462
+ }
1463
+ } else {
1464
+ const filePath = layout === "dir" ? path7.join(targetDir, skill.id, "SKILL.md") : path7.join(targetDir, `${skill.id}.md`);
1465
+ assertWithinBase2(targetDir, filePath);
1466
+ ensureDir(path7.dirname(filePath));
1467
+ fs7.writeFileSync(filePath, skill.content, "utf-8");
1468
+ }
1381
1469
  }
1382
1470
  saveOwnership2(targetDir, { version: 1, paths: Array.from(newPaths) });
1383
1471
  }
@@ -2969,50 +3057,222 @@ function saveManagedIndex(index, filePath = getManagedIndexPath()) {
2969
3057
  // src/core/skills.ts
2970
3058
  import fs13 from "node:fs";
2971
3059
  import path19 from "node:path";
3060
+ import { z as z15 } from "zod";
3061
+ var skillStateSchema = z15.record(z15.string(), z15.object({ source: z15.string(), skillName: z15.string().optional(), pinned: z15.boolean().default(false), previousPath: z15.string().optional() }));
3062
+ function readSkillState() {
3063
+ const statePath = getSkillStatePath();
3064
+ if (!fs13.existsSync(statePath))
3065
+ return {};
3066
+ try {
3067
+ return skillStateSchema.parse(JSON.parse(fs13.readFileSync(statePath, "utf8")));
3068
+ } catch {
3069
+ return {};
3070
+ }
3071
+ }
3072
+ function writeSkillState(state) {
3073
+ ensureDir(path19.dirname(getSkillStatePath()));
3074
+ fs13.writeFileSync(getSkillStatePath(), JSON.stringify(state, null, 2) + `
3075
+ `, "utf8");
3076
+ }
3077
+ function validateSkillPackage(root) {
3078
+ const skillPath = path19.join(root, "SKILL.md");
3079
+ if (!fs13.existsSync(skillPath))
3080
+ throw new Error(`Agent Skill is missing SKILL.md: ${root}`);
3081
+ const content = fs13.readFileSync(skillPath, "utf8");
3082
+ const match = content.match(/^---\s*[\s\S]*?^name:\s*([^\r\n]+)[\s\S]*?^description:\s*([^\r\n]+)[\s\S]*?^---/m);
3083
+ if (!match || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(match[1].trim()) || !match[2].trim()) {
3084
+ throw new Error(`Invalid Agent Skill frontmatter in ${skillPath}; name and description are required`);
3085
+ }
3086
+ }
3087
+ function installedSkills() {
3088
+ const root = getSkillPackagesRoot();
3089
+ if (!fs13.existsSync(root))
3090
+ return [];
3091
+ return fs13.readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
3092
+ if (!entry.isDirectory())
3093
+ return [];
3094
+ const packageRoot = path19.join(root, entry.name);
3095
+ const skillFile = path19.join(packageRoot, "SKILL.md");
3096
+ if (!fs13.existsSync(skillFile))
3097
+ return [];
3098
+ return [{ id: entry.name, content: fs13.readFileSync(skillFile, "utf8"), root: packageRoot, source: "installed", readOnly: true }];
3099
+ });
3100
+ }
2972
3101
  function listSkills() {
2973
3102
  const skillsDir = getSkillsDir();
2974
- if (!fs13.existsSync(skillsDir)) {
2975
- return [];
2976
- }
2977
- const files = fs13.readdirSync(skillsDir);
2978
3103
  const skills = [];
3104
+ const files = fs13.existsSync(skillsDir) ? fs13.readdirSync(skillsDir, { withFileTypes: true }) : [];
3105
+ const authoredDirectories = new Set(files.filter((file) => file.isDirectory()).map((file) => file.name));
2979
3106
  for (const file of files) {
2980
- if (file.endsWith(".md")) {
2981
- const id = path19.basename(file, ".md");
3107
+ const id = file.isDirectory() ? file.name : file.name.endsWith(".md") ? path19.basename(file.name, ".md") : null;
3108
+ if (id && (file.isDirectory() || !authoredDirectories.has(id))) {
2982
3109
  try {
2983
- const content = fs13.readFileSync(path19.join(skillsDir, file), "utf-8");
2984
- skills.push({ id, content });
3110
+ const root = file.isDirectory() ? path19.join(skillsDir, id) : skillsDir;
3111
+ const filePath = file.isDirectory() ? path19.join(root, "SKILL.md") : path19.join(skillsDir, file.name);
3112
+ if (!fs13.existsSync(filePath))
3113
+ continue;
3114
+ const content = fs13.readFileSync(filePath, "utf-8");
3115
+ skills.push({ id, content, root: file.isDirectory() ? root : undefined, source: "authored" });
2985
3116
  } catch {
2986
- console.error(`[mcpx] Warning: could not read skill ${file}`);
3117
+ console.error(`[mcpx] Warning: could not read skill ${file.name}`);
2987
3118
  }
2988
3119
  }
2989
3120
  }
2990
- return skills;
3121
+ const authoredIds = new Set(skills.map((skill) => skill.id));
3122
+ return [...skills, ...installedSkills().filter((skill) => !authoredIds.has(skill.id))];
2991
3123
  }
2992
3124
  function getSkill(id) {
2993
3125
  validateSkillId(id);
2994
3126
  const skillsDir = getSkillsDir();
2995
- const filePath = path19.join(skillsDir, `${id}.md`);
3127
+ const directory = path19.join(skillsDir, id);
3128
+ const packagePath = path19.join(directory, "SKILL.md");
3129
+ const legacyPath = path19.join(skillsDir, `${id}.md`);
3130
+ const filePath = fs13.existsSync(packagePath) ? packagePath : legacyPath;
2996
3131
  if (!fs13.existsSync(filePath)) {
2997
- return null;
3132
+ const installedPath = path19.join(getSkillPackagesRoot(), id, "SKILL.md");
3133
+ if (!fs13.existsSync(installedPath))
3134
+ return null;
3135
+ const installedRoot = path19.dirname(installedPath);
3136
+ return { id, content: fs13.readFileSync(installedPath, "utf8"), root: installedRoot, source: "installed", readOnly: true };
2998
3137
  }
2999
3138
  const content = fs13.readFileSync(filePath, "utf-8");
3000
- return { id, content };
3139
+ return { id, content, root: fs13.existsSync(packagePath) ? directory : undefined, source: "authored" };
3001
3140
  }
3002
3141
  function saveSkill(id, content) {
3003
3142
  validateSkillId(id);
3004
3143
  const skillsDir = getSkillsDir();
3005
3144
  ensureDir(skillsDir);
3006
- const filePath = path19.join(skillsDir, `${id}.md`);
3145
+ const directory = path19.join(skillsDir, id);
3146
+ const filePath = path19.join(directory, "SKILL.md");
3147
+ ensureDir(directory);
3007
3148
  fs13.writeFileSync(filePath, content, "utf-8");
3149
+ const legacyPath = path19.join(skillsDir, `${id}.md`);
3150
+ if (fs13.existsSync(legacyPath)) {
3151
+ if (fs13.readFileSync(filePath, "utf-8") === fs13.readFileSync(legacyPath, "utf-8"))
3152
+ fs13.unlinkSync(legacyPath);
3153
+ else
3154
+ console.error(`[mcpx] Warning: preserving conflicting legacy skill file: ${legacyPath}`);
3155
+ }
3008
3156
  }
3009
3157
  function deleteSkill(id) {
3010
3158
  validateSkillId(id);
3011
3159
  const skillsDir = getSkillsDir();
3012
- const filePath = path19.join(skillsDir, `${id}.md`);
3013
- if (fs13.existsSync(filePath)) {
3014
- fs13.unlinkSync(filePath);
3015
- }
3160
+ const directory = path19.join(skillsDir, id);
3161
+ const filePath = path19.join(directory, "SKILL.md");
3162
+ const legacyPath = path19.join(skillsDir, `${id}.md`);
3163
+ const hadAuthored = fs13.existsSync(directory) || fs13.existsSync(legacyPath);
3164
+ if (fs13.existsSync(directory))
3165
+ fs13.rmSync(directory, { recursive: true, force: true });
3166
+ if (fs13.existsSync(legacyPath))
3167
+ fs13.unlinkSync(legacyPath);
3168
+ if (!hadAuthored) {
3169
+ const installedPath = path19.join(getSkillPackagesRoot(), id);
3170
+ if (fs13.existsSync(installedPath))
3171
+ fs13.rmSync(installedPath, { recursive: true, force: true });
3172
+ const state = readSkillState();
3173
+ delete state[id];
3174
+ writeSkillState(state);
3175
+ }
3176
+ }
3177
+ function customizeSkill(id, targetId = id) {
3178
+ validateSkillId(id);
3179
+ validateSkillId(targetId);
3180
+ const existing = getSkill(id);
3181
+ if (!existing)
3182
+ throw new Error(`Skill "${id}" not found`);
3183
+ saveSkill(targetId, existing.content);
3184
+ return getSkill(targetId);
3185
+ }
3186
+ async function installSkillFromSource(source, skillName) {
3187
+ const parsed = parseSource(source);
3188
+ const cache = new PluginCache;
3189
+ const cached = await cache.fetch(parsed, skillName ?? "skill");
3190
+ const candidates = [];
3191
+ const declaredName = (root) => {
3192
+ try {
3193
+ const content = fs13.readFileSync(path19.join(root, "SKILL.md"), "utf8");
3194
+ const frontmatter = content.match(/^---\s*[\s\S]*?^name:\s*([^\r\n]+)[\s\S]*?^---/m)?.[1]?.trim();
3195
+ return frontmatter && /^[a-z0-9][a-z0-9-]*$/.test(frontmatter) ? frontmatter : undefined;
3196
+ } catch {
3197
+ return;
3198
+ }
3199
+ };
3200
+ const addCandidate = (root, id = path19.basename(root)) => {
3201
+ if (fs13.existsSync(path19.join(root, "SKILL.md"))) {
3202
+ validateSkillPackage(root);
3203
+ candidates.push({ id: declaredName(root) ?? id, root });
3204
+ }
3205
+ };
3206
+ addCandidate(cached.root);
3207
+ const skillsRoot = path19.join(cached.root, "skills");
3208
+ if (fs13.existsSync(skillsRoot)) {
3209
+ for (const entry of fs13.readdirSync(skillsRoot, { withFileTypes: true })) {
3210
+ if (entry.isDirectory())
3211
+ addCandidate(path19.join(skillsRoot, entry.name), entry.name);
3212
+ }
3213
+ }
3214
+ if (candidates.length === 0)
3215
+ throw new Error(`No Agent Skill package found in ${source}`);
3216
+ const selected = skillName ? candidates.find((candidate) => candidate.id === skillName) : candidates[0];
3217
+ if (!selected)
3218
+ throw new Error(`Skill "${skillName}" was not found in ${source}`);
3219
+ validateSkillId(selected.id);
3220
+ const destination = path19.join(getSkillPackagesRoot(), selected.id);
3221
+ ensureDir(path19.dirname(destination));
3222
+ const state = readSkillState();
3223
+ const stateSource = parsed.type === "local" ? path19.resolve(source) : source;
3224
+ if (fs13.existsSync(destination)) {
3225
+ if (state[selected.id]?.pinned)
3226
+ throw new Error(`Skill "${selected.id}" is pinned; unpin it before updating`);
3227
+ const previousPath = `${destination}.previous`;
3228
+ if (fs13.existsSync(previousPath))
3229
+ fs13.rmSync(previousPath, { recursive: true, force: true });
3230
+ fs13.renameSync(destination, previousPath);
3231
+ state[selected.id] = { ...state[selected.id], source: stateSource, skillName: selected.id, pinned: false, previousPath };
3232
+ } else {
3233
+ state[selected.id] = { source: stateSource, skillName: selected.id, pinned: false };
3234
+ }
3235
+ fs13.cpSync(selected.root, destination, { recursive: true, dereference: true });
3236
+ writeSkillState(state);
3237
+ return { id: selected.id, content: fs13.readFileSync(path19.join(destination, "SKILL.md"), "utf8"), root: destination, source: "installed", readOnly: true };
3238
+ }
3239
+ function pinSkill(id) {
3240
+ const state = readSkillState();
3241
+ if (!state[id])
3242
+ throw new Error(`Skill "${id}" is not installed`);
3243
+ state[id].pinned = true;
3244
+ writeSkillState(state);
3245
+ }
3246
+ function unpinSkill(id) {
3247
+ const state = readSkillState();
3248
+ if (!state[id])
3249
+ throw new Error(`Skill "${id}" is not installed`);
3250
+ state[id].pinned = false;
3251
+ writeSkillState(state);
3252
+ }
3253
+ async function updateSkill(id) {
3254
+ const state = readSkillState();
3255
+ const entry = state[id];
3256
+ if (!entry)
3257
+ throw new Error(`Skill "${id}" is not installed`);
3258
+ if (entry.pinned)
3259
+ throw new Error(`Skill "${id}" is pinned; unpin it before updating`);
3260
+ return installSkillFromSource(entry.source, entry.skillName ?? id);
3261
+ }
3262
+ function rollbackSkill(id) {
3263
+ const state = readSkillState();
3264
+ const entry = state[id];
3265
+ if (!entry?.previousPath || !fs13.existsSync(entry.previousPath))
3266
+ throw new Error(`Skill "${id}" has no previous revision`);
3267
+ const destination = path19.join(getSkillPackagesRoot(), id);
3268
+ const failedPath = `${destination}.failed`;
3269
+ if (fs13.existsSync(failedPath))
3270
+ fs13.rmSync(failedPath, { recursive: true, force: true });
3271
+ fs13.renameSync(destination, failedPath);
3272
+ fs13.renameSync(entry.previousPath, destination);
3273
+ entry.previousPath = failedPath;
3274
+ writeSkillState(state);
3275
+ return { id, content: fs13.readFileSync(path19.join(destination, "SKILL.md"), "utf8"), root: destination, source: "installed", readOnly: true };
3016
3276
  }
3017
3277
 
3018
3278
  // src/core/managed-index-lock.ts
@@ -3444,6 +3704,8 @@ class PluginManager {
3444
3704
  const current = loadConfig(this.configPath).plugins?.[pluginId];
3445
3705
  if (!current)
3446
3706
  throw new Error(`Plugin ${pluginId} not found`);
3707
+ if (current.pinned)
3708
+ throw new Error(`Plugin ${pluginId} is pinned; unpin it before updating`);
3447
3709
  const info = await this.inspectSource(current.source);
3448
3710
  return this.applyPluginUpdate(pluginId, info, undefined, true);
3449
3711
  }
@@ -3466,6 +3728,7 @@ class PluginManager {
3466
3728
  for (const oldName of current.serverNames) {
3467
3729
  delete config.servers[oldName];
3468
3730
  }
3731
+ current.previous = { version: current.version, resolvedSha: current.resolvedSha, root: current.root };
3469
3732
  current.version = info.manifest?.version || current.version;
3470
3733
  current.resolvedSha = info.sha;
3471
3734
  current.root = info.root;
@@ -3614,12 +3877,44 @@ async function updatePlugin(nameOrId) {
3614
3877
  const manager = new PluginManager;
3615
3878
  const config = loadConfig(manager["configPath"]);
3616
3879
  const id = resolvePluginId(config, nameOrId);
3880
+ if (config.plugins?.[id]?.pinned)
3881
+ throw new Error(`Plugin ${nameOrId} is pinned; unpin it before updating`);
3617
3882
  if (config.plugins?.[id]?.marketplace) {
3618
- const { updateMarketplaceInstalledPlugin } = await import("./chunk-02r7k4ax.js");
3883
+ const { updateMarketplaceInstalledPlugin } = await import("./chunk-d2v9rxy0.js");
3619
3884
  return updateMarketplaceInstalledPlugin(id);
3620
3885
  }
3621
3886
  return manager.updatePlugin(id);
3622
3887
  }
3888
+ async function pinPlugin(nameOrId) {
3889
+ await mutateConfig((config) => {
3890
+ const id = resolvePluginId(config, nameOrId);
3891
+ const plugin = config.plugins?.[id];
3892
+ if (!plugin)
3893
+ throw new Error(`Plugin ${nameOrId} not found`);
3894
+ plugin.pinned = true;
3895
+ });
3896
+ }
3897
+ async function unpinPlugin(nameOrId) {
3898
+ await mutateConfig((config) => {
3899
+ const id = resolvePluginId(config, nameOrId);
3900
+ const plugin = config.plugins?.[id];
3901
+ if (!plugin)
3902
+ throw new Error(`Plugin ${nameOrId} not found`);
3903
+ plugin.pinned = false;
3904
+ });
3905
+ }
3906
+ async function rollbackPlugin(nameOrId) {
3907
+ const manager = new PluginManager;
3908
+ const config = loadConfig(manager["configPath"]);
3909
+ const id = resolvePluginId(config, nameOrId);
3910
+ const current = config.plugins?.[id];
3911
+ if (!current?.previous)
3912
+ throw new Error(`Plugin ${nameOrId} has no previous revision`);
3913
+ const source = parseSource(current.source);
3914
+ source.resolvedSha = current.previous.resolvedSha;
3915
+ const info = await manager.inspectResolvedSource(source, current.source);
3916
+ return manager["applyPluginUpdate"](id, info, undefined, true);
3917
+ }
3623
3918
  async function uninstallPlugin(nameOrId, keepData) {
3624
3919
  const manager = new PluginManager;
3625
3920
  const config = loadConfig(manager["configPath"]);
@@ -3755,6 +4050,6 @@ async function preparePlugin(name) {
3755
4050
  await manager.preparePlugin(name);
3756
4051
  }
3757
4052
 
3758
- export { mutateConfig, mutateProjectConfig, mutateActiveConfig, listSkills, getSkill, saveSkill, deleteSkill, prunePluginProjections, pruneAllPluginProjections, syncPluginsToClient, APP_VERSION, getAdapters, loadManagedIndex, getGatewayUrl, getGatewayInternalUrl, syncAllClients, persistSyncState, PluginCache, readManifest, discoverComponents, hasManifest, resolvePluginVars, PluginDataManager, PluginLifecycle, parseSource, PluginManager, inspectPlugin, installPlugin, updatePlugin, uninstallPlugin, enablePlugin, disablePlugin, setPluginProjectOverride, resetPluginProjectOverride, approvePluginComponent, getPluginStatus, listPlugins, resolvePluginId, pluginConfigSet, pluginSync, preparePlugin };
4053
+ export { mutateConfig, mutateProjectConfig, mutateActiveConfig, validateSkillId, parseSource, PluginCache, validateSkillPackage, listSkills, getSkill, saveSkill, deleteSkill, customizeSkill, installSkillFromSource, pinSkill, unpinSkill, updateSkill, rollbackSkill, prunePluginProjections, pruneAllPluginProjections, syncPluginsToClient, APP_VERSION, getAdapters, loadManagedIndex, getGatewayUrl, getGatewayInternalUrl, syncAllClients, persistSyncState, readManifest, discoverComponents, hasManifest, resolvePluginVars, PluginDataManager, PluginLifecycle, PluginManager, inspectPlugin, installPlugin, updatePlugin, pinPlugin, unpinPlugin, rollbackPlugin, uninstallPlugin, enablePlugin, disablePlugin, setPluginProjectOverride, resetPluginProjectOverride, approvePluginComponent, getPluginStatus, listPlugins, resolvePluginId, pluginConfigSet, pluginSync, preparePlugin };
3759
4054
 
3760
- //# debugId=3872A3A17F73C93764756E2164756E21
4055
+ //# debugId=250A96A4D45E61A664756E2164756E21
@@ -8,8 +8,8 @@ import {
8
8
  resolveActiveConfig,
9
9
  saveConfig,
10
10
  saveProjectConfig
11
- } from "./chunk-wq4zd3t7.js";
12
- import"./chunk-cqenqnxk.js";
11
+ } from "./chunk-01s7rhx3.js";
12
+ import"./chunk-4y0t0tfj.js";
13
13
  import"./chunk-bjw71y54.js";
14
14
  import"./chunk-wj4tjacg.js";
15
15
  export {
@@ -7,18 +7,21 @@ import {
7
7
  inspectPlugin,
8
8
  installPlugin,
9
9
  listPlugins,
10
+ pinPlugin,
10
11
  pluginConfigSet,
11
12
  pluginSync,
12
13
  preparePlugin,
13
14
  resetPluginProjectOverride,
14
15
  resolvePluginId,
16
+ rollbackPlugin,
15
17
  setPluginProjectOverride,
16
18
  uninstallPlugin,
19
+ unpinPlugin,
17
20
  updatePlugin
18
- } from "./chunk-z1k63fa3.js";
19
- import"./chunk-wq4zd3t7.js";
20
- import"./chunk-cz8cdmw1.js";
21
- import"./chunk-cqenqnxk.js";
21
+ } from "./chunk-56whm3hm.js";
22
+ import"./chunk-01s7rhx3.js";
23
+ import"./chunk-fw8dyt89.js";
24
+ import"./chunk-4y0t0tfj.js";
22
25
  import"./chunk-bjw71y54.js";
23
26
  import"./chunk-wj4tjacg.js";
24
27
  export {
@@ -30,14 +33,17 @@ export {
30
33
  inspectPlugin,
31
34
  installPlugin,
32
35
  listPlugins,
36
+ pinPlugin,
33
37
  pluginConfigSet,
34
38
  pluginSync,
35
39
  preparePlugin,
36
40
  resetPluginProjectOverride,
37
41
  resolvePluginId,
42
+ rollbackPlugin,
38
43
  setPluginProjectOverride,
39
44
  uninstallPlugin,
45
+ unpinPlugin,
40
46
  updatePlugin
41
47
  };
42
48
 
43
- //# debugId=11040DF9544111AB64756E2164756E21
49
+ //# debugId=F3AE0823719415BE64756E2164756E21
@@ -6,14 +6,14 @@ import {
6
6
  mutateConfig,
7
7
  readManifest,
8
8
  uninstallPlugin
9
- } from "./chunk-z1k63fa3.js";
9
+ } from "./chunk-56whm3hm.js";
10
10
  import {
11
11
  loadConfig
12
- } from "./chunk-wq4zd3t7.js";
12
+ } from "./chunk-01s7rhx3.js";
13
13
  import {
14
14
  ensureDir,
15
15
  getMarketplaceCacheRoot
16
- } from "./chunk-cqenqnxk.js";
16
+ } from "./chunk-4y0t0tfj.js";
17
17
  import {
18
18
  __require
19
19
  } from "./chunk-wj4tjacg.js";
@@ -537,6 +537,8 @@ async function updateMarketplaceInstalledPlugin(pluginId, sync = true) {
537
537
  const plugin = loadConfig().plugins?.[pluginId];
538
538
  if (!plugin?.marketplace)
539
539
  throw new Error(`Plugin ${pluginId} is not marketplace-managed`);
540
+ if (plugin.pinned)
541
+ throw new Error(`Plugin ${pluginId} is pinned; unpin it before updating`);
540
542
  const qualified = `${plugin.marketplace.pluginName}@${plugin.marketplace.name}`;
541
543
  try {
542
544
  const detail = await inspectMarketplacePlugin(qualified);
@@ -578,7 +580,7 @@ async function refreshMarketplaceWithPlugins(name) {
578
580
  }
579
581
  }
580
582
  if (updated.length > 0) {
581
- const { pluginSync } = await import("./chunk-b58zsdp4.js");
583
+ const { pluginSync } = await import("./chunk-am903qjk.js");
582
584
  await pluginSync();
583
585
  }
584
586
  return { marketplace, updated, errors };
@@ -600,4 +602,4 @@ async function refreshAutoUpdateMarketplaces() {
600
602
 
601
603
  export { resolveMarketplacePluginSource, listMarketplaces, addMarketplace, refreshMarketplace, setMarketplaceAutoUpdate, removeMarketplace, listMarketplacePlugins, inspectMarketplacePlugin, installMarketplacePlugin, updateMarketplaceInstalledPlugin, refreshMarketplaceWithPlugins, refreshAutoUpdateMarketplaces };
602
604
 
603
- //# debugId=95199795C0DBE5F464756E2164756E21
605
+ //# debugId=347C06BDB5A3194064756E2164756E21
@@ -11,11 +11,11 @@ import {
11
11
  resolveMarketplacePluginSource,
12
12
  setMarketplaceAutoUpdate,
13
13
  updateMarketplaceInstalledPlugin
14
- } from "./chunk-s9v1epnk.js";
15
- import"./chunk-z1k63fa3.js";
16
- import"./chunk-wq4zd3t7.js";
17
- import"./chunk-cz8cdmw1.js";
18
- import"./chunk-cqenqnxk.js";
14
+ } from "./chunk-btbww96n.js";
15
+ import"./chunk-56whm3hm.js";
16
+ import"./chunk-01s7rhx3.js";
17
+ import"./chunk-fw8dyt89.js";
18
+ import"./chunk-4y0t0tfj.js";
19
19
  import"./chunk-bjw71y54.js";
20
20
  import"./chunk-wj4tjacg.js";
21
21
  export {
@@ -5,7 +5,7 @@ import {
5
5
  getSecretsStorePath,
6
6
  readJsonFile,
7
7
  writeJsonAtomic
8
- } from "./chunk-cqenqnxk.js";
8
+ } from "./chunk-4y0t0tfj.js";
9
9
  import {
10
10
  isServerEnabled,
11
11
  normalizeServerSpecEnabled