@quicktvui/ai-cli 0.1.3 → 1.1.2
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 +18 -9
- package/lib/index.js +374 -47
- package/package.json +5 -2
- package/scripts/install +2 -1
- package/scripts/install.ps1 +2 -1
- package/scripts/postinstall-sync.js +34 -0
package/README.md
CHANGED
|
@@ -8,6 +8,15 @@ QuickTVUI skill runtime CLI.
|
|
|
8
8
|
npm install -g @quicktvui/ai-cli @quicktvui/ai-skills
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Starting from `@quicktvui/ai-cli@1.1.2`, global install/upgrade auto-runs
|
|
12
|
+
`quicktvui-ai update` to sync:
|
|
13
|
+
|
|
14
|
+
- `~/.agents/skills/quicktvui`
|
|
15
|
+
- `~/.gemini/GEMINI.md` bridge block
|
|
16
|
+
- `~/.gemini/settings.json` context file names
|
|
17
|
+
|
|
18
|
+
Set `QUICKTVUI_AI_SKIP_POSTINSTALL=1` to disable auto-sync.
|
|
19
|
+
|
|
11
20
|
## Commands
|
|
12
21
|
|
|
13
22
|
```bash
|
|
@@ -41,18 +50,18 @@ quicktvui-aicreate-project quick-tv-app
|
|
|
41
50
|
- `--skip-install`: skip dependency install for `create-project`
|
|
42
51
|
- `--strict`: fail on doctor check issues
|
|
43
52
|
- `--lang <code>`: prompt language for `prompt` command (`zh` or `en`)
|
|
53
|
+
- `--gemini-dir <path>`: custom Gemini config directory (default `~/.gemini`, or `$GEMINI_CLI_HOME/.gemini`)
|
|
54
|
+
- `--skip-gemini-config`: skip updating Gemini bridge config during `init`/`update`
|
|
44
55
|
- `--node-major <n>`: target Node.js LTS major for `setup-vue-env` (default `20`)
|
|
45
56
|
- `--skip-node-install`: skip Node.js install stage in `setup-vue-env`
|
|
46
57
|
- `--force-node-install`: force Node.js install stage in `setup-vue-env`
|
|
47
58
|
- `--skip-yarn-install`: skip yarn global install in `setup-vue-env`
|
|
48
|
-
- `--skip-quicktvui-cli-install`: skip `@quicktvui/cli` global install in `setup-vue-env`
|
|
49
59
|
- `--skip-project-install`: skip project dependency install in `setup-vue-env`
|
|
50
60
|
- `--auto-emulator <true|false>`: auto create/start emulator when no adb device
|
|
51
61
|
- `--adb-path <path>`: custom adb path/command (or use env `QUICKTVUI_ADB_PATH`)
|
|
52
62
|
- `--device-ip <ip[:port]>`: preferred real device endpoint for `adb connect`
|
|
53
63
|
- `--avd-name <name>`: custom AVD name for `setup-android-env`
|
|
54
64
|
- `--headless`: start emulator with `-no-window -no-audio`
|
|
55
|
-
- `--runtime-setup-mode <direct|qui>`: runtime setup mode (default `direct`)
|
|
56
65
|
- `--runtime-version <version>`: pin runtime version in `direct` mode
|
|
57
66
|
- `--runtime-url <url>`: use custom runtime apk url in `direct` mode
|
|
58
67
|
- `--server-host <ip>`: override debug server host IP
|
|
@@ -76,6 +85,12 @@ quicktvui-ai doctor
|
|
|
76
85
|
|
|
77
86
|
Then reload your AI agent so it rescans local skills.
|
|
78
87
|
|
|
88
|
+
`init`/`update` also auto-maintains Gemini global context files:
|
|
89
|
+
|
|
90
|
+
- updates `~/.gemini/GEMINI.md` with QuickTVUI `@.../SKILL.md` bridge block
|
|
91
|
+
- ensures `~/.gemini/settings.json` contains `context.fileName` entries:
|
|
92
|
+
`GEMINI.md`, `AGENTS.md`, `SKILL.md`, `CONTEXT.md`
|
|
93
|
+
|
|
79
94
|
## Create project with network fallback
|
|
80
95
|
|
|
81
96
|
```bash
|
|
@@ -110,12 +125,6 @@ This command:
|
|
|
110
125
|
10. Installs runtime APK and configures debug server host (direct mode by default).
|
|
111
126
|
11. Launches runtime app and waits it enters running state.
|
|
112
127
|
|
|
113
|
-
To use official interactive setup flow instead of direct mode:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
quicktvui-ai setup-android-env --project ./quick-tv-app --runtime-setup-mode qui
|
|
117
|
-
```
|
|
118
|
-
|
|
119
128
|
## Configure Vue env (Node + package manager)
|
|
120
129
|
|
|
121
130
|
```bash
|
|
@@ -125,7 +134,7 @@ quicktvui-ai setup-vue-env --project ./quick-tv-app
|
|
|
125
134
|
This command:
|
|
126
135
|
|
|
127
136
|
1. Ensures Node.js LTS (macOS/Windows auto install).
|
|
128
|
-
2. Ensures `yarn`
|
|
137
|
+
2. Ensures `yarn` is installed globally.
|
|
129
138
|
3. Installs project dependencies (`yarn install` or `npm install` fallback).
|
|
130
139
|
|
|
131
140
|
## Configure All Dev Envs
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,13 @@ const https = require("https");
|
|
|
7
7
|
const net = require("net");
|
|
8
8
|
const { spawnSync, spawn } = require("child_process");
|
|
9
9
|
|
|
10
|
-
const PACKAGE_VERSION =
|
|
10
|
+
const PACKAGE_VERSION = (() => {
|
|
11
|
+
try {
|
|
12
|
+
return require("../package.json").version || "0.0.0";
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return "0.0.0";
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
11
17
|
const DEFAULT_INSTALL_DIR = path.join(
|
|
12
18
|
os.homedir(),
|
|
13
19
|
".agents",
|
|
@@ -33,9 +39,19 @@ const REQUIRED_SKILL_FILES = [
|
|
|
33
39
|
"SKILL.md",
|
|
34
40
|
path.join("references", "scaffold-checklist.md"),
|
|
35
41
|
path.join("references", "create-project-checklist.md"),
|
|
42
|
+
path.join("references", "dev-env-checklist.md"),
|
|
43
|
+
path.join("references", "esapp-protocol-cheatsheet.md"),
|
|
36
44
|
path.join("references", "lookup-checklist.md"),
|
|
37
45
|
path.join("references", "bug-report-template.md"),
|
|
38
46
|
];
|
|
47
|
+
const QUICKTVUI_GEMINI_BRIDGE_START = "<!-- QUICKTVUI_SKILL_BRIDGE_START -->";
|
|
48
|
+
const QUICKTVUI_GEMINI_BRIDGE_END = "<!-- QUICKTVUI_SKILL_BRIDGE_END -->";
|
|
49
|
+
const DEFAULT_GEMINI_CONTEXT_FILENAMES = [
|
|
50
|
+
"GEMINI.md",
|
|
51
|
+
"AGENTS.md",
|
|
52
|
+
"SKILL.md",
|
|
53
|
+
"CONTEXT.md",
|
|
54
|
+
];
|
|
39
55
|
|
|
40
56
|
function exists(filePath) {
|
|
41
57
|
return fs.existsSync(filePath);
|
|
@@ -65,6 +81,21 @@ function removeDirectoryIfExists(dirPath) {
|
|
|
65
81
|
}
|
|
66
82
|
}
|
|
67
83
|
|
|
84
|
+
function backupFile(filePath) {
|
|
85
|
+
if (!exists(filePath)) return null;
|
|
86
|
+
const backupPath = `${filePath}.bak`;
|
|
87
|
+
try {
|
|
88
|
+
fs.copyFileSync(filePath, backupPath);
|
|
89
|
+
return backupPath;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function escapeRegExp(value) {
|
|
96
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
function needsWindowsShell(command) {
|
|
69
100
|
return process.platform === "win32" && /\.(bat|cmd)$/i.test(String(command));
|
|
70
101
|
}
|
|
@@ -1760,10 +1791,6 @@ async function runSetupVueEnv(args) {
|
|
|
1760
1791
|
);
|
|
1761
1792
|
const skipNodeInstall = toBooleanFlag(args["skip-node-install"], false);
|
|
1762
1793
|
const skipYarnInstall = toBooleanFlag(args["skip-yarn-install"], false);
|
|
1763
|
-
const skipQuicktvuiCliInstall = toBooleanFlag(
|
|
1764
|
-
args["skip-quicktvui-cli-install"],
|
|
1765
|
-
false,
|
|
1766
|
-
);
|
|
1767
1794
|
const skipProjectInstall = toBooleanFlag(args["skip-project-install"], false);
|
|
1768
1795
|
|
|
1769
1796
|
if (
|
|
@@ -1795,13 +1822,6 @@ async function runSetupVueEnv(args) {
|
|
|
1795
1822
|
runCommand("npm", ["install", "-g", "yarn"], { stdio: "inherit" });
|
|
1796
1823
|
}
|
|
1797
1824
|
|
|
1798
|
-
if (!skipQuicktvuiCliInstall && !commandCanRun("qui", ["--help"])) {
|
|
1799
|
-
console.log("Installing @quicktvui/cli globally...");
|
|
1800
|
-
runCommand("npm", ["install", "-g", "@quicktvui/cli@latest"], {
|
|
1801
|
-
stdio: "inherit",
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
1825
|
if (!skipProjectInstall) {
|
|
1806
1826
|
if (!exists(path.join(projectRoot, "package.json"))) {
|
|
1807
1827
|
throw new Error(`Missing package.json in project root: ${projectRoot}`);
|
|
@@ -1842,10 +1862,6 @@ async function runSetupAndroidEnv(args) {
|
|
|
1842
1862
|
const projectRoot = args.project ? path.resolve(args.project) : process.cwd();
|
|
1843
1863
|
const skipRuntimeSetup = toBooleanFlag(args["skip-runtime-setup"], false);
|
|
1844
1864
|
const autoEmulator = toBooleanFlag(args["auto-emulator"], true);
|
|
1845
|
-
const runtimeSetupMode =
|
|
1846
|
-
typeof args["runtime-setup-mode"] === "string"
|
|
1847
|
-
? String(args["runtime-setup-mode"]).trim().toLowerCase()
|
|
1848
|
-
: "direct";
|
|
1849
1865
|
const avdName =
|
|
1850
1866
|
typeof args["avd-name"] === "string" && args["avd-name"].trim()
|
|
1851
1867
|
? args["avd-name"].trim()
|
|
@@ -2002,23 +2018,12 @@ async function runSetupAndroidEnv(args) {
|
|
|
2002
2018
|
|
|
2003
2019
|
let hostIp = getLocalIPv4Address();
|
|
2004
2020
|
if (!skipRuntimeSetup) {
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
console.log("Running 'qui setup' to configure runtime APK...");
|
|
2012
|
-
runCommand("qui", ["setup"], { cwd: projectRoot });
|
|
2013
|
-
hostIp = getLocalIPv4Address();
|
|
2014
|
-
} else {
|
|
2015
|
-
const runtimeResult = await ensureRuntimeInstalledAndConfigured(
|
|
2016
|
-
adbPath,
|
|
2017
|
-
targetSerial,
|
|
2018
|
-
args,
|
|
2019
|
-
);
|
|
2020
|
-
hostIp = runtimeResult.hostIp;
|
|
2021
|
-
}
|
|
2021
|
+
const runtimeResult = await ensureRuntimeInstalledAndConfigured(
|
|
2022
|
+
adbPath,
|
|
2023
|
+
targetSerial,
|
|
2024
|
+
args,
|
|
2025
|
+
);
|
|
2026
|
+
hostIp = runtimeResult.hostIp;
|
|
2022
2027
|
} else {
|
|
2023
2028
|
console.log("Skip runtime setup due to --skip-runtime-setup.");
|
|
2024
2029
|
}
|
|
@@ -2193,34 +2198,155 @@ async function runRunEsapp(args) {
|
|
|
2193
2198
|
console.log("ES app launch command sent.");
|
|
2194
2199
|
}
|
|
2195
2200
|
|
|
2196
|
-
function
|
|
2201
|
+
function parseVersionSegments(version) {
|
|
2202
|
+
const normalized = String(version || "0.0.0")
|
|
2203
|
+
.trim()
|
|
2204
|
+
.split("-")[0];
|
|
2205
|
+
const parts = normalized ? normalized.split(".") : [];
|
|
2206
|
+
if (parts.length === 0) return [0, 0, 0];
|
|
2207
|
+
|
|
2208
|
+
return parts.map((part) => {
|
|
2209
|
+
const parsed = Number.parseInt(part, 10);
|
|
2210
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
function compareVersionStrings(left, right) {
|
|
2215
|
+
const leftParts = parseVersionSegments(left);
|
|
2216
|
+
const rightParts = parseVersionSegments(right);
|
|
2217
|
+
const total = Math.max(leftParts.length, rightParts.length);
|
|
2218
|
+
for (let i = 0; i < total; i += 1) {
|
|
2219
|
+
const leftValue = leftParts[i] || 0;
|
|
2220
|
+
const rightValue = rightParts[i] || 0;
|
|
2221
|
+
if (leftValue > rightValue) return 1;
|
|
2222
|
+
if (leftValue < rightValue) return -1;
|
|
2223
|
+
}
|
|
2224
|
+
return 0;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
function buildSkillsSourceCandidateFromPackageRoot(packageRoot, sourceLabel) {
|
|
2228
|
+
if (!packageRoot) return null;
|
|
2229
|
+
const resolvedPackageRoot = path.resolve(packageRoot);
|
|
2230
|
+
const sourceDir = path.join(resolvedPackageRoot, "skills", "quicktvui");
|
|
2231
|
+
if (!exists(sourceDir)) return null;
|
|
2232
|
+
|
|
2233
|
+
let version = "0.0.0";
|
|
2234
|
+
const packageJsonPath = path.join(resolvedPackageRoot, "package.json");
|
|
2235
|
+
if (exists(packageJsonPath)) {
|
|
2236
|
+
try {
|
|
2237
|
+
const packageJson = readJsonFile(packageJsonPath);
|
|
2238
|
+
if (
|
|
2239
|
+
packageJson &&
|
|
2240
|
+
typeof packageJson.version === "string" &&
|
|
2241
|
+
packageJson.version.trim()
|
|
2242
|
+
) {
|
|
2243
|
+
version = packageJson.version.trim();
|
|
2244
|
+
}
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
// Ignore parse errors; keep default version for ordering.
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
return {
|
|
2251
|
+
sourceLabel,
|
|
2252
|
+
sourceDir,
|
|
2253
|
+
packageRoot: resolvedPackageRoot,
|
|
2254
|
+
version,
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
function buildSkillsSourceCandidateFromRequire() {
|
|
2197
2259
|
try {
|
|
2198
|
-
// Preferred: installed dependency
|
|
2199
2260
|
const { getSkillsRoot } = require("@quicktvui/ai-skills");
|
|
2200
|
-
|
|
2201
|
-
|
|
2261
|
+
if (typeof getSkillsRoot !== "function") return null;
|
|
2262
|
+
const resolvedSkillsRoot = getSkillsRoot();
|
|
2263
|
+
const sourceDir = path.join(resolvedSkillsRoot, "quicktvui");
|
|
2264
|
+
if (!exists(sourceDir)) return null;
|
|
2265
|
+
|
|
2266
|
+
let packageRoot = null;
|
|
2267
|
+
let version = "0.0.0";
|
|
2268
|
+
try {
|
|
2269
|
+
const packageJsonPath =
|
|
2270
|
+
require.resolve("@quicktvui/ai-skills/package.json");
|
|
2271
|
+
packageRoot = path.dirname(packageJsonPath);
|
|
2272
|
+
const packageJson = readJsonFile(packageJsonPath);
|
|
2273
|
+
if (
|
|
2274
|
+
packageJson &&
|
|
2275
|
+
typeof packageJson.version === "string" &&
|
|
2276
|
+
packageJson.version.trim()
|
|
2277
|
+
) {
|
|
2278
|
+
version = packageJson.version.trim();
|
|
2279
|
+
}
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
// Keep fallback metadata if package.json cannot be resolved.
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
return {
|
|
2285
|
+
sourceLabel: "dependency-resolved",
|
|
2286
|
+
sourceDir: path.resolve(sourceDir),
|
|
2287
|
+
packageRoot: packageRoot ? path.resolve(packageRoot) : null,
|
|
2288
|
+
version,
|
|
2289
|
+
};
|
|
2202
2290
|
} catch (error) {
|
|
2203
|
-
|
|
2291
|
+
return null;
|
|
2204
2292
|
}
|
|
2293
|
+
}
|
|
2205
2294
|
|
|
2206
|
-
|
|
2295
|
+
function resolveSkillsSource() {
|
|
2296
|
+
const candidates = [];
|
|
2297
|
+
const seenSourceDirs = new Set();
|
|
2298
|
+
|
|
2299
|
+
function addCandidate(candidate) {
|
|
2300
|
+
if (!candidate || !candidate.sourceDir) return;
|
|
2301
|
+
const key = path.resolve(candidate.sourceDir);
|
|
2302
|
+
if (seenSourceDirs.has(key)) return;
|
|
2303
|
+
seenSourceDirs.add(key);
|
|
2304
|
+
candidates.push(candidate);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// Prefer globally upgraded sibling package if present.
|
|
2308
|
+
const globalSiblingPackageRoot = path.resolve(
|
|
2309
|
+
__dirname,
|
|
2310
|
+
"..",
|
|
2311
|
+
"..",
|
|
2312
|
+
"ai-skills",
|
|
2313
|
+
);
|
|
2314
|
+
addCandidate(
|
|
2315
|
+
buildSkillsSourceCandidateFromPackageRoot(
|
|
2316
|
+
globalSiblingPackageRoot,
|
|
2317
|
+
"global-sibling",
|
|
2318
|
+
),
|
|
2319
|
+
);
|
|
2320
|
+
|
|
2321
|
+
// Dependency resolution keeps compatibility when only @quicktvui/ai-cli is installed.
|
|
2322
|
+
addCandidate(buildSkillsSourceCandidateFromRequire());
|
|
2323
|
+
|
|
2324
|
+
// Monorepo fallback for local development.
|
|
2325
|
+
const monorepoPackageRoot = path.resolve(
|
|
2207
2326
|
__dirname,
|
|
2208
2327
|
"..",
|
|
2209
2328
|
"..",
|
|
2210
2329
|
"..",
|
|
2211
2330
|
"packages",
|
|
2212
2331
|
"quicktvui-ai-skills",
|
|
2213
|
-
|
|
2214
|
-
|
|
2332
|
+
);
|
|
2333
|
+
addCandidate(
|
|
2334
|
+
buildSkillsSourceCandidateFromPackageRoot(monorepoPackageRoot, "monorepo"),
|
|
2215
2335
|
);
|
|
2216
2336
|
|
|
2217
|
-
if (
|
|
2337
|
+
if (candidates.length === 0) {
|
|
2218
2338
|
throw new Error(
|
|
2219
2339
|
"Unable to locate QuickTVUI skill assets. Install @quicktvui/ai-skills or use repository local layout.",
|
|
2220
2340
|
);
|
|
2221
2341
|
}
|
|
2222
2342
|
|
|
2223
|
-
|
|
2343
|
+
candidates.sort((left, right) => {
|
|
2344
|
+
const versionOrder = compareVersionStrings(right.version, left.version);
|
|
2345
|
+
if (versionOrder !== 0) return versionOrder;
|
|
2346
|
+
return left.sourceDir.localeCompare(right.sourceDir);
|
|
2347
|
+
});
|
|
2348
|
+
|
|
2349
|
+
return candidates[0].sourceDir;
|
|
2224
2350
|
}
|
|
2225
2351
|
|
|
2226
2352
|
function getInstallDir(args) {
|
|
@@ -2272,7 +2398,7 @@ Commands:
|
|
|
2272
2398
|
validate Strict check for required skill files (non-zero exit if missing)
|
|
2273
2399
|
update Reinstall skill assets to target directory
|
|
2274
2400
|
create-project Create a QuickTVUI project (remote clone, local fallback)
|
|
2275
|
-
setup-vue-env Setup Vue development environment (Node/yarn
|
|
2401
|
+
setup-vue-env Setup Vue development environment (Node/yarn)
|
|
2276
2402
|
setup-android-env Configure Android device/emulator + runtime environment
|
|
2277
2403
|
setup-all-env Setup both Vue and Android development environments
|
|
2278
2404
|
run-dev Run project dev command (optionally checks Android env first)
|
|
@@ -2288,20 +2414,20 @@ Options:
|
|
|
2288
2414
|
--skip-install Skip dependency install in create-project
|
|
2289
2415
|
--strict Non-zero exit in doctor when checks fail
|
|
2290
2416
|
--lang <code> Prompt language for 'prompt' command: zh | en
|
|
2417
|
+
--gemini-dir <path> Gemini config directory (default: ~/.gemini or $GEMINI_CLI_HOME/.gemini)
|
|
2418
|
+
--skip-gemini-config Skip updating Gemini bridge files during init/update
|
|
2291
2419
|
--yes Non-interactive mode; accept default prompts
|
|
2292
2420
|
--no-interactive Disable interactive prompts
|
|
2293
2421
|
--node-major <n> Target Node.js LTS major for setup-vue-env (default: 20)
|
|
2294
2422
|
--skip-node-install Skip Node.js auto-install stage in setup-vue-env
|
|
2295
2423
|
--force-node-install Force Node.js auto-install even if current version is ok
|
|
2296
2424
|
--skip-yarn-install Skip yarn global install in setup-vue-env
|
|
2297
|
-
--skip-quicktvui-cli-install Skip @quicktvui/cli global install in setup-vue-env
|
|
2298
2425
|
--skip-project-install Skip project dependency install in setup-vue-env
|
|
2299
2426
|
--auto-emulator <true|false> Auto create/start emulator when no adb device
|
|
2300
2427
|
--adb-path <path> Custom adb binary path/command (or set QUICKTVUI_ADB_PATH)
|
|
2301
2428
|
--device-ip <ip[:port]> Preferred real device endpoint for adb connect
|
|
2302
2429
|
--avd-name <name> Custom AVD name for setup-android-env
|
|
2303
2430
|
--headless Start emulator with -no-window -no-audio
|
|
2304
|
-
--runtime-setup-mode <direct|qui> Runtime setup mode in setup-android-env (default: direct)
|
|
2305
2431
|
--runtime-version <version> Pin runtime version when direct mode is used
|
|
2306
2432
|
--runtime-url <url> Use custom runtime apk url in direct mode
|
|
2307
2433
|
--server-host <ip> Override local debug server host IP
|
|
@@ -2401,6 +2527,191 @@ function installSkills(installDir) {
|
|
|
2401
2527
|
return { installDir, sourceDir };
|
|
2402
2528
|
}
|
|
2403
2529
|
|
|
2530
|
+
function resolveGeminiConfigDir(args) {
|
|
2531
|
+
if (typeof args["gemini-dir"] === "string" && args["gemini-dir"].trim()) {
|
|
2532
|
+
return path.resolve(args["gemini-dir"].trim());
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
const customGeminiHome = process.env.GEMINI_CLI_HOME
|
|
2536
|
+
? String(process.env.GEMINI_CLI_HOME).trim()
|
|
2537
|
+
: "";
|
|
2538
|
+
if (customGeminiHome) {
|
|
2539
|
+
const resolved = path.resolve(customGeminiHome);
|
|
2540
|
+
if (path.basename(resolved).toLowerCase() === ".gemini") {
|
|
2541
|
+
return resolved;
|
|
2542
|
+
}
|
|
2543
|
+
return path.join(resolved, ".gemini");
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
return path.join(os.homedir(), ".gemini");
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
function buildGeminiBridgeImportPaths(installDir) {
|
|
2550
|
+
const preferredFiles = [
|
|
2551
|
+
"SKILL.md",
|
|
2552
|
+
path.join("references", "create-project-checklist.md"),
|
|
2553
|
+
path.join("references", "dev-env-checklist.md"),
|
|
2554
|
+
path.join("references", "esapp-protocol-cheatsheet.md"),
|
|
2555
|
+
];
|
|
2556
|
+
|
|
2557
|
+
const importPaths = [];
|
|
2558
|
+
for (const relPath of preferredFiles) {
|
|
2559
|
+
const absolutePath = path.join(installDir, relPath);
|
|
2560
|
+
if (exists(absolutePath)) {
|
|
2561
|
+
importPaths.push(absolutePath);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
if (importPaths.length === 0) {
|
|
2566
|
+
importPaths.push(path.join(installDir, "SKILL.md"));
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
return importPaths;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
function updateGeminiBridgeConfig(args, installDir) {
|
|
2573
|
+
if (toBooleanFlag(args["skip-gemini-config"], false)) {
|
|
2574
|
+
return {
|
|
2575
|
+
skipped: true,
|
|
2576
|
+
reason: "flag",
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
const geminiDir = resolveGeminiConfigDir(args);
|
|
2581
|
+
ensureDir(geminiDir);
|
|
2582
|
+
|
|
2583
|
+
const geminiMdPath = path.join(geminiDir, "GEMINI.md");
|
|
2584
|
+
const settingsPath = path.join(geminiDir, "settings.json");
|
|
2585
|
+
const importPaths = buildGeminiBridgeImportPaths(installDir);
|
|
2586
|
+
|
|
2587
|
+
const bridgeBlock = [
|
|
2588
|
+
QUICKTVUI_GEMINI_BRIDGE_START,
|
|
2589
|
+
"QuickTVUI global skill bridge (managed by quicktvui-ai assistant).",
|
|
2590
|
+
...importPaths.map((importPath) => `@${importPath}`),
|
|
2591
|
+
QUICKTVUI_GEMINI_BRIDGE_END,
|
|
2592
|
+
].join("\n");
|
|
2593
|
+
|
|
2594
|
+
const beforeGeminiMd = exists(geminiMdPath)
|
|
2595
|
+
? fs.readFileSync(geminiMdPath, "utf8")
|
|
2596
|
+
: "";
|
|
2597
|
+
const bridgePattern = new RegExp(
|
|
2598
|
+
`${escapeRegExp(QUICKTVUI_GEMINI_BRIDGE_START)}[\\s\\S]*?${escapeRegExp(QUICKTVUI_GEMINI_BRIDGE_END)}`,
|
|
2599
|
+
"m",
|
|
2600
|
+
);
|
|
2601
|
+
let nextGeminiMd;
|
|
2602
|
+
if (bridgePattern.test(beforeGeminiMd)) {
|
|
2603
|
+
nextGeminiMd = beforeGeminiMd.replace(bridgePattern, bridgeBlock);
|
|
2604
|
+
} else {
|
|
2605
|
+
const trimmed = beforeGeminiMd.replace(/\s*$/, "");
|
|
2606
|
+
nextGeminiMd = trimmed
|
|
2607
|
+
? `${trimmed}\n\n${bridgeBlock}\n`
|
|
2608
|
+
: `${bridgeBlock}\n`;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
let geminiMdUpdated = false;
|
|
2612
|
+
let geminiMdBackup = null;
|
|
2613
|
+
if (nextGeminiMd !== beforeGeminiMd) {
|
|
2614
|
+
geminiMdBackup = backupFile(geminiMdPath);
|
|
2615
|
+
fs.writeFileSync(geminiMdPath, nextGeminiMd, "utf8");
|
|
2616
|
+
geminiMdUpdated = true;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
const result = {
|
|
2620
|
+
skipped: false,
|
|
2621
|
+
geminiDir,
|
|
2622
|
+
geminiMdPath,
|
|
2623
|
+
settingsPath,
|
|
2624
|
+
importPaths,
|
|
2625
|
+
geminiMdUpdated,
|
|
2626
|
+
geminiMdBackup,
|
|
2627
|
+
settingsUpdated: false,
|
|
2628
|
+
settingsBackup: null,
|
|
2629
|
+
settingsParseError: null,
|
|
2630
|
+
};
|
|
2631
|
+
|
|
2632
|
+
let settings = {};
|
|
2633
|
+
const beforeSettings = exists(settingsPath)
|
|
2634
|
+
? fs.readFileSync(settingsPath, "utf8")
|
|
2635
|
+
: "";
|
|
2636
|
+
if (beforeSettings.trim()) {
|
|
2637
|
+
try {
|
|
2638
|
+
settings = JSON.parse(beforeSettings);
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
result.settingsParseError = error.message;
|
|
2641
|
+
return result;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
|
|
2646
|
+
settings = {};
|
|
2647
|
+
}
|
|
2648
|
+
if (!settings.context || typeof settings.context !== "object") {
|
|
2649
|
+
settings.context = {};
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
const current = settings.context.fileName;
|
|
2653
|
+
const normalizedList = Array.isArray(current)
|
|
2654
|
+
? current
|
|
2655
|
+
.map((item) => String(item).trim())
|
|
2656
|
+
.filter((item) => item.length > 0)
|
|
2657
|
+
: typeof current === "string" && current.trim()
|
|
2658
|
+
? [current.trim()]
|
|
2659
|
+
: [];
|
|
2660
|
+
|
|
2661
|
+
const lowerSet = new Set(normalizedList.map((item) => item.toLowerCase()));
|
|
2662
|
+
for (const fileName of DEFAULT_GEMINI_CONTEXT_FILENAMES) {
|
|
2663
|
+
if (!lowerSet.has(fileName.toLowerCase())) {
|
|
2664
|
+
normalizedList.push(fileName);
|
|
2665
|
+
lowerSet.add(fileName.toLowerCase());
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
settings.context.fileName = normalizedList;
|
|
2669
|
+
|
|
2670
|
+
const nextSettings = `${JSON.stringify(settings, null, 2)}\n`;
|
|
2671
|
+
if (nextSettings !== beforeSettings) {
|
|
2672
|
+
result.settingsBackup = backupFile(settingsPath);
|
|
2673
|
+
fs.writeFileSync(settingsPath, nextSettings, "utf8");
|
|
2674
|
+
result.settingsUpdated = true;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
return result;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
function printGeminiBridgeResult(state) {
|
|
2681
|
+
if (!state) return;
|
|
2682
|
+
if (state.error) {
|
|
2683
|
+
console.log(`- Gemini config: failed (${state.error})`);
|
|
2684
|
+
console.log(
|
|
2685
|
+
` Hint: rerun with --skip-gemini-config or fix filesystem permission.`,
|
|
2686
|
+
);
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
if (state.skipped) {
|
|
2690
|
+
console.log(`- Gemini config: skipped (--skip-gemini-config)`);
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
console.log(`- Gemini config dir: ${state.geminiDir}`);
|
|
2695
|
+
console.log(
|
|
2696
|
+
`- Gemini bridge block: ${state.geminiMdUpdated ? "updated" : "already up to date"}`,
|
|
2697
|
+
);
|
|
2698
|
+
console.log(
|
|
2699
|
+
`- Gemini context.fileName: ${
|
|
2700
|
+
state.settingsParseError
|
|
2701
|
+
? "skipped (settings.json parse failed)"
|
|
2702
|
+
: state.settingsUpdated
|
|
2703
|
+
? "updated"
|
|
2704
|
+
: "already up to date"
|
|
2705
|
+
}`,
|
|
2706
|
+
);
|
|
2707
|
+
if (state.settingsParseError) {
|
|
2708
|
+
console.log(` Warning: ${state.settingsParseError}`);
|
|
2709
|
+
console.log(
|
|
2710
|
+
` Hint: fix ${state.settingsPath} JSON and rerun 'quicktvui-ai init'.`,
|
|
2711
|
+
);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2404
2715
|
function printDoctorReport(skillState, projectState, projectRoot) {
|
|
2405
2716
|
console.log(`Skill dir: ${skillState.installDir}`);
|
|
2406
2717
|
console.log(`- Directory exists: ${skillState.exists ? "yes" : "no"}`);
|
|
@@ -2428,9 +2739,16 @@ function printDoctorReport(skillState, projectState, projectRoot) {
|
|
|
2428
2739
|
async function runInit(args) {
|
|
2429
2740
|
const installDir = getInstallDir(args);
|
|
2430
2741
|
const result = installSkills(installDir);
|
|
2742
|
+
let geminiState;
|
|
2743
|
+
try {
|
|
2744
|
+
geminiState = updateGeminiBridgeConfig(args, result.installDir);
|
|
2745
|
+
} catch (error) {
|
|
2746
|
+
geminiState = { error: error.message };
|
|
2747
|
+
}
|
|
2431
2748
|
console.log(`Installed QuickTVUI skill assets.`);
|
|
2432
2749
|
console.log(`- source: ${result.sourceDir}`);
|
|
2433
2750
|
console.log(`- target: ${result.installDir}`);
|
|
2751
|
+
printGeminiBridgeResult(geminiState);
|
|
2434
2752
|
console.log(`Next: restart/reload your AI agent so it can rescan skills.`);
|
|
2435
2753
|
}
|
|
2436
2754
|
|
|
@@ -2484,9 +2802,16 @@ async function runValidate(args) {
|
|
|
2484
2802
|
async function runUpdate(args) {
|
|
2485
2803
|
const installDir = getInstallDir(args);
|
|
2486
2804
|
const result = installSkills(installDir);
|
|
2805
|
+
let geminiState;
|
|
2806
|
+
try {
|
|
2807
|
+
geminiState = updateGeminiBridgeConfig(args, result.installDir);
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
geminiState = { error: error.message };
|
|
2810
|
+
}
|
|
2487
2811
|
console.log(`Updated QuickTVUI skill assets.`);
|
|
2488
2812
|
console.log(`- source: ${result.sourceDir}`);
|
|
2489
2813
|
console.log(`- target: ${result.installDir}`);
|
|
2814
|
+
printGeminiBridgeResult(geminiState);
|
|
2490
2815
|
}
|
|
2491
2816
|
|
|
2492
2817
|
function cloneTemplateProject(targetDir) {
|
|
@@ -2547,7 +2872,7 @@ async function runCreateProject(args) {
|
|
|
2547
2872
|
"- ensured: package.json name/version updated and @quicktvui/ai added to devDependencies",
|
|
2548
2873
|
);
|
|
2549
2874
|
console.log(
|
|
2550
|
-
"- next:
|
|
2875
|
+
"- next: run 'quicktvui-ai setup-all-env --project <project-path>' to prepare development and runtime environments",
|
|
2551
2876
|
);
|
|
2552
2877
|
}
|
|
2553
2878
|
|
|
@@ -2571,6 +2896,7 @@ npm install @quicktvui/ai --save-dev
|
|
|
2571
2896
|
\`\`\`
|
|
2572
2897
|
|
|
2573
2898
|
安装完成后请提醒用户重启或 reload AI Agent,以便重新扫描 ~/.agents/skills/quicktvui。
|
|
2899
|
+
并提示:quicktvui-ai init 会自动更新 ~/.gemini/GEMINI.md 与 ~/.gemini/settings.json 的 QuickTVUI bridge 配置。
|
|
2574
2900
|
|
|
2575
2901
|
创建项目时优先使用:
|
|
2576
2902
|
\`\`\`bash
|
|
@@ -2606,6 +2932,7 @@ npm install @quicktvui/ai --save-dev
|
|
|
2606
2932
|
\`\`\`
|
|
2607
2933
|
|
|
2608
2934
|
After installation, ask the user to restart or reload the AI Agent so it rescans ~/.agents/skills/quicktvui.
|
|
2935
|
+
Also note that quicktvui-ai init auto-updates QuickTVUI bridge config in ~/.gemini/GEMINI.md and ~/.gemini/settings.json.
|
|
2609
2936
|
|
|
2610
2937
|
When creating a project, prefer:
|
|
2611
2938
|
\`\`\`bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quicktvui/ai-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "CLI for installing and validating QuickTVUI AI skills",
|
|
5
5
|
"bin": {
|
|
6
6
|
"quicktvui-ai": "bin/quicktvui-ai.js",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"quicktvui-aicreate-project": "bin/quicktvui-aicreate-project.js"
|
|
9
9
|
},
|
|
10
10
|
"main": "lib/index.js",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"postinstall": "node scripts/postinstall-sync.js"
|
|
13
|
+
},
|
|
11
14
|
"files": [
|
|
12
15
|
"bin",
|
|
13
16
|
"lib",
|
|
@@ -25,6 +28,6 @@
|
|
|
25
28
|
"author": "QuickTVUI",
|
|
26
29
|
"license": "MIT",
|
|
27
30
|
"dependencies": {
|
|
28
|
-
"@quicktvui/ai-skills": "^
|
|
31
|
+
"@quicktvui/ai-skills": "^1.1.1"
|
|
29
32
|
}
|
|
30
33
|
}
|
package/scripts/install
CHANGED
|
@@ -41,4 +41,5 @@ else
|
|
|
41
41
|
quicktvui-ai doctor --strict --dir "$INSTALL_DIR"
|
|
42
42
|
fi
|
|
43
43
|
|
|
44
|
-
echo "Done.
|
|
44
|
+
echo "Done. quicktvui-ai init also updated Gemini bridge config under ~/.gemini."
|
|
45
|
+
echo "Reload your AI agent to rescan local skills."
|
package/scripts/install.ps1
CHANGED
|
@@ -25,4 +25,5 @@ if ($Project -and $Project.Length -gt 0) {
|
|
|
25
25
|
quicktvui-ai doctor --strict --dir $Dir
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
Write-Host "Done.
|
|
28
|
+
Write-Host "Done. quicktvui-ai init also updated Gemini bridge config under ~/.gemini." -ForegroundColor Green
|
|
29
|
+
Write-Host "Reload your AI agent to rescan local skills." -ForegroundColor Green
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { runCli } = require("../lib/index");
|
|
4
|
+
|
|
5
|
+
function isTruthy(value) {
|
|
6
|
+
return value === true || value === "true" || value === "1";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isGlobalInstall() {
|
|
10
|
+
if (isTruthy(process.env.npm_config_global)) return true;
|
|
11
|
+
return process.env.npm_config_location === "global";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
if (isTruthy(process.env.QUICKTVUI_AI_SKIP_POSTINSTALL)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!isGlobalInstall()) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await runCli(["update"]);
|
|
24
|
+
console.log(
|
|
25
|
+
"[quicktvui-ai] postinstall: synced latest skill files into ~/.agents/skills/quicktvui.",
|
|
26
|
+
);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.warn(
|
|
29
|
+
`[quicktvui-ai] postinstall: unable to auto-sync skill files (${error.message}). Run 'quicktvui-ai update' manually.`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main();
|