@comate/zulu 1.4.0-beta.4 → 1.4.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/comate-engine/assets/skills/auto-commit/SKILL.md +2 -0
- package/comate-engine/assets/skills/auto-commit-sandbox-comate/SKILL.md +2 -2
- package/comate-engine/assets/skills/code-review/SKILL.md +6 -5
- package/comate-engine/assets/skills/code-review/agents/custom-reviewer.md +2 -2
- package/comate-engine/assets/skills/code-review/agents/meta-reviewer.md +2 -2
- package/comate-engine/assets/skills/code-review/agents/style-reviewer.md +72 -10
- package/comate-engine/assets/skills/code-review/references/dispatch-template.md +12 -12
- package/comate-engine/assets/skills/code-review/references/rules/Java/JAVA_STYLE_RULES.md +11 -5
- package/comate-engine/assets/skills/code-security/SKILL.md +110 -41
- package/comate-engine/assets/skills/code-security/references/credential_hosting.md +190 -28
- package/comate-engine/assets/skills/code-security/references/vul_analysis-go_sql_injection.md +149 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-java_sql_injection.md +185 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-php_sql_injection.md +147 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-python_sql_injection.md +143 -0
- package/comate-engine/assets/skills/code-security/references/vul_repair-go_sql_injection.md +2 -2
- package/comate-engine/assets/skills/code-security/references/vul_repair-sca.md +225 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +12 -10
- package/comate-engine/assets/skills/code-security/scripts/credential_open_page.py +125 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +12 -9
- package/comate-engine/assets/skills/code-security/scripts/credential_url.py +81 -0
- package/comate-engine/assets/skills/code-security/scripts/ducc/get_claude_session_id.sh +33 -0
- package/comate-engine/assets/skills/code-security/scripts/ducc/open_browser.py +191 -0
- package/comate-engine/assets/skills/code-security/scripts/parse_scan_result.py +99 -16
- package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +66 -13
- package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +44 -12
- package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/SKILL.md +8 -8
- package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/long_running_task.md +0 -15
- package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/testing_strategy.md +1 -1
- package/comate-engine/assets/skills/create-image/SKILL.md +197 -206
- package/comate-engine/assets/skills/create-image/scripts/generate-image.ps1 +213 -0
- package/comate-engine/assets/skills/create-image/scripts/generate-image.sh +322 -0
- package/comate-engine/assets/skills/create-subagent/SKILL.md +23 -5
- package/comate-engine/fallbackServer.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +1 -1
- package/comate-engine/server.js +89 -66
- package/dist/bundle/index.js +3 -3
- package/package.json +1 -1
- package/scripts/postinstall.js +4 -3
- package/comate-engine/assets/skills/code-review/evals/SKILL.md +0 -334
- package/comate-engine/assets/skills/code-review/evals/agents/gt-generator.md +0 -76
- package/comate-engine/assets/skills/code-review/evals/agents/miner.md +0 -87
- package/comate-engine/assets/skills/code-review/evals/agents/score-judge.md +0 -168
- package/comate-engine/assets/skills/code-review/evals/references/cli-query-template.md +0 -114
- package/comate-engine/assets/skills/code-review/evals/references/gt-schema.md +0 -77
- package/comate-engine/assets/skills/code-review/references/custom-rules/RULE_TEMPLATE.md +0 -141
- /package/comate-engine/assets/commands/{code-review-comate.md → code-review.md} +0 -0
- /package/comate-engine/assets/commands/{debug-comate.md → debug.md} +0 -0
- /package/comate-engine/assets/commands/{unit-test-comate.md → unit-test.md} +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/backend_dev.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/env_setup.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/frontend_dev.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/git_operations.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/scripts/check_config.py +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# generate-image.ps1 — Nano Banana Pro (Gemini 3 Pro Image) wrapper for Windows
|
|
2
|
+
#
|
|
3
|
+
# Design goals:
|
|
4
|
+
# - Idempotent: re-running is safe; existing `images\` dir is reused.
|
|
5
|
+
# - Agent-friendly errors: every failure writes a single-line tagged message
|
|
6
|
+
# to stderr: `[generate-image:<stage>] <msg>`.
|
|
7
|
+
#
|
|
8
|
+
# Exit codes mirror the bash script:
|
|
9
|
+
# 0 success (absolute output path on stdout)
|
|
10
|
+
# 2 bad args 3 missing dep 4 missing env
|
|
11
|
+
# 5 input file error 10 HTTP/network 11 API error payload
|
|
12
|
+
# 12 no image data 13 local I/O error
|
|
13
|
+
|
|
14
|
+
[CmdletBinding()]
|
|
15
|
+
param(
|
|
16
|
+
[Parameter(Mandatory=$true)] [string] $Prompt,
|
|
17
|
+
[Parameter(Mandatory=$true)] [string] $Output,
|
|
18
|
+
[string] $InputPath = "",
|
|
19
|
+
[ValidateSet("1K","2K","4K","")] [string] $Resolution = "",
|
|
20
|
+
[ValidateSet("1:1","16:9","9:16","4:3","3:4","3:2","2:3","21:9")] [string] $AspectRatio = "1:1"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
$ErrorActionPreference = "Stop"
|
|
24
|
+
|
|
25
|
+
function Write-Err([string]$Stage, [string]$Msg) {
|
|
26
|
+
[Console]::Error.WriteLine("[generate-image:{0}] {1}" -f $Stage, $Msg)
|
|
27
|
+
}
|
|
28
|
+
function Die([string]$Stage, [string]$Msg, [int]$Code) {
|
|
29
|
+
Write-Err $Stage $Msg
|
|
30
|
+
exit $Code
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$ApiUrl = "https://comate.baidu-int.com/api/aidevops/autocomate/rest/autowork/v1/generate-image"
|
|
34
|
+
$OutDir = "images"
|
|
35
|
+
|
|
36
|
+
# ---- arg validation ---------------------------------------------------------
|
|
37
|
+
if ($Output -match '[\\/]') { Die "args" "-Output must be a filename only, not a path: $Output" 2 }
|
|
38
|
+
|
|
39
|
+
# ---- dependency / env check -------------------------------------------------
|
|
40
|
+
if (-not (Get-Command curl.exe -ErrorAction SilentlyContinue)) {
|
|
41
|
+
Die "deps" "curl.exe not found (requires Windows 10 1803+)" 3
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function Resolve-LoginName {
|
|
45
|
+
if ($env:COMATE_USERNAME_ENCRYPTED) { return $env:COMATE_USERNAME_ENCRYPTED }
|
|
46
|
+
$homeDir = if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME }
|
|
47
|
+
$f = Join-Path $homeDir ".comate/login"
|
|
48
|
+
if (-not (Test-Path -LiteralPath $f)) { return $null }
|
|
49
|
+
try {
|
|
50
|
+
$token = ((Get-Content -Raw -LiteralPath $f).Trim()) -replace '^Bearer-',''
|
|
51
|
+
$parts = $token.Split('.')
|
|
52
|
+
if ($parts.Count -lt 2) { return $null }
|
|
53
|
+
$p = $parts[1].Replace('-','+').Replace('_','/')
|
|
54
|
+
switch ($p.Length % 4) { 2 { $p += '==' } 3 { $p += '=' } }
|
|
55
|
+
$json = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($p))
|
|
56
|
+
return ($json | ConvertFrom-Json).content.identity
|
|
57
|
+
} catch { return $null }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$LoginName = Resolve-LoginName
|
|
61
|
+
if (-not $LoginName) {
|
|
62
|
+
Die "env" "COMATE_USERNAME_ENCRYPTED not set and ~/.comate/login unavailable/unparseable" 4
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# ---- input file check -------------------------------------------------------
|
|
66
|
+
if ($InputPath) {
|
|
67
|
+
if (-not (Test-Path -LiteralPath $InputPath -PathType Leaf)) {
|
|
68
|
+
Die "input" "input image not found: $InputPath" 5
|
|
69
|
+
}
|
|
70
|
+
$inItem = Get-Item -LiteralPath $InputPath
|
|
71
|
+
if ($inItem.Length -le 0) { Die "input" "input image is empty: $InputPath" 5 }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ---- output dir (idempotent) ------------------------------------------------
|
|
75
|
+
if (Test-Path -LiteralPath $OutDir) {
|
|
76
|
+
if (-not (Test-Path -LiteralPath $OutDir -PathType Container)) {
|
|
77
|
+
Die "output" "'$OutDir' exists but is not a directory" 13
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
try { New-Item -ItemType Directory -Path $OutDir -ErrorAction Stop | Out-Null }
|
|
81
|
+
catch { Die "output" "failed to create dir '$OutDir': $($_.Exception.Message)" 13 }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# ---- resolution auto-detect (image-to-image only) ---------------------------
|
|
85
|
+
if ($InputPath -and -not $Resolution) {
|
|
86
|
+
try {
|
|
87
|
+
Add-Type -AssemblyName System.Drawing
|
|
88
|
+
$img = [System.Drawing.Image]::FromFile((Resolve-Path -LiteralPath $InputPath))
|
|
89
|
+
$maxDim = [Math]::Max($img.Width, $img.Height)
|
|
90
|
+
$img.Dispose()
|
|
91
|
+
$Resolution = if ($maxDim -ge 3000) { "4K" } elseif ($maxDim -ge 1500) { "2K" } else { "1K" }
|
|
92
|
+
} catch {
|
|
93
|
+
Write-Err "input" "resolution auto-detect failed ($($_.Exception.Message)); falling back to 1K"
|
|
94
|
+
$Resolution = "1K"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (-not $Resolution) { $Resolution = "1K" }
|
|
98
|
+
|
|
99
|
+
# ---- build payload ----------------------------------------------------------
|
|
100
|
+
$tmpFile = Join-Path $env:TEMP ("genimg-" + [guid]::NewGuid().ToString() + ".json")
|
|
101
|
+
$tmpResp = Join-Path $env:TEMP ("genimg-r-" + [guid]::NewGuid().ToString() + ".json")
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
if ($InputPath) {
|
|
105
|
+
try {
|
|
106
|
+
$bytes = [IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $InputPath))
|
|
107
|
+
$b64 = [Convert]::ToBase64String($bytes)
|
|
108
|
+
} catch {
|
|
109
|
+
Die "input" "base64 encoding failed: $($_.Exception.Message)" 5
|
|
110
|
+
}
|
|
111
|
+
$mime = switch -Regex ($InputPath) {
|
|
112
|
+
'\.jpe?g$' { 'image/jpeg'; break }
|
|
113
|
+
'\.webp$' { 'image/webp'; break }
|
|
114
|
+
default { 'image/png' }
|
|
115
|
+
}
|
|
116
|
+
$payload = @{
|
|
117
|
+
contents = @(@{
|
|
118
|
+
role = "USER"
|
|
119
|
+
parts = @(
|
|
120
|
+
@{ inline_data = @{ mime_type = $mime; data = $b64 } },
|
|
121
|
+
@{ text = $Prompt }
|
|
122
|
+
)
|
|
123
|
+
})
|
|
124
|
+
resolution = $Resolution
|
|
125
|
+
aspectRatio = $AspectRatio
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
$payload = @{
|
|
129
|
+
contents = @(@{
|
|
130
|
+
role = "USER"
|
|
131
|
+
parts = @(@{ text = $Prompt })
|
|
132
|
+
})
|
|
133
|
+
resolution = $Resolution
|
|
134
|
+
aspectRatio = $AspectRatio
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
$payload | ConvertTo-Json -Depth 10 -Compress |
|
|
140
|
+
Set-Content -Path $tmpFile -Encoding UTF8 -NoNewline -ErrorAction Stop
|
|
141
|
+
} catch {
|
|
142
|
+
Die "payload" "failed to write request JSON: $($_.Exception.Message)" 13
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# ---- HTTP call (capture status + body separately) -----------------------
|
|
146
|
+
$httpCode = ""
|
|
147
|
+
try {
|
|
148
|
+
$httpCode = & curl.exe -sS -o "$tmpResp" -w "%{http_code}" -X POST $ApiUrl `
|
|
149
|
+
-H "Content-Type: application/json" `
|
|
150
|
+
-H "login-name: $LoginName" `
|
|
151
|
+
-d "@$tmpFile" 2>&1
|
|
152
|
+
} catch {
|
|
153
|
+
Die "http" "curl invocation failed: $($_.Exception.Message)" 10
|
|
154
|
+
}
|
|
155
|
+
if ($LASTEXITCODE -ne 0) {
|
|
156
|
+
Die "http" "curl exit=$LASTEXITCODE output=$httpCode" 10
|
|
157
|
+
}
|
|
158
|
+
if ($httpCode -notmatch '^2\d\d$') {
|
|
159
|
+
$snippet = ""
|
|
160
|
+
if (Test-Path -LiteralPath $tmpResp) {
|
|
161
|
+
$snippet = (Get-Content -LiteralPath $tmpResp -Raw -ErrorAction SilentlyContinue)
|
|
162
|
+
if ($snippet.Length -gt 2000) { $snippet = $snippet.Substring(0,2000) }
|
|
163
|
+
$snippet = $snippet -replace "\r?\n"," "
|
|
164
|
+
}
|
|
165
|
+
Write-Err "http" "HTTP $httpCode from API"
|
|
166
|
+
Write-Err "http" "body: $snippet"
|
|
167
|
+
exit 10
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# ---- parse response -----------------------------------------------------
|
|
171
|
+
$raw = Get-Content -LiteralPath $tmpResp -Raw
|
|
172
|
+
try {
|
|
173
|
+
$response = $raw | ConvertFrom-Json -ErrorAction Stop
|
|
174
|
+
} catch {
|
|
175
|
+
$snip = if ($raw.Length -gt 2000) { $raw.Substring(0,2000) } else { $raw }
|
|
176
|
+
Write-Err "api" "response is not valid JSON"
|
|
177
|
+
Write-Err "api" "body: $($snip -replace '\r?\n',' ')"
|
|
178
|
+
exit 11
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ($response.error) {
|
|
182
|
+
$msg = if ($response.error.message) { $response.error.message } else { ($response.error | ConvertTo-Json -Compress) }
|
|
183
|
+
Die "api" "server returned error: $msg" 11
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
$imgPart = $response.candidates[0].content.parts |
|
|
187
|
+
Where-Object { $_.inlineData } | Select-Object -First 1
|
|
188
|
+
if (-not $imgPart) {
|
|
189
|
+
$text = ($response.candidates[0].content.parts |
|
|
190
|
+
Where-Object { $_.text } | Select-Object -ExpandProperty text) -join " "
|
|
191
|
+
if ($text) { Write-Err "api" "no image returned; model said: $text" }
|
|
192
|
+
$snip = if ($raw.Length -gt 2000) { $raw.Substring(0,2000) } else { $raw }
|
|
193
|
+
Write-Err "api" "no inlineData in response. body: $($snip -replace '\r?\n',' ')"
|
|
194
|
+
exit 12
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# ---- write output -------------------------------------------------------
|
|
198
|
+
$outPath = Join-Path (Get-Location) (Join-Path $OutDir $Output)
|
|
199
|
+
try {
|
|
200
|
+
[IO.File]::WriteAllBytes($outPath, [Convert]::FromBase64String($imgPart.inlineData.data))
|
|
201
|
+
} catch {
|
|
202
|
+
Die "write" "failed to write output file: $($_.Exception.Message)" 13
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (-not (Test-Path -LiteralPath $outPath) -or (Get-Item -LiteralPath $outPath).Length -le 0) {
|
|
206
|
+
Die "write" "output file empty after decode: $outPath" 13
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Write-Output $outPath
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
Remove-Item -Force -ErrorAction SilentlyContinue $tmpFile, $tmpResp
|
|
213
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# generate-image.sh — Nano Banana Pro (Gemini 3 Pro Image) wrapper for macOS/Linux
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# generate-image.sh --prompt "..." --output name.png \
|
|
7
|
+
# [--input path/to/ref.png] \
|
|
8
|
+
# [--resolution 1K|2K|4K] \
|
|
9
|
+
# [--aspect-ratio 1:1|16:9|9:16|4:3|3:4|3:2|2:3|21:9]
|
|
10
|
+
#
|
|
11
|
+
# Design goals:
|
|
12
|
+
# - Idempotent: safe to run repeatedly; existing `images/` dir is fine.
|
|
13
|
+
# - Agent-friendly errors: every failure path writes a single-line tagged
|
|
14
|
+
# message to stderr prefixed with `[generate-image:<stage>]` so the caller
|
|
15
|
+
# can pattern-match and react.
|
|
16
|
+
# - Portable: requires only curl + base64 + (python3 OR jq). No hard jq dep.
|
|
17
|
+
#
|
|
18
|
+
# Exit codes:
|
|
19
|
+
# 0 success — absolute output path on stdout
|
|
20
|
+
# 2 bad CLI args
|
|
21
|
+
# 3 missing dependency (curl/base64/python3|jq)
|
|
22
|
+
# 4 missing/invalid env (COMATE_USERNAME_ENCRYPTED)
|
|
23
|
+
# 5 input file missing / unreadable
|
|
24
|
+
# 10 HTTP/network failure
|
|
25
|
+
# 11 API returned error payload
|
|
26
|
+
# 12 API response had no image data
|
|
27
|
+
# 13 output file empty after write
|
|
28
|
+
|
|
29
|
+
set -uo pipefail
|
|
30
|
+
|
|
31
|
+
err() { printf '[generate-image:%s] %s\n' "$1" "$2" >&2; }
|
|
32
|
+
die() { err "$1" "$2"; exit "$3"; }
|
|
33
|
+
|
|
34
|
+
API_URL="https://comate.baidu-int.com/api/aidevops/autocomate/rest/autowork/v1/generate-image"
|
|
35
|
+
OUT_DIR="images"
|
|
36
|
+
|
|
37
|
+
PROMPT=""
|
|
38
|
+
OUTPUT=""
|
|
39
|
+
INPUT=""
|
|
40
|
+
RESOLUTION=""
|
|
41
|
+
ASPECT="1:1"
|
|
42
|
+
|
|
43
|
+
while [[ $# -gt 0 ]]; do
|
|
44
|
+
case "$1" in
|
|
45
|
+
-p|--prompt) PROMPT="${2:-}"; shift 2 ;;
|
|
46
|
+
-o|--output) OUTPUT="${2:-}"; shift 2 ;;
|
|
47
|
+
-i|--input) INPUT="${2:-}"; shift 2 ;;
|
|
48
|
+
-r|--resolution) RESOLUTION="${2:-}"; shift 2 ;;
|
|
49
|
+
-a|--aspect-ratio) ASPECT="${2:-}"; shift 2 ;;
|
|
50
|
+
-h|--help)
|
|
51
|
+
sed -n '3,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
52
|
+
*) die "args" "unknown argument: $1" 2 ;;
|
|
53
|
+
esac
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
# ---- arg validation ---------------------------------------------------------
|
|
57
|
+
[[ -z "$PROMPT" ]] && die "args" "--prompt is required" 2
|
|
58
|
+
[[ -z "$OUTPUT" ]] && die "args" "--output is required" 2
|
|
59
|
+
[[ "$OUTPUT" == */* ]] && die "args" "--output must be a filename only, not a path: $OUTPUT" 2
|
|
60
|
+
|
|
61
|
+
if [[ -n "$RESOLUTION" ]]; then
|
|
62
|
+
case "$RESOLUTION" in 1K|2K|4K) ;; *) die "args" "invalid --resolution: $RESOLUTION (expect 1K|2K|4K)" 2 ;; esac
|
|
63
|
+
fi
|
|
64
|
+
case "$ASPECT" in
|
|
65
|
+
1:1|16:9|9:16|4:3|3:4|3:2|2:3|21:9) ;;
|
|
66
|
+
*) die "args" "invalid --aspect-ratio: $ASPECT" 2 ;;
|
|
67
|
+
esac
|
|
68
|
+
|
|
69
|
+
# ---- dependency check -------------------------------------------------------
|
|
70
|
+
for bin in curl base64; do
|
|
71
|
+
command -v "$bin" >/dev/null 2>&1 || die "deps" "required binary not found: $bin" 3
|
|
72
|
+
done
|
|
73
|
+
# JSON tool: prefer python3 (universally available on Linux/macOS), fallback jq.
|
|
74
|
+
JSON_TOOL=""
|
|
75
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
76
|
+
JSON_TOOL="python3"
|
|
77
|
+
elif command -v jq >/dev/null 2>&1; then
|
|
78
|
+
JSON_TOOL="jq"
|
|
79
|
+
else
|
|
80
|
+
die "deps" "need either 'python3' or 'jq' for JSON handling (install one)" 3
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# ---- JSON helpers (branch on $JSON_TOOL) ------------------------------------
|
|
84
|
+
# build_payload_text <outfile> — uses PROMPT/RESOLUTION/ASPECT env
|
|
85
|
+
# build_payload_image <outfile> <b64> <mime> — uses PROMPT/RESOLUTION/ASPECT env
|
|
86
|
+
# parse_error <respfile> -> prints error message or empty
|
|
87
|
+
# parse_image <respfile> -> prints base64 data or empty
|
|
88
|
+
# parse_text <respfile> -> prints concatenated text parts (reasoning)
|
|
89
|
+
# parse_jwt_identity <raw-b64-decoded-json on stdin> -> prints identity
|
|
90
|
+
|
|
91
|
+
build_payload_text() {
|
|
92
|
+
local out="$1"
|
|
93
|
+
if [[ "$JSON_TOOL" == "python3" ]]; then
|
|
94
|
+
PROMPT="$PROMPT" RES="$RESOLUTION" ASP="$ASPECT" python3 - <<'PY' > "$out"
|
|
95
|
+
import json, os
|
|
96
|
+
print(json.dumps({
|
|
97
|
+
"contents":[{"role":"USER","parts":[{"text":os.environ["PROMPT"]}]}],
|
|
98
|
+
"resolution":os.environ["RES"],
|
|
99
|
+
"aspectRatio":os.environ["ASP"]
|
|
100
|
+
}))
|
|
101
|
+
PY
|
|
102
|
+
else
|
|
103
|
+
jq -n --arg t "$PROMPT" --arg r "$RESOLUTION" --arg a "$ASPECT" \
|
|
104
|
+
'{contents:[{role:"USER",parts:[{text:$t}]}],resolution:$r,aspectRatio:$a}' > "$out"
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# NOTE: base64 payloads can exceed ARG_MAX (E2BIG) if passed via argv/env,
|
|
109
|
+
# so we always pass the base64 via a file path.
|
|
110
|
+
build_payload_image() {
|
|
111
|
+
local out="$1" b64_file="$2" mime="$3"
|
|
112
|
+
if [[ "$JSON_TOOL" == "python3" ]]; then
|
|
113
|
+
PROMPT="$PROMPT" RES="$RESOLUTION" ASP="$ASPECT" MIME="$mime" B64_FILE="$b64_file" python3 - <<'PY' > "$out"
|
|
114
|
+
import json, os
|
|
115
|
+
with open(os.environ["B64_FILE"], "r") as f:
|
|
116
|
+
b64 = f.read()
|
|
117
|
+
print(json.dumps({
|
|
118
|
+
"contents":[{"role":"USER","parts":[
|
|
119
|
+
{"inline_data":{"mime_type":os.environ["MIME"],"data":b64}},
|
|
120
|
+
{"text":os.environ["PROMPT"]}
|
|
121
|
+
]}],
|
|
122
|
+
"resolution":os.environ["RES"],
|
|
123
|
+
"aspectRatio":os.environ["ASP"]
|
|
124
|
+
}))
|
|
125
|
+
PY
|
|
126
|
+
else
|
|
127
|
+
# --rawfile reads the whole file as a raw string, bypassing argv length limits.
|
|
128
|
+
jq -n --arg t "$PROMPT" --rawfile b "$b64_file" --arg m "$mime" --arg r "$RESOLUTION" --arg a "$ASPECT" \
|
|
129
|
+
'{contents:[{role:"USER",parts:[{inline_data:{mime_type:$m,data:$b}},{text:$t}]}],resolution:$r,aspectRatio:$a}' > "$out"
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
parse_error() {
|
|
134
|
+
local f="$1"
|
|
135
|
+
if [[ "$JSON_TOOL" == "python3" ]]; then
|
|
136
|
+
python3 - "$f" <<'PY'
|
|
137
|
+
import json, sys
|
|
138
|
+
try:
|
|
139
|
+
d = json.load(open(sys.argv[1]))
|
|
140
|
+
e = d.get("error")
|
|
141
|
+
if not e: sys.exit(0)
|
|
142
|
+
if isinstance(e, dict):
|
|
143
|
+
print(e.get("message") or json.dumps(e, ensure_ascii=False))
|
|
144
|
+
else:
|
|
145
|
+
print(str(e))
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
PY
|
|
149
|
+
else
|
|
150
|
+
jq -r 'if .error then (.error.message // (.error|tostring)) else empty end' "$f" 2>/dev/null
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
parse_image() {
|
|
155
|
+
local f="$1"
|
|
156
|
+
if [[ "$JSON_TOOL" == "python3" ]]; then
|
|
157
|
+
python3 - "$f" <<'PY'
|
|
158
|
+
import json, sys
|
|
159
|
+
try:
|
|
160
|
+
d = json.load(open(sys.argv[1]))
|
|
161
|
+
for p in (d.get("candidates") or [{}])[0].get("content",{}).get("parts",[]) or []:
|
|
162
|
+
data = (p.get("inlineData") or {}).get("data") or (p.get("inline_data") or {}).get("data")
|
|
163
|
+
if data:
|
|
164
|
+
sys.stdout.write(data)
|
|
165
|
+
break
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
PY
|
|
169
|
+
else
|
|
170
|
+
jq -r '.candidates[0].content.parts[]? | select(.inlineData // .inline_data) | (.inlineData // .inline_data).data' "$f" 2>/dev/null | head -1
|
|
171
|
+
fi
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
parse_text() {
|
|
175
|
+
local f="$1"
|
|
176
|
+
if [[ "$JSON_TOOL" == "python3" ]]; then
|
|
177
|
+
python3 - "$f" <<'PY'
|
|
178
|
+
import json, sys
|
|
179
|
+
try:
|
|
180
|
+
d = json.load(open(sys.argv[1]))
|
|
181
|
+
out=[]
|
|
182
|
+
for p in (d.get("candidates") or [{}])[0].get("content",{}).get("parts",[]) or []:
|
|
183
|
+
if "text" in p and p["text"]:
|
|
184
|
+
out.append(p["text"])
|
|
185
|
+
sys.stdout.write("\n".join(out))
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
PY
|
|
189
|
+
else
|
|
190
|
+
jq -r '.candidates[0].content.parts[]? | select(.text) | .text' "$f" 2>/dev/null
|
|
191
|
+
fi
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
parse_jwt_identity_from_stdin() {
|
|
195
|
+
if [[ "$JSON_TOOL" == "python3" ]]; then
|
|
196
|
+
# Use -c (not heredoc) so stdin remains the piped JWT bytes, not the script.
|
|
197
|
+
python3 -c 'import json,sys
|
|
198
|
+
try:
|
|
199
|
+
d=json.load(sys.stdin)
|
|
200
|
+
sys.stdout.write((d.get("content") or {}).get("identity") or "")
|
|
201
|
+
except Exception:
|
|
202
|
+
pass'
|
|
203
|
+
else
|
|
204
|
+
jq -r '.content.identity // empty' 2>/dev/null
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# ---- resolve login-name (env > ~/.comate/login JWT) -------------------------
|
|
209
|
+
resolve_login_name() {
|
|
210
|
+
if [[ -n "${COMATE_USERNAME_ENCRYPTED:-}" ]]; then
|
|
211
|
+
printf '%s' "$COMATE_USERNAME_ENCRYPTED"; return 0
|
|
212
|
+
fi
|
|
213
|
+
local f="${HOME}/.comate/login"
|
|
214
|
+
[[ -f "$f" ]] || return 1
|
|
215
|
+
local token payload pad ident
|
|
216
|
+
token=$(tr -d '[:space:]' < "$f" | sed 's/^Bearer-//')
|
|
217
|
+
payload="${token#*.}"; payload="${payload%%.*}"
|
|
218
|
+
[[ -z "$payload" ]] && return 1
|
|
219
|
+
# base64url -> base64 + pad to multiple of 4
|
|
220
|
+
payload=$(printf '%s' "$payload" | tr '_-' '/+')
|
|
221
|
+
pad=$(( (4 - ${#payload} % 4) % 4 ))
|
|
222
|
+
while [[ $pad -gt 0 ]]; do payload+='='; pad=$((pad-1)); done
|
|
223
|
+
ident=$(printf '%s' "$payload" | base64 --decode 2>/dev/null | parse_jwt_identity_from_stdin)
|
|
224
|
+
[[ -n "$ident" ]] && { printf '%s' "$ident"; return 0; }
|
|
225
|
+
return 1
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
LOGIN_NAME=$(resolve_login_name) || die "env" "COMATE_USERNAME_ENCRYPTED not set and ~/.comate/login unavailable/unparseable" 4
|
|
229
|
+
|
|
230
|
+
if [[ -n "$INPUT" ]]; then
|
|
231
|
+
[[ -f "$INPUT" ]] || die "input" "input image not found: $INPUT" 5
|
|
232
|
+
[[ -r "$INPUT" ]] || die "input" "input image not readable: $INPUT" 5
|
|
233
|
+
[[ -s "$INPUT" ]] || die "input" "input image is empty: $INPUT" 5
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
# ---- output dir (idempotent) ------------------------------------------------
|
|
237
|
+
if [[ -e "$OUT_DIR" && ! -d "$OUT_DIR" ]]; then
|
|
238
|
+
die "output" "'$OUT_DIR' exists but is not a directory" 13
|
|
239
|
+
fi
|
|
240
|
+
mkdir -p "$OUT_DIR" || die "output" "failed to create output dir: $OUT_DIR" 13
|
|
241
|
+
|
|
242
|
+
# ---- resolution auto-detect (image-to-image only) ---------------------------
|
|
243
|
+
if [[ -n "$INPUT" && -z "$RESOLUTION" ]]; then
|
|
244
|
+
MAX_DIM=0
|
|
245
|
+
if command -v sips >/dev/null 2>&1; then
|
|
246
|
+
MAX_DIM=$(sips -g pixelWidth -g pixelHeight "$INPUT" 2>/dev/null | awk '/pixel/{print $2}' | sort -rn | head -1 || echo 0)
|
|
247
|
+
elif command -v identify >/dev/null 2>&1; then
|
|
248
|
+
MAX_DIM=$(identify -format "%[fx:max(w,h)]" "$INPUT" 2>/dev/null || echo 0)
|
|
249
|
+
fi
|
|
250
|
+
if [[ "$MAX_DIM" =~ ^[0-9]+$ ]] && [[ "$MAX_DIM" -ge 3000 ]]; then RESOLUTION="4K"
|
|
251
|
+
elif [[ "$MAX_DIM" =~ ^[0-9]+$ ]] && [[ "$MAX_DIM" -ge 1500 ]]; then RESOLUTION="2K"
|
|
252
|
+
else RESOLUTION="1K"
|
|
253
|
+
fi
|
|
254
|
+
fi
|
|
255
|
+
RESOLUTION="${RESOLUTION:-1K}"
|
|
256
|
+
|
|
257
|
+
# ---- build payload ----------------------------------------------------------
|
|
258
|
+
TMP_JSON=$(mktemp -t genimg.XXXXXX) || die "tmp" "mktemp failed" 13
|
|
259
|
+
TMP_RESP=$(mktemp -t genimg-resp.XXXXXX) || die "tmp" "mktemp failed" 13
|
|
260
|
+
TMP_B64=$(mktemp -t genimg-b64.XXXXXX) || die "tmp" "mktemp failed" 13
|
|
261
|
+
trap 'rm -f "$TMP_JSON" "$TMP_RESP" "$TMP_B64"' EXIT
|
|
262
|
+
|
|
263
|
+
if [[ -n "$INPUT" ]]; then
|
|
264
|
+
# Write base64 to a file (not an env var / argv) to avoid ARG_MAX (E2BIG) for large images.
|
|
265
|
+
# macOS base64 supports `-i <file>`; GNU coreutils uses `base64 <file>`.
|
|
266
|
+
if ! base64 -i "$INPUT" 2>/dev/null | tr -d '\n' > "$TMP_B64"; then
|
|
267
|
+
base64 "$INPUT" 2>/dev/null | tr -d '\n' > "$TMP_B64" \
|
|
268
|
+
|| die "input" "base64 encoding failed for: $INPUT" 5
|
|
269
|
+
fi
|
|
270
|
+
[[ -s "$TMP_B64" ]] || die "input" "base64 produced empty output for: $INPUT" 5
|
|
271
|
+
MIME="image/png"
|
|
272
|
+
case "${INPUT##*.}" in
|
|
273
|
+
jpg|jpeg|JPG|JPEG) MIME="image/jpeg" ;;
|
|
274
|
+
webp|WEBP) MIME="image/webp" ;;
|
|
275
|
+
esac
|
|
276
|
+
build_payload_image "$TMP_JSON" "$TMP_B64" "$MIME" || die "payload" "failed to build request JSON" 13
|
|
277
|
+
else
|
|
278
|
+
build_payload_text "$TMP_JSON" || die "payload" "failed to build request JSON" 13
|
|
279
|
+
fi
|
|
280
|
+
[[ -s "$TMP_JSON" ]] || die "payload" "payload file is empty" 13
|
|
281
|
+
|
|
282
|
+
# ---- HTTP call --------------------------------------------------------------
|
|
283
|
+
HTTP_CODE=$(curl -sS -o "$TMP_RESP" -w '%{http_code}' -X POST "$API_URL" \
|
|
284
|
+
-H "Content-Type: application/json" \
|
|
285
|
+
-H "login-name: ${LOGIN_NAME}" \
|
|
286
|
+
-d "@$TMP_JSON" 2>&1) || {
|
|
287
|
+
err "http" "curl failed: $HTTP_CODE"
|
|
288
|
+
exit 10
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if [[ "$HTTP_CODE" != 2* ]]; then
|
|
292
|
+
BODY_SNIP=$(head -c 2000 "$TMP_RESP" | tr '\n' ' ')
|
|
293
|
+
err "http" "HTTP $HTTP_CODE from API"
|
|
294
|
+
err "http" "body: $BODY_SNIP"
|
|
295
|
+
exit 10
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
# ---- parse response ---------------------------------------------------------
|
|
299
|
+
ERR_MSG=$(parse_error "$TMP_RESP")
|
|
300
|
+
if [[ -n "$ERR_MSG" ]]; then
|
|
301
|
+
err "api" "server returned error: $(printf '%s' "$ERR_MSG" | head -c 1000)"
|
|
302
|
+
exit 11
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
B64=$(parse_image "$TMP_RESP")
|
|
306
|
+
if [[ -z "$B64" ]]; then
|
|
307
|
+
TEXT=$(parse_text "$TMP_RESP" | head -c 1000)
|
|
308
|
+
BODY_SNIP=$(head -c 2000 "$TMP_RESP" | tr '\n' ' ')
|
|
309
|
+
[[ -n "$TEXT" ]] && err "api" "no image returned; model said: $TEXT"
|
|
310
|
+
err "api" "no inlineData in response. body: $BODY_SNIP"
|
|
311
|
+
exit 12
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
# ---- write output -----------------------------------------------------------
|
|
315
|
+
OUT_PATH="$OUT_DIR/$OUTPUT"
|
|
316
|
+
printf '%s' "$B64" | base64 --decode > "$OUT_PATH" || die "write" "base64 decode failed" 13
|
|
317
|
+
|
|
318
|
+
if [[ ! -s "$OUT_PATH" ]]; then
|
|
319
|
+
die "write" "output file is empty after decode: $OUT_PATH" 13
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
printf '%s\n' "$(cd "$(dirname "$OUT_PATH")" && pwd)/$(basename "$OUT_PATH")"
|
|
@@ -30,6 +30,18 @@ When multiple subagents share the same name, the higher-priority location wins.
|
|
|
30
30
|
|
|
31
31
|
**Personal subagents** (`~/.comate/agents/`): Personal agents available across all your projects.
|
|
32
32
|
|
|
33
|
+
## Available Models
|
|
34
|
+
|
|
35
|
+
Subagents can optionally use a different model than the main agent via the `model` frontmatter field. Valid values are:
|
|
36
|
+
|
|
37
|
+
- `inherit` — use the main agent's current model (default when `model` is omitted or the value is unrecognized)
|
|
38
|
+
- `fast` — a cheaper, faster model
|
|
39
|
+
- An exact model name from the list below:
|
|
40
|
+
|
|
41
|
+
${COMATE_AVAILABLE_MODELS}
|
|
42
|
+
|
|
43
|
+
Models listed with a `-Thinking` suffix support extended thinking/reasoning mode.
|
|
44
|
+
|
|
33
45
|
## Subagent File Format
|
|
34
46
|
|
|
35
47
|
Create a `.md` file with YAML frontmatter and a markdown body (the system prompt):
|
|
@@ -38,6 +50,7 @@ Create a `.md` file with YAML frontmatter and a markdown body (the system prompt
|
|
|
38
50
|
---
|
|
39
51
|
name: code-reviewer
|
|
40
52
|
description: Reviews code for quality and best practices
|
|
53
|
+
model: Model this subagent uses
|
|
41
54
|
---
|
|
42
55
|
|
|
43
56
|
You are a code reviewer. When invoked, analyze the code and provide
|
|
@@ -50,6 +63,7 @@ specific, actionable feedback on quality, security, and best practices.
|
|
|
50
63
|
|-------|-------------|
|
|
51
64
|
| `name` | Unique identifier (lowercase letters and hyphens only) |
|
|
52
65
|
| `description` | When to delegate to this subagent (be specific!) |
|
|
66
|
+
| `model` | (Optional) Model this subagent uses. Must be one of the valid values from the **Available Models** section above: `inherit` (default), `fast`, or an exact model name from that list (including `-Thinking` variants). Unrecognized names fall back to `inherit`. |
|
|
53
67
|
|
|
54
68
|
## Writing Effective Descriptions
|
|
55
69
|
|
|
@@ -199,7 +213,11 @@ Below are candidate tools name, according to the user's query, decide the subage
|
|
|
199
213
|
If you are not sure with some tools, ask her directly.
|
|
200
214
|
Empty means only selecting default read-only tools; Lack of this key means selecting all tools
|
|
201
215
|
|
|
202
|
-
### Step 3:
|
|
216
|
+
### Step 3: Clarify the Agent's Model
|
|
217
|
+
**If the user requests a specific model, confirm the exact name from the **Available Models** list above**.
|
|
218
|
+
Otherwise, tell her she can use `inherit` or `fast`.
|
|
219
|
+
|
|
220
|
+
### Step 4: Create the File
|
|
203
221
|
|
|
204
222
|
```bash
|
|
205
223
|
# For project-level
|
|
@@ -211,11 +229,11 @@ mkdir -p ~/.comate/agents
|
|
|
211
229
|
touch ~/.comate/agents/my-agent.md
|
|
212
230
|
```
|
|
213
231
|
|
|
214
|
-
### Step
|
|
232
|
+
### Step 5: Define Configuration
|
|
215
233
|
|
|
216
|
-
Write the frontmatter with the required fields (`name` and `description`).
|
|
234
|
+
Write the frontmatter with the required fields (`name` and `description`). Optionally add `model` (placed after `description`) using one of the valid values from the **Available Models** section: `inherit`, `fast`, or an exact model name from that list. If the user requests a specific model, confirm the exact name from the **Available Models** list.
|
|
217
235
|
|
|
218
|
-
### Step
|
|
236
|
+
### Step 6: Write the System Prompt
|
|
219
237
|
|
|
220
238
|
The body becomes the system prompt. Be specific about:
|
|
221
239
|
- What the agent should do when invoked
|
|
@@ -223,7 +241,7 @@ The body becomes the system prompt. Be specific about:
|
|
|
223
241
|
- Output format and structure
|
|
224
242
|
- Any constraints or guidelines
|
|
225
243
|
|
|
226
|
-
### Step
|
|
244
|
+
### Step 7: Test the Agent
|
|
227
245
|
|
|
228
246
|
Ask the AI to use your new agent:
|
|
229
247
|
|