#!/usr/bin/env node
/**
* Wirecore CLI - Command-line interface for Wirecore
*
* Usage:
* fedicore init - Initialize Wirecore
* fedicore post "Hello world" - Post a message
* fedicore timeline - View timeline
* fedicore follow <publicKey> [name] - Follow a user
* fedicore unfollow <publicKey> - Unfollow a user
* fedicore profile - View your profile
* fedicore profile <publicKey> - View another user's profile
* fedicore whoami - Show your public key
*/
import { Wirecore } from "../core/wirecore.js"
import { stdin, stdout } from 'process'
import { createInterface } from 'readline'
const STORAGE_PATH = './data/cli'
// ANSI colors
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
gray: '\x1b[90m'
}
class WirecoreCLI {
constructor() {
this.fc = null
}
async run() {
const args = process.argv.slice(2)
const command = args[0]
if (!command || command === 'help') {
this.showHelp()
return
}
// Commands that don't require init
if (command === 'init') {
await this.init()
return
}
// All other commands require Wirecore to be initialized
this.fc = new Wirecore(STORAGE_PATH)
await this.fc.init()
try {
switch (command) {
case 'post':
await this.post(args.slice(1).join(' '))
break
case 'timeline':
await this.timeline()
break
case 'follow':
await this.follow(args[1], args[2])
break
case 'unfollow':
await this.unfollow(args[1])
break
case 'profile':
await this.profile(args[1])
break
case 'whoami':
await this.whoami()
break
case 'like':
await this.like(args[1], args[2])
break
case 'boost':
await this.boost(args[1], args[2])
break
case 'reply':
await this.reply(args[1], args[2], args.slice(3).join(' '))
break
case 'interactive':
case 'i':
await this.interactive()
break
default:
console.log(colors.yellow + 'Unknown command:', command, colors.reset)
this.showHelp()
}
} finally {
await this.fc.close()
}
}
showHelp() {
console.log(`
${colors.bright}Wirecore CLI${colors.reset} - Decentralized Social Network
${colors.cyan}Commands:${colors.reset}
init Initialize Wirecore
post <text> Post a message
timeline View your timeline
follow <publicKey> [name] Follow a user
unfollow <publicKey> Unfollow a user
profile [publicKey] View profile (yours or another user's)
whoami Show your public key
like <author> <sequence> Like a post
boost <author> <sequence> Boost a post
reply <author> <sequence> <text> Reply to a post
interactive Interactive mode
help Show this help
${colors.cyan}Examples:${colors.reset}
fedicore init
fedicore post "Hello decentralized world!"
fedicore follow BSY7/er4VJIu08o39NaRAiPY... Alice
fedicore timeline
fedicore interactive
${colors.gray}Learn more: https://github.com/your-repo/fedicore${colors.reset}
`)
}
async init() {
console.log(colors.bright + 'Initializing Wirecore...' + colors.reset)
const fc = new Wirecore(STORAGE_PATH)
await fc.init()
console.log(colors.green + '\n✓ Wirecore initialized!' + colors.reset)
console.log('\nYour public key:')
console.log(colors.cyan + fc.getPublicKey() + colors.reset)
console.log('\nShare this key with others so they can follow you!')
console.log('\nBackup your keypair:')
console.log(colors.yellow + fc.getKeypair() + colors.reset)
await fc.close()
}
async post(text) {
if (!text) {
console.log(colors.yellow + 'Please provide text to post' + colors.reset)
return
}
const message = await this.fc.post({ text })
console.log(colors.green + '✓ Posted!' + colors.reset)
console.log(`Sequence: ${message.sequence}`)
}
async timeline() {
console.log(colors.bright + 'Loading timeline...' + colors.reset + '\n')
const posts = await this.fc.getTimeline({ limit: 20 })
if (posts.length === 0) {
console.log(colors.gray + 'No posts yet. Follow some users!' + colors.reset)
return
}
for (const post of posts) {
this.printPost(post)
}
console.log(colors.dim + `\n(${posts.length} posts)` + colors.reset)
}
async follow(publicKey, name = 'Anonymous') {
if (!publicKey) {
console.log(colors.yellow + 'Please provide a public key' + colors.reset)
return
}
await this.fc.follow(publicKey, name)
console.log(colors.green + `✓ Now following ${name}` + colors.reset)
}
async unfollow(publicKey) {
if (!publicKey) {
console.log(colors.yellow + 'Please provide a public key' + colors.reset)
return
}
await this.fc.unfollow(publicKey)
console.log(colors.green + '✓ Unfollowed' + colors.reset)
}
async profile(publicKey) {
const key = publicKey || this.fc.getPublicKey()
const profile = await this.fc.getProfile(key)
console.log('\n' + colors.bright + profile.name + colors.reset)
console.log(colors.gray + profile.publicKey.substring(0, 16) + '...' + colors.reset)
if (profile.bio) {
console.log('\n' + profile.bio)
}
console.log('\n' + colors.dim + `${profile.posts} posts` + colors.reset + '\n')
}
async whoami() {
console.log('\nYour public key:')
console.log(colors.cyan + this.fc.getPublicKey() + colors.reset + '\n')
}
async like(author, sequence) {
if (!author || !sequence) {
console.log(colors.yellow + 'Usage: fedicore like <author> <sequence>' + colors.reset)
return
}
await this.fc.like(author, parseInt(sequence))
console.log(colors.green + '✓ Liked!' + colors.reset)
}
async boost(author, sequence) {
if (!author || !sequence) {
console.log(colors.yellow + 'Usage: fedicore boost <author> <sequence>' + colors.reset)
return
}
await this.fc.boost(author, parseInt(sequence))
console.log(colors.green + '✓ Boosted!' + colors.reset)
}
async reply(author, sequence, text) {
if (!author || !sequence || !text) {
console.log(colors.yellow + 'Usage: fedicore reply <author> <sequence> <text>' + colors.reset)
return
}
await this.fc.reply(author, parseInt(sequence), text)
console.log(colors.green + '✓ Replied!' + colors.reset)
}
async interactive() {
console.log(colors.bright + 'Wirecore Interactive Mode' + colors.reset)
console.log(colors.dim + 'Type "help" for commands, "exit" to quit\n' + colors.reset)
const rl = createInterface({
input: stdin,
output: stdout,
prompt: colors.cyan + 'fedicore> ' + colors.reset
})
rl.prompt()
rl.on('line', async (line) => {
const args = line.trim().split(' ')
const cmd = args[0]
try {
switch (cmd) {
case '':
break
case 'help':
console.log('Commands: post, timeline, follow, unfollow, profile, whoami, exit')
break
case 'post':
await this.post(args.slice(1).join(' '))
break
case 'timeline':
await this.timeline()
break
case 'follow':
await this.follow(args[1], args[2])
break
case 'unfollow':
await this.unfollow(args[1])
break
case 'profile':
await this.profile(args[1])
break
case 'whoami':
await this.whoami()
break
case 'exit':
case 'quit':
console.log(colors.green + 'Goodbye!' + colors.reset)
await this.fc.close()
rl.close()
return
default:
console.log(colors.yellow + 'Unknown command:', cmd, colors.reset)
}
} catch (err) {
console.error(colors.yellow + 'Error:', err.message, colors.reset)
}
rl.prompt()
})
rl.on('close', async () => {
await this.fc.close()
process.exit(0)
})
}
printPost(post) {
const authorShort = post.author.substring(0, 8) + '...'
const date = new Date(post.timestamp).toLocaleString()
const type = post.content.type
// Header
console.log(colors.cyan + authorShort + colors.reset +
colors.gray + ' • ' + date + colors.reset)
// Content
switch (type) {
case 'post':
console.log(post.content.text)
break
case 'reply':
console.log(colors.dim + 'Reply to ' + post.content.target.author.substring(0, 8) + '...' + colors.reset)
console.log(post.content.text)
break
case 'boost':
console.log(colors.green + '↻ Boosted ' + post.content.target.author.substring(0, 8) + '...' + colors.reset)
break
case 'like':
console.log(colors.magenta + '♥ Liked ' + post.content.target.author.substring(0, 8) + '...' + colors.reset)
break
}
console.log(colors.dim + `[seq: ${post.sequence}]` + colors.reset)
console.log('')
}
}
// Run CLI
const cli = new WirecoreCLI()
cli.run().catch(err => {
console.error(colors.yellow + 'Error:', err.message, colors.reset)
process.exit(1)
})