Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
26b8410
test charm lib impelentation for cre init
ejacquier Jan 30, 2026
4a7699d
Add shared UI package with Charm ecosystem for styled CLI output
ejacquier Jan 30, 2026
c37b25c
Add spinner to cre init and fix layout issues
ejacquier Jan 30, 2026
26ec16a
Style help page with Lipgloss
ejacquier Jan 30, 2026
46d61de
Apply Chainlink Blocks color palette to CLI
ejacquier Jan 30, 2026
fbfda62
Modernize cre login command with Charm UI components
ejacquier Jan 30, 2026
6ebd121
Add styled error display for CLI using ui.Error()
ejacquier Jan 30, 2026
3d819a4
Improve CLI error display with styled output and smart usage handling
ejacquier Jan 30, 2026
744e5de
Add login prompt when authentication is required
ejacquier Jan 30, 2026
ebe0091
Fix tests broken by Charm UI changes
ejacquier Jan 30, 2026
eb5a70e
Fix cre logout to skip credential check and handle nil credentials
ejacquier Jan 30, 2026
3b0f685
Modernize cre version command with Charm UI styling
ejacquier Jan 30, 2026
137594c
Modernize cre update command with progress bar and Charm UI
ejacquier Jan 30, 2026
f56168f
Fix creinit tests to provide all inputs via flags
ejacquier Jan 30, 2026
214ed37
Update the list-key command to use the shared Charm UI components for
ejacquier Jan 30, 2026
f2ba1ae
Update test assertions for Charm UI output changes
ejacquier Jan 30, 2026
dc83bff
Update the generate-bindings command to use the shared Charm UI
ejacquier Jan 30, 2026
4dd218d
Update the secrets list command to use the shared Charm UI component
ejacquier Jan 30, 2026
a6dde07
Update the secrets delete command to use the shared Charm UI components
ejacquier Jan 30, 2026
ac70c74
Modernize workflow pause, activate, delete with Charm UI
ejacquier Jan 30, 2026
277e84b
updated styling for workflow deploy, pause, delete and transaction
ejacquier Jan 31, 2026
d76bdc3
mondernized link and unlink command using charm lib
ejacquier Jan 31, 2026
30ecbde
updated missing charm console output in creinit.go
ejacquier Jan 31, 2026
c32a002
fixed build output ui
ejacquier Jan 31, 2026
932908c
updated fmt.print with new ui that were not replaced
ejacquier Jan 31, 2026
182ae41
mondernized simulate command with charm ui output
ejacquier Jan 31, 2026
620e035
1. Updated internal/settings/settings_generate.go:
ejacquier Jan 31, 2026
6daa25a
improved prompt behavior with autocomplete and default value
ejacquier Jan 31, 2026
81bbfc9
added error helpers
ejacquier Jan 31, 2026
bee55e8
improved login command with cancellation / escape
ejacquier Jan 31, 2026
1856e4e
fixed logout issue that was not flushing credentials
ejacquier Jan 31, 2026
2b6a6f6
updated login to remove the esc logic
ejacquier Jan 31, 2026
e8c8cb8
updated secret tests that were failing
ejacquier Jan 31, 2026
2d89b8d
fixed lint error: unused functions and returns
ejacquier Feb 2, 2026
0678640
Refactored credentials.go for deploy access status and added deploy a…
ejacquier Jan 30, 2026
9981c7b
Updated gated message with the command to request access
ejacquier Jan 30, 2026
42a5c48
added new account access command
ejacquier Jan 30, 2026
ff49d9a
added account access command to settings exclusion
ejacquier Jan 30, 2026
6ca89a8
access command logic to submit form to zendesk
ejacquier Jan 30, 2026
0882d73
added prompt to request access when running cre account access cmd
ejacquier Jan 30, 2026
1a5eb0a
Refactor access request logic into shared package and add deploy acce…
ejacquier Jan 30, 2026
6222ef6
Fix background goroutine error appearing during deploy access prompt
ejacquier Jan 30, 2026
4af0fa9
Update account command description to mention deploy access
ejacquier Jan 30, 2026
6d74f56
Show deploy access hint after successful workflow simulation
ejacquier Jan 30, 2026
5a24a1c
Add deploy access hint to global help template for gated users
ejacquier Jan 30, 2026
80834e9
Added prompt to describe use cases when request access request
ejacquier Jan 30, 2026
ef2d10c
update code so that request is sent to a proxy that will take care of…
ejacquier Jan 30, 2026
1a26d58
updated deploy request changes to be compatible with new charm lib re…
ejacquier Feb 2, 2026
4153aef
updated simulator deploy message to use box layout
ejacquier Feb 2, 2026
1b261c9
Yes is now selected by default for cre deploy access request prompt
ejacquier Feb 2, 2026
1f992ad
updated creinit to use bubbletea wizard
ejacquier Feb 3, 2026
6ec58c1
fixed linter issues
ejacquier Feb 4, 2026
0a0b4c0
Merge branch 'main' into feature/charm-lib-refactoring
ejacquier Feb 4, 2026
29eb300
fixed weird spacing in creinit.go
ejacquier Feb 4, 2026
7fbd64d
cre init: wizard now display files created in <workflow directory> and
ejacquier Feb 4, 2026
3a785ef
restored original comments in creinit.go
ejacquier Feb 4, 2026
9a3af2c
fixed update cmd progress indicator issue
ejacquier Feb 4, 2026
df51dfa
Merge branch 'feature/charm-lib-refactoring' into feature/DEVSVCS-372…
ejacquier Feb 4, 2026
16a2c0c
Temp mock access request behavior before API implementation
ejacquier Feb 4, 2026
d99f261
Added ascii logo in creinit wizard
ejacquier Feb 4, 2026
72eceef
Merge branch 'feature/charm-lib-refactoring' into feature/DEVSVCS-372…
ejacquier Feb 4, 2026
4fc6d6e
Spinner do not display in verbose mode to avoid stdout and stderr con…
ejacquier Feb 5, 2026
516444c
Merge branch 'feature/charm-lib-refactoring' into feature/DEVSVCS-372…
ejacquier Feb 6, 2026
96a477f
replaced temp REST HTTP client with GraphQL client
ejacquier Feb 6, 2026
83f2225
added tests for access cmd
ejacquier Feb 6, 2026
5a022b9
Replace embedded templates with dynamic GitHub-based template fetching
ejacquier Feb 6, 2026
463d73a
fix & log for extracing templates
ejacquier Feb 7, 2026
ab3a99e
Built-in hello-world template — always available, even offline:
ejacquier Feb 9, 2026
5819e38
Remove template-repo flag from cre init, added template-id as a depre…
ejacquier Feb 9, 2026
1551d58
Added back builtin TS hello world template, updated template-id behavior
ejacquier Feb 9, 2026
0c781bf
Added networks field to template metadata (internal/templaterepo/type…
ejacquier Feb 9, 2026
dab9ff1
Patch RPC when template arleady have rpc configured in project.yaml
ejacquier Feb 10, 2026
504ff36
Fixed go init for default embed template
ejacquier Feb 10, 2026
e8b4512
Fixed TS template default template (same as main branch now)
ejacquier Feb 10, 2026
09f9f1a
Updated init wizard for better layout
ejacquier Feb 10, 2026
f12da0a
Add multi-workflow support and preserve template-shipped workflow.yaml
ejacquier Feb 10, 2026
c154dfa
added in wizard check for cre init - folder and rpc url
ejacquier Feb 11, 2026
d1db769
updated cre init list layout
ejacquier Feb 11, 2026
d5ba0ec
cre init search wizard stay displayed
ejacquier Feb 11, 2026
e15d582
updated template.yaml local to .cre/template.yaml
ejacquier Feb 12, 2026
a78377b
added a flag to pass new repository url, made ~/.cre/template.yaml so…
ejacquier Feb 17, 2026
a139159
Root-level template repo extraction fix
ejacquier Feb 19, 2026
99bfbb8
added cre templates list/add/remove to manage repo sources
ejacquier Feb 19, 2026
c32f1ee
template.yaml now has projectDir to specify the cre project dir, upda…
ejacquier Feb 20, 2026
0166159
Added prediction market repo as default
ejacquier Feb 20, 2026
23aa2bc
--target is now explicit and prompted if ommitted
ejacquier Feb 20, 2026
069afd1
Fixed log message (from log to debug)
ejacquier Feb 20, 2026
6ea8496
fix go mod tidy for dynamic template in go
ejacquier Feb 20, 2026
80d78be
Merge branch 'feature/dynamic-templates' into feature/DEVSVCS-3827/ta…
ejacquier Feb 20, 2026
33fd168
Merge branch 'main' into feature/dynamic-templates
ejacquier Feb 20, 2026
5f68805
fix linter
ejacquier Feb 20, 2026
92c55a7
cre init create .env file if not present
ejacquier Feb 20, 2026
0560bd2
Merge branch 'feature/dynamic-templates' into feature/DEVSVCS-3827
ejacquier Feb 20, 2026
0f8ad25
Merge remote-tracking branch 'origin/main' into feature/DEVSVCS-3827/…
ejacquier Mar 3, 2026
4054f1a
Merge branch 'main' into feature/DEVSVCS-3827
tarcisiozf Mar 3, 2026
b46af48
fix linter
ejacquier Mar 3, 2026
d9c0124
fixed e2e tests with new prompt
ejacquier Mar 3, 2026
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
9 changes: 9 additions & 0 deletions cmd/creinit/creinit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ func TestInitExecuteFlows(t *testing.T) {
expectWorkflowName: "starter-wf",
expectTemplateFiles: GetTemplateFileListGo(),
},
{
name: "Starter template with all flags",
projectNameFlag: "starterProj",
templateNameFlag: "starter-go",
workflowNameFlag: "starter-wf",
expectProjectDirRel: "starterProj",
expectWorkflowName: "starter-wf",
expectTemplateFiles: GetTemplateFileListGo(),
},
}

for _, tc := range cases {
Expand Down
14 changes: 11 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,21 @@ func newRootCommand() *cobra.Command {
return err
}

// Stop spinner before AttachSettings — it may prompt for target selection
if showSpinner {
spinner.Stop()
}

err := runtimeContext.AttachSettings(cmd, isLoadDeploymentRPC(cmd))
if err != nil {
if showSpinner {
spinner.Stop()
}
return fmt.Errorf("%w", err)
}

// Restart spinner for remaining initialization
if showSpinner {
spinner = ui.NewSpinner()
spinner.Start("Loading settings...")
}
}

// Stop the initialization spinner - commands can start their own if needed
Expand Down
78 changes: 78 additions & 0 deletions internal/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"path/filepath"
"strings"

"github.com/charmbracelet/huh"
"github.com/joho/godotenv"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/smartcontractkit/cre-cli/internal/constants"
"github.com/smartcontractkit/cre-cli/internal/ui"
)

// sensitive information (not in configuration file)
Expand Down Expand Up @@ -69,6 +71,19 @@ func New(logger *zerolog.Logger, v *viper.Viper, cmd *cobra.Command, registryCha
return nil, err
}

if target == "" {
if v.GetBool(Flags.NonInteractive.Name) {
target, err = autoSelectTarget(logger)
} else {
target, err = promptForTarget(logger)
}
if err != nil {
return nil, err
}
// Store the selected target so subsequent GetTarget() calls find it
v.Set(Flags.Target.Name, target)
}

logger.Debug().Msgf("Target: %s", target)

err = LoadSettingsIntoViper(v, cmd)
Expand Down Expand Up @@ -169,3 +184,66 @@ func NormalizeHexKey(k string) string {
}
return k
}

// autoSelectTarget discovers available targets and auto-selects when possible (non-interactive mode).
func autoSelectTarget(logger *zerolog.Logger) (string, error) {
targets, err := GetAvailableTargets()
if err != nil {
return "", fmt.Errorf("target not set and unable to discover targets: %w\nSpecify --%s or set %s env var",
err, Flags.Target.Name, CreTargetEnvVar)
}

if len(targets) == 0 {
return "", fmt.Errorf("no targets found in project.yaml; specify --%s or set %s env var",
Flags.Target.Name, CreTargetEnvVar)
}

if len(targets) == 1 {
logger.Debug().Msgf("Auto-selecting target: %s", targets[0])
return targets[0], nil
}

return "", fmt.Errorf("multiple targets found in project.yaml and --non-interactive is set; specify --%s or set %s env var",
Flags.Target.Name, CreTargetEnvVar)
}

// promptForTarget discovers available targets from project.yaml and prompts the user to select one.
func promptForTarget(logger *zerolog.Logger) (string, error) {
targets, err := GetAvailableTargets()
if err != nil {
return "", fmt.Errorf("target not set and unable to discover targets: %w\nSpecify --%s or set %s env var",
err, Flags.Target.Name, CreTargetEnvVar)
}

if len(targets) == 0 {
return "", fmt.Errorf("no targets found in project.yaml; specify --%s or set %s env var",
Flags.Target.Name, CreTargetEnvVar)
}

if len(targets) == 1 {
logger.Debug().Msgf("Auto-selecting target: %s", targets[0])
return targets[0], nil
}

var selected string
options := make([]huh.Option[string], len(targets))
for i, t := range targets {
options[i] = huh.NewOption(t, t)
}

form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Select a target").
Description("No --target flag or CRE_TARGET env var set.").
Options(options...).
Value(&selected),
),
).WithTheme(ui.ChainlinkTheme())

if err := form.Run(); err != nil {
return "", fmt.Errorf("target selection cancelled: %w", err)
}

return selected, nil
}
48 changes: 44 additions & 4 deletions internal/settings/settings_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"

chainSelectors "github.com/smartcontractkit/chain-selectors"

Expand Down Expand Up @@ -172,10 +173,49 @@ func GetTarget(v *viper.Viper) (string, error) {
return target, nil
}

return "", fmt.Errorf(
"target not set: specify --%s or set %s env var",
Flags.Target.Name, CreTargetEnvVar,
)
return "", nil
}

// GetAvailableTargets reads project.yaml and returns the top-level keys
// that represent target configurations, preserving the order from the file.
func GetAvailableTargets() ([]string, error) {
projectPath, err := getProjectSettingsPath()
if err != nil {
return nil, fmt.Errorf("failed to find project settings: %w", err)
}

data, err := os.ReadFile(projectPath)
if err != nil {
return nil, fmt.Errorf("failed to read project settings: %w", err)
}

// Parse with yaml.v3 Node to preserve key order
var doc yaml.Node
if err := yaml.Unmarshal(data, &doc); err != nil {
return nil, fmt.Errorf("failed to parse project settings: %w", err)
}

if doc.Kind != yaml.DocumentNode || len(doc.Content) == 0 {
return nil, nil
}

root := doc.Content[0]
if root.Kind != yaml.MappingNode {
return nil, nil
}

// Mapping nodes alternate key, value, key, value...
// Only include keys whose values are mappings (actual target configs).
var targets []string
for i := 0; i+1 < len(root.Content); i += 2 {
key := root.Content[i]
val := root.Content[i+1]
if key.Kind == yaml.ScalarNode && val.Kind == yaml.MappingNode {
targets = append(targets, key.Value)
}
}

return targets, nil
}

func GetChainNameByChainSelector(chainSelector uint64) (string, error) {
Expand Down
7 changes: 4 additions & 3 deletions internal/settings/settings_get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ func TestGetTarget_EnvWhenNoFlag(t *testing.T) {
assert.Equal(t, "envOnly", got)
}

func TestGetTarget_ErrorWhenNeither(t *testing.T) {
func TestGetTarget_EmptyWhenNeither(t *testing.T) {
v := viper.New()

_, err := settings.GetTarget(v)
assert.Error(t, err)
got, err := settings.GetTarget(v)
assert.NoError(t, err)
assert.Equal(t, "", got)
}
14 changes: 11 additions & 3 deletions internal/settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,17 @@ func TestLoadEnvAndSettingsEmptyTarget(t *testing.T) {
cmd := &cobra.Command{Use: "login"}
s, err := settings.New(logger, v, cmd, "")

assert.Error(t, err, "Expected error due to empty target")
assert.Contains(t, err.Error(), "target not set", "Expected missing target error")
assert.Nil(t, s, "Settings object should be nil on error")
// With no target set, settings.New() tries to prompt for a target.
// In a non-TTY test environment, this will either auto-select (single target)
// or fail with a prompt error (multiple targets).
if err != nil {
// Expected in non-TTY when multiple targets exist
assert.Nil(t, s, "Settings object should be nil on error")
} else {
// Auto-selected the only available target
assert.NotNil(t, s)
assert.NotEmpty(t, s.User.TargetName)
}
}

func TestLoadEnvAndSettings(t *testing.T) {
Expand Down
2 changes: 0 additions & 2 deletions internal/settings/template/.env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,3 @@
###############################################################################
# Ethereum private key or 1Password reference (e.g. op://vault/item/field)
CRE_ETH_PRIVATE_KEY={{EthPrivateKey}}
# Default target used when --target flag is not specified (e.g. staging-settings, production-settings, my-target)
CRE_TARGET=staging-settings
2 changes: 2 additions & 0 deletions test/convert_simulate_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func convertSimulateCaptureOutput(t *testing.T, projectRoot, workflowName string
cmd := exec.Command(CLIPath, "workflow", "simulate", workflowName,
"--project-root", projectRoot,
"--non-interactive", "--trigger-index=0",
"--target=staging-settings",
)
cmd.Dir = projectRoot
cmd.Stdout = &stdout
Expand All @@ -30,6 +31,7 @@ func convertSimulateRequireOutputContains(t *testing.T, projectRoot, workflowNam
cmd := exec.Command(CLIPath, "workflow", "simulate", workflowName,
"--project-root", projectRoot,
"--non-interactive", "--trigger-index=0",
"--target=staging-settings",
)
cmd.Dir = projectRoot
cmd.Stdout = &stdout
Expand Down
1 change: 1 addition & 0 deletions test/init_and_binding_generation_and_simulate_go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func TestE2EInit_DevPoRTemplate(t *testing.T) {
"--project-root", projectRoot,
"--non-interactive",
"--trigger-index=0",
"--target=staging-settings",
}
simulateCmd := exec.Command(CLIPath, simulateArgs...)
simulateCmd.Dir = projectRoot
Expand Down
1 change: 1 addition & 0 deletions test/init_and_simulate_ts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func TestE2EInit_DevPoRTemplateTS(t *testing.T) {
"--project-root", projectRoot,
"--non-interactive",
"--trigger-index=0",
"--target=staging-settings",
}
simulateCmd := exec.Command(CLIPath, simulateArgs...)
simulateCmd.Dir = projectRoot
Expand Down
1 change: 1 addition & 0 deletions test/init_convert_simulate_go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func convertGoBuildWithFlagAndAssert(t *testing.T, projectRoot, workflowDir, wor
cmd := exec.Command(CLIPath, "workflow", "simulate", workflowName,
"--project-root", projectRoot,
"--non-interactive", "--trigger-index=0",
"--target=staging-settings",
)
cmd.Dir = projectRoot
cmd.Stdout = &stdout
Expand Down
1 change: 1 addition & 0 deletions test/init_convert_simulate_ts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ func convertTSBuildWithFlagAndAssert(t *testing.T, projectRoot, workflowDir, wor
cmd := exec.Command(CLIPath, "workflow", "simulate", workflowDirAbs,
"--project-root", projectRoot,
"--non-interactive", "--trigger-index=0",
"--target=staging-settings",
)
cmd.Dir = projectRoot
cmd.Stdout = &stdout
Expand Down
1 change: 1 addition & 0 deletions test/multi_command_flows/workflow_simulator_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func RunSimulationHappyPath(t *testing.T, tc TestConfig, projectDir string) {
tc.GetProjectRootFlag(),
"--non-interactive",
"--trigger-index=0",
"--target=staging-settings",
}

cmd := exec.Command(CLIPath, args...)
Expand Down
Loading