gwanli 0.2.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.
@@ -0,0 +1,4 @@
1
+ export declare function startAuthServer(): Promise<{
2
+ url: string;
3
+ tokenPromise: Promise<string>;
4
+ }>;
@@ -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
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const auth: Command;
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,57 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- import { helloGwanli, indexNotionPages } from "gwanli-core";
4
- import { exec } from "child_process";
3
+ import { auth } from "./auth.js";
4
+ import { index } from "./index.js";
5
+ import { ls } from "./ls.js";
6
+ import { job } from "./job.js";
5
7
  const program = new Command("gwanli");
6
8
  program
7
9
  .name("gwanli")
8
10
  .description("Gwanli - Notion management CLI")
9
- .version("0.1.0");
10
- program
11
- .command("hello")
12
- .description("Say hello from Gwanli")
13
- .action(() => {
14
- console.log(helloGwanli());
15
- });
16
- program
17
- .command("index")
18
- .description("Index pages from your Notion workspace")
19
- .option("-t, --token <token>", "Notion integration token")
20
- .option("-d, --db <dbPath>", "Path to the SQLite database file")
21
- .action(async (options) => {
22
- const token = options.token || process.env.NOTION_API_KEY;
23
- const dbPath = options.db || "./notion.db";
24
- if (!token) {
25
- console.error("Error: Notion token is required. Provide it via --token option or NOTION_API_KEY environment variable.");
26
- process.exit(1);
27
- }
28
- await indexNotionPages(token, dbPath);
29
- });
30
- program
31
- .command("auth")
32
- .description("Interactively generate a Notion API token")
33
- .action(async () => {
34
- console.log("\n🔐 Notion API Token Generator");
35
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
36
- console.log("\n🚀 Opening authentication page in your browser...");
37
- const workerUrl = "https://worker.ivanleomk9297.workers.dev";
38
- // Open URL in default browser
39
- const openCommand = process.platform === 'darwin' ? 'open' :
40
- process.platform === 'win32' ? 'start' : 'xdg-open';
41
- exec(`${openCommand} ${workerUrl}`, (error) => {
42
- if (error) {
43
- console.log(`\n❌ Could not open browser automatically. Please visit: ${workerUrl}`);
44
- }
45
- else {
46
- console.log(`\n✅ Browser opened to: ${workerUrl}`);
47
- }
48
- });
49
- console.log("\nAfter completing authentication:");
50
- console.log("1. Copy the generated token");
51
- console.log("2. Set it as an environment variable:");
52
- console.log(" export NOTION_API_KEY=your_token_here");
53
- console.log("\nAlternatively, you can use the --token option with any gwanli command.");
54
- console.log("\n💡 For more details about the worker, check: ./worker/index.ts");
55
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
56
- });
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);
57
20
  program.parse();
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ declare const program: Command;
4
+ export { program as index };
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
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const job: Command;
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
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const ls: Command;
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,39 +1,314 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer, } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { exec } from "child_process";
5
- import { promisify } from "util";
6
- const execAsync = promisify(exec);
4
+ import { z } from "zod";
5
+ import { loadConfig, addWorkspace, updateWorkspace, deleteWorkspace, OAUTH_BASE_URL, checkWorkspace, indexNotionPages, JobTracker, listFiles, getRecentJobs, getJobById, } from "gwanli-core";
7
6
  // Create an MCP server
8
7
  const server = new McpServer({
9
- name: "demo-server",
8
+ name: "gwanli-mcp",
10
9
  version: "1.0.0",
11
10
  });
12
- // Add auth tool
11
+ // Register auth tool
13
12
  server.registerTool("auth", {
14
- description: "Authenticate user by opening browser for API key setup or showing current status",
13
+ description: "Check authentication status and get OAuth URLs for workspace setup",
15
14
  inputSchema: {},
16
15
  }, async () => {
17
- const notionApiKey = process.env.NOTION_API_KEY;
18
- if (notionApiKey) {
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) {
19
261
  return {
20
262
  content: [
21
263
  {
22
264
  type: "text",
23
- text: "User already authenticated, NOTION_API_KEY has been set",
265
+ text: `Error starting indexing: ${error instanceof Error ? error.message : String(error)}`,
24
266
  },
25
267
  ],
268
+ isError: true,
26
269
  };
27
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) => {
28
290
  try {
29
- // Open browser to authentication URL
30
- const url = "https://worker.ivanleomk9297.workers.dev";
31
- await execAsync(`open "${url}"`);
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);
32
307
  return {
33
308
  content: [
34
309
  {
35
310
  type: "text",
36
- text: `Opening browser to ${url} for authentication`,
311
+ text: `**Files from: ${searchWorkspace}**\nPrefix: ${prefix}, Max Depth: ${maxDepth}\n\n${result}`,
37
312
  },
38
313
  ],
39
314
  };
@@ -43,9 +318,97 @@ server.registerTool("auth", {
43
318
  content: [
44
319
  {
45
320
  type: "text",
46
- text: `Please visit https://worker.ivanleomk9297.workers.dev to authenticate`,
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)}`,
47
409
  },
48
410
  ],
411
+ isError: true,
49
412
  };
50
413
  }
51
414
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gwanli",
3
- "version": "0.2.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.2.0\n}",
23
- "gwanli-mcp": "{\n gwanli-mcp: 0.2.0\n}"
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",