@plaud-ai/mcp 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MPCF6HMK.js → chunk-IZKXHQM3.js} +28 -18
- package/dist/{chunk-K22WPZ7P.js → chunk-QT25MKZJ.js} +17 -7
- package/dist/index.js +17 -13
- package/dist/install-EEQXUUG3.js +657 -0
- package/dist/{server-D3H6AMVD.js → server-QWAZPBHU.js} +20 -21
- package/dist/{setup-QRUDA7ES.js → setup-QXZHVVDP.js} +1 -1
- package/package.json +11 -12
- package/plugin.json +1 -1
- package/dist/install-I2N3DJG4.js +0 -384
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getClient
|
|
3
|
+
} from "./chunk-7KGB7GSZ.js";
|
|
4
|
+
import {
|
|
5
|
+
copyToClipboard,
|
|
6
|
+
getMcpEntry,
|
|
7
|
+
writeSkillsToClaudeCode
|
|
8
|
+
} from "./chunk-QT25MKZJ.js";
|
|
9
|
+
import {
|
|
10
|
+
skillsCombined
|
|
11
|
+
} from "./chunk-4QBEOJPX.js";
|
|
12
|
+
import "./chunk-SNSGVRCU.js";
|
|
13
|
+
|
|
14
|
+
// src/install.ts
|
|
15
|
+
import { createInterface } from "readline/promises";
|
|
16
|
+
import { createServer } from "http";
|
|
17
|
+
import open from "open";
|
|
18
|
+
|
|
19
|
+
// src/installers/registry.ts
|
|
20
|
+
import { homedir as homedir4, platform as platform2 } from "os";
|
|
21
|
+
import { join as join4 } from "path";
|
|
22
|
+
|
|
23
|
+
// src/installers/claude-code.ts
|
|
24
|
+
import { existsSync } from "fs";
|
|
25
|
+
import { spawnSync } from "child_process";
|
|
26
|
+
import { homedir, platform } from "os";
|
|
27
|
+
import { join } from "path";
|
|
28
|
+
var RESTART_HINT = "Exit any running Claude Code session and start a new `claude` session so MCP and Plaud skills load.";
|
|
29
|
+
function claudeCodeDir() {
|
|
30
|
+
return join(homedir(), ".claude");
|
|
31
|
+
}
|
|
32
|
+
function commandExists(command) {
|
|
33
|
+
const lookup = platform() === "win32" ? "where" : "which";
|
|
34
|
+
return spawnSync(lookup, [command], { encoding: "utf-8" }).status === 0;
|
|
35
|
+
}
|
|
36
|
+
function claudeCodeAdapter() {
|
|
37
|
+
return {
|
|
38
|
+
id: "claude-code",
|
|
39
|
+
label: "Claude Code",
|
|
40
|
+
detect() {
|
|
41
|
+
const configPath = claudeCodeDir();
|
|
42
|
+
return { detected: existsSync(configPath), configPath };
|
|
43
|
+
},
|
|
44
|
+
async install({ entry }) {
|
|
45
|
+
const serverCommand = [entry.command, ...entry.args].join(" ");
|
|
46
|
+
const manualCmd = `claude mcp add --scope user plaud -- ${serverCommand}`;
|
|
47
|
+
if (!commandExists("claude")) {
|
|
48
|
+
return {
|
|
49
|
+
status: "failed",
|
|
50
|
+
message: `failed: Claude Code CLI not on PATH. Run manually once Claude Code CLI is available:
|
|
51
|
+
${manualCmd}`,
|
|
52
|
+
manualCmd
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
spawnSync("claude", ["mcp", "remove", "plaud", "--scope", "user"], { encoding: "utf-8" });
|
|
56
|
+
const register = spawnSync(
|
|
57
|
+
"claude",
|
|
58
|
+
["mcp", "add", "--scope", "user", "plaud", "--", entry.command, ...entry.args],
|
|
59
|
+
{ encoding: "utf-8" }
|
|
60
|
+
);
|
|
61
|
+
if (register.status !== 0) {
|
|
62
|
+
const err = (register.stderr || register.stdout || "").trim();
|
|
63
|
+
return {
|
|
64
|
+
status: "failed",
|
|
65
|
+
message: `failed: auto-registering MCP failed (${err || "unknown"}). Run manually:
|
|
66
|
+
${manualCmd}`,
|
|
67
|
+
manualCmd
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
status: "configured",
|
|
72
|
+
message: "MCP registered at user scope.",
|
|
73
|
+
restartHint: RESTART_HINT
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/installers/codex.ts
|
|
80
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
81
|
+
import { existsSync as existsSync2 } from "fs";
|
|
82
|
+
import { homedir as homedir2 } from "os";
|
|
83
|
+
import { dirname, join as join2 } from "path";
|
|
84
|
+
var RESTART_HINT2 = "Quit Codex Desktop and reopen it.";
|
|
85
|
+
function codexConfigPath() {
|
|
86
|
+
return join2(homedir2(), ".codex", "config.toml");
|
|
87
|
+
}
|
|
88
|
+
function quoteTomlString(value) {
|
|
89
|
+
return JSON.stringify(value);
|
|
90
|
+
}
|
|
91
|
+
function codexAdapter() {
|
|
92
|
+
return {
|
|
93
|
+
id: "codex",
|
|
94
|
+
label: "Codex Desktop",
|
|
95
|
+
detect() {
|
|
96
|
+
const configPath = codexConfigPath();
|
|
97
|
+
return { detected: existsSync2(configPath) || existsSync2(dirname(configPath)), configPath };
|
|
98
|
+
},
|
|
99
|
+
async install({ entry }) {
|
|
100
|
+
const configPath = codexConfigPath();
|
|
101
|
+
const argsStr = entry.args.map(quoteTomlString).join(", ");
|
|
102
|
+
const serverEntry = `
|
|
103
|
+
[mcp_servers.plaud]
|
|
104
|
+
command = ${quoteTomlString(entry.command)}
|
|
105
|
+
args = [${argsStr}]
|
|
106
|
+
`;
|
|
107
|
+
let content = "";
|
|
108
|
+
try {
|
|
109
|
+
content = await readFile(configPath, "utf-8");
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
if (content.includes("[mcp_servers.plaud]")) {
|
|
113
|
+
return {
|
|
114
|
+
status: "already-configured",
|
|
115
|
+
message: "already configured",
|
|
116
|
+
restartHint: RESTART_HINT2
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
120
|
+
await writeFile(configPath, content + serverEntry, "utf-8");
|
|
121
|
+
const combined = await skillsCombined();
|
|
122
|
+
const copied = copyToClipboard(combined);
|
|
123
|
+
return {
|
|
124
|
+
status: "configured",
|
|
125
|
+
message: 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).",
|
|
126
|
+
restartHint: RESTART_HINT2
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/installers/json-mcp-servers.ts
|
|
133
|
+
import { existsSync as existsSync3 } from "fs";
|
|
134
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
135
|
+
import { dirname as dirname2 } from "path";
|
|
136
|
+
function isJsonObject(value) {
|
|
137
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
138
|
+
}
|
|
139
|
+
function readErrorCode(err) {
|
|
140
|
+
return isJsonObject(err) && typeof err.code === "string" ? err.code : void 0;
|
|
141
|
+
}
|
|
142
|
+
function jsonMcpServersAdapter(spec) {
|
|
143
|
+
const rootKey = spec.rootKey ?? "mcpServers";
|
|
144
|
+
const serverKey = spec.serverKey ?? "plaud";
|
|
145
|
+
const toEntry = spec.entry ?? ((entry) => entry);
|
|
146
|
+
return {
|
|
147
|
+
id: spec.id,
|
|
148
|
+
label: spec.label,
|
|
149
|
+
detect() {
|
|
150
|
+
const configPath = spec.configPath();
|
|
151
|
+
return {
|
|
152
|
+
detected: configPath !== null && (existsSync3(configPath) || existsSync3(dirname2(configPath))),
|
|
153
|
+
configPath: configPath ?? "(unsupported on this OS)"
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
async install(ctx) {
|
|
157
|
+
const configPath = spec.configPath();
|
|
158
|
+
if (!configPath) {
|
|
159
|
+
return { status: "skipped", message: spec.unsupportedMessage ?? "skipped (OS not supported)" };
|
|
160
|
+
}
|
|
161
|
+
let config = {};
|
|
162
|
+
try {
|
|
163
|
+
const raw = await readFile2(configPath, "utf-8");
|
|
164
|
+
const parsed = JSON.parse(raw);
|
|
165
|
+
if (!isJsonObject(parsed)) {
|
|
166
|
+
return { status: "failed", message: `failed: ${configPath} must contain a JSON object.` };
|
|
167
|
+
}
|
|
168
|
+
config = parsed;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (readErrorCode(err) !== "ENOENT") {
|
|
171
|
+
return { status: "failed", message: `failed: ${configPath} contains invalid JSON. Fix it and retry.` };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const existingServers = config[rootKey];
|
|
175
|
+
if (existingServers !== void 0 && !isJsonObject(existingServers)) {
|
|
176
|
+
return { status: "failed", message: `failed: ${configPath} field ${rootKey} must be a JSON object.` };
|
|
177
|
+
}
|
|
178
|
+
const servers = existingServers ?? {};
|
|
179
|
+
if (servers[serverKey]) {
|
|
180
|
+
return {
|
|
181
|
+
status: "already-configured",
|
|
182
|
+
message: "already configured",
|
|
183
|
+
restartHint: spec.restartHint
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
config[rootKey] = { ...servers, [serverKey]: toEntry(ctx.entry) };
|
|
187
|
+
await mkdir2(dirname2(configPath), { recursive: true });
|
|
188
|
+
await writeFile2(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
189
|
+
return {
|
|
190
|
+
status: "configured",
|
|
191
|
+
message: spec.configuredMessage ?? "configured",
|
|
192
|
+
restartHint: spec.restartHint
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/installers/web-client.ts
|
|
199
|
+
function remoteMcpUrl() {
|
|
200
|
+
const explicit = process.env.PLAUD_REMOTE_MCP_URL?.trim();
|
|
201
|
+
if (explicit) return explicit;
|
|
202
|
+
const serverUrl = process.env.PLAUD_SERVER_URL?.trim();
|
|
203
|
+
if (!serverUrl) return null;
|
|
204
|
+
return /\/(mcp|sse)\/?$/.test(serverUrl) ? serverUrl : `${serverUrl.replace(/\/+$/, "")}/mcp`;
|
|
205
|
+
}
|
|
206
|
+
function webClientAdapter(spec) {
|
|
207
|
+
return {
|
|
208
|
+
id: spec.id,
|
|
209
|
+
label: spec.label,
|
|
210
|
+
manualOnly: true,
|
|
211
|
+
detect() {
|
|
212
|
+
return { detected: true, configPath: "remote connector (manual setup)" };
|
|
213
|
+
},
|
|
214
|
+
async install() {
|
|
215
|
+
const url = remoteMcpUrl();
|
|
216
|
+
const remoteLine = url ? `Remote MCP URL: ${url}` : "Remote MCP URL: not configured. Deploy `plaud-mcp http` behind HTTPS, then rerun with PLAUD_REMOTE_MCP_URL=https://your-host.example/mcp.";
|
|
217
|
+
return {
|
|
218
|
+
status: "configured",
|
|
219
|
+
requiresLocalAuth: false,
|
|
220
|
+
restartHint: `Finish setup in ${spec.label}; no local restart is needed.`,
|
|
221
|
+
message: [
|
|
222
|
+
"manual setup only:",
|
|
223
|
+
` Open: ${spec.setupUrl}`,
|
|
224
|
+
` ${remoteLine}`,
|
|
225
|
+
` Step: ${spec.setupStep}`
|
|
226
|
+
].join("\n")
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/installers/zed.ts
|
|
233
|
+
import { existsSync as existsSync4 } from "fs";
|
|
234
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
235
|
+
import { homedir as homedir3 } from "os";
|
|
236
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
237
|
+
var RESTART_HINT3 = "Restart Zed to load the Plaud MCP.";
|
|
238
|
+
function zedConfigPath() {
|
|
239
|
+
return join3(homedir3(), ".config", "zed", "settings.json");
|
|
240
|
+
}
|
|
241
|
+
function isJsonObject2(value) {
|
|
242
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
243
|
+
}
|
|
244
|
+
function readErrorCode2(err) {
|
|
245
|
+
return isJsonObject2(err) && typeof err.code === "string" ? err.code : void 0;
|
|
246
|
+
}
|
|
247
|
+
function zedAdapter() {
|
|
248
|
+
return {
|
|
249
|
+
id: "zed",
|
|
250
|
+
label: "Zed",
|
|
251
|
+
detect() {
|
|
252
|
+
const configPath = zedConfigPath();
|
|
253
|
+
return { detected: existsSync4(configPath) || existsSync4(dirname3(configPath)), configPath };
|
|
254
|
+
},
|
|
255
|
+
async install({ entry }) {
|
|
256
|
+
const configPath = zedConfigPath();
|
|
257
|
+
let config = {};
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(await readFile3(configPath, "utf-8"));
|
|
260
|
+
if (!isJsonObject2(parsed)) {
|
|
261
|
+
return { status: "failed", message: `failed: ${configPath} must contain a JSON object.` };
|
|
262
|
+
}
|
|
263
|
+
config = parsed;
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (readErrorCode2(err) !== "ENOENT") {
|
|
266
|
+
return { status: "failed", message: `failed: ${configPath} contains invalid JSON. Fix it and retry.` };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const existingServers = config.context_servers;
|
|
270
|
+
if (existingServers !== void 0 && !isJsonObject2(existingServers)) {
|
|
271
|
+
return { status: "failed", message: `failed: ${configPath} field context_servers must be a JSON object.` };
|
|
272
|
+
}
|
|
273
|
+
const servers = existingServers ?? {};
|
|
274
|
+
if (servers.plaud) {
|
|
275
|
+
return { status: "already-configured", message: "already configured", restartHint: RESTART_HINT3 };
|
|
276
|
+
}
|
|
277
|
+
config.context_servers = {
|
|
278
|
+
...servers,
|
|
279
|
+
plaud: { command: { path: entry.command, args: entry.args } }
|
|
280
|
+
};
|
|
281
|
+
await mkdir3(dirname3(configPath), { recursive: true });
|
|
282
|
+
await writeFile3(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
283
|
+
return {
|
|
284
|
+
status: "configured",
|
|
285
|
+
message: "configured. Restart Zed to load the Plaud MCP.",
|
|
286
|
+
restartHint: RESTART_HINT3
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/installers/registry.ts
|
|
293
|
+
var CLAUDE_DESKTOP_RESTART = "Fully quit Claude Desktop (\u2318Q on macOS) and reopen it \u2014 closing the window is not enough.";
|
|
294
|
+
function claudeDesktopConfigPath() {
|
|
295
|
+
if (platform2() === "darwin") {
|
|
296
|
+
return join4(homedir4(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
297
|
+
}
|
|
298
|
+
if (platform2() === "win32") {
|
|
299
|
+
return join4(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
function cursorConfigPath() {
|
|
304
|
+
return join4(homedir4(), ".cursor", "mcp.json");
|
|
305
|
+
}
|
|
306
|
+
function windsurfConfigPath() {
|
|
307
|
+
return join4(homedir4(), ".codeium", "windsurf", "mcp_config.json");
|
|
308
|
+
}
|
|
309
|
+
function vsCodeConfigPath() {
|
|
310
|
+
return join4(process.cwd(), ".vscode", "mcp.json");
|
|
311
|
+
}
|
|
312
|
+
var adapters = [
|
|
313
|
+
jsonMcpServersAdapter({
|
|
314
|
+
id: "claude-desktop",
|
|
315
|
+
label: "Claude Desktop",
|
|
316
|
+
configPath: claudeDesktopConfigPath,
|
|
317
|
+
configuredMessage: "configured. Restart Claude Desktop to load the Plaud MCP.",
|
|
318
|
+
restartHint: CLAUDE_DESKTOP_RESTART
|
|
319
|
+
}),
|
|
320
|
+
claudeCodeAdapter(),
|
|
321
|
+
codexAdapter(),
|
|
322
|
+
jsonMcpServersAdapter({
|
|
323
|
+
id: "cursor",
|
|
324
|
+
label: "Cursor",
|
|
325
|
+
configPath: cursorConfigPath,
|
|
326
|
+
configuredMessage: "configured. Restart Cursor to load the Plaud MCP.",
|
|
327
|
+
restartHint: "Restart Cursor to load the Plaud MCP."
|
|
328
|
+
}),
|
|
329
|
+
jsonMcpServersAdapter({
|
|
330
|
+
id: "windsurf",
|
|
331
|
+
label: "Windsurf",
|
|
332
|
+
configPath: windsurfConfigPath,
|
|
333
|
+
configuredMessage: "configured. Restart Windsurf to load the Plaud MCP.",
|
|
334
|
+
restartHint: "Restart Windsurf to load the Plaud MCP."
|
|
335
|
+
}),
|
|
336
|
+
jsonMcpServersAdapter({
|
|
337
|
+
id: "vscode",
|
|
338
|
+
label: "VS Code",
|
|
339
|
+
configPath: vsCodeConfigPath,
|
|
340
|
+
rootKey: "servers",
|
|
341
|
+
entry: (entry) => ({ type: "stdio", command: entry.command, args: entry.args }),
|
|
342
|
+
configuredMessage: "configured. Restart VS Code to load the Plaud MCP.",
|
|
343
|
+
restartHint: "Restart VS Code to load the Plaud MCP."
|
|
344
|
+
}),
|
|
345
|
+
zedAdapter(),
|
|
346
|
+
webClientAdapter({
|
|
347
|
+
id: "claude-web",
|
|
348
|
+
label: "Claude Web",
|
|
349
|
+
setupUrl: "https://claude.ai/customize/connectors",
|
|
350
|
+
setupStep: "Click +, choose Add custom connector, paste the Remote MCP URL, then connect."
|
|
351
|
+
}),
|
|
352
|
+
webClientAdapter({
|
|
353
|
+
id: "chatgpt-web",
|
|
354
|
+
label: "ChatGPT Web",
|
|
355
|
+
setupUrl: "https://chatgpt.com/apps",
|
|
356
|
+
setupStep: "Open Settings > Apps, enable Developer mode if needed, create an app from the Remote MCP URL, then connect."
|
|
357
|
+
})
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
// src/install.ts
|
|
361
|
+
var promptInterface = null;
|
|
362
|
+
var pipedAnswers = null;
|
|
363
|
+
var pipedAnswersPromise = null;
|
|
364
|
+
function detectClients() {
|
|
365
|
+
return adapters.map((adapter) => ({ adapter, id: adapter.id, label: adapter.label, ...adapter.detect() }));
|
|
366
|
+
}
|
|
367
|
+
async function prompt(question, defaultYes = true) {
|
|
368
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
369
|
+
let answer;
|
|
370
|
+
if (!process.stdin.isTTY) {
|
|
371
|
+
process.stdout.write(`${question} [${hint}] `);
|
|
372
|
+
pipedAnswers ??= await (pipedAnswersPromise ??= readPipedAnswers());
|
|
373
|
+
answer = (pipedAnswers.shift() ?? "").trim().toLowerCase();
|
|
374
|
+
} else {
|
|
375
|
+
promptInterface ??= createInterface({ input: process.stdin, output: process.stdout });
|
|
376
|
+
answer = (await promptInterface.question(`${question} [${hint}] `)).trim().toLowerCase();
|
|
377
|
+
}
|
|
378
|
+
if (answer === "") return defaultYes;
|
|
379
|
+
return answer.startsWith("y");
|
|
380
|
+
}
|
|
381
|
+
async function readPipedAnswers() {
|
|
382
|
+
let input = "";
|
|
383
|
+
for await (const chunk of process.stdin) {
|
|
384
|
+
input += Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
|
|
385
|
+
}
|
|
386
|
+
return input.split(/\r?\n/);
|
|
387
|
+
}
|
|
388
|
+
function closePrompt() {
|
|
389
|
+
promptInterface?.close();
|
|
390
|
+
promptInterface = null;
|
|
391
|
+
pipedAnswers = null;
|
|
392
|
+
pipedAnswersPromise = null;
|
|
393
|
+
}
|
|
394
|
+
var CALLBACK_PORT = 8199;
|
|
395
|
+
var LOGIN_TIMEOUT_MS = 12e4;
|
|
396
|
+
function pickIdentity(user) {
|
|
397
|
+
for (const k of ["email", "username", "name", "id"]) {
|
|
398
|
+
const v = user[k];
|
|
399
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
400
|
+
}
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
async function runLogin() {
|
|
404
|
+
const client = getClient();
|
|
405
|
+
try {
|
|
406
|
+
const existing = await client.auth.getAccessToken();
|
|
407
|
+
if (existing) {
|
|
408
|
+
try {
|
|
409
|
+
const user = await client.getCurrentUser();
|
|
410
|
+
return { status: "already-authed", who: pickIdentity(user) };
|
|
411
|
+
} catch {
|
|
412
|
+
return { status: "already-authed" };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
await client.auth.logout().catch(() => void 0);
|
|
417
|
+
}
|
|
418
|
+
const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
|
|
419
|
+
return new Promise((resolve) => {
|
|
420
|
+
const httpServer = createServer(async (req, res) => {
|
|
421
|
+
const reqUrl = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
|
|
422
|
+
if (reqUrl.pathname !== "/auth/callback") {
|
|
423
|
+
res.writeHead(404);
|
|
424
|
+
res.end();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const code = reqUrl.searchParams.get("code");
|
|
428
|
+
if (!code) {
|
|
429
|
+
res.writeHead(400);
|
|
430
|
+
res.end("Missing code");
|
|
431
|
+
cleanup();
|
|
432
|
+
resolve({ status: "failed", message: "missing authorization code in callback" });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
437
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
438
|
+
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
439
|
+
cleanup();
|
|
440
|
+
let who;
|
|
441
|
+
try {
|
|
442
|
+
const user = await client.getCurrentUser();
|
|
443
|
+
who = pickIdentity(user);
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
resolve({ status: "success", who });
|
|
447
|
+
} catch (err) {
|
|
448
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
449
|
+
res.end("<h1>Token exchange failed</h1>");
|
|
450
|
+
cleanup();
|
|
451
|
+
resolve({ status: "failed", message: err instanceof Error ? err.message : String(err) });
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
let timeoutId;
|
|
455
|
+
function cleanup() {
|
|
456
|
+
clearTimeout(timeoutId);
|
|
457
|
+
httpServer.closeAllConnections?.();
|
|
458
|
+
httpServer.close();
|
|
459
|
+
}
|
|
460
|
+
timeoutId = setTimeout(() => {
|
|
461
|
+
cleanup();
|
|
462
|
+
resolve({ status: "timeout" });
|
|
463
|
+
}, LOGIN_TIMEOUT_MS);
|
|
464
|
+
httpServer.listen(CALLBACK_PORT, () => {
|
|
465
|
+
console.log(` if no browser opens, open this URL while this command is still running:
|
|
466
|
+
${url}`);
|
|
467
|
+
open(url).catch(() => {
|
|
468
|
+
console.log(" browser launch failed \u2014 waiting for a manual browser callback.");
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
httpServer.on("error", (err) => {
|
|
472
|
+
cleanup();
|
|
473
|
+
resolve({ status: "failed", message: `callback server error: ${err.message}` });
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
async function runInstall(opts = {}) {
|
|
478
|
+
console.log("Plaud MCP installer\n");
|
|
479
|
+
const clients = detectClients();
|
|
480
|
+
console.log("Detected AI clients:");
|
|
481
|
+
for (const c of clients) {
|
|
482
|
+
const mark = c.detected ? "\u2713" : "\xB7";
|
|
483
|
+
console.log(` ${mark} ${c.label.padEnd(16)} ${c.detected ? c.configPath : "(not detected)"}`);
|
|
484
|
+
}
|
|
485
|
+
console.log();
|
|
486
|
+
const selected = [];
|
|
487
|
+
if (opts.yes) {
|
|
488
|
+
for (const c of clients) {
|
|
489
|
+
if (c.detected && !c.adapter.manualOnly) selected.push(c);
|
|
490
|
+
}
|
|
491
|
+
if (selected.length > 0) {
|
|
492
|
+
console.log(`--yes: configuring ${selected.map((c) => c.label).join(", ")} without prompting.
|
|
493
|
+
`);
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
for (const c of clients) {
|
|
497
|
+
if (!c.detected) continue;
|
|
498
|
+
const yes = await prompt(`Configure ${c.label}?`, true);
|
|
499
|
+
if (yes) selected.push(c);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (selected.length === 0) {
|
|
503
|
+
console.log("\nNothing to configure. Exiting.");
|
|
504
|
+
closePrompt();
|
|
505
|
+
process.exit(0);
|
|
506
|
+
}
|
|
507
|
+
console.log();
|
|
508
|
+
const entry = getMcpEntry();
|
|
509
|
+
const succeeded = [];
|
|
510
|
+
for (const c of selected) {
|
|
511
|
+
process.stdout.write(`\u2192 ${c.label}... `);
|
|
512
|
+
try {
|
|
513
|
+
const result = await c.adapter.install({ entry, yes: Boolean(opts.yes) });
|
|
514
|
+
console.log(result.message);
|
|
515
|
+
if (isSuccessResult(result)) {
|
|
516
|
+
succeeded.push({
|
|
517
|
+
id: c.id,
|
|
518
|
+
label: c.label,
|
|
519
|
+
restartHint: result.restartHint,
|
|
520
|
+
requiresLocalAuth: result.requiresLocalAuth ?? true
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
} catch (err) {
|
|
524
|
+
console.log(`failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (succeeded.some((c) => c.id === "claude-code")) {
|
|
528
|
+
process.stdout.write("\u2192 Claude Code skills... ");
|
|
529
|
+
try {
|
|
530
|
+
await writeSkillsToClaudeCode();
|
|
531
|
+
console.log("written to ~/.claude/CLAUDE.md.");
|
|
532
|
+
} catch (err) {
|
|
533
|
+
console.log(`failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const needsLocalAuth = succeeded.some((c) => c.requiresLocalAuth);
|
|
537
|
+
const loginOutcome = opts.noLogin || !needsLocalAuth ? null : await doLoginStep(Boolean(opts.yes));
|
|
538
|
+
printNextSteps(succeeded, loginOutcome);
|
|
539
|
+
closePrompt();
|
|
540
|
+
process.exit(0);
|
|
541
|
+
}
|
|
542
|
+
function isSuccessResult(result) {
|
|
543
|
+
return result.status === "configured" || result.status === "already-configured";
|
|
544
|
+
}
|
|
545
|
+
async function doLoginStep(nonInteractive) {
|
|
546
|
+
console.log();
|
|
547
|
+
console.log("\u2192 Authenticating with Plaud...");
|
|
548
|
+
const client = getClient();
|
|
549
|
+
try {
|
|
550
|
+
const existing = await client.auth.getAccessToken();
|
|
551
|
+
if (existing) {
|
|
552
|
+
try {
|
|
553
|
+
const user = await client.getCurrentUser();
|
|
554
|
+
const who = pickIdentity(user);
|
|
555
|
+
console.log(` already signed in${who ? ` as ${who}` : ""} \u2014 skipping OAuth.`);
|
|
556
|
+
return { status: "already-authed", who };
|
|
557
|
+
} catch {
|
|
558
|
+
console.log(" token present but user lookup failed \u2014 will re-auth.");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
if (!nonInteractive) {
|
|
564
|
+
const yes = await prompt("Log in to Plaud now? (opens browser)", true);
|
|
565
|
+
if (!yes) {
|
|
566
|
+
console.log(" skipped \u2014 you'll be prompted on first tool call after restart.");
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
console.log(" opening browser \u2014 click Authorize to finish.");
|
|
571
|
+
const outcome = await runLogin();
|
|
572
|
+
switch (outcome.status) {
|
|
573
|
+
case "already-authed":
|
|
574
|
+
console.log(` already signed in${outcome.who ? ` as ${outcome.who}` : ""}.`);
|
|
575
|
+
break;
|
|
576
|
+
case "success":
|
|
577
|
+
console.log(` \u2713 authenticated${outcome.who ? ` as ${outcome.who}` : ""}.`);
|
|
578
|
+
break;
|
|
579
|
+
case "timeout":
|
|
580
|
+
console.log(" \u2717 timed out after 2 minutes \u2014 you can retry with `plaud-mcp install --yes` or run `login` from your AI client.");
|
|
581
|
+
break;
|
|
582
|
+
case "failed":
|
|
583
|
+
console.log(` \u2717 failed: ${outcome.message}`);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
return outcome;
|
|
587
|
+
}
|
|
588
|
+
function printNextSteps(clients, login) {
|
|
589
|
+
console.log();
|
|
590
|
+
if (clients.length === 0) {
|
|
591
|
+
console.log("No clients were configured. Nothing to do.");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const localClients = clients.filter((c) => c.requiresLocalAuth);
|
|
595
|
+
const webClients = clients.filter((c) => !c.requiresLocalAuth);
|
|
596
|
+
const authed = login?.status === "already-authed" || login?.status === "success";
|
|
597
|
+
const bar = "\u2500".repeat(60);
|
|
598
|
+
console.log(bar);
|
|
599
|
+
console.log(`\u2713 Plaud MCP ${localClients.length > 0 ? "installed" : "setup instructions shown"} for: ${clients.map((c) => c.label).join(", ")}`);
|
|
600
|
+
if (authed) {
|
|
601
|
+
console.log(`\u2713 Authenticated${login?.who ? ` as ${login.who}` : ""}`);
|
|
602
|
+
}
|
|
603
|
+
console.log(bar);
|
|
604
|
+
console.log();
|
|
605
|
+
if (localClients.length === 0) {
|
|
606
|
+
console.log("NEXT \u2014 finish setup in the web UI:");
|
|
607
|
+
console.log();
|
|
608
|
+
for (const c of webClients) {
|
|
609
|
+
console.log(` ${c.label}: ${c.restartHint}`);
|
|
610
|
+
}
|
|
611
|
+
console.log();
|
|
612
|
+
console.log(bar);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (authed) {
|
|
616
|
+
console.log("NEXT \u2014 one thing left:");
|
|
617
|
+
console.log();
|
|
618
|
+
if (localClients.length === 1) {
|
|
619
|
+
console.log(` ${localClients[0].restartHint}`);
|
|
620
|
+
} else {
|
|
621
|
+
console.log(" Restart each configured client:");
|
|
622
|
+
for (const c of localClients) {
|
|
623
|
+
console.log(` \u2022 ${c.label}: ${c.restartHint}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
console.log();
|
|
627
|
+
console.log(" After restart, Plaud tools are live \u2014 just ask your AI client");
|
|
628
|
+
console.log(' about your recordings (e.g. "list my recent Plaud recordings").');
|
|
629
|
+
} else {
|
|
630
|
+
console.log("NEXT \u2014 do these two things:");
|
|
631
|
+
console.log();
|
|
632
|
+
if (localClients.length === 1) {
|
|
633
|
+
console.log(` 1. ${localClients[0].restartHint}`);
|
|
634
|
+
} else {
|
|
635
|
+
console.log(" 1. Restart each configured client:");
|
|
636
|
+
for (const c of localClients) {
|
|
637
|
+
console.log(` \u2022 ${c.label}: ${c.restartHint}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
console.log();
|
|
641
|
+
console.log(" 2. In any chat with your AI client, type:");
|
|
642
|
+
console.log();
|
|
643
|
+
console.log(" list my recent Plaud recordings");
|
|
644
|
+
console.log();
|
|
645
|
+
console.log(" This triggers OAuth in your browser (once). Tokens are then");
|
|
646
|
+
console.log(" stored in ~/.plaud/tokens-mcp.json and refreshed automatically.");
|
|
647
|
+
}
|
|
648
|
+
if (webClients.length > 0) {
|
|
649
|
+
console.log();
|
|
650
|
+
console.log(" Web clients: finish setup using the connector instructions above.");
|
|
651
|
+
}
|
|
652
|
+
console.log();
|
|
653
|
+
console.log(bar);
|
|
654
|
+
}
|
|
655
|
+
export {
|
|
656
|
+
runInstall
|
|
657
|
+
};
|