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.cjs
CHANGED
|
@@ -3947,6 +3947,10 @@ ${e.content}`).join("\n\n---\n\n");
|
|
|
3947
3947
|
|
|
3948
3948
|
// src/tools/skill-tools.ts
|
|
3949
3949
|
var import_plugin6 = require("@opencode-ai/plugin");
|
|
3950
|
+
var import_child_process = require("child_process");
|
|
3951
|
+
var import_fs2 = require("fs");
|
|
3952
|
+
var import_path2 = require("path");
|
|
3953
|
+
var import_os2 = require("os");
|
|
3950
3954
|
var z6 = import_plugin6.tool.schema;
|
|
3951
3955
|
var PREFIX2 = "agents";
|
|
3952
3956
|
var SKILLS_PREFIX = `${PREFIX2}/skills`;
|
|
@@ -4061,9 +4065,21 @@ async function rebuildIndex(instanceUrl, token, scope, id, prefix, indexSlug) {
|
|
|
4061
4065
|
if (!dirty && added.length === 0) return currentEntries;
|
|
4062
4066
|
const kept = currentEntries.filter((e) => actual.has(e.name));
|
|
4063
4067
|
for (const name of added) {
|
|
4068
|
+
let description = "";
|
|
4069
|
+
try {
|
|
4070
|
+
const page = await getWikiPage(instanceUrl, token, scope, id, `${prefix}/${name}/SKILL`);
|
|
4071
|
+
const content = page.content ?? "";
|
|
4072
|
+
const fmMatch = content.match(/^---\s*\n[\s\S]*?description:\s*(.+)\n[\s\S]*?---/);
|
|
4073
|
+
if (fmMatch) {
|
|
4074
|
+
description = fmMatch[1].trim();
|
|
4075
|
+
} else {
|
|
4076
|
+
description = content.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim().slice(0, 200);
|
|
4077
|
+
}
|
|
4078
|
+
} catch {
|
|
4079
|
+
}
|
|
4064
4080
|
kept.push({
|
|
4065
4081
|
name,
|
|
4066
|
-
description: "(
|
|
4082
|
+
description: description || "(no description \u2014 update with gitlab_skill_save)",
|
|
4067
4083
|
draft: prefix === DRAFTS_PREFIX
|
|
4068
4084
|
});
|
|
4069
4085
|
}
|
|
@@ -4077,6 +4093,83 @@ async function upsertPage(instanceUrl, token, scope, id, slug, content) {
|
|
|
4077
4093
|
await createWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4078
4094
|
}
|
|
4079
4095
|
}
|
|
4096
|
+
function searchSkillsSh(query) {
|
|
4097
|
+
try {
|
|
4098
|
+
const raw = (0, import_child_process.execSync)(`npx skills find ${JSON.stringify(query)}`, {
|
|
4099
|
+
timeout: 3e4,
|
|
4100
|
+
encoding: "utf-8",
|
|
4101
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4102
|
+
});
|
|
4103
|
+
const lines = raw.split("\n");
|
|
4104
|
+
const results = [];
|
|
4105
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4106
|
+
const clean = lines[i].replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
4107
|
+
const match = clean.match(/^(\S+\/\S+@\S+)\s+(.+installs?)$/);
|
|
4108
|
+
if (match) {
|
|
4109
|
+
const urlLine = (lines[i + 1] ?? "").replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
4110
|
+
const url = urlLine.startsWith("\u2514 ") ? urlLine.slice(2) : urlLine;
|
|
4111
|
+
results.push({ identifier: match[1], installs: match[2], url });
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
return results;
|
|
4115
|
+
} catch {
|
|
4116
|
+
return [];
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
function downloadSkillFromSkillsSh(identifier) {
|
|
4120
|
+
const tmp = (0, import_fs2.mkdtempSync)((0, import_path2.join)((0, import_os2.tmpdir)(), "skill-install-"));
|
|
4121
|
+
try {
|
|
4122
|
+
(0, import_child_process.execSync)(`npx skills add ${JSON.stringify(identifier)} -y --copy`, {
|
|
4123
|
+
timeout: 6e4,
|
|
4124
|
+
cwd: tmp,
|
|
4125
|
+
encoding: "utf-8",
|
|
4126
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4127
|
+
});
|
|
4128
|
+
const agentsDir = (0, import_path2.join)(tmp, ".agents", "skills");
|
|
4129
|
+
if (!(0, import_fs2.statSync)(agentsDir).isDirectory()) return null;
|
|
4130
|
+
const dirs = (0, import_fs2.readdirSync)(agentsDir);
|
|
4131
|
+
if (dirs.length === 0) return null;
|
|
4132
|
+
const skillName = dirs[0];
|
|
4133
|
+
const skillDir = (0, import_path2.join)(agentsDir, skillName);
|
|
4134
|
+
const skillMd = (0, import_path2.join)(skillDir, "SKILL.md");
|
|
4135
|
+
let mainContent;
|
|
4136
|
+
try {
|
|
4137
|
+
mainContent = (0, import_fs2.readFileSync)(skillMd, "utf-8");
|
|
4138
|
+
} catch {
|
|
4139
|
+
return null;
|
|
4140
|
+
}
|
|
4141
|
+
let description = "";
|
|
4142
|
+
const descMatch = mainContent.match(/^---\s*\n[\s\S]*?description:\s*(.+)\n[\s\S]*?---/);
|
|
4143
|
+
if (descMatch) {
|
|
4144
|
+
description = descMatch[1].trim();
|
|
4145
|
+
} else {
|
|
4146
|
+
const firstParagraph = mainContent.replace(/^---[\s\S]*?---\s*\n/, "").replace(/^#[^\n]*\n+/, "").split("\n\n")[0].replace(/\n/g, " ").trim();
|
|
4147
|
+
description = firstParagraph.slice(0, 200);
|
|
4148
|
+
}
|
|
4149
|
+
const files = [];
|
|
4150
|
+
const walkStack = [{ dir: skillDir, prefix: "" }];
|
|
4151
|
+
while (walkStack.length > 0) {
|
|
4152
|
+
const { dir, prefix } = walkStack.pop();
|
|
4153
|
+
for (const entry of (0, import_fs2.readdirSync)(dir)) {
|
|
4154
|
+
const full = (0, import_path2.join)(dir, entry);
|
|
4155
|
+
const rel = prefix ? `${prefix}/${entry}` : entry;
|
|
4156
|
+
if ((0, import_fs2.statSync)(full).isDirectory()) {
|
|
4157
|
+
walkStack.push({ dir: full, prefix: rel });
|
|
4158
|
+
} else if (entry !== "SKILL.md") {
|
|
4159
|
+
files.push({ path: rel, content: (0, import_fs2.readFileSync)(full, "utf-8") });
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
return { name: skillName, content: mainContent, description, files };
|
|
4164
|
+
} catch {
|
|
4165
|
+
return null;
|
|
4166
|
+
} finally {
|
|
4167
|
+
try {
|
|
4168
|
+
(0, import_fs2.rmSync)(tmp, { recursive: true, force: true });
|
|
4169
|
+
} catch {
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4080
4173
|
function makeSkillTools(ctx) {
|
|
4081
4174
|
function authAndValidate(projectId) {
|
|
4082
4175
|
const auth = ctx.ensureAuth();
|
|
@@ -4244,55 +4337,125 @@ Load with: gitlab_skill_load_reference(name="${args.name}", reference="<name>")`
|
|
|
4244
4337
|
}
|
|
4245
4338
|
}),
|
|
4246
4339
|
gitlab_skill_discover: (0, import_plugin6.tool)({
|
|
4247
|
-
description: "Search for skills
|
|
4340
|
+
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.",
|
|
4248
4341
|
args: {
|
|
4249
4342
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4250
4343
|
query: z6.string().describe("Search query (matches skill name and description)"),
|
|
4251
|
-
group_id: z6.string().describe("Group path to search for shared skills")
|
|
4344
|
+
group_id: z6.string().optional().describe("Group path to search for shared skills (optional)")
|
|
4252
4345
|
},
|
|
4253
4346
|
execute: async (args) => {
|
|
4254
4347
|
const auth = authAndValidate(args.project_id);
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
${entries.map((e) => `- ${e.name}: ${e.description}`).join("\n")}`;
|
|
4273
|
-
}
|
|
4274
|
-
return `Found ${matches.length} skill(s) in group "${args.group_id}":
|
|
4348
|
+
const sections = [];
|
|
4349
|
+
if (args.group_id) {
|
|
4350
|
+
try {
|
|
4351
|
+
const entries = await readIndex(
|
|
4352
|
+
auth.instanceUrl,
|
|
4353
|
+
auth.token,
|
|
4354
|
+
"groups",
|
|
4355
|
+
args.group_id,
|
|
4356
|
+
SKILLS_INDEX
|
|
4357
|
+
);
|
|
4358
|
+
const q = args.query.toLowerCase();
|
|
4359
|
+
const matches = entries.filter(
|
|
4360
|
+
(e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
|
|
4361
|
+
);
|
|
4362
|
+
if (matches.length > 0) {
|
|
4363
|
+
sections.push(
|
|
4364
|
+
`### Group skills (${matches.length})
|
|
4275
4365
|
|
|
4276
4366
|
` + matches.map(
|
|
4277
|
-
|
|
4278
|
-
Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4367
|
+
(e) => `**${e.name}**: ${e.description}
|
|
4368
|
+
Install: \`gitlab_skill_install(name="${e.name}", source="group", group_id="${args.group_id}")\``
|
|
4369
|
+
).join("\n\n")
|
|
4370
|
+
);
|
|
4371
|
+
}
|
|
4372
|
+
} catch {
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
const shResults = searchSkillsSh(args.query);
|
|
4376
|
+
if (shResults.length > 0) {
|
|
4377
|
+
sections.push(
|
|
4378
|
+
`### skills.sh (${shResults.length})
|
|
4379
|
+
|
|
4380
|
+
` + shResults.map(
|
|
4381
|
+
(r) => `**${r.identifier}** (${r.installs})
|
|
4382
|
+
${r.url}
|
|
4383
|
+
Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
4384
|
+
).join("\n\n")
|
|
4385
|
+
);
|
|
4282
4386
|
}
|
|
4387
|
+
if (sections.length === 0) {
|
|
4388
|
+
return `No skills found matching "${args.query}" in ${args.group_id ? "group wiki or " : ""}skills.sh.`;
|
|
4389
|
+
}
|
|
4390
|
+
return sections.join("\n\n---\n\n");
|
|
4283
4391
|
}
|
|
4284
4392
|
}),
|
|
4285
4393
|
gitlab_skill_install: (0, import_plugin6.tool)({
|
|
4286
|
-
description: "Install a skill from a group wiki into the project wiki.\
|
|
4394
|
+
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.",
|
|
4287
4395
|
args: {
|
|
4288
4396
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4289
|
-
name: z6.string().describe(
|
|
4290
|
-
|
|
4397
|
+
name: z6.string().describe(
|
|
4398
|
+
'Skill identifier. For group: skill name (e.g., "incident-retro"). For skills.sh: full identifier (e.g., "vercel-labs/agent-skills@nextjs-developer").'
|
|
4399
|
+
),
|
|
4400
|
+
source: z6.enum(["group", "skills.sh"]).describe('Where to install from: "group" (group wiki) or "skills.sh" (public registry)'),
|
|
4401
|
+
group_id: z6.string().optional().describe("Group path (required when source is group)"),
|
|
4291
4402
|
draft: z6.boolean().optional().describe("Install as draft (default: false)")
|
|
4292
4403
|
},
|
|
4293
4404
|
execute: async (args) => {
|
|
4294
4405
|
const auth = authAndValidate(args.project_id);
|
|
4295
4406
|
const projectScope = resolveScope2(args);
|
|
4407
|
+
const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4408
|
+
const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4409
|
+
if (args.source === "skills.sh") {
|
|
4410
|
+
const downloaded = downloadSkillFromSkillsSh(args.name);
|
|
4411
|
+
if (!downloaded) {
|
|
4412
|
+
return `Failed to download skill "${args.name}" from skills.sh. Check that the identifier is correct (e.g., "owner/repo@skill-name").`;
|
|
4413
|
+
}
|
|
4414
|
+
const skillSlug = `${targetPrefix}/${downloaded.name}/SKILL`;
|
|
4415
|
+
try {
|
|
4416
|
+
await upsertPage(
|
|
4417
|
+
auth.instanceUrl,
|
|
4418
|
+
auth.token,
|
|
4419
|
+
projectScope.scope,
|
|
4420
|
+
projectScope.id,
|
|
4421
|
+
skillSlug,
|
|
4422
|
+
downloaded.content
|
|
4423
|
+
);
|
|
4424
|
+
let fileCount = 1;
|
|
4425
|
+
for (const file of downloaded.files) {
|
|
4426
|
+
const ext = file.path.replace(/\.[^.]+$/, "");
|
|
4427
|
+
const refSlug = `${targetPrefix}/${downloaded.name}/${ext}`;
|
|
4428
|
+
await upsertPage(
|
|
4429
|
+
auth.instanceUrl,
|
|
4430
|
+
auth.token,
|
|
4431
|
+
projectScope.scope,
|
|
4432
|
+
projectScope.id,
|
|
4433
|
+
refSlug,
|
|
4434
|
+
file.content
|
|
4435
|
+
);
|
|
4436
|
+
fileCount++;
|
|
4437
|
+
}
|
|
4438
|
+
await upsertIndexEntry(
|
|
4439
|
+
auth.instanceUrl,
|
|
4440
|
+
auth.token,
|
|
4441
|
+
projectScope.scope,
|
|
4442
|
+
projectScope.id,
|
|
4443
|
+
targetIndex,
|
|
4444
|
+
{
|
|
4445
|
+
name: downloaded.name,
|
|
4446
|
+
description: downloaded.description,
|
|
4447
|
+
source: `skills.sh:${args.name}`,
|
|
4448
|
+
draft: !!args.draft
|
|
4449
|
+
}
|
|
4450
|
+
);
|
|
4451
|
+
return `Installed skill "${downloaded.name}" from skills.sh. ${fileCount} page(s) written.`;
|
|
4452
|
+
} catch (err) {
|
|
4453
|
+
return `Error installing skill from skills.sh: ${err.message}`;
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
if (!args.group_id) {
|
|
4457
|
+
return 'Error: group_id is required when source is "group".';
|
|
4458
|
+
}
|
|
4296
4459
|
try {
|
|
4297
4460
|
const groupPages = await listWikiPages(
|
|
4298
4461
|
auth.instanceUrl,
|
|
@@ -4308,8 +4471,6 @@ Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")`
|
|
|
4308
4471
|
if (skillPages.length === 0) {
|
|
4309
4472
|
return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
|
|
4310
4473
|
}
|
|
4311
|
-
const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4312
|
-
const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4313
4474
|
for (const page of skillPages) {
|
|
4314
4475
|
const newSlug = page.slug.replace(SKILLS_PREFIX, targetPrefix);
|
|
4315
4476
|
await upsertPage(
|
|
@@ -4343,7 +4504,7 @@ Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")`
|
|
|
4343
4504
|
draft: !!args.draft
|
|
4344
4505
|
}
|
|
4345
4506
|
);
|
|
4346
|
-
return `Installed skill "${args.name}" from group "${args.group_id}"
|
|
4507
|
+
return `Installed skill "${args.name}" from group "${args.group_id}". ${skillPages.length} page(s) copied.`;
|
|
4347
4508
|
} catch (err) {
|
|
4348
4509
|
return `Error installing skill: ${err.message}`;
|
|
4349
4510
|
}
|