UNPKG

@ghostspeak/cli

Version:

Command-line interface for GhostSpeak AI Agent Commerce Protocol - Production Ready Beta

1,480 lines (1,473 loc) 349 kB
#!/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