@pixelbyte-software/pixcode 1.49.2 → 1.49.4
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/dist/assets/index-ntmmehps.css +32 -0
- package/dist/assets/{index-BsQAPOnf.js → index-u5D2ObPr.js} +125 -125
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +45 -18
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/services/hermes-install-jobs.js +186 -23
- package/dist-server/server/services/hermes-install-jobs.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/hermes-api-install.mjs +14 -0
- package/scripts/smoke/pixcode-workbench-1-48.mjs +6 -2
- package/server/index.js +45 -18
- package/server/services/hermes-install-jobs.js +191 -28
- package/dist/assets/index-B8TbMftk.css +0 -32
|
@@ -80,20 +80,196 @@ function knownHermesBinDirs(env = process.env) {
|
|
|
80
80
|
];
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function
|
|
83
|
+
export function buildHermesPathEnv(baseEnv = process.env, extras = {}) {
|
|
84
84
|
const env = mergePathEntries(buildCliSpawnEnv(baseEnv), knownHermesBinDirs(baseEnv));
|
|
85
85
|
for (const [key, value] of Object.entries(extras)) {
|
|
86
86
|
if (typeof value === 'string' && value.length > 0) {
|
|
87
87
|
env[key] = value;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
return env;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildHermesEnv(baseEnv = process.env, extras = {}) {
|
|
94
|
+
const env = buildHermesPathEnv(baseEnv, extras);
|
|
90
95
|
delete env.PYTHONPATH;
|
|
91
96
|
delete env.PYTHONHOME;
|
|
92
97
|
return env;
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
const ANSI_ESCAPE_REGEX =
|
|
101
|
+
/(?:\u001B\[[0-?]*[ -/]*[@-~]|\u009B[0-?]*[ -/]*[@-~]|\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)|\u009D[^\u0007\u009C]*(?:\u0007|\u009C)|\u001B[PX^_][^\u001B]*\u001B\\|[\u0090\u0098\u009E\u009F][^\u009C]*\u009C|\u001B[@-Z\\-_])/g;
|
|
102
|
+
|
|
103
|
+
export function formatHermesVersionOutput(output) {
|
|
104
|
+
const firstLine = String(output || '')
|
|
105
|
+
.replace(ANSI_ESCAPE_REGEX, '')
|
|
106
|
+
.split(/\r?\n/)
|
|
107
|
+
.map((line) => line.trim())
|
|
108
|
+
.find(Boolean);
|
|
109
|
+
if (!firstLine) return null;
|
|
110
|
+
|
|
111
|
+
const versionMatch = firstLine.match(/Hermes Agent v[^\s]+(?:\s+\([^)]+\))?/i);
|
|
112
|
+
return versionMatch?.[0] || firstLine;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function hermesExecutableNames() {
|
|
116
|
+
return process.platform === 'win32'
|
|
117
|
+
? ['hermes.cmd', 'hermes.bat', 'hermes.exe', 'hermes']
|
|
118
|
+
: ['hermes'];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function pushHermesCommandFiles(candidates, dir) {
|
|
122
|
+
if (!dir) return;
|
|
123
|
+
for (const name of hermesExecutableNames()) {
|
|
124
|
+
candidates.push(path.join(dir, name));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function runHermesVersion(candidate, env) {
|
|
129
|
+
try {
|
|
130
|
+
const result = spawn.sync(candidate, ['--version'], {
|
|
131
|
+
encoding: 'utf8',
|
|
132
|
+
env,
|
|
133
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
134
|
+
timeout: 5000,
|
|
135
|
+
windowsHide: true,
|
|
136
|
+
});
|
|
137
|
+
if (result.error || result.status !== 0) {
|
|
138
|
+
return { ok: false, output: '', error: result.error?.message || result.stderr || result.stdout || null };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { ok: true, output: `${result.stdout || result.stderr || ''}` };
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return { ok: false, output: '', error: error instanceof Error ? error.message : String(error) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function isUsableHermesCommand(candidate, env = process.env) {
|
|
148
|
+
return runHermesVersion(candidate, buildHermesEnv(env)).ok;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isHermesPythonLauncher(candidate) {
|
|
152
|
+
if (!candidate || process.platform !== 'win32') return false;
|
|
153
|
+
const extension = path.extname(candidate).toLowerCase();
|
|
154
|
+
if (extension && extension !== '.py') return false;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const source = fs.readFileSync(candidate, 'utf8').slice(0, 1000);
|
|
158
|
+
return source.includes('Hermes Agent CLI launcher') || source.includes('hermes_cli.main');
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function windowsPythonCandidates(candidate, env = process.env) {
|
|
165
|
+
const localAppData = env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
166
|
+
const candidateDir = candidate ? path.dirname(candidate) : '';
|
|
167
|
+
const installDir = env.HERMES_INSTALL_DIR || path.join(localAppData, 'hermes', 'hermes-agent');
|
|
168
|
+
return [
|
|
169
|
+
candidateDir ? path.join(candidateDir, 'python.exe') : null,
|
|
170
|
+
path.join(installDir, 'venv', 'Scripts', 'python.exe'),
|
|
171
|
+
path.join(installDir, '.venv', 'Scripts', 'python.exe'),
|
|
172
|
+
env.HERMES_HOME ? path.join(env.HERMES_HOME, 'hermes-agent', 'venv', 'Scripts', 'python.exe') : null,
|
|
173
|
+
env.HERMES_HOME ? path.join(env.HERMES_HOME, 'hermes-agent', '.venv', 'Scripts', 'python.exe') : null,
|
|
174
|
+
].filter(Boolean);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function windowsHermesCmdShimBody(command, env = process.env) {
|
|
178
|
+
const hermesHome = env.HERMES_HOME || path.join(env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'hermes');
|
|
179
|
+
const normalizedCommand = command.replace(/"/g, '""');
|
|
180
|
+
const extension = path.extname(command).toLowerCase();
|
|
181
|
+
const lines = [
|
|
182
|
+
'@echo off',
|
|
183
|
+
'setlocal',
|
|
184
|
+
'set PYTHONPATH=',
|
|
185
|
+
'set PYTHONHOME=',
|
|
186
|
+
`set "HERMES_HOME=${hermesHome.replace(/"/g, '""')}"`,
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
if (isHermesPythonLauncher(command)) {
|
|
190
|
+
const python = windowsPythonCandidates(command, env).find((candidate) => {
|
|
191
|
+
try {
|
|
192
|
+
return fs.existsSync(candidate);
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
if (python) {
|
|
198
|
+
lines.push(`"${python.replace(/"/g, '""')}" "${normalizedCommand}" %*`);
|
|
199
|
+
lines.push('exit /b %ERRORLEVEL%');
|
|
200
|
+
return `${lines.join('\r\n')}\r\n`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (extension === '.ps1') {
|
|
205
|
+
lines.push(`powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${normalizedCommand}" %*`);
|
|
206
|
+
} else if (extension === '.cmd' || extension === '.bat') {
|
|
207
|
+
lines.push(`call "${normalizedCommand}" %*`);
|
|
208
|
+
} else {
|
|
209
|
+
lines.push(`"${normalizedCommand}" %*`);
|
|
210
|
+
}
|
|
211
|
+
lines.push('exit /b %ERRORLEVEL%');
|
|
212
|
+
return `${lines.join('\r\n')}\r\n`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function repairWindowsHermesShim(command, env, appendLog) {
|
|
216
|
+
if (!path.isAbsolute(command) || !fs.existsSync(command)) return null;
|
|
217
|
+
const localAppData = env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
218
|
+
const binDir = path.join(localAppData, 'hermes', 'bin');
|
|
219
|
+
const shimPath = path.join(binDir, 'hermes.cmd');
|
|
220
|
+
if (path.resolve(command).toLowerCase() === path.resolve(shimPath).toLowerCase()) return shimPath;
|
|
221
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
222
|
+
const body = windowsHermesCmdShimBody(command, env);
|
|
223
|
+
const previous = fs.existsSync(shimPath) ? fs.readFileSync(shimPath, 'utf8') : '';
|
|
224
|
+
if (previous !== body) {
|
|
225
|
+
fs.writeFileSync(shimPath, body);
|
|
226
|
+
appendLog?.('meta', `Repaired Windows Hermes command shim: ${shimPath}\n`);
|
|
227
|
+
}
|
|
228
|
+
return shimPath;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function repairPosixHermesShim(command, env, appendLog) {
|
|
232
|
+
void env;
|
|
233
|
+
if (!path.isAbsolute(command) || !fs.existsSync(command)) return null;
|
|
234
|
+
const localBin = path.join(os.homedir(), '.local', 'bin');
|
|
235
|
+
const shimPath = path.join(localBin, 'hermes');
|
|
236
|
+
const resolvedCommand = fs.realpathSync.native?.(command) || fs.realpathSync(command);
|
|
237
|
+
const resolvedShim = fs.existsSync(shimPath)
|
|
238
|
+
? (fs.realpathSync.native?.(shimPath) || fs.realpathSync(shimPath))
|
|
239
|
+
: null;
|
|
240
|
+
if (resolvedShim && resolvedShim === resolvedCommand) return shimPath;
|
|
241
|
+
|
|
242
|
+
fs.mkdirSync(localBin, { recursive: true });
|
|
243
|
+
const body = [
|
|
244
|
+
'#!/usr/bin/env bash',
|
|
245
|
+
'unset PYTHONPATH',
|
|
246
|
+
'unset PYTHONHOME',
|
|
247
|
+
`exec "${resolvedCommand.replace(/"/g, '\\"')}" "$@"`,
|
|
248
|
+
'',
|
|
249
|
+
].join('\n');
|
|
250
|
+
const previous = fs.existsSync(shimPath) ? fs.readFileSync(shimPath, 'utf8') : '';
|
|
251
|
+
if (previous !== body) {
|
|
252
|
+
fs.writeFileSync(shimPath, body, { mode: 0o755 });
|
|
253
|
+
fs.chmodSync(shimPath, 0o755);
|
|
254
|
+
appendLog?.('meta', `Repaired Hermes command shim: ${shimPath}\n`);
|
|
255
|
+
}
|
|
256
|
+
return shimPath;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function repairHermesCommandLaunchers(command, env = process.env, appendLog = null) {
|
|
260
|
+
if (!command || command === 'hermes') return null;
|
|
261
|
+
try {
|
|
262
|
+
return process.platform === 'win32'
|
|
263
|
+
? repairWindowsHermesShim(command, env, appendLog)
|
|
264
|
+
: repairPosixHermesShim(command, env, appendLog);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
appendLog?.('stderr', `Hermes command shim repair skipped: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
95
271
|
export function primeHermesPath(env = process.env) {
|
|
96
|
-
const next =
|
|
272
|
+
const next = buildHermesPathEnv(env);
|
|
97
273
|
env.PATH = next.PATH;
|
|
98
274
|
if ('Path' in env || next.Path) env.Path = next.Path || next.PATH;
|
|
99
275
|
}
|
|
@@ -104,26 +280,15 @@ export function hermesCommandCandidates(env = process.env) {
|
|
|
104
280
|
const resolved = findExecutableOnPath('hermes', hermesEnv);
|
|
105
281
|
|
|
106
282
|
if (env.HERMES_CLI_PATH) candidates.push(env.HERMES_CLI_PATH);
|
|
107
|
-
|
|
108
|
-
|
|
283
|
+
for (const dir of knownHermesBinDirs(env)) {
|
|
284
|
+
pushHermesCommandFiles(candidates, dir);
|
|
285
|
+
}
|
|
109
286
|
|
|
110
287
|
if (process.platform === 'win32') {
|
|
111
288
|
const localAppData = env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
112
|
-
candidates.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
path.join(env.HERMES_INSTALL_DIR, '.venv', 'Scripts', 'hermes.exe'),
|
|
116
|
-
path.join(env.HERMES_INSTALL_DIR, 'hermes.exe'),
|
|
117
|
-
] : []),
|
|
118
|
-
...(env.HERMES_HOME ? [
|
|
119
|
-
path.join(env.HERMES_HOME, 'hermes-agent', 'venv', 'Scripts', 'hermes.exe'),
|
|
120
|
-
path.join(env.HERMES_HOME, 'hermes-agent', '.venv', 'Scripts', 'hermes.exe'),
|
|
121
|
-
path.join(env.HERMES_HOME, 'hermes-agent', 'hermes.exe'),
|
|
122
|
-
] : []),
|
|
123
|
-
path.join(localAppData, 'hermes', 'hermes-agent', 'venv', 'Scripts', 'hermes.exe'),
|
|
124
|
-
path.join(localAppData, 'hermes', 'hermes-agent', '.venv', 'Scripts', 'hermes.exe'),
|
|
125
|
-
path.join(localAppData, 'hermes', 'hermes-agent', 'hermes.exe'),
|
|
126
|
-
);
|
|
289
|
+
pushHermesCommandFiles(candidates, env.HERMES_INSTALL_DIR);
|
|
290
|
+
pushHermesCommandFiles(candidates, env.HERMES_HOME ? path.join(env.HERMES_HOME, 'hermes-agent') : null);
|
|
291
|
+
pushHermesCommandFiles(candidates, path.join(localAppData, 'hermes', 'hermes-agent'));
|
|
127
292
|
} else {
|
|
128
293
|
candidates.push(
|
|
129
294
|
...(env.HERMES_INSTALL_DIR ? [
|
|
@@ -142,6 +307,9 @@ export function hermesCommandCandidates(env = process.env) {
|
|
|
142
307
|
);
|
|
143
308
|
}
|
|
144
309
|
|
|
310
|
+
if (resolved) candidates.push(resolved);
|
|
311
|
+
candidates.push('hermes');
|
|
312
|
+
|
|
145
313
|
return [...new Set(candidates.filter(Boolean))];
|
|
146
314
|
}
|
|
147
315
|
|
|
@@ -154,15 +322,10 @@ export function readHermesInstallStatus(env = process.env) {
|
|
|
154
322
|
continue;
|
|
155
323
|
}
|
|
156
324
|
|
|
157
|
-
const result =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
timeout: 5000,
|
|
162
|
-
windowsHide: true,
|
|
163
|
-
});
|
|
164
|
-
if (!result.error && result.status === 0) {
|
|
165
|
-
const version = `${result.stdout || result.stderr || ''}`.trim() || null;
|
|
325
|
+
const result = runHermesVersion(candidate, hermesEnv);
|
|
326
|
+
if (result.ok) {
|
|
327
|
+
repairHermesCommandLaunchers(candidate, hermesEnv);
|
|
328
|
+
const version = formatHermesVersionOutput(result.output);
|
|
166
329
|
return {
|
|
167
330
|
installed: true,
|
|
168
331
|
command: candidate,
|