recmp3-cli 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/LICENSE +661 -0
- package/LICENSE-COMMERCIAL.md +25 -0
- package/README.md +255 -0
- package/dist/chunk-7NR5CU7W.js +46 -0
- package/dist/chunk-DDXRBIWU.js +40 -0
- package/dist/chunk-FNZ6ZCOK.js +51 -0
- package/dist/chunk-NUWDWBJQ.js +63 -0
- package/dist/chunk-NY5EJT5D.js +127 -0
- package/dist/chunk-XGHYROLT.js +481 -0
- package/dist/index.js +1960 -0
- package/dist/keychain-CLYHGYS2.js +3 -0
- package/dist/linux-pulse-AROLYZNB.js +183 -0
- package/dist/local-whisper-VH26RX7Y.js +4 -0
- package/dist/mac-avfoundation-COPCFRZT.js +136 -0
- package/dist/registry-D5SOVUEJ.js +5 -0
- package/dist/windows-dshow-O2GU4ZLR.js +150 -0
- package/package.json +75 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { findFfmpeg } from './chunk-7NR5CU7W.js';
|
|
3
|
+
import { AudioCaptureError } from './chunk-NUWDWBJQ.js';
|
|
4
|
+
import { execFile, spawn } from 'child_process';
|
|
5
|
+
import { stat } from 'fs/promises';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
|
|
8
|
+
var execFileAsync = promisify(execFile);
|
|
9
|
+
var LinuxPulseCapture = class {
|
|
10
|
+
process = null;
|
|
11
|
+
startedAt = null;
|
|
12
|
+
outputPath = null;
|
|
13
|
+
recording = false;
|
|
14
|
+
async start(opts) {
|
|
15
|
+
const ffmpeg = await findFfmpeg();
|
|
16
|
+
const args = [
|
|
17
|
+
"-hide_banner",
|
|
18
|
+
"-loglevel",
|
|
19
|
+
"error",
|
|
20
|
+
"-f",
|
|
21
|
+
"pulse",
|
|
22
|
+
"-i",
|
|
23
|
+
opts.source,
|
|
24
|
+
"-ac",
|
|
25
|
+
String(opts.channels),
|
|
26
|
+
"-ar",
|
|
27
|
+
String(opts.sampleRate),
|
|
28
|
+
"-c:a",
|
|
29
|
+
"pcm_s16le",
|
|
30
|
+
"-y",
|
|
31
|
+
opts.outputPath
|
|
32
|
+
];
|
|
33
|
+
this.outputPath = opts.outputPath;
|
|
34
|
+
this.startedAt = /* @__PURE__ */ new Date();
|
|
35
|
+
const proc = spawn(ffmpeg, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
36
|
+
this.process = proc;
|
|
37
|
+
this.recording = true;
|
|
38
|
+
await new Promise((resolve, reject) => {
|
|
39
|
+
const timer = setTimeout(() => resolve(), 500);
|
|
40
|
+
proc.on("error", (err) => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
this.recording = false;
|
|
43
|
+
reject(new AudioCaptureError(`Failed to start ffmpeg: ${err.message}`));
|
|
44
|
+
});
|
|
45
|
+
proc.on("close", (code) => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
this.recording = false;
|
|
48
|
+
});
|
|
49
|
+
let stderrBuf = "";
|
|
50
|
+
proc.stderr?.on("data", (chunk) => {
|
|
51
|
+
stderrBuf += chunk.toString();
|
|
52
|
+
});
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
if (!proc.pid) {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
reject(
|
|
57
|
+
new AudioCaptureError(
|
|
58
|
+
`ffmpeg failed to start. Check audio source: "${opts.source}"`
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}, 200);
|
|
63
|
+
});
|
|
64
|
+
if (!proc.pid) {
|
|
65
|
+
throw new AudioCaptureError(
|
|
66
|
+
`ffmpeg failed to start. Run 'recmp3 sources' to list available audio sources.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async stop() {
|
|
71
|
+
if (!this.process || !this.recording) {
|
|
72
|
+
throw new AudioCaptureError("Not currently recording.");
|
|
73
|
+
}
|
|
74
|
+
const proc = this.process;
|
|
75
|
+
const startedAt = this.startedAt ?? /* @__PURE__ */ new Date();
|
|
76
|
+
const outputPath = this.outputPath;
|
|
77
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
78
|
+
this.recording = false;
|
|
79
|
+
this.process = null;
|
|
80
|
+
this.startedAt = null;
|
|
81
|
+
this.outputPath = null;
|
|
82
|
+
await new Promise((resolve, reject) => {
|
|
83
|
+
proc.on("close", () => resolve());
|
|
84
|
+
proc.on("error", reject);
|
|
85
|
+
try {
|
|
86
|
+
proc.stdin?.write("q");
|
|
87
|
+
proc.stdin?.end();
|
|
88
|
+
} catch {
|
|
89
|
+
proc.kill("SIGTERM");
|
|
90
|
+
}
|
|
91
|
+
const forceKill = setTimeout(() => {
|
|
92
|
+
proc.kill("SIGTERM");
|
|
93
|
+
}, 5e3);
|
|
94
|
+
proc.on("close", () => clearTimeout(forceKill));
|
|
95
|
+
});
|
|
96
|
+
const fileStat = await stat(outputPath).catch(() => ({ size: 0 }));
|
|
97
|
+
const durationSec = (endedAt.getTime() - startedAt.getTime()) / 1e3;
|
|
98
|
+
return {
|
|
99
|
+
path: outputPath,
|
|
100
|
+
durationSec,
|
|
101
|
+
sizeBytes: fileStat.size,
|
|
102
|
+
startedAt,
|
|
103
|
+
endedAt
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
isRecording() {
|
|
107
|
+
return this.recording;
|
|
108
|
+
}
|
|
109
|
+
async dispose() {
|
|
110
|
+
if (this.process) {
|
|
111
|
+
try {
|
|
112
|
+
this.process.stdin?.end();
|
|
113
|
+
this.process.kill("SIGTERM");
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
this.process = null;
|
|
117
|
+
}
|
|
118
|
+
this.recording = false;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var LinuxPulseCaptureFactory = class {
|
|
122
|
+
create() {
|
|
123
|
+
return new LinuxPulseCapture();
|
|
124
|
+
}
|
|
125
|
+
async listSources() {
|
|
126
|
+
try {
|
|
127
|
+
const { stdout } = await execFileAsync("pactl", [
|
|
128
|
+
"list",
|
|
129
|
+
"sources",
|
|
130
|
+
"short"
|
|
131
|
+
]);
|
|
132
|
+
const sources = [];
|
|
133
|
+
for (const line of stdout.trim().split("\n")) {
|
|
134
|
+
const parts = line.split(" ");
|
|
135
|
+
if (parts.length < 2) continue;
|
|
136
|
+
const id = parts[1];
|
|
137
|
+
if (!id) continue;
|
|
138
|
+
const isMonitor = id.includes(".monitor");
|
|
139
|
+
sources.push({
|
|
140
|
+
id,
|
|
141
|
+
label: isMonitor ? `${id} (system audio monitor)` : id,
|
|
142
|
+
isDefault: id === "default" || false
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const hasDefault = sources.some((s) => s.id === "default");
|
|
146
|
+
if (!hasDefault) {
|
|
147
|
+
sources.unshift({
|
|
148
|
+
id: "default",
|
|
149
|
+
label: "default (system default)",
|
|
150
|
+
isDefault: true
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return sources;
|
|
154
|
+
} catch {
|
|
155
|
+
try {
|
|
156
|
+
const ffmpeg = await findFfmpeg();
|
|
157
|
+
const { stderr } = await execFileAsync(ffmpeg, [
|
|
158
|
+
"-sources",
|
|
159
|
+
"pulse",
|
|
160
|
+
"-hide_banner"
|
|
161
|
+
]);
|
|
162
|
+
const sources = [
|
|
163
|
+
{ id: "default", label: "default (system default)", isDefault: true }
|
|
164
|
+
];
|
|
165
|
+
for (const line of stderr.split("\n")) {
|
|
166
|
+
const match = line.match(/^\s+(\S+)\s/);
|
|
167
|
+
if (match)
|
|
168
|
+
sources.push({ id: match[1], label: match[1], isDefault: false });
|
|
169
|
+
}
|
|
170
|
+
return sources;
|
|
171
|
+
} catch {
|
|
172
|
+
return [
|
|
173
|
+
{ id: "default", label: "default (system default)", isDefault: true }
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
defaultSource() {
|
|
179
|
+
return process.env.RECMP3_SOURCE ?? "default";
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export { LinuxPulseCapture, LinuxPulseCaptureFactory };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { findFfmpeg } from './chunk-7NR5CU7W.js';
|
|
3
|
+
import { AudioCaptureError } from './chunk-NUWDWBJQ.js';
|
|
4
|
+
import { execFile, spawn } from 'child_process';
|
|
5
|
+
import { stat } from 'fs/promises';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
|
|
8
|
+
var execFileAsync = promisify(execFile);
|
|
9
|
+
var MacAvFoundationCapture = class {
|
|
10
|
+
process = null;
|
|
11
|
+
startedAt = null;
|
|
12
|
+
outputPath = null;
|
|
13
|
+
recording = false;
|
|
14
|
+
async start(opts) {
|
|
15
|
+
const ffmpeg = await findFfmpeg();
|
|
16
|
+
const source = opts.source.startsWith(":") ? opts.source : `:${opts.source}`;
|
|
17
|
+
const args = [
|
|
18
|
+
"-hide_banner",
|
|
19
|
+
"-loglevel",
|
|
20
|
+
"error",
|
|
21
|
+
"-f",
|
|
22
|
+
"avfoundation",
|
|
23
|
+
"-i",
|
|
24
|
+
source,
|
|
25
|
+
"-ac",
|
|
26
|
+
String(opts.channels),
|
|
27
|
+
"-ar",
|
|
28
|
+
String(opts.sampleRate),
|
|
29
|
+
"-c:a",
|
|
30
|
+
"pcm_s16le",
|
|
31
|
+
"-y",
|
|
32
|
+
opts.outputPath
|
|
33
|
+
];
|
|
34
|
+
this.outputPath = opts.outputPath;
|
|
35
|
+
this.startedAt = /* @__PURE__ */ new Date();
|
|
36
|
+
const proc = spawn(ffmpeg, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
37
|
+
this.process = proc;
|
|
38
|
+
this.recording = true;
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
const timer = setTimeout(() => resolve(), 800);
|
|
41
|
+
proc.on("error", (err) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
this.recording = false;
|
|
44
|
+
reject(
|
|
45
|
+
new AudioCaptureError(
|
|
46
|
+
`Failed to start ffmpeg: ${err.message}. Grant microphone access in System Settings \u2192 Privacy & Security \u2192 Microphone.`
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async stop() {
|
|
53
|
+
if (!this.process || !this.recording)
|
|
54
|
+
throw new AudioCaptureError("Not recording.");
|
|
55
|
+
const proc = this.process;
|
|
56
|
+
const startedAt = this.startedAt ?? /* @__PURE__ */ new Date();
|
|
57
|
+
const outputPath = this.outputPath;
|
|
58
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
59
|
+
this.recording = false;
|
|
60
|
+
this.process = null;
|
|
61
|
+
await new Promise((resolve) => {
|
|
62
|
+
proc.on("close", resolve);
|
|
63
|
+
try {
|
|
64
|
+
proc.stdin?.write("q");
|
|
65
|
+
proc.stdin?.end();
|
|
66
|
+
} catch {
|
|
67
|
+
proc.kill("SIGTERM");
|
|
68
|
+
}
|
|
69
|
+
setTimeout(() => proc.kill("SIGTERM"), 5e3);
|
|
70
|
+
});
|
|
71
|
+
const fileStat = await stat(outputPath).catch(() => ({ size: 0 }));
|
|
72
|
+
return {
|
|
73
|
+
path: outputPath,
|
|
74
|
+
durationSec: (endedAt.getTime() - startedAt.getTime()) / 1e3,
|
|
75
|
+
sizeBytes: fileStat.size,
|
|
76
|
+
startedAt,
|
|
77
|
+
endedAt
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
isRecording() {
|
|
81
|
+
return this.recording;
|
|
82
|
+
}
|
|
83
|
+
async dispose() {
|
|
84
|
+
if (this.process) {
|
|
85
|
+
try {
|
|
86
|
+
this.process.kill("SIGTERM");
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
this.process = null;
|
|
90
|
+
}
|
|
91
|
+
this.recording = false;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var MacAvFoundationFactory = class {
|
|
95
|
+
create() {
|
|
96
|
+
return new MacAvFoundationCapture();
|
|
97
|
+
}
|
|
98
|
+
async listSources() {
|
|
99
|
+
try {
|
|
100
|
+
const ffmpeg = await findFfmpeg();
|
|
101
|
+
const { stderr } = await execFileAsync(ffmpeg, [
|
|
102
|
+
"-f",
|
|
103
|
+
"avfoundation",
|
|
104
|
+
"-list_devices",
|
|
105
|
+
"true",
|
|
106
|
+
"-i",
|
|
107
|
+
""
|
|
108
|
+
]);
|
|
109
|
+
const sources = [];
|
|
110
|
+
let inAudioSection = false;
|
|
111
|
+
for (const line of stderr.split("\n")) {
|
|
112
|
+
if (line.includes("AVFoundation audio devices:")) {
|
|
113
|
+
inAudioSection = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!inAudioSection) continue;
|
|
117
|
+
const match = line.match(/\[(\d+)\] (.+)/);
|
|
118
|
+
if (match) {
|
|
119
|
+
sources.push({
|
|
120
|
+
id: match[1],
|
|
121
|
+
label: match[2],
|
|
122
|
+
isDefault: match[1] === "0"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return sources.length > 0 ? sources : [{ id: "0", label: "Default audio device", isDefault: true }];
|
|
127
|
+
} catch {
|
|
128
|
+
return [{ id: "0", label: "Default audio device", isDefault: true }];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
defaultSource() {
|
|
132
|
+
return process.env.RECMP3_SOURCE ?? "0";
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export { MacAvFoundationCapture, MacAvFoundationFactory };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { findFfmpeg } from './chunk-7NR5CU7W.js';
|
|
3
|
+
import { AudioCaptureError } from './chunk-NUWDWBJQ.js';
|
|
4
|
+
import { execFile, spawn } from 'child_process';
|
|
5
|
+
import { stat } from 'fs/promises';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
|
|
8
|
+
var execFileAsync = promisify(execFile);
|
|
9
|
+
var WindowsDshowCapture = class {
|
|
10
|
+
process = null;
|
|
11
|
+
startedAt = null;
|
|
12
|
+
outputPath = null;
|
|
13
|
+
recording = false;
|
|
14
|
+
async start(opts) {
|
|
15
|
+
const ffmpeg = await findFfmpeg();
|
|
16
|
+
const deviceName = opts.source === "default" ? await this.getDefaultDevice() : opts.source;
|
|
17
|
+
const args = [
|
|
18
|
+
"-hide_banner",
|
|
19
|
+
"-loglevel",
|
|
20
|
+
"error",
|
|
21
|
+
"-f",
|
|
22
|
+
"dshow",
|
|
23
|
+
"-i",
|
|
24
|
+
`audio=${deviceName}`,
|
|
25
|
+
"-ac",
|
|
26
|
+
String(opts.channels),
|
|
27
|
+
"-ar",
|
|
28
|
+
String(opts.sampleRate),
|
|
29
|
+
"-c:a",
|
|
30
|
+
"pcm_s16le",
|
|
31
|
+
"-y",
|
|
32
|
+
opts.outputPath
|
|
33
|
+
];
|
|
34
|
+
this.outputPath = opts.outputPath;
|
|
35
|
+
this.startedAt = /* @__PURE__ */ new Date();
|
|
36
|
+
const proc = spawn(ffmpeg, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
37
|
+
this.process = proc;
|
|
38
|
+
this.recording = true;
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
const timer = setTimeout(() => resolve(), 1e3);
|
|
41
|
+
proc.on("error", (err) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
this.recording = false;
|
|
44
|
+
reject(
|
|
45
|
+
new AudioCaptureError(
|
|
46
|
+
`Failed to start recording: ${err.message}. Run 'recmp3 sources' to list available devices.`
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async getDefaultDevice() {
|
|
53
|
+
const sources = await this.listSources();
|
|
54
|
+
const first = sources.find((s) => !s.label.includes("monitor")) ?? sources[0];
|
|
55
|
+
return first?.id ?? "Microphone";
|
|
56
|
+
}
|
|
57
|
+
async stop() {
|
|
58
|
+
if (!this.process || !this.recording)
|
|
59
|
+
throw new AudioCaptureError("Not recording.");
|
|
60
|
+
const proc = this.process;
|
|
61
|
+
const startedAt = this.startedAt ?? /* @__PURE__ */ new Date();
|
|
62
|
+
const outputPath = this.outputPath;
|
|
63
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
64
|
+
this.recording = false;
|
|
65
|
+
this.process = null;
|
|
66
|
+
await new Promise((resolve) => {
|
|
67
|
+
proc.on("close", resolve);
|
|
68
|
+
try {
|
|
69
|
+
proc.stdin?.write("q");
|
|
70
|
+
proc.stdin?.end();
|
|
71
|
+
} catch {
|
|
72
|
+
proc.kill("SIGTERM");
|
|
73
|
+
}
|
|
74
|
+
setTimeout(() => proc.kill("SIGTERM"), 5e3);
|
|
75
|
+
});
|
|
76
|
+
const fileStat = await stat(outputPath).catch(() => ({ size: 0 }));
|
|
77
|
+
return {
|
|
78
|
+
path: outputPath,
|
|
79
|
+
durationSec: (endedAt.getTime() - startedAt.getTime()) / 1e3,
|
|
80
|
+
sizeBytes: fileStat.size,
|
|
81
|
+
startedAt,
|
|
82
|
+
endedAt
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
isRecording() {
|
|
86
|
+
return this.recording;
|
|
87
|
+
}
|
|
88
|
+
async dispose() {
|
|
89
|
+
if (this.process) {
|
|
90
|
+
try {
|
|
91
|
+
this.process.kill("SIGTERM");
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
this.process = null;
|
|
95
|
+
}
|
|
96
|
+
this.recording = false;
|
|
97
|
+
}
|
|
98
|
+
async listSources() {
|
|
99
|
+
try {
|
|
100
|
+
const ffmpeg = await findFfmpeg();
|
|
101
|
+
const { stderr } = await execFileAsync(ffmpeg, [
|
|
102
|
+
"-list_devices",
|
|
103
|
+
"true",
|
|
104
|
+
"-f",
|
|
105
|
+
"dshow",
|
|
106
|
+
"-i",
|
|
107
|
+
"dummy"
|
|
108
|
+
]);
|
|
109
|
+
const sources = [];
|
|
110
|
+
let inAudioSection = false;
|
|
111
|
+
for (const line of stderr.split("\n")) {
|
|
112
|
+
if (line.includes("DirectShow audio devices")) {
|
|
113
|
+
inAudioSection = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (line.includes("DirectShow video devices")) {
|
|
117
|
+
inAudioSection = false;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!inAudioSection) continue;
|
|
121
|
+
const match = line.match(/"([^"]+)"/);
|
|
122
|
+
if (match)
|
|
123
|
+
sources.push({
|
|
124
|
+
id: match[1],
|
|
125
|
+
label: match[1],
|
|
126
|
+
isDefault: sources.length === 0
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return sources;
|
|
130
|
+
} catch {
|
|
131
|
+
return [
|
|
132
|
+
{ id: "Microphone", label: "Microphone (default)", isDefault: true }
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
var WindowsDshowFactory = class {
|
|
138
|
+
capture = new WindowsDshowCapture();
|
|
139
|
+
create() {
|
|
140
|
+
return new WindowsDshowCapture();
|
|
141
|
+
}
|
|
142
|
+
async listSources() {
|
|
143
|
+
return this.capture.listSources();
|
|
144
|
+
}
|
|
145
|
+
defaultSource() {
|
|
146
|
+
return process.env.RECMP3_SOURCE ?? "default";
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export { WindowsDshowCapture, WindowsDshowFactory };
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "recmp3-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Record audio, transcribe with AI, output developer-ready prompts — for humans and AI agents, from a single terminal command.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"recmp3": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"LICENSE-COMMERCIAL.md",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup && chmod +x dist/index.js",
|
|
20
|
+
"dev": "tsx src/index.ts",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "biome check src",
|
|
23
|
+
"format": "biome format --write src",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:coverage": "vitest run --coverage"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
+
"clipboardy": "^4.0.0",
|
|
31
|
+
"commander": "^13.1.0",
|
|
32
|
+
"dotenv": "^16.4.5",
|
|
33
|
+
"env-paths": "^3.0.0",
|
|
34
|
+
"ink": "^5.1.0",
|
|
35
|
+
"ora": "^8.1.1",
|
|
36
|
+
"picocolors": "^1.1.1",
|
|
37
|
+
"react": "^18.3.1",
|
|
38
|
+
"zod": "^3.23.8"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@biomejs/biome": "^1.9.3",
|
|
42
|
+
"@types/node": "^20.16.11",
|
|
43
|
+
"@types/react": "^18.3.11",
|
|
44
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
45
|
+
"msw": "^2.4.9",
|
|
46
|
+
"tsup": "^8.3.0",
|
|
47
|
+
"tsx": "^4.19.1",
|
|
48
|
+
"typescript": "^5.6.3",
|
|
49
|
+
"vitest": "^4.1.8"
|
|
50
|
+
},
|
|
51
|
+
"optionalDependencies": {
|
|
52
|
+
"keytar": "^7.9.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20.0.0"
|
|
56
|
+
},
|
|
57
|
+
"license": "AGPL-3.0-or-later",
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/aedneth/recmp3-cli.git"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"cli",
|
|
64
|
+
"audio",
|
|
65
|
+
"recording",
|
|
66
|
+
"transcription",
|
|
67
|
+
"whisper",
|
|
68
|
+
"groq",
|
|
69
|
+
"developer-tools",
|
|
70
|
+
"vibecoding",
|
|
71
|
+
"mcp",
|
|
72
|
+
"ai-agent",
|
|
73
|
+
"agent-native"
|
|
74
|
+
]
|
|
75
|
+
}
|