fleet-worker.ps1 4.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. #!/usr/bin/env pwsh
  2. # fleet-worker.ps1 - run a cheap headless Claude Code worker on a non-Anthropic model (PowerShell).
  3. #
  4. # Thin launcher: points Claude Code (`claude -p`) at any Anthropic-compatible
  5. # endpoint (default: z.ai / GLM) via env, inside an ISOLATED config dir, then
  6. # runs it. The result is a headless agent with Claude Code's full tool harness
  7. # but a cheaper "grunt" brain. See ../SKILL.md.
  8. #
  9. # Usage: fleet-worker.ps1 [-Help] [claude-flags...] "PROMPT"
  10. # Output: whatever `claude -p` emits (text, or --output-format json/stream-json)
  11. # Exit: 0 ok; 1 worker/API error; 5 missing dep / no key resolved
  12. #
  13. # Config (env, all optional - defaults target the z.ai GLM Coding Plan):
  14. # FLEET_WORKER_CONFIG_DIR isolated CLAUDE_CONFIG_DIR (default ~/.fleet-worker/cfg)
  15. # FLEET_WORKER_BASE_URL Anthropic-compatible endpoint (default z.ai)
  16. # FLEET_WORKER_MODEL main model (default GLM-5.2)
  17. # FLEET_WORKER_SMALL_MODEL background model (default GLM-4.5-Air)
  18. # FLEET_WORKER_EFFORT seeded effortLevel (default high)
  19. # Key resolution order (the key is never printed):
  20. # 1. ANTHROPIC_AUTH_TOKEN (already set) -> used as-is
  21. # 2. FLEET_WORKER_KEYRING_SERVICE + FLEET_WORKER_KEYRING_KEY -> `keyring get svc key`
  22. # 3. ZHIPU_API_KEY / GLM_API_KEY -> used as-is
  23. #
  24. # Examples:
  25. # ./fleet-worker.ps1 "List the TODOs under src/ and summarize them"
  26. # ./fleet-worker.ps1 --output-format json "Refactor utils.py"
  27. $ErrorActionPreference = 'Stop'
  28. if ($args.Count -ge 1 -and ($args[0] -eq '-Help' -or $args[0] -eq '--help' -or $args[0] -eq '-h')) {
  29. Get-Content $PSCommandPath | Select-Object -Skip 1 |
  30. ForEach-Object { if ($_ -match '^#') { $_ -replace '^# ?', '' } else { return } }
  31. exit 0
  32. }
  33. # Auth isolation (LOAD-BEARING; see references/fleet-worker-spec.md sec 4): a dedicated
  34. # config dir => the worker inherits no host Claude.ai OAuth account, so our token
  35. # is the only credential and actually reaches the endpoint (else 401).
  36. $cfg = if ($env:FLEET_WORKER_CONFIG_DIR) { $env:FLEET_WORKER_CONFIG_DIR } `
  37. else { Join-Path $HOME '.fleet-worker/cfg' }
  38. New-Item -ItemType Directory -Force -Path $cfg | Out-Null
  39. $settings = Join-Path $cfg 'settings.json'
  40. if (-not (Test-Path $settings)) {
  41. $effort = if ($env:FLEET_WORKER_EFFORT) { $env:FLEET_WORKER_EFFORT } else { 'high' }
  42. "{ ""hooks"": {}, ""effortLevel"": ""$effort"" }" | Set-Content -Path $settings -Encoding utf8
  43. }
  44. $env:CLAUDE_CONFIG_DIR = $cfg
  45. # Resolve the API key (never printed). .Trim() strips trailing CRLF from `keyring get`.
  46. function Resolve-GlmKey {
  47. if ($env:ANTHROPIC_AUTH_TOKEN) { return $env:ANTHROPIC_AUTH_TOKEN }
  48. if ($env:FLEET_WORKER_KEYRING_SERVICE -and $env:FLEET_WORKER_KEYRING_KEY -and (Get-Command keyring -ErrorAction SilentlyContinue)) {
  49. $k = (keyring get $env:FLEET_WORKER_KEYRING_SERVICE $env:FLEET_WORKER_KEYRING_KEY 2>$null)
  50. if ($k) { return ($k | Out-String).Trim() }
  51. }
  52. if ($env:ZHIPU_API_KEY) { return $env:ZHIPU_API_KEY }
  53. if ($env:GLM_API_KEY) { return $env:GLM_API_KEY }
  54. return $null
  55. }
  56. $key = Resolve-GlmKey
  57. if (-not $key) {
  58. Write-Error @'
  59. fleet-worker: no API key resolved. Provide one of:
  60. - $env:ANTHROPIC_AUTH_TOKEN = '<key>'
  61. - $env:FLEET_WORKER_KEYRING_SERVICE / FLEET_WORKER_KEYRING_KEY (uses `keyring get`)
  62. - $env:ZHIPU_API_KEY = '<key>' (or GLM_API_KEY)
  63. '@
  64. exit 5
  65. }
  66. if (-not (Get-Command claude -ErrorAction SilentlyContinue)) {
  67. Write-Error "fleet-worker: 'claude' (Claude Code) not found on PATH"; exit 5
  68. }
  69. $env:ANTHROPIC_BASE_URL = if ($env:FLEET_WORKER_BASE_URL) { $env:FLEET_WORKER_BASE_URL } else { 'https://api.z.ai/api/anthropic' }
  70. $env:ANTHROPIC_AUTH_TOKEN = $key
  71. $env:ANTHROPIC_DEFAULT_OPUS_MODEL = if ($env:FLEET_WORKER_MODEL) { $env:FLEET_WORKER_MODEL } else { 'GLM-5.2' }
  72. $env:ANTHROPIC_DEFAULT_SONNET_MODEL = $env:ANTHROPIC_DEFAULT_OPUS_MODEL
  73. $env:ANTHROPIC_DEFAULT_HAIKU_MODEL = if ($env:FLEET_WORKER_SMALL_MODEL) { $env:FLEET_WORKER_SMALL_MODEL } else { 'GLM-4.5-Air' }
  74. claude -p --model sonnet --permission-mode bypassPermissions @args
  75. exit $LASTEXITCODE