comfyui-mcp
Version:
Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),
131 lines • 4.58 kB
JavaScript
import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
import { stat } from "node:fs/promises";
import { basename } from "node:path";
import { logger } from "../utils/logger.js";
const INIT_SQL = `
CREATE TABLE IF NOT EXISTS file_hashes (
filename TEXT PRIMARY KEY,
file_path TEXT NOT NULL,
file_size INTEGER NOT NULL,
file_mtime TEXT NOT NULL,
sha256 TEXT NOT NULL,
autov2 TEXT NOT NULL,
file_type TEXT NOT NULL,
civitai_id INTEGER,
civitai_name TEXT,
civitai_model_id INTEGER,
checked_at TEXT
);
`;
export class FileHasher {
db;
constructor(db) {
this.db = db;
this.db.exec(INIT_SQL);
}
/**
* Get or compute the hash for a file. Uses cache if file hasn't changed.
*/
async getHash(filePath, fileType) {
const filename = basename(filePath);
const info = await stat(filePath);
const fileSize = info.size;
const fileMtime = info.mtime.toISOString();
// Check cache
const cached = this.db
.prepare(`SELECT * FROM file_hashes WHERE filename = ? AND file_size = ? AND file_mtime = ?`)
.get(filename, fileSize, fileMtime);
if (cached) {
return {
filename,
filePath,
sha256: cached.sha256,
autov2: cached.autov2,
fileType: cached.file_type,
civitaiId: cached.civitai_id,
civitaiName: cached.civitai_name,
civitaiModelId: cached.civitai_model_id,
};
}
// Compute SHA256
logger.info(`Hashing ${filename} (${(fileSize / 1024 / 1024).toFixed(0)} MB)...`);
const sha256 = await computeSHA256(filePath);
const autov2 = sha256.slice(0, 10).toUpperCase();
logger.info(`Hash complete: ${filename} → ${autov2}`);
// Upsert into cache
this.db
.prepare(`INSERT INTO file_hashes (filename, file_path, file_size, file_mtime, sha256, autov2, file_type)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(filename) DO UPDATE SET
file_path = excluded.file_path,
file_size = excluded.file_size,
file_mtime = excluded.file_mtime,
sha256 = excluded.sha256,
autov2 = excluded.autov2,
file_type = excluded.file_type`)
.run(filename, filePath, fileSize, fileMtime, sha256, autov2, fileType);
return {
filename,
filePath,
sha256,
autov2,
fileType,
civitaiId: null,
civitaiName: null,
civitaiModelId: null,
};
}
/**
* Update CivitAI metadata for a cached file hash.
*/
updateCivitaiInfo(filename, civitaiId, civitaiName, civitaiModelId) {
this.db
.prepare(`UPDATE file_hashes
SET civitai_id = ?, civitai_name = ?, civitai_model_id = ?, checked_at = datetime('now')
WHERE filename = ?`)
.run(civitaiId, civitaiName, civitaiModelId, filename);
}
/**
* Look up a cached hash by filename (no recomputation).
*/
getCached(filename) {
const row = this.db
.prepare(`SELECT * FROM file_hashes WHERE filename = ?`)
.get(filename);
if (!row)
return null;
return {
filename: row.filename,
filePath: row.file_path,
sha256: row.sha256,
autov2: row.autov2,
fileType: row.file_type,
civitaiId: row.civitai_id,
civitaiName: row.civitai_name,
civitaiModelId: row.civitai_model_id,
};
}
/**
* Check if a file needs CivitAI lookup (no civitai check recorded).
*/
needsCivitaiCheck(filename) {
const row = this.db
.prepare(`SELECT checked_at FROM file_hashes WHERE filename = ?`)
.get(filename);
return !row || row.checked_at === null;
}
}
/**
* Stream-based SHA256 for large files. Doesn't load the whole file into memory.
*/
function computeSHA256(filePath) {
return new Promise((resolve, reject) => {
const hash = createHash("sha256");
const stream = createReadStream(filePath);
stream.on("data", (chunk) => hash.update(chunk));
stream.on("end", () => resolve(hash.digest("hex")));
stream.on("error", reject);
});
}
//# sourceMappingURL=file-hasher.js.map