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
22 changes: 22 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Check

on:
pull_request:
push:
branches: [main]

jobs:
extensions:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get the SDK types
run: git clone --depth 1 https://github.com/backslash-app/backslash .backslash
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- run: deno fmt --check extensions scripts
- run: deno lint extensions scripts
- run: deno check extensions/*/commands/*.ts
- name: Verify registry entries (clones each extension at its commit)
run: deno run -A scripts/validate.ts
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.backslash
node_modules
136 changes: 81 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,81 @@
![bs_banner](https://github.com/user-attachments/assets/ca33bf1a-9910-4d18-9bf4-bbfc62be8df3)
[Backslash app](https://github.com/backslash-app/backslash) | [Documentation](https://github.com/backslash-app/backslash/blob/main/DOCUMENTATION.md) | [Releases](https://github.com/backslash-app/backslash/releases) | [Discord server](https://discord.gg/sTzwBzDkK9)

## Backslash Plugins: Where Dreams Come to Compile (Sometimes)

Welcome to the Backslash Plugins repository, the digital equivalent of that one drawer in your kitchen where you throw all the gadgets you're convinced will change your life... someday.

### What is this madness?

This is where the Backslash community comes to share their plugins, snippets of code that range from "mildly useful" to "why does this exist?" It's like a potluck, but instead of questionable casseroles, you're sharing questionable code. Yum!

### Why should I care?

Because, dear Linux enthusiast, this is your chance to:

1. Show off your coding skills
2. Solve problems you didn't know you had
3. Create problems you definitely don't need

### How do I contribute?

1. Fork this repo (it's like stealing, but legal)
2. Create your plugin (coffee recommended, sanity optional)
3. Submit a pull request (and pray to Linus Torvalds for mercy)

Remember, the only bad plugin is the one that doesn't compile. Actually, scratch that. We've seen some that compile and are still pretty bad.

### Community Guidelines

1. Be nice (or at least pretend to be)
2. Don't break things (unless it's funny, then maybe)
3. Document your code (future you will thank present you)
4. Have fun (that's an order)

### FAQ

**Q: Can I create a plugin that [insert ridiculous idea here]?**
**A:** We were going to say "within reason," but who are we kidding? Go nuts! Just remember, with great power comes great responsibility... and possibly some raised eyebrows.

**Q: Will my plugin be accepted?**
**A:** If it runs without setting fire to the user's computer, you're already ahead of 50% of the submissions. We have standards, they're just very, very low.

**Q: What icon library does backslash use for the plugins?**
**A:** It's [Phosphor Icons](https://phosphoricons.com/), you can use their icons for your plugins.

**Q: I found a bug in a plugin. What should I do?**
**A:** First, make sure it's actually a bug and not a "feature." If it's indeed a bug, congratulations! You've discovered a new species. Report it, and maybe the plugin author will name it after you.

## In Conclusion

This is your playground, your canvas, your digital sandbox. Create, share, and most importantly, try not to break too many things in the process. Remember, in the world of Backslash plugins, you're not just a coder – you're an artist. A slightly unhinged, possibly caffeinated artist, but an artist nonetheless.

Now go forth and plugin! May your code be bug-free and your compiler errors be few.

_P.S. If you accidentally create SkyNet while making a plugin, we take no responsibility. But we'd appreciate a heads-up before the robot apocalypse, thanks._
# Backslash extensions

The registry of [Backslash](https://github.com/backslash-app/backslash) extensions, and the first-party extensions
themselves.

```sh
backslash search # browse the registry (or "Browse Extensions" in the launcher)
backslash install github # install, after showing what it is allowed to do
backslash update # update everything installed
```

## How the registry works

`registry/<id>.toml` is a small file per extension. It points to the extension's code **wherever it lives** (your own
repository), pinned to a commit, with a checksum of its files and the list of what it is allowed to do:

```toml
id = "github"
title = "GitHub"
description = "Search repositories and follow your pull requests"
author = "Backslash"
version = "1.0.0"
repository = "https://github.com/backslash-app/plugins"
path = "extensions/github"
commit = "a64f60074557298bf4cabf235c6e91e1f4e54082"
checksum = "sha256:37f89a1e…"
permissions = ["Access api.github.com"]
commands = ["Search GitHub Repositories", "My Pull Requests"]
```

Backslash installs exactly that commit and refuses anything whose files, version or permissions differ from the entry.
So:

- **You keep your code** in your repository; publishing a version is a one-file pull request.
- **Reviews are small and meaningful**: reviewers read the code at one commit and see the permissions it gets. Nothing
can change after the review.
- **Users see the permissions before installing**, and are asked again when an update wants more. The sandbox (Deno)
enforces them.

## Publishing an extension

1. Write it (see the [extension guide](https://github.com/backslash-app/backslash/blob/main/docs/EXTENSIONS.md)), commit
and push it to a public git repository.
2. Run `backslash publish path/to/extension`. It prints the registry entry for your last commit.
3. Open a pull request adding that file as `registry/<id>.toml`.
4. To release a new version: bump `version` in `backslash.toml`, commit, push, run `backslash publish` again, and open a
pull request updating the file.

CI checks every entry: it clones your repository at the commit, recomputes the checksum and compares the manifest and
permissions (`deno run -A scripts/validate.ts`).

Anyone can also install straight from a repository, without the registry:
`backslash install https://github.com/you/your-extension` (users are told it is not reviewed).

### Review guidelines

- The extension does what its title and description say, nothing else.
- Permissions are as narrow as possible: specific hosts, specific programs.
- No obfuscated or minified code, no downloading and running code at runtime.
- Secrets are `type = "secret"` preferences, never hardcoded.
- Destructive actions are marked `destructive` and ask for confirmation.

## First-party extensions

| Extension | Commands | Permissions |
| ----------------------------------- | ---------------------------------------------- | ------------------------------------ |
| [GitHub](extensions/github) | Search repositories, my pull requests | `api.github.com` |
| [Packages](extensions/packages) | Search apt/dnf/pacman/zypper and Flathub | runs the package tools (search only) |
| [Services](extensions/systemd) | Start, stop, restart, logs of systemd services | `systemctl`, `journalctl` |
| [Containers](extensions/docker) | Docker/Podman: start, stop, logs, remove | `docker`, `podman` |
| [Emoji & Symbols](extensions/emoji) | Search and copy | none |

Their registry entries are regenerated with `scripts/update-entries.sh` after a change is merged.

## Developing here

```sh
git clone https://github.com/backslash-app/backslash .backslash # for the SDK types
deno check extensions/*/commands/*.ts
backslash dev extensions/github # live reload in the launcher
```
5 changes: 0 additions & 5 deletions backslash/bs-documentation.js

This file was deleted.

6 changes: 0 additions & 6 deletions backslash/bs-quit-app.js

This file was deleted.

6 changes: 0 additions & 6 deletions backslash/index.js

This file was deleted.

27 changes: 0 additions & 27 deletions backslash/manifest.yml

This file was deleted.

8 changes: 8 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"imports": {
"@backslash/api": "./.backslash/sdk/mod.ts",
"@std/toml": "jsr:@std/toml@^1"
},
"fmt": { "lineWidth": 120 },
"lint": { "rules": { "exclude": ["no-explicit-any"] } }
}
26 changes: 26 additions & 0 deletions deno.lock

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

16 changes: 16 additions & 0 deletions extensions/docker/backslash.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[extension]
id = "docker"
title = "Containers"
description = "Docker and Podman containers: start, stop, logs"
version = "1.0.0"
author = "Backslash"
icon = "utilities-terminal"
license = "MIT"

[permissions]
run = ["docker", "podman"]

[[commands]]
name = "containers"
title = "Containers"
keywords = ["docker", "podman", "container", "compose"]
72 changes: 72 additions & 0 deletions extensions/docker/commands/containers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { action, copy, detail, exec, type Item, list, refresh } from "@backslash/api";

interface Container {
ID: string;
Image: string;
Names: string;
State: string;
Status: string;
Ports?: string;
}

/** Docker first, Podman otherwise (same CLI). */
async function engine(): Promise<string> {
for (const program of ["docker", "podman"]) {
try {
await exec(program, ["version", "--format", "{{.Client.Version}}"]);
return program;
} catch {
// try the next one
}
}
throw new Error("Neither Docker nor Podman is available");
}

function item(cli: string, c: Container): Item {
const running = c.State === "running";
const run = (verb: string) => async () => {
await exec(cli, [verb, c.ID]);
return refresh();
};
return {
id: c.ID,
title: c.Names,
subtitle: c.Image,
icon: "utilities-terminal",
section: running ? "Running" : "Stopped",
keywords: [c.ID, c.Image],
accessories: [{ text: c.Status }, { tag: c.State, color: running ? "green" : "gray" }],
actions: [
action("Show Logs", async () => {
const logs = await exec(cli, ["logs", "--tail", "100", c.ID]).catch((e) => e.message);
return detail({
title: c.Names,
markdown: `# ${c.Names}\n\n\`\`\`\n${logs || "No logs."}\n\`\`\``,
metadata: { Image: c.Image, Status: c.Status, Ports: c.Ports || "—", ID: c.ID },
});
}),
running
? action("Stop", run("stop"), { shortcut: "<Ctrl>s" })
: action("Start", run("start"), { shortcut: "<Ctrl>s" }),
action("Restart", run("restart"), { shortcut: "<Ctrl>r" }),
copy(`${cli} exec -it ${c.Names} sh`, "Copy Shell Command"),
copy(c.ID, "Copy ID"),
action("Remove", run("rm"), { destructive: true, confirm: `Remove ${c.Names}?` }),
],
};
}

export default list({
placeholder: "Filter containers…",
empty: { title: "No containers", description: "Nothing created yet" },
items: async () => {
const cli = await engine();
const out = await exec(cli, ["ps", "-a", "--format", "{{json .}}"]);
const containers = out.split("\n").filter(Boolean).map((l) => JSON.parse(l) as Container)
// Podman prints `Names` as an array.
.map((c) => ({ ...c, Names: Array.isArray(c.Names) ? c.Names.join(", ") : c.Names }));
return containers.sort((a, b) => Number(b.State === "running") - Number(a.State === "running")).map((c) =>
item(cli, c)
);
},
});
13 changes: 13 additions & 0 deletions extensions/emoji/backslash.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[extension]
id = "emoji"
title = "Emoji & Symbols"
description = "Search emoji and symbols by name and copy them"
version = "1.0.0"
author = "Backslash"
icon = "😀"
license = "MIT"

[[commands]]
name = "search"
title = "Search Emoji & Symbols"
keywords = ["emoji", "smiley", "symbol", "unicode", "emoticon"]
29 changes: 29 additions & 0 deletions extensions/emoji/commands/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { copy, list } from "@backslash/api";
import data from "../emoji.json" with { type: "json" };

// Names are "common words · official unicode name"; the title keeps the
// official name and the common words become search keywords.
const official = (name: string) => name.split(" · ").pop()!;
const title = (name: string) => official(name).replace(/\b\w/g, (c) => c.toUpperCase());

export default list({
placeholder: "Search emoji and symbols…",
items: (data as [string, string][]).map(([emoji, name]) => {
const codepoints = [...emoji]
.filter((c) => c !== "️")
.map((c) => `U+${c.codePointAt(0)!.toString(16).toUpperCase().padStart(4, "0")}`)
.join(" ");
return {
id: codepoints,
title: title(name),
subtitle: codepoints,
icon: emoji,
keywords: name.includes(" · ") ? [name.split(" · ")[0], ...name.split(" ")] : name.split(" "),
actions: [
copy(emoji, "Copy Emoji"),
copy(title(name), "Copy Name"),
copy(codepoints, "Copy Code Point"),
],
};
}),
});
Loading
Loading