@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.
- package/README.md +219 -150
- package/README.zh.md +227 -159
- package/dist/{auth-B2BRSYMS.js → auth-56Z45UVR.js} +1 -1
- package/dist/{capability-6KPXYNO7.js → capability-2H6PAOA3.js} +1 -1
- package/dist/{capability-ROKYESAP.js → capability-YPOQX6PL.js} +1 -1
- package/dist/{chunk-JTKMDN4A.js → chunk-KTYR3U6D.js} +471 -31
- package/dist/{chunk-EBPXT3TN.js → chunk-LHVM35J4.js} +1 -1
- package/dist/{chunk-RRFK2W7I.js → chunk-PVBYJWC2.js} +1 -1
- package/dist/{config-DDYJPJX3.js → config-BSSALXEN.js} +1 -1
- package/dist/index.js +45 -15
- package/dist/{metadata-2ZFRMVOL.js → metadata-AN3YFZEV.js} +2 -2
- package/dist/{metadata-ABQCKU5P.js → metadata-JIQ77HFY.js} +2 -2
- package/dist/{raw-6HXFFHLV.js → raw-XJCAT3HX.js} +1 -1
- package/dist/{sync-BF3IWYFC.js → sync-QFP4XFN3.js} +1 -1
- package/dist/{te-agent-3GJ5H2XF.js → te-agent-JHUG6DVV.js} +27 -0
- package/dist/{te-analysis-PCGESPLW.js → te-analysis-IWQZO423.js} +2 -2
- package/dist/{te-analysis-DJ6KELTU.js → te-analysis-X222IRYR.js} +2 -2
- package/dist/{te-dataops-ROHUJOX5.js → te-dataops-PZQ5NQLY.js} +1 -1
- package/dist/{te-dataops-LXL5YULV.js → te-dataops-XTWVTJCA.js} +1 -1
- package/dist/{te-engage-2YDCA552.js → te-engage-E7F4HTXU.js} +2 -2
- package/dist/{te-engage-VW6NJZ5V.js → te-engage-NLZUPSBK.js} +2 -2
- package/dist/{te-experiment-OYK54B72.js → te-experiment-D32TB6ZB.js} +2 -2
- package/dist/{te-experiment-7JGJNDXO.js → te-experiment-N63WF7XA.js} +2 -2
- package/dist/{te-team-RRZI4WRI.js → te-team-BQ3SKSZV.js} +1 -1
- package/dist/{update-ER7VFU55.js → update-DKG6UXEM.js} +44 -22
- package/package.json +4 -3
- package/skills/ae-agent/SKILL.md +2 -2
- package/skills/ae-agent/references/create-automation.md +11 -0
- package/skills/ae-agent/references/update-automation.md +11 -3
- package/skills/ae-generate-tracking-code/SKILL.md +5 -5
- package/skills/ae-generate-tracking-code/references/autotrack-enum.md +4 -4
- package/skills/ae-generate-tracking-code/references/client-sdk-insert.md +1 -1
- package/skills/ae-generate-tracking-code/references/debug-script.md +2 -2
- package/skills/ae-generate-tracking-code/references/logbus-config.md +1 -1
- package/skills/ae-generate-tracking-code/references/restful-call.md +1 -1
- 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
|
|
238
|
-
import
|
|
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
|
|
580
|
+
return path2.join(getConfigDir(), "compat-check.json");
|
|
244
581
|
}
|
|
245
582
|
function readStore() {
|
|
246
583
|
try {
|
|
247
584
|
const file = cacheFilePath();
|
|
248
|
-
if (!
|
|
249
|
-
const raw = JSON.parse(
|
|
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 (!
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
command: "ae-cli update",
|
|
411
|
-
current: pkg.version,
|
|
842
|
+
setUpdateNotice(
|
|
843
|
+
pkg,
|
|
412
844
|
expected,
|
|
413
|
-
cluster
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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
|
-
|
|
564
|
-
|
|
1001
|
+
recordVersionSyncResult,
|
|
1002
|
+
buildVersionInstallPlan,
|
|
1003
|
+
friendlyVersionSyncFailure,
|
|
1004
|
+
installVersion,
|
|
565
1005
|
fetchCliConfig,
|
|
566
1006
|
getCachedCompatForHost,
|
|
567
1007
|
formatCapabilityMissCompatHint,
|