From d642aa64a02ac7a7ddcd84fac3c71d8b4e771a4b Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Mon, 12 Feb 2024 10:05:19 -0800 Subject: notes: move old notes into the 2023 folder without breaking links Signed-off-by: Xe Iaso --- .vscode/settings.json | 3 + lume/src/notes/2023/_data.yml | 5 + lume/src/notes/2023/cursorless-alien-magic.mdx | 134 ++++++++++++++++++++ .../notes/2023/recover-github-action-secret.mdx | 62 ++++++++++ lume/src/notes/2023/talon-lists.mdx | 137 +++++++++++++++++++++ lume/src/notes/2023/vscode-go-ext.mdx | 28 +++++ lume/src/notes/cursorless-alien-magic.mdx | 133 -------------------- lume/src/notes/recover-github-action-secret.mdx | 61 --------- lume/src/notes/talon-lists.mdx | 136 -------------------- lume/src/notes/vscode-go-ext.mdx | 27 ---- pb/xesite.proto | 1 + 11 files changed, 370 insertions(+), 357 deletions(-) create mode 100644 lume/src/notes/2023/_data.yml create mode 100644 lume/src/notes/2023/cursorless-alien-magic.mdx create mode 100644 lume/src/notes/2023/recover-github-action-secret.mdx create mode 100644 lume/src/notes/2023/talon-lists.mdx create mode 100644 lume/src/notes/2023/vscode-go-ext.mdx delete mode 100644 lume/src/notes/cursorless-alien-magic.mdx delete mode 100644 lume/src/notes/recover-github-action-secret.mdx delete mode 100644 lume/src/notes/talon-lists.mdx delete mode 100644 lume/src/notes/vscode-go-ext.mdx diff --git a/.vscode/settings.json b/.vscode/settings.json index 14c59a7..74b5c0f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,5 +29,8 @@ ], "[markdown][mdx][nunjucks]": { "editor.wordBasedSuggestions": "off" + }, + "protoc": { + "options": ["--proto_path=pb", "--proto_path=internal/adminpb"] } } diff --git a/lume/src/notes/2023/_data.yml b/lume/src/notes/2023/_data.yml new file mode 100644 index 0000000..a458d74 --- /dev/null +++ b/lume/src/notes/2023/_data.yml @@ -0,0 +1,5 @@ +layout: blog.njk +type: blog +index: true +is_note: true +year: 2023 diff --git a/lume/src/notes/2023/cursorless-alien-magic.mdx b/lume/src/notes/2023/cursorless-alien-magic.mdx new file mode 100644 index 0000000..966a438 --- /dev/null +++ b/lume/src/notes/2023/cursorless-alien-magic.mdx @@ -0,0 +1,134 @@ +--- +title: "Cursorless is alien magic from the future" +date: 2023-11-09 +tags: ["cursorless", "vscode"] +basename: ../../notes/cursorless-alien-magic +--- + +Just in time for me to start a new job at a new place, my RSI has decided to flare up. + + + For the record, I'm fine, I've known this has been coming for a while. The + flare-up is on its exit anyways. I've gotten lucky and I'm going to be fine. + But it's still a bit of a bummer. + + +The last time this happened, I was able to get by by doing mostly writing about technology things, but I think I'm going to need to be able to program again. I know I'm a bit of an emacs user, but for this I've been using visual studio code because of one extension in particular: [Cursorless](https://marketplace.visualstudio.com/items?itemName=pokey.cursorless). + +Cursorless is a plugin that integrates with voice control software to let you do AST level code editing with your voice. This is crazy alien magic from the future. + +I've talked about cursorless before on my blog, but I have decided to really get deep into it this time around. The last time I used it, I didn't actually use it for much more than moving around the screen, but this time I'm going to try to use it for everything. + +I wish I had this as an input method for slack and discord messages. + +The most magic parts about this are the ideas of destinations and targets when it comes to cursorless inputs. Targets are individual anchors in a document and destinations are places relative to individual targets. Every single token in a document is given a hat over a letter with a color. These hats act as anchors that let you give commands based off of locations, destinations, and paths between them. Here's a simple example. Consider this code: + +```js +function fetchBlog() { + fetch("https://xeiaso.net/blog.json") + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }) + .then((data) => console.log(data)) + .catch((error) => console.error("Error:", error)); +} +``` + +This is a fairly standard looking JavaScript function. But, cursorless puts a bunch of hats over all of the code so it may look something like this: + + + + + So that's why your editor is full of random video artifacts? + + + + They're not artifacts, they're targets! + + +Take a good look at that picture again: + + + +The hats are color coded above individual letters. The position tells you the name and the color tells you how to disambiguate it. For example, That word `function` would be referred to as `green urge` because the hat is green over the letter u. If I wanted to delete that word for some reason or if I wanted to move it somewhere else, I could use `green urge` as the target for that action. + +By itself, this gives you some pretty powerful actions and effectively lets you do spoken vim motions. But, that is only thinking in terms of simple actions that you can do with your editor. The real power of cursorless comes in from not only the idea of paths (such as `green urge past green bat` to select the `function fetchBlog` in that screenshot), but the fact that cursorless knows what the AST of the language is doing. This means that you can do things across the entire function, like deleting it or moving it somewhere else. As an example, here are the lambdas of this function visualized separately (with the "visualize lambdas" command): + + + +These AST units are also targets. This means that I can do things like select the body of a definition and then work off of that. So if I want to refactor this into an asynchronous function, the refactoring becomes trivial: + +![a gif of doing the process of refactoring a synchronous function to an async function by writing it all using talon commands](https://cdn.xeiaso.net/file/christine-static/blog/2023/cl-note/async-refactor.gif) + + + That's pretty cool, but I don't think I'd be able to remember all of those + commands. + + + + After a while, they just become second nature like Vim commands do. I have + been forcing myself to use this over and over again for the past few days and + it's starting to become second nature. I'm introducing individual commands at + one of the time and building up into bigger and better things. + + +The real magic comes when you start writing your own commands with the full power of Cursorless and Talon. In that example I just showed you, I have a action for inserting `"async "` before the function definition. Here is the code for that: + +``` +[state] async : + user.cursorless_insert(cursorless_destination, "async") +``` + +You can break talon commands into two basic parts: patterns and captures. Patterns are the spoken words that you say and captures are the things that you want to extract out of what you say. In this case, the pattern is just the word `async` and the capture is the destination that you want to insert the word `async` before. The `` capture is a special capture lets you specify if you want something before or after a target. Of course, this is just a very simple example and it can get way more intricate than this. + +Here is the most complicated Talon rule I've written so far: + +``` +(method|meth) [] [] [over] [] named [over]: + user.go_method(go_pointer or "", letter, go_visibility_1 or "public", text_1, go_visibility_2 or "public", text_2) +``` + + + What the heck is going on there? + + +This looks like a lot, but it is actually really simple. This lets you declare a method in Go. In Go, a method looks like this: + +```go +func (reciever *Type) MethodName() { + // function body here or something +} +``` + +Pedantically, Go doesn't have methods in the traditional sense, it just has functions that take structs as the receiver (read: a hidden first argument but in a way that is namespaced to that struct in particular). Without something to automate writing this for you, you would have to say something like this: + +> state funk args word reciever space star hammer type over go right space hammer method name args go right brack enter + +That's a lot of words to say. But, with this talon rule, you can just say: + +> meth r raised type named method name over + + + That's still a lot of words. + + + + Well, yes, you're not going to be able to get over that. But this is at least + more efficient and it makes more sense. I'm not going to be able to get rid of + all of the words, but I can at least make it so that it's close to how I + conceptualize it in my head. + + + + I guess that makes sense, but what do you mean by raised? I don't think go has + raised types I know it has pointers but not "raising". What is raising? + + +I'm glad you asked! This is something that I'm experimenting with to try to find a different way to explain the concept of pointers in Go. I think that one of the oversights in the Go language is that pointers use C-style syntax. This specifically has you use an `*` to lower a value from a pointer value to a normal value and `&` to raise the value from a normal value into a pointer value. + +Since I'm taking the opportunity to radically redesign the Talon bindings for Go, I want to try unifying the syntax of pointer values into the idea of raising and lowering to see how it makes it easier to understand Go programs. I don't know if this is a good idea, but you have to fuck around in order to find out. + +Maybe some parts of our industry are actually good. I really hope that I get led into the [GitHub copilot voice beta](https://githubnext.com/projects/copilot-voice/) soon, I want to compare how Talon does voice coding versus how copilot voice does it. diff --git a/lume/src/notes/2023/recover-github-action-secret.mdx b/lume/src/notes/2023/recover-github-action-secret.mdx new file mode 100644 index 0000000..385ec2f --- /dev/null +++ b/lume/src/notes/2023/recover-github-action-secret.mdx @@ -0,0 +1,62 @@ +--- +title: "How to recover a GitHub Actions secret" +date: 2023-11-02 +tags: + - github + - actions + - secrets + - tailscale +basename: ../../notes/recover-github-action-secret +--- + +Sometimes you fuck up and lose your only copy of a GitHub secret that you can't replace easily, such as a [Cachix](https://www.cachix.org/) signing key. However you lucked out and that key is actually saved in GitHub Actions secrets...which won't let you read the contents of that secret for understandable security reasons. Here's how you work around that. + +First, make sure [Deno](https://deno.land) is installed and copy this program to `recover-secret.ts`. + +```ts +const port = 8080; + +const handler = async (req: Request): Promise => { + const body = await req.text(); + console.log(body); + + return new Response(); +}; + +console.log(`HTTP server running. Access it at: http://localhost:${port}/`); +Deno.serve({ port }, handler); +``` + +Run it with `deno run -A recover-secret.ts`. + +Next go to the [Tailscale admin console OAuth clients section](https://login.tailscale.com/admin/settings/oauth) and generate a new OAuth client that lets you create auth keys (you want the write on devices scope). Add a helpful description like "GitHub Actions secret recovery" and copy the client ID and secret to your password manager. Then add them as GitHub secrets named `TAILSCALE_CLIENT_ID` and `TAILSCALE_CLIENT_SECRET`. + +Now create a new GitHub Actions workflow with the following contents: + +```yaml +on: + workflow_dispatch: + +jobs: + recoversecret: + runs-on: ubuntu-latest + steps: + - name: Tailscale + uses: tailscale/github-action@v2 + with: + oauth-client-id: ${{ secrets.TAILSCALE_CLIENT_ID }} + oauth-secret: ${{ secrets.TAILSCALE_CLIENT_SECRET }} + tags: tag:ci + version: 1.52.0 + - name: "Recover secret" + run: | + echo ${SECRET} > ./output.txt + curl --data-binary @./output.txt ${TARGET} + env: + SECRET: ${{ secrets.CACHIX_SIGNING_KEY }} + TARGET: "http://kaine.shark-harmonic.ts.net:8080" +``` + +Replace the contents of `TARGET` as facts and circumstances demand. + +Now you can recover your secret by hitting the "Run workflow" button on the Actions tab of your repo. The secret will be in your terminal, and you can copy it to your password manager as a note. diff --git a/lume/src/notes/2023/talon-lists.mdx b/lume/src/notes/2023/talon-lists.mdx new file mode 100644 index 0000000..56456d9 --- /dev/null +++ b/lume/src/notes/2023/talon-lists.mdx @@ -0,0 +1,137 @@ +--- +title: "How to use lists in Talon" +date: 2023-11-11 +tags: [talon, lists] +basename: ../../notes/talon-lists +--- + +When you are making commands in Talon, sometimes you need to have a of things that you say which are turned into things that the computer understands.There are a few ways to implement this but it is easy to do it very wrong. + +The naive way to implement this is to make a bunch of little commands. For example, let's say that I was trying to implement a command to insert the conversation snippets for my blog. The naive way would be to do something like this: + +``` +blog conversation mara hacker [ [over]]: + insert('') + insert("\n") + insert(text or "") + insert("\n") + insert("") + +blog conversation mara happy [ [over]]: + insert('') + insert("\n") + insert(text or "") + insert("\n") + insert("") + +# etc... +``` + +This would work, but I would have to implement a bunch of different nearly identical snippets. This would be easy to make exhaustive across all of the possible combinations of names and moods. But this is very not ideal from a maintainability standpoint. If I wanted to change the format of the snippet or add additional characteristics, I would have to change it in potentially 27 places. + +There are a couple other options that I can use here though. They are custom captures and lists. Both of them are valid ways to solve this problem, But I think that lists are the way that I'm going to do it in my implementation. + +## Custom Captures + +Let's take a look at the syntax of Talon commands again. At a high level, a command is a spoken phrase with elements that are captured out of it. For example, if you wanted to write a command that would type out some text, you would do something like this: + +``` +insert [over]: + insert(text) +``` + +This command would take whatever you said after the word insert and type it out. The `user.text` inside the angle brackets is the captured variable. It's put into the variable named text in your talon code. This command would also optionally let you terminate the string of text with the word "over". This can let you stack multiple commands into the same utterance. For example, consider a command like this: + +> slog debug unexpected filesystem error go left comma double quote second go left error go right comma error + +This command would type out the following: + +```go +slog.Debug("unexpected filesystem error", "err", err) +``` + +Once you really learn how to stack commands like this, you really understand the flow model of something like Talon. It allows you to have a lot of power when you're doing things. But, the real important part is when you declare your own custom captures that are used in context with these elidable terminators. + +Let's take a look at the syntax for a custom capture: + +```py +@mod.capture(rule="(pointer | raised)") +def go_pointer(m: str) -> str: + return "*" if "pointer" or "raised" in m else "" +``` + +A capture is just a Python function that takes a string and returns some data. The `@mod.capture` decorator tells Talon that this is a capture. The `rule` parameter defines the patterns that this capture will meet. For example, this capture returns `"*"` if it matches the strings `"pointer"` and `"raised"`. This allows you to define your own custom logic for capturing values from speech. + +For many cases, this will work perfectly. Sometimes you need something a bit simpler than arbitrary Python code. Sometimes you just have a list of things to say versus things the computer should understand. Such as that giant list of conversation moods and blog characters. For that, we can use a list. + +## Lists + +Lists are a different type of capture that allow you to have spoken forms turn into machine forms. They are defined differently from custom captures because they are a lot simpler. Here's an example list: + +```py +from talon import Context, Module, actions + + +mod = Module() +mod.list("xesite_blog_character", desc="Characters for xesite blog conversations") + + +ctx = Context() +ctx.matches = r""" +app: vscode +""" +ctx.lists["user.xesite_blog_character"] = { + "owie": "Aoi", + "mara": "Mara", + "katie": "Cadey", + "numa": "Numa", + "mini": "Mimi", + "scoots": "Scoots", +} +``` + +So when I say "owie", the list would interpret that as Aoi. lists have their own capture format too: + +``` +blog conversation {user.xesite_blog_character} {user.xesite_blog_mood} [ [over]]: + insert('') + insert("\n") + insert(text or "") + insert("\n") + insert("") +``` + +This would let me say something like "blog conversation owie grin this is some text" and it would insert the following: + + + This is some text. + + +But, we can go one step further. Right now, this would have you define your list in Python code. This does work. But its a bit much to go in and modify Python code in order to change something. Given that lists are a first class concept in Talon, there is a way to express them more natively: using `.talon-list` files. Here's what the list of characters would look like in a `.talon-list` file: + +``` +list: user.xesite_blog_character +app: vscode +- +owie: Aoi +mara: Mara +katie: Cadey +numa: Numa +mini: Mimi +scoots: Scoots +``` + +This is a lot easier to read, understand, and modify by people that don't understand Python, but need to use Talon anyways. + + +You need to be in the Patreon Beta in order to use `.talon-list` files. If you are not in the Patreon Beta, you can use the Python code version of this. They are functionally identical. + +If you're using Talon more seriously, you should be in the Patreon Beta anyways. The public version hasn't had updates for a while and is missing a lot of features that have been added in the beta. Plus, it helps make sure that the Talon developers are able to... well... eat. Ain't no such thing as a free lunch. + + + +## Conclusion + +Talon is really cool and a lot of the really cool parts are tragically under-documented I am just barely scratching the surface here, but this is documenting what I am figuring out as I dig more deeply into Talon. I hope this is useful to someone. + +If you want to see the Talon bindings I've created for my blog characters, you can look [in my `Xe/invocations` repo](https://github.com/Xe/invocations/tree/main/apps/vscode). I'm still working on them, but they're a good starting point for anyone that wants to do something similar. diff --git a/lume/src/notes/2023/vscode-go-ext.mdx b/lume/src/notes/2023/vscode-go-ext.mdx new file mode 100644 index 0000000..e12c845 --- /dev/null +++ b/lume/src/notes/2023/vscode-go-ext.mdx @@ -0,0 +1,28 @@ +--- +title: "Why does VSCode keep uninstalling the Go extension?" +date: 2023-11-15 +hero: + ai: Counterfeit-XL + file: galaxy-waifu + prompt: A green-haired anime woman with cyberpunk style clothing drinking coffee in a cyberpunk space station +basename: ../vscode-go-ext +--- + +When I write these articles, I like having an answer to the question at hand before I start writing. I don't feel like it is fair to readers to have a question without an answer. + +This isn't a normal article. This is a cry for help. I don't know how to google for this and tools of last resort are failing me. + +I use VSCode on my macbook and gaming PC. Both have extension syncing enabled to make my life easier. The [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) keeps randomly getting uninstalled on every machine. I have no idea why it does this. I have no idea how to stop it from doing this. I have no idea how to debug it. + +What the fuck am I doing wrong? + +Here are all of the steps I've tried: + +- Removing settings sync and re-enabling it on all affected machines +- Looking through system logs around the time of the removal happening +- Starting from scratch on a new machine (my new work computer) and then syncing settings to find that the Go extension is not being synced +- Marking the Go extension as "do not sync" and then marking it as "do sync" + +If anyone is able to figure out what I could have possibly fucked up, I will post the solution here and credit them. + +I hope it's something really stupid. diff --git a/lume/src/notes/cursorless-alien-magic.mdx b/lume/src/notes/cursorless-alien-magic.mdx deleted file mode 100644 index 8bb80b9..0000000 --- a/lume/src/notes/cursorless-alien-magic.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: "Cursorless is alien magic from the future" -date: 2023-11-09 -tags: ["cursorless", "vscode"] ---- - -Just in time for me to start a new job at a new place, my RSI has decided to flare up. - - - For the record, I'm fine, I've known this has been coming for a while. The - flare-up is on its exit anyways. I've gotten lucky and I'm going to be fine. - But it's still a bit of a bummer. - - -The last time this happened, I was able to get by by doing mostly writing about technology things, but I think I'm going to need to be able to program again. I know I'm a bit of an emacs user, but for this I've been using visual studio code because of one extension in particular: [Cursorless](https://marketplace.visualstudio.com/items?itemName=pokey.cursorless). - -Cursorless is a plugin that integrates with voice control software to let you do AST level code editing with your voice. This is crazy alien magic from the future. - -I've talked about cursorless before on my blog, but I have decided to really get deep into it this time around. The last time I used it, I didn't actually use it for much more than moving around the screen, but this time I'm going to try to use it for everything. - -I wish I had this as an input method for slack and discord messages. - -The most magic parts about this are the ideas of destinations and targets when it comes to cursorless inputs. Targets are individual anchors in a document and destinations are places relative to individual targets. Every single token in a document is given a hat over a letter with a color. These hats act as anchors that let you give commands based off of locations, destinations, and paths between them. Here's a simple example. Consider this code: - -```js -function fetchBlog() { - fetch("https://xeiaso.net/blog.json") - .then((response) => { - if (!response.ok) { - throw new Error("Network response was not ok"); - } - return response.json(); - }) - .then((data) => console.log(data)) - .catch((error) => console.error("Error:", error)); -} -``` - -This is a fairly standard looking JavaScript function. But, cursorless puts a bunch of hats over all of the code so it may look something like this: - - - - - So that's why your editor is full of random video artifacts? - - - - They're not artifacts, they're targets! - - -Take a good look at that picture again: - - - -The hats are color coded above individual letters. The position tells you the name and the color tells you how to disambiguate it. For example, That word `function` would be referred to as `green urge` because the hat is green over the letter u. If I wanted to delete that word for some reason or if I wanted to move it somewhere else, I could use `green urge` as the target for that action. - -By itself, this gives you some pretty powerful actions and effectively lets you do spoken vim motions. But, that is only thinking in terms of simple actions that you can do with your editor. The real power of cursorless comes in from not only the idea of paths (such as `green urge past green bat` to select the `function fetchBlog` in that screenshot), but the fact that cursorless knows what the AST of the language is doing. This means that you can do things across the entire function, like deleting it or moving it somewhere else. As an example, here are the lambdas of this function visualized separately (with the "visualize lambdas" command): - - - -These AST units are also targets. This means that I can do things like select the body of a definition and then work off of that. So if I want to refactor this into an asynchronous function, the refactoring becomes trivial: - -![a gif of doing the process of refactoring a synchronous function to an async function by writing it all using talon commands](https://cdn.xeiaso.net/file/christine-static/blog/2023/cl-note/async-refactor.gif) - - - That's pretty cool, but I don't think I'd be able to remember all of those - commands. - - - - After a while, they just become second nature like Vim commands do. I have - been forcing myself to use this over and over again for the past few days and - it's starting to become second nature. I'm introducing individual commands at - one of the time and building up into bigger and better things. - - -The real magic comes when you start writing your own commands with the full power of Cursorless and Talon. In that example I just showed you, I have a action for inserting `"async "` before the function definition. Here is the code for that: - -``` -[state] async : - user.cursorless_insert(cursorless_destination, "async") -``` - -You can break talon commands into two basic parts: patterns and captures. Patterns are the spoken words that you say and captures are the things that you want to extract out of what you say. In this case, the pattern is just the word `async` and the capture is the destination that you want to insert the word `async` before. The `` capture is a special capture lets you specify if you want something before or after a target. Of course, this is just a very simple example and it can get way more intricate than this. - -Here is the most complicated Talon rule I've written so far: - -``` -(method|meth) [] [] [over] [] named [over]: - user.go_method(go_pointer or "", letter, go_visibility_1 or "public", text_1, go_visibility_2 or "public", text_2) -``` - - - What the heck is going on there? - - -This looks like a lot, but it is actually really simple. This lets you declare a method in Go. In Go, a method looks like this: - -```go -func (reciever *Type) MethodName() { - // function body here or something -} -``` - -Pedantically, Go doesn't have methods in the traditional sense, it just has functions that take structs as the receiver (read: a hidden first argument but in a way that is namespaced to that struct in particular). Without something to automate writing this for you, you would have to say something like this: - -> state funk args word reciever space star hammer type over go right space hammer method name args go right brack enter - -That's a lot of words to say. But, with this talon rule, you can just say: - -> meth r raised type named method name over - - - That's still a lot of words. - - - - Well, yes, you're not going to be able to get over that. But this is at least - more efficient and it makes more sense. I'm not going to be able to get rid of - all of the words, but I can at least make it so that it's close to how I - conceptualize it in my head. - - - - I guess that makes sense, but what do you mean by raised? I don't think go has - raised types I know it has pointers but not "raising". What is raising? - - -I'm glad you asked! This is something that I'm experimenting with to try to find a different way to explain the concept of pointers in Go. I think that one of the oversights in the Go language is that pointers use C-style syntax. This specifically has you use an `*` to lower a value from a pointer value to a normal value and `&` to raise the value from a normal value into a pointer value. - -Since I'm taking the opportunity to radically redesign the Talon bindings for Go, I want to try unifying the syntax of pointer values into the idea of raising and lowering to see how it makes it easier to understand Go programs. I don't know if this is a good idea, but you have to fuck around in order to find out. - -Maybe some parts of our industry are actually good. I really hope that I get led into the [GitHub copilot voice beta](https://githubnext.com/projects/copilot-voice/) soon, I want to compare how Talon does voice coding versus how copilot voice does it. \ No newline at end of file diff --git a/lume/src/notes/recover-github-action-secret.mdx b/lume/src/notes/recover-github-action-secret.mdx deleted file mode 100644 index 1c0c825..0000000 --- a/lume/src/notes/recover-github-action-secret.mdx +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: "How to recover a GitHub Actions secret" -date: 2023-11-02 -tags: - - github - - actions - - secrets - - tailscale ---- - -Sometimes you fuck up and lose your only copy of a GitHub secret that you can't replace easily, such as a [Cachix](https://www.cachix.org/) signing key. However you lucked out and that key is actually saved in GitHub Actions secrets...which won't let you read the contents of that secret for understandable security reasons. Here's how you work around that. - -First, make sure [Deno](https://deno.land) is installed and copy this program to `recover-secret.ts`. - -```ts -const port = 8080; - -const handler = async (req: Request): Promise => { - const body = (await req.text()); - console.log(body); - - return new Response() -}; - -console.log(`HTTP server running. Access it at: http://localhost:${port}/`); -Deno.serve({ port }, handler); -``` - -Run it with `deno run -A recover-secret.ts`. - -Next go to the [Tailscale admin console OAuth clients section](https://login.tailscale.com/admin/settings/oauth) and generate a new OAuth client that lets you create auth keys (you want the write on devices scope). Add a helpful description like "GitHub Actions secret recovery" and copy the client ID and secret to your password manager. Then add them as GitHub secrets named `TAILSCALE_CLIENT_ID` and `TAILSCALE_CLIENT_SECRET`. - -Now create a new GitHub Actions workflow with the following contents: - -```yaml -on: - workflow_dispatch: - -jobs: - recoversecret: - runs-on: ubuntu-latest - steps: - - name: Tailscale - uses: tailscale/github-action@v2 - with: - oauth-client-id: ${{ secrets.TAILSCALE_CLIENT_ID }} - oauth-secret: ${{ secrets.TAILSCALE_CLIENT_SECRET }} - tags: tag:ci - version: 1.52.0 - - name: "Recover secret" - run: | - echo ${SECRET} > ./output.txt - curl --data-binary @./output.txt ${TARGET} - env: - SECRET: ${{ secrets.CACHIX_SIGNING_KEY }} - TARGET: "http://kaine.shark-harmonic.ts.net:8080" -``` - -Replace the contents of `TARGET` as facts and circumstances demand. - -Now you can recover your secret by hitting the "Run workflow" button on the Actions tab of your repo. The secret will be in your terminal, and you can copy it to your password manager as a note. \ No newline at end of file diff --git a/lume/src/notes/talon-lists.mdx b/lume/src/notes/talon-lists.mdx deleted file mode 100644 index f9cebaa..0000000 --- a/lume/src/notes/talon-lists.mdx +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: "How to use lists in Talon" -date: 2023-11-11 -tags: [talon, lists] ---- - -When you are making commands in Talon, sometimes you need to have a of things that you say which are turned into things that the computer understands.There are a few ways to implement this but it is easy to do it very wrong. - -The naive way to implement this is to make a bunch of little commands. For example, let's say that I was trying to implement a command to insert the conversation snippets for my blog. The naive way would be to do something like this: - -``` -blog conversation mara hacker [ [over]]: - insert('') - insert("\n") - insert(text or "") - insert("\n") - insert("") - -blog conversation mara happy [ [over]]: - insert('') - insert("\n") - insert(text or "") - insert("\n") - insert("") - -# etc... -``` - -This would work, but I would have to implement a bunch of different nearly identical snippets. This would be easy to make exhaustive across all of the possible combinations of names and moods. But this is very not ideal from a maintainability standpoint. If I wanted to change the format of the snippet or add additional characteristics, I would have to change it in potentially 27 places. - -There are a couple other options that I can use here though. They are custom captures and lists. Both of them are valid ways to solve this problem, But I think that lists are the way that I'm going to do it in my implementation. - -## Custom Captures - -Let's take a look at the syntax of Talon commands again. At a high level, a command is a spoken phrase with elements that are captured out of it. For example, if you wanted to write a command that would type out some text, you would do something like this: - -``` -insert [over]: - insert(text) -``` - -This command would take whatever you said after the word insert and type it out. The `user.text` inside the angle brackets is the captured variable. It's put into the variable named text in your talon code. This command would also optionally let you terminate the string of text with the word "over". This can let you stack multiple commands into the same utterance. For example, consider a command like this: - -> slog debug unexpected filesystem error go left comma double quote second go left error go right comma error - -This command would type out the following: - -```go -slog.Debug("unexpected filesystem error", "err", err) -``` - -Once you really learn how to stack commands like this, you really understand the flow model of something like Talon. It allows you to have a lot of power when you're doing things. But, the real important part is when you declare your own custom captures that are used in context with these elidable terminators. - -Let's take a look at the syntax for a custom capture: - -```py -@mod.capture(rule="(pointer | raised)") -def go_pointer(m: str) -> str: - return "*" if "pointer" or "raised" in m else "" -``` - -A capture is just a Python function that takes a string and returns some data. The `@mod.capture` decorator tells Talon that this is a capture. The `rule` parameter defines the patterns that this capture will meet. For example, this capture returns `"*"` if it matches the strings `"pointer"` and `"raised"`. This allows you to define your own custom logic for capturing values from speech. - -For many cases, this will work perfectly. Sometimes you need something a bit simpler than arbitrary Python code. Sometimes you just have a list of things to say versus things the computer should understand. Such as that giant list of conversation moods and blog characters. For that, we can use a list. - -## Lists - -Lists are a different type of capture that allow you to have spoken forms turn into machine forms. They are defined differently from custom captures because they are a lot simpler. Here's an example list: - -```py -from talon import Context, Module, actions - - -mod = Module() -mod.list("xesite_blog_character", desc="Characters for xesite blog conversations") - - -ctx = Context() -ctx.matches = r""" -app: vscode -""" -ctx.lists["user.xesite_blog_character"] = { - "owie": "Aoi", - "mara": "Mara", - "katie": "Cadey", - "numa": "Numa", - "mini": "Mimi", - "scoots": "Scoots", -} -``` - -So when I say "owie", the list would interpret that as Aoi. lists have their own capture format too: - -``` -blog conversation {user.xesite_blog_character} {user.xesite_blog_mood} [ [over]]: - insert('') - insert("\n") - insert(text or "") - insert("\n") - insert("") -``` - -This would let me say something like "blog conversation owie grin this is some text" and it would insert the following: - - - This is some text. - - -But, we can go one step further. Right now, this would have you define your list in Python code. This does work. But its a bit much to go in and modify Python code in order to change something. Given that lists are a first class concept in Talon, there is a way to express them more natively: using `.talon-list` files. Here's what the list of characters would look like in a `.talon-list` file: - -``` -list: user.xesite_blog_character -app: vscode -- -owie: Aoi -mara: Mara -katie: Cadey -numa: Numa -mini: Mimi -scoots: Scoots -``` - -This is a lot easier to read, understand, and modify by people that don't understand Python, but need to use Talon anyways. - - -You need to be in the Patreon Beta in order to use `.talon-list` files. If you are not in the Patreon Beta, you can use the Python code version of this. They are functionally identical. - -If you're using Talon more seriously, you should be in the Patreon Beta anyways. The public version hasn't had updates for a while and is missing a lot of features that have been added in the beta. Plus, it helps make sure that the Talon developers are able to... well... eat. Ain't no such thing as a free lunch. - - - -## Conclusion - -Talon is really cool and a lot of the really cool parts are tragically under-documented I am just barely scratching the surface here, but this is documenting what I am figuring out as I dig more deeply into Talon. I hope this is useful to someone. - -If you want to see the Talon bindings I've created for my blog characters, you can look [in my `Xe/invocations` repo](https://github.com/Xe/invocations/tree/main/apps/vscode). I'm still working on them, but they're a good starting point for anyone that wants to do something similar. \ No newline at end of file diff --git a/lume/src/notes/vscode-go-ext.mdx b/lume/src/notes/vscode-go-ext.mdx deleted file mode 100644 index 6735bd8..0000000 --- a/lume/src/notes/vscode-go-ext.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Why does VSCode keep uninstalling the Go extension?" -date: 2023-11-15 -hero: - ai: Counterfeit-XL - file: galaxy-waifu - prompt: A green-haired anime woman with cyberpunk style clothing drinking coffee in a cyberpunk space station ---- - -When I write these articles, I like having an answer to the question at hand before I start writing. I don't feel like it is fair to readers to have a question without an answer. - -This isn't a normal article. This is a cry for help. I don't know how to google for this and tools of last resort are failing me. - -I use VSCode on my macbook and gaming PC. Both have extension syncing enabled to make my life easier. The [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) keeps randomly getting uninstalled on every machine. I have no idea why it does this. I have no idea how to stop it from doing this. I have no idea how to debug it. - -What the fuck am I doing wrong? - -Here are all of the steps I've tried: - -* Removing settings sync and re-enabling it on all affected machines -* Looking through system logs around the time of the removal happening -* Starting from scratch on a new machine (my new work computer) and then syncing settings to find that the Go extension is not being synced -* Marking the Go extension as "do not sync" and then marking it as "do sync" - -If anyone is able to figure out what I could have possibly fucked up, I will post the solution here and credit them. - -I hope it's something really stupid. diff --git a/pb/xesite.proto b/pb/xesite.proto index 6ced6fa..9dbaabf 100644 --- a/pb/xesite.proto +++ b/pb/xesite.proto @@ -12,4 +12,5 @@ message BuildInfo { google.protobuf.Timestamp build_time = 2; string go_version = 3; string deno_version = 4; + string xesite_version = 5; } \ No newline at end of file -- cgit v1.2.3