joplin-mcp-server 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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/bin/cli.d.ts +2 -0
  4. package/dist/bin/cli.js +7 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +204 -0
  7. package/dist/lib/joplin-api-client.d.ts +23 -0
  8. package/dist/lib/joplin-api-client.js +110 -0
  9. package/dist/lib/logger.d.ts +21 -0
  10. package/dist/lib/logger.js +68 -0
  11. package/dist/lib/parse-args.d.ts +2 -0
  12. package/dist/lib/parse-args.js +81 -0
  13. package/dist/lib/tools/base-tool.d.ts +27 -0
  14. package/dist/lib/tools/base-tool.js +24 -0
  15. package/dist/lib/tools/create-folder.d.ts +9 -0
  16. package/dist/lib/tools/create-folder.js +79 -0
  17. package/dist/lib/tools/create-note.d.ts +13 -0
  18. package/dist/lib/tools/create-note.js +88 -0
  19. package/dist/lib/tools/delete-folder.d.ts +10 -0
  20. package/dist/lib/tools/delete-folder.js +138 -0
  21. package/dist/lib/tools/delete-note.d.ts +9 -0
  22. package/dist/lib/tools/delete-note.js +92 -0
  23. package/dist/lib/tools/edit-folder.d.ts +10 -0
  24. package/dist/lib/tools/edit-folder.js +136 -0
  25. package/dist/lib/tools/edit-note.d.ts +15 -0
  26. package/dist/lib/tools/edit-note.js +153 -0
  27. package/dist/lib/tools/index.d.ts +12 -0
  28. package/dist/lib/tools/index.js +12 -0
  29. package/dist/lib/tools/list-notebooks.d.ts +7 -0
  30. package/dist/lib/tools/list-notebooks.js +59 -0
  31. package/dist/lib/tools/read-multi-note.d.ts +5 -0
  32. package/dist/lib/tools/read-multi-note.js +108 -0
  33. package/dist/lib/tools/read-note.d.ts +5 -0
  34. package/dist/lib/tools/read-note.js +80 -0
  35. package/dist/lib/tools/read-notebook.d.ts +5 -0
  36. package/dist/lib/tools/read-notebook.js +66 -0
  37. package/dist/lib/tools/search-notes.d.ts +5 -0
  38. package/dist/lib/tools/search-notes.js +68 -0
  39. package/dist/tests/integration/joplin-integration.test.d.ts +1 -0
  40. package/dist/tests/integration/joplin-integration.test.js +117 -0
  41. package/dist/tests/manual/create-folder.test.d.ts +1 -0
  42. package/dist/tests/manual/create-folder.test.js +81 -0
  43. package/dist/tests/manual/create-note.test.d.ts +1 -0
  44. package/dist/tests/manual/create-note.test.js +84 -0
  45. package/dist/tests/manual/delete-folder.test.d.ts +1 -0
  46. package/dist/tests/manual/delete-folder.test.js +118 -0
  47. package/dist/tests/manual/delete-note.test.d.ts +1 -0
  48. package/dist/tests/manual/delete-note.test.js +101 -0
  49. package/dist/tests/manual/edit-folder.test.d.ts +1 -0
  50. package/dist/tests/manual/edit-folder.test.js +104 -0
  51. package/dist/tests/manual/edit-note.test.d.ts +1 -0
  52. package/dist/tests/manual/edit-note.test.js +118 -0
  53. package/dist/tests/manual/list-notebooks.test.d.ts +1 -0
  54. package/dist/tests/manual/list-notebooks.test.js +42 -0
  55. package/dist/tests/manual/read-note.test.d.ts +1 -0
  56. package/dist/tests/manual/read-note.test.js +54 -0
  57. package/dist/tests/manual/search-notes.test.d.ts +1 -0
  58. package/dist/tests/manual/search-notes.test.js +43 -0
  59. package/dist/tests/unit/create-tools.test.d.ts +1 -0
  60. package/dist/tests/unit/create-tools.test.js +223 -0
  61. package/dist/tests/unit/delete-tools.test.d.ts +1 -0
  62. package/dist/tests/unit/delete-tools.test.js +225 -0
  63. package/dist/tests/unit/edit-tools.test.d.ts +1 -0
  64. package/dist/tests/unit/edit-tools.test.js +261 -0
  65. package/dist/tests/unit/joplin-api-client.test.d.ts +1 -0
  66. package/dist/tests/unit/joplin-api-client.test.js +154 -0
  67. package/dist/vitest.config.d.ts +2 -0
  68. package/dist/vitest.config.js +22 -0
  69. package/dist/vitest.setup.d.ts +1 -0
  70. package/dist/vitest.setup.js +24 -0
  71. package/package.json +58 -0
@@ -0,0 +1,223 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import JoplinAPIClient from "../../lib/joplin-api-client.js";
3
+ import CreateNote from "../../lib/tools/create-note.js";
4
+ import CreateFolder from "../../lib/tools/create-folder.js";
5
+ // Mock JoplinAPIClient
6
+ vi.mock("../../lib/joplin-api-client.js", () => {
7
+ const mockClient = {
8
+ post: vi.fn(),
9
+ get: vi.fn(),
10
+ };
11
+ return { default: vi.fn(() => mockClient) };
12
+ });
13
+ describe("Create Tools", () => {
14
+ let mockApiClient;
15
+ let createNote;
16
+ let createFolder;
17
+ beforeEach(() => {
18
+ vi.clearAllMocks();
19
+ mockApiClient = new JoplinAPIClient({ token: "test-token" });
20
+ createNote = new CreateNote(mockApiClient);
21
+ createFolder = new CreateFolder(mockApiClient);
22
+ });
23
+ describe("CreateNote", () => {
24
+ it("should create a note with title and body", async () => {
25
+ const mockCreatedNote = {
26
+ id: "note-123",
27
+ title: "Test Note",
28
+ body: "Test content",
29
+ created_time: 1234567890000,
30
+ updated_time: 1234567890000,
31
+ parent_id: null,
32
+ is_todo: false,
33
+ };
34
+ mockApiClient.post.mockResolvedValue(mockCreatedNote);
35
+ const result = await createNote.call({
36
+ title: "Test Note",
37
+ body: "Test content",
38
+ });
39
+ expect(mockApiClient.post).toHaveBeenCalledWith("/notes", {
40
+ title: "Test Note",
41
+ body: "Test content",
42
+ });
43
+ expect(result).toContain("✅ Successfully created note!");
44
+ expect(result).toContain("Test Note");
45
+ expect(result).toContain("note-123");
46
+ });
47
+ it("should create a note in a specific notebook", async () => {
48
+ const mockCreatedNote = {
49
+ id: "note-123",
50
+ title: "Test Note",
51
+ body: "Test content",
52
+ created_time: 1234567890000,
53
+ updated_time: 1234567890000,
54
+ parent_id: "notebook-456",
55
+ is_todo: false,
56
+ };
57
+ const mockNotebook = {
58
+ id: "notebook-456",
59
+ title: "Test Notebook",
60
+ };
61
+ mockApiClient.post.mockResolvedValue(mockCreatedNote);
62
+ mockApiClient.get.mockResolvedValue(mockNotebook);
63
+ const result = await createNote.call({
64
+ title: "Test Note",
65
+ body: "Test content",
66
+ parent_id: "notebook-456",
67
+ });
68
+ expect(mockApiClient.post).toHaveBeenCalledWith("/notes", {
69
+ title: "Test Note",
70
+ body: "Test content",
71
+ parent_id: "notebook-456",
72
+ });
73
+ expect(result).toContain("Test Notebook");
74
+ });
75
+ it("should create a todo note", async () => {
76
+ const mockCreatedNote = {
77
+ id: "note-123",
78
+ title: "Test Todo",
79
+ body: "Todo content",
80
+ created_time: 1234567890000,
81
+ updated_time: 1234567890000,
82
+ parent_id: null,
83
+ is_todo: true,
84
+ todo_completed: false,
85
+ };
86
+ mockApiClient.post.mockResolvedValue(mockCreatedNote);
87
+ const result = await createNote.call({
88
+ title: "Test Todo",
89
+ body: "Todo content",
90
+ is_todo: true,
91
+ });
92
+ expect(mockApiClient.post).toHaveBeenCalledWith("/notes", {
93
+ title: "Test Todo",
94
+ body: "Todo content",
95
+ is_todo: true,
96
+ });
97
+ expect(result).toContain("Type: Todo item");
98
+ });
99
+ it("should validate required fields", async () => {
100
+ const result = await createNote.call({});
101
+ expect(result).toContain("Please provide at least a title, body, or body_html");
102
+ expect(mockApiClient.post).not.toHaveBeenCalled();
103
+ });
104
+ it("should validate parent_id format", async () => {
105
+ const result = await createNote.call({
106
+ title: "Test Note",
107
+ parent_id: "short",
108
+ });
109
+ expect(result).toContain("does not appear to be a valid notebook ID");
110
+ expect(mockApiClient.post).not.toHaveBeenCalled();
111
+ });
112
+ it("should handle API errors", async () => {
113
+ mockApiClient.post.mockRejectedValue(new Error("API Error"));
114
+ const result = await createNote.call({
115
+ title: "Test Note",
116
+ });
117
+ expect(result).toContain("Error creating note");
118
+ });
119
+ it("should handle 404 errors for parent notebook", async () => {
120
+ const error = new Error("Not found");
121
+ error.response = { status: 404 };
122
+ mockApiClient.post.mockRejectedValue(error);
123
+ const result = await createNote.call({
124
+ title: "Test Note",
125
+ parent_id: "a1b2c3d4e5f6789012345678901234567890abcd",
126
+ });
127
+ expect(result).toContain("Notebook with ID");
128
+ expect(result).toContain("not found");
129
+ });
130
+ });
131
+ describe("CreateFolder", () => {
132
+ it("should create a folder with title", async () => {
133
+ const mockCreatedFolder = {
134
+ id: "folder-123",
135
+ title: "Test Folder",
136
+ parent_id: null,
137
+ created_time: 1234567890000,
138
+ updated_time: 1234567890000,
139
+ };
140
+ mockApiClient.post.mockResolvedValue(mockCreatedFolder);
141
+ const result = await createFolder.call({
142
+ title: "Test Folder",
143
+ });
144
+ expect(mockApiClient.post).toHaveBeenCalledWith("/folders", {
145
+ title: "Test Folder",
146
+ });
147
+ expect(result).toContain("✅ Successfully created notebook!");
148
+ expect(result).toContain("Test Folder");
149
+ expect(result).toContain("folder-123");
150
+ });
151
+ it("should create a subfolder", async () => {
152
+ const mockCreatedFolder = {
153
+ id: "folder-123",
154
+ title: "Test Subfolder",
155
+ parent_id: "parent-456",
156
+ created_time: 1234567890000,
157
+ updated_time: 1234567890000,
158
+ };
159
+ const mockParentFolder = {
160
+ id: "parent-456",
161
+ title: "Parent Folder",
162
+ };
163
+ mockApiClient.post.mockResolvedValue(mockCreatedFolder);
164
+ mockApiClient.get.mockResolvedValue(mockParentFolder);
165
+ const result = await createFolder.call({
166
+ title: "Test Subfolder",
167
+ parent_id: "parent-456",
168
+ });
169
+ expect(mockApiClient.post).toHaveBeenCalledWith("/folders", {
170
+ title: "Test Subfolder",
171
+ parent_id: "parent-456",
172
+ });
173
+ expect(result).toContain("Parent Folder");
174
+ });
175
+ it("should validate required title", async () => {
176
+ const result = await createFolder.call({});
177
+ expect(result).toContain("Please provide a title");
178
+ expect(mockApiClient.post).not.toHaveBeenCalled();
179
+ });
180
+ it("should validate empty title", async () => {
181
+ const result = await createFolder.call({
182
+ title: " ",
183
+ });
184
+ expect(result).toContain("Please provide a title");
185
+ expect(mockApiClient.post).not.toHaveBeenCalled();
186
+ });
187
+ it("should validate parent_id format", async () => {
188
+ const result = await createFolder.call({
189
+ title: "Test Folder",
190
+ parent_id: "short",
191
+ });
192
+ expect(result).toContain("does not appear to be a valid parent notebook ID");
193
+ expect(mockApiClient.post).not.toHaveBeenCalled();
194
+ });
195
+ it("should handle API errors", async () => {
196
+ mockApiClient.post.mockRejectedValue(new Error("API Error"));
197
+ const result = await createFolder.call({
198
+ title: "Test Folder",
199
+ });
200
+ expect(result).toContain("Error creating notebook");
201
+ });
202
+ it("should handle 404 errors for parent folder", async () => {
203
+ const error = new Error("Not found");
204
+ error.response = { status: 404 };
205
+ mockApiClient.post.mockRejectedValue(error);
206
+ const result = await createFolder.call({
207
+ title: "Test Folder",
208
+ parent_id: "a1b2c3d4e5f6789012345678901234567890abcd",
209
+ });
210
+ expect(result).toContain("Parent notebook with ID");
211
+ expect(result).toContain("not found");
212
+ });
213
+ it("should handle 409 conflict errors", async () => {
214
+ const error = new Error("Conflict");
215
+ error.response = { status: 409 };
216
+ mockApiClient.post.mockRejectedValue(error);
217
+ const result = await createFolder.call({
218
+ title: "Existing Folder",
219
+ });
220
+ expect(result).toContain('notebook with the title "Existing Folder" might already exist');
221
+ });
222
+ });
223
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,225 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import JoplinAPIClient from "../../lib/joplin-api-client.js";
3
+ import DeleteNote from "../../lib/tools/delete-note.js";
4
+ import DeleteFolder from "../../lib/tools/delete-folder.js";
5
+ // Mock JoplinAPIClient
6
+ vi.mock("../../lib/joplin-api-client.js", () => {
7
+ const mockClient = {
8
+ get: vi.fn(),
9
+ delete: vi.fn(),
10
+ };
11
+ return { default: vi.fn(() => mockClient) };
12
+ });
13
+ describe("Delete Tools", () => {
14
+ let mockApiClient;
15
+ let deleteNote;
16
+ let deleteFolder;
17
+ beforeEach(() => {
18
+ vi.clearAllMocks();
19
+ mockApiClient = new JoplinAPIClient({ token: "test-token" });
20
+ deleteNote = new DeleteNote(mockApiClient);
21
+ deleteFolder = new DeleteFolder(mockApiClient);
22
+ });
23
+ describe("DeleteNote", () => {
24
+ const mockNote = {
25
+ id: "note-123",
26
+ title: "Test Note",
27
+ body: "This is a test note content",
28
+ parent_id: "notebook-456",
29
+ is_todo: false,
30
+ todo_completed: false,
31
+ created_time: 1234567890000,
32
+ updated_time: 1234567891000,
33
+ };
34
+ it("should require confirmation before deleting", async () => {
35
+ const result = await deleteNote.call({
36
+ note_id: "a1b2c3d4e5f6789012345678901234567890abcd",
37
+ });
38
+ expect(result).toContain("This will permanently delete the note!");
39
+ expect(result).toContain('"confirm": true');
40
+ expect(mockApiClient.get).not.toHaveBeenCalled();
41
+ expect(mockApiClient.delete).not.toHaveBeenCalled();
42
+ });
43
+ it("should delete note with confirmation", async () => {
44
+ const mockNotebook = { title: "Test Notebook" };
45
+ mockApiClient.get.mockResolvedValueOnce(mockNote).mockResolvedValueOnce(mockNotebook);
46
+ mockApiClient.delete.mockResolvedValue({});
47
+ const result = await deleteNote.call({
48
+ note_id: "a1b2c3d4e5f6789012345678901234567890abcd",
49
+ confirm: true,
50
+ });
51
+ expect(mockApiClient.get).toHaveBeenCalledWith("/notes/a1b2c3d4e5f6789012345678901234567890abcd", {
52
+ query: { fields: "id,title,body,parent_id,is_todo,todo_completed,created_time,updated_time" },
53
+ });
54
+ expect(mockApiClient.delete).toHaveBeenCalledWith("/notes/a1b2c3d4e5f6789012345678901234567890abcd");
55
+ expect(result).toContain("🗑️ Successfully deleted note!");
56
+ expect(result).toContain("Test Note");
57
+ expect(result).toContain("Test Notebook");
58
+ });
59
+ it("should delete todo note with status", async () => {
60
+ const mockTodo = { ...mockNote, is_todo: true, todo_completed: true };
61
+ mockApiClient.get.mockResolvedValue(mockTodo);
62
+ mockApiClient.delete.mockResolvedValue({});
63
+ const result = await deleteNote.call({
64
+ note_id: "a1b2c3d4e5f6789012345678901234567890abcd",
65
+ confirm: true,
66
+ });
67
+ expect(result).toContain("Type: Todo (Completed)");
68
+ });
69
+ it("should handle note not found", async () => {
70
+ mockApiClient.get.mockResolvedValue(null);
71
+ const result = await deleteNote.call({
72
+ note_id: "a1b2c3d4e5f6789012345678901234567890abcd",
73
+ confirm: true,
74
+ });
75
+ expect(result).toContain("Note with ID");
76
+ expect(result).toContain("not found");
77
+ expect(mockApiClient.delete).not.toHaveBeenCalled();
78
+ });
79
+ it("should validate note_id format", async () => {
80
+ const result = await deleteNote.call({
81
+ note_id: "short",
82
+ confirm: true,
83
+ });
84
+ expect(result).toContain("does not appear to be a valid note ID");
85
+ expect(mockApiClient.get).not.toHaveBeenCalled();
86
+ });
87
+ it("should handle API errors", async () => {
88
+ mockApiClient.get.mockResolvedValue(mockNote);
89
+ mockApiClient.delete.mockRejectedValue(new Error("API Error"));
90
+ const result = await deleteNote.call({
91
+ note_id: "a1b2c3d4e5f6789012345678901234567890abcd",
92
+ confirm: true,
93
+ });
94
+ expect(result).toContain("Error deleting note");
95
+ });
96
+ it("should handle 404 errors on delete", async () => {
97
+ mockApiClient.get.mockResolvedValue(mockNote);
98
+ const error = new Error("Not found");
99
+ error.response = { status: 404 };
100
+ mockApiClient.delete.mockRejectedValue(error);
101
+ const result = await deleteNote.call({
102
+ note_id: "a1b2c3d4e5f6789012345678901234567890abcd",
103
+ confirm: true,
104
+ });
105
+ expect(result).toContain("Note with ID");
106
+ expect(result).toContain("not found");
107
+ });
108
+ });
109
+ describe("DeleteFolder", () => {
110
+ const mockFolder = {
111
+ id: "folder-123",
112
+ title: "Test Folder",
113
+ parent_id: "parent-456",
114
+ };
115
+ it("should require confirmation before deleting", async () => {
116
+ const result = await deleteFolder.call({
117
+ folder_id: "a1b2c3d4e5f6789012345678901234567890abcd",
118
+ });
119
+ expect(result).toContain("This will permanently delete the notebook/folder!");
120
+ expect(result).toContain('"confirm": true');
121
+ expect(mockApiClient.get).not.toHaveBeenCalled();
122
+ expect(mockApiClient.delete).not.toHaveBeenCalled();
123
+ });
124
+ it("should delete empty folder with confirmation", async () => {
125
+ const mockParent = { title: "Parent Folder" };
126
+ mockApiClient.get
127
+ .mockResolvedValueOnce(mockFolder)
128
+ .mockResolvedValueOnce({ items: [] }) // notes
129
+ .mockResolvedValueOnce({ items: [] }) // subfolders
130
+ .mockResolvedValueOnce(mockParent);
131
+ mockApiClient.delete.mockResolvedValue({});
132
+ const result = await deleteFolder.call({
133
+ folder_id: "a1b2c3d4e5f6789012345678901234567890abcd",
134
+ confirm: true,
135
+ });
136
+ expect(mockApiClient.delete).toHaveBeenCalledWith("/folders/a1b2c3d4e5f6789012345678901234567890abcd");
137
+ expect(result).toContain("🗑️ Successfully deleted notebook!");
138
+ expect(result).toContain("Test Folder");
139
+ expect(result).toContain("Parent Folder");
140
+ });
141
+ it("should warn about non-empty folder without force", async () => {
142
+ const mockNotes = { items: [{ id: "note1", title: "Note 1" }] };
143
+ const mockSubfolders = { items: [{ id: "sub1", title: "Subfolder 1" }] };
144
+ mockApiClient.get
145
+ .mockResolvedValueOnce(mockFolder)
146
+ .mockResolvedValueOnce(mockNotes)
147
+ .mockResolvedValueOnce({ items: mockSubfolders.items });
148
+ const result = await deleteFolder.call({
149
+ folder_id: "a1b2c3d4e5f6789012345678901234567890abcd",
150
+ confirm: true,
151
+ });
152
+ expect(result).toContain("Cannot delete non-empty notebook!");
153
+ expect(result).toContain("Contains: 1 notes and 0 subfolders");
154
+ expect(result).toContain("Force delete");
155
+ expect(mockApiClient.delete).not.toHaveBeenCalled();
156
+ });
157
+ it("should force delete non-empty folder when requested", async () => {
158
+ const mockNotes = { items: [{ id: "note1", title: "Note 1" }] };
159
+ const mockSubfolders = { items: [] };
160
+ mockApiClient.get
161
+ .mockResolvedValueOnce(mockFolder)
162
+ .mockResolvedValueOnce(mockNotes)
163
+ .mockResolvedValueOnce({ items: mockSubfolders.items });
164
+ mockApiClient.delete.mockResolvedValue({});
165
+ const result = await deleteFolder.call({
166
+ folder_id: "a1b2c3d4e5f6789012345678901234567890abcd",
167
+ confirm: true,
168
+ force: true,
169
+ });
170
+ expect(mockApiClient.delete).toHaveBeenCalledWith("/folders/a1b2c3d4e5f6789012345678901234567890abcd");
171
+ expect(result).toContain("🗑️ Successfully deleted notebook!");
172
+ expect(result).toContain("Deleted Content: 1 notes and 0 subfolders");
173
+ });
174
+ it("should handle folder not found", async () => {
175
+ mockApiClient.get.mockResolvedValue(null);
176
+ const result = await deleteFolder.call({
177
+ folder_id: "a1b2c3d4e5f6789012345678901234567890abcd",
178
+ confirm: true,
179
+ });
180
+ expect(result).toContain("Folder with ID");
181
+ expect(result).toContain("not found");
182
+ expect(mockApiClient.delete).not.toHaveBeenCalled();
183
+ });
184
+ it("should validate folder_id format", async () => {
185
+ const result = await deleteFolder.call({
186
+ folder_id: "short",
187
+ confirm: true,
188
+ });
189
+ expect(result).toContain("does not appear to be a valid folder ID");
190
+ expect(mockApiClient.get).not.toHaveBeenCalled();
191
+ });
192
+ it("should handle API errors", async () => {
193
+ mockApiClient.get
194
+ .mockResolvedValueOnce(mockFolder)
195
+ .mockResolvedValueOnce({ items: [] })
196
+ .mockResolvedValueOnce({ items: [] });
197
+ mockApiClient.delete.mockRejectedValue(new Error("API Error"));
198
+ const result = await deleteFolder.call({
199
+ folder_id: "a1b2c3d4e5f6789012345678901234567890abcd",
200
+ confirm: true,
201
+ });
202
+ expect(result).toContain("Error deleting folder");
203
+ });
204
+ it("should handle 409 conflict errors", async () => {
205
+ mockApiClient.get
206
+ .mockResolvedValueOnce(mockFolder)
207
+ .mockResolvedValueOnce({ items: [] })
208
+ .mockResolvedValueOnce({ items: [] });
209
+ const error = new Error("Conflict");
210
+ error.response = { status: 409 };
211
+ mockApiClient.delete.mockRejectedValue(error);
212
+ const result = await deleteFolder.call({
213
+ folder_id: "a1b2c3d4e5f6789012345678901234567890abcd",
214
+ confirm: true,
215
+ });
216
+ expect(result).toContain("Cannot delete folder");
217
+ expect(result).toContain("may contain items that prevent deletion");
218
+ });
219
+ it("should validate required parameters", async () => {
220
+ const result = await deleteFolder.call({});
221
+ expect(result).toContain("Please provide folder deletion options");
222
+ expect(mockApiClient.get).not.toHaveBeenCalled();
223
+ });
224
+ });
225
+ });
@@ -0,0 +1 @@
1
+ export {};