Skip to content

Feature/install skills#13

Merged
shanliuling merged 3 commits into
mainfrom
feature/install-skills
Apr 26, 2026
Merged

Feature/install skills#13
shanliuling merged 3 commits into
mainfrom
feature/install-skills

Conversation

@shanliuling
Copy link
Copy Markdown
Owner

@shanliuling shanliuling commented Apr 26, 2026

Feature/install skills

Summary by CodeRabbit

  • New Features

    • Introduced skills-link add command to install skills directly from GitHub repositories with optional skill selection for specific installations.
  • Documentation

    • Enhanced documentation covering supported subcommands (add, list, sync, app) with practical GitHub repository installation examples in English and Chinese.

- Add skills-link add command
- Support user/repo and full URL formats
- Support --skill flag to install specific skill
- Update README with new command
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Warning

Rate limit exceeded

@shanliuling has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 40 minutes and 4 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 40 minutes and 4 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b94ce763-afcd-4c59-986a-bb0229c9eb27

📥 Commits

Reviewing files that changed from the base of the PR and between 45091b8 and b88b1ef.

📒 Files selected for processing (2)
  • src/commands/add.ts
  • src/locales/zh.json
📝 Walkthrough

Walkthrough

The PR introduces a new skills-link add subcommand enabling installation of skills from GitHub repositories. Changes include CLI command wiring, a new command module with GitHub repo cloning and skill detection logic, localization for English and Chinese, documentation updates, and a minor version bump.

Changes

Cohort / File(s) Summary
CLI & Command Implementation
bin/skills-link.js, src/commands/add.ts
Adds add subcommand with support for GitHub URL/repo input and optional --skill flag. Implements full installation flow: cloning repos, detecting SKILL.md directories, prompting user selection, checking for overwrites, and managing temp files.
Localization
src/locales/en.json, src/locales/zh.json
Adds English and Chinese translations for new add command, covering CLI labels, usage help, validation messages, cloning status, skill selection prompts, overwrite confirmations, and completion notifications.
Documentation
README.md, README.zh.md
Expands skills-link command table with new subcommands (add, list, sync, app) and adds usage examples for installing skills from GitHub repositories.
Configuration & Versioning
.claude/settings.local.json, package.json
Updates Claude local settings to allow WebFetch and additional Bash execution permissions; increments version from 1.2.3 to 1.3.0.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as skills-link add
    participant Config as Config Loader
    participant Git as GitHub
    participant FS as File System
    participant Inquirer as User Selection

    User->>CLI: Run: skills-link add <repo> [--skill X]
    CLI->>Config: Load masterDir config
    Config-->>CLI: Config data
    alt Config invalid
        CLI-->>User: Error: Missing/invalid config
    else Config valid
        CLI->>Git: Clone repo (shallow)
        Git-->>FS: Create temp directory
        Git-->>FS: Clone into .temp-add/
        alt Clone succeeds
            CLI->>FS: Find directories with SKILL.md
            alt Found skills
                alt --skill specified
                    CLI->>CLI: Use specified skill
                else Single skill found
                    CLI->>CLI: Auto-select
                else Multiple skills
                    CLI->>Inquirer: Prompt skill selection
                    Inquirer-->>User: Show skill choices
                    User-->>Inquirer: Select skill
                    Inquirer-->>CLI: Selected skill
                end
                CLI->>Config: Check existing skills
                Config-->>CLI: Existing skill list
                alt Skill exists
                    CLI->>Inquirer: Prompt overwrite
                    Inquirer-->>User: Overwrite? (y/n)
                    User-->>Inquirer: Response
                    Inquirer-->>CLI: User choice
                    alt User declines
                        CLI-->>User: Cancelled
                    else User confirms
                        CLI->>FS: Remove old skill
                        CLI->>FS: Move new skill to masterDir
                        CLI-->>User: Installation success
                    end
                else New skill
                    CLI->>FS: Move skill to masterDir
                    CLI-->>User: Installation success
                end
                CLI->>FS: Clean up temp directory
            else No skills found
                CLI-->>User: Error: No SKILL.md found
                CLI->>FS: Remove temp directory
            end
        else Clone fails
            CLI-->>User: Error: Clone failed
            CLI->>FS: Remove temp directory
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Feature/list deduplicate #5: Adds a remove subcommand to bin/skills-link.js following the same CLI registration pattern as the new add command.

Poem

🐰 A skill to add, hopped right in place,
From GitHub clones, what a perfect space!
Temp dirs and choices, all neat and tidy,
Installation flows, oh how they guide ye!
SKILL.md detected—success, hooray! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/install skills' directly describes the main change: adding a new feature to install skills from GitHub repositories via the new 'skills-link add' command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/install-skills

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new add command to the skills-link CLI, enabling users to install skills directly from GitHub repositories. The update includes changes to the CLI entry point, documentation, and the addition of a new command module. Review feedback identified several critical issues in the add command implementation: a naming bug when skills are located at the repository root, a recursive search function that misses root-level skills and uses synchronous methods incorrectly, and redundant logic in URL parsing. Furthermore, inconsistencies were found in the localization files, where certain keys are unused or missing across languages.

Comment thread src/commands/add.ts
} else if (skillDirs.length === 1) {
// 只有一个,直接使用
targetDir = skillDirs[0]
skillName = path.basename(targetDir)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the skill is found at the root of the repository (i.e., targetDir is the temporary directory), path.basename(targetDir) will return .temp-add instead of the actual skill or repository name. You should handle this case by deriving the name from the repository URL or the user input.

Comment thread src/commands/add.ts
Comment on lines +195 to +216
async function findSkillDirs(dir: string): Promise<string[]> {
const results: string[] = []
const entries = fs.readdirSync(dir, { withFileTypes: true })

for (const entry of entries) {
const fullPath = path.join(dir, entry.name)

if (entry.isDirectory()) {
// 检查是否包含 SKILL.md
const skillFile = path.join(fullPath, 'SKILL.md')
if (fs.existsSync(skillFile)) {
results.push(fullPath)
} else {
// 递归搜索子目录
const subDirs = await findSkillDirs(fullPath)
results.push(...subDirs)
}
}
}

return results
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are two issues here:

  1. Bug: The function misses SKILL.md files located at the root of the repository because it only iterates over subdirectories.
  2. Consistency: The function is marked async but uses synchronous filesystem methods (fs.readdirSync, fs.existsSync). It's better to make it fully synchronous or use fs.promises.

Also, it's good practice to ignore the .git directory during recursion.

function findSkillDirs(dir: string): string[] {
  const results: string[] = []

  // Check if the directory itself is a skill
  if (fs.existsSync(path.join(dir, 'SKILL.md'))) {
    return [dir]
  }

  const entries = fs.readdirSync(dir, { withFileTypes: true })
  for (const entry of entries) {
    if (entry.isDirectory() && entry.name !== '.git') {
      const fullPath = path.join(dir, entry.name)
      const skillFile = path.join(fullPath, 'SKILL.md')
      if (fs.existsSync(skillFile)) {
        results.push(fullPath)
      } else {
        results.push(...findSkillDirs(fullPath))
      }
    }
  }

  return results
}

Comment thread src/commands/add.ts
await git.clone(repoUrl, tempDir, ['--depth', '1'])

// 查找 SKILL.md 文件
const skillDirs = await findSkillDirs(tempDir)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If findSkillDirs is changed to a synchronous function (as suggested below), the await keyword here should be removed.

Suggested change
const skillDirs = await findSkillDirs(tempDir)
const skillDirs = findSkillDirs(tempDir)

Comment thread src/commands/add.ts Outdated
Comment on lines +173 to +175
if (input.startsWith('https://github.com/')) {
return input.replace('https://github.com/', 'https://github.com/')
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This replacement is redundant as it replaces the string with itself. You can simply return the input.

Suggested change
if (input.startsWith('https://github.com/')) {
return input.replace('https://github.com/', 'https://github.com/')
}
if (input.startsWith('https://github.com/')) {
return input
}

Comment thread src/locales/zh.json Outdated
Comment on lines +432 to +434
"linkPrompt": "是否立即创建链接?",
"linking": "正在创建链接...",
"linksCreated": "已创建 {count} 个链接",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These translation keys (linkPrompt, linking, linksCreated) are present in the Chinese locale but missing in the English locale. Additionally, they do not appear to be used in the current implementation of the add command. If you intended to offer automatic linking after installation, the logic seems to be missing from src/commands/add.ts.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
bin/skills-link.js (1)

97-103: Argument/option descriptions are not localized.

The command description uses t('cli.commands.add'), but the <repo> argument and --skill option descriptions are hardcoded English. Other commands consistently use t(...) for option help (e.g., cli.options.message, cli.options.dryRun). Consider adding cli.options.skill (and an arg description key) to src/locales/{en,zh}.json and using t(...) here for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/skills-link.js` around lines 97 - 103, The argument and option
descriptions for the "add" command are hardcoded English; update the
.argument('<repo>', ...) and .option('-s, --skill <name>', ...) calls in the
program.command('add') handler to use localized strings via t(...), e.g., add
keys like cli.args.repo and cli.options.skill to your locale files
(src/locales/{en,zh}.json) and replace the literal descriptions with
t('cli.args.repo') and t('cli.options.skill') so they match other commands that
use t(...).
src/commands/add.ts (1)

195-216: findSkillDirs recurses into .git and other irrelevant directories.

The cloned repo always has a .git subtree which can contain thousands of entries; the function recurses into it because it has no SKILL.md. Skipping .git, node_modules, and other dotfile dirs would be a meaningful speedup on real repos and is a one-line guard.

   for (const entry of entries) {
+    if (entry.name === '.git' || entry.name === 'node_modules' || entry.name.startsWith('.')) {
+      continue
+    }
     const fullPath = path.join(dir, entry.name)

Also, this is declared async but uses fully synchronous fs.readdirSync / fs.existsSync; consider switching to fs.promises.readdir for consistency, or drop the async and unnecessary await at line 209.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/add.ts` around lines 195 - 216, The findSkillDirs function
currently recurses into irrelevant dot dirs (like .git and node_modules) and
mixes synchronous fs calls with async/await; fix by skipping dot and known junk
dirs and making I/O consistently async: inside findSkillDirs, add a
short-circuit guard that ignores directories whose name starts with '.' or are
in a SKIP_DIRS set (e.g., const SKIP_DIRS = new Set(['.git','node_modules'])),
and convert the body to use fs.promises.readdir(fullPath, { withFileTypes: true
}) and fs.promises.access or fs.promises.stat for checking SKILL.md so you can
await the recursive call to findSkillDirs(fullPath) rather than calling sync
APIs; update the await at the recursive call site accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/add.ts`:
- Around line 168-190: parseRepoUrl currently has a no-op replace and a loose
host/path check; update parseRepoUrl to (1) normalize and validate explicit
GitHub URLs only by checking hostname for https/http via new URL(input).hostname
=== 'github.com' and accept SSH only when it starts with 'git@github.com:'; (2)
remove the useless input.replace(...) branch and instead normalize by stripping
a trailing '.git' and any trailing slash from returned URLs; and (3) tighten the
user/repo fallback by validating input against a safe regex like
^[\\w.-]+/[\\w.-]+$ before returning `https://github.com/${input}` so inputs
like '../foo/bar' are rejected. Ensure these changes are applied inside the
parseRepoUrl function.
- Around line 58-162: The temp clone uses a fixed tempDir (const tempDir =
path.join(masterDir, '.temp-add')) which risks leftover .git and race
conditions; change to create a unique temp directory using os.tmpdir() and
fs.mkdtempSync('skills-link-add-') (or append process.pid/Date.now()) and wrap
the main work in a try { ... } finally { if (fs.existsSync(tempDir))
fs.rmSync(tempDir, { recursive: true, force: true }) } to guarantee cleanup;
also replace fs.renameSync(targetDir, destDir) with a cross-device-safe
copy-then-remove (or use a recursive copy helper) to move from tempDir to
destDir; update references to tempDir, masterDir, and functions
findSkillDirs/listSkills accordingly.

In `@src/locales/en.json`:
- Around line 418-435: The en.json "add" namespace is missing the keys
"linkPrompt", "linking", and "linksCreated" that exist in zh.json; update the
en.json "add" block to include these three keys (with appropriate English text)
if the intended flow will call runAdd's linking step, or remove those keys from
zh.json if no linking step is ever invoked; check the runAdd function to decide
whether to add and wire these messages into the linking flow (use the same key
names: linkPrompt, linking, linksCreated) so both locales remain parity with the
actual runAdd behavior.

---

Nitpick comments:
In `@bin/skills-link.js`:
- Around line 97-103: The argument and option descriptions for the "add" command
are hardcoded English; update the .argument('<repo>', ...) and .option('-s,
--skill <name>', ...) calls in the program.command('add') handler to use
localized strings via t(...), e.g., add keys like cli.args.repo and
cli.options.skill to your locale files (src/locales/{en,zh}.json) and replace
the literal descriptions with t('cli.args.repo') and t('cli.options.skill') so
they match other commands that use t(...).

In `@src/commands/add.ts`:
- Around line 195-216: The findSkillDirs function currently recurses into
irrelevant dot dirs (like .git and node_modules) and mixes synchronous fs calls
with async/await; fix by skipping dot and known junk dirs and making I/O
consistently async: inside findSkillDirs, add a short-circuit guard that ignores
directories whose name starts with '.' or are in a SKIP_DIRS set (e.g., const
SKIP_DIRS = new Set(['.git','node_modules'])), and convert the body to use
fs.promises.readdir(fullPath, { withFileTypes: true }) and fs.promises.access or
fs.promises.stat for checking SKILL.md so you can await the recursive call to
findSkillDirs(fullPath) rather than calling sync APIs; update the await at the
recursive call site accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dfe2637b-7505-4115-8694-d41830726ece

📥 Commits

Reviewing files that changed from the base of the PR and between bdffedd and 45091b8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .claude/settings.local.json
  • README.md
  • README.zh.md
  • bin/skills-link.js
  • package.json
  • src/commands/add.ts
  • src/locales/en.json
  • src/locales/zh.json

Comment thread src/commands/add.ts
Comment thread src/commands/add.ts
Comment thread src/locales/en.json
Comment on lines +418 to 435
"add": {
"noConfig": "Config file not found",
"runSetupFirst": "Please run skills-link setup first",
"masterDirNotExist": "Master directory does not exist",
"usage": "Usage: skills-link add <repo>",
"usageExample": "Example: skills-link add vercel-labs/agent-skills",
"invalidUrl": "Invalid GitHub address",
"cloning": "Cloning {url}...",
"cloneFailed": "Clone failed: {error}",
"noSkillFound": "No SKILL.md file found",
"selectSkill": "Select skill to install:",
"skillExists": "Skill {name} already exists",
"overwritePrompt": "Overwrite?",
"installed": "Installed: {name}",
"done": "Install complete!",
"skillNotFound": "Skill not found: {name}",
"availableSkills": "Available skills:"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Locale parity: add namespace differs between en.json and zh.json.

src/locales/zh.json adds three additional keys under add: linkPrompt, linking, linksCreated. None of these exist here in en.json, and src/commands/add.ts does not reference them either. Either drop them from zh.json (current code path doesn't create links after install) or, if a follow-up "create links" step is intended, mirror the keys here and wire them into runAdd. Otherwise users running with --lang zh will see strings that are never displayed and English users won't have parity if they ever are.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/locales/en.json` around lines 418 - 435, The en.json "add" namespace is
missing the keys "linkPrompt", "linking", and "linksCreated" that exist in
zh.json; update the en.json "add" block to include these three keys (with
appropriate English text) if the intended flow will call runAdd's linking step,
or remove those keys from zh.json if no linking step is ever invoked; check the
runAdd function to decide whether to add and wire these messages into the
linking flow (use the same key names: linkPrompt, linking, linksCreated) so both
locales remain parity with the actual runAdd behavior.

- Use unique temp dir with os.tmpdir() and timestamp
- Add try/finally for guaranteed cleanup
- Use recursive copy instead of rename for cross-device safety
- Tighten URL validation with regex for user/repo
- Remove unused linking prompt
@shanliuling shanliuling merged commit 37a5242 into main Apr 26, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant