opencode-gitlab-dap 1.16.2 → 1.16.4

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/index.cjs CHANGED
@@ -4017,139 +4017,70 @@ async function listSnippetFiles(instanceUrl, token, projectId, snippetId) {
4017
4017
  }
4018
4018
 
4019
4019
  // src/tools/skill-tools.ts
4020
+ var import_fs3 = require("fs");
4021
+ var import_path3 = require("path");
4022
+
4023
+ // src/tools/skill-helpers.ts
4020
4024
  var import_child_process = require("child_process");
4021
4025
  var import_fs2 = require("fs");
4022
4026
  var import_path2 = require("path");
4023
4027
  var import_os2 = require("os");
4024
4028
  var import_zlib = require("zlib");
4025
- var z6 = import_plugin6.tool.schema;
4026
4029
  var PREFIX2 = "agents";
4027
4030
  var SKILLS_PREFIX = `${PREFIX2}/skills`;
4028
4031
  var DRAFTS_PREFIX = `${PREFIX2}/skills-drafts`;
4029
- var SKILLS_INDEX = `${SKILLS_PREFIX}/_registry`;
4030
- var DRAFTS_INDEX = `${DRAFTS_PREFIX}/_registry`;
4031
- var PROJECT_ID_DESC2 = "Project path from git remote";
4032
- function resolveScope2(args) {
4033
- if (args.scope === "groups" && args.group_id) {
4034
- return { scope: "groups", id: args.group_id };
4035
- }
4036
- return { scope: "projects", id: args.project_id };
4037
- }
4038
- function validateProjectId2(projectId) {
4039
- if (!projectId.includes("/")) {
4040
- return `Invalid project_id "${projectId}". Must be the full project path containing at least one slash (e.g., "my-group/my-project"), not just the project name.`;
4041
- }
4042
- return null;
4043
- }
4044
- function parseIndex(content) {
4045
- const entries = [];
4046
- const blocks = content.split(/^## /m).filter(Boolean);
4047
- for (const block of blocks) {
4048
- const lines = block.trim().split("\n");
4049
- const name = lines[0].trim();
4050
- if (!name) continue;
4051
- const rest = lines.slice(1).join("\n").trim();
4052
- const descLines = [];
4053
- let source;
4054
- let snippetId;
4055
- for (const line of rest.split("\n")) {
4056
- if (line.startsWith("Source:")) {
4057
- source = line.slice(7).trim();
4058
- } else if (line.startsWith("Snippet:")) {
4059
- snippetId = parseInt(line.slice(8).trim(), 10) || void 0;
4060
- } else if (line.trim()) {
4061
- descLines.push(line);
4062
- }
4063
- }
4064
- entries.push({ name, description: descLines.join("\n"), source, snippetId, draft: false });
4065
- }
4066
- return entries;
4067
- }
4068
- function formatIndex(entries) {
4069
- return entries.map((e) => {
4070
- let block = `## ${e.name}
4071
- ${e.description}`;
4072
- if (e.source) block += `
4073
- Source: ${e.source}`;
4074
- if (e.snippetId) block += `
4075
- Snippet: ${e.snippetId}`;
4076
- return block;
4077
- }).join("\n\n");
4078
- }
4079
- async function readIndex(instanceUrl, token, scope, id, indexSlug) {
4080
- try {
4081
- const page = await getWikiPage(instanceUrl, token, scope, id, indexSlug);
4082
- return parseIndex(page.content);
4083
- } catch {
4084
- return [];
4085
- }
4086
- }
4087
- async function writeIndex(instanceUrl, token, scope, id, indexSlug, entries) {
4088
- const content = formatIndex(entries) || "# Skills Index";
4089
- try {
4090
- await updateWikiPage(instanceUrl, token, scope, id, indexSlug, content);
4091
- } catch (updateErr) {
4092
- if (!updateErr.message?.includes("not found") && !updateErr.message?.includes("404"))
4093
- throw updateErr;
4094
- await createWikiPage(instanceUrl, token, scope, id, indexSlug, content);
4032
+ var EMPTY_FILE_SENTINEL = "__EMPTY_FILE__";
4033
+ function parseFrontmatter(content) {
4034
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
4035
+ if (!match) return { meta: {}, body: content };
4036
+ const meta = {};
4037
+ for (const line of match[1].split("\n")) {
4038
+ const [key, ...rest] = line.split(":");
4039
+ const val = rest.join(":").trim();
4040
+ if (!key || !val) continue;
4041
+ const k = key.trim();
4042
+ if (k === "name") meta.name = val;
4043
+ else if (k === "description") meta.description = val;
4044
+ else if (k === "source") meta.source = val;
4045
+ else if (k === "snippet") meta.snippetId = parseInt(val, 10) || void 0;
4095
4046
  }
4047
+ return { meta, body: match[2] };
4096
4048
  }
4097
- async function upsertIndexEntry(instanceUrl, token, scope, id, indexSlug, entry) {
4098
- const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
4099
- const idx = entries.findIndex((e) => e.name === entry.name);
4100
- if (idx >= 0) {
4101
- entries[idx] = entry;
4102
- } else {
4103
- entries.push(entry);
4104
- }
4105
- await writeIndex(instanceUrl, token, scope, id, indexSlug, entries);
4049
+ function formatFrontmatter(meta, body) {
4050
+ const lines = ["---", `name: ${meta.name}`, `description: ${meta.description}`];
4051
+ if (meta.source) lines.push(`source: ${meta.source}`);
4052
+ if (meta.snippetId) lines.push(`snippet: ${meta.snippetId}`);
4053
+ lines.push("---", "");
4054
+ return lines.join("\n") + body;
4106
4055
  }
4107
- async function removeIndexEntry(instanceUrl, token, scope, id, indexSlug, name) {
4108
- const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
4109
- const filtered = entries.filter((e) => e.name !== name);
4110
- if (filtered.length !== entries.length) {
4111
- await writeIndex(instanceUrl, token, scope, id, indexSlug, filtered);
4112
- }
4056
+ function extractSkillNameFromSlug(slug, prefix) {
4057
+ if (!slug.startsWith(prefix + "/") || !slug.endsWith("/SKILL")) return null;
4058
+ const middle = slug.slice(prefix.length + 1, -"/SKILL".length);
4059
+ return middle && !middle.includes("/") ? middle : null;
4113
4060
  }
4114
- async function upsertPage(instanceUrl, token, scope, id, slug, content) {
4115
- try {
4116
- await updateWikiPage(instanceUrl, token, scope, id, slug, content);
4117
- } catch (updateErr) {
4118
- if (!updateErr.message?.includes("not found") && !updateErr.message?.includes("404"))
4119
- throw updateErr;
4120
- await createWikiPage(instanceUrl, token, scope, id, slug, content);
4121
- }
4061
+ function isMarkdownFile(path) {
4062
+ return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
4122
4063
  }
4123
- var EMPTY_FILE_SENTINEL = "__EMPTY_FILE__";
4124
4064
  function packFiles(files) {
4125
4065
  const manifest = files.map((f) => ({
4126
4066
  path: f.path,
4127
4067
  content: f.content.trim() ? f.content : EMPTY_FILE_SENTINEL
4128
4068
  }));
4129
- const json = JSON.stringify(manifest);
4130
- return (0, import_zlib.gzipSync)(Buffer.from(json, "utf-8")).toString("base64");
4069
+ return (0, import_zlib.gzipSync)(Buffer.from(JSON.stringify(manifest), "utf-8")).toString("base64");
4131
4070
  }
4132
4071
  function unpackFiles(packed) {
4133
4072
  const json = (0, import_zlib.gunzipSync)(Buffer.from(packed, "base64")).toString("utf-8");
4134
- const manifest = JSON.parse(json);
4135
- return manifest.map((f) => ({
4073
+ return JSON.parse(json).map((f) => ({
4136
4074
  path: f.path,
4137
4075
  content: f.content === EMPTY_FILE_SENTINEL ? "" : f.content
4138
4076
  }));
4139
4077
  }
4140
- function isMarkdownFile(path) {
4141
- return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
4142
- }
4143
- function isAgentsGitignored(dir) {
4078
+ function ensureGitignore(dir) {
4144
4079
  try {
4145
4080
  (0, import_child_process.execSync)("git check-ignore -q .agents", { cwd: dir, stdio: "pipe" });
4146
- return true;
4081
+ return;
4147
4082
  } catch {
4148
- return false;
4149
4083
  }
4150
- }
4151
- function ensureGitignore(dir) {
4152
- if (isAgentsGitignored(dir)) return;
4153
4084
  const gitignorePath = (0, import_path2.join)(dir, ".gitignore");
4154
4085
  if ((0, import_fs2.existsSync)(gitignorePath)) {
4155
4086
  const content = (0, import_fs2.readFileSync)(gitignorePath, "utf-8");
@@ -4202,10 +4133,9 @@ function downloadSkillFromSkillsSh(identifier) {
4202
4133
  if (dirs.length === 0) return null;
4203
4134
  const skillName = dirs[0];
4204
4135
  const skillDir = (0, import_path2.join)(agentsDir, skillName);
4205
- const skillMd = (0, import_path2.join)(skillDir, "SKILL.md");
4206
4136
  let mainContent;
4207
4137
  try {
4208
- mainContent = (0, import_fs2.readFileSync)(skillMd, "utf-8");
4138
+ mainContent = (0, import_fs2.readFileSync)((0, import_path2.join)(skillDir, "SKILL.md"), "utf-8");
4209
4139
  } catch {
4210
4140
  return null;
4211
4141
  }
@@ -4214,8 +4144,7 @@ function downloadSkillFromSkillsSh(identifier) {
4214
4144
  if (descMatch) {
4215
4145
  description = descMatch[1].trim();
4216
4146
  } else {
4217
- const firstParagraph = mainContent.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim();
4218
- description = firstParagraph.slice(0, 200);
4147
+ description = mainContent.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim().slice(0, 200);
4219
4148
  }
4220
4149
  const files = [];
4221
4150
  const walkStack = [{ dir: skillDir, prefix: "" }];
@@ -4244,6 +4173,50 @@ function downloadSkillFromSkillsSh(identifier) {
4244
4173
  }
4245
4174
  }
4246
4175
  }
4176
+
4177
+ // src/tools/skill-tools.ts
4178
+ var z6 = import_plugin6.tool.schema;
4179
+ var PROJECT_ID_DESC2 = "Project path from git remote";
4180
+ function resolveScope2(args) {
4181
+ if (args.scope === "groups" && args.group_id) {
4182
+ return { scope: "groups", id: args.group_id };
4183
+ }
4184
+ return { scope: "projects", id: args.project_id };
4185
+ }
4186
+ function validateProjectId2(projectId) {
4187
+ if (!projectId.includes("/")) {
4188
+ return `Invalid project_id "${projectId}". Must be the full project path containing at least one slash.`;
4189
+ }
4190
+ return null;
4191
+ }
4192
+ async function upsertPage(instanceUrl, token, scope, id, slug, content) {
4193
+ try {
4194
+ await updateWikiPage(instanceUrl, token, scope, id, slug, content);
4195
+ } catch (err) {
4196
+ if (err.message?.includes("not found") || err.message?.includes("404")) {
4197
+ await createWikiPage(instanceUrl, token, scope, id, slug, content);
4198
+ return;
4199
+ }
4200
+ throw err;
4201
+ }
4202
+ }
4203
+ async function listSkills(instanceUrl, token, scope, id, prefix) {
4204
+ const pages = await listWikiPages(instanceUrl, token, scope, id, true);
4205
+ const skills = [];
4206
+ for (const page of pages) {
4207
+ const name = extractSkillNameFromSlug(page.slug, prefix);
4208
+ if (!name || !page.content) continue;
4209
+ const { meta } = parseFrontmatter(page.content);
4210
+ skills.push({
4211
+ name: meta.name ?? name,
4212
+ description: meta.description ?? "",
4213
+ source: meta.source,
4214
+ snippetId: meta.snippetId,
4215
+ draft: prefix === DRAFTS_PREFIX
4216
+ });
4217
+ }
4218
+ return skills;
4219
+ }
4247
4220
  function makeSkillTools(ctx) {
4248
4221
  function authAndValidate(projectId) {
4249
4222
  const auth = ctx.ensureAuth();
@@ -4254,7 +4227,7 @@ function makeSkillTools(ctx) {
4254
4227
  }
4255
4228
  return {
4256
4229
  gitlab_skill_list: (0, import_plugin6.tool)({
4257
- description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).",
4230
+ description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks.",
4258
4231
  args: {
4259
4232
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4260
4233
  include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
@@ -4265,12 +4238,16 @@ function makeSkillTools(ctx) {
4265
4238
  const auth = authAndValidate(args.project_id);
4266
4239
  const { scope, id } = resolveScope2(args);
4267
4240
  try {
4268
- const published = await readIndex(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX);
4241
+ const published = await listSkills(
4242
+ auth.instanceUrl,
4243
+ auth.token,
4244
+ scope,
4245
+ id,
4246
+ SKILLS_PREFIX
4247
+ );
4269
4248
  let drafts = [];
4270
4249
  if (args.include_drafts) {
4271
- drafts = (await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX)).map(
4272
- (e) => ({ ...e, draft: true })
4273
- );
4250
+ drafts = await listSkills(auth.instanceUrl, auth.token, scope, id, DRAFTS_PREFIX);
4274
4251
  }
4275
4252
  const all = [...published, ...drafts];
4276
4253
  if (all.length === 0) return "No skills found. Use gitlab_skill_save to create one.";
@@ -4281,18 +4258,17 @@ function makeSkillTools(ctx) {
4281
4258
  }
4282
4259
  }),
4283
4260
  gitlab_skill_load: (0, import_plugin6.tool)({
4284
- description: "Load a specific skill by name.\nSkills contain step-by-step instructions for common tasks.\nChecks published skills first, then falls back to draft skills.\nReturns the SKILL content and lists available reference pages.",
4261
+ description: "Load a specific skill by name.\nChecks published skills first, then falls back to draft skills.\nReturns the SKILL content and lists available reference pages.",
4285
4262
  args: {
4286
4263
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4287
- name: z6.string().describe('Skill name (e.g., "incident-retro", "helm-rollback")'),
4264
+ name: z6.string().describe('Skill name (e.g., "incident-retro")'),
4288
4265
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4289
4266
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4290
4267
  },
4291
4268
  execute: async (args) => {
4292
4269
  const auth = authAndValidate(args.project_id);
4293
4270
  const { scope, id } = resolveScope2(args);
4294
- const prefixes = [SKILLS_PREFIX, DRAFTS_PREFIX];
4295
- for (const prefix of prefixes) {
4271
+ for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
4296
4272
  try {
4297
4273
  const page = await getWikiPage(
4298
4274
  auth.instanceUrl,
@@ -4301,36 +4277,28 @@ function makeSkillTools(ctx) {
4301
4277
  id,
4302
4278
  `${prefix}/${args.name}/SKILL`
4303
4279
  );
4280
+ const { meta, body } = parseFrontmatter(page.content);
4281
+ const isDraft = prefix === DRAFTS_PREFIX;
4304
4282
  const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
4305
4283
  const skillPrefix = `${prefix}/${args.name}/`;
4306
- const skillSlug = `${prefix}/${args.name}/SKILL`;
4307
- const refs = pages.filter((p) => p.slug.startsWith(skillPrefix) && p.slug !== skillSlug).map((p) => p.slug.slice(skillPrefix.length));
4308
- const isDraft = prefix === DRAFTS_PREFIX;
4284
+ const refs = pages.filter(
4285
+ (p) => p.slug.startsWith(skillPrefix) && p.slug !== `${prefix}/${args.name}/SKILL`
4286
+ ).map((p) => p.slug.slice(skillPrefix.length));
4309
4287
  let result = isDraft ? `[DRAFT SKILL]
4310
4288
 
4311
- ${page.content}` : page.content;
4289
+ ${body}` : body;
4312
4290
  if (refs.length > 0) {
4313
4291
  result += `
4314
4292
 
4315
4293
  ---
4316
4294
  Available references: ${refs.join(", ")}`;
4317
4295
  }
4318
- const indexSlug = isDraft ? DRAFTS_INDEX : SKILLS_INDEX;
4319
- const indexEntries = await readIndex(
4320
- auth.instanceUrl,
4321
- auth.token,
4322
- scope,
4323
- id,
4324
- indexSlug
4325
- );
4326
- const entry = indexEntries.find((e) => e.name === args.name);
4327
- if (entry?.snippetId) {
4296
+ if (meta.snippetId) {
4328
4297
  result += `
4329
4298
 
4330
- ---
4331
- This skill has executable scripts in snippet #${entry.snippetId}.`;
4299
+ This skill has executable scripts in snippet #${meta.snippetId}.`;
4332
4300
  result += `
4333
- Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skills/${args.name}/ for execution.`;
4301
+ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them locally.`;
4334
4302
  }
4335
4303
  return result;
4336
4304
  } catch {
@@ -4341,13 +4309,13 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4341
4309
  }
4342
4310
  }),
4343
4311
  gitlab_skill_save: (0, import_plugin6.tool)({
4344
- description: "Create or update a skill.\nSkills define step-by-step procedures for common tasks.\nUse draft=true for skills that haven't been proven yet.\nUpdates the skill index with the provided description.",
4312
+ description: "Create or update a skill.\nUse draft=true for skills that haven't been proven yet.",
4345
4313
  args: {
4346
4314
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4347
4315
  name: z6.string().describe('Skill name (e.g., "incident-retro")'),
4348
4316
  content: z6.string().describe("Skill content in markdown"),
4349
- description: z6.string().describe("Short description for the skill index (1-2 sentences)"),
4350
- draft: z6.boolean().optional().describe("Save as draft skill (default: false)"),
4317
+ description: z6.string().describe("Short description (1-2 sentences)"),
4318
+ draft: z6.boolean().optional().describe("Save as draft (default: false)"),
4351
4319
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4352
4320
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4353
4321
  },
@@ -4355,31 +4323,25 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4355
4323
  const auth = authAndValidate(args.project_id);
4356
4324
  const { scope, id } = resolveScope2(args);
4357
4325
  const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4358
- const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4359
4326
  const slug = `${prefix}/${args.name}/SKILL`;
4360
- const label = args.draft ? "draft " : "";
4327
+ const body = formatFrontmatter(
4328
+ { name: args.name, description: args.description, source: "project" },
4329
+ args.content
4330
+ );
4361
4331
  try {
4362
- await upsertPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
4363
- await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, {
4364
- name: args.name,
4365
- description: args.description,
4366
- source: "project",
4367
- draft: !!args.draft
4368
- });
4369
- return `Saved ${label}skill: ${args.name}`;
4332
+ await upsertPage(auth.instanceUrl, auth.token, scope, id, slug, body);
4333
+ return `Saved ${args.draft ? "draft " : ""}skill: ${args.name}`;
4370
4334
  } catch (err) {
4371
4335
  return `Error saving skill: ${err.message}`;
4372
4336
  }
4373
4337
  }
4374
4338
  }),
4375
4339
  gitlab_skill_promote: (0, import_plugin6.tool)({
4376
- description: "Promote a skill.\nDefault (target='published'): moves a draft skill to published within the same scope.\nTarget 'group': copies a published project skill to the group wiki, making it available to all projects in the group.",
4340
+ description: "Promote a skill.\nDefault (target='published'): moves a draft to published.\nTarget 'group': moves a project skill to the group wiki.",
4377
4341
  args: {
4378
4342
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4379
4343
  name: z6.string().describe("Skill name to promote"),
4380
- target: z6.enum(["published", "group"]).optional().describe(
4381
- 'Promotion target: "published" (default, draft\u2192published) or "group" (project\u2192group wiki)'
4382
- ),
4344
+ target: z6.enum(["published", "group"]).optional().describe('"published" (default) or "group"'),
4383
4345
  group_id: z6.string().optional().describe("Group path (required when target is group)"),
4384
4346
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)")
4385
4347
  },
@@ -4387,9 +4349,7 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4387
4349
  const auth = authAndValidate(args.project_id);
4388
4350
  const promotionTarget = args.target ?? "published";
4389
4351
  if (promotionTarget === "group") {
4390
- if (!args.group_id) {
4391
- return 'Error: group_id is required when target is "group".';
4392
- }
4352
+ if (!args.group_id) return 'Error: group_id is required when target is "group".';
4393
4353
  const projectScope = resolveScope2(args);
4394
4354
  try {
4395
4355
  const pages = await listWikiPages(
@@ -4400,11 +4360,9 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4400
4360
  true
4401
4361
  );
4402
4362
  const skillPrefix = `${SKILLS_PREFIX}/${args.name}/`;
4403
- const skillPages = pages.filter(
4404
- (p) => p.slug.startsWith(skillPrefix) && p.content
4405
- );
4363
+ const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix) && p.content);
4406
4364
  if (skillPages.length === 0) {
4407
- return `Skill "${args.name}" not found in project. Use gitlab_skill_list to see available skills.`;
4365
+ return `Skill "${args.name}" not found in project.`;
4408
4366
  }
4409
4367
  for (const page of skillPages) {
4410
4368
  await upsertPage(
@@ -4416,54 +4374,6 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4416
4374
  page.content
4417
4375
  );
4418
4376
  }
4419
- const projectIndex = await readIndex(
4420
- auth.instanceUrl,
4421
- auth.token,
4422
- projectScope.scope,
4423
- projectScope.id,
4424
- SKILLS_INDEX
4425
- );
4426
- const entry = projectIndex.find((e) => e.name === args.name);
4427
- const description = entry?.description ?? "(promoted from project)";
4428
- if (entry?.snippetId) {
4429
- const bundleFiles = await listSnippetFiles(
4430
- auth.instanceUrl,
4431
- auth.token,
4432
- args.project_id,
4433
- entry.snippetId
4434
- );
4435
- for (const bf of bundleFiles) {
4436
- const raw = await getSnippetFileRaw(
4437
- auth.instanceUrl,
4438
- auth.token,
4439
- args.project_id,
4440
- entry.snippetId,
4441
- bf.path
4442
- );
4443
- await upsertPage(
4444
- auth.instanceUrl,
4445
- auth.token,
4446
- "groups",
4447
- args.group_id,
4448
- `${SKILLS_PREFIX}/${args.name}/bundle/${bf.path}`,
4449
- raw
4450
- );
4451
- }
4452
- }
4453
- await upsertIndexEntry(
4454
- auth.instanceUrl,
4455
- auth.token,
4456
- "groups",
4457
- args.group_id,
4458
- SKILLS_INDEX,
4459
- {
4460
- name: args.name,
4461
- description,
4462
- source: `project:${args.project_id}`,
4463
- snippetId: entry?.snippetId,
4464
- draft: false
4465
- }
4466
- );
4467
4377
  for (const page of skillPages) {
4468
4378
  await deleteWikiPage(
4469
4379
  auth.instanceUrl,
@@ -4473,55 +4383,30 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4473
4383
  page.slug
4474
4384
  );
4475
4385
  }
4476
- if (entry?.snippetId) {
4477
- try {
4478
- await deleteProjectSnippet(
4479
- auth.instanceUrl,
4480
- auth.token,
4481
- args.project_id,
4482
- entry.snippetId
4483
- );
4484
- } catch {
4485
- }
4486
- }
4487
- await removeIndexEntry(
4488
- auth.instanceUrl,
4489
- auth.token,
4490
- projectScope.scope,
4491
- projectScope.id,
4492
- SKILLS_INDEX,
4493
- args.name
4494
- );
4495
- return `Promoted skill "${args.name}" to group "${args.group_id}". ${skillPages.length} page(s) moved (removed from project).`;
4386
+ return `Promoted skill "${args.name}" to group "${args.group_id}". ${skillPages.length} page(s) moved.`;
4496
4387
  } catch (err) {
4497
4388
  return `Error promoting skill to group: ${err.message}`;
4498
4389
  }
4499
4390
  }
4500
4391
  const { scope, id } = resolveScope2(args);
4501
4392
  try {
4502
- const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id, true);
4503
- const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
4504
- const draftPages = pages.filter(
4505
- (p) => p.slug.startsWith(draftPrefix) && p.content
4393
+ const pages = await listWikiPages(
4394
+ auth.instanceUrl,
4395
+ auth.token,
4396
+ scope,
4397
+ id,
4398
+ true
4506
4399
  );
4400
+ const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
4401
+ const draftPages = pages.filter((p) => p.slug.startsWith(draftPrefix) && p.content);
4507
4402
  if (draftPages.length === 0) {
4508
- return `Draft skill "${args.name}" not found. Use gitlab_skill_list(include_drafts=true) to see available drafts.`;
4403
+ return `Draft skill "${args.name}" not found.`;
4509
4404
  }
4510
- const draftIndex = await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX);
4511
- const entry = draftIndex.find((e) => e.name === args.name);
4512
- const description = entry?.description ?? "(promoted from draft)";
4513
4405
  for (const page of draftPages) {
4514
4406
  const newSlug = page.slug.replace(DRAFTS_PREFIX, SKILLS_PREFIX);
4515
4407
  await upsertPage(auth.instanceUrl, auth.token, scope, id, newSlug, page.content);
4516
4408
  await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
4517
4409
  }
4518
- await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX, args.name);
4519
- await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX, {
4520
- name: args.name,
4521
- description,
4522
- source: "project",
4523
- draft: false
4524
- });
4525
4410
  return `Promoted skill "${args.name}" from draft to published.`;
4526
4411
  } catch (err) {
4527
4412
  return `Error promoting skill: ${err.message}`;
@@ -4529,11 +4414,11 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4529
4414
  }
4530
4415
  }),
4531
4416
  gitlab_skill_discover: (0, import_plugin6.tool)({
4532
- description: "Search for skills in the skills.sh public registry and optionally a group wiki.\nUse gitlab_skill_install to install a discovered skill into your project.\nIMPORTANT: project_id is only needed when searching a group wiki. For skills.sh only, it is optional.",
4417
+ description: "Search for skills in skills.sh and optionally a group wiki.\nUse gitlab_skill_install to install a discovered skill.",
4533
4418
  args: {
4534
- query: z6.string().describe("Search query (matches skill name and description)"),
4535
- project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group wiki search."),
4536
- group_id: z6.string().optional().describe("Group path to search for shared skills (optional)")
4419
+ query: z6.string().describe("Search query"),
4420
+ project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group search."),
4421
+ group_id: z6.string().optional().describe("Group path to search")
4537
4422
  },
4538
4423
  execute: async (args) => {
4539
4424
  let auth = null;
@@ -4545,15 +4430,15 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4545
4430
  const sections = [];
4546
4431
  if (args.group_id && auth) {
4547
4432
  try {
4548
- const entries = await readIndex(
4433
+ const skills = await listSkills(
4549
4434
  auth.instanceUrl,
4550
4435
  auth.token,
4551
4436
  "groups",
4552
4437
  args.group_id,
4553
- SKILLS_INDEX
4438
+ SKILLS_PREFIX
4554
4439
  );
4555
4440
  const q = args.query.toLowerCase();
4556
- const matches = entries.filter(
4441
+ const matches = skills.filter(
4557
4442
  (e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
4558
4443
  );
4559
4444
  if (matches.length > 0) {
@@ -4582,19 +4467,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4582
4467
  );
4583
4468
  }
4584
4469
  if (sections.length === 0) {
4585
- return `No skills found matching "${args.query}" in ${args.group_id ? "group wiki or " : ""}skills.sh.`;
4470
+ return `No skills found matching "${args.query}".`;
4586
4471
  }
4587
4472
  return sections.join("\n\n---\n\n");
4588
4473
  }
4589
4474
  }),
4590
4475
  gitlab_skill_install: (0, import_plugin6.tool)({
4591
- description: "Install a skill from a group wiki or skills.sh into the project wiki.\nFor group: copies all skill pages (SKILL + references) from the group.\nFor skills.sh: downloads via npx, extracts SKILL.md and files, writes to wiki.\nUpdates the project skill index with the installed skill.",
4476
+ description: "Install a skill from group wiki or skills.sh into the project wiki.\nMarkdown files go to wiki pages, scripts are bundled in a project snippet.",
4592
4477
  args: {
4593
4478
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4594
- name: z6.string().describe(
4595
- 'Skill identifier. For group: skill name (e.g., "incident-retro"). For skills.sh: full identifier (e.g., "vercel-labs/agent-skills@nextjs-developer").'
4596
- ),
4597
- source: z6.enum(["group", "skills.sh"]).describe('Where to install from: "group" (group wiki) or "skills.sh" (public registry)'),
4479
+ name: z6.string().describe("Skill identifier"),
4480
+ source: z6.enum(["group", "skills.sh"]).describe("Where to install from"),
4598
4481
  group_id: z6.string().optional().describe("Group path (required when source is group)"),
4599
4482
  draft: z6.boolean().optional().describe("Install as draft (default: false)")
4600
4483
  },
@@ -4602,24 +4485,45 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4602
4485
  const auth = authAndValidate(args.project_id);
4603
4486
  const projectScope = resolveScope2(args);
4604
4487
  const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4605
- const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4606
4488
  if (args.source === "skills.sh") {
4607
4489
  const downloaded = downloadSkillFromSkillsSh(args.name);
4608
4490
  if (!downloaded) {
4609
- return `Failed to download skill "${args.name}" from skills.sh. Check that the identifier is correct (e.g., "owner/repo@skill-name").`;
4491
+ return `Failed to download skill "${args.name}" from skills.sh.`;
4610
4492
  }
4611
4493
  try {
4494
+ const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
4495
+ const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
4496
+ let snippetId;
4497
+ if (scriptFiles.length > 0) {
4498
+ const snippet = await createProjectSnippet(
4499
+ auth.instanceUrl,
4500
+ auth.token,
4501
+ args.project_id,
4502
+ `skill:${downloaded.name}`,
4503
+ `Scripts for skill "${downloaded.name}"`,
4504
+ [{ file_path: `${downloaded.name}.bundle`, content: packFiles(scriptFiles) }],
4505
+ "private"
4506
+ );
4507
+ snippetId = snippet.id;
4508
+ }
4509
+ const skillBody = formatFrontmatter(
4510
+ {
4511
+ name: downloaded.name,
4512
+ description: downloaded.description,
4513
+ source: `skills.sh:${args.name}`,
4514
+ snippetId
4515
+ },
4516
+ downloaded.content.replace(/^---[\s\S]*?---\s*\n/, "")
4517
+ );
4612
4518
  await upsertPage(
4613
4519
  auth.instanceUrl,
4614
4520
  auth.token,
4615
4521
  projectScope.scope,
4616
4522
  projectScope.id,
4617
4523
  `${targetPrefix}/${downloaded.name}/SKILL`,
4618
- downloaded.content
4524
+ skillBody
4619
4525
  );
4620
4526
  let wikiCount = 1;
4621
- const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
4622
- const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
4623
4527
  for (const file of mdFiles) {
4624
4528
  const slug = `${targetPrefix}/${downloaded.name}/${file.path.replace(/\.[^.]+$/, "")}`;
4625
4529
  await upsertPage(
@@ -4632,44 +4536,14 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4632
4536
  );
4633
4537
  wikiCount++;
4634
4538
  }
4635
- let snippetId;
4636
- if (scriptFiles.length > 0) {
4637
- const packed = packFiles(scriptFiles);
4638
- const snippet = await createProjectSnippet(
4639
- auth.instanceUrl,
4640
- auth.token,
4641
- args.project_id,
4642
- `skill:${downloaded.name}`,
4643
- `Scripts for skill "${downloaded.name}" (${scriptFiles.length} files, installed from skills.sh:${args.name})`,
4644
- [{ file_path: `${downloaded.name}.bundle`, content: packed }],
4645
- "private"
4646
- );
4647
- snippetId = snippet.id;
4648
- }
4649
- await upsertIndexEntry(
4650
- auth.instanceUrl,
4651
- auth.token,
4652
- projectScope.scope,
4653
- projectScope.id,
4654
- targetIndex,
4655
- {
4656
- name: downloaded.name,
4657
- description: downloaded.description,
4658
- source: `skills.sh:${args.name}`,
4659
- snippetId,
4660
- draft: !!args.draft
4661
- }
4662
- );
4663
4539
  const parts = [`${wikiCount} wiki page(s)`];
4664
4540
  if (snippetId) parts.push(`snippet #${snippetId} with ${scriptFiles.length} script(s)`);
4665
- return `Installed skill "${downloaded.name}" from skills.sh. ${parts.join(", ")}. Use gitlab_skill_setup to extract scripts to disk.`;
4541
+ return `Installed skill "${downloaded.name}" from skills.sh. ${parts.join(", ")}. Use gitlab_skill_setup to extract scripts.`;
4666
4542
  } catch (err) {
4667
- return `Error installing skill from skills.sh: ${err.message}`;
4543
+ return `Error installing from skills.sh: ${err.message}`;
4668
4544
  }
4669
4545
  }
4670
- if (!args.group_id) {
4671
- return 'Error: group_id is required when source is "group".';
4672
- }
4546
+ if (!args.group_id) return 'Error: group_id is required when source is "group".';
4673
4547
  try {
4674
4548
  const groupPages = await listWikiPages(
4675
4549
  auth.instanceUrl,
@@ -4679,9 +4553,7 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4679
4553
  true
4680
4554
  );
4681
4555
  const sourcePrefix = `${SKILLS_PREFIX}/${args.name}/`;
4682
- const skillPages = groupPages.filter(
4683
- (p) => p.slug.startsWith(sourcePrefix) && p.content
4684
- );
4556
+ const skillPages = groupPages.filter((p) => p.slug.startsWith(sourcePrefix) && p.content);
4685
4557
  if (skillPages.length === 0) {
4686
4558
  return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
4687
4559
  }
@@ -4696,39 +4568,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4696
4568
  page.content
4697
4569
  );
4698
4570
  }
4699
- const groupIndex = await readIndex(
4700
- auth.instanceUrl,
4701
- auth.token,
4702
- "groups",
4703
- args.group_id,
4704
- SKILLS_INDEX
4705
- );
4706
- const entry = groupIndex.find((e) => e.name === args.name);
4707
- const description = entry?.description ?? "(installed from group)";
4708
- await upsertIndexEntry(
4709
- auth.instanceUrl,
4710
- auth.token,
4711
- projectScope.scope,
4712
- projectScope.id,
4713
- targetIndex,
4714
- {
4715
- name: args.name,
4716
- description,
4717
- source: `group:${args.group_id}`,
4718
- draft: !!args.draft
4719
- }
4720
- );
4721
- return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s) copied.`;
4571
+ return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s).`;
4722
4572
  } catch (err) {
4723
- return `Error installing skill: ${err.message}`;
4573
+ return `Error installing from group: ${err.message}`;
4724
4574
  }
4725
4575
  }
4726
4576
  }),
4727
4577
  gitlab_skill_setup: (0, import_plugin6.tool)({
4728
- description: "Extract a skill to the local .agents/skills/ directory for execution.\nDownloads SKILL.md from wiki and script files from the associated snippet.\nWrites to .agents/skills/<name>/ and ensures .agents/ is in .gitignore.\nAfter setup, OpenCode will auto-discover the skill from the local directory.",
4578
+ description: "Extract a skill to .agents/skills/ for local execution.\nDownloads SKILL.md from wiki and scripts from the associated snippet.\nOpenCode will auto-discover the skill from the local directory.",
4729
4579
  args: {
4730
4580
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4731
- name: z6.string().describe("Skill name to set up locally"),
4581
+ name: z6.string().describe("Skill name to set up"),
4732
4582
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4733
4583
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4734
4584
  },
@@ -4736,29 +4586,35 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4736
4586
  const auth = authAndValidate(args.project_id);
4737
4587
  const { scope, id } = resolveScope2(args);
4738
4588
  const workDir = ctx.getDirectory();
4739
- const targetDir = (0, import_path2.join)(workDir, ".agents", "skills", args.name);
4589
+ const targetDir = (0, import_path3.join)(workDir, ".agents", "skills", args.name);
4740
4590
  try {
4741
- let skillContent = null;
4591
+ let skillPage = null;
4742
4592
  for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
4743
4593
  try {
4744
- const page = await getWikiPage(
4594
+ skillPage = await getWikiPage(
4745
4595
  auth.instanceUrl,
4746
4596
  auth.token,
4747
4597
  scope,
4748
4598
  id,
4749
4599
  `${prefix}/${args.name}/SKILL`
4750
4600
  );
4751
- skillContent = page.content;
4752
4601
  break;
4753
4602
  } catch {
4754
4603
  }
4755
4604
  }
4756
- if (!skillContent) {
4757
- return `Skill "${args.name}" not found in wiki. Use gitlab_skill_list to see available skills.`;
4605
+ if (!skillPage) {
4606
+ return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
4758
4607
  }
4759
- (0, import_fs2.mkdirSync)(targetDir, { recursive: true });
4760
- (0, import_fs2.writeFileSync)((0, import_path2.join)(targetDir, "SKILL.md"), skillContent);
4761
- const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id, true);
4608
+ const { meta, body } = parseFrontmatter(skillPage.content);
4609
+ (0, import_fs3.mkdirSync)(targetDir, { recursive: true });
4610
+ (0, import_fs3.writeFileSync)((0, import_path3.join)(targetDir, "SKILL.md"), body);
4611
+ const pages = await listWikiPages(
4612
+ auth.instanceUrl,
4613
+ auth.token,
4614
+ scope,
4615
+ id,
4616
+ true
4617
+ );
4762
4618
  let refCount = 0;
4763
4619
  for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
4764
4620
  const skillPagePrefix = `${prefix}/${args.name}/`;
@@ -4767,59 +4623,36 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4767
4623
  );
4768
4624
  for (const page of extraPages) {
4769
4625
  const relPath = page.slug.slice(skillPagePrefix.length);
4770
- const filePath = (0, import_path2.join)(targetDir, `${relPath}.md`);
4771
- (0, import_fs2.mkdirSync)((0, import_path2.dirname)(filePath), { recursive: true });
4772
- (0, import_fs2.writeFileSync)(filePath, page.content);
4626
+ const filePath = (0, import_path3.join)(targetDir, `${relPath}.md`);
4627
+ (0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
4628
+ (0, import_fs3.writeFileSync)(filePath, page.content);
4773
4629
  refCount++;
4774
4630
  }
4775
4631
  }
4776
4632
  let scriptCount = 0;
4777
- const indexEntries = await readIndex(
4778
- auth.instanceUrl,
4779
- auth.token,
4780
- scope,
4781
- id,
4782
- SKILLS_INDEX
4783
- );
4784
- const draftsEntries = await readIndex(
4785
- auth.instanceUrl,
4786
- auth.token,
4787
- scope,
4788
- id,
4789
- DRAFTS_INDEX
4790
- );
4791
- const entry = [...indexEntries, ...draftsEntries].find((e) => e.name === args.name);
4792
- if (entry?.snippetId) {
4633
+ if (meta.snippetId) {
4793
4634
  const bundleFiles = await listSnippetFiles(
4794
4635
  auth.instanceUrl,
4795
4636
  auth.token,
4796
4637
  args.project_id,
4797
- entry.snippetId
4638
+ meta.snippetId
4798
4639
  );
4799
4640
  for (const bf of bundleFiles) {
4800
4641
  const raw = await getSnippetFileRaw(
4801
4642
  auth.instanceUrl,
4802
4643
  auth.token,
4803
4644
  args.project_id,
4804
- entry.snippetId,
4645
+ meta.snippetId,
4805
4646
  bf.path
4806
4647
  );
4807
4648
  if (bf.path.endsWith(".bundle")) {
4808
- const unpacked = unpackFiles(raw);
4809
- for (const file of unpacked) {
4810
- const filePath = (0, import_path2.join)(targetDir, file.path);
4811
- (0, import_fs2.mkdirSync)((0, import_path2.dirname)(filePath), { recursive: true });
4812
- (0, import_fs2.writeFileSync)(filePath, file.content);
4813
- if (file.path.endsWith(".sh")) {
4814
- (0, import_fs2.chmodSync)(filePath, 493);
4815
- }
4649
+ for (const file of unpackFiles(raw)) {
4650
+ const filePath = (0, import_path3.join)(targetDir, file.path);
4651
+ (0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
4652
+ (0, import_fs3.writeFileSync)(filePath, file.content);
4653
+ if (file.path.endsWith(".sh")) (0, import_fs3.chmodSync)(filePath, 493);
4816
4654
  scriptCount++;
4817
4655
  }
4818
- } else {
4819
- const filePath = (0, import_path2.join)(targetDir, bf.path);
4820
- (0, import_fs2.mkdirSync)((0, import_path2.dirname)(filePath), { recursive: true });
4821
- (0, import_fs2.writeFileSync)(filePath, raw);
4822
- scriptCount++;
4823
4656
  }
4824
4657
  }
4825
4658
  }
@@ -4827,18 +4660,18 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4827
4660
  const parts = ["SKILL.md"];
4828
4661
  if (refCount > 0) parts.push(`${refCount} reference(s)`);
4829
4662
  if (scriptCount > 0) parts.push(`${scriptCount} script(s)`);
4830
- return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")}). OpenCode will auto-discover it on next session.`;
4663
+ return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")}).`;
4831
4664
  } catch (err) {
4832
4665
  return `Error setting up skill: ${err.message}`;
4833
4666
  }
4834
4667
  }
4835
4668
  }),
4836
4669
  gitlab_skill_delete: (0, import_plugin6.tool)({
4837
- description: "Delete a skill and remove it from the index.\nRemoves all wiki pages under the skill directory and any associated snippet.\nWiki and snippet git history preserves deleted content.",
4670
+ description: "Delete a skill and its associated snippet.\nRemoves all wiki pages under the skill directory.",
4838
4671
  args: {
4839
4672
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4840
4673
  name: z6.string().describe("Skill name to delete"),
4841
- draft: z6.boolean().optional().describe("Delete from drafts instead of published (default: false)"),
4674
+ draft: z6.boolean().optional().describe("Delete from drafts (default: false)"),
4842
4675
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4843
4676
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4844
4677
  },
@@ -4846,36 +4679,36 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4846
4679
  const auth = authAndValidate(args.project_id);
4847
4680
  const { scope, id } = resolveScope2(args);
4848
4681
  const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4849
- const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4850
4682
  const skillPrefix = `${prefix}/${args.name}/`;
4851
4683
  try {
4852
4684
  const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
4853
4685
  const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix));
4854
4686
  if (skillPages.length === 0) {
4855
- return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
4687
+ return `Skill "${args.name}" not found.`;
4856
4688
  }
4689
+ const skillPage = await getWikiPage(
4690
+ auth.instanceUrl,
4691
+ auth.token,
4692
+ scope,
4693
+ id,
4694
+ `${prefix}/${args.name}/SKILL`
4695
+ );
4696
+ const { meta } = parseFrontmatter(skillPage.content);
4857
4697
  for (const page of skillPages) {
4858
4698
  await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
4859
4699
  }
4860
- const indexEntries = await readIndex(auth.instanceUrl, auth.token, scope, id, indexSlug);
4861
- const entry = indexEntries.find((e) => e.name === args.name);
4862
- let snippetDeleted = false;
4863
- if (entry?.snippetId) {
4700
+ if (meta.snippetId) {
4864
4701
  try {
4865
4702
  await deleteProjectSnippet(
4866
4703
  auth.instanceUrl,
4867
4704
  auth.token,
4868
4705
  args.project_id,
4869
- entry.snippetId
4706
+ meta.snippetId
4870
4707
  );
4871
- snippetDeleted = true;
4872
4708
  } catch {
4873
4709
  }
4874
4710
  }
4875
- await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, args.name);
4876
- const parts = [`${skillPages.length} wiki page(s)`];
4877
- if (snippetDeleted) parts.push("snippet");
4878
- return `Deleted skill "${args.name}" (${parts.join(" + ")} removed).`;
4711
+ return `Deleted skill "${args.name}" (${skillPages.length} page(s)${meta.snippetId ? " + snippet" : ""}).`;
4879
4712
  } catch (err) {
4880
4713
  return `Error deleting skill: ${err.message}`;
4881
4714
  }