feat(skills): render full skill directories#2604
Merged
Merged
Conversation
Skills become directories in the UI, not a single markdown file: - SkillsService.getSkillContents(skillPath) returns the skill's file tree (relative paths + sizes); readSkillFile(skillPath, relPath) returns file contents. Both validate the target resolves to a skill directory directly under a discovery root, so the endpoints cannot be used for arbitrary filesystem reads. Symlinked files are skipped. - skills.contents / skills.readFile host-router queries (one-line forwards with Zod output schemas). - SkillInfo gains a derived editable flag (user/repo true, bundled/marketplace false), computed server-side. - SkillDetailPanel shows a file tree for multi-file skills; SKILL.md renders as markdown (frontmatter stripped) as before, other files open in read-only CodeMirror. Non-editable skills get a lock badge. - Unit tests cover discovery, the file walker (symlink skipping, file cap) and ../ traversal rejection on both new endpoints. Generated-By: PostHog Code Task-Id: f4e84f1a-19c9-490c-9b98-47787a7dddcf
The plan behind the skills-01..07 stack: goals, non-goals, source/ editability model, per-PR scope, and security notes. Generated-By: PostHog Code Task-Id: f4e84f1a-19c9-490c-9b98-47787a7dddcf
Contributor
Author
This was referenced Jun 11, 2026
|
React Doctor could not complete this scan.
Reviewed by React Doctor for commit |
Contributor
|
tatoalo
approved these changes
Jun 11, 2026
…stsSync Review feedback on #2604: realpath containment check covers both direct and intermediate-directory symlink escapes; resolveKnownSkillDir now uses async fs.promises.access. Generated-By: PostHog Code Task-Id: f4e84f1a-19c9-490c-9b98-47787a7dddcf
d3d7209 to
abb385b
Compare
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
The Skills tab only rendered
SKILL.mdfrom each skill directory and had no awareness of companion files (references/,scripts/, assets). There was also no way to distinguish editable skills (user/repo) from read-only ones (bundled/marketplace) in the UI.Changes
Shared types
editable: booleantoSkillInfo— computed server-side based on source, not in the UI.SkillFileEntrytype (path,size) to@posthog/shared.Backend (
workspace-server)listSkillFiles()toskill-discovery.ts: recursively walks a skill directory, skips symlinks (preventing path escape via crafted skills), sorts withSKILL.mdfirst, and respects a file cap.isEditableSource()helper;readSkillMetadataFromDirnow setseditableon eachSkillInfo.getSkillContents(skillPath)andreadSkillFile(skillPath, filePath)toSkillsService. Both validate the requested path against the known discovery roots before touching the filesystem —getSkillContentschecks the skill directory is directly under a known root and containsSKILL.md;readSkillFileadditionally checks the resolved file path stays within the skill directory, rejecting../traversal and absolute paths.listSkillsto derive skill roots via a sharedgetSkillRoots()helper used by all three methods.skillContentsInput/Output,readSkillFileInput/Output,skillFileEntry) toschemas.ts.Router (
host-router)skills.contentsandskills.readFiletRPC query procedures as one-line forwards toSkillsService.UI
SkillDetailPanel: replaced the singleuseAbsoluteFileContentcall withuseSkillContents+useSkillFile. When a skill has more than one file, a collapsible file tree appears above the content area.SKILL.mdcontinues to render as markdown; all other files open in a read-only CodeMirror editor. A "Read-only" lock badge is shown for non-editable skills.SkillFileTreecomponent: builds a nested directory tree from flatSkillFileEntry[]and renders it using the existingTreeDirectoryRow/TreeFileRowprimitives with expand/collapse state.useSkillContents/useSkillFilehooks backed by the new tRPC procedures.key={selectedSkill.path}toSkillDetailPanelinSkillsViewso state resets when switching between skills.docs/plans/skills-tab.md) covering the full seven-PR roadmap for the Skills tab.How did you test this?
listSkillFiles: nested directory walking with size reporting, symlink skipping, and file-cap enforcement.readSkillMetadataFromDir: verifieseditableistrueforuser/reposources andfalseforbundled/marketplace.skills.test.tscoveringSkillsServiceend-to-end against a real temp directory:listSkillseditable flags,getSkillContentsfull file listing, path-traversal rejection on bothgetSkillContentsandreadSkillFile, absolute path rejection, empty path rejection, and null return for missing files.Automatic notifications