@foxian/esl 0.1.0
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/LICENSE +21 -0
- package/dist/api-error.d.ts +13 -0
- package/dist/api-error.js +31 -0
- package/dist/bin/esl.d.ts +35 -0
- package/dist/bin/esl.js +1033 -0
- package/dist/builtin/builtin-packages.json +11 -0
- package/dist/builtin/esl-operator/SKILL.md +65 -0
- package/dist/builtin/esl-operator/references/agent-interaction.md +110 -0
- package/dist/builtin/esl-operator/references/author.md +98 -0
- package/dist/builtin/esl-operator/references/consumer.md +109 -0
- package/dist/builtin/esl-operator/references/setup.md +41 -0
- package/dist/builtin/esl-operator/skill.json +7 -0
- package/dist/builtin-dir.d.ts +1 -0
- package/dist/builtin-dir.js +11 -0
- package/dist/commands/adapt.d.ts +8 -0
- package/dist/commands/adapt.js +27 -0
- package/dist/commands/admin.d.ts +11 -0
- package/dist/commands/admin.js +96 -0
- package/dist/commands/clone.d.ts +10 -0
- package/dist/commands/clone.js +21 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +19 -0
- package/dist/commands/delete.d.ts +13 -0
- package/dist/commands/delete.js +51 -0
- package/dist/commands/deprecate.d.ts +16 -0
- package/dist/commands/deprecate.js +29 -0
- package/dist/commands/import.d.ts +14 -0
- package/dist/commands/import.js +21 -0
- package/dist/commands/info.d.ts +28 -0
- package/dist/commands/info.js +69 -0
- package/dist/commands/init.d.ts +28 -0
- package/dist/commands/init.js +252 -0
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +435 -0
- package/dist/commands/link.d.ts +11 -0
- package/dist/commands/link.js +170 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +7 -0
- package/dist/commands/login.d.ts +20 -0
- package/dist/commands/login.js +132 -0
- package/dist/commands/logout.d.ts +6 -0
- package/dist/commands/logout.js +18 -0
- package/dist/commands/network-options.d.ts +27 -0
- package/dist/commands/network-options.js +133 -0
- package/dist/commands/notes.d.ts +10 -0
- package/dist/commands/notes.js +24 -0
- package/dist/commands/publish.d.ts +19 -0
- package/dist/commands/publish.js +271 -0
- package/dist/commands/release-delete.d.ts +21 -0
- package/dist/commands/release-delete.js +34 -0
- package/dist/commands/release-manifest.d.ts +5 -0
- package/dist/commands/release-manifest.js +23 -0
- package/dist/commands/rename.d.ts +5 -0
- package/dist/commands/rename.js +25 -0
- package/dist/commands/repair-tag.d.ts +5 -0
- package/dist/commands/repair-tag.js +14 -0
- package/dist/commands/reset-source.d.ts +16 -0
- package/dist/commands/reset-source.js +71 -0
- package/dist/commands/resolve-unlink-identity.d.ts +14 -0
- package/dist/commands/resolve-unlink-identity.js +47 -0
- package/dist/commands/search.d.ts +19 -0
- package/dist/commands/search.js +33 -0
- package/dist/commands/share.d.ts +17 -0
- package/dist/commands/share.js +56 -0
- package/dist/commands/source.d.ts +10 -0
- package/dist/commands/source.js +34 -0
- package/dist/commands/status.d.ts +16 -0
- package/dist/commands/status.js +77 -0
- package/dist/commands/sync-builtin.d.ts +10 -0
- package/dist/commands/sync-builtin.js +68 -0
- package/dist/commands/tools.d.ts +19 -0
- package/dist/commands/tools.js +73 -0
- package/dist/commands/uninstall.d.ts +19 -0
- package/dist/commands/uninstall.js +75 -0
- package/dist/commands/unlink.d.ts +14 -0
- package/dist/commands/unlink.js +108 -0
- package/dist/commands/update.d.ts +18 -0
- package/dist/commands/update.js +176 -0
- package/dist/commands/upload.d.ts +27 -0
- package/dist/commands/upload.js +446 -0
- package/dist/commands/use.d.ts +9 -0
- package/dist/commands/use.js +51 -0
- package/dist/commands/validate.d.ts +4 -0
- package/dist/commands/validate.js +8 -0
- package/dist/commands/version.d.ts +16 -0
- package/dist/commands/version.js +147 -0
- package/dist/commands/whoami.d.ts +14 -0
- package/dist/commands/whoami.js +54 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -0
- package/dist/output.d.ts +1 -0
- package/dist/output.js +3 -0
- package/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +8 -0
- package/dist/prompt.d.ts +9 -0
- package/dist/prompt.js +24 -0
- package/dist/vendor/core/adapt/adapt-engine.d.ts +24 -0
- package/dist/vendor/core/adapt/adapt-engine.js +99 -0
- package/dist/vendor/core/adapt/adapted-skill-copy.d.ts +2 -0
- package/dist/vendor/core/adapt/adapted-skill-copy.js +17 -0
- package/dist/vendor/core/adapt/claude-adapter.d.ts +8 -0
- package/dist/vendor/core/adapt/claude-adapter.js +19 -0
- package/dist/vendor/core/adapt/codex-adapter.d.ts +8 -0
- package/dist/vendor/core/adapt/codex-adapter.js +19 -0
- package/dist/vendor/core/adapt/index.d.ts +2 -0
- package/dist/vendor/core/adapt/index.js +2 -0
- package/dist/vendor/core/adapt/tool-adapter.d.ts +12 -0
- package/dist/vendor/core/adapt/tool-adapter.js +1 -0
- package/dist/vendor/core/adapt/trae-adapter.d.ts +8 -0
- package/dist/vendor/core/adapt/trae-adapter.js +19 -0
- package/dist/vendor/core/adapt/trae-cn-adapter.d.ts +8 -0
- package/dist/vendor/core/adapt/trae-cn-adapter.js +19 -0
- package/dist/vendor/core/agent-interaction.d.ts +58 -0
- package/dist/vendor/core/agent-interaction.js +58 -0
- package/dist/vendor/core/command-params.d.ts +5 -0
- package/dist/vendor/core/command-params.js +46 -0
- package/dist/vendor/core/index.d.ts +22 -0
- package/dist/vendor/core/index.js +22 -0
- package/dist/vendor/core/link/skill-links.d.ts +38 -0
- package/dist/vendor/core/link/skill-links.js +245 -0
- package/dist/vendor/core/link/tool-links.d.ts +83 -0
- package/dist/vendor/core/link/tool-links.js +376 -0
- package/dist/vendor/core/org/account-policy.d.ts +8 -0
- package/dist/vendor/core/org/account-policy.js +57 -0
- package/dist/vendor/core/org/org-name.d.ts +2 -0
- package/dist/vendor/core/org/org-name.js +22 -0
- package/dist/vendor/core/org/standing-teams.d.ts +10 -0
- package/dist/vendor/core/org/standing-teams.js +27 -0
- package/dist/vendor/core/schema/compatibility.d.ts +17 -0
- package/dist/vendor/core/schema/compatibility.js +45 -0
- package/dist/vendor/core/schema/release-manifest.d.ts +88 -0
- package/dist/vendor/core/schema/release-manifest.js +161 -0
- package/dist/vendor/core/schema/skill-json.d.ts +61 -0
- package/dist/vendor/core/schema/skill-json.js +47 -0
- package/dist/vendor/core/schema/validation-result.d.ts +7 -0
- package/dist/vendor/core/schema/validation-result.js +1 -0
- package/dist/vendor/core/skill/builtin-package.d.ts +32 -0
- package/dist/vendor/core/skill/builtin-package.js +143 -0
- package/dist/vendor/core/skill/directory-validator.d.ts +9 -0
- package/dist/vendor/core/skill/directory-validator.js +72 -0
- package/dist/vendor/core/skill/import-skill.d.ts +10 -0
- package/dist/vendor/core/skill/import-skill.js +68 -0
- package/dist/vendor/core/skill/published-package.d.ts +1 -0
- package/dist/vendor/core/skill/published-package.js +9 -0
- package/dist/vendor/core/skill/skill-md.d.ts +15 -0
- package/dist/vendor/core/skill/skill-md.js +32 -0
- package/dist/vendor/core/store/file-copy.d.ts +3 -0
- package/dist/vendor/core/store/file-copy.js +102 -0
- package/dist/vendor/core/store/local-store.d.ts +43 -0
- package/dist/vendor/core/store/local-store.js +104 -0
- package/dist/vendor/core/store/skill-store.d.ts +24 -0
- package/dist/vendor/core/store/skill-store.js +41 -0
- package/dist/vendor/core/store/skills-json.d.ts +34 -0
- package/dist/vendor/core/store/skills-json.js +146 -0
- package/dist/vendor/core/version/select-version.d.ts +16 -0
- package/dist/vendor/core/version/select-version.js +34 -0
- package/dist/vendor/core/version/version.d.ts +2 -0
- package/dist/vendor/core/version/version.js +25 -0
- package/dist/vendor/i18n/api-errors.d.ts +121 -0
- package/dist/vendor/i18n/api-errors.js +240 -0
- package/dist/vendor/i18n/i18n-runtime.d.ts +4 -0
- package/dist/vendor/i18n/i18n-runtime.js +16 -0
- package/dist/vendor/i18n/index.d.ts +5 -0
- package/dist/vendor/i18n/index.js +5 -0
- package/dist/vendor/i18n/locales.d.ts +9 -0
- package/dist/vendor/i18n/locales.js +33 -0
- package/dist/vendor/i18n/resources.d.ts +11 -0
- package/dist/vendor/i18n/resources.js +925 -0
- package/dist/vendor/i18n/translate-api-error.d.ts +8 -0
- package/dist/vendor/i18n/translate-api-error.js +12 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +24 -0
- package/package.json +46 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { addLockEntry, addSkillDependency, loadConfig, loadInstallManifest, loadSkillsJson, resolveProjectStorePaths, syncToolLinks, SUPPORTED_TOOLS, copySkillDirectory, createMinimalSkillManifest, evaluateCompatibility, fileExists, highestStableVersion, isBuiltinIdentity, loadBuiltinPackageOrThrow, BUILTIN_SPECIFIER_PREFIX, recordInstalledSkill, removeDirectory, resolveLocalStorePaths, resolveToolName, skillSourceRelativeDir, validateReleaseManifest, validateSkillDirectory, validateSkillMd, preparePublishedSkillPackage } from '../vendor/core/index.js';
|
|
8
|
+
import { requireOkResponse } from '../api-error.js';
|
|
9
|
+
import { installTargetDir, gitAuthHeaderConfig, fetchWithTimeout, requireConfigured, requireFreshToken, resolveNetworkConfig, projectSkillsDir, publishedInstallTargetDir, publishedProjectSkillsDir } from './network-options.js';
|
|
10
|
+
import { executeInfo } from './info.js';
|
|
11
|
+
import { notify } from '../output.js';
|
|
12
|
+
import { resolveBuiltinDir } from '../builtin-dir.js';
|
|
13
|
+
const defaultExecFileAsync = promisify(execFile);
|
|
14
|
+
function isLocalPath(nameOrPath) {
|
|
15
|
+
return nameOrPath.startsWith('.') || nameOrPath.startsWith('/') || nameOrPath.startsWith('\\') || path.isAbsolute(nameOrPath);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A deprecated release stays installable — it just carries a warning for whoever
|
|
19
|
+
* lands on it. Silence would hide that a safer release exists.
|
|
20
|
+
*/
|
|
21
|
+
function warnIfDeprecated(name, version, message) {
|
|
22
|
+
if (!message) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.warn(`Warning: ${name}@${version} is deprecated — ${message}`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The version to record for a skill installed from a bare source directory: the
|
|
29
|
+
* Release Manifest's version when the source carries one, and 0.1.0 for a draft
|
|
30
|
+
* directory that has no manifest yet.
|
|
31
|
+
*/
|
|
32
|
+
async function readSourceVersion(directory) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(await fs.readFile(path.join(directory, 'release.json'), 'utf8'));
|
|
35
|
+
const validation = validateReleaseManifest(parsed);
|
|
36
|
+
if (validation.success) {
|
|
37
|
+
return validation.data.version;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// No manifest, or an unreadable one: fall back to the draft version.
|
|
42
|
+
}
|
|
43
|
+
return '0.1.0';
|
|
44
|
+
}
|
|
45
|
+
async function installFromLocalPath(sourcePath, projectRoot, options) {
|
|
46
|
+
const resolved = path.resolve(sourcePath);
|
|
47
|
+
const skillJsonPath = path.join(resolved, 'skill.json');
|
|
48
|
+
let identity;
|
|
49
|
+
let version;
|
|
50
|
+
let needsGeneratedSkillJson = false;
|
|
51
|
+
let generatedDescription = '';
|
|
52
|
+
const packageValidation = await validateSkillDirectory(resolved);
|
|
53
|
+
if (packageValidation.success) {
|
|
54
|
+
identity = packageValidation.data.skillJson.name;
|
|
55
|
+
version = packageValidation.data.skillJson.version;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (await fileExists(skillJsonPath)) {
|
|
59
|
+
throw new Error(`Invalid skill package at ${resolved}: ${packageValidation.errors.join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
if (!(await fileExists(path.join(resolved, 'SKILL.md')))) {
|
|
62
|
+
throw new Error(`Invalid skill package at ${resolved}: ${packageValidation.errors.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
const skillMdValidation = validateSkillMd(await fs.readFile(path.join(resolved, 'SKILL.md'), 'utf8'));
|
|
65
|
+
if (!skillMdValidation.success) {
|
|
66
|
+
throw new Error(`Invalid skill package at ${resolved}: ${skillMdValidation.errors.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
identity = `@local/${skillMdValidation.data.name}`;
|
|
69
|
+
version = await readSourceVersion(resolved);
|
|
70
|
+
needsGeneratedSkillJson = true;
|
|
71
|
+
generatedDescription = skillMdValidation.data.description;
|
|
72
|
+
}
|
|
73
|
+
const storeRoot = options.global
|
|
74
|
+
? resolveLocalStorePaths(options).root
|
|
75
|
+
: resolveProjectStorePaths(projectRoot).root;
|
|
76
|
+
const dependencyRoot = options.global ? storeRoot : projectRoot;
|
|
77
|
+
const targetDir = options.global ? installTargetDir(identity, options) : projectSkillsDir(projectRoot, identity);
|
|
78
|
+
await copySkillDirectory(resolved, targetDir);
|
|
79
|
+
if (needsGeneratedSkillJson) {
|
|
80
|
+
const author = process.env.USER ?? process.env.USERNAME ?? 'anonymous';
|
|
81
|
+
const skillJson = {
|
|
82
|
+
...createMinimalSkillManifest({
|
|
83
|
+
name: identity,
|
|
84
|
+
description: generatedDescription,
|
|
85
|
+
author
|
|
86
|
+
}),
|
|
87
|
+
version
|
|
88
|
+
};
|
|
89
|
+
await fs.writeFile(path.join(targetDir, 'skill.json'), `${JSON.stringify(skillJson, null, 2)}\n`, 'utf8');
|
|
90
|
+
}
|
|
91
|
+
const specifier = `file:${resolved}`;
|
|
92
|
+
const lockEntry = {
|
|
93
|
+
version,
|
|
94
|
+
resolved: specifier,
|
|
95
|
+
integrity: '',
|
|
96
|
+
source: 'local'
|
|
97
|
+
};
|
|
98
|
+
await addSkillDependency(dependencyRoot, identity, specifier);
|
|
99
|
+
await addLockEntry(dependencyRoot, identity, lockEntry);
|
|
100
|
+
await recordInstalledSkill(storeRoot, identity, lockEntry, specifier);
|
|
101
|
+
return targetDir;
|
|
102
|
+
}
|
|
103
|
+
async function installFromBuiltin(name, projectRoot, options) {
|
|
104
|
+
const builtinDir = options.builtinDir ?? resolveBuiltinDir();
|
|
105
|
+
const builtin = await loadBuiltinPackageOrThrow(builtinDir, name);
|
|
106
|
+
const storeRoot = options.global
|
|
107
|
+
? resolveLocalStorePaths(options).root
|
|
108
|
+
: resolveProjectStorePaths(projectRoot).root;
|
|
109
|
+
const dependencyRoot = options.global ? storeRoot : projectRoot;
|
|
110
|
+
const targetDir = options.global
|
|
111
|
+
? installTargetDir(name, options)
|
|
112
|
+
: projectSkillsDir(projectRoot, name);
|
|
113
|
+
await copySkillDirectory(builtin.directory, targetDir);
|
|
114
|
+
const specifier = `${BUILTIN_SPECIFIER_PREFIX}${builtin.shortName}`;
|
|
115
|
+
const lockEntry = {
|
|
116
|
+
identity: name,
|
|
117
|
+
version: builtin.version,
|
|
118
|
+
resolved: specifier,
|
|
119
|
+
integrity: builtin.checksum,
|
|
120
|
+
source: 'builtin'
|
|
121
|
+
};
|
|
122
|
+
await addSkillDependency(dependencyRoot, name, specifier);
|
|
123
|
+
await addLockEntry(dependencyRoot, name, lockEntry);
|
|
124
|
+
await recordInstalledSkill(storeRoot, name, lockEntry, specifier);
|
|
125
|
+
return targetDir;
|
|
126
|
+
}
|
|
127
|
+
async function installFromServer(name, projectRoot, options) {
|
|
128
|
+
const execFileAsync = options.execFileAsync ?? defaultExecFileAsync;
|
|
129
|
+
const authToken = await requireFreshToken(options);
|
|
130
|
+
const info = await executeInfo(name, options);
|
|
131
|
+
const authHeader = gitAuthHeaderConfig(authToken);
|
|
132
|
+
const version = options.version ?? highestStableVersion(info.versions ?? []);
|
|
133
|
+
if (!version) {
|
|
134
|
+
throw new Error(`Skill ${name} has no published Skill Release; use esl source for source access`);
|
|
135
|
+
}
|
|
136
|
+
const requestedRelease = info.releases?.find((release) => release.version === version);
|
|
137
|
+
warnIfDeprecated(name, version, requestedRelease?.deprecatedMessage);
|
|
138
|
+
const requestedPackageUrl = requestedRelease?.packageUrl ?? info.packageUrl;
|
|
139
|
+
if (requestedPackageUrl) {
|
|
140
|
+
return installPublishedPackage(name, version, requestedPackageUrl, projectRoot, options, authToken);
|
|
141
|
+
}
|
|
142
|
+
const remoteUrl = requireConfigured(info.cloneUrl, 'cloneUrl');
|
|
143
|
+
if (options.global || !projectRoot) {
|
|
144
|
+
const globalRoot = resolveLocalStorePaths(options).root;
|
|
145
|
+
const targetDir = path.normalize(installTargetDir(name, options));
|
|
146
|
+
await removeDirectory(targetDir);
|
|
147
|
+
notify(`Cloning ${name}...`);
|
|
148
|
+
await execFileAsync('git', ['-c', authHeader, 'clone', remoteUrl, targetDir]);
|
|
149
|
+
await execFileAsync('git', ['checkout', version], { cwd: targetDir });
|
|
150
|
+
if (info.publishedPackage) {
|
|
151
|
+
await preparePublishedSkillPackage(targetDir, name);
|
|
152
|
+
}
|
|
153
|
+
const specifier = `^${version}`;
|
|
154
|
+
const lockEntry = {
|
|
155
|
+
version,
|
|
156
|
+
resolved: remoteUrl,
|
|
157
|
+
integrity: '',
|
|
158
|
+
source: 'registry'
|
|
159
|
+
};
|
|
160
|
+
await addSkillDependency(globalRoot, name, specifier);
|
|
161
|
+
await addLockEntry(globalRoot, name, lockEntry);
|
|
162
|
+
await recordInstalledSkill(globalRoot, name, lockEntry, specifier);
|
|
163
|
+
return targetDir;
|
|
164
|
+
}
|
|
165
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'esl-install-'));
|
|
166
|
+
try {
|
|
167
|
+
const cloneDir = path.join(tmpDir, 'repo');
|
|
168
|
+
notify(`Cloning ${name}...`);
|
|
169
|
+
await execFileAsync('git', ['-c', authHeader, 'clone', remoteUrl, cloneDir]);
|
|
170
|
+
if (version) {
|
|
171
|
+
await execFileAsync('git', ['checkout', version], { cwd: cloneDir });
|
|
172
|
+
}
|
|
173
|
+
const targetDir = projectSkillsDir(projectRoot, name);
|
|
174
|
+
await copySkillDirectory(cloneDir, targetDir);
|
|
175
|
+
if (info.publishedPackage) {
|
|
176
|
+
await preparePublishedSkillPackage(targetDir, name);
|
|
177
|
+
}
|
|
178
|
+
const specifier = `^${version}`;
|
|
179
|
+
const lockEntry = {
|
|
180
|
+
version,
|
|
181
|
+
resolved: remoteUrl,
|
|
182
|
+
integrity: '',
|
|
183
|
+
source: 'registry'
|
|
184
|
+
};
|
|
185
|
+
const storeRoot = resolveProjectStorePaths(projectRoot).root;
|
|
186
|
+
await addSkillDependency(projectRoot, name, specifier);
|
|
187
|
+
await addLockEntry(projectRoot, name, lockEntry);
|
|
188
|
+
await recordInstalledSkill(storeRoot, name, lockEntry, specifier);
|
|
189
|
+
return targetDir;
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await removeDirectory(tmpDir);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function installPublishedPackage(name, version, packageUrl, projectRoot, options, authToken) {
|
|
196
|
+
const fetchImpl = options.customFetch ?? fetch;
|
|
197
|
+
const execFileAsync = options.execFileAsync ?? defaultExecFileAsync;
|
|
198
|
+
const serverUrl = options.server ?? (await resolveNetworkConfig(options)).server;
|
|
199
|
+
const resolvedPackageUrl = packageUrl.startsWith('http') ? packageUrl : `${await serverUrl}${packageUrl}`;
|
|
200
|
+
const response = await fetchWithTimeout(fetchImpl, resolvedPackageUrl, {
|
|
201
|
+
headers: { Authorization: `token ${authToken}` }
|
|
202
|
+
});
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
await requireOkResponse(response, 'Failed to download Published Skill Package');
|
|
205
|
+
}
|
|
206
|
+
const packageBytes = Buffer.from(await response.arrayBuffer());
|
|
207
|
+
const integrity = `sha256-${crypto.createHash('sha256').update(packageBytes).digest('hex')}`;
|
|
208
|
+
const expectedIntegrity = path.basename(new URL(resolvedPackageUrl).pathname).replace(/\.json$/, '');
|
|
209
|
+
if (expectedIntegrity.startsWith('sha256-') && integrity !== expectedIntegrity) {
|
|
210
|
+
throw new Error(`Published Skill Package checksum does not match Registry metadata: expected ${expectedIntegrity}, got ${integrity}`);
|
|
211
|
+
}
|
|
212
|
+
const packageData = JSON.parse(packageBytes.toString('utf8'));
|
|
213
|
+
if (!options.ignoreCompatibility) {
|
|
214
|
+
const compatibility = await evaluateCompatibility(packageData.releaseManifest.compatibility ?? {}, { execFileAsync });
|
|
215
|
+
if (!compatibility.compatible) {
|
|
216
|
+
const reasons = [
|
|
217
|
+
...compatibility.missingTools.map((tool) => `missing tool ${tool}`),
|
|
218
|
+
...compatibility.unsupportedLanguages.map((language) => `unsupported language ${language}`)
|
|
219
|
+
];
|
|
220
|
+
throw new Error(`Published Skill Package is incompatible: ${reasons.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const targetDir = options.global || !projectRoot
|
|
224
|
+
? publishedInstallTargetDir(name, options)
|
|
225
|
+
: publishedProjectSkillsDir(projectRoot, name);
|
|
226
|
+
const stagingDir = `${targetDir}.staging-${process.pid}-${Date.now()}`;
|
|
227
|
+
await removeDirectory(stagingDir);
|
|
228
|
+
for (const [relativePath, content] of Object.entries(packageData.files ?? {})) {
|
|
229
|
+
const destination = path.join(stagingDir, relativePath);
|
|
230
|
+
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
231
|
+
await fs.writeFile(destination, content, 'utf8');
|
|
232
|
+
}
|
|
233
|
+
if (packageData.version !== version || packageData.name !== name) {
|
|
234
|
+
await removeDirectory(stagingDir);
|
|
235
|
+
throw new Error('Published Skill Package metadata does not match the requested Release');
|
|
236
|
+
}
|
|
237
|
+
const previousDir = `${targetDir}.previous-${process.pid}-${Date.now()}`;
|
|
238
|
+
try {
|
|
239
|
+
await fs.rename(targetDir, previousDir);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
await removeDirectory(previousDir);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
await fs.rename(stagingDir, targetDir);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
await removeDirectory(stagingDir);
|
|
249
|
+
try {
|
|
250
|
+
await fs.rename(previousDir, targetDir);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
await removeDirectory(previousDir);
|
|
257
|
+
const storeRoot = options.global || !projectRoot
|
|
258
|
+
? resolveLocalStorePaths(options).root
|
|
259
|
+
: resolveProjectStorePaths(projectRoot).root;
|
|
260
|
+
const dependencyRoot = options.global || !projectRoot ? storeRoot : projectRoot;
|
|
261
|
+
const specifier = `^${version}`;
|
|
262
|
+
const lockEntry = {
|
|
263
|
+
skillId: packageData.skillId,
|
|
264
|
+
identity: name,
|
|
265
|
+
version,
|
|
266
|
+
resolved: resolvedPackageUrl,
|
|
267
|
+
integrity,
|
|
268
|
+
source: 'registry'
|
|
269
|
+
};
|
|
270
|
+
await addSkillDependency(dependencyRoot, name, specifier);
|
|
271
|
+
await addLockEntry(dependencyRoot, name, lockEntry);
|
|
272
|
+
await recordInstalledSkill(storeRoot, name, lockEntry, specifier);
|
|
273
|
+
await installPublishedDependencies(packageData.dependencyLock ?? {}, dependencyRoot, storeRoot, options, authToken, new Set([name]));
|
|
274
|
+
return targetDir;
|
|
275
|
+
}
|
|
276
|
+
async function installPublishedDependencies(dependencyLock, dependencyRoot, storeRoot, options, authToken, seen) {
|
|
277
|
+
for (const [dependencyName, dependency] of Object.entries(dependencyLock)) {
|
|
278
|
+
if (seen.has(dependencyName))
|
|
279
|
+
continue;
|
|
280
|
+
seen.add(dependencyName);
|
|
281
|
+
const dependencyInfo = await executeInfo(dependencyName, options);
|
|
282
|
+
const dependencyPackageUrl = dependencyInfo.releases?.find((release) => release.version === dependency.version)?.packageUrl ?? dependencyInfo.packageUrl;
|
|
283
|
+
if (!dependencyPackageUrl) {
|
|
284
|
+
throw new Error(`Dependency ${dependencyName} has no published Skill Release`);
|
|
285
|
+
}
|
|
286
|
+
const fetchImpl = options.customFetch ?? fetch;
|
|
287
|
+
const serverUrl = options.server ?? (await resolveNetworkConfig(options)).server;
|
|
288
|
+
const resolvedPackageUrl = dependencyPackageUrl.startsWith('http')
|
|
289
|
+
? dependencyPackageUrl
|
|
290
|
+
: `${serverUrl}${dependencyPackageUrl}`;
|
|
291
|
+
const response = await fetchWithTimeout(fetchImpl, resolvedPackageUrl, {
|
|
292
|
+
headers: { Authorization: `token ${authToken}` }
|
|
293
|
+
});
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
await requireOkResponse(response, 'Failed to download dependency Published Skill Package');
|
|
296
|
+
}
|
|
297
|
+
const packageBytes = Buffer.from(await response.arrayBuffer());
|
|
298
|
+
const integrity = `sha256-${crypto.createHash('sha256').update(packageBytes).digest('hex')}`;
|
|
299
|
+
const expectedIntegrity = path.basename(new URL(resolvedPackageUrl).pathname).replace(/\.json$/, '');
|
|
300
|
+
if (expectedIntegrity.startsWith('sha256-') && integrity !== expectedIntegrity) {
|
|
301
|
+
throw new Error(`Dependency Published Skill Package checksum does not match Registry metadata: expected ${expectedIntegrity}, got ${integrity}`);
|
|
302
|
+
}
|
|
303
|
+
const packageData = JSON.parse(packageBytes.toString('utf8'));
|
|
304
|
+
if (!options.ignoreCompatibility) {
|
|
305
|
+
const compatibility = await evaluateCompatibility(packageData.releaseManifest.compatibility ?? {}, {
|
|
306
|
+
execFileAsync: options.execFileAsync
|
|
307
|
+
});
|
|
308
|
+
if (!compatibility.compatible) {
|
|
309
|
+
throw new Error(`Dependency ${dependencyName} is incompatible: ${[
|
|
310
|
+
...compatibility.missingTools.map((tool) => `missing tool ${tool}`),
|
|
311
|
+
...compatibility.unsupportedLanguages.map((language) => `unsupported language ${language}`)
|
|
312
|
+
].join(', ')}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const dependencyTargetDir = path.join(storeRoot, skillSourceRelativeDir(dependencyName));
|
|
316
|
+
const dependencyStagingDir = `${dependencyTargetDir}.staging-${process.pid}-${Date.now()}`;
|
|
317
|
+
await removeDirectory(dependencyStagingDir);
|
|
318
|
+
for (const [relativePath, content] of Object.entries(packageData.files ?? {})) {
|
|
319
|
+
const destination = path.join(dependencyStagingDir, relativePath);
|
|
320
|
+
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
321
|
+
await fs.writeFile(destination, content, 'utf8');
|
|
322
|
+
}
|
|
323
|
+
const dependencyPreviousDir = `${dependencyTargetDir}.previous-${process.pid}-${Date.now()}`;
|
|
324
|
+
try {
|
|
325
|
+
await fs.rename(dependencyTargetDir, dependencyPreviousDir);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
await removeDirectory(dependencyPreviousDir);
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
await fs.rename(dependencyStagingDir, dependencyTargetDir);
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
await removeDirectory(dependencyStagingDir);
|
|
335
|
+
try {
|
|
336
|
+
await fs.rename(dependencyPreviousDir, dependencyTargetDir);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
}
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
await removeDirectory(dependencyPreviousDir);
|
|
343
|
+
const dependencySpecifier = `^${dependency.version}`;
|
|
344
|
+
const dependencyLockEntry = {
|
|
345
|
+
skillId: dependency.skillId,
|
|
346
|
+
identity: dependencyName,
|
|
347
|
+
version: dependency.version,
|
|
348
|
+
resolved: resolvedPackageUrl,
|
|
349
|
+
integrity,
|
|
350
|
+
source: 'registry'
|
|
351
|
+
};
|
|
352
|
+
await addLockEntry(dependencyRoot, dependencyName, dependencyLockEntry);
|
|
353
|
+
await recordInstalledSkill(storeRoot, dependencyName, dependencyLockEntry, dependencySpecifier);
|
|
354
|
+
await installPublishedDependencies(dependency.dependencyLock ?? {}, dependencyRoot, storeRoot, options, authToken, seen);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
export async function resolveDefaultInstallTools(projectRoot, options) {
|
|
358
|
+
const dependencyRoot = options.global
|
|
359
|
+
? resolveLocalStorePaths(options).root
|
|
360
|
+
: projectRoot;
|
|
361
|
+
const skillsJson = await loadSkillsJson(dependencyRoot);
|
|
362
|
+
if (skillsJson.tools && skillsJson.tools.length > 0) {
|
|
363
|
+
return skillsJson.tools.map((tool) => {
|
|
364
|
+
const resolved = resolveToolName(tool);
|
|
365
|
+
if (resolved === undefined) {
|
|
366
|
+
throw new Error(`Unknown configured tool: ${tool}. Supported tools: ${SUPPORTED_TOOLS.join(', ')}`);
|
|
367
|
+
}
|
|
368
|
+
return resolved;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const config = await loadConfig(options);
|
|
372
|
+
return config.tools.map((tool) => {
|
|
373
|
+
const resolved = resolveToolName(tool);
|
|
374
|
+
if (resolved === undefined) {
|
|
375
|
+
throw new Error(`Unknown configured tool: ${tool}. Supported tools: ${SUPPORTED_TOOLS.join(', ')}`);
|
|
376
|
+
}
|
|
377
|
+
return resolved;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
export async function executeInstall(nameOrPath, options = {}) {
|
|
381
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
382
|
+
const failedLinks = [];
|
|
383
|
+
const storeRoot = options.global
|
|
384
|
+
? resolveLocalStorePaths(options).root
|
|
385
|
+
: resolveProjectStorePaths(projectRoot).root;
|
|
386
|
+
const beforeManifest = await loadInstallManifest(storeRoot);
|
|
387
|
+
const targetDir = isBuiltinIdentity(nameOrPath)
|
|
388
|
+
? await installFromBuiltin(nameOrPath, projectRoot, options)
|
|
389
|
+
: isLocalPath(nameOrPath)
|
|
390
|
+
? await installFromLocalPath(nameOrPath, projectRoot, options)
|
|
391
|
+
: await installFromServer(nameOrPath, options.global ? null : projectRoot, options);
|
|
392
|
+
if (!options.noAdapt) {
|
|
393
|
+
const tools = options.tools ?? await resolveDefaultInstallTools(projectRoot, options);
|
|
394
|
+
if (tools.length > 0) {
|
|
395
|
+
const installManifest = await loadInstallManifest(storeRoot);
|
|
396
|
+
const targetPath = path.resolve(targetDir);
|
|
397
|
+
const installedIdentities = new Set();
|
|
398
|
+
for (const [identity, entry] of Object.entries(installManifest.skills)) {
|
|
399
|
+
const previouslyInstalled = beforeManifest.skills[identity];
|
|
400
|
+
if (path.resolve(storeRoot, entry.sourceDir) === targetPath ||
|
|
401
|
+
!previouslyInstalled ||
|
|
402
|
+
JSON.stringify(previouslyInstalled) !== JSON.stringify(entry)) {
|
|
403
|
+
installedIdentities.add(identity);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const results = await syncToolLinks({
|
|
407
|
+
storeRoot,
|
|
408
|
+
level: options.global ? 'global' : 'project',
|
|
409
|
+
tools,
|
|
410
|
+
identities: [...installedIdentities],
|
|
411
|
+
projectRoot,
|
|
412
|
+
homeDir: options.homeDir,
|
|
413
|
+
force: options.force
|
|
414
|
+
});
|
|
415
|
+
failedLinks.push(...results
|
|
416
|
+
.filter((result) => result.status === 'conflict' || result.status === 'failed')
|
|
417
|
+
.map((result) => {
|
|
418
|
+
const detail = result.status === 'conflict'
|
|
419
|
+
? `: ${result.error ?? 'conflict'}`
|
|
420
|
+
: result.error
|
|
421
|
+
? `: ${result.error}`
|
|
422
|
+
: '';
|
|
423
|
+
return `${result.tool} (${result.targetDir})${detail}`;
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (!options.global) {
|
|
428
|
+
const { ensureGitignore } = await import('./uninstall.js');
|
|
429
|
+
await ensureGitignore(projectRoot);
|
|
430
|
+
}
|
|
431
|
+
if (failedLinks.length > 0) {
|
|
432
|
+
throw new Error(`Tool link failed; existing content was not overwritten: ${failedLinks.join(', ')}`);
|
|
433
|
+
}
|
|
434
|
+
return targetDir;
|
|
435
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ToolName } from '@esl/core';
|
|
2
|
+
import { type NetworkCommandOptions } from './network-options.js';
|
|
3
|
+
export interface LinkOptions extends NetworkCommandOptions {
|
|
4
|
+
identity?: string;
|
|
5
|
+
global?: boolean;
|
|
6
|
+
tools?: ToolName[];
|
|
7
|
+
noTools?: boolean;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
projectRoot?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function executeLink(sourcePath: string, options?: LinkOptions): Promise<string>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { addLockEntry, addSkillDependency, fileExists, linkSkillDirectory, loadInstallManifest, loadSkillsJson, loadSkillsLock, parseSkillName, resolveLocalStorePaths, resolveProjectStorePaths, saveInstallManifest, skillSourceRelativeDir, syncToolLinks, validateReleaseManifest, validateSkillMd } from '../vendor/core/index.js';
|
|
4
|
+
import { installTargetDir, projectSkillsDir } from './network-options.js';
|
|
5
|
+
import { resolveDefaultInstallTools } from './install.js';
|
|
6
|
+
async function readSourceVersion(directory) {
|
|
7
|
+
const releasePath = path.join(directory, 'release.json');
|
|
8
|
+
if (!(await fileExists(releasePath))) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(await fs.readFile(releasePath, 'utf8'));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error(`Invalid release.json at ${releasePath}: file must contain valid JSON`);
|
|
17
|
+
}
|
|
18
|
+
const validation = validateReleaseManifest(parsed);
|
|
19
|
+
if (!validation.success) {
|
|
20
|
+
throw new Error(`Invalid release.json at ${releasePath}: ${validation.errors.join(', ')}`);
|
|
21
|
+
}
|
|
22
|
+
return validation.data.version;
|
|
23
|
+
}
|
|
24
|
+
function completeBareIdentity(shortName, requestedIdentity) {
|
|
25
|
+
if (!requestedIdentity) {
|
|
26
|
+
return `@local/${shortName}`;
|
|
27
|
+
}
|
|
28
|
+
if (requestedIdentity.startsWith('@') && !requestedIdentity.includes('/')) {
|
|
29
|
+
return `@${requestedIdentity.slice(1)}/${shortName}`;
|
|
30
|
+
}
|
|
31
|
+
if (requestedIdentity.startsWith('@')) {
|
|
32
|
+
const { scope, skillName } = parseSkillName(requestedIdentity);
|
|
33
|
+
if (skillName !== shortName) {
|
|
34
|
+
throw new Error(`--identity short name must match the skill source: expected ${skillName}, got ${shortName}`);
|
|
35
|
+
}
|
|
36
|
+
return `@${scope}/${skillName}`;
|
|
37
|
+
}
|
|
38
|
+
throw new Error('--identity must be a namespace (@namespace) or a full skill identity (@namespace/skill-name)');
|
|
39
|
+
}
|
|
40
|
+
async function resolveSourceSkill(sourceDir, requestedIdentity) {
|
|
41
|
+
const skillMdPath = path.join(sourceDir, 'SKILL.md');
|
|
42
|
+
if (!(await fileExists(skillMdPath))) {
|
|
43
|
+
throw new Error(`Skill source requires SKILL.md: ${sourceDir}`);
|
|
44
|
+
}
|
|
45
|
+
const skillMd = validateSkillMd(await fs.readFile(skillMdPath, 'utf8'));
|
|
46
|
+
if (!skillMd.success) {
|
|
47
|
+
throw new Error(`Invalid SKILL.md at ${skillMdPath}: ${skillMd.errors.join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
const version = await readSourceVersion(sourceDir);
|
|
50
|
+
const shortName = skillMd.data.name;
|
|
51
|
+
let releaseIdentity = null;
|
|
52
|
+
if (version !== null) {
|
|
53
|
+
const release = validateReleaseManifest(JSON.parse(await fs.readFile(path.join(sourceDir, 'release.json'), 'utf8')));
|
|
54
|
+
if (!release.success) {
|
|
55
|
+
throw new Error(`Invalid release.json: ${release.errors.join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
releaseIdentity = release.data.name;
|
|
58
|
+
}
|
|
59
|
+
if (releaseIdentity?.includes('/')) {
|
|
60
|
+
if (requestedIdentity && requestedIdentity !== releaseIdentity) {
|
|
61
|
+
throw new Error(`release.json declares ${releaseIdentity}; --identity cannot rename a linked skill`);
|
|
62
|
+
}
|
|
63
|
+
parseSkillName(releaseIdentity);
|
|
64
|
+
return { identity: releaseIdentity, version: version ?? '0.1.0', shortName };
|
|
65
|
+
}
|
|
66
|
+
const sourceShortName = releaseIdentity ?? shortName;
|
|
67
|
+
if (sourceShortName !== shortName) {
|
|
68
|
+
throw new Error(`release.json short name must match SKILL.md: expected ${shortName}, got ${sourceShortName}`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
identity: completeBareIdentity(shortName, requestedIdentity),
|
|
72
|
+
version: version ?? '0.1.0',
|
|
73
|
+
shortName
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export async function executeLink(sourcePath, options = {}) {
|
|
77
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
78
|
+
const resolved = path.resolve(sourcePath);
|
|
79
|
+
const source = await resolveSourceSkill(resolved, options.identity);
|
|
80
|
+
const identity = source.identity;
|
|
81
|
+
const storeRoot = options.global
|
|
82
|
+
? resolveLocalStorePaths(options).root
|
|
83
|
+
: resolveProjectStorePaths(projectRoot).root;
|
|
84
|
+
const dependencyRoot = options.global ? storeRoot : projectRoot;
|
|
85
|
+
const targetDir = options.global ? installTargetDir(identity, options) : projectSkillsDir(projectRoot, identity);
|
|
86
|
+
const [installManifest, skillsJson, lockJson] = await Promise.all([
|
|
87
|
+
loadInstallManifest(storeRoot),
|
|
88
|
+
loadSkillsJson(dependencyRoot),
|
|
89
|
+
loadSkillsLock(dependencyRoot)
|
|
90
|
+
]);
|
|
91
|
+
const previous = {
|
|
92
|
+
manifestEntry: installManifest.skills[identity],
|
|
93
|
+
dependencySpecifier: skillsJson.skills[identity],
|
|
94
|
+
lockEntry: lockJson.skills[identity]
|
|
95
|
+
};
|
|
96
|
+
await linkSkillDirectory({
|
|
97
|
+
identity,
|
|
98
|
+
sourceDir: resolved,
|
|
99
|
+
storeRoot,
|
|
100
|
+
force: options.force,
|
|
101
|
+
previous
|
|
102
|
+
});
|
|
103
|
+
const specifier = `link:${resolved}`;
|
|
104
|
+
const lockEntry = {
|
|
105
|
+
identity,
|
|
106
|
+
version: source.version,
|
|
107
|
+
resolved,
|
|
108
|
+
integrity: '',
|
|
109
|
+
source: 'link'
|
|
110
|
+
};
|
|
111
|
+
await addSkillDependency(dependencyRoot, identity, specifier);
|
|
112
|
+
await addLockEntry(dependencyRoot, identity, lockEntry);
|
|
113
|
+
await recordLinkInstallState(storeRoot, identity, lockEntry, specifier);
|
|
114
|
+
await finishLink(identity, projectRoot, storeRoot, targetDir, options);
|
|
115
|
+
return targetDir;
|
|
116
|
+
}
|
|
117
|
+
async function recordLinkInstallState(storeRoot, identity, lockEntry, specifier) {
|
|
118
|
+
const installManifest = await loadInstallManifest(storeRoot);
|
|
119
|
+
const current = installManifest.skills[identity];
|
|
120
|
+
installManifest.skills[identity] = {
|
|
121
|
+
identity,
|
|
122
|
+
version: lockEntry.version,
|
|
123
|
+
resolved: lockEntry.resolved,
|
|
124
|
+
integrity: lockEntry.integrity,
|
|
125
|
+
source: 'link',
|
|
126
|
+
specifier,
|
|
127
|
+
sourceDir: current?.sourceDir ?? skillSourceRelativeDir(identity),
|
|
128
|
+
installedAt: current?.installedAt ?? new Date().toISOString()
|
|
129
|
+
};
|
|
130
|
+
await saveInstallManifest(storeRoot, installManifest);
|
|
131
|
+
}
|
|
132
|
+
async function syncSelectedTools(identity, projectRoot, storeRoot, options) {
|
|
133
|
+
if (options.noTools) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const tools = options.tools ?? await resolveDefaultInstallTools(projectRoot, options);
|
|
137
|
+
if (tools.length === 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const results = await syncToolLinks({
|
|
141
|
+
storeRoot,
|
|
142
|
+
level: options.global ? 'global' : 'project',
|
|
143
|
+
tools,
|
|
144
|
+
identities: [identity],
|
|
145
|
+
projectRoot,
|
|
146
|
+
homeDir: options.homeDir,
|
|
147
|
+
force: options.force
|
|
148
|
+
});
|
|
149
|
+
const failedLinks = results
|
|
150
|
+
.filter((result) => result.status === 'conflict' || result.status === 'failed')
|
|
151
|
+
.map((result) => {
|
|
152
|
+
const detail = result.status === 'conflict'
|
|
153
|
+
? `: ${result.error ?? 'conflict'}`
|
|
154
|
+
: result.error
|
|
155
|
+
? `: ${result.error}`
|
|
156
|
+
: '';
|
|
157
|
+
return `${result.tool} (${result.targetDir})${detail}`;
|
|
158
|
+
});
|
|
159
|
+
if (failedLinks.length > 0) {
|
|
160
|
+
throw new Error(`Tool link failed; existing content was not overwritten: ${failedLinks.join(', ')}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function finishLink(identity, projectRoot, storeRoot, targetDir, options) {
|
|
164
|
+
await syncSelectedTools(identity, projectRoot, storeRoot, options);
|
|
165
|
+
if (!options.global) {
|
|
166
|
+
const { ensureGitignore } = await import('./uninstall.js');
|
|
167
|
+
await ensureGitignore(projectRoot);
|
|
168
|
+
}
|
|
169
|
+
return targetDir;
|
|
170
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { listSkills, resolveLocalStorePaths, resolveProjectStorePaths } from '../vendor/core/index.js';
|
|
2
|
+
export async function executeList(options = {}) {
|
|
3
|
+
const storeRoot = options.global
|
|
4
|
+
? resolveLocalStorePaths(options).root
|
|
5
|
+
: resolveProjectStorePaths(process.cwd()).root;
|
|
6
|
+
return listSkills(storeRoot);
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type LocalStoreOptions, type OrganizationMembership } from '@esl/core';
|
|
2
|
+
export interface LoginOptions extends LocalStoreOptions {
|
|
3
|
+
server?: string;
|
|
4
|
+
username?: string;
|
|
5
|
+
passwordFile?: string;
|
|
6
|
+
tokenFile?: string;
|
|
7
|
+
locale?: string | null;
|
|
8
|
+
noInput?: boolean;
|
|
9
|
+
readInput?: () => Promise<string>;
|
|
10
|
+
readServer?: () => Promise<string>;
|
|
11
|
+
readUsername?: (prompt: string) => Promise<string>;
|
|
12
|
+
customFetch?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
export interface LoginResult {
|
|
15
|
+
token: string;
|
|
16
|
+
username: string;
|
|
17
|
+
organizations: OrganizationMembership[];
|
|
18
|
+
locale?: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function executeLogin(options: LoginOptions): Promise<LoginResult>;
|