Skip to content

Commit d177710

Browse files
committed
aitools list: preserve fallback and text summaries
1 parent e71f268 commit d177710

2 files changed

Lines changed: 65 additions & 8 deletions

File tree

cmd/aitools/list.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import (
1010
"strings"
1111
"text/tabwriter"
1212

13-
"github.com/databricks/cli/libs/aitools/installer"
1413
"github.com/databricks/cli/cmd/root"
14+
"github.com/databricks/cli/libs/aitools/installer"
1515
"github.com/databricks/cli/libs/cmdio"
1616
"github.com/databricks/cli/libs/flags"
1717
"github.com/databricks/cli/libs/log"
@@ -36,7 +36,7 @@ func NewListCmd() *cobra.Command {
3636
return err
3737
}
3838

39-
// list: empty scope = show both. Both flags set is equivalent.
39+
// list: empty scope = show both. --scope=both also lands here.
4040
var scope string
4141
switch {
4242
case projectFlag && !globalFlag:
@@ -74,6 +74,9 @@ type skillEntry struct {
7474
type scopeSummary struct {
7575
Installed int `json:"installed"`
7676
Total int `json:"total"`
77+
78+
// loaded preserves text rendering semantics without changing the JSON contract.
79+
loaded bool
7780
}
7881

7982
func defaultListSkills(cmd *cobra.Command, scope string) error {
@@ -97,13 +100,13 @@ func defaultListSkills(cmd *cobra.Command, scope string) error {
97100
// returns the structured listOutput. scope=="" loads both scopes; "global"
98101
// or "project" loads only that scope.
99102
func buildListOutput(ctx context.Context, scope string) (listOutput, error) {
100-
ref, _, err := installer.GetSkillsRef(ctx)
103+
ref, explicit, err := installer.GetSkillsRef(ctx)
101104
if err != nil {
102105
return listOutput{}, err
103106
}
104107

105108
src := &installer.GitHubManifestSource{}
106-
manifest, err := src.FetchManifest(ctx, ref)
109+
manifest, ref, err := installer.FetchSkillsManifestWithFallback(ctx, src, ref, !explicit)
107110
if err != nil {
108111
return listOutput{}, fmt.Errorf("failed to fetch manifest: %w", err)
109112
}
@@ -147,10 +150,10 @@ func buildListOutput(ctx context.Context, scope string) (listOutput, error) {
147150
// install state is missing — agents should see "0/N" rather than guess
148151
// from the absence of a key.
149152
if scope != installer.ScopeProject {
150-
out.Summary[installer.ScopeGlobal] = scopeSummary{Installed: globalCount, Total: len(names)}
153+
out.Summary[installer.ScopeGlobal] = scopeSummary{Installed: globalCount, Total: len(names), loaded: globalState != nil}
151154
}
152155
if scope != installer.ScopeGlobal {
153-
out.Summary[installer.ScopeProject] = scopeSummary{Installed: projectCount, Total: len(names)}
156+
out.Summary[installer.ScopeProject] = scopeSummary{Installed: projectCount, Total: len(names), loaded: projectState != nil}
154157
}
155158

156159
return out, nil
@@ -259,10 +262,10 @@ func summaryLine(out listOutput, scope string) string {
259262
case gOK && pOK:
260263
// Mirror prior behavior: only print the dual-scope line when both
261264
// scopes have a state file; otherwise only mention the one that does.
262-
if anyInstalled(out, installer.ScopeGlobal) && anyInstalled(out, installer.ScopeProject) {
265+
if g.loaded && p.loaded {
263266
return fmt.Sprintf("%d/%d skills installed (global), %d/%d (project)", g.Installed, g.Total, p.Installed, p.Total)
264267
}
265-
if anyInstalled(out, installer.ScopeProject) {
268+
if p.loaded {
266269
return fmt.Sprintf("%d/%d skills installed (project)", p.Installed, p.Total)
267270
}
268271
return fmt.Sprintf("%d/%d skills installed (global)", g.Installed, g.Total)

cmd/aitools/list_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,60 @@ func TestInstalledStatusFromEntry(t *testing.T) {
170170
}
171171
}
172172

173+
func TestSummaryLinePreservesStatePresence(t *testing.T) {
174+
tests := []struct {
175+
name string
176+
out listOutput
177+
want string
178+
}{
179+
{
180+
name: "both state files loaded even with no installs",
181+
out: listOutput{
182+
Skills: []skillEntry{
183+
{Name: "databricks-jobs", LatestVersion: "1.0.0", Installed: map[string]string{}},
184+
},
185+
Summary: map[string]scopeSummary{
186+
installer.ScopeGlobal: {Installed: 0, Total: 1, loaded: true},
187+
installer.ScopeProject: {Installed: 0, Total: 1, loaded: true},
188+
},
189+
},
190+
want: "0/1 skills installed (global), 0/1 (project)",
191+
},
192+
{
193+
name: "only project state loaded",
194+
out: listOutput{
195+
Skills: []skillEntry{
196+
{Name: "databricks-jobs", LatestVersion: "1.0.0", Installed: map[string]string{}},
197+
},
198+
Summary: map[string]scopeSummary{
199+
installer.ScopeGlobal: {Installed: 0, Total: 1},
200+
installer.ScopeProject: {Installed: 0, Total: 1, loaded: true},
201+
},
202+
},
203+
want: "0/1 skills installed (project)",
204+
},
205+
{
206+
name: "only global state loaded",
207+
out: listOutput{
208+
Skills: []skillEntry{
209+
{Name: "databricks-jobs", LatestVersion: "1.0.0", Installed: map[string]string{}},
210+
},
211+
Summary: map[string]scopeSummary{
212+
installer.ScopeGlobal: {Installed: 0, Total: 1, loaded: true},
213+
installer.ScopeProject: {Installed: 0, Total: 1},
214+
},
215+
},
216+
want: "0/1 skills installed (global)",
217+
},
218+
}
219+
220+
for _, tt := range tests {
221+
t.Run(tt.name, func(t *testing.T) {
222+
assert.Equal(t, tt.want, summaryLine(tt.out, ""))
223+
})
224+
}
225+
}
226+
173227
func TestListScopeFlag(t *testing.T) {
174228
tests := []struct {
175229
name string

0 commit comments

Comments
 (0)