Hagicode.Libs: Engineering Practice for Unified Integration of Multiple AI Coding Assistant CLIs
Hagicode.Libs: Engineering Practice for Unified Integration of Multiple AI Coding Assistant CLIs
Section titled “Hagicode.Libs: Engineering Practice for Unified Integration of Multiple AI Coding Assistant CLIs”During the development of the HagiCode project, we needed to integrate multiple AI coding assistant CLIs at the same time, including Claude Code, Codex, and CodeBuddy. Each CLI has different interfaces, parameters, and output formats, and the repeated integration code made the project harder and harder to maintain. In this article, we share how we built a unified abstraction layer with HagiCode.Libs to solve this engineering pain point. You could also say it is simply some hard-earned experience gathered from the pitfalls we have already hit.
Background
Section titled “Background”The market for AI coding assistants is quite lively now. Besides Claude Code, there are also OpenAI’s Codex, Zhipu’s CodeBuddy, and more. As an AI coding assistant project, HagiCode needs to integrate these different CLI tools across multiple subprojects, including desktop, backend, and web.
At first, the problem was manageable. Integrating one CLI was only a few hundred lines of code. But as the number of CLIs we needed to support kept growing, things started to get messy.
Each CLI has its own command-line argument format, different environment variable requirements, and a wide variety of output formats. Some output JSON, some output streaming JSON, and some output plain text. On top of that, there are cross-platform compatibility issues. Executable discovery and process management work very differently between Windows and Unix systems, so code duplication kept increasing. In truth, it was just a bit more Ctrl+C and Ctrl+V, but maintenance quickly became painful.
The most frustrating part was that every time we wanted to add support for a new CLI capability, we had to change the same code in several projects. That approach was clearly not sustainable in the long run. Code has a temper too; duplicate it too many times and it starts causing trouble.
About HagiCode
Section titled “About HagiCode”The approach shared in this article comes from our practical experience in the HagiCode project. HagiCode is an open-source AI coding assistant project that needs to maintain multiple subprojects at the same time, including a frontend VSCode extension, backend AI services, and a cross-platform desktop client. In a way, it was exactly this complex, multi-language, multi-platform environment that led to the birth of HagiCode.Libs. You could say we were forced into it, and so be it.
Analysis: Finding Common Ground
Section titled “Analysis: Finding Common Ground”Although these AI coding assistant CLIs each have their own characteristics, from a technical perspective they share several obvious traits:
Similar interaction patterns: they all start a CLI process, send a prompt, receive streaming responses, parse messages, and then either end or continue the session. At the end of the day, the whole flow follows the same basic mold.
Similar configuration needs: they all need API key authentication, working directory setup, model selection, tool permission control, and session management. After all, everyone is making a living from APIs; the differences are mostly a matter of flavor.
The same cross-platform challenges: they all need to solve executable path resolution (claude vs claude.exe vs /usr/local/bin/claude), process startup and environment variable handling, shell command escaping, and argument construction. Cross-platform work is painful no matter how you describe it. Only people who have stepped into the traps really understand the difference between Windows and Unix.
Based on this analysis, we needed a unified abstraction layer that could provide a consistent interface, encapsulate cross-platform CLI discovery logic, handle streaming output parsing, and support both dependency injection and non-DI scenarios. It is the kind of problem that makes your head hurt just thinking about it, but you still have to face it. After all, it is our own project, so we have to finish it even if we have to cry our way through it.
Solution: HagiCode.Libs
Section titled “Solution: HagiCode.Libs”We created HagiCode.Libs, a lightweight .NET 10 library workspace released under the MIT license and now published on GitHub. It may not be some world-shaking masterpiece, but it is genuinely useful for solving real problems.
Project structure
Section titled “Project structure”HagiCode.Libs/├── src/│ ├── HagiCode.Libs.Core/ # Core capabilities│ │ ├── Discovery/ # CLI executable discovery│ │ ├── Process/ # Cross-platform process management│ │ ├── Transport/ # Streaming message transport│ │ └── Environment/ # Runtime environment resolution│ ├── HagiCode.Libs.Providers/ # Provider implementations│ │ ├── ClaudeCode/ # Claude Code provider│ │ ├── Codex/ # Codex provider│ │ └── Codebuddy/ # CodeBuddy provider│ ├── HagiCode.Libs.ConsoleTesting/ # Testing framework│ ├── HagiCode.Libs.ClaudeCode.Console/│ ├── HagiCode.Libs.Codex.Console/│ └── HagiCode.Libs.Codebuddy.Console/└── tests/ # xUnit testsDesign goals
Section titled “Design goals”When designing HagiCode.Libs, we followed a few principles. They all came from lessons learned the hard way:
Zero heavy framework dependencies: it does not depend on ABP or any other large framework, which keeps it lightweight. These days, the fewer dependencies you have, the fewer headaches you get. Most people have already been beaten up by dependency hell at least once.
Cross-platform support: native support for Windows, macOS, and Linux, without writing separate code for different platforms. One codebase that runs everywhere is a pretty good thing.
Streaming processing: CLI output is handled with asynchronous streams, which fits modern .NET programming patterns much better. Times change, and async is king.
Flexible integration: it supports dependency injection scenarios while also allowing direct instantiation. Different people have different preferences, so we wanted it to be convenient either way.
How to use it
Section titled “How to use it”Through dependency injection
Section titled “Through dependency injection”If your project already uses dependency injection, such as ASP.NET Core or the generic host, you can integrate it directly. It is a small thing, but a well-behaved one:
using HagiCode.Libs.Providers;using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();services.AddHagiCodeLibs();
await using var provider = services.BuildServiceProvider();var claude = provider.GetRequiredService<ICliProvider<ClaudeCodeOptions>>();
var options = new ClaudeCodeOptions{ ApiKey = "your-api-key", Model = "claude-sonnet-4-20250514"};
await foreach (var message in claude.ExecuteAsync(options, "Hello, Claude!")){ Console.WriteLine($"{message.Type}: {message.Content}");}Direct instantiation
Section titled “Direct instantiation”If you are writing a simple script or working in a non-DI scenario, creating an instance directly also works. Put simply, it depends on your personal preference:
var claude = new ClaudeCodeProvider();var options = new ClaudeCodeOptions{ ApiKey = "sk-ant-xxx", Model = "claude-sonnet-4-20250514"};
await foreach (var message in claude.ExecuteAsync(options, "Help me write a quicksort")){ // Handle messages}Both approaches use the same underlying implementation, so you can choose the integration style that best fits your project. There is no universal right answer in this world. What suits you is the best option. It may sound cliché, but it is true.
Practical experience
Section titled “Practical experience”1. Dedicated testing consoles
Section titled “1. Dedicated testing consoles”Each provider has its own dedicated testing console project, making it easier to validate the integration independently. Testing is one of those things where if you are going to do it, you should do it properly:
# Claude Code testsdotnet run --project src/HagiCode.Libs.ClaudeCode.Console -- --test-providerdotnet run --project src/HagiCode.Libs.ClaudeCode.Console -- --test-all claude
# CodeBuddy testsdotnet run --project src/HagiCode.Libs.Codebuddy.Console -- --test-provider codebuddy-cli
# Codex testsdotnet run --project src/HagiCode.Libs.Codex.Console -- --test-provider codex-cliThe testing scenarios cover several key cases:
- Ping: health check to confirm the CLI is available
- Simple Prompt: basic prompt test
- Complex Prompt: multi-turn conversation test
- Session Restore/Resume: session recovery test
- Repository Analysis: repository analysis test
This standalone testing console design is especially useful during debugging because it lets us quickly identify whether the issue is in the HagiCode.Libs layer or in the CLI itself. Debugging is really just about finding where the problem is. Once the direction is right, you are already halfway there.
2. Cross-platform CI/CD validation
Section titled “2. Cross-platform CI/CD validation”Cross-platform compatibility is one of the core goals of HagiCode.Libs. We configured the GitHub Actions workflow .github/workflows/cli-discovery-cross-platform.yml to run real CLI discovery validation across ubuntu-latest, macos-latest, and windows-latest.
This ensures that every code change does not break cross-platform compatibility. During local development, you can also reproduce it with the following commands. After all, you cannot ask CI to take the blame for everything. Your local environment should be able to run it too:
npm install --global @anthropic-ai/claude-code@2.1.79HAGICODE_REAL_CLI_TESTS=1 dotnet test --filter "Category=RealCli"3. Message stream processing
Section titled “3. Message stream processing”HagiCode.Libs uses asynchronous streams to process CLI output. Compared with traditional callback or event-based approaches, this fits the asynchronous programming style of modern .NET much better. In the end, this is simply how technology moves forward, whether anyone likes it or not:
public async IAsyncEnumerable<CliMessage> ExecuteAsync( TOptions options, string prompt, [EnumeratorCancellation] CancellationToken cancellationToken = default){ // Start the CLI process // Parse streaming JSON output // Yield the CliMessage sequence}The message types include:
user: user messageassistant: assistant responsetool_use: tool invocationresult: session end
This design lets callers handle streaming output flexibly, whether for real-time display, buffered post-processing, or forwarding to other services. Why worry whether the sky is sunny or cloudy? What matters is that once the idea opens up, you can use it however you like.
4. Git repository exploration
Section titled “4. Git repository exploration”The HagiCode.Libs.Exploration module provides Git repository discovery and status checking, which is especially useful in repository analysis scenarios. This feature was also born out of necessity, because HagiCode needs to analyze repositories:
// Discover Git repositoriesvar repositories = await GitRepositoryDiscovery.DiscoverAsync("/path/to/search");
// Get repository informationvar info = await GitRepository.GetInfoAsync(repoPath);Console.WriteLine($"Branch: {info.Branch}, Remote: {info.RemoteUrl}");Console.WriteLine($"Has uncommitted changes: {info.HasUncommittedChanges}");HagiCode’s code analysis capabilities use this module to identify project structure and Git status. It is a good example of making full use of what we built.
Things to note
Section titled “Things to note”Based on our practice in the HagiCode project, there are several points that deserve special attention. They are all real issues that need to be handled carefully:
API key security: do not hardcode API keys in your code. Use environment variables or configuration management instead. HagiCode.Libs supports passing configuration through Options objects, making it easier to integrate with different configuration sources. When it comes to security, there is no such thing as being too careful.
CLI version pinning: in CI/CD, we pin specific versions, such as @anthropic-ai/claude-code@2.1.79, to reduce uncertainty caused by version drift. It is also a good idea to use fixed versions in local development. Versioning can be painful. If you do not pin versions, the problem will teach you a lesson very quickly.
Test categorization: default tests use fake providers to keep them deterministic and fast, while real CLI tests must be enabled explicitly. This gives CI fast feedback while still allowing real-environment validation when needed. Striking that balance is never easy. Speed and stability always require trade-offs.
Session management: different CLIs have different session recovery mechanisms. Claude Code uses the .claude/ directory to store sessions, while Codex and CodeBuddy each have their own approaches. When using them, be sure to check their respective documentation and understand the details of their session persistence mechanisms. There is no harm in understanding it clearly.
Summary
Section titled “Summary”HagiCode.Libs is the unified abstraction layer we built during the development of HagiCode to solve the repeated engineering work involved in multi-CLI integration. By providing a consistent interface, encapsulating cross-platform details, and supporting flexible integration patterns, it greatly reduces the engineering complexity of integrating multiple AI coding assistants. Much may fade away, but the experience remains.
If you also need to integrate multiple AI CLI tools in your project, or if you are interested in cross-platform process management and streaming message handling, feel free to check it out on GitHub. The project is released under the MIT license, and contributions and feedback are welcome. In the end, it is a happy coincidence that we met here, so since you are already here, we might as well become friends.
The approach shared in this article was shaped by real pitfalls and real optimization work inside HagiCode. What else could we do? Running into pitfalls is normal. If you think this solution is valuable, then perhaps our engineering work is doing all right. And HagiCode itself may also be worth your attention. You might even find a pleasant surprise.
References
Section titled “References”- HagiCode.Libs GitHub: github.com/HagiCode-org/Hagicode.Libs
- HagiCode main project: github.com/HagiCode-org/site
- HagiCode official website: hagicode.com
- Claude Code official documentation: docs.anthropic.com
If this article helped you:
- Give it a like so more people can see it
- Give us a Star on GitHub: github.com/HagiCode-org/site
- Visit the official website to learn more: hagicode.com
- Watch the 30-minute hands-on demo: www.bilibili.com/video/BV1pirZBuEzq/
- Try the one-click installation: docs.hagicode.com/installation/docker-compose
- Quick install for the Desktop app: hagicode.com/desktop/
- Public beta has started, and you are welcome to install and try it
Copyright notice
Section titled “Copyright notice”Thank you for reading. If you found this article useful, you are welcome to like, bookmark, and share it. This content was created with AI-assisted collaboration, and the final content was reviewed and confirmed by the author.
- Author: newbe36524
- Original link: https://docs.hagicode.com/blog/2026-03-20-hagicode-libs-unified-cli-integration/
- Copyright statement: Unless otherwise stated, all articles on this blog are licensed under BY-NC-SA. Please indicate the source when reposting.