npm cli app
This commit is contained in:
parent
b62a25c5eb
commit
aed85dff72
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Source files (only compiled JS will be included)
|
||||||
|
*.ts
|
||||||
|
!*.d.ts
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
tests/
|
||||||
|
**/*.test.ts
|
||||||
|
**/*.test.js
|
||||||
|
|
||||||
|
# Build config
|
||||||
|
tsconfig.json
|
||||||
|
tsconfig.build.json
|
||||||
|
bun.lockb
|
||||||
|
|
||||||
|
# Development
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
anvil.log
|
||||||
|
.anvil.pid
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github/
|
||||||
|
|
||||||
|
# Documentation (keep README.md)
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# Deployments
|
||||||
|
deployments/
|
||||||
|
|
||||||
|
# Node modules (will be installed by user)
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Parent directory references
|
||||||
|
../
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
# Installing NLA CLI Globally
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js >= 18.0.0
|
||||||
|
- npm or yarn or pnpm
|
||||||
|
- A blockchain node (local Anvil or remote RPC URL)
|
||||||
|
- At least one LLM provider API key (OpenAI, Anthropic, or OpenRouter)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Global Installation (Recommended)
|
||||||
|
|
||||||
|
Install the NLA CLI globally to use it from anywhere:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g nla
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with yarn:
|
||||||
|
```bash
|
||||||
|
yarn global add nla
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with pnpm:
|
||||||
|
```bash
|
||||||
|
pnpm add -g nla
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nla --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create a `.env` file in your project directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the example environment file
|
||||||
|
cp node_modules/nla/.env.example .env
|
||||||
|
|
||||||
|
# Edit with your configuration
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Required environment variables:
|
||||||
|
```bash
|
||||||
|
# At least one LLM provider API key
|
||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
# OR
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
# OR
|
||||||
|
OPENROUTER_API_KEY=sk-or-...
|
||||||
|
|
||||||
|
# Oracle configuration
|
||||||
|
ORACLE_PRIVATE_KEY=0x...
|
||||||
|
RPC_URL=http://localhost:8545
|
||||||
|
|
||||||
|
# Optional: For enhanced search
|
||||||
|
PERPLEXITY_API_KEY=pplx-...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Start Development Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nla dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Start Anvil (local blockchain)
|
||||||
|
- Deploy all contracts
|
||||||
|
- Start the oracle
|
||||||
|
|
||||||
|
### 2. Create an Escrow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nla escrow:create \
|
||||||
|
--demand "The sky is blue" \
|
||||||
|
--amount 10 \
|
||||||
|
--token 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 \
|
||||||
|
--oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Fulfill an Escrow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nla escrow:fulfill \
|
||||||
|
--escrow-uid 0x... \
|
||||||
|
--fulfillment "The sky appears blue today" \
|
||||||
|
--oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Collect Payment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nla escrow:collect \
|
||||||
|
--escrow-uid 0x... \
|
||||||
|
--fulfillment-uid 0x...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstallation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm uninstall -g nla
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
If you want to contribute or modify the CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/arkhai-io/natural-language-agreements.git
|
||||||
|
cd natural-language-agreements
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Build
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Link locally
|
||||||
|
npm link
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Command not found after installation
|
||||||
|
|
||||||
|
Make sure your npm global bin directory is in your PATH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check npm global bin path
|
||||||
|
npm bin -g
|
||||||
|
|
||||||
|
# Add to PATH (add to ~/.bashrc or ~/.zshrc)
|
||||||
|
export PATH="$(npm bin -g):$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission errors on Linux/Mac
|
||||||
|
|
||||||
|
If you get permission errors, either:
|
||||||
|
|
||||||
|
1. Use a Node version manager (recommended):
|
||||||
|
```bash
|
||||||
|
# Install nvm
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||||
|
|
||||||
|
# Install and use Node
|
||||||
|
nvm install 18
|
||||||
|
nvm use 18
|
||||||
|
|
||||||
|
# Now install without sudo
|
||||||
|
npm install -g nla
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Or configure npm to use a different directory:
|
||||||
|
```bash
|
||||||
|
mkdir ~/.npm-global
|
||||||
|
npm config set prefix '~/.npm-global'
|
||||||
|
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues and questions:
|
||||||
|
- GitHub: https://github.com/arkhai-io/natural-language-agreements/issues
|
||||||
|
- Documentation: https://github.com/arkhai-io/natural-language-agreements
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
import { spawn, spawnSync } from "child_process";
|
||||||
|
import { existsSync, readFileSync, writeFileSync, createWriteStream, unlinkSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
// Colors for console output
|
||||||
|
const colors = {
|
||||||
|
green: '\x1b[32m',
|
||||||
|
blue: '\x1b[34m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
reset: '\x1b[0m'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load .env file if it exists
|
||||||
|
function loadEnvFile() {
|
||||||
|
const envPath = '.env';
|
||||||
|
if (existsSync(envPath)) {
|
||||||
|
console.log(`${colors.blue}📄 Loading .env file...${colors.reset}`);
|
||||||
|
const envContent = readFileSync(envPath, 'utf-8');
|
||||||
|
const lines = envContent.split('\n');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
// Skip comments and empty lines
|
||||||
|
if (line.trim().startsWith('#') || !line.trim()) continue;
|
||||||
|
|
||||||
|
// Parse key=value
|
||||||
|
const match = line.match(/^([^=]+)=(.*)$/);
|
||||||
|
if (match) {
|
||||||
|
const key = match[1].trim();
|
||||||
|
const value = match[2].trim();
|
||||||
|
// Only set if not already in environment
|
||||||
|
if (!process.env[key]) {
|
||||||
|
process.env[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`${colors.green}✅ Environment variables loaded${colors.reset}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to check if a command exists
|
||||||
|
function commandExists(command: string): boolean {
|
||||||
|
try {
|
||||||
|
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [command], {
|
||||||
|
stdio: 'pipe'
|
||||||
|
});
|
||||||
|
return result.status === 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to check if a port is in use
|
||||||
|
function isPortInUse(port: number): boolean {
|
||||||
|
try {
|
||||||
|
const result = spawnSync('lsof', ['-Pi', `:${port}`, '-sTCP:LISTEN', '-t'], {
|
||||||
|
stdio: 'pipe'
|
||||||
|
});
|
||||||
|
return result.status === 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dev command - Start complete development environment
|
||||||
|
export async function runDevCommand(cliDir: string) {
|
||||||
|
console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}`);
|
||||||
|
console.log(`${colors.blue} Natural Language Agreement Oracle - Quick Setup${colors.reset}`);
|
||||||
|
console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}\n`);
|
||||||
|
|
||||||
|
// Load .env file first
|
||||||
|
loadEnvFile();
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Check prerequisites
|
||||||
|
console.log(`${colors.blue}📋 Checking prerequisites...${colors.reset}\n`);
|
||||||
|
|
||||||
|
// Check Bun
|
||||||
|
if (!commandExists('bun')) {
|
||||||
|
console.error(`${colors.red}❌ Bun is not installed${colors.reset}`);
|
||||||
|
console.log('Please install it: https://bun.sh');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`${colors.green}✅ Bun installed${colors.reset}`);
|
||||||
|
|
||||||
|
// Check Foundry
|
||||||
|
if (!commandExists('forge')) {
|
||||||
|
console.error(`${colors.red}❌ Foundry (forge) is not installed${colors.reset}`);
|
||||||
|
console.log('Please install it: https://book.getfoundry.sh/getting-started/installation');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`${colors.green}✅ Foundry installed${colors.reset}`);
|
||||||
|
|
||||||
|
// Check Anvil
|
||||||
|
if (!commandExists('anvil')) {
|
||||||
|
console.error(`${colors.red}❌ Anvil is not installed${colors.reset}`);
|
||||||
|
console.log('Please install Foundry: https://book.getfoundry.sh/getting-started/installation');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`${colors.green}✅ Anvil installed${colors.reset}`);
|
||||||
|
|
||||||
|
// Check LLM API keys
|
||||||
|
const hasOpenAI = !!process.env.OPENAI_API_KEY;
|
||||||
|
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
||||||
|
const hasOpenRouter = !!process.env.OPENROUTER_API_KEY;
|
||||||
|
const hasPerplexity = !!process.env.PERPLEXITY_API_KEY;
|
||||||
|
|
||||||
|
if (!hasOpenAI && !hasAnthropic && !hasOpenRouter && !hasPerplexity) {
|
||||||
|
console.error(`${colors.red}❌ No LLM provider API key set${colors.reset}`);
|
||||||
|
console.log('Please add at least one API key to your environment:');
|
||||||
|
console.log(' export OPENAI_API_KEY=sk-...');
|
||||||
|
console.log(' export ANTHROPIC_API_KEY=sk-ant-...');
|
||||||
|
console.log(' export OPENROUTER_API_KEY=sk-or-...');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOpenAI) console.log(`${colors.green}✅ OpenAI API key configured${colors.reset}`);
|
||||||
|
if (hasAnthropic) console.log(`${colors.green}✅ Anthropic API key configured${colors.reset}`);
|
||||||
|
if (hasOpenRouter) console.log(`${colors.green}✅ OpenRouter API key configured${colors.reset}`);
|
||||||
|
if (hasPerplexity) console.log(`${colors.green}✅ Perplexity API key configured${colors.reset}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Check if Anvil is already running
|
||||||
|
if (isPortInUse(8545)) {
|
||||||
|
console.log(`${colors.yellow}⚠️ Anvil is already running on port 8545${colors.reset}`);
|
||||||
|
console.log(`${colors.blue}Using existing Anvil instance${colors.reset}\n`);
|
||||||
|
} else {
|
||||||
|
// Start Anvil
|
||||||
|
console.log(`${colors.blue}🔨 Starting Anvil...${colors.reset}`);
|
||||||
|
const anvilProcess = spawn('anvil', [], {
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
detached: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save PID
|
||||||
|
writeFileSync('.anvil.pid', anvilProcess.pid!.toString());
|
||||||
|
|
||||||
|
// Redirect output to log file
|
||||||
|
const logStream = createWriteStream('anvil.log', { flags: 'a' });
|
||||||
|
anvilProcess.stdout?.pipe(logStream);
|
||||||
|
anvilProcess.stderr?.pipe(logStream);
|
||||||
|
|
||||||
|
anvilProcess.unref();
|
||||||
|
|
||||||
|
console.log(`${colors.green}✅ Anvil started (PID: ${anvilProcess.pid})${colors.reset}`);
|
||||||
|
console.log(' Logs: tail -f anvil.log');
|
||||||
|
|
||||||
|
// Wait for Anvil to be ready
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy contracts
|
||||||
|
console.log(`\n${colors.blue}📝 Deploying contracts...${colors.reset}\n`);
|
||||||
|
const deployScript = join(cliDir, 'server', 'deploy.js');
|
||||||
|
const deployResult = spawnSync('bun', ['run', deployScript, '--network', 'localhost', '--rpc-url', 'http://localhost:8545'], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd: process.cwd()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deployResult.status !== 0) {
|
||||||
|
console.error(`${colors.red}❌ Deployment failed${colors.reset}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start oracle
|
||||||
|
console.log(`\n${colors.blue}🚀 Starting oracle...${colors.reset}\n`);
|
||||||
|
const oracleScript = join(cliDir, 'server', 'oracle.js');
|
||||||
|
const deploymentFile = join(cliDir, 'deployments', 'localhost.json');
|
||||||
|
|
||||||
|
const oracleProcess = spawn('bun', ['run', oracleScript, '--deployment', deploymentFile], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd: process.cwd()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle cleanup on exit
|
||||||
|
const cleanup = () => {
|
||||||
|
console.log(`\n${colors.yellow}🛑 Shutting down...${colors.reset}`);
|
||||||
|
|
||||||
|
// Kill oracle
|
||||||
|
oracleProcess.kill();
|
||||||
|
|
||||||
|
// Kill Anvil
|
||||||
|
try {
|
||||||
|
const pidFile = '.anvil.pid';
|
||||||
|
if (existsSync(pidFile)) {
|
||||||
|
const pid = readFileSync(pidFile, 'utf-8').trim();
|
||||||
|
try {
|
||||||
|
process.kill(parseInt(pid));
|
||||||
|
console.log(`${colors.green}✅ Anvil stopped${colors.reset}`);
|
||||||
|
} catch (e) {
|
||||||
|
// Process might already be dead
|
||||||
|
}
|
||||||
|
unlinkSync(pidFile);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('SIGINT', cleanup);
|
||||||
|
process.on('SIGTERM', cleanup);
|
||||||
|
|
||||||
|
// Keep process alive
|
||||||
|
await new Promise(() => {});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { spawnSync } from "child_process";
|
||||||
|
import { existsSync, readFileSync, unlinkSync } from "fs";
|
||||||
|
|
||||||
|
// Colors for console output
|
||||||
|
const colors = {
|
||||||
|
green: '\x1b[32m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
reset: '\x1b[0m'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stop command - Stop all services
|
||||||
|
export async function runStopCommand() {
|
||||||
|
console.log(`${colors.yellow}🛑 Stopping services...${colors.reset}\n`);
|
||||||
|
|
||||||
|
// Stop Anvil
|
||||||
|
try {
|
||||||
|
const pidFile = '.anvil.pid';
|
||||||
|
if (existsSync(pidFile)) {
|
||||||
|
const pid = readFileSync(pidFile, 'utf-8').trim();
|
||||||
|
try {
|
||||||
|
process.kill(parseInt(pid));
|
||||||
|
console.log(`${colors.green}✅ Anvil stopped${colors.reset}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`${colors.yellow}⚠️ Anvil process not found (may have already stopped)${colors.reset}`);
|
||||||
|
}
|
||||||
|
unlinkSync(pidFile);
|
||||||
|
} else {
|
||||||
|
console.log(`${colors.yellow}⚠️ No Anvil PID file found${colors.reset}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`${colors.red}❌ Error stopping Anvil:${colors.reset}`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to kill any remaining processes on port 8545
|
||||||
|
try {
|
||||||
|
spawnSync('pkill', ['-f', 'anvil']);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n${colors.green}✅ Services stopped${colors.reset}`);
|
||||||
|
}
|
||||||
54
cli/index.ts
54
cli/index.ts
|
|
@ -1,11 +1,20 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env node
|
||||||
import { parseArgs } from "util";
|
import { parseArgs } from "util";
|
||||||
import { spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
import { existsSync, readFileSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { dirname, join } from "path";
|
||||||
|
|
||||||
import { createPublicClient, http, parseAbiParameters, decodeAbiParameters } from "viem";
|
import { createPublicClient, http, parseAbiParameters, decodeAbiParameters } from "viem";
|
||||||
import { foundry } from "viem/chains";
|
import { foundry } from "viem/chains";
|
||||||
import { contracts } from "alkahest-ts";
|
import { contracts } from "alkahest-ts";
|
||||||
|
|
||||||
|
import { runDevCommand } from "./commands/dev.js";
|
||||||
|
import { runStopCommand } from "./commands/stop.js";
|
||||||
|
|
||||||
|
// Get the directory name for ESM modules (compatible with both Node and Bun)
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
// Helper function to display usage
|
// Helper function to display usage
|
||||||
function displayHelp() {
|
function displayHelp() {
|
||||||
console.log(`
|
console.log(`
|
||||||
|
|
@ -83,7 +92,7 @@ Examples:
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
function parseCliArgs() {
|
function parseCliArgs() {
|
||||||
const args = Bun.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
displayHelp();
|
displayHelp();
|
||||||
|
|
@ -120,27 +129,9 @@ function parseCliArgs() {
|
||||||
return { command, ...values };
|
return { command, ...values };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shell command handler
|
|
||||||
async function runShellCommand(scriptName: string, args: string[] = []) {
|
|
||||||
const { spawnSync } = await import("child_process");
|
|
||||||
const scriptDir = import.meta.dir;
|
|
||||||
const scriptPath = `${scriptDir}/scripts/${scriptName}`;
|
|
||||||
|
|
||||||
// Run the shell script
|
|
||||||
const result = spawnSync(scriptPath, args, {
|
|
||||||
stdio: "inherit",
|
|
||||||
cwd: process.cwd(),
|
|
||||||
shell: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
process.exit(result.status || 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server command handler (for deploy.ts, oracle.ts)
|
// Server command handler (for deploy.ts, oracle.ts)
|
||||||
async function runServerCommand(scriptName: string, args: string[] = []) {
|
async function runServerCommand(scriptName: string, args: string[] = []) {
|
||||||
const { spawnSync } = await import("child_process");
|
const scriptPath = join(__dirname, "server", scriptName);
|
||||||
const scriptDir = import.meta.dir;
|
|
||||||
const scriptPath = `${scriptDir}/server/${scriptName}`;
|
|
||||||
|
|
||||||
// Run the TypeScript file directly
|
// Run the TypeScript file directly
|
||||||
const result = spawnSync("bun", ["run", scriptPath, ...args], {
|
const result = spawnSync("bun", ["run", scriptPath, ...args], {
|
||||||
|
|
@ -157,25 +148,25 @@ async function main() {
|
||||||
const args = parseCliArgs();
|
const args = parseCliArgs();
|
||||||
const command = args.command;
|
const command = args.command;
|
||||||
|
|
||||||
// Handle shell script commands (dev and stop need shell for process management)
|
// Handle dev and stop commands
|
||||||
if (command === "dev") {
|
if (command === "dev") {
|
||||||
await runShellCommand("dev.sh");
|
await runDevCommand(__dirname);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === "stop") {
|
if (command === "stop") {
|
||||||
await runShellCommand("stop.sh");
|
await runStopCommand();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle TypeScript commands that can run directly
|
// Handle TypeScript commands that can run directly
|
||||||
if (command === "deploy") {
|
if (command === "deploy") {
|
||||||
await runServerCommand("deploy.ts", Bun.argv.slice(3));
|
await runServerCommand("deploy.js", process.argv.slice(3));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === "start-oracle") {
|
if (command === "start-oracle") {
|
||||||
await runServerCommand("oracle.ts", Bun.argv.slice(3));
|
await runServerCommand("oracle.js", process.argv.slice(3));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,13 +175,13 @@ async function main() {
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case "escrow:create":
|
case "escrow:create":
|
||||||
scriptPath = "./client/create-escrow.ts";
|
scriptPath = "./client/create-escrow.js";
|
||||||
break;
|
break;
|
||||||
case "escrow:fulfill":
|
case "escrow:fulfill":
|
||||||
scriptPath = "./client/fulfill-escrow.ts";
|
scriptPath = "./client/fulfill-escrow.js";
|
||||||
break;
|
break;
|
||||||
case "escrow:collect":
|
case "escrow:collect":
|
||||||
scriptPath = "./client/collect-escrow.ts";
|
scriptPath = "./client/collect-escrow.js";
|
||||||
break;
|
break;
|
||||||
case "escrow:status":
|
case "escrow:status":
|
||||||
await runStatusCommand(args);
|
await runStatusCommand(args);
|
||||||
|
|
@ -203,11 +194,10 @@ async function main() {
|
||||||
|
|
||||||
// Run the command as a subprocess with the args (excluding the command name)
|
// Run the command as a subprocess with the args (excluding the command name)
|
||||||
const { spawnSync } = await import("child_process");
|
const { spawnSync } = await import("child_process");
|
||||||
const scriptDir = import.meta.dir;
|
const fullScriptPath = join(__dirname, scriptPath);
|
||||||
const fullScriptPath = `${scriptDir}/${scriptPath}`;
|
|
||||||
|
|
||||||
// Build args array without the command name
|
// Build args array without the command name
|
||||||
const commandArgs = Bun.argv.slice(3); // Skip bun, script, and command
|
const commandArgs = process.argv.slice(3); // Skip node, script, and command
|
||||||
|
|
||||||
const result = spawnSync("bun", ["run", fullScriptPath, ...commandArgs], {
|
const result = spawnSync("bun", ["run", fullScriptPath, ...commandArgs], {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Complete setup and deployment for local development
|
|
||||||
# This script will:
|
|
||||||
# 1. Check prerequisites
|
|
||||||
# 2. Start Anvil
|
|
||||||
# 3. Deploy contracts
|
|
||||||
# 4. Start the oracle
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
|
|
||||||
echo -e "${BLUE} Natural Language Agreement Oracle - Quick Setup${NC}"
|
|
||||||
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}\n"
|
|
||||||
|
|
||||||
# Check prerequisites
|
|
||||||
echo -e "${BLUE}📋 Checking prerequisites...${NC}\n"
|
|
||||||
|
|
||||||
# Load .env file if it exists
|
|
||||||
if [ -f ".env" ]; then
|
|
||||||
echo -e "${BLUE}📄 Loading .env file...${NC}"
|
|
||||||
export $(cat .env | grep -v '^#' | xargs)
|
|
||||||
echo -e "${GREEN}✅ Environment variables loaded${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check Bun
|
|
||||||
if ! command -v bun &> /dev/null; then
|
|
||||||
echo -e "${RED}❌ Bun is not installed${NC}"
|
|
||||||
echo "Please install it: https://bun.sh"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✅ Bun installed${NC}"
|
|
||||||
|
|
||||||
# Check Foundry
|
|
||||||
if ! command -v forge &> /dev/null; then
|
|
||||||
echo -e "${RED}❌ Foundry (forge) is not installed${NC}"
|
|
||||||
echo "Please install it: https://book.getfoundry.sh/getting-started/installation"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✅ Foundry installed${NC}"
|
|
||||||
|
|
||||||
# Check Anvil
|
|
||||||
if ! command -v anvil &> /dev/null; then
|
|
||||||
echo -e "${RED}❌ Anvil is not installed${NC}"
|
|
||||||
echo "Please install Foundry: https://book.getfoundry.sh/getting-started/installation"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✅ Anvil installed${NC}"
|
|
||||||
|
|
||||||
# Check alkahest
|
|
||||||
if [ ! -d "../alkahest" ]; then
|
|
||||||
echo -e "${RED}❌ alkahest repository not found${NC}"
|
|
||||||
echo "Please clone it in the parent directory:"
|
|
||||||
echo " cd .. && git clone https://github.com/arkhai-io/alkahest.git"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✅ alkahest repository found${NC}"
|
|
||||||
|
|
||||||
# Check LLM API keys
|
|
||||||
if [ -z "$OPENAI_API_KEY" ] && [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$OPENROUTER_API_KEY" ]; then
|
|
||||||
echo -e "${RED}❌ No LLM provider API key set${NC}"
|
|
||||||
echo "Please add at least one API key to your .env file:"
|
|
||||||
echo " OPENAI_API_KEY=sk-..."
|
|
||||||
echo " ANTHROPIC_API_KEY=sk-ant-..."
|
|
||||||
echo " OPENROUTER_API_KEY=sk-or-..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$OPENAI_API_KEY" ]; then
|
|
||||||
echo -e "${GREEN}✅ OpenAI API key configured${NC}"
|
|
||||||
fi
|
|
||||||
if [ -n "$ANTHROPIC_API_KEY" ]; then
|
|
||||||
echo -e "${GREEN}✅ Anthropic API key configured${NC}"
|
|
||||||
fi
|
|
||||||
if [ -n "$OPENROUTER_API_KEY" ]; then
|
|
||||||
echo -e "${GREEN}✅ OpenRouter API key configured${NC}"
|
|
||||||
fi
|
|
||||||
if [ -n "$PERPLEXITY_API_KEY" ]; then
|
|
||||||
echo -e "${GREEN}✅ Perplexity API key configured${NC}"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
echo -e "${BLUE}📦 Installing dependencies...${NC}\n"
|
|
||||||
bun install
|
|
||||||
|
|
||||||
# Check if Anvil is already running
|
|
||||||
if lsof -Pi :8545 -sTCP:LISTEN -t >/dev/null ; then
|
|
||||||
echo -e "${YELLOW}⚠️ Anvil is already running on port 8545${NC}"
|
|
||||||
read -p "Do you want to kill it and start fresh? (y/n) " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
pkill -f anvil || true
|
|
||||||
sleep 2
|
|
||||||
else
|
|
||||||
echo -e "${BLUE}Using existing Anvil instance${NC}\n"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start Anvil in background if not running
|
|
||||||
if ! lsof -Pi :8545 -sTCP:LISTEN -t >/dev/null ; then
|
|
||||||
echo -e "${BLUE}🔨 Starting Anvil...${NC}"
|
|
||||||
anvil > anvil.log 2>&1 &
|
|
||||||
ANVIL_PID=$!
|
|
||||||
echo $ANVIL_PID > .anvil.pid
|
|
||||||
echo -e "${GREEN}✅ Anvil started (PID: $ANVIL_PID)${NC}"
|
|
||||||
echo " Logs: tail -f anvil.log"
|
|
||||||
sleep 3
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Deploy contracts
|
|
||||||
echo -e "\n${BLUE}📝 Deploying contracts...${NC}\n"
|
|
||||||
bun run cli/server/deploy.ts --network localhost --rpc-url http://localhost:8545
|
|
||||||
|
|
||||||
# Start oracle
|
|
||||||
echo -e "\n${BLUE}🚀 Starting oracle...${NC}\n"
|
|
||||||
bun run cli/server/oracle.ts --deployment ./cli/deployments/localhost.json
|
|
||||||
|
|
||||||
# Cleanup function
|
|
||||||
cleanup() {
|
|
||||||
echo -e "\n${YELLOW}🛑 Shutting down...${NC}"
|
|
||||||
if [ -f .anvil.pid ]; then
|
|
||||||
ANVIL_PID=$(cat .anvil.pid)
|
|
||||||
kill $ANVIL_PID 2>/dev/null || true
|
|
||||||
rm .anvil.pid
|
|
||||||
echo -e "${GREEN}✅ Anvil stopped${NC}"
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup SIGINT SIGTERM
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Stop all running oracle and Anvil processes
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
echo -e "${BLUE}🛑 Stopping Natural Language Agreement services...${NC}\n"
|
|
||||||
|
|
||||||
# Stop Anvil
|
|
||||||
if [ -f .anvil.pid ]; then
|
|
||||||
ANVIL_PID=$(cat .anvil.pid)
|
|
||||||
if kill -0 $ANVIL_PID 2>/dev/null; then
|
|
||||||
kill $ANVIL_PID
|
|
||||||
echo -e "${GREEN}✅ Stopped Anvil (PID: $ANVIL_PID)${NC}"
|
|
||||||
fi
|
|
||||||
rm .anvil.pid
|
|
||||||
else
|
|
||||||
# Try to kill any running anvil
|
|
||||||
pkill -f anvil && echo -e "${GREEN}✅ Stopped Anvil${NC}" || echo -e "${YELLOW}⚠️ No Anvil process found${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Stop oracle
|
|
||||||
pkill -f "bun run oracle" && echo -e "${GREEN}✅ Stopped oracle${NC}" || echo -e "${YELLOW}⚠️ No oracle process found${NC}"
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✨ Cleanup complete${NC}"
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Deployment script for Alkahest Natural Language Agreement Oracle
|
* Deployment script for Alkahest Natural Language Agreement Oracle
|
||||||
*
|
*
|
||||||
|
|
@ -54,7 +54,7 @@ Examples:
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
function parseCliArgs() {
|
function parseCliArgs() {
|
||||||
const { values } = parseArgs({
|
const { values } = parseArgs({
|
||||||
args: Bun.argv.slice(2),
|
args: process.argv.slice(2),
|
||||||
options: {
|
options: {
|
||||||
"network": { type: "string" },
|
"network": { type: "string" },
|
||||||
"rpc-url": { type: "string" },
|
"rpc-url": { type: "string" },
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env node
|
||||||
import { parseArgs } from "util";
|
import { parseArgs } from "util";
|
||||||
import { parseAbiParameters, createWalletClient, http, publicActions } from "viem";
|
import { parseAbiParameters, createWalletClient, http, publicActions } from "viem";
|
||||||
import { privateKeyToAccount } from "viem/accounts";
|
import { privateKeyToAccount } from "viem/accounts";
|
||||||
|
|
@ -57,7 +57,7 @@ Examples:
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
function parseCliArgs() {
|
function parseCliArgs() {
|
||||||
const { values } = parseArgs({
|
const { values } = parseArgs({
|
||||||
args: Bun.argv.slice(2),
|
args: process.argv.slice(2),
|
||||||
options: {
|
options: {
|
||||||
"rpc-url": { type: "string" },
|
"rpc-url": { type: "string" },
|
||||||
"private-key": { type: "string" },
|
"private-key": { type: "string" },
|
||||||
|
|
|
||||||
42
package.json
42
package.json
|
|
@ -1,15 +1,45 @@
|
||||||
{
|
{
|
||||||
"name": "natural-language-agreement-extension",
|
"name": "nla",
|
||||||
"module": "index.ts",
|
"version": "1.0.0",
|
||||||
|
"description": "Natural Language Agreement Oracle - CLI for creating and managing blockchain agreements using natural language",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": false,
|
||||||
"devDependencies": {
|
"author": "Arkhai",
|
||||||
"@types/bun": "latest"
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/arkhai-io/natural-language-agreements.git"
|
||||||
},
|
},
|
||||||
|
"keywords": [
|
||||||
|
"blockchain",
|
||||||
|
"ethereum",
|
||||||
|
"smart-contracts",
|
||||||
|
"natural-language",
|
||||||
|
"oracle",
|
||||||
|
"ai",
|
||||||
|
"llm",
|
||||||
|
"escrow"
|
||||||
|
],
|
||||||
"bin": {
|
"bin": {
|
||||||
"nla": "cli/index.ts"
|
"nla": "./dist/cli/index.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*",
|
||||||
|
"README.md",
|
||||||
|
".env.example"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"preferGlobal": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "^20.0.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "tsc --project tsconfig.build.json",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
"dev": "bun run index.ts",
|
"dev": "bun run index.ts",
|
||||||
"start": "bun run index.ts",
|
"start": "bun run index.ts",
|
||||||
"test": "bun test ./tests --exclude alkahest-ts/** ",
|
"test": "bun test ./tests --exclude alkahest-ts/** ",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./",
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": ["cli/**/*"],
|
||||||
|
"exclude": ["node_modules", "tests", "dist"]
|
||||||
|
}
|
||||||
|
|
@ -1,30 +1,16 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Environment setup & latest features
|
"outDir": "dist",
|
||||||
"lib": ["ESNext"],
|
"rootDir": ".",
|
||||||
"target": "ESNext",
|
"target": "ES2022",
|
||||||
"module": "Preserve",
|
"module": "ESNext",
|
||||||
"moduleDetection": "force",
|
"moduleResolution": "Bundler",
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
|
||||||
|
|
||||||
// Bundler mode
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
// Best practices
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true
|
||||||
"noFallthroughCasesInSwitch": true,
|
},
|
||||||
"noUncheckedIndexedAccess": true,
|
"include": [
|
||||||
"noImplicitOverride": true,
|
"cli/**/*"
|
||||||
|
]
|
||||||
// Some stricter flags (disabled by default)
|
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noPropertyAccessFromIndexSignature": false,
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue