@aurora-foundation/obsidian-next 0.2.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/LICENSE +190 -0
- package/README.md +138 -0
- package/dist/index.js +3736 -0
- package/dist/mcp/index.js +448 -0
- package/package.json +71 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/mcp/server.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import fs from "fs/promises";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { exec } from "child_process";
|
|
10
|
+
import { promisify } from "util";
|
|
11
|
+
var execAsync = promisify(exec);
|
|
12
|
+
var MAX_OUTPUT_LENGTH = 1e4;
|
|
13
|
+
var MAX_FILE_READ_LINES = 500;
|
|
14
|
+
var IGNORED_DIRS = ["node_modules", ".git", "dist", ".next", "__pycache__", ".cache"];
|
|
15
|
+
function truncateOutput(output, maxLength = MAX_OUTPUT_LENGTH) {
|
|
16
|
+
if (output.length <= maxLength) return output;
|
|
17
|
+
return `${output.slice(0, maxLength)}
|
|
18
|
+
|
|
19
|
+
... [TRUNCATED: ${output.length - maxLength} more characters]`;
|
|
20
|
+
}
|
|
21
|
+
function createMcpServer() {
|
|
22
|
+
const server = new McpServer(
|
|
23
|
+
{
|
|
24
|
+
name: "obsidian-next",
|
|
25
|
+
version: "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
capabilities: {
|
|
29
|
+
tools: {},
|
|
30
|
+
resources: {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
server.registerTool(
|
|
35
|
+
"bash",
|
|
36
|
+
{
|
|
37
|
+
title: "Execute Shell Command",
|
|
38
|
+
description: "Execute a shell command in the workspace. Use for git, npm, and other CLI operations.",
|
|
39
|
+
inputSchema: {
|
|
40
|
+
command: z.string().describe("The shell command to execute"),
|
|
41
|
+
timeout: z.number().optional().describe("Timeout in milliseconds (default: 30000)")
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
async ({ command, timeout }) => {
|
|
45
|
+
const blocked = ["rm -rf /", "mkfs", "dd if=", ":(){:|:&};:"];
|
|
46
|
+
if (blocked.some((p) => command.includes(p))) {
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: "Error: Blocked dangerous command pattern" }],
|
|
49
|
+
isError: true
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
54
|
+
cwd: process.cwd(),
|
|
55
|
+
timeout: timeout || 3e4,
|
|
56
|
+
maxBuffer: 1024 * 1024
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
content: [{ type: "text", text: truncateOutput(stdout || stderr || "Command executed successfully") }]
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
64
|
+
isError: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
server.registerTool(
|
|
70
|
+
"read",
|
|
71
|
+
{
|
|
72
|
+
title: "Read File",
|
|
73
|
+
description: "Read contents of a file with line numbers",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
path: z.string().describe("File path relative to workspace"),
|
|
76
|
+
offset: z.number().optional().describe("Starting line (1-indexed)"),
|
|
77
|
+
limit: z.number().optional().describe("Number of lines to read")
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
async ({ path: filePath, offset, limit }) => {
|
|
81
|
+
try {
|
|
82
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
83
|
+
if (!fullPath.startsWith(process.cwd())) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: "Error: Path outside workspace" }],
|
|
86
|
+
isError: true
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
90
|
+
const lines = content.split("\n");
|
|
91
|
+
const startLine = (offset || 1) - 1;
|
|
92
|
+
const endLine = limit ? startLine + limit : Math.min(startLine + MAX_FILE_READ_LINES, lines.length);
|
|
93
|
+
const selectedLines = lines.slice(startLine, endLine);
|
|
94
|
+
const numbered = selectedLines.map((line, i) => `${String(startLine + i + 1).padStart(4)} | ${line}`).join("\n");
|
|
95
|
+
const truncationNote = lines.length > endLine ? `
|
|
96
|
+
|
|
97
|
+
... [${lines.length - endLine} more lines]` : "";
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: `File: ${filePath} (${lines.length} lines)
|
|
102
|
+
${"=".repeat(60)}
|
|
103
|
+
${numbered}${truncationNote}`
|
|
104
|
+
}]
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
109
|
+
isError: true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
server.registerTool(
|
|
115
|
+
"write",
|
|
116
|
+
{
|
|
117
|
+
title: "Create File",
|
|
118
|
+
description: "Create a new file (fails if file exists)",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
path: z.string().describe("File path relative to workspace"),
|
|
121
|
+
content: z.string().describe("File content")
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
async ({ path: filePath, content }) => {
|
|
125
|
+
try {
|
|
126
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
127
|
+
if (!fullPath.startsWith(process.cwd())) {
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: "text", text: "Error: Path outside workspace" }],
|
|
130
|
+
isError: true
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
await fs.access(fullPath);
|
|
135
|
+
return {
|
|
136
|
+
content: [{ type: "text", text: "Error: File already exists. Use edit tool instead." }],
|
|
137
|
+
isError: true
|
|
138
|
+
};
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
142
|
+
await fs.writeFile(fullPath, content, "utf-8");
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: "text", text: `Created: ${filePath} (${content.length} bytes)` }]
|
|
145
|
+
};
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
149
|
+
isError: true
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
server.registerTool(
|
|
155
|
+
"edit",
|
|
156
|
+
{
|
|
157
|
+
title: "Edit File",
|
|
158
|
+
description: "Edit an existing file using search/replace",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
path: z.string().describe("File path relative to workspace"),
|
|
161
|
+
search: z.string().describe("Text to find"),
|
|
162
|
+
replace: z.string().describe("Text to replace with")
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
async ({ path: filePath, search, replace }) => {
|
|
166
|
+
try {
|
|
167
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
168
|
+
if (!fullPath.startsWith(process.cwd())) {
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: "Error: Path outside workspace" }],
|
|
171
|
+
isError: true
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const original = await fs.readFile(fullPath, "utf-8");
|
|
175
|
+
if (!original.includes(search)) {
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: "text", text: "Error: Search string not found in file" }],
|
|
178
|
+
isError: true
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const modified = original.replace(search, replace);
|
|
182
|
+
await fs.writeFile(fullPath, modified, "utf-8");
|
|
183
|
+
const originalLines = original.split("\n").length;
|
|
184
|
+
const modifiedLines = modified.split("\n").length;
|
|
185
|
+
const delta = modifiedLines - originalLines;
|
|
186
|
+
return {
|
|
187
|
+
content: [{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: `Edited: ${filePath}
|
|
190
|
+
Lines: ${originalLines} -> ${modifiedLines} (${delta >= 0 ? "+" : ""}${delta})`
|
|
191
|
+
}]
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
196
|
+
isError: true
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
server.registerTool(
|
|
202
|
+
"list",
|
|
203
|
+
{
|
|
204
|
+
title: "List Directory",
|
|
205
|
+
description: "List files and directories",
|
|
206
|
+
inputSchema: {
|
|
207
|
+
path: z.string().optional().describe("Directory path (default: current directory)")
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
async ({ path: dirPath }) => {
|
|
211
|
+
try {
|
|
212
|
+
const fullPath = path.resolve(process.cwd(), dirPath || ".");
|
|
213
|
+
if (!fullPath.startsWith(process.cwd())) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: "Error: Path outside workspace" }],
|
|
216
|
+
isError: true
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const entries = await fs.readdir(fullPath, { withFileTypes: true });
|
|
220
|
+
const filtered = entries.filter(
|
|
221
|
+
(e) => !IGNORED_DIRS.includes(e.name) && !e.name.startsWith(".")
|
|
222
|
+
);
|
|
223
|
+
const formatted = filtered.map((e) => `${e.isDirectory() ? "[DIR]" : "[FILE]"} ${e.name}`).join("\n");
|
|
224
|
+
const hiddenCount = entries.length - filtered.length;
|
|
225
|
+
const note = hiddenCount > 0 ? `
|
|
226
|
+
|
|
227
|
+
(${hiddenCount} hidden)` : "";
|
|
228
|
+
return {
|
|
229
|
+
content: [{ type: "text", text: `Directory: ${dirPath || "."}
|
|
230
|
+
${formatted}${note}` }]
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
return {
|
|
234
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
235
|
+
isError: true
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
server.registerTool(
|
|
241
|
+
"grep",
|
|
242
|
+
{
|
|
243
|
+
title: "Search Content",
|
|
244
|
+
description: "Search for patterns in files using regex",
|
|
245
|
+
inputSchema: {
|
|
246
|
+
pattern: z.string().describe("Regex pattern to search"),
|
|
247
|
+
path: z.string().optional().describe("Directory to search (default: current)"),
|
|
248
|
+
limit: z.number().optional().describe("Max results (default: 50)")
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
async ({ pattern, path: searchPath, limit }) => {
|
|
252
|
+
try {
|
|
253
|
+
const maxResults = limit || 50;
|
|
254
|
+
const results = [];
|
|
255
|
+
async function searchDir(dir, depth = 0) {
|
|
256
|
+
if (results.length >= maxResults || depth > 10) return;
|
|
257
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
258
|
+
const regex = new RegExp(pattern, "gi");
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
if (results.length >= maxResults) break;
|
|
261
|
+
if (entry.name.startsWith(".") || IGNORED_DIRS.includes(entry.name)) continue;
|
|
262
|
+
const fullPath = path.join(dir, entry.name);
|
|
263
|
+
const relativePath = path.relative(process.cwd(), fullPath);
|
|
264
|
+
if (entry.isDirectory()) {
|
|
265
|
+
await searchDir(fullPath, depth + 1);
|
|
266
|
+
} else if (entry.isFile()) {
|
|
267
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
268
|
+
const textExts = [".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".txt", ".yaml", ".yml", ".css", ".html"];
|
|
269
|
+
if (textExts.includes(ext) || ext === "") {
|
|
270
|
+
try {
|
|
271
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
272
|
+
const lines = content.split("\n");
|
|
273
|
+
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
274
|
+
if (regex.test(lines[i])) {
|
|
275
|
+
results.push(`${relativePath}:${i + 1}: ${lines[i].trim().slice(0, 100)}`);
|
|
276
|
+
}
|
|
277
|
+
regex.lastIndex = 0;
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const startPath = path.resolve(process.cwd(), searchPath || ".");
|
|
286
|
+
await searchDir(startPath);
|
|
287
|
+
if (results.length === 0) {
|
|
288
|
+
return { content: [{ type: "text", text: `No matches for: ${pattern}` }] };
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
content: [{
|
|
292
|
+
type: "text",
|
|
293
|
+
text: `Found ${results.length} matches:
|
|
294
|
+
${results.join("\n")}`
|
|
295
|
+
}]
|
|
296
|
+
};
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return {
|
|
299
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
300
|
+
isError: true
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
server.registerTool(
|
|
306
|
+
"glob",
|
|
307
|
+
{
|
|
308
|
+
title: "Find Files",
|
|
309
|
+
description: "Find files matching a glob pattern",
|
|
310
|
+
inputSchema: {
|
|
311
|
+
pattern: z.string().describe("Glob pattern (e.g., **/*.ts, src/**/*.tsx)"),
|
|
312
|
+
path: z.string().optional().describe("Base directory")
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
async ({ pattern, path: basePath }) => {
|
|
316
|
+
try {
|
|
317
|
+
const results = [];
|
|
318
|
+
const maxResults = 100;
|
|
319
|
+
async function globSearch(dir, depth = 0) {
|
|
320
|
+
if (results.length >= maxResults || depth > 15) return;
|
|
321
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
322
|
+
const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
|
|
323
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
324
|
+
for (const entry of entries) {
|
|
325
|
+
if (results.length >= maxResults) break;
|
|
326
|
+
if (entry.name.startsWith(".") || IGNORED_DIRS.includes(entry.name)) continue;
|
|
327
|
+
const fullPath = path.join(dir, entry.name);
|
|
328
|
+
const relativePath = path.relative(process.cwd(), fullPath);
|
|
329
|
+
if (entry.isDirectory()) {
|
|
330
|
+
if (pattern.includes("**") || pattern.includes("/")) {
|
|
331
|
+
await globSearch(fullPath, depth + 1);
|
|
332
|
+
}
|
|
333
|
+
} else if (entry.isFile()) {
|
|
334
|
+
if (regex.test(relativePath) || regex.test(entry.name)) {
|
|
335
|
+
results.push(relativePath);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const startPath = path.resolve(process.cwd(), basePath || ".");
|
|
341
|
+
await globSearch(startPath);
|
|
342
|
+
if (results.length === 0) {
|
|
343
|
+
return { content: [{ type: "text", text: `No files matching: ${pattern}` }] };
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
content: [{ type: "text", text: `Found ${results.length} files:
|
|
347
|
+
${results.join("\n")}` }]
|
|
348
|
+
};
|
|
349
|
+
} catch (error) {
|
|
350
|
+
return {
|
|
351
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
352
|
+
isError: true
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
server.registerTool(
|
|
358
|
+
"web_fetch",
|
|
359
|
+
{
|
|
360
|
+
title: "Fetch URL",
|
|
361
|
+
description: "Fetch content from a URL",
|
|
362
|
+
inputSchema: {
|
|
363
|
+
url: z.string().url().describe("URL to fetch")
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
async ({ url }) => {
|
|
367
|
+
try {
|
|
368
|
+
const urlObj = new URL(url);
|
|
369
|
+
const blocked = ["localhost", "127.0.0.1", "0.0.0.0"];
|
|
370
|
+
if (blocked.some((d) => urlObj.hostname.includes(d))) {
|
|
371
|
+
return {
|
|
372
|
+
content: [{ type: "text", text: "Error: Cannot fetch from local addresses" }],
|
|
373
|
+
isError: true
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const controller = new AbortController();
|
|
377
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
378
|
+
const response = await fetch(url, {
|
|
379
|
+
signal: controller.signal,
|
|
380
|
+
headers: { "User-Agent": "Obsidian-Next-MCP/1.0" }
|
|
381
|
+
});
|
|
382
|
+
clearTimeout(timeoutId);
|
|
383
|
+
if (!response.ok) {
|
|
384
|
+
return {
|
|
385
|
+
content: [{ type: "text", text: `Error: HTTP ${response.status}` }],
|
|
386
|
+
isError: true
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
let content = await response.text();
|
|
390
|
+
const contentType = response.headers.get("content-type") || "";
|
|
391
|
+
if (contentType.includes("text/html")) {
|
|
392
|
+
content = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
content: [{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: truncateOutput(`URL: ${url}
|
|
398
|
+
Content-Type: ${contentType}
|
|
399
|
+
${"=".repeat(60)}
|
|
400
|
+
${content}`)
|
|
401
|
+
}]
|
|
402
|
+
};
|
|
403
|
+
} catch (error) {
|
|
404
|
+
return {
|
|
405
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
406
|
+
isError: true
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
server.registerResource(
|
|
412
|
+
"workspace",
|
|
413
|
+
"obsidian://workspace",
|
|
414
|
+
{
|
|
415
|
+
description: "Current workspace information"
|
|
416
|
+
},
|
|
417
|
+
async () => ({
|
|
418
|
+
contents: [{
|
|
419
|
+
uri: "obsidian://workspace",
|
|
420
|
+
mimeType: "application/json",
|
|
421
|
+
text: JSON.stringify({
|
|
422
|
+
cwd: process.cwd(),
|
|
423
|
+
name: path.basename(process.cwd())
|
|
424
|
+
}, null, 2)
|
|
425
|
+
}]
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
return server;
|
|
429
|
+
}
|
|
430
|
+
async function startMcpServer() {
|
|
431
|
+
const server = createMcpServer();
|
|
432
|
+
const transport = new StdioServerTransport();
|
|
433
|
+
await server.connect(transport);
|
|
434
|
+
process.on("SIGINT", async () => {
|
|
435
|
+
await server.close();
|
|
436
|
+
process.exit(0);
|
|
437
|
+
});
|
|
438
|
+
process.on("SIGTERM", async () => {
|
|
439
|
+
await server.close();
|
|
440
|
+
process.exit(0);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/mcp/index.ts
|
|
445
|
+
startMcpServer().catch((error) => {
|
|
446
|
+
console.error("Failed to start MCP server:", error);
|
|
447
|
+
process.exit(1);
|
|
448
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aurora-foundation/obsidian-next",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Next-gen AI Agent CLI",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/auroradeveloperops/obsidian-next.git"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ai",
|
|
19
|
+
"agent",
|
|
20
|
+
"cli",
|
|
21
|
+
"mcp",
|
|
22
|
+
"anthropic",
|
|
23
|
+
"terminal",
|
|
24
|
+
"automation"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"obsidian": "dist/index.js",
|
|
31
|
+
"obsidian-mcp": "dist/mcp.js"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
35
|
+
"build": "tsup src/index.ts src/mcp/index.ts --format esm",
|
|
36
|
+
"start": "node dist/index.js",
|
|
37
|
+
"mcp": "node dist/mcp/index.js",
|
|
38
|
+
"test": "vitest"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@anthropic-ai/sandbox-runtime": "^0.0.32",
|
|
42
|
+
"@anthropic-ai/sdk": "^0.33.1",
|
|
43
|
+
"@inkjs/ui": "^1.0.0",
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.0.1",
|
|
45
|
+
"@prisma/client": "^6.1.0",
|
|
46
|
+
"bullmq": "^5.34.0",
|
|
47
|
+
"chalk": "^5.3.0",
|
|
48
|
+
"dotenv": "^16.4.5",
|
|
49
|
+
"ink": "^4.4.1",
|
|
50
|
+
"ink-spinner": "^5.0.0",
|
|
51
|
+
"ink-text-input": "^5.0.1",
|
|
52
|
+
"ioredis": "^5.4.1",
|
|
53
|
+
"pg": "^8.13.1",
|
|
54
|
+
"react": "^18.3.1",
|
|
55
|
+
"zod": "^3.23.8"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^20.14.9",
|
|
59
|
+
"@types/react": "^18.3.3",
|
|
60
|
+
"ink-testing-library": "^3.0.0",
|
|
61
|
+
"pkg": "^5.8.1",
|
|
62
|
+
"prisma": "^6.1.0",
|
|
63
|
+
"tsup": "^8.1.0",
|
|
64
|
+
"typescript": "^5.5.3",
|
|
65
|
+
"vitest": "^2.0.1"
|
|
66
|
+
},
|
|
67
|
+
"author": "Aurora Labs <aurora.foundation.labs@gmail.com>",
|
|
68
|
+
"contributors": [
|
|
69
|
+
"Polyoxy <iversonbusiness3@gmail.com>"
|
|
70
|
+
]
|
|
71
|
+
}
|