@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.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api-error.d.ts +13 -0
  3. package/dist/api-error.js +31 -0
  4. package/dist/bin/esl.d.ts +35 -0
  5. package/dist/bin/esl.js +1033 -0
  6. package/dist/builtin/builtin-packages.json +11 -0
  7. package/dist/builtin/esl-operator/SKILL.md +65 -0
  8. package/dist/builtin/esl-operator/references/agent-interaction.md +110 -0
  9. package/dist/builtin/esl-operator/references/author.md +98 -0
  10. package/dist/builtin/esl-operator/references/consumer.md +109 -0
  11. package/dist/builtin/esl-operator/references/setup.md +41 -0
  12. package/dist/builtin/esl-operator/skill.json +7 -0
  13. package/dist/builtin-dir.d.ts +1 -0
  14. package/dist/builtin-dir.js +11 -0
  15. package/dist/commands/adapt.d.ts +8 -0
  16. package/dist/commands/adapt.js +27 -0
  17. package/dist/commands/admin.d.ts +11 -0
  18. package/dist/commands/admin.js +96 -0
  19. package/dist/commands/clone.d.ts +10 -0
  20. package/dist/commands/clone.js +21 -0
  21. package/dist/commands/config.d.ts +8 -0
  22. package/dist/commands/config.js +19 -0
  23. package/dist/commands/delete.d.ts +13 -0
  24. package/dist/commands/delete.js +51 -0
  25. package/dist/commands/deprecate.d.ts +16 -0
  26. package/dist/commands/deprecate.js +29 -0
  27. package/dist/commands/import.d.ts +14 -0
  28. package/dist/commands/import.js +21 -0
  29. package/dist/commands/info.d.ts +28 -0
  30. package/dist/commands/info.js +69 -0
  31. package/dist/commands/init.d.ts +28 -0
  32. package/dist/commands/init.js +252 -0
  33. package/dist/commands/install.d.ts +17 -0
  34. package/dist/commands/install.js +435 -0
  35. package/dist/commands/link.d.ts +11 -0
  36. package/dist/commands/link.js +170 -0
  37. package/dist/commands/list.d.ts +6 -0
  38. package/dist/commands/list.js +7 -0
  39. package/dist/commands/login.d.ts +20 -0
  40. package/dist/commands/login.js +132 -0
  41. package/dist/commands/logout.d.ts +6 -0
  42. package/dist/commands/logout.js +18 -0
  43. package/dist/commands/network-options.d.ts +27 -0
  44. package/dist/commands/network-options.js +133 -0
  45. package/dist/commands/notes.d.ts +10 -0
  46. package/dist/commands/notes.js +24 -0
  47. package/dist/commands/publish.d.ts +19 -0
  48. package/dist/commands/publish.js +271 -0
  49. package/dist/commands/release-delete.d.ts +21 -0
  50. package/dist/commands/release-delete.js +34 -0
  51. package/dist/commands/release-manifest.d.ts +5 -0
  52. package/dist/commands/release-manifest.js +23 -0
  53. package/dist/commands/rename.d.ts +5 -0
  54. package/dist/commands/rename.js +25 -0
  55. package/dist/commands/repair-tag.d.ts +5 -0
  56. package/dist/commands/repair-tag.js +14 -0
  57. package/dist/commands/reset-source.d.ts +16 -0
  58. package/dist/commands/reset-source.js +71 -0
  59. package/dist/commands/resolve-unlink-identity.d.ts +14 -0
  60. package/dist/commands/resolve-unlink-identity.js +47 -0
  61. package/dist/commands/search.d.ts +19 -0
  62. package/dist/commands/search.js +33 -0
  63. package/dist/commands/share.d.ts +17 -0
  64. package/dist/commands/share.js +56 -0
  65. package/dist/commands/source.d.ts +10 -0
  66. package/dist/commands/source.js +34 -0
  67. package/dist/commands/status.d.ts +16 -0
  68. package/dist/commands/status.js +77 -0
  69. package/dist/commands/sync-builtin.d.ts +10 -0
  70. package/dist/commands/sync-builtin.js +68 -0
  71. package/dist/commands/tools.d.ts +19 -0
  72. package/dist/commands/tools.js +73 -0
  73. package/dist/commands/uninstall.d.ts +19 -0
  74. package/dist/commands/uninstall.js +75 -0
  75. package/dist/commands/unlink.d.ts +14 -0
  76. package/dist/commands/unlink.js +108 -0
  77. package/dist/commands/update.d.ts +18 -0
  78. package/dist/commands/update.js +176 -0
  79. package/dist/commands/upload.d.ts +27 -0
  80. package/dist/commands/upload.js +446 -0
  81. package/dist/commands/use.d.ts +9 -0
  82. package/dist/commands/use.js +51 -0
  83. package/dist/commands/validate.d.ts +4 -0
  84. package/dist/commands/validate.js +8 -0
  85. package/dist/commands/version.d.ts +16 -0
  86. package/dist/commands/version.js +147 -0
  87. package/dist/commands/whoami.d.ts +14 -0
  88. package/dist/commands/whoami.js +54 -0
  89. package/dist/index.d.ts +24 -0
  90. package/dist/index.js +24 -0
  91. package/dist/output.d.ts +1 -0
  92. package/dist/output.js +3 -0
  93. package/dist/postinstall.d.ts +1 -0
  94. package/dist/postinstall.js +8 -0
  95. package/dist/prompt.d.ts +9 -0
  96. package/dist/prompt.js +24 -0
  97. package/dist/vendor/core/adapt/adapt-engine.d.ts +24 -0
  98. package/dist/vendor/core/adapt/adapt-engine.js +99 -0
  99. package/dist/vendor/core/adapt/adapted-skill-copy.d.ts +2 -0
  100. package/dist/vendor/core/adapt/adapted-skill-copy.js +17 -0
  101. package/dist/vendor/core/adapt/claude-adapter.d.ts +8 -0
  102. package/dist/vendor/core/adapt/claude-adapter.js +19 -0
  103. package/dist/vendor/core/adapt/codex-adapter.d.ts +8 -0
  104. package/dist/vendor/core/adapt/codex-adapter.js +19 -0
  105. package/dist/vendor/core/adapt/index.d.ts +2 -0
  106. package/dist/vendor/core/adapt/index.js +2 -0
  107. package/dist/vendor/core/adapt/tool-adapter.d.ts +12 -0
  108. package/dist/vendor/core/adapt/tool-adapter.js +1 -0
  109. package/dist/vendor/core/adapt/trae-adapter.d.ts +8 -0
  110. package/dist/vendor/core/adapt/trae-adapter.js +19 -0
  111. package/dist/vendor/core/adapt/trae-cn-adapter.d.ts +8 -0
  112. package/dist/vendor/core/adapt/trae-cn-adapter.js +19 -0
  113. package/dist/vendor/core/agent-interaction.d.ts +58 -0
  114. package/dist/vendor/core/agent-interaction.js +58 -0
  115. package/dist/vendor/core/command-params.d.ts +5 -0
  116. package/dist/vendor/core/command-params.js +46 -0
  117. package/dist/vendor/core/index.d.ts +22 -0
  118. package/dist/vendor/core/index.js +22 -0
  119. package/dist/vendor/core/link/skill-links.d.ts +38 -0
  120. package/dist/vendor/core/link/skill-links.js +245 -0
  121. package/dist/vendor/core/link/tool-links.d.ts +83 -0
  122. package/dist/vendor/core/link/tool-links.js +376 -0
  123. package/dist/vendor/core/org/account-policy.d.ts +8 -0
  124. package/dist/vendor/core/org/account-policy.js +57 -0
  125. package/dist/vendor/core/org/org-name.d.ts +2 -0
  126. package/dist/vendor/core/org/org-name.js +22 -0
  127. package/dist/vendor/core/org/standing-teams.d.ts +10 -0
  128. package/dist/vendor/core/org/standing-teams.js +27 -0
  129. package/dist/vendor/core/schema/compatibility.d.ts +17 -0
  130. package/dist/vendor/core/schema/compatibility.js +45 -0
  131. package/dist/vendor/core/schema/release-manifest.d.ts +88 -0
  132. package/dist/vendor/core/schema/release-manifest.js +161 -0
  133. package/dist/vendor/core/schema/skill-json.d.ts +61 -0
  134. package/dist/vendor/core/schema/skill-json.js +47 -0
  135. package/dist/vendor/core/schema/validation-result.d.ts +7 -0
  136. package/dist/vendor/core/schema/validation-result.js +1 -0
  137. package/dist/vendor/core/skill/builtin-package.d.ts +32 -0
  138. package/dist/vendor/core/skill/builtin-package.js +143 -0
  139. package/dist/vendor/core/skill/directory-validator.d.ts +9 -0
  140. package/dist/vendor/core/skill/directory-validator.js +72 -0
  141. package/dist/vendor/core/skill/import-skill.d.ts +10 -0
  142. package/dist/vendor/core/skill/import-skill.js +68 -0
  143. package/dist/vendor/core/skill/published-package.d.ts +1 -0
  144. package/dist/vendor/core/skill/published-package.js +9 -0
  145. package/dist/vendor/core/skill/skill-md.d.ts +15 -0
  146. package/dist/vendor/core/skill/skill-md.js +32 -0
  147. package/dist/vendor/core/store/file-copy.d.ts +3 -0
  148. package/dist/vendor/core/store/file-copy.js +102 -0
  149. package/dist/vendor/core/store/local-store.d.ts +43 -0
  150. package/dist/vendor/core/store/local-store.js +104 -0
  151. package/dist/vendor/core/store/skill-store.d.ts +24 -0
  152. package/dist/vendor/core/store/skill-store.js +41 -0
  153. package/dist/vendor/core/store/skills-json.d.ts +34 -0
  154. package/dist/vendor/core/store/skills-json.js +146 -0
  155. package/dist/vendor/core/version/select-version.d.ts +16 -0
  156. package/dist/vendor/core/version/select-version.js +34 -0
  157. package/dist/vendor/core/version/version.d.ts +2 -0
  158. package/dist/vendor/core/version/version.js +25 -0
  159. package/dist/vendor/i18n/api-errors.d.ts +121 -0
  160. package/dist/vendor/i18n/api-errors.js +240 -0
  161. package/dist/vendor/i18n/i18n-runtime.d.ts +4 -0
  162. package/dist/vendor/i18n/i18n-runtime.js +16 -0
  163. package/dist/vendor/i18n/index.d.ts +5 -0
  164. package/dist/vendor/i18n/index.js +5 -0
  165. package/dist/vendor/i18n/locales.d.ts +9 -0
  166. package/dist/vendor/i18n/locales.js +33 -0
  167. package/dist/vendor/i18n/resources.d.ts +11 -0
  168. package/dist/vendor/i18n/resources.js +925 -0
  169. package/dist/vendor/i18n/translate-api-error.d.ts +8 -0
  170. package/dist/vendor/i18n/translate-api-error.js +12 -0
  171. package/dist/version.d.ts +1 -0
  172. package/dist/version.js +24 -0
  173. package/package.json +46 -0
@@ -0,0 +1,32 @@
1
+ import YAML from 'yaml';
2
+ import { z } from 'zod';
3
+ const SkillMdMetadataSchema = z.object({
4
+ name: z.string().regex(/^[a-z0-9-]{1,64}$/, {
5
+ message: 'SKILL.md name must use lowercase letters, digits, and hyphens'
6
+ }),
7
+ description: z.string().min(1).max(1024)
8
+ });
9
+ export function validateSkillMd(content) {
10
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
11
+ if (!match) {
12
+ return { success: false, errors: ['SKILL.md: missing YAML frontmatter'] };
13
+ }
14
+ let parsed;
15
+ try {
16
+ parsed = YAML.parse(match[1]);
17
+ }
18
+ catch (error) {
19
+ return { success: false, errors: [`SKILL.md: invalid YAML frontmatter: ${error.message}`] };
20
+ }
21
+ const result = SkillMdMetadataSchema.safeParse(parsed);
22
+ if (result.success) {
23
+ return { success: true, data: result.data };
24
+ }
25
+ return {
26
+ success: false,
27
+ errors: result.error.issues.map((issue) => {
28
+ const path = issue.path.join('.') || 'frontmatter';
29
+ return `SKILL.md ${path}: ${issue.message}`;
30
+ })
31
+ };
32
+ }
@@ -0,0 +1,3 @@
1
+ export declare function fileExists(filePath: string): Promise<boolean>;
2
+ export declare function copySkillDirectory(source: string, target: string): Promise<void>;
3
+ export declare function removeDirectory(target: string): Promise<void>;
@@ -0,0 +1,102 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const EXCLUDED_DIRS = new Set(['.git']);
4
+ export async function fileExists(filePath) {
5
+ try {
6
+ await fs.access(filePath);
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ async function copyRecursive(src, dest) {
14
+ await fs.mkdir(dest, { recursive: true });
15
+ const entries = await fs.readdir(src, { withFileTypes: true });
16
+ for (const entry of entries) {
17
+ const srcPath = path.join(src, entry.name);
18
+ const destPath = path.join(dest, entry.name);
19
+ if (entry.isDirectory()) {
20
+ if (EXCLUDED_DIRS.has(entry.name)) {
21
+ continue;
22
+ }
23
+ await copyRecursive(srcPath, destPath);
24
+ }
25
+ else {
26
+ await fs.copyFile(srcPath, destPath);
27
+ await fs.chmod(destPath, 0o444);
28
+ }
29
+ }
30
+ }
31
+ export async function copySkillDirectory(source, target) {
32
+ const resolvedSource = await resolveExistingPath(source);
33
+ const resolvedTarget = path.resolve(target);
34
+ if (pathsOverlap(resolvedSource, resolvedTarget)) {
35
+ throw new Error(`Source and target directories must not overlap: ${resolvedSource} and ${resolvedTarget}`);
36
+ }
37
+ await removeDirectory(target);
38
+ await copyRecursive(source, target);
39
+ }
40
+ export async function removeDirectory(target) {
41
+ if (await isSymbolicLink(target)) {
42
+ await fs.rm(target, { force: true });
43
+ return;
44
+ }
45
+ try {
46
+ await makeWritableRecursive(target);
47
+ }
48
+ catch {
49
+ // Directory may not exist.
50
+ }
51
+ await fs.rm(target, { recursive: true, force: true });
52
+ }
53
+ async function makeWritableRecursive(dir) {
54
+ let entries;
55
+ try {
56
+ entries = await fs.readdir(dir, { withFileTypes: true });
57
+ }
58
+ catch {
59
+ return;
60
+ }
61
+ for (const entry of entries) {
62
+ const fullPath = path.join(dir, entry.name);
63
+ const stat = await fs.lstat(fullPath);
64
+ if (stat.isSymbolicLink()) {
65
+ continue;
66
+ }
67
+ if (stat.isDirectory()) {
68
+ await makeWritableRecursive(fullPath);
69
+ }
70
+ else {
71
+ try {
72
+ await fs.chmod(fullPath, 0o666);
73
+ }
74
+ catch {
75
+ // Ignore failures while restoring file permissions.
76
+ }
77
+ }
78
+ }
79
+ }
80
+ async function resolveExistingPath(input) {
81
+ try {
82
+ return await fs.realpath(input);
83
+ }
84
+ catch {
85
+ return path.resolve(input);
86
+ }
87
+ }
88
+ async function isSymbolicLink(input) {
89
+ try {
90
+ return (await fs.lstat(input)).isSymbolicLink();
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ }
96
+ function pathsOverlap(source, target) {
97
+ return isSameOrDescendant(source, target) || isSameOrDescendant(target, source);
98
+ }
99
+ function isSameOrDescendant(parent, candidate) {
100
+ const relative = path.relative(parent, candidate);
101
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
102
+ }
@@ -0,0 +1,43 @@
1
+ export interface LocalStorePaths {
2
+ root: string;
3
+ configJson: string;
4
+ credentialsJson: string;
5
+ cacheDir: string;
6
+ skillsDir: string;
7
+ }
8
+ export interface ProjectStorePaths {
9
+ root: string;
10
+ skillsDir: string;
11
+ }
12
+ export interface LocalStoreOptions {
13
+ homeDir?: string;
14
+ }
15
+ export declare function resolveLocalStorePaths(options?: LocalStoreOptions): LocalStorePaths;
16
+ export declare function resolveProjectStorePaths(projectRoot: string): ProjectStorePaths;
17
+ export declare function initializeLocalStore(options?: LocalStoreOptions): Promise<LocalStorePaths>;
18
+ export interface OrganizationMembership {
19
+ org: string;
20
+ identity: 'ordinary' | 'managing' | 'owner';
21
+ /** 便捷判据:是否所有者成员。 */
22
+ isOwnerMember: boolean;
23
+ }
24
+ export interface EslCredentials {
25
+ token: string | null;
26
+ loginAt: string | null;
27
+ }
28
+ export interface ConfigIdentity {
29
+ legacyIdentity?: boolean;
30
+ }
31
+ export interface EslConfig extends ConfigIdentity {
32
+ server: string | null;
33
+ username: string | null;
34
+ /** 账户语言偏好(ADR-0044);null 表示未主动选择。 */
35
+ locale?: string | null;
36
+ organizations: OrganizationMembership[] | null;
37
+ tools: string[];
38
+ }
39
+ export declare function loadConfig(options?: LocalStoreOptions): Promise<EslConfig>;
40
+ export declare function saveConfig(config: Partial<EslConfig>, options?: LocalStoreOptions): Promise<EslConfig>;
41
+ export declare function loadCredentials(options?: LocalStoreOptions): Promise<EslCredentials>;
42
+ export declare function saveCredentials(credentials: Partial<EslCredentials>, options?: LocalStoreOptions): Promise<EslCredentials>;
43
+ export declare function clearCredentials(options?: LocalStoreOptions): Promise<EslCredentials>;
@@ -0,0 +1,104 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ export function resolveLocalStorePaths(options = {}) {
5
+ const homeDir = options.homeDir ?? os.homedir();
6
+ const root = path.join(homeDir, '.eslib');
7
+ return {
8
+ root,
9
+ configJson: path.join(root, 'config.json'),
10
+ credentialsJson: path.join(root, 'credentials.json'),
11
+ cacheDir: path.join(root, 'cache'),
12
+ skillsDir: path.join(root, 'skills')
13
+ };
14
+ }
15
+ export function resolveProjectStorePaths(projectRoot) {
16
+ const root = path.join(projectRoot, '.eslib');
17
+ return {
18
+ root,
19
+ skillsDir: path.join(root, 'skills')
20
+ };
21
+ }
22
+ async function writeJsonIfMissing(filePath, value) {
23
+ try {
24
+ await fs.access(filePath);
25
+ }
26
+ catch {
27
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
28
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
29
+ }
30
+ }
31
+ export async function initializeLocalStore(options = {}) {
32
+ const paths = resolveLocalStorePaths(options);
33
+ await fs.mkdir(paths.cacheDir, { recursive: true });
34
+ await fs.mkdir(paths.skillsDir, { recursive: true });
35
+ await writeJsonIfMissing(paths.configJson, {
36
+ server: null,
37
+ username: null,
38
+ organizations: null,
39
+ tools: []
40
+ });
41
+ await writeJsonIfMissing(paths.credentialsJson, {
42
+ token: null,
43
+ loginAt: null
44
+ });
45
+ return paths;
46
+ }
47
+ export async function loadConfig(options = {}) {
48
+ const paths = resolveLocalStorePaths(options);
49
+ const raw = await fs.readFile(paths.configJson, 'utf8');
50
+ const parsed = JSON.parse(raw);
51
+ // 旧版配置带 org/role(ADR-0032 已废除):读取侧剥离并打标记。
52
+ const staleMemberships = parsed.organizations;
53
+ const staleMembershipShape = Array.isArray(staleMemberships) &&
54
+ staleMemberships.some((membership) => typeof membership === 'object' &&
55
+ membership !== null &&
56
+ ('role' in membership || 'isOrgManager' in membership));
57
+ const legacyIdentity = 'org' in parsed || 'role' in parsed || staleMembershipShape;
58
+ const { org: _legacyOrg, role: _legacyRole, ...rest } = parsed;
59
+ if (rest.organizations === undefined) {
60
+ rest.organizations = null;
61
+ }
62
+ return legacyIdentity ? { ...rest, legacyIdentity: true } : rest;
63
+ }
64
+ export async function saveConfig(config, options = {}) {
65
+ const paths = resolveLocalStorePaths(options);
66
+ const current = await loadConfig(options);
67
+ const updated = {
68
+ ...current,
69
+ ...config,
70
+ organizations: config.organizations ?? current.organizations,
71
+ tools: config.tools ?? current.tools
72
+ };
73
+ await fs.mkdir(paths.root, { recursive: true });
74
+ await fs.writeFile(paths.configJson, `${JSON.stringify(updated, null, 2)}\n`, 'utf8');
75
+ return updated;
76
+ }
77
+ export async function loadCredentials(options = {}) {
78
+ const paths = resolveLocalStorePaths(options);
79
+ return JSON.parse(await fs.readFile(paths.credentialsJson, 'utf8'));
80
+ }
81
+ export async function saveCredentials(credentials, options = {}) {
82
+ const paths = resolveLocalStorePaths(options);
83
+ const current = await loadCredentials(options);
84
+ const updated = {
85
+ token: credentials.token === undefined ? current.token : credentials.token,
86
+ loginAt: credentials.loginAt === undefined ? current.loginAt : credentials.loginAt
87
+ };
88
+ await fs.mkdir(paths.root, { recursive: true });
89
+ await fs.writeFile(paths.credentialsJson, `${JSON.stringify(updated, null, 2)}\n`, {
90
+ encoding: 'utf8',
91
+ mode: 0o600
92
+ });
93
+ return updated;
94
+ }
95
+ export async function clearCredentials(options = {}) {
96
+ const paths = resolveLocalStorePaths(options);
97
+ await fs.mkdir(paths.root, { recursive: true });
98
+ const cleared = { token: null, loginAt: null };
99
+ await fs.writeFile(paths.credentialsJson, `${JSON.stringify(cleared, null, 2)}\n`, {
100
+ encoding: 'utf8',
101
+ mode: 0o600
102
+ });
103
+ return cleared;
104
+ }
@@ -0,0 +1,24 @@
1
+ export interface InstallManifestSkill {
2
+ identity: string;
3
+ version: string;
4
+ source: 'registry' | 'local' | 'builtin' | 'link';
5
+ specifier: string;
6
+ resolved?: string;
7
+ integrity?: string;
8
+ skillId?: string;
9
+ sourceDir: string;
10
+ installedAt: string;
11
+ }
12
+ export interface InstallManifest {
13
+ version: 1;
14
+ tools?: string[];
15
+ skills: Record<string, InstallManifestSkill>;
16
+ }
17
+ export declare const INSTALL_MANIFEST_FILE = ".esl-install-manifest.json";
18
+ export declare function skillDirectoryName(identity: string): string;
19
+ export declare function skillSourceDirectoryName(identity: string): string;
20
+ export declare function skillSourceRelativeDir(identity: string): string;
21
+ export declare function installManifestPath(storeRoot: string): string;
22
+ export declare function defaultInstallManifest(): InstallManifest;
23
+ export declare function loadInstallManifest(storeRoot: string): Promise<InstallManifest>;
24
+ export declare function saveInstallManifest(storeRoot: string, manifest: InstallManifest): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { parseSkillName } from '../schema/skill-json.js';
4
+ export const INSTALL_MANIFEST_FILE = '.esl-install-manifest.json';
5
+ export function skillDirectoryName(identity) {
6
+ const { scope, skillName } = parseSkillName(identity);
7
+ return `${scope}_${skillName}`;
8
+ }
9
+ export function skillSourceDirectoryName(identity) {
10
+ const { scope, skillName } = parseSkillName(identity);
11
+ return path.join(`@${scope}`, skillName);
12
+ }
13
+ export function skillSourceRelativeDir(identity) {
14
+ return path.join('skills', skillSourceDirectoryName(identity));
15
+ }
16
+ export function installManifestPath(storeRoot) {
17
+ return path.join(storeRoot, INSTALL_MANIFEST_FILE);
18
+ }
19
+ export function defaultInstallManifest() {
20
+ return { version: 1, skills: {} };
21
+ }
22
+ export async function loadInstallManifest(storeRoot) {
23
+ try {
24
+ const raw = await fs.readFile(installManifestPath(storeRoot), 'utf8');
25
+ const parsed = JSON.parse(raw);
26
+ if (parsed.version !== 1 || typeof parsed.skills !== 'object' || parsed.skills === null) {
27
+ throw new Error('Invalid ESL install manifest');
28
+ }
29
+ return parsed;
30
+ }
31
+ catch (error) {
32
+ if (error.code === 'ENOENT') {
33
+ return defaultInstallManifest();
34
+ }
35
+ throw error;
36
+ }
37
+ }
38
+ export async function saveInstallManifest(storeRoot, manifest) {
39
+ await fs.mkdir(storeRoot, { recursive: true });
40
+ await fs.writeFile(installManifestPath(storeRoot), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
41
+ }
@@ -0,0 +1,34 @@
1
+ export interface SkillsJson {
2
+ skills: Record<string, string>;
3
+ tools?: string[];
4
+ }
5
+ export interface SkillsLockEntry {
6
+ skillId?: string;
7
+ identity?: string;
8
+ version: string;
9
+ resolved: string;
10
+ integrity: string;
11
+ source?: 'registry' | 'local' | 'builtin' | 'link';
12
+ }
13
+ export interface SkillsLockJson {
14
+ lockfileVersion: number;
15
+ skills: Record<string, SkillsLockEntry>;
16
+ }
17
+ export declare function loadSkillsJson(root: string): Promise<SkillsJson>;
18
+ export declare function saveSkillsJson(root: string, data: SkillsJson): Promise<void>;
19
+ export declare function loadSkillsLock(root: string): Promise<SkillsLockJson>;
20
+ export declare function saveSkillsLock(root: string, data: SkillsLockJson): Promise<void>;
21
+ export declare function addSkillDependency(root: string, name: string, specifier: string): Promise<void>;
22
+ export declare function removeSkillDependency(root: string, name: string): Promise<void>;
23
+ export declare function addLockEntry(root: string, name: string, entry: SkillsLockEntry): Promise<void>;
24
+ export declare function removeLockEntry(root: string, name: string): Promise<void>;
25
+ export declare function recordInstalledSkill(storeRoot: string, name: string, entry: SkillsLockEntry, specifier?: string): Promise<void>;
26
+ export declare function removeInstalledSkill(storeRoot: string, name: string): Promise<void>;
27
+ export declare function renameSkillState(dependencyRoot: string, storeRoot: string, oldName: string, newName: string): Promise<void>;
28
+ export interface SkillListEntry {
29
+ name: string;
30
+ version: string;
31
+ source: 'registry' | 'local' | 'builtin' | 'link';
32
+ }
33
+ export declare function listSkills(root: string): Promise<SkillListEntry[]>;
34
+ export { defaultInstallManifest, loadInstallManifest, saveInstallManifest } from './skill-store.js';
@@ -0,0 +1,146 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { BUILTIN_SPECIFIER_PREFIX } from '../skill/builtin-package.js';
4
+ import { loadInstallManifest, saveInstallManifest, skillSourceRelativeDir } from './skill-store.js';
5
+ const SKILLS_JSON_FILE = '.skills.json';
6
+ const SKILLS_LOCK_FILE = '.skills-lock.json';
7
+ function defaultSkillsJson() {
8
+ return { skills: {} };
9
+ }
10
+ function defaultSkillsLock() {
11
+ return { lockfileVersion: 1, skills: {} };
12
+ }
13
+ async function readJsonFile(filePath, fallback) {
14
+ try {
15
+ return JSON.parse(await fs.readFile(filePath, 'utf8'));
16
+ }
17
+ catch (error) {
18
+ if (error.code === 'ENOENT') {
19
+ return fallback;
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+ async function writeJsonFile(filePath, value) {
25
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
26
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
27
+ }
28
+ function sourceFromSpecifier(specifier) {
29
+ if (specifier.startsWith('file:')) {
30
+ return 'local';
31
+ }
32
+ if (specifier.startsWith('link:')) {
33
+ return 'link';
34
+ }
35
+ if (specifier.startsWith(BUILTIN_SPECIFIER_PREFIX)) {
36
+ return 'builtin';
37
+ }
38
+ return 'registry';
39
+ }
40
+ function manifestEntryFromLock(identity, entry, specifier, current) {
41
+ return {
42
+ identity: entry.identity ?? identity,
43
+ version: entry.version,
44
+ source: entry.source ?? current?.source ?? sourceFromSpecifier(specifier),
45
+ specifier: current?.specifier ?? specifier,
46
+ resolved: entry.resolved,
47
+ integrity: entry.integrity,
48
+ skillId: entry.skillId,
49
+ sourceDir: current?.sourceDir ?? skillSourceRelativeDir(identity),
50
+ installedAt: current?.installedAt ?? new Date().toISOString()
51
+ };
52
+ }
53
+ export async function loadSkillsJson(root) {
54
+ return readJsonFile(path.join(root, SKILLS_JSON_FILE), defaultSkillsJson());
55
+ }
56
+ export async function saveSkillsJson(root, data) {
57
+ await writeJsonFile(path.join(root, SKILLS_JSON_FILE), {
58
+ ...(data.tools === undefined ? {} : { tools: data.tools }),
59
+ skills: { ...data.skills }
60
+ });
61
+ }
62
+ export async function loadSkillsLock(root) {
63
+ return readJsonFile(path.join(root, SKILLS_LOCK_FILE), defaultSkillsLock());
64
+ }
65
+ export async function saveSkillsLock(root, data) {
66
+ await writeJsonFile(path.join(root, SKILLS_LOCK_FILE), {
67
+ lockfileVersion: data.lockfileVersion ?? 1,
68
+ skills: { ...data.skills }
69
+ });
70
+ }
71
+ export async function addSkillDependency(root, name, specifier) {
72
+ const current = await loadSkillsJson(root);
73
+ await saveSkillsJson(root, {
74
+ ...current,
75
+ skills: { ...current.skills, [name]: specifier }
76
+ });
77
+ }
78
+ export async function removeSkillDependency(root, name) {
79
+ const skillsJson = await loadSkillsJson(root);
80
+ delete skillsJson.skills[name];
81
+ await saveSkillsJson(root, skillsJson);
82
+ }
83
+ export async function addLockEntry(root, name, entry) {
84
+ const lockJson = await loadSkillsLock(root);
85
+ lockJson.skills[name] = {
86
+ ...entry,
87
+ identity: entry.identity ?? name,
88
+ source: entry.source ?? sourceFromSpecifier(entry.resolved)
89
+ };
90
+ await saveSkillsLock(root, lockJson);
91
+ }
92
+ export async function removeLockEntry(root, name) {
93
+ const lockJson = await loadSkillsLock(root);
94
+ delete lockJson.skills[name];
95
+ await saveSkillsLock(root, lockJson);
96
+ }
97
+ export async function recordInstalledSkill(storeRoot, name, entry, specifier) {
98
+ const installManifest = await loadInstallManifest(storeRoot);
99
+ installManifest.skills[name] = manifestEntryFromLock(name, entry, specifier ?? installManifest.skills[name]?.specifier ?? entry.resolved, installManifest.skills[name]);
100
+ await saveInstallManifest(storeRoot, installManifest);
101
+ }
102
+ export async function removeInstalledSkill(storeRoot, name) {
103
+ const installManifest = await loadInstallManifest(storeRoot);
104
+ delete installManifest.skills[name];
105
+ await saveInstallManifest(storeRoot, installManifest);
106
+ }
107
+ export async function renameSkillState(dependencyRoot, storeRoot, oldName, newName) {
108
+ const [skillsJson, lockJson, installManifest] = await Promise.all([
109
+ loadSkillsJson(dependencyRoot),
110
+ loadSkillsLock(dependencyRoot),
111
+ loadInstallManifest(storeRoot)
112
+ ]);
113
+ const dependency = skillsJson.skills[oldName];
114
+ const lockEntry = lockJson.skills[oldName];
115
+ const installed = installManifest.skills[oldName];
116
+ if (dependency !== undefined) {
117
+ delete skillsJson.skills[oldName];
118
+ skillsJson.skills[newName] = dependency;
119
+ }
120
+ if (lockEntry !== undefined) {
121
+ delete lockJson.skills[oldName];
122
+ lockJson.skills[newName] = { ...lockEntry, identity: newName };
123
+ }
124
+ if (installed !== undefined) {
125
+ delete installManifest.skills[oldName];
126
+ installManifest.skills[newName] = {
127
+ ...installed,
128
+ identity: newName,
129
+ sourceDir: skillSourceRelativeDir(newName)
130
+ };
131
+ }
132
+ await Promise.all([
133
+ saveSkillsJson(dependencyRoot, skillsJson),
134
+ saveSkillsLock(dependencyRoot, lockJson),
135
+ saveInstallManifest(storeRoot, installManifest)
136
+ ]);
137
+ }
138
+ export async function listSkills(root) {
139
+ const manifest = await loadInstallManifest(root);
140
+ return Object.entries(manifest.skills).map(([name, entry]) => ({
141
+ name,
142
+ version: entry.version,
143
+ source: entry.source
144
+ }));
145
+ }
146
+ export { defaultInstallManifest, loadInstallManifest, saveInstallManifest } from './skill-store.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The Skill Release a Skill User gets by default: the highest published version
3
+ * that is not a prerelease. Prereleases are installable only when a version is
4
+ * requested explicitly.
5
+ */
6
+ export declare function highestStableVersion(versions: readonly string[]): string | undefined;
7
+ /**
8
+ * Published versions newest first, so a version listing reads the same wherever
9
+ * it is shown. Entries that are not valid SemVer sort to the end.
10
+ */
11
+ export declare function sortVersionsDescending(versions: readonly string[]): string[];
12
+ /**
13
+ * The version a dependency range resolves to: the highest published version that
14
+ * satisfies the range. Used when freezing a Release Dependency Lock.
15
+ */
16
+ export declare function highestSatisfyingVersion(versions: readonly string[], range: string): string | undefined;
@@ -0,0 +1,34 @@
1
+ import semver from 'semver';
2
+ /**
3
+ * The Skill Release a Skill User gets by default: the highest published version
4
+ * that is not a prerelease. Prereleases are installable only when a version is
5
+ * requested explicitly.
6
+ */
7
+ export function highestStableVersion(versions) {
8
+ return versions
9
+ .filter((version) => semver.valid(version) !== null && semver.prerelease(version) === null)
10
+ .sort(semver.rcompare)[0];
11
+ }
12
+ /**
13
+ * Published versions newest first, so a version listing reads the same wherever
14
+ * it is shown. Entries that are not valid SemVer sort to the end.
15
+ */
16
+ export function sortVersionsDescending(versions) {
17
+ return [...versions].sort((left, right) => {
18
+ const leftValid = semver.valid(left) !== null;
19
+ const rightValid = semver.valid(right) !== null;
20
+ if (!leftValid || !rightValid) {
21
+ return leftValid === rightValid ? 0 : leftValid ? -1 : 1;
22
+ }
23
+ return semver.rcompare(left, right);
24
+ });
25
+ }
26
+ /**
27
+ * The version a dependency range resolves to: the highest published version that
28
+ * satisfies the range. Used when freezing a Release Dependency Lock.
29
+ */
30
+ export function highestSatisfyingVersion(versions, range) {
31
+ return versions
32
+ .filter((version) => semver.valid(version) !== null && semver.satisfies(version, range))
33
+ .sort(semver.rcompare)[0];
34
+ }
@@ -0,0 +1,2 @@
1
+ export type ReleaseType = 'major' | 'minor' | 'patch';
2
+ export declare function bumpSkillVersion(directory: string, release: ReleaseType): Promise<string>;
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { validateSkillJson } from '../schema/skill-json.js';
4
+ function nextVersion(current, release) {
5
+ const [major, minor, patch] = current.split('.').map(Number);
6
+ if (release === 'major') {
7
+ return `${major + 1}.0.0`;
8
+ }
9
+ if (release === 'minor') {
10
+ return `${major}.${minor + 1}.0`;
11
+ }
12
+ return `${major}.${minor}.${patch + 1}`;
13
+ }
14
+ export async function bumpSkillVersion(directory, release) {
15
+ const skillJsonPath = path.join(directory, 'skill.json');
16
+ const raw = await fs.readFile(skillJsonPath, 'utf8');
17
+ const data = JSON.parse(raw);
18
+ const validation = validateSkillJson(data);
19
+ if (!validation.success) {
20
+ throw new Error(`Cannot bump invalid skill.json: ${validation.errors.join(', ')}`);
21
+ }
22
+ const version = nextVersion(validation.data.version, release);
23
+ await fs.writeFile(skillJsonPath, `${JSON.stringify({ ...validation.data, version }, null, 2)}\n`, 'utf8');
24
+ return version;
25
+ }