Files
bDS/src/main/shared/electronApi.ts
Georg Bauer 7e1e8981a3 Feature/semantic similarity (#36)
* fix: mixed up migrations

* feat: semantic similarity first take

* feat: semantic similarity first round of fixes

* feat: more work on making semantic similarity work properly

* feat: getPostBySlug for the AI

* feat: show similarity in post-link-insert-modal

* chore: remove done doc

---------

Co-authored-by: hugo <hugoms@me.com>
2026-03-05 22:05:32 +01:00

1032 lines
35 KiB
TypeScript

// Type definitions for the Electron API exposed via preload
/** Payload emitted when the CLI mutates an entity (via db_notifications). */
export interface EntityChangedPayload {
entity: 'post' | 'media' | 'script' | 'template';
entityId: string;
action: 'created' | 'updated' | 'deleted';
}
export interface ImportExecuteResult {
taskId: string;
totalItems: number;
}
export interface ImportExecutionProgress {
taskId: string;
phase: string;
current: number;
total: number;
detail?: string;
eta?: number;
}
export interface ImportCompleteResult {
taskId: string;
success: boolean;
posts: { imported: number; skipped: number; errors: number };
media: { imported: number; skipped: number; errors: number };
pages: { imported: number; skipped: number; errors: number };
tags: { created: number; skipped: number };
}
export interface ImportDefinitionData {
id: string;
projectId: string;
name: string;
wxrFilePath: string | null;
uploadsFolderPath: string | null;
lastAnalysisResult: unknown | null;
createdAt: string;
updatedAt: string;
}
export interface ProjectMetadata {
name: string;
description?: string;
dataPath?: string;
publicUrl?: string;
mainLanguage?: string;
defaultAuthor?: string;
maxPostsPerPage?: number;
blogmarkCategory?: string;
pythonRuntimeMode?: 'webworker' | 'main-thread';
picoTheme?: import('./picoThemes').PicoThemeName;
categoryMetadata?: Record<string, CategoryMetadata>;
categorySettings?: Record<string, CategoryRenderSettings>;
semanticSimilarityEnabled?: boolean;
}
export interface CategoryRenderSettings {
renderInLists: boolean;
showTitle: boolean;
postTemplateSlug?: string;
listTemplateSlug?: string;
}
export interface CategoryMetadata extends CategoryRenderSettings {
title: string;
}
export interface PublishingPreferences {
sshHost: string;
sshUser: string;
sshRemotePath: string;
sshMode: 'scp' | 'rsync';
}
export interface ProjectData {
id: string;
name: string;
slug: string;
description?: string;
dataPath?: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
export interface PostData {
id: string;
projectId: string;
title: string;
slug: string;
excerpt?: string;
content: string;
status: 'draft' | 'published' | 'archived';
author?: string;
language?: string;
createdAt: string;
updatedAt: string;
publishedAt?: string;
tags: string[];
categories: string[];
templateSlug?: string;
}
export interface PostFilter {
status?: 'draft' | 'published' | 'archived';
tags?: string[];
categories?: string[];
year?: number;
month?: number;
from?: string;
to?: string;
}
export interface SearchResult {
id: string;
title: string;
slug: string;
excerpt?: string;
}
export interface MediaData {
id: string;
projectId: string;
filename: string;
originalName: string;
mimeType: string;
size: number;
width?: number;
height?: number;
title?: string;
alt?: string;
caption?: string;
author?: string;
createdAt: string;
updatedAt: string;
tags: string[];
}
export interface MediaFilter {
tags?: string[];
year?: number;
month?: number;
}
export interface MediaSearchResult {
id: string;
originalName: string;
title?: string;
mimeType: string;
createdAt: string;
}
export type ScriptKind = 'macro' | 'utility' | 'transform';
export interface ScriptData {
id: string;
projectId: string;
slug: string;
title: string;
kind: ScriptKind;
entrypoint: string;
enabled: boolean;
version: number;
filePath: string;
content: string;
createdAt: string;
updatedAt: string;
}
export type TemplateKind = 'post' | 'list' | 'not-found' | 'partial';
export interface TemplateData {
id: string;
projectId: string;
slug: string;
title: string;
kind: TemplateKind;
enabled: boolean;
version: number;
filePath: string;
content: string;
createdAt: string;
updatedAt: string;
}
export interface TemplateDeleteResult {
deleted: boolean;
references?: { postIds: string[]; tagIds: string[] };
}
export interface TaskProgress {
taskId: string;
name: string;
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
progress: number;
message: string;
startTime: string;
endTime?: string;
error?: string;
groupId?: string;
groupName?: string;
}
export interface PaginatedPostsResult {
items: PostData[];
hasMore: boolean;
total: number;
}
export interface DashboardStats {
totalPosts: number;
draftCount: number;
publishedCount: number;
archivedCount: number;
}
export interface TagCount {
tag: string;
count: number;
}
export interface CategoryCount {
category: string;
count: number;
}
export interface TagData {
id: string;
projectId: string;
name: string;
color?: string;
postTemplateSlug?: string;
createdAt: string;
updatedAt: string;
}
export interface TagWithCount {
name: string;
color: string | null;
count: number;
}
export interface DeleteTagResult {
success: boolean;
postsUpdated: number;
}
export interface MergeTagsResult {
success: boolean;
postsUpdated: number;
tagsDeleted: number;
targetTag: string;
}
export interface RenameTagResult {
success: boolean;
postsUpdated: number;
oldName: string;
newName: string;
}
export interface SyncTagsResult {
discovered: number;
added: string[];
}
export interface GitAvailability {
gitFound: boolean;
version?: string;
}
export interface GitRepoState {
isRepo: boolean;
rootPath?: string;
currentBranch?: string;
hasRemote: boolean;
}
export type GitFileStatus = 'untracked' | 'modified' | 'deleted' | 'renamed' | 'staged';
export interface GitStatusFile {
path: string;
status: GitFileStatus;
previousPath?: string;
}
export interface GitStatusCounts {
untracked: number;
modified: number;
deleted: number;
renamed: number;
staged: number;
total: number;
}
export interface GitStatusDto {
files: GitStatusFile[];
counts: GitStatusCounts;
}
export interface GitDiffDto {
filePath: string;
patch: string;
}
export interface GitDiffContentDto {
filePath: string;
original: string;
modified: string;
}
export interface GitCommitDiffContentDto {
commitHash: string;
original: string;
modified: string;
files: GitCommitDiffFileDto[];
}
export interface GitCommitDiffFileDto {
filePath: string;
original: string;
modified: string;
}
export type GitHistorySyncStatus = 'both' | 'local-only' | 'remote-only';
export interface GitHistoryEntry {
hash: string;
shortHash: string;
date: string;
subject: string;
author: string;
syncStatus?: GitHistorySyncStatus;
}
export interface GitRemoteStateDto {
localBranch: string | null;
upstreamBranch: string | null;
hasUpstream: boolean;
ahead: number;
behind: number;
}
export type GitInitPhase =
| 'checking-git'
| 'initializing-repo'
| 'configuring-lfs'
| 'tracking-lfs-patterns'
| 'staging-files'
| 'creating-initial-commit'
| 'configuring-remote'
| 'completed'
| 'failed';
export interface GitInitProgress {
phase: GitInitPhase;
progress: number;
message: string;
detail?: string;
}
export interface GitInitResult {
success: boolean;
error?: string;
code?: 'git-missing' | 'git-lfs-missing' | 'init-failed' | 'remote-failed' | 'commit-failed';
}
export interface GitIgnoreEnsureResult {
updated: boolean;
created: boolean;
addedEntries: string[];
}
export interface GitLfsPruneResult {
success: boolean;
dryRun: boolean;
verifyRemote: boolean;
recentCommitsToKeep: number;
output?: string;
error?: string;
}
export interface GitActionResult {
success: boolean;
code?: 'auth-required' | 'conflict' | 'network' | 'action-failed' | 'offline';
error?: string;
guidance?: string[];
}
// Post-Media Link types
export interface MediaLinkData {
id: string;
projectId: string;
postId: string;
mediaId: string;
sortOrder: number;
createdAt: string;
}
// Chat/AI types
export interface ChatConversation {
id: string;
title: string;
model?: string;
createdAt: string;
updatedAt: string;
}
export interface ChatMessage {
id: string;
conversationId: string;
role: 'user' | 'assistant' | 'system' | 'tool';
content: string;
toolCallId?: string;
toolCalls?: string;
createdAt: string;
}
export interface ChatModel {
id: string;
name: string;
provider: string;
vision?: boolean;
}
export interface ModelCatalogEntry {
id: string;
name: string;
family: string | null;
contextWindow: number | null;
maxInputTokens: number | null;
maxOutputTokens: number | null;
inputPrice: number | null;
outputPrice: number | null;
cacheReadPrice: number | null;
supportsAttachments: boolean | null;
supportsReasoning: boolean | null;
supportsToolCall: boolean | null;
}
export interface ModelCatalogRefreshResult {
success: boolean;
modelsUpdated: number;
notModified?: boolean;
error?: string;
}
export interface ChatReadyStatus {
ready: boolean;
error?: string;
backend?: string;
providers?: { opencode: boolean; mistral: boolean; ollama: boolean; lmstudio: boolean; offlineMode: boolean };
}
export interface ChatApiKeyStatus {
hasKey: boolean;
maskedKey: string;
}
export interface ChatStreamDelta {
conversationId: string;
delta: string;
}
export interface ChatToolCall {
conversationId: string;
toolCall: {
name: string;
arguments: Record<string, unknown>;
};
}
export interface ChatToolResult {
conversationId: string;
result: unknown;
}
export interface ChatTitleUpdate {
conversationId: string;
title: string;
}
export interface ChatTokenUsage {
conversationId: string;
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
cacheWriteTokens: number;
totalTokens: number;
cumulativeInputTokens: number;
cumulativeOutputTokens: number;
cumulativeCacheReadTokens: number;
cumulativeCacheWriteTokens: number;
cumulativeTotalTokens: number;
}
export interface ChatSendMetadata {
surface?: 'tab' | 'sidebar';
}
// A2UI types imported for use in ElectronAPI and re-exported for renderer
import type { A2UIServerMessage, A2UIClientAction } from '../a2ui/types';
export type { A2UIServerMessage, A2UIClientAction };
export interface SimilarPost {
postId: string;
similarity: number;
}
export interface TagSuggestion {
name: string;
score: number;
}
export interface DuplicatePair {
postA: { id: string; title: string; slug: string; publishedAt?: Date };
postB: { id: string; title: string; slug: string; publishedAt?: Date };
similarity: number;
exactMatch?: boolean;
}
export interface SiteValidationReport {
sitemapPath: string;
sitemapChanged: boolean;
missingUrlPaths: string[];
extraUrlPaths: string[];
updatedPostUrlPaths: string[];
expectedUrlCount: number;
existingHtmlUrlCount: number;
}
export interface SiteValidationApplyResult {
renderedUrlCount: number;
deletedUrlCount: number;
removedEmptyDirCount: number;
}
export interface CalendarRegenerationResult {
calendarPath: string;
changed: boolean;
}
export type MenuItemKind = 'page' | 'submenu' | 'category-archive' | 'home';
export interface MenuItemData {
id: string;
title: string;
kind: MenuItemKind;
pageId?: string;
pageSlug?: string;
categoryName?: string;
children: MenuItemData[];
}
export interface MenuDocument {
items: MenuItemData[];
}
export interface ElectronAPI {
git: {
checkAvailability: () => Promise<GitAvailability>;
getRepoState: (projectPath: string) => Promise<GitRepoState>;
getStatus: (projectPath: string) => Promise<GitStatusDto>;
getDiff: (projectPath: string, filePath: string) => Promise<GitDiffDto>;
getDiffContent: (projectPath: string, filePath: string) => Promise<GitDiffContentDto>;
getCommitDiffContent: (projectPath: string, commitHash: string) => Promise<GitCommitDiffContentDto>;
getHistory: (projectPath: string, limit?: number) => Promise<GitHistoryEntry[]>;
getFileHistory: (projectPath: string, filePath: string, limit?: number) => Promise<GitHistoryEntry[]>;
getRemoteState: (projectPath: string) => Promise<GitRemoteStateDto>;
fetch: (projectPath: string) => Promise<GitActionResult>;
pull: (projectPath: string) => Promise<GitActionResult>;
push: (projectPath: string) => Promise<GitActionResult>;
commitAll: (projectPath: string, message: string) => Promise<GitActionResult>;
ensureGitignore: (projectPath: string) => Promise<GitIgnoreEnsureResult>;
pruneLfs: (projectPath: string, options?: { dryRun?: boolean; verifyRemote?: boolean; recentCommitsToKeep?: number }) => Promise<GitLfsPruneResult>;
init: (projectPath: string, remoteUrl?: string) => Promise<GitInitResult>;
onInitProgress: (callback: (progress: GitInitProgress) => void) => () => void;
};
projects: {
create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => Promise<ProjectData>;
update: (id: string, data: Partial<ProjectData>) => Promise<ProjectData | null>;
delete: (id: string) => Promise<boolean>;
deleteWithData: (id: string) => Promise<boolean>;
get: (id: string) => Promise<ProjectData | null>;
getAll: () => Promise<ProjectData[]>;
getActive: () => Promise<ProjectData | null>;
setActive: (id: string) => Promise<ProjectData | null>;
};
posts: {
create: (data: Partial<PostData>) => Promise<PostData>;
update: (id: string, data: Partial<PostData>) => Promise<PostData | null>;
delete: (id: string) => Promise<boolean>;
get: (id: string) => Promise<PostData | null>;
getBySlug: (slug: string) => Promise<PostData | null>;
getPreviewUrl: (id: string, options?: { draft?: boolean }) => Promise<string | null>;
getAll: (options?: { limit?: number; offset?: number }) => Promise<PaginatedPostsResult>;
getByStatus: (status: string) => Promise<PostData[]>;
publish: (id: string) => Promise<PostData | null>;
discard: (id: string) => Promise<PostData | null>;
hasPublishedVersion: (id: string) => Promise<boolean>;
rebuildFromFiles: () => Promise<void>;
reindexText: () => Promise<void>;
search: (query: string) => Promise<SearchResult[]>;
filter: (filter: PostFilter) => Promise<PostData[]>;
getTags: () => Promise<string[]>;
getCategories: () => Promise<string[]>;
getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>;
getDashboardStats: () => Promise<DashboardStats>;
getTagsWithCounts: () => Promise<TagCount[]>;
getCategoriesWithCounts: () => Promise<CategoryCount[]>;
getLinksTo: (id: string) => Promise<PostData[]>;
getLinkedBy: (id: string) => Promise<PostData[]>;
rebuildLinks: () => Promise<void>;
isSlugAvailable: (slug: string, excludePostId?: string) => Promise<boolean>;
generateUniqueSlug: (title: string, excludePostId?: string) => Promise<string>;
};
media: {
import: (sourcePath: string, metadata?: Partial<MediaData>) => Promise<MediaData>;
importDialog: () => Promise<MediaData[]>;
update: (id: string, data: Partial<MediaData>) => Promise<MediaData | null>;
replaceFile: (id: string, newSourcePath: string) => Promise<MediaData | null>;
replaceFileDialog: (id: string) => Promise<MediaData | null>;
delete: (id: string) => Promise<boolean>;
get: (id: string) => Promise<MediaData | null>;
getUrl: (id: string) => Promise<string | null>;
getFilePath: (id: string) => Promise<string | null>;
getAll: () => Promise<MediaData[]>;
rebuildFromFiles: () => Promise<void>;
reindexText: () => Promise<void>;
getThumbnail: (id: string, size?: 'small' | 'medium' | 'large') => Promise<string | null>;
regenerateThumbnails: (id: string) => Promise<Record<string, string> | null>;
regenerateMissingThumbnails: () => Promise<{ processed: number; generated: number; failed: number }>;
filter: (filter: MediaFilter) => Promise<MediaData[]>;
search: (query: string) => Promise<MediaSearchResult[]>;
getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>;
getTags: () => Promise<string[]>;
getTagsWithCounts: () => Promise<TagCount[]>;
};
scripts: {
create: (data: {
title: string;
kind: ScriptKind;
content: string;
slug?: string;
entrypoint?: string;
enabled?: boolean;
}) => Promise<ScriptData>;
update: (id: string, data: {
title?: string;
kind?: ScriptKind;
content?: string;
slug?: string;
entrypoint?: string;
enabled?: boolean;
}) => Promise<ScriptData | null>;
delete: (id: string) => Promise<boolean>;
get: (id: string) => Promise<ScriptData | null>;
getAll: () => Promise<ScriptData[]>;
/** Internal: editor macro plugin helper. Not exposed via Python API contract. */
getEnabledMacroSlugs: () => Promise<string[]>;
rebuildFromFiles: () => Promise<void>;
/** Create a task entry for a running utility script. */
startTask: (taskId: string, name: string) => Promise<void>;
/** Mark a utility script task as completed. */
completeTask: (taskId: string) => Promise<void>;
/** Mark a utility script task as failed. */
failTask: (taskId: string, error: string) => Promise<void>;
};
templates: {
create: (data: {
title: string;
kind: TemplateKind;
content: string;
slug?: string;
enabled?: boolean;
}) => Promise<TemplateData>;
update: (id: string, data: {
title?: string;
kind?: TemplateKind;
content?: string;
slug?: string;
enabled?: boolean;
}) => Promise<TemplateData | null>;
delete: (id: string, options?: { force?: boolean }) => Promise<TemplateDeleteResult>;
get: (id: string) => Promise<TemplateData | null>;
getAll: () => Promise<TemplateData[]>;
getEnabledByKind: (kind: TemplateKind) => Promise<TemplateData[]>;
validate: (content: string) => Promise<{ valid: boolean; errors: string[] }>;
rebuildFromFiles: () => Promise<void>;
};
postMedia: {
link: (postId: string, mediaId: string) => Promise<MediaLinkData>;
unlink: (postId: string, mediaId: string) => Promise<void>;
linkMany: (postId: string, mediaIds: string[]) => Promise<{ linked: string[]; skipped: string[] }>;
unlinkMany: (postId: string, mediaIds: string[]) => Promise<{ unlinked: string[] }>;
getForPost: (postId: string) => Promise<MediaLinkData[]>;
getForMedia: (mediaId: string) => Promise<MediaLinkData[]>;
getMediaDataForPost: (postId: string) => Promise<Array<MediaLinkData & { media: MediaData }>>;
reorder: (postId: string, mediaIds: string[]) => Promise<void>;
isLinked: (postId: string, mediaId: string) => Promise<boolean>;
import: (postId: string, filePath: string) => Promise<MediaLinkData>;
rebuild: () => Promise<void>;
};
sync: {
checkAvailability: () => Promise<{ gitFound: boolean; version?: string }>;
getRepoState: () => Promise<{ isRepo: boolean; rootPath?: string; currentBranch?: string; hasRemote: boolean }>;
getStatus: () => Promise<{ files: Array<{ path: string; status: string; previousPath?: string }>; counts: { untracked: number; modified: number; deleted: number; renamed: number; staged: number } }>;
getHistory: (limit?: number) => Promise<Array<{ hash: string; shortHash: string; date: string; subject: string; author: string }>>;
getRemoteState: () => Promise<{ localBranch: string | null; upstreamBranch: string | null; hasUpstream: boolean; ahead: number; behind: number }>;
fetch: () => Promise<{ success: boolean; code?: string; error?: string; guidance?: string[] }>;
pull: () => Promise<{ success: boolean; code?: string; error?: string; guidance?: string[] }>;
push: () => Promise<{ success: boolean; code?: string; error?: string; guidance?: string[] }>;
commitAll: (message: string) => Promise<{ success: boolean; code?: string; error?: string; guidance?: string[] }>;
};
tasks: {
getAll: () => Promise<TaskProgress[]>;
getRunning: () => Promise<TaskProgress[]>;
cancel: (taskId: string) => Promise<boolean>;
clearCompleted: () => Promise<void>;
};
app: {
getDataPaths: () => Promise<{ database: string; posts: string; media: string }>;
getSystemLanguage: () => Promise<string>;
getTitleBarMetrics: () => Promise<{ macosLeftInset: number } | null>;
openFolder: (folderPath: string) => Promise<string>;
showItemInFolder: (itemPath: string) => Promise<void>;
selectFolder: (title?: string) => Promise<string | null>;
getDefaultProjectPath: (projectId: string) => Promise<string>;
readProjectMetadata: (folderPath: string) => Promise<{ name?: string; description?: string; publicUrl?: string; mainLanguage?: string } | null>;
getBlogmarkBookmarklet: () => Promise<string>;
copyToClipboard: (text: string) => Promise<boolean>;
notifyRendererReady: () => Promise<boolean>;
setPreviewPostTarget: (postId: string | null) => Promise<void>;
triggerMenuAction: (action: string) => Promise<void>;
};
meta: {
getTags: () => Promise<string[]>;
getCategories: () => Promise<string[]>;
addTag: (tag: string) => Promise<string[]>;
removeTag: (tag: string) => Promise<string[]>;
addCategory: (category: string) => Promise<string[]>;
removeCategory: (category: string) => Promise<string[]>;
syncOnStartup: () => Promise<{ tags: string[]; categories: string[]; projectMetadata: ProjectMetadata | null }>;
getProjectMetadata: () => Promise<ProjectMetadata | null>;
setProjectMetadata: (metadata: { name: string; description?: string }) => Promise<ProjectMetadata | null>;
updateProjectMetadata: (updates: { name?: string; description?: string; dataPath?: string; publicUrl?: string; mainLanguage?: string; defaultAuthor?: string; maxPostsPerPage?: number; blogmarkCategory?: string; pythonRuntimeMode?: 'webworker' | 'main-thread'; picoTheme?: import('./picoThemes').PicoThemeName; categoryMetadata?: Record<string, CategoryMetadata>; categorySettings?: Record<string, CategoryRenderSettings>; semanticSimilarityEnabled?: boolean }) => Promise<ProjectMetadata | null>;
getPublishingPreferences: () => Promise<PublishingPreferences | null>;
setPublishingPreferences: (prefs: PublishingPreferences) => Promise<void>;
clearPublishingPreferences: () => Promise<void>;
};
tags: {
getAll: () => Promise<TagData[]>;
getWithCounts: () => Promise<TagWithCount[]>;
get: (id: string) => Promise<TagData | null>;
getByName: (name: string) => Promise<TagData | null>;
create: (data: { name: string; color?: string }) => Promise<TagData>;
update: (id: string, data: { name?: string; color?: string | null; postTemplateSlug?: string | null }) => Promise<TagData | null>;
delete: (id: string) => Promise<DeleteTagResult>;
merge: (sourceTagIds: string[], targetTagId: string) => Promise<MergeTagsResult>;
rename: (id: string, newName: string) => Promise<RenameTagResult>;
getPostsWithTag: (tagId: string) => Promise<string[]>;
syncFromPosts: () => Promise<SyncTagsResult>;
};
import: {
selectAndAnalyze: (uploadsFolder?: string) => Promise<unknown>;
analyzeFile: (filePath: string, uploadsFolder?: string) => Promise<unknown>;
selectUploadsFolder: () => Promise<string | null>;
execute: (reportJson: string, uploadsFolder?: string) => Promise<ImportExecuteResult>;
onProgress: (callback: (data: { step: string; detail?: string }) => void) => () => void;
onExecutionProgress: (callback: (data: ImportExecutionProgress) => void) => () => void;
onComplete: (callback: (data: ImportCompleteResult) => void) => () => void;
};
importDefinitions: {
create: (name?: string) => Promise<ImportDefinitionData>;
get: (id: string) => Promise<ImportDefinitionData | null>;
getAll: () => Promise<ImportDefinitionData[]>;
update: (id: string, updates: Partial<Pick<ImportDefinitionData, 'name' | 'wxrFilePath' | 'uploadsFolderPath' | 'lastAnalysisResult'>>) => Promise<ImportDefinitionData | null>;
delete: (id: string) => Promise<boolean>;
onNameUpdated: (callback: (data: { definitionId: string; name: string }) => void) => () => void;
};
metadataDiff: {
getStats: () => Promise<{
totalPosts: number;
publishedPosts: number;
draftPosts: number;
totalMedia: number;
totalScripts: number;
publishedScripts: number;
totalTemplates: number;
publishedTemplates: number;
}>;
scan: () => Promise<{
totalScanned: number;
postsWithDifferences: number;
differences: Array<{
postId: string;
title: string;
slug: string;
filePath?: string;
hasDifferences: boolean;
fileMissing?: boolean;
differences: Record<string, { dbValue: unknown; fileValue: unknown }>;
}>;
groups: Array<{
field: string;
label: string;
posts: Array<{
postId: string;
title: string;
slug: string;
dbValue: unknown;
fileValue: unknown;
}>;
}>;
orphanFiles: Array<{
filePath: string;
slug: string;
title?: string;
id?: string;
}>;
}>;
syncDbToFile: (postIds: string[], groupLabel: string) => Promise<{ success: number; failed: number }>;
syncFileToDb: (postIds: string[], field: string, groupLabel: string) => Promise<{ success: number; failed: number }>;
scanMedia: () => Promise<{
totalScanned: number;
itemsWithDifferences: number;
differences: Array<{
mediaId: string;
originalName: string;
filePath?: string;
hasDifferences: boolean;
fileMissing?: boolean;
differences: Record<string, { dbValue: unknown; fileValue: unknown }>;
}>;
groups: Array<{
field: string;
label: string;
items: Array<{ mediaId: string; originalName: string; dbValue: unknown; fileValue: unknown }>;
}>;
}>;
syncMediaDbToFile: (mediaIds: string[], groupLabel: string) => Promise<{ success: number; failed: number }>;
syncMediaFileToDb: (mediaIds: string[], field: string, groupLabel: string) => Promise<{ success: number; failed: number }>;
scanScripts: () => Promise<{
totalScanned: number;
itemsWithDifferences: number;
differences: Array<{
scriptId: string;
title: string;
slug: string;
filePath?: string;
hasDifferences: boolean;
fileMissing?: boolean;
differences: Record<string, { dbValue: unknown; fileValue: unknown }>;
}>;
groups: Array<{
field: string;
label: string;
items: Array<{ scriptId: string; title: string; slug: string; dbValue: unknown; fileValue: unknown }>;
}>;
}>;
syncScriptDbToFile: (scriptIds: string[], groupLabel: string) => Promise<{ success: number; failed: number }>;
syncScriptFileToDb: (scriptIds: string[], field: string, groupLabel: string) => Promise<{ success: number; failed: number }>;
scanTemplates: () => Promise<{
totalScanned: number;
itemsWithDifferences: number;
differences: Array<{
templateId: string;
title: string;
slug: string;
filePath?: string;
hasDifferences: boolean;
fileMissing?: boolean;
differences: Record<string, { dbValue: unknown; fileValue: unknown }>;
}>;
groups: Array<{
field: string;
label: string;
items: Array<{ templateId: string; title: string; slug: string; dbValue: unknown; fileValue: unknown }>;
}>;
}>;
syncTemplateDbToFile: (templateIds: string[], groupLabel: string) => Promise<{ success: number; failed: number }>;
syncTemplateFileToDb: (templateIds: string[], field: string, groupLabel: string) => Promise<{ success: number; failed: number }>;
importOrphanFiles: (filePaths: string[]) => Promise<{ success: number; failed: number }>;
};
blog: {
generateSitemap: () => Promise<{
path: string;
urlCount: number;
postCount: number;
tagCount: number;
categoryCount: number;
archiveCount: number;
pagesGenerated: number;
}>;
validateSite: () => Promise<SiteValidationReport>;
applyValidation: (report: SiteValidationReport) => Promise<SiteValidationApplyResult>;
regenerateCalendar: () => Promise<CalendarRegenerationResult>;
};
publish: {
uploadSite: (credentials: {
sshHost: string;
sshUser: string;
sshRemotePath: string;
sshMode: 'scp' | 'rsync';
}) => Promise<{
htmlFilesUploaded: number;
thumbnailFilesUploaded: number;
mediaFilesUploaded: number;
filesSkipped: number;
}>;
};
menu: {
get: () => Promise<MenuDocument>;
save: (menu: MenuDocument) => Promise<MenuDocument>;
};
chat: {
// API Key Management
checkReady: () => Promise<ChatReadyStatus>;
validateApiKey: (apiKey: string) => Promise<{ isValid: boolean; models: ChatModel[] }>;
setApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>;
getApiKey: () => Promise<ChatApiKeyStatus>;
// Mistral API Key
validateMistralApiKey: (apiKey: string) => Promise<{ isValid: boolean; models: ChatModel[] }>;
setMistralApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>;
getMistralApiKey: () => Promise<ChatApiKeyStatus>;
// Ollama (local)
getOllamaEnabled: () => Promise<boolean>;
setOllamaEnabled: (enabled: boolean) => Promise<{ success: boolean; error?: string }>;
getOllamaModels: () => Promise<ChatModel[]>;
getOllamaModelCapabilities: () => Promise<Record<string, { tools: boolean; vision: boolean }>>;
setOllamaModelCapabilities: (modelId: string, caps: { tools: boolean; vision: boolean }) => Promise<{ success: boolean; error?: string }>;
// LM Studio (local)
getLmstudioEnabled: () => Promise<boolean>;
setLmstudioEnabled: (enabled: boolean) => Promise<{ success: boolean; error?: string }>;
getLmstudioModels: () => Promise<ChatModel[]>;
getLmstudioModelCapabilities: () => Promise<Record<string, { tools: boolean; vision: boolean }>>;
setLmstudioModelCapabilities: (modelId: string, caps: { tools: boolean; vision: boolean }) => Promise<{ success: boolean; error?: string }>;
// Offline / Airplane mode
getOfflineMode: () => Promise<boolean>;
setOfflineMode: (enabled: boolean) => Promise<{ success: boolean; error?: string }>;
getOfflineChatModel: () => Promise<{ success: boolean; modelId?: string | null }>;
setOfflineChatModel: (modelId: string | null) => Promise<{ success: boolean; error?: string }>;
getOfflineTitleModel: () => Promise<{ success: boolean; modelId?: string | null }>;
setOfflineTitleModel: (modelId: string | null) => Promise<{ success: boolean; error?: string }>;
getOfflineImageAnalysisModel: () => Promise<{ success: boolean; modelId?: string | null }>;
setOfflineImageAnalysisModel: (modelId: string | null) => Promise<{ success: boolean; error?: string }>;
getKnownLocalModels: () => Promise<ChatModel[]>;
// Settings
getAvailableModels: () => Promise<{ success: boolean; models?: ChatModel[]; selectedModel?: string; error?: string }>;
setDefaultModel: (modelId: string) => Promise<{ success: boolean; error?: string }>;
getSystemPrompt: () => Promise<{ success: boolean; prompt?: string; error?: string }>;
setSystemPrompt: (prompt: string) => Promise<{ success: boolean; error?: string }>;
// Per-purpose model preferences
getTitleModel: () => Promise<{ success: boolean; modelId?: string | null; error?: string }>;
setTitleModel: (modelId: string | null) => Promise<{ success: boolean; error?: string }>;
getImageAnalysisModel: () => Promise<{ success: boolean; modelId?: string | null; error?: string }>;
setImageAnalysisModel: (modelId: string | null) => Promise<{ success: boolean; error?: string }>;
// Model Catalog
refreshModelCatalog: () => Promise<ModelCatalogRefreshResult>;
getModelCatalog: () => Promise<{ success: boolean; entries: ModelCatalogEntry[]; error?: string }>;
// Conversations
getConversations: () => Promise<ChatConversation[]>;
createConversation: (title?: string, model?: string) => Promise<ChatConversation>;
getConversation: (id: string) => Promise<ChatConversation | null>;
updateConversation: (id: string, updates: { title?: string; model?: string }) => Promise<ChatConversation | null>;
deleteConversation: (id: string) => Promise<boolean>;
// Messaging
sendMessage: (conversationId: string, message: string, metadata?: ChatSendMetadata) => Promise<{ success: boolean; message?: string; error?: string }>;
addSystemEvent: (conversationId: string, content: string) => Promise<{ success: boolean; error?: string }>;
abortMessage: (conversationId: string) => Promise<void>;
getHistory: (conversationId: string) => Promise<ChatMessage[]>;
clearMessages: (conversationId: string) => Promise<void>;
setConversationModel: (conversationId: string, modelId: string) => Promise<void>;
// Taxonomy Analysis
analyzeTaxonomy: (categories: Array<{ name: string; slug: string; existsInProject: boolean }>, tags: Array<{ name: string; slug: string; existsInProject: boolean }>, modelId: string) => Promise<{ success: boolean; categoryMappings?: Record<string, string>; tagMappings?: Record<string, string>; error?: string }>;
// Media Analysis
analyzeMediaImage: (mediaId: string, language?: string) => Promise<{ success: boolean; title?: string; alt?: string; caption?: string; error?: string }>;
// Post Language Detection
detectPostLanguage: (title: string, content: string) => Promise<{ success: boolean; language?: string; error?: string }>;
// Event listeners for streaming/progress
onStreamDelta: (callback: (data: ChatStreamDelta) => void) => () => void;
onToolCall: (callback: (data: ChatToolCall) => void) => () => void;
onToolResult: (callback: (data: ChatToolResult) => void) => () => void;
onTitleUpdated: (callback: (data: ChatTitleUpdate) => void) => () => void;
onTokenUsage: (callback: (data: ChatTokenUsage) => void) => () => void;
// A2UI streaming
onA2UIMessage: (callback: (data: { conversationId: string; message: A2UIServerMessage }) => void) => () => void;
dispatchA2UIAction: (action: A2UIClientAction) => Promise<{ success: boolean; error?: string }>;
};
embeddings: {
findSimilar: (postId: string, k?: number) => Promise<SimilarPost[]>;
computeSimilarities: (sourcePostId: string, targetPostIds: string[]) => Promise<Record<string, number>>;
getProgress: () => Promise<{ indexed: number; total: number }>;
suggestTags: (postId: string, excludeTags: string[]) => Promise<TagSuggestion[]>;
findDuplicates: (threshold?: number) => Promise<DuplicatePair[]>;
runDuplicateSearch: (threshold?: number) => Promise<void>;
dismissPair: (postIdA: string, postIdB: string) => Promise<void>;
dismissPairs: (pairIds: Array<[string, string]>) => Promise<void>;
indexUnindexedPosts: () => Promise<void>;
};
on: (channel: string, callback: (...args: unknown[]) => void) => () => void;
once: (channel: string, callback: (...args: unknown[]) => void) => void;
/** Subscribe to entity-changed events fired by the CLI NotificationWatcher. */
onEntityChanged: (callback: (payload: EntityChangedPayload) => void) => () => void;
mcp: {
getAgents: () => Promise<Array<{ id: string; label: string }>>;
addToAgentConfig: (agentId: string) => Promise<{ success: boolean; configPath: string; error?: string }>;
removeFromAgentConfig: (agentId: string) => Promise<{ success: boolean; configPath: string; error?: string }>;
isConfigured: (agentId: string) => Promise<boolean>;
getPort: () => Promise<number | null>;
};
}