Skip to content

Commit c5766ba

Browse files
committed
feat: add cli tool to interact with nano community API
1 parent 86d49a6 commit c5766ba

File tree

3 files changed

+425
-4
lines changed

3 files changed

+425
-4
lines changed

cli/index.mjs

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env node
2+
3+
import yargs from 'yargs'
4+
import crypto from 'crypto'
5+
import { hideBin } from 'yargs/helpers'
6+
import inquirer from 'inquirer'
7+
import process from 'process'
8+
import nano from 'nanocurrency'
9+
10+
import {
11+
sign_nano_community_link_key,
12+
sign_nano_community_revoke_key,
13+
sign_nano_community_message
14+
} from '#common'
15+
import ed25519 from '@trashman/ed25519-blake2b'
16+
17+
const load_private_key = async () => {
18+
let private_key = process.env.NANO_PRIVATE_KEY
19+
if (private_key) {
20+
console.log('Private key found in environment variable.')
21+
} else {
22+
console.log('No private key found in environment variable or stdin.')
23+
// Restore stdin for inquirer
24+
const answers = await inquirer.prompt([
25+
{
26+
type: 'password',
27+
name: 'private_key',
28+
message: 'Please enter your private key:'
29+
}
30+
])
31+
private_key = answers.private_key
32+
}
33+
34+
const public_key = nano.derivePublicKey(private_key)
35+
const nano_account_address = nano.deriveAddress(public_key)
36+
return {
37+
private_key,
38+
public_key,
39+
nano_account_address
40+
}
41+
}
42+
43+
const add_signing_key = {
44+
command: 'add-signing-key',
45+
describe: 'Add a new signing key',
46+
handler: async () => {
47+
const { private_key, public_key, nano_account_address } =
48+
await load_private_key()
49+
50+
const linked_private_key = crypto.randomBytes(32)
51+
const linked_public_key = ed25519.publicKey(linked_private_key)
52+
53+
const signature = sign_nano_community_link_key({
54+
linked_public_key,
55+
nano_account: nano_account_address,
56+
nano_account_private_key: private_key,
57+
nano_account_public_key: public_key
58+
})
59+
60+
console.log({
61+
private_key,
62+
public_key,
63+
nano_account_address,
64+
signature,
65+
linked_public_key,
66+
linked_private_key
67+
})
68+
69+
// TODO send signed message to API
70+
// TODO print out the linked public key and private key
71+
}
72+
}
73+
74+
const revoke_signing_key = {
75+
command: 'revoke-signing-key <linked_public_key>',
76+
describe: 'Revoke an existing signing key',
77+
builder: (yargs) =>
78+
yargs.positional('linked_public_key', {
79+
describe: 'Public key of the signing key to revoke',
80+
type: 'string'
81+
}),
82+
handler: async ({ linked_public_key }) => {
83+
const { private_key, public_key, nano_account_address } =
84+
await load_private_key()
85+
86+
// Confirm revocation
87+
const answers = await inquirer.prompt([
88+
{
89+
type: 'confirm',
90+
name: 'confirm_revoke',
91+
message: `Are you sure you want to revoke the signing key: ${public_key}?`,
92+
default: false
93+
}
94+
])
95+
96+
if (answers.confirm_revoke) {
97+
console.log('Revoking signing key...')
98+
const signature = sign_nano_community_revoke_key({
99+
linked_public_key: public_key,
100+
nano_account: nano_account_address,
101+
nano_account_private_key: private_key,
102+
nano_account_public_key: public_key
103+
})
104+
105+
console.log({
106+
signature,
107+
linked_public_key: public_key,
108+
nano_account_address,
109+
private_key,
110+
public_key
111+
})
112+
113+
// TODO send signed message to API
114+
} else {
115+
console.log('Signing key revocation cancelled.')
116+
}
117+
}
118+
}
119+
120+
const send_message = {
121+
command: 'send-message <type>',
122+
describe: 'Send a message',
123+
builder: (yargs) =>
124+
yargs.positional('type', {
125+
describe: 'Type of message to send',
126+
choices: ['update-rep-meta', 'update-account-meta', 'update-block-meta']
127+
}),
128+
handler: async (argv) => {
129+
const { private_key, public_key } = await load_private_key()
130+
131+
// TODO fetch current values from API
132+
133+
let message_content_prompts = []
134+
console.log(`Sending message of type: ${argv.type}`)
135+
switch (argv.type) {
136+
case 'update-rep-meta':
137+
message_content_prompts = [
138+
{ name: 'alias', message: 'Alias:' },
139+
{ name: 'description', message: 'Description:' },
140+
{ name: 'donation_address', message: 'Donation Address:' },
141+
{ name: 'cpu_model', message: 'CPU Model:' },
142+
{ name: 'cpu_cores', message: 'CPU Cores:' },
143+
{ name: 'ram_amount', message: 'RAM Amount:' },
144+
{ name: 'reddit_username', message: 'Reddit Username:' },
145+
{ name: 'twitter_username', message: 'Twitter Username:' },
146+
{ name: 'discord_username', message: 'Discord Username:' },
147+
{ name: 'github_username', message: 'GitHub Username:' },
148+
{ name: 'email', message: 'Email:' },
149+
{ name: 'website_url', message: 'Website URL:' }
150+
]
151+
break
152+
case 'update-account-meta':
153+
message_content_prompts = [{ name: 'alias', message: 'Alias:' }]
154+
break
155+
case 'update-block-meta':
156+
message_content_prompts = [{ name: 'note', message: 'Note:' }]
157+
break
158+
default:
159+
console.error('Unknown message type')
160+
return
161+
}
162+
163+
const message_content = await inquirer.prompt(message_content_prompts)
164+
let confirm_edit = false
165+
do {
166+
console.log('Please review your message content:', message_content)
167+
confirm_edit = await inquirer.prompt([
168+
{
169+
type: 'confirm',
170+
name: 'edit',
171+
message: 'Would you like to edit any field?',
172+
default: false
173+
}
174+
])
175+
confirm_edit = confirm_edit.edit
176+
if (confirm_edit) {
177+
const field_to_edit = await inquirer.prompt([
178+
{
179+
type: 'list',
180+
name: 'field',
181+
message: 'Which field would you like to edit?',
182+
choices: message_content_prompts.map((prompt) => prompt.name)
183+
}
184+
])
185+
const new_value = await inquirer.prompt([
186+
{
187+
name: 'new_value',
188+
message: `Enter new value for ${field_to_edit.field}:`
189+
}
190+
])
191+
message_content[field_to_edit.field] = new_value.new_value
192+
}
193+
} while (confirm_edit)
194+
195+
const message = {
196+
created_at: Math.floor(Date.now() / 1000),
197+
public_key, // public key of signing key
198+
operation: argv.type.replace('-', '_'),
199+
content: message_content
200+
}
201+
202+
const signature = sign_nano_community_message(message, private_key)
203+
204+
console.log({
205+
message,
206+
signature
207+
})
208+
209+
// TODO send signed message to API
210+
}
211+
}
212+
213+
// eslint-disable-next-line no-unused-expressions
214+
yargs(hideBin(process.argv))
215+
.scriptName('nano-cli')
216+
.usage('$0 <cmd> [args]')
217+
.command(add_signing_key)
218+
.command(revoke_signing_key)
219+
.command(send_message)
220+
.help('h')
221+
.alias('h', 'help').argv

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"fetch-cheerio-object": "^1.3.0",
102102
"front-matter": "^4.0.2",
103103
"fs-extra": "^11.1.1",
104+
"inquirer": "^9.2.19",
104105
"jsonwebtoken": "^9.0.1",
105106
"knex": "^0.95.15",
106107
"markdown-it": "^12.3.2",

0 commit comments

Comments
 (0)