@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,58 @@
1
+ import type { ToolName } from './link/tool-links.js';
2
+ export declare const AGENT_INTERACTION_REQUEST_TYPE = "esl.interaction.request";
3
+ export declare const AGENT_INTERACTION_SCHEMA_VERSION = 1;
4
+ export type AgentInteractionFieldKind = 'text' | 'textarea' | 'select' | 'multiselect' | 'confirm' | 'path';
5
+ export interface AgentInteractionOption {
6
+ label: string;
7
+ description?: string;
8
+ }
9
+ export interface AgentInteractionField {
10
+ id: string;
11
+ kind: AgentInteractionFieldKind;
12
+ label: string;
13
+ description?: string;
14
+ required?: boolean;
15
+ default?: string | boolean | string[];
16
+ options?: Array<string | AgentInteractionOption>;
17
+ }
18
+ export interface AgentInteractionRequest {
19
+ type: typeof AGENT_INTERACTION_REQUEST_TYPE;
20
+ schemaVersion: typeof AGENT_INTERACTION_SCHEMA_VERSION;
21
+ requestId: string;
22
+ command: string;
23
+ /** 发起本次调用的 AI 工具标识,来自 --agent-tool。 */
24
+ agentTool?: ToolName;
25
+ fields: AgentInteractionField[];
26
+ }
27
+ export interface AgentInteractionRequestInput {
28
+ command: string;
29
+ fields: AgentInteractionField[];
30
+ requestId?: string;
31
+ agentTool?: ToolName;
32
+ }
33
+ export declare function createAgentInteractionRequest(input: AgentInteractionRequestInput): AgentInteractionRequest;
34
+ /** Claude Code AskUserQuestion 输入中单个选项。 */
35
+ export interface AskUserQuestionOption {
36
+ label: string;
37
+ description?: string;
38
+ }
39
+ /** Claude Code AskUserQuestion 输入中的单个问题。 */
40
+ export interface AskUserQuestionQuestion {
41
+ question: string;
42
+ header: string;
43
+ options: AskUserQuestionOption[];
44
+ multiSelect: boolean;
45
+ }
46
+ /** 与 Claude Code AskUserQuestion 工具输入一致的请求负载。 */
47
+ export interface AskUserQuestionPayload {
48
+ questions: AskUserQuestionQuestion[];
49
+ metadata?: {
50
+ source?: string;
51
+ };
52
+ }
53
+ /** 把内部交互字段映射为 Claude Code AskUserQuestion 风格的 JSON 负载。 */
54
+ export declare function toAskUserQuestionPayload(request: AgentInteractionRequest): AskUserQuestionPayload;
55
+ export declare class AgentInteractionRequiredError extends Error {
56
+ readonly request: AgentInteractionRequest;
57
+ constructor(request: AgentInteractionRequest);
58
+ }
@@ -0,0 +1,58 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export const AGENT_INTERACTION_REQUEST_TYPE = 'esl.interaction.request';
3
+ export const AGENT_INTERACTION_SCHEMA_VERSION = 1;
4
+ export function createAgentInteractionRequest(input) {
5
+ return {
6
+ type: AGENT_INTERACTION_REQUEST_TYPE,
7
+ schemaVersion: AGENT_INTERACTION_SCHEMA_VERSION,
8
+ requestId: input.requestId ?? `ir_${randomUUID()}`,
9
+ command: input.command,
10
+ ...(input.agentTool !== undefined ? { agentTool: input.agentTool } : {}),
11
+ fields: input.fields
12
+ };
13
+ }
14
+ const ASK_USER_QUESTION_SOURCE = 'esl-cli';
15
+ /** 把字段映射为 AskUserQuestion 的一个问题;question/header 复用字段 label。 */
16
+ function fieldToAskUserQuestionQuestion(field) {
17
+ const question = field.label;
18
+ let options;
19
+ switch (field.kind) {
20
+ case 'select':
21
+ case 'multiselect':
22
+ options = (field.options ?? []).map((option) => typeof option === 'string' ? { label: option } : option);
23
+ break;
24
+ case 'confirm':
25
+ options = [
26
+ { label: 'yes', description: '确认' },
27
+ { label: 'no', description: '取消' }
28
+ ];
29
+ break;
30
+ default:
31
+ // text / textarea / path:AskUserQuestion 只做选择题,把默认值作为快捷选项,
32
+ // 自定义文本由用户走 Other 输入;没有默认值时不臆造选项。
33
+ options =
34
+ field.default !== undefined ? [{ label: String(field.default), description: '默认值' }] : [];
35
+ break;
36
+ }
37
+ return {
38
+ question,
39
+ header: question,
40
+ options,
41
+ multiSelect: field.kind === 'multiselect'
42
+ };
43
+ }
44
+ /** 把内部交互字段映射为 Claude Code AskUserQuestion 风格的 JSON 负载。 */
45
+ export function toAskUserQuestionPayload(request) {
46
+ return {
47
+ questions: request.fields.map(fieldToAskUserQuestionQuestion),
48
+ metadata: { source: ASK_USER_QUESTION_SOURCE }
49
+ };
50
+ }
51
+ export class AgentInteractionRequiredError extends Error {
52
+ request;
53
+ constructor(request) {
54
+ super(`Command ${request.command} requires user interaction`);
55
+ this.name = 'AgentInteractionRequiredError';
56
+ this.request = request;
57
+ }
58
+ }
@@ -0,0 +1,5 @@
1
+ export type CommandParams = Record<string, unknown>;
2
+ export declare function parseCommandParams(raw: string, command: string, allowedKeys: readonly string[]): CommandParams;
3
+ export declare function readOptionalStringParam(params: CommandParams, key: string, command: string): string | undefined;
4
+ export declare function readOptionalStringArrayParam(params: CommandParams, key: string, command: string): string[] | undefined;
5
+ export declare function assertNoDuplicateCommandParams(params: CommandParams, explicitFlags: Record<string, unknown>, command: string): void;
@@ -0,0 +1,46 @@
1
+ export function parseCommandParams(raw, command, allowedKeys) {
2
+ let parsed;
3
+ try {
4
+ parsed = JSON.parse(raw);
5
+ }
6
+ catch {
7
+ throw new Error(`Invalid --params-json for ${command}: expected valid JSON`);
8
+ }
9
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
10
+ throw new Error(`Invalid --params-json for ${command}: expected a JSON object`);
11
+ }
12
+ const params = parsed;
13
+ const unknownKeys = Object.keys(params).filter((key) => !allowedKeys.includes(key));
14
+ if (unknownKeys.length > 0) {
15
+ throw new Error(`Invalid --params-json for ${command}: unknown parameter${unknownKeys.length === 1 ? '' : 's'} ${unknownKeys.join(', ')}`);
16
+ }
17
+ return params;
18
+ }
19
+ export function readOptionalStringParam(params, key, command) {
20
+ const value = params[key];
21
+ if (value === undefined) {
22
+ return undefined;
23
+ }
24
+ if (typeof value !== 'string') {
25
+ throw new Error(`Invalid --params-json for ${command}: "${key}" must be a string`);
26
+ }
27
+ return value;
28
+ }
29
+ export function readOptionalStringArrayParam(params, key, command) {
30
+ const value = params[key];
31
+ if (value === undefined) {
32
+ return undefined;
33
+ }
34
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
35
+ throw new Error(`Invalid --params-json for ${command}: "${key}" must be an array of strings`);
36
+ }
37
+ return value;
38
+ }
39
+ export function assertNoDuplicateCommandParams(params, explicitFlags, command) {
40
+ const duplicates = Object.entries(explicitFlags)
41
+ .filter(([key, value]) => value !== undefined && params[key] !== undefined)
42
+ .map(([key]) => key);
43
+ if (duplicates.length > 0) {
44
+ throw new Error(`Parameter${duplicates.length === 1 ? '' : 's'} ${duplicates.join(', ')} cannot be passed both as a flag and in --params-json`);
45
+ }
46
+ }
@@ -0,0 +1,22 @@
1
+ export declare const VERSION = "0.1.0";
2
+ export * from './agent-interaction.js';
3
+ export * from './command-params.js';
4
+ export * from './adapt/index.js';
5
+ export * from './org/org-name.js';
6
+ export * from './org/standing-teams.js';
7
+ export * from './org/account-policy.js';
8
+ export * from './schema/skill-json.js';
9
+ export * from './schema/release-manifest.js';
10
+ export * from './schema/compatibility.js';
11
+ export * from './schema/validation-result.js';
12
+ export * from './skill/builtin-package.js';
13
+ export * from './skill/directory-validator.js';
14
+ export * from './skill/published-package.js';
15
+ export * from './skill/skill-md.js';
16
+ export * from './version/select-version.js';
17
+ export * from './store/file-copy.js';
18
+ export * from './store/local-store.js';
19
+ export * from './store/skills-json.js';
20
+ export * from './link/tool-links.js';
21
+ export * from './link/skill-links.js';
22
+ export * from './store/skill-store.js';
@@ -0,0 +1,22 @@
1
+ export const VERSION = '0.1.0';
2
+ export * from './agent-interaction.js';
3
+ export * from './command-params.js';
4
+ export * from './adapt/index.js';
5
+ export * from './org/org-name.js';
6
+ export * from './org/standing-teams.js';
7
+ export * from './org/account-policy.js';
8
+ export * from './schema/skill-json.js';
9
+ export * from './schema/release-manifest.js';
10
+ export * from './schema/compatibility.js';
11
+ export * from './schema/validation-result.js';
12
+ export * from './skill/builtin-package.js';
13
+ export * from './skill/directory-validator.js';
14
+ export * from './skill/published-package.js';
15
+ export * from './skill/skill-md.js';
16
+ export * from './version/select-version.js';
17
+ export * from './store/file-copy.js';
18
+ export * from './store/local-store.js';
19
+ export * from './store/skills-json.js';
20
+ export * from './link/tool-links.js';
21
+ export * from './link/skill-links.js';
22
+ export * from './store/skill-store.js';
@@ -0,0 +1,38 @@
1
+ import type { SkillsLockEntry } from '../store/skills-json.js';
2
+ import { type InstallManifestSkill } from '../store/skill-store.js';
3
+ export interface LinkStagingMetadata {
4
+ dependencySpecifier?: string;
5
+ lockEntry?: SkillsLockEntry;
6
+ manifestEntry?: InstallManifestSkill;
7
+ }
8
+ export interface LinkStagingRecord extends LinkStagingMetadata {
9
+ identity: string;
10
+ sourceDir: string;
11
+ originalDir: string;
12
+ stagedDir: string;
13
+ }
14
+ export interface LinkSkillOptions {
15
+ identity: string;
16
+ sourceDir: string;
17
+ storeRoot: string;
18
+ force?: boolean;
19
+ previous?: LinkStagingMetadata;
20
+ }
21
+ export interface LinkSkillResult {
22
+ identity: string;
23
+ sourceDir: string;
24
+ targetDir: string;
25
+ status: 'linked' | 'relinked';
26
+ staged: boolean;
27
+ }
28
+ export interface UnlinkSkillDirectoryResult {
29
+ restored: boolean;
30
+ record?: LinkStagingRecord;
31
+ }
32
+ export declare function linkStagingRelativeDir(): string;
33
+ export declare function linkStagingRecordPath(storeRoot: string): string;
34
+ export declare function linkSkillDirectory(options: LinkSkillOptions): Promise<LinkSkillResult>;
35
+ export declare function hasLinkStagingRecord(identity: string, storeRoot: string): Promise<boolean>;
36
+ export declare function unlinkSkillDirectory(identity: string, storeRoot: string): Promise<UnlinkSkillDirectoryResult>;
37
+ export declare function removeLinkedSkillDirectory(identity: string, storeRoot: string): Promise<void>;
38
+ export declare function isLinkedSkill(identity: string, storeRoot: string): Promise<boolean>;
@@ -0,0 +1,245 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { skillSourceDirectoryName, skillSourceRelativeDir } from '../store/skill-store.js';
4
+ const LINK_STAGING_RECORD_FILE = '.esl-link-staging.json';
5
+ export function linkStagingRelativeDir() {
6
+ return path.join('link-staging');
7
+ }
8
+ export function linkStagingRecordPath(storeRoot) {
9
+ return path.join(storeRoot, LINK_STAGING_RECORD_FILE);
10
+ }
11
+ function linkStagingDir(storeRoot, identity) {
12
+ return path.join(storeRoot, linkStagingRelativeDir(), skillSourceDirectoryName(identity));
13
+ }
14
+ async function loadLinkStagingStore(storeRoot) {
15
+ try {
16
+ const raw = await fs.readFile(linkStagingRecordPath(storeRoot), 'utf8');
17
+ const parsed = JSON.parse(raw);
18
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
19
+ throw new Error('Invalid ESL link staging record');
20
+ }
21
+ return parsed;
22
+ }
23
+ catch (error) {
24
+ if (error.code === 'ENOENT') {
25
+ return {};
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+ async function saveLinkStagingStore(storeRoot, records) {
31
+ await fs.mkdir(storeRoot, { recursive: true });
32
+ await fs.writeFile(linkStagingRecordPath(storeRoot), `${JSON.stringify(records, null, 2)}\n`, 'utf8');
33
+ }
34
+ async function isSymbolicLink(targetDir) {
35
+ try {
36
+ const stat = await fs.lstat(targetDir);
37
+ return stat.isSymbolicLink();
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ async function directoryExists(dir) {
44
+ try {
45
+ const stat = await fs.stat(dir);
46
+ return stat.isDirectory();
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ async function readLinkTarget(linkPath) {
53
+ const target = await fs.readlink(linkPath);
54
+ return path.resolve(path.dirname(linkPath), target);
55
+ }
56
+ function samePath(left, right) {
57
+ const normalizedLeft = path.resolve(left);
58
+ const normalizedRight = path.resolve(right);
59
+ return process.platform === 'win32'
60
+ ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
61
+ : normalizedLeft === normalizedRight;
62
+ }
63
+ function isInside(parent, candidate) {
64
+ const relative = path.relative(path.resolve(parent), path.resolve(candidate));
65
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
66
+ }
67
+ async function createSourceLink(sourceDir, targetDir) {
68
+ if (process.platform === 'win32') {
69
+ await fs.symlink(sourceDir, targetDir, 'junction');
70
+ }
71
+ else {
72
+ await fs.symlink(sourceDir, targetDir, 'dir');
73
+ }
74
+ }
75
+ async function assertStagingAvailable(storeRoot, identity, stagedDir) {
76
+ const records = await loadLinkStagingStore(storeRoot);
77
+ if (records[identity]) {
78
+ throw new Error(`Link staging already contains ${identity}`);
79
+ }
80
+ try {
81
+ await fs.access(stagedDir);
82
+ throw new Error(`Unexpected content exists at link staging path for ${identity}`);
83
+ }
84
+ catch (error) {
85
+ if (error.code !== 'ENOENT') {
86
+ throw error;
87
+ }
88
+ }
89
+ }
90
+ async function stageOriginalDirectory(options) {
91
+ const { identity, sourceDir, storeRoot, targetDir, stagedDir, metadata } = options;
92
+ const record = {
93
+ identity,
94
+ sourceDir,
95
+ originalDir: targetDir,
96
+ stagedDir,
97
+ ...(metadata ?? {})
98
+ };
99
+ await fs.mkdir(path.dirname(stagedDir), { recursive: true });
100
+ await fs.rename(targetDir, stagedDir);
101
+ const records = await loadLinkStagingStore(storeRoot);
102
+ records[identity] = record;
103
+ try {
104
+ await saveLinkStagingStore(storeRoot, records);
105
+ await fs.mkdir(path.dirname(targetDir), { recursive: true });
106
+ await createSourceLink(sourceDir, targetDir);
107
+ }
108
+ catch (error) {
109
+ const rollbackRecords = await loadLinkStagingStore(storeRoot);
110
+ delete rollbackRecords[identity];
111
+ try {
112
+ await saveLinkStagingStore(storeRoot, rollbackRecords);
113
+ await fs.rename(stagedDir, targetDir);
114
+ }
115
+ catch (rollbackError) {
116
+ throw new Error(`Failed to create skill source link and roll back staging: ${rollbackError.message}`, { cause: error });
117
+ }
118
+ throw error;
119
+ }
120
+ }
121
+ export async function linkSkillDirectory(options) {
122
+ const { identity, storeRoot, force = false } = options;
123
+ const sourceDir = path.resolve(options.sourceDir);
124
+ const targetDir = path.resolve(storeRoot, skillSourceRelativeDir(identity));
125
+ const stagedDir = linkStagingDir(storeRoot, identity);
126
+ if (!(await directoryExists(sourceDir))) {
127
+ throw new Error(`Skill source directory does not exist: ${sourceDir}`);
128
+ }
129
+ if (isInside(sourceDir, targetDir)) {
130
+ throw new Error(`Skill source link target must not be inside the source directory: ${targetDir}`);
131
+ }
132
+ if (await isSymbolicLink(targetDir)) {
133
+ const currentTarget = await readLinkTarget(targetDir);
134
+ if (samePath(currentTarget, sourceDir)) {
135
+ return { identity, sourceDir, targetDir, status: 'linked', staged: false };
136
+ }
137
+ if (!force) {
138
+ throw new Error(`Skill ${identity} is already linked to ${currentTarget}; use --force to replace it`);
139
+ }
140
+ await fs.rm(targetDir, { force: true });
141
+ await fs.mkdir(path.dirname(targetDir), { recursive: true });
142
+ await createSourceLink(sourceDir, targetDir);
143
+ return { identity, sourceDir, targetDir, status: 'relinked', staged: false };
144
+ }
145
+ if (await directoryExists(targetDir)) {
146
+ if (!force) {
147
+ throw new Error(`Skill ${identity} is already installed at ${targetDir}; use --force to link it`);
148
+ }
149
+ await assertStagingAvailable(storeRoot, identity, stagedDir);
150
+ await stageOriginalDirectory({
151
+ identity,
152
+ sourceDir,
153
+ storeRoot,
154
+ targetDir,
155
+ stagedDir,
156
+ metadata: options.previous
157
+ });
158
+ return { identity, sourceDir, targetDir, status: 'relinked', staged: true };
159
+ }
160
+ try {
161
+ await fs.lstat(targetDir);
162
+ throw new Error(`Skill target must not be a regular file: ${targetDir}`);
163
+ }
164
+ catch (error) {
165
+ if (error.code !== 'ENOENT') {
166
+ throw error;
167
+ }
168
+ }
169
+ await fs.mkdir(path.dirname(targetDir), { recursive: true });
170
+ await createSourceLink(sourceDir, targetDir);
171
+ return { identity, sourceDir, targetDir, status: 'linked', staged: false };
172
+ }
173
+ export async function hasLinkStagingRecord(identity, storeRoot) {
174
+ const records = await loadLinkStagingStore(storeRoot);
175
+ return Boolean(records[identity]);
176
+ }
177
+ export async function unlinkSkillDirectory(identity, storeRoot) {
178
+ const targetDir = path.resolve(storeRoot, skillSourceRelativeDir(identity));
179
+ const records = await loadLinkStagingStore(storeRoot);
180
+ const record = records[identity];
181
+ if (!await isSymbolicLink(targetDir)) {
182
+ try {
183
+ await fs.lstat(targetDir);
184
+ throw new Error(`Skill ${identity} target is not a skill source link: ${targetDir}`);
185
+ }
186
+ catch (error) {
187
+ if (error.code !== 'ENOENT') {
188
+ throw error;
189
+ }
190
+ }
191
+ }
192
+ if (!record) {
193
+ await fs.rm(targetDir, { force: true });
194
+ return { restored: false };
195
+ }
196
+ if (!(await directoryExists(record.stagedDir))) {
197
+ throw new Error(`Link staging for ${identity} is missing its original copy; link state was kept`);
198
+ }
199
+ if (!samePath(record.originalDir, targetDir)) {
200
+ throw new Error(`Link staging for ${identity} points to an unexpected original location`);
201
+ }
202
+ await fs.rm(targetDir, { force: true });
203
+ try {
204
+ await fs.mkdir(path.dirname(targetDir), { recursive: true });
205
+ await fs.rename(record.stagedDir, targetDir);
206
+ }
207
+ catch (error) {
208
+ await createSourceLink(record.sourceDir, targetDir);
209
+ throw error;
210
+ }
211
+ delete records[identity];
212
+ await saveLinkStagingStore(storeRoot, records);
213
+ await fs.rmdir(path.dirname(record.stagedDir)).catch(() => { });
214
+ await fs.rmdir(path.join(storeRoot, linkStagingRelativeDir())).catch(() => { });
215
+ return { restored: true, record };
216
+ }
217
+ export async function removeLinkedSkillDirectory(identity, storeRoot) {
218
+ const targetDir = path.resolve(storeRoot, skillSourceRelativeDir(identity));
219
+ const records = await loadLinkStagingStore(storeRoot);
220
+ const record = records[identity];
221
+ if (!await isSymbolicLink(targetDir)) {
222
+ try {
223
+ await fs.lstat(targetDir);
224
+ throw new Error(`Skill ${identity} target is not a skill source link: ${targetDir}`);
225
+ }
226
+ catch (error) {
227
+ if (error.code !== 'ENOENT') {
228
+ throw error;
229
+ }
230
+ }
231
+ }
232
+ await fs.rm(targetDir, { force: true });
233
+ if (record) {
234
+ if (!(await directoryExists(record.stagedDir))) {
235
+ throw new Error(`Link staging for ${identity} is missing its original copy`);
236
+ }
237
+ await fs.rm(record.stagedDir, { recursive: true, force: true });
238
+ delete records[identity];
239
+ await saveLinkStagingStore(storeRoot, records);
240
+ }
241
+ }
242
+ export async function isLinkedSkill(identity, storeRoot) {
243
+ const targetDir = path.resolve(storeRoot, skillSourceRelativeDir(identity));
244
+ return isSymbolicLink(targetDir);
245
+ }
@@ -0,0 +1,83 @@
1
+ export declare const SUPPORTED_TOOLS: readonly ["claude", "codex", "cursor", "trae-intl", "trae-cn", "workbuddy", "opencode", "openclaw", "hermes"];
2
+ export type ToolName = (typeof SUPPORTED_TOOLS)[number];
3
+ export type ToolLevel = 'project' | 'global';
4
+ export type ToolLinkStatus = 'linked' | 'broken' | 'conflict' | 'source-only' | 'unmanaged';
5
+ export interface ToolLinkRecord {
6
+ identity: string;
7
+ tool: ToolName;
8
+ level: ToolLevel;
9
+ sourceDir: string;
10
+ targetDir: string;
11
+ createdAt: string;
12
+ }
13
+ export interface ToolLinkManifest {
14
+ version: 1;
15
+ links: ToolLinkRecord[];
16
+ }
17
+ export interface ToolLinkEntry {
18
+ tool: ToolName | 'source';
19
+ identity: string;
20
+ level: ToolLevel;
21
+ status: ToolLinkStatus;
22
+ managed: boolean;
23
+ sourceDir: string;
24
+ targetDir?: string;
25
+ }
26
+ export interface CreateToolLinkOptions {
27
+ identity: string;
28
+ tool: ToolName;
29
+ level: ToolLevel;
30
+ storeRoot: string;
31
+ projectRoot?: string;
32
+ homeDir?: string;
33
+ force?: boolean;
34
+ }
35
+ export interface ToolLinkOperationResult {
36
+ identity: string;
37
+ tool: ToolName;
38
+ targetDir: string;
39
+ status: 'created' | 'existing' | 'conflict' | 'failed';
40
+ error?: string;
41
+ }
42
+ export declare function isSupportedTool(value: string): value is ToolName;
43
+ export declare function resolveToolName(value: string): ToolName | undefined;
44
+ export declare function parseToolSelection(value: string): ToolName[];
45
+ export declare function toolDirectory(tool: ToolName, level: ToolLevel, options?: {
46
+ projectRoot?: string;
47
+ homeDir?: string;
48
+ }): string;
49
+ export declare function toolLinkManifestPath(storeRoot: string): string;
50
+ export declare function defaultToolLinkManifest(): ToolLinkManifest;
51
+ export declare function loadToolLinkManifest(storeRoot: string): Promise<ToolLinkManifest>;
52
+ export declare function saveToolLinkManifest(storeRoot: string, manifest: ToolLinkManifest): Promise<void>;
53
+ export declare function createToolLink(options: CreateToolLinkOptions): Promise<ToolLinkOperationResult>;
54
+ export interface SyncToolLinksOptions {
55
+ storeRoot: string;
56
+ level: ToolLevel;
57
+ tools: ToolName[];
58
+ identities?: string[];
59
+ projectRoot?: string;
60
+ homeDir?: string;
61
+ force?: boolean;
62
+ }
63
+ export declare function syncToolLinks(options: SyncToolLinksOptions): Promise<ToolLinkOperationResult[]>;
64
+ export interface RemoveToolLinksOptions {
65
+ storeRoot: string;
66
+ identity: string;
67
+ tools: ToolName[];
68
+ level: ToolLevel;
69
+ }
70
+ export interface RemovedToolLinkResult {
71
+ identity: string;
72
+ tool: ToolName;
73
+ targetDir: string;
74
+ status: 'removed' | 'missing' | 'conflict';
75
+ }
76
+ export declare function removeToolLinks(options: RemoveToolLinksOptions): Promise<RemovedToolLinkResult[]>;
77
+ export interface ListToolLinksOptions {
78
+ storeRoot: string;
79
+ level: ToolLevel;
80
+ projectRoot?: string;
81
+ homeDir?: string;
82
+ }
83
+ export declare function listToolLinks(options: ListToolLinksOptions): Promise<ToolLinkEntry[]>;