Merge pull request #4 from arkhai-io/refactor

Feat: switch network, public npm package
This commit is contained in:
thanhngoc541 2026-01-26 12:08:32 +07:00 committed by GitHub
commit 9b83e5c3d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1000 additions and 268 deletions

2
.gitignore vendored
View File

@ -41,4 +41,4 @@ anvil.log
.env
# Local deployment files
cli/deployments/localhost.json
cli/deployments/devnet.json

52
.npmignore Normal file
View File

@ -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
../

173
INSTALL.md Normal file
View File

@ -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

View File

@ -79,7 +79,7 @@ export OPENAI_API_KEY=sk-your-key-here
nla deploy
```
This creates `cli/deployments/localhost.json` with all contract addresses.
This creates `cli/deployments/devnet.json` with all contract addresses.
#### 3. Start Oracle
@ -316,7 +316,7 @@ natural-language-agreements/
│ │ ├── start-oracle.sh # Oracle starter
│ │ └── stop.sh # Cleanup script
│ └── deployments/ # Deployment addresses (generated)
│ ├── localhost.json
│ ├── devnet.json
│ ├── sepolia.json
│ └── mainnet.json
├── nla.ts # Natural Language Agreement client library

View File

@ -99,7 +99,7 @@ Monitor the escrow and arbitration progress:
```bash
nla escrow:status \
--escrow-uid 0x... \
--deployment ./cli/deployments/localhost.json
--deployment ./cli/deployments/devnet.json
```
This will show:
@ -185,7 +185,7 @@ Options:
--token <address> ERC20 token address (required)
--oracle <address> Oracle address (required)
--private-key <key> Your private key (or set PRIVATE_KEY env var)
--deployment <path> Deployment file (default: ./cli/deployments/localhost.json)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json)
--rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help
```
@ -200,7 +200,7 @@ Options:
--fulfillment <text> Your fulfillment text (required)
--oracle <address> Oracle address (required)
--private-key <key> Your private key (or set PRIVATE_KEY env var)
--deployment <path> Deployment file (default: ./cli/deployments/localhost.json)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json)
--rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help
```
@ -214,7 +214,7 @@ Options:
--escrow-uid <uid> Escrow UID (required)
--fulfillment-uid <uid> Approved fulfillment UID (required)
--private-key <key> Your private key (or set PRIVATE_KEY env var)
--deployment <path> Deployment file (default: ./cli/deployments/localhost.json)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json)
--rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help
```
@ -226,7 +226,7 @@ nla escrow:status [options]
Options:
--escrow-uid <uid> Escrow UID to check (required)
--deployment <path> Deployment file (default: ./cli/deployments/localhost.json)
--deployment <path> Deployment file (default: ./cli/deployments/devnet.json)
--rpc-url <url> RPC URL (default: from deployment)
--help, -h Show help
```
@ -293,7 +293,7 @@ nla escrow:fulfill \
# Terminal 2: Check the status
nla escrow:status \
--escrow-uid 0xd9e1402e96c2f7a64e60bf53a45445f7254e9b72389f6ede25181bff542d7b65 \
--deployment ./cli/deployments/localhost.json
--deployment ./cli/deployments/devnet.json
# Terminal 2: Collect the escrow (as Bob)
nla escrow:collect \

View File

@ -8,10 +8,43 @@
import { parseArgs } from "util";
import { createWalletClient, http, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { foundry } from "viem/chains";
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
import { resolve, dirname, join } from "path";
import { fileURLToPath } from "url";
import { makeClient } from "alkahest-ts";
import { getChainFromNetwork } from "../utils.js";
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Helper function to find deployment file
function findDeploymentFile(deploymentPath: string): string | null {
// Try the provided path first
if (existsSync(resolve(deploymentPath))) {
return resolve(deploymentPath);
}
// Try relative to current working directory
const cwdPath = resolve(process.cwd(), deploymentPath);
if (existsSync(cwdPath)) {
return cwdPath;
}
// Try relative to the CLI installation directory
const cliPath = resolve(__dirname, "..", "deployments", "devnet.json");
if (existsSync(cliPath)) {
return cliPath;
}
// Try in the project root (for local development)
const projectPath = resolve(__dirname, "..", "..", "cli", "deployments", "devnet.json");
if (existsSync(projectPath)) {
return projectPath;
}
return null;
}
// Helper function to display usage
function displayHelp() {
@ -27,7 +60,7 @@ Options:
--escrow-uid <uid> Escrow UID to collect (required)
--fulfillment-uid <uid> Fulfillment UID that was approved (required)
--private-key <key> Your private key (required)
--deployment <path> Path to deployment file (default: ./cli/deployments/localhost.json)
--deployment <path> Path to deployment file (default: ./cli/deployments/devnet.json)
--rpc-url <url> RPC URL (default: from deployment file)
--help, -h Display this help message
@ -80,7 +113,7 @@ async function main() {
const escrowUid = args["escrow-uid"];
const fulfillmentUid = args["fulfillment-uid"];
const privateKey = args["private-key"] || process.env.PRIVATE_KEY;
const deploymentPath = args.deployment || "./cli/deployments/localhost.json";
const deploymentPath = args.deployment || "./cli/deployments/devnet.json";
// Validate required parameters
if (!escrowUid) {
@ -102,14 +135,20 @@ async function main() {
}
// Load deployment file
if (!existsSync(resolve(deploymentPath))) {
const resolvedDeploymentPath = findDeploymentFile(deploymentPath);
if (!resolvedDeploymentPath) {
console.error(`❌ Error: Deployment file not found: ${deploymentPath}`);
console.error("Please deploy contracts first or specify correct path with --deployment");
console.error("\nSearched in:");
console.error(` - ${resolve(deploymentPath)}`);
console.error(` - ${resolve(process.cwd(), deploymentPath)}`);
console.error(` - ${resolve(__dirname, "..", "deployments", "devnet.json")}`);
process.exit(1);
}
const deployment = JSON.parse(readFileSync(resolve(deploymentPath), "utf-8"));
const deployment = JSON.parse(readFileSync(resolvedDeploymentPath, "utf-8"));
const rpcUrl = args["rpc-url"] || deployment.rpcUrl;
const chain = getChainFromNetwork(deployment.network);
console.log("🚀 Collecting Natural Language Agreement Escrow\n");
console.log("Configuration:");
@ -121,7 +160,7 @@ async function main() {
const account = privateKeyToAccount(privateKey as `0x${string}`);
const walletClient = createWalletClient({
account,
chain: foundry,
chain,
transport: http(rpcUrl),
}).extend(publicActions);

View File

@ -9,20 +9,64 @@
import { parseArgs } from "util";
import { createWalletClient, http, publicActions, parseEther } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { foundry } from "viem/chains";
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
import { resolve, dirname, join } from "path";
import { fileURLToPath } from "url";
import { makeClient } from "alkahest-ts";
import { makeLLMClient } from "../..";
import {fixtures} from "alkahest-ts";
import { getCurrentEnvironment } from "../commands/switch.js";
import { getChainFromNetwork } from "../utils.js";
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Helper function to find deployment file
function findDeploymentFile(deploymentPath: string, environment?: string): string | null {
// If no path provided, use current environment
if (!deploymentPath) {
const env = environment || getCurrentEnvironment();
deploymentPath = `./cli/deployments/${env}.json`;
}
// Try the provided path first
if (existsSync(resolve(deploymentPath))) {
return resolve(deploymentPath);
}
// Try relative to current working directory
const cwdPath = resolve(process.cwd(), deploymentPath);
if (existsSync(cwdPath)) {
return cwdPath;
}
// Try relative to the CLI installation directory with current environment
const env = environment || getCurrentEnvironment();
const cliPath = resolve(__dirname, "..", "deployments", `${env}.json`);
if (existsSync(cliPath)) {
return cliPath;
}
// Try in the project root (for local development)
const projectPath = resolve(__dirname, "..", "..", "cli", "deployments", `${env}.json`);
if (existsSync(projectPath)) {
return projectPath;
}
return null;
}
// Helper function to display usage
function displayHelp() {
const currentEnv = getCurrentEnvironment();
console.log(`
Natural Language Agreement Escrow CLI
Create an escrow with a natural language demand that will be arbitrated by an oracle.
Current environment: ${currentEnv}
Usage:
bun cli/create-escrow.ts [options]
@ -32,7 +76,7 @@ Options:
--token <address> ERC20 token address (required)
--oracle <address> Oracle address that will arbitrate (required)
--private-key <key> Your private key (required)
--deployment <path> Path to deployment file (default: ./cli/deployments/localhost.json)
--deployment <path> Path to deployment file (default: current environment)
--rpc-url <url> RPC URL (default: from deployment file)
--arbitration-provider <name> Arbitration provider (default: OpenAI)
--arbitration-model <model> Arbitration model (default: gpt-4o-mini)
@ -97,7 +141,7 @@ async function main() {
const tokenAddress = args.token;
const oracleAddress = args.oracle;
const privateKey = args["private-key"] || process.env.PRIVATE_KEY;
const deploymentPath = args.deployment || "./cli/deployments/localhost.json";
const deploymentPath = args.deployment || `./cli/deployments/${getCurrentEnvironment()}.json`;
// Arbitration configuration with defaults
const arbitrationProvider = args["arbitration-provider"] || "OpenAI";
@ -141,14 +185,20 @@ Fulfillment: {{obligation}}`;
}
// Load deployment file
if (!existsSync(resolve(deploymentPath))) {
const resolvedDeploymentPath = findDeploymentFile(deploymentPath);
if (!resolvedDeploymentPath) {
console.error(`❌ Error: Deployment file not found: ${deploymentPath}`);
console.error("Please deploy contracts first or specify correct path with --deployment");
console.error("\nSearched in:");
console.error(` - ${resolve(deploymentPath)}`);
console.error(` - ${resolve(process.cwd(), deploymentPath)}`);
console.error(` - ${resolve(__dirname, "..", "deployments", "devnet.json")}`);
process.exit(1);
}
const deployment = JSON.parse(readFileSync(resolve(deploymentPath), "utf-8"));
const deployment = JSON.parse(readFileSync(resolvedDeploymentPath, "utf-8"));
const rpcUrl = args["rpc-url"] || deployment.rpcUrl;
const chain = getChainFromNetwork(deployment.network);
console.log("🚀 Creating Natural Language Agreement Escrow\n");
console.log("Configuration:");
@ -156,13 +206,14 @@ Fulfillment: {{obligation}}`;
console.log(` 💰 Amount: ${amount} tokens`);
console.log(` 🪙 Token: ${tokenAddress}`);
console.log(` ⚖️ Oracle: ${oracleAddress}`);
console.log(` 🌐 Network: ${deployment.network}`);
console.log(` 🌐 RPC URL: ${rpcUrl}\n`);
// Create account and wallet
const account = privateKeyToAccount(privateKey as `0x${string}`);
const walletClient = createWalletClient({
account,
chain: foundry,
chain,
transport: http(rpcUrl),
}).extend(publicActions);

View File

@ -9,11 +9,44 @@
import { parseArgs } from "util";
import { createWalletClient, http, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { foundry } from "viem/chains";
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
import { resolve, dirname, join } from "path";
import { fileURLToPath } from "url";
import { makeClient } from "alkahest-ts";
import { makeLLMClient } from "../..";
import { getChainFromNetwork } from "../utils.js";
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Helper function to find deployment file
function findDeploymentFile(deploymentPath: string): string | null {
// Try the provided path first
if (existsSync(resolve(deploymentPath))) {
return resolve(deploymentPath);
}
// Try relative to current working directory
const cwdPath = resolve(process.cwd(), deploymentPath);
if (existsSync(cwdPath)) {
return cwdPath;
}
// Try relative to the CLI installation directory
const cliPath = resolve(__dirname, "..", "deployments", "devnet.json");
if (existsSync(cliPath)) {
return cliPath;
}
// Try in the project root (for local development)
const projectPath = resolve(__dirname, "..", "..", "cli", "deployments", "devnet.json");
if (existsSync(projectPath)) {
return projectPath;
}
return null;
}
// Helper function to display usage
function displayHelp() {
@ -30,7 +63,7 @@ Options:
--fulfillment <text> Your fulfillment text (required)
--oracle <address> Oracle address that will arbitrate (required)
--private-key <key> Your private key (required)
--deployment <path> Path to deployment file (default: ./cli/deployments/localhost.json)
--deployment <path> Path to deployment file (default: ./cli/deployments/devnet.json)
--rpc-url <url> RPC URL (default: from deployment file)
--help, -h Display this help message
@ -86,7 +119,7 @@ async function main() {
const fulfillment = args.fulfillment;
const oracleAddress = args.oracle;
const privateKey = args["private-key"] || process.env.PRIVATE_KEY;
const deploymentPath = args.deployment || "./cli/deployments/localhost.json";
const deploymentPath = args.deployment || "./cli/deployments/devnet.json";
// Validate required parameters
if (!escrowUid) {
@ -114,27 +147,34 @@ async function main() {
}
// Load deployment file
if (!existsSync(resolve(deploymentPath))) {
const resolvedDeploymentPath = findDeploymentFile(deploymentPath);
if (!resolvedDeploymentPath) {
console.error(`❌ Error: Deployment file not found: ${deploymentPath}`);
console.error("Please deploy contracts first or specify correct path with --deployment");
console.error("\nSearched in:");
console.error(` - ${resolve(deploymentPath)}`);
console.error(` - ${resolve(process.cwd(), deploymentPath)}`);
console.error(` - ${resolve(__dirname, "..", "deployments", "devnet.json")}`);
process.exit(1);
}
const deployment = JSON.parse(readFileSync(resolve(deploymentPath), "utf-8"));
const deployment = JSON.parse(readFileSync(resolvedDeploymentPath, "utf-8"));
const rpcUrl = args["rpc-url"] || deployment.rpcUrl;
const chain = getChainFromNetwork(deployment.network);
console.log("🚀 Fulfilling Natural Language Agreement Escrow\n");
console.log("Configuration:");
console.log(` 📦 Escrow UID: ${escrowUid}`);
console.log(` 📝 Fulfillment: "${fulfillment}"`);
console.log(` ⚖️ Oracle: ${oracleAddress}`);
console.log(` 🌐 Network: ${deployment.network}`);
console.log(` 🌐 RPC URL: ${rpcUrl}\n`);
// Create account and wallet
const account = privateKeyToAccount(privateKey as `0x${string}`);
const walletClient = createWalletClient({
account,
chain: foundry,
chain,
transport: http(rpcUrl),
}).extend(publicActions);

250
cli/commands/dev.ts Normal file
View File

@ -0,0 +1,250 @@
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(envPath: string = '.env') {
if (existsSync(envPath)) {
console.log(`${colors.blue}📄 Loading .env file from: ${envPath}${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}`);
} else if (envPath !== '.env') {
// Only error if a custom path was specified but not found
console.error(`${colors.red}❌ .env file not found at: ${envPath}${colors.reset}`);
process.exit(1);
}
}
// 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, envPath?: 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(envPath);
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');
// Look for deployment file in source directory (for local dev) or dist directory (for installed package)
const sourcePath = join(process.cwd(), 'cli', 'deployments', 'devnet.json');
const distPath = join(cliDir, 'deployments', 'devnet.json');
const deploymentFile = existsSync(sourcePath) ? sourcePath : distPath;
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);
// Open a new terminal
console.log(`\n${colors.green}✅ Setup complete!${colors.reset}`);
console.log(`${colors.blue}Opening new terminal...${colors.reset}\n`);
try {
// For macOS, open a new Terminal window
if (process.platform === 'darwin') {
spawn('osascript', [
'-e',
`tell application "Terminal" to do script "cd ${process.cwd()}"`
], { detached: true, stdio: 'ignore' }).unref();
}
// For Linux, try common terminal emulators
else if (process.platform === 'linux') {
const terminals = ['gnome-terminal', 'konsole', 'xterm'];
for (const term of terminals) {
if (commandExists(term)) {
spawn(term, ['--working-directory', process.cwd()], {
detached: true,
stdio: 'ignore'
}).unref();
break;
}
}
}
// For Windows
else if (process.platform === 'win32') {
spawn('cmd', ['/c', 'start', 'cmd', '/K', `cd /d ${process.cwd()}`], {
detached: true,
stdio: 'ignore'
}).unref();
}
} catch (e) {
console.log(`${colors.yellow}⚠️ Could not open new terminal automatically${colors.reset}`);
}
// Keep process alive
await new Promise(() => {});
}

43
cli/commands/stop.ts Normal file
View File

@ -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}`);
}

127
cli/commands/switch.ts Normal file
View File

@ -0,0 +1,127 @@
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
import { join, dirname } from "path";
import { homedir } from "os";
import { fileURLToPath } from "url";
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Colors for console output
const colors = {
green: '\x1b[32m',
blue: '\x1b[34m',
red: '\x1b[31m',
yellow: '\x1b[33m',
reset: '\x1b[0m'
};
// Get NLA config directory
function getNLAConfigDir(): string {
const configDir = join(homedir(), '.nla');
if (!existsSync(configDir)) {
mkdirSync(configDir, { recursive: true });
}
return configDir;
}
// Get current environment
export function getCurrentEnvironment(): string {
const configPath = join(getNLAConfigDir(), 'config.json');
if (!existsSync(configPath)) {
// Default to devnet
return 'devnet';
}
try {
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
return config.environment || 'devnet';
} catch (e) {
return 'devnet';
}
}
// Set current environment
function setCurrentEnvironment(env: string): void {
const configPath = join(getNLAConfigDir(), 'config.json');
const config = existsSync(configPath)
? JSON.parse(readFileSync(configPath, 'utf-8'))
: {};
config.environment = env;
writeFileSync(configPath, JSON.stringify(config, null, 2));
}
// Get deployment path for environment
export function getDeploymentPath(cliDir: string, env?: string): string {
const environment = env || getCurrentEnvironment();
const filename = `${environment}.json`;
// Try multiple locations
const paths = [
join(cliDir, 'deployments', filename), // dist/cli/deployments/
join(__dirname, '..', 'deployments', filename), // Relative to switch.ts
join(process.cwd(), 'cli', 'deployments', filename), // Project root
];
for (const path of paths) {
if (existsSync(path)) {
return path;
}
}
// Return the first path as default (even if it doesn't exist yet)
return paths[0];
}
// Switch command
export function runSwitchCommand(env?: string) {
console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}`);
console.log(`${colors.blue} Natural Language Agreement - Environment Switch${colors.reset}`);
console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}\n`);
// If no environment specified, show current
if (!env) {
const current = getCurrentEnvironment();
console.log(`${colors.blue}Current environment:${colors.reset} ${colors.green}${current}${colors.reset}\n`);
console.log('Available environments:');
console.log(' • devnet (local Anvil blockchain)');
console.log(' • sepolia (Ethereum Sepolia testnet)');
console.log(' • mainnet (Ethereum mainnet)\n');
console.log(`${colors.yellow}Usage:${colors.reset} nla switch <environment>`);
console.log(`${colors.yellow}Example:${colors.reset} nla switch sepolia\n`);
return;
}
// Validate environment
const validEnvs = ['devnet', 'sepolia', 'mainnet'];
if (!validEnvs.includes(env)) {
console.error(`${colors.red}❌ Invalid environment: ${env}${colors.reset}`);
console.log('Valid environments: devnet, sepolia, mainnet\n');
process.exit(1);
}
// Switch environment
const currentEnv = getCurrentEnvironment();
if (currentEnv === env) {
console.log(`${colors.yellow}⚠️ Already on ${env}${colors.reset}\n`);
return;
}
setCurrentEnvironment(env);
console.log(`${colors.green}✅ Switched to ${env}${colors.reset}\n`);
// Show info about the environment
if (env === 'devnet') {
console.log('📝 Using local Anvil blockchain (http://localhost:8545)');
console.log(' Run "nla dev" to start the development environment\n');
} else if (env === 'sepolia') {
console.log('📝 Using Ethereum Sepolia testnet');
console.log(' Make sure you have deployed contracts and updated sepolia.json\n');
} else if (env === 'mainnet') {
console.log('📝 Using Ethereum mainnet');
console.log(` ${colors.yellow}⚠️ WARNING: This is production! Use with caution.${colors.reset}\n`);
}
}

View File

@ -0,0 +1,50 @@
{
"network": "sepolia",
"chainId": 11155111,
"rpcUrl": "https://eth-sepolia.g.alchemy.com/v2/YOUR_ALCHEMY_KEY",
"addresses": {
"eas": "",
"easSchemaRegistry": "",
"erc20EscrowObligation": "",
"erc20PaymentObligation": "",
"erc20BarterUtils": "",
"erc721EscrowObligation": "",
"erc721PaymentObligation": "",
"erc721BarterUtils": "",
"erc1155EscrowObligation": "",
"erc1155BarterUtils": "",
"erc1155PaymentObligation": "",
"tokenBundleEscrowObligation": "",
"tokenBundlePaymentObligation": "",
"tokenBundleBarterUtils": "",
"attestationEscrowObligation": "",
"attestationEscrowObligation2": "",
"attestationBarterUtils": "",
"stringObligation": "",
"trivialArbiter": "",
"trustedOracleArbiter": "",
"anyArbiter": "",
"allArbiter": "",
"intrinsicsArbiter": "",
"intrinsicsArbiter2": "",
"exclusiveRevocableConfirmationArbiter": "",
"exclusiveUnrevocableConfirmationArbiter": "",
"nonexclusiveRevocableConfirmationArbiter": "",
"nonexclusiveUnrevocableConfirmationArbiter": "",
"nativeTokenEscrowObligation": "",
"nativeTokenPaymentObligation": "",
"nativeTokenBarterUtils": "",
"recipientArbiter": "",
"attesterArbiter": "",
"schemaArbiter": "",
"uidArbiter": "",
"refUidArbiter": "",
"revocableArbiter": "",
"timeAfterArbiter": "",
"timeBeforeArbiter": "",
"timeEqualArbiter": "",
"expirationTimeAfterArbiter": "",
"expirationTimeBeforeArbiter": "",
"expirationTimeEqualArbiter": ""
}
}

View File

@ -1,11 +1,21 @@
#!/usr/bin/env bun
#!/usr/bin/env node
import { parseArgs } from "util";
import { spawnSync } from "child_process";
import { existsSync, readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import { createPublicClient, http, parseAbiParameters, decodeAbiParameters } from "viem";
import { foundry } from "viem/chains";
import { contracts } from "alkahest-ts";
import { runDevCommand } from "./commands/dev.js";
import { runStopCommand } from "./commands/stop.js";
import { runSwitchCommand } from "./commands/switch.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
function displayHelp() {
console.log(`
@ -19,6 +29,8 @@ Commands:
deploy Deploy contracts to blockchain
start-oracle Start the oracle service
stop Stop all services (Anvil + Oracle)
switch [env] Switch between environments (devnet, sepolia, mainnet)
network Show current network/environment
escrow:create Create a new escrow with natural language demand
escrow:fulfill Fulfill an existing escrow
escrow:collect Collect an approved escrow
@ -39,6 +51,7 @@ Options (vary by command):
--arbitration-provider <name> Arbitration provider (create, default: OpenAI)
--arbitration-model <model> Arbitration model (create, default: gpt-4o-mini)
--arbitration-prompt <text> Custom arbitration prompt (create, optional)
--env <file> Path to .env file (dev, default: .env)
Environment Variables:
PRIVATE_KEY Private key for transactions
@ -49,6 +62,9 @@ Examples:
# Start development environment
nla dev
# Start development with custom .env file
nla dev --env /path/to/.env.production
# Deploy contracts
nla deploy
@ -83,7 +99,7 @@ Examples:
// Parse command line arguments
function parseCliArgs() {
const args = Bun.argv.slice(2);
const args = process.argv.slice(2);
if (args.length === 0) {
displayHelp();
@ -113,34 +129,19 @@ function parseCliArgs() {
"arbitration-provider": { type: "string" },
"arbitration-model": { type: "string" },
"arbitration-prompt": { type: "string" },
"env": { type: "string" },
"environment": { type: "string" },
},
strict: true,
strict: command !== "switch" && command !== "network", // Allow positional args for switch command
allowPositionals: command === "switch" || command === "network",
});
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)
async function runServerCommand(scriptName: string, args: string[] = []) {
const { spawnSync } = await import("child_process");
const scriptDir = import.meta.dir;
const scriptPath = `${scriptDir}/server/${scriptName}`;
const scriptPath = join(__dirname, "server", scriptName);
// Run the TypeScript file directly
const result = spawnSync("bun", ["run", scriptPath, ...args], {
@ -157,25 +158,38 @@ async function main() {
const args = parseCliArgs();
const command = args.command;
// Handle shell script commands (dev and stop need shell for process management)
// Handle dev and stop commands
if (command === "dev") {
await runShellCommand("dev.sh");
await runDevCommand(__dirname, args.env as string | undefined);
return;
}
if (command === "stop") {
await runShellCommand("stop.sh");
await runStopCommand();
return;
}
if (command === "switch") {
// Get environment from either --environment flag or second positional arg
const env = args.environment as string | undefined || process.argv[3];
runSwitchCommand(env);
return;
}
if (command === "network") {
// Show current network (same as switch with no args)
runSwitchCommand();
return;
}
// Handle TypeScript commands that can run directly
if (command === "deploy") {
await runServerCommand("deploy.ts", Bun.argv.slice(3));
await runServerCommand("deploy.js", process.argv.slice(3));
return;
}
if (command === "start-oracle") {
await runServerCommand("oracle.ts", Bun.argv.slice(3));
await runServerCommand("oracle.js", process.argv.slice(3));
return;
}
@ -184,13 +198,13 @@ async function main() {
switch (command) {
case "escrow:create":
scriptPath = "./client/create-escrow.ts";
scriptPath = "./client/create-escrow.js";
break;
case "escrow:fulfill":
scriptPath = "./client/fulfill-escrow.ts";
scriptPath = "./client/fulfill-escrow.js";
break;
case "escrow:collect":
scriptPath = "./client/collect-escrow.ts";
scriptPath = "./client/collect-escrow.js";
break;
case "escrow:status":
await runStatusCommand(args);
@ -203,11 +217,10 @@ async function main() {
// Run the command as a subprocess with the args (excluding the command name)
const { spawnSync } = await import("child_process");
const scriptDir = import.meta.dir;
const fullScriptPath = `${scriptDir}/${scriptPath}`;
const fullScriptPath = join(__dirname, scriptPath);
// 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], {
stdio: "inherit",

View File

@ -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

View File

@ -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}"

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bun
#!/usr/bin/env node
/**
* Deployment script for Alkahest Natural Language Agreement Oracle
*
@ -54,7 +54,7 @@ Examples:
// Parse command line arguments
function parseCliArgs() {
const { values } = parseArgs({
args: Bun.argv.slice(2),
args: process.argv.slice(2),
options: {
"network": { type: "string" },
"rpc-url": { type: "string" },
@ -329,7 +329,10 @@ async function main() {
// Get the script directory and go up to project root, then into cli/deployments
const scriptDir = import.meta.dir;
const projectRoot = resolve(scriptDir, "../..");
const outputPath = args.output || resolve(projectRoot, `cli/deployments/${network}.json`);
// Map localhost to devnet for file naming
const deploymentFileName = network === "localhost" ? "devnet" : network;
const outputPath = args.output || resolve(projectRoot, `cli/deployments/${deploymentFileName}.json`);
const outputDir = resolve(outputPath, "..");
if (!existsSync(outputDir)) {

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bun
#!/usr/bin/env node
import { parseArgs } from "util";
import { parseAbiParameters, createWalletClient, http, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
@ -44,7 +44,7 @@ Examples:
bun oracle.ts --rpc-url http://localhost:8545 --private-key 0x... --openai-api-key sk-...
# Using deployment file
bun oracle.ts --deployment ./deployments/localhost.json --private-key 0x... --openai-api-key sk-...
bun oracle.ts --deployment ./deployments/devnet.json --private-key 0x... --openai-api-key sk-...
# Using environment variables
export OPENAI_API_KEY=sk-...
@ -57,7 +57,7 @@ Examples:
// Parse command line arguments
function parseCliArgs() {
const { values } = parseArgs({
args: Bun.argv.slice(2),
args: process.argv.slice(2),
options: {
"rpc-url": { type: "string" },
"private-key": { type: "string" },

23
cli/utils.ts Normal file
View File

@ -0,0 +1,23 @@
/**
* Shared utilities for NLA CLI
*/
import { foundry, sepolia, mainnet } from "viem/chains";
import type { Chain } from "viem/chains";
/**
* Get viem chain configuration from network name
*/
export function getChainFromNetwork(network: string): Chain {
switch (network.toLowerCase()) {
case "localhost":
case "devnet":
return foundry;
case "sepolia":
return sepolia;
case "mainnet":
return mainnet;
default:
return foundry;
}
}

View File

@ -1,16 +1,50 @@
{
"name": "natural-language-agreement-extension",
"module": "index.ts",
"name": "nla",
"version": "1.0.3",
"description": "Natural Language Agreement Oracle - CLI for creating and managing blockchain agreements using natural language",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
"private": false,
"author": "Arkhai",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/arkhai-io/natural-language-agreements.git"
},
"keywords": [
"blockchain",
"ethereum",
"smart-contracts",
"natural-language",
"oracle",
"ai",
"llm",
"escrow"
],
"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": {
"build": "tsc --project tsconfig.build.json && chmod +x dist/cli/index.js",
"prepublishOnly": "npm run build",
"patch": "npm version patch",
"minor": "npm version minor",
"major": "npm version major",
"dev": "bun run index.ts",
"public": "npm publish --access public",
"start": "bun run index.ts",
"test": "bun test ./tests --exclude alkahest-ts/** ",
"setup": "bun run cli/index.ts dev",

16
tsconfig.build.json Normal file
View File

@ -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"]
}

View File

@ -1,30 +1,16 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
"outDir": "dist",
"rootDir": ".",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
}
"skipLibCheck": true
},
"include": [
"cli/**/*"
]
}