@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.
@@ -80,20 +80,196 @@ function knownHermesBinDirs(env = process.env) {
80
80
  ];
81
81
  }
82
82
 
83
- function buildHermesEnv(baseEnv = process.env, extras = {}) {
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 = buildHermesEnv(env);
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
- if (resolved) candidates.push(resolved);
108
- candidates.push('hermes');
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.push(
113
- ...(env.HERMES_INSTALL_DIR ? [
114
- path.join(env.HERMES_INSTALL_DIR, 'venv', 'Scripts', 'hermes.exe'),
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 = spawn.sync(candidate, ['--version'], {
158
- encoding: 'utf8',
159
- env: hermesEnv,
160
- stdio: ['ignore', 'pipe', 'pipe'],
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,