@sweny-ai/core 0.1.136 → 0.1.138
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.
- package/dist/cli/main.js +12 -0
- package/dist/cli/new.js +3 -0
- package/dist/cli/packs.d.ts +20 -0
- package/dist/cli/packs.js +1004 -0
- package/dist/cli/templates.d.ts +13 -0
- package/dist/cli/templates.js +4 -0
- package/dist/harness/claude-code.d.ts +27 -1
- package/dist/harness/claude-code.js +40 -77
- package/dist/harness/index.d.ts +2 -0
- package/dist/harness/index.js +1 -0
- package/dist/harness/tool-bridge/protocol.d.ts +91 -0
- package/dist/harness/tool-bridge/protocol.js +184 -0
- package/dist/harness/tool-bridge/server.d.ts +57 -0
- package/dist/harness/tool-bridge/server.js +189 -0
- package/dist/harness/tool-bridge/shim.d.ts +38 -0
- package/dist/harness/tool-bridge/shim.js +125 -0
- package/dist/skills/github.js +73 -0
- package/package.json +2 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwenyToolBridge server (#414): the sweny side of the bridge.
|
|
3
|
+
*
|
|
4
|
+
* Holds a node's skill `Tool[]` in the sweny process and serves them over a
|
|
5
|
+
* per-run unix socket to the stdio MCP shim (`sweny tool-bridge`), so any
|
|
6
|
+
* harness that speaks stdio MCP gets skill tools while the handlers, and the
|
|
7
|
+
* executor's tool:call / tool:result observation, stay in sweny.
|
|
8
|
+
*
|
|
9
|
+
* Security properties (this is an injection boundary):
|
|
10
|
+
* - The socket lives in a fresh `mkdtemp` directory (mode 0700) and is itself
|
|
11
|
+
* chmod 0600, so other users cannot connect.
|
|
12
|
+
* - A random 256-bit token is required on every frame. A wrong or missing
|
|
13
|
+
* token gets an `unauthorized` error and the connection is dropped.
|
|
14
|
+
* - Only the tools passed in are listed or callable. The executor has already
|
|
15
|
+
* applied the node's allow/deny filter and the dry-run read-only filter, so
|
|
16
|
+
* the bridge can never widen what the node sees.
|
|
17
|
+
* - Tool results use the same MCP content and `isError` status as the
|
|
18
|
+
* in-process path (`callToolAsMcp`).
|
|
19
|
+
* - `close()` removes the socket and its directory; a process `exit` hook
|
|
20
|
+
* and SIGINT/SIGTERM handlers do the same if the run crashes first.
|
|
21
|
+
* - Nothing here logs the tool list, tool arguments or results.
|
|
22
|
+
*/
|
|
23
|
+
import * as fs from "node:fs";
|
|
24
|
+
import * as net from "node:net";
|
|
25
|
+
import * as os from "node:os";
|
|
26
|
+
import * as path from "node:path";
|
|
27
|
+
import { randomBytes } from "node:crypto";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
import { TOKEN_ENV, callToolAsMcp, createFrameReader, encodeFrame, parseRequest, tokenMatches, } from "./protocol.js";
|
|
30
|
+
// ─── Crash cleanup ───────────────────────────────────────────────
|
|
31
|
+
/** Directories of bridges that are still open, removed synchronously on exit or signal. */
|
|
32
|
+
const liveDirs = new Set();
|
|
33
|
+
let hooksInstalled = false;
|
|
34
|
+
function removeDirSync(dir) {
|
|
35
|
+
try {
|
|
36
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Best effort: the process is going down.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function cleanupAllSync() {
|
|
43
|
+
for (const dir of liveDirs)
|
|
44
|
+
removeDirSync(dir);
|
|
45
|
+
liveDirs.clear();
|
|
46
|
+
}
|
|
47
|
+
function onSignal(signal) {
|
|
48
|
+
cleanupAllSync();
|
|
49
|
+
// Keep default signal semantics: if nobody else handles it, re-raise so the
|
|
50
|
+
// process still dies with the signal.
|
|
51
|
+
process.removeListener("exit", cleanupAllSync);
|
|
52
|
+
process.removeListener("SIGINT", onSignal);
|
|
53
|
+
process.removeListener("SIGTERM", onSignal);
|
|
54
|
+
hooksInstalled = false;
|
|
55
|
+
if (process.listenerCount(signal) === 0)
|
|
56
|
+
process.kill(process.pid, signal);
|
|
57
|
+
}
|
|
58
|
+
function installCrashHooks() {
|
|
59
|
+
if (hooksInstalled)
|
|
60
|
+
return;
|
|
61
|
+
hooksInstalled = true;
|
|
62
|
+
// `exit` also fires after an uncaught exception, before the process ends.
|
|
63
|
+
process.on("exit", cleanupAllSync);
|
|
64
|
+
process.on("SIGINT", onSignal);
|
|
65
|
+
process.on("SIGTERM", onSignal);
|
|
66
|
+
}
|
|
67
|
+
function uninstallCrashHooksIfIdle() {
|
|
68
|
+
if (!hooksInstalled || liveDirs.size > 0)
|
|
69
|
+
return;
|
|
70
|
+
hooksInstalled = false;
|
|
71
|
+
process.removeListener("exit", cleanupAllSync);
|
|
72
|
+
process.removeListener("SIGINT", onSignal);
|
|
73
|
+
process.removeListener("SIGTERM", onSignal);
|
|
74
|
+
}
|
|
75
|
+
// ─── Server ──────────────────────────────────────────────────────
|
|
76
|
+
/** Default shim command: the sweny CLI that ships next to this module. */
|
|
77
|
+
export function defaultShimCommand() {
|
|
78
|
+
const cli = fileURLToPath(new URL("../../cli/main.js", import.meta.url));
|
|
79
|
+
return { command: process.execPath, args: [cli, "tool-bridge"] };
|
|
80
|
+
}
|
|
81
|
+
function describeTool(t) {
|
|
82
|
+
return {
|
|
83
|
+
name: t.name,
|
|
84
|
+
description: t.description,
|
|
85
|
+
inputSchema: { type: "object", ...t.input_schema },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Start a bridge for one node run. Call `close()` when the run ends. */
|
|
89
|
+
export async function startToolBridge(opts) {
|
|
90
|
+
const { tools, context, logger } = opts;
|
|
91
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
92
|
+
const descriptors = tools.map(describeTool);
|
|
93
|
+
// mkdtemp creates the directory with mode 0700.
|
|
94
|
+
const dir = fs.mkdtempSync(path.join(opts.tmpDir ?? os.tmpdir(), "sweny-tb-"));
|
|
95
|
+
fs.chmodSync(dir, 0o700);
|
|
96
|
+
const socketPath = path.join(dir, "b.sock");
|
|
97
|
+
const token = randomBytes(32).toString("hex");
|
|
98
|
+
liveDirs.add(dir);
|
|
99
|
+
installCrashHooks();
|
|
100
|
+
const sockets = new Set();
|
|
101
|
+
const server = net.createServer((sock) => {
|
|
102
|
+
sockets.add(sock);
|
|
103
|
+
sock.on("close", () => sockets.delete(sock));
|
|
104
|
+
sock.on("error", () => sock.destroy());
|
|
105
|
+
// Set on the first bad token: later frames on this connection, even ones
|
|
106
|
+
// already buffered in the same chunk, are ignored.
|
|
107
|
+
let rejected = false;
|
|
108
|
+
const send = (res) => {
|
|
109
|
+
if (!sock.destroyed && sock.writable)
|
|
110
|
+
sock.write(encodeFrame(res));
|
|
111
|
+
};
|
|
112
|
+
const read = createFrameReader((value) => {
|
|
113
|
+
if (rejected)
|
|
114
|
+
return;
|
|
115
|
+
const req = parseRequest(value);
|
|
116
|
+
const id = req?.id ?? -1;
|
|
117
|
+
// Token first: an unauthenticated peer learns nothing else.
|
|
118
|
+
if (!tokenMatches(token, value?.token)) {
|
|
119
|
+
logger?.warn("tool bridge: rejected a request with a bad token");
|
|
120
|
+
send({ id, ok: false, error: { code: "unauthorized", message: "unauthorized" } });
|
|
121
|
+
rejected = true;
|
|
122
|
+
sock.end();
|
|
123
|
+
setTimeout(() => sock.destroy(), 1000).unref();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!req) {
|
|
127
|
+
send({ id, ok: false, error: { code: "bad_request", message: "malformed request" } });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (req.method === "tools/list") {
|
|
131
|
+
send({ id, ok: true, result: { tools: descriptors } });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const name = req.params?.name;
|
|
135
|
+
const tool = typeof name === "string" ? byName.get(name) : undefined;
|
|
136
|
+
if (!tool) {
|
|
137
|
+
send({ id, ok: false, error: { code: "unknown_tool", message: `unknown tool: ${String(name)}` } });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
callToolAsMcp(tool, req.params?.arguments, context).then((result) => send({ id, ok: true, result }), () => send({ id, ok: false, error: { code: "internal", message: "tool call failed" } }));
|
|
141
|
+
}, () => {
|
|
142
|
+
send({ id: -1, ok: false, error: { code: "bad_request", message: "frame too large" } });
|
|
143
|
+
sock.destroy();
|
|
144
|
+
});
|
|
145
|
+
sock.on("data", read);
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
await new Promise((resolve, reject) => {
|
|
149
|
+
server.once("error", reject);
|
|
150
|
+
server.listen(socketPath, () => {
|
|
151
|
+
server.removeListener("error", reject);
|
|
152
|
+
resolve();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
fs.chmodSync(socketPath, 0o600);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
server.close();
|
|
159
|
+
liveDirs.delete(dir);
|
|
160
|
+
removeDirSync(dir);
|
|
161
|
+
uninstallCrashHooksIfIdle();
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
logger?.debug("tool bridge: listening");
|
|
165
|
+
const shim = opts.shimCommand ?? defaultShimCommand();
|
|
166
|
+
let closed = false;
|
|
167
|
+
return {
|
|
168
|
+
socketPath,
|
|
169
|
+
token,
|
|
170
|
+
mcpServer: {
|
|
171
|
+
type: "stdio",
|
|
172
|
+
command: shim.command,
|
|
173
|
+
args: [...shim.args, "--socket", socketPath],
|
|
174
|
+
// No update-check nudge in the shim: its stdio belongs to MCP.
|
|
175
|
+
env: { [TOKEN_ENV]: token, SWENY_NO_UPDATE_CHECK: "1" },
|
|
176
|
+
},
|
|
177
|
+
async close() {
|
|
178
|
+
if (closed)
|
|
179
|
+
return;
|
|
180
|
+
closed = true;
|
|
181
|
+
for (const s of sockets)
|
|
182
|
+
s.destroy();
|
|
183
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
184
|
+
liveDirs.delete(dir);
|
|
185
|
+
removeDirSync(dir);
|
|
186
|
+
uninstallCrashHooksIfIdle();
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwenyToolBridge shim (#414): `sweny tool-bridge --socket <path>`.
|
|
3
|
+
*
|
|
4
|
+
* A stdio MCP server a harness starts as a child process. It holds no tools
|
|
5
|
+
* of its own: `tools/list` and `tools/call` are forwarded over the per-run
|
|
6
|
+
* unix socket to the sweny process (server.ts), which runs the real handlers.
|
|
7
|
+
*
|
|
8
|
+
* stdout carries MCP only; diagnostics go to stderr. The token comes from
|
|
9
|
+
* `--token` or, by default, the `SWENY_TOOL_BRIDGE_TOKEN` env var the server
|
|
10
|
+
* puts in the MCP server config, so it never shows up in `ps` output.
|
|
11
|
+
*/
|
|
12
|
+
import { type BridgeMethod, type BridgeResponse } from "./protocol.js";
|
|
13
|
+
/** Socket client that multiplexes requests by id over one connection. */
|
|
14
|
+
export declare class BridgeClient {
|
|
15
|
+
private readonly socketPath;
|
|
16
|
+
private readonly token;
|
|
17
|
+
private sock;
|
|
18
|
+
private connecting;
|
|
19
|
+
private nextId;
|
|
20
|
+
private pending;
|
|
21
|
+
private closedError;
|
|
22
|
+
constructor(socketPath: string, token: string);
|
|
23
|
+
private connect;
|
|
24
|
+
private failAll;
|
|
25
|
+
request(method: BridgeMethod, params?: {
|
|
26
|
+
name: string;
|
|
27
|
+
arguments?: Record<string, unknown>;
|
|
28
|
+
}): Promise<BridgeResponse>;
|
|
29
|
+
close(): void;
|
|
30
|
+
}
|
|
31
|
+
export interface ShimOptions {
|
|
32
|
+
socket: string;
|
|
33
|
+
/** Falls back to `SWENY_TOOL_BRIDGE_TOKEN`. */
|
|
34
|
+
token?: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Run the stdio MCP shim until stdin closes or the bridge goes away. */
|
|
38
|
+
export declare function runToolBridgeShim(opts: ShimOptions): Promise<void>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwenyToolBridge shim (#414): `sweny tool-bridge --socket <path>`.
|
|
3
|
+
*
|
|
4
|
+
* A stdio MCP server a harness starts as a child process. It holds no tools
|
|
5
|
+
* of its own: `tools/list` and `tools/call` are forwarded over the per-run
|
|
6
|
+
* unix socket to the sweny process (server.ts), which runs the real handlers.
|
|
7
|
+
*
|
|
8
|
+
* stdout carries MCP only; diagnostics go to stderr. The token comes from
|
|
9
|
+
* `--token` or, by default, the `SWENY_TOOL_BRIDGE_TOKEN` env var the server
|
|
10
|
+
* puts in the MCP server config, so it never shows up in `ps` output.
|
|
11
|
+
*/
|
|
12
|
+
import * as net from "node:net";
|
|
13
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
import { BRIDGE_PROTOCOL_VERSION, TOKEN_ENV, createFrameReader, encodeFrame, } from "./protocol.js";
|
|
17
|
+
/** Socket client that multiplexes requests by id over one connection. */
|
|
18
|
+
export class BridgeClient {
|
|
19
|
+
socketPath;
|
|
20
|
+
token;
|
|
21
|
+
sock;
|
|
22
|
+
connecting;
|
|
23
|
+
nextId = 1;
|
|
24
|
+
pending = new Map();
|
|
25
|
+
closedError;
|
|
26
|
+
constructor(socketPath, token) {
|
|
27
|
+
this.socketPath = socketPath;
|
|
28
|
+
this.token = token;
|
|
29
|
+
}
|
|
30
|
+
connect() {
|
|
31
|
+
// Once the connection is gone (bad token, server closed) every later call fails fast.
|
|
32
|
+
if (this.closedError)
|
|
33
|
+
return Promise.reject(this.closedError);
|
|
34
|
+
if (this.sock)
|
|
35
|
+
return Promise.resolve(this.sock);
|
|
36
|
+
this.connecting ??= new Promise((resolve, reject) => {
|
|
37
|
+
const sock = net.createConnection(this.socketPath);
|
|
38
|
+
const read = createFrameReader((value) => {
|
|
39
|
+
const res = value;
|
|
40
|
+
if (!res || typeof res.id !== "number")
|
|
41
|
+
return;
|
|
42
|
+
const waiter = this.pending.get(res.id);
|
|
43
|
+
if (waiter) {
|
|
44
|
+
this.pending.delete(res.id);
|
|
45
|
+
waiter.resolve(res);
|
|
46
|
+
}
|
|
47
|
+
else if (res.ok === false) {
|
|
48
|
+
// Unsolicited error (for example a frame-size rejection): fail everything.
|
|
49
|
+
this.failAll(new Error(`tool bridge: ${res.error.message}`));
|
|
50
|
+
}
|
|
51
|
+
}, () => this.failAll(new Error("tool bridge: response frame too large")));
|
|
52
|
+
sock.on("data", read);
|
|
53
|
+
sock.once("connect", () => {
|
|
54
|
+
this.sock = sock;
|
|
55
|
+
resolve(sock);
|
|
56
|
+
});
|
|
57
|
+
sock.once("error", (err) => {
|
|
58
|
+
reject(err);
|
|
59
|
+
this.failAll(err);
|
|
60
|
+
});
|
|
61
|
+
sock.once("close", () => this.failAll(new Error("tool bridge: connection closed")));
|
|
62
|
+
});
|
|
63
|
+
return this.connecting;
|
|
64
|
+
}
|
|
65
|
+
failAll(err) {
|
|
66
|
+
this.closedError ??= err;
|
|
67
|
+
for (const w of this.pending.values())
|
|
68
|
+
w.reject(err);
|
|
69
|
+
this.pending.clear();
|
|
70
|
+
this.sock?.destroy();
|
|
71
|
+
this.sock = undefined;
|
|
72
|
+
}
|
|
73
|
+
async request(method, params) {
|
|
74
|
+
const sock = await this.connect();
|
|
75
|
+
const id = this.nextId++;
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
if (this.closedError || sock.destroyed) {
|
|
78
|
+
reject(this.closedError ?? new Error("tool bridge: connection closed"));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.pending.set(id, { resolve, reject });
|
|
82
|
+
sock.write(encodeFrame({ v: BRIDGE_PROTOCOL_VERSION, id, token: this.token, method, params }));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
close() {
|
|
86
|
+
this.sock?.destroy();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Run the stdio MCP shim until stdin closes or the bridge goes away. */
|
|
90
|
+
export async function runToolBridgeShim(opts) {
|
|
91
|
+
const token = opts.token ?? process.env[TOKEN_ENV];
|
|
92
|
+
if (!opts.socket || !token) {
|
|
93
|
+
process.stderr.write(`sweny tool-bridge: --socket and a token (--token or ${TOKEN_ENV}) are required\n`);
|
|
94
|
+
process.exit(2);
|
|
95
|
+
}
|
|
96
|
+
// Keep the token out of anything the shim might spawn or report.
|
|
97
|
+
delete process.env[TOKEN_ENV];
|
|
98
|
+
const client = new BridgeClient(opts.socket, token);
|
|
99
|
+
const server = new Server({ name: "sweny-core", version: opts.version ?? "0.0.0" }, { capabilities: { tools: {} } });
|
|
100
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
101
|
+
const res = await client.request("tools/list");
|
|
102
|
+
if (!res.ok)
|
|
103
|
+
throw new Error(`tool bridge: ${res.error.message}`);
|
|
104
|
+
return { tools: res.result.tools };
|
|
105
|
+
});
|
|
106
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
107
|
+
const res = await client.request("tools/call", {
|
|
108
|
+
name: req.params.name,
|
|
109
|
+
arguments: req.params.arguments,
|
|
110
|
+
});
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
// Unknown tool and friends surface as a tool error result, the same way
|
|
113
|
+
// the in-process SDK server reports a call it cannot serve.
|
|
114
|
+
return { content: [{ type: "text", text: `Error: ${res.error.message}` }], isError: true };
|
|
115
|
+
}
|
|
116
|
+
return res.result;
|
|
117
|
+
});
|
|
118
|
+
const shutdown = () => {
|
|
119
|
+
client.close();
|
|
120
|
+
void server.close().finally(() => process.exit(0));
|
|
121
|
+
};
|
|
122
|
+
process.stdin.once("end", shutdown);
|
|
123
|
+
process.stdin.once("close", shutdown);
|
|
124
|
+
await server.connect(new StdioServerTransport());
|
|
125
|
+
}
|
package/dist/skills/github.js
CHANGED
|
@@ -266,6 +266,79 @@ export const github = {
|
|
|
266
266
|
return data;
|
|
267
267
|
},
|
|
268
268
|
},
|
|
269
|
+
{
|
|
270
|
+
name: "github_list_pr_files",
|
|
271
|
+
access: "read",
|
|
272
|
+
description: "List the files changed in a pull request with per-file status and line counts (no patch bodies). " +
|
|
273
|
+
"Use it to size a change and spot risky paths without reading the whole diff.",
|
|
274
|
+
input_schema: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
repo: { type: "string", description: "owner/repo" },
|
|
278
|
+
number: { type: "number", description: "Pull request number" },
|
|
279
|
+
},
|
|
280
|
+
required: ["repo", "number"],
|
|
281
|
+
},
|
|
282
|
+
handler: async (input, ctx) => {
|
|
283
|
+
const files = (await gh(`/repos/${input.repo}/pulls/${input.number}/files?per_page=100`, ctx));
|
|
284
|
+
return {
|
|
285
|
+
count: files.length,
|
|
286
|
+
truncated: files.length >= 100,
|
|
287
|
+
files: files.map((f) => ({
|
|
288
|
+
filename: f.filename,
|
|
289
|
+
status: f.status,
|
|
290
|
+
additions: f.additions,
|
|
291
|
+
deletions: f.deletions,
|
|
292
|
+
changes: f.changes,
|
|
293
|
+
})),
|
|
294
|
+
};
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "github_list_dependabot_alerts",
|
|
299
|
+
access: "read",
|
|
300
|
+
description: "List open Dependabot security alerts for a repository (package, severity, advisory id, patched version). " +
|
|
301
|
+
"Needs a token with Dependabot alerts read access; the built-in Actions GITHUB_TOKEN does not have it. " +
|
|
302
|
+
"Returns { unavailable: true } instead of throwing when alerts cannot be read.",
|
|
303
|
+
input_schema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
repo: { type: "string", description: "owner/repo" },
|
|
307
|
+
severity: { type: "string", description: "Optional filter: low, medium, high, or critical" },
|
|
308
|
+
},
|
|
309
|
+
required: ["repo"],
|
|
310
|
+
},
|
|
311
|
+
handler: async (input, ctx) => {
|
|
312
|
+
const sev = input.severity ? `&severity=${encodeURIComponent(input.severity)}` : "";
|
|
313
|
+
let alerts;
|
|
314
|
+
try {
|
|
315
|
+
alerts = (await gh(`/repos/${input.repo}/dependabot/alerts?state=open&per_page=50${sev}`, ctx));
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
if (err instanceof GitHubApiError && (err.status === 403 || err.status === 404)) {
|
|
319
|
+
return { unavailable: true, status: err.status, alerts: [] };
|
|
320
|
+
}
|
|
321
|
+
throw err;
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
unavailable: false,
|
|
325
|
+
count: alerts.length,
|
|
326
|
+
alerts: alerts.map((a) => ({
|
|
327
|
+
number: a.number,
|
|
328
|
+
package: a.dependency?.package?.name,
|
|
329
|
+
ecosystem: a.dependency?.package?.ecosystem,
|
|
330
|
+
manifest: a.dependency?.manifest_path,
|
|
331
|
+
severity: a.security_advisory?.severity,
|
|
332
|
+
ghsa_id: a.security_advisory?.ghsa_id,
|
|
333
|
+
cve_id: a.security_advisory?.cve_id,
|
|
334
|
+
summary: a.security_advisory?.summary,
|
|
335
|
+
vulnerable_range: a.security_vulnerability?.vulnerable_version_range,
|
|
336
|
+
patched_version: a.security_vulnerability?.first_patched_version?.identifier ?? null,
|
|
337
|
+
url: a.html_url,
|
|
338
|
+
})),
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
},
|
|
269
342
|
],
|
|
270
343
|
// Equivalent tool names on GitHub's official MCP server
|
|
271
344
|
// (github.com/github/github-mcp-server). Aliases declared for every
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sweny-ai/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.138",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sweny": "./dist/cli/main.js"
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
|
61
61
|
"@clack/prompts": "^1.7.0",
|
|
62
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
62
63
|
"chalk": "^5",
|
|
63
64
|
"commander": "^13",
|
|
64
65
|
"yaml": "^2.9.0",
|