@@ -184,7 +184,7 @@ func (lc *ListCommand) listLocalPlugins() error {
184184 }
185185 specs = append (specs , spec )
186186 }
187- // claude/cursor/codex have no project-scoped plugin registry to list from — same
187+ // claude/cursor/codex/vscode have no project-scoped plugin registry to list from — same
188188 // restriction install/update already enforce (RejectUnsupportedProjectScope). Without
189189 // this, a non-global list would silently show claude/codex's one global native list
190190 // (see buildNativeRows) under a --project-dir the plugins were never actually scoped to.
@@ -260,7 +260,8 @@ func (lc *ListCommand) buildPluginRowsForHarness(registry map[string]agentcommon
260260}
261261
262262// buildDirRows resolves the install dir for agentName and lists installed plugins by
263- // scanning it directly. Used for agents with no native plugin registry (e.g. cursor).
263+ // scanning it directly. Used for agents with no native plugin registry (e.g. cursor,
264+ // vscode).
264265func (lc * ListCommand ) buildDirRows (registry map [string ]agentcommon.AgentSpec , agentName string ) ([]localListRow , error ) {
265266 spec , err := agentcommon .ResolveAgent (registry , agentName , pluginscommon .RegistryHelp )
266267 if err != nil {
@@ -286,6 +287,10 @@ func (lc *ListCommand) buildDirRows(registry map[string]agentcommon.AgentSpec, a
286287 projectDir = lc .projectDir
287288 }
288289
290+ if pluginscommon .UsesRepoKeyedLayout (agentName ) {
291+ return lc .buildRepoKeyedRows (dir , projectDir , agentName , entries )
292+ }
293+
289294 var rows []localListRow
290295 for _ , entry := range entries {
291296 if ! entry .IsDir () {
@@ -299,6 +304,32 @@ func (lc *ListCommand) buildDirRows(registry map[string]agentcommon.AgentSpec, a
299304 return rows , nil
300305}
301306
307+ // buildRepoKeyedRows lists plugins nested one level deeper, one subdirectory per
308+ // Artifactory repo (<dir>/<repoKey>/<slug>), e.g. vscode.
309+ func (lc * ListCommand ) buildRepoKeyedRows (dir , projectDir , agentName string , entries []os.DirEntry ) ([]localListRow , error ) {
310+ var rows []localListRow
311+ for _ , repoEntry := range entries {
312+ if ! repoEntry .IsDir () || strings .HasPrefix (repoEntry .Name (), "." ) {
313+ continue
314+ }
315+ repoDir := filepath .Join (dir , repoEntry .Name ())
316+ pluginEntries , err := os .ReadDir (repoDir )
317+ if err != nil {
318+ return nil , fmt .Errorf ("failed to read plugins directory %s: %w" , repoDir , err )
319+ }
320+ for _ , entry := range pluginEntries {
321+ if ! entry .IsDir () {
322+ continue
323+ }
324+ row , ok := lc .buildRowForPlugin (filepath .Join (repoDir , entry .Name ()), entry .Name (), projectDir , agentName )
325+ if ok {
326+ rows = append (rows , row )
327+ }
328+ }
329+ }
330+ return rows , nil
331+ }
332+
302333// buildNativeRows lists every plugin agentName's own CLI reports as installed. The native
303334// registries themselves don't track a description, but the plugin's own manifest usually
304335// exists on disk at its native install path (e.g. .claude-plugin/plugin.json), so Description
@@ -544,7 +575,7 @@ func RunList(c *components.Context) error {
544575
545576// resolveListScope applies the same project/global-scope default as install/update's
546577// DefaultGlobalScope: when neither --global nor --project-dir is explicitly given, list
547- // defaults to global scope, not project scope. claude/cursor/codex only support a global
578+ // defaults to global scope, not project scope. claude/cursor/codex/vscode only support a global
548579// native registry/config anyway (see agentcommon.RejectUnsupportedProjectScope), so
549580// defaulting to project scope here would just make list --harness (no flags) reject in
550581// listLocalPlugins where install/update would have quietly gone global.
0 commit comments