@@ -25,7 +25,7 @@ const (
2525 skillsRepoOwner = "databricks"
2626 skillsRepoName = "databricks-agent-skills"
2727 skillsRepoPath = "skills"
28- defaultSkillsRepoRef = "v0.1.3 "
28+ defaultSkillsRepoRef = "v0.1.4 "
2929)
3030
3131// fetchFileFn is the function used to download individual skill files.
@@ -73,9 +73,20 @@ func FetchManifest(ctx context.Context) (*Manifest, error) {
7373 return src .FetchManifest (ctx , ref )
7474}
7575
76+ // sharedFilePrefix marks files in the manifest that live at the repo root
77+ // rather than under skills/<name>/. The CLI strips this prefix when writing
78+ // to disk and adjusts the download URL accordingly.
79+ const sharedFilePrefix = "@root:"
80+
7681func fetchSkillFile (ctx context.Context , ref , skillName , filePath string ) ([]byte , error ) {
77- url := fmt .Sprintf ("https://raw.githubusercontent.com/%s/%s/%s/%s/%s/%s" ,
78- skillsRepoOwner , skillsRepoName , ref , skillsRepoPath , skillName , filePath )
82+ var url string
83+ if rootPath , ok := strings .CutPrefix (filePath , sharedFilePrefix ); ok {
84+ url = fmt .Sprintf ("https://raw.githubusercontent.com/%s/%s/%s/%s" ,
85+ skillsRepoOwner , skillsRepoName , ref , rootPath )
86+ } else {
87+ url = fmt .Sprintf ("https://raw.githubusercontent.com/%s/%s/%s/%s/%s/%s" ,
88+ skillsRepoOwner , skillsRepoName , ref , skillsRepoPath , skillName , filePath )
89+ }
7990
8091 req , err := http .NewRequestWithContext (ctx , http .MethodGet , url , nil )
8192 if err != nil {
@@ -390,14 +401,33 @@ type installParams struct {
390401 ref string
391402}
392403
404+ // agentMetadataDirs lists subdirectory prefixes that are agent-specific UI
405+ // metadata (marketplace icons, display names). Only agents with
406+ // InstallAgentMetadata=true receive these during a copy install.
407+ var agentMetadataDirs = []string {"agents" , "assets" }
408+
409+ // isAgentMetadataPath reports whether rel (forward-slash separated) falls
410+ // under one of the agent metadata directories.
411+ func isAgentMetadataPath (rel string ) bool {
412+ normalized := filepath .ToSlash (rel )
413+ for _ , dir := range agentMetadataDirs {
414+ if normalized == dir || strings .HasPrefix (normalized , dir + "/" ) {
415+ return true
416+ }
417+ }
418+ return false
419+ }
420+
393421func installSkillForAgents (ctx context.Context , skillName string , files []string , detectedAgents []* agents.Agent , params installParams ) error {
394422 canonicalDir := filepath .Join (params .baseDir , skillName )
395423 if err := installSkillToDir (ctx , params .ref , skillName , canonicalDir , files ); err != nil {
396424 return err
397425 }
398426
399427 // For project scope, always symlink. For global, symlink when multiple agents.
400- useSymlinks := params .scope == ScopeProject || len (detectedAgents ) > 1
428+ // Agents with PreferSkillCopy always get a full directory copy so
429+ // marketplace metadata under agents/ and assets/ stays under their config dir.
430+ symlinkEligible := params .scope == ScopeProject || len (detectedAgents ) > 1
401431
402432 for _ , agent := range detectedAgents {
403433 agentSkillDir , err := agentSkillsDirForScope (ctx , agent , params .scope , params .cwd )
@@ -413,7 +443,8 @@ func installSkillForAgents(ctx context.Context, skillName string, files []string
413443 continue
414444 }
415445
416- if useSymlinks {
446+ useSymlink := symlinkEligible && ! agent .PreferSkillCopy
447+ if useSymlink {
417448 symlinkTarget := canonicalDir
418449 // For project scope, use relative symlinks so they work for teammates.
419450 if params .scope == ScopeProject {
@@ -431,8 +462,10 @@ func installSkillForAgents(ctx context.Context, skillName string, files []string
431462 }
432463 log .Debugf (ctx , "Installed %q for %s (symlinked)" , skillName , agent .DisplayName )
433464 } else {
434- // Copy from canonical dir instead of re-downloading.
435- if err := copyDir (canonicalDir , destDir ); err != nil {
465+ // Copy from canonical dir. Skip agent metadata dirs for agents
466+ // that don't consume them.
467+ skipMetadata := ! agent .InstallAgentMetadata
468+ if err := copyDirFiltered (canonicalDir , destDir , skipMetadata ); err != nil {
436469 log .Warnf (ctx , "Failed to install for %s: %v" , agent .DisplayName , err )
437470 continue
438471 }
@@ -506,7 +539,13 @@ func installSkillToDir(ctx context.Context, ref, skillName, destDir string, file
506539 return err
507540 }
508541
509- destPath := filepath .Join (destDir , file )
542+ // Strip the @root: prefix so shared assets land at a local path
543+ // (e.g. "@root:assets/databricks.svg" → "assets/databricks.svg").
544+ destFile := file
545+ if rootPath , ok := strings .CutPrefix (file , sharedFilePrefix ); ok {
546+ destFile = rootPath
547+ }
548+ destPath := filepath .Join (destDir , destFile )
510549
511550 if err := os .MkdirAll (filepath .Dir (destPath ), 0o755 ); err != nil {
512551 return fmt .Errorf ("failed to create directory: %w" , err )
@@ -523,6 +562,13 @@ func installSkillToDir(ctx context.Context, ref, skillName, destDir string, file
523562
524563// copyDir copies all files from src to dest, recreating the directory structure.
525564func copyDir (src , dest string ) error {
565+ return copyDirFiltered (src , dest , false )
566+ }
567+
568+ // copyDirFiltered copies files from src to dest. When skipAgentMetadata is
569+ // true, subdirectories matching agentMetadataDirs (agents/, assets/) are
570+ // skipped so non-Codex agents don't receive marketplace UI files.
571+ func copyDirFiltered (src , dest string , skipAgentMetadata bool ) error {
526572 if err := os .RemoveAll (dest ); err != nil {
527573 return fmt .Errorf ("failed to remove existing path: %w" , err )
528574 }
@@ -536,6 +582,14 @@ func copyDir(src, dest string) error {
536582 if err != nil {
537583 return err
538584 }
585+
586+ if skipAgentMetadata && rel != "." && isAgentMetadataPath (rel ) {
587+ if info .IsDir () {
588+ return filepath .SkipDir
589+ }
590+ return nil
591+ }
592+
539593 target := filepath .Join (dest , rel )
540594
541595 if info .IsDir () {
0 commit comments