Skip to main content
Glama
fake_cli.py5.02 kB
#!/usr/bin/env python3 """Fake CLI for integration testing. This script simulates a long-running CLI agent that outputs JSONL events. It properly responds to SIGINT/SIGTERM signals and exits gracefully. Usage: python fake_cli.py [--duration SECONDS] [--interval SECONDS] [--fail] [--exit-code CODE] Arguments: --duration: Total duration to run (default: 10 seconds) --interval: Interval between events (default: 0.5 seconds) --fail: Exit with non-zero code --exit-code: Specific exit code (default: 0, or 1 if --fail) The script reads a prompt from stdin and outputs JSONL events to stdout. Event format (compatible with Codex/Gemini/Claude parsers): {"type": "message", "role": "assistant", "content": "...", "session_id": "..."} """ from __future__ import annotations import argparse import json import os import signal import sys import time from typing import NoReturn # Flag to indicate if we should stop _should_stop = False _exit_code = 0 def signal_handler(signum: int, frame) -> None: """Handle SIGINT/SIGTERM signals.""" global _should_stop, _exit_code signal_name = signal.Signals(signum).name # Output a system event indicating cancellation emit_event({ "type": "system", "subtype": "cancelled", "message": f"Received {signal_name}, stopping gracefully", "severity": "warning", }) _should_stop = True # Exit code 130 for SIGINT (128 + 2), 143 for SIGTERM (128 + 15) _exit_code = 128 + signum def emit_event(data: dict) -> None: """Emit a JSONL event to stdout.""" print(json.dumps(data, ensure_ascii=False), flush=True) def main() -> NoReturn: """Main entry point.""" global _exit_code parser = argparse.ArgumentParser(description="Fake CLI for testing") parser.add_argument("--duration", type=float, default=10.0, help="Duration in seconds") parser.add_argument("--interval", type=float, default=0.5, help="Interval between events") parser.add_argument("--fail", action="store_true", help="Exit with error") parser.add_argument("--exit-code", type=int, default=None, help="Specific exit code") parser.add_argument("--session-id", type=str, default="fake-session-123", help="Session ID") # Accept any positional arguments (for compatibility with CLI patterns) parser.add_argument("args", nargs="*", help="Additional arguments") args = parser.parse_args() # Set up signal handlers signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # Read prompt from stdin (non-blocking for testing) prompt = "" if not sys.stdin.isatty(): try: # Set a timeout for stdin read to avoid hanging import select if select.select([sys.stdin], [], [], 0.1)[0]: prompt = sys.stdin.read().strip() except Exception: pass # Emit session start event emit_event({ "type": "session", "session_id": args.session_id, "status": "started", }) # Emit initial system info emit_event({ "type": "system", "subtype": "info", "message": f"Processing prompt: {prompt[:50]}..." if len(prompt) > 50 else f"Processing prompt: {prompt}", "model": "fake-model-1.0", }) # Simulate long-running operation with periodic events start_time = time.time() event_count = 0 total_response = "" while not _should_stop: elapsed = time.time() - start_time if elapsed >= args.duration: break # Emit progress events event_count += 1 chunk = f"Processing step {event_count}... " total_response += chunk emit_event({ "type": "message", "role": "assistant", "content": chunk, "is_delta": True, "session_id": args.session_id, }) time.sleep(args.interval) # If not cancelled, emit final response if not _should_stop: final_message = f"Task completed successfully. Processed {event_count} steps." emit_event({ "type": "message", "role": "assistant", "content": final_message, "is_delta": False, "session_id": args.session_id, }) # Emit stats emit_event({ "type": "stats", "stats": { "input_tokens": 100, "output_tokens": 200, "total_input_tokens": 100, "total_output_tokens": 200, }, "model": "fake-model-1.0", }) # Emit session end emit_event({ "type": "session", "session_id": args.session_id, "status": "completed", }) # Determine exit code if args.exit_code is not None: _exit_code = args.exit_code elif args.fail: _exit_code = 1 sys.exit(_exit_code) if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shiharuharu/cli-agent-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server