openowl 0.3.16 → 0.3.17
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/bin/owl +159 -5
- package/install.js +1 -1
- package/package.json +1 -1
package/bin/owl
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenOwl MCP Launcher — Usage-gated proxy.
|
|
4
|
+
*
|
|
5
|
+
* Sits between the MCP client (Claude Code, etc.) and the owl binary.
|
|
6
|
+
* Intercepts every tool call, checks usage with the server (atomic
|
|
7
|
+
* check + increment), and blocks if over the daily limit.
|
|
8
|
+
*
|
|
9
|
+
* The binary handles all actual tool execution unchanged.
|
|
10
|
+
*/
|
|
2
11
|
"use strict";
|
|
3
12
|
|
|
4
13
|
const os = require("os");
|
|
5
14
|
const path = require("path");
|
|
6
|
-
const { spawn } = require("child_process");
|
|
7
15
|
const fs = require("fs");
|
|
16
|
+
const https = require("https");
|
|
17
|
+
const crypto = require("crypto");
|
|
18
|
+
const { spawn } = require("child_process");
|
|
19
|
+
|
|
20
|
+
// ── Version check ──────────────────────────────────────────────
|
|
21
|
+
const VERSION = "0.3.17";
|
|
22
|
+
if (process.argv[2] === "--version" || process.argv[2] === "-v") {
|
|
23
|
+
console.log(`OpenOwl v${VERSION}`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
8
26
|
|
|
27
|
+
// ── Platform & binary resolution ───────────────────────────────
|
|
9
28
|
const BINARIES = {
|
|
10
29
|
"darwin-arm64": "owl-darwin-arm64",
|
|
11
30
|
"darwin-x64": "owl-darwin-x86_64",
|
|
@@ -20,8 +39,6 @@ if (!binaryName) {
|
|
|
20
39
|
process.exit(1);
|
|
21
40
|
}
|
|
22
41
|
|
|
23
|
-
|
|
24
|
-
|
|
25
42
|
const installDir = path.join(__dirname, "..", ".owl");
|
|
26
43
|
const binaryPath = path.join(installDir, binaryName);
|
|
27
44
|
|
|
@@ -30,12 +47,149 @@ if (!fs.existsSync(binaryPath)) {
|
|
|
30
47
|
process.exit(1);
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
//
|
|
50
|
+
// ── API key & machine ID ───────────────────────────────────────
|
|
51
|
+
const OWL_DATA = path.join(os.homedir(), ".openowl");
|
|
52
|
+
const API_KEY_FILE = path.join(OWL_DATA, "api.key");
|
|
53
|
+
|
|
54
|
+
function getApiKey() {
|
|
55
|
+
const envKey = process.env.OPENOWL_API_KEY;
|
|
56
|
+
if (envKey) return envKey.trim();
|
|
57
|
+
try {
|
|
58
|
+
return fs.readFileSync(API_KEY_FILE, "utf8").trim();
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getMachineId() {
|
|
65
|
+
const ifaces = os.networkInterfaces();
|
|
66
|
+
let mac = "unknown";
|
|
67
|
+
for (const name of Object.keys(ifaces)) {
|
|
68
|
+
for (const iface of ifaces[name]) {
|
|
69
|
+
if (!iface.internal && iface.mac && iface.mac !== "00:00:00:00:00:00") {
|
|
70
|
+
mac = iface.mac;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (mac !== "unknown") break;
|
|
75
|
+
}
|
|
76
|
+
const raw = `${mac}-${os.hostname()}`;
|
|
77
|
+
return crypto.createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Usage check (atomic check + track) ─────────────────────────
|
|
81
|
+
function checkUsage(apiKey, toolName, machineId) {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const data = JSON.stringify({
|
|
84
|
+
key: apiKey,
|
|
85
|
+
machine_id: machineId,
|
|
86
|
+
tool: toolName,
|
|
87
|
+
});
|
|
88
|
+
const req = https.request(
|
|
89
|
+
{
|
|
90
|
+
hostname: "openowl.dev",
|
|
91
|
+
path: "/api/v1/usage/check",
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
"Content-Length": Buffer.byteLength(data),
|
|
96
|
+
},
|
|
97
|
+
timeout: 5000,
|
|
98
|
+
},
|
|
99
|
+
(res) => {
|
|
100
|
+
let body = "";
|
|
101
|
+
res.on("data", (chunk) => (body += chunk));
|
|
102
|
+
res.on("end", () => {
|
|
103
|
+
try {
|
|
104
|
+
resolve(JSON.parse(body));
|
|
105
|
+
} catch {
|
|
106
|
+
resolve({ allowed: true });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
req.on("error", () => resolve({ allowed: true }));
|
|
112
|
+
req.on("timeout", () => {
|
|
113
|
+
req.destroy();
|
|
114
|
+
resolve({ allowed: true });
|
|
115
|
+
});
|
|
116
|
+
req.write(data);
|
|
117
|
+
req.end();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Main ───────────────────────────────────────────────────────
|
|
122
|
+
const apiKey = getApiKey();
|
|
123
|
+
const machineId = getMachineId();
|
|
124
|
+
|
|
125
|
+
// Spawn the real owl binary with piped stdin/stdout for interception
|
|
34
126
|
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
35
|
-
stdio: "inherit",
|
|
127
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
36
128
|
env: process.env,
|
|
37
129
|
});
|
|
38
130
|
|
|
131
|
+
// Parse newline-delimited JSON-RPC from stdin, intercept tool calls
|
|
132
|
+
let stdinBuffer = "";
|
|
133
|
+
|
|
134
|
+
process.stdin.on("data", async (chunk) => {
|
|
135
|
+
stdinBuffer += chunk.toString();
|
|
136
|
+
|
|
137
|
+
while (stdinBuffer.length > 0) {
|
|
138
|
+
const newlineIdx = stdinBuffer.indexOf("\n");
|
|
139
|
+
if (newlineIdx === -1) break;
|
|
140
|
+
|
|
141
|
+
const line = stdinBuffer.slice(0, newlineIdx);
|
|
142
|
+
stdinBuffer = stdinBuffer.slice(newlineIdx + 1);
|
|
143
|
+
|
|
144
|
+
if (!line.trim()) {
|
|
145
|
+
child.stdin.write("\n");
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let msg;
|
|
150
|
+
try {
|
|
151
|
+
msg = JSON.parse(line);
|
|
152
|
+
} catch {
|
|
153
|
+
child.stdin.write(line + "\n");
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Intercept tool calls for usage enforcement
|
|
158
|
+
if (msg.method === "tools/call" && apiKey) {
|
|
159
|
+
const toolName = msg.params?.name || "unknown";
|
|
160
|
+
const usage = await checkUsage(apiKey, toolName, machineId);
|
|
161
|
+
|
|
162
|
+
if (!usage.allowed) {
|
|
163
|
+
const errorResp = JSON.stringify({
|
|
164
|
+
jsonrpc: "2.0",
|
|
165
|
+
id: msg.id,
|
|
166
|
+
result: {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text:
|
|
171
|
+
usage.message ||
|
|
172
|
+
`You've reached your daily limit of ${usage.daily_limit || 50} tool calls.\n\nUpgrade your plan at https://openowl.dev/pricing\nLimits reset at midnight UTC.`,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
isError: true,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
process.stdout.write(errorResp + "\n");
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Pass through to binary
|
|
184
|
+
child.stdin.write(line + "\n");
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
process.stdin.on("end", () => child.stdin.end());
|
|
189
|
+
|
|
190
|
+
// Pass binary stdout to our stdout
|
|
191
|
+
child.stdout.on("data", (data) => process.stdout.write(data));
|
|
192
|
+
|
|
39
193
|
child.on("exit", (code) => process.exit(code || 0));
|
|
40
194
|
child.on("error", (err) => {
|
|
41
195
|
console.error(`[OpenOwl] ${err.message}`);
|
package/install.js
CHANGED
|
@@ -9,7 +9,7 @@ const http = require("http");
|
|
|
9
9
|
const { execSync } = require("child_process");
|
|
10
10
|
const zlib = require("zlib");
|
|
11
11
|
|
|
12
|
-
const VERSION = "0.3.
|
|
12
|
+
const VERSION = "0.3.17";
|
|
13
13
|
const BASE_URL =
|
|
14
14
|
`https://github.com/mihir-kanzariya/openowl-releases/releases/download/v${VERSION}`;
|
|
15
15
|
|