@plaud-ai/mcp 0.1.58 → 0.2.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,370 @@
1
+ import {
2
+ getClient
3
+ } from "./chunk-7KGB7GSZ.js";
4
+ import {
5
+ copyToClipboard,
6
+ getMcpEntry,
7
+ writeSkillsToClaudeCode
8
+ } from "./chunk-UPEENHCG.js";
9
+ import {
10
+ skillsCombined
11
+ } from "./chunk-4QBEOJPX.js";
12
+ import "./chunk-SNSGVRCU.js";
13
+
14
+ // src/install.ts
15
+ import { readFile, writeFile, mkdir } from "fs/promises";
16
+ import { existsSync } from "fs";
17
+ import { createInterface } from "readline/promises";
18
+ import { createServer } from "http";
19
+ import { join, dirname } from "path";
20
+ import { homedir, platform } from "os";
21
+ import { spawnSync } from "child_process";
22
+ import open from "open";
23
+ function claudeDesktopConfigPath() {
24
+ if (platform() === "darwin") {
25
+ return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
26
+ }
27
+ if (platform() === "win32") {
28
+ return join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
29
+ }
30
+ return null;
31
+ }
32
+ function claudeCodeDir() {
33
+ return join(homedir(), ".claude");
34
+ }
35
+ function codexConfigPath() {
36
+ return join(homedir(), ".codex", "config.toml");
37
+ }
38
+ function detectClients() {
39
+ const list = [];
40
+ const cdPath = claudeDesktopConfigPath();
41
+ list.push({
42
+ id: "claude-desktop",
43
+ label: "Claude Desktop",
44
+ detected: cdPath !== null && (existsSync(cdPath) || existsSync(dirname(cdPath))),
45
+ configPath: cdPath ?? "(unsupported on this OS)"
46
+ });
47
+ const ccDir = claudeCodeDir();
48
+ list.push({
49
+ id: "claude-code",
50
+ label: "Claude Code",
51
+ detected: existsSync(ccDir),
52
+ configPath: ccDir
53
+ });
54
+ const codexPath = codexConfigPath();
55
+ list.push({
56
+ id: "codex",
57
+ label: "Codex Desktop",
58
+ detected: existsSync(codexPath) || existsSync(dirname(codexPath)),
59
+ configPath: codexPath
60
+ });
61
+ return list;
62
+ }
63
+ async function prompt(question, defaultYes = true) {
64
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
65
+ const hint = defaultYes ? "Y/n" : "y/N";
66
+ const answer = (await rl.question(`${question} [${hint}] `)).trim().toLowerCase();
67
+ rl.close();
68
+ if (answer === "") return defaultYes;
69
+ return answer.startsWith("y");
70
+ }
71
+ async function installClaudeDesktop() {
72
+ const configPath = claudeDesktopConfigPath();
73
+ if (!configPath) return "skipped (OS not supported)";
74
+ let config = {};
75
+ try {
76
+ const raw = await readFile(configPath, "utf-8");
77
+ config = JSON.parse(raw);
78
+ } catch {
79
+ }
80
+ const mcpServers = config.mcpServers ?? {};
81
+ if (mcpServers.plaud) return "already configured";
82
+ config.mcpServers = { ...mcpServers, plaud: getMcpEntry() };
83
+ await mkdir(dirname(configPath), { recursive: true });
84
+ await writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
85
+ return "configured. Restart Claude Desktop to load the Plaud MCP.";
86
+ }
87
+ async function installClaudeCode() {
88
+ await writeSkillsToClaudeCode();
89
+ const { command, args } = getMcpEntry();
90
+ const manualCmd = `claude mcp add --scope user plaud ${command} ${args.join(" ")}`;
91
+ const cliCheck = spawnSync("which", ["claude"], { encoding: "utf-8" });
92
+ if (cliCheck.status !== 0) {
93
+ return `skills written to ~/.claude/CLAUDE.md. Claude Code CLI not on PATH \u2014 once installed, run:
94
+ ${manualCmd}`;
95
+ }
96
+ const existing = spawnSync("claude", ["mcp", "get", "plaud"], { encoding: "utf-8" });
97
+ if (existing.status === 0) {
98
+ return "skills written to ~/.claude/CLAUDE.md. MCP already registered (scope: user).";
99
+ }
100
+ const register = spawnSync(
101
+ "claude",
102
+ ["mcp", "add", "--scope", "user", "plaud", command, ...args],
103
+ { encoding: "utf-8" }
104
+ );
105
+ if (register.status !== 0) {
106
+ const err = (register.stderr || register.stdout || "").trim();
107
+ return `skills written to ~/.claude/CLAUDE.md. Failed to auto-register MCP (${err || "unknown"}). Run manually:
108
+ ${manualCmd}`;
109
+ }
110
+ return "skills written to ~/.claude/CLAUDE.md and MCP registered at user scope.";
111
+ }
112
+ async function installCodex() {
113
+ const configPath = codexConfigPath();
114
+ const { command, args } = getMcpEntry();
115
+ const argsStr = args.map((a) => `"${a}"`).join(", ");
116
+ const entry = `
117
+ [mcp_servers.plaud]
118
+ command = "${command}"
119
+ args = [${argsStr}]
120
+ `;
121
+ let content = "";
122
+ try {
123
+ content = await readFile(configPath, "utf-8");
124
+ } catch {
125
+ }
126
+ if (content.includes("[mcp_servers.plaud]")) return "already configured";
127
+ await mkdir(dirname(configPath), { recursive: true });
128
+ await writeFile(configPath, content + entry, "utf-8");
129
+ await writeSkillsToClaudeCode();
130
+ const combined = await skillsCombined();
131
+ const copied = copyToClipboard(combined);
132
+ return copied ? "configured. Skills copied to clipboard \u2014 paste into Codex custom instructions, then restart." : "configured. Paste the Plaud skills into Codex custom instructions manually (see docs).";
133
+ }
134
+ var CALLBACK_PORT = 8199;
135
+ var LOGIN_TIMEOUT_MS = 12e4;
136
+ function pickIdentity(user) {
137
+ for (const k of ["email", "username", "name", "id"]) {
138
+ const v = user[k];
139
+ if (typeof v === "string" && v.length > 0) return v;
140
+ }
141
+ return void 0;
142
+ }
143
+ async function runLogin() {
144
+ const client = getClient();
145
+ try {
146
+ const existing = await client.auth.getAccessToken();
147
+ if (existing) {
148
+ try {
149
+ const user = await client.getCurrentUser();
150
+ return { status: "already-authed", who: pickIdentity(user) };
151
+ } catch {
152
+ return { status: "already-authed" };
153
+ }
154
+ }
155
+ } catch {
156
+ await client.auth.logout().catch(() => void 0);
157
+ }
158
+ const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
159
+ return new Promise((resolve) => {
160
+ const httpServer = createServer(async (req, res) => {
161
+ const reqUrl = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
162
+ if (reqUrl.pathname !== "/auth/callback") {
163
+ res.writeHead(404);
164
+ res.end();
165
+ return;
166
+ }
167
+ const code = reqUrl.searchParams.get("code");
168
+ if (!code) {
169
+ res.writeHead(400);
170
+ res.end("Missing code");
171
+ cleanup();
172
+ resolve({ status: "failed", message: "missing authorization code in callback" });
173
+ return;
174
+ }
175
+ try {
176
+ await client.auth.exchangeCode(code, codeVerifier, state);
177
+ res.writeHead(200, { "Content-Type": "text/html" });
178
+ res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
179
+ cleanup();
180
+ let who;
181
+ try {
182
+ const user = await client.getCurrentUser();
183
+ who = pickIdentity(user);
184
+ } catch {
185
+ }
186
+ resolve({ status: "success", who });
187
+ } catch (err) {
188
+ res.writeHead(500, { "Content-Type": "text/html" });
189
+ res.end("<h1>Token exchange failed</h1>");
190
+ cleanup();
191
+ resolve({ status: "failed", message: err instanceof Error ? err.message : String(err) });
192
+ }
193
+ });
194
+ let timeoutId;
195
+ function cleanup() {
196
+ clearTimeout(timeoutId);
197
+ httpServer.closeAllConnections?.();
198
+ httpServer.close();
199
+ }
200
+ timeoutId = setTimeout(() => {
201
+ cleanup();
202
+ resolve({ status: "timeout" });
203
+ }, LOGIN_TIMEOUT_MS);
204
+ httpServer.listen(CALLBACK_PORT, () => {
205
+ open(url).catch(() => {
206
+ cleanup();
207
+ resolve({ status: "failed", message: `could not open browser. Visit manually: ${url}` });
208
+ });
209
+ });
210
+ httpServer.on("error", (err) => {
211
+ cleanup();
212
+ resolve({ status: "failed", message: `callback server error: ${err.message}` });
213
+ });
214
+ });
215
+ }
216
+ async function runInstall(opts = {}) {
217
+ console.log("Plaud MCP installer\n");
218
+ const clients = detectClients();
219
+ console.log("Detected AI clients:");
220
+ for (const c of clients) {
221
+ const mark = c.detected ? "\u2713" : "\xB7";
222
+ console.log(` ${mark} ${c.label.padEnd(16)} ${c.detected ? c.configPath : "(not detected)"}`);
223
+ }
224
+ console.log();
225
+ const selected = [];
226
+ if (opts.yes) {
227
+ for (const c of clients) {
228
+ if (c.detected) selected.push(c);
229
+ }
230
+ if (selected.length > 0) {
231
+ console.log(`--yes: configuring ${selected.map((c) => c.label).join(", ")} without prompting.
232
+ `);
233
+ }
234
+ } else {
235
+ for (const c of clients) {
236
+ if (!c.detected) continue;
237
+ const yes = await prompt(`Configure ${c.label}?`, true);
238
+ if (yes) selected.push(c);
239
+ }
240
+ }
241
+ if (selected.length === 0) {
242
+ console.log("\nNothing to configure. Exiting.");
243
+ process.exit(0);
244
+ }
245
+ console.log();
246
+ const succeeded = [];
247
+ for (const c of selected) {
248
+ process.stdout.write(`\u2192 ${c.label}... `);
249
+ try {
250
+ let result = "";
251
+ if (c.id === "claude-desktop") result = await installClaudeDesktop();
252
+ else if (c.id === "claude-code") result = await installClaudeCode();
253
+ else if (c.id === "codex") result = await installCodex();
254
+ console.log(result);
255
+ if (!/^(skipped|failed)/i.test(result)) succeeded.push(c);
256
+ } catch (err) {
257
+ console.log(`failed: ${err instanceof Error ? err.message : String(err)}`);
258
+ }
259
+ }
260
+ const loginOutcome = opts.noLogin ? null : await doLoginStep(Boolean(opts.yes));
261
+ printNextSteps(succeeded, loginOutcome);
262
+ process.exit(0);
263
+ }
264
+ async function doLoginStep(nonInteractive) {
265
+ console.log();
266
+ console.log("\u2192 Authenticating with Plaud...");
267
+ const client = getClient();
268
+ try {
269
+ const existing = await client.auth.getAccessToken();
270
+ if (existing) {
271
+ try {
272
+ const user = await client.getCurrentUser();
273
+ const who = pickIdentity(user);
274
+ console.log(` already signed in${who ? ` as ${who}` : ""} \u2014 skipping OAuth.`);
275
+ return { status: "already-authed", who };
276
+ } catch {
277
+ console.log(" token present but user lookup failed \u2014 will re-auth.");
278
+ }
279
+ }
280
+ } catch {
281
+ }
282
+ if (!nonInteractive) {
283
+ const yes = await prompt("Log in to Plaud now? (opens browser)", true);
284
+ if (!yes) {
285
+ console.log(" skipped \u2014 you'll be prompted on first tool call after restart.");
286
+ return null;
287
+ }
288
+ }
289
+ console.log(" opening browser \u2014 click Authorize to finish.");
290
+ const outcome = await runLogin();
291
+ switch (outcome.status) {
292
+ case "already-authed":
293
+ console.log(` already signed in${outcome.who ? ` as ${outcome.who}` : ""}.`);
294
+ break;
295
+ case "success":
296
+ console.log(` \u2713 authenticated${outcome.who ? ` as ${outcome.who}` : ""}.`);
297
+ break;
298
+ case "timeout":
299
+ console.log(" \u2717 timed out after 2 minutes \u2014 you can retry with `plaud-mcp install --yes` or run `login` from your AI client.");
300
+ break;
301
+ case "failed":
302
+ console.log(` \u2717 failed: ${outcome.message}`);
303
+ break;
304
+ }
305
+ return outcome;
306
+ }
307
+ function restartLine(id) {
308
+ switch (id) {
309
+ case "claude-desktop":
310
+ return "Fully quit Claude Desktop (\u2318Q on macOS) and reopen it \u2014 closing the window is not enough.";
311
+ case "claude-code":
312
+ return "Exit any running Claude Code session and start a new `claude` session.";
313
+ case "codex":
314
+ return "Quit Codex Desktop and reopen it.";
315
+ }
316
+ }
317
+ function printNextSteps(clients, login) {
318
+ console.log();
319
+ if (clients.length === 0) {
320
+ console.log("No clients were configured. Nothing to do.");
321
+ return;
322
+ }
323
+ const authed = login?.status === "already-authed" || login?.status === "success";
324
+ const bar = "\u2500".repeat(60);
325
+ console.log(bar);
326
+ console.log(`\u2713 Plaud MCP installed for: ${clients.map((c) => c.label).join(", ")}`);
327
+ if (authed) {
328
+ console.log(`\u2713 Authenticated${login?.who ? ` as ${login.who}` : ""}`);
329
+ }
330
+ console.log(bar);
331
+ console.log();
332
+ if (authed) {
333
+ console.log("NEXT \u2014 one thing left:");
334
+ console.log();
335
+ if (clients.length === 1) {
336
+ console.log(` ${restartLine(clients[0].id)}`);
337
+ } else {
338
+ console.log(" Restart each configured client:");
339
+ for (const c of clients) {
340
+ console.log(` \u2022 ${c.label}: ${restartLine(c.id)}`);
341
+ }
342
+ }
343
+ console.log();
344
+ console.log(" After restart, Plaud tools are live \u2014 just ask your AI client");
345
+ console.log(' about your recordings (e.g. "list my recent Plaud recordings").');
346
+ } else {
347
+ console.log("NEXT \u2014 do these two things:");
348
+ console.log();
349
+ if (clients.length === 1) {
350
+ console.log(` 1. ${restartLine(clients[0].id)}`);
351
+ } else {
352
+ console.log(" 1. Restart each configured client:");
353
+ for (const c of clients) {
354
+ console.log(` \u2022 ${c.label}: ${restartLine(c.id)}`);
355
+ }
356
+ }
357
+ console.log();
358
+ console.log(" 2. In any chat with your AI client, type:");
359
+ console.log();
360
+ console.log(" list my recent Plaud recordings");
361
+ console.log();
362
+ console.log(" This triggers OAuth in your browser (once). Tokens are then");
363
+ console.log(" stored in ~/.plaud/tokens.json and refreshed automatically.");
364
+ }
365
+ console.log();
366
+ console.log(bar);
367
+ }
368
+ export {
369
+ runInstall
370
+ };
@@ -1,8 +1,10 @@
1
1
  import {
2
- PlaudClient,
3
2
  logger,
4
3
  registerTools
5
- } from "./chunk-YMQLKZLW.js";
4
+ } from "./chunk-MPCF6HMK.js";
5
+ import {
6
+ PlaudClient
7
+ } from "./chunk-SNSGVRCU.js";
6
8
 
7
9
  // src/http/server.ts
8
10
  import express from "express";
@@ -184,7 +186,7 @@ var HTTP_PORT = Number(process.env.PLAUD_HTTP_PORT ?? 3e3);
184
186
  var HTTP_HOST = process.env.PLAUD_HTTP_HOST ?? "0.0.0.0";
185
187
  var CALLBACK_PORT = 8199;
186
188
  var CALLBACK_PATH = "/auth/callback";
187
- var CALLBACK_URL = process.env.PLAUD_CALLBACK_URL ?? `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
189
+ var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
188
190
  function startHttpServer() {
189
191
  const clientId = process.env.PLAUD_MCP_CLIENT_ID ?? process.env.PLAUD_CLIENT_ID ?? "client_9c501dad-8a0d-40b2-a7b0-d1cb8787f674";
190
192
  const clientSecret = process.env.PLAUD_CLIENT_SECRET ?? "";
@@ -203,18 +205,6 @@ function startHttpServer() {
203
205
  app.get("/health", (_req, res) => {
204
206
  res.json({ status: "ok", uptime_s: Math.floor(process.uptime()) });
205
207
  });
206
- if (process.env.PLAUD_CALLBACK_URL) {
207
- app.get(CALLBACK_PATH, (req, res) => {
208
- const code = req.query["code"];
209
- const state = req.query["state"];
210
- if (!code || !state) {
211
- logger.warn({ event: "oauth_callback_invalid", code: !!code, state: !!state });
212
- res.status(400).send("Missing code or state");
213
- return;
214
- }
215
- provider.handleCallback(code, state, res);
216
- });
217
- }
218
208
  app.use((req, res, next) => {
219
209
  const reqId = req.headers["x-request-id"] ?? randomUUID2();
220
210
  res.locals["reqId"] = reqId;
@@ -1,58 +1,20 @@
1
1
  import {
2
- SKILLS_COMBINED
3
- } from "./chunk-7PYQFJJW.js";
2
+ copyToClipboard,
3
+ getMcpEntry,
4
+ removeSkillsFromClaudeCode,
5
+ writeSkillsToClaudeCode
6
+ } from "./chunk-UPEENHCG.js";
7
+ import {
8
+ skillsCombined
9
+ } from "./chunk-4QBEOJPX.js";
4
10
 
5
11
  // src/setup.ts
6
12
  import { readFile, writeFile, mkdir, rm } from "fs/promises";
7
13
  import { join, dirname } from "path";
8
- import { homedir, platform } from "os";
9
- import { fileURLToPath } from "url";
10
- import { spawnSync } from "child_process";
11
- var SKILLS_MARKER_START = "<!-- plaud-skills:start -->";
12
- var SKILLS_MARKER_END = "<!-- plaud-skills:end -->";
13
- var SKILLS_BLOCK = `${SKILLS_MARKER_START}
14
- ${SKILLS_COMBINED}
15
- ${SKILLS_MARKER_END}`;
16
- async function removeSkillsFromClaudeCode() {
17
- const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
18
- let existing = "";
19
- try {
20
- existing = await readFile(claudeMdPath, "utf-8");
21
- } catch {
22
- return;
23
- }
24
- if (!existing.includes(SKILLS_MARKER_START)) {
25
- return;
26
- }
27
- const updated = existing.replace(new RegExp(`\\n?${SKILLS_MARKER_START}[\\s\\S]*?${SKILLS_MARKER_END}\\n?`), "").trimEnd();
28
- await writeFile(claudeMdPath, updated ? updated + "\n" : "", "utf-8");
29
- }
30
- async function writeSkillsToClaudeCode() {
31
- const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
32
- let existing = "";
33
- try {
34
- existing = await readFile(claudeMdPath, "utf-8");
35
- } catch {
36
- }
37
- if (existing.includes(SKILLS_MARKER_START)) {
38
- const updated = existing.replace(
39
- new RegExp(`${SKILLS_MARKER_START}[\\s\\S]*?${SKILLS_MARKER_END}`),
40
- SKILLS_BLOCK
41
- );
42
- await mkdir(dirname(claudeMdPath), { recursive: true });
43
- await writeFile(claudeMdPath, updated, "utf-8");
44
- } else {
45
- await mkdir(dirname(claudeMdPath), { recursive: true });
46
- await writeFile(claudeMdPath, existing + (existing.endsWith("\n") ? "" : "\n") + SKILLS_BLOCK + "\n", "utf-8");
47
- }
48
- }
49
- function copyToClipboard(content) {
50
- const cmd = platform() === "win32" ? "clip" : "pbcopy";
51
- const result = spawnSync(cmd, [], { input: content });
52
- return result.status === 0;
53
- }
54
- function printSkillsPasteGuide() {
55
- const copied = copyToClipboard(SKILLS_COMBINED);
14
+ import { homedir } from "os";
15
+ async function printSkillsPasteGuide() {
16
+ const combined = await skillsCombined();
17
+ const copied = copyToClipboard(combined);
56
18
  console.log("");
57
19
  if (copied) {
58
20
  console.log("Plaud Skills copied to clipboard.");
@@ -63,13 +25,6 @@ function printSkillsPasteGuide() {
63
25
  console.log(" Claude Desktop: Settings \u2192 Profile \u2192 Custom Instructions");
64
26
  console.log(" Codex Desktop: refer to client documentation");
65
27
  }
66
- function getMcpEntry() {
67
- const __dirname = dirname(fileURLToPath(import.meta.url));
68
- return {
69
- command: process.execPath,
70
- args: [join(__dirname, "index.js")]
71
- };
72
- }
73
28
  function getConfigPath() {
74
29
  if (process.platform === "darwin") {
75
30
  return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
@@ -105,10 +60,11 @@ async function runCleanPlugin() {
105
60
  async function runSetupCodex() {
106
61
  const configPath = join(homedir(), ".codex", "config.toml");
107
62
  const { command, args } = getMcpEntry();
63
+ const argsStr = args.map((a) => `"${a}"`).join(", ");
108
64
  const entry = `
109
65
  [mcp_servers.plaud]
110
66
  command = "${command}"
111
- args = ["${args[0]}"]
67
+ args = [${argsStr}]
112
68
  `;
113
69
  let content = "";
114
70
  try {
@@ -126,7 +82,7 @@ args = ["${args[0]}"]
126
82
  console.log("Please restart Codex Desktop to complete the setup.");
127
83
  await writeSkillsToClaudeCode();
128
84
  console.log("Plaud Skills have been added to ~/.claude/CLAUDE.md for Claude Code.");
129
- printSkillsPasteGuide();
85
+ await printSkillsPasteGuide();
130
86
  process.exit(0);
131
87
  }
132
88
  async function runUnsetupCodex() {
@@ -142,7 +98,7 @@ async function runUnsetupCodex() {
142
98
  console.log("Plaud is not configured in Codex Desktop. Nothing to remove.");
143
99
  return;
144
100
  }
145
- const cleaned = content.replace(/\n*\[mcp_servers\.plaud\]\n(?:(?!\[)[^\n]*\n)*/g, "");
101
+ const cleaned = content.replace(/\[mcp_servers\.plaud\]\n(?:(?!\[)[^\n]*\n)*/g, "").replace(/\n{3,}/g, "\n\n");
146
102
  await writeFile(configPath, cleaned, "utf-8");
147
103
  await removeSkillsFromClaudeCode();
148
104
  console.log("Plaud has been removed from Codex Desktop.");
@@ -201,7 +157,7 @@ async function runSetup() {
201
157
  console.log("Please restart Claude Desktop to complete the setup.");
202
158
  await writeSkillsToClaudeCode();
203
159
  console.log("Plaud Skills have been added to ~/.claude/CLAUDE.md for Claude Code.");
204
- printSkillsPasteGuide();
160
+ await printSkillsPasteGuide();
205
161
  process.exit(0);
206
162
  }
207
163
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaud-ai/mcp",
3
- "version": "0.1.58",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -12,12 +12,9 @@
12
12
  ".mcp.json",
13
13
  "skills"
14
14
  ],
15
- "scripts": {
16
- "version:show": "node -p \"require('./package.json').version\"",
17
- "build": "tsup",
18
- "dev": "tsup --watch",
19
- "clean": "rm -rf dist",
20
- "prepublishOnly": "node -e \"const fs=require('fs'),v=require('./package.json').version,p=JSON.parse(fs.readFileSync('./plugin.json','utf8'));p.version=v;fs.writeFileSync('./plugin.json',JSON.stringify(p,null,2)+'\\n');\""
15
+ "publishConfig": {
16
+ "registry": "https://registry.npmjs.org/",
17
+ "access": "public"
21
18
  },
22
19
  "dependencies": {
23
20
  "@modelcontextprotocol/sdk": "^1.12.0",
@@ -27,9 +24,16 @@
27
24
  "zod": "^4.3.6"
28
25
  },
29
26
  "devDependencies": {
30
- "@plaud-ai/shared": "workspace:*",
31
27
  "@types/express": "^5.0.6",
32
28
  "@types/node": "^25.5.0",
33
- "typescript": "^5.7.0"
29
+ "typescript": "^5.7.0",
30
+ "@plaud-ai/shared": "0.1.0"
31
+ },
32
+ "scripts": {
33
+ "version:show": "node -p \"require('./package.json').version\"",
34
+ "prebuild": "node ../../scripts/sync-skills.mjs",
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "clean": "rm -rf dist skills"
34
38
  }
35
- }
39
+ }
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plaud",
3
- "version": "0.1.58",
3
+ "version": "0.2.0",
4
4
  "description": "Access your Plaud recordings in Claude",
5
5
  "author": {
6
6
  "name": "Plaud AI"
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: plaud-browse
3
+ version: 1.0.0
4
+ description: "Browse, list, or paginate through Plaud recordings. Use when the user says 'what recordings do I have', 'show my recent recordings', 'list my recordings', or asks to see the most recent uploads."
5
+ metadata:
6
+ requires:
7
+ bins: []
8
+ ---
9
+
10
+ # plaud-browse
11
+
12
+ **Read [`plaud-shared`](../plaud-shared/SKILL.md) first** for auth and output conventions.
13
+
14
+ ## When to use
15
+
16
+ - User wants to see what is in their library without a specific target in mind.
17
+ - User explicitly asks for a page, or says "next page", "more results".
18
+ - User asks "what's the most recent recording" — fetch page 1 and return the top item.
19
+
20
+ ## Steps
21
+
22
+ 1. Call `list_files` with `page=1` and `page_size=20` (default). No `query` / `date_from` / `date_to` unless the user said something that matches `plaud-find`.
23
+ 2. Present results in a compact table: **ID**, **NAME**, **DATE** (`YYYY-MM-DD`), **DURATION** (`5m23s` style).
24
+ 3. If the page looks like the whole library (fewer than `page_size` returned), tell the user there is no next page.
25
+ 4. If the user asks for more, increment `page` by 1 and call again.
26
+
27
+ ## Anti-patterns
28
+
29
+ - Do **not** fetch every page eagerly; pagination is lazy.
30
+ - Do **not** call `get_note` or `get_transcript` during a browse — that belongs to `plaud-read` and burns tokens.
31
+ - Do **not** expose raw timestamps or durations in milliseconds.
32
+
33
+ ## Example
34
+
35
+ User: "show me my recordings"
36
+
37
+ Agent:
38
+ - `list_files(page=1, page_size=20)`
39
+ - Render table, mention "page 1, say 'next page' for more"
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: plaud-digest
3
+ version: 1.0.0
4
+ description: "Summarize multiple Plaud recordings into a digest. Use when the user says 'weekly report', 'digest of this month', 'what meetings did I have this week', 'recap of last quarter', or asks to roll up multiple recordings into one overview."
5
+ metadata:
6
+ requires:
7
+ bins: []
8
+ ---
9
+
10
+ # plaud-digest
11
+
12
+ **Read [`plaud-shared`](../plaud-shared/SKILL.md) first.**
13
+
14
+ ## When to use
15
+
16
+ - User asks for a roll-up across multiple recordings.
17
+ - Time window is explicit ("this week") or implicit ("recap of recent meetings").
18
+ - Scope is "what happened", not "find one specific meeting" (that's `plaud-find`).
19
+
20
+ ## Steps
21
+
22
+ 1. **Resolve the window.** Use the date interpretation table in `plaud-find` for relative phrases.
23
+ 2. **List the corpus.** `list_files` with `date_from` / `date_to`. Cap at 50 recordings — if the window returns more, ask the user to narrow it.
24
+ 3. **Fetch notes in batch.** For each recording, call `get_note`. Do **not** call `get_transcript` unless a specific recording merits a deeper pull.
25
+ 4. **Synthesize.** Produce a structured digest:
26
+ - **Headline** — one-line theme of the window.
27
+ - **By recording** — one bullet per recording: `• name (date, duration) — one-sentence takeaway`.
28
+ - **Recurring themes** — topics that appeared in ≥ 2 recordings.
29
+ - **Open action items** — aggregated across recordings, deduplicated.
30
+ 5. **Cite sources.** Every non-trivial claim must reference the recording it came from, using the file name (not the raw ID unless the user asked).
31
+
32
+ ## Budget
33
+
34
+ - Hard cap: 50 `get_note` calls per digest. If the window has more recordings, compress or ask user to narrow.
35
+ - Skip recordings where `note_list` is empty — mention them at the end under "unsummarized".
36
+
37
+ ## Anti-patterns
38
+
39
+ - Do not load transcripts just to pad the digest.
40
+ - Do not synthesize across windows the user didn't ask for ("while we're at it, here's last month too").
41
+ - Do not invent action items that aren't in the notes — only aggregate what's there.