@wingman-ai/gateway 0.3.2 → 0.4.1

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 (103) hide show
  1. package/README.md +29 -111
  2. package/dist/agent/config/agentConfig.cjs +2 -0
  3. package/dist/agent/config/agentConfig.d.ts +6 -0
  4. package/dist/agent/config/agentConfig.js +2 -0
  5. package/dist/agent/config/agentLoader.cjs +21 -18
  6. package/dist/agent/config/agentLoader.js +22 -19
  7. package/dist/agent/config/mcpClientManager.cjs +48 -9
  8. package/dist/agent/config/mcpClientManager.d.ts +12 -0
  9. package/dist/agent/config/mcpClientManager.js +48 -9
  10. package/dist/agent/config/toolRegistry.cjs +19 -0
  11. package/dist/agent/config/toolRegistry.d.ts +4 -0
  12. package/dist/agent/config/toolRegistry.js +17 -1
  13. package/dist/agent/middleware/additional-messages.cjs +115 -11
  14. package/dist/agent/middleware/additional-messages.d.ts +9 -0
  15. package/dist/agent/middleware/additional-messages.js +115 -11
  16. package/dist/agent/tests/agentLoader.test.cjs +45 -0
  17. package/dist/agent/tests/agentLoader.test.js +45 -0
  18. package/dist/agent/tests/mcpClientManager.test.cjs +50 -0
  19. package/dist/agent/tests/mcpClientManager.test.js +50 -0
  20. package/dist/agent/tests/toolRegistry.test.cjs +2 -0
  21. package/dist/agent/tests/toolRegistry.test.js +2 -0
  22. package/dist/agent/tools/node_invoke.cjs +146 -0
  23. package/dist/agent/tools/node_invoke.d.ts +86 -0
  24. package/dist/agent/tools/node_invoke.js +109 -0
  25. package/dist/cli/commands/gateway.cjs +1 -1
  26. package/dist/cli/commands/gateway.js +1 -1
  27. package/dist/cli/commands/skill.cjs +12 -4
  28. package/dist/cli/commands/skill.js +12 -4
  29. package/dist/cli/config/jsonSchema.cjs +55 -0
  30. package/dist/cli/config/jsonSchema.d.ts +2 -0
  31. package/dist/cli/config/jsonSchema.js +18 -0
  32. package/dist/cli/config/loader.cjs +33 -1
  33. package/dist/cli/config/loader.js +33 -1
  34. package/dist/cli/config/schema.cjs +119 -2
  35. package/dist/cli/config/schema.d.ts +40 -0
  36. package/dist/cli/config/schema.js +119 -2
  37. package/dist/cli/core/agentInvoker.cjs +25 -4
  38. package/dist/cli/core/agentInvoker.d.ts +13 -0
  39. package/dist/cli/core/agentInvoker.js +25 -4
  40. package/dist/cli/services/skillRepository.cjs +138 -20
  41. package/dist/cli/services/skillRepository.d.ts +10 -2
  42. package/dist/cli/services/skillRepository.js +138 -20
  43. package/dist/cli/services/skillSecurityScanner.cjs +158 -0
  44. package/dist/cli/services/skillSecurityScanner.d.ts +28 -0
  45. package/dist/cli/services/skillSecurityScanner.js +121 -0
  46. package/dist/cli/services/skillService.cjs +44 -12
  47. package/dist/cli/services/skillService.d.ts +2 -0
  48. package/dist/cli/services/skillService.js +46 -14
  49. package/dist/cli/types/skill.d.ts +9 -0
  50. package/dist/gateway/http/nodes.cjs +247 -0
  51. package/dist/gateway/http/nodes.d.ts +20 -0
  52. package/dist/gateway/http/nodes.js +210 -0
  53. package/dist/gateway/node.cjs +10 -1
  54. package/dist/gateway/node.d.ts +10 -1
  55. package/dist/gateway/node.js +10 -1
  56. package/dist/gateway/server.cjs +418 -27
  57. package/dist/gateway/server.d.ts +34 -0
  58. package/dist/gateway/server.js +412 -27
  59. package/dist/gateway/types.d.ts +15 -1
  60. package/dist/gateway/validation.cjs +2 -0
  61. package/dist/gateway/validation.d.ts +4 -0
  62. package/dist/gateway/validation.js +2 -0
  63. package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
  64. package/dist/tests/additionalMessageMiddleware.test.js +92 -0
  65. package/dist/tests/cli-config-loader.test.cjs +33 -1
  66. package/dist/tests/cli-config-loader.test.js +33 -1
  67. package/dist/tests/config-json-schema.test.cjs +25 -0
  68. package/dist/tests/config-json-schema.test.d.ts +1 -0
  69. package/dist/tests/config-json-schema.test.js +19 -0
  70. package/dist/tests/gateway-http-security.test.cjs +277 -0
  71. package/dist/tests/gateway-http-security.test.d.ts +1 -0
  72. package/dist/tests/gateway-http-security.test.js +271 -0
  73. package/dist/tests/gateway-node-mode.test.cjs +174 -0
  74. package/dist/tests/gateway-node-mode.test.d.ts +1 -0
  75. package/dist/tests/gateway-node-mode.test.js +168 -0
  76. package/dist/tests/gateway-origin-policy.test.cjs +60 -0
  77. package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
  78. package/dist/tests/gateway-origin-policy.test.js +54 -0
  79. package/dist/tests/gateway.test.cjs +1 -0
  80. package/dist/tests/gateway.test.js +1 -0
  81. package/dist/tests/node-tools.test.cjs +77 -0
  82. package/dist/tests/node-tools.test.d.ts +1 -0
  83. package/dist/tests/node-tools.test.js +71 -0
  84. package/dist/tests/nodes-api.test.cjs +86 -0
  85. package/dist/tests/nodes-api.test.d.ts +1 -0
  86. package/dist/tests/nodes-api.test.js +80 -0
  87. package/dist/tests/skill-repository.test.cjs +106 -0
  88. package/dist/tests/skill-repository.test.d.ts +1 -0
  89. package/dist/tests/skill-repository.test.js +100 -0
  90. package/dist/tests/skill-security-scanner.test.cjs +126 -0
  91. package/dist/tests/skill-security-scanner.test.d.ts +1 -0
  92. package/dist/tests/skill-security-scanner.test.js +120 -0
  93. package/dist/tests/uv.test.cjs +47 -0
  94. package/dist/tests/uv.test.d.ts +1 -0
  95. package/dist/tests/uv.test.js +41 -0
  96. package/dist/utils/uv.cjs +64 -0
  97. package/dist/utils/uv.d.ts +3 -0
  98. package/dist/utils/uv.js +24 -0
  99. package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
  100. package/dist/webui/index.html +1 -1
  101. package/package.json +2 -1
  102. package/skills/gog/SKILL.md +36 -0
  103. package/skills/weather/SKILL.md +49 -0
@@ -0,0 +1,210 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const CLIENT_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/;
4
+ const normalizeClientId = (raw)=>{
5
+ let decoded;
6
+ try {
7
+ decoded = decodeURIComponent(raw);
8
+ } catch {
9
+ return null;
10
+ }
11
+ const trimmed = decoded.trim();
12
+ if (!trimmed) return null;
13
+ if (!CLIENT_ID_PATTERN.test(trimmed)) return null;
14
+ return trimmed;
15
+ };
16
+ const createNodeApprovalStore = (resolveConfigDirPath)=>{
17
+ const resolvePath = ()=>{
18
+ const configDir = resolveConfigDirPath();
19
+ mkdirSync(configDir, {
20
+ recursive: true
21
+ });
22
+ return join(configDir, "nodes.json");
23
+ };
24
+ const readRecords = ()=>{
25
+ const path = resolvePath();
26
+ if (!existsSync(path)) return [];
27
+ try {
28
+ const raw = readFileSync(path, "utf-8");
29
+ const parsed = JSON.parse(raw);
30
+ if (!Array.isArray(parsed)) return [];
31
+ const records = [];
32
+ for (const entry of parsed){
33
+ if (!entry || "object" != typeof entry) continue;
34
+ const typed = entry;
35
+ const clientId = "string" == typeof typed.clientId ? typed.clientId : "";
36
+ if (clientId.trim()) records.push({
37
+ clientId: clientId.trim(),
38
+ name: "string" == typeof typed.name && typed.name.trim() ? typed.name.trim() : void 0,
39
+ enabled: false !== typed.enabled,
40
+ createdAt: "number" == typeof typed.createdAt ? typed.createdAt : Date.now(),
41
+ updatedAt: "number" == typeof typed.updatedAt ? typed.updatedAt : Date.now(),
42
+ lastSeenAt: "number" == typeof typed.lastSeenAt ? typed.lastSeenAt : void 0
43
+ });
44
+ }
45
+ return records;
46
+ } catch {
47
+ return [];
48
+ }
49
+ };
50
+ const writeRecords = (records)=>{
51
+ const path = resolvePath();
52
+ writeFileSync(path, JSON.stringify(records, null, 2));
53
+ };
54
+ const replaceRecord = (records, nextRecord)=>{
55
+ const index = records.findIndex((record)=>record.clientId === nextRecord.clientId);
56
+ if (index >= 0) records[index] = nextRecord;
57
+ else records.unshift(nextRecord);
58
+ return records;
59
+ };
60
+ return {
61
+ load: ()=>readRecords(),
62
+ save: (records)=>writeRecords(records),
63
+ isEnabled: (clientId)=>{
64
+ const trimmed = clientId.trim();
65
+ if (!trimmed) return false;
66
+ return readRecords().some((record)=>record.clientId === trimmed && record.enabled);
67
+ },
68
+ setEnabled: (clientId, enabled, name, lastSeenAt)=>{
69
+ const trimmed = clientId.trim();
70
+ const now = Date.now();
71
+ const records = readRecords();
72
+ const existing = records.find((record)=>record.clientId === trimmed);
73
+ const nextRecord = {
74
+ clientId: trimmed,
75
+ name: "string" == typeof name && name.trim() ? name.trim() : existing?.name,
76
+ enabled,
77
+ createdAt: existing?.createdAt ?? now,
78
+ updatedAt: now,
79
+ lastSeenAt: "number" == typeof lastSeenAt ? lastSeenAt : existing?.lastSeenAt
80
+ };
81
+ writeRecords(replaceRecord(records, nextRecord));
82
+ return nextRecord;
83
+ },
84
+ markSeen: (clientId, name)=>{
85
+ const trimmed = clientId.trim();
86
+ if (!trimmed) return null;
87
+ const records = readRecords();
88
+ const existing = records.find((record)=>record.clientId === trimmed);
89
+ if (!existing) return null;
90
+ const now = Date.now();
91
+ const nextRecord = {
92
+ ...existing,
93
+ name: "string" == typeof name && name.trim() ? name.trim() : existing.name,
94
+ updatedAt: now,
95
+ lastSeenAt: now
96
+ };
97
+ writeRecords(replaceRecord(records, nextRecord));
98
+ return nextRecord;
99
+ }
100
+ };
101
+ };
102
+ const handleNodesApi = async (_ctx, nodeManager, store, req, url)=>{
103
+ if ("/api/nodes" === url.pathname && "GET" === req.method) {
104
+ const approvals = store.load();
105
+ const connectedNodes = nodeManager.getAllNodes();
106
+ const connectedByClientId = new Map();
107
+ for (const node of connectedNodes){
108
+ if (!node.clientId) continue;
109
+ const bucket = connectedByClientId.get(node.clientId) || [];
110
+ bucket.push(node);
111
+ connectedByClientId.set(node.clientId, bucket);
112
+ }
113
+ const clientIds = new Set([
114
+ ...approvals.map((record)=>record.clientId),
115
+ ...connectedByClientId.keys()
116
+ ]);
117
+ const nodes = Array.from(clientIds).map((clientId)=>{
118
+ const approval = approvals.find((record)=>record.clientId === clientId);
119
+ const connected = connectedByClientId.get(clientId) || [];
120
+ const capabilities = Array.from(new Set(connected.flatMap((node)=>Array.isArray(node.capabilities) ? node.capabilities : [])));
121
+ return {
122
+ clientId,
123
+ name: approval?.name || connected[0]?.name || clientId,
124
+ enabled: approval?.enabled ?? false,
125
+ createdAt: approval?.createdAt,
126
+ updatedAt: approval?.updatedAt,
127
+ lastSeenAt: approval?.lastSeenAt,
128
+ connected: connected.length > 0,
129
+ nodeIds: connected.map((node)=>node.id),
130
+ capabilities
131
+ };
132
+ }).sort((a, b)=>{
133
+ const aTime = a.updatedAt || 0;
134
+ const bTime = b.updatedAt || 0;
135
+ return bTime - aTime;
136
+ });
137
+ return new Response(JSON.stringify({
138
+ nodes
139
+ }, null, 2), {
140
+ headers: {
141
+ "Content-Type": "application/json"
142
+ }
143
+ });
144
+ }
145
+ const clientMatch = url.pathname.match(/^\/api\/nodes\/([^/]+)$/);
146
+ if (!clientMatch) return null;
147
+ const clientId = normalizeClientId(clientMatch[1]);
148
+ if (!clientId) return new Response("valid clientId required", {
149
+ status: 400
150
+ });
151
+ if ("PUT" === req.method) {
152
+ let body;
153
+ try {
154
+ body = await req.json();
155
+ } catch {
156
+ return new Response("Invalid JSON body", {
157
+ status: 400
158
+ });
159
+ }
160
+ if ("boolean" != typeof body.enabled) return new Response("enabled boolean required", {
161
+ status: 400
162
+ });
163
+ const nextRecord = store.setEnabled(clientId, body.enabled, body.name, body.enabled ? Date.now() : void 0);
164
+ if (!nextRecord.enabled) {
165
+ const activeNodes = nodeManager.getNodesByClientId(clientId);
166
+ for (const node of activeNodes)nodeManager.unregisterNode(node.id);
167
+ }
168
+ return new Response(JSON.stringify(nextRecord, null, 2), {
169
+ headers: {
170
+ "Content-Type": "application/json"
171
+ }
172
+ });
173
+ }
174
+ if ("DELETE" === req.method) {
175
+ const nextRecord = store.setEnabled(clientId, false);
176
+ const activeNodes = nodeManager.getNodesByClientId(clientId);
177
+ for (const node of activeNodes)nodeManager.unregisterNode(node.id);
178
+ return new Response(JSON.stringify(nextRecord, null, 2), {
179
+ headers: {
180
+ "Content-Type": "application/json"
181
+ }
182
+ });
183
+ }
184
+ if ("GET" === req.method) {
185
+ const approvals = store.load();
186
+ const approval = approvals.find((record)=>record.clientId === clientId);
187
+ const connected = nodeManager.getNodesByClientId(clientId);
188
+ if (!approval && 0 === connected.length) return new Response("Node not found", {
189
+ status: 404
190
+ });
191
+ return new Response(JSON.stringify({
192
+ clientId,
193
+ name: approval?.name || connected[0]?.name || clientId,
194
+ enabled: approval?.enabled ?? false,
195
+ createdAt: approval?.createdAt,
196
+ updatedAt: approval?.updatedAt,
197
+ lastSeenAt: approval?.lastSeenAt,
198
+ connected: connected.length > 0,
199
+ nodeIds: connected.map((node)=>node.id)
200
+ }, null, 2), {
201
+ headers: {
202
+ "Content-Type": "application/json"
203
+ }
204
+ });
205
+ }
206
+ return new Response("Method Not Allowed", {
207
+ status: 405
208
+ });
209
+ };
210
+ export { createNodeApprovalStore, handleNodesApi };
@@ -40,12 +40,13 @@ function _define_property(obj, key, value) {
40
40
  }
41
41
  const logger = (0, external_logger_cjs_namespaceObject.createLogger)();
42
42
  class NodeManager {
43
- registerNode(ws, name, capabilities, sessionId, agentName) {
43
+ registerNode(ws, name, capabilities, sessionId, agentName, clientId) {
44
44
  if (this.nodes.size >= this.maxNodes) return null;
45
45
  const id = this.generateNodeId();
46
46
  const node = {
47
47
  id,
48
48
  name,
49
+ clientId,
49
50
  capabilities,
50
51
  groups: new Set(),
51
52
  connectedAt: Date.now(),
@@ -54,6 +55,7 @@ class NodeManager {
54
55
  agentName
55
56
  };
56
57
  ws.data = {
58
+ ...ws.data,
57
59
  nodeId: id
58
60
  };
59
61
  this.nodes.set(id, node);
@@ -73,12 +75,18 @@ class NodeManager {
73
75
  getAllNodes() {
74
76
  return Array.from(this.nodes.values());
75
77
  }
78
+ getNodesByClientId(clientId) {
79
+ const trimmed = clientId.trim();
80
+ if (!trimmed) return [];
81
+ return Array.from(this.nodes.values()).filter((node)=>node.clientId === trimmed);
82
+ }
76
83
  getNodeMetadata(nodeId) {
77
84
  const node = this.nodes.get(nodeId);
78
85
  if (!node) return;
79
86
  return {
80
87
  id: node.id,
81
88
  name: node.name,
89
+ clientId: node.clientId,
82
90
  capabilities: node.capabilities,
83
91
  groups: node.groups,
84
92
  connectedAt: node.connectedAt,
@@ -190,6 +198,7 @@ class NodeManager {
190
198
  nodes: Array.from(this.nodes.values()).map((n)=>({
191
199
  id: n.id,
192
200
  name: n.name,
201
+ clientId: n.clientId,
193
202
  groupCount: n.groups.size,
194
203
  connectedAt: n.connectedAt,
195
204
  lastPing: n.lastPing,
@@ -14,7 +14,11 @@ export declare class NodeManager {
14
14
  */
15
15
  registerNode(ws: ServerWebSocket<{
16
16
  nodeId: string;
17
- }>, name: string, capabilities?: string[], sessionId?: string, agentName?: string): Node | null;
17
+ clientId?: string;
18
+ clientType?: string;
19
+ authenticated?: boolean;
20
+ tailscaleUser?: string;
21
+ }>, name: string, capabilities?: string[], sessionId?: string, agentName?: string, clientId?: string): Node | null;
18
22
  /**
19
23
  * Unregister a node
20
24
  */
@@ -27,6 +31,10 @@ export declare class NodeManager {
27
31
  * Get all nodes
28
32
  */
29
33
  getAllNodes(): Node[];
34
+ /**
35
+ * Get all nodes for a stable client ID
36
+ */
37
+ getNodesByClientId(clientId: string): Node[];
30
38
  /**
31
39
  * Get node metadata (without WebSocket)
32
40
  */
@@ -102,6 +110,7 @@ export declare class NodeManager {
102
110
  nodes: {
103
111
  id: string;
104
112
  name: string;
113
+ clientId: string | undefined;
105
114
  groupCount: number;
106
115
  connectedAt: number;
107
116
  lastPing: number | undefined;
@@ -12,12 +12,13 @@ function _define_property(obj, key, value) {
12
12
  }
13
13
  const logger = createLogger();
14
14
  class NodeManager {
15
- registerNode(ws, name, capabilities, sessionId, agentName) {
15
+ registerNode(ws, name, capabilities, sessionId, agentName, clientId) {
16
16
  if (this.nodes.size >= this.maxNodes) return null;
17
17
  const id = this.generateNodeId();
18
18
  const node = {
19
19
  id,
20
20
  name,
21
+ clientId,
21
22
  capabilities,
22
23
  groups: new Set(),
23
24
  connectedAt: Date.now(),
@@ -26,6 +27,7 @@ class NodeManager {
26
27
  agentName
27
28
  };
28
29
  ws.data = {
30
+ ...ws.data,
29
31
  nodeId: id
30
32
  };
31
33
  this.nodes.set(id, node);
@@ -45,12 +47,18 @@ class NodeManager {
45
47
  getAllNodes() {
46
48
  return Array.from(this.nodes.values());
47
49
  }
50
+ getNodesByClientId(clientId) {
51
+ const trimmed = clientId.trim();
52
+ if (!trimmed) return [];
53
+ return Array.from(this.nodes.values()).filter((node)=>node.clientId === trimmed);
54
+ }
48
55
  getNodeMetadata(nodeId) {
49
56
  const node = this.nodes.get(nodeId);
50
57
  if (!node) return;
51
58
  return {
52
59
  id: node.id,
53
60
  name: node.name,
61
+ clientId: node.clientId,
54
62
  capabilities: node.capabilities,
55
63
  groups: node.groups,
56
64
  connectedAt: node.connectedAt,
@@ -162,6 +170,7 @@ class NodeManager {
162
170
  nodes: Array.from(this.nodes.values()).map((n)=>({
163
171
  id: n.id,
164
172
  name: n.name,
173
+ clientId: n.clientId,
165
174
  groupCount: n.groups.size,
166
175
  connectedAt: n.connectedAt,
167
176
  lastPing: n.lastPing,