diff --git a/README.md b/README.md index 0b764ae1d..a5c051b67 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ A key advantage of the Hedera CLI Tool is its potential to enhance your workflow - [Video Guide](#video-guide) - [Commands](#commands) - [Setup Commands](#setup-commands) + - [Telemetry Commands](#telemetry-commands) - [Network Commands](#network-commands) - [Wait Command](#wait-command) - [Account Commands](#account-commands) @@ -99,10 +100,10 @@ LOCALNET_OPERATOR_ID= LOCALNET_OPERATOR_KEY= ``` -Next, set up the CLI tool with the command: +Next, set up the CLI tool with the command. **The `--telemetry` flag is optional and enables telemetry. By default, telemetry is disabled. Hedera collects anonymous data to improve the CLI tool. For example, it records the command name, not the parameters or any other sensitive information. If you don't want us to collect telemetry data, run the command without the `--telemetry` flag.** ```sh -node dist/hedera-cli.js setup init +node dist/hedera-cli.js setup init --telemetry ``` > **Note.** You can set a custom absolute path for your `.env` file by using the `--path` flag. For example, `node dist/hedera-cli.js setup init --path /Users/myUser/projects/cli/.env`. More information can be found in the [setup command](#setup-commands) section below. @@ -190,6 +191,7 @@ Learn how to use the Hedera CLI Tool by watching the video below. Let's explore the different commands, their options, and outputs. - [Setup Commands](#setup-commands): Instantiate or reset the Hedera CLI tool +- [Telemetry Commands](#telemetry-commands): Enable or disable telemetry - [Network Commands](#network-commands): Switch Hedera networks - [Wait Command](#wait-command): Wait for a specified amount of seconds - [Account Commands](#account-commands): Create and manage accounts @@ -225,11 +227,12 @@ setup reload Sets up the CLI with the operator key and ID. ```sh -hcli setup init [--path ] +hcli setup init [--telemetry] [--path ] ``` Flags: +- **Telemetry:** (optional) Enables telemetry. By default disabled. Hedera collects anonymous data to improve the CLI tool. For example, it records the command name, not the parameters or any other sensitive information. - **Path:** (optional) Sets a custom absolute path for your `.env` file. Defaults to your homedir, e.g. `~/.hedera/.env`. When executed, the setup command performs several key functions: @@ -245,12 +248,41 @@ Once the localnet, previewnet, testnet, and mainnet operator key and ID are vali Reload the operator key and ID from the `.env` file. This command is useful when you add new networks to your `.env` file and want to update the state, so you can use the new networks. ```sh -hcli setup reload [--path ] +hcli setup reload [--telemetry] ``` Flags: -- **Path:** (optional) Sets a custom absolute path for your `.env` file. Defaults to your homedir, e.g. `~/.hedera/.env`. +- **Telemetry:** (optional) Enables telemetry. By default disabled. Hedera collects anonymous data to improve the CLI tool. For example, it records the command name, not the parameters or any other sensitive information. + +## Telemetry Commands + +### Overview + +The telemetry command in the Hedera CLI tool is designed to enable or disable telemetry. This feature allows users to opt-in or opt-out of telemetry data collection. Hedera **anonymizes data** and only records the command name, not the parameters or any other sensitive information. For example, it records `account create` but not the account alias or ID. The data is used to improve the CLI tool and provide better features and functionality, by trying to understand how users use the CLI. However, the CLI tool uses a UUID to identify the user, so no personal information is collected. This allows us to better understand how users interact with the CLI tool. + +```sh +telemetry enable +telemetry disable +``` + +#### Usage + +**1. Enable telemetry:** + +This command enables telemetry and sets the `telemetry` variable in the state to `1`. + +```sh +hcli telemetry enable +``` + +**2. Disable telemetry:** + +This command disables telemetry and sets the `telemetry` variable in the state to `0`. + +```sh +hcli telemetry disable +``` ## Network Commands @@ -1001,18 +1033,26 @@ Here's an example state: ```json { "network": "testnet", + "mirrorNodeLocalnet": "http://localhost:5551/api/v1", + "mirrorNodePreviewnet": "https://previewnet.mirrornode.hedera.com/api/v1", "mirrorNodeTestnet": "https://testnet.mirrornode.hedera.com/api/v1", "mirrorNodeMainnet": "https://mainnet.mirrornode.hedera.com/api/v1", + "telemetryServer": "http://localhost:3000/track", + "localNodeAddress": "127.0.0.1:50211", + "localNodeAccountId": "0.0.3", + "localNodeMirrorAddressGRPC": "127.0.0.1:5600", "testnetOperatorKey": "", "testnetOperatorId": "", "mainnetOperatorKey": "", "mainnetOperatorId": "", "previewnetOperatorId": "", "previewnetOperatorKey": "", + "telemetry": 0, "recording": 0, "recordingScriptName": "", "scriptExecution": 0, "scriptExecutionName": "", + "uuid": "", "accounts": { "bob": { "network": "testnet", diff --git a/__tests__/commands/account/view.test.ts b/__tests__/commands/account/view.test.ts index d026ea664..bf18a4c12 100644 --- a/__tests__/commands/account/view.test.ts +++ b/__tests__/commands/account/view.test.ts @@ -32,7 +32,7 @@ describe("account view command", () => { commands.accountCommands(program); // Act - await program.parse([ + await program.parseAsync([ "node", "hedera-cli.ts", "account", diff --git a/__tests__/commands/network/use.test.ts b/__tests__/commands/network/use.test.ts index 50cd9cfd8..986ef0094 100644 --- a/__tests__/commands/network/use.test.ts +++ b/__tests__/commands/network/use.test.ts @@ -35,7 +35,7 @@ describe('network use command', () => { commands.networkCommands(program); // Act - program.parse(['node', 'hedera-cli.ts', 'network', 'use', 'mainnet']); + await program.parseAsync(['node', 'hedera-cli.ts', 'network', 'use', 'mainnet']); // Assert expect(stateControllerSpy).toHaveBeenCalledWith('network', 'mainnet'); diff --git a/__tests__/commands/topic/list.test.ts b/__tests__/commands/topic/list.test.ts index f577ce351..38834cfcb 100644 --- a/__tests__/commands/topic/list.test.ts +++ b/__tests__/commands/topic/list.test.ts @@ -24,7 +24,7 @@ describe("topic list command", () => { commands.topicCommands(program); // Act - program.parse(["node", "hedera-cli.ts", "topic", "list"]); + await program.parseAsync(["node", "hedera-cli.ts", "topic", "list"]); // Assert expect(logSpy).toHaveBeenCalledWith(`Topics:`); diff --git a/__tests__/e2e.test.ts b/__tests__/e2e.test.ts index 39740bd55..7a056134c 100644 --- a/__tests__/e2e.test.ts +++ b/__tests__/e2e.test.ts @@ -65,7 +65,7 @@ describe('End to end tests', () => { commands.networkCommands(program); // Act - program.parse(['node', 'hedera-cli.ts', 'network', 'use', 'localnet']); + await program.parseAsync(['node', 'hedera-cli.ts', 'network', 'use', 'localnet']); // Assert const network = stateController.get('network'); @@ -127,7 +127,7 @@ describe('End to end tests', () => { const backupName = 'e2e'; // Act - program.parse([ + await program.parseAsync([ 'node', 'hedera-cli.ts', 'backup', @@ -144,7 +144,7 @@ describe('End to end tests', () => { commands.accountCommands(program); // Act - program.parse([ + await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', @@ -161,7 +161,7 @@ describe('End to end tests', () => { commands.backupCommands(program); // Act - program.parse([ + await program.parseAsync([ 'node', 'hedera-cli.ts', 'backup', @@ -244,7 +244,7 @@ describe('End to end tests', () => { // Arrange: Delete script and verify it is deleted in state file // Act - program.parse([ + await program.parseAsync([ 'node', 'hedera-cli.ts', 'script', @@ -434,7 +434,7 @@ describe('End to end tests', () => { commands.networkCommands(program); // Act - program.parse(['node', 'hedera-cli.ts', 'network', 'use', 'localnet']); + await program.parseAsync(['node', 'hedera-cli.ts', 'network', 'use', 'localnet']); // Assert const network = stateController.get('network'); diff --git a/__tests__/helpers/state.ts b/__tests__/helpers/state.ts index d2ed7bb2e..a82de4e0c 100644 --- a/__tests__/helpers/state.ts +++ b/__tests__/helpers/state.ts @@ -13,6 +13,8 @@ export const baseState: State = { mirrorNodePreviewnet: 'https://previewnet.mirrornode.hedera.com/api/v1', mirrorNodeTestnet: 'https://testnet.mirrornode.hedera.com/api/v1', mirrorNodeMainnet: 'https://mainnet.mirrornode.hedera.com/api/v1', + telemetryServer: "https://hedera-cli-telemetry.onrender.com/track", + telemetry: 0, recording: 0, recordingScriptName: '', scriptExecution: 0, @@ -33,6 +35,7 @@ export const baseState: State = { localNodeAddress: '127.0.0.1:50211', localNodeAccountId: '0.0.3', localNodeMirrorAddressGRPC: '127.0.0.1:5600', + uuid: '', }; /* accounts */ diff --git a/package-lock.json b/package-lock.json index 551d36aef..3c3474d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "axios": "^1.7.9", "commander": "^11.0.0", "dotenv": "^16.4.7", - "enquirer": "^2.4.1" + "enquirer": "^2.4.1", + "uuid": "^11.0.5" }, "bin": { "hedera-cli": "node ./dist/hedera-cli.js" @@ -2913,6 +2914,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3339,6 +3370,20 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3471,13 +3516,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -4305,16 +4347,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4332,6 +4379,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -4447,12 +4507,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4506,6 +4566,7 @@ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true, + "peer": true, "engines": { "node": ">= 0.4" }, @@ -4514,9 +4575,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "engines": { "node": ">= 0.4" @@ -4551,9 +4612,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -5392,6 +5453,15 @@ "node": ">=10.12.0" } }, + "node_modules/jest-junit/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jest-leak-detector": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", @@ -5915,6 +5985,15 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6087,11 +6166,14 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "peer": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7153,15 +7235,76 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "peer": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7846,12 +7989,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-to-istanbul": { @@ -10075,6 +10221,27 @@ "set-function-length": "^1.2.1" } }, + "call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "peer": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -10373,6 +10540,17 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -10492,13 +10670,10 @@ } }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true }, "es-errors": { "version": "1.3.0", @@ -11092,16 +11267,21 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, "get-package-type": { @@ -11110,6 +11290,16 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -11184,13 +11374,10 @@ } }, "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true }, "graceful-fs": { "version": "4.2.11", @@ -11230,12 +11417,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true + "dev": true, + "peer": true }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true }, "has-tostringtag": { @@ -11258,9 +11446,9 @@ } }, "hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "requires": { "function-bind": "^1.1.2" @@ -11852,6 +12040,14 @@ "strip-ansi": "^6.0.1", "uuid": "^8.3.2", "xml": "^1.0.1" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "jest-leak-detector": { @@ -12273,6 +12469,12 @@ "tmpl": "1.0.5" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -12405,9 +12607,9 @@ } }, "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "peer": true }, @@ -13161,15 +13363,55 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "peer": true, "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "peer": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "peer": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "peer": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, "signal-exit": { @@ -13670,10 +13912,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==" }, "v8-to-istanbul": { "version": "9.2.0", diff --git a/package.json b/package.json index f93f0b6d6..7e2a2da6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/hedera-cli", - "version": "0.2.0", + "version": "0.2.1", "description": "CLI tool to manage and setup developer environments for Hedera Hashgraph.", "main": "hedera-cli.js", "target": "esnext", @@ -44,7 +44,8 @@ "axios": "^1.7.9", "commander": "^11.0.0", "dotenv": "^16.4.7", - "enquirer": "^2.4.1" + "enquirer": "^2.4.1", + "uuid": "^11.0.5" }, "devDependencies": { "@types/jest": "^29.5.14", diff --git a/src/commands/account/balance.ts b/src/commands/account/balance.ts index 58799efd1..d4c212ad0 100644 --- a/src/commands/account/balance.ts +++ b/src/commands/account/balance.ts @@ -2,6 +2,7 @@ import stateUtils from '../../utils/state'; import { Logger } from '../../utils/logger'; import accountUtils from '../../utils/account'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; +import telemetryUtils from '../../utils/telemetry'; import type { Command } from '../../../types'; @@ -10,11 +11,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('balance') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Retrieve the balance for an account ID or alias') diff --git a/src/commands/account/clear.ts b/src/commands/account/clear.ts index bd6428f79..8ac969f01 100644 --- a/src/commands/account/clear.ts +++ b/src/commands/account/clear.ts @@ -1,5 +1,5 @@ import stateUtils from '../../utils/state'; - +import telemetryUtils from '../../utils/telemetry'; import accountUtils from '../../utils/account'; import type { Command } from '../../../types'; import { Logger } from '../../utils/logger'; @@ -9,11 +9,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('clear') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Clear all accounts from the address book') diff --git a/src/commands/account/create.ts b/src/commands/account/create.ts index 671f414df..04b092f00 100644 --- a/src/commands/account/create.ts +++ b/src/commands/account/create.ts @@ -3,6 +3,7 @@ import { Logger } from '../../utils/logger'; import { myParseInt } from '../../utils/verification'; import accountUtils from '../../utils/account'; +import telemetryUtils from '../../utils/telemetry'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; import type { Command } from '../../../types'; @@ -12,11 +13,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('create') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description( diff --git a/src/commands/account/delete.ts b/src/commands/account/delete.ts index 753736e39..a16968bfc 100644 --- a/src/commands/account/delete.ts +++ b/src/commands/account/delete.ts @@ -2,6 +2,7 @@ import stateUtils from '../../utils/state'; import { Logger } from '../../utils/logger'; import accountUtils from '../../utils/account'; +import telemetryUtils from '../../utils/telemetry'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; import type { Command } from '../../../types'; @@ -11,11 +12,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('delete') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Delete an account from the address book') diff --git a/src/commands/account/import.ts b/src/commands/account/import.ts index e81f81bce..7191a6611 100644 --- a/src/commands/account/import.ts +++ b/src/commands/account/import.ts @@ -1,5 +1,6 @@ import stateUtils from '../../utils/state'; import accountUtils from '../../utils/account'; +import telemetryUtils from '../../utils/telemetry'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; import { Logger } from '../../utils/logger'; @@ -10,11 +11,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('import') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description( diff --git a/src/commands/account/list.ts b/src/commands/account/list.ts index e6ec9a42b..05d3e408e 100644 --- a/src/commands/account/list.ts +++ b/src/commands/account/list.ts @@ -1,6 +1,7 @@ import stateUtils from '../../utils/state'; import accountUtils from '../../utils/account'; import { Logger } from '../../utils/logger'; +import telemetryUtils from '../../utils/telemetry'; import type { Command } from '../../../types'; @@ -9,11 +10,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('list') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('List all accounts in the address book') diff --git a/src/commands/account/view.ts b/src/commands/account/view.ts index e387d66bc..800a4aa19 100644 --- a/src/commands/account/view.ts +++ b/src/commands/account/view.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; import { Logger } from '../../utils/logger'; @@ -10,11 +11,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('view') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description( diff --git a/src/commands/backup.ts b/src/commands/backup.ts index 56c846176..d00150334 100644 --- a/src/commands/backup.ts +++ b/src/commands/backup.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import stateUtils from '../utils/state'; +import telemetryUtils from '../utils/telemetry'; import enquirerUtils from '../utils/enquirer'; import stateController from '../state/stateController'; import { Logger } from '../utils/logger'; @@ -155,11 +156,14 @@ export default (program: any) => { network .command('create') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Create a backup of the state.json file') @@ -179,11 +183,14 @@ export default (program: any) => { network .command('restore') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Restore a backup of the full state') diff --git a/src/commands/hbar.ts b/src/commands/hbar.ts index ca38ab87c..c13cf6e48 100644 --- a/src/commands/hbar.ts +++ b/src/commands/hbar.ts @@ -1,4 +1,5 @@ import stateUtils from '../utils/state'; +import telemetryUtils from '../utils/telemetry'; import stateController from '../state/stateController'; import enquirerUtils from '../utils/enquirer'; import dynamicVariablesUtils from '../utils/dynamicVariables'; @@ -14,11 +15,14 @@ export default (program: any) => { hbar .command('transfer') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Transfer tinybars between accounts') diff --git a/src/commands/index.ts b/src/commands/index.ts index 2ec6151ef..a33e02963 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -6,6 +6,7 @@ import hbarCommands from './hbar'; import setupCommands from './setup'; import stateCommands from './state'; import recordCommands from './record'; +import telemetryCommands from './telemetry'; import scriptCommands from './script'; import topicCommands from './topic'; import waitCommands from './wait'; @@ -19,6 +20,7 @@ const commands = { setupCommands, stateCommands, recordCommands, + telemetryCommands, scriptCommands, topicCommands, waitCommands, diff --git a/src/commands/network/list.ts b/src/commands/network/list.ts index 55ab64846..8c0b3ffa3 100644 --- a/src/commands/network/list.ts +++ b/src/commands/network/list.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import type { Command } from '../../../types'; @@ -8,11 +9,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('list') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('List all available networks') diff --git a/src/commands/network/use.ts b/src/commands/network/use.ts index 688f7eba7..b5435b24d 100644 --- a/src/commands/network/use.ts +++ b/src/commands/network/use.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import type { Command } from '../../../types'; @@ -8,11 +9,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('use ') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Switch to a specific network') diff --git a/src/commands/record.ts b/src/commands/record.ts index c576751f8..be5cdd4e1 100644 --- a/src/commands/record.ts +++ b/src/commands/record.ts @@ -1,6 +1,10 @@ import stateController from '../state/stateController'; +import stateUtils from '../utils/state'; +import telemetryUtils from '../utils/telemetry'; import { Logger } from '../utils/logger'; +import type { Command } from '../../types'; + const logger = Logger.getInstance(); function startRecording(scriptName: string) { @@ -24,6 +28,15 @@ function stopRecording(): void { export default (program: any) => { program .command('record [name]') + .hook('preAction', async (thisCommand: Command) => { + const command = [ + thisCommand.parent.action().name(), + ...thisCommand.parent.args, + ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } + }) .description('Manage recording of a script') .action((action: string, name: string) => { switch (action) { diff --git a/src/commands/script/delete.ts b/src/commands/script/delete.ts index 73413d8b5..cfb86b8b8 100644 --- a/src/commands/script/delete.ts +++ b/src/commands/script/delete.ts @@ -1,5 +1,6 @@ import stateUtils from '../../utils/state'; import scriptUtils from '../../utils/script'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; @@ -10,11 +11,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('delete') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Delete a script') diff --git a/src/commands/script/list.ts b/src/commands/script/list.ts index d20e73b14..9ba157d13 100644 --- a/src/commands/script/list.ts +++ b/src/commands/script/list.ts @@ -1,5 +1,6 @@ import stateUtils from '../../utils/state'; import scriptUtils from '../../utils/script'; +import telemetryUtils from '../../utils/telemetry'; import type { Command } from '../../../types'; import { Logger } from '../../utils/logger'; @@ -8,11 +9,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('list') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('List all scripts') diff --git a/src/commands/script/load.ts b/src/commands/script/load.ts index 71116ec99..80ab6452f 100644 --- a/src/commands/script/load.ts +++ b/src/commands/script/load.ts @@ -1,5 +1,6 @@ import stateController from '../../state/stateController'; import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { execSync } from 'child_process'; import { Logger } from '../../utils/logger'; @@ -46,11 +47,14 @@ function loadScript(name: string) { export default (program: any) => { program .command('load') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Load and execute a script') diff --git a/src/commands/setup.ts b/src/commands/setup.ts index e606cb255..988e265f6 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -3,6 +3,7 @@ import * as dotenv from 'dotenv'; import * as os from 'os'; import stateUtils from '../utils/state'; +import telemetryUtils from '../utils/telemetry'; import config from '../state/config'; import { Logger } from '../utils/logger'; import accountUtils from '../utils/account'; @@ -15,6 +16,11 @@ const logger = Logger.getInstance(); interface SetupOptions { path: string; + telemetry: boolean; +} + +interface ReloadOptions { + telemetry: boolean; } /** @@ -56,7 +62,11 @@ async function verifyOperatorBalance( * @param action Action to perform (init or reload) * @param envPath Path to the .env file */ -async function setupCLI(action: string, envPath: string = ''): Promise { +async function setupCLI( + action: string, + telemetry: boolean = false, + envPath: string = '', +): Promise { let finalPath = ''; if (envPath !== '') { finalPath = path.normalize(envPath); @@ -95,6 +105,7 @@ async function setupCLI(action: string, envPath: string = ''): Promise { PREVIEWNET_OPERATOR_KEY, LOCALNET_OPERATOR_ID, LOCALNET_OPERATOR_KEY, + TELEMETRY_URL, } = process.env; let mainnetOperatorId = MAINNET_OPERATOR_ID || ''; @@ -167,6 +178,14 @@ async function setupCLI(action: string, envPath: string = ''): Promise { localnetOperatorId, localnetOperatorKey, ); + + // Set telemetry server URL + let telemetryServer = + TELEMETRY_URL || 'https://hedera-cli-telemetry.onrender.com/track'; + stateController.saveKey('telemetryServer', telemetryServer); + if (telemetry === true) { + stateController.saveKey('telemetry', 1); + } } export default (program: any) => { @@ -174,37 +193,52 @@ export default (program: any) => { setup .command('init') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Setup the CLI with operator key and ID') .option('--path ', 'Specify a custom path for the .env file') + .option( + '--telemetry', + 'Enable telemetry for Hedera to process anonymous usage data, disabled by default', + ) .action(async (options: SetupOptions) => { logger.verbose( 'Initializing the CLI tool with the config and operator key and ID for different networks', ); - await setupCLI('init', options.path); + await setupCLI('init', options.telemetry, options.path); + stateUtils.createUUID(); // Create a new UUID for the user if doesn't exist }); setup .command('reload') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Reload the CLI with operator key and ID') .option('--path ', 'Specify a custom path for the .env file') - .action(async () => { + .option( + '--telemetry', + 'Enable telemetry for Hedera to process anonymous usage data, disabled by default', + ) + .action(async (options: ReloadOptions) => { logger.verbose( 'Reloading the CLI tool with operator key and ID for different networks', ); - await setupCLI('reload'); + await setupCLI('reload', options.telemetry); }); }; diff --git a/src/commands/state/clear.ts b/src/commands/state/clear.ts index 6359b6171..ad8176cd1 100644 --- a/src/commands/state/clear.ts +++ b/src/commands/state/clear.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import type { Command } from '../../../types'; import { Logger } from '../../utils/logger'; import stateController from '../../state/stateController'; @@ -33,11 +34,14 @@ function clear( export default (program: any) => { program .command('clear') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Clear all state and reset to default') diff --git a/src/commands/state/download.ts b/src/commands/state/download.ts index 856ddecd0..488d66951 100644 --- a/src/commands/state/download.ts +++ b/src/commands/state/download.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import type { Command } from '../../../types'; @@ -8,11 +9,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('download') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description( diff --git a/src/commands/state/view.ts b/src/commands/state/view.ts index 75184c5bc..81474143a 100644 --- a/src/commands/state/view.ts +++ b/src/commands/state/view.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import type { Command } from '../../../types'; import { Logger } from '../../utils/logger'; import stateController from '../../state/stateController'; @@ -9,11 +10,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('view') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('View state') diff --git a/src/commands/telemetry.ts b/src/commands/telemetry.ts new file mode 100644 index 000000000..e3c043b59 --- /dev/null +++ b/src/commands/telemetry.ts @@ -0,0 +1,27 @@ +import stateController from '../state/stateController'; +import { Logger } from '../utils/logger'; + +const logger = Logger.getInstance(); + +export default (program: any) => { + program + .command('telemetry ') + .description('Enable or disable telemetry') + .action((action: string) => { + switch (action) { + case 'enable': + stateController.saveKey('telemetry', 1); + logger.log('Telemetry turned on'); + break; + case 'disable': + stateController.saveKey('telemetry', 0); + logger.log('Telemetry turned off'); + break; + default: + logger.error( + `Unknown telemetry option: Use 'enable' or 'disable' commands`, + ); + process.exit(1); + } + }); +}; diff --git a/src/commands/token/associate.ts b/src/commands/token/associate.ts index 368a2259d..44409f2c8 100644 --- a/src/commands/token/associate.ts +++ b/src/commands/token/associate.ts @@ -1,5 +1,6 @@ import tokenUtils from '../../utils/token'; import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import type { Command } from '../../../types'; @@ -10,11 +11,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('associate') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Associate a token with an account') diff --git a/src/commands/token/create.ts b/src/commands/token/create.ts index 78cf8dc50..872713499 100644 --- a/src/commands/token/create.ts +++ b/src/commands/token/create.ts @@ -3,6 +3,7 @@ import { TokenCreateTransaction, TokenType, PrivateKey } from '@hashgraph/sdk'; import { myParseInt } from '../../utils/verification'; import tokenUtils from '../../utils/token'; import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import stateController from '../../state/stateController'; @@ -112,11 +113,14 @@ async function createFungibleToken( export default (program: any) => { program .command('create') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Create a new fungible token') diff --git a/src/commands/token/createFromFile.ts b/src/commands/token/createFromFile.ts index 36a786ac4..bdf17b1ac 100644 --- a/src/commands/token/createFromFile.ts +++ b/src/commands/token/createFromFile.ts @@ -9,6 +9,7 @@ import { import accountUtils from '../../utils/account'; import tokenUtils from '../../utils/token'; import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import feeUtils from '../../utils/fees'; import { Logger } from '../../utils/logger'; import stateController from '../../state/stateController'; @@ -351,11 +352,14 @@ async function createToken(options: CreateTokenFromFileOptions) { export default (program: any) => { program .command('create-from-file') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Create a new token from a file') diff --git a/src/commands/token/transfer.ts b/src/commands/token/transfer.ts index 1f88a6ecb..c2ad6f614 100644 --- a/src/commands/token/transfer.ts +++ b/src/commands/token/transfer.ts @@ -1,5 +1,6 @@ import { myParseInt } from '../../utils/verification'; import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; import { Logger } from '../../utils/logger'; @@ -11,11 +12,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('transfer') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Transfer a fungible token') diff --git a/src/commands/topic/create.ts b/src/commands/topic/create.ts index 81bb27aa8..296b687c3 100644 --- a/src/commands/topic/create.ts +++ b/src/commands/topic/create.ts @@ -1,6 +1,7 @@ import stateUtils from '../../utils/state'; import { Logger } from '../../utils/logger'; import signUtils from '../../utils/sign'; +import telemetryUtils from '../../utils/telemetry'; import stateController from '../../state/stateController'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; import { TopicCreateTransaction, PrivateKey } from '@hashgraph/sdk'; @@ -12,11 +13,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('create') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Create a new topic') diff --git a/src/commands/topic/list.ts b/src/commands/topic/list.ts index 679400e28..cd591cce6 100644 --- a/src/commands/topic/list.ts +++ b/src/commands/topic/list.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import topicUtils from '../../utils/topic'; @@ -9,11 +10,14 @@ const logger = Logger.getInstance(); export default (program: any) => { program .command('list') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('List all topics') diff --git a/src/commands/topic/message.ts b/src/commands/topic/message.ts index 4e1f4f1e3..f146d78a5 100644 --- a/src/commands/topic/message.ts +++ b/src/commands/topic/message.ts @@ -1,4 +1,5 @@ import stateUtils from '../../utils/state'; +import telemetryUtils from '../../utils/telemetry'; import { Logger } from '../../utils/logger'; import stateController from '../../state/stateController'; import dynamicVariablesUtils from '../../utils/dynamicVariables'; @@ -80,11 +81,14 @@ export default (program: any) => { message .command('submit') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Create a new topic') @@ -127,11 +131,14 @@ export default (program: any) => { message .command('find') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Find a message by sequence number') diff --git a/src/commands/wait.ts b/src/commands/wait.ts index 4e6397234..ddb700c92 100644 --- a/src/commands/wait.ts +++ b/src/commands/wait.ts @@ -1,4 +1,5 @@ import stateUtils from '../utils/state'; +import telemetryUtils from '../utils/telemetry'; import { Logger } from '../utils/logger'; import type { Command } from '../../types'; @@ -12,11 +13,14 @@ async function wait(seconds: number) { export default (program: any) => { program .command('wait ') - .hook('preAction', (thisCommand: Command) => { + .hook('preAction', async (thisCommand: Command) => { const command = [ thisCommand.parent.action().name(), ...thisCommand.parent.args, ]; + if (stateUtils.isTelemetryEnabled()) { + await telemetryUtils.recordCommand(command.join(' ')); + } stateUtils.recordCommand(command); }) .description('Wait for a specified number of seconds') diff --git a/src/hedera-cli.ts b/src/hedera-cli.ts index 2f6254593..af9da7e95 100755 --- a/src/hedera-cli.ts +++ b/src/hedera-cli.ts @@ -23,6 +23,7 @@ commands.setupCommands(program); commands.networkCommands(program); commands.accountCommands(program); commands.recordCommands(program); +commands.telemetryCommands(program); commands.scriptCommands(program); commands.backupCommands(program); commands.tokenCommands(program); diff --git a/src/state/base_state.json b/src/state/base_state.json index db3bc6ec9..6ed0b8d5a 100644 --- a/src/state/base_state.json +++ b/src/state/base_state.json @@ -1,9 +1,10 @@ { - "network": "testnet", + "network": "localnet", "mirrorNodeLocalnet": "http://localhost:5551/api/v1", "mirrorNodePreviewnet": "https://previewnet.mirrornode.hedera.com/api/v1", "mirrorNodeTestnet": "https://testnet.mirrornode.hedera.com/api/v1", "mirrorNodeMainnet": "https://mainnet.mirrornode.hedera.com/api/v1", + "telemetryServer": "https://hedera-cli-telemetry.onrender.com/track", "testnetOperatorKey": "", "testnetOperatorId": "", "mainnetOperatorKey": "", @@ -12,6 +13,7 @@ "previewnetOperatorKey": "", "localnetOperatorKey": "", "localnetOperatorId": "", + "telemetry": 0, "recording": 0, "recordingScriptName": "", "scriptExecution": 0, @@ -27,5 +29,6 @@ }, "localNodeAddress": "127.0.0.1:50211", "localNodeAccountId": "0.0.3", - "localNodeMirrorAddressGRPC": "127.0.0.1:5600" + "localNodeMirrorAddressGRPC": "127.0.0.1:5600", + "uuid": "" } diff --git a/src/state/config.ts b/src/state/config.ts index 394927dab..9ca79d08a 100644 --- a/src/state/config.ts +++ b/src/state/config.ts @@ -4,6 +4,8 @@ export default { mirrorNodePreviewnet: 'https://previewnet.mirrornode.hedera.com/api/v1', mirrorNodeTestnet: 'https://testnet.mirrornode.hedera.com/api/v1', mirrorNodeMainnet: 'https://mainnet.mirrornode.hedera.com/api/v1', + telemetryServer: 'https://hedera-cli-telemetry.onrender.com/track', + telemetry: 0, recording: 0, recordingScriptName: '', scriptExecution: 0, @@ -24,4 +26,5 @@ export default { localNodeAddress: '127.0.0.1:50211', localNodeAccountId: '0.0.3', localNodeMirrorAddressGRPC: '127.0.0.1:5600', + uuid: '', }; diff --git a/src/state/state.json b/src/state/state.json index e9756f614..8a8227389 100644 --- a/src/state/state.json +++ b/src/state/state.json @@ -4,6 +4,8 @@ "mirrorNodePreviewnet": "https://previewnet.mirrornode.hedera.com/api/v1", "mirrorNodeTestnet": "https://testnet.mirrornode.hedera.com/api/v1", "mirrorNodeMainnet": "https://mainnet.mirrornode.hedera.com/api/v1", + "telemetryServer": "https://hedera-cli-telemetry.onrender.com/track", + "telemetry": 0, "recording": 0, "recordingScriptName": "", "scriptExecution": 0, @@ -22,5 +24,6 @@ "localnetOperatorId": "0.0.2", "localNodeAddress": "127.0.0.1:50211", "localNodeAccountId": "0.0.3", - "localNodeMirrorAddressGRPC": "127.0.0.1:5600" + "localNodeMirrorAddressGRPC": "127.0.0.1:5600", + "uuid": "" } \ No newline at end of file diff --git a/src/state/test_state.json b/src/state/test_state.json index 55f9f6e54..2628dac4f 100644 --- a/src/state/test_state.json +++ b/src/state/test_state.json @@ -4,6 +4,8 @@ "mirrorNodePreviewnet": "https://previewnet.mirrornode.hedera.com/api/v1", "mirrorNodeTestnet": "https://testnet.mirrornode.hedera.com/api/v1", "mirrorNodeMainnet": "https://mainnet.mirrornode.hedera.com/api/v1", + "telemetryServer": "https://hedera-cli-telemetry.onrender.com/track", + "telemetry": 0, "testnetOperatorKey": "", "testnetOperatorId": "", "mainnetOperatorKey": "", @@ -28,5 +30,6 @@ }, "localNodeAddress": "127.0.0.1:50211", "localNodeAccountId": "0.0.3", - "localNodeMirrorAddressGRPC": "127.0.0.1:5600" + "localNodeMirrorAddressGRPC": "127.0.0.1:5600", + "uuid": "" } diff --git a/src/utils/state.ts b/src/utils/state.ts index e867cdba6..0da669542 100644 --- a/src/utils/state.ts +++ b/src/utils/state.ts @@ -1,5 +1,6 @@ import { Client, AccountId, PrivateKey } from '@hashgraph/sdk'; import axios from 'axios'; +import { v4 as uuidv4 } from 'uuid'; import { Logger } from '../utils/logger'; import stateController from '../state/stateController'; @@ -20,6 +21,26 @@ function recordCommand(command: string[]): void { } } +/** + * Generates a UUID when it doesn't exist + */ +function createUUID() { + const uuid = stateController.get('uuid'); + if (uuid === '' || !uuid) { + const newUUID = uuidv4(); + stateController.saveKey('uuid', newUUID); + } +} + +/** + * Returns the current telemetry setting + * @returns {boolean} telemetry + */ +function isTelemetryEnabled(): boolean { + const telemetry = stateController.get('telemetry'); + return telemetry === 1; +} + function getMirrorNodeURL(): string { const network = stateController.get('network'); let mirrorNodeURL = stateController.get('mirrorNodeTestnet'); @@ -438,6 +459,8 @@ function importState(data: any, overwrite: boolean, merge: boolean) { } const stateUtils = { + createUUID, + isTelemetryEnabled, getMirrorNodeURL, getMirrorNodeURLByNetwork, getHederaClient, diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts new file mode 100644 index 000000000..be7a0e830 --- /dev/null +++ b/src/utils/telemetry.ts @@ -0,0 +1,36 @@ +import stateController from '../state/stateController'; +const { version } = require('../../package.json'); + +async function recordCommand(command: string) { + const payload = { + command: command, + timestamp: new Date().toISOString(), + version, + }; + + try { + // TODO: Replace with actual telemetry endpoint. + // If .env contains a TELEMETRY_URL, use that instead otherwise use the default URL. + const telemetryUrl = + stateController.get('telemetryServer') || + 'https://hedera-cli-telemetry.onrender.com/track'; + await fetch(telemetryUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Telemetry-Token': + stateController.get('uuid') || 'facade00-0000-4000-a000-000000000000', // Default user ID + }, + body: JSON.stringify(payload), + }); + } catch (err) { + // Fail silently; telemetry errors should not impact user experience. + console.error('Telemetry error:', err); + } +} + +const telemetryUtils = { + recordCommand, +}; + +export default telemetryUtils; diff --git a/types/state.d.ts b/types/state.d.ts index 1670388af..857d06e0a 100644 --- a/types/state.d.ts +++ b/types/state.d.ts @@ -88,6 +88,8 @@ export interface State { mirrorNodePreviewnet: string; mirrorNodeTestnet: string; mirrorNodeMainnet: string; + telemetryServer: string; + telemetry: number; recording: number; recordingScriptName: string; scriptExecution: number; @@ -107,6 +109,7 @@ export interface State { testnetOperatorId: string; mainnetOperatorKey: string; mainnetOperatorId: string; + uuid: string; } export interface DownloadState {