mirror of
https://github.com/run-llama/agentfs-claude.git
synced 2026-06-30 21:27:55 -04:00
first commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
@@ -0,0 +1,3 @@
|
||||
# Claude + AgentFS + LlamaIndex Workflows
|
||||
|
||||
Attempt to run Claude Code within a fully-virtualized file system (AgentFS), orchestrating it with LlamaIndex Workflows.
|
||||
@@ -0,0 +1,9 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "agentfs-claude",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"eslint": "^9.39.1",
|
||||
"globals": "^16.5.0",
|
||||
"jiti": "^2.6.1",
|
||||
"prettier": "^3.7.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.59",
|
||||
"@llamaindex/workflow-core": "^1.3.3",
|
||||
"@modelcontextprotocol/sdk": "^1.24.3",
|
||||
"agentfs-sdk": "^0.2.1",
|
||||
"mime-types": "^3.0.2",
|
||||
"zod": "3.25.76"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import {type FileWithContent} from "./types";
|
||||
import path from 'path';
|
||||
import * as mime from 'mime-types';
|
||||
import { AgentFS } from 'agentfs-sdk';
|
||||
|
||||
async function getFilesInDir({dirPath = "./"}: {dirPath?: string}): Promise<FileWithContent[]> {
|
||||
const files: FileWithContent[] = []
|
||||
try {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
const pt = path.join(dirPath, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
const subFiles = await getFilesInDir({dirPath: pt})
|
||||
files.push(...subFiles)
|
||||
} else {
|
||||
const mimeType = mime.lookup(pt)
|
||||
if (typeof mimeType === "string" && mimeType.startsWith("text/")) {
|
||||
const content = await fs.readFile(pt, {encoding: "utf-8"})
|
||||
files.push({filePath: pt, content: content})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
export async function recordFiles(agentfs: AgentFS): Promise<boolean> {
|
||||
try {
|
||||
const files = await getFilesInDir({})
|
||||
for (const file of files) {
|
||||
await agentfs.fs.writeFile(file.filePath, file.content)
|
||||
}
|
||||
return true
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function readFile(filePath: string, agentfs: AgentFS): Promise<string | null> {
|
||||
let content: string | null = null
|
||||
try {
|
||||
content = await agentfs.fs.readFile(filePath, "utf-8") as string
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
export async function writeFile(filePath: string, fileContent: string, agentfs: AgentFS): Promise<boolean> {
|
||||
try {
|
||||
await agentfs.fs.writeFile(filePath, fileContent)
|
||||
return true
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function editFile(filePath: string, oldString: string, newString: string, agentfs: AgentFS): Promise<string | null> {
|
||||
let editedContent: string | null = null
|
||||
try {
|
||||
const content = await agentfs.fs.readFile(filePath, "utf-8") as string
|
||||
editedContent = content.replace(oldString, newString)
|
||||
await agentfs.fs.writeFile(filePath, editedContent)
|
||||
return editedContent
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
return editedContent
|
||||
}
|
||||
}
|
||||
|
||||
export async function fileExists(filePath: string, agentfs: AgentFS): Promise<boolean> {
|
||||
try {
|
||||
const dirPath = path.dirname(filePath)
|
||||
const files = await agentfs.fs.readdir(dirPath)
|
||||
for (const file of files) {
|
||||
if (file == filePath || file == path.basename(filePath)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function listFiles(agentfs: AgentFS): Promise<string> {
|
||||
const dirPath = "./"
|
||||
let availableFiles: string = ""
|
||||
try {
|
||||
const files = await agentfs.fs.readdir(dirPath)
|
||||
availableFiles += "AVAILABLE FILES:\n"
|
||||
for (const file of files) {
|
||||
availableFiles += file + ", "
|
||||
}
|
||||
return availableFiles.trim()
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
return availableFiles
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
import { AgentFS } from "agentfs-sdk";
|
||||
import * as z from "zod";
|
||||
import { type CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { editFile, fileExists, listFiles, readFile, writeFile } from "./filesystem";
|
||||
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
const readSchemaShape = {
|
||||
filePath: z.string().describe("Path of the file to read")
|
||||
}
|
||||
|
||||
const writeSchemaShape = {
|
||||
filePath: z.string().describe("Path of the file to write"),
|
||||
fileContent: z.string().describe("Content of the file")
|
||||
}
|
||||
|
||||
const editSchemaShape = {
|
||||
filePath: z.string().describe("Path of the file to write"),
|
||||
oldString: z.string().describe("Old string in the file (to replace)"),
|
||||
newString: z.string().describe("New string with which to replace the old string"),
|
||||
}
|
||||
|
||||
const fileExistsSchemaShape = {
|
||||
filePath: z.string().describe("Path of the file whose existence has to be checked")
|
||||
}
|
||||
|
||||
const listFilesSchemaShape = {}
|
||||
|
||||
// eslint-disable-next-line
|
||||
const readSchema = z.object(readSchemaShape);
|
||||
// eslint-disable-next-line
|
||||
const writeSchema = z.object(writeSchemaShape);
|
||||
// eslint-disable-next-line
|
||||
const editSchema = z.object(editSchemaShape);
|
||||
// eslint-disable-next-line
|
||||
const fileExistsSchema = z.object(fileExistsSchemaShape)
|
||||
// eslint-disable-next-line
|
||||
const listFilesSchema = z.object(listFilesSchemaShape)
|
||||
|
||||
async function getAgentFS({filePath = null}: {filePath?: string | null}): Promise<AgentFS> {
|
||||
if (!filePath) {
|
||||
filePath = "fs.db"
|
||||
}
|
||||
const agentfs = await AgentFS.open({ id: "claude-agentfs", path: filePath})
|
||||
return agentfs
|
||||
}
|
||||
|
||||
async function readTool(input: z.infer<typeof readSchema>): Promise<CallToolResult> {
|
||||
const agentfs = await getAgentFS({})
|
||||
const content = await readFile(input.filePath, agentfs)
|
||||
if (typeof content == "string") {
|
||||
return {content: [{type: "text", text: content}]}
|
||||
} else {
|
||||
return {content: [{type: "text", text: `Could not read ${input.filePath}. Please check that the file exists and submit the request again.`}], isError: true}
|
||||
}
|
||||
}
|
||||
|
||||
async function fileExistsTool(input: z.infer<typeof fileExistsSchema>): Promise<CallToolResult> {
|
||||
const agentfs = await getAgentFS({})
|
||||
const exists = await fileExists(input.filePath, agentfs)
|
||||
if (exists) {
|
||||
return {content: [{type: "text", text: `File ${input.filePath} exists`}]}
|
||||
} else {
|
||||
return {content: [{type: "text", text: `File ${input.filePath} does not exist.`}]}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeFileTool(input: z.infer<typeof writeSchema>): Promise<CallToolResult> {
|
||||
const agentfs = await getAgentFS({})
|
||||
const success = await writeFile(input.filePath, input.fileContent, agentfs)
|
||||
if (success) {
|
||||
return {content: [{type: "text", text: `File ${input.filePath} successfully written with content:\n\n'''\n${input.fileContent}\n'''`}]}
|
||||
} else {
|
||||
return {content: [{type: "text", text: `There was an error while writing file ${input.filePath}`}]}
|
||||
}
|
||||
}
|
||||
|
||||
async function editFileTool(input: z.infer<typeof editSchema>): Promise<CallToolResult> {
|
||||
const agentfs = await getAgentFS({})
|
||||
const editedContent = await editFile(input.filePath, input.oldString, input.newString, agentfs)
|
||||
if (typeof editedContent == "string") {
|
||||
return {content: [{type: "text", text: `Successfully edited ${input.filePath}. New content:\n\n'''\n${editedContent}\n'''`}]} as CallToolResult
|
||||
} else {
|
||||
return {content: [{type: "text", text: `Could not edit ${input.filePath}. Please check that the file exists and submit the request again.`}], isError: true}
|
||||
}
|
||||
}
|
||||
|
||||
async function listFilesTool(input: z.infer<typeof listFilesSchema>): Promise<CallToolResult> {
|
||||
const agentfs = await getAgentFS(input)
|
||||
const files = await listFiles(agentfs)
|
||||
if (files != "") {
|
||||
return {content: [{type: "text", text: files}]}
|
||||
} else {
|
||||
return {content: [{type: "text", text: `Could not list files. Please report this failure to the user`}], isError: true}
|
||||
}
|
||||
}
|
||||
|
||||
const mcpReadTool = tool(
|
||||
"read_file",
|
||||
"Read a file by passing its path.",
|
||||
readSchemaShape,
|
||||
readTool,
|
||||
)
|
||||
|
||||
const mcpWriteTool = tool(
|
||||
"write_file",
|
||||
"Write a file by passing its path and content.",
|
||||
writeSchemaShape,
|
||||
writeFileTool,
|
||||
)
|
||||
|
||||
const mcpEditTool = tool(
|
||||
"edit_file",
|
||||
"Edit a file by passing its path, the old string and the new string.",
|
||||
editSchemaShape,
|
||||
editFileTool
|
||||
)
|
||||
|
||||
const mcpFileExists = tool(
|
||||
"file_exists",
|
||||
"Check whether a file exists or not by passing its path.",
|
||||
fileExistsSchemaShape,
|
||||
fileExistsTool,
|
||||
)
|
||||
|
||||
const mcplistFiles = tool(
|
||||
"list_files",
|
||||
"List all the available files",
|
||||
listFilesSchemaShape,
|
||||
listFilesTool,
|
||||
)
|
||||
|
||||
export const fileSystemMCP = createSdkMcpServer({
|
||||
name: "my-custom-tools",
|
||||
version: "1.0.0",
|
||||
tools: [
|
||||
mcpReadTool,
|
||||
mcpWriteTool,
|
||||
mcplistFiles,
|
||||
mcpEditTool,
|
||||
mcpFileExists,
|
||||
]
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
export type FileWithContent = {
|
||||
filePath: string
|
||||
content: string
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"types": ["node"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user