promptmic 0.1.0 → 0.1.2
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/README.md +1 -1
- package/apps/server/dist/pty-spawner.js +96 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,85 @@
|
|
|
1
1
|
import pty from 'node-pty';
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { HOME_DIR } from './runtime-config.js';
|
|
5
|
+
function isExecutable(filePath, platform) {
|
|
6
|
+
try {
|
|
7
|
+
fs.accessSync(filePath, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function resolveDirectExecutable(command, envPath, platform = process.platform, pathExt = process.env.PATHEXT) {
|
|
15
|
+
const expandedCommand = expandTilde(command);
|
|
16
|
+
const hasPathSeparator = expandedCommand.includes('/') || expandedCommand.includes('\\');
|
|
17
|
+
const pathDelimiter = platform === 'win32' ? ';' : path.delimiter;
|
|
18
|
+
const windowsExtensions = platform === 'win32'
|
|
19
|
+
? (pathExt ?? '.COM;.EXE;.BAT;.CMD')
|
|
20
|
+
.split(';')
|
|
21
|
+
.map((entry) => entry.trim())
|
|
22
|
+
.map((entry) => entry.toLowerCase())
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
: [];
|
|
25
|
+
const hasWindowsExtension = platform === 'win32' &&
|
|
26
|
+
windowsExtensions.some((extension) => expandedCommand.toLowerCase().endsWith(extension.toLowerCase()));
|
|
27
|
+
const candidatesFor = (baseCommand) => {
|
|
28
|
+
if (platform !== 'win32')
|
|
29
|
+
return [baseCommand];
|
|
30
|
+
if (hasWindowsExtension)
|
|
31
|
+
return [baseCommand];
|
|
32
|
+
return [baseCommand, ...windowsExtensions.map((extension) => `${baseCommand}${extension}`)];
|
|
33
|
+
};
|
|
34
|
+
if (hasPathSeparator) {
|
|
35
|
+
for (const candidate of candidatesFor(expandedCommand)) {
|
|
36
|
+
if (isExecutable(candidate, platform))
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (!envPath)
|
|
42
|
+
return null;
|
|
43
|
+
for (const dir of envPath.split(pathDelimiter)) {
|
|
44
|
+
if (!dir)
|
|
45
|
+
continue;
|
|
46
|
+
for (const candidate of candidatesFor(path.join(dir, expandedCommand))) {
|
|
47
|
+
if (isExecutable(candidate, platform))
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function readShebang(filePath) {
|
|
54
|
+
try {
|
|
55
|
+
const contents = fs.readFileSync(filePath, 'utf8');
|
|
56
|
+
const firstLine = contents.split(/\r?\n/, 1)[0];
|
|
57
|
+
return firstLine.startsWith('#!') ? firstLine : null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function resolveDirectSpawnTarget(command, args, env, platform) {
|
|
64
|
+
const executable = resolveDirectExecutable(command, env.PATH, platform, env.PATHEXT) ?? command;
|
|
65
|
+
try {
|
|
66
|
+
const realPath = fs.realpathSync(executable);
|
|
67
|
+
const shebang = readShebang(realPath)?.toLowerCase() ?? '';
|
|
68
|
+
if (shebang.includes('node')) {
|
|
69
|
+
return {
|
|
70
|
+
file: process.execPath,
|
|
71
|
+
args: [realPath, ...args],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fall back to the resolved executable path when realpath/read fails.
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
file: executable,
|
|
80
|
+
args,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
4
83
|
export function expandTilde(value) {
|
|
5
84
|
return value.startsWith('~/') ? path.join(HOME_DIR, value.slice(2)) : value;
|
|
6
85
|
}
|
|
@@ -10,11 +89,13 @@ function quoteShellArg(value) {
|
|
|
10
89
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11
90
|
}
|
|
12
91
|
function formatSpawnError(entry, error) {
|
|
92
|
+
const errorMessage = error instanceof Error ? error.message : String(error ?? '');
|
|
93
|
+
const errorCode = error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
|
|
13
94
|
if (entry.executionMode === 'direct' &&
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
95
|
+
(errorCode === 'ENOENT' ||
|
|
96
|
+
/posix_spawnp failed/i.test(errorMessage) ||
|
|
97
|
+
/spawn .* ENOENT/i.test(errorMessage) ||
|
|
98
|
+
/not found/i.test(errorMessage))) {
|
|
18
99
|
return new Error(`Could not start "${entry.label}". Command "${entry.command}" was not found in PATH. Install it or switch this provider to Shell command if it depends on a shell wrapper.`);
|
|
19
100
|
}
|
|
20
101
|
if (error instanceof Error) {
|
|
@@ -50,19 +131,24 @@ export function createPtySpawner(options = {}) {
|
|
|
50
131
|
const baseEnv = Object.fromEntries(Object.entries(options.env ?? process.env).filter((entry) => typeof entry[1] === 'string'));
|
|
51
132
|
const spawn = options.spawn ?? pty.spawn;
|
|
52
133
|
const shell = options.shell ?? process.env.SHELL ?? '/bin/bash';
|
|
134
|
+
const platform = options.platform ?? process.platform;
|
|
53
135
|
return ({ provider, cwd, config }) => {
|
|
54
136
|
const entry = config.providers[provider];
|
|
55
137
|
const command = resolveProviderCommand(config, provider, shell);
|
|
138
|
+
const env = {
|
|
139
|
+
...baseEnv,
|
|
140
|
+
...command.envOverrides,
|
|
141
|
+
};
|
|
142
|
+
const spawnTarget = command.executionMode === 'direct'
|
|
143
|
+
? resolveDirectSpawnTarget(command.file, command.args, env, platform)
|
|
144
|
+
: { file: command.file, args: command.args };
|
|
56
145
|
try {
|
|
57
|
-
return spawn(
|
|
146
|
+
return spawn(spawnTarget.file, spawnTarget.args, {
|
|
58
147
|
name: 'xterm-256color',
|
|
59
148
|
cols: 120,
|
|
60
149
|
rows: 30,
|
|
61
150
|
cwd,
|
|
62
|
-
env
|
|
63
|
-
...baseEnv,
|
|
64
|
-
...command.envOverrides,
|
|
65
|
-
},
|
|
151
|
+
env,
|
|
66
152
|
});
|
|
67
153
|
}
|
|
68
154
|
catch (error) {
|
|
@@ -70,3 +156,4 @@ export function createPtySpawner(options = {}) {
|
|
|
70
156
|
}
|
|
71
157
|
};
|
|
72
158
|
}
|
|
159
|
+
export { resolveDirectExecutable, resolveDirectSpawnTarget };
|