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.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +204 -0
- package/dist/lib/joplin-api-client.d.ts +23 -0
- package/dist/lib/joplin-api-client.js +110 -0
- package/dist/lib/logger.d.ts +21 -0
- package/dist/lib/logger.js +68 -0
- package/dist/lib/parse-args.d.ts +2 -0
- package/dist/lib/parse-args.js +81 -0
- package/dist/lib/tools/base-tool.d.ts +27 -0
- package/dist/lib/tools/base-tool.js +24 -0
- package/dist/lib/tools/create-folder.d.ts +9 -0
- package/dist/lib/tools/create-folder.js +79 -0
- package/dist/lib/tools/create-note.d.ts +13 -0
- package/dist/lib/tools/create-note.js +88 -0
- package/dist/lib/tools/delete-folder.d.ts +10 -0
- package/dist/lib/tools/delete-folder.js +138 -0
- package/dist/lib/tools/delete-note.d.ts +9 -0
- package/dist/lib/tools/delete-note.js +92 -0
- package/dist/lib/tools/edit-folder.d.ts +10 -0
- package/dist/lib/tools/edit-folder.js +136 -0
- package/dist/lib/tools/edit-note.d.ts +15 -0
- package/dist/lib/tools/edit-note.js +153 -0
- package/dist/lib/tools/index.d.ts +12 -0
- package/dist/lib/tools/index.js +12 -0
- package/dist/lib/tools/list-notebooks.d.ts +7 -0
- package/dist/lib/tools/list-notebooks.js +59 -0
- package/dist/lib/tools/read-multi-note.d.ts +5 -0
- package/dist/lib/tools/read-multi-note.js +108 -0
- package/dist/lib/tools/read-note.d.ts +5 -0
- package/dist/lib/tools/read-note.js +80 -0
- package/dist/lib/tools/read-notebook.d.ts +5 -0
- package/dist/lib/tools/read-notebook.js +66 -0
- package/dist/lib/tools/search-notes.d.ts +5 -0
- package/dist/lib/tools/search-notes.js +68 -0
- package/dist/tests/integration/joplin-integration.test.d.ts +1 -0
- package/dist/tests/integration/joplin-integration.test.js +117 -0
- package/dist/tests/manual/create-folder.test.d.ts +1 -0
- package/dist/tests/manual/create-folder.test.js +81 -0
- package/dist/tests/manual/create-note.test.d.ts +1 -0
- package/dist/tests/manual/create-note.test.js +84 -0
- package/dist/tests/manual/delete-folder.test.d.ts +1 -0
- package/dist/tests/manual/delete-folder.test.js +118 -0
- package/dist/tests/manual/delete-note.test.d.ts +1 -0
- package/dist/tests/manual/delete-note.test.js +101 -0
- package/dist/tests/manual/edit-folder.test.d.ts +1 -0
- package/dist/tests/manual/edit-folder.test.js +104 -0
- package/dist/tests/manual/edit-note.test.d.ts +1 -0
- package/dist/tests/manual/edit-note.test.js +118 -0
- package/dist/tests/manual/list-notebooks.test.d.ts +1 -0
- package/dist/tests/manual/list-notebooks.test.js +42 -0
- package/dist/tests/manual/read-note.test.d.ts +1 -0
- package/dist/tests/manual/read-note.test.js +54 -0
- package/dist/tests/manual/search-notes.test.d.ts +1 -0
- package/dist/tests/manual/search-notes.test.js +43 -0
- package/dist/tests/unit/create-tools.test.d.ts +1 -0
- package/dist/tests/unit/create-tools.test.js +223 -0
- package/dist/tests/unit/delete-tools.test.d.ts +1 -0
- package/dist/tests/unit/delete-tools.test.js +225 -0
- package/dist/tests/unit/edit-tools.test.d.ts +1 -0
- package/dist/tests/unit/edit-tools.test.js +261 -0
- package/dist/tests/unit/joplin-api-client.test.d.ts +1 -0
- package/dist/tests/unit/joplin-api-client.test.js +154 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +22 -0
- package/dist/vitest.setup.d.ts +1 -0
- package/dist/vitest.setup.js +24 -0
- package/package.json +58 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import JoplinAPIClient from "../../lib/joplin-api-client.js";
|
|
3
|
+
import { DeleteFolder, ListNotebooks, ReadNotebook } from "../../lib/tools/index.js";
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
// Check for required environment variables
|
|
7
|
+
const requiredEnvVars = ["JOPLIN_PORT", "JOPLIN_TOKEN"];
|
|
8
|
+
for (const envVar of requiredEnvVars) {
|
|
9
|
+
if (!process.env[envVar]) {
|
|
10
|
+
console.error(`Error: ${envVar} environment variable is required`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Create the Joplin API client
|
|
15
|
+
const apiClient = new JoplinAPIClient({
|
|
16
|
+
port: parseInt(process.env.JOPLIN_PORT),
|
|
17
|
+
token: process.env.JOPLIN_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
// Create the tools
|
|
20
|
+
const deleteFolder = new DeleteFolder(apiClient);
|
|
21
|
+
const listNotebooks = new ListNotebooks(apiClient);
|
|
22
|
+
const readNotebook = new ReadNotebook(apiClient);
|
|
23
|
+
// Test the delete folder functionality
|
|
24
|
+
async function testDeleteFolder() {
|
|
25
|
+
try {
|
|
26
|
+
// Check if Joplin is available
|
|
27
|
+
const available = await apiClient.serviceAvailable();
|
|
28
|
+
if (!available) {
|
|
29
|
+
console.error("Error: Joplin service is not available");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
// Parse command line arguments
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const folderId = args[0];
|
|
35
|
+
const confirmFlag = args[1];
|
|
36
|
+
const forceFlag = args[2];
|
|
37
|
+
if (!folderId) {
|
|
38
|
+
console.log("No folder ID provided. Listing available notebooks:");
|
|
39
|
+
const notebooks = await listNotebooks.call();
|
|
40
|
+
console.log(notebooks);
|
|
41
|
+
console.log("\n⚠️ DANGER ZONE - DELETE OPERATIONS");
|
|
42
|
+
console.log("Please run again with a folder ID from the list above.");
|
|
43
|
+
console.log("Usage: tsx tests/manual/delete-folder.test.ts <folder_id> [confirm] [force]");
|
|
44
|
+
console.log("\nExamples:");
|
|
45
|
+
console.log(" tsx tests/manual/delete-folder.test.ts abc123 # Show deletion preview");
|
|
46
|
+
console.log(" tsx tests/manual/delete-folder.test.ts abc123 confirm # Delete if empty");
|
|
47
|
+
console.log(" tsx tests/manual/delete-folder.test.ts abc123 confirm force # Force delete with contents");
|
|
48
|
+
console.log("\n⚠️ DELETION IS PERMANENT AND CANNOT BE UNDONE!");
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
console.log("📁 Notebook to be deleted:");
|
|
52
|
+
const currentNotebook = await readNotebook.call(folderId);
|
|
53
|
+
console.log(currentNotebook);
|
|
54
|
+
console.log("\n" + "=".repeat(80) + "\n");
|
|
55
|
+
const deleteOptions = { folder_id: folderId };
|
|
56
|
+
// Check if user wants to actually confirm deletion
|
|
57
|
+
if (confirmFlag === "confirm") {
|
|
58
|
+
deleteOptions.confirm = true;
|
|
59
|
+
if (forceFlag === "force") {
|
|
60
|
+
deleteOptions.force = true;
|
|
61
|
+
console.log("🗑️ PROCEEDING WITH FORCE DELETION (INCLUDING ALL CONTENTS)...");
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log("🗑️ PROCEEDING WITH DELETION...");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log("🔍 DELETION PREVIEW (not actually deleting):");
|
|
69
|
+
}
|
|
70
|
+
console.log("");
|
|
71
|
+
const result = await deleteFolder.call(deleteOptions);
|
|
72
|
+
console.log(result);
|
|
73
|
+
if (!confirmFlag || confirmFlag !== "confirm") {
|
|
74
|
+
console.log("\n💡 To actually delete this notebook:");
|
|
75
|
+
console.log(` Empty folder: tsx tests/manual/delete-folder.test.ts ${folderId} confirm`);
|
|
76
|
+
console.log(` With contents: tsx tests/manual/delete-folder.test.ts ${folderId} confirm force`);
|
|
77
|
+
console.log("\n⚠️ WARNING: This will permanently delete the notebook and all its contents!");
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log("\n📋 Updated notebook hierarchy:");
|
|
81
|
+
const updatedNotebooks = await listNotebooks.call();
|
|
82
|
+
console.log(updatedNotebooks);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error("Error testing delete folder:", error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Show usage if help requested
|
|
90
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
91
|
+
console.log(`
|
|
92
|
+
⚠️ DANGER: DELETE FOLDER TOOL
|
|
93
|
+
|
|
94
|
+
Usage: tsx tests/manual/delete-folder.test.ts <folder_id> [confirm] [force]
|
|
95
|
+
|
|
96
|
+
Arguments:
|
|
97
|
+
folder_id - ID of the folder/notebook to delete (required)
|
|
98
|
+
confirm - Actually perform the deletion (optional)
|
|
99
|
+
force - Force delete even if folder contains items (optional)
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
tsx tests/manual/delete-folder.test.ts abc123 # Preview deletion
|
|
103
|
+
tsx tests/manual/delete-folder.test.ts abc123 confirm # Delete if empty
|
|
104
|
+
tsx tests/manual/delete-folder.test.ts abc123 confirm force # Force delete with all contents
|
|
105
|
+
|
|
106
|
+
⚠️ WARNING:
|
|
107
|
+
- Deletion is permanent and cannot be undone!
|
|
108
|
+
- Force deletion will destroy ALL notes and subfolders inside!
|
|
109
|
+
|
|
110
|
+
First list notebooks to get their IDs:
|
|
111
|
+
npm run test:manual:list-notebooks
|
|
112
|
+
`);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
// Run if called directly
|
|
116
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
117
|
+
testDeleteFolder();
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import JoplinAPIClient from "../../lib/joplin-api-client.js";
|
|
3
|
+
import { DeleteNote, SearchNotes, ReadNote } from "../../lib/tools/index.js";
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
// Check for required environment variables
|
|
7
|
+
const requiredEnvVars = ["JOPLIN_PORT", "JOPLIN_TOKEN"];
|
|
8
|
+
for (const envVar of requiredEnvVars) {
|
|
9
|
+
if (!process.env[envVar]) {
|
|
10
|
+
console.error(`Error: ${envVar} environment variable is required`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Create the Joplin API client
|
|
15
|
+
const apiClient = new JoplinAPIClient({
|
|
16
|
+
port: parseInt(process.env.JOPLIN_PORT),
|
|
17
|
+
token: process.env.JOPLIN_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
// Create the tools
|
|
20
|
+
const deleteNote = new DeleteNote(apiClient);
|
|
21
|
+
const searchNotes = new SearchNotes(apiClient);
|
|
22
|
+
const readNote = new ReadNote(apiClient);
|
|
23
|
+
// Test the delete note functionality
|
|
24
|
+
async function testDeleteNote() {
|
|
25
|
+
try {
|
|
26
|
+
// Check if Joplin is available
|
|
27
|
+
const available = await apiClient.serviceAvailable();
|
|
28
|
+
if (!available) {
|
|
29
|
+
console.error("Error: Joplin service is not available");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
// Parse command line arguments
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const noteId = args[0];
|
|
35
|
+
const confirmFlag = args[1];
|
|
36
|
+
if (!noteId) {
|
|
37
|
+
console.log("No note ID provided. Searching for notes to delete:");
|
|
38
|
+
const searchQuery = args[1] || "test";
|
|
39
|
+
const searchResults = await searchNotes.call(searchQuery);
|
|
40
|
+
console.log(searchResults);
|
|
41
|
+
console.log("\n⚠️ DANGER ZONE - DELETE OPERATIONS");
|
|
42
|
+
console.log("Please run again with a note ID from the search results above.");
|
|
43
|
+
console.log("Usage: tsx tests/manual/delete-note.test.ts <note_id> [confirm]");
|
|
44
|
+
console.log("\nExamples:");
|
|
45
|
+
console.log(" tsx tests/manual/delete-note.test.ts abc123 # Show deletion preview");
|
|
46
|
+
console.log(" tsx tests/manual/delete-note.test.ts abc123 confirm # Actually delete");
|
|
47
|
+
console.log("\n⚠️ DELETION IS PERMANENT AND CANNOT BE UNDONE!");
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
console.log("📝 Note to be deleted:");
|
|
51
|
+
const currentNote = await readNote.call(noteId);
|
|
52
|
+
console.log(currentNote);
|
|
53
|
+
console.log("\n" + "=".repeat(80) + "\n");
|
|
54
|
+
const deleteOptions = { note_id: noteId };
|
|
55
|
+
// Check if user wants to actually confirm deletion
|
|
56
|
+
if (confirmFlag === "confirm") {
|
|
57
|
+
deleteOptions.confirm = true;
|
|
58
|
+
console.log("🗑️ PROCEEDING WITH DELETION...");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log("🔍 DELETION PREVIEW (not actually deleting):");
|
|
62
|
+
}
|
|
63
|
+
console.log("");
|
|
64
|
+
const result = await deleteNote.call(deleteOptions);
|
|
65
|
+
console.log(result);
|
|
66
|
+
if (!confirmFlag || confirmFlag !== "confirm") {
|
|
67
|
+
console.log("\n💡 To actually delete this note, run:");
|
|
68
|
+
console.log(`tsx tests/manual/delete-note.test.ts ${noteId} confirm`);
|
|
69
|
+
console.log("\n⚠️ WARNING: This will permanently delete the note!");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error("Error testing delete note:", error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Show usage if help requested
|
|
77
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
78
|
+
console.log(`
|
|
79
|
+
⚠️ DANGER: DELETE NOTE TOOL
|
|
80
|
+
|
|
81
|
+
Usage: tsx tests/manual/delete-note.test.ts <note_id> [confirm]
|
|
82
|
+
|
|
83
|
+
Arguments:
|
|
84
|
+
note_id - ID of the note to delete (required)
|
|
85
|
+
confirm - Actually perform the deletion (optional)
|
|
86
|
+
|
|
87
|
+
Examples:
|
|
88
|
+
tsx tests/manual/delete-note.test.ts abc123 # Preview deletion
|
|
89
|
+
tsx tests/manual/delete-note.test.ts abc123 confirm # Actually delete
|
|
90
|
+
|
|
91
|
+
⚠️ WARNING: Deletion is permanent and cannot be undone!
|
|
92
|
+
|
|
93
|
+
First search for notes to get their IDs:
|
|
94
|
+
tsx tests/manual/search-notes.test.ts "search term"
|
|
95
|
+
`);
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
// Run if called directly
|
|
99
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
100
|
+
testDeleteNote();
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import JoplinAPIClient from "../../lib/joplin-api-client.js";
|
|
3
|
+
import { EditFolder, ListNotebooks, ReadNotebook } from "../../lib/tools/index.js";
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
// Check for required environment variables
|
|
7
|
+
const requiredEnvVars = ["JOPLIN_PORT", "JOPLIN_TOKEN"];
|
|
8
|
+
for (const envVar of requiredEnvVars) {
|
|
9
|
+
if (!process.env[envVar]) {
|
|
10
|
+
console.error(`Error: ${envVar} environment variable is required`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Create the Joplin API client
|
|
15
|
+
const apiClient = new JoplinAPIClient({
|
|
16
|
+
port: parseInt(process.env.JOPLIN_PORT),
|
|
17
|
+
token: process.env.JOPLIN_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
// Create the tools
|
|
20
|
+
const editFolder = new EditFolder(apiClient);
|
|
21
|
+
const listNotebooks = new ListNotebooks(apiClient);
|
|
22
|
+
const readNotebook = new ReadNotebook(apiClient);
|
|
23
|
+
// Test the edit folder functionality
|
|
24
|
+
async function testEditFolder() {
|
|
25
|
+
try {
|
|
26
|
+
// Check if Joplin is available
|
|
27
|
+
const available = await apiClient.serviceAvailable();
|
|
28
|
+
if (!available) {
|
|
29
|
+
console.error("Error: Joplin service is not available");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
// Parse command line arguments
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const folderId = args[0];
|
|
35
|
+
if (!folderId) {
|
|
36
|
+
console.log("No folder ID provided. Listing available notebooks:");
|
|
37
|
+
const notebooks = await listNotebooks.call();
|
|
38
|
+
console.log(notebooks);
|
|
39
|
+
console.log("\nPlease run again with a folder ID from the list above.");
|
|
40
|
+
console.log("Usage: tsx tests/manual/edit-folder.test.ts <folder_id> [field=value] [field=value]...");
|
|
41
|
+
console.log("\nExamples:");
|
|
42
|
+
console.log(' tsx tests/manual/edit-folder.test.ts abc123 title="New Name"');
|
|
43
|
+
console.log(' tsx tests/manual/edit-folder.test.ts abc123 title="Subfolder" parent_id="def456"');
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
// Parse edit parameters from remaining arguments
|
|
47
|
+
const editOptions = { folder_id: folderId };
|
|
48
|
+
for (let i = 1; i < args.length; i++) {
|
|
49
|
+
const arg = args[i];
|
|
50
|
+
const [key, value] = arg.split("=", 2);
|
|
51
|
+
if (!value) {
|
|
52
|
+
console.error(`Invalid argument: ${arg}. Use format key=value`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Remove quotes if present
|
|
56
|
+
editOptions[key] = value.replace(/^["']|["']$/g, "");
|
|
57
|
+
}
|
|
58
|
+
console.log("📁 Current notebook:");
|
|
59
|
+
const currentNotebook = await readNotebook.call(folderId);
|
|
60
|
+
console.log(currentNotebook);
|
|
61
|
+
console.log("\n" + "=".repeat(80) + "\n");
|
|
62
|
+
console.log("🔄 Editing notebook with:");
|
|
63
|
+
Object.entries(editOptions).forEach(([key, value]) => {
|
|
64
|
+
if (key !== "folder_id") {
|
|
65
|
+
console.log(` ${key}: ${JSON.stringify(value)}`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
console.log("");
|
|
69
|
+
const result = await editFolder.call(editOptions);
|
|
70
|
+
console.log(result);
|
|
71
|
+
console.log("\n📋 Updated notebook hierarchy:");
|
|
72
|
+
const updatedNotebooks = await listNotebooks.call();
|
|
73
|
+
console.log(updatedNotebooks);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error("Error testing edit folder:", error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Show usage if help requested
|
|
80
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
81
|
+
console.log(`
|
|
82
|
+
Usage: tsx tests/manual/edit-folder.test.ts <folder_id> [field=value] [field=value]...
|
|
83
|
+
|
|
84
|
+
Arguments:
|
|
85
|
+
folder_id - ID of the folder/notebook to edit (required)
|
|
86
|
+
|
|
87
|
+
Available fields:
|
|
88
|
+
title="New Name" - Update folder title
|
|
89
|
+
parent_id="parent_id" - Move to different parent folder
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
tsx tests/manual/edit-folder.test.ts abc123 title="New Name"
|
|
93
|
+
tsx tests/manual/edit-folder.test.ts abc123 title="Subfolder" parent_id="def456"
|
|
94
|
+
tsx tests/manual/edit-folder.test.ts abc123 parent_id="" # Move to top level
|
|
95
|
+
|
|
96
|
+
First list notebooks to get their IDs:
|
|
97
|
+
npm run test:manual:list-notebooks
|
|
98
|
+
`);
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
// Run if called directly
|
|
102
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
103
|
+
testEditFolder();
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import JoplinAPIClient from "../../lib/joplin-api-client.js";
|
|
3
|
+
import { EditNote, SearchNotes, ReadNote } from "../../lib/tools/index.js";
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
// Check for required environment variables
|
|
7
|
+
const requiredEnvVars = ["JOPLIN_PORT", "JOPLIN_TOKEN"];
|
|
8
|
+
for (const envVar of requiredEnvVars) {
|
|
9
|
+
if (!process.env[envVar]) {
|
|
10
|
+
console.error(`Error: ${envVar} environment variable is required`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Create the Joplin API client
|
|
15
|
+
const apiClient = new JoplinAPIClient({
|
|
16
|
+
port: parseInt(process.env.JOPLIN_PORT),
|
|
17
|
+
token: process.env.JOPLIN_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
// Create the tools
|
|
20
|
+
const editNote = new EditNote(apiClient);
|
|
21
|
+
const searchNotes = new SearchNotes(apiClient);
|
|
22
|
+
const readNote = new ReadNote(apiClient);
|
|
23
|
+
// Test the edit note functionality
|
|
24
|
+
async function testEditNote() {
|
|
25
|
+
try {
|
|
26
|
+
// Check if Joplin is available
|
|
27
|
+
const available = await apiClient.serviceAvailable();
|
|
28
|
+
if (!available) {
|
|
29
|
+
console.error("Error: Joplin service is not available");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
// Parse command line arguments
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const noteId = args[0];
|
|
35
|
+
if (!noteId) {
|
|
36
|
+
console.log("No note ID provided. Searching for notes to edit:");
|
|
37
|
+
const searchQuery = args[1] || "test";
|
|
38
|
+
const searchResults = await searchNotes.call(searchQuery);
|
|
39
|
+
console.log(searchResults);
|
|
40
|
+
console.log("\nPlease run again with a note ID from the search results above.");
|
|
41
|
+
console.log("Usage: tsx tests/manual/edit-note.test.ts <note_id> [field=value] [field=value]...");
|
|
42
|
+
console.log("\nExamples:");
|
|
43
|
+
console.log(' tsx tests/manual/edit-note.test.ts abc123 title="Updated Title"');
|
|
44
|
+
console.log(' tsx tests/manual/edit-note.test.ts abc123 body="New content" is_todo=true');
|
|
45
|
+
console.log(" tsx tests/manual/edit-note.test.ts abc123 todo_completed=true");
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
// Parse edit parameters from remaining arguments
|
|
49
|
+
const editOptions = { note_id: noteId };
|
|
50
|
+
for (let i = 1; i < args.length; i++) {
|
|
51
|
+
const arg = args[i];
|
|
52
|
+
const [key, value] = arg.split("=", 2);
|
|
53
|
+
if (!value) {
|
|
54
|
+
console.error(`Invalid argument: ${arg}. Use format key=value`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Parse different types of values
|
|
58
|
+
if (key === "is_todo" || key === "todo_completed") {
|
|
59
|
+
editOptions[key] = value.toLowerCase() === "true";
|
|
60
|
+
}
|
|
61
|
+
else if (key === "todo_due") {
|
|
62
|
+
editOptions[key] = parseInt(value);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Remove quotes if present
|
|
66
|
+
editOptions[key] = value.replace(/^["']|["']$/g, "");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
console.log("📝 Current note:");
|
|
70
|
+
const currentNote = await readNote.call(noteId);
|
|
71
|
+
console.log(currentNote);
|
|
72
|
+
console.log("\n" + "=".repeat(80) + "\n");
|
|
73
|
+
console.log("🔄 Editing note with:");
|
|
74
|
+
Object.entries(editOptions).forEach(([key, value]) => {
|
|
75
|
+
if (key !== "note_id") {
|
|
76
|
+
console.log(` ${key}: ${JSON.stringify(value)}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
console.log("");
|
|
80
|
+
const result = await editNote.call(editOptions);
|
|
81
|
+
console.log(result);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error("Error testing edit note:", error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Show usage if help requested
|
|
88
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
89
|
+
console.log(`
|
|
90
|
+
Usage: tsx tests/manual/edit-note.test.ts <note_id> [field=value] [field=value]...
|
|
91
|
+
|
|
92
|
+
Arguments:
|
|
93
|
+
note_id - ID of the note to edit (required)
|
|
94
|
+
|
|
95
|
+
Available fields:
|
|
96
|
+
title="New Title" - Update note title
|
|
97
|
+
body="New content" - Update note body (Markdown)
|
|
98
|
+
body_html="<p>HTML</p>" - Update note body (HTML)
|
|
99
|
+
parent_id="notebook_id" - Move to different notebook
|
|
100
|
+
is_todo=true/false - Convert to/from todo
|
|
101
|
+
todo_completed=true/false - Mark todo as completed/incomplete
|
|
102
|
+
todo_due=1234567890000 - Set due date (timestamp)
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
tsx tests/manual/edit-note.test.ts abc123 title="Updated Title"
|
|
106
|
+
tsx tests/manual/edit-note.test.ts abc123 body="New content" is_todo=true
|
|
107
|
+
tsx tests/manual/edit-note.test.ts abc123 todo_completed=true
|
|
108
|
+
tsx tests/manual/edit-note.test.ts abc123 parent_id="def456"
|
|
109
|
+
|
|
110
|
+
First search for notes to get their IDs:
|
|
111
|
+
tsx tests/manual/search-notes.test.ts "search term"
|
|
112
|
+
`);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
// Run if called directly
|
|
116
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
117
|
+
testEditNote();
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import JoplinAPIClient from "../../lib/joplin-api-client.js";
|
|
3
|
+
import { ListNotebooks } from "../../lib/tools/index.js";
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
// Check for required environment variables
|
|
7
|
+
const requiredEnvVars = ["JOPLIN_PORT", "JOPLIN_TOKEN"];
|
|
8
|
+
for (const envVar of requiredEnvVars) {
|
|
9
|
+
if (!process.env[envVar]) {
|
|
10
|
+
console.error(`Error: ${envVar} environment variable is required`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Create the Joplin API client
|
|
15
|
+
const apiClient = new JoplinAPIClient({
|
|
16
|
+
port: parseInt(process.env.JOPLIN_PORT),
|
|
17
|
+
token: process.env.JOPLIN_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
// Create the tool
|
|
20
|
+
const listNotebooks = new ListNotebooks(apiClient);
|
|
21
|
+
// Test the list notebooks functionality
|
|
22
|
+
async function testListNotebooks() {
|
|
23
|
+
try {
|
|
24
|
+
// Check if Joplin is available
|
|
25
|
+
const available = await apiClient.serviceAvailable();
|
|
26
|
+
if (!available) {
|
|
27
|
+
console.error("Error: Joplin service is not available");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
console.log("📋 Listing all notebooks:");
|
|
31
|
+
console.log("");
|
|
32
|
+
const result = await listNotebooks.call();
|
|
33
|
+
console.log(result);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error("Error testing list notebooks:", error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Run if called directly
|
|
40
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
41
|
+
testListNotebooks();
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import JoplinAPIClient from "../../lib/joplin-api-client.js";
|
|
3
|
+
import { SearchNotes, ReadNote } from "../../lib/tools/index.js";
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
// Check for required environment variables
|
|
7
|
+
const requiredEnvVars = ["JOPLIN_PORT", "JOPLIN_TOKEN"];
|
|
8
|
+
for (const envVar of requiredEnvVars) {
|
|
9
|
+
if (!process.env[envVar]) {
|
|
10
|
+
console.error(`Error: ${envVar} environment variable is required`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Create the Joplin API client
|
|
15
|
+
const apiClient = new JoplinAPIClient({
|
|
16
|
+
port: parseInt(process.env.JOPLIN_PORT),
|
|
17
|
+
token: process.env.JOPLIN_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
// Create the tools
|
|
20
|
+
const searchNotes = new SearchNotes(apiClient);
|
|
21
|
+
const readNote = new ReadNote(apiClient);
|
|
22
|
+
// Test the read note functionality
|
|
23
|
+
async function testReadNote() {
|
|
24
|
+
try {
|
|
25
|
+
// Check if Joplin is available
|
|
26
|
+
const available = await apiClient.serviceAvailable();
|
|
27
|
+
if (!available) {
|
|
28
|
+
console.error("Error: Joplin service is not available");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// If no note ID is provided, search for notes first
|
|
32
|
+
const noteId = process.argv[2];
|
|
33
|
+
if (!noteId) {
|
|
34
|
+
console.log("No note ID provided. Searching for a sample note:");
|
|
35
|
+
const searchQuery = process.argv[3] || "todo"; // Default search term if none provided
|
|
36
|
+
const searchResults = await searchNotes.call(searchQuery);
|
|
37
|
+
console.log(searchResults);
|
|
38
|
+
console.log("\nPlease run again with a note ID from the search results above.");
|
|
39
|
+
console.log("Example: tsx tests/manual/read-note.test.ts your-note-id");
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
// Read the specified note
|
|
43
|
+
console.log(`Reading note with ID: "${noteId}"`);
|
|
44
|
+
const result = await readNote.call(noteId);
|
|
45
|
+
console.log(result);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error("Error testing read note:", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Run if called directly
|
|
52
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
53
|
+
testReadNote();
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import JoplinAPIClient from "../../lib/joplin-api-client.js";
|
|
3
|
+
import { SearchNotes } from "../../lib/tools/index.js";
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
// Check for required environment variables
|
|
7
|
+
const requiredEnvVars = ["JOPLIN_PORT", "JOPLIN_TOKEN"];
|
|
8
|
+
for (const envVar of requiredEnvVars) {
|
|
9
|
+
if (!process.env[envVar]) {
|
|
10
|
+
console.error(`Error: ${envVar} environment variable is required`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Create the Joplin API client
|
|
15
|
+
const apiClient = new JoplinAPIClient({
|
|
16
|
+
port: parseInt(process.env.JOPLIN_PORT),
|
|
17
|
+
token: process.env.JOPLIN_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
// Create the search tool
|
|
20
|
+
const searchNotes = new SearchNotes(apiClient);
|
|
21
|
+
// Test the search functionality
|
|
22
|
+
async function testSearch() {
|
|
23
|
+
try {
|
|
24
|
+
// Check if Joplin is available
|
|
25
|
+
const available = await apiClient.serviceAvailable();
|
|
26
|
+
if (!available) {
|
|
27
|
+
console.error("Error: Joplin service is not available");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
// Perform a search
|
|
31
|
+
const query = process.argv[2] || "test";
|
|
32
|
+
console.log(`Searching for: "${query}"`);
|
|
33
|
+
const result = await searchNotes.call(query);
|
|
34
|
+
console.log(result);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error("Error testing search:", error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Run if called directly
|
|
41
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
42
|
+
testSearch();
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|