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,47 @@
1
+ # State: OpenSidian
2
+
3
+ **Version:** 2.0.0
4
+ **Status:** completed
5
+ **Started:** 2026-05-06
6
+ **Completed:** 2026-05-06
7
+
8
+ ## Milestone v2.0
9
+
10
+ **Status:** Complete 🎉
11
+
12
+ All 3 phases finished:
13
+
14
+ | Phase | Status |
15
+ |-------|--------|
16
+ | 8. Indexador Full-Text (TF-IDF, stopwords, persistência) | ✅ |
17
+ | 9. API de Busca (REST + MCP + filtros + highlighting) | ✅ |
18
+ | 10. Frontend de Busca (painel avançado com filtros visuais) | ✅ |
19
+
20
+ ## Project Reference
21
+
22
+ See: .planning/PROJECT.md (updated 2026-05-06)
23
+
24
+ **Core value:** Usuários podem escrever notas em Markdown, conectá-las com links, visualizar o grafo de conhecimento e sincronizar em tempo real — open-source e auto-hospedável.
25
+
26
+ ## Decisions
27
+
28
+ | Decision | Rationale | Outcome |
29
+ |----------|-----------|---------|
30
+ | TypeScript + Node.js | Ecossistema maduro, tipagem segura | ✓ Good |
31
+ | MCP SDK oficial | Integração nativa com LLMs | ✓ Good |
32
+ | Markdown puro | Máxima compatibilidade | ✓ Good |
33
+ | Vitest | Mais rápido, nativo ESM | ✓ Good |
34
+ | React + Vite | Popular, tipagem forte, build rápido | ✓ Good |
35
+
36
+ ## Resultados
37
+
38
+ - 79 testes unitários passando
39
+ - Cobertura de código >80%
40
+ - Busca full-text com TF-IDF e ranking por relevância
41
+ - Highlighting com snippets posicionais e tags `<mark>`
42
+ - Filtros por tag, data e backlinks
43
+ - Índice persistido em disco
44
+ - Painel de busca avançada no frontend
45
+
46
+ ---
47
+ *Last updated: 2026-05-06 after milestone v2.0 completion*
@@ -0,0 +1,14 @@
1
+ {
2
+ "mode": "yolo",
3
+ "granularity": "coarse",
4
+ "parallelization": true,
5
+ "commit_docs": true,
6
+ "model_profile": "balanced",
7
+ "workflow": {
8
+ "research": true,
9
+ "plan_check": true,
10
+ "verifier": true,
11
+ "nyquist_validation": false,
12
+ "auto_advance": true
13
+ }
14
+ }
@@ -0,0 +1,232 @@
1
+ # Contributing to OpenSidian
2
+
3
+ Thank you for your interest in contributing to OpenSidian!
4
+
5
+ ## Development Setup
6
+
7
+ ### Prerequisites
8
+
9
+ - Node.js 20.x or higher
10
+ - npm 10.x or higher
11
+
12
+ ### Getting Started
13
+
14
+ 1. **Clone the repository**
15
+ ```bash
16
+ git clone https://github.com/your-username/opensidian.git
17
+ cd opensidian
18
+ ```
19
+
20
+ 2. **Install dependencies**
21
+ ```bash
22
+ npm install
23
+ ```
24
+
25
+ 3. **Run tests**
26
+ ```bash
27
+ npm test
28
+ ```
29
+
30
+ 4. **Build the project**
31
+ ```bash
32
+ npm run build
33
+ ```
34
+
35
+ 5. **Start development server**
36
+ ```bash
37
+ npm run dev
38
+ ```
39
+
40
+ ## Project Structure
41
+
42
+ ```
43
+ opensidian/
44
+ ├── src/
45
+ │ ├── index.ts # Application entry point
46
+ │ ├── server.ts # Express server setup
47
+ │ ├── api/
48
+ │ │ └── routes.ts # REST API routes
49
+ │ ├── core/
50
+ │ │ ├── vault.ts # Vault management
51
+ │ │ ├── markdown.ts # Markdown parsing
52
+ │ │ ├── graph.ts # Knowledge graph
53
+ │ │ └── sync.ts # Real-time sync
54
+ │ ├── mcp/
55
+ │ │ └── server.ts # MCP server
56
+ │ ├── plugins/
57
+ │ │ ├── host.ts # Plugin system
58
+ │ │ └── sample-plugin.ts
59
+ │ └── shared/
60
+ │ └── types.ts # TypeScript types
61
+ ├── tests/ # Test files
62
+ ├── docker/ # Docker configuration
63
+ └── docs/ # Documentation
64
+ ```
65
+
66
+ ## Coding Standards
67
+
68
+ ### TypeScript
69
+
70
+ - Use strict mode in `tsconfig.json`
71
+ - Prefer interfaces over type aliases for object shapes
72
+ - Use explicit return types for public functions
73
+ - Avoid `any` type
74
+
75
+ ### Code Style
76
+
77
+ - 2 spaces for indentation
78
+ - Single quotes for strings
79
+ - Trailing commas in multiline
80
+ - Semicolons at end of statements
81
+
82
+ ### Naming Conventions
83
+
84
+ - **Classes**: PascalCase (e.g., `VaultManager`)
85
+ - **Functions/Methods**: camelCase (e.g., `createNote`)
86
+ - **Constants**: SCREAMING_SNAKE_CASE (e.g., `DEFAULT_PORT`)
87
+ - **Files**: kebab-case (e.g., `vault-manager.ts`)
88
+
89
+ ## Testing
90
+
91
+ ### Writing Tests
92
+
93
+ - Place test files in `tests/` directory
94
+ - Name test files matching source: `vault.test.ts` for `vault.ts`
95
+ - Use descriptive test names: `should create note with frontmatter`
96
+ - Mock external dependencies
97
+
98
+ ### Test Coverage
99
+
100
+ Maintain coverage above 80%:
101
+ - Statements
102
+ - Branches
103
+ - Functions
104
+ - Lines
105
+
106
+ Run coverage report:
107
+ ```bash
108
+ npm run test:coverage
109
+ ```
110
+
111
+ ## Git Workflow
112
+
113
+ ### Branch Naming
114
+
115
+ - `main` - Production-ready code
116
+ - `develop` - Integration branch
117
+ - `feature/*` - New features
118
+ - `bugfix/*` - Bug fixes
119
+ - `hotfix/*` - Urgent production fixes
120
+
121
+ ### Commit Messages
122
+
123
+ Follow conventional commits:
124
+
125
+ ```
126
+ feat: add note search functionality
127
+ fix: resolve vault creation error
128
+ docs: update API documentation
129
+ test: add graph engine tests
130
+ refactor: improve link extraction
131
+ ```
132
+
133
+ ### Pull Requests
134
+
135
+ 1. Create feature branch from `develop`
136
+ 2. Make your changes
137
+ 3. Add tests
138
+ 4. Ensure all tests pass
139
+ 5. Submit PR with description
140
+
141
+ ## API Development
142
+
143
+ ### REST API
144
+
145
+ Add new routes in `src/api/routes.ts`:
146
+
147
+ ```typescript
148
+ router.get('/resource', (req, res) => {
149
+ // Handle request
150
+ res.json({ data });
151
+ });
152
+ ```
153
+
154
+ ### MCP Tools
155
+
156
+ Add new tools in `src/mcp/server.ts`:
157
+
158
+ ```typescript
159
+ {
160
+ name: 'tool_name',
161
+ description: 'What the tool does',
162
+ inputSchema: {
163
+ type: 'object',
164
+ properties: {
165
+ param: { type: 'string', description: 'Description' }
166
+ },
167
+ required: ['param']
168
+ }
169
+ }
170
+ ```
171
+
172
+ ## Plugin Development
173
+
174
+ ### Creating a Plugin
175
+
176
+ ```typescript
177
+ import { Plugin, PluginContext } from '@opensidian/plugin-api';
178
+
179
+ const myPlugin: Plugin = {
180
+ name: 'my-plugin',
181
+ version: '1.0.0',
182
+ async onLoad(context: PluginContext) {
183
+ context.registerCommand({
184
+ id: 'my-plugin:command',
185
+ label: 'My Command',
186
+ execute: () => console.log('Hello!')
187
+ });
188
+
189
+ context.registerHook('note:post-save', (note) => {
190
+ console.log('Note saved:', note.path);
191
+ });
192
+ }
193
+ };
194
+
195
+ export default myPlugin;
196
+ ```
197
+
198
+ ### Plugin Hooks
199
+
200
+ - `note:pre-save(note)` - Modify note before saving
201
+ - `note:post-save(note)` - Side effects after saving
202
+ - `vault:pre-create(vault)` - Before vault creation
203
+ - `vault:post-create(vault)` - After vault creation
204
+
205
+ ## Documentation
206
+
207
+ - Update README.md for user-facing changes
208
+ - Update ARCHITECTURE.md for architectural changes
209
+ - Add JSDoc comments for public APIs
210
+ - Update type definitions in `types.ts`
211
+
212
+ ## Issues
213
+
214
+ ### Bug Reports
215
+
216
+ Include:
217
+ - Clear description
218
+ - Steps to reproduce
219
+ - Expected vs actual behavior
220
+ - Environment (OS, Node.js version)
221
+
222
+ ### Feature Requests
223
+
224
+ Include:
225
+ - Problem statement
226
+ - Proposed solution
227
+ - Use cases
228
+ - Alternatives considered
229
+
230
+ ## License
231
+
232
+ By contributing, you agree that your contributions will be licensed under the MIT License.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OpenSidian Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # OpenSidian
2
+
3
+ **OpenSidian** é um sistema de gerenciamento de conhecimento open-source inspirado no Obsidian. Permite criar, organizar e interconectar notas em Markdown com indexação de grafo, sincronização em tempo real e suporte a plugins.
4
+
5
+ [![CI](https://github.com/thiagovasconcelosti/opensidian/actions/workflows/ci.yml/badge.svg)](https://github.com/thiagovasconcelosti/opensidian/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ ## Funcionalidades
9
+
10
+ - **Vaults**: Gerenciar múltiplos cofres de notas
11
+ - **Editor Markdown**: Preview ao vivo com suporte a `[[wikilink]]`
12
+ - **Grafo de Conhecimento**: Visualização interativa com D3.js
13
+ - **Sincronização**: Tempo real via WebSocket
14
+ - **Autenticação**: Login com JWT e sessão persistente
15
+ - **Temas**: Claro, escuro e temas customizáveis via editor visual
16
+ - **Plugins**: API para extensões com hooks e comandos
17
+ - **MCP Server**: Integração com IAs via Model Context Protocol
18
+ - **REST API**: HTTP endpoints para integração
19
+ - **Docker**: Containerização multi-stage
20
+
21
+ ## Stack
22
+
23
+ | Camada | Tecnologia |
24
+ |--------|-----------|
25
+ | Backend | Node.js 20+, TypeScript, Express |
26
+ | Frontend | React 18, Vite, React Router |
27
+ | Graph | D3.js (force simulation) |
28
+ | Markdown | marked, front-matter |
29
+ | Sync | WebSocket (ws) |
30
+ | MCP | @modelcontextprotocol/sdk |
31
+ | Testes | Vitest (67 testes, cobertura >80%) |
32
+ | CI/CD | GitHub Actions (Windows/Mac/Linux) |
33
+ | Container | Docker multi-stage |
34
+
35
+ ## Começar
36
+
37
+ ```bash
38
+ git clone https://github.com/thiagovasconcelosti/opensidian.git
39
+ cd opensidian
40
+ npm install
41
+ npm run build
42
+ npm test
43
+ npm run dev
44
+ ```
45
+
46
+ Acessar: http://localhost:3000
47
+
48
+ ## Estrutura
49
+
50
+ ```
51
+ opensidian/
52
+ ├── src/
53
+ │ ├── api/ # REST + Auth + Themes
54
+ │ ├── core/ # Vault, Markdown, Graph, Sync
55
+ │ ├── mcp/ # Model Context Protocol server
56
+ │ └── plugins/ # Plugin host + sample
57
+ ├── web/ # React frontend
58
+ │ └── src/
59
+ │ ├── api/ # HTTP client
60
+ │ ├── components/ # UI components
61
+ │ └── hooks/ # AuthContext, ThemeContext
62
+ ├── tests/ # 67 testes unitários
63
+ ├── docker/ # Dockerfile + compose
64
+ └── docs/ # Arquitetura
65
+ ```
66
+
67
+ ## API REST
68
+
69
+ ### Auth
70
+
71
+ | Método | Rota | Descrição |
72
+ |--------|------|-----------|
73
+ | POST | `/auth/register` | Criar conta |
74
+ | POST | `/auth/login` | Login |
75
+ | GET | `/auth/me` | Dados do usuário |
76
+ | POST | `/auth/logout` | Logout |
77
+
78
+ ### Vaults
79
+
80
+ | Método | Rota | Descrição |
81
+ |--------|------|-----------|
82
+ | GET | `/api/vaults` | Listar vaults |
83
+ | POST | `/api/vaults` | Criar vault |
84
+ | GET | `/api/vaults/:id` | Abrir vault |
85
+
86
+ ### Notas
87
+
88
+ | Método | Rota | Descrição |
89
+ |--------|------|-----------|
90
+ | GET | `/api/notes?vault=` | Listar notas |
91
+ | POST | `/api/notes` | Criar nota |
92
+ | GET | `/api/notes/:path?vault=` | Ler nota |
93
+ | PUT | `/api/notes/:path?vault=` | Atualizar |
94
+ | DELETE | `/api/notes/:path?vault=` | Deletar |
95
+
96
+ ### Graph
97
+
98
+ | Método | Rota | Descrição |
99
+ |--------|------|-----------|
100
+ | GET | `/api/graph?vault=` | Grafo completo |
101
+ | GET | `/api/graph/neighbors/:path?vault=` | Vizinhança |
102
+
103
+ ### Temas
104
+
105
+ | Método | Rota | Descrição |
106
+ |--------|------|-----------|
107
+ | GET | `/api/themes` | Listar temas |
108
+ | POST | `/api/themes` | Salvar tema |
109
+ | DELETE | `/api/themes/:name` | Deletar tema |
110
+
111
+ ## Plugin API
112
+
113
+ O sistema de plugins permite estender o OpenSidian com funcionalidades customizadas.
114
+
115
+ ### Interface
116
+
117
+ ```typescript
118
+ interface Plugin {
119
+ name: string; // Identificador único
120
+ version: string; // Semver
121
+ onLoad(ctx: PluginContext): void;
122
+ onUnload?(): void;
123
+ }
124
+
125
+ interface PluginContext {
126
+ registerCommand(cmd: PluginCommand): void;
127
+ registerHook(hook: string, handler: Function): void;
128
+ getVaultManager(): VaultManager;
129
+ getGraphEngine(): GraphEngine;
130
+ }
131
+ ```
132
+
133
+ ### Hooks disponíveis
134
+
135
+ | Hook | Disparo | Uso |
136
+ |------|---------|-----|
137
+ | `note:pre-save` | Antes de salvar | Modificar conteúdo |
138
+ | `note:post-save` | Após salvar | Side effects |
139
+
140
+ ### Exemplo
141
+
142
+ ```typescript
143
+ import { Plugin, PluginContext } from '../../shared/types';
144
+
145
+ export default {
146
+ name: 'meu-plugin',
147
+ version: '1.0.0',
148
+ onLoad(ctx: PluginContext) {
149
+ ctx.registerCommand({
150
+ id: 'meu-plugin:hello',
151
+ label: 'Dizer olá',
152
+ execute: () => console.log('Olá!'),
153
+ });
154
+ ctx.registerHook('note:post-save', (note) => {
155
+ console.log(`Nota salva: ${note.path}`);
156
+ });
157
+ },
158
+ } satisfies Plugin;
159
+ ```
160
+
161
+ Para carregar, coloque o arquivo em `./plugins/` e reinicie o servidor.
162
+
163
+ ## MCP Server (Model Context Protocol)
164
+
165
+ O servidor MCP permite que IAs (Claude, etc.) interajam com o OpenSidian via stdio.
166
+
167
+ ```bash
168
+ npx tsx src/mcp/server.ts
169
+ ```
170
+
171
+ Ou via pacote npm (após publicar):
172
+
173
+ ```bash
174
+ npx opensidian-mcp
175
+ ```
176
+
177
+ ### Configuração no Claude Desktop
178
+
179
+ Adicione no `claude_desktop_config.json`:
180
+
181
+ ```json
182
+ {
183
+ "mcpServers": {
184
+ "opensidian": {
185
+ "command": "npx",
186
+ "args": ["opensidian-mcp"]
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
192
+ ### Ferramentas expostas
193
+
194
+ - `vault_list`, `vault_open`, `vault_create`
195
+ - `note_create`, `note_read`, `note_update`, `note_delete`, `note_search`
196
+ - `graph_get`, `graph_neighbors`
197
+
198
+ ## Docker
199
+
200
+ ```bash
201
+ docker-compose -f docker/docker-compose.yml up -d
202
+ ```
203
+
204
+ Ou build manual:
205
+
206
+ ```bash
207
+ docker build -f docker/Dockerfile -t opensidian .
208
+ docker run -p 3000:3000 -p 3001:3001 -v ./vaults:/app/vaults opensidian
209
+ ```
210
+
211
+ ## Testes
212
+
213
+ ```bash
214
+ npm test # 67 testes
215
+ npm run test:coverage # Cobertura >80%
216
+ ```
217
+
218
+ ## Milestones
219
+
220
+ ### v1.0 (7 fases)
221
+
222
+ | Fase | Status |
223
+ |------|--------|
224
+ | 1. Backend Core | ✅ |
225
+ | 2. Sync & API | ✅ |
226
+ | 3. Plugin System | ✅ |
227
+ | 4. Web UI | ✅ |
228
+ | 5. Authentication | ✅ |
229
+ | 6. Themes | ✅ |
230
+ | 7. Polish & Release | ✅ |
231
+
232
+ ### v2.0 — Busca Full-Text (3 fases)
233
+
234
+ | Fase | Status |
235
+ |------|--------|
236
+ | 8. Indexador Full-Text (TF-IDF, stopwords, persistência) | ✅ |
237
+ | 9. API de Busca (REST + MCP + filtros + highlighting) | ✅ |
238
+ | 10. Frontend de Busca (painel avançado com filtros visuais) | ✅ |
239
+
240
+ **79 testes** | **Cobertura >80%** | **Build TypeScript** | **Docker** | **CI/CD**
241
+
242
+ ## Licença
243
+
244
+ MIT. Veja [LICENSE](LICENSE).
@@ -0,0 +1,5 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ export declare function authMiddleware(req: Request, res: Response, next: NextFunction): void;
3
+ declare const router: import("express-serve-static-core").Router;
4
+ export default router;
5
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAmDnE,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAgBpF;AAED,QAAA,MAAM,MAAM,4CAAmB,CAAC;AA2DhC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,112 @@
1
+ import crypto from 'crypto';
2
+ import express from 'express';
3
+ const users = [];
4
+ const JWT_SECRET = process.env.JWT_SECRET || crypto.randomBytes(32).toString('hex');
5
+ function hashPassword(password, salt) {
6
+ return crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha256').toString('hex');
7
+ }
8
+ function generateSalt() {
9
+ return crypto.randomBytes(16).toString('hex');
10
+ }
11
+ function base64UrlEncode(str) {
12
+ return Buffer.from(str).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
13
+ }
14
+ function base64UrlDecode(str) {
15
+ str = str.replace(/-/g, '+').replace(/_/g, '/');
16
+ while (str.length % 4)
17
+ str += '=';
18
+ return Buffer.from(str, 'base64').toString();
19
+ }
20
+ function createToken(payload) {
21
+ const header = base64UrlEncode(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
22
+ const body = base64UrlEncode(JSON.stringify({ ...payload, iat: Date.now() }));
23
+ const signature = crypto.createHmac('sha256', JWT_SECRET).update(`${header}.${body}`).digest('base64url');
24
+ return `${header}.${body}.${signature}`;
25
+ }
26
+ function verifyToken(token) {
27
+ const parts = token.split('.');
28
+ if (parts.length !== 3)
29
+ return null;
30
+ const signature = crypto.createHmac('sha256', JWT_SECRET).update(`${parts[0]}.${parts[1]}`).digest('base64url');
31
+ if (signature !== parts[2])
32
+ return null;
33
+ try {
34
+ return JSON.parse(base64UrlDecode(parts[1]));
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ export function authMiddleware(req, res, next) {
41
+ const header = req.headers.authorization;
42
+ if (!header?.startsWith('Bearer ')) {
43
+ res.status(401).json({ error: 'Token não fornecido' });
44
+ return;
45
+ }
46
+ const payload = verifyToken(header.slice(7));
47
+ if (!payload || !payload.userId) {
48
+ res.status(401).json({ error: 'Token inválido ou expirado' });
49
+ return;
50
+ }
51
+ req.user = {
52
+ userId: payload.userId,
53
+ email: payload.email,
54
+ };
55
+ next();
56
+ }
57
+ const router = express.Router();
58
+ router.post('/register', (req, res) => {
59
+ const { email, password, name } = req.body;
60
+ if (!email || !password || !name) {
61
+ res.status(400).json({ error: 'email, password e name são obrigatórios' });
62
+ return;
63
+ }
64
+ if (password.length < 6) {
65
+ res.status(400).json({ error: 'Senha deve ter no mínimo 6 caracteres' });
66
+ return;
67
+ }
68
+ if (users.find(u => u.email === email)) {
69
+ res.status(409).json({ error: 'Email já cadastrado' });
70
+ return;
71
+ }
72
+ const salt = generateSalt();
73
+ const user = {
74
+ id: crypto.randomUUID(),
75
+ email,
76
+ name,
77
+ passwordHash: hashPassword(password, salt),
78
+ salt,
79
+ createdAt: new Date().toISOString(),
80
+ };
81
+ users.push(user);
82
+ const token = createToken({ userId: user.id, email: user.email });
83
+ res.status(201).json({ token, user: { id: user.id, email: user.email, name: user.name } });
84
+ });
85
+ router.post('/login', (req, res) => {
86
+ const { email, password } = req.body;
87
+ if (!email || !password) {
88
+ res.status(400).json({ error: 'email e password são obrigatórios' });
89
+ return;
90
+ }
91
+ const user = users.find(u => u.email === email);
92
+ if (!user || user.passwordHash !== hashPassword(password, user.salt)) {
93
+ res.status(401).json({ error: 'Email ou senha inválidos' });
94
+ return;
95
+ }
96
+ const token = createToken({ userId: user.id, email: user.email });
97
+ res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
98
+ });
99
+ router.get('/me', authMiddleware, (req, res) => {
100
+ const { userId } = req.user;
101
+ const user = users.find(u => u.id === userId);
102
+ if (!user) {
103
+ res.status(404).json({ error: 'Usuário não encontrado' });
104
+ return;
105
+ }
106
+ res.json({ user: { id: user.id, email: user.email, name: user.name, createdAt: user.createdAt } });
107
+ });
108
+ router.post('/logout', (_req, res) => {
109
+ res.json({ message: 'Sessão encerrada' });
110
+ });
111
+ export default router;
112
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAmB,CAAC;AAgIhC,eAAe,MAAM,CAAC"}