@bowenqt/qiniu-ai-sdk 0.10.0 → 0.13.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 (149) hide show
  1. package/README.md +225 -0
  2. package/dist/ai/agent-graph.d.ts +99 -0
  3. package/dist/ai/agent-graph.d.ts.map +1 -0
  4. package/dist/ai/agent-graph.js +321 -0
  5. package/dist/ai/agent-graph.js.map +1 -0
  6. package/dist/ai/agent-graph.mjs +317 -0
  7. package/dist/ai/generate-text.d.ts +51 -0
  8. package/dist/ai/generate-text.d.ts.map +1 -1
  9. package/dist/ai/generate-text.js +132 -0
  10. package/dist/ai/generate-text.js.map +1 -1
  11. package/dist/ai/generate-text.mjs +131 -0
  12. package/dist/ai/graph/checkpointer.d.ts +112 -0
  13. package/dist/ai/graph/checkpointer.d.ts.map +1 -0
  14. package/dist/ai/graph/checkpointer.js +131 -0
  15. package/dist/ai/graph/checkpointer.js.map +1 -0
  16. package/dist/ai/graph/checkpointer.mjs +126 -0
  17. package/dist/ai/graph/index.d.ts +13 -0
  18. package/dist/ai/graph/index.d.ts.map +1 -0
  19. package/dist/ai/graph/index.js +22 -0
  20. package/dist/ai/graph/index.js.map +1 -0
  21. package/dist/ai/graph/index.mjs +12 -0
  22. package/dist/ai/graph/postgres-checkpointer.d.ts +54 -0
  23. package/dist/ai/graph/postgres-checkpointer.d.ts.map +1 -0
  24. package/dist/ai/graph/postgres-checkpointer.js +134 -0
  25. package/dist/ai/graph/postgres-checkpointer.js.map +1 -0
  26. package/dist/ai/graph/postgres-checkpointer.mjs +130 -0
  27. package/dist/ai/graph/redis-checkpointer.d.ts +51 -0
  28. package/dist/ai/graph/redis-checkpointer.d.ts.map +1 -0
  29. package/dist/ai/graph/redis-checkpointer.js +124 -0
  30. package/dist/ai/graph/redis-checkpointer.js.map +1 -0
  31. package/dist/ai/graph/redis-checkpointer.mjs +120 -0
  32. package/dist/ai/graph/state-graph.d.ts +41 -0
  33. package/dist/ai/graph/state-graph.d.ts.map +1 -0
  34. package/dist/ai/graph/state-graph.js +149 -0
  35. package/dist/ai/graph/state-graph.js.map +1 -0
  36. package/dist/ai/graph/state-graph.mjs +144 -0
  37. package/dist/ai/graph/types.d.ts +41 -0
  38. package/dist/ai/graph/types.d.ts.map +1 -0
  39. package/dist/ai/graph/types.js +10 -0
  40. package/dist/ai/graph/types.js.map +1 -0
  41. package/dist/ai/graph/types.mjs +7 -0
  42. package/dist/ai/internal-types.d.ts +109 -0
  43. package/dist/ai/internal-types.d.ts.map +1 -0
  44. package/dist/ai/internal-types.js +28 -0
  45. package/dist/ai/internal-types.js.map +1 -0
  46. package/dist/ai/internal-types.mjs +23 -0
  47. package/dist/ai/nodes/execute-node.d.ts +27 -0
  48. package/dist/ai/nodes/execute-node.d.ts.map +1 -0
  49. package/dist/ai/nodes/execute-node.js +118 -0
  50. package/dist/ai/nodes/execute-node.js.map +1 -0
  51. package/dist/ai/nodes/execute-node.mjs +114 -0
  52. package/dist/ai/nodes/index.d.ts +8 -0
  53. package/dist/ai/nodes/index.d.ts.map +1 -0
  54. package/dist/ai/nodes/index.js +16 -0
  55. package/dist/ai/nodes/index.js.map +1 -0
  56. package/dist/ai/nodes/index.mjs +7 -0
  57. package/dist/ai/nodes/memory-node.d.ts +34 -0
  58. package/dist/ai/nodes/memory-node.d.ts.map +1 -0
  59. package/dist/ai/nodes/memory-node.js +164 -0
  60. package/dist/ai/nodes/memory-node.js.map +1 -0
  61. package/dist/ai/nodes/memory-node.mjs +158 -0
  62. package/dist/ai/nodes/predict-node.d.ts +42 -0
  63. package/dist/ai/nodes/predict-node.d.ts.map +1 -0
  64. package/dist/ai/nodes/predict-node.js +89 -0
  65. package/dist/ai/nodes/predict-node.js.map +1 -0
  66. package/dist/ai/nodes/predict-node.mjs +86 -0
  67. package/dist/ai/nodes/types.d.ts +44 -0
  68. package/dist/ai/nodes/types.d.ts.map +1 -0
  69. package/dist/ai/nodes/types.js +6 -0
  70. package/dist/ai/nodes/types.js.map +1 -0
  71. package/dist/ai/nodes/types.mjs +5 -0
  72. package/dist/index.d.ts +23 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +80 -1
  75. package/dist/index.js.map +1 -1
  76. package/dist/index.mjs +34 -0
  77. package/dist/lib/otel-tracer.d.ts +47 -0
  78. package/dist/lib/otel-tracer.d.ts.map +1 -0
  79. package/dist/lib/otel-tracer.js +79 -0
  80. package/dist/lib/otel-tracer.js.map +1 -0
  81. package/dist/lib/otel-tracer.mjs +75 -0
  82. package/dist/lib/token-estimator.d.ts +62 -0
  83. package/dist/lib/token-estimator.d.ts.map +1 -0
  84. package/dist/lib/token-estimator.js +106 -0
  85. package/dist/lib/token-estimator.js.map +1 -0
  86. package/dist/lib/token-estimator.mjs +100 -0
  87. package/dist/lib/tool-registry.d.ts +103 -0
  88. package/dist/lib/tool-registry.d.ts.map +1 -0
  89. package/dist/lib/tool-registry.js +159 -0
  90. package/dist/lib/tool-registry.js.map +1 -0
  91. package/dist/lib/tool-registry.mjs +154 -0
  92. package/dist/lib/tracer.d.ts +85 -0
  93. package/dist/lib/tracer.d.ts.map +1 -0
  94. package/dist/lib/tracer.js +170 -0
  95. package/dist/lib/tracer.js.map +1 -0
  96. package/dist/lib/tracer.mjs +161 -0
  97. package/dist/lib/types.d.ts +11 -0
  98. package/dist/lib/types.d.ts.map +1 -1
  99. package/dist/modules/mcp/adapter.d.ts +23 -0
  100. package/dist/modules/mcp/adapter.d.ts.map +1 -0
  101. package/dist/modules/mcp/adapter.js +63 -0
  102. package/dist/modules/mcp/adapter.js.map +1 -0
  103. package/dist/modules/mcp/adapter.mjs +58 -0
  104. package/dist/modules/mcp/client.d.ts +75 -0
  105. package/dist/modules/mcp/client.d.ts.map +1 -0
  106. package/dist/modules/mcp/client.js +300 -0
  107. package/dist/modules/mcp/client.js.map +1 -0
  108. package/dist/modules/mcp/client.mjs +295 -0
  109. package/dist/modules/mcp/http-transport.d.ts +51 -0
  110. package/dist/modules/mcp/http-transport.d.ts.map +1 -0
  111. package/dist/modules/mcp/http-transport.js +146 -0
  112. package/dist/modules/mcp/http-transport.js.map +1 -0
  113. package/dist/modules/mcp/http-transport.mjs +141 -0
  114. package/dist/modules/mcp/index.d.ts +11 -0
  115. package/dist/modules/mcp/index.d.ts.map +1 -0
  116. package/dist/modules/mcp/index.js +34 -0
  117. package/dist/modules/mcp/index.js.map +1 -0
  118. package/dist/modules/mcp/index.mjs +14 -0
  119. package/dist/modules/mcp/oauth.d.ts +101 -0
  120. package/dist/modules/mcp/oauth.d.ts.map +1 -0
  121. package/dist/modules/mcp/oauth.js +347 -0
  122. package/dist/modules/mcp/oauth.js.map +1 -0
  123. package/dist/modules/mcp/oauth.mjs +304 -0
  124. package/dist/modules/mcp/token-store.d.ts +69 -0
  125. package/dist/modules/mcp/token-store.d.ts.map +1 -0
  126. package/dist/modules/mcp/token-store.js +174 -0
  127. package/dist/modules/mcp/token-store.js.map +1 -0
  128. package/dist/modules/mcp/token-store.mjs +135 -0
  129. package/dist/modules/mcp/types.d.ts +91 -0
  130. package/dist/modules/mcp/types.d.ts.map +1 -0
  131. package/dist/modules/mcp/types.js +14 -0
  132. package/dist/modules/mcp/types.js.map +1 -0
  133. package/dist/modules/mcp/types.mjs +11 -0
  134. package/dist/modules/skills/index.d.ts +7 -0
  135. package/dist/modules/skills/index.d.ts.map +1 -0
  136. package/dist/modules/skills/index.js +14 -0
  137. package/dist/modules/skills/index.js.map +1 -0
  138. package/dist/modules/skills/index.mjs +6 -0
  139. package/dist/modules/skills/loader.d.ts +51 -0
  140. package/dist/modules/skills/loader.d.ts.map +1 -0
  141. package/dist/modules/skills/loader.js +237 -0
  142. package/dist/modules/skills/loader.js.map +1 -0
  143. package/dist/modules/skills/loader.mjs +198 -0
  144. package/dist/modules/skills/types.d.ts +60 -0
  145. package/dist/modules/skills/types.d.ts.map +1 -0
  146. package/dist/modules/skills/types.js +20 -0
  147. package/dist/modules/skills/types.js.map +1 -0
  148. package/dist/modules/skills/types.mjs +17 -0
  149. package/package.json +4 -1
@@ -0,0 +1,91 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Client types.
3
+ * Phase 1: stdio transport + Bearer token
4
+ * Phase 3: HTTP transport + OAuth 2.0
5
+ */
6
+ import type { ToolParameters } from '../../lib/tool-registry';
7
+ /** MCP transport type */
8
+ export type MCPTransport = 'stdio' | 'http';
9
+ /** OAuth 2.0 configuration for HTTP transport */
10
+ export interface MCPOAuthConfig {
11
+ /** OAuth client ID */
12
+ clientId: string;
13
+ /** OAuth client secret (optional for public clients) */
14
+ clientSecret?: string;
15
+ /** Requested scopes */
16
+ scopes: string[];
17
+ /** Authorization endpoint URL (auto-discovered if not set) */
18
+ authorizationUrl?: string;
19
+ /** Token endpoint URL (auto-discovered if not set) */
20
+ tokenUrl?: string;
21
+ /** Device code endpoint for headless auth (optional) */
22
+ deviceCodeUrl?: string;
23
+ /** Redirect URI for auth code flow (default: random localhost port) */
24
+ redirectUri?: string;
25
+ }
26
+ /** Base MCP server configuration */
27
+ export interface MCPServerConfigBase {
28
+ /** Server name (unique identifier) */
29
+ name: string;
30
+ /** Bearer token for authentication (legacy) */
31
+ token?: string;
32
+ }
33
+ /** Stdio transport configuration */
34
+ export interface MCPStdioServerConfig extends MCPServerConfigBase {
35
+ transport: 'stdio';
36
+ /** Command to execute */
37
+ command: string;
38
+ /** Command arguments */
39
+ args?: string[];
40
+ /** Environment variables */
41
+ env?: Record<string, string>;
42
+ }
43
+ /** HTTP transport configuration */
44
+ export interface MCPHttpServerConfig extends MCPServerConfigBase {
45
+ transport: 'http';
46
+ /** MCP server URL (e.g., https://mcp.example.com/mcp) */
47
+ url: string;
48
+ /** Static bearer token for authentication */
49
+ token?: string;
50
+ /** Dynamic token provider (e.g., from TokenManager) */
51
+ tokenProvider?: () => Promise<string>;
52
+ /** OAuth configuration (for reference, use with TokenManager) */
53
+ oauth?: MCPOAuthConfig;
54
+ /** Custom headers */
55
+ headers?: Record<string, string>;
56
+ /** Request timeout in ms (default: 30000) */
57
+ timeout?: number;
58
+ }
59
+ /** Union type for server configuration */
60
+ export type MCPServerConfig = MCPStdioServerConfig | MCPHttpServerConfig;
61
+ /** MCP tool definition from server */
62
+ export interface MCPToolDefinition {
63
+ name: string;
64
+ description: string;
65
+ inputSchema: ToolParameters;
66
+ }
67
+ /** MCP client configuration */
68
+ export interface MCPClientConfig {
69
+ /** Servers to connect */
70
+ servers: MCPServerConfig[];
71
+ /** Connection timeout in ms */
72
+ connectionTimeout?: number;
73
+ }
74
+ /** MCP tool execution result */
75
+ export interface MCPToolResult {
76
+ content: Array<{
77
+ type: 'text' | 'image' | 'resource';
78
+ text?: string;
79
+ data?: string;
80
+ mimeType?: string;
81
+ }>;
82
+ isError?: boolean;
83
+ }
84
+ /** MCP server connection state */
85
+ export type MCPConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
86
+ /** Default configuration */
87
+ export declare const DEFAULT_MCP_CONFIG: {
88
+ readonly connectionTimeout: 30000;
89
+ readonly httpTimeout: 30000;
90
+ };
91
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modules/mcp/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,yBAAyB;AACzB,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5C,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC3B,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,oCAAoC;AACpC,MAAM,WAAW,mBAAmB;IAChC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,oCAAoC;AACpC,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC7D,SAAS,EAAE,OAAO,CAAC;IACnB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,mCAAmC;AACnC,MAAM,WAAW,mBAAoB,SAAQ,mBAAmB;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,iEAAiE;IACjE,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,0CAA0C;AAC1C,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG,mBAAmB,CAAC;AAEzE,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,CAAC;CAC/B;AAED,+BAA+B;AAC/B,MAAM,WAAW,eAAe;IAC5B,yBAAyB;IACzB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,+BAA+B;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gCAAgC;AAChC,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;QACpC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,kCAAkC;AAClC,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;AAEvF,4BAA4B;AAC5B,eAAO,MAAM,kBAAkB;;;CAGrB,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * MCP (Model Context Protocol) Client types.
4
+ * Phase 1: stdio transport + Bearer token
5
+ * Phase 3: HTTP transport + OAuth 2.0
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.DEFAULT_MCP_CONFIG = void 0;
9
+ /** Default configuration */
10
+ exports.DEFAULT_MCP_CONFIG = {
11
+ connectionTimeout: 30000, // 30s
12
+ httpTimeout: 30000, // 30s per request
13
+ };
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/modules/mcp/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AA6FH,4BAA4B;AACf,QAAA,kBAAkB,GAAG;IAC9B,iBAAiB,EAAE,KAAK,EAAE,MAAM;IAChC,WAAW,EAAE,KAAK,EAAE,kBAAkB;CAChC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Client types.
3
+ * Phase 1: stdio transport + Bearer token
4
+ * Phase 3: HTTP transport + OAuth 2.0
5
+ */
6
+ /** Default configuration */
7
+ export const DEFAULT_MCP_CONFIG = {
8
+ connectionTimeout: 30000, // 30s
9
+ httpTimeout: 30000, // 30s per request
10
+ };
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Skills module public exports.
3
+ */
4
+ export { SkillLoader, SkillSecurityError, SkillNotFoundError } from './loader';
5
+ export type { Skill, SkillLoaderConfig, SkillReference, SkillInjectionConfig, SkillInjectionPosition, SkillBudget, } from './types';
6
+ export { DEFAULT_SKILL_CONFIG, DEFAULT_SKILL_BUDGET } from './types';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC/E,YAAY,EACR,KAAK,EACL,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * Skills module public exports.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_SKILL_BUDGET = exports.DEFAULT_SKILL_CONFIG = exports.SkillNotFoundError = exports.SkillSecurityError = exports.SkillLoader = void 0;
7
+ var loader_1 = require("./loader");
8
+ Object.defineProperty(exports, "SkillLoader", { enumerable: true, get: function () { return loader_1.SkillLoader; } });
9
+ Object.defineProperty(exports, "SkillSecurityError", { enumerable: true, get: function () { return loader_1.SkillSecurityError; } });
10
+ Object.defineProperty(exports, "SkillNotFoundError", { enumerable: true, get: function () { return loader_1.SkillNotFoundError; } });
11
+ var types_1 = require("./types");
12
+ Object.defineProperty(exports, "DEFAULT_SKILL_CONFIG", { enumerable: true, get: function () { return types_1.DEFAULT_SKILL_CONFIG; } });
13
+ Object.defineProperty(exports, "DEFAULT_SKILL_BUDGET", { enumerable: true, get: function () { return types_1.DEFAULT_SKILL_BUDGET; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,mCAA+E;AAAtE,qGAAA,WAAW,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAS5D,iCAAqE;AAA5D,6GAAA,oBAAoB,OAAA;AAAE,6GAAA,oBAAoB,OAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Skills module public exports.
3
+ */
4
+ export { SkillLoader, SkillSecurityError, SkillNotFoundError } from './loader.mjs';
5
+ export { DEFAULT_SKILL_CONFIG, DEFAULT_SKILL_BUDGET } from './types.mjs';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Skill Loader with security-first file validation.
3
+ * - Only relative paths allowed
4
+ * - realpath + root check for each reference
5
+ * - Size limits enforced
6
+ */
7
+ import type { Skill, SkillLoaderConfig } from './types';
8
+ /** Security error for path/file violations */
9
+ export declare class SkillSecurityError extends Error {
10
+ readonly path?: string | undefined;
11
+ constructor(message: string, path?: string | undefined);
12
+ }
13
+ /** Skill not found error */
14
+ export declare class SkillNotFoundError extends Error {
15
+ constructor(skillName: string);
16
+ }
17
+ /**
18
+ * Skill Loader loads and validates skill files.
19
+ */
20
+ export declare class SkillLoader {
21
+ private readonly config;
22
+ private readonly resolvedSkillsDir;
23
+ constructor(config: SkillLoaderConfig);
24
+ /**
25
+ * Load a skill by name.
26
+ */
27
+ load(skillName: string): Promise<Skill>;
28
+ /**
29
+ * Load all skills from the skills directory.
30
+ */
31
+ loadAll(): Promise<Skill[]>;
32
+ /**
33
+ * Read a file with security validation.
34
+ */
35
+ private readFileSecure;
36
+ /**
37
+ * Parse and load references from content.
38
+ * Only relative paths are allowed.
39
+ */
40
+ private loadReferences;
41
+ /**
42
+ * Estimate token count using unified token-estimator.
43
+ */
44
+ private estimateTokens;
45
+ /**
46
+ * Safe boundary check for path containment.
47
+ * Uses path.relative to avoid prefix bypass attacks.
48
+ */
49
+ private isWithinRoot;
50
+ }
51
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAkB,MAAM,SAAS,CAAC;AAIxE,8CAA8C;AAC9C,qBAAa,kBAAmB,SAAQ,KAAK;aACI,IAAI,CAAC,EAAE,MAAM;gBAA9C,OAAO,EAAE,MAAM,EAAkB,IAAI,CAAC,EAAE,MAAM,YAAA;CAI7D;AAED,4BAA4B;AAC5B,qBAAa,kBAAmB,SAAQ,KAAK;gBAC7B,SAAS,EAAE,MAAM;CAIhC;AAED;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAE/B,MAAM,EAAE,iBAAiB;IAerC;;OAEG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IA0C7C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IA0BjC;;OAEG;YACW,cAAc;IAgC5B;;;OAGG;YACW,cAAc;IAmD5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;;OAGG;IACH,OAAO,CAAC,YAAY;CAcvB"}
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ /**
3
+ * Skill Loader with security-first file validation.
4
+ * - Only relative paths allowed
5
+ * - realpath + root check for each reference
6
+ * - Size limits enforced
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.SkillLoader = exports.SkillNotFoundError = exports.SkillSecurityError = void 0;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const types_1 = require("./types");
46
+ const token_estimator_1 = require("../../lib/token-estimator");
47
+ /** Security error for path/file violations */
48
+ class SkillSecurityError extends Error {
49
+ constructor(message, path) {
50
+ super(message);
51
+ this.path = path;
52
+ this.name = 'SkillSecurityError';
53
+ }
54
+ }
55
+ exports.SkillSecurityError = SkillSecurityError;
56
+ /** Skill not found error */
57
+ class SkillNotFoundError extends Error {
58
+ constructor(skillName) {
59
+ super(`Skill not found: ${skillName}`);
60
+ this.name = 'SkillNotFoundError';
61
+ }
62
+ }
63
+ exports.SkillNotFoundError = SkillNotFoundError;
64
+ /**
65
+ * Skill Loader loads and validates skill files.
66
+ */
67
+ class SkillLoader {
68
+ constructor(config) {
69
+ this.config = {
70
+ skillsDir: config.skillsDir,
71
+ allowedExtensions: config.allowedExtensions ?? [...types_1.DEFAULT_SKILL_CONFIG.allowedExtensions],
72
+ maxFileSize: config.maxFileSize ?? types_1.DEFAULT_SKILL_CONFIG.maxFileSize,
73
+ maxReferenceDepth: config.maxReferenceDepth ?? types_1.DEFAULT_SKILL_CONFIG.maxReferenceDepth,
74
+ };
75
+ // Resolve and validate skills directory
76
+ if (!fs.existsSync(config.skillsDir)) {
77
+ throw new SkillSecurityError(`Skills directory not found: ${config.skillsDir}`);
78
+ }
79
+ this.resolvedSkillsDir = fs.realpathSync(config.skillsDir);
80
+ }
81
+ /**
82
+ * Load a skill by name.
83
+ */
84
+ async load(skillName) {
85
+ // Validate skill name (no path separators)
86
+ if (skillName.includes('/') || skillName.includes('\\') || skillName.includes('..')) {
87
+ throw new SkillSecurityError('Invalid skill name: path separators not allowed', skillName);
88
+ }
89
+ const skillDir = path.join(this.resolvedSkillsDir, skillName);
90
+ // Check skill directory exists
91
+ if (!fs.existsSync(skillDir)) {
92
+ throw new SkillNotFoundError(skillName);
93
+ }
94
+ // Validate skill directory is within root using safe boundary check
95
+ const resolvedSkillDir = fs.realpathSync(skillDir);
96
+ if (!this.isWithinRoot(resolvedSkillDir, this.resolvedSkillsDir)) {
97
+ throw new SkillSecurityError('Skill directory escape detected', skillName);
98
+ }
99
+ // Load SKILL.md
100
+ const skillMdPath = path.join(resolvedSkillDir, 'SKILL.md');
101
+ if (!fs.existsSync(skillMdPath)) {
102
+ throw new SkillNotFoundError(`${skillName}/SKILL.md`);
103
+ }
104
+ const content = await this.readFileSecure(skillMdPath, resolvedSkillDir);
105
+ // Parse and load references
106
+ const references = await this.loadReferences(content, resolvedSkillDir, 0);
107
+ // Estimate token count
108
+ const totalContent = content + references.map(r => r.content).join('\n');
109
+ const tokenCount = this.estimateTokens(totalContent);
110
+ return {
111
+ name: skillName,
112
+ content,
113
+ references,
114
+ tokenCount,
115
+ };
116
+ }
117
+ /**
118
+ * Load all skills from the skills directory.
119
+ */
120
+ async loadAll() {
121
+ const entries = fs.readdirSync(this.resolvedSkillsDir, { withFileTypes: true });
122
+ const skills = [];
123
+ // Sort for deterministic order
124
+ const dirs = entries
125
+ .filter(e => e.isDirectory())
126
+ .map(e => e.name)
127
+ .sort();
128
+ for (const dir of dirs) {
129
+ try {
130
+ const skill = await this.load(dir);
131
+ skills.push(skill);
132
+ }
133
+ catch (e) {
134
+ // Skip invalid skills
135
+ if (e instanceof SkillNotFoundError) {
136
+ continue;
137
+ }
138
+ throw e;
139
+ }
140
+ }
141
+ return skills;
142
+ }
143
+ /**
144
+ * Read a file with security validation.
145
+ */
146
+ async readFileSecure(filePath, rootDir) {
147
+ // Resolve real path
148
+ let resolvedPath;
149
+ try {
150
+ resolvedPath = fs.realpathSync(filePath);
151
+ }
152
+ catch {
153
+ throw new SkillSecurityError(`File not found: ${filePath}`, filePath);
154
+ }
155
+ // Root check using safe boundary
156
+ if (!this.isWithinRoot(resolvedPath, rootDir)) {
157
+ throw new SkillSecurityError('Path escape detected', filePath);
158
+ }
159
+ // Extension check
160
+ const ext = path.extname(resolvedPath).toLowerCase();
161
+ if (!this.config.allowedExtensions.includes(ext)) {
162
+ throw new SkillSecurityError(`Extension not allowed: ${ext}`, filePath);
163
+ }
164
+ // Size check
165
+ const stat = fs.statSync(resolvedPath);
166
+ if (stat.size > this.config.maxFileSize) {
167
+ throw new SkillSecurityError(`File exceeds size limit: ${stat.size} > ${this.config.maxFileSize}`, filePath);
168
+ }
169
+ return fs.readFileSync(resolvedPath, 'utf-8');
170
+ }
171
+ /**
172
+ * Parse and load references from content.
173
+ * Only relative paths are allowed.
174
+ */
175
+ async loadReferences(content, rootDir, depth) {
176
+ if (depth >= this.config.maxReferenceDepth) {
177
+ return [];
178
+ }
179
+ const references = [];
180
+ // Match markdown-style references: [text](path)
181
+ const refPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
182
+ let match;
183
+ while ((match = refPattern.exec(content)) !== null) {
184
+ const refPath = match[2];
185
+ // Skip URLs
186
+ if (refPath.startsWith('http://') || refPath.startsWith('https://')) {
187
+ continue;
188
+ }
189
+ // Only allow relative paths
190
+ if (path.isAbsolute(refPath)) {
191
+ throw new SkillSecurityError('Absolute paths not allowed in references', refPath);
192
+ }
193
+ // Skip if not an allowed extension
194
+ const ext = path.extname(refPath).toLowerCase();
195
+ if (!this.config.allowedExtensions.includes(ext)) {
196
+ continue;
197
+ }
198
+ const fullPath = path.join(rootDir, refPath);
199
+ try {
200
+ const refContent = await this.readFileSecure(fullPath, rootDir);
201
+ references.push({
202
+ path: refPath,
203
+ content: refContent,
204
+ });
205
+ }
206
+ catch {
207
+ // Skip invalid references
208
+ continue;
209
+ }
210
+ }
211
+ return references;
212
+ }
213
+ /**
214
+ * Estimate token count using unified token-estimator.
215
+ */
216
+ estimateTokens(text) {
217
+ return (0, token_estimator_1.defaultContentEstimator)(text);
218
+ }
219
+ /**
220
+ * Safe boundary check for path containment.
221
+ * Uses path.relative to avoid prefix bypass attacks.
222
+ */
223
+ isWithinRoot(targetPath, rootDir) {
224
+ // Ensure both paths are normalized and absolute
225
+ const normalizedRoot = path.resolve(rootDir) + path.sep;
226
+ const normalizedTarget = path.resolve(targetPath);
227
+ // Check if target starts with root + separator
228
+ // This prevents /root-malicious from matching /root
229
+ if (normalizedTarget === path.resolve(rootDir)) {
230
+ return true;
231
+ }
232
+ // Use startsWith with separator boundary
233
+ return normalizedTarget.startsWith(normalizedRoot);
234
+ }
235
+ }
236
+ exports.SkillLoader = SkillLoader;
237
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/modules/skills/loader.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAyB;AACzB,2CAA6B;AAE7B,mCAA+C;AAC/C,+DAAoE;AAEpE,8CAA8C;AAC9C,MAAa,kBAAmB,SAAQ,KAAK;IACzC,YAAY,OAAe,EAAkB,IAAa;QACtD,KAAK,CAAC,OAAO,CAAC,CAAC;QAD0B,SAAI,GAAJ,IAAI,CAAS;QAEtD,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACrC,CAAC;CACJ;AALD,gDAKC;AAED,4BAA4B;AAC5B,MAAa,kBAAmB,SAAQ,KAAK;IACzC,YAAY,SAAiB;QACzB,KAAK,CAAC,oBAAoB,SAAS,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACrC,CAAC;CACJ;AALD,gDAKC;AAED;;GAEG;AACH,MAAa,WAAW;IAIpB,YAAY,MAAyB;QACjC,IAAI,CAAC,MAAM,GAAG;YACV,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,IAAI,CAAC,GAAG,4BAAoB,CAAC,iBAAiB,CAAC;YAC1F,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,4BAAoB,CAAC,WAAW;YACnE,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,IAAI,4BAAoB,CAAC,iBAAiB;SACxF,CAAC;QAEF,wCAAwC;QACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,kBAAkB,CAAC,+BAA+B,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC/D,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI,CAAC,SAAiB;QACxB,2CAA2C;QAC3C,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAClF,MAAM,IAAI,kBAAkB,CAAC,iDAAiD,EAAE,SAAS,CAAC,CAAC;QAC/F,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC;QAE9D,+BAA+B;QAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC5C,CAAC;QAED,oEAAoE;QACpE,MAAM,gBAAgB,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,gBAAgB,EAAE,IAAI,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,kBAAkB,CAAC,iCAAiC,EAAE,SAAS,CAAC,CAAC;QAC/E,CAAC;QAED,gBAAgB;QAChB,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;QAC5D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,kBAAkB,CAAC,GAAG,SAAS,WAAW,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;QAEzE,4BAA4B;QAC5B,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;QAE3E,uBAAuB;QACvB,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;QAErD,OAAO;YACH,IAAI,EAAE,SAAS;YACf,OAAO;YACP,UAAU;YACV,UAAU;SACb,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACT,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,MAAM,MAAM,GAAY,EAAE,CAAC;QAE3B,+BAA+B;QAC/B,MAAM,IAAI,GAAG,OAAO;aACf,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;aAC5B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAChB,IAAI,EAAE,CAAC;QAEZ,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACnC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,sBAAsB;gBACtB,IAAI,CAAC,YAAY,kBAAkB,EAAE,CAAC;oBAClC,SAAS;gBACb,CAAC;gBACD,MAAM,CAAC,CAAC;YACZ,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAClB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,cAAc,CAAC,QAAgB,EAAE,OAAe;QAC1D,oBAAoB;QACpB,IAAI,YAAoB,CAAC;QACzB,IAAI,CAAC;YACD,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC1E,CAAC;QAED,iCAAiC;QACjC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC;YAC5C,MAAM,IAAI,kBAAkB,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;QACnE,CAAC;QAED,kBAAkB;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACrD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,kBAAkB,CAAC,0BAA0B,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC5E,CAAC;QAED,aAAa;QACb,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACtC,MAAM,IAAI,kBAAkB,CACxB,4BAA4B,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,EACpE,QAAQ,CACX,CAAC;QACN,CAAC;QAED,OAAO,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,cAAc,CACxB,OAAe,EACf,OAAe,EACf,KAAa;QAEb,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YACzC,OAAO,EAAE,CAAC;QACd,CAAC;QAED,MAAM,UAAU,GAAqB,EAAE,CAAC;QAExC,gDAAgD;QAChD,MAAM,UAAU,GAAG,0BAA0B,CAAC;QAC9C,IAAI,KAAK,CAAC;QAEV,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACjD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAEzB,YAAY;YACZ,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClE,SAAS;YACb,CAAC;YAED,4BAA4B;YAC5B,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,kBAAkB,CAAC,0CAA0C,EAAE,OAAO,CAAC,CAAC;YACtF,CAAC;YAED,mCAAmC;YACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/C,SAAS;YACb,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAE7C,IAAI,CAAC;gBACD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAChE,UAAU,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,OAAO;oBACb,OAAO,EAAE,UAAU;iBACtB,CAAC,CAAC;YACP,CAAC;YAAC,MAAM,CAAC;gBACL,0BAA0B;gBAC1B,SAAS;YACb,CAAC;QACL,CAAC;QAED,OAAO,UAAU,CAAC;IACtB,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,IAAY;QAC/B,OAAO,IAAA,yCAAuB,EAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,UAAkB,EAAE,OAAe;QACpD,gDAAgD;QAChD,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;QACxD,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAElD,+CAA+C;QAC/C,oDAAoD;QACpD,IAAI,gBAAgB,KAAK,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,yCAAyC;QACzC,OAAO,gBAAgB,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACvD,CAAC;CACJ;AAhND,kCAgNC"}
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Skill Loader with security-first file validation.
3
+ * - Only relative paths allowed
4
+ * - realpath + root check for each reference
5
+ * - Size limits enforced
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { DEFAULT_SKILL_CONFIG } from './types.mjs';
10
+ import { defaultContentEstimator } from '../../lib/token-estimator.mjs';
11
+ /** Security error for path/file violations */
12
+ export class SkillSecurityError extends Error {
13
+ constructor(message, path) {
14
+ super(message);
15
+ this.path = path;
16
+ this.name = 'SkillSecurityError';
17
+ }
18
+ }
19
+ /** Skill not found error */
20
+ export class SkillNotFoundError extends Error {
21
+ constructor(skillName) {
22
+ super(`Skill not found: ${skillName}`);
23
+ this.name = 'SkillNotFoundError';
24
+ }
25
+ }
26
+ /**
27
+ * Skill Loader loads and validates skill files.
28
+ */
29
+ export class SkillLoader {
30
+ constructor(config) {
31
+ this.config = {
32
+ skillsDir: config.skillsDir,
33
+ allowedExtensions: config.allowedExtensions ?? [...DEFAULT_SKILL_CONFIG.allowedExtensions],
34
+ maxFileSize: config.maxFileSize ?? DEFAULT_SKILL_CONFIG.maxFileSize,
35
+ maxReferenceDepth: config.maxReferenceDepth ?? DEFAULT_SKILL_CONFIG.maxReferenceDepth,
36
+ };
37
+ // Resolve and validate skills directory
38
+ if (!fs.existsSync(config.skillsDir)) {
39
+ throw new SkillSecurityError(`Skills directory not found: ${config.skillsDir}`);
40
+ }
41
+ this.resolvedSkillsDir = fs.realpathSync(config.skillsDir);
42
+ }
43
+ /**
44
+ * Load a skill by name.
45
+ */
46
+ async load(skillName) {
47
+ // Validate skill name (no path separators)
48
+ if (skillName.includes('/') || skillName.includes('\\') || skillName.includes('..')) {
49
+ throw new SkillSecurityError('Invalid skill name: path separators not allowed', skillName);
50
+ }
51
+ const skillDir = path.join(this.resolvedSkillsDir, skillName);
52
+ // Check skill directory exists
53
+ if (!fs.existsSync(skillDir)) {
54
+ throw new SkillNotFoundError(skillName);
55
+ }
56
+ // Validate skill directory is within root using safe boundary check
57
+ const resolvedSkillDir = fs.realpathSync(skillDir);
58
+ if (!this.isWithinRoot(resolvedSkillDir, this.resolvedSkillsDir)) {
59
+ throw new SkillSecurityError('Skill directory escape detected', skillName);
60
+ }
61
+ // Load SKILL.md
62
+ const skillMdPath = path.join(resolvedSkillDir, 'SKILL.md');
63
+ if (!fs.existsSync(skillMdPath)) {
64
+ throw new SkillNotFoundError(`${skillName}/SKILL.md`);
65
+ }
66
+ const content = await this.readFileSecure(skillMdPath, resolvedSkillDir);
67
+ // Parse and load references
68
+ const references = await this.loadReferences(content, resolvedSkillDir, 0);
69
+ // Estimate token count
70
+ const totalContent = content + references.map(r => r.content).join('\n');
71
+ const tokenCount = this.estimateTokens(totalContent);
72
+ return {
73
+ name: skillName,
74
+ content,
75
+ references,
76
+ tokenCount,
77
+ };
78
+ }
79
+ /**
80
+ * Load all skills from the skills directory.
81
+ */
82
+ async loadAll() {
83
+ const entries = fs.readdirSync(this.resolvedSkillsDir, { withFileTypes: true });
84
+ const skills = [];
85
+ // Sort for deterministic order
86
+ const dirs = entries
87
+ .filter(e => e.isDirectory())
88
+ .map(e => e.name)
89
+ .sort();
90
+ for (const dir of dirs) {
91
+ try {
92
+ const skill = await this.load(dir);
93
+ skills.push(skill);
94
+ }
95
+ catch (e) {
96
+ // Skip invalid skills
97
+ if (e instanceof SkillNotFoundError) {
98
+ continue;
99
+ }
100
+ throw e;
101
+ }
102
+ }
103
+ return skills;
104
+ }
105
+ /**
106
+ * Read a file with security validation.
107
+ */
108
+ async readFileSecure(filePath, rootDir) {
109
+ // Resolve real path
110
+ let resolvedPath;
111
+ try {
112
+ resolvedPath = fs.realpathSync(filePath);
113
+ }
114
+ catch {
115
+ throw new SkillSecurityError(`File not found: ${filePath}`, filePath);
116
+ }
117
+ // Root check using safe boundary
118
+ if (!this.isWithinRoot(resolvedPath, rootDir)) {
119
+ throw new SkillSecurityError('Path escape detected', filePath);
120
+ }
121
+ // Extension check
122
+ const ext = path.extname(resolvedPath).toLowerCase();
123
+ if (!this.config.allowedExtensions.includes(ext)) {
124
+ throw new SkillSecurityError(`Extension not allowed: ${ext}`, filePath);
125
+ }
126
+ // Size check
127
+ const stat = fs.statSync(resolvedPath);
128
+ if (stat.size > this.config.maxFileSize) {
129
+ throw new SkillSecurityError(`File exceeds size limit: ${stat.size} > ${this.config.maxFileSize}`, filePath);
130
+ }
131
+ return fs.readFileSync(resolvedPath, 'utf-8');
132
+ }
133
+ /**
134
+ * Parse and load references from content.
135
+ * Only relative paths are allowed.
136
+ */
137
+ async loadReferences(content, rootDir, depth) {
138
+ if (depth >= this.config.maxReferenceDepth) {
139
+ return [];
140
+ }
141
+ const references = [];
142
+ // Match markdown-style references: [text](path)
143
+ const refPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
144
+ let match;
145
+ while ((match = refPattern.exec(content)) !== null) {
146
+ const refPath = match[2];
147
+ // Skip URLs
148
+ if (refPath.startsWith('http://') || refPath.startsWith('https://')) {
149
+ continue;
150
+ }
151
+ // Only allow relative paths
152
+ if (path.isAbsolute(refPath)) {
153
+ throw new SkillSecurityError('Absolute paths not allowed in references', refPath);
154
+ }
155
+ // Skip if not an allowed extension
156
+ const ext = path.extname(refPath).toLowerCase();
157
+ if (!this.config.allowedExtensions.includes(ext)) {
158
+ continue;
159
+ }
160
+ const fullPath = path.join(rootDir, refPath);
161
+ try {
162
+ const refContent = await this.readFileSecure(fullPath, rootDir);
163
+ references.push({
164
+ path: refPath,
165
+ content: refContent,
166
+ });
167
+ }
168
+ catch {
169
+ // Skip invalid references
170
+ continue;
171
+ }
172
+ }
173
+ return references;
174
+ }
175
+ /**
176
+ * Estimate token count using unified token-estimator.
177
+ */
178
+ estimateTokens(text) {
179
+ return defaultContentEstimator(text);
180
+ }
181
+ /**
182
+ * Safe boundary check for path containment.
183
+ * Uses path.relative to avoid prefix bypass attacks.
184
+ */
185
+ isWithinRoot(targetPath, rootDir) {
186
+ // Ensure both paths are normalized and absolute
187
+ const normalizedRoot = path.resolve(rootDir) + path.sep;
188
+ const normalizedTarget = path.resolve(targetPath);
189
+ // Check if target starts with root + separator
190
+ // This prevents /root-malicious from matching /root
191
+ if (normalizedTarget === path.resolve(rootDir)) {
192
+ return true;
193
+ }
194
+ // Use startsWith with separator boundary
195
+ return normalizedTarget.startsWith(normalizedRoot);
196
+ }
197
+ }
198
+ //# sourceMappingURL=loader.js.map