Skip to content
Merged
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
203 changes: 7 additions & 196 deletions cmd/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import (

"github.com/agentuity/cli/internal/agent"
"github.com/agentuity/cli/internal/deployer"
"github.com/agentuity/cli/internal/envutil"
"github.com/agentuity/cli/internal/errsystem"
"github.com/agentuity/cli/internal/ignore"
"github.com/agentuity/cli/internal/project"
"github.com/agentuity/cli/internal/util"
"github.com/agentuity/go-common/crypto"
"github.com/agentuity/go-common/env"
"github.com/agentuity/go-common/logger"
cstr "github.com/agentuity/go-common/string"
"github.com/agentuity/go-common/tui"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -124,58 +124,6 @@ func ShowNewProjectImport(ctx context.Context, logger logger.Logger, cmd *cobra.

var envTemplateFileNames = []string{".env.example", ".env.template"}

func readPossibleEnvTemplateFiles(baseDir string) map[string][]env.EnvLineComment {
var results map[string][]env.EnvLineComment
keys := make(map[string]bool)
for _, file := range envTemplateFileNames {
filename := filepath.Join(baseDir, file)
if !util.Exists(filename) {
continue
}
efc, err := env.ParseEnvFileWithComments(filename)
if err == nil {
if results == nil {
results = make(map[string][]env.EnvLineComment)
}
for _, ev := range efc {
if _, ok := keys[ev.Key]; !ok {
if isAgentuityEnv.MatchString(ev.Key) {
continue
}
keys[ev.Key] = true
results[file] = append(results[file], ev)
}
}
}
}
return results
}

func appendToEnvFile(envfile string, envs []env.EnvLineComment) ([]env.EnvLineComment, error) {
le, err := env.ParseEnvFileWithComments(envfile)
if err != nil {
return nil, err
}
var buf strings.Builder
for _, ev := range le {
if ev.Comment != "" {
buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment))
}
buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw))
}
for _, ev := range envs {
if ev.Comment != "" {
buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment))
}
buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw))
le = append(le, ev)
}
if err := os.WriteFile(envfile, []byte(buf.String()), 0644); err != nil {
return nil, err
}
return le, nil
}

var border = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).Padding(1).BorderForeground(lipgloss.AdaptiveColor{Light: "#999999", Dark: "#999999"})
var redDiff = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#990000", Dark: "#EE0000"})

Expand Down Expand Up @@ -251,13 +199,11 @@ Examples:
}

var err error
var le []env.EnvLineComment
var projectExists bool
var action func()

if !context.NewProject {
action = func() {
var err error
projectData, err = theproject.GetProject(ctx, logger, apiUrl, token)
if err != nil {
if err == project.ErrProjectNotFound {
Expand All @@ -282,148 +228,12 @@ Examples:
ShowNewProjectImport(ctx, logger, cmd, apiUrl, token, projectId, theproject, dir, false)
}

// check to see if we have any env vars that are not in the project
envfilename := filepath.Join(dir, ".env")
if tui.HasTTY && util.Exists(envfilename) {

// attempt to see if we have any template files
templateEnvs := readPossibleEnvTemplateFiles(dir)

le, err = env.ParseEnvFileWithComments(envfilename)
if err != nil {
errsystem.New(errsystem.ErrParseEnvironmentFile, err,
errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit()
}
envFile = &deployer.EnvFile{Filepath: envfilename, Env: le}

if len(templateEnvs) > 0 {
kvmap := make(map[string]env.EnvLineComment)
for _, ev := range le {
if isAgentuityEnv.MatchString(ev.Key) {
continue
}
kvmap[ev.Key] = ev
}
var osenv map[string]string
var addtoenvfile []env.EnvLineComment
// look to see if we have any template environment variables that are not in the .env file
for filename, evs := range templateEnvs {
for _, ev := range evs {
if _, ok := kvmap[ev.Key]; !ok {
isSecret := looksLikeSecret.MatchString(ev.Key)
if !isSecret && descriptionLookingLikeASecret(ev.Comment) {
isSecret = true
}
_ = filename
var content string
var para []string
para = append(para, tui.Warning("Missing Environment Variable\n"))
para = append(para, fmt.Sprintf("The variable %s was found in %s but not in your %s file:\n", tui.Bold(ev.Key), tui.Bold(filename), tui.Bold(".env")))
if ev.Comment != "" {
para = append(para, tui.Muted(fmt.Sprintf("# %s", ev.Comment)))
}
if isSecret {
para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, cstr.Mask(ev.Val))))
} else {
para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, ev.Val)))
}
content = lipgloss.JoinVertical(lipgloss.Left, para...)
fmt.Println(border.Render(content))
if !tui.Ask(logger, "Would you like to add it to your .env file?", true) {
fmt.Println()
tui.ShowWarning("cancelled")
continue
}
if osenv == nil {
osenv = loadOSEnv()
}
val := promptForEnv(logger, ev.Key, isSecret, nil, osenv, ev.Val, ev.Comment)
addtoenvfile = append(addtoenvfile, env.EnvLineComment{
EnvLine: env.EnvLine{
Key: ev.Key,
Val: val,
Raw: val,
},
Comment: ev.Comment,
})
}
}
}
if len(addtoenvfile) > 0 {
le, err = appendToEnvFile(envfilename, addtoenvfile)
if err != nil {
errsystem.New(errsystem.ErrParseEnvironmentFile, err,
errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit()
}
tui.ShowSuccess("added %s to your .env file", util.Pluralize(len(addtoenvfile), "environment variable", "environment variables"))
fmt.Println()
}
}

var foundkeys []string
for _, ev := range le {
if isAgentuityEnv.MatchString(ev.Key) {
continue
}
if projectData != nil && projectData.Env != nil && projectData.Env[ev.Key] == ev.Val {
continue
}
if projectData != nil && projectData.Secrets != nil && projectData.Secrets[ev.Key] == cstr.Mask(ev.Val) {
continue
}
foundkeys = append(foundkeys, ev.Key)
}
if len(foundkeys) > 0 {
var title string
var suffix string
switch {
case len(foundkeys) < 3 && len(foundkeys) > 1:
suffix = "it"
var colorized []string
for _, key := range foundkeys {
colorized = append(colorized, tui.Bold(key))
}
title = fmt.Sprintf("The environment variables %s from %s are not been set in the project.", strings.Join(colorized, ", "), tui.Bold(".env"))
case len(foundkeys) == 1:
suffix = "it"
title = fmt.Sprintf("The environment variable %s from %s has not been set in the project.", tui.Bold(foundkeys[0]), tui.Bold(".env"))
default:
suffix = "them"
title = fmt.Sprintf("There are %d environment variables from %s that are not set in the project.", len(foundkeys), tui.Bold(".env"))
}
fmt.Println(title)
if !tui.Ask(logger, "Would you like to set "+suffix+"?", true) {
fmt.Println()
tui.ShowWarning("cancelled")
return
}
envs, secrets := loadEnvFile(le, false)
pd, err := theproject.SetProjectEnv(ctx, logger, apiUrl, token, envs, secrets)
if err != nil {
if isCancelled(ctx) {
os.Exit(1)
}
errsystem.New(errsystem.ErrEnvironmentVariablesNotSet, err,
errsystem.WithContextMessage("Failed to set project environment variables")).ShowErrorAndExit()
}
fmt.Println()
fmt.Println()
projectData = pd // overwrite with the new version
switch {
case len(envs) > 0 && len(secrets) > 0:
tui.ShowSuccess("Environment variables and secrets added")
case len(envs) == 1:
tui.ShowSuccess("Environment variable added")
case len(envs) > 1:
tui.ShowSuccess("Environment variables added")
case len(secrets) == 1:
tui.ShowSuccess("Secret added")
case len(secrets) > 1:
tui.ShowSuccess("Secrets added")
}
fmt.Println()
}
force, _ := cmd.Flags().GetBool("force")
if !tui.HasTTY {
force = true
}
// check to see if we have any env vars that are not in the project
envFile, projectData = envutil.ProcessEnvFiles(ctx, logger, dir, theproject, projectData, apiUrl, token, force)

if tui.HasTTY {
_, localIssues, remoteIssues, err := buildAgentTree(keys, state, context)
Expand Down Expand Up @@ -827,6 +637,7 @@ func init() {
cloudDeployCmd.Flags().StringArray("tag", nil, "Tag(s) to associate with this deployment (can be specified multiple times)")
cloudDeployCmd.Flags().String("description", "", "Description for the deployment")
cloudDeployCmd.Flags().String("message", "", "A shorter description for the deployment")
cloudDeployCmd.Flags().Bool("force", false, "Force the processing of environment files")

cloudDeployCmd.Flags().MarkHidden("deploymentId")
cloudDeployCmd.Flags().MarkHidden("ci")
Expand Down
8 changes: 8 additions & 0 deletions cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/agentuity/cli/internal/bundler"
"github.com/agentuity/cli/internal/dev"
"github.com/agentuity/cli/internal/envutil"
"github.com/agentuity/cli/internal/errsystem"
"github.com/agentuity/cli/internal/project"
"github.com/agentuity/cli/internal/util"
Expand Down Expand Up @@ -66,6 +67,12 @@ Examples:
errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithUserMessage("Failed to validate project (%s) using the provided API key from the .env file in %s. This is most likely due to the API key being invalid or the project has been deleted.\n\nYou can import this project using the following command:\n\n"+tui.Command("project import"), theproject.Project.ProjectId, dir), errsystem.WithContextMessage(fmt.Sprintf("Failed to get project: %s", err))).ShowErrorAndExit()
}

force, _ := cmd.Flags().GetBool("force")
if !tui.HasTTY {
force = true
}
_, project = envutil.ProcessEnvFiles(ctx, log, dir, theproject.Project, project, theproject.APIURL, apiKey, force)

orgId := project.OrgId

port, _ := cmd.Flags().GetInt("port")
Expand Down Expand Up @@ -275,4 +282,5 @@ func init() {
devCmd.Flags().Int("port", 0, "The port to run the development server on (uses project default if not provided)")
devCmd.Flags().String("server", "echo.agentuity.cloud", "the echo server to connect to")
devCmd.Flags().MarkHidden("server")
devCmd.Flags().Bool("force", false, "Force the processing of environment files")
}
45 changes: 6 additions & 39 deletions cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
"strings"
"syscall"

"github.com/agentuity/cli/internal/envutil"
"github.com/agentuity/cli/internal/errsystem"
"github.com/agentuity/cli/internal/project"
"github.com/agentuity/cli/internal/util"
"github.com/agentuity/go-common/env"
"github.com/agentuity/go-common/logger"
cstr "github.com/agentuity/go-common/string"
"github.com/agentuity/go-common/sys"
"github.com/agentuity/go-common/tui"
Expand Down Expand Up @@ -74,40 +75,6 @@ func loadEnvFile(le []env.EnvLineComment, forceSecret bool) (map[string]string,
return envs, secrets
}

func promptForEnv(logger logger.Logger, key string, isSecret bool, localenv map[string]string, osenv map[string]string, defaultValue string, placeholder string) string {
prompt := "Enter the value for " + key
var help string
var value string
if isSecret {
prompt = "Enter the secret value for " + key
if val, ok := localenv[key]; ok {
help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your .env file"
if defaultValue == "" {
defaultValue = val
}
} else if val, ok := osenv[key]; ok {
help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your environment"
if defaultValue == "" {
defaultValue = val
}
} else {
help = "Your input will be masked"
}
value = tui.Password(logger, prompt, help)
} else {
if placeholder == "" {
value = tui.InputWithPlaceholder(logger, prompt, placeholder, defaultValue)
} else {
value = tui.InputWithPlaceholder(logger, prompt, help, defaultValue)
}
}

if value == "" && defaultValue != "" {
value = defaultValue
}
return value
}

func loadOSEnv() map[string]string {
osenv := make(map[string]string)
for _, line := range os.Environ() {
Expand Down Expand Up @@ -251,16 +218,16 @@ Examples:
}
}
if value == "" {
value = promptForEnv(logger, key, isSecret, localenv, osenv, "", "")
value = envutil.PromptForEnv(logger, key, isSecret, localenv, osenv, "", "")
}
}
if key != "" && value != "" {
if isSecret {
secrets[key] = value
tui.ShowSuccess("%s=%s", key, maxString(cstr.Mask(value), 40))
tui.ShowSuccess("%s=%s", key, util.MaxString(cstr.Mask(value), 40))
} else {
envs[key] = value
tui.ShowSuccess("%s=%s", key, maxString(value, 40))
tui.ShowSuccess("%s=%s", key, util.MaxString(value, 40))
}
}
if askMore {
Expand Down Expand Up @@ -426,7 +393,7 @@ Examples:
if !hasTTY {
fmt.Printf("%s=%s\n", key, value)
} else {
fmt.Printf("%s=%s\n", tui.Title(key), tui.Muted(maxString(value, 40)))
fmt.Printf("%s=%s\n", tui.Title(key), tui.Muted(util.MaxString(value, 40)))
}
}
if len(projectData.Env) == 0 && len(projectData.Secrets) == 0 {
Expand Down
7 changes: 7 additions & 0 deletions cmd/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sort"
"syscall"

"github.com/agentuity/cli/internal/envutil"
"github.com/agentuity/cli/internal/errsystem"
"github.com/agentuity/cli/internal/mcp"
"github.com/agentuity/cli/internal/organization"
Expand Down Expand Up @@ -820,6 +821,11 @@ Examples:
}

ShowNewProjectImport(ctx, logger, cmd, context.APIURL, context.Token, "", context.Project, context.Dir, true)
force, _ := cmd.Flags().GetBool("force")
if !tui.HasTTY {
force = true
}
_, _ = envutil.ProcessEnvFiles(ctx, logger, context.Dir, context.Project, nil, context.APIURL, context.Token, force)

},
}
Expand Down Expand Up @@ -868,6 +874,7 @@ func init() {
projectImportCmd.Flags().String("apikey", "", "The API key to use for the project")
projectImportCmd.Flags().String("name", "", "The name of the project to import")
projectImportCmd.Flags().String("description", "", "The description of the project to import")
projectImportCmd.Flags().Bool("force", false, "Force the processing of environment files")

projectImportCmd.Flags().MarkHidden("apikey")
projectImportCmd.Flags().MarkHidden("name")
Expand Down
Loading
Loading