openmeld 0.3.205 → 0.3.207
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/dist/{add-me-membership-jhCNTfdl.js → add-me-membership-1JHMjZoR.js} +2 -2
- package/dist/{add-me-membership-jhCNTfdl.js.map → add-me-membership-1JHMjZoR.js.map} +1 -1
- package/dist/{agents-9krfNX_L.js → agents-CIDsThyG.js} +1041 -597
- package/dist/agents-CIDsThyG.js.map +1 -0
- package/dist/{api-client-foundation-BguumKMv.js → api-client-foundation-D6_NXHPV.js} +1 -1
- package/dist/{api-client-foundation-BMPOo4bG.js → api-client-foundation-DSurenul.js} +17 -7
- package/dist/api-client-foundation-DSurenul.js.map +1 -0
- package/dist/auth-session-B7QMhHwh.js +4 -0
- package/dist/{auth-session-wmefguut.js → auth-session-CO5XtFaq.js} +450 -128
- package/dist/auth-session-CO5XtFaq.js.map +1 -0
- package/dist/{base-url-C89TOpPV.js → base-url-BE3pFiOZ.js} +1 -1
- package/dist/{base-url-jWXPssF_.js → base-url-C6sHRNGu.js} +3 -3
- package/dist/{base-url-jWXPssF_.js.map → base-url-C6sHRNGu.js.map} +1 -1
- package/dist/cli-binary-manifest-DiUulkp0.js +117 -0
- package/dist/cli-binary-manifest-DiUulkp0.js.map +1 -0
- package/dist/{command-DKM3wO3V.js → command-Bzt9zgnI.js} +14 -12
- package/dist/{command-DKM3wO3V.js.map → command-Bzt9zgnI.js.map} +1 -1
- package/dist/computer-connection-BOl79Ftf.js +54 -0
- package/dist/computer-connection-BOl79Ftf.js.map +1 -0
- package/dist/computer-connection-review-BTOvbJYL.js +39 -0
- package/dist/computer-connection-review-BTOvbJYL.js.map +1 -0
- package/dist/{computer-setup-continuation-Dad0lLP9.js → computer-setup-continuation-DmKdhR5u.js} +4 -4
- package/dist/{computer-setup-continuation-Dad0lLP9.js.map → computer-setup-continuation-DmKdhR5u.js.map} +1 -1
- package/dist/control-plane-D9jDcbMZ.js +1211 -0
- package/dist/control-plane-D9jDcbMZ.js.map +1 -0
- package/dist/daemon-local-state-file-jOXuXrky.js +45 -0
- package/dist/daemon-local-state-file-jOXuXrky.js.map +1 -0
- package/dist/daemon-runtime-lease-Dd4hdHNo.js +50 -0
- package/dist/daemon-runtime-lease-Dd4hdHNo.js.map +1 -0
- package/dist/{daemon-runtime-lease-BG9g0a6Z.js → daemon-runtime-lease-y3n1K0-C.js} +1 -1
- package/dist/{daemon-service-manager-DqVtRLBY.js → daemon-service-manager-Bh0LoLed.js} +1 -1
- package/dist/{daemon-service-manager-BVipiZwK.js → daemon-service-manager-GiMCiTmu.js} +99 -85
- package/dist/daemon-service-manager-GiMCiTmu.js.map +1 -0
- package/dist/daemon-service-runtime-DQW9gSLT.js +3642 -0
- package/dist/daemon-service-runtime-DQW9gSLT.js.map +1 -0
- package/dist/{daemon-state-BwIoDq7_.js → daemon-state-C37ZtIZK.js} +11 -5
- package/dist/daemon-state-C37ZtIZK.js.map +1 -0
- package/dist/{dist-CkixulB-.js → dist-D-kJdWZE.js} +4272 -3990
- package/dist/dist-D-kJdWZE.js.map +1 -0
- package/dist/dist-DTG_Vqc7.js +4 -0
- package/dist/distribution-DY2fBeSW.js +72 -0
- package/dist/distribution-DY2fBeSW.js.map +1 -0
- package/dist/{elapsed-time-CjAPqg-B.js → elapsed-time-CcoM2R3N.js} +7 -4
- package/dist/{elapsed-time-CjAPqg-B.js.map → elapsed-time-CcoM2R3N.js.map} +1 -1
- package/dist/{installer-CZyyvNZr.js → installation-inventory-DJlwXa1q.js} +362 -361
- package/dist/installation-inventory-DJlwXa1q.js.map +1 -0
- package/dist/{lock-CvBnBcFK.js → lock-Dxp6o_t_.js} +56 -7
- package/dist/lock-Dxp6o_t_.js.map +1 -0
- package/dist/{openmeld-DVThrV4O.js → openmeld-CiL4AiB0.js} +232 -23
- package/dist/openmeld-CiL4AiB0.js.map +1 -0
- package/dist/openmeld-dev.js +1 -1
- package/dist/openmeld-environment-target-NtJYJe1D.js +256 -0
- package/dist/openmeld-environment-target-NtJYJe1D.js.map +1 -0
- package/dist/{openmeld-maintenance-worker-Cxp6fwB5.js → openmeld-maintenance-worker-DjdmRASI.js} +9 -8
- package/dist/{openmeld-maintenance-worker-Cxp6fwB5.js.map → openmeld-maintenance-worker-DjdmRASI.js.map} +1 -1
- package/dist/{openmeld-tools-update-coordinator-DJ65uI3O.js → openmeld-tools-update-coordinator-D9Skj8IE.js} +52 -4013
- package/dist/openmeld-tools-update-coordinator-D9Skj8IE.js.map +1 -0
- package/dist/openmeld.js +3 -3
- package/dist/{prepare-session-executor-aE1YARgn.js → prepare-session-executor-B0NtjHEL.js} +15 -13
- package/dist/{prepare-session-executor-aE1YARgn.js.map → prepare-session-executor-B0NtjHEL.js.map} +1 -1
- package/dist/{profiles-Dx3gaXgQ.js → runtime-transport-QIZmAo4-.js} +634 -1825
- package/dist/runtime-transport-QIZmAo4-.js.map +1 -0
- package/dist/service-account-authorization-Dt8kGvq8.js +56 -0
- package/dist/service-account-authorization-Dt8kGvq8.js.map +1 -0
- package/dist/service-control-capabilities-BU5F75zi.js +54 -0
- package/dist/service-control-capabilities-BU5F75zi.js.map +1 -0
- package/dist/service-control-capabilities-CzUB1ovW.js +4 -0
- package/dist/upgrade-DLECkjMt.js +4 -0
- package/dist/{upgrade-Bk8t5oav.js → upgrade-NxpTydHp.js} +6 -4
- package/dist/upgrade-NxpTydHp.js.map +1 -0
- package/dist/{urls-4D7CaXe3.js → urls-CY2uzH2-.js} +2 -1
- package/dist/urls-CY2uzH2-.js.map +1 -0
- package/package.json +2 -2
- package/dist/agents-9krfNX_L.js.map +0 -1
- package/dist/api-client-foundation-BMPOo4bG.js.map +0 -1
- package/dist/auth-session-CpPqG8Xz.js +0 -4
- package/dist/auth-session-wmefguut.js.map +0 -1
- package/dist/daemon-runtime-lease-CXlGR-iA.js +0 -87
- package/dist/daemon-runtime-lease-CXlGR-iA.js.map +0 -1
- package/dist/daemon-service-manager-BVipiZwK.js.map +0 -1
- package/dist/daemon-state-BwIoDq7_.js.map +0 -1
- package/dist/dist-BFbC-9BX.js +0 -4
- package/dist/dist-CkixulB-.js.map +0 -1
- package/dist/installer-CZyyvNZr.js.map +0 -1
- package/dist/lock-CvBnBcFK.js.map +0 -1
- package/dist/openmeld-DVThrV4O.js.map +0 -1
- package/dist/openmeld-tools-update-coordinator-DJ65uI3O.js.map +0 -1
- package/dist/profiles-Dx3gaXgQ.js.map +0 -1
- package/dist/upgrade-BFGKy5He.js +0 -4
- package/dist/upgrade-Bk8t5oav.js.map +0 -1
- package/dist/urls-4D7CaXe3.js.map +0 -1
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { An as resolveSkillsAgentRegistry,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { _t as skillsDir, dt as resolveOpenMeldEnvPreset, gt as runtimeLogsDir, rt as openMeldRootDir } from "./lock-Dxp6o_t_.js";
|
|
4
|
+
import { An as resolveSkillsAgentRegistry, On as canManageRegistryAgentSkillTargets, kn as detectInstalledSkillsAgents } from "./openmeld-tools-update-coordinator-D9Skj8IE.js";
|
|
5
|
+
import { t as isBinaryDistribution } from "./distribution-DY2fBeSW.js";
|
|
6
|
+
import { lt as compareSemver, nt as OPENMELD_VERSION } from "./daemon-service-runtime-DQW9gSLT.js";
|
|
7
|
+
import { T as updateConfiguredSkillsConfig, g as getConfiguredSkillsConfig, y as resolveRuntimeSkillsCustomTargetId } from "./base-url-C6sHRNGu.js";
|
|
7
8
|
import { createHash, randomUUID, verify } from "node:crypto";
|
|
8
9
|
import path, { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
9
10
|
import { cp, lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
10
11
|
import * as z$1 from "zod/v4";
|
|
11
12
|
import { existsSync } from "node:fs";
|
|
12
13
|
import process$1 from "node:process";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
15
16
|
const OFFICIAL_CLI_SKILL_URL = "https://openmeld.ai/skills/openmeld-cli/SKILL.md";
|
|
16
17
|
const OFFICIAL_CLI_SKILL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
17
18
|
MCowBQYDK2VwAyEAFZjFHrQ/QLsJQpHkAbKBRG57fEazhm7zc0dMa54ilWQ=
|
|
@@ -42,160 +43,6 @@ const officialCliSkillChannelSchema = z$1.object({
|
|
|
42
43
|
releases: z$1.array(officialCliSkillReleaseSchema).min(1).max(16)
|
|
43
44
|
}).strict();
|
|
44
45
|
//#endregion
|
|
45
|
-
//#region src/skills/bundled.ts
|
|
46
|
-
const CLI_PACKAGE_JSON = "package.json";
|
|
47
|
-
/**
|
|
48
|
-
* Locate the on-disk bundled skills directory for the npm distribution by
|
|
49
|
-
* walking up from this module to the CLI package root (a directory that has
|
|
50
|
-
* both package.json and skills/). Returns null under the binary distribution,
|
|
51
|
-
* where import.meta.url is a virtual path and the skills are embedded instead —
|
|
52
|
-
* callers in that mode go through bundled-assets.ts, not this function.
|
|
53
|
-
*/
|
|
54
|
-
function resolveBundledSkillsLocation() {
|
|
55
|
-
const cliRootDir = resolveCliRootDir(path.dirname(fileURLToPath(import.meta.url)));
|
|
56
|
-
if (!cliRootDir) return null;
|
|
57
|
-
return {
|
|
58
|
-
cliRootDir,
|
|
59
|
-
skillsRootDir: path.join(cliRootDir, "skills")
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function resolveCliRootDir(fromDir) {
|
|
63
|
-
let current = fromDir;
|
|
64
|
-
for (;;) {
|
|
65
|
-
const hasPackageJson = existsSync(path.join(current, CLI_PACKAGE_JSON));
|
|
66
|
-
const hasSkillsDir = existsSync(path.join(current, "skills"));
|
|
67
|
-
if (hasPackageJson && hasSkillsDir) return current;
|
|
68
|
-
const parent = path.dirname(current);
|
|
69
|
-
if (parent === current) return null;
|
|
70
|
-
current = parent;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
//#endregion
|
|
74
|
-
//#region src/skills/bundled-assets-fs.ts
|
|
75
|
-
/**
|
|
76
|
-
* npm-distribution source: read the bundled skills from the on-disk `skills/`
|
|
77
|
-
* directory next to the CLI package. Assets are sorted by relative path so the
|
|
78
|
-
* order (and therefore any content hash computed from them) is stable and
|
|
79
|
-
* matches the embedded distribution.
|
|
80
|
-
*/
|
|
81
|
-
async function loadFsBundledSkillAssets() {
|
|
82
|
-
const location = resolveBundledSkillsLocation();
|
|
83
|
-
if (!location) throw new Error("unable to resolve bundled OpenMeld skills directory");
|
|
84
|
-
return sortBundledSkillAssets(await collectSkillAssets(location.skillsRootDir, location.skillsRootDir));
|
|
85
|
-
}
|
|
86
|
-
function sortBundledSkillAssets(assets) {
|
|
87
|
-
return [...assets].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
88
|
-
}
|
|
89
|
-
async function collectSkillAssets(dir, rootDir) {
|
|
90
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
91
|
-
const assets = [];
|
|
92
|
-
for (const entry of entries) {
|
|
93
|
-
const absolutePath = join(dir, entry.name);
|
|
94
|
-
if (entry.isDirectory()) {
|
|
95
|
-
assets.push(...await collectSkillAssets(absolutePath, rootDir));
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (!entry.isFile()) continue;
|
|
99
|
-
assets.push({
|
|
100
|
-
relativePath: toPosixRelativePath(relative(rootDir, absolutePath)),
|
|
101
|
-
content: await readFile(absolutePath)
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
return assets;
|
|
105
|
-
}
|
|
106
|
-
function toPosixRelativePath(value) {
|
|
107
|
-
return value.split("\\").join("/");
|
|
108
|
-
}
|
|
109
|
-
//#endregion
|
|
110
|
-
//#region src/skills/bundled-assets.ts
|
|
111
|
-
/**
|
|
112
|
-
* Load the OpenMeld-managed skill files that ship with this CLI build.
|
|
113
|
-
*
|
|
114
|
-
* - npm distribution: read from the on-disk `skills/` directory.
|
|
115
|
-
* - binary distribution: read from files embedded into the single binary at
|
|
116
|
-
* compile time. The embedded module is a Bun-only island resolved only by
|
|
117
|
-
* `bun build --compile`; the npm build never reaches this branch and marks
|
|
118
|
-
* the module external, so its `with { type: "file" }` imports are never
|
|
119
|
-
* parsed by tsdown.
|
|
120
|
-
*/
|
|
121
|
-
async function loadBundledSkillAssets() {
|
|
122
|
-
if (isBinaryDistribution()) return (await import("./skills/bundled-assets-embedded")).loadEmbeddedBundledSkillAssets();
|
|
123
|
-
return loadFsBundledSkillAssets();
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Content hash over an ordered list of skill assets. Byte-for-byte compatible
|
|
127
|
-
* with `computeDirectoryContentHash` (same `file:<relativePath>\n<content>\n`
|
|
128
|
-
* framing) so the embedded and on-disk sources produce identical hashes as long
|
|
129
|
-
* as `assets` is sorted by relative path (as `loadBundledSkillAssets` returns
|
|
130
|
-
* it). This is the single skills-hash algorithm for both distribution forms.
|
|
131
|
-
*/
|
|
132
|
-
function hashBundledSkillAssets(assets) {
|
|
133
|
-
const hash = createHash("sha256");
|
|
134
|
-
for (const asset of assets) {
|
|
135
|
-
hash.update(`file:${asset.relativePath}\n`);
|
|
136
|
-
hash.update(asset.content);
|
|
137
|
-
hash.update("\n");
|
|
138
|
-
}
|
|
139
|
-
return hash.digest("hex");
|
|
140
|
-
}
|
|
141
|
-
//#endregion
|
|
142
|
-
//#region src/skills/content-hash.ts
|
|
143
|
-
async function computeDirectoryContentHash(input) {
|
|
144
|
-
const ignoredNames = new Set(input.ignoredNames ?? []);
|
|
145
|
-
const files = await collectFiles({
|
|
146
|
-
dir: input.rootDir,
|
|
147
|
-
rootDir: input.rootDir,
|
|
148
|
-
ignoredNames
|
|
149
|
-
});
|
|
150
|
-
const hash = createHash("sha256");
|
|
151
|
-
for (const file of files) {
|
|
152
|
-
hash.update(`file:${file.relativePath}\n`);
|
|
153
|
-
hash.update(file.content);
|
|
154
|
-
hash.update("\n");
|
|
155
|
-
}
|
|
156
|
-
return hash.digest("hex");
|
|
157
|
-
}
|
|
158
|
-
async function collectFiles(input) {
|
|
159
|
-
const entries = await readdir(input.dir, { withFileTypes: true }).catch((error) => {
|
|
160
|
-
if (getErrorCode$1(error) === "ENOENT") return [];
|
|
161
|
-
warnDirectoryReadError(input.dir, error);
|
|
162
|
-
throw error;
|
|
163
|
-
});
|
|
164
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
165
|
-
const files = [];
|
|
166
|
-
for (const entry of entries) {
|
|
167
|
-
if (input.ignoredNames.has(entry.name)) continue;
|
|
168
|
-
const absolutePath = join(input.dir, entry.name);
|
|
169
|
-
if (entry.isDirectory()) {
|
|
170
|
-
files.push(...await collectFiles({
|
|
171
|
-
dir: absolutePath,
|
|
172
|
-
rootDir: input.rootDir,
|
|
173
|
-
ignoredNames: input.ignoredNames
|
|
174
|
-
}));
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
if (!entry.isFile()) continue;
|
|
178
|
-
const content = await readFile(absolutePath);
|
|
179
|
-
files.push({
|
|
180
|
-
relativePath: normalizeRelativePath(relative(input.rootDir, absolutePath)),
|
|
181
|
-
content
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
return files;
|
|
185
|
-
}
|
|
186
|
-
function normalizeRelativePath(value) {
|
|
187
|
-
return value.split("\\").join("/");
|
|
188
|
-
}
|
|
189
|
-
function getErrorCode$1(error) {
|
|
190
|
-
if (!error || typeof error !== "object") return;
|
|
191
|
-
const code = error.code;
|
|
192
|
-
return typeof code === "string" ? code : void 0;
|
|
193
|
-
}
|
|
194
|
-
function warnDirectoryReadError(dir, error) {
|
|
195
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
196
|
-
process.stderr.write(`[openmeld] failed to read directory while hashing skills content (${dir}): ${reason}\n`);
|
|
197
|
-
}
|
|
198
|
-
//#endregion
|
|
199
46
|
//#region src/skills/install-lock.ts
|
|
200
47
|
const heldLock = new AsyncLocalStorage();
|
|
201
48
|
const pendingLocks = /* @__PURE__ */ new Map();
|
|
@@ -227,7 +74,7 @@ async function acquireSkillsInstallLock(run) {
|
|
|
227
74
|
acquired = true;
|
|
228
75
|
break;
|
|
229
76
|
} catch (error) {
|
|
230
|
-
if (getErrorCode(error) !== "EEXIST") throw error;
|
|
77
|
+
if (getErrorCode$1(error) !== "EEXIST") throw error;
|
|
231
78
|
if (!await recoverStaleSkillsInstallLock(lockPath)) throw new Error("another skills installation is already running; retry after it finishes");
|
|
232
79
|
try {
|
|
233
80
|
await writeFile(lockPath, lockContents, {
|
|
@@ -237,7 +84,7 @@ async function acquireSkillsInstallLock(run) {
|
|
|
237
84
|
acquired = true;
|
|
238
85
|
break;
|
|
239
86
|
} catch (retryError) {
|
|
240
|
-
if (getErrorCode(retryError) !== "EEXIST") throw retryError;
|
|
87
|
+
if (getErrorCode$1(retryError) !== "EEXIST") throw retryError;
|
|
241
88
|
}
|
|
242
89
|
}
|
|
243
90
|
if (!acquired) throw new Error("another skills installation is already running; retry after it finishes");
|
|
@@ -249,7 +96,7 @@ async function acquireSkillsInstallLock(run) {
|
|
|
249
96
|
}
|
|
250
97
|
async function recoverStaleSkillsInstallLock(lockPath) {
|
|
251
98
|
const lockRaw = await readFile(lockPath, "utf8").catch((error) => {
|
|
252
|
-
if (getErrorCode(error) === "ENOENT") return "";
|
|
99
|
+
if (getErrorCode$1(error) === "ENOENT") return "";
|
|
253
100
|
throw error;
|
|
254
101
|
});
|
|
255
102
|
if (!lockRaw) return true;
|
|
@@ -260,7 +107,7 @@ async function recoverStaleSkillsInstallLock(lockPath) {
|
|
|
260
107
|
}
|
|
261
108
|
if (pid && isPidAlive(pid)) return false;
|
|
262
109
|
const latestLockRaw = await readFile(lockPath, "utf8").catch((error) => {
|
|
263
|
-
if (getErrorCode(error) === "ENOENT") return "";
|
|
110
|
+
if (getErrorCode$1(error) === "ENOENT") return "";
|
|
264
111
|
throw error;
|
|
265
112
|
});
|
|
266
113
|
if (!latestLockRaw) return true;
|
|
@@ -271,7 +118,7 @@ async function recoverStaleSkillsInstallLock(lockPath) {
|
|
|
271
118
|
afterPid: resolveLockPid(parseLockMeta(latestLockRaw))
|
|
272
119
|
})) return false;
|
|
273
120
|
await rm(lockPath, { force: true }).catch((error) => {
|
|
274
|
-
if (getErrorCode(error) === "ENOENT") return;
|
|
121
|
+
if (getErrorCode$1(error) === "ENOENT") return;
|
|
275
122
|
throw error;
|
|
276
123
|
});
|
|
277
124
|
return true;
|
|
@@ -293,10 +140,10 @@ function isPidAlive(pid) {
|
|
|
293
140
|
process.kill(pid, 0);
|
|
294
141
|
return true;
|
|
295
142
|
} catch (error) {
|
|
296
|
-
return getErrorCode(error) !== "ESRCH";
|
|
143
|
+
return getErrorCode$1(error) !== "ESRCH";
|
|
297
144
|
}
|
|
298
145
|
}
|
|
299
|
-
function getErrorCode(error) {
|
|
146
|
+
function getErrorCode$1(error) {
|
|
300
147
|
if (!error || typeof error !== "object") return;
|
|
301
148
|
const code = error.code;
|
|
302
149
|
return typeof code === "string" ? code : void 0;
|
|
@@ -306,6 +153,219 @@ function isSameLockSnapshot(input) {
|
|
|
306
153
|
return input.beforePid === input.afterPid;
|
|
307
154
|
}
|
|
308
155
|
//#endregion
|
|
156
|
+
//#region src/skills/replace-directory.ts
|
|
157
|
+
async function recoverSkillReplacement(target) {
|
|
158
|
+
const previous = join(dirname(target), `.${basename(target)}.openmeld-previous`);
|
|
159
|
+
if (!await exists(previous)) return;
|
|
160
|
+
if (!await isManagedReplacement(previous, basename(target))) throw new Error("OpenMeld Skill backup conflicts with an unmanaged path");
|
|
161
|
+
if (await exists(target)) {
|
|
162
|
+
if (!await isManagedReplacement(target, basename(target))) throw new Error("OpenMeld Skill recovery would affect an unmanaged path");
|
|
163
|
+
await rm(previous, {
|
|
164
|
+
recursive: true,
|
|
165
|
+
force: true
|
|
166
|
+
});
|
|
167
|
+
} else await rename(previous, target);
|
|
168
|
+
}
|
|
169
|
+
async function replaceSkillDirectory(target, prepare) {
|
|
170
|
+
await recoverSkillReplacement(target);
|
|
171
|
+
const staged = join(dirname(target), `.${basename(target)}.openmeld-staged-${randomUUID()}`);
|
|
172
|
+
const previous = join(dirname(target), `.${basename(target)}.openmeld-previous`);
|
|
173
|
+
try {
|
|
174
|
+
await prepare(staged);
|
|
175
|
+
const hadPrevious = await exists(target);
|
|
176
|
+
if (hadPrevious) await rename(target, previous);
|
|
177
|
+
try {
|
|
178
|
+
await rename(staged, target);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (hadPrevious) await rename(previous, target);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
await rm(previous, {
|
|
184
|
+
recursive: true,
|
|
185
|
+
force: true
|
|
186
|
+
}).catch(() => void 0);
|
|
187
|
+
} finally {
|
|
188
|
+
await rm(staged, {
|
|
189
|
+
recursive: true,
|
|
190
|
+
force: true
|
|
191
|
+
}).catch(() => void 0);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function isManagedReplacement(path, skillId) {
|
|
195
|
+
const raw = await readFile(join(path, ".openmeld-skill.json"), "utf8").catch(() => null);
|
|
196
|
+
if (raw) try {
|
|
197
|
+
const marker = JSON.parse(raw);
|
|
198
|
+
return marker.managedBy === "openmeld" && marker.skillId === skillId;
|
|
199
|
+
} catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
if ((await lstat(path)).isSymbolicLink()) {
|
|
203
|
+
const destination = resolve(dirname(path), await readlink(path));
|
|
204
|
+
return basename(destination) === skillId && !await exists(destination);
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
function exists(path) {
|
|
209
|
+
return lstat(path).then(() => true, (error) => {
|
|
210
|
+
if (error.code === "ENOENT") return false;
|
|
211
|
+
throw error;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/skills/content-hash.ts
|
|
216
|
+
async function computeDirectoryContentHash(input) {
|
|
217
|
+
const ignoredNames = new Set(input.ignoredNames ?? []);
|
|
218
|
+
const files = await collectFiles({
|
|
219
|
+
dir: input.rootDir,
|
|
220
|
+
rootDir: input.rootDir,
|
|
221
|
+
ignoredNames
|
|
222
|
+
});
|
|
223
|
+
const hash = createHash("sha256");
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
hash.update(`file:${file.relativePath}\n`);
|
|
226
|
+
hash.update(file.content);
|
|
227
|
+
hash.update("\n");
|
|
228
|
+
}
|
|
229
|
+
return hash.digest("hex");
|
|
230
|
+
}
|
|
231
|
+
async function collectFiles(input) {
|
|
232
|
+
const entries = await readdir(input.dir, { withFileTypes: true }).catch((error) => {
|
|
233
|
+
if (getErrorCode(error) === "ENOENT") return [];
|
|
234
|
+
warnDirectoryReadError(input.dir, error);
|
|
235
|
+
throw error;
|
|
236
|
+
});
|
|
237
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
238
|
+
const files = [];
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
if (input.ignoredNames.has(entry.name)) continue;
|
|
241
|
+
const absolutePath = join(input.dir, entry.name);
|
|
242
|
+
if (entry.isDirectory()) {
|
|
243
|
+
files.push(...await collectFiles({
|
|
244
|
+
dir: absolutePath,
|
|
245
|
+
rootDir: input.rootDir,
|
|
246
|
+
ignoredNames: input.ignoredNames
|
|
247
|
+
}));
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (!entry.isFile()) continue;
|
|
251
|
+
const content = await readFile(absolutePath);
|
|
252
|
+
files.push({
|
|
253
|
+
relativePath: normalizeRelativePath(relative(input.rootDir, absolutePath)),
|
|
254
|
+
content
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return files;
|
|
258
|
+
}
|
|
259
|
+
function normalizeRelativePath(value) {
|
|
260
|
+
return value.split("\\").join("/");
|
|
261
|
+
}
|
|
262
|
+
function getErrorCode(error) {
|
|
263
|
+
if (!error || typeof error !== "object") return;
|
|
264
|
+
const code = error.code;
|
|
265
|
+
return typeof code === "string" ? code : void 0;
|
|
266
|
+
}
|
|
267
|
+
function warnDirectoryReadError(dir, error) {
|
|
268
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
269
|
+
process.stderr.write(`[openmeld] failed to read directory while hashing skills content (${dir}): ${reason}\n`);
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/skills/bundled.ts
|
|
273
|
+
const CLI_PACKAGE_JSON = "package.json";
|
|
274
|
+
/**
|
|
275
|
+
* Locate the on-disk bundled skills directory for the npm distribution by
|
|
276
|
+
* walking up from this module to the CLI package root (a directory that has
|
|
277
|
+
* both package.json and skills/). Returns null under the binary distribution,
|
|
278
|
+
* where import.meta.url is a virtual path and the skills are embedded instead —
|
|
279
|
+
* callers in that mode go through bundled-assets.ts, not this function.
|
|
280
|
+
*/
|
|
281
|
+
function resolveBundledSkillsLocation() {
|
|
282
|
+
const cliRootDir = resolveCliRootDir(path.dirname(fileURLToPath(import.meta.url)));
|
|
283
|
+
if (!cliRootDir) return null;
|
|
284
|
+
return {
|
|
285
|
+
cliRootDir,
|
|
286
|
+
skillsRootDir: path.join(cliRootDir, "skills")
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function resolveCliRootDir(fromDir) {
|
|
290
|
+
let current = fromDir;
|
|
291
|
+
for (;;) {
|
|
292
|
+
const hasPackageJson = existsSync(path.join(current, CLI_PACKAGE_JSON));
|
|
293
|
+
const hasSkillsDir = existsSync(path.join(current, "skills"));
|
|
294
|
+
if (hasPackageJson && hasSkillsDir) return current;
|
|
295
|
+
const parent = path.dirname(current);
|
|
296
|
+
if (parent === current) return null;
|
|
297
|
+
current = parent;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/skills/bundled-assets-fs.ts
|
|
302
|
+
/**
|
|
303
|
+
* npm-distribution source: read the bundled skills from the on-disk `skills/`
|
|
304
|
+
* directory next to the CLI package. Assets are sorted by relative path so the
|
|
305
|
+
* order (and therefore any content hash computed from them) is stable and
|
|
306
|
+
* matches the embedded distribution.
|
|
307
|
+
*/
|
|
308
|
+
async function loadFsBundledSkillAssets() {
|
|
309
|
+
const location = resolveBundledSkillsLocation();
|
|
310
|
+
if (!location) throw new Error("unable to resolve bundled OpenMeld skills directory");
|
|
311
|
+
return sortBundledSkillAssets(await collectSkillAssets(location.skillsRootDir, location.skillsRootDir));
|
|
312
|
+
}
|
|
313
|
+
function sortBundledSkillAssets(assets) {
|
|
314
|
+
return [...assets].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
315
|
+
}
|
|
316
|
+
async function collectSkillAssets(dir, rootDir) {
|
|
317
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
318
|
+
const assets = [];
|
|
319
|
+
for (const entry of entries) {
|
|
320
|
+
const absolutePath = join(dir, entry.name);
|
|
321
|
+
if (entry.isDirectory()) {
|
|
322
|
+
assets.push(...await collectSkillAssets(absolutePath, rootDir));
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (!entry.isFile()) continue;
|
|
326
|
+
assets.push({
|
|
327
|
+
relativePath: toPosixRelativePath(relative(rootDir, absolutePath)),
|
|
328
|
+
content: await readFile(absolutePath)
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return assets;
|
|
332
|
+
}
|
|
333
|
+
function toPosixRelativePath(value) {
|
|
334
|
+
return value.split("\\").join("/");
|
|
335
|
+
}
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region src/skills/bundled-assets.ts
|
|
338
|
+
/**
|
|
339
|
+
* Load the OpenMeld-managed skill files that ship with this CLI build.
|
|
340
|
+
*
|
|
341
|
+
* - npm distribution: read from the on-disk `skills/` directory.
|
|
342
|
+
* - binary distribution: read from files embedded into the single binary at
|
|
343
|
+
* compile time. The embedded module is a Bun-only island resolved only by
|
|
344
|
+
* `bun build --compile`; the npm build never reaches this branch and marks
|
|
345
|
+
* the module external, so its `with { type: "file" }` imports are never
|
|
346
|
+
* parsed by tsdown.
|
|
347
|
+
*/
|
|
348
|
+
async function loadBundledSkillAssets() {
|
|
349
|
+
if (isBinaryDistribution()) return (await import("./skills/bundled-assets-embedded")).loadEmbeddedBundledSkillAssets();
|
|
350
|
+
return loadFsBundledSkillAssets();
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Content hash over an ordered list of skill assets. Byte-for-byte compatible
|
|
354
|
+
* with `computeDirectoryContentHash` (same `file:<relativePath>\n<content>\n`
|
|
355
|
+
* framing) so the embedded and on-disk sources produce identical hashes as long
|
|
356
|
+
* as `assets` is sorted by relative path (as `loadBundledSkillAssets` returns
|
|
357
|
+
* it). This is the single skills-hash algorithm for both distribution forms.
|
|
358
|
+
*/
|
|
359
|
+
function hashBundledSkillAssets(assets) {
|
|
360
|
+
const hash = createHash("sha256");
|
|
361
|
+
for (const asset of assets) {
|
|
362
|
+
hash.update(`file:${asset.relativePath}\n`);
|
|
363
|
+
hash.update(asset.content);
|
|
364
|
+
hash.update("\n");
|
|
365
|
+
}
|
|
366
|
+
return hash.digest("hex");
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
309
369
|
//#region src/skills/official-release.ts
|
|
310
370
|
const CACHE_FILE = "official-cli-skill.json";
|
|
311
371
|
function verifyOfficialSkillRelease(value) {
|
|
@@ -390,83 +450,24 @@ async function downloadOfficialSkillUpdate() {
|
|
|
390
450
|
if (size > 1048576) throw new Error("OpenMeld Skill download exceeds the size limit");
|
|
391
451
|
chunks.push(chunk);
|
|
392
452
|
}
|
|
393
|
-
const selected = selectOfficialSkillRelease(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
394
|
-
if (!selected) return "incompatible";
|
|
395
|
-
return withSkillsInstallLock(async () => {
|
|
396
|
-
const cached = await readCachedRelease();
|
|
397
|
-
if (cached && cached.payload.revision >= selected.payload.revision) return "current";
|
|
398
|
-
await mkdir(openMeldRootDir(), { recursive: true });
|
|
399
|
-
const cache = join(openMeldRootDir(), CACHE_FILE);
|
|
400
|
-
const staged = `${cache}.${randomUUID()}.tmp`;
|
|
401
|
-
try {
|
|
402
|
-
await writeFile(staged, JSON.stringify(selected.release), {
|
|
403
|
-
flag: "wx",
|
|
404
|
-
mode: 384
|
|
405
|
-
});
|
|
406
|
-
await rename(staged, cache);
|
|
407
|
-
} finally {
|
|
408
|
-
await rm(staged, { force: true });
|
|
409
|
-
}
|
|
410
|
-
return "updated";
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
//#endregion
|
|
414
|
-
//#region src/skills/replace-directory.ts
|
|
415
|
-
async function recoverSkillReplacement(target) {
|
|
416
|
-
const previous = join(dirname(target), `.${basename(target)}.openmeld-previous`);
|
|
417
|
-
if (!await exists(previous)) return;
|
|
418
|
-
if (!await isManagedReplacement(previous, basename(target))) throw new Error("OpenMeld Skill backup conflicts with an unmanaged path");
|
|
419
|
-
if (await exists(target)) {
|
|
420
|
-
if (!await isManagedReplacement(target, basename(target))) throw new Error("OpenMeld Skill recovery would affect an unmanaged path");
|
|
421
|
-
await rm(previous, {
|
|
422
|
-
recursive: true,
|
|
423
|
-
force: true
|
|
424
|
-
});
|
|
425
|
-
} else await rename(previous, target);
|
|
426
|
-
}
|
|
427
|
-
async function replaceSkillDirectory(target, prepare) {
|
|
428
|
-
await recoverSkillReplacement(target);
|
|
429
|
-
const staged = join(dirname(target), `.${basename(target)}.openmeld-staged-${randomUUID()}`);
|
|
430
|
-
const previous = join(dirname(target), `.${basename(target)}.openmeld-previous`);
|
|
431
|
-
try {
|
|
432
|
-
await prepare(staged);
|
|
433
|
-
const hadPrevious = await exists(target);
|
|
434
|
-
if (hadPrevious) await rename(target, previous);
|
|
435
|
-
try {
|
|
436
|
-
await rename(staged, target);
|
|
437
|
-
} catch (error) {
|
|
438
|
-
if (hadPrevious) await rename(previous, target);
|
|
439
|
-
throw error;
|
|
440
|
-
}
|
|
441
|
-
await rm(previous, {
|
|
442
|
-
recursive: true,
|
|
443
|
-
force: true
|
|
444
|
-
}).catch(() => void 0);
|
|
445
|
-
} finally {
|
|
446
|
-
await rm(staged, {
|
|
447
|
-
recursive: true,
|
|
448
|
-
force: true
|
|
449
|
-
}).catch(() => void 0);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
async function isManagedReplacement(path, skillId) {
|
|
453
|
-
const raw = await readFile(join(path, ".openmeld-skill.json"), "utf8").catch(() => null);
|
|
454
|
-
if (raw) try {
|
|
455
|
-
const marker = JSON.parse(raw);
|
|
456
|
-
return marker.managedBy === "openmeld" && marker.skillId === skillId;
|
|
457
|
-
} catch {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
if ((await lstat(path)).isSymbolicLink()) {
|
|
461
|
-
const destination = resolve(dirname(path), await readlink(path));
|
|
462
|
-
return basename(destination) === skillId && !await exists(destination);
|
|
463
|
-
}
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
|
-
function exists(path) {
|
|
467
|
-
return lstat(path).then(() => true, (error) => {
|
|
468
|
-
if (error.code === "ENOENT") return false;
|
|
469
|
-
throw error;
|
|
453
|
+
const selected = selectOfficialSkillRelease(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
454
|
+
if (!selected) return "incompatible";
|
|
455
|
+
return withSkillsInstallLock(async () => {
|
|
456
|
+
const cached = await readCachedRelease();
|
|
457
|
+
if (cached && cached.payload.revision >= selected.payload.revision) return "current";
|
|
458
|
+
await mkdir(openMeldRootDir(), { recursive: true });
|
|
459
|
+
const cache = join(openMeldRootDir(), CACHE_FILE);
|
|
460
|
+
const staged = `${cache}.${randomUUID()}.tmp`;
|
|
461
|
+
try {
|
|
462
|
+
await writeFile(staged, JSON.stringify(selected.release), {
|
|
463
|
+
flag: "wx",
|
|
464
|
+
mode: 384
|
|
465
|
+
});
|
|
466
|
+
await rename(staged, cache);
|
|
467
|
+
} finally {
|
|
468
|
+
await rm(staged, { force: true });
|
|
469
|
+
}
|
|
470
|
+
return "updated";
|
|
470
471
|
});
|
|
471
472
|
}
|
|
472
473
|
//#endregion
|
|
@@ -746,6 +747,122 @@ function pathsEqual(left, right) {
|
|
|
746
747
|
return resolve(left) === resolve(right);
|
|
747
748
|
}
|
|
748
749
|
//#endregion
|
|
750
|
+
//#region src/skills/installer.ts
|
|
751
|
+
function installSkillsToTargets(input) {
|
|
752
|
+
return withSkillsInstallLock(() => installSkillsToTargetsLocked(input));
|
|
753
|
+
}
|
|
754
|
+
async function installSkillsToTargetsLocked(input) {
|
|
755
|
+
const results = [];
|
|
756
|
+
for (const target of input.targets) for (const skill of input.skills) {
|
|
757
|
+
const result = await installOneSkillToTarget({
|
|
758
|
+
skill,
|
|
759
|
+
target
|
|
760
|
+
});
|
|
761
|
+
results.push(result);
|
|
762
|
+
}
|
|
763
|
+
return results;
|
|
764
|
+
}
|
|
765
|
+
async function installOneSkillToTarget(input) {
|
|
766
|
+
const targetDir = input.target.dir;
|
|
767
|
+
const failInstall = (reason) => ({
|
|
768
|
+
targetId: input.target.id,
|
|
769
|
+
targetDir,
|
|
770
|
+
targetType: input.target.type,
|
|
771
|
+
skillId: input.skill.id,
|
|
772
|
+
status: "failed",
|
|
773
|
+
mode: "unknown",
|
|
774
|
+
degraded: false,
|
|
775
|
+
reason
|
|
776
|
+
});
|
|
777
|
+
if (!isAbsolute(targetDir)) return failInstall("target dir must be an absolute path");
|
|
778
|
+
const skillId = input.skill.id;
|
|
779
|
+
if (skillId.trim().length === 0 || isAbsolute(skillId) || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\")) return failInstall("skill id would escape target dir");
|
|
780
|
+
const resolvedTargetDir = resolve(targetDir);
|
|
781
|
+
const resolvedSkillDir = resolve(resolvedTargetDir, skillId);
|
|
782
|
+
const skillRelativePath = relative(resolvedTargetDir, resolvedSkillDir);
|
|
783
|
+
if (skillRelativePath === ".." || skillRelativePath.startsWith("../") || skillRelativePath.startsWith("..\\")) return failInstall("skill id would escape target dir");
|
|
784
|
+
const skillTargetDir = resolvedSkillDir;
|
|
785
|
+
await mkdir(targetDir, { recursive: true });
|
|
786
|
+
await recoverSkillReplacement(skillTargetDir);
|
|
787
|
+
const state = await resolveSkillInstallStateForTarget({
|
|
788
|
+
skill: input.skill,
|
|
789
|
+
target: input.target
|
|
790
|
+
});
|
|
791
|
+
if (state.state === "external_conflict") return {
|
|
792
|
+
targetId: input.target.id,
|
|
793
|
+
targetDir,
|
|
794
|
+
targetType: input.target.type,
|
|
795
|
+
skillId: input.skill.id,
|
|
796
|
+
status: "skipped",
|
|
797
|
+
mode: resolveSkippedInstallMode(state),
|
|
798
|
+
degraded: false,
|
|
799
|
+
reason: state.reason
|
|
800
|
+
};
|
|
801
|
+
if (state.state === "up_to_date") return {
|
|
802
|
+
targetId: input.target.id,
|
|
803
|
+
targetDir,
|
|
804
|
+
targetType: input.target.type,
|
|
805
|
+
skillId: input.skill.id,
|
|
806
|
+
status: "skipped",
|
|
807
|
+
mode: resolveSkippedInstallMode(state),
|
|
808
|
+
degraded: false,
|
|
809
|
+
reason: state.reason
|
|
810
|
+
};
|
|
811
|
+
const installation = { mode: "symlink" };
|
|
812
|
+
try {
|
|
813
|
+
await readFile(join(input.skill.absolutePath, "SKILL.md"), "utf8");
|
|
814
|
+
await replaceSkillDirectory(skillTargetDir, async (stagedDir) => {
|
|
815
|
+
if (await tryCreateSymlink({
|
|
816
|
+
sourceDir: input.skill.absolutePath,
|
|
817
|
+
targetDir: stagedDir
|
|
818
|
+
})) return;
|
|
819
|
+
installation.mode = "copy";
|
|
820
|
+
await cp(input.skill.absolutePath, stagedDir, {
|
|
821
|
+
recursive: true,
|
|
822
|
+
errorOnExist: true,
|
|
823
|
+
force: false
|
|
824
|
+
});
|
|
825
|
+
await writeOpenMeldSkillMarker({
|
|
826
|
+
skillDir: stagedDir,
|
|
827
|
+
skillId: input.skill.id,
|
|
828
|
+
mode: "copy",
|
|
829
|
+
...input.skill.contentHash ? { contentHash: input.skill.contentHash } : {},
|
|
830
|
+
...input.skill.version ? { version: input.skill.version } : {}
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
return {
|
|
834
|
+
targetId: input.target.id,
|
|
835
|
+
targetDir,
|
|
836
|
+
targetType: input.target.type,
|
|
837
|
+
skillId: input.skill.id,
|
|
838
|
+
status: "installed",
|
|
839
|
+
mode: installation.mode,
|
|
840
|
+
degraded: installation.mode === "copy",
|
|
841
|
+
...installation.mode === "copy" ? { reason: "symlink failed; fell back to copy" } : {}
|
|
842
|
+
};
|
|
843
|
+
} catch (error) {
|
|
844
|
+
return failInstall(error instanceof Error ? error.message : "failed to install skill to target");
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function resolveSkippedInstallMode(state) {
|
|
848
|
+
if (state.targetMode === "copy") return "copy";
|
|
849
|
+
if (state.targetMode === "symlink") return "symlink";
|
|
850
|
+
return "unknown";
|
|
851
|
+
}
|
|
852
|
+
async function tryCreateSymlink(input) {
|
|
853
|
+
try {
|
|
854
|
+
await mkdir(dirname(input.targetDir), { recursive: true });
|
|
855
|
+
const targetParentDir = dirname(input.targetDir);
|
|
856
|
+
const resolvedTargetParentDir = await realpath(targetParentDir).catch(() => targetParentDir);
|
|
857
|
+
const resolvedSourceDir = await realpath(input.sourceDir).catch(() => input.sourceDir);
|
|
858
|
+
const relativeTarget = relative(resolvedTargetParentDir, resolvedSourceDir);
|
|
859
|
+
await symlink(process$1.platform === "win32" ? resolvedSourceDir : relativeTarget, input.targetDir, process$1.platform === "win32" ? "junction" : void 0);
|
|
860
|
+
return true;
|
|
861
|
+
} catch {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
//#endregion
|
|
749
866
|
//#region src/skills/installation-inventory.ts
|
|
750
867
|
async function collectSkillsReadinessSnapshot() {
|
|
751
868
|
const skillsConfig = await getConfiguredSkillsConfig();
|
|
@@ -979,122 +1096,6 @@ function normalizeInstallRecordForCompare(input) {
|
|
|
979
1096
|
};
|
|
980
1097
|
}
|
|
981
1098
|
//#endregion
|
|
982
|
-
|
|
983
|
-
function installSkillsToTargets(input) {
|
|
984
|
-
return withSkillsInstallLock(() => installSkillsToTargetsLocked(input));
|
|
985
|
-
}
|
|
986
|
-
async function installSkillsToTargetsLocked(input) {
|
|
987
|
-
const results = [];
|
|
988
|
-
for (const target of input.targets) for (const skill of input.skills) {
|
|
989
|
-
const result = await installOneSkillToTarget({
|
|
990
|
-
skill,
|
|
991
|
-
target
|
|
992
|
-
});
|
|
993
|
-
results.push(result);
|
|
994
|
-
}
|
|
995
|
-
return results;
|
|
996
|
-
}
|
|
997
|
-
async function installOneSkillToTarget(input) {
|
|
998
|
-
const targetDir = input.target.dir;
|
|
999
|
-
const failInstall = (reason) => ({
|
|
1000
|
-
targetId: input.target.id,
|
|
1001
|
-
targetDir,
|
|
1002
|
-
targetType: input.target.type,
|
|
1003
|
-
skillId: input.skill.id,
|
|
1004
|
-
status: "failed",
|
|
1005
|
-
mode: "unknown",
|
|
1006
|
-
degraded: false,
|
|
1007
|
-
reason
|
|
1008
|
-
});
|
|
1009
|
-
if (!isAbsolute(targetDir)) return failInstall("target dir must be an absolute path");
|
|
1010
|
-
const skillId = input.skill.id;
|
|
1011
|
-
if (skillId.trim().length === 0 || isAbsolute(skillId) || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\")) return failInstall("skill id would escape target dir");
|
|
1012
|
-
const resolvedTargetDir = resolve(targetDir);
|
|
1013
|
-
const resolvedSkillDir = resolve(resolvedTargetDir, skillId);
|
|
1014
|
-
const skillRelativePath = relative(resolvedTargetDir, resolvedSkillDir);
|
|
1015
|
-
if (skillRelativePath === ".." || skillRelativePath.startsWith("../") || skillRelativePath.startsWith("..\\")) return failInstall("skill id would escape target dir");
|
|
1016
|
-
const skillTargetDir = resolvedSkillDir;
|
|
1017
|
-
await mkdir(targetDir, { recursive: true });
|
|
1018
|
-
await recoverSkillReplacement(skillTargetDir);
|
|
1019
|
-
const state = await resolveSkillInstallStateForTarget({
|
|
1020
|
-
skill: input.skill,
|
|
1021
|
-
target: input.target
|
|
1022
|
-
});
|
|
1023
|
-
if (state.state === "external_conflict") return {
|
|
1024
|
-
targetId: input.target.id,
|
|
1025
|
-
targetDir,
|
|
1026
|
-
targetType: input.target.type,
|
|
1027
|
-
skillId: input.skill.id,
|
|
1028
|
-
status: "skipped",
|
|
1029
|
-
mode: resolveSkippedInstallMode(state),
|
|
1030
|
-
degraded: false,
|
|
1031
|
-
reason: state.reason
|
|
1032
|
-
};
|
|
1033
|
-
if (state.state === "up_to_date") return {
|
|
1034
|
-
targetId: input.target.id,
|
|
1035
|
-
targetDir,
|
|
1036
|
-
targetType: input.target.type,
|
|
1037
|
-
skillId: input.skill.id,
|
|
1038
|
-
status: "skipped",
|
|
1039
|
-
mode: resolveSkippedInstallMode(state),
|
|
1040
|
-
degraded: false,
|
|
1041
|
-
reason: state.reason
|
|
1042
|
-
};
|
|
1043
|
-
const installation = { mode: "symlink" };
|
|
1044
|
-
try {
|
|
1045
|
-
await readFile(join(input.skill.absolutePath, "SKILL.md"), "utf8");
|
|
1046
|
-
await replaceSkillDirectory(skillTargetDir, async (stagedDir) => {
|
|
1047
|
-
if (await tryCreateSymlink({
|
|
1048
|
-
sourceDir: input.skill.absolutePath,
|
|
1049
|
-
targetDir: stagedDir
|
|
1050
|
-
})) return;
|
|
1051
|
-
installation.mode = "copy";
|
|
1052
|
-
await cp(input.skill.absolutePath, stagedDir, {
|
|
1053
|
-
recursive: true,
|
|
1054
|
-
errorOnExist: true,
|
|
1055
|
-
force: false
|
|
1056
|
-
});
|
|
1057
|
-
await writeOpenMeldSkillMarker({
|
|
1058
|
-
skillDir: stagedDir,
|
|
1059
|
-
skillId: input.skill.id,
|
|
1060
|
-
mode: "copy",
|
|
1061
|
-
...input.skill.contentHash ? { contentHash: input.skill.contentHash } : {},
|
|
1062
|
-
...input.skill.version ? { version: input.skill.version } : {}
|
|
1063
|
-
});
|
|
1064
|
-
});
|
|
1065
|
-
return {
|
|
1066
|
-
targetId: input.target.id,
|
|
1067
|
-
targetDir,
|
|
1068
|
-
targetType: input.target.type,
|
|
1069
|
-
skillId: input.skill.id,
|
|
1070
|
-
status: "installed",
|
|
1071
|
-
mode: installation.mode,
|
|
1072
|
-
degraded: installation.mode === "copy",
|
|
1073
|
-
...installation.mode === "copy" ? { reason: "symlink failed; fell back to copy" } : {}
|
|
1074
|
-
};
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
return failInstall(error instanceof Error ? error.message : "failed to install skill to target");
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
function resolveSkippedInstallMode(state) {
|
|
1080
|
-
if (state.targetMode === "copy") return "copy";
|
|
1081
|
-
if (state.targetMode === "symlink") return "symlink";
|
|
1082
|
-
return "unknown";
|
|
1083
|
-
}
|
|
1084
|
-
async function tryCreateSymlink(input) {
|
|
1085
|
-
try {
|
|
1086
|
-
await mkdir(dirname(input.targetDir), { recursive: true });
|
|
1087
|
-
const targetParentDir = dirname(input.targetDir);
|
|
1088
|
-
const resolvedTargetParentDir = await realpath(targetParentDir).catch(() => targetParentDir);
|
|
1089
|
-
const resolvedSourceDir = await realpath(input.sourceDir).catch(() => input.sourceDir);
|
|
1090
|
-
const relativeTarget = relative(resolvedTargetParentDir, resolvedSourceDir);
|
|
1091
|
-
await symlink(process$1.platform === "win32" ? resolvedSourceDir : relativeTarget, input.targetDir, process$1.platform === "win32" ? "junction" : void 0);
|
|
1092
|
-
return true;
|
|
1093
|
-
} catch {
|
|
1094
|
-
return false;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
//#endregion
|
|
1098
|
-
export { readSelectedSkillDocument as _, filterTargetsWithInstalledOpenMeldSkills as a, hashBundledSkillAssets as b, resolveTrackedInstallTargets as c, resolveSkillsInstallStates as d, ensureLocalSkills as f, loadSelectedSkillAssets as g, downloadOfficialSkillUpdate as h, compareInstallRecords as i, summarizeSkillState as l, readOpenMeldSkillMarker as m, buildAgentTargets as n, mergeTargetsByDir as o, isOpenMeldManagedSkillDir as p, collectSkillsReadinessSnapshot as r, persistDetectedInstallStateFromCheck as s, installSkillsToTargets as t, resolveSkillInstallStateForTarget as u, withSkillsInstallLock as v, OFFICIAL_CLI_SKILL_URL as x, computeDirectoryContentHash as y };
|
|
1099
|
+
export { readSelectedSkillDocument as _, mergeTargetsByDir as a, withSkillsInstallLock as b, summarizeSkillState as c, resolveSkillsInstallStates as d, ensureLocalSkills as f, loadSelectedSkillAssets as g, downloadOfficialSkillUpdate as h, filterTargetsWithInstalledOpenMeldSkills as i, installSkillsToTargets as l, readOpenMeldSkillMarker as m, collectSkillsReadinessSnapshot as n, persistDetectedInstallStateFromCheck as o, isOpenMeldManagedSkillDir as p, compareInstallRecords as r, resolveTrackedInstallTargets as s, buildAgentTargets as t, resolveSkillInstallStateForTarget as u, hashBundledSkillAssets as v, OFFICIAL_CLI_SKILL_URL as x, computeDirectoryContentHash as y };
|
|
1099
1100
|
|
|
1100
|
-
//# sourceMappingURL=
|
|
1101
|
+
//# sourceMappingURL=installation-inventory-DJlwXa1q.js.map
|