opencode-gitlab-dap 1.16.3 → 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 -410
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +211 -411
- 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,172 +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 sleep2(ms) {
|
|
3929
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3930
|
-
}
|
|
3931
|
-
async function writeIndex(instanceUrl, token, scope, id, indexSlug, entries) {
|
|
3932
|
-
const content = formatIndex(entries) || "# Skills Registry";
|
|
3933
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
3934
|
-
try {
|
|
3935
|
-
await updateWikiPage(instanceUrl, token, scope, id, indexSlug, content);
|
|
3936
|
-
return;
|
|
3937
|
-
} catch (updateErr) {
|
|
3938
|
-
const msg = updateErr.message ?? "";
|
|
3939
|
-
if (msg.includes("not found") || msg.includes("404")) {
|
|
3940
|
-
await createWikiPage(instanceUrl, token, scope, id, indexSlug, content);
|
|
3941
|
-
return;
|
|
3942
|
-
}
|
|
3943
|
-
if (attempt < 2) {
|
|
3944
|
-
await sleep2(1e3 * (attempt + 1));
|
|
3945
|
-
continue;
|
|
3946
|
-
}
|
|
3947
|
-
throw updateErr;
|
|
3948
|
-
}
|
|
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;
|
|
3949
3885
|
}
|
|
3886
|
+
return { meta, body: match[2] };
|
|
3950
3887
|
}
|
|
3951
|
-
|
|
3952
|
-
const
|
|
3953
|
-
|
|
3954
|
-
if (
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
entries.push(entry);
|
|
3958
|
-
}
|
|
3959
|
-
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;
|
|
3960
3894
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
const
|
|
3964
|
-
|
|
3965
|
-
await writeIndex(instanceUrl, token, scope, id, indexSlug, filtered);
|
|
3966
|
-
}
|
|
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;
|
|
3967
3899
|
}
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
try {
|
|
3971
|
-
await updateWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
3972
|
-
return;
|
|
3973
|
-
} catch (updateErr) {
|
|
3974
|
-
const msg = updateErr.message ?? "";
|
|
3975
|
-
if (msg.includes("not found") || msg.includes("404")) {
|
|
3976
|
-
try {
|
|
3977
|
-
await createWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
3978
|
-
return;
|
|
3979
|
-
} catch (createErr) {
|
|
3980
|
-
if (attempt < 2 && (createErr.message?.includes("Duplicate") || createErr.message?.includes("reference"))) {
|
|
3981
|
-
await sleep2(1e3 * (attempt + 1));
|
|
3982
|
-
continue;
|
|
3983
|
-
}
|
|
3984
|
-
throw createErr;
|
|
3985
|
-
}
|
|
3986
|
-
}
|
|
3987
|
-
if (attempt < 2) {
|
|
3988
|
-
await sleep2(1e3 * (attempt + 1));
|
|
3989
|
-
continue;
|
|
3990
|
-
}
|
|
3991
|
-
throw updateErr;
|
|
3992
|
-
}
|
|
3993
|
-
}
|
|
3900
|
+
function isMarkdownFile(path) {
|
|
3901
|
+
return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
|
|
3994
3902
|
}
|
|
3995
|
-
var EMPTY_FILE_SENTINEL = "__EMPTY_FILE__";
|
|
3996
3903
|
function packFiles(files) {
|
|
3997
3904
|
const manifest = files.map((f) => ({
|
|
3998
3905
|
path: f.path,
|
|
3999
3906
|
content: f.content.trim() ? f.content : EMPTY_FILE_SENTINEL
|
|
4000
3907
|
}));
|
|
4001
|
-
|
|
4002
|
-
return gzipSync(Buffer.from(json, "utf-8")).toString("base64");
|
|
3908
|
+
return gzipSync(Buffer.from(JSON.stringify(manifest), "utf-8")).toString("base64");
|
|
4003
3909
|
}
|
|
4004
3910
|
function unpackFiles(packed) {
|
|
4005
3911
|
const json = gunzipSync(Buffer.from(packed, "base64")).toString("utf-8");
|
|
4006
|
-
|
|
4007
|
-
return manifest.map((f) => ({
|
|
3912
|
+
return JSON.parse(json).map((f) => ({
|
|
4008
3913
|
path: f.path,
|
|
4009
3914
|
content: f.content === EMPTY_FILE_SENTINEL ? "" : f.content
|
|
4010
3915
|
}));
|
|
4011
3916
|
}
|
|
4012
|
-
function
|
|
4013
|
-
return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
|
|
4014
|
-
}
|
|
4015
|
-
function isAgentsGitignored(dir) {
|
|
3917
|
+
function ensureGitignore(dir) {
|
|
4016
3918
|
try {
|
|
4017
3919
|
execSync("git check-ignore -q .agents", { cwd: dir, stdio: "pipe" });
|
|
4018
|
-
return
|
|
3920
|
+
return;
|
|
4019
3921
|
} catch {
|
|
4020
|
-
return false;
|
|
4021
3922
|
}
|
|
4022
|
-
}
|
|
4023
|
-
function ensureGitignore(dir) {
|
|
4024
|
-
if (isAgentsGitignored(dir)) return;
|
|
4025
3923
|
const gitignorePath = join2(dir, ".gitignore");
|
|
4026
3924
|
if (existsSync(gitignorePath)) {
|
|
4027
3925
|
const content = readFileSync2(gitignorePath, "utf-8");
|
|
@@ -4074,10 +3972,9 @@ function downloadSkillFromSkillsSh(identifier) {
|
|
|
4074
3972
|
if (dirs.length === 0) return null;
|
|
4075
3973
|
const skillName = dirs[0];
|
|
4076
3974
|
const skillDir = join2(agentsDir, skillName);
|
|
4077
|
-
const skillMd = join2(skillDir, "SKILL.md");
|
|
4078
3975
|
let mainContent;
|
|
4079
3976
|
try {
|
|
4080
|
-
mainContent = readFileSync2(
|
|
3977
|
+
mainContent = readFileSync2(join2(skillDir, "SKILL.md"), "utf-8");
|
|
4081
3978
|
} catch {
|
|
4082
3979
|
return null;
|
|
4083
3980
|
}
|
|
@@ -4086,8 +3983,7 @@ function downloadSkillFromSkillsSh(identifier) {
|
|
|
4086
3983
|
if (descMatch) {
|
|
4087
3984
|
description = descMatch[1].trim();
|
|
4088
3985
|
} else {
|
|
4089
|
-
|
|
4090
|
-
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);
|
|
4091
3987
|
}
|
|
4092
3988
|
const files = [];
|
|
4093
3989
|
const walkStack = [{ dir: skillDir, prefix: "" }];
|
|
@@ -4116,6 +4012,50 @@ function downloadSkillFromSkillsSh(identifier) {
|
|
|
4116
4012
|
}
|
|
4117
4013
|
}
|
|
4118
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
|
+
}
|
|
4119
4059
|
function makeSkillTools(ctx) {
|
|
4120
4060
|
function authAndValidate(projectId) {
|
|
4121
4061
|
const auth = ctx.ensureAuth();
|
|
@@ -4126,7 +4066,7 @@ function makeSkillTools(ctx) {
|
|
|
4126
4066
|
}
|
|
4127
4067
|
return {
|
|
4128
4068
|
gitlab_skill_list: tool6({
|
|
4129
|
-
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.",
|
|
4130
4070
|
args: {
|
|
4131
4071
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4132
4072
|
include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
|
|
@@ -4137,12 +4077,16 @@ function makeSkillTools(ctx) {
|
|
|
4137
4077
|
const auth = authAndValidate(args.project_id);
|
|
4138
4078
|
const { scope, id } = resolveScope2(args);
|
|
4139
4079
|
try {
|
|
4140
|
-
const published = await
|
|
4080
|
+
const published = await listSkills(
|
|
4081
|
+
auth.instanceUrl,
|
|
4082
|
+
auth.token,
|
|
4083
|
+
scope,
|
|
4084
|
+
id,
|
|
4085
|
+
SKILLS_PREFIX
|
|
4086
|
+
);
|
|
4141
4087
|
let drafts = [];
|
|
4142
4088
|
if (args.include_drafts) {
|
|
4143
|
-
drafts =
|
|
4144
|
-
(e) => ({ ...e, draft: true })
|
|
4145
|
-
);
|
|
4089
|
+
drafts = await listSkills(auth.instanceUrl, auth.token, scope, id, DRAFTS_PREFIX);
|
|
4146
4090
|
}
|
|
4147
4091
|
const all = [...published, ...drafts];
|
|
4148
4092
|
if (all.length === 0) return "No skills found. Use gitlab_skill_save to create one.";
|
|
@@ -4153,18 +4097,17 @@ function makeSkillTools(ctx) {
|
|
|
4153
4097
|
}
|
|
4154
4098
|
}),
|
|
4155
4099
|
gitlab_skill_load: tool6({
|
|
4156
|
-
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.",
|
|
4157
4101
|
args: {
|
|
4158
4102
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4159
|
-
name: z6.string().describe('Skill name (e.g., "incident-retro"
|
|
4103
|
+
name: z6.string().describe('Skill name (e.g., "incident-retro")'),
|
|
4160
4104
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4161
4105
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4162
4106
|
},
|
|
4163
4107
|
execute: async (args) => {
|
|
4164
4108
|
const auth = authAndValidate(args.project_id);
|
|
4165
4109
|
const { scope, id } = resolveScope2(args);
|
|
4166
|
-
const
|
|
4167
|
-
for (const prefix of prefixes) {
|
|
4110
|
+
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4168
4111
|
try {
|
|
4169
4112
|
const page = await getWikiPage(
|
|
4170
4113
|
auth.instanceUrl,
|
|
@@ -4173,36 +4116,28 @@ function makeSkillTools(ctx) {
|
|
|
4173
4116
|
id,
|
|
4174
4117
|
`${prefix}/${args.name}/SKILL`
|
|
4175
4118
|
);
|
|
4119
|
+
const { meta, body } = parseFrontmatter(page.content);
|
|
4120
|
+
const isDraft = prefix === DRAFTS_PREFIX;
|
|
4176
4121
|
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
4177
4122
|
const skillPrefix = `${prefix}/${args.name}/`;
|
|
4178
|
-
const
|
|
4179
|
-
|
|
4180
|
-
|
|
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));
|
|
4181
4126
|
let result = isDraft ? `[DRAFT SKILL]
|
|
4182
4127
|
|
|
4183
|
-
${
|
|
4128
|
+
${body}` : body;
|
|
4184
4129
|
if (refs.length > 0) {
|
|
4185
4130
|
result += `
|
|
4186
4131
|
|
|
4187
4132
|
---
|
|
4188
4133
|
Available references: ${refs.join(", ")}`;
|
|
4189
4134
|
}
|
|
4190
|
-
|
|
4191
|
-
const indexEntries = await readIndex(
|
|
4192
|
-
auth.instanceUrl,
|
|
4193
|
-
auth.token,
|
|
4194
|
-
scope,
|
|
4195
|
-
id,
|
|
4196
|
-
indexSlug
|
|
4197
|
-
);
|
|
4198
|
-
const entry = indexEntries.find((e) => e.name === args.name);
|
|
4199
|
-
if (entry?.snippetId) {
|
|
4135
|
+
if (meta.snippetId) {
|
|
4200
4136
|
result += `
|
|
4201
4137
|
|
|
4202
|
-
|
|
4203
|
-
This skill has executable scripts in snippet #${entry.snippetId}.`;
|
|
4138
|
+
This skill has executable scripts in snippet #${meta.snippetId}.`;
|
|
4204
4139
|
result += `
|
|
4205
|
-
Run \`gitlab_skill_setup(name="${args.name}")\` to extract them
|
|
4140
|
+
Run \`gitlab_skill_setup(name="${args.name}")\` to extract them locally.`;
|
|
4206
4141
|
}
|
|
4207
4142
|
return result;
|
|
4208
4143
|
} catch {
|
|
@@ -4213,13 +4148,13 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4213
4148
|
}
|
|
4214
4149
|
}),
|
|
4215
4150
|
gitlab_skill_save: tool6({
|
|
4216
|
-
description: "Create or update a skill.\
|
|
4151
|
+
description: "Create or update a skill.\nUse draft=true for skills that haven't been proven yet.",
|
|
4217
4152
|
args: {
|
|
4218
4153
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4219
4154
|
name: z6.string().describe('Skill name (e.g., "incident-retro")'),
|
|
4220
4155
|
content: z6.string().describe("Skill content in markdown"),
|
|
4221
|
-
description: z6.string().describe("Short description
|
|
4222
|
-
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)"),
|
|
4223
4158
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4224
4159
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4225
4160
|
},
|
|
@@ -4227,31 +4162,25 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4227
4162
|
const auth = authAndValidate(args.project_id);
|
|
4228
4163
|
const { scope, id } = resolveScope2(args);
|
|
4229
4164
|
const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4230
|
-
const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4231
4165
|
const slug = `${prefix}/${args.name}/SKILL`;
|
|
4232
|
-
const
|
|
4166
|
+
const body = formatFrontmatter(
|
|
4167
|
+
{ name: args.name, description: args.description, source: "project" },
|
|
4168
|
+
args.content
|
|
4169
|
+
);
|
|
4233
4170
|
try {
|
|
4234
|
-
await upsertPage(auth.instanceUrl, auth.token, scope, id, slug,
|
|
4235
|
-
|
|
4236
|
-
name: args.name,
|
|
4237
|
-
description: args.description,
|
|
4238
|
-
source: "project",
|
|
4239
|
-
draft: !!args.draft
|
|
4240
|
-
});
|
|
4241
|
-
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}`;
|
|
4242
4173
|
} catch (err) {
|
|
4243
4174
|
return `Error saving skill: ${err.message}`;
|
|
4244
4175
|
}
|
|
4245
4176
|
}
|
|
4246
4177
|
}),
|
|
4247
4178
|
gitlab_skill_promote: tool6({
|
|
4248
|
-
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.",
|
|
4249
4180
|
args: {
|
|
4250
4181
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4251
4182
|
name: z6.string().describe("Skill name to promote"),
|
|
4252
|
-
target: z6.enum(["published", "group"]).optional().describe(
|
|
4253
|
-
'Promotion target: "published" (default, draft\u2192published) or "group" (project\u2192group wiki)'
|
|
4254
|
-
),
|
|
4183
|
+
target: z6.enum(["published", "group"]).optional().describe('"published" (default) or "group"'),
|
|
4255
4184
|
group_id: z6.string().optional().describe("Group path (required when target is group)"),
|
|
4256
4185
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)")
|
|
4257
4186
|
},
|
|
@@ -4259,9 +4188,7 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4259
4188
|
const auth = authAndValidate(args.project_id);
|
|
4260
4189
|
const promotionTarget = args.target ?? "published";
|
|
4261
4190
|
if (promotionTarget === "group") {
|
|
4262
|
-
if (!args.group_id)
|
|
4263
|
-
return 'Error: group_id is required when target is "group".';
|
|
4264
|
-
}
|
|
4191
|
+
if (!args.group_id) return 'Error: group_id is required when target is "group".';
|
|
4265
4192
|
const projectScope = resolveScope2(args);
|
|
4266
4193
|
try {
|
|
4267
4194
|
const pages = await listWikiPages(
|
|
@@ -4272,11 +4199,9 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4272
4199
|
true
|
|
4273
4200
|
);
|
|
4274
4201
|
const skillPrefix = `${SKILLS_PREFIX}/${args.name}/`;
|
|
4275
|
-
const skillPages = pages.filter(
|
|
4276
|
-
(p) => p.slug.startsWith(skillPrefix) && p.content
|
|
4277
|
-
);
|
|
4202
|
+
const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix) && p.content);
|
|
4278
4203
|
if (skillPages.length === 0) {
|
|
4279
|
-
return `Skill "${args.name}" not found in project
|
|
4204
|
+
return `Skill "${args.name}" not found in project.`;
|
|
4280
4205
|
}
|
|
4281
4206
|
for (const page of skillPages) {
|
|
4282
4207
|
await upsertPage(
|
|
@@ -4288,54 +4213,6 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4288
4213
|
page.content
|
|
4289
4214
|
);
|
|
4290
4215
|
}
|
|
4291
|
-
const projectIndex = await readIndex(
|
|
4292
|
-
auth.instanceUrl,
|
|
4293
|
-
auth.token,
|
|
4294
|
-
projectScope.scope,
|
|
4295
|
-
projectScope.id,
|
|
4296
|
-
SKILLS_INDEX
|
|
4297
|
-
);
|
|
4298
|
-
const entry = projectIndex.find((e) => e.name === args.name);
|
|
4299
|
-
const description = entry?.description ?? "(promoted from project)";
|
|
4300
|
-
if (entry?.snippetId) {
|
|
4301
|
-
const bundleFiles = await listSnippetFiles(
|
|
4302
|
-
auth.instanceUrl,
|
|
4303
|
-
auth.token,
|
|
4304
|
-
args.project_id,
|
|
4305
|
-
entry.snippetId
|
|
4306
|
-
);
|
|
4307
|
-
for (const bf of bundleFiles) {
|
|
4308
|
-
const raw = await getSnippetFileRaw(
|
|
4309
|
-
auth.instanceUrl,
|
|
4310
|
-
auth.token,
|
|
4311
|
-
args.project_id,
|
|
4312
|
-
entry.snippetId,
|
|
4313
|
-
bf.path
|
|
4314
|
-
);
|
|
4315
|
-
await upsertPage(
|
|
4316
|
-
auth.instanceUrl,
|
|
4317
|
-
auth.token,
|
|
4318
|
-
"groups",
|
|
4319
|
-
args.group_id,
|
|
4320
|
-
`${SKILLS_PREFIX}/${args.name}/bundle/${bf.path}`,
|
|
4321
|
-
raw
|
|
4322
|
-
);
|
|
4323
|
-
}
|
|
4324
|
-
}
|
|
4325
|
-
await upsertIndexEntry(
|
|
4326
|
-
auth.instanceUrl,
|
|
4327
|
-
auth.token,
|
|
4328
|
-
"groups",
|
|
4329
|
-
args.group_id,
|
|
4330
|
-
SKILLS_INDEX,
|
|
4331
|
-
{
|
|
4332
|
-
name: args.name,
|
|
4333
|
-
description,
|
|
4334
|
-
source: `project:${args.project_id}`,
|
|
4335
|
-
snippetId: entry?.snippetId,
|
|
4336
|
-
draft: false
|
|
4337
|
-
}
|
|
4338
|
-
);
|
|
4339
4216
|
for (const page of skillPages) {
|
|
4340
4217
|
await deleteWikiPage(
|
|
4341
4218
|
auth.instanceUrl,
|
|
@@ -4345,55 +4222,30 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4345
4222
|
page.slug
|
|
4346
4223
|
);
|
|
4347
4224
|
}
|
|
4348
|
-
|
|
4349
|
-
try {
|
|
4350
|
-
await deleteProjectSnippet(
|
|
4351
|
-
auth.instanceUrl,
|
|
4352
|
-
auth.token,
|
|
4353
|
-
args.project_id,
|
|
4354
|
-
entry.snippetId
|
|
4355
|
-
);
|
|
4356
|
-
} catch {
|
|
4357
|
-
}
|
|
4358
|
-
}
|
|
4359
|
-
await removeIndexEntry(
|
|
4360
|
-
auth.instanceUrl,
|
|
4361
|
-
auth.token,
|
|
4362
|
-
projectScope.scope,
|
|
4363
|
-
projectScope.id,
|
|
4364
|
-
SKILLS_INDEX,
|
|
4365
|
-
args.name
|
|
4366
|
-
);
|
|
4367
|
-
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.`;
|
|
4368
4226
|
} catch (err) {
|
|
4369
4227
|
return `Error promoting skill to group: ${err.message}`;
|
|
4370
4228
|
}
|
|
4371
4229
|
}
|
|
4372
4230
|
const { scope, id } = resolveScope2(args);
|
|
4373
4231
|
try {
|
|
4374
|
-
const pages = await listWikiPages(
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4232
|
+
const pages = await listWikiPages(
|
|
4233
|
+
auth.instanceUrl,
|
|
4234
|
+
auth.token,
|
|
4235
|
+
scope,
|
|
4236
|
+
id,
|
|
4237
|
+
true
|
|
4378
4238
|
);
|
|
4239
|
+
const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
|
|
4240
|
+
const draftPages = pages.filter((p) => p.slug.startsWith(draftPrefix) && p.content);
|
|
4379
4241
|
if (draftPages.length === 0) {
|
|
4380
|
-
return `Draft skill "${args.name}" not found
|
|
4242
|
+
return `Draft skill "${args.name}" not found.`;
|
|
4381
4243
|
}
|
|
4382
|
-
const draftIndex = await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX);
|
|
4383
|
-
const entry = draftIndex.find((e) => e.name === args.name);
|
|
4384
|
-
const description = entry?.description ?? "(promoted from draft)";
|
|
4385
4244
|
for (const page of draftPages) {
|
|
4386
4245
|
const newSlug = page.slug.replace(DRAFTS_PREFIX, SKILLS_PREFIX);
|
|
4387
4246
|
await upsertPage(auth.instanceUrl, auth.token, scope, id, newSlug, page.content);
|
|
4388
4247
|
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
|
|
4389
4248
|
}
|
|
4390
|
-
await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX, args.name);
|
|
4391
|
-
await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX, {
|
|
4392
|
-
name: args.name,
|
|
4393
|
-
description,
|
|
4394
|
-
source: "project",
|
|
4395
|
-
draft: false
|
|
4396
|
-
});
|
|
4397
4249
|
return `Promoted skill "${args.name}" from draft to published.`;
|
|
4398
4250
|
} catch (err) {
|
|
4399
4251
|
return `Error promoting skill: ${err.message}`;
|
|
@@ -4401,11 +4253,11 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4401
4253
|
}
|
|
4402
4254
|
}),
|
|
4403
4255
|
gitlab_skill_discover: tool6({
|
|
4404
|
-
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.",
|
|
4405
4257
|
args: {
|
|
4406
|
-
query: z6.string().describe("Search query
|
|
4407
|
-
project_id: z6.string().optional().describe(PROJECT_ID_DESC2 + " Only needed for group
|
|
4408
|
-
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")
|
|
4409
4261
|
},
|
|
4410
4262
|
execute: async (args) => {
|
|
4411
4263
|
let auth = null;
|
|
@@ -4417,15 +4269,15 @@ Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skill
|
|
|
4417
4269
|
const sections = [];
|
|
4418
4270
|
if (args.group_id && auth) {
|
|
4419
4271
|
try {
|
|
4420
|
-
const
|
|
4272
|
+
const skills = await listSkills(
|
|
4421
4273
|
auth.instanceUrl,
|
|
4422
4274
|
auth.token,
|
|
4423
4275
|
"groups",
|
|
4424
4276
|
args.group_id,
|
|
4425
|
-
|
|
4277
|
+
SKILLS_PREFIX
|
|
4426
4278
|
);
|
|
4427
4279
|
const q = args.query.toLowerCase();
|
|
4428
|
-
const matches =
|
|
4280
|
+
const matches = skills.filter(
|
|
4429
4281
|
(e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
|
|
4430
4282
|
);
|
|
4431
4283
|
if (matches.length > 0) {
|
|
@@ -4454,19 +4306,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4454
4306
|
);
|
|
4455
4307
|
}
|
|
4456
4308
|
if (sections.length === 0) {
|
|
4457
|
-
return `No skills found matching "${args.query}"
|
|
4309
|
+
return `No skills found matching "${args.query}".`;
|
|
4458
4310
|
}
|
|
4459
4311
|
return sections.join("\n\n---\n\n");
|
|
4460
4312
|
}
|
|
4461
4313
|
}),
|
|
4462
4314
|
gitlab_skill_install: tool6({
|
|
4463
|
-
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.",
|
|
4464
4316
|
args: {
|
|
4465
4317
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4466
|
-
name: z6.string().describe(
|
|
4467
|
-
|
|
4468
|
-
),
|
|
4469
|
-
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"),
|
|
4470
4320
|
group_id: z6.string().optional().describe("Group path (required when source is group)"),
|
|
4471
4321
|
draft: z6.boolean().optional().describe("Install as draft (default: false)")
|
|
4472
4322
|
},
|
|
@@ -4474,24 +4324,45 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4474
4324
|
const auth = authAndValidate(args.project_id);
|
|
4475
4325
|
const projectScope = resolveScope2(args);
|
|
4476
4326
|
const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4477
|
-
const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4478
4327
|
if (args.source === "skills.sh") {
|
|
4479
4328
|
const downloaded = downloadSkillFromSkillsSh(args.name);
|
|
4480
4329
|
if (!downloaded) {
|
|
4481
|
-
return `Failed to download skill "${args.name}" from skills.sh
|
|
4330
|
+
return `Failed to download skill "${args.name}" from skills.sh.`;
|
|
4482
4331
|
}
|
|
4483
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
|
+
);
|
|
4484
4357
|
await upsertPage(
|
|
4485
4358
|
auth.instanceUrl,
|
|
4486
4359
|
auth.token,
|
|
4487
4360
|
projectScope.scope,
|
|
4488
4361
|
projectScope.id,
|
|
4489
4362
|
`${targetPrefix}/${downloaded.name}/SKILL`,
|
|
4490
|
-
|
|
4363
|
+
skillBody
|
|
4491
4364
|
);
|
|
4492
4365
|
let wikiCount = 1;
|
|
4493
|
-
const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
|
|
4494
|
-
const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
|
|
4495
4366
|
for (const file of mdFiles) {
|
|
4496
4367
|
const slug = `${targetPrefix}/${downloaded.name}/${file.path.replace(/\.[^.]+$/, "")}`;
|
|
4497
4368
|
await upsertPage(
|
|
@@ -4504,44 +4375,14 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4504
4375
|
);
|
|
4505
4376
|
wikiCount++;
|
|
4506
4377
|
}
|
|
4507
|
-
let snippetId;
|
|
4508
|
-
if (scriptFiles.length > 0) {
|
|
4509
|
-
const packed = packFiles(scriptFiles);
|
|
4510
|
-
const snippet = await createProjectSnippet(
|
|
4511
|
-
auth.instanceUrl,
|
|
4512
|
-
auth.token,
|
|
4513
|
-
args.project_id,
|
|
4514
|
-
`skill:${downloaded.name}`,
|
|
4515
|
-
`Scripts for skill "${downloaded.name}" (${scriptFiles.length} files, installed from skills.sh:${args.name})`,
|
|
4516
|
-
[{ file_path: `${downloaded.name}.bundle`, content: packed }],
|
|
4517
|
-
"private"
|
|
4518
|
-
);
|
|
4519
|
-
snippetId = snippet.id;
|
|
4520
|
-
}
|
|
4521
|
-
await upsertIndexEntry(
|
|
4522
|
-
auth.instanceUrl,
|
|
4523
|
-
auth.token,
|
|
4524
|
-
projectScope.scope,
|
|
4525
|
-
projectScope.id,
|
|
4526
|
-
targetIndex,
|
|
4527
|
-
{
|
|
4528
|
-
name: downloaded.name,
|
|
4529
|
-
description: downloaded.description,
|
|
4530
|
-
source: `skills.sh:${args.name}`,
|
|
4531
|
-
snippetId,
|
|
4532
|
-
draft: !!args.draft
|
|
4533
|
-
}
|
|
4534
|
-
);
|
|
4535
4378
|
const parts = [`${wikiCount} wiki page(s)`];
|
|
4536
4379
|
if (snippetId) parts.push(`snippet #${snippetId} with ${scriptFiles.length} script(s)`);
|
|
4537
|
-
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.`;
|
|
4538
4381
|
} catch (err) {
|
|
4539
|
-
return `Error installing
|
|
4382
|
+
return `Error installing from skills.sh: ${err.message}`;
|
|
4540
4383
|
}
|
|
4541
4384
|
}
|
|
4542
|
-
if (!args.group_id)
|
|
4543
|
-
return 'Error: group_id is required when source is "group".';
|
|
4544
|
-
}
|
|
4385
|
+
if (!args.group_id) return 'Error: group_id is required when source is "group".';
|
|
4545
4386
|
try {
|
|
4546
4387
|
const groupPages = await listWikiPages(
|
|
4547
4388
|
auth.instanceUrl,
|
|
@@ -4551,9 +4392,7 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4551
4392
|
true
|
|
4552
4393
|
);
|
|
4553
4394
|
const sourcePrefix = `${SKILLS_PREFIX}/${args.name}/`;
|
|
4554
|
-
const skillPages = groupPages.filter(
|
|
4555
|
-
(p) => p.slug.startsWith(sourcePrefix) && p.content
|
|
4556
|
-
);
|
|
4395
|
+
const skillPages = groupPages.filter((p) => p.slug.startsWith(sourcePrefix) && p.content);
|
|
4557
4396
|
if (skillPages.length === 0) {
|
|
4558
4397
|
return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
|
|
4559
4398
|
}
|
|
@@ -4568,39 +4407,17 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4568
4407
|
page.content
|
|
4569
4408
|
);
|
|
4570
4409
|
}
|
|
4571
|
-
|
|
4572
|
-
auth.instanceUrl,
|
|
4573
|
-
auth.token,
|
|
4574
|
-
"groups",
|
|
4575
|
-
args.group_id,
|
|
4576
|
-
SKILLS_INDEX
|
|
4577
|
-
);
|
|
4578
|
-
const entry = groupIndex.find((e) => e.name === args.name);
|
|
4579
|
-
const description = entry?.description ?? "(installed from group)";
|
|
4580
|
-
await upsertIndexEntry(
|
|
4581
|
-
auth.instanceUrl,
|
|
4582
|
-
auth.token,
|
|
4583
|
-
projectScope.scope,
|
|
4584
|
-
projectScope.id,
|
|
4585
|
-
targetIndex,
|
|
4586
|
-
{
|
|
4587
|
-
name: args.name,
|
|
4588
|
-
description,
|
|
4589
|
-
source: `group:${args.group_id}`,
|
|
4590
|
-
draft: !!args.draft
|
|
4591
|
-
}
|
|
4592
|
-
);
|
|
4593
|
-
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).`;
|
|
4594
4411
|
} catch (err) {
|
|
4595
|
-
return `Error installing
|
|
4412
|
+
return `Error installing from group: ${err.message}`;
|
|
4596
4413
|
}
|
|
4597
4414
|
}
|
|
4598
4415
|
}),
|
|
4599
4416
|
gitlab_skill_setup: tool6({
|
|
4600
|
-
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.",
|
|
4601
4418
|
args: {
|
|
4602
4419
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4603
|
-
name: z6.string().describe("Skill name to set up
|
|
4420
|
+
name: z6.string().describe("Skill name to set up"),
|
|
4604
4421
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4605
4422
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4606
4423
|
},
|
|
@@ -4608,29 +4425,35 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4608
4425
|
const auth = authAndValidate(args.project_id);
|
|
4609
4426
|
const { scope, id } = resolveScope2(args);
|
|
4610
4427
|
const workDir = ctx.getDirectory();
|
|
4611
|
-
const targetDir =
|
|
4428
|
+
const targetDir = join3(workDir, ".agents", "skills", args.name);
|
|
4612
4429
|
try {
|
|
4613
|
-
let
|
|
4430
|
+
let skillPage = null;
|
|
4614
4431
|
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4615
4432
|
try {
|
|
4616
|
-
|
|
4433
|
+
skillPage = await getWikiPage(
|
|
4617
4434
|
auth.instanceUrl,
|
|
4618
4435
|
auth.token,
|
|
4619
4436
|
scope,
|
|
4620
4437
|
id,
|
|
4621
4438
|
`${prefix}/${args.name}/SKILL`
|
|
4622
4439
|
);
|
|
4623
|
-
skillContent = page.content;
|
|
4624
4440
|
break;
|
|
4625
4441
|
} catch {
|
|
4626
4442
|
}
|
|
4627
4443
|
}
|
|
4628
|
-
if (!
|
|
4629
|
-
return `Skill "${args.name}" not found
|
|
4444
|
+
if (!skillPage) {
|
|
4445
|
+
return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
|
|
4630
4446
|
}
|
|
4447
|
+
const { meta, body } = parseFrontmatter(skillPage.content);
|
|
4631
4448
|
mkdirSync(targetDir, { recursive: true });
|
|
4632
|
-
|
|
4633
|
-
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
|
+
);
|
|
4634
4457
|
let refCount = 0;
|
|
4635
4458
|
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4636
4459
|
const skillPagePrefix = `${prefix}/${args.name}/`;
|
|
@@ -4639,59 +4462,36 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4639
4462
|
);
|
|
4640
4463
|
for (const page of extraPages) {
|
|
4641
4464
|
const relPath = page.slug.slice(skillPagePrefix.length);
|
|
4642
|
-
const filePath =
|
|
4465
|
+
const filePath = join3(targetDir, `${relPath}.md`);
|
|
4643
4466
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
4644
|
-
|
|
4467
|
+
writeFileSync2(filePath, page.content);
|
|
4645
4468
|
refCount++;
|
|
4646
4469
|
}
|
|
4647
4470
|
}
|
|
4648
4471
|
let scriptCount = 0;
|
|
4649
|
-
|
|
4650
|
-
auth.instanceUrl,
|
|
4651
|
-
auth.token,
|
|
4652
|
-
scope,
|
|
4653
|
-
id,
|
|
4654
|
-
SKILLS_INDEX
|
|
4655
|
-
);
|
|
4656
|
-
const draftsEntries = await readIndex(
|
|
4657
|
-
auth.instanceUrl,
|
|
4658
|
-
auth.token,
|
|
4659
|
-
scope,
|
|
4660
|
-
id,
|
|
4661
|
-
DRAFTS_INDEX
|
|
4662
|
-
);
|
|
4663
|
-
const entry = [...indexEntries, ...draftsEntries].find((e) => e.name === args.name);
|
|
4664
|
-
if (entry?.snippetId) {
|
|
4472
|
+
if (meta.snippetId) {
|
|
4665
4473
|
const bundleFiles = await listSnippetFiles(
|
|
4666
4474
|
auth.instanceUrl,
|
|
4667
4475
|
auth.token,
|
|
4668
4476
|
args.project_id,
|
|
4669
|
-
|
|
4477
|
+
meta.snippetId
|
|
4670
4478
|
);
|
|
4671
4479
|
for (const bf of bundleFiles) {
|
|
4672
4480
|
const raw = await getSnippetFileRaw(
|
|
4673
4481
|
auth.instanceUrl,
|
|
4674
4482
|
auth.token,
|
|
4675
4483
|
args.project_id,
|
|
4676
|
-
|
|
4484
|
+
meta.snippetId,
|
|
4677
4485
|
bf.path
|
|
4678
4486
|
);
|
|
4679
4487
|
if (bf.path.endsWith(".bundle")) {
|
|
4680
|
-
const
|
|
4681
|
-
|
|
4682
|
-
const filePath = join2(targetDir, file.path);
|
|
4488
|
+
for (const file of unpackFiles(raw)) {
|
|
4489
|
+
const filePath = join3(targetDir, file.path);
|
|
4683
4490
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
4684
|
-
|
|
4685
|
-
if (file.path.endsWith(".sh"))
|
|
4686
|
-
chmodSync(filePath, 493);
|
|
4687
|
-
}
|
|
4491
|
+
writeFileSync2(filePath, file.content);
|
|
4492
|
+
if (file.path.endsWith(".sh")) chmodSync(filePath, 493);
|
|
4688
4493
|
scriptCount++;
|
|
4689
4494
|
}
|
|
4690
|
-
} else {
|
|
4691
|
-
const filePath = join2(targetDir, bf.path);
|
|
4692
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
4693
|
-
writeFileSync(filePath, raw);
|
|
4694
|
-
scriptCount++;
|
|
4695
4495
|
}
|
|
4696
4496
|
}
|
|
4697
4497
|
}
|
|
@@ -4699,18 +4499,18 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4699
4499
|
const parts = ["SKILL.md"];
|
|
4700
4500
|
if (refCount > 0) parts.push(`${refCount} reference(s)`);
|
|
4701
4501
|
if (scriptCount > 0) parts.push(`${scriptCount} script(s)`);
|
|
4702
|
-
return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")})
|
|
4502
|
+
return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")}).`;
|
|
4703
4503
|
} catch (err) {
|
|
4704
4504
|
return `Error setting up skill: ${err.message}`;
|
|
4705
4505
|
}
|
|
4706
4506
|
}
|
|
4707
4507
|
}),
|
|
4708
4508
|
gitlab_skill_delete: tool6({
|
|
4709
|
-
description: "Delete a skill and
|
|
4509
|
+
description: "Delete a skill and its associated snippet.\nRemoves all wiki pages under the skill directory.",
|
|
4710
4510
|
args: {
|
|
4711
4511
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4712
4512
|
name: z6.string().describe("Skill name to delete"),
|
|
4713
|
-
draft: z6.boolean().optional().describe("Delete from drafts
|
|
4513
|
+
draft: z6.boolean().optional().describe("Delete from drafts (default: false)"),
|
|
4714
4514
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4715
4515
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4716
4516
|
},
|
|
@@ -4718,36 +4518,36 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4718
4518
|
const auth = authAndValidate(args.project_id);
|
|
4719
4519
|
const { scope, id } = resolveScope2(args);
|
|
4720
4520
|
const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4721
|
-
const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4722
4521
|
const skillPrefix = `${prefix}/${args.name}/`;
|
|
4723
4522
|
try {
|
|
4724
4523
|
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
4725
4524
|
const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix));
|
|
4726
4525
|
if (skillPages.length === 0) {
|
|
4727
|
-
return `Skill "${args.name}" not found
|
|
4526
|
+
return `Skill "${args.name}" not found.`;
|
|
4728
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);
|
|
4729
4536
|
for (const page of skillPages) {
|
|
4730
4537
|
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
|
|
4731
4538
|
}
|
|
4732
|
-
|
|
4733
|
-
const entry = indexEntries.find((e) => e.name === args.name);
|
|
4734
|
-
let snippetDeleted = false;
|
|
4735
|
-
if (entry?.snippetId) {
|
|
4539
|
+
if (meta.snippetId) {
|
|
4736
4540
|
try {
|
|
4737
4541
|
await deleteProjectSnippet(
|
|
4738
4542
|
auth.instanceUrl,
|
|
4739
4543
|
auth.token,
|
|
4740
4544
|
args.project_id,
|
|
4741
|
-
|
|
4545
|
+
meta.snippetId
|
|
4742
4546
|
);
|
|
4743
|
-
snippetDeleted = true;
|
|
4744
4547
|
} catch {
|
|
4745
4548
|
}
|
|
4746
4549
|
}
|
|
4747
|
-
|
|
4748
|
-
const parts = [`${skillPages.length} wiki page(s)`];
|
|
4749
|
-
if (snippetDeleted) parts.push("snippet");
|
|
4750
|
-
return `Deleted skill "${args.name}" (${parts.join(" + ")} removed).`;
|
|
4550
|
+
return `Deleted skill "${args.name}" (${skillPages.length} page(s)${meta.snippetId ? " + snippet" : ""}).`;
|
|
4751
4551
|
} catch (err) {
|
|
4752
4552
|
return `Error deleting skill: ${err.message}`;
|
|
4753
4553
|
}
|