macbox 0.6.2
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/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/macbox.js +92 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Opslane
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<p align="center"><img src="https://raw.githubusercontent.com/opslane/macbox-agent/main/logo.png" width="96" alt="macbox"></p>
|
|
2
|
+
|
|
3
|
+
# macbox
|
|
4
|
+
|
|
5
|
+
**A real Mac for your coding agent.** macbox lets Claude Code, Codex, Cursor, or any MCP
|
|
6
|
+
client build, test, run, and tap through native iOS apps on a real Mac in the cloud, from
|
|
7
|
+
any machine, including Linux boxes and cloud agent sandboxes.
|
|
8
|
+
|
|
9
|
+
- **Build and test:** `xcodebuild` and your XCTest and UI tests, on a Mac mini running the
|
|
10
|
+
latest Xcode. Your agent gets the results and the error lines.
|
|
11
|
+
- **Run and drive the app:** launch it on the iOS simulator, read what's on screen, tap,
|
|
12
|
+
type, and swipe, so your agent can check its own work.
|
|
13
|
+
- **Watch live:** every running app gets a browser link where you can watch and use it too.
|
|
14
|
+
|
|
15
|
+
Pay per Mac minute, whole minutes only: $0.05, and a job under a minute is free. Get access
|
|
16
|
+
and free credit at **[macbox.build](https://macbox.build)**.
|
|
17
|
+
|
|
18
|
+
## Set up
|
|
19
|
+
|
|
20
|
+
The quickest way: tell your agent
|
|
21
|
+
|
|
22
|
+
> Set up macbox by following https://api.macbox.build/guide/
|
|
23
|
+
|
|
24
|
+
Or pick your client:
|
|
25
|
+
|
|
26
|
+
**Claude Code (plugin: skill + MCP tools)**
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
claude plugin marketplace add opslane/macbox-agent
|
|
30
|
+
claude plugin install macbox@macbox
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Any MCP client** (Codex, Cursor, Claude Desktop, VS Code, ...), with Node 18+:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"macbox": { "command": "npx", "args": ["-y", "macbox", "mcp"] }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**The CLI** (macOS or Linux):
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
curl -fsSL https://api.macbox.build/install.sh | sh
|
|
47
|
+
macbox login
|
|
48
|
+
macbox setup # installs the skill for Claude Code and Codex, and notes in CLAUDE.md / AGENTS.md
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Log in with `macbox login`, or set `MACBOX_API_KEY` (in a cloud agent, as an environment
|
|
52
|
+
secret).
|
|
53
|
+
|
|
54
|
+
## What your agent gets
|
|
55
|
+
|
|
56
|
+
| MCP tool | CLI | What it does |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `build` | `macbox build` | Build for the iOS simulator (or macOS, for a Mac app) |
|
|
59
|
+
| `test` | `macbox test` | Run tests on a fresh simulator; one PASS/FAIL line per test |
|
|
60
|
+
| `screenshot` | `macbox screenshot` | Launch the last build and return a picture |
|
|
61
|
+
| `run` | `macbox run` | Launch the app and keep it running; returns a live link |
|
|
62
|
+
| `describe`, `tap`, `type`, `swipe`, `press_button`, `look` | `macbox ui ...` | Use the running app like a person |
|
|
63
|
+
| `stop` | `macbox stop` | End the running app |
|
|
64
|
+
| `report_problem` | `macbox feedback` | Tell the macbox team when something on our side is wrong |
|
|
65
|
+
|
|
66
|
+
For deeper work there is also `macbox mcp --xcode`: Sentry's open-source
|
|
67
|
+
[MobileBuildMCP](https://www.mobilebuildmcp.com/) Xcode tools (LLDB debugging, Swift packages,
|
|
68
|
+
simulator settings), running on the macbox Mac.
|
|
69
|
+
|
|
70
|
+
## How it works
|
|
71
|
+
|
|
72
|
+
`macbox` sends the files git tracks (never `.env` files unless you ask) to one Mac mini,
|
|
73
|
+
into your own macOS VM that only you use and that can't reach other VMs or our network.
|
|
74
|
+
Only changed files are sent after the first time, and the build cache stays, so the second
|
|
75
|
+
build is fast. Native iOS apps work today: Xcode projects and workspaces, Swift packages,
|
|
76
|
+
CocoaPods, XcodeGen. Not yet: signing, TestFlight, React Native, Flutter.
|
|
77
|
+
|
|
78
|
+
Full guide: https://api.macbox.build/guide/ · Questions: [Discord](https://discord.gg/zukPCGsTnw)
|
|
79
|
+
|
|
80
|
+
## This repository
|
|
81
|
+
|
|
82
|
+
The open parts of macbox: the Claude Code plugin (`.claude-plugin/`, `skills/`, `.mcp.json`),
|
|
83
|
+
the `macbox` npm package (`npm/`, which downloads the CLI from api.macbox.build and checks
|
|
84
|
+
it against the published SHA256SUMS), and the MCP Registry entry (`server.json`). The macbox
|
|
85
|
+
service itself runs on our Macs.
|
|
86
|
+
|
|
87
|
+
MIT licensed.
|
package/bin/macbox.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// macbox on npm: runs the macbox CLI, downloading it on first use.
|
|
3
|
+
//
|
|
4
|
+
// The CLI is a single Go binary served from https://api.macbox.build/dl/. This wrapper
|
|
5
|
+
// fetches the build for this OS and CPU, checks it against the published SHA256SUMS,
|
|
6
|
+
// keeps it in ~/.macbox/bin/<version>/, and runs it with the same arguments, stdin,
|
|
7
|
+
// stdout, and exit code. That makes `npx -y macbox mcp` work as an MCP server.
|
|
8
|
+
//
|
|
9
|
+
// Everything the wrapper itself prints goes to stderr: on stdout, an MCP client expects
|
|
10
|
+
// nothing but protocol messages.
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
const crypto = require("crypto");
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const os = require("os");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const { spawn } = require("child_process");
|
|
18
|
+
|
|
19
|
+
const BASE = process.env.MACBOX_DOWNLOAD_URL || "https://api.macbox.build/dl";
|
|
20
|
+
const HOME = process.env.MACBOX_HOME || path.join(os.homedir(), ".macbox");
|
|
21
|
+
const BIN_DIR = path.join(HOME, "bin");
|
|
22
|
+
|
|
23
|
+
function target() {
|
|
24
|
+
const plat = { darwin: "darwin", linux: "linux" }[process.platform];
|
|
25
|
+
const arch = { x64: "amd64", arm64: "arm64" }[process.arch];
|
|
26
|
+
if (!plat || !arch) {
|
|
27
|
+
throw new Error(`macbox runs on macOS and Linux (x64 or arm64), not ${process.platform} ${process.arch}`);
|
|
28
|
+
}
|
|
29
|
+
return `macbox-${plat}-${arch}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function get(url, timeoutMs) {
|
|
33
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), headers: { "user-agent": "macbox-npm" } });
|
|
34
|
+
if (!res.ok) throw new Error(`${url}: ${res.status} ${res.statusText}`);
|
|
35
|
+
return Buffer.from(await res.arrayBuffer());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// newestCached is the highest version already downloaded, for when the network is down.
|
|
39
|
+
function newestCached(name) {
|
|
40
|
+
let dirs = [];
|
|
41
|
+
try {
|
|
42
|
+
dirs = fs.readdirSync(BIN_DIR).filter((v) => fs.existsSync(path.join(BIN_DIR, v, name)));
|
|
43
|
+
} catch {}
|
|
44
|
+
dirs.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
45
|
+
return dirs.length ? path.join(BIN_DIR, dirs[dirs.length - 1], name) : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function binary() {
|
|
49
|
+
const name = target();
|
|
50
|
+
let version;
|
|
51
|
+
try {
|
|
52
|
+
version = (await get(`${BASE}/VERSION`, 5000)).toString().trim();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
const cached = newestCached(name);
|
|
55
|
+
if (cached) return cached; // offline: run what we have
|
|
56
|
+
throw new Error(`could not reach ${BASE} to download macbox: ${e.message}`);
|
|
57
|
+
}
|
|
58
|
+
if (!/^[0-9A-Za-z._-]+$/.test(version)) throw new Error(`unexpected version ${JSON.stringify(version)}`);
|
|
59
|
+
const file = path.join(BIN_DIR, version, name);
|
|
60
|
+
if (fs.existsSync(file)) return file;
|
|
61
|
+
|
|
62
|
+
process.stderr.write(`macbox: downloading macbox ${version} for ${name.slice(7)}\n`);
|
|
63
|
+
const [bin, sums] = await Promise.all([get(`${BASE}/${name}`, 120000), get(`${BASE}/SHA256SUMS`, 15000)]);
|
|
64
|
+
const want = sums
|
|
65
|
+
.toString()
|
|
66
|
+
.split("\n")
|
|
67
|
+
.map((l) => l.trim().split(/\s+/))
|
|
68
|
+
.find(([, f]) => f === name);
|
|
69
|
+
const got = crypto.createHash("sha256").update(bin).digest("hex");
|
|
70
|
+
if (!want || want[0] !== got) throw new Error(`checksum mismatch for ${name}; not running it`);
|
|
71
|
+
|
|
72
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
73
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
74
|
+
fs.writeFileSync(tmp, bin, { mode: 0o755 });
|
|
75
|
+
fs.renameSync(tmp, file); // atomic, so a second process never runs half a file
|
|
76
|
+
return file;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
binary()
|
|
80
|
+
.then((file) => {
|
|
81
|
+
const child = spawn(file, process.argv.slice(2), { stdio: "inherit" });
|
|
82
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(sig, () => child.kill(sig));
|
|
83
|
+
child.on("exit", (code, signal) => process.exit(signal ? 128 + (os.constants.signals[signal] || 0) : code ?? 0));
|
|
84
|
+
child.on("error", (e) => {
|
|
85
|
+
process.stderr.write(`macbox: ${e.message}\n`);
|
|
86
|
+
process.exit(2);
|
|
87
|
+
});
|
|
88
|
+
})
|
|
89
|
+
.catch((e) => {
|
|
90
|
+
process.stderr.write(`macbox: ${e.message}\n`);
|
|
91
|
+
process.exit(2);
|
|
92
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "macbox",
|
|
3
|
+
"version": "0.6.2",
|
|
4
|
+
"description": "A real Mac for your coding agent: build, test, run, and tap through iOS apps from any machine. CLI and MCP server.",
|
|
5
|
+
"mcpName": "io.github.opslane/macbox",
|
|
6
|
+
"bin": {
|
|
7
|
+
"macbox": "bin/macbox.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/macbox.js",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"os": [
|
|
18
|
+
"darwin",
|
|
19
|
+
"linux"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"mcp-server",
|
|
24
|
+
"ios",
|
|
25
|
+
"xcode",
|
|
26
|
+
"simulator",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"codex",
|
|
29
|
+
"cursor",
|
|
30
|
+
"agents"
|
|
31
|
+
],
|
|
32
|
+
"homepage": "https://macbox.build",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/opslane/macbox-agent.git"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"author": "Opslane (https://macbox.build)"
|
|
39
|
+
}
|