@hasna/recordings 0.1.10 → 0.1.11
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/.takumi/settings.local.json +7 -0
- package/dist/cli/index.js +73 -122
- package/dist/index.js +64 -25
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/enhancer.d.ts +2 -2
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/mcp/index.js +65 -26
- package/package.json +1 -1
- package/src/__tests__/enhancer.test.ts +65 -0
- package/src/cli/index.ts +3 -130
- package/src/lib/config.ts +56 -16
- package/src/lib/enhancer.ts +17 -11
- package/src/native/Recordings/Recordings/FnKeyMonitor.swift +58 -49
- package/src/native/Recordings/Recordings/MenuBarPopover.swift +247 -118
- package/src/native/Recordings/Recordings/ProjectStore.swift +126 -0
- package/src/native/Recordings/Recordings/RecordingEngine.swift +157 -57
- package/src/native/Recordings/Recordings/RecordingsApp.swift +12 -4
- package/src/native/Recordings/Recordings/SettingsView.swift +147 -36
- package/src/native/RecordingsHelper.swift +0 -395
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __require = import.meta.require;
|
|
|
7
7
|
var require_package = __commonJS((exports, module) => {
|
|
8
8
|
module.exports = {
|
|
9
9
|
name: "@hasna/recordings",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.11",
|
|
11
11
|
type: "module",
|
|
12
12
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
13
13
|
main: "dist/index.js",
|
|
@@ -60,7 +60,7 @@ import { Command } from "commander";
|
|
|
60
60
|
import chalk from "chalk";
|
|
61
61
|
|
|
62
62
|
// src/lib/config.ts
|
|
63
|
-
import { existsSync, readFileSync, mkdirSync, cpSync } from "fs";
|
|
63
|
+
import { existsSync, readFileSync, mkdirSync, cpSync, readdirSync, statSync } from "fs";
|
|
64
64
|
import { join } from "path";
|
|
65
65
|
import { homedir } from "os";
|
|
66
66
|
var DEFAULT_CONFIG = {
|
|
@@ -97,7 +97,7 @@ function loadConfig(configPath) {
|
|
|
97
97
|
try {
|
|
98
98
|
const raw = readFileSync(filePath, "utf-8");
|
|
99
99
|
const fileConfig = JSON.parse(raw);
|
|
100
|
-
Object.assign(config, fileConfig);
|
|
100
|
+
Object.assign(config, expandEnvBackedConfig(fileConfig));
|
|
101
101
|
} catch {}
|
|
102
102
|
}
|
|
103
103
|
if (process.env.OPENAI_API_KEY) {
|
|
@@ -143,6 +143,16 @@ function loadConfig(configPath) {
|
|
|
143
143
|
}
|
|
144
144
|
return config;
|
|
145
145
|
}
|
|
146
|
+
function expandEnvBackedConfig(config) {
|
|
147
|
+
const expanded = { ...config };
|
|
148
|
+
for (const key of ["openai_api_key", "enhancement_api_key"]) {
|
|
149
|
+
const value = expanded[key];
|
|
150
|
+
if (typeof value === "string" && value.startsWith("$") && value.length > 1) {
|
|
151
|
+
expanded[key] = process.env[value.slice(1)] || value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return expanded;
|
|
155
|
+
}
|
|
146
156
|
function findConfigFile() {
|
|
147
157
|
let dir = process.cwd();
|
|
148
158
|
const root = "/";
|
|
@@ -184,20 +194,44 @@ function loadSecretKey(keyName) {
|
|
|
184
194
|
const secretsPath = join(homedir(), ".secrets");
|
|
185
195
|
if (!existsSync(secretsPath))
|
|
186
196
|
return "";
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
for (const candidate of listSecretFiles(secretsPath)) {
|
|
198
|
+
try {
|
|
199
|
+
const content = readFileSync(candidate, "utf-8");
|
|
200
|
+
const match = content.match(new RegExp(`export\\s+${keyName}\\s*=\\s*"([^"]+)"`));
|
|
201
|
+
if (match)
|
|
202
|
+
return match[1];
|
|
203
|
+
const match2 = content.match(new RegExp(`export\\s+${keyName}\\s*=\\s*'([^']+)'`));
|
|
204
|
+
if (match2)
|
|
205
|
+
return match2[1];
|
|
206
|
+
const match3 = content.match(new RegExp(`${keyName}\\s*=\\s*(.+)`));
|
|
207
|
+
if (match3)
|
|
208
|
+
return match3[1].trim().replace(/^["']|["']$/g, "");
|
|
209
|
+
} catch {}
|
|
210
|
+
}
|
|
199
211
|
return "";
|
|
200
212
|
}
|
|
213
|
+
function listSecretFiles(path) {
|
|
214
|
+
try {
|
|
215
|
+
const stats = statSync(path);
|
|
216
|
+
if (stats.isFile())
|
|
217
|
+
return [path];
|
|
218
|
+
if (!stats.isDirectory())
|
|
219
|
+
return [];
|
|
220
|
+
return readdirSync(path).sort().flatMap((entry) => {
|
|
221
|
+
const child = join(path, entry);
|
|
222
|
+
try {
|
|
223
|
+
const childStats = statSync(child);
|
|
224
|
+
if (childStats.isDirectory())
|
|
225
|
+
return listSecretFiles(child);
|
|
226
|
+
if (childStats.isFile() && child.endsWith(".env"))
|
|
227
|
+
return [child];
|
|
228
|
+
} catch {}
|
|
229
|
+
return [];
|
|
230
|
+
});
|
|
231
|
+
} catch {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
201
235
|
function ensureDataDir(config) {
|
|
202
236
|
const { mkdirSync: mkdirSync2 } = __require("fs");
|
|
203
237
|
mkdirSync2(config.audio_dir, { recursive: true });
|
|
@@ -212,7 +246,7 @@ import { Database } from "bun:sqlite";
|
|
|
212
246
|
import {
|
|
213
247
|
existsSync as existsSync2,
|
|
214
248
|
mkdirSync as mkdirSync2,
|
|
215
|
-
readdirSync,
|
|
249
|
+
readdirSync as readdirSync2,
|
|
216
250
|
copyFileSync
|
|
217
251
|
} from "fs";
|
|
218
252
|
import { homedir as homedir2 } from "os";
|
|
@@ -220,7 +254,7 @@ import { join as join2, relative } from "path";
|
|
|
220
254
|
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
221
255
|
import { homedir as homedir22 } from "os";
|
|
222
256
|
import { join as join22 } from "path";
|
|
223
|
-
import { readdirSync as
|
|
257
|
+
import { readdirSync as readdirSync22, existsSync as existsSync3 } from "fs";
|
|
224
258
|
import { join as join3 } from "path";
|
|
225
259
|
import { homedir as homedir3 } from "os";
|
|
226
260
|
import { homedir as homedir4 } from "os";
|
|
@@ -9499,7 +9533,7 @@ function discoverServices() {
|
|
|
9499
9533
|
if (!existsSync3(dataDir))
|
|
9500
9534
|
return [];
|
|
9501
9535
|
try {
|
|
9502
|
-
const entries =
|
|
9536
|
+
const entries = readdirSync22(dataDir, { withFileTypes: true });
|
|
9503
9537
|
return entries.filter((e) => {
|
|
9504
9538
|
if (!e.isDirectory())
|
|
9505
9539
|
return false;
|
|
@@ -9526,7 +9560,7 @@ function getServiceDbPath(service) {
|
|
|
9526
9560
|
join3(dataDir, "database.db")
|
|
9527
9561
|
];
|
|
9528
9562
|
try {
|
|
9529
|
-
const files =
|
|
9563
|
+
const files = readdirSync22(dataDir);
|
|
9530
9564
|
for (const f of files) {
|
|
9531
9565
|
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
9532
9566
|
candidates.push(join3(dataDir, f));
|
|
@@ -10244,15 +10278,9 @@ function extractInstruction(text, trigger) {
|
|
|
10244
10278
|
}
|
|
10245
10279
|
return text;
|
|
10246
10280
|
}
|
|
10247
|
-
async function enhanceText(rawText, instruction, config) {
|
|
10281
|
+
async function enhanceText(rawText, instruction, config, systemPrompt) {
|
|
10248
10282
|
const client = getEnhancementClient(config);
|
|
10249
|
-
|
|
10250
|
-
const response = await client.chat.completions.create({
|
|
10251
|
-
model: config.enhancement_model,
|
|
10252
|
-
messages: [
|
|
10253
|
-
{
|
|
10254
|
-
role: "system",
|
|
10255
|
-
content: `You are a writing assistant. The user has dictated speech that needs to be transformed into polished output.
|
|
10283
|
+
const basePrompt = `You are a writing assistant. The user has dictated speech that needs to be transformed into polished output.
|
|
10256
10284
|
|
|
10257
10285
|
Rules:
|
|
10258
10286
|
- Output ONLY the enhanced/rewritten text \u2014 no explanations, no preamble
|
|
@@ -10260,7 +10288,18 @@ Rules:
|
|
|
10260
10288
|
- Fix grammar, structure, and clarity
|
|
10261
10289
|
- If the user is giving instructions (e.g., "write an email saying..."), produce the actual output (the email), not a description of it
|
|
10262
10290
|
- If the user says "say it better" or similar, rewrite their preceding text to be clearer and more professional
|
|
10263
|
-
- Match the appropriate tone (formal for business, casual for personal)
|
|
10291
|
+
- Match the appropriate tone (formal for business, casual for personal)`;
|
|
10292
|
+
const fullPrompt = systemPrompt ? `${basePrompt}
|
|
10293
|
+
|
|
10294
|
+
Additional context:
|
|
10295
|
+
${systemPrompt}` : basePrompt;
|
|
10296
|
+
try {
|
|
10297
|
+
const response = await client.chat.completions.create({
|
|
10298
|
+
model: config.enhancement_model,
|
|
10299
|
+
messages: [
|
|
10300
|
+
{
|
|
10301
|
+
role: "system",
|
|
10302
|
+
content: fullPrompt
|
|
10264
10303
|
},
|
|
10265
10304
|
{
|
|
10266
10305
|
role: "user",
|
|
@@ -10282,7 +10321,7 @@ Rules:
|
|
|
10282
10321
|
throw new EnhancementError(`Enhancement failed: ${msg}`);
|
|
10283
10322
|
}
|
|
10284
10323
|
}
|
|
10285
|
-
async function processText(rawText, config) {
|
|
10324
|
+
async function processText(rawText, config, systemPrompt) {
|
|
10286
10325
|
if (!config.auto_enhance) {
|
|
10287
10326
|
return { text: rawText, mode: "raw", enhancement_model: null };
|
|
10288
10327
|
}
|
|
@@ -10290,7 +10329,7 @@ async function processText(rawText, config) {
|
|
|
10290
10329
|
if (!detection.needs) {
|
|
10291
10330
|
return { text: rawText, mode: "raw", enhancement_model: null };
|
|
10292
10331
|
}
|
|
10293
|
-
const result = await enhanceText(rawText, detection.instruction, config);
|
|
10332
|
+
const result = await enhanceText(rawText, detection.instruction, config, systemPrompt);
|
|
10294
10333
|
return {
|
|
10295
10334
|
text: result.enhanced,
|
|
10296
10335
|
mode: "enhanced",
|
|
@@ -10299,7 +10338,6 @@ async function processText(rawText, config) {
|
|
|
10299
10338
|
}
|
|
10300
10339
|
|
|
10301
10340
|
// src/cli/index.ts
|
|
10302
|
-
var __dirname = "/home/hasna/workspace/hasna/opensource/opensourcedev/open-recordings/src/cli";
|
|
10303
10341
|
var program = new Command;
|
|
10304
10342
|
program.name("recordings").description("Speech-to-text recording tool \u2014 record, transcribe, and enhance with AI").version("0.0.3").option("--json", "Output as JSON").option("--agent <name>", "Agent name or ID").option("--project <name>", "Project name or ID").option("--session <id>", "Session ID");
|
|
10305
10343
|
program.command("record").description("Record from microphone, transcribe, and optionally enhance").option("-d, --duration <seconds>", "Record for specific duration").option("--no-enhance", "Skip AI enhancement").option("-t, --tags <tags>", "Comma-separated tags").option("-l, --language <lang>", "Language code (e.g. en, es, fr)").action(async (opts) => {
|
|
@@ -10371,14 +10409,14 @@ Output:`));
|
|
|
10371
10409
|
Saved as ${recording.id.slice(0, 8)}`));
|
|
10372
10410
|
}
|
|
10373
10411
|
});
|
|
10374
|
-
program.command("transcribe <file>").description("Transcribe an existing audio file").option("--no-enhance", "Skip AI enhancement").option("-t, --tags <tags>", "Comma-separated tags").action(async (file, opts) => {
|
|
10412
|
+
program.command("transcribe <file>").description("Transcribe an existing audio file").option("--no-enhance", "Skip AI enhancement").option("-t, --tags <tags>", "Comma-separated tags").option("--system-prompt <prompt>", "System prompt for enhancement context").action(async (file, opts) => {
|
|
10375
10413
|
const config = loadConfig();
|
|
10376
10414
|
ensureDataDir(config);
|
|
10377
10415
|
if (opts.noEnhance === false)
|
|
10378
10416
|
config.auto_enhance = false;
|
|
10379
10417
|
console.log(chalk.blue("Transcribing..."));
|
|
10380
10418
|
const transcription = await transcribeAudio(file, config);
|
|
10381
|
-
const processed = await processText(transcription.text, config);
|
|
10419
|
+
const processed = await processText(transcription.text, config, opts.systemPrompt);
|
|
10382
10420
|
const parentOpts = program.opts();
|
|
10383
10421
|
const tags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [];
|
|
10384
10422
|
const recording = createRecording({
|
|
@@ -10582,42 +10620,6 @@ program.command("check").description("Check system dependencies (sox, API keys)"
|
|
|
10582
10620
|
console.log(chalk.yellow(`\u26A0 Enhancement API key not configured \u2014 enhancement disabled`));
|
|
10583
10621
|
}
|
|
10584
10622
|
});
|
|
10585
|
-
program.command("start").description("Launch the menu bar helper app (F5 to toggle recording)").option("--login", "Also add to Login Items so it starts automatically").action(async (opts) => {
|
|
10586
|
-
const { execSync } = __require("child_process");
|
|
10587
|
-
const { join: pathJoin } = __require("path");
|
|
10588
|
-
const { homedir: getHome } = __require("os");
|
|
10589
|
-
const { existsSync: fileExists } = __require("fs");
|
|
10590
|
-
const home = getHome();
|
|
10591
|
-
const appPath = pathJoin(home, ".hasna", "recordings", "RecordingsHelper.app");
|
|
10592
|
-
const oldAppPath = pathJoin(home, ".recordings", "RecordingsHelper.app");
|
|
10593
|
-
if (!fileExists(appPath) && !fileExists(oldAppPath)) {
|
|
10594
|
-
console.error(chalk.red("RecordingsHelper.app not found. Run: recordings shortcut --install"));
|
|
10595
|
-
process.exit(1);
|
|
10596
|
-
}
|
|
10597
|
-
const resolvedAppPath = fileExists(appPath) ? appPath : oldAppPath;
|
|
10598
|
-
try {
|
|
10599
|
-
execSync("pkill -f RecordingsHelper", { stdio: "pipe" });
|
|
10600
|
-
} catch {}
|
|
10601
|
-
execSync(`open "${resolvedAppPath}"`, { stdio: "pipe" });
|
|
10602
|
-
console.log(chalk.green("Recordings helper launched \u2014 press F5 to record"));
|
|
10603
|
-
if (opts.login) {
|
|
10604
|
-
try {
|
|
10605
|
-
execSync(`osascript -e 'tell application "System Events" to make login item at end with properties {path:"${resolvedAppPath}", hidden:true}'`, { stdio: "pipe" });
|
|
10606
|
-
console.log(chalk.green("Added to Login Items \u2014 will start on boot"));
|
|
10607
|
-
} catch {
|
|
10608
|
-
console.log(chalk.yellow("Could not add to Login Items \u2014 add manually in System Settings > General > Login Items"));
|
|
10609
|
-
}
|
|
10610
|
-
}
|
|
10611
|
-
});
|
|
10612
|
-
program.command("stop").description("Stop the menu bar helper app").action(() => {
|
|
10613
|
-
const { execSync } = __require("child_process");
|
|
10614
|
-
try {
|
|
10615
|
-
execSync("pkill -f RecordingsHelper", { stdio: "pipe" });
|
|
10616
|
-
console.log(chalk.green("Recordings helper stopped"));
|
|
10617
|
-
} catch {
|
|
10618
|
-
console.log(chalk.dim("Not running"));
|
|
10619
|
-
}
|
|
10620
|
-
});
|
|
10621
10623
|
program.command("listen").description("Push-to-talk mode \u2014 press Space to start/stop recording, Esc to quit").option("-t, --tags <tags>", "Comma-separated tags for all recordings").option("--no-enhance", "Skip AI enhancement").option("-l, --language <lang>", "Language code").option("--copy", "Copy output to clipboard").option("--paste", "Copy output to clipboard AND paste into frontmost app").action(async (opts) => {
|
|
10622
10624
|
const config = loadConfig();
|
|
10623
10625
|
ensureDataDir(config);
|
|
@@ -10726,8 +10728,8 @@ Bye.`));
|
|
|
10726
10728
|
}
|
|
10727
10729
|
});
|
|
10728
10730
|
});
|
|
10729
|
-
program.command("shortcut").description("Set up a global keyboard shortcut for recording (macOS)").option("--raycast", "Generate Raycast script command").option("--
|
|
10730
|
-
const { writeFileSync: writeFileSync2, mkdirSync: mkdirSync4, chmodSync
|
|
10731
|
+
program.command("shortcut").description("Set up a global keyboard shortcut for recording (macOS)").option("--raycast", "Generate Raycast script command").option("--karabiner", "Set up Fn key via Karabiner-Elements").option("--skhd", "Generate skhd hotkey config").option("--hammerspoon", "Generate Hammerspoon config").option("--script", "Just output the shell script path").action((opts) => {
|
|
10732
|
+
const { writeFileSync: writeFileSync2, mkdirSync: mkdirSync4, chmodSync } = __require("fs");
|
|
10731
10733
|
const { join: pathJoin } = __require("path");
|
|
10732
10734
|
const { homedir: getHome } = __require("os");
|
|
10733
10735
|
const home = getHome();
|
|
@@ -10782,54 +10784,6 @@ fi
|
|
|
10782
10784
|
`;
|
|
10783
10785
|
writeFileSync2(scriptPath, script, "utf-8");
|
|
10784
10786
|
chmodSync(scriptPath, 493);
|
|
10785
|
-
if (opts.install) {
|
|
10786
|
-
const { execSync: exec } = __require("child_process");
|
|
10787
|
-
const appPath = pathJoin(home, ".hasna", "recordings", "RecordingsHelper.app");
|
|
10788
|
-
const srcSwift = pathJoin(__dirname, "..", "native", "RecordingsHelper.swift");
|
|
10789
|
-
const distApp = pathJoin(__dirname, "..", "RecordingsHelper.app");
|
|
10790
|
-
if (fileExists(pathJoin(distApp, "Contents", "MacOS", "RecordingsHelper"))) {
|
|
10791
|
-
exec(`rm -rf "${appPath}" && cp -R "${distApp}" "${appPath}"`, { stdio: "pipe", shell: "/bin/bash" });
|
|
10792
|
-
} else if (fileExists(srcSwift)) {
|
|
10793
|
-
console.log(chalk.blue("Compiling native helper app..."));
|
|
10794
|
-
const appDir = pathJoin(home, ".hasna", "recordings", "RecordingsHelper.app", "Contents", "MacOS");
|
|
10795
|
-
mkdirSync4(appDir, { recursive: true });
|
|
10796
|
-
const plistDir = pathJoin(home, ".hasna", "recordings", "RecordingsHelper.app", "Contents");
|
|
10797
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
10798
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
10799
|
-
<plist version="1.0"><dict>
|
|
10800
|
-
<key>CFBundleExecutable</key><string>RecordingsHelper</string>
|
|
10801
|
-
<key>CFBundleIdentifier</key><string>com.hasna.recordings-helper</string>
|
|
10802
|
-
<key>CFBundleName</key><string>Recordings</string>
|
|
10803
|
-
<key>LSUIElement</key><true/>
|
|
10804
|
-
<key>NSMicrophoneUsageDescription</key><string>Recordings needs microphone access for speech transcription.</string>
|
|
10805
|
-
</dict></plist>`;
|
|
10806
|
-
writeFileSync2(pathJoin(plistDir, "Info.plist"), plist, "utf-8");
|
|
10807
|
-
try {
|
|
10808
|
-
exec(`DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun swiftc -O -o "${appDir}/RecordingsHelper" "${srcSwift}" -framework Cocoa -framework Carbon`, { stdio: "pipe" });
|
|
10809
|
-
} catch {
|
|
10810
|
-
exec(`swiftc -O -o "${appDir}/RecordingsHelper" "${srcSwift}" -framework Cocoa -framework Carbon`, { stdio: "pipe" });
|
|
10811
|
-
}
|
|
10812
|
-
} else {
|
|
10813
|
-
console.error(chalk.red("Cannot find RecordingsHelper. Run from the project directory or rebuild."));
|
|
10814
|
-
process.exit(1);
|
|
10815
|
-
}
|
|
10816
|
-
try {
|
|
10817
|
-
exec("pkill -f RecordingsHelper", { stdio: "pipe" });
|
|
10818
|
-
} catch {}
|
|
10819
|
-
exec(`open "${appPath}"`, { stdio: "pipe" });
|
|
10820
|
-
try {
|
|
10821
|
-
exec(`osascript -e 'tell application "System Events" to make login item at end with properties {path:"${appPath}", hidden:true}'`, { stdio: "pipe" });
|
|
10822
|
-
} catch {}
|
|
10823
|
-
console.log(chalk.green(`
|
|
10824
|
-
Recordings helper installed and running!
|
|
10825
|
-
`));
|
|
10826
|
-
console.log(` ${chalk.yellow("F5")} Start/stop recording`);
|
|
10827
|
-
console.log(` ${chalk.dim("\uD83C\uDF99")} Menu bar icon (click for options)`);
|
|
10828
|
-
console.log(` ${chalk.dim("Auto")} Starts on login
|
|
10829
|
-
`);
|
|
10830
|
-
console.log(chalk.dim(" Press F5 \u2192 speak \u2192 F5 \u2192 text is pasted where your cursor is."));
|
|
10831
|
-
return;
|
|
10832
|
-
}
|
|
10833
10787
|
if (opts.karabiner) {
|
|
10834
10788
|
const karabinerDir = pathJoin(home, ".config", "karabiner", "assets", "complex_modifications");
|
|
10835
10789
|
mkdirSync4(karabinerDir, { recursive: true });
|
|
@@ -10917,9 +10871,6 @@ ${scriptPath}
|
|
|
10917
10871
|
console.log(chalk.cyan(` ${scriptPath}
|
|
10918
10872
|
`));
|
|
10919
10873
|
console.log(`Bind it to a hotkey using any of these:
|
|
10920
|
-
`);
|
|
10921
|
-
console.log(chalk.bold(" macOS built-in") + chalk.dim(" (no extra installs \u2014 recommended)"));
|
|
10922
|
-
console.log(` recordings shortcut --install
|
|
10923
10874
|
`);
|
|
10924
10875
|
console.log(chalk.bold(" Karabiner-Elements") + chalk.dim(" (for Fn key specifically)"));
|
|
10925
10876
|
console.log(` brew install --cask karabiner-elements`);
|
package/dist/index.js
CHANGED
|
@@ -9559,7 +9559,7 @@ import { mkdirSync as mkdirSync5 } from "fs";
|
|
|
9559
9559
|
import { dirname as dirname2 } from "path";
|
|
9560
9560
|
|
|
9561
9561
|
// src/lib/config.ts
|
|
9562
|
-
import { existsSync as existsSync4, readFileSync as readFileSync2, mkdirSync as mkdirSync3, cpSync } from "fs";
|
|
9562
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, mkdirSync as mkdirSync3, cpSync, readdirSync as readdirSync3, statSync } from "fs";
|
|
9563
9563
|
import { join as join5 } from "path";
|
|
9564
9564
|
import { homedir as homedir6 } from "os";
|
|
9565
9565
|
var DEFAULT_CONFIG = {
|
|
@@ -9596,7 +9596,7 @@ function loadConfig(configPath) {
|
|
|
9596
9596
|
try {
|
|
9597
9597
|
const raw = readFileSync2(filePath, "utf-8");
|
|
9598
9598
|
const fileConfig = JSON.parse(raw);
|
|
9599
|
-
Object.assign(config, fileConfig);
|
|
9599
|
+
Object.assign(config, expandEnvBackedConfig(fileConfig));
|
|
9600
9600
|
} catch {}
|
|
9601
9601
|
}
|
|
9602
9602
|
if (process.env.OPENAI_API_KEY) {
|
|
@@ -9642,6 +9642,16 @@ function loadConfig(configPath) {
|
|
|
9642
9642
|
}
|
|
9643
9643
|
return config;
|
|
9644
9644
|
}
|
|
9645
|
+
function expandEnvBackedConfig(config) {
|
|
9646
|
+
const expanded = { ...config };
|
|
9647
|
+
for (const key of ["openai_api_key", "enhancement_api_key"]) {
|
|
9648
|
+
const value = expanded[key];
|
|
9649
|
+
if (typeof value === "string" && value.startsWith("$") && value.length > 1) {
|
|
9650
|
+
expanded[key] = process.env[value.slice(1)] || value;
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
return expanded;
|
|
9654
|
+
}
|
|
9645
9655
|
function findConfigFile() {
|
|
9646
9656
|
let dir = process.cwd();
|
|
9647
9657
|
const root = "/";
|
|
@@ -9683,20 +9693,44 @@ function loadSecretKey(keyName) {
|
|
|
9683
9693
|
const secretsPath = join5(homedir6(), ".secrets");
|
|
9684
9694
|
if (!existsSync4(secretsPath))
|
|
9685
9695
|
return "";
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9696
|
+
for (const candidate of listSecretFiles(secretsPath)) {
|
|
9697
|
+
try {
|
|
9698
|
+
const content = readFileSync2(candidate, "utf-8");
|
|
9699
|
+
const match = content.match(new RegExp(`export\\s+${keyName}\\s*=\\s*"([^"]+)"`));
|
|
9700
|
+
if (match)
|
|
9701
|
+
return match[1];
|
|
9702
|
+
const match2 = content.match(new RegExp(`export\\s+${keyName}\\s*=\\s*'([^']+)'`));
|
|
9703
|
+
if (match2)
|
|
9704
|
+
return match2[1];
|
|
9705
|
+
const match3 = content.match(new RegExp(`${keyName}\\s*=\\s*(.+)`));
|
|
9706
|
+
if (match3)
|
|
9707
|
+
return match3[1].trim().replace(/^["']|["']$/g, "");
|
|
9708
|
+
} catch {}
|
|
9709
|
+
}
|
|
9698
9710
|
return "";
|
|
9699
9711
|
}
|
|
9712
|
+
function listSecretFiles(path) {
|
|
9713
|
+
try {
|
|
9714
|
+
const stats = statSync(path);
|
|
9715
|
+
if (stats.isFile())
|
|
9716
|
+
return [path];
|
|
9717
|
+
if (!stats.isDirectory())
|
|
9718
|
+
return [];
|
|
9719
|
+
return readdirSync3(path).sort().flatMap((entry) => {
|
|
9720
|
+
const child = join5(path, entry);
|
|
9721
|
+
try {
|
|
9722
|
+
const childStats = statSync(child);
|
|
9723
|
+
if (childStats.isDirectory())
|
|
9724
|
+
return listSecretFiles(child);
|
|
9725
|
+
if (childStats.isFile() && child.endsWith(".env"))
|
|
9726
|
+
return [child];
|
|
9727
|
+
} catch {}
|
|
9728
|
+
return [];
|
|
9729
|
+
});
|
|
9730
|
+
} catch {
|
|
9731
|
+
return [];
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9700
9734
|
function ensureDataDir(config) {
|
|
9701
9735
|
const { mkdirSync: mkdirSync4 } = __require("fs");
|
|
9702
9736
|
mkdirSync4(config.audio_dir, { recursive: true });
|
|
@@ -10166,15 +10200,9 @@ function extractInstruction(text, trigger) {
|
|
|
10166
10200
|
}
|
|
10167
10201
|
return text;
|
|
10168
10202
|
}
|
|
10169
|
-
async function enhanceText(rawText, instruction, config) {
|
|
10203
|
+
async function enhanceText(rawText, instruction, config, systemPrompt) {
|
|
10170
10204
|
const client = getEnhancementClient(config);
|
|
10171
|
-
|
|
10172
|
-
const response = await client.chat.completions.create({
|
|
10173
|
-
model: config.enhancement_model,
|
|
10174
|
-
messages: [
|
|
10175
|
-
{
|
|
10176
|
-
role: "system",
|
|
10177
|
-
content: `You are a writing assistant. The user has dictated speech that needs to be transformed into polished output.
|
|
10205
|
+
const basePrompt = `You are a writing assistant. The user has dictated speech that needs to be transformed into polished output.
|
|
10178
10206
|
|
|
10179
10207
|
Rules:
|
|
10180
10208
|
- Output ONLY the enhanced/rewritten text \u2014 no explanations, no preamble
|
|
@@ -10182,7 +10210,18 @@ Rules:
|
|
|
10182
10210
|
- Fix grammar, structure, and clarity
|
|
10183
10211
|
- If the user is giving instructions (e.g., "write an email saying..."), produce the actual output (the email), not a description of it
|
|
10184
10212
|
- If the user says "say it better" or similar, rewrite their preceding text to be clearer and more professional
|
|
10185
|
-
- Match the appropriate tone (formal for business, casual for personal)
|
|
10213
|
+
- Match the appropriate tone (formal for business, casual for personal)`;
|
|
10214
|
+
const fullPrompt = systemPrompt ? `${basePrompt}
|
|
10215
|
+
|
|
10216
|
+
Additional context:
|
|
10217
|
+
${systemPrompt}` : basePrompt;
|
|
10218
|
+
try {
|
|
10219
|
+
const response = await client.chat.completions.create({
|
|
10220
|
+
model: config.enhancement_model,
|
|
10221
|
+
messages: [
|
|
10222
|
+
{
|
|
10223
|
+
role: "system",
|
|
10224
|
+
content: fullPrompt
|
|
10186
10225
|
},
|
|
10187
10226
|
{
|
|
10188
10227
|
role: "user",
|
|
@@ -10204,7 +10243,7 @@ Rules:
|
|
|
10204
10243
|
throw new EnhancementError(`Enhancement failed: ${msg}`);
|
|
10205
10244
|
}
|
|
10206
10245
|
}
|
|
10207
|
-
async function processText(rawText, config) {
|
|
10246
|
+
async function processText(rawText, config, systemPrompt) {
|
|
10208
10247
|
if (!config.auto_enhance) {
|
|
10209
10248
|
return { text: rawText, mode: "raw", enhancement_model: null };
|
|
10210
10249
|
}
|
|
@@ -10212,7 +10251,7 @@ async function processText(rawText, config) {
|
|
|
10212
10251
|
if (!detection.needs) {
|
|
10213
10252
|
return { text: rawText, mode: "raw", enhancement_model: null };
|
|
10214
10253
|
}
|
|
10215
|
-
const result = await enhanceText(rawText, detection.instruction, config);
|
|
10254
|
+
const result = await enhanceText(rawText, detection.instruction, config, systemPrompt);
|
|
10216
10255
|
return {
|
|
10217
10256
|
text: result.enhanced,
|
|
10218
10257
|
mode: "enhanced",
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,eAAO,MAAM,cAAc,EAAE,gBA0B5B,CAAC;AAEF,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAqEhE;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,eAAO,MAAM,cAAc,EAAE,gBA0B5B,CAAC;AAEF,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAqEhE;AA6BD,wBAAgB,UAAU,IAAI,MAAM,CA4BnC;AAqDD,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAU5D"}
|
package/dist/lib/enhancer.d.ts
CHANGED
|
@@ -13,11 +13,11 @@ export declare function needsEnhancement(text: string, config: RecordingsConfig)
|
|
|
13
13
|
reason: string;
|
|
14
14
|
instruction: string;
|
|
15
15
|
};
|
|
16
|
-
export declare function enhanceText(rawText: string, instruction: string, config: RecordingsConfig): Promise<EnhancementResult>;
|
|
16
|
+
export declare function enhanceText(rawText: string, instruction: string, config: RecordingsConfig, systemPrompt?: string): Promise<EnhancementResult>;
|
|
17
17
|
/**
|
|
18
18
|
* Full pipeline: detect if enhancement is needed, enhance if so.
|
|
19
19
|
*/
|
|
20
|
-
export declare function processText(rawText: string, config: RecordingsConfig): Promise<{
|
|
20
|
+
export declare function processText(rawText: string, config: RecordingsConfig, systemPrompt?: string): Promise<{
|
|
21
21
|
text: string;
|
|
22
22
|
mode: "raw" | "enhanced";
|
|
23
23
|
enhancement_model: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enhancer.d.ts","sourceRoot":"","sources":["../../src/lib/enhancer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAiB3B,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,GACvB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAmCzD;AAoBD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"enhancer.d.ts","sourceRoot":"","sources":["../../src/lib/enhancer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAiB3B,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,GACvB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAmCzD;AAoBD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,gBAAgB,EACxB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,iBAAiB,CAAC,CA6C5B;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,EACxB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,GAAG,UAAU,CAAC;IACzB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,CAAC,CAkBD"}
|