@nomac/cli 0.13.1 → 0.13.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nomac/cli",
3
- "version": "0.13.1",
4
- "description": "Cloud Mac sessions over SSH and HTTPS, plus iOS builds and publishing — built for agents.",
3
+ "version": "0.13.3",
4
+ "description": "A real cloud Mac for AI agents: Xcode, the iOS Simulator, signing, TestFlight and App Store publishing, by the minute, over MCP, SSH and HTTPS.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "bin": {
package/skill/SKILL.md CHANGED
@@ -1,15 +1,17 @@
1
1
  ---
2
2
  name: nomac
3
3
  description: >
4
- Use a full cloud Mac over SSH, HTTPS or MCP for macOS tools, Xcode and iOS
5
- development. Start sessions, sync files, run commands, retrieve work and
6
- stop.
4
+ A real cloud Mac for agents on Linux, Windows or a sandbox: build, run and
5
+ test iOS and macOS apps with Xcode and the iOS Simulator (simulator-use),
6
+ sign, send to TestFlight and publish. Fully enabled for the agent, $0.80 an
7
+ hour by the minute, over SSH, HTTPS or MCP.
7
8
  ---
8
9
 
9
10
  # nomac: a Mac for your agent
10
11
 
11
- NoMac provides a full macOS VM with administrator access, Xcode and the iOS
12
- Simulator. You choose the commands and tools. Mac time is prepaid credit.
12
+ NoMac gives your agent a full macOS VM that is entirely its own: administrator,
13
+ no prompts, nothing blocked, with Xcode and the iOS Simulator. You choose the
14
+ commands and tools. Mac time is prepaid credit.
13
15
 
14
16
  ## Cloud Mac setup and workflow
15
17
 
@@ -49,7 +51,9 @@ Simulator. You choose the commands and tools. Mac time is prepaid credit.
49
51
  The CLI keeps the project on the account's drive, in `~/drive/src/<folder
50
52
  name>`, which is kept between sessions; stopping deletes everything else on
51
53
  the Mac, logs included. Download what you need from outside `~/drive` before
52
- stopping. SSH disconnects
54
+ stopping. Keep the drive under 10 GB: a Mac that does not hold it yet downloads
55
+ all of it before it boots; `nomac-drive` on the Mac shows what takes the space,
56
+ and `nomac-drive clean` deletes stale build products. SSH disconnects
53
57
  do not stop the session. Ready time includes idle time; queue/setup are free.
54
58
  A Mac stops at its time limit (2 hours unless `nomac start --max-duration`
55
59
  set another), its spend cap, when credit runs out, or when idle; a build left
@@ -83,10 +87,14 @@ MP4 and prints a share link); MCP `start_mac_recording`, `stop_mac_recording`.
83
87
  Commands drive any app with AppleScript without a permission prompt. For real
84
88
  keyboard and mouse, also on the lock screen and in password dialogs, use
85
89
  `nomac input type TEXT`, `nomac input click X Y`, `nomac unlock` (MCP
86
- `mac_input`): each answers with a screenshot. Drive the Simulator with simctl,
87
- and give the human `open_mac_desktop` when they want to see the screen.
90
+ `mac_input`): each answers with a screenshot. Drive the Simulator with
91
+ simulator-use, preinstalled: `simulator-use look` reads the screen as text,
92
+ `simulator-use act 'tap "Sign In"'` taps and types by label, `simulator-use run`
93
+ builds, installs and launches (simctl works too). Give the human
94
+ `open_mac_desktop` when they want to see the screen.
88
95
  `get_mac_session` (and `nomac status --json`) lists what
89
- is installed: Ruby with CocoaPods, xcbeautify, xcodegen, gh, Git LFS, jq.
96
+ is installed: Node, Python, uv, nvm, Ruby with CocoaPods, xcbeautify, xcodegen,
97
+ gh, Git LFS, jq and simulator-use.
90
98
  Pipe xcodebuild through `xcbeautify`. Simulator devices share names across iOS
91
99
  runtimes: pick one by ID (`xcrun simctl list devices available`) or with
92
100
  `OS=27.0` in `-destination`. Apple Intelligence cannot generate on a virtual
package/src/mac-tools.mjs CHANGED
@@ -26,7 +26,7 @@ export const MAC_TOOLS = [
26
26
  { name: "list_mac_sessions", description: "Find existing Mac sessions, including after losing a response. Shows state, cost, limits and cleanup.", inputSchema: schema({}), handler: async () => text(await api("GET", "/api/v1/mac/sessions")) },
27
27
  { name: "get_mac_session", description: "Read live Mac allocation state, cost, funded time and cleanup confirmation, and once ready the credentials installed on it (secrets, signing identities and profiles, by name and path, never values) and account: the Mac's user, whether its login password is set (in $NOMAC_MAC_PASSWORD on the Mac), the access choices, and how to type and click (mac_input). queued/provisioning means wait; stopping is not yet confirmed cleanup.", inputSchema: schema({ session_id: session }, ["session_id"]), handler: async args => text(await api("GET", base(args))) },
28
28
  { name: "extend_mac", description: "Keep a running Mac going longer: raises its time limit by minutes (default 60), up to 8 hours since it started, and its spend cap with it. Call it when an answer's stop_notice says the Mac stops soon at its time limit or spend cap, or before a build or test that would not finish in time. It adds no credit: when stop_notice's reason is credit, the human adds some (get_mac_checkout). The Mac still bills $0.80 an hour while it runs.", inputSchema: schema({ session_id: session, minutes: { type: "integer", minimum: 1, maximum: 480, default: 60, description: "Minutes to add to the time limit, 1-480. Default 60." } }, ["session_id"]), handler: async ({ session_id, minutes = 60 }) => text(await api("POST", `${base({ session_id })}/extend`, { body: { minutes } })) },
29
- { name: "get_mac_drive", description: "Read the account's persistent drive: a disk kept between sessions and mounted at ~/drive on every Mac (50 GB). What is there stays for the next session: keep projects in ~/drive/src for incremental builds; package and build caches (DerivedData, Swift packages, CocoaPods, npm) are kept there already. NoMac saves it when a Mac stops, with nothing to call. Shows the space used, the newest saved version, a save still in progress, recent versions, and the date it is deleted after 90 days without a Mac.", inputSchema: schema({}), handler: async () => text(await api("GET", "/api/v1/mac/drive")) },
29
+ { name: "get_mac_drive", description: "Read the account's persistent drive: a disk kept between sessions and mounted at ~/drive on every Mac (20 GB). What is there stays for the next session: keep projects in ~/drive/src for incremental builds; package and build caches (DerivedData, Swift packages, CocoaPods, npm) are kept there already. NoMac saves it when a Mac stops, with nothing to call. Shows the space used, the newest saved version, a save still in progress, recent versions, and the date it is deleted after 90 days without a Mac.", inputSchema: schema({}), handler: async () => text(await api("GET", "/api/v1/mac/drive")) },
30
30
  { name: "reset_mac_drive", description: "Empty the account's persistent drive: the next Mac starts with a new, empty ~/drive, and everything on the old drive is deleted, with no way back. Stop any running session first (stop_mac). For a single Mac without the drive, start_mac with access drive none instead.", inputSchema: schema({}), handler: async () => text(await api("DELETE", "/api/v1/mac/drive")) },
31
31
  { name: "list_secrets", description: "List the account's secrets by name: variables (set in every shell and command on the Mac) and files (with their path on the Mac), whether every Mac receives them or only when asked, sizes, and secrets an agent asked for that the person has not entered yet. Values are never returned.", inputSchema: schema({}), handler: async () => text(await api("GET", "/api/v1/secrets")) },
32
32
  { name: "request_secret", description: "Get a link where the person enters a secret in their browser, so its value never passes through this chat: an API token, a .env value, a .p12 certificate or a config file. kind variable is one line of text, set as an environment variable on the Mac; kind file is stored at destination (under the home folder, like ~/.netrc; default ~/.nomac/secrets/<name>). Give the url to the person who holds the value; they need no NoMac account, and it works once, for 24 hours. Then list_secrets shows it, new Macs receive it, and install_secrets puts it on a running Mac. With a shell on this machine, the person can also run nomac secrets set NAME themselves. Never ask for a secret in the chat.", inputSchema: schema({ name: { type: "string", minLength: 1, maxLength: 128 }, kind: { type: "string", enum: ["variable", "file"] }, description: { type: "string", minLength: 1, maxLength: 500, description: "What the person should enter and why; shown to them" }, destination: { type: "string", maxLength: 256 }, delivery: { type: "string", enum: ["every_mac", "on_request"] } }, ["name", "kind", "description"]), handler: async body => text(await api("POST", "/api/v1/secrets/requests", { body })) },
package/src/mcp.mjs CHANGED
@@ -21,7 +21,8 @@ const INSTRUCTIONS = [
21
21
  "NoMac gives you a cloud Mac with Xcode, billed from prepaid credit ($0.80 an hour, by the minute).",
22
22
  "Use it when work needs macOS that this machine does not have (Windows, Linux or a cloud sandbox): building, running or testing an iOS, iPadOS, watchOS, visionOS or macOS app (Swift, SwiftUI, React Native, Expo, Flutter, Capacitor), the iOS Simulator, Xcode, code signing, TestFlight or App Store submission.",
23
23
  "Typical flow: get_mac_credits, start_mac, poll get_mac_session until ready, put code there (exec_mac with git clone into ~/drive/src, write_mac_file, fetch_to_mac, create_mac_upload_link), run exec_mac, get results with read_mac_file or create_mac_download_link, then stop_mac.",
24
- "The account's drive at ~/drive (50 GB) is on every Mac and kept between sessions; everything outside it is deleted at stop. Clone and build in ~/drive/src/<repo>, so the next session rebuilds only what changed.",
24
+ "The account's drive at ~/drive (20 GB) is on every Mac and kept between sessions; everything outside it is deleted at stop. Clone and build in ~/drive/src/<repo>, so the next session rebuilds only what changed. Keep it under 10 GB: a Mac that does not hold it yet downloads all of it before it boots; nomac-drive on the Mac shows what takes the space, and nomac-drive clean deletes stale build products.",
25
+ "For the iOS Simulator, simulator-use is on the Mac (run it with exec_mac): simulator-use look reads the screen as text, simulator-use act 'tap \"Sign In\"' taps and types by label, and simulator-use run builds, installs and launches the app; ~/NOMAC.md has the rest.",
25
26
  "When an answer says low_credit (under an hour of Mac time left), tell the human before a long build and offer its buy_url: a Mac stops when the credit runs out. For a refill, suggest 3 packs ($30, +5%).",
26
27
  "One Mac runs per account at a time: start_mac while one runs answers session_limit_reached with its id, to use. For work that needs two at once, start_mac with parallel: true starts a second (accounts that bought credit, while NoMac has a Mac to spare); it runs without the drive.",
27
28
  "exec_mac returns a quick command's output in the same call; pass wait_seconds: 50 for builds and tests. A command still running comes back as a job: poll get_mac_job with its job_id, then read_mac_output.",
@@ -402,18 +403,18 @@ export const TOOLS = [
402
403
  },
403
404
  ];
404
405
 
405
- /** Shown to everyone: the Mac tools and support. The rest serve Builder subscribers. */
406
- const PUBLIC_TOOLS = new Set([...MAC_TOOLS.map(t => t.name), "report_issue"]);
406
+ /** The Builder's own tools: its plans are closed, so they are listed only for accounts that still have one. */
407
+ const BUILDER_TOOLS = new Set(["check_project", "get_billing_status", "get_refund_status", "push_project", "build", "status", "get_failure"]);
407
408
  let subscriber;
408
409
 
409
410
  /**
410
- * The Builder (publishing) plans are closed to new subscriptions, so their tools
411
- * are listed only for accounts that still have one. Hidden tools still answer
412
- * when called, so existing subscribers' agents keep working.
411
+ * Everyone gets the Mac, App Store and support tools (publish follows send_to_testflight for
412
+ * every account); the Builder's own tools are listed only for its remaining subscribers.
413
+ * Hidden tools still answer when called, so existing subscribers' agents keep working.
413
414
  */
414
415
  export async function listedTools(isSubscriber = () => (subscriber ??= api("GET", "/api/v1/billing/status").then(s => Boolean(s?.tier) && s.tier !== "none", () => false))) {
415
416
  const legacy = await isSubscriber();
416
- return TOOLS.filter(t => legacy || PUBLIC_TOOLS.has(t.name));
417
+ return TOOLS.filter(t => legacy || !BUILDER_TOOLS.has(t.name));
417
418
  }
418
419
 
419
420
  export async function runMcpServer() {