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.
@@ -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
+ }