@easynet/agent-tool 1.0.53 → 1.0.55

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.
@@ -1,8 +1,10 @@
1
1
  import { ToolRegistry, createTaggedError, withRetry } from './chunk-FCYBA7PR.js';
2
- import { normalizeToolName } from './chunk-ODEHUAR4.js';
2
+ import { normalizeToolName, DEFAULT_OUTPUT_SCHEMA } from './chunk-ODEHUAR4.js';
3
3
  import { enrichSpecWithCanonicalSchema } from './chunk-NTWOVFEY.js';
4
+ import * as fs from 'fs';
4
5
  import { readFileSync, existsSync, statSync } from 'fs';
5
- import { resolve, dirname, join, normalize, basename, isAbsolute } from 'path';
6
+ import * as path from 'path';
7
+ import { resolve, dirname, join, normalize, basename, isAbsolute, extname, relative } from 'path';
6
8
  import { parseYamlContent, resolveConfigPath } from '@easynet/agent-common';
7
9
  import Ajv from 'ajv';
8
10
  import addFormats from 'ajv-formats';
@@ -10,9 +12,11 @@ import { bulkhead, circuitBreaker, handleAll, ConsecutiveBreaker } from 'cockati
10
12
  import { EventEmitter } from 'eventemitter3';
11
13
  import { v4 } from 'uuid';
12
14
  import pTimeout from 'p-timeout';
13
- import { realpath, access } from 'fs/promises';
15
+ import { realpath, access, readdir, readFile, stat } from 'fs/promises';
16
+ import * as ts2 from 'typescript';
14
17
  import { createRequire } from 'module';
15
18
  import { pathToFileURL } from 'url';
19
+ import yaml from 'js-yaml';
16
20
  import { resolveLatestVersionFromRegistry, ensurePackageInCache, getPackageEntryPath, importFromCache } from '@easynet/agent-common/npm';
17
21
  import { createServer } from 'http';
18
22
 
@@ -193,13 +197,13 @@ function expandToolDescriptorsToRegistryNames(descriptors, registryNames) {
193
197
  out.push(s);
194
198
  continue;
195
199
  }
196
- const path = parseToolPath(s);
197
- if (path) {
198
- const packagePrefix = path.protocol === "npm" ? npmDescriptorToPackagePrefix(s) : path.protocol === "file" ? fileDescriptorToPackagePrefix(s) : "";
200
+ const path7 = parseToolPath(s);
201
+ if (path7) {
202
+ const packagePrefix = path7.protocol === "npm" ? npmDescriptorToPackagePrefix(s) : path7.protocol === "file" ? fileDescriptorToPackagePrefix(s) : "";
199
203
  const prefixWithDot = packagePrefix ? packagePrefix + "." : "";
200
204
  if (prefixWithDot) {
201
- if (path.toolName) {
202
- const suffix = "." + path.toolName;
205
+ if (path7.toolName) {
206
+ const suffix = "." + path7.toolName;
203
207
  for (const r of registryNames) {
204
208
  if (r.startsWith(prefixWithDot) && r.endsWith(suffix) && !seen.has(r)) {
205
209
  seen.add(r);
@@ -228,9 +232,9 @@ function resolveToolDescriptor(descriptor) {
228
232
  return s;
229
233
  }
230
234
  function fileDescriptorToPackagePrefix(descriptor) {
231
- const path = parseToolPath(descriptor.trim());
232
- if (!path || path.protocol !== "file") return "";
233
- const pathPart = `${path.scope}/${path.packageWithVersion}`;
235
+ const path7 = parseToolPath(descriptor.trim());
236
+ if (!path7 || path7.protocol !== "file") return "";
237
+ const pathPart = `${path7.scope}/${path7.packageWithVersion}`;
234
238
  const normalized = normalizeToolName(pathPart);
235
239
  if (!normalized) return "";
236
240
  return "file." + normalized;
@@ -530,17 +534,17 @@ var PolicyEngine = class {
530
534
  */
531
535
  extractStringValues(args, keyPatterns) {
532
536
  const results = [];
533
- const walk = (obj) => {
537
+ const walk2 = (obj) => {
534
538
  for (const [key, val] of Object.entries(obj)) {
535
539
  const lowerKey = key.toLowerCase();
536
540
  if (typeof val === "string" && keyPatterns.some((p) => lowerKey.includes(p))) {
537
541
  results.push(val);
538
542
  } else if (val && typeof val === "object" && !Array.isArray(val)) {
539
- walk(val);
543
+ walk2(val);
540
544
  }
541
545
  }
542
546
  };
543
- walk(args);
547
+ walk2(args);
544
548
  return results;
545
549
  }
546
550
  };
@@ -725,30 +729,30 @@ function summarizeValue(value, maxLen) {
725
729
  }
726
730
  function extractUrls(obj) {
727
731
  const urls = [];
728
- const walk = (val) => {
732
+ const walk2 = (val) => {
729
733
  if (typeof val === "string" && /^https?:\/\//i.test(val)) {
730
734
  urls.push(val);
731
735
  } else if (val && typeof val === "object") {
732
736
  for (const v of Object.values(val)) {
733
- walk(v);
737
+ walk2(v);
734
738
  }
735
739
  }
736
740
  };
737
- walk(obj);
741
+ walk2(obj);
738
742
  return urls.slice(0, 10);
739
743
  }
740
744
  function extractFilePaths(obj) {
741
745
  const paths = [];
742
- const walk = (val) => {
746
+ const walk2 = (val) => {
743
747
  if (typeof val === "string" && (val.startsWith("/") || val.startsWith("./")) && val.includes(".")) {
744
748
  paths.push(val);
745
749
  } else if (val && typeof val === "object") {
746
750
  for (const v of Object.values(val)) {
747
- walk(v);
751
+ walk2(v);
748
752
  }
749
753
  }
750
754
  };
751
- walk(obj);
755
+ walk2(obj);
752
756
  return paths.slice(0, 10);
753
757
  }
754
758
  var EventLog = class {
@@ -1751,11 +1755,1527 @@ async function resolveSandboxedPath2(inputPath, sandboxRoot) {
1751
1755
  }
1752
1756
  return real;
1753
1757
  }
1754
- function isWithinRoot(path, root) {
1755
- const normalizedPath = normalize(path);
1758
+ function isWithinRoot(path7, root) {
1759
+ const normalizedPath = normalize(path7);
1756
1760
  const normalizedRoot = normalize(root);
1757
1761
  return normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + "/");
1758
1762
  }
1763
+ async function findDirsContainingFile(rootPath, fileName) {
1764
+ const found = [];
1765
+ await collectDirsWithFile(rootPath, fileName, found);
1766
+ return found;
1767
+ }
1768
+ async function collectDirsWithFile(dir, fileName, acc) {
1769
+ let entries;
1770
+ try {
1771
+ const e = await readdir(dir, { withFileTypes: true });
1772
+ entries = e.map((x) => ({
1773
+ name: x.name,
1774
+ isDirectory: x.isDirectory(),
1775
+ isFile: x.isFile()
1776
+ }));
1777
+ } catch {
1778
+ return;
1779
+ }
1780
+ if (entries.some((x) => x.isFile && x.name === fileName)) acc.push(dir);
1781
+ for (const entry of entries) {
1782
+ if (!entry.isDirectory || entry.name === "node_modules" || entry.name.startsWith(".")) continue;
1783
+ await collectDirsWithFile(join(dir, entry.name), fileName, acc);
1784
+ }
1785
+ }
1786
+ function pathToToolName(sourcePath, programName) {
1787
+ const normalized = sourcePath.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|json)$/i, "");
1788
+ const segments = normalized.split("/").filter(Boolean);
1789
+ return segments.length === 0 ? programName : `${segments.join(".")}.${programName}`;
1790
+ }
1791
+ function buildOutputSchemaFromReturnType(node, typeChecker, onWarn) {
1792
+ const sig = typeChecker.getSignatureFromDeclaration(node);
1793
+ if (!sig) {
1794
+ onWarn?.("Could not get signature for return type, using object");
1795
+ return { type: "object", additionalProperties: true };
1796
+ }
1797
+ let returnType = typeChecker.getReturnTypeOfSignature(sig);
1798
+ if (returnType.getSymbol?.()?.getName() === "Promise") {
1799
+ const typeArgs = returnType.typeArguments;
1800
+ if (typeArgs?.[0]) returnType = typeArgs[0];
1801
+ }
1802
+ const schema = typeToJsonSchema(returnType, typeChecker, onWarn);
1803
+ const hasProps = typeof schema === "object" && schema.type === "object" && Object.keys(schema.properties ?? {}).length > 0;
1804
+ return hasProps ? schema : { type: "object", additionalProperties: true };
1805
+ }
1806
+ function buildInputSchemaFromParams(node, typeChecker, onWarn) {
1807
+ const properties = {};
1808
+ const required = [];
1809
+ if (!node.parameters.length) {
1810
+ return { schema: { type: "object", properties: {} }, required: [] };
1811
+ }
1812
+ for (const param of node.parameters) {
1813
+ const name = param.name.getText();
1814
+ if (name.startsWith("_") && name.length <= 2) continue;
1815
+ const sym = param.symbol;
1816
+ const paramType = sym ? typeChecker.getTypeOfSymbolAtLocation(sym, param) : typeChecker.getTypeAtLocation(param);
1817
+ const isOptional = !!param.questionToken || param.initializer !== void 0;
1818
+ const propSchema = typeToJsonSchema(paramType, typeChecker, onWarn);
1819
+ properties[name] = propSchema;
1820
+ if (!isOptional) required.push(name);
1821
+ }
1822
+ const paramNames = Object.keys(properties);
1823
+ if (paramNames.length === 1) {
1824
+ const soleName = paramNames[0];
1825
+ const inner = properties[soleName];
1826
+ if (inner && typeof inner === "object" && inner.type === "object" && inner.properties && typeof inner.properties === "object") {
1827
+ return {
1828
+ schema: {
1829
+ type: "object",
1830
+ properties: inner.properties,
1831
+ ...Array.isArray(inner.required) && inner.required.length > 0 ? { required: inner.required } : {},
1832
+ ...inner.additionalProperties !== void 0 ? { additionalProperties: inner.additionalProperties } : {}
1833
+ },
1834
+ required: inner.required ?? []
1835
+ };
1836
+ }
1837
+ }
1838
+ return {
1839
+ schema: {
1840
+ type: "object",
1841
+ properties,
1842
+ ...required.length > 0 ? { required } : {}
1843
+ },
1844
+ required
1845
+ };
1846
+ }
1847
+ function typeToJsonSchema(type, typeChecker, onWarn) {
1848
+ const flags = type.flags;
1849
+ if (flags & ts2.TypeFlags.String) return { type: "string" };
1850
+ if (flags & ts2.TypeFlags.Number) return { type: "number" };
1851
+ if (flags & ts2.TypeFlags.Boolean) return { type: "boolean" };
1852
+ if (flags & ts2.TypeFlags.BooleanLiteral) return { type: "boolean" };
1853
+ if (flags & ts2.TypeFlags.Null) return { type: "null" };
1854
+ if (flags & ts2.TypeFlags.Undefined || flags & ts2.TypeFlags.Void) return {};
1855
+ if (flags & ts2.TypeFlags.Any || flags & ts2.TypeFlags.Unknown) {
1856
+ onWarn?.(`Unsupported type: any/unknown, using empty schema`);
1857
+ return {};
1858
+ }
1859
+ if (type.isUnion?.()) {
1860
+ const union = type;
1861
+ const types = union.types;
1862
+ const withoutUndef = types.filter(
1863
+ (t) => !(t.flags & ts2.TypeFlags.Undefined) && !(t.flags & ts2.TypeFlags.Void)
1864
+ );
1865
+ if (withoutUndef.length === 1) return typeToJsonSchema(withoutUndef[0], typeChecker, onWarn);
1866
+ if (withoutUndef.length === 0) return {};
1867
+ const stringEnumValues = [];
1868
+ let allStringLiterals = true;
1869
+ for (const t of withoutUndef) {
1870
+ if (t.flags & ts2.TypeFlags.StringLiteral) {
1871
+ const lit = t;
1872
+ if (typeof lit.value === "string") stringEnumValues.push(lit.value);
1873
+ } else {
1874
+ allStringLiterals = false;
1875
+ break;
1876
+ }
1877
+ }
1878
+ if (allStringLiterals && stringEnumValues.length > 0) {
1879
+ return { type: "string", enum: [...new Set(stringEnumValues)] };
1880
+ }
1881
+ let allBooleanLiterals = true;
1882
+ for (const t of withoutUndef) {
1883
+ if (!(t.flags & ts2.TypeFlags.BooleanLiteral)) {
1884
+ allBooleanLiterals = false;
1885
+ break;
1886
+ }
1887
+ }
1888
+ if (allBooleanLiterals) return { type: "boolean" };
1889
+ }
1890
+ if (flags & ts2.TypeFlags.StringLiteral) {
1891
+ const lit = type;
1892
+ if (typeof lit.value === "string") {
1893
+ return { type: "string", enum: [lit.value] };
1894
+ }
1895
+ return { type: "string" };
1896
+ }
1897
+ if (typeChecker.isArrayType(type)) {
1898
+ const typeRef = type;
1899
+ const typeArgs = typeRef.typeArguments;
1900
+ const itemType = typeArgs?.[0];
1901
+ const items = itemType ? typeToJsonSchema(itemType, typeChecker, onWarn) : {};
1902
+ return { type: "array", items: Object.keys(items).length ? items : {} };
1903
+ }
1904
+ const str = typeChecker.typeToString(type);
1905
+ if (str === "string") return { type: "string" };
1906
+ if (str === "number") return { type: "number" };
1907
+ if (str === "boolean") return { type: "boolean" };
1908
+ if (str.endsWith("[]")) {
1909
+ const inner = str.slice(0, -2).trim();
1910
+ const itemType = inner === "string" ? { type: "string" } : inner === "number" ? { type: "number" } : {};
1911
+ return { type: "array", items: itemType };
1912
+ }
1913
+ if (type.getProperties && type.getProperties().length >= 0) {
1914
+ const props = type.getProperties();
1915
+ const properties = {};
1916
+ const required = [];
1917
+ for (const p of props) {
1918
+ const decl = p.valueDeclaration;
1919
+ const propType = decl ? typeChecker.getTypeAtLocation(decl) : typeChecker.getTypeOfSymbolAtLocation(p, p.valueDeclaration);
1920
+ const optional = decl && ts2.isPropertySignature(decl) ? !!decl.questionToken : false;
1921
+ properties[p.name] = typeToJsonSchema(propType, typeChecker, onWarn);
1922
+ if (!optional) required.push(p.name);
1923
+ }
1924
+ return { type: "object", properties, ...required.length ? { required } : {} };
1925
+ }
1926
+ onWarn?.(`Unsupported type: ${str}, using object`);
1927
+ return { type: "object" };
1928
+ }
1929
+
1930
+ // src/tools/function/types.ts
1931
+ var FUNCTION_KIND = "function";
1932
+
1933
+ // src/tools/skill/types.ts
1934
+ var SKILL_KIND = "skill";
1935
+ var SKILL_DIR_NAME = "skill";
1936
+
1937
+ // src/tools/n8n/types.ts
1938
+ var N8N_KIND = "n8n";
1939
+
1940
+ // src/tools/mcp/mcpSpecToToolSpec.ts
1941
+ var DEFAULT_OUTPUT = { type: "object", additionalProperties: true };
1942
+ function mcpSpecToToolSpec(spec, projectPath) {
1943
+ const base = {
1944
+ name: spec.name,
1945
+ version: "1.0.0",
1946
+ kind: spec.kind,
1947
+ description: spec.description,
1948
+ inputSchema: spec.inputSchema ?? DEFAULT_OUTPUT,
1949
+ outputSchema: "outputSchema" in spec && spec.outputSchema ? spec.outputSchema : DEFAULT_OUTPUT_SCHEMA,
1950
+ capabilities: [],
1951
+ _meta: spec._meta,
1952
+ ...spec.kind === N8N_KIND && "webhookUrl" in spec && spec.webhookUrl ? { endpoint: spec.webhookUrl } : {}
1953
+ };
1954
+ if (spec.kind === FUNCTION_KIND && "sourcePath" in spec && "exportName" in spec) {
1955
+ base._meta = {
1956
+ ...base._meta,
1957
+ sourcePath: spec.sourcePath,
1958
+ exportName: spec.exportName,
1959
+ ...projectPath && { projectPath }
1960
+ };
1961
+ }
1962
+ if (spec.kind === SKILL_KIND && "sourcePath" in spec && projectPath) {
1963
+ base._meta = { ...base._meta, sourcePath: spec.sourcePath, projectPath };
1964
+ }
1965
+ if (spec.kind === N8N_KIND && "sourcePath" in spec && projectPath) {
1966
+ base._meta = { ...base._meta, sourcePath: spec.sourcePath, projectPath };
1967
+ }
1968
+ return base;
1969
+ }
1970
+
1971
+ // src/tools/function/scanner.ts
1972
+ var TOOL_TAG = "@tool";
1973
+ var EFFECT_VALUES = ["none", "local_write", "external_write", "destructive"];
1974
+ function scanForTools(options) {
1975
+ const projectPath = path.resolve(options.projectPath);
1976
+ const tsconfigPath = options.tsconfigPath ?? path.join(projectPath, "tsconfig.json");
1977
+ const include = options.include ?? ["**/*.ts"];
1978
+ const errors = [];
1979
+ const warnings = [];
1980
+ let config;
1981
+ let configPathResolved = path.resolve(projectPath, tsconfigPath);
1982
+ if (!fs.existsSync(configPathResolved)) {
1983
+ configPathResolved = path.join(projectPath, "tsconfig.json");
1984
+ }
1985
+ if (fs.existsSync(configPathResolved)) {
1986
+ const configFile = ts2.readConfigFile(configPathResolved, ts2.sys.readFile);
1987
+ if (configFile.error) {
1988
+ errors.push({ file: configPathResolved, message: String(configFile.error.messageText) });
1989
+ return { specs: [], errors, warnings };
1990
+ }
1991
+ const parsed = ts2.parseJsonConfigFileContent(
1992
+ configFile.config,
1993
+ ts2.sys,
1994
+ path.dirname(configPathResolved)
1995
+ );
1996
+ if (parsed.errors.length) {
1997
+ for (const e of parsed.errors) {
1998
+ errors.push({ file: e.file?.fileName ?? "tsconfig", message: String(e.messageText) });
1999
+ }
2000
+ return { specs: [], errors, warnings };
2001
+ }
2002
+ config = parsed;
2003
+ } else {
2004
+ config = {
2005
+ options: {
2006
+ target: ts2.ScriptTarget.ES2022,
2007
+ module: ts2.ModuleKind.ESNext,
2008
+ moduleResolution: ts2.ModuleResolutionKind.NodeNext,
2009
+ strict: true,
2010
+ skipLibCheck: true,
2011
+ noEmit: true
2012
+ },
2013
+ fileNames: resolveGlob(projectPath, include),
2014
+ errors: []
2015
+ };
2016
+ }
2017
+ const program = ts2.createProgram(config.fileNames, config.options);
2018
+ const typeChecker = program.getTypeChecker();
2019
+ const specs = [];
2020
+ for (const sourceFile of program.getSourceFiles()) {
2021
+ const fileName = sourceFile.fileName;
2022
+ if (fileName.includes("node_modules") || fileName.endsWith(".d.ts")) continue;
2023
+ if (!config.fileNames.some((f) => path.resolve(f) === path.resolve(fileName))) continue;
2024
+ ts2.forEachChild(sourceFile, (node) => {
2025
+ const decl = getExportedFunctionDeclaration(node);
2026
+ if (!decl) return;
2027
+ const func = decl.func;
2028
+ const name = decl.name;
2029
+ if (!name) return;
2030
+ const host = getJSDocHost(func);
2031
+ if (!hasToolTag(host)) return;
2032
+ const jsDoc = getJSDocComments(host);
2033
+ const description = getDescription(jsDoc);
2034
+ if (!description) {
2035
+ warnings.push({ file: fileName, message: `Tool ${name}: missing description, using humanized name` });
2036
+ }
2037
+ const sideEffect = getEffect(host);
2038
+ const onWarn = (msg) => warnings.push({ file: fileName, message: `${name}: ${msg}` });
2039
+ const { schema } = buildInputSchemaFromParams(func, typeChecker, onWarn);
2040
+ const inputSchema = Object.keys(schema.properties ?? {}).length > 0 ? schema : { type: "object", properties: {} };
2041
+ const outputSchema = buildOutputSchemaFromReturnType(func, typeChecker, onWarn);
2042
+ const sourcePath = path.relative(projectPath, fileName) || path.basename(fileName);
2043
+ const toolName = pathToToolName(sourcePath, name);
2044
+ specs.push({
2045
+ kind: FUNCTION_KIND,
2046
+ name: toolName,
2047
+ description: description || humanize(name),
2048
+ inputSchema,
2049
+ outputSchema,
2050
+ _meta: { hitl: { sideEffect } },
2051
+ sourcePath,
2052
+ exportName: name
2053
+ });
2054
+ });
2055
+ }
2056
+ return { specs, errors, warnings };
2057
+ }
2058
+ function resolveGlob(projectPath, patterns) {
2059
+ const result = [];
2060
+ const seen = /* @__PURE__ */ new Set();
2061
+ const add = (f) => {
2062
+ const abs = path.resolve(f);
2063
+ if (f.endsWith(".ts") && !f.endsWith(".d.ts") && !seen.has(abs)) {
2064
+ seen.add(abs);
2065
+ result.push(abs);
2066
+ }
2067
+ };
2068
+ for (const p of patterns) {
2069
+ const full = path.join(projectPath, p);
2070
+ if (full.includes("*")) {
2071
+ const baseDir = full.replace(/\*\*\/.*$/, "").replace(/\*.*$/, "").replace(/\/?$/, "") || ".";
2072
+ const dir = path.resolve(projectPath, baseDir);
2073
+ if (fs.existsSync(dir)) walk(dir, add);
2074
+ } else {
2075
+ const resolved = path.resolve(projectPath, full);
2076
+ if (fs.existsSync(resolved)) {
2077
+ if (fs.statSync(resolved).isFile()) add(resolved);
2078
+ else walk(resolved, add);
2079
+ }
2080
+ }
2081
+ }
2082
+ if (result.length > 0) return result;
2083
+ const srcDir = path.join(projectPath, "src");
2084
+ if (fs.existsSync(srcDir)) return walkCollect(srcDir);
2085
+ return [];
2086
+ }
2087
+ function walkCollect(dir) {
2088
+ const out = [];
2089
+ walk(dir, (fullPath) => {
2090
+ if (fullPath.endsWith(".ts") && !fullPath.endsWith(".d.ts")) out.push(path.resolve(fullPath));
2091
+ });
2092
+ return out;
2093
+ }
2094
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "generated", "dist"]);
2095
+ function walk(dir, visit) {
2096
+ try {
2097
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
2098
+ for (const e of entries) {
2099
+ const full = path.join(dir, e.name);
2100
+ if (e.isDirectory() && !SKIP_DIRS.has(e.name)) walk(full, visit);
2101
+ else if (e.isFile()) visit(full);
2102
+ }
2103
+ } catch {
2104
+ }
2105
+ }
2106
+ function getExportedFunctionDeclaration(node, _sourceFile) {
2107
+ if (ts2.isFunctionDeclaration(node) && node.name) {
2108
+ const exported = (ts2.getModifiers(node) ?? []).some((m) => m.kind === ts2.SyntaxKind.ExportKeyword);
2109
+ if (exported) return { func: node, name: node.name.getText() };
2110
+ return null;
2111
+ }
2112
+ if (ts2.isVariableStatement(node)) {
2113
+ const exported = (ts2.getModifiers(node) ?? []).some((m) => m.kind === ts2.SyntaxKind.ExportKeyword);
2114
+ if (!exported) return null;
2115
+ for (const decl of node.declarationList.declarations) {
2116
+ let init = decl.initializer;
2117
+ while (init && (ts2.isParenthesizedExpression(init) || ts2.isAsExpression(init)))
2118
+ init = init.expression;
2119
+ if (init && ts2.isArrowFunction(init)) {
2120
+ const name = decl.name.getText();
2121
+ return { func: init, name };
2122
+ }
2123
+ if (init && ts2.isFunctionExpression(init)) {
2124
+ const name = decl.name.getText();
2125
+ return { func: init, name };
2126
+ }
2127
+ }
2128
+ }
2129
+ return null;
2130
+ }
2131
+ function getJSDocHost(node) {
2132
+ const parent = node.parent;
2133
+ if (ts2.isVariableDeclaration(parent)) {
2134
+ const gp = parent.parent;
2135
+ if (ts2.isVariableDeclarationList(gp) && gp.parent && ts2.isVariableStatement(gp.parent)) return gp.parent;
2136
+ }
2137
+ return node;
2138
+ }
2139
+ function getJSDocComments(host) {
2140
+ const all = ts2.getJSDocCommentsAndTags(host);
2141
+ return all.filter((t) => ts2.isJSDoc(t));
2142
+ }
2143
+ function hasToolTag(host) {
2144
+ const tags = ts2.getJSDocTags(host);
2145
+ for (const tag of tags) {
2146
+ const name = tag.tagName?.getText() ?? "";
2147
+ if (name === "tool") return true;
2148
+ }
2149
+ const all = ts2.getJSDocCommentsAndTags(host);
2150
+ for (const t of all) {
2151
+ if (ts2.isJSDoc(t)) {
2152
+ const full = t.getFullText();
2153
+ if (full.includes(TOOL_TAG)) return true;
2154
+ }
2155
+ }
2156
+ return false;
2157
+ }
2158
+ function getDescription(jsDocs, fallbackName) {
2159
+ for (const doc of jsDocs) {
2160
+ const comment = doc.comment;
2161
+ if (typeof comment === "string") {
2162
+ const first = comment.split(/\n/)[0]?.trim() ?? "";
2163
+ if (first && !first.startsWith("@")) return first;
2164
+ }
2165
+ if (Array.isArray(comment)) {
2166
+ const first = comment[0];
2167
+ if (first && typeof first === "object" && "text" in first) {
2168
+ const t = first.text.trim();
2169
+ if (t && !t.startsWith("@")) return t;
2170
+ }
2171
+ }
2172
+ const full = doc.getFullText();
2173
+ const match = full.match(/\*\s*@tool\s+(.+?)(?=\n|$|\*\/)/s);
2174
+ if (match?.[1]) return match[1].trim();
2175
+ }
2176
+ return "";
2177
+ }
2178
+ function getEffect(host) {
2179
+ const tags = ts2.getJSDocTags(host);
2180
+ for (const tag of tags) {
2181
+ const name = tag.tagName?.getText() ?? "";
2182
+ if (name === "effect") {
2183
+ const comment = tag.comment;
2184
+ const v = (typeof comment === "string" ? comment : "").trim().toLowerCase();
2185
+ if (EFFECT_VALUES.includes(v)) return v;
2186
+ }
2187
+ }
2188
+ const all = ts2.getJSDocCommentsAndTags(host);
2189
+ for (const t of all) {
2190
+ if (ts2.isJSDoc(t)) {
2191
+ const full = t.getFullText();
2192
+ const match = full.match(/\*\s*@effect\s+(\w+)/);
2193
+ if (match && EFFECT_VALUES.includes(match[1])) return match[1];
2194
+ }
2195
+ }
2196
+ return "none";
2197
+ }
2198
+ function humanize(name) {
2199
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
2200
+ }
2201
+ function scan(projectPath, options = {}) {
2202
+ const root = path.resolve(projectPath);
2203
+ const result = scanForTools({
2204
+ projectPath: root,
2205
+ include: options.include ?? ["**/*.ts"],
2206
+ tsconfigPath: options.tsconfigPath
2207
+ });
2208
+ const specs = result.specs.map((s) => mcpSpecToToolSpec(s, root));
2209
+ return Promise.resolve({
2210
+ specs,
2211
+ errors: result.errors,
2212
+ warnings: result.warnings
2213
+ });
2214
+ }
2215
+ var DEFAULT_EXTENSIONS = [".js", ".mjs"];
2216
+ async function resolveEntryPoint(dirPath, baseName, extensions = DEFAULT_EXTENSIONS) {
2217
+ if (extensions.some((ext) => baseName.endsWith(ext))) {
2218
+ const fullPath = join(dirPath, baseName);
2219
+ await stat(fullPath);
2220
+ return fullPath;
2221
+ }
2222
+ for (const ext of extensions) {
2223
+ const fullPath = join(dirPath, `${baseName}${ext}`);
2224
+ try {
2225
+ await stat(fullPath);
2226
+ return fullPath;
2227
+ } catch {
2228
+ }
2229
+ }
2230
+ throw new Error(
2231
+ `Could not find entry point in ${dirPath}. Tried: ${extensions.map((e) => baseName + e).join(", ")}`
2232
+ );
2233
+ }
2234
+
2235
+ // src/tools/skill/SkillManifest.ts
2236
+ var SkillManifestError = class extends Error {
2237
+ constructor(path7, field, message) {
2238
+ super(`SKILL.md error in ${path7}: ${message}`);
2239
+ this.path = path7;
2240
+ this.field = field;
2241
+ this.name = "SkillManifestError";
2242
+ }
2243
+ };
2244
+ var NAME_PATTERN = /^[a-z0-9-]+$/;
2245
+ var NAME_MAX_LENGTH = 64;
2246
+ var DESCRIPTION_MAX_LENGTH = 1024;
2247
+ var COMPATIBILITY_MAX_LENGTH = 500;
2248
+ var RESERVED_WORDS = ["anthropic", "claude"];
2249
+ var XML_TAG_PATTERN = /<\/?[a-zA-Z][^>]*>/;
2250
+ function validateFrontmatter(fm, filePath) {
2251
+ if (!fm.name || typeof fm.name !== "string") {
2252
+ throw new SkillManifestError(filePath, "name", "name is required");
2253
+ }
2254
+ if (fm.name.length > NAME_MAX_LENGTH) {
2255
+ throw new SkillManifestError(
2256
+ filePath,
2257
+ "name",
2258
+ `name must be at most ${NAME_MAX_LENGTH} characters (got ${fm.name.length})`
2259
+ );
2260
+ }
2261
+ if (!NAME_PATTERN.test(fm.name)) {
2262
+ throw new SkillManifestError(
2263
+ filePath,
2264
+ "name",
2265
+ "name must contain only lowercase letters, numbers, and hyphens"
2266
+ );
2267
+ }
2268
+ if (fm.name.startsWith("-") || fm.name.endsWith("-")) {
2269
+ throw new SkillManifestError(
2270
+ filePath,
2271
+ "name",
2272
+ "name must not start or end with a hyphen"
2273
+ );
2274
+ }
2275
+ if (fm.name.includes("--")) {
2276
+ throw new SkillManifestError(
2277
+ filePath,
2278
+ "name",
2279
+ "name must not contain consecutive hyphens"
2280
+ );
2281
+ }
2282
+ if (XML_TAG_PATTERN.test(fm.name)) {
2283
+ throw new SkillManifestError(filePath, "name", "name cannot contain XML tags");
2284
+ }
2285
+ for (const reserved of RESERVED_WORDS) {
2286
+ if (fm.name.includes(reserved)) {
2287
+ throw new SkillManifestError(
2288
+ filePath,
2289
+ "name",
2290
+ `name cannot contain reserved word "${reserved}"`
2291
+ );
2292
+ }
2293
+ }
2294
+ if (!fm.description || typeof fm.description !== "string") {
2295
+ throw new SkillManifestError(
2296
+ filePath,
2297
+ "description",
2298
+ "description is required and must be non-empty"
2299
+ );
2300
+ }
2301
+ if (fm.description.length > DESCRIPTION_MAX_LENGTH) {
2302
+ throw new SkillManifestError(
2303
+ filePath,
2304
+ "description",
2305
+ `description must be at most ${DESCRIPTION_MAX_LENGTH} characters (got ${fm.description.length})`
2306
+ );
2307
+ }
2308
+ if (XML_TAG_PATTERN.test(fm.description)) {
2309
+ throw new SkillManifestError(
2310
+ filePath,
2311
+ "description",
2312
+ "description cannot contain XML tags"
2313
+ );
2314
+ }
2315
+ if (fm.compatibility != null && typeof fm.compatibility === "string") {
2316
+ if (fm.compatibility.length > COMPATIBILITY_MAX_LENGTH) {
2317
+ throw new SkillManifestError(
2318
+ filePath,
2319
+ "compatibility",
2320
+ `compatibility must be at most ${COMPATIBILITY_MAX_LENGTH} characters (got ${fm.compatibility.length})`
2321
+ );
2322
+ }
2323
+ }
2324
+ }
2325
+
2326
+ // src/tools/skill/SkillMdParser.ts
2327
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
2328
+ ".py",
2329
+ ".js",
2330
+ ".mjs",
2331
+ ".ts",
2332
+ ".sh",
2333
+ ".bash",
2334
+ ".rb",
2335
+ ".go"
2336
+ ]);
2337
+ var INSTRUCTION_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]);
2338
+ var EXCLUDED_FILES = /* @__PURE__ */ new Set(["SKILL.md", "tool.json"]);
2339
+ function parseSkillMd(content, filePath) {
2340
+ const trimmed = content.trimStart();
2341
+ if (!trimmed.startsWith("---")) {
2342
+ throw new SkillManifestError(
2343
+ filePath,
2344
+ "frontmatter",
2345
+ "SKILL.md must start with YAML frontmatter (---)"
2346
+ );
2347
+ }
2348
+ const endIndex = trimmed.indexOf("\n---", 3);
2349
+ if (endIndex === -1) {
2350
+ throw new SkillManifestError(
2351
+ filePath,
2352
+ "frontmatter",
2353
+ "SKILL.md frontmatter is not closed (missing closing ---)"
2354
+ );
2355
+ }
2356
+ const yamlBlock = trimmed.slice(4, endIndex).trim();
2357
+ const body = trimmed.slice(endIndex + 4).trim();
2358
+ let raw;
2359
+ try {
2360
+ const parsed = yaml.load(yamlBlock);
2361
+ if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
2362
+ throw new SkillManifestError(
2363
+ filePath,
2364
+ "frontmatter",
2365
+ "YAML frontmatter must be an object (key: value)"
2366
+ );
2367
+ }
2368
+ raw = parsed;
2369
+ } catch (err) {
2370
+ const message = err instanceof Error ? err.message : String(err);
2371
+ throw new SkillManifestError(
2372
+ filePath,
2373
+ "frontmatter",
2374
+ `Invalid YAML frontmatter: ${message}`
2375
+ );
2376
+ }
2377
+ const name = stringField(raw, "name", filePath);
2378
+ const description = stringField(raw, "description", filePath);
2379
+ if (!name || !description) {
2380
+ throw new SkillManifestError(
2381
+ filePath,
2382
+ "frontmatter",
2383
+ !name ? "name is required" : "description is required"
2384
+ );
2385
+ }
2386
+ const license = stringField(raw, "license");
2387
+ const compatibility = stringField(raw, "compatibility");
2388
+ const allowedTools = stringField(raw, "allowed-tools");
2389
+ const metadata = normalizeMetadata(raw.metadata);
2390
+ const frontmatter = {
2391
+ name,
2392
+ description,
2393
+ ...license && { license },
2394
+ ...compatibility && { compatibility },
2395
+ ...allowedTools && { allowedTools },
2396
+ ...metadata && Object.keys(metadata).length > 0 && { metadata }
2397
+ };
2398
+ validateFrontmatter(frontmatter, filePath);
2399
+ return { frontmatter, instructions: body };
2400
+ }
2401
+ function stringField(raw, key, filePath) {
2402
+ const v = raw[key];
2403
+ if (v == null) return "";
2404
+ if (typeof v === "string") return v;
2405
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
2406
+ if (Array.isArray(v)) {
2407
+ return v.map((x) => typeof x === "string" ? x : String(x)).join("\n");
2408
+ }
2409
+ if (filePath) {
2410
+ throw new SkillManifestError(
2411
+ filePath,
2412
+ "frontmatter",
2413
+ `Frontmatter field "${key}" must be a string, number, boolean, or array`
2414
+ );
2415
+ }
2416
+ return String(v);
2417
+ }
2418
+ function normalizeMetadata(val) {
2419
+ if (val == null) return void 0;
2420
+ if (typeof val === "object" && !Array.isArray(val)) {
2421
+ const out = {};
2422
+ for (const [k, v] of Object.entries(val)) {
2423
+ if (typeof k === "string" && v !== void 0 && v !== null) {
2424
+ out[k] = typeof v === "object" ? JSON.stringify(v) : String(v);
2425
+ }
2426
+ }
2427
+ return Object.keys(out).length ? out : void 0;
2428
+ }
2429
+ if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
2430
+ return { value: String(val) };
2431
+ }
2432
+ return void 0;
2433
+ }
2434
+ async function scanSkillResources(dirPath) {
2435
+ const resources = [];
2436
+ await scanDir(dirPath, dirPath, resources);
2437
+ return resources;
2438
+ }
2439
+ async function scanDir(basePath, currentPath, resources) {
2440
+ let entries;
2441
+ try {
2442
+ entries = await readdir(currentPath, { withFileTypes: true });
2443
+ } catch {
2444
+ return;
2445
+ }
2446
+ for (const entry of entries) {
2447
+ const fullPath = join(currentPath, entry.name);
2448
+ if (entry.isDirectory()) {
2449
+ if (entry.name.startsWith(".") || entry.name === "node_modules") {
2450
+ continue;
2451
+ }
2452
+ await scanDir(basePath, fullPath, resources);
2453
+ } else if (entry.isFile()) {
2454
+ if (EXCLUDED_FILES.has(entry.name)) {
2455
+ continue;
2456
+ }
2457
+ const ext = extname(entry.name).toLowerCase();
2458
+ const relPath = relative(basePath, fullPath);
2459
+ resources.push({
2460
+ relativePath: relPath,
2461
+ absolutePath: fullPath,
2462
+ extension: ext,
2463
+ type: inferResourceType(ext)
2464
+ });
2465
+ }
2466
+ }
2467
+ }
2468
+ function inferResourceType(ext) {
2469
+ if (CODE_EXTENSIONS.has(ext)) return "code";
2470
+ if (INSTRUCTION_EXTENSIONS.has(ext)) return "instructions";
2471
+ return "data";
2472
+ }
2473
+ async function loadSkillDefinition(dirPath) {
2474
+ const skillMdPath = join(dirPath, "SKILL.md");
2475
+ let content;
2476
+ try {
2477
+ content = await readFile(skillMdPath, "utf-8");
2478
+ } catch (err) {
2479
+ throw new SkillManifestError(
2480
+ skillMdPath,
2481
+ "file",
2482
+ `Cannot read SKILL.md: ${err.message}`
2483
+ );
2484
+ }
2485
+ const { frontmatter, instructions } = parseSkillMd(content, skillMdPath);
2486
+ const resources = await scanSkillResources(dirPath);
2487
+ return {
2488
+ frontmatter,
2489
+ instructions,
2490
+ resources,
2491
+ dirPath,
2492
+ skillMdPath
2493
+ };
2494
+ }
2495
+
2496
+ // src/tools/skill/scanSkill.ts
2497
+ var defaultInputSchema = { type: "object", properties: {}, additionalProperties: true };
2498
+ async function scanForSkill(projectPath) {
2499
+ const projectRoot = path.resolve(projectPath);
2500
+ const dirs = await findDirsContainingFile(projectRoot, "SKILL.md");
2501
+ const skills = [];
2502
+ const errors = [];
2503
+ for (const dirPath of dirs) {
2504
+ const relativePath = path.relative(projectRoot, dirPath) || path.basename(dirPath);
2505
+ try {
2506
+ const skillDef = await loadSkillDefinition(dirPath);
2507
+ const name = pathToToolName(relativePath, skillDef.frontmatter.name);
2508
+ skills.push({
2509
+ kind: SKILL_KIND,
2510
+ name,
2511
+ description: skillDef.frontmatter.description,
2512
+ inputSchema: defaultInputSchema,
2513
+ _meta: { hitl: { sideEffect: "none" } },
2514
+ sourcePath: relativePath.replace(/\\/g, "/")
2515
+ });
2516
+ } catch (err) {
2517
+ errors.push({ dir: relativePath, message: err instanceof Error ? err.message : String(err) });
2518
+ }
2519
+ }
2520
+ return { skills, errors };
2521
+ }
2522
+ async function scan2(projectPath, _options = {}) {
2523
+ const root = path.resolve(projectPath);
2524
+ const result = await scanForSkill(root);
2525
+ const specs = result.skills.map((s) => mcpSpecToToolSpec(s, root));
2526
+ return {
2527
+ specs,
2528
+ errors: result.errors.map((e) => ({ file: e.dir, message: e.message }))
2529
+ };
2530
+ }
2531
+ async function readWorkflowMeta(dirPath, workflowFileName = "workflow.json") {
2532
+ const workflowPath = join(dirPath, workflowFileName);
2533
+ let raw;
2534
+ try {
2535
+ raw = await readFile(workflowPath, "utf-8");
2536
+ } catch (err) {
2537
+ throw new DiscoveryError(
2538
+ dirPath,
2539
+ "load",
2540
+ `Failed to read workflow: ${workflowPath}`,
2541
+ err
2542
+ );
2543
+ }
2544
+ let workflowDef;
2545
+ try {
2546
+ workflowDef = JSON.parse(raw);
2547
+ } catch (err) {
2548
+ throw new DiscoveryError(
2549
+ dirPath,
2550
+ "load",
2551
+ `Invalid JSON in ${workflowPath}`,
2552
+ err
2553
+ );
2554
+ }
2555
+ if (!workflowDef.nodes || !Array.isArray(workflowDef.nodes)) {
2556
+ throw new DiscoveryError(
2557
+ dirPath,
2558
+ "validate",
2559
+ `workflow.json must have a "nodes" array`
2560
+ );
2561
+ }
2562
+ const meta = workflowDef.meta;
2563
+ const name = workflowDef.name || meta?.name || basename(dirPath);
2564
+ const description = workflowDef.description || meta?.description || `n8n workflow: ${name}`;
2565
+ let webhookUrl;
2566
+ const nodes = workflowDef.nodes;
2567
+ if (Array.isArray(nodes)) {
2568
+ const webhookNode = nodes.find(
2569
+ (n) => n.type === "n8n-nodes-base.webhook" || n.type?.includes("webhook")
2570
+ );
2571
+ if (webhookNode?.parameters && typeof webhookNode.parameters === "object") {
2572
+ const params = webhookNode.parameters;
2573
+ const pathVal = params.path ?? params.webhookPath;
2574
+ if (typeof pathVal === "string" && pathVal.startsWith("http")) {
2575
+ webhookUrl = pathVal;
2576
+ }
2577
+ }
2578
+ }
2579
+ return { name, description, webhookUrl, workflowDef };
2580
+ }
2581
+ async function loadN8nTool(dirPath, manifest) {
2582
+ const { workflowDef } = await readWorkflowMeta(
2583
+ dirPath,
2584
+ manifest.entryPoint ?? "workflow.json"
2585
+ );
2586
+ return { manifest, dirPath, workflowDef };
2587
+ }
2588
+
2589
+ // src/tools/n8n/scanN8n.ts
2590
+ var defaultInputSchema2 = { type: "object", properties: {}, additionalProperties: true };
2591
+ async function scanForN8n(projectPath) {
2592
+ const projectRoot = path.resolve(projectPath);
2593
+ const dirs = await findDirsContainingFile(projectRoot, "workflow.json");
2594
+ const n8n = [];
2595
+ const errors = [];
2596
+ for (const dirPath of dirs) {
2597
+ const relativePath = path.relative(projectRoot, dirPath) || path.basename(dirPath);
2598
+ try {
2599
+ const { name: wfName, description: wfDesc, webhookUrl } = await readWorkflowMeta(dirPath);
2600
+ const toolName = pathToToolName(relativePath, wfName);
2601
+ n8n.push({
2602
+ kind: N8N_KIND,
2603
+ name: toolName,
2604
+ description: wfDesc,
2605
+ inputSchema: defaultInputSchema2,
2606
+ _meta: { hitl: { sideEffect: "external_write" } },
2607
+ sourcePath: relativePath.replace(/\\/g, "/"),
2608
+ webhookUrl
2609
+ });
2610
+ } catch (err) {
2611
+ errors.push({ dir: relativePath, message: err instanceof Error ? err.message : String(err) });
2612
+ }
2613
+ }
2614
+ return { n8n, errors };
2615
+ }
2616
+ async function scan3(projectPath, _options = {}) {
2617
+ const root = path.resolve(projectPath);
2618
+ const result = await scanForN8n(root);
2619
+ const specs = result.n8n.map((s) => mcpSpecToToolSpec(s, root));
2620
+ return {
2621
+ specs,
2622
+ errors: result.errors.map((e) => ({ file: e.dir, message: e.message }))
2623
+ };
2624
+ }
2625
+
2626
+ // src/tools/mcp/types.ts
2627
+ var MCP_KIND = "mcp";
2628
+
2629
+ // src/tools/mcp/scanner.ts
2630
+ async function scan4(projectPath, options = {}) {
2631
+ const root = path.resolve(projectPath);
2632
+ const namespace = options.namespace ?? "dir";
2633
+ const errors = [];
2634
+ const scanner = new DirectoryScanner({
2635
+ roots: [root],
2636
+ namespace,
2637
+ extensions: options.extensions,
2638
+ onError: (dir, err) => {
2639
+ errors.push({ file: dir, message: err.message });
2640
+ options.onError?.(dir, err);
2641
+ }
2642
+ });
2643
+ let specs;
2644
+ try {
2645
+ specs = await scanner.scan();
2646
+ } catch (err) {
2647
+ errors.push({
2648
+ file: root,
2649
+ message: err instanceof Error ? err.message : String(err)
2650
+ });
2651
+ return { specs: [], errors };
2652
+ }
2653
+ const filtered = specs.filter((s) => s.kind === MCP_KIND);
2654
+ return { specs: filtered, errors };
2655
+ }
2656
+
2657
+ // src/tools/langchain/types.ts
2658
+ var LANGCHAIN_KIND = "langchain";
2659
+ var LANGCHAIN_DIR_NAME = "langchain";
2660
+
2661
+ // src/tools/langchain/scanner.ts
2662
+ async function scan5(projectPath, options = {}) {
2663
+ const root = path.resolve(projectPath);
2664
+ const namespace = options.namespace ?? "dir";
2665
+ const errors = [];
2666
+ const scanner = new DirectoryScanner({
2667
+ roots: [root],
2668
+ namespace,
2669
+ extensions: options.extensions,
2670
+ onError: (dir, err) => {
2671
+ errors.push({ file: dir, message: err.message });
2672
+ options.onError?.(dir, err);
2673
+ }
2674
+ });
2675
+ let specs;
2676
+ try {
2677
+ specs = await scanner.scan();
2678
+ } catch (err) {
2679
+ errors.push({
2680
+ file: root,
2681
+ message: err instanceof Error ? err.message : String(err)
2682
+ });
2683
+ return { specs: [], errors };
2684
+ }
2685
+ const filtered = specs.filter((s) => s.kind === LANGCHAIN_KIND);
2686
+ return { specs: filtered, errors };
2687
+ }
2688
+ async function loadLangChainTool(dirPath, manifest, extensions) {
2689
+ let entryFile;
2690
+ try {
2691
+ entryFile = await resolveEntryPoint(
2692
+ dirPath,
2693
+ manifest.entryPoint ?? "index",
2694
+ extensions
2695
+ );
2696
+ } catch (err) {
2697
+ throw new DiscoveryError(
2698
+ dirPath,
2699
+ "load",
2700
+ `Cannot find LangChain entry point`,
2701
+ err
2702
+ );
2703
+ }
2704
+ let mod;
2705
+ try {
2706
+ mod = await import(pathToFileURL(entryFile).href);
2707
+ } catch (err) {
2708
+ throw new DiscoveryError(
2709
+ dirPath,
2710
+ "load",
2711
+ `Failed to import ${entryFile}`,
2712
+ err
2713
+ );
2714
+ }
2715
+ const tool = mod.default ?? mod.tool ?? mod;
2716
+ if (!tool || typeof tool.invoke !== "function") {
2717
+ throw new DiscoveryError(
2718
+ dirPath,
2719
+ "validate",
2720
+ `Entry point must export an object with invoke() method (LangChainToolLike)`
2721
+ );
2722
+ }
2723
+ return { manifest, dirPath, impl: tool };
2724
+ }
2725
+ var DEFAULT_EXTENSIONS2 = [".js", ".mjs"];
2726
+ async function listSkillProgramFiles(dirPath, extensions = DEFAULT_EXTENSIONS2) {
2727
+ let entries;
2728
+ try {
2729
+ const dirEntries = await readdir(dirPath, { withFileTypes: true });
2730
+ entries = dirEntries.map((entry) => ({
2731
+ name: entry.name,
2732
+ isFile: entry.isFile()
2733
+ }));
2734
+ } catch {
2735
+ return [];
2736
+ }
2737
+ return entries.filter((e) => e.isFile).map((e) => e.name).filter((name) => {
2738
+ if (name.startsWith(".") || name.startsWith("_")) return false;
2739
+ if (name.includes(".test.") || name.includes(".spec.")) return false;
2740
+ return extensions.some((ext) => name.endsWith(ext));
2741
+ }).sort((a, b) => {
2742
+ if (a === "handler.js" || a === "index.js") return -1;
2743
+ if (b === "handler.js" || b === "index.js") return 1;
2744
+ return a.localeCompare(b);
2745
+ });
2746
+ }
2747
+ function isLangChainLikeTool(val) {
2748
+ return val != null && typeof val === "object" && "invoke" in val && typeof val.invoke === "function";
2749
+ }
2750
+ function isConstructable(val) {
2751
+ return typeof val === "function" && typeof val.prototype === "object";
2752
+ }
2753
+ async function loadOneSkillProgram(dirPath, manifest, entryFile, skillDef, programKey, extensions) {
2754
+ let impl;
2755
+ try {
2756
+ const fullPath = await resolveEntryPoint(dirPath, entryFile, extensions ?? [".js", ".mjs"]);
2757
+ const mod = await import(pathToFileURL(fullPath).href);
2758
+ const fn = mod.default ?? mod.handler ?? mod.Tool;
2759
+ if (isLangChainLikeTool(fn)) {
2760
+ impl = fn;
2761
+ } else if (isConstructable(fn)) {
2762
+ const instance = new fn();
2763
+ if (isLangChainLikeTool(instance)) impl = instance;
2764
+ } else if (typeof fn === "function") {
2765
+ impl = fn;
2766
+ }
2767
+ } catch {
2768
+ }
2769
+ return {
2770
+ manifest,
2771
+ dirPath,
2772
+ impl,
2773
+ skillDefinition: skillDef,
2774
+ programKey
2775
+ };
2776
+ }
2777
+ async function loadSkillTools(dirPath, manifest, extensions) {
2778
+ let skillDef;
2779
+ try {
2780
+ skillDef = await loadSkillDefinition(dirPath);
2781
+ } catch (err) {
2782
+ throw new DiscoveryError(
2783
+ dirPath,
2784
+ "load",
2785
+ `Failed to parse SKILL.md: ${err.message}`,
2786
+ err
2787
+ );
2788
+ }
2789
+ const programs = manifest.programs;
2790
+ if (programs && typeof programs === "object" && Object.keys(programs).length > 0) {
2791
+ const result = [];
2792
+ for (const [programKey, entryFile2] of Object.entries(programs)) {
2793
+ const loaded2 = await loadOneSkillProgram(
2794
+ dirPath,
2795
+ manifest,
2796
+ entryFile2,
2797
+ skillDef,
2798
+ programKey,
2799
+ extensions
2800
+ );
2801
+ result.push(loaded2);
2802
+ }
2803
+ return result;
2804
+ }
2805
+ const exts = extensions ?? DEFAULT_EXTENSIONS2;
2806
+ const files = await listSkillProgramFiles(dirPath, exts);
2807
+ if (files.length >= 2) {
2808
+ const result = [];
2809
+ for (let i = 0; i < files.length; i++) {
2810
+ const file = files[i];
2811
+ const programKey = i === 0 ? "default" : file.replace(/\.[^.]+$/, "");
2812
+ const loaded2 = await loadOneSkillProgram(
2813
+ dirPath,
2814
+ manifest,
2815
+ file,
2816
+ skillDef,
2817
+ programKey,
2818
+ extensions
2819
+ );
2820
+ result.push(loaded2);
2821
+ }
2822
+ return result;
2823
+ }
2824
+ const entryFile = manifest.entryPoint ?? files[0] ?? "handler";
2825
+ const loaded = await loadOneSkillProgram(
2826
+ dirPath,
2827
+ manifest,
2828
+ entryFile,
2829
+ skillDef,
2830
+ void 0,
2831
+ extensions
2832
+ );
2833
+ return [loaded];
2834
+ }
2835
+ async function listLangchainEntryFiles(dirPath, extensions) {
2836
+ let entries;
2837
+ try {
2838
+ const dirEntries = await readdir(dirPath, { withFileTypes: true });
2839
+ entries = dirEntries.map((entry) => ({
2840
+ name: entry.name,
2841
+ isFile: entry.isFile()
2842
+ }));
2843
+ } catch {
2844
+ return [];
2845
+ }
2846
+ return entries.filter((e) => e.isFile).map((e) => e.name).filter((name) => {
2847
+ if (name.startsWith(".") || name.startsWith("_")) return false;
2848
+ if (name.endsWith(".d.ts")) return false;
2849
+ if (name.includes(".test.") || name.includes(".spec.")) return false;
2850
+ return extensions.some((ext) => name.endsWith(ext));
2851
+ });
2852
+ }
2853
+ async function loadLangChainToolsFromDir(dirPath, dirName, manifest, strict, namespace, extensions, langchainDirName, toSpec, onError) {
2854
+ const entryFiles = await listLangchainEntryFiles(dirPath, extensions);
2855
+ if (entryFiles.length === 0) {
2856
+ if (strict) {
2857
+ throw new DiscoveryError(dirPath, "load", "No LangChain entry files found");
2858
+ }
2859
+ return [];
2860
+ }
2861
+ const specs = [];
2862
+ const useDirNameForSingle = dirName !== langchainDirName;
2863
+ for (const entryFile of entryFiles) {
2864
+ const fileManifest = { ...manifest, entryPoint: entryFile };
2865
+ try {
2866
+ const loaded = await loadLangChainTool(dirPath, fileManifest, extensions);
2867
+ const fileBase = basename(entryFile).replace(/\.[^.]+$/, "");
2868
+ const nameHint = entryFiles.length === 1 && useDirNameForSingle ? dirName : fileBase;
2869
+ specs.push(toSpec(loaded, nameHint, dirPath, namespace));
2870
+ } catch (error) {
2871
+ const err = error;
2872
+ if (err instanceof DiscoveryError && err.phase === "validate") {
2873
+ if (strict) throw err;
2874
+ continue;
2875
+ }
2876
+ onError?.(join(dirPath, entryFile), err);
2877
+ if (strict) throw err;
2878
+ }
2879
+ }
2880
+ return specs;
2881
+ }
2882
+ function isCursorFormat(obj) {
2883
+ return typeof obj === "object" && obj !== null && "mcpServers" in obj && typeof obj.mcpServers === "object" && obj.mcpServers !== null;
2884
+ }
2885
+ function extractMCPConfig(parsed, toolName) {
2886
+ if (isCursorFormat(parsed)) {
2887
+ const servers = parsed.mcpServers;
2888
+ const keys = Object.keys(servers);
2889
+ if (keys.length === 0) {
2890
+ return {};
2891
+ }
2892
+ const name = toolName && keys.includes(toolName) ? toolName : keys[0];
2893
+ return servers[name];
2894
+ }
2895
+ return parsed;
2896
+ }
2897
+ async function loadMCPTool(dirPath, manifest) {
2898
+ const mcpPath = join(dirPath, manifest.entryPoint ?? "mcp.json");
2899
+ let raw;
2900
+ try {
2901
+ raw = await readFile(mcpPath, "utf-8");
2902
+ } catch (err) {
2903
+ throw new DiscoveryError(
2904
+ dirPath,
2905
+ "load",
2906
+ `Failed to read MCP config: ${mcpPath}`,
2907
+ err
2908
+ );
2909
+ }
2910
+ let parsed;
2911
+ try {
2912
+ parsed = JSON.parse(raw);
2913
+ } catch (err) {
2914
+ throw new DiscoveryError(
2915
+ dirPath,
2916
+ "load",
2917
+ `Invalid JSON in ${mcpPath}`,
2918
+ err
2919
+ );
2920
+ }
2921
+ const baseName = manifest.name?.split("/").pop();
2922
+ const config = extractMCPConfig(parsed, baseName);
2923
+ if (!config.command && !config.url) {
2924
+ throw new DiscoveryError(
2925
+ dirPath,
2926
+ "validate",
2927
+ `mcp.json must have either "command" or "url" field`
2928
+ );
2929
+ }
2930
+ return { manifest, dirPath, mcpConfig: config };
2931
+ }
2932
+
2933
+ // src/tools/mcp/directoryApply.ts
2934
+ function applyLoadedToSpec(spec, loaded, _manifest, _defaultDirName, _namespace) {
2935
+ if (loaded.mcpConfig?.url) spec.endpoint = loaded.mcpConfig.url;
2936
+ spec.impl = loaded.mcpConfig;
2937
+ }
2938
+ var directoryMarker = {
2939
+ kind: "mcp",
2940
+ markerFile: "mcp.json",
2941
+ defaultEntryPoint: "mcp.json"
2942
+ };
2943
+
2944
+ // src/tools/langchain/directoryApply.ts
2945
+ function applyLoadedToSpec2(spec, loaded, manifest, _defaultDirName, namespace) {
2946
+ spec.impl = loaded.impl;
2947
+ if (!manifest.name && loaded.impl) {
2948
+ const toolName = loaded.impl.name;
2949
+ if (toolName) spec.name = `${namespace}/${toolName}`;
2950
+ }
2951
+ if (!manifest.description && loaded.impl) {
2952
+ const d = loaded.impl.description;
2953
+ if (d) spec.description = d;
2954
+ }
2955
+ if (!manifest.inputSchema && loaded.impl) {
2956
+ const schema = loaded.impl.schema;
2957
+ if (schema) spec.inputSchema = schema;
2958
+ }
2959
+ }
2960
+
2961
+ // src/tools/skill/directoryApply.ts
2962
+ function applyLoadedToSpec3(spec, loaded, manifest, defaultDirName, namespace) {
2963
+ const skillDef = loaded.skillDefinition;
2964
+ if (skillDef) {
2965
+ spec.name = manifest.name ?? skillDef.frontmatter.name;
2966
+ spec.description = skillDef.frontmatter.description;
2967
+ if (loaded.programKey === "default") {
2968
+ spec.name = `${namespace}/${defaultDirName}`;
2969
+ } else if (loaded.programKey) {
2970
+ spec.name = `${namespace}/${defaultDirName}/${loaded.programKey}`;
2971
+ }
2972
+ const impl = loaded.impl;
2973
+ if (impl && typeof impl === "object" && typeof impl.invoke === "function") {
2974
+ if (impl.description != null && impl.description !== "")
2975
+ spec.description = impl.description;
2976
+ if (impl.schema != null && typeof impl.schema === "object")
2977
+ spec.inputSchema = impl.schema;
2978
+ }
2979
+ spec.impl = { ...skillDef, handler: loaded.impl };
2980
+ } else {
2981
+ spec.impl = loaded.impl;
2982
+ }
2983
+ }
2984
+ var directoryMarker2 = {
2985
+ kind: "skill",
2986
+ markerFile: "SKILL.md",
2987
+ defaultEntryPoint: "handler"
2988
+ };
2989
+
2990
+ // src/tools/n8n/directoryApply.ts
2991
+ function applyLoadedToSpec4(spec, loaded, manifest, _defaultDirName, _namespace) {
2992
+ const workflow = loaded.workflowDef;
2993
+ if (workflow?.id) spec.resourceId = String(workflow.id);
2994
+ if (!manifest.description && workflow) {
2995
+ const d = workflow.description ?? workflow.meta?.description ?? (typeof workflow.name === "string" ? workflow.name : void 0);
2996
+ if (d) spec.description = d;
2997
+ }
2998
+ spec.impl = loaded.workflowDef;
2999
+ }
3000
+ var directoryMarker3 = {
3001
+ kind: "n8n",
3002
+ markerFile: "workflow.json",
3003
+ defaultEntryPoint: "workflow.json"
3004
+ };
3005
+
3006
+ // src/tools/discoveryFactory.ts
3007
+ var DiscoveryError = class extends Error {
3008
+ toolDir;
3009
+ phase;
3010
+ cause;
3011
+ constructor(toolDir, phase, message, cause) {
3012
+ super(`[${phase}] ${toolDir}: ${message}`);
3013
+ this.name = "DiscoveryError";
3014
+ this.toolDir = toolDir;
3015
+ this.phase = phase;
3016
+ this.cause = cause;
3017
+ }
3018
+ };
3019
+ var DIRECTORY_KINDS = ["mcp", "langchain", "skill", "n8n"];
3020
+ var DIRECTORY_DISCOVERABLE_KINDS = DIRECTORY_KINDS;
3021
+ var DIRECTORY_KIND_MARKERS = [
3022
+ directoryMarker2,
3023
+ directoryMarker3,
3024
+ directoryMarker
3025
+ ];
3026
+ var DIRECTORY_LOADERS = {
3027
+ mcp: async (dirPath, manifest) => [await loadMCPTool(dirPath, manifest)],
3028
+ langchain: async (dirPath, manifest, ext) => [await loadLangChainTool(dirPath, manifest, ext)],
3029
+ skill: (dirPath, manifest, ext) => loadSkillTools(dirPath, manifest, ext),
3030
+ n8n: async (dirPath, manifest) => [await loadN8nTool(dirPath, manifest)]
3031
+ };
3032
+ function getDirectoryLoader(kind) {
3033
+ const loader = DIRECTORY_LOADERS[kind];
3034
+ if (!loader) {
3035
+ throw new DiscoveryError("", "manifest", `Unknown directory tool kind: "${kind}"`);
3036
+ }
3037
+ return loader;
3038
+ }
3039
+ function applyDirectoryLoadedToSpec(spec, loaded, manifest, defaultDirName, namespace) {
3040
+ switch (manifest.kind) {
3041
+ case "mcp":
3042
+ return applyLoadedToSpec(spec, loaded);
3043
+ case "langchain":
3044
+ return applyLoadedToSpec2(spec, loaded, manifest, defaultDirName, namespace);
3045
+ case "skill":
3046
+ return applyLoadedToSpec3(spec, loaded, manifest, defaultDirName, namespace);
3047
+ case "n8n":
3048
+ return applyLoadedToSpec4(spec, loaded, manifest);
3049
+ }
3050
+ }
3051
+ async function discoverTools(type, projectPath, options = {}) {
3052
+ const root = path.resolve(projectPath);
3053
+ switch (type) {
3054
+ case "function":
3055
+ return scan(root, options);
3056
+ case "skill":
3057
+ return scan2(root, options);
3058
+ case "n8n":
3059
+ return scan3(root, options);
3060
+ case "mcp":
3061
+ return scan4(root, options);
3062
+ case "langchain":
3063
+ return scan5(root, options);
3064
+ default: {
3065
+ const _ = type;
3066
+ return _;
3067
+ }
3068
+ }
3069
+ }
3070
+ var DEFAULT_EXTENSIONS3 = [".js", ".mjs"];
3071
+ var DirectoryScanner = class {
3072
+ roots;
3073
+ extensions;
3074
+ onError;
3075
+ constructor(options) {
3076
+ const defaultNamespace = options.namespace ?? "dir";
3077
+ this.roots = options.roots.map((root) => {
3078
+ if (typeof root === "string") {
3079
+ return { path: root, namespace: defaultNamespace };
3080
+ }
3081
+ return {
3082
+ path: root.path,
3083
+ namespace: root.namespace ?? defaultNamespace
3084
+ };
3085
+ });
3086
+ this.extensions = options.extensions ?? DEFAULT_EXTENSIONS3;
3087
+ this.onError = options.onError;
3088
+ }
3089
+ async scan() {
3090
+ const specs = [];
3091
+ for (const root of this.roots) {
3092
+ const rootSpecs = await this.scanRoot(root.path, root.namespace);
3093
+ specs.push(...rootSpecs);
3094
+ }
3095
+ return specs;
3096
+ }
3097
+ async scanRoot(rootPath, namespace) {
3098
+ return this.scanRecursive(rootPath, namespace);
3099
+ }
3100
+ async scanRecursive(dirPath, namespace) {
3101
+ const specs = [];
3102
+ let dirEntries;
3103
+ try {
3104
+ const entries = await readdir(dirPath, { withFileTypes: true });
3105
+ dirEntries = entries.map((entry) => ({
3106
+ name: entry.name,
3107
+ isDirectory: entry.isDirectory()
3108
+ }));
3109
+ } catch (error) {
3110
+ this.onError?.(dirPath, error);
3111
+ return specs;
3112
+ }
3113
+ const dirName = basename(dirPath);
3114
+ try {
3115
+ const loadedSpecs = await this.loadToolDir(dirPath, dirName, namespace);
3116
+ if (loadedSpecs.length > 0) specs.push(...loadedSpecs);
3117
+ } catch (error) {
3118
+ this.onError?.(dirPath, error);
3119
+ }
3120
+ for (const entry of dirEntries) {
3121
+ if (!entry.isDirectory) continue;
3122
+ const childPath = join(dirPath, entry.name);
3123
+ try {
3124
+ const childSpecs = await this.scanRecursive(childPath, namespace);
3125
+ specs.push(...childSpecs);
3126
+ } catch (error) {
3127
+ this.onError?.(childPath, error);
3128
+ }
3129
+ }
3130
+ return specs;
3131
+ }
3132
+ async loadToolDir(dirPath, dirName, namespace) {
3133
+ const manifestPath = join(dirPath, "tool.json");
3134
+ let manifestRaw;
3135
+ try {
3136
+ manifestRaw = await readFile(manifestPath, "utf-8");
3137
+ } catch {
3138
+ const inferred = await this.inferManifest(dirPath, dirName);
3139
+ if (!inferred) return [];
3140
+ if (inferred.kind === "langchain") {
3141
+ if (inferred.entryPoint) {
3142
+ const loaded3 = await loadLangChainTool(dirPath, inferred, this.extensions);
3143
+ return [this.toToolSpec(loaded3, dirName, dirPath, namespace)];
3144
+ }
3145
+ return loadLangChainToolsFromDir(
3146
+ dirPath,
3147
+ dirName,
3148
+ inferred,
3149
+ false,
3150
+ namespace,
3151
+ this.extensions,
3152
+ LANGCHAIN_DIR_NAME,
3153
+ (loaded3, nameHint, dp, ns) => this.toToolSpec(loaded3, nameHint, dp, ns),
3154
+ this.onError
3155
+ );
3156
+ }
3157
+ if (inferred.kind === "skill") {
3158
+ const loadedList = await loadSkillTools(dirPath, inferred, this.extensions);
3159
+ return loadedList.map(
3160
+ (loaded3) => this.toToolSpec(loaded3, dirName, dirPath, namespace)
3161
+ );
3162
+ }
3163
+ const loaded2 = await this.loadByKind(dirPath, inferred);
3164
+ return [this.toToolSpec(loaded2, dirName, dirPath, namespace)];
3165
+ }
3166
+ let manifest;
3167
+ try {
3168
+ manifest = JSON.parse(manifestRaw);
3169
+ } catch (err) {
3170
+ throw new DiscoveryError(dirPath, "manifest", "Invalid JSON in tool.json", err);
3171
+ }
3172
+ if (!manifest.kind) {
3173
+ throw new DiscoveryError(dirPath, "manifest", `tool.json must have a "kind" field`);
3174
+ }
3175
+ if (manifest.enabled === false) return [];
3176
+ if (manifest.kind === "langchain") {
3177
+ if (manifest.entryPoint) {
3178
+ const loaded2 = await loadLangChainTool(dirPath, manifest, this.extensions);
3179
+ return [this.toToolSpec(loaded2, dirName, dirPath, namespace)];
3180
+ }
3181
+ return loadLangChainToolsFromDir(
3182
+ dirPath,
3183
+ dirName,
3184
+ manifest,
3185
+ true,
3186
+ namespace,
3187
+ this.extensions,
3188
+ LANGCHAIN_DIR_NAME,
3189
+ (loaded2, nameHint, dp, ns) => this.toToolSpec(loaded2, nameHint, dp, ns),
3190
+ this.onError
3191
+ );
3192
+ }
3193
+ if (manifest.kind === "skill") {
3194
+ const loadedList = await loadSkillTools(dirPath, manifest, this.extensions);
3195
+ return loadedList.map(
3196
+ (loaded2) => this.toToolSpec(loaded2, dirName, dirPath, namespace)
3197
+ );
3198
+ }
3199
+ const loaded = await this.loadByKind(dirPath, manifest);
3200
+ return [this.toToolSpec(loaded, dirName, dirPath, namespace)];
3201
+ }
3202
+ async inferManifest(dirPath, dirName) {
3203
+ const kinds = [];
3204
+ for (const m of DIRECTORY_KIND_MARKERS) {
3205
+ if (await this.fileExists(join(dirPath, m.markerFile))) kinds.push(m.kind);
3206
+ }
3207
+ const isLangchainDir = dirName === LANGCHAIN_DIR_NAME;
3208
+ const hasLangchain = isLangchainDir ? (await listLangchainEntryFiles(dirPath, this.extensions)).length > 0 : dirName !== SKILL_DIR_NAME && await this.hasEntryPoint(dirPath, "index");
3209
+ if (hasLangchain) kinds.push("langchain");
3210
+ if (kinds.length === 0) return null;
3211
+ if (kinds.length > 1) {
3212
+ throw new DiscoveryError(
3213
+ dirPath,
3214
+ "manifest",
3215
+ `Ambiguous tool kind (found ${kinds.join(", ")}). Add tool.json to disambiguate.`
3216
+ );
3217
+ }
3218
+ const kind = kinds[0];
3219
+ const manifest = { kind };
3220
+ const marker = DIRECTORY_KIND_MARKERS.find((m) => m.kind === kind);
3221
+ if (marker) {
3222
+ manifest.entryPoint = marker.defaultEntryPoint;
3223
+ }
3224
+ if (kind === "langchain" && !isLangchainDir) manifest.entryPoint = "index";
3225
+ return manifest;
3226
+ }
3227
+ async fileExists(path7) {
3228
+ try {
3229
+ await access(path7);
3230
+ return true;
3231
+ } catch {
3232
+ return false;
3233
+ }
3234
+ }
3235
+ async hasEntryPoint(dirPath, baseName) {
3236
+ try {
3237
+ await resolveEntryPoint(dirPath, baseName, this.extensions);
3238
+ return true;
3239
+ } catch {
3240
+ return false;
3241
+ }
3242
+ }
3243
+ async loadByKind(dirPath, manifest) {
3244
+ const kind = manifest.kind;
3245
+ const loader = getDirectoryLoader(kind);
3246
+ const result = await loader(dirPath, manifest, this.extensions);
3247
+ const list = Array.isArray(result) ? result : [result];
3248
+ if (list.length === 0) {
3249
+ throw new DiscoveryError(dirPath, "load", "No tools loaded", new Error("empty"));
3250
+ }
3251
+ return list[0];
3252
+ }
3253
+ toToolSpec(loaded, dirName, dirPath, namespace) {
3254
+ const { manifest } = loaded;
3255
+ const kindDirNames = new Set(DIRECTORY_DISCOVERABLE_KINDS);
3256
+ const parentName = basename(join(dirPath, ".."));
3257
+ const isKindDir = kindDirNames.has(dirName);
3258
+ const defaultDirName = isKindDir ? parentName : dirName;
3259
+ const inferredName = isKindDir ? `${namespace}/${defaultDirName}-${dirName}` : `${namespace}/${defaultDirName}`;
3260
+ const name = manifest.name ?? inferredName;
3261
+ const spec = this.buildBaseSpec(manifest, name, dirName);
3262
+ applyDirectoryLoadedToSpec(spec, loaded, manifest, defaultDirName, namespace);
3263
+ return spec;
3264
+ }
3265
+ buildBaseSpec(manifest, name, dirName) {
3266
+ return {
3267
+ name,
3268
+ version: manifest.version ?? "1.0.0",
3269
+ kind: manifest.kind,
3270
+ description: manifest.description ?? `${manifest.kind} tool: ${dirName}`,
3271
+ tags: manifest.tags,
3272
+ inputSchema: manifest.inputSchema ?? { type: "object", additionalProperties: true },
3273
+ outputSchema: manifest.outputSchema ?? { type: "object", additionalProperties: true },
3274
+ capabilities: manifest.capabilities ?? [],
3275
+ costHints: manifest.costHints
3276
+ };
3277
+ }
3278
+ };
1759
3279
 
1760
3280
  // src/api/runtimeFromConfig.ts
1761
3281
  var requireFromPackage = createRequire(import.meta.url);
@@ -1792,6 +3312,98 @@ function getInstalledPackageVersion(packageName) {
1792
3312
  }
1793
3313
  return null;
1794
3314
  }
3315
+ function hasInvokeFunction(value) {
3316
+ return value != null && typeof value === "object" && "invoke" in value && typeof value.invoke === "function";
3317
+ }
3318
+ function normalizeToolOutput(output) {
3319
+ if (output != null && typeof output === "object" && "result" in output) {
3320
+ const o = output;
3321
+ const evidence = Array.isArray(o.evidence) ? o.evidence : void 0;
3322
+ return evidence ? { result: o.result, raw: { evidence } } : { result: o.result };
3323
+ }
3324
+ return { result: output };
3325
+ }
3326
+ function createLocalDirectoryAdapter(kind) {
3327
+ return {
3328
+ kind,
3329
+ async invoke(spec, args, ctx) {
3330
+ if (spec.kind === "skill") {
3331
+ const impl = spec.impl;
3332
+ if (hasInvokeFunction(impl)) {
3333
+ const out2 = await impl.invoke(args);
3334
+ return normalizeToolOutput(out2);
3335
+ }
3336
+ const handler = typeof impl?.handler === "function" ? impl.handler : typeof spec.impl === "function" ? spec.impl : void 0;
3337
+ if (typeof handler !== "function") {
3338
+ throw new Error(`Local skill ${spec.name} has no executable handler`);
3339
+ }
3340
+ const out = await handler(args, {
3341
+ requestId: ctx.requestId,
3342
+ taskId: ctx.taskId,
3343
+ skill: {
3344
+ name: impl?.frontmatter?.name ?? spec.name,
3345
+ description: impl?.frontmatter?.description ?? spec.description ?? "",
3346
+ instructions: impl?.instructions ?? "",
3347
+ resources: [],
3348
+ readResource: async () => "",
3349
+ getResourcesByType: () => [],
3350
+ dirPath: impl?.dirPath ?? ""
3351
+ }
3352
+ });
3353
+ return normalizeToolOutput(out);
3354
+ }
3355
+ if (spec.kind === "langchain") {
3356
+ if (!hasInvokeFunction(spec.impl)) {
3357
+ throw new Error(`Local langchain tool ${spec.name} missing invoke()`);
3358
+ }
3359
+ const out = await spec.impl.invoke(args);
3360
+ return normalizeToolOutput(out);
3361
+ }
3362
+ if (spec.kind === "n8n") {
3363
+ if (!spec.endpoint) {
3364
+ throw new Error(`Local n8n tool ${spec.name} missing endpoint/webhook URL`);
3365
+ }
3366
+ const res = await fetch(spec.endpoint, {
3367
+ method: "POST",
3368
+ headers: { "content-type": "application/json" },
3369
+ body: JSON.stringify(args ?? {})
3370
+ });
3371
+ if (!res.ok) {
3372
+ throw new Error(`n8n request failed (${res.status})`);
3373
+ }
3374
+ const text = await res.text();
3375
+ if (!text.trim()) return { result: {} };
3376
+ try {
3377
+ return { result: JSON.parse(text) };
3378
+ } catch {
3379
+ return { result: text };
3380
+ }
3381
+ }
3382
+ if (spec.kind === "function") {
3383
+ const meta = spec._meta;
3384
+ if (!meta?.sourcePath || !meta?.exportName || !meta?.projectPath) {
3385
+ throw new Error(`Local function ${spec.name} missing sourcePath/exportName/projectPath`);
3386
+ }
3387
+ const source = resolve(meta.projectPath, meta.sourcePath);
3388
+ const mod = await import(pathToFileURL(source).href);
3389
+ const fn = mod[meta.exportName];
3390
+ if (typeof fn !== "function") {
3391
+ throw new Error(`Local function ${spec.name} export "${meta.exportName}" is not a function`);
3392
+ }
3393
+ const out = await fn(args);
3394
+ return normalizeToolOutput(out);
3395
+ }
3396
+ throw new Error(`Local directory tool kind not supported for invoke: ${spec.kind}`);
3397
+ }
3398
+ };
3399
+ }
3400
+ function resolveFileDescriptorPath(descriptor, configFilePath) {
3401
+ const parsed = parseToolPath(descriptor.trim());
3402
+ if (!parsed || parsed.protocol !== "file") return null;
3403
+ const localPath = isAbsolute(configFilePath) ? configFilePath : resolve(process.cwd(), configFilePath);
3404
+ const configDir = dirname(localPath);
3405
+ return resolve(configDir, `${parsed.scope}/${parsed.packageWithVersion}`);
3406
+ }
1795
3407
  function getRegisterFn(mod) {
1796
3408
  return mod?.register ?? mod?.registerCoreTools;
1797
3409
  }
@@ -1860,11 +3472,11 @@ function parseNpmDescriptor(entry) {
1860
3472
  }
1861
3473
  function loadExtensionFromFileDescriptorSync(descriptor, configFilePath, stepLog) {
1862
3474
  const entryStr = descriptor.trim();
1863
- const path = parseToolPath(entryStr);
1864
- if (!path || path.protocol !== "file") return null;
3475
+ const path7 = parseToolPath(entryStr);
3476
+ if (!path7 || path7.protocol !== "file") return null;
1865
3477
  const localPath = isAbsolute(configFilePath) ? configFilePath : resolve(process.cwd(), configFilePath);
1866
3478
  const configDir = dirname(localPath);
1867
- const pathPart = `${path.scope}/${path.packageWithVersion}`;
3479
+ const pathPart = `${path7.scope}/${path7.packageWithVersion}`;
1868
3480
  const resolvedPath = resolve(configDir, pathPart);
1869
3481
  if (!existsSync(resolvedPath) || !statSync(resolvedPath).isDirectory()) return null;
1870
3482
  try {
@@ -2007,6 +3619,29 @@ async function loadExtensionForDescriptorAsync(descriptor, configFilePath, stepL
2007
3619
  }
2008
3620
  return null;
2009
3621
  }
3622
+ async function loadLocalDirectoryForFileDescriptor(descriptor, configFilePath, stepLog) {
3623
+ const resolvedPath = resolveFileDescriptorPath(descriptor, configFilePath);
3624
+ if (!resolvedPath || !existsSync(resolvedPath) || !statSync(resolvedPath).isDirectory()) return null;
3625
+ if (existsSync(join(resolvedPath, "package.json"))) return null;
3626
+ const scanErrors = [];
3627
+ const scanner = new DirectoryScanner({
3628
+ roots: [{ path: resolvedPath, namespace: "local" }],
3629
+ onError: (toolDir, error) => scanErrors.push(`${toolDir}: ${error.message}`)
3630
+ });
3631
+ const specs = await scanner.scan();
3632
+ if (scanErrors.length > 0 && stepLog) {
3633
+ stepLog(`Local directory scan (${descriptor}) reported ${scanErrors.length} error(s)`);
3634
+ }
3635
+ if (specs.length === 0) return null;
3636
+ if (stepLog) {
3637
+ stepLog(`Loaded ${specs.length} local tool(s) from ${descriptor}`);
3638
+ }
3639
+ return {
3640
+ specs,
3641
+ descriptor: descriptor.trim(),
3642
+ resolvedVersion: "local"
3643
+ };
3644
+ }
2010
3645
  async function loadAllExtensionsFromToolYamlAsync(configFilePath, stepLog) {
2011
3646
  const localPath = isAbsolute(configFilePath) ? configFilePath : resolve(process.cwd(), configFilePath);
2012
3647
  if (!existsSync(localPath)) return [];
@@ -2022,7 +3657,12 @@ async function loadAllExtensionsFromToolYamlAsync(configFilePath, stepLog) {
2022
3657
  if (result) loaded.push(result);
2023
3658
  } else if (entryStr.startsWith("file:")) {
2024
3659
  const result = loadExtensionFromFileDescriptorSync(entryStr, configFilePath, stepLog);
2025
- if (result) loaded.push(result);
3660
+ if (result) {
3661
+ loaded.push(result);
3662
+ } else {
3663
+ const local = await loadLocalDirectoryForFileDescriptor(entryStr, configFilePath, stepLog);
3664
+ if (local) loaded.push(local);
3665
+ }
2026
3666
  }
2027
3667
  }
2028
3668
  return loaded;
@@ -2083,10 +3723,28 @@ async function createRuntimeFromConfig(options = {}) {
2083
3723
  const before = new Set(registry.snapshot().map((s) => s.name));
2084
3724
  const prefix = ext.descriptor.startsWith("file:") ? fileDescriptorToRegistryPrefix(ext.descriptor) : npmDescriptorToRegistryPrefix(ext.descriptor, ext.resolvedVersion);
2085
3725
  const reg = createPrefixingRegistry(registry, prefix ?? "");
2086
- const adapter = ext.register(reg, options.coreTools);
2087
- runtime.registerAdapter(adapter);
2088
- const registeredNow = registry.snapshot().map((s) => s.name).filter((name) => !before.has(name));
2089
- runtime.registerAdapterForTools(registeredNow, adapter);
3726
+ if ("register" in ext) {
3727
+ const adapter = ext.register(reg, options.coreTools);
3728
+ runtime.registerAdapter(adapter);
3729
+ const registeredNow = registry.snapshot().map((s) => s.name).filter((name) => !before.has(name));
3730
+ runtime.registerAdapterForTools(registeredNow, adapter);
3731
+ } else {
3732
+ reg.bulkRegister(ext.specs);
3733
+ const registeredNow = registry.snapshot().map((s) => s.name).filter((name) => !before.has(name));
3734
+ const byKind = /* @__PURE__ */ new Map();
3735
+ for (const toolName of registeredNow) {
3736
+ const spec = registry.get(toolName);
3737
+ if (!spec) continue;
3738
+ const list = byKind.get(spec.kind) ?? [];
3739
+ list.push(toolName);
3740
+ byKind.set(spec.kind, list);
3741
+ }
3742
+ for (const [kind, names] of byKind.entries()) {
3743
+ const adapter = createLocalDirectoryAdapter(kind);
3744
+ runtime.registerAdapter(adapter);
3745
+ runtime.registerAdapterForTools(names, adapter);
3746
+ }
3747
+ }
2090
3748
  }
2091
3749
  return { runtime, registry };
2092
3750
  }
@@ -2254,17 +3912,17 @@ var DEFAULT_CTX = {
2254
3912
  ]
2255
3913
  };
2256
3914
  function parseBody(req) {
2257
- return new Promise((resolve4, reject) => {
3915
+ return new Promise((resolve10, reject) => {
2258
3916
  const chunks = [];
2259
3917
  req.on("data", (chunk) => chunks.push(chunk));
2260
3918
  req.on("end", () => {
2261
3919
  const raw = Buffer.concat(chunks).toString("utf-8");
2262
3920
  if (!raw.trim()) {
2263
- resolve4({});
3921
+ resolve10({});
2264
3922
  return;
2265
3923
  }
2266
3924
  try {
2267
- resolve4(JSON.parse(raw));
3925
+ resolve10(JSON.parse(raw));
2268
3926
  } catch {
2269
3927
  reject(new Error("Invalid JSON body"));
2270
3928
  }
@@ -2312,8 +3970,8 @@ function createOpenAPIHttpServer(runtime, options = {}) {
2312
3970
  const ctxFactory = options.execContextFactory ?? (() => ({ ...DEFAULT_CTX }));
2313
3971
  const server = createServer(async (req, res) => {
2314
3972
  const url = req.url ?? "/";
2315
- const path = url.split("?")[0] ?? "/";
2316
- const norm = basePath ? path === basePath ? "" : path.replace(basePath, "") || "/" : path;
3973
+ const path7 = url.split("?")[0] ?? "/";
3974
+ const norm = basePath ? path7 === basePath ? "" : path7.replace(basePath, "") || "/" : path7;
2317
3975
  try {
2318
3976
  if (req.method === "GET" && (norm === "/" || norm === "/swagger")) {
2319
3977
  const specPath = basePath ? `${basePath}/openapi.json` : "/openapi.json";
@@ -2403,13 +4061,13 @@ function createOpenAPIHttpServer(runtime, options = {}) {
2403
4061
  return server;
2404
4062
  }
2405
4063
  function listenOpenAPIHttpServer(server, options = {}) {
2406
- return new Promise((resolve4, reject) => {
4064
+ return new Promise((resolve10, reject) => {
2407
4065
  const port = options.port ?? 0;
2408
4066
  const host = options.host ?? "localhost";
2409
4067
  server.listen(port, host, () => {
2410
4068
  const addr = server.address();
2411
4069
  const actualPort = typeof addr === "object" && addr?.port != null ? addr.port : port;
2412
- resolve4({ port: actualPort, host });
4070
+ resolve10({ port: actualPort, host });
2413
4071
  });
2414
4072
  server.on("error", reject);
2415
4073
  });
@@ -2429,9 +4087,6 @@ async function createHttpService(runtimeOrConfig, options = {}) {
2429
4087
  };
2430
4088
  }
2431
4089
 
2432
- // src/tools/mcp/types.ts
2433
- var MCP_KIND = "mcp";
2434
-
2435
4090
  // src/api/expose/mcpServer.ts
2436
4091
  var DEFAULT_CTX2 = {
2437
4092
  requestId: `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
@@ -2534,24 +4189,24 @@ function createMCPStreamableHttpHandler(runtimeOrConfig, options = {}) {
2534
4189
  })();
2535
4190
  }
2536
4191
  async function createMCPServerStreamableHttp(runtimeOrConfig, options = {}) {
2537
- const path = options.path ?? "/mcp";
4192
+ const path7 = options.path ?? "/mcp";
2538
4193
  const host = options.host ?? "127.0.0.1";
2539
4194
  const port = options.port ?? 3e3;
2540
4195
  const { createMcpExpressApp } = await import('@modelcontextprotocol/sdk/server/express.js');
2541
4196
  const handler = "invoke" in runtimeOrConfig && typeof runtimeOrConfig.invoke === "function" ? createMCPStreamableHttpHandler(runtimeOrConfig, options) : await createMCPStreamableHttpHandler(runtimeOrConfig, options);
2542
4197
  const app = createMcpExpressApp({ host });
2543
- app.post(path, handler);
4198
+ app.post(path7, handler);
2544
4199
  return {
2545
4200
  app,
2546
- path,
4201
+ path: path7,
2547
4202
  async listen(listenPort, listenHost) {
2548
4203
  const p = listenPort ?? port;
2549
4204
  const h = listenHost ?? host;
2550
- return new Promise((resolve4, reject) => {
4205
+ return new Promise((resolve10, reject) => {
2551
4206
  const server = app.listen(p, h, () => {
2552
4207
  const addr = server.address();
2553
4208
  const actualPort = typeof addr === "object" && addr !== null && "port" in addr ? addr.port : p;
2554
- resolve4({ url: `http://${h}:${actualPort}${path}`, port: actualPort });
4209
+ resolve10({ url: `http://${h}:${actualPort}${path7}`, port: actualPort });
2555
4210
  });
2556
4211
  });
2557
4212
  }
@@ -2563,10 +4218,6 @@ async function runMCPServerOverStdio(runtime, options = {}) {
2563
4218
  return result;
2564
4219
  }
2565
4220
 
2566
- // src/tools/langchain/types.ts
2567
- var LANGCHAIN_KIND = "langchain";
2568
- var LANGCHAIN_DIR_NAME = "langchain";
2569
-
2570
- export { LANGCHAIN_DIR_NAME, LANGCHAIN_KIND, MCP_KIND, PTCRuntime, createHttpService, createMCPServer, createMCPServerStreamableHttp, createMCPStreamableHttpHandler, createRuntimeFromConfig, createRuntimeFromConfigSync, expandToolDescriptorsToRegistryNames, fileDescriptorToPackagePrefix, findAndLoadToolConfig, getDisplayScope, isBarePackageDescriptor, loadToolConfig, npmDescriptorToPackagePrefixWithVersion, resolveSandboxedPath, resolveSandboxedPath2, resolveToolDescriptor, runMCPServerOverStdio, setSandboxValidationEnabled };
2571
- //# sourceMappingURL=chunk-6VU6PRO3.js.map
2572
- //# sourceMappingURL=chunk-6VU6PRO3.js.map
4221
+ export { FUNCTION_KIND, LANGCHAIN_KIND, N8N_KIND, PTCRuntime, SKILL_KIND, createHttpService, createMCPServer, createMCPServerStreamableHttp, createMCPStreamableHttpHandler, createRuntimeFromConfig, createRuntimeFromConfigSync, discoverTools, expandToolDescriptorsToRegistryNames, fileDescriptorToPackagePrefix, findAndLoadToolConfig, getDisplayScope, isBarePackageDescriptor, loadToolConfig, npmDescriptorToPackagePrefixWithVersion, resolveSandboxedPath, resolveSandboxedPath2, resolveToolDescriptor, runMCPServerOverStdio, scan, scanForTools, setSandboxValidationEnabled };
4222
+ //# sourceMappingURL=chunk-XT2CXR6A.js.map
4223
+ //# sourceMappingURL=chunk-XT2CXR6A.js.map