@thinkingai/ae-cli 6.1.8 → 6.1.9

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.
Files changed (36) hide show
  1. package/README.md +219 -150
  2. package/README.zh.md +227 -159
  3. package/dist/{auth-B2BRSYMS.js → auth-56Z45UVR.js} +1 -1
  4. package/dist/{capability-6KPXYNO7.js → capability-2H6PAOA3.js} +1 -1
  5. package/dist/{capability-ROKYESAP.js → capability-YPOQX6PL.js} +1 -1
  6. package/dist/{chunk-JTKMDN4A.js → chunk-KTYR3U6D.js} +471 -31
  7. package/dist/{chunk-EBPXT3TN.js → chunk-LHVM35J4.js} +1 -1
  8. package/dist/{chunk-RRFK2W7I.js → chunk-PVBYJWC2.js} +1 -1
  9. package/dist/{config-DDYJPJX3.js → config-BSSALXEN.js} +1 -1
  10. package/dist/index.js +45 -15
  11. package/dist/{metadata-2ZFRMVOL.js → metadata-AN3YFZEV.js} +2 -2
  12. package/dist/{metadata-ABQCKU5P.js → metadata-JIQ77HFY.js} +2 -2
  13. package/dist/{raw-6HXFFHLV.js → raw-XJCAT3HX.js} +1 -1
  14. package/dist/{sync-BF3IWYFC.js → sync-QFP4XFN3.js} +1 -1
  15. package/dist/{te-agent-3GJ5H2XF.js → te-agent-JHUG6DVV.js} +27 -0
  16. package/dist/{te-analysis-PCGESPLW.js → te-analysis-IWQZO423.js} +2 -2
  17. package/dist/{te-analysis-DJ6KELTU.js → te-analysis-X222IRYR.js} +2 -2
  18. package/dist/{te-dataops-ROHUJOX5.js → te-dataops-PZQ5NQLY.js} +1 -1
  19. package/dist/{te-dataops-LXL5YULV.js → te-dataops-XTWVTJCA.js} +1 -1
  20. package/dist/{te-engage-2YDCA552.js → te-engage-E7F4HTXU.js} +2 -2
  21. package/dist/{te-engage-VW6NJZ5V.js → te-engage-NLZUPSBK.js} +2 -2
  22. package/dist/{te-experiment-OYK54B72.js → te-experiment-D32TB6ZB.js} +2 -2
  23. package/dist/{te-experiment-7JGJNDXO.js → te-experiment-N63WF7XA.js} +2 -2
  24. package/dist/{te-team-RRZI4WRI.js → te-team-BQ3SKSZV.js} +1 -1
  25. package/dist/{update-ER7VFU55.js → update-DKG6UXEM.js} +44 -22
  26. package/package.json +4 -3
  27. package/skills/ae-agent/SKILL.md +2 -2
  28. package/skills/ae-agent/references/create-automation.md +11 -0
  29. package/skills/ae-agent/references/update-automation.md +11 -3
  30. package/skills/ae-generate-tracking-code/SKILL.md +5 -5
  31. package/skills/ae-generate-tracking-code/references/autotrack-enum.md +4 -4
  32. package/skills/ae-generate-tracking-code/references/client-sdk-insert.md +1 -1
  33. package/skills/ae-generate-tracking-code/references/debug-script.md +2 -2
  34. package/skills/ae-generate-tracking-code/references/logbus-config.md +1 -1
  35. package/skills/ae-generate-tracking-code/references/restful-call.md +1 -1
  36. package/skills/ae-generate-tracking-code/references/sdk-index.md +36 -36
@@ -14,6 +14,11 @@ import {
14
14
  logger
15
15
  } from "./chunk-LYVNONC4.js";
16
16
 
17
+ // src/core/version-sync.ts
18
+ import { spawnSync } from "child_process";
19
+ import fs from "fs";
20
+ import path from "path";
21
+
17
22
  // src/core/update-check.ts
18
23
  function shouldSkipUpdateCheck(argv = process.argv) {
19
24
  if (process.env.AE_CLI_NO_UPDATE_CHECK === "1") return true;
@@ -233,20 +238,352 @@ function formatCompatNotice(verdict) {
233
238
  ].join("\n");
234
239
  }
235
240
 
241
+ // src/core/version-sync.ts
242
+ var COMMAND_TIMEOUT_MS = 12e4;
243
+ var AUTO_ATTEMPT_INTERVAL_MS = 60 * 60 * 1e3;
244
+ var MAX_AUTO_ATTEMPTS_PER_DAY = 3;
245
+ var LOCK_STALE_MS = 10 * 60 * 1e3;
246
+ var SYNC_STATE_FILE = "version-sync.json";
247
+ var SYNC_LOCK_FILE = "version-sync.lock";
248
+ function syncStatePath() {
249
+ return path.join(getConfigDir(), SYNC_STATE_FILE);
250
+ }
251
+ function syncLockPath() {
252
+ return path.join(getConfigDir(), SYNC_LOCK_FILE);
253
+ }
254
+ function readSyncStore() {
255
+ try {
256
+ const raw = JSON.parse(fs.readFileSync(syncStatePath(), "utf8"));
257
+ if (raw && typeof raw === "object" && raw.hosts && typeof raw.hosts === "object") {
258
+ return raw;
259
+ }
260
+ } catch {
261
+ }
262
+ return { hosts: {} };
263
+ }
264
+ function writeSyncStore(store) {
265
+ try {
266
+ fs.mkdirSync(getConfigDir(), { recursive: true });
267
+ fs.writeFileSync(syncStatePath(), JSON.stringify(store, null, 2));
268
+ } catch {
269
+ }
270
+ }
271
+ function localDay(now) {
272
+ const year = now.getFullYear();
273
+ const month = String(now.getMonth() + 1).padStart(2, "0");
274
+ const day = String(now.getDate()).padStart(2, "0");
275
+ return `${year}-${month}-${day}`;
276
+ }
277
+ function freshAttemptEntry(target, now) {
278
+ return {
279
+ target,
280
+ attemptDay: localDay(now),
281
+ attemptsToday: 0
282
+ };
283
+ }
284
+ function evaluateAutoAttempt(existing, target, now = /* @__PURE__ */ new Date()) {
285
+ const today = localDay(now);
286
+ const entry = !existing || existing.target !== target || existing.attemptDay !== today ? freshAttemptEntry(target, now) : { ...existing };
287
+ if (entry.attemptsToday >= MAX_AUTO_ATTEMPTS_PER_DAY) {
288
+ return { allowed: false, reason: "daily_limit", entry };
289
+ }
290
+ if (entry.lastAttemptAt) {
291
+ const elapsed = now.getTime() - new Date(entry.lastAttemptAt).getTime();
292
+ if (Number.isFinite(elapsed) && elapsed < AUTO_ATTEMPT_INTERVAL_MS) {
293
+ return { allowed: false, reason: "hourly_limit", entry };
294
+ }
295
+ }
296
+ return { allowed: true, entry };
297
+ }
298
+ function reserveAutoAttempt(host, target, now = /* @__PURE__ */ new Date()) {
299
+ const store = readSyncStore();
300
+ const key = normalizeUrl(host);
301
+ const decision = evaluateAutoAttempt(store.hosts[key], target, now);
302
+ if (!decision.allowed) return decision;
303
+ decision.entry.attemptsToday += 1;
304
+ decision.entry.lastAttemptAt = now.toISOString();
305
+ store.hosts[key] = decision.entry;
306
+ writeSyncStore(store);
307
+ return decision;
308
+ }
309
+ function getPendingSkillsTarget(host) {
310
+ const entry = readSyncStore().hosts[normalizeUrl(host)];
311
+ return entry?.pendingSkills ? entry.target : void 0;
312
+ }
313
+ function recordVersionSyncResult(host, target, result) {
314
+ const store = readSyncStore();
315
+ const key = normalizeUrl(host);
316
+ const existing = store.hosts[key] ?? freshAttemptEntry(target, /* @__PURE__ */ new Date());
317
+ const entry = existing.target === target ? existing : freshAttemptEntry(target, /* @__PURE__ */ new Date());
318
+ if (result.ok) {
319
+ entry.pendingSkills = false;
320
+ delete entry.lastFailureStage;
321
+ } else {
322
+ entry.pendingSkills = result.skillsPending;
323
+ entry.lastFailureStage = result.stage;
324
+ }
325
+ store.hosts[key] = entry;
326
+ writeSyncStore(store);
327
+ }
328
+ function isAutoSyncTargetEligible(version) {
329
+ const normalized = normalizeCliVersion(version);
330
+ const parts = normalized.split("-")[0].split(".").map((part) => Number.parseInt(part, 10));
331
+ if (parts.length < 3 || parts.some((part) => !Number.isFinite(part))) return false;
332
+ const [major, minor, patch] = parts;
333
+ if (major > 6) return true;
334
+ if (major !== 6) return false;
335
+ if (minor > 1) return true;
336
+ if (minor === 1) return patch >= 5;
337
+ if (minor === 0) return patch >= 33;
338
+ return false;
339
+ }
340
+ function isPublicAeCliPackage(packageName) {
341
+ return packageName === OPEN_SOURCE_AE_CLI_PACKAGE;
342
+ }
343
+ function resolveNodeTool(tool, execPath = process.execPath, platform = process.platform) {
344
+ const fileName = platform === "win32" ? `${tool}.cmd` : tool;
345
+ const sibling = path.join(path.dirname(execPath), fileName);
346
+ return fs.existsSync(sibling) ? sibling : fileName;
347
+ }
348
+ function buildVersionInstallPlan(targetRaw, globalRoot = "<npm-root>") {
349
+ const target = normalizeCliVersion(targetRaw);
350
+ const localSkills = path.join(globalRoot, "@thinkingai", "ae-cli", "skills");
351
+ return {
352
+ target,
353
+ commands: [
354
+ `npm install -g ${OPEN_SOURCE_AE_CLI_PACKAGE}@${target}`,
355
+ `npx -y skills add ${localSkills} -g -y`,
356
+ `npx -y skills add ${AE_CLI_SKILLS_REPO}#v${target} -g -y`
357
+ ],
358
+ skillsSources: ["installed-package", "github-fallback"]
359
+ };
360
+ }
361
+ function defaultRunner(command, args, timeoutMs) {
362
+ const result = spawnSync(command, args, {
363
+ encoding: "utf8",
364
+ maxBuffer: 2 * 1024 * 1024,
365
+ stdio: ["ignore", "pipe", "pipe"],
366
+ timeout: timeoutMs,
367
+ windowsHide: true
368
+ });
369
+ return {
370
+ status: result.status,
371
+ stdout: result.stdout || "",
372
+ stderr: result.stderr || "",
373
+ error: result.error,
374
+ signal: result.signal
375
+ };
376
+ }
377
+ function resultMessage(result) {
378
+ if (result.error?.message) return result.error.message;
379
+ const detail = (result.stderr || result.stdout).trim().split(/\r?\n/).filter(Boolean).slice(-3).join(" ");
380
+ if (detail) return detail;
381
+ if (result.signal) return `Command terminated by ${result.signal}`;
382
+ return `Command exited with status ${result.status ?? "unknown"}`;
383
+ }
384
+ function classifyFailure(result) {
385
+ const text = `${result.error?.message || ""}
386
+ ${result.stderr}
387
+ ${result.stdout}`;
388
+ if (result.error && result.error.code === "ETIMEDOUT") return "timeout";
389
+ if (/timed?\s*out|ETIMEDOUT/i.test(text)) return "timeout";
390
+ if (/EACCES|EPERM|permission denied|access is denied/i.test(text)) return "permission";
391
+ if (/EAI_AGAIN|ENOTFOUND|ECONNRESET|ECONNREFUSED|network|unable to access|could not resolve|certificate|SSL/i.test(text)) {
392
+ return "network";
393
+ }
394
+ if (/E404|404 Not Found|no matching version|couldn't find remote ref|not found/i.test(text)) {
395
+ return "package_missing";
396
+ }
397
+ return "unknown";
398
+ }
399
+ function friendlyVersionSyncFailure(result) {
400
+ if (result.cause === "busy") {
401
+ return "Another ae-cli process is synchronizing the global installation.";
402
+ }
403
+ if (result.cause === "permission") {
404
+ return "The global npm installation directory is not writable.";
405
+ }
406
+ if (result.cause === "timeout") {
407
+ return "The npm or Skills download timed out.";
408
+ }
409
+ if (result.cause === "network") {
410
+ return "The npm registry or Skills source could not be reached.";
411
+ }
412
+ if (result.cause === "package_missing") {
413
+ return "The required CLI package or Skills tag is not available.";
414
+ }
415
+ if (result.cause === "validation") {
416
+ return "The installed package could not be validated.";
417
+ }
418
+ return `Version synchronization failed at ${result.stage}.`;
419
+ }
420
+ function commandSucceeded(result) {
421
+ return !result.error && result.status === 0;
422
+ }
423
+ function acquireInstallLock(now) {
424
+ fs.mkdirSync(getConfigDir(), { recursive: true });
425
+ const file = syncLockPath();
426
+ try {
427
+ return fs.openSync(file, "wx");
428
+ } catch {
429
+ try {
430
+ const age = now.getTime() - fs.statSync(file).mtimeMs;
431
+ if (age >= LOCK_STALE_MS) {
432
+ fs.unlinkSync(file);
433
+ return fs.openSync(file, "wx");
434
+ }
435
+ } catch {
436
+ try {
437
+ return fs.openSync(file, "wx");
438
+ } catch {
439
+ return null;
440
+ }
441
+ }
442
+ return null;
443
+ }
444
+ }
445
+ function releaseInstallLock(fd) {
446
+ try {
447
+ fs.closeSync(fd);
448
+ } catch {
449
+ }
450
+ try {
451
+ fs.unlinkSync(syncLockPath());
452
+ } catch {
453
+ }
454
+ }
455
+ function validateInstalledPackage(npm, target, runner) {
456
+ const rootResult = runner(npm, ["root", "-g"], COMMAND_TIMEOUT_MS);
457
+ if (!commandSucceeded(rootResult)) {
458
+ return { ok: false, message: resultMessage(rootResult) };
459
+ }
460
+ const globalRoot = rootResult.stdout.trim();
461
+ const packageRoot = path.join(globalRoot, "@thinkingai", "ae-cli");
462
+ const packageJson = path.join(packageRoot, "package.json");
463
+ const skillsPath = path.join(packageRoot, "skills");
464
+ try {
465
+ const installed = JSON.parse(fs.readFileSync(packageJson, "utf8"));
466
+ if (normalizeCliVersion(installed.version || "") !== target) {
467
+ return {
468
+ ok: false,
469
+ message: `Installed package version ${installed.version || "unknown"} does not match ${target}`
470
+ };
471
+ }
472
+ if (!fs.statSync(skillsPath).isDirectory()) {
473
+ return { ok: false, message: `Installed package has no skills directory: ${skillsPath}` };
474
+ }
475
+ return { ok: true, skillsPath };
476
+ } catch (error) {
477
+ return { ok: false, message: error?.message || String(error) };
478
+ }
479
+ }
480
+ function installVersion(targetRaw, options = {}) {
481
+ const target = normalizeCliVersion(targetRaw);
482
+ const runner = options.runner ?? defaultRunner;
483
+ const progress = options.progress ?? (() => {
484
+ });
485
+ const npm = resolveNodeTool("npm");
486
+ const npx = resolveNodeTool("npx");
487
+ const lock = acquireInstallLock(options.now ?? /* @__PURE__ */ new Date());
488
+ if (lock === null) {
489
+ return {
490
+ ok: false,
491
+ stage: "lock",
492
+ cliInstalled: false,
493
+ skillsPending: Boolean(options.skipCliInstall),
494
+ cause: "busy",
495
+ message: "Another ae-cli process is already synchronizing the global installation."
496
+ };
497
+ }
498
+ let cliInstalled = Boolean(options.skipCliInstall);
499
+ try {
500
+ if (!options.skipCliInstall) {
501
+ progress(`[ae-cli] [1/2] Installing ae-cli ${target}...`);
502
+ const npmResult = runner(
503
+ npm,
504
+ ["install", "-g", `${OPEN_SOURCE_AE_CLI_PACKAGE}@${target}`],
505
+ COMMAND_TIMEOUT_MS
506
+ );
507
+ if (!commandSucceeded(npmResult)) {
508
+ return {
509
+ ok: false,
510
+ stage: "npm_install",
511
+ cliInstalled: false,
512
+ skillsPending: false,
513
+ cause: classifyFailure(npmResult),
514
+ message: resultMessage(npmResult)
515
+ };
516
+ }
517
+ cliInstalled = true;
518
+ }
519
+ const installed = validateInstalledPackage(npm, target, runner);
520
+ if (!installed.ok) {
521
+ return {
522
+ ok: false,
523
+ stage: "package_validation",
524
+ cliInstalled,
525
+ skillsPending: cliInstalled,
526
+ cause: "validation",
527
+ message: installed.message
528
+ };
529
+ }
530
+ progress("[ae-cli] [2/2] Synchronizing Skills from the installed npm package...");
531
+ const localResult = runner(
532
+ npx,
533
+ ["-y", "skills", "add", installed.skillsPath, "-g", "-y"],
534
+ COMMAND_TIMEOUT_MS
535
+ );
536
+ if (commandSucceeded(localResult)) {
537
+ return { ok: true, cliInstalled, skillsSource: "local" };
538
+ }
539
+ progress("[ae-cli] Local Skills sync did not complete; trying the GitHub fallback...");
540
+ const githubResult = runner(
541
+ npx,
542
+ ["-y", "skills", "add", `${AE_CLI_SKILLS_REPO}#v${target}`, "-g", "-y"],
543
+ COMMAND_TIMEOUT_MS
544
+ );
545
+ if (commandSucceeded(githubResult)) {
546
+ return { ok: true, cliInstalled, skillsSource: "github" };
547
+ }
548
+ return {
549
+ ok: false,
550
+ stage: "skills_github",
551
+ cliInstalled,
552
+ skillsPending: cliInstalled,
553
+ cause: classifyFailure(githubResult),
554
+ message: resultMessage(githubResult) || resultMessage(localResult)
555
+ };
556
+ } finally {
557
+ releaseInstallLock(lock);
558
+ }
559
+ }
560
+ function autoSyncDirection(current, expected) {
561
+ if (versionLine(current) !== versionLine(expected)) return "switch";
562
+ const currentParts = normalizeCliVersion(current).split("-")[0].split(".").map(Number);
563
+ const expectedParts = normalizeCliVersion(expected).split("-")[0].split(".").map(Number);
564
+ for (let i = 0; i < Math.max(currentParts.length, expectedParts.length, 3); i += 1) {
565
+ const local = currentParts[i] ?? 0;
566
+ const target = expectedParts[i] ?? 0;
567
+ if (local < target) return "upgrade";
568
+ if (local > target) return "downgrade";
569
+ }
570
+ return "switch";
571
+ }
572
+
236
573
  // src/core/compat-check.ts
237
- import fs from "fs";
238
- import path from "path";
574
+ import fs2 from "fs";
575
+ import path2 from "path";
239
576
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
240
577
  var FETCH_TIMEOUT_MS = 3e3;
241
578
  var CLI_CONFIG_PATH = "/v1/ta/cli/config";
242
579
  function cacheFilePath() {
243
- return path.join(getConfigDir(), "compat-check.json");
580
+ return path2.join(getConfigDir(), "compat-check.json");
244
581
  }
245
582
  function readStore() {
246
583
  try {
247
584
  const file = cacheFilePath();
248
- if (!fs.existsSync(file)) return { hosts: {} };
249
- const raw = JSON.parse(fs.readFileSync(file, "utf8"));
585
+ if (!fs2.existsSync(file)) return { hosts: {} };
586
+ const raw = JSON.parse(fs2.readFileSync(file, "utf8"));
250
587
  if (!raw || typeof raw !== "object" || !raw.hosts) return { hosts: {} };
251
588
  return raw;
252
589
  } catch {
@@ -256,8 +593,8 @@ function readStore() {
256
593
  function writeStore(store) {
257
594
  try {
258
595
  const dir = getConfigDir();
259
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
260
- fs.writeFileSync(cacheFilePath(), JSON.stringify(store, null, 2));
596
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
597
+ fs2.writeFileSync(cacheFilePath(), JSON.stringify(store, null, 2));
261
598
  } catch {
262
599
  }
263
600
  }
@@ -279,6 +616,25 @@ function shouldSkipCompatCheck(argv = process.argv) {
279
616
  if (isAeSandboxRuntime()) return true;
280
617
  return shouldSkipUpdateCheck(argv);
281
618
  }
619
+ function rootCommand(argv) {
620
+ const args = argv.slice(2);
621
+ for (let i = 0; i < args.length; i += 1) {
622
+ const token = args[i];
623
+ if (token === "--") return void 0;
624
+ if (token.startsWith("-")) {
625
+ if (!token.includes("=") && ["--host", "--mcp-url", "--format", "--jq"].includes(token)) {
626
+ i += 1;
627
+ }
628
+ continue;
629
+ }
630
+ return token;
631
+ }
632
+ return void 0;
633
+ }
634
+ function shouldSkipAutoSync(argv = process.argv) {
635
+ const command = rootCommand(argv);
636
+ return command === "update" || command === "auth" || command === "config";
637
+ }
282
638
  async function fetchCliConfig(host, cliToken) {
283
639
  const url = new URL(CLI_CONFIG_PATH, host.endsWith("/") ? host : `${host}/`);
284
640
  url.searchParams.set("cli-token", cliToken);
@@ -352,9 +708,22 @@ function formatCapabilityMissCompatHint(host) {
352
708
  }
353
709
  return "This capability may be missing on the current private cluster. Update cluster components to the latest version, or pin this machine CLI + skills to the environment version (see ae-cli host compat notices after auth login).";
354
710
  }
355
- async function refreshAndMaybeNotify(host, pkg) {
711
+ function setUpdateNotice(pkg, expected, cluster, verdict, state, stage, message) {
712
+ pendingHostCompatNotice = void 0;
713
+ pendingUpdateNotice = {
714
+ command: "ae-cli update",
715
+ current: pkg.version,
716
+ expected,
717
+ cluster,
718
+ reason: verdict.kind === "ok" && state === "skills_pending" ? "skills_pending" : verdict.kind,
719
+ state,
720
+ ...stage ? { stage } : {},
721
+ message: message ?? `Current ae-cli is ${pkg.version}; this host requires ${expected}. Run: ae-cli update`
722
+ };
723
+ }
724
+ async function refreshAndMaybeNotify(host, pkg, allowAutoSync) {
356
725
  const token = peekCliToken(host);
357
- if (!token) return;
726
+ if (!token) return { status: "continue" };
358
727
  const store = readStore();
359
728
  const key = hostKey(host);
360
729
  const existing = store.hosts[key];
@@ -364,7 +733,7 @@ async function refreshAndMaybeNotify(host, pkg) {
364
733
  if (isStale(existing)) {
365
734
  const remote = await fetchCliConfig(host, token);
366
735
  if (!remote?.aeCliVersion) {
367
- return;
736
+ return { status: "continue" };
368
737
  }
369
738
  expected = remote.aeCliVersion;
370
739
  cluster = remote.clusterVersion ?? "";
@@ -379,15 +748,15 @@ async function refreshAndMaybeNotify(host, pkg) {
379
748
  };
380
749
  writeStore(store);
381
750
  }
382
- if (!expected) return;
383
- const verdict = evaluateCompat(pkg.version, expected, cluster);
384
- const notice = formatCompatNotice(verdict);
385
- if (!notice) {
751
+ if (!expected) return { status: "continue" };
752
+ let verdict = evaluateCompat(pkg.version, expected, cluster);
753
+ let pendingSkills = getPendingSkillsTarget(host) === expected;
754
+ if (verdict.kind === "ok" && !pendingSkills) {
386
755
  pendingHostCompatNotice = void 0;
387
756
  pendingUpdateNotice = void 0;
388
- return;
757
+ return { status: "continue" };
389
758
  }
390
- const entry = store.hosts[key] ?? {
759
+ let entry = store.hosts[key] ?? {
391
760
  host: key,
392
761
  localVersion: pkg.version,
393
762
  expectedVersion: expected,
@@ -397,35 +766,104 @@ async function refreshAndMaybeNotify(host, pkg) {
397
766
  entry.localVersion = pkg.version;
398
767
  entry.expectedVersion = expected;
399
768
  entry.clusterVersion = cluster || entry.clusterVersion;
769
+ const canAutoSync = allowAutoSync && isPublicAeCliPackage(pkg.name) && isAutoSyncTargetEligible(expected);
770
+ if (canAutoSync) {
771
+ const confirmed = await fetchCliConfig(host, token);
772
+ if (confirmed?.aeCliVersion) {
773
+ expected = confirmed.aeCliVersion;
774
+ cluster = confirmed.clusterVersion ?? cluster ?? "";
775
+ verdict = evaluateCompat(pkg.version, expected, cluster);
776
+ pendingSkills = getPendingSkillsTarget(host) === expected;
777
+ entry = {
778
+ ...entry,
779
+ localVersion: pkg.version,
780
+ expectedVersion: expected,
781
+ clusterVersion: cluster || "",
782
+ lastFetchedAt: (/* @__PURE__ */ new Date()).toISOString()
783
+ };
784
+ store.hosts[key] = entry;
785
+ writeStore(store);
786
+ if (verdict.kind === "ok" && !pendingSkills) {
787
+ pendingHostCompatNotice = void 0;
788
+ pendingUpdateNotice = void 0;
789
+ return { status: "continue" };
790
+ }
791
+ if (isAutoSyncTargetEligible(expected)) {
792
+ const attempt = reserveAutoAttempt(host, expected);
793
+ if (attempt.allowed) {
794
+ const direction = verdict.kind === "ok" && pendingSkills ? "skills" : autoSyncDirection(pkg.version, expected);
795
+ const verb = direction === "upgrade" ? "upgrading" : direction === "downgrade" ? "downgrading" : direction === "skills" ? "repairing" : "switching";
796
+ printNotice(
797
+ `[ae-cli] Current version ${pkg.version}; this host requires ${expected}. Automatically ${verb} CLI and Skills, please wait...`
798
+ );
799
+ const result = installVersion(expected, {
800
+ skipCliInstall: verdict.kind === "ok" && pendingSkills,
801
+ progress: printNotice
802
+ });
803
+ recordVersionSyncResult(host, expected, result);
804
+ if (result.ok) {
805
+ printNotice(
806
+ `[ae-cli] Synchronized to ${expected}. Re-run the previous command to use the new version.`
807
+ );
808
+ pendingHostCompatNotice = void 0;
809
+ pendingUpdateNotice = void 0;
810
+ return {
811
+ status: "synced",
812
+ current: pkg.version,
813
+ expected,
814
+ cluster: cluster || "",
815
+ direction
816
+ };
817
+ }
818
+ const state = result.skillsPending ? "skills_pending" : "auto_sync_failed";
819
+ const friendlyCause = friendlyVersionSyncFailure(result);
820
+ const failure = result.skillsPending ? `CLI switched to ${expected}, but Skills synchronization failed. ${friendlyCause} This command will continue; run ae-cli update after access is restored.` : `${friendlyCause} This command will continue; run ae-cli update after fixing access.`;
821
+ printNotice(`[ae-cli] ${failure}`);
822
+ setUpdateNotice(pkg, expected, cluster || "", verdict, state, result.stage, failure);
823
+ entry.lastNotifiedAt = (/* @__PURE__ */ new Date()).toISOString();
824
+ store.hosts[key] = entry;
825
+ writeStore(store);
826
+ return { status: "continue" };
827
+ }
828
+ }
829
+ }
830
+ }
831
+ const notice = verdict.kind === "ok" && pendingSkills ? `[ae-cli] CLI ${expected} is installed, but its Skills are not synchronized.
832
+ Run: ae-cli update` : formatCompatNotice(verdict);
833
+ if (!notice) return { status: "continue" };
400
834
  const dueForTip = shouldNotify(entry);
401
835
  if (!dueForTip) {
402
836
  pendingHostCompatNotice = void 0;
403
837
  pendingUpdateNotice = void 0;
404
838
  store.hosts[key] = entry;
405
839
  writeStore(store);
406
- return;
840
+ return { status: "continue" };
407
841
  }
408
- pendingHostCompatNotice = notice;
409
- pendingUpdateNotice = {
410
- command: "ae-cli update",
411
- current: pkg.version,
842
+ setUpdateNotice(
843
+ pkg,
412
844
  expected,
413
- cluster: cluster || "",
414
- reason: verdict.kind,
415
- message: `Current ae-cli is ${pkg.version}; this host requires ${expected}. Run: ae-cli update`
416
- };
845
+ cluster || "",
846
+ verdict,
847
+ pendingSkills ? "skills_pending" : "prompt_only",
848
+ void 0,
849
+ pendingSkills ? `Skills for ae-cli ${expected} are not synchronized. Run: ae-cli update` : void 0
850
+ );
417
851
  printNotice(notice);
418
852
  entry.lastNotifiedAt = (/* @__PURE__ */ new Date()).toISOString();
419
853
  store.hosts[key] = entry;
420
854
  writeStore(store);
855
+ return { status: "continue" };
421
856
  }
422
- async function runHostCompatCheck(pkg, hostOverride) {
423
- if (shouldSkipCompatCheck()) return;
857
+ async function runHostCompatCheck(pkg, hostOverride, argv = process.argv) {
858
+ pendingHostCompatNotice = void 0;
859
+ pendingUpdateNotice = void 0;
860
+ if (shouldSkipCompatCheck(argv)) return { status: "continue" };
424
861
  const host = hostOverride || getActiveHost();
425
- if (!host) return;
862
+ if (!host) return { status: "continue" };
426
863
  try {
427
- await refreshAndMaybeNotify(host, pkg);
864
+ return await refreshAndMaybeNotify(host, pkg, !shouldSkipAutoSync(argv));
428
865
  } catch {
866
+ return { status: "continue" };
429
867
  }
430
868
  }
431
869
 
@@ -560,8 +998,10 @@ async function printOutput(data, format, jqExpr) {
560
998
  }
561
999
 
562
1000
  export {
563
- OPEN_SOURCE_AE_CLI_PACKAGE,
564
- AE_CLI_SKILLS_REPO,
1001
+ recordVersionSyncResult,
1002
+ buildVersionInstallPlan,
1003
+ friendlyVersionSyncFailure,
1004
+ installVersion,
565
1005
  fetchCliConfig,
566
1006
  getCachedCompatForHost,
567
1007
  formatCapabilityMissCompatHint,
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-RGXCNC4N.js";
9
9
  import {
10
10
  withOutputMetadata
11
- } from "./chunk-JTKMDN4A.js";
11
+ } from "./chunk-KTYR3U6D.js";
12
12
 
13
13
  // src/core/capability-command.ts
14
14
  import { randomBytes } from "crypto";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-HQ2A7ITL.js";
9
9
  import {
10
10
  withOutputMetadata
11
- } from "./chunk-JTKMDN4A.js";
11
+ } from "./chunk-KTYR3U6D.js";
12
12
 
13
13
  // src/core/capability-command.ts
14
14
  import { randomBytes } from "crypto";
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  printError,
10
10
  printOutput
11
- } from "./chunk-JTKMDN4A.js";
11
+ } from "./chunk-KTYR3U6D.js";
12
12
  import "./chunk-E7UXXHO3.js";
13
13
  import "./chunk-IR4ZLVPW.js";
14
14
  import {