-
Notifications
You must be signed in to change notification settings - Fork 0
Description
This is the application entry point — the REPL (Read-Eval-Print Loop) that ties everything together. It's relatively simple, which is good, but has several notable issues.
Architecture
main()
├── Load INI config
├── Setup logger
├── Initialize engines
├── Print welcome banner
└── REPL Loop
├── Read input (% prompt)
├── shlex.split()
├── Split on "->" into pipeline
└── dispatch_command() per step
Issues
1. Module docstring is wrong
"""
Parsing utilities for Multi-AI CLI.
Handles CLI argument parsing, prompt building, and @sequence step parsing logic.
"""This is the docstring for parsers.py, not main.py. It was clearly copied and never updated.
2. The -> pipeline operator conflicts with shlex.split() semantics
parts = shlex.split(user_input)
for part in parts:
if part == "->":
...Consider this input:
@gemini "use -> to indicate flow" -> @gpt "continue"
After shlex.split(), the -> inside the quoted string is preserved as part of the token "use -> to indicate flow" — that's fine. But this only works because of quoting. An unquoted arrow in a prompt:
@gemini explain what -> means in Python
Would split into a pipeline at the wrong place. The -> operator should be parsed before shlex.split, or a different delimiter should be chosen (e.g., |>, &&, or ;;).
3. Pipeline failure semantics are inconsistent with @sequence
# main.py pipeline:
if not success and len(command_chain) > 1:
print("[!] Pipeline stopped due to an error in the current step.")
breakFor single commands (len(command_chain) == 1), failure is silently ignored — the REPL just continues. But for pipelines, failure halts the chain. This means:
@gemini "bad prompt"→ failure ignored, no message@gemini "bad prompt" -> @gpt "next"→ failure stops pipeline with message
This is reasonable behavior, but the asymmetry should be documented. Also, the single-command case could benefit from a visual failure indicator.
4. No data flows between pipeline steps
The -> operator suggests data piping, but there's no mechanism to pass the output of one command as input to the next:
@gemini "write code" -w code.py -> @sh -r code.py -> @gpt "review" -r code.py
This works because the intermediate file (code.py) acts as the data channel. But a user might expect:
@gemini "write a haiku" -> @gpt "translate this to French"
To pass Gemini's output as GPT's input — which it doesn't. The -> is purely sequential execution. This should be clearly documented, or ideally the output should be chainable.
5. INI path is hardcoded
ini_path = "multi_ai_cli.ini"
if not os.path.exists(ini_path):
print(f"[!] Error: '{ini_path}' not found in the current directory.")
sys.exit(1)There's no way to specify an alternative config path via:
- Command-line argument (
--config path/to/config.ini) - Environment variable (
MULTI_AI_CONFIG)
This forces users to always run from the directory containing the INI file.
Fix:
import argparse
def main():
parser = argparse.ArgumentParser(description="Multi-AI CLI")
parser.add_argument("--config", default=os.getenv("MULTI_AI_CONFIG", "multi_ai_cli.ini"))
parser.add_argument("--no-log", action="store_true")
args = parser.parse_args()
ini_path = args.config
if not os.path.exists(ini_path):
print(f"[!] Error: '{ini_path}' not found.")
sys.exit(1)
setup_config(ini_path)
setup_logger(no_log=args.no_log)
...6. setup_logger() ignores the no_log parameter
setup_logger() # no_log is never passedThe setup_logger function in config.py accepts no_log: bool = False, but main() never passes it. There's no CLI flag to disable logging at runtime.
7. KeyboardInterrupt handling prevents clean exit
except KeyboardInterrupt:
print("\n[!] Session interrupted. Type 'exit' to quit.")Pressing Ctrl+C doesn't exit — it just prints a message. To actually quit, the user must type exit. This is a UX choice, but a common pattern is to exit on double Ctrl+C:
_last_interrupt = 0
except KeyboardInterrupt:
now = time.monotonic()
if now - _last_interrupt < 2.0:
print("\n[*] Exiting.")
break
_last_interrupt = now
print("\n[!] Press Ctrl+C again to quit, or type 'exit'.")8. Over-commented code
Nearly every line has a comment that restates the code:
setup_config(ini_path) # Load settings from the INI file
setup_logger() # Configure the logger for the application
initialize_engines() # Initialize the available AI enginesif not user_input: # Skip empty input
continue
if user_input.lower() in ["exit", "quit"]: # Allow user to exit the loop
...current_command.append(part) # Add part to the current commandThese comments add visual noise without informational value. Comments should explain why, not what. The code is already clear from the function and variable names.
9. No command history or readline support
The bare input("% ") call doesn't provide:
- Arrow key history navigation
- Tab completion
- Line editing
Adding readline support is trivial:
try:
import readline
history_file = os.path.expanduser("~/.multi_ai_history")
try:
readline.read_history_file(history_file)
except FileNotFoundError:
pass
import atexit
atexit.register(readline.write_history_file, history_file)
except ImportError:
pass # readline not available on all platforms10. The broad except Exception in the REPL swallows everything
except Exception as e:
print(f"[!] An unexpected error occurred: {e}")
logger.error(f"Main loop critical error: {e}")This catches and continues on any exception, including:
MemoryErrorSystemError- Programming bugs (
AttributeError,TypeError,KeyError)
For a REPL this is defensible (you don't want a typo to crash the session), but it should at minimum log the full traceback:
import traceback
logger.error(f"Main loop critical error: {e}\n{traceback.format_exc()}")Missing Features
| Feature | Impact |
|---|---|
--config CLI argument |
Can't use alternative config files |
--no-log CLI flag |
Can't disable logging at runtime |
| Readline/history support | Poor interactive UX |
--version flag |
No way to check installed version |
--help for available commands |
No built-in help system |
| Pipeline data passing | -> is misleading without data flow |
Summary Table
| Severity | Issue | Location |
|---|---|---|
| 🟠 High | Wrong module docstring | Top of file |
| 🟠 High | -> operator ambiguity with shlex.split() |
Pipeline parsing |
| 🟠 High | Hardcoded INI path, no CLI arguments | main() |
| 🟡 Medium | no_log parameter never passed |
setup_logger() call |
| 🟡 Medium | No readline/history support | input() call |
| 🟡 Medium | Broad except Exception without traceback |
REPL loop |
| 🟡 Medium | No data flow between pipeline steps | -> handling |
| 🟢 Low | Excessive restating comments | Throughout |
| 🟢 Low | Single Ctrl+C doesn't exit | KeyboardInterrupt handler |
| 🟢 Low | No --help or --version |
Missing CLI interface |