mortgram-cli 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/dist/index.js +35 -0
- package/dist/lib/config.js +28 -0
- package/dist/lib/guard.js +42 -0
- package/dist/lib/logger.js +35 -0
- package/dist/lib/neural.js +33 -0
- package/dist/lib/spawner.js +70 -0
- package/dist/lib/vitals.js +48 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const spawner_1 = require("./lib/spawner");
|
|
9
|
+
const config_1 = require("./lib/config");
|
|
10
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
// Load environment variables from the parent directory if possible, or current
|
|
13
|
+
dotenv_1.default.config({ path: path_1.default.join(process.cwd(), "..", ".env.local") });
|
|
14
|
+
dotenv_1.default.config(); // Fallback to current dir
|
|
15
|
+
const program = new commander_1.Command();
|
|
16
|
+
program
|
|
17
|
+
.name("mortgram")
|
|
18
|
+
.description("MORTGRAM Observer CLI - Zero-code observability wrapper")
|
|
19
|
+
.version("1.0.0");
|
|
20
|
+
program
|
|
21
|
+
.command("login <key>")
|
|
22
|
+
.description("Authenticate your terminal with your MORTGRAM API Key")
|
|
23
|
+
.action((key) => {
|
|
24
|
+
(0, config_1.saveConfig)(key);
|
|
25
|
+
console.log("MORTGRAM: Authentication successful. Key saved to ~/.mortgramrc");
|
|
26
|
+
});
|
|
27
|
+
program
|
|
28
|
+
.command("run <command...>")
|
|
29
|
+
.description("Run an agent command with MORTGRAM observability")
|
|
30
|
+
.action(async (commandParts) => {
|
|
31
|
+
const fullCommand = commandParts.join(" ");
|
|
32
|
+
console.log(`MORTGRAM: Wrapping command: "${fullCommand}"`);
|
|
33
|
+
await (0, spawner_1.spawnAgent)(fullCommand);
|
|
34
|
+
});
|
|
35
|
+
program.parse();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.saveConfig = saveConfig;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".mortgramrc");
|
|
12
|
+
function saveConfig(key) {
|
|
13
|
+
const config = { api_key: key };
|
|
14
|
+
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
15
|
+
}
|
|
16
|
+
function loadConfig() {
|
|
17
|
+
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
18
|
+
try {
|
|
19
|
+
const content = fs_1.default.readFileSync(CONFIG_FILE, "utf-8");
|
|
20
|
+
const config = JSON.parse(content);
|
|
21
|
+
return config.api_key || null;
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.trackUsage = trackUsage;
|
|
7
|
+
exports.checkBudget = checkBudget;
|
|
8
|
+
exports.getCurrentSpend = getCurrentSpend;
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const MODEL_COSTS = {
|
|
11
|
+
"gpt-4o": 0.03, // per 1k input/output avg
|
|
12
|
+
"gpt-4-turbo": 0.02,
|
|
13
|
+
"claude-3-opus": 0.045
|
|
14
|
+
};
|
|
15
|
+
let currentSpend = 0.0;
|
|
16
|
+
const SESSION_BUDGET = parseFloat(process.env.MG_SESSION_BUDGET || "1.00"); // default $1
|
|
17
|
+
function trackUsage(logLine) {
|
|
18
|
+
// Regex for: "Using model: gpt-4o" or "Token usage: 500"
|
|
19
|
+
// This is a naive implementation for demo purposes
|
|
20
|
+
// Check for model usage
|
|
21
|
+
for (const [model, cost] of Object.entries(MODEL_COSTS)) {
|
|
22
|
+
if (logLine.includes(model)) {
|
|
23
|
+
// Assume an average specific cost per interaction for now
|
|
24
|
+
// In a real app, we'd parse exact token counts
|
|
25
|
+
const estimatedCost = 0.002; // $0.002 per interaction
|
|
26
|
+
currentSpend += estimatedCost;
|
|
27
|
+
console.log(chalk_1.default.yellow(`MORTGRAM: +$${estimatedCost.toFixed(4)} (Spend: $${currentSpend.toFixed(4)})`));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function checkBudget(childProcess) {
|
|
32
|
+
if (currentSpend > SESSION_BUDGET) {
|
|
33
|
+
console.log(chalk_1.default.red.bold(`\n!!! MORTGRAM FINANCIAL GUARDRAIL !!!`));
|
|
34
|
+
console.log(chalk_1.default.red(`Session budget of $${SESSION_BUDGET} exceeded.`));
|
|
35
|
+
console.log(chalk_1.default.red(`Terminating process immediately.`));
|
|
36
|
+
childProcess.kill('SIGKILL');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function getCurrentSpend() {
|
|
41
|
+
return currentSpend;
|
|
42
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ingestLog = ingestLog;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
// ensure env is loaded
|
|
11
|
+
dotenv_1.default.config();
|
|
12
|
+
const API_KEY = process.env.MG_LIVE_KEY || (0, config_1.loadConfig)();
|
|
13
|
+
const API_URL = process.env.MORTGRAM_API_URL || "http://localhost:3000/api/ingest";
|
|
14
|
+
const AGENT_ID = process.env.MG_AGENT_ID || "cli-agent";
|
|
15
|
+
async function ingestLog(type, content, metadata = {}) {
|
|
16
|
+
if (!API_KEY)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
await axios_1.default.post(API_URL, {
|
|
20
|
+
agent_id: AGENT_ID,
|
|
21
|
+
type,
|
|
22
|
+
content,
|
|
23
|
+
metadata
|
|
24
|
+
}, {
|
|
25
|
+
headers: {
|
|
26
|
+
"Authorization": `Bearer ${API_KEY}`,
|
|
27
|
+
"Content-Type": "application/json"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// Silently fail to not disrupt the agent
|
|
33
|
+
// console.error("MORTGRAM: Ingest failed", error.message);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startNeuralLink = startNeuralLink;
|
|
7
|
+
const ably_1 = __importDefault(require("ably"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
10
|
+
dotenv_1.default.config();
|
|
11
|
+
const ABLY_KEY = process.env.ABLY_API_KEY;
|
|
12
|
+
const AGENT_ID = process.env.MG_AGENT_ID || "cli-agent";
|
|
13
|
+
let client = null;
|
|
14
|
+
async function startNeuralLink(childProcess) {
|
|
15
|
+
if (!ABLY_KEY) {
|
|
16
|
+
console.warn(chalk_1.default.yellow("MORTGRAM: No ABLY_API_KEY found. Neural link disabled."));
|
|
17
|
+
return { close: () => { } };
|
|
18
|
+
}
|
|
19
|
+
client = new ably_1.default.Realtime(ABLY_KEY);
|
|
20
|
+
const channel = client.channels.get(`control:${AGENT_ID}`);
|
|
21
|
+
await channel.subscribe("KILL", (message) => {
|
|
22
|
+
console.log(chalk_1.default.bgRed.white.bold("\n!!! MORTGRAM NEURAL LINK: KILL SIGNAL RECEIVED !!!\n"));
|
|
23
|
+
childProcess.kill('SIGKILL');
|
|
24
|
+
process.exit(0);
|
|
25
|
+
});
|
|
26
|
+
console.log(chalk_1.default.blue(`MORTGRAM: Neural Link active on channel control:${AGENT_ID}`));
|
|
27
|
+
return {
|
|
28
|
+
close: () => {
|
|
29
|
+
if (client)
|
|
30
|
+
client.close();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.spawnAgent = spawnAgent;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const logger_1 = require("./logger");
|
|
9
|
+
const guard_1 = require("./guard");
|
|
10
|
+
const vitals_1 = require("./vitals");
|
|
11
|
+
const neural_1 = require("./neural");
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
let childProcess = null;
|
|
14
|
+
async function spawnAgent(command) {
|
|
15
|
+
console.log(chalk_1.default.green("MORTGRAM: Initializing Observer..."));
|
|
16
|
+
// Parse command string into cmd and args
|
|
17
|
+
// crude parsing, careful with quotes
|
|
18
|
+
const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
|
|
19
|
+
if (parts.length === 0)
|
|
20
|
+
return;
|
|
21
|
+
const cmd = parts[0];
|
|
22
|
+
if (!cmd)
|
|
23
|
+
return;
|
|
24
|
+
const args = parts.slice(1).map(arg => arg.replace(/^"|"$/g, ''));
|
|
25
|
+
childProcess = (0, child_process_1.spawn)(cmd, args, {
|
|
26
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
27
|
+
shell: true,
|
|
28
|
+
env: { ...process.env, MORTGRAM_WRAPPED: "true" }
|
|
29
|
+
});
|
|
30
|
+
// Neural Link (Kill Switch)
|
|
31
|
+
const neural = await (0, neural_1.startNeuralLink)(childProcess);
|
|
32
|
+
// Vitals Monitor
|
|
33
|
+
const vitalsInterval = (0, vitals_1.startVitals)(childProcess);
|
|
34
|
+
// Stdout Interception
|
|
35
|
+
childProcess.stdout.on('data', (data) => {
|
|
36
|
+
const line = data.toString();
|
|
37
|
+
process.stdout.write(line); // Pass through
|
|
38
|
+
// Analyze
|
|
39
|
+
analyzeLog(line);
|
|
40
|
+
});
|
|
41
|
+
// Stderr Interception
|
|
42
|
+
childProcess.stderr.on('data', (data) => {
|
|
43
|
+
const line = data.toString();
|
|
44
|
+
process.stderr.write(line); // Pass through
|
|
45
|
+
analyzeLog(line, true);
|
|
46
|
+
});
|
|
47
|
+
childProcess.on('close', (code) => {
|
|
48
|
+
console.log(chalk_1.default.gray(`MORTGRAM: Child process exited with code ${code}`));
|
|
49
|
+
clearInterval(vitalsInterval);
|
|
50
|
+
neural.close();
|
|
51
|
+
process.exit(code);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function analyzeLog(content, isError = false) {
|
|
55
|
+
// 1. Ingest
|
|
56
|
+
if (content.includes("Thought:") || content.includes("Action:") || content.includes("Error:")) {
|
|
57
|
+
let type = "log";
|
|
58
|
+
if (content.includes("Thought:"))
|
|
59
|
+
type = "thought";
|
|
60
|
+
if (content.includes("Action:"))
|
|
61
|
+
type = "action";
|
|
62
|
+
if (isError)
|
|
63
|
+
type = "error";
|
|
64
|
+
(0, logger_1.ingestLog)(type, content.trim());
|
|
65
|
+
}
|
|
66
|
+
// 2. Financial Guard
|
|
67
|
+
// Detect model usage, e.g. "Using gpt-4o"
|
|
68
|
+
(0, guard_1.trackUsage)(content);
|
|
69
|
+
(0, guard_1.checkBudget)(childProcess);
|
|
70
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startVitals = startVitals;
|
|
7
|
+
const pidusage_1 = __importDefault(require("pidusage"));
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const guard_1 = require("./guard");
|
|
10
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
11
|
+
const config_1 = require("./config");
|
|
12
|
+
dotenv_1.default.config();
|
|
13
|
+
const API_KEY = process.env.MG_LIVE_KEY || (0, config_1.loadConfig)();
|
|
14
|
+
const API_URL = process.env.MORTGRAM_API_URL ? `${process.env.MORTGRAM_API_URL}/heartbeat` : "http://localhost:3000/api/cron/heartbeat";
|
|
15
|
+
// Note: reusing cron heartbeat or creating a specific agent heartbeat endpoint?
|
|
16
|
+
// For CLI, we likely want a direct ingestion endpoint or use the log ingestion with type "heartbeat"
|
|
17
|
+
const INGEST_URL = process.env.MORTGRAM_API_URL || "http://localhost:3000/api/ingest";
|
|
18
|
+
const AGENT_ID = process.env.MG_AGENT_ID || "cli-agent";
|
|
19
|
+
function startVitals(childProcess) {
|
|
20
|
+
const interval = setInterval(async () => {
|
|
21
|
+
if (!childProcess || childProcess.killed)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
const stats = await (0, pidusage_1.default)(childProcess.pid);
|
|
25
|
+
const spend = (0, guard_1.getCurrentSpend)();
|
|
26
|
+
// Send heartbeat log
|
|
27
|
+
if (API_KEY) {
|
|
28
|
+
await axios_1.default.post(INGEST_URL, {
|
|
29
|
+
agent_id: AGENT_ID,
|
|
30
|
+
type: "status", // Special type for heartbeat/status
|
|
31
|
+
content: `Heartbeat: CPU ${stats.cpu.toFixed(1)}% | MEM ${(stats.memory / 1024 / 1024).toFixed(0)}MB | $${spend.toFixed(4)}`,
|
|
32
|
+
metadata: {
|
|
33
|
+
cpu: stats.cpu,
|
|
34
|
+
memory: stats.memory,
|
|
35
|
+
spend: spend,
|
|
36
|
+
status: "online"
|
|
37
|
+
}
|
|
38
|
+
}, {
|
|
39
|
+
headers: { "Authorization": `Bearer ${API_KEY}` }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
// pidusage might fail if process dies quickly
|
|
45
|
+
}
|
|
46
|
+
}, 30000); // 30s
|
|
47
|
+
return interval;
|
|
48
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mortgram-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zero-code observability wrapper for AI agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"prepublishOnly": "npm run build"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"ai",
|
|
12
|
+
"observability",
|
|
13
|
+
"agent",
|
|
14
|
+
"cli"
|
|
15
|
+
],
|
|
16
|
+
"author": "MORTGRAM",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"ably": "^2.17.1",
|
|
20
|
+
"axios": "^1.13.5",
|
|
21
|
+
"chalk": "^4.1.2",
|
|
22
|
+
"commander": "^14.0.3",
|
|
23
|
+
"dotenv": "^17.2.4",
|
|
24
|
+
"pidusage": "^4.0.1",
|
|
25
|
+
"strip-ansi": "^6.0.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^25.2.2",
|
|
29
|
+
"@types/pidusage": "^2.0.5",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
31
|
+
},
|
|
32
|
+
"bin": {
|
|
33
|
+
"mortgram": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md"
|
|
38
|
+
]
|
|
39
|
+
}
|