opencode-gitlab-dap 1.16.3 → 1.16.5
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 +203 -492
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +202 -493
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3950,237 +3950,68 @@ ${e.content}`).join("\n\n---\n\n");
|
|
|
3950
3950
|
|
|
3951
3951
|
// src/tools/skill-tools.ts
|
|
3952
3952
|
var import_plugin6 = require("@opencode-ai/plugin");
|
|
3953
|
+
var import_fs3 = require("fs");
|
|
3954
|
+
var import_path3 = require("path");
|
|
3953
3955
|
|
|
3954
|
-
// src/
|
|
3955
|
-
function snippetApi(instanceUrl, token, projectId) {
|
|
3956
|
-
const base = instanceUrl.replace(/\/$/, "");
|
|
3957
|
-
const encoded = typeof projectId === "number" ? projectId : encodeURIComponent(projectId);
|
|
3958
|
-
return {
|
|
3959
|
-
url: `${base}/api/v4/projects/${encoded}/snippets`,
|
|
3960
|
-
headers: {
|
|
3961
|
-
"Content-Type": "application/json",
|
|
3962
|
-
Authorization: `Bearer ${token}`
|
|
3963
|
-
}
|
|
3964
|
-
};
|
|
3965
|
-
}
|
|
3966
|
-
async function handleResponse2(res) {
|
|
3967
|
-
if (!res.ok) {
|
|
3968
|
-
const text = await res.text();
|
|
3969
|
-
throw new Error(`Snippet API error (${res.status}): ${text}`);
|
|
3970
|
-
}
|
|
3971
|
-
return res.json();
|
|
3972
|
-
}
|
|
3973
|
-
async function createProjectSnippet(instanceUrl, token, projectId, title, description, files, visibility = "private") {
|
|
3974
|
-
const { url, headers } = snippetApi(instanceUrl, token, projectId);
|
|
3975
|
-
const res = await fetch(url, {
|
|
3976
|
-
method: "POST",
|
|
3977
|
-
headers,
|
|
3978
|
-
body: JSON.stringify({ title, description, visibility, files })
|
|
3979
|
-
});
|
|
3980
|
-
return handleResponse2(res);
|
|
3981
|
-
}
|
|
3982
|
-
async function deleteProjectSnippet(instanceUrl, token, projectId, snippetId) {
|
|
3983
|
-
const { url, headers } = snippetApi(instanceUrl, token, projectId);
|
|
3984
|
-
const res = await fetch(`${url}/${snippetId}`, { method: "DELETE", headers });
|
|
3985
|
-
if (!res.ok) {
|
|
3986
|
-
const text = await res.text();
|
|
3987
|
-
throw new Error(`Snippet API error (${res.status}): ${text}`);
|
|
3988
|
-
}
|
|
3989
|
-
}
|
|
3990
|
-
async function getSnippetFileRaw(instanceUrl, token, projectId, snippetId, filePath, ref = "main") {
|
|
3991
|
-
const base = instanceUrl.replace(/\/$/, "");
|
|
3992
|
-
const encoded = typeof projectId === "number" ? projectId : encodeURIComponent(projectId);
|
|
3993
|
-
const encodedPath = encodeURIComponent(filePath);
|
|
3994
|
-
const url = `${base}/api/v4/projects/${encoded}/snippets/${snippetId}/files/${ref}/${encodedPath}/raw`;
|
|
3995
|
-
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
3996
|
-
if (!res.ok) {
|
|
3997
|
-
const text = await res.text();
|
|
3998
|
-
throw new Error(`Snippet file API error (${res.status}): ${text}`);
|
|
3999
|
-
}
|
|
4000
|
-
return res.text();
|
|
4001
|
-
}
|
|
4002
|
-
async function listSnippetFiles(instanceUrl, token, projectId, snippetId) {
|
|
4003
|
-
const base = instanceUrl.replace(/\/$/, "");
|
|
4004
|
-
const encoded = typeof projectId === "number" ? projectId : encodeURIComponent(projectId);
|
|
4005
|
-
const url = `${base}/api/v4/projects/${encoded}/snippets/${snippetId}`;
|
|
4006
|
-
const res = await fetch(url, {
|
|
4007
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }
|
|
4008
|
-
});
|
|
4009
|
-
const snippet = await handleResponse2(res);
|
|
4010
|
-
if (snippet.files) {
|
|
4011
|
-
return snippet.files.map((f) => ({ path: f.path, raw_url: f.raw_url }));
|
|
4012
|
-
}
|
|
4013
|
-
if (snippet.file_name) {
|
|
4014
|
-
return [{ path: snippet.file_name, raw_url: snippet.raw_url }];
|
|
4015
|
-
}
|
|
4016
|
-
return [];
|
|
4017
|
-
}
|
|
4018
|
-
|
|
4019
|
-
// src/tools/skill-tools.ts
|
|
3956
|
+
// src/tools/skill-helpers.ts
|
|
4020
3957
|
var import_child_process = require("child_process");
|
|
4021
3958
|
var import_fs2 = require("fs");
|
|
4022
3959
|
var import_path2 = require("path");
|
|
4023
3960
|
var import_os2 = require("os");
|
|
4024
3961
|
var import_zlib = require("zlib");
|
|
4025
|
-
var z6 = import_plugin6.tool.schema;
|
|
4026
3962
|
var PREFIX2 = "agents";
|
|
4027
3963
|
var SKILLS_PREFIX = `${PREFIX2}/skills`;
|
|
4028
3964
|
var DRAFTS_PREFIX = `${PREFIX2}/skills-drafts`;
|
|
4029
|
-
var
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
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 sleep2(ms) {
|
|
4088
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4089
|
-
}
|
|
4090
|
-
async function writeIndex(instanceUrl, token, scope, id, indexSlug, entries) {
|
|
4091
|
-
const content = formatIndex(entries) || "# Skills Registry";
|
|
4092
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
4093
|
-
try {
|
|
4094
|
-
await updateWikiPage(instanceUrl, token, scope, id, indexSlug, content);
|
|
4095
|
-
return;
|
|
4096
|
-
} catch (updateErr) {
|
|
4097
|
-
const msg = updateErr.message ?? "";
|
|
4098
|
-
if (msg.includes("not found") || msg.includes("404")) {
|
|
4099
|
-
await createWikiPage(instanceUrl, token, scope, id, indexSlug, content);
|
|
4100
|
-
return;
|
|
4101
|
-
}
|
|
4102
|
-
if (attempt < 2) {
|
|
4103
|
-
await sleep2(1e3 * (attempt + 1));
|
|
4104
|
-
continue;
|
|
4105
|
-
}
|
|
4106
|
-
throw updateErr;
|
|
4107
|
-
}
|
|
3965
|
+
var EMPTY_FILE_SENTINEL = "__EMPTY_FILE__";
|
|
3966
|
+
function parseFrontmatter(content) {
|
|
3967
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
3968
|
+
if (!match) return { meta: {}, body: content };
|
|
3969
|
+
const meta = {};
|
|
3970
|
+
for (const line of match[1].split("\n")) {
|
|
3971
|
+
const [key, ...rest] = line.split(":");
|
|
3972
|
+
const val = rest.join(":").trim();
|
|
3973
|
+
if (!key || !val) continue;
|
|
3974
|
+
const k = key.trim();
|
|
3975
|
+
if (k === "name") meta.name = val;
|
|
3976
|
+
else if (k === "description") meta.description = val;
|
|
3977
|
+
else if (k === "source") meta.source = val;
|
|
4108
3978
|
}
|
|
3979
|
+
return { meta, body: match[2] };
|
|
4109
3980
|
}
|
|
4110
|
-
|
|
4111
|
-
const
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
} else {
|
|
4116
|
-
entries.push(entry);
|
|
4117
|
-
}
|
|
4118
|
-
await writeIndex(instanceUrl, token, scope, id, indexSlug, entries);
|
|
3981
|
+
function formatFrontmatter(meta, body) {
|
|
3982
|
+
const lines = ["---", `name: ${meta.name}`, `description: ${meta.description}`];
|
|
3983
|
+
if (meta.source) lines.push(`source: ${meta.source}`);
|
|
3984
|
+
lines.push("---", "");
|
|
3985
|
+
return lines.join("\n") + body;
|
|
4119
3986
|
}
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
const
|
|
4123
|
-
|
|
4124
|
-
await writeIndex(instanceUrl, token, scope, id, indexSlug, filtered);
|
|
4125
|
-
}
|
|
3987
|
+
function extractSkillNameFromSlug(slug, prefix) {
|
|
3988
|
+
if (!slug.startsWith(prefix + "/") || !slug.endsWith("/SKILL")) return null;
|
|
3989
|
+
const middle = slug.slice(prefix.length + 1, -"/SKILL".length);
|
|
3990
|
+
return middle && !middle.includes("/") ? middle : null;
|
|
4126
3991
|
}
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
try {
|
|
4130
|
-
await updateWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4131
|
-
return;
|
|
4132
|
-
} catch (updateErr) {
|
|
4133
|
-
const msg = updateErr.message ?? "";
|
|
4134
|
-
if (msg.includes("not found") || msg.includes("404")) {
|
|
4135
|
-
try {
|
|
4136
|
-
await createWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4137
|
-
return;
|
|
4138
|
-
} catch (createErr) {
|
|
4139
|
-
if (attempt < 2 && (createErr.message?.includes("Duplicate") || createErr.message?.includes("reference"))) {
|
|
4140
|
-
await sleep2(1e3 * (attempt + 1));
|
|
4141
|
-
continue;
|
|
4142
|
-
}
|
|
4143
|
-
throw createErr;
|
|
4144
|
-
}
|
|
4145
|
-
}
|
|
4146
|
-
if (attempt < 2) {
|
|
4147
|
-
await sleep2(1e3 * (attempt + 1));
|
|
4148
|
-
continue;
|
|
4149
|
-
}
|
|
4150
|
-
throw updateErr;
|
|
4151
|
-
}
|
|
4152
|
-
}
|
|
3992
|
+
function isMarkdownFile(path) {
|
|
3993
|
+
return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
|
|
4153
3994
|
}
|
|
4154
|
-
var EMPTY_FILE_SENTINEL = "__EMPTY_FILE__";
|
|
4155
3995
|
function packFiles(files) {
|
|
4156
3996
|
const manifest = files.map((f) => ({
|
|
4157
3997
|
path: f.path,
|
|
4158
3998
|
content: f.content.trim() ? f.content : EMPTY_FILE_SENTINEL
|
|
4159
3999
|
}));
|
|
4160
|
-
|
|
4161
|
-
return (0, import_zlib.gzipSync)(Buffer.from(json, "utf-8")).toString("base64");
|
|
4000
|
+
return (0, import_zlib.gzipSync)(Buffer.from(JSON.stringify(manifest), "utf-8")).toString("base64");
|
|
4162
4001
|
}
|
|
4163
4002
|
function unpackFiles(packed) {
|
|
4164
4003
|
const json = (0, import_zlib.gunzipSync)(Buffer.from(packed, "base64")).toString("utf-8");
|
|
4165
|
-
|
|
4166
|
-
return manifest.map((f) => ({
|
|
4004
|
+
return JSON.parse(json).map((f) => ({
|
|
4167
4005
|
path: f.path,
|
|
4168
4006
|
content: f.content === EMPTY_FILE_SENTINEL ? "" : f.content
|
|
4169
4007
|
}));
|
|
4170
4008
|
}
|
|
4171
|
-
function
|
|
4172
|
-
return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
|
|
4173
|
-
}
|
|
4174
|
-
function isAgentsGitignored(dir) {
|
|
4009
|
+
function ensureGitignore(dir) {
|
|
4175
4010
|
try {
|
|
4176
4011
|
(0, import_child_process.execSync)("git check-ignore -q .agents", { cwd: dir, stdio: "pipe" });
|
|
4177
|
-
return
|
|
4012
|
+
return;
|
|
4178
4013
|
} catch {
|
|
4179
|
-
return false;
|
|
4180
4014
|
}
|
|
4181
|
-
}
|
|
4182
|
-
function ensureGitignore(dir) {
|
|
4183
|
-
if (isAgentsGitignored(dir)) return;
|
|
4184
4015
|
const gitignorePath = (0, import_path2.join)(dir, ".gitignore");
|
|
4185
4016
|
if ((0, import_fs2.existsSync)(gitignorePath)) {
|
|
4186
4017
|
const content = (0, import_fs2.readFileSync)(gitignorePath, "utf-8");
|
|
@@ -4233,10 +4064,9 @@ function downloadSkillFromSkillsSh(identifier) {
|
|
|
4233
4064
|
if (dirs.length === 0) return null;
|
|
4234
4065
|
const skillName = dirs[0];
|
|
4235
4066
|
const skillDir = (0, import_path2.join)(agentsDir, skillName);
|
|
4236
|
-
const skillMd = (0, import_path2.join)(skillDir, "SKILL.md");
|
|
4237
4067
|
let mainContent;
|
|
4238
4068
|
try {
|
|
4239
|
-
mainContent = (0, import_fs2.readFileSync)(
|
|
4069
|
+
mainContent = (0, import_fs2.readFileSync)((0, import_path2.join)(skillDir, "SKILL.md"), "utf-8");
|
|
4240
4070
|
} catch {
|
|
4241
4071
|
return null;
|
|
4242
4072
|
}
|
|
@@ -4245,8 +4075,7 @@ function downloadSkillFromSkillsSh(identifier) {
|
|
|
4245
4075
|
if (descMatch) {
|
|
4246
4076
|
description = descMatch[1].trim();
|
|
4247
4077
|
} else {
|
|
4248
|
-
|
|
4249
|
-
description = firstParagraph.slice(0, 200);
|
|
4078
|
+
description = mainContent.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim().slice(0, 200);
|
|
4250
4079
|
}
|
|
4251
4080
|
const files = [];
|
|
4252
4081
|
const walkStack = [{ dir: skillDir, prefix: "" }];
|
|
@@ -4275,6 +4104,51 @@ function downloadSkillFromSkillsSh(identifier) {
|
|
|
4275
4104
|
}
|
|
4276
4105
|
}
|
|
4277
4106
|
}
|
|
4107
|
+
|
|
4108
|
+
// src/tools/skill-tools.ts
|
|
4109
|
+
var z6 = import_plugin6.tool.schema;
|
|
4110
|
+
var PROJECT_ID_DESC2 = "Project path from git remote";
|
|
4111
|
+
function resolveScope2(args) {
|
|
4112
|
+
if (args.scope === "groups" && args.group_id) {
|
|
4113
|
+
return { scope: "groups", id: args.group_id };
|
|
4114
|
+
}
|
|
4115
|
+
return { scope: "projects", id: args.project_id };
|
|
4116
|
+
}
|
|
4117
|
+
function validateProjectId2(projectId) {
|
|
4118
|
+
if (!projectId.includes("/")) {
|
|
4119
|
+
return `Invalid project_id "${projectId}". Must be the full project path containing at least one slash.`;
|
|
4120
|
+
}
|
|
4121
|
+
return null;
|
|
4122
|
+
}
|
|
4123
|
+
async function upsertPage(instanceUrl, token, scope, id, slug, content) {
|
|
4124
|
+
try {
|
|
4125
|
+
await updateWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4126
|
+
} catch (err) {
|
|
4127
|
+
if (err.message?.includes("not found") || err.message?.includes("404")) {
|
|
4128
|
+
await createWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
4131
|
+
throw err;
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
async function listSkills(instanceUrl, token, scope, id, prefix) {
|
|
4135
|
+
const pages = await listWikiPages(instanceUrl, token, scope, id, true);
|
|
4136
|
+
const skills = [];
|
|
4137
|
+
for (const page of pages) {
|
|
4138
|
+
const name = extractSkillNameFromSlug(page.slug, prefix);
|
|
4139
|
+
if (!name || !page.content) continue;
|
|
4140
|
+
const { meta } = parseFrontmatter(page.content);
|
|
4141
|
+
const hasBundle = pages.some((p) => p.slug === `${prefix}/${name}/bundle`);
|
|
4142
|
+
skills.push({
|
|
4143
|
+
name: meta.name ?? name,
|
|
4144
|
+
description: meta.description ?? "",
|
|
4145
|
+
source: meta.source,
|
|
4146
|
+
hasScripts: hasBundle,
|
|
4147
|
+
draft: prefix === DRAFTS_PREFIX
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
return skills;
|
|
4151
|
+
}
|
|
4278
4152
|
function makeSkillTools(ctx) {
|
|
4279
4153
|
function authAndValidate(projectId) {
|
|
4280
4154
|
const auth = ctx.ensureAuth();
|
|
@@ -4285,7 +4159,7 @@ function makeSkillTools(ctx) {
|
|
|
4285
4159
|
}
|
|
4286
4160
|
return {
|
|
4287
4161
|
gitlab_skill_list: (0, import_plugin6.tool)({
|
|
4288
|
-
description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks
|
|
4162
|
+
description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks.",
|
|
4289
4163
|
args: {
|
|
4290
4164
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4291
4165
|
include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
|
|
@@ -4296,12 +4170,16 @@ function makeSkillTools(ctx) {
|
|
|
4296
4170
|
const auth = authAndValidate(args.project_id);
|
|
4297
4171
|
const { scope, id } = resolveScope2(args);
|
|
4298
4172
|
try {
|
|
4299
|
-
const published = await
|
|
4173
|
+
const published = await listSkills(
|
|
4174
|
+
auth.instanceUrl,
|
|
4175
|
+
auth.token,
|
|
4176
|
+
scope,
|
|
4177
|
+
id,
|
|
4178
|
+
SKILLS_PREFIX
|
|
4179
|
+
);
|
|
4300
4180
|
let drafts = [];
|
|
4301
4181
|
if (args.include_drafts) {
|
|
4302
|
-
drafts =
|
|
4303
|
-
(e) => ({ ...e, draft: true })
|
|
4304
|
-
);
|
|
4182
|
+
drafts = await listSkills(auth.instanceUrl, auth.token, scope, id, DRAFTS_PREFIX);
|
|
4305
4183
|
}
|
|
4306
4184
|
const all = [...published, ...drafts];
|
|
4307
4185
|
if (all.length === 0) return "No skills found. Use gitlab_skill_save to create one.";
|
|
@@ -4312,18 +4190,17 @@ function makeSkillTools(ctx) {
|
|
|
4312
4190
|
}
|
|
4313
4191
|
}),
|
|
4314
4192
|
gitlab_skill_load: (0, import_plugin6.tool)({
|
|
4315
|
-
description: "Load a specific skill by name.\
|
|
4193
|
+
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.",
|
|
4316
4194
|
args: {
|
|
4317
4195
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4318
|
-
name: z6.string().describe('Skill name (e.g., "incident-retro"
|
|
4196
|
+
name: z6.string().describe('Skill name (e.g., "incident-retro")'),
|
|
4319
4197
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4320
4198
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4321
4199
|
},
|
|
4322
4200
|
execute: async (args) => {
|
|
4323
4201
|
const auth = authAndValidate(args.project_id);
|
|
4324
4202
|
const { scope, id } = resolveScope2(args);
|
|
4325
|
-
const
|
|
4326
|
-
for (const prefix of prefixes) {
|
|
4203
|
+
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4327
4204
|
try {
|
|
4328
4205
|
const page = await getWikiPage(
|
|
4329
4206
|
auth.instanceUrl,
|
|
@@ -4332,36 +4209,29 @@ function makeSkillTools(ctx) {
|
|
|
4332
4209
|
id,
|
|
4333
4210
|
`${prefix}/${args.name}/SKILL`
|
|
4334
4211
|
);
|
|
4212
|
+
const { body } = parseFrontmatter(page.content);
|
|
4213
|
+
const isDraft = prefix === DRAFTS_PREFIX;
|
|
4335
4214
|
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
4336
4215
|
const skillPrefix = `${prefix}/${args.name}/`;
|
|
4337
|
-
const
|
|
4338
|
-
|
|
4339
|
-
|
|
4216
|
+
const refs = pages.filter(
|
|
4217
|
+
(p) => p.slug.startsWith(skillPrefix) && p.slug !== `${prefix}/${args.name}/SKILL`
|
|
4218
|
+
).map((p) => p.slug.slice(skillPrefix.length));
|
|
4340
4219
|
let result = isDraft ? `[DRAFT SKILL]
|
|
4341
4220
|
|
|
4342
|
-
${
|
|
4221
|
+
${body}` : body;
|
|
4343
4222
|
if (refs.length > 0) {
|
|
4344
4223
|
result += `
|
|
4345
4224
|
|
|
4346
4225
|
---
|
|
4347
4226
|
Available references: ${refs.join(", ")}`;
|
|
4348
4227
|
}
|
|
4349
|
-
const
|
|
4350
|
-
|
|
4351
|
-
auth.instanceUrl,
|
|
4352
|
-
auth.token,
|
|
4353
|
-
scope,
|
|
4354
|
-
id,
|
|
4355
|
-
indexSlug
|
|
4356
|
-
);
|
|
4357
|
-
const entry = indexEntries.find((e) => e.name === args.name);
|
|
4358
|
-
if (entry?.snippetId) {
|
|
4228
|
+
const hasBundle = refs.some((r) => r === "bundle");
|
|
4229
|
+
if (hasBundle) {
|
|
4359
4230
|
result += `
|
|
4360
4231
|
|
|
4361
|
-
|
|
4362
|
-
This skill has executable scripts in snippet #${entry.snippetId}.`;
|
|
4232
|
+
This skill has executable scripts.`;
|
|
4363
4233
|
result += `
|
|
4364
|
-
Run \`gitlab_skill_setup(name="${args.name}")\` to extract them
|
|
4234
|
+
Run \`gitlab_skill_setup(name="${args.name}")\` to extract them locally.`;
|
|
4365
4235
|
}
|
|
4366
4236
|
return result;
|
|
4367
4237
|
} catch {
|
|
@@ -4372,13 +4242,13 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4372
4242
|
}
|
|
4373
4243
|
}),
|
|
4374
4244
|
gitlab_skill_save: (0, import_plugin6.tool)({
|
|
4375
|
-
description: "Create or update a skill.\
|
|
4245
|
+
description: "Create or update a skill.\nUse draft=true for skills that haven't been proven yet.",
|
|
4376
4246
|
args: {
|
|
4377
4247
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4378
4248
|
name: z6.string().describe('Skill name (e.g., "incident-retro")'),
|
|
4379
4249
|
content: z6.string().describe("Skill content in markdown"),
|
|
4380
|
-
description: z6.string().describe("Short description
|
|
4381
|
-
draft: z6.boolean().optional().describe("Save as draft
|
|
4250
|
+
description: z6.string().describe("Short description (1-2 sentences)"),
|
|
4251
|
+
draft: z6.boolean().optional().describe("Save as draft (default: false)"),
|
|
4382
4252
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4383
4253
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4384
4254
|
},
|
|
@@ -4386,31 +4256,25 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4386
4256
|
const auth = authAndValidate(args.project_id);
|
|
4387
4257
|
const { scope, id } = resolveScope2(args);
|
|
4388
4258
|
const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4389
|
-
const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4390
4259
|
const slug = `${prefix}/${args.name}/SKILL`;
|
|
4391
|
-
const
|
|
4260
|
+
const body = formatFrontmatter(
|
|
4261
|
+
{ name: args.name, description: args.description, source: "project" },
|
|
4262
|
+
args.content
|
|
4263
|
+
);
|
|
4392
4264
|
try {
|
|
4393
|
-
await upsertPage(auth.instanceUrl, auth.token, scope, id, slug,
|
|
4394
|
-
|
|
4395
|
-
name: args.name,
|
|
4396
|
-
description: args.description,
|
|
4397
|
-
source: "project",
|
|
4398
|
-
draft: !!args.draft
|
|
4399
|
-
});
|
|
4400
|
-
return `Saved ${label}skill: ${args.name}`;
|
|
4265
|
+
await upsertPage(auth.instanceUrl, auth.token, scope, id, slug, body);
|
|
4266
|
+
return `Saved ${args.draft ? "draft " : ""}skill: ${args.name}`;
|
|
4401
4267
|
} catch (err) {
|
|
4402
4268
|
return `Error saving skill: ${err.message}`;
|
|
4403
4269
|
}
|
|
4404
4270
|
}
|
|
4405
4271
|
}),
|
|
4406
4272
|
gitlab_skill_promote: (0, import_plugin6.tool)({
|
|
4407
|
-
description: "Promote a skill.\nDefault (target='published'): moves a draft
|
|
4273
|
+
description: "Promote a skill.\nDefault (target='published'): moves a draft to published.\nTarget 'group': moves a project skill to the group wiki.",
|
|
4408
4274
|
args: {
|
|
4409
4275
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4410
4276
|
name: z6.string().describe("Skill name to promote"),
|
|
4411
|
-
target: z6.enum(["published", "group"]).optional().describe(
|
|
4412
|
-
'Promotion target: "published" (default, draft\u2192published) or "group" (project\u2192group wiki)'
|
|
4413
|
-
),
|
|
4277
|
+
target: z6.enum(["published", "group"]).optional().describe('"published" (default) or "group"'),
|
|
4414
4278
|
group_id: z6.string().optional().describe("Group path (required when target is group)"),
|
|
4415
4279
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)")
|
|
4416
4280
|
},
|
|
@@ -4418,9 +4282,7 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4418
4282
|
const auth = authAndValidate(args.project_id);
|
|
4419
4283
|
const promotionTarget = args.target ?? "published";
|
|
4420
4284
|
if (promotionTarget === "group") {
|
|
4421
|
-
if (!args.group_id)
|
|
4422
|
-
return 'Error: group_id is required when target is "group".';
|
|
4423
|
-
}
|
|
4285
|
+
if (!args.group_id) return 'Error: group_id is required when target is "group".';
|
|
4424
4286
|
const projectScope = resolveScope2(args);
|
|
4425
4287
|
try {
|
|
4426
4288
|
const pages = await listWikiPages(
|
|
@@ -4431,11 +4293,9 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4431
4293
|
true
|
|
4432
4294
|
);
|
|
4433
4295
|
const skillPrefix = `${SKILLS_PREFIX}/${args.name}/`;
|
|
4434
|
-
const skillPages = pages.filter(
|
|
4435
|
-
(p) => p.slug.startsWith(skillPrefix) && p.content
|
|
4436
|
-
);
|
|
4296
|
+
const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix) && p.content);
|
|
4437
4297
|
if (skillPages.length === 0) {
|
|
4438
|
-
return `Skill "${args.name}" not found in project
|
|
4298
|
+
return `Skill "${args.name}" not found in project.`;
|
|
4439
4299
|
}
|
|
4440
4300
|
for (const page of skillPages) {
|
|
4441
4301
|
await upsertPage(
|
|
@@ -4447,54 +4307,6 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4447
4307
|
page.content
|
|
4448
4308
|
);
|
|
4449
4309
|
}
|
|
4450
|
-
const projectIndex = await readIndex(
|
|
4451
|
-
auth.instanceUrl,
|
|
4452
|
-
auth.token,
|
|
4453
|
-
projectScope.scope,
|
|
4454
|
-
projectScope.id,
|
|
4455
|
-
SKILLS_INDEX
|
|
4456
|
-
);
|
|
4457
|
-
const entry = projectIndex.find((e) => e.name === args.name);
|
|
4458
|
-
const description = entry?.description ?? "(promoted from project)";
|
|
4459
|
-
if (entry?.snippetId) {
|
|
4460
|
-
const bundleFiles = await listSnippetFiles(
|
|
4461
|
-
auth.instanceUrl,
|
|
4462
|
-
auth.token,
|
|
4463
|
-
args.project_id,
|
|
4464
|
-
entry.snippetId
|
|
4465
|
-
);
|
|
4466
|
-
for (const bf of bundleFiles) {
|
|
4467
|
-
const raw = await getSnippetFileRaw(
|
|
4468
|
-
auth.instanceUrl,
|
|
4469
|
-
auth.token,
|
|
4470
|
-
args.project_id,
|
|
4471
|
-
entry.snippetId,
|
|
4472
|
-
bf.path
|
|
4473
|
-
);
|
|
4474
|
-
await upsertPage(
|
|
4475
|
-
auth.instanceUrl,
|
|
4476
|
-
auth.token,
|
|
4477
|
-
"groups",
|
|
4478
|
-
args.group_id,
|
|
4479
|
-
`${SKILLS_PREFIX}/${args.name}/bundle/${bf.path}`,
|
|
4480
|
-
raw
|
|
4481
|
-
);
|
|
4482
|
-
}
|
|
4483
|
-
}
|
|
4484
|
-
await upsertIndexEntry(
|
|
4485
|
-
auth.instanceUrl,
|
|
4486
|
-
auth.token,
|
|
4487
|
-
"groups",
|
|
4488
|
-
args.group_id,
|
|
4489
|
-
SKILLS_INDEX,
|
|
4490
|
-
{
|
|
4491
|
-
name: args.name,
|
|
4492
|
-
description,
|
|
4493
|
-
source: `project:${args.project_id}`,
|
|
4494
|
-
snippetId: entry?.snippetId,
|
|
4495
|
-
draft: false
|
|
4496
|
-
}
|
|
4497
|
-
);
|
|
4498
4310
|
for (const page of skillPages) {
|
|
4499
4311
|
await deleteWikiPage(
|
|
4500
4312
|
auth.instanceUrl,
|
|
@@ -4504,55 +4316,30 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4504
4316
|
page.slug
|
|
4505
4317
|
);
|
|
4506
4318
|
}
|
|
4507
|
-
|
|
4508
|
-
try {
|
|
4509
|
-
await deleteProjectSnippet(
|
|
4510
|
-
auth.instanceUrl,
|
|
4511
|
-
auth.token,
|
|
4512
|
-
args.project_id,
|
|
4513
|
-
entry.snippetId
|
|
4514
|
-
);
|
|
4515
|
-
} catch {
|
|
4516
|
-
}
|
|
4517
|
-
}
|
|
4518
|
-
await removeIndexEntry(
|
|
4519
|
-
auth.instanceUrl,
|
|
4520
|
-
auth.token,
|
|
4521
|
-
projectScope.scope,
|
|
4522
|
-
projectScope.id,
|
|
4523
|
-
SKILLS_INDEX,
|
|
4524
|
-
args.name
|
|
4525
|
-
);
|
|
4526
|
-
return `Promoted skill "${args.name}" to group "${args.group_id}". ${skillPages.length} page(s) moved (removed from project).`;
|
|
4319
|
+
return `Promoted skill "${args.name}" to group "${args.group_id}". ${skillPages.length} page(s) moved.`;
|
|
4527
4320
|
} catch (err) {
|
|
4528
4321
|
return `Error promoting skill to group: ${err.message}`;
|
|
4529
4322
|
}
|
|
4530
4323
|
}
|
|
4531
4324
|
const { scope, id } = resolveScope2(args);
|
|
4532
4325
|
try {
|
|
4533
|
-
const pages = await listWikiPages(
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4326
|
+
const pages = await listWikiPages(
|
|
4327
|
+
auth.instanceUrl,
|
|
4328
|
+
auth.token,
|
|
4329
|
+
scope,
|
|
4330
|
+
id,
|
|
4331
|
+
true
|
|
4537
4332
|
);
|
|
4333
|
+
const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
|
|
4334
|
+
const draftPages = pages.filter((p) => p.slug.startsWith(draftPrefix) && p.content);
|
|
4538
4335
|
if (draftPages.length === 0) {
|
|
4539
|
-
return `Draft skill "${args.name}" not found
|
|
4336
|
+
return `Draft skill "${args.name}" not found.`;
|
|
4540
4337
|
}
|
|
4541
|
-
const draftIndex = await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX);
|
|
4542
|
-
const entry = draftIndex.find((e) => e.name === args.name);
|
|
4543
|
-
const description = entry?.description ?? "(promoted from draft)";
|
|
4544
4338
|
for (const page of draftPages) {
|
|
4545
4339
|
const newSlug = page.slug.replace(DRAFTS_PREFIX, SKILLS_PREFIX);
|
|
4546
4340
|
await upsertPage(auth.instanceUrl, auth.token, scope, id, newSlug, page.content);
|
|
4547
4341
|
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
|
|
4548
4342
|
}
|
|
4549
|
-
await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX, args.name);
|
|
4550
|
-
await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX, {
|
|
4551
|
-
name: args.name,
|
|
4552
|
-
description,
|
|
4553
|
-
source: "project",
|
|
4554
|
-
draft: false
|
|
4555
|
-
});
|
|
4556
4343
|
return `Promoted skill "${args.name}" from draft to published.`;
|
|
4557
4344
|
} catch (err) {
|
|
4558
4345
|
return `Error promoting skill: ${err.message}`;
|
|
@@ -4560,11 +4347,11 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4560
4347
|
}
|
|
4561
4348
|
}),
|
|
4562
4349
|
gitlab_skill_discover: (0, import_plugin6.tool)({
|
|
4563
|
-
description: "Search for skills in
|
|
4350
|
+
description: "Search for skills in skills.sh and optionally a group wiki.\nUse gitlab_skill_install to install a discovered skill.",
|
|
4564
4351
|
args: {
|
|
4565
|
-
query: z6.string().describe("Search query
|
|
4566
|
-
project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group
|
|
4567
|
-
group_id: z6.string().optional().describe("Group path to search
|
|
4352
|
+
query: z6.string().describe("Search query"),
|
|
4353
|
+
project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group search."),
|
|
4354
|
+
group_id: z6.string().optional().describe("Group path to search")
|
|
4568
4355
|
},
|
|
4569
4356
|
execute: async (args) => {
|
|
4570
4357
|
let auth = null;
|
|
@@ -4576,15 +4363,15 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4576
4363
|
const sections = [];
|
|
4577
4364
|
if (args.group_id && auth) {
|
|
4578
4365
|
try {
|
|
4579
|
-
const
|
|
4366
|
+
const skills = await listSkills(
|
|
4580
4367
|
auth.instanceUrl,
|
|
4581
4368
|
auth.token,
|
|
4582
4369
|
"groups",
|
|
4583
4370
|
args.group_id,
|
|
4584
|
-
|
|
4371
|
+
SKILLS_PREFIX
|
|
4585
4372
|
);
|
|
4586
4373
|
const q = args.query.toLowerCase();
|
|
4587
|
-
const matches =
|
|
4374
|
+
const matches = skills.filter(
|
|
4588
4375
|
(e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
|
|
4589
4376
|
);
|
|
4590
4377
|
if (matches.length > 0) {
|
|
@@ -4613,19 +4400,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4613
4400
|
);
|
|
4614
4401
|
}
|
|
4615
4402
|
if (sections.length === 0) {
|
|
4616
|
-
return `No skills found matching "${args.query}"
|
|
4403
|
+
return `No skills found matching "${args.query}".`;
|
|
4617
4404
|
}
|
|
4618
4405
|
return sections.join("\n\n---\n\n");
|
|
4619
4406
|
}
|
|
4620
4407
|
}),
|
|
4621
4408
|
gitlab_skill_install: (0, import_plugin6.tool)({
|
|
4622
|
-
description: "Install a skill from
|
|
4409
|
+
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.",
|
|
4623
4410
|
args: {
|
|
4624
4411
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4625
|
-
name: z6.string().describe(
|
|
4626
|
-
|
|
4627
|
-
),
|
|
4628
|
-
source: z6.enum(["group", "skills.sh"]).describe('Where to install from: "group" (group wiki) or "skills.sh" (public registry)'),
|
|
4412
|
+
name: z6.string().describe("Skill identifier"),
|
|
4413
|
+
source: z6.enum(["group", "skills.sh"]).describe("Where to install from"),
|
|
4629
4414
|
group_id: z6.string().optional().describe("Group path (required when source is group)"),
|
|
4630
4415
|
draft: z6.boolean().optional().describe("Install as draft (default: false)")
|
|
4631
4416
|
},
|
|
@@ -4633,24 +4418,32 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4633
4418
|
const auth = authAndValidate(args.project_id);
|
|
4634
4419
|
const projectScope = resolveScope2(args);
|
|
4635
4420
|
const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4636
|
-
const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4637
4421
|
if (args.source === "skills.sh") {
|
|
4638
4422
|
const downloaded = downloadSkillFromSkillsSh(args.name);
|
|
4639
4423
|
if (!downloaded) {
|
|
4640
|
-
return `Failed to download skill "${args.name}" from skills.sh
|
|
4424
|
+
return `Failed to download skill "${args.name}" from skills.sh.`;
|
|
4641
4425
|
}
|
|
4642
4426
|
try {
|
|
4427
|
+
const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
|
|
4428
|
+
const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
|
|
4429
|
+
const hasBundle = scriptFiles.length > 0;
|
|
4430
|
+
const skillBody = formatFrontmatter(
|
|
4431
|
+
{
|
|
4432
|
+
name: downloaded.name,
|
|
4433
|
+
description: downloaded.description,
|
|
4434
|
+
source: `skills.sh:${args.name}`
|
|
4435
|
+
},
|
|
4436
|
+
downloaded.content.replace(/^---[\s\S]*?---\s*\n/, "")
|
|
4437
|
+
);
|
|
4643
4438
|
await upsertPage(
|
|
4644
4439
|
auth.instanceUrl,
|
|
4645
4440
|
auth.token,
|
|
4646
4441
|
projectScope.scope,
|
|
4647
4442
|
projectScope.id,
|
|
4648
4443
|
`${targetPrefix}/${downloaded.name}/SKILL`,
|
|
4649
|
-
|
|
4444
|
+
skillBody
|
|
4650
4445
|
);
|
|
4651
4446
|
let wikiCount = 1;
|
|
4652
|
-
const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
|
|
4653
|
-
const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
|
|
4654
4447
|
for (const file of mdFiles) {
|
|
4655
4448
|
const slug = `${targetPrefix}/${downloaded.name}/${file.path.replace(/\.[^.]+$/, "")}`;
|
|
4656
4449
|
await upsertPage(
|
|
@@ -4663,44 +4456,26 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4663
4456
|
);
|
|
4664
4457
|
wikiCount++;
|
|
4665
4458
|
}
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
const snippet = await createProjectSnippet(
|
|
4459
|
+
if (hasBundle) {
|
|
4460
|
+
const bundleSlug = `${targetPrefix}/${downloaded.name}/bundle`;
|
|
4461
|
+
await upsertPage(
|
|
4670
4462
|
auth.instanceUrl,
|
|
4671
4463
|
auth.token,
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
"private"
|
|
4464
|
+
projectScope.scope,
|
|
4465
|
+
projectScope.id,
|
|
4466
|
+
bundleSlug,
|
|
4467
|
+
packFiles(scriptFiles)
|
|
4677
4468
|
);
|
|
4678
|
-
|
|
4469
|
+
wikiCount++;
|
|
4679
4470
|
}
|
|
4680
|
-
await upsertIndexEntry(
|
|
4681
|
-
auth.instanceUrl,
|
|
4682
|
-
auth.token,
|
|
4683
|
-
projectScope.scope,
|
|
4684
|
-
projectScope.id,
|
|
4685
|
-
targetIndex,
|
|
4686
|
-
{
|
|
4687
|
-
name: downloaded.name,
|
|
4688
|
-
description: downloaded.description,
|
|
4689
|
-
source: `skills.sh:${args.name}`,
|
|
4690
|
-
snippetId,
|
|
4691
|
-
draft: !!args.draft
|
|
4692
|
-
}
|
|
4693
|
-
);
|
|
4694
4471
|
const parts = [`${wikiCount} wiki page(s)`];
|
|
4695
|
-
if (
|
|
4696
|
-
return `Installed skill "${downloaded.name}" from skills.sh. ${parts.join(", ")}. Use gitlab_skill_setup to extract scripts
|
|
4472
|
+
if (hasBundle) parts.push(`${scriptFiles.length} bundled script(s)`);
|
|
4473
|
+
return `Installed skill "${downloaded.name}" from skills.sh. ${parts.join(", ")}. Use gitlab_skill_setup to extract scripts.`;
|
|
4697
4474
|
} catch (err) {
|
|
4698
|
-
return `Error installing
|
|
4475
|
+
return `Error installing from skills.sh: ${err.message}`;
|
|
4699
4476
|
}
|
|
4700
4477
|
}
|
|
4701
|
-
if (!args.group_id)
|
|
4702
|
-
return 'Error: group_id is required when source is "group".';
|
|
4703
|
-
}
|
|
4478
|
+
if (!args.group_id) return 'Error: group_id is required when source is "group".';
|
|
4704
4479
|
try {
|
|
4705
4480
|
const groupPages = await listWikiPages(
|
|
4706
4481
|
auth.instanceUrl,
|
|
@@ -4710,9 +4485,7 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4710
4485
|
true
|
|
4711
4486
|
);
|
|
4712
4487
|
const sourcePrefix = `${SKILLS_PREFIX}/${args.name}/`;
|
|
4713
|
-
const skillPages = groupPages.filter(
|
|
4714
|
-
(p) => p.slug.startsWith(sourcePrefix) && p.content
|
|
4715
|
-
);
|
|
4488
|
+
const skillPages = groupPages.filter((p) => p.slug.startsWith(sourcePrefix) && p.content);
|
|
4716
4489
|
if (skillPages.length === 0) {
|
|
4717
4490
|
return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
|
|
4718
4491
|
}
|
|
@@ -4727,39 +4500,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4727
4500
|
page.content
|
|
4728
4501
|
);
|
|
4729
4502
|
}
|
|
4730
|
-
|
|
4731
|
-
auth.instanceUrl,
|
|
4732
|
-
auth.token,
|
|
4733
|
-
"groups",
|
|
4734
|
-
args.group_id,
|
|
4735
|
-
SKILLS_INDEX
|
|
4736
|
-
);
|
|
4737
|
-
const entry = groupIndex.find((e) => e.name === args.name);
|
|
4738
|
-
const description = entry?.description ?? "(installed from group)";
|
|
4739
|
-
await upsertIndexEntry(
|
|
4740
|
-
auth.instanceUrl,
|
|
4741
|
-
auth.token,
|
|
4742
|
-
projectScope.scope,
|
|
4743
|
-
projectScope.id,
|
|
4744
|
-
targetIndex,
|
|
4745
|
-
{
|
|
4746
|
-
name: args.name,
|
|
4747
|
-
description,
|
|
4748
|
-
source: `group:${args.group_id}`,
|
|
4749
|
-
draft: !!args.draft
|
|
4750
|
-
}
|
|
4751
|
-
);
|
|
4752
|
-
return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s) copied.`;
|
|
4503
|
+
return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s).`;
|
|
4753
4504
|
} catch (err) {
|
|
4754
|
-
return `Error installing
|
|
4505
|
+
return `Error installing from group: ${err.message}`;
|
|
4755
4506
|
}
|
|
4756
4507
|
}
|
|
4757
4508
|
}),
|
|
4758
4509
|
gitlab_skill_setup: (0, import_plugin6.tool)({
|
|
4759
|
-
description: "Extract a skill to
|
|
4510
|
+
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.",
|
|
4760
4511
|
args: {
|
|
4761
4512
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4762
|
-
name: z6.string().describe("Skill name to set up
|
|
4513
|
+
name: z6.string().describe("Skill name to set up"),
|
|
4763
4514
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4764
4515
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4765
4516
|
},
|
|
@@ -4767,29 +4518,35 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4767
4518
|
const auth = authAndValidate(args.project_id);
|
|
4768
4519
|
const { scope, id } = resolveScope2(args);
|
|
4769
4520
|
const workDir = ctx.getDirectory();
|
|
4770
|
-
const targetDir = (0,
|
|
4521
|
+
const targetDir = (0, import_path3.join)(workDir, ".agents", "skills", args.name);
|
|
4771
4522
|
try {
|
|
4772
|
-
let
|
|
4523
|
+
let skillPage = null;
|
|
4773
4524
|
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4774
4525
|
try {
|
|
4775
|
-
|
|
4526
|
+
skillPage = await getWikiPage(
|
|
4776
4527
|
auth.instanceUrl,
|
|
4777
4528
|
auth.token,
|
|
4778
4529
|
scope,
|
|
4779
4530
|
id,
|
|
4780
4531
|
`${prefix}/${args.name}/SKILL`
|
|
4781
4532
|
);
|
|
4782
|
-
skillContent = page.content;
|
|
4783
4533
|
break;
|
|
4784
4534
|
} catch {
|
|
4785
4535
|
}
|
|
4786
4536
|
}
|
|
4787
|
-
if (!
|
|
4788
|
-
return `Skill "${args.name}" not found
|
|
4537
|
+
if (!skillPage) {
|
|
4538
|
+
return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
|
|
4789
4539
|
}
|
|
4790
|
-
|
|
4791
|
-
(0,
|
|
4792
|
-
|
|
4540
|
+
const { body } = parseFrontmatter(skillPage.content);
|
|
4541
|
+
(0, import_fs3.mkdirSync)(targetDir, { recursive: true });
|
|
4542
|
+
(0, import_fs3.writeFileSync)((0, import_path3.join)(targetDir, "SKILL.md"), body);
|
|
4543
|
+
const pages = await listWikiPages(
|
|
4544
|
+
auth.instanceUrl,
|
|
4545
|
+
auth.token,
|
|
4546
|
+
scope,
|
|
4547
|
+
id,
|
|
4548
|
+
true
|
|
4549
|
+
);
|
|
4793
4550
|
let refCount = 0;
|
|
4794
4551
|
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4795
4552
|
const skillPagePrefix = `${prefix}/${args.name}/`;
|
|
@@ -4798,78 +4555,51 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4798
4555
|
);
|
|
4799
4556
|
for (const page of extraPages) {
|
|
4800
4557
|
const relPath = page.slug.slice(skillPagePrefix.length);
|
|
4801
|
-
const filePath = (0,
|
|
4802
|
-
(0,
|
|
4803
|
-
(0,
|
|
4558
|
+
const filePath = (0, import_path3.join)(targetDir, `${relPath}.md`);
|
|
4559
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
|
|
4560
|
+
(0, import_fs3.writeFileSync)(filePath, page.content);
|
|
4804
4561
|
refCount++;
|
|
4805
4562
|
}
|
|
4806
4563
|
}
|
|
4807
4564
|
let scriptCount = 0;
|
|
4808
|
-
const
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
scope,
|
|
4812
|
-
id,
|
|
4813
|
-
SKILLS_INDEX
|
|
4814
|
-
);
|
|
4815
|
-
const draftsEntries = await readIndex(
|
|
4816
|
-
auth.instanceUrl,
|
|
4817
|
-
auth.token,
|
|
4818
|
-
scope,
|
|
4819
|
-
id,
|
|
4820
|
-
DRAFTS_INDEX
|
|
4821
|
-
);
|
|
4822
|
-
const entry = [...indexEntries, ...draftsEntries].find((e) => e.name === args.name);
|
|
4823
|
-
if (entry?.snippetId) {
|
|
4824
|
-
const bundleFiles = await listSnippetFiles(
|
|
4825
|
-
auth.instanceUrl,
|
|
4826
|
-
auth.token,
|
|
4827
|
-
args.project_id,
|
|
4828
|
-
entry.snippetId
|
|
4829
|
-
);
|
|
4830
|
-
for (const bf of bundleFiles) {
|
|
4831
|
-
const raw = await getSnippetFileRaw(
|
|
4565
|
+
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4566
|
+
try {
|
|
4567
|
+
const bundlePage = await getWikiPage(
|
|
4832
4568
|
auth.instanceUrl,
|
|
4833
4569
|
auth.token,
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4570
|
+
scope,
|
|
4571
|
+
id,
|
|
4572
|
+
`${prefix}/${args.name}/bundle`
|
|
4837
4573
|
);
|
|
4838
|
-
if (
|
|
4839
|
-
const
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
(0,
|
|
4843
|
-
(0,
|
|
4844
|
-
if (file.path.endsWith(".sh")) {
|
|
4845
|
-
(0, import_fs2.chmodSync)(filePath, 493);
|
|
4846
|
-
}
|
|
4574
|
+
if (bundlePage.content) {
|
|
4575
|
+
for (const file of unpackFiles(bundlePage.content)) {
|
|
4576
|
+
const filePath = (0, import_path3.join)(targetDir, file.path);
|
|
4577
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
|
|
4578
|
+
(0, import_fs3.writeFileSync)(filePath, file.content);
|
|
4579
|
+
if (file.path.endsWith(".sh")) (0, import_fs3.chmodSync)(filePath, 493);
|
|
4847
4580
|
scriptCount++;
|
|
4848
4581
|
}
|
|
4849
|
-
} else {
|
|
4850
|
-
const filePath = (0, import_path2.join)(targetDir, bf.path);
|
|
4851
|
-
(0, import_fs2.mkdirSync)((0, import_path2.dirname)(filePath), { recursive: true });
|
|
4852
|
-
(0, import_fs2.writeFileSync)(filePath, raw);
|
|
4853
|
-
scriptCount++;
|
|
4854
4582
|
}
|
|
4583
|
+
break;
|
|
4584
|
+
} catch {
|
|
4855
4585
|
}
|
|
4856
4586
|
}
|
|
4857
4587
|
ensureGitignore(workDir);
|
|
4858
4588
|
const parts = ["SKILL.md"];
|
|
4859
4589
|
if (refCount > 0) parts.push(`${refCount} reference(s)`);
|
|
4860
4590
|
if (scriptCount > 0) parts.push(`${scriptCount} script(s)`);
|
|
4861
|
-
return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")})
|
|
4591
|
+
return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")}).`;
|
|
4862
4592
|
} catch (err) {
|
|
4863
4593
|
return `Error setting up skill: ${err.message}`;
|
|
4864
4594
|
}
|
|
4865
4595
|
}
|
|
4866
4596
|
}),
|
|
4867
4597
|
gitlab_skill_delete: (0, import_plugin6.tool)({
|
|
4868
|
-
description: "Delete a skill and
|
|
4598
|
+
description: "Delete a skill and its associated snippet.\nRemoves all wiki pages under the skill directory.",
|
|
4869
4599
|
args: {
|
|
4870
4600
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4871
4601
|
name: z6.string().describe("Skill name to delete"),
|
|
4872
|
-
draft: z6.boolean().optional().describe("Delete from drafts
|
|
4602
|
+
draft: z6.boolean().optional().describe("Delete from drafts (default: false)"),
|
|
4873
4603
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4874
4604
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4875
4605
|
},
|
|
@@ -4877,36 +4607,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4877
4607
|
const auth = authAndValidate(args.project_id);
|
|
4878
4608
|
const { scope, id } = resolveScope2(args);
|
|
4879
4609
|
const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4880
|
-
const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4881
4610
|
const skillPrefix = `${prefix}/${args.name}/`;
|
|
4882
4611
|
try {
|
|
4883
4612
|
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
4884
4613
|
const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix));
|
|
4885
4614
|
if (skillPages.length === 0) {
|
|
4886
|
-
return `Skill "${args.name}" not found
|
|
4615
|
+
return `Skill "${args.name}" not found.`;
|
|
4887
4616
|
}
|
|
4888
4617
|
for (const page of skillPages) {
|
|
4889
4618
|
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
|
|
4890
4619
|
}
|
|
4891
|
-
|
|
4892
|
-
const entry = indexEntries.find((e) => e.name === args.name);
|
|
4893
|
-
let snippetDeleted = false;
|
|
4894
|
-
if (entry?.snippetId) {
|
|
4895
|
-
try {
|
|
4896
|
-
await deleteProjectSnippet(
|
|
4897
|
-
auth.instanceUrl,
|
|
4898
|
-
auth.token,
|
|
4899
|
-
args.project_id,
|
|
4900
|
-
entry.snippetId
|
|
4901
|
-
);
|
|
4902
|
-
snippetDeleted = true;
|
|
4903
|
-
} catch {
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, args.name);
|
|
4907
|
-
const parts = [`${skillPages.length} wiki page(s)`];
|
|
4908
|
-
if (snippetDeleted) parts.push("snippet");
|
|
4909
|
-
return `Deleted skill "${args.name}" (${parts.join(" + ")} removed).`;
|
|
4620
|
+
return `Deleted skill "${args.name}" (${skillPages.length} page(s) removed).`;
|
|
4910
4621
|
} catch (err) {
|
|
4911
4622
|
return `Error deleting skill: ${err.message}`;
|
|
4912
4623
|
}
|