@quicktvui/ai-cli 1.1.2 → 1.1.5
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 +15 -11
- package/docs/esapp-protocol.md +2 -6
- package/lib/index.js +222 -34
- package/package.json +1 -1
- package/scripts/postinstall-sync.js +65 -3
package/README.md
CHANGED
|
@@ -60,6 +60,8 @@ quicktvui-aicreate-project quick-tv-app
|
|
|
60
60
|
- `--auto-emulator <true|false>`: auto create/start emulator when no adb device
|
|
61
61
|
- `--adb-path <path>`: custom adb path/command (or use env `QUICKTVUI_ADB_PATH`)
|
|
62
62
|
- `--device-ip <ip[:port]>`: preferred real device endpoint for `adb connect`
|
|
63
|
+
- `--device <serial>`: explicit target adb serial for `setup-android-env` / `run-esapp`
|
|
64
|
+
- `--allow-non-tv-device <true|false>`: allow phone/tablet target for TV run flow (default `false`)
|
|
63
65
|
- `--avd-name <name>`: custom AVD name for `setup-android-env`
|
|
64
66
|
- `--headless`: start emulator with `-no-window -no-audio`
|
|
65
67
|
- `--runtime-version <version>`: pin runtime version in `direct` mode
|
|
@@ -71,8 +73,7 @@ quicktvui-aicreate-project quick-tv-app
|
|
|
71
73
|
- `--port <n>`: dev server port used by `run-dev` auto load (default `38989`)
|
|
72
74
|
- `--skip-env-check`: skip environment stage in `run-dev`
|
|
73
75
|
- `--runtime-package <pkg>`: runtime package for `run-esapp` (default `com.extscreen.runtime`)
|
|
74
|
-
- `--
|
|
75
|
-
- `--esapp-uri <uri>`: raw launch URI (`esapp://`, `quicktv://`, `appcast://`)
|
|
76
|
+
- `--esapp-uri <uri>`: raw launch URI (`esapp://`)
|
|
76
77
|
- `--esapp-query <json>`: extra query params JSON merged in structured mode
|
|
77
78
|
- `--pkg --ver --min-ver --repository --uri --from --args --exp --flags --use-latest`: structured `esapp://action/start` params
|
|
78
79
|
|
|
@@ -115,15 +116,18 @@ This command:
|
|
|
115
116
|
|
|
116
117
|
1. Detects Android SDK root (auto creates a default SDK root when missing).
|
|
117
118
|
2. Detects Android SDK/adb/emulator tools. `@quicktvui/ai-cli` does not bundle adb.
|
|
118
|
-
3.
|
|
119
|
-
4.
|
|
120
|
-
5.
|
|
121
|
-
6. If
|
|
122
|
-
7.
|
|
123
|
-
8.
|
|
124
|
-
9.
|
|
125
|
-
10.
|
|
126
|
-
11.
|
|
119
|
+
3. Verifies target device looks like TV (`leanback` / `television` feature). Use `--allow-non-tv-device true` only if you intentionally target phone/tablet.
|
|
120
|
+
4. In non-interactive mode, requires explicit `--device <serial>` when adb devices are connected.
|
|
121
|
+
5. If sdkmanager/avdmanager is missing, it auto-downloads and installs official Android Command-line Tools.
|
|
122
|
+
6. If adb is missing, it auto-installs `platform-tools` (with size estimate + sdkmanager progress).
|
|
123
|
+
7. Detects connected adb devices, and asks whether to use connected device.
|
|
124
|
+
8. If user doesn't use connected device (or no device exists), it asks for real-device IP and runs `adb connect`.
|
|
125
|
+
9. If still no real device, asks whether to download/start official Google Android emulator.
|
|
126
|
+
10. Checks Google repository reachability before emulator download; if unavailable, asks user to install emulator manually.
|
|
127
|
+
11. Prints estimated download size and then starts sdkmanager download (progress shown by sdkmanager).
|
|
128
|
+
12. Installs runtime APK and configures debug server host.
|
|
129
|
+
13. If runtime already exists, asks whether to reinstall runtime before run.
|
|
130
|
+
14. Launches runtime app and waits it enters running state.
|
|
127
131
|
|
|
128
132
|
## Configure Vue env (Node + package manager)
|
|
129
133
|
|
package/docs/esapp-protocol.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ESApp Protocol Reference (`esapp
|
|
1
|
+
# ESApp Protocol Reference (`esapp://`)
|
|
2
2
|
|
|
3
3
|
This document summarizes runtime launch protocol behavior from:
|
|
4
4
|
|
|
@@ -12,8 +12,6 @@ This document summarizes runtime launch protocol behavior from:
|
|
|
12
12
|
Runtime entry activity accepts:
|
|
13
13
|
|
|
14
14
|
- `esapp://`
|
|
15
|
-
- `quicktv://`
|
|
16
|
-
- `appcast://`
|
|
17
15
|
|
|
18
16
|
## 2) Parsing Modes
|
|
19
17
|
|
|
@@ -29,13 +27,11 @@ Behavior:
|
|
|
29
27
|
- `/start` maps to `action=start_es`.
|
|
30
28
|
- All query params are forwarded to protocol JSON.
|
|
31
29
|
|
|
32
|
-
### Mode B: V2 package URI (esapp
|
|
30
|
+
### Mode B: V2 package URI (esapp)
|
|
33
31
|
|
|
34
32
|
```text
|
|
35
33
|
esapp://es.hello.world?from=cmd
|
|
36
34
|
esapp://es.hello.world/1.0.3?from=cmd
|
|
37
|
-
quicktv://es.hello.world?from=cmd
|
|
38
|
-
appcast://es.hello.world/1.0.3?from=cmd
|
|
39
35
|
```
|
|
40
36
|
|
|
41
37
|
Behavior:
|
package/lib/index.js
CHANGED
|
@@ -31,6 +31,8 @@ const RUNTIME_LAUNCH_ACTIVITY =
|
|
|
31
31
|
"com.extscreen.runtime/com.extscreen.runtime.LauncherAlias";
|
|
32
32
|
const RUNTIME_DEBUG_BROADCAST_ACTION =
|
|
33
33
|
"com.extscreen.runtime.ACTION_CHANGE_DEBUG_SERVER";
|
|
34
|
+
const RUNTIME_DEBUG_BROADCAST_ACTION_FALLBACK =
|
|
35
|
+
"tv.eskit.debugger.ACTION_CHANGE_DEBUG_SERVER";
|
|
34
36
|
const RUNTIME_REPOSITORY_ROOT =
|
|
35
37
|
"http://hub.quicktvui.com/repository/maven-files/apk/runtime/dev";
|
|
36
38
|
const RUNTIME_REPOSITORY_METADATA_URL = `${RUNTIME_REPOSITORY_ROOT}/maven-metadata.xml`;
|
|
@@ -1212,6 +1214,105 @@ function listConnectedDevices(adbPath) {
|
|
|
1212
1214
|
return parseAdbDevices(result.stdout);
|
|
1213
1215
|
}
|
|
1214
1216
|
|
|
1217
|
+
function runAdbShellCapture(adbPath, serial, shellArgs) {
|
|
1218
|
+
try {
|
|
1219
|
+
const result = runCommandCapture(adbPath, [
|
|
1220
|
+
"-s",
|
|
1221
|
+
serial,
|
|
1222
|
+
"shell",
|
|
1223
|
+
...shellArgs,
|
|
1224
|
+
]);
|
|
1225
|
+
return (result.stdout || "").trim();
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
return "";
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function parseAndroidWmSize(rawText) {
|
|
1232
|
+
const text = String(rawText || "");
|
|
1233
|
+
const match = text.match(/(\d+)\s*x\s*(\d+)/);
|
|
1234
|
+
if (!match) return null;
|
|
1235
|
+
const width = Number.parseInt(match[1], 10);
|
|
1236
|
+
const height = Number.parseInt(match[2], 10);
|
|
1237
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
|
|
1238
|
+
return { width, height };
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function inspectAndroidDeviceProfile(adbPath, serial) {
|
|
1242
|
+
const model =
|
|
1243
|
+
runAdbShellCapture(adbPath, serial, ["getprop", "ro.product.model"]) ||
|
|
1244
|
+
"unknown";
|
|
1245
|
+
const characteristics =
|
|
1246
|
+
runAdbShellCapture(adbPath, serial, [
|
|
1247
|
+
"getprop",
|
|
1248
|
+
"ro.build.characteristics",
|
|
1249
|
+
]) || "";
|
|
1250
|
+
const featureText = runAdbShellCapture(adbPath, serial, [
|
|
1251
|
+
"pm",
|
|
1252
|
+
"list",
|
|
1253
|
+
"features",
|
|
1254
|
+
]);
|
|
1255
|
+
const wmSizeText = runAdbShellCapture(adbPath, serial, ["wm", "size"]);
|
|
1256
|
+
const wmSize = parseAndroidWmSize(wmSizeText);
|
|
1257
|
+
|
|
1258
|
+
const featureLines = featureText
|
|
1259
|
+
.split(/\r?\n/)
|
|
1260
|
+
.map((line) => line.trim().toLowerCase())
|
|
1261
|
+
.filter(Boolean);
|
|
1262
|
+
const hasLeanbackFeature = featureLines.some(
|
|
1263
|
+
(line) =>
|
|
1264
|
+
line.includes("feature:android.software.leanback") ||
|
|
1265
|
+
line.includes("feature:android.hardware.type.television"),
|
|
1266
|
+
);
|
|
1267
|
+
const characteristicText = characteristics.toLowerCase();
|
|
1268
|
+
const hasTvCharacteristic =
|
|
1269
|
+
characteristicText.includes("tv") ||
|
|
1270
|
+
characteristicText.includes("television");
|
|
1271
|
+
const isLikelyTv = hasLeanbackFeature || hasTvCharacteristic;
|
|
1272
|
+
|
|
1273
|
+
return {
|
|
1274
|
+
serial,
|
|
1275
|
+
model,
|
|
1276
|
+
characteristics,
|
|
1277
|
+
wmSize,
|
|
1278
|
+
hasLeanbackFeature,
|
|
1279
|
+
hasTvCharacteristic,
|
|
1280
|
+
isLikelyTv,
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
async function ensureTvSuitableDevice(adbPath, serial, args) {
|
|
1285
|
+
const allowNonTvDevice = toBooleanFlag(args["allow-non-tv-device"], false);
|
|
1286
|
+
const profile = inspectAndroidDeviceProfile(adbPath, serial);
|
|
1287
|
+
const sizeText = profile.wmSize
|
|
1288
|
+
? `${profile.wmSize.width}x${profile.wmSize.height}`
|
|
1289
|
+
: "unknown";
|
|
1290
|
+
console.log(
|
|
1291
|
+
`Target device profile: serial=${serial}, model=${profile.model}, size=${sizeText}, tvFeature=${profile.hasLeanbackFeature}, tvCharacteristic=${profile.hasTvCharacteristic}`,
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
if (profile.isLikelyTv || allowNonTvDevice) {
|
|
1295
|
+
if (!profile.isLikelyTv && allowNonTvDevice) {
|
|
1296
|
+
console.log(
|
|
1297
|
+
"Warning: selected device does not look like a TV device. Continue because --allow-non-tv-device is enabled.",
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
return profile;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const continueWithNonTv = await askYesNo(
|
|
1304
|
+
`Selected device ${serial} (${profile.model}) does not look like a TV/box device. Continue with this device anyway?`,
|
|
1305
|
+
false,
|
|
1306
|
+
args,
|
|
1307
|
+
);
|
|
1308
|
+
if (!continueWithNonTv) {
|
|
1309
|
+
throw new Error(
|
|
1310
|
+
`Selected device is likely non-TV. Connect/select a TV device or pass --allow-non-tv-device true to continue with ${serial}.`,
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
return profile;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1215
1316
|
function listAvds(emulatorPath) {
|
|
1216
1317
|
const result = runCommandCapture(emulatorPath, ["-list-avds"]);
|
|
1217
1318
|
return result.stdout
|
|
@@ -1391,19 +1492,54 @@ async function waitForRuntimeRunning(adbPath, serial, timeoutMs) {
|
|
|
1391
1492
|
return false;
|
|
1392
1493
|
}
|
|
1393
1494
|
|
|
1495
|
+
function broadcastDebugServerHost(adbPath, serial, action, hostIp) {
|
|
1496
|
+
try {
|
|
1497
|
+
const result = runCommandCapture(adbPath, [
|
|
1498
|
+
"-s",
|
|
1499
|
+
serial,
|
|
1500
|
+
"shell",
|
|
1501
|
+
"am",
|
|
1502
|
+
"broadcast",
|
|
1503
|
+
"-a",
|
|
1504
|
+
action,
|
|
1505
|
+
"--es",
|
|
1506
|
+
"ip",
|
|
1507
|
+
hostIp,
|
|
1508
|
+
]);
|
|
1509
|
+
const output = `${result.stdout || ""}\n${result.stderr || ""}`.trim();
|
|
1510
|
+
const hasError = /(error|exception|failed)/i.test(output);
|
|
1511
|
+
const hasCompletion = /broadcast completed/i.test(output);
|
|
1512
|
+
return {
|
|
1513
|
+
action,
|
|
1514
|
+
success: hasCompletion && !hasError,
|
|
1515
|
+
output,
|
|
1516
|
+
};
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
return {
|
|
1519
|
+
action,
|
|
1520
|
+
success: false,
|
|
1521
|
+
output: error.message || "unknown broadcast error",
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1394
1526
|
function setRuntimeDebugServerHost(adbPath, serial, hostIp) {
|
|
1395
|
-
|
|
1396
|
-
|
|
1527
|
+
const primary = broadcastDebugServerHost(
|
|
1528
|
+
adbPath,
|
|
1397
1529
|
serial,
|
|
1398
|
-
"shell",
|
|
1399
|
-
"am",
|
|
1400
|
-
"broadcast",
|
|
1401
|
-
"-a",
|
|
1402
1530
|
RUNTIME_DEBUG_BROADCAST_ACTION,
|
|
1403
|
-
"--es",
|
|
1404
|
-
"ip",
|
|
1405
1531
|
hostIp,
|
|
1406
|
-
|
|
1532
|
+
);
|
|
1533
|
+
const fallback = broadcastDebugServerHost(
|
|
1534
|
+
adbPath,
|
|
1535
|
+
serial,
|
|
1536
|
+
RUNTIME_DEBUG_BROADCAST_ACTION_FALLBACK,
|
|
1537
|
+
hostIp,
|
|
1538
|
+
);
|
|
1539
|
+
return {
|
|
1540
|
+
success: primary.success || fallback.success,
|
|
1541
|
+
details: [primary, fallback],
|
|
1542
|
+
};
|
|
1407
1543
|
}
|
|
1408
1544
|
|
|
1409
1545
|
async function ensureRuntimeInstalledAndConfigured(adbPath, serial, args) {
|
|
@@ -1436,7 +1572,26 @@ async function ensureRuntimeInstalledAndConfigured(adbPath, serial, args) {
|
|
|
1436
1572
|
Boolean(desiredRuntimeVersion) &&
|
|
1437
1573
|
(!installedVersion || installedVersion !== desiredRuntimeVersion);
|
|
1438
1574
|
|
|
1439
|
-
if (
|
|
1575
|
+
if (runtimeInfo.installed) {
|
|
1576
|
+
console.log(
|
|
1577
|
+
`Detected runtime: installed version=${installedVersion || "unknown"}`,
|
|
1578
|
+
);
|
|
1579
|
+
} else {
|
|
1580
|
+
console.log("Detected runtime: not installed");
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
let shouldInstallRuntime =
|
|
1584
|
+
forceRuntimeInstall || !runtimeInfo.installed || shouldInstallByVersion;
|
|
1585
|
+
if (!shouldInstallRuntime && runtimeInfo.installed) {
|
|
1586
|
+
const reinstallNow = await askYesNo(
|
|
1587
|
+
`Runtime already exists on ${serial}. Reinstall runtime now?`,
|
|
1588
|
+
false,
|
|
1589
|
+
args,
|
|
1590
|
+
);
|
|
1591
|
+
shouldInstallRuntime = reinstallNow;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
if (shouldInstallRuntime) {
|
|
1440
1595
|
let targetVersion = desiredRuntimeVersion;
|
|
1441
1596
|
if (!targetVersion && !overrideRuntimeUrl) {
|
|
1442
1597
|
const versions = await fetchRuntimeVersions();
|
|
@@ -1483,7 +1638,15 @@ async function ensureRuntimeInstalledAndConfigured(adbPath, serial, args) {
|
|
|
1483
1638
|
throw new Error("Runtime app did not enter running state in time.");
|
|
1484
1639
|
}
|
|
1485
1640
|
|
|
1486
|
-
setRuntimeDebugServerHost(adbPath, serial, hostIp);
|
|
1641
|
+
const debugServerResult = setRuntimeDebugServerHost(adbPath, serial, hostIp);
|
|
1642
|
+
if (!debugServerResult.success) {
|
|
1643
|
+
const details = debugServerResult.details
|
|
1644
|
+
.map((item) => `${item.action}: ${item.output || "no output"}`)
|
|
1645
|
+
.join(" | ");
|
|
1646
|
+
throw new Error(
|
|
1647
|
+
`Failed to configure runtime debug server host: ${hostIp}. Broadcast details: ${details}`,
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1487
1650
|
console.log(`Runtime debug server host configured: ${hostIp}`);
|
|
1488
1651
|
|
|
1489
1652
|
return {
|
|
@@ -1551,18 +1714,18 @@ function ensureSupportedEsappUri(uri) {
|
|
|
1551
1714
|
const normalized = String(uri || "")
|
|
1552
1715
|
.trim()
|
|
1553
1716
|
.toLowerCase();
|
|
1554
|
-
if (
|
|
1555
|
-
normalized.startsWith("esapp://") ||
|
|
1556
|
-
normalized.startsWith("quicktv://") ||
|
|
1557
|
-
normalized.startsWith("appcast://")
|
|
1558
|
-
) {
|
|
1717
|
+
if (normalized.startsWith("esapp://")) {
|
|
1559
1718
|
return;
|
|
1560
1719
|
}
|
|
1561
1720
|
throw new Error(
|
|
1562
|
-
`Unsupported URI scheme for runtime launch: ${uri}. Use esapp
|
|
1721
|
+
`Unsupported URI scheme for runtime launch: ${uri}. Use esapp:// only.`,
|
|
1563
1722
|
);
|
|
1564
1723
|
}
|
|
1565
1724
|
|
|
1725
|
+
function quotePosixShellArg(value) {
|
|
1726
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1566
1729
|
function buildEsappLaunchUri(args, projectRoot, defaults = {}) {
|
|
1567
1730
|
const positional =
|
|
1568
1731
|
args._ && typeof args._[1] === "string" ? args._[1].trim() : "";
|
|
@@ -1644,18 +1807,19 @@ function buildEsappLaunchUri(args, projectRoot, defaults = {}) {
|
|
|
1644
1807
|
|
|
1645
1808
|
function startEsappOnRuntimeByUri(adbPath, serial, runtimePackage, launchUri) {
|
|
1646
1809
|
ensureSupportedEsappUri(launchUri);
|
|
1810
|
+
// Use explicit POSIX shell quoting so `&` in URI query is not truncated by
|
|
1811
|
+
// remote Android shell parsing (e.g. `...from=cmd&pkg=...`).
|
|
1812
|
+
const shellCommand = [
|
|
1813
|
+
"am start",
|
|
1814
|
+
"-a android.intent.action.VIEW",
|
|
1815
|
+
`-p ${quotePosixShellArg(runtimePackage)}`,
|
|
1816
|
+
`-d ${quotePosixShellArg(launchUri)}`,
|
|
1817
|
+
].join(" ");
|
|
1647
1818
|
const result = runCommandCapture(adbPath, [
|
|
1648
1819
|
"-s",
|
|
1649
1820
|
serial,
|
|
1650
1821
|
"shell",
|
|
1651
|
-
|
|
1652
|
-
"start",
|
|
1653
|
-
"-a",
|
|
1654
|
-
"android.intent.action.VIEW",
|
|
1655
|
-
"-p",
|
|
1656
|
-
runtimePackage,
|
|
1657
|
-
"-d",
|
|
1658
|
-
launchUri,
|
|
1822
|
+
shellCommand,
|
|
1659
1823
|
]);
|
|
1660
1824
|
const output = `${result.stdout || ""}\n${result.stderr || ""}`.trim();
|
|
1661
1825
|
if (/(^|\s)error[:\s]/i.test(output) || /exception/i.test(output)) {
|
|
@@ -1862,6 +2026,10 @@ async function runSetupAndroidEnv(args) {
|
|
|
1862
2026
|
const projectRoot = args.project ? path.resolve(args.project) : process.cwd();
|
|
1863
2027
|
const skipRuntimeSetup = toBooleanFlag(args["skip-runtime-setup"], false);
|
|
1864
2028
|
const autoEmulator = toBooleanFlag(args["auto-emulator"], true);
|
|
2029
|
+
const requestedSerial =
|
|
2030
|
+
typeof args.device === "string" && args.device.trim()
|
|
2031
|
+
? args.device.trim()
|
|
2032
|
+
: "";
|
|
1865
2033
|
const avdName =
|
|
1866
2034
|
typeof args["avd-name"] === "string" && args["avd-name"].trim()
|
|
1867
2035
|
? args["avd-name"].trim()
|
|
@@ -1881,12 +2049,29 @@ async function runSetupAndroidEnv(args) {
|
|
|
1881
2049
|
console.log(
|
|
1882
2050
|
`ADB devices: connected=${deviceState.devices.length}, unauthorized=${deviceState.unauthorized.length}, offline=${deviceState.offline.length}`,
|
|
1883
2051
|
);
|
|
1884
|
-
let useConnectedDevice =
|
|
2052
|
+
let useConnectedDevice = false;
|
|
1885
2053
|
let shouldSetupEmulator = false;
|
|
1886
2054
|
let preferredRealDeviceSerial = null;
|
|
1887
2055
|
|
|
1888
|
-
if (
|
|
2056
|
+
if (requestedSerial) {
|
|
2057
|
+
if (!deviceState.devices.includes(requestedSerial)) {
|
|
2058
|
+
const connectedList = deviceState.devices.join(", ") || "none";
|
|
2059
|
+
throw new Error(
|
|
2060
|
+
`Requested --device ${requestedSerial} is not connected. Connected devices: ${connectedList}`,
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
useConnectedDevice = true;
|
|
2064
|
+
preferredRealDeviceSerial = requestedSerial;
|
|
2065
|
+
console.log(`Using requested device: ${requestedSerial}`);
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
if (!useConnectedDevice && deviceState.devices.length > 0) {
|
|
1889
2069
|
const connectedList = deviceState.devices.join(", ");
|
|
2070
|
+
if (!isInteractivePromptEnabled(args)) {
|
|
2071
|
+
throw new Error(
|
|
2072
|
+
`Detected connected Android device(s): ${connectedList}. Non-interactive mode requires explicit --device <serial> to avoid selecting the wrong device.`,
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
1890
2075
|
useConnectedDevice = await askYesNo(
|
|
1891
2076
|
`Detected connected Android device(s): ${connectedList}. Use this device for setup?`,
|
|
1892
2077
|
true,
|
|
@@ -2015,6 +2200,7 @@ async function runSetupAndroidEnv(args) {
|
|
|
2015
2200
|
if (!targetSerial) {
|
|
2016
2201
|
throw new Error("Unable to resolve target adb device serial.");
|
|
2017
2202
|
}
|
|
2203
|
+
await ensureTvSuitableDevice(adbPath, targetSerial, args);
|
|
2018
2204
|
|
|
2019
2205
|
let hostIp = getLocalIPv4Address();
|
|
2020
2206
|
if (!skipRuntimeSetup) {
|
|
@@ -2179,6 +2365,7 @@ async function runRunEsapp(args) {
|
|
|
2179
2365
|
"No Android device available. Use --device/--device-ip or run setup-android-env first.",
|
|
2180
2366
|
);
|
|
2181
2367
|
}
|
|
2368
|
+
await ensureTvSuitableDevice(adbPath, serial, args);
|
|
2182
2369
|
|
|
2183
2370
|
const launchUri = buildEsappLaunchUri(args, projectRoot, {
|
|
2184
2371
|
pkg: resolveProjectAppPackage(projectRoot),
|
|
@@ -2198,7 +2385,7 @@ async function runRunEsapp(args) {
|
|
|
2198
2385
|
console.log("ES app launch command sent.");
|
|
2199
2386
|
}
|
|
2200
2387
|
|
|
2201
|
-
function
|
|
2388
|
+
function parseSemverSegments(version) {
|
|
2202
2389
|
const normalized = String(version || "0.0.0")
|
|
2203
2390
|
.trim()
|
|
2204
2391
|
.split("-")[0];
|
|
@@ -2211,9 +2398,9 @@ function parseVersionSegments(version) {
|
|
|
2211
2398
|
});
|
|
2212
2399
|
}
|
|
2213
2400
|
|
|
2214
|
-
function
|
|
2215
|
-
const leftParts =
|
|
2216
|
-
const rightParts =
|
|
2401
|
+
function compareSemverStrings(left, right) {
|
|
2402
|
+
const leftParts = parseSemverSegments(left);
|
|
2403
|
+
const rightParts = parseSemverSegments(right);
|
|
2217
2404
|
const total = Math.max(leftParts.length, rightParts.length);
|
|
2218
2405
|
for (let i = 0; i < total; i += 1) {
|
|
2219
2406
|
const leftValue = leftParts[i] || 0;
|
|
@@ -2341,7 +2528,7 @@ function resolveSkillsSource() {
|
|
|
2341
2528
|
}
|
|
2342
2529
|
|
|
2343
2530
|
candidates.sort((left, right) => {
|
|
2344
|
-
const versionOrder =
|
|
2531
|
+
const versionOrder = compareSemverStrings(right.version, left.version);
|
|
2345
2532
|
if (versionOrder !== 0) return versionOrder;
|
|
2346
2533
|
return left.sourceDir.localeCompare(right.sourceDir);
|
|
2347
2534
|
});
|
|
@@ -2426,6 +2613,8 @@ Options:
|
|
|
2426
2613
|
--auto-emulator <true|false> Auto create/start emulator when no adb device
|
|
2427
2614
|
--adb-path <path> Custom adb binary path/command (or set QUICKTVUI_ADB_PATH)
|
|
2428
2615
|
--device-ip <ip[:port]> Preferred real device endpoint for adb connect
|
|
2616
|
+
--device <serial> Explicit target adb serial for setup-android-env/run-esapp
|
|
2617
|
+
--allow-non-tv-device <true|false> Allow phone/tablet device for TV run flow (default: false)
|
|
2429
2618
|
--avd-name <name> Custom AVD name for setup-android-env
|
|
2430
2619
|
--headless Start emulator with -no-window -no-audio
|
|
2431
2620
|
--runtime-version <version> Pin runtime version when direct mode is used
|
|
@@ -2437,8 +2626,7 @@ Options:
|
|
|
2437
2626
|
--port <n> Dev server port used by run-dev auto load (default: 38989)
|
|
2438
2627
|
--skip-env-check Skip setup-android-env stage in run-dev
|
|
2439
2628
|
--runtime-package <pkg> Runtime package name for run-esapp (default: com.extscreen.runtime)
|
|
2440
|
-
--
|
|
2441
|
-
--esapp-uri <uri> Raw esapp:// / quicktv:// / appcast:// URI for run-esapp
|
|
2629
|
+
--esapp-uri <uri> Raw esapp:// URI for run-esapp
|
|
2442
2630
|
--esapp-query <json> Extra query params JSON merged into action/start URI
|
|
2443
2631
|
--pkg <pkg> ES app package for run-esapp structured mode
|
|
2444
2632
|
--ver <version> ES app version for run-esapp structured mode
|
package/package.json
CHANGED
|
@@ -1,16 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { runCli } = require("../lib/index");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
4
7
|
|
|
5
8
|
function isTruthy(value) {
|
|
6
9
|
return value === true || value === "true" || value === "1";
|
|
7
10
|
}
|
|
8
11
|
|
|
12
|
+
function exists(filePath) {
|
|
13
|
+
return fs.existsSync(filePath);
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
function isGlobalInstall() {
|
|
10
17
|
if (isTruthy(process.env.npm_config_global)) return true;
|
|
11
18
|
return process.env.npm_config_location === "global";
|
|
12
19
|
}
|
|
13
20
|
|
|
21
|
+
function resolveCodexHome() {
|
|
22
|
+
const fromEnv = process.env.CODEX_HOME
|
|
23
|
+
? String(process.env.CODEX_HOME).trim()
|
|
24
|
+
: "";
|
|
25
|
+
if (fromEnv) {
|
|
26
|
+
return path.resolve(fromEnv);
|
|
27
|
+
}
|
|
28
|
+
return path.join(os.homedir(), ".codex");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function shouldSyncCodex() {
|
|
32
|
+
const fromEnv = process.env.CODEX_HOME
|
|
33
|
+
? String(process.env.CODEX_HOME).trim()
|
|
34
|
+
: "";
|
|
35
|
+
if (fromEnv) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return exists(path.join(os.homedir(), ".codex"));
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
async function main() {
|
|
15
42
|
if (isTruthy(process.env.QUICKTVUI_AI_SKIP_POSTINSTALL)) {
|
|
16
43
|
return;
|
|
@@ -19,16 +46,51 @@ async function main() {
|
|
|
19
46
|
return;
|
|
20
47
|
}
|
|
21
48
|
|
|
49
|
+
const syncCodex = shouldSyncCodex();
|
|
50
|
+
const codexSkillsDir = path.join(resolveCodexHome(), "skills", "quicktvui");
|
|
51
|
+
const failures = [];
|
|
52
|
+
|
|
22
53
|
try {
|
|
23
|
-
|
|
54
|
+
if (syncCodex) {
|
|
55
|
+
await runCli(["update", "--skip-gemini-config"]);
|
|
56
|
+
} else {
|
|
57
|
+
await runCli(["update"]);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
failures.push(`agents: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (syncCodex) {
|
|
64
|
+
try {
|
|
65
|
+
await runCli(["update", "--dir", codexSkillsDir]);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
failures.push(`codex: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (failures.length === 0) {
|
|
72
|
+
if (syncCodex) {
|
|
73
|
+
console.log(
|
|
74
|
+
`[quicktvui-ai] postinstall: synced latest skill files into ~/.agents/skills/quicktvui and ${codexSkillsDir}.`,
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
24
78
|
console.log(
|
|
25
79
|
"[quicktvui-ai] postinstall: synced latest skill files into ~/.agents/skills/quicktvui.",
|
|
26
80
|
);
|
|
27
|
-
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (syncCodex) {
|
|
28
85
|
console.warn(
|
|
29
|
-
`[quicktvui-ai] postinstall: unable to auto-sync skill files (${
|
|
86
|
+
`[quicktvui-ai] postinstall: unable to fully auto-sync skill files (${failures.join("; ")}). Run 'quicktvui-ai update' and 'quicktvui-ai update --dir ${codexSkillsDir}' manually.`,
|
|
30
87
|
);
|
|
88
|
+
return;
|
|
31
89
|
}
|
|
90
|
+
|
|
91
|
+
console.warn(
|
|
92
|
+
`[quicktvui-ai] postinstall: unable to auto-sync skill files (${failures.join("; ")}). Run 'quicktvui-ai update' manually.`,
|
|
93
|
+
);
|
|
32
94
|
}
|
|
33
95
|
|
|
34
96
|
main();
|