Skip to content

Instantly share code, notes, and snippets.

@mutaguchi
Last active April 16, 2025 02:42
Show Gist options
  • Save mutaguchi/929d85af65d032d9c1762eeb65ffbe8d to your computer and use it in GitHub Desktop.
Save mutaguchi/929d85af65d032d9c1762eeb65ffbe8d to your computer and use it in GitHub Desktop.
copilot_shell.ps1
<#
This script customizes the PowerShell command line to detect when an entered command is not found.
If a command is not recognized, it automatically rewrites the line to call a LLM assistant for help.
To run this script, execute it in the console as follows:
. .\copilot_shell.ps1
このスクリプトは、PowerShellのコマンドラインをカスタマイズし、入力されたコマンドが見つからない場合に検出します。
コマンドが見つからない場合、自動的にLLMアシスタントに問い合わせる形に行を書き換えます。
コンソールで
. .\copilot_shell.ps1
のように実行します。
【APIのURLとAPIKeyの指定方法】
- APIのURLは、Get-LLMResponse関数の-Uriパラメータで指定できます。省略時は環境変数OPENAI_API_URI、なければ https://api.openai.com/v1/chat/completions が使われます。
- APIKeyは-ApiKeyパラメータで指定できます。省略時は環境変数OPENAI_API_KEYが参照されます。指定も環境変数もない場合は認証ヘッダーは付与されません。
#>
Set-PSReadLineKeyHandler -Chord Enter -ScriptBlock {
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line,
[ref]$cursor)
try
{
$sb = [scriptblock]::Create($line)
$command_name = $sb.Ast.EndBlock.Statements[0].PipelineElements[0].CommandElements[0].Value
$command = Get-Command $command_name -ErrorAction SilentlyContinue
if ($command -eq $null)
{
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("?? $line")
}
}
catch
{
}
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}
$global:messages = @()
# Function to generate text from LLM
function Get-LLMResponse
{
param (
[Parameter(Mandatory = $true)]
[string]$Prompt,
[switch]$NoStream,
[string]$Uri,
[string]$ApiKey,
[string]$Model = "gpt-4.1-mini"
)
# Set API URL
if (-not $Uri)
{
$Uri = ${env:OPENAI_API_URI}
if (-not $Uri)
{
$Uri = "https://api.openai.com/v1/chat/completions"
}
}
# Set API Key from environment if not provided
if (-not $ApiKey)
{
$ApiKey = $env:OPENAI_API_KEY
}
# Set system message according to locale
$locale = [System.Globalization.CultureInfo]::CurrentUICulture.Name
if ($locale -like "ja*")
{
$system_message = [PSCustomObject]@{
"role" = "system"
"content" = "あなたは優秀なアシスタントです。ユーザーの質問に答えてください。"
}
}
else
{
$system_message = [PSCustomObject]@{
"role" = "system"
"content" = "You are an excellent assistant. Please answer the user's questions."
}
}
# First, add the current user message
$global:messages += [PSCustomObject]@{
"role" = "user"
"content" = $Prompt
}
# Remove old messages so that the total number of characters does not exceed 5000
$maxLength = 5000
$new_messages = @()
$totalLength = $system_message.content.Length
$cursor = 0
# Remove system message
$global:messages = $global:messages[1..($global:messages.Count - 1)]
while ($true)
{
$cursor--
$message = $global:messages[$cursor]
$totalLength += $message.content.Length
if (-not $message -or $totalLength -gt $maxLength)
{
$new_messages = @($system_message) + $new_messages
break
}
else
{
$new_messages = @($message) + $new_messages
}
}
# Rebuild messages
$global:messages = $new_messages
# Request body
$body = @{
"model" = $Model
"messages" = $global:messages
"stream" = -not $NoStream
} | ConvertTo-Json -Depth 10
if (-not $NoStream)
{
# Streaming response
$client = New-Object System.Net.Http.HttpClient
$request = [System.Net.Http.HttpRequestMessage]::new()
$request.Method = "POST"
$request.RequestUri = $Uri
$request.Headers.Clear()
if ($ApiKey)
{
$request.Headers.Add("Authorization", "Bearer $ApiKey")
}
$request.Content = [System.Net.Http.StringContent]::new(($body), [System.Text.Encoding]::UTF8)
$request.Content.Headers.Clear()
$request.Content.Headers.Add("Content-Type", "application/json;chatset=utf-8")
$task = $client.Send($request)
$response = $task.Content.ReadAsStream()
$reader = [System.IO.StreamReader]::new($response)
$result = ""
while ($true)
{
$line = $reader.ReadLine()
if (($line -eq $null) -or ($line -eq "data: [DONE]")) { break }
$chunk = ($line -replace "data: ", "" | ConvertFrom-Json).choices.delta.content
Write-Host $chunk -NoNewline
$result += $chunk
Start-Sleep -Milliseconds 1
}
Write-Host ""
$reader.Close()
$reader.Dispose()
# Add AI response to history
if ($result)
{
$global:messages += [PSCustomObject]@{
"role" = "assistant"
"content" = $result
}
}
}
else
{
# Normal response
$headers = @{
"Content-Type" = "application/json"
}
if ($ApiKey)
{
$headers["Authorization"] = "Bearer $ApiKey"
}
$response = Invoke-RestMethod -Uri $Uri -Headers $headers -Method POST -Body $body
$result = $response.choices[0].message.content
if ($result)
{
$global:messages += [PSCustomObject]@{
"role" = "assistant"
"content" = $result
}
}
return $result
}
}
function ??
{
$prompt = $args -join " "
Get-LLMResponse -Prompt $prompt
}
image

When you enter a command that does not exist, it is automatically replaced with a query to the LLM.

このように、存在しないコマンドを入力すると、LLMへのクエリに自動置換される。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment