fixing imports

This commit is contained in:
2026-02-04 21:32:30 -05:00
parent 74b0a0dbab
commit db79856b08
166 changed files with 1986 additions and 982 deletions

View File

@@ -448,9 +448,7 @@ const pullFromCloud = async (
/**
* Check if pulled item conflicts with local changes
*/
const checkLocalConflict = async (
_item: SyncItem,
): Promise<boolean> => {
const checkLocalConflict = async (_item: SyncItem): Promise<boolean> => {
// Check if we have pending changes for this item
const queued = await hasQueuedItems();
return queued;

View File

@@ -4,9 +4,7 @@
* Handles sync conflicts between local and remote brain data.
*/
import {
CONFLICT_LABELS,
} from "@constants/brain-cloud";
import { CONFLICT_LABELS } from "@constants/brain-cloud";
import type {
SyncConflict,
ConflictStrategy,
@@ -83,21 +81,22 @@ export const resolveAllConflicts = (
/**
* Conflict resolution strategies
*/
const resolvers: Record<ConflictStrategy, (conflict: SyncConflict) => unknown> = {
"local-wins": (conflict) => conflict.localData,
const resolvers: Record<ConflictStrategy, (conflict: SyncConflict) => unknown> =
{
"local-wins": (conflict) => conflict.localData,
"remote-wins": (conflict) => conflict.remoteData,
"remote-wins": (conflict) => conflict.remoteData,
manual: (_conflict) => {
// Manual resolution returns null - requires user input
return null;
},
manual: (_conflict) => {
// Manual resolution returns null - requires user input
return null;
},
merge: (conflict) => {
// Attempt to merge the data
return mergeData(conflict.localData, conflict.remoteData);
},
};
merge: (conflict) => {
// Attempt to merge the data
return mergeData(conflict.localData, conflict.remoteData);
},
};
/**
* Attempt to merge two data objects
@@ -119,7 +118,9 @@ const mergeData = (local: unknown, remote: unknown): unknown => {
// Use most recent timestamp
const localTime = (localObj.updatedAt ?? localObj.timestamp ?? 0) as number;
const remoteTime = (remoteObj.updatedAt ?? remoteObj.timestamp ?? 0) as number;
const remoteTime = (remoteObj.updatedAt ??
remoteObj.timestamp ??
0) as number;
merged.updatedAt = Math.max(localTime, remoteTime);
return merged;

View File

@@ -3,7 +3,12 @@
* Exposes Brain as an MCP server for external tools
*/
import { createServer, type Server, type IncomingMessage, type ServerResponse } from "node:http";
import {
createServer,
type Server,
type IncomingMessage,
type ServerResponse,
} from "node:http";
import type {
BrainMcpServerConfig,
@@ -20,16 +25,26 @@ import {
BRAIN_MCP_TOOLS,
MCP_ERROR_CODES,
} from "@/types/brain-mcp";
import {
BRAIN_MCP_MESSAGES,
BRAIN_MCP_ERRORS,
} from "@constants/brain-mcp";
import { BRAIN_MCP_MESSAGES, BRAIN_MCP_ERRORS } from "@constants/brain-mcp";
type BrainService = {
recall: (query: string, limit?: number) => Promise<unknown>;
learn: (name: string, whatItDoes: string, options?: unknown) => Promise<unknown>;
searchMemories: (query: string, limit?: number, type?: string) => Promise<unknown>;
relate: (source: string, target: string, type: string, weight?: number) => Promise<unknown>;
learn: (
name: string,
whatItDoes: string,
options?: unknown,
) => Promise<unknown>;
searchMemories: (
query: string,
limit?: number,
type?: string,
) => Promise<unknown>;
relate: (
source: string,
target: string,
type: string,
weight?: number,
) => Promise<unknown>;
getContext: (query: string, maxConcepts?: number) => Promise<string>;
getStats: () => Promise<unknown>;
isConnected: () => boolean;
@@ -59,7 +74,11 @@ const state: McpServerState = {
apiKeys: new Set(),
};
const createMcpError = (code: number, message: string, data?: unknown): McpError => ({
const createMcpError = (
code: number,
message: string,
data?: unknown,
): McpError => ({
code,
message,
data,
@@ -68,7 +87,7 @@ const createMcpError = (code: number, message: string, data?: unknown): McpError
const createMcpResponse = (
id: string | number,
content?: ReadonlyArray<McpContent>,
error?: McpError
error?: McpError,
): BrainMcpResponse => {
if (error) {
return { id, error };
@@ -111,7 +130,9 @@ const checkRateLimit = (clientIp: string): boolean => {
const validateApiKey = (req: IncomingMessage): boolean => {
if (!state.config.enableAuth) return true;
const apiKey = req.headers[state.config.apiKeyHeader.toLowerCase()] as string | undefined;
const apiKey = req.headers[state.config.apiKeyHeader.toLowerCase()] as
| string
| undefined;
if (!apiKey) return false;
@@ -123,45 +144,62 @@ const validateApiKey = (req: IncomingMessage): boolean => {
const handleToolCall = async (
toolName: BrainMcpToolName,
args: Record<string, unknown>
args: Record<string, unknown>,
): Promise<McpContent[]> => {
if (!state.brainService) {
throw createMcpError(MCP_ERROR_CODES.BRAIN_UNAVAILABLE, BRAIN_MCP_MESSAGES.SERVER_NOT_RUNNING);
throw createMcpError(
MCP_ERROR_CODES.BRAIN_UNAVAILABLE,
BRAIN_MCP_MESSAGES.SERVER_NOT_RUNNING,
);
}
if (!state.brainService.isConnected()) {
throw createMcpError(MCP_ERROR_CODES.BRAIN_UNAVAILABLE, "Brain service not connected");
throw createMcpError(
MCP_ERROR_CODES.BRAIN_UNAVAILABLE,
"Brain service not connected",
);
}
const tool = BRAIN_MCP_TOOLS.find((t: BrainMcpTool) => t.name === toolName);
if (!tool) {
throw createMcpError(MCP_ERROR_CODES.TOOL_NOT_FOUND, `Tool not found: ${toolName}`);
throw createMcpError(
MCP_ERROR_CODES.TOOL_NOT_FOUND,
`Tool not found: ${toolName}`,
);
}
let result: unknown;
const toolHandlers: Record<BrainMcpToolName, () => Promise<unknown>> = {
brain_recall: () => state.brainService!.recall(args.query as string, args.limit as number | undefined),
brain_learn: () => state.brainService!.learn(
args.name as string,
args.whatItDoes as string,
{ keywords: args.keywords, patterns: args.patterns, files: args.files }
),
brain_search: () => state.brainService!.searchMemories(
args.query as string,
args.limit as number | undefined,
args.type as string | undefined
),
brain_relate: () => state.brainService!.relate(
args.sourceConcept as string,
args.targetConcept as string,
args.relationType as string,
args.weight as number | undefined
),
brain_context: () => state.brainService!.getContext(
args.query as string,
args.maxConcepts as number | undefined
),
brain_recall: () =>
state.brainService!.recall(
args.query as string,
args.limit as number | undefined,
),
brain_learn: () =>
state.brainService!.learn(
args.name as string,
args.whatItDoes as string,
{ keywords: args.keywords, patterns: args.patterns, files: args.files },
),
brain_search: () =>
state.brainService!.searchMemories(
args.query as string,
args.limit as number | undefined,
args.type as string | undefined,
),
brain_relate: () =>
state.brainService!.relate(
args.sourceConcept as string,
args.targetConcept as string,
args.relationType as string,
args.weight as number | undefined,
),
brain_context: () =>
state.brainService!.getContext(
args.query as string,
args.maxConcepts as number | undefined,
),
brain_stats: () => state.brainService!.getStats(),
brain_projects: async () => {
// Import dynamically to avoid circular dependency
@@ -172,7 +210,10 @@ const handleToolCall = async (
const handler = toolHandlers[toolName];
if (!handler) {
throw createMcpError(MCP_ERROR_CODES.TOOL_NOT_FOUND, `No handler for tool: ${toolName}`);
throw createMcpError(
MCP_ERROR_CODES.TOOL_NOT_FOUND,
`No handler for tool: ${toolName}`,
);
}
result = await handler();
@@ -180,19 +221,26 @@ const handleToolCall = async (
return [
{
type: "text",
text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
text:
typeof result === "string" ? result : JSON.stringify(result, null, 2),
},
];
};
const handleRequest = async (
req: IncomingMessage,
res: ServerResponse
res: ServerResponse,
): Promise<void> => {
// Set CORS headers
res.setHeader("Access-Control-Allow-Origin", state.config.allowedOrigins.join(","));
res.setHeader(
"Access-Control-Allow-Origin",
state.config.allowedOrigins.join(","),
);
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", `Content-Type, ${state.config.apiKeyHeader}`);
res.setHeader(
"Access-Control-Allow-Headers",
`Content-Type, ${state.config.apiKeyHeader}`,
);
// Handle preflight
if (req.method === "OPTIONS") {
@@ -203,7 +251,11 @@ const handleRequest = async (
if (req.method !== "POST") {
res.writeHead(405);
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.INVALID_REQUEST)));
res.end(
JSON.stringify(
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.INVALID_REQUEST),
),
);
return;
}
@@ -213,14 +265,22 @@ const handleRequest = async (
// Check rate limit
if (!checkRateLimit(clientIp)) {
res.writeHead(429);
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.RATE_LIMITED)));
res.end(
JSON.stringify(
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.RATE_LIMITED),
),
);
return;
}
// Validate API key
if (!validateApiKey(req)) {
res.writeHead(401);
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.UNAUTHORIZED)));
res.end(
JSON.stringify(
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.UNAUTHORIZED),
),
);
return;
}
@@ -240,7 +300,11 @@ const handleRequest = async (
mcpRequest = JSON.parse(body) as BrainMcpRequest;
} catch {
res.writeHead(400);
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.PARSE_ERROR)));
res.end(
JSON.stringify(
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.PARSE_ERROR),
),
);
return;
}
@@ -258,21 +322,37 @@ const handleRequest = async (
inputSchema: tool.inputSchema,
}));
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
id: mcpRequest.id,
result: { tools },
}));
res.end(
JSON.stringify({
id: mcpRequest.id,
result: { tools },
}),
);
} else {
res.writeHead(400);
res.end(JSON.stringify(createMcpResponse(mcpRequest.id, undefined, BRAIN_MCP_ERRORS.METHOD_NOT_FOUND)));
res.end(
JSON.stringify(
createMcpResponse(
mcpRequest.id,
undefined,
BRAIN_MCP_ERRORS.METHOD_NOT_FOUND,
),
),
);
}
} catch (error) {
const mcpError = error instanceof Object && "code" in error
? error as McpError
: createMcpError(MCP_ERROR_CODES.INTERNAL_ERROR, error instanceof Error ? error.message : "Unknown error");
const mcpError =
error instanceof Object && "code" in error
? (error as McpError)
: createMcpError(
MCP_ERROR_CODES.INTERNAL_ERROR,
error instanceof Error ? error.message : "Unknown error",
);
res.writeHead(500);
res.end(JSON.stringify(createMcpResponse(mcpRequest.id, undefined, mcpError)));
res.end(
JSON.stringify(createMcpResponse(mcpRequest.id, undefined, mcpError)),
);
}
});
};
@@ -281,7 +361,7 @@ const handleRequest = async (
export const start = async (
brainService: BrainService,
config?: Partial<BrainMcpServerConfig>
config?: Partial<BrainMcpServerConfig>,
): Promise<void> => {
if (state.server) {
throw new Error(BRAIN_MCP_MESSAGES.SERVER_ALREADY_RUNNING);
@@ -348,5 +428,11 @@ export const updateConfig = (config: Partial<BrainMcpServerConfig>): void => {
state.config = { ...state.config, ...config };
};
export const getAvailableTools = (): ReadonlyArray<{ name: string; description: string }> =>
BRAIN_MCP_TOOLS.map((t: BrainMcpTool) => ({ name: t.name, description: t.description }));
export const getAvailableTools = (): ReadonlyArray<{
name: string;
description: string;
}> =>
BRAIN_MCP_TOOLS.map((t: BrainMcpTool) => ({
name: t.name,
description: t.description,
}));

View File

@@ -128,7 +128,9 @@ export const enqueueBatch = async (items: SyncItem[]): Promise<number> => {
/**
* Get items from queue for processing
*/
export const dequeue = async (limit: number = SYNC_CONFIG.MAX_BATCH_SIZE): Promise<OfflineQueueItem[]> => {
export const dequeue = async (
limit: number = SYNC_CONFIG.MAX_BATCH_SIZE,
): Promise<OfflineQueueItem[]> => {
await loadQueue();
// Get items that haven't exceeded retry limit

View File

@@ -39,7 +39,13 @@ interface ProjectServiceState {
const state: ProjectServiceState = {
projects: new Map(),
activeProjectId: null,
configPath: join(homedir(), ".local", "share", "codetyper", BRAIN_PROJECT_STORAGE.CONFIG_FILE),
configPath: join(
homedir(),
".local",
"share",
"codetyper",
BRAIN_PROJECT_STORAGE.CONFIG_FILE,
),
initialized: false,
};
@@ -116,7 +122,9 @@ export const initialize = async (): Promise<void> => {
state.initialized = true;
};
export const createProject = async (input: BrainProjectCreateInput): Promise<BrainProject> => {
export const createProject = async (
input: BrainProjectCreateInput,
): Promise<BrainProject> => {
await initialize();
// Validate name
@@ -130,7 +138,7 @@ export const createProject = async (input: BrainProjectCreateInput): Promise<Bra
// Check for duplicate names
const existingProject = Array.from(state.projects.values()).find(
(p) => p.name.toLowerCase() === input.name.toLowerCase()
(p) => p.name.toLowerCase() === input.name.toLowerCase(),
);
if (existingProject) {
@@ -161,7 +169,7 @@ export const createProject = async (input: BrainProjectCreateInput): Promise<Bra
export const updateProject = async (
projectId: number,
input: BrainProjectUpdateInput
input: BrainProjectUpdateInput,
): Promise<BrainProject> => {
await initialize();
@@ -205,7 +213,9 @@ export const deleteProject = async (projectId: number): Promise<boolean> => {
return true;
};
export const switchProject = async (projectId: number): Promise<BrainProjectSwitchResult> => {
export const switchProject = async (
projectId: number,
): Promise<BrainProjectSwitchResult> => {
await initialize();
const newProject = state.projects.get(projectId);
@@ -219,7 +229,10 @@ export const switchProject = async (projectId: number): Promise<BrainProjectSwit
// Update active status
if (previousProject) {
state.projects.set(previousProject.id, { ...previousProject, isActive: false });
state.projects.set(previousProject.id, {
...previousProject,
isActive: false,
});
}
state.projects.set(projectId, { ...newProject, isActive: true });
@@ -235,35 +248,45 @@ export const switchProject = async (projectId: number): Promise<BrainProjectSwit
};
};
export const getProject = async (projectId: number): Promise<BrainProject | undefined> => {
export const getProject = async (
projectId: number,
): Promise<BrainProject | undefined> => {
await initialize();
return state.projects.get(projectId);
};
export const getActiveProject = async (): Promise<BrainProject | undefined> => {
await initialize();
return state.activeProjectId ? state.projects.get(state.activeProjectId) : undefined;
return state.activeProjectId
? state.projects.get(state.activeProjectId)
: undefined;
};
export const listProjects = async (): Promise<BrainProjectListResult> => {
await initialize();
return {
projects: Array.from(state.projects.values()).sort((a, b) => b.updatedAt - a.updatedAt),
projects: Array.from(state.projects.values()).sort(
(a, b) => b.updatedAt - a.updatedAt,
),
activeProjectId: state.activeProjectId ?? undefined,
total: state.projects.size,
};
};
export const findProjectByPath = async (rootPath: string): Promise<BrainProject | undefined> => {
export const findProjectByPath = async (
rootPath: string,
): Promise<BrainProject | undefined> => {
await initialize();
return Array.from(state.projects.values()).find((p) => p.rootPath === rootPath);
return Array.from(state.projects.values()).find(
(p) => p.rootPath === rootPath,
);
};
export const updateProjectStats = async (
projectId: number,
stats: Partial<BrainProjectStats>
stats: Partial<BrainProjectStats>,
): Promise<void> => {
await initialize();
@@ -280,7 +303,9 @@ export const updateProjectStats = async (
await saveProjectsToConfig();
};
export const exportProject = async (projectId: number): Promise<BrainProjectExport> => {
export const exportProject = async (
projectId: number,
): Promise<BrainProjectExport> => {
await initialize();
const project = state.projects.get(projectId);
@@ -307,7 +332,7 @@ export const exportProject = async (projectId: number): Promise<BrainProjectExpo
"codetyper",
"brain",
"exports",
`${project.name}-${Date.now()}${BRAIN_PROJECT_STORAGE.EXPORT_EXTENSION}`
`${project.name}-${Date.now()}${BRAIN_PROJECT_STORAGE.EXPORT_EXTENSION}`,
);
await writeFile(exportPath, JSON.stringify(exportData, null, 2));
@@ -316,7 +341,7 @@ export const exportProject = async (projectId: number): Promise<BrainProjectExpo
};
export const importProject = async (
exportData: BrainProjectExport
exportData: BrainProjectExport,
): Promise<BrainProjectImportResult> => {
await initialize();
@@ -352,7 +377,9 @@ export const importProject = async (
}
};
export const getProjectSettings = async (projectId: number): Promise<BrainProjectSettings | undefined> => {
export const getProjectSettings = async (
projectId: number,
): Promise<BrainProjectSettings | undefined> => {
await initialize();
const project = state.projects.get(projectId);
@@ -361,13 +388,15 @@ export const getProjectSettings = async (projectId: number): Promise<BrainProjec
export const updateProjectSettings = async (
projectId: number,
settings: Partial<BrainProjectSettings>
settings: Partial<BrainProjectSettings>,
): Promise<BrainProjectSettings> => {
const project = await updateProject(projectId, { settings });
return project.settings;
};
export const setActiveProjectByPath = async (rootPath: string): Promise<BrainProject | undefined> => {
export const setActiveProjectByPath = async (
rootPath: string,
): Promise<BrainProject | undefined> => {
const project = await findProjectByPath(rootPath);
if (project) {