From a64f60074557298bf4cabf235c6e91e1f4e54082 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 18:20:12 +0000 Subject: [PATCH 1/2] Add first-party extensions for Backslash 0.3 GitHub, Packages, Services (systemd), Containers (Docker/Podman) and Emoji & Symbols, written for the new TypeScript SDK. The JavaScript plugins of Backslash 0.2 are removed: the new engine does not load them. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WzDMDJteorsmKRm6FVPX94 --- .gitignore | 2 + backslash/bs-documentation.js | 5 - backslash/bs-quit-app.js | 6 - backslash/index.js | 6 - backslash/manifest.yml | 27 - deno.json | 8 + extensions/docker/backslash.toml | 16 + extensions/docker/commands/containers.ts | 72 + extensions/emoji/backslash.toml | 13 + extensions/emoji/commands/search.ts | 29 + extensions/emoji/emoji.json | 1729 +++++++++++++++++++ extensions/github/backslash.toml | 28 + extensions/github/commands/api.ts | 40 + extensions/github/commands/pull-requests.ts | 50 + extensions/github/commands/repositories.ts | 42 + extensions/packages/backslash.toml | 16 + extensions/packages/commands/search.ts | 106 ++ extensions/systemd/backslash.toml | 17 + extensions/systemd/commands/services.ts | 78 + google/google-calendar-add.js | 5 - google/google-calendar-view.js | 5 - google/index.js | 6 - google/manifest.yml | 29 - npm/index.js | 5 - npm/manifest.yml | 15 - npm/npm-search.js | 86 - scripts/validate.ts | 141 ++ system/index.js | 12 - system/manifest.yml | 101 -- system/sys-kill-process.js | 132 -- system/sys-poweroff.js | 6 - system/sys-reboot.js | 6 - system/sys-set-volume-0.js | 5 - system/sys-set-volume-100.js | 5 - system/sys-set-volume-25.js | 5 - system/sys-set-volume-50.js | 5 - system/sys-set-volume-75.js | 5 - 37 files changed, 2387 insertions(+), 477 deletions(-) create mode 100644 .gitignore delete mode 100644 backslash/bs-documentation.js delete mode 100644 backslash/bs-quit-app.js delete mode 100644 backslash/index.js delete mode 100644 backslash/manifest.yml create mode 100644 deno.json create mode 100644 extensions/docker/backslash.toml create mode 100644 extensions/docker/commands/containers.ts create mode 100644 extensions/emoji/backslash.toml create mode 100644 extensions/emoji/commands/search.ts create mode 100644 extensions/emoji/emoji.json create mode 100644 extensions/github/backslash.toml create mode 100644 extensions/github/commands/api.ts create mode 100644 extensions/github/commands/pull-requests.ts create mode 100644 extensions/github/commands/repositories.ts create mode 100644 extensions/packages/backslash.toml create mode 100644 extensions/packages/commands/search.ts create mode 100644 extensions/systemd/backslash.toml create mode 100644 extensions/systemd/commands/services.ts delete mode 100644 google/google-calendar-add.js delete mode 100644 google/google-calendar-view.js delete mode 100644 google/index.js delete mode 100644 google/manifest.yml delete mode 100644 npm/index.js delete mode 100644 npm/manifest.yml delete mode 100644 npm/npm-search.js create mode 100644 scripts/validate.ts delete mode 100644 system/index.js delete mode 100644 system/manifest.yml delete mode 100644 system/sys-kill-process.js delete mode 100644 system/sys-poweroff.js delete mode 100644 system/sys-reboot.js delete mode 100644 system/sys-set-volume-0.js delete mode 100644 system/sys-set-volume-100.js delete mode 100644 system/sys-set-volume-25.js delete mode 100644 system/sys-set-volume-50.js delete mode 100644 system/sys-set-volume-75.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98bd8f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.backslash +node_modules diff --git a/backslash/bs-documentation.js b/backslash/bs-documentation.js deleted file mode 100644 index cf5beec..0000000 --- a/backslash/bs-documentation.js +++ /dev/null @@ -1,5 +0,0 @@ -const URL = `https://github.com/backslash-app/backslash/blob/main/DOCUMENTATION.md`; - -const run = async (_, { shell }) => await shell.openExternal(URL); - -module.exports = { run, actions: [] }; diff --git a/backslash/bs-quit-app.js b/backslash/bs-quit-app.js deleted file mode 100644 index 4ddfbff..0000000 --- a/backslash/bs-quit-app.js +++ /dev/null @@ -1,6 +0,0 @@ -const run = async (_, { app }) => { - app.isQuitting = true; - await app.quit(); -}; - -module.exports = { run, actions: [] }; diff --git a/backslash/index.js b/backslash/index.js deleted file mode 100644 index b8c9b2e..0000000 --- a/backslash/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - commands: { - "bs-documentation": require('./bs-documentation'), - "bs-quit-app": require('./bs-quit-app') - }, -}; diff --git a/backslash/manifest.yml b/backslash/manifest.yml deleted file mode 100644 index f4d312b..0000000 --- a/backslash/manifest.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: backslash -label: Backslash -version: 1.0.0 -author: Backslash - -commands: - - name: bs-documentation - label: Documentation - isImmediate: true - bgColor: "#4F46E5" - color: "#FFFFFF" - icon: book - keywords: - - documentation - - bs - - - name: bs-quit-app - label: Quit Backslash - isImmediate: true - bgColor: "#4F46E5" - color: "#FFFFFF" - icon: power - keywords: - - quit - - exit - - close - - bs \ No newline at end of file diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..e42dd36 --- /dev/null +++ b/deno.json @@ -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"] } } +} diff --git a/extensions/docker/backslash.toml b/extensions/docker/backslash.toml new file mode 100644 index 0000000..6593cbe --- /dev/null +++ b/extensions/docker/backslash.toml @@ -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"] diff --git a/extensions/docker/commands/containers.ts b/extensions/docker/commands/containers.ts new file mode 100644 index 0000000..79f8ec7 --- /dev/null +++ b/extensions/docker/commands/containers.ts @@ -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 { + 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: "s" }) + : action("Start", run("start"), { shortcut: "s" }), + action("Restart", run("restart"), { shortcut: "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) + ); + }, +}); diff --git a/extensions/emoji/backslash.toml b/extensions/emoji/backslash.toml new file mode 100644 index 0000000..1a1a52f --- /dev/null +++ b/extensions/emoji/backslash.toml @@ -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"] diff --git a/extensions/emoji/commands/search.ts b/extensions/emoji/commands/search.ts new file mode 100644 index 0000000..161d52a --- /dev/null +++ b/extensions/emoji/commands/search.ts @@ -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"), + ], + }; + }), +}); diff --git a/extensions/emoji/emoji.json b/extensions/emoji/emoji.json new file mode 100644 index 0000000..3069551 --- /dev/null +++ b/extensions/emoji/emoji.json @@ -0,0 +1,1729 @@ +[ + ["๐ŸŒ€", "cyclone"], + ["๐ŸŒ", "foggy"], + ["๐ŸŒ‚", "closed umbrella"], + ["๐ŸŒƒ", "night with stars"], + ["๐ŸŒ„", "sunrise over mountains"], + ["๐ŸŒ…", "sunrise"], + ["๐ŸŒ†", "cityscape at dusk"], + ["๐ŸŒ‡", "sunset over buildings"], + ["๐ŸŒˆ", "rainbow"], + ["๐ŸŒ‰", "bridge at night"], + ["๐ŸŒŠ", "water wave"], + ["๐ŸŒ‹", "volcano"], + ["๐ŸŒŒ", "milky way"], + ["๐ŸŒ", "earth globe europe-africa"], + ["๐ŸŒŽ", "earth globe americas"], + ["๐ŸŒ", "earth globe asia-australia"], + ["๐ŸŒ", "globe with meridians"], + ["๐ŸŒ‘", "new moon symbol"], + ["๐ŸŒ’", "waxing crescent moon symbol"], + ["๐ŸŒ“", "first quarter moon symbol"], + ["๐ŸŒ”", "waxing gibbous moon symbol"], + ["๐ŸŒ•", "full moon symbol"], + ["๐ŸŒ–", "waning gibbous moon symbol"], + ["๐ŸŒ—", "last quarter moon symbol"], + ["๐ŸŒ˜", "waning crescent moon symbol"], + ["๐ŸŒ™", "crescent moon"], + ["๐ŸŒš", "new moon with face"], + ["๐ŸŒ›", "first quarter moon with face"], + ["๐ŸŒœ", "last quarter moon with face"], + ["๐ŸŒ", "full moon with face"], + ["๐ŸŒž", "sun with face"], + ["๐ŸŒŸ", "glowing star"], + ["๐ŸŒ ", "shooting star"], + ["๐ŸŒก", "thermometer"], + ["๐ŸŒข", "black droplet"], + ["๐ŸŒฃ", "white sun"], + ["๐ŸŒค", "white sun with small cloud"], + ["๐ŸŒฅ", "white sun behind cloud"], + ["๐ŸŒฆ", "white sun behind cloud with rain"], + ["๐ŸŒง", "cloud with rain"], + ["๐ŸŒจ", "cloud with snow"], + ["๐ŸŒฉ", "cloud with lightning"], + ["๐ŸŒช", "cloud with tornado"], + ["๐ŸŒซ", "fog"], + ["๐ŸŒฌ", "wind blowing face"], + ["๐ŸŒญ", "hot dog"], + ["๐ŸŒฎ", "taco"], + ["๐ŸŒฏ", "burrito"], + ["๐ŸŒฐ", "chestnut"], + ["๐ŸŒฑ", "seedling"], + ["๐ŸŒฒ", "evergreen tree"], + ["๐ŸŒณ", "deciduous tree"], + ["๐ŸŒด", "palm tree"], + ["๐ŸŒต", "cactus"], + ["๐ŸŒถ", "hot pepper"], + ["๐ŸŒท", "tulip"], + ["๐ŸŒธ", "cherry blossom"], + ["๐ŸŒน", "rose"], + ["๐ŸŒบ", "hibiscus"], + ["๐ŸŒป", "sunflower"], + ["๐ŸŒผ", "blossom"], + ["๐ŸŒฝ", "ear of maize"], + ["๐ŸŒพ", "ear of rice"], + ["๐ŸŒฟ", "herb"], + ["๐Ÿ€", "four leaf clover"], + ["๐Ÿ", "maple leaf"], + ["๐Ÿ‚", "fallen leaf"], + ["๐Ÿƒ", "leaf fluttering in wind"], + ["๐Ÿ„", "mushroom"], + ["๐Ÿ…", "tomato"], + ["๐Ÿ†", "aubergine"], + ["๐Ÿ‡", "grapes"], + ["๐Ÿˆ", "melon"], + ["๐Ÿ‰", "watermelon"], + ["๐ŸŠ", "tangerine"], + ["๐Ÿ‹", "lemon"], + ["๐ŸŒ", "banana"], + ["๐Ÿ", "pineapple"], + ["๐ŸŽ", "red apple"], + ["๐Ÿ", "green apple"], + ["๐Ÿ", "pear"], + ["๐Ÿ‘", "peach"], + ["๐Ÿ’", "cherries"], + ["๐Ÿ“", "strawberry"], + ["๐Ÿ”", "hamburger"], + ["๐Ÿ•", "slice of pizza"], + ["๐Ÿ–", "meat on bone"], + ["๐Ÿ—", "poultry leg"], + ["๐Ÿ˜", "rice cracker"], + ["๐Ÿ™", "rice ball"], + ["๐Ÿš", "cooked rice"], + ["๐Ÿ›", "curry and rice"], + ["๐Ÿœ", "steaming bowl"], + ["๐Ÿ", "spaghetti"], + ["๐Ÿž", "bread"], + ["๐ŸŸ", "french fries"], + ["๐Ÿ ", "roasted sweet potato"], + ["๐Ÿก", "dango"], + ["๐Ÿข", "oden"], + ["๐Ÿฃ", "sushi"], + ["๐Ÿค", "fried shrimp"], + ["๐Ÿฅ", "fish cake with swirl design"], + ["๐Ÿฆ", "soft ice cream"], + ["๐Ÿง", "shaved ice"], + ["๐Ÿจ", "ice cream"], + ["๐Ÿฉ", "doughnut"], + ["๐Ÿช", "cookie"], + ["๐Ÿซ", "chocolate bar"], + ["๐Ÿฌ", "candy"], + ["๐Ÿญ", "lollipop"], + ["๐Ÿฎ", "custard"], + ["๐Ÿฏ", "honey pot"], + ["๐Ÿฐ", "shortcake"], + ["๐Ÿฑ", "bento box"], + ["๐Ÿฒ", "pot of food"], + ["๐Ÿณ", "cooking"], + ["๐Ÿด", "fork and knife"], + ["๐Ÿต", "teacup without handle"], + ["๐Ÿถ", "sake bottle and cup"], + ["๐Ÿท", "wine glass"], + ["๐Ÿธ", "cocktail glass"], + ["๐Ÿน", "tropical drink"], + ["๐Ÿบ", "beer mug"], + ["๐Ÿป", "clinking beer mugs"], + ["๐Ÿผ", "baby bottle"], + ["๐Ÿฝ", "fork and knife with plate"], + ["๐Ÿพ", "bottle with popping cork"], + ["๐Ÿฟ", "popcorn"], + ["๐ŸŽ€", "ribbon"], + ["๐ŸŽ", "wrapped present"], + ["๐ŸŽ‚", "birthday cake"], + ["๐ŸŽƒ", "jack-o-lantern"], + ["๐ŸŽ„", "christmas tree"], + ["๐ŸŽ…", "father christmas"], + ["๐ŸŽ†", "fireworks"], + ["๐ŸŽ‡", "firework sparkler"], + ["๐ŸŽˆ", "balloon"], + ["๐ŸŽ‰", "party tada congratulations ยท party popper"], + ["๐ŸŽŠ", "confetti ball"], + ["๐ŸŽ‹", "tanabata tree"], + ["๐ŸŽŒ", "crossed flags"], + ["๐ŸŽ", "pine decoration"], + ["๐ŸŽŽ", "japanese dolls"], + ["๐ŸŽ", "carp streamer"], + ["๐ŸŽ", "wind chime"], + ["๐ŸŽ‘", "moon viewing ceremony"], + ["๐ŸŽ’", "school satchel"], + ["๐ŸŽ“", "graduation cap"], + ["๐ŸŽ”", "heart with tip on the left"], + ["๐ŸŽ•", "bouquet of flowers"], + ["๐ŸŽ–", "military medal"], + ["๐ŸŽ—", "reminder ribbon"], + ["๐ŸŽ˜", "musical keyboard with jacks"], + ["๐ŸŽ™", "studio microphone"], + ["๐ŸŽš", "level slider"], + ["๐ŸŽ›", "control knobs"], + ["๐ŸŽœ", "beamed ascending musical notes"], + ["๐ŸŽ", "beamed descending musical notes"], + ["๐ŸŽž", "film frames"], + ["๐ŸŽŸ", "admission tickets"], + ["๐ŸŽ ", "carousel horse"], + ["๐ŸŽก", "ferris wheel"], + ["๐ŸŽข", "roller coaster"], + ["๐ŸŽฃ", "fishing pole and fish"], + ["๐ŸŽค", "microphone"], + ["๐ŸŽฅ", "movie camera"], + ["๐ŸŽฆ", "cinema"], + ["๐ŸŽง", "headphone"], + ["๐ŸŽจ", "artist palette"], + ["๐ŸŽฉ", "top hat"], + ["๐ŸŽช", "circus tent"], + ["๐ŸŽซ", "ticket"], + ["๐ŸŽฌ", "clapper board"], + ["๐ŸŽญ", "performing arts"], + ["๐ŸŽฎ", "video game"], + ["๐ŸŽฏ", "direct hit"], + ["๐ŸŽฐ", "slot machine"], + ["๐ŸŽฑ", "billiards"], + ["๐ŸŽฒ", "game die"], + ["๐ŸŽณ", "bowling"], + ["๐ŸŽด", "flower playing cards"], + ["๐ŸŽต", "musical note"], + ["๐ŸŽถ", "multiple musical notes"], + ["๐ŸŽท", "saxophone"], + ["๐ŸŽธ", "guitar"], + ["๐ŸŽน", "musical keyboard"], + ["๐ŸŽบ", "trumpet"], + ["๐ŸŽป", "violin"], + ["๐ŸŽผ", "musical score"], + ["๐ŸŽฝ", "running shirt with sash"], + ["๐ŸŽพ", "tennis racquet and ball"], + ["๐ŸŽฟ", "ski and ski boot"], + ["๐Ÿ€", "basketball and hoop"], + ["๐Ÿ", "chequered flag"], + ["๐Ÿ‚", "snowboarder"], + ["๐Ÿƒ", "runner"], + ["๐Ÿ„", "surfer"], + ["๐Ÿ…", "sports medal"], + ["๐Ÿ†", "trophy"], + ["๐Ÿ‡", "horse racing"], + ["๐Ÿˆ", "american football"], + ["๐Ÿ‰", "rugby football"], + ["๐ŸŠ", "swimmer"], + ["๐Ÿ‹", "weight lifter"], + ["๐ŸŒ", "golfer"], + ["๐Ÿ", "racing motorcycle"], + ["๐ŸŽ", "racing car"], + ["๐Ÿ", "cricket bat and ball"], + ["๐Ÿ", "volleyball"], + ["๐Ÿ‘", "field hockey stick and ball"], + ["๐Ÿ’", "ice hockey stick and puck"], + ["๐Ÿ“", "table tennis paddle and ball"], + ["๐Ÿ”", "snow capped mountain"], + ["๐Ÿ•", "camping"], + ["๐Ÿ–", "beach with umbrella"], + ["๐Ÿ—", "building construction"], + ["๐Ÿ˜", "house buildings"], + ["๐Ÿ™", "cityscape"], + ["๐Ÿš", "derelict house building"], + ["๐Ÿ›", "classical building"], + ["๐Ÿœ", "desert"], + ["๐Ÿ", "desert island"], + ["๐Ÿž", "national park"], + ["๐ŸŸ", "stadium"], + ["๐Ÿ ", "house building"], + ["๐Ÿก", "house with garden"], + ["๐Ÿข", "office building"], + ["๐Ÿฃ", "japanese post office"], + ["๐Ÿค", "european post office"], + ["๐Ÿฅ", "hospital"], + ["๐Ÿฆ", "bank"], + ["๐Ÿง", "automated teller machine"], + ["๐Ÿจ", "hotel"], + ["๐Ÿฉ", "love hotel"], + ["๐Ÿช", "convenience store"], + ["๐Ÿซ", "school"], + ["๐Ÿฌ", "department store"], + ["๐Ÿญ", "factory"], + ["๐Ÿฎ", "izakaya lantern"], + ["๐Ÿฏ", "japanese castle"], + ["๐Ÿฐ", "european castle"], + ["๐Ÿฑ", "white pennant"], + ["๐Ÿฒ", "black pennant"], + ["๐Ÿณ", "waving white flag"], + ["๐Ÿด", "waving black flag"], + ["๐Ÿต", "rosette"], + ["๐Ÿถ", "black rosette"], + ["๐Ÿท", "label"], + ["๐Ÿธ", "badminton racquet and shuttlecock"], + ["๐Ÿน", "bow and arrow"], + ["๐Ÿบ", "amphora"], + ["๐Ÿ€", "rat"], + ["๐Ÿ", "mouse"], + ["๐Ÿ‚", "ox"], + ["๐Ÿƒ", "water buffalo"], + ["๐Ÿ„", "cow"], + ["๐Ÿ…", "tiger"], + ["๐Ÿ†", "leopard"], + ["๐Ÿ‡", "rabbit"], + ["๐Ÿˆ", "cat"], + ["๐Ÿ‰", "dragon"], + ["๐ŸŠ", "crocodile"], + ["๐Ÿ‹", "whale"], + ["๐ŸŒ", "snail"], + ["๐Ÿ", "snake"], + ["๐ŸŽ", "horse"], + ["๐Ÿ", "ram"], + ["๐Ÿ", "goat"], + ["๐Ÿ‘", "sheep"], + ["๐Ÿ’", "monkey"], + ["๐Ÿ“", "rooster"], + ["๐Ÿ”", "chicken"], + ["๐Ÿ•", "dog"], + ["๐Ÿ–", "pig"], + ["๐Ÿ—", "boar"], + ["๐Ÿ˜", "elephant"], + ["๐Ÿ™", "octopus"], + ["๐Ÿš", "spiral shell"], + ["๐Ÿ›", "bug"], + ["๐Ÿœ", "ant"], + ["๐Ÿ", "honeybee"], + ["๐Ÿž", "lady beetle"], + ["๐ŸŸ", "fish"], + ["๐Ÿ ", "tropical fish"], + ["๐Ÿก", "blowfish"], + ["๐Ÿข", "turtle"], + ["๐Ÿฃ", "hatching chick"], + ["๐Ÿค", "baby chick"], + ["๐Ÿฅ", "front-facing baby chick"], + ["๐Ÿฆ", "bird"], + ["๐Ÿง", "penguin"], + ["๐Ÿจ", "koala"], + ["๐Ÿฉ", "poodle"], + ["๐Ÿช", "dromedary camel"], + ["๐Ÿซ", "bactrian camel"], + ["๐Ÿฌ", "dolphin"], + ["๐Ÿญ", "mouse face"], + ["๐Ÿฎ", "cow face"], + ["๐Ÿฏ", "tiger face"], + ["๐Ÿฐ", "rabbit face"], + ["๐Ÿฑ", "cat face"], + ["๐Ÿฒ", "dragon face"], + ["๐Ÿณ", "spouting whale"], + ["๐Ÿด", "horse face"], + ["๐Ÿต", "monkey face"], + ["๐Ÿถ", "dog face"], + ["๐Ÿท", "pig face"], + ["๐Ÿธ", "frog face"], + ["๐Ÿน", "hamster face"], + ["๐Ÿบ", "wolf face"], + ["๐Ÿป", "bear face"], + ["๐Ÿผ", "panda face"], + ["๐Ÿฝ", "pig nose"], + ["๐Ÿพ", "paw prints"], + ["๐Ÿฟ", "chipmunk"], + ["๐Ÿ‘€", "eyes look ยท eyes"], + ["๐Ÿ‘", "eye"], + ["๐Ÿ‘‚", "ear"], + ["๐Ÿ‘ƒ", "nose"], + ["๐Ÿ‘„", "mouth"], + ["๐Ÿ‘…", "tongue"], + ["๐Ÿ‘†", "white up pointing backhand index"], + ["๐Ÿ‘‡", "white down pointing backhand index"], + ["๐Ÿ‘ˆ", "white left pointing backhand index"], + ["๐Ÿ‘‰", "white right pointing backhand index"], + ["๐Ÿ‘Š", "fisted hand sign"], + ["๐Ÿ‘‹", "wave hello hi bye ยท waving hand sign"], + ["๐Ÿ‘Œ", "ok hand sign"], + ["๐Ÿ‘", "thumbs up like ok yes +1 ยท thumbs up sign"], + ["๐Ÿ‘Ž", "thumbs down sign"], + ["๐Ÿ‘", "clap applause bravo ยท clapping hands sign"], + ["๐Ÿ‘", "open hands sign"], + ["๐Ÿ‘‘", "crown"], + ["๐Ÿ‘’", "womans hat"], + ["๐Ÿ‘“", "eyeglasses"], + ["๐Ÿ‘”", "necktie"], + ["๐Ÿ‘•", "t-shirt"], + ["๐Ÿ‘–", "jeans"], + ["๐Ÿ‘—", "dress"], + ["๐Ÿ‘˜", "kimono"], + ["๐Ÿ‘™", "bikini"], + ["๐Ÿ‘š", "womans clothes"], + ["๐Ÿ‘›", "purse"], + ["๐Ÿ‘œ", "handbag"], + ["๐Ÿ‘", "pouch"], + ["๐Ÿ‘ž", "mans shoe"], + ["๐Ÿ‘Ÿ", "athletic shoe"], + ["๐Ÿ‘ ", "high-heeled shoe"], + ["๐Ÿ‘ก", "womans sandal"], + ["๐Ÿ‘ข", "womans boots"], + ["๐Ÿ‘ฃ", "footprints"], + ["๐Ÿ‘ค", "bust in silhouette"], + ["๐Ÿ‘ฅ", "busts in silhouette"], + ["๐Ÿ‘ฆ", "boy"], + ["๐Ÿ‘ง", "girl"], + ["๐Ÿ‘จ", "man"], + ["๐Ÿ‘ฉ", "woman"], + ["๐Ÿ‘ช", "family"], + ["๐Ÿ‘ซ", "man and woman holding hands"], + ["๐Ÿ‘ฌ", "two men holding hands"], + ["๐Ÿ‘ญ", "two women holding hands"], + ["๐Ÿ‘ฎ", "police officer"], + ["๐Ÿ‘ฏ", "woman with bunny ears"], + ["๐Ÿ‘ฐ", "bride with veil"], + ["๐Ÿ‘ฑ", "person with blond hair"], + ["๐Ÿ‘ฒ", "man with gua pi mao"], + ["๐Ÿ‘ณ", "man with turban"], + ["๐Ÿ‘ด", "older man"], + ["๐Ÿ‘ต", "older woman"], + ["๐Ÿ‘ถ", "baby"], + ["๐Ÿ‘ท", "construction worker"], + ["๐Ÿ‘ธ", "princess"], + ["๐Ÿ‘น", "japanese ogre"], + ["๐Ÿ‘บ", "japanese goblin"], + ["๐Ÿ‘ป", "ghost"], + ["๐Ÿ‘ผ", "baby angel"], + ["๐Ÿ‘ฝ", "extraterrestrial alien"], + ["๐Ÿ‘พ", "alien monster"], + ["๐Ÿ‘ฟ", "imp"], + ["๐Ÿ’€", "skull"], + ["๐Ÿ’", "information desk person"], + ["๐Ÿ’‚", "guardsman"], + ["๐Ÿ’ƒ", "dancer"], + ["๐Ÿ’„", "lipstick"], + ["๐Ÿ’…", "nail polish"], + ["๐Ÿ’†", "face massage"], + ["๐Ÿ’‡", "haircut"], + ["๐Ÿ’ˆ", "barber pole"], + ["๐Ÿ’‰", "syringe"], + ["๐Ÿ’Š", "pill"], + ["๐Ÿ’‹", "kiss mark"], + ["๐Ÿ’Œ", "love letter"], + ["๐Ÿ’", "ring"], + ["๐Ÿ’Ž", "gem stone"], + ["๐Ÿ’", "kiss"], + ["๐Ÿ’", "bouquet"], + ["๐Ÿ’‘", "couple with heart"], + ["๐Ÿ’’", "wedding"], + ["๐Ÿ’“", "beating heart"], + ["๐Ÿ’”", "broken heart"], + ["๐Ÿ’•", "two hearts"], + ["๐Ÿ’–", "sparkling heart"], + ["๐Ÿ’—", "growing heart"], + ["๐Ÿ’˜", "heart with arrow"], + ["๐Ÿ’™", "blue heart"], + ["๐Ÿ’š", "green heart"], + ["๐Ÿ’›", "yellow heart"], + ["๐Ÿ’œ", "purple heart"], + ["๐Ÿ’", "heart with ribbon"], + ["๐Ÿ’ž", "revolving hearts"], + ["๐Ÿ’Ÿ", "heart decoration"], + ["๐Ÿ’ ", "diamond shape with a dot inside"], + ["๐Ÿ’ก", "idea light bulb ยท electric light bulb"], + ["๐Ÿ’ข", "anger symbol"], + ["๐Ÿ’ฃ", "bomb"], + ["๐Ÿ’ค", "sleeping symbol"], + ["๐Ÿ’ฅ", "collision symbol"], + ["๐Ÿ’ฆ", "splashing sweat symbol"], + ["๐Ÿ’ง", "droplet"], + ["๐Ÿ’จ", "dash symbol"], + ["๐Ÿ’ฉ", "pile of poo"], + ["๐Ÿ’ช", "muscle strong ยท flexed biceps"], + ["๐Ÿ’ซ", "dizzy symbol"], + ["๐Ÿ’ฌ", "speech balloon"], + ["๐Ÿ’ญ", "thought balloon"], + ["๐Ÿ’ฎ", "white flower"], + ["๐Ÿ’ฏ", "hundred perfect ยท hundred points symbol"], + ["๐Ÿ’ฐ", "money bag"], + ["๐Ÿ’ฑ", "currency exchange"], + ["๐Ÿ’ฒ", "heavy dollar sign"], + ["๐Ÿ’ณ", "credit card"], + ["๐Ÿ’ด", "banknote with yen sign"], + ["๐Ÿ’ต", "banknote with dollar sign"], + ["๐Ÿ’ถ", "banknote with euro sign"], + ["๐Ÿ’ท", "banknote with pound sign"], + ["๐Ÿ’ธ", "money with wings"], + ["๐Ÿ’น", "chart with upwards trend and yen sign"], + ["๐Ÿ’บ", "seat"], + ["๐Ÿ’ป", "personal computer"], + ["๐Ÿ’ผ", "briefcase"], + ["๐Ÿ’ฝ", "minidisc"], + ["๐Ÿ’พ", "floppy disk"], + ["๐Ÿ’ฟ", "optical disc"], + ["๐Ÿ“€", "dvd"], + ["๐Ÿ“", "file folder"], + ["๐Ÿ“‚", "open file folder"], + ["๐Ÿ“ƒ", "page with curl"], + ["๐Ÿ“„", "page facing up"], + ["๐Ÿ“…", "calendar"], + ["๐Ÿ“†", "tear-off calendar"], + ["๐Ÿ“‡", "card index"], + ["๐Ÿ“ˆ", "chart with upwards trend"], + ["๐Ÿ“‰", "chart with downwards trend"], + ["๐Ÿ“Š", "bar chart"], + ["๐Ÿ“‹", "clipboard"], + ["๐Ÿ“Œ", "pushpin"], + ["๐Ÿ“", "round pushpin"], + ["๐Ÿ“Ž", "paperclip"], + ["๐Ÿ“", "straight ruler"], + ["๐Ÿ“", "triangular ruler"], + ["๐Ÿ“‘", "bookmark tabs"], + ["๐Ÿ“’", "ledger"], + ["๐Ÿ““", "notebook"], + ["๐Ÿ“”", "notebook with decorative cover"], + ["๐Ÿ“•", "closed book"], + ["๐Ÿ“–", "open book"], + ["๐Ÿ“—", "green book"], + ["๐Ÿ“˜", "blue book"], + ["๐Ÿ“™", "orange book"], + ["๐Ÿ“š", "books"], + ["๐Ÿ“›", "name badge"], + ["๐Ÿ“œ", "scroll"], + ["๐Ÿ“", "memo"], + ["๐Ÿ“ž", "telephone receiver"], + ["๐Ÿ“Ÿ", "pager"], + ["๐Ÿ“ ", "fax machine"], + ["๐Ÿ“ก", "satellite antenna"], + ["๐Ÿ“ข", "public address loudspeaker"], + ["๐Ÿ“ฃ", "cheering megaphone"], + ["๐Ÿ“ค", "outbox tray"], + ["๐Ÿ“ฅ", "inbox tray"], + ["๐Ÿ“ฆ", "package box ยท package"], + ["๐Ÿ“ง", "e-mail symbol"], + ["๐Ÿ“จ", "incoming envelope"], + ["๐Ÿ“ฉ", "envelope with downwards arrow above"], + ["๐Ÿ“ช", "closed mailbox with lowered flag"], + ["๐Ÿ“ซ", "closed mailbox with raised flag"], + ["๐Ÿ“ฌ", "open mailbox with raised flag"], + ["๐Ÿ“ญ", "open mailbox with lowered flag"], + ["๐Ÿ“ฎ", "postbox"], + ["๐Ÿ“ฏ", "postal horn"], + ["๐Ÿ“ฐ", "newspaper"], + ["๐Ÿ“ฑ", "mobile phone"], + ["๐Ÿ“ฒ", "mobile phone with rightwards arrow at left"], + ["๐Ÿ“ณ", "vibration mode"], + ["๐Ÿ“ด", "mobile phone off"], + ["๐Ÿ“ต", "no mobile phones"], + ["๐Ÿ“ถ", "antenna with bars"], + ["๐Ÿ“ท", "camera"], + ["๐Ÿ“ธ", "camera with flash"], + ["๐Ÿ“น", "video camera"], + ["๐Ÿ“บ", "television"], + ["๐Ÿ“ป", "radio"], + ["๐Ÿ“ผ", "videocassette"], + ["๐Ÿ“ฝ", "film projector"], + ["๐Ÿ“พ", "portable stereo"], + ["๐Ÿ“ฟ", "prayer beads"], + ["๐Ÿ”€", "twisted rightwards arrows"], + ["๐Ÿ”", "clockwise rightwards and leftwards open circle arrows"], + ["๐Ÿ”‚", "clockwise rightwards and leftwards open circle arrows with circled one overlay"], + ["๐Ÿ”ƒ", "clockwise downwards and upwards open circle arrows"], + ["๐Ÿ”„", "anticlockwise downwards and upwards open circle arrows"], + ["๐Ÿ”…", "low brightness symbol"], + ["๐Ÿ”†", "high brightness symbol"], + ["๐Ÿ”‡", "speaker with cancellation stroke"], + ["๐Ÿ”ˆ", "speaker"], + ["๐Ÿ”‰", "speaker with one sound wave"], + ["๐Ÿ”Š", "speaker with three sound waves"], + ["๐Ÿ”‹", "battery"], + ["๐Ÿ”Œ", "electric plug"], + ["๐Ÿ”", "left-pointing magnifying glass"], + ["๐Ÿ”Ž", "right-pointing magnifying glass"], + ["๐Ÿ”", "lock with ink pen"], + ["๐Ÿ”", "closed lock with key"], + ["๐Ÿ”‘", "key"], + ["๐Ÿ”’", "lock secure ยท lock"], + ["๐Ÿ”“", "open lock"], + ["๐Ÿ””", "bell"], + ["๐Ÿ”•", "bell with cancellation stroke"], + ["๐Ÿ”–", "bookmark"], + ["๐Ÿ”—", "link symbol"], + ["๐Ÿ”˜", "radio button"], + ["๐Ÿ”™", "back with leftwards arrow above"], + ["๐Ÿ”š", "end with leftwards arrow above"], + ["๐Ÿ”›", "on with exclamation mark with left right arrow above"], + ["๐Ÿ”œ", "soon with rightwards arrow above"], + ["๐Ÿ”", "top with upwards arrow above"], + ["๐Ÿ”ž", "no one under eighteen symbol"], + ["๐Ÿ”Ÿ", "keycap ten"], + ["๐Ÿ” ", "input symbol for latin capital letters"], + ["๐Ÿ”ก", "input symbol for latin small letters"], + ["๐Ÿ”ข", "input symbol for numbers"], + ["๐Ÿ”ฃ", "input symbol for symbols"], + ["๐Ÿ”ค", "input symbol for latin letters"], + ["๐Ÿ”ฅ", "fire lit hot ยท fire"], + ["๐Ÿ”ฆ", "electric torch"], + ["๐Ÿ”ง", "wrench"], + ["๐Ÿ”จ", "hammer"], + ["๐Ÿ”ฉ", "nut and bolt"], + ["๐Ÿ”ช", "hocho"], + ["๐Ÿ”ซ", "pistol"], + ["๐Ÿ”ฌ", "microscope"], + ["๐Ÿ”ญ", "telescope"], + ["๐Ÿ”ฎ", "crystal ball"], + ["๐Ÿ”ฏ", "six pointed star with middle dot"], + ["๐Ÿ”ฐ", "japanese symbol for beginner"], + ["๐Ÿ”ฑ", "trident emblem"], + ["๐Ÿ”ฒ", "black square button"], + ["๐Ÿ”ณ", "white square button"], + ["๐Ÿ”ด", "large red circle"], + ["๐Ÿ”ต", "large blue circle"], + ["๐Ÿ”ถ", "large orange diamond"], + ["๐Ÿ”ท", "large blue diamond"], + ["๐Ÿ”ธ", "small orange diamond"], + ["๐Ÿ”น", "small blue diamond"], + ["๐Ÿ”บ", "up-pointing red triangle"], + ["๐Ÿ”ป", "down-pointing red triangle"], + ["๐Ÿ”ผ", "up-pointing small red triangle"], + ["๐Ÿ”ฝ", "down-pointing small red triangle"], + ["๐Ÿ”พ", "lower right shadowed white circle"], + ["๐Ÿ”ฟ", "upper right shadowed white circle"], + ["๐Ÿ•€", "circled cross pommee"], + ["๐Ÿ•", "cross pommee with half-circle below"], + ["๐Ÿ•‚", "cross pommee"], + ["๐Ÿ•ƒ", "notched left semicircle with three dots"], + ["๐Ÿ•„", "notched right semicircle with three dots"], + ["๐Ÿ•…", "symbol for marks chapter"], + ["๐Ÿ•†", "white latin cross"], + ["๐Ÿ•‡", "heavy latin cross"], + ["๐Ÿ•ˆ", "celtic cross"], + ["๐Ÿ•‰", "om symbol"], + ["๐Ÿ•Š", "dove of peace"], + ["๐Ÿ•‹", "kaaba"], + ["๐Ÿ•Œ", "mosque"], + ["๐Ÿ•", "synagogue"], + ["๐Ÿ•Ž", "menorah with nine branches"], + ["๐Ÿ•", "bowl of hygieia"], + ["๐Ÿ•", "clock face one oclock"], + ["๐Ÿ•‘", "clock face two oclock"], + ["๐Ÿ•’", "clock face three oclock"], + ["๐Ÿ•“", "clock face four oclock"], + ["๐Ÿ•”", "clock face five oclock"], + ["๐Ÿ••", "clock face six oclock"], + ["๐Ÿ•–", "clock face seven oclock"], + ["๐Ÿ•—", "clock face eight oclock"], + ["๐Ÿ•˜", "clock face nine oclock"], + ["๐Ÿ•™", "clock face ten oclock"], + ["๐Ÿ•š", "clock face eleven oclock"], + ["๐Ÿ•›", "clock face twelve oclock"], + ["๐Ÿ•œ", "clock face one-thirty"], + ["๐Ÿ•", "clock face two-thirty"], + ["๐Ÿ•ž", "clock face three-thirty"], + ["๐Ÿ•Ÿ", "clock face four-thirty"], + ["๐Ÿ• ", "clock face five-thirty"], + ["๐Ÿ•ก", "clock face six-thirty"], + ["๐Ÿ•ข", "clock face seven-thirty"], + ["๐Ÿ•ฃ", "clock face eight-thirty"], + ["๐Ÿ•ค", "clock face nine-thirty"], + ["๐Ÿ•ฅ", "clock face ten-thirty"], + ["๐Ÿ•ฆ", "clock face eleven-thirty"], + ["๐Ÿ•ง", "clock face twelve-thirty"], + ["๐Ÿ•จ", "right speaker"], + ["๐Ÿ•ฉ", "right speaker with one sound wave"], + ["๐Ÿ•ช", "right speaker with three sound waves"], + ["๐Ÿ•ซ", "bullhorn"], + ["๐Ÿ•ฌ", "bullhorn with sound waves"], + ["๐Ÿ•ญ", "ringing bell"], + ["๐Ÿ•ฎ", "book"], + ["๐Ÿ•ฏ", "candle"], + ["๐Ÿ•ฐ", "mantelpiece clock"], + ["๐Ÿ•ฑ", "black skull and crossbones"], + ["๐Ÿ•ฒ", "no piracy"], + ["๐Ÿ•ณ", "hole"], + ["๐Ÿ•ด", "man in business suit levitating"], + ["๐Ÿ•ต", "sleuth or spy"], + ["๐Ÿ•ถ", "dark sunglasses"], + ["๐Ÿ•ท", "spider"], + ["๐Ÿ•ธ", "spider web"], + ["๐Ÿ•น", "joystick"], + ["๐Ÿ•บ", "man dancing"], + ["๐Ÿ•ป", "left hand telephone receiver"], + ["๐Ÿ•ผ", "telephone receiver with page"], + ["๐Ÿ•ฝ", "right hand telephone receiver"], + ["๐Ÿ•พ", "white touchtone telephone"], + ["๐Ÿ•ฟ", "black touchtone telephone"], + ["๐Ÿ–€", "telephone on top of modem"], + ["๐Ÿ–", "clamshell mobile phone"], + ["๐Ÿ–‚", "back of envelope"], + ["๐Ÿ–ƒ", "stamped envelope"], + ["๐Ÿ–„", "envelope with lightning"], + ["๐Ÿ–…", "flying envelope"], + ["๐Ÿ–†", "pen over stamped envelope"], + ["๐Ÿ–‡", "linked paperclips"], + ["๐Ÿ–ˆ", "black pushpin"], + ["๐Ÿ–‰", "lower left pencil"], + ["๐Ÿ–Š", "lower left ballpoint pen"], + ["๐Ÿ–‹", "lower left fountain pen"], + ["๐Ÿ–Œ", "lower left paintbrush"], + ["๐Ÿ–", "lower left crayon"], + ["๐Ÿ–Ž", "left writing hand"], + ["๐Ÿ–", "turned ok hand sign"], + ["๐Ÿ–", "raised hand with fingers splayed"], + ["๐Ÿ–‘", "reversed raised hand with fingers splayed"], + ["๐Ÿ–’", "reversed thumbs up sign"], + ["๐Ÿ–“", "reversed thumbs down sign"], + ["๐Ÿ–”", "reversed victory hand"], + ["๐Ÿ–•", "reversed hand with middle finger extended"], + ["๐Ÿ––", "raised hand with part between middle and ring fingers"], + ["๐Ÿ–—", "white down pointing left hand index"], + ["๐Ÿ–˜", "sideways white left pointing index"], + ["๐Ÿ–™", "sideways white right pointing index"], + ["๐Ÿ–š", "sideways black left pointing index"], + ["๐Ÿ–›", "sideways black right pointing index"], + ["๐Ÿ–œ", "black left pointing backhand index"], + ["๐Ÿ–", "black right pointing backhand index"], + ["๐Ÿ–ž", "sideways white up pointing index"], + ["๐Ÿ–Ÿ", "sideways white down pointing index"], + ["๐Ÿ– ", "sideways black up pointing index"], + ["๐Ÿ–ก", "sideways black down pointing index"], + ["๐Ÿ–ข", "black up pointing backhand index"], + ["๐Ÿ–ฃ", "black down pointing backhand index"], + ["๐Ÿ–ค", "black heart"], + ["๐Ÿ–ฅ", "desktop computer"], + ["๐Ÿ–ฆ", "keyboard and mouse"], + ["๐Ÿ–ง", "three networked computers"], + ["๐Ÿ–จ", "printer"], + ["๐Ÿ–ฉ", "pocket calculator"], + ["๐Ÿ–ช", "black hard shell floppy disk"], + ["๐Ÿ–ซ", "white hard shell floppy disk"], + ["๐Ÿ–ฌ", "soft shell floppy disk"], + ["๐Ÿ–ญ", "tape cartridge"], + ["๐Ÿ–ฎ", "wired keyboard"], + ["๐Ÿ–ฏ", "one button mouse"], + ["๐Ÿ–ฐ", "two button mouse"], + ["๐Ÿ–ฑ", "three button mouse"], + ["๐Ÿ–ฒ", "trackball"], + ["๐Ÿ–ณ", "old personal computer"], + ["๐Ÿ–ด", "hard disk"], + ["๐Ÿ–ต", "screen"], + ["๐Ÿ–ถ", "printer icon"], + ["๐Ÿ–ท", "fax icon"], + ["๐Ÿ–ธ", "optical disc icon"], + ["๐Ÿ–น", "document with text"], + ["๐Ÿ–บ", "document with text and picture"], + ["๐Ÿ–ป", "document with picture"], + ["๐Ÿ–ผ", "frame with picture"], + ["๐Ÿ–ฝ", "frame with tiles"], + ["๐Ÿ–พ", "frame with an x"], + ["๐Ÿ–ฟ", "black folder"], + ["๐Ÿ—€", "folder"], + ["๐Ÿ—", "open folder"], + ["๐Ÿ—‚", "card index dividers"], + ["๐Ÿ—ƒ", "card file box"], + ["๐Ÿ—„", "file cabinet"], + ["๐Ÿ—…", "empty note"], + ["๐Ÿ—†", "empty note page"], + ["๐Ÿ—‡", "empty note pad"], + ["๐Ÿ—ˆ", "note"], + ["๐Ÿ—‰", "note page"], + ["๐Ÿ—Š", "note pad"], + ["๐Ÿ—‹", "empty document"], + ["๐Ÿ—Œ", "empty page"], + ["๐Ÿ—", "empty pages"], + ["๐Ÿ—Ž", "document"], + ["๐Ÿ—", "page"], + ["๐Ÿ—", "pages"], + ["๐Ÿ—‘", "wastebasket"], + ["๐Ÿ—’", "spiral note pad"], + ["๐Ÿ—“", "spiral calendar pad"], + ["๐Ÿ—”", "desktop window"], + ["๐Ÿ—•", "minimize"], + ["๐Ÿ—–", "maximize"], + ["๐Ÿ——", "overlap"], + ["๐Ÿ—˜", "clockwise right and left semicircle arrows"], + ["๐Ÿ—™", "cancellation x"], + ["๐Ÿ—š", "increase font size symbol"], + ["๐Ÿ—›", "decrease font size symbol"], + ["๐Ÿ—œ", "compression"], + ["๐Ÿ—", "old key"], + ["๐Ÿ—ž", "rolled-up newspaper"], + ["๐Ÿ—Ÿ", "page with circled text"], + ["๐Ÿ— ", "stock chart"], + ["๐Ÿ—ก", "dagger knife"], + ["๐Ÿ—ข", "lips"], + ["๐Ÿ—ฃ", "speaking head in silhouette"], + ["๐Ÿ—ค", "three rays above"], + ["๐Ÿ—ฅ", "three rays below"], + ["๐Ÿ—ฆ", "three rays left"], + ["๐Ÿ—ง", "three rays right"], + ["๐Ÿ—จ", "left speech bubble"], + ["๐Ÿ—ฉ", "right speech bubble"], + ["๐Ÿ—ช", "two speech bubbles"], + ["๐Ÿ—ซ", "three speech bubbles"], + ["๐Ÿ—ฌ", "left thought bubble"], + ["๐Ÿ—ญ", "right thought bubble"], + ["๐Ÿ—ฎ", "left anger bubble"], + ["๐Ÿ—ฏ", "right anger bubble"], + ["๐Ÿ—ฐ", "mood bubble"], + ["๐Ÿ—ฑ", "lightning mood bubble"], + ["๐Ÿ—ฒ", "lightning mood"], + ["๐Ÿ—ณ", "ballot box with ballot"], + ["๐Ÿ—ด", "ballot script x"], + ["๐Ÿ—ต", "ballot box with script x"], + ["๐Ÿ—ถ", "ballot bold script x"], + ["๐Ÿ—ท", "ballot box with bold script x"], + ["๐Ÿ—ธ", "light check mark"], + ["๐Ÿ—น", "ballot box with bold check"], + ["๐Ÿ—บ", "world map"], + ["๐Ÿ—ป", "mount fuji"], + ["๐Ÿ—ผ", "tokyo tower"], + ["๐Ÿ—ฝ", "statue of liberty"], + ["๐Ÿ—พ", "silhouette of japan"], + ["๐Ÿ—ฟ", "moyai"], + ["๐Ÿ˜€", "grinning face"], + ["๐Ÿ˜", "grinning face with smiling eyes"], + ["๐Ÿ˜‚", "joy laughing tears lol ยท face with tears of joy"], + ["๐Ÿ˜ƒ", "smiling face with open mouth"], + ["๐Ÿ˜„", "smiling face with open mouth and smiling eyes"], + ["๐Ÿ˜…", "sweat smile nervous ยท smiling face with open mouth and cold sweat"], + ["๐Ÿ˜†", "smiling face with open mouth and tightly-closed eyes"], + ["๐Ÿ˜‡", "angel innocent ยท smiling face with halo"], + ["๐Ÿ˜ˆ", "smiling face with horns"], + ["๐Ÿ˜‰", "winking face"], + ["๐Ÿ˜Š", "smile blush happy ยท smiling face with smiling eyes"], + ["๐Ÿ˜‹", "face savouring delicious food"], + ["๐Ÿ˜Œ", "relieved face"], + ["๐Ÿ˜", "heart eyes love ยท smiling face with heart-shaped eyes"], + ["๐Ÿ˜Ž", "cool sunglasses ยท smiling face with sunglasses"], + ["๐Ÿ˜", "smirking face"], + ["๐Ÿ˜", "neutral face"], + ["๐Ÿ˜‘", "expressionless face"], + ["๐Ÿ˜’", "unamused face"], + ["๐Ÿ˜“", "face with cold sweat"], + ["๐Ÿ˜”", "pensive face"], + ["๐Ÿ˜•", "confused face"], + ["๐Ÿ˜–", "confounded face"], + ["๐Ÿ˜—", "kissing face"], + ["๐Ÿ˜˜", "face throwing a kiss"], + ["๐Ÿ˜™", "kissing face with smiling eyes"], + ["๐Ÿ˜š", "kissing face with closed eyes"], + ["๐Ÿ˜›", "face with stuck-out tongue"], + ["๐Ÿ˜œ", "face with stuck-out tongue and winking eye"], + ["๐Ÿ˜", "face with stuck-out tongue and tightly-closed eyes"], + ["๐Ÿ˜ž", "disappointed face"], + ["๐Ÿ˜Ÿ", "worried face"], + ["๐Ÿ˜ ", "angry face"], + ["๐Ÿ˜ก", "angry mad ยท pouting face"], + ["๐Ÿ˜ข", "sad cry tear ยท crying face"], + ["๐Ÿ˜ฃ", "persevering face"], + ["๐Ÿ˜ค", "face with look of triumph"], + ["๐Ÿ˜ฅ", "disappointed but relieved face"], + ["๐Ÿ˜ฆ", "frowning face with open mouth"], + ["๐Ÿ˜ง", "anguished face"], + ["๐Ÿ˜จ", "fearful face"], + ["๐Ÿ˜ฉ", "weary face"], + ["๐Ÿ˜ช", "sleepy face"], + ["๐Ÿ˜ซ", "tired face"], + ["๐Ÿ˜ฌ", "grimace awkward ยท grimacing face"], + ["๐Ÿ˜ญ", "cry sob sad ยท loudly crying face"], + ["๐Ÿ˜ฎ", "face with open mouth"], + ["๐Ÿ˜ฏ", "hushed face"], + ["๐Ÿ˜ฐ", "face with open mouth and cold sweat"], + ["๐Ÿ˜ฑ", "face screaming in fear"], + ["๐Ÿ˜ฒ", "astonished face"], + ["๐Ÿ˜ณ", "flushed face"], + ["๐Ÿ˜ด", "sleep tired ยท sleeping face"], + ["๐Ÿ˜ต", "dizzy face"], + ["๐Ÿ˜ถ", "face without mouth"], + ["๐Ÿ˜ท", "face with medical mask"], + ["๐Ÿ˜ธ", "grinning cat face with smiling eyes"], + ["๐Ÿ˜น", "cat face with tears of joy"], + ["๐Ÿ˜บ", "smiling cat face with open mouth"], + ["๐Ÿ˜ป", "smiling cat face with heart-shaped eyes"], + ["๐Ÿ˜ผ", "cat face with wry smile"], + ["๐Ÿ˜ฝ", "kissing cat face with closed eyes"], + ["๐Ÿ˜พ", "pouting cat face"], + ["๐Ÿ˜ฟ", "crying cat face"], + ["๐Ÿ™€", "weary cat face"], + ["๐Ÿ™", "slightly frowning face"], + ["๐Ÿ™‚", "slight smile ยท slightly smiling face"], + ["๐Ÿ™ƒ", "upside down ยท upside-down face"], + ["๐Ÿ™„", "face with rolling eyes"], + ["๐Ÿ™…", "face with no good gesture"], + ["๐Ÿ™†", "face with ok gesture"], + ["๐Ÿ™‡", "person bowing deeply"], + ["๐Ÿ™ˆ", "see-no-evil monkey"], + ["๐Ÿ™‰", "hear-no-evil monkey"], + ["๐Ÿ™Š", "speak-no-evil monkey"], + ["๐Ÿ™‹", "happy person raising one hand"], + ["๐Ÿ™Œ", "person raising both hands in celebration"], + ["๐Ÿ™", "person frowning"], + ["๐Ÿ™Ž", "person with pouting face"], + ["๐Ÿ™", "pray please thanks thank you ยท person with folded hands"], + ["๐Ÿš€", "rocket launch ship ยท rocket"], + ["๐Ÿš", "helicopter"], + ["๐Ÿš‚", "steam locomotive"], + ["๐Ÿšƒ", "railway car"], + ["๐Ÿš„", "high-speed train"], + ["๐Ÿš…", "high-speed train with bullet nose"], + ["๐Ÿš†", "train"], + ["๐Ÿš‡", "metro"], + ["๐Ÿšˆ", "light rail"], + ["๐Ÿš‰", "station"], + ["๐ŸšŠ", "tram"], + ["๐Ÿš‹", "tram car"], + ["๐ŸšŒ", "bus"], + ["๐Ÿš", "oncoming bus"], + ["๐ŸšŽ", "trolleybus"], + ["๐Ÿš", "bus stop"], + ["๐Ÿš", "minibus"], + ["๐Ÿš‘", "ambulance"], + ["๐Ÿš’", "fire engine"], + ["๐Ÿš“", "police car"], + ["๐Ÿš”", "oncoming police car"], + ["๐Ÿš•", "taxi"], + ["๐Ÿš–", "oncoming taxi"], + ["๐Ÿš—", "automobile"], + ["๐Ÿš˜", "oncoming automobile"], + ["๐Ÿš™", "recreational vehicle"], + ["๐Ÿšš", "delivery truck"], + ["๐Ÿš›", "articulated lorry"], + ["๐Ÿšœ", "tractor"], + ["๐Ÿš", "monorail"], + ["๐Ÿšž", "mountain railway"], + ["๐ŸšŸ", "suspension railway"], + ["๐Ÿš ", "mountain cableway"], + ["๐Ÿšก", "aerial tramway"], + ["๐Ÿšข", "ship"], + ["๐Ÿšฃ", "rowboat"], + ["๐Ÿšค", "speedboat"], + ["๐Ÿšฅ", "horizontal traffic light"], + ["๐Ÿšฆ", "vertical traffic light"], + ["๐Ÿšง", "construction sign"], + ["๐Ÿšจ", "police cars revolving light"], + ["๐Ÿšฉ", "triangular flag on post"], + ["๐Ÿšช", "door"], + ["๐Ÿšซ", "no entry sign"], + ["๐Ÿšฌ", "smoking symbol"], + ["๐Ÿšญ", "no smoking symbol"], + ["๐Ÿšฎ", "put litter in its place symbol"], + ["๐Ÿšฏ", "do not litter symbol"], + ["๐Ÿšฐ", "potable water symbol"], + ["๐Ÿšฑ", "non-potable water symbol"], + ["๐Ÿšฒ", "bicycle"], + ["๐Ÿšณ", "no bicycles"], + ["๐Ÿšด", "bicyclist"], + ["๐Ÿšต", "mountain bicyclist"], + ["๐Ÿšถ", "pedestrian"], + ["๐Ÿšท", "no pedestrians"], + ["๐Ÿšธ", "children crossing"], + ["๐Ÿšน", "mens symbol"], + ["๐Ÿšบ", "womens symbol"], + ["๐Ÿšป", "restroom"], + ["๐Ÿšผ", "baby symbol"], + ["๐Ÿšฝ", "toilet"], + ["๐Ÿšพ", "water closet"], + ["๐Ÿšฟ", "shower"], + ["๐Ÿ›€", "bath"], + ["๐Ÿ›", "bathtub"], + ["๐Ÿ›‚", "passport control"], + ["๐Ÿ›ƒ", "customs"], + ["๐Ÿ›„", "baggage claim"], + ["๐Ÿ›…", "left luggage"], + ["๐Ÿ›†", "triangle with rounded corners"], + ["๐Ÿ›‡", "prohibited sign"], + ["๐Ÿ›ˆ", "circled information source"], + ["๐Ÿ›‰", "boys symbol"], + ["๐Ÿ›Š", "girls symbol"], + ["๐Ÿ›‹", "couch and lamp"], + ["๐Ÿ›Œ", "sleeping accommodation"], + ["๐Ÿ›", "shopping bags"], + ["๐Ÿ›Ž", "bellhop bell"], + ["๐Ÿ›", "bed"], + ["๐Ÿ›", "place of worship"], + ["๐Ÿ›‘", "octagonal sign"], + ["๐Ÿ›’", "shopping trolley"], + ["๐Ÿ›“", "stupa"], + ["๐Ÿ›”", "pagoda"], + ["๐Ÿ›•", "hindu temple"], + ["๐Ÿ›–", "hut"], + ["๐Ÿ›—", "elevator"], + ["๐Ÿ›œ", "wireless"], + ["๐Ÿ›", "playground slide"], + ["๐Ÿ›ž", "wheel"], + ["๐Ÿ›Ÿ", "ring buoy"], + ["๐Ÿ› ", "hammer and wrench"], + ["๐Ÿ›ก", "shield"], + ["๐Ÿ›ข", "oil drum"], + ["๐Ÿ›ฃ", "motorway"], + ["๐Ÿ›ค", "railway track"], + ["๐Ÿ›ฅ", "motor boat"], + ["๐Ÿ›ฆ", "up-pointing military airplane"], + ["๐Ÿ›ง", "up-pointing airplane"], + ["๐Ÿ›จ", "up-pointing small airplane"], + ["๐Ÿ›ฉ", "small airplane"], + ["๐Ÿ›ช", "northeast-pointing airplane"], + ["๐Ÿ›ซ", "airplane departure"], + ["๐Ÿ›ฌ", "airplane arriving"], + ["๐Ÿ›ฐ", "satellite"], + ["๐Ÿ›ฑ", "oncoming fire engine"], + ["๐Ÿ›ฒ", "diesel locomotive"], + ["๐Ÿ›ณ", "passenger ship"], + ["๐Ÿ›ด", "scooter"], + ["๐Ÿ›ต", "motor scooter"], + ["๐Ÿ›ถ", "canoe"], + ["๐Ÿ›ท", "sled"], + ["๐Ÿ›ธ", "flying saucer"], + ["๐Ÿ›น", "skateboard"], + ["๐Ÿ›บ", "auto rickshaw"], + ["๐Ÿ›ป", "pickup truck"], + ["๐Ÿ›ผ", "roller skate"], + ["๐Ÿค€", "circled cross formee with four dots"], + ["๐Ÿค", "circled cross formee with two dots"], + ["๐Ÿค‚", "circled cross formee"], + ["๐Ÿคƒ", "left half circle with four dots"], + ["๐Ÿค„", "left half circle with three dots"], + ["๐Ÿค…", "left half circle with two dots"], + ["๐Ÿค†", "left half circle with dot"], + ["๐Ÿค‡", "left half circle"], + ["๐Ÿคˆ", "downward facing hook"], + ["๐Ÿค‰", "downward facing notched hook"], + ["๐ŸคŠ", "downward facing hook with dot"], + ["๐Ÿค‹", "downward facing notched hook with dot"], + ["๐ŸคŒ", "pinched fingers"], + ["๐Ÿค", "white heart"], + ["๐ŸคŽ", "brown heart"], + ["๐Ÿค", "pinching hand"], + ["๐Ÿค", "zipper-mouth face"], + ["๐Ÿค‘", "money-mouth face"], + ["๐Ÿค’", "face with thermometer"], + ["๐Ÿค“", "nerd face"], + ["๐Ÿค”", "thinking hmm ยท thinking face"], + ["๐Ÿค•", "face with head-bandage"], + ["๐Ÿค–", "robot face"], + ["๐Ÿค—", "hugging face"], + ["๐Ÿค˜", "sign of the horns"], + ["๐Ÿค™", "call me hand"], + ["๐Ÿคš", "raised back of hand"], + ["๐Ÿค›", "left-facing fist"], + ["๐Ÿคœ", "right-facing fist"], + ["๐Ÿค", "handshake deal ยท handshake"], + ["๐Ÿคž", "hand with index and middle fingers crossed"], + ["๐ŸคŸ", "i love you hand sign"], + ["๐Ÿค ", "face with cowboy hat"], + ["๐Ÿคก", "clown face"], + ["๐Ÿคข", "nauseated face"], + ["๐Ÿคฃ", "rolling on the floor laughing"], + ["๐Ÿคค", "drooling face"], + ["๐Ÿคฅ", "lying face"], + ["๐Ÿคฆ", "facepalm ยท face palm"], + ["๐Ÿคง", "sneezing face"], + ["๐Ÿคจ", "face with one eyebrow raised"], + ["๐Ÿคฉ", "grinning face with star eyes"], + ["๐Ÿคช", "grinning face with one large and one small eye"], + ["๐Ÿคซ", "face with finger covering closed lips"], + ["๐Ÿคฌ", "serious face with symbols covering mouth"], + ["๐Ÿคญ", "smiling face with smiling eyes and hand covering mouth"], + ["๐Ÿคฎ", "face with open mouth vomiting"], + ["๐Ÿคฏ", "mind blown ยท shocked face with exploding head"], + ["๐Ÿคฐ", "pregnant woman"], + ["๐Ÿคฑ", "breast-feeding"], + ["๐Ÿคฒ", "palms up together"], + ["๐Ÿคณ", "selfie"], + ["๐Ÿคด", "prince"], + ["๐Ÿคต", "man in tuxedo"], + ["๐Ÿคถ", "mother christmas"], + ["๐Ÿคท", "shrug dunno ยท shrug"], + ["๐Ÿคธ", "person doing cartwheel"], + ["๐Ÿคน", "juggling"], + ["๐Ÿคบ", "fencer"], + ["๐Ÿคป", "modern pentathlon"], + ["๐Ÿคผ", "wrestlers"], + ["๐Ÿคฝ", "water polo"], + ["๐Ÿคพ", "handball"], + ["๐Ÿคฟ", "diving mask"], + ["๐Ÿฅ€", "wilted flower"], + ["๐Ÿฅ", "drum with drumsticks"], + ["๐Ÿฅ‚", "clinking glasses"], + ["๐Ÿฅƒ", "tumbler glass"], + ["๐Ÿฅ„", "spoon"], + ["๐Ÿฅ…", "goal net"], + ["๐Ÿฅ†", "rifle"], + ["๐Ÿฅ‡", "first place medal"], + ["๐Ÿฅˆ", "second place medal"], + ["๐Ÿฅ‰", "third place medal"], + ["๐ŸฅŠ", "boxing glove"], + ["๐Ÿฅ‹", "martial arts uniform"], + ["๐ŸฅŒ", "curling stone"], + ["๐Ÿฅ", "lacrosse stick and ball"], + ["๐ŸฅŽ", "softball"], + ["๐Ÿฅ", "flying disc"], + ["๐Ÿฅ", "croissant"], + ["๐Ÿฅ‘", "avocado"], + ["๐Ÿฅ’", "cucumber"], + ["๐Ÿฅ“", "bacon"], + ["๐Ÿฅ”", "potato"], + ["๐Ÿฅ•", "carrot"], + ["๐Ÿฅ–", "baguette bread"], + ["๐Ÿฅ—", "green salad"], + ["๐Ÿฅ˜", "shallow pan of food"], + ["๐Ÿฅ™", "stuffed flatbread"], + ["๐Ÿฅš", "egg"], + ["๐Ÿฅ›", "glass of milk"], + ["๐Ÿฅœ", "peanuts"], + ["๐Ÿฅ", "kiwifruit"], + ["๐Ÿฅž", "pancakes"], + ["๐ŸฅŸ", "dumpling"], + ["๐Ÿฅ ", "fortune cookie"], + ["๐Ÿฅก", "takeout box"], + ["๐Ÿฅข", "chopsticks"], + ["๐Ÿฅฃ", "bowl with spoon"], + ["๐Ÿฅค", "cup with straw"], + ["๐Ÿฅฅ", "coconut"], + ["๐Ÿฅฆ", "broccoli"], + ["๐Ÿฅง", "pie"], + ["๐Ÿฅจ", "pretzel"], + ["๐Ÿฅฉ", "cut of meat"], + ["๐Ÿฅช", "sandwich"], + ["๐Ÿฅซ", "canned food"], + ["๐Ÿฅฌ", "leafy green"], + ["๐Ÿฅญ", "mango"], + ["๐Ÿฅฎ", "moon cake"], + ["๐Ÿฅฏ", "bagel"], + ["๐Ÿฅฐ", "love hearts ยท smiling face with smiling eyes and three hearts"], + ["๐Ÿฅฑ", "yawning face"], + ["๐Ÿฅฒ", "smiling face with tear"], + ["๐Ÿฅณ", "party celebrate ยท face with party horn and party hat"], + ["๐Ÿฅด", "face with uneven eyes and wavy mouth"], + ["๐Ÿฅต", "overheated face"], + ["๐Ÿฅถ", "freezing face"], + ["๐Ÿฅท", "ninja"], + ["๐Ÿฅธ", "disguised face"], + ["๐Ÿฅน", "face holding back tears"], + ["๐Ÿฅบ", "face with pleading eyes"], + ["๐Ÿฅป", "sari"], + ["๐Ÿฅผ", "lab coat"], + ["๐Ÿฅฝ", "goggles"], + ["๐Ÿฅพ", "hiking boot"], + ["๐Ÿฅฟ", "flat shoe"], + ["๐Ÿฆ€", "crab"], + ["๐Ÿฆ", "lion face"], + ["๐Ÿฆ‚", "scorpion"], + ["๐Ÿฆƒ", "turkey"], + ["๐Ÿฆ„", "unicorn face"], + ["๐Ÿฆ…", "eagle"], + ["๐Ÿฆ†", "duck"], + ["๐Ÿฆ‡", "bat"], + ["๐Ÿฆˆ", "shark"], + ["๐Ÿฆ‰", "owl"], + ["๐ŸฆŠ", "fox face"], + ["๐Ÿฆ‹", "butterfly"], + ["๐ŸฆŒ", "deer"], + ["๐Ÿฆ", "gorilla"], + ["๐ŸฆŽ", "lizard"], + ["๐Ÿฆ", "rhinoceros"], + ["๐Ÿฆ", "shrimp"], + ["๐Ÿฆ‘", "squid"], + ["๐Ÿฆ’", "giraffe face"], + ["๐Ÿฆ“", "zebra face"], + ["๐Ÿฆ”", "hedgehog"], + ["๐Ÿฆ•", "sauropod"], + ["๐Ÿฆ–", "t-rex"], + ["๐Ÿฆ—", "cricket"], + ["๐Ÿฆ˜", "kangaroo"], + ["๐Ÿฆ™", "llama"], + ["๐Ÿฆš", "peacock"], + ["๐Ÿฆ›", "hippopotamus"], + ["๐Ÿฆœ", "parrot"], + ["๐Ÿฆ", "raccoon"], + ["๐Ÿฆž", "lobster"], + ["๐ŸฆŸ", "mosquito"], + ["๐Ÿฆ ", "microbe"], + ["๐Ÿฆก", "badger"], + ["๐Ÿฆข", "swan"], + ["๐Ÿฆฃ", "mammoth"], + ["๐Ÿฆค", "dodo"], + ["๐Ÿฆฅ", "sloth"], + ["๐Ÿฆฆ", "otter"], + ["๐Ÿฆง", "orangutan"], + ["๐Ÿฆจ", "skunk"], + ["๐Ÿฆฉ", "flamingo"], + ["๐Ÿฆช", "oyster"], + ["๐Ÿฆซ", "beaver"], + ["๐Ÿฆฌ", "bison"], + ["๐Ÿฆญ", "seal"], + ["๐Ÿฆฎ", "guide dog"], + ["๐Ÿฆฏ", "probing cane"], + ["๐Ÿฆฐ", "emoji component red hair"], + ["๐Ÿฆฑ", "emoji component curly hair"], + ["๐Ÿฆฒ", "emoji component bald"], + ["๐Ÿฆณ", "emoji component white hair"], + ["๐Ÿฆด", "bone"], + ["๐Ÿฆต", "leg"], + ["๐Ÿฆถ", "foot"], + ["๐Ÿฆท", "tooth"], + ["๐Ÿฆธ", "superhero"], + ["๐Ÿฆน", "supervillain"], + ["๐Ÿฆบ", "safety vest"], + ["๐Ÿฆป", "ear with hearing aid"], + ["๐Ÿฆผ", "motorized wheelchair"], + ["๐Ÿฆฝ", "manual wheelchair"], + ["๐Ÿฆพ", "mechanical arm"], + ["๐Ÿฆฟ", "mechanical leg"], + ["๐Ÿง€", "cheese wedge"], + ["๐Ÿง", "cupcake"], + ["๐Ÿง‚", "salt shaker"], + ["๐Ÿงƒ", "beverage box"], + ["๐Ÿง„", "garlic"], + ["๐Ÿง…", "onion"], + ["๐Ÿง†", "falafel"], + ["๐Ÿง‡", "waffle"], + ["๐Ÿงˆ", "butter"], + ["๐Ÿง‰", "mate drink"], + ["๐ŸงŠ", "ice cube"], + ["๐Ÿง‹", "bubble tea"], + ["๐ŸงŒ", "troll"], + ["๐Ÿง", "standing person"], + ["๐ŸงŽ", "kneeling person"], + ["๐Ÿง", "deaf person"], + ["๐Ÿง", "face with monocle"], + ["๐Ÿง‘", "adult"], + ["๐Ÿง’", "child"], + ["๐Ÿง“", "older adult"], + ["๐Ÿง”", "bearded person"], + ["๐Ÿง•", "person with headscarf"], + ["๐Ÿง–", "person in steamy room"], + ["๐Ÿง—", "person climbing"], + ["๐Ÿง˜", "person in lotus position"], + ["๐Ÿง™", "mage"], + ["๐Ÿงš", "fairy"], + ["๐Ÿง›", "vampire"], + ["๐Ÿงœ", "merperson"], + ["๐Ÿง", "elf"], + ["๐Ÿงž", "genie"], + ["๐ŸงŸ", "zombie"], + ["๐Ÿง ", "brain"], + ["๐Ÿงก", "orange heart"], + ["๐Ÿงข", "billed cap"], + ["๐Ÿงฃ", "scarf"], + ["๐Ÿงค", "gloves"], + ["๐Ÿงฅ", "coat"], + ["๐Ÿงฆ", "socks"], + ["๐Ÿงง", "red gift envelope"], + ["๐Ÿงจ", "firecracker"], + ["๐Ÿงฉ", "jigsaw puzzle piece"], + ["๐Ÿงช", "test tube"], + ["๐Ÿงซ", "petri dish"], + ["๐Ÿงฌ", "dna double helix"], + ["๐Ÿงญ", "compass"], + ["๐Ÿงฎ", "abacus"], + ["๐Ÿงฏ", "fire extinguisher"], + ["๐Ÿงฐ", "toolbox"], + ["๐Ÿงฑ", "brick"], + ["๐Ÿงฒ", "magnet"], + ["๐Ÿงณ", "luggage"], + ["๐Ÿงด", "lotion bottle"], + ["๐Ÿงต", "spool of thread"], + ["๐Ÿงถ", "ball of yarn"], + ["๐Ÿงท", "safety pin"], + ["๐Ÿงธ", "teddy bear"], + ["๐Ÿงน", "broom"], + ["๐Ÿงบ", "basket"], + ["๐Ÿงป", "roll of paper"], + ["๐Ÿงผ", "bar of soap"], + ["๐Ÿงฝ", "sponge"], + ["๐Ÿงพ", "receipt"], + ["๐Ÿงฟ", "nazar amulet"], + ["๐Ÿฉฐ", "ballet shoes"], + ["๐Ÿฉฑ", "one-piece swimsuit"], + ["๐Ÿฉฒ", "briefs"], + ["๐Ÿฉณ", "shorts"], + ["๐Ÿฉด", "thong sandal"], + ["๐Ÿฉต", "light blue heart"], + ["๐Ÿฉถ", "grey heart"], + ["๐Ÿฉท", "pink heart"], + ["๐Ÿฉธ", "drop of blood"], + ["๐Ÿฉน", "adhesive bandage"], + ["๐Ÿฉบ", "stethoscope"], + ["๐Ÿฉป", "x-ray"], + ["๐Ÿฉผ", "crutch"], + ["๐Ÿช€", "yo-yo"], + ["๐Ÿช", "kite"], + ["๐Ÿช‚", "parachute"], + ["๐Ÿชƒ", "boomerang"], + ["๐Ÿช„", "magic wand"], + ["๐Ÿช…", "pinata"], + ["๐Ÿช†", "nesting dolls"], + ["๐Ÿช‡", "maracas"], + ["๐Ÿชˆ", "flute"], + ["๐Ÿช", "ringed planet"], + ["๐Ÿช‘", "chair"], + ["๐Ÿช’", "razor"], + ["๐Ÿช“", "axe"], + ["๐Ÿช”", "diya lamp"], + ["๐Ÿช•", "banjo"], + ["๐Ÿช–", "military helmet"], + ["๐Ÿช—", "accordion"], + ["๐Ÿช˜", "long drum"], + ["๐Ÿช™", "coin"], + ["๐Ÿชš", "carpentry saw"], + ["๐Ÿช›", "screwdriver"], + ["๐Ÿชœ", "ladder"], + ["๐Ÿช", "hook"], + ["๐Ÿชž", "mirror"], + ["๐ŸชŸ", "window"], + ["๐Ÿช ", "plunger"], + ["๐Ÿชก", "sewing needle"], + ["๐Ÿชข", "knot"], + ["๐Ÿชฃ", "bucket"], + ["๐Ÿชค", "mouse trap"], + ["๐Ÿชฅ", "toothbrush"], + ["๐Ÿชฆ", "headstone"], + ["๐Ÿชง", "placard"], + ["๐Ÿชจ", "rock"], + ["๐Ÿชฉ", "mirror ball"], + ["๐Ÿชช", "identification card"], + ["๐Ÿชซ", "low battery"], + ["๐Ÿชฌ", "hamsa"], + ["๐Ÿชญ", "folding hand fan"], + ["๐Ÿชฎ", "hair pick"], + ["๐Ÿชฏ", "khanda"], + ["๐Ÿชฐ", "fly"], + ["๐Ÿชฑ", "worm"], + ["๐Ÿชฒ", "beetle"], + ["๐Ÿชณ", "cockroach"], + ["๐Ÿชด", "potted plant"], + ["๐Ÿชต", "wood"], + ["๐Ÿชถ", "feather"], + ["๐Ÿชท", "lotus"], + ["๐Ÿชธ", "coral"], + ["๐Ÿชน", "empty nest"], + ["๐Ÿชบ", "nest with eggs"], + ["๐Ÿชป", "hyacinth"], + ["๐Ÿชผ", "jellyfish"], + ["๐Ÿชฝ", "wing"], + ["๐Ÿชฟ", "goose"], + ["๐Ÿซ€", "anatomical heart"], + ["๐Ÿซ", "lungs"], + ["๐Ÿซ‚", "people hugging"], + ["๐Ÿซƒ", "pregnant man"], + ["๐Ÿซ„", "pregnant person"], + ["๐Ÿซ…", "person with crown"], + ["๐ŸซŽ", "moose"], + ["๐Ÿซ", "donkey"], + ["๐Ÿซ", "blueberries"], + ["๐Ÿซ‘", "bell pepper"], + ["๐Ÿซ’", "olive"], + ["๐Ÿซ“", "flatbread"], + ["๐Ÿซ”", "tamale"], + ["๐Ÿซ•", "fondue"], + ["๐Ÿซ–", "teapot"], + ["๐Ÿซ—", "pouring liquid"], + ["๐Ÿซ˜", "beans"], + ["๐Ÿซ™", "jar"], + ["๐Ÿซš", "ginger root"], + ["๐Ÿซ›", "pea pod"], + ["๐Ÿซ ", "melting face"], + ["๐Ÿซก", "saluting face"], + ["๐Ÿซข", "face with open eyes and hand over mouth"], + ["๐Ÿซฃ", "face with peeking eye"], + ["๐Ÿซค", "face with diagonal mouth"], + ["๐Ÿซฅ", "dotted line face"], + ["๐Ÿซฆ", "biting lip"], + ["๐Ÿซง", "bubbles"], + ["๐Ÿซจ", "shaking face"], + ["๐Ÿซฐ", "hand with index finger and thumb crossed"], + ["๐Ÿซฑ", "rightwards hand"], + ["๐Ÿซฒ", "leftwards hand"], + ["๐Ÿซณ", "palm down hand"], + ["๐Ÿซด", "palm up hand"], + ["๐Ÿซต", "index pointing at the viewer"], + ["๐Ÿซถ", "heart hands"], + ["๐Ÿซท", "leftwards pushing hand"], + ["๐Ÿซธ", "rightwards pushing hand"], + ["โ˜€๏ธ", "black sun with rays"], + ["โ˜๏ธ", "cloud"], + ["โ˜‚๏ธ", "umbrella"], + ["โ˜ƒ๏ธ", "snowman"], + ["โ˜„๏ธ", "comet"], + ["โ˜…๏ธ", "black star"], + ["โ˜†๏ธ", "white star"], + ["โ˜‡๏ธ", "lightning"], + ["โ˜ˆ๏ธ", "thunderstorm"], + ["โ˜‰๏ธ", "sun"], + ["โ˜Š๏ธ", "ascending node"], + ["โ˜‹๏ธ", "descending node"], + ["โ˜Œ๏ธ", "conjunction"], + ["โ˜๏ธ", "opposition"], + ["โ˜Ž๏ธ", "black telephone"], + ["โ˜๏ธ", "white telephone"], + ["โ˜๏ธ", "ballot box"], + ["โ˜‘๏ธ", "ballot box with check"], + ["โ˜’๏ธ", "ballot box with x"], + ["โ˜“๏ธ", "saltire"], + ["โ˜”๏ธ", "umbrella with rain drops"], + ["โ˜•๏ธ", "coffee tea ยท hot beverage"], + ["โ˜–๏ธ", "white shogi piece"], + ["โ˜—๏ธ", "black shogi piece"], + ["โ˜˜๏ธ", "shamrock"], + ["โ˜™๏ธ", "reversed rotated floral heart bullet"], + ["โ˜š๏ธ", "black left pointing index"], + ["โ˜›๏ธ", "black right pointing index"], + ["โ˜œ๏ธ", "white left pointing index"], + ["โ˜๏ธ", "white up pointing index"], + ["โ˜ž๏ธ", "white right pointing index"], + ["โ˜Ÿ๏ธ", "white down pointing index"], + ["โ˜ ๏ธ", "skull and crossbones"], + ["โ˜ก๏ธ", "caution sign"], + ["โ˜ข๏ธ", "radioactive sign"], + ["โ˜ฃ๏ธ", "biohazard sign"], + ["โ˜ค๏ธ", "caduceus"], + ["โ˜ฅ๏ธ", "ankh"], + ["โ˜ฆ๏ธ", "orthodox cross"], + ["โ˜ง๏ธ", "chi rho"], + ["โ˜จ๏ธ", "cross of lorraine"], + ["โ˜ฉ๏ธ", "cross of jerusalem"], + ["โ˜ช๏ธ", "star and crescent"], + ["โ˜ซ๏ธ", "farsi symbol"], + ["โ˜ฌ๏ธ", "adi shakti"], + ["โ˜ญ๏ธ", "hammer and sickle"], + ["โ˜ฎ๏ธ", "peace symbol"], + ["โ˜ฏ๏ธ", "yin yang"], + ["โ˜ฐ๏ธ", "trigram for heaven"], + ["โ˜ฑ๏ธ", "trigram for lake"], + ["โ˜ฒ๏ธ", "trigram for fire"], + ["โ˜ณ๏ธ", "trigram for thunder"], + ["โ˜ด๏ธ", "trigram for wind"], + ["โ˜ต๏ธ", "trigram for water"], + ["โ˜ถ๏ธ", "trigram for mountain"], + ["โ˜ท๏ธ", "trigram for earth"], + ["โ˜ธ๏ธ", "wheel of dharma"], + ["โ˜น๏ธ", "white frowning face"], + ["โ˜บ๏ธ", "white smiling face"], + ["โ˜ป๏ธ", "black smiling face"], + ["โ˜ผ๏ธ", "white sun with rays"], + ["โ˜ฝ๏ธ", "first quarter moon"], + ["โ˜พ๏ธ", "last quarter moon"], + ["โ˜ฟ๏ธ", "mercury"], + ["โ™€๏ธ", "female sign"], + ["โ™๏ธ", "earth"], + ["โ™‚๏ธ", "male sign"], + ["โ™ƒ๏ธ", "jupiter"], + ["โ™„๏ธ", "saturn"], + ["โ™…๏ธ", "uranus"], + ["โ™†๏ธ", "neptune"], + ["โ™‡๏ธ", "pluto"], + ["โ™ˆ๏ธ", "aries"], + ["โ™‰๏ธ", "taurus"], + ["โ™Š๏ธ", "gemini"], + ["โ™‹๏ธ", "cancer"], + ["โ™Œ๏ธ", "leo"], + ["โ™๏ธ", "virgo"], + ["โ™Ž๏ธ", "libra"], + ["โ™๏ธ", "scorpius"], + ["โ™๏ธ", "sagittarius"], + ["โ™‘๏ธ", "capricorn"], + ["โ™’๏ธ", "aquarius"], + ["โ™“๏ธ", "pisces"], + ["โ™”๏ธ", "white chess king"], + ["โ™•๏ธ", "white chess queen"], + ["โ™–๏ธ", "white chess rook"], + ["โ™—๏ธ", "white chess bishop"], + ["โ™˜๏ธ", "white chess knight"], + ["โ™™๏ธ", "white chess pawn"], + ["โ™š๏ธ", "black chess king"], + ["โ™›๏ธ", "black chess queen"], + ["โ™œ๏ธ", "black chess rook"], + ["โ™๏ธ", "black chess bishop"], + ["โ™ž๏ธ", "black chess knight"], + ["โ™Ÿ๏ธ", "black chess pawn"], + ["โ™ ๏ธ", "black spade suit"], + ["โ™ก๏ธ", "white heart suit"], + ["โ™ข๏ธ", "white diamond suit"], + ["โ™ฃ๏ธ", "black club suit"], + ["โ™ค๏ธ", "white spade suit"], + ["โ™ฅ๏ธ", "black heart suit"], + ["โ™ฆ๏ธ", "black diamond suit"], + ["โ™ง๏ธ", "white club suit"], + ["โ™จ๏ธ", "hot springs"], + ["โ™ฉ๏ธ", "quarter note"], + ["โ™ช๏ธ", "eighth note"], + ["โ™ซ๏ธ", "beamed eighth notes"], + ["โ™ฌ๏ธ", "beamed sixteenth notes"], + ["โ™ญ๏ธ", "music flat sign"], + ["โ™ฎ๏ธ", "music natural sign"], + ["โ™ฐ๏ธ", "west syriac cross"], + ["โ™ฑ๏ธ", "east syriac cross"], + ["โ™ฒ๏ธ", "universal recycling symbol"], + ["โ™ณ๏ธ", "recycling symbol for type-1 plastics"], + ["โ™ด๏ธ", "recycling symbol for type-2 plastics"], + ["โ™ต๏ธ", "recycling symbol for type-3 plastics"], + ["โ™ถ๏ธ", "recycling symbol for type-4 plastics"], + ["โ™ท๏ธ", "recycling symbol for type-5 plastics"], + ["โ™ธ๏ธ", "recycling symbol for type-6 plastics"], + ["โ™น๏ธ", "recycling symbol for type-7 plastics"], + ["โ™บ๏ธ", "recycling symbol for generic materials"], + ["โ™ป๏ธ", "black universal recycling symbol"], + ["โ™ผ๏ธ", "recycled paper symbol"], + ["โ™ฝ๏ธ", "partially-recycled paper symbol"], + ["โ™พ๏ธ", "permanent paper sign"], + ["โ™ฟ๏ธ", "wheelchair symbol"], + ["โš€๏ธ", "die face-1"], + ["โš๏ธ", "die face-2"], + ["โš‚๏ธ", "die face-3"], + ["โšƒ๏ธ", "die face-4"], + ["โš„๏ธ", "die face-5"], + ["โš…๏ธ", "die face-6"], + ["โš†๏ธ", "white circle with dot right"], + ["โš‡๏ธ", "white circle with two dots"], + ["โšˆ๏ธ", "black circle with white dot right"], + ["โš‰๏ธ", "black circle with two white dots"], + ["โšŠ๏ธ", "monogram for yang"], + ["โš‹๏ธ", "monogram for yin"], + ["โšŒ๏ธ", "digram for greater yang"], + ["โš๏ธ", "digram for lesser yin"], + ["โšŽ๏ธ", "digram for lesser yang"], + ["โš๏ธ", "digram for greater yin"], + ["โš๏ธ", "white flag"], + ["โš‘๏ธ", "black flag"], + ["โš’๏ธ", "hammer and pick"], + ["โš“๏ธ", "anchor"], + ["โš”๏ธ", "crossed swords"], + ["โš•๏ธ", "staff of aesculapius"], + ["โš–๏ธ", "scales"], + ["โš—๏ธ", "alembic"], + ["โš˜๏ธ", "flower"], + ["โš™๏ธ", "gear"], + ["โšš๏ธ", "staff of hermes"], + ["โš›๏ธ", "atom symbol"], + ["โšœ๏ธ", "fleur-de-lis"], + ["โš๏ธ", "outlined white star"], + ["โšž๏ธ", "three lines converging right"], + ["โšŸ๏ธ", "three lines converging left"], + ["โš ๏ธ", "warning caution ยท warning sign"], + ["โšก๏ธ", "high voltage sign"], + ["โšข๏ธ", "doubled female sign"], + ["โšฃ๏ธ", "doubled male sign"], + ["โšค๏ธ", "interlocked female and male sign"], + ["โšฅ๏ธ", "male and female sign"], + ["โšฆ๏ธ", "male with stroke sign"], + ["โšง๏ธ", "male with stroke and male and female sign"], + ["โšจ๏ธ", "vertical male with stroke sign"], + ["โšฉ๏ธ", "horizontal male with stroke sign"], + ["โšช๏ธ", "medium white circle"], + ["โšซ๏ธ", "medium black circle"], + ["โšฌ๏ธ", "medium small white circle"], + ["โšญ๏ธ", "marriage symbol"], + ["โšฎ๏ธ", "divorce symbol"], + ["โšฏ๏ธ", "unmarried partnership symbol"], + ["โšฐ๏ธ", "coffin"], + ["โšฑ๏ธ", "funeral urn"], + ["โšฒ๏ธ", "neuter"], + ["โšณ๏ธ", "ceres"], + ["โšด๏ธ", "pallas"], + ["โšต๏ธ", "juno"], + ["โšถ๏ธ", "vesta"], + ["โšท๏ธ", "chiron"], + ["โšธ๏ธ", "black moon lilith"], + ["โšน๏ธ", "sextile"], + ["โšบ๏ธ", "semisextile"], + ["โšป๏ธ", "quincunx"], + ["โšผ๏ธ", "sesquiquadrate"], + ["โšฝ๏ธ", "soccer ball"], + ["โšพ๏ธ", "baseball"], + ["โšฟ๏ธ", "squared key"], + ["โ›€๏ธ", "white draughts man"], + ["โ›๏ธ", "white draughts king"], + ["โ›‚๏ธ", "black draughts man"], + ["โ›ƒ๏ธ", "black draughts king"], + ["โ›„๏ธ", "snowman without snow"], + ["โ›…๏ธ", "sun behind cloud"], + ["โ›†๏ธ", "rain"], + ["โ›‡๏ธ", "black snowman"], + ["โ›ˆ๏ธ", "thunder cloud and rain"], + ["โ›‰๏ธ", "turned white shogi piece"], + ["โ›Š๏ธ", "turned black shogi piece"], + ["โ›‹๏ธ", "white diamond in square"], + ["โ›Œ๏ธ", "crossing lanes"], + ["โ›๏ธ", "disabled car"], + ["โ›Ž๏ธ", "ophiuchus"], + ["โ›๏ธ", "pick"], + ["โ›๏ธ", "car sliding"], + ["โ›‘๏ธ", "helmet with white cross"], + ["โ›’๏ธ", "circled crossing lanes"], + ["โ›“๏ธ", "chains"], + ["โ›”๏ธ", "no entry"], + ["โ›•๏ธ", "alternate one-way left way traffic"], + ["โ›–๏ธ", "black two-way left way traffic"], + ["โ›—๏ธ", "white two-way left way traffic"], + ["โ›˜๏ธ", "black left lane merge"], + ["โ›™๏ธ", "white left lane merge"], + ["โ›š๏ธ", "drive slow sign"], + ["โ››๏ธ", "heavy white down-pointing triangle"], + ["โ›œ๏ธ", "left closed entry"], + ["โ›๏ธ", "squared saltire"], + ["โ›ž๏ธ", "falling diagonal in white circle in black square"], + ["โ›Ÿ๏ธ", "black truck"], + ["โ› ๏ธ", "restricted left entry-1"], + ["โ›ก๏ธ", "restricted left entry-2"], + ["โ›ข๏ธ", "astronomical symbol for uranus"], + ["โ›ฃ๏ธ", "heavy circle with stroke and two dots above"], + ["โ›ค๏ธ", "pentagram"], + ["โ›ฅ๏ธ", "right-handed interlaced pentagram"], + ["โ›ฆ๏ธ", "left-handed interlaced pentagram"], + ["โ›ง๏ธ", "inverted pentagram"], + ["โ›จ๏ธ", "black cross on shield"], + ["โ›ฉ๏ธ", "shinto shrine"], + ["โ›ช๏ธ", "church"], + ["โ›ซ๏ธ", "castle"], + ["โ›ฌ๏ธ", "historic site"], + ["โ›ญ๏ธ", "gear without hub"], + ["โ›ฎ๏ธ", "gear with handles"], + ["โ›ฏ๏ธ", "map symbol for lighthouse"], + ["โ›ฐ๏ธ", "mountain"], + ["โ›ฑ๏ธ", "umbrella on ground"], + ["โ›ฒ๏ธ", "fountain"], + ["โ›ณ๏ธ", "flag in hole"], + ["โ›ด๏ธ", "ferry"], + ["โ›ต๏ธ", "sailboat"], + ["โ›ถ๏ธ", "square four corners"], + ["โ›ท๏ธ", "skier"], + ["โ›ธ๏ธ", "ice skate"], + ["โ›น๏ธ", "person with ball"], + ["โ›บ๏ธ", "tent"], + ["โ›ป๏ธ", "japanese bank symbol"], + ["โ›ผ๏ธ", "headstone graveyard symbol"], + ["โ›ฝ๏ธ", "fuel pump"], + ["โ›พ๏ธ", "cup on black square"], + ["โ›ฟ๏ธ", "white flag with horizontal middle black stripe"], + ["โœ€๏ธ", "black safety scissors"], + ["โœ๏ธ", "upper blade scissors"], + ["โœ‚๏ธ", "black scissors"], + ["โœƒ๏ธ", "lower blade scissors"], + ["โœ„๏ธ", "white scissors"], + ["โœ…๏ธ", "check done ok yes ยท white heavy check mark"], + ["โœ†๏ธ", "telephone location sign"], + ["โœ‡๏ธ", "tape drive"], + ["โœˆ๏ธ", "airplane"], + ["โœ‰๏ธ", "envelope"], + ["โœŠ๏ธ", "raised fist"], + ["โœ‹๏ธ", "raised hand"], + ["โœŒ๏ธ", "victory hand"], + ["โœ๏ธ", "writing hand"], + ["โœŽ๏ธ", "lower right pencil"], + ["โœ๏ธ", "pencil"], + ["โœ๏ธ", "upper right pencil"], + ["โœ‘๏ธ", "white nib"], + ["โœ’๏ธ", "black nib"], + ["โœ“๏ธ", "check mark"], + ["โœ”๏ธ", "heavy check mark"], + ["โœ•๏ธ", "multiplication x"], + ["โœ–๏ธ", "heavy multiplication x"], + ["โœ—๏ธ", "ballot x"], + ["โœ˜๏ธ", "heavy ballot x"], + ["โœ™๏ธ", "outlined greek cross"], + ["โœš๏ธ", "heavy greek cross"], + ["โœ›๏ธ", "open centre cross"], + ["โœœ๏ธ", "heavy open centre cross"], + ["โœ๏ธ", "latin cross"], + ["โœž๏ธ", "shadowed white latin cross"], + ["โœŸ๏ธ", "outlined latin cross"], + ["โœ ๏ธ", "maltese cross"], + ["โœก๏ธ", "star of david"], + ["โœข๏ธ", "four teardrop-spoked asterisk"], + ["โœฃ๏ธ", "four balloon-spoked asterisk"], + ["โœค๏ธ", "heavy four balloon-spoked asterisk"], + ["โœฅ๏ธ", "four club-spoked asterisk"], + ["โœฆ๏ธ", "black four pointed star"], + ["โœง๏ธ", "white four pointed star"], + ["โœจ๏ธ", "sparkles magic ยท sparkles"], + ["โœฉ๏ธ", "stress outlined white star"], + ["โœช๏ธ", "circled white star"], + ["โœซ๏ธ", "open centre black star"], + ["โœฌ๏ธ", "black centre white star"], + ["โœญ๏ธ", "outlined black star"], + ["โœฎ๏ธ", "heavy outlined black star"], + ["โœฏ๏ธ", "pinwheel star"], + ["โœฐ๏ธ", "shadowed white star"], + ["โœฑ๏ธ", "heavy asterisk"], + ["โœฒ๏ธ", "open centre asterisk"], + ["โœณ๏ธ", "eight spoked asterisk"], + ["โœด๏ธ", "eight pointed black star"], + ["โœต๏ธ", "eight pointed pinwheel star"], + ["โœถ๏ธ", "six pointed black star"], + ["โœท๏ธ", "eight pointed rectilinear black star"], + ["โœธ๏ธ", "heavy eight pointed rectilinear black star"], + ["โœน๏ธ", "twelve pointed black star"], + ["โœบ๏ธ", "sixteen pointed asterisk"], + ["โœป๏ธ", "teardrop-spoked asterisk"], + ["โœผ๏ธ", "open centre teardrop-spoked asterisk"], + ["โœฝ๏ธ", "heavy teardrop-spoked asterisk"], + ["โœพ๏ธ", "six petalled black and white florette"], + ["โœฟ๏ธ", "black florette"], + ["โ€๏ธ", "white florette"], + ["โ๏ธ", "eight petalled outlined black florette"], + ["โ‚๏ธ", "circled open centre eight pointed star"], + ["โƒ๏ธ", "heavy teardrop-spoked pinwheel asterisk"], + ["โ„๏ธ", "snowflake"], + ["โ…๏ธ", "tight trifoliate snowflake"], + ["โ†๏ธ", "heavy chevron snowflake"], + ["โ‡๏ธ", "sparkle"], + ["โˆ๏ธ", "heavy sparkle"], + ["โ‰๏ธ", "balloon-spoked asterisk"], + ["โŠ๏ธ", "eight teardrop-spoked propeller asterisk"], + ["โ‹๏ธ", "heavy eight teardrop-spoked propeller asterisk"], + ["โŒ๏ธ", "cross no wrong ยท cross mark"], + ["โ๏ธ", "shadowed white circle"], + ["โŽ๏ธ", "negative squared cross mark"], + ["โ๏ธ", "lower right drop-shadowed white square"], + ["โ๏ธ", "upper right drop-shadowed white square"], + ["โ‘๏ธ", "lower right shadowed white square"], + ["โ’๏ธ", "upper right shadowed white square"], + ["โ“๏ธ", "black question mark ornament"], + ["โ”๏ธ", "white question mark ornament"], + ["โ•๏ธ", "white exclamation mark ornament"], + ["โ–๏ธ", "black diamond minus white x"], + ["โ—๏ธ", "heavy exclamation mark symbol"], + ["โ˜๏ธ", "light vertical bar"], + ["โ™๏ธ", "medium vertical bar"], + ["โš๏ธ", "heavy vertical bar"], + ["โ›๏ธ", "heavy single turned comma quotation mark ornament"], + ["โœ๏ธ", "heavy single comma quotation mark ornament"], + ["โ๏ธ", "heavy double turned comma quotation mark ornament"], + ["โž๏ธ", "heavy double comma quotation mark ornament"], + ["โŸ๏ธ", "heavy low single comma quotation mark ornament"], + ["โ ๏ธ", "heavy low double comma quotation mark ornament"], + ["โก๏ธ", "curved stem paragraph sign ornament"], + ["โข๏ธ", "heavy exclamation mark ornament"], + ["โฃ๏ธ", "heavy heart exclamation mark ornament"], + ["โค๏ธ", "red heart love ยท heavy black heart"], + ["โฅ๏ธ", "rotated heavy black heart bullet"], + ["โฆ๏ธ", "floral heart"], + ["โง๏ธ", "rotated floral heart bullet"], + ["โž”๏ธ", "heavy wide-headed rightwards arrow"], + ["โž•๏ธ", "heavy plus sign"], + ["โž–๏ธ", "heavy minus sign"], + ["โž—๏ธ", "heavy division sign"], + ["โž˜๏ธ", "heavy south east arrow"], + ["โž™๏ธ", "heavy rightwards arrow"], + ["โžš๏ธ", "heavy north east arrow"], + ["โž›๏ธ", "drafting point rightwards arrow"], + ["โžœ๏ธ", "heavy round-tipped rightwards arrow"], + ["โž๏ธ", "triangle-headed rightwards arrow"], + ["โžž๏ธ", "heavy triangle-headed rightwards arrow"], + ["โžŸ๏ธ", "dashed triangle-headed rightwards arrow"], + ["โž ๏ธ", "heavy dashed triangle-headed rightwards arrow"], + ["โžก๏ธ", "black rightwards arrow"], + ["โžข๏ธ", "three-d top-lighted rightwards arrowhead"], + ["โžฃ๏ธ", "three-d bottom-lighted rightwards arrowhead"], + ["โžค๏ธ", "black rightwards arrowhead"], + ["โžฅ๏ธ", "heavy black curved downwards and rightwards arrow"], + ["โžฆ๏ธ", "heavy black curved upwards and rightwards arrow"], + ["โžง๏ธ", "squat black rightwards arrow"], + ["โžจ๏ธ", "heavy concave-pointed black rightwards arrow"], + ["โžฉ๏ธ", "right-shaded white rightwards arrow"], + ["โžช๏ธ", "left-shaded white rightwards arrow"], + ["โžซ๏ธ", "back-tilted shadowed white rightwards arrow"], + ["โžฌ๏ธ", "front-tilted shadowed white rightwards arrow"], + ["โžญ๏ธ", "heavy lower right-shadowed white rightwards arrow"], + ["โžฎ๏ธ", "heavy upper right-shadowed white rightwards arrow"], + ["โžฏ๏ธ", "notched lower right-shadowed white rightwards arrow"], + ["โžฐ๏ธ", "curly loop"], + ["โžฑ๏ธ", "notched upper right-shadowed white rightwards arrow"], + ["โžฒ๏ธ", "circled heavy white rightwards arrow"], + ["โžณ๏ธ", "white-feathered rightwards arrow"], + ["โžด๏ธ", "black-feathered south east arrow"], + ["โžต๏ธ", "black-feathered rightwards arrow"], + ["โžถ๏ธ", "black-feathered north east arrow"], + ["โžท๏ธ", "heavy black-feathered south east arrow"], + ["โžธ๏ธ", "heavy black-feathered rightwards arrow"], + ["โžน๏ธ", "heavy black-feathered north east arrow"], + ["โžบ๏ธ", "teardrop-barbed rightwards arrow"], + ["โžป๏ธ", "heavy teardrop-shanked rightwards arrow"], + ["โžผ๏ธ", "wedge-tailed rightwards arrow"], + ["โžฝ๏ธ", "heavy wedge-tailed rightwards arrow"], + ["โžพ๏ธ", "open-outlined rightwards arrow"], + ["โžฟ๏ธ", "double curly loop"] +] diff --git a/extensions/github/backslash.toml b/extensions/github/backslash.toml new file mode 100644 index 0000000..2cd88d8 --- /dev/null +++ b/extensions/github/backslash.toml @@ -0,0 +1,28 @@ +[extension] +id = "github" +title = "GitHub" +description = "Search repositories and follow your pull requests" +version = "1.0.0" +author = "Backslash" +icon = "folder-remote" +license = "MIT" + +[permissions] +net = ["api.github.com"] + +[[commands]] +name = "repositories" +title = "Search GitHub Repositories" +keywords = ["github", "repo", "git", "code"] + +[[commands]] +name = "pull-requests" +title = "My Pull Requests" +description = "Pull requests you opened and those waiting for your review" +keywords = ["github", "pr", "review"] + +[[preferences]] +name = "token" +title = "Personal access token" +description = "Needed for your pull requests; raises the search rate limit. Read-only scopes are enough." +type = "secret" diff --git a/extensions/github/commands/api.ts b/extensions/github/commands/api.ts new file mode 100644 index 0000000..48db898 --- /dev/null +++ b/extensions/github/commands/api.ts @@ -0,0 +1,40 @@ +// Small GitHub REST helper shared by the commands. + +export async function github(path: string, token: string | undefined, signal?: AbortSignal): Promise { + const res = await fetch(`https://api.github.com${path}`, { + signal, + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "backslash-launcher", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); + if (res.status === 401) throw new Error("GitHub rejected the token: check it in Settings โ†’ Extensions โ†’ GitHub"); + if (res.status === 403 || res.status === 429) { + if (res.headers.get("x-ratelimit-remaining") === "0") { + throw new Error("GitHub rate limit reached: add a token in Settings โ†’ Extensions โ†’ GitHub"); + } + throw new Error(`GitHub refused the request (${res.status})`); + } + if (!res.ok) throw new Error(`GitHub answered ${res.status}`); + return await res.json() as T; +} + +export async function readme(fullName: string, token: string | undefined): Promise { + const res = await fetch(`https://api.github.com/repos/${fullName}/readme`, { + headers: { + Accept: "application/vnd.github.raw", + "User-Agent": "backslash-launcher", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); + return res.ok ? await res.text() : "_No README._"; +} + +export function ago(iso: string): string { + const days = Math.floor((Date.now() - new Date(iso).getTime()) / 86_400_000); + if (days <= 0) return "today"; + if (days === 1) return "yesterday"; + if (days < 30) return `${days} days ago`; + return new Date(iso).toLocaleDateString(); +} diff --git a/extensions/github/commands/pull-requests.ts b/extensions/github/commands/pull-requests.ts new file mode 100644 index 0000000..e770add --- /dev/null +++ b/extensions/github/commands/pull-requests.ts @@ -0,0 +1,50 @@ +import { copy, type Item, list, open } from "@backslash/api"; +import { ago, github } from "./api.ts"; + +interface Issue { + number: number; + title: string; + html_url: string; + updated_at: string; + draft?: boolean; + repository_url: string; + user: { login: string }; +} + +function item(pr: Issue, section: string): Item { + const repo = pr.repository_url.replace("https://api.github.com/repos/", ""); + return { + id: pr.html_url, + title: pr.title, + subtitle: `${repo} #${pr.number} ยท ${ago(pr.updated_at)}`, + icon: "mail-forward", + section, + keywords: [repo, pr.user.login, String(pr.number)], + accessories: pr.draft ? [{ tag: "Draft", color: "gray" }] : [], + actions: [open(pr.html_url, "Open in Browser"), copy(pr.html_url, "Copy URL")], + }; +} + +export default list({ + placeholder: "Filter pull requestsโ€ฆ", + empty: { + title: "No open pull requests", + description: "Nothing waits for you. (A token is needed: Settings โ†’ Extensions โ†’ GitHub.)", + }, + async items({ preferences, signal }) { + const token = preferences.token as string | undefined; + if (!token) { + throw new Error("Add a GitHub token in Settings โ†’ Extensions โ†’ GitHub to see your pull requests"); + } + const search = (q: string) => + github<{ items: Issue[] }>(`/search/issues?q=${encodeURIComponent(q)}&per_page=50`, token, signal); + const [review, mine] = await Promise.all([ + search("is:pr is:open review-requested:@me archived:false"), + search("is:pr is:open author:@me archived:false"), + ]); + return [ + ...review.items.map((pr) => item(pr, "Waiting for your review")), + ...mine.items.map((pr) => item(pr, "Opened by you")), + ]; + }, +}); diff --git a/extensions/github/commands/repositories.ts b/extensions/github/commands/repositories.ts new file mode 100644 index 0000000..133facc --- /dev/null +++ b/extensions/github/commands/repositories.ts @@ -0,0 +1,42 @@ +import { action, copy, detail, list, open } from "@backslash/api"; +import { github, readme } from "./api.ts"; + +interface Repo { + full_name: string; + description: string | null; + html_url: string; + clone_url: string; + stargazers_count: number; + language: string | null; + updated_at: string; + owner: { login: string }; +} + +const stars = (n: number) => (n >= 1000 ? `${(n / 1000).toFixed(1)}k โ˜…` : `${n} โ˜…`); + +export default list({ + placeholder: "Search repositoriesโ€ฆ", + empty: { title: "Search GitHub", description: "Type a name, a topic, or `language:rust tui`" }, + async search(query, { signal, preferences }) { + if (!query.trim()) return []; + const token = preferences.token as string | undefined; + const q = encodeURIComponent(query); + const { items } = await github<{ items: Repo[] }>(`/search/repositories?q=${q}&per_page=25`, token, signal); + return items.map((r) => ({ + id: r.full_name, + title: r.full_name, + subtitle: r.description ?? undefined, + icon: "folder-remote", + accessories: [ + ...(r.language ? [{ text: r.language }] : []), + { tag: stars(r.stargazers_count) }, + ], + actions: [ + open(r.html_url, "Open in Browser"), + action("Show README", async () => detail({ title: r.full_name, markdown: await readme(r.full_name, token) })), + copy(`git clone ${r.clone_url}`, "Copy Clone Command", { shortcut: "c" }), + copy(r.html_url, "Copy URL"), + ], + })); + }, +}); diff --git a/extensions/packages/backslash.toml b/extensions/packages/backslash.toml new file mode 100644 index 0000000..948eaaa --- /dev/null +++ b/extensions/packages/backslash.toml @@ -0,0 +1,16 @@ +[extension] +id = "packages" +title = "Packages" +description = "Search your distribution's packages and Flathub" +version = "1.0.0" +author = "Backslash" +icon = "system-software-install" +license = "MIT" + +[permissions] +run = ["apt-cache", "dnf", "pacman", "zypper", "flatpak"] + +[[commands]] +name = "search" +title = "Search Packages" +keywords = ["apt", "dnf", "pacman", "zypper", "flatpak", "install", "software"] diff --git a/extensions/packages/commands/search.ts b/extensions/packages/commands/search.ts new file mode 100644 index 0000000..a23bc34 --- /dev/null +++ b/extensions/packages/commands/search.ts @@ -0,0 +1,106 @@ +import { copy, exec, type Item, list, open } from "@backslash/api"; + +interface Package { + name: string; + summary: string; + version?: string; + source: "apt" | "dnf" | "pacman" | "zypper" | "flatpak"; +} + +const INSTALL: Record string> = { + apt: (n) => `sudo apt install ${n}`, + dnf: (n) => `sudo dnf install ${n}`, + pacman: (n) => `sudo pacman -S ${n}`, + zypper: (n) => `sudo zypper install ${n}`, + flatpak: (n) => `flatpak install flathub ${n}`, +}; + +const PAGE: Record string> = { + apt: (n) => `https://packages.debian.org/search?keywords=${n}`, + dnf: (n) => `https://packages.fedoraproject.org/pkgs/${n}/`, + pacman: (n) => `https://archlinux.org/packages/?q=${n}`, + zypper: (n) => `https://software.opensuse.org/package/${n}`, + flatpak: (n) => `https://flathub.org/apps/${n}`, +}; + +/** Runs a search tool; a missing tool just means "not this distribution". */ +async function tryExec(program: string, args: string[], signal: AbortSignal): Promise { + try { + return await exec(program, args, { signal }); + } catch (err) { + if (signal.aborted) throw err; + return null; + } +} + +async function system(query: string, signal: AbortSignal): Promise { + const apt = await tryExec("apt-cache", ["search", "--names-only", query], signal); + if (apt !== null) { + return apt.split("\n").filter(Boolean).map((line) => { + const [name, ...rest] = line.split(" - "); + return { name, summary: rest.join(" - "), source: "apt" as const }; + }); + } + const dnf = await tryExec("dnf", ["search", "-q", query], signal); + if (dnf !== null) { + return dnf.split("\n").filter((l) => l.includes(" : ")).map((line) => { + const [name, summary] = line.split(" : "); + return { name: name.trim().replace(/\.[^.]+$/, ""), summary: summary.trim(), source: "dnf" as const }; + }); + } + const pacman = await tryExec("pacman", ["-Ss", query], signal); + if (pacman !== null) { + const lines = pacman.split("\n"); + const out: Package[] = []; + for (let i = 0; i + 1 < lines.length; i += 2) { + const [full, version] = lines[i].split(" "); + out.push({ name: full.split("/")[1] ?? full, version, summary: lines[i + 1].trim(), source: "pacman" }); + } + return out; + } + const zypper = await tryExec("zypper", ["--quiet", "search", query], signal); + if (zypper !== null) { + return zypper.split("\n").filter((l) => l.includes("|") && !l.includes("Name")).map((line) => { + const cols = line.split("|").map((c) => c.trim()); + return { name: cols[1], summary: cols[2] ?? "", source: "zypper" as const }; + }).filter((p) => p.name); + } + return []; +} + +async function flathub(query: string, signal: AbortSignal): Promise { + const out = await tryExec("flatpak", ["search", "--columns=application,name,description,version", query], signal); + if (!out || out.startsWith("No matches")) return []; + return out.split("\n").filter(Boolean).map((line) => { + const [id, name, summary, version] = line.split("\t"); + return { name: id, summary: `${name}: ${summary}`, version, source: "flatpak" as const }; + }); +} + +function item(p: Package): Item { + return { + id: `${p.source}:${p.name}`, + title: p.name, + subtitle: p.summary, + icon: p.source === "flatpak" ? "application-x-flatpak" : "package-x-generic", + section: p.source === "flatpak" ? "Flathub" : "Distribution", + accessories: [{ tag: p.source }, ...(p.version ? [{ text: p.version }] : [])], + actions: [ + copy(INSTALL[p.source](p.name), "Copy Install Command"), + open(PAGE[p.source](p.name), "Open Package Page"), + copy(p.name, "Copy Name"), + ], + }; +} + +export default list({ + placeholder: "Search packagesโ€ฆ", + empty: { title: "Search packages", description: "apt, dnf, pacman or zypper, and Flathub" }, + async search(query, { signal }) { + if (query.trim().length < 2) return []; + const [sys, flat] = await Promise.all([system(query, signal), flathub(query, signal)]); + // Exact and prefix matches first. + const rank = (p: Package) => (p.name === query ? 0 : p.name.startsWith(query) ? 1 : 2); + return [...sys.sort((a, b) => rank(a) - rank(b)).slice(0, 40), ...flat.slice(0, 20)].map(item); + }, +}); diff --git a/extensions/systemd/backslash.toml b/extensions/systemd/backslash.toml new file mode 100644 index 0000000..1a088d5 --- /dev/null +++ b/extensions/systemd/backslash.toml @@ -0,0 +1,17 @@ +[extension] +id = "systemd" +title = "Services" +description = "Start, stop and inspect systemd services" +version = "1.0.0" +author = "Backslash" +icon = "preferences-system" +license = "MIT" + +[permissions] +run = ["systemctl", "journalctl"] + +[[commands]] +name = "services" +title = "Services" +description = "Your user services and the system's" +keywords = ["systemd", "systemctl", "daemon", "unit", "restart"] diff --git a/extensions/systemd/commands/services.ts b/extensions/systemd/commands/services.ts new file mode 100644 index 0000000..6a0f9f8 --- /dev/null +++ b/extensions/systemd/commands/services.ts @@ -0,0 +1,78 @@ +import { action, type Color, copy, detail, exec, type Item, list, refresh, toast } from "@backslash/api"; + +interface Unit { + name: string; + active: string; + sub: string; + description: string; + user: boolean; +} + +async function units(user: boolean): Promise { + const args = [...(user ? ["--user"] : []), "list-units", "--type=service", "--all", "--plain", "--no-legend"]; + const out = await exec("systemctl", args).catch(() => ""); + return out.split("\n").filter(Boolean).map((line) => { + const [name, _load, active, sub, ...description] = line.trim().split(/\s+/); + return { name, active, sub, description: description.join(" "), user }; + }).filter((u) => u.name?.endsWith(".service")); +} + +const color = (u: Unit): Color => (u.active === "active" ? "green" : u.active === "failed" ? "red" : "gray"); + +function control(u: Unit, verb: string) { + return async () => { + // System units ask for authentication through polkit when needed. + await exec("systemctl", [...(u.user ? ["--user"] : []), verb, u.name]); + await toast(`${u.name}: ${verb} done`, { style: "success" }); + return refresh(); + }; +} + +function item(u: Unit): Item { + const running = u.active === "active"; + return { + id: `${u.user ? "user" : "system"}:${u.name}`, + title: u.name.replace(/\.service$/, ""), + subtitle: u.description, + icon: "system-run", + section: u.user ? "User services" : "System services", + keywords: [u.sub, u.active], + accessories: [{ tag: u.sub, color: color(u) }], + actions: [ + action("Show Logs", async () => { + const logs = await exec("journalctl", [ + ...(u.user ? ["--user"] : []), + "-u", + u.name, + "-n", + "100", + "--no-pager", + "-o", + "short-iso", + ]).catch((e) => `Could not read the logs: ${e.message}`); + return detail({ + title: u.name, + markdown: `# ${u.name}\n\n${u.description}\n\n\`\`\`\n${logs || "No logs."}\n\`\`\``, + metadata: { State: `${u.active} (${u.sub})`, Scope: u.user ? "user" : "system" }, + }); + }), + action("Restart", control(u, "restart"), { icon: "view-refresh", shortcut: "r" }), + running + ? action("Stop", control(u, "stop"), { destructive: true, confirm: `Stop ${u.name}?`, shortcut: "s" }) + : action("Start", control(u, "start"), { shortcut: "s" }), + copy(u.name, "Copy Unit Name"), + ], + }; +} + +export default list({ + placeholder: "Filter servicesโ€ฆ", + empty: { title: "No services", description: "systemd does not seem to run here" }, + items: async () => { + const [user, system] = await Promise.all([units(true), units(false)]); + // Running and failed services first. + const order = (u: Unit) => (u.active === "failed" ? 0 : u.active === "active" ? 1 : 2); + return [...user, ...system].sort((a, b) => Number(a.user) - Number(b.user) || order(a) - order(b)).map(item) + .sort((a, b) => (a.section === b.section ? 0 : a.section === "User services" ? -1 : 1)); + }, +}); diff --git a/google/google-calendar-add.js b/google/google-calendar-add.js deleted file mode 100644 index 30b7d11..0000000 --- a/google/google-calendar-add.js +++ /dev/null @@ -1,5 +0,0 @@ -const URL = `https://calendar.google.com/calendar/u/0/r/eventedit`; - -const run = async (_, { shell }) => await shell.openExternal(URL); - -module.exports = { run, actions: [] }; diff --git a/google/google-calendar-view.js b/google/google-calendar-view.js deleted file mode 100644 index 8ec0413..0000000 --- a/google/google-calendar-view.js +++ /dev/null @@ -1,5 +0,0 @@ -const URL = `https://calendar.google.com/calendar/`; - -const run = async (_, { shell }) => await shell.openExternal(URL); - -module.exports = { run, actions: [] }; diff --git a/google/index.js b/google/index.js deleted file mode 100644 index fe1a158..0000000 --- a/google/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - commands: { - 'google-calendar-add': require('./google-calendar-add'), - 'google-calendar-view': require('./google-calendar-view'), - }, -}; diff --git a/google/manifest.yml b/google/manifest.yml deleted file mode 100644 index 5742e62..0000000 --- a/google/manifest.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: google -label: Google -version: 1.0.0 -author: Backslash - -commands: - - name: google-calendar-add - label: Add an event - isImmediate: true - bgColor: "#2563EB" - color: "#FFFFFF" - icon: calendar - keywords: - - add - - google - - calendar - - event - - - name: google-calendar-view - label: View events - isImmediate: true - bgColor: "#2563EB" - color: "#FFFFFF" - icon: calendar - keywords: - - view - - google - - calendar - - events \ No newline at end of file diff --git a/npm/index.js b/npm/index.js deleted file mode 100644 index 4245122..0000000 --- a/npm/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - commands: { - 'npm-search': require('./npm-search'), - }, -}; diff --git a/npm/manifest.yml b/npm/manifest.yml deleted file mode 100644 index b6a3788..0000000 --- a/npm/manifest.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: npm -label: NPM -version: 1.0.0 -author: Backslash - -commands: - - name: npm-search - label: NPM Search - isImmediate: false - bgColor: "#DC2626" - color: "#FFFFFF" - icon: package - keywords: - - search - - npm diff --git a/npm/npm-search.js b/npm/npm-search.js deleted file mode 100644 index e80acea..0000000 --- a/npm/npm-search.js +++ /dev/null @@ -1,86 +0,0 @@ -const run = async (query, { axios }) => { - try { - const response = await axios.get( - `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=10` - ); - - if (response.data && response.data.objects && response.data.objects.length > 0) { - return response.data.objects.map((obj) => ({ - data: { - id: obj.package.name, - name: obj.package.name, - version: obj.package.version, - link: `https://www.npmjs.com/package/${obj.package.name}`, - }, - content: [ - { - type: 'div', - className: 'flex items-center w-full gap-2', - children: [ - { - type: 'title', - content: obj.package.name, - className: '' - }, - { - type: 'p', - content: obj.package.description, - className: 'flex-1' - }, - { - type: 'badge', - content: `v${obj.package.version}`, - className: '' - } - ] - } - ] - })); - } else { - return []; - } - } catch (error) { - console.log(error); - } -}; - -const openNpmPackage = async (package, { shell }) => { - shell.openExternal(`https://www.npmjs.com/package/${package.name}`); -}; - -const openBundlephobia = async (package, { shell }) => { - shell.openExternal(`https://bundlephobia.com/result?p=${package.name}`); -}; - -const openESM = async (package, { shell }) => { - shell.openExternal(`https://esm.sh/${package.name}`); -}; - -const openSnykVulnCheck = async (package, { shell }) => { - shell.openExternal(`https://snyk.io/test/npm/${package.name}`); -}; - -const copyNpmInstallCommand = async (package, { clipboard }) => { - clipboard.writeText(`npm install ${package.name}`); -}; - -const copyYarnInstallCommand = async (package, { clipboard }) => { - clipboard.writeText(`yarn add ${package.name}`); -}; - -const copyPackageName = async (package, { clipboard }) => { - clipboard.writeText(package.name); -}; - -module.exports = { - run, - actions: [ - { name: 'Open NPM Package', action: openNpmPackage }, - { name: 'Open Bundlephobia', action: openBundlephobia }, - { name: 'Open ESM', action: openESM }, - { name: 'Open Snyk Vulnerability Check', action: openSnykVulnCheck }, - { name: 'Copy npm install command', action: copyNpmInstallCommand }, - { name: 'Copy yarn add command', action: copyYarnInstallCommand }, - { name: 'Copy package name', action: copyPackageName }, - ], -}; diff --git a/scripts/validate.ts b/scripts/validate.ts new file mode 100644 index 0000000..07246f9 --- /dev/null +++ b/scripts/validate.ts @@ -0,0 +1,141 @@ +// Validates registry entries: `deno run -A scripts/validate.ts [registry/x.tomlโ€ฆ]` +// +// For each entry, clones the repository at the pinned commit and checks that +// the files, manifest and permissions match what the entry says. This is the +// same verification Backslash does before installing, run at review time. + +import { parse } from "@std/toml"; + +interface Entry { + id: string; + title: string; + version: string; + repository: string; + path?: string; + commit: string; + checksum: string; + permissions?: string[]; + commands?: string[]; +} + +interface Manifest { + extension: { id: string; title: string; version?: string }; + runtime?: { kind: string }; + permissions?: { + net?: string[]; + run?: string[]; + read?: string[]; + write?: string[]; + env?: string[]; + "clipboard-read"?: boolean; + }; + commands?: { title: string }[]; +} + +async function git(args: string[], cwd?: string): Promise { + const out = await new Deno.Command("git", { args, cwd, stdout: "piped", stderr: "piped" }).output(); + if (!out.success) throw new Error(`git ${args[0]}: ${new TextDecoder().decode(out.stderr).trim()}`); + return new TextDecoder().decode(out.stdout).trim(); +} + +/** Same algorithm as `backslash_core::catalog::checksum`. */ +async function checksum(dir: string): Promise { + const files: string[] = []; + async function walk(rel: string) { + for await (const e of Deno.readDir(rel ? `${dir}/${rel}` : dir)) { + if ([".git", "node_modules", ".backslash-install.json"].includes(e.name)) continue; + const path = rel ? `${rel}/${e.name}` : e.name; + if (e.isSymlink) throw new Error(`${path} is a symbolic link`); + if (e.isDirectory) await walk(path); + else if (e.isFile) files.push(path); + } + } + await walk(""); + // Rust sorts strings by bytes; so does this comparison on UTF-8 bytes. + const enc = new TextEncoder(); + files.sort((a, b) => { + const [x, y] = [enc.encode(a), enc.encode(b)]; + for (let i = 0; i < Math.min(x.length, y.length); i++) if (x[i] !== y[i]) return x[i] - y[i]; + return x.length - y.length; + }); + const parts: Uint8Array[] = []; + for (const name of files) { + const content = await Deno.readFile(`${dir}/${name}`); + parts.push(enc.encode(name), new Uint8Array([0]), enc.encode(String(content.length)), new Uint8Array([0]), content); + } + const total = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); + let offset = 0; + for (const p of parts) { + total.set(p, offset); + offset += p.length; + } + const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", total)); + return "sha256:" + [...digest].map((b) => b.toString(16).padStart(2, "0")).join(""); +} + +/** Same lines as `backslash_core::approvals::requested`. */ +function requested(m: Manifest): string[] { + const p = m.permissions ?? {}; + const lines: string[] = []; + if (m.runtime?.kind === "exec") lines.push("Run as a regular program, without sandbox"); + for (const host of p.net ?? []) lines.push(host === "*" ? "Access any website" : `Access ${host}`); + for (const x of p.run ?? []) lines.push(`Run ${x}`); + for (const x of p.read ?? []) lines.push(`Read ${x}`); + for (const x of p.write ?? []) lines.push(`Write ${x}`); + for (const x of p.env ?? []) lines.push(`Read the environment variable ${x}`); + if (p["clipboard-read"]) lines.push("Read the clipboard"); + return lines; +} + +async function validate(file: string): Promise { + const errors: string[] = []; + const entry = parse(await Deno.readTextFile(file)) as unknown as Entry; + const id = file.split("/").pop()!.replace(/\.toml$/, ""); + if (entry.id !== id) errors.push(`id "${entry.id}" does not match the file name`); + if (!/^[a-z0-9-]+$/.test(entry.id ?? "")) errors.push("id must match [a-z0-9-]+"); + for (const field of ["title", "version", "repository", "commit", "checksum"] as const) { + if (!entry[field]) errors.push(`missing ${field}`); + } + if (!/^[0-9a-f]{40}$/.test(entry.commit ?? "")) errors.push("commit must be a full 40-character hash"); + if (!/^https:\/\//.test(entry.repository ?? "")) errors.push("repository must be an https URL"); + if (errors.length) return errors; + + const tmp = await Deno.makeTempDir(); + try { + await git(["init", "-q"], tmp); + await git(["remote", "add", "origin", entry.repository], tmp); + await git(["fetch", "-q", "--depth", "1", "origin", entry.commit], tmp) + .catch(() => git(["fetch", "-q", "origin"], tmp)); + await git(["-c", "advice.detachedHead=false", "checkout", "-q", entry.commit], tmp); + const dir = entry.path ? `${tmp}/${entry.path}` : tmp; + const manifest = parse(await Deno.readTextFile(`${dir}/backslash.toml`)) as unknown as Manifest; + if (manifest.extension.id !== entry.id) errors.push(`manifest id is "${manifest.extension.id}"`); + if ((manifest.extension.version ?? "0.0.0") !== entry.version) { + errors.push(`manifest version is ${manifest.extension.version}, entry says ${entry.version}`); + } + const asked = requested(manifest).sort(); + const listed = [...(entry.permissions ?? [])].sort(); + if (JSON.stringify(asked) !== JSON.stringify(listed)) { + errors.push(`permissions differ: the extension asks for ${JSON.stringify(asked)}`); + } + const sum = await checksum(dir); + if (sum !== entry.checksum) errors.push(`checksum is ${sum}`); + } catch (err) { + errors.push(err instanceof Error ? err.message : String(err)); + } finally { + await Deno.remove(tmp, { recursive: true }); + } + return errors; +} + +const files = Deno.args.length + ? Deno.args + : [...Deno.readDirSync("registry")].filter((e) => e.name.endsWith(".toml")).map((e) => `registry/${e.name}`).sort(); +let failed = false; +for (const file of files) { + const errors = await validate(file); + console.log(`${errors.length ? "โœ—" : "โœ“"} ${file}`); + for (const e of errors) console.log(` ${e}`); + failed ||= errors.length > 0; +} +if (failed) Deno.exit(1); diff --git a/system/index.js b/system/index.js deleted file mode 100644 index 96df42f..0000000 --- a/system/index.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - commands: { - 'sys-kill-process': require('./sys-kill-process'), - 'sys-reboot': require('./sys-reboot'), - 'sys-poweroff': require('./sys-poweroff'), - 'sys-set-volume-0': require('./sys-set-volume-0'), - 'sys-set-volume-25': require('./sys-set-volume-25'), - 'sys-set-volume-50': require('./sys-set-volume-50'), - 'sys-set-volume-75': require('./sys-set-volume-75'), - 'sys-set-volume-100': require('./sys-set-volume-100'), - }, -}; diff --git a/system/manifest.yml b/system/manifest.yml deleted file mode 100644 index 4b10e6a..0000000 --- a/system/manifest.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: system -label: System -version: 1.0.0 -author: Backslash - -commands: - - name: sys-kill-process - label: Kill process - isImmediate: false - bgColor: "#52525B" - color: "#FFFFFF" - icon: skull - keywords: - - system - - kill - - process - - - name: sys-reboot - label: Restart - isImmediate: true - bgColor: "#fab387" - color: "#1e1e2e" - icon: arrow-clockwise - keywords: - - system - - sys - - restart - - reboot - - - name: sys-poweroff - label: Shutdown - isImmediate: true - bgColor: "#f38ba8" - color: "#1e1e2e" - icon: power - keywords: - - system - - sys - - shutdown - - off - - poweroff - - - name: sys-set-volume-0 - label: Set volume to 0% - isImmediate: true - bgColor: "#52525B" - color: "#FFFFFF" - icon: ear - keywords: - - system - - set - - volume - - 0% - - - name: sys-set-volume-25 - label: Set volume to 25% - isImmediate: true - bgColor: "#52525B" - color: "#FFFFFF" - icon: ear - keywords: - - system - - set - - volume - - 25% - - - name: sys-set-volume-50 - label: Set volume to 50% - isImmediate: true - bgColor: "#52525B" - color: "#FFFFFF" - icon: ear - keywords: - - system - - set - - volume - - 50% - - - name: sys-set-volume-75 - label: Set volume to 75% - isImmediate: true - bgColor: "#52525B" - color: "#FFFFFF" - icon: ear - keywords: - - system - - set - - volume - - 75% - - - name: sys-set-volume-100 - label: Set volume to 100% - isImmediate: true - bgColor: "#52525B" - color: "#FFFFFF" - icon: ear - keywords: - - system - - set - - volume - - 100% diff --git a/system/sys-kill-process.js b/system/sys-kill-process.js deleted file mode 100644 index 4bf3a44..0000000 --- a/system/sys-kill-process.js +++ /dev/null @@ -1,132 +0,0 @@ -const run = async (query, { exec, path }) => { - const stdout = async () => { - return new Promise((resolve, reject) => { - exec("ps aux", (error, stdout, stderr) => { - if (error) return reject(`Error: ${stderr}`); - resolve(stdout); - }); - }); - }; - - const processesList = await stdout(); - - const processes = processesList - .split("\n") - .slice(1) - .filter(Boolean) - .map((line) => { - const parts = line.split(/\s+/); - const user = parts[0]; - const pid = parts[1]; - const cpuUsage = parseFloat(parts[2]); - const rssMemoryKB = parseInt(parts[5], 10); - const commandWithArgs = parts.slice(10).join(" "); - const commandName = path.basename(commandWithArgs.split(" ")[0]); - const memUsageMB = (rssMemoryKB / 1024).toFixed(1); - - return { - data: { - id: pid, - user, - pid, - cpuUsage, - memUsage: memUsageMB, - command: commandName, - fullCommand: commandWithArgs, - }, - - content: [ - { - type: "div", - className: "flex items-center w-full", - children: [ - { - type: "title", - content: commandName, - className: "flex-1", - }, - { - type: "div", - className: "flex items-center gap-4", - children: [ - { - type: "div", - className: "flex items-center gap-2", - children: [ - { - type: "icon", - className: "ph ph-identification-badge text-lg", - }, - { - type: "p", - content: pid, - className: "flex-1", - }, - ], - }, - { - type: "div", - className: "flex items-center gap-2", - children: [ - { - type: "icon", - className: "ph ph-cpu text-lg", - }, - { - type: "p", - content: `${cpuUsage}%`, - className: "flex-1", - }, - ], - }, - { - type: "div", - className: "flex items-center gap-2", - children: [ - { - type: "icon", - className: "ph ph-memory text-lg", - }, - { - type: "p", - content: `${memUsageMB} MB`, - className: "flex-1", - }, - ], - }, - ], - }, - ], - }, - ], - }; - }) - .sort((a, b) => { - const totalA = a.data.cpuUsage + parseFloat(a.data.memUsage); - const totalB = b.data.cpuUsage + parseFloat(b.data.memUsage); - return totalB - totalA; - }) - .slice(0, 10); - - const filteredProcesses = query - ? processes.filter((process) => - process.data.command.toLowerCase().includes(query.toLowerCase()) - ) - : processes; - - return filteredProcesses; -}; - -const killProcess = async (data, { exec }) => { - return new Promise((resolve, reject) => { - exec(`kill ${data.pid}`, (error, stdout, stderr) => { - if (error) return reject(`Error: ${stderr}`); - resolve(stdout); - }); - }); -}; - -module.exports = { - run, - actions: [{ name: "Kill process", action: killProcess }], -}; diff --git a/system/sys-poweroff.js b/system/sys-poweroff.js deleted file mode 100644 index fdcf607..0000000 --- a/system/sys-poweroff.js +++ /dev/null @@ -1,6 +0,0 @@ -const run = async (_, { exec }) => { - await exec("systemctl poweroff"); - }; - - module.exports = { run, actions: [] }; - \ No newline at end of file diff --git a/system/sys-reboot.js b/system/sys-reboot.js deleted file mode 100644 index bc84d5e..0000000 --- a/system/sys-reboot.js +++ /dev/null @@ -1,6 +0,0 @@ -const run = async (_, { exec }) => { - await exec("systemctl reboot"); - }; - - module.exports = { run, actions: [] }; - \ No newline at end of file diff --git a/system/sys-set-volume-0.js b/system/sys-set-volume-0.js deleted file mode 100644 index 8fd6fb4..0000000 --- a/system/sys-set-volume-0.js +++ /dev/null @@ -1,5 +0,0 @@ -const run = async (_, { exec }) => { - await exec("pactl set-sink-volume @DEFAULT_SINK@ 0%"); -}; - -module.exports = { run, actions: [] }; diff --git a/system/sys-set-volume-100.js b/system/sys-set-volume-100.js deleted file mode 100644 index 6963dde..0000000 --- a/system/sys-set-volume-100.js +++ /dev/null @@ -1,5 +0,0 @@ -const run = async (_, { exec }) => { - await exec("pactl set-sink-volume @DEFAULT_SINK@ 100%"); -}; - -module.exports = { run, actions: [] }; diff --git a/system/sys-set-volume-25.js b/system/sys-set-volume-25.js deleted file mode 100644 index 520f27b..0000000 --- a/system/sys-set-volume-25.js +++ /dev/null @@ -1,5 +0,0 @@ -const run = async (_, { exec }) => { - await exec("pactl set-sink-volume @DEFAULT_SINK@ 25%"); -}; - -module.exports = { run, actions: [] }; diff --git a/system/sys-set-volume-50.js b/system/sys-set-volume-50.js deleted file mode 100644 index 2af3a68..0000000 --- a/system/sys-set-volume-50.js +++ /dev/null @@ -1,5 +0,0 @@ -const run = async (_, { exec }) => { - await exec("pactl set-sink-volume @DEFAULT_SINK@ 50%"); -}; - -module.exports = { run, actions: [] }; diff --git a/system/sys-set-volume-75.js b/system/sys-set-volume-75.js deleted file mode 100644 index 3430ead..0000000 --- a/system/sys-set-volume-75.js +++ /dev/null @@ -1,5 +0,0 @@ -const run = async (_, { exec }) => { - await exec("pactl set-sink-volume @DEFAULT_SINK@ 75%"); -}; - -module.exports = { run, actions: [] }; From 6ad7f6b761e33c8e6f5900fcc1d242f33d77f7f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 18:21:14 +0000 Subject: [PATCH 2/2] Turn this repository into the extension registry registry/.toml entries point to an extension's own repository, pinned to a commit with a checksum and its permissions; Backslash installs exactly that and refuses anything else. CI re-verifies every entry. Entries for the first-party extensions, a publishing guide and review guidelines. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WzDMDJteorsmKRm6FVPX94 --- .github/workflows/check.yml | 22 ++++++ README.md | 136 +++++++++++++++++++++--------------- deno.lock | 26 +++++++ registry/docker.toml | 13 ++++ registry/emoji.toml | 13 ++++ registry/github.toml | 13 ++++ registry/packages.toml | 13 ++++ registry/systemd.toml | 13 ++++ scripts/update-entries.sh | 10 +++ 9 files changed, 204 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/check.yml create mode 100644 deno.lock create mode 100644 registry/docker.toml create mode 100644 registry/emoji.toml create mode 100644 registry/github.toml create mode 100644 registry/packages.toml create mode 100644 registry/systemd.toml create mode 100755 scripts/update-entries.sh diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..73dc672 --- /dev/null +++ b/.github/workflows/check.yml @@ -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 diff --git a/README.md b/README.md index 7cec216..bdf90f3 100644 --- a/README.md +++ b/README.md @@ -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/.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/.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 +``` diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..43d2894 --- /dev/null +++ b/deno.lock @@ -0,0 +1,26 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/collections@^1.1.3": "1.4.0", + "jsr:@std/toml@1": "1.0.11" + }, + "jsr": { + "@std/collections@1.4.0": { + "integrity": "bd7f74966e11de11b1f0420b7e8cb7bb381d8af7872b738443b92050db28c9c0" + }, + "@std/toml@1.0.11": { + "integrity": "e084988b872ca4bad6aedfb7350f6eeed0e8ba88e9ee5e1590621c5b5bb8f715", + "dependencies": [ + "jsr:@std/collections" + ] + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/toml@1" + ], + "links": { + "jsr:@backslash/api@0.1.0": {} + } + } +} diff --git a/registry/docker.toml b/registry/docker.toml new file mode 100644 index 0000000..0e392f3 --- /dev/null +++ b/registry/docker.toml @@ -0,0 +1,13 @@ +id = "docker" +title = "Containers" +description = "Docker and Podman containers: start, stop, logs" +author = "Backslash" +icon = "utilities-terminal" +version = "1.0.0" +repository = "https://github.com/backslash-app/plugins" +path = "extensions/docker" +commit = "a64f60074557298bf4cabf235c6e91e1f4e54082" +checksum = "sha256:7b338ac7fb63f6b846d10366f2c680dd62069ab442d46816b02430fd8ff0addc" +permissions = ["Run docker", "Run podman"] +commands = ["Containers"] + diff --git a/registry/emoji.toml b/registry/emoji.toml new file mode 100644 index 0000000..abc23bb --- /dev/null +++ b/registry/emoji.toml @@ -0,0 +1,13 @@ +id = "emoji" +title = "Emoji & Symbols" +description = "Search emoji and symbols by name and copy them" +author = "Backslash" +icon = "๐Ÿ˜€" +version = "1.0.0" +repository = "https://github.com/backslash-app/plugins" +path = "extensions/emoji" +commit = "a64f60074557298bf4cabf235c6e91e1f4e54082" +checksum = "sha256:fd9b92b103190fe973a4a048ec275371c4b187a4bc41365c1a7a4694f3d62992" +permissions = [] +commands = ["Search Emoji & Symbols"] + diff --git a/registry/github.toml b/registry/github.toml new file mode 100644 index 0000000..dbe31ca --- /dev/null +++ b/registry/github.toml @@ -0,0 +1,13 @@ +id = "github" +title = "GitHub" +description = "Search repositories and follow your pull requests" +author = "Backslash" +icon = "folder-remote" +version = "1.0.0" +repository = "https://github.com/backslash-app/plugins" +path = "extensions/github" +commit = "a64f60074557298bf4cabf235c6e91e1f4e54082" +checksum = "sha256:37f89a1ec235d4369ac134883ce1a6e9288ffa7e5f3e903ecaca127ee881b346" +permissions = ["Access api.github.com"] +commands = ["Search GitHub Repositories", "My Pull Requests"] + diff --git a/registry/packages.toml b/registry/packages.toml new file mode 100644 index 0000000..1955245 --- /dev/null +++ b/registry/packages.toml @@ -0,0 +1,13 @@ +id = "packages" +title = "Packages" +description = "Search your distribution's packages and Flathub" +author = "Backslash" +icon = "system-software-install" +version = "1.0.0" +repository = "https://github.com/backslash-app/plugins" +path = "extensions/packages" +commit = "a64f60074557298bf4cabf235c6e91e1f4e54082" +checksum = "sha256:b16dfa71c1501ab138039585387b2d1e8d05d65129b18845e17316606990571d" +permissions = ["Run apt-cache", "Run dnf", "Run pacman", "Run zypper", "Run flatpak"] +commands = ["Search Packages"] + diff --git a/registry/systemd.toml b/registry/systemd.toml new file mode 100644 index 0000000..f4f2010 --- /dev/null +++ b/registry/systemd.toml @@ -0,0 +1,13 @@ +id = "systemd" +title = "Services" +description = "Start, stop and inspect systemd services" +author = "Backslash" +icon = "preferences-system" +version = "1.0.0" +repository = "https://github.com/backslash-app/plugins" +path = "extensions/systemd" +commit = "a64f60074557298bf4cabf235c6e91e1f4e54082" +checksum = "sha256:9234b202cf96d9a49fe707f2c2048420cbb6c380a67acb40c7377805a5b06b83" +permissions = ["Run systemctl", "Run journalctl"] +commands = ["Services"] + diff --git a/scripts/update-entries.sh b/scripts/update-entries.sh new file mode 100755 index 0000000..ea4088f --- /dev/null +++ b/scripts/update-entries.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# Regenerates the registry entries of the first-party extensions from the +# current commit. Run after merging changes to extensions/, then commit. +set -eu +cd "$(dirname "$0")/.." +for dir in extensions/*/; do + id=$(basename "$dir") + backslash publish "$dir" | sed '1d' > "registry/$id.toml" + echo "registry/$id.toml" +done