@pod-protocol/cli
Version:
Command-line interface for PoD Protocol (Prompt or Die) AI Agent Communication Protocol
318 lines (317 loc) • 11.9 kB
JavaScript
import ora from "ora";
import { table } from "table";
import { BRAND_COLORS, ICONS, keyValue, sectionHeader } from "./branding.js";
export class OutputFormatter {
constructor(options = {}) {
this.verbose = false;
this.quiet = false;
this.spinners = new Map();
this.verbose = options.verbose || false;
this.quiet = options.quiet || false;
}
/**
* Display a formatted table
*/
displayTable(data, columns) {
if (this.quiet || data.length === 0)
return;
const headers = columns.map(col => BRAND_COLORS.accent(col.label));
const rows = data.map(item => columns.map(col => {
const value = item[col.key];
return col.formatter ? col.formatter(value) : String(value || '');
}));
const tableConfig = {
header: {
alignment: 'center',
content: BRAND_COLORS.primary('Data Table')
},
columns: columns.map(col => ({
alignment: col.align || 'left',
width: col.width,
})),
border: {
topBody: BRAND_COLORS.muted('─'),
topJoin: BRAND_COLORS.muted('┬'),
topLeft: BRAND_COLORS.muted('┌'),
topRight: BRAND_COLORS.muted('┐'),
bottomBody: BRAND_COLORS.muted('─'),
bottomJoin: BRAND_COLORS.muted('┴'),
bottomLeft: BRAND_COLORS.muted('└'),
bottomRight: BRAND_COLORS.muted('┘'),
bodyLeft: BRAND_COLORS.muted('│'),
bodyRight: BRAND_COLORS.muted('│'),
bodyJoin: BRAND_COLORS.muted('│'),
joinBody: BRAND_COLORS.muted('─'),
joinLeft: BRAND_COLORS.muted('├'),
joinRight: BRAND_COLORS.muted('┤'),
joinJoin: BRAND_COLORS.muted('┼'),
}
};
console.log(table([headers, ...rows], tableConfig));
}
/**
* Display agent information in a formatted way
*/
displayAgentInfo(agent) {
if (this.quiet)
return;
console.log(sectionHeader('Agent Information', ICONS.agent));
console.log();
console.log(keyValue('Address', agent.pubkey?.toString() || 'Unknown', ICONS.key));
console.log(keyValue('Capabilities', this.formatCapabilities(agent.capabilities), ICONS.gear));
console.log(keyValue('Metadata URI', agent.metadataUri || 'None', ICONS.info));
console.log(keyValue('Reputation', agent.reputation || 0, ICONS.star));
console.log(keyValue('Last Updated', this.formatTimestamp(agent.lastUpdated), ICONS.bell));
if (this.verbose) {
console.log();
console.log(BRAND_COLORS.muted('Additional Details:'));
console.log(` Bump: ${agent.bump || 'N/A'}`);
}
console.log();
}
/**
* Display message information
*/
displayMessageInfo(message) {
if (this.quiet)
return;
console.log(sectionHeader('Message Information', ICONS.message));
console.log();
console.log(keyValue('Message ID', message.id || 'Unknown', ICONS.key));
console.log(keyValue('From', message.sender?.toString() || 'Unknown', ICONS.agent));
console.log(keyValue('To', message.recipient?.toString() || 'Unknown', ICONS.agent));
console.log(keyValue('Type', this.formatMessageType(message.messageType), ICONS.info));
console.log(keyValue('Status', this.formatMessageStatus(message.status), ICONS.bell));
console.log(keyValue('Timestamp', this.formatTimestamp(message.timestamp), ICONS.bell));
if (message.payload && message.payload.length > 0) {
console.log();
console.log(BRAND_COLORS.accent('Content:'));
console.log(this.formatMessageContent(message.payload));
}
console.log();
}
/**
* Display channel information
*/
displayChannelInfo(channel) {
if (this.quiet)
return;
console.log(sectionHeader('Channel Information', ICONS.channel));
console.log();
console.log(keyValue('Name', channel.name || 'Unknown', ICONS.channel));
console.log(keyValue('Description', channel.description || 'None', ICONS.info));
console.log(keyValue('Creator', channel.creator?.toString() || 'Unknown', ICONS.agent));
console.log(keyValue('Visibility', this.formatVisibility(channel.visibility), ICONS.shield));
console.log(keyValue('Participants', `${channel.participantCount || 0}/${channel.maxParticipants || 'Unlimited'}`, ICONS.agent));
console.log(keyValue('Fee per Message', this.formatSol(channel.feePerMessage), ICONS.escrow));
console.log();
}
/**
* Display escrow information
*/
displayEscrowInfo(escrow) {
if (this.quiet)
return;
console.log(sectionHeader('Escrow Information', ICONS.escrow));
console.log();
console.log(keyValue('Channel', escrow.channel?.toString() || 'Unknown', ICONS.channel));
console.log(keyValue('Depositor', escrow.depositor?.toString() || 'Unknown', ICONS.agent));
console.log(keyValue('Balance', this.formatSol(escrow.balance), ICONS.escrow));
console.log(keyValue('Last Deposit', this.formatTimestamp(escrow.lastDeposit), ICONS.bell));
console.log();
}
/**
* Display a progress indicator for multi-step operations
*/
displayProgress(steps) {
if (this.quiet)
return;
console.log(sectionHeader('Progress', ICONS.loading));
console.log();
steps.forEach((step, index) => {
const stepNumber = `[${index + 1}/${steps.length}]`;
let statusIcon = '';
let statusColor = BRAND_COLORS.muted;
switch (step.status) {
case 'completed':
statusIcon = ICONS.success;
statusColor = BRAND_COLORS.success;
break;
case 'running':
statusIcon = ICONS.loading;
statusColor = BRAND_COLORS.info;
break;
case 'failed':
statusIcon = ICONS.error;
statusColor = BRAND_COLORS.error;
break;
default:
statusIcon = '⭕';
statusColor = BRAND_COLORS.muted;
}
console.log(` ${statusColor(stepNumber)} ${statusIcon} ${step.message}`);
});
console.log();
}
/**
* Start a spinner with a message
*/
startSpinner(id, message) {
if (this.quiet)
return;
const spinner = ora({
text: message,
color: 'magenta',
spinner: 'dots'
});
spinner.start();
this.spinners.set(id, spinner);
}
/**
* Update spinner message
*/
updateSpinner(id, message) {
const spinner = this.spinners.get(id);
if (spinner) {
spinner.text = message;
}
}
/**
* Stop spinner with success
*/
succeedSpinner(id, message) {
const spinner = this.spinners.get(id);
if (spinner) {
spinner.succeed(message);
this.spinners.delete(id);
}
}
/**
* Stop spinner with failure
*/
failSpinner(id, message) {
const spinner = this.spinners.get(id);
if (spinner) {
spinner.fail(message);
this.spinners.delete(id);
}
}
/**
* Display a summary box
*/
displaySummary(title, items) {
if (this.quiet)
return;
console.log(sectionHeader(title, ICONS.info));
console.log();
const maxLabelLength = Math.max(...items.map(item => item.label.length));
items.forEach(item => {
const padding = ' '.repeat(maxLabelLength - item.label.length + 2);
const icon = item.icon ? `${item.icon} ` : '';
console.log(` ${icon}${BRAND_COLORS.accent(item.label)}:${padding}${BRAND_COLORS.secondary(item.value)}`);
});
console.log();
}
/**
* Display command help with examples
*/
displayCommandHelp(command, description, examples) {
console.log(sectionHeader(`${command} Command Help`, ICONS.info));
console.log();
console.log(description);
console.log();
if (examples.length > 0) {
console.log(BRAND_COLORS.accent('Examples:'));
console.log();
examples.forEach(example => {
console.log(` ${BRAND_COLORS.muted('$')} ${BRAND_COLORS.accent(example.command)}`);
console.log(` ${BRAND_COLORS.dim(example.description)}`);
console.log();
});
}
}
// Private helper methods
formatCapabilities(capabilities) {
if (!capabilities)
return 'None';
const capabilityNames = [];
if (capabilities & 1)
capabilityNames.push('Trading');
if (capabilities & 2)
capabilityNames.push('Analysis');
if (capabilities & 4)
capabilityNames.push('Data Processing');
if (capabilities & 8)
capabilityNames.push('Content Generation');
if (capabilities & 16)
capabilityNames.push('Communication');
if (capabilities & 32)
capabilityNames.push('Learning');
return capabilityNames.length > 0 ? capabilityNames.join(', ') : 'Custom';
}
formatMessageType(type) {
const typeMap = {
'text': 'Text Message',
'data': 'Data Transfer',
'command': 'Command',
'response': 'Response',
};
return typeMap[type] || String(type);
}
formatMessageStatus(status) {
const statusMap = {
'pending': { text: 'Pending', color: BRAND_COLORS.warning },
'sent': { text: 'Sent', color: BRAND_COLORS.success },
'delivered': { text: 'Delivered', color: BRAND_COLORS.success },
'failed': { text: 'Failed', color: BRAND_COLORS.error },
};
const mapped = statusMap[status] || { text: String(status), color: BRAND_COLORS.muted };
return mapped.color(mapped.text);
}
formatVisibility(visibility) {
const visibilityMap = {
'public': '🌐 Public',
'private': '🔒 Private',
'invite': '📨 Invite Only',
};
return visibilityMap[visibility] || String(visibility);
}
formatSol(lamports) {
if (!lamports)
return '0 SOL';
const sol = Number(lamports) / 1e9;
return `${sol.toFixed(4)} SOL`;
}
formatTimestamp(timestamp) {
if (!timestamp)
return 'Unknown';
try {
const date = new Date(Number(timestamp) * 1000);
return date.toLocaleString();
}
catch {
return 'Invalid date';
}
}
formatMessageContent(payload) {
if (!payload)
return BRAND_COLORS.muted('(empty)');
// Try to parse as JSON for pretty formatting
try {
const parsed = JSON.parse(payload);
return BRAND_COLORS.dim(JSON.stringify(parsed, null, 2));
}
catch {
// Display as plain text, truncated if too long
const maxLength = this.verbose ? 500 : 100;
if (payload.length > maxLength) {
return `${payload.substring(0, maxLength)}${BRAND_COLORS.muted('...')}`;
}
return payload;
}
}
}
/**
* Global output formatter instance
*/
export const outputFormatter = new OutputFormatter();