@nomac/cli 0.16.0 → 0.18.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/package.json +1 -1
- package/skill/SKILL.md +9 -7
- package/src/cli.mjs +15 -0
- package/src/hosted-sessions.mjs +4 -2
- package/src/mcp.mjs +1 -1
- package/src/phone-tools.json +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomac/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
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",
|
package/skill/SKILL.md
CHANGED
|
@@ -23,7 +23,9 @@ alone; or `npx simulator-use cloud start` from a terminal
|
|
|
23
23
|
the project here: in `nomac mcp`, `local_path` sends the folder (only what
|
|
24
24
|
changed); on the hosted server, `phone_upload_link` answers one command that
|
|
25
25
|
sends it. `phone_ui_look` with `screenshot_file` saves the screen as a
|
|
26
|
-
full-size PNG (in `nomac mcp`, a file here; hosted, a download link).
|
|
26
|
+
full-size PNG (in `nomac mcp`, a file here; hosted, a download link). For App
|
|
27
|
+
Store screenshots start the phone with `device: "iphone-pro-max"` (6.9-inch,
|
|
28
|
+
1320x2868); `upload_screenshots` takes the hosted links.
|
|
27
29
|
|
|
28
30
|
## Cloud Mac setup and workflow
|
|
29
31
|
|
|
@@ -184,9 +186,9 @@ more than twice.
|
|
|
184
186
|
|
|
185
187
|
## Feedback
|
|
186
188
|
|
|
187
|
-
When you stop a Mac, its answer asks
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
When you stop a Mac or a phone, its answer asks what got in your way, failed,
|
|
190
|
+
was missing or was slow. Answer in a sentence or two: `report_issue` with
|
|
191
|
+
`kind: "feedback"` and the `session_id`, or `nomac feedback "..."` after
|
|
192
|
+
`nomac stop`. Leave out code, secrets and personal data. Send it once: a person
|
|
193
|
+
at NoMac reads it and answers in that ticket (`report_issue` with no
|
|
194
|
+
description shows the answer).
|
package/src/cli.mjs
CHANGED
|
@@ -9,6 +9,7 @@ Start:
|
|
|
9
9
|
nomac start start a Mac, or reconnect to yours
|
|
10
10
|
nomac attach use from this folder the Mac your agent started elsewhere (MCP, dashboard)
|
|
11
11
|
nomac stop stop the Mac: deletes it and its files (not ~/drive), billing ends
|
|
12
|
+
nomac feedback "..." tell NoMac what got in your way, failed, was missing or was slow
|
|
12
13
|
|
|
13
14
|
Use it:
|
|
14
15
|
nomac exec -- ls run a command; prints its output, keeps its exit code
|
|
@@ -170,6 +171,20 @@ export async function main(argv) {
|
|
|
170
171
|
console.log(describeApple(view));
|
|
171
172
|
return view;
|
|
172
173
|
}
|
|
174
|
+
case "feedback": {
|
|
175
|
+
// A note for NoMac, about this folder's last Mac when there is one (nomac stop asks for it).
|
|
176
|
+
const description = rest.join(" ").trim();
|
|
177
|
+
if (isHelp(rest) || description.length < 10) {
|
|
178
|
+
const usage = 'Usage: nomac feedback "what got in your way, failed, was missing or was slow"\n A person at NoMac reads it and answers. Leave out code, secrets and personal data.';
|
|
179
|
+
if (isHelp(rest)) return console.log(usage);
|
|
180
|
+
throw new CliError("feedback usage", usage);
|
|
181
|
+
}
|
|
182
|
+
const { sessionStore } = await import("./session-store.mjs");
|
|
183
|
+
const last = sessionStore().load();
|
|
184
|
+
const answer = await api("POST", "/api/v1/report-issue", { body: { description, kind: "feedback", ...(last?.mode === "hosted" && last.id ? { session_id: last.id } : {}) } });
|
|
185
|
+
console.log(answer.note ?? `Sent (${answer.ticket_id}).`);
|
|
186
|
+
return answer;
|
|
187
|
+
}
|
|
173
188
|
case "whoami": {
|
|
174
189
|
const [me, credit] = await Promise.all([api("GET", "/api/v1/me"), api("GET", "/api/v1/mac/credits").catch(() => null)]);
|
|
175
190
|
const o = me.org ?? {}, c = credit?.credits ?? credit ?? {};
|
package/src/hosted-sessions.mjs
CHANGED
|
@@ -14,6 +14,8 @@ import { refreshSshConfig, sshConfigBlock, writeSshConfig } from "./ssh-config.m
|
|
|
14
14
|
const WORKSPACE = "/Users/agent/workspace";
|
|
15
15
|
/** How long the CLI keeps trying to put a project on the drive before it gives up for now. */
|
|
16
16
|
const PLACE_BUDGET_MS = 120_000;
|
|
17
|
+
/** Stop's ask, as the API's feedback_request words it, with the CLI's way to answer (nomac feedback). */
|
|
18
|
+
const FEEDBACK_REQUEST = 'What got in your way, failed, was missing or was slow in this session? Tell NoMac in a sentence or two: nomac feedback "...". A person reads each one. Leave out code, secrets and personal data.';
|
|
17
19
|
|
|
18
20
|
const HELP = `Nomac cloud Mac sessions
|
|
19
21
|
nomac start [--wait 300] [--max-duration 7200] [--idle-timeout 600] [--max-cost 5] [--json]
|
|
@@ -385,7 +387,7 @@ export async function hostedSessionCommand(command, argv, { store = sessionStore
|
|
|
385
387
|
: "Check: nomac status";
|
|
386
388
|
// A ready Mac says what is on it, as nomac status does.
|
|
387
389
|
const mac = result.session.state === "ready" && macLine(lastImage) ? `${macLine(lastImage)}\n` : "";
|
|
388
|
-
print(`${result.attached ? `${result.attached}\n` : ""}${result.session.id}: ${result.session.state}\n${mac}${next}\n${usage}${result.warning ? `\n${result.warning}` : ""}`);
|
|
390
|
+
print(`${result.attached ? `${result.attached}\n` : ""}${result.session.id}: ${result.session.state}\n${mac}${next}\n${usage}${result.warning ? `\n${result.warning}` : ""}${result.feedback_request ? `\n\n${result.feedback_request}` : ""}`);
|
|
389
391
|
return result;
|
|
390
392
|
};
|
|
391
393
|
const saveView = (local, view) => {
|
|
@@ -833,7 +835,7 @@ export async function hostedSessionCommand(command, argv, { store = sessionStore
|
|
|
833
835
|
saveView(local, (await request("DELETE", `/api/v1/mac/sessions/${sessionId(local.id)}`)).session);
|
|
834
836
|
const deadline = now() + wait * 1000;
|
|
835
837
|
while (!["stopped", "failed"].includes(local.state) && now() < deadline) { await sleep(1000); await refresh(local); }
|
|
836
|
-
return output({ session: local.snapshot });
|
|
838
|
+
return output({ session: local.snapshot, feedback_request: FEEDBACK_REQUEST });
|
|
837
839
|
}
|
|
838
840
|
await ready(local);
|
|
839
841
|
if (command === "sync") {
|
package/src/mcp.mjs
CHANGED
|
@@ -162,7 +162,7 @@ export const TOOLS = [
|
|
|
162
162
|
{
|
|
163
163
|
name: "upload_screenshots",
|
|
164
164
|
description:
|
|
165
|
-
"Start a screenshot replacement for one display type and iOS version. PNGs are validated and prior images are backed up. Reuse request_key and the original body when retrying. Poll get_screenshot_upload until complete, rolled_back or needs_attention; accepted means still in progress. images: [{filename, link}] with a create_mac_download_link url for each PNG on the Mac (any size
|
|
165
|
+
"Start a screenshot replacement for one display type and iOS version. PNGs are validated and prior images are backed up. Reuse request_key and the original body when retrying. Poll get_screenshot_upload until complete, rolled_back or needs_attention; accepted means still in progress. images: [{filename, link}] with a create_mac_download_link url for each PNG on the Mac, or a cloud iPhone's screenshot_file link (phone_ui_look; an iphone-pro-max phone gives APP_IPHONE_67's 1320x2868), any size, preferred; or [{filename, data: base64 PNG}] for small images (a request is capped near 4.5 MB in all).",
|
|
166
166
|
inputSchema: {
|
|
167
167
|
type: "object",
|
|
168
168
|
properties: {
|
package/src/phone-tools.json
CHANGED
|
@@ -535,11 +535,12 @@
|
|
|
535
535
|
"pattern": "^[A-Za-z0-9._-]{1,64}$"
|
|
536
536
|
},
|
|
537
537
|
"device": {
|
|
538
|
-
"description": "start: iphone (iPhone 17 Pro), ipad (iPad Pro 11-inch (M5)); the first is quickest",
|
|
538
|
+
"description": "start: iphone (iPhone 17 Pro), ipad (iPad Pro 11-inch (M5)), iphone-pro-max (iPhone 17 Pro Max); the first is quickest",
|
|
539
539
|
"type": "string",
|
|
540
540
|
"enum": [
|
|
541
541
|
"iphone",
|
|
542
|
-
"ipad"
|
|
542
|
+
"ipad",
|
|
543
|
+
"iphone-pro-max"
|
|
543
544
|
]
|
|
544
545
|
},
|
|
545
546
|
"appearance": {
|