@ycodium-ai/plugin-devices 0.2.3799 → 0.2.3851

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/dist/index.js CHANGED
@@ -135,6 +135,7 @@ var parseDeviceAction = (platform2, input) => {
135
135
  "setPermission",
136
136
  "openUrl",
137
137
  "launchApp",
138
+ "relaunchApp",
138
139
  "terminateApp",
139
140
  "shake",
140
141
  "sendPush"
@@ -178,6 +179,7 @@ var parseDeviceAction = (platform2, input) => {
178
179
  case "openUrl":
179
180
  return { type, url: url(input["url"]) };
180
181
  case "launchApp":
182
+ case "relaunchApp":
181
183
  case "terminateApp":
182
184
  return { type, appId: appId(platform2, input["appId"]) };
183
185
  case "sendPush":
@@ -199,6 +201,7 @@ var IOS_ACTIONS = /* @__PURE__ */ new Set([
199
201
  "setPermission",
200
202
  "openUrl",
201
203
  "launchApp",
204
+ "relaunchApp",
202
205
  "terminateApp",
203
206
  "sendPush"
204
207
  ]);
@@ -208,10 +211,10 @@ var ANDROID_ACTIONS = /* @__PURE__ */ new Set([
208
211
  "setToggle",
209
212
  "setOrientation",
210
213
  "setLocation",
211
- "clearLocation",
212
214
  "setPermission",
213
215
  "openUrl",
214
216
  "launchApp",
217
+ "relaunchApp",
215
218
  "terminateApp"
216
219
  ]);
217
220
  var IOS_TOGGLES = /* @__PURE__ */ new Set([
@@ -279,6 +282,7 @@ var actionPayload = {
279
282
  ),
280
283
  branch("openUrl", { url: { type: "string", minLength: 1, maxLength: 2048 } }, ["url"]),
281
284
  branch("launchApp", { appId: appId2 }, ["appId"]),
285
+ branch("relaunchApp", { appId: appId2 }, ["appId"]),
282
286
  branch("terminateApp", { appId: appId2 }, ["appId"]),
283
287
  branch("shake"),
284
288
  branch("sendPush", { appId: appId2, payload: { type: ["string", "object"] } }, ["appId", "payload"])
@@ -358,6 +362,12 @@ var SETTINGS_CARD = {
358
362
  labelKey: "settings.androidSdk.label",
359
363
  descriptionKey: "settings.androidSdk.description",
360
364
  placeholderKey: "settings.androidSdk.placeholder"
365
+ },
366
+ {
367
+ key: "downloadProxy",
368
+ kind: "proxy",
369
+ labelKey: "settings.downloadProxy.label",
370
+ descriptionKey: "settings.downloadProxy.description"
361
371
  }
362
372
  ]
363
373
  };
@@ -632,9 +642,10 @@ var childPath = (root, name) => {
632
642
  throw new Error("Archive path escapes its destination");
633
643
  return result;
634
644
  };
635
- var downloadArchive = async (item, directory, fetch) => {
645
+ var archivePath = (item, directory) => NodePath.join(directory, item.url.split("/").at(-1));
646
+ var downloadArchive = async (item, directory, fetch, received = () => void 0) => {
636
647
  await NodeFSP.mkdir(directory, { recursive: true });
637
- const target2 = NodePath.join(directory, item.url.split("/").at(-1));
648
+ const target2 = archivePath(item, directory);
638
649
  if ((await NodeFSP.lstat(target2).catch(() => null))?.isSymbolicLink())
639
650
  throw new Error("Download cache entry must not be linked");
640
651
  const verify = async () => {
@@ -665,6 +676,7 @@ var downloadArchive = async (item, directory, fetch) => {
665
676
  return;
666
677
  }
667
678
  hash.update(chunk);
679
+ received(size);
668
680
  callback(null, chunk);
669
681
  }
670
682
  });
@@ -793,6 +805,27 @@ var extractEnvironmentZip = async (file, destination, signal) => {
793
805
  }
794
806
  };
795
807
 
808
+ // src/environmentProgress.ts
809
+ var PUBLISH_INTERVAL_MS = 1e3;
810
+ var SPEED_SMOOTHING = 0.3;
811
+ var throttledEnvironmentProgress = (publish, now = Date.now) => {
812
+ let last = null;
813
+ let speed;
814
+ return (stage) => {
815
+ const time = now();
816
+ const same = last?.stage.phase === stage.phase && last.stage.component === stage.component;
817
+ if (last !== null && same && time - last.time < PUBLISH_INTERVAL_MS) return;
818
+ if (last !== null && same && stage.receivedBytes !== void 0) {
819
+ const received = stage.receivedBytes - (last.stage.receivedBytes ?? stage.receivedBytes);
820
+ const sample = received * 1e3 / (time - last.time);
821
+ speed = speed === void 0 ? sample : speed + (sample - speed) * SPEED_SMOOTHING;
822
+ }
823
+ if (stage.phase !== "download") speed = void 0;
824
+ last = { stage, time };
825
+ publish(stage, speed);
826
+ };
827
+ };
828
+
796
829
  // src/environmentStorage.ts
797
830
  import * as NodeFSP2 from "node:fs/promises";
798
831
  import * as NodePath2 from "node:path";
@@ -920,7 +953,7 @@ var privateEnvironmentReady = async (root) => {
920
953
  await assertPrivatePath(root, NodePath2.join(avd, "config.ini"));
921
954
  await assertPrivatePath(root, NodePath2.join(paths.avd, "Ycodium_API_35.ini"));
922
955
  const config = await NodeFSP2.readFile(NodePath2.join(avd, "config.ini"), "utf8");
923
- const image = /^image\.sysdir\.1=(system-images\/android-35\/default\/(?:x86_64|arm64-v8a)\/)\r?$/m.exec(
956
+ const image = /^image\.sysdir\.1\s*=\s*(system-images\/android-35\/default\/(?:x86_64|arm64-v8a)\/)\s*$/m.exec(
924
957
  config
925
958
  )?.[1];
926
959
  if (!image) return false;
@@ -980,6 +1013,7 @@ var DeviceEnvironments = class {
980
1013
  progress = { status: "idle" };
981
1014
  listeners = /* @__PURE__ */ new Set();
982
1015
  active = null;
1016
+ job = null;
983
1017
  nextJobId = 1;
984
1018
  options;
985
1019
  constructor(options) {
@@ -1000,19 +1034,36 @@ var DeviceEnvironments = class {
1000
1034
  if (this.busy)
1001
1035
  throw new DeviceError("invalid-input", "Another simulator preparation is already running");
1002
1036
  const id = this.nextJobId++;
1037
+ const job = new AbortController();
1038
+ this.job = job;
1003
1039
  this.publish({ id, status: "working" });
1004
- this.active = this.prepare(
1005
- input,
1006
- (stage) => this.publish({ id, status: "working", stage })
1007
- ).then(
1040
+ const report = throttledEnvironmentProgress(
1041
+ (stage, bytesPerSecond) => this.publish({
1042
+ id,
1043
+ status: "working",
1044
+ ...stage,
1045
+ ...bytesPerSecond === void 0 ? {} : { bytesPerSecond }
1046
+ })
1047
+ );
1048
+ this.active = this.prepare(input, report, job.signal).then(
1008
1049
  (plan) => this.publish({ id, status: "done", plan }),
1009
1050
  (error) => {
1010
- this.options.log?.warn("Simulator preparation failed", {
1011
- detail: error instanceof Error ? error.message : String(error)
1012
- });
1013
- this.publish({ id, status: "failed" });
1051
+ if (job.signal.aborted) {
1052
+ this.publish({ id, status: "cancelled" });
1053
+ return;
1054
+ }
1055
+ const message = error instanceof Error ? error.message : String(error);
1056
+ this.options.log?.warn("Simulator preparation failed", { detail: message });
1057
+ this.publish({ id, status: "failed", message });
1014
1058
  }
1015
- );
1059
+ ).finally(() => {
1060
+ this.job = null;
1061
+ });
1062
+ return this.progress;
1063
+ }
1064
+ /** 只中止正在跑的准备;已下完并校验过的组件留在缓存里,下次不用重下。 */
1065
+ cancel() {
1066
+ this.job?.abort();
1016
1067
  return this.progress;
1017
1068
  }
1018
1069
  async dispose() {
@@ -1071,6 +1122,13 @@ var DeviceEnvironments = class {
1071
1122
  const archives = androidArchives(probe.platform, probe.arch);
1072
1123
  const sdk = await this.options.externalSdk();
1073
1124
  const prepared = await privateEnvironmentReady(root);
1125
+ const installed = await NodeFSP3.lstat(paths.sdk).catch(() => null) !== null;
1126
+ const missing = installed ? [] : await Promise.all(
1127
+ (archives ?? []).map(async (item) => {
1128
+ const cached = await NodeFSP3.lstat(archivePath(item, paths.cache)).catch(() => null);
1129
+ return cached?.size === item.bytes ? 0 : item.bytes;
1130
+ })
1131
+ );
1074
1132
  return {
1075
1133
  platform: "android",
1076
1134
  supported: archives !== null && probe.virtualization.available,
@@ -1078,7 +1136,7 @@ var DeviceEnvironments = class {
1078
1136
  ...sdk.available && sdk.root ? { existingSdkRoot: sdk.root } : {},
1079
1137
  root,
1080
1138
  version: ANDROID_ENVIRONMENT_VERSION,
1081
- downloadBytes: archives?.reduce((sum, item) => sum + item.bytes, 0) ?? null,
1139
+ downloadBytes: archives ? missing.reduce((sum, bytes) => sum + bytes, 0) : null,
1082
1140
  requiredFreeBytes: 12 * GIB,
1083
1141
  freeBytes: free,
1084
1142
  ready: sdk.available && (sdk.root !== paths.sdk || prepared),
@@ -1106,10 +1164,11 @@ var DeviceEnvironments = class {
1106
1164
  this.busy = false;
1107
1165
  }
1108
1166
  }
1109
- async prepare(input, progress = () => void 0) {
1167
+ async prepare(input, progress = () => void 0, cancelled) {
1168
+ const signal = cancelled ? AbortSignal.any([this.abort.signal, cancelled]) : this.abort.signal;
1110
1169
  return this.exclusively(async () => {
1111
1170
  const plan = await this.plan(input);
1112
- this.abort.signal.throwIfAborted();
1171
+ signal.throwIfAborted();
1113
1172
  if (!input.acceptLicenses)
1114
1173
  throw new DeviceError(
1115
1174
  "invalid-input",
@@ -1143,8 +1202,8 @@ var DeviceEnvironments = class {
1143
1202
  NodePath3.join(plan.root, "config")
1144
1203
  ])
1145
1204
  await NodeFSP3.mkdir(directory, { recursive: true });
1146
- if (input.platform === "ios") await this.prepareIos(plan, progress);
1147
- else await this.prepareAndroid(plan, progress);
1205
+ if (input.platform === "ios") await this.prepareIos(plan, progress, signal);
1206
+ else await this.prepareAndroid(plan, progress, signal);
1148
1207
  } finally {
1149
1208
  await release?.();
1150
1209
  }
@@ -1152,7 +1211,7 @@ var DeviceEnvironments = class {
1152
1211
  return this.plan(input);
1153
1212
  });
1154
1213
  }
1155
- async prepareAndroid(plan, progress) {
1214
+ async prepareAndroid(plan, progress, signal) {
1156
1215
  const probe = await this.probe();
1157
1216
  const archives = androidArchives(probe.platform, probe.arch);
1158
1217
  if (!archives) throw new Error("Android host target is unsupported");
@@ -1168,26 +1227,43 @@ var DeviceEnvironments = class {
1168
1227
  "An unrecognized private SDK already exists; its installation will not be overwritten"
1169
1228
  );
1170
1229
  } else {
1230
+ const totalBytes = archives.reduce((sum, item) => sum + item.bytes, 0);
1231
+ let finished = 0;
1171
1232
  for (const item of archives) {
1172
- progress(item.name);
1173
- this.abort.signal.throwIfAborted();
1233
+ const received = (bytes) => progress({
1234
+ phase: "download",
1235
+ component: item.name,
1236
+ receivedBytes: finished + bytes,
1237
+ totalBytes
1238
+ });
1239
+ received(0);
1240
+ signal.throwIfAborted();
1174
1241
  const file = await downloadArchive(
1175
1242
  item,
1176
1243
  paths.cache,
1177
- (url2, init) => this.options.fetch(url2, { ...init, signal: this.abort.signal })
1244
+ (url2, init) => this.options.fetch(url2, { ...init, signal }),
1245
+ received
1178
1246
  );
1247
+ finished += item.bytes;
1248
+ progress({
1249
+ phase: "extract",
1250
+ component: item.name,
1251
+ receivedBytes: finished,
1252
+ totalBytes
1253
+ });
1179
1254
  await extractEnvironmentZip(
1180
1255
  file,
1181
1256
  item.destination === "sdk" ? staging : NodePath3.join(staging, "system-images", "android-35", "default"),
1182
- this.abort.signal
1257
+ signal
1183
1258
  );
1184
1259
  }
1185
1260
  await NodeFSP3.writeFile(NodePath3.join(staging, ".ycodium-components"), plan.version);
1186
1261
  await NodeFSP3.rename(staging, paths.sdk);
1187
1262
  }
1263
+ progress({ phase: "device" });
1188
1264
  await this.createPrivateAvd(paths.root, probe.arch === "arm64" ? "arm64-v8a" : "x86_64");
1189
- this.abort.signal.throwIfAborted();
1190
- progress("validate");
1265
+ signal.throwIfAborted();
1266
+ progress({ phase: "validate" });
1191
1267
  const validated = await this.options.process.run({
1192
1268
  program: "host:node",
1193
1269
  args: [this.options.helperPath, "validate-android", "--sdk", paths.sdk],
@@ -1253,41 +1329,37 @@ target=android-35
1253
1329
  { flag: "wx" }
1254
1330
  );
1255
1331
  }
1256
- async prepareIos(plan, progress) {
1332
+ async prepareIos(plan, progress, cancelled) {
1257
1333
  if (plan.ready) return;
1258
1334
  const cache = NodePath3.join(plan.root, "cache", `ios-${plan.version}`);
1259
1335
  await NodeFSP3.mkdir(cache, { recursive: true });
1260
- progress(`iOS ${plan.version}`);
1261
- if (await this.runApple([
1262
- "-downloadPlatform",
1263
- "iOS",
1264
- "-exportPath",
1265
- cache,
1266
- "-buildVersion",
1267
- plan.version
1268
- ]) !== 0)
1336
+ progress({ phase: "apple-download", component: `iOS ${plan.version}` });
1337
+ if (await this.runApple(
1338
+ ["-downloadPlatform", "iOS", "-exportPath", cache, "-buildVersion", plan.version],
1339
+ cancelled
1340
+ ) !== 0)
1269
1341
  throw new Error(
1270
1342
  "Apple could not download this iOS runtime; check the selected Xcode version, network and Apple account in Xcode"
1271
1343
  );
1272
1344
  const names = (await NodeFSP3.readdir(cache)).filter((name) => name.endsWith(".dmg"));
1273
1345
  if (names.length !== 1)
1274
1346
  throw new Error("Apple did not return a unique simulator runtime archive");
1275
- progress("apple-install");
1276
- if (await this.runApple(["-importPlatform", NodePath3.join(cache, names[0])]) !== 0)
1347
+ progress({ phase: "apple-install" });
1348
+ if (await this.runApple(["-importPlatform", NodePath3.join(cache, names[0])], cancelled) !== 0)
1277
1349
  throw new Error(
1278
1350
  "Apple could not import the runtime. Complete any required permissions in Xcode; Ycodium will not elevate privileges"
1279
1351
  );
1280
1352
  if (!(await this.probe()).xcode.runtimes.some((runtime) => runtime.version === plan.version))
1281
1353
  throw new Error("The requested iOS runtime is still unavailable after Apple's import");
1282
1354
  }
1283
- async runApple(args) {
1284
- this.abort.signal.throwIfAborted();
1355
+ async runApple(args, cancelled) {
1356
+ cancelled.throwIfAborted();
1285
1357
  const handle = await this.options.process.spawn({
1286
1358
  program: "host:node",
1287
1359
  args: [this.options.helperPath, "run", "--", "xcodebuild", ...args],
1288
1360
  cwd: this.options.dataDir
1289
1361
  });
1290
- const signal = AbortSignal.any([this.abort.signal, AbortSignal.timeout(36e5)]);
1362
+ const signal = AbortSignal.any([cancelled, AbortSignal.timeout(36e5)]);
1291
1363
  const terminate = () => {
1292
1364
  void handle.kill().catch(() => void 0);
1293
1365
  };
@@ -1364,6 +1436,13 @@ var registerEnvironmentCommands = (ctx, environments) => {
1364
1436
  confirmAppleInstall: payload["confirmAppleInstall"] === true
1365
1437
  })
1366
1438
  });
1439
+ ctx.command({
1440
+ name: "cancelEnvironment",
1441
+ description: "Cancel the running simulator preparation. Components already downloaded and verified stay in the cache.",
1442
+ scope: "operate",
1443
+ payloadSchema: { type: "object", properties: {}, additionalProperties: false },
1444
+ handle: () => environments.cancel()
1445
+ });
1367
1446
  ctx.stream({
1368
1447
  name: "environmentProgress",
1369
1448
  description: "Current user-triggered simulator preparation progress; subscribing does not start preparation.",
@@ -2022,7 +2101,10 @@ var HubProcess = class {
2022
2101
  "--host",
2023
2102
  "127.0.0.1",
2024
2103
  "--hide-sidebar",
2025
- "--hide-boot-device"
2104
+ "--hide-boot-device",
2105
+ // hub 默认的 grpc-screenshot 要机器上有 ffmpeg,缺了就一帧都没有;scrcpy 只靠随包的 server。
2106
+ "--stream-source",
2107
+ "scrcpy"
2026
2108
  );
2027
2109
  let handle;
2028
2110
  try {
@@ -2618,6 +2700,10 @@ var runIos = async (tools, udid, action) => {
2618
2700
  case "launchApp":
2619
2701
  await simctl(tools, udid, ["launch", action.appId], "launch");
2620
2702
  return;
2703
+ case "relaunchApp":
2704
+ await simctl(tools, udid, ["terminate", action.appId], "terminate").catch(() => void 0);
2705
+ await simctl(tools, udid, ["launch", action.appId], "launch");
2706
+ return;
2621
2707
  case "terminateApp":
2622
2708
  await simctl(tools, udid, ["terminate", action.appId], "terminate");
2623
2709
  return;
@@ -2634,6 +2720,7 @@ var runIos = async (tools, udid, action) => {
2634
2720
  var adb = (tools, serial, args, operation) => checked(tools.run("adb", ["-s", serial, ...args]), operation);
2635
2721
  var runAndroid = async (tools, serial, action) => {
2636
2722
  const shell = (args, operation) => adb(tools, serial, ["shell", ...args], operation);
2723
+ const launch = (appId3) => shell(["monkey", "-p", appId3, "-c", "android.intent.category.LAUNCHER", "1"], "launch");
2637
2724
  switch (action.type) {
2638
2725
  case "setAppearance":
2639
2726
  await shell(["cmd", "uimode", "night", action.value === "dark" ? "yes" : "no"], "appearance");
@@ -2690,13 +2777,21 @@ var runAndroid = async (tools, serial, action) => {
2690
2777
  "location"
2691
2778
  );
2692
2779
  return;
2693
- case "clearLocation":
2694
- return;
2695
2780
  case "setPermission": {
2696
2781
  const permissions = ANDROID_PERMISSIONS[action.permission];
2697
2782
  if (permissions === void 0) throw unsupported("permission", "android");
2698
2783
  const verb = action.decision === "grant" ? "grant" : "revoke";
2699
- for (const permission of permissions) {
2784
+ const declared = new Set(
2785
+ (await shell(["dumpsys", "package", action.appId], "permission")).split(/[\s:,]+/u)
2786
+ );
2787
+ const requested = permissions.filter((permission) => declared.has(permission));
2788
+ if (requested.length === 0) {
2789
+ throw new DeviceError(
2790
+ "command-failed",
2791
+ `${action.appId} does not declare this permission, so Android cannot change it.`
2792
+ );
2793
+ }
2794
+ for (const permission of requested) {
2700
2795
  await shell(["pm", verb, action.appId, permission], "permission").catch(() => void 0);
2701
2796
  }
2702
2797
  return;
@@ -2708,14 +2803,16 @@ var runAndroid = async (tools, serial, action) => {
2708
2803
  );
2709
2804
  return;
2710
2805
  case "launchApp":
2711
- await shell(
2712
- ["monkey", "-p", action.appId, "-c", "android.intent.category.LAUNCHER", "1"],
2713
- "launch"
2714
- );
2806
+ await launch(action.appId);
2807
+ return;
2808
+ case "relaunchApp":
2809
+ await shell(["am", "force-stop", action.appId], "terminate");
2810
+ await launch(action.appId);
2715
2811
  return;
2716
2812
  case "terminateApp":
2717
2813
  await shell(["am", "force-stop", action.appId], "terminate");
2718
2814
  return;
2815
+ case "clearLocation":
2719
2816
  case "setLiquidGlass":
2720
2817
  case "setColorFilter":
2721
2818
  case "shake":
@@ -2796,7 +2893,8 @@ var readAndroid = async (tools, serial) => {
2796
2893
  const settings = {
2797
2894
  ...night?.includes("yes") ? { appearance: "dark" } : night?.includes("no") ? { appearance: "light" } : {},
2798
2895
  ...Number.isFinite(scale) ? { textSize: textSizeFromAndroid(scale) } : {},
2799
- ...animator !== void 0 && animator !== "null" ? { reduceMotion: Number(animator) === 0 } : {},
2896
+ // 没动过这项设置时系统读出 "null",含义是默认动画,不是未知。
2897
+ ...animator === void 0 ? {} : { reduceMotion: animator !== "null" && Number(animator) === 0 },
2800
2898
  ...wifi === "1" || wifi === "0" ? { networkEnabled: wifi === "1" } : {}
2801
2899
  };
2802
2900
  const foregroundApp = focused?.[1] === void 0 ? null : { id: focused[1] };
@@ -2840,7 +2938,18 @@ var parseHubDeviceList = (value) => {
2840
2938
  var parseHubActionResult = (value) => {
2841
2939
  if (!isRecord3(value) || typeof value["ok"] !== "boolean") return null;
2842
2940
  const text2 = (key2) => typeof value[key2] === "string" ? value[key2] : void 0;
2843
- return { ok: value["ok"], id: text2("id"), serial: text2("serial"), error: text2("error") };
2941
+ const causes = Array.isArray(value["errors"]) ? value["errors"].flatMap(
2942
+ (item) => isRecord3(item) && typeof item["message"] === "string" ? [
2943
+ [item["message"], typeof item["error"] === "string" ? item["error"] : ""].join(" ").split("\n")[0].trim()
2944
+ ] : []
2945
+ ) : [];
2946
+ const error = [text2("error"), ...causes].filter(Boolean).join(": ");
2947
+ return {
2948
+ ok: value["ok"],
2949
+ id: text2("id"),
2950
+ serial: text2("serial"),
2951
+ error: error === "" ? void 0 : error
2952
+ };
2844
2953
  };
2845
2954
  var HubClient = class {
2846
2955
  origin;
@@ -3072,7 +3181,9 @@ var DeviceService = class {
3072
3181
  }
3073
3182
  }
3074
3183
  }
3075
- return list.errors.length > 0 ? { devices, detail: list.errors.join("\n") } : { devices };
3184
+ const iosAvailable = (await this.host.platformAvailability("ios")).available;
3185
+ const errors = iosAvailable ? list.errors : list.errors.filter((error) => !error.startsWith("[apple-utils]"));
3186
+ return errors.length > 0 ? { devices, detail: errors.join("\n") } : { devices };
3076
3187
  }
3077
3188
  async refresh(ready) {
3078
3189
  const generation = this.environmentGeneration;
@@ -3563,7 +3674,7 @@ var deviceToolkit = (service) => ({
3563
3674
  var HUB_SERVICE = "hub";
3564
3675
  var index_default = definePlugin({
3565
3676
  id: "devices",
3566
- apiVersion: 60,
3677
+ apiVersion: 63,
3567
3678
  activate: async (ctx) => {
3568
3679
  const direct = (input, init) => ctx.proxies.fetch(PLUGIN_PROXY_DIRECT).then((fetchDirect) => fetchDirect(input, init));
3569
3680
  const host = new LocalDeviceHost({
@@ -3592,12 +3703,16 @@ var index_default = definePlugin({
3592
3703
  });
3593
3704
  ctx.toolkit(deviceToolkit(service));
3594
3705
  registerCommands(ctx, service);
3706
+ const downloadProxy = ctx.config.downloadProxy;
3707
+ const environmentFetch = (input, init) => ctx.proxies.fetch(
3708
+ !downloadProxy ? void 0 : downloadProxy === PLUGIN_PROXY_DIRECT ? null : downloadProxy
3709
+ ).then((fetchThroughProxy) => fetchThroughProxy(input, init));
3595
3710
  const environments = new DeviceEnvironments({
3596
3711
  dataDir: ctx.paths.dataDir,
3597
3712
  helperPath: NodePath9.join(ctx.paths.pluginDir, "dist", "helper.mjs"),
3598
3713
  runtimeDir: bundledToolPaths(ctx.paths.pluginDir, HUB_SPEC, ctx.paths.runtimeDir).installDir,
3599
3714
  process: ctx.process,
3600
- fetch: ctx.http,
3715
+ fetch: environmentFetch,
3601
3716
  log: ctx.log,
3602
3717
  externalSdk: async () => {
3603
3718
  const probe = await host.machine(true);
@@ -3609,6 +3724,7 @@ var index_default = definePlugin({
3609
3724
  beforeMutation: () => service.beginEnvironmentMutation(),
3610
3725
  afterPrepared: async () => {
3611
3726
  await service.inspect(true);
3727
+ await service.list();
3612
3728
  }
3613
3729
  });
3614
3730
  registerEnvironmentCommands(ctx, environments);
package/locales/en.json CHANGED
@@ -41,9 +41,7 @@
41
41
  "confirmData": "I understand this permanently deletes apps and data in the private Android virtual device",
42
42
  "cleanDevices": "Delete private virtual device data",
43
43
  "working": "Checking the machine…",
44
- "preparing": "Preparing components. Downloads and Apple's installation can take several minutes. You can return to this panel to check progress.",
45
44
  "failed": "The operation could not finish. Check the official guidance, selected disk, network and whether the virtual device is powered off, then retry. More detail is available in this machine's plugin logs.",
46
- "done": "Operation completed. Refresh the device list to check and open the simulator.",
47
45
  "reasons": {
48
46
  "ios-needs-macos": "iOS simulators require a Mac with Xcode.",
49
47
  "ios-tools-missing": "The bundled iOS Simulator helpers are missing or could not load on this machine. Repair or update Ycodium with the matching platform components.",
@@ -59,7 +57,27 @@
59
57
  "virtualization-probe-failed": "The emulator acceleration status could not be determined. This is not evidence that hardware virtualization is disabled. Retry the check or consult the official setup guidance."
60
58
  },
61
59
  "unsupported": "This machine can't run the emulator yet, so preparation is unavailable. See the official setup guidance above.",
62
- "notEnoughSpace": "Not enough free space on this disk. Choose a directory on another disk above, then check again."
60
+ "notEnoughSpace": "Not enough free space on this disk. Choose a directory on another disk above, then check again.",
61
+ "stages": {
62
+ "starting": "Getting ready…",
63
+ "download": "Downloading {{component}}",
64
+ "extract": "Unpacking {{component}}",
65
+ "device": "Creating the virtual device",
66
+ "validate": "Checking the installation",
67
+ "apple-download": "Apple is downloading {{component}}",
68
+ "apple-install": "Apple is installing the runtime"
69
+ },
70
+ "transferred": "{{done}} of {{total}}",
71
+ "speed": "{{speed}}/s",
72
+ "timeLeft": "about {{minutes}} min left",
73
+ "slowDownload": "This download is slow. If this machine uses a proxy, try another one or none under Download proxy in the Devices settings. Cancelling keeps the components that finished.",
74
+ "continues": "This keeps running if you leave this panel.",
75
+ "cancel": "Cancel",
76
+ "cancelled": "Preparation cancelled. Components that finished downloading are kept for next time.",
77
+ "prepareFailed": "Preparation couldn't finish. Fix the problem below, then try again. Components that finished downloading are kept.",
78
+ "ready": "This environment is ready. Start the emulator from the list below.",
79
+ "nothingToDownload": "Nothing to download",
80
+ "cleaned": "Cleaned up."
63
81
  },
64
82
  "settings": {
65
83
  "card": {
@@ -78,6 +96,10 @@
78
96
  "path": "found through adb on PATH"
79
97
  }
80
98
  },
99
+ "downloadProxy": {
100
+ "label": "Download proxy",
101
+ "description": "Used when downloading emulator components. If downloads are slow, try a different proxy or no proxy."
102
+ },
81
103
  "checkTools": {
82
104
  "label": "Check tool versions",
83
105
  "description": "Reads the installed device tools without installing or starting anything.",
@@ -222,6 +244,7 @@
222
244
  "presetPlaceholder": "Preset…",
223
245
  "set": "Set",
224
246
  "clear": "Clear",
247
+ "current": "Set to {{latitude}}, {{longitude}}",
225
248
  "sanFrancisco": "San Francisco",
226
249
  "newYork": "New York",
227
250
  "london": "London",
@@ -272,6 +295,7 @@
272
295
  "updateTo": "Verify bundled {{version}}",
273
296
  "pendingPolicy": "Device helpers are included with Ycodium. Missing or incomplete components require repairing or updating Ycodium on this machine.",
274
297
  "checkHint": "Checking reads the installed tools without installing or starting anything.",
298
+ "checked": "Checked. The versions above are current.",
275
299
  "check": "Check versions",
276
300
  "checkFailed": "Could not read the tool versions. Try again.",
277
301
  "updateFailed": "Bundled components are missing or incomplete. Repair or update Ycodium on this machine."
@@ -41,9 +41,7 @@
41
41
  "confirmData": "我理解此操作会永久删除私有 Android 虚拟设备中的应用和数据",
42
42
  "cleanDevices": "删除私有虚拟设备数据",
43
43
  "working": "正在检查这台机器…",
44
- "preparing": "正在准备组件。下载与 Apple 安装可能需要几分钟,可稍后回到此面板查看进度。",
45
44
  "failed": "操作未能完成。请按官方指引检查所选磁盘、网络及虚拟设备是否已关机,再重试。具体原因可在这台机器的插件日志中查看。",
46
- "done": "操作已完成。刷新设备列表以检查并打开模拟器。",
47
45
  "reasons": {
48
46
  "ios-needs-macos": "iOS 模拟器需要装有 Xcode 的 Mac。",
49
47
  "ios-tools-missing": "这台机器上的随包 iOS 模拟器助手缺失或无法加载。请使用匹配的平台组件修复或更新 Ycodium。",
@@ -59,7 +57,27 @@
59
57
  "virtualization-probe-failed": "无法确定模拟器加速状态,不能据此认定硬件虚拟化未开启。请重试检查或参考官方设置指引。"
60
58
  },
61
59
  "unsupported": "这台机器暂时跑不了模拟器,所以现在不能准备。请看上面的官方设置指引。",
62
- "notEnoughSpace": "这块磁盘剩余空间不够。请在上面换一个其他磁盘上的目录,再检查一次。"
60
+ "notEnoughSpace": "这块磁盘剩余空间不够。请在上面换一个其他磁盘上的目录,再检查一次。",
61
+ "stages": {
62
+ "starting": "正在准备…",
63
+ "download": "正在下载 {{component}}",
64
+ "extract": "正在解包 {{component}}",
65
+ "device": "正在创建虚拟设备",
66
+ "validate": "正在检查安装结果",
67
+ "apple-download": "Apple 正在下载 {{component}}",
68
+ "apple-install": "Apple 正在安装运行时"
69
+ },
70
+ "transferred": "{{done}} / {{total}}",
71
+ "speed": "{{speed}}/s",
72
+ "timeLeft": "还需约 {{minutes}} 分钟",
73
+ "slowDownload": "下载很慢。如果这台机器用了代理,可以在设备设置的「下载代理」里换一个或不走代理。取消后,已下完的组件会保留。",
74
+ "continues": "离开这个面板后会继续进行。",
75
+ "cancel": "取消",
76
+ "cancelled": "已取消准备。已下完的组件会保留,下次不用重下。",
77
+ "prepareFailed": "准备没能完成。请处理下面的问题后重试,已下完的组件会保留。",
78
+ "ready": "环境已就绪。请从下方列表启动模拟器。",
79
+ "nothingToDownload": "无需下载",
80
+ "cleaned": "已清理。"
63
81
  },
64
82
  "settings": {
65
83
  "card": {
@@ -78,6 +96,10 @@
78
96
  "path": "按 PATH 上的 adb 找到"
79
97
  }
80
98
  },
99
+ "downloadProxy": {
100
+ "label": "下载代理",
101
+ "description": "下载模拟器组件时使用。下载很慢时,可以换一个代理或不走代理。"
102
+ },
81
103
  "checkTools": {
82
104
  "label": "检查工具版本",
83
105
  "description": "读取已安装的设备工具,不安装也不启动任何东西。",
@@ -222,6 +244,7 @@
222
244
  "presetPlaceholder": "预设…",
223
245
  "set": "设置",
224
246
  "clear": "清除",
247
+ "current": "已设为 {{latitude}}, {{longitude}}",
225
248
  "sanFrancisco": "旧金山",
226
249
  "newYork": "纽约",
227
250
  "london": "伦敦",
@@ -272,6 +295,7 @@
272
295
  "updateTo": "校验随包版本 {{version}}",
273
296
  "pendingPolicy": "设备辅助工具随 Ycodium 提供。组件缺失或不完整时,请修复或更新这台机器上的 Ycodium。",
274
297
  "checkHint": "检查只读取已装的工具,不安装也不启动任何东西。",
298
+ "checked": "检查完成,上面的版本是最新读取的结果。",
275
299
  "check": "检查版本",
276
300
  "checkFailed": "读不到工具版本,请重试。",
277
301
  "updateFailed": "随包组件缺失或不完整,请修复或更新这台机器上的 Ycodium。"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/plugin-devices",
3
- "version": "0.2.3799",
3
+ "version": "0.2.3851",
4
4
  "description": "Lets agents drive iOS Simulators and Android Emulators on this machine.",
5
5
  "keywords": [
6
6
  "android",
@@ -16,6 +16,7 @@
16
16
  "dist",
17
17
  "locales",
18
18
  "assets",
19
+ "skills",
19
20
  "README.md"
20
21
  ],
21
22
  "type": "module",
@@ -33,9 +34,9 @@
33
34
  "tailwindcss": "^4.0.0",
34
35
  "typescript": "~6.0.3",
35
36
  "vite-plus": "0.2.2",
36
- "@ycodium-ai/plugin-api": "0.2.3799",
37
- "ycodium-fetch-ports": "0.0.0",
38
- "ycodium-sse-stream": "0.0.0"
37
+ "ycodium-sse-stream": "0.0.0",
38
+ "@ycodium-ai/plugin-api": "0.2.3851",
39
+ "ycodium-fetch-ports": "0.0.0"
39
40
  },
40
41
  "ycodium": {
41
42
  "plugin": "./dist/index.js",
@@ -44,6 +45,11 @@
44
45
  "description": "Lets agents drive iOS Simulators and Android Emulators on this machine.",
45
46
  "descriptionKey": "presentation.description",
46
47
  "icon": "./assets/icon.svg",
48
+ "skills": [
49
+ {
50
+ "dir": "./skills/mobile-devices"
51
+ }
52
+ ],
47
53
  "ui": {
48
54
  "assets": "./assets",
49
55
  "entry": "ui.js",