Repository: onflow/flow-cli
Pull request #1643 (2024-06-05 23:06:18)

Update feature branch with master

______

For contributor use:

- [ ] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels



Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index 76b64afb..0614942c 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -2,9 +2,7 @@
 name: Reporting a Problem/Bug
 about: Reporting a Problem/Bug
 title: ''
-labels: bug, Feedback
-assignees: chasefleming
-
+labels: bug
 ---
 ### Instructions
 
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md
index e9722075..9bc65a8b 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.md
+++ b/.github/ISSUE_TEMPLATE/feature-request.md
@@ -2,9 +2,7 @@
 name: Requesting a Feature or Improvement
 about: "For feature requests. Please search for existing issues first. Also see CONTRIBUTING.md"
 title: ''
-labels: Feedback, Feature
-assignees: chasefleming
-
+labels: Feature
 ---
 
 ## Instructions
diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml
index 2b027d30..5d5817a7 100644
--- a/.github/workflows/build-release.yaml
+++ b/.github/workflows/build-release.yaml
@@ -26,8 +26,7 @@ jobs:
           echo 'LILICO_TOKEN=${{ secrets.LILICO_TOKEN }}' >> .release-env
           echo 'APP_VERSION=$(basename ${GITHUB_REF})' >> .release-env
           echo 'BUILD_TIME=$(date --iso-8601=seconds)' >> .release-env
-          echo 'VERSION=${{ github.ref_name }}' >> .release-env
-          echo 'COMMIT=${{ github.sha }}' >> .release-env
+          echo 'VERSION=${{ github.event.release.tag_name }}' >> .release-env
           echo 'GITHUB_TOKEN=${{ secrets.FLOW_CLI_RELEASE }}' >> .release-env
       - name: Build and Release
         run: make release
\ No newline at end of file
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 47a0873e..b1c08ab8 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -32,7 +32,7 @@ builds:
     flags:
       - -mod=readonly
     ldflags:
-      - -X github.com/onflow/flow-cli/build.commit={{ .Env.COMMIT }} -X github.com/onflow/flow-cli/build.semver={{ .Env.VERSION }} -X github.com/onflow/flow-cli/internal/command.mixpanelToken={{ .Env.MIXPANEL_PROJECT_TOKEN }} -X github.com/onflow/flow-cli/internal/accounts.accountToken={{ .Env.LILICO_TOKEN }}
+      - -X github.com/onflow/flow-cli/build.semver={{ .Env.VERSION }} -X github.com/onflow/flow-cli/internal/command.mixpanelToken={{ .Env.MIXPANEL_PROJECT_TOKEN }} -X github.com/onflow/flow-cli/internal/accounts.accountToken={{ .Env.LILICO_TOKEN }}
 
 archives:
   - id: golang-cross
diff --git a/Makefile b/Makefile
index c910d82a..29c24fed 100644
--- a/Makefile
+++ b/Makefile
@@ -2,10 +2,6 @@
 PACKAGE_NAME := github.com/onflow/flow-cli
 GOLANG_CROSS_VERSION ?= v1.22.0
 
-# The short Git commit hash
-SHORT_COMMIT := $(shell git rev-parse --short HEAD)
-# The Git commit hash
-COMMIT := $(shell git rev-parse HEAD)
 # The tag of the current commit, otherwise empty
 VERSION := $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null)
 # Name of the cover profile
@@ -63,7 +59,7 @@ $(BINARY):
 	GO111MODULE=on go build \
 		-trimpath \
 		-ldflags \
-		"-X github.com/onflow/flow-cli/build.commit=$(COMMIT) -X github.com/onflow/flow-cli/build.semver=$(VERSION) -X github.com/onflow/flow-cli/internal/accounts.accountToken=${ACCOUNT_TOKEN}"\
+		"-X github.com/onflow/flow-cli/build.semver=$(VERSION) -X github.com/onflow/flow-cli/internal/accounts.accountToken=${ACCOUNT_TOKEN}"\
 		-o $(BINARY) ./cmd/flow
 
 .PHONY: versioned-binaries
diff --git a/cmd/flow/main.go b/cmd/flow/main.go
index 27e91e4a..677e1a7f 100644
--- a/cmd/flow/main.go
+++ b/cmd/flow/main.go
@@ -56,7 +56,6 @@ func main() {
 	}
 
 	// quick commands
-	quick.InitCommand.AddToParent(cmd)
 	quick.DeployCommand.AddToParent(cmd)
 	quick.RunCommand.AddToParent(cmd)
 
diff --git a/go.mod b/go.mod
index e6abf505..b00717b3 100644
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,10 @@ go 1.22
 toolchain go1.22.3
 
 require (
+	github.com/charmbracelet/bubbles v0.18.0
+	github.com/charmbracelet/bubbletea v0.25.0
 	github.com/dukex/mixpanel v1.0.1
-	github.com/getsentry/sentry-go v0.27.0
+	github.com/getsentry/sentry-go v0.28.0
 	github.com/go-git/go-git/v5 v5.11.0
 	github.com/google/go-github v17.0.0+incompatible
 	github.com/gosuri/uilive v0.0.4
@@ -31,7 +33,7 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/psiemens/sconfig v0.1.0
 	github.com/radovskyb/watcher v1.0.7
-	github.com/rs/zerolog v1.32.0
+	github.com/rs/zerolog v1.33.0
 	github.com/sergi/go-diff v1.3.1
 	github.com/spf13/afero v1.10.0
 	github.com/spf13/cobra v1.8.0
@@ -55,6 +57,8 @@ require (
 	github.com/SaveTheRbtz/mph v0.1.1-0.20240117162131-4166ec7869bc // indirect
 	github.com/StackExchange/wmi v1.2.1 // indirect
 	github.com/VictoriaMetrics/fastcache v1.12.1 // indirect
+	github.com/atotto/clipboard v0.1.4 // indirect
+	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bits-and-blooms/bitset v1.10.0 // indirect
 	github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect
@@ -63,6 +67,7 @@ require (
 	github.com/cenkalti/backoff/v4 v4.2.1 // indirect
 	github.com/cespare/xxhash v1.1.0 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/charmbracelet/lipgloss v0.9.1 // indirect
 	github.com/chzyer/readline v1.5.0 // indirect
 	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/cockroachdb/errors v1.9.1 // indirect
@@ -72,6 +77,7 @@ require (
 	github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
 	github.com/consensys/bavard v0.1.13 // indirect
 	github.com/consensys/gnark-crypto v0.12.1 // indirect
+	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
 	github.com/coreos/go-semver v0.3.0 // indirect
 	github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
@@ -157,7 +163,7 @@ require (
 	github.com/karalabe/usb v0.0.2 // indirect
 	github.com/kevinburke/go-bindata v3.24.0+incompatible // indirect
 	github.com/kevinburke/ssh_config v1.2.0 // indirect
-	github.com/klauspost/compress v1.17.4 // indirect
+	github.com/klauspost/compress v1.17.7 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
 	github.com/kr/pretty v0.3.1 // indirect
 	github.com/kr/text v0.2.0 // indirect
@@ -167,9 +173,11 @@ require (
 	github.com/libp2p/go-msgio v0.3.0 // indirect
 	github.com/lmars/go-slip10 v0.0.0-20190606092855-400ba44fee12 // indirect
 	github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
+	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/mattn/go-localereader v0.0.1 // indirect
 	github.com/mattn/go-runewidth v0.0.15 // indirect
 	github.com/mattn/go-tty v0.0.4 // indirect
 	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
@@ -178,6 +186,10 @@ require (
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mmcloughlin/addchain v0.4.0 // indirect
 	github.com/mr-tron/base58 v1.2.0 // indirect
+	github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
+	github.com/muesli/cancelreader v0.2.2 // indirect
+	github.com/muesli/reflow v0.3.0 // indirect
+	github.com/muesli/termenv v0.15.2 // indirect
 	github.com/multiformats/go-base32 v0.1.0 // indirect
 	github.com/multiformats/go-base36 v0.2.0 // indirect
 	github.com/multiformats/go-multiaddr v0.12.2 // indirect
diff --git a/go.sum b/go.sum
index 14d6ea81..9404a52c 100644
--- a/go.sum
+++ b/go.sum
@@ -1030,6 +1030,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
+github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
+github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
@@ -1083,6 +1085,8 @@ github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J
 github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
 github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
 github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
@@ -1127,6 +1131,12 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
+github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
+github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
+github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
+github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
+github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM=
 github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
@@ -1191,6 +1201,8 @@ github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJ
 github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
 github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
 github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
+github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
+github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -1368,8 +1380,8 @@ github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b/go.mod h1:CDncRY
 github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
 github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
 github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
-github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
-github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
+github.com/getsentry/sentry-go v0.28.0 h1:7Rqx9M3ythTKy2J6uZLHmc8Sz9OGgIlseuO1iBX/s0M=
+github.com/getsentry/sentry-go v0.28.0/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
 github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
@@ -1888,8 +1900,8 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
 github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
 github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
-github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
-github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
+github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -1972,6 +1984,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG
 github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
 github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
 github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
 github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
 github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
 github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
@@ -2010,11 +2024,14 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
@@ -2079,6 +2096,14 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA
 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
 github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
 github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
+github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
+github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
+github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
+github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
+github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
 github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
 github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
 github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
@@ -2118,8 +2143,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
 github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA=
 github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
+github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@@ -2327,6 +2352,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -2344,8 +2370,8 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
 github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
-github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
+github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
+github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
 github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
diff --git a/internal/accounts/contract-add.go b/internal/accounts/contract-add.go
index b82e999b..87c8538d 100644
--- a/internal/accounts/contract-add.go
+++ b/internal/accounts/contract-add.go
@@ -22,7 +22,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/onflow/flow-cli/internal/util"
+	"github.com/onflow/flow-cli/internal/prompt"
 
 	"github.com/onflow/cadence"
 	flowsdk "github.com/onflow/flow-go-sdk"
@@ -88,7 +88,7 @@ func deployContract(update bool, flags *deployContractFlags) command.RunWithStat
 
 		deployFunc := flowkit.UpdateExistingContract(update)
 		if updateContractFlags.ShowDiff {
-			deployFunc = util.ShowContractDiffPrompt(logger)
+			deployFunc = prompt.ShowContractDiffPrompt(logger)
 		}
 
 		txID, _, err := flow.AddContract(
diff --git a/internal/accounts/contract-remove.go b/internal/accounts/contract-remove.go
index 5061289f..b0f69bfd 100644
--- a/internal/accounts/contract-remove.go
+++ b/internal/accounts/contract-remove.go
@@ -22,9 +22,9 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/spf13/cobra"
+	"github.com/onflow/flow-cli/internal/prompt"
 
-	"github.com/onflow/flow-cli/internal/util"
+	"github.com/spf13/cobra"
 
 	"github.com/onflow/flowkit/v2"
 	"github.com/onflow/flowkit/v2/output"
@@ -70,7 +70,7 @@ func removeContract(
 		return nil, err
 	}
 
-	removeFromState := util.RemoveContractFromFlowJSONPrompt(contractName)
+	removeFromState := prompt.RemoveContractFromFlowJSONPrompt(contractName)
 
 	if removeFromState {
 		// If a network flag is provided, remove from that networks deployments
diff --git a/internal/accounts/create-interactive.go b/internal/accounts/create-interactive.go
index 113ed74d..791f72af 100644
--- a/internal/accounts/create-interactive.go
+++ b/internal/accounts/create-interactive.go
@@ -30,6 +30,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	flowsdk "github.com/onflow/flow-go-sdk"
 	"github.com/onflow/flow-go-sdk/crypto"
 	"google.golang.org/grpc/codes"
@@ -50,8 +52,8 @@ import (
 // and it then uses account creation APIs to automatically create the account on the network as well as save it.
 func createInteractive(state *flowkit.State) (*accountResult, error) {
 	log := output.NewStdoutLogger(output.InfoLog)
-	name := util.AccountNamePrompt(state.Accounts().Names())
-	networkName, selectedNetwork := util.CreateAccountNetworkPrompt()
+	name := prompt.AccountNamePrompt(state.Accounts().Names())
+	networkName, selectedNetwork := prompt.CreateAccountNetworkPrompt()
 	privateFile := accounts.PrivateKeyFile(name, "")
 
 	// create new gateway based on chosen network
diff --git a/internal/command/command.go b/internal/command/command.go
index 871ad541..13abd0b0 100644
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -34,6 +34,8 @@ import (
 	"sync"
 	"time"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/dukex/mixpanel"
 	"github.com/getsentry/sentry-go"
 	"github.com/google/go-github/github"
@@ -303,14 +305,16 @@ func checkVersion(logger output.Logger) {
 		return
 	}
 
-	latestVersion, ok := versions["stable"].(string)
+	latestVersionRaw, ok := versions["stable"].(string)
 	if !ok {
 		return
 	}
 
+	latestVersion := fmt.Sprintf("v%s", strings.TrimPrefix(latestVersionRaw, "v"))
+
 	if currentVersion != latestVersion {
 		logger.Info(fmt.Sprintf(
-			"\n%s  Version warning: a new version of Flow CLI is available (v%s).\n"+
+			"\n%s  Version warning: a new version of Flow CLI is available (%s).\n"+
 				"   Read the installation guide for upgrade instructions: https://docs.onflow.org/flow-cli/install\n",
 			output.WarningEmoji(),
 			strings.ReplaceAll(latestVersion, "\n", ""),
@@ -385,7 +389,7 @@ func initCrashReporting() {
 			// ask for crash report permission
 			fmt.Printf("\n%s Crash detected! %s\n\n", output.ErrorEmoji(), event.Message)
 
-			if util.ReportCrash() {
+			if prompt.ReportCrash() {
 				return event
 			} else {
 				fmt.Printf("\nPlease help us improve the Flow CLI by opening an issue on https://github.com/onflow/flow-cli/issues, \nand pasting the output as well as a description of the actions you took that resulted in this crash.\n\n")
diff --git a/internal/config/add-account.go b/internal/config/add-account.go
index e5317c20..1211678a 100644
--- a/internal/config/add-account.go
+++ b/internal/config/add-account.go
@@ -22,6 +22,8 @@ import (
 	"fmt"
 	"strconv"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flowkit/v2/accounts"
 
 	"github.com/onflow/flow-go-sdk"
@@ -33,7 +35,6 @@ import (
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsAddAccount struct {
@@ -71,7 +72,7 @@ func addAccount(
 	}
 
 	if !flagsProvided {
-		raw = util.NewAccountPrompt()
+		raw = prompt.NewAccountPrompt()
 	}
 
 	key, err := parseKey(raw.Key, raw.SigAlgo)
@@ -140,7 +141,7 @@ func parseKeyIndex(value string) (int, error) {
 	return v, nil
 }
 
-func flagsToAccountData(flags flagsAddAccount) (*util.AccountData, bool, error) {
+func flagsToAccountData(flags flagsAddAccount) (*prompt.AccountData, bool, error) {
 	if flags.Name == "" && flags.Address == "" && flags.Key == "" {
 		return nil, false, nil
 	}
@@ -157,7 +158,7 @@ func flagsToAccountData(flags flagsAddAccount) (*util.AccountData, bool, error)
 		return nil, true, fmt.Errorf("invalid address")
 	}
 
-	return &util.AccountData{
+	return &prompt.AccountData{
 		Name:     flags.Name,
 		Address:  flags.Address,
 		SigAlgo:  flags.SigAlgo,
diff --git a/internal/config/add-contract.go b/internal/config/add-contract.go
index 3153e2ee..e2bbfc7f 100644
--- a/internal/config/add-contract.go
+++ b/internal/config/add-contract.go
@@ -21,6 +21,8 @@ package config
 import (
 	"fmt"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flow-go-sdk"
 	"github.com/spf13/cobra"
 
@@ -29,7 +31,6 @@ import (
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsAddContract struct {
@@ -66,7 +67,7 @@ func addContract(
 	}
 
 	if !flagsProvided {
-		raw = util.NewContractPrompt()
+		raw = prompt.NewContractPrompt()
 	}
 
 	contract := config.Contract{
@@ -114,7 +115,7 @@ func addContract(
 	}, nil
 }
 
-func flagsToContractData(flags flagsAddContract) (*util.ContractData, bool, error) {
+func flagsToContractData(flags flagsAddContract) (*prompt.ContractData, bool, error) {
 	if flags.Name == "" && flags.Filename == "" {
 		return nil, false, nil
 	}
@@ -143,7 +144,7 @@ func flagsToContractData(flags flagsAddContract) (*util.ContractData, bool, erro
 		return nil, true, fmt.Errorf("invalid mainnnet alias address")
 	}
 
-	return &util.ContractData{
+	return &prompt.ContractData{
 		Name:     flags.Name,
 		Source:   flags.Filename,
 		Emulator: flags.EmulatorAlias,
diff --git a/internal/config/add-deployment.go b/internal/config/add-deployment.go
index efdec39c..35d70402 100644
--- a/internal/config/add-deployment.go
+++ b/internal/config/add-deployment.go
@@ -21,6 +21,8 @@ package config
 import (
 	"fmt"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/spf13/cobra"
 
 	"github.com/onflow/flowkit/v2"
@@ -28,7 +30,6 @@ import (
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsAddDeployment struct {
@@ -63,7 +64,7 @@ func addDeployment(
 	}
 
 	if !flagsProvided {
-		raw = util.NewDeploymentPrompt(*state.Networks(), state.Config().Accounts, *state.Contracts())
+		raw = prompt.NewDeploymentPrompt(*state.Networks(), state.Config().Accounts, *state.Contracts())
 	}
 
 	deployment := state.Deployments().ByAccountAndNetwork(raw.Account, raw.Network)
@@ -90,7 +91,7 @@ func addDeployment(
 	}, nil
 }
 
-func flagsToDeploymentData(flags flagsAddDeployment) (*util.DeploymentData, bool, error) {
+func flagsToDeploymentData(flags flagsAddDeployment) (*prompt.DeploymentData, bool, error) {
 	if flags.Network == "" && flags.Account == "" && len(flags.Contracts) == 0 {
 		return nil, false, nil
 	}
@@ -103,7 +104,7 @@ func flagsToDeploymentData(flags flagsAddDeployment) (*util.DeploymentData, bool
 		return nil, true, fmt.Errorf("at least one contract name must be provided")
 	}
 
-	return &util.DeploymentData{
+	return &prompt.DeploymentData{
 		Network:   flags.Network,
 		Account:   flags.Account,
 		Contracts: flags.Contracts,
diff --git a/internal/config/add-network.go b/internal/config/add-network.go
index d2a68f61..8d7a7d81 100644
--- a/internal/config/add-network.go
+++ b/internal/config/add-network.go
@@ -22,6 +22,8 @@ import (
 	"fmt"
 	"net/url"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/spf13/cobra"
 
 	"github.com/onflow/flowkit/v2"
@@ -64,7 +66,7 @@ func addNetwork(
 	}
 
 	if !flagsProvided {
-		raw = util.NewNetworkPrompt()
+		raw = prompt.NewNetworkPrompt()
 	}
 
 	state.Networks().AddOrUpdate(config.Network{
diff --git a/internal/config/config.go b/internal/config/config.go
index 3689ca34..84b4eae7 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -29,7 +29,6 @@ var Cmd = &cobra.Command{
 }
 
 func init() {
-	initCommand.AddToParent(Cmd)
 	Cmd.AddCommand(addCmd)
 	Cmd.AddCommand(removeCmd)
 }
diff --git a/internal/config/init.go b/internal/config/init.go
index 3439764f..c5868075 100644
--- a/internal/config/init.go
+++ b/internal/config/init.go
@@ -19,58 +19,55 @@
 package config
 
 import (
-	"bytes"
 	"fmt"
+	"os"
 
 	"github.com/onflow/flowkit/v2/accounts"
 
 	"github.com/onflow/flow-go-sdk/crypto"
-	"github.com/spf13/cobra"
 
 	"github.com/onflow/flowkit/v2"
 	"github.com/onflow/flowkit/v2/config"
-	"github.com/onflow/flowkit/v2/output"
-
-	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
-type flagsInit struct {
-	ServicePrivateKey  string `flag:"service-private-key" info:"Service account private key"`
-	ServiceKeySigAlgo  string `default:"ECDSA_P256" flag:"service-sig-algo" info:"Service account key signature algorithm"`
-	ServiceKeyHashAlgo string `default:"SHA3_256" flag:"service-hash-algo" info:"Service account key hash algorithm"`
-	Reset              bool   `default:"false" flag:"reset" info:"Reset configuration file"`
-	Global             bool   `default:"false" flag:"global" info:"Initialize global user configuration"`
+// InitConfigParameters holds all necessary parameters for initializing the configuration.
+type InitConfigParameters struct {
+	ServicePrivateKey  string
+	ServiceKeySigAlgo  string
+	ServiceKeyHashAlgo string
+	Reset              bool
+	Global             bool
+	TargetDirectory    string
 }
 
-var InitFlag = flagsInit{}
-
-var initCommand = &command.Command{
-	Cmd: &cobra.Command{
-		Use:   "init",
-		Short: "Initialize a new configuration",
-	},
-	Flags: &InitFlag,
-	Run:   Initialise,
-}
+// InitializeConfiguration creates the Flow configuration json file based on the provided parameters.
+func InitializeConfiguration(params InitConfigParameters, readerWriter flowkit.ReaderWriter) (*flowkit.State, error) {
+	var path string
+	if params.TargetDirectory != "" {
+		path = fmt.Sprintf("%s/flow.json", params.TargetDirectory)
 
-func Initialise(
-	_ []string,
-	_ command.GlobalFlags,
-	logger output.Logger,
-	readerWriter flowkit.ReaderWriter,
-	_ flowkit.Services,
-) (command.Result, error) {
-	logger.Info("⚠️Notice: for starting a new project prefer using 'flow setup'.")
+		// Create the directory if it doesn't exist
+		err := readerWriter.MkdirAll(params.TargetDirectory, os.ModePerm)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create target directory: %w", err)
+		}
+	} else {
+		// Otherwise, choose between the default and global paths
+		if params.Global {
+			path = config.GlobalPath()
+		} else {
+			path = config.DefaultPath
+		}
+	}
 
-	sigAlgo := crypto.StringToSignatureAlgorithm(InitFlag.ServiceKeySigAlgo)
+	sigAlgo := crypto.StringToSignatureAlgorithm(params.ServiceKeySigAlgo)
 	if sigAlgo == crypto.UnknownSignatureAlgorithm {
-		return nil, fmt.Errorf("invalid signature algorithm: %s", InitFlag.ServiceKeySigAlgo)
+		return nil, fmt.Errorf("invalid signature algorithm: %s", params.ServiceKeySigAlgo)
 	}
 
-	hashAlgo := crypto.StringToHashAlgorithm(InitFlag.ServiceKeyHashAlgo)
+	hashAlgo := crypto.StringToHashAlgorithm(params.ServiceKeyHashAlgo)
 	if hashAlgo == crypto.UnknownHashAlgorithm {
-		return nil, fmt.Errorf("invalid hash algorithm: %s", InitFlag.ServiceKeyHashAlgo)
+		return nil, fmt.Errorf("invalid hash algorithm: %s", params.ServiceKeyHashAlgo)
 	}
 
 	state, err := flowkit.Init(readerWriter)
@@ -78,15 +75,15 @@ func Initialise(
 		return nil, err
 	}
 
-	emulatorAccount, err := accounts.NewEmulatorAccount(readerWriter, crypto.ECDSA_P256, crypto.SHA3_256, "")
+	emulatorAccount, err := accounts.NewEmulatorAccount(readerWriter, crypto.ECDSA_P256, crypto.SHA3_256, params.TargetDirectory)
 	if err != nil {
 		return nil, err
 	}
 
 	state.Accounts().AddOrUpdate(emulatorAccount)
 
-	if InitFlag.ServicePrivateKey != "" {
-		privateKey, err := crypto.DecodePrivateKeyHex(sigAlgo, InitFlag.ServicePrivateKey)
+	if params.ServicePrivateKey != "" {
+		privateKey, err := crypto.DecodePrivateKeyHex(sigAlgo, params.ServicePrivateKey)
 		if err != nil {
 			return nil, fmt.Errorf("invalid private key: %w", err)
 		}
@@ -94,51 +91,12 @@ func Initialise(
 		state.SetEmulatorKey(privateKey)
 	}
 
-	path := config.DefaultPath
-	if InitFlag.Global {
-		path = config.GlobalPath()
-	}
-
-	if config.Exists(path) && !InitFlag.Reset {
+	if config.Exists(path) && !params.Reset {
 		return nil, fmt.Errorf(
 			"configuration already exists at: %s, if you want to reset configuration use the reset flag",
 			path,
 		)
 	}
 
-	err = state.Save(path)
-	if err != nil {
-		return nil, err
-	}
-
-	return &initResult{State: state}, nil
-}
-
-type initResult struct {
-	*flowkit.State
-}
-
-func (r *initResult) JSON() any {
-	return r
-}
-
-func (r *initResult) String() string {
-	var b bytes.Buffer
-	writer := util.CreateTabWriter(&b)
-	account, _ := r.State.EmulatorServiceAccount()
-
-	_, _ = fmt.Fprintf(writer, "Configuration initialized\n")
-	_, _ = fmt.Fprintf(writer, "Service account: %s\n\n", output.Bold("0x"+account.Address.String()))
-	_, _ = fmt.Fprintf(writer,
-		"Start emulator by running: %s \nReset configuration using: %s\n",
-		output.Bold("'flow emulator'"),
-		output.Bold("'flow init --reset'"),
-	)
-
-	_ = writer.Flush()
-	return b.String()
-}
-
-func (r *initResult) Oneliner() string {
-	return ""
+	return state, nil
 }
diff --git a/internal/config/remove-account.go b/internal/config/remove-account.go
index 2e26d63b..8cac41a1 100644
--- a/internal/config/remove-account.go
+++ b/internal/config/remove-account.go
@@ -21,11 +21,12 @@ package config
 import (
 	"github.com/spf13/cobra"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flowkit/v2"
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsRemoveAccount struct{}
@@ -54,7 +55,7 @@ func removeAccount(
 	if len(args) == 1 {
 		name = args[0]
 	} else {
-		name = util.RemoveAccountPrompt(state.Config().Accounts)
+		name = prompt.RemoveAccountPrompt(state.Config().Accounts)
 	}
 
 	err := state.Accounts().Remove(name)
diff --git a/internal/config/remove-contract.go b/internal/config/remove-contract.go
index dcccd634..237a9370 100644
--- a/internal/config/remove-contract.go
+++ b/internal/config/remove-contract.go
@@ -21,11 +21,12 @@ package config
 import (
 	"github.com/spf13/cobra"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flowkit/v2"
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsRemoveContract struct{}
@@ -54,7 +55,7 @@ func removeContract(
 	if len(args) == 1 {
 		name = args[0]
 	} else {
-		name = util.RemoveContractPrompt(*state.Contracts())
+		name = prompt.RemoveContractPrompt(*state.Contracts())
 	}
 
 	err := state.Contracts().Remove(name)
diff --git a/internal/config/remove-deployment.go b/internal/config/remove-deployment.go
index 1c4051ee..54bac98c 100644
--- a/internal/config/remove-deployment.go
+++ b/internal/config/remove-deployment.go
@@ -21,11 +21,12 @@ package config
 import (
 	"github.com/spf13/cobra"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flowkit/v2"
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsRemoveDeployment struct{}
@@ -56,7 +57,7 @@ func removeDeployment(
 		account = args[0]
 		network = args[1]
 	} else {
-		account, network = util.RemoveDeploymentPrompt(*state.Deployments())
+		account, network = prompt.RemoveDeploymentPrompt(*state.Deployments())
 	}
 
 	err := state.Deployments().Remove(account, network)
diff --git a/internal/config/remove-network.go b/internal/config/remove-network.go
index 26e09a3f..85d5270a 100644
--- a/internal/config/remove-network.go
+++ b/internal/config/remove-network.go
@@ -21,11 +21,12 @@ package config
 import (
 	"github.com/spf13/cobra"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flowkit/v2"
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsRemoveNetwork struct{}
@@ -54,7 +55,7 @@ func removeNetwork(
 	if len(args) == 1 {
 		name = args[0]
 	} else {
-		name = util.RemoveNetworkPrompt(*state.Networks())
+		name = prompt.RemoveNetworkPrompt(*state.Networks())
 	}
 
 	err := state.Networks().Remove(name)
diff --git a/internal/dependencymanager/add.go b/internal/dependencymanager/add.go
index 22a01c78..0bae6cf0 100644
--- a/internal/dependencymanager/add.go
+++ b/internal/dependencymanager/add.go
@@ -32,12 +32,12 @@ import (
 )
 
 type addFlagsCollection struct {
-	*dependencyManagerFlagsCollection
+	*Flags
 	name string
 }
 
 var addFlags = addFlagsCollection{
-	dependencyManagerFlagsCollection: &dependencyManagerFlagsCollection{},
+	Flags: &Flags{},
 }
 
 var addCommand = &command.Command{
@@ -53,7 +53,7 @@ var addCommand = &command.Command{
 
 func init() {
 	// Add common flags.
-	addFlags.dependencyManagerFlagsCollection.AddToCommand(addCommand.Cmd)
+	addFlags.Flags.AddToCommand(addCommand.Cmd)
 	// Add command-specific flags.
 	addCommand.Cmd.Flags().StringVar(&addFlags.name, "name", "", "Name of the dependency")
 }
@@ -69,13 +69,13 @@ func add(
 
 	dep := args[0]
 
-	installer, err := NewDependencyInstaller(logger, state, *addFlags.dependencyManagerFlagsCollection)
+	installer, err := NewDependencyInstaller(logger, state, true, "", *addFlags.Flags)
 	if err != nil {
 		logger.Error(fmt.Sprintf("Error: %v", err))
 		return nil, err
 	}
 
-	if err := installer.Add(dep, addFlags.name); err != nil {
+	if err := installer.AddBySourceString(dep, addFlags.name); err != nil {
 		logger.Error(fmt.Sprintf("Error: %v", err))
 		return nil, err
 	}
diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go
index 356b2ea8..ab54c665 100644
--- a/internal/dependencymanager/dependencyinstaller.go
+++ b/internal/dependencymanager/dependencyinstaller.go
@@ -28,13 +28,14 @@ import (
 
 	"github.com/psiemens/sconfig"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+	"github.com/onflow/flow-cli/internal/util"
+
 	"github.com/spf13/cobra"
 
 	"github.com/onflow/flow-go/fvm/systemcontracts"
 	flowGo "github.com/onflow/flow-go/model/flow"
 
-	"github.com/onflow/flow-cli/internal/util"
-
 	"github.com/onflow/flowkit/v2/gateway"
 
 	"github.com/onflow/flowkit/v2/project"
@@ -86,12 +87,12 @@ func (cl *categorizedLogs) LogAll(logger output.Logger) {
 	}
 }
 
-type dependencyManagerFlagsCollection struct {
+type Flags struct {
 	skipDeployments bool `default:"false" flag:"skip-deployments" info:"Skip adding the dependency to deployments"`
 	skipAlias       bool `default:"false" flag:"skip-alias" info:"Skip prompting for an alias"`
 }
 
-func (f *dependencyManagerFlagsCollection) AddToCommand(cmd *cobra.Command) {
+func (f *Flags) AddToCommand(cmd *cobra.Command) {
 	err := sconfig.New(f).
 		FromEnvironment(util.EnvPrefix).
 		BindFlags(cmd.Flags()).
@@ -106,6 +107,8 @@ type DependencyInstaller struct {
 	Gateways        map[string]gateway.Gateway
 	Logger          output.Logger
 	State           *flowkit.State
+	SaveState       bool
+	TargetDir       string
 	SkipDeployments bool
 	SkipAlias       bool
 	logs            categorizedLogs
@@ -113,7 +116,7 @@ type DependencyInstaller struct {
 }
 
 // NewDependencyInstaller creates a new instance of DependencyInstaller
-func NewDependencyInstaller(logger output.Logger, state *flowkit.State, flags dependencyManagerFlagsCollection) (*DependencyInstaller, error) {
+func NewDependencyInstaller(logger output.Logger, state *flowkit.State, saveState bool, targetDir string, flags Flags) (*DependencyInstaller, error) {
 	emulatorGateway, err := gateway.NewGrpcGateway(config.EmulatorNetwork)
 	if err != nil {
 		return nil, fmt.Errorf("error creating emulator gateway: %v", err)
@@ -145,12 +148,25 @@ func NewDependencyInstaller(logger output.Logger, state *flowkit.State, flags de
 		Gateways:        gateways,
 		Logger:          logger,
 		State:           state,
+		SaveState:       saveState,
+		TargetDir:       targetDir,
 		SkipDeployments: flags.skipDeployments,
 		SkipAlias:       flags.skipAlias,
 		dependencies:    make(map[string]config.Dependency),
 	}, nil
 }
 
+// saveState checks the SaveState flag and saves the state if set to true.
+func (di *DependencyInstaller) saveState() error {
+	if di.SaveState {
+		statePath := filepath.Join(di.TargetDir, "flow.json")
+		if err := di.State.Save(statePath); err != nil {
+			return fmt.Errorf("error saving state: %w", err)
+		}
+	}
+	return nil
+}
+
 // Install processes all the dependencies in the state and installs them and any dependencies they have
 func (di *DependencyInstaller) Install() error {
 	for _, dependency := range *di.State.Dependencies() {
@@ -162,8 +178,7 @@ func (di *DependencyInstaller) Install() error {
 
 	di.checkForConflictingContracts()
 
-	err := di.State.SaveDefault()
-	if err != nil {
+	if err := di.saveState(); err != nil {
 		return fmt.Errorf("error saving state: %w", err)
 	}
 
@@ -172,8 +187,8 @@ func (di *DependencyInstaller) Install() error {
 	return nil
 }
 
-// Add processes a single dependency and installs it and any dependencies it has, as well as adding it to the state
-func (di *DependencyInstaller) Add(depSource, customName string) error {
+// AddBySourceString processes a single dependency and installs it and any dependencies it has, as well as adding it to the state
+func (di *DependencyInstaller) AddBySourceString(depSource, customName string) error {
 	depNetwork, depAddress, depContractName, err := config.ParseSourceString(depSource)
 	if err != nil {
 		return fmt.Errorf("error parsing source: %w", err)
@@ -200,9 +215,40 @@ func (di *DependencyInstaller) Add(depSource, customName string) error {
 
 	di.checkForConflictingContracts()
 
-	err = di.State.SaveDefault()
-	if err != nil {
-		return fmt.Errorf("error saving state: %w", err)
+	if err := di.saveState(); err != nil {
+		return err
+	}
+
+	di.logs.LogAll(di.Logger)
+
+	return nil
+}
+
+// Add processes a single dependency and installs it and any dependencies it has, as well as adding it to the state
+func (di *DependencyInstaller) Add(dep config.Dependency) error {
+	if err := di.processDependency(dep); err != nil {
+		return fmt.Errorf("error processing dependency: %w", err)
+	}
+
+	if err := di.saveState(); err != nil {
+		return err
+	}
+
+	di.logs.LogAll(di.Logger)
+
+	return nil
+}
+
+// AddMany processes multiple dependencies and installs them as well as adding them to the state
+func (di *DependencyInstaller) AddMany(dependencies []config.Dependency) error {
+	for _, dep := range dependencies {
+		if err := di.processDependency(dep); err != nil {
+			return fmt.Errorf("error processing dependency: %w", err)
+		}
+	}
+
+	if err := di.saveState(); err != nil {
+		return err
 	}
 
 	di.logs.LogAll(di.Logger)
@@ -316,7 +362,7 @@ func (di *DependencyInstaller) contractFileExists(address, contractName string)
 
 func (di *DependencyInstaller) createContractFile(address, contractName, data string) error {
 	fileName := fmt.Sprintf("%s.cdc", contractName)
-	path := filepath.Join("imports", address, fileName)
+	path := filepath.Join(di.TargetDir, "imports", address, fileName)
 	dir := filepath.Dir(path)
 
 	if err := di.State.ReaderWriter().MkdirAll(dir, 0755); err != nil {
@@ -376,7 +422,7 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as
 	// If no hash, ignore
 	if dependency != nil && dependency.Hash != "" && dependency.Hash != originalContractDataHash {
 		msg := fmt.Sprintf("The latest version of %s is different from the one you have locally. Do you want to update it?", contractName)
-		if !util.GenericBoolPrompt(msg) {
+		if !prompt.GenericBoolPrompt(msg) {
 			return nil
 		}
 	}
@@ -435,7 +481,7 @@ func (di *DependencyInstaller) handleAdditionalDependencyTasks(networkName, cont
 func (di *DependencyInstaller) updateDependencyDeployment(contractName string) error {
 	// Add to deployments
 	// If a deployment already exists for that account, contract, and network, then ignore
-	raw := util.AddContractToDeploymentPrompt("emulator", *di.State.Accounts(), contractName)
+	raw := prompt.AddContractToDeploymentPrompt("emulator", *di.State.Accounts(), contractName)
 
 	if raw != nil {
 		deployment := di.State.Deployments().ByAccountAndNetwork(raw.Account, raw.Network)
@@ -465,7 +511,7 @@ func (di *DependencyInstaller) updateDependencyAlias(contractName, aliasNetwork
 	}
 
 	label := fmt.Sprintf("Enter an alias address for %s on %s if you have one, otherwise leave blank", contractName, missingNetwork)
-	raw := util.AddressPromptOrEmpty(label, "Invalid alias address")
+	raw := prompt.AddressPromptOrEmpty(label, "Invalid alias address")
 
 	if raw != "" {
 		contract, err := di.State.Contracts().ByName(contractName)
diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go
index ecb3a0e2..a73abf53 100644
--- a/internal/dependencymanager/install.go
+++ b/internal/dependencymanager/install.go
@@ -31,7 +31,7 @@ import (
 	"github.com/onflow/flow-cli/internal/command"
 )
 
-var installFlags = dependencyManagerFlagsCollection{}
+var installFlags = Flags{}
 
 var installCommand = &command.Command{
 	Cmd: &cobra.Command{
@@ -52,7 +52,7 @@ func install(
 ) (result command.Result, err error) {
 	logger.Info(util.MessageWithEmojiPrefix("🔄", "Installing dependencies from flow.json..."))
 
-	installer, err := NewDependencyInstaller(logger, state, installFlags)
+	installer, err := NewDependencyInstaller(logger, state, true, "", installFlags)
 	if err != nil {
 		logger.Error(fmt.Sprintf("Error: %v", err))
 		return nil, err
diff --git a/internal/migrate/stage.go b/internal/migrate/stage.go
index 542d6280..64902fc1 100644
--- a/internal/migrate/stage.go
+++ b/internal/migrate/stage.go
@@ -24,6 +24,8 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/logrusorgru/aurora/v4"
 	"github.com/onflow/cadence/runtime/common"
 	"github.com/onflow/flow-go-sdk"
@@ -141,7 +143,7 @@ func promptStagingUnvalidatedContracts(logger output.Logger) func(validatorError
 		infoMessage.WriteString("\nYou may still stage your contract, however it will be unable to be migrated until the missing contracts are staged by their respective owners.  It is important to monitor the status of your contract using the `flow migrate is-validated` command\n")
 		logger.Error(infoMessage.String())
 
-		return util.GenericBoolPrompt("Do you wish to continue staging your contract?")
+		return prompt.GenericBoolPrompt("Do you wish to continue staging your contract?")
 	}
 }
 
diff --git a/internal/project/deploy.go b/internal/project/deploy.go
index 20594f4e..844b576f 100644
--- a/internal/project/deploy.go
+++ b/internal/project/deploy.go
@@ -23,6 +23,8 @@ import (
 	"errors"
 	"fmt"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flow-go/fvm/systemcontracts"
 	flowGo "github.com/onflow/flow-go/model/flow"
 
@@ -36,7 +38,6 @@ import (
 	"github.com/onflow/flowkit/v2/project"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsDeploy struct {
@@ -73,7 +74,7 @@ func deploy(
 
 	deployFunc := flowkit.UpdateExistingContract(deployFlags.Update)
 	if deployFlags.ShowDiff {
-		deployFunc = util.ShowContractDiffPrompt(logger)
+		deployFunc = prompt.ShowContractDiffPrompt(logger)
 	}
 
 	c, err := flow.DeployProject(context.Background(), deployFunc)
@@ -149,7 +150,7 @@ func checkForStandardContractUsageOnMainnet(state *flowkit.State, logger output.
 		logger.Info(fmt.Sprintf("It is a standard contract already deployed at address 0x%s \n", standardContract.address.String()))
 		logger.Info(fmt.Sprintf("You can read more about it here: %s \n", standardContract.infoLink))
 
-		if replace || util.WantToUseMainnetVersionPrompt() {
+		if replace || prompt.WantToUseMainnetVersionPrompt() {
 			err := replaceContractWithAlias(state, standardContract)
 			if err != nil {
 				return err
diff --git a/internal/util/prompt.go b/internal/prompt/prompt.go
similarity index 97%
rename from internal/util/prompt.go
rename to internal/prompt/prompt.go
index f17663f0..bd8fc26c 100644
--- a/internal/util/prompt.go
+++ b/internal/prompt/prompt.go
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package util
+package prompt
 
 import (
 	"fmt"
@@ -25,6 +25,8 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/onflow/flow-cli/internal/util"
+
 	"github.com/onflow/flowkit/v2/accounts"
 
 	"github.com/gosuri/uilive"
@@ -193,7 +195,7 @@ func secureNetworkKeyPrompt() string {
 				return nil
 			}
 
-			return ValidateECDSAP256Pub(s)
+			return util.ValidateECDSAP256Pub(s)
 		},
 	}
 	networkKey, err := networkKeyPrompt.Run()
@@ -489,6 +491,17 @@ func NewDeploymentPrompt(
 	return deploymentData
 }
 
+func removeFromStringArray(s []string, el string) []string {
+	for i, v := range s {
+		if v == el {
+			s = append(s[:i], s[i+1:]...)
+			break
+		}
+	}
+
+	return s
+}
+
 // AddContractToDeploymentPrompt prompts a user to select an account to deploy a given contract on a given network
 func AddContractToDeploymentPrompt(networkName string, accounts accounts.Accounts, contractName string) *DeploymentData {
 	deploymentData := &DeploymentData{
@@ -795,3 +808,13 @@ func GenericBoolPrompt(msg string) bool {
 
 	return result == "Yes"
 }
+
+func GenericSelect(items []string, message string) string {
+	prompt := promptui.Select{
+		Label: message,
+		Items: items,
+	}
+	_, result, _ := prompt.Run()
+
+	return result
+}
diff --git a/internal/prompt/select-options.go b/internal/prompt/select-options.go
new file mode 100644
index 00000000..a1732873
--- /dev/null
+++ b/internal/prompt/select-options.go
@@ -0,0 +1,118 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019 Dapper Labs, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package prompt
+
+import (
+	"fmt"
+	"strings"
+
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+// optionSelectModel represents the prompt state but is now private
+type optionSelectModel struct {
+	message  string           // message to display
+	cursor   int              // position of the cursor
+	choices  []string         // items on the list
+	selected map[int]struct{} // which items are selected
+}
+
+// selectOptions creates a prompt for selecting multiple options but is now private
+func selectOptions(options []string, message string) optionSelectModel {
+	return optionSelectModel{
+		message:  message,
+		choices:  options,
+		selected: make(map[int]struct{}),
+	}
+}
+
+func (m optionSelectModel) Init() tea.Cmd {
+	return nil // No initial command
+}
+
+func (m optionSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.Type {
+		case tea.KeyCtrlC, tea.KeyEsc: // Quit the program
+			return m, tea.Quit
+
+		case tea.KeyUp: // Navigate up
+			if m.cursor > 0 {
+				m.cursor--
+			}
+
+		case tea.KeyDown: // Navigate down
+			if m.cursor < len(m.choices)-1 {
+				m.cursor++
+			}
+
+		case tea.KeySpace: // Select an item
+			// Toggle selection
+			if _, ok := m.selected[m.cursor]; ok {
+				delete(m.selected, m.cursor) // Deselect
+			} else {
+				m.selected[m.cursor] = struct{}{} // Select
+			}
+
+		case tea.KeyEnter: // Confirm selection
+			return m, tea.Quit // Quit and process selections in main
+		}
+	}
+
+	return m, nil
+}
+
+func (m optionSelectModel) View() string {
+	var b strings.Builder
+	b.WriteString(fmt.Sprintf("%s\n", m.message))
+	b.WriteString("Use arrow keys to navigate, space to select, enter to confirm or skip, q to quit:\n\n")
+	for i, choice := range m.choices {
+		if m.cursor == i {
+			b.WriteString("> ")
+		} else {
+			b.WriteString("  ")
+		}
+		// Mark selected items
+		if _, ok := m.selected[i]; ok {
+			b.WriteString("[x] ")
+		} else {
+			b.WriteString("[ ] ")
+		}
+		b.WriteString(choice + "\n")
+	}
+	return b.String()
+}
+
+// RunSelectOptions remains public and is the interface for external usage.
+func RunSelectOptions(options []string, message string) ([]string, error) {
+	model := selectOptions(options, message)
+	p := tea.NewProgram(model)
+	finalModel, err := p.Run()
+	if err != nil {
+		return nil, err
+	}
+
+	final := finalModel.(optionSelectModel)
+	selectedChoices := make([]string, 0)
+	for i := range final.selected {
+		selectedChoices = append(selectedChoices, final.choices[i])
+	}
+	return selectedChoices, nil
+}
diff --git a/internal/prompt/text-input.go b/internal/prompt/text-input.go
new file mode 100644
index 00000000..16e89b22
--- /dev/null
+++ b/internal/prompt/text-input.go
@@ -0,0 +1,83 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019 Dapper Labs, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package prompt
+
+import (
+	"fmt"
+
+	"github.com/charmbracelet/bubbles/textinput"
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+// textInputModel is now private, only accessible within the 'prompt' package.
+type textInputModel struct {
+	textInput textinput.Model
+	err       error
+	customMsg string
+}
+
+// newTextInput is a private function that initializes a new text input model.
+func newTextInput(customMsg, placeholder string) textInputModel {
+	ti := textinput.New()
+	ti.Placeholder = placeholder
+	ti.Focus()
+	ti.CharLimit = 256
+	ti.Width = 30
+
+	return textInputModel{
+		textInput: ti,
+		customMsg: customMsg,
+	}
+}
+
+func (m textInputModel) Init() tea.Cmd {
+	return textinput.Blink
+}
+
+func (m textInputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.Type {
+		case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
+			return m, tea.Quit
+		}
+		var cmd tea.Cmd
+		m.textInput, cmd = m.textInput.Update(msg)
+		return m, cmd
+	}
+
+	return m, nil
+}
+
+func (m textInputModel) View() string {
+	return fmt.Sprintf("%s\n\n%s\n\n%s", m.customMsg, m.textInput.View(), "(Enter to submit, Esc to quit)")
+}
+
+// RunTextInput remains public. It's the entry point for external usage.
+func RunTextInput(customMsg, placeholder string) (string, error) {
+	model := newTextInput(customMsg, placeholder)
+	p := tea.NewProgram(model)
+
+	if finalModel, err := p.Run(); err != nil {
+		return "", err
+	} else {
+		final := finalModel.(textInputModel)
+		return final.textInput.Value(), nil
+	}
+}
diff --git a/internal/super/generate.go b/internal/super/generate.go
index 8324d5cb..e0d10ca3 100644
--- a/internal/super/generate.go
+++ b/internal/super/generate.go
@@ -23,13 +23,13 @@ import (
 	"embed"
 	"fmt"
 	"path/filepath"
-	"strings"
 	"text/template"
 
 	flowsdk "github.com/onflow/flow-go-sdk"
-
 	"github.com/onflow/flowkit/v2/config"
 
+	"github.com/onflow/flow-cli/internal/util"
+
 	"github.com/onflow/flowkit/v2"
 
 	"github.com/onflow/flowkit/v2/output"
@@ -95,188 +95,347 @@ func init() {
 	GenerateScriptCommand.AddToParent(GenerateCommand)
 }
 
+const (
+	DefaultCadenceDirectory = "cadence"
+	ContractType            = "contract"
+	TransactionType         = "transaction"
+	ScriptType              = "script"
+)
+
 func generateContract(
 	args []string,
 	_ command.GlobalFlags,
 	logger output.Logger,
-	flow flowkit.Services,
+	_ flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	return generateNew(args, "contract", logger, state)
+	generator := NewGenerator(DefaultCadenceDirectory, state, logger, false, true)
+	contract := Contract{Name: args[0], Account: args[1]}
+	err = generator.Create(TemplateMap{ContractType: []TemplateItem{contract}})
+	return nil, err
 }
 
 func generateTransaction(
 	args []string,
 	_ command.GlobalFlags,
 	logger output.Logger,
-	flow flowkit.Services,
+	_ flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	return generateNew(args, "transaction", logger, state)
+	generator := NewGenerator(DefaultCadenceDirectory, state, logger, false, true)
+	transaction := ScriptTemplate{Name: args[0]}
+	err = generator.Create(TemplateMap{TransactionType: []TemplateItem{transaction}})
+	return nil, err
 }
 
 func generateScript(
 	args []string,
 	_ command.GlobalFlags,
 	logger output.Logger,
-	flow flowkit.Services,
+	_ flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	return generateNew(args, "script", logger, state)
+	generator := NewGenerator(DefaultCadenceDirectory, state, logger, false, true)
+	script := ScriptTemplate{Name: args[0]}
+	err = generator.Create(TemplateMap{ScriptType: []TemplateItem{script}})
+	return nil, err
 }
 
-func addCDCExtension(name string) string {
-	if strings.HasSuffix(name, ".cdc") {
-		return name
-	}
-	return fmt.Sprintf("%s.cdc", name)
+// TemplateItem is an interface for different template types
+type TemplateItem interface {
+	GetName() string
+	GetTemplate() string
+	GetAccount() string
+	GetData() map[string]interface{}
 }
 
-func stripCDCExtension(name string) string {
-	return strings.TrimSuffix(name, filepath.Ext(name))
+// Contract contains properties for contracts
+type Contract struct {
+	Name     string
+	Template string
+	Account  string
+	Data     map[string]interface{}
 }
 
-// processTemplate reads a template file from the embedded filesystem and processes it with the provided data
-// If you don't need to provide data, pass nil
-func processTemplate(templatePath string, data map[string]interface{}) (string, error) {
-	templateData, err := templatesFS.ReadFile(templatePath)
-	if err != nil {
-		return "", fmt.Errorf("failed to read template file: %w", err)
+// GetName returns the name of the contract
+func (c Contract) GetName() string {
+	return c.Name
+}
+
+// GetTemplate returns the template of the contract
+func (c Contract) GetTemplate() string {
+	if c.Template == "" {
+		return "contract_init"
 	}
 
-	tmpl, err := template.New("template").Parse(string(templateData))
-	if err != nil {
-		return "", fmt.Errorf("failed to parse template: %w", err)
+	return c.Template
+}
+
+// GetAccount returns the account of the contract
+func (c Contract) GetAccount() string {
+	return c.Account
+}
+
+// GetData returns the data of the contract
+func (c Contract) GetData() map[string]interface{} {
+	return c.Data
+}
+
+// ScriptTemplate contains only a name property for scripts and transactions
+type ScriptTemplate struct {
+	Name     string
+	Template string
+	Data     map[string]interface{}
+}
+
+// GetName returns the name of the script or transaction
+func (o ScriptTemplate) GetName() string {
+	return o.Name
+}
+
+// GetTemplate returns an empty string for scripts and transactions
+func (o ScriptTemplate) GetTemplate() string {
+	if o.Template == "" {
+		return "script_init"
 	}
 
-	var executedTemplate bytes.Buffer
-	// Execute the template with the provided data or nil if no data is needed
-	if err = tmpl.Execute(&executedTemplate, data); err != nil {
-		return "", fmt.Errorf("failed to execute template: %w", err)
+	return o.Template
+}
+
+// GetAccount returns an empty string for scripts and transactions
+func (o ScriptTemplate) GetAccount() string {
+	return ""
+}
+
+// GetData returns the data of the script or transaction
+func (o ScriptTemplate) GetData() map[string]interface{} {
+	return o.Data
+}
+
+// TransactionTemplate contains only a name property for scripts and transactions
+type TransactionTemplate struct {
+	Name     string
+	Template string
+	Data     map[string]interface{}
+}
+
+// GetName returns the name of the script or transaction
+func (o TransactionTemplate) GetName() string {
+	return o.Name
+}
+
+// GetTemplate returns an empty string for scripts and transactions
+func (o TransactionTemplate) GetTemplate() string {
+	if o.Template == "" {
+		return "transaction_init"
 	}
 
-	return executedTemplate.String(), nil
+	return o.Template
 }
 
-func generateNew(
-	args []string,
-	templateType string,
-	logger output.Logger,
-	state *flowkit.State,
-) (result command.Result, err error) {
-	if len(args) < 1 {
-		return nil, fmt.Errorf("invalid number of arguments")
+// GetAccount returns an empty string for scripts and transactions
+func (o TransactionTemplate) GetAccount() string {
+	return ""
+}
+
+// GetData returns the data of the script or transaction
+func (o TransactionTemplate) GetData() map[string]interface{} {
+	return o.Data
+}
+
+// TemplateMap holds all templates with flexibility
+type TemplateMap map[string][]TemplateItem
+
+type Generator struct {
+	directory   string
+	state       *flowkit.State
+	logger      output.Logger
+	disableLogs bool
+	saveState   bool
+}
+
+func NewGenerator(directory string, state *flowkit.State, logger output.Logger, disableLogs, saveState bool) *Generator {
+	return &Generator{
+		directory:   directory,
+		state:       state,
+		logger:      logger,
+		disableLogs: disableLogs,
+		saveState:   saveState,
 	}
+}
 
-	name := stripCDCExtension(args[0])
-	filename := addCDCExtension(name)
+func (g *Generator) Create(typeNames TemplateMap) error {
+	for templateType, items := range typeNames {
+		for _, item := range items {
+			err := g.generate(templateType, item.GetTemplate(), item.GetName(), item.GetAccount(), item.GetData())
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (g *Generator) generate(templateType, templateName, name, account string, data map[string]interface{}) error {
+
+	name = util.StripCDCExtension(name)
+	filename := util.AddCDCExtension(name)
 
 	var fileToWrite string
 	var testFileToWrite string
+	var rootDir = DefaultCadenceDirectory
 	var basePath string
+	var testsBasePath = "tests"
+	var err error
 
-	if generateFlags.Directory != "" {
-		basePath = generateFlags.Directory
-	} else {
-		switch templateType {
-		case "contract":
-			basePath = "cadence/contracts"
-		case "script":
-			basePath = "cadence/scripts"
-		case "transaction":
-			basePath = "cadence/transactions"
-		default:
-			return nil, fmt.Errorf("invalid template type: %s", templateType)
-		}
+	if g.directory != "" {
+		rootDir = g.directory
 	}
 
+	templatePath := fmt.Sprintf("templates/%s.cdc.tmpl", templateName)
+
 	switch templateType {
-	case "contract":
-		nameData := map[string]interface{}{"Name": name}
-		fileToWrite, err = processTemplate("templates/contract_init.cdc.tmpl", nameData)
+	case ContractType:
+		basePath = "contracts"
+		fileData := map[string]interface{}{"Name": name}
+		for k, v := range data {
+			fileData[k] = v
+		}
+		fileToWrite, err = processTemplate(templatePath, fileData)
 		if err != nil {
-			return nil, fmt.Errorf("error generating contract template: %w", err)
+			return fmt.Errorf("error generating contract template: %w", err)
 		}
 
-		testFileToWrite, err = processTemplate("templates/contract_init_test.cdc.tmpl", nameData)
+		testFileToWrite, err = processTemplate("templates/contract_init_test.cdc.tmpl", fileData)
 		if err != nil {
-			return nil, fmt.Errorf("error generating contract test template: %w", err)
+			return fmt.Errorf("error generating contract test template: %w", err)
+		}
+	case ScriptType:
+		basePath = "scripts"
+		fileData := map[string]interface{}{}
+		for k, v := range data {
+			fileData[k] = v
 		}
-	case "script":
-		fileToWrite, err = processTemplate("templates/script_init.cdc.tmpl", nil)
+		fileToWrite, err = processTemplate(templatePath, fileData)
 		if err != nil {
-			return nil, fmt.Errorf("error generating script template: %w", err)
+			return fmt.Errorf("error generating script template: %w", err)
 		}
-	case "transaction":
-		fileToWrite, err = processTemplate("templates/transaction_init.cdc.tmpl", nil)
+	case TransactionType:
+		basePath = "transactions"
+		fileData := map[string]interface{}{}
+		for k, v := range data {
+			fileData[k] = v
+		}
+		fileToWrite, err = processTemplate(templatePath, fileData)
 		if err != nil {
-			return nil, fmt.Errorf("error generating transaction template: %w", err)
+			return fmt.Errorf("error generating transaction template: %w", err)
 		}
 	default:
-		return nil, fmt.Errorf("invalid template type: %s", templateType)
+		return fmt.Errorf("invalid template type: %s", templateType)
 	}
 
-	filenameWithBasePath := filepath.Join(basePath, filename)
+	directoryWithBasePath := filepath.Join(rootDir, basePath, account)
+	filenameWithBasePath := filepath.Join(rootDir, basePath, account, filename)
+	relativeFilenameWithBasePath := filepath.Join(basePath, account, filename)
 
 	// Check file existence
-	if _, err := state.ReaderWriter().ReadFile(filenameWithBasePath); err == nil {
-		return nil, fmt.Errorf("file already exists: %s", filenameWithBasePath)
+	if _, err := g.state.ReaderWriter().ReadFile(filenameWithBasePath); err == nil {
+		return fmt.Errorf("file already exists: %s", filenameWithBasePath)
 	}
 
 	// Ensure the directory exists
-	if err := state.ReaderWriter().MkdirAll(basePath, 0755); err != nil {
-		return nil, fmt.Errorf("error creating directories: %w", err)
+	if err := g.state.ReaderWriter().MkdirAll(directoryWithBasePath, 0755); err != nil {
+		return fmt.Errorf("error creating directories: %w", err)
 	}
 
 	// Write files
-	err = state.ReaderWriter().WriteFile(filenameWithBasePath, []byte(fileToWrite), 0644)
+	err = g.state.ReaderWriter().WriteFile(filenameWithBasePath, []byte(fileToWrite), 0644)
 	if err != nil {
-		return nil, fmt.Errorf("error writing file: %w", err)
+		return fmt.Errorf("error writing file: %w", err)
 	}
 
-	logger.Info(fmt.Sprintf("Generated new %s: %s at %s", templateType, name, filenameWithBasePath))
+	if !g.disableLogs {
+		g.logger.Info(fmt.Sprintf("Generated new %s: %s at %s", templateType, name, filenameWithBasePath))
+	}
 
-	if generateFlags.SkipTests != true && templateType == "contract" {
-		testsBasePath := "cadence/tests"
-		testFilenameWithBasePath := filepath.Join(testsBasePath, addCDCExtension(fmt.Sprintf("%s_test", name)))
+	if generateFlags.SkipTests != true && templateType == ContractType {
+		testDirectoryWithBasePath := filepath.Join(rootDir, testsBasePath)
+		testFilenameWithBasePath := filepath.Join(rootDir, testsBasePath, util.AddCDCExtension(fmt.Sprintf("%s_test", name)))
 
-		if _, err := state.ReaderWriter().ReadFile(testFilenameWithBasePath); err == nil {
-			return nil, fmt.Errorf("file already exists: %s", testFilenameWithBasePath)
+		if _, err := g.state.ReaderWriter().ReadFile(testFilenameWithBasePath); err == nil {
+			return fmt.Errorf("file already exists: %s", testFilenameWithBasePath)
 		}
 
-		if err := state.ReaderWriter().MkdirAll(testsBasePath, 0755); err != nil {
-			return nil, fmt.Errorf("error creating test directory: %w", err)
+		if err := g.state.ReaderWriter().MkdirAll(testDirectoryWithBasePath, 0755); err != nil {
+			return fmt.Errorf("error creating test directory: %w", err)
 		}
 
-		err = state.ReaderWriter().WriteFile(testFilenameWithBasePath, []byte(testFileToWrite), 0644)
+		err := g.state.ReaderWriter().WriteFile(testFilenameWithBasePath, []byte(testFileToWrite), 0644)
 		if err != nil {
-			return nil, fmt.Errorf("error writing test file: %w", err)
+			return fmt.Errorf("error writing test file: %w", err)
 		}
 
-		logger.Info(fmt.Sprintf("Generated new test file: %s at %s", name, testFilenameWithBasePath))
+		if !g.disableLogs {
+			g.logger.Info(fmt.Sprintf("Generated new test file: %s at %s", name, testFilenameWithBasePath))
+		}
 	}
 
-	if templateType == "contract" {
-		var aliases config.Aliases
-
-		if generateFlags.SkipTests != true {
-			aliases = config.Aliases{{
-				Network: config.TestingNetwork.Name,
-				Address: flowsdk.HexToAddress("0x0000000000000007"),
-			}}
+	if templateType == ContractType {
+		err := g.updateContractsState(name, relativeFilenameWithBasePath)
+		if err != nil {
+			return err
 		}
+	}
 
-		contract := config.Contract{
-			Name:     name,
-			Location: filenameWithBasePath,
-			Aliases:  aliases,
-		}
-		state.Contracts().AddOrUpdate(contract)
-		err = state.SaveDefault()
+	return nil
+}
+
+func (g *Generator) updateContractsState(name, location string) error {
+	var aliases config.Aliases
+
+	if generateFlags.SkipTests != true {
+		aliases = config.Aliases{{
+			Network: config.TestingNetwork.Name,
+			Address: flowsdk.HexToAddress("0x0000000000000007"),
+		}}
+	}
+
+	contract := config.Contract{
+		Name:     name,
+		Location: location,
+		Aliases:  aliases,
+	}
+
+	g.state.Contracts().AddOrUpdate(contract)
+
+	if g.saveState {
+		err := g.state.SaveDefault() // TODO: Support adding a target project directory
 		if err != nil {
-			return nil, fmt.Errorf("error saving to flow.json: %w", err)
+			return fmt.Errorf("error saving to flow.json: %w", err)
 		}
 	}
 
-	return nil, err
+	return nil
+}
+
+// processTemplate reads a template file from the embedded filesystem and processes it with the provided data
+// If you don't need to provide data, pass nil
+func processTemplate(templatePath string, data map[string]interface{}) (string, error) {
+	templateData, err := templatesFS.ReadFile(templatePath)
+	if err != nil {
+		return "", fmt.Errorf("failed to read template file: %w", err)
+	}
+
+	tmpl, err := template.New("template").Parse(string(templateData))
+	if err != nil {
+		return "", fmt.Errorf("failed to parse template: %w", err)
+	}
+
+	var executedTemplate bytes.Buffer
+	// Execute the template with the provided data or nil if no data is needed
+	if err = tmpl.Execute(&executedTemplate, data); err != nil {
+		return "", fmt.Errorf("failed to execute template: %w", err)
+	}
+
+	return executedTemplate.String(), nil
 }
diff --git a/internal/super/scaffolds.go b/internal/super/scaffolds.go
new file mode 100644
index 00000000..e53d1bfa
--- /dev/null
+++ b/internal/super/scaffolds.go
@@ -0,0 +1,188 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019 Dapper Labs, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package super
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/onflow/flow-cli/internal/prompt"
+
+	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/onflow/flowkit/v2/output"
+)
+
+const scaffoldListURL = "https://raw.githubusercontent.com/onflow/flow-cli/master/scaffolds.json"
+
+type scaffold struct {
+	Repo        string `json:"repo"`
+	Branch      string `json:"branch"`
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	Commit      string `json:"commit"`
+	Folder      string `json:"folder"`
+	Type        string `json:"type"`
+}
+
+func handleScaffold(
+	projectName string,
+	logger output.Logger,
+) (string, error) {
+	targetDir, err := getTargetDirectory(projectName)
+	if err != nil {
+		return "", err
+	}
+
+	selectedScaffold, err := selectScaffold(logger)
+	if err != nil {
+		return "", fmt.Errorf("error selecting scaffold %w", err)
+	}
+
+	logger.StartProgress(fmt.Sprintf("Creating your project %s", targetDir))
+	defer logger.StopProgress()
+
+	if selectedScaffold != nil {
+		err = cloneScaffold(targetDir, *selectedScaffold)
+		if err != nil {
+			return "", fmt.Errorf("failed creating scaffold %w", err)
+		}
+	}
+
+	return targetDir, nil
+}
+
+func selectScaffold(logger output.Logger) (*scaffold, error) {
+	scaffolds, err := getScaffolds()
+	if err != nil {
+		return nil, err
+	}
+
+	// default to first scaffold - basic scaffold
+	pickedScaffold := scaffolds[0]
+
+	if setupFlags.ScaffoldID != 0 {
+		if setupFlags.ScaffoldID > len(scaffolds) {
+			return nil, fmt.Errorf("scaffold with id %d does not exist", setupFlags.ScaffoldID)
+		}
+		pickedScaffold = scaffolds[setupFlags.ScaffoldID-1]
+	}
+
+	if setupFlags.Scaffold {
+		scaffoldItems := make([]prompt.ScaffoldItem, 0)
+		for i, s := range scaffolds {
+			scaffoldItems = append(
+				scaffoldItems,
+				prompt.ScaffoldItem{
+					Index:    i,
+					Title:    fmt.Sprintf("%s - %s", output.Bold(s.Name), s.Description),
+					Category: s.Type,
+				},
+			)
+		}
+
+		selected := prompt.ScaffoldPrompt(logger, scaffoldItems)
+		pickedScaffold = scaffolds[selected]
+	}
+
+	return &pickedScaffold, nil
+}
+
+func getScaffolds() ([]scaffold, error) {
+	httpClient := http.Client{
+		Timeout: time.Second * 5,
+	}
+
+	req, err := http.NewRequest(http.MethodGet, scaffoldListURL, nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed creating request for scaffold list: %w", err)
+	}
+
+	res, err := httpClient.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed requesting scaffold list: %w", err)
+	}
+	if res.Body != nil {
+		defer res.Body.Close()
+	}
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed reading scaffold list response: %w", err)
+	}
+
+	var all []scaffold
+	err = json.Unmarshal(body, &all)
+	if err != nil {
+		return nil, fmt.Errorf("failed parsing scaffold list response: %w", err)
+	}
+
+	valid := make([]scaffold, 0)
+	for _, s := range all {
+		if s.Repo != "" && s.Description != "" && s.Name != "" && s.Commit != "" {
+			valid = append(valid, s)
+		}
+	}
+
+	return valid, nil
+}
+
+func cloneScaffold(targetDir string, conf scaffold) error {
+	repo, err := git.PlainClone(targetDir, false, &git.CloneOptions{
+		URL: conf.Repo,
+	})
+	if err != nil {
+		return fmt.Errorf("could not download the scaffold: %w", err)
+	}
+
+	worktree, _ := repo.Worktree()
+	err = worktree.Checkout(&git.CheckoutOptions{
+		Hash:  plumbing.NewHash(conf.Commit),
+		Force: true,
+	})
+	if err != nil {
+		return fmt.Errorf("could not find the scaffold version")
+	}
+
+	// if we defined a folder remove everything else
+	if conf.Folder != "" {
+		err = os.Rename(
+			filepath.Join(targetDir, conf.Folder),
+			filepath.Join(targetDir, "../scaffold-temp"),
+		)
+		if err != nil {
+			return err
+		}
+
+		if err = os.RemoveAll(targetDir); err != nil {
+			return err
+		}
+
+		if err = os.Rename(filepath.Join(targetDir, "../scaffold-temp"), targetDir); err != nil {
+			return err
+		}
+	}
+
+	return os.RemoveAll(filepath.Join(targetDir, ".git"))
+}
diff --git a/internal/super/setup.go b/internal/super/setup.go
index f41a630b..ea691036 100644
--- a/internal/super/setup.go
+++ b/internal/super/setup.go
@@ -20,213 +20,309 @@ package super
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
 	"io"
-	"net/http"
 	"os"
 	"path/filepath"
-	"time"
 
-	"github.com/go-git/go-git/v5"
-	"github.com/go-git/go-git/v5/plumbing"
+	flowsdk "github.com/onflow/flow-go-sdk"
+	"github.com/onflow/flow-go/fvm/systemcontracts"
+	flowGo "github.com/onflow/flow-go/model/flow"
+	flowkitConfig "github.com/onflow/flowkit/v2/config"
+	"golang.org/x/exp/slices"
+
+	"github.com/onflow/flow-cli/internal/dependencymanager"
+	"github.com/onflow/flow-cli/internal/util"
+
+	"github.com/spf13/afero"
+
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/spf13/cobra"
 
 	"github.com/onflow/flowkit/v2"
 	"github.com/onflow/flowkit/v2/output"
 
+	"github.com/onflow/flow-cli/internal/config"
+
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsSetup struct {
+	ConfigOnly bool `default:"false" flag:"config-only" info:"Only create a flow.json default config"`
 	Scaffold   bool `default:"" flag:"scaffold" info:"Interactively select a provided scaffold for project creation"`
 	ScaffoldID int  `default:"" flag:"scaffold-id" info:"Use provided scaffold ID for project creation"`
 }
 
 var setupFlags = flagsSetup{}
 
+// TODO: Add --config-only flag
 var SetupCommand = &command.Command{
 	Cmd: &cobra.Command{
-		Use:     "setup <project name>",
+		Use:     "init <project name>",
 		Short:   "Start a new Flow project",
 		Example: "flow setup my-project",
-		Args:    cobra.ExactArgs(1),
+		Args:    cobra.MaximumNArgs(1),
 		GroupID: "super",
 	},
 	Flags: &setupFlags,
 	Run:   create,
 }
 
-const scaffoldListURL = "https://raw.githubusercontent.com/onflow/flow-cli/master/scaffolds.json"
-
-type scaffold struct {
-	Repo        string `json:"repo"`
-	Branch      string `json:"branch"`
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Commit      string `json:"commit"`
-	Folder      string `json:"folder"`
-	Type        string `json:"type"`
-}
-
 func create(
 	args []string,
 	_ command.GlobalFlags,
 	logger output.Logger,
-	_ flowkit.ReaderWriter,
+	readerWriter flowkit.ReaderWriter,
 	_ flowkit.Services,
 ) (command.Result, error) {
-	targetDir, err := getTargetDirectory(args[0])
-	if err != nil {
-		return nil, err
-	}
+	var targetDir string
+	var err error
 
-	scaffolds, err := getScaffolds()
-	if err != nil {
-		return nil, err
-	}
+	if setupFlags.Scaffold || setupFlags.ScaffoldID != 0 {
+		// Error if no project name is given
+		if len(args) < 1 || args[0] == "" {
+			return nil, fmt.Errorf("no project name provided")
+		}
+
+		targetDir, err = handleScaffold(args[0], logger)
+		if err != nil {
+			return nil, err
+		}
+	} else if setupFlags.ConfigOnly {
+		if len(args) > 0 {
+			return nil, fmt.Errorf("project name not required when using --config-only flag")
+		}
 
-	// default to first scaffold - basic scaffold
-	pickedScaffold := scaffolds[0]
+		err = createConfigOnly("", readerWriter)
+		if err != nil {
+			return nil, err
+		}
+
+		logger.Info(util.MessageWithEmojiPrefix("🎉", "Configuration created successfully!"))
 
-	if setupFlags.ScaffoldID != 0 {
-		if setupFlags.ScaffoldID > len(scaffolds) {
-			return nil, fmt.Errorf("scaffold with id %d does not exist", setupFlags.ScaffoldID)
+		return nil, nil
+	} else {
+		targetDir, err = startInteractiveSetup(args, logger)
+		if err != nil {
+			return nil, err
 		}
-		pickedScaffold = scaffolds[setupFlags.ScaffoldID-1]
 	}
 
-	if setupFlags.Scaffold {
-		scaffoldItems := make([]util.ScaffoldItem, 0)
-		for i, s := range scaffolds {
-			scaffoldItems = append(
-				scaffoldItems,
-				util.ScaffoldItem{
-					Index:    i,
-					Title:    fmt.Sprintf("%s - %s", output.Bold(s.Name), s.Description),
-					Category: s.Type,
-				},
-			)
-		}
+	return &setupResult{targetDir: targetDir}, nil
+}
 
-		selected := util.ScaffoldPrompt(logger, scaffoldItems)
-		pickedScaffold = scaffolds[selected]
+func createConfigOnly(targetDir string, readerWriter flowkit.ReaderWriter) error {
+	params := config.InitConfigParameters{
+		ServiceKeySigAlgo:  "ECDSA_P256",
+		ServiceKeyHashAlgo: "SHA3_256",
+		Reset:              false,
+		Global:             false,
+		TargetDirectory:    targetDir,
+	}
+	state, err := config.InitializeConfiguration(params, readerWriter)
+	if err != nil {
+		return err
 	}
 
-	logger.StartProgress(fmt.Sprintf("Creating your project %s", targetDir))
-	defer logger.StopProgress()
-	err = cloneScaffold(targetDir, pickedScaffold)
+	err = state.SaveDefault()
 	if err != nil {
-		return nil, fmt.Errorf("failed creating scaffold %w", err)
+		return err
 	}
 
-	return &setupResult{targetDir: targetDir}, nil
+	return nil
 }
 
-func getTargetDirectory(directory string) (string, error) {
-	pwd, err := os.Getwd()
-	if err != nil {
-		return "", err
+func startInteractiveSetup(
+	args []string,
+	logger output.Logger,
+) (string, error) {
+	var targetDir string
+	var err error
+
+	rw := afero.Afero{
+		Fs: afero.NewOsFs(),
 	}
 
-	target := filepath.Join(pwd, directory)
-	info, err := os.Stat(target)
-	if !os.IsNotExist(err) {
-		if !info.IsDir() {
-			return "", fmt.Errorf("%s is a file", target)
+	// Ask for project name if not given
+	if len(args) < 1 {
+		userInput, err := prompt.RunTextInput("Enter the name of your project", "Type your project name here...")
+		if err != nil {
+			return "", fmt.Errorf("error running project name: %v", err)
 		}
 
-		file, err := os.Open(target)
+		targetDir, err = getTargetDirectory(userInput)
 		if err != nil {
 			return "", err
 		}
-		defer file.Close()
-
-		_, err = file.Readdirnames(1)
-		if err != io.EOF {
-			return "", fmt.Errorf("directory is not empty: %s", target)
+	} else {
+		targetDir, err = getTargetDirectory(args[0])
+		if err != nil {
+			return "", err
 		}
 	}
-	return target, nil
-}
 
-func getScaffolds() ([]scaffold, error) {
-	httpClient := http.Client{
-		Timeout: time.Second * 5,
+	// Create a temp directory which will later be moved to the target directory if successful
+	tempDir, err := os.MkdirTemp("", "flow-cli-*")
+	if err != nil {
+		return "", fmt.Errorf("failed to create temp directory: %w", err)
 	}
 
-	req, err := http.NewRequest(http.MethodGet, scaffoldListURL, nil)
+	defer func() {
+		if err := os.RemoveAll(tempDir); err != nil {
+			logger.Error(fmt.Sprintf("Failed to remove %s: %v", tempDir, err))
+		}
+	}()
+
+	params := config.InitConfigParameters{
+		ServiceKeySigAlgo:  "ECDSA_P256",
+		ServiceKeyHashAlgo: "SHA3_256",
+		Reset:              false,
+		Global:             false,
+		TargetDirectory:    tempDir,
+	}
+	state, err := config.InitializeConfiguration(params, rw)
 	if err != nil {
-		return nil, fmt.Errorf("failed creating request for scaffold list: %w", err)
+		return "", fmt.Errorf("failed to initialize configuration: %w", err)
 	}
 
-	res, err := httpClient.Do(req)
+	// Generate standard cadence files
+	// cadence/contracts/DefaultContract.cdc
+	// cadence/scripts/DefaultScript.cdc
+	// cadence/transactions/DefaultTransaction.cdc
+	// cadence/tests/DefaultContract_test.cdc
+
+	directoryPath := filepath.Join(tempDir, "cadence")
+
+	templates := TemplateMap{
+		"contract": []TemplateItem{
+			Contract{
+				Name:     "Counter",
+				Template: "contract_counter",
+				Account:  "",
+			},
+		},
+		"script": []TemplateItem{
+			ScriptTemplate{
+				Name:     "GetCounter",
+				Template: "script_counter",
+				Data:     map[string]interface{}{"ContractName": "Counter"},
+			},
+		},
+		"transaction": []TemplateItem{
+			TransactionTemplate{
+				Name:     "IncrementCounter",
+				Template: "transaction_counter",
+				Data:     map[string]interface{}{"ContractName": "Counter"},
+			},
+		},
+	}
+
+	generator := NewGenerator(directoryPath, state, logger, true, false)
+	err = generator.Create(templates)
 	if err != nil {
-		return nil, fmt.Errorf("failed requesting scaffold list: %w", err)
+		return "", err
 	}
-	if res.Body != nil {
-		defer res.Body.Close()
+
+	msg := "Would you like to install any core contracts and their dependencies?"
+	if prompt.GenericBoolPrompt(msg) {
+		err := installCoreContracts(logger, state, tempDir)
+		if err != nil {
+			return "", err
+		}
+	}
+
+	err = state.Save(filepath.Join(tempDir, "flow.json"))
+	if err != nil {
+		return "", err
 	}
 
-	body, err := io.ReadAll(res.Body)
+	// Move the temp directory to the target directory
+	err = os.Rename(tempDir, targetDir)
 	if err != nil {
-		return nil, fmt.Errorf("failed reading scaffold list response: %w", err)
+		return "", fmt.Errorf("failed to move temp directory to target directory: %w", err)
+	}
+
+	return targetDir, nil
+}
+
+func installCoreContracts(logger output.Logger, state *flowkit.State, tempDir string) error {
+	// Prompt to ask which core contracts should be installed
+	sc := systemcontracts.SystemContractsForChain(flowGo.Mainnet)
+	promptMessage := "Select any core contracts you would like to install or skip to continue."
+
+	contractNames := make([]string, 0)
+
+	for _, contract := range sc.All() {
+		contractNames = append(contractNames, contract.Name)
 	}
 
-	var all []scaffold
-	err = json.Unmarshal(body, &all)
+	selectedContractNames, err := prompt.RunSelectOptions(contractNames, promptMessage)
 	if err != nil {
-		return nil, fmt.Errorf("failed parsing scaffold list response: %w", err)
+		return fmt.Errorf("error running dependency selection: %v\n", err)
 	}
 
-	valid := make([]scaffold, 0)
-	for _, s := range all {
-		if s.Repo != "" && s.Description != "" && s.Name != "" && s.Commit != "" {
-			valid = append(valid, s)
+	var dependencies []flowkitConfig.Dependency
+
+	// Loop standard contracts and add them to the dependencies if selected
+	for _, contract := range sc.All() {
+		if slices.Contains(selectedContractNames, contract.Name) {
+			dependencies = append(dependencies, flowkitConfig.Dependency{
+				Name: contract.Name,
+				Source: flowkitConfig.Source{
+					NetworkName:  flowkitConfig.MainnetNetwork.Name,
+					Address:      flowsdk.HexToAddress(contract.Address.String()),
+					ContractName: contract.Name,
+				},
+			})
 		}
 	}
 
-	return valid, nil
-}
+	logger.Info("")
+	logger.Info(util.MessageWithEmojiPrefix("🔄", "Installing selected core contracts and dependencies..."))
 
-func cloneScaffold(targetDir string, conf scaffold) error {
-	repo, err := git.PlainClone(targetDir, false, &git.CloneOptions{
-		URL: conf.Repo,
-	})
+	// Add the selected core contracts as dependencies
+	installer, err := dependencymanager.NewDependencyInstaller(logger, state, false, tempDir, dependencymanager.Flags{})
 	if err != nil {
-		return fmt.Errorf("could not download the scaffold: %w", err)
+		return err
+	}
+
+	if err := installer.AddMany(dependencies); err != nil {
+		return err
 	}
 
-	worktree, _ := repo.Worktree()
-	err = worktree.Checkout(&git.CheckoutOptions{
-		Hash:  plumbing.NewHash(conf.Commit),
-		Force: true,
-	})
+	return nil
+}
+
+// getTargetDirectory checks if the specified directory path is suitable for use.
+// It verifies that the path points to an existing, empty directory.
+// If the directory does not exist, the function returns the path without error,
+// indicating that the path is available for use (assuming creation is handled elsewhere).
+func getTargetDirectory(directory string) (string, error) {
+	pwd, err := os.Getwd()
 	if err != nil {
-		return fmt.Errorf("could not find the scaffold version")
+		return "", err
 	}
 
-	// if we defined a folder remove everything else
-	if conf.Folder != "" {
-		err = os.Rename(
-			filepath.Join(targetDir, conf.Folder),
-			filepath.Join(targetDir, "../scaffold-temp"),
-		)
-		if err != nil {
-			return err
+	target := filepath.Join(pwd, directory)
+	info, err := os.Stat(target)
+	if !os.IsNotExist(err) {
+		if !info.IsDir() {
+			return "", fmt.Errorf("%s is a file", target)
 		}
 
-		if err = os.RemoveAll(targetDir); err != nil {
-			return err
+		file, err := os.Open(target)
+		if err != nil {
+			return "", err
 		}
+		defer file.Close()
 
-		if err = os.Rename(filepath.Join(targetDir, "../scaffold-temp"), targetDir); err != nil {
-			return err
+		_, err = file.Readdirnames(1)
+		if err != io.EOF {
+			return "", fmt.Errorf("directory is not empty: %s", target)
 		}
 	}
-
-	return os.RemoveAll(filepath.Join(targetDir, ".git"))
+	return target, nil
 }
 
 type setupResult struct {
@@ -242,7 +338,8 @@ func (s *setupResult) String() string {
 	out.WriteString("Start development by following these steps:\n")
 	out.WriteString(fmt.Sprintf("1. '%s' to change to your new project,\n", output.Bold(fmt.Sprintf("cd %s", relDir))))
 	out.WriteString(fmt.Sprintf("2. '%s' or run Flowser to start the emulator,\n", output.Bold("flow emulator")))
-	out.WriteString(fmt.Sprintf("3. '%s' to start developing.\n\n", output.Bold("flow dev")))
+	out.WriteString(fmt.Sprintf("3. '%s' to start developing.\n", output.Bold("flow dev")))
+	out.WriteString(fmt.Sprintf("4. '%s' to test your project.\n\n", output.Bold("flow test")))
 	out.WriteString(fmt.Sprintf("You should also read README.md to learn more about the development process!\n"))
 
 	return out.String()
diff --git a/internal/super/templates/contract_counter.cdc.tmpl b/internal/super/templates/contract_counter.cdc.tmpl
new file mode 100644
index 00000000..d0a3fc78
--- /dev/null
+++ b/internal/super/templates/contract_counter.cdc.tmpl
@@ -0,0 +1,31 @@
+access(all) contract {{ .Name }} {
+
+    access(all) var count: Int
+
+    // Event to be emitted when the counter is incremented
+    access(all) event CounterIncremented(newCount: Int)
+
+    // Event to be emitted when the counter is decremented
+    access(all) event CounterDecremented(newCount: Int)
+
+    init() {
+        self.count = 0
+    }
+
+    // Public function to increment the counter
+    access(all) fun increment() {
+        self.count = self.count + 1
+        emit CounterIncremented(newCount: self.count)
+    }
+
+    // Public function to decrement the counter
+    access(all) fun decrement() {
+        self.count = self.count - 1
+        emit CounterDecremented(newCount: self.count)
+    }
+
+    // Public function to get the current count
+    view access(all) fun getCount(): Int {
+        return self.count
+    }
+}
\ No newline at end of file
diff --git a/internal/super/templates/script_counter.cdc.tmpl b/internal/super/templates/script_counter.cdc.tmpl
new file mode 100644
index 00000000..c334a2aa
--- /dev/null
+++ b/internal/super/templates/script_counter.cdc.tmpl
@@ -0,0 +1,6 @@
+import "{{ .ContractName }}"
+
+access(all)
+fun main(): String {
+  return {{ .ContractName }}.greeting
+}
\ No newline at end of file
diff --git a/internal/super/templates/transaction_counter.cdc.tmpl b/internal/super/templates/transaction_counter.cdc.tmpl
new file mode 100644
index 00000000..ee2e10a4
--- /dev/null
+++ b/internal/super/templates/transaction_counter.cdc.tmpl
@@ -0,0 +1,16 @@
+import "{{ .ContractName }}"
+
+transaction {
+    prepare(signer: auth(Account) &Account) {
+        // Borrow a reference to the {{ .Name }} contract's public capability
+        let counterRef = signer.borrow<&{{ .ContractName }}>(from: /storage/counter)
+            ?? panic("Could not borrow reference to the counter")
+
+        // Call the increment function on the Counter contract
+        counterRef.increment()
+    }
+
+    execute {
+        log("Counter incremented successfully")
+    }
+}
\ No newline at end of file
diff --git a/internal/tools/flowser.go b/internal/tools/flowser.go
index 8fde1391..8ea5aebb 100644
--- a/internal/tools/flowser.go
+++ b/internal/tools/flowser.go
@@ -23,6 +23,8 @@ import (
 	"os"
 	"runtime"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflowser/flowser/v3/pkg/flowser"
 	"github.com/spf13/cobra"
 
@@ -32,7 +34,6 @@ import (
 
 	"github.com/onflow/flow-cli/internal/command"
 	"github.com/onflow/flow-cli/internal/settings"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsFlowser struct{}
@@ -94,21 +95,21 @@ func runFlowser(
 
 func installFlowser(flowser *flowser.App, installPath string) (string, error) {
 	fmt.Println("It looks like Flowser is not yet installed on your system.")
-	installChoice := util.InstallPrompt()
-	if installChoice == util.CancelInstall {
+	installChoice := prompt.InstallPrompt()
+	if installChoice == prompt.CancelInstall {
 		return "", fmt.Errorf("user denied install")
 	}
 
 	// if user says it already installed it we only ask for path and return it
-	if installChoice == util.AlreadyInstalled {
-		installPath = util.InstallPathPrompt(installPath)
+	if installChoice == prompt.AlreadyInstalled {
+		installPath = prompt.InstallPathPrompt(installPath)
 		_ = settings.SetFlowserPath(installPath)
 		return installPath, nil
 	}
 
 	// MacOS apps must always be installed inside Application folder
 	if runtime.GOOS != settings.Darwin {
-		installPath = util.InstallPathPrompt(installPath)
+		installPath = prompt.InstallPathPrompt(installPath)
 		_ = settings.SetFlowserPath(installPath)
 	}
 
diff --git a/internal/transactions/build.go b/internal/transactions/build.go
index cec2ad99..c2cabc6c 100644
--- a/internal/transactions/build.go
+++ b/internal/transactions/build.go
@@ -22,6 +22,8 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/cadence"
 	flowsdk "github.com/onflow/flow-go-sdk"
 	"github.com/spf13/cobra"
@@ -32,7 +34,6 @@ import (
 	"github.com/onflow/flowkit/v2/transactions"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsBuild struct {
@@ -119,7 +120,7 @@ func build(
 		return nil, err
 	}
 
-	if !globalFlags.Yes && !util.ApproveTransactionForBuildingPrompt(tx.FlowTransaction()) {
+	if !globalFlags.Yes && !prompt.ApproveTransactionForBuildingPrompt(tx.FlowTransaction()) {
 		return nil, fmt.Errorf("transaction was not approved")
 	}
 
diff --git a/internal/transactions/send-signed.go b/internal/transactions/send-signed.go
index 95f4c037..584bcd54 100644
--- a/internal/transactions/send-signed.go
+++ b/internal/transactions/send-signed.go
@@ -22,6 +22,8 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flowkit/v2/transactions"
 
 	"github.com/spf13/cobra"
@@ -30,7 +32,6 @@ import (
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsSendSigned struct {
@@ -70,7 +71,7 @@ func sendSigned(
 		return nil, err
 	}
 
-	if !globalFlags.Yes && !util.ApproveTransactionForSendingPrompt(tx.FlowTransaction()) {
+	if !globalFlags.Yes && !prompt.ApproveTransactionForSendingPrompt(tx.FlowTransaction()) {
 		return nil, fmt.Errorf("transaction was not approved for sending")
 	}
 
diff --git a/internal/transactions/sign.go b/internal/transactions/sign.go
index fea67ea7..32b33444 100644
--- a/internal/transactions/sign.go
+++ b/internal/transactions/sign.go
@@ -27,6 +27,8 @@ import (
 	"net/http"
 	"sort"
 
+	"github.com/onflow/flow-cli/internal/prompt"
+
 	"github.com/onflow/flowkit/v2/transactions"
 
 	"github.com/onflow/flowkit/v2/accounts"
@@ -38,7 +40,6 @@ import (
 	"github.com/onflow/flowkit/v2/output"
 
 	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/util"
 )
 
 type flagsSign struct {
@@ -115,7 +116,7 @@ func sign(
 	})
 
 	for _, signer := range signers {
-		if !globalFlags.Yes && !util.ApproveTransactionForSigningPrompt(tx.FlowTransaction()) {
+		if !globalFlags.Yes && !prompt.ApproveTransactionForSigningPrompt(tx.FlowTransaction()) {
 			return nil, fmt.Errorf("transaction was not approved for signing")
 		}
 
diff --git a/internal/quick/init.go b/internal/util/files.go
similarity index 51%
rename from internal/quick/init.go
rename to internal/util/files.go
index 4f9c3df5..ce011bc1 100644
--- a/internal/quick/init.go
+++ b/internal/util/files.go
@@ -16,24 +16,21 @@
  * limitations under the License.
  */
 
-package quick
+package util
 
 import (
-	"github.com/spf13/cobra"
-
-	"github.com/onflow/flow-cli/internal/command"
-	"github.com/onflow/flow-cli/internal/config"
+	"fmt"
+	"path/filepath"
+	"strings"
 )
 
-// TODO(sideninja) workaround - init needed to be copied in order to work else there is flag duplicate error
+func AddCDCExtension(name string) string {
+	if strings.HasSuffix(name, ".cdc") {
+		return name
+	}
+	return fmt.Sprintf("%s.cdc", name)
+}
 
-var InitCommand = &command.Command{
-	Cmd: &cobra.Command{
-		Use:     "init",
-		Short:   "Initialize a new configuration",
-		Example: "flow project init",
-		GroupID: "project",
-	},
-	Flags: &config.InitFlag,
-	Run:   config.Initialise, // TODO(sideninja) workaround - init needed to be copied in order to work else there is flag duplicate error
+func StripCDCExtension(name string) string {
+	return strings.TrimSuffix(name, filepath.Ext(name))
 }
diff --git a/internal/version/version.go b/internal/version/version.go
index cf93d761..858f4baf 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -38,7 +38,6 @@ func init() {
 
 type versionCmd struct {
 	Version      string
-	Commit       string
 	Dependencies []debug.Module
 }
 
@@ -48,7 +47,6 @@ func (c versionCmd) Print(format string) error {
 	case command.FormatInline, command.FormatText:
 		var txtBuilder strings.Builder
 		txtBuilder.WriteString(fmt.Sprintf("Version: %s\n", c.Version))
-		txtBuilder.WriteString(fmt.Sprintf("Commit: %s\n", c.Commit))
 
 		if verboseFlag {
 			txtBuilder.WriteString("\nFlow Package Dependencies \n")
@@ -80,14 +78,12 @@ func (c versionCmd) Print(format string) error {
 func (c *versionCmd) MarshalJSON() ([]byte, error) {
 	js := struct {
 		Version      string `json:"version"`
-		Commit       string `json:"commit"`
 		Dependencies []struct {
 			Package string `json:"package"`
 			Version string `json:"version"`
 		} `json:"dependencies"`
 	}{
 		Version: c.Version,
-		Commit:  c.Commit,
 	}
 
 	for _, dep := range c.Dependencies {
@@ -105,14 +101,12 @@ func (c *versionCmd) MarshalJSON() ([]byte, error) {
 
 var Cmd = &cobra.Command{
 	Use:   "version",
-	Short: "View version and commit information",
+	Short: "View version information",
 	RunE: func(cmd *cobra.Command, args []string) error {
 		semver := build.Semver()
-		commit := build.Commit()
 
 		v := &versionCmd{
 			Version: semver,
-			Commit:  commit,
 		}
 
 		bi, ok := debug.ReadBuildInfo()


Test patch:
diff --git a/internal/dependencymanager/dependencyinstaller_test.go b/internal/dependencymanager/dependencyinstaller_test.go
index d74084bd..f1a9e56a 100644
--- a/internal/dependencymanager/dependencyinstaller_test.go
+++ b/internal/dependencymanager/dependencyinstaller_test.go
@@ -77,6 +77,8 @@ func TestDependencyInstallerInstall(t *testing.T) {
 			},
 			Logger:          logger,
 			State:           state,
+			SaveState:       true,
+			TargetDir:       "",
 			SkipDeployments: true,
 			SkipAlias:       true,
 			dependencies:    make(map[string]config.Dependency),
@@ -123,13 +125,15 @@ func TestDependencyInstallerAdd(t *testing.T) {
 			},
 			Logger:          logger,
 			State:           state,
+			SaveState:       true,
+			TargetDir:       "",
 			SkipDeployments: true,
 			SkipAlias:       true,
 			dependencies:    make(map[string]config.Dependency),
 		}
 
 		sourceStr := fmt.Sprintf("emulator://%s.%s", serviceAddress.String(), tests.ContractHelloString.Name)
-		err := di.Add(sourceStr, "")
+		err := di.AddBySourceString(sourceStr, "")
 		assert.NoError(t, err, "Failed to install dependencies")
 
 		filePath := fmt.Sprintf("imports/%s/%s.cdc", serviceAddress.String(), tests.ContractHelloString.Name)
@@ -137,4 +141,115 @@ func TestDependencyInstallerAdd(t *testing.T) {
 		assert.NoError(t, err, "Failed to read generated file")
 		assert.NotNil(t, fileContent)
 	})
+
+	t.Run("Success", func(t *testing.T) {
+		gw := mocks.DefaultMockGateway()
+
+		gw.GetAccount.Run(func(args mock.Arguments) {
+			addr := args.Get(1).(flow.Address)
+			assert.Equal(t, addr.String(), serviceAcc.Address.String())
+			acc := tests.NewAccountWithAddress(addr.String())
+			acc.Contracts = map[string][]byte{
+				tests.ContractHelloString.Name: tests.ContractHelloString.Source,
+			}
+
+			gw.GetAccount.Return(acc, nil)
+		})
+
+		di := &DependencyInstaller{
+			Gateways: map[string]gateway.Gateway{
+				config.EmulatorNetwork.Name: gw.Mock,
+				config.TestnetNetwork.Name:  gw.Mock,
+				config.MainnetNetwork.Name:  gw.Mock,
+			},
+			Logger:          logger,
+			State:           state,
+			SaveState:       true,
+			TargetDir:       "",
+			SkipDeployments: true,
+			SkipAlias:       true,
+			dependencies:    make(map[string]config.Dependency),
+		}
+
+		dep := config.Dependency{
+			Name: tests.ContractHelloString.Name,
+			Source: config.Source{
+				NetworkName:  "emulator",
+				Address:      flow.HexToAddress(serviceAddress.String()),
+				ContractName: tests.ContractHelloString.Name,
+			},
+		}
+		err := di.Add(dep)
+		assert.NoError(t, err, "Failed to install dependencies")
+
+		filePath := fmt.Sprintf("imports/%s/%s.cdc", serviceAddress.String(), tests.ContractHelloString.Name)
+		fileContent, err := state.ReaderWriter().ReadFile(filePath)
+		assert.NoError(t, err, "Failed to read generated file")
+		assert.NotNil(t, fileContent)
+	})
+}
+
+func TestDependencyInstallerAddMany(t *testing.T) {
+	logger := output.NewStdoutLogger(output.NoneLog)
+	_, state, _ := util.TestMocks(t)
+
+	serviceAcc, _ := state.EmulatorServiceAccount()
+	serviceAddress := serviceAcc.Address.String()
+
+	dependencies := []config.Dependency{
+		{
+			Name: "ContractOne",
+			Source: config.Source{
+				NetworkName:  "emulator",
+				Address:      flow.HexToAddress(serviceAddress),
+				ContractName: "ContractOne",
+			},
+		},
+		{
+			Name: "ContractTwo",
+			Source: config.Source{
+				NetworkName:  "emulator",
+				Address:      flow.HexToAddress(serviceAddress),
+				ContractName: "ContractTwo",
+			},
+		},
+	}
+
+	t.Run("AddMultipleDependencies", func(t *testing.T) {
+		gw := mocks.DefaultMockGateway()
+		gw.GetAccount.Run(func(args mock.Arguments) {
+			addr := args.Get(1).(flow.Address)
+			assert.Equal(t, addr.String(), serviceAddress)
+			acc := tests.NewAccountWithAddress(addr.String())
+			acc.Contracts = map[string][]byte{
+				"ContractOne": []byte("access(all) contract ContractOne {}"),
+				"ContractTwo": []byte("access(all) contract ContractTwo {}"),
+			}
+			gw.GetAccount.Return(acc, nil)
+		})
+
+		di := &DependencyInstaller{
+			Gateways: map[string]gateway.Gateway{
+				config.EmulatorNetwork.Name: gw.Mock,
+				config.TestnetNetwork.Name:  gw.Mock,
+				config.MainnetNetwork.Name:  gw.Mock,
+			},
+			Logger:          logger,
+			State:           state,
+			SaveState:       true,
+			TargetDir:       "",
+			SkipDeployments: true,
+			SkipAlias:       true,
+			dependencies:    make(map[string]config.Dependency),
+		}
+
+		err := di.AddMany(dependencies)
+		assert.NoError(t, err, "Failed to add multiple dependencies")
+
+		for _, dep := range dependencies {
+			filePath := fmt.Sprintf("imports/%s/%s.cdc", dep.Source.Address.String(), dep.Name)
+			_, err := state.ReaderWriter().ReadFile(filePath)
+			assert.NoError(t, err, fmt.Sprintf("Failed to read generated file for %s", dep.Name))
+		}
+	})
 }
diff --git a/internal/super/generate_test.go b/internal/super/generate_test.go
index e0ea7ec9..e07c7cbe 100644
--- a/internal/super/generate_test.go
+++ b/internal/super/generate_test.go
@@ -34,8 +34,10 @@ func TestGenerateNewContract(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
+	generator := NewGenerator("cadence", state, logger, false, true)
+
 	// Test contract generation
-	_, err := generateNew([]string{"TestContract"}, "contract", logger, state)
+	err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	fileContent, err := state.ReaderWriter().ReadFile("cadence/contracts/TestContract.cdc")
@@ -70,24 +72,45 @@ access(all) fun testContract() {
 	assert.Equal(t, expectedTestContent, util.NormalizeLineEndings(string(testContent)))
 
 	// Test file already exists scenario
-	_, err = generateNew([]string{"TestContract"}, "contract", logger, state)
+	generatorTwo := NewGenerator("cadence", state, logger, false, true)
+	err = generatorTwo.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}})
 	assert.Error(t, err)
 	expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc"))
 	assert.Equal(t, expectedError, err.Error())
 }
 
+func TestGenerateContractWithAccount(t *testing.T) {
+	logger := output.NewStdoutLogger(output.NoneLog)
+	_, state, _ := util.TestMocks(t)
+
+	generator := NewGenerator("cadence", state, logger, false, true)
+
+	// Test contract generation
+	err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: "example-account"}}})
+	assert.NoError(t, err, "Failed to generate contract")
+
+	fileContent, err := state.ReaderWriter().ReadFile("cadence/contracts/example-account/TestContract.cdc")
+	assert.NoError(t, err, "Failed to read generated file")
+	assert.NotNil(t, fileContent)
+
+	testContent, err := state.ReaderWriter().ReadFile("cadence/tests/TestContract_test.cdc")
+	assert.NoError(t, err, "Failed to read generated file")
+	assert.NotNil(t, testContent)
+}
+
 func TestGenerateNewContractSkipTests(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
 	generateFlags.SkipTests = true
 
+	generator := NewGenerator("cadence", state, logger, false, true)
 	t.Cleanup(func() {
 		generateFlags.SkipTests = false
 	})
 
 	// Test contract generation
-	_, err := generateNew([]string{"TestContract"}, "contract", logger, state)
+	err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	fileContent, err := state.ReaderWriter().ReadFile("cadence/contracts/TestContract.cdc")
@@ -104,7 +127,8 @@ func TestGenerateNewContractWithCDCExtension(t *testing.T) {
 	_, state, _ := util.TestMocks(t)
 
 	// Test contract generation
-	_, err := generateNew([]string{"Tester.cdc"}, "contract", logger, state)
+	generator := NewGenerator("cadence", state, logger, false, true)
+	err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "Tester.cdc", Account: ""}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	fileContent, err := state.ReaderWriter().ReadFile("cadence/contracts/Tester.cdc")
@@ -121,7 +145,8 @@ func TestGenerateNewContractFileAlreadyExists(t *testing.T) {
 	_, state, _ := util.TestMocks(t)
 
 	// Test contract generation
-	_, err := generateNew([]string{"TestContract"}, "contract", logger, state)
+	generator := NewGenerator("cadence", state, logger, false, true)
+	err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	//// Check if the file exists in the correct directory
@@ -130,7 +155,8 @@ func TestGenerateNewContractFileAlreadyExists(t *testing.T) {
 	assert.NotNil(t, content)
 
 	// Test file already exists scenario
-	_, err = generateNew([]string{"TestContract"}, "contract", logger, state)
+	generatorTwo := NewGenerator("cadence", state, logger, false, true)
+	err = generatorTwo.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}})
 	assert.Error(t, err)
 	expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc"))
 	assert.Equal(t, expectedError, err.Error())
@@ -140,7 +166,8 @@ func TestGenerateNewContractWithFileExtension(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	_, err := generateNew([]string{"TestContract.cdc"}, "contract", logger, state)
+	generator := NewGenerator("cadence", state, logger, false, true)
+	err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract.cdc", Account: ""}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	// Check file exists
@@ -153,7 +180,8 @@ func TestGenerateNewScript(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	_, err := generateNew([]string{"TestScript"}, "script", logger, state)
+	generator := NewGenerator("cadence", state, logger, false, true)
+	err := generator.Create(TemplateMap{"script": []TemplateItem{ScriptTemplate{Name: "TestScript"}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	content, err := state.ReaderWriter().ReadFile("cadence/scripts/TestScript.cdc")
@@ -171,7 +199,8 @@ func TestGenerateNewTransaction(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	_, err := generateNew([]string{"TestTransaction"}, "transaction", logger, state)
+	generator := NewGenerator("cadence", state, logger, false, true)
+	err := generator.Create(TemplateMap{"transaction": []TemplateItem{TransactionTemplate{Name: "TestTransaction"}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	content, err := state.ReaderWriter().ReadFile("cadence/transactions/TestTransaction.cdc")
@@ -190,16 +219,18 @@ func TestGenerateNewWithDirFlag(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	// Set a custom directory
-	generateFlags.Directory = "customDir"
-
-	_, err := generateNew([]string{"TestContract"}, "contract", logger, state)
+	generator := NewGenerator("customDir", state, logger, false, true)
+	err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}})
 	assert.NoError(t, err, "Failed to generate contract")
 
-	content, err := state.ReaderWriter().ReadFile("customDir/TestContract.cdc")
+	content, err := state.ReaderWriter().ReadFile("customDir/contracts/TestContract.cdc")
 	assert.NoError(t, err, "Failed to read generated file")
 	assert.NotNil(t, content)
 
+	testContent, err := state.ReaderWriter().ReadFile("customDir/tests/TestContract_test.cdc")
+	assert.NoError(t, err, "Failed to read generated file")
+	assert.NotNil(t, testContent)
+
 	expectedContent := `access(all)
 contract TestContract {
     init() {}