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.
@@ -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,15 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import { helloGwanli } from 'gwanli-core';
4
- const program = new Command('gwanli');
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('gwanli')
7
- .description('Gwanli - Notion management CLI')
8
- .version('0.1.0');
9
- program
10
- .command('hello')
11
- .description('Say hello from Gwanli')
12
- .action(() => {
13
- console.log(helloGwanli());
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();
@@ -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,28 +1,417 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer, ResourceTemplate } 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
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: "demo-server",
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.1.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.1.0\n}",
23
- "gwanli-mcp": "{\n gwanli-mcp: 0.1.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",