openowl 0.3.15 → 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.
Files changed (3) hide show
  1. package/bin/owl +159 -5
  2. package/install.js +1 -1
  3. 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
- // Spawn with inherited stdio critical for MCP stdio transport
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.15";
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openowl",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "AI desktop automation MCP server — give your AI eyes and hands",
5
5
  "homepage": "https://openowl.dev",
6
6
  "repository": {