@comate/zulu 1.4.0-beta.3 → 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.
Files changed (39) hide show
  1. package/comate-engine/assets/skills/code-review/SKILL.md +6 -5
  2. package/comate-engine/assets/skills/code-review/agents/custom-reviewer.md +2 -2
  3. package/comate-engine/assets/skills/code-review/agents/meta-reviewer.md +2 -2
  4. package/comate-engine/assets/skills/code-review/agents/style-reviewer.md +72 -10
  5. package/comate-engine/assets/skills/code-review/references/dispatch-template.md +12 -12
  6. package/comate-engine/assets/skills/code-review/references/rules/Java/JAVA_STYLE_RULES.md +11 -5
  7. package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/SKILL.md +5 -8
  8. package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/long_running_task.md +0 -15
  9. package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/testing_strategy.md +1 -1
  10. package/comate-engine/assets/skills/create-image/SKILL.md +197 -201
  11. package/comate-engine/assets/skills/create-image/scripts/generate-image.ps1 +213 -0
  12. package/comate-engine/assets/skills/create-image/scripts/generate-image.sh +322 -0
  13. package/comate-engine/assets/skills/create-skill/SKILL.md +1 -2
  14. package/comate-engine/assets/skills/create-subagent/SKILL.md +11 -5
  15. package/comate-engine/assets/skills/get-ugate-token/SKILL.md +97 -13
  16. package/comate-engine/assets/skills/get-ugate-token/getUgateToken.py +99 -5
  17. package/comate-engine/node_modules/@baidu/comate-browser-use/dist/launch-chrome/index.js +1 -1
  18. package/comate-engine/node_modules/@baidu/comate-browser-use/package.json +5 -5
  19. package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +1 -1
  20. package/comate-engine/package.json +1 -1
  21. package/comate-engine/server.js +149 -180
  22. package/dist/bundle/index.js +3 -3
  23. package/package.json +1 -1
  24. package/scripts/postinstall.js +4 -3
  25. package/comate-engine/assets/skills/code-review/evals/SKILL.md +0 -334
  26. package/comate-engine/assets/skills/code-review/evals/agents/gt-generator.md +0 -76
  27. package/comate-engine/assets/skills/code-review/evals/agents/miner.md +0 -87
  28. package/comate-engine/assets/skills/code-review/evals/agents/score-judge.md +0 -168
  29. package/comate-engine/assets/skills/code-review/evals/references/cli-query-template.md +0 -114
  30. package/comate-engine/assets/skills/code-review/evals/references/gt-schema.md +0 -77
  31. package/comate-engine/assets/skills/code-review/references/custom-rules/RULE_TEMPLATE.md +0 -141
  32. /package/comate-engine/assets/commands/{code-review-comate.md → code-review.md} +0 -0
  33. /package/comate-engine/assets/commands/{debug-comate.md → debug.md} +0 -0
  34. /package/comate-engine/assets/commands/{unit-test-comate.md → unit-test.md} +0 -0
  35. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/backend_dev.md +0 -0
  36. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/env_setup.md +0 -0
  37. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/frontend_dev.md +0 -0
  38. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/git_operations.md +0 -0
  39. /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")"
@@ -79,7 +79,6 @@ Based on the user interview, fill in these components:
79
79
 
80
80
  - **name**: Skill identifier
81
81
  - **description**: When to trigger, what it does. This is the primary triggering mechanism - include both what the skill does AND specific contexts for when to use it. All "when to use" info goes here, not in the body. Note: currently Comate has a tendency to "undertrigger" skills -- to not use them when they'd be useful. To combat this, please make the skill descriptions a little bit "pushy". So for instance, instead of "How to build a simple fast dashboard to display internal Anthropic data.", you might write "How to build a simple fast dashboard to display internal Anthropic data. Make sure to use this skill whenever the user mentions dashboards, data visualization, internal metrics, or wants to display any kind of company data, even if they don't explicitly ask for a 'dashboard.'"
82
- - **compatibility**: Required tools, dependencies (optional, rarely needed)
83
82
  - **the rest of the skill :)**
84
83
 
85
84
  ### Skill Writing Guide
@@ -446,4 +445,4 @@ Repeating one more time the core loop here for emphasis:
446
445
  - Repeat until you and the user are satisfied
447
446
  - Package the final skill and return it to the user.
448
447
 
449
- Please add steps to your TodoList, if you have such a thing, to make sure you don't forget.
448
+ Please add steps to your TodoList, if you have such a thing, to make sure you don't forget.
@@ -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: Create the File
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 4: Define Configuration
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 5: Write the System Prompt
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 6: Test the Agent
232
+ ### Step 7: Test the Agent
227
233
 
228
234
  Ask the AI to use your new agent:
229
235