fonino 0.1.0 → 0.1.1

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.
@@ -7845,6 +7845,23 @@ function coveredByTask(g, risk, spec) {
7845
7845
  if (amount === void 0) return true;
7846
7846
  return g.cap !== void 0 && amount <= g.cap;
7847
7847
  }
7848
+ function forAssistants(r) {
7849
+ if (!Array.isArray(r.content) || !r.content.some((c) => c.type === "image")) return r;
7850
+ const content = [...r.content.filter((c) => c.type !== "image"), ...r.content.filter((c) => c.type === "image")];
7851
+ const out = { ...r, content };
7852
+ if (!("structuredContent" in r)) {
7853
+ for (const c of content) {
7854
+ if (c.type !== "text") continue;
7855
+ try {
7856
+ const v = JSON.parse(c.text.split("\nDo NOT")[0]);
7857
+ if (v && typeof v === "object" && !Array.isArray(v)) out.structuredContent = v;
7858
+ } catch {
7859
+ }
7860
+ break;
7861
+ }
7862
+ }
7863
+ return out;
7864
+ }
7848
7865
  async function modelImage(pngPath) {
7849
7866
  const out = pathJoin(tmpdir7(), `fonino-${process.pid}-${randomUUID5().slice(0, 8)}.jpg`);
7850
7867
  try {
@@ -8725,7 +8742,7 @@ ${describeSpec(spec)}
8725
8742
  ms: Date.now() - t0,
8726
8743
  heldMs: heldSince && !result.isError ? Date.now() - Date.parse(heldSince) : void 0
8727
8744
  });
8728
- return result;
8745
+ return forAssistants(result);
8729
8746
  };
8730
8747
  const c = { ...config, ...extra ?? {} };
8731
8748
  c._meta = { ...c._meta ?? {}, securitySchemes: SECURITY };
@@ -10479,7 +10496,7 @@ var init_serve = __esm({
10479
10496
  init_src2();
10480
10497
  init_src4();
10481
10498
  init_src();
10482
- import { spawn as spawn5 } from "node:child_process";
10499
+ import { execFileSync as execFileSync3, spawn as spawn5 } from "node:child_process";
10483
10500
  import { copyFileSync as copyFileSync2, existsSync as existsSync20, mkdirSync as mkdirSync19, readdirSync as readdirSync6, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "node:fs";
10484
10501
  import { homedir as homedir22 } from "node:os";
10485
10502
  import { randomBytes as randomBytes8 } from "node:crypto";
@@ -10893,13 +10910,24 @@ async function main() {
10893
10910
  const connected = (await listMuxDevices2()).filter((d) => d.connectionType === "USB").map((d) => d.udid);
10894
10911
  const stampFile = join28(process.env.AGENTPHONE_HOME ?? join28(homedir22(), ".agentphone"), "resign.json");
10895
10912
  const stamps = existsSync20(stampFile) ? JSON.parse(readFileSync23(stampFile, "utf8")) : {};
10896
- const udids = flags["if-stale"] ? connected.filter((u) => !stamps[u] || Date.now() - Date.parse(stamps[u]) > 3 * 864e5) : connected;
10913
+ const profile = helperProfile(process.env.AGENTPHONE_WDA_BUILD ?? join28(fileURLToPath8(new URL("../../..", import.meta.url)), "vendor/wda-build"));
10914
+ const stale = (u) => {
10915
+ if (!stamps[u]) return true;
10916
+ if (profile) return profile.expires - Date.now() < 3 * 864e5 || Date.parse(stamps[u]) < profile.created;
10917
+ return Date.now() - Date.parse(stamps[u]) > 3 * 864e5;
10918
+ };
10919
+ const udids = flags["if-stale"] ? connected.filter(stale) : connected;
10897
10920
  if (!udids.length) return ok(connected.length ? "all connected iPhones were renewed recently" : "no iPhone connected");
10898
10921
  const repo = fileURLToPath8(new URL("../../..", import.meta.url));
10899
10922
  const code = await new Promise(
10900
10923
  (r) => spawn5("/bin/sh", [join28(repo, "scripts/wda.sh"), "setup", ...udids], {
10901
10924
  stdio: "inherit",
10902
- env: { ...process.env, AGENTPHONE_TEAM_ID: cfg.team, AGENTPHONE_BUNDLE: cfg.bundle ?? `com.fonino.${cfg.team.toLowerCase()}.helper` }
10925
+ env: {
10926
+ ...process.env,
10927
+ AGENTPHONE_TEAM_ID: cfg.team,
10928
+ AGENTPHONE_BUNDLE: cfg.bundle ?? `com.fonino.${cfg.team.toLowerCase()}.helper`,
10929
+ ...cfg.ascKey ? { AGENTPHONE_ASC_KEY: cfg.ascKey.path, AGENTPHONE_ASC_KEY_ID: cfg.ascKey.id, AGENTPHONE_ASC_ISSUER: cfg.ascKey.issuer } : {}
10930
+ }
10903
10931
  }).on("exit", (c) => r(c ?? 1))
10904
10932
  );
10905
10933
  if (code !== 0) throw new DeviceError("resign_failed", "Couldn't renew the helper's signature", "Open Xcode \u2192 Settings \u2192 Accounts and check your Apple ID is still signed in.");
@@ -11360,6 +11388,19 @@ async function agentCommand() {
11360
11388
  }
11361
11389
  rl.close();
11362
11390
  }
11391
+ function helperProfile(buildDir) {
11392
+ try {
11393
+ const products = join28(buildDir, "Build/Products/Debug-iphoneos");
11394
+ const app = readdirSync6(products).find((f) => f.endsWith("-Runner.app"));
11395
+ if (!app) return void 0;
11396
+ const plist2 = execFileSync3("/usr/bin/security", ["cms", "-D", "-i", join28(products, app, "embedded.mobileprovision")], { stdio: ["ignore", "pipe", "ignore"] }).toString();
11397
+ const date = (key) => Date.parse(plist2.match(new RegExp(`<key>${key}</key>\\s*<date>([^<]+)</date>`))?.[1] ?? "");
11398
+ const created = date("CreationDate"), expires = date("ExpirationDate");
11399
+ return Number.isFinite(created) && Number.isFinite(expires) ? { created, expires } : void 0;
11400
+ } catch {
11401
+ return void 0;
11402
+ }
11403
+ }
11363
11404
  function listRuns(limit) {
11364
11405
  const dir = defaultRunsDir();
11365
11406
  const runs = existsSync20(dir) ? readdirSync6(dir).filter((f) => existsSync20(resolve(dir, f, "goal.json"))).sort().reverse().slice(0, limit) : [];
package/docs/setup.md CHANGED
@@ -34,6 +34,11 @@ locked with Face ID ask for your Mac password, which AgentPhone will not enter.
34
34
  Signing: a free Apple ID ("Personal Team") works for testing but allows **3 devices** and its
35
35
  signing **expires after 7 days** (rerun step 4). A paid Apple Developer account ($99/yr) allows
36
36
  100 iPhones per membership year and 1‑year certificates, and can be shared by several Macs.
37
+ With a paid account, add an App Store Connect API key (Users and Access → Integrations, role
38
+ Developer or higher) to `~/.agentphone/setup.json` as
39
+ `"ascKey": {"path": "/path/AuthKey_XXXX.p8", "id": "XXXX", "issuer": "<issuer id>"}`: the helper is
40
+ then signed for a year without an Xcode sign-in, and `fonino wda resign --if-stale` only renews it
41
+ near the end of that year instead of every few days.
37
42
 
38
43
  1. Install Xcode, open it once (install iOS components), accept the license, then
39
44
  `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer` and sign in under
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fonino",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Let your AI agent use your real iPhone or Android phone, with your OK before anything is sent.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://fonino.app",
package/scripts/wda.sh CHANGED
@@ -36,6 +36,10 @@ case "${1:-}" in
36
36
  DESTS=""
37
37
  for u in "$@"; do DESTS="$DESTS -destination id=$u"; done
38
38
  [ -n "$DESTS" ] || DESTS="-destination generic/platform=iOS"
39
+ # With a paid team's App Store Connect API key, Xcode needs no signed-in account and the profile
40
+ # lasts a year; without one it uses Xcode's account (free Apple IDs: 7 days).
41
+ AUTH=""
42
+ [ -n "${AGENTPHONE_ASC_KEY:-}" ] && AUTH="-authenticationKeyPath $AGENTPHONE_ASC_KEY -authenticationKeyID $AGENTPHONE_ASC_KEY_ID -authenticationKeyIssuerID $AGENTPHONE_ASC_ISSUER"
39
43
  # shellcheck disable=SC2086
40
44
  xcodebuild build-for-testing \
41
45
  -project "$WDA_DIR/WebDriverAgent.xcodeproj" \
@@ -44,6 +48,7 @@ case "${1:-}" in
44
48
  -derivedDataPath "$DERIVED" \
45
49
  -allowProvisioningUpdates \
46
50
  -allowProvisioningDeviceRegistration \
51
+ $AUTH \
47
52
  DEVELOPMENT_TEAM="$AGENTPHONE_TEAM_ID" \
48
53
  PRODUCT_BUNDLE_IDENTIFIER="$BUNDLE" \
49
54
  CODE_SIGN_STYLE=Automatic