@pixelbyte-software/pixcode 1.50.9 → 1.51.1

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.
@@ -7,9 +7,12 @@ const baseUrl = (process.env.PIXCODE_BASE_URL || '').replace(/\/$/, '');
7
7
  const apiKey = process.env.PIXCODE_API_KEY || '';
8
8
  const appRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
9
9
  const mcpServerPath = path.join(appRoot, 'scripts', 'hermes', 'pixcode-mcp-server.mjs');
10
- const READBACK_IDLE_STABLE_MS = 2500;
10
+ const READBACK_IDLE_STABLE_MS = Math.max(
11
+ 1000,
12
+ Number.parseInt(process.env.PIXCODE_MCP_READBACK_IDLE_STABLE_MS || '8000', 10) || 8000,
13
+ );
11
14
  const DEFAULT_STARTUP_WAIT_MS = 100000;
12
- const CODEX_PROMPT_INPUT_PENDING_REASON = 'codex_prompt_input_pending';
15
+ const ALLOWED_PIXCODE_API_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
13
16
 
14
17
  const tools = [
15
18
  {
@@ -127,7 +130,7 @@ const tools = [
127
130
  },
128
131
  {
129
132
  name: 'pixcode_probe_hermes_gateway',
130
- description: 'Ask Pixcode to call Hermes Agent REST endpoints and report whether health, capabilities, and model discovery respond.',
133
+ description: 'Ask Pixcode to call Hermes Agent REST endpoints and report whether health, capabilities, model discovery, and an optional real prompt respond.',
131
134
  inputSchema: {
132
135
  type: 'object',
133
136
  properties: {
@@ -141,12 +144,155 @@ const tools = [
141
144
  },
142
145
  startIfNeeded: {
143
146
  type: 'boolean',
144
- description: 'When false, only probe an already-running gateway. Defaults to true so Pixcode keeps Hermes REST ready.',
147
+ description: 'When true, Pixcode starts the managed Hermes gateway before probing.',
145
148
  },
146
149
  },
147
150
  additionalProperties: false,
148
151
  },
149
152
  },
153
+ {
154
+ name: 'pixcode_get_hermes_diagnostics',
155
+ description: 'Read Pixcode Hermes integration diagnostics: installed command, active model/provider, Hermes toolsets, Pixcode MCP tool registration, REST gateway status, cron API state, and redacted recent error signals.',
156
+ inputSchema: {
157
+ type: 'object',
158
+ properties: {
159
+ projectPath: {
160
+ type: 'string',
161
+ description: 'Absolute project path. Omit to diagnose the first running managed Hermes gateway and default Hermes profile.',
162
+ },
163
+ },
164
+ additionalProperties: false,
165
+ },
166
+ },
167
+ {
168
+ name: 'pixcode_get_api_manifest',
169
+ description: 'Read Pixcode public API documentation manifest. Use this to discover controllable Pixcode API groups, paths, and scopes before calling pixcode_api_request.',
170
+ inputSchema: {
171
+ type: 'object',
172
+ properties: {},
173
+ additionalProperties: false,
174
+ },
175
+ },
176
+ {
177
+ name: 'pixcode_api_request',
178
+ description: 'Call the authenticated local Pixcode REST API. Use this for full Pixcode control after reading pixcode_get_api_manifest. Path must be a local /api/... path or /health; never pass an external URL.',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: {
182
+ method: {
183
+ type: 'string',
184
+ enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
185
+ description: 'HTTP method to use.',
186
+ },
187
+ path: {
188
+ type: 'string',
189
+ description: 'Local Pixcode path, for example /api/projects, /api/providers/codex/auth/status?refresh=1, or /api/remote/config.',
190
+ },
191
+ body: {
192
+ type: 'object',
193
+ description: 'Optional JSON body for POST, PUT, PATCH, or DELETE requests.',
194
+ additionalProperties: true,
195
+ },
196
+ },
197
+ required: ['method', 'path'],
198
+ additionalProperties: false,
199
+ },
200
+ },
201
+ {
202
+ name: 'pixcode_hermes_gateway_request',
203
+ description: 'Call the Pixcode-managed Hermes REST gateway for advanced Hermes features such as /v1/runs, /v1/responses, /api/jobs cron management, /v1/capabilities, and /health. Use startIfNeeded when the gateway is not already running.',
204
+ inputSchema: {
205
+ type: 'object',
206
+ properties: {
207
+ method: {
208
+ type: 'string',
209
+ enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
210
+ },
211
+ endpoint: {
212
+ type: 'string',
213
+ description: 'Hermes gateway endpoint, for example /api/jobs, /api/jobs/<id>/run, /v1/capabilities, or /v1/responses.',
214
+ },
215
+ body: {
216
+ type: 'object',
217
+ additionalProperties: true,
218
+ },
219
+ projectPath: {
220
+ type: 'string',
221
+ description: 'Absolute project path for the managed Hermes gateway.',
222
+ },
223
+ startIfNeeded: {
224
+ type: 'boolean',
225
+ description: 'Start the managed Hermes gateway first when it is not already running.',
226
+ },
227
+ },
228
+ required: ['method', 'endpoint'],
229
+ additionalProperties: false,
230
+ },
231
+ },
232
+ {
233
+ name: 'pixcode_manage_hermes_cron',
234
+ description: 'Create, list, update, pause, resume, run, or delete Hermes cron jobs through the Pixcode-managed Hermes REST gateway. Cron jobs can run in a project workdir and use Hermes skills/toolsets.',
235
+ inputSchema: {
236
+ type: 'object',
237
+ properties: {
238
+ action: {
239
+ type: 'string',
240
+ enum: ['list', 'create', 'get', 'update', 'delete', 'pause', 'resume', 'run'],
241
+ },
242
+ jobId: {
243
+ type: 'string',
244
+ description: 'Required for get, update, delete, pause, resume, and run.',
245
+ },
246
+ projectPath: {
247
+ type: 'string',
248
+ description: 'Absolute project path for the managed Hermes gateway and default cron workdir.',
249
+ },
250
+ name: { type: 'string' },
251
+ schedule: { type: 'string' },
252
+ prompt: { type: 'string' },
253
+ workdir: { type: 'string' },
254
+ skills: {
255
+ type: 'array',
256
+ items: { type: 'string' },
257
+ },
258
+ delivery: { type: 'string' },
259
+ startIfNeeded: { type: 'boolean' },
260
+ },
261
+ required: ['action'],
262
+ additionalProperties: false,
263
+ },
264
+ },
265
+ {
266
+ name: 'pixcode_send_cli_input',
267
+ description: 'Send text or an Enter key directly to an existing visible Pixcode provider terminal. Use this when a terminal is already open and the user asks Hermes to continue that exact visible session.',
268
+ inputSchema: {
269
+ type: 'object',
270
+ properties: {
271
+ provider: {
272
+ type: 'string',
273
+ enum: ['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode'],
274
+ },
275
+ projectPath: {
276
+ type: 'string',
277
+ description: 'Absolute project path. Omit to use the newest visible terminal for the provider.',
278
+ },
279
+ input: {
280
+ type: 'string',
281
+ description: 'Text to type. May be empty when submit=true to press Enter on already typed input.',
282
+ },
283
+ submit: {
284
+ type: 'boolean',
285
+ description: 'Append Enter after input. Defaults to true.',
286
+ },
287
+ launchId: {
288
+ type: 'number',
289
+ description: 'Optional Pixcode terminal launch id to target one visible terminal.',
290
+ },
291
+ },
292
+ required: ['provider'],
293
+ additionalProperties: false,
294
+ },
295
+ },
150
296
  ];
151
297
 
152
298
  function send(payload) {
@@ -196,6 +342,73 @@ async function pixcodeFetch(endpoint, options = {}) {
196
342
  return body;
197
343
  }
198
344
 
345
+ function normalizeLocalPixcodePath(pathValue) {
346
+ const endpoint = typeof pathValue === 'string' ? pathValue.trim() : '';
347
+ if (!endpoint) {
348
+ throw new Error('Pixcode API path is required.');
349
+ }
350
+ if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(endpoint) || endpoint.startsWith('//')) {
351
+ throw new Error('Pixcode API path must be local; external URLs are not allowed.');
352
+ }
353
+ if (endpoint !== '/health' && !endpoint.startsWith('/api/')) {
354
+ throw new Error('Pixcode API path must start with /api/ or be /health.');
355
+ }
356
+ return endpoint;
357
+ }
358
+
359
+ function normalizeHermesGatewayEndpoint(endpointValue) {
360
+ const endpoint = typeof endpointValue === 'string' ? endpointValue.trim() : '';
361
+ if (!endpoint) {
362
+ throw new Error('Hermes gateway endpoint is required.');
363
+ }
364
+ if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(endpoint) || endpoint.startsWith('//')) {
365
+ throw new Error('Hermes gateway endpoint must be local; external URLs are not allowed.');
366
+ }
367
+ if (!endpoint.startsWith('/')) {
368
+ throw new Error('Hermes gateway endpoint must start with /.');
369
+ }
370
+ if (
371
+ endpoint !== '/health' &&
372
+ endpoint !== '/health/detailed' &&
373
+ !endpoint.startsWith('/v1/') &&
374
+ !endpoint.startsWith('/api/')
375
+ ) {
376
+ throw new Error('Hermes gateway endpoint must be /health, /v1/..., or /api/....');
377
+ }
378
+ return endpoint;
379
+ }
380
+
381
+ function normalizeHttpMethod(methodValue) {
382
+ const method = String(methodValue || 'GET').trim().toUpperCase();
383
+ if (!ALLOWED_PIXCODE_API_METHODS.has(method)) {
384
+ throw new Error(`Unsupported HTTP method: ${method || '(empty)'}`);
385
+ }
386
+ return method;
387
+ }
388
+
389
+ async function pixcodeJsonRequest(pathValue, { method = 'GET', body } = {}) {
390
+ const endpoint = normalizeLocalPixcodePath(pathValue);
391
+ const normalizedMethod = normalizeHttpMethod(method);
392
+ const requestOptions = { method: normalizedMethod };
393
+ if (typeof body !== 'undefined' && normalizedMethod !== 'GET') {
394
+ requestOptions.body = JSON.stringify(body);
395
+ }
396
+ return pixcodeFetch(endpoint, requestOptions);
397
+ }
398
+
399
+ async function sendProviderTerminalInput(provider, projectPath, input, submit = true, launchId = null) {
400
+ return pixcodeFetch('/api/shell/sessions/provider-input', {
401
+ method: 'POST',
402
+ body: JSON.stringify({
403
+ provider,
404
+ projectPath: projectPath || null,
405
+ input: typeof input === 'string' ? input : '',
406
+ submit: submit !== false,
407
+ launchId: Number(launchId || 0) || null,
408
+ }),
409
+ });
410
+ }
411
+
199
412
  async function readProviderStatus(provider) {
200
413
  const body = await pixcodeFetch(`/api/providers/${encodeURIComponent(provider)}/auth/status?refresh=1`);
201
414
  return body?.data ?? body;
@@ -219,29 +432,13 @@ function getLastMatchIndex(text, pattern) {
219
432
  return lastIndex;
220
433
  }
221
434
 
222
- function normalizePromptInput(value) {
223
- return String(value || '').replace(/(?:\r\n|\r|\n)+$/u, '').trim();
224
- }
225
-
226
- function hasCodexPromptInputPending(output, expectedInput) {
227
- const expected = normalizePromptInput(expectedInput);
228
- if (!expected) return false;
229
- const match = String(output || '').match(/(?:^|\n)[^\S\r\n]*[›❯][ \t]+([^\r\n]+)[\r\n]*$/u);
230
- if (!match) return false;
231
- return normalizePromptInput(match[1]) === expected;
232
- }
233
-
234
- function inferTerminalState(provider, terminalOutput, expectedInput = null) {
435
+ function inferTerminalState(provider, terminalOutput) {
235
436
  if (!terminalOutput) return 'unknown';
236
- const output = String(terminalOutput.output || '');
237
- if (provider === 'codex' && hasCodexPromptInputPending(output, expectedInput)) {
238
- terminalOutput.terminalStateReason = terminalOutput.terminalStateReason || CODEX_PROMPT_INPUT_PENDING_REASON;
239
- return 'busy';
240
- }
241
437
  if (typeof terminalOutput.terminalState === 'string') return terminalOutput.terminalState;
242
438
  if (typeof terminalOutput.isBusy === 'boolean') return terminalOutput.isBusy ? 'busy' : 'idle';
243
439
  if (terminalOutput.active === false) return terminalOutput.output ? 'idle' : 'unknown';
244
440
 
441
+ const output = String(terminalOutput.output || '');
245
442
  if (!output.trim()) return 'unknown';
246
443
  if (/Process exited with code/iu.test(output)) return 'idle';
247
444
 
@@ -257,10 +454,6 @@ function inferTerminalState(provider, terminalOutput, expectedInput = null) {
257
454
  getLastMatchIndex(output, /(?:^|\n)\s*›(?:\s|$)/gu),
258
455
  getLastMatchIndex(output, /(?:^|\n)\s*❯(?:\s|$)/gu),
259
456
  );
260
- if (hasCodexPromptInputPending(output, expectedInput)) {
261
- terminalOutput.terminalStateReason = terminalOutput.terminalStateReason || CODEX_PROMPT_INPUT_PENDING_REASON;
262
- return 'busy';
263
- }
264
457
  if (lastPrompt >= 0) return lastStrongBusy > lastPrompt ? 'busy' : 'idle';
265
458
  if (lastBusy >= 0) return 'busy';
266
459
  return 'unknown';
@@ -270,13 +463,13 @@ function inferTerminalState(provider, terminalOutput, expectedInput = null) {
270
463
  return 'unknown';
271
464
  }
272
465
 
273
- function isTerminalReadbackFinal(provider, terminalOutput, expectedInput = null) {
274
- const terminalState = inferTerminalState(provider, terminalOutput, expectedInput);
466
+ function isTerminalReadbackFinal(provider, terminalOutput) {
467
+ const terminalState = inferTerminalState(provider, terminalOutput);
275
468
  return terminalState === 'idle' || terminalState === 'completed' || terminalState === 'exited' || terminalState === 'failed';
276
469
  }
277
470
 
278
- function isTerminalReadbackHardFinal(provider, terminalOutput, expectedInput = null) {
279
- const terminalState = inferTerminalState(provider, terminalOutput, expectedInput);
471
+ function isTerminalReadbackHardFinal(provider, terminalOutput) {
472
+ const terminalState = inferTerminalState(provider, terminalOutput);
280
473
  return terminalState === 'completed' || terminalState === 'exited' || terminalState === 'failed' || Boolean(terminalOutput?.terminalFailed);
281
474
  }
282
475
 
@@ -290,7 +483,41 @@ function getReadbackFingerprint(terminalOutput) {
290
483
  ].join('\n---pixcode-readback---\n');
291
484
  }
292
485
 
293
- async function waitForProviderTerminalOutput(provider, projectPath, waitMs, launchId = null, expectedInput = null) {
486
+ function outputHasProviderPrompt(provider, output) {
487
+ const text = String(output || '');
488
+ if (provider === 'codex') {
489
+ return /(?:^|\n)\s*[›❯]\s*$/u.test(text) || /(?:^|\n)\s*›\s+[^\n]*$/u.test(text);
490
+ }
491
+ return /(?:^|\n).{0,80}(?:>\s*|❯\s*)$/u.test(text);
492
+ }
493
+
494
+ function startupInputLooksStuckAtPrompt(provider, terminalOutput, startupInput) {
495
+ if (!startupInput || !terminalOutput?.output || terminalOutput.isBusy) return false;
496
+ const output = String(terminalOutput.output || '');
497
+ const escapedInput = startupInput.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
498
+ if (provider === 'codex') {
499
+ return new RegExp(`(?:^|\\n)\\s*[›❯]\\s*${escapedInput}\\s*$`, 'u').test(output);
500
+ }
501
+ return output.endsWith(startupInput) || outputHasProviderPrompt(provider, output);
502
+ }
503
+
504
+ async function recoverStuckStartupInput(provider, projectPath, startupInput, terminalOutput, launchId = null) {
505
+ if (!startupInputLooksStuckAtPrompt(provider, terminalOutput, startupInput)) {
506
+ return null;
507
+ }
508
+
509
+ const output = String(terminalOutput?.output || '');
510
+ const inputAlreadyVisible = output.includes(startupInput);
511
+ return sendProviderTerminalInput(
512
+ provider,
513
+ projectPath,
514
+ inputAlreadyVisible ? '' : startupInput,
515
+ true,
516
+ launchId,
517
+ );
518
+ }
519
+
520
+ async function waitForProviderTerminalOutput(provider, projectPath, waitMs, launchId = null) {
294
521
  const startedAt = Date.now();
295
522
  let latestOutput = null;
296
523
  let stableFingerprint = null;
@@ -306,8 +533,8 @@ async function waitForProviderTerminalOutput(provider, projectPath, waitMs, laun
306
533
  error: error instanceof Error ? error.message : String(error),
307
534
  }));
308
535
 
309
- if (latestOutput?.output && isTerminalReadbackFinal(provider, latestOutput, expectedInput)) {
310
- if (isTerminalReadbackHardFinal(provider, latestOutput, expectedInput)) {
536
+ if (latestOutput?.output && isTerminalReadbackFinal(provider, latestOutput)) {
537
+ if (isTerminalReadbackHardFinal(provider, latestOutput)) {
311
538
  stableFinal = true;
312
539
  break;
313
540
  }
@@ -328,7 +555,7 @@ async function waitForProviderTerminalOutput(provider, projectPath, waitMs, laun
328
555
  } while (Date.now() - startedAt < waitMs);
329
556
 
330
557
  if (latestOutput && !latestOutput.terminalState) {
331
- latestOutput.terminalState = inferTerminalState(provider, latestOutput, expectedInput);
558
+ latestOutput.terminalState = inferTerminalState(provider, latestOutput);
332
559
  }
333
560
  if (latestOutput && typeof latestOutput.isBusy !== 'boolean') {
334
561
  latestOutput.isBusy = latestOutput.terminalState === 'busy';
@@ -457,10 +684,22 @@ async function callTool(name, args = {}) {
457
684
  const requestedWaitMs = Number(args.waitForCompletionMs ?? args.waitForOutputMs ?? defaultWaitMs);
458
685
  const waitForOutputMs = Math.min(600000, Math.max(0, requestedWaitMs));
459
686
  if (waitForOutputMs > 0) {
460
- terminalOutput = await waitForProviderTerminalOutput(provider, projectPath, waitForOutputMs, launchId, startupInput);
687
+ terminalOutput = await waitForProviderTerminalOutput(provider, projectPath, waitForOutputMs, launchId);
688
+ if (startupInput && terminalOutput && !isTerminalReadbackFinal(provider, terminalOutput)) {
689
+ const recovery = await recoverStuckStartupInput(provider, projectPath, startupInput, terminalOutput, launchId).catch((error) => ({
690
+ error: error instanceof Error ? error.message : String(error),
691
+ }));
692
+ if (recovery) {
693
+ const recoveredOutput = await waitForProviderTerminalOutput(provider, projectPath, Math.min(waitForOutputMs, 120000), launchId);
694
+ terminalOutput = recoveredOutput || terminalOutput;
695
+ if (terminalOutput) {
696
+ terminalOutput.startupInputRecovery = recovery;
697
+ }
698
+ }
699
+ }
461
700
  }
462
701
  const terminalOutputFinal = terminalOutput
463
- ? Boolean(terminalOutput.terminalOutputFinal ?? isTerminalReadbackFinal(provider, terminalOutput, startupInput))
702
+ ? Boolean(terminalOutput.terminalOutputFinal ?? isTerminalReadbackFinal(provider, terminalOutput))
464
703
  : false;
465
704
  return textResult(JSON.stringify({
466
705
  launched: true,
@@ -516,9 +755,117 @@ async function callTool(name, args = {}) {
516
755
  body: JSON.stringify({
517
756
  projectPath: args.projectPath || null,
518
757
  input: args.input || null,
758
+ startIfNeeded: args.startIfNeeded === true,
759
+ }),
760
+ });
761
+ return textResult(JSON.stringify(body, null, 2));
762
+ }
763
+
764
+ if (name === 'pixcode_get_hermes_diagnostics') {
765
+ const projectPath = typeof args.projectPath === 'string' && args.projectPath.trim()
766
+ ? `?projectPath=${encodeURIComponent(args.projectPath.trim())}`
767
+ : '';
768
+ const body = await pixcodeFetch(`/api/orchestration/hermes/diagnostics${projectPath}`);
769
+ return textResult(JSON.stringify(body, null, 2));
770
+ }
771
+
772
+ if (name === 'pixcode_get_api_manifest') {
773
+ const body = await pixcodeJsonRequest('/api/public/manifest', { method: 'GET' });
774
+ return textResult(JSON.stringify(body, null, 2));
775
+ }
776
+
777
+ if (name === 'pixcode_api_request') {
778
+ const body = await pixcodeJsonRequest(args.path, {
779
+ method: args.method || 'GET',
780
+ body: args.body,
781
+ });
782
+ return textResult(JSON.stringify(body, null, 2));
783
+ }
784
+
785
+ if (name === 'pixcode_hermes_gateway_request') {
786
+ const endpoint = normalizeHermesGatewayEndpoint(args.endpoint);
787
+ const body = await pixcodeFetch('/api/orchestration/hermes/gateway/request', {
788
+ method: 'POST',
789
+ body: JSON.stringify({
790
+ method: normalizeHttpMethod(args.method || 'GET'),
791
+ endpoint,
792
+ body: args.body || null,
793
+ projectPath: args.projectPath || null,
794
+ startIfNeeded: args.startIfNeeded === true,
795
+ }),
796
+ });
797
+ return textResult(JSON.stringify(body, null, 2));
798
+ }
799
+
800
+ if (name === 'pixcode_manage_hermes_cron') {
801
+ const action = String(args.action || '').trim();
802
+ const jobId = typeof args.jobId === 'string' && args.jobId.trim() ? args.jobId.trim() : null;
803
+ const jobBody = {
804
+ name: args.name || undefined,
805
+ schedule: args.schedule || undefined,
806
+ prompt: args.prompt || undefined,
807
+ workdir: args.workdir || args.projectPath || undefined,
808
+ skills: Array.isArray(args.skills) ? args.skills : undefined,
809
+ delivery: args.delivery || undefined,
810
+ };
811
+ Object.keys(jobBody).forEach((key) => {
812
+ if (typeof jobBody[key] === 'undefined') delete jobBody[key];
813
+ });
814
+
815
+ let method = 'GET';
816
+ let endpoint = '/api/jobs';
817
+ let body = null;
818
+ if (action === 'create') {
819
+ method = 'POST';
820
+ body = jobBody;
821
+ } else if (action === 'list') {
822
+ method = 'GET';
823
+ } else {
824
+ if (!jobId) throw new Error(`jobId is required for Hermes cron action "${action}".`);
825
+ const encodedJobId = encodeURIComponent(jobId);
826
+ if (action === 'get') {
827
+ method = 'GET';
828
+ endpoint = `/api/jobs/${encodedJobId}`;
829
+ } else if (action === 'update') {
830
+ method = 'PATCH';
831
+ endpoint = `/api/jobs/${encodedJobId}`;
832
+ body = jobBody;
833
+ } else if (action === 'delete') {
834
+ method = 'DELETE';
835
+ endpoint = `/api/jobs/${encodedJobId}`;
836
+ } else if (action === 'pause' || action === 'resume' || action === 'run') {
837
+ method = 'POST';
838
+ endpoint = `/api/jobs/${encodedJobId}/${action}`;
839
+ } else {
840
+ throw new Error(`Unsupported Hermes cron action: ${action || '(empty)'}`);
841
+ }
842
+ }
843
+
844
+ const response = await pixcodeFetch('/api/orchestration/hermes/gateway/request', {
845
+ method: 'POST',
846
+ body: JSON.stringify({
847
+ method,
848
+ endpoint,
849
+ body,
850
+ projectPath: args.projectPath || args.workdir || null,
519
851
  startIfNeeded: args.startIfNeeded !== false,
520
852
  }),
521
853
  });
854
+ return textResult(JSON.stringify(response, null, 2));
855
+ }
856
+
857
+ if (name === 'pixcode_send_cli_input') {
858
+ const provider = String(args.provider || '');
859
+ const projectPath = typeof args.projectPath === 'string' && args.projectPath.trim()
860
+ ? args.projectPath.trim()
861
+ : null;
862
+ const body = await sendProviderTerminalInput(
863
+ provider,
864
+ projectPath,
865
+ typeof args.input === 'string' ? args.input : '',
866
+ args.submit !== false,
867
+ Number(args.launchId || 0) || null,
868
+ );
522
869
  return textResult(JSON.stringify(body, null, 2));
523
870
  }
524
871
 
@@ -37,7 +37,7 @@ assert.match(hermesInstallJobs, /windowsCmdFallbackCommand/, 'Hermes install sho
37
37
  assert.match(hermesInstallJobs, /retrying through cmd\.exe without elevation/, 'Hermes install logs should explain the no-admin Windows EPERM fallback.');
38
38
  assert.match(read('scripts/hermes/configure-pixcode-mcp.mjs'), /platform_toolsets/, 'Pixcode MCP config should enable the Pixcode toolset for Hermes API server runs.');
39
39
  assert.match(read('scripts/hermes/configure-pixcode-mcp.mjs'), /api_server:[\s\S]+pixcode/, 'Hermes API server should see Pixcode MCP tools during /v1/runs.');
40
- assert.match(read('scripts/hermes/configure-pixcode-mcp.mjs'), /const cliToolsets = \['mcp-pixcode'\]/, 'Hermes CLI profile should use Pixcode MCP-only tools by default so provider CLIs stay visible.');
40
+ assert.match(read('scripts/hermes/configure-pixcode-mcp.mjs'), /const cliToolsets = \['hermes-cli', 'mcp-pixcode'\]/, 'Hermes CLI profile should keep the full Hermes toolset while adding Pixcode MCP.');
41
41
  assert.match(serverIndex, /buildHermesPathEnv\(process\.env/, 'Shell PTYs should inherit Hermes bin directories so typing hermes in Pixcode terminal works on Windows.');
42
42
  assert.match(serverIndex, /env: shellEnv/, 'Shell PTYs should use the augmented shell environment instead of raw process.env.');
43
43
  assert.doesNotMatch(serverIndex, /pixcode:hermes:start/, 'Hermes H button should not depend on a shell sentinel.');