@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 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` and `@quicktvui/cli` are installed globally.
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 = "0.1.2";
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
- if (runtimeSetupMode === "qui") {
2006
- if (!commandCanRun("qui", ["--help"])) {
2007
- throw new Error(
2008
- "QuickTVUI CLI 'qui' is unavailable. Run: npm install -g @quicktvui/cli@latest",
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 resolveSkillsSource() {
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
- const resolved = path.join(getSkillsRoot(), "quicktvui");
2201
- if (exists(resolved)) return resolved;
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
- // Fallback for monorepo local development
2291
+ return null;
2204
2292
  }
2293
+ }
2205
2294
 
2206
- const localFallback = path.resolve(
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
- "skills",
2214
- "quicktvui",
2332
+ );
2333
+ addCandidate(
2334
+ buildSkillsSourceCandidateFromPackageRoot(monorepoPackageRoot, "monorepo"),
2215
2335
  );
2216
2336
 
2217
- if (!exists(localFallback)) {
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
- return localFallback;
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/quicktvui cli)
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: if not configured, run 'npm install -g @quicktvui/cli@latest' and 'qui setup'",
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": "0.1.3",
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": "^0.1.0"
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. Reload your AI agent to rescan local skills."
44
+ echo "Done. quicktvui-ai init also updated Gemini bridge config under ~/.gemini."
45
+ echo "Reload your AI agent to rescan local skills."
@@ -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. Reload your AI agent to rescan local skills." -ForegroundColor Green
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();