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
32 changes: 31 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.5.0] - 2026-06-01

### Added
- **"Not installed" detection** — agents whose required app or CLI is missing on
the machine show a peach **not installed** badge in the dropdown, with the
badge/text dimmed. The Launch button is disabled and a `● not installed`
hint is shown next to it. The launch path runs a pre-flight check that
fails with a clear `<Agent> is not installed` error instead of returning a
false ✓. Detection is rule-based for known agents (Codex App, VS Code,
Cursor, Codex, Claude Code, opencode); unknown integrations are
conservatively reported as installed so an unrecognised agent is never
blocked.
- Install state is refreshed alongside running state on every poll (5s) and
is included in the change-detection signature, so the badge updates the
moment the missing app appears in `/Applications` or the binary lands in
PATH.

### Changed
- Agent dropdown popup auto-expands beyond the field width (min 320px) so the
`not installed` badge fits alongside long agent names without overflowing
the panel. The name `Text` now elides with `…` when space is tight; the
popup container and each row clip overflow at their rounded border.

### Internal
- Install detection is split into pure helpers (`check_install_spec`,
`binary_in`, `bundle_in`) with 11 new unit tests covering the rule table,
OR semantics, missing/empty specs, multi-directory lookup, the batch
invariant, and the Windows `.exe` / `.cmd` / `.bat` extension fallback.

## [0.4.0] - 2026-06-01

### Added
Expand Down Expand Up @@ -115,7 +144,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Persisted last-used agent + model across runs.
- App icon, banner, and `bundle.sh` to assemble `Llaunchpad.app`.

[Unreleased]: https://github.com/draugvar/llaunchpad/compare/v0.4.0...HEAD
[Unreleased]: https://github.com/draugvar/llaunchpad/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/draugvar/llaunchpad/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/draugvar/llaunchpad/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/draugvar/llaunchpad/compare/v0.2.1...v0.3.0
[0.2.1]: https://github.com/draugvar/llaunchpad/compare/v0.2.0...v0.2.1
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "llaunchpad"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
description = "A cross-platform launcher for Ollama coding agents with cloud models"
license = "MIT"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ the exact cloud model names, and re-typing the command every time.
| **Local model badge** | Local models from your server show up first in the dropdown, tagged with a teal **local** badge. |
| **Correct model names** | Cloud ids are auto-normalized to launchable refs (`glm-4.6` → `glm-4.6:cloud`, `gpt-oss:120b` → `gpt-oss:120b-cloud`). |
| **GUI & CLI agents** | GUI apps (Codex, VS Code) are opened/relaunched; CLI agents spawn in Terminal. |
| **Install awareness** | Agents whose app or CLI is missing on the machine get a `not installed` badge and Launch is disabled — no more silent "success" toasts when the integration isn't really there. |
| **Codex App fix** | Strips the legacy `profile =` line modern Codex rejects, so launches just work. |
| **One-click restore** | Restore button reverts an agent to its original profile when Ollama has a backup; disabled otherwise. |
| **Smart "running" badge** | Detects the real GUI process by bundle path — no false positives from background helpers. |
Expand Down
31 changes: 21 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ mod ollama;
slint::include_modules!();

use ollama::{
launch_agent, list_agents, list_cloud_models, list_local_models, restore_agent,
restore_available, running_states, test_connection, Agent,
installed_states, launch_agent, list_agents, list_cloud_models, list_local_models,
restore_agent, restore_available, running_states, test_connection, Agent,
};
use slint::{Model, ModelRc, SharedString, VecModel};
use std::sync::atomic::{AtomicBool, Ordering};
Expand Down Expand Up @@ -39,7 +39,7 @@ fn color_index(token: &str) -> i32 {
(sum % PALETTE_LEN as u32) as i32
}

fn make_agent_items(agents: &[Agent], running: &[bool]) -> Vec<AgentItem> {
fn make_agent_items(agents: &[Agent], running: &[bool], installed: &[bool]) -> Vec<AgentItem> {
agents
.iter()
.enumerate()
Expand All @@ -48,6 +48,7 @@ fn make_agent_items(agents: &[Agent], running: &[bool]) -> Vec<AgentItem> {
display: a.display.clone().into(),
is_gui: a.is_gui,
running: running.get(i).copied().unwrap_or(false),
installed: installed.get(i).copied().unwrap_or(true),
restorable: restore_available(&a.name),
initials: initials(&a.display).into(),
color_index: color_index(&a.name),
Expand Down Expand Up @@ -103,17 +104,22 @@ fn set_models_preserving_selection(ui: &AppWindow, items: Vec<ModelItem>) {
ui.set_sel_model_index(new_idx);
}

/// Fetch agents, their running state, and the cloud model list.
async fn fetch_all() -> anyhow::Result<(Vec<Agent>, Vec<bool>, Vec<String>)> {
/// Fetch agents, their running + installed state, and the cloud model list.
async fn fetch_all() -> anyhow::Result<(Vec<Agent>, Vec<bool>, Vec<bool>, Vec<String>)> {
let agents = list_agents().await?;
let models = list_cloud_models()
.await?
.into_iter()
.map(|m| m.name)
.collect::<Vec<_>>();
let agents_for_scan = agents.clone();
let running = tokio::task::spawn_blocking(move || running_states(&agents_for_scan)).await?;
Ok((agents, running, models))
let (running, installed) = tokio::task::spawn_blocking(move || {
let r = running_states(&agents_for_scan);
let i = installed_states(&agents_for_scan);
(r, i)
})
.await?;
Ok((agents, running, installed, models))
}

fn main() -> anyhow::Result<()> {
Expand Down Expand Up @@ -319,14 +325,19 @@ fn main() -> anyhow::Result<()> {
});
}
match fetch_all().await {
Ok((agents, running, cloud_names)) => {
Ok((agents, running, installed, cloud_names)) => {
*store.lock().unwrap() = agents.clone();
let items = make_agent_items(&agents, &running);
let items = make_agent_items(&agents, &running, &installed);

// use a content-hash so we only repaint on real changes
let agent_sig: Vec<String> = items
.iter()
.map(|it| format!("{}|{}|{}", it.name, it.running, it.restorable))
.map(|it| {
format!(
"{}|{}|{}|{}",
it.name, it.running, it.restorable, it.installed
)
})
.collect();

let (agents_changed, models_changed) = {
Expand Down
Loading
Loading