Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion cli/azd/extensions/azure.ai.agents/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: azure.ai.agents
namespace: ai.agent
displayName: Foundry agents (Preview)
description: Extension for the Foundry Agent Service. (Preview)
description: Ship agents with Microsoft Foundry from your terminal. (Preview)
usage: azd ai agent <command> [options]
# NOTE: Make sure version.txt is in sync with this version.
version: 0.1.16-preview
Expand Down
42 changes: 42 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/banner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"fmt"
"io"
"strings"

"azureaiagent/internal/version"

"github.com/fatih/color"
)

// ASCII art using ANSI Shadow font for "FOUNDRY".
// Visual width is 61 columns; each box-drawing character is one display column
// but occupies multiple UTF-8 bytes, so len() over-counts. Tests use
// rune-aware width measurement.
const bannerArt = `███████╗ ██████╗ ██╗ ██╗███╗ ██╗██████╗ ██████╗ ██╗ ██╗
██╔════╝██╔═══██╗██║ ██║████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝
█████╗ ██║ ██║██║ ██║██╔██╗ ██║██║ ██║██████╔╝ ╚████╔╝
██╔══╝ ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══██╗ ╚██╔╝
██║ ╚██████╔╝╚██████╔╝██║ ╚████║██████╔╝██║ ██║ ██║
╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝ ╚═╝
`

func printBanner(w io.Writer) {
purple := color.RGB(109, 53, 255).Add(color.Bold)
dim := color.New(color.Faint)
fmt.Fprintln(w)

for _, line := range strings.Split(bannerArt, "\n") {
purple.Fprintln(w, line)

Check failure on line 34 in cli/azd/extensions/azure.ai.agents/internal/cmd/banner.go

View workflow job for this annotation

GitHub Actions / lint / golangci-lint (windows-latest)

G104: Errors unhandled (gosec)
}

dim.Fprintf(w, "v%s", version.Version)

Check failure on line 37 in cli/azd/extensions/azure.ai.agents/internal/cmd/banner.go

View workflow job for this annotation

GitHub Actions / lint / golangci-lint (windows-latest)

G104: Errors unhandled (gosec)
fmt.Fprint(w, " ")
fmt.Fprintln(w)
dim.Fprintln(w, "Visit the docs at https://aka.ms/azd-ai-agent-docs")

Check failure on line 40 in cli/azd/extensions/azure.ai.agents/internal/cmd/banner.go

View workflow job for this annotation

GitHub Actions / lint / golangci-lint (windows-latest)

G104: Errors unhandled (gosec)
fmt.Fprintln(w)
}
71 changes: 71 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/banner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"bytes"
"strings"
"testing"
"unicode/utf8"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// displayWidth returns the number of runes (visual columns) in s.
// Box-drawing and block characters are each one column wide, but multi-byte
// in UTF-8, so we count runes instead of bytes.
func displayWidth(s string) int {
return utf8.RuneCountInString(s)
}

func TestBannerFitsWithin100Columns(t *testing.T) {
for i, line := range strings.Split(bannerArt, "\n") {
w := displayWidth(line)
assert.LessOrEqualf(t, w, 100,
"banner line %d exceeds 100 columns (%d runes): %q", i+1, w, line)
}
}

func TestPrintBannerWritesOutput(t *testing.T) {
var buf bytes.Buffer
printBanner(&buf)

output := buf.String()
require.NotEmpty(t, output, "printBanner should produce output when writing to a buffer")
assert.Contains(t, output, "██", "banner should contain block-drawing characters")
assert.Contains(t, output, "https://aka.ms/azd-ai-agent-docs", "banner should contain the docs link")
}

func TestPrintBannerAllLinesFit100Cols(t *testing.T) {
var buf bytes.Buffer
printBanner(&buf)

for i, line := range strings.Split(buf.String(), "\n") {
clean := stripAnsi(line)
w := displayWidth(clean)
assert.LessOrEqualf(t, w, 100,
"rendered line %d exceeds 100 columns (%d runes): %q", i+1, w, clean)
}
}

// stripAnsi removes ANSI escape sequences from a string.
func stripAnsi(s string) string {
var out strings.Builder
inEsc := false
for _, r := range s {
if r == '\x1b' {
inEsc = true
continue
}
if inEsc {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
inEsc = false
}
continue
}
out.WriteRune(r)
}
return out.String()
}
4 changes: 2 additions & 2 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ func newInitCommand(rootFlags *rootFlagsDefinition) *cobra.Command {
Short: fmt.Sprintf("Initialize a new AI agent project. %s", color.YellowString("(Preview)")),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
printBanner(cmd.OutOrStdout())

ctx := azdext.WithAccessToken(cmd.Context())

setupDebugLogging(cmd.Flags())
Expand Down Expand Up @@ -236,8 +238,6 @@ func newInitCommand(rootFlags *rootFlagsDefinition) *cobra.Command {
}

func (a *InitAction) Run(ctx context.Context) error {
color.Green("Initializing AI agent project...")
fmt.Println()

// If src path is absolute, convert it to relative path compared to the azd project path
if a.flags.src != "" && filepath.IsAbs(a.flags.src) {
Expand Down
11 changes: 10 additions & 1 deletion cli/azd/extensions/azure.ai.agents/internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,23 @@ var rootFlags rootFlagsDefinition
func NewRootCommand() *cobra.Command {
rootCmd := &cobra.Command{
Use: "agent <command> [options]",
Short: fmt.Sprintf("Extension for the Foundry Agent Service. %s", color.YellowString("(Preview)")),
Short: fmt.Sprintf("Ship agents with Microsoft Foundry from your terminal. %s", color.YellowString("(Preview)")),
Comment thread
therealjohn marked this conversation as resolved.
SilenceUsage: true,
SilenceErrors: true,
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
}

// Show the ASCII art banner above the default help text for the root command
defaultHelp := rootCmd.HelpFunc()
rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if cmd == rootCmd {
printBanner(cmd.OutOrStdout())
}
defaultHelp(cmd, args)
})

rootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
rootCmd.PersistentFlags().BoolVar(
&rootFlags.Debug,
Expand Down
Loading