@plaud-ai/mcp 0.1.58 → 0.2.1

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,92 @@
1
+ // src/skills.ts
2
+ import { readdir, readFile } from "fs/promises";
3
+ import { statSync } from "fs";
4
+ import { join, dirname, resolve } from "path";
5
+ import { fileURLToPath } from "url";
6
+ function parseFrontmatter(raw) {
7
+ if (!raw.startsWith("---\n")) return { meta: {}, body: raw };
8
+ const end = raw.indexOf("\n---\n", 4);
9
+ if (end === -1) return { meta: {}, body: raw };
10
+ const yaml = raw.slice(4, end);
11
+ const body = raw.slice(end + 5);
12
+ const meta = {};
13
+ let currentKey = null;
14
+ for (const line of yaml.split("\n")) {
15
+ if (!line.trim()) continue;
16
+ const topMatch = line.match(/^([a-zA-Z_][\w-]*):\s*(.*)$/);
17
+ if (topMatch && !line.startsWith(" ")) {
18
+ const [, k, v] = topMatch;
19
+ currentKey = k;
20
+ if (v !== "") {
21
+ const unq = v.replace(/^["']|["']$/g, "");
22
+ meta[k] = unq;
23
+ currentKey = null;
24
+ } else {
25
+ meta[k] = {};
26
+ }
27
+ }
28
+ }
29
+ return { meta, body };
30
+ }
31
+ function findSkillsDir() {
32
+ const here = dirname(fileURLToPath(import.meta.url));
33
+ const candidates = [
34
+ resolve(here, "..", "skills"),
35
+ resolve(here, "..", "..", "skills")
36
+ ];
37
+ for (const c of candidates) {
38
+ try {
39
+ if (statSync(c).isDirectory()) return c;
40
+ } catch {
41
+ }
42
+ }
43
+ return candidates[0];
44
+ }
45
+ var cached = null;
46
+ async function loadSkills() {
47
+ if (cached) return cached;
48
+ const dir = findSkillsDir();
49
+ let entries;
50
+ try {
51
+ entries = await readdir(dir);
52
+ } catch {
53
+ cached = [];
54
+ return cached;
55
+ }
56
+ const skills = [];
57
+ for (const name of entries) {
58
+ try {
59
+ const skillPath = join(dir, name, "SKILL.md");
60
+ const raw = await readFile(skillPath, "utf-8");
61
+ const { meta, body } = parseFrontmatter(raw);
62
+ const skillName = typeof meta.name === "string" ? meta.name : name;
63
+ const description = typeof meta.description === "string" ? meta.description : "";
64
+ const version = typeof meta.version === "string" ? meta.version : "0.0.0";
65
+ skills.push({
66
+ name: skillName,
67
+ version,
68
+ description,
69
+ body: body.trimStart(),
70
+ content: raw,
71
+ supported: true
72
+ });
73
+ } catch {
74
+ }
75
+ }
76
+ skills.sort((a, b) => {
77
+ if (a.name === "plaud-shared") return -1;
78
+ if (b.name === "plaud-shared") return 1;
79
+ return a.name.localeCompare(b.name);
80
+ });
81
+ cached = skills;
82
+ return cached;
83
+ }
84
+ async function skillsCombined() {
85
+ const skills = await loadSkills();
86
+ return skills.map((s) => s.content).join("\n\n---\n\n");
87
+ }
88
+
89
+ export {
90
+ loadSkills,
91
+ skillsCombined
92
+ };
@@ -0,0 +1,33 @@
1
+ import {
2
+ PlaudClient
3
+ } from "./chunk-SNSGVRCU.js";
4
+
5
+ // src/config.ts
6
+ function buildExtraHeaders() {
7
+ const headers = {};
8
+ if (process.env.PLAUD_ENV) headers["x-pld-env"] = process.env.PLAUD_ENV;
9
+ if (process.env.PLAUD_REGION) headers["x-pld-region"] = process.env.PLAUD_REGION;
10
+ return headers;
11
+ }
12
+ var CONFIG = {
13
+ clientId: process.env.PLAUD_MCP_CLIENT_ID ?? process.env.PLAUD_CLIENT_ID ?? "client_9c501dad-8a0d-40b2-a7b0-d1cb8787f674",
14
+ clientSecret: process.env.PLAUD_CLIENT_SECRET ?? "",
15
+ redirectUri: "http://localhost:8199/auth/callback",
16
+ tokenFile: "tokens-mcp.json",
17
+ apiBase: process.env.PLAUD_API_BASE,
18
+ authorizationUrl: process.env.PLAUD_AUTH_URL,
19
+ tokenUrl: process.env.PLAUD_TOKEN_URL,
20
+ refreshUrl: process.env.PLAUD_REFRESH_URL,
21
+ extraHeaders: buildExtraHeaders()
22
+ };
23
+ var client = null;
24
+ function getClient() {
25
+ if (!client) {
26
+ client = new PlaudClient(CONFIG);
27
+ }
28
+ return client;
29
+ }
30
+
31
+ export {
32
+ getClient
33
+ };
@@ -0,0 +1,79 @@
1
+ import {
2
+ skillsCombined
3
+ } from "./chunk-4QBEOJPX.js";
4
+
5
+ // src/install-utils.ts
6
+ import { readFile, writeFile, mkdir } from "fs/promises";
7
+ import { existsSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { homedir, platform } from "os";
10
+ import { spawnSync } from "child_process";
11
+ var SKILLS_MARKER_START = "<!-- plaud-skills:start -->";
12
+ var SKILLS_MARKER_END = "<!-- plaud-skills:end -->";
13
+ function findNpxCommand() {
14
+ const npxName = platform() === "win32" ? "npx.cmd" : "npx";
15
+ const found = spawnSync(platform() === "win32" ? "where" : "which", [npxName], { encoding: "utf-8" });
16
+ const firstMatch = found.status === 0 ? found.stdout.split(/\r?\n/).find((line) => line.trim())?.trim() : void 0;
17
+ if (firstMatch) return firstMatch;
18
+ const sibling = join(dirname(process.execPath), npxName);
19
+ return existsSync(sibling) ? sibling : npxName;
20
+ }
21
+ function getMcpEntry() {
22
+ return {
23
+ command: findNpxCommand(),
24
+ args: ["-y", "@plaud-ai/mcp@latest"]
25
+ };
26
+ }
27
+ function copyToClipboard(content) {
28
+ if (platform() === "win32") {
29
+ return spawnSync("clip", [], { input: content }).status === 0;
30
+ }
31
+ if (platform() === "darwin") {
32
+ return spawnSync("pbcopy", [], { input: content }).status === 0;
33
+ }
34
+ return false;
35
+ }
36
+ async function writeSkillsToClaudeCode() {
37
+ const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
38
+ const combined = await skillsCombined();
39
+ const block = `${SKILLS_MARKER_START}
40
+ ${combined}
41
+ ${SKILLS_MARKER_END}`;
42
+ let existing = "";
43
+ try {
44
+ existing = await readFile(claudeMdPath, "utf-8");
45
+ } catch {
46
+ }
47
+ if (existing.includes(SKILLS_MARKER_START)) {
48
+ const updated = existing.replace(
49
+ new RegExp(`${SKILLS_MARKER_START}[\\s\\S]*?${SKILLS_MARKER_END}`),
50
+ block
51
+ );
52
+ await mkdir(dirname(claudeMdPath), { recursive: true });
53
+ await writeFile(claudeMdPath, updated, "utf-8");
54
+ } else {
55
+ await mkdir(dirname(claudeMdPath), { recursive: true });
56
+ await writeFile(claudeMdPath, existing + (existing.endsWith("\n") ? "" : "\n") + block + "\n", "utf-8");
57
+ }
58
+ }
59
+ async function removeSkillsFromClaudeCode() {
60
+ const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
61
+ let existing = "";
62
+ try {
63
+ existing = await readFile(claudeMdPath, "utf-8");
64
+ } catch {
65
+ return;
66
+ }
67
+ if (!existing.includes(SKILLS_MARKER_START)) {
68
+ return;
69
+ }
70
+ const updated = existing.replace(new RegExp(`\\n?${SKILLS_MARKER_START}[\\s\\S]*?${SKILLS_MARKER_END}\\n?`), "").trimEnd();
71
+ await writeFile(claudeMdPath, updated ? updated + "\n" : "", "utf-8");
72
+ }
73
+
74
+ export {
75
+ getMcpEntry,
76
+ copyToClipboard,
77
+ writeSkillsToClaudeCode,
78
+ removeSkillsFromClaudeCode
79
+ };
@@ -0,0 +1,151 @@
1
+ // src/logger.ts
2
+ import pino from "pino";
3
+ var logger = pino(
4
+ { level: process.env.LOG_LEVEL ?? "info" },
5
+ pino.destination(2)
6
+ );
7
+
8
+ // src/tools/index.ts
9
+ import { z } from "zod";
10
+ var MAX_FILTER_PAGES = 5;
11
+ var FILTER_PAGE_SIZE = 100;
12
+ function parseDate(s) {
13
+ if (!s) return null;
14
+ const d = new Date(s);
15
+ if (Number.isNaN(d.getTime())) return null;
16
+ return d.getTime();
17
+ }
18
+ function registerTools(server, client) {
19
+ server.tool(
20
+ "list_files",
21
+ "List Plaud recordings. Supports optional client-side filtering: `query` (case-insensitive name substring), `date_from`/`date_to` (YYYY-MM-DD, inclusive). When any filter is set, paginates up to 5 pages \xD7 100 recordings and returns all matches.",
22
+ {
23
+ page: z.number().optional().default(1).describe("Page number (ignored when filters are set)"),
24
+ page_size: z.number().optional().default(20).describe("Items per page (ignored when filters are set)"),
25
+ query: z.string().optional().describe("Case-insensitive substring match on recording name"),
26
+ date_from: z.string().optional().describe("Start date inclusive, YYYY-MM-DD"),
27
+ date_to: z.string().optional().describe("End date inclusive, YYYY-MM-DD")
28
+ },
29
+ async ({ page, page_size, query, date_from, date_to }) => {
30
+ const start = Date.now();
31
+ const hasFilter = Boolean(query || date_from || date_to);
32
+ logger.info({ event: "tool_call", tool: "list_files", has_filter: hasFilter });
33
+ try {
34
+ if (!hasFilter) {
35
+ const result = await client.listFiles(page, page_size);
36
+ logger.info({ event: "tool_call_end", tool: "list_files", duration_ms: Date.now() - start });
37
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
38
+ }
39
+ const q = query?.toLowerCase();
40
+ const from = parseDate(date_from);
41
+ const toRaw = parseDate(date_to);
42
+ const to = toRaw !== null ? toRaw + 24 * 60 * 60 * 1e3 - 1 : null;
43
+ const matches = [];
44
+ let scanned = 0;
45
+ let truncated = false;
46
+ for (let p = 1; p <= MAX_FILTER_PAGES; p++) {
47
+ const pageResult = await client.listFiles(p, FILTER_PAGE_SIZE);
48
+ const items = pageResult.data;
49
+ scanned += items.length;
50
+ for (const item of items) {
51
+ if (q && !(item.name ?? "").toLowerCase().includes(q)) continue;
52
+ if (from !== null || to !== null) {
53
+ const created = parseDate(item.created_at);
54
+ if (created === null) continue;
55
+ if (from !== null && created < from) continue;
56
+ if (to !== null && created > to) continue;
57
+ }
58
+ matches.push(item);
59
+ }
60
+ if (items.length < FILTER_PAGE_SIZE) break;
61
+ if (p === MAX_FILTER_PAGES) truncated = true;
62
+ }
63
+ logger.info({ event: "tool_call_end", tool: "list_files", duration_ms: Date.now() - start, scanned, matched: matches.length, truncated });
64
+ return {
65
+ content: [{ type: "text", text: JSON.stringify({
66
+ data: matches,
67
+ scanned,
68
+ matched: matches.length,
69
+ truncated,
70
+ note: truncated ? `Scanned first ${MAX_FILTER_PAGES * FILTER_PAGE_SIZE} recordings; narrow filters for a complete match.` : void 0
71
+ }, null, 2) }]
72
+ };
73
+ } catch (err) {
74
+ logger.error({ event: "tool_call_error", tool: "list_files", duration_ms: Date.now() - start, error: String(err) });
75
+ return { content: [{ type: "text", text: `Failed to list files: ${err}` }], isError: true };
76
+ }
77
+ }
78
+ );
79
+ server.tool(
80
+ "get_file",
81
+ "Get details of a specific Plaud recording by ID",
82
+ { file_id: z.string().describe("The file ID to retrieve") },
83
+ async ({ file_id }) => {
84
+ const start = Date.now();
85
+ logger.info({ event: "tool_call", tool: "get_file", file_id });
86
+ try {
87
+ const file = await client.getFile(file_id);
88
+ logger.info({ event: "tool_call_end", tool: "get_file", duration_ms: Date.now() - start });
89
+ return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
90
+ } catch (err) {
91
+ logger.error({ event: "tool_call_error", tool: "get_file", duration_ms: Date.now() - start, error: String(err) });
92
+ return { content: [{ type: "text", text: `Failed to get file: ${err}` }], isError: true };
93
+ }
94
+ }
95
+ );
96
+ server.tool(
97
+ "get_note",
98
+ "Fetch AI-generated notes for a Plaud recording \u2014 compact summary, action items, and key topics",
99
+ { file_id: z.string().describe("The file ID to retrieve notes for") },
100
+ async ({ file_id }) => {
101
+ const start = Date.now();
102
+ logger.info({ event: "tool_call", tool: "get_note", file_id });
103
+ try {
104
+ const file = await client.getFile(file_id);
105
+ logger.info({ event: "tool_call_end", tool: "get_note", duration_ms: Date.now() - start });
106
+ return { content: [{ type: "text", text: JSON.stringify(file.note_list ?? [], null, 2) }] };
107
+ } catch (err) {
108
+ logger.error({ event: "tool_call_error", tool: "get_note", duration_ms: Date.now() - start, error: String(err) });
109
+ return { content: [{ type: "text", text: `Failed to get note: ${err}` }], isError: true };
110
+ }
111
+ }
112
+ );
113
+ server.tool(
114
+ "get_transcript",
115
+ "Fetch the full timestamped transcript with speaker attribution for a Plaud recording",
116
+ { file_id: z.string().describe("The file ID to retrieve transcript for") },
117
+ async ({ file_id }) => {
118
+ const start = Date.now();
119
+ logger.info({ event: "tool_call", tool: "get_transcript", file_id });
120
+ try {
121
+ const file = await client.getFile(file_id);
122
+ logger.info({ event: "tool_call_end", tool: "get_transcript", duration_ms: Date.now() - start });
123
+ return { content: [{ type: "text", text: JSON.stringify(file.source_list ?? [], null, 2) }] };
124
+ } catch (err) {
125
+ logger.error({ event: "tool_call_error", tool: "get_transcript", duration_ms: Date.now() - start, error: String(err) });
126
+ return { content: [{ type: "text", text: `Failed to get transcript: ${err}` }], isError: true };
127
+ }
128
+ }
129
+ );
130
+ server.tool(
131
+ "get_current_user",
132
+ "Get current authenticated user info",
133
+ async () => {
134
+ const start = Date.now();
135
+ logger.info({ event: "tool_call", tool: "get_current_user" });
136
+ try {
137
+ const user = await client.getCurrentUser();
138
+ logger.info({ event: "tool_call_end", tool: "get_current_user", duration_ms: Date.now() - start });
139
+ return { content: [{ type: "text", text: JSON.stringify(user, null, 2) }] };
140
+ } catch (err) {
141
+ logger.error({ event: "tool_call_error", tool: "get_current_user", duration_ms: Date.now() - start, error: String(err) });
142
+ return { content: [{ type: "text", text: `Failed to get user info: ${err}` }], isError: true };
143
+ }
144
+ }
145
+ );
146
+ }
147
+
148
+ export {
149
+ logger,
150
+ registerTools
151
+ };
@@ -229,106 +229,6 @@ var PlaudClient = class {
229
229
  }
230
230
  };
231
231
 
232
- // src/logger.ts
233
- import pino from "pino";
234
- var logger = pino({
235
- level: process.env.LOG_LEVEL ?? "info"
236
- });
237
-
238
- // src/tools/index.ts
239
- import { z } from "zod";
240
- function registerTools(server, client) {
241
- server.tool(
242
- "list_files",
243
- "List Plaud recordings",
244
- {
245
- page: z.number().optional().default(1).describe("Page number"),
246
- page_size: z.number().optional().default(20).describe("Items per page")
247
- },
248
- async ({ page, page_size }) => {
249
- const start = Date.now();
250
- logger.info({ event: "tool_call", tool: "list_files" });
251
- try {
252
- const result = await client.listFiles(page, page_size);
253
- logger.info({ event: "tool_call_end", tool: "list_files", duration_ms: Date.now() - start });
254
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
255
- } catch (err) {
256
- logger.error({ event: "tool_call_error", tool: "list_files", duration_ms: Date.now() - start, error: String(err) });
257
- return { content: [{ type: "text", text: `Failed to list files: ${err}` }], isError: true };
258
- }
259
- }
260
- );
261
- server.tool(
262
- "get_file",
263
- "Get details of a specific Plaud recording by ID",
264
- { file_id: z.string().describe("The file ID to retrieve") },
265
- async ({ file_id }) => {
266
- const start = Date.now();
267
- logger.info({ event: "tool_call", tool: "get_file", file_id });
268
- try {
269
- const file = await client.getFile(file_id);
270
- logger.info({ event: "tool_call_end", tool: "get_file", duration_ms: Date.now() - start });
271
- return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
272
- } catch (err) {
273
- logger.error({ event: "tool_call_error", tool: "get_file", duration_ms: Date.now() - start, error: String(err) });
274
- return { content: [{ type: "text", text: `Failed to get file: ${err}` }], isError: true };
275
- }
276
- }
277
- );
278
- server.tool(
279
- "get_note",
280
- "Fetch AI-generated notes for a Plaud recording \u2014 compact summary, action items, and key topics",
281
- { file_id: z.string().describe("The file ID to retrieve notes for") },
282
- async ({ file_id }) => {
283
- const start = Date.now();
284
- logger.info({ event: "tool_call", tool: "get_note", file_id });
285
- try {
286
- const file = await client.getFile(file_id);
287
- logger.info({ event: "tool_call_end", tool: "get_note", duration_ms: Date.now() - start });
288
- return { content: [{ type: "text", text: JSON.stringify(file.note_list ?? [], null, 2) }] };
289
- } catch (err) {
290
- logger.error({ event: "tool_call_error", tool: "get_note", duration_ms: Date.now() - start, error: String(err) });
291
- return { content: [{ type: "text", text: `Failed to get note: ${err}` }], isError: true };
292
- }
293
- }
294
- );
295
- server.tool(
296
- "get_transcript",
297
- "Fetch the full timestamped transcript with speaker attribution for a Plaud recording",
298
- { file_id: z.string().describe("The file ID to retrieve transcript for") },
299
- async ({ file_id }) => {
300
- const start = Date.now();
301
- logger.info({ event: "tool_call", tool: "get_transcript", file_id });
302
- try {
303
- const file = await client.getFile(file_id);
304
- logger.info({ event: "tool_call_end", tool: "get_transcript", duration_ms: Date.now() - start });
305
- return { content: [{ type: "text", text: JSON.stringify(file.source_list ?? [], null, 2) }] };
306
- } catch (err) {
307
- logger.error({ event: "tool_call_error", tool: "get_transcript", duration_ms: Date.now() - start, error: String(err) });
308
- return { content: [{ type: "text", text: `Failed to get transcript: ${err}` }], isError: true };
309
- }
310
- }
311
- );
312
- server.tool(
313
- "get_current_user",
314
- "Get current authenticated user info",
315
- async () => {
316
- const start = Date.now();
317
- logger.info({ event: "tool_call", tool: "get_current_user" });
318
- try {
319
- const user = await client.getCurrentUser();
320
- logger.info({ event: "tool_call_end", tool: "get_current_user", duration_ms: Date.now() - start });
321
- return { content: [{ type: "text", text: JSON.stringify(user, null, 2) }] };
322
- } catch (err) {
323
- logger.error({ event: "tool_call_error", tool: "get_current_user", duration_ms: Date.now() - start, error: String(err) });
324
- return { content: [{ type: "text", text: `Failed to get user info: ${err}` }], isError: true };
325
- }
326
- }
327
- );
328
- }
329
-
330
232
  export {
331
- PlaudClient,
332
- logger,
333
- registerTools
233
+ PlaudClient
334
234
  };
package/dist/index.js CHANGED
@@ -1,58 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ACTIVE_SKILLS
4
- } from "./chunk-7PYQFJJW.js";
3
+ getClient
4
+ } from "./chunk-7KGB7GSZ.js";
5
+ import {
6
+ loadSkills
7
+ } from "./chunk-4QBEOJPX.js";
5
8
  import {
6
- PlaudClient,
7
9
  registerTools
8
- } from "./chunk-YMQLKZLW.js";
10
+ } from "./chunk-MPCF6HMK.js";
11
+ import "./chunk-SNSGVRCU.js";
9
12
 
10
13
  // src/index.ts
11
14
  import { createServer } from "http";
12
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
16
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
17
  import open from "open";
15
-
16
- // src/config.ts
17
- function buildExtraHeaders() {
18
- const headers = {};
19
- if (process.env.PLAUD_ENV) headers["x-pld-env"] = process.env.PLAUD_ENV;
20
- if (process.env.PLAUD_REGION) headers["x-pld-region"] = process.env.PLAUD_REGION;
21
- return headers;
22
- }
23
- var CONFIG = {
24
- clientId: process.env.PLAUD_MCP_CLIENT_ID ?? process.env.PLAUD_CLIENT_ID ?? "client_9c501dad-8a0d-40b2-a7b0-d1cb8787f674",
25
- clientSecret: process.env.PLAUD_CLIENT_SECRET ?? "",
26
- redirectUri: "http://localhost:8199/auth/callback",
27
- tokenFile: "tokens-mcp.json",
28
- apiBase: process.env.PLAUD_API_BASE,
29
- authorizationUrl: process.env.PLAUD_AUTH_URL,
30
- tokenUrl: process.env.PLAUD_TOKEN_URL,
31
- refreshUrl: process.env.PLAUD_REFRESH_URL,
32
- extraHeaders: buildExtraHeaders()
33
- };
34
- var client = null;
35
- function getClient() {
36
- if (!client) {
37
- client = new PlaudClient(CONFIG);
38
- }
39
- return client;
40
- }
41
-
42
- // src/index.ts
43
18
  var server = new McpServer({
44
19
  name: "plaud",
45
- version: "0.1.0"
20
+ version: "0.2.0"
46
21
  });
47
22
  var CALLBACK_PORT = 8199;
48
23
  var LOGIN_TIMEOUT_MS = 12e4;
49
24
  server.tool("login", "Log in, sign in, or authenticate with Plaud account via OAuth (opens browser)", async () => {
50
- const client2 = getClient();
51
- const existingToken = await client2.auth.getAccessToken();
25
+ const client = getClient();
26
+ const existingToken = await client.auth.getAccessToken();
52
27
  if (existingToken) {
53
28
  return { content: [{ type: "text", text: "Already logged in." }] };
54
29
  }
55
- const { url, codeVerifier, state } = client2.auth.createAuthorizationRequest();
30
+ const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
56
31
  return new Promise((resolve) => {
57
32
  const httpServer = createServer(async (req, res) => {
58
33
  const reqUrl = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
@@ -73,7 +48,7 @@ server.tool("login", "Log in, sign in, or authenticate with Plaud account via OA
73
48
  return;
74
49
  }
75
50
  try {
76
- await client2.auth.exchangeCode(code, codeVerifier, state);
51
+ await client.auth.exchangeCode(code, codeVerifier, state);
77
52
  res.writeHead(200, { "Content-Type": "text/html" });
78
53
  res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
79
54
  cleanup();
@@ -106,10 +81,14 @@ server.tool("login", "Log in, sign in, or authenticate with Plaud account via OA
106
81
  }, LOGIN_TIMEOUT_MS);
107
82
  httpServer.listen(CALLBACK_PORT, () => {
108
83
  open(url).catch(() => {
109
- cleanup();
110
84
  resolve({
111
- content: [{ type: "text", text: `Could not open browser automatically. Please open this URL to authenticate:
112
- ${url}` }],
85
+ content: [{
86
+ type: "text",
87
+ text: `Could not open browser automatically. Open this URL within 2 minutes to authenticate:
88
+ ${url}
89
+
90
+ If this is a remote/headless machine, forward local port 8199 to this machine first, then open the URL locally.`
91
+ }],
113
92
  isError: true
114
93
  });
115
94
  });
@@ -125,56 +104,87 @@ ${url}` }],
125
104
  });
126
105
  registerTools(server, getClient());
127
106
  server.tool("logout", "Log out, sign out, revoke authorization, and disconnect from Plaud account", async () => {
128
- const client2 = getClient();
129
- const existingToken = await client2.auth.getAccessToken();
107
+ const client = getClient();
108
+ const existingToken = await client.auth.getAccessToken();
130
109
  if (!existingToken) {
131
110
  return { content: [{ type: "text", text: "Already logged out." }] };
132
111
  }
133
112
  try {
134
- await client2.revokeCurrentUser();
113
+ await client.revokeCurrentUser();
135
114
  } catch {
136
115
  }
137
- await client2.auth.logout();
116
+ await client.auth.logout();
138
117
  return {
139
118
  content: [{ type: "text", text: "Logged out and revoked authorization." }]
140
119
  };
141
120
  });
142
- for (const skill of ACTIVE_SKILLS) {
143
- server.prompt(skill.name, skill.description, () => ({
144
- messages: [{ role: "user", content: { type: "text", text: skill.content } }]
145
- }));
121
+ async function registerSkillPrompts() {
122
+ const skills = await loadSkills();
123
+ for (const skill of skills) {
124
+ server.prompt(skill.name, skill.description, () => ({
125
+ messages: [{ role: "user", content: { type: "text", text: skill.content } }]
126
+ }));
127
+ }
146
128
  }
147
129
  async function main() {
148
- if (process.argv[2] === "clean-plugin") {
149
- const { runCleanPlugin } = await import("./setup-KGUMS567.js");
130
+ const sub = process.argv[2];
131
+ const sub2 = process.argv[3];
132
+ if (sub === "install") {
133
+ const { runInstall } = await import("./install-I2N3DJG4.js");
134
+ const args = process.argv.slice(3);
135
+ const yes = args.some((a) => a === "--yes" || a === "-y");
136
+ const noLogin = args.some((a) => a === "--no-login");
137
+ await runInstall({ yes, noLogin });
138
+ return;
139
+ }
140
+ if (sub === "clean-plugin") {
141
+ const { runCleanPlugin } = await import("./setup-QRUDA7ES.js");
150
142
  await runCleanPlugin();
151
143
  return;
152
144
  }
153
- if (process.argv[2] === "setup" && process.argv[3] === "codex") {
154
- const { runSetupCodex } = await import("./setup-KGUMS567.js");
145
+ if (sub === "setup" && sub2 === "codex") {
146
+ const { runSetupCodex } = await import("./setup-QRUDA7ES.js");
155
147
  await runSetupCodex();
156
148
  return;
157
149
  }
158
- if (process.argv[2] === "unsetup" && process.argv[3] === "codex") {
159
- const { runUnsetupCodex } = await import("./setup-KGUMS567.js");
150
+ if (sub === "unsetup" && sub2 === "codex") {
151
+ const { runUnsetupCodex } = await import("./setup-QRUDA7ES.js");
160
152
  await runUnsetupCodex();
161
153
  return;
162
154
  }
163
- if (process.argv[2] === "setup") {
164
- const { runSetup } = await import("./setup-KGUMS567.js");
155
+ if (sub === "setup") {
156
+ const { runSetup } = await import("./setup-QRUDA7ES.js");
165
157
  await runSetup();
166
158
  return;
167
159
  }
168
- if (process.argv[2] === "unsetup") {
169
- const { runUnsetup } = await import("./setup-KGUMS567.js");
160
+ if (sub === "unsetup") {
161
+ const { runUnsetup } = await import("./setup-QRUDA7ES.js");
170
162
  await runUnsetup();
171
163
  return;
172
164
  }
173
- if (process.argv[2] === "http") {
174
- const { startHttpServer } = await import("./server-5L7BYLYR.js");
165
+ if (sub === "http") {
166
+ const { startHttpServer } = await import("./server-D3H6AMVD.js");
175
167
  startHttpServer();
176
168
  return;
177
169
  }
170
+ if (sub === "--help" || sub === "-h") {
171
+ console.log(`plaud-mcp \u2014 Plaud MCP server
172
+
173
+ Usage:
174
+ plaud-mcp start MCP over stdio
175
+ plaud-mcp install detect AI clients, configure them, and run OAuth inline (recommended)
176
+ plaud-mcp install --yes non-interactive; auto-configure every detected client (for agent-driven install)
177
+ plaud-mcp install --no-login skip the OAuth step (for CI / headless setup)
178
+ plaud-mcp http start HTTP MCP server (advanced)
179
+ plaud-mcp setup [deprecated] alias for install (Claude Desktop only)
180
+ plaud-mcp setup codex [deprecated] alias for install (Codex only)
181
+ plaud-mcp unsetup remove Plaud from Claude Desktop
182
+ plaud-mcp unsetup codex remove Plaud from Codex Desktop
183
+ plaud-mcp clean-plugin clear Claude Code plugin cache
184
+ `);
185
+ return;
186
+ }
187
+ await registerSkillPrompts();
178
188
  const transport = new StdioServerTransport();
179
189
  await server.connect(transport);
180
190
  }