@thinkingai/ae-cli 6.0.55 → 6.0.57
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 +35 -6
- package/README.zh.md +35 -6
- package/bin/ae-cli.js +5 -0
- package/bin/runtime-capabilities.json +5 -0
- package/cli-installation-guide.md +24 -3
- package/dist/{auth-RQBO6MTY.js → auth-YEIJLBFY.js} +61 -19
- package/dist/{automation-VAAQBM7Y.js → automation-NAR5DDGL.js} +94 -38
- package/dist/{capability-JRIFC4AJ.js → capability-P7DD4Q73.js} +50 -21
- package/dist/{chunk-5XJYLEVC.js → chunk-72NJKS7O.js} +4 -4
- package/dist/chunk-752BSLZC.js +222 -0
- package/dist/{chunk-RGYPSEYD.js → chunk-7ABK2PEJ.js} +3 -3
- package/dist/{chunk-Q5US7JC5.js → chunk-7JAVT5TK.js} +3 -3
- package/dist/{chunk-GHXZK54S.js → chunk-H3K5UGYC.js} +41 -13
- package/dist/{chunk-OP44TT4H.js → chunk-IN7KKL6K.js} +4 -2
- package/dist/{chunk-VZZNLV3I.js → chunk-JCWM2TYP.js} +61 -15
- package/dist/{chunk-S6M42KND.js → chunk-JEUXDY3B.js} +1 -1
- package/dist/{chunk-ZL7SGNIA.js → chunk-JNVVJ273.js} +58 -46
- package/dist/chunk-N6XBLRB7.js +105 -0
- package/dist/{chunk-AP7YP2NX.js → chunk-OAWPT7VP.js} +2 -2
- package/dist/{chunk-7KU2WNQE.js → chunk-QEMQKVHW.js} +37 -15
- package/dist/{chunk-CT7QDJS6.js → chunk-QVRIDN42.js} +2 -2
- package/dist/{chunk-RPBOAQQQ.js → chunk-QZUVLFNR.js} +1 -1
- package/dist/{chunk-3YVEAZUN.js → chunk-RJHGDWSC.js} +32 -21
- package/dist/{chunk-374V5R6N.js → chunk-TKVVACEJ.js} +6 -4
- package/dist/chunk-TULINOLS.js +2283 -0
- package/dist/chunk-YVG6QD3V.js +233 -0
- package/dist/{chunk-WNGFSIOB.js → chunk-Z3Q2AEIN.js} +237 -86
- package/dist/chunk-ZGIYG473.js +132 -0
- package/dist/{community-report-client-PEERQCX2.js → community-report-client-TJVCLZ3K.js} +1 -1
- package/dist/compat-check-PMEGUPZU.js +32 -0
- package/dist/{config-SICKWZ2A.js → config-VWVPR4WW.js} +43 -10
- package/dist/{context-NGVFPCBK.js → context-F6FCGTF3.js} +4 -4
- package/dist/{data-integration-BCLQFTGU.js → data-integration-6UAZO4GG.js} +207 -24
- package/dist/evaluation-4LO2O4AF.js +3714 -0
- package/dist/index.js +343 -79
- package/dist/{local-data-upload-client-TWMHFLUA.js → local-data-upload-client-KCMG6J62.js} +1 -1
- package/dist/{memory-CQKH2JAV.js → memory-HHPX3NF2.js} +6 -6
- package/dist/{metadata-EMFGUI2W.js → metadata-HEQQEEG3.js} +17 -9
- package/dist/{model-VX5M2CJ6.js → model-7UHGWYVR.js} +4 -4
- package/dist/{personal-semantic-preference-SLWVVYZ4.js → personal-semantic-preference-V4L6ZWR2.js} +7 -6
- package/dist/{project-semantic-DPSZBKQD.js → project-semantic-NKHKJX6M.js} +7 -6
- package/dist/{sync-RZIAV37C.js → sync-ZJMXCRIN.js} +8 -7
- package/dist/{te-agent-AORRE2FM.js → te-agent-WBKIFEQD.js} +757 -94
- package/dist/{te-analysis-ISXKJKRT.js → te-analysis-ADX7RKO4.js} +578 -94
- package/dist/{te-community-42M7ICDO.js → te-community-PREDRXG3.js} +722 -228
- package/dist/{te-dataops-BNKNZ4F2.js → te-dataops-ITWJHO2U.js} +9 -6
- package/dist/{te-engage-BZE3MBKT.js → te-engage-VE2NUH5V.js} +7 -6
- package/dist/{te-kb-ULATOTQ3.js → te-kb-WSMOW7WS.js} +407 -83
- package/dist/{te-system-UGHVKBVV.js → te-system-YM657FKO.js} +6 -6
- package/dist/{te-team-YA6SNOJ3.js → te-team-T2WRLOOD.js} +64 -41
- package/dist/{things-DTCOQIQB.js → things-6QOMFXOG.js} +7 -7
- package/dist/{update-ESNYKUVR.js → update-I4MOUYOX.js} +11 -7
- package/dist/{workspace-JEOHKFPX.js → workspace-LYDAP647.js} +6 -6
- package/package.json +25 -11
- package/skills/ae-agent/SKILL.md +20 -3
- package/skills/ae-agent/references/command_index.md +8 -0
- package/skills/ae-agent/references/resource-package.md +57 -0
- package/skills/ae-analysis/SKILL.md +23 -10
- package/skills/ae-analysis/references/adhoc_export.md +1 -1
- package/skills/ae-analysis/references/agent_review_preflight.md +55 -10
- package/skills/ae-analysis/references/agent_review_priorities_comparisons.md +5 -3
- package/skills/ae-analysis/references/agent_review_submit_to_page.md +60 -24
- package/skills/ae-analysis/references/ai_models/event.md +58 -2
- package/skills/ae-analysis/references/ai_models/rank_list.md +1 -0
- package/skills/ae-analysis/references/ai_models/retention.md +2 -0
- package/skills/ae-analysis/references/ai_models/revenue.md +2 -0
- package/skills/ae-analysis/references/analysis_data_export.md +11 -1
- package/skills/ae-analysis/references/asset_batch_info_export.md +2 -2
- package/skills/ae-analysis/references/asset_batch_sql_export.md +2 -2
- package/skills/ae-analysis/references/asset_export.md +1 -1
- package/skills/ae-analysis/references/bi_panel_page_data_export.md +1 -1
- package/skills/ae-analysis/references/check_export.md +1 -1
- package/skills/ae-analysis/references/command_index.md +39 -26
- package/skills/ae-analysis/references/dashboard_report_data_export.md +2 -0
- package/skills/ae-analysis/references/dashboard_report_get.md +6 -2
- package/skills/ae-analysis/references/dashboard_report_update.md +24 -5
- package/skills/ae-analysis/references/feedback_submit.md +1 -1
- package/skills/ae-analysis/references/governance_recommendation_export.md +40 -18
- package/skills/ae-analysis/references/governance_recommendation_run_update.md +13 -0
- package/skills/ae-analysis/references/live_data_export.md +1 -1
- package/skills/ae-analysis/references/metric_create.md +29 -0
- package/skills/ae-analysis/references/metric_get.md +2 -0
- package/skills/ae-analysis/references/metric_update.md +2 -0
- package/skills/ae-analysis/references/operation_record_export.md +1 -1
- package/skills/ae-analysis/references/plan_change_log_export.md +1 -1
- package/skills/ae-analysis/references/plan_export.md +1 -1
- package/skills/ae-analysis/references/project_info_get.md +4 -0
- package/skills/ae-analysis/references/project_member_group_add_members.md +35 -0
- package/skills/ae-analysis/references/project_member_group_create.md +25 -0
- package/skills/ae-analysis/references/project_member_group_delete.md +34 -0
- package/skills/ae-analysis/references/project_member_group_list.md +29 -0
- package/skills/ae-analysis/references/project_member_group_members.md +28 -0
- package/skills/ae-analysis/references/project_member_group_remove_members.md +35 -0
- package/skills/ae-analysis/references/project_member_group_rename.md +26 -0
- package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +9 -1
- package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +6 -1
- package/skills/ae-analysis/references/report_data_export.md +1 -1
- package/skills/ae-analysis/references/scenario_append.md +31 -0
- package/skills/ae-analysis/references/scenario_begin.md +11 -0
- package/skills/ae-analysis/references/scenario_collect.md +43 -0
- package/skills/ae-analysis/references/scenario_evaluation.md +18 -0
- package/skills/ae-analysis/references/scenario_get.md +15 -0
- package/skills/ae-analysis/references/scenario_list.md +64 -0
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs +73 -4
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/company-kb.mjs +14 -1
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-schema.md +5 -1
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/execution-contract.mjs +173 -0
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +109 -64
- package/skills/ae-automation/SKILL.md +2 -2
- package/skills/ae-automation/references/cli.md +18 -1
- package/skills/ae-capability/SKILL.md +7 -3
- package/skills/ae-community/SKILL.md +77 -27
- package/skills/ae-community/references/community-analyzing-theme-comment.md +10 -8
- package/skills/ae-community/references/community-data-quality.md +68 -0
- package/skills/ae-community/references/community-query-evidence.md +85 -0
- package/skills/ae-community/references/community-scenario-capabilities.md +137 -0
- package/skills/ae-community/references/community-weekly-report.md +6 -3
- package/skills/ae-community/references/get_channel_info.md +7 -4
- package/skills/ae-community/references/get_comment_tag_analysis.md +3 -3
- package/skills/ae-community/references/get_comments_summary.md +4 -4
- package/skills/ae-community/references/get_corpus_tags.md +3 -3
- package/skills/ae-community/references/get_daily_summary.md +3 -3
- package/skills/ae-community/references/get_hot_topics.md +3 -3
- package/skills/ae-community/references/get_livestream_analysis.md +3 -3
- package/skills/ae-community/references/get_livestream_detail.md +4 -4
- package/skills/ae-community/references/get_livestream_list.md +3 -3
- package/skills/ae-community/references/get_livestream_overview.md +3 -3
- package/skills/ae-community/references/get_livestream_room_metrics.md +3 -3
- package/skills/ae-community/references/get_livestream_rooms.md +3 -3
- package/skills/ae-community/references/get_overview_metrics.md +3 -3
- package/skills/ae-community/references/get_post_detail.md +4 -4
- package/skills/ae-community/references/get_risk_content.md +5 -5
- package/skills/ae-community/references/get_sentiment_overview.md +4 -4
- package/skills/ae-community/references/get_tag_trends.md +3 -3
- package/skills/ae-community/references/get_topic_detail.md +3 -3
- package/skills/ae-community/references/list_sources.md +28 -0
- package/skills/ae-community/references/list_spaces.md +39 -0
- package/skills/ae-community/references/search_posts.md +21 -7
- package/skills/ae-data-integration/SKILL.md +6 -4
- package/skills/ae-data-integration/references/error-handling.md +1 -0
- package/skills/ae-data-integration/references/handoff.md +1 -1
- package/skills/ae-data-integration/references/sink-upload.md +11 -0
- package/skills/ae-data-integration/references/source-inspect.md +2 -1
- package/skills/ae-data-integration/references/transform.md +1 -1
- package/skills/ae-data-integration/references/ue-mapping.md +1 -1
- package/skills/ae-evaluation/SKILL.md +83 -0
- package/skills/ae-evaluation/references/access-and-members.md +55 -0
- package/skills/ae-evaluation/references/connections.md +78 -0
- package/skills/ae-evaluation/references/datasets.md +57 -0
- package/skills/ae-evaluation/references/evaluators.md +81 -0
- package/skills/ae-evaluation/references/files.md +33 -0
- package/skills/ae-evaluation/references/integrations.md +39 -0
- package/skills/ae-evaluation/references/runs.md +160 -0
- package/skills/ae-evaluation/references/schedules.md +31 -0
- package/skills/ae-kb/SKILL.md +33 -6
- package/skills/ae-kb/references/publication.md +81 -0
- package/skills/ae-kb/references/query-workflow.md +14 -1
- package/skills/ae-metadata/SKILL.md +3 -2
- package/skills/ae-metadata/references/metadata_data_table_download.md +7 -1
- package/skills/ae-metadata/references/metadata_data_table_get.md +37 -0
- package/skills/ae-team/SKILL.md +2 -2
- package/skills/ae-team/references/run-start.md +1 -0
- package/skills/ae-team/references/run-watch.md +7 -5
- package/skills/ae-things/SKILL.md +53 -51
- package/skills/ae-things/references/cli.md +56 -54
- package/skills/ae-things-progress/SKILL.md +20 -20
- package/skills/ae-things-review/SKILL.md +19 -19
- package/dist/chunk-6VNIR4AP.js +0 -1422
- package/dist/chunk-CI24BDR2.js +0 -43
|
@@ -0,0 +1,2283 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
peekCliToken
|
|
4
|
+
} from "./chunk-H3K5UGYC.js";
|
|
5
|
+
import {
|
|
6
|
+
getActiveHost,
|
|
7
|
+
getConfigDir,
|
|
8
|
+
isAeSandboxRuntime,
|
|
9
|
+
readSandboxCliTokenEntry
|
|
10
|
+
} from "./chunk-JNVVJ273.js";
|
|
11
|
+
import {
|
|
12
|
+
PermissionError,
|
|
13
|
+
externalCliTokenRequestOptions,
|
|
14
|
+
normalizeUrl,
|
|
15
|
+
readExternalCliToken
|
|
16
|
+
} from "./chunk-Z3Q2AEIN.js";
|
|
17
|
+
|
|
18
|
+
// src/core/compat-check.ts
|
|
19
|
+
import fs10 from "fs";
|
|
20
|
+
import path10 from "path";
|
|
21
|
+
import { createHash as createHash2 } from "crypto";
|
|
22
|
+
import { Agent } from "undici";
|
|
23
|
+
|
|
24
|
+
// src/core/update-check.ts
|
|
25
|
+
function shouldSkipUpdateCheck(argv = process.argv) {
|
|
26
|
+
if (process.env.AE_CLI_NO_UPDATE_CHECK === "1") return true;
|
|
27
|
+
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
28
|
+
return argv.slice(2).some((arg, index, args) => {
|
|
29
|
+
if (arg === "--no-update-check") return true;
|
|
30
|
+
if (arg === "--version") {
|
|
31
|
+
const value = args[index + 1];
|
|
32
|
+
return !value || value.startsWith("-");
|
|
33
|
+
}
|
|
34
|
+
if (arg === "-V" || arg === "--help" || arg === "-h") return true;
|
|
35
|
+
return false;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function parseVersion(version) {
|
|
39
|
+
const core = version.trim().split("-")[0];
|
|
40
|
+
return core.split(".").map((part) => {
|
|
41
|
+
const n = Number.parseInt(part, 10);
|
|
42
|
+
return Number.isFinite(n) ? n : 0;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function isNewer(latest, current) {
|
|
46
|
+
const a = parseVersion(latest);
|
|
47
|
+
const b = parseVersion(current);
|
|
48
|
+
const len = Math.max(a.length, b.length, 3);
|
|
49
|
+
for (let i = 0; i < len; i += 1) {
|
|
50
|
+
const av = a[i] ?? 0;
|
|
51
|
+
const bv = b[i] ?? 0;
|
|
52
|
+
if (av > bv) return true;
|
|
53
|
+
if (av < bv) return false;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/core/legacy-cli-version-map.ts
|
|
59
|
+
var HISTORICAL_CLUSTER_TO_PUBLIC_PIN = {
|
|
60
|
+
"6.0.0": "1.0.15",
|
|
61
|
+
"6.0.1": "1.0.15",
|
|
62
|
+
"6.0.2": "1.0.18",
|
|
63
|
+
"6.0.3": "1.0.18",
|
|
64
|
+
"6.0.4": "1.0.20",
|
|
65
|
+
"6.0.5": "1.0.21",
|
|
66
|
+
"6.0.6": "1.0.22",
|
|
67
|
+
"6.0.7": "1.0.22",
|
|
68
|
+
"6.0.8": "1.0.22",
|
|
69
|
+
"6.0.9": "1.0.22",
|
|
70
|
+
"6.0.10": "1.0.24",
|
|
71
|
+
"6.0.11": "1.0.24",
|
|
72
|
+
"6.0.12": "1.0.27",
|
|
73
|
+
"6.0.13": "1.0.28",
|
|
74
|
+
"6.0.14": "1.0.28",
|
|
75
|
+
"6.0.15": "1.0.30",
|
|
76
|
+
"6.0.16": "6.0.16",
|
|
77
|
+
"6.0.17": "6.0.17",
|
|
78
|
+
"6.0.18": "6.0.18",
|
|
79
|
+
"6.0.19": "6.0.18",
|
|
80
|
+
"6.0.20": "6.0.20",
|
|
81
|
+
"6.0.21": "6.0.20",
|
|
82
|
+
"6.0.22": "6.0.22",
|
|
83
|
+
"6.0.23": "6.0.22",
|
|
84
|
+
"6.0.24": "6.0.24",
|
|
85
|
+
"6.0.25": "6.0.24",
|
|
86
|
+
"6.0.26": "6.0.24"
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/core/install-process.ts
|
|
90
|
+
import spawn from "cross-spawn";
|
|
91
|
+
import fs from "fs";
|
|
92
|
+
import path from "path";
|
|
93
|
+
var INSTALL_TIMEOUT_MS = 12e4;
|
|
94
|
+
var SKILLS_TOOL = "skills@1.5.12";
|
|
95
|
+
var defaultInstallRunner = (command, args, timeoutMs) => {
|
|
96
|
+
const result2 = spawn.sync(command, args, {
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
99
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
100
|
+
timeout: timeoutMs,
|
|
101
|
+
windowsHide: true,
|
|
102
|
+
env: {
|
|
103
|
+
...process.env,
|
|
104
|
+
DISABLE_TELEMETRY: "1",
|
|
105
|
+
NO_COLOR: "1",
|
|
106
|
+
// Select installed clients, not only the AI that launched AE CLI.
|
|
107
|
+
...args[1] === SKILLS_TOOL && args[2] === "add" ? { AI_AGENT: "ae-cli" } : {}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return { ...result2, stdout: result2.stdout || "", stderr: result2.stderr || "" };
|
|
111
|
+
};
|
|
112
|
+
function resolveNodeTool(tool, execPath = process.execPath, platform = process.platform) {
|
|
113
|
+
const filename = platform === "win32" ? `${tool}.cmd` : tool;
|
|
114
|
+
const sibling = path.join(path.dirname(execPath), filename);
|
|
115
|
+
return fs.existsSync(sibling) ? sibling : filename;
|
|
116
|
+
}
|
|
117
|
+
function commandSucceeded(result2) {
|
|
118
|
+
return !result2.error && !result2.signal && result2.status === 0;
|
|
119
|
+
}
|
|
120
|
+
function exactVersion(raw) {
|
|
121
|
+
const version = raw.trim().replace(/^v/i, "");
|
|
122
|
+
if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(version)) {
|
|
123
|
+
throw new Error("Expected an exact version such as 6.0.49 or 6.1.9.");
|
|
124
|
+
}
|
|
125
|
+
return version;
|
|
126
|
+
}
|
|
127
|
+
function installHost(raw) {
|
|
128
|
+
if (!raw) return null;
|
|
129
|
+
const normalized = normalizeUrl(raw.trim());
|
|
130
|
+
try {
|
|
131
|
+
if (raw.includes("://") && !/^https?:\/\//.test(raw.trim())) throw new Error();
|
|
132
|
+
const url = new URL(normalized);
|
|
133
|
+
if (!["https:", "http:"].includes(url.protocol) || !url.hostname || url.username || url.password || url.search || url.hash || /[\s'"`$\\<>]/.test(normalized)) throw new Error();
|
|
134
|
+
return normalized;
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error("Host must be an HTTP(S) URL without credentials, query parameters, or fragments.");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function classifyInstallFailure(result2) {
|
|
140
|
+
const text = `${result2.error?.message || ""}
|
|
141
|
+
${result2.stderr}
|
|
142
|
+
${result2.stdout}`;
|
|
143
|
+
if (result2.error?.code === "ETIMEDOUT" || /timed?\s*out|ETIMEDOUT/i.test(text)) return "timeout";
|
|
144
|
+
if (/EACCES|EPERM|permission denied|access is denied/i.test(text)) return "permission";
|
|
145
|
+
if (/EAI_AGAIN|ENOTFOUND|ECONNRESET|ECONNREFUSED|network|unable to access|could not resolve|certificate|SSL/i.test(text)) return "network";
|
|
146
|
+
if (/E404|404 Not Found|no matching version|couldn't find remote ref/i.test(text)) return "package_missing";
|
|
147
|
+
return "unknown";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/core/version-compat.ts
|
|
151
|
+
var OPEN_SOURCE_AE_CLI_PACKAGE = "@thinkingai/ae-cli";
|
|
152
|
+
var PUBLISHED_EXTERNAL_VERSIONS = /* @__PURE__ */ new Set([
|
|
153
|
+
"1.0.15",
|
|
154
|
+
"1.0.18",
|
|
155
|
+
"1.0.20",
|
|
156
|
+
"1.0.21",
|
|
157
|
+
"1.0.22",
|
|
158
|
+
"1.0.24",
|
|
159
|
+
"1.0.27",
|
|
160
|
+
"1.0.28",
|
|
161
|
+
"1.0.30",
|
|
162
|
+
"6.0.16",
|
|
163
|
+
"6.0.17",
|
|
164
|
+
"6.0.18",
|
|
165
|
+
"6.0.20",
|
|
166
|
+
"6.0.22",
|
|
167
|
+
"6.0.24",
|
|
168
|
+
"6.0.27",
|
|
169
|
+
"6.0.28",
|
|
170
|
+
"6.0.29",
|
|
171
|
+
"6.0.30",
|
|
172
|
+
"6.0.31"
|
|
173
|
+
]);
|
|
174
|
+
function normalizeCliVersion(version) {
|
|
175
|
+
return version.trim().replace(/^v/i, "");
|
|
176
|
+
}
|
|
177
|
+
function versionLine(version) {
|
|
178
|
+
const parts = normalizeCliVersion(version).split(/[.-]/);
|
|
179
|
+
if (parts.length < 2) return normalizeCliVersion(version);
|
|
180
|
+
return `${parts[0]}.${parts[1]}`;
|
|
181
|
+
}
|
|
182
|
+
function sameVersionLine(a, b) {
|
|
183
|
+
return versionLine(a) === versionLine(b);
|
|
184
|
+
}
|
|
185
|
+
function parseVersionParts(version) {
|
|
186
|
+
const core = normalizeCliVersion(version).split("-")[0];
|
|
187
|
+
return core.split(".").map((p) => {
|
|
188
|
+
const n = Number.parseInt(p, 10);
|
|
189
|
+
return Number.isFinite(n) ? n : 0;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function compareNormalized(a, b) {
|
|
193
|
+
const pa = parseVersionParts(a);
|
|
194
|
+
const pb = parseVersionParts(b);
|
|
195
|
+
const len = Math.max(pa.length, pb.length, 3);
|
|
196
|
+
for (let i = 0; i < len; i += 1) {
|
|
197
|
+
const av = pa[i] ?? 0;
|
|
198
|
+
const bv = pb[i] ?? 0;
|
|
199
|
+
if (av > bv) return 1;
|
|
200
|
+
if (av < bv) return -1;
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
function evaluateCompat(localRaw, expectedRaw, clusterLine) {
|
|
205
|
+
const local = normalizeCliVersion(localRaw);
|
|
206
|
+
const expected = normalizeCliVersion(expectedRaw);
|
|
207
|
+
if (!local || !expected) return { kind: "ok" };
|
|
208
|
+
const line = clusterLine && clusterLine.trim() || versionLine(expected);
|
|
209
|
+
if (!sameVersionLine(local, expected)) {
|
|
210
|
+
return { kind: "line_mismatch", local, expected, line };
|
|
211
|
+
}
|
|
212
|
+
if (clusterLine && clusterLine.trim()) {
|
|
213
|
+
const clusterNorm = clusterLine.trim();
|
|
214
|
+
const localLine = versionLine(local);
|
|
215
|
+
if (localLine !== clusterNorm && localLine !== versionLine(clusterNorm)) {
|
|
216
|
+
return { kind: "line_mismatch", local, expected, line: clusterNorm };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (local === expected) return { kind: "ok" };
|
|
220
|
+
if (isNewer(local, expected)) {
|
|
221
|
+
return { kind: "local_newer", local, expected, line };
|
|
222
|
+
}
|
|
223
|
+
if (isNewer(expected, local)) {
|
|
224
|
+
return { kind: "local_older", local, expected, line };
|
|
225
|
+
}
|
|
226
|
+
return { kind: "ok" };
|
|
227
|
+
}
|
|
228
|
+
function resolvePinTarget(expectedVersion) {
|
|
229
|
+
const expected = expectedVersion.trim().replace(/^v/i, "");
|
|
230
|
+
if (!expected) return null;
|
|
231
|
+
if (PUBLISHED_EXTERNAL_VERSIONS.has(expected)) return expected;
|
|
232
|
+
const historical = HISTORICAL_CLUSTER_TO_PUBLIC_PIN[expected];
|
|
233
|
+
if (historical && PUBLISHED_EXTERNAL_VERSIONS.has(historical)) {
|
|
234
|
+
return historical;
|
|
235
|
+
}
|
|
236
|
+
const line = versionLine(expected);
|
|
237
|
+
const candidates = [...PUBLISHED_EXTERNAL_VERSIONS].filter((tag) => {
|
|
238
|
+
if (versionLine(tag) !== line) return false;
|
|
239
|
+
return compareNormalized(tag, expected) <= 0;
|
|
240
|
+
});
|
|
241
|
+
if (candidates.length === 0) return null;
|
|
242
|
+
candidates.sort((a, b) => compareNormalized(a, b));
|
|
243
|
+
return candidates[candidates.length - 1] ?? null;
|
|
244
|
+
}
|
|
245
|
+
function formatPinCommands(expectedVersion) {
|
|
246
|
+
const ver = resolvePinTarget(expectedVersion);
|
|
247
|
+
if (!ver) return [];
|
|
248
|
+
return [
|
|
249
|
+
`npx ${OPEN_SOURCE_AE_CLI_PACKAGE}@latest update --target ${ver}`
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
function formatUpgradeCommands(expectedVersion) {
|
|
253
|
+
let ver;
|
|
254
|
+
try {
|
|
255
|
+
ver = exactVersion(expectedVersion);
|
|
256
|
+
} catch {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
return [
|
|
260
|
+
`npx ${OPEN_SOURCE_AE_CLI_PACKAGE}@latest update --target ${ver}`
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
function formatUnifiedUpdateCommand() {
|
|
264
|
+
return "ae-cli update";
|
|
265
|
+
}
|
|
266
|
+
function formatCompatNotice(verdict) {
|
|
267
|
+
if (verdict.kind === "ok") return void 0;
|
|
268
|
+
const canUpdateToHost = formatUpgradeCommands(verdict.expected).length > 0;
|
|
269
|
+
if (verdict.kind === "local_newer") {
|
|
270
|
+
if (canUpdateToHost) {
|
|
271
|
+
return [
|
|
272
|
+
`[ae-cli] Host compat: local ${verdict.local} > environment ${verdict.expected} (cluster ${verdict.line}).`,
|
|
273
|
+
` Run: ${formatUnifiedUpdateCommand()} to sync CLI + skills to this host.`,
|
|
274
|
+
` Or update cluster components to the latest version.`
|
|
275
|
+
].join("\n");
|
|
276
|
+
}
|
|
277
|
+
return [
|
|
278
|
+
`[ae-cli] Host compat: local ${verdict.local} > environment ${verdict.expected} (cluster ${verdict.line}).`,
|
|
279
|
+
` No public GitHub/npm release on this line is \u2264 environment ${verdict.expected}.`,
|
|
280
|
+
` Update cluster components to the latest version to match your CLI.`
|
|
281
|
+
].join("\n");
|
|
282
|
+
}
|
|
283
|
+
if (verdict.kind === "local_older") {
|
|
284
|
+
if (canUpdateToHost) {
|
|
285
|
+
return [
|
|
286
|
+
`[ae-cli] Host compat: local ${verdict.local} < environment ${verdict.expected} (cluster ${verdict.line}).`,
|
|
287
|
+
` Run: ${formatUnifiedUpdateCommand()} to sync CLI + skills to this host.`
|
|
288
|
+
].join("\n");
|
|
289
|
+
}
|
|
290
|
+
return [
|
|
291
|
+
`[ae-cli] Host compat: local ${verdict.local} < environment ${verdict.expected} (cluster ${verdict.line}).`,
|
|
292
|
+
` No installable semver target for environment ${verdict.expected}.`,
|
|
293
|
+
` Ask the platform owner for the install package, or wait for a published build.`
|
|
294
|
+
].join("\n");
|
|
295
|
+
}
|
|
296
|
+
if (canUpdateToHost) {
|
|
297
|
+
return [
|
|
298
|
+
`[ae-cli] Host compat: local ${verdict.local} line mismatches environment ${verdict.expected} (cluster ${verdict.line}).`,
|
|
299
|
+
` Run: ${formatUnifiedUpdateCommand()} to sync CLI + skills to this host.`,
|
|
300
|
+
` Or update cluster components to the latest version to match your CLI line.`
|
|
301
|
+
].join("\n");
|
|
302
|
+
}
|
|
303
|
+
return [
|
|
304
|
+
`[ae-cli] Host compat: local ${verdict.local} line mismatches environment ${verdict.expected} (cluster ${verdict.line}).`,
|
|
305
|
+
` No public GitHub/npm release for this line; update cluster components to the latest version, or install a published build on the same line.`
|
|
306
|
+
].join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/core/version-sync.ts
|
|
310
|
+
import fs8 from "fs";
|
|
311
|
+
import path8 from "path";
|
|
312
|
+
|
|
313
|
+
// src/core/installation.ts
|
|
314
|
+
import fs7 from "fs";
|
|
315
|
+
import path7 from "path";
|
|
316
|
+
import crypto2 from "crypto";
|
|
317
|
+
|
|
318
|
+
// src/core/package-info.ts
|
|
319
|
+
import fs2 from "fs";
|
|
320
|
+
import path2 from "path";
|
|
321
|
+
import { fileURLToPath } from "url";
|
|
322
|
+
function getLocalCliPackageInfo() {
|
|
323
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
324
|
+
for (let i = 0; i < 8; i += 1) {
|
|
325
|
+
const candidate = path2.join(dir, "package.json");
|
|
326
|
+
try {
|
|
327
|
+
if (fs2.existsSync(candidate)) {
|
|
328
|
+
const pkg = JSON.parse(fs2.readFileSync(candidate, "utf8"));
|
|
329
|
+
if (pkg.name && pkg.version && String(pkg.name).includes("ae-cli")) {
|
|
330
|
+
return { name: pkg.name, version: pkg.version, root: dir };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
const parent = path2.dirname(dir);
|
|
336
|
+
if (parent === dir) break;
|
|
337
|
+
dir = parent;
|
|
338
|
+
}
|
|
339
|
+
return { name: "@tant/ae-cli", version: "0.0.0", root: "" };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/core/skills-install.ts
|
|
343
|
+
import fs3 from "fs";
|
|
344
|
+
import os from "os";
|
|
345
|
+
import path3 from "path";
|
|
346
|
+
import { parseDocument } from "yaml";
|
|
347
|
+
function globalSkillsDirectory() {
|
|
348
|
+
return path3.join(os.homedir(), ".agents", "skills");
|
|
349
|
+
}
|
|
350
|
+
function skillName(content) {
|
|
351
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
352
|
+
if (!frontmatter) throw new Error("Missing YAML frontmatter.");
|
|
353
|
+
const parsed = parseDocument(frontmatter[1], { uniqueKeys: true });
|
|
354
|
+
if (parsed.errors.length) throw new Error("Invalid YAML frontmatter.");
|
|
355
|
+
const data = parsed.toJS({ maxAliasCount: 100 });
|
|
356
|
+
if (!data || typeof data.name !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(data.name) || typeof data.description !== "string" || !data.description.trim()) throw new Error("Invalid Skill metadata.");
|
|
357
|
+
return data.name;
|
|
358
|
+
}
|
|
359
|
+
function readFiles(root, prefix = "", files = /* @__PURE__ */ new Map()) {
|
|
360
|
+
for (const entry of fs3.readdirSync(path3.join(root, prefix), { withFileTypes: true })) {
|
|
361
|
+
if (entry.name.startsWith(".") || ["metadata.json", "__pycache__", "__pypackages__"].includes(entry.name)) continue;
|
|
362
|
+
const relative = path3.join(prefix, entry.name);
|
|
363
|
+
if (entry.isSymbolicLink()) throw new Error("Bundled Skills must contain regular files.");
|
|
364
|
+
if (entry.isDirectory()) readFiles(root, relative, files);
|
|
365
|
+
else if (entry.isFile()) files.set(relative, fs3.readFileSync(path3.join(root, relative)));
|
|
366
|
+
}
|
|
367
|
+
return files;
|
|
368
|
+
}
|
|
369
|
+
function readBundledSkills(directory) {
|
|
370
|
+
const skills = fs3.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
|
|
371
|
+
try {
|
|
372
|
+
const files = readFiles(path3.join(directory, entry.name));
|
|
373
|
+
return { name: skillName(files.get("SKILL.md").toString("utf8")), files };
|
|
374
|
+
} catch {
|
|
375
|
+
const name = /^[a-z0-9-]+$/.test(entry.name) ? entry.name : "(invalid name)";
|
|
376
|
+
throw new Error(`Invalid bundled Skill ${name}: missing files or YAML/frontmatter error.`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
if (!skills.length || new Set(skills.map((skill) => skill.name)).size !== skills.length) {
|
|
380
|
+
throw new Error("Bundled Skills are empty or contain duplicate names.");
|
|
381
|
+
}
|
|
382
|
+
return skills;
|
|
383
|
+
}
|
|
384
|
+
function verifyGlobalSkills(skills, directory) {
|
|
385
|
+
return skills.flatMap((skill) => {
|
|
386
|
+
let reason = "";
|
|
387
|
+
try {
|
|
388
|
+
const target = path3.join(directory, skill.name);
|
|
389
|
+
if (skillName(fs3.readFileSync(path3.join(target, "SKILL.md"), "utf8")) !== skill.name) {
|
|
390
|
+
reason = "Installed Skill name does not match the bundled Skill.";
|
|
391
|
+
} else {
|
|
392
|
+
for (const [file, expected] of skill.files) {
|
|
393
|
+
if (!fs3.readFileSync(path3.join(target, file)).equals(expected)) {
|
|
394
|
+
reason = "Installed files do not match the CLI package version.";
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
reason = error.code === "ENOENT" ? "Expected Skill file is missing." : "Installed Skill has invalid YAML/frontmatter or unreadable files.";
|
|
401
|
+
}
|
|
402
|
+
return reason ? [{ skill: skill.name, reason }] : [];
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/core/business-config.ts
|
|
407
|
+
import crypto from "crypto";
|
|
408
|
+
import fs4 from "fs";
|
|
409
|
+
import path4 from "path";
|
|
410
|
+
|
|
411
|
+
// src/core/skill-businesses.json
|
|
412
|
+
var skill_businesses_default = {
|
|
413
|
+
base: [
|
|
414
|
+
"ae-capability"
|
|
415
|
+
],
|
|
416
|
+
analysis: [
|
|
417
|
+
"ae-analysis",
|
|
418
|
+
"ae-analysis-global",
|
|
419
|
+
"ae-data-integration",
|
|
420
|
+
"ae-data-integration-helper",
|
|
421
|
+
"ae-generate-tracking-code",
|
|
422
|
+
"ae-generate-tracking-plan",
|
|
423
|
+
"ae-metadata",
|
|
424
|
+
"ae-migrate-tracking-code"
|
|
425
|
+
],
|
|
426
|
+
dataops: [
|
|
427
|
+
"ae-dataops"
|
|
428
|
+
],
|
|
429
|
+
engage: [
|
|
430
|
+
"ae-engage"
|
|
431
|
+
],
|
|
432
|
+
community: [
|
|
433
|
+
"ae-community"
|
|
434
|
+
],
|
|
435
|
+
agent: [
|
|
436
|
+
"ae-agent",
|
|
437
|
+
"ae-automation",
|
|
438
|
+
"ae-current-context",
|
|
439
|
+
"ae-evaluation",
|
|
440
|
+
"ae-kb",
|
|
441
|
+
"ae-kb-discovery",
|
|
442
|
+
"ae-page-context",
|
|
443
|
+
"ae-system",
|
|
444
|
+
"ae-team",
|
|
445
|
+
"ae-things",
|
|
446
|
+
"ae-things-progress",
|
|
447
|
+
"ae-things-review",
|
|
448
|
+
"ae-use-agent",
|
|
449
|
+
"ae-workspace"
|
|
450
|
+
]
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// src/core/businesses.ts
|
|
454
|
+
var OPTIONAL_BUSINESSES = ["dataops", "engage", "agent", "community"];
|
|
455
|
+
var COMMAND_BUSINESSES = {
|
|
456
|
+
dataops_datatable: "dataops",
|
|
457
|
+
dataops_flow: "dataops",
|
|
458
|
+
dataops_integration: "dataops",
|
|
459
|
+
dataops_operations: "dataops",
|
|
460
|
+
dataops_repo: "dataops",
|
|
461
|
+
dataops_ide: "dataops",
|
|
462
|
+
"engage-flow": "engage",
|
|
463
|
+
"engage-task": "engage",
|
|
464
|
+
"engage-setting": "engage",
|
|
465
|
+
"engage-scene": "engage",
|
|
466
|
+
"engage-activity": "engage",
|
|
467
|
+
"engage-workbench": "engage",
|
|
468
|
+
"engage-query": "engage",
|
|
469
|
+
community: "community",
|
|
470
|
+
kb: "agent",
|
|
471
|
+
team: "agent",
|
|
472
|
+
agent: "agent",
|
|
473
|
+
memory: "agent",
|
|
474
|
+
automation: "agent",
|
|
475
|
+
things: "agent",
|
|
476
|
+
workspace: "agent",
|
|
477
|
+
evaluation: "agent",
|
|
478
|
+
sync: "agent",
|
|
479
|
+
model: "agent",
|
|
480
|
+
context: "agent"
|
|
481
|
+
};
|
|
482
|
+
var AGENT_SYSTEM_COMMANDS = /* @__PURE__ */ new Set([
|
|
483
|
+
"channel",
|
|
484
|
+
"+bind-feishu-users",
|
|
485
|
+
"+create-channel",
|
|
486
|
+
"+list-channels",
|
|
487
|
+
"+remove-channel",
|
|
488
|
+
"+update-channel",
|
|
489
|
+
"+bind-quota-rule-user",
|
|
490
|
+
"+create-quota-rule",
|
|
491
|
+
"+get-balance",
|
|
492
|
+
"+get-balance-alert",
|
|
493
|
+
"+get-cost-summary",
|
|
494
|
+
"+list-over-limit-users",
|
|
495
|
+
"+list-quota-rules",
|
|
496
|
+
"+remove-quota-rule",
|
|
497
|
+
"+set-balance-alert",
|
|
498
|
+
"+update-quota-rule",
|
|
499
|
+
"+add-members",
|
|
500
|
+
"+get-member-stats",
|
|
501
|
+
"+list-member-candidates",
|
|
502
|
+
"+list-members",
|
|
503
|
+
"+remove-member",
|
|
504
|
+
"+set-member-role",
|
|
505
|
+
"+set-member-status",
|
|
506
|
+
"+clear-default-model",
|
|
507
|
+
"+get-default-models",
|
|
508
|
+
"+get-model-sync-settings",
|
|
509
|
+
"+get-system-model-price-rules",
|
|
510
|
+
"+list-company-models",
|
|
511
|
+
"+list-system-models",
|
|
512
|
+
"+set-company-model-enabled",
|
|
513
|
+
"+set-default-model",
|
|
514
|
+
"+set-model-sync-settings",
|
|
515
|
+
"+set-system-model-enabled",
|
|
516
|
+
"+get-sandbox-tool-distribution",
|
|
517
|
+
"+list-sandbox-tool-operations",
|
|
518
|
+
"+list-sandbox-tools",
|
|
519
|
+
"+npm-install",
|
|
520
|
+
"+remove-sandbox-tool",
|
|
521
|
+
"+set-sandbox-tool-enabled",
|
|
522
|
+
"+sync-sandbox-tools",
|
|
523
|
+
"+upload-sandbox-tool",
|
|
524
|
+
"+activate-sandbox-tools",
|
|
525
|
+
"+deactivate-sandbox-tools",
|
|
526
|
+
"+refresh-sandbox-tool-status",
|
|
527
|
+
"+batch-create-sandboxes",
|
|
528
|
+
"+bind-sandbox-user",
|
|
529
|
+
"+get-sandbox-config",
|
|
530
|
+
"+list-sandbox-users",
|
|
531
|
+
"+list-sandboxes",
|
|
532
|
+
"+remove-sandbox",
|
|
533
|
+
"+set-sandbox-enabled",
|
|
534
|
+
"+start-sandbox",
|
|
535
|
+
"+stop-sandbox",
|
|
536
|
+
"+unbind-sandbox-user",
|
|
537
|
+
"+update-sandbox",
|
|
538
|
+
"+export-usage",
|
|
539
|
+
"+export-usage-details",
|
|
540
|
+
"+get-agent-tool-calls",
|
|
541
|
+
"+get-usage-combinations",
|
|
542
|
+
"+get-usage-details",
|
|
543
|
+
"+get-usage-summary"
|
|
544
|
+
]);
|
|
545
|
+
var SKILL_BUSINESSES = Object.fromEntries(
|
|
546
|
+
OPTIONAL_BUSINESSES.flatMap((business) => skill_businesses_default[business].map((name) => [name, business]))
|
|
547
|
+
);
|
|
548
|
+
function normalizeBusinesses(value) {
|
|
549
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
550
|
+
const record = value;
|
|
551
|
+
const result2 = {};
|
|
552
|
+
for (const business of OPTIONAL_BUSINESSES) {
|
|
553
|
+
if (typeof record[business] === "boolean") result2[business] = record[business];
|
|
554
|
+
}
|
|
555
|
+
return result2;
|
|
556
|
+
}
|
|
557
|
+
function businessForCommand(commandPath) {
|
|
558
|
+
const [service, resource] = commandPath;
|
|
559
|
+
if (service === "system") return AGENT_SYSTEM_COMMANDS.has(resource) ? "agent" : void 0;
|
|
560
|
+
return COMMAND_BUSINESSES[service];
|
|
561
|
+
}
|
|
562
|
+
function businessForCapability(idOrDomain) {
|
|
563
|
+
const namespace = idOrDomain.trim().toLowerCase().split(".")[0];
|
|
564
|
+
if (namespace === "dataops" || namespace === "engage") return namespace;
|
|
565
|
+
return COMMAND_BUSINESSES[namespace];
|
|
566
|
+
}
|
|
567
|
+
function businessForSkill(name) {
|
|
568
|
+
return SKILL_BUSINESSES[name];
|
|
569
|
+
}
|
|
570
|
+
function getSkillBusinessState(name, states) {
|
|
571
|
+
const business = businessForSkill(name);
|
|
572
|
+
return business ? states[business] : true;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/core/local-skill-context.ts
|
|
576
|
+
function isSandboxExecutionContext() {
|
|
577
|
+
return process.env.TE_AGENT_CLI_CONFIG_DIR !== void 0 || isAeSandboxRuntime() || !!readSandboxCliTokenEntry();
|
|
578
|
+
}
|
|
579
|
+
function canManageGlobalSkills() {
|
|
580
|
+
return !readExternalCliToken() && !isSandboxExecutionContext();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/core/business-config.ts
|
|
584
|
+
var BUSINESS_CONFIG_TTL_MS = 10 * 60 * 1e3;
|
|
585
|
+
var UNAVAILABLE_TTL_MS = 5 * 60 * 1e3;
|
|
586
|
+
function cachePath() {
|
|
587
|
+
return path4.join(getConfigDir(), "business-config.json");
|
|
588
|
+
}
|
|
589
|
+
function cacheKey(host, token) {
|
|
590
|
+
return `${normalizeUrl(host)}#${crypto.createHash("sha256").update(token).digest("hex")}`;
|
|
591
|
+
}
|
|
592
|
+
function readStore() {
|
|
593
|
+
try {
|
|
594
|
+
const value = JSON.parse(fs4.readFileSync(cachePath(), "utf8"));
|
|
595
|
+
if (value?.entries && typeof value.entries === "object" && !Array.isArray(value.entries)) return value;
|
|
596
|
+
} catch {
|
|
597
|
+
}
|
|
598
|
+
return { entries: {} };
|
|
599
|
+
}
|
|
600
|
+
function remember(host, token, entry) {
|
|
601
|
+
const store = readStore();
|
|
602
|
+
store.entries[cacheKey(host, token)] = entry;
|
|
603
|
+
const target = cachePath();
|
|
604
|
+
const temporary = `${target}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
|
|
605
|
+
try {
|
|
606
|
+
fs4.mkdirSync(path4.dirname(target), { recursive: true });
|
|
607
|
+
fs4.writeFileSync(temporary, JSON.stringify(store), { encoding: "utf8", mode: 384 });
|
|
608
|
+
fs4.renameSync(temporary, target);
|
|
609
|
+
fs4.chmodSync(target, 384);
|
|
610
|
+
} catch {
|
|
611
|
+
try {
|
|
612
|
+
fs4.rmSync(temporary, { force: true });
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function rememberBusinessConfig(host, token, businesses, now = Date.now()) {
|
|
618
|
+
remember(host, token, { businesses: normalizeBusinesses(businesses), fetchedAt: now });
|
|
619
|
+
}
|
|
620
|
+
function peekBusinessConfig(host, token, now = Date.now()) {
|
|
621
|
+
const entry = readStore().entries[cacheKey(host, token)];
|
|
622
|
+
if (entry?.unavailableAt !== void 0 && now >= entry.unavailableAt && now - entry.unavailableAt < UNAVAILABLE_TTL_MS) return {};
|
|
623
|
+
if (!entry || !Number.isFinite(entry.fetchedAt) || now < entry.fetchedAt || now - entry.fetchedAt >= BUSINESS_CONFIG_TTL_MS) return void 0;
|
|
624
|
+
return normalizeBusinesses(entry.businesses);
|
|
625
|
+
}
|
|
626
|
+
function readBusinessConfig(host, token) {
|
|
627
|
+
const entry = readStore().entries[cacheKey(host, token)];
|
|
628
|
+
return entry && Number.isFinite(entry.fetchedAt) && entry.fetchedAt > 0 ? normalizeBusinesses(entry.businesses) : void 0;
|
|
629
|
+
}
|
|
630
|
+
function readCurrentBusinessConfig(hostOverride) {
|
|
631
|
+
try {
|
|
632
|
+
if (isSandboxExecutionContext()) return {};
|
|
633
|
+
const host = hostOverride || getActiveHost();
|
|
634
|
+
if (!host) return {};
|
|
635
|
+
const token = peekCliToken(host);
|
|
636
|
+
return token ? readBusinessConfig(host, token) ?? {} : {};
|
|
637
|
+
} catch {
|
|
638
|
+
return {};
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async function resolveBusinessConfig(host, token, fetchRemote, now = Date.now()) {
|
|
642
|
+
const cached = peekBusinessConfig(host, token, now);
|
|
643
|
+
if (cached !== void 0) return cached;
|
|
644
|
+
const config = await fetchRemote().catch(() => null);
|
|
645
|
+
if (!config) {
|
|
646
|
+
const previous = readStore().entries[cacheKey(host, token)];
|
|
647
|
+
remember(host, token, {
|
|
648
|
+
businesses: normalizeBusinesses(previous?.businesses),
|
|
649
|
+
fetchedAt: previous?.fetchedAt ?? 0,
|
|
650
|
+
unavailableAt: now
|
|
651
|
+
});
|
|
652
|
+
return {};
|
|
653
|
+
}
|
|
654
|
+
const businesses = normalizeBusinesses(config.businesses);
|
|
655
|
+
rememberBusinessConfig(host, token, businesses, now);
|
|
656
|
+
return businesses;
|
|
657
|
+
}
|
|
658
|
+
var BusinessDisabledError = class extends PermissionError {
|
|
659
|
+
constructor(business) {
|
|
660
|
+
super(
|
|
661
|
+
`The '${business}' business is not enabled for the current environment.`,
|
|
662
|
+
"BUSINESS_NOT_ENABLED",
|
|
663
|
+
"Contact your administrator if this business should be enabled, then run: ae-cli config refresh"
|
|
664
|
+
);
|
|
665
|
+
this.business = business;
|
|
666
|
+
this.name = "BusinessDisabledError";
|
|
667
|
+
}
|
|
668
|
+
business;
|
|
669
|
+
};
|
|
670
|
+
async function readBusinessConfigForExecution(hostOverride) {
|
|
671
|
+
try {
|
|
672
|
+
if (isSandboxExecutionContext()) return {};
|
|
673
|
+
const host = hostOverride || getActiveHost();
|
|
674
|
+
if (!host) return {};
|
|
675
|
+
const token = peekCliToken(host);
|
|
676
|
+
if (!token) return {};
|
|
677
|
+
return await resolveBusinessConfig(host, token, async () => {
|
|
678
|
+
const { fetchCliConfig: fetchCliConfig2 } = await import("./compat-check-PMEGUPZU.js");
|
|
679
|
+
return fetchCliConfig2(host, token);
|
|
680
|
+
});
|
|
681
|
+
} catch {
|
|
682
|
+
return {};
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async function assertBusinessEnabled(business, hostOverride) {
|
|
686
|
+
if (business && (await readBusinessConfigForExecution(hostOverride))[business] === false) {
|
|
687
|
+
throw new BusinessDisabledError(business);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/core/business-skill-sync.ts
|
|
692
|
+
import fs6 from "fs";
|
|
693
|
+
import path6 from "path";
|
|
694
|
+
import { randomUUID } from "crypto";
|
|
695
|
+
|
|
696
|
+
// src/core/skills-tool.ts
|
|
697
|
+
import fs5 from "fs";
|
|
698
|
+
import os2 from "os";
|
|
699
|
+
import path5 from "path";
|
|
700
|
+
import { createHash } from "crypto";
|
|
701
|
+
import { createRequire, isBuiltin } from "module";
|
|
702
|
+
import { pathToFileURL } from "url";
|
|
703
|
+
import spawn2 from "cross-spawn";
|
|
704
|
+
var MANAGED_SKILLS_TOOL_VERSION = "1.5.12";
|
|
705
|
+
var TOOL_SOURCE_SHA256 = "5a2c18ddd70886d5367e39a0f88827ffd1024420e4d9db08149742386c088d2d";
|
|
706
|
+
function realSkillPath(file) {
|
|
707
|
+
try {
|
|
708
|
+
return fs5.realpathSync(file);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
if (error.code !== "ENOENT") throw error;
|
|
711
|
+
const parent = path5.dirname(file);
|
|
712
|
+
if (parent === file) throw error;
|
|
713
|
+
return path5.join(realSkillPath(parent), path5.basename(file));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function skillPathProblem(targetDirectory, skill, sourceRoot) {
|
|
717
|
+
try {
|
|
718
|
+
const target = realSkillPath(path5.resolve(targetDirectory, skill));
|
|
719
|
+
const source = fs5.realpathSync(sourceRoot);
|
|
720
|
+
const overlaps = (a, b) => a === b || a.startsWith(b + path5.sep);
|
|
721
|
+
if (overlaps(target, source) || overlaps(source, target)) return "package_source_overlap";
|
|
722
|
+
let parent = path5.resolve(targetDirectory);
|
|
723
|
+
while (true) {
|
|
724
|
+
try {
|
|
725
|
+
if (fs5.lstatSync(parent).isSymbolicLink()) return "copy_parent_linked";
|
|
726
|
+
} catch (error) {
|
|
727
|
+
if (error.code !== "ENOENT") throw error;
|
|
728
|
+
}
|
|
729
|
+
const next = path5.dirname(parent);
|
|
730
|
+
if (next === parent) return null;
|
|
731
|
+
parent = next;
|
|
732
|
+
}
|
|
733
|
+
} catch {
|
|
734
|
+
return "copy_parent_unreadable";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function skillCopyHash(directory, source = false) {
|
|
738
|
+
try {
|
|
739
|
+
if (!fs5.lstatSync(directory).isDirectory()) throw new Error("Not a regular Skill directory.");
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (error.code === "ENOENT") return null;
|
|
742
|
+
throw error;
|
|
743
|
+
}
|
|
744
|
+
const hash = createHash("sha256");
|
|
745
|
+
const visit = (relative) => {
|
|
746
|
+
for (const name of fs5.readdirSync(path5.join(directory, relative)).sort()) {
|
|
747
|
+
if (name === ".DS_Store") continue;
|
|
748
|
+
const child = path5.join(relative, name);
|
|
749
|
+
const absolute = path5.join(directory, child);
|
|
750
|
+
const stat = fs5.lstatSync(absolute);
|
|
751
|
+
if (source && (name === "metadata.json" || stat.isDirectory() && [".git", "__pycache__", "__pypackages__"].includes(name))) continue;
|
|
752
|
+
const normalized = child.split(path5.sep).join("/");
|
|
753
|
+
if (stat.isSymbolicLink()) throw new Error("Symbolic links are not managed Skill copies.");
|
|
754
|
+
if (stat.isDirectory()) {
|
|
755
|
+
hash.update(`directory:${normalized}\0`);
|
|
756
|
+
visit(child);
|
|
757
|
+
} else if (stat.isFile()) {
|
|
758
|
+
hash.update(`file:${normalized}\0${stat.size}\0`);
|
|
759
|
+
hash.update(fs5.readFileSync(absolute));
|
|
760
|
+
} else throw new Error("Unsupported Skill file type.");
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
visit("");
|
|
764
|
+
return hash.digest("hex");
|
|
765
|
+
}
|
|
766
|
+
function replaceOnce(source, before, after) {
|
|
767
|
+
if (source.split(before).length !== 2) throw new Error("Pinned Skills adapter no longer matches its upstream source.");
|
|
768
|
+
return source.replace(before, after);
|
|
769
|
+
}
|
|
770
|
+
function adaptedToolSource(source, entry) {
|
|
771
|
+
const removeStart = source.indexOf("async function removeCommand(skillNames, options) {");
|
|
772
|
+
const removeEnd = source.indexOf("\nfunction parseRemoveOptions(args)", removeStart);
|
|
773
|
+
if (removeStart < 0 || removeEnd < 0) throw new Error("Pinned Skills removal implementation is missing.");
|
|
774
|
+
let remove = source.slice(removeStart, removeEnd);
|
|
775
|
+
remove = replaceOnce(remove, "const agentResult = await detectAgent();", "const agentResult = { isAgent: false };");
|
|
776
|
+
remove = replaceOnce(remove, "const spinner = Y();", "const spinner = { start() {}, stop() {} };");
|
|
777
|
+
remove = replaceOnce(
|
|
778
|
+
remove,
|
|
779
|
+
"if (pathToCleanup === canonicalPath) continue;",
|
|
780
|
+
"if (pathToCleanup === canonicalPath || !options.__aeCliAllowedPaths.has(pathToCleanup)) continue;"
|
|
781
|
+
);
|
|
782
|
+
remove = replaceOnce(
|
|
783
|
+
remove,
|
|
784
|
+
"if (await lstat(pathToCleanup).catch(() => null)) await rm(pathToCleanup, {",
|
|
785
|
+
"if (await lstat(pathToCleanup).catch(() => null) && await options.__aeCliBeforeMutation(pathToCleanup)) await rm(pathToCleanup, {"
|
|
786
|
+
);
|
|
787
|
+
remove = replaceOnce(
|
|
788
|
+
remove,
|
|
789
|
+
"if (!isStillUsed) await rm(canonicalPath, {",
|
|
790
|
+
"if (options.__aeCliAllowedPaths.has(canonicalPath) && await options.__aeCliBeforeMutation(canonicalPath)) await rm(canonicalPath, {"
|
|
791
|
+
);
|
|
792
|
+
remove = replaceOnce(
|
|
793
|
+
remove,
|
|
794
|
+
"if (isGlobal) await removeSkillFromLock(skillName);",
|
|
795
|
+
"if (isGlobal && options.__aeCliRemoveLockEntry) await removeSkillFromLock(skillName);"
|
|
796
|
+
);
|
|
797
|
+
remove = remove.replace(/\bM\.(?:info|warn|error|message|success)\(/g, "__aeCliQuiet(").replace(/\bconsole\.log\(/g, "__aeCliQuiet(").replace(/\bSe\(/g, "__aeCliQuiet(");
|
|
798
|
+
source = source.slice(0, removeStart) + remove + source.slice(removeEnd);
|
|
799
|
+
source = replaceOnce(
|
|
800
|
+
source,
|
|
801
|
+
"function getAgentBaseDir(agentType, global, cwd) {",
|
|
802
|
+
"function getAgentBaseDir(agentType, global, cwd) {\n if (global && __aeCliTargetOverride?.agent === agentType) return __aeCliTargetOverride.directory;"
|
|
803
|
+
);
|
|
804
|
+
source = replaceOnce(
|
|
805
|
+
source,
|
|
806
|
+
"async function cleanAndCreateDirectory(path) {",
|
|
807
|
+
'async function cleanAndCreateDirectory(path) {\n if (__aeCliBeforeInstallMutation && !await __aeCliBeforeInstallMutation(path)) throw new Error("Skill copy changed before installation.");'
|
|
808
|
+
);
|
|
809
|
+
source = replaceOnce(source, "main().finally(() => flushTelemetry().then(() => process.exit(0)));", "");
|
|
810
|
+
const require2 = createRequire(entry);
|
|
811
|
+
const resolveImport = (specifier) => isBuiltin(specifier) ? specifier : pathToFileURL(specifier.startsWith(".") ? path5.resolve(path5.dirname(entry), specifier) : require2.resolve(specifier)).href;
|
|
812
|
+
source = source.replace(
|
|
813
|
+
/\bfrom (['"])([^'"]+)\1/g,
|
|
814
|
+
(_match, quote, specifier) => `from ${quote}${resolveImport(specifier)}${quote}`
|
|
815
|
+
);
|
|
816
|
+
source = source.replace(
|
|
817
|
+
/\bimport (['"])([^'"]+)\1/g,
|
|
818
|
+
(_match, quote, specifier) => `import ${quote}${resolveImport(specifier)}${quote}`
|
|
819
|
+
);
|
|
820
|
+
return source + WORKER_SOURCE;
|
|
821
|
+
}
|
|
822
|
+
var WORKER_SOURCE = String.raw`
|
|
823
|
+
import * as __aeCliFs from 'node:fs';
|
|
824
|
+
import * as __aeCliPath from 'node:path';
|
|
825
|
+
const __aeCliQuiet = () => {};
|
|
826
|
+
let __aeCliTargetOverride = null;
|
|
827
|
+
let __aeCliBeforeInstallMutation = null;
|
|
828
|
+
function __aeCliRealPath(file) {
|
|
829
|
+
try { return __aeCliFs.realpathSync(file); }
|
|
830
|
+
catch (error) {
|
|
831
|
+
if (error.code !== 'ENOENT') throw error;
|
|
832
|
+
const parent = dirname(file);
|
|
833
|
+
if (parent === file) throw error;
|
|
834
|
+
return join(__aeCliRealPath(parent), basename(file));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function __aeCliPathProblem(targetDirectory, skill, sourceRoot) {
|
|
838
|
+
try {
|
|
839
|
+
const target = __aeCliRealPath(resolve(targetDirectory, skill));
|
|
840
|
+
const source = __aeCliFs.realpathSync(sourceRoot);
|
|
841
|
+
const overlaps = (a, b) => a === b || a.startsWith(b + sep);
|
|
842
|
+
if (overlaps(target, source) || overlaps(source, target)) return 'package_source_overlap';
|
|
843
|
+
let parent = resolve(targetDirectory);
|
|
844
|
+
while (true) {
|
|
845
|
+
try { if (__aeCliFs.lstatSync(parent).isSymbolicLink()) return 'copy_parent_linked'; }
|
|
846
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
847
|
+
const next = dirname(parent);
|
|
848
|
+
if (next === parent) return null;
|
|
849
|
+
parent = next;
|
|
850
|
+
}
|
|
851
|
+
} catch { return 'copy_parent_unreadable'; }
|
|
852
|
+
}
|
|
853
|
+
function __aeCliHash(directory, source = false) {
|
|
854
|
+
try { if (!__aeCliFs.lstatSync(directory).isDirectory()) throw new Error('Unsupported Skill directory.'); }
|
|
855
|
+
catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
856
|
+
const hash = createHash('sha256');
|
|
857
|
+
function visit(relativePath) {
|
|
858
|
+
for (const name of __aeCliFs.readdirSync(join(directory, relativePath)).sort()) {
|
|
859
|
+
if (name === '.DS_Store') continue;
|
|
860
|
+
const child = join(relativePath, name), absolute = join(directory, child);
|
|
861
|
+
const info = __aeCliFs.lstatSync(absolute);
|
|
862
|
+
if (source && (name === 'metadata.json' || (info.isDirectory() && ['.git', '__pycache__', '__pypackages__'].includes(name)))) continue;
|
|
863
|
+
const normalized = child.split(sep).join('/');
|
|
864
|
+
if (info.isSymbolicLink()) throw new Error('Symbolic links are not managed Skill copies.');
|
|
865
|
+
if (info.isDirectory()) { hash.update('directory:' + normalized + '\0'); visit(child); }
|
|
866
|
+
else if (info.isFile()) { hash.update('file:' + normalized + '\0' + info.size + '\0'); hash.update(__aeCliFs.readFileSync(absolute)); }
|
|
867
|
+
else throw new Error('Unsupported Skill file type.');
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
visit('');
|
|
871
|
+
return hash.digest('hex');
|
|
872
|
+
}
|
|
873
|
+
function __aeCliLegacyLinkProblem(operation, targetPath, targets, guard) {
|
|
874
|
+
try {
|
|
875
|
+
const canonical = resolve(getCanonicalSkillsDir(true));
|
|
876
|
+
const canonicalTarget = targets.find(target => target.agent === 'universal' && target.directory === canonical);
|
|
877
|
+
const link = operation.legacy_link;
|
|
878
|
+
if (!canonicalTarget || link.target !== join(canonical, operation.skill) || targetPath === link.target
|
|
879
|
+
|| !guard.ledger_file || operation.expected_hash !== operation.source_hash) return 'legacy_link_not_managed';
|
|
880
|
+
const pathProblem = __aeCliPathProblem(canonical, operation.skill, operation.source_root);
|
|
881
|
+
if (pathProblem) return pathProblem;
|
|
882
|
+
const ledger = JSON.parse(__aeCliFs.readFileSync(guard.ledger_file, 'utf8'));
|
|
883
|
+
const owned = ledger.schema_version === 1 && ledger.copies?.[link.target];
|
|
884
|
+
if (!owned || owned.skill !== operation.skill || owned.agent !== canonicalTarget.agent
|
|
885
|
+
|| owned.hash !== operation.source_hash) return 'legacy_link_not_managed';
|
|
886
|
+
if (!__aeCliFs.lstatSync(targetPath).isSymbolicLink() || __aeCliFs.readlinkSync(targetPath) !== link.text
|
|
887
|
+
|| resolve(dirname(targetPath), link.text) !== link.target
|
|
888
|
+
|| __aeCliFs.realpathSync(targetPath) !== __aeCliFs.realpathSync(link.target)) return 'legacy_link_changed';
|
|
889
|
+
if (__aeCliHash(link.target) !== operation.source_hash) return 'legacy_link_target_modified';
|
|
890
|
+
return null;
|
|
891
|
+
} catch { return 'legacy_link_unreadable'; }
|
|
892
|
+
}
|
|
893
|
+
function __aeCliLinkDependsOn(file, dependency) {
|
|
894
|
+
const absolute = resolve(file);
|
|
895
|
+
let cursor = __aeCliPath.parse(absolute).root;
|
|
896
|
+
const pending = absolute.slice(cursor.length).split(sep);
|
|
897
|
+
let links = 0;
|
|
898
|
+
while (pending.length) {
|
|
899
|
+
const part = pending.shift();
|
|
900
|
+
if (!part || part === '.') continue;
|
|
901
|
+
if (part === '..') { cursor = dirname(cursor); continue; }
|
|
902
|
+
cursor = join(cursor, part);
|
|
903
|
+
if (cursor === dependency || cursor.startsWith(dependency + sep)) return true;
|
|
904
|
+
if (!__aeCliFs.lstatSync(cursor).isSymbolicLink()) continue;
|
|
905
|
+
if (++links > 64) throw new Error('Skill link chain is too deep.');
|
|
906
|
+
const text = __aeCliFs.readlinkSync(cursor);
|
|
907
|
+
const root = __aeCliPath.parse(text).root;
|
|
908
|
+
cursor = root || dirname(cursor);
|
|
909
|
+
// Expand a link before applying subsequent '..' components, matching filesystem lookup.
|
|
910
|
+
pending.unshift(...text.slice(root.length).split(sep));
|
|
911
|
+
}
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
async function __aeCliTargets() {
|
|
915
|
+
const detected = new Set(await detectInstalledAgents());
|
|
916
|
+
const targets = new Map();
|
|
917
|
+
// Prefer the canonical registry entry when several agents share its global directory.
|
|
918
|
+
// Keep raw registry paths for legacy migration, but create copies only at installer destinations.
|
|
919
|
+
const entries = Object.entries(agents).sort(([a], [b]) => a === 'universal' ? -1 : b === 'universal' ? 1 : a.localeCompare(b));
|
|
920
|
+
for (const [agent, config] of entries) {
|
|
921
|
+
if (config.globalSkillsDir === undefined) continue;
|
|
922
|
+
const installDirectory = resolve(getAgentBaseDir(agent, true));
|
|
923
|
+
for (const directory of new Set([installDirectory, config.globalSkillsDir])) {
|
|
924
|
+
const key = __aeCliRealPath(resolve(directory));
|
|
925
|
+
const installable = resolve(directory) === installDirectory;
|
|
926
|
+
const target = targets.get(key);
|
|
927
|
+
if (target) { target.detected ||= detected.has(agent); target.installable ||= installable; }
|
|
928
|
+
else targets.set(key, { agent, directory: resolve(directory), installable,
|
|
929
|
+
detected: detected.has(agent) || key === __aeCliRealPath(resolve(getCanonicalSkillsDir(true))) });
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return Array.from(targets.values());
|
|
933
|
+
}
|
|
934
|
+
async function __aeCliMain() {
|
|
935
|
+
// spawnSync pipes may be nonblocking; a full synchronization can exceed their buffer.
|
|
936
|
+
process.stdin.setEncoding('utf8');
|
|
937
|
+
let input = '';
|
|
938
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
939
|
+
const request = JSON.parse(input);
|
|
940
|
+
const targets = await __aeCliTargets();
|
|
941
|
+
if (request.action === 'targets') return targets;
|
|
942
|
+
const receipts = [];
|
|
943
|
+
for (const operation of request.operations) {
|
|
944
|
+
const target = targets.find(item => item.agent === operation.target.agent && item.directory === operation.target.directory);
|
|
945
|
+
if (!target || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(operation.skill)) throw new Error('Invalid Skills operation target.');
|
|
946
|
+
const targetPath = join(target.directory, operation.skill);
|
|
947
|
+
const receipt = { action: operation.action, skill: operation.skill, agent: target.agent, path: targetPath, status: 'failed' };
|
|
948
|
+
let blockedReason = null;
|
|
949
|
+
const beforeMutation = async (candidate) => {
|
|
950
|
+
try {
|
|
951
|
+
const active = JSON.parse(__aeCliFs.readFileSync(request.guard.file, 'utf8'));
|
|
952
|
+
if (active.generation !== request.guard.generation) { blockedReason = 'context_changed'; return false; }
|
|
953
|
+
const pathProblem = __aeCliPathProblem(target.directory, operation.skill, operation.source_root);
|
|
954
|
+
if (pathProblem) { blockedReason = pathProblem; return false; }
|
|
955
|
+
// Registry compatibility paths remain available for existing copies only.
|
|
956
|
+
if (operation.action === 'install' && operation.expected_hash === null && !target.installable) {
|
|
957
|
+
blockedReason = 'legacy_target_missing'; return false;
|
|
958
|
+
}
|
|
959
|
+
if (candidate !== targetPath) { blockedReason = 'copy_changed'; return false; }
|
|
960
|
+
if (__aeCliHash(operation.source, true) !== operation.source_hash) { blockedReason = 'package_changed'; return false; }
|
|
961
|
+
if (operation.legacy_link) {
|
|
962
|
+
const problem = __aeCliLegacyLinkProblem(operation, targetPath, targets, request.guard);
|
|
963
|
+
if (problem) { blockedReason = problem; return false; }
|
|
964
|
+
} else if (__aeCliHash(targetPath) !== operation.expected_hash) { blockedReason = 'copy_changed'; return false; }
|
|
965
|
+
if (operation.action === 'remove') for (const other of targets) {
|
|
966
|
+
try {
|
|
967
|
+
// Preserve custom aliases too; only exact same-name installer links are migrated.
|
|
968
|
+
for (const name of __aeCliFs.readdirSync(other.directory)) {
|
|
969
|
+
const otherPath = join(other.directory, name);
|
|
970
|
+
if (otherPath === targetPath) continue;
|
|
971
|
+
try {
|
|
972
|
+
if (__aeCliFs.lstatSync(otherPath).isSymbolicLink() && __aeCliLinkDependsOn(otherPath, targetPath)) {
|
|
973
|
+
blockedReason = 'dependent_unmanaged_link'; return false;
|
|
974
|
+
}
|
|
975
|
+
} catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
976
|
+
}
|
|
977
|
+
} catch (error) { if (error.code !== 'ENOENT') { blockedReason = 'dependent_copy_unreadable'; return false; } }
|
|
978
|
+
}
|
|
979
|
+
return true;
|
|
980
|
+
} catch { blockedReason = 'copy_unreadable'; return false; }
|
|
981
|
+
};
|
|
982
|
+
try {
|
|
983
|
+
if (!await beforeMutation(targetPath)) {
|
|
984
|
+
receipt.status = 'skipped'; receipt.reason = blockedReason;
|
|
985
|
+
} else {
|
|
986
|
+
__aeCliTargetOverride = target;
|
|
987
|
+
if (operation.action === 'install') {
|
|
988
|
+
const skill = await parseSkillMd(join(operation.source, 'SKILL.md'), { includeInternal: true });
|
|
989
|
+
if (!skill || skill.name !== operation.skill) throw new Error('Invalid package Skill.');
|
|
990
|
+
if (!await beforeMutation(targetPath)) { receipt.status = 'skipped'; receipt.reason = blockedReason; }
|
|
991
|
+
else {
|
|
992
|
+
__aeCliBeforeInstallMutation = beforeMutation;
|
|
993
|
+
const result = await installSkillForAgent(skill, target.agent, { global: true, mode: 'copy' });
|
|
994
|
+
if (blockedReason) { receipt.status = 'skipped'; receipt.reason = blockedReason; }
|
|
995
|
+
else {
|
|
996
|
+
const actualHash = __aeCliHash(targetPath);
|
|
997
|
+
if (!result.success || actualHash !== operation.source_hash) throw new Error('Skills installation verification failed.');
|
|
998
|
+
receipt.status = 'success'; receipt.hash = actualHash;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
} else {
|
|
1002
|
+
await removeCommand([operation.skill], { global: true, yes: true, agent: [target.agent],
|
|
1003
|
+
__aeCliAllowedPaths: new Set([targetPath]), __aeCliBeforeMutation: beforeMutation });
|
|
1004
|
+
if (blockedReason) { receipt.status = 'skipped'; receipt.reason = blockedReason; }
|
|
1005
|
+
else {
|
|
1006
|
+
let exists = true;
|
|
1007
|
+
try { __aeCliFs.lstatSync(targetPath); }
|
|
1008
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; exists = false; }
|
|
1009
|
+
if (exists) throw new Error('Skills removal verification failed.');
|
|
1010
|
+
receipt.status = 'success';
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
} catch { receipt.status = blockedReason ? 'skipped' : 'failed'; receipt.reason = blockedReason || 'tool_operation_failed'; }
|
|
1015
|
+
finally { __aeCliTargetOverride = null; __aeCliBeforeInstallMutation = null; }
|
|
1016
|
+
if (receipt.status === 'success' && operation.legacy_link) receipt.reason = operation.action === 'install'
|
|
1017
|
+
? 'legacy_link_materialized' : 'legacy_link_removed';
|
|
1018
|
+
receipts.push(receipt);
|
|
1019
|
+
}
|
|
1020
|
+
return receipts;
|
|
1021
|
+
}
|
|
1022
|
+
try { process.stdout.write(JSON.stringify({ ok: true, data: await __aeCliMain() })); }
|
|
1023
|
+
catch { process.stdout.write(JSON.stringify({ ok: false })); process.exitCode = 1; }
|
|
1024
|
+
`;
|
|
1025
|
+
function createPinnedSkillsTool(options = {}) {
|
|
1026
|
+
const require2 = createRequire(import.meta.url);
|
|
1027
|
+
const packageFile = options.entry ? path5.join(path5.dirname(options.entry), "..", "package.json") : require2.resolve("skills/package.json");
|
|
1028
|
+
const entry = options.entry ?? path5.join(path5.dirname(packageFile), "dist", "cli.mjs");
|
|
1029
|
+
const metadata = JSON.parse(fs5.readFileSync(packageFile, "utf8"));
|
|
1030
|
+
const source = fs5.readFileSync(entry, "utf8");
|
|
1031
|
+
if (metadata.version !== MANAGED_SKILLS_TOOL_VERSION || createHash("sha256").update(source).digest("hex") !== TOOL_SOURCE_SHA256) {
|
|
1032
|
+
throw new Error("Managed Skill synchronization requires the verified skills@1.5.12 package.");
|
|
1033
|
+
}
|
|
1034
|
+
const script = adaptedToolSource(source, entry);
|
|
1035
|
+
const run = (request) => {
|
|
1036
|
+
const directory = fs5.mkdtempSync(path5.join(os2.tmpdir(), "ae-cli-skills-tool-"));
|
|
1037
|
+
const worker = path5.join(directory, "adapter.mjs");
|
|
1038
|
+
try {
|
|
1039
|
+
fs5.writeFileSync(worker, script, { mode: 384 });
|
|
1040
|
+
const result2 = spawn2.sync(process.execPath, [worker], {
|
|
1041
|
+
input: JSON.stringify(request),
|
|
1042
|
+
encoding: "utf8",
|
|
1043
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
1044
|
+
timeout: options.timeoutMs ?? 12e4,
|
|
1045
|
+
windowsHide: true,
|
|
1046
|
+
cwd: options.cwd ?? directory,
|
|
1047
|
+
env: { ...process.env, ...options.env, DISABLE_TELEMETRY: "1", NO_COLOR: "1", AI_AGENT: "ae-cli" }
|
|
1048
|
+
});
|
|
1049
|
+
if (result2.error || result2.signal || result2.status !== 0) throw new Error("Skills adapter process failed.");
|
|
1050
|
+
const response = JSON.parse(result2.stdout);
|
|
1051
|
+
if (response.ok !== true) throw new Error("Skills adapter returned an invalid response.");
|
|
1052
|
+
return response.data;
|
|
1053
|
+
} catch {
|
|
1054
|
+
throw new Error("Skills adapter failed. Check the pinned package and directory permissions.");
|
|
1055
|
+
} finally {
|
|
1056
|
+
fs5.rmSync(directory, { recursive: true, force: true });
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
return {
|
|
1060
|
+
targets: () => run({ action: "targets" }),
|
|
1061
|
+
apply: (operations, guard) => run({ action: "apply", operations, guard })
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// src/core/business-skill-sync.ts
|
|
1066
|
+
var CONTEXT_FILE = "business-skill-context.json";
|
|
1067
|
+
var LEDGER_FILE = "business-skills.json";
|
|
1068
|
+
var LOCK_FILE = "business-skills.lock";
|
|
1069
|
+
function writeJson(file, value) {
|
|
1070
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
1071
|
+
try {
|
|
1072
|
+
fs6.writeFileSync(temporary, JSON.stringify(value, null, 2) + "\n", { mode: 384, flag: "wx" });
|
|
1073
|
+
fs6.renameSync(temporary, file);
|
|
1074
|
+
} finally {
|
|
1075
|
+
try {
|
|
1076
|
+
fs6.unlinkSync(temporary);
|
|
1077
|
+
} catch {
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function contextIdentity(options) {
|
|
1082
|
+
const fingerprint = options.tokenFingerprint ?? "";
|
|
1083
|
+
if (fingerprint && !/^[a-f0-9]{64}$/.test(fingerprint)) throw new Error("Expected a SHA-256 token fingerprint.");
|
|
1084
|
+
let host = "";
|
|
1085
|
+
if (options.host) {
|
|
1086
|
+
const url = new URL(options.host);
|
|
1087
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
1088
|
+
throw new Error("Invalid active host.");
|
|
1089
|
+
}
|
|
1090
|
+
host = url.href.replace(/\/+$/, "");
|
|
1091
|
+
}
|
|
1092
|
+
return { host, token_fingerprint: fingerprint };
|
|
1093
|
+
}
|
|
1094
|
+
function reserveBusinessSkillContext(options) {
|
|
1095
|
+
const directory = options.stateDirectory ?? getConfigDir();
|
|
1096
|
+
fs6.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
1097
|
+
const generation = randomUUID();
|
|
1098
|
+
writeJson(path6.join(directory, CONTEXT_FILE), { ...contextIdentity(options), generation });
|
|
1099
|
+
return generation;
|
|
1100
|
+
}
|
|
1101
|
+
function currentContext(options, file, generation) {
|
|
1102
|
+
try {
|
|
1103
|
+
if (options.isCurrentContext && !options.isCurrentContext()) return false;
|
|
1104
|
+
const active = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
1105
|
+
const identity = contextIdentity(options);
|
|
1106
|
+
return active.generation === generation && active.host === identity.host && active.token_fingerprint === identity.token_fingerprint;
|
|
1107
|
+
} catch {
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function isBusinessSkillContextCurrent(options) {
|
|
1112
|
+
return currentContext(options, path6.join(options.stateDirectory ?? getConfigDir(), CONTEXT_FILE), options.generation);
|
|
1113
|
+
}
|
|
1114
|
+
function takeLock(file) {
|
|
1115
|
+
const owner = randomUUID();
|
|
1116
|
+
const ownerName = `owner-${process.pid}-${owner}.json`;
|
|
1117
|
+
const candidate = fs6.mkdtempSync(`${file}.candidate-`);
|
|
1118
|
+
const alive = (pid) => {
|
|
1119
|
+
try {
|
|
1120
|
+
process.kill(pid, 0);
|
|
1121
|
+
return true;
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
return error.code !== "ESRCH";
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
try {
|
|
1127
|
+
fs6.chmodSync(candidate, 448);
|
|
1128
|
+
fs6.writeFileSync(path6.join(candidate, ownerName), JSON.stringify({ pid: process.pid, owner }), { mode: 384 });
|
|
1129
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
1130
|
+
try {
|
|
1131
|
+
fs6.renameSync(candidate, file);
|
|
1132
|
+
try {
|
|
1133
|
+
fs6.unlinkSync(`${file}.reclaim`);
|
|
1134
|
+
} catch {
|
|
1135
|
+
}
|
|
1136
|
+
return () => {
|
|
1137
|
+
try {
|
|
1138
|
+
fs6.unlinkSync(path6.join(file, ownerName));
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
try {
|
|
1142
|
+
fs6.rmdirSync(file);
|
|
1143
|
+
} catch {
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
let stat;
|
|
1148
|
+
try {
|
|
1149
|
+
stat = fs6.lstatSync(file);
|
|
1150
|
+
} catch (cause) {
|
|
1151
|
+
if (cause.code === "ENOENT") continue;
|
|
1152
|
+
throw error;
|
|
1153
|
+
}
|
|
1154
|
+
if (stat.isSymbolicLink()) return null;
|
|
1155
|
+
if (stat.isFile()) {
|
|
1156
|
+
let pid;
|
|
1157
|
+
try {
|
|
1158
|
+
pid = JSON.parse(fs6.readFileSync(file, "utf8")).pid;
|
|
1159
|
+
} catch {
|
|
1160
|
+
}
|
|
1161
|
+
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && alive(pid)) return null;
|
|
1162
|
+
try {
|
|
1163
|
+
fs6.unlinkSync(file);
|
|
1164
|
+
} catch (cause) {
|
|
1165
|
+
if (!["ENOENT", "EISDIR", "EPERM"].includes(cause.code ?? "")) throw cause;
|
|
1166
|
+
}
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (!stat.isDirectory()) return null;
|
|
1170
|
+
let names;
|
|
1171
|
+
try {
|
|
1172
|
+
names = fs6.readdirSync(file);
|
|
1173
|
+
} catch (cause) {
|
|
1174
|
+
if (cause.code === "ENOENT") continue;
|
|
1175
|
+
throw cause;
|
|
1176
|
+
}
|
|
1177
|
+
for (const name of names) {
|
|
1178
|
+
const match = /^owner-(\d+)-[a-f0-9-]{36}\.json$/.exec(name);
|
|
1179
|
+
if (!match || alive(Number(match[1]))) return null;
|
|
1180
|
+
}
|
|
1181
|
+
for (const name of names) {
|
|
1182
|
+
try {
|
|
1183
|
+
fs6.unlinkSync(path6.join(file, name));
|
|
1184
|
+
} catch (cause) {
|
|
1185
|
+
if (cause.code !== "ENOENT") throw cause;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
try {
|
|
1189
|
+
fs6.rmdirSync(file);
|
|
1190
|
+
} catch (cause) {
|
|
1191
|
+
if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(cause.code ?? "")) throw cause;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return null;
|
|
1196
|
+
} finally {
|
|
1197
|
+
fs6.rmSync(candidate, { recursive: true, force: true });
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function readLedger(file) {
|
|
1201
|
+
let value;
|
|
1202
|
+
try {
|
|
1203
|
+
value = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
if (error.code === "ENOENT") return { schema_version: 1, copies: {} };
|
|
1206
|
+
throw new Error("Managed Skill ledger is unreadable.");
|
|
1207
|
+
}
|
|
1208
|
+
if (value.schema_version !== 1 || !value.copies || typeof value.copies !== "object" || Array.isArray(value.copies)) {
|
|
1209
|
+
throw new Error("Managed Skill ledger has an unsupported format.");
|
|
1210
|
+
}
|
|
1211
|
+
for (const [filePath, copy] of Object.entries(value.copies)) {
|
|
1212
|
+
if (!path6.isAbsolute(filePath) || !copy || typeof copy.skill !== "string" || typeof copy.agent !== "string" || !/^[a-f0-9]{64}$/.test(copy.hash) || typeof copy.package_version !== "string") throw new Error("Managed Skill ledger has an invalid copy.");
|
|
1213
|
+
}
|
|
1214
|
+
return value;
|
|
1215
|
+
}
|
|
1216
|
+
function result(skill, target, reason) {
|
|
1217
|
+
return { skill, agent: target.agent, path: path6.join(target.directory, skill), ...reason ? { reason } : {} };
|
|
1218
|
+
}
|
|
1219
|
+
function legacySkillLink(file, skill, sourceRoot, sourceHash, targets, ledger) {
|
|
1220
|
+
const canonical = targets.find((target2) => target2.agent === "universal");
|
|
1221
|
+
if (!canonical) return;
|
|
1222
|
+
const target = path6.join(canonical.directory, skill);
|
|
1223
|
+
const managed = ledger.copies[target];
|
|
1224
|
+
if (!managed || managed.skill !== skill || managed.agent !== canonical.agent || managed.hash !== sourceHash || skillPathProblem(canonical.directory, skill, sourceRoot)) return;
|
|
1225
|
+
try {
|
|
1226
|
+
if (!fs6.lstatSync(file).isSymbolicLink() || skillCopyHash(target) !== sourceHash) return;
|
|
1227
|
+
const text = fs6.readlinkSync(file);
|
|
1228
|
+
if (path6.resolve(path6.dirname(file), text) !== target || fs6.realpathSync(file) !== fs6.realpathSync(target)) return;
|
|
1229
|
+
return { target, text };
|
|
1230
|
+
} catch {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function syncBusinessSkills(options) {
|
|
1235
|
+
const report = {
|
|
1236
|
+
status: "failed",
|
|
1237
|
+
adopted: [],
|
|
1238
|
+
installed: [],
|
|
1239
|
+
removed: [],
|
|
1240
|
+
skipped: [],
|
|
1241
|
+
failed: [],
|
|
1242
|
+
managed_count: 0
|
|
1243
|
+
};
|
|
1244
|
+
const directory = options.stateDirectory ?? getConfigDir();
|
|
1245
|
+
const contextFile = path6.join(directory, CONTEXT_FILE);
|
|
1246
|
+
let unlock = null;
|
|
1247
|
+
try {
|
|
1248
|
+
if (options.isCurrentContext && !options.isCurrentContext()) {
|
|
1249
|
+
report.status = "stale";
|
|
1250
|
+
return report;
|
|
1251
|
+
}
|
|
1252
|
+
fs6.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
1253
|
+
const generation = options.generation ?? reserveBusinessSkillContext(options);
|
|
1254
|
+
if (!currentContext(options, contextFile, generation)) {
|
|
1255
|
+
report.status = "stale";
|
|
1256
|
+
return report;
|
|
1257
|
+
}
|
|
1258
|
+
unlock = takeLock(path6.join(directory, LOCK_FILE));
|
|
1259
|
+
if (!unlock) {
|
|
1260
|
+
report.status = "busy";
|
|
1261
|
+
return report;
|
|
1262
|
+
}
|
|
1263
|
+
if (!currentContext(options, contextFile, generation)) {
|
|
1264
|
+
report.status = "stale";
|
|
1265
|
+
return report;
|
|
1266
|
+
}
|
|
1267
|
+
const ledgerFile = path6.join(directory, LEDGER_FILE);
|
|
1268
|
+
const ledger = readLedger(ledgerFile);
|
|
1269
|
+
const tool = options.tool ?? createPinnedSkillsTool({ env: options.env });
|
|
1270
|
+
const targets = tool.targets();
|
|
1271
|
+
const skills = readBundledSkills(options.skillsDirectory);
|
|
1272
|
+
const packageVersion = options.packageVersion ?? "unknown";
|
|
1273
|
+
const operations = [];
|
|
1274
|
+
for (const skill of skills) {
|
|
1275
|
+
const source = path6.join(options.skillsDirectory, skill.name);
|
|
1276
|
+
const sourceHash = skillCopyHash(source, true);
|
|
1277
|
+
if (!sourceHash) throw new Error("Complete package Skill source is missing.");
|
|
1278
|
+
const state = getSkillBusinessState(skill.name, options.businesses);
|
|
1279
|
+
if (options.mode !== "adopt" && state === void 0) continue;
|
|
1280
|
+
for (const target of targets) {
|
|
1281
|
+
const item = result(skill.name, target);
|
|
1282
|
+
const pathProblem = skillPathProblem(target.directory, skill.name, options.skillsDirectory);
|
|
1283
|
+
if (pathProblem) {
|
|
1284
|
+
report.skipped.push({ ...item, reason: pathProblem });
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
let actualHash;
|
|
1288
|
+
let legacyLink;
|
|
1289
|
+
try {
|
|
1290
|
+
actualHash = skillCopyHash(item.path);
|
|
1291
|
+
} catch {
|
|
1292
|
+
if (options.mode !== "adopt") legacyLink = legacySkillLink(
|
|
1293
|
+
item.path,
|
|
1294
|
+
skill.name,
|
|
1295
|
+
options.skillsDirectory,
|
|
1296
|
+
sourceHash,
|
|
1297
|
+
targets,
|
|
1298
|
+
ledger
|
|
1299
|
+
);
|
|
1300
|
+
if (!legacyLink) {
|
|
1301
|
+
report.skipped.push({ ...item, reason: "copy_unreadable_or_linked" });
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
actualHash = sourceHash;
|
|
1305
|
+
}
|
|
1306
|
+
const managed = ledger.copies[item.path];
|
|
1307
|
+
if (options.mode === "adopt") {
|
|
1308
|
+
if (actualHash === null) {
|
|
1309
|
+
if (managed) delete ledger.copies[item.path];
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
if (actualHash !== sourceHash) {
|
|
1313
|
+
report.skipped.push({ ...item, reason: "copy_does_not_match_package" });
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
ledger.copies[item.path] = { skill: skill.name, agent: target.agent, hash: actualHash, package_version: packageVersion };
|
|
1317
|
+
report.adopted.push(item);
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (!legacyLink && actualHash !== null && (!managed || managed.skill !== skill.name || actualHash !== managed.hash)) {
|
|
1321
|
+
report.skipped.push({ ...item, reason: managed ? "copy_modified" : "copy_not_managed" });
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
if (actualHash === null && state === false) {
|
|
1325
|
+
if (managed) delete ledger.copies[item.path];
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
if (state === true && actualHash === sourceHash && !legacyLink) continue;
|
|
1329
|
+
if (state === true && actualHash === null && !target.installable) {
|
|
1330
|
+
if (managed) delete ledger.copies[item.path];
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
if (state === true && actualHash === null && !target.detected && !managed && !fs6.existsSync(target.directory)) continue;
|
|
1334
|
+
operations.push({
|
|
1335
|
+
action: state ? "install" : "remove",
|
|
1336
|
+
skill: skill.name,
|
|
1337
|
+
source,
|
|
1338
|
+
source_root: options.skillsDirectory,
|
|
1339
|
+
target,
|
|
1340
|
+
expected_hash: actualHash,
|
|
1341
|
+
source_hash: sourceHash,
|
|
1342
|
+
...legacyLink ? { legacy_link: legacyLink } : {}
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (!currentContext(options, contextFile, generation)) {
|
|
1347
|
+
report.status = "stale";
|
|
1348
|
+
return report;
|
|
1349
|
+
}
|
|
1350
|
+
if (options.mode === "adopt") {
|
|
1351
|
+
writeJson(ledgerFile, ledger);
|
|
1352
|
+
} else if (operations.length) {
|
|
1353
|
+
operations.sort((a, b) => Number(Boolean(b.legacy_link)) - Number(Boolean(a.legacy_link)));
|
|
1354
|
+
const receipts = tool.apply(operations, { file: contextFile, generation, ledger_file: ledgerFile });
|
|
1355
|
+
if (receipts.length !== operations.length) throw new Error("Skills adapter returned incomplete receipts.");
|
|
1356
|
+
for (let index = 0; index < operations.length; index++) {
|
|
1357
|
+
const operation = operations[index];
|
|
1358
|
+
const receipt = receipts[index];
|
|
1359
|
+
const item = result(operation.skill, operation.target, receipt.reason);
|
|
1360
|
+
if (receipt.path !== item.path || receipt.agent !== item.agent || receipt.skill !== item.skill || receipt.action !== operation.action) throw new Error("Skills adapter returned mismatched receipts.");
|
|
1361
|
+
if (receipt.status === "skipped") report.skipped.push(item);
|
|
1362
|
+
else if (receipt.status !== "success") report.failed.push(item);
|
|
1363
|
+
else if (operation.action === "remove") {
|
|
1364
|
+
if (skillCopyHash(item.path) !== null) {
|
|
1365
|
+
report.failed.push({ ...item, reason: "removal_not_verified" });
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
delete ledger.copies[item.path];
|
|
1369
|
+
report.removed.push(item);
|
|
1370
|
+
} else {
|
|
1371
|
+
const actualHash = skillCopyHash(item.path);
|
|
1372
|
+
if (actualHash !== operation.source_hash || receipt.hash !== actualHash) {
|
|
1373
|
+
report.failed.push({ ...item, reason: "installation_not_verified" });
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
ledger.copies[item.path] = { skill: operation.skill, agent: operation.target.agent, hash: actualHash, package_version: packageVersion };
|
|
1377
|
+
report.installed.push(item);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
writeJson(ledgerFile, ledger);
|
|
1381
|
+
} else writeJson(ledgerFile, ledger);
|
|
1382
|
+
report.managed_count = Object.keys(ledger.copies).length;
|
|
1383
|
+
report.status = !currentContext(options, contextFile, generation) ? "stale" : report.skipped.length || report.failed.length ? "partial" : "success";
|
|
1384
|
+
return report;
|
|
1385
|
+
} catch {
|
|
1386
|
+
report.status = report.installed.length || report.removed.length || report.adopted.length ? "partial" : "failed";
|
|
1387
|
+
return report;
|
|
1388
|
+
} finally {
|
|
1389
|
+
unlock?.();
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/core/installation.ts
|
|
1394
|
+
function distribution(packageName) {
|
|
1395
|
+
if (packageName === "@thinkingai/ae-cli") return { registry: "https://registry.npmjs.org", repository: "ThinkingAIAgenticEngine/ae-cli" };
|
|
1396
|
+
if (packageName === "@tant/ae-cli") return { registry: "https://npm.thinkingdata.cn:3443", repository: "http://10.27.249.150:8888/te-ai/te-cli.git" };
|
|
1397
|
+
throw new Error("Unsupported CLI package. Use an official AE CLI distribution.");
|
|
1398
|
+
}
|
|
1399
|
+
function newInstallationReport(packageName, version, dryRun = false) {
|
|
1400
|
+
return {
|
|
1401
|
+
ok: false,
|
|
1402
|
+
dry_run: dryRun,
|
|
1403
|
+
cli: { package: packageName, version, path: null, entrypoint: null, verified: false, action: "pending", warnings: [] },
|
|
1404
|
+
skills: {
|
|
1405
|
+
directory: globalSkillsDirectory(),
|
|
1406
|
+
expected_count: 0,
|
|
1407
|
+
installed_count: 0,
|
|
1408
|
+
missing: [],
|
|
1409
|
+
source: null,
|
|
1410
|
+
version,
|
|
1411
|
+
status: "pending",
|
|
1412
|
+
failures: [],
|
|
1413
|
+
attempts: []
|
|
1414
|
+
},
|
|
1415
|
+
auth: { host: null, authenticated: null, warnings: [] },
|
|
1416
|
+
next_steps: []
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function failInstallation(report, stage, message, cause = "validation") {
|
|
1420
|
+
report.ok = false;
|
|
1421
|
+
report.error = { type: "config", code: "AE_CLI_INSTALL_FAILED", message, stage, cause };
|
|
1422
|
+
if (!report.next_steps.length) report.next_steps.push("Fix the reported problem and rerun the same install or update command.");
|
|
1423
|
+
return report;
|
|
1424
|
+
}
|
|
1425
|
+
function installPlan(packageName, version, root = "<npm-root>") {
|
|
1426
|
+
const config = distribution(packageName);
|
|
1427
|
+
const target = exactVersion(version);
|
|
1428
|
+
const executable = path7.join(root, packageName, "bin", "ae-cli.js");
|
|
1429
|
+
return [
|
|
1430
|
+
`npm install -g ${packageName}@${target} --registry=${config.registry}`,
|
|
1431
|
+
`node ${JSON.stringify(executable)} install`
|
|
1432
|
+
];
|
|
1433
|
+
}
|
|
1434
|
+
function currentSkillIdentity() {
|
|
1435
|
+
const host = getActiveHost();
|
|
1436
|
+
const token = host ? peekCliToken(host) : void 0;
|
|
1437
|
+
return { host, tokenFingerprint: token ? crypto2.createHash("sha256").update(token).digest("hex") : "" };
|
|
1438
|
+
}
|
|
1439
|
+
function skipGlobalSkillReason() {
|
|
1440
|
+
try {
|
|
1441
|
+
if (canManageGlobalSkills()) return void 0;
|
|
1442
|
+
} catch {
|
|
1443
|
+
}
|
|
1444
|
+
return "An external-token or AE sandbox execution context is selected; global Skills were left unchanged.";
|
|
1445
|
+
}
|
|
1446
|
+
function readPackage(root) {
|
|
1447
|
+
try {
|
|
1448
|
+
return JSON.parse(fs7.readFileSync(path7.join(root, "package.json"), "utf8"));
|
|
1449
|
+
} catch {
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
function linked(root) {
|
|
1454
|
+
try {
|
|
1455
|
+
return fs7.lstatSync(root).isSymbolicLink();
|
|
1456
|
+
} catch {
|
|
1457
|
+
return false;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
function cliBinBelongsToPackage(bin, packageRoot, platform) {
|
|
1461
|
+
const entry = fs7.realpathSync(path7.join(packageRoot, "bin", "ae-cli.js"));
|
|
1462
|
+
if (platform !== "win32") return fs7.realpathSync(bin) === entry;
|
|
1463
|
+
const shim = fs7.readFileSync(bin, "utf8");
|
|
1464
|
+
const targets = [...shim.matchAll(/"%(?:dp0%|~dp0)[\\/]([^"\r\n]+)"/gi)];
|
|
1465
|
+
return targets.some(([, relative]) => {
|
|
1466
|
+
try {
|
|
1467
|
+
const target = path7.resolve(path7.dirname(bin), relative.replace(/\\/g, path7.sep));
|
|
1468
|
+
return fs7.realpathSync(target).toLowerCase() === entry.toLowerCase();
|
|
1469
|
+
} catch {
|
|
1470
|
+
return false;
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
function runInstallation(options = {}) {
|
|
1475
|
+
const local = getLocalCliPackageInfo();
|
|
1476
|
+
const packageName = options.packageName ?? local.name;
|
|
1477
|
+
const report = newInstallationReport(packageName, "", Boolean(options.dryRun));
|
|
1478
|
+
const runner = options.runner ?? defaultInstallRunner;
|
|
1479
|
+
const progress = options.progress ?? (() => {
|
|
1480
|
+
});
|
|
1481
|
+
const platform = options.platform ?? process.platform;
|
|
1482
|
+
report.skills.directory = options.skillsDirectory ?? globalSkillsDirectory();
|
|
1483
|
+
let stage = "environment";
|
|
1484
|
+
try {
|
|
1485
|
+
const target = exactVersion(options.target ?? local.version);
|
|
1486
|
+
report.cli.version = target;
|
|
1487
|
+
report.skills.version = target;
|
|
1488
|
+
const config = distribution(packageName);
|
|
1489
|
+
report.auth.host = installHost(options.host);
|
|
1490
|
+
const major = Number((options.nodeVersion ?? process.versions.node).split(".")[0]);
|
|
1491
|
+
if (!Number.isInteger(major) || major < 20) return failInstallation(report, stage, "AE CLI requires Node.js 20 or later. Select an existing supported Node runtime and rerun.");
|
|
1492
|
+
if (options.dryRun) {
|
|
1493
|
+
report.ok = true;
|
|
1494
|
+
report.commands = installPlan(packageName, target);
|
|
1495
|
+
report.next_steps.push("Dry run only; CLI, global Skills, and authentication have not been verified.");
|
|
1496
|
+
return report;
|
|
1497
|
+
}
|
|
1498
|
+
const npm = resolveNodeTool("npm", process.execPath, platform);
|
|
1499
|
+
progress("[ae-cli] [1/4] Checking Node.js, npm, and npx...");
|
|
1500
|
+
for (const tool of [npm, resolveNodeTool("npx", process.execPath, platform)]) {
|
|
1501
|
+
const result2 = runner(tool, ["--version"], 15e3);
|
|
1502
|
+
if (!commandSucceeded(result2)) return failInstallation(report, stage, "npm and npx must be available alongside the selected Node.js runtime.", classifyInstallFailure(result2));
|
|
1503
|
+
}
|
|
1504
|
+
stage = "package_validation";
|
|
1505
|
+
const rootResult = runner(npm, ["root", "-g"], 15e3);
|
|
1506
|
+
const prefixResult = runner(npm, ["prefix", "-g"], 15e3);
|
|
1507
|
+
const globalRoot = rootResult.stdout.trim();
|
|
1508
|
+
const prefix = prefixResult.stdout.trim();
|
|
1509
|
+
if (!commandSucceeded(rootResult) || !commandSucceeded(prefixResult) || !path7.isAbsolute(globalRoot) || !path7.isAbsolute(prefix) || /[\r\n]/.test(globalRoot + prefix)) return failInstallation(report, stage, "Cannot resolve npm global root and prefix. Check npm configuration.");
|
|
1510
|
+
const packageRoot = path7.join(globalRoot, packageName);
|
|
1511
|
+
const bin = platform === "win32" ? path7.join(prefix, "ae-cli.cmd") : path7.join(prefix, "bin", "ae-cli");
|
|
1512
|
+
report.cli.path = bin;
|
|
1513
|
+
const env = options.env ?? process.env;
|
|
1514
|
+
const installedBefore = readPackage(packageRoot);
|
|
1515
|
+
if (linked(packageRoot) && !options.skipCliInstall) return failInstallation(report, stage, "The global CLI is npm-linked to source. Preserve the link; use a separate npm prefix for a packaged installation.");
|
|
1516
|
+
const syncSkills = options.syncSkills ?? syncBusinessSkills;
|
|
1517
|
+
const skipSkills = skipGlobalSkillReason();
|
|
1518
|
+
let context;
|
|
1519
|
+
if (!skipSkills) {
|
|
1520
|
+
const stateDirectory = options.skillStateDirectory ?? getConfigDir();
|
|
1521
|
+
const identity = options.skillContext ?? currentSkillIdentity();
|
|
1522
|
+
const isCurrentContext = () => {
|
|
1523
|
+
if (skipGlobalSkillReason()) return false;
|
|
1524
|
+
if (options.skillContext) return options.skillContext.isCurrentContext?.() ?? true;
|
|
1525
|
+
try {
|
|
1526
|
+
const current = currentSkillIdentity();
|
|
1527
|
+
return current.host === identity.host && current.tokenFingerprint === identity.tokenFingerprint;
|
|
1528
|
+
} catch {
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
const generation = reserveBusinessSkillContext({ ...identity, stateDirectory });
|
|
1533
|
+
context = { ...identity, stateDirectory, generation, isCurrentContext };
|
|
1534
|
+
if (!fs7.existsSync(path7.join(stateDirectory, "business-skills.json")) && installedBefore?.name === packageName) {
|
|
1535
|
+
const previousSkills = path7.join(packageRoot, "skills");
|
|
1536
|
+
try {
|
|
1537
|
+
readBundledSkills(previousSkills);
|
|
1538
|
+
syncSkills({ ...context, skillsDirectory: previousSkills, businesses: {}, mode: "adopt", packageVersion: installedBefore.version });
|
|
1539
|
+
} catch {
|
|
1540
|
+
report.next_steps.push("Existing Skills could not be adopted from the previous package; unmatched copies will be preserved.");
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (!isBusinessSkillContextCurrent(context)) {
|
|
1544
|
+
return failInstallation(report, "skills_local", "The active environment changed before installation. Re-run install or update for the current environment.");
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (options.skipCliInstall || installedBefore?.name === packageName && installedBefore.version === target && fs7.existsSync(bin)) {
|
|
1548
|
+
report.cli.action = "skipped";
|
|
1549
|
+
progress("[ae-cli] [2/4] Verifying the existing CLI...");
|
|
1550
|
+
} else {
|
|
1551
|
+
stage = "npm_install";
|
|
1552
|
+
progress(`[ae-cli] [2/4] Installing ${packageName}@${target}...`);
|
|
1553
|
+
const result2 = runner(npm, ["install", "-g", `${packageName}@${target}`, "--registry", config.registry], INSTALL_TIMEOUT_MS);
|
|
1554
|
+
if (!commandSucceeded(result2)) return failInstallation(report, stage, "npm installation failed. Check network access and a user-writable npm prefix; do not use sudo.", classifyInstallFailure(result2));
|
|
1555
|
+
report.cli.action = "installed";
|
|
1556
|
+
}
|
|
1557
|
+
stage = "package_validation";
|
|
1558
|
+
const installed = readPackage(packageRoot);
|
|
1559
|
+
if (installed?.name !== packageName || installed.version !== target) return failInstallation(report, stage, "Installed CLI package identity or version does not match the requested distribution.");
|
|
1560
|
+
if (!fs7.existsSync(bin)) return failInstallation(report, stage, "npm did not create the global ae-cli executable. Enable npm bin-links, reinstall, and add the global executable directory to PATH.");
|
|
1561
|
+
if (!cliBinBelongsToPackage(bin, packageRoot, platform)) return failInstallation(report, stage, "The global executable does not belong to the installed CLI package.");
|
|
1562
|
+
const versionResult = runner(bin, ["--version"], 15e3);
|
|
1563
|
+
if (!commandSucceeded(versionResult) || versionResult.stdout.trim() !== target) return failInstallation(report, stage, "ae-cli --version did not return the requested version. Refresh the shell command cache and check npm bin links.");
|
|
1564
|
+
report.cli.entrypoint = fs7.realpathSync(path7.join(packageRoot, "bin", "ae-cli.js"));
|
|
1565
|
+
report.cli.verified = true;
|
|
1566
|
+
const pathKey = platform === "win32" ? Object.keys(env).find((key) => key.toLowerCase() === "path") : "PATH";
|
|
1567
|
+
const installedPathBin = (env[pathKey ?? "PATH"] ?? "").split(platform === "win32" ? ";" : path7.delimiter).filter(Boolean).map((dir) => path7.join(dir.replace(/^"(.*)"$/, "$1"), platform === "win32" ? "ae-cli.cmd" : "ae-cli")).find((file) => fs7.existsSync(file) && !file.includes(`${path7.sep}_npx${path7.sep}`));
|
|
1568
|
+
if (!installedPathBin || fs7.realpathSync(installedPathBin) !== fs7.realpathSync(bin)) {
|
|
1569
|
+
report.cli.warnings.push(`Add ${path7.dirname(bin)} to the front of PATH and refresh the shell command cache. The absolute executable was verified.`);
|
|
1570
|
+
report.next_steps.push(`Use the verified executable at ${bin}, or correct PATH before invoking ae-cli.`);
|
|
1571
|
+
}
|
|
1572
|
+
if (skipSkills) {
|
|
1573
|
+
report.skills.status = "skipped";
|
|
1574
|
+
report.next_steps.push(skipSkills);
|
|
1575
|
+
progress(`[ae-cli] [3/4] ${skipSkills}`);
|
|
1576
|
+
} else if (context) {
|
|
1577
|
+
stage = "skills_local";
|
|
1578
|
+
const skillsPath = path7.join(packageRoot, "skills");
|
|
1579
|
+
const skills = readBundledSkills(skillsPath);
|
|
1580
|
+
if (!isBusinessSkillContextCurrent(context)) {
|
|
1581
|
+
return failInstallation(report, stage, "The active environment changed during CLI installation. Re-run install or update to synchronize its Skills.");
|
|
1582
|
+
}
|
|
1583
|
+
const token = context.host ? peekCliToken(context.host) : void 0;
|
|
1584
|
+
const knownBusinesses = options.businesses ?? (token ? readBusinessConfig(context.host, token) : void 0);
|
|
1585
|
+
const businesses = knownBusinesses ?? Object.fromEntries(OPTIONAL_BUSINESSES.map((business) => [business, true]));
|
|
1586
|
+
const expected = skills.filter((skill) => getSkillBusinessState(skill.name, businesses) === true);
|
|
1587
|
+
report.skills.expected_count = expected.length;
|
|
1588
|
+
report.skills.source = "installed-package";
|
|
1589
|
+
progress("[ae-cli] [3/4] Synchronizing managed Skills for the current environment...");
|
|
1590
|
+
const synchronized = syncSkills({ ...context, skillsDirectory: skillsPath, businesses, packageVersion: target });
|
|
1591
|
+
report.skills.synchronization = synchronized;
|
|
1592
|
+
report.skills.failures = verifyGlobalSkills(expected, report.skills.directory);
|
|
1593
|
+
report.skills.missing = report.skills.failures.map((failure) => failure.skill);
|
|
1594
|
+
report.skills.installed_count = expected.length - report.skills.missing.length;
|
|
1595
|
+
for (const item of [...synchronized.failed, ...synchronized.skipped]) {
|
|
1596
|
+
if (!report.skills.failures.some((failure) => failure.skill === item.skill)) {
|
|
1597
|
+
report.skills.failures.push({ skill: item.skill, reason: item.reason ?? "Managed Skill synchronization did not complete." });
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
report.skills.status = synchronized.status === "success" && !report.skills.failures.length ? "success" : report.skills.installed_count || synchronized.installed.length || synchronized.removed.length ? "partial" : "failed";
|
|
1601
|
+
report.skills.attempts.push({ source: "installed-package", stage, cause: report.skills.status === "success" ? null : "validation", failures: [...report.skills.failures] });
|
|
1602
|
+
if (report.skills.status !== "success") {
|
|
1603
|
+
return failInstallation(
|
|
1604
|
+
report,
|
|
1605
|
+
stage,
|
|
1606
|
+
"Managed Skill synchronization is incomplete. User-modified and unowned copies were preserved; see skills.synchronization for details.",
|
|
1607
|
+
synchronized.status === "busy" ? "busy" : "validation"
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
progress("[ae-cli] [4/4] Checking authentication guidance...");
|
|
1612
|
+
if (report.auth.host) {
|
|
1613
|
+
const result2 = runner(bin, ["--no-update-check", "--format", "json", "auth", "status", "--host", report.auth.host], INSTALL_TIMEOUT_MS);
|
|
1614
|
+
try {
|
|
1615
|
+
const body = JSON.parse(result2.stdout);
|
|
1616
|
+
if (!commandSucceeded(result2) || !body.ok || typeof body.data?.authenticated !== "boolean") throw new Error();
|
|
1617
|
+
report.auth.authenticated = body.data.authenticated;
|
|
1618
|
+
} catch {
|
|
1619
|
+
report.auth.warnings.push("Authentication status could not be verified. Run auth status for this host.");
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (report.auth.authenticated !== true) {
|
|
1623
|
+
report.next_steps.push(`ae-cli auth login --host ${report.auth.host ?? "<AE_HOST>"} --no-wait`);
|
|
1624
|
+
report.next_steps.push("Complete browser authorization, then run ae-cli auth login --host <AE_HOST> --device-code <DEVICE_CODE>.");
|
|
1625
|
+
}
|
|
1626
|
+
report.ok = true;
|
|
1627
|
+
return report;
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
const safe = error instanceof Error && /^(Expected an exact|Unsupported CLI package|Host must|Invalid bundled Skill|Bundled Skills are empty)/.test(error.message);
|
|
1630
|
+
return failInstallation(report, stage, safe ? error.message : "Installation could not complete at this stage. Check file permissions, package contents, and global Skills directory.");
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// src/core/version-sync.ts
|
|
1635
|
+
var AUTO_ATTEMPT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
1636
|
+
var MAX_AUTO_ATTEMPTS_PER_DAY = 3;
|
|
1637
|
+
var LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
1638
|
+
var SYNC_STATE_FILE = "version-sync.json";
|
|
1639
|
+
var SYNC_LOCK_FILE = "version-sync.lock";
|
|
1640
|
+
function syncStatePath() {
|
|
1641
|
+
return path8.join(getConfigDir(), SYNC_STATE_FILE);
|
|
1642
|
+
}
|
|
1643
|
+
function syncLockPath() {
|
|
1644
|
+
return path8.join(getConfigDir(), SYNC_LOCK_FILE);
|
|
1645
|
+
}
|
|
1646
|
+
function readSyncStore() {
|
|
1647
|
+
try {
|
|
1648
|
+
const raw = JSON.parse(fs8.readFileSync(syncStatePath(), "utf8"));
|
|
1649
|
+
if (raw && typeof raw === "object" && raw.hosts && typeof raw.hosts === "object") {
|
|
1650
|
+
return raw;
|
|
1651
|
+
}
|
|
1652
|
+
} catch {
|
|
1653
|
+
}
|
|
1654
|
+
return { hosts: {} };
|
|
1655
|
+
}
|
|
1656
|
+
function writeSyncStore(store) {
|
|
1657
|
+
try {
|
|
1658
|
+
fs8.mkdirSync(getConfigDir(), { recursive: true });
|
|
1659
|
+
fs8.writeFileSync(syncStatePath(), JSON.stringify(store, null, 2));
|
|
1660
|
+
} catch {
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
function localDay(now) {
|
|
1664
|
+
const year = now.getFullYear();
|
|
1665
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
1666
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
1667
|
+
return `${year}-${month}-${day}`;
|
|
1668
|
+
}
|
|
1669
|
+
function freshAttemptEntry(target, now) {
|
|
1670
|
+
return {
|
|
1671
|
+
target,
|
|
1672
|
+
attemptDay: localDay(now),
|
|
1673
|
+
attemptsToday: 0
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
function evaluateAutoAttempt(existing, target, now = /* @__PURE__ */ new Date()) {
|
|
1677
|
+
const today = localDay(now);
|
|
1678
|
+
const entry = !existing || existing.target !== target ? freshAttemptEntry(target, now) : existing.attemptDay !== today ? { ...freshAttemptEntry(target, now), pendingSkills: existing.pendingSkills, lastFailureStage: existing.lastFailureStage } : { ...existing };
|
|
1679
|
+
if (entry.attemptsToday >= MAX_AUTO_ATTEMPTS_PER_DAY) {
|
|
1680
|
+
return { allowed: false, reason: "daily_limit", entry };
|
|
1681
|
+
}
|
|
1682
|
+
if (entry.lastAttemptAt) {
|
|
1683
|
+
const elapsed = now.getTime() - new Date(entry.lastAttemptAt).getTime();
|
|
1684
|
+
if (Number.isFinite(elapsed) && elapsed < AUTO_ATTEMPT_INTERVAL_MS) {
|
|
1685
|
+
return { allowed: false, reason: "hourly_limit", entry };
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
return { allowed: true, entry };
|
|
1689
|
+
}
|
|
1690
|
+
function reserveAutoAttempt(host, target, now = /* @__PURE__ */ new Date()) {
|
|
1691
|
+
const store = readSyncStore();
|
|
1692
|
+
const key = normalizeUrl(host);
|
|
1693
|
+
const decision = evaluateAutoAttempt(store.hosts[key], target, now);
|
|
1694
|
+
if (!decision.allowed) return decision;
|
|
1695
|
+
decision.entry.attemptsToday += 1;
|
|
1696
|
+
decision.entry.lastAttemptAt = now.toISOString();
|
|
1697
|
+
store.hosts[key] = decision.entry;
|
|
1698
|
+
writeSyncStore(store);
|
|
1699
|
+
return decision;
|
|
1700
|
+
}
|
|
1701
|
+
function getPendingSkillsTarget(host) {
|
|
1702
|
+
const entry = readSyncStore().hosts[normalizeUrl(host)];
|
|
1703
|
+
return entry?.pendingSkills ? entry.target : void 0;
|
|
1704
|
+
}
|
|
1705
|
+
function recordVersionSyncResult(host, target, result2) {
|
|
1706
|
+
const store = readSyncStore();
|
|
1707
|
+
const key = normalizeUrl(host);
|
|
1708
|
+
const existing = store.hosts[key] ?? freshAttemptEntry(target, /* @__PURE__ */ new Date());
|
|
1709
|
+
const entry = existing.target === target ? existing : freshAttemptEntry(target, /* @__PURE__ */ new Date());
|
|
1710
|
+
if (result2.ok) {
|
|
1711
|
+
entry.pendingSkills = false;
|
|
1712
|
+
delete entry.lastFailureStage;
|
|
1713
|
+
} else {
|
|
1714
|
+
entry.pendingSkills = result2.skillsPending;
|
|
1715
|
+
entry.lastFailureStage = result2.stage;
|
|
1716
|
+
}
|
|
1717
|
+
store.hosts[key] = entry;
|
|
1718
|
+
writeSyncStore(store);
|
|
1719
|
+
}
|
|
1720
|
+
function isRegularCliVersion(version) {
|
|
1721
|
+
const normalized = normalizeCliVersion(version);
|
|
1722
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(normalized) && normalized.split(".").every((part) => Number.isSafeInteger(Number(part)));
|
|
1723
|
+
}
|
|
1724
|
+
function isAutoSyncTargetEligible(version) {
|
|
1725
|
+
if (!isRegularCliVersion(version)) return false;
|
|
1726
|
+
const [major, minor, patch] = normalizeCliVersion(version).split(".").map(Number);
|
|
1727
|
+
if (major > 6) return true;
|
|
1728
|
+
if (major !== 6) return false;
|
|
1729
|
+
if (minor > 1) return true;
|
|
1730
|
+
if (minor === 1) return patch >= 5;
|
|
1731
|
+
if (minor === 0) return patch >= 33;
|
|
1732
|
+
return false;
|
|
1733
|
+
}
|
|
1734
|
+
function isPublicAeCliPackage(packageName) {
|
|
1735
|
+
return packageName === OPEN_SOURCE_AE_CLI_PACKAGE;
|
|
1736
|
+
}
|
|
1737
|
+
function buildVersionInstallPlan(targetRaw, globalRoot = "<npm-root>", packageName = OPEN_SOURCE_AE_CLI_PACKAGE) {
|
|
1738
|
+
const target = normalizeCliVersion(targetRaw);
|
|
1739
|
+
return { target, commands: installPlan(packageName, target, globalRoot), skillsSources: ["installed-package"] };
|
|
1740
|
+
}
|
|
1741
|
+
function friendlyVersionSyncFailure(result2) {
|
|
1742
|
+
if (result2.cause === "busy") {
|
|
1743
|
+
return "Another ae-cli process is synchronizing the global installation.";
|
|
1744
|
+
}
|
|
1745
|
+
if (result2.cause === "permission") {
|
|
1746
|
+
return "The global npm installation directory is not writable.";
|
|
1747
|
+
}
|
|
1748
|
+
if (result2.cause === "timeout") {
|
|
1749
|
+
return "The npm or Skills download timed out.";
|
|
1750
|
+
}
|
|
1751
|
+
if (result2.cause === "network") {
|
|
1752
|
+
return "The npm registry or Skills source could not be reached.";
|
|
1753
|
+
}
|
|
1754
|
+
if (result2.cause === "package_missing") {
|
|
1755
|
+
return "The required CLI package or Skills tag is not available.";
|
|
1756
|
+
}
|
|
1757
|
+
if (result2.cause === "validation") {
|
|
1758
|
+
return "The installed package could not be validated.";
|
|
1759
|
+
}
|
|
1760
|
+
return `Version synchronization failed at ${result2.stage}.`;
|
|
1761
|
+
}
|
|
1762
|
+
function acquireInstallLock(now) {
|
|
1763
|
+
fs8.mkdirSync(getConfigDir(), { recursive: true });
|
|
1764
|
+
const file = syncLockPath();
|
|
1765
|
+
try {
|
|
1766
|
+
return fs8.openSync(file, "wx");
|
|
1767
|
+
} catch {
|
|
1768
|
+
try {
|
|
1769
|
+
const age = now.getTime() - fs8.statSync(file).mtimeMs;
|
|
1770
|
+
if (age >= LOCK_STALE_MS) {
|
|
1771
|
+
fs8.unlinkSync(file);
|
|
1772
|
+
return fs8.openSync(file, "wx");
|
|
1773
|
+
}
|
|
1774
|
+
} catch {
|
|
1775
|
+
try {
|
|
1776
|
+
return fs8.openSync(file, "wx");
|
|
1777
|
+
} catch {
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
function releaseInstallLock(fd) {
|
|
1785
|
+
try {
|
|
1786
|
+
fs8.closeSync(fd);
|
|
1787
|
+
} catch {
|
|
1788
|
+
}
|
|
1789
|
+
try {
|
|
1790
|
+
fs8.unlinkSync(syncLockPath());
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
function installVersion(targetRaw, options = {}) {
|
|
1795
|
+
const run = () => {
|
|
1796
|
+
const report = runInstallation({ ...options, target: targetRaw });
|
|
1797
|
+
const cliInstalled = report.cli.verified || report.cli.action === "installed";
|
|
1798
|
+
if (report.ok) return { ok: true, cliInstalled, skillsSource: "local", report };
|
|
1799
|
+
return {
|
|
1800
|
+
ok: false,
|
|
1801
|
+
stage: report.error?.stage ?? "environment",
|
|
1802
|
+
cliInstalled,
|
|
1803
|
+
skillsPending: cliInstalled,
|
|
1804
|
+
cause: report.error?.cause ?? "validation",
|
|
1805
|
+
message: report.error?.message ?? "Installation failed.",
|
|
1806
|
+
report
|
|
1807
|
+
};
|
|
1808
|
+
};
|
|
1809
|
+
if (options.dryRun) return run();
|
|
1810
|
+
let lock;
|
|
1811
|
+
try {
|
|
1812
|
+
lock = acquireInstallLock(options.now ?? /* @__PURE__ */ new Date());
|
|
1813
|
+
} catch {
|
|
1814
|
+
return {
|
|
1815
|
+
ok: false,
|
|
1816
|
+
stage: "lock",
|
|
1817
|
+
cliInstalled: false,
|
|
1818
|
+
skillsPending: Boolean(options.skipCliInstall),
|
|
1819
|
+
cause: "permission",
|
|
1820
|
+
message: "The installation lock directory is not writable."
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
if (lock === null) return {
|
|
1824
|
+
ok: false,
|
|
1825
|
+
stage: "lock",
|
|
1826
|
+
cliInstalled: false,
|
|
1827
|
+
skillsPending: Boolean(options.skipCliInstall),
|
|
1828
|
+
cause: "busy",
|
|
1829
|
+
message: "Another ae-cli process is already synchronizing the global installation."
|
|
1830
|
+
};
|
|
1831
|
+
try {
|
|
1832
|
+
return run();
|
|
1833
|
+
} finally {
|
|
1834
|
+
releaseInstallLock(lock);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
function autoSyncDirection(current, expected) {
|
|
1838
|
+
if (versionLine(current) !== versionLine(expected)) return "switch";
|
|
1839
|
+
const currentParts = normalizeCliVersion(current).split("-")[0].split(".").map(Number);
|
|
1840
|
+
const expectedParts = normalizeCliVersion(expected).split("-")[0].split(".").map(Number);
|
|
1841
|
+
for (let i = 0; i < Math.max(currentParts.length, expectedParts.length, 3); i += 1) {
|
|
1842
|
+
const local = currentParts[i] ?? 0;
|
|
1843
|
+
const target = expectedParts[i] ?? 0;
|
|
1844
|
+
if (local < target) return "upgrade";
|
|
1845
|
+
if (local > target) return "downgrade";
|
|
1846
|
+
}
|
|
1847
|
+
return "switch";
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// src/core/feature-config.ts
|
|
1851
|
+
import crypto3 from "crypto";
|
|
1852
|
+
import fs9 from "fs";
|
|
1853
|
+
import path9 from "path";
|
|
1854
|
+
var FEATURE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1855
|
+
var UNAVAILABLE_TTL_MS2 = 5 * 60 * 1e3;
|
|
1856
|
+
function cachePath2() {
|
|
1857
|
+
return path9.join(getConfigDir(), "feature-config.json");
|
|
1858
|
+
}
|
|
1859
|
+
function cacheKey2(host, token) {
|
|
1860
|
+
const fingerprint = crypto3.createHash("sha256").update(token).digest("hex");
|
|
1861
|
+
return `${normalizeUrl(host)}#${fingerprint}`;
|
|
1862
|
+
}
|
|
1863
|
+
function readStore2() {
|
|
1864
|
+
try {
|
|
1865
|
+
const value = JSON.parse(fs9.readFileSync(cachePath2(), "utf8"));
|
|
1866
|
+
return value?.entries && typeof value.entries === "object" ? value : { entries: {} };
|
|
1867
|
+
} catch {
|
|
1868
|
+
return { entries: {} };
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
function writeStore(store) {
|
|
1872
|
+
const target = cachePath2();
|
|
1873
|
+
const temp = `${target}.${process.pid}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
|
|
1874
|
+
try {
|
|
1875
|
+
fs9.mkdirSync(path9.dirname(target), { recursive: true });
|
|
1876
|
+
fs9.writeFileSync(temp, JSON.stringify(store), { encoding: "utf8", mode: 384 });
|
|
1877
|
+
fs9.renameSync(temp, target);
|
|
1878
|
+
fs9.chmodSync(target, 384);
|
|
1879
|
+
} catch {
|
|
1880
|
+
try {
|
|
1881
|
+
fs9.rmSync(temp, { force: true });
|
|
1882
|
+
} catch {
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
function remember2(host, token, entry) {
|
|
1887
|
+
const store = readStore2();
|
|
1888
|
+
store.entries[cacheKey2(host, token)] = entry;
|
|
1889
|
+
writeStore(store);
|
|
1890
|
+
}
|
|
1891
|
+
function rememberKbAutoDiscoveryFeature(host, token, enabled) {
|
|
1892
|
+
remember2(host, token, { enabled, available: true, fetchedAt: Date.now() });
|
|
1893
|
+
}
|
|
1894
|
+
async function resolveKbAutoDiscoveryFeature(host, token, fetchRemote) {
|
|
1895
|
+
const entry = readStore2().entries[cacheKey2(host, token)];
|
|
1896
|
+
const ttl = entry?.available ? FEATURE_TTL_MS : UNAVAILABLE_TTL_MS2;
|
|
1897
|
+
if (entry && Date.now() >= entry.fetchedAt && Date.now() - entry.fetchedAt < ttl) {
|
|
1898
|
+
return { enabled: entry.enabled, source: entry.available ? "cache" : "unavailable" };
|
|
1899
|
+
}
|
|
1900
|
+
const remote = await fetchRemote().catch(() => null);
|
|
1901
|
+
if (!remote) {
|
|
1902
|
+
remember2(host, token, { enabled: false, available: false, fetchedAt: Date.now() });
|
|
1903
|
+
return { enabled: false, source: "unavailable" };
|
|
1904
|
+
}
|
|
1905
|
+
const enabled = remote.projectSemanticKbAutoDiscovery === true;
|
|
1906
|
+
remember2(host, token, { enabled, available: true, fetchedAt: Date.now() });
|
|
1907
|
+
return { enabled, source: "remote" };
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/core/compat-check.ts
|
|
1911
|
+
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
1912
|
+
var NOTIFY_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1913
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
1914
|
+
var CLI_CONFIG_PATH = "/v1/ta/cli/config";
|
|
1915
|
+
function cacheFilePath() {
|
|
1916
|
+
return path10.join(getConfigDir(), "compat-check.json");
|
|
1917
|
+
}
|
|
1918
|
+
function readStore3() {
|
|
1919
|
+
try {
|
|
1920
|
+
const file = cacheFilePath();
|
|
1921
|
+
if (!fs10.existsSync(file)) return { hosts: {} };
|
|
1922
|
+
const raw = JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
1923
|
+
if (!raw || typeof raw !== "object" || !raw.hosts) return { hosts: {} };
|
|
1924
|
+
return raw;
|
|
1925
|
+
} catch {
|
|
1926
|
+
return { hosts: {} };
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
function writeStore2(store) {
|
|
1930
|
+
try {
|
|
1931
|
+
const dir = getConfigDir();
|
|
1932
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
1933
|
+
fs10.writeFileSync(cacheFilePath(), JSON.stringify(store, null, 2));
|
|
1934
|
+
} catch {
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
function hostKey(host) {
|
|
1938
|
+
return normalizeUrl(host);
|
|
1939
|
+
}
|
|
1940
|
+
function isCompatCacheStale(entry, now = Date.now()) {
|
|
1941
|
+
if (!entry?.lastFetchedAt) return true;
|
|
1942
|
+
const age = now - new Date(entry.lastFetchedAt).getTime();
|
|
1943
|
+
return age >= CHECK_INTERVAL_MS;
|
|
1944
|
+
}
|
|
1945
|
+
function shouldNotifyCompat(entry, now = Date.now()) {
|
|
1946
|
+
if (!entry?.lastNotifiedAt) return true;
|
|
1947
|
+
const age = now - new Date(entry.lastNotifiedAt).getTime();
|
|
1948
|
+
return age >= NOTIFY_INTERVAL_MS;
|
|
1949
|
+
}
|
|
1950
|
+
function shouldSkipCompatCheck(argv = process.argv) {
|
|
1951
|
+
if (process.env.AE_CLI_NO_COMPAT_CHECK === "1") return true;
|
|
1952
|
+
if (isAeSandboxRuntime()) return true;
|
|
1953
|
+
return shouldSkipUpdateCheck(argv);
|
|
1954
|
+
}
|
|
1955
|
+
function rootCommand(argv) {
|
|
1956
|
+
const args = argv.slice(2);
|
|
1957
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1958
|
+
const token = args[i];
|
|
1959
|
+
if (token === "--") return void 0;
|
|
1960
|
+
if (token.startsWith("-")) {
|
|
1961
|
+
if (!token.includes("=") && ["--host", "--cli-token-file", "--mcp-url", "--format", "--jq"].includes(token)) {
|
|
1962
|
+
i += 1;
|
|
1963
|
+
}
|
|
1964
|
+
continue;
|
|
1965
|
+
}
|
|
1966
|
+
return token;
|
|
1967
|
+
}
|
|
1968
|
+
return void 0;
|
|
1969
|
+
}
|
|
1970
|
+
function shouldSkipAutoSync(argv = process.argv) {
|
|
1971
|
+
const command = rootCommand(argv);
|
|
1972
|
+
return command === "update" || command === "install" || command === "auth" || command === "config";
|
|
1973
|
+
}
|
|
1974
|
+
async function fetchCliConfig(host, cliToken) {
|
|
1975
|
+
const url = new URL(CLI_CONFIG_PATH, host.endsWith("/") ? host : `${host}/`);
|
|
1976
|
+
const external = readExternalCliToken(host)?.token === cliToken;
|
|
1977
|
+
if (!external) url.searchParams.set("cli-token", cliToken);
|
|
1978
|
+
const controller = new AbortController();
|
|
1979
|
+
const dispatcher = new Agent({ connect: { timeout: FETCH_TIMEOUT_MS } });
|
|
1980
|
+
let cleanup;
|
|
1981
|
+
const destroy = () => cleanup ??= dispatcher.destroy().catch(() => {
|
|
1982
|
+
});
|
|
1983
|
+
const timer = setTimeout(() => {
|
|
1984
|
+
controller.abort();
|
|
1985
|
+
void destroy();
|
|
1986
|
+
}, FETCH_TIMEOUT_MS);
|
|
1987
|
+
let resp;
|
|
1988
|
+
try {
|
|
1989
|
+
const options = {
|
|
1990
|
+
dispatcher,
|
|
1991
|
+
signal: controller.signal,
|
|
1992
|
+
headers: { Accept: "application/json", ...external ? { "cli-token": cliToken } : {} },
|
|
1993
|
+
...externalCliTokenRequestOptions(url.toString())
|
|
1994
|
+
};
|
|
1995
|
+
resp = await fetch(url.toString(), options);
|
|
1996
|
+
if (!resp.ok) return null;
|
|
1997
|
+
const body = await resp.json();
|
|
1998
|
+
const data = body.data ?? body;
|
|
1999
|
+
const versions = data.versions;
|
|
2000
|
+
if (!versions) return null;
|
|
2001
|
+
const clusterVersion = versions.clusterVersion ?? versions.cluster_version ?? null;
|
|
2002
|
+
const aeCliVersion = versions.aeCliVersion ?? versions.ae_cli_version ?? null;
|
|
2003
|
+
return {
|
|
2004
|
+
clusterVersion: clusterVersion?.trim() || null,
|
|
2005
|
+
aeCliVersion: aeCliVersion?.trim() || null,
|
|
2006
|
+
...data.businesses && typeof data.businesses === "object" && !Array.isArray(data.businesses) ? { businesses: normalizeBusinesses(data.businesses) } : {},
|
|
2007
|
+
...typeof data.features?.projectSemanticKbAutoDiscovery === "boolean" ? { projectSemanticKbAutoDiscovery: data.features.projectSemanticKbAutoDiscovery } : {}
|
|
2008
|
+
};
|
|
2009
|
+
} catch {
|
|
2010
|
+
return null;
|
|
2011
|
+
} finally {
|
|
2012
|
+
clearTimeout(timer);
|
|
2013
|
+
const destroyed = destroy();
|
|
2014
|
+
if (resp?.body && !resp.bodyUsed) await Promise.allSettled([resp.body.cancel(), destroyed]);
|
|
2015
|
+
else await destroyed;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
function printNotice(text) {
|
|
2019
|
+
process.stderr.write(`${text}
|
|
2020
|
+
`);
|
|
2021
|
+
}
|
|
2022
|
+
function isNonInteractiveOutput() {
|
|
2023
|
+
return !process.stdout.isTTY || !process.stderr.isTTY;
|
|
2024
|
+
}
|
|
2025
|
+
var pendingHostCompatNotice;
|
|
2026
|
+
var pendingUpdateNotice;
|
|
2027
|
+
function getPendingHostCompatNotice() {
|
|
2028
|
+
return pendingHostCompatNotice;
|
|
2029
|
+
}
|
|
2030
|
+
function getPendingUpdateNotice() {
|
|
2031
|
+
return pendingUpdateNotice;
|
|
2032
|
+
}
|
|
2033
|
+
function getCachedCompatForHost(host) {
|
|
2034
|
+
return readStore3().hosts[hostKey(host)];
|
|
2035
|
+
}
|
|
2036
|
+
function formatCapabilityMissCompatHint(host) {
|
|
2037
|
+
if (shouldSkipCompatCheck()) return void 0;
|
|
2038
|
+
const active = host || getActiveHost();
|
|
2039
|
+
if (!active) {
|
|
2040
|
+
return "This capability may be missing on the current private cluster. Update cluster components to the latest version, or pin this machine to the environment ae-cli version.";
|
|
2041
|
+
}
|
|
2042
|
+
const cached = getCachedCompatForHost(active);
|
|
2043
|
+
if (cached?.expectedVersion) {
|
|
2044
|
+
const pins = formatPinCommands(cached.expectedVersion);
|
|
2045
|
+
if (pins.length > 0) {
|
|
2046
|
+
return [
|
|
2047
|
+
`Host may be older than this CLI (environment ae-cli ${cached.expectedVersion}, cluster ${cached.clusterVersion || "?"}).`,
|
|
2048
|
+
`Update cluster components to the latest version, or pin locally:`,
|
|
2049
|
+
...pins.map((c) => ` ${c}`)
|
|
2050
|
+
].join("\n");
|
|
2051
|
+
}
|
|
2052
|
+
return [
|
|
2053
|
+
`Host may be older than this CLI (environment ae-cli ${cached.expectedVersion}, cluster ${cached.clusterVersion || "?"}).`,
|
|
2054
|
+
`No public GitHub/npm release matches that environment version; update cluster components to the latest version instead of pinning.`
|
|
2055
|
+
].join("\n");
|
|
2056
|
+
}
|
|
2057
|
+
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).";
|
|
2058
|
+
}
|
|
2059
|
+
function setUpdateNotice(pkg, expected, cluster, verdict, state, stage, message) {
|
|
2060
|
+
pendingHostCompatNotice = void 0;
|
|
2061
|
+
pendingUpdateNotice = {
|
|
2062
|
+
command: "ae-cli update",
|
|
2063
|
+
current: pkg.version,
|
|
2064
|
+
expected,
|
|
2065
|
+
cluster,
|
|
2066
|
+
reason: verdict.kind === "ok" && state === "skills_pending" ? "skills_pending" : verdict.kind,
|
|
2067
|
+
state,
|
|
2068
|
+
...stage ? { stage } : {},
|
|
2069
|
+
message: message ?? `Current ae-cli is ${pkg.version}; this host requires ${expected}. Run: ae-cli update`
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
async function refreshAndMaybeNotify(host, pkg, allowAutoSync) {
|
|
2073
|
+
const token = peekCliToken(host);
|
|
2074
|
+
if (!token) return { status: "continue" };
|
|
2075
|
+
const store = readStore3();
|
|
2076
|
+
const key = hostKey(host);
|
|
2077
|
+
const existing = store.hosts[key];
|
|
2078
|
+
let expected = existing?.expectedVersion;
|
|
2079
|
+
let cluster = existing?.clusterVersion;
|
|
2080
|
+
let fetchedAt = existing?.lastFetchedAt;
|
|
2081
|
+
if (isCompatCacheStale(existing)) {
|
|
2082
|
+
const remote = await fetchCliConfig(host, token);
|
|
2083
|
+
if (remote) {
|
|
2084
|
+
rememberKbAutoDiscoveryFeature(host, token, remote.projectSemanticKbAutoDiscovery ?? false);
|
|
2085
|
+
rememberBusinessConfig(host, token, remote.businesses);
|
|
2086
|
+
}
|
|
2087
|
+
if (!remote?.aeCliVersion) {
|
|
2088
|
+
return { status: "continue" };
|
|
2089
|
+
}
|
|
2090
|
+
expected = remote.aeCliVersion;
|
|
2091
|
+
cluster = remote.clusterVersion ?? "";
|
|
2092
|
+
fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2093
|
+
store.hosts[key] = {
|
|
2094
|
+
host: key,
|
|
2095
|
+
localVersion: pkg.version,
|
|
2096
|
+
expectedVersion: expected,
|
|
2097
|
+
clusterVersion: cluster,
|
|
2098
|
+
lastFetchedAt: fetchedAt,
|
|
2099
|
+
lastNotifiedAt: existing?.lastNotifiedAt
|
|
2100
|
+
};
|
|
2101
|
+
writeStore2(store);
|
|
2102
|
+
}
|
|
2103
|
+
if (!expected) return { status: "continue" };
|
|
2104
|
+
let verdict = evaluateCompat(pkg.version, expected, cluster);
|
|
2105
|
+
let pendingSkills = getPendingSkillsTarget(host) === expected;
|
|
2106
|
+
if (verdict.kind === "ok" && !pendingSkills) {
|
|
2107
|
+
pendingHostCompatNotice = void 0;
|
|
2108
|
+
pendingUpdateNotice = void 0;
|
|
2109
|
+
return { status: "continue" };
|
|
2110
|
+
}
|
|
2111
|
+
let entry = store.hosts[key] ?? {
|
|
2112
|
+
host: key,
|
|
2113
|
+
localVersion: pkg.version,
|
|
2114
|
+
expectedVersion: expected,
|
|
2115
|
+
clusterVersion: cluster || "",
|
|
2116
|
+
lastFetchedAt: fetchedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
2117
|
+
};
|
|
2118
|
+
entry.localVersion = pkg.version;
|
|
2119
|
+
entry.expectedVersion = expected;
|
|
2120
|
+
entry.clusterVersion = cluster || entry.clusterVersion;
|
|
2121
|
+
const canAutoSync = allowAutoSync && isPublicAeCliPackage(pkg.name) && isRegularCliVersion(pkg.version) && isAutoSyncTargetEligible(expected);
|
|
2122
|
+
if (canAutoSync) {
|
|
2123
|
+
const isCurrentLocalIdentity = () => {
|
|
2124
|
+
try {
|
|
2125
|
+
return canManageGlobalSkills() && normalizeUrl(getActiveHost()) === key && peekCliToken(host) === token;
|
|
2126
|
+
} catch {
|
|
2127
|
+
return false;
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
const context = isCurrentLocalIdentity() ? { host, tokenFingerprint: createHash2("sha256").update(token).digest("hex") } : void 0;
|
|
2131
|
+
const generation = context ? reserveBusinessSkillContext(context) : void 0;
|
|
2132
|
+
const confirmed = await fetchCliConfig(host, token);
|
|
2133
|
+
if (context && generation && !isBusinessSkillContextCurrent({
|
|
2134
|
+
...context,
|
|
2135
|
+
generation,
|
|
2136
|
+
isCurrentContext: isCurrentLocalIdentity
|
|
2137
|
+
})) return { status: "continue" };
|
|
2138
|
+
if (confirmed) {
|
|
2139
|
+
rememberKbAutoDiscoveryFeature(host, token, confirmed.projectSemanticKbAutoDiscovery ?? false);
|
|
2140
|
+
rememberBusinessConfig(host, token, confirmed.businesses);
|
|
2141
|
+
}
|
|
2142
|
+
if (confirmed?.aeCliVersion) {
|
|
2143
|
+
expected = confirmed.aeCliVersion;
|
|
2144
|
+
cluster = confirmed.clusterVersion ?? cluster ?? "";
|
|
2145
|
+
verdict = evaluateCompat(pkg.version, expected, cluster);
|
|
2146
|
+
pendingSkills = getPendingSkillsTarget(host) === expected;
|
|
2147
|
+
entry = {
|
|
2148
|
+
...entry,
|
|
2149
|
+
localVersion: pkg.version,
|
|
2150
|
+
expectedVersion: expected,
|
|
2151
|
+
clusterVersion: cluster || "",
|
|
2152
|
+
lastFetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2153
|
+
};
|
|
2154
|
+
store.hosts[key] = entry;
|
|
2155
|
+
writeStore2(store);
|
|
2156
|
+
if (verdict.kind === "ok" && !pendingSkills) {
|
|
2157
|
+
pendingHostCompatNotice = void 0;
|
|
2158
|
+
pendingUpdateNotice = void 0;
|
|
2159
|
+
return { status: "continue" };
|
|
2160
|
+
}
|
|
2161
|
+
if (isAutoSyncTargetEligible(expected)) {
|
|
2162
|
+
const attempt = reserveAutoAttempt(host, expected);
|
|
2163
|
+
if (attempt.allowed) {
|
|
2164
|
+
const direction = verdict.kind === "ok" && pendingSkills ? "skills" : autoSyncDirection(pkg.version, expected);
|
|
2165
|
+
const verb = direction === "upgrade" ? "upgrading" : direction === "downgrade" ? "downgrading" : direction === "skills" ? "repairing" : "switching";
|
|
2166
|
+
printNotice(
|
|
2167
|
+
`[ae-cli] Current version ${pkg.version}; this host requires ${expected}. Automatically ${verb} the CLI installation; global Skills will be synchronized when this local context allows it. Please wait...`
|
|
2168
|
+
);
|
|
2169
|
+
const result2 = installVersion(expected, {
|
|
2170
|
+
skipCliInstall: verdict.kind === "ok" && pendingSkills,
|
|
2171
|
+
progress: printNotice
|
|
2172
|
+
});
|
|
2173
|
+
const globalSkillsSkipped = result2.ok && result2.report?.skills.status === "skipped";
|
|
2174
|
+
if (globalSkillsSkipped && direction === "skills") {
|
|
2175
|
+
printNotice("[ae-cli] Global Skills were not changed for this external-token or sandbox context. This command will continue; refresh Skills from a local signed-in environment.");
|
|
2176
|
+
pendingHostCompatNotice = void 0;
|
|
2177
|
+
pendingUpdateNotice = void 0;
|
|
2178
|
+
return { status: "continue" };
|
|
2179
|
+
}
|
|
2180
|
+
recordVersionSyncResult(host, expected, result2);
|
|
2181
|
+
if (result2.ok) {
|
|
2182
|
+
printNotice(globalSkillsSkipped ? `[ae-cli] CLI changed from ${pkg.version} to ${expected}; global Skills were preserved. Starting the original command with the synchronized CLI...` : `[ae-cli] Synchronized to ${expected}. Starting the original command with the synchronized CLI...`);
|
|
2183
|
+
pendingHostCompatNotice = void 0;
|
|
2184
|
+
pendingUpdateNotice = void 0;
|
|
2185
|
+
return {
|
|
2186
|
+
status: "synced",
|
|
2187
|
+
current: pkg.version,
|
|
2188
|
+
expected,
|
|
2189
|
+
cluster: cluster || "",
|
|
2190
|
+
direction,
|
|
2191
|
+
entrypoint: result2.report?.cli.verified ? result2.report.cli.entrypoint ?? void 0 : void 0
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
const state = result2.skillsPending ? "skills_pending" : "auto_sync_failed";
|
|
2195
|
+
const friendlyCause = friendlyVersionSyncFailure(result2);
|
|
2196
|
+
const failure = result2.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.`;
|
|
2197
|
+
printNotice(`[ae-cli] ${failure}`);
|
|
2198
|
+
setUpdateNotice(pkg, expected, cluster || "", verdict, state, result2.stage, failure);
|
|
2199
|
+
entry.lastNotifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2200
|
+
store.hosts[key] = entry;
|
|
2201
|
+
writeStore2(store);
|
|
2202
|
+
return { status: "continue" };
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
const notice = verdict.kind === "ok" && pendingSkills ? `[ae-cli] CLI ${expected} is installed, but its Skills are not synchronized.
|
|
2208
|
+
Run: ae-cli update` : formatCompatNotice(verdict);
|
|
2209
|
+
if (!notice) return { status: "continue" };
|
|
2210
|
+
const dueForTip = shouldNotifyCompat(entry);
|
|
2211
|
+
if (!dueForTip) {
|
|
2212
|
+
pendingHostCompatNotice = void 0;
|
|
2213
|
+
pendingUpdateNotice = void 0;
|
|
2214
|
+
store.hosts[key] = entry;
|
|
2215
|
+
writeStore2(store);
|
|
2216
|
+
return { status: "continue" };
|
|
2217
|
+
}
|
|
2218
|
+
setUpdateNotice(
|
|
2219
|
+
pkg,
|
|
2220
|
+
expected,
|
|
2221
|
+
cluster || "",
|
|
2222
|
+
verdict,
|
|
2223
|
+
pendingSkills ? "skills_pending" : "prompt_only",
|
|
2224
|
+
void 0,
|
|
2225
|
+
pendingSkills ? `Skills for ae-cli ${expected} are not synchronized. Run: ae-cli update` : void 0
|
|
2226
|
+
);
|
|
2227
|
+
printNotice(notice);
|
|
2228
|
+
entry.lastNotifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2229
|
+
store.hosts[key] = entry;
|
|
2230
|
+
writeStore2(store);
|
|
2231
|
+
return { status: "continue" };
|
|
2232
|
+
}
|
|
2233
|
+
async function runHostCompatCheck(pkg, hostOverride, argv = process.argv) {
|
|
2234
|
+
pendingHostCompatNotice = void 0;
|
|
2235
|
+
pendingUpdateNotice = void 0;
|
|
2236
|
+
if (shouldSkipCompatCheck(argv)) return { status: "continue" };
|
|
2237
|
+
const host = hostOverride || getActiveHost();
|
|
2238
|
+
if (!host) return { status: "continue" };
|
|
2239
|
+
try {
|
|
2240
|
+
return await refreshAndMaybeNotify(host, pkg, !shouldSkipAutoSync(argv));
|
|
2241
|
+
} catch {
|
|
2242
|
+
return { status: "continue" };
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
function notifyIfHostCompat(pkg, hostOverride) {
|
|
2246
|
+
void runHostCompatCheck(pkg, hostOverride);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
export {
|
|
2250
|
+
exactVersion,
|
|
2251
|
+
installHost,
|
|
2252
|
+
getLocalCliPackageInfo,
|
|
2253
|
+
businessForCommand,
|
|
2254
|
+
businessForCapability,
|
|
2255
|
+
canManageGlobalSkills,
|
|
2256
|
+
rememberBusinessConfig,
|
|
2257
|
+
readCurrentBusinessConfig,
|
|
2258
|
+
BusinessDisabledError,
|
|
2259
|
+
readBusinessConfigForExecution,
|
|
2260
|
+
assertBusinessEnabled,
|
|
2261
|
+
reserveBusinessSkillContext,
|
|
2262
|
+
isBusinessSkillContextCurrent,
|
|
2263
|
+
syncBusinessSkills,
|
|
2264
|
+
newInstallationReport,
|
|
2265
|
+
failInstallation,
|
|
2266
|
+
recordVersionSyncResult,
|
|
2267
|
+
buildVersionInstallPlan,
|
|
2268
|
+
friendlyVersionSyncFailure,
|
|
2269
|
+
installVersion,
|
|
2270
|
+
resolveKbAutoDiscoveryFeature,
|
|
2271
|
+
isCompatCacheStale,
|
|
2272
|
+
shouldNotifyCompat,
|
|
2273
|
+
shouldSkipCompatCheck,
|
|
2274
|
+
shouldSkipAutoSync,
|
|
2275
|
+
fetchCliConfig,
|
|
2276
|
+
isNonInteractiveOutput,
|
|
2277
|
+
getPendingHostCompatNotice,
|
|
2278
|
+
getPendingUpdateNotice,
|
|
2279
|
+
getCachedCompatForHost,
|
|
2280
|
+
formatCapabilityMissCompatHint,
|
|
2281
|
+
runHostCompatCheck,
|
|
2282
|
+
notifyIfHostCompat
|
|
2283
|
+
};
|