Open-source PowerShell AI Agent
Not just an AI that writes code — an agent that installs tools, automates your browser, and runs the work on your Windows PC. Free to try.
No coding needed. Say what you want done — the agent picks the tools, writes the code, and runs it on your PC. It can even improve itself: copy its own code, install new libraries, and keep working with new skills. Copy a prompt and paste it into the agent.
Describe the site you want. The agent creates files, design, features and opens the result in the browser.
Create a modern website for a car service in English. Save it on the Desktop and open it in the browser.
Can install the tools it needs, launch a browser and automate actions online.
Install what you need and create a script that opens this website, fills a form with test data, and shows me the result.
Copies its own code, modifies it and gains new skills — e.g. browsing like a human.
Create a folder on the Desktop, copy your own code there and keep working from the copy. Modify yourself so you can browse websites like a human — install Puppeteer and use it next.
Opens several pages, reads data, compares it and prepares a result.
Find prices for this product in five Latvian online shops and make a comparison table on the Desktop.
If you do the same thing in the browser every day, the agent writes the automation.
Create a program that opens our admin page, logs in, opens the orders section and saves the latest order list.
Describe the problem in plain language — local app, calculator, data tool or small system.
Create a program where I enter a client, hours worked and hourly rate, and it prepares a work estimate as PDF.
Find, rename, copy, convert and analyse files on your PC.
Find all PDF files in Documents, organise them by year, and show me the plan before moving anything.
Writes data scripts, merges Excel/CSV files and builds reports.
Merge all Excel (.xlsx) and CSV files from this folder into one Excel file and create a sales volume summary. If tools for reading Excel are missing, install them.
Administrator rights — Windows, processes, services, disks, network and configuration.
Check what is loading my computer the most right now, and explain what is safe to optimise.
If a tool is missing, the agent can install it and use it.
Check whether I have Node.js and Git. If not, install them and verify everything works.
You don’t need to know the technical solution — describe the outcome.
Every morning I need to download a file from a website, rename it with today’s date and put it in a specific folder. Automate that.
One PowerShell window with Administrator rights. You describe the result you want — the AI agent writes and runs the commands and code itself. It is not limited to the features it shipped with: it can copy and edit its own source, install new tools, and gain capabilities while it works — without waiting for a new artificial.lv release.
You don’t need to know PowerShell or programming. Tell the agent what you want to achieve, not how to code it.
One small .cmd file — download, double‑click, chat. No PowerShell typing.
Install-artificial-en-agent.cmd./model gpt-4o · Esc / S / Q stops thinking.Fully open-source — read it, fork it, tighten it. Same PowerShell agent the installer fetches.
#Requires -Version 5.1
<#
.SYNOPSIS
artificial.lv — generic Admin AI agent in English (GPT 5.4).
.DESCRIPTION
Run as Administrator. Chat in English; the agent writes and runs PowerShell,
can build sites/tools, control the PC, and look things up online.
Run: powershell -NoProfile -ExecutionPolicy Bypass -File artificial_agent_en.ps1
#>
$ErrorActionPreference = 'Continue'
$script:ArtBundled = $true
$script:ArtApiKey = 'sk-art-free'
$script:ArtModel = 'gpt-5.4'
$script:ArtApiBase = 'https://artificial.lv/api/v1'
$script:ArtUserAgent = 'ArtificialAgentEN/1.0'
$script:ArtKnownModels = @() # filled from GET /models when needed
$script:ArtMaxHistoryMessages = 16 # keep system + last N messages (cuts token cost)
$script:ArtMaxResultChars = 2500 # truncate command output in chat history
Set-StrictMode -Version Latest
# Output UTF-8 for display; do NOT force InputEncoding=UTF8 — that breaks Read-Host with Latvian/special chars on Windows PowerShell 5.1.
try {
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$OutputEncoding = [Text.Encoding]::UTF8
chcp 65001 | Out-Null
} catch {}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Reduce accidental console "freeze" (click-select / QuickEdit). Ctrl+S still pauses old consoles — use Esc/S to stop the agent, Ctrl+Q to unfreeze.
try {
$t = Add-Type -PassThru -Name ArtConsoleMode -Namespace ArtificialLv -MemberDefinition @'
[DllImport("kernel32.dll", SetLastError=true)] public static extern IntPtr GetStdHandle(int n);
[DllImport("kernel32.dll", SetLastError=true)] public static extern bool GetConsoleMode(IntPtr h, out uint mode);
[DllImport("kernel32.dll", SetLastError=true)] public static extern bool SetConsoleMode(IntPtr h, uint mode);
'@ -ErrorAction Stop
$h = $t::GetStdHandle(-10)
$mode = [uint32]0
if ($t::GetConsoleMode($h, [ref]$mode)) {
$ENABLE_QUICK_EDIT = [uint32]0x0040
$ENABLE_EXTENDED = [uint32]0x0080
$newMode = ($mode -bor $ENABLE_EXTENDED) -band (-bnot $ENABLE_QUICK_EDIT)
[void]$t::SetConsoleMode($h, $newMode)
}
} catch {}
# --- Admin elevate ---
function Test-IsAdmin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-ArtSelfPath {
if ($PSCommandPath) { return $PSCommandPath }
if ($MyInvocation.MyCommand.Path) { return $MyInvocation.MyCommand.Path }
return $null
}
function Get-ArtLaunchContext {
$scriptPath = Get-ArtSelfPath
$scriptDir = $null
if ($scriptPath) {
try { $scriptDir = [IO.Path]::GetDirectoryName($scriptPath) } catch {}
}
if (-not $scriptDir -and $PSScriptRoot) { $scriptDir = $PSScriptRoot }
$cwd = $null
try { $cwd = (Get-Location -PSProvider FileSystem).Path } catch {
try { $cwd = [Environment]::CurrentDirectory } catch {}
}
return [pscustomobject]@{
ScriptPath = $scriptPath
ScriptDir = $scriptDir
Cwd = $cwd
Desktop = [Environment]::GetFolderPath('Desktop')
UserProfile = $env:USERPROFILE
}
}
if ($env:ARTIFICIAL_AGENT_SMOKE -ne '1' -and -not (Test-IsAdmin)) {
$self = Get-ArtSelfPath
if (-not $self) {
Write-Host 'Cannot find the agent .ps1 path. Run: powershell -File path\artificial_agent_en.ps1' -ForegroundColor Red
Write-Host 'Press Enter to close...'
try { Read-Host | Out-Null } catch {}
exit 1
}
try { Unblock-File -LiteralPath $self -ErrorAction SilentlyContinue } catch {}
Write-Host 'Requesting Administrator rights...' -ForegroundColor Yellow
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
$psi.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$self`""
$psi.UseShellExecute = $true
$psi.Verb = 'runas'
try {
[Diagnostics.Process]::Start($psi) | Out-Null
} catch {
Write-Host 'Administrator rights required (UAC cancelled?).' -ForegroundColor Red
Write-Host 'Press Enter to close...'
try { Read-Host | Out-Null } catch {}
}
exit
}
$script:ArtLaunch = Get-ArtLaunchContext
# --- API client ---
function Get-ArtConfig {
$model = $script:ArtModel
if ($env:ARTIFICIAL_MODEL) { $model = $env:ARTIFICIAL_MODEL }
$apiKey = $script:ArtApiKey
if ($env:ARTIFICIAL_API_KEY) { $apiKey = $env:ARTIFICIAL_API_KEY }
$apiBase = $script:ArtApiBase
if ($env:ARTIFICIAL_API_BASE) { $apiBase = $env:ARTIFICIAL_API_BASE }
return [pscustomobject]@{
apiKey = $apiKey
model = $model
apiBase = $apiBase
}
}
function Get-ArtModelsFromApi {
if (-not $script:Config) { $script:Config = Get-ArtConfig }
$uri = ($script:Config.apiBase.TrimEnd('/')) + '/models'
$req = [System.Net.HttpWebRequest]::Create($uri)
$req.Method = 'GET'
$req.Accept = 'application/json'
$req.Timeout = 15000
$req.UserAgent = $script:ArtUserAgent
if ($script:Config.apiKey) {
$req.Headers['Authorization'] = 'Bearer ' + $script:Config.apiKey
}
$resp = $req.GetResponse()
try {
$reader = New-Object System.IO.StreamReader($resp.GetResponseStream(), [Text.Encoding]::UTF8)
$raw = $reader.ReadToEnd()
$reader.Close()
} finally {
$resp.Close()
}
$obj = $raw | ConvertFrom-Json
$list = @()
if ($obj -and $obj.data) {
$list = @($obj.data | ForEach-Object { [string]$_.id } | Where-Object { $_ })
}
if ($list.Count -eq 0) {
throw 'API did not return a model list.'
}
$script:ArtKnownModels = $list
return $list
}
function Set-ArtModel {
param([Parameter(Mandatory)][string]$Name)
$n = $Name.Trim()
if (-not $n) { throw 'Specify a model, e.g. gpt-4o' }
$list = @()
try {
$list = @(Get-ArtModelsFromApi)
} catch {
throw ("Could not load models from API: {0}" -f $_.Exception.Message)
}
if ($list -notcontains $n) {
throw "Unknown model '$n'. Allowed: $($list -join ', ')"
}
$script:ArtModel = $n
if ($script:Config) {
$script:Config | Add-Member -NotePropertyName model -NotePropertyValue $n -Force
} else {
$script:Config = Get-ArtConfig
}
return $n
}
function ConvertFrom-ArtAiJson {
param([Parameter(Mandatory)][string]$Content)
$text = $Content.Trim()
if ($text -match '(?s)```(?:json)?\s*(.*?)\s*```') { $text = $Matches[1].Trim() }
$start = $text.IndexOf('{')
$end = $text.LastIndexOf('}')
if ($start -ge 0 -and $end -gt $start) {
$text = $text.Substring($start, $end - $start + 1)
}
return ($text | ConvertFrom-Json)
}
function Truncate-ArtText {
param([string]$Text, [int]$MaxChars = 2500)
if ($null -eq $Text) { return '' }
if ($Text.Length -le $MaxChars) { return $Text }
return ($Text.Substring(0, $MaxChars) + "`n...[truncated $($Text.Length - $MaxChars) chars]")
}
function Compact-ArtChatMessages {
if ($null -eq $script:ChatMessages -or $script:ChatMessages.Count -le 1) { return }
$max = [Math]::Max(4, [int]$script:ArtMaxHistoryMessages)
if ($script:ChatMessages.Count -le ($max + 1)) { return }
$keep = New-Object System.Collections.Generic.List[object]
$keep.Add($script:ChatMessages[0])
$start = $script:ChatMessages.Count - $max
for ($i = $start; $i -lt $script:ChatMessages.Count; $i++) {
$keep.Add($script:ChatMessages[$i])
}
$script:ChatMessages = $keep
}
function Format-ArtApiError {
param(
[int]$StatusCode = 0,
[string]$Message = '',
[string]$Body = ''
)
$blob = (($Message) + "`n" + ($Body)).Trim()
$isQuota = ($StatusCode -eq 429) -or (
$blob -match '(?i)free tier|spend limit|exhausted|rate_limit|too many requests|insufficient balance|prepaid access'
)
if ($isQuota) {
return @"
You've run out of free / prepaid tokens.
Top up:
1) Open https://artificial.lv
2) Log in to your account
3) Click «Buy API tokens»
After payment, download your personal agent from the account page (or set ARTIFICIAL_API_KEY to your sk-art-u-… key).
"@.Trim()
}
if ($Body) { return "API error: $Message" }
return "API error: $Message"
}
function Invoke-ArtChat {
param(
[Parameter(Mandatory)]$Config,
[Parameter(Mandatory)][System.Collections.IList]$Messages,
[string]$Label = 'Thinking'
)
$uri = ($Config.apiBase.TrimEnd('/')) + '/chat/completions'
$bodyObj = @{ model = $Config.model; messages = @($Messages) }
$json = $bodyObj | ConvertTo-Json -Depth 12
$bodyBytes = [Text.Encoding]::UTF8.GetBytes($json)
$raw = $null
$req = $null
try {
$req = [System.Net.HttpWebRequest]::Create($uri)
$req.Method = 'POST'
$req.Accept = 'application/json'
$req.ContentType = 'application/json; charset=utf-8'
$req.Timeout = 300000
$req.ReadWriteTimeout = 300000
$req.UserAgent = $script:ArtUserAgent
$req.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate
$null = $req.Headers.Add('Authorization', "Bearer $($Config.apiKey)")
$req.ContentLength = $bodyBytes.Length
$reqStream = $req.GetRequestStream()
try { $reqStream.Write($bodyBytes, 0, $bodyBytes.Length) } finally { $reqStream.Close() }
Write-Host -NoNewline (" {0}" -f $Label) -ForegroundColor DarkGray
$sw = [Diagnostics.Stopwatch]::StartNew()
$ar = $req.BeginGetResponse($null, $null)
$dots = 0
while (-not $ar.IsCompleted) {
if (Test-AgentStopRequested) {
try { $req.Abort() } catch {}
Write-Host (" {0}" -f $sw.Elapsed.ToString('mm\:ss')) -ForegroundColor DarkGray
throw 'ART_CANCELLED'
}
Start-Sleep -Milliseconds 120
$dots++
if (($dots % 3) -eq 0) {
try { [Console]::Write('.') } catch { Write-Host -NoNewline '.' }
}
}
Write-Host (" {0}" -f $sw.Elapsed.ToString('mm\:ss')) -ForegroundColor DarkGray
try {
$resp = $req.EndGetResponse($ar)
} catch [System.Net.WebException] {
if ($_.Exception.Status -eq [System.Net.WebExceptionStatus]::RequestCanceled) {
throw 'ART_CANCELLED'
}
throw
}
try {
$reader = New-Object System.IO.StreamReader($resp.GetResponseStream(), [Text.Encoding]::UTF8, $true)
try { $raw = $reader.ReadToEnd() } finally { $reader.Close() }
} finally { $resp.Close() }
} catch {
$em = [string]$_.Exception.Message
if (-not $em) { $em = [string]$_ }
if ($em -eq 'ART_CANCELLED' -or $em -like '*ART_CANCELLED*') { throw 'ART_CANCELLED' }
$ex = $_.Exception
while ($ex.InnerException) { $ex = $ex.InnerException }
if ($ex -is [System.Net.WebException]) {
if ($ex.Status -eq [System.Net.WebExceptionStatus]::RequestCanceled) { throw 'ART_CANCELLED' }
$msg = $ex.Message
$errBody = ''
$statusCode = 0
try {
if ($ex.Response) {
try { $statusCode = [int]$ex.Response.StatusCode } catch {}
$er = New-Object System.IO.StreamReader($ex.Response.GetResponseStream(), [Text.Encoding]::UTF8, $true)
$errBody = $er.ReadToEnd(); $er.Close()
if ($errBody) { $msg += "`n" + $errBody }
}
} catch {}
throw (Format-ArtApiError -StatusCode $statusCode -Message $msg -Body $errBody)
}
throw
}
if ([string]::IsNullOrWhiteSpace($raw)) { throw 'API response was empty.' }
$parsed = $null
try { $parsed = $raw | ConvertFrom-Json } catch {
throw "API JSON error: $($_.Exception.Message)"
}
$choices = @($parsed.choices)
if ($choices.Count -lt 1) { throw 'API returned no choices.' }
$msgObj = $choices[0].message
if ($null -eq $msgObj) { throw 'API returned no message.' }
return [string]$msgObj.content
}
# --- Optional shortcuts (agent can also write free-form PowerShell) ---
$script:ArtActions = [ordered]@{
pc_overview = @{
Id = 'pc_overview'; Title = 'PC overview'
Description = 'CPU, RAM, disk, OS — short status'
Script = {
$os = Get-CimInstance Win32_OperatingSystem
$cs = Get-CimInstance Win32_ComputerSystem
$disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'"
[pscustomobject]@{
Dators = $cs.Name
Lietotajs = $env:USERNAME
OS = "$($os.Caption) $($os.Version)"
RAM_GB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1)
C_Brivs_GB = if ($disk) { [math]::Round($disk.FreeSpace / 1GB, 1) } else { $null }
Procesi = @(Get-Process).Count
} | Format-List | Out-String
}
}
open_url = @{
Id = 'open_url'; Title = 'Open URL in browser'
Description = 'Start-Process in the default browser (e.g. https://artificial.lv)'
Script = {
$u = $env:ART_OPEN_URL
if (-not $u) { $u = 'https://artificial.lv' }
Start-Process $u
"Opened: $u"
}
}
web_get = @{
Id = 'web_get'; Title = 'Fetch web page (text)'
Description = 'Invoke-WebRequest -UseBasicParsing; URL no $env:ART_WEB_URL'
Script = {
$u = $env:ART_WEB_URL
if (-not $u) { throw 'Iestati $env:ART_WEB_URL' }
$r = Invoke-WebRequest -Uri $u -UseBasicParsing -TimeoutSec 60
$t = $r.Content
if ($t.Length -gt 6000) { $t = $t.Substring(0, 6000) + "`n...(truncated)" }
"Status=$($r.StatusCode) Len=$(@($r.Content).Length)`n$t"
}
}
list_desktop = @{
Id = 'list_desktop'; Title = 'Desktop files'
Description = 'Lists Desktop contents'
Script = {
Get-ChildItem -LiteralPath ([Environment]::GetFolderPath('Desktop')) |
Select-Object Mode, Length, LastWriteTime, Name |
Format-Table -AutoSize | Out-String
}
}
make_hello_html = @{
Id = 'make_hello_html'; Title = 'Create hello.html on Desktop'
Description = 'Simple example HTML page'
Script = {
$path = Join-Path ([Environment]::GetFolderPath('Desktop')) 'artificial-hello.html'
@'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sveiki no artificial.lv</title>
<style>
body{font-family:system-ui;margin:0;min-height:100vh;display:grid;place-items:center;
background:linear-gradient(145deg,#0f172a,#14532d);color:#ecfdf5}
h1{font-size:clamp(2rem,6vw,4rem);margin:0}
p{opacity:.8}
</style>
</head>
<body>
<main>
<h1>Sveiki!</h1>
<p>This page was created by the artificial.lv agent.</p>
</main>
</body>
</html>
'@ | Set-Content -Path $path -Encoding UTF8
Start-Process $path
"Created and opened: $path"
}
}
make_ps_tool = @{
Id = 'make_ps_tool'; Title = 'Create a small .ps1 tool on Desktop'
Description = 'Example: Show-DiskFree.ps1'
Script = {
$path = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Show-DiskFree.ps1'
@'
#Requires -Version 5.1
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
[pscustomobject]@{
Disks = $_.DeviceID
Total_GB = [math]::Round($_.Size/1GB,1)
Free_GB = [math]::Round($_.FreeSpace/1GB,1)
Proc = [math]::Round(100*$_.FreeSpace/[math]::Max($_.Size,1),1)
}
} | Format-Table -AutoSize
'@ | Set-Content -Path $path -Encoding UTF8
"Izveidots: $path"
}
}
}
function Get-ArtActionCatalogText {
$lines = @('Shortcuts (action_id) — you can also write your own powershell:')
foreach ($key in $script:ArtActions.Keys) {
$a = $script:ArtActions[$key]
$lines += "- $($a.Id): $($a.Title) — $($a.Description)"
}
return ($lines -join "`n")
}
function Invoke-ArtAction {
param([Parameter(Mandatory)][string]$ActionId)
if (-not $script:ArtActions.Contains($ActionId)) { throw "Unknown action_id: $ActionId" }
$action = $script:ArtActions[$ActionId]
$result = & $action.Script
return [pscustomobject]@{ Ok = $true; ActionId = $ActionId; Title = $action.Title; Output = [string]$result }
}
function Get-ArtAgentSystemPrompt {
param([string]$CatalogText)
$ctx = $script:ArtLaunch
if (-not $ctx) { $ctx = Get-ArtLaunchContext }
$locBlock = @"
Launch context (use these paths when writing files / reading the agent):
- Agent .ps1 file: $($ctx.ScriptPath)
- Agent folder (where it was launched): $($ctx.ScriptDir)
- Current working directory (PWD): $($ctx.Cwd)
- Desktop: $($ctx.Desktop)
- User profile: $($ctx.UserProfile)
If the user says "here" / "in this folder" without a path — default to the agent folder ($($ctx.ScriptDir)), not only Desktop.
"@
@"
You are artificial.lv — a generic AI agent with Windows Administrator rights.
Default language: English. Reply in English unless the user switches language.
You ARE at the keyboard — you are NOT a chatbot that says "open CMD yourself". Never claim you lack admin rights.
$locBlock
What you can do (write PowerShell commands[] and run them):
1) PC: processes, services, disks, network, registry, Task Scheduler, install/uninstall, files/folders.
2) Coding: create HTML/CSS/JS sites, Python/Node/C#/PowerShell scripts, .bat/.ps1 tools, save to Desktop or where the user asks, run/test.
3) Lookup: Invoke-WebRequest -UseBasicParsing (NO ParsedHtml), downloads, API calls, search (open browser or fetch URL).
4) Automation: Windows UI (Start-Process), file management, backup, cleanup, diagnostics.
5) Self-improve: you may read/edit this agent .ps1 ($($ctx.ScriptPath)); changes apply after restart.
Good examples (commands[].powershell):
A) Site on Desktop:
`$dir = Join-Path ([Environment]::GetFolderPath('Desktop')) 'my-site'
New-Item -ItemType Directory -Path `$dir -Force | Out-Null
@'
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>My page</title>
<style>body{font-family:Georgia,serif;max-width:40rem;margin:3rem auto;padding:0 1rem}</style>
</head><body><h1>Hello</h1><p>Text here.</p></body></html>
'@ | Set-Content (Join-Path `$dir 'index.html') -Encoding UTF8
Start-Process (Join-Path `$dir 'index.html')
Get-ChildItem `$dir | Format-Table Name,Length | Out-String
B) PC status:
Get-CimInstance Win32_OperatingSystem | Select Caption,Version,LastBootUpTime | Format-List | Out-String
Get-Process | Sort CPU -Descending | Select -First 8 Name,CPU,Id | Format-Table | Out-String
C) Look up info online (text):
`$r = Invoke-WebRequest -Uri 'https://example.com' -UseBasicParsing
`$r.StatusCode; `$r.Content.Substring(0,[Math]::Min(2000,`$r.Content.Length)) | Out-String
D) Small tool:
@'
Write-Host "Today: `$(Get-Date -Format 'yyyy-MM-dd HH:mm')"
'@ | Set-Content (Join-Path ([Environment]::GetFolderPath('Desktop')) 'time.ps1') -Encoding UTF8
How the agent loop works:
1) Briefly say in English what you are doing (message).
2) Put real PowerShell in commands[].
3) Read COMMAND RESULT, continue or finish (done=true).
4) Max 3 commands / turn. Always pipe to Out-String when you need to read output.
5) Set-StrictMode: NEVER .Count on a possible single object — use @(`$x).Count.
6) Do not use ParsedHtml with UseBasicParsing.
JSON ONLY (no markdown fences):
{
"message": "what you would tell the user in English",
"commands": [
{
"title": "short title",
"powershell": "# your script",
"action_id": null,
"risk": "low"
}
],
"done": false
}
- Chat only / waiting: commands=[] done=true.
- Restart/shutdown only if the user clearly asks.
- action_id (optional) from the catalog; otherwise powershell.
$CatalogText
"@
}
# --- UI ---
$script:Config = $null
$script:ChatMessages = New-Object System.Collections.Generic.List[object]
$script:MaxAgentRounds = 12
function Get-ArtLen { param($Value); return @($Value).Count }
function Get-ActionProp {
param($Action, [string]$Name, $Default = $null)
$prop = $Action.PSObject.Properties[$Name]
if ($null -eq $prop) { return $Default }
return $prop.Value
}
function Write-Sep {
param([string]$Label = '', [string]$Color = 'DarkGray', [int]$Width = 64)
if ($Label) {
$pad = [math]::Max(0, $Width - $Label.Length - 4)
$left = [math]::Floor($pad / 2)
$right = $pad - $left
$line = ('-' * $left) + "[ $Label ]" + ('-' * $right)
if ($line.Length -lt $Width) { $line += ('-' * ($Width - $line.Length)) }
Write-Host $line -ForegroundColor $Color
} else {
Write-Host ('-' * $Width) -ForegroundColor $Color
}
}
function Write-Role {
param([string]$Role, [string]$Text)
switch ($Role) {
'assistant' { Write-Host 'AI> ' -NoNewline -ForegroundColor Green; Write-Host $Text }
'cmd' { Write-Host 'CMD> ' -NoNewline -ForegroundColor Magenta; Write-Host $Text }
'out' { Write-Host 'OUT> ' -NoNewline -ForegroundColor DarkCyan; Write-Host $Text }
'error' { Write-Host 'ERR> ' -NoNewline -ForegroundColor Red; Write-Host $Text }
default { Write-Host 'SYS> ' -NoNewline -ForegroundColor DarkYellow; Write-Host $Text }
}
}
function Clear-ArtKeyBuffer {
try {
while ([Console]::KeyAvailable) { $null = [Console]::ReadKey($true) }
} catch {}
}
function Test-AgentStopRequested {
try {
while ([Console]::KeyAvailable) {
$k = [Console]::ReadKey($true)
# S / Esc / Q — NOT Ctrl+S (that freezes classic Windows consoles until Ctrl+Q)
if ($k.Key -eq 'Escape' -or $k.Key -eq 'S' -or $k.Key -eq 'Q') { return $true }
if ($k.Character -eq 's' -or $k.Character -eq 'S' -or $k.Character -eq 'q' -or $k.Character -eq 'Q') { return $true }
if ([int]$k.Modifiers -band [ConsoleModifiers]::Control) {
if ($k.Key -eq 'C') { return $true }
}
}
} catch {}
return $false
}
function Test-ArtCancelled {
param($ErrorRecord)
if ($null -eq $ErrorRecord) { return $false }
$m = [string]$ErrorRecord.Exception.Message
if (-not $m) { $m = [string]$ErrorRecord }
return ($m -eq 'ART_CANCELLED' -or $m -like '*ART_CANCELLED*')
}
function Initialize-ArtChat {
$catalog = Get-ArtActionCatalogText
$system = Get-ArtAgentSystemPrompt -CatalogText $catalog
if ($script:ChatMessages.Count -eq 0) {
$script:ChatMessages.Add(@{ role = 'system'; content = $system })
} else {
$script:ChatMessages[0] = @{ role = 'system'; content = $system }
}
}
function Invoke-AgentPowerShell {
param([string]$Title, [string]$Code, [string]$ActionId)
$title = if ($Title) { $Title } else { 'command' }
Write-Host ''
Write-Sep -Label 'CMD' -Color 'Magenta'
Write-Role cmd $title
try {
if ($ActionId -and $ActionId -ne 'custom' -and $script:ArtActions.Contains($ActionId)) {
Write-Host (" action_id: {0}" -f $ActionId) -ForegroundColor DarkGray
$result = Invoke-ArtAction -ActionId $ActionId
$out = [string]$result.Output
} else {
if (-not $Code) { throw 'Empty powershell and no action_id.' }
Write-Sep -Label 'SCRIPT' -Color 'DarkGray'
Write-Host $Code -ForegroundColor Gray
Write-Sep -Color 'DarkGray'
$out = Invoke-Expression $Code 2>&1 | Out-String
}
if ($null -eq $out) { $out = '' }
$out = [string]$out
if ($out.Length -gt 8000) { $out = $out.Substring(0, 8000) + "`n...(truncated)" }
if ([string]::IsNullOrWhiteSpace($out)) { $out = '(no output)' }
Write-Sep -Label 'OUT' -Color 'DarkCyan'
Write-Role out $out
Write-Sep -Color 'DarkCyan'
Write-Host ''
return [pscustomobject]@{ Ok = $true; Title = $title; Output = $out }
} catch {
$err = $_.Exception.Message
Write-Sep -Label 'ERR' -Color 'Red'
Write-Role error $err
Write-Sep -Color 'Red'
Write-Host ''
return [pscustomobject]@{ Ok = $false; Title = $title; Output = "ERROR: $err" }
}
}
function Invoke-AgentLoop {
param([int]$MaxRounds = 12)
try {
if (-not $script:Config) { $script:Config = Get-ArtConfig }
} catch {
Write-Role error $_.Exception.Message
return
}
Clear-ArtKeyBuffer
for ($round = 1; $round -le $MaxRounds; $round++) {
try {
if (Test-AgentStopRequested) {
Write-Role system 'Stopped. Keep typing.'
return
}
$label = if ($MaxRounds -gt 1) { "Thinking ($round/$MaxRounds)" } else { 'Thinking' }
Compact-ArtChatMessages
$raw = Invoke-ArtChat -Config $script:Config -Messages $script:ChatMessages -Label $label
if ($null -eq $raw) { $raw = '' }
$raw = [string]$raw
$script:ChatMessages.Add(@{ role = 'assistant'; content = $raw })
$parsed = $null
try { $parsed = ConvertFrom-ArtAiJson -Content $raw } catch { $parsed = $null }
if (-not $parsed) {
Write-Role assistant $raw
Write-Role system 'Reply was not JSON — paused. Tell me what to do next.'
return
}
$msg = [string](Get-ActionProp $parsed 'message' '')
if ($msg) { Write-Role assistant $msg }
$done = $false
try {
$doneProp = $parsed.PSObject.Properties['done']
if ($doneProp -and $null -ne $doneProp.Value) {
$done = [System.Convert]::ToBoolean($doneProp.Value)
}
} catch { $done = $false }
$commands = @()
try {
$cProp = $parsed.PSObject.Properties['commands']
if ($cProp -and $null -ne $cProp.Value) { $commands = @($cProp.Value) }
} catch { $commands = @() }
if ((Get-ArtLen $commands) -eq 0) { return }
$resultBlocks = New-Object System.Collections.Generic.List[string]
foreach ($c in $commands) {
if (Test-AgentStopRequested) {
Write-Role system 'Stopped before the next command. Keep typing.'
return
}
$title = [string](Get-ActionProp $c 'title' 'command')
$code = Get-ActionProp $c 'powershell' $null
if ($null -ne $code) { $code = [string]$code }
$aid = Get-ActionProp $c 'action_id' $null
if ($null -ne $aid) { $aid = [string]$aid }
$exec = Invoke-AgentPowerShell -Title $title -Code $code -ActionId $aid
$status = if ($exec.Ok) { 'OK' } else { 'ERROR' }
$histOut = Truncate-ArtText -Text ([string]$exec.Output) -MaxChars $script:ArtMaxResultChars
$resultBlocks.Add("COMMAND RESULT [$status] $title`n$histOut")
}
$follow = ($resultBlocks.ToArray() -join "`n`n")
if ($done) {
$script:ChatMessages.Add(@{ role = 'user'; content = "$follow`n(User already received the results. done=true if finished.)" })
return
}
$script:ChatMessages.Add(@{
role = 'user'
content = "$follow`nContinue the agent loop: more commands or done=true. JSON only."
})
} catch {
if (Test-ArtCancelled $_) {
Write-Role system 'Stopped (Esc/S/Q). Keep typing.'
return
}
Write-Role error $_.Exception.Message
Write-Role system 'Turn interrupted by an error — tell me what to do next.'
return
}
}
Write-Role system 'Reached the maximum number of turns. Say whether to continue.'
}
function Send-ArtMessage {
param([string]$Text)
try {
if ($null -eq $Text) { return }
$Text = [string]$Text
$Text = $Text.Trim()
if (-not $Text) { return }
if (-not $script:Config) { $script:Config = Get-ArtConfig }
if ($null -eq $script:ChatMessages) {
$script:ChatMessages = New-Object System.Collections.Generic.List[object]
}
Initialize-ArtChat
$script:ChatMessages.Add(@{ role = 'user'; content = $Text })
Invoke-AgentLoop -MaxRounds $script:MaxAgentRounds
} catch {
Write-Role error $_.Exception.Message
Write-Role system 'Error — conversation continues. Try again or /new.'
}
}
# --- boot ---
if ($env:ARTIFICIAL_AGENT_SMOKE -eq '1') {
try {
$script:ArtLaunch = Get-ArtLaunchContext
$script:Config = Get-ArtConfig
Write-Host "SMOKE_OK model=$($script:Config.model) dir=$($script:ArtLaunch.ScriptDir)"
exit 0
} catch {
Write-Host ("SMOKE_FAIL: {0}" -f $_.Exception.Message)
exit 1
}
}
try {
$script:ArtLaunch = Get-ArtLaunchContext
$script:Config = Get-ArtConfig
Initialize-ArtChat
Write-Role assistant 'Hi! I am the artificial.lv PowerShell agent. What would you like to do?'
} catch {
Write-Role error $_.Exception.Message
Write-Role system 'Started with an error — you can still type; the API will retry.'
}
while ($true) {
try {
Clear-ArtKeyBuffer
Write-Host ''
Write-Host -NoNewline '> ' -ForegroundColor Cyan
$line = $null
try { $line = Read-Host } catch {
Write-Role error $_.Exception.Message
continue
}
if ($null -eq $line) { break }
$cmd = [string]$line
$cmd = $cmd.Trim()
if (-not $cmd) { continue }
switch -Regex ($cmd) {
'^(?i)/?(quit|exit|q|iziet)$' {
Write-Host 'Goodbye.'
exit 0
}
'^(?i)/?(help)$' {
Write-Host @"
Type freely in English — the agent runs PowerShell.
/model Current model (+ list from API)
/model gpt-4o Switch model
/help This help
/new New chat (clears context)
/quit Exit
"@ -ForegroundColor DarkGray
continue
}
'^(?i)/?model(\s+(.+))?$' {
$arg = $Matches[2]
if (-not $arg) {
try {
if (-not $script:Config) { $script:Config = Get-ArtConfig }
$models = @(Get-ArtModelsFromApi)
Write-Role system ("Model: {0} | available: {1}" -f $script:Config.model, ($models -join ', '))
} catch {
Write-Role error $_.Exception.Message
}
} else {
try {
$m = Set-ArtModel -Name $arg
Write-Role system "Model set: $m"
} catch {
Write-Role error $_.Exception.Message
}
}
continue
}
'^(?i)/?(jauns|new|clear)$' {
try {
$script:ChatMessages = New-Object System.Collections.Generic.List[object]
Initialize-ArtChat
Write-Role system 'New chat. Context cleared.'
} catch {
Write-Role error $_.Exception.Message
}
continue
}
default {
Send-ArtMessage -Text $cmd
}
}
} catch [System.Management.Automation.PipelineStoppedException] {
Write-Role system 'Interrupted (Ctrl+C). Keep typing or /quit.'
} catch {
Write-Role error $_.Exception.Message
Write-Role system 'Input/loop error — keep typing.'
}
}
Write-Host ''
Write-Host 'Session ended. Press Enter to close...' -ForegroundColor DarkGray
try { Read-Host | Out-Null } catch {}
POST https://artificial.lv/api/v1/chat/completions
Authorization: Bearer sk-art-free
{ "model": "gpt-5.4", "messages": [{"role":"user","content":"Hello"}] }
Share tips and ideas. No account — pick a display name. Fold a topic to read and reply.
No topics yet — be the first.