src / index.ts
import { createConfigSchematics, type PluginContext } from "@lmstudio/sdk";
import { Provider } from "./provider";
import { PluginConfig, PluginConfigSchema } from "./types";
import type { ArtifactFSProvider } from "./types";
const configSchematics = createConfigSchematics()
.field("enabled", "boolean", {
displayName: "Enable ArtifactFS",
}, true)
.field("maxChunkSize", "numeric", {
displayName: "Max Chunk Size (lines)",
min: 100,
max: 5000,
slider: { min: 100, max: 5000, step: 100 },
}, 1000)
.field("syncEnabled", "boolean", {
displayName: "Enable Sync",
}, true)
.build();
export async function main(context: PluginContext) {
context.withConfigSchematics(configSchematics);
context.withGenerator(async (ctl, history) => {
const config = ctl.getPluginConfig(configSchematics);
const pluginConfig: PluginConfig = PluginConfigSchema.parse({
enabled: config.get("enabled"),
maxChunkSize: config.get("maxChunkSize"),
syncEnabled: config.get("syncEnabled"),
});
const allMessages = history.getMessagesArray();
const lastUserMsg = [...allMessages]
.reverse()
.find((m) => m.getRole() === "user");
const lastUserMessage = lastUserMsg?.getText() ?? "";
const provider: ArtifactFSProvider = new Provider(pluginConfig, ctl.client);
if (lastUserMessage.startsWith("/")) {
await handleCommand(lastUserMessage, provider, ctl);
return;
}
const result = await provider.queryFiles({ text: lastUserMessage, limit: 5, offset: 0 });
const model = await ctl.client.llm.model();
const systemPrompt = `You are ArtifactFS file system assistant. Manage files, chunks, and metadata.
Available files:
${result.chunks.map((c) => `[${c.artifactId}]: ${c.content.slice(0, 200)}`).join("\n")}
Commands: /list, /read <path>, /write <path> <content>, /delete <id>, /search <text>, /sync-status`;
const recentHistory = allMessages.slice(-5).map((m) => ({
role: m.getRole() as "user" | "assistant" | "system",
content: m.getText(),
}));
const response = await model.respond(
[
{ role: "system", content: systemPrompt },
...recentHistory,
],
{ temperature: 0.7 },
);
ctl.fragmentGenerated(response.content);
});
}
async function handleCommand(cmd: string, provider: ArtifactFSProvider, ctl: any): Promise<void> {
const parts = cmd.slice(1).split(/\s+/);
const command = parts[0];
switch (command) {
case "help":
ctl.fragmentGenerated(
"Commands:\n /list [path] List files\n /read <path> Read file\n /write <path> <content> Write file\n /delete <id> Delete file\n /search <text> Search chunks\n /sync-status Show sync queue",
);
break;
case "list": {
const files = await provider.listFiles(parts[1]);
ctl.fragmentGenerated(files.map((f) => `${f.path} (v${f.version})`).join("\n") || "No files.");
break;
}
case "read": {
if (!parts[1]) { ctl.fragmentGenerated("Usage: /read <path>"); return; }
const content = await provider.readFile(parts[1]);
ctl.fragmentGenerated(content);
break;
}
case "write": {
if (!parts[1]) { ctl.fragmentGenerated("Usage: /write <path> <content>"); return; }
const artifact = await provider.writeFile(parts[1], parts.slice(2).join(" ") || "empty");
ctl.fragmentGenerated(`Written: ${artifact.path} (v${artifact.version})`);
break;
}
case "delete": {
if (!parts[1]) { ctl.fragmentGenerated("Usage: /delete <id>"); return; }
await provider.deleteFile(parts[1]);
ctl.fragmentGenerated(`Deleted: ${parts[1]}`);
break;
}
case "search": {
const result = await provider.queryFiles({ text: parts.slice(1).join(" "), limit: 10, offset: 0 });
ctl.fragmentGenerated(
result.chunks.length > 0
? result.chunks.map((c) => `[${c.artifactId}:${c.startLine}-${c.endLine}] ${c.content.slice(0, 100)}`).join("\n\n")
: "No matches found.",
);
break;
}
case "sync-status": {
const pending = await provider.sync();
ctl.fragmentGenerated(pending.length > 0 ? `Pending sync: ${pending.length} events` : "All synced.");
break;
}
default:
ctl.fragmentGenerated(`Unknown: /${command}. Try /help`);
}
}