argus-demo-cli
Version:
Demo CLI tool for Argus financial control plane
530 lines (461 loc) ⢠22.9 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { readFileSync } from 'fs';
import Argus from 'argus-sdk';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
const program = new Command();
// Helper function to parse integers correctly
// (avoids parseInt radix issue with Commander.js defaults)
const parseIntValue = (value) => parseInt(value, 10);
program
.name('argus-demo-cli')
.description('Demo CLI tool for Argus financial control plane')
.version(packageJson.version)
.helpOption('-h, --help', 'Display help for command')
.addHelpText('after', `
${chalk.yellow('Authentication Requirements:')}
This CLI requires the following environment variables to be set:
${chalk.cyan('ARGUS_API_KEY')} - Your Argus API key
${chalk.cyan('ARGUS_BASE_URL')} - The base URL for the Argus API (e.g., https://api.argus.example.com)
${chalk.yellow('Example:')}
export ARGUS_API_KEY="your-api-key-here"
export ARGUS_BASE_URL="https://api.argus.example.com"
argus-demo-cli evaluate --amount 100 --currency USD
${chalk.yellow('Getting Started:')}
1. Set your environment variables
2. Run 'argus-demo-cli --help' to see available commands
3. Use 'argus-demo-cli <command> --help' for command-specific help
`);
// Example command to demonstrate functionality
program
.command('evaluate')
.description('Evaluate a transaction against policies')
.option('-a, --amount <amount>', 'Transaction amount', parseFloat)
.option('-c, --currency <currency>', 'Currency code (e.g., USD, EUR)', 'USD')
.option('-m, --merchant <merchant>', 'Merchant name')
.action((options) => {
// Check for required environment variables
if (!process.env.ARGUS_API_KEY || !process.env.ARGUS_BASE_URL) {
console.error(chalk.red('Error: Missing required environment variables'));
console.error(chalk.yellow('Please set ARGUS_API_KEY and ARGUS_BASE_URL'));
console.error(chalk.gray('Run "argus-demo-cli --help" for more information'));
process.exit(1);
}
console.log(chalk.green('Evaluating transaction...'));
console.log(chalk.gray(`Amount: ${options.amount} ${options.currency}`));
if (options.merchant) {
console.log(chalk.gray(`Merchant: ${options.merchant}`));
}
console.log(chalk.gray(`Using API: ${process.env.ARGUS_BASE_URL}`));
// This is where you would make the actual API call
console.log(chalk.yellow('\nThis is a demo - actual API integration not implemented'));
});
// Demo command that runs 5 test transactions
program
.command('demo')
.description('Run a demo with 5 preset test transactions')
.option('-i, --iterations <number>', 'Number of iterations to run', parseIntValue, 1)
.action(async (options) => {
// Check for required environment variables
const apiKey = process.env.ARGUS_API_KEY;
const baseURL = process.env.ARGUS_BASE_URL || 'http://localhost:8000';
if (!apiKey) {
console.error(chalk.red('Error: Missing required environment variables'));
console.error(chalk.yellow('Please set ARGUS_API_KEY and ARGUS_BASE_URL'));
console.error(chalk.gray('Run "argus-demo-cli --help" for more information'));
process.exit(1);
}
// Preset transactions for demo - Realistic B2B finance scenarios
const transactions = [
{
name: "AWS Monthly Services",
amount: "3847.23",
currency: "USD",
payment_method: "card",
recipient_name: "Amazon Web Services",
merchant_category_code: "5734", // Computer Software Stores
metadata: { category: "cloud_infrastructure", recurring: true, vendor_id: "aws_prod_01" }
},
{
name: "Office Supplies Vendor",
amount: "892.47",
currency: "USD",
payment_method: "ach",
recipient_name: "Staples Business Advantage",
merchant_category_code: "5943", // Office Equipment/Supplies
metadata: { category: "office_supplies", po_number: "PO-2024-0187" }
},
{
name: "Software License Renewal",
amount: "4999.00",
currency: "USD",
payment_method: "card",
recipient_name: "Salesforce Inc",
merchant_category_code: "5734", // Computer Software Stores
metadata: { category: "software_licenses", license_type: "enterprise", seats: 50 }
},
{
name: "Suspicious Wire Transfer",
amount: "45000.00",
currency: "USD",
payment_method: "wire",
recipient_name: "Offshore Holdings LLC",
merchant_category_code: "6051", // Cryptocurrency/Digital Currency
recipient_type: "business",
metadata: { category: "investment", first_time_vendor: true, country: "Cayman Islands" }
},
{
name: "International Contractor Payment",
amount: "8500.00",
currency: "EUR",
payment_method: "wire",
recipient_name: "TechConsult GmbH",
recipient_type: "business",
metadata: { category: "professional_services", country: "Germany", tax_form: "W-8BEN-E" }
}
];
console.log(chalk.blue('š Starting Argus Demo - B2B Finance Operations'));
console.log(chalk.gray(`š API URL: ${baseURL}`));
console.log(chalk.gray(`š API Key: ${apiKey.substring(0, 20)}...${apiKey.substring(apiKey.length - 4)}`));
console.log(chalk.gray(`š Running ${transactions.length} transactions${options.iterations > 1 ? ` (${options.iterations} iterations)` : ''}...\n`));
// Initialize client
const client = new Argus({
apiKey,
baseURL,
});
// Debug: log the actual request being made
console.log(chalk.gray('Debug: Initializing Argus client with:'));
console.log(chalk.gray(` - baseURL: ${baseURL}`));
console.log(chalk.gray(` - apiKey starts with: ${apiKey.substring(0, 10)}...`));
// Track results
const results = { approved: 0, rejected: 0, manual_review: 0, total: 0 };
const timings = [];
try {
// Run iterations
for (let iter = 0; iter < options.iterations; iter++) {
if (options.iterations > 1) {
console.log(chalk.gray(`\nš Iteration ${iter + 1}/${options.iterations}`));
}
// Process each transaction
for (let i = 0; i < transactions.length; i++) {
const tx = transactions[i];
const startTime = Date.now();
try {
const response = await client.evaluate.transaction({
amount: tx.amount,
currency: tx.currency,
payment_method: tx.payment_method,
recipient_name: tx.recipient_name,
recipient_type: tx.recipient_type,
merchant_category_code: tx.merchant_category_code,
metadata: tx.metadata,
});
const duration = Date.now() - startTime;
timings.push(duration);
results.total++;
// Update counters
switch (response.decision.toUpperCase()) {
case 'APPROVED':
results.approved++;
console.log(chalk.green(`ā
APPROVED`) + ` Transaction ${i + 1}: ${tx.name} (${tx.currency} ${tx.amount})`);
break;
case 'REJECTED':
results.rejected++;
console.log(chalk.red(`ā REJECTED`) + ` Transaction ${i + 1}: ${tx.name} (${tx.currency} ${tx.amount})`);
break;
case 'MANUAL_REVIEW':
results.manual_review++;
console.log(chalk.yellow(`ā ļø MANUAL REVIEW`) + ` Transaction ${i + 1}: ${tx.name} (${tx.currency} ${tx.amount})`);
break;
}
console.log(chalk.gray(` Decision ID: ${response.decision_id}`));
if (response.failed_rule) {
console.log(chalk.gray(` Failed Rule: ${response.failed_rule}`));
}
console.log(chalk.gray(` Time: ${duration}ms\n`));
} catch (error) {
console.error(chalk.red(`ā Error processing ${tx.name}:`), error instanceof Error ? error.message : error);
if (error.response) {
console.error(chalk.gray('Response status:'), error.response.status);
console.error(chalk.gray('Response data:'), JSON.stringify(error.response.data, null, 2));
}
results.total++;
}
}
}
// Display summary
const avgTime = timings.length > 0 ? Math.round(timings.reduce((a, b) => a + b, 0) / timings.length) : 0;
const approvalRate = results.total > 0 ? Math.round((results.approved / results.total) * 100) : 0;
console.log(chalk.blue('\nš Summary:'));
console.log(chalk.gray(` Total: ${results.total} transactions`));
console.log(chalk.gray(` Approved: ${results.approved} (${approvalRate}%)`));
console.log(chalk.gray(` Rejected: ${results.rejected} (${Math.round((results.rejected / results.total) * 100)}%)`));
console.log(chalk.gray(` Manual Review: ${results.manual_review} (${Math.round((results.manual_review / results.total) * 100)}%)`));
if (timings.length > 0) {
console.log(chalk.gray(` Avg Response Time: ${avgTime}ms`));
}
} catch (error) {
console.error(chalk.red('\nā Fatal error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
// Seed dashboard command
program
.command('seed-dashboard')
.description('Generate historical transaction data for dashboard analytics')
.option('-d, --days <number>', 'Number of days of history to generate', parseIntValue, 30)
.option('-t, --transactions-per-day <number>', 'Average transactions per day', parseIntValue, 25)
.action(async (options) => {
// Check for required environment variables
const apiKey = process.env.ARGUS_API_KEY;
const baseURL = process.env.ARGUS_BASE_URL || 'http://localhost:8000';
if (!apiKey) {
console.error(chalk.red('Error: Missing required environment variables'));
console.error(chalk.yellow('Please set ARGUS_API_KEY'));
console.error(chalk.gray('Run "argus-demo-cli --help" for more information'));
process.exit(1);
}
const days = options.days;
const avgTransactionsPerDay = options.transactionsPerDay;
console.log(chalk.blue('š± Seeding Dashboard with Historical Data'));
console.log(chalk.gray(`š API URL: ${baseURL}`));
console.log(chalk.gray(`š
Days of history: ${days}`));
console.log(chalk.gray(`š Average transactions per day: ${avgTransactionsPerDay}`));
console.log(chalk.gray(`š¦ Estimated total transactions: ~${days * avgTransactionsPerDay}\n`));
// Initialize client
const client = new Argus({
apiKey,
baseURL,
});
// Merchant templates for variety
const merchantTemplates = [
// Software/SaaS (30%)
{ name: "Amazon Web Services", mcc: "5734", category: "cloud_infrastructure", weight: 8 },
{ name: "Microsoft Azure", mcc: "5734", category: "cloud_infrastructure", weight: 7 },
{ name: "Salesforce Inc", mcc: "5734", category: "software_licenses", weight: 5 },
{ name: "Adobe Creative Cloud", mcc: "5734", category: "software_licenses", weight: 4 },
{ name: "Slack Technologies", mcc: "5734", category: "software_subscriptions", weight: 3 },
{ name: "Zoom Video Communications", mcc: "5734", category: "software_subscriptions", weight: 3 },
// Office Supplies (20%)
{ name: "Staples Business", mcc: "5943", category: "office_supplies", weight: 10 },
{ name: "Office Depot", mcc: "5943", category: "office_supplies", weight: 7 },
{ name: "W.B. Mason", mcc: "5943", category: "office_supplies", weight: 3 },
// Professional Services (20%)
{ name: "McKinsey & Company", mcc: "8999", category: "consulting", weight: 5 },
{ name: "Deloitte Consulting", mcc: "8999", category: "consulting", weight: 5 },
{ name: "Legal Services LLC", mcc: "8111", category: "legal_services", weight: 5 },
{ name: "BDO Accounting", mcc: "8931", category: "accounting", weight: 5 },
// Travel/Hotels (10%)
{ name: "United Airlines", mcc: "3000", category: "travel", weight: 3 },
{ name: "Marriott Hotels", mcc: "7011", category: "travel", weight: 3 },
{ name: "Hertz Car Rental", mcc: "7512", category: "travel", weight: 2 },
{ name: "Uber for Business", mcc: "4121", category: "travel", weight: 2 },
// Electronics (10%)
{ name: "Apple Store", mcc: "5732", category: "electronics", weight: 5 },
{ name: "Dell Technologies", mcc: "5732", category: "electronics", weight: 5 },
// High-risk (5%)
{ name: "Crypto Exchange Ltd", mcc: "6051", category: "cryptocurrency", weight: 2 },
{ name: "Online Gaming Platform", mcc: "5967", category: "gambling", weight: 2 },
{ name: "Betting Services Inc", mcc: "7995", category: "gambling", weight: 1 },
// Other business expenses (5%)
{ name: "FedEx", mcc: "4215", category: "shipping", weight: 2 },
{ name: "AT&T Business", mcc: "4814", category: "telecommunications", weight: 2 },
{ name: "Business Insurance Co", mcc: "6300", category: "insurance", weight: 1 }
];
// Calculate total weight for probability distribution
const totalWeight = merchantTemplates.reduce((sum, m) => sum + m.weight, 0);
// Helper function to get random merchant based on weights
function getRandomMerchant() {
let random = Math.random() * totalWeight;
for (const merchant of merchantTemplates) {
random -= merchant.weight;
if (random <= 0) return merchant;
}
return merchantTemplates[0]; // Fallback
}
// Helper function to generate transaction amount based on category
function generateAmount(category) {
const ranges = {
cloud_infrastructure: { min: 1000, max: 15000, decimal: true },
software_licenses: { min: 500, max: 10000, decimal: false },
software_subscriptions: { min: 50, max: 2000, decimal: true },
office_supplies: { min: 50, max: 2000, decimal: true },
consulting: { min: 5000, max: 50000, decimal: false },
legal_services: { min: 2000, max: 25000, decimal: false },
accounting: { min: 1000, max: 10000, decimal: false },
travel: { min: 100, max: 5000, decimal: true },
electronics: { min: 500, max: 5000, decimal: true },
cryptocurrency: { min: 10000, max: 50000, decimal: false },
gambling: { min: 1000, max: 10000, decimal: false },
shipping: { min: 20, max: 500, decimal: true },
telecommunications: { min: 200, max: 2000, decimal: true },
insurance: { min: 1000, max: 5000, decimal: false }
};
const range = ranges[category] || { min: 100, max: 5000, decimal: true };
const amount = Math.random() * (range.max - range.min) + range.min;
return range.decimal ? amount.toFixed(2) : Math.round(amount).toFixed(2);
}
// Helper function to determine if transaction happens on given hour
function shouldTransactAtHour(hour, isWeekend) {
// Business hours pattern (9 AM - 6 PM)
if (isWeekend) {
// Much lower chance on weekends
return Math.random() < 0.1;
}
if (hour >= 9 && hour <= 18) {
// Peak business hours
return Math.random() < 0.7;
} else if (hour >= 7 && hour <= 21) {
// Extended business hours
return Math.random() < 0.3;
} else {
// Night time
return Math.random() < 0.05;
}
}
// Track results
const results = { approved: 0, rejected: 0, manual_review: 0, total: 0, errors: 0 };
const startTime = Date.now();
try {
// Generate transactions for each day
for (let dayOffset = days - 1; dayOffset >= 0; dayOffset--) {
const date = new Date();
date.setDate(date.getDate() - dayOffset);
date.setHours(0, 0, 0, 0);
const dayOfWeek = date.getDay();
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
// Generate random transaction count with variance
let baseTxCount;
if (isWeekend) {
// Weekends: 20-40% of average
baseTxCount = avgTransactionsPerDay * (0.2 + Math.random() * 0.2);
} else if (dayOfWeek === 1) {
// Mondays: 110-130% (busy catch-up day)
baseTxCount = avgTransactionsPerDay * (1.1 + Math.random() * 0.2);
} else if (dayOfWeek === 5) {
// Fridays: 80-100% (wind down)
baseTxCount = avgTransactionsPerDay * (0.8 + Math.random() * 0.2);
} else {
// Tuesday-Thursday: 90-110% of average
baseTxCount = avgTransactionsPerDay * (0.9 + Math.random() * 0.2);
}
// Add some additional random variance (±20%)
const variance = 0.2;
const randomFactor = 1 + (Math.random() - 0.5) * variance;
const dayTransactions = Math.max(1, Math.floor(baseTxCount * randomFactor));
console.log(chalk.gray(`š
Generating ${date.toDateString()} (${dayTransactions} transactions)...`));
// Generate transactions throughout the day
const transactionTimes = [];
for (let hour = 0; hour < 24; hour++) {
if (shouldTransactAtHour(hour, isWeekend)) {
const minute = Math.floor(Math.random() * 60);
const second = Math.floor(Math.random() * 60);
transactionTimes.push({ hour, minute, second });
}
}
// Ensure we have enough transaction times
while (transactionTimes.length < dayTransactions) {
const hour = Math.floor(Math.random() * 24);
const minute = Math.floor(Math.random() * 60);
const second = Math.floor(Math.random() * 60);
transactionTimes.push({ hour, minute, second });
}
// Shuffle and limit to desired number
transactionTimes.sort(() => Math.random() - 0.5);
transactionTimes.splice(dayTransactions);
// Generate transactions for this day
for (const time of transactionTimes) {
const merchant = getRandomMerchant();
const amount = generateAmount(merchant.category);
// Determine currency (70% USD, 20% EUR, 10% GBP)
const currencyRand = Math.random();
const currency = currencyRand < 0.7 ? "USD" : currencyRand < 0.9 ? "EUR" : "GBP";
// Determine payment method based on amount and category
let payment_method;
const amountNum = parseFloat(amount);
if (amountNum > 10000) {
payment_method = "wire";
} else if (merchant.category.includes("consulting") || merchant.category.includes("legal")) {
payment_method = Math.random() < 0.7 ? "ach" : "wire";
} else {
payment_method = Math.random() < 0.6 ? "card" : "ach";
}
// Create transaction timestamp
const transactionDate = new Date(date);
transactionDate.setHours(time.hour, time.minute, time.second);
try {
const response = await client.evaluate.transaction({
amount,
currency,
payment_method,
recipient_name: merchant.name,
merchant_category_code: merchant.mcc,
created_at: transactionDate.toISOString(),
metadata: {
category: merchant.category,
day_of_week: transactionDate.getDay(),
hour_of_day: transactionDate.getHours()
}
});
results.total++;
// Update counters
switch (response.decision.toUpperCase()) {
case 'APPROVED':
results.approved++;
break;
case 'REJECTED':
results.rejected++;
break;
case 'MANUAL_REVIEW':
results.manual_review++;
break;
}
} catch (error) {
results.errors++;
if (results.errors <= 5) {
console.error(chalk.red(` ā Error: ${error instanceof Error ? error.message : error}`));
}
}
}
// Show progress
const progress = ((days - dayOffset) / days * 100).toFixed(0);
console.log(chalk.green(` ā Day complete. Progress: ${progress}%`));
}
// Calculate summary statistics
const duration = Math.round((Date.now() - startTime) / 1000);
const approvalRate = results.total > 0 ? Math.round((results.approved / results.total) * 100) : 0;
const rejectionRate = results.total > 0 ? Math.round((results.rejected / results.total) * 100) : 0;
const reviewRate = results.total > 0 ? Math.round((results.manual_review / results.total) * 100) : 0;
console.log(chalk.blue('\n⨠Dashboard Seeding Complete!'));
console.log(chalk.gray('ā'.repeat(50)));
console.log(chalk.gray(`ā±ļø Duration: ${duration} seconds`));
console.log(chalk.gray(`š Total Transactions: ${results.total}`));
console.log(chalk.green(`ā
Approved: ${results.approved} (${approvalRate}%)`));
console.log(chalk.red(`ā Rejected: ${results.rejected} (${rejectionRate}%)`));
console.log(chalk.yellow(`ā ļø Manual Review: ${results.manual_review} (${reviewRate}%)`));
if (results.errors > 0) {
console.log(chalk.red(`š« Errors: ${results.errors}`));
}
console.log(chalk.gray('ā'.repeat(50)));
console.log(chalk.cyan('\nšÆ Your dashboard should now display:'));
console.log(chalk.gray(' ⢠Transaction volume trends over time'));
console.log(chalk.gray(' ⢠Decision breakdown pie chart'));
console.log(chalk.gray(' ⢠Top rejection reasons'));
console.log(chalk.gray(' ⢠Payment method distribution'));
console.log(chalk.gray('\nš Visit your dashboard to see the analytics!'));
} catch (error) {
console.error(chalk.red('\nā Fatal error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
// Parse command line arguments
program.parse(process.argv);
// Show help if no command is provided
if (!process.argv.slice(2).length) {
program.outputHelp();
}