CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

Preserve My Voice + Grammarly battle plan #4011

Merged⚡ AI-generatedXSccantynz wants to mergeclaude/grammarly-killermainopened Mar 26, 2026
2 changed files+100−6
ModifiedSTRATEGY.md+27−5View fileUnifiedSplit
11# 48co Strategy & Roadmap
2> Last updated: 2026-03-25
3> Status: EXECUTE MODE
2> Last updated: 2026-03-26
3> Status: EXECUTE MODE — Grammarly killer
44
55## The Hook
66
7**"You're not selling dictation. You're selling a thinking amplifier."**
7**"Everything you write, perfected by AI. On every device."**
88
9Every competitor just transcribes. 48co REWRITES. You ramble, it writes professionally. You describe code intent, it writes actual code. You say "email Sarah about being late", it drafts the entire email.
9We're not a dictation tool. We're not a grammar checker. We're the **AI writing layer for everything.** Voice + grammar + tone + style — one tool, every device, works offline.
1010
11Once users try AI rewrite, raw dictation feels broken. That's the addiction.
11Grammarly is rules-based, English-only, $30/mo, cloud-only. We use Claude AI, support 30+ languages, cost $12/mo, and work offline. That's the 80% advantage.
12
13---
14
15## How We Beat Grammarly (The 80% Plan)
16
17| Grammarly weakness | 48co advantage |
18|-------------------|----------------|
19| Old AI (rules + GPT-3 era) | Claude AI (frontier model) |
20| English only | 30+ languages |
21| $30/mo ($360/year) | $12/mo ($99/year) + $89 lifetime |
22| No voice-to-text | Full voice built in |
23| Cloud-only (privacy risk) | Offline mode (local Whisper + local LLM) |
24| Makes writing generic | "Preserve My Voice" — learns YOUR style |
25| Laggy desktop app | Lightweight, types into any app |
26| No developer API | Public API for integrations |
27| 55% grammar detection | Claude catches more with context understanding |
28| No mobile keyboard | Custom iOS + Android keyboards |
29
30### The 3 Killer Features Grammarly Can't Copy:
311. **Voice + Grammar in one tool** — Grammarly killed their voice feature
322. **"Preserve My Voice"** — AI learns your writing style from past docs
333. **Privacy-first offline** — entire enterprise market Grammarly can't touch
1234
1335---
1436
Modifiedapi/server.js+73−1View fileUnifiedSplit
6666 FOREIGN KEY (user_id) REFERENCES users(id)
6767 );
6868
69 CREATE TABLE IF NOT EXISTS voice_samples (
70 id INTEGER PRIMARY KEY AUTOINCREMENT,
71 user_id TEXT NOT NULL,
72 content TEXT NOT NULL,
73 source TEXT DEFAULT 'manual',
74 created_at TEXT DEFAULT (datetime('now')),
75 FOREIGN KEY (user_id) REFERENCES users(id)
76 );
77
6978 CREATE INDEX IF NOT EXISTS idx_usage_user_date ON usage(user_id, created_at);
79 CREATE INDEX IF NOT EXISTS idx_voice_samples_user ON voice_samples(user_id);
7080 CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
7181`)
7282
282292
283293// ── AI Rewrite (Claude Sonnet — higher quality) ─────────
284294
295// ── "Preserve My Voice" — Upload Writing Samples ─────────
296
297app.post('/voice-samples', (req, res) => {
298 const user = authenticate(req)
299 if (!user) return res.status(401).json({ error: 'Please sign in' })
300 if (user.plan === 'free') return res.status(403).json({ error: 'Voice learning is a Pro feature', upgrade: true })
301
302 const { samples } = req.body // array of text strings
303 if (!samples || !Array.isArray(samples) || samples.length === 0) {
304 return res.status(400).json({ error: 'Provide an array of writing samples' })
305 }
306
307 const insert = db.prepare('INSERT INTO voice_samples (user_id, content, source) VALUES (?, ?, ?)')
308 const insertMany = db.transaction((items) => {
309 for (const text of items) {
310 if (text && text.length > 20) { // min 20 chars per sample
311 insert.run(user.id, text.substring(0, 5000), 'upload') // max 5000 chars each
312 }
313 }
314 })
315
316 insertMany(samples.slice(0, 50)) // max 50 samples
317 const count = db.prepare('SELECT COUNT(*) as n FROM voice_samples WHERE user_id = ?').get(user.id)
318
319 res.json({ stored: count.n, message: `${count.n} writing samples stored. AI will now preserve your voice.` })
320})
321
322app.get('/voice-samples', (req, res) => {
323 const user = authenticate(req)
324 if (!user) return res.status(401).json({ error: 'Please sign in' })
325
326 const count = db.prepare('SELECT COUNT(*) as n FROM voice_samples WHERE user_id = ?').get(user.id)
327 res.json({ count: count.n })
328})
329
330app.delete('/voice-samples', (req, res) => {
331 const user = authenticate(req)
332 if (!user) return res.status(401).json({ error: 'Please sign in' })
333
334 db.prepare('DELETE FROM voice_samples WHERE user_id = ?').run(user.id)
335 res.json({ message: 'All writing samples deleted.' })
336})
337
338// Helper: build voice context from user's samples
339function getVoiceContext(userId) {
340 const samples = db.prepare(
341 'SELECT content FROM voice_samples WHERE user_id = ? ORDER BY created_at DESC LIMIT 10'
342 ).all(userId)
343
344 if (samples.length === 0) return ''
345
346 const excerpts = samples.map(s => s.content.substring(0, 300)).join('\n---\n')
347 return `\n\nIMPORTANT: The user has a specific writing voice. Here are examples of how they naturally write. Preserve their tone, word choice, sentence structure, and personality. Do NOT make it sound generic or corporate:\n\n${excerpts}`
348}
349
350// ── AI Rewrite (Claude Sonnet — now with voice preservation) ──
351
285352app.post('/rewrite', async (req, res) => {
286353 const user = authenticate(req)
287354 if (!user) return res.status(401).json({ error: 'Please sign in' })
289356 return res.status(429).json({ error: 'Daily rewrite limit reached', upgrade: user.plan === 'free' })
290357 }
291358
292 const { text, mode = 'professional' } = req.body
359 const { text, mode = 'professional', preserveVoice = true } = req.body
293360 if (!text) return res.status(400).json({ error: 'No text provided' })
294361
362 // Get user's voice context if they have samples and want to preserve voice
363 const voiceContext = (preserveVoice && user.plan !== 'free') ? getVoiceContext(user.id) : ''
364
295365 const prompts = {
296366 professional: 'Rewrite this dictated text into clean, professional prose. Fix grammar, remove filler words. Keep original meaning. Return ONLY the rewritten text.',
297367 casual: 'Clean up this dictated text into natural, casual writing. Fix errors but keep the conversational tone. Return ONLY the cleaned text.',
301371 code: 'Convert this spoken description into a clear technical request or code comment. Return ONLY the formatted text.',
302372 }
303373
374 const systemPrompt = (prompts[mode] || prompts.professional) + voiceContext
375
304376 try {
305377 const response = await fetch('https://api.anthropic.com/v1/messages', {
306378 method: 'POST',
307379
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts