opencode-gitlab-dap 1.14.2 → 1.15.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/README.md +15 -12
- package/dist/index.cjs +258 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +270 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ The plugin provides a multi-round design workflow:
|
|
|
110
110
|
|
|
111
111
|
The vendored `flow_v2.json` schema from GitLab Rails powers client-side validation using `ajv`, catching errors before hitting the API.
|
|
112
112
|
|
|
113
|
-
###
|
|
113
|
+
### 35 Tools
|
|
114
114
|
|
|
115
115
|
#### DAP Tools (20)
|
|
116
116
|
|
|
@@ -157,17 +157,20 @@ Say **"bootstrap project memory"** to automatically inspect a project and build
|
|
|
157
157
|
|
|
158
158
|
##### Skill Tools
|
|
159
159
|
|
|
160
|
-
| Tool | Description
|
|
161
|
-
| ----------------------- |
|
|
162
|
-
| `gitlab_skill_list` | List skills with auto-rebuilding index
|
|
163
|
-
| `gitlab_skill_load` | Load a skill (SKILL page +
|
|
164
|
-
| `gitlab_skill_save` | Create/update a skill with required description for index
|
|
165
|
-
| `gitlab_skill_promote` | Promote a draft skill to published (moves all pages)
|
|
166
|
-
| `gitlab_skill_discover` | Search group wiki for
|
|
167
|
-
| `gitlab_skill_install` |
|
|
168
|
-
| `
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
| Tool | Description |
|
|
161
|
+
| ----------------------- | ------------------------------------------------------------- |
|
|
162
|
+
| `gitlab_skill_list` | List skills with auto-rebuilding index |
|
|
163
|
+
| `gitlab_skill_load` | Load a skill (SKILL page + references + snippet info) |
|
|
164
|
+
| `gitlab_skill_save` | Create/update a skill with required description for index |
|
|
165
|
+
| `gitlab_skill_promote` | Promote a draft skill to published (moves all pages) |
|
|
166
|
+
| `gitlab_skill_discover` | Search group wiki and skills.sh for available skills |
|
|
167
|
+
| `gitlab_skill_install` | Install from group wiki or skills.sh (wiki + snippet) |
|
|
168
|
+
| `gitlab_skill_setup` | Extract skill to `.agents/skills/<name>/` for local execution |
|
|
169
|
+
| `gitlab_skill_delete` | Delete skill (wiki pages + snippet + index entry) |
|
|
170
|
+
|
|
171
|
+
**Hybrid storage:** SKILL.md and markdown references are stored in the wiki. Executable files
|
|
172
|
+
(`.py`, `.js`, `.sh`) are stored as a multi-file project snippet with extensions preserved.
|
|
173
|
+
Use `gitlab_skill_setup` to extract everything to `.agents/skills/<name>/` for local execution.
|
|
171
174
|
Skills are indexed in `agents/skills/index` for fast discovery.
|
|
172
175
|
All tools support project scope (default) and group scope (`scope="groups"`).
|
|
173
176
|
|
package/dist/index.cjs
CHANGED
|
@@ -3947,6 +3947,73 @@ ${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
|
+
|
|
3951
|
+
// src/snippets.ts
|
|
3952
|
+
function snippetApi(instanceUrl, token, projectId) {
|
|
3953
|
+
const base = instanceUrl.replace(/\/$/, "");
|
|
3954
|
+
const encoded = typeof projectId === "number" ? projectId : encodeURIComponent(projectId);
|
|
3955
|
+
return {
|
|
3956
|
+
url: `${base}/api/v4/projects/${encoded}/snippets`,
|
|
3957
|
+
headers: {
|
|
3958
|
+
"Content-Type": "application/json",
|
|
3959
|
+
Authorization: `Bearer ${token}`
|
|
3960
|
+
}
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
async function handleResponse2(res) {
|
|
3964
|
+
if (!res.ok) {
|
|
3965
|
+
const text = await res.text();
|
|
3966
|
+
throw new Error(`Snippet API error (${res.status}): ${text}`);
|
|
3967
|
+
}
|
|
3968
|
+
return res.json();
|
|
3969
|
+
}
|
|
3970
|
+
async function createProjectSnippet(instanceUrl, token, projectId, title, description, files, visibility = "private") {
|
|
3971
|
+
const { url, headers } = snippetApi(instanceUrl, token, projectId);
|
|
3972
|
+
const res = await fetch(url, {
|
|
3973
|
+
method: "POST",
|
|
3974
|
+
headers,
|
|
3975
|
+
body: JSON.stringify({ title, description, visibility, files })
|
|
3976
|
+
});
|
|
3977
|
+
return handleResponse2(res);
|
|
3978
|
+
}
|
|
3979
|
+
async function deleteProjectSnippet(instanceUrl, token, projectId, snippetId) {
|
|
3980
|
+
const { url, headers } = snippetApi(instanceUrl, token, projectId);
|
|
3981
|
+
const res = await fetch(`${url}/${snippetId}`, { method: "DELETE", headers });
|
|
3982
|
+
if (!res.ok) {
|
|
3983
|
+
const text = await res.text();
|
|
3984
|
+
throw new Error(`Snippet API error (${res.status}): ${text}`);
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
async function getSnippetFileRaw(instanceUrl, token, projectId, snippetId, filePath, ref = "main") {
|
|
3988
|
+
const base = instanceUrl.replace(/\/$/, "");
|
|
3989
|
+
const encoded = typeof projectId === "number" ? projectId : encodeURIComponent(projectId);
|
|
3990
|
+
const encodedPath = encodeURIComponent(filePath);
|
|
3991
|
+
const url = `${base}/api/v4/projects/${encoded}/snippets/${snippetId}/files/${ref}/${encodedPath}/raw`;
|
|
3992
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
3993
|
+
if (!res.ok) {
|
|
3994
|
+
const text = await res.text();
|
|
3995
|
+
throw new Error(`Snippet file API error (${res.status}): ${text}`);
|
|
3996
|
+
}
|
|
3997
|
+
return res.text();
|
|
3998
|
+
}
|
|
3999
|
+
async function listSnippetFiles(instanceUrl, token, projectId, snippetId) {
|
|
4000
|
+
const base = instanceUrl.replace(/\/$/, "");
|
|
4001
|
+
const encoded = typeof projectId === "number" ? projectId : encodeURIComponent(projectId);
|
|
4002
|
+
const url = `${base}/api/v4/projects/${encoded}/snippets/${snippetId}`;
|
|
4003
|
+
const res = await fetch(url, {
|
|
4004
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }
|
|
4005
|
+
});
|
|
4006
|
+
const snippet = await handleResponse2(res);
|
|
4007
|
+
if (snippet.files) {
|
|
4008
|
+
return snippet.files.map((f) => ({ path: f.path, raw_url: f.raw_url }));
|
|
4009
|
+
}
|
|
4010
|
+
if (snippet.file_name) {
|
|
4011
|
+
return [{ path: snippet.file_name, raw_url: snippet.raw_url }];
|
|
4012
|
+
}
|
|
4013
|
+
return [];
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
// src/tools/skill-tools.ts
|
|
3950
4017
|
var import_child_process = require("child_process");
|
|
3951
4018
|
var import_fs2 = require("fs");
|
|
3952
4019
|
var import_path2 = require("path");
|
|
@@ -3980,14 +4047,17 @@ function parseIndex(content) {
|
|
|
3980
4047
|
const rest = lines.slice(1).join("\n").trim();
|
|
3981
4048
|
const descLines = [];
|
|
3982
4049
|
let source;
|
|
4050
|
+
let snippetId;
|
|
3983
4051
|
for (const line of rest.split("\n")) {
|
|
3984
4052
|
if (line.startsWith("Source:")) {
|
|
3985
4053
|
source = line.slice(7).trim();
|
|
4054
|
+
} else if (line.startsWith("Snippet:")) {
|
|
4055
|
+
snippetId = parseInt(line.slice(8).trim(), 10) || void 0;
|
|
3986
4056
|
} else if (line.trim()) {
|
|
3987
4057
|
descLines.push(line);
|
|
3988
4058
|
}
|
|
3989
4059
|
}
|
|
3990
|
-
entries.push({ name, description: descLines.join("\n"), source, draft: false });
|
|
4060
|
+
entries.push({ name, description: descLines.join("\n"), source, snippetId, draft: false });
|
|
3991
4061
|
}
|
|
3992
4062
|
return entries;
|
|
3993
4063
|
}
|
|
@@ -3997,6 +4067,8 @@ function formatIndex(entries) {
|
|
|
3997
4067
|
${e.description}`;
|
|
3998
4068
|
if (e.source) block += `
|
|
3999
4069
|
Source: ${e.source}`;
|
|
4070
|
+
if (e.snippetId) block += `
|
|
4071
|
+
Snippet: ${e.snippetId}`;
|
|
4000
4072
|
return block;
|
|
4001
4073
|
}).join("\n\n");
|
|
4002
4074
|
}
|
|
@@ -4117,6 +4189,33 @@ async function upsertPage(instanceUrl, token, scope, id, slug, content) {
|
|
|
4117
4189
|
await createWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4118
4190
|
}
|
|
4119
4191
|
}
|
|
4192
|
+
function isMarkdownFile(path) {
|
|
4193
|
+
return path === "SKILL.md" || path.endsWith(".md") || path.endsWith(".markdown");
|
|
4194
|
+
}
|
|
4195
|
+
function isAgentsGitignored(dir) {
|
|
4196
|
+
try {
|
|
4197
|
+
(0, import_child_process.execSync)("git check-ignore -q .agents", { cwd: dir, stdio: "pipe" });
|
|
4198
|
+
return true;
|
|
4199
|
+
} catch {
|
|
4200
|
+
return false;
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
function ensureGitignore(dir) {
|
|
4204
|
+
if (isAgentsGitignored(dir)) return;
|
|
4205
|
+
const gitignorePath = (0, import_path2.join)(dir, ".gitignore");
|
|
4206
|
+
if ((0, import_fs2.existsSync)(gitignorePath)) {
|
|
4207
|
+
const content = (0, import_fs2.readFileSync)(gitignorePath, "utf-8");
|
|
4208
|
+
(0, import_fs2.writeFileSync)(
|
|
4209
|
+
gitignorePath,
|
|
4210
|
+
content.trimEnd() + "\n\n# Agent skills cache (managed by opencode-gitlab-dap)\n.agents/\n"
|
|
4211
|
+
);
|
|
4212
|
+
} else {
|
|
4213
|
+
(0, import_fs2.writeFileSync)(
|
|
4214
|
+
gitignorePath,
|
|
4215
|
+
"# Agent skills cache (managed by opencode-gitlab-dap)\n.agents/\n"
|
|
4216
|
+
);
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4120
4219
|
function searchSkillsSh(query) {
|
|
4121
4220
|
try {
|
|
4122
4221
|
const raw = (0, import_child_process.execSync)(`npx skills find ${JSON.stringify(query)}`, {
|
|
@@ -4276,8 +4375,23 @@ ${page.content}` : page.content;
|
|
|
4276
4375
|
|
|
4277
4376
|
---
|
|
4278
4377
|
Available references: ${refs.join(", ")}`;
|
|
4378
|
+
}
|
|
4379
|
+
const indexSlug = isDraft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4380
|
+
const indexEntries = await readIndex(
|
|
4381
|
+
auth.instanceUrl,
|
|
4382
|
+
auth.token,
|
|
4383
|
+
scope,
|
|
4384
|
+
id,
|
|
4385
|
+
indexSlug
|
|
4386
|
+
);
|
|
4387
|
+
const entry = indexEntries.find((e) => e.name === args.name);
|
|
4388
|
+
if (entry?.snippetId) {
|
|
4389
|
+
result += `
|
|
4390
|
+
|
|
4391
|
+
---
|
|
4392
|
+
This skill has executable scripts in snippet #${entry.snippetId}.`;
|
|
4279
4393
|
result += `
|
|
4280
|
-
|
|
4394
|
+
Run \`gitlab_skill_setup(name="${args.name}")\` to extract them to .agents/skills/${args.name}/ for execution.`;
|
|
4281
4395
|
}
|
|
4282
4396
|
return result;
|
|
4283
4397
|
} catch {
|
|
@@ -4435,29 +4549,42 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4435
4549
|
if (!downloaded) {
|
|
4436
4550
|
return `Failed to download skill "${args.name}" from skills.sh. Check that the identifier is correct (e.g., "owner/repo@skill-name").`;
|
|
4437
4551
|
}
|
|
4438
|
-
const skillSlug = `${targetPrefix}/${downloaded.name}/SKILL`;
|
|
4439
4552
|
try {
|
|
4440
4553
|
await upsertPage(
|
|
4441
4554
|
auth.instanceUrl,
|
|
4442
4555
|
auth.token,
|
|
4443
4556
|
projectScope.scope,
|
|
4444
4557
|
projectScope.id,
|
|
4445
|
-
|
|
4558
|
+
`${targetPrefix}/${downloaded.name}/SKILL`,
|
|
4446
4559
|
downloaded.content
|
|
4447
4560
|
);
|
|
4448
|
-
let
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4561
|
+
let wikiCount = 1;
|
|
4562
|
+
const mdFiles = downloaded.files.filter((f) => isMarkdownFile(f.path));
|
|
4563
|
+
const scriptFiles = downloaded.files.filter((f) => !isMarkdownFile(f.path));
|
|
4564
|
+
for (const file of mdFiles) {
|
|
4565
|
+
const slug = `${targetPrefix}/${downloaded.name}/references/${file.path.replace(/\.[^.]+$/, "")}`;
|
|
4452
4566
|
await upsertPage(
|
|
4453
4567
|
auth.instanceUrl,
|
|
4454
4568
|
auth.token,
|
|
4455
4569
|
projectScope.scope,
|
|
4456
4570
|
projectScope.id,
|
|
4457
|
-
|
|
4571
|
+
slug,
|
|
4458
4572
|
file.content
|
|
4459
4573
|
);
|
|
4460
|
-
|
|
4574
|
+
wikiCount++;
|
|
4575
|
+
}
|
|
4576
|
+
let snippetId;
|
|
4577
|
+
if (scriptFiles.length > 0) {
|
|
4578
|
+
const snippet = await createProjectSnippet(
|
|
4579
|
+
auth.instanceUrl,
|
|
4580
|
+
auth.token,
|
|
4581
|
+
args.project_id,
|
|
4582
|
+
`skill:${downloaded.name}`,
|
|
4583
|
+
`Scripts for skill "${downloaded.name}" (installed from skills.sh:${args.name})`,
|
|
4584
|
+
scriptFiles.map((f) => ({ file_path: f.path, content: f.content })),
|
|
4585
|
+
"private"
|
|
4586
|
+
);
|
|
4587
|
+
snippetId = snippet.id;
|
|
4461
4588
|
}
|
|
4462
4589
|
await upsertIndexEntry(
|
|
4463
4590
|
auth.instanceUrl,
|
|
@@ -4469,10 +4596,13 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4469
4596
|
name: downloaded.name,
|
|
4470
4597
|
description: downloaded.description,
|
|
4471
4598
|
source: `skills.sh:${args.name}`,
|
|
4599
|
+
snippetId,
|
|
4472
4600
|
draft: !!args.draft
|
|
4473
4601
|
}
|
|
4474
4602
|
);
|
|
4475
|
-
|
|
4603
|
+
const parts = [`${wikiCount} wiki page(s)`];
|
|
4604
|
+
if (snippetId) parts.push(`snippet #${snippetId} with ${scriptFiles.length} script(s)`);
|
|
4605
|
+
return `Installed skill "${downloaded.name}" from skills.sh. ${parts.join(", ")}. Use gitlab_skill_setup to extract scripts to disk.`;
|
|
4476
4606
|
} catch (err) {
|
|
4477
4607
|
return `Error installing skill from skills.sh: ${err.message}`;
|
|
4478
4608
|
}
|
|
@@ -4534,8 +4664,106 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4534
4664
|
}
|
|
4535
4665
|
}
|
|
4536
4666
|
}),
|
|
4667
|
+
gitlab_skill_setup: (0, import_plugin6.tool)({
|
|
4668
|
+
description: "Extract a skill to the local .agents/skills/ directory for execution.\nDownloads SKILL.md from wiki and script files from the associated snippet.\nWrites to .agents/skills/<name>/ and ensures .agents/ is in .gitignore.\nAfter setup, OpenCode will auto-discover the skill from the local directory.",
|
|
4669
|
+
args: {
|
|
4670
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4671
|
+
name: z6.string().describe("Skill name to set up locally"),
|
|
4672
|
+
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4673
|
+
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4674
|
+
},
|
|
4675
|
+
execute: async (args) => {
|
|
4676
|
+
const auth = authAndValidate(args.project_id);
|
|
4677
|
+
const { scope, id } = resolveScope2(args);
|
|
4678
|
+
const targetDir = (0, import_path2.join)(process.cwd(), ".agents", "skills", args.name);
|
|
4679
|
+
try {
|
|
4680
|
+
let skillContent = null;
|
|
4681
|
+
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4682
|
+
try {
|
|
4683
|
+
const page = await getWikiPage(
|
|
4684
|
+
auth.instanceUrl,
|
|
4685
|
+
auth.token,
|
|
4686
|
+
scope,
|
|
4687
|
+
id,
|
|
4688
|
+
`${prefix}/${args.name}/SKILL`
|
|
4689
|
+
);
|
|
4690
|
+
skillContent = page.content;
|
|
4691
|
+
break;
|
|
4692
|
+
} catch {
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
if (!skillContent) {
|
|
4696
|
+
return `Skill "${args.name}" not found in wiki. Use gitlab_skill_list to see available skills.`;
|
|
4697
|
+
}
|
|
4698
|
+
(0, import_fs2.mkdirSync)(targetDir, { recursive: true });
|
|
4699
|
+
(0, import_fs2.writeFileSync)((0, import_path2.join)(targetDir, "SKILL.md"), skillContent);
|
|
4700
|
+
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id, true);
|
|
4701
|
+
let refCount = 0;
|
|
4702
|
+
for (const prefix of [SKILLS_PREFIX, DRAFTS_PREFIX]) {
|
|
4703
|
+
const refPrefix = `${prefix}/${args.name}/references/`;
|
|
4704
|
+
const refPages = pages.filter(
|
|
4705
|
+
(p) => p.slug.startsWith(refPrefix) && p.content
|
|
4706
|
+
);
|
|
4707
|
+
for (const ref of refPages) {
|
|
4708
|
+
const refName = ref.slug.slice(refPrefix.length);
|
|
4709
|
+
const refDir = (0, import_path2.join)(targetDir, "references");
|
|
4710
|
+
(0, import_fs2.mkdirSync)(refDir, { recursive: true });
|
|
4711
|
+
(0, import_fs2.writeFileSync)((0, import_path2.join)(refDir, `${refName}.md`), ref.content);
|
|
4712
|
+
refCount++;
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
let scriptCount = 0;
|
|
4716
|
+
const indexEntries = await readIndex(
|
|
4717
|
+
auth.instanceUrl,
|
|
4718
|
+
auth.token,
|
|
4719
|
+
scope,
|
|
4720
|
+
id,
|
|
4721
|
+
SKILLS_INDEX
|
|
4722
|
+
);
|
|
4723
|
+
const draftsEntries = await readIndex(
|
|
4724
|
+
auth.instanceUrl,
|
|
4725
|
+
auth.token,
|
|
4726
|
+
scope,
|
|
4727
|
+
id,
|
|
4728
|
+
DRAFTS_INDEX
|
|
4729
|
+
);
|
|
4730
|
+
const entry = [...indexEntries, ...draftsEntries].find((e) => e.name === args.name);
|
|
4731
|
+
if (entry?.snippetId) {
|
|
4732
|
+
const files = await listSnippetFiles(
|
|
4733
|
+
auth.instanceUrl,
|
|
4734
|
+
auth.token,
|
|
4735
|
+
args.project_id,
|
|
4736
|
+
entry.snippetId
|
|
4737
|
+
);
|
|
4738
|
+
for (const file of files) {
|
|
4739
|
+
const filePath = (0, import_path2.join)(targetDir, file.path);
|
|
4740
|
+
(0, import_fs2.mkdirSync)((0, import_path2.dirname)(filePath), { recursive: true });
|
|
4741
|
+
const content = await getSnippetFileRaw(
|
|
4742
|
+
auth.instanceUrl,
|
|
4743
|
+
auth.token,
|
|
4744
|
+
args.project_id,
|
|
4745
|
+
entry.snippetId,
|
|
4746
|
+
file.path
|
|
4747
|
+
);
|
|
4748
|
+
(0, import_fs2.writeFileSync)(filePath, content);
|
|
4749
|
+
if (file.path.endsWith(".sh")) {
|
|
4750
|
+
(0, import_fs2.chmodSync)(filePath, 493);
|
|
4751
|
+
}
|
|
4752
|
+
scriptCount++;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
ensureGitignore(process.cwd());
|
|
4756
|
+
const parts = ["SKILL.md"];
|
|
4757
|
+
if (refCount > 0) parts.push(`${refCount} reference(s)`);
|
|
4758
|
+
if (scriptCount > 0) parts.push(`${scriptCount} script(s)`);
|
|
4759
|
+
return `Skill "${args.name}" extracted to ${targetDir} (${parts.join(", ")}). OpenCode will auto-discover it on next session.`;
|
|
4760
|
+
} catch (err) {
|
|
4761
|
+
return `Error setting up skill: ${err.message}`;
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
}),
|
|
4537
4765
|
gitlab_skill_delete: (0, import_plugin6.tool)({
|
|
4538
|
-
description: "Delete a skill and remove it from the index.\nRemoves all pages under the skill directory
|
|
4766
|
+
description: "Delete a skill and remove it from the index.\nRemoves all wiki pages under the skill directory and any associated snippet.\nWiki and snippet git history preserves deleted content.",
|
|
4539
4767
|
args: {
|
|
4540
4768
|
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4541
4769
|
name: z6.string().describe("Skill name to delete"),
|
|
@@ -4558,8 +4786,25 @@ Install: \`gitlab_skill_install(name="${r.identifier}", source="skills.sh")\``
|
|
|
4558
4786
|
for (const page of skillPages) {
|
|
4559
4787
|
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
|
|
4560
4788
|
}
|
|
4789
|
+
const indexEntries = await readIndex(auth.instanceUrl, auth.token, scope, id, indexSlug);
|
|
4790
|
+
const entry = indexEntries.find((e) => e.name === args.name);
|
|
4791
|
+
let snippetDeleted = false;
|
|
4792
|
+
if (entry?.snippetId) {
|
|
4793
|
+
try {
|
|
4794
|
+
await deleteProjectSnippet(
|
|
4795
|
+
auth.instanceUrl,
|
|
4796
|
+
auth.token,
|
|
4797
|
+
args.project_id,
|
|
4798
|
+
entry.snippetId
|
|
4799
|
+
);
|
|
4800
|
+
snippetDeleted = true;
|
|
4801
|
+
} catch {
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4561
4804
|
await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, args.name);
|
|
4562
|
-
|
|
4805
|
+
const parts = [`${skillPages.length} wiki page(s)`];
|
|
4806
|
+
if (snippetDeleted) parts.push("snippet");
|
|
4807
|
+
return `Deleted skill "${args.name}" (${parts.join(" + ")} removed).`;
|
|
4563
4808
|
} catch (err) {
|
|
4564
4809
|
return `Error deleting skill: ${err.message}`;
|
|
4565
4810
|
}
|