Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
12e31b2
Add optional camera preview plugin for pre-meeting checks
digitalbase Oct 23, 2026
7a545cf
Handle preview visibility through the FloatingWindow lifecycle
digitalbase Oct 23, 2026
61cf1ad
Keep the preview plugin mounted for repeat launches
digitalbase Oct 23, 2026
5d265bb
Release the camera device by unloading capture on close
digitalbase Oct 23, 2026
c3472c8
Adds kernel menus and provider fallbacks (#45)
adamcbrewer Oct 28, 2026
7480534
perf(search): accelerate matching and result updates
thisisgm Oct 28, 2026
c2c4065
Merge pull request #47 from thisisgm/feature/flea-search
cpow Oct 28, 2026
6a89383
Merge pull request #46 from adamcbrewer/fix/45-menu-compatibility
cpow Oct 28, 2026
1761972
perf(menu): speed up refresh and quiet loading feedback
thisisgm Oct 28, 2026
c12be62
Merge pull request #48 from thisisgm/perf/menu-guards
cpow Oct 28, 2026
65e95e6
Switch to an app's open window instead of launching another
gcaraciolo Oct 22, 2026
7562461
Bring the focused window to the top on floating workspaces
gcaraciolo Oct 26, 2026
8a89b66
update to handle generic windows
cpow Oct 29, 2026
582bc73
Merge pull request #31 from gcaraciolo/focus-open-app-windows
cpow Oct 29, 2026
6cd068a
Shares project instructions with Codex
adamcbrewer Oct 30, 2026
fdae22c
prepare for omarchy plugin submission
cpow Nov 1, 2026
66965cf
Merge pull request #58 from typecraft-dev/marketplace
cpow Nov 2, 2026
9ebe4c9
Add marketplace preview image
cpow Nov 2, 2026
5e16a6b
Merge pull request #50 from adamcbrewer/chore/agents-symlink
cpow Nov 2, 2026
5236a6f
Keep histories and typed text private for the marketplace review
cpow Nov 2, 2026
98bd0bb
Merge pull request #59 from typecraft-dev/fix/marketplace-privacy
cpow Nov 2, 2026
2149a6f
Merge camera preview with latest omacast upstream
digitalbase Nov 9, 2026
743a113
Use omacast camera plugin id
digitalbase Nov 9, 2026
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: 13 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,19 @@ jobs:
- name: Unit tests
run: node --test tests/*.test.js

- uses: dtolnay/rust-toolchain@stable

- name: Search helper
run: |
cargo test --locked --manifest-path search/Cargo.toml
bin/build-search
node --test tests/search-integration.test.js

- name: Shell syntax
run: bash -n bin/oma-cast tests/smoke.sh
run: |
for script in bin/omacast bin/build-search tests/smoke.sh tests/native-transport.sh; do
bash -n "$script"
done

- name: Plugin manifest is valid JSON
run: node -e 'JSON.parse(require("fs").readFileSync("plugin/manifest.json", "utf8"))'
run: node -e 'JSON.parse(require("fs").readFileSync("manifest.json", "utf8"))'
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/search/target/
/bin/omacast-search
/bin/.search.*
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# AGENTS.md

Project instructions for coding agents live in [CLAUDE.md](CLAUDE.md). Read it first.

(This is a plain file, not a symlink: `omarchy plugin validate` rejects symlinks inside a plugin.)
87 changes: 66 additions & 21 deletions plugin/Backend.qml → Backend.qml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import "core/Paths.js" as Paths
import "core/Log.js" as Log
import "core/Schema.js" as Schema
import "core/Ranker.js" as Ranker
import "core/FleaSearch.js" as FleaSearch
import "core/Frecency.js" as Frecency
import "core/OmarchyMenu.js" as OmarchyMenu
import "core/RootSearch.js" as RootSearch
import "core/Agent.js" as Agent
import "core/Calculator.js" as Calculator
import "services"

Expand All @@ -35,7 +35,7 @@ Item {
property var candidates: []
property var candidatesById: ({})
property int candidatesRevision: 0
readonly property bool loading: menuSource.loading || agentLauncher.checking
readonly property bool loading: menuSource.loading || nativeSearch.pending !== null
// Bumped when exchange rates or zone offsets arrive, so an open
// calculator row re-evaluates.
readonly property int calculatorRevision: (calculatorData.rates ? 1 : 0) + calculatorData.ratesFetchedAt + calculatorData.zonesAt
Expand All @@ -45,6 +45,25 @@ Item {

function call(method, data) { return Rpc.call(backend.registry, method, data) }
function callSync(method, data) { return Rpc.callSync(backend.registry, method, data) }
function cancelSearch() { nativeSearch.cancel() }

function searchAsync(data, callback) {
nativeSearch.cancel()
var query = String(data && data.query || "").trim()
var view = data && data.view
if (!query || (view && view.kind !== "root")) {
callback(backend.callSync("rootSearch.search", data))
return
}
nativeSearch.search(Ranker.transliterate(query.toLowerCase()), backend.searchContext().sensitivity, function(matches) {
callback(backend.callSync("rootSearch.search", Object.assign({}, data, { fleaHits: matches })))
})
}

NativeSearch {
id: nativeSearch
onFailed: function(message) { backend.log("warning", "search", message) }
}
function subscribe(method, callback) { return Rpc.subscribe(backend.registry, method, callback) }

function emit(method, data) {
Expand Down Expand Up @@ -78,8 +97,12 @@ Item {
for (var b = 0; b < RootSearch.BUILTINS.length; b++) next.push(RootSearch.builtinCandidate(RootSearch.BUILTINS[b]))
var byId = ({})
for (var j = 0; j < next.length; j++) byId[next[j].id] = next[j]
var entries = FleaSearch.entries(next, Ranker.transliterate)
nativeSearch.cancel()
backend.candidates = next
backend.candidatesById = byId
nativeSearch.ids = next.map(function(c) { return c.id })
nativeSearch.entries = entries
backend.candidatesRevision++
}

Expand Down Expand Up @@ -147,16 +170,16 @@ Item {
case "open":
case "copy-answer":
if (!historyId) backend.saveCalculation(entry)
Util.execArgv(["wl-copy", "--", entry.copy])
clipboard.copy(entry.copy)
return { close: true, hud: "Copied to Clipboard" }
case "paste-answer":
if (!historyId) backend.saveCalculation(entry)
// Omarchy's own paste: copies, waits for our window to unmap, types.
Util.execArgv(["omarchy-clipboard-paste-text", entry.copy])
// Copies, waits for our window to unmap, then pastes.
clipboard.paste(entry.copy)
return { close: true }
case "copy-question-answer":
if (!historyId) backend.saveCalculation(entry)
Util.execArgv(["wl-copy", "--", entry.query + " = " + entry.result])
clipboard.copy(entry.query + " = " + entry.result)
return { close: true, hud: "Copied to Clipboard" }
case "open-history":
return { push: { kind: "calculator-history", title: "Calculator History" } }
Expand Down Expand Up @@ -220,6 +243,19 @@ Item {
return candidate.kind === "omarchy-link" ? candidate.target : candidate.menuId
}

function openMenu(item) {
if (item.provider && !OmarchyMenu.PROVIDERS[item.provider]) {
backend.summonPlugin("omarchy.menu", JSON.stringify({ menu: item.id }))
return { close: true }
}
return { push: { kind: "menu", menuId: item.id, title: item.label } }
}

function launchApp(candidate) {
if (backend.appLibrary) backend.appLibrary.launch(candidate.appId, candidate.title)
else Util.execArgv(["uwsm-app", "--", "gtk-launch", candidate.appId + ".desktop"])
}

function performAction(data) {
var id = String(data && data.id || "")
var action = String(data && data.action || "open")
Expand All @@ -231,8 +267,7 @@ Item {
switch (candidate.kind) {
case "app":
backend.recordVisit(id, query)
if (backend.appLibrary) backend.appLibrary.launch(candidate.appId, candidate.title)
else Util.execArgv(["uwsm-app", "--", "gtk-launch", candidate.appId + ".desktop"])
appFocuser.open(candidate)
return { close: true }
case "omarchy-action":
if (candidate.disabled) return { toast: { style: "failure", title: candidate.title + " is already set up" } }
Expand All @@ -241,12 +276,12 @@ Item {
return { close: true }
case "omarchy-menu":
backend.recordVisit(id, query)
return { push: { kind: "menu", menuId: candidate.menuId, title: candidate.title } }
return backend.openMenu(menuSource.tree.items[candidate.menuId])
case "omarchy-link":
backend.recordVisit(id, query)
var target = menuSource.tree.items[candidate.target]
if (!target) return { toast: { style: "failure", title: "Menu " + candidate.target + " not found" } }
return { push: { kind: "menu", menuId: target.id, title: target.label } }
return backend.openMenu(target)
case "builtin":
if (candidate.run.url) {
var values = (data && data.arguments) || {}
Expand All @@ -268,12 +303,16 @@ Item {
}

switch (action) {
case "open-new":
backend.recordVisit(id, query)
backend.launchApp(candidate)
return { close: true }
case "open-in-omarchy-menu":
backend.summonPlugin("omarchy.menu", JSON.stringify({ menu: backend.omarchyRouteFor(candidate) }))
return { close: true }
case "copy-command":
case "copy-id":
Util.execArgv(["wl-copy", "--", action === "copy-id" ? candidate.appId + ".desktop" : candidate.action])
clipboard.copy(action === "copy-id" ? candidate.appId + ".desktop" : candidate.action)
return { close: true, hud: "Copied to Clipboard" }
case "favorite-add":
case "favorite-remove":
Expand Down Expand Up @@ -386,6 +425,7 @@ Item {
var query = String(data && data.query || "")
var view = data && data.view
var ctx = backend.searchContext()
if (data && data.fleaHits) ctx.fleaHits = data.fleaHits
var sections
if (view && view.kind === "menu") sections = RootSearch.submenuSections(backend.candidates, view.menuId, query, ctx, backend.deps)
else if (view && view.kind === "calculator-history") sections = RootSearch.historySections(calculatorStore.doc.history, query, ctx.nowMs, Calculator.filterHistory)
Expand Down Expand Up @@ -437,10 +477,6 @@ Item {
}
var fallback = RootSearch.FALLBACKS.filter(function(f) { return f.id === (data && data.id) })[0]
if (fallback) {
if (fallback.id === Agent.FALLBACK_ID) {
agentLauncher.start(data.query)
return { pending: true }
}
Util.execArgv(["omarchy-launch-browser", RootSearch.fallbackUrl(fallback, data.query)])
return { close: true }
}
Expand Down Expand Up @@ -518,19 +554,28 @@ Item {
menuSource.refresh()
calculatorData.refresh()
}
if (method === "window.visibilityChanged" && data && !data.visible) agentLauncher.cancel()
}

AgentLauncher {
id: agentLauncher
onPrepared: function(argv) { backend.emit("agent.launchReady", { argv: argv }) }
onFailed: function(message) { backend.emit("agent.launchFailed", { message: message }) }
// Clipboard text goes over stdin, never argv (other users can read argv).
function copyText(text) { clipboard.copy(text) }

ClipboardWriter {
id: clipboard
onFailed: function(message) { backend.log("error", "clipboard", message, null) }
}

AppFocuser {
id: appFocuser
onLaunchRequested: function(candidate) { backend.launchApp(candidate) }
}


Process {
id: mkdirs
command: ["mkdir", "-p"].concat(Paths.directoriesToCreate(backend.paths))
// Moves directories left under the old name, then creates everything
// owner-only and repairs modes left readable by older builds.
command: Paths.setupArgv(backend.paths, [Schema.documents.settings.file, Schema.documents.frecency.file, Schema.documents.calculator.file]
.map(function(file) { return backend.paths.db + "/" + file }))
onExited: function(exitCode) {
if (exitCode !== 0) backend.log("error", "paths", "could not create data directories", { exitCode: exitCode })
backend.directoriesReady = true
Expand Down
Loading