junis 0.1.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.
- package/README.md +162 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +987 -0
- package/dist/server/mcp.js +793 -0
- package/dist/server/stdio.js +549 -0
- package/package.json +36 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/server/stdio.ts
|
|
27
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
28
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
29
|
+
|
|
30
|
+
// src/tools/filesystem.ts
|
|
31
|
+
var import_child_process = require("child_process");
|
|
32
|
+
var import_util = require("util");
|
|
33
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
34
|
+
var import_path = __toESM(require("path"));
|
|
35
|
+
var import_glob = require("glob");
|
|
36
|
+
var import_zod = require("zod");
|
|
37
|
+
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
38
|
+
var FilesystemTools = class {
|
|
39
|
+
register(server) {
|
|
40
|
+
server.tool(
|
|
41
|
+
"execute_command",
|
|
42
|
+
"\uD130\uBBF8\uB110 \uBA85\uB839 \uC2E4\uD589",
|
|
43
|
+
{
|
|
44
|
+
command: import_zod.z.string().describe("\uC2E4\uD589\uD560 \uC258 \uBA85\uB839"),
|
|
45
|
+
timeout_ms: import_zod.z.number().optional().default(3e4).describe("\uD0C0\uC784\uC544\uC6C3 (ms)"),
|
|
46
|
+
background: import_zod.z.boolean().optional().default(false).describe("\uBC31\uADF8\uB77C\uC6B4\uB4DC \uC2E4\uD589")
|
|
47
|
+
},
|
|
48
|
+
async ({ command, timeout_ms, background }) => {
|
|
49
|
+
if (background) {
|
|
50
|
+
(0, import_child_process.exec)(command);
|
|
51
|
+
return { content: [{ type: "text", text: "\uBC31\uADF8\uB77C\uC6B4\uB4DC \uC2E4\uD589 \uC2DC\uC791\uB428" }] };
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
55
|
+
timeout: timeout_ms
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: stdout || stderr || "(\uCD9C\uB825 \uC5C6\uC74C)" }]
|
|
59
|
+
};
|
|
60
|
+
} catch (err) {
|
|
61
|
+
const error = err;
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `\uC624\uB958 (exit ${error.code ?? "?"}): ${error.message}
|
|
67
|
+
${error.stderr ?? ""}`
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
isError: true
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
server.tool(
|
|
76
|
+
"read_file",
|
|
77
|
+
"\uD30C\uC77C \uC77D\uAE30",
|
|
78
|
+
{
|
|
79
|
+
path: import_zod.z.string().describe("\uD30C\uC77C \uACBD\uB85C"),
|
|
80
|
+
encoding: import_zod.z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("\uC778\uCF54\uB529")
|
|
81
|
+
},
|
|
82
|
+
async ({ path: filePath, encoding }) => {
|
|
83
|
+
const content = await import_promises.default.readFile(filePath, encoding);
|
|
84
|
+
return { content: [{ type: "text", text: content }] };
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
server.tool(
|
|
88
|
+
"write_file",
|
|
89
|
+
"\uD30C\uC77C \uC4F0\uAE30/\uC0DD\uC131",
|
|
90
|
+
{
|
|
91
|
+
path: import_zod.z.string().describe("\uD30C\uC77C \uACBD\uB85C"),
|
|
92
|
+
content: import_zod.z.string().describe("\uD30C\uC77C \uB0B4\uC6A9")
|
|
93
|
+
},
|
|
94
|
+
async ({ path: filePath, content }) => {
|
|
95
|
+
await import_promises.default.mkdir(import_path.default.dirname(filePath), { recursive: true });
|
|
96
|
+
await import_promises.default.writeFile(filePath, content, "utf-8");
|
|
97
|
+
return { content: [{ type: "text", text: "\uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC" }] };
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
server.tool(
|
|
101
|
+
"list_directory",
|
|
102
|
+
"\uB514\uB809\uD1A0\uB9AC \uBAA9\uB85D \uC870\uD68C",
|
|
103
|
+
{
|
|
104
|
+
path: import_zod.z.string().describe("\uB514\uB809\uD1A0\uB9AC \uACBD\uB85C")
|
|
105
|
+
},
|
|
106
|
+
async ({ path: dirPath }) => {
|
|
107
|
+
const entries = await import_promises.default.readdir(dirPath, { withFileTypes: true });
|
|
108
|
+
const lines = entries.map((e) => `${e.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}"} ${e.name}`);
|
|
109
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
server.tool(
|
|
113
|
+
"search_code",
|
|
114
|
+
"\uCF54\uB4DC/\uD14D\uC2A4\uD2B8 \uAC80\uC0C9",
|
|
115
|
+
{
|
|
116
|
+
pattern: import_zod.z.string().describe("\uAC80\uC0C9 \uD328\uD134 (\uC815\uADDC\uC2DD \uC9C0\uC6D0)"),
|
|
117
|
+
directory: import_zod.z.string().optional().default(".").describe("\uAC80\uC0C9 \uB514\uB809\uD1A0\uB9AC"),
|
|
118
|
+
file_pattern: import_zod.z.string().optional().default("**/*").describe("\uD30C\uC77C \uD328\uD134")
|
|
119
|
+
},
|
|
120
|
+
async ({ pattern, directory, file_pattern }) => {
|
|
121
|
+
try {
|
|
122
|
+
const { stdout } = await execAsync(
|
|
123
|
+
`rg --no-heading -n "${pattern}" ${directory}`,
|
|
124
|
+
{ timeout: 1e4 }
|
|
125
|
+
);
|
|
126
|
+
return { content: [{ type: "text", text: stdout || "\uACB0\uACFC \uC5C6\uC74C" }] };
|
|
127
|
+
} catch {
|
|
128
|
+
const files = await (0, import_glob.glob)(file_pattern, { cwd: directory });
|
|
129
|
+
const results = [];
|
|
130
|
+
for (const file of files.slice(0, 100)) {
|
|
131
|
+
try {
|
|
132
|
+
const content = await import_promises.default.readFile(
|
|
133
|
+
import_path.default.join(directory, file),
|
|
134
|
+
"utf-8"
|
|
135
|
+
);
|
|
136
|
+
const lines = content.split("\n");
|
|
137
|
+
const re = new RegExp(pattern, "gi");
|
|
138
|
+
lines.forEach((line, i) => {
|
|
139
|
+
if (re.test(line)) results.push(`${file}:${i + 1}: ${line}`);
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{ type: "text", text: results.join("\n") || "\uACB0\uACFC \uC5C6\uC74C" }
|
|
147
|
+
]
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
server.tool(
|
|
153
|
+
"list_processes",
|
|
154
|
+
"\uC2E4\uD589 \uC911\uC778 \uD504\uB85C\uC138\uC2A4 \uBAA9\uB85D",
|
|
155
|
+
{},
|
|
156
|
+
async () => {
|
|
157
|
+
const cmd = process.platform === "win32" ? "tasklist" : process.platform === "darwin" ? "ps aux | sort -rk 3 | head -30" : "ps aux --sort=-%cpu | head -30";
|
|
158
|
+
const { stdout } = await execAsync(cmd);
|
|
159
|
+
return { content: [{ type: "text", text: stdout }] };
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
server.tool(
|
|
163
|
+
"kill_process",
|
|
164
|
+
"\uD504\uB85C\uC138\uC2A4 \uC885\uB8CC (SIGTERM \uD6C4 3\uCD08 \uB300\uAE30, \uC0B4\uC544\uC788\uC73C\uBA74 SIGKILL \uC790\uB3D9 \uC801\uC6A9)",
|
|
165
|
+
{
|
|
166
|
+
pid: import_zod.z.number().describe("\uC885\uB8CC\uD560 \uD504\uB85C\uC138\uC2A4 PID"),
|
|
167
|
+
signal: import_zod.z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("\uCD08\uAE30 \uC2DC\uADF8\uB110 (\uAE30\uBCF8\uAC12: SIGTERM). SIGKILL \uC9C0\uC815 \uC2DC \uC989\uC2DC \uAC15\uC81C \uC885\uB8CC)")
|
|
168
|
+
},
|
|
169
|
+
async ({ pid, signal }) => {
|
|
170
|
+
const isWindows = process.platform === "win32";
|
|
171
|
+
if (isWindows) {
|
|
172
|
+
await execAsync(`taskkill /PID ${pid} /F`);
|
|
173
|
+
return {
|
|
174
|
+
content: [{ type: "text", text: `PID ${pid} \uC885\uB8CC \uC644\uB8CC (taskkill /F)` }]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (signal === "SIGKILL") {
|
|
178
|
+
await execAsync(`kill -9 ${pid}`);
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: `PID ${pid} \uAC15\uC81C \uC885\uB8CC \uC644\uB8CC (SIGKILL)` }]
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
await execAsync(`kill -15 ${pid}`);
|
|
185
|
+
} catch {
|
|
186
|
+
return {
|
|
187
|
+
content: [
|
|
188
|
+
{ type: "text", text: `PID ${pid} \uC885\uB8CC \uC2E4\uD328: \uD504\uB85C\uC138\uC2A4\uAC00 \uC874\uC7AC\uD558\uC9C0 \uC54A\uAC70\uB098 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.` }
|
|
189
|
+
],
|
|
190
|
+
isError: true
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
194
|
+
const isAlive = await execAsync(`kill -0 ${pid}`).then(() => true).catch(() => false);
|
|
195
|
+
if (!isAlive) {
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: "text", text: `PID ${pid} \uC885\uB8CC \uC644\uB8CC (SIGTERM)` }]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
await execAsync(`kill -9 ${pid}`);
|
|
201
|
+
return {
|
|
202
|
+
content: [
|
|
203
|
+
{
|
|
204
|
+
type: "text",
|
|
205
|
+
text: `PID ${pid} \uAC15\uC81C \uC885\uB8CC \uC644\uB8CC (SIGTERM \uBB34\uC751\uB2F5 \u2192 SIGKILL \uC790\uB3D9 \uC801\uC6A9)`
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/tools/browser.ts
|
|
215
|
+
var import_playwright = require("playwright");
|
|
216
|
+
var import_zod2 = require("zod");
|
|
217
|
+
var BrowserTools = class {
|
|
218
|
+
browser = null;
|
|
219
|
+
page = null;
|
|
220
|
+
async init() {
|
|
221
|
+
try {
|
|
222
|
+
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
223
|
+
this.page = await this.browser.newPage();
|
|
224
|
+
} catch {
|
|
225
|
+
console.warn(
|
|
226
|
+
"\u26A0\uFE0F Playwright \uBBF8\uC124\uCE58. \uBE0C\uB77C\uC6B0\uC800 \uB3C4\uAD6C \uBE44\uD65C\uC131\uD654.\n \uD65C\uC131\uD654: npx playwright install chromium"
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async cleanup() {
|
|
231
|
+
await this.browser?.close();
|
|
232
|
+
}
|
|
233
|
+
register(server) {
|
|
234
|
+
const requirePage = () => {
|
|
235
|
+
if (!this.page) throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uBBF8\uCD08\uAE30\uD654. playwright \uC124\uCE58 \uD655\uC778.");
|
|
236
|
+
return this.page;
|
|
237
|
+
};
|
|
238
|
+
server.tool(
|
|
239
|
+
"browser_navigate",
|
|
240
|
+
"URL\uB85C \uC774\uB3D9",
|
|
241
|
+
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
242
|
+
async ({ url }) => {
|
|
243
|
+
const page = requirePage();
|
|
244
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
server.tool(
|
|
251
|
+
"browser_click",
|
|
252
|
+
"\uC694\uC18C \uD074\uB9AD",
|
|
253
|
+
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
254
|
+
async ({ selector }) => {
|
|
255
|
+
await requirePage().click(selector);
|
|
256
|
+
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
server.tool(
|
|
260
|
+
"browser_type",
|
|
261
|
+
"\uD14D\uC2A4\uD2B8 \uC785\uB825",
|
|
262
|
+
{
|
|
263
|
+
selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790"),
|
|
264
|
+
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
265
|
+
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
266
|
+
},
|
|
267
|
+
async ({ selector, text, clear }) => {
|
|
268
|
+
const page = requirePage();
|
|
269
|
+
if (clear) await page.fill(selector, text);
|
|
270
|
+
else await page.type(selector, text);
|
|
271
|
+
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
server.tool(
|
|
275
|
+
"browser_screenshot",
|
|
276
|
+
"\uD604\uC7AC \uD398\uC774\uC9C0 \uC2A4\uD06C\uB9B0\uC0F7",
|
|
277
|
+
{
|
|
278
|
+
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
279
|
+
full_page: import_zod2.z.boolean().optional().default(false)
|
|
280
|
+
},
|
|
281
|
+
async ({ path: path2, full_page }) => {
|
|
282
|
+
const page = requirePage();
|
|
283
|
+
const screenshot = await page.screenshot({
|
|
284
|
+
path: path2 ?? void 0,
|
|
285
|
+
fullPage: full_page
|
|
286
|
+
});
|
|
287
|
+
if (path2) {
|
|
288
|
+
return { content: [{ type: "text", text: `\uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
content: [
|
|
292
|
+
{
|
|
293
|
+
type: "image",
|
|
294
|
+
data: screenshot.toString("base64"),
|
|
295
|
+
mimeType: "image/png"
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
server.tool(
|
|
302
|
+
"browser_snapshot",
|
|
303
|
+
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
304
|
+
{},
|
|
305
|
+
async () => {
|
|
306
|
+
const page = requirePage();
|
|
307
|
+
const snapshot = await page.locator("body").ariaSnapshot();
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{ type: "text", text: snapshot }
|
|
311
|
+
]
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
server.tool(
|
|
316
|
+
"browser_evaluate",
|
|
317
|
+
"JavaScript \uC2E4\uD589",
|
|
318
|
+
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
319
|
+
async ({ code }) => {
|
|
320
|
+
const result = await requirePage().evaluate(code);
|
|
321
|
+
return {
|
|
322
|
+
content: [
|
|
323
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
324
|
+
]
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
);
|
|
328
|
+
server.tool(
|
|
329
|
+
"browser_pdf",
|
|
330
|
+
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
331
|
+
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
332
|
+
async ({ path: path2 }) => {
|
|
333
|
+
await requirePage().pdf({ path: path2 });
|
|
334
|
+
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// src/tools/notebook.ts
|
|
341
|
+
var import_zod3 = require("zod");
|
|
342
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
343
|
+
var import_child_process2 = require("child_process");
|
|
344
|
+
var import_util2 = require("util");
|
|
345
|
+
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
346
|
+
async function readNotebook(filePath) {
|
|
347
|
+
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
348
|
+
return JSON.parse(raw);
|
|
349
|
+
}
|
|
350
|
+
async function writeNotebook(filePath, nb) {
|
|
351
|
+
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
352
|
+
}
|
|
353
|
+
var NotebookTools = class {
|
|
354
|
+
register(server) {
|
|
355
|
+
server.tool(
|
|
356
|
+
"notebook_read",
|
|
357
|
+
".ipynb \uB178\uD2B8\uBD81 \uC77D\uAE30",
|
|
358
|
+
{ path: import_zod3.z.string().describe("\uB178\uD2B8\uBD81 \uD30C\uC77C \uACBD\uB85C") },
|
|
359
|
+
async ({ path: filePath }) => {
|
|
360
|
+
const nb = await readNotebook(filePath);
|
|
361
|
+
const cells = nb.cells.map((cell, i) => ({
|
|
362
|
+
index: i,
|
|
363
|
+
type: cell.cell_type,
|
|
364
|
+
source: cell.source.join(""),
|
|
365
|
+
outputs: cell.outputs?.length ?? 0
|
|
366
|
+
}));
|
|
367
|
+
return {
|
|
368
|
+
content: [{ type: "text", text: JSON.stringify(cells, null, 2) }]
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
server.tool(
|
|
373
|
+
"notebook_edit_cell",
|
|
374
|
+
"\uB178\uD2B8\uBD81 \uD2B9\uC815 \uC140 \uC218\uC815",
|
|
375
|
+
{
|
|
376
|
+
path: import_zod3.z.string(),
|
|
377
|
+
cell_index: import_zod3.z.number().describe("0\uBD80\uD130 \uC2DC\uC791\uD558\uB294 \uC140 \uC778\uB371\uC2A4"),
|
|
378
|
+
source: import_zod3.z.string().describe("\uC0C8 \uC18C\uC2A4 \uCF54\uB4DC")
|
|
379
|
+
},
|
|
380
|
+
async ({ path: filePath, cell_index, source }) => {
|
|
381
|
+
const nb = await readNotebook(filePath);
|
|
382
|
+
if (cell_index < 0 || cell_index >= nb.cells.length) {
|
|
383
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC140 \uC778\uB371\uC2A4: ${cell_index}`);
|
|
384
|
+
}
|
|
385
|
+
nb.cells[cell_index].source = source.split("\n").map(
|
|
386
|
+
(l, i, arr) => i < arr.length - 1 ? l + "\n" : l
|
|
387
|
+
);
|
|
388
|
+
await writeNotebook(filePath, nb);
|
|
389
|
+
return { content: [{ type: "text", text: "\uC140 \uC218\uC815 \uC644\uB8CC" }] };
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
server.tool(
|
|
393
|
+
"notebook_execute",
|
|
394
|
+
"\uB178\uD2B8\uBD81 \uC2E4\uD589 (nbconvert --execute)",
|
|
395
|
+
{
|
|
396
|
+
path: import_zod3.z.string().describe("\uB178\uD2B8\uBD81 \uD30C\uC77C \uACBD\uB85C"),
|
|
397
|
+
timeout: import_zod3.z.number().optional().default(300).describe("\uC140\uB2F9 \uD0C0\uC784\uC544\uC6C3 (\uCD08)")
|
|
398
|
+
},
|
|
399
|
+
async ({ path: filePath, timeout }) => {
|
|
400
|
+
const nbconvertArgs = `nbconvert --to notebook --execute --inplace "${filePath}" --ExecutePreprocessor.timeout=${timeout}`;
|
|
401
|
+
const candidates = [
|
|
402
|
+
"jupyter",
|
|
403
|
+
`${process.env.HOME}/Library/Python/3.9/bin/jupyter`,
|
|
404
|
+
`${process.env.HOME}/Library/Python/3.10/bin/jupyter`,
|
|
405
|
+
`${process.env.HOME}/Library/Python/3.11/bin/jupyter`,
|
|
406
|
+
`${process.env.HOME}/Library/Python/3.12/bin/jupyter`,
|
|
407
|
+
"/usr/local/bin/jupyter",
|
|
408
|
+
"/opt/homebrew/bin/jupyter"
|
|
409
|
+
];
|
|
410
|
+
for (const jupyter of candidates) {
|
|
411
|
+
try {
|
|
412
|
+
const { stdout, stderr } = await execAsync2(`${jupyter} ${nbconvertArgs}`);
|
|
413
|
+
return { content: [{ type: "text", text: stdout || stderr || "\uC2E4\uD589 \uC644\uB8CC" }] };
|
|
414
|
+
} catch (err) {
|
|
415
|
+
const error = err;
|
|
416
|
+
if (error.code !== "127" && !error.message?.includes("not found") && !error.message?.includes("No such file")) {
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
throw new Error("jupyter\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC124\uCE58 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694: pip install jupyter");
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// src/tools/device.ts
|
|
428
|
+
var import_child_process3 = require("child_process");
|
|
429
|
+
var import_util3 = require("util");
|
|
430
|
+
var import_zod4 = require("zod");
|
|
431
|
+
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
432
|
+
function platform() {
|
|
433
|
+
if (process.platform === "darwin") return "mac";
|
|
434
|
+
if (process.platform === "win32") return "win";
|
|
435
|
+
return "linux";
|
|
436
|
+
}
|
|
437
|
+
var DeviceTools = class {
|
|
438
|
+
register(server) {
|
|
439
|
+
server.tool(
|
|
440
|
+
"screen_capture",
|
|
441
|
+
"\uD654\uBA74 \uC2A4\uD06C\uB9B0\uC0F7 (OS \uB124\uC774\uD2F0\uBE0C)",
|
|
442
|
+
{
|
|
443
|
+
output_path: import_zod4.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 temp \uC800\uC7A5 \uD6C4 base64 \uBC18\uD658)")
|
|
444
|
+
},
|
|
445
|
+
async ({ output_path }) => {
|
|
446
|
+
const p = platform();
|
|
447
|
+
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
448
|
+
const cmd = {
|
|
449
|
+
mac: `screencapture -x "${tmpPath}"`,
|
|
450
|
+
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
451
|
+
linux: `scrot "${tmpPath}"`
|
|
452
|
+
}[p];
|
|
453
|
+
await execAsync3(cmd);
|
|
454
|
+
const { readFileSync } = await import("fs");
|
|
455
|
+
const data = readFileSync(tmpPath).toString("base64");
|
|
456
|
+
return {
|
|
457
|
+
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
server.tool(
|
|
462
|
+
"camera_capture",
|
|
463
|
+
"\uCE74\uBA54\uB77C \uC0AC\uC9C4 \uCD2C\uC601",
|
|
464
|
+
{
|
|
465
|
+
output_path: import_zod4.z.string().optional()
|
|
466
|
+
},
|
|
467
|
+
async ({ output_path }) => {
|
|
468
|
+
const p = platform();
|
|
469
|
+
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
470
|
+
const cmd = {
|
|
471
|
+
mac: `imagesnap "${tmpPath}"`,
|
|
472
|
+
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
473
|
+
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
474
|
+
}[p];
|
|
475
|
+
await execAsync3(cmd);
|
|
476
|
+
const { readFileSync } = await import("fs");
|
|
477
|
+
const data = readFileSync(tmpPath).toString("base64");
|
|
478
|
+
return {
|
|
479
|
+
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
server.tool(
|
|
484
|
+
"notification_send",
|
|
485
|
+
"OS \uC54C\uB9BC \uC804\uC1A1",
|
|
486
|
+
{
|
|
487
|
+
title: import_zod4.z.string().describe("\uC54C\uB9BC \uC81C\uBAA9"),
|
|
488
|
+
message: import_zod4.z.string().describe("\uC54C\uB9BC \uB0B4\uC6A9")
|
|
489
|
+
},
|
|
490
|
+
async ({ title, message }) => {
|
|
491
|
+
const p = platform();
|
|
492
|
+
const cmd = {
|
|
493
|
+
mac: `osascript -e 'display notification "${message}" with title "${title}"'`,
|
|
494
|
+
win: `powershell -Command "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime]::CreateToastNotifier('Junis').Show([Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime]::new())"`,
|
|
495
|
+
linux: `notify-send "${title}" "${message}"`
|
|
496
|
+
}[p];
|
|
497
|
+
await execAsync3(cmd);
|
|
498
|
+
return { content: [{ type: "text", text: "\uC54C\uB9BC \uC804\uC1A1 \uC644\uB8CC" }] };
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
server.tool(
|
|
502
|
+
"clipboard_read",
|
|
503
|
+
"\uD074\uB9BD\uBCF4\uB4DC \uC77D\uAE30",
|
|
504
|
+
{},
|
|
505
|
+
async () => {
|
|
506
|
+
const p = platform();
|
|
507
|
+
const cmd = { mac: "pbpaste", win: "powershell Get-Clipboard", linux: "xclip -o" }[p];
|
|
508
|
+
const { stdout } = await execAsync3(cmd);
|
|
509
|
+
return { content: [{ type: "text", text: stdout }] };
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
server.tool(
|
|
513
|
+
"clipboard_write",
|
|
514
|
+
"\uD074\uB9BD\uBCF4\uB4DC \uC4F0\uAE30",
|
|
515
|
+
{ text: import_zod4.z.string() },
|
|
516
|
+
async ({ text }) => {
|
|
517
|
+
const p = platform();
|
|
518
|
+
const cmd = {
|
|
519
|
+
mac: `echo "${text}" | pbcopy`,
|
|
520
|
+
win: `powershell Set-Clipboard "${text}"`,
|
|
521
|
+
linux: `echo "${text}" | xclip -selection clipboard`
|
|
522
|
+
}[p];
|
|
523
|
+
await execAsync3(cmd);
|
|
524
|
+
return { content: [{ type: "text", text: "\uD074\uB9BD\uBCF4\uB4DC \uC800\uC7A5 \uC644\uB8CC" }] };
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// src/server/stdio.ts
|
|
531
|
+
async function main() {
|
|
532
|
+
const server = new import_mcp.McpServer({ name: "junis", version: "0.1.0" });
|
|
533
|
+
const fsTools = new FilesystemTools();
|
|
534
|
+
fsTools.register(server);
|
|
535
|
+
const browserTools = new BrowserTools();
|
|
536
|
+
await browserTools.init();
|
|
537
|
+
browserTools.register(server);
|
|
538
|
+
const notebookTools = new NotebookTools();
|
|
539
|
+
notebookTools.register(server);
|
|
540
|
+
const deviceTools = new DeviceTools();
|
|
541
|
+
deviceTools.register(server);
|
|
542
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
543
|
+
await server.connect(transport);
|
|
544
|
+
process.on("SIGINT", async () => {
|
|
545
|
+
await browserTools.cleanup();
|
|
546
|
+
process.exit(0);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "junis",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One-line device control for AI agents",
|
|
5
|
+
"bin": {
|
|
6
|
+
"junis": "./dist/cli/index.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup src/cli/index.ts --format cjs --dts --out-dir dist/cli && tsup src/server/mcp.ts --format cjs --out-dir dist/server && tsup src/server/stdio.ts --format cjs --out-dir dist/server",
|
|
10
|
+
"dev": "tsx src/cli/index.ts",
|
|
11
|
+
"type-check": "tsc --noEmit",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"commander": "^12.0.0",
|
|
17
|
+
"glob": "^11.0.0",
|
|
18
|
+
"open": "^10.1.0",
|
|
19
|
+
"playwright": "^1.49.0",
|
|
20
|
+
"ws": "^8.18.0",
|
|
21
|
+
"zod": "^4.3.6"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"@types/ws": "^8.5.0",
|
|
26
|
+
"tsup": "^8.0.0",
|
|
27
|
+
"tsx": "^4.0.0",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/"
|
|
35
|
+
]
|
|
36
|
+
}
|