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.js CHANGED
@@ -3848,6 +3848,10 @@ async function listSnippetFiles(instanceUrl, token, projectId, snippetId) {
3848
3848
  }
3849
3849
 
3850
3850
  // src/tools/skill-tools.ts
3851
+ import { writeFileSync as writeFileSync2, mkdirSync, chmodSync } from "fs";
3852
+ import { join as join3, dirname } from "path";
3853
+
3854
+ // src/tools/skill-helpers.ts
3851
3855
  import { execSync } from "child_process";
3852
3856
  import {
3853
3857
  mkdtempSync,
@@ -3856,141 +3860,66 @@ import {
3856
3860
  readdirSync,
3857
3861
  statSync,
3858
3862
  writeFileSync,
3859
- mkdirSync,
3860
- existsSync,
3861
- chmodSync
3863
+ existsSync
3862
3864
  } from "fs";
3863
- import { join as join2, dirname } from "path";
3865
+ import { join as join2 } from "path";
3864
3866
  import { tmpdir } from "os";
3865
3867
  import { gzipSync, gunzipSync } from "zlib";
3866
- var z6 = tool6.schema;
3867
3868
  var PREFIX2 = "agents";
3868
3869
  var SKILLS_PREFIX = `${PREFIX2}/skills`;
3869
3870
  var DRAFTS_PREFIX = `${PREFIX2}/skills-drafts`;
3870
- var SKILLS_INDEX = `${SKILLS_PREFIX}/_registry`;
3871
- var DRAFTS_INDEX = `${DRAFTS_PREFIX}/_registry`;
3872
- var PROJECT_ID_DESC2 = "Project path from git remote";
3873
- function resolveScope2(args) {
3874
- if (args.scope === "groups" && args.group_id) {
3875
- return { scope: "groups", id: args.group_id };
3876
- }
3877
- return { scope: "projects", id: args.project_id };
3878
- }
3879
- function validateProjectId2(projectId) {
3880
- if (!projectId.includes("/")) {
3881
- 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.`;
3882
- }
3883
- return null;
3884
- }
3885
- function parseIndex(content) {
3886
- const entries = [];
3887
- const blocks = content.split(/^## /m).filter(Boolean);
3888
- for (const block of blocks) {
3889
- const lines = block.trim().split("\n");
3890
- const name = lines[0].trim();
3891
- if (!name) continue;
3892
- const rest = lines.slice(1).join("\n").trim();
3893
- const descLines = [];
3894
- let source;
3895
- let snippetId;
3896
- for (const line of rest.split("\n")) {
3897
- if (line.startsWith("Source:")) {
3898
- source = line.slice(7).trim();
3899
- } else if (line.startsWith("Snippet:")) {
3900
- snippetId = parseInt(line.slice(8).trim(), 10) || void 0;
3901
- } else if (line.trim()) {
3902
- descLines.push(line);
3903
- }
3904
- }
3905
- entries.push({ name, description: descLines.join("\n"), source, snippetId, draft: false });
3906
- }
3907
- return entries;
3908
- }
3909
- function formatIndex(entries) {
3910
- return entries.map((e) => {
3911
- let block = `## ${e.name}
3912
- ${e.description}`;
3913
- if (e.source) block += `
3914
- Source: ${e.source}`;
3915
- if (e.snippetId) block += `
3916
- Snippet: ${e.snippetId}`;
3917
- return block;
3918
- }).join("\n\n");
3919
- }
3920
- async function readIndex(instanceUrl, token, scope, id, indexSlug) {
3921
- try {
3922
- const page = await getWikiPage(instanceUrl, token, scope, id, indexSlug);
3923
- return parseIndex(page.content);
3924
- } catch {
3925
- return [];
3926
- }
3927
- }
3928
- async function writeIndex(instanceUrl, token, scope, id, indexSlug, entries) {
3929
- const content = formatIndex(entries) || "# Skills Index";
3930
- try {
3931
- await updateWikiPage(instanceUrl, token, scope, id, indexSlug, content);
3932
- } catch (updateErr) {
3933
- if (!updateErr.message?.includes("not found") && !updateErr.message?.includes("404"))
3934
- throw updateErr;
3935
- await createWikiPage(instanceUrl, token, scope, id, indexSlug, content);
3871
+ var EMPTY_FILE_SENTINEL = "__EMPTY_FILE__";
3872
+ function parseFrontmatter(content) {
3873
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
3874
+ if (!match) return { meta: {}, body: content };
3875
+ const meta = {};
3876
+ for (const line of match[1].split("\n")) {
3877
+ const [key, ...rest] = line.split(":");
3878
+ const val = rest.join(":").trim();
3879
+ if (!key || !val) continue;
3880
+ const k = key.trim();
3881
+ if (k === "name") meta.name = val;
3882
+ else if (k === "description") meta.description = val;
3883
+ else if (k === "source") meta.source = val;
3884
+ else if (k === "snippet") meta.snippetId = parseInt(val, 10) || void 0;
3936
3885
  }
3886
+ return { meta, body: match[2] };
3937
3887
  }
3938
- async function upsertIndexEntry(instanceUrl, token, scope, id, indexSlug, entry) {
3939
- const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
3940
- const idx = entries.findIndex((e) => e.name === entry.name);
3941
- if (idx >= 0) {
3942
- entries[idx] = entry;
3943
- } else {
3944
- entries.push(entry);
3945
- }
3946
- await writeIndex(instanceUrl, token, scope, id, indexSlug, entries);
3888
+ function formatFrontmatter(meta, body) {
3889
+ const lines = ["---", `name: ${meta.name}`, `description: ${meta.description}`];
3890
+ if (meta.source) lines.push(`source: ${meta.source}`);
3891
+ if (meta.snippetId) lines.push(`snippet: ${meta.snippetId}`);
3892
+ lines.push("---", "");
3893
+ return lines.join("\n") + body;
3947
3894
  }
3948
- async function removeIndexEntry(instanceUrl, token, scope, id, indexSlug, name) {
3949
- const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
3950
- const filtered = entries.filter((e) => e.name !== name);
3951
- if (filtered.length !== entries.length) {
3952
- await writeIndex(instanceUrl, token, scope, id, indexSlug, filtered);
3953
- }
3895
+ function extractSkillNameFromSlug(slug, prefix) {
3896
+ if (!slug.startsWith(prefix + "/") || !slug.endsWith("/SKILL")) return null;
3897
+ const middle = slug.slice(prefix.length + 1, -"/SKILL".length);
3898
+ return middle && !middle.includes("/") ? middle : null;
3954
3899
  }
3955
- async function upsertPage(instanceUrl, token, scope, id, slug, content) {
3956
- try {
3957
- await updateWikiPage(instanceUrl, token, scope, id, slug, content);
3958
- } catch (updateErr) {
3959
- if (!updateErr.message?.includes("not found") && !updateErr.message?.includes("404"))
3960
- throw updateErr;
3961
- await createWikiPage(instanceUrl, token, scope, id, slug, content);
3962
- }
3900
+ function isMarkdownFile(path) {
3901
+ return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
3963
3902
  }
3964
- var EMPTY_FILE_SENTINEL = "__EMPTY_FILE__";
3965
3903
  function packFiles(files) {
3966
3904
  const manifest = files.map((f) => ({
3967
3905
  path: f.path,
3968
3906
  content: f.content.trim() ? f.content : EMPTY_FILE_SENTINEL
3969
3907
  }));
3970
- const json = JSON.stringify(manifest);
3971
- return gzipSync(Buffer.from(json, "utf-8")).toString("base64");
3908
+ return gzipSync(Buffer.from(JSON.stringify(manifest), "utf-8")).toString("base64");
3972
3909
  }
3973
3910
  function unpackFiles(packed) {
3974
3911
  const json = gunzipSync(Buffer.from(packed, "base64")).toString("utf-8");
3975
- const manifest = JSON.parse(json);
3976
- return manifest.map((f) => ({
3912
+ return JSON.parse(json).map((f) => ({
3977
3913
  path: f.path,
3978
3914
  content: f.content === EMPTY_FILE_SENTINEL ? "" : f.content
3979
3915
  }));
3980
3916
  }
3981
- function isMarkdownFile(path) {
3982
- return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
3983
- }
3984
- function isAgentsGitignored(dir) {
3917
+ function ensureGitignore(dir) {
3985
3918
  try {
3986
3919
  execSync("git check-ignore -q .agents", { cwd: dir, stdio: "pipe" });
3987
- return true;
3920
+ return;
3988
3921
  } catch {
3989
- return false;
3990
3922
  }
3991
- }
3992
- function ensureGitignore(dir) {
3993
- if (isAgentsGitignored(dir)) return;
3994
3923
  const gitignorePath = join2(dir, ".gitignore");
3995
3924
  if (existsSync(gitignorePath)) {
3996
3925
  const content = readFileSync2(gitignorePath, "utf-8");
@@ -4043,10 +3972,9 @@ function downloadSkillFromSkillsSh(identifier) {
4043
3972
  if (dirs.length === 0) return null;
4044
3973
  const skillName = dirs[0];
4045
3974
  const skillDir = join2(agentsDir, skillName);
4046
- const skillMd = join2(skillDir, "SKILL.md");
4047
3975
  let mainContent;
4048
3976
  try {
4049
- mainContent = readFileSync2(skillMd, "utf-8");
3977
+ mainContent = readFileSync2(join2(skillDir, "SKILL.md"), "utf-8");
4050
3978
  } catch {
4051
3979
  return null;
4052
3980
  }
@@ -4055,8 +3983,7 @@ function downloadSkillFromSkillsSh(identifier) {
4055
3983
  if (descMatch) {
4056
3984
  description = descMatch[1].trim();
4057
3985
  } else {
4058
- const firstParagraph = mainContent.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim();
4059
- description = firstParagraph.slice(0, 200);
3986
+ description = mainContent.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim().slice(0, 200);
4060
3987
  }
4061
3988
  const files = [];
4062
3989
  const walkStack = [{ dir: skillDir, prefix: "" }];
@@ -4085,6 +4012,50 @@ function downloadSkillFromSkillsSh(identifier) {
4085
4012
  }
4086
4013
  }
4087
4014
  }
4015
+
4016
+ // src/tools/skill-tools.ts
4017
+ var z6 = tool6.schema;
4018
+ var PROJECT_ID_DESC2 = "Project path from git remote";
4019
+ function resolveScope2(args) {
4020
+ if (args.scope === "groups" && args.group_id) {
4021
+ return { scope: "groups", id: args.group_id };
4022
+ }
4023
+ return { scope: "projects", id: args.project_id };
4024
+ }
4025
+ function validateProjectId2(projectId) {
4026
+ if (!projectId.includes("/")) {
4027
+ return `Invalid project_id "${projectId}". Must be the full project path containing at least one slash.`;
4028
+ }
4029
+ return null;
4030
+ }
4031
+ async function upsertPage(instanceUrl, token, scope, id, slug, content) {
4032
+ try {
4033
+ await updateWikiPage(instanceUrl, token, scope, id, slug, content);
4034
+ } catch (err) {
4035
+ if (err.message?.includes("not found") || err.message?.includes("404")) {
4036
+ await createWikiPage(instanceUrl, token, scope, id, slug, content);
4037
+ return;
4038
+ }
4039
+ throw err;
4040
+ }
4041
+ }
4042
+ async function listSkills(instanceUrl, token, scope, id, prefix) {
4043
+ const pages = await listWikiPages(instanceUrl, token, scope, id, true);
4044
+ const skills = [];
4045
+ for (const page of pages) {
4046
+ const name = extractSkillNameFromSlug(page.slug, prefix);
4047
+ if (!name || !page.content) continue;
4048
+ const { meta } = parseFrontmatter(page.content);
4049
+ skills.push({
4050
+ name: meta.name ?? name,
4051
+ description: meta.description ?? "",
4052
+ source: meta.source,
4053
+ snippetId: meta.snippetId,
4054
+ draft: prefix === DRAFTS_PREFIX
4055
+ });
4056
+ }
4057
+ return skills;
4058
+ }
4088
4059
  function makeSkillTools(ctx) {
4089
4060
  function authAndValidate(projectId) {
4090
4061
  const auth = ctx.ensureAuth();
@@ -4095,7 +4066,7 @@ function makeSkillTools(ctx) {
4095
4066
  }
4096
4067
  return {
4097
4068
  gitlab_skill_list: tool6({
4098
- description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).",
4069
+ description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks.",
4099
4070
  args: {
4100
4071
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4101
4072
  include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
@@ -4106,12 +4077,16 @@ function makeSkillTools(ctx) {
4106
4077
  const auth = authAndValidate(args.project_id);
4107
4078
  const { scope, id } = resolveScope2(args);
4108
4079
  try {
4109
- const published = await readIndex(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX);
4080
+ const published = await listSkills(
4081
+ auth.instanceUrl,
4082
+ auth.token,
4083
+ scope,
4084
+ id,
4085
+ SKILLS_PREFIX
4086
+ );
4110
4087
  let drafts = [];
4111
4088
  if (args.include_drafts) {
4112
- drafts = (await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX)).map(
4113
- (e) => ({ ...e, draft: true })
4114
- );
4089
+ drafts = await listSkills(auth.instanceUrl, auth.token, scope, id, DRAFTS_PREFIX);
4115
4090
  }
4116
4091
  const all = [...published, ...drafts];
4117
4092
  if (all.length === 0) return "No skills found. Use gitlab_skill_save to create one.";
@@ -4122,18 +4097,17 @@ function makeSkillTools(ctx) {
4122
4097
  }
4123
4098
  }),
4124
4099
  gitlab_skill_load: tool6({
4125
- 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.",
4100
+ 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.",
4126
4101
  args: {
4127
4102
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4128
- name: z6.string().describe('Skill name (e.g., "incident-retro", "helm-rollback")'),
4103
+ name: z6.string().describe('Skill name (e.g., "incident-retro")'),
4129
4104
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4130
4105
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4131
4106
  },
4132
4107
  execute: async (args) => {
4133
4108
  const auth = authAndValidate(args.project_id);
4134
4109
  const { scope, id } = resolveScope2(args);
4135
- const prefixes = [SKILLS_PREFIX, DRAFTS_PREFIX];
4136
- for (const prefix of prefixes) {
4110
+ for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
4137
4111
  try {
4138
4112
  const page = await getWikiPage(
4139
4113
  auth.instanceUrl,
@@ -4142,36 +4116,28 @@ function makeSkillTools(ctx) {
4142
4116
  id,
4143
4117
  `${prefix}/${args.name}/SKILL`
4144
4118
  );
4119
+ const { meta, body } = parseFrontmatter(page.content);
4120
+ const isDraft = prefix === DRAFTS_PREFIX;
4145
4121
  const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
4146
4122
  const skillPrefix = `${prefix}/${args.name}/`;
4147
- const skillSlug = `${prefix}/${args.name}/SKILL`;
4148
- const refs = pages.filter((p) => p.slug.startsWith(skillPrefix) && p.slug !== skillSlug).map((p) => p.slug.slice(skillPrefix.length));
4149
- const isDraft = prefix === DRAFTS_PREFIX;
4123
+ const refs = pages.filter(
4124
+ (p) => p.slug.startsWith(skillPrefix) && p.slug !== `${prefix}/${args.name}/SKILL`
4125
+ ).map((p) => p.slug.slice(skillPrefix.length));
4150
4126
  let result = isDraft ? `[DRAFT SKILL]
4151
4127
 
4152
- ${page.content}` : page.content;
4128
+ ${body}` : body;
4153
4129
  if (refs.length > 0) {
4154
4130
  result += `
4155
4131
 
4156
4132
  ---
4157
4133
  Available references: ${refs.join(", ")}`;
4158
4134
  }
4159
- const indexSlug = isDraft ? DRAFTS_INDEX : SKILLS_INDEX;
4160
- const indexEntries = await readIndex(
4161
- auth.instanceUrl,
4162
- auth.token,
4163
- scope,
4164
- id,
4165
- indexSlug
4166
- );
4167
- const entry = indexEntries.find((e) => e.name === args.name);
4168
- if (entry?.snippetId) {
4135
+ if (meta.snippetId) {
4169
4136
  result += `
4170
4137
 
4171
- ---
4172
- This skill has executable scripts in snippet #${entry.snippetId}.`;
4138
+ This skill has executable scripts in snippet #${meta.snippetId}.`;
4173
4139
  result += `
4174
- Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skills/${args.name}/ for execution.`;
4140
+ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them locally.`;
4175
4141
  }
4176
4142
  return result;
4177
4143
  } catch {
@@ -4182,13 +4148,13 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4182
4148
  }
4183
4149
  }),
4184
4150
  gitlab_skill_save: tool6({
4185
- 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.",
4151
+ description: "Create or update a skill.\nUse draft=true for skills that haven't been proven yet.",
4186
4152
  args: {
4187
4153
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4188
4154
  name: z6.string().describe('Skill name (e.g., "incident-retro")'),
4189
4155
  content: z6.string().describe("Skill content in markdown"),
4190
- description: z6.string().describe("Short description for the skill index (1-2 sentences)"),
4191
- draft: z6.boolean().optional().describe("Save as draft skill (default: false)"),
4156
+ description: z6.string().describe("Short description (1-2 sentences)"),
4157
+ draft: z6.boolean().optional().describe("Save as draft (default: false)"),
4192
4158
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4193
4159
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4194
4160
  },
@@ -4196,31 +4162,25 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4196
4162
  const auth = authAndValidate(args.project_id);
4197
4163
  const { scope, id } = resolveScope2(args);
4198
4164
  const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4199
- const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4200
4165
  const slug = `${prefix}/${args.name}/SKILL`;
4201
- const label = args.draft ? "draft " : "";
4166
+ const body = formatFrontmatter(
4167
+ { name: args.name, description: args.description, source: "project" },
4168
+ args.content
4169
+ );
4202
4170
  try {
4203
- await upsertPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
4204
- await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, {
4205
- name: args.name,
4206
- description: args.description,
4207
- source: "project",
4208
- draft: !!args.draft
4209
- });
4210
- return `Saved ${label}skill: ${args.name}`;
4171
+ await upsertPage(auth.instanceUrl, auth.token, scope, id, slug, body);
4172
+ return `Saved ${args.draft ? "draft " : ""}skill: ${args.name}`;
4211
4173
  } catch (err) {
4212
4174
  return `Error saving skill: ${err.message}`;
4213
4175
  }
4214
4176
  }
4215
4177
  }),
4216
4178
  gitlab_skill_promote: tool6({
4217
- 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.",
4179
+ description: "Promote a skill.\nDefault (target='published'): moves a draft to published.\nTarget 'group': moves a project skill to the group wiki.",
4218
4180
  args: {
4219
4181
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4220
4182
  name: z6.string().describe("Skill name to promote"),
4221
- target: z6.enum(["published", "group"]).optional().describe(
4222
- 'Promotion target: "published" (default, draft\u2192published) or "group" (project\u2192group wiki)'
4223
- ),
4183
+ target: z6.enum(["published", "group"]).optional().describe('"published" (default) or "group"'),
4224
4184
  group_id: z6.string().optional().describe("Group path (required when target is group)"),
4225
4185
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)")
4226
4186
  },
@@ -4228,9 +4188,7 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4228
4188
  const auth = authAndValidate(args.project_id);
4229
4189
  const promotionTarget = args.target ?? "published";
4230
4190
  if (promotionTarget === "group") {
4231
- if (!args.group_id) {
4232
- return 'Error: group_id is required when target is "group".';
4233
- }
4191
+ if (!args.group_id) return 'Error: group_id is required when target is "group".';
4234
4192
  const projectScope = resolveScope2(args);
4235
4193
  try {
4236
4194
  const pages = await listWikiPages(
@@ -4241,11 +4199,9 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4241
4199
  true
4242
4200
  );
4243
4201
  const skillPrefix = `${SKILLS_PREFIX}/${args.name}/`;
4244
- const skillPages = pages.filter(
4245
- (p) => p.slug.startsWith(skillPrefix) && p.content
4246
- );
4202
+ const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix) && p.content);
4247
4203
  if (skillPages.length === 0) {
4248
- return `Skill "${args.name}" not found in project. Use gitlab_skill_list to see available skills.`;
4204
+ return `Skill "${args.name}" not found in project.`;
4249
4205
  }
4250
4206
  for (const page of skillPages) {
4251
4207
  await upsertPage(
@@ -4257,54 +4213,6 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4257
4213
  page.content
4258
4214
  );
4259
4215
  }
4260
- const projectIndex = await readIndex(
4261
- auth.instanceUrl,
4262
- auth.token,
4263
- projectScope.scope,
4264
- projectScope.id,
4265
- SKILLS_INDEX
4266
- );
4267
- const entry = projectIndex.find((e) => e.name === args.name);
4268
- const description = entry?.description ?? "(promoted from project)";
4269
- if (entry?.snippetId) {
4270
- const bundleFiles = await listSnippetFiles(
4271
- auth.instanceUrl,
4272
- auth.token,
4273
- args.project_id,
4274
- entry.snippetId
4275
- );
4276
- for (const bf of bundleFiles) {
4277
- const raw = await getSnippetFileRaw(
4278
- auth.instanceUrl,
4279
- auth.token,
4280
- args.project_id,
4281
- entry.snippetId,
4282
- bf.path
4283
- );
4284
- await upsertPage(
4285
- auth.instanceUrl,
4286
- auth.token,
4287
- "groups",
4288
- args.group_id,
4289
- `${SKILLS_PREFIX}/${args.name}/bundle/${bf.path}`,
4290
- raw
4291
- );
4292
- }
4293
- }
4294
- await upsertIndexEntry(
4295
- auth.instanceUrl,
4296
- auth.token,
4297
- "groups",
4298
- args.group_id,
4299
- SKILLS_INDEX,
4300
- {
4301
- name: args.name,
4302
- description,
4303
- source: `project:${args.project_id}`,
4304
- snippetId: entry?.snippetId,
4305
- draft: false
4306
- }
4307
- );
4308
4216
  for (const page of skillPages) {
4309
4217
  await deleteWikiPage(
4310
4218
  auth.instanceUrl,
@@ -4314,55 +4222,30 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4314
4222
  page.slug
4315
4223
  );
4316
4224
  }
4317
- if (entry?.snippetId) {
4318
- try {
4319
- await deleteProjectSnippet(
4320
- auth.instanceUrl,
4321
- auth.token,
4322
- args.project_id,
4323
- entry.snippetId
4324
- );
4325
- } catch {
4326
- }
4327
- }
4328
- await removeIndexEntry(
4329
- auth.instanceUrl,
4330
- auth.token,
4331
- projectScope.scope,
4332
- projectScope.id,
4333
- SKILLS_INDEX,
4334
- args.name
4335
- );
4336
- return `Promoted skill "${args.name}" to group "${args.group_id}". ${skillPages.length} page(s) moved (removed from project).`;
4225
+ return `Promoted skill "${args.name}" to group "${args.group_id}". ${skillPages.length} page(s) moved.`;
4337
4226
  } catch (err) {
4338
4227
  return `Error promoting skill to group: ${err.message}`;
4339
4228
  }
4340
4229
  }
4341
4230
  const { scope, id } = resolveScope2(args);
4342
4231
  try {
4343
- const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id, true);
4344
- const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
4345
- const draftPages = pages.filter(
4346
- (p) => p.slug.startsWith(draftPrefix) && p.content
4232
+ const pages = await listWikiPages(
4233
+ auth.instanceUrl,
4234
+ auth.token,
4235
+ scope,
4236
+ id,
4237
+ true
4347
4238
  );
4239
+ const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
4240
+ const draftPages = pages.filter((p) => p.slug.startsWith(draftPrefix) && p.content);
4348
4241
  if (draftPages.length === 0) {
4349
- return `Draft skill "${args.name}" not found. Use gitlab_skill_list(include_drafts=true) to see available drafts.`;
4242
+ return `Draft skill "${args.name}" not found.`;
4350
4243
  }
4351
- const draftIndex = await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX);
4352
- const entry = draftIndex.find((e) => e.name === args.name);
4353
- const description = entry?.description ?? "(promoted from draft)";
4354
4244
  for (const page of draftPages) {
4355
4245
  const newSlug = page.slug.replace(DRAFTS_PREFIX, SKILLS_PREFIX);
4356
4246
  await upsertPage(auth.instanceUrl, auth.token, scope, id, newSlug, page.content);
4357
4247
  await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
4358
4248
  }
4359
- await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX, args.name);
4360
- await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX, {
4361
- name: args.name,
4362
- description,
4363
- source: "project",
4364
- draft: false
4365
- });
4366
4249
  return `Promoted skill "${args.name}" from draft to published.`;
4367
4250
  } catch (err) {
4368
4251
  return `Error promoting skill: ${err.message}`;
@@ -4370,11 +4253,11 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4370
4253
  }
4371
4254
  }),
4372
4255
  gitlab_skill_discover: tool6({
4373
- 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.",
4256
+ description: "Search for skills in skills.sh and optionally a group wiki.\nUse gitlab_skill_install to install a discovered skill.",
4374
4257
  args: {
4375
- query: z6.string().describe("Search query (matches skill name and description)"),
4376
- project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group wiki search."),
4377
- group_id: z6.string().optional().describe("Group path to search for shared skills (optional)")
4258
+ query: z6.string().describe("Search query"),
4259
+ project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group search."),
4260
+ group_id: z6.string().optional().describe("Group path to search")
4378
4261
  },
4379
4262
  execute: async (args) => {
4380
4263
  let auth = null;
@@ -4386,15 +4269,15 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
4386
4269
  const sections = [];
4387
4270
  if (args.group_id && auth) {
4388
4271
  try {
4389
- const entries = await readIndex(
4272
+ const skills = await listSkills(
4390
4273
  auth.instanceUrl,
4391
4274
  auth.token,
4392
4275
  "groups",
4393
4276
  args.group_id,
4394
- SKILLS_INDEX
4277
+ SKILLS_PREFIX
4395
4278
  );
4396
4279
  const q = args.query.toLowerCase();
4397
- const matches = entries.filter(
4280
+ const matches = skills.filter(
4398
4281
  (e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
4399
4282
  );
4400
4283
  if (matches.length > 0) {
@@ -4423,19 +4306,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4423
4306
  );
4424
4307
  }
4425
4308
  if (sections.length === 0) {
4426
- return `No skills found matching "${args.query}" in ${args.group_id ? "group wiki or " : ""}skills.sh.`;
4309
+ return `No skills found matching "${args.query}".`;
4427
4310
  }
4428
4311
  return sections.join("\n\n---\n\n");
4429
4312
  }
4430
4313
  }),
4431
4314
  gitlab_skill_install: tool6({
4432
- 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.",
4315
+ 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.",
4433
4316
  args: {
4434
4317
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4435
- name: z6.string().describe(
4436
- 'Skill identifier. For group: skill name (e.g., "incident-retro"). For skills.sh: full identifier (e.g., "vercel-labs/agent-skills@nextjs-developer").'
4437
- ),
4438
- source: z6.enum(["group", "skills.sh"]).describe('Where to install from: "group" (group wiki) or "skills.sh" (public registry)'),
4318
+ name: z6.string().describe("Skill identifier"),
4319
+ source: z6.enum(["group", "skills.sh"]).describe("Where to install from"),
4439
4320
  group_id: z6.string().optional().describe("Group path (required when source is group)"),
4440
4321
  draft: z6.boolean().optional().describe("Install as draft (default: false)")
4441
4322
  },
@@ -4443,24 +4324,45 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4443
4324
  const auth = authAndValidate(args.project_id);
4444
4325
  const projectScope = resolveScope2(args);
4445
4326
  const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4446
- const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4447
4327
  if (args.source === "skills.sh") {
4448
4328
  const downloaded = downloadSkillFromSkillsSh(args.name);
4449
4329
  if (!downloaded) {
4450
- return `Failed to download skill "${args.name}" from skills.sh. Check that the identifier is correct (e.g., "owner/repo@skill-name").`;
4330
+ return `Failed to download skill "${args.name}" from skills.sh.`;
4451
4331
  }
4452
4332
  try {
4333
+ const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
4334
+ const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
4335
+ let snippetId;
4336
+ if (scriptFiles.length > 0) {
4337
+ const snippet = await createProjectSnippet(
4338
+ auth.instanceUrl,
4339
+ auth.token,
4340
+ args.project_id,
4341
+ `skill:${downloaded.name}`,
4342
+ `Scripts for skill "${downloaded.name}"`,
4343
+ [{ file_path: `${downloaded.name}.bundle`, content: packFiles(scriptFiles) }],
4344
+ "private"
4345
+ );
4346
+ snippetId = snippet.id;
4347
+ }
4348
+ const skillBody = formatFrontmatter(
4349
+ {
4350
+ name: downloaded.name,
4351
+ description: downloaded.description,
4352
+ source: `skills.sh:${args.name}`,
4353
+ snippetId
4354
+ },
4355
+ downloaded.content.replace(/^---[\s\S]*?---\s*\n/, "")
4356
+ );
4453
4357
  await upsertPage(
4454
4358
  auth.instanceUrl,
4455
4359
  auth.token,
4456
4360
  projectScope.scope,
4457
4361
  projectScope.id,
4458
4362
  `${targetPrefix}/${downloaded.name}/SKILL`,
4459
- downloaded.content
4363
+ skillBody
4460
4364
  );
4461
4365
  let wikiCount = 1;
4462
- const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
4463
- const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
4464
4366
  for (const file of mdFiles) {
4465
4367
  const slug = `${targetPrefix}/${downloaded.name}/${file.path.replace(/\.[^.]+$/, "")}`;
4466
4368
  await upsertPage(
@@ -4473,44 +4375,14 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4473
4375
  );
4474
4376
  wikiCount++;
4475
4377
  }
4476
- let snippetId;
4477
- if (scriptFiles.length > 0) {
4478
- const packed = packFiles(scriptFiles);
4479
- const snippet = await createProjectSnippet(
4480
- auth.instanceUrl,
4481
- auth.token,
4482
- args.project_id,
4483
- `skill:${downloaded.name}`,
4484
- `Scripts for skill "${downloaded.name}" (${scriptFiles.length} files, installed from skills.sh:${args.name})`,
4485
- [{ file_path: `${downloaded.name}.bundle`, content: packed }],
4486
- "private"
4487
- );
4488
- snippetId = snippet.id;
4489
- }
4490
- await upsertIndexEntry(
4491
- auth.instanceUrl,
4492
- auth.token,
4493
- projectScope.scope,
4494
- projectScope.id,
4495
- targetIndex,
4496
- {
4497
- name: downloaded.name,
4498
- description: downloaded.description,
4499
- source: `skills.sh:${args.name}`,
4500
- snippetId,
4501
- draft: !!args.draft
4502
- }
4503
- );
4504
4378
  const parts = [`${wikiCount} wiki page(s)`];
4505
4379
  if (snippetId) parts.push(`snippet #${snippetId} with ${scriptFiles.length} script(s)`);
4506
- return `Installed skill "${downloaded.name}" from skills.sh. ${parts.join(", ")}. Use gitlab_skill_setup to extract scripts to disk.`;
4380
+ return `Installed skill "${downloaded.name}" from skills.sh. ${parts.join(", ")}. Use gitlab_skill_setup to extract scripts.`;
4507
4381
  } catch (err) {
4508
- return `Error installing skill from skills.sh: ${err.message}`;
4382
+ return `Error installing from skills.sh: ${err.message}`;
4509
4383
  }
4510
4384
  }
4511
- if (!args.group_id) {
4512
- return 'Error: group_id is required when source is "group".';
4513
- }
4385
+ if (!args.group_id) return 'Error: group_id is required when source is "group".';
4514
4386
  try {
4515
4387
  const groupPages = await listWikiPages(
4516
4388
  auth.instanceUrl,
@@ -4520,9 +4392,7 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4520
4392
  true
4521
4393
  );
4522
4394
  const sourcePrefix = `${SKILLS_PREFIX}/${args.name}/`;
4523
- const skillPages = groupPages.filter(
4524
- (p) => p.slug.startsWith(sourcePrefix) && p.content
4525
- );
4395
+ const skillPages = groupPages.filter((p) => p.slug.startsWith(sourcePrefix) && p.content);
4526
4396
  if (skillPages.length === 0) {
4527
4397
  return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
4528
4398
  }
@@ -4537,39 +4407,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4537
4407
  page.content
4538
4408
  );
4539
4409
  }
4540
- const groupIndex = await readIndex(
4541
- auth.instanceUrl,
4542
- auth.token,
4543
- "groups",
4544
- args.group_id,
4545
- SKILLS_INDEX
4546
- );
4547
- const entry = groupIndex.find((e) => e.name === args.name);
4548
- const description = entry?.description ?? "(installed from group)";
4549
- await upsertIndexEntry(
4550
- auth.instanceUrl,
4551
- auth.token,
4552
- projectScope.scope,
4553
- projectScope.id,
4554
- targetIndex,
4555
- {
4556
- name: args.name,
4557
- description,
4558
- source: `group:${args.group_id}`,
4559
- draft: !!args.draft
4560
- }
4561
- );
4562
- return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s) copied.`;
4410
+ return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s).`;
4563
4411
  } catch (err) {
4564
- return `Error installing skill: ${err.message}`;
4412
+ return `Error installing from group: ${err.message}`;
4565
4413
  }
4566
4414
  }
4567
4415
  }),
4568
4416
  gitlab_skill_setup: tool6({
4569
- 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.",
4417
+ 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.",
4570
4418
  args: {
4571
4419
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4572
- name: z6.string().describe("Skill name to set up locally"),
4420
+ name: z6.string().describe("Skill name to set up"),
4573
4421
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4574
4422
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4575
4423
  },
@@ -4577,29 +4425,35 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4577
4425
  const auth = authAndValidate(args.project_id);
4578
4426
  const { scope, id } = resolveScope2(args);
4579
4427
  const workDir = ctx.getDirectory();
4580
- const targetDir = join2(workDir, ".agents", "skills", args.name);
4428
+ const targetDir = join3(workDir, ".agents", "skills", args.name);
4581
4429
  try {
4582
- let skillContent = null;
4430
+ let skillPage = null;
4583
4431
  for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
4584
4432
  try {
4585
- const page = await getWikiPage(
4433
+ skillPage = await getWikiPage(
4586
4434
  auth.instanceUrl,
4587
4435
  auth.token,
4588
4436
  scope,
4589
4437
  id,
4590
4438
  `${prefix}/${args.name}/SKILL`
4591
4439
  );
4592
- skillContent = page.content;
4593
4440
  break;
4594
4441
  } catch {
4595
4442
  }
4596
4443
  }
4597
- if (!skillContent) {
4598
- return `Skill "${args.name}" not found in wiki. Use gitlab_skill_list to see available skills.`;
4444
+ if (!skillPage) {
4445
+ return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
4599
4446
  }
4447
+ const { meta, body } = parseFrontmatter(skillPage.content);
4600
4448
  mkdirSync(targetDir, { recursive: true });
4601
- writeFileSync(join2(targetDir, "SKILL.md"), skillContent);
4602
- const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id, true);
4449
+ writeFileSync2(join3(targetDir, "SKILL.md"), body);
4450
+ const pages = await listWikiPages(
4451
+ auth.instanceUrl,
4452
+ auth.token,
4453
+ scope,
4454
+ id,
4455
+ true
4456
+ );
4603
4457
  let refCount = 0;
4604
4458
  for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
4605
4459
  const skillPagePrefix = `${prefix}/${args.name}/`;
@@ -4608,59 +4462,36 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4608
4462
  );
4609
4463
  for (const page of extraPages) {
4610
4464
  const relPath = page.slug.slice(skillPagePrefix.length);
4611
- const filePath = join2(targetDir, `${relPath}.md`);
4465
+ const filePath = join3(targetDir, `${relPath}.md`);
4612
4466
  mkdirSync(dirname(filePath), { recursive: true });
4613
- writeFileSync(filePath, page.content);
4467
+ writeFileSync2(filePath, page.content);
4614
4468
  refCount++;
4615
4469
  }
4616
4470
  }
4617
4471
  let scriptCount = 0;
4618
- const indexEntries = await readIndex(
4619
- auth.instanceUrl,
4620
- auth.token,
4621
- scope,
4622
- id,
4623
- SKILLS_INDEX
4624
- );
4625
- const draftsEntries = await readIndex(
4626
- auth.instanceUrl,
4627
- auth.token,
4628
- scope,
4629
- id,
4630
- DRAFTS_INDEX
4631
- );
4632
- const entry = [...indexEntries, ...draftsEntries].find((e) => e.name === args.name);
4633
- if (entry?.snippetId) {
4472
+ if (meta.snippetId) {
4634
4473
  const bundleFiles = await listSnippetFiles(
4635
4474
  auth.instanceUrl,
4636
4475
  auth.token,
4637
4476
  args.project_id,
4638
- entry.snippetId
4477
+ meta.snippetId
4639
4478
  );
4640
4479
  for (const bf of bundleFiles) {
4641
4480
  const raw = await getSnippetFileRaw(
4642
4481
  auth.instanceUrl,
4643
4482
  auth.token,
4644
4483
  args.project_id,
4645
- entry.snippetId,
4484
+ meta.snippetId,
4646
4485
  bf.path
4647
4486
  );
4648
4487
  if (bf.path.endsWith(".bundle")) {
4649
- const unpacked = unpackFiles(raw);
4650
- for (const file of unpacked) {
4651
- const filePath = join2(targetDir, file.path);
4488
+ for (const file of unpackFiles(raw)) {
4489
+ const filePath = join3(targetDir, file.path);
4652
4490
  mkdirSync(dirname(filePath), { recursive: true });
4653
- writeFileSync(filePath, file.content);
4654
- if (file.path.endsWith(".sh")) {
4655
- chmodSync(filePath, 493);
4656
- }
4491
+ writeFileSync2(filePath, file.content);
4492
+ if (file.path.endsWith(".sh")) chmodSync(filePath, 493);
4657
4493
  scriptCount++;
4658
4494
  }
4659
- } else {
4660
- const filePath = join2(targetDir, bf.path);
4661
- mkdirSync(dirname(filePath), { recursive: true });
4662
- writeFileSync(filePath, raw);
4663
- scriptCount++;
4664
4495
  }
4665
4496
  }
4666
4497
  }
@@ -4668,18 +4499,18 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4668
4499
  const parts = ["SKILL.md"];
4669
4500
  if (refCount > 0) parts.push(`${refCount} reference(s)`);
4670
4501
  if (scriptCount > 0) parts.push(`${scriptCount} script(s)`);
4671
- return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")}). OpenCode will auto-discover it on next session.`;
4502
+ return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")}).`;
4672
4503
  } catch (err) {
4673
4504
  return `Error setting up skill: ${err.message}`;
4674
4505
  }
4675
4506
  }
4676
4507
  }),
4677
4508
  gitlab_skill_delete: tool6({
4678
- 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.",
4509
+ description: "Delete a skill and its associated snippet.\nRemoves all wiki pages under the skill directory.",
4679
4510
  args: {
4680
4511
  project_id: z6.string().describe(PROJECT_ID_DESC2),
4681
4512
  name: z6.string().describe("Skill name to delete"),
4682
- draft: z6.boolean().optional().describe("Delete from drafts instead of published (default: false)"),
4513
+ draft: z6.boolean().optional().describe("Delete from drafts (default: false)"),
4683
4514
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4684
4515
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4685
4516
  },
@@ -4687,36 +4518,36 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
4687
4518
  const auth = authAndValidate(args.project_id);
4688
4519
  const { scope, id } = resolveScope2(args);
4689
4520
  const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4690
- const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4691
4521
  const skillPrefix = `${prefix}/${args.name}/`;
4692
4522
  try {
4693
4523
  const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
4694
4524
  const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix));
4695
4525
  if (skillPages.length === 0) {
4696
- return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
4526
+ return `Skill "${args.name}" not found.`;
4697
4527
  }
4528
+ const skillPage = await getWikiPage(
4529
+ auth.instanceUrl,
4530
+ auth.token,
4531
+ scope,
4532
+ id,
4533
+ `${prefix}/${args.name}/SKILL`
4534
+ );
4535
+ const { meta } = parseFrontmatter(skillPage.content);
4698
4536
  for (const page of skillPages) {
4699
4537
  await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
4700
4538
  }
4701
- const indexEntries = await readIndex(auth.instanceUrl, auth.token, scope, id, indexSlug);
4702
- const entry = indexEntries.find((e) => e.name === args.name);
4703
- let snippetDeleted = false;
4704
- if (entry?.snippetId) {
4539
+ if (meta.snippetId) {
4705
4540
  try {
4706
4541
  await deleteProjectSnippet(
4707
4542
  auth.instanceUrl,
4708
4543
  auth.token,
4709
4544
  args.project_id,
4710
- entry.snippetId
4545
+ meta.snippetId
4711
4546
  );
4712
- snippetDeleted = true;
4713
4547
  } catch {
4714
4548
  }
4715
4549
  }
4716
- await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, args.name);
4717
- const parts = [`${skillPages.length} wiki page(s)`];
4718
- if (snippetDeleted) parts.push("snippet");
4719
- return `Deleted skill "${args.name}" (${parts.join(" + ")} removed).`;
4550
+ return `Deleted skill "${args.name}" (${skillPages.length} page(s)${meta.snippetId ? " + snippet" : ""}).`;
4720
4551
  } catch (err) {
4721
4552
  return `Error deleting skill: ${err.message}`;
4722
4553
  }