diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ab2920..56a4126 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 0.11.0 — 2026-10-26
+
+Settings in a window.
+
+- Start menu *ADO Lens - settings* (or `adolens setup --window`) opens a window instead of a menu in a terminal:
+ - General: who uses it (mode), tools at start, language, download folder;
+ - Connections: the list with their PATs, add one (address or link, PAT, default project - checked before anything
+ is saved), remove, make default, project, team, project with the code, required label languages;
+ - Environments: per connection, the map in flow order as a table (repository, branch or release stage, name),
+ "Find stages and branches", reorder, save for you or for the whole team (wiki page "ADO Lens");
+ - Status: the same check as `adolens status`;
+ - "Open in a terminal" starts the menu of `adolens setup`, which stays as it was.
+- The program is now built for Windows with Windows Forms (net9.0-windows); the program file is larger.
+
## 0.10.1 — 2026-10-26
- A Polish installation shows Polish texts people read: the descriptions of the skills in Claude Code and in the ZIPs
diff --git a/README.md b/README.md
index 1ea4e4f..6450bcd 100644
--- a/README.md
+++ b/README.md
@@ -52,8 +52,10 @@ called (for you or the whole team); it installs the skills and registers ADO Len
English) is chosen at the start and kept for the settings; an update skips the questions. Until the program is signed, Windows SmartScreen may show "Windows protected your PC" for a file downloaded
in a browser: *More info → Run anyway* (or use the one-line install above, which avoids it).
-After installing, the Start menu has an **ADO Lens** folder: *settings* (a menu: mode, environments — change, add,
-remove, for you or the team —, add or remove a connection, status, language), *status* (is everything working) and the skills for Claude Desktop; `adolens` works in
+After installing, the Start menu has an **ADO Lens** folder: *settings* (a window with tabs: mode, tools at start,
+language, download folder; connections — add, remove, default, project, team, project with the code, label languages;
+environments — find, edit, order, save for you or the team; status — and a button to the same menu in a terminal,
+`adolens setup`), *status* (is everything working) and the skills for Claude Desktop; `adolens` works in
any new terminal. Uninstall from *Settings → Apps → ADO Lens* (or the Start menu): it removes the program, its Claude
registrations and skills, and asks whether to remove the stored PATs and settings too (by default they stay).
diff --git a/README.pl.md b/README.pl.md
index 3bc200b..6e8a379 100644
--- a/README.pl.md
+++ b/README.pl.md
@@ -53,8 +53,10 @@ starcie i zostaje też dla ustawień; aktualizacja pomija pytania. Dopóki
program nie jest podpisany, Windows SmartScreen może przy pliku pobranym przeglądarką pokazać „System Windows ochronił
ten komputer”: *Więcej informacji → Uruchom mimo to* (albo użyj instalacji jedną linijką powyżej, która tego unika).
-Po instalacji w menu Start jest folder **ADO Lens**: *ustawienia* (menu: tryb, środowiska — zmiana, dodanie, usunięcie,
-dla ciebie lub zespołu —, dodanie lub usunięcie połączenia, status, język), *status* (czy wszystko działa) i skille dla Claude Desktop; `adolens` działa w każdym nowym terminalu.
+Po instalacji w menu Start jest folder **ADO Lens**: *ustawienia* (okno z zakładkami: tryb, narzędzia na start,
+język, folder pobierania; połączenia — dodanie, usunięcie, domyślne, projekt, zespół, projekt z kodem, języki etykiet;
+środowiska — wykrycie, edycja, kolejność, zapis dla ciebie lub zespołu; status — oraz przycisk do tego samego menu w
+terminalu, `adolens setup`), *status* (czy wszystko działa) i skille dla Claude Desktop; `adolens` działa w każdym nowym terminalu.
Odinstalowanie: *Ustawienia → Aplikacje → ADO Lens* (albo z menu Start) — usuwa program, jego rejestracje w Claudzie
i skille, i pyta, czy usunąć też zapisane PAT-y i ustawienia (domyślnie zostają).
diff --git a/packaging/inno/ado-lens.iss b/packaging/inno/ado-lens.iss
index 4068807..42cfd91 100644
--- a/packaging/inno/ado-lens.iss
+++ b/packaging/inno/ado-lens.iss
@@ -61,7 +61,9 @@ Source: "{#DesktopSkills}\*.zip"; DestDir: "{app}\skills-for-claude-desktop"; Fl
[Icons]
; Start menu: settings and status open in a console that stays open (adolens commands work there too).
-Name: "{group}\{cm:IconSettings}"; Filename: "{cmd}"; Parameters: "/k """"{app}\bin\adolens.exe"" setup"""; WorkingDir: "{app}"; IconFilename: "{app}\bin\adolens.exe"
+; The settings window; its button "Open in a terminal" starts the menu of 'adolens setup'. Minimized: the console the
+; program starts with is closed at once, so it does not flash.
+Name: "{group}\{cm:IconSettings}"; Filename: "{app}\bin\adolens.exe"; Parameters: "setup --window"; WorkingDir: "{app}"; Flags: runminimized
Name: "{group}\{cm:IconStatus}"; Filename: "{cmd}"; Parameters: "/k """"{app}\bin\adolens.exe"" status"""; WorkingDir: "{app}"; IconFilename: "{app}\bin\adolens.exe"
Name: "{group}\{cm:IconSkills}"; Filename: "{app}\skills-for-claude-desktop"
Name: "{group}\{cm:UninstallProgram,ADO Lens}"; Filename: "{uninstallexe}"; IconFilename: "{app}\bin\adolens.exe"
diff --git a/src/AdoLens/AdoLens.csproj b/src/AdoLens/AdoLens.csproj
index c600bf8..dbdd98e 100644
--- a/src/AdoLens/AdoLens.csproj
+++ b/src/AdoLens/AdoLens.csproj
@@ -2,7 +2,9 @@
Exe
- net9.0
+ net9.0-windows
+
+ true
enable
enable
adolens
@@ -23,7 +25,7 @@
ADO Lens for Azure DevOps
Azure DevOps (work items, boards, wiki, Git and TFVC, pipelines) served to AI assistants over MCP
Copyright © 2026 WillowDev
- 0.10.1
+ 0.11.0
diff --git a/src/AdoLens/Cli.cs b/src/AdoLens/Cli.cs
index 73152ec..fa03557 100644
--- a/src/AdoLens/Cli.cs
+++ b/src/AdoLens/Cli.cs
@@ -79,7 +79,8 @@ recipe list | show | save --file | delete
+/// The settings after installation in a window (Start menu "ADO Lens - settings", adolens setup --window): the
+/// same things as the menu of adolens setup, which "Open in a terminal" starts. Settings are changed through the
+/// same classes as the menu; signing in and the status run adolens.exe itself, as the installer does.
+///
+public sealed class SettingsWindow : Form
+{
+ readonly string path;
+ readonly string version;
+ Settings settings;
+
+ readonly ComboBox mode = Combo(), loading = Combo(), language = Combo();
+ readonly TextBox downloads = new() { Dock = DockStyle.Fill };
+
+ readonly ListView connections = new() { View = View.Details, FullRowSelect = true, MultiSelect = false, HideSelection = false, Dock = DockStyle.Fill };
+ readonly TextBox project = Field(), team = Field(), codeProject = Field(), labelLanguages = Field();
+
+ readonly ComboBox envConnection = Combo();
+ readonly DataGridView envGrid = new()
+ {
+ Dock = DockStyle.Fill, AllowUserToAddRows = true, AllowUserToResizeRows = false, RowHeadersVisible = false,
+ AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill, SelectionMode = DataGridViewSelectionMode.FullRowSelect, BackgroundColor = SystemColors.Window,
+ };
+ readonly Label envInfo = new() { Dock = DockStyle.Fill, AutoEllipsis = true };
+
+ readonly TextBox status = new() { Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Both, WordWrap = false, Dock = DockStyle.Fill, Font = new Font(FontFamily.GenericMonospace, 9) };
+
+ static readonly string[] Modes = ["po", "dev", "full"];
+ static readonly string[] Loadings = ["auto", "demand", "all"];
+ static readonly string?[] Languages = ["pl", "en", null];
+
+ public SettingsWindow(string settingsPath, string version)
+ {
+ path = settingsPath;
+ this.version = version;
+ settings = Settings.Load(path);
+ Text = Ui.T($"ADO Lens {version} - settings", $"ADO Lens {version} - ustawienia");
+ try { Icon = Icon.ExtractAssociatedIcon(Environment.ProcessPath ?? ""); } catch (Exception) { }
+ StartPosition = FormStartPosition.CenterScreen;
+ AutoScaleMode = AutoScaleMode.Dpi;
+ Font = new Font("Segoe UI", 9);
+ ClientSize = new Size(820, 540);
+ MinimumSize = new Size(700, 460);
+
+ var tabs = new TabControl { Dock = DockStyle.Fill };
+ tabs.TabPages.Add(GeneralPage());
+ tabs.TabPages.Add(ConnectionsPage());
+ tabs.TabPages.Add(EnvironmentsPage());
+ tabs.TabPages.Add(StatusPage());
+ tabs.SelectedIndexChanged += async (_, _) => { if (tabs.SelectedIndex == 2 && envGrid.Rows.Count <= 1) await LoadEnvironmentsAsync(detect: false); };
+
+ var terminal = new Button { Text = Ui.T("Open in a terminal", "Otwórz w terminalu"), AutoSize = true };
+ terminal.Click += (_, _) => Start("setup", shell: true);
+ var close = new Button { Text = Ui.T("Close", "Zamknij"), AutoSize = true, DialogResult = DialogResult.Cancel };
+ close.Click += (_, _) => Close();
+ CancelButton = close;
+ var bottom = new FlowLayoutPanel { Dock = DockStyle.Bottom, FlowDirection = FlowDirection.RightToLeft, AutoSize = true, Padding = new Padding(6) };
+ bottom.Controls.Add(close);
+ bottom.Controls.Add(terminal);
+
+ Controls.Add(tabs);
+ Controls.Add(bottom);
+ Shown += (_, _) => { WindowState = FormWindowState.Normal; Activate(); };
+ LoadGeneral();
+ LoadConnections();
+ }
+
+ // ---- entry ------------------------------------------------------------------------------------------------------
+
+ [DllImport("kernel32.dll")] static extern bool FreeConsole();
+ [DllImport("kernel32.dll")] static extern uint GetConsoleProcessList(uint[] list, uint count);
+
+ /// Shows the window on its own UI thread; a console opened only for this program (Start menu) is closed.
+ public static int Run(string settingsPath, string version)
+ {
+ if (GetConsoleProcessList(new uint[2], 2) == 1) FreeConsole();
+ Exception? failed = null;
+ var thread = new Thread(() =>
+ {
+ try
+ {
+ Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new SettingsWindow(settingsPath, version));
+ }
+ catch (Exception ex) { failed = ex; }
+ });
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+ thread.Join();
+ if (failed is not null) { Console.Error.WriteLine(failed.Message); return 1; }
+ return 0;
+ }
+
+ // ---- general ----------------------------------------------------------------------------------------------------
+
+ TabPage GeneralPage()
+ {
+ mode.Items.AddRange([Ui.T("Product Owner - work items, sprints, board, wiki, checking delivered work", "Product Owner - work itemy, sprinty, tablica, wiki, sprawdzanie wykonanej pracy"),
+ Ui.T("Developer - work items, wiki, code, pull requests, pipelines", "Programista - work itemy, wiki, kod, pull requesty, pipeline'y"),
+ Ui.T("Everything", "Wszystko")]);
+ loading.Items.AddRange([Ui.T("auto - on demand in Claude Code, all at start in Claude Desktop", "auto - na żądanie w Claude Code, wszystkie od startu w Claude Desktop"),
+ Ui.T("demand - core at start, the rest when needed (fewest tokens)", "demand - podstawowe na start, reszta, gdy potrzebna (najmniej tokenów)"),
+ Ui.T("all - every tool of the mode at start", "all - wszystkie narzędzia trybu od startu")]);
+ language.Items.AddRange(["polski", "English", Ui.T("as Windows", "jak Windows")]);
+ var browse = new Button { Text = "...", Width = 32 };
+ browse.Click += (_, _) =>
+ {
+ using var dialog = new FolderBrowserDialog { SelectedPath = Downloads.BaseDir(settings) };
+ if (dialog.ShowDialog(this) == DialogResult.OK) downloads.Text = dialog.SelectedPath;
+ };
+ var folder = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, AutoSize = true };
+ folder.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
+ folder.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
+ folder.Controls.Add(downloads, 0, 0);
+ folder.Controls.Add(browse, 1, 0);
+
+ var save = new Button { Text = Ui.T("Save", "Zapisz"), AutoSize = true };
+ save.Click += (_, _) => Guard(SaveGeneral);
+ var grid = Grid(
+ (Ui.T("Who uses it", "Kto używa"), mode),
+ (Ui.T("Tools at start", "Narzędzia na start"), loading),
+ (Ui.T("Language", "Język"), language),
+ (Ui.T("Download folder (empty = Downloads\\ADO Lens)", "Folder pobierania (puste = Pobrane\\ADO Lens)"), folder));
+ grid.Controls.Add(Note(Ui.T("Changes apply to new conversations with Claude.", "Zmiany obowiązują w nowych rozmowach z Claude.")), 1, grid.RowCount);
+ grid.Controls.Add(save, 1, grid.RowCount + 1);
+ return Page(Ui.T("General", "Ogólne"), grid);
+ }
+
+ void LoadGeneral()
+ {
+ mode.SelectedIndex = Math.Max(0, Array.IndexOf(Modes, settings.Mode));
+ loading.SelectedIndex = Math.Max(0, Array.IndexOf(Loadings, settings.ToolLoading));
+ language.SelectedIndex = Math.Max(0, Array.IndexOf(Languages, settings.Language));
+ downloads.Text = settings.DownloadDir ?? "";
+ }
+
+ void SaveGeneral()
+ {
+ settings = Settings.Load(path);
+ var newMode = Modes[mode.SelectedIndex];
+ var newLanguage = Languages[language.SelectedIndex];
+ var skillsChange = newMode != settings.Mode || newLanguage != settings.Language;
+ settings.Mode = newMode;
+ settings.ToolLoading = Loadings[loading.SelectedIndex];
+ settings.Language = newLanguage;
+ settings.DownloadDir = string.IsNullOrWhiteSpace(downloads.Text) ? null : downloads.Text.Trim().Trim('"');
+ settings.Save(path);
+ var said = Ui.T("Saved - new conversations with Claude use it.", "Zapisane - obowiązuje w nowych rozmowach z Claude.");
+ if (skillsChange)
+ {
+ var polish = newLanguage == "pl" || newLanguage is null && Ui.Polish;
+ said += "\n\n" + Skills.Sync(newMode, polish: polish);
+ if (Skills.DesktopZips(polish) is { Length: > 0 } zips) said += "\n" + zips;
+ }
+ MessageBox.Show(this, said, Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+
+ // ---- connections ------------------------------------------------------------------------------------------------
+
+ TabPage ConnectionsPage()
+ {
+ connections.Columns.Add(Ui.T("Connection", "Połączenie"), 110);
+ connections.Columns.Add(Ui.T("Organization", "Organizacja"), 120);
+ connections.Columns.Add(Ui.T("Project", "Projekt"), 120);
+ connections.Columns.Add(Ui.T("Code in", "Kod w"), 100);
+ connections.Columns.Add(Ui.T("Team", "Zespół"), 100);
+ connections.Columns.Add("PAT", 90);
+ connections.SelectedIndexChanged += (_, _) => ShowConnection();
+
+ var add = Button(Ui.T("Add a connection...", "Dodaj połączenie..."), () => { using var d = new LoginDialog(path, version); if (d.ShowDialog(this) == DialogResult.OK) LoadConnections(); });
+ var makeDefault = Button(Ui.T("Make default", "Ustaw jako domyślne"), () =>
+ {
+ if (Selected() is not { } name) return;
+ settings = Settings.Load(path);
+ settings.DefaultConnection = name;
+ settings.Save(path);
+ LoadConnections();
+ });
+ var remove = Button(Ui.T("Remove...", "Usuń..."), () =>
+ {
+ if (Selected() is not { } name) return;
+ if (MessageBox.Show(this, Ui.T($"Remove the connection '{name}' with its PAT?", $"Usunąć połączenie '{name}' razem z PAT-em?"), Text,
+ MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
+ RemoveConnection(name);
+ LoadConnections();
+ });
+ var buttons = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true };
+ buttons.Controls.AddRange([add, makeDefault, remove]);
+
+ var save = Button(Ui.T("Save changes", "Zapisz zmiany"), SaveConnection);
+ var details = Grid(
+ (Ui.T("Default project (work items)", "Projekt domyślny (work itemy)"), project),
+ (Ui.T("Team", "Zespół"), team),
+ (Ui.T("Project with the code, PRs and pipelines (empty = the same)", "Projekt z kodem, PR-ami i pipeline'ami (puste = ten sam)"), codeProject),
+ (Ui.T("Required label languages (e.g. en-US, pl; empty = all)", "Wymagane języki etykiet (np. en-US, pl; puste = wszystkie)"), labelLanguages));
+ details.Controls.Add(save, 1, details.RowCount);
+
+ var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 3 };
+ layout.RowStyles.Add(new RowStyle(SizeType.Percent, 45));
+ layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
+ layout.RowStyles.Add(new RowStyle(SizeType.Percent, 55));
+ layout.Controls.Add(connections, 0, 0);
+ layout.Controls.Add(buttons, 0, 1);
+ layout.Controls.Add(details, 0, 2);
+ return Page(Ui.T("Connections", "Połączenia"), layout);
+ }
+
+ void LoadConnections()
+ {
+ settings = Settings.Load(path);
+ connections.Items.Clear();
+ foreach (var (name, c) in settings.Connections.OrderBy(kv => kv.Key, StringComparer.OrdinalIgnoreCase))
+ {
+ var item = new ListViewItem(name + (name.Equals(settings.DefaultConnection, StringComparison.OrdinalIgnoreCase) ? Ui.T(" (default)", " (domyślne)") : "")) { Tag = name };
+ item.SubItems.AddRange([c.Organization, c.Project ?? "-", c.CodeProject ?? "", c.Team ?? "", CredentialStore.Read(name) is null ? Ui.T("missing", "brak") : Ui.T("stored", "zapisany")]);
+ connections.Items.Add(item);
+ }
+ if (connections.Items.Count > 0) connections.Items[0].Selected = true;
+ envConnection.Items.Clear();
+ envConnection.Items.AddRange(settings.Connections.Keys.Order(StringComparer.OrdinalIgnoreCase).Cast