From 826be9520f45359c343ab66dc645cba7aef42e8b Mon Sep 17 00:00:00 2001 From: misha-db Date: Fri, 21 Aug 2026 17:06:17 +0400 Subject: [PATCH] aitools: expose supports_project_scope in `aitools list --output json` Adds a `supports_project_scope` boolean to every agent entry in the `agents` list of `databricks aitools list --output json`. - New `SupportsProjectScope bool \`json:"supports_project_scope"\`` field on `agentEntry` in `cmd/aitools/list.go`, populated in `buildAgentEntries` directly from the registry's `agents.Agent.SupportsProjectScope`. - The field is always emitted (no `omitempty`), matching the existing `managed` and `detected` bools, so the JSON shape stays stable. --- cmd/aitools/list.go | 12 ++++++++---- cmd/aitools/list_test.go | 18 +++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/cmd/aitools/list.go b/cmd/aitools/list.go index f2ee584a996..e26a7116719 100644 --- a/cmd/aitools/list.go +++ b/cmd/aitools/list.go @@ -87,6 +87,9 @@ type agentEntry struct { // Detected is the CLI's presence verdict (Agent.IsPreselected), so consumers get // the same answer the CLI would act on. Detected bool `json:"detected"` + // SupportsProjectScope mirrors agents.Agent.SupportsProjectScope so consumers can + // offer a project-scoped install without hardcoding which agents allow it. + SupportsProjectScope bool `json:"supports_project_scope"` // Installed maps CLI scope -> the install found there; always present, empty // when nothing is installed, matching the skills shape. Installed map[string]installInfo `json:"installed"` @@ -231,10 +234,11 @@ func buildAgentEntries(ctx context.Context, states map[string]*installer.Install entries := make([]agentEntry, 0, len(agents.Registry)) for _, a := range agents.Registry { entry := agentEntry{ - Name: a.Name, - DisplayName: a.DisplayName, - Managed: a.Plugin != nil, - Detected: a.IsPreselected(ctx), + Name: a.Name, + DisplayName: a.DisplayName, + Managed: a.Plugin != nil, + Detected: a.IsPreselected(ctx), + SupportsProjectScope: a.SupportsProjectScope, } // Always emit an installed map, empty when nothing is installed, so the diff --git a/cmd/aitools/list_test.go b/cmd/aitools/list_test.go index 45980c3ed22..9b07e0afb92 100644 --- a/cmd/aitools/list_test.go +++ b/cmd/aitools/list_test.go @@ -114,11 +114,12 @@ func TestRenderListJSONWithAgents(t *testing.T) { Summary: map[string]scopeSummary{installer.ScopeGlobal: {Installed: 0, Total: 0}}, Agents: []agentEntry{ { - Name: "claude-code", - DisplayName: "Claude Code", - Managed: true, - Detected: true, - Installed: map[string]installInfo{installer.ScopeGlobal: {Delivery: "plugin", Version: "0.2.6"}}, + Name: "claude-code", + DisplayName: "Claude Code", + Managed: true, + Detected: true, + SupportsProjectScope: true, + Installed: map[string]installInfo{installer.ScopeGlobal: {Delivery: "plugin", Version: "0.2.6"}}, }, { Name: "cursor", @@ -146,6 +147,7 @@ func TestRenderListJSONWithAgents(t *testing.T) { assert.Equal(t, "Claude Code", first["display_name"]) assert.Equal(t, true, first["managed"]) assert.Equal(t, true, first["detected"]) + assert.Equal(t, true, first["supports_project_scope"]) installed := first["installed"].(map[string]any) global := installed["global"].(map[string]any) assert.Equal(t, "0.2.6", global["version"]) @@ -156,6 +158,7 @@ func TestRenderListJSONWithAgents(t *testing.T) { second := agentsRaw[1].(map[string]any) assert.Contains(t, second, "installed") assert.Empty(t, second["installed"]) + assert.Equal(t, false, second["supports_project_scope"]) } func TestBuildAgentEntries(t *testing.T) { @@ -182,17 +185,22 @@ func TestBuildAgentEntries(t *testing.T) { assert.Equal(t, "Claude Code", byName["claude-code"].DisplayName) assert.Equal(t, "0.2.6", byName["claude-code"].Installed[installer.ScopeGlobal].Version) assert.Equal(t, "databricks plugin · v0.2.6 · up to date", agentStatusLabel(byName["claude-code"], "0.2.6")) + // SupportsProjectScope is wired straight from the registry: claude-code allows + // project scope, whereas the managed/skills-only global-only agents below do not. + assert.True(t, byName["claude-code"].SupportsProjectScope) require.Contains(t, byName, "codex") assert.True(t, byName["codex"].Managed) assert.Equal(t, "0.2.5", byName["codex"].Installed[installer.ScopeGlobal].Version) assert.Equal(t, "databricks plugin · v0.2.5 · update available", agentStatusLabel(byName["codex"], "0.2.6")) + assert.False(t, byName["codex"].SupportsProjectScope) // The JSON output carries an entry for every registry agent, including // skills-only agents like Cursor and managed agents with no recorded install. require.Contains(t, byName, "cursor") assert.False(t, byName["cursor"].Managed) assert.Empty(t, byName["cursor"].Installed) + assert.False(t, byName["cursor"].SupportsProjectScope) require.Contains(t, byName, "copilot") assert.True(t, byName["copilot"].Managed)