@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/README.md +9 -3
- package/assets/ui.js +13 -4
- package/dist/adb-long-paths.mjs +42 -0
- package/dist/environment-helper.mjs +20 -0
- package/dist/helper.mjs +24 -0
- package/dist/index.js +169 -53
- package/locales/en.json +27 -3
- package/locales/zh-CN.json +27 -3
- package/package.json +10 -4
- package/skills/mobile-devices/SKILL.md +110 -0
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
1173
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
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([
|
|
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
|
-
|
|
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
|
|
2712
|
-
|
|
2713
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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."
|
package/locales/zh-CN.json
CHANGED
|
@@ -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.
|
|
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
|
-
"
|
|
37
|
-
"ycodium-
|
|
38
|
-
"ycodium-
|
|
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",
|