opensidian 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/.eslintrc.json +1 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +35 -0
  4. package/.github/ISSUE_TEMPLATE/question.md +23 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +45 -0
  6. package/.github/README.md +5 -0
  7. package/.github/workflows/cd.yml +44 -0
  8. package/.github/workflows/ci.yml +128 -0
  9. package/.github/workflows/qa.yml +45 -0
  10. package/.planning/PROJECT.md +96 -0
  11. package/.planning/REQUIREMENTS.md +66 -0
  12. package/.planning/ROADMAP.md +129 -0
  13. package/.planning/STATE.md +47 -0
  14. package/.planning/config.json +14 -0
  15. package/CONTRIBUTING.md +232 -0
  16. package/LICENSE +21 -0
  17. package/README.md +244 -0
  18. package/dist/api/auth.d.ts +5 -0
  19. package/dist/api/auth.d.ts.map +1 -0
  20. package/dist/api/auth.js +112 -0
  21. package/dist/api/auth.js.map +1 -0
  22. package/dist/api/routes.d.ts +3 -0
  23. package/dist/api/routes.d.ts.map +1 -0
  24. package/dist/api/routes.js +119 -0
  25. package/dist/api/routes.js.map +1 -0
  26. package/dist/api/themes.d.ts +3 -0
  27. package/dist/api/themes.d.ts.map +1 -0
  28. package/dist/api/themes.js +48 -0
  29. package/dist/api/themes.js.map +1 -0
  30. package/dist/core/graph.d.ts +16 -0
  31. package/dist/core/graph.d.ts.map +1 -0
  32. package/dist/core/graph.js +115 -0
  33. package/dist/core/graph.js.map +1 -0
  34. package/dist/core/markdown.d.ts +21 -0
  35. package/dist/core/markdown.d.ts.map +1 -0
  36. package/dist/core/markdown.js +77 -0
  37. package/dist/core/markdown.js.map +1 -0
  38. package/dist/core/search.d.ts +34 -0
  39. package/dist/core/search.d.ts.map +1 -0
  40. package/dist/core/search.js +159 -0
  41. package/dist/core/search.js.map +1 -0
  42. package/dist/core/sync.d.ts +30 -0
  43. package/dist/core/sync.d.ts.map +1 -0
  44. package/dist/core/sync.js +121 -0
  45. package/dist/core/sync.js.map +1 -0
  46. package/dist/core/vault.d.ts +28 -0
  47. package/dist/core/vault.d.ts.map +1 -0
  48. package/dist/core/vault.js +235 -0
  49. package/dist/core/vault.js.map +1 -0
  50. package/dist/index.d.ts +2 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +32 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/mcp/cli.d.ts +3 -0
  55. package/dist/mcp/cli.d.ts.map +1 -0
  56. package/dist/mcp/cli.js +7 -0
  57. package/dist/mcp/cli.js.map +1 -0
  58. package/dist/mcp/server.d.ts +18 -0
  59. package/dist/mcp/server.d.ts.map +1 -0
  60. package/dist/mcp/server.js +272 -0
  61. package/dist/mcp/server.js.map +1 -0
  62. package/dist/plugins/host.d.ts +23 -0
  63. package/dist/plugins/host.d.ts.map +1 -0
  64. package/dist/plugins/host.js +104 -0
  65. package/dist/plugins/host.js.map +1 -0
  66. package/dist/plugins/sample-plugin.d.ts +10 -0
  67. package/dist/plugins/sample-plugin.d.ts.map +1 -0
  68. package/dist/plugins/sample-plugin.js +23 -0
  69. package/dist/plugins/sample-plugin.js.map +1 -0
  70. package/dist/server.d.ts +15 -0
  71. package/dist/server.d.ts.map +1 -0
  72. package/dist/server.js +77 -0
  73. package/dist/server.js.map +1 -0
  74. package/dist/shared/types.d.ts +86 -0
  75. package/dist/shared/types.d.ts.map +1 -0
  76. package/dist/shared/types.js +2 -0
  77. package/dist/shared/types.js.map +1 -0
  78. package/docker/Dockerfile +37 -0
  79. package/docker/docker-compose.yml +46 -0
  80. package/docs/ARCHITECTURE.md +321 -0
  81. package/futuras_implementacoes.md +0 -0
  82. package/package.json +65 -0
  83. package/scripts/fix-gitignore.ps1 +5 -0
  84. package/scripts/seed-notes.mjs +60 -0
  85. package/src/api/auth.ts +130 -0
  86. package/src/api/routes.ts +133 -0
  87. package/src/api/themes.ts +60 -0
  88. package/src/core/graph.ts +145 -0
  89. package/src/core/markdown.ts +92 -0
  90. package/src/core/search.ts +208 -0
  91. package/src/core/sync.ts +157 -0
  92. package/src/core/vault.ts +286 -0
  93. package/src/index.ts +37 -0
  94. package/src/mcp/cli.ts +7 -0
  95. package/src/mcp/server.ts +296 -0
  96. package/src/plugins/host.ts +120 -0
  97. package/src/plugins/sample-plugin.ts +29 -0
  98. package/src/server.ts +90 -0
  99. package/src/shared/types.ts +92 -0
  100. package/tests/api/routes.test.ts +167 -0
  101. package/tests/core/graph.test.ts +236 -0
  102. package/tests/core/markdown.test.ts +157 -0
  103. package/tests/core/search.test.ts +132 -0
  104. package/tests/core/sync.test.ts +62 -0
  105. package/tests/core/vault.test.ts +162 -0
  106. package/tests/mcp/server.test.ts +118 -0
  107. package/tests/plugins/host.test.ts +165 -0
  108. package/tests/plugins/sample-plugin.test.ts +35 -0
  109. package/tests/server.test.ts +76 -0
  110. package/tsconfig.json +27 -0
  111. package/vite.config.ts +27 -0
  112. package/vitest.config.ts +33 -0
  113. package/web/index.html +13 -0
  114. package/web/package.json +26 -0
  115. package/web/public/favicon.svg +4 -0
  116. package/web/src/App.tsx +63 -0
  117. package/web/src/api/auth.ts +65 -0
  118. package/web/src/api/client.ts +117 -0
  119. package/web/src/api/themes.ts +78 -0
  120. package/web/src/components/GraphView.tsx +139 -0
  121. package/web/src/components/Layout.tsx +74 -0
  122. package/web/src/components/LoginPage.tsx +52 -0
  123. package/web/src/components/NoteEditor.tsx +114 -0
  124. package/web/src/components/NoteList.tsx +95 -0
  125. package/web/src/components/RegisterPage.tsx +58 -0
  126. package/web/src/components/SearchBar.tsx +71 -0
  127. package/web/src/components/SearchPanel.tsx +152 -0
  128. package/web/src/components/ThemeEditor.tsx +129 -0
  129. package/web/src/components/ThemeSelector.tsx +41 -0
  130. package/web/src/components/VaultList.tsx +89 -0
  131. package/web/src/hooks/AuthContext.tsx +57 -0
  132. package/web/src/hooks/ThemeContext.tsx +77 -0
  133. package/web/src/hooks/useWebSocket.ts +34 -0
  134. package/web/src/main.tsx +10 -0
  135. package/web/src/styles/global.css +449 -0
  136. package/web/tsconfig.json +21 -0
  137. package/web/vite.config.ts +19 -0
@@ -0,0 +1,296 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ Tool,
7
+ } from '@modelcontextprotocol/sdk/types.js';
8
+ import { VaultManager } from '../core/vault.js';
9
+ import { GraphEngine } from '../core/graph.js';
10
+ import { SyncService } from '../core/sync.js';
11
+ import { z } from 'zod';
12
+
13
+ const vaultManager = new VaultManager();
14
+ const graphEngine = new GraphEngine();
15
+ const syncService = new SyncService();
16
+
17
+ const tools: Tool[] = [
18
+ {
19
+ name: 'vault_list',
20
+ description: 'List all available vaults',
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {},
24
+ },
25
+ },
26
+ {
27
+ name: 'vault_open',
28
+ description: 'Open a specific vault',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ path: { type: 'string', description: 'Path to the vault' },
33
+ },
34
+ required: ['path'],
35
+ },
36
+ },
37
+ {
38
+ name: 'vault_create',
39
+ description: 'Create a new vault',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ name: { type: 'string', description: 'Name of the vault' },
44
+ path: { type: 'string', description: 'Path where to create the vault' },
45
+ },
46
+ required: ['name', 'path'],
47
+ },
48
+ },
49
+ {
50
+ name: 'note_create',
51
+ description: 'Create a new note',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: {
55
+ vaultPath: { type: 'string', description: 'Path to the vault' },
56
+ filename: { type: 'string', description: 'Note filename (without .md)' },
57
+ content: { type: 'string', description: 'Note content in markdown' },
58
+ },
59
+ required: ['vaultPath', 'filename', 'content'],
60
+ },
61
+ },
62
+ {
63
+ name: 'note_read',
64
+ description: 'Read a note',
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {
68
+ vaultPath: { type: 'string', description: 'Path to the vault' },
69
+ path: { type: 'string', description: 'Path to the note relative to vault' },
70
+ },
71
+ required: ['vaultPath', 'path'],
72
+ },
73
+ },
74
+ {
75
+ name: 'note_update',
76
+ description: 'Update a note',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ vaultPath: { type: 'string', description: 'Path to the vault' },
81
+ path: { type: 'string', description: 'Path to the note relative to vault' },
82
+ content: { type: 'string', description: 'New content in markdown' },
83
+ },
84
+ required: ['vaultPath', 'path', 'content'],
85
+ },
86
+ },
87
+ {
88
+ name: 'note_delete',
89
+ description: 'Delete a note',
90
+ inputSchema: {
91
+ type: 'object',
92
+ properties: {
93
+ vaultPath: { type: 'string', description: 'Path to the vault' },
94
+ path: { type: 'string', description: 'Path to the note relative to vault' },
95
+ },
96
+ required: ['vaultPath', 'path'],
97
+ },
98
+ },
99
+ {
100
+ name: 'note_search',
101
+ description: 'Full-text search notes with ranking, highlighting, and optional filters',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ vaultPath: { type: 'string', description: 'Path to the vault' },
106
+ query: { type: 'string', description: 'Search query' },
107
+ tag: { type: 'string', description: 'Filter by tag (e.g. "javascript")' },
108
+ after: { type: 'string', description: 'Filter by date after (ISO string, e.g. "2026-01-01")' },
109
+ before: { type: 'string', description: 'Filter by date before (ISO string)' },
110
+ hasBacklinks: { type: 'boolean', description: 'Filter by presence of backlinks' },
111
+ },
112
+ required: ['vaultPath', 'query'],
113
+ },
114
+ },
115
+ {
116
+ name: 'graph_get',
117
+ description: 'Get knowledge graph data',
118
+ inputSchema: {
119
+ type: 'object',
120
+ properties: {
121
+ vaultPath: { type: 'string', description: 'Path to the vault' },
122
+ },
123
+ required: ['vaultPath'],
124
+ },
125
+ },
126
+ {
127
+ name: 'graph_neighbors',
128
+ description: 'Get linked notes for a specific note',
129
+ inputSchema: {
130
+ type: 'object',
131
+ properties: {
132
+ vaultPath: { type: 'string', description: 'Path to the vault' },
133
+ path: { type: 'string', description: 'Path to the note' },
134
+ },
135
+ required: ['vaultPath', 'path'],
136
+ },
137
+ },
138
+ ];
139
+
140
+ const server = new Server(
141
+ { name: 'opensidian-mcp', version: '0.1.0' },
142
+ {
143
+ capabilities: {
144
+ tools: {},
145
+ },
146
+ }
147
+ );
148
+
149
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
150
+ return { tools };
151
+ });
152
+
153
+ export async function handleToolCall(name: string, args: unknown) {
154
+ try {
155
+ switch (name) {
156
+ case 'vault_list': {
157
+ const vaults = vaultManager.listVaults();
158
+ return {
159
+ content: [{ type: 'text', text: JSON.stringify(vaults, null, 2) }],
160
+ };
161
+ }
162
+
163
+ case 'vault_open': {
164
+ const schema = z.object({ path: z.string() });
165
+ const { path } = schema.parse(args);
166
+ const vault = vaultManager.openVault(path);
167
+ return {
168
+ content: [{ type: 'text', text: JSON.stringify(vault, null, 2) }],
169
+ };
170
+ }
171
+
172
+ case 'vault_create': {
173
+ const schema = z.object({ name: z.string(), path: z.string() });
174
+ const { name, path } = schema.parse(args);
175
+ const vault = vaultManager.createVault(name, path);
176
+ return {
177
+ content: [{ type: 'text', text: JSON.stringify(vault, null, 2) }],
178
+ };
179
+ }
180
+
181
+ case 'note_create': {
182
+ const schema = z.object({
183
+ vaultPath: z.string(),
184
+ filename: z.string(),
185
+ content: z.string(),
186
+ });
187
+ const { vaultPath, filename, content } = schema.parse(args);
188
+ const note = await vaultManager.createNote(vaultPath, filename, content);
189
+ graphEngine.indexNote(vaultPath, note);
190
+ syncService.broadcastChange({ type: 'note_created', note });
191
+ return {
192
+ content: [{ type: 'text', text: JSON.stringify(note, null, 2) }],
193
+ };
194
+ }
195
+
196
+ case 'note_read': {
197
+ const schema = z.object({ vaultPath: z.string(), path: z.string() });
198
+ const { vaultPath, path } = schema.parse(args);
199
+ const note = await vaultManager.readNote(vaultPath, path);
200
+ return {
201
+ content: [{ type: 'text', text: JSON.stringify(note, null, 2) }],
202
+ };
203
+ }
204
+
205
+ case 'note_update': {
206
+ const schema = z.object({
207
+ vaultPath: z.string(),
208
+ path: z.string(),
209
+ content: z.string(),
210
+ });
211
+ const { vaultPath, path, content } = schema.parse(args);
212
+ const note = await vaultManager.updateNote(vaultPath, path, content);
213
+ graphEngine.indexNote(vaultPath, note);
214
+ syncService.broadcastChange({ type: 'note_updated', note });
215
+ return {
216
+ content: [{ type: 'text', text: JSON.stringify(note, null, 2) }],
217
+ };
218
+ }
219
+
220
+ case 'note_delete': {
221
+ const schema = z.object({ vaultPath: z.string(), path: z.string() });
222
+ const { vaultPath, path } = schema.parse(args);
223
+ await vaultManager.deleteNote(vaultPath, path);
224
+ graphEngine.removeNote(vaultPath, path);
225
+ syncService.broadcastChange({ type: 'note_deleted', note: { path } });
226
+ return {
227
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
228
+ };
229
+ }
230
+
231
+ case 'note_search': {
232
+ const schema = z.object({
233
+ vaultPath: z.string(),
234
+ query: z.string(),
235
+ tag: z.string().optional(),
236
+ after: z.string().optional(),
237
+ before: z.string().optional(),
238
+ hasBacklinks: z.boolean().optional(),
239
+ });
240
+ const { vaultPath, query, tag, after, before, hasBacklinks } = schema.parse(args);
241
+ const results = await vaultManager.fullTextSearch(vaultPath, query, { tag, after, before, hasBacklinks });
242
+ return {
243
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
244
+ };
245
+ }
246
+
247
+ case 'graph_get': {
248
+ const schema = z.object({ vaultPath: z.string() });
249
+ const { vaultPath } = schema.parse(args);
250
+ const graph = graphEngine.getGraph(vaultPath);
251
+ return {
252
+ content: [{ type: 'text', text: JSON.stringify(graph, null, 2) }],
253
+ };
254
+ }
255
+
256
+ case 'graph_neighbors': {
257
+ const schema = z.object({ vaultPath: z.string(), path: z.string() });
258
+ const { vaultPath, path } = schema.parse(args);
259
+ const neighbors = graphEngine.getNeighbors(vaultPath, path);
260
+ return {
261
+ content: [{ type: 'text', text: JSON.stringify(neighbors, null, 2) }],
262
+ };
263
+ }
264
+
265
+ default:
266
+ return {
267
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
268
+ isError: true,
269
+ };
270
+ }
271
+ } catch (error) {
272
+ return {
273
+ content: [
274
+ {
275
+ type: 'text',
276
+ text: error instanceof Error ? error.message : String(error),
277
+ },
278
+ ],
279
+ isError: true,
280
+ };
281
+ }
282
+ }
283
+
284
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
285
+ const { name, arguments: args } = request.params;
286
+ return handleToolCall(name, args);
287
+ });
288
+
289
+ export async function startServer() {
290
+ const transport = new StdioServerTransport();
291
+ await server.connect(transport);
292
+ console.error('OpenSidian MCP Server running on stdio');
293
+ }
294
+
295
+ export { tools };
296
+
@@ -0,0 +1,120 @@
1
+ import { Plugin, PluginContext, PluginCommand } from '../shared/types.js';
2
+ import { VaultManager } from '../core/vault.js';
3
+ import { GraphEngine } from '../core/graph.js';
4
+
5
+ export class PluginHost {
6
+ private plugins: Map<string, Plugin> = new Map();
7
+ private commands: Map<string, PluginCommand> = new Map();
8
+ private hooks: Map<string, Function[]> = new Map();
9
+ private vaultManager: VaultManager;
10
+ private graphEngine: GraphEngine;
11
+
12
+ constructor() {
13
+ this.vaultManager = new VaultManager();
14
+ this.graphEngine = new GraphEngine();
15
+ }
16
+
17
+ async loadPlugin(plugin: Plugin): Promise<void> {
18
+ if (this.plugins.has(plugin.name)) {
19
+ throw new Error(`Plugin ${plugin.name} is already loaded`);
20
+ }
21
+
22
+ const context = this.createContext(plugin.name);
23
+
24
+ try {
25
+ await plugin.onLoad(context);
26
+ this.plugins.set(plugin.name, plugin);
27
+ console.error(`Plugin ${plugin.name} loaded successfully`);
28
+ } catch (error) {
29
+ console.error(`Failed to load plugin ${plugin.name}:`, error);
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ async unloadPlugin(name: string): Promise<void> {
35
+ const plugin = this.plugins.get(name);
36
+ if (!plugin) {
37
+ throw new Error(`Plugin ${name} is not loaded`);
38
+ }
39
+
40
+ if (plugin.onUnload) {
41
+ await plugin.onUnload();
42
+ }
43
+
44
+ for (const [commandId, command] of this.commands.entries()) {
45
+ if ((command as unknown as { pluginName?: string }).pluginName === name) {
46
+ this.commands.delete(commandId);
47
+ }
48
+ }
49
+
50
+ for (const [hookName, handlers] of this.hooks.entries()) {
51
+ const filtered = handlers.filter(h => (h as unknown as { pluginName?: string }).pluginName !== name);
52
+ if (filtered.length === 0) {
53
+ this.hooks.delete(hookName);
54
+ } else {
55
+ this.hooks.set(hookName, filtered);
56
+ }
57
+ }
58
+
59
+ this.plugins.delete(name);
60
+ console.error(`Plugin ${name} unloaded`);
61
+ }
62
+
63
+ registerCommand(command: PluginCommand & { pluginName?: string }): void {
64
+ if (this.commands.has(command.id)) {
65
+ throw new Error(`Command ${command.id} is already registered`);
66
+ }
67
+ (command as unknown as { pluginName?: string }).pluginName = command.pluginName;
68
+ this.commands.set(command.id, command);
69
+ }
70
+
71
+ registerHook(hook: string, handler: Function & { pluginName?: string }, pluginName?: string): void {
72
+ if (!this.hooks.has(hook)) {
73
+ this.hooks.set(hook, []);
74
+ }
75
+ (handler as unknown as { pluginName?: string }).pluginName = pluginName;
76
+ this.hooks.get(hook)!.push(handler);
77
+ }
78
+
79
+ async executeHook(hook: string, ...args: unknown[]): Promise<unknown[]> {
80
+ const handlers = this.hooks.get(hook) || [];
81
+ const results: unknown[] = [];
82
+
83
+ for (const handler of handlers) {
84
+ try {
85
+ const result = await handler(...args);
86
+ results.push(result);
87
+ } catch (error) {
88
+ console.error(`Error in hook ${hook}:`, error);
89
+ results.push(undefined);
90
+ }
91
+ }
92
+
93
+ return results;
94
+ }
95
+
96
+ getCommand(id: string): PluginCommand | undefined {
97
+ return this.commands.get(id);
98
+ }
99
+
100
+ listCommands(): PluginCommand[] {
101
+ return Array.from(this.commands.values());
102
+ }
103
+
104
+ listPlugins(): string[] {
105
+ return Array.from(this.plugins.keys());
106
+ }
107
+
108
+ private createContext(pluginName: string): PluginContext {
109
+ return {
110
+ registerCommand: (command: PluginCommand) => {
111
+ this.registerCommand({ ...command, pluginName });
112
+ },
113
+ registerHook: (hook: string, handler: Function) => {
114
+ this.registerHook(hook, handler, pluginName);
115
+ },
116
+ getVaultManager: () => this.vaultManager,
117
+ getGraphEngine: () => this.graphEngine,
118
+ };
119
+ }
120
+ }
@@ -0,0 +1,29 @@
1
+ import { Plugin, PluginContext } from '../shared/types.js';
2
+
3
+ export class SamplePlugin implements Plugin {
4
+ name = 'sample-plugin';
5
+ version = '1.0.0';
6
+
7
+ async onLoad(context: PluginContext): Promise<void> {
8
+ context.registerCommand({
9
+ id: 'sample:greet',
10
+ label: 'Greet',
11
+ execute: () => console.log('Hello from sample plugin!'),
12
+ });
13
+
14
+ context.registerHook('note:pre-save', (note: { path: string; content: string }) => {
15
+ console.log(`Sample plugin: note ${note.path} is being saved`);
16
+ return note;
17
+ });
18
+
19
+ context.registerHook('note:post-save', (note: { path: string }) => {
20
+ console.log(`Sample plugin: note ${note.path} has been saved`);
21
+ });
22
+ }
23
+
24
+ async onUnload(): Promise<void> {
25
+ console.log('Sample plugin unloading...');
26
+ }
27
+ }
28
+
29
+ export default new SamplePlugin();
package/src/server.ts ADDED
@@ -0,0 +1,90 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import { createServer } from 'http';
4
+ import path from 'path';
5
+ import { existsSync } from 'fs';
6
+ import { WebSocketServer } from 'ws';
7
+ import routes from './api/routes.js';
8
+ import authRoutes from './api/auth.js';
9
+ import themeRoutes from './api/themes.js';
10
+ import { SyncService } from './core/sync.js';
11
+ import { Config } from './shared/types.js';
12
+
13
+ export class OpenSidianServer {
14
+ private app: express.Application;
15
+ private httpServer: ReturnType<typeof createServer> | null = null;
16
+ private wss: WebSocketServer | null = null;
17
+ private syncService: SyncService;
18
+ private port: number;
19
+ private host: string;
20
+
21
+ constructor(config: Partial<Config> = {}) {
22
+ this.port = config.server?.port || 3000;
23
+ this.host = config.server?.host || '0.0.0.0';
24
+ this.syncService = new SyncService(config.sync?.port || 3001);
25
+
26
+ this.app = express();
27
+ this.setupMiddleware();
28
+ this.setupRoutes();
29
+ }
30
+
31
+ private setupMiddleware(): void {
32
+ this.app.use(cors());
33
+ this.app.use(express.json());
34
+ this.app.use(express.urlencoded({ extended: true }));
35
+ }
36
+
37
+ private setupRoutes(): void {
38
+ this.app.get('/health', (_req, res) => {
39
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
40
+ });
41
+
42
+ this.app.use('/api', routes);
43
+ this.app.use('/auth', authRoutes);
44
+ this.app.use('/api/themes', themeRoutes);
45
+
46
+ const webDist = path.resolve(process.cwd(), 'web', 'dist');
47
+ if (existsSync(webDist)) {
48
+ this.app.use(express.static(webDist));
49
+ this.app.get('*', (_req, res) => {
50
+ res.sendFile(path.join(webDist, 'index.html'));
51
+ });
52
+ }
53
+ }
54
+
55
+ start(): void {
56
+ this.httpServer = createServer(this.app);
57
+
58
+ this.wss = new WebSocketServer({ server: this.httpServer });
59
+ this.wss.on('connection', (socket) => {
60
+ socket.on('message', (data) => {
61
+ try {
62
+ const message = JSON.parse(data.toString());
63
+ console.log('WebSocket message:', message);
64
+ } catch (error) {
65
+ console.error('Invalid WebSocket message:', error);
66
+ }
67
+ });
68
+ });
69
+
70
+ this.syncService.start();
71
+
72
+ this.httpServer.listen(this.port, this.host, () => {
73
+ console.error(`OpenSidian server running at http://${this.host}:${this.port}`);
74
+ });
75
+ }
76
+
77
+ stop(): void {
78
+ this.syncService.stop();
79
+
80
+ if (this.wss) {
81
+ this.wss.close();
82
+ this.wss = null;
83
+ }
84
+
85
+ if (this.httpServer) {
86
+ this.httpServer.close();
87
+ this.httpServer = null;
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,92 @@
1
+ import type { GraphEngine } from '../core/graph.js';
2
+ import type { VaultManager } from '../core/vault.js';
3
+
4
+ export interface Vault {
5
+ path: string;
6
+ name: string;
7
+ created: string;
8
+ notes: Note[];
9
+ }
10
+
11
+ export interface Note {
12
+ path: string;
13
+ filename: string;
14
+ content: string;
15
+ frontmatter: Record<string, unknown>;
16
+ links: string[];
17
+ created: string;
18
+ modified: string;
19
+ }
20
+
21
+ export interface ParsedNote<T extends Record<string, unknown> = Record<string, unknown>> {
22
+ frontmatter: T;
23
+ body: string;
24
+ html: string;
25
+ links: string[];
26
+ headings: Array<{ level: number; text: string; id: string }>;
27
+ }
28
+
29
+ export interface Graph {
30
+ nodes: GraphNode[];
31
+ edges: GraphEdge[];
32
+ metadata: GraphMetadata;
33
+ }
34
+
35
+ export interface GraphNode {
36
+ id: string;
37
+ label: string;
38
+ tags?: string[];
39
+ connections?: number;
40
+ }
41
+
42
+ export interface GraphEdge {
43
+ id: string;
44
+ source: string;
45
+ target: string;
46
+ type: 'wikilink' | 'mdlink' | 'tag';
47
+ }
48
+
49
+ export interface GraphMetadata {
50
+ totalNotes: number;
51
+ totalLinks: number;
52
+ }
53
+
54
+ export interface Plugin {
55
+ name: string;
56
+ version: string;
57
+ onLoad(context: PluginContext): Promise<void> | void;
58
+ onUnload?(): Promise<void> | void;
59
+ }
60
+
61
+ export interface PluginContext {
62
+ registerCommand(command: PluginCommand): void;
63
+ registerHook(hook: string, handler: Function): void;
64
+ getVaultManager(): VaultManager;
65
+ getGraphEngine(): GraphEngine;
66
+ }
67
+
68
+ export interface PluginCommand {
69
+ id: string;
70
+ label: string;
71
+ icon?: string;
72
+ execute(): void | Promise<void>;
73
+ }
74
+
75
+ export interface Config {
76
+ server: {
77
+ port: number;
78
+ host: string;
79
+ };
80
+ vaults: {
81
+ defaultPath: string;
82
+ autoOpen: boolean;
83
+ };
84
+ sync: {
85
+ enabled: boolean;
86
+ port: number;
87
+ };
88
+ plugins: {
89
+ enabled: boolean;
90
+ paths: string[];
91
+ };
92
+ }