Side Project Podcast: Aaron Francis

December 23, 2024

Aaron Francis hops on to go full galaxy brain on his interactive TUI Solo. 00:00 - Introduction to Side Projects and Aaron Francis 01:05 - Exploring the Laravel Package: Solo 03:58 - The Development Journey of Solo 07:38 - Understanding ANSI Codes and Terminal Manipulation 11:36 - The Challenges of Process Management 15:14 - Enhancements and Future Directions for Solo 22:26 - Customizing Development Environments 25:14 - Enhanced Logging and Command Hotkeys 28:30 - Flexibility in Command Customization 32:21 - Interactive Command Handling 37:26 - Deep Integration of Commands 41:24 - Project Overview and Future Directions All things Aaron: https://aaronfrancis.com https://github.com/aarondfrancis/solo https://tryhardstudios.com https://x.com/aarondfrancis https://bsky.app/profile/aaronfrancis.com https://sideprojectpodcast.com https://bsky.app/profile/sideprojectpodcast.com

Transcript

Joe Tannenbaum
00:00:06 – 00:00:17
Welcome to Side Projects. I am your host, Joe Tannenbaum. And with me today, we have a man of the Internet, Aaron Francis, cofounder of TryHard Studios. How you doing today, Aaron?
Aaron Francis
00:00:17 – 00:00:21
Good. I am honored to be your guest on side projects today.
Joe Tannenbaum
00:00:21 – 00:00:25
I don't know if honor is the right word, but I'll take it. I will I will accept that. Yeah. I get to decide.
Aaron Francis
00:00:25 – 00:00:29
I I am honored. So that it is That is the right word.
Joe Tannenbaum
00:00:29 – 00:00:39
That is fair. You have another podcast, which is excellent, called Mostly Technical. And on that that podcast, although I am an Ian Stansman, I I'm a huge fan,
Aaron Francis
00:00:40 – 00:00:42
you get a little bit of shame.
Joe Tannenbaum
00:00:42 – 00:00:56
There's a little side project shame on that, and we don't have any of that here. You That's good. You can walk work on whatever you want without guilt, without shame. Doesn't matter if anything comes out of it. Doesn't matter if there's a publish button hit, whatever you want.
Aaron Francis
00:00:56 – 00:00:56
Thank you.
Joe Tannenbaum
00:00:56 – 00:01:04
And you seem to have a lot of those going on lately. And so I'm Yeah. I'm curious which one of the thousand you'd like to talk about today.
Aaron Francis
00:01:05 – 00:01:13
Well, I just first want to acknowledge that I'm glad that we acknowledge that Ian is a no man. He's always bringing me down. He's like, don't do that, Aaron. I'm like, what in
Joe Tannenbaum
00:01:13 – 00:01:13
the world?
Aaron Francis
00:01:13 – 00:01:14
I gotta do stuff.
Joe Tannenbaum
00:01:15 – 00:01:16
Joyless. Joyless. So, yeah,
Aaron Francis
00:01:16 – 00:01:35
I appreciate having, a yes man on the show today. I kinda wanna talk about I kinda wanna talk about solo. So we got a lot a lot of projects going on, one of which is probably near and dear to your heart, and that's this new library Laravel package called Solo. So let's talk about that one.
Joe Tannenbaum
00:01:35 – 00:01:41
Let's talk about it. So this correct me if I'm wrong. This came from that new composer run dev.
Aaron Francis
00:01:41 – 00:01:42
Is that
Joe Tannenbaum
00:01:42 – 00:01:43
what it was? Composer run dev?
Aaron Francis
00:01:43 – 00:01:43
Mhmm.
Joe Tannenbaum
00:01:43 – 00:01:51
Yep. And you said, that's cute. Let's go a hundred times further with that. So tell me tell me the process. Tell me how this came.
Aaron Francis
00:01:51 – 00:02:01
So I was creating a new Laravel project for, this, like, game show that I was on, Jason Lingsdorf's channel. Super fun.
Joe Tannenbaum
00:02:02 – 00:02:02
Why won't you say
Aaron Francis
00:02:02 – 00:02:03
he didn't get a great
Joe Tannenbaum
00:02:03 – 00:02:04
job with that?
Aaron Francis
00:02:04 – 00:02:40
It was awesome. This was the first new Laravel project that I had created since the introduction of Composer Run Dev, which Taylor introduced a few weeks ago, that is basically a PHP entry point around n p x concurrently. So n p x concurrently, they set it up to run, you know, npm run dev, PHP artisan serve, PHP q work, and maybe pale or tail the logs or something. And so I ran that and I was like, oh, this is kinda nice. You know, first time experiencing like the combined command to run the whole thing.
Aaron Francis
00:02:40 – 00:03:06
Kinda nice. However, as I was like developing the application, I needed to add reverb. And I was like, so now I gotta go muck around with, you know, n p x concurrently and the escape like, all the escaping and adding of the new colors. And I was like, oh, that kinda sucks. And then you switch over to the place where it's running and the logs are interspersed with HTTP responses or interspersed with reverb errors.
Aaron Francis
00:03:06 – 00:03:20
And I'm like, this is not great. We could do so much better. This is not great. And so I came home after that and on a lark, watched your CLI experiments Laracasts course, which is very good. I highly recommend it.
Joe Tannenbaum
00:03:20 – 00:03:21
Thank you.
Aaron Francis
00:03:21 – 00:03:30
And so I'm I'm laying in bed. It's, like, 10:30, and I got the phone sideways, and I'm just watching you just type stuff. Like, man, this is really good. I could do this. This.
Aaron Francis
00:03:30 – 00:04:01
And so the next day, I came in and was like, I'm gonna I'm gonna make, a better version of this composer run dev, and I'm gonna do it as, like, a a CLI tool, as a as a TUI. And as things do, it just kinda spiraled from there. It just kinda you know, it was like, oh, this will be simple. And now, you know, we're several several weeks later and open issues and dozens to a few hundred commits, and it's not finished yet. So hoisted by my own petard, once again.
Aaron Francis
00:04:01 – 00:04:03
But it's kinda awesome.
Joe Tannenbaum
00:04:03 – 00:04:14
It's kind of incredible. You took it so far. You took it further than I even I think you mentioned sort of in the beginning, like, oh, I'm thinking of doing this, and here's, like, a prototype. And I was like, that's amazing. That's great.
Joe Tannenbaum
00:04:14 – 00:04:29
And then you, like, started sprinting, just sprinting down the road. So you have accidentally recreated Tmux via PHP, which I'm not, like, super familiar with Tmux, but I know enough about it to know that's basically what you do.
Aaron Francis
00:04:29 – 00:04:31
That you shouldn't do that. Yeah. Same.
Joe Tannenbaum
00:04:32 – 00:04:55
And I also wanna, like, emphasize that your learning curve is, like, enormous. You went from, I have virtually zero knowledge of this space. I don't know how to put together, like, a comprehensive 2e slash CLI app. And then you went, I am building a full screen interactive multi process 2e. Outside of the course, I mean, there's you're doing way more than what's in the course.
Joe Tannenbaum
00:04:55 – 00:05:17
Where where were your stumbling blocks? Like, where where did you go, I don't know what this is, and I wish I knew more about this or because a lot of it I find in this space is not even knowing the term to search for yet. You've gotta figure out what you're looking for before you even get there. So, like Yes. Can you I do you have any of those identified, like, what the stumbling blocks were in the beginning process?
Aaron Francis
00:05:17 – 00:05:24
Where were the stumbling blocks? Everywhere. All over the place. Everything was a stumbling block.
Joe Tannenbaum
00:05:24 – 00:05:25
It's all stumbling blocks.
Aaron Francis
00:05:25 – 00:05:40
It's all stumbling blocks all the way down. It so I I I think of myself as a very, like, working class developer. And so when people talk about, Tmux, I'm like, I don't I don't know what that means. Sounds awesome for you. That sounds great.
Aaron Francis
00:05:40 – 00:05:50
But I don't know what that is. They're like, oh, I gotta, you know, set up my tree sitter and my LSP and Neovim. I'm like, is that PHPStorm? Because that's what I use, and PHPStorm rules.
Joe Tannenbaum
00:05:50 – 00:05:51
Yeah.
Aaron Francis
00:05:51 – 00:06:10
And so once you, like, once you start mucking around with accidentally inventing tmux, there's a whole layer down there that I'm just not terribly familiar with. I mean, so we can talk about we can talk about a terminal and a shell. What are those things? I still don't know. How are they different?
Aaron Francis
00:06:10 – 00:06:20
I have no idea. And so it's like, how am I supposed to even do any of this when I can't describe to you even now the difference between a terminal and a shell. Beats me. And so then we
Joe Tannenbaum
00:06:20 – 00:06:30
start every two weeks when I have to relook it up. That's that's when I know it. Okay. And then it the the information just disappears from my head. I I know shockingly little about what I'm actually doing.
Joe Tannenbaum
00:06:30 – 00:06:32
I'm really just doing what you're doing stumbling along.
Aaron Francis
00:06:32 – 00:06:42
Correct. Yes. I I am I am like a plumber that doesn't know theory and only knows practice. It's like, well, if water flows downhill, make sure it doesn't leak. I'm like, hey.
Aaron Francis
00:06:42 – 00:06:56
Yeah. That's pretty good. I'm a plumber now. And so so, you know, you start mucking around with, like, dealing with the output of other processes. And you're in the wild, wild west because it's like, they could do anything.
Aaron Francis
00:06:56 – 00:07:19
And you don't you don't really have control over it, but you gotta be prepared for it. And so then you enter into the whole world of ANSI codes and stuff. Fortunately, I have found, you know, ANSI codes have been around for one hundred years. And therefore, ChetGPT is very well versed in, like, what ANSI codes are, what they do, like, how to work with them. And so I spent a lot of time being like, hey.
Aaron Francis
00:07:19 – 00:07:27
This is happening. I don't know what this means. Can you help me? Like, why did my whole screen just turn blue? Can you help me?
Aaron Francis
00:07:27 – 00:07:33
Because I I can't figure that out. Yeah. It's like, oh, you have a ANSI code that never got reset. Okay.
Joe Tannenbaum
00:07:33 – 00:07:34
Yeah.
Aaron Francis
00:07:34 – 00:07:53
Tell me more about ANSI codes. What does that mean? And so, you know, it kinda the guy that created SQLite has said in in later years that if he had known you weren't supposed to write a database from scratch, he wouldn't have done it, but he didn't know that. And that's really that's, like, that's how I feel. Like, if I had known that this was stupid, I wouldn't have done it.
Aaron Francis
00:07:53 – 00:07:56
But because I didn't, I did it, and it still is awesome.
Joe Tannenbaum
00:07:56 – 00:07:58
You're describing my career.
Aaron Francis
00:07:58 – 00:08:13
Yes. Exactly. And it it's great. It works. I ended up, you know, in the fullness of time, I've ended up creating basically, like, a I don't know what a terminal emulator, a a screen emulator, basically.
Aaron Francis
00:08:13 – 00:08:43
Because one of the problems is, like, I'm gathering all of this output from these underlying processes, and then they're full of ANSI codes. But some of the ANSI codes I'm looking at you, Jess Archer. Some of some of the Laravel prompts, for example, is like, hey. What if we as you're typing, we move up five lines and clear down and then redraw from the five lines that we moved up. And I'm like, guys, I wasn't prepared for that.
Aaron Francis
00:08:43 – 00:08:53
I started with the idea that you would be tailing a log and I could just be like, I got the output there. I'm gonna write it over there. And then Jess comes along. She's like, no. Laravel prompts are awesome.
Aaron Francis
00:08:53 – 00:09:16
And we do partial screen clearing, and you gotta be ready for that. And so that is the point when I moved into alright. I have to take all of this output from these underlying processes. I have to take it all. I have to run it through an emulator to figure out what the final representable state should be and then give that to solo to represent.
Aaron Francis
00:09:17 – 00:09:18
And so I'm, like, doing all the
Joe Tannenbaum
00:09:18 – 00:09:28
stuff that's an enormous task. That's like a that's like Dude. You're that's like parsing level 1,000. Because, like, when you look at ANSI codes, it's not like HTML. Like HTML, you can kinda suss out.
Joe Tannenbaum
00:09:28 – 00:09:48
You can say, like, I see tags here or whatever. ANSI amidst content is a jumbled nonhuman parsable mess. You've gotta, like, really go through it. And the thing you just talked about, that mental leap of saying, like, I just printed these these this sequence to the terminal, and it actually is not about formatting. It's gonna move the cursor around and erase things.
Aaron Francis
00:09:48 – 00:09:48
The cursor.
Joe Tannenbaum
00:09:48 – 00:10:03
And that's not to me, that was not intuitive in the beginning. I was like, I'm just printing something to the screen, and it's like it's controlling Yes. The terminal. And so that is a mental hurdle that you have to sort of, like, get over and and figure out and, like, grok in order to move forward. Right?
Joe Tannenbaum
00:10:03 – 00:10:11
That's a huge one in my because In my it was just formatting. You're like, okay. I just need to, like, sort of parse out before I but there's actions here. You know?
Aaron Francis
00:10:11 – 00:10:42
In my in my adorable naivety, I thought ANSI codes are for for colors. And, like, it's just it makes everything pretty. And then you realize it's like, no. This is this manipulates the cursor and it clears the screen and it sets settings and retrieves settings. And you're like, well, f, that means like, if I'm if I am in control of a process and I'm just randomly or or rather, faithfully printing out the code or the output from the underlying process, it's gonna muck about with my process.
Aaron Francis
00:10:42 – 00:11:03
Like, it's gonna muck about with what I'm trying to show on the screen. So I'm drawing this, you know, beautiful chrome around this little pane of, like, output. But this output in this little pane is now reaching outside and clearing the screen. It's like, god freaking dang it. So now I run it all I run it all through a buffer, but then you get into the situation where it's like, alright.
Aaron Francis
00:11:03 – 00:11:30
Well, how do you keep track of content and ANSI kinda separately but kinda together because you need to do, like, you need to do word wrapping and you need to keep track of like what colors this line should be up to this character, but then then turn it off. And then the insidious part is, let's say you've got a string of red text. Right? And your cursors at the end and then somebody decides, alright. Let's turn the text back to black for the next few characters.
Aaron Francis
00:11:30 – 00:11:35
Right? String of red text. Turn off red. Go back to black. Let's start typing again.
Aaron Francis
00:11:35 – 00:11:45
Here's something fun. Let's move the cursor all the way back to the middle of the red text and keep typing. What color should it be? Well, whatever it was at the end. It doesn't become red.
Aaron Francis
00:11:45 – 00:12:20
The cursor carries these ANSI codes back in time, and it's like, I'm gonna start writing, but you need to respect my ANSI codes. But also, once I'm done writing and I move the cursor away, the the very next character has to retain the fact that it was read before you came in here and overwrote it. And so now, it's like you can't just say, alright, red goes from position zero to position 20. You have to say red is position zero through 20. Every single character is red such that if you bring the cursor and start overwriting it, doesn't matter.
Aaron Francis
00:12:20 – 00:12:31
Because positions seventeen, eighteen, 19, and 20 are still red, even though you overwrote in the middle with black. It's like, shoot. So now I have to keep track of every ANSI code for every single character.
Joe Tannenbaum
00:12:31 – 00:12:42
And anybody listening to this would go answers to this. Would you do this? Why would you put yourself through this? Because that's that's like a recipe for insanity. That's It is.
Joe Tannenbaum
00:12:42 – 00:12:55
Wild. And, actually, I'm not even sure I knew that before you told me about that a couple weeks ago. And it does make sense because all you're doing is just continually printing things, and and there is a sequence to it. It's just that, visually, it's doing something else. But
Aaron Francis
00:12:56 – 00:12:57
Mhmm. So it
Joe Tannenbaum
00:12:57 – 00:13:05
does it does sort of make sense. There is there is logic there, but it's not to our human brains, it's just a scramble. Like, that's It's insane.
Aaron Francis
00:13:05 – 00:13:15
Yeah. And so I was like you know, when I when I realized that, when I it was it was truly, a Laravel prompts thing because it was the one moving the cursor around
Joe Tannenbaum
00:13:16 – 00:13:16
Mhmm.
Aaron Francis
00:13:16 – 00:13:38
In a way that I hadn't experienced before. And that's when I started to think like, crap. I I have to keep track of a buffer of characters and a buffer of, like, display, decoration. Mhmm. And I like I felt I felt like I reached a new or a, a level of, like, oneness with the machine.
Aaron Francis
00:13:38 – 00:13:47
And I felt like what's his name? Jeff Daniels? Is that his name? Jeff Daniels? When he goes into Tron, he's, like, inside the computer, and you can see how it's working.
Aaron Francis
00:13:47 – 00:13:57
I don't know if that's his name. Somebody's gonna make fun of me for that. But I felt like I was inside the computer. This is so bizarre. I was in Boise and, you know, hanging out with Steve all day, Steve and Kelsey all day.
Aaron Francis
00:13:57 – 00:14:21
And then at night, I'm in the hotel, and it's, like, quiet, and there are no children there. And I'm like, what if I just, like, worked on something? And so I'm laying in bed. I've got music playing, and I'm my eyes are closed as I'm trying to figure out how am I going to move a cursor and take information with it and, like, insert it without having to override. It's it was just the weirdest thing.
Aaron Francis
00:14:21 – 00:14:31
It was a delight. It was so fun. But it was one of those things where, like, you close your eyes for thirty minutes, and then you open it and write it down and see if it works. And then I just haven't done that in forever.
Joe Tannenbaum
00:14:32 – 00:14:42
And it felt great. Right? Isn't that isn't that so satisfying? It just, like, poured out of you, and you're, like, even if it's not a %, we probably just got 85% there. And now we just need to tweak it a little bit.
Joe Tannenbaum
00:14:42 – 00:14:44
Right? It just sort of, like, flowed. I love that.
Aaron Francis
00:14:45 – 00:14:46
So this is fun.
Joe Tannenbaum
00:14:46 – 00:14:53
This is one of those projects where it's, like, virtually 100% learning, probably a 10% learning. Yes. Dude.
Aaron Francis
00:14:53 – 00:14:53
Totally.
Joe Tannenbaum
00:14:54 – 00:15:09
There's no just, like, I'm in a familiar space, and I just know, like, the thing I wanna do. You're just you're doing both things at the same time. You're you have an objective, but you have to learn everything to get there. But that being said, you did this all within, I wanna say, three weeks. Is that the correct timeline?
Joe Tannenbaum
00:15:09 – 00:15:09
It's some
Aaron Francis
00:15:09 – 00:15:11
Yeah. I think that's about right. I mean,
Joe Tannenbaum
00:15:11 – 00:15:17
that's wild. Bad. Yeah. That's not bad at all. And are you using this now on a on, like, a regular basis?
Joe Tannenbaum
00:15:17 – 00:15:18
Are you using
Aaron Francis
00:15:18 – 00:15:19
your own package?
Joe Tannenbaum
00:15:19 – 00:15:25
Yeah. And and you're you're happy with it or you're finding more things with it? Do you wanna tweak or how's that looking?
Aaron Francis
00:15:25 – 00:15:27
Yes. Both. Both? Yes. Yes.
Aaron Francis
00:15:27 – 00:15:30
And. His name is Jeff Bridges. I looked it up. So I had Jeff right. Oh.
Aaron Francis
00:15:30 – 00:15:31
So I was like, Jeff Daniels?
Joe Tannenbaum
00:15:31 – 00:15:32
I don't know what that No.
Aaron Francis
00:15:32 – 00:15:43
Jeff Daniels is dumb and dumber. So, you know, six to one, half a dozen to the other. So I am using this package. Some of the things that I knew were going to be issues have become issues. I guess Okay.
Aaron Francis
00:15:44 – 00:16:02
Congrats to me. I knew there were going to be problems, and I was right. The biggest the biggest one that we found early on was stubborn processes or processes that the sub processes spawned, like, many layers deep. And so then when you quit solo, it left a few of these lingering processes
Joe Tannenbaum
00:16:02 – 00:16:03
around. Processes. Yeah.
Aaron Francis
00:16:03 – 00:16:28
Which is not quite. Like, if the whole point is, like, start a bunch of processes, and when you stop, it stops a bunch. Well, you've only delivered half of the promise if you don't stop them all. So Right. It was stuff like, you know, it was stuff like npm run dev actually spawns Vite, and PHP artisan serve spawns a few other, you know, underlying processes depending on the platform you're on, which has been another big problem.
Aaron Francis
00:16:29 – 00:17:05
And so, being able to, consistently kill all of the underlying processes was the first kinda like major hurdle that took us from like 0.1 to 0.2 after I fixed it. And the solution there was to do a little bit and this is again above my pay grade slash below the layer of the stack that I normally work on. I had to like do a little bit of command line food to figure out, alright. Based on my PID of solo, give me all the PIDs that I spawned and all the PIDs that they spawned. Just forever and ever and ever.
Aaron Francis
00:17:05 – 00:17:23
And so the the solution there was continue to try to kill them gracefully from PHP. Just like say, you know, the symphony process, just tell the symphony, underlying symphony process to stop. Give it a few seconds and then just like, you know, basically bail out. Don't care. Yeah.
Aaron Francis
00:17:23 – 00:17:49
So then then after the main solo process stops, there is now a basically a monitoring process. So when you start solo, it starts the the TUI process, but it also itself spawns an underlying process that is the monitoring process. And so it's running these two processes the whole time. One is, like, rendering the 2e and doing all the work and everything.
Joe Tannenbaum
00:17:49 – 00:17:49
And the
Aaron Francis
00:17:49 – 00:18:08
other is just kinda hanging out and watching. And the one that's hanging out and watching every, like, second or something like that, it says, alright. What are all the processes that have been spawned? Let me just hang on to those. Then when solo, the main process dies, that monitor process kicks into gear and says, alright.
Aaron Francis
00:18:08 – 00:18:27
The main Tooie, the thing that you've been looking at is dead. Let's go see if anybody else is still alive and kill them all. And then after it does that, it then exits. And so that has been that's just been totally foolproof. And so that's been incredibly helpful to have because there are there are some weird, like, pale, Nuno's tailing package.
Aaron Francis
00:18:27 – 00:18:44
Mhmm. Doesn't respond to sigterm or something like that. And so it's like, it just doesn't it just doesn't die. And so this monitoring process, once Solo is dead, it assumes that anyone that's left over is pretty stubborn, and it just, like, brute forces them. And so that that's now foolproof, which is which is quite nice.
Joe Tannenbaum
00:18:44 – 00:18:55
That's a that's a tricky one. That's a tricky one. That's a tricky one to solve, but that's that's a clever unshockingly a clever solution. I was doing what was it for? My US talk.
Joe Tannenbaum
00:18:55 – 00:19:11
I was doing the FFmpeg stuff. Yeah. I was spawning all of these child FFmpeg processes, and I wasn't consistently cleaning them up. And I I would constantly PSOX, you know, pipe, grep, you know, what am I looking for? And it was just, like, so many on the screen, so I had to figure out how to effectively clean that up.
Joe Tannenbaum
00:19:11 – 00:19:25
But that was a much more contained situation. I can't are you allowing people to add their own processes into the mix? I know that was an idea at some point. And Yeah. And it's and they can just say, like, this command is running, pipe it into a new tab, and it just works.
Joe Tannenbaum
00:19:25 – 00:19:26
Exactly. And you're a wizard. That's
Aaron Francis
00:19:26 – 00:19:33
incredible. Exactly. That's amazing. Yeah. So I ship with you know, it ships with this, like, app or or solo service provider.
Aaron Francis
00:19:33 – 00:19:53
And in the solo service provider, it's got, like, npm run dev, tail f storage logs, and then maybe one more that's like commented out. Like php artisan serve, I think, has commented out. But I also so those are all the ones because I use herd. I don't need php artisan serve, but a lot of people do. So I just left it in there, commented out.
Aaron Francis
00:19:53 – 00:20:20
Those are set up as like auto starting commands. So if you run solo and you've uncommented that one, it's gonna start three commands in three different tabs running those processes. There's also the notion of lazy commands which would be like PHP artisan queue work and PHP artisan reverb start. So you can add those as lazy commands but they don't auto start. So So this is a nice, this is a nice situation if you're like, I'm just working on the front end.
Aaron Francis
00:20:20 – 00:20:33
I'm gonna run solo. I don't need a key worker. I don't need reverb. I don't need, you know, whatever else. But they're there such that if, you know, you're working on the back end, you can just hit the right arrow, go over to reverb and hit s and reverb will start.
Aaron Francis
00:20:34 – 00:20:52
But it doesn't auto start because that could be annoying and you might need it, you know, one tenth of the time or something. And so we have those two affordances. Lazy commands don't auto start but they show up in in your provider or like on your screen. And then regular commands just show up and start themselves. And it's just an array.
Aaron Francis
00:20:52 – 00:21:08
Like you could you could just say, you know, Joe's command as the key and then any literally any command that you wanna run as the value. And then that will be running in solo with output piping in and showing you. And you can tab over to it and pause it and that sort of stuff.
Joe Tannenbaum
00:21:09 – 00:21:20
The notion of just saying run any command and off I'll figure out how to properly output it feels stressful in my entire body, and I'm so impressed that you are doing it. That's that's amazing.
Aaron Francis
00:21:20 – 00:21:22
It's stressful. It's very stressful.
Joe Tannenbaum
00:21:22 – 00:21:30
You're like, I haven't slept in in weeks. Truly. What is what is on the docket? Are you pushing this further? Are you saying this is kinda done?
Joe Tannenbaum
00:21:30 – 00:21:32
Where are you at with it?
Aaron Francis
00:21:32 – 00:21:46
So the final thing that I have I think there are two final things that both kinda got munched into one PR because I was, like, manic. So the final things, the first would be custom, hot keys. So right now
Joe Tannenbaum
00:21:46 – 00:21:47
I was gonna
Aaron Francis
00:21:47 – 00:21:59
ask you. Yeah. You can do hot keys to, like, scroll the logs up if you miss something, switch tabs, stop a process, restart a process, that sort of stuff. Then somebody was, like, I wanna use VIM hot keys. I was, like, that's awesome.
Aaron Francis
00:21:59 – 00:22:02
What are you talking about? What is VIM hot keys? I don't know what VIM hot keys is.
Joe Tannenbaum
00:22:02 – 00:22:07
You put out a TUI. It's t minus negative five seconds before somebody says VIM hot keys question mark. VIM
Aaron Francis
00:22:07 – 00:22:20
hot keys. I'm, like, that's cool. What are what does that mean? So instead of instead of doing the easy thing, which is like, yeah, we can just instead of arrow up, you can also add j or k or whatever it is. Yeah.
Aaron Francis
00:22:20 – 00:22:22
We'll just make that an alias. No. No. No. No.
Aaron Francis
00:22:22 – 00:22:29
Of course not. Of course not. Of course not. Of course not. We have to have an entire way of setting up your own custom hot keys.
Aaron Francis
00:22:29 – 00:22:58
And so we're close. Now I've got that fully working on a PR that allows you to basically register, like, a key provider. So you can have default keys, or you can have VIM hot keys or you could extend either one of those or some combination of those and have like Joe's favorite hot keys or something. And a lot of what I'm trying to do in this is set it up such that teams can customize it for themselves without stomping over each other's setups. And so, like, you may want them.
Aaron Francis
00:22:58 – 00:23:19
I certainly don't. How do we give that affordance in the package such that two developers working on the same repo can have differences like that. And so it ends up being a lot of like, let's register a thing. Let's register a class whether that's a theme, dark and light modes both supported. A theme or a hot key provider or something.
Aaron Francis
00:23:19 – 00:24:03
And let's register it under a key and then use your, dot env to pick which key you want. And so maybe everyone on the team has registered in their service provider Joe's Awesome hotkeys, but nobody else uses it because their dot env is set to default mode or VIM keys or whatever. And so that's the way that I've kind of figured out to, like, let's have 15 different options and each one each person gets to choose their own individually. So in that PR, we've got global custom global hot keys, but also command specific hot keys, which is very interesting. Because let's say you're on, like so the the logs command that I wrote, it, like, tails the Laravel log file.
Aaron Francis
00:24:04 – 00:24:35
But in the process of of, like, mucking about with the output that it received from tail storage logs, it collapses vendor frames so you don't get, you know, 18 vendor frames going through the service container. And you're like, god freaking dang it. I can't see what I'm working with here. And so it's like an enhanced log command. Now you can imagine that sometimes you might wanna see the vendor frames depending on the error that you're debugging, but you're kinda host unless this command has its own hot key.
Aaron Francis
00:24:35 – 00:24:43
So when you, like, arrow over to logs, you see a new row of hot keys and one of them is v and that says vendor frames.
Joe Tannenbaum
00:24:43 – 00:24:44
Yeah. Yes.
Aaron Francis
00:24:44 – 00:24:54
And it shows the vendor frames. You're like, ah, I got it. It is in the container. Hit v again, and the vendor frames go away. So now commands themselves can register hot keys.
Aaron Francis
00:24:54 – 00:25:01
Because you can also imagine on the logs, you might wanna hit t to truncate. Be like, I've done I don't need to see all this crap. It's fine. Yeah. Just truncate it.
Aaron Francis
00:25:01 – 00:25:09
Let me start over. And so now commands can register hot keys. So we have global and local hot keys in that PR and then there's one other thing, but I'll pause there.
Joe Tannenbaum
00:25:09 – 00:25:27
So if you so let's say I have my own command I'm running. I can register hot keys that do custom behavior on that screen. How does that how am I tapping into the command? Am I just running other commands? Am I are you providing an interface to interact with that command?
Joe Tannenbaum
00:25:27 – 00:25:31
Like, how how is that happening to in a generic way? I'm just curious.
Aaron Francis
00:25:31 – 00:25:57
Yeah. So in a generic way, the enhanced let's say the enhanced log command, which is my wrapper around tail, that is a subclass of the command class that Right. Solo ships with. And in in the command class, I've left a few basically, like, no op methods where it's like, hey. I've got all of the, I've got all of like, everything that has been output, I have all of it right here.
Aaron Francis
00:25:57 – 00:26:21
I'm gonna pass it through this method called modify output, and then I will put it out to the screen. Modify output doesn't do anything. It just returns what you gave it. Except in the enhanced log command, modify output walks the lines looking for vendor frames, modifies it, and then gives it back. And so there are a few affordances like that, the like, the super or the or the parent just left blank for the sake of the children.
Aaron Francis
00:26:21 – 00:26:45
But you can also imagine, like, let's say, the truncate hot key, which I haven't written yet. But you have access in the subclass. You have access to the command as it was written. So you can see, like, what is the log file that they're working with. When I hit t, I can just do whatever I want inside of PHP and just say, like, file put contents nothing and put it into that file.
Aaron Francis
00:26:45 – 00:27:01
That's totally fine. There is also a way because Symfony gives you Symfony so, things I don't know, tty and pty. Don't know what those things are, but I do know that PTY is like pretend, or I think it's actually pseudo. Yeah. I like pretend a little better.
Aaron Francis
00:27:01 – 00:27:29
But it's like a it's a it's a pretend TTY or a pseudo TTY. And this is the method by which you can give input to the underlying process from, like, the PHP slash Symfony layer. Right? And so interactive commands are coming, but you don't actually have to go interactive. You could have a hot key that says, send this string and the string could be like type a a r o n enter.
Aaron Francis
00:27:29 – 00:28:00
Send that string to the underlying process. And so you can imagine that if you have a certain command that pauses for yes or pauses for are you sure or something and you don't wanna go full interactive mode because you're like, I don't actually need it. You can just add hot keys of y and n. And then in your PHP class, you can say when the person hits y just send through the input the character y, or send through the input the character n. And then just like, we'll we'll just keep going.
Aaron Francis
00:28:00 – 00:28:22
And so those are kind of the three methods by which I think somebody would hook into local hot keys. That would be like modifying something, some affordance that the parent class has given you, run some just arbitrary command like file put contents or arbitrary PHP, or send input into the underlying running process through Symfony's PTY thing.
Joe Tannenbaum
00:28:25 – 00:28:49
Here's what I like about all this, and I think it's maybe, correct me if I'm wrong, indicative of your personality. You were like, I'm not in control of this, so I'm taking control of all of this. Like, no. Nothing gets through the gates without my stamp of approval, like, without without running through my code base, which I love. And I've never I've never really thought about approaching it that way, but it affords basically, like, unlimited flexibility.
Joe Tannenbaum
00:28:49 – 00:28:52
You've done a very, very smart thing here. That's very cool.
Aaron Francis
00:28:53 – 00:29:07
Yeah. And I'm always thinking about, like because I am, like, a working man developer, I spend a ton of time just, like, looking around in source code and be like, where can I get my grab your little pause on this post? Yeah.
Joe Tannenbaum
00:29:07 – 00:29:08
Yeah. Yeah.
Aaron Francis
00:29:08 – 00:29:21
How how can I, like yeah? Laravel does that. I wanna do something different. Like, what is some weird way that I can, like, hook into what's about to happen to affect my will. And so when I'm when I'm writing a library, I'm also thinking that.
Aaron Francis
00:29:21 – 00:29:24
I'm, like, how do people wanna hook into this? Well What
Joe Tannenbaum
00:29:24 – 00:29:25
are the hook how I wanna
Aaron Francis
00:29:25 – 00:29:31
hook into it? Yep. A hundred different ways, and so let me count them. And so that's kind of, like, where all of that comes from.
Joe Tannenbaum
00:29:31 – 00:29:49
I saw I I was sore siding a little bit because I was trying to figure out a better way to to to rerender on terminal resize that was a little more effective. And I found that I think prompts itself is hooking into the symphony class, but it's accessing a protected method through reflection and firing.
Aaron Francis
00:29:49 – 00:29:50
And I
Joe Tannenbaum
00:29:50 – 00:29:54
was like, yeah. That's what I'm talking about. Yep. And by any means necessary, get in there. You know?
Joe Tannenbaum
00:29:54 – 00:29:55
It was great.
Aaron Francis
00:29:55 – 00:29:59
I got the solution for that, by the way. I can I can show you after the code that does that?
Joe Tannenbaum
00:29:59 – 00:30:03
I have one too. I'm I'm curious what you came up with, but mine
Aaron Francis
00:30:03 – 00:30:03
It's gnarly.
Joe Tannenbaum
00:30:04 – 00:30:08
It's gnarly? Mine's pretty clean. Mine's pretty chill. So maybe yeah. I was comparing that.
Aaron Francis
00:30:08 – 00:30:10
So Mine's not a chill guy. Mine
Joe Tannenbaum
00:30:10 – 00:30:10
Okay.
Aaron Francis
00:30:11 – 00:30:15
I think it's what is what is it? The sig winch command or sig winch signal?
Joe Tannenbaum
00:30:15 – 00:30:15
Yeah.
Aaron Francis
00:30:15 – 00:30:38
And then I basically I barely remember, but I basically have to do get env and put env to rewrite the amount of calls that like, I I basically create a new terminal, measure it, or I clear out the get in for put in so that it's not cached, get a new terminal, measure it, and then, like, rerender, and then put it back just in case. So it feels a little bit wonky to me.
Joe Tannenbaum
00:30:38 – 00:30:52
I did the same thing except that you can just do after you clear out those, first of all, those ENVs were really messed with me for a while because I didn't realize that those were being put somewhere, and I I didn't realize it was like a cash situation. Yep. I was like, why is this not happening? I know it's happening.
Aaron Francis
00:30:52 – 00:30:52
I know.
Joe Tannenbaum
00:30:52 – 00:31:04
I spent a lot of time logging and dumping on that one. But, yeah, if you if you clear out the ENV and then you just save this in it dimensions again, you just get it. It's just fresh. It's just like it's a fits a, like, a four liner or something like that.
Aaron Francis
00:31:04 – 00:31:07
Yeah. I think I think that's close to what I do. Yeah. I think that sounds
Joe Tannenbaum
00:31:07 – 00:31:08
like what you did.
Aaron Francis
00:31:09 – 00:31:23
But yeah. And the thing I remember one time I was showing you interactive commands, which show which we can talk about in a second, and I resized. And that was it was the sequinched along with the whatever it is, like stream choose or whatever. Stream selects. Yeah.
Joe Tannenbaum
00:31:24 – 00:31:24
Stream select. Yeah.
Aaron Francis
00:31:24 – 00:31:40
Those things working together, the sequence killed the stream select, and that was when the error blew up. And so I have to have, like, a I have to have, like, a process that's like, hey. If if you're in interactive mode and you're resizing, we're gonna need to suppress this error or eat the error. Keep all the balls there.
Joe Tannenbaum
00:31:40 – 00:31:41
Keep all the balls there.
Aaron Francis
00:31:41 – 00:31:44
Freaking nightmare. My god. Yeah.
Joe Tannenbaum
00:31:44 – 00:31:57
Yeah. You cracked the interactive thing, which was something I was trying to do for a while, and you went in control of all of these processes and all of these output, you could say, I I say what goes on this terminal.
Aaron Francis
00:31:57 – 00:31:57
Exactly.
Joe Tannenbaum
00:31:57 – 00:32:07
You don't get to change things. And so, yeah, you you cracked something that I had been trying to crack for a while in a in an elegant way. I cracked it in a very inelegant way, and it was very gross.
Aaron Francis
00:32:07 – 00:32:08
Jury's out. But yeah.
Joe Tannenbaum
00:32:10 – 00:32:22
Yeah. So that was that was basically a result of you basically creating your own pseudo terminal, your own emulator, and and controlling the situation. Right? So as as soon as you did that, anything goes underneath. Right?
Aaron Francis
00:32:22 – 00:32:22
Yeah.
Joe Tannenbaum
00:32:22 – 00:32:45
And you're just sending so, basically, just as a background, like, if you you have basically one tab that allows you to run, like, you know, make model or or make controller or, like, all of the artisan make commands, and you can interact with them in that tab without messing with the outside process and having it all in line and clean. And that is that may not sound hard, but it's very difficult. And
Aaron Francis
00:32:45 – 00:32:46
Turns out it's very difficult.
Joe Tannenbaum
00:32:47 – 00:32:56
Very difficult. And so was was this controlling in this sort of a emulator thing born out of that? Or this was already there and you said, oh, now that I have that, I can do this?
Aaron Francis
00:32:56 – 00:33:04
No. The emulator came as a necessity to handle Laravel prompts, which Yeah. Came from the interactive part.
Joe Tannenbaum
00:33:04 – 00:33:04
So Okay.
Aaron Francis
00:33:05 – 00:33:24
I watched, Josh Siri, friend of friend of the show, friend of ours. Josh Siri did a a good, YouTube video as he is want to do on solo. And the first thing I think that he tried was, like, I wanna run, I wanna have my make command constantly running so I can just hop over and make new stuff. And it didn't work. I was like, oh, shoot.
Aaron Francis
00:33:24 – 00:33:29
It's not interactive. Alright. What the hell? And so I watched that and I was like, yeah. Shoot.
Aaron Francis
00:33:29 – 00:33:51
Interactive's a good idea. And so that's when I like went down the deep dark rabbit hole of TTY PTY, figured out that Symphony has this input thing. So I'm like deep in the bowels of the computer and got it working. Right? So I get it working and I'm like, I'm gonna hit the I key to go interactive, and then I'm gonna start typing.
Aaron Francis
00:33:51 – 00:34:30
And so I hit the I key, and I was like, it's working. And I started typing, and my screen just got shorter and shorter and shorter and shorter because Jess was like moving five up, clearing, moving five up, clearing. And I'm like, oh, this is bad. And so that's when the interactive mode necessitated writing a screen emulator such that I could calculate what is the final representable state and then feed that out to solo. And so that was the part that led me down the whole like I have to write I have to write these buffers to keep track of it all so that I could, you know, tab over, hit I and start typing and everything just looks exactly as it should.
Aaron Francis
00:34:30 – 00:34:55
And the kind of like we're going like six levels deep because that command that commands that I run-in the make tab, you may you may know that you can type PHP artisan make colon model test factory enum. You can't type PHP artisan make and get a Laravel prompt that says, what do you want to make? Unless you're using solo because now you can. Because what what I do
Joe Tannenbaum
00:34:55 – 00:34:58
is price of $99.99 a month.
Aaron Francis
00:34:58 – 00:35:05
You can get a make Exactly. It's available for free on GitHub, but you can Venmo me $99.99 a month, and I will accept it.
Joe Tannenbaum
00:35:05 – 00:35:05
That's right.
Aaron Francis
00:35:06 – 00:35:25
What you can do so what I did was I wrote just a honest to goodness Laravel, like, command. And then, that's the command that I, like, register with solo. So that's kind of a nice thing. It's like, I want some advanced functionality. I'll just write it as a tip traditional Laravel command, and then I'll, you know, run that in solo.
Aaron Francis
00:35:25 – 00:35:25
But this
Joe Tannenbaum
00:35:25 – 00:35:26
Type it in.
Aaron Francis
00:35:26 – 00:35:50
This this solo colon make command, what it does So we're going like literally three levels deep. So the solo make command under the hood when you run solo make, it runs php artisan make. And it gets back a list of did you mean and it's like, actually I don't know what I meant. But I'm gonna parse all of that out. Take all of those.
Aaron Francis
00:35:50 – 00:35:55
Put those into a Laravel like So what? Ahead search select. Yes.
Joe Tannenbaum
00:35:55 – 00:35:55
Yeah.
Aaron Francis
00:35:55 – 00:36:10
Auto complete or whatever prompt. And so I can run solo make and it says, what do you want to make? And I can type in and model and migration and, like, all that stuff shows up. And so I type model. Then what happens is I now have to spawn the proper command.
Aaron Francis
00:36:11 – 00:36:21
So, like, I'm in I'm in solo make. I've run the underlying artisan process, gathered up all of the options, put those into a prompt that is inside of my control.
Joe Tannenbaum
00:36:22 – 00:36:22
Mhmm.
Aaron Francis
00:36:22 – 00:37:01
Now the user says, I wanna make a model. And I, as solo, am, like, not my responsibility. But what I can do is I can run PHP artisan make model under the hood, and then I need to gather all of the output from that underlying process and proxy all of the input to that underlying process. And so within my solo make command, I am now running a subprocess of PHP artisan make model. And I'm I'm whenever I type into solo make, it receives that input and forwards it to the underlying process, which is make model.
Aaron Francis
00:37:01 – 00:37:29
And so now I'm typing inside the subprocess, and it's doing all of its stuff. And then finally, when that sub process completes, it just that sub process dies and exits and it comes back up to the solo control and says, alright. What do you wanna make next? And so it just runs in an infinite loop such that you can leave it open in the solo tabs. When you run this inside of solo, the stupid thing that is happening is you're running PHP artisan solo.
Aaron Francis
00:37:29 – 00:37:43
Inside of solo, you're running PHP artisan solo make. Inside of solo make, you're running PHP artisan make model. And you're proxying truly, you're proxying the input from solo to solo make to make model. So
Joe Tannenbaum
00:37:43 – 00:37:44
from the activity
Aaron Francis
00:37:44 – 00:37:54
deep. Yes. Yeah. Yes. And so and then all that output has to make its way all the way back out to the top and then run through my screen emulator and then be put to the screen.
Aaron Francis
00:37:55 – 00:38:11
But now you can just leave a make tab open all the time. You can enter it's great. You can enter interactive mode, do all your stuff, and then exit interactive mode without killing the process. Mode without killing the process. And so the nice thing about that is you can exit interactive mode mid command and be like, f.
Aaron Francis
00:38:11 – 00:38:18
What was the name of that thing? Hop over to PHPStorm. Be like, like, that's right. Okay. We use plural or we you you know, we call it this way.
Aaron Francis
00:38:18 – 00:38:34
Hop back to solo, hit interactive again, and pick up right where you left off inside that command, which is two levels deep, and continue typing. And then all of your enters and your control c's, all that is proxied all the way down. Down. So you can, like, leave it and pause and come back later. You can actually kill it, and it'll restart.
Aaron Francis
00:38:34 – 00:38:48
You can let it run and then loop, and it just sits there forever. It's great. It's great. And I did all of that accidentally on one PR that was supposed to be just about hot keys, but I kinda munched it Munched it all together, and I've apologized to people, but it's, like, you know, hundreds of changes.
Joe Tannenbaum
00:38:48 – 00:38:51
Is that out yet? Is that something upcoming, or is that out yet?
Aaron Francis
00:38:51 – 00:38:57
No. So that is open in PR number four, and it's just a matter of being brave enough to merge it.
Joe Tannenbaum
00:38:57 – 00:38:58
People are gonna
Aaron Francis
00:38:58 – 00:38:59
flip their heads. I know.
Joe Tannenbaum
00:39:00 – 00:39:06
I feel like do you know that GIF of John c Riley where he's, like, like, just confused, consistent with John
Aaron Francis
00:39:06 – 00:39:06
c Riley?
Joe Tannenbaum
00:39:07 – 00:39:10
John c Riley? Stepbrothers? Will Will Carroll and John c Riley?
Aaron Francis
00:39:11 – 00:39:12
Oh, yeah. Yeah. Yeah. Yeah. Yeah.
Aaron Francis
00:39:12 – 00:39:13
Yeah. He's got
Joe Tannenbaum
00:39:13 – 00:39:27
this, like, weird, like, little throw, and he's just, like, confused and it's smash panning in. Yep. That's what I felt like as that was, like, happening as you were laying all that information at me. It was Yeah. That I I, if there was a crown to give, it's yours.
Joe Tannenbaum
00:39:27 – 00:39:38
I don't I don't it ain't me anymore. I'll tell you that much. Wow, dude. I could talk to you all day, but I also know we have things to do, and we're coming up on a holiday. So I'm going to unfortunately wrap this up.
Joe Tannenbaum
00:39:38 – 00:39:43
But if you ever wanna come and talk about a frivolous side project again, you're always welcome to.
Aaron Francis
00:39:43 – 00:39:47
I don't know if it can get more frivolous than this, but I would love to come back at some point.
Joe Tannenbaum
00:39:47 – 00:39:56
I think you could get more frivolous than this. I think we could. This is actually a side project with tangible output. I mean, this is really genuinely helpful. I think it's it's not I
Aaron Francis
00:39:56 – 00:39:57
hope so. Actually,
Joe Tannenbaum
00:39:57 – 00:40:15
I don't think it's frivolous at all. I think it's overengineered in a wonderful way, but I think the end product is genuinely useful. I mean, I I I think I told you this, but, like, when Taylor popped that into Slack the first time, the composer run dev thing, I was like, this is awesome. I hate this output. I
Aaron Francis
00:40:15 – 00:40:16
have a fan. You're like,
Joe Tannenbaum
00:40:16 – 00:40:23
I could do it better. I was like, is this why I was hired? I maybe. And then Mhmm. And I was like, I don't have the time.
Joe Tannenbaum
00:40:23 – 00:40:28
I have so many things to do. And then you come along and you're like, I got you. No problem solved.
Aaron Francis
00:40:28 – 00:40:43
Problem solved. People have asked, like, why why is this merged into core? And I was like, guys, do you know how stupid this is? Like, do you understand all of this garbage that I'm having to do? I don't wanna put I don't wanna foist that upon the shoulders of a different set of maintainers.
Aaron Francis
00:40:44 – 00:40:51
So if, you know, if you ever get the the inkling, you can lobby, but I I don't wanna give this to y'all.
Joe Tannenbaum
00:40:51 – 00:41:07
As a concept and as a construct, what you've created is very fascinating. And I think I've seen some of the code. It looks very clean, and I think it's, like, pretty maintainable. And you've abstracted it away enough where I feel like you could apply this to other things in other ways.
Aaron Francis
00:41:07 – 00:41:08
Mhmm.
Joe Tannenbaum
00:41:09 – 00:41:17
So it is fascinating. I you know, I've thought about it. I'm not I'm not I'm this is a zero promises. I I'm not even Yeah. Authorized to do this.
Joe Tannenbaum
00:41:17 – 00:41:24
But, I I I think it's I think what you've done here is, like, beyond fascinating and, like, incredibly smart. And congrats congrats on business work.
Aaron Francis
00:41:25 – 00:41:26
Thanks. Thanks.
Joe Tannenbaum
00:41:26 – 00:41:29
Alright. So solo is the project. That's
Aaron Francis
00:41:29 – 00:41:30
up on project.
Joe Tannenbaum
00:41:30 – 00:41:37
GitHub. GitHub. And then where where can people find you or where should people find you? Where would you like people to find you?
Aaron Francis
00:41:38 – 00:41:53
Well, I'm still in the good or bad place depending on your point of view. So you can find me on twitter.com. X is a stupid name. Twitter..comaarondfrancis. I will dip my toes into the bluer skies after Thanksgiving perhaps, or you can just go to aaronfrancis.com.
Aaron Francis
00:41:53 – 00:41:54
Everything should be there.
Joe Tannenbaum
00:41:55 – 00:42:02
That's where I've been point I've been pointing people to just the site. I'm like, I don't know exactly where you're gonna find me right now. Just go to the site, and you'll you'll figure
Aaron Francis
00:42:02 – 00:42:07
it out from there. It's pretty obvious. That's right. Own your stuff, people. Regardless of where you hang out,
Joe Tannenbaum
00:42:07 – 00:42:10
you gotta own your stuff. Alright. So aaron aaron francis
Aaron Francis
00:42:10 – 00:42:12
dot com? Aaron francis dot com. No d.
Joe Tannenbaum
00:42:12 – 00:42:14
Yeah. No no d. Correct.
Aaron Francis
00:42:14 – 00:42:14
No d.
Joe Tannenbaum
00:42:14 – 00:42:21
That's that's where people should go as as a statement of record. Yes. Okay. Amazing. Thank you so much, Aaron.
Joe Tannenbaum
00:42:21 – 00:42:22
I really appreciate it.
Aaron Francis
00:42:22 – 00:42:23
For having me.
Joe Tannenbaum
00:42:23 – 00:42:25
And, enjoy the rest of the day.
Aaron Francis
00:42:25 – 00:42:26
Alright. You too. See you.
Me

Thanks for reading! My name is Aaron and I write, make videos , and generally try really hard .

If you ever have any questions or want to chat, I'm always on Twitter.

You can find me on YouTube on my personal channel or the Try Hard Studios channel.

If you love podcasts, I got you covered. You can listen to me on Mostly Technical .