gwanli 0.1.0 → 0.3.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/dist/auth-server.d.ts +4 -0
- package/dist/auth-server.js +48 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +75 -0
- package/dist/cli.js +17 -12
- package/dist/index.d.ts +4 -0
- package/dist/index.js +39 -0
- package/dist/job.d.ts +2 -0
- package/dist/job.js +73 -0
- package/dist/ls.d.ts +2 -0
- package/dist/ls.js +26 -0
- package/dist/mcp.js +408 -19
- package/package.json +6 -3
@@ -0,0 +1,48 @@
|
|
1
|
+
import { Hono } from "hono";
|
2
|
+
import { cors } from "hono/cors";
|
3
|
+
import { serve } from "@hono/node-server";
|
4
|
+
export async function startAuthServer() {
|
5
|
+
return new Promise((resolve, reject) => {
|
6
|
+
let server = null;
|
7
|
+
// Create promise that resolves when token received
|
8
|
+
const tokenPromise = new Promise((resolveToken, rejectToken) => {
|
9
|
+
const app = new Hono();
|
10
|
+
app.use("/*", cors());
|
11
|
+
app.post("/callback", async (c) => {
|
12
|
+
try {
|
13
|
+
const { token } = await c.req.json();
|
14
|
+
// Close server after a short delay to ensure response is sent
|
15
|
+
setTimeout(() => {
|
16
|
+
server?.close();
|
17
|
+
}, 100);
|
18
|
+
resolveToken(token); // Resolve with token!
|
19
|
+
return c.json({ success: true });
|
20
|
+
}
|
21
|
+
catch (error) {
|
22
|
+
setTimeout(() => {
|
23
|
+
server?.close();
|
24
|
+
}, 100);
|
25
|
+
rejectToken(new Error("Invalid callback data"));
|
26
|
+
return c.json({ error: "Invalid JSON" }, 400);
|
27
|
+
}
|
28
|
+
});
|
29
|
+
app.notFound((c) => c.text("Not Found", 404));
|
30
|
+
server = serve({
|
31
|
+
fetch: app.fetch,
|
32
|
+
port: 0,
|
33
|
+
hostname: "127.0.0.1"
|
34
|
+
}, (info) => {
|
35
|
+
const url = `http://127.0.0.1:${info.port}`;
|
36
|
+
// Set timeout to reject after 5 minutes
|
37
|
+
setTimeout(() => {
|
38
|
+
server?.close();
|
39
|
+
rejectToken(new Error("Authentication timeout after 5 minutes"));
|
40
|
+
}, 5 * 60 * 1000);
|
41
|
+
resolve({ url, tokenPromise });
|
42
|
+
});
|
43
|
+
server.on?.("error", (error) => {
|
44
|
+
rejectToken(error);
|
45
|
+
});
|
46
|
+
});
|
47
|
+
});
|
48
|
+
}
|
package/dist/auth.d.ts
ADDED
package/dist/auth.js
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
import { Command } from "commander";
|
2
|
+
import open from "open";
|
3
|
+
import { createInterface } from "readline";
|
4
|
+
import { checkWorkspace, addWorkspace, cliLogger, OAUTH_BASE_URL } from "gwanli-core";
|
5
|
+
async function promptUser(question) {
|
6
|
+
const rl = createInterface({
|
7
|
+
input: process.stdin,
|
8
|
+
output: process.stdout,
|
9
|
+
});
|
10
|
+
return new Promise((resolve) => {
|
11
|
+
rl.question(question, (answer) => {
|
12
|
+
rl.close();
|
13
|
+
resolve(answer.trim());
|
14
|
+
});
|
15
|
+
});
|
16
|
+
}
|
17
|
+
async function runAuthFlow(workspace) {
|
18
|
+
cliLogger.console(`\nSetting up workspace: ${workspace}`);
|
19
|
+
// Build OAuth URL and open browser
|
20
|
+
const authUrl = `${OAUTH_BASE_URL}`;
|
21
|
+
cliLogger.console("\nOpening browser for authentication...");
|
22
|
+
await open(authUrl);
|
23
|
+
cliLogger.console("Please complete authentication in your browser and copy the token.");
|
24
|
+
// Ask user to paste token
|
25
|
+
const token = await promptUser("Paste your Notion API token here: ");
|
26
|
+
// Success
|
27
|
+
cliLogger.console("\nAuthentication successful!");
|
28
|
+
cliLogger.console(`Token: ${token.substring(0, 10)}...`);
|
29
|
+
// Check if workspace already exists
|
30
|
+
const workspaceExists = checkWorkspace(workspace);
|
31
|
+
if (workspaceExists) {
|
32
|
+
cliLogger.console(`\nWorkspace '${workspace}' already exists.`);
|
33
|
+
const shouldOverride = await promptUser("Do you want to override it? (y/n): ");
|
34
|
+
if (shouldOverride.toLowerCase() !== "y" && shouldOverride.toLowerCase() !== "yes") {
|
35
|
+
cliLogger.console("Operation cancelled. Workspace not updated.");
|
36
|
+
return;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
// Add workspace to config
|
40
|
+
try {
|
41
|
+
addWorkspace(workspace, token);
|
42
|
+
cliLogger.console(`\n✓ Workspace '${workspace}' has been ${workspaceExists ? "updated" : "created"} successfully!`);
|
43
|
+
cliLogger.console("You can now use this workspace with gwanli commands.");
|
44
|
+
// Force exit after a short delay to ensure all async operations complete
|
45
|
+
setTimeout(() => {
|
46
|
+
process.exit(0);
|
47
|
+
}, 500);
|
48
|
+
}
|
49
|
+
catch (error) {
|
50
|
+
cliLogger.error(`Failed to save workspace configuration: ${error}`);
|
51
|
+
throw error;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
function printHeader(title) {
|
55
|
+
cliLogger.console(`\n${title}`);
|
56
|
+
cliLogger.console("=".repeat(70));
|
57
|
+
}
|
58
|
+
function printFooter() {
|
59
|
+
cliLogger.console("=".repeat(70));
|
60
|
+
}
|
61
|
+
export const auth = new Command("auth")
|
62
|
+
.description("Generate a Notion API token and add workspace")
|
63
|
+
.option("--workspace <name>", "Workspace name to add after authentication (default: 'default')")
|
64
|
+
.action(async (options) => {
|
65
|
+
const { workspace = "default" } = options;
|
66
|
+
printHeader("Notion API Token Generator");
|
67
|
+
try {
|
68
|
+
await runAuthFlow(workspace);
|
69
|
+
}
|
70
|
+
catch (error) {
|
71
|
+
cliLogger.error(`\nAuthentication failed: ${error}`);
|
72
|
+
process.exit(1);
|
73
|
+
}
|
74
|
+
printFooter();
|
75
|
+
});
|
package/dist/cli.js
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
|
-
import { Command } from
|
3
|
-
import {
|
4
|
-
|
2
|
+
import { Command } from "commander";
|
3
|
+
import { auth } from "./auth.js";
|
4
|
+
import { index } from "./index.js";
|
5
|
+
import { ls } from "./ls.js";
|
6
|
+
import { job } from "./job.js";
|
7
|
+
const program = new Command("gwanli");
|
5
8
|
program
|
6
|
-
.name(
|
7
|
-
.description(
|
8
|
-
.version(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
.name("gwanli")
|
10
|
+
.description("Gwanli - Notion management CLI")
|
11
|
+
.version("0.2.0");
|
12
|
+
// Add auth command
|
13
|
+
program.addCommand(auth);
|
14
|
+
// Add index command
|
15
|
+
program.addCommand(index);
|
16
|
+
// Add ls command
|
17
|
+
program.addCommand(ls);
|
18
|
+
// Add job command
|
19
|
+
program.addCommand(job);
|
15
20
|
program.parse();
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
import { Command } from "commander";
|
3
|
+
import { loadConfig, checkWorkspace, indexNotionPages, JobTracker } from "gwanli-core";
|
4
|
+
const program = new Command("index");
|
5
|
+
program
|
6
|
+
.name("index")
|
7
|
+
.description("Index a Notion workspace")
|
8
|
+
.option("-w, --workspace <workspace>", "Workspace name to index", "default")
|
9
|
+
.action(async (options) => {
|
10
|
+
try {
|
11
|
+
const workspaceName = options.workspace;
|
12
|
+
// Check if workspace exists
|
13
|
+
if (!checkWorkspace(workspaceName)) {
|
14
|
+
console.error(`Workspace "${workspaceName}" not found. Use 'gwanli auth' to list available workspaces.`);
|
15
|
+
process.exit(1);
|
16
|
+
}
|
17
|
+
console.log(`Starting indexing for workspace: ${workspaceName}`);
|
18
|
+
const config = loadConfig();
|
19
|
+
const workspace = config.workspace[workspaceName];
|
20
|
+
if (!workspace) {
|
21
|
+
console.error(`Workspace configuration not found for: ${workspaceName}`);
|
22
|
+
process.exit(1);
|
23
|
+
}
|
24
|
+
// Create job tracker
|
25
|
+
const jobId = `cli-${Date.now()}`;
|
26
|
+
const jobTracker = new JobTracker(jobId);
|
27
|
+
console.log(`Job ID: ${jobId}`);
|
28
|
+
console.log(`Job directory: ${jobTracker.getJobDir()}`);
|
29
|
+
// Start indexing
|
30
|
+
await indexNotionPages(workspace.api_key, workspace.db_path, jobTracker);
|
31
|
+
console.log(`Indexing completed for workspace: ${workspaceName}`);
|
32
|
+
jobTracker.updateStatus("END");
|
33
|
+
}
|
34
|
+
catch (error) {
|
35
|
+
console.error(`Indexing failed:`, error);
|
36
|
+
process.exit(1);
|
37
|
+
}
|
38
|
+
});
|
39
|
+
export { program as index };
|
package/dist/job.d.ts
ADDED
package/dist/job.js
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
import { Command } from "commander";
|
2
|
+
import { cliLogger, getRecentJobs, getJobById } from "gwanli-core";
|
3
|
+
function formatJobsList(jobs) {
|
4
|
+
if (jobs.length === 0) {
|
5
|
+
cliLogger.console("No jobs found.");
|
6
|
+
return;
|
7
|
+
}
|
8
|
+
cliLogger.console(`\nFound ${jobs.length} job(s):\n`);
|
9
|
+
jobs.forEach((job, index) => {
|
10
|
+
const statusText = job.state?.status ? ` (${job.state.status})` : "";
|
11
|
+
const timeStr = new Date(job.timestamp).toLocaleString();
|
12
|
+
cliLogger.console(`${index + 1}. ${job.jobId}${statusText}`);
|
13
|
+
cliLogger.console(` ${job.prefix.toUpperCase()} job - ${timeStr}`);
|
14
|
+
if (index < jobs.length - 1) {
|
15
|
+
cliLogger.console("");
|
16
|
+
}
|
17
|
+
});
|
18
|
+
}
|
19
|
+
function formatJobDetails(job) {
|
20
|
+
cliLogger.console(`\nJob Details:`);
|
21
|
+
cliLogger.console("=".repeat(50));
|
22
|
+
cliLogger.console(`Job ID: ${job.jobId}`);
|
23
|
+
cliLogger.console(`Type: ${job.prefix.toUpperCase()}`);
|
24
|
+
cliLogger.console(`Timestamp: ${job.timestamp}`);
|
25
|
+
cliLogger.console(`Created: ${new Date(job.timestamp).toLocaleString()}`);
|
26
|
+
if (job.state) {
|
27
|
+
cliLogger.console(`\nStatus Information:`);
|
28
|
+
cliLogger.console(JSON.stringify(job.state, null, 2));
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
cliLogger.console(`\nNo status file found`);
|
32
|
+
}
|
33
|
+
cliLogger.console("=".repeat(50));
|
34
|
+
}
|
35
|
+
export const job = new Command("job")
|
36
|
+
.description("Manage and check job status")
|
37
|
+
.option("-i, --id <jobId>", "Check specific job by ID")
|
38
|
+
.option("-n, --count <number>", "Number of recent jobs to show", "5")
|
39
|
+
.option("-p, --prefix <type>", "Filter by job prefix (mcp|cli)", "mcp")
|
40
|
+
.action(async (options) => {
|
41
|
+
try {
|
42
|
+
const { id, count, prefix } = options;
|
43
|
+
// Validate prefix
|
44
|
+
if (prefix && !['mcp', 'cli'].includes(prefix)) {
|
45
|
+
cliLogger.error("Invalid prefix. Use 'mcp' or 'cli'");
|
46
|
+
process.exit(1);
|
47
|
+
}
|
48
|
+
if (id) {
|
49
|
+
// Get specific job by ID
|
50
|
+
cliLogger.console(`Checking job: ${id}`);
|
51
|
+
const job = getJobById(id);
|
52
|
+
if (!job) {
|
53
|
+
cliLogger.error(`Job '${id}' not found.`);
|
54
|
+
process.exit(1);
|
55
|
+
}
|
56
|
+
formatJobDetails(job);
|
57
|
+
}
|
58
|
+
else {
|
59
|
+
// Get recent jobs
|
60
|
+
const jobCount = parseInt(count) || 5;
|
61
|
+
cliLogger.console(`Fetching ${jobCount} recent ${prefix.toUpperCase()} jobs...`);
|
62
|
+
const jobs = getRecentJobs(jobCount, prefix);
|
63
|
+
formatJobsList(jobs);
|
64
|
+
if (jobs.length > 0) {
|
65
|
+
cliLogger.console(`\nUse 'gwanli job --id <jobId>' to see detailed status.`);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
catch (error) {
|
70
|
+
cliLogger.error(`Error checking jobs: ${error instanceof Error ? error.message : String(error)}`);
|
71
|
+
process.exit(1);
|
72
|
+
}
|
73
|
+
});
|
package/dist/ls.d.ts
ADDED
package/dist/ls.js
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
import { Command } from "commander";
|
2
|
+
import { listFiles, loadConfig, cliLogger } from "gwanli-core";
|
3
|
+
export const ls = new Command("ls")
|
4
|
+
.description("List files from a workspace or database path")
|
5
|
+
.argument("[workspace]", "Workspace name or database path (defaults to 'default' workspace)")
|
6
|
+
.option("--prefix <prefix>", "Path prefix to filter results (default: '/')", "/")
|
7
|
+
.option("--depth <depth>", "Maximum depth to display (default: 2)", "2")
|
8
|
+
.action(async (workspace, options) => {
|
9
|
+
const { prefix, depth } = options;
|
10
|
+
const maxDepth = parseInt(depth, 10);
|
11
|
+
const config = loadConfig();
|
12
|
+
if (!config.default_search) {
|
13
|
+
return cliLogger.error("No default search configured");
|
14
|
+
}
|
15
|
+
let searchWorkspace = workspace ?? config.default_search;
|
16
|
+
cliLogger.console(`\nListing files from: ${searchWorkspace}`);
|
17
|
+
cliLogger.console(`Prefix: ${prefix}, Max Depth: ${maxDepth}\n`);
|
18
|
+
try {
|
19
|
+
const result = listFiles(searchWorkspace, prefix, maxDepth);
|
20
|
+
cliLogger.console(result);
|
21
|
+
}
|
22
|
+
catch (error) {
|
23
|
+
cliLogger.error(`Error listing files: ${error}`);
|
24
|
+
process.exit(1);
|
25
|
+
}
|
26
|
+
});
|
package/dist/mcp.js
CHANGED
@@ -1,28 +1,417 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
|
-
import { McpServer
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
4
4
|
import { z } from "zod";
|
5
|
+
import { loadConfig, addWorkspace, updateWorkspace, deleteWorkspace, OAUTH_BASE_URL, checkWorkspace, indexNotionPages, JobTracker, listFiles, getRecentJobs, getJobById, } from "gwanli-core";
|
5
6
|
// Create an MCP server
|
6
7
|
const server = new McpServer({
|
7
|
-
name: "
|
8
|
-
version: "1.0.0"
|
8
|
+
name: "gwanli-mcp",
|
9
|
+
version: "1.0.0",
|
10
|
+
});
|
11
|
+
// Register auth tool
|
12
|
+
server.registerTool("auth", {
|
13
|
+
description: "Check authentication status and get OAuth URLs for workspace setup",
|
14
|
+
inputSchema: {},
|
15
|
+
}, async () => {
|
16
|
+
try {
|
17
|
+
// Load current config to check existing workspaces
|
18
|
+
const config = loadConfig();
|
19
|
+
const existingWorkspaces = Object.keys(config.workspace);
|
20
|
+
const authUrl = `${OAUTH_BASE_URL}/`;
|
21
|
+
const message = existingWorkspaces.length > 0
|
22
|
+
? `**Authenticated Workspaces:**\n${existingWorkspaces
|
23
|
+
.map((workspace) => `- ${workspace}${workspace === "default" ? " (default)" : ""}`)
|
24
|
+
.join("\n")}\n\n**Get a new token for any workspace:**\n${authUrl}\n\nVisit the URL above, complete OAuth, and you'll receive a token to add to your workspace configuration.`
|
25
|
+
: `**No authenticated workspaces found.**\n\nGenerate a token by visiting the URL above: ${authUrl}`;
|
26
|
+
return {
|
27
|
+
content: [
|
28
|
+
{
|
29
|
+
type: "text",
|
30
|
+
text: message,
|
31
|
+
},
|
32
|
+
],
|
33
|
+
};
|
34
|
+
}
|
35
|
+
catch (error) {
|
36
|
+
return {
|
37
|
+
content: [
|
38
|
+
{
|
39
|
+
type: "text",
|
40
|
+
text: `Error checking authentication: ${error instanceof Error ? error.message : String(error)}`,
|
41
|
+
},
|
42
|
+
],
|
43
|
+
isError: true,
|
44
|
+
};
|
45
|
+
}
|
46
|
+
});
|
47
|
+
// Register workspace management tool
|
48
|
+
server.registerTool("workspace", {
|
49
|
+
description: "Manage workspace configurations - add, delete, or update workspaces",
|
50
|
+
inputSchema: {
|
51
|
+
type: z
|
52
|
+
.enum(["ADD", "DELETE", "UPDATE", "LIST"])
|
53
|
+
.describe("Type of workspace operation: ADD to create new workspace, DELETE to remove existing workspace, UPDATE to modify workspace details, LIST to show all workspaces"),
|
54
|
+
name: z
|
55
|
+
.string()
|
56
|
+
.optional()
|
57
|
+
.default("default")
|
58
|
+
.describe("Workspace name - defaults to 'default' if not provided. Used to identify which workspace to operate on"),
|
59
|
+
api_key: z
|
60
|
+
.string()
|
61
|
+
.optional()
|
62
|
+
.describe("API key/token for workspace authentication - required for ADD operations, optional for UPDATE to change the key"),
|
63
|
+
description: z
|
64
|
+
.string()
|
65
|
+
.optional()
|
66
|
+
.describe("Human-readable description of the workspace - required for UPDATE operations to set workspace description"),
|
67
|
+
},
|
68
|
+
}, async (args) => {
|
69
|
+
try {
|
70
|
+
switch (args.type) {
|
71
|
+
case "ADD":
|
72
|
+
if (!args.api_key) {
|
73
|
+
return {
|
74
|
+
content: [
|
75
|
+
{
|
76
|
+
type: "text",
|
77
|
+
text: "API key is required for adding workspace.",
|
78
|
+
},
|
79
|
+
],
|
80
|
+
isError: true,
|
81
|
+
};
|
82
|
+
}
|
83
|
+
const addName = args.name || "default";
|
84
|
+
addWorkspace(addName, args.api_key, {
|
85
|
+
description: `Workspace: ${addName}`,
|
86
|
+
});
|
87
|
+
return {
|
88
|
+
content: [
|
89
|
+
{
|
90
|
+
type: "text",
|
91
|
+
text: `Successfully added workspace "${addName}" with API key.`,
|
92
|
+
},
|
93
|
+
],
|
94
|
+
};
|
95
|
+
case "DELETE":
|
96
|
+
const deleteName = args.name || "default";
|
97
|
+
try {
|
98
|
+
deleteWorkspace(deleteName);
|
99
|
+
return {
|
100
|
+
content: [
|
101
|
+
{
|
102
|
+
type: "text",
|
103
|
+
text: `Successfully deleted workspace "${deleteName}".`,
|
104
|
+
},
|
105
|
+
],
|
106
|
+
};
|
107
|
+
}
|
108
|
+
catch (error) {
|
109
|
+
return {
|
110
|
+
content: [
|
111
|
+
{
|
112
|
+
type: "text",
|
113
|
+
text: `Workspace "${deleteName}" not found.`,
|
114
|
+
},
|
115
|
+
],
|
116
|
+
isError: true,
|
117
|
+
};
|
118
|
+
}
|
119
|
+
case "UPDATE":
|
120
|
+
if (!args.description) {
|
121
|
+
return {
|
122
|
+
content: [
|
123
|
+
{
|
124
|
+
type: "text",
|
125
|
+
text: "Description is required for updating workspace.",
|
126
|
+
},
|
127
|
+
],
|
128
|
+
isError: true,
|
129
|
+
};
|
130
|
+
}
|
131
|
+
const updateName = args.name || "default";
|
132
|
+
try {
|
133
|
+
updateWorkspace(updateName, {
|
134
|
+
description: args.description,
|
135
|
+
...(args.api_key && { apiKey: args.api_key }),
|
136
|
+
});
|
137
|
+
return {
|
138
|
+
content: [
|
139
|
+
{
|
140
|
+
type: "text",
|
141
|
+
text: `Successfully updated workspace "${updateName}".`,
|
142
|
+
},
|
143
|
+
],
|
144
|
+
};
|
145
|
+
}
|
146
|
+
catch (error) {
|
147
|
+
return {
|
148
|
+
content: [
|
149
|
+
{
|
150
|
+
type: "text",
|
151
|
+
text: `Workspace "${updateName}" not found.`,
|
152
|
+
},
|
153
|
+
],
|
154
|
+
isError: true,
|
155
|
+
};
|
156
|
+
}
|
157
|
+
case "LIST":
|
158
|
+
const config = loadConfig();
|
159
|
+
const workspaces = Object.entries(config.workspace);
|
160
|
+
if (workspaces.length === 0) {
|
161
|
+
return {
|
162
|
+
content: [
|
163
|
+
{
|
164
|
+
type: "text",
|
165
|
+
text: "**No workspaces found.**\n\nUse the workspace tool with type 'ADD' to create a new workspace.",
|
166
|
+
},
|
167
|
+
],
|
168
|
+
};
|
169
|
+
}
|
170
|
+
const workspaceList = workspaces
|
171
|
+
.map(([name, workspace]) => {
|
172
|
+
const defaultLabel = name === "default" ? " (default)" : "";
|
173
|
+
const description = workspace.description
|
174
|
+
? ` - ${workspace.description}`
|
175
|
+
: "";
|
176
|
+
return `- **${name}**${defaultLabel}${description}`;
|
177
|
+
})
|
178
|
+
.join("\n");
|
179
|
+
return {
|
180
|
+
content: [
|
181
|
+
{
|
182
|
+
type: "text",
|
183
|
+
text: `**Available Workspaces:**\n\n${workspaceList}`,
|
184
|
+
},
|
185
|
+
],
|
186
|
+
};
|
187
|
+
default:
|
188
|
+
return {
|
189
|
+
content: [
|
190
|
+
{
|
191
|
+
type: "text",
|
192
|
+
text: "Invalid workspace operation type.",
|
193
|
+
},
|
194
|
+
],
|
195
|
+
isError: true,
|
196
|
+
};
|
197
|
+
}
|
198
|
+
}
|
199
|
+
catch (error) {
|
200
|
+
return {
|
201
|
+
content: [
|
202
|
+
{
|
203
|
+
type: "text",
|
204
|
+
text: `Error managing workspace: ${error instanceof Error ? error.message : String(error)}`,
|
205
|
+
},
|
206
|
+
],
|
207
|
+
isError: true,
|
208
|
+
};
|
209
|
+
}
|
210
|
+
});
|
211
|
+
// Register index tool
|
212
|
+
server.registerTool("index", {
|
213
|
+
description: "Start indexing a Notion workspace in the background",
|
214
|
+
inputSchema: {
|
215
|
+
workspace: z
|
216
|
+
.string()
|
217
|
+
.default("default")
|
218
|
+
.describe("Workspace name to index - defaults to 'default' if not provided"),
|
219
|
+
},
|
220
|
+
}, async (args) => {
|
221
|
+
try {
|
222
|
+
const workspaceName = args.workspace || "default";
|
223
|
+
// Check if workspace exists
|
224
|
+
if (!checkWorkspace(workspaceName)) {
|
225
|
+
return {
|
226
|
+
content: [
|
227
|
+
{
|
228
|
+
type: "text",
|
229
|
+
text: `Workspace "${workspaceName}" not found. Use the workspace tool to list available workspaces or add a new one.`,
|
230
|
+
},
|
231
|
+
],
|
232
|
+
isError: true,
|
233
|
+
};
|
234
|
+
}
|
235
|
+
// Run indexing directly in background
|
236
|
+
const config = loadConfig();
|
237
|
+
const workspace = config.workspace[workspaceName];
|
238
|
+
// Create job tracker
|
239
|
+
const jobId = `mcp-${Date.now()}`;
|
240
|
+
const jobTracker = new JobTracker(jobId);
|
241
|
+
// Start indexing asynchronously
|
242
|
+
indexNotionPages(workspace.api_key, workspace.db_path, jobTracker)
|
243
|
+
.then(() => {
|
244
|
+
jobTracker.updateStatus("END");
|
245
|
+
console.log(`Indexing completed for workspace: ${workspaceName}`);
|
246
|
+
})
|
247
|
+
.catch((error) => {
|
248
|
+
jobTracker.updateStatus("ERROR");
|
249
|
+
console.error(`Indexing failed for workspace ${workspaceName}:`, error);
|
250
|
+
});
|
251
|
+
return {
|
252
|
+
content: [
|
253
|
+
{
|
254
|
+
type: "text",
|
255
|
+
text: `Indexing of Notion workspace has begun.\n\n**Job ID:** ${jobId}\n\nCheck back in a while to see its progress using the checkJob tool.`,
|
256
|
+
},
|
257
|
+
],
|
258
|
+
};
|
259
|
+
}
|
260
|
+
catch (error) {
|
261
|
+
return {
|
262
|
+
content: [
|
263
|
+
{
|
264
|
+
type: "text",
|
265
|
+
text: `Error starting indexing: ${error instanceof Error ? error.message : String(error)}`,
|
266
|
+
},
|
267
|
+
],
|
268
|
+
isError: true,
|
269
|
+
};
|
270
|
+
}
|
271
|
+
});
|
272
|
+
// Register ls tool
|
273
|
+
server.registerTool("ls", {
|
274
|
+
description: "List files from a workspace or database path",
|
275
|
+
inputSchema: {
|
276
|
+
workspace: z
|
277
|
+
.string()
|
278
|
+
.optional()
|
279
|
+
.describe("Workspace name or database path - defaults to default_search from config if not provided"),
|
280
|
+
prefix: z
|
281
|
+
.string()
|
282
|
+
.default("/")
|
283
|
+
.describe("Path prefix to filter results - defaults to '/'"),
|
284
|
+
depth: z
|
285
|
+
.number()
|
286
|
+
.default(2)
|
287
|
+
.describe("Maximum depth to display - defaults to 2"),
|
288
|
+
},
|
289
|
+
}, async (args) => {
|
290
|
+
try {
|
291
|
+
const config = loadConfig();
|
292
|
+
if (!config.default_search) {
|
293
|
+
return {
|
294
|
+
content: [
|
295
|
+
{
|
296
|
+
type: "text",
|
297
|
+
text: "No default search configured",
|
298
|
+
},
|
299
|
+
],
|
300
|
+
isError: true,
|
301
|
+
};
|
302
|
+
}
|
303
|
+
const searchWorkspace = args.workspace ?? config.default_search;
|
304
|
+
const prefix = args.prefix ?? "/";
|
305
|
+
const maxDepth = args.depth ?? 2;
|
306
|
+
const result = listFiles(searchWorkspace, prefix, maxDepth);
|
307
|
+
return {
|
308
|
+
content: [
|
309
|
+
{
|
310
|
+
type: "text",
|
311
|
+
text: `**Files from: ${searchWorkspace}**\nPrefix: ${prefix}, Max Depth: ${maxDepth}\n\n${result}`,
|
312
|
+
},
|
313
|
+
],
|
314
|
+
};
|
315
|
+
}
|
316
|
+
catch (error) {
|
317
|
+
return {
|
318
|
+
content: [
|
319
|
+
{
|
320
|
+
type: "text",
|
321
|
+
text: `Error listing files: ${error instanceof Error ? error.message : String(error)}`,
|
322
|
+
},
|
323
|
+
],
|
324
|
+
isError: true,
|
325
|
+
};
|
326
|
+
}
|
327
|
+
});
|
328
|
+
// Register checkJob tool
|
329
|
+
server.registerTool("checkJob", {
|
330
|
+
description: "Check job status - either by specific ID or get recent jobs",
|
331
|
+
inputSchema: {
|
332
|
+
id: z
|
333
|
+
.string()
|
334
|
+
.optional()
|
335
|
+
.describe("Specific job ID to check (e.g., 'mcp-1755172514281')"),
|
336
|
+
count: z
|
337
|
+
.number()
|
338
|
+
.default(5)
|
339
|
+
.describe("Number of recent jobs to show when no ID is provided - defaults to 5"),
|
340
|
+
prefix: z
|
341
|
+
.enum(["mcp", "cli"])
|
342
|
+
.default("mcp")
|
343
|
+
.describe("Job prefix filter - defaults to 'mcp'"),
|
344
|
+
},
|
345
|
+
}, async (args) => {
|
346
|
+
try {
|
347
|
+
if (args.id) {
|
348
|
+
// Get specific job by ID
|
349
|
+
const job = getJobById(args.id);
|
350
|
+
if (!job) {
|
351
|
+
return {
|
352
|
+
content: [
|
353
|
+
{
|
354
|
+
type: "text",
|
355
|
+
text: `Job "${args.id}" not found.`,
|
356
|
+
},
|
357
|
+
],
|
358
|
+
isError: true,
|
359
|
+
};
|
360
|
+
}
|
361
|
+
const stateText = job.state
|
362
|
+
? `**Status:** \`\`\`json\n${JSON.stringify(job.state, null, 2)}\n\`\`\``
|
363
|
+
: "**Status:** No status file found";
|
364
|
+
return {
|
365
|
+
content: [
|
366
|
+
{
|
367
|
+
type: "text",
|
368
|
+
text: `**Job ID:** ${job.jobId}\n**Prefix:** ${job.prefix}\n**Timestamp:** ${job.timestamp}\n\n${stateText}`,
|
369
|
+
},
|
370
|
+
],
|
371
|
+
};
|
372
|
+
}
|
373
|
+
else {
|
374
|
+
// Get recent jobs
|
375
|
+
const jobs = getRecentJobs(args.count, args.prefix);
|
376
|
+
if (jobs.length === 0) {
|
377
|
+
return {
|
378
|
+
content: [
|
379
|
+
{
|
380
|
+
type: "text",
|
381
|
+
text: `No ${args.prefix} jobs found.`,
|
382
|
+
},
|
383
|
+
],
|
384
|
+
};
|
385
|
+
}
|
386
|
+
const jobsList = jobs
|
387
|
+
.map((job) => {
|
388
|
+
const statusText = job.state?.status ? ` (${job.state.status})` : "";
|
389
|
+
const timeStr = new Date(job.timestamp).toLocaleString();
|
390
|
+
return `- **${job.jobId}**${statusText} - ${timeStr}`;
|
391
|
+
})
|
392
|
+
.join("\n");
|
393
|
+
return {
|
394
|
+
content: [
|
395
|
+
{
|
396
|
+
type: "text",
|
397
|
+
text: `**Recent ${args.prefix.toUpperCase()} Jobs (${jobs.length}):**\n\n${jobsList}\n\nUse checkJob with a specific ID to see detailed status.`,
|
398
|
+
},
|
399
|
+
],
|
400
|
+
};
|
401
|
+
}
|
402
|
+
}
|
403
|
+
catch (error) {
|
404
|
+
return {
|
405
|
+
content: [
|
406
|
+
{
|
407
|
+
type: "text",
|
408
|
+
text: `Error checking job: ${error instanceof Error ? error.message : String(error)}`,
|
409
|
+
},
|
410
|
+
],
|
411
|
+
isError: true,
|
412
|
+
};
|
413
|
+
}
|
9
414
|
});
|
10
|
-
// Add an addition tool
|
11
|
-
server.registerTool("add", {
|
12
|
-
description: "Add two numbers",
|
13
|
-
inputSchema: { a: z.number(), b: z.number() }
|
14
|
-
}, async ({ a, b }) => ({
|
15
|
-
content: [{ type: "text", text: String(a + b) }]
|
16
|
-
}));
|
17
|
-
// Add a dynamic greeting resource
|
18
|
-
server.resource("greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), {
|
19
|
-
description: "Dynamic greeting generator"
|
20
|
-
}, async (uri, variables) => ({
|
21
|
-
contents: [{
|
22
|
-
uri: uri.href,
|
23
|
-
text: `Hello, ${variables.name}!`
|
24
|
-
}]
|
25
|
-
}));
|
26
415
|
// Start receiving messages on stdin and sending messages on stdout
|
27
416
|
const transport = new StdioServerTransport();
|
28
417
|
await server.connect(transport);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "gwanli",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.0",
|
4
4
|
"description": "Notion management with AI capabilities - CLI and MCP server",
|
5
5
|
"type": "module",
|
6
6
|
"main": "dist/cli.js",
|
@@ -18,9 +18,12 @@
|
|
18
18
|
"author": "Ivan Leo & Timothy Chen",
|
19
19
|
"license": "MIT",
|
20
20
|
"dependencies": {
|
21
|
+
"@hono/node-server": "^1.18.2",
|
21
22
|
"commander": "^11.0.0",
|
22
|
-
"gwanli-core": "{\n gwanli-core: 0.
|
23
|
-
"gwanli-mcp": "{\n gwanli-mcp: 0.
|
23
|
+
"gwanli-core": "{\n gwanli-core: 0.3.0\n}",
|
24
|
+
"gwanli-mcp": "{\n gwanli-mcp: 0.3.0\n}",
|
25
|
+
"hono": "^4.9.1",
|
26
|
+
"open": "^10.2.0"
|
24
27
|
},
|
25
28
|
"devDependencies": {
|
26
29
|
"@types/node": "^20.0.0",
|