@wingman-ai/gateway 0.3.1 → 0.4.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 (59) hide show
  1. package/dist/agent/config/mcpClientManager.cjs +48 -9
  2. package/dist/agent/config/mcpClientManager.d.ts +12 -0
  3. package/dist/agent/config/mcpClientManager.js +48 -9
  4. package/dist/agent/tests/internet_search.test.cjs +22 -28
  5. package/dist/agent/tests/internet_search.test.js +22 -28
  6. package/dist/agent/tests/mcpClientManager.test.cjs +50 -0
  7. package/dist/agent/tests/mcpClientManager.test.js +50 -0
  8. package/dist/agent/tools/internet_search.cjs +9 -5
  9. package/dist/agent/tools/internet_search.js +9 -5
  10. package/dist/cli/commands/skill.cjs +12 -4
  11. package/dist/cli/commands/skill.js +12 -4
  12. package/dist/cli/config/jsonSchema.cjs +55 -0
  13. package/dist/cli/config/jsonSchema.d.ts +2 -0
  14. package/dist/cli/config/jsonSchema.js +18 -0
  15. package/dist/cli/config/loader.cjs +33 -1
  16. package/dist/cli/config/loader.js +33 -1
  17. package/dist/cli/config/schema.cjs +119 -2
  18. package/dist/cli/config/schema.d.ts +40 -0
  19. package/dist/cli/config/schema.js +119 -2
  20. package/dist/cli/core/agentInvoker.cjs +4 -1
  21. package/dist/cli/core/agentInvoker.d.ts +3 -0
  22. package/dist/cli/core/agentInvoker.js +4 -1
  23. package/dist/cli/services/skillRepository.cjs +138 -20
  24. package/dist/cli/services/skillRepository.d.ts +10 -2
  25. package/dist/cli/services/skillRepository.js +138 -20
  26. package/dist/cli/services/skillSecurityScanner.cjs +158 -0
  27. package/dist/cli/services/skillSecurityScanner.d.ts +28 -0
  28. package/dist/cli/services/skillSecurityScanner.js +121 -0
  29. package/dist/cli/services/skillService.cjs +44 -12
  30. package/dist/cli/services/skillService.d.ts +2 -0
  31. package/dist/cli/services/skillService.js +46 -14
  32. package/dist/cli/types/skill.d.ts +9 -0
  33. package/dist/gateway/server.cjs +5 -1
  34. package/dist/gateway/server.js +5 -1
  35. package/dist/gateway/types.d.ts +9 -0
  36. package/dist/tests/cli-config-loader.test.cjs +33 -1
  37. package/dist/tests/cli-config-loader.test.js +33 -1
  38. package/dist/tests/config-json-schema.test.cjs +25 -0
  39. package/dist/tests/config-json-schema.test.d.ts +1 -0
  40. package/dist/tests/config-json-schema.test.js +19 -0
  41. package/dist/tests/skill-repository.test.cjs +106 -0
  42. package/dist/tests/skill-repository.test.d.ts +1 -0
  43. package/dist/tests/skill-repository.test.js +100 -0
  44. package/dist/tests/skill-security-scanner.test.cjs +126 -0
  45. package/dist/tests/skill-security-scanner.test.d.ts +1 -0
  46. package/dist/tests/skill-security-scanner.test.js +120 -0
  47. package/dist/tests/uv.test.cjs +47 -0
  48. package/dist/tests/uv.test.d.ts +1 -0
  49. package/dist/tests/uv.test.js +41 -0
  50. package/dist/utils/uv.cjs +64 -0
  51. package/dist/utils/uv.d.ts +3 -0
  52. package/dist/utils/uv.js +24 -0
  53. package/dist/webui/assets/index-Cwkg4DKj.css +11 -0
  54. package/dist/webui/assets/{index-C8-oboEC.js → index-DHbfLOUR.js} +21 -19
  55. package/dist/webui/index.html +2 -2
  56. package/package.json +2 -3
  57. package/skills/gog/SKILL.md +36 -0
  58. package/skills/weather/SKILL.md +49 -0
  59. package/dist/webui/assets/index-BW9nM0J2.css +0 -11
@@ -52,15 +52,7 @@ class MCPClientManager {
52
52
  for (const [key, value] of Object.entries(stdioServer.env || {}))resolvedEnv[key] = resolveEnvValue(value);
53
53
  const runtimeEnv = this.applyRuntimeEnv(resolvedEnv);
54
54
  const defaultToolTimeout = getDefaultToolTimeout(stdioServer);
55
- mcpServers[server.name] = {
56
- transport: "stdio",
57
- command: stdioServer.command,
58
- args: stdioServer.args || [],
59
- env: runtimeEnv,
60
- ...void 0 !== defaultToolTimeout ? {
61
- defaultToolTimeout
62
- } : {}
63
- };
55
+ mcpServers[server.name] = this.buildStdioServerConfig(stdioServer, runtimeEnv, defaultToolTimeout);
64
56
  } else if ("sse" === server.transport) {
65
57
  const sseServer = server;
66
58
  const defaultToolTimeout = getDefaultToolTimeout(sseServer);
@@ -83,6 +75,51 @@ class MCPClientManager {
83
75
  }
84
76
  };
85
77
  }
78
+ buildStdioServerConfig(server, env, defaultToolTimeout) {
79
+ const baseConfig = {
80
+ transport: "stdio",
81
+ command: server.command,
82
+ args: server.args || [],
83
+ env,
84
+ ...void 0 !== defaultToolTimeout ? {
85
+ defaultToolTimeout
86
+ } : {}
87
+ };
88
+ if (!this.proxyConfig?.enabled) return baseConfig;
89
+ const proxyCommand = this.proxyConfig.command?.trim() || "uvx";
90
+ const proxyBaseArgs = this.proxyConfig.baseArgs && this.proxyConfig.baseArgs.length > 0 ? this.proxyConfig.baseArgs : [
91
+ "invariant-gateway@latest",
92
+ "mcp"
93
+ ];
94
+ const proxyEnv = {
95
+ ...env
96
+ };
97
+ if (this.proxyConfig.apiKey) proxyEnv.INVARIANT_API_KEY = this.proxyConfig.apiKey;
98
+ if (this.proxyConfig.apiUrl) {
99
+ proxyEnv.INVARIANT_API_URL = this.proxyConfig.apiUrl;
100
+ proxyEnv.GUARDRAILS_API_URL = this.proxyConfig.apiUrl;
101
+ }
102
+ const proxyArgs = [
103
+ ...proxyBaseArgs,
104
+ "--project-name",
105
+ this.proxyConfig.projectName || "wingman-gateway",
106
+ ...this.proxyConfig.pushExplorer ? [
107
+ "--push-explorer"
108
+ ] : [],
109
+ "--exec",
110
+ baseConfig.command,
111
+ ...baseConfig.args || []
112
+ ];
113
+ return {
114
+ transport: "stdio",
115
+ command: proxyCommand,
116
+ args: proxyArgs,
117
+ env: proxyEnv,
118
+ ...void 0 !== defaultToolTimeout ? {
119
+ defaultToolTimeout
120
+ } : {}
121
+ };
122
+ }
86
123
  applyRuntimeEnv(env) {
87
124
  if (!this.executionWorkspace) return env;
88
125
  const next = {
@@ -176,9 +213,11 @@ class MCPClientManager {
176
213
  _define_property(this, "logger", void 0);
177
214
  _define_property(this, "serverConfigs", void 0);
178
215
  _define_property(this, "executionWorkspace", void 0);
216
+ _define_property(this, "proxyConfig", void 0);
179
217
  this.logger = logger;
180
218
  this.serverConfigs = this.mergeConfigs(configs);
181
219
  this.executionWorkspace = options?.executionWorkspace?.trim() || null;
220
+ this.proxyConfig = options?.proxyConfig;
182
221
  }
183
222
  }
184
223
  function resolveEnvValue(value) {
@@ -1,6 +1,15 @@
1
1
  import type { StructuredTool } from "@langchain/core/tools";
2
2
  import type { Logger } from "@/logger.js";
3
3
  import type { MCPServersConfig } from "@/types/mcp.js";
4
+ export type MCPProxyConfig = {
5
+ enabled?: boolean;
6
+ command?: string;
7
+ baseArgs?: string[];
8
+ projectName?: string;
9
+ pushExplorer?: boolean;
10
+ apiKey?: string;
11
+ apiUrl?: string;
12
+ };
4
13
  /**
5
14
  * Manages MCP server connections and tool retrieval
6
15
  * Handles server lifecycle: initialization, tool loading, and cleanup
@@ -10,8 +19,10 @@ export declare class MCPClientManager {
10
19
  private logger;
11
20
  private serverConfigs;
12
21
  private executionWorkspace;
22
+ private proxyConfig;
13
23
  constructor(configs: MCPServersConfig[], logger: Logger, options?: {
14
24
  executionWorkspace?: string | null;
25
+ proxyConfig?: MCPProxyConfig;
15
26
  });
16
27
  /**
17
28
  * Merge multiple MCP configurations (global + agent-specific)
@@ -22,6 +33,7 @@ export declare class MCPClientManager {
22
33
  * Convert Wingman MCP config to MultiServerMCPClient format
23
34
  */
24
35
  private buildClientConfig;
36
+ private buildStdioServerConfig;
25
37
  private applyRuntimeEnv;
26
38
  /**
27
39
  * Initialize MCP client and connect to servers
@@ -24,15 +24,7 @@ class MCPClientManager {
24
24
  for (const [key, value] of Object.entries(stdioServer.env || {}))resolvedEnv[key] = resolveEnvValue(value);
25
25
  const runtimeEnv = this.applyRuntimeEnv(resolvedEnv);
26
26
  const defaultToolTimeout = getDefaultToolTimeout(stdioServer);
27
- mcpServers[server.name] = {
28
- transport: "stdio",
29
- command: stdioServer.command,
30
- args: stdioServer.args || [],
31
- env: runtimeEnv,
32
- ...void 0 !== defaultToolTimeout ? {
33
- defaultToolTimeout
34
- } : {}
35
- };
27
+ mcpServers[server.name] = this.buildStdioServerConfig(stdioServer, runtimeEnv, defaultToolTimeout);
36
28
  } else if ("sse" === server.transport) {
37
29
  const sseServer = server;
38
30
  const defaultToolTimeout = getDefaultToolTimeout(sseServer);
@@ -55,6 +47,51 @@ class MCPClientManager {
55
47
  }
56
48
  };
57
49
  }
50
+ buildStdioServerConfig(server, env, defaultToolTimeout) {
51
+ const baseConfig = {
52
+ transport: "stdio",
53
+ command: server.command,
54
+ args: server.args || [],
55
+ env,
56
+ ...void 0 !== defaultToolTimeout ? {
57
+ defaultToolTimeout
58
+ } : {}
59
+ };
60
+ if (!this.proxyConfig?.enabled) return baseConfig;
61
+ const proxyCommand = this.proxyConfig.command?.trim() || "uvx";
62
+ const proxyBaseArgs = this.proxyConfig.baseArgs && this.proxyConfig.baseArgs.length > 0 ? this.proxyConfig.baseArgs : [
63
+ "invariant-gateway@latest",
64
+ "mcp"
65
+ ];
66
+ const proxyEnv = {
67
+ ...env
68
+ };
69
+ if (this.proxyConfig.apiKey) proxyEnv.INVARIANT_API_KEY = this.proxyConfig.apiKey;
70
+ if (this.proxyConfig.apiUrl) {
71
+ proxyEnv.INVARIANT_API_URL = this.proxyConfig.apiUrl;
72
+ proxyEnv.GUARDRAILS_API_URL = this.proxyConfig.apiUrl;
73
+ }
74
+ const proxyArgs = [
75
+ ...proxyBaseArgs,
76
+ "--project-name",
77
+ this.proxyConfig.projectName || "wingman-gateway",
78
+ ...this.proxyConfig.pushExplorer ? [
79
+ "--push-explorer"
80
+ ] : [],
81
+ "--exec",
82
+ baseConfig.command,
83
+ ...baseConfig.args || []
84
+ ];
85
+ return {
86
+ transport: "stdio",
87
+ command: proxyCommand,
88
+ args: proxyArgs,
89
+ env: proxyEnv,
90
+ ...void 0 !== defaultToolTimeout ? {
91
+ defaultToolTimeout
92
+ } : {}
93
+ };
94
+ }
58
95
  applyRuntimeEnv(env) {
59
96
  if (!this.executionWorkspace) return env;
60
97
  const next = {
@@ -148,9 +185,11 @@ class MCPClientManager {
148
185
  _define_property(this, "logger", void 0);
149
186
  _define_property(this, "serverConfigs", void 0);
150
187
  _define_property(this, "executionWorkspace", void 0);
188
+ _define_property(this, "proxyConfig", void 0);
151
189
  this.logger = logger;
152
190
  this.serverConfigs = this.mergeConfigs(configs);
153
191
  this.executionWorkspace = options?.executionWorkspace?.trim() || null;
192
+ this.proxyConfig = options?.proxyConfig;
154
193
  }
155
194
  }
156
195
  function resolveEnvValue(value) {
@@ -1,16 +1,6 @@
1
1
  "use strict";
2
2
  var __webpack_exports__ = {};
3
3
  const external_vitest_namespaceObject = require("vitest");
4
- function _define_property(obj, key, value) {
5
- if (key in obj) Object.defineProperty(obj, key, {
6
- value: value,
7
- enumerable: true,
8
- configurable: true,
9
- writable: true
10
- });
11
- else obj[key] = value;
12
- return obj;
13
- }
14
4
  const originalEnv = {
15
5
  ...process.env
16
6
  };
@@ -22,18 +12,14 @@ const loadSearchModule = async (overrides)=>{
22
12
  for (const [key, value] of Object.entries(overrides))if (void 0 === value) delete process.env[key];
23
13
  else process.env[key] = value;
24
14
  external_vitest_namespaceObject.vi.resetModules();
25
- const callMock = external_vitest_namespaceObject.vi.fn();
26
- external_vitest_namespaceObject.vi.doMock("@langchain/community/tools/duckduckgo_search", ()=>({
27
- DuckDuckGoSearch: class {
28
- constructor(){
29
- _define_property(this, "_call", callMock);
30
- }
31
- }
15
+ const searchMock = external_vitest_namespaceObject.vi.fn();
16
+ external_vitest_namespaceObject.vi.doMock("duck-duck-scrape", ()=>({
17
+ search: searchMock
32
18
  }));
33
19
  const module = await import("../tools/internet_search.cjs");
34
20
  return {
35
21
  ...module,
36
- callMock
22
+ searchMock
37
23
  };
38
24
  };
39
25
  (0, external_vitest_namespaceObject.beforeEach)(()=>{
@@ -47,13 +33,21 @@ const loadSearchModule = async (overrides)=>{
47
33
  });
48
34
  (0, external_vitest_namespaceObject.describe)("Internet search tool (DuckDuckGo)", ()=>{
49
35
  (0, external_vitest_namespaceObject.it)("retries on DDG anomaly and succeeds", async ()=>{
50
- const { createInternetSearchTool, callMock } = await loadSearchModule({
36
+ const { createInternetSearchTool, searchMock } = await loadSearchModule({
51
37
  WINGMAN_DDG_MIN_DELAY_MS: "0",
52
38
  WINGMAN_DDG_BACKOFF_BASE_MS: "1",
53
39
  WINGMAN_DDG_BACKOFF_MAX_MS: "1",
54
40
  WINGMAN_DDG_MAX_RETRIES: "2"
55
41
  });
56
- callMock.mockRejectedValueOnce(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly.")).mockResolvedValueOnce("ok");
42
+ searchMock.mockRejectedValueOnce(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly.")).mockResolvedValueOnce({
43
+ results: [
44
+ {
45
+ title: "Wingman",
46
+ url: "https://wingman.ai",
47
+ description: "Agentic coding assistant"
48
+ }
49
+ ]
50
+ });
57
51
  const tool = createInternetSearchTool({
58
52
  provider: "duckduckgo",
59
53
  maxResults: 3
@@ -61,19 +55,19 @@ const loadSearchModule = async (overrides)=>{
61
55
  const resultPromise = tool.invoke({
62
56
  query: "test"
63
57
  });
64
- const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).resolves.toBe("ok");
58
+ const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).resolves.toContain("Wingman");
65
59
  await external_vitest_namespaceObject.vi.runAllTimersAsync();
66
60
  await expectation;
67
- (0, external_vitest_namespaceObject.expect)(callMock).toHaveBeenCalledTimes(2);
61
+ (0, external_vitest_namespaceObject.expect)(searchMock).toHaveBeenCalledTimes(2);
68
62
  });
69
63
  (0, external_vitest_namespaceObject.it)("throws a friendly error after retry exhaustion", async ()=>{
70
- const { createInternetSearchTool, callMock } = await loadSearchModule({
64
+ const { createInternetSearchTool, searchMock } = await loadSearchModule({
71
65
  WINGMAN_DDG_MIN_DELAY_MS: "0",
72
66
  WINGMAN_DDG_BACKOFF_BASE_MS: "1",
73
67
  WINGMAN_DDG_BACKOFF_MAX_MS: "1",
74
68
  WINGMAN_DDG_MAX_RETRIES: "1"
75
69
  });
76
- callMock.mockRejectedValue(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly."));
70
+ searchMock.mockRejectedValue(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly."));
77
71
  const tool = createInternetSearchTool({
78
72
  provider: "duckduckgo",
79
73
  maxResults: 3
@@ -84,16 +78,16 @@ const loadSearchModule = async (overrides)=>{
84
78
  const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).rejects.toThrow(/rate-limited|anomaly/i);
85
79
  await external_vitest_namespaceObject.vi.runAllTimersAsync();
86
80
  await expectation;
87
- (0, external_vitest_namespaceObject.expect)(callMock).toHaveBeenCalledTimes(2);
81
+ (0, external_vitest_namespaceObject.expect)(searchMock).toHaveBeenCalledTimes(2);
88
82
  });
89
83
  (0, external_vitest_namespaceObject.it)("does not retry on non-anomaly errors", async ()=>{
90
- const { createInternetSearchTool, callMock } = await loadSearchModule({
84
+ const { createInternetSearchTool, searchMock } = await loadSearchModule({
91
85
  WINGMAN_DDG_MIN_DELAY_MS: "0",
92
86
  WINGMAN_DDG_BACKOFF_BASE_MS: "1",
93
87
  WINGMAN_DDG_BACKOFF_MAX_MS: "1",
94
88
  WINGMAN_DDG_MAX_RETRIES: "2"
95
89
  });
96
- callMock.mockRejectedValue(new Error("network down"));
90
+ searchMock.mockRejectedValue(new Error("network down"));
97
91
  const tool = createInternetSearchTool({
98
92
  provider: "duckduckgo",
99
93
  maxResults: 3
@@ -104,7 +98,7 @@ const loadSearchModule = async (overrides)=>{
104
98
  const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).rejects.toThrow("network down");
105
99
  await external_vitest_namespaceObject.vi.runAllTimersAsync();
106
100
  await expectation;
107
- (0, external_vitest_namespaceObject.expect)(callMock).toHaveBeenCalledTimes(1);
101
+ (0, external_vitest_namespaceObject.expect)(searchMock).toHaveBeenCalledTimes(1);
108
102
  });
109
103
  });
110
104
  for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
@@ -1,14 +1,4 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- function _define_property(obj, key, value) {
3
- if (key in obj) Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- else obj[key] = value;
10
- return obj;
11
- }
12
2
  const originalEnv = {
13
3
  ...process.env
14
4
  };
@@ -20,18 +10,14 @@ const loadSearchModule = async (overrides)=>{
20
10
  for (const [key, value] of Object.entries(overrides))if (void 0 === value) delete process.env[key];
21
11
  else process.env[key] = value;
22
12
  vi.resetModules();
23
- const callMock = vi.fn();
24
- vi.doMock("@langchain/community/tools/duckduckgo_search", ()=>({
25
- DuckDuckGoSearch: class {
26
- constructor(){
27
- _define_property(this, "_call", callMock);
28
- }
29
- }
13
+ const searchMock = vi.fn();
14
+ vi.doMock("duck-duck-scrape", ()=>({
15
+ search: searchMock
30
16
  }));
31
17
  const module = await import("../tools/internet_search.js");
32
18
  return {
33
19
  ...module,
34
- callMock
20
+ searchMock
35
21
  };
36
22
  };
37
23
  beforeEach(()=>{
@@ -45,13 +31,21 @@ afterEach(()=>{
45
31
  });
46
32
  describe("Internet search tool (DuckDuckGo)", ()=>{
47
33
  it("retries on DDG anomaly and succeeds", async ()=>{
48
- const { createInternetSearchTool, callMock } = await loadSearchModule({
34
+ const { createInternetSearchTool, searchMock } = await loadSearchModule({
49
35
  WINGMAN_DDG_MIN_DELAY_MS: "0",
50
36
  WINGMAN_DDG_BACKOFF_BASE_MS: "1",
51
37
  WINGMAN_DDG_BACKOFF_MAX_MS: "1",
52
38
  WINGMAN_DDG_MAX_RETRIES: "2"
53
39
  });
54
- callMock.mockRejectedValueOnce(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly.")).mockResolvedValueOnce("ok");
40
+ searchMock.mockRejectedValueOnce(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly.")).mockResolvedValueOnce({
41
+ results: [
42
+ {
43
+ title: "Wingman",
44
+ url: "https://wingman.ai",
45
+ description: "Agentic coding assistant"
46
+ }
47
+ ]
48
+ });
55
49
  const tool = createInternetSearchTool({
56
50
  provider: "duckduckgo",
57
51
  maxResults: 3
@@ -59,19 +53,19 @@ describe("Internet search tool (DuckDuckGo)", ()=>{
59
53
  const resultPromise = tool.invoke({
60
54
  query: "test"
61
55
  });
62
- const expectation = expect(resultPromise).resolves.toBe("ok");
56
+ const expectation = expect(resultPromise).resolves.toContain("Wingman");
63
57
  await vi.runAllTimersAsync();
64
58
  await expectation;
65
- expect(callMock).toHaveBeenCalledTimes(2);
59
+ expect(searchMock).toHaveBeenCalledTimes(2);
66
60
  });
67
61
  it("throws a friendly error after retry exhaustion", async ()=>{
68
- const { createInternetSearchTool, callMock } = await loadSearchModule({
62
+ const { createInternetSearchTool, searchMock } = await loadSearchModule({
69
63
  WINGMAN_DDG_MIN_DELAY_MS: "0",
70
64
  WINGMAN_DDG_BACKOFF_BASE_MS: "1",
71
65
  WINGMAN_DDG_BACKOFF_MAX_MS: "1",
72
66
  WINGMAN_DDG_MAX_RETRIES: "1"
73
67
  });
74
- callMock.mockRejectedValue(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly."));
68
+ searchMock.mockRejectedValue(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly."));
75
69
  const tool = createInternetSearchTool({
76
70
  provider: "duckduckgo",
77
71
  maxResults: 3
@@ -82,16 +76,16 @@ describe("Internet search tool (DuckDuckGo)", ()=>{
82
76
  const expectation = expect(resultPromise).rejects.toThrow(/rate-limited|anomaly/i);
83
77
  await vi.runAllTimersAsync();
84
78
  await expectation;
85
- expect(callMock).toHaveBeenCalledTimes(2);
79
+ expect(searchMock).toHaveBeenCalledTimes(2);
86
80
  });
87
81
  it("does not retry on non-anomaly errors", async ()=>{
88
- const { createInternetSearchTool, callMock } = await loadSearchModule({
82
+ const { createInternetSearchTool, searchMock } = await loadSearchModule({
89
83
  WINGMAN_DDG_MIN_DELAY_MS: "0",
90
84
  WINGMAN_DDG_BACKOFF_BASE_MS: "1",
91
85
  WINGMAN_DDG_BACKOFF_MAX_MS: "1",
92
86
  WINGMAN_DDG_MAX_RETRIES: "2"
93
87
  });
94
- callMock.mockRejectedValue(new Error("network down"));
88
+ searchMock.mockRejectedValue(new Error("network down"));
95
89
  const tool = createInternetSearchTool({
96
90
  provider: "duckduckgo",
97
91
  maxResults: 3
@@ -102,6 +96,6 @@ describe("Internet search tool (DuckDuckGo)", ()=>{
102
96
  const expectation = expect(resultPromise).rejects.toThrow("network down");
103
97
  await vi.runAllTimersAsync();
104
98
  await expectation;
105
- expect(callMock).toHaveBeenCalledTimes(1);
99
+ expect(searchMock).toHaveBeenCalledTimes(1);
106
100
  });
107
101
  });
@@ -117,6 +117,56 @@ const getClientConfig = (manager)=>manager.buildClientConfig();
117
117
  const clientConfig = getClientConfig(manager);
118
118
  (0, external_vitest_namespaceObject.expect)(clientConfig.mcpServers["fal-ai"].defaultToolTimeout).toBe(300000);
119
119
  });
120
+ (0, external_vitest_namespaceObject.it)("wraps stdio servers with proxy command when enabled", ()=>{
121
+ const configs = [
122
+ {
123
+ servers: [
124
+ {
125
+ name: "fal-ai",
126
+ transport: "stdio",
127
+ command: "bun",
128
+ args: [
129
+ "run",
130
+ "src/tools/mcp-fal-ai.ts"
131
+ ],
132
+ env: {
133
+ EXISTING: "value"
134
+ }
135
+ }
136
+ ]
137
+ }
138
+ ];
139
+ const manager = new mcpClientManager_cjs_namespaceObject.MCPClientManager(configs, testLogger, {
140
+ proxyConfig: {
141
+ enabled: true,
142
+ command: "uvx",
143
+ baseArgs: [
144
+ "invariant-gateway@latest",
145
+ "mcp"
146
+ ],
147
+ projectName: "wingman-gateway",
148
+ apiKey: "test-api-key",
149
+ apiUrl: "https://explorer.invariantlabs.ai"
150
+ }
151
+ });
152
+ const clientConfig = getClientConfig(manager);
153
+ const server = clientConfig.mcpServers["fal-ai"];
154
+ (0, external_vitest_namespaceObject.expect)(server.command).toBe("uvx");
155
+ (0, external_vitest_namespaceObject.expect)(server.args).toEqual([
156
+ "invariant-gateway@latest",
157
+ "mcp",
158
+ "--project-name",
159
+ "wingman-gateway",
160
+ "--exec",
161
+ "bun",
162
+ "run",
163
+ "src/tools/mcp-fal-ai.ts"
164
+ ]);
165
+ (0, external_vitest_namespaceObject.expect)(server.env.EXISTING).toBe("value");
166
+ (0, external_vitest_namespaceObject.expect)(server.env.INVARIANT_API_KEY).toBe("test-api-key");
167
+ (0, external_vitest_namespaceObject.expect)(server.env.INVARIANT_API_URL).toBe("https://explorer.invariantlabs.ai");
168
+ (0, external_vitest_namespaceObject.expect)(server.env.GUARDRAILS_API_URL).toBe("https://explorer.invariantlabs.ai");
169
+ });
120
170
  });
121
171
  for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
122
172
  Object.defineProperty(exports, '__esModule', {
@@ -115,4 +115,54 @@ describe("MCPClientManager runtime env", ()=>{
115
115
  const clientConfig = getClientConfig(manager);
116
116
  expect(clientConfig.mcpServers["fal-ai"].defaultToolTimeout).toBe(300000);
117
117
  });
118
+ it("wraps stdio servers with proxy command when enabled", ()=>{
119
+ const configs = [
120
+ {
121
+ servers: [
122
+ {
123
+ name: "fal-ai",
124
+ transport: "stdio",
125
+ command: "bun",
126
+ args: [
127
+ "run",
128
+ "src/tools/mcp-fal-ai.ts"
129
+ ],
130
+ env: {
131
+ EXISTING: "value"
132
+ }
133
+ }
134
+ ]
135
+ }
136
+ ];
137
+ const manager = new MCPClientManager(configs, testLogger, {
138
+ proxyConfig: {
139
+ enabled: true,
140
+ command: "uvx",
141
+ baseArgs: [
142
+ "invariant-gateway@latest",
143
+ "mcp"
144
+ ],
145
+ projectName: "wingman-gateway",
146
+ apiKey: "test-api-key",
147
+ apiUrl: "https://explorer.invariantlabs.ai"
148
+ }
149
+ });
150
+ const clientConfig = getClientConfig(manager);
151
+ const server = clientConfig.mcpServers["fal-ai"];
152
+ expect(server.command).toBe("uvx");
153
+ expect(server.args).toEqual([
154
+ "invariant-gateway@latest",
155
+ "mcp",
156
+ "--project-name",
157
+ "wingman-gateway",
158
+ "--exec",
159
+ "bun",
160
+ "run",
161
+ "src/tools/mcp-fal-ai.ts"
162
+ ]);
163
+ expect(server.env.EXISTING).toBe("value");
164
+ expect(server.env.INVARIANT_API_KEY).toBe("test-api-key");
165
+ expect(server.env.INVARIANT_API_URL).toBe("https://explorer.invariantlabs.ai");
166
+ expect(server.env.GUARDRAILS_API_URL).toBe("https://explorer.invariantlabs.ai");
167
+ });
118
168
  });
@@ -28,7 +28,7 @@ __webpack_require__.d(__webpack_exports__, {
28
28
  internetSearch: ()=>internetSearch
29
29
  });
30
30
  const external_langchain_namespaceObject = require("langchain");
31
- const duckduckgo_search_namespaceObject = require("@langchain/community/tools/duckduckgo_search");
31
+ const external_duck_duck_scrape_namespaceObject = require("duck-duck-scrape");
32
32
  const openai_namespaceObject = require("@langchain/openai");
33
33
  const external_zod_namespaceObject = require("zod");
34
34
  function _define_property(obj, key, value) {
@@ -103,13 +103,17 @@ const formatDdgError = (error, attempts)=>new Error(`DuckDuckGo search was rate-
103
103
  cause: error
104
104
  });
105
105
  async function runDuckDuckGoSearch(query, maxResults) {
106
- const search = new duckduckgo_search_namespaceObject.DuckDuckGoSearch({
107
- maxResults
108
- });
109
106
  let attempt = 0;
110
107
  let delayMs = ddgBackoffBaseMs;
111
108
  while(true)try {
112
- return await ddgRateLimiter.run(()=>search._call(query));
109
+ return await ddgRateLimiter.run(async ()=>{
110
+ const { results } = await (0, external_duck_duck_scrape_namespaceObject.search)(query);
111
+ return JSON.stringify(results.map((result)=>({
112
+ title: result.title,
113
+ link: result.url,
114
+ snippet: result.description
115
+ })).slice(0, maxResults));
116
+ });
113
117
  } catch (error) {
114
118
  const shouldRetry = isDdgAnomalyError(error);
115
119
  if (!shouldRetry || attempt >= ddgMaxRetries) {
@@ -1,5 +1,5 @@
1
1
  import { tool } from "langchain";
2
- import { DuckDuckGoSearch } from "@langchain/community/tools/duckduckgo_search";
2
+ import { search } from "duck-duck-scrape";
3
3
  import { ChatOpenAI } from "@langchain/openai";
4
4
  import { number, object, string } from "zod";
5
5
  function _define_property(obj, key, value) {
@@ -74,13 +74,17 @@ const formatDdgError = (error, attempts)=>new Error(`DuckDuckGo search was rate-
74
74
  cause: error
75
75
  });
76
76
  async function runDuckDuckGoSearch(query, maxResults) {
77
- const search = new DuckDuckGoSearch({
78
- maxResults
79
- });
80
77
  let attempt = 0;
81
78
  let delayMs = ddgBackoffBaseMs;
82
79
  while(true)try {
83
- return await ddgRateLimiter.run(()=>search._call(query));
80
+ return await ddgRateLimiter.run(async ()=>{
81
+ const { results } = await search(query);
82
+ return JSON.stringify(results.map((result)=>({
83
+ title: result.title,
84
+ link: result.url,
85
+ snippet: result.description
86
+ })).slice(0, maxResults));
87
+ });
84
88
  } catch (error) {
85
89
  const shouldRetry = isDdgAnomalyError(error);
86
90
  if (!shouldRetry || attempt >= ddgMaxRetries) {
@@ -41,14 +41,17 @@ async function executeSkillCommand(args, options = {}) {
41
41
  const config = configLoader.loadConfig();
42
42
  try {
43
43
  const repository = new skillRepository_cjs_namespaceObject.SkillRepository({
44
+ provider: config.skills?.provider,
44
45
  repositoryOwner: config.skills?.repositoryOwner,
45
46
  repositoryName: config.skills?.repositoryName,
46
- githubToken: config.skills?.githubToken
47
+ githubToken: config.skills?.githubToken,
48
+ clawhubBaseUrl: config.skills?.clawhubBaseUrl
47
49
  });
48
50
  const service = new skillService_cjs_namespaceObject.SkillService(repository, outputManager, logger, {
49
51
  workspace,
50
52
  skillsDirectory: config.skills?.skillsDirectory,
51
- outputMode: args.outputMode
53
+ outputMode: args.outputMode,
54
+ security: config.skills?.security
52
55
  });
53
56
  const subcommand = args.subcommand;
54
57
  const subcommandArgs = args.args;
@@ -100,7 +103,7 @@ async function executeSkillCommand(args, options = {}) {
100
103
  }
101
104
  function showSkillHelp(outputManager) {
102
105
  if ("interactive" === outputManager.getMode()) console.log(`
103
- Wingman Skill Manager - Install skills from the Anthropic skills repository
106
+ Wingman Skill Manager - Install skills from configured registries
104
107
 
105
108
  Usage:
106
109
  wingman skill browse Browse available skills
@@ -123,10 +126,15 @@ Configuration:
123
126
  Skills can be configured in .wingman/wingman.config.json:
124
127
  {
125
128
  "skills": {
129
+ "provider": "github",
126
130
  "repositoryOwner": "anthropics",
127
131
  "repositoryName": "skills",
128
132
  "githubToken": "optional-token",
129
- "skillsDirectory": "skills"
133
+ "clawhubBaseUrl": "https://clawhub.ai",
134
+ "skillsDirectory": "skills",
135
+ "security": {
136
+ "scanOnInstall": true
137
+ }
130
138
  }
131
139
  }
132
140
  `);