Skip to content

Commit 113448b

Browse files
authored
[4/5] Aitools: list command, flat structure, --skills/--agents/--include-experimental flags (#4813)
## PR Stack 1. [1/5] State + release discovery + directory rename (#4810) 2. [2/5] Install writes state + interactive agent selection (#4811) 3. [3/5] Update + uninstall + version commands (#4812) 4. **[4/5] List improvements + command restructuring + flags** (this PR) 5. [5/5] Project scope (--project/--global) (#4814) Manifest v2 PR: databricks/databricks-agent-skills#35 **Base**: `simonfaltum/aitools-pr3-lifecycle` (PR 3) ## Why Commands are nested under `skills` which burns namespace for future component types (hooks, etc.). There's no way to install specific skills, target specific agents from the CLI, or see installed status in the list output. ## Changes **New `list` command**: Table output with skill names, versions, installed status, `[experimental]` tags. Sorted alphabetically. Uses `tabwriter` for alignment. **Flat command structure**: `aitools install/update/uninstall/list/version` at the top level. Hidden `skills install` and `skills list` aliases for backward compat. **Flags**: - `--skills` (string, comma-separated) on install, update, uninstall: operate on specific skills - `--skills` (bool) on list, version: show detailed skills view - `--agents` (string, comma-separated) on install: target specific agents, validates against registry, skips interactive prompt - `--include-experimental` on install: include experimental skills **Selective uninstall**: `--skills` on uninstall removes only named skills, preserves state file with remaining. ## Test plan - [x] `install --skills`, `--agents`, `--include-experimental` flags work - [x] `--agents` validates against registry, errors on unknown - [x] `uninstall --skills` selective removal, state preserved - [x] `update --skills` selective update - [x] `list` shows detailed table - [x] `skills install` and `skills list` hidden aliases work (Execute path tested) - [x] `cmd/apps/init.go` integration preserved - [x] All lint checks pass
1 parent 57711c5 commit 113448b

File tree

14 files changed

+793
-181
lines changed

14 files changed

+793
-181
lines changed

experimental/aitools/cmd/aitools.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ Provides commands to:
1818
}
1919

2020
cmd.AddCommand(newInstallCmd())
21-
cmd.AddCommand(newSkillsCmd())
22-
cmd.AddCommand(newToolsCmd())
2321
cmd.AddCommand(newUpdateCmd())
2422
cmd.AddCommand(newUninstallCmd())
23+
cmd.AddCommand(newListCmd())
2524
cmd.AddCommand(newVersionCmd())
25+
cmd.AddCommand(newSkillsCmd())
26+
cmd.AddCommand(newToolsCmd())
2627

2728
return cmd
2829
}

experimental/aitools/cmd/flags.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package aitools
2+
3+
import "strings"
4+
5+
// splitAndTrim splits a comma-separated string, trims whitespace from each
6+
// element, and drops empty entries.
7+
func splitAndTrim(s string) []string {
8+
if s == "" {
9+
return nil
10+
}
11+
parts := strings.Split(s, ",")
12+
var result []string
13+
for _, p := range parts {
14+
p = strings.TrimSpace(p)
15+
if p != "" {
16+
result = append(result, p)
17+
}
18+
}
19+
if len(result) == 0 {
20+
return nil
21+
}
22+
return result
23+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package aitools
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestSplitAndTrim(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input string
13+
expected []string
14+
}{
15+
{"simple", "a,b", []string{"a", "b"}},
16+
{"whitespace", "a, b , c", []string{"a", "b", "c"}},
17+
{"empty input", "", nil},
18+
{"trailing comma", "a,b,", []string{"a", "b"}},
19+
{"only commas", ",,", nil},
20+
{"single value", "a", []string{"a"}},
21+
}
22+
for _, tt := range tests {
23+
t.Run(tt.name, func(t *testing.T) {
24+
assert.Equal(t, tt.expected, splitAndTrim(tt.input))
25+
})
26+
}
27+
}
Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,120 @@
11
package aitools
22

33
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/databricks/cli/experimental/aitools/lib/agents"
10+
"github.com/databricks/cli/experimental/aitools/lib/installer"
11+
"github.com/databricks/cli/libs/cmdio"
12+
"github.com/fatih/color"
413
"github.com/spf13/cobra"
514
)
615

716
func newInstallCmd() *cobra.Command {
17+
var skillsFlag, agentsFlag string
818
var includeExperimental bool
919

1020
cmd := &cobra.Command{
11-
Use: "install [skill-name]",
12-
Short: "Alias for skills install",
13-
Long: `Alias for "databricks experimental aitools skills install".
21+
Use: "install",
22+
Short: "Install AI skills for coding agents",
23+
Long: `Install Databricks AI skills for detected coding agents.
24+
25+
Skills are installed globally to each agent's skills directory.
26+
When multiple agents are detected, skills are stored in a canonical location
27+
and symlinked to each agent to avoid duplication.
1428
15-
Installs Databricks skills for detected coding agents.`,
29+
Supported agents: Claude Code, Cursor, Codex CLI, OpenCode, GitHub Copilot, Antigravity`,
30+
Args: cobra.NoArgs,
1631
RunE: func(cmd *cobra.Command, args []string) error {
17-
return runSkillsInstall(cmd.Context(), args, includeExperimental)
32+
ctx := cmd.Context()
33+
34+
// Resolve target agents.
35+
var targetAgents []*agents.Agent
36+
if agentsFlag != "" {
37+
var err error
38+
targetAgents, err = resolveAgentNames(ctx, agentsFlag)
39+
if err != nil {
40+
return err
41+
}
42+
} else {
43+
detected := agents.DetectInstalled(ctx)
44+
if len(detected) == 0 {
45+
printNoAgentsMessage(ctx)
46+
return nil
47+
}
48+
49+
switch {
50+
case len(detected) == 1:
51+
targetAgents = detected
52+
case cmdio.IsPromptSupported(ctx):
53+
var err error
54+
targetAgents, err = promptAgentSelection(ctx, detected)
55+
if err != nil {
56+
return err
57+
}
58+
default:
59+
targetAgents = detected
60+
}
61+
}
62+
63+
// Build install options.
64+
opts := installer.InstallOptions{
65+
IncludeExperimental: includeExperimental,
66+
}
67+
opts.SpecificSkills = splitAndTrim(skillsFlag)
68+
69+
installer.PrintInstallingFor(ctx, targetAgents)
70+
71+
src := &installer.GitHubManifestSource{}
72+
return installSkillsForAgentsFn(ctx, src, targetAgents, opts)
1873
},
1974
}
2075

76+
cmd.Flags().StringVar(&skillsFlag, "skills", "", "Specific skills to install (comma-separated)")
77+
cmd.Flags().StringVar(&agentsFlag, "agents", "", "Agents to install for (comma-separated, e.g. claude-code,cursor)")
2178
cmd.Flags().BoolVar(&includeExperimental, "experimental", false, "Include experimental skills")
2279
return cmd
2380
}
81+
82+
// resolveAgentNames parses a comma-separated list of agent names and validates
83+
// them against the registry. Returns an error for unrecognized names.
84+
func resolveAgentNames(ctx context.Context, names string) ([]*agents.Agent, error) {
85+
available := make(map[string]*agents.Agent, len(agents.Registry))
86+
var availableNames []string
87+
for i := range agents.Registry {
88+
a := &agents.Registry[i]
89+
available[a.Name] = a
90+
availableNames = append(availableNames, a.Name)
91+
}
92+
93+
var result []*agents.Agent
94+
seen := make(map[string]bool)
95+
for _, name := range strings.Split(names, ",") {
96+
name = strings.TrimSpace(name)
97+
if name == "" || seen[name] {
98+
continue
99+
}
100+
seen[name] = true
101+
agent, ok := available[name]
102+
if !ok {
103+
return nil, fmt.Errorf("unknown agent %q. Available agents: %s", name, strings.Join(availableNames, ", "))
104+
}
105+
result = append(result, agent)
106+
}
107+
108+
if len(result) == 0 {
109+
return nil, errors.New("no agents specified")
110+
}
111+
return result, nil
112+
}
113+
114+
// printNoAgentsMessage prints the "no agents detected" message.
115+
func printNoAgentsMessage(ctx context.Context) {
116+
cmdio.LogString(ctx, color.YellowString("No supported coding agents detected."))
117+
cmdio.LogString(ctx, "")
118+
cmdio.LogString(ctx, "Supported agents: Claude Code, Cursor, Codex CLI, OpenCode, GitHub Copilot, Antigravity")
119+
cmdio.LogString(ctx, "Please install at least one coding agent first.")
120+
}

0 commit comments

Comments
 (0)