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
15 changes: 15 additions & 0 deletions plugins/sourceos/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.19)

project(sourceos
VERSION 0.1.0
DESCRIPTION "SourceOS action bus plugin"
)

# In-tree build: FindAlbert.cmake in the build directory provides the macros.
# Standalone build: find_package(Albert) resolves from system install.
find_package(Albert REQUIRED)

# Minimal plugin: no UI config yet.
albert_plugin(
QT Core
)
24 changes: 24 additions & 0 deletions plugins/sourceos/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# SourceOS Albert plugin

This plugin adds **keyboard-first SourceOS workstation actions** to Albert.

Design goals:

- Treat Albert as an **action bus** (command palette), not as a second filesystem index.
- Keep actions auditable and compatible with SourceOS execution boundaries.

## Trigger

Default trigger: `sourceos `

## Actions (v0)

- `doctor` → runs `sourceos doctor workstation-v0`
- `profile apply` → runs `sourceos profile apply workstation-v0`
- `k9s` → launches `k9s`
- `lazygit` → launches `lazygit`

## Notes

- This plugin assumes the `sourceos` CLI is installed on PATH (installed by workstation profile tooling).
- A future iteration will add richer parameterization and optional GNOME portal gating.
13 changes: 13 additions & 0 deletions plugins/sourceos/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "SourceOS",
"description": "SourceOS workstation actions (doctor, profiles, tools)",
"license": "MIT",
"url": "https://github.com/SociOS-Linux/albert",
"readme_url": "https://github.com/SociOS-Linux/albert/tree/dev/plugins/sourceos",
"authors": ["SociOS-Linux"],
"maintainers": ["@mdheller"],
"binary_dependencies": ["sourceos"],
"runtime_dependencies": [],
"plugin_dependencies": [],
"loadtype": "user"
}
152 changes: 152 additions & 0 deletions plugins/sourceos/src/sourceos.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// SPDX-FileCopyrightText: 2026 SociOS-Linux
// SPDX-License-Identifier: MIT

#include <albert/extensionplugin.h>
#include <albert/rankedqueryhandler.h>
#include <albert/matcher.h>
#include <albert/standarditem.h>
#include <albert/icon.h>
#include <albert/systemutil.h>

#include <QString>
#include <QStringList>

#include <algorithm>
#include <vector>

namespace {

static inline double clamp01(double s)
{
if (s < 0.0) return 0.0;
if (s > 1.0) return 1.0;
return s;
}

static inline double matchScore(const QString &query, const QString &candidate)
{
albert::Matcher m(candidate);
const albert::Match mm = m.match(query);
if (!mm)
return 0.0;
return clamp01(mm.score());
}

} // namespace

class SourceOSPlugin final : public albert::ExtensionPlugin,
public albert::RankedQueryHandler
{
ALBERT_PLUGIN

public:
SourceOSPlugin() = default;

QString id() const override { return "sourceos"; }
QString name() const override { return "SourceOS"; }
QString description() const override { return "SourceOS workstation actions (doctor, profiles, tools)"; }

QString synopsis(const QString &query) const override
{
(void)query;
return "doctor | profile apply | k9s | lazygit";
}

QString defaultTrigger() const override
{
return "sourceos ";
}

bool supportsFuzzyMatching() const override
{
return true;
}

std::vector<albert::RankItem> rankItems(albert::QueryContext &context) override
{
const QString q = context.query().trimmed();
const bool empty = q.isEmpty();

struct ActionDef {
QString key;
QString title;
QString subtitle;
QString grapheme;
QStringList cmd;
std::vector<QString> terms;
};

const std::vector<ActionDef> actions = {
{"doctor",
"SourceOS: doctor",
"Run workstation checks (workstation-v0)",
"🩺",
{"sourceos", "doctor", "workstation-v0"},
{"doctor", "check", "diagnose", "health"}},

{"apply",
"SourceOS: profile apply",
"Apply workstation profile (workstation-v0)",
"🧰",
{"sourceos", "profile", "apply", "workstation-v0"},
{"apply", "profile", "install", "bootstrap"}},

{"k9s",
"Open k9s",
"Kubernetes TUI",
"⎈",
{"k9s"},
{"k9s", "kube", "kubernetes"}},

{"lazygit",
"Open lazygit",
"Git TUI",
"🌿",
{"lazygit"},
{"lazygit", "git"}},
};

std::vector<albert::RankItem> out;
out.reserve(actions.size());

for (const auto &a : actions) {
double score = 0.0;

if (empty) {
// Be conservative: RankItem doc says (0,1] even though empty queries are a special case.
score = 0.01;
} else {
for (const auto &term : a.terms)
score = std::max(score, matchScore(q, term));

if (score <= 0.0)
continue;
}

auto icon_factory = [g=a.grapheme]() {
return albert::Icon::grapheme(g, 1.0);
};

std::vector<albert::Action> acts;
acts.push_back(albert::Action{
"run",
"Run",
[cmd=a.cmd]() { albert::runDetachedProcess(cmd); },
true
});

auto item = albert::StandardItem::make(
QString("sourceos.%1").arg(a.key),
a.title,
a.subtitle,
icon_factory,
std::move(acts),
a.key
);

out.emplace_back(item, score);
}

return out;
}
};