@comate/zulu 1.4.0-beta.4 → 1.4.0-beta.5
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/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/{create-automation-tasks-comate → create-automation}/SKILL.md +5 -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 +11 -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 +143 -180
- 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")"
|
|
@@ -38,6 +38,7 @@ Create a `.md` file with YAML frontmatter and a markdown body (the system prompt
|
|
|
38
38
|
---
|
|
39
39
|
name: code-reviewer
|
|
40
40
|
description: Reviews code for quality and best practices
|
|
41
|
+
model: Model this subagent uses
|
|
41
42
|
---
|
|
42
43
|
|
|
43
44
|
You are a code reviewer. When invoked, analyze the code and provide
|
|
@@ -50,6 +51,7 @@ specific, actionable feedback on quality, security, and best practices.
|
|
|
50
51
|
|-------|-------------|
|
|
51
52
|
| `name` | Unique identifier (lowercase letters and hyphens only) |
|
|
52
53
|
| `description` | When to delegate to this subagent (be specific!) |
|
|
54
|
+
| `model` | (Optional) Model this subagent uses. One of: `inherit` (inherit from main agent, default), `fast` (cheaper/faster model), or a specific model display name. For specific models, the user must pick from their available list at https://ku.baidu-int.com/knowledge/HFVrC7hq1Q/_SKPgSwp2G/jyGhbHUQQG/ZS3uu0XgQjLm31 (**models supporting thinking have `-Thinking` appended to the displayName, e.g. `Claude Sonnet 4.6-Thinking`** MUST tell user this, she may not know). Unrecognized names fall back to `inherit`. |
|
|
53
55
|
|
|
54
56
|
## Writing Effective Descriptions
|
|
55
57
|
|
|
@@ -199,7 +201,11 @@ Below are candidate tools name, according to the user's query, decide the subage
|
|
|
199
201
|
If you are not sure with some tools, ask her directly.
|
|
200
202
|
Empty means only selecting default read-only tools; Lack of this key means selecting all tools
|
|
201
203
|
|
|
202
|
-
### Step 3:
|
|
204
|
+
### Step 3: Clarify the Agent's Model
|
|
205
|
+
**If the user requests a specific model, ask her to confirm the exact displayName from the user's available model list** [model name](https://ku.baidu-int.com/knowledge/HFVrC7hq1Q/_SKPgSwp2G/jyGhbHUQQG/ZS3uu0XgQjLm31).
|
|
206
|
+
Otherwise, tell her can use inherit or fast.
|
|
207
|
+
|
|
208
|
+
### Step 4: Create the File
|
|
203
209
|
|
|
204
210
|
```bash
|
|
205
211
|
# For project-level
|
|
@@ -211,11 +217,11 @@ mkdir -p ~/.comate/agents
|
|
|
211
217
|
touch ~/.comate/agents/my-agent.md
|
|
212
218
|
```
|
|
213
219
|
|
|
214
|
-
### Step
|
|
220
|
+
### Step 5: Define Configuration
|
|
215
221
|
|
|
216
|
-
Write the frontmatter with the required fields (`name` and `description`).
|
|
222
|
+
Write the frontmatter with the required fields (`name` and `description`). Optionally add `model` (placed after `description`) with one of: `inherit`, `fast`, or a specific model display name from the user's available model list (https://ku.baidu-int.com/knowledge/HFVrC7hq1Q/_SKPgSwp2G/jyGhbHUQQG/ZS3uu0XgQjLm31). If the user requests a specific model, ask them to confirm the exact displayName from that list.
|
|
217
223
|
|
|
218
|
-
### Step
|
|
224
|
+
### Step 6: Write the System Prompt
|
|
219
225
|
|
|
220
226
|
The body becomes the system prompt. Be specific about:
|
|
221
227
|
- What the agent should do when invoked
|
|
@@ -223,7 +229,7 @@ The body becomes the system prompt. Be specific about:
|
|
|
223
229
|
- Output format and structure
|
|
224
230
|
- Any constraints or guidelines
|
|
225
231
|
|
|
226
|
-
### Step
|
|
232
|
+
### Step 7: Test the Agent
|
|
227
233
|
|
|
228
234
|
Ask the AI to use your new agent:
|
|
229
235
|
|