@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,34 @@
1
+ import { isBuiltinIdentity, parseSkillName } from '../vendor/core/index.js';
2
+ import { requireOkResponse } from '../api-error.js';
3
+ import { apiUrl, fetchWithTimeout, requireFreshToken, resolveNetworkConfig } from './network-options.js';
4
+ /**
5
+ * Delete a single Skill Release (CONTEXT:单版本删除). The version number stays burned,
6
+ * so this is a deliberate, hard-to-undo action: the caller has to echo the version.
7
+ *
8
+ * A release other skills pin through their Release Dependency Lock is refused unless
9
+ * `force` is passed, and the server only accepts that from a platform administrator.
10
+ */
11
+ export async function executeReleaseDelete(identity, version, options = {}) {
12
+ if (isBuiltinIdentity(identity)) {
13
+ throw new Error(`Unknown built-in skill: ${identity}; built-in skills have no releases`);
14
+ }
15
+ if (options.confirm !== version) {
16
+ throw new Error(`Deleting ${identity}@${version} requires an explicit confirmation: pass --confirm ${version} (the version number is burned and cannot be republished).`);
17
+ }
18
+ const token = await requireFreshToken(options);
19
+ const server = options.server ?? (await resolveNetworkConfig(options)).server;
20
+ const { scope, skillName } = parseSkillName(identity);
21
+ const fetchImpl = options.customFetch ?? fetch;
22
+ const response = await fetchWithTimeout(fetchImpl, apiUrl(server, `/api/skills/${encodeURIComponent(scope)}/${encodeURIComponent(skillName)}/releases/${encodeURIComponent(version)}/delete`), {
23
+ method: 'POST',
24
+ headers: {
25
+ Authorization: `token ${token}`,
26
+ 'Content-Type': 'application/json'
27
+ },
28
+ body: JSON.stringify(options.force ? { confirm: version, force: true } : { confirm: version })
29
+ });
30
+ if (!response.ok) {
31
+ await requireOkResponse(response, 'Failed to delete the release');
32
+ }
33
+ return response.json();
34
+ }
@@ -0,0 +1,5 @@
1
+ export interface ManifestOptions {
2
+ license?: string;
3
+ }
4
+ export declare const DEFAULT_LICENSE = "MIT";
5
+ export declare function ensureReleaseManifest(options: ManifestOptions, directory: string): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createMinimalReleaseManifest, validateSkillMd } from '../vendor/core/index.js';
4
+ import { notify } from '../output.js';
5
+ export const DEFAULT_LICENSE = 'MIT';
6
+ // v3 清单必须有 name(ADR-0032)。补缺场景下身份尚未与服务器确认,
7
+ // 以 SKILL.md 的 short name 写裸名——即个人命名空间语义。
8
+ async function deriveSkillName(directory) {
9
+ const skillMd = await fs
10
+ .readFile(path.join(directory, 'SKILL.md'), 'utf8')
11
+ .then((content) => validateSkillMd(content), () => undefined);
12
+ if (skillMd?.success)
13
+ return skillMd.data.name;
14
+ return path.basename(directory);
15
+ }
16
+ export async function ensureReleaseManifest(options, directory) {
17
+ const license = options.license ?? DEFAULT_LICENSE;
18
+ if (!options.license) {
19
+ notify(`release.json is missing; creating it with the default license ${license} (pass --license to override).`);
20
+ }
21
+ const releaseJson = createMinimalReleaseManifest(await deriveSkillName(directory), license);
22
+ await fs.writeFile(path.join(directory, 'release.json'), `${JSON.stringify(releaseJson, null, 2)}\n`, 'utf8');
23
+ }
@@ -0,0 +1,5 @@
1
+ import { type NetworkCommandOptions } from './network-options.js';
2
+ export interface RenameOptions extends NetworkCommandOptions {
3
+ newName: string;
4
+ }
5
+ export declare function executeRename(identity: string, options: RenameOptions): Promise<unknown>;
@@ -0,0 +1,25 @@
1
+ import { apiUrl, fetchWithTimeout, requireFreshToken } from './network-options.js';
2
+ import { isBuiltinIdentity } from '../vendor/core/index.js';
3
+ import { requireOkResponse } from '../api-error.js';
4
+ export async function executeRename(identity, options) {
5
+ if (isBuiltinIdentity(identity)) {
6
+ throw new Error(`Unknown built-in skill: ${identity}; built-in skills cannot be renamed`);
7
+ }
8
+ const token = await requireFreshToken(options);
9
+ const server = options.server;
10
+ if (!server)
11
+ throw new Error('Missing server; run esl login or pass --server');
12
+ const fetchImpl = options.customFetch ?? fetch;
13
+ const response = await fetchWithTimeout(fetchImpl, apiUrl(server, `/api/skills/${encodeURIComponent(identity)}/rename`), {
14
+ method: 'POST',
15
+ headers: {
16
+ Authorization: `token ${token}`,
17
+ 'Content-Type': 'application/json'
18
+ },
19
+ body: JSON.stringify({ name: options.newName })
20
+ });
21
+ if (!response.ok) {
22
+ await requireOkResponse(response, 'Failed to rename skill');
23
+ }
24
+ return response.json();
25
+ }
@@ -0,0 +1,5 @@
1
+ import { type NetworkCommandOptions } from './network-options.js';
2
+ export interface RepairTagOptions extends NetworkCommandOptions {
3
+ version: string;
4
+ }
5
+ export declare function executeRepairTag(identity: string, options: RepairTagOptions): Promise<unknown>;
@@ -0,0 +1,14 @@
1
+ import { apiUrl, fetchWithTimeout, requireFreshToken } from './network-options.js';
2
+ import { requireOkResponse } from '../api-error.js';
3
+ export async function executeRepairTag(identity, options) {
4
+ const token = await requireFreshToken(options);
5
+ const server = options.server;
6
+ if (!server)
7
+ throw new Error('Missing server; run esl login or pass --server');
8
+ const fetchImpl = options.customFetch ?? fetch;
9
+ const response = await fetchWithTimeout(fetchImpl, apiUrl(server, `/api/skills/${encodeURIComponent(identity)}/releases/${encodeURIComponent(options.version)}/repair-tag`), { method: 'POST', headers: { Authorization: `token ${token}` } });
10
+ if (!response.ok) {
11
+ await requireOkResponse(response, 'Failed to repair release tag');
12
+ }
13
+ return response.json();
14
+ }
@@ -0,0 +1,16 @@
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 ResetSourceOptions extends NetworkCommandOptions {
5
+ directory?: string;
6
+ force?: boolean;
7
+ noInput?: boolean;
8
+ execFileAsync?: typeof defaultExecFileAsync;
9
+ }
10
+ export interface ResetSourceResult {
11
+ directory: string;
12
+ remoteUrl: string;
13
+ releaseManifestRenamedTo: string | null;
14
+ }
15
+ export declare function executeResetSource(options?: ResetSourceOptions): Promise<ResetSourceResult>;
16
+ export {};
@@ -0,0 +1,71 @@
1
+ import { execFile } from 'node:child_process';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { executeInfo } from './info.js';
6
+ import { notify } from '../output.js';
7
+ import { confirm, isInteractive } from '../prompt.js';
8
+ import { fileExists } from '../vendor/core/index.js';
9
+ const defaultExecFileAsync = promisify(execFile);
10
+ export async function executeResetSource(options = {}) {
11
+ const directory = options.directory ?? process.cwd();
12
+ const execFileAsync = options.execFileAsync ?? defaultExecFileAsync;
13
+ let remoteUrl;
14
+ try {
15
+ remoteUrl = (await execFileAsync('git', ['remote', 'get-url', 'esl'], { cwd: directory })).stdout.trim();
16
+ }
17
+ catch {
18
+ throw new Error(`Skill source at ${directory} has no esl remote; it is already an un-managed local source and needs no reset`);
19
+ }
20
+ // 守门:Registry API 只读探测。身份在当前登录下可见时,服务器上的源仍在——
21
+ // 脱管前的核实假设(「源已删除」)不成立。硬阻断(ADR-0028:避免误删),
22
+ // 提示先核实;--force 显式表态「我知道源还在,就是要另立新源」才放行。
23
+ if (options.force !== true && remoteUrl.startsWith('http')) {
24
+ const identity = identityFromRemoteUrl(remoteUrl);
25
+ if (identity) {
26
+ try {
27
+ await executeInfo(identity, options);
28
+ throw new Error(`The skill identity ${identity} still exists on the configured ESL server; refusing to reset the source link. ` +
29
+ 'Verify on the server first: if you only switched accounts, log in with the maintaining account and "esl upload" to sync; ' +
30
+ 'if the source really should be deleted, delete it in the Web skill lifecycle UI (unpublished skills: a manage-permission holder; published skills: the platform administrator or organization owner). ' +
31
+ 'To proceed with a reset anyway - detaching locally and registering a brand-new source with a new Skill ID later - re-run with --force.');
32
+ }
33
+ catch (error) {
34
+ // 守门自身抛出的拒绝执行错误必须向上传递;其余探测失败(源已删除、
35
+ // 无权限或网络不通)→ 按用户主张的脱管场景放行。
36
+ if (error instanceof Error && error.message.includes('refusing to reset'))
37
+ throw error;
38
+ }
39
+ }
40
+ }
41
+ if (options.force !== true) {
42
+ if (options.noInput || !isInteractive()) {
43
+ throw new Error('Resetting the source link requires confirmation; pass --force to skip the confirmation in non-interactive runs');
44
+ }
45
+ const confirmed = await confirm(`Remove the esl remote (${remoteUrl}) and reset this directory to a local skill source? `);
46
+ if (!confirmed) {
47
+ throw new Error('Reset cancelled');
48
+ }
49
+ }
50
+ const manifestPath = path.join(directory, 'release.json');
51
+ const backupPath = `${path.join(directory, 'release.json')}.before-reset`;
52
+ const hasManifest = await fileExists(manifestPath);
53
+ if (hasManifest && (await fileExists(backupPath))) {
54
+ // 破坏性前置检查:任何冲突都在动 remote 之前发现,绝不留下脱管一半的目录。
55
+ throw new Error(`A previous reset backup already exists at ${backupPath}; remove or rename it, then re-run "esl reset-source"`);
56
+ }
57
+ await execFileAsync('git', ['remote', 'remove', 'esl'], { cwd: directory });
58
+ let releaseManifestRenamedTo = null;
59
+ if (hasManifest) {
60
+ await fs.rename(manifestPath, backupPath);
61
+ releaseManifestRenamedTo = backupPath;
62
+ notify(`Release Manifest renamed to ${path.basename(backupPath)}`);
63
+ }
64
+ return { directory, remoteUrl, releaseManifestRenamedTo };
65
+ }
66
+ // 与 upload 的 skillNameFromRemote 相同的解析约定:取 /git/ 与 .git 之间的
67
+ // 组织/仓库路径并拼成 `@scope/name`。
68
+ function identityFromRemoteUrl(remoteUrl) {
69
+ const match = remoteUrl.match(/\/git\/(.+?)(?:\.git)?\/?$/);
70
+ return match ? `@${match[1]}` : null;
71
+ }
@@ -0,0 +1,14 @@
1
+ export interface ResolveUnlinkIdentityOptions {
2
+ /** 默认 process.cwd();测试可注入,避免依赖 chdir */
3
+ cwd?: string;
4
+ }
5
+ export interface ResolvedUnlinkTarget {
6
+ identity: string;
7
+ /** 是否从技能目录的 release.json 推导(含省略 / . / 路径) */
8
+ fromDirectory: boolean;
9
+ /** 推导时使用的绝对技能目录;显式 @identity 时为 null */
10
+ skillDir: string | null;
11
+ /** 调用方传入 undefined 或 '.'(脚枪提示用) */
12
+ usedOmitOrDot: boolean;
13
+ }
14
+ export declare function resolveUnlinkIdentity(target: string | undefined, options?: ResolveUnlinkIdentityOptions): Promise<ResolvedUnlinkTarget>;
@@ -0,0 +1,47 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileExists, validateReleaseManifest } from '../vendor/core/index.js';
4
+ function completeBareIdentity(name) {
5
+ if (name.startsWith('@')) {
6
+ return name;
7
+ }
8
+ return `@local/${name}`;
9
+ }
10
+ async function identityFromSkillDir(skillDir) {
11
+ const releasePath = path.join(skillDir, 'release.json');
12
+ if (!(await fileExists(releasePath))) {
13
+ throw new Error(`Cannot infer skill identity from ${skillDir}: release.json is missing; pass @scope/skill-name explicitly`);
14
+ }
15
+ let parsed;
16
+ try {
17
+ parsed = JSON.parse(await fs.readFile(releasePath, 'utf8'));
18
+ }
19
+ catch {
20
+ throw new Error(`Cannot infer skill identity from ${skillDir}: release.json is not valid JSON; pass @scope/skill-name explicitly`);
21
+ }
22
+ const validation = validateReleaseManifest(parsed);
23
+ if (!validation.success) {
24
+ throw new Error(`Cannot infer skill identity from ${skillDir}: ${validation.errors.join(', ')}; pass @scope/skill-name explicitly`);
25
+ }
26
+ return completeBareIdentity(validation.data.name);
27
+ }
28
+ export async function resolveUnlinkIdentity(target, options = {}) {
29
+ const cwd = options.cwd ?? process.cwd();
30
+ if (typeof target === 'string' && target.startsWith('@')) {
31
+ return {
32
+ identity: target,
33
+ fromDirectory: false,
34
+ skillDir: null,
35
+ usedOmitOrDot: false
36
+ };
37
+ }
38
+ const usedOmitOrDot = target === undefined || target === '.';
39
+ const skillDir = usedOmitOrDot ? path.resolve(cwd) : path.resolve(cwd, target);
40
+ const identity = await identityFromSkillDir(skillDir);
41
+ return {
42
+ identity,
43
+ fromDirectory: true,
44
+ skillDir,
45
+ usedOmitOrDot
46
+ };
47
+ }
@@ -0,0 +1,19 @@
1
+ import { type NetworkCommandOptions } from './network-options.js';
2
+ export interface SkillSearchResult {
3
+ name: string;
4
+ /** 对外当前显示名(ADR-0048):标题位优先显示名,name 作技术名。 */
5
+ displayName?: string;
6
+ description: string;
7
+ scope?: string;
8
+ skillName?: string;
9
+ latestStableVersion?: string;
10
+ visibility?: string;
11
+ }
12
+ export interface SkillSearchFilters {
13
+ query?: string;
14
+ namespace?: string;
15
+ keyword?: string;
16
+ visibility?: 'public' | 'private';
17
+ limit?: number;
18
+ }
19
+ export declare function executeSearch(query: string | undefined, options?: NetworkCommandOptions): Promise<SkillSearchResult[]>;
@@ -0,0 +1,33 @@
1
+ import { apiUrl, fetchWithTimeout, resolveOptionalFreshToken, resolveNetworkConfig } from './network-options.js';
2
+ import { isBuiltinIdentity } from '../vendor/core/index.js';
3
+ import { requireOkResponse } from '../api-error.js';
4
+ export async function executeSearch(query, options = {}) {
5
+ const fetchImpl = options.customFetch ?? fetch;
6
+ const server = options.server ?? (await resolveNetworkConfig(options)).server;
7
+ // 与 info 对齐(ADR-0049):存在有效登录态则随请求携带 Skill User Token,
8
+ // 匿名仍可浏览 public 已发布技能。
9
+ const token = await resolveOptionalFreshToken({ homeDir: options.homeDir });
10
+ const filters = new URLSearchParams();
11
+ if (query !== undefined && query !== '') {
12
+ filters.set('q', query);
13
+ }
14
+ const searchOptions = options;
15
+ if (searchOptions.visibility === 'private' && !token) {
16
+ throw new Error('Searching private skills requires login; run "esl login" first');
17
+ }
18
+ if (searchOptions.namespace)
19
+ filters.set('namespace', searchOptions.namespace);
20
+ if (searchOptions.keyword)
21
+ filters.set('keyword', searchOptions.keyword);
22
+ if (searchOptions.visibility)
23
+ filters.set('visibility', searchOptions.visibility);
24
+ if (searchOptions.limit !== undefined)
25
+ filters.set('limit', String(searchOptions.limit));
26
+ const queryString = filters.toString();
27
+ const res = await fetchWithTimeout(fetchImpl, apiUrl(server, `/api/skills/search${queryString ? `?${queryString}` : ''}`), { headers: token ? { Authorization: `token ${token}` } : {} });
28
+ if (!res.ok) {
29
+ await requireOkResponse(res, 'Failed to search skills');
30
+ }
31
+ const results = (await res.json());
32
+ return results.filter((result) => !isBuiltinIdentity(result.name));
33
+ }
@@ -0,0 +1,17 @@
1
+ import { type NetworkCommandOptions } from './network-options.js';
2
+ export interface ShareOptions extends NetworkCommandOptions {
3
+ all?: boolean;
4
+ team?: string;
5
+ user?: string;
6
+ write?: boolean;
7
+ manage?: boolean;
8
+ reset?: boolean;
9
+ }
10
+ export interface ShareTarget {
11
+ action: 'share_all_read' | 'share_all_write' | 'set_team' | 'add_member' | 'reset_to_private';
12
+ team?: string;
13
+ username?: string;
14
+ permission?: 'read' | 'write' | 'manage';
15
+ }
16
+ export declare function resolveShareTarget(options: ShareOptions): ShareTarget;
17
+ export declare function executeShare(identity: string, options: ShareOptions): Promise<unknown>;
@@ -0,0 +1,56 @@
1
+ import { parseSkillName, isBuiltinIdentity } from '../vendor/core/index.js';
2
+ import { apiUrl, fetchWithTimeout, requireFreshToken, resolveNetworkConfig } from './network-options.js';
3
+ import { requireOkResponse } from '../api-error.js';
4
+ export function resolveShareTarget(options) {
5
+ const selected = [options.all, options.team, options.user, options.reset].filter(Boolean);
6
+ if (selected.length === 0) {
7
+ throw new Error('Choose one sharing target: --all, --team, --user, or --reset');
8
+ }
9
+ if (selected.length > 1) {
10
+ throw new Error('Choose only one of --all, --team, --user, or --reset');
11
+ }
12
+ if (options.write && options.manage) {
13
+ throw new Error('Choose only one permission level: --write or --manage');
14
+ }
15
+ if (options.all) {
16
+ return { action: options.write ? 'share_all_write' : 'share_all_read' };
17
+ }
18
+ if (options.team) {
19
+ return {
20
+ action: 'set_team',
21
+ team: options.team,
22
+ permission: options.manage ? 'manage' : options.write ? 'write' : 'read'
23
+ };
24
+ }
25
+ if (options.user) {
26
+ // ADR-0025 三档:manage 档授予对方协管(配权限、发布)的能力。
27
+ return {
28
+ action: 'add_member',
29
+ username: options.user,
30
+ permission: options.manage ? 'manage' : options.write ? 'write' : 'read'
31
+ };
32
+ }
33
+ return { action: 'reset_to_private' };
34
+ }
35
+ export async function executeShare(identity, options) {
36
+ if (isBuiltinIdentity(identity)) {
37
+ throw new Error(`Unknown built-in skill: ${identity}; built-in skills have no permissions to share`);
38
+ }
39
+ const target = resolveShareTarget(options);
40
+ const token = await requireFreshToken(options);
41
+ const server = options.server ?? (await resolveNetworkConfig(options)).server;
42
+ const { scope, skillName } = parseSkillName(identity);
43
+ const fetchImpl = options.customFetch ?? fetch;
44
+ const response = await fetchWithTimeout(fetchImpl, apiUrl(server, `/api/skills/${encodeURIComponent(scope)}/${encodeURIComponent(skillName)}/permissions`), {
45
+ method: 'POST',
46
+ headers: {
47
+ Authorization: `token ${token}`,
48
+ 'Content-Type': 'application/json'
49
+ },
50
+ body: JSON.stringify(target)
51
+ });
52
+ if (!response.ok) {
53
+ await requireOkResponse(response, 'Failed to update skill permissions');
54
+ }
55
+ return response.json();
56
+ }
@@ -0,0 +1,10 @@
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 SourceOptions extends NetworkCommandOptions {
5
+ target?: string;
6
+ cwd?: string;
7
+ execFileAsync?: typeof defaultExecFileAsync;
8
+ }
9
+ export declare function executeSource(name: string, options?: SourceOptions): Promise<string>;
10
+ export {};
@@ -0,0 +1,34 @@
1
+ import { execFile } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { promisify } from 'node:util';
4
+ import { parseSkillName, isBuiltinIdentity } from '../vendor/core/index.js';
5
+ import { gitAuthHeaderConfig, fetchWithTimeout, requireConfigured, requireFreshToken, resolveNetworkConfig } from './network-options.js';
6
+ import { executeInfo } from './info.js';
7
+ import { notify } from '../output.js';
8
+ const defaultExecFileAsync = promisify(execFile);
9
+ export async function executeSource(name, options = {}) {
10
+ if (isBuiltinIdentity(name)) {
11
+ throw new Error(`Unknown built-in skill: ${name}; built-in skills have no source repository`);
12
+ }
13
+ const execFileAsync = options.execFileAsync ?? defaultExecFileAsync;
14
+ const authToken = await requireFreshToken(options);
15
+ const info = await executeInfo(name, options);
16
+ const { server } = await resolveNetworkConfig(options);
17
+ const fetchImpl = options.customFetch ?? fetch;
18
+ const accessResponse = await fetchWithTimeout(fetchImpl, `${server.replace(/\/$/, '')}/api/skills/${encodeURIComponent(name)}/source-access`, {
19
+ method: 'POST',
20
+ headers: { Authorization: `token ${authToken}` }
21
+ });
22
+ if (!accessResponse.ok) {
23
+ throw new Error(`Failed to authorize source access: ${await accessResponse.text()}`);
24
+ }
25
+ const access = (await accessResponse.json());
26
+ const remoteUrl = requireConfigured(access.cloneUrl ?? info.cloneUrl, 'cloneUrl');
27
+ const authHeader = gitAuthHeaderConfig(authToken);
28
+ const { skillName } = parseSkillName(name);
29
+ const cwd = options.cwd ?? process.cwd();
30
+ const targetDir = options.target ?? path.join(cwd, skillName);
31
+ notify(`Cloning ${name}...`);
32
+ await execFileAsync('git', ['-c', authHeader, 'clone', remoteUrl, targetDir]);
33
+ return targetDir;
34
+ }
@@ -0,0 +1,16 @@
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 StatusOptions extends NetworkCommandOptions {
5
+ directory?: string;
6
+ execFileAsync?: typeof defaultExecFileAsync;
7
+ }
8
+ export interface SourceStatus {
9
+ serverHosted: boolean;
10
+ clean: boolean;
11
+ ahead: number;
12
+ behind: number;
13
+ lastCommit?: string;
14
+ }
15
+ export declare function executeStatus(options?: StatusOptions): Promise<SourceStatus>;
16
+ export {};
@@ -0,0 +1,77 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { gitAuthHeaderConfig, requireFreshToken, resolveNetworkConfig, sourceRemoteOriginDrifted } from './network-options.js';
4
+ import { notify } from '../output.js';
5
+ const defaultExecFileAsync = promisify(execFile);
6
+ export async function executeStatus(options = {}) {
7
+ const directory = options.directory ?? process.cwd();
8
+ const execFileAsync = options.execFileAsync ?? defaultExecFileAsync;
9
+ if (!(await isInsideWorkTree(execFileAsync, directory))) {
10
+ return { serverHosted: false, clean: true, ahead: 0, behind: 0 };
11
+ }
12
+ const remoteUrl = await eslRemoteUrl(execFileAsync, directory);
13
+ if (remoteUrl === null) {
14
+ return { serverHosted: false, clean: true, ahead: 0, behind: 0 };
15
+ }
16
+ // ADR-0023:Source Remote origin 与配置 server 漂移时只读提示——不修改任何
17
+ // 配置,指引跑一次 upload 完成重指;提示后 ahead/behind 仍按上次本地同步显示。
18
+ try {
19
+ const { server } = await resolveNetworkConfig(options);
20
+ if (sourceRemoteOriginDrifted(remoteUrl, server)) {
21
+ notify('Notice: the esl remote origin differs from the configured ESL server (server origin migration). ' +
22
+ 'Run "esl upload" in this directory once to re-home the remote; ' +
23
+ 'the ahead/behind counts below may be stale until then.');
24
+ }
25
+ }
26
+ catch {
27
+ // server 未配置或读取失败;维持现状
28
+ }
29
+ // Refresh the server-side ref when logged in, so ahead/behind are accurate
30
+ // even when another maintainer pushed in the meantime.
31
+ try {
32
+ const token = await requireFreshToken(options);
33
+ await execFileAsync('git', ['-c', gitAuthHeaderConfig(token), 'fetch', 'esl'], { cwd: directory });
34
+ }
35
+ catch {
36
+ // Not logged in or server unreachable; fall back to the last local sync.
37
+ }
38
+ const status = await execFileAsync('git', ['status', '--porcelain'], { cwd: directory });
39
+ const clean = !status.stdout.trim();
40
+ let ahead = 0;
41
+ let behind = 0;
42
+ try {
43
+ ahead = parseInt((await execFileAsync('git', ['rev-list', '--count', 'esl/main..HEAD'], { cwd: directory })).stdout.trim() || '0', 10);
44
+ behind = parseInt((await execFileAsync('git', ['rev-list', '--count', 'HEAD..esl/main'], { cwd: directory })).stdout.trim() || '0', 10);
45
+ }
46
+ catch {
47
+ // esl/main may not exist yet (the source was never pushed).
48
+ }
49
+ let lastCommit;
50
+ try {
51
+ const log = await execFileAsync('git', ['log', '-1', '--format=%h %s'], { cwd: directory });
52
+ lastCommit = log.stdout.trim() || undefined;
53
+ }
54
+ catch {
55
+ // no commits yet
56
+ }
57
+ return { serverHosted: true, clean, ahead, behind, lastCommit };
58
+ }
59
+ async function isInsideWorkTree(execFileAsync, directory) {
60
+ try {
61
+ const res = await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: directory });
62
+ return res.stdout.trim() === 'true';
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ async function eslRemoteUrl(execFileAsync, directory) {
69
+ try {
70
+ const res = await execFileAsync('git', ['remote', 'get-url', 'esl'], { cwd: directory });
71
+ const url = res.stdout.trim();
72
+ return url.length > 0 ? url : null;
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
@@ -0,0 +1,10 @@
1
+ import { type LocalStoreOptions } from '@esl/core';
2
+ export interface SyncBuiltinOptions extends LocalStoreOptions {
3
+ builtinDir?: string;
4
+ }
5
+ export interface SyncBuiltinResult {
6
+ synced: boolean;
7
+ failed: boolean;
8
+ }
9
+ export declare function syncGlobalBuiltinSkill(options?: SyncBuiltinOptions): Promise<SyncBuiltinResult>;
10
+ export declare function retryPendingGlobalSync(options?: SyncBuiltinOptions): Promise<SyncBuiltinResult>;
@@ -0,0 +1,68 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { BUILTIN_IDENTITY, loadSkillsJson, loadSkillsLock, resolveLocalStorePaths } from '../vendor/core/index.js';
4
+ import { executeInstall } from './install.js';
5
+ import { resolveBuiltinDir } from '../builtin-dir.js';
6
+ const SYNC_PENDING_MARKER = '.builtin-sync-pending';
7
+ export async function syncGlobalBuiltinSkill(options = {}) {
8
+ const globalRoot = resolveLocalStorePaths(options).root;
9
+ try {
10
+ const skillsJson = await loadSkillsJson(globalRoot);
11
+ if (!skillsJson.skills[BUILTIN_IDENTITY]) {
12
+ return { synced: false, failed: false };
13
+ }
14
+ const builtinDir = options.builtinDir ?? resolveBuiltinDir();
15
+ const lock = await loadSkillsLock(globalRoot);
16
+ const installedVersion = lock.skills[BUILTIN_IDENTITY]?.version;
17
+ await executeInstall(BUILTIN_IDENTITY, {
18
+ homeDir: options.homeDir,
19
+ builtinDir,
20
+ global: true,
21
+ noAdapt: false
22
+ });
23
+ const newLock = await loadSkillsLock(globalRoot);
24
+ const synced = newLock.skills[BUILTIN_IDENTITY]?.version !== installedVersion;
25
+ return { synced, failed: false };
26
+ }
27
+ catch (error) {
28
+ await writeSyncPendingMarker(globalRoot, error);
29
+ return { synced: false, failed: true };
30
+ }
31
+ }
32
+ export async function retryPendingGlobalSync(options = {}) {
33
+ const globalRoot = resolveLocalStorePaths(options).root;
34
+ if (!await hasSyncPendingMarker(globalRoot)) {
35
+ return { synced: false, failed: false };
36
+ }
37
+ const result = await syncGlobalBuiltinSkill(options);
38
+ if (!result.failed) {
39
+ await clearSyncPendingMarker(globalRoot);
40
+ }
41
+ return result;
42
+ }
43
+ async function writeSyncPendingMarker(globalRoot, error) {
44
+ try {
45
+ await fs.mkdir(globalRoot, { recursive: true });
46
+ await fs.writeFile(path.join(globalRoot, SYNC_PENDING_MARKER), `${new Date().toISOString()} ${error.message ?? String(error)}\n`, 'utf8');
47
+ }
48
+ catch {
49
+ // Marker writes are best-effort; a failed marker must not mask the original error.
50
+ }
51
+ }
52
+ async function hasSyncPendingMarker(globalRoot) {
53
+ try {
54
+ await fs.access(path.join(globalRoot, SYNC_PENDING_MARKER));
55
+ return true;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
61
+ async function clearSyncPendingMarker(globalRoot) {
62
+ try {
63
+ await fs.rm(path.join(globalRoot, SYNC_PENDING_MARKER), { force: true });
64
+ }
65
+ catch {
66
+ // Best-effort cleanup.
67
+ }
68
+ }
@@ -0,0 +1,19 @@
1
+ import { type LocalStoreOptions, type RemovedToolLinkResult, type ToolLinkEntry, type ToolName } from '@esl/core';
2
+ export interface ToolsListOptions extends LocalStoreOptions {
3
+ projectRoot?: string;
4
+ global?: boolean;
5
+ tool?: string;
6
+ skill?: string;
7
+ managed?: boolean;
8
+ unmanaged?: boolean;
9
+ status?: string;
10
+ }
11
+ export interface ToolsRemoveOptions extends LocalStoreOptions {
12
+ projectRoot?: string;
13
+ global?: boolean;
14
+ tools: ToolName[];
15
+ }
16
+ export declare function executeToolsList(options?: ToolsListOptions): Promise<ToolLinkEntry[]>;
17
+ export declare function executeToolsRemove(skillName: string, options: ToolsRemoveOptions): Promise<RemovedToolLinkResult[]>;
18
+ export declare function parseToolsOption(value: string | undefined): ToolName[];
19
+ export declare function formatToolsList(entries: ToolLinkEntry[]): string[];