@ycodium-ai/plugin-devices 0.2.3817 → 0.2.3904
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 +5 -2
- package/assets/ui.js +5 -5
- package/dist/adb-long-paths.mjs +42 -0
- package/dist/helper.mjs +7 -5
- package/dist/index.js +206 -102
- package/locales/en.json +5 -1
- package/locales/zh-CN.json +5 -1
- package/package.json +8 -2
- 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"])
|
|
@@ -559,9 +563,16 @@ var registerCommands = (ctx, service) => {
|
|
|
559
563
|
import * as NodeFSP3 from "node:fs/promises";
|
|
560
564
|
import * as NodePath3 from "node:path";
|
|
561
565
|
|
|
566
|
+
// src/environmentTypes.ts
|
|
567
|
+
var ANDROID_VERSIONS = [
|
|
568
|
+
{ api: "35", name: "Android 15" },
|
|
569
|
+
{ api: "30", name: "Android 11" }
|
|
570
|
+
];
|
|
571
|
+
var BASE_ANDROID_API = ANDROID_VERSIONS[0].api;
|
|
572
|
+
|
|
562
573
|
// src/androidPackages.ts
|
|
563
574
|
var repo = "https://dl.google.com/android/repository/";
|
|
564
|
-
var archive = (name, file, sha1, bytes, destination = "
|
|
575
|
+
var archive = (name, file, sha1, bytes, destination = "") => ({ name, url: `${repo}${file}`, sha1, bytes, destination });
|
|
565
576
|
var platformTools = {
|
|
566
577
|
linux: archive(
|
|
567
578
|
"Platform-Tools 37.0.1",
|
|
@@ -608,19 +619,38 @@ var emulators = {
|
|
|
608
619
|
455342868
|
|
609
620
|
)
|
|
610
621
|
};
|
|
611
|
-
var
|
|
622
|
+
var images = {
|
|
623
|
+
"35": {
|
|
624
|
+
name: "Android 15 (API 35), AOSP",
|
|
625
|
+
x64: ["x86_64-35_r02.zip", "2d857d170c0d1b827149565da34b3383e5306f7f", 782404023],
|
|
626
|
+
arm64: ["arm64-v8a-35_r02.zip", "2026a06409db630b56711afdbffb457c1dbaed49", 769099654]
|
|
627
|
+
},
|
|
628
|
+
"30": {
|
|
629
|
+
name: "Android 11 (API 30), AOSP",
|
|
630
|
+
x64: ["x86_64-30_r11.zip", "5e4de3946d46f88856c35efcc4d797b381456347", 676379777],
|
|
631
|
+
arm64: ["arm64-v8a-30_r02.zip", "e96298145a5e0bfd6da4816f51b06c520d8dba72", 548363615]
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
var image = (api, arch2) => {
|
|
635
|
+
const [file, sha1, bytes] = images[api][arch2];
|
|
636
|
+
return archive(
|
|
637
|
+
images[api].name,
|
|
638
|
+
`sys-img/android/${file}`,
|
|
639
|
+
sha1,
|
|
640
|
+
bytes,
|
|
641
|
+
`system-images/android-${api}/default`
|
|
642
|
+
);
|
|
643
|
+
};
|
|
644
|
+
var androidArchives = (platform2, arch2, api = BASE_ANDROID_API) => {
|
|
612
645
|
const emulator = emulators[`${platform2}-${arch2}`];
|
|
613
646
|
const tools = platformTools[platform2];
|
|
614
647
|
if (!emulator || !tools) return null;
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
"image"
|
|
622
|
-
);
|
|
623
|
-
return [tools, emulator, image];
|
|
648
|
+
const imageArch = arch2 === "arm64" ? "arm64" : "x64";
|
|
649
|
+
return [
|
|
650
|
+
tools,
|
|
651
|
+
emulator,
|
|
652
|
+
...[.../* @__PURE__ */ new Set([BASE_ANDROID_API, api])].map((item) => image(item, imageArch))
|
|
653
|
+
];
|
|
624
654
|
};
|
|
625
655
|
|
|
626
656
|
// src/environmentArchive.ts
|
|
@@ -826,7 +856,9 @@ var throttledEnvironmentProgress = (publish, now = Date.now) => {
|
|
|
826
856
|
import * as NodeFSP2 from "node:fs/promises";
|
|
827
857
|
import * as NodePath2 from "node:path";
|
|
828
858
|
var ENVIRONMENT_MARKER = ".ycodium-devices-environment.json";
|
|
829
|
-
var
|
|
859
|
+
var androidVersionLabel = (api) => `${ANDROID_VERSIONS.find((item) => item.api === api)?.name} / API ${api} \xB7 Emulator 37.2.12`;
|
|
860
|
+
var ANDROID_ENVIRONMENT_VERSION = androidVersionLabel(BASE_ANDROID_API);
|
|
861
|
+
var androidAvdName = (api) => `Ycodium_API_${api}`;
|
|
830
862
|
var environmentPaths = (root) => ({
|
|
831
863
|
root,
|
|
832
864
|
sdk: NodePath2.join(root, "sdk"),
|
|
@@ -939,27 +971,28 @@ var selectedPrivateSdk = async (dataDir) => {
|
|
|
939
971
|
if (!root) return null;
|
|
940
972
|
return await privateEnvironmentReady(root) ? NodePath2.join(root, "sdk") : null;
|
|
941
973
|
};
|
|
942
|
-
var privateEnvironmentReady = async (root) => {
|
|
974
|
+
var privateEnvironmentReady = async (root, api = BASE_ANDROID_API) => {
|
|
943
975
|
try {
|
|
944
976
|
await assertPrivateEnvironmentPaths(root);
|
|
945
977
|
const paths = environmentPaths(root);
|
|
946
978
|
if (await NodeFSP2.readFile(NodePath2.join(root, ".prepared"), "utf8") !== ANDROID_ENVIRONMENT_VERSION || await NodeFSP2.readFile(NodePath2.join(paths.sdk, ".ycodium-components"), "utf8") !== ANDROID_ENVIRONMENT_VERSION)
|
|
947
979
|
return false;
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
config
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
980
|
+
for (const item of /* @__PURE__ */ new Set([BASE_ANDROID_API, api])) {
|
|
981
|
+
const avd = NodePath2.join(paths.avd, `${androidAvdName(item)}.avd`);
|
|
982
|
+
const ini = NodePath2.join(paths.avd, `${androidAvdName(item)}.ini`);
|
|
983
|
+
await assertPrivatePath(root, NodePath2.join(avd, "config.ini"));
|
|
984
|
+
await assertPrivatePath(root, ini);
|
|
985
|
+
const config = await NodeFSP2.readFile(NodePath2.join(avd, "config.ini"), "utf8");
|
|
986
|
+
const image2 = new RegExp(
|
|
987
|
+
`^image\\.sysdir\\.1\\s*=\\s*(system-images/android-${item}/default/(?:x86_64|arm64-v8a)/)\\s*$`,
|
|
988
|
+
"m"
|
|
989
|
+
).exec(config)?.[1];
|
|
990
|
+
if (!image2) return false;
|
|
991
|
+
await assertPrivatePath(root, NodePath2.join(paths.sdk, image2, "system.img"));
|
|
992
|
+
await NodeFSP2.access(NodePath2.join(paths.sdk, image2, "system.img"));
|
|
993
|
+
if (!(await NodeFSP2.readFile(ini, "utf8")).includes(`path=${avd}
|
|
994
|
+
`)) return false;
|
|
995
|
+
}
|
|
963
996
|
for (const suffix of ["", ".exe"]) {
|
|
964
997
|
try {
|
|
965
998
|
await NodeFSP2.access(NodePath2.join(paths.sdk, "platform-tools", `adb${suffix}`));
|
|
@@ -1003,6 +1036,18 @@ var cleanEnvironment = async (root, category) => {
|
|
|
1003
1036
|
var GIB = 1024 ** 3;
|
|
1004
1037
|
var androidGuide = "https://developer.android.com/studio/run/emulator-acceleration";
|
|
1005
1038
|
var appleGuide = "https://developer.apple.com/documentation/xcode/downloading-and-installing-additional-xcode-components";
|
|
1039
|
+
var androidApi = (version) => {
|
|
1040
|
+
const requested = version?.trim() || BASE_ANDROID_API;
|
|
1041
|
+
const selected = ANDROID_VERSIONS.find((item) => item.api === requested);
|
|
1042
|
+
if (!selected) throw new DeviceError("invalid-input", `Unsupported Android API: ${requested}`);
|
|
1043
|
+
return selected.api;
|
|
1044
|
+
};
|
|
1045
|
+
var pendingArchives = async (archives, sdk) => {
|
|
1046
|
+
const present = await Promise.all(
|
|
1047
|
+
archives.map((item) => NodeFSP3.lstat(NodePath3.join(sdk, item.destination)).catch(() => null))
|
|
1048
|
+
);
|
|
1049
|
+
return archives.filter((_item, index) => present[index] === null);
|
|
1050
|
+
};
|
|
1006
1051
|
var DeviceEnvironments = class {
|
|
1007
1052
|
busy = false;
|
|
1008
1053
|
abort = new AbortController();
|
|
@@ -1115,12 +1160,12 @@ var DeviceEnvironments = class {
|
|
|
1115
1160
|
deviceDataBytes
|
|
1116
1161
|
};
|
|
1117
1162
|
}
|
|
1118
|
-
const
|
|
1163
|
+
const api = androidApi(input.version);
|
|
1164
|
+
const archives = androidArchives(probe.platform, probe.arch, api);
|
|
1119
1165
|
const sdk = await this.options.externalSdk();
|
|
1120
|
-
const prepared = await privateEnvironmentReady(root);
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
(archives ?? []).map(async (item) => {
|
|
1166
|
+
const prepared = await privateEnvironmentReady(root, api);
|
|
1167
|
+
const missing = await Promise.all(
|
|
1168
|
+
(await pendingArchives(archives ?? [], paths.sdk)).map(async (item) => {
|
|
1124
1169
|
const cached = await NodeFSP3.lstat(archivePath(item, paths.cache)).catch(() => null);
|
|
1125
1170
|
return cached?.size === item.bytes ? 0 : item.bytes;
|
|
1126
1171
|
})
|
|
@@ -1131,7 +1176,7 @@ var DeviceEnvironments = class {
|
|
|
1131
1176
|
source: sdk.available && sdk.root !== paths.sdk ? "external" : "private",
|
|
1132
1177
|
...sdk.available && sdk.root ? { existingSdkRoot: sdk.root } : {},
|
|
1133
1178
|
root,
|
|
1134
|
-
version:
|
|
1179
|
+
version: androidVersionLabel(api),
|
|
1135
1180
|
downloadBytes: archives ? missing.reduce((sum, bytes) => sum + bytes, 0) : null,
|
|
1136
1181
|
requiredFreeBytes: 12 * GIB,
|
|
1137
1182
|
freeBytes: free,
|
|
@@ -1199,7 +1244,7 @@ var DeviceEnvironments = class {
|
|
|
1199
1244
|
])
|
|
1200
1245
|
await NodeFSP3.mkdir(directory, { recursive: true });
|
|
1201
1246
|
if (input.platform === "ios") await this.prepareIos(plan, progress, signal);
|
|
1202
|
-
else await this.prepareAndroid(plan, progress, signal);
|
|
1247
|
+
else await this.prepareAndroid(plan, androidApi(input.version), progress, signal);
|
|
1203
1248
|
} finally {
|
|
1204
1249
|
await release?.();
|
|
1205
1250
|
}
|
|
@@ -1207,57 +1252,68 @@ var DeviceEnvironments = class {
|
|
|
1207
1252
|
return this.plan(input);
|
|
1208
1253
|
});
|
|
1209
1254
|
}
|
|
1210
|
-
async prepareAndroid(plan, progress, signal) {
|
|
1255
|
+
async prepareAndroid(plan, api, progress, signal) {
|
|
1211
1256
|
const probe = await this.probe();
|
|
1212
|
-
const archives = androidArchives(probe.platform, probe.arch);
|
|
1257
|
+
const archives = androidArchives(probe.platform, probe.arch, api);
|
|
1213
1258
|
if (!archives) throw new Error("Android host target is unsupported");
|
|
1214
1259
|
const paths = environmentPaths(plan.root);
|
|
1215
1260
|
const staging = await NodeFSP3.mkdtemp(NodePath3.join(paths.temp, "prepare-"));
|
|
1216
1261
|
try {
|
|
1217
1262
|
const existing2 = await NodeFSP3.lstat(paths.sdk).catch(() => null);
|
|
1263
|
+
if (existing2 && (existing2.isSymbolicLink() || !existing2.isDirectory() || await NodeFSP3.readFile(NodePath3.join(paths.sdk, ".ycodium-components"), "utf8").catch(
|
|
1264
|
+
() => ""
|
|
1265
|
+
) !== ANDROID_ENVIRONMENT_VERSION))
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
"An unrecognized private SDK already exists; its installation will not be overwritten"
|
|
1268
|
+
);
|
|
1269
|
+
const pending = await pendingArchives(archives, paths.sdk);
|
|
1270
|
+
const totalBytes = pending.reduce((sum, item) => sum + item.bytes, 0);
|
|
1271
|
+
let finished = 0;
|
|
1272
|
+
for (const item of pending) {
|
|
1273
|
+
const received = (bytes) => progress({
|
|
1274
|
+
phase: "download",
|
|
1275
|
+
component: item.name,
|
|
1276
|
+
receivedBytes: finished + bytes,
|
|
1277
|
+
totalBytes
|
|
1278
|
+
});
|
|
1279
|
+
received(0);
|
|
1280
|
+
signal.throwIfAborted();
|
|
1281
|
+
const file = await downloadArchive(
|
|
1282
|
+
item,
|
|
1283
|
+
paths.cache,
|
|
1284
|
+
(url2, init) => this.options.fetch(url2, { ...init, signal }),
|
|
1285
|
+
received
|
|
1286
|
+
);
|
|
1287
|
+
finished += item.bytes;
|
|
1288
|
+
progress({
|
|
1289
|
+
phase: "extract",
|
|
1290
|
+
component: item.name,
|
|
1291
|
+
receivedBytes: finished,
|
|
1292
|
+
totalBytes
|
|
1293
|
+
});
|
|
1294
|
+
await extractEnvironmentZip(file, NodePath3.join(staging, item.destination), signal);
|
|
1295
|
+
}
|
|
1218
1296
|
if (existing2) {
|
|
1219
|
-
|
|
1220
|
-
()
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
);
|
|
1225
|
-
} else {
|
|
1226
|
-
const totalBytes = archives.reduce((sum, item) => sum + item.bytes, 0);
|
|
1227
|
-
let finished = 0;
|
|
1228
|
-
for (const item of archives) {
|
|
1229
|
-
const received = (bytes) => progress({
|
|
1230
|
-
phase: "download",
|
|
1231
|
-
component: item.name,
|
|
1232
|
-
receivedBytes: finished + bytes,
|
|
1233
|
-
totalBytes
|
|
1234
|
-
});
|
|
1235
|
-
received(0);
|
|
1236
|
-
signal.throwIfAborted();
|
|
1237
|
-
const file = await downloadArchive(
|
|
1238
|
-
item,
|
|
1239
|
-
paths.cache,
|
|
1240
|
-
(url2, init) => this.options.fetch(url2, { ...init, signal }),
|
|
1241
|
-
received
|
|
1242
|
-
);
|
|
1243
|
-
finished += item.bytes;
|
|
1244
|
-
progress({
|
|
1245
|
-
phase: "extract",
|
|
1246
|
-
component: item.name,
|
|
1247
|
-
receivedBytes: finished,
|
|
1248
|
-
totalBytes
|
|
1249
|
-
});
|
|
1250
|
-
await extractEnvironmentZip(
|
|
1251
|
-
file,
|
|
1252
|
-
item.destination === "sdk" ? staging : NodePath3.join(staging, "system-images", "android-35", "default"),
|
|
1253
|
-
signal
|
|
1254
|
-
);
|
|
1297
|
+
for (const item of pending) {
|
|
1298
|
+
const target2 = NodePath3.join(paths.sdk, item.destination);
|
|
1299
|
+
await assertPrivatePath(paths.root, target2);
|
|
1300
|
+
await NodeFSP3.mkdir(NodePath3.dirname(target2), { recursive: true });
|
|
1301
|
+
await NodeFSP3.rename(NodePath3.join(staging, item.destination), target2);
|
|
1255
1302
|
}
|
|
1256
|
-
|
|
1303
|
+
} else {
|
|
1304
|
+
await NodeFSP3.writeFile(
|
|
1305
|
+
NodePath3.join(staging, ".ycodium-components"),
|
|
1306
|
+
ANDROID_ENVIRONMENT_VERSION
|
|
1307
|
+
);
|
|
1257
1308
|
await NodeFSP3.rename(staging, paths.sdk);
|
|
1258
1309
|
}
|
|
1259
1310
|
progress({ phase: "device" });
|
|
1260
|
-
|
|
1311
|
+
for (const item of /* @__PURE__ */ new Set([BASE_ANDROID_API, api]))
|
|
1312
|
+
await this.createPrivateAvd(
|
|
1313
|
+
paths.root,
|
|
1314
|
+
probe.arch === "arm64" ? "arm64-v8a" : "x86_64",
|
|
1315
|
+
item
|
|
1316
|
+
);
|
|
1261
1317
|
signal.throwIfAborted();
|
|
1262
1318
|
progress({ phase: "validate" });
|
|
1263
1319
|
const validated = await this.options.process.run({
|
|
@@ -1268,7 +1324,7 @@ var DeviceEnvironments = class {
|
|
|
1268
1324
|
});
|
|
1269
1325
|
if (validated.exitCode !== 0)
|
|
1270
1326
|
throw new Error(`The private SDK could not be validated. ${validated.stderr.trim()}`);
|
|
1271
|
-
await NodeFSP3.writeFile(NodePath3.join(plan.root, ".prepared"),
|
|
1327
|
+
await NodeFSP3.writeFile(NodePath3.join(plan.root, ".prepared"), ANDROID_ENVIRONMENT_VERSION);
|
|
1272
1328
|
await NodeFSP3.writeFile(
|
|
1273
1329
|
NodePath3.join(this.options.dataDir, "android-environment.json"),
|
|
1274
1330
|
JSON.stringify({ root: plan.root })
|
|
@@ -1277,9 +1333,9 @@ var DeviceEnvironments = class {
|
|
|
1277
1333
|
await NodeFSP3.rm(staging, { recursive: true, force: true });
|
|
1278
1334
|
}
|
|
1279
1335
|
}
|
|
1280
|
-
async createPrivateAvd(root, abi) {
|
|
1336
|
+
async createPrivateAvd(root, abi, api) {
|
|
1281
1337
|
const paths = environmentPaths(root);
|
|
1282
|
-
const name =
|
|
1338
|
+
const name = androidAvdName(api);
|
|
1283
1339
|
const avd = NodePath3.join(paths.avd, `${name}.avd`);
|
|
1284
1340
|
await assertPrivatePath(root, avd);
|
|
1285
1341
|
await assertPrivatePath(root, NodePath3.join(paths.avd, `${name}.ini`));
|
|
@@ -1294,7 +1350,7 @@ var DeviceEnvironments = class {
|
|
|
1294
1350
|
await NodeFSP3.mkdir(avd, { recursive: true });
|
|
1295
1351
|
const config = {
|
|
1296
1352
|
AvdId: name,
|
|
1297
|
-
"avd.ini.displayname":
|
|
1353
|
+
"avd.ini.displayname": `Ycodium ${ANDROID_VERSIONS.find((item) => item.api === api)?.name}`,
|
|
1298
1354
|
"abi.type": abi,
|
|
1299
1355
|
"hw.cpu.arch": abi === "arm64-v8a" ? "arm64" : "x86_64",
|
|
1300
1356
|
"hw.cpu.ncore": "2",
|
|
@@ -1305,8 +1361,10 @@ var DeviceEnvironments = class {
|
|
|
1305
1361
|
"hw.gpu.enabled": "yes",
|
|
1306
1362
|
"hw.gpu.mode": "auto",
|
|
1307
1363
|
"hw.keyboard": "yes",
|
|
1364
|
+
// 不写时 API 30 按实体按键处理,屏幕上没有导航栏。
|
|
1365
|
+
"hw.mainKeys": "no",
|
|
1308
1366
|
"disk.dataPartition.size": "4G",
|
|
1309
|
-
"image.sysdir.1": `system-images/android
|
|
1367
|
+
"image.sysdir.1": `system-images/android-${api}/default/${abi}/`,
|
|
1310
1368
|
"tag.id": "default",
|
|
1311
1369
|
"tag.display": "Default Android System Image",
|
|
1312
1370
|
"PlayStore.enabled": "false"
|
|
@@ -1320,7 +1378,7 @@ var DeviceEnvironments = class {
|
|
|
1320
1378
|
NodePath3.join(paths.avd, `${name}.ini`),
|
|
1321
1379
|
`avd.ini.encoding=UTF-8
|
|
1322
1380
|
path=${avd}
|
|
1323
|
-
target=android
|
|
1381
|
+
target=android-${api}
|
|
1324
1382
|
`,
|
|
1325
1383
|
{ flag: "wx" }
|
|
1326
1384
|
);
|
|
@@ -2189,7 +2247,7 @@ import * as NodePath7 from "node:path";
|
|
|
2189
2247
|
import * as NodeFSP6 from "node:fs/promises";
|
|
2190
2248
|
import * as NodePath6 from "node:path";
|
|
2191
2249
|
import * as NodeOS from "node:os";
|
|
2192
|
-
var DEVICE_HUB_VERSION = "0.
|
|
2250
|
+
var DEVICE_HUB_VERSION = "0.19.1";
|
|
2193
2251
|
var AGENT_DEVICE_VERSION = "0.21.12";
|
|
2194
2252
|
var runtimeTarget = `${NodeOS.platform()}-${NodeOS.arch()}`;
|
|
2195
2253
|
var HUB_SPEC = {
|
|
@@ -2696,6 +2754,10 @@ var runIos = async (tools, udid, action) => {
|
|
|
2696
2754
|
case "launchApp":
|
|
2697
2755
|
await simctl(tools, udid, ["launch", action.appId], "launch");
|
|
2698
2756
|
return;
|
|
2757
|
+
case "relaunchApp":
|
|
2758
|
+
await simctl(tools, udid, ["terminate", action.appId], "terminate").catch(() => void 0);
|
|
2759
|
+
await simctl(tools, udid, ["launch", action.appId], "launch");
|
|
2760
|
+
return;
|
|
2699
2761
|
case "terminateApp":
|
|
2700
2762
|
await simctl(tools, udid, ["terminate", action.appId], "terminate");
|
|
2701
2763
|
return;
|
|
@@ -2712,6 +2774,7 @@ var runIos = async (tools, udid, action) => {
|
|
|
2712
2774
|
var adb = (tools, serial, args, operation) => checked(tools.run("adb", ["-s", serial, ...args]), operation);
|
|
2713
2775
|
var runAndroid = async (tools, serial, action) => {
|
|
2714
2776
|
const shell = (args, operation) => adb(tools, serial, ["shell", ...args], operation);
|
|
2777
|
+
const launch = (appId3) => shell(["monkey", "-p", appId3, "-c", "android.intent.category.LAUNCHER", "1"], "launch");
|
|
2715
2778
|
switch (action.type) {
|
|
2716
2779
|
case "setAppearance":
|
|
2717
2780
|
await shell(["cmd", "uimode", "night", action.value === "dark" ? "yes" : "no"], "appearance");
|
|
@@ -2768,13 +2831,21 @@ var runAndroid = async (tools, serial, action) => {
|
|
|
2768
2831
|
"location"
|
|
2769
2832
|
);
|
|
2770
2833
|
return;
|
|
2771
|
-
case "clearLocation":
|
|
2772
|
-
return;
|
|
2773
2834
|
case "setPermission": {
|
|
2774
2835
|
const permissions = ANDROID_PERMISSIONS[action.permission];
|
|
2775
2836
|
if (permissions === void 0) throw unsupported("permission", "android");
|
|
2776
2837
|
const verb = action.decision === "grant" ? "grant" : "revoke";
|
|
2777
|
-
|
|
2838
|
+
const declared = new Set(
|
|
2839
|
+
(await shell(["dumpsys", "package", action.appId], "permission")).split(/[\s:,]+/u)
|
|
2840
|
+
);
|
|
2841
|
+
const requested = permissions.filter((permission) => declared.has(permission));
|
|
2842
|
+
if (requested.length === 0) {
|
|
2843
|
+
throw new DeviceError(
|
|
2844
|
+
"command-failed",
|
|
2845
|
+
`${action.appId} does not declare this permission, so Android cannot change it.`
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
for (const permission of requested) {
|
|
2778
2849
|
await shell(["pm", verb, action.appId, permission], "permission").catch(() => void 0);
|
|
2779
2850
|
}
|
|
2780
2851
|
return;
|
|
@@ -2786,14 +2857,16 @@ var runAndroid = async (tools, serial, action) => {
|
|
|
2786
2857
|
);
|
|
2787
2858
|
return;
|
|
2788
2859
|
case "launchApp":
|
|
2789
|
-
await
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
);
|
|
2860
|
+
await launch(action.appId);
|
|
2861
|
+
return;
|
|
2862
|
+
case "relaunchApp":
|
|
2863
|
+
await shell(["am", "force-stop", action.appId], "terminate");
|
|
2864
|
+
await launch(action.appId);
|
|
2793
2865
|
return;
|
|
2794
2866
|
case "terminateApp":
|
|
2795
2867
|
await shell(["am", "force-stop", action.appId], "terminate");
|
|
2796
2868
|
return;
|
|
2869
|
+
case "clearLocation":
|
|
2797
2870
|
case "setLiquidGlass":
|
|
2798
2871
|
case "setColorFilter":
|
|
2799
2872
|
case "shake":
|
|
@@ -2859,22 +2932,32 @@ var readIos = async (tools, udid) => {
|
|
|
2859
2932
|
};
|
|
2860
2933
|
return { settings, foregroundApp: null };
|
|
2861
2934
|
};
|
|
2935
|
+
var ANDROID_DETAIL_SEPARATOR = "--ycodium-detail--";
|
|
2936
|
+
var ANDROID_DETAIL_READS = [
|
|
2937
|
+
"cmd uimode night",
|
|
2938
|
+
"settings get system font_scale",
|
|
2939
|
+
"settings get global animator_duration_scale",
|
|
2940
|
+
"settings get global wifi_on",
|
|
2941
|
+
// `dumpsys window windows` 在 API 36 起不再打印焦点;不带过滤的整份 dump 还打。
|
|
2942
|
+
"dumpsys window"
|
|
2943
|
+
];
|
|
2862
2944
|
var readAndroid = async (tools, serial) => {
|
|
2863
|
-
const
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
]);
|
|
2945
|
+
const output = await quiet(
|
|
2946
|
+
() => adb(
|
|
2947
|
+
tools,
|
|
2948
|
+
serial,
|
|
2949
|
+
["shell", ANDROID_DETAIL_READS.join(` ; echo ${ANDROID_DETAIL_SEPARATOR} ; `)],
|
|
2950
|
+
"detail"
|
|
2951
|
+
)
|
|
2952
|
+
);
|
|
2953
|
+
const [night, fontScale, animator, wifi, focus] = (output ?? "").split(ANDROID_DETAIL_SEPARATOR).map((part) => part.trim() || void 0);
|
|
2872
2954
|
const scale = fontScale && fontScale !== "null" ? Number(fontScale) : Number.NaN;
|
|
2873
2955
|
const focused = focus?.match(/m(?:CurrentFocus|FocusedApp)=\w+\{[^ ]+ u\d+ ([^/ ]+)\//);
|
|
2874
2956
|
const settings = {
|
|
2875
2957
|
...night?.includes("yes") ? { appearance: "dark" } : night?.includes("no") ? { appearance: "light" } : {},
|
|
2876
2958
|
...Number.isFinite(scale) ? { textSize: textSizeFromAndroid(scale) } : {},
|
|
2877
|
-
|
|
2959
|
+
// 没动过这项设置时系统读出 "null",含义是默认动画,不是未知。
|
|
2960
|
+
...animator === void 0 ? {} : { reduceMotion: animator !== "null" && Number(animator) === 0 },
|
|
2878
2961
|
...wifi === "1" || wifi === "0" ? { networkEnabled: wifi === "1" } : {}
|
|
2879
2962
|
};
|
|
2880
2963
|
const foregroundApp = focused?.[1] === void 0 ? null : { id: focused[1] };
|
|
@@ -3048,6 +3131,8 @@ var classifyBootFailure = (error) => {
|
|
|
3048
3131
|
};
|
|
3049
3132
|
|
|
3050
3133
|
// src/service.ts
|
|
3134
|
+
var ANDROID_BOOT_POLL_MS = 2e3;
|
|
3135
|
+
var ANDROID_BOOT_POLLS = 60;
|
|
3051
3136
|
var sameDevice = (a, hostId, deviceId) => a.hostId === hostId && a.id === deviceId;
|
|
3052
3137
|
var DeviceService = class {
|
|
3053
3138
|
environmentMutating = false;
|
|
@@ -3161,7 +3246,9 @@ var DeviceService = class {
|
|
|
3161
3246
|
}
|
|
3162
3247
|
}
|
|
3163
3248
|
}
|
|
3164
|
-
|
|
3249
|
+
const iosAvailable = (await this.host.platformAvailability("ios")).available;
|
|
3250
|
+
const errors = iosAvailable ? list.errors : list.errors.filter((error) => !error.startsWith("[apple-utils]"));
|
|
3251
|
+
return errors.length > 0 ? { devices, detail: errors.join("\n") } : { devices };
|
|
3165
3252
|
}
|
|
3166
3253
|
async refresh(ready) {
|
|
3167
3254
|
const generation = this.environmentGeneration;
|
|
@@ -3213,6 +3300,22 @@ var DeviceService = class {
|
|
|
3213
3300
|
this.pendingOpens--;
|
|
3214
3301
|
}
|
|
3215
3302
|
}
|
|
3303
|
+
/** hub 在模拟器连上 adb 时就返回,这时系统还没开机完,画面出不来;等不到也照常打开。 */
|
|
3304
|
+
async waitForAndroidBoot(serial) {
|
|
3305
|
+
for (let poll = 0; poll < ANDROID_BOOT_POLLS; poll += 1) {
|
|
3306
|
+
const result = await this.host.runSdkCommand("adb", [
|
|
3307
|
+
"-s",
|
|
3308
|
+
serial,
|
|
3309
|
+
"shell",
|
|
3310
|
+
"getprop",
|
|
3311
|
+
"sys.boot_completed"
|
|
3312
|
+
]);
|
|
3313
|
+
if (result.code === 0 && result.stdout.trim() === "1") return;
|
|
3314
|
+
await new Promise(
|
|
3315
|
+
(resolve3) => setTimeout(resolve3, this.options.androidBootPollMs ?? ANDROID_BOOT_POLL_MS)
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3216
3319
|
async openDevice(input) {
|
|
3217
3320
|
this.resolveHost(input.hostId);
|
|
3218
3321
|
const availability = await this.host.platformAvailability(input.platform);
|
|
@@ -3254,6 +3357,7 @@ var DeviceService = class {
|
|
|
3254
3357
|
}
|
|
3255
3358
|
if (device.platform === "ios") await hub.attachIosStream(device.id);
|
|
3256
3359
|
bootedId = result.serial ?? result.id ?? device.id;
|
|
3360
|
+
if (device.platform === "android") await this.waitForAndroidBoot(bootedId);
|
|
3257
3361
|
} finally {
|
|
3258
3362
|
this.publish((state) => ({
|
|
3259
3363
|
...state,
|
|
@@ -3652,7 +3756,7 @@ var deviceToolkit = (service) => ({
|
|
|
3652
3756
|
var HUB_SERVICE = "hub";
|
|
3653
3757
|
var index_default = definePlugin({
|
|
3654
3758
|
id: "devices",
|
|
3655
|
-
apiVersion:
|
|
3759
|
+
apiVersion: 66,
|
|
3656
3760
|
activate: async (ctx) => {
|
|
3657
3761
|
const direct = (input, init) => ctx.proxies.fetch(PLUGIN_PROXY_DIRECT).then((fetchDirect) => fetchDirect(input, init));
|
|
3658
3762
|
const host = new LocalDeviceHost({
|
package/locales/en.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"directory": "Storage directory on this machine",
|
|
10
10
|
"defaultDirectory": "Ycodium private data directory",
|
|
11
11
|
"directoryHint": "Choose an absolute folder on the desired disk. A dedicated ycodium-devices folder is created inside it. This path belongs to the connected machine.",
|
|
12
|
+
"androidVersion": "Android version",
|
|
13
|
+
"androidVersionOption": "{{name}} (API {{api}})",
|
|
12
14
|
"iosVersion": "iOS runtime version",
|
|
13
15
|
"iosVersionPlaceholder": "For example, 18.0",
|
|
14
16
|
"inspect": "Check prerequisites and storage",
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
"external": "Existing SDKs are reused by default. Preparing a private environment selects it unless an SDK location is explicitly set in Settings. Normal use of existing virtual devices writes to their data. Private preparation does not modify the external SDK.",
|
|
30
32
|
"guide": "Official setup guidance",
|
|
31
33
|
"appleOwnership": "Apple installs this runtime in its shared simulator environment. Only its download archive is stored privately. No existing runtime is upgraded or removed. Xcode may require you to complete permissions or initialization yourself.",
|
|
32
|
-
"privateOwnership": "Android
|
|
34
|
+
"privateOwnership": "The chosen Android AOSP image, Platform-Tools and Emulator are downloaded from Google at pinned revisions. SDK files, virtual device data, configuration and temporary files stay in private storage. Applications installed in a virtual device are user data.",
|
|
33
35
|
"appleLicense": "Apple software license agreements",
|
|
34
36
|
"androidLicense": "Android SDK License Agreement",
|
|
35
37
|
"acceptLicense": "I accept the applicable component licenses",
|
|
@@ -244,6 +246,7 @@
|
|
|
244
246
|
"presetPlaceholder": "Preset…",
|
|
245
247
|
"set": "Set",
|
|
246
248
|
"clear": "Clear",
|
|
249
|
+
"current": "Set to {{latitude}}, {{longitude}}",
|
|
247
250
|
"sanFrancisco": "San Francisco",
|
|
248
251
|
"newYork": "New York",
|
|
249
252
|
"london": "London",
|
|
@@ -294,6 +297,7 @@
|
|
|
294
297
|
"updateTo": "Verify bundled {{version}}",
|
|
295
298
|
"pendingPolicy": "Device helpers are included with Ycodium. Missing or incomplete components require repairing or updating Ycodium on this machine.",
|
|
296
299
|
"checkHint": "Checking reads the installed tools without installing or starting anything.",
|
|
300
|
+
"checked": "Checked. The versions above are current.",
|
|
297
301
|
"check": "Check versions",
|
|
298
302
|
"checkFailed": "Could not read the tool versions. Try again.",
|
|
299
303
|
"updateFailed": "Bundled components are missing or incomplete. Repair or update Ycodium on this machine."
|
package/locales/zh-CN.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"directory": "这台机器上的存储目录",
|
|
10
10
|
"defaultDirectory": "Ycodium 私有数据目录",
|
|
11
11
|
"directoryHint": "填写目标磁盘上的绝对路径,组件将放进其中专用的 ycodium-devices 文件夹。路径属于当前连接的机器。",
|
|
12
|
+
"androidVersion": "Android 版本",
|
|
13
|
+
"androidVersionOption": "{{name}}(API {{api}})",
|
|
12
14
|
"iosVersion": "iOS 运行时版本",
|
|
13
15
|
"iosVersionPlaceholder": "例如 18.0",
|
|
14
16
|
"inspect": "检查条件与存储空间",
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
"external": "默认复用已有 SDK。准备私有环境后会接入它,但设置中明确指定的 SDK 位置优先。正常使用已有虚拟设备会写入其数据;私有准备不会修改外部 SDK。",
|
|
30
32
|
"guide": "官方设置指引",
|
|
31
33
|
"appleOwnership": "Apple 会将运行时安装到共享模拟器环境,只有下载归档存放在私有目录。不会升级或删除已有运行时。Xcode 如需权限或首次初始化,须由你自行完成。",
|
|
32
|
-
"privateOwnership": "从 Google
|
|
34
|
+
"privateOwnership": "从 Google 下载固定版本的所选 Android AOSP 镜像、Platform-Tools 和 Emulator。SDK、虚拟设备数据、配置和临时文件保存在私有目录。虚拟设备中安装的应用属于用户数据。",
|
|
33
35
|
"appleLicense": "Apple 软件许可协议",
|
|
34
36
|
"androidLicense": "Android SDK 许可协议",
|
|
35
37
|
"acceptLicense": "我接受适用的组件许可协议",
|
|
@@ -244,6 +246,7 @@
|
|
|
244
246
|
"presetPlaceholder": "预设…",
|
|
245
247
|
"set": "设置",
|
|
246
248
|
"clear": "清除",
|
|
249
|
+
"current": "已设为 {{latitude}}, {{longitude}}",
|
|
247
250
|
"sanFrancisco": "旧金山",
|
|
248
251
|
"newYork": "纽约",
|
|
249
252
|
"london": "伦敦",
|
|
@@ -294,6 +297,7 @@
|
|
|
294
297
|
"updateTo": "校验随包版本 {{version}}",
|
|
295
298
|
"pendingPolicy": "设备辅助工具随 Ycodium 提供。组件缺失或不完整时,请修复或更新这台机器上的 Ycodium。",
|
|
296
299
|
"checkHint": "检查只读取已装的工具,不安装也不启动任何东西。",
|
|
300
|
+
"checked": "检查完成,上面的版本是最新读取的结果。",
|
|
297
301
|
"check": "检查版本",
|
|
298
302
|
"checkFailed": "读不到工具版本,请重试。",
|
|
299
303
|
"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.3904",
|
|
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,7 +34,7 @@
|
|
|
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.
|
|
37
|
+
"@ycodium-ai/plugin-api": "0.2.3904",
|
|
37
38
|
"ycodium-fetch-ports": "0.0.0",
|
|
38
39
|
"ycodium-sse-stream": "0.0.0"
|
|
39
40
|
},
|
|
@@ -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",
|