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 +212 -379
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +211 -380
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
3860
|
-
existsSync,
|
|
3861
|
-
chmodSync
|
|
3863
|
+
existsSync
|
|
3862
3864
|
} from "fs";
|
|
3863
|
-
import { join as join2
|
|
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
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
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
|
-
|
|
3939
|
-
const
|
|
3940
|
-
|
|
3941
|
-
if (
|
|
3942
|
-
|
|
3943
|
-
|
|
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
|
-
|
|
3949
|
-
|
|
3950
|
-
const
|
|
3951
|
-
|
|
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
|
-
|
|
3956
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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.\
|
|
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"
|
|
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
|
|
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
|
|
4148
|
-
|
|
4149
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
|
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.\
|
|
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
|
|
4191
|
-
draft: z6.boolean().optional().describe("Save as draft
|
|
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
|
|
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,
|
|
4204
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
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
|
|
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
|
|
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
|
|
4376
|
-
project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group
|
|
4377
|
-
group_id: z6.string().optional().describe("Group path to search
|
|
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
|
|
4272
|
+
const skills = await listSkills(
|
|
4390
4273
|
auth.instanceUrl,
|
|
4391
4274
|
auth.token,
|
|
4392
4275
|
"groups",
|
|
4393
4276
|
args.group_id,
|
|
4394
|
-
|
|
4277
|
+
SKILLS_PREFIX
|
|
4395
4278
|
);
|
|
4396
4279
|
const q = args.query.toLowerCase();
|
|
4397
|
-
const matches =
|
|
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}"
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
4428
|
+
const targetDir = join3(workDir, ".agents", "skills", args.name);
|
|
4581
4429
|
try {
|
|
4582
|
-
let
|
|
4430
|
+
let skillPage = null;
|
|
4583
4431
|
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4584
4432
|
try {
|
|
4585
|
-
|
|
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 (!
|
|
4598
|
-
return `Skill "${args.name}" not found
|
|
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
|
-
|
|
4602
|
-
const pages = await listWikiPages(
|
|
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 =
|
|
4465
|
+
const filePath = join3(targetDir, `${relPath}.md`);
|
|
4612
4466
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
4613
|
-
|
|
4467
|
+
writeFileSync2(filePath, page.content);
|
|
4614
4468
|
refCount++;
|
|
4615
4469
|
}
|
|
4616
4470
|
}
|
|
4617
4471
|
let scriptCount = 0;
|
|
4618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4484
|
+
meta.snippetId,
|
|
4646
4485
|
bf.path
|
|
4647
4486
|
);
|
|
4648
4487
|
if (bf.path.endsWith(".bundle")) {
|
|
4649
|
-
const
|
|
4650
|
-
|
|
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
|
-
|
|
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(", ")})
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4545
|
+
meta.snippetId
|
|
4711
4546
|
);
|
|
4712
|
-
snippetDeleted = true;
|
|
4713
4547
|
} catch {
|
|
4714
4548
|
}
|
|
4715
4549
|
}
|
|
4716
|
-
|
|
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
|
}
|