@lucas_zaia/agent-voice 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,58 @@
1
+ // Starts a program with an argument array and never through a shell, on every
2
+ // OS. The spoken sentence ends up in these arguments, and it is user text: a
3
+ // shell would let "fix auth & deploy" run `deploy`.
4
+ //
5
+ // Windows needs two things done by hand. Bare names are resolved through PATH
6
+ // and PATHEXT here, so a missing command is known before anything runs. And
7
+ // .cmd/.bat files cannot be started without cmd.exe, so they get an explicit
8
+ // `cmd.exe /d /s /c "<line>"` in which every argument is quoted for the C
9
+ // runtime and then caret-escaped twice (the cross-spawn technique): once for
10
+ // cmd.exe itself, once more because the batch file re-reads its arguments
11
+ // (%* or %1) as part of its own command line.
12
+ import { spawn as nodeSpawn } from 'node:child_process';
13
+ import { findOnPath, envValue } from './platform.js';
14
+
15
+ const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
16
+
17
+ export function quoteCmdArg(arg) {
18
+ // A line break would end cmd.exe's command line; it cannot be passed anyway.
19
+ let s = String(arg).replace(/[\r\n]+/g, ' ');
20
+ // C runtime rules: backslashes before a quote double and the quote is
21
+ // escaped; trailing backslashes double so the closing quote stays a quote.
22
+ s = s.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, '$1$1');
23
+ return `"${s}"`.replace(CMD_META, '^$1').replace(CMD_META, '^$1');
24
+ }
25
+
26
+ // → { file, args, options } to hand to spawn, or null when Windows has no
27
+ // such command.
28
+ export function spawnPlan(cmd, args, { env = process.env, platform = process.platform, exists } = {}) {
29
+ if (platform !== 'win32') return { file: cmd, args, options: {} };
30
+ const file = findOnPath(cmd, env, platform, exists);
31
+ if (!file) return null;
32
+ if (!/\.(cmd|bat)$/i.test(file)) return { file, args, options: {} };
33
+ // Windows paths cannot contain ", so quoting the batch file's path is enough.
34
+ const line = [`"${file}"`, ...args.map(quoteCmdArg)].join(' ');
35
+ return {
36
+ file: envValue(env, 'COMSPEC', platform) || 'cmd.exe',
37
+ args: ['/d', '/s', '/c', `"${line}"`],
38
+ options: { windowsVerbatimArguments: true },
39
+ };
40
+ }
41
+
42
+ // One wording for "could not start it", shared by outputs and wrap. POSIX
43
+ // reports EACCES for a missing command when some PATH entry is unreadable,
44
+ // so a bare name that is not on PATH is "not found" whatever the code says.
45
+ export function spawnErrorMessage(e, cmd, env = process.env, platform = process.platform) {
46
+ const bareMissing = !/[\\/]/.test(cmd) && !findOnPath(cmd, env, platform);
47
+ if (e.code === 'ENOENT' || (e.code === 'EACCES' && bareMissing)) return `command not found: ${cmd}`;
48
+ if (e.code === 'EACCES' || e.code === 'EISDIR') return `not found or not executable: ${cmd}`;
49
+ return e.message;
50
+ }
51
+
52
+ // Like child_process.spawn(cmd, args, options) minus the shell. Throws an
53
+ // ENOENT error, without spawning, when Windows cannot find the command.
54
+ export function spawnCommand(cmd, args, options = {}, { spawn = nodeSpawn, platform = process.platform, exists } = {}) {
55
+ const plan = spawnPlan(cmd, args, { env: options.env ?? process.env, platform, exists });
56
+ if (!plan) throw Object.assign(new Error(`command not found: ${cmd}`), { code: 'ENOENT' });
57
+ return spawn(plan.file, plan.args, { ...options, ...plan.options, shell: false });
58
+ }