Skip to content

Add CLI interface and extract shared analysis library#15

Merged
baronfel merged 12 commits intobaronfel:mainfrom
ihvo:extract-shared-library
Apr 6, 2026
Merged

Add CLI interface and extract shared analysis library#15
baronfel merged 12 commits intobaronfel:mainfrom
ihvo:extract-shared-library

Conversation

@ihvo
Copy link
Copy Markdown

@ihvo ihvo commented Mar 25, 2026

Motivation

The MCP server is great for AI assistants, but agents that work via shell commands (and humans) need a CLI. Extracting a shared library also lets both frontends evolve independently without duplicating analysis logic.

Approach

Split the single binlog.mcp project into three: base implementation, MCP server, CLI

All 24 analysis tools are now CLI subcommands that call the same static methodd as the MCP.
Namespaces are preserved as Binlog.MCP.* to keep the diff minimal.

CLI highlights

  • 24 commands matching every MCP tool: diagnostics, expensive-projects, search, target-info, etc.
  • batch loads the binlog once, reads commands from stdin. Avoids the ~30s per-command reload overhead for multi-command sessions.mode
  • Compact JSON output via the same BinlogJsonContext source-gen serializer as MCP.

Bug fix

  • Timeline.NodesByNodeId was a ConcurrentDictionary STJ source gen silently skipped it, producing {}. Changed to a Dictionary<int, NodeStats> property populated after parallel traversal.

Packaging

  • binlog library is IsPackable=false (internal only).
  • binlog.cli has its own CHANGELOG.md (version 0.0.1), PACKAGE_README.md, and Ionide.KeepAChangelog.Tasks for changelog-driven versioning.
  • release.yml updated to glob-push baronfel.binlog.*.nupkg so both packages land on NuGet.

Non-obvious decisions

  • global.json SDK version bumped from 10.0.100 to 10.0.201 to match the installed SDK.
  • Batch mode injects the binlog the user types expensive-projects --top 5 and the batch loop prepends the binlog path before dispatching to the same System.CommandLine root. No separate command definitions needed.path

Ihar Voitka and others added 11 commits March 19, 2026 23:02
All MCP tools are now available as individual CLI commands:
  load, list-files, get-file, diagnostics, search,
  list-projects, expensive-projects, project-build-time,
  project-target-list, project-target-times,
  expensive-targets, search-targets, target-info,
  expensive-tasks, task-info, list-tasks, search-tasks,
  expensive-analyzers, task-analyzers,
  list-evaluations, eval-global-props, eval-properties, eval-items,
  timeline

CLI and MCP share the same static tool implementations.
CliRunner.EnsureLoaded delegates to LoadBinlogTool.Load so
the load path is identical in both modes.

Output is trim-safe indented JSON via source-generated BinlogJsonContext.

MCP server still starts with no args (backward compat) or 'mcp'.
server.json updated to pass 'mcp' arg when run via MCP host.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agents are the primary CLI consumers so human-readable formatting
is not needed. PrintJson<T> now delegates directly to
JsonSerializer.Serialize(value, typeInfo) using the same
source-generated BinlogJsonContext as the MCP server.

Removes the JsonDocument + Utf8JsonWriter pretty-print round-trip.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…onary for public property

ConcurrentDictionary<int, NodeStats> was silently skipped by the STJ
source generator, producing {} for the timeline command.

The fix: populate in parallel using a local ConcurrentDictionary,
then assign a plain Dictionary<int, NodeStats> to the public property
once Populate() completes. Dictionary<int, Timeline.NodeStats> is
already registered in BinlogJsonContext and serializes correctly.

Also removes the now-unnecessary ConcurrentDictionary entry that was
added to BinlogJsonContext.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Every CLI command now carries the exact same description text as the
corresponding [Description(...)] attribute on its MCP tool method.
The search command uses the full multi-line query language reference.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r), binlog.cli (CLI)

- Create binlog/ class library with all analysis tools and infrastructure
- Slim binlog.mcp/ down to just Program.cs and *Extensions.cs files
- Create binlog.cli/ with CLI commands (without MCP server command)
- Remove args from server.json transport (MCP server now launched directly)
- Make BinlogJsonContext and BinlogJsonOptions public for cross-project access
- Update solution file to include all three projects

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ackaging

- Add ToolCommandName, Ionide.KeepAChangelog.Tasks, CHANGELOG.md, and PACKAGE_README.md to binlog.cli
- Add IsPackable=false to binlog shared library (internal only)
- Update release.yml to push all baronfel.binlog.*.nupkg on tag

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ihvo ihvo marked this pull request as ready for review March 25, 2026 01:01
Copy link
Copy Markdown
Owner

@baronfel baronfel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's funny about this PR is that I have a library for converting S.CL commands into MCP tools - so completely the other way around :D

One design question - why have separate clients at all? Could the 'argument-less' form of the CLI be an MCP mode, and then also provide the CLI commands for direct usage? That could reduce confusion and enable both usage modes from the same tool.

// Inject the binlog path after the command name.
// Input: "expensive-projects --top 5"
// We need: "expensive-projects <binlog> --top 5"
var parts = SplitCommandLine(line);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S.CL should be able to parse these lines directly if you don't make BatchCommand a root command, and the position of the binlog argument doesn't matter to S.CL - options and arguments are interchangeable so you can always add it to the end of the argument array safely. Basically from a CLI hygiene PoV I don't want to be managing splitting tokens, that's a hell I have so many scars from.

@ihvo
Copy link
Copy Markdown
Author

ihvo commented Mar 31, 2026

What's funny about this PR is that I have a library for converting S.CL commands into MCP tools - so completely the other way around :D

One design question - why have separate clients at all? Could the 'argument-less' form of the CLI be an MCP mode, and then also provide the CLI commands for direct usage? That could reduce confusion and enable both usage modes from the same tool.

I agree that having a single tool is better, but it would be confusing if the tool goes into mcp server mode instead of printing usage instructions, when started without cmd-line arguments.

A better way is to start mcp server would be using 'mcp' argument. This way 'no arguments' would print help text which is expected by agents and humans. However, this is a breaking change to the existing mcp package, and that is why I added the cli package.

It is your call though, and I am happy to merge into mcp, or keep it separately, just let me know which direction you prefer. @baronfel

…andle parsing

Append binlog path to the raw input line and let S.CL tokenize and
resolve argument positions. Eliminates fragile hand-rolled splitter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@baronfel baronfel merged commit 883352f into baronfel:main Apr 6, 2026
1 check passed
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ionide.KeepAChangelog.Tasks" Version="0.3.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that this is using beta-4. I don't want that since there's a stable release, so I'm doing that update then will do a release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants