@salefronts/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v22.12.0
package/bin/sf ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ const args = process.argv.slice(2);
8
+ const command = args[0];
9
+
10
+ if (!command) {
11
+ console.log('Usage: sf <command>');
12
+ process.exit(1);
13
+ }
14
+
15
+ const jsCommandPath = path.join(__dirname, '../commands', `${command}.js`);
16
+ const shCommandPath = path.join(__dirname, '../commands', `${command}.sh`);
17
+
18
+ if (fs.existsSync(jsCommandPath)) {
19
+ try {
20
+ const commandModule = require(jsCommandPath);
21
+ if (typeof commandModule === 'function') {
22
+ commandModule(args.slice(1));
23
+ } else {
24
+ console.error('Error: Command module is not a function.');
25
+ }
26
+ } catch (err) {
27
+ console.error('Error:', err.message);
28
+ process.exit(1);
29
+ }
30
+ } else if (fs.existsSync(shCommandPath)) {
31
+ const bash = spawn('bash', [shCommandPath, ...args.slice(1)], {
32
+ stdio: 'inherit',
33
+ });
34
+
35
+ bash.on('exit', (code) => {
36
+ process.exit(code);
37
+ });
38
+ } else {
39
+ console.error(`Unknown command: ${command}`);
40
+ process.exit(1);
41
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // Helper function to display the general help
7
+ function showHelp() {
8
+ console.log('Usage: sf <command>\n');
9
+ console.log('Available commands:');
10
+ console.log(' jwt Generate JWT tokens');
11
+ console.log(' keypair Generate public/private key pairs');
12
+ console.log(' passphrase Generate secure passphrases');
13
+ console.log(' secret Generate a random secret');
14
+ console.log(' ssh Generate SSH key');;
15
+ console.log(' uuidv4 Generate a UUID v4');
16
+ console.log(' uuidv7 Generate a UUID v7');
17
+ console.log(' seal Seal Kubernetes secrets');
18
+ console.log(' unseal Unseal Kubernetes secrets');
19
+ console.log(' tag Tagging version for the latest commit on main')
20
+ }
21
+
22
+ module.exports = async function () {
23
+ showHelp();
24
+ process.exit(0);
25
+ }
@@ -0,0 +1,39 @@
1
+ const readline = require('readline');
2
+ const jwt = require('jsonwebtoken');
3
+
4
+ function ask(question) {
5
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6
+ return new Promise((resolve) => rl.question(question, (answer) => {
7
+ rl.close();
8
+ resolve(answer.trim());
9
+ }));
10
+ }
11
+
12
+ module.exports = async function () {
13
+ try {
14
+ const sub = await ask('Subject (sub): ');
15
+ if (!sub) {
16
+ console.error('Aborted: subject is required.');
17
+ return;
18
+ }
19
+
20
+ const secret = await ask('Secret: ');
21
+ if (!secret) {
22
+ console.error('Aborted: secret is required.');
23
+ return;
24
+ }
25
+
26
+ const expInput = await ask('Expiration in seconds (default 3600): ');
27
+ const exp = expInput ? parseInt(expInput, 10) : 3600;
28
+
29
+ const payload = {
30
+ sub,
31
+ exp: Math.floor(Date.now() / 1000) + exp,
32
+ };
33
+
34
+ const token = jwt.sign(payload, secret);
35
+ console.log('\nJWT:\n' + token);
36
+ } catch (err) {
37
+ console.error('Error:', err.message);
38
+ }
39
+ };
@@ -0,0 +1,63 @@
1
+ const readline = require('readline');
2
+ const { execSync } = require('child_process');
3
+ const fs = require('fs');
4
+ const niceware = require('niceware');
5
+
6
+ function ask(question) {
7
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
8
+ return new Promise((resolve) => rl.question(question, (answer) => {
9
+ rl.close();
10
+ resolve(answer.trim());
11
+ }));
12
+ }
13
+
14
+ module.exports = async function () {
15
+ try {
16
+ // 1. Ask if passphrase is needed
17
+ const passphraseAnswer = (await ask('Use passphrase? (y/N): ')).toLowerCase();
18
+ const usePassphrase = passphraseAnswer === 'y' || passphraseAnswer === 'yes';
19
+
20
+ let passphrase = null;
21
+ const tempPrivate = 'temp_private.pem';
22
+ const tempPublic = 'temp_public.pem';
23
+
24
+ // 2. Generate private key
25
+ if (usePassphrase) {
26
+ const words = niceware.generatePassphrase(6);
27
+ passphrase = words.join('-');
28
+ console.log(`Generated passphrase: ${passphrase}\n`);
29
+ execSync(`openssl genpkey -algorithm RSA -out ${tempPrivate} -aes256 -pass pass:${passphrase}`, { stdio: 'ignore' });
30
+ } else {
31
+ execSync(`openssl genpkey -algorithm RSA -out ${tempPrivate}`, { stdio: 'ignore' });
32
+ }
33
+
34
+ // 3. Generate public key
35
+ if (usePassphrase) {
36
+ execSync(`openssl rsa -pubout -in ${tempPrivate} -out ${tempPublic} -passin pass:${passphrase}`, { stdio: 'ignore' });
37
+ } else {
38
+ execSync(`openssl rsa -pubout -in ${tempPrivate} -out ${tempPublic}`, { stdio: 'ignore' });
39
+ }
40
+
41
+ // 4. Ask for file name
42
+ const name = await ask('File name (without extension): ');
43
+ if (!name) {
44
+ fs.unlinkSync(tempPrivate);
45
+ fs.unlinkSync(tempPublic);
46
+ return;
47
+ }
48
+
49
+ // 5. Rename files
50
+ const privateOut = `${name}_private_key.pem`;
51
+ const publicOut = `${name}_public_key.pem`;
52
+ fs.renameSync(tempPrivate, privateOut);
53
+ fs.renameSync(tempPublic, publicOut);
54
+
55
+ // 6. Write passphrase if needed
56
+ if (usePassphrase && passphrase) {
57
+ const passphraseFile = `${name}_passphrase.txt`;
58
+ fs.writeFileSync(passphraseFile, passphrase + '\n', { mode: 0o600 }); // restrict permissions
59
+ }
60
+ } catch (err) {
61
+ console.error('Error:', err.message);
62
+ }
63
+ };
@@ -0,0 +1,18 @@
1
+ const niceware = require('niceware');
2
+
3
+ module.exports = function (args) {
4
+ // Default values
5
+ let wordCount = 5;
6
+ let separator = '-';
7
+
8
+ args.forEach(arg => {
9
+ if (arg.startsWith('-W=')) {
10
+ wordCount = parseInt(arg.split('=')[1], 10);
11
+ } else if (arg.startsWith('-S=')) {
12
+ separator = arg.split('=')[1];
13
+ }
14
+ });
15
+
16
+ const passphrase = niceware.generatePassphrase(wordCount * 2);
17
+ console.log(passphrase.join(separator));
18
+ };
@@ -0,0 +1,163 @@
1
+ #!/bin/bash
2
+
3
+ set -euo pipefail # Safe mode
4
+
5
+ # Ensure required dependencies are installed
6
+ for cmd in kubectl yq jq kubeseal; do
7
+ if ! command -v "$cmd" >/dev/null 2>&1; then
8
+ echo "❌ Error: $cmd is required but not installed."
9
+ exit 1
10
+ fi
11
+ done
12
+
13
+ # Check if a file path is provided, otherwise prompt for it
14
+ if [ -z "${1:-}" ]; then
15
+ echo -n "📄 Enter path to the secret file (e.g., secret.yaml): "
16
+ read SECRET_FILE
17
+ else
18
+ SECRET_FILE=$1
19
+ fi
20
+
21
+ # Ensure the file exists
22
+ if [ ! -f "$SECRET_FILE" ]; then
23
+ echo "❌ Error: File not found: $SECRET_FILE"
24
+ exit 1
25
+ fi
26
+
27
+ # Ensure the file is a Kubernetes Secret
28
+ KIND=$(yq eval '.kind' "$SECRET_FILE")
29
+ if [ "$KIND" != "Secret" ]; then
30
+ echo "❌ Error: File is not a Kubernetes Secret (found kind: $KIND)"
31
+ exit 1
32
+ fi
33
+
34
+ # Extract namespace and secret name
35
+ NAMESPACE=$(yq eval '.metadata.namespace // "default"' "$SECRET_FILE")
36
+ SECRET_NAME=$(yq eval '.metadata.name' "$SECRET_FILE")
37
+
38
+ # Validate extracted values
39
+ if [ -z "$SECRET_NAME" ]; then
40
+ echo "❌ Error: Could not extract secret name from $SECRET_FILE"
41
+ exit 1
42
+ fi
43
+
44
+ # Get the current Kubernetes cluster name
45
+ CLUSTER_NAME=$(kubectl config current-context)
46
+
47
+ if ! kubectl get secret "$SECRET_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
48
+ echo "❗ Secret does not exist in cluster."
49
+ changes_detected=true
50
+ else
51
+ # Extract and decode Kubernetes secret
52
+ K8S_SECRET_JSON=$(kubectl get secret "$SECRET_NAME" -n "$NAMESPACE" -o json)
53
+ K8S_SECRET_DATA=$(echo "$K8S_SECRET_JSON" | jq -r '.data | to_entries | map("\(.key) \(.value | @base64d)") | .[]')
54
+
55
+ # Extract `stringData` from input file while preserving order
56
+ FILE_SECRET_JSON=$(yq eval -o=json '.stringData // {}' "$SECRET_FILE")
57
+ FILE_SECRET_DATA=$(echo "$FILE_SECRET_JSON" | jq -r 'to_entries | map("\(.key) \(.value)") | .[]')
58
+
59
+ # Convert to associative arrays for easier comparison and maintain order
60
+ declare -A k8s_secret_map
61
+ while IFS= read -r line; do
62
+ key=$(echo "$line" | awk '{print $1}')
63
+ value=$(echo "$line" | cut -d' ' -f2-)
64
+ k8s_secret_map["$key"]="$value"
65
+ done <<< "$K8S_SECRET_DATA"
66
+
67
+ declare -A file_secret_map
68
+ declare -a file_secret_keys
69
+ while IFS= read -r line; do
70
+ key=$(echo "$line" | awk '{print $1}')
71
+ value=$(echo "$line" | cut -d' ' -f2-)
72
+ file_secret_map["$key"]="$value"
73
+ file_secret_keys+=("$key")
74
+ done <<< "$FILE_SECRET_DATA"
75
+
76
+ RED="\e[31m"
77
+ GREEN="\e[32m"
78
+ YELLOW="\e[33m"
79
+ MAGENTA="\e[35m"
80
+ BLUE="\e[34m"
81
+ RESET="\e[0m"
82
+
83
+ # Get terminal width
84
+ COLUMNS=$(tput cols)
85
+
86
+ # Function to truncate text with ellipses
87
+ truncate_text() {
88
+ local text="$1"
89
+ local max_length=$((COLUMNS - 20)) # Adjust based on your needs
90
+ if [ ${#text} -gt $max_length ]; then
91
+ echo "${text:0:$max_length}..."
92
+ else
93
+ echo "$text"
94
+ fi
95
+ }
96
+
97
+ # Compare and print secrets
98
+ changes_detected=false
99
+ for key in "${file_secret_keys[@]}"; do
100
+ if [[ -v k8s_secret_map["$key"] ]]; then
101
+ if [[ "${file_secret_map[$key]}" != "${k8s_secret_map[$key]}" ]]; then
102
+ changes_detected=true
103
+ break
104
+ fi
105
+ else
106
+ changes_detected=true
107
+ break
108
+ fi
109
+ done
110
+
111
+ for key in "${!k8s_secret_map[@]}"; do
112
+ if [[ ! -v file_secret_map["$key"] ]]; then
113
+ changes_detected=true
114
+ break
115
+ fi
116
+ done
117
+
118
+ if $changes_detected; then
119
+ echo -e "Comparing secrets...\n"
120
+ for key in "${file_secret_keys[@]}"; do
121
+ if [[ -v k8s_secret_map["$key"] ]]; then
122
+ if [[ "${file_secret_map[$key]}" == "${k8s_secret_map[$key]}" ]]; then
123
+ :
124
+ # truncated_value=$(truncate_text "${file_secret_map[$key]}")
125
+ # printf "• %s: %s\n" "$key" "$truncated_value"
126
+ else
127
+ printf "${YELLOW}🔄 %s:${RESET}\n" "$key"
128
+ printf " ${MAGENTA}Old:${RESET} %s\n" "${k8s_secret_map[$key]}"
129
+ printf " ${BLUE}New:${RESET} %s\n" "${file_secret_map[$key]}"
130
+ fi
131
+ else
132
+ printf "${GREEN}➕ %s:${RESET} %s\n" "$key" "${file_secret_map[$key]}"
133
+ fi
134
+ done
135
+
136
+ for key in "${!k8s_secret_map[@]}"; do
137
+ if [[ ! -v file_secret_map["$key"] ]]; then
138
+ printf "${RED}➖ %s:${RESET} %s\n" "$key" "${k8s_secret_map[$key]}"
139
+ fi
140
+ done
141
+ else
142
+ echo "😶 No changes detected!"
143
+ fi
144
+ fi
145
+
146
+ # Ask for confirmation to create a sealed secret
147
+ echo
148
+ echo -e "📂 Input File ($SECRET_FILE)"
149
+ echo -e "🌎 K8s Secret ($SECRET_NAME in $NAMESPACE)"
150
+ echo -e "🔗 Cluster: $CLUSTER_NAME"
151
+ echo "Press Enter to create a Sealed Secret or Ctrl+C to cancel"
152
+ read
153
+
154
+ # Ensure output is a valid YAML
155
+ OUTPUT_FILE="${SECRET_FILE/secret.yaml/sealed-secret.yaml}"
156
+ kubeseal < "$SECRET_FILE" | yq eval -P > "$OUTPUT_FILE"
157
+
158
+ if [ $? -eq 0 ]; then
159
+ echo "✅ Sealed secret successfully created: $OUTPUT_FILE (YAML format)"
160
+ else
161
+ echo "❌ Error: Failed to create sealed secret."
162
+ exit 1
163
+ fi
@@ -0,0 +1,10 @@
1
+ const crypto = require('crypto');
2
+
3
+ module.exports = function () {
4
+ const randomString = crypto.randomBytes(32)
5
+ .toString('base64')
6
+ .replace(/[^a-zA-Z0-9]/g, '')
7
+ .slice(0, 32);
8
+
9
+ console.log(randomString);
10
+ };
@@ -0,0 +1,30 @@
1
+ const readline = require('readline');
2
+ const { execSync } = require('child_process');
3
+
4
+ function ask(question) {
5
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6
+ return new Promise((resolve) => rl.question(question, (answer) => {
7
+ rl.close();
8
+ resolve(answer.trim());
9
+ }));
10
+ }
11
+
12
+ module.exports = async function () {
13
+ try {
14
+ const filename = await ask('File name (without extension): ');
15
+ if (!filename) {
16
+ console.error('Cancelled: No file name provided.');
17
+ return;
18
+ }
19
+
20
+ const username = await ask('Username/Email (for comment): ');
21
+ if (!username) {
22
+ console.error('Cancelled: No username provided.');
23
+ return;
24
+ }
25
+
26
+ execSync(`ssh-keygen -t rsa -b 4096 -f ${filename} -C "${username}"`, { stdio: 'inherit' });
27
+ } catch (err) {
28
+ console.error('Error generating SSH key:', err.message);
29
+ }
30
+ };
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require("child_process");
4
+ const readline = require("readline");
5
+
6
+ function runCommand(cmd, allowError = false) {
7
+ try {
8
+ return execSync(cmd, { encoding: "utf-8" }).trim();
9
+ } catch (error) {
10
+ if (!allowError) {
11
+ console.error(`❌ Error running command: ${cmd}`);
12
+ }
13
+ return "";
14
+ }
15
+ }
16
+
17
+ function getLatestGlobalTag() {
18
+ const tags = runCommand("git tag", true)
19
+ .split("\n")
20
+ .filter(tag => /^v\d{6}\.\d+$/.test(tag)) // match vYYMMDD.N
21
+ .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
22
+ .filter(Boolean);
23
+
24
+ return tags[tags.length - 1] || "";
25
+ }
26
+
27
+ function getNextGlobalTag() {
28
+ const today = new Intl.DateTimeFormat("vi-VN", {
29
+ timeZone: "Asia/Ho_Chi_Minh",
30
+ year: "2-digit",
31
+ month: "2-digit",
32
+ day: "2-digit",
33
+ })
34
+ .format(new Date())
35
+ .split("/")
36
+ .reverse()
37
+ .join(""); // YYMMDD
38
+
39
+ const lastTag = getLatestGlobalTag();
40
+
41
+ let nextNumber = 1;
42
+ if (lastTag) {
43
+ const lastNumber = parseInt(lastTag.split(".")[1], 10);
44
+ nextNumber = lastNumber + 1;
45
+ }
46
+
47
+ return `v${today}.${nextNumber}`;
48
+ }
49
+
50
+ function getLastTaggableCommitOnMain() {
51
+ const commits = runCommand(`git log origin/main --pretty=format:"%h%x09%s"`).split("\n");
52
+
53
+ for (const line of commits) {
54
+ const [hash, message] = line.trim().split("\t");
55
+ if (!/skip-ci/i.test(message)) {
56
+ return { hash, message };
57
+ }
58
+ }
59
+
60
+ console.error("❌ No suitable commit found on origin/main without 'skip-ci' in the message.");
61
+ return null;
62
+ }
63
+
64
+ function tagLatestCommitOnMain(tag) {
65
+ const result = getLastTaggableCommitOnMain();
66
+ if (!result) return;
67
+
68
+ const { hash, message } = result;
69
+
70
+ const tagsOnCommit = runCommand(`git tag --contains ${hash}`, true)
71
+ .split("\n")
72
+ .filter(Boolean);
73
+
74
+ if (tagsOnCommit.length > 0) {
75
+ console.error(`❌ Commit ${hash} - ${message} is already tagged with: ${tagsOnCommit.join(", ")}`);
76
+ return;
77
+ }
78
+
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stdout,
82
+ });
83
+
84
+ rl.question(
85
+ `👉 New tag will be: ${tag}\nTag commit: ${hash} - ${message}\nPress Enter to confirm, or Ctrl+C to cancel: `,
86
+ () => {
87
+ runCommand(`git tag ${tag} ${hash}`);
88
+ runCommand(`git push origin ${tag}`);
89
+ console.log(`✅ Tagged ${hash} on origin/main with ${tag}`);
90
+ rl.close();
91
+ }
92
+ );
93
+ }
94
+ function isGitRepo() {
95
+ try {
96
+ const result = execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' })
97
+ .toString()
98
+ .trim();
99
+ return result === 'true';
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+ function fetchOriginMain() {
105
+ if (!isGitRepo()) {
106
+ console.error("❌ Not a git repository.");
107
+ process.exit(1);
108
+ }
109
+
110
+ console.log("📥 Fetching latest from origin...");
111
+ runCommand("git fetch origin main");
112
+ }
113
+
114
+ module.exports = function () {
115
+ fetchOriginMain();
116
+ const tag = getNextGlobalTag();
117
+ tagLatestCommitOnMain(tag);
118
+ };
@@ -0,0 +1,78 @@
1
+ #!/bin/bash
2
+
3
+ fetch_secret() {
4
+ local sealed_secret_path=$1
5
+
6
+ # Prompt if no path provided
7
+ while [[ -z "$sealed_secret_path" ]]; do
8
+ echo -n "📄 Enter path to the SealedSecret YAML file: "
9
+ read sealed_secret_path
10
+ done
11
+
12
+ # Ensure file exists
13
+ if [[ ! -f "$sealed_secret_path" ]]; then
14
+ echo "❌ File not found: $sealed_secret_path"
15
+ exit 1
16
+ fi
17
+
18
+ if ! cat "$sealed_secret_path" | kubeseal --validate; then
19
+ echo "❌ SealedSecret validation failed for: $sealed_secret_path"
20
+ exit 1
21
+ fi
22
+
23
+ # Check if the kind of the file is SealedSecret
24
+ local kind=$(yq eval '.kind' "$sealed_secret_path")
25
+ if [[ "$kind" != "SealedSecret" ]]; then
26
+ echo "❌ The file is not a SealedSecret."
27
+ exit 1
28
+ fi
29
+
30
+ # Derive the secret_output_path by removing 'sealed-' from the sealed_secret_path
31
+ local secret_output_path="${sealed_secret_path/sealed-/}"
32
+
33
+ # Extract name and namespace from spec.template.metadata
34
+ local secret_name=$(yq eval '.spec.template.metadata.name // ""' "$sealed_secret_path")
35
+ local secret_namespace=$(yq eval '.spec.template.metadata.namespace // ""' "$sealed_secret_path")
36
+
37
+ if [[ -z "$secret_name" || -z "$secret_namespace" ]]; then
38
+ echo "❌ Missing name or namespace in spec.template.metadata."
39
+ exit 1
40
+ fi
41
+
42
+ echo "📡 Fetching secret: $secret_name from namespace: $secret_namespace"
43
+
44
+ # Fetch the secret from Kubernetes
45
+ raw_secret=$(kubectl get secret "$secret_name" -n "$secret_namespace" -o yaml 2>/dev/null)
46
+
47
+ if [[ -z "$raw_secret" ]]; then
48
+ echo "❌ Failed to fetch the secret from Kubernetes."
49
+ exit 1
50
+ fi
51
+
52
+ # Extract the encryptedData keys into a list of strings
53
+ encrypted_data_keys=$(yq eval -o=json '.spec.encryptedData | keys' "$sealed_secret_path" | jq -c '.')
54
+
55
+ # Extract metadata from the sealed secret and convert it to JSON
56
+ metadata_json=$(yq eval '.spec.template.metadata' "$sealed_secret_path" | yq eval -o=json)
57
+
58
+ # Construct new Secret YAML
59
+ echo "$raw_secret" | yq eval "
60
+ .apiVersion = \"v1\" |
61
+ .kind = \"Secret\" |
62
+ .metadata = $metadata_json |
63
+ del(.metadata.creationTimestamp) |
64
+ .stringData = (.data | with_entries(select(.key as \$k | ($encrypted_data_keys | map(select(. == \$k))) | length > 0) | .value |= @base64d)) |
65
+ del(.data)
66
+ " - > "$secret_output_path"
67
+
68
+ if [[ $? -ne 0 ]]; then
69
+ echo "❌ Failed to process the secret."
70
+ exit 1
71
+ fi
72
+
73
+ echo "✅ Secret has been written to: $secret_output_path"
74
+ }
75
+
76
+ # Accept path as optional argument
77
+ sealed_secret_path="$1"
78
+ fetch_secret "$sealed_secret_path"
@@ -0,0 +1,11 @@
1
+ const { uuidv4 } = require('uuidv7');
2
+
3
+ module.exports = function () {
4
+ try {
5
+ const uuid = uuidv4()
6
+ console.log(uuid);
7
+ } catch (err) {
8
+ console.error('Error generating UUID:', err.message);
9
+ process.exit(1);
10
+ }
11
+ };
@@ -0,0 +1,11 @@
1
+ const { uuidv7 } = require('uuidv7');
2
+
3
+ module.exports = function () {
4
+ try {
5
+ const uuid = uuidv7()
6
+ console.log(uuid);
7
+ } catch (err) {
8
+ console.error('Error generating UUID:', err.message);
9
+ process.exit(1);
10
+ }
11
+ };
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@salefronts/cli",
3
+ "version": "1.0.0",
4
+ "bin": {
5
+ "sf": "bin/sf"
6
+ },
7
+ "scripts": {
8
+ "postinstall": "chmod +x bin/sf",
9
+ "publish": "publish --access public"
10
+ },
11
+ "dependencies": {
12
+ "jsonwebtoken": "^9.0.2",
13
+ "niceware": "^4.0.0",
14
+ "uuidv7": "~1.0.2"
15
+ }
16
+ }