opencode-gitlab-dap 1.13.0 → 1.14.1
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 +195 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +195 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3778,6 +3778,10 @@ ${e.content}`).join("\n\n---\n\n");
|
|
|
3778
3778
|
|
|
3779
3779
|
// src/tools/skill-tools.ts
|
|
3780
3780
|
import { tool as tool6 } from "@opencode-ai/plugin";
|
|
3781
|
+
import { execSync } from "child_process";
|
|
3782
|
+
import { mkdtempSync, readFileSync as readFileSync2, rmSync, readdirSync, statSync } from "fs";
|
|
3783
|
+
import { join as join2 } from "path";
|
|
3784
|
+
import { tmpdir } from "os";
|
|
3781
3785
|
var z6 = tool6.schema;
|
|
3782
3786
|
var PREFIX2 = "agents";
|
|
3783
3787
|
var SKILLS_PREFIX = `${PREFIX2}/skills`;
|
|
@@ -3892,9 +3896,21 @@ async function rebuildIndex(instanceUrl, token, scope, id, prefix, indexSlug) {
|
|
|
3892
3896
|
if (!dirty && added.length === 0) return currentEntries;
|
|
3893
3897
|
const kept = currentEntries.filter((e) => actual.has(e.name));
|
|
3894
3898
|
for (const name of added) {
|
|
3899
|
+
let description = "";
|
|
3900
|
+
try {
|
|
3901
|
+
const page = await getWikiPage(instanceUrl, token, scope, id, `${prefix}/${name}/SKILL`);
|
|
3902
|
+
const content = page.content ?? "";
|
|
3903
|
+
const fmMatch = content.match(/^---\s*\n[\s\S]*?description:\s*(.+)\n[\s\S]*?---/);
|
|
3904
|
+
if (fmMatch) {
|
|
3905
|
+
description = fmMatch[1].trim();
|
|
3906
|
+
} else {
|
|
3907
|
+
description = content.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim().slice(0, 200);
|
|
3908
|
+
}
|
|
3909
|
+
} catch {
|
|
3910
|
+
}
|
|
3895
3911
|
kept.push({
|
|
3896
3912
|
name,
|
|
3897
|
-
description: "(
|
|
3913
|
+
description: description || "(no description \u2014 update with gitlab_skill_save)",
|
|
3898
3914
|
draft: prefix === DRAFTS_PREFIX
|
|
3899
3915
|
});
|
|
3900
3916
|
}
|
|
@@ -3908,6 +3924,83 @@ async function upsertPage(instanceUrl, token, scope, id, slug, content) {
|
|
|
3908
3924
|
await createWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
3909
3925
|
}
|
|
3910
3926
|
}
|
|
3927
|
+
function searchSkillsSh(query) {
|
|
3928
|
+
try {
|
|
3929
|
+
const raw = execSync(`npx skills find ${JSON.stringify(query)}`, {
|
|
3930
|
+
timeout: 3e4,
|
|
3931
|
+
encoding: "utf-8",
|
|
3932
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3933
|
+
});
|
|
3934
|
+
const lines = raw.split("\n");
|
|
3935
|
+
const results = [];
|
|
3936
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3937
|
+
const clean = lines[i].replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
3938
|
+
const match = clean.match(/^(\S+\/\S+@\S+)\s+(.+installs?)$/);
|
|
3939
|
+
if (match) {
|
|
3940
|
+
const urlLine = (lines[i + 1] ?? "").replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
3941
|
+
const url = urlLine.startsWith("\u2514 ") ? urlLine.slice(2) : urlLine;
|
|
3942
|
+
results.push({ identifier: match[1], installs: match[2], url });
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
return results;
|
|
3946
|
+
} catch {
|
|
3947
|
+
return [];
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
function downloadSkillFromSkillsSh(identifier) {
|
|
3951
|
+
const tmp = mkdtempSync(join2(tmpdir(), "skill-install-"));
|
|
3952
|
+
try {
|
|
3953
|
+
execSync(`npx skills add ${JSON.stringify(identifier)} -y --copy`, {
|
|
3954
|
+
timeout: 6e4,
|
|
3955
|
+
cwd: tmp,
|
|
3956
|
+
encoding: "utf-8",
|
|
3957
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3958
|
+
});
|
|
3959
|
+
const agentsDir = join2(tmp, ".agents", "skills");
|
|
3960
|
+
if (!statSync(agentsDir).isDirectory()) return null;
|
|
3961
|
+
const dirs = readdirSync(agentsDir);
|
|
3962
|
+
if (dirs.length === 0) return null;
|
|
3963
|
+
const skillName = dirs[0];
|
|
3964
|
+
const skillDir = join2(agentsDir, skillName);
|
|
3965
|
+
const skillMd = join2(skillDir, "SKILL.md");
|
|
3966
|
+
let mainContent;
|
|
3967
|
+
try {
|
|
3968
|
+
mainContent = readFileSync2(skillMd, "utf-8");
|
|
3969
|
+
} catch {
|
|
3970
|
+
return null;
|
|
3971
|
+
}
|
|
3972
|
+
let description = "";
|
|
3973
|
+
const descMatch = mainContent.match(/^---\s*\n[\s\S]*?description:\s*(.+)\n[\s\S]*?---/);
|
|
3974
|
+
if (descMatch) {
|
|
3975
|
+
description = descMatch[1].trim();
|
|
3976
|
+
} else {
|
|
3977
|
+
const firstParagraph = mainContent.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim();
|
|
3978
|
+
description = firstParagraph.slice(0, 200);
|
|
3979
|
+
}
|
|
3980
|
+
const files = [];
|
|
3981
|
+
const walkStack = [{ dir: skillDir, prefix: "" }];
|
|
3982
|
+
while (walkStack.length > 0) {
|
|
3983
|
+
const { dir, prefix } = walkStack.pop();
|
|
3984
|
+
for (const entry of readdirSync(dir)) {
|
|
3985
|
+
const full = join2(dir, entry);
|
|
3986
|
+
const rel = prefix ? `${prefix}/${entry}` : entry;
|
|
3987
|
+
if (statSync(full).isDirectory()) {
|
|
3988
|
+
walkStack.push({ dir: full, prefix: rel });
|
|
3989
|
+
} else if (entry !== "SKILL.md") {
|
|
3990
|
+
files.push({ path: rel, content: readFileSync2(full, "utf-8") });
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
return { name: skillName, content: mainContent, description, files };
|
|
3995
|
+
} catch {
|
|
3996
|
+
return null;
|
|
3997
|
+
} finally {
|
|
3998
|
+
try {
|
|
3999
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
4000
|
+
} catch {
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
3911
4004
|
function makeSkillTools(ctx) {
|
|
3912
4005
|
function authAndValidate(projectId) {
|
|
3913
4006
|
const auth = ctx.ensureAuth();
|
|
@@ -4075,55 +4168,125 @@ Load with: gitlab_skill_load_reference(name="${args.name}", reference="<name>")`
|
|
|
4075
4168
|
}
|
|
4076
4169
|
}),
|
|
4077
4170
|
gitlab_skill_discover: tool6({
|
|
4078
|
-
description: "Search for skills
|
|
4171
|
+
description: "Search for skills in the group wiki and the skills.sh public registry.\nGroup wiki skills are searched first, then skills.sh for community skills.\nUse gitlab_skill_install to install a discovered skill into your project.",
|
|
4079
4172
|
args: {
|
|
4080
4173
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4081
4174
|
query: z6.string().describe("Search query (matches skill name and description)"),
|
|
4082
|
-
group_id: z6.string().describe("Group path to search for shared skills")
|
|
4175
|
+
group_id: z6.string().optional().describe("Group path to search for shared skills (optional)")
|
|
4083
4176
|
},
|
|
4084
4177
|
execute: async (args) => {
|
|
4085
4178
|
const auth = authAndValidate(args.project_id);
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
${entries.map((e) => `- ${e.name}: ${e.description}`).join("\n")}`;
|
|
4104
|
-
}
|
|
4105
|
-
return `Found ${matches.length} skill(s) in group "${args.group_id}":
|
|
4179
|
+
const sections = [];
|
|
4180
|
+
if (args.group_id) {
|
|
4181
|
+
try {
|
|
4182
|
+
const entries = await readIndex(
|
|
4183
|
+
auth.instanceUrl,
|
|
4184
|
+
auth.token,
|
|
4185
|
+
"groups",
|
|
4186
|
+
args.group_id,
|
|
4187
|
+
SKILLS_INDEX
|
|
4188
|
+
);
|
|
4189
|
+
const q = args.query.toLowerCase();
|
|
4190
|
+
const matches = entries.filter(
|
|
4191
|
+
(e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
|
|
4192
|
+
);
|
|
4193
|
+
if (matches.length > 0) {
|
|
4194
|
+
sections.push(
|
|
4195
|
+
`### Group skills (${matches.length})
|
|
4106
4196
|
|
|
4107
4197
|
` + matches.map(
|
|
4108
|
-
|
|
4109
|
-
Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4198
|
+
(e) => `**${e.name}**: ${e.description}
|
|
4199
|
+
Install: \`gitlab_skill_install(name="${e.name}", source="group", group_id="${args.group_id}")\``
|
|
4200
|
+
).join("\n\n")
|
|
4201
|
+
);
|
|
4202
|
+
}
|
|
4203
|
+
} catch {
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
const shResults = searchSkillsSh(args.query);
|
|
4207
|
+
if (shResults.length > 0) {
|
|
4208
|
+
sections.push(
|
|
4209
|
+
`### skills.sh (${shResults.length})
|
|
4210
|
+
|
|
4211
|
+
` + shResults.map(
|
|
4212
|
+
(r) => `**${r.identifier}** (${r.installs})
|
|
4213
|
+
${r.url}
|
|
4214
|
+
Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
4215
|
+
).join("\n\n")
|
|
4216
|
+
);
|
|
4113
4217
|
}
|
|
4218
|
+
if (sections.length === 0) {
|
|
4219
|
+
return `No skills found matching "${args.query}" in ${args.group_id ? "group wiki or " : ""}skills.sh.`;
|
|
4220
|
+
}
|
|
4221
|
+
return sections.join("\n\n---\n\n");
|
|
4114
4222
|
}
|
|
4115
4223
|
}),
|
|
4116
4224
|
gitlab_skill_install: tool6({
|
|
4117
|
-
description: "Install a skill from a group wiki into the project wiki.\
|
|
4225
|
+
description: "Install a skill from a group wiki or skills.sh into the project wiki.\nFor group: copies all skill pages (SKILL + references) from the group.\nFor skills.sh: downloads via npx, extracts SKILL.md and files, writes to wiki.\nUpdates the project skill index with the installed skill.",
|
|
4118
4226
|
args: {
|
|
4119
4227
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4120
|
-
name: z6.string().describe(
|
|
4121
|
-
|
|
4228
|
+
name: z6.string().describe(
|
|
4229
|
+
'Skill identifier. For group: skill name (e.g., "incident-retro"). For skills.sh: full identifier (e.g., "vercel-labs/agent-skills@nextjs-developer").'
|
|
4230
|
+
),
|
|
4231
|
+
source: z6.enum(["group", "skills.sh"]).describe('Where to install from: "group" (group wiki) or "skills.sh" (public registry)'),
|
|
4232
|
+
group_id: z6.string().optional().describe("Group path (required when source is group)"),
|
|
4122
4233
|
draft: z6.boolean().optional().describe("Install as draft (default: false)")
|
|
4123
4234
|
},
|
|
4124
4235
|
execute: async (args) => {
|
|
4125
4236
|
const auth = authAndValidate(args.project_id);
|
|
4126
4237
|
const projectScope = resolveScope2(args);
|
|
4238
|
+
const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4239
|
+
const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4240
|
+
if (args.source === "skills.sh") {
|
|
4241
|
+
const downloaded = downloadSkillFromSkillsSh(args.name);
|
|
4242
|
+
if (!downloaded) {
|
|
4243
|
+
return `Failed to download skill "${args.name}" from skills.sh. Check that the identifier is correct (e.g., "owner/repo@skill-name").`;
|
|
4244
|
+
}
|
|
4245
|
+
const skillSlug = `${targetPrefix}/${downloaded.name}/SKILL`;
|
|
4246
|
+
try {
|
|
4247
|
+
await upsertPage(
|
|
4248
|
+
auth.instanceUrl,
|
|
4249
|
+
auth.token,
|
|
4250
|
+
projectScope.scope,
|
|
4251
|
+
projectScope.id,
|
|
4252
|
+
skillSlug,
|
|
4253
|
+
downloaded.content
|
|
4254
|
+
);
|
|
4255
|
+
let fileCount = 1;
|
|
4256
|
+
for (const file of downloaded.files) {
|
|
4257
|
+
const ext = file.path.replace(/\.[^.]+$/, "");
|
|
4258
|
+
const refSlug = `${targetPrefix}/${downloaded.name}/${ext}`;
|
|
4259
|
+
await upsertPage(
|
|
4260
|
+
auth.instanceUrl,
|
|
4261
|
+
auth.token,
|
|
4262
|
+
projectScope.scope,
|
|
4263
|
+
projectScope.id,
|
|
4264
|
+
refSlug,
|
|
4265
|
+
file.content
|
|
4266
|
+
);
|
|
4267
|
+
fileCount++;
|
|
4268
|
+
}
|
|
4269
|
+
await upsertIndexEntry(
|
|
4270
|
+
auth.instanceUrl,
|
|
4271
|
+
auth.token,
|
|
4272
|
+
projectScope.scope,
|
|
4273
|
+
projectScope.id,
|
|
4274
|
+
targetIndex,
|
|
4275
|
+
{
|
|
4276
|
+
name: downloaded.name,
|
|
4277
|
+
description: downloaded.description,
|
|
4278
|
+
source: `skills.sh:${args.name}`,
|
|
4279
|
+
draft: !!args.draft
|
|
4280
|
+
}
|
|
4281
|
+
);
|
|
4282
|
+
return `Installed skill "${downloaded.name}" from skills.sh. ${fileCount} page(s) written.`;
|
|
4283
|
+
} catch (err) {
|
|
4284
|
+
return `Error installing skill from skills.sh: ${err.message}`;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (!args.group_id) {
|
|
4288
|
+
return 'Error: group_id is required when source is "group".';
|
|
4289
|
+
}
|
|
4127
4290
|
try {
|
|
4128
4291
|
const groupPages = await listWikiPages(
|
|
4129
4292
|
auth.instanceUrl,
|
|
@@ -4139,8 +4302,6 @@ Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")`
|
|
|
4139
4302
|
if (skillPages.length === 0) {
|
|
4140
4303
|
return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
|
|
4141
4304
|
}
|
|
4142
|
-
const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4143
|
-
const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4144
4305
|
for (const page of skillPages) {
|
|
4145
4306
|
const newSlug = page.slug.replace(SKILLS_PREFIX, targetPrefix);
|
|
4146
4307
|
await upsertPage(
|
|
@@ -4174,7 +4335,7 @@ Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")`
|
|
|
4174
4335
|
draft: !!args.draft
|
|
4175
4336
|
}
|
|
4176
4337
|
);
|
|
4177
|
-
return `Installed skill "${args.name}" from group "${args.group_id}"
|
|
4338
|
+
return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s) copied.`;
|
|
4178
4339
|
} catch (err) {
|
|
4179
4340
|
return `Error installing skill: ${err.message}`;
|
|
4180
4341
|
}
|