artificial.lv

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.

Try saying this

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.

  1. Build a website

    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.
  2. Automate browser work

    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.
  3. Improve itself

    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.
  4. Gather info from the web

    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.
  5. Repeat admin tasks

    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.
  6. Build a business tool

    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.
  7. Process files

    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.
  8. Work with Excel and CSV

    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.
  9. Check the PC

    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.
  10. Install the software you need

    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.
  11. Automation from scratch

    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.

What it can do

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.

Try it in a minute

One small .cmd file — download, double‑click, chat. No PowerShell typing.

  1. Download Install-artificial-en-agent.cmd.
  2. Double‑click it. If Windows says it protected your PC → More info → Run anyway.
  3. Click Yes on Administrator (UAC). The agent opens.
  4. Chat in Latvian or English — e.g. Create a site on the Desktop · /model gpt-4o · Esc / S / Q stops thinking.

Source

Fully open-source — read it, fork it, tighten it. Same PowerShell agent the installer fetches.

Download .ps1 Raw file 845 lines · 31.8 KB · https://artificial.lv/api/v1
#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 {}

API too

POST https://artificial.lv/api/v1/chat/completions
Authorization: Bearer sk-art-free

{ "model": "gpt-5.4", "messages": [{"role":"user","content":"Hello"}] }

API status

Mini forum

Share tips and ideas. No account — pick a display name. Fold a topic to read and reply.

No topics yet — be the first.