diff --git a/.ci/release_script.sh b/.ci/release_script.sh
deleted file mode 100644
index 5f3b6d389b2ecf2af9392324f94f9d81d6d0fa8c..0000000000000000000000000000000000000000
--- a/.ci/release_script.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-
-# stop script if any command fails
-set -e
-
-# Vérifiez si le token d'accès privé GitLab est défini
-if [ -z "$GITLAB_PRIVATE_TOKEN" ]; then
-    echo "Token d'accès privé GitLab non défini. Arrêt du script."
-    exit 1
-fi
-
-if [ -z "$CI_COMMIT_TAG" ]; then
-    echo "This script should only be run on a tag."
-    exit 1
-fi
-
-# Variables
-PROJECT_ID="604"
-RELEASE_NAME="v$CI_COMMIT_TAG"
-RELEASE_DESCRIPTION="Release v$CI_COMMIT_TAG is awesome !"
-ARTIFACT_PATH="target/release/gcli"
-GITLAB_API_URL="https://git.duniter.org/api/v4"
-
-# Création de la release
-echo "Création de la release $RELEASE_NAME..."
-curl --header "PRIVATE-TOKEN: $GITLAB_PRIVATE_TOKEN" \
-     --data "name=$RELEASE_NAME&tag_name=$CI_COMMIT_TAG&description=$RELEASE_DESCRIPTION" \
-     "$GITLAB_API_URL/projects/$PROJECT_ID/releases"
-
-# Construction de l'URL de l'artéfact
-ARTIFACT_URL="$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/$ARTIFACT_PATH?job=build"
-
-# Ajout de l'artéfact à la release
-echo "Ajout de l'artéfact à la release..."
-curl --header "PRIVATE-TOKEN: $GITLAB_PRIVATE_TOKEN" \
-     --data-urlencode "url=$ARTIFACT_URL" \
-     --data-urlencode "name=$(basename $ARTIFACT_PATH)" \
-     "$GITLAB_API_URL/projects/$PROJECT_ID/releases/$CI_COMMIT_TAG/assets/links"
-
-echo "Script terminé."
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 74553382c04235f0230ebe0231b0761488115d92..6bc60149ffd5d2bddaa4065a8635315c6476e98f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,6 @@
 # Official language image. Look for the different tagged releases at:
 # https://hub.docker.com/r/library/rust/tags/
+
 image: "rust:latest"
 
 stages:
@@ -19,22 +20,39 @@ build:
       - target/release/gcli
   # use cache to avoid re-downloading and re-building all dependencies
   cache:
-    - key:
-        files:
-          - Cargo.lock
-      paths:
-        - target/release
+    key:
+      files:
+        - Cargo.lock
+    paths:
+      - target/release
   # only build gcli when adding a tag
   only:
     - tags
 
 release:
   stage: release
-  image: rust
+  image: registry.gitlab.com/gitlab-org/release-cli:latest
   script:
-    - chmod +x .ci/release_script.sh
-    - .ci/release_script.sh
+    - echo "Creating a release..."
+    - apk update && apk add git
+    - git fetch --tags
+    - LAST_VERSION=$(git tag --sort=-v:refname | sed -n '2p')
+    - git log --pretty="format:- %s ([%h]($CI_PROJECT_URL/-/commit/%h)) " HEAD...$LAST_VERSION --reverse > release_description.txt
+  # Define release parameters
+  release:
+    # Release name and description using the tag name
+    name: "v$CI_COMMIT_TAG"
+    description: "Latest changes:\n$(cat release_description.txt)"
+    # Set the tag for the release
+    tag_name: "$CI_COMMIT_TAG"
+    # Attach the artifact to the release
+    assets:
+      links:
+        - name: "gcli v$CI_COMMIT_TAG for Linux"
+          url: "$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/target/release/gcli?job=build"
+  # Trigger release creation only for tagged commits
   only:
     - tags
+  # Not necessary but clearly shows the dependency
   dependencies:
     - build
diff --git a/Cargo.lock b/Cargo.lock
index 5cf8c391a12905c87cb6203914d594db29dab0ef..000e9efcdb7c840b72ff1e70dcaf1208a844215a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1173,6 +1173,31 @@ version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
 
+[[package]]
+name = "crossterm"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
+dependencies = [
+ "bitflags 1.3.2",
+ "crossterm_winapi",
+ "libc",
+ "mio",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "crunchy"
 version = "0.2.2"
@@ -1467,7 +1492,7 @@ dependencies = [
  "regex",
  "syn 2.0.48",
  "termcolor",
- "toml 0.8.8",
+ "toml 0.8.9",
  "walkdir",
 ]
 
@@ -1875,7 +1900,7 @@ dependencies = [
 
 [[package]]
 name = "gcli"
-version = "0.2.1"
+version = "0.2.2"
 dependencies = [
  "anyhow",
  "bs58",
@@ -1885,6 +1910,7 @@ dependencies = [
  "futures",
  "graphql_client",
  "hex",
+ "inquire",
  "log",
  "nacl",
  "parity-scale-codec",
@@ -2333,6 +2359,22 @@ dependencies = [
  "generic-array 0.14.7",
 ]
 
+[[package]]
+name = "inquire"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33e7c1ddeb15c9abcbfef6029d8e29f69b52b6d6c891031b88ed91b5065803b"
+dependencies = [
+ "bitflags 1.3.2",
+ "crossterm",
+ "dyn-clone",
+ "lazy_static",
+ "newline-converter",
+ "thiserror",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -2728,6 +2770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
 dependencies = [
  "libc",
+ "log",
  "wasi",
  "windows-sys 0.48.0",
 ]
@@ -2756,6 +2799,15 @@ dependencies = [
  "tempfile",
 ]
 
+[[package]]
+name = "newline-converter"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f"
+dependencies = [
+ "unicode-segmentation",
+]
+
 [[package]]
 name = "no-std-net"
 version = "0.6.0"
@@ -3150,7 +3202,7 @@ version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
 dependencies = [
- "toml_edit 0.21.0",
+ "toml_edit 0.21.1",
 ]
 
 [[package]]
@@ -4024,6 +4076,27 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "signal-hook"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
 [[package]]
 name = "signal-hook-registry"
 version = "1.4.1"
@@ -5031,14 +5104,14 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.8"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
+checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325"
 dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit 0.21.0",
+ "toml_edit 0.21.1",
 ]
 
 [[package]]
@@ -5074,9 +5147,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.21.0"
+version = "0.21.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
+checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
 dependencies = [
  "indexmap 2.2.1",
  "serde",
@@ -5267,6 +5340,18 @@ dependencies = [
  "tinyvec",
 ]
 
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.4"
diff --git a/Cargo.toml b/Cargo.toml
index ae6e0208ea64032eecf58baa920bad8758b5cdc9..19e223c2441933748e3eeb09581393a7bbc8715b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,14 +1,22 @@
 [package]
-authors = ["librelois <c@elo.tf>", "tuxmain <tuxmain@zettascript.org>", "h30x <hugo@trentesaux.fr>"]
+authors = [
+    "librelois <c@elo.tf>",
+    "tuxmain <tuxmain@zettascript.org>",
+    "h30x <hugo@trentesaux.fr>",
+]
 edition = "2021"
-license = "AGPL-3.0"
+license = "AGPL-3.0-only"
 name = "gcli"
 repository = "https://git.duniter.org/clients/rust/gcli-v2s"
-version = "0.2.1"
+version = "0.2.2"
 
 [dependencies]
 # subxt is main dependency
-subxt = { git = 'https://github.com/duniter/subxt', branch = 'subxt-v0.34.0-duniter-substrate-v1.6.0', default-features = false, features = ["substrate-compat", "native", "jsonrpsee"] }
+subxt = { git = 'https://github.com/duniter/subxt', branch = 'subxt-v0.34.0-duniter-substrate-v1.6.0', default-features = false, features = [
+    "substrate-compat",
+    "native",
+    "jsonrpsee",
+] }
 # substrate primitives dependencies
 sp-core = { git = "https://github.com/duniter/duniter-polkadot-sdk.git", branch = "duniter-substrate-v1.6.0" }
 sp-runtime = { git = "https://github.com/duniter/duniter-polkadot-sdk.git", branch = "duniter-substrate-v1.6.0" }
@@ -27,9 +35,10 @@ serde = { version = "^1.0", features = ["derive"] }
 serde_json = "^1.0.113"
 tokio = { version = "^1.35.1", features = ["macros"] }
 confy = "^0.5.1"
-scrypt = { version = "^0.11", default-features = false } # for old-style key generation
-nacl = { version = "^0.5.3" } # for old-style key generation
+scrypt = { version = "^0.11", default-features = false }         # for old-style key generation
+nacl = { version = "^0.5.3" }                                    # for old-style key generation
 bs58 = "^0.5.0"
+inquire = "0.6.2"
 
 # allows to build gcli for different runtimes and with different predefined networks 
 [features]
@@ -37,5 +46,3 @@ default = ["gdev"] # default runtime is "gdev", gdev network is available
 gdev = []
 gtest = []
 g1 = []
-
-
diff --git a/src/commands.rs b/src/commands.rs
index 3ce40227796a407e7cd83fe8e806a3363632e896..277764b2f115d52555166b96be2221fcd64a23fe 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -8,6 +8,7 @@ pub mod expire;
 pub mod identity;
 pub mod net_test;
 pub mod oneshot;
+pub mod publish;
 pub mod revocation;
 pub mod runtime;
 pub mod smith;
diff --git a/src/commands/publish.rs b/src/commands/publish.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cdae6ef940b1a7884dd0bccdc4e51ea34ac1e891
--- /dev/null
+++ b/src/commands/publish.rs
@@ -0,0 +1,55 @@
+// commands/publish.rs
+// This module handles the 'publish' command of the CLI.
+
+use crate::GcliError;
+use anyhow::anyhow;
+use inquire::Confirm;
+use std::process::Command;
+
+/// Executes the 'publish' operation.
+pub async fn handle_command() -> Result<(), GcliError> {
+	// Step 1: Get actual version of gcli
+	const VERSION: &str = env!("CARGO_PKG_VERSION");
+
+	// Step 2: Check if the git tag already exists
+	let tag_check_output = Command::new("git").args(["tag", "-l", VERSION]).output()?;
+
+	if !tag_check_output.stdout.is_empty() {
+		return Err(GcliError::Logic(format!("Tag {VERSION} already exists")));
+	}
+
+	// Display a confirmation prompt with the version number.
+	match Confirm::new(&format!(
+		"Are you sure you want to publish version {VERSION} ?"
+	))
+	.with_default(false)
+	.prompt()
+	{
+		Ok(true) => {
+			// User confirmed, proceed publishing
+			// Step 3: Create and push the git tag
+			Command::new("git")
+				.args(["tag", "-a", VERSION, "-m", &format!("Release v{VERSION}")])
+				.status()
+				.map_err(|e| anyhow!(e))?;
+
+			Command::new("git")
+				.args(["push", "origin", &format!("refs/tags/{VERSION}")])
+				.status()
+				.map_err(|e| anyhow!(e))?;
+			println!("Publication of version {VERSION} completed successfully.");
+			Ok(())
+		}
+		Ok(false) => {
+			// User did not confirm, cancel the operation
+			println!("Publication cancelled.");
+			Ok(())
+		}
+		Err(_) => {
+			// There was an error with the prompt, return an error
+			Err(GcliError::Input(
+				"Failed to display confirmation prompt".to_string(),
+			))
+		}
+	}
+}
diff --git a/src/main.rs b/src/main.rs
index eb0241866ce02b46fa833eaba50548f2ca3788ee..3eaa6df94ad8f53dcf50884882f0d2399dd7e507 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -100,6 +100,9 @@ pub enum Subcommand {
 	/// Cesium
 	#[clap(subcommand, hide = true)]
 	Cesium(commands::cesium::Subcommand),
+	/// Publish a new git tag with actual version
+	#[clap(hide = true)]
+	Publish,
 }
 
 /// main function
@@ -135,6 +138,7 @@ async fn main() -> Result<(), GcliError> {
 		Subcommand::Indexer(subcommand) => indexer::handle_command(data, subcommand).await,
 		Subcommand::Config(subcommand) => conf::handle_command(data, subcommand),
 		Subcommand::Cesium(subcommand) => commands::cesium::handle_command(data, subcommand).await,
+		Subcommand::Publish => commands::publish::handle_command().await,
 	};
 	if let Err(ref e) = result {
 		println!("{}", e)
diff --git a/src/utils.rs b/src/utils.rs
index c2b73022d8122adaaeed18175fedeed166375b70..91c0796334bba8cd4fc05a465f56bd8453f6326f 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -103,6 +103,8 @@ pub enum GcliError {
 	Input(String),
 	/// error coming from anyhow (to be removed)
 	Anyhow(anyhow::Error),
+	/// error coming from io
+	IoError(std::io::Error),
 }
 impl std::fmt::Display for GcliError {
 	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
@@ -132,3 +134,8 @@ impl From<confy::ConfyError> for GcliError {
 		GcliError::Anyhow(e.into())
 	}
 }
+impl From<std::io::Error> for GcliError {
+	fn from(error: std::io::Error) -> Self {
+		GcliError::IoError(error)
+	}
+}