📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Scripting the GitHub Copilot CLI - Deep Dive

Microsoft Developer26:14

Transcription

[Music] Thank you.

Hey, I'm John Linquist. You can check out information about me at egghehead.io, but I want you to stay here, and we, we're going to learn about scripting Copilot and diving deep into using the terminal with AI to build out some really neat agents.

Now, as with any sane developer, I had to vibe code using Copilot, an entire slide presentation system in my terminal. So, you'll see me using that today. And as the agents explore this codebase through some of these pre-recorded scripts, that's what I'll be referring to.

So, Copilot can be run from the terminal just by typing `copilot`. And with a `-p`, it can put you into this mode where it just spits out text that can intelligently analyze what's going on and, uh, render that into your terminal. And you can take that, it's called standard out, and take that information and do some incredible things with it.

So if you have just a quick question, you can ask it about, "What's this git command?" And if you have, uh, a command that you want to run and inject into it, you can say something like, "Please explain this error." And command substitution in the terminal allows you to run a command in there. You can run your build. It would say, "Explain this error," and then get, get information about that and then act upon it.

And then another thing you can do is, uh, you can just dump files into context. So you're essentially preloading context ahead of time. You don't have to wait for Copilot to search and find the files if you know exactly the files you want, using glob patterns, using exact file names, or however you want to reference your files and dump them into an initial prompt.

And you may be wondering why I'm using all of this text here in, uh, in Markdown and on a display. And that's because AIs work so well with Markdown. And in the future, once the AI pulls down this video and anyone wants to download this video and upload it to a place where an AI can analyze it, uh, it's great to have the text on the screen in Markdown. It's the best way to communicate with, with AI. So whenever you're doing a presentation, showing off, just use Markdown. It's fantastic for AI understanding.

And then lastly on this slide for, uh, an example is in a script, you could ask it to grab the diff, assign that to a variable, and then inside of the prompt, you could drop the diff information in there, ask Copilot to talk about it, get a message back from that, and then commit with that message. If you've ever done this automatically in Copilot through the, through the GUI, this is essentially what, what's doing under the hood.

So the key flags here with Copilot is `-p` for the prompt you want to use. This turns it into the non-interactive prompt mode and it'll output everything to standard out as text. And then we're going to be using `allow-all-tools` to allow it to run bash, to run any of the, the scripts and anything we want it to do. Uh, there's not time enough in this session to talk about allowing and denying tools. But as you build out tools for yourself and workflows for your company, please make sure to look into `allow-tool`, `deny-tool`, and everything that can, uh, really lock down your agents and your workflows to be more respectful of, uh, what you want to happen as, as your agents look through your codebase.

So quick demo time. Uh, this is going to replay a session of using Copilot. So you can see I type `copilot` in this `slid` directory. `slid` is the name of this slides project. Going to give it a prompt of "hello world," classic hello world example. And it's going to start spitting out text. It's going to say, "I'm the Copilot CLI. I'm here to help you." And then, "What can I do to help?" And then give you the final information down here.

Now, you can disable this one with the `silent` flag. Uh, but otherwise, if you just run `copilot`, you can just start asking any sort of question you want right there on the terminal.

All right, so the first thing I'd recommend anyone do is to hop over to your ZSH config or whatever you're using for your shell and set up custom aliases. This is going to allow you to forget the flags so that once it's time to pick a model that you like the most, once it's time to pass in `silent` or the prompt, you can just have a much shorter version. I don't have to type `copilot` every single time. I can just type `co`. And that's, that's what I use for Copilot in the terminal. Or, uh, or for example here. So if I type `co explain this error`, it's just going to, it's just going to run and then print the result compared to `copilot -p explain this error`. Uh, similarly, I have one called `ico` for interactive Copilot, which turns on interactive mode with the `-i` flag. This is some special syntax, kind of, uh, off-topic, that just turns on the `i` flag if an argument exists. Not, not too important. If you want to learn more about that, just Google it or ask an AI.

Uh, so the usage here is you can start `io` for a fully interactive session. This is where most people spend their time in Copilot, or most people are more familiar with using the terminal user interface for Copilot than, than scripting. So you can spread these out. I have mine set as `co-` for, uh, Copilot interactive Copilot. I have one for using the Sonnet model, one for using Haiku model, one for using CodeX Max. And this is one of the fantastic things about Copilot is you get to pick and choose whatever model you want to use. And so depending on how you want to put together and orchestrate your agents, uh, you can have, uh, multiple of them work on the same task at the same time. Lots of amazing options here. And this makes it simple just to say, just type a few characters and fire up an agent with a single, uh, with just a few characters.

And then for session management, anytime it's critical to realize that anytime a session ends, you, it's never lost. You can go back and find it. We have a `continue` flag and we have a `resume` flag. And so I have `co-copilot`, `co-` for continue and `co-res` for continue and resume. These allow you to go back to a conversation and then, uh, bring it back into your session and resume where, where you last left off.

So we now have a `model` flag, we have our `allow-all-tools`, we have `prompt`, we have `interactive`, and we have `continue` and `resume`. So for this demo, you'll see that now all I have to type is, I'm going to just show you the `package.json` contents of this project. So it has some dependencies in there. And now I'm going to type `co` and then type in just a string. There's no other flags I have to worry about. I can just forget about those. And I can convert this into a Markdown table. And I'm going to use that command substitution, which is going to `cat` the `package.json` directly into that context. And then the end result is a Markdown table, which I could write into a file or do whatever I want with it, uh, using, uh, using our, uh, custom aliases and commands.

All right, so interactive mode is a little bit different than using the prompt mode. So with this alias, I can just spin up an interactive mode with a preloaded prompt. So if I want to do scripted things ahead of time to build up context, to build up a much prompt, I can do it by typing interactive mode. And again, this is just Copilot with the `interactive` flag or Copilot that you'd run by default. I can do this by putting together a prompt ahead of time to kick off the task. So instead of opening Copilot and asking it to do a bunch of information like find these files or run this GitHub, uh, CLI command, I can do that inline.

Now, there are some features in the Copilot terminal user interface like slash commands and going back through message history and better messaging and formatting that, that are awesome and definitely useful for, if you're, uh, having back and forth conversations and need to really go have a deeper interaction with, with the AI, that's where you want to go.

So for example here, if I run `ico` again, this is `copilot -i`, and I pass in a prompt, I'll say, "Where is the container defined in the slides project?" You'll see that it starts up an interactive session. It'll start thinking, and then you get this much, uh, prettier experience because it's waiting for me to type text to end and work with it. And then to exit out of it, you hit Command or hit Control C, and then that exits out of your session, and you go back into the terminal. It shuts down, and you're good to go.

All right, so combining your AI with commands you already know is where things start getting really powerful. So command substitution inside of your prompts is going to allow you to run things like a GitHub CLI command. For example, if I want to get my first GitHub issue, like GitHub issue number one, then I can run that inside of the command substitution and drop it into the prompt. So if I ask something like, "Is this issue valid?" and then I use a trick often where I use the, uh, double exclamation mark, and that expands the previously run command, which I'll show. That's going to allow me to ask a question about the previously run output. And you can do this for all sorts of things with the GitHub, uh, CLI for checking on PRs, for diffing PRs. You can ask it intelligent things like, "Get a list of all the open PRs or open issues and ask it like, 'Which one seems most important?'" and then start a new session working on that.

So the key here is that this command substitution, you can ask any sort of question here and then run any sort of command and kick off a new session with everything already intact and scripted and ready to go without having to open Copilot, type it all in manually, wait for it to finish. This kicks it all off for you and, uh, manages all the permissions ahead of time based on your system, and you don't have to worry about approving these tools and such inside of your session.

So the demo here is if I run `github issue` to view my first issue of this project. You'll see it will spit out the issue with all the Markdown nicely formatted. And this issue is about creating an intro slide. So once that's done, I can take Copilot and I can say, "Okay, Copilot, I'm going to jump into interactive mode. I'm going to say, 'Is this issue valid?'" and then pass in the previously run command. Once I hit enter, it's going to jump in, and you'll see that it does, "Is this issue valid?" and it drops in that issue that it pulled in from the GitHub CLI and all the information there. It hides some of it and it'll continue through. Now I have this session where I can kind of sit back and relax for it to work through whether that's a good issue for me to work on. It says it's a valid issue, and now I have a chance, because it's interactive, to say, "All right, let's go. I'll start this issue or move on with my work however I need to."

So `continue` and `resume` are, uh, are lifesavers whenever you're working on a task. Often you might accidentally quit out of the terminal, or you might, uh, want to pause something and go work on something else. And they, and they also allow you to chain together some really interesting conversations with your AIs because you can do things step by step. So when I have, uh, something like, "Please suggest what I should do next, what are the most important things, what should I fix?" then you can continue on that conversation by taking the output of this one, and then once it continues, it picks up right back from where it left off.

This is Copilot with the `continue` flag. This one's `cocon`, so it uses `continue`. And this will get the most recent sessions. So it's great when you're just doing the double ampersand to continue a Copilot session just finished. This one will prompt an interactive list of all the sessions you've had in the current directory. And then you can even, if you grab the session ID from your `/session` command inside of Copilot, you can grab that and resume from there.

So we'll see that this is going to allow us to maintain context. It's going to allow us to build previous, build upon what we've done previously and just dive deeper into an initial, an initial prompting question. So for example here, I'll ask it, "Please suggest the next three issues or next three tasks," and then continue the conversation in interactive mode, uh, so that I can do an intelligent thing up first. And then once the intelligent thing is ready, I can hop in and do, you can see it automatically swapped over into interactive mode where I didn't need to, uh, worry about that initial prompt or typing it out. I can script that away and feed it into a continued conversation where it can go in and do more interactive things. It's checking for test files. Says we should fix some TypeScript errors. And then now I can say, "Do I want to continue?" I can say, "Yes." And now it continues on. And now I'm in this interactive session where it chained one thing to the next. And once this is finished, then we'll move on. So it edits all these files. It's verifying it's fixed, and we handled the, what, what it suggested were the most important issues for us.

All right. Now, for custom agents, you can build agents inside of a `GitHub agents` directory. So, for example, if you have a Markdown file that looks something like this, like a "test specialist," which has a name and a description of when it should be used. Uh, you can use these a lot inside of the interactive mode. If you want to use them inside of, uh, the non-interactive mode with a prompt, you can just pass in `--agent`. And we'll assign this one a "testing specialist." So that we give it a prompt and we tell it, "Swap over into testing specialist mode," which will use the, this, the Markdown that we defined inside of this Markdown file. So you're going to have your front matter and the body, and that's just Markdown again being the hero here. It, it's going to use that, that information and set up your session in a way where it's following these rules and adopting this sort of behavior.

So if we look at a custom agent, if I say `copilot`, I can ask what its role is without any other prompt and say, "Look at your testing specialist," and it'll print out my responsibilities, our existing tests, writing tests, reviewing test quality. It knows it's supposed to do all of those things anytime I give it a prompt. And so I'm going to ask it, or I'm going to show you that prompt, just show that it's, uh, living in that directory. You can see the output there is the testing specialist.

So, if I can ask it something like, "Find a recent commit with a TypeScript file and make sure it's covered by tests," um, that's the only thing I'd have to prompt. And then the testing specialist would know its role. Okay, I'm going to look at this TypeScript file. I'm going to go through and analyze it based on what I know, and I can start creating tests for it.

All right, so any of these, uh, again, you just kind of want to forget the flags. Build your own custom aliases. These are the workflows and the things that help you, that that help you organize and they autocomplete in the terminal. It's, it's just so nice to have them around. You can set up all these agents for you and your team. You can share them around, and then I can have like `co-test`, `co-test` for that specific file, `co-test` for the last three commits. I can set up a security agent, and I could say `co-sec`, a documentation agent, which would specialize in documentation, something for code review, and all these would allow me to, uh, just type those few characters. Such as I can say, this is, uh, if you type `which` on a ZSH function, it'll show you the body of the function. This is just showing you that's what this is, is agent pointing there. And if I run `code test` with just a simple prompt of the file that I want it to test, it'll just kick it off and start writing tests and analyzing it. Uh, because it, it knows that its role is to handle tests and you gave it a file. It's like, okay, that's what I'm going to do.

All right. So, because we have, uh, scriptable agents now, we can drop agents into things like package JSON scripts. We can drop them into pre-commit hooks or other, uh, or other Git hooks that you want to allow it to intelligently identify things you might want to block. So this specific example is using a pre-commit hook, and it's going to say, "Please check the staged TypeScript files. If any of them lack tests, then we're going to say output an error flag that we can capture." And then later on in our pre-commit, we're going to say, "If I find that error, then block this commit from happening."

So the result can be, I tried to add this function, but it's blocking this because it didn't, our test, our testing agent didn't find any tests on it. So if I show this running, I'm going to add all of my files, and it's going to write a commit message to add a function. There were some TypeScript files added, and then it went ahead and blocked this. That went really fast. Uh, it checked the test coverage, and it blocked the commit intelligently because the, uh, the test coverage issues were detected, and it'll write out all of these issues. Then what I can do from here is I can continue that conversation and say, "Okay, how can I fix this? How can I make sure that I, I cover these, uh, these scenarios that weren't caught?" I can drop into, into an interactive session and then go through these issues that were found during the pre-commit hook without ever kind of leaving the context of that task that I'm working on.

All right. So, a project I set up because I get, I got so used to writing so many ZSH aliases and so many, uh, so many ways of capturing prompts and agents is called MDFlow. And MDFlow is what I call Markdown that Thinks. And if you want to check it out, it's at mdflow.dev. And it's kind of a research preview. And this is going to allow you to write Markdown files. I'll just scroll down to some of the examples. Markdown files that have intelligence built in. So Markdown files that can run multiple, uh, multiple agents at the same time. It can run things in orchestrated ways where you can, uh, run the tests here and run it through CodeX and Gemini in different models. And with all of this, you're able to, uh, use all these globbing patterns. And the key thing for me is that because it's Markdown, it's something that AIs can train on. And I can generate these new Markdown definitions of these agents. It's kind of like the agents and prompts built in, but on steroids, because it allows you to import files. It allows you to have, uh, I have a VS Code extension which has front matter which autocompletes. So, it gives me documentation for all the flags inline. So I can learn about that as well. And it just allows you to continue to build out all these custom, uh, aid Markdown files you can share with your team that represent these complex tasks that reference certain files, certain URLs, certain tasks, and run them all inline. So it's what we've been doing so far, but kind of taken to the next level where everything is captured inside of a single Markdown file.

So all you have to do is create a simple Markdown file. I'm going to assign its model to Haiku, just by default. And then I can run it with either the `mdflow` command, or you can run it directly. You can actually run a Markdown file through a trick in ZSH called the ZSH suffix, where it detects that, uh, that at the end of the file and swaps over into invoking this based on the file. That's something to, to look up for the real nerds out there. And then it's going to take all of that, and the CLI flag is going to be parsed out of the front matter. Uh, so it'll swap over to that, uh, to that model. And then you'll see, if I just let this demo run, I'm going to open up a little editor. Uh, I use micro. It's kind of similar to nano in the terminal. And this is going to allow me to set up in the front matter the model of Cloud Haiku. Then all I'm going to do is type out a simple hello message. And then if I run this Markdown file with `hello`. So one thing to call out is that because this is `copilot.md`, `copilot` is the command it's going to use to run and pass all the flags to. So it's going to run this and give you that information back. So if I continue with this, and now I can just run it directly as a Markdown file agent and get a very similar response. And if I set up some of the, the tools in there where it intelligently looks in the path or looks in your `mdflow` directory, you can, all you have to do is just type the, the Markdown file. You get autocomplete. It runs the agent with all of that wonderful scripting logic and everything built in.

So this even allows you to do piping and, uh, allows you to pass in and define flags as variables, which allow you to, uh, use template logic. It uses Liquid.js. I made sure to use the most common, uh, templating libraries and most common patterns. As with anything in AI land, the, the rule is code normal. Do what other people do. That way the AIs will have the best chance of being successful when it's running this. And so if I set up something like, "What's the most important fix for this topic?" and I pipe this, will expose a flag of `_topic`. It's like an internal flag, and this will load in all these files up front. So it just, uh, pumps that context in without having to look up everything. All I have to do is say `plan copilot.md` and then pass in a flag of `topic` around performance. And it's going to look in my source directory and it's going to find the most important performance fixes. Then we can also pipe in standard in and use that as a variable as well. And we can even chain things together, which we'll show off where we run one, we get the output of it, and then we can pipe out, which looks kind of crazy. It pipes out to another Markdown file.

So now if we let this run, this will show making a plan. We'll go ahead and type it up and let it go here real quick. We'll select a model of Haiku because it's the fastest when recording a lot of code. Uh, we'll pipe in, or we'll glob together all the files we want to include in this, so it brings them in up front. This is kind of choosing the cost of loading context up front versus the cost of running all the tool calls to find everything that we wanted to do. And I often choose the cost of the context up front. And now it's going to pass in a flag like, "What is this topic?" I'm going to say, "This topic I want for my source directory is performance." And now it's going to create a plan for improving performance. And now if I go down and, uh, write an implementation `copilot.md`, which will set to model Haiku, and this can take, uh, the previous output where it was making a plan and say, "Here's the rules of implementing anytime I get a plan." So it's going to take that output, bring it into this specific agent, and write everything out.

So now if I run `plan` and then I give it the different topic. I'll give it a topic of reliability and then I'll pipe that into my implementation, it'll do an agent handoff of the output, and it's kind of compressed down to only that context of the output, and it starts a new session where, uh, where the implementation can take over. And one thing I didn't do in this one is I didn't give it `allow-all-tools`, which is critical. We had that in our, uh, we had that in our in our aliases earlier. You have to enable that here. It's not on by default. And so now it can allow all tools on both of these and allow them to run all the tools necessary to actually make these fixes and implement things. So we'll let this go ahead and run. And if it's missing an environment or missing a variable like standard in, it, uh, it will prompt you to put that in. So we need to actually pipe in from our plan. And this will start analyzing TypeScript files, creating a plan, pipe it in, and go from there. It's piping in, and it'll build this out.

All right. So now it's going to start writing tests and creating commit messages and using all the files we, we brought in. So you can do things like embedding shell commands where you can run, uh, the individual things you want to bring in like `git logs`. You can import and run branches. You can set up all of these things that run scripts and imports and file globs ahead of time. And then with, because it has Liquid templates, we can use flags like positional arguments to say, if this one is yes, then do this one, otherwise do no. So you can have conditionals inside of your templates as well. So you can say yes this or no that.

And then finally, if we just let this run out, I'll let this play out real quick to finish this off. So run this conditional. I'll say, "Hey, Copilot and set up an an if template." And then it'll write out yes. And then I believe I'm at time. So I'm going to call it here. And just this will write out this template. And I didn't even need front matter here. I can just run this and let it pick one or the other. So we'll say conditional and Copilot and pass in no green light, red light. And I am definitely getting the red light here. So, we'll dry run this, and you'll see it says, "Say red light," as the, as the dry run there.

So, thank you so much for watching. That is Copilot, scripting Copilot, and the advanced tricks you can do with it.

[Music]