From e5efa80747b42856f1b6056cf10aa3252b417368 Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Sun, 3 May 2026 17:46:26 +0300 Subject: [PATCH 1/7] feat: enhance skill management with version handling and cleanup - Added version handling for skills during import and extraction. - Implemented pruning of non-manifest skill versions to maintain a clean directory. - Updated logging to reflect skill version details for better traceability. --- src/index.ts | 86 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5455c1b..39aed16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,14 @@ import type { Plugin } from '@opencode-ai/plugin'; -import { appendFileSync, createWriteStream, readFileSync, existsSync, mkdirSync } from 'fs'; +import { + appendFileSync, + createWriteStream, + readFileSync, + existsSync, + mkdirSync, + readdirSync, + rmSync, + statSync, +} from 'fs'; import { Readable } from 'stream'; import { pipeline } from 'stream/promises'; import { dirname, join } from 'path'; @@ -41,9 +50,9 @@ const fetchAndSaveFile = async ( const extractZip = async ( $: any, - directory: any, skillZipFile: string, skillName: string, + skillVersion: string, skillZipDir: string, log: (_msg: string) => void ): Promise<{ success: boolean; error?: string }> => { @@ -53,11 +62,11 @@ const extractZip = async ( log(`Failed to extract JFrog ${skillName} skill: ${unzipResponse.stderr}`); return { success: false, - error: `Failed to extract JFrog ${skillName} skill: ${unzipResponse.stderr}`, + error: `Failed to extract JFrog ${skillName}-${skillVersion} skill: ${unzipResponse.stderr}`, }; } - log(`JFrog ${skillName} skill extracted!`); - // remove zip file and its version directory + log(`JFrog ${skillName}-${skillVersion} skill extracted!`); + // remove zip file await $`rm -fR ${skillZipFile}`; log(`Jfrog ${skillName} skill zip file removed!`); return { success: true }; @@ -223,12 +232,16 @@ const pullSkills = async ( mkdirSync(skillsDir, { recursive: true }); log(`Skills directory created: ${skillsDir}`); } + + // pull skills one by one for (const skill of skillsToPull) { - const skillExists = await $`test -d ~/.config/opencode/skills/${skill.name}/`.nothrow().quiet(); - if (skillExists.exitCode !== 0) { - log(`JFrog ${skill.name} skill not found, importing them locally!`); + const keepVersion = String(skill.version).trim(); + const skillInstallDir = join(skillsDir, skill.name, keepVersion); + const skillExists = existsSync(skillInstallDir) && statSync(skillInstallDir).isDirectory(); + if (!skillExists) { + log(`JFrog ${skill.name}-${keepVersion} skill not found, importing them locally!`); const skillName = skill.name; - const skillVersion = skill.version; + const skillVersion = keepVersion; const skillZipDir = join(skillsDir, skillName, skillVersion); const skillZipFile = join(skillZipDir, `${skillName}-${skillVersion}.zip`); const result = await fetchAndSaveFile( @@ -242,9 +255,9 @@ const pullSkills = async ( } else { const unzipResult = await extractZip( $, - directory, skillZipFile, skillName, + skillVersion, skillZipDir, log ); @@ -252,9 +265,12 @@ const pullSkills = async ( log(`Failed to extract ${skillName} skill: ${unzipResult.error}`); failedSkills.push(skillName); } else { - log(`${skillName} skill extracted!`); + log(`${skillName} skill handling completed!`); + pruneNonManifestSkillVersions(skillsDir, skillName, keepVersion, log); } } + } else { + log(`JFrog ${skill.name}-${keepVersion} skill already present.`); } } // return success if no failed skills, otherwise return failed skills @@ -265,6 +281,54 @@ const pullSkills = async ( } }; +/** Runs whenever a skill is satisfied (fresh install or already on disk). Not inside import-only path. */ +const pruneNonManifestSkillVersions = ( + skillsDir: string, + skillName: string, + keepVersion: string, + log: (_msg: string) => void +) => { + const skillRoot = join(skillsDir, skillName); + if (!existsSync(skillRoot)) { + log(`No local version dirs for ${skillName} under ${skillRoot}`); + return; + } + let entries: string[]; + try { + entries = readdirSync(skillRoot); + } catch (e) { + log(`Could not list versions under ${skillRoot}: ${e}`); + return; + } + log( + `Found version dirs for ${skillName}: ${entries.join(', ')} (latest version: ${keepVersion})` + ); + for (const olderVersion of entries) { + const versionPath = join(skillRoot, olderVersion); + let isDir = false; + try { + isDir = statSync(versionPath).isDirectory(); + } catch { + continue; + } + if (!isDir) { + continue; + } + if (olderVersion === keepVersion) { + continue; + } + log( + `Removing non-manifest version ${olderVersion} of ${skillName} (manifest: ${keepVersion})...` + ); + try { + rmSync(versionPath, { recursive: true, force: true }); + log(`Removed ${skillName}/${olderVersion}`); + } catch (e) { + log(`Failed to remove ${skillName}/${olderVersion}: ${e}`); + } + } +}; + /** OpenCode loads plugins via the `server` export (see `PluginModule` in @opencode-ai/plugin). */ const jfrogOpencodePlugin: Plugin = async ({ client, $, directory }) => { const logDir = dirname(LOG_FILE); From deb530a5b21f35296e8d68300ab28a75fe5e05a3 Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Sun, 3 May 2026 17:53:47 +0300 Subject: [PATCH 2/7] chore: update CLA Assistant action to version 2.6.1 --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 475ab4e..1cac135 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -19,7 +19,7 @@ jobs: - name: "CLA Assistant" if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} # Alpha Release - uses: cla-assistant/github-action@v2.3.0 + uses: cla-assistant/github-action@v2.6.1 env: # Generated and maintained by GitHub GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 1a6ca035528cfe776002bd5cbb8741652381b64d Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Sun, 3 May 2026 17:55:23 +0300 Subject: [PATCH 3/7] chore: update CLA Assistant action reference to contributor-assistant --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 1cac135..cf01daa 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -19,7 +19,7 @@ jobs: - name: "CLA Assistant" if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} # Alpha Release - uses: cla-assistant/github-action@v2.6.1 + uses: contributor-assistant/github-action@v2.6.1 env: # Generated and maintained by GitHub GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 053c38f898b91c3afef83fbf37726f665c078cc2 Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Sun, 3 May 2026 18:01:28 +0300 Subject: [PATCH 4/7] chore: add backup configuration for CLA Assistant workflow --- .github/workflows/{cla.yml => cla.yml.back} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{cla.yml => cla.yml.back} (100%) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml.back similarity index 100% rename from .github/workflows/cla.yml rename to .github/workflows/cla.yml.back From c007f0acd6819e0598da7f39074f53b8b0e56061 Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Sun, 3 May 2026 18:06:51 +0300 Subject: [PATCH 5/7] chore: remove backup configuration for CLA Assistant workflow --- .github/workflows/{cla.yml.back => cla.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{cla.yml.back => cla.yml} (95%) diff --git a/.github/workflows/cla.yml.back b/.github/workflows/cla.yml similarity index 95% rename from .github/workflows/cla.yml.back rename to .github/workflows/cla.yml index cf01daa..a7e38b0 100644 --- a/.github/workflows/cla.yml.back +++ b/.github/workflows/cla.yml @@ -19,7 +19,7 @@ jobs: - name: "CLA Assistant" if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} # Alpha Release - uses: contributor-assistant/github-action@v2.6.1 + uses: contributor-assistant/github-action@v2.3.0 env: # Generated and maintained by GitHub GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4659dd45021332781e727234faf24b1c21e4fcd4 Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Tue, 5 May 2026 08:29:16 +0300 Subject: [PATCH 6/7] chore: update CLA Assistant action to version 2.6.1 --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index a7e38b0..cf01daa 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -19,7 +19,7 @@ jobs: - name: "CLA Assistant" if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} # Alpha Release - uses: contributor-assistant/github-action@v2.3.0 + uses: contributor-assistant/github-action@v2.6.1 env: # Generated and maintained by GitHub GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 687fac18f174bc4737a0dc87840dfe5760fb4753 Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Tue, 5 May 2026 08:39:09 +0300 Subject: [PATCH 7/7] feat: add CLA Assistant workflow for managing contributor agreements - Introduced a new GitHub Actions workflow to handle CLA signing and verification. - The workflow triggers on issue comments and pull requests, utilizing regex to check for CLA agreement. - Integrated with the contributor-assistant action to manage signatures and document references. --- .github/workflows/{cla.yml.back => cla.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{cla.yml.back => cla.yml} (100%) diff --git a/.github/workflows/cla.yml.back b/.github/workflows/cla.yml similarity index 100% rename from .github/workflows/cla.yml.back rename to .github/workflows/cla.yml