Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ brews:
(bash_completion/"auth0").write `#{bin}/auth0 completion bash`
(fish_completion/"auth0.fish").write `#{bin}/auth0 completion fish`
(zsh_completion/"_auth0").write `#{bin}/auth0 completion zsh`
caveats: "Thanks for installing the Auth0 CLI"

system "#{bin}/auth0", "ai", "skills", "post-install-hook", "--auto"
caveats: "Thanks for installing the Auth0 CLI\n\nTip: run 'auth0 ai skills install' to manage your Auth0 skills for AI coding assistants."

scoops:
- name: auth0
Expand All @@ -68,4 +70,4 @@ scoops:
description: Build, manage and test your Auth0 integrations from the command line
license: MIT
skip_upload: true
post_install: ["Write-Host 'Thanks for installing the Auth0 CLI'"]
post_install: ["Write-Host 'Thanks for installing the Auth0 CLI'", "& auth0 ai skills post-install-hook --auto"]
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Authenticating as a user is not supported for **private cloud** tenants. Instead

- [auth0 actions](auth0_actions.md) - Manage resources for actions
- [auth0 acul](auth0_acul.md) - Advanced Customization the Universal Login experience
- [auth0 ai](auth0_ai.md) - Manage Auth0 AI capabilities
- [auth0 api](auth0_api.md) - Makes an authenticated HTTP request to the Auth0 Management API
- [auth0 apis](auth0_apis.md) - Manage resources for APIs
- [auth0 apps](auth0_apps.md) - Manage resources for applications
Expand Down
1 change: 1 addition & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ execute() {
log_info "installed ${BINDIR}/${binexe}"
done
rm -rf "${tmpdir}"
"${BINDIR}/auth0" ai skills post-install-hook --auto || true
}
get_binaries() {
case "$PLATFORM" in
Expand Down
14 changes: 13 additions & 1 deletion internal/ai/skills/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"errors"
"os"
"os/exec"
"os/user"
"path/filepath"
"strconv"
)

type AgentConfig struct {
Expand Down Expand Up @@ -62,8 +64,18 @@ func (a AgentConfig) IsInstalled() bool {

var SupportedAgents []AgentConfig

func homeDir() string {
if u, err := user.LookupId(strconv.Itoa(os.Getuid())); err == nil && u.HomeDir != "" {
return u.HomeDir
}
if h, err := os.UserHomeDir(); err == nil && h != "" {
return h
}
return ""
}

func init() {
home, _ := os.UserHomeDir()
home := homeDir()
if home == "" {
SupportedAgents = []AgentConfig{
{ID: "universal", DisplayName: "Universal", ProjectSkillsDir: filepath.Join(".agents", "skills")},
Expand Down
8 changes: 4 additions & 4 deletions internal/ai/skills/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func DownloadPlugin(targetDir, ref string) (string, error) {
// downloadViaZip fetches the commit SHA first, downloads and extracts the ZIP archive,
// then promotes the plugins/auth0 subtree into targetDir.
func downloadViaZip(targetDir, ref string) (string, error) {
sha, err := fetchCommitSHA(ref)
sha, err := FetchCommitSHA(ref)
if err != nil {
return "", err
}
Expand All @@ -63,7 +63,7 @@ func downloadViaZip(targetDir, ref string) (string, error) {

// GitHub flattens "/" in ref names to "-" in archive root directory names.
archiveRef := strings.ReplaceAll(ref, "/", "-")
subtreeSrc := filepath.Join(tmpUnzipDir, "auth0-agent-skills-"+archiveRef, filepath.FromSlash(pluginSubtreePath))
subtreeSrc := filepath.Join(tmpUnzipDir, "agent-skills-"+archiveRef, filepath.FromSlash(pluginSubtreePath))

if err := checkHasSkills(subtreeSrc); err != nil {
return "", err
Expand Down Expand Up @@ -98,8 +98,8 @@ func checkHasSkills(dir string) error {
return nil
}

// fetchCommitSHA fetches the latest commit SHA for ref from the GitHub API.
func fetchCommitSHA(ref string) (string, error) {
// FetchCommitSHA fetches the latest commit SHA for ref from the GitHub API.
func FetchCommitSHA(ref string) (string, error) {
req, err := http.NewRequest(http.MethodGet, agentSkillsAPI+ref, nil)
if err != nil {
return "", err
Expand Down
22 changes: 11 additions & 11 deletions internal/ai/skills/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func TestFetchCommitSHA(t *testing.T) {

t.Run("returns SHA from valid response", func(t *testing.T) {
setHTTPClient(t, shaResponse("abc123def456"))
sha, err := fetchCommitSHA("main")
sha, err := FetchCommitSHA("main")
require.NoError(t, err)
assert.Equal(t, "abc123def456", sha)
})
Expand All @@ -141,14 +141,14 @@ func TestFetchCommitSHA(t *testing.T) {
setHTTPClient(t, func(_ *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: http.StatusForbidden, Body: io.NopCloser(strings.NewReader(""))}, nil
})
_, err := fetchCommitSHA("main")
_, err := FetchCommitSHA("main")
require.Error(t, err)
assert.Contains(t, err.Error(), "403")
})

t.Run("returns error when SHA field is empty", func(t *testing.T) {
setHTTPClient(t, shaResponse(""))
_, err := fetchCommitSHA("main")
_, err := FetchCommitSHA("main")
require.Error(t, err)
assert.Contains(t, err.Error(), "empty SHA")
})
Expand All @@ -157,15 +157,15 @@ func TestFetchCommitSHA(t *testing.T) {
setHTTPClient(t, func(_ *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("not json"))}, nil
})
_, err := fetchCommitSHA("main")
_, err := FetchCommitSHA("main")
require.Error(t, err)
})

t.Run("returns error on request failure", func(t *testing.T) {
setHTTPClient(t, func(_ *http.Request) (*http.Response, error) {
return nil, errors.New("network error")
})
_, err := fetchCommitSHA("main")
_, err := FetchCommitSHA("main")
require.Error(t, err)
assert.Contains(t, err.Error(), "github API request failed")
})
Expand All @@ -178,7 +178,7 @@ func TestFetchCommitSHA(t *testing.T) {
body, _ := json.Marshal(map[string]string{"sha": "abc123"})
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(body))}, nil
})
_, err := fetchCommitSHA("main")
_, err := FetchCommitSHA("main")
require.NoError(t, err)
assert.Equal(t, "Bearer test-token-xyz", capturedAuth)
})
Expand All @@ -191,7 +191,7 @@ func TestFetchCommitSHA(t *testing.T) {
body, _ := json.Marshal(map[string]string{"sha": "abc123"})
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(body))}, nil
})
_, err := fetchCommitSHA("main")
_, err := FetchCommitSHA("main")
require.NoError(t, err)
assert.Empty(t, capturedAuth)
})
Expand Down Expand Up @@ -245,7 +245,7 @@ func makeZipTransport(t *testing.T, zipData []byte, sha string) roundTripFunc {
func TestDownloadViaZip(t *testing.T) {
const ref = "main"
const wantSHA = "cafebabe1234"
prefix := fmt.Sprintf("auth0-agent-skills-%s/%s/", ref, pluginSubtreePath)
prefix := fmt.Sprintf("agent-skills-%s/%s/", ref, pluginSubtreePath)

t.Run("extracts subtree and returns commit SHA", func(t *testing.T) {
zipData := makeZipBytes(t, map[string]string{
Expand Down Expand Up @@ -294,7 +294,7 @@ func TestDownloadViaZip(t *testing.T) {
t.Run("handles slash-containing ref by flattening to dash", func(t *testing.T) {
const slashRef = "release/1.0"
const flatRef = "release-1.0"
prefix := fmt.Sprintf("auth0-agent-skills-%s/%s/", flatRef, pluginSubtreePath)
prefix := fmt.Sprintf("agent-skills-%s/%s/", flatRef, pluginSubtreePath)
zipData := makeZipBytes(t, map[string]string{
prefix + "skills/skill-y/SKILL.md": "# skill-y",
})
Expand Down Expand Up @@ -329,7 +329,7 @@ func TestDownloadPlugin_EmptyExtraction(t *testing.T) {
func TestDownloadPlugin_CreatesMissingTargetDir(t *testing.T) {
const ref = "main"
const wantSHA = "abc123"
prefix := fmt.Sprintf("auth0-agent-skills-%s/%s/", ref, pluginSubtreePath)
prefix := fmt.Sprintf("agent-skills-%s/%s/", ref, pluginSubtreePath)

zipData := makeZipBytes(t, map[string]string{
prefix + "skills/skill-a/SKILL.md": "# skill-a",
Expand All @@ -347,7 +347,7 @@ func TestDownloadPlugin_CreatesMissingTargetDir(t *testing.T) {

func TestDownloadPlugin_DefaultsRefToMain(t *testing.T) {
const wantSHA = "mainsha"
prefix := fmt.Sprintf("auth0-agent-skills-main/%s/", pluginSubtreePath)
prefix := fmt.Sprintf("agent-skills-main/%s/", pluginSubtreePath)

zipData := makeZipBytes(t, map[string]string{
prefix + "skills/skill-a/SKILL.md": "# skill-a",
Expand Down
7 changes: 7 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/auth0/auth0-cli/internal/buildinfo"
"github.com/auth0/auth0-cli/internal/display"
"github.com/auth0/auth0-cli/internal/instrumentation"
"github.com/auth0/auth0-cli/internal/iostream"
)

const rootShort = "Build, manage and test your Auth0 integrations from the command line."
Expand Down Expand Up @@ -88,6 +89,10 @@ func buildRootCmd(cli *cli) *cobra.Command {
prepareInteractivity(cmd)
cli.configureRenderer()

if cmd.CommandPath() != "auth0 ai skills post-install-hook" && !skillsSentinelExists() && iostream.IsOutputTerminal() {
fmt.Fprintln(os.Stderr, skillsInstallTip)
}

if !commandRequiresAuthentication(cmd.CommandPath()) {
return nil
}
Expand Down Expand Up @@ -121,6 +126,7 @@ func commandRequiresAuthentication(invokedCommandName string) bool {
"auth0 logout",
"auth0 tenants use",
"auth0 tenants list",
"auth0 ai skills post-install-hook",
}

for _, cmd := range commandsWithNoAuthRequired {
Expand Down Expand Up @@ -175,6 +181,7 @@ func addSubCommands(rootCmd *cobra.Command, cli *cli) {
rootCmd.AddCommand(networkACLCmd(cli))
rootCmd.AddCommand(tenantSettingsCmd(cli))
rootCmd.AddCommand(tokenExchangeCmd(cli))
rootCmd.AddCommand(aiCmd(cli))

// Keep completion at the bottom.
rootCmd.AddCommand(completionCmd(cli))
Expand Down
Loading
Loading