mantle-forge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PUBLISH_INSTRUCTIONS.md +57 -0
- package/README.md +170 -0
- package/index.js +580 -0
- package/package.json +43 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Publishing MantlePush CLI to npm
|
|
2
|
+
|
|
3
|
+
## Publishing Steps
|
|
4
|
+
|
|
5
|
+
1. **Verify npm authentication:**
|
|
6
|
+
```bash
|
|
7
|
+
npm whoami
|
|
8
|
+
```
|
|
9
|
+
If not logged in:
|
|
10
|
+
```bash
|
|
11
|
+
npm login
|
|
12
|
+
# Or create account: npm adduser
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
2. **Check package name availability:**
|
|
16
|
+
```bash
|
|
17
|
+
npm view @marshalz/git-mantle-agent
|
|
18
|
+
```
|
|
19
|
+
Should return 404 (not found) - package name is available!
|
|
20
|
+
|
|
21
|
+
3. **Prepare for publishing:**
|
|
22
|
+
```bash
|
|
23
|
+
cd git-agent-cli
|
|
24
|
+
npm version patch # Increment version (e.g., 1.0.2 ā 1.0.3)
|
|
25
|
+
npm publish
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
4. **Post-publishing installation:**
|
|
29
|
+
Users can now install the MantlePush CLI globally:
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g @marshalz/git-mantle-agent
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Package Details
|
|
35
|
+
|
|
36
|
+
- **Package name**: `@marshalz/git-mantle-agent` (npm registry identifier)
|
|
37
|
+
- **Command name**: `git-mantle-agent` (accessed via Git alias)
|
|
38
|
+
- **Versioning**: Follow semantic versioning (major.minor.patch)
|
|
39
|
+
- **Dependencies**: All required packages must be listed in `package.json`
|
|
40
|
+
|
|
41
|
+
## User Setup Instructions
|
|
42
|
+
|
|
43
|
+
After installing from npm, users configure the Git alias:
|
|
44
|
+
```bash
|
|
45
|
+
git config --global alias.mantle-agent '!git-mantle-agent'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Alternatively, users can invoke the CLI directly:
|
|
49
|
+
```bash
|
|
50
|
+
git-mantle-agent stats
|
|
51
|
+
git-mantle-agent compare main aggressive
|
|
52
|
+
git-mantle-agent logs
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Mantle Network Integration
|
|
56
|
+
|
|
57
|
+
This CLI package is designed specifically for Mantle Sepolia testnet deployments. All agent contracts are deployed to Mantle's Layer 2 infrastructure, providing fast and cost-effective AI agent execution.
|
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# MantleForge CLI
|
|
2
|
+
|
|
3
|
+
**Deploy AI Agents on Mantle with Git** - Professional command-line interface for the MantleForge deployment platform.
|
|
4
|
+
|
|
5
|
+
MantleForge transforms AI agent deployment into a seamless Git-native workflow. Deploy autonomous agents to Mantle Sepolia testnet with a single `git push`. Each branch becomes a unique smart contract, enabling true parallel A/B testing of trading strategies.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### Quick Install (For Users)
|
|
10
|
+
|
|
11
|
+
**Option 1: Install from npm (Recommended)**
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g mantle-forge
|
|
14
|
+
|
|
15
|
+
# Start using MantleForge CLI
|
|
16
|
+
mantle-forge stats
|
|
17
|
+
mantle-forge compare main aggressive
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Option 2: Install from source (Development)**
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/xaviersharwin10/somnia-git-agent.git
|
|
23
|
+
cd somnia-git-agent/git-agent-cli
|
|
24
|
+
npm install -g .
|
|
25
|
+
|
|
26
|
+
# Set up Git alias
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Development
|
|
30
|
+
```bash
|
|
31
|
+
npm link
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## CLI Commands
|
|
35
|
+
|
|
36
|
+
* `mantle-forge init` - Configure MantleForge deployment pipeline for your repository
|
|
37
|
+
* `mantle-forge secrets set <KEY=VALUE>` - Store encrypted secrets for the current branch (e.g., `GROQ_API_KEY=sk-...`)
|
|
38
|
+
* `mantle-forge secrets check` - Verify which secrets are configured for your agent
|
|
39
|
+
* `mantle-forge stats` - Display real-time performance metrics from your Mantle agent
|
|
40
|
+
* `mantle-forge logs` - Stream the latest 50 log entries from your agent process
|
|
41
|
+
* `mantle-forge compare <branch1> <branch2>` - Side-by-side comparison of two agent strategies
|
|
42
|
+
* `mantle-forge restart` - Restart the agent for the current branch
|
|
43
|
+
|
|
44
|
+
You can use `mantle-forge` directly as shown above.
|
|
45
|
+
|
|
46
|
+
## Key Features
|
|
47
|
+
|
|
48
|
+
- š **One-Command Setup**: Interactive initialization that configures everything automatically
|
|
49
|
+
- š **Enterprise-Grade Security**: AES-256 encrypted secret storage, one set per Git branch
|
|
50
|
+
- š **Live Performance Tracking**: Real-time metrics from agents executing on Mantle Sepolia
|
|
51
|
+
- š **Parallel Strategy Testing**: Compare multiple trading strategies running simultaneously
|
|
52
|
+
- āļø **Native Mantle Integration**: Built specifically for Mantle's Layer 2 infrastructure
|
|
53
|
+
- šØ **Developer-Friendly UX**: Color-coded terminal output with clear, actionable feedback
|
|
54
|
+
- ā” **Lightweight & Fast**: Minimal dependencies, instant command execution
|
|
55
|
+
|
|
56
|
+
## Mantle Sepolia Integration
|
|
57
|
+
|
|
58
|
+
MantleForge leverages Mantle's high-performance Layer 2 infrastructure to deploy and manage AI agents:
|
|
59
|
+
|
|
60
|
+
- **Smart Contract Deployment**: Each Git branch deploys as a unique `Agent.sol` contract on Mantle Sepolia
|
|
61
|
+
- **On-Chain Identity**: Every agent has a permanent contract address on Mantle blockchain
|
|
62
|
+
- **DEX Integration**: Agents execute real trades on Mantle-compatible decentralized exchanges
|
|
63
|
+
- **Factory Pattern**: Centralized `AgentFactory` contract manages all agent deployments
|
|
64
|
+
- **Blockchain-Backed Registry**: Agent addresses persist on-chain, surviving backend failures
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Initialize Repository for Mantle Deployment
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
mantle-forge init
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The initialization process:
|
|
75
|
+
- Creates `.gitagent.json` configuration file in your repository root
|
|
76
|
+
- Prompts for GitHub repository URL (used for webhook auto-configuration)
|
|
77
|
+
- Provides step-by-step instructions for connecting to MantleForge backend
|
|
78
|
+
- Guides you through secret management and first deployment workflow
|
|
79
|
+
|
|
80
|
+
### Configure Agent Secrets
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
mantle-forge secrets set GROQ_API_KEY=sk-your-key-here
|
|
84
|
+
mantle-forge secrets set AGENT_PRIVATE_KEY=0x...
|
|
85
|
+
mantle-forge secrets set AI_PROMPT="You are an aggressive trader"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Security Features:**
|
|
89
|
+
- Secrets are encrypted using AES-256 before storage
|
|
90
|
+
- Each Git branch maintains its own isolated secret set
|
|
91
|
+
- Secrets are automatically injected as environment variables when agents deploy to Mantle
|
|
92
|
+
- Never stored in plaintext - all encryption handled by MantleForge backend
|
|
93
|
+
|
|
94
|
+
### Monitor Agent Performance
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
mantle-forge stats # View comprehensive performance metrics
|
|
98
|
+
mantle-forge logs # Stream real-time agent decision logs
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `stats` command displays:
|
|
102
|
+
- Total trading decisions made
|
|
103
|
+
- BUY vs HOLD signal breakdown
|
|
104
|
+
- Number of executed trades
|
|
105
|
+
- Success rate percentage
|
|
106
|
+
- Price statistics (average, min, max)
|
|
107
|
+
- Activity timeline (first/last decision timestamps)
|
|
108
|
+
|
|
109
|
+
### Compare Trading Strategies
|
|
110
|
+
|
|
111
|
+
Run parallel A/B tests by comparing different Git branches deployed as separate Mantle contracts:
|
|
112
|
+
```bash
|
|
113
|
+
mantle-forge compare main aggressive
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Comparison Metrics:**
|
|
117
|
+
- Decision volume (total decisions per strategy)
|
|
118
|
+
- Signal distribution (BUY vs HOLD ratios)
|
|
119
|
+
- Trade execution counts
|
|
120
|
+
- Win rate percentages
|
|
121
|
+
- Average trading prices
|
|
122
|
+
- Performance winner analysis
|
|
123
|
+
|
|
124
|
+
Perfect for testing different AI prompts, trading thresholds, or risk parameters across multiple Mantle agent contracts simultaneously.
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
The CLI creates a `.mantlepush.json` file in your repository root:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"repo_url": "https://github.com/username/repo.git"
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
To test the CLI locally:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm link
|
|
142
|
+
mantle-forge --help
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## About MantleForge
|
|
146
|
+
|
|
147
|
+
MantleForge is a Git-native deployment platform built specifically for Mantle Network. It brings the simplicity of Vercel-style deployments to blockchain AI agents, making Mantle the easiest network to deploy autonomous agents on.
|
|
148
|
+
|
|
149
|
+
**Key Benefits:**
|
|
150
|
+
- ā” **30-second deployments** - From `git push` to live on-chain agent
|
|
151
|
+
- š **Branch-based A/B testing** - Test multiple strategies simultaneously
|
|
152
|
+
- š **Enterprise security** - Encrypted secrets, on-chain identity
|
|
153
|
+
- š **Real-time monitoring** - CLI and web dashboard for agent metrics
|
|
154
|
+
|
|
155
|
+
**Repository**: https://github.com/xaviersharwin10/somnia-git-agent
|
|
156
|
+
**Live Dashboard**: https://mantle-git-agent.onrender.com/dashboard
|
|
157
|
+
**Mantle Explorer**: https://sepolia.mantlescan.xyz
|
|
158
|
+
|
|
159
|
+
## Requirements
|
|
160
|
+
|
|
161
|
+
- **Node.js** 16+ (for CLI tool)
|
|
162
|
+
- **Git** repository (for version control)
|
|
163
|
+
- **MantleForge Backend** (deployed at https://mantle-git-agent.onrender.com or self-hosted)
|
|
164
|
+
- **Mantle Sepolia Testnet** access (for contract deployment)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const axios = require('axios');
|
|
5
|
+
const shell = require('shelljs');
|
|
6
|
+
const { createPromptModule } = require('inquirer');
|
|
7
|
+
const prompt = createPromptModule();
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program.name('mantle-forge');
|
|
14
|
+
program.version('1.0.0');
|
|
15
|
+
|
|
16
|
+
// --- Configuration ---
|
|
17
|
+
// MantleForge backend API endpoint
|
|
18
|
+
// For local development, use http://localhost:3005
|
|
19
|
+
// For production deployments, use your deployed backend URL
|
|
20
|
+
const API_BASE_URL = 'https://mantle-git-agent.onrender.com'; // Production MantleForge backend
|
|
21
|
+
const CONFIG_FILE = '.mantlepush.json';
|
|
22
|
+
|
|
23
|
+
// --- Helper Functions ---
|
|
24
|
+
|
|
25
|
+
// Reads the .mantlepush.json file (or migrates from .gitagent.json)
|
|
26
|
+
function getConfig() {
|
|
27
|
+
// Check for new config file first
|
|
28
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
29
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check for old config file and migrate
|
|
33
|
+
const oldConfigFile = '.gitagent.json';
|
|
34
|
+
if (fs.existsSync(oldConfigFile)) {
|
|
35
|
+
console.log(chalk.yellow(`Migrating from ${oldConfigFile} to ${CONFIG_FILE}...`));
|
|
36
|
+
const oldConfig = JSON.parse(fs.readFileSync(oldConfigFile, 'utf8'));
|
|
37
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(oldConfig, null, 2));
|
|
38
|
+
console.log(chalk.green(`ā
Migrated to ${CONFIG_FILE}`));
|
|
39
|
+
return oldConfig;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// No config file found
|
|
43
|
+
console.error(chalk.red(`Error: This repository is not configured for MantleForge. Missing ${CONFIG_FILE}.`));
|
|
44
|
+
console.log(chalk.yellow('Run `mantle-forge init` to initialize MantleForge in this repository.'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Gets the current git branch
|
|
51
|
+
function getCurrentBranch() {
|
|
52
|
+
const branch = shell.exec('git rev-parse --abbrev-ref HEAD', { silent: true }).stdout.trim();
|
|
53
|
+
if (!branch) {
|
|
54
|
+
console.error(chalk.red('Error: Could not determine git branch.'));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
return branch;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Calculate branch_hash (same as backend)
|
|
61
|
+
const { ethers } = require('ethers');
|
|
62
|
+
|
|
63
|
+
function calculateBranchHash(repo_url, branch_name) {
|
|
64
|
+
return ethers.id(repo_url + "/" + branch_name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Helper function to fetch stats for a specific branch
|
|
68
|
+
async function getStats(repo_url, branch_name) {
|
|
69
|
+
try {
|
|
70
|
+
const branch_hash = calculateBranchHash(repo_url, branch_name);
|
|
71
|
+
const url = `${API_BASE_URL}/api/stats/${branch_hash}`;
|
|
72
|
+
const { data } = await axios.get(url);
|
|
73
|
+
return { ...data, branch_name, repo_url };
|
|
74
|
+
} catch (err) {
|
|
75
|
+
const errorMsg = err.response?.data?.error || err.message;
|
|
76
|
+
if (err.response?.status === 404) {
|
|
77
|
+
console.error(chalk.red(`Agent not found for branch "${branch_name}"`));
|
|
78
|
+
console.log(chalk.yellow(` ā Make sure you've pushed this branch: ${chalk.cyan(`git push origin ${branch_name}`)}`));
|
|
79
|
+
console.log(chalk.yellow(` ā The backend webhook will deploy it automatically`));
|
|
80
|
+
} else {
|
|
81
|
+
console.error(chalk.red(`Error fetching stats for ${branch_name}: ${errorMsg}`));
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --- CLI Commands ---
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 1. INIT
|
|
91
|
+
* Initializes the project by creating .mantlepush.json
|
|
92
|
+
*/
|
|
93
|
+
program
|
|
94
|
+
.command('init')
|
|
95
|
+
.description('Initialize MantleForge deployment pipeline for this repository')
|
|
96
|
+
.action(async () => {
|
|
97
|
+
// Check for both old and new config file names
|
|
98
|
+
const oldConfigFile = '.gitagent.json';
|
|
99
|
+
const hasOldConfig = fs.existsSync(oldConfigFile);
|
|
100
|
+
const hasNewConfig = fs.existsSync(CONFIG_FILE);
|
|
101
|
+
|
|
102
|
+
if (hasNewConfig) {
|
|
103
|
+
const existingConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
104
|
+
console.log(chalk.yellow(`This project is already initialized.`));
|
|
105
|
+
console.log(chalk.cyan(`Current repository: ${existingConfig.repo_url}`));
|
|
106
|
+
console.log(chalk.yellow(`\nTo reinitialize with a different repository, delete ${CONFIG_FILE} first.`));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Migrate from old config file if it exists, but still prompt to confirm/change
|
|
111
|
+
if (hasOldConfig) {
|
|
112
|
+
const oldConfig = JSON.parse(fs.readFileSync(oldConfigFile, 'utf8'));
|
|
113
|
+
console.log(chalk.yellow(`Found old config file (${oldConfigFile}).`));
|
|
114
|
+
console.log(chalk.cyan(`Current repository: ${oldConfig.repo_url}`));
|
|
115
|
+
console.log(chalk.yellow(`\nDo you want to keep this repository or change it?`));
|
|
116
|
+
|
|
117
|
+
const answers = await prompt([
|
|
118
|
+
{
|
|
119
|
+
type: 'input',
|
|
120
|
+
name: 'repo_url',
|
|
121
|
+
message: 'What is your GitHub repository URL (e.g., https://github.com/user/repo.git)?',
|
|
122
|
+
default: oldConfig.repo_url || shell.exec('git remote get-url origin', { silent: true }).stdout.trim(),
|
|
123
|
+
}
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
if (!answers.repo_url) {
|
|
127
|
+
console.error(chalk.red('Error: Repository URL is required.'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const config = { repo_url: answers.repo_url };
|
|
132
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
133
|
+
|
|
134
|
+
// Remove old config file after successful migration
|
|
135
|
+
if (fs.existsSync(oldConfigFile)) {
|
|
136
|
+
fs.unlinkSync(oldConfigFile);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(chalk.green(`ā
${CONFIG_FILE} created.`));
|
|
140
|
+
if (answers.repo_url !== oldConfig.repo_url) {
|
|
141
|
+
console.log(chalk.green(`ā
Repository URL updated from old config.`));
|
|
142
|
+
} else {
|
|
143
|
+
console.log(chalk.green(`ā
Migrated from ${oldConfigFile} to ${CONFIG_FILE}`));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Continue with next steps (same as new initialization)
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log(chalk.bold('š Next Steps:'));
|
|
149
|
+
console.log('');
|
|
150
|
+
|
|
151
|
+
const repoUrl = answers.repo_url;
|
|
152
|
+
const oauthUrl = `https://mantle-git-agent.onrender.com/auth/github?repo_url=${encodeURIComponent(repoUrl)}`;
|
|
153
|
+
|
|
154
|
+
console.log(chalk.cyan('š Option A: Automatic Webhook Configuration (Recommended)'));
|
|
155
|
+
console.log(` Visit: ${chalk.underline(oauthUrl)}`);
|
|
156
|
+
console.log(` Authorize GitHub to automatically set up deployment webhooks`);
|
|
157
|
+
console.log('');
|
|
158
|
+
|
|
159
|
+
console.log(chalk.yellow('āļø Option B: Manual Webhook Configuration'));
|
|
160
|
+
console.log(` Navigate to: GitHub ā ${answers.repo_url.split('/').slice(-2).join('/')} ā Settings ā Webhooks`);
|
|
161
|
+
console.log(` Webhook URL: ${chalk.cyan('https://mantle-git-agent.onrender.com/webhook/github/push')}`);
|
|
162
|
+
console.log(` Content type: ${chalk.cyan('application/json')}`);
|
|
163
|
+
console.log(` Events: ${chalk.cyan('Just the push event')}`);
|
|
164
|
+
console.log('');
|
|
165
|
+
|
|
166
|
+
console.log(chalk.bold('š Configure Agent Secrets:'));
|
|
167
|
+
console.log(` ${chalk.cyan('mantle-forge secrets set GROQ_API_KEY=your-key-here')}`);
|
|
168
|
+
console.log(` ${chalk.cyan('mantle-forge secrets set AGENT_PRIVATE_KEY=0x...')}`);
|
|
169
|
+
console.log(` These secrets are encrypted and securely stored for each branch`);
|
|
170
|
+
console.log('');
|
|
171
|
+
|
|
172
|
+
console.log(chalk.bold('š Deploy to Mantle Sepolia:'));
|
|
173
|
+
console.log(` ${chalk.cyan('git push origin main')}`);
|
|
174
|
+
console.log(` Each push automatically deploys a new smart contract on Mantle Sepolia testnet`);
|
|
175
|
+
console.log(` Your agent will be live on-chain within 30 seconds!`);
|
|
176
|
+
console.log('');
|
|
177
|
+
|
|
178
|
+
console.log(chalk.bold('š Monitor Your Agents:'));
|
|
179
|
+
console.log(` ${chalk.cyan('mantle-forge stats')} - View real-time performance metrics`);
|
|
180
|
+
console.log(` ${chalk.cyan('mantle-forge logs')} - Stream live agent decision logs`);
|
|
181
|
+
console.log(` Web Dashboard: ${chalk.underline('https://mantle-git-agent.onrender.com/dashboard')}`);
|
|
182
|
+
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const answers = await prompt([
|
|
187
|
+
{
|
|
188
|
+
type: 'input',
|
|
189
|
+
name: 'repo_url',
|
|
190
|
+
message: 'What is your GitHub repository URL (e.g., https://github.com/user/repo.git)?',
|
|
191
|
+
default: shell.exec('git remote get-url origin', { silent: true }).stdout.trim(),
|
|
192
|
+
}
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
if (!answers.repo_url) {
|
|
196
|
+
console.error(chalk.red('Error: Repository URL is required.'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const config = { repo_url: answers.repo_url };
|
|
201
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
202
|
+
|
|
203
|
+
console.log(chalk.green(`ā
${CONFIG_FILE} created.`));
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(chalk.bold('š Next Steps:'));
|
|
206
|
+
console.log('');
|
|
207
|
+
|
|
208
|
+
// Show OAuth URL for automatic setup
|
|
209
|
+
const repoUrl = answers.repo_url;
|
|
210
|
+
const oauthUrl = `https://mantle-git-agent.onrender.com/auth/github?repo_url=${encodeURIComponent(repoUrl)}`;
|
|
211
|
+
|
|
212
|
+
console.log(chalk.cyan('š Option A: Automatic Webhook Configuration (Recommended)'));
|
|
213
|
+
console.log(` Visit: ${chalk.underline(oauthUrl)}`);
|
|
214
|
+
console.log(` Authorize GitHub to automatically set up deployment webhooks`);
|
|
215
|
+
console.log('');
|
|
216
|
+
|
|
217
|
+
console.log(chalk.yellow('āļø Option B: Manual Webhook Configuration'));
|
|
218
|
+
console.log(` Navigate to: GitHub ā ${answers.repo_url.split('/').slice(-2).join('/')} ā Settings ā Webhooks`);
|
|
219
|
+
console.log(` Webhook URL: ${chalk.cyan('https://mantle-git-agent.onrender.com/webhook/github/push')}`);
|
|
220
|
+
console.log(` Content type: ${chalk.cyan('application/json')}`);
|
|
221
|
+
console.log(` Events: ${chalk.cyan('Just the push event')}`);
|
|
222
|
+
console.log('');
|
|
223
|
+
|
|
224
|
+
console.log(chalk.bold('š Configure Agent Secrets:'));
|
|
225
|
+
console.log(` ${chalk.cyan('mantle-forge secrets set GROQ_API_KEY=your-key-here')}`);
|
|
226
|
+
console.log(` ${chalk.cyan('mantle-forge secrets set AGENT_PRIVATE_KEY=0x...')}`);
|
|
227
|
+
console.log(` These secrets are encrypted and securely stored for each branch`);
|
|
228
|
+
console.log('');
|
|
229
|
+
|
|
230
|
+
console.log(chalk.bold('š Deploy to Mantle Sepolia:'));
|
|
231
|
+
console.log(` ${chalk.cyan('git push origin main')}`);
|
|
232
|
+
console.log(` Each push automatically deploys a new smart contract on Mantle Sepolia testnet`);
|
|
233
|
+
console.log(` Your agent will be live on-chain within 30 seconds!`);
|
|
234
|
+
console.log('');
|
|
235
|
+
|
|
236
|
+
console.log(chalk.bold('š Monitor Your Agents:'));
|
|
237
|
+
console.log(` ${chalk.cyan('mantle-forge stats')} - View real-time performance metrics`);
|
|
238
|
+
console.log(` ${chalk.cyan('mantle-forge logs')} - Stream live agent decision logs`);
|
|
239
|
+
console.log(` Web Dashboard: ${chalk.underline('https://mantle-git-agent.onrender.com/dashboard')}`);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 2. SECRETS - Create a command group for secrets
|
|
244
|
+
*/
|
|
245
|
+
const secretsCommand = program
|
|
246
|
+
.command('secrets')
|
|
247
|
+
.description('Manage secrets for the current branch');
|
|
248
|
+
|
|
249
|
+
// SECRETS SET - Set a secret
|
|
250
|
+
secretsCommand
|
|
251
|
+
.command('set <KEY_VALUE>')
|
|
252
|
+
.description('Set a secret for the current branch (e.g., KEY=VALUE)')
|
|
253
|
+
.action(async (keyValue) => {
|
|
254
|
+
// Handle the case where commander might parse this incorrectly
|
|
255
|
+
const fullCommand = process.argv.slice(2).join(' ');
|
|
256
|
+
const match = fullCommand.match(/secrets set (.+)/);
|
|
257
|
+
|
|
258
|
+
if (!match) {
|
|
259
|
+
console.error(chalk.red('Error: Invalid format. Use KEY=VALUE'));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const keyValueStr = match[1];
|
|
264
|
+
const [key, ...valueParts] = keyValueStr.split('=');
|
|
265
|
+
const value = valueParts.join('=');
|
|
266
|
+
|
|
267
|
+
if (!key || !value) {
|
|
268
|
+
console.error(chalk.red('Error: Invalid format. Use KEY=VALUE'));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const config = getConfig();
|
|
273
|
+
const branch_name = getCurrentBranch();
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
console.log(chalk.cyan(`Setting secret ${key} for branch ${branch_name}...`));
|
|
277
|
+
await axios.post(`${API_BASE_URL}/api/secrets`, {
|
|
278
|
+
repo_url: config.repo_url,
|
|
279
|
+
branch_name: branch_name,
|
|
280
|
+
key: key,
|
|
281
|
+
value: value,
|
|
282
|
+
});
|
|
283
|
+
console.log(chalk.green(`ā
Secret ${key} set.`));
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error(chalk.red(`Error setting secret: ${err.response?.data?.error || err.message}`));
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// SECRETS CHECK - Check which secrets are set
|
|
290
|
+
secretsCommand
|
|
291
|
+
.command('check')
|
|
292
|
+
.description('Check which required secrets are set for the current branch')
|
|
293
|
+
.action(async () => {
|
|
294
|
+
const config = getConfig();
|
|
295
|
+
const branch_name = getCurrentBranch();
|
|
296
|
+
const branch_hash = calculateBranchHash(config.repo_url, branch_name);
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
console.log(chalk.cyan(`š Checking secrets for branch: ${chalk.bold(branch_name)}...`));
|
|
300
|
+
const { data } = await axios.get(`${API_BASE_URL}/api/secrets/check/${branch_hash}`);
|
|
301
|
+
|
|
302
|
+
console.log(chalk.bold(`\n--- Secrets Status for ${branch_name} ---`));
|
|
303
|
+
|
|
304
|
+
// Required secrets
|
|
305
|
+
console.log(chalk.bold('\nš Required Secrets:'));
|
|
306
|
+
data.secrets.required.forEach(secret => {
|
|
307
|
+
const status = secret.set ? chalk.green('ā
Set') : chalk.red('ā Missing');
|
|
308
|
+
console.log(` ${status} ${chalk.bold(secret.key)}`);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Optional secrets
|
|
312
|
+
if (data.secrets.optional.length > 0) {
|
|
313
|
+
console.log(chalk.bold('\nāļø Optional Secrets:'));
|
|
314
|
+
data.secrets.optional.forEach(secret => {
|
|
315
|
+
const status = secret.set ? chalk.cyan('ā Set') : chalk.gray('ā Not set');
|
|
316
|
+
console.log(` ${status} ${secret.key}`);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Overall status
|
|
321
|
+
console.log(chalk.bold('\nš Status:'));
|
|
322
|
+
if (data.all_required_set) {
|
|
323
|
+
console.log(chalk.green(` ā
All required secrets are set! Agent is ready to run.`));
|
|
324
|
+
} else {
|
|
325
|
+
console.log(chalk.red(` ā Missing required secrets: ${chalk.bold(data.missing.join(', '))}`));
|
|
326
|
+
console.log(chalk.yellow(`\nš” Set missing secrets with:`));
|
|
327
|
+
data.missing.forEach(key => {
|
|
328
|
+
console.log(chalk.cyan(` mantle-forge secrets set ${key}=<your-value>`));
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
} catch (err) {
|
|
333
|
+
if (err.response?.status === 404) {
|
|
334
|
+
console.error(chalk.red(`Agent not found for branch "${branch_name}"`));
|
|
335
|
+
console.log(chalk.yellow(` ā Make sure you've pushed this branch: ${chalk.cyan(`git push origin ${branch_name}`)}`));
|
|
336
|
+
} else {
|
|
337
|
+
console.error(chalk.red(`Error checking secrets: ${err.response?.data?.error || err.message}`));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 4. STATS
|
|
344
|
+
* Gets stats for the current branch
|
|
345
|
+
*/
|
|
346
|
+
program
|
|
347
|
+
.command('stats')
|
|
348
|
+
.description('View real-time performance metrics for your Mantle agent')
|
|
349
|
+
.action(async () => {
|
|
350
|
+
const config = getConfig();
|
|
351
|
+
const branch_name = getCurrentBranch();
|
|
352
|
+
|
|
353
|
+
console.log(chalk.cyan(`š Fetching stats for ${branch_name}...`));
|
|
354
|
+
const result = await getStats(config.repo_url, branch_name);
|
|
355
|
+
|
|
356
|
+
if (!result) {
|
|
357
|
+
console.log(chalk.yellow(`\nā ļø Could not fetch stats for "${branch_name}"`));
|
|
358
|
+
console.log(chalk.yellow(` The agent may not be deployed yet, or there was an error.`));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (result && result.stats) {
|
|
363
|
+
const s = result.stats;
|
|
364
|
+
const totalDecisions = s.total_decisions || 0;
|
|
365
|
+
|
|
366
|
+
console.log(chalk.bold(`\n--- Mantle Agent Performance: ${branch_name} ---`));
|
|
367
|
+
console.log(chalk.green(` Total Decisions: ${totalDecisions}`));
|
|
368
|
+
console.log(chalk.cyan(` BUY Signals: ${s.buy_count || 0}`));
|
|
369
|
+
console.log(chalk.yellow(` HOLD Signals: ${s.hold_count || 0}`));
|
|
370
|
+
console.log(chalk.magenta(` Trades Executed: ${s.trades_executed || 0}`));
|
|
371
|
+
|
|
372
|
+
if (totalDecisions === 0) {
|
|
373
|
+
console.log(chalk.yellow(`\nā ļø No trading decisions recorded yet.`));
|
|
374
|
+
console.log(chalk.yellow(` ā Check agent status: ${chalk.cyan('mantle-forge logs')}`));
|
|
375
|
+
console.log(chalk.yellow(` ā Verify the agent process is running on MantleForge backend`));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (s.avg_price) {
|
|
380
|
+
console.log(`\n Price Statistics:`);
|
|
381
|
+
console.log(` Average: $${parseFloat(s.avg_price).toFixed(4)}`);
|
|
382
|
+
console.log(` Min: $${parseFloat(s.min_price).toFixed(4)}`);
|
|
383
|
+
console.log(` Max: $${parseFloat(s.max_price).toFixed(4)}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (s.first_decision && s.last_decision) {
|
|
387
|
+
console.log(`\n Activity:`);
|
|
388
|
+
console.log(` First Decision: ${s.first_decision}`);
|
|
389
|
+
console.log(` Last Decision: ${s.last_decision}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (s.trades_executed > 0 && totalDecisions > 0) {
|
|
393
|
+
const successRate = ((s.trades_executed / totalDecisions) * 100).toFixed(1);
|
|
394
|
+
console.log(chalk.green(`\n Success Rate: ${successRate}%`));
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
console.log(chalk.yellow('No performance metrics available yet. The agent needs to make trading decisions first.'));
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* 5. LOGS
|
|
403
|
+
* Gets logs for the current branch
|
|
404
|
+
*/
|
|
405
|
+
program
|
|
406
|
+
.command('logs')
|
|
407
|
+
.description('Stream real-time logs from your Mantle agent process')
|
|
408
|
+
.action(async () => {
|
|
409
|
+
const config = getConfig();
|
|
410
|
+
const branch_name = getCurrentBranch();
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
console.log(chalk.cyan(`Fetching logs for ${branch_name}...`));
|
|
414
|
+
const url = `${API_BASE_URL}/api/logs/${encodeURIComponent(config.repo_url)}/${encodeURIComponent(branch_name)}`;
|
|
415
|
+
const { data } = await axios.get(url);
|
|
416
|
+
|
|
417
|
+
console.log(chalk.bold(`--- Recent Agent Logs: ${branch_name} (Last 50 entries) ---`));
|
|
418
|
+
if (data.logs && data.logs.length > 0) {
|
|
419
|
+
data.logs.forEach(line => console.log(line));
|
|
420
|
+
} else {
|
|
421
|
+
console.log(chalk.yellow('No logs found.'));
|
|
422
|
+
}
|
|
423
|
+
} catch (err) {
|
|
424
|
+
console.error(chalk.red(`Error fetching logs: ${err.response?.data?.error || err.message}`));
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* 6. RESTART
|
|
430
|
+
* Restarts the agent for the current branch
|
|
431
|
+
*/
|
|
432
|
+
program
|
|
433
|
+
.command('restart')
|
|
434
|
+
.description('Restart the Mantle agent for the current branch')
|
|
435
|
+
.action(async () => {
|
|
436
|
+
const config = getConfig();
|
|
437
|
+
const branch_name = getCurrentBranch();
|
|
438
|
+
const branch_hash = calculateBranchHash(config.repo_url, branch_name);
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
console.log(chalk.cyan(`š Restarting agent for branch: ${chalk.bold(branch_name)}...`));
|
|
442
|
+
const url = `${API_BASE_URL}/api/agents/branch/${branch_hash}/restart`;
|
|
443
|
+
const { data } = await axios.post(url);
|
|
444
|
+
|
|
445
|
+
if (data.success) {
|
|
446
|
+
console.log(chalk.green(`ā
Agent restarted successfully!`));
|
|
447
|
+
console.log(chalk.gray(` Branch: ${data.agent?.branch_name || branch_name}`));
|
|
448
|
+
console.log(chalk.gray(` Repository: ${data.agent?.repo_url || config.repo_url}`));
|
|
449
|
+
console.log(chalk.cyan(`\nš” The agent will reload with the latest code and secrets.`));
|
|
450
|
+
} else {
|
|
451
|
+
console.log(chalk.yellow(`ā ļø Restart response: ${JSON.stringify(data)}`));
|
|
452
|
+
}
|
|
453
|
+
} catch (err) {
|
|
454
|
+
const errorMsg = err.response?.data?.error || err.message;
|
|
455
|
+
if (err.response?.status === 404) {
|
|
456
|
+
console.error(chalk.red(`Agent not found for branch "${branch_name}"`));
|
|
457
|
+
console.log(chalk.yellow(` ā Make sure you've pushed this branch: ${chalk.cyan(`git push origin ${branch_name}`)}`));
|
|
458
|
+
console.log(chalk.yellow(` ā The backend webhook will deploy it automatically`));
|
|
459
|
+
} else {
|
|
460
|
+
console.error(chalk.red(`Error restarting agent: ${errorMsg}`));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* 7. COMPARE
|
|
467
|
+
* Compares two branches side-by-side
|
|
468
|
+
*/
|
|
469
|
+
program
|
|
470
|
+
.command('compare <branch1> <branch2>')
|
|
471
|
+
.description('Compare performance metrics between two agent strategies on Mantle')
|
|
472
|
+
.action(async (branch1, branch2) => {
|
|
473
|
+
const config = getConfig();
|
|
474
|
+
|
|
475
|
+
console.log(chalk.cyan(`š Comparing Mantle agent strategies: ${chalk.bold(branch1)} vs ${chalk.bold(branch2)}...`));
|
|
476
|
+
|
|
477
|
+
const [result1, result2] = await Promise.all([
|
|
478
|
+
getStats(config.repo_url, branch1),
|
|
479
|
+
getStats(config.repo_url, branch2)
|
|
480
|
+
]);
|
|
481
|
+
|
|
482
|
+
if (!result1 || !result2) {
|
|
483
|
+
console.error(chalk.red('Could not fetch stats for comparison.'));
|
|
484
|
+
if (!result1) {
|
|
485
|
+
console.log(chalk.yellow(` ${branch1}: Agent not found or error occurred`));
|
|
486
|
+
console.log(chalk.yellow(` ā Make sure you've pushed: ${chalk.cyan(`git push origin ${branch1}`)}`));
|
|
487
|
+
}
|
|
488
|
+
if (!result2) {
|
|
489
|
+
console.log(chalk.yellow(` ${branch2}: Agent not found or error occurred`));
|
|
490
|
+
console.log(chalk.yellow(` ā Make sure you've pushed: ${chalk.cyan(`git push origin ${branch2}`)}`));
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!result1.stats || !result2.stats) {
|
|
496
|
+
console.log(chalk.yellow('\nā ļø One or both agents have no metrics yet.'));
|
|
497
|
+
if (!result1.stats) console.log(chalk.yellow(` ${branch1}: Waiting for first decision...`));
|
|
498
|
+
if (!result2.stats) console.log(chalk.yellow(` ${branch2}: Waiting for first decision...`));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const s1 = result1.stats;
|
|
503
|
+
const s2 = result2.stats;
|
|
504
|
+
|
|
505
|
+
// Helper function to strip ANSI codes for width calculation
|
|
506
|
+
const stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
507
|
+
|
|
508
|
+
// Helper function to pad string accounting for ANSI codes
|
|
509
|
+
const padWithAnsi = (str, width) => {
|
|
510
|
+
const visibleLength = stripAnsi(str).length;
|
|
511
|
+
const padding = Math.max(0, width - visibleLength);
|
|
512
|
+
return str + ' '.repeat(padding);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
console.log(chalk.bold('\nāāāāāāāāāāāāāāāāāāāāāāā¦āāāāāāāāāāāāāāāāāāāāāāāāāāāā¦āāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
516
|
+
const titleText = ' Mantle Agent Strategy Comparison';
|
|
517
|
+
const titlePadding = 78 - titleText.length; // 77 total width minus title and borders
|
|
518
|
+
console.log(chalk.bold(`ā${titleText}${' '.repeat(titlePadding)}ā`));
|
|
519
|
+
console.log(chalk.bold('ā āāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāā£'));
|
|
520
|
+
|
|
521
|
+
// Create comparison table
|
|
522
|
+
const metrics = [
|
|
523
|
+
{ label: 'Total Decisions', v1: s1.total_decisions || 0, v2: s2.total_decisions || 0, format: (v) => v.toString() },
|
|
524
|
+
{ label: 'BUY Signals', v1: s1.buy_count || 0, v2: s2.buy_count || 0, format: (v) => chalk.cyan(v.toString()) },
|
|
525
|
+
{ label: 'HOLD Signals', v1: s1.hold_count || 0, v2: s2.hold_count || 0, format: (v) => chalk.yellow(v.toString()) },
|
|
526
|
+
{ label: 'Trades Executed', v1: s1.trades_executed || 0, v2: s2.trades_executed || 0, format: (v) => chalk.magenta(v.toString()) },
|
|
527
|
+
{ label: 'Avg Price', v1: s1.avg_price || 0, v2: s2.avg_price || 0, format: (v) => `$${parseFloat(v).toFixed(4)}` },
|
|
528
|
+
{ label: 'Success Rate',
|
|
529
|
+
v1: s1.total_decisions ? ((s1.trades_executed / s1.total_decisions) * 100).toFixed(1) : '0.0',
|
|
530
|
+
v2: s2.total_decisions ? ((s2.trades_executed / s2.total_decisions) * 100).toFixed(1) : '0.0',
|
|
531
|
+
format: (v) => chalk.green(`${v}%`)
|
|
532
|
+
},
|
|
533
|
+
];
|
|
534
|
+
|
|
535
|
+
// Header row
|
|
536
|
+
const headerMetric = padWithAnsi('Metric', 19);
|
|
537
|
+
const header1 = padWithAnsi(chalk.bold(branch1), 27);
|
|
538
|
+
const header2 = padWithAnsi(chalk.bold(branch2), 27);
|
|
539
|
+
console.log(`ā ${headerMetric}ā ${header1}ā ${header2}ā`);
|
|
540
|
+
console.log(chalk.bold('ā āāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāā£'));
|
|
541
|
+
|
|
542
|
+
// Data rows
|
|
543
|
+
metrics.forEach((m, idx) => {
|
|
544
|
+
const label = padWithAnsi(m.label, 19);
|
|
545
|
+
const v1Str = padWithAnsi(m.format(m.v1), 27);
|
|
546
|
+
const v2Str = padWithAnsi(m.format(m.v2), 27);
|
|
547
|
+
console.log(`ā ${label}ā ${v1Str}ā ${v2Str}ā`);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
console.log(chalk.bold('āāāāāāāāāāāāāāāāāāāāāāā©āāāāāāāāāāāāāāāāāāāāāāāāāāāā©āāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
551
|
+
|
|
552
|
+
// Determine winner
|
|
553
|
+
console.log('\n' + chalk.bold('š Performance Analysis:'));
|
|
554
|
+
if ((s1.trades_executed || 0) > (s2.trades_executed || 0)) {
|
|
555
|
+
console.log(chalk.green(` ${branch1} has executed more trades`));
|
|
556
|
+
} else if ((s2.trades_executed || 0) > (s1.trades_executed || 0)) {
|
|
557
|
+
console.log(chalk.green(` ${branch2} has executed more trades`));
|
|
558
|
+
} else {
|
|
559
|
+
console.log(chalk.yellow(` Both strategies show similar trade execution patterns`));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (s1.total_decisions && s2.total_decisions) {
|
|
563
|
+
const rate1 = (s1.trades_executed / s1.total_decisions) * 100;
|
|
564
|
+
const rate2 = (s2.trades_executed / s2.total_decisions) * 100;
|
|
565
|
+
if (rate1 > rate2) {
|
|
566
|
+
console.log(chalk.green(` ${branch1} has better success rate (${rate1.toFixed(1)}% vs ${rate2.toFixed(1)}%)`));
|
|
567
|
+
} else if (rate2 > rate1) {
|
|
568
|
+
console.log(chalk.green(` ${branch2} has better success rate (${rate2.toFixed(1)}% vs ${rate1.toFixed(1)}%)`));
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// --- Parse and Run ---
|
|
574
|
+
program.parse(process.argv);
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mantle-forge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for deploying and managing AI agents on Mantle Sepolia via Git workflows",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"readme": "README.md",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mantle-forge": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/xaviersharwin10/somnia-git-agent.git",
|
|
13
|
+
"directory": "git-agent-cli"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"git",
|
|
17
|
+
"agent",
|
|
18
|
+
"cli",
|
|
19
|
+
"ai",
|
|
20
|
+
"trading",
|
|
21
|
+
"blockchain",
|
|
22
|
+
"mantle",
|
|
23
|
+
"web3",
|
|
24
|
+
"defi",
|
|
25
|
+
"autonomous-agents"
|
|
26
|
+
],
|
|
27
|
+
"author": "marshal.25ec@licet.ac.in",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"axios": "^1.13.1",
|
|
34
|
+
"chalk": "^4.1.2",
|
|
35
|
+
"commander": "^14.0.2",
|
|
36
|
+
"ethers": "^6.15.0",
|
|
37
|
+
"inquirer": "^12.10.0",
|
|
38
|
+
"shelljs": "^0.10.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=16.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|