Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A cross‑platform desktop UI (Avalonia/.NET 8) for driving the Codex CLI app se
- Send user input that is wrapped as protocol `Submission`s (app server)
- Auto‑approve exec/patch requests (automatic)
- Select a Codex profile (from `config.toml`) and load MCP servers from a JSON config
- Keep multiple Codex sessions active at once using the tabbed header (each tab title shows its live status, e.g., `Session 2 – thinking…`)
– See live token usage and estimated context remaining in the header

> Important: This app runs Codex through the `app-server` subcommand.
Expand Down Expand Up @@ -43,6 +44,7 @@ A cross‑platform desktop UI (Avalonia/.NET 8) for driving the Codex CLI app se
- Use API Key for Codex CLI (pipes the key to `codex login --with-api-key` before sessions; does not rely on existing CLI auth)
- Allow network access for tools (sets sandbox_policy.network_access=true on turns so MCP tools can reach the network)
- Without API key enabled, the app proactively authenticates with `codex auth login` (falling back to `codex login`) before sessions so your chat/GPT token is used.
5. Need a second workspace or want to keep another Codex stream alive? Hit the **+** button next to the session tabs to spin up a parallel session—tab titles update in real time so you can see whether each workspace is `disconnected`, `thinking…`, or `idle`.

### Directory Guardrails with `AGENTS.md`

Expand Down
79 changes: 62 additions & 17 deletions SemanticDeveloper/SemanticDeveloper/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using SemanticDeveloper.Models;
using SemanticDeveloper.Services;
using SemanticDeveloper.Views;
using System.Threading.Tasks;

namespace SemanticDeveloper;

Expand All @@ -17,6 +19,7 @@ public partial class MainWindow : Window, INotifyPropertyChanged
private SessionTab? _selectedSession;
private int _sessionCounter = 0;
private AppSettings _sharedSettings;
private bool _profileCheckPerformed;

public MainWindow()
{
Expand Down Expand Up @@ -82,17 +85,7 @@ private async void OnOpenCliSettingsClick(object? sender, RoutedEventArgs e)
if (SelectedSession is null)
return;

var updated = await SelectedSession.View.ShowCliSettingsDialogAsync(_sharedSettings);
if (updated is null)
return;

_sharedSettings = CloneAppSettings(updated);
foreach (var session in _sessions)
{
if (session == SelectedSession)
continue;
session.View.ApplySettingsSnapshot(_sharedSettings);
}
await OpenCliSettingsAsync(SelectedSession);
}

private async void OnOpenAboutClick(object? sender, RoutedEventArgs e)
Expand Down Expand Up @@ -129,6 +122,58 @@ private void OnOpenReadmeClick(object? sender, RoutedEventArgs e)
private void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
Dispatcher.UIThread.Post(async () => await EnsureProfilesConfiguredAsync());
}

private async Task EnsureProfilesConfiguredAsync()
{
if (_profileCheckPerformed)
return;
_profileCheckPerformed = true;

try
{
var profiles = CodexConfigService.TryGetProfiles();
if (profiles.Count > 0)
return;

var info = new InfoDialog
{
Title = "Add a Codex Profile",
Message = "No Codex CLI profiles were found in ~/.codex/config.toml.\n\nCreate a profile under [profiles.<name>] with the model you want to use, for example:\n\n[profiles.default]\nmodel = \"gpt-5-codex\"\nmodel_provider = \"openai\"\n\nAfter you add a profile, select it in CLI Settings so sessions know which model to run."
};

await info.ShowDialog(this);

if (SelectedSession is not null)
{
await OpenCliSettingsAsync(SelectedSession);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to prompt for profile setup: {ex.Message}");
}
}

private async Task OpenCliSettingsAsync(SessionTab session)
{
var updated = await session.View.ShowCliSettingsDialogAsync(_sharedSettings);
if (updated is null)
return;

_sharedSettings = CloneAppSettings(updated);
foreach (var other in _sessions)
{
if (other == session)
continue;
other.View.ApplySettingsSnapshot(_sharedSettings);
}
}

private static AppSettings CloneAppSettings(AppSettings source) => new()
{
Command = source.Command,
Expand Down Expand Up @@ -169,11 +214,11 @@ private set

public void UpdateHeader()
{
Header = $"{Title} - {View.SessionStatus}";
}

public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
Header = $"{Title} - {View.SessionStatus}";
}

public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
22 changes: 22 additions & 0 deletions SemanticDeveloper/SemanticDeveloper/Views/InfoDialog.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SemanticDeveloper.Views.InfoDialog"
Width="460" Height="260"
CanResize="False"
WindowStartupLocation="CenterOwner"
Title="Information"
Background="#2E2E2E"
Icon="avares://SemanticDeveloper/Images/semantic-developer-logo-large.png">
<Grid RowDefinitions="*,Auto" Margin="16">
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="MessageText"
TextWrapping="Wrap"
Foreground="#E6E6E6"
Margin="0,0,0,8"/>
</ScrollViewer>

<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="96" Click="OnClose" IsDefault="True">OK</Button>
</StackPanel>
</Grid>
</Window>
21 changes: 21 additions & 0 deletions SemanticDeveloper/SemanticDeveloper/Views/InfoDialog.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Avalonia.Controls;
using Avalonia.Interactivity;

namespace SemanticDeveloper.Views;

public partial class InfoDialog : Window
{
public InfoDialog()
{
InitializeComponent();
}

public string Message
{
get => MessageText.Text ?? string.Empty;
set => MessageText.Text = value;
}

private void OnClose(object? sender, RoutedEventArgs e)
=> Close();
}