feat(platforms): add Hermes Agent platform support#110
feat(platforms): add Hermes Agent platform support#110legeling merged 3 commits intolegeling:mainfrom
Conversation
- Add Hermes Agent to SKILL_PLATFORMS with skills dir at ~/.hermes/skills - Support one-level nested directory scanning for category-nested SKILL.md files (e.g., ~/.hermes/skills/apple/apple-reminders/SKILL.md)
- Add hermes.svg icon asset (Caduceus/double-snake staff design) - Register Hermes in PLATFORM_ICONS and FALLBACK_ICONS mappings - Hermes platform entry already existed in platforms.ts, but the UI had no icon import, so Hermes appeared without an icon in the UI
|
@caohong is attempting to deploy a commit to the legeling's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthrough新增对 Hermes Agent 平台的支持(常量与 UI 图标映射),并重构桌面端技能扫描逻辑以在扫描路径的一层或两层子目录中发现 Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 诗歌
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
Review Summary by QodoAdd Hermes Agent platform with nested skill scanning
WalkthroughsDescription• Add Hermes Agent platform with nested skill directory scanning support • Support one-level nested directory structures for category-organized skills • Add Hermes platform icon asset and UI mappings • Refactor skill scanning logic to handle both flat and nested skill layouts Diagramflowchart LR
A["Hermes Agent Platform"] --> B["Nested Skill Scanning"]
A --> C["Platform Icon Asset"]
B --> D["Support flat & nested layouts"]
C --> E["UI Icon Mappings"]
E --> F["PlatformIcon Component"]
File Changes1. apps/desktop/src/main/services/skill-installer.ts
|
Code Review by Qodo
1.
|
| id: "hermes", | ||
| name: "Hermes Agent", | ||
| icon: "Bot", | ||
| skillsDir: { | ||
| darwin: "~/.hermes/skills", | ||
| win32: "%USERPROFILE%\\.hermes\\skills", | ||
| linux: "~/.hermes/skills", | ||
| }, |
There was a problem hiding this comment.
2. Hardcoded hermes agent name 📘 Rule violation ⚙ Maintainability
A new user-visible platform label Hermes Agent is hardcoded in source instead of being localized via react-i18next. This introduces non-localized UI text in the platform list/settings UI.
Agent Prompt
## Issue description
A new platform display name is hardcoded (`Hermes Agent`) and is rendered directly in the UI.
## Issue Context
`SKILL_PLATFORMS` is used by the renderer to display `platform.name` in settings, so literals added here become user-facing UI strings.
## Fix Focus Areas
- packages/shared/constants/platforms.ts[175-184]
- apps/desktop/src/renderer/components/settings/SkillSettings.tsx[367-380]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/desktop/src/main/services/skill-installer.ts (1)
515-540: 建议抽取共享的“目录发现”逻辑,避免双处实现漂移。
scanLocal和scanLocalPreview中一层嵌套扫描实现基本重复,后续若再改扫描规则容易出现不一致。建议提取为私有 helper(例如collectSkillDirs(scanPath))复用。Also applies to: 672-697
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/services/skill-installer.ts` around lines 515 - 540, The directory discovery logic in scanLocal and scanLocalPreview is duplicated and should be extracted into a single private helper (e.g., collectSkillDirs(scanPath)) to avoid drift; implement collectSkillDirs that accepts a scanPath, uses the existing pattern (iterate entries, push directories, check for SKILL.md directly and in one-level subdirectories using fileExists and fs.readdir with withFileTypes), returns the array of discovered skill directories, and replace the duplicated loops in scanLocal and scanLocalPreview to call collectSkillDirs(scanPath) so all directory-resolution logic (including error handling) lives in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/services/skill-installer.ts`:
- Line 539: Two empty catch blocks that read "catch { /* ignore directory read
errors */ }" silently swallow filesystem errors; replace each with a catch (err)
{ logger.warn("Failed reading skill directory, skipping", err) } (or
logger.debug if more appropriate) so EACCES/EPERM and other IO errors are
recorded; locate the two occurrences of the exact snippet "catch { /* ignore
directory read errors */ }" in skill-installer.ts and use the module's existing
logger (or console.warn if no logger is available) to log the error with context
instead of swallowing it.
In `@apps/desktop/src/renderer/components/ui/PlatformIcon.tsx`:
- Line 33: PlatformIcon currently imports platform SVG/PNG assets (e.g.,
hermes.svg) which violates AGENTS.md; update the PlatformIcon component so it
uses lucide-react icons as the primary source: remove direct asset imports
(e.g., hermesIcon and other platform asset imports), create a mapping from
platform identifiers to lucide-react icon components inside PlatformIcon, render
the lucide-react icon first (with the existing fallback behavior preserved to
load legacy asset files only if no matching lucide icon exists), and add a
comment referencing AGENTS.md to document any deliberate exceptions if you must
keep a legacy asset fallback.
---
Nitpick comments:
In `@apps/desktop/src/main/services/skill-installer.ts`:
- Around line 515-540: The directory discovery logic in scanLocal and
scanLocalPreview is duplicated and should be extracted into a single private
helper (e.g., collectSkillDirs(scanPath)) to avoid drift; implement
collectSkillDirs that accepts a scanPath, uses the existing pattern (iterate
entries, push directories, check for SKILL.md directly and in one-level
subdirectories using fileExists and fs.readdir with withFileTypes), returns the
array of discovered skill directories, and replace the duplicated loops in
scanLocal and scanLocalPreview to call collectSkillDirs(scanPath) so all
directory-resolution logic (including error handling) lives in one place.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5ccd66a2-6bab-4557-95e3-4a6d7f852acd
⛔ Files ignored due to path filters (1)
apps/desktop/src/renderer/assets/platforms/hermes.svgis excluded by!**/*.svg
📒 Files selected for processing (3)
apps/desktop/src/main/services/skill-installer.tsapps/desktop/src/renderer/components/ui/PlatformIcon.tsxpackages/shared/constants/platforms.ts
| import qoderworkIcon from "../../assets/platforms/qoderwork.png"; | ||
| import codebuddyLightIcon from "../../assets/platforms/codebuddy-light.svg"; | ||
| import codebuddyDarkIcon from "../../assets/platforms/codebuddy-dark.svg"; | ||
| import hermesIcon from "../../assets/platforms/hermes.svg"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 定位规范文档(大小写不敏感)
fd -i 'gemini.md'
# 2) 查看规范中与图标约束相关的内容
fd -i 'gemini.md' --exec rg -n "lucide-react|icon|SVG|custom" {}
# 3) 盘点 renderer/components 下平台资产导入现状,判断是否已有统一例外
rg -n --type=tsx 'assets/platforms/.*\.(svg|png)' apps/desktop/src/renderer/componentsRepository: legeling/PromptHub
Length of output: 90
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 定位并查看 GEMINI.md 规范文档
echo "=== 查找 GEMINI.md ==="
find . -iname "gemini.md" -type f
# 2) 使用正确的文件类型搜索 tsx 文件中的 SVG 导入
echo "=== 搜索 renderer/components 中的 SVG/PNG 平台资产导入 ==="
rg -t ts 'assets/platforms' apps/desktop/src/renderer/components
# 3) 查看 PlatformIcon.tsx 文件的完整实现
echo "=== 查看 PlatformIcon.tsx 内容 ==="
cat -n apps/desktop/src/renderer/components/ui/PlatformIcon.tsxRepository: legeling/PromptHub
Length of output: 8729
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 查找可能的规范文档(README、GUIDELINES、STYLE等)
echo "=== 查找规范相关文档 ==="
find . -maxdepth 2 -type f \( -iname "README*" -o -iname "*GUIDELINE*" -o -iname "*STYLE*" -o -iname "*CONVENTION*" -o -iname "*.md" \) | head -20
# 2) 搜索仓库中关于图标规范的任何提及
echo "=== 搜索仓库中关于 lucide-react 图标的提及 ==="
rg -t md "lucide-react" | head -20
# 3) 检查是否有其他组件同样使用自定义 SVG/PNG 资产
echo "=== 检查其他组件的图标导入模式 ==="
rg -t ts "import.*from.*assets/.*\.(svg|png)" apps/desktop/src/renderer/components --countRepository: legeling/PromptHub
Length of output: 835
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 查看 AGENTS.md 中的图标规范部分
echo "=== AGENTS.md 中的图标规范 ==="
rg -B 5 -A 5 "lucide-react" AGENTS.mdRepository: legeling/PromptHub
Length of output: 1770
整个 PlatformIcon.tsx 违反了 AGENTS.md 中"仅使用 lucide-react"的规范。
Line 33 的 hermes.svg 导入遵循了文件既有的模式,但该模式本身违反架构规范。AGENTS.md 明确要求:"Use lucide-react for all icons. Do not import other icon libraries"。
该文件包含 18+ 个自定义平台资产(PNG 和 SVG)导入,应重构为:
- 仅使用 lucide-react 作为主要图标源,或
- 为平台图标建立明确的例外规则并在 AGENTS.md 中记录
组件的 fallback 机制设计合理,但当前实现优先加载自定义资产而非默认使用 lucide。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/renderer/components/ui/PlatformIcon.tsx` at line 33,
PlatformIcon currently imports platform SVG/PNG assets (e.g., hermes.svg) which
violates AGENTS.md; update the PlatformIcon component so it uses lucide-react
icons as the primary source: remove direct asset imports (e.g., hermesIcon and
other platform asset imports), create a mapping from platform identifiers to
lucide-react icon components inside PlatformIcon, render the lucide-react icon
first (with the existing fallback behavior preserved to load legacy asset files
only if no matching lucide icon exists), and add a comment referencing AGENTS.md
to document any deliberate exceptions if you must keep a legacy asset fallback.
- Extract collectSkillDirs() shared helper to eliminate duplicated nested
scan logic between scanLocal() and scanLocalPreview()
- Replace empty catch{} blocks with console.warn() to avoid silently
swallowing fs.readdir() errors
- Replace hardcoded fallback strings ("Local skill found in ...", "Local")
with undefined to let sanitizeImportedSkillDraft handle defaults
- Use @renderer/ path alias for hermes.svg import instead of relative path
Note: "Hermes Agent" platform name follows the same convention as all
other 15 platforms in platforms.ts (all use hardcoded English names).
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/services/skill-installer.ts`:
- Around line 526-547: The collectSkillDirs logic currently stops scanning
subdirectories when baseDir contains SKILL.md (the else at the directMd check),
missing nested skill dirs; modify collectSkillDirs so that after detecting
directMd (directMd or baseDir/SKILL.md) it still scans the immediate children:
always run the fs.readdir + subEntries loop (or remove the else) for baseDir,
checking each sub.isDirectory() and testing path.join(nestedDir, "SKILL.md") to
push nestedDir into result; keep the existing try/catch and only skip on real
read errors.
- Line 642: There are extra closing braces before two catch blocks that break
the try/catch pairing in skill-installer.ts; remove the stray '}' immediately
before each "catch (e)" occurrence so that the catch clauses directly follow
their corresponding try blocks (specifically the two "catch (e)" blocks shown in
the diff), ensuring the try/catch structures around those sections (look for the
surrounding try blocks and the nearby functions in skill-installer.ts) are
properly paired and parse without error.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 848e4fdf-b27b-468c-93ce-0a8ed42d77b7
📒 Files selected for processing (2)
apps/desktop/src/main/services/skill-installer.tsapps/desktop/src/renderer/components/ui/PlatformIcon.tsx
✅ Files skipped from review due to trivial changes (1)
- apps/desktop/src/renderer/components/ui/PlatformIcon.tsx
| if (await fileExists(directMd)) { | ||
| result.push(baseDir); | ||
| } else { | ||
| // Check subdirectories for category-nested structures (e.g., Hermes) | ||
| try { | ||
| const subEntries = await fs.readdir(baseDir, { withFileTypes: true }); | ||
| for (const sub of subEntries) { | ||
| if (sub.isDirectory()) { | ||
| const nestedDir = path.join(baseDir, sub.name); | ||
| if (await fileExists(path.join(nestedDir, "SKILL.md"))) { | ||
| result.push(nestedDir); | ||
| } | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.warn( | ||
| `Failed reading skill directory: ${baseDir}, skipping`, | ||
| err, | ||
| ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
collectSkillDirs 会漏扫“父目录和子目录都含 SKILL.md”的场景。
Line 528 的 else 使得只要 baseDir/SKILL.md 存在,就不会继续扫描一层子目录;这会漏掉同目录树下的嵌套技能。
🔧 建议修正
- if (await fileExists(directMd)) {
- result.push(baseDir);
- } else {
- // Check subdirectories for category-nested structures (e.g., Hermes)
- try {
- const subEntries = await fs.readdir(baseDir, { withFileTypes: true });
- for (const sub of subEntries) {
- if (sub.isDirectory()) {
- const nestedDir = path.join(baseDir, sub.name);
- if (await fileExists(path.join(nestedDir, "SKILL.md"))) {
- result.push(nestedDir);
- }
- }
- }
- } catch (err) {
- console.warn(
- `Failed reading skill directory: ${baseDir}, skipping`,
- err,
- );
- }
- }
+ if (await fileExists(directMd)) {
+ result.push(baseDir);
+ }
+
+ // 也扫描一层子目录,避免漏掉“父目录与子目录都含 SKILL.md”的情况
+ try {
+ const subEntries = await fs.readdir(baseDir, { withFileTypes: true });
+ for (const sub of subEntries) {
+ if (sub.isDirectory()) {
+ const nestedDir = path.join(baseDir, sub.name);
+ if (await fileExists(path.join(nestedDir, "SKILL.md"))) {
+ result.push(nestedDir);
+ }
+ }
+ }
+ } catch (err) {
+ console.warn(
+ `Failed reading skill directory: ${baseDir}, skipping`,
+ err,
+ );
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/services/skill-installer.ts` around lines 526 - 547,
The collectSkillDirs logic currently stops scanning subdirectories when baseDir
contains SKILL.md (the else at the directMd check), missing nested skill dirs;
modify collectSkillDirs so that after detecting directMd (directMd or
baseDir/SKILL.md) it still scans the immediate children: always run the
fs.readdir + subEntries loop (or remove the else) for baseDir, checking each
sub.isDirectory() and testing path.join(nestedDir, "SKILL.md") to push nestedDir
into result; keep the existing try/catch and only skip on real read errors.
| } | ||
| } | ||
| } catch (e) { | ||
| } catch (e) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
sed -n '620,780p' apps/desktop/src/main/services/skill-installer.ts
rg -n '^\s*}\s*catch\s*\(' apps/desktop/src/main/services/skill-installer.tsRepository: legeling/PromptHub
Length of output: 7340
🏁 Script executed:
# Get the exact number of lines in the file to understand its structure
wc -l apps/desktop/src/main/services/skill-installer.tsRepository: legeling/PromptHub
Length of output: 116
🏁 Script executed:
# Read the specific sections around lines 640-650 and 765-775 to see bracket matching
sed -n '635,650p' apps/desktop/src/main/services/skill-installer.ts | cat -n
echo "---"
sed -n '760,775p' apps/desktop/src/main/services/skill-installer.ts | cat -nRepository: legeling/PromptHub
Length of output: 1100
🏁 Script executed:
# Check if there's a TypeScript/JavaScript syntax validator available
# Try to find any configuration files for linters like Biome
find . -maxdepth 2 -name "biome.json" -o -name ".biomerc" -o -name "tsconfig.json" | head -10Repository: legeling/PromptHub
Length of output: 75
🏁 Script executed:
# Count opening and closing braces in relevant sections to verify structure
python3 << 'PYEOF'
import re
# Read the file
with open('apps/desktop/src/main/services/skill-installer.ts', 'r') as f:
lines = f.readlines()
# Extract sections around problematic lines
section1 = lines[620:650] # Around line 642
section2 = lines[755:780] # Around line 768
def analyze_braces(lines, start_line):
"""Analyze brace balance in a section"""
open_count = 0
close_count = 0
for i, line in enumerate(lines):
open_count += line.count('{')
close_count += line.count('}')
return open_count, close_count
o1, c1 = analyze_braces(section1, 620)
o2, c2 = analyze_braces(section2, 755)
print(f"Section 1 (lines 620-650): {o1} opens, {c1} closes, diff={o1-c1}")
print(f"Section 2 (lines 755-780): {o2} opens, {c2} closes, diff={o2-c2}")
# Print the actual lines to inspect
print("\n=== Section 1 (lines 620-650) ===")
for i, line in enumerate(section1, start=620):
print(f"{i}: {line.rstrip()}")
print("\n=== Section 2 (lines 755-780) ===")
for i, line in enumerate(section2, start=755):
print(f"{i}: {line.rstrip()}")
PYEOFRepository: legeling/PromptHub
Length of output: 2486
🏁 Script executed:
# Check if Biome is installed and can be run
which biome || npm list -g biome 2>/dev/null || echo "Biome not found globally"
cd apps/desktop && npm list biome 2>/dev/null | head -20Repository: legeling/PromptHub
Length of output: 254
🏁 Script executed:
# Try to run Biome on the file if available
if command -v biome &> /dev/null; then
biome check apps/desktop/src/main/services/skill-installer.ts 2>&1 | head -100
elif [ -f "apps/desktop/node_modules/.bin/biome" ]; then
apps/desktop/node_modules/.bin/biome check apps/desktop/src/main/services/skill-installer.ts 2>&1 | head -100
else
echo "Biome not available in this environment"
fiRepository: legeling/PromptHub
Length of output: 2953
🏁 Script executed:
# Get wider context around the first try block (likely starts earlier)
# Look backwards from line 642 to find where the try starts
sed -n '550,650p' apps/desktop/src/main/services/skill-installer.ts | cat -nRepository: legeling/PromptHub
Length of output: 4681
🏁 Script executed:
# Check the structure around line 768 with more context
sed -n '700,780p' apps/desktop/src/main/services/skill-installer.ts | cat -nRepository: legeling/PromptHub
Length of output: 4367
try/catch 块结构存在多余闭合块导致语法错误,是阻断发布问题。
Biome 已确认在第 642 行和第 768 行处的 catch 语句报出 parse error。在这两处 catch 之前各存在 2 个多余的 } 闭合符,导致 catch 与其对应的 try 块断裂,引发解析失败。
请删除第 641 行和第 767 行各多余的一个 },使 catch 子句能正确关联其对应的 try 块。
修正方案
第一处(行 640-643):
}
}
- }
- } catch (e) {
+ } catch (e) {
console.error(`Failed to scan path: ${scanPath}`, e);第二处(行 766-769):
}
}
- }
- } catch (e) {
+ } catch (e) {
console.error(`Failed to scan path: ${scanPath}`, e);🧰 Tools
🪛 Biome (2.4.13)
[error] 642-642: Expected a catch clause but instead found '}'.
(parse)
[error] 642-642: Expected a statement but instead found 'catch (e)'.
(parse)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/services/skill-installer.ts` at line 642, There are
extra closing braces before two catch blocks that break the try/catch pairing in
skill-installer.ts; remove the stray '}' immediately before each "catch (e)"
occurrence so that the catch clauses directly follow their corresponding try
blocks (specifically the two "catch (e)" blocks shown in the diff), ensuring the
try/catch structures around those sections (look for the surrounding try blocks
and the nearby functions in skill-installer.ts) are properly paired and parse
without error.
|
nice |
|
coderabbit发现了一些问题,可以先看着改一改 |
Summary
Add Hermes Agent as a custom skill platform with full support for nested skill scanning and icon display.
Changes
apps/desktop/src/main/services/skill-installer.tspackages/shared/constants/platforms.tsapps/desktop/src/renderer/components/ui/PlatformIcon.tsxapps/desktop/src/renderer/assets/platforms/hermes.svgTest Status
Commits
This PR follows the contribution guidelines in CONTRIBUTING.md
Summary by CodeRabbit
发布说明
新功能
改进