#!/usr/bin/env node
/**
* Command-line interface for Keet messaging.
* This script provides a command-line interface for interacting with the Keet messaging platform.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { parseArgs } from 'node:util';
// Get the directory name of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Configure logging
const logLevels = {
INFO: 'INFO',
ERROR: 'ERROR',
WARNING: 'WARNING'
};
function log(level, message) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level}] ${message}`);
}
// Import Keet dependencies or mock implementations
let KeetBackend, KeetCoreApi;
let KEET_AVAILABLE = false;
let useMock = false;
async function importDependencies() {
try {
// Try to import the real Keet dependencies
const backendModule = await import('@holepunchto/keet-backend');
const coreApiModule = await import('@holepunchto/keet-core-api');
// Try to get the exports
KeetBackend = backendModule.default || backendModule;
KeetCoreApi = coreApiModule.default || coreApiModule;
KEET_AVAILABLE = true;
log(logLevels.INFO, 'Successfully imported Keet dependencies');
} catch (error) {
log(logLevels.WARNING, `Failed to import Keet dependencies: ${error.message}`);
log(logLevels.WARNING, 'Using mock implementation');
// Import the mock implementation
const { KeetBackend: MockKeetBackend, KeetCoreApi: MockKeetCoreApi } = await import('./keet_messaging_mock_test.js');
KeetBackend = MockKeetBackend;
KeetCoreApi = MockKeetCoreApi;
KEET_AVAILABLE = true;
useMock = true;
}
}
/**
* Parse command-line arguments.
*/
function parseArguments() {
const options = {
command: { type: 'string' },
storage: { type: 'string' },
output: { type: 'string' },
'room-key': { type: 'string' },
message: { type: 'string' },
name: { type: 'string' },
mock: { type: 'boolean', default: false }
};
const { values } = parseArgs({ options });
// Force mock mode if specified
if (values.mock) {
useMock = true;
}
return values;
}
/**
* Get the current identity.
*/
async function getIdentity(backend, outputPath) {
try {
const identity = await backend.api.core.getIdentity();
if (outputPath) {
fs.writeFileSync(outputPath, JSON.stringify(identity || {}));
} else {
console.log(JSON.stringify(identity || {}));
}
return true;
} catch (error) {
log(logLevels.ERROR, `Failed to get identity: ${error.message}`);
return false;
}
}
/**
* Create a new identity.
*/
async function createIdentity(backend, name, outputPath) {
try {
// Generate a new identity
await backend.api.core.generateIdentity();
// Get the new identity
const identity = await backend.api.core.getIdentity();
// Set the display name if provided
if (name && identity) {
// TODO: Set the display name if the API supports it
}
if (outputPath) {
fs.writeFileSync(outputPath, JSON.stringify(identity || {}));
} else {
console.log(JSON.stringify(identity || {}));
}
return true;
} catch (error) {
log(logLevels.ERROR, `Failed to create identity: ${error.message}`);
return false;
}
}
/**
* Get recent rooms.
*/
async function getRecentRooms(backend, outputPath) {
try {
const rooms = await backend.api.core.getRecentRooms();
if (outputPath) {
fs.writeFileSync(outputPath, JSON.stringify(rooms || []));
} else {
console.log(JSON.stringify(rooms || []));
}
return true;
} catch (error) {
log(logLevels.ERROR, `Failed to get recent rooms: ${error.message}`);
return false;
}
}
/**
* Join a room.
*/
async function joinRoom(backend, roomKey) {
try {
// Extract the room key from the invite link if needed
if (roomKey.startsWith('pear://keet/')) {
roomKey = roomKey.replace('pear://keet/', '');
}
// Check if there's a pairRoom method available
if (backend.api.core.pairRoom) {
log(logLevels.INFO, 'Using pairRoom method to join the room...');
await backend.api.core.pairRoom(roomKey);
log(logLevels.INFO, 'Successfully paired with the room');
} else {
log(logLevels.WARNING, 'pairRoom method not available');
return false;
}
return true;
} catch (error) {
log(logLevels.ERROR, `Failed to join room: ${error.message}`);
return false;
}
}
/**
* Get messages from a room.
*/
async function getMessages(backend, roomKey, outputPath) {
try {
// Extract the room key from the invite link if needed
if (roomKey.startsWith('pear://keet/')) {
roomKey = roomKey.replace('pear://keet/', '');
}
const messages = await backend.api.core.getChatMessages(roomKey);
if (outputPath) {
fs.writeFileSync(outputPath, JSON.stringify(messages || []));
} else {
console.log(JSON.stringify(messages || []));
}
return true;
} catch (error) {
log(logLevels.ERROR, `Failed to get messages: ${error.message}`);
return false;
}
}
/**
* Send a message to a room.
*/
async function sendMessage(backend, roomKey, message) {
try {
// Extract the room key from the invite link if needed
if (roomKey.startsWith('pear://keet/')) {
roomKey = roomKey.replace('pear://keet/', '');
}
await backend.api.core.addChatMessage(roomKey, message);
log(logLevels.INFO, 'Message sent successfully');
return true;
} catch (error) {
log(logLevels.ERROR, `Failed to send message: ${error.message}`);
return false;
}
}
/**
* Main function.
*/
async function main() {
// Parse command-line arguments
const args = parseArguments();
// Import dependencies
await importDependencies();
if (!KEET_AVAILABLE) {
log(logLevels.ERROR, 'Keet dependencies not available');
return 1;
}
// Create a storage directory if it doesn't exist
const storagePath = args.storage || path.join(__dirname, '..', 'test-keet-storage');
if (!fs.existsSync(storagePath)) {
fs.mkdirSync(storagePath, { recursive: true });
}
// Initialize Keet backend
log(logLevels.INFO, 'Initializing Keet backend...');
const backend = new KeetBackend(storagePath, {
experimental: false,
devMirrors: false
});
// Wait for backend to be ready
await backend.ready();
let success = false;
// Execute the command
switch (args.command) {
case 'get-identity':
success = await getIdentity(backend, args.output);
break;
case 'create-identity':
success = await createIdentity(backend, args.name, args.output);
break;
case 'get-recent-rooms':
success = await getRecentRooms(backend, args.output);
break;
case 'join-room':
if (!args['room-key']) {
log(logLevels.ERROR, 'Room key is required');
success = false;
} else {
success = await joinRoom(backend, args['room-key']);
}
break;
case 'get-messages':
if (!args['room-key']) {
log(logLevels.ERROR, 'Room key is required');
success = false;
} else {
success = await getMessages(backend, args['room-key'], args.output);
}
break;
case 'send-message':
if (!args['room-key']) {
log(logLevels.ERROR, 'Room key is required');
success = false;
} else if (!args.message) {
log(logLevels.ERROR, 'Message is required');
success = false;
} else {
success = await sendMessage(backend, args['room-key'], args.message);
}
break;
default:
log(logLevels.ERROR, `Unknown command: ${args.command}`);
success = false;
}
// Close the backend
log(logLevels.INFO, 'Closing backend...');
await backend.close();
return success ? 0 : 1;
}
// Run the main function if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
try {
const exitCode = await main();
process.exit(exitCode);
} catch (error) {
log(logLevels.ERROR, `Unhandled error: ${error.message}`);
log(logLevels.ERROR, error.stack);
process.exit(1);
}
}