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.
@@ -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
+ }