@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,73 @@
|
|
|
1
|
+
import { listToolLinks, parseToolSelection, removeToolLinks, resolveToolName, resolveLocalStorePaths, resolveProjectStorePaths, SUPPORTED_TOOLS } from '../vendor/core/index.js';
|
|
2
|
+
function storeRootFor(options) {
|
|
3
|
+
if (options.global) {
|
|
4
|
+
return resolveLocalStorePaths(options).root;
|
|
5
|
+
}
|
|
6
|
+
return resolveProjectStorePaths(options.projectRoot ?? process.cwd()).root;
|
|
7
|
+
}
|
|
8
|
+
function parseCommaList(value) {
|
|
9
|
+
if (!value) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
return value
|
|
13
|
+
.split(',')
|
|
14
|
+
.map((entry) => entry.trim())
|
|
15
|
+
.filter((entry) => entry.length > 0);
|
|
16
|
+
}
|
|
17
|
+
export async function executeToolsList(options = {}) {
|
|
18
|
+
const storeRoot = storeRootFor(options);
|
|
19
|
+
const entries = await listToolLinks({
|
|
20
|
+
storeRoot,
|
|
21
|
+
level: options.global ? 'global' : 'project',
|
|
22
|
+
projectRoot: options.projectRoot ?? process.cwd(),
|
|
23
|
+
homeDir: options.homeDir
|
|
24
|
+
});
|
|
25
|
+
const tools = parseCommaList(options.tool).map((tool) => resolveToolName(tool) ?? tool.toLowerCase());
|
|
26
|
+
const skills = parseCommaList(options.skill).map((skill) => skill.toLowerCase());
|
|
27
|
+
const statuses = parseCommaList(options.status).map((status) => status.toLowerCase());
|
|
28
|
+
return entries.filter((entry) => {
|
|
29
|
+
if (tools.length > 0 && !tools.includes(entry.tool)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (skills.length > 0 && !skills.includes(entry.identity.toLowerCase())) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (options.managed === true && !entry.managed) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (options.unmanaged === true && entry.managed) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (statuses.length > 0 && !statuses.includes(entry.status)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export async function executeToolsRemove(skillName, options) {
|
|
48
|
+
if (options.tools.length === 0) {
|
|
49
|
+
throw new Error(`No tools selected. Supported tools: ${SUPPORTED_TOOLS.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
const storeRoot = storeRootFor(options);
|
|
52
|
+
return removeToolLinks({
|
|
53
|
+
storeRoot,
|
|
54
|
+
identity: skillName,
|
|
55
|
+
tools: options.tools,
|
|
56
|
+
level: options.global ? 'global' : 'project'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function parseToolsOption(value) {
|
|
60
|
+
if (!value) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return parseToolSelection(value);
|
|
64
|
+
}
|
|
65
|
+
export function formatToolsList(entries) {
|
|
66
|
+
if (entries.length === 0) {
|
|
67
|
+
return ['No matching skill links.'];
|
|
68
|
+
}
|
|
69
|
+
return entries.map((entry) => {
|
|
70
|
+
const target = entry.targetDir ? ` -> ${entry.targetDir}` : '';
|
|
71
|
+
return `${entry.tool.padEnd(10)} ${entry.identity.padEnd(32)} ${entry.level.padEnd(7)} ${entry.status.padEnd(11)} ${entry.managed ? 'managed' : 'unmanaged'}${target}`;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type LocalStoreOptions, type ToolName } from '@esl/core';
|
|
2
|
+
export interface UninstallOptions extends LocalStoreOptions {
|
|
3
|
+
projectRoot?: string;
|
|
4
|
+
global?: boolean;
|
|
5
|
+
noAdapt?: boolean;
|
|
6
|
+
force?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface UninstallResult {
|
|
9
|
+
removedLinks: Array<{
|
|
10
|
+
identity: string;
|
|
11
|
+
tool: ToolName;
|
|
12
|
+
targetDir: string;
|
|
13
|
+
status: 'removed' | 'missing' | 'conflict';
|
|
14
|
+
}>;
|
|
15
|
+
sourceRemoved: boolean;
|
|
16
|
+
sourcePath?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function executeUninstall(name: string, options?: UninstallOptions): Promise<UninstallResult>;
|
|
19
|
+
export declare function ensureGitignore(projectRoot: string): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadInstallManifest, removeDirectory, removeInstalledSkill, removeLinkedSkillDirectory, removeLockEntry, removeSkillDependency, removeToolLinks, resolveLocalStorePaths, resolveProjectStorePaths, SUPPORTED_TOOLS } from '../vendor/core/index.js';
|
|
4
|
+
import { installTargetDir, projectSkillsDir } from './network-options.js';
|
|
5
|
+
function throwIfToolLinkConflicts(removedLinks) {
|
|
6
|
+
const conflicts = removedLinks.filter((result) => result.status === 'conflict');
|
|
7
|
+
if (conflicts.length > 0) {
|
|
8
|
+
throw new Error(`Tool link conflict; content was not removed: ${conflicts
|
|
9
|
+
.map((result) => `${result.tool} (${result.targetDir})`)
|
|
10
|
+
.join(', ')}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function executeUninstall(name, options = {}) {
|
|
14
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
15
|
+
const storeRoot = options.global
|
|
16
|
+
? resolveLocalStorePaths(options).root
|
|
17
|
+
: resolveProjectStorePaths(projectRoot).root;
|
|
18
|
+
const dependencyRoot = options.global ? storeRoot : projectRoot;
|
|
19
|
+
const targetDir = options.global
|
|
20
|
+
? installTargetDir(name, options)
|
|
21
|
+
: projectSkillsDir(projectRoot, name);
|
|
22
|
+
const installManifest = await loadInstallManifest(storeRoot);
|
|
23
|
+
const entry = installManifest.skills[name];
|
|
24
|
+
if (!entry) {
|
|
25
|
+
throw new Error(`Skill ${name} is not installed by ESL`);
|
|
26
|
+
}
|
|
27
|
+
const removedLinks = await removeToolLinks({
|
|
28
|
+
storeRoot,
|
|
29
|
+
identity: name,
|
|
30
|
+
tools: [...SUPPORTED_TOOLS],
|
|
31
|
+
level: options.global ? 'global' : 'project'
|
|
32
|
+
});
|
|
33
|
+
throwIfToolLinkConflicts(removedLinks);
|
|
34
|
+
if (entry.source === 'link') {
|
|
35
|
+
await removeLinkedSkillDirectory(name, storeRoot);
|
|
36
|
+
await removeSkillDependency(dependencyRoot, name);
|
|
37
|
+
await removeLockEntry(dependencyRoot, name);
|
|
38
|
+
await removeInstalledSkill(storeRoot, name);
|
|
39
|
+
return {
|
|
40
|
+
removedLinks,
|
|
41
|
+
sourceRemoved: false,
|
|
42
|
+
sourcePath: entry.resolved
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
await removeDirectory(targetDir);
|
|
46
|
+
await removeSkillDependency(dependencyRoot, name);
|
|
47
|
+
await removeLockEntry(dependencyRoot, name);
|
|
48
|
+
await removeInstalledSkill(storeRoot, name);
|
|
49
|
+
return {
|
|
50
|
+
removedLinks,
|
|
51
|
+
sourceRemoved: true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const ESL_GITIGNORE_MARKER = '# ESL managed (do not edit)';
|
|
55
|
+
const ESL_GITIGNORE_ENTRIES = ['.eslib/'];
|
|
56
|
+
export async function ensureGitignore(projectRoot) {
|
|
57
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
58
|
+
let content = '';
|
|
59
|
+
try {
|
|
60
|
+
content = await fs.readFile(gitignorePath, 'utf8');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// .gitignore does not exist yet.
|
|
64
|
+
}
|
|
65
|
+
const missingEntries = ESL_GITIGNORE_ENTRIES.filter((entry) => !content.includes(entry));
|
|
66
|
+
if (missingEntries.length === 0) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (content.includes(ESL_GITIGNORE_MARKER)) {
|
|
70
|
+
await fs.writeFile(gitignorePath, `${content.trimEnd()}\n${missingEntries.join('\n')}\n`, 'utf8');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const newBlock = `\n${ESL_GITIGNORE_MARKER}\n${missingEntries.join('\n')}\n`;
|
|
74
|
+
await fs.writeFile(gitignorePath, content.trimEnd() + newBlock, 'utf8');
|
|
75
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type LocalStoreOptions } from '@esl/core';
|
|
2
|
+
export interface UnlinkOptions extends LocalStoreOptions {
|
|
3
|
+
projectRoot?: string;
|
|
4
|
+
global?: boolean;
|
|
5
|
+
/** 仅用于解析技能目录;默认 process.cwd()。不等于改变项目根语义之外的额外根 */
|
|
6
|
+
cwd?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface UnlinkResult {
|
|
9
|
+
identity: string;
|
|
10
|
+
targetDir: string;
|
|
11
|
+
sourceDir: string;
|
|
12
|
+
restored: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function executeUnlink(target: string | undefined, options?: UnlinkOptions): Promise<UnlinkResult>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileExists, loadInstallManifest, loadSkillsJson, loadSkillsLock, removeInstalledSkill, removeLockEntry, removeSkillDependency, removeToolLinks, resolveLocalStorePaths, resolveProjectStorePaths, saveInstallManifest, saveSkillsJson, saveSkillsLock, SUPPORTED_TOOLS, unlinkSkillDirectory } from '../vendor/core/index.js';
|
|
3
|
+
import { installTargetDir, projectSkillsDir } from './network-options.js';
|
|
4
|
+
import { resolveUnlinkIdentity } from './resolve-unlink-identity.js';
|
|
5
|
+
function samePath(left, right) {
|
|
6
|
+
const a = path.resolve(left);
|
|
7
|
+
const b = path.resolve(right);
|
|
8
|
+
return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
9
|
+
}
|
|
10
|
+
export async function executeUnlink(target, options = {}) {
|
|
11
|
+
const cwd = options.cwd ?? process.cwd();
|
|
12
|
+
const projectRoot = options.projectRoot ?? cwd;
|
|
13
|
+
const resolved = await resolveUnlinkIdentity(target, { cwd });
|
|
14
|
+
const name = resolved.identity;
|
|
15
|
+
const storeRoot = options.global
|
|
16
|
+
? resolveLocalStorePaths(options).root
|
|
17
|
+
: resolveProjectStorePaths(projectRoot).root;
|
|
18
|
+
const dependencyRoot = options.global ? storeRoot : projectRoot;
|
|
19
|
+
const targetDir = options.global
|
|
20
|
+
? installTargetDir(name, options)
|
|
21
|
+
: projectSkillsDir(projectRoot, name);
|
|
22
|
+
const installManifest = await loadInstallManifest(storeRoot);
|
|
23
|
+
const entry = installManifest.skills[name];
|
|
24
|
+
if (resolved.fromDirectory && resolved.skillDir) {
|
|
25
|
+
const pathMatches = Object.entries(installManifest.skills)
|
|
26
|
+
.filter(([, skill]) => skill.source === 'link' &&
|
|
27
|
+
typeof skill.resolved === 'string' &&
|
|
28
|
+
samePath(skill.resolved, resolved.skillDir))
|
|
29
|
+
.map(([identity]) => identity);
|
|
30
|
+
const mismatched = pathMatches.filter((identity) => identity !== name);
|
|
31
|
+
if (mismatched.length > 0) {
|
|
32
|
+
throw new Error(`Skill directory ${resolved.skillDir} is linked as ${pathMatches.join(', ')}, not ${name}; pass the linked @scope/skill-name explicitly`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!entry || entry.source !== 'link') {
|
|
36
|
+
let message = `Skill ${name} is not linked by ESL`;
|
|
37
|
+
if (!options.global && resolved.usedOmitOrDot && resolved.skillDir) {
|
|
38
|
+
const looksLikeSkillDir = (await fileExists(path.join(resolved.skillDir, 'release.json'))) ||
|
|
39
|
+
(await fileExists(path.join(resolved.skillDir, 'SKILL.md')));
|
|
40
|
+
if (looksLikeSkillDir) {
|
|
41
|
+
message +=
|
|
42
|
+
'. For a project-level Skill Source Link, run this from the project root as `esl unlink ./relative/path` or pass @identity; if the skill was linked with --global, pass --global';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throw new Error(message);
|
|
46
|
+
}
|
|
47
|
+
const sourceDir = entry.resolved;
|
|
48
|
+
if (!sourceDir) {
|
|
49
|
+
throw new Error(`Skill ${name} has no linked source path in the install manifest`);
|
|
50
|
+
}
|
|
51
|
+
const { hasLinkStagingRecord } = await import('../vendor/core/index.js');
|
|
52
|
+
const hasStaging = await hasLinkStagingRecord(name, storeRoot);
|
|
53
|
+
if (!hasStaging) {
|
|
54
|
+
const removedLinks = await removeToolLinks({
|
|
55
|
+
storeRoot,
|
|
56
|
+
identity: name,
|
|
57
|
+
tools: [...SUPPORTED_TOOLS],
|
|
58
|
+
level: options.global ? 'global' : 'project'
|
|
59
|
+
});
|
|
60
|
+
const conflicts = removedLinks.filter((result) => result.status === 'conflict');
|
|
61
|
+
if (conflicts.length > 0) {
|
|
62
|
+
throw new Error(`Tool link conflict; content was not removed: ${conflicts
|
|
63
|
+
.map((result) => `${result.tool} (${result.targetDir})`)
|
|
64
|
+
.join(', ')}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const result = await unlinkSkillDirectory(name, storeRoot);
|
|
68
|
+
if (result.restored && result.record) {
|
|
69
|
+
const skillsJson = await loadSkillsJson(dependencyRoot);
|
|
70
|
+
const previousSpecifier = result.record.dependencySpecifier;
|
|
71
|
+
if (previousSpecifier === undefined) {
|
|
72
|
+
delete skillsJson.skills[name];
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
skillsJson.skills[name] = previousSpecifier;
|
|
76
|
+
}
|
|
77
|
+
await saveSkillsJson(dependencyRoot, skillsJson);
|
|
78
|
+
const lockJson = await loadSkillsLock(dependencyRoot);
|
|
79
|
+
const previousLock = result.record.lockEntry;
|
|
80
|
+
if (previousLock === undefined) {
|
|
81
|
+
delete lockJson.skills[name];
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
lockJson.skills[name] = previousLock;
|
|
85
|
+
}
|
|
86
|
+
await saveSkillsLock(dependencyRoot, lockJson);
|
|
87
|
+
const restoredManifest = await loadInstallManifest(storeRoot);
|
|
88
|
+
const previousManifest = result.record.manifestEntry;
|
|
89
|
+
if (previousManifest === undefined) {
|
|
90
|
+
delete restoredManifest.skills[name];
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
restoredManifest.skills[name] = previousManifest;
|
|
94
|
+
}
|
|
95
|
+
await saveInstallManifest(storeRoot, restoredManifest);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
await removeSkillDependency(dependencyRoot, name);
|
|
99
|
+
await removeLockEntry(dependencyRoot, name);
|
|
100
|
+
await removeInstalledSkill(storeRoot, name);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
identity: name,
|
|
104
|
+
targetDir,
|
|
105
|
+
sourceDir,
|
|
106
|
+
restored: result.restored
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ToolName } from '@esl/core';
|
|
2
|
+
import type { NetworkCommandOptions } from './network-options.js';
|
|
3
|
+
export interface UpdateOptions extends NetworkCommandOptions {
|
|
4
|
+
projectRoot?: string;
|
|
5
|
+
skillName?: string;
|
|
6
|
+
global?: boolean;
|
|
7
|
+
noAdapt?: boolean;
|
|
8
|
+
tools?: ToolName[];
|
|
9
|
+
force?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface UpdateResultEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
from: string;
|
|
14
|
+
skipped?: 'link';
|
|
15
|
+
to: string;
|
|
16
|
+
}
|
|
17
|
+
export type UpdateResult = UpdateResultEntry[];
|
|
18
|
+
export declare function executeUpdate(options?: UpdateOptions): Promise<UpdateResult>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
import { resolveProjectStorePaths, syncToolLinks, BUILTIN_SPECIFIER_PREFIX, highestStableVersion, isBuiltinIdentity, listToolLinks, loadInstallManifest, loadSkillsJson, loadSkillsLock, renameSkillState, resolveLocalStorePaths, validateSkillDirectory } from '../vendor/core/index.js';
|
|
5
|
+
import { executeInfo } from './info.js';
|
|
6
|
+
import { executeInstall } from './install.js';
|
|
7
|
+
import { publishedInstallTargetDir, publishedProjectSkillsDir, requireFreshToken } from './network-options.js';
|
|
8
|
+
export async function executeUpdate(options = {}) {
|
|
9
|
+
const dependencyRoot = options.global ? resolveLocalStorePaths(options).root : options.projectRoot ?? process.cwd();
|
|
10
|
+
const storeRoot = options.global ? dependencyRoot : resolveProjectStorePaths(dependencyRoot).root;
|
|
11
|
+
const skillsJson = await loadSkillsJson(dependencyRoot);
|
|
12
|
+
const lockJson = await loadSkillsLock(dependencyRoot);
|
|
13
|
+
const results = [];
|
|
14
|
+
const targetIdentities = new Set();
|
|
15
|
+
const hasRegistrySkills = Object.entries(skillsJson.skills).some(([name, specifier]) => (!options.skillName || options.skillName === name) &&
|
|
16
|
+
!specifier.startsWith('file:') &&
|
|
17
|
+
!specifier.startsWith('link:') &&
|
|
18
|
+
!specifier.startsWith(BUILTIN_SPECIFIER_PREFIX));
|
|
19
|
+
if (hasRegistrySkills) {
|
|
20
|
+
await requireFreshToken(options);
|
|
21
|
+
}
|
|
22
|
+
for (const [name, specifier] of Object.entries(skillsJson.skills)) {
|
|
23
|
+
if (options.skillName && options.skillName !== name) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const installManifest = await loadInstallManifest(storeRoot);
|
|
27
|
+
if (installManifest.skills[name]?.source === 'link' || specifier.startsWith('link:')) {
|
|
28
|
+
results.push({ name, from: 'link', to: 'linked (skipped)', skipped: 'link' });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (specifier.startsWith(BUILTIN_SPECIFIER_PREFIX) || isBuiltinIdentity(name)) {
|
|
32
|
+
targetIdentities.add(name);
|
|
33
|
+
const currentVersion = lockJson.skills[name]?.version;
|
|
34
|
+
const targetDir = await executeInstall(name, {
|
|
35
|
+
...options,
|
|
36
|
+
projectRoot: dependencyRoot,
|
|
37
|
+
noAdapt: true
|
|
38
|
+
});
|
|
39
|
+
const validation = await validateSkillDirectory(targetDir);
|
|
40
|
+
const to = validation.success ? validation.data.skillJson.version : 'builtin';
|
|
41
|
+
if (currentVersion !== to) {
|
|
42
|
+
results.push({ name, from: currentVersion ?? 'builtin', to });
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (specifier.startsWith('file:')) {
|
|
47
|
+
targetIdentities.add(name);
|
|
48
|
+
const targetDir = await executeInstall(specifier.slice('file:'.length), {
|
|
49
|
+
...options,
|
|
50
|
+
projectRoot: dependencyRoot,
|
|
51
|
+
noAdapt: true
|
|
52
|
+
});
|
|
53
|
+
const validation = await validateSkillDirectory(targetDir);
|
|
54
|
+
results.push({
|
|
55
|
+
name,
|
|
56
|
+
from: lockJson.skills[name]?.version ?? 'local',
|
|
57
|
+
to: validation.success ? validation.data.skillJson.version : 'local'
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const info = await executeInfo(name, options);
|
|
63
|
+
const resolvedName = info.currentName ?? name;
|
|
64
|
+
targetIdentities.add(resolvedName);
|
|
65
|
+
const latestVersion = highestStableVersion(info.versions ?? []);
|
|
66
|
+
if (!latestVersion) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const currentVersion = lockJson.skills[name]?.version;
|
|
70
|
+
// An update only ever moves forward: a lower version published later must
|
|
71
|
+
// not pull an installed skill backwards.
|
|
72
|
+
const needsUpdate = !currentVersion || semver.gt(latestVersion, currentVersion);
|
|
73
|
+
if (needsUpdate) {
|
|
74
|
+
await executeInstall(resolvedName, {
|
|
75
|
+
...options,
|
|
76
|
+
projectRoot: dependencyRoot,
|
|
77
|
+
version: latestVersion,
|
|
78
|
+
noAdapt: true
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (resolvedName !== name) {
|
|
82
|
+
const oldDirectory = options.global
|
|
83
|
+
? publishedInstallTargetDir(name, options)
|
|
84
|
+
: publishedProjectSkillsDir(dependencyRoot, name);
|
|
85
|
+
const newDirectory = options.global
|
|
86
|
+
? publishedInstallTargetDir(resolvedName, options)
|
|
87
|
+
: publishedProjectSkillsDir(dependencyRoot, resolvedName);
|
|
88
|
+
if (!needsUpdate && await directoryExists(oldDirectory)) {
|
|
89
|
+
await fs.mkdir(path.dirname(newDirectory), { recursive: true });
|
|
90
|
+
await fs.rename(oldDirectory, newDirectory);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
await fs.rm(oldDirectory, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
await renameSkillState(dependencyRoot, storeRoot, name, resolvedName);
|
|
96
|
+
results.push({
|
|
97
|
+
name: resolvedName,
|
|
98
|
+
from: currentVersion ?? 'unknown',
|
|
99
|
+
to: needsUpdate ? latestVersion : (currentVersion ?? latestVersion)
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!needsUpdate) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
results.push({
|
|
107
|
+
name: resolvedName,
|
|
108
|
+
from: currentVersion ?? 'unknown',
|
|
109
|
+
to: latestVersion
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (isBlockingNetworkConfigurationError(error)) {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
console.error(`Failed to update ${name}: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (targetIdentities.size === 0) {
|
|
120
|
+
return results;
|
|
121
|
+
}
|
|
122
|
+
if (options.tools && options.tools.length > 0) {
|
|
123
|
+
const linkResults = await syncToolLinks({
|
|
124
|
+
storeRoot,
|
|
125
|
+
level: options.global ? 'global' : 'project',
|
|
126
|
+
tools: options.tools,
|
|
127
|
+
identities: [...targetIdentities],
|
|
128
|
+
projectRoot: dependencyRoot,
|
|
129
|
+
homeDir: options.homeDir,
|
|
130
|
+
force: options.force
|
|
131
|
+
});
|
|
132
|
+
const failedLinks = linkResults
|
|
133
|
+
.filter((result) => result.status === 'conflict' || result.status === 'failed')
|
|
134
|
+
.map((result) => {
|
|
135
|
+
const detail = result.status === 'conflict'
|
|
136
|
+
? `: ${result.error ?? 'conflict'}`
|
|
137
|
+
: result.error
|
|
138
|
+
? `: ${result.error}`
|
|
139
|
+
: '';
|
|
140
|
+
return `${result.tool} (${result.targetDir})${detail}`;
|
|
141
|
+
});
|
|
142
|
+
if (failedLinks.length > 0) {
|
|
143
|
+
throw new Error(`Tool link failed; existing content was not overwritten: ${failedLinks.join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const linkEntries = await listToolLinks({
|
|
148
|
+
storeRoot,
|
|
149
|
+
level: options.global ? 'global' : 'project',
|
|
150
|
+
projectRoot: dependencyRoot,
|
|
151
|
+
homeDir: options.homeDir
|
|
152
|
+
});
|
|
153
|
+
const issues = linkEntries.filter((entry) => targetIdentities.has(entry.identity) &&
|
|
154
|
+
(entry.status === 'broken' || entry.status === 'conflict'));
|
|
155
|
+
if (issues.length > 0) {
|
|
156
|
+
throw new Error(`Tool link issue: ${issues
|
|
157
|
+
.map((entry) => `${entry.tool} (${entry.identity}: ${entry.status})`)
|
|
158
|
+
.join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return results;
|
|
162
|
+
}
|
|
163
|
+
async function directoryExists(directory) {
|
|
164
|
+
try {
|
|
165
|
+
return (await fs.stat(directory)).isDirectory();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function isBlockingNetworkConfigurationError(error) {
|
|
172
|
+
const message = error.message ?? '';
|
|
173
|
+
return (message.startsWith('Missing server;') ||
|
|
174
|
+
message.startsWith('Missing token;') ||
|
|
175
|
+
message.startsWith('Login expired;'));
|
|
176
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { type NetworkCommandOptions } from './network-options.js';
|
|
3
|
+
declare const defaultExecFileAsync: typeof execFile.__promisify__;
|
|
4
|
+
export interface UploadOptions extends NetworkCommandOptions {
|
|
5
|
+
directory?: string;
|
|
6
|
+
license?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
noInput?: boolean;
|
|
9
|
+
/** Required in non-interactive first uploads; must equal release.json.name. */
|
|
10
|
+
confirmIdentity?: string;
|
|
11
|
+
/** Injected by tests in place of the interactive confirmation prompt. */
|
|
12
|
+
confirmPrompt?: (question: string) => Promise<boolean>;
|
|
13
|
+
execFileAsync?: typeof defaultExecFileAsync;
|
|
14
|
+
}
|
|
15
|
+
export interface UploadedSkill {
|
|
16
|
+
name: string;
|
|
17
|
+
skillId: string;
|
|
18
|
+
cloneUrl: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
alreadyUpToDate?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function executeUpload(options?: UploadOptions): Promise<UploadedSkill>;
|
|
23
|
+
export interface GitIdentity {
|
|
24
|
+
name: string;
|
|
25
|
+
email: string;
|
|
26
|
+
}
|
|
27
|
+
export {};
|