Skip to content

Instantly share code, notes, and snippets.

@dfinke
Last active July 27, 2025 17:23
Show Gist options
  • Save dfinke/af1063ab9cea740b6eada9c1bdd26261 to your computer and use it in GitHub Desktop.
Save dfinke/af1063ab9cea740b6eada9c1bdd26261 to your computer and use it in GitHub Desktop.
AI-Powered To-Do List in PowerShell
# using psaisuite
# Ensure PSAISuite is available
#Requires -Module PSAISuite
<#
.SYNOPSIS
Outputs text using the 'glow' markdown renderer if available, otherwise falls back to Write-Host.
.DESCRIPTION
This function checks if the 'glow' command is available. If so, it pipes the input to 'glow' for rich markdown rendering. Otherwise, it outputs the text using Write-Host.
.PARAMETER InputObject
The string to output.
#>
function Write-Glow {
param(
[Parameter(ValueFromPipeline = $true)]
[string]$InputObject
)
if (Get-Command glow -ErrorAction SilentlyContinue) {
$InputObject | glow
}
else {
Write-Host $InputObject
}
}
# Path to the JSON file for storing tasks
$TaskFile = "$PSScriptRoot\tasks.json"
# Path to config file
$ConfigFile = "$PSScriptRoot\config.json"
# Load model from config file if present, else use external or default
if (Test-Path $ConfigFile) {
$config = Get-Content $ConfigFile | ConvertFrom-Json
if ($config.Model) { $Model = $config.Model }
}
if (-not $Model) { $Model = 'github:openai/gpt-4.1' }
<#
.SYNOPSIS
Loads the current to-do list from disk.
.DESCRIPTION
Reads the tasks from the JSON file if it exists, otherwise returns an empty array.
#>
function Get-TaskList {
if (Test-Path $TaskFile) {
$tasks = Get-Content $TaskFile | ConvertFrom-Json
return $tasks
}
else {
@()
}
}
<#
.SYNOPSIS
Saves the current to-do list to disk.
.DESCRIPTION
Writes the provided tasks array to the JSON file.
.PARAMETER tasks
The array of task objects to save.
#>
function Save-TaskList {
param($tasks)
$tasks | ConvertTo-Json -Depth 5 | Set-Content $TaskFile
# No technical output for users
}
<#
.SYNOPSIS
Adds a new task to the to-do list.
.DESCRIPTION
Uses the AI model to extract task details from input text, adds the task, saves the list, and displays the updated list as a markdown table.
.PARAMETER InputText
The user's input describing the task to add.
#>
function Add-Task {
param(
[string]$InputText
)
# Use psaisuite to parse the input and extract details
$prompt = @"
Extract the following from this task description:
- Task title
- Due date (if any)
- Priority (high/medium/low, if mentioned)
Text: $InputText
Return as JSON.
"@
$result = Invoke-ChatCompletion -Model $Model -Prompt $prompt
$task = $result | ConvertFrom-Json
$tasks = Get-TaskList
$newTask = [PSCustomObject]@{
Id = ($tasks | Measure-Object).Count + 1
Title = $task.'Task title'
DueDate = $task.'Due date'
Priority = $task.Priority
Status = 'Pending'
Notes = ''
}
$tasks += $newTask
Save-TaskList $tasks
Write-Host "Task added: $($newTask.Title)" -ForegroundColor Green
# Show updated list in markdown table
$prompt = @"
Convert the following to-do list JSON to a markdown table. Include all fields and format the table nicely.
$($tasks | ConvertTo-Json -Depth 5)
"@
$result = Invoke-ChatCompletion -Model $Model -Prompt $prompt
Write-Glow $result
}
<#
.SYNOPSIS
Displays all tasks in the to-do list in plain text.
.DESCRIPTION
Prints each task with its details to the console.
#>
function Get-Task {
$tasks = Get-TaskList
if ($tasks.Count -eq 0) {
Write-Host "You have no tasks yet. Add a new one!" -ForegroundColor Yellow
return
}
$tasks | ForEach-Object {
Write-Host ("{0}. {1} (Priority: {2}, Due: {3}, Status: {4})" -f $_.Id, $_.Title, $_.Priority, $_.DueDate, $_.Status)
}
}
<#
.SYNOPSIS
Marks a task as complete by its ID.
.DESCRIPTION
Finds the task by ID, marks it as complete, and saves the updated list.
.PARAMETER Id
The ID of the task to mark as complete.
#>
function Complete-Task {
param(
[int]$Id
)
$tasks = Get-TaskList
$task = $tasks | Where-Object { $_.Id -eq $Id }
if ($null -eq $task) {
Write-Host "Sorry, I couldn't find that task." -ForegroundColor Yellow
return
}
$task.Status = 'Complete'
Save-TaskList $tasks
Write-Host "Great job! Task $Id marked as complete." -ForegroundColor Green
}
# On startup, get suggestions from AI
# Show a friendly welcome message
Write-Host "Welcome to your To-Do List!" -ForegroundColor Cyan
$tasks = Get-TaskList
$today = Get-Date
$todayFmt = $today.ToString('dddd MMMM dd, yyyy hh:mm tt')
$prompt = @"
Today is $todayFmt.
Here is my current to-do list:
$($tasks | ConvertTo-Json -Depth 5)
What should I focus on today? Are any tasks urgent or overdue?
"@
$result = Invoke-ChatCompletion -Model $Model -Prompt $prompt
Write-Glow $result
# Pure NLP-driven main loop
while ($true) {
$userInput = Read-Host "What would you like to do? (e.g., 'call bank next week', 'what's on my list?')"
if ($userInput -match '^(exit|quit)$') { break }
$tasks = Get-TaskList
$today = Get-Date
$todayFmt = $today.ToString('dddd MMMM dd, yyyy hh:mm tt')
# Check if user wants to see the list
if ($userInput -match "(show|list|what's on my list | display).*") {
Write-Host "[AI] Calling AI model for: markdown table conversion" -ForegroundColor Magenta
$prompt = @"
Convert the following to-do list JSON to a markdown table. Include all fields and format the table nicely.
$($tasks | ConvertTo-Json -Depth 5)
"@
$result = Invoke-ChatCompletion -Model $Model -Prompt $prompt
Write-Host "`nYour To-Do List in Markdown:`n" -ForegroundColor Yellow
Write-Glow $result
continue
}
$prompt = @"
Today is $todayFmt.
You are a helpful to-do list assistant. Here is the current list:
$($tasks | ConvertTo-Json -Depth 5)
User input: $userInput
Interpret the user's intent (add, list, complete, query, etc.), extract all relevant details, update the list if needed, and return a response for the user. If the list should be updated, return the new list as JSON under the key 'updatedList'.
When saving a due date, always use the format: 'Tuesday July 29, 2025 12:00 pm'.
"@
$result = Invoke-ChatCompletion -Model $Model -Prompt $prompt
# Only process and display user-friendly output, suppressing raw AI/JSON output
if ($result -match '(?:"updatedList"|updatedList)\s*:\s*(\[[\s\S]*?\])') {
try {
$jsonMatch = $matches[1]
$updatedList = $jsonMatch | ConvertFrom-Json
Save-TaskList $updatedList
# Show updated list in markdown table
$prompt = @"
Convert the following to-do list JSON to a markdown table. Include all fields and format the table nicely.
$($updatedList | ConvertTo-Json -Depth 5)
"@
$mdResult = Invoke-ChatCompletion -Model $Model -Prompt $prompt
Write-Glow $mdResult
# Show the rest of the AI response (excluding the updatedList JSON block), but only if it contains user-friendly text
$responseWithoutList = $result -replace '(?:"updatedList"|updatedList)\s*:\s*\[[\s\S]*?\][\s\r\n,]*', ''
if ($responseWithoutList.Trim()) {
Write-Glow $responseWithoutList
}
}
catch {
# If something goes wrong, show a friendly error
Write-Host "Sorry, something went wrong updating your list." -ForegroundColor Red
}
}
else {
# Check if the AI response contains task information without updatedList wrapper
if ($result -match '\{.*"task".*"due".*\}') {
try {
# Try to extract the task object and wrap it in an array
$taskMatch = [regex]::Match($result, '\{[^}]*"task"[^}]*\}')
if ($taskMatch.Success) {
$taskJson = "[$($taskMatch.Value)]"
$updatedList = $taskJson | ConvertFrom-Json
Save-TaskList $updatedList
# Show updated list in markdown table
$prompt = @"
Convert the following to-do list JSON to a markdown table. Include all fields and format the table nicely.
$($updatedList | ConvertTo-Json -Depth 5)
"@
$mdResult = Invoke-ChatCompletion -Model $Model -Prompt $prompt
Write-Glow $mdResult
}
}
catch {
Write-Host "Sorry, something went wrong updating your list." -ForegroundColor Red
}
}
else {
# If no updatedList or task JSON, just show the AI's user-friendly response
Write-Glow $result
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment