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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
<div style="text-align:center" align="center">
<a href="https://chain.link" target="_blank">
<img src="https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/docs/logo-chainlink-blue.svg" width="225" alt="Chainlink logo">
</a>

[![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/smartcontractkit/cre-cli/blob/main/README.md)
[![CRE Documentation](https://img.shields.io/static/v1?label=CRE&message=Home&color=blue)](https://chain.link/chainlink-runtime-environment)

</div>

# Chainlink Runtime Environment (CRE) - CLI Tool

Note this README is for CRE developers only, if you are a CRE user, please ask Dev Services team for the user guide.
Expand Down
60 changes: 40 additions & 20 deletions cmd/creinit/creinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ const (
TemplateLangTS TemplateLanguage = "typescript"
)

const (
HelloWorldTemplate string = "HelloWorld"
PoRTemplate string = "PoR"
)

type WorkflowTemplate struct {
Folder string
Title string
ID uint32
Name string
}

type LanguageTemplate struct {
Expand All @@ -54,17 +60,17 @@ var languageTemplates = []LanguageTemplate{
Lang: TemplateLangGo,
EntryPoint: ".",
Workflows: []WorkflowTemplate{
{Folder: "porExampleDev", Title: "Custom data feed: Updating on-chain data periodically using offchain API data", ID: 1},
{Folder: "blankTemplate", Title: "Boilerplate: A barebones template with just the essentials", ID: 2},
{Folder: "porExampleDev", Title: "Custom data feed: Updating on-chain data periodically using offchain API data", ID: 1, Name: PoRTemplate},
{Folder: "blankTemplate", Title: "Helloworld: A Golang Hello World example", ID: 2, Name: HelloWorldTemplate},
},
},
{
Title: "Typescript",
Lang: TemplateLangTS,
EntryPoint: "./main.ts",
Workflows: []WorkflowTemplate{
{Folder: "typescriptSimpleExample", Title: "Boilerplate: Typescript Hello World example for a simple workflow", ID: 3},
{Folder: "typescriptPorExampleDev", Title: "Custom data feed: Typescript updating on-chain data periodically using offchain API data", ID: 4},
{Folder: "typescriptSimpleExample", Title: "Helloworld: Typescript Hello World example", ID: 3, Name: HelloWorldTemplate},
{Folder: "typescriptPorExampleDev", Title: "Custom data feed: Typescript updating on-chain data periodically using offchain API data", ID: 4, Name: PoRTemplate},
},
},
}
Expand All @@ -77,10 +83,14 @@ type Inputs struct {

func New(runtimeContext *runtime.Context) *cobra.Command {
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize a new workflow project or add a workflow to an existing one",
Long: "Initialize or extend a workflow project by setting up core files, gathering any missing details, and scaffolding the chosen template.",
Args: cobra.NoArgs,
Use: "init",
Aliases: []string{"new"},
Short: "Initialize a new cre project (recommended starting point)",
Long: `Initialize a new CRE project or add a workflow to an existing one.

This sets up the project structure, configuration, and starter files so you can
build, test, and deploy workflows quickly.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
handler := newHandler(runtimeContext, cmd.InOrStdin())

Expand Down Expand Up @@ -207,12 +217,12 @@ func (h *handler) Execute(inputs Inputs) error {
}
}

var tpl WorkflowTemplate
var selectedWorkflowTemplate WorkflowTemplate
var selectedLanguageTemplate LanguageTemplate
var workflowTemplates []WorkflowTemplate
if inputs.TemplateID != 0 {
var findErr error
tpl, selectedLanguageTemplate, findErr = h.getWorkflowTemplateByID(inputs.TemplateID)
selectedWorkflowTemplate, selectedLanguageTemplate, findErr = h.getWorkflowTemplateByID(inputs.TemplateID)
if findErr != nil {
return fmt.Errorf("invalid template ID %d: %w", inputs.TemplateID, findErr)
}
Expand Down Expand Up @@ -242,7 +252,7 @@ func (h *handler) Execute(inputs Inputs) error {
workflowTitles := h.extractWorkflowTitles(workflowTemplates)
if err := prompt.SelectPrompt(h.stdin, "Pick a workflow template", workflowTitles, func(choice string) error {
selected, selErr := h.getWorkflowTemplateByTitle(choice, workflowTemplates)
tpl = selected
selectedWorkflowTemplate = selected
return selErr
}); err != nil {
return fmt.Errorf("template selection aborted: %w", err)
Expand Down Expand Up @@ -281,19 +291,19 @@ func (h *handler) Execute(inputs Inputs) error {
return err
}

if err := h.copySecretsFileIfExists(projectRoot, tpl); err != nil {
if err := h.copySecretsFileIfExists(projectRoot, selectedWorkflowTemplate); err != nil {
return fmt.Errorf("failed to copy secrets file: %w", err)
}

// Get project name from project root
projectName := filepath.Base(projectRoot)

if err := h.generateWorkflowTemplate(workflowDirectory, tpl, projectName); err != nil {
if err := h.generateWorkflowTemplate(workflowDirectory, selectedWorkflowTemplate, projectName); err != nil {
return fmt.Errorf("failed to scaffold workflow: %w", err)
}

// Generate contracts at project level if template has contracts
if err := h.generateContractsTemplate(projectRoot, tpl, projectName); err != nil {
if err := h.generateContractsTemplate(projectRoot, selectedWorkflowTemplate, projectName); err != nil {
return fmt.Errorf("failed to scaffold contracts: %w", err)
}

Expand All @@ -312,12 +322,22 @@ func (h *handler) Execute(inputs Inputs) error {
fmt.Println("")
fmt.Println("Next steps:")
fmt.Println("")
fmt.Println(" 1. Navigate to your workflow directory to see workflow details:")
fmt.Printf(" cd %s\n", workflowDirectory)
fmt.Println("")
fmt.Println(" 2. To learn more about this template view the README.MD file:")
fmt.Printf(" %s\n", filepath.Join(workflowDirectory, "README.md"))
fmt.Println("")

if selectedWorkflowTemplate.Name == HelloWorldTemplate {
fmt.Println(" 1. Navigate to your project directory:")
fmt.Printf(" cd %s\n", projectRoot)
fmt.Println("")
fmt.Println(" 2. Run the worfklow on your machine:")
fmt.Printf(" `cre workflow simulate %s`\n", workflowName)
fmt.Println("")
} else {
fmt.Println(" 1. Navigate to your workflow directory to see workflow details:")
fmt.Printf(" cd %s\n", workflowDirectory)
fmt.Println("")
fmt.Println(" 2. To learn more about this template view the README.MD file:")
fmt.Printf(" %s\n", filepath.Join(workflowDirectory, "README.md"))
fmt.Println("")
}

return nil
}
Expand Down
157 changes: 145 additions & 12 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package cmd
import (
"fmt"
"os"
"strings"
"time"

"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"

"github.com/smartcontractkit/cre-cli/cmd/account"
Expand Down Expand Up @@ -58,7 +60,7 @@ func newRootCommand() *cobra.Command {
rootCmd := &cobra.Command{
Use: "cre",
Short: "CRE CLI tool",
Long: `A command line tool for testing and managing CRE workflows.`,
Long: `A command line tool for building, testing and managing Chainlink Runtime Environment (CRE) workflows.`,
// remove autogenerated string that contains this comment: "Auto generated by spf13/cobra on DD-Mon-YYYY"
// timestamps can cause docs to keep regenerating on each new PR for no good reason
DisableAutoGenTag: true,
Expand Down Expand Up @@ -120,6 +122,109 @@ func newRootCommand() *cobra.Command {
},
}

cobra.AddTemplateFunc("wrappedFlagUsages", func(fs *pflag.FlagSet) string {
// 100 = wrap width
return strings.TrimRight(fs.FlagUsagesWrapped(100), "\n")
})

cobra.AddTemplateFunc("hasUngrouped", func(c *cobra.Command) bool {
for _, cmd := range c.Commands() {
if cmd.IsAvailableCommand() && !cmd.Hidden && cmd.GroupID == "" {
return true
}
}
return false
})

rootCmd.SetHelpTemplate(`{{with (or .Long .Short)}}{{.}}{{end}}

Usage:
{{- if .Runnable}}
{{.UseLine}}
{{- else if .HasAvailableSubCommands}}
{{.CommandPath}} [command]
{{- end}}

{{- /* Only show Available Commands if there are any */}}
{{- if .HasAvailableSubCommands}}

Available Commands:
Copy link
Collaborator

@anirudhwarrier anirudhwarrier Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. can Available Commands be shown only if there are sub-commands. right now for commands like init, version, login and any sub-command like workflow/deploy if you do --help, it prints out empty Available Commands:
    Eg:
❯ cre workflow deploy --help
Compiles the workflow, uploads the artifacts, and registers the workflow in the Workflow Registry contract.

Usage:
  cre workflow deploy [command]

Available Commands:

Flags:
  -r, --auto-start           Activate and run the workflow after registration, or pause it
                             (default true)
  -h, --help                 help for deploy
  -o, --output string        The output file for the compiled WASM binary encoded in base64
                             (default "./binary.wasm.br.b64")
  -l, --owner-label string   Label for the workflow owner (used during auto-link if owner is
                             not already linked)
      --unsigned             If set, the command will either return the raw transaction
                             instead of sending it to the network or execute the second step
                             of secrets operations using a previously generated raw transaction
      --yes                  If set, the command will skip the confirmation prompt and proceed
                             with the operation even if it is potentially destructive

Use "cre workflow deploy [command] --help" for more information about a command.

💡 Tip: New here? Run:
  cre init
  to create your first cre project.

📘 Need more help?
  Visit https://docs.chain.link/cre
  1. Also the Examples and Global Flags are no longer shown now, while it was previously. It should be brought back?
❯ cre workflow deploy --help
Compiles the workflow, uploads the artifacts, and registers the workflow in the Workflow Registry contract.

Usage:
  cre workflow deploy <workflow-folder-path> [flags]

Examples:

		cre workflow deploy ./my-workflow


Flags:
  -r, --auto-start           Activate and run the workflow after registration, or pause it (default true)
  -h, --help                 help for deploy
  -o, --output string        The output file for the compiled WASM binary encoded in base64 (default "./binary.wasm.br.b64")
  -l, --owner-label string   Label for the workflow owner (used during auto-link if owner is not already linked)
      --unsigned             If set, the command will either return the raw transaction instead of sending it to the network or execute the second step of secrets operations using a previously generated raw transaction
      --yes                  If set, the command will skip the confirmation prompt and proceed with the operation even if it is potentially destructive

Global Flags:
  -e, --env string            Path to .env file which contains sensitive info (default ".env")
  -R, --project-root string   Path to the project root
  -T, --target string         Set the target settings
  -v, --verbose               Print DEBUG logs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch @anirudhwarrier

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. please see test plan for details.

{{- $groupsUsed := false -}}
{{- $firstGroup := true -}}

{{- range $grp := .Groups}}
{{- $has := false -}}
{{- range $.Commands}}
{{- if (and (not .Hidden) (.IsAvailableCommand) (eq .GroupID $grp.ID))}}{{- $has = true}}{{- end}}
{{- end}}
{{- if $has}}
{{- $groupsUsed = true -}}
{{- if $firstGroup}}{{- $firstGroup = false -}}{{end}}
{{printf "%s:" $grp.Title}}
{{- range $.Commands}}
{{- if (and (not .Hidden) (.IsAvailableCommand) (eq .GroupID $grp.ID))}}
{{rpad .Name .NamePadding}} {{.Short}}
{{- end}}
{{- end}}
{{- end}}
{{- end}}

{{- if $groupsUsed }}
{{- /* Groups are in use; show ungrouped as "Other" if any */}}
{{- if hasUngrouped .}}
Other:
{{- range .Commands}}
{{- if (and (not .Hidden) (.IsAvailableCommand) (eq .GroupID ""))}}
{{rpad .Name .NamePadding}} {{.Short}}
{{- end}}
{{- end}}
{{- end}}
{{- else }}
{{- /* No groups at this level; show a flat list with no "Other" header */}}
{{- range .Commands}}
{{- if (and (not .Hidden) (.IsAvailableCommand))}}
{{rpad .Name .NamePadding}} {{.Short}}
{{- end}}
{{- end}}
{{- end }}
{{- end }}

{{- /* Examples (if any) */}}
{{- if .HasExample}}

Examples:
{{.Example}}
{{- end }}

{{- /* Flags (local) */}}
{{- $local := (.LocalFlags.FlagUsagesWrapped 100 | trimTrailingWhitespaces) -}}
{{- if $local }}

Flags:
{{$local}}
{{- end }}

{{- /* Global / inherited flags */}}
{{- $inherited := (.InheritedFlags.FlagUsagesWrapped 100 | trimTrailingWhitespaces) -}}
{{- if $inherited }}

Global Flags:
{{$inherited}}
{{- end }}

{{- if .HasAvailableSubCommands }}

Use "{{.CommandPath}} [command] --help" for more information about a command.
{{- end }}

💡 Tip: New here? Run:
cre init
to create your first cre project.

📘 Need more help?
Visit https://docs.chain.link/cre
`)

// Definition of global flags:
// env file flag is present for every subcommand
rootCmd.PersistentFlags().StringP(
Expand All @@ -140,26 +245,54 @@ func newRootCommand() *cobra.Command {
settings.Flags.Verbose.Name,
settings.Flags.Verbose.Short,
false,
"Print DEBUG logs",
"Run command in VERBOSE mode",
)
// target settings is present in every subcommand
rootCmd.PersistentFlags().StringP(
settings.Flags.Target.Name,
settings.Flags.Target.Short,
"",
"Set the target settings",
"Use target settings from YAML config",
)
rootCmd.CompletionOptions.HiddenDefaultCmd = true

rootCmd.AddCommand(secrets.New(runtimeContext))
rootCmd.AddCommand(workflow.New(runtimeContext))
rootCmd.AddCommand(version.New(runtimeContext))
rootCmd.AddCommand(login.New(runtimeContext))
rootCmd.AddCommand(logout.New(runtimeContext))
rootCmd.AddCommand(creinit.New(runtimeContext))
rootCmd.AddCommand(generatebindings.New(runtimeContext))
rootCmd.AddCommand(account.New(runtimeContext))
rootCmd.AddCommand(whoami.New(runtimeContext))
secretsCmd := secrets.New(runtimeContext)
workflowCmd := workflow.New(runtimeContext)
versionCmd := version.New(runtimeContext)
loginCmd := login.New(runtimeContext)
logoutCmd := logout.New(runtimeContext)
initCmd := creinit.New(runtimeContext)
genBindingsCmd := generatebindings.New(runtimeContext)
accountCmd := account.New(runtimeContext)
whoamiCmd := whoami.New(runtimeContext)

// Define groups (order controls display order)
rootCmd.AddGroup(&cobra.Group{ID: "getting-started", Title: "Getting Started"})
rootCmd.AddGroup(&cobra.Group{ID: "account", Title: "Account"})
rootCmd.AddGroup(&cobra.Group{ID: "workflow", Title: "Workflow"})
rootCmd.AddGroup(&cobra.Group{ID: "secret", Title: "Secret"})

initCmd.GroupID = "getting-started"

loginCmd.GroupID = "account"
logoutCmd.GroupID = "account"
accountCmd.GroupID = "account"
whoamiCmd.GroupID = "account"

secretsCmd.GroupID = "secret"
workflowCmd.GroupID = "workflow"

rootCmd.AddCommand(
initCmd,
versionCmd,
loginCmd,
logoutCmd,
accountCmd,
whoamiCmd,
secretsCmd,
workflowCmd,
genBindingsCmd,
)

return rootCmd
}
Expand Down
12 changes: 5 additions & 7 deletions cmd/workflow/activate/activate.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@ type Inputs struct {

func New(runtimeContext *runtime.Context) *cobra.Command {
activateCmd := &cobra.Command{
Use: "activate <workflow-folder-path>",
Short: "Activates workflow on the Workflow Registry contract",
Long: `Changes workflow status to active on the Workflow Registry contract`,
Args: cobra.ExactArgs(1),
Example: `
cre workflow activate ./my-workflow
`,
Use: "activate <workflow-folder-path>",
Short: "Activates workflow on the Workflow Registry contract",
Long: `Changes workflow status to active on the Workflow Registry contract`,
Args: cobra.ExactArgs(1),
Example: `cre workflow activate ./my-workflow`,
RunE: func(cmd *cobra.Command, args []string) error {
handler := newHandler(runtimeContext)

Expand Down
12 changes: 5 additions & 7 deletions cmd/workflow/delete/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ type Inputs struct {

func New(runtimeContext *runtime.Context) *cobra.Command {
deleteCmd := &cobra.Command{
Use: "delete <workflow-folder-path>",
Short: "Deletes all versions of a workflow from the Workflow Registry",
Long: "Deletes all workflow versions matching the given name and owner address.",
Args: cobra.ExactArgs(1),
Example: `
cre workflow delete ./my-workflow
`,
Use: "delete <workflow-folder-path>",
Short: "Deletes all versions of a workflow from the Workflow Registry",
Long: "Deletes all workflow versions matching the given name and owner address.",
Args: cobra.ExactArgs(1),
Example: `cre workflow delete ./my-workflow`,
RunE: func(cmd *cobra.Command, args []string) error {
handler := newHandler(runtimeContext, cmd.InOrStdin())

Expand Down
Loading
Loading