@ghostspeak/cli
Version:
Command-line interface for GhostSpeak AI Agent Commerce Protocol - Production Ready Beta
1,480 lines (1,473 loc) • 349 kB
JavaScript
#!/usr/bin/env node
import { createRequire } from 'module';
import { config } from 'dotenv';
import path, { join, dirname, resolve } from 'path';
import { existsSync, readFileSync, promises, writeFileSync, chmodSync, mkdirSync } from 'fs';
import { URL, fileURLToPath } from 'url';
import { Keypair, PublicKey } from '@solana/web3.js';
import os, { homedir } from 'os';
import { address } from '@solana/addresses';
import { createSolanaRpc, createKeyPairSignerFromBytes, createSolanaRpcSubscriptions, address as address$1, getProgramDerivedAddress, getBytesEncoder, getAddressEncoder, addEncoderSizePrefix, getUtf8Encoder, getU32Encoder, generateKeyPairSigner } from '@solana/kit';
import { Command, program } from 'commander';
import chalk3 from 'chalk';
import figlet from 'figlet';
import { intro, isCancel, cancel, spinner, text, outro, multiselect, select, confirm, log, note } from '@clack/prompts';
import { WorkOrderStatus, NATIVE_MINT_ADDRESS, AuctionType, AuctionStatus, DisputeStatus, deriveMultisigPda as deriveMultisigPda$1, deriveProposalPda, ProposalType, ProposalStatus, GHOSTSPEAK_PROGRAM_ID, GhostSpeakClient } from '@ghostspeak/sdk';
import { randomUUID, scrypt, randomBytes, createCipheriv, createDecipheriv } from 'crypto';
import fs2, { chmod, access, mkdir, writeFile, readFile } from 'fs/promises';
import fetch2 from 'node-fetch';
import pc from 'picocolors';
import { exec } from 'child_process';
import { promisify } from 'util';
createRequire(import.meta.url);
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
function loadEnvFiles() {
const envLocations = [
// 1. Current working directory
resolve(process.cwd(), ".env"),
// 2. Project root (two levels up from CLI package)
resolve(process.cwd(), "../../.env"),
// 3. CLI package directory (same as this file)
(() => {
try {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
return resolve(__dirname, "../../.env");
} catch {
return "";
}
})(),
// 4. Parent of CLI package directory
(() => {
try {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
return resolve(__dirname, "../../../.env");
} catch {
return "";
}
})()
].filter(Boolean);
for (const envPath of envLocations) {
if (existsSync(envPath)) {
config({ path: envPath });
console.debug(`Loaded environment from: ${envPath}`);
break;
}
}
}
function getCurrentNetwork() {
const network = process.env.GHOSTSPEAK_NETWORK ?? "devnet";
if (!["mainnet-beta", "devnet", "testnet", "localnet"].includes(network)) {
throw new Error(`Invalid network: ${network}`);
}
return network;
}
function getProgramId() {
const network = getCurrentNetwork();
let programIdStr;
switch (network) {
case "mainnet-beta":
programIdStr = process.env.GHOSTSPEAK_PROGRAM_ID_MAINNET;
break;
case "devnet":
programIdStr = process.env.GHOSTSPEAK_PROGRAM_ID_DEVNET;
break;
case "testnet":
programIdStr = process.env.GHOSTSPEAK_PROGRAM_ID_TESTNET;
break;
case "localnet":
programIdStr = process.env.GHOSTSPEAK_PROGRAM_ID_LOCALNET;
break;
}
if (!programIdStr) {
throw new Error(`Program ID not configured for network: ${network}`);
}
try {
return new PublicKey(programIdStr);
} catch {
throw new Error(`Invalid program ID for ${network}: ${programIdStr}`);
}
}
function getUsdcMint() {
const network = getCurrentNetwork();
let mintStr;
switch (network) {
case "mainnet-beta":
mintStr = process.env.GHOSTSPEAK_USDC_MINT_MAINNET ?? "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
break;
case "devnet":
mintStr = process.env.GHOSTSPEAK_USDC_MINT_DEVNET ?? "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
break;
case "testnet":
case "localnet":
mintStr = process.env.GHOSTSPEAK_USDC_MINT_TESTNET ?? "11111111111111111111111111111111";
break;
}
return new PublicKey(mintStr);
}
function loadEnvironmentConfig() {
const network = getCurrentNetwork();
return {
network,
rpcUrl: process.env.GHOSTSPEAK_RPC_URL ?? getDefaultRpcUrl(network),
programId: getProgramId(),
walletPath: process.env.GHOSTSPEAK_WALLET_PATH ?? "~/.config/solana/ghostspeak.json",
usdcMint: getUsdcMint(),
debug: process.env.GHOSTSPEAK_DEBUG === "true",
logLevel: process.env.GHOSTSPEAK_LOG_LEVEL ?? "info",
encryptionSalt: process.env.GHOSTSPEAK_ENCRYPTION_SALT,
keyDerivationIterations: parseInt(process.env.GHOSTSPEAK_KEY_DERIVATION_ITERATIONS ?? "100000", 10)
};
}
function getDefaultRpcUrl(network) {
switch (network) {
case "mainnet-beta":
return "https://api.mainnet-beta.solana.com";
case "devnet":
return "https://api.devnet.solana.com";
case "testnet":
return "https://api.testnet.solana.com";
case "localnet":
return "http://localhost:8899";
default:
return "https://api.devnet.solana.com";
}
}
var envConfig;
var init_env_config = __esm({
"src/utils/env-config.ts"() {
loadEnvFiles();
envConfig = loadEnvironmentConfig();
}
});
// src/utils/config.ts
var config_exports = {};
__export(config_exports, {
ensureConfigDir: () => ensureConfigDir,
getConfigPath: () => getConfigPath,
loadConfig: () => loadConfig,
resetConfig: () => resetConfig,
saveConfig: () => saveConfig
});
function ensureConfigDir() {
if (!existsSync(CONFIG_DIR)) {
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
}
}
function loadConfig() {
ensureConfigDir();
if (!existsSync(CONFIG_FILE)) {
return DEFAULT_CONFIG;
}
try {
const configData = readFileSync(CONFIG_FILE, "utf-8");
const config2 = JSON.parse(configData);
return { ...DEFAULT_CONFIG, ...config2 };
} catch (error) {
console.error("Error loading config:", error);
return DEFAULT_CONFIG;
}
}
function saveConfig(config2) {
ensureConfigDir();
const currentConfig = loadConfig();
const newConfig = { ...currentConfig, ...config2 };
writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
chmodSync(CONFIG_FILE, 384);
}
function resetConfig() {
ensureConfigDir();
if (existsSync(CONFIG_FILE)) {
writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2));
chmodSync(CONFIG_FILE, 384);
}
}
function getConfigPath() {
return CONFIG_FILE;
}
var CONFIG_DIR, CONFIG_FILE, DEFAULT_CONFIG;
var init_config = __esm({
"src/utils/config.ts"() {
init_env_config();
CONFIG_DIR = join(homedir(), ".ghostspeak");
CONFIG_FILE = join(CONFIG_DIR, "config.json");
DEFAULT_CONFIG = {
network: envConfig.network,
walletPath: envConfig.walletPath.startsWith("~") ? join(homedir(), envConfig.walletPath.slice(2)) : envConfig.walletPath,
programId: envConfig.programId.toString(),
rpcUrl: envConfig.rpcUrl
};
}
});
// src/utils/pda.ts
var pda_exports = {};
__export(pda_exports, {
calculateDeadline: () => calculateDeadline,
deriveAgentPda: () => deriveAgentPda,
deriveJobApplicationPda: () => deriveJobApplicationPda,
deriveJobContractPda: () => deriveJobContractPda,
deriveJobPostingPda: () => deriveJobPostingPda,
deriveMultisigPda: () => deriveMultisigPda,
deriveServiceListingPda: () => deriveServiceListingPda,
deriveServicePurchasePda: () => deriveServicePurchasePda,
deriveUserRegistryPda: () => deriveUserRegistryPda,
deriveWorkOrderPda: () => deriveWorkOrderPda,
generateUniqueId: () => generateUniqueId,
getDefaultPaymentToken: () => getDefaultPaymentToken,
getUSDCMintDevnet: () => getUSDCMintDevnet,
solToLamports: () => solToLamports
});
async function deriveAgentPda(programId, owner, agentId) {
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([97, 103, 101, 110, 116])),
// 'agent'
getAddressEncoder().encode(owner),
addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder()).encode(agentId)
]
});
return address13;
}
async function deriveServiceListingPda(programId, creator, listingId) {
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
115,
101,
114,
118,
105,
99,
101,
95,
108,
105,
115,
116,
105,
110,
103
])),
// 'service_listing'
getAddressEncoder().encode(creator),
getUtf8Encoder().encode(listingId)
]
});
return address13;
}
async function deriveJobPostingPda(programId, employer, jobId) {
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
106,
111,
98,
95,
112,
111,
115,
116,
105,
110,
103
])),
// 'job_posting'
getAddressEncoder().encode(employer),
getUtf8Encoder().encode(jobId)
]
});
return address13;
}
async function deriveJobApplicationPda(programId, jobPosting, applicant) {
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
106,
111,
98,
95,
97,
112,
112,
108,
105,
99,
97,
116,
105,
111,
110
])),
// 'job_application'
getAddressEncoder().encode(jobPosting),
getAddressEncoder().encode(applicant)
]
});
return address13;
}
async function deriveWorkOrderPda(programId, client, orderId) {
const orderIdBytes = new Uint8Array(8);
const dataView = new DataView(orderIdBytes.buffer);
dataView.setBigUint64(0, orderId, true);
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
119,
111,
114,
107,
95,
111,
114,
100,
101,
114
])),
// 'work_order'
getAddressEncoder().encode(client),
orderIdBytes
]
});
return address13;
}
async function deriveUserRegistryPda(programId, signer) {
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
117,
115,
101,
114,
95,
114,
101,
103,
105,
115,
116,
114,
121
])),
// 'user_registry'
getAddressEncoder().encode(signer)
]
});
return address13;
}
async function deriveServicePurchasePda(programId, serviceListing, buyer) {
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
115,
101,
114,
118,
105,
99,
101,
95,
112,
117,
114,
99,
104,
97,
115,
101
])),
// 'service_purchase'
getAddressEncoder().encode(serviceListing),
getAddressEncoder().encode(buyer)
]
});
return address13;
}
async function deriveJobContractPda(programId, jobPosting, agent) {
const [address13] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
106,
111,
98,
95,
99,
111,
110,
116,
114,
97,
99,
116
])),
// 'job_contract'
getAddressEncoder().encode(jobPosting),
getAddressEncoder().encode(agent)
]
});
return address13;
}
function generateUniqueId(prefix = "") {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 9);
return prefix ? `${prefix}-${timestamp}-${random}` : `${timestamp}-${random}`;
}
function solToLamports(sol) {
const amount = typeof sol === "string" ? parseFloat(sol) : sol;
return BigInt(Math.floor(amount * 1e9));
}
function getDefaultPaymentToken() {
return address("So11111111111111111111111111111111111111112");
}
function getUSDCMintDevnet() {
return address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
}
function calculateDeadline(days) {
return BigInt(Math.floor(Date.now() / 1e3) + days * 24 * 60 * 60);
}
async function deriveMultisigPda(programId, owner, multisigId) {
const multisigIdBytes = new Uint8Array(8);
const dataView = new DataView(multisigIdBytes.buffer);
dataView.setBigUint64(0, multisigId, true);
const [pda] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
getBytesEncoder().encode(new Uint8Array([
109,
117,
108,
116,
105,
115,
105,
103
])),
// 'multisig'
getAddressEncoder().encode(address(owner)),
multisigIdBytes
]
});
return pda;
}
var init_pda = __esm({
"src/utils/pda.ts"() {
}
});
async function registerAgentPrompts(options) {
const name = options.name ?? await text({
message: "What is your agent's name?",
placeholder: "e.g., DataAnalyzer Pro",
validate: (value) => {
if (!value) return "Agent name is required";
if (value.length < 3) return "Agent name must be at least 3 characters";
if (value.length > 50) return "Agent name must be less than 50 characters";
}
});
if (isCancel(name)) {
cancel("Agent registration cancelled");
process.exit(0);
}
const description = options.description ?? await text({
message: "Describe what your agent does:",
placeholder: "e.g., Analyzes data and generates insights for businesses",
validate: (value) => {
if (!value) return "Description is required";
if (value.length < 10) return "Description must be at least 10 characters";
if (value.length > 500) return "Description must be less than 500 characters";
}
});
if (isCancel(description)) {
cancel("Agent registration cancelled");
process.exit(0);
}
const capabilities = await multiselect({
message: "Select your agent's capabilities:",
options: [
{ value: "data-analysis", label: "\u{1F4CA} Data Analysis & Insights" },
{ value: "writing", label: "\u270D\uFE0F Content Writing & Editing" },
{ value: "coding", label: "\u{1F4BB} Programming & Development" },
{ value: "translation", label: "\u{1F310} Language Translation" },
{ value: "image-processing", label: "\u{1F5BC}\uFE0F Image Processing & AI Vision" },
{ value: "automation", label: "\u{1F916} Task Automation & Workflows" },
{ value: "research", label: "\u{1F50D} Research & Information Gathering" },
{ value: "customer-service", label: "\u{1F3A7} Customer Service & Support" },
{ value: "financial-analysis", label: "\u{1F4B0} Financial Analysis & Trading" },
{ value: "content-moderation", label: "\u{1F6E1}\uFE0F Content Moderation" }
],
required: true
});
if (isCancel(capabilities)) {
cancel("Agent registration cancelled");
process.exit(0);
}
const serviceEndpoint = options.endpoint ?? await text({
message: "Enter your agent's service endpoint URL:",
placeholder: "https://api.your-agent.com/v1",
validate: (value) => {
if (!value) return "Service endpoint is required";
try {
new URL(value);
return void 0;
} catch {
return "Please enter a valid URL";
}
}
});
if (isCancel(serviceEndpoint)) {
cancel("Agent registration cancelled");
process.exit(0);
}
const hasMetadata = await confirm({
message: "Do you have additional metadata to link? (JSON file with detailed specs)"
});
if (isCancel(hasMetadata)) {
cancel("Agent registration cancelled");
process.exit(0);
}
let metadataUri = "";
if (hasMetadata) {
const uri = await text({
message: "Enter metadata URI:",
placeholder: "https://your-site.com/agent-metadata.json",
validate: (value) => {
if (!value) return "Metadata URI is required when metadata is enabled";
try {
new URL(value);
return void 0;
} catch {
return "Please enter a valid URL";
}
}
});
if (isCancel(uri)) {
cancel("Agent registration cancelled");
process.exit(0);
}
metadataUri = uri;
}
console.log("\n" + chalk3.bold("\u{1F4CB} Registration Summary:"));
console.log("\u2500".repeat(40));
console.log(chalk3.cyan("Name:") + ` ${name}`);
console.log(chalk3.cyan("Description:") + ` ${description}`);
console.log(chalk3.cyan("Capabilities:") + ` ${capabilities.join(", ")}`);
console.log(chalk3.cyan("Endpoint:") + ` ${serviceEndpoint}`);
if (metadataUri) {
console.log(chalk3.cyan("Metadata:") + ` ${metadataUri}`);
}
const confirmed = await confirm({
message: "Register this agent on the blockchain?"
});
if (isCancel(confirmed) || !confirmed) {
cancel("Agent registration cancelled");
process.exit(0);
}
return {
name,
description,
capabilities,
metadataUri,
serviceEndpoint
};
}
// src/utils/client.ts
init_config();
async function getWallet() {
const config2 = loadConfig();
if (config2.walletPath && existsSync(config2.walletPath)) {
try {
const walletData = JSON.parse(readFileSync(config2.walletPath, "utf-8"));
return await createKeyPairSignerFromBytes(new Uint8Array(walletData));
} catch (error) {
log.warn(`Failed to load wallet from config: ${config2.walletPath}`);
}
}
const walletPath = join(homedir(), ".config", "solana", "ghostspeak-cli.json");
if (existsSync(walletPath)) {
try {
const walletData = JSON.parse(readFileSync(walletPath, "utf-8"));
return await createKeyPairSignerFromBytes(new Uint8Array(walletData));
} catch (error) {
log.warn("Failed to load GhostSpeak CLI wallet");
}
}
const defaultWalletPath = join(homedir(), ".config", "solana", "id.json");
if (existsSync(defaultWalletPath)) {
try {
const walletData = JSON.parse(readFileSync(defaultWalletPath, "utf-8"));
return await createKeyPairSignerFromBytes(new Uint8Array(walletData));
} catch (error) {
log.warn("Failed to load default Solana wallet");
}
}
const newWallet = crypto.getRandomValues(new Uint8Array(64));
const signer = await createKeyPairSignerFromBytes(newWallet);
writeFileSync(walletPath, JSON.stringify([...newWallet]));
log.info(`Created new wallet: ${signer.address}`);
log.warn(`Please configure your wallet using: gs config setup`);
log.warn(`Or request SOL from faucet using: gs faucet --save`);
return signer;
}
function toSDKSigner(signer) {
return signer;
}
async function initializeClient(network) {
const config2 = loadConfig();
const selectedNetwork = network ?? config2.network ?? "devnet";
let rpcUrl = config2.rpcUrl;
if (!rpcUrl) {
switch (selectedNetwork) {
case "mainnet-beta":
rpcUrl = "https://api.mainnet-beta.solana.com";
break;
case "testnet":
rpcUrl = "https://api.testnet.solana.com";
break;
case "localnet":
rpcUrl = "http://localhost:8899";
break;
case "devnet":
default:
rpcUrl = "https://api.devnet.solana.com";
break;
}
}
if (!rpcUrl || typeof rpcUrl !== "string") {
throw new Error("Invalid RPC URL configuration");
}
try {
new URL(rpcUrl);
} catch {
throw new Error(`Invalid RPC endpoint URL: ${rpcUrl}`);
}
const rpc = createSolanaRpc(rpcUrl);
let rpcSubscriptions;
try {
const wsUrl = rpcUrl.replace("https://", "wss://").replace("http://", "ws://").replace("api.devnet", "api.devnet").replace("api.testnet", "api.testnet").replace("api.mainnet-beta", "api.mainnet-beta");
rpcSubscriptions = createSolanaRpcSubscriptions(wsUrl);
} catch {
console.warn("Warning: Could not create RPC subscriptions, transaction confirmations may be slower");
}
const wallet = await getWallet();
const programId = config2.programId || GHOSTSPEAK_PROGRAM_ID;
const extendedRpc = rpc;
const client = new GhostSpeakClient({
rpc: extendedRpc,
rpcSubscriptions: rpcSubscriptions ?? void 0,
programId: address$1(programId),
defaultFeePayer: wallet.address,
commitment: "confirmed",
cluster: selectedNetwork === "mainnet-beta" ? "mainnet-beta" : selectedNetwork,
rpcEndpoint: rpcUrl
});
try {
if (!wallet || !wallet.address) {
log.warn(chalk3.yellow("\u26A0\uFE0F Wallet loaded but address not available"));
} else {
const balanceResponse = await rpc.getBalance(wallet.address).send();
const balance = balanceResponse.value;
if (balance === 0n) {
log.warn(chalk3.yellow("\u26A0\uFE0F Your wallet has 0 SOL. You need SOL to perform transactions."));
log.info(chalk3.dim("Run: npx ghostspeak faucet --save"));
}
}
} catch (error) {
console.warn("Balance check failed:", error instanceof Error ? error.message : "Unknown error");
}
const originalClient = client;
const enhancedClient = {
...originalClient,
cleanup: async () => {
try {
if (rpcSubscriptions) {
if ("close" in rpcSubscriptions && typeof rpcSubscriptions.close === "function") {
const closeMethod = rpcSubscriptions.close;
await closeMethod.call(rpcSubscriptions);
}
}
} catch (error) {
console.debug("Client cleanup warning:", error instanceof Error ? error.message : "Unknown error");
}
}
};
return {
// @ts-expect-error Client type includes our cleanup method
client: enhancedClient,
wallet,
rpc
};
}
function getExplorerUrl(signature, network = "devnet") {
const cluster = network === "mainnet-beta" ? "" : `?cluster=${network}`;
return `https://explorer.solana.com/tx/${signature}${cluster}`;
}
function getAddressExplorerUrl(address13, network = "devnet") {
const cluster = network === "mainnet-beta" ? "" : `?cluster=${network}`;
return `https://explorer.solana.com/address/${address13}${cluster}`;
}
function handleTransactionError(error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes("insufficient funds")) {
return "Insufficient SOL balance. Run: npx ghostspeak faucet --save";
}
if (message.includes("blockhash not found")) {
return "Transaction expired. Please try again.";
}
if (message.includes("already in use")) {
return "Account already exists. Try a different ID.";
}
return message || "Transaction failed";
}
// src/utils/secure-storage.ts
init_env_config();
var SecureStorage = class {
static ALGORITHM = "aes-256-gcm";
static KEY_LENGTH = 32;
static SALT_LENGTH = 32;
static IV_LENGTH = 16;
static TAG_LENGTH = 16;
static VERSION = 1;
static STORAGE_DIR = ".ghostspeak/secure";
/**
* Get the secure storage directory path
*/
static getStorageDir() {
return resolve(homedir(), this.STORAGE_DIR);
}
/**
* Get the full path for a storage key
*/
static getStoragePath(key) {
return resolve(this.getStorageDir(), `${key}.enc`);
}
/**
* Derive encryption key from password and salt using scrypt
*/
static async deriveKey(password, salt) {
return new Promise((resolve3, reject) => {
const iterations = envConfig.keyDerivationIterations || 1e5;
scrypt(password, salt, this.KEY_LENGTH, { N: iterations }, (err, derivedKey) => {
if (err) reject(err);
else resolve3(derivedKey);
});
});
}
/**
* Ensure storage directory exists with proper permissions
*/
static async ensureStorageDir() {
const dir = this.getStorageDir();
try {
await access(dir);
} catch {
await mkdir(dir, { recursive: true, mode: 448 });
}
await chmod(dir, 448);
}
/**
* Encrypt data with password
*/
static async encrypt(data, password) {
const salt = randomBytes(this.SALT_LENGTH);
const iv = randomBytes(this.IV_LENGTH);
const key = await this.deriveKey(password, salt);
const cipher = createCipheriv(this.ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(data, "utf8"),
cipher.final()
]);
const authTag = cipher.getAuthTag();
const combined = Buffer.concat([encrypted, authTag]);
return {
iv: iv.toString("hex"),
salt: salt.toString("hex"),
encrypted: combined.toString("hex"),
version: this.VERSION
};
}
/**
* Decrypt data with password
*/
static async decrypt(encryptedData, password) {
if (encryptedData.version !== this.VERSION) {
throw new Error(`Unsupported encryption version: ${encryptedData.version}`);
}
const salt = Buffer.from(encryptedData.salt, "hex");
const iv = Buffer.from(encryptedData.iv, "hex");
const combined = Buffer.from(encryptedData.encrypted, "hex");
const encrypted = combined.subarray(0, combined.length - this.TAG_LENGTH);
const authTag = combined.subarray(combined.length - this.TAG_LENGTH);
const key = await this.deriveKey(password, salt);
const decipher = createDecipheriv(this.ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
try {
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
return decrypted.toString("utf8");
} catch {
throw new Error("Failed to decrypt: Invalid password or corrupted data");
}
}
/**
* Store encrypted data
*/
static async store(key, data, password) {
await this.ensureStorageDir();
const encrypted = await this.encrypt(data, password);
const path3 = this.getStoragePath(key);
await writeFile(path3, JSON.stringify(encrypted, null, 2), "utf8");
await chmod(path3, 384);
}
/**
* Retrieve and decrypt data
*/
static async retrieve(key, password) {
const path3 = this.getStoragePath(key);
try {
const content = await readFile(path3, "utf8");
const encrypted = JSON.parse(content);
return await this.decrypt(encrypted, password);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
throw new Error(`No data found for key: ${key}`);
}
throw error;
}
}
/**
* Check if a key exists
*/
static async exists(key) {
try {
await access(this.getStoragePath(key));
return true;
} catch {
return false;
}
}
/**
* Store a Solana keypair securely
*/
static async storeKeypair(key, keypair, password) {
const secretKey = JSON.stringify(Array.from(keypair.secretKey));
await this.store(key, secretKey, password);
}
/**
* Retrieve a Solana keypair
*/
static async retrieveKeypair(key, password) {
const secretKeyJson = await this.retrieve(key, password);
const secretKey = new Uint8Array(JSON.parse(secretKeyJson));
return Keypair.fromSecretKey(secretKey);
}
/**
* Delete encrypted data
*/
static async delete(key) {
const path3 = this.getStoragePath(key);
const { unlink } = await import('fs/promises');
try {
await unlink(path3);
} catch (error) {
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
throw error;
}
}
}
/**
* List all stored keys
*/
static async listKeys() {
const { readdir } = await import('fs/promises');
try {
const dir = this.getStorageDir();
const files = await readdir(dir);
return files.filter((f) => f.endsWith(".enc")).map((f) => f.replace(".enc", ""));
} catch {
return [];
}
}
};
async function promptPassword(message = "Enter password: ") {
const { createInterface } = await import('readline');
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve3) => {
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
}
process.stdout.write(message);
let password = "";
process.stdin.on("data", (char) => {
const key = char.toString();
switch (key) {
case "\n":
case "\r":
case "":
process.stdin.pause();
process.stdin.removeAllListeners("data");
if (process.stdin.isTTY) {
process.stdin.setRawMode(false);
}
process.stdout.write("\n");
rl.close();
resolve3(password);
break;
case "":
process.exit(0);
break;
case "\x7F":
if (password.length > 0) {
password = password.slice(0, -1);
process.stdout.write("\b \b");
}
break;
default:
password += key;
process.stdout.write("*");
break;
}
});
});
}
function clearMemory(data) {
if (typeof data === "string") {
data = "";
} else if (data instanceof Uint8Array) {
data.fill(0);
}
}
var AtomicFileManager = class {
static locks = /* @__PURE__ */ new Map();
static async writeJSON(filePath, data) {
const lockKey = filePath;
if (this.locks.has(lockKey)) {
await this.locks.get(lockKey);
}
const operation = this.performAtomicWrite(filePath, data);
this.locks.set(lockKey, operation);
try {
await operation;
} finally {
this.locks.delete(lockKey);
}
}
static async performAtomicWrite(filePath, data) {
const tempPath = `${filePath}.tmp`;
const backupPath = `${filePath}.backup`;
try {
try {
await promises.access(filePath);
await promises.copyFile(filePath, backupPath);
} catch {
}
await promises.writeFile(tempPath, JSON.stringify(data, null, 2));
await promises.rename(tempPath, filePath);
try {
await promises.unlink(backupPath);
} catch {
}
} catch (error) {
try {
await promises.unlink(tempPath);
} catch {
}
try {
await promises.access(backupPath);
await promises.copyFile(backupPath, filePath);
await promises.unlink(backupPath);
} catch {
}
throw error;
}
}
static async readJSON(filePath) {
const lockKey = filePath;
if (this.locks.has(lockKey)) {
await this.locks.get(lockKey);
}
try {
const data = await promises.readFile(filePath, "utf-8");
return JSON.parse(data);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return null;
}
throw error;
}
}
};
var AGENT_CREDENTIALS_DIR = join(homedir(), ".ghostspeak", "agents");
var AgentWalletManager = class {
/**
* Generate a new agent wallet with credentials
*/
static async generateAgentWallet(agentName, description, ownerWallet) {
const agentId = agentName.toLowerCase().replace(/\s+/g, "-");
const uuid = randomUUID();
const keypairBytes = new Uint8Array(64);
crypto.getRandomValues(keypairBytes);
const exportableWallet = await createKeyPairSignerFromBytes(keypairBytes);
const credentials = {
agentId,
uuid,
name: agentName,
description,
agentWallet: {
publicKey: exportableWallet.address.toString()
},
ownerWallet: ownerWallet.toString(),
createdAt: Date.now(),
updatedAt: Date.now()
};
return {
agentWallet: exportableWallet,
credentials,
secretKey: keypairBytes
// Return separately for secure storage
};
}
/**
* Save agent credentials to file system (with secure key storage)
*/
static async saveCredentials(credentials, secretKey, password) {
await promises.mkdir(AGENT_CREDENTIALS_DIR, { recursive: true });
const agentDir = join(AGENT_CREDENTIALS_DIR, credentials.agentId);
await promises.mkdir(agentDir, { recursive: true });
const credentialsPath = join(agentDir, "credentials.json");
await promises.writeFile(credentialsPath, JSON.stringify(credentials, null, 2));
await chmod(credentialsPath, 384);
if (secretKey && password) {
const keyStorageId = `agent-${credentials.agentId}`;
const keypair = Keypair.fromSecretKey(secretKey);
await SecureStorage.storeKeypair(keyStorageId, keypair, password);
clearMemory(secretKey);
}
const uuidMappingPath = join(AGENT_CREDENTIALS_DIR, "uuid-mapping.json");
try {
let uuidMapping = await AtomicFileManager.readJSON(uuidMappingPath) ?? {};
uuidMapping[credentials.uuid] = credentials.agentId;
await AtomicFileManager.writeJSON(uuidMappingPath, uuidMapping);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
console.error("Error updating UUID mapping:", message);
throw new Error(`Failed to update UUID mapping: ${message}`);
}
}
/**
* Load agent credentials by agent ID
*/
static async loadCredentials(agentId) {
try {
const credentialsPath = join(AGENT_CREDENTIALS_DIR, agentId, "credentials.json");
const credentialsData = await promises.readFile(credentialsPath, "utf-8");
return JSON.parse(credentialsData);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return null;
}
const message = error instanceof Error ? error.message : "Unknown error";
console.error(`Error reading agent credentials for ${agentId}:`, message);
throw new Error(`Failed to read agent credentials for ${agentId}: ${message}`);
}
}
/**
* Load agent credentials by UUID
*/
static async loadCredentialsByUuid(uuid) {
try {
const uuidMappingPath = join(AGENT_CREDENTIALS_DIR, "uuid-mapping.json");
const mappingData = await promises.readFile(uuidMappingPath, "utf-8");
const uuidMapping = JSON.parse(mappingData);
const agentId = uuidMapping[uuid];
if (!agentId) return null;
return await this.loadCredentials(agentId);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return null;
}
const message = error instanceof Error ? error.message : "Unknown error";
console.error(`Error loading credentials by UUID ${uuid}:`, message);
throw new Error(`Failed to load credentials by UUID: ${message}`);
}
}
/**
* Get all agent credentials for a specific owner
*/
static async getAgentsByOwner(ownerAddress) {
try {
const agentDirs = await promises.readdir(AGENT_CREDENTIALS_DIR);
const agents = [];
for (const agentId of agentDirs) {
if (agentId === "uuid-mapping.json") continue;
const credentials = await this.loadCredentials(agentId);
if (credentials && credentials.ownerWallet === ownerAddress.toString()) {
agents.push(credentials);
}
}
return agents.sort((a, b) => b.createdAt - a.createdAt);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return [];
}
const message = error instanceof Error ? error.message : "Unknown error";
console.error(`Error getting agents by owner ${ownerAddress}:`, message);
throw new Error(`Failed to get agents by owner: ${message}`);
}
}
/**
* Create agent wallet signer from credentials (requires password)
*/
static async createAgentSigner(credentials, password) {
password ??= await promptPassword(`Enter password for agent ${credentials.name}: `);
const keyStorageId = `agent-${credentials.agentId}`;
const keypair = await SecureStorage.retrieveKeypair(keyStorageId, password);
clearMemory(password);
return createKeyPairSignerFromBytes(keypair.secretKey);
}
/**
* Update agent credentials
*/
static async updateCredentials(agentId, updates) {
const existingCredentials = await this.loadCredentials(agentId);
if (!existingCredentials) {
throw new Error(`Agent credentials not found for ID: ${agentId}`);
}
const updatedCredentials = {
...existingCredentials,
...updates,
updatedAt: Date.now()
};
await this.saveCredentials(updatedCredentials);
}
/**
* Delete agent credentials
*/
static async deleteCredentials(agentId) {
const credentials = await this.loadCredentials(agentId);
if (!credentials) return;
const uuidMappingPath = join(AGENT_CREDENTIALS_DIR, "uuid-mapping.json");
try {
const uuidMapping = await AtomicFileManager.readJSON(uuidMappingPath) ?? {};
delete uuidMapping[credentials.uuid];
await AtomicFileManager.writeJSON(uuidMappingPath, uuidMapping);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
console.error("Error updating UUID mapping during deletion:", message);
}
const agentDir = join(AGENT_CREDENTIALS_DIR, agentId);
await promises.rm(agentDir, { recursive: true, force: true });
}
/**
* Check if agent credentials exist
*/
static async credentialsExist(agentId) {
return await this.loadCredentials(agentId) !== null;
}
/**
* List all agent IDs
*/
static async listAgentIds() {
try {
const agentDirs = await promises.readdir(AGENT_CREDENTIALS_DIR);
return agentDirs.filter((dir) => dir !== "uuid-mapping.json");
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return [];
}
const message = error instanceof Error ? error.message : "Unknown error";
console.error("Error listing agent IDs:", message);
throw new Error(`Failed to list agent IDs: ${message}`);
}
}
};
var AgentCNFTManager = class {
/**
* Mint a compressed NFT as an agent ownership token
*/
static async mintOwnershipToken(credentials, ownerWallet, _rpcUrl) {
const agentHash = `${credentials.agentId}_${ownerWallet.address}_${Date.now()}`;
const cnftMint = `cnft_${Buffer.from(agentHash).toString("base64").slice(0, 32)}`;
const merkleTree = `tree_${Buffer.from(`${credentials.agentId}_tree`).toString("base64").slice(0, 32)}`;
console.log(`Generated CNFT credentials for agent ${credentials.agentId}:`);
console.log(` CNFT Mint: ${cnftMint}`);
console.log(` Merkle Tree: ${merkleTree}`);
await AgentWalletManager.updateCredentials(credentials.agentId, {
cnftMint,
merkleTree
});
return {
cnftMint,
merkleTree
};
}
/**
* Verify ownership of agent via CNFT
*/
static async verifyOwnership(agentUuid, ownerWallet, _rpcUrl) {
const credentials = await AgentWalletManager.loadCredentialsByUuid(agentUuid);
if (!credentials) return false;
if (credentials.ownerWallet !== ownerWallet.toString()) return false;
if (!credentials.cnftMint) {
return true;
}
try {
const cnftAssetId = credentials.cnftMint;
const dasApiUrl = process.env.DAS_API_URL ?? "https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY";
const response = await fetch2(dasApiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
jsonrpc: "2.0",
id: "cnft-ownership-check",
method: "getAsset",
params: {
id: cnftAssetId
}
})
});
const data = await response.json();
if (data.error) {
console.error("DAS API error:", data.error);
return false;
}
const asset = data.result;
if (!asset?.ownership) {
return false;
}
const currentOwner = asset.ownership.owner;
const expectedOwner = ownerWallet.toString();
const delegate = asset.ownership.delegate;
return currentOwner === expectedOwner || delegate !== void 0 && delegate === expectedOwner;
} catch (error) {
console.error("Failed to verify CNFT ownership:", error);
return true;
}
}
};
var AgentBackupManager = class {
/**
* Backup agent credentials to a file
*/
static async backupAgent(agentId, backupPath) {
const credentials = await AgentWalletManager.loadCredentials(agentId);
if (!credentials) {
throw new Error(`Agent credentials not found for ID: ${agentId}`);
}
await promises.mkdir(dirname(backupPath), { recursive: true });
const backupData = {
version: "1.0.0",
exportedAt: Date.now(),
credentials
};
await promises.writeFile(backupPath, JSON.stringify(backupData, null, 2));
}
/**
* Restore agent credentials from backup
*/
static async restoreAgent(backupPath) {
const backupData = await promises.readFile(backupPath, "utf-8");
const backup = JSON.parse(backupData);
if (!backup.credentials) {
throw new Error("Invalid backup file format");
}
const credentials = backup.credentials;
const existingCredentials = await AgentWalletManager.loadCredentials(credentials.agentId);
if (existingCredentials) {
throw new Error(`Agent ${credentials.agentId} already exists`);
}
await AgentWalletManager.saveCredentials(credentials);
return credentials.agentId;
}
/**
* Backup all agents for an owner
*/
static async backupAllAgents(ownerAddress, backupDir) {
const agents = await AgentWalletManager.getAgentsByOwner(ownerAddress);
await promises.mkdir(backupDir, { recursive: true });
for (const agent of agents) {
const backupPath = join(backupDir, `${agent.agentId}.json`);
await this.backupAgent(agent.agentId, backupPath);
}
}
};
// src/types/cli-types.ts
function isValidUrl(value) {
try {
new globalThis.URL(value);
return true;
} catch {
return false;
}
}
function isValidAuctionData(data) {
if (typeof data !== "object" || data === null) return false;
const auction = data;
return typeof auction.auction === "string" && (typeof auction.auctionType === "string" || typeof auction.auctionType === "object" && auction.auctionType !== null && "toString" in auction.auctionType) && (typeof auction.currentPrice === "bigint" || typeof auction.currentPrice === "number" || typeof auction.currentPrice === "string") && (typeof auction.startingPrice === "bigint" || typeof auction.startingPrice === "number" || typeof auction.startingPrice === "string") && (typeof auction.auctionEndTime === "number" || typeof auction.auctionEndTime === "string" || typeof auction.auctionEndTime === "bigint") && typeof auction.totalBids === "number";
}
// src/commands/agent.ts
var agentCommand = new Command("agent").description("Manage AI agents on the GhostSpeak protocol");
agentCommand.command("register").description("Register a new AI agent").option("-n, --name <name>", "Agent name").option("-d, --description <description>", "Agent description").option("--endpoint <endpoint>", "Service endpoint URL").action(async (_options) => {
intro(chalk3.cyan("\u{1F916} Register New AI Agent"));
try {
const agentData = await registerAgentPrompts(_options);
if (isCancel(agentData)) {
cancel("Agent registration cancelled");
return;
}
const s = spinner();
s.start("Connecting to Solana network...");
const { client, wallet } = await initializeClient("devnet");
s.stop("\u2705 Connected to Solana devnet");
s.start("Generating agent wallet and credentials...");
let credentials = null;
let agentWallet = null;
let registrationComplete = false;
try {
const walletResult = await AgentWalletManager.generateAgentWallet(
agentData.name,
agentData.description,
address(wallet.address.toString())
);
agentWallet = walletResult.agentWallet;
credentials = walletResult.credentials;
const secretKey = walletResult.secretKey;
s.stop("\u2705 Agent wallet generated");
const password = await text({
message: "Enter a password to secure your agent's private key:",
validate: (value) => {
if (value.length < 8) return "Password must be at least 8 characters";
return void 0;
}
});
if (isCancel(password)) {
cancel("Agent registration cancelled");
return;
}
const confirmPassword = await text({
message: "Confirm password:",
validate: (value) => {
if (value !== password) return "Passwords do not match";
return void 0;
}
});
if (isCancel(confirmPassword)) {
cancel("Agent registration cancelled");
return;
}
s.start("Saving agent credentials...");
await AgentWalletManager.saveCredentials(credentials, secretKey, password);
s.stop("\u2705 Agent credentials saved securely");
s.start("Registering agent on the blockchain...");
s.start("Registering agent on-chain (this may take a moment)...");
const agentId = credentials.agentId;
const agentAddress = await client.agent.create(
toSDKSigner(wallet),
{
name: agentData.name,
description: agentData.description,
category: agentData.capabilities[0] || "automation",
// Use first capability as category
capabilities: agentData.capabilities,
serviceEndpoint: agentData.serviceEndpoint,
agentId,
// Optional: Add IPFS configuration if available
ipfsConfig: void 0,
// Can be configured later if needed
forceIPFS: false
}
);
registrationComplete = true;
s.stop("\u2705 Agent registered successfully!");
s.start("Minting agent ownership token (CNFT)...");
try {
const { cnftMint, merkleTree } = await AgentCNFTManager.mintOwnershipToken(
credentials,
wallet,
client.config.rpcEndpoint ?? "https://api.devnet.solana.com"
);
console.log("CNFT Mint:", cnftMint);
console.log("Merkle Tree:", merkleTree);
s.stop("\u2705 Ownership token minted");
} catch (error) {
console.warn("\u26A0\uFE0F CNFT minting failed (using credentials only):", error);
s.stop("\u26A0\uFE0F Using credential-based ownership");
}
console.log("\n" + chalk3.green("\u{1F389} Your agent has been registered!"));
console.log(chalk3.gray(`Name: ${agentData.name}`));
console.log(chalk3.gray(`Description: ${agentData.description}`));
console.log(chalk3.gray(`Capabilities: ${agentData.capabilities.join(", ")}`));
console.log(chalk3.gray(`Agent ID: ${credentials.agentId}`));
console.log(chalk3.gray(`Agent UUID: ${credentials.uuid}`));
console.log(chalk3.gray(`Agent Address: ${agentAddress}`));
console.log(chalk3.gray(`Agent Wallet: ${agentWallet.address.toString()}`));
console.log("");
console.log(chalk3.yellow("\u{1F4A1} Agent credentials saved to:"));
console.log(chalk3.gray(` ~/.ghostspeak/agents/${credentials.agentId}/credentials.json`));
console.log(chalk3.yellow("\u{1F4A1} Use your agent UUID for marketplace operations:"));
console.log(chalk3.gray(` ${credentials.uuid}`));
console.log(chalk3.yellow("\u{1F4A1} Use your agent ID for future updates:"));
console.log(chalk3.gray(` ${credentials.agentId}`));
outro("Agent registration completed");
} catch (error) {
s.stop("\u274C Registration failed");
if (credentials && !registrationComplete) {
try {
console.log("\u{1F9F9} Cleaning up partial registration...");
await AgentWalletManager.deleteCredentials(credentials.agentId);
console.log("\u2705 Cleanup completed");
} catch (cleanupError) {
console.warn("\u26A0\uFE0F Cleanup failed:", cleanupError instanceof Error ? cleanupError.message : String(cleanupError));
}
}
throw new Error(handleTransactionError(error));
} finally {
}
} catch (error) {
cancel(chalk3.red("Failed to register agent: " + (error instanceof Error ? error.message : "Unknown error")));
}
});
agentCommand.command("list").description("List all registered agents").option("--limit <limit>", "Maximum number of agents to display", "10").action(async (options) => {
intro(chalk3.cyan("\u{1F4CB} List Registered Agents"));
const s = spinner();
s.start("Connecting to Solana network...");
try {
const { client } = await initializeClient("devnet");
s.stop("\u2705 Connected");
s.start("Fetching agents from the blockchain...");
const agents = await client.agent.list({
limit: parseInt(options.limit)
});
s.stop("\u2705 Agents loaded");
if (agents.length === 0) {
console.log("\n" + chalk3.yellow("No agents found on the network"));
try {
const { wallet } = await initializeClient("devnet");
const localAgents = await AgentWalletManager.getAgentsByOwner(wallet.address);
if (localAgents.length > 0) {
console.log("\n" + chalk3.cyan("Local agents found (not yet synchronized with blockchain):"));
console.log("\u2500".repeat(60));
localAgents.forEach((agent, index) => {
console.log(chalk3.yellow(`${index + 1}. ${agent.name} (Local)`));
console.log(chalk3.gray(` Description: ${agent.d