diff --git a/cli/index.mjs b/cli/index.mjs new file mode 100644 index 00000000..bf47e642 --- /dev/null +++ b/cli/index.mjs @@ -0,0 +1,221 @@ +#!/usr/bin/env node + +import yargs from 'yargs' +import crypto from 'crypto' +import { hideBin } from 'yargs/helpers' +import inquirer from 'inquirer' +import process from 'process' +import nano from 'nanocurrency' + +import { + sign_nano_community_link_key, + sign_nano_community_revoke_key, + sign_nano_community_message +} from '#common' +import ed25519 from '@trashman/ed25519-blake2b' + +const load_private_key = async () => { + let private_key = process.env.NANO_PRIVATE_KEY + if (private_key) { + console.log('Private key found in environment variable.') + } else { + console.log('No private key found in environment variable or stdin.') + // Restore stdin for inquirer + const answers = await inquirer.prompt([ + { + type: 'password', + name: 'private_key', + message: 'Please enter your private key:' + } + ]) + private_key = answers.private_key + } + + const public_key = nano.derivePublicKey(private_key) + const nano_account_address = nano.deriveAddress(public_key) + return { + private_key, + public_key, + nano_account_address + } +} + +const add_signing_key = { + command: 'add-signing-key', + describe: 'Add a new signing key', + handler: async () => { + const { private_key, public_key, nano_account_address } = + await load_private_key() + + const linked_private_key = crypto.randomBytes(32) + const linked_public_key = ed25519.publicKey(linked_private_key) + + const signature = sign_nano_community_link_key({ + linked_public_key, + nano_account: nano_account_address, + nano_account_private_key: private_key, + nano_account_public_key: public_key + }) + + console.log({ + private_key, + public_key, + nano_account_address, + signature, + linked_public_key, + linked_private_key + }) + + // TODO send signed message to API + // TODO print out the linked public key and private key + } +} + +const revoke_signing_key = { + command: 'revoke-signing-key ', + describe: 'Revoke an existing signing key', + builder: (yargs) => + yargs.positional('linked_public_key', { + describe: 'Public key of the signing key to revoke', + type: 'string' + }), + handler: async ({ linked_public_key }) => { + const { private_key, public_key, nano_account_address } = + await load_private_key() + + // Confirm revocation + const answers = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm_revoke', + message: `Are you sure you want to revoke the signing key: ${public_key}?`, + default: false + } + ]) + + if (answers.confirm_revoke) { + console.log('Revoking signing key...') + const signature = sign_nano_community_revoke_key({ + linked_public_key: public_key, + nano_account: nano_account_address, + nano_account_private_key: private_key, + nano_account_public_key: public_key + }) + + console.log({ + signature, + linked_public_key: public_key, + nano_account_address, + private_key, + public_key + }) + + // TODO send signed message to API + } else { + console.log('Signing key revocation cancelled.') + } + } +} + +const send_message = { + command: 'send-message ', + describe: 'Send a message', + builder: (yargs) => + yargs.positional('type', { + describe: 'Type of message to send', + choices: ['update-rep-meta', 'update-account-meta', 'update-block-meta'] + }), + handler: async (argv) => { + const { private_key, public_key } = await load_private_key() + + // TODO fetch current values from API + + let message_content_prompts = [] + console.log(`Sending message of type: ${argv.type}`) + switch (argv.type) { + case 'update-rep-meta': + message_content_prompts = [ + { name: 'alias', message: 'Alias:' }, + { name: 'description', message: 'Description:' }, + { name: 'donation_address', message: 'Donation Address:' }, + { name: 'cpu_model', message: 'CPU Model:' }, + { name: 'cpu_cores', message: 'CPU Cores:' }, + { name: 'ram_amount', message: 'RAM Amount:' }, + { name: 'reddit_username', message: 'Reddit Username:' }, + { name: 'twitter_username', message: 'Twitter Username:' }, + { name: 'discord_username', message: 'Discord Username:' }, + { name: 'github_username', message: 'GitHub Username:' }, + { name: 'email', message: 'Email:' }, + { name: 'website_url', message: 'Website URL:' } + ] + break + case 'update-account-meta': + message_content_prompts = [{ name: 'alias', message: 'Alias:' }] + break + case 'update-block-meta': + message_content_prompts = [{ name: 'note', message: 'Note:' }] + break + default: + console.error('Unknown message type') + return + } + + const message_content = await inquirer.prompt(message_content_prompts) + let confirm_edit = false + do { + console.log('Please review your message content:', message_content) + confirm_edit = await inquirer.prompt([ + { + type: 'confirm', + name: 'edit', + message: 'Would you like to edit any field?', + default: false + } + ]) + confirm_edit = confirm_edit.edit + if (confirm_edit) { + const field_to_edit = await inquirer.prompt([ + { + type: 'list', + name: 'field', + message: 'Which field would you like to edit?', + choices: message_content_prompts.map((prompt) => prompt.name) + } + ]) + const new_value = await inquirer.prompt([ + { + name: 'new_value', + message: `Enter new value for ${field_to_edit.field}:` + } + ]) + message_content[field_to_edit.field] = new_value.new_value + } + } while (confirm_edit) + + const message = { + created_at: Math.floor(Date.now() / 1000), + public_key, // public key of signing key + operation: argv.type.replace('-', '_'), + content: message_content + } + + const signature = sign_nano_community_message(message, private_key) + + console.log({ + message, + signature + }) + + // TODO send signed message to API + } +} + +// eslint-disable-next-line no-unused-expressions +yargs(hideBin(process.argv)) + .scriptName('nano-cli') + .usage('$0 [args]') + .command(add_signing_key) + .command(revoke_signing_key) + .command(send_message) + .help('h') + .alias('h', 'help').argv diff --git a/package.json b/package.json index 09ba4e31..4acae922 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "fetch-cheerio-object": "^1.3.0", "front-matter": "^4.0.2", "fs-extra": "^11.1.1", + "inquirer": "^9.2.19", "jsonwebtoken": "^9.0.1", "knex": "^0.95.15", "markdown-it": "^12.3.2", diff --git a/yarn.lock b/yarn.lock index c5d0b986..2a1b0435 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2865,6 +2865,13 @@ __metadata: languageName: node linkType: hard +"@inquirer/figures@npm:^1.0.1": + version: 1.0.1 + resolution: "@inquirer/figures@npm:1.0.1" + checksum: e428dac4921c12fa65f1e2f2846f3fdb2c8ea98ef250e8758bc117d67625496bf1f844b67364e69815b6a8d9b2b0857c9864aec5aebb8a92fc3408d16ccdcc39 + languageName: node + linkType: hard + "@ipld/car@npm:^3.0.1, @ipld/car@npm:^3.2.3": version: 3.2.4 resolution: "@ipld/car@npm:3.2.4" @@ -2968,6 +2975,15 @@ __metadata: languageName: node linkType: hard +"@ljharb/through@npm:^2.3.13": + version: 2.3.13 + resolution: "@ljharb/through@npm:2.3.13" + dependencies: + call-bind: ^1.0.7 + checksum: 0255464a0ec7901b08cff3e99370b87e66663f46249505959c0cb4f6121095d533bbb7c7cda338063d3e134cbdd721e2705bc18eac7611b4f9ead6e7935d13ba + languageName: node + linkType: hard + "@mui/base@npm:5.0.0-beta.37": version: 5.0.0-beta.37 resolution: "@mui/base@npm:5.0.0-beta.37" @@ -5029,7 +5045,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -5543,6 +5559,17 @@ __metadata: languageName: node linkType: hard +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + "bl@npm:^5.0.0": version: 5.1.0 resolution: "bl@npm:5.1.0" @@ -5851,7 +5878,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.2.1": +"buffer@npm:^5.2.1, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -6232,6 +6259,20 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 6fd5da1f5d18ff5712c1e0aed41da200d7c51c28f11b36ee3c7b483f3696dabc08927fc6b227735eb8f0e1215c9a8abd8154637f3eff8cada5959df7f58b024d + languageName: node + linkType: hard + "charset@npm:^1.0.1": version: 1.0.1 resolution: "charset@npm:1.0.1" @@ -6416,6 +6457,29 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: ^3.1.0 + checksum: 2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 1bd588289b28432e4676cb5d40505cfe3e53f2e4e10fbe05c8a710a154d6fe0ce7836844b00d6858f740f2ffe67cdc36e0fce9c7b6a8430e80e6388d5aa4956c + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 0a79cff2dbf89ef530bcd54c713703ba94461457b11e5634bd024c78796ed21401e32349c004995954e06f442d82609287e7aabf6a5f02c919a1cf3b9b6854ff + languageName: node + linkType: hard + "clipboardy@npm:^2.3.0": version: 2.3.0 resolution: "clipboardy@npm:2.3.0" @@ -6476,6 +6540,13 @@ __metadata: languageName: node linkType: hard +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + "clsx@npm:^2.0.0, clsx@npm:^2.1.0": version: 2.1.0 resolution: "clsx@npm:2.1.0" @@ -7459,6 +7530,15 @@ __metadata: languageName: node linkType: hard +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a + languageName: node + linkType: hard + "defer-to-connect@npm:^1.0.1": version: 1.1.3 resolution: "defer-to-connect@npm:1.1.3" @@ -8826,6 +8906,17 @@ __metadata: languageName: node linkType: hard +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: ^0.7.0 + iconv-lite: ^0.4.24 + tmp: ^0.0.33 + checksum: 1c2a616a73f1b3435ce04030261bed0e22d4737e14b090bb48e58865da92529c9f2b05b893de650738d55e692d071819b45e1669259b2b354bc3154d27a698c7 + languageName: node + linkType: hard + "extract-zip@npm:^1.6.6": version: 1.7.0 resolution: "extract-zip@npm:1.7.0" @@ -10411,7 +10502,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": +"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -10713,6 +10804,29 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^9.2.19": + version: 9.2.19 + resolution: "inquirer@npm:9.2.19" + dependencies: + "@inquirer/figures": ^1.0.1 + "@ljharb/through": ^2.3.13 + ansi-escapes: ^4.3.2 + chalk: ^5.3.0 + cli-cursor: ^3.1.0 + cli-width: ^4.1.0 + external-editor: ^3.1.0 + lodash: ^4.17.21 + mute-stream: 1.0.0 + ora: ^5.4.1 + run-async: ^3.0.0 + rxjs: ^7.8.1 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wrap-ansi: ^6.2.0 + checksum: 2bcfbed4293e4f0af85c240e86b192bba5dae6a8df6d01d7cddf54b76d8df5dd5706d67cd2e5934f679973311ab74228f25fb0237a58d63e44add425e93da3a6 + languageName: node + linkType: hard + "interface-blockstore@npm:^2.0.2, interface-blockstore@npm:^2.0.3": version: 2.0.3 resolution: "interface-blockstore@npm:2.0.3" @@ -11429,6 +11543,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 + languageName: node + linkType: hard + "is-ip@npm:^2.0.0": version: 2.0.0 resolution: "is-ip@npm:2.0.0" @@ -12603,7 +12724,7 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:4.1.0": +"log-symbols@npm:4.1.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -13565,6 +13686,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + "mysql2@npm:^3.5.2": version: 3.5.2 resolution: "mysql2@npm:3.5.2" @@ -14198,6 +14326,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: ^4.1.0 + chalk: ^4.1.0 + cli-cursor: ^3.1.0 + cli-spinners: ^2.5.0 + is-interactive: ^1.0.0 + is-unicode-supported: ^0.1.0 + log-symbols: ^4.1.0 + strip-ansi: ^6.0.0 + wcwidth: ^1.0.1 + checksum: 28d476ee6c1049d68368c0dc922e7225e3b5600c3ede88fade8052837f9ed342625fdaa84a6209302587c8ddd9b664f71f0759833cbdb3a4cf81344057e63c63 + languageName: node + linkType: hard + "os-filter-obj@npm:^2.0.0": version: 2.0.0 resolution: "os-filter-obj@npm:2.0.0" @@ -14207,6 +14352,13 @@ __metadata: languageName: node linkType: hard +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + "ow@npm:^0.17.0": version: 0.17.0 resolution: "ow@npm:0.17.0" @@ -16027,6 +16179,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: ^5.1.0 + signal-exit: ^3.0.2 + checksum: f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 + languageName: node + linkType: hard + "retimer@npm:^2.0.0": version: 2.0.0 resolution: "retimer@npm:2.0.0" @@ -16143,6 +16305,7 @@ __metadata: html-loader: ^2.1.2 html-webpack-plugin: ^5.5.3 image-webpack-loader: ^7.0.1 + inquirer: ^9.2.19 ipfs-deploy: ^12.0.1 jsonwebtoken: ^9.0.1 jss: ^10.10.0 @@ -16192,6 +16355,13 @@ __metadata: languageName: unknown linkType: soft +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 280c03d5a88603f48103fc6fd69f07fb0c392a1e0d319c34ec96a2516030e07ba06f79231a563c78698b882649c2fc1fda601bc84705f57d50efcd1fa506cfc0 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -17609,6 +17779,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: ~1.0.2 + checksum: 902d7aceb74453ea02abbf58c203f4a8fc1cead89b60b31e354f74ed5b3fb09ea817f94fb310f884a5d16987dd9fa5a735412a7c2dd088dd3d415aa819ae3a28 + languageName: node + linkType: hard + "to-buffer@npm:^1.1.1": version: 1.1.1 resolution: "to-buffer@npm:1.1.1" @@ -18324,6 +18503,15 @@ __metadata: languageName: node linkType: hard +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 814e9d1ddcc9798f7377ffa448a5a3892232b9275ebb30a41b529607691c0491de47cba426e917a4d08ded3ee7e9ba2f3fe32e62ee3cd9c7d3bafb7754bd553c + languageName: node + linkType: hard + "web-encoding@npm:1.1.5": version: 1.1.5 resolution: "web-encoding@npm:1.1.5" @@ -18629,6 +18817,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: 6cd96a410161ff617b63581a08376f0cb9162375adeb7956e10c8cd397821f7eb2a6de24eb22a0b28401300bf228c86e50617cd568209b5f6775b93c97d2fe3a + languageName: node + linkType: hard + "wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0"