talktocursor 1.0.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.
- package/INSTALL.md +249 -0
- package/README.md +177 -0
- package/build/config.js +82 -0
- package/build/index.js +166 -0
- package/build/settings-server.js +124 -0
- package/package.json +54 -0
- package/public/index.html +1574 -0
- package/scripts/auto-submit.py +394 -0
- package/scripts/silence_detector.py +146 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
6
|
+
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const app = express();
|
|
10
|
+
app.use(express.json());
|
|
11
|
+
// Serve static files from /public
|
|
12
|
+
app.use(express.static(join(__dirname, "..", "public")));
|
|
13
|
+
// GET /api/config - return current config (mask API key)
|
|
14
|
+
app.get("/api/config", (_req, res) => {
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
res.json({
|
|
17
|
+
apiKey: config.apiKey ? maskKey(config.apiKey) : "",
|
|
18
|
+
apiKeySet: !!config.apiKey,
|
|
19
|
+
voiceId: config.voiceId,
|
|
20
|
+
model: config.model,
|
|
21
|
+
voiceSettings: config.voiceSettings,
|
|
22
|
+
autoSubmit: config.autoSubmit,
|
|
23
|
+
wisprLoop: config.wisprLoop,
|
|
24
|
+
autoListen: config.autoListen,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
// POST /api/config - save config
|
|
28
|
+
app.post("/api/config", (req, res) => {
|
|
29
|
+
const { apiKey, voiceId, model, voiceSettings, autoSubmit, wisprLoop, autoListen } = req.body;
|
|
30
|
+
const updates = {};
|
|
31
|
+
if (apiKey !== undefined && apiKey !== "")
|
|
32
|
+
updates.apiKey = apiKey;
|
|
33
|
+
if (voiceId !== undefined)
|
|
34
|
+
updates.voiceId = voiceId;
|
|
35
|
+
if (model !== undefined)
|
|
36
|
+
updates.model = model;
|
|
37
|
+
if (voiceSettings !== undefined)
|
|
38
|
+
updates.voiceSettings = voiceSettings;
|
|
39
|
+
if (autoSubmit !== undefined)
|
|
40
|
+
updates.autoSubmit = autoSubmit;
|
|
41
|
+
if (wisprLoop !== undefined)
|
|
42
|
+
updates.wisprLoop = wisprLoop;
|
|
43
|
+
if (autoListen !== undefined)
|
|
44
|
+
updates.autoListen = autoListen;
|
|
45
|
+
const saved = saveConfig(updates);
|
|
46
|
+
res.json({
|
|
47
|
+
success: true,
|
|
48
|
+
config: {
|
|
49
|
+
apiKey: saved.apiKey ? maskKey(saved.apiKey) : "",
|
|
50
|
+
apiKeySet: !!saved.apiKey,
|
|
51
|
+
voiceId: saved.voiceId,
|
|
52
|
+
model: saved.model,
|
|
53
|
+
voiceSettings: saved.voiceSettings,
|
|
54
|
+
autoSubmit: saved.autoSubmit,
|
|
55
|
+
wisprLoop: saved.wisprLoop,
|
|
56
|
+
autoListen: saved.autoListen,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
// POST /api/test - test TTS with current config
|
|
61
|
+
app.post("/api/test", async (req, res) => {
|
|
62
|
+
const config = loadConfig();
|
|
63
|
+
const apiKey = req.body.apiKey || config.apiKey;
|
|
64
|
+
if (!apiKey) {
|
|
65
|
+
res.status(400).json({ error: "No API key configured" });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const client = new ElevenLabsClient({ apiKey });
|
|
70
|
+
// Verify the key works by fetching voices
|
|
71
|
+
const voices = await client.voices.getAll();
|
|
72
|
+
res.json({
|
|
73
|
+
success: true,
|
|
74
|
+
message: `API key is valid! Found ${voices.voices.length} voices.`,
|
|
75
|
+
voices: voices.voices.map((v) => ({
|
|
76
|
+
id: v.voiceId,
|
|
77
|
+
name: v.name,
|
|
78
|
+
category: v.category,
|
|
79
|
+
preview_url: v.previewUrl,
|
|
80
|
+
})),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
85
|
+
res.status(400).json({ error: `API key test failed: ${msg}` });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// POST /api/voices - list available voices
|
|
89
|
+
app.post("/api/voices", async (req, res) => {
|
|
90
|
+
const config = loadConfig();
|
|
91
|
+
const apiKey = req.body.apiKey || config.apiKey;
|
|
92
|
+
if (!apiKey) {
|
|
93
|
+
res.status(400).json({ error: "No API key configured" });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const client = new ElevenLabsClient({ apiKey });
|
|
98
|
+
const voices = await client.voices.getAll();
|
|
99
|
+
res.json({
|
|
100
|
+
voices: voices.voices.map((v) => ({
|
|
101
|
+
id: v.voiceId,
|
|
102
|
+
name: v.name,
|
|
103
|
+
category: v.category,
|
|
104
|
+
preview_url: v.previewUrl,
|
|
105
|
+
labels: v.labels,
|
|
106
|
+
})),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
111
|
+
res.status(400).json({ error: msg });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
function maskKey(key) {
|
|
115
|
+
if (key.length <= 8)
|
|
116
|
+
return "****";
|
|
117
|
+
return key.slice(0, 4) + "****" + key.slice(-4);
|
|
118
|
+
}
|
|
119
|
+
const PORT = parseInt(process.env.PORT || "3847", 10);
|
|
120
|
+
app.listen(PORT, () => {
|
|
121
|
+
console.log(`\n Cursor TTS Settings UI`);
|
|
122
|
+
console.log(` ───────────────────────`);
|
|
123
|
+
console.log(` Open http://localhost:${PORT} in your browser\n`);
|
|
124
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "talktocursor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Hands-free voice interface for Cursor AI — text-to-speech, voice commands, and conversational coding using ElevenLabs TTS",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"talktocursor": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && chmod +x build/index.js",
|
|
12
|
+
"watch": "tsc --watch",
|
|
13
|
+
"settings": "node build/settings-server.js",
|
|
14
|
+
"auto-submit": ".venv/bin/python scripts/auto-submit.py",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"text-to-speech",
|
|
20
|
+
"elevenlabs",
|
|
21
|
+
"cursor",
|
|
22
|
+
"tts",
|
|
23
|
+
"voice",
|
|
24
|
+
"hands-free",
|
|
25
|
+
"dictation",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"ai-assistant"
|
|
28
|
+
],
|
|
29
|
+
"author": "Mike Sheehan",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/mikesheehan/talktocursor"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/mikesheehan/talktocursor#readme",
|
|
36
|
+
"files": [
|
|
37
|
+
"build/",
|
|
38
|
+
"public/",
|
|
39
|
+
"scripts/*.py",
|
|
40
|
+
"INSTALL.md",
|
|
41
|
+
"README.md"
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@elevenlabs/elevenlabs-js": "^2.34.0",
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
46
|
+
"express": "^5.2.1",
|
|
47
|
+
"zod": "^3.25.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/express": "^5.0.6",
|
|
51
|
+
"@types/node": "^20.0.0",
|
|
52
|
+
"typescript": "^5.3.0"
|
|
53
|
+
}
|
|
54
|
+
}
|