@indigoai-us/hq-cli 5.290.0 → 5.291.0
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/CHANGELOG.md +44 -0
- package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
- package/assets/scaffold/core/scripts/hq-session.sh +182 -21
- package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
- package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
- package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
- package/dist/command-catalog.generated.d.ts +59 -0
- package/dist/command-catalog.generated.js +73 -0
- package/dist/commands/cloud.d.ts +26 -14
- package/dist/commands/cloud.js +207 -56
- package/dist/commands/core-jobs-native.d.ts +8 -0
- package/dist/commands/core-jobs-native.js +624 -0
- package/dist/commands/core-native-jobs-flag.d.ts +4 -0
- package/dist/commands/core-native-jobs-flag.js +7 -0
- package/dist/commands/core-policy.js +24 -5
- package/dist/commands/core.js +7 -0
- package/dist/commands/integrations-core.js +7 -0
- package/dist/commands/lanes.js +111 -35
- package/dist/commands/members.js +5 -0
- package/dist/commands/mesh.js +46 -25
- package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
- package/dist/commands/personal-targeted-push-flag.js +7 -0
- package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
- package/dist/commands/personal-targeted-push-paths.js +67 -0
- package/dist/commands/secrets.js +9 -0
- package/dist/commands/skill.d.ts +3 -0
- package/dist/commands/skill.js +28 -0
- package/dist/fast-core.js +1 -0
- package/dist/lib/billing/free-month.d.ts +18 -0
- package/dist/lib/billing/free-month.js +31 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
- package/dist/lib/billing/plan-limit-refusals.js +187 -0
- package/dist/lib/billing/plan-lock.d.ts +2 -6
- package/dist/lib/billing/plan-lock.js +5 -16
- package/dist/lib/core-utils/secret-patterns.d.ts +3 -1
- package/dist/lib/core-utils/secret-patterns.js +3 -2
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/lanes/autopilot.d.ts +69 -0
- package/dist/lib/lanes/autopilot.js +418 -0
- package/dist/lib/lanes/mesh-events.js +2 -0
- package/dist/lib/lanes/store.js +11 -6
- package/dist/lib/mesh/api.js +4 -0
- package/dist/lib/mesh/live/daemon/state.d.ts +7 -1
- package/dist/lib/mesh/live/daemon/state.js +34 -2
- package/dist/main.js +23 -12
- package/dist/utils/plan-gate-error.d.ts +24 -0
- package/dist/utils/plan-gate-error.js +219 -9
- package/package.json +2 -2
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# jobs-validate.sh — validate Outpost scheduled job YAML registry files.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# core/scripts/jobs-validate.sh <file-or-dir> [file-or-dir...]
|
|
7
|
+
#
|
|
8
|
+
# Exits 0 when all jobs are valid; non-zero with per-field errors on stderr.
|
|
9
|
+
# Schema: core/knowledge/public/hq-core/outpost-jobs-spec.md
|
|
10
|
+
#
|
|
11
|
+
# Requires: yq (mikefarah), jq. Timezone checked via IANA-looking regex only.
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
|
|
17
|
+
# shellcheck source=lib/secret-patterns.sh
|
|
18
|
+
. "$SCRIPT_DIR/lib/secret-patterns.sh"
|
|
19
|
+
|
|
20
|
+
ERRORS=0
|
|
21
|
+
declare -a SEEN_IDS=()
|
|
22
|
+
|
|
23
|
+
die_usage() {
|
|
24
|
+
echo "usage: jobs-validate.sh <file-or-dir> [file-or-dir...]" >&2
|
|
25
|
+
exit 2
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
err() {
|
|
29
|
+
# err <file> <field> <message>
|
|
30
|
+
printf 'jobs-validate: %s: %s: %s\n' "$1" "$2" "$3" >&2
|
|
31
|
+
ERRORS=$((ERRORS + 1))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
command -v yq >/dev/null 2>&1 || {
|
|
35
|
+
echo "jobs-validate: yq is required (mikefarah/yq)" >&2
|
|
36
|
+
exit 2
|
|
37
|
+
}
|
|
38
|
+
command -v jq >/dev/null 2>&1 || {
|
|
39
|
+
echo "jobs-validate: jq is required" >&2
|
|
40
|
+
exit 2
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[ "$#" -ge 1 ] || die_usage
|
|
44
|
+
|
|
45
|
+
collect_files() {
|
|
46
|
+
local arg="$1"
|
|
47
|
+
if [ -f "$arg" ]; then
|
|
48
|
+
case "$arg" in
|
|
49
|
+
*.yaml|*.yml) printf '%s\n' "$arg" ;;
|
|
50
|
+
*) err "$arg" "path" "not a .yaml/.yml job file" ;;
|
|
51
|
+
esac
|
|
52
|
+
return
|
|
53
|
+
fi
|
|
54
|
+
if [ -d "$arg" ]; then
|
|
55
|
+
# Prefer find -print0 / sort for stable duplicate-id detection order
|
|
56
|
+
find "$arg" \( -name '*.yaml' -o -name '*.yml' \) -type f | sort
|
|
57
|
+
return
|
|
58
|
+
fi
|
|
59
|
+
err "$arg" "path" "file or directory not found"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cron_num_in_range() {
|
|
63
|
+
local n="$1" min="$2" max="$3"
|
|
64
|
+
[[ "$n" =~ ^[0-9]+$ ]] || return 1
|
|
65
|
+
((10#$n >= min && 10#$n <= max))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
is_valid_cron_field() {
|
|
69
|
+
# $1=value $2=min $3=max — *, */n, a-b, a-b/n, lists, dow/month names
|
|
70
|
+
local v="$1" min="$2" max="$3" part base step a b
|
|
71
|
+
local IFS=','
|
|
72
|
+
set -f
|
|
73
|
+
# shellcheck disable=SC2086
|
|
74
|
+
set -- $v
|
|
75
|
+
set +f
|
|
76
|
+
for part in "$@"; do
|
|
77
|
+
case "$part" in
|
|
78
|
+
\*) continue ;;
|
|
79
|
+
\*/*)
|
|
80
|
+
step="${part#*/}"
|
|
81
|
+
[[ "$step" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
82
|
+
continue
|
|
83
|
+
;;
|
|
84
|
+
sun|mon|tue|wed|thu|fri|sat|SUN|MON|TUE|WED|THU|FRI|SAT)
|
|
85
|
+
[ "$max" -eq 7 ] || return 1
|
|
86
|
+
continue
|
|
87
|
+
;;
|
|
88
|
+
jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)
|
|
89
|
+
[ "$max" -eq 12 ] || return 1
|
|
90
|
+
continue
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
if [[ "$part" =~ ^([0-9]+)-([0-9]+)(/([1-9][0-9]*))?$ ]]; then
|
|
94
|
+
a="${BASH_REMATCH[1]}"
|
|
95
|
+
b="${BASH_REMATCH[2]}"
|
|
96
|
+
cron_num_in_range "$a" "$min" "$max" || return 1
|
|
97
|
+
cron_num_in_range "$b" "$min" "$max" || return 1
|
|
98
|
+
((10#$a <= 10#$b)) || return 1
|
|
99
|
+
continue
|
|
100
|
+
fi
|
|
101
|
+
if [[ "$part" =~ ^([0-9]+)(/([1-9][0-9]*))?$ ]]; then
|
|
102
|
+
base="${BASH_REMATCH[1]}"
|
|
103
|
+
cron_num_in_range "$base" "$min" "$max" || return 1
|
|
104
|
+
continue
|
|
105
|
+
fi
|
|
106
|
+
return 1
|
|
107
|
+
done
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
validate_cron() {
|
|
112
|
+
local file="$1" cron="$2"
|
|
113
|
+
# strip surrounding quotes leftovers
|
|
114
|
+
cron="${cron#"${cron%%[![:space:]]*}"}"
|
|
115
|
+
cron="${cron%"${cron##*[![:space:]]}"}"
|
|
116
|
+
local -a fields
|
|
117
|
+
# noglob: cron uses '*' which must not expand against the filesystem
|
|
118
|
+
set -f
|
|
119
|
+
# shellcheck disable=SC2206
|
|
120
|
+
fields=($cron)
|
|
121
|
+
set +f
|
|
122
|
+
if [ "${#fields[@]}" -ne 5 ]; then
|
|
123
|
+
err "$file" "schedule" "invalid cron — expected 5 fields (minute hour dom month dow), got ${#fields[@]}"
|
|
124
|
+
return
|
|
125
|
+
fi
|
|
126
|
+
is_valid_cron_field "${fields[0]}" 0 59 || err "$file" "schedule" "invalid cron minute field '${fields[0]}'"
|
|
127
|
+
is_valid_cron_field "${fields[1]}" 0 23 || err "$file" "schedule" "invalid cron hour field '${fields[1]}'"
|
|
128
|
+
is_valid_cron_field "${fields[2]}" 1 31 || err "$file" "schedule" "invalid cron day-of-month field '${fields[2]}'"
|
|
129
|
+
is_valid_cron_field "${fields[3]}" 1 12 || err "$file" "schedule" "invalid cron month field '${fields[3]}'"
|
|
130
|
+
is_valid_cron_field "${fields[4]}" 0 7 || err "$file" "schedule" "invalid cron day-of-week field '${fields[4]}'"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
looks_like_secret_name_ok() {
|
|
134
|
+
# vault key name: UPPER_SNAKE / mixed alnum._:- without whitespace or credential shapes
|
|
135
|
+
local s="$1"
|
|
136
|
+
[[ "$s" =~ ^[A-Za-z][A-Za-z0-9._:/-]{0,127}$ ]] || return 1
|
|
137
|
+
return 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
scan_string_for_secrets() {
|
|
141
|
+
local file="$1" field="$2" value="$3"
|
|
142
|
+
local tmp entry pattern name
|
|
143
|
+
tmp="$(mktemp "${TMPDIR:-/tmp}/jobs-validate-secret.XXXXXX")"
|
|
144
|
+
printf '%s' "$value" >"$tmp"
|
|
145
|
+
for entry in "${SECRET_PATTERNS[@]}"; do
|
|
146
|
+
pattern="${entry%%:*}"
|
|
147
|
+
name="${entry#*:}"
|
|
148
|
+
if grep -Eq "$pattern" "$tmp" 2>/dev/null; then
|
|
149
|
+
err "$file" "$field" "inline credential-shaped value matched pattern '$name' (names only; never values)"
|
|
150
|
+
fi
|
|
151
|
+
done
|
|
152
|
+
rm -f "$tmp"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
validate_timezone() {
|
|
156
|
+
local file="$1" tz="$2"
|
|
157
|
+
if [[ ! "$tz" =~ ^[A-Za-z_]+(/[A-Za-z0-9_+-]+)+$ ]] && [[ "$tz" != "UTC" ]]; then
|
|
158
|
+
err "$file" "timezone" "not an IANA-looking timezone '$tz'"
|
|
159
|
+
return
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
json_type() {
|
|
164
|
+
jq -r "$1 | type" <<<"$2" 2>/dev/null || echo "null"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# cwd must be HQ-root-relative: no absolute paths, no "."/".." components.
|
|
168
|
+
# Runtime resolves under HQ_ROOT; validate shape only (directory need not exist).
|
|
169
|
+
assert_cwd_safe() {
|
|
170
|
+
local file="$1" field="$2" cwd="$3"
|
|
171
|
+
[ -z "$cwd" ] && return 0
|
|
172
|
+
case "$cwd" in
|
|
173
|
+
/*)
|
|
174
|
+
err "$file" "$field" "absolute cwd not allowed — use an HQ-root-relative path"
|
|
175
|
+
return
|
|
176
|
+
;;
|
|
177
|
+
esac
|
|
178
|
+
local part
|
|
179
|
+
local IFS='/'
|
|
180
|
+
# shellcheck disable=SC2086
|
|
181
|
+
for part in $cwd; do
|
|
182
|
+
case "$part" in
|
|
183
|
+
''|.) ;;
|
|
184
|
+
..)
|
|
185
|
+
err "$file" "$field" "cwd must not contain '..' path components"
|
|
186
|
+
return
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
done
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
validate_job_file() {
|
|
193
|
+
local file="$1"
|
|
194
|
+
local json
|
|
195
|
+
if ! json="$(yq -o=json '.' "$file" 2>/dev/null)"; then
|
|
196
|
+
err "$file" "yaml" "failed to parse YAML"
|
|
197
|
+
return
|
|
198
|
+
fi
|
|
199
|
+
if [ "$(jq -r 'type' <<<"$json")" != "object" ]; then
|
|
200
|
+
err "$file" "yaml" "job root must be a mapping"
|
|
201
|
+
return
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
local id name schedule timezone runtime timeout notify enabled owner created_at
|
|
205
|
+
id="$(jq -r '.id // empty' <<<"$json")"
|
|
206
|
+
name="$(jq -r '.name // empty' <<<"$json")"
|
|
207
|
+
schedule="$(jq -r '.schedule // empty' <<<"$json")"
|
|
208
|
+
if jq -e 'has("timezone") and ((.timezone | type) != "string" or .timezone == "")' \
|
|
209
|
+
<<<"$json" >/dev/null; then
|
|
210
|
+
err "$file" "timezone" "must be a non-empty string when present"
|
|
211
|
+
fi
|
|
212
|
+
timezone="$(jq -r '.timezone // empty' <<<"$json")"
|
|
213
|
+
runtime="$(jq -r '.runtime // empty' <<<"$json")"
|
|
214
|
+
timeout="$(jq -r '.timeout_seconds // empty' <<<"$json")"
|
|
215
|
+
notify="$(jq -r '.notify // empty' <<<"$json")"
|
|
216
|
+
# jq's // treats boolean false as missing — use has() for enabled.
|
|
217
|
+
enabled="$(jq -r 'if has("enabled") then (.enabled|tostring) else empty end' <<<"$json")"
|
|
218
|
+
owner="$(jq -r '.owner // empty' <<<"$json")"
|
|
219
|
+
created_at="$(jq -r '.created_at // empty' <<<"$json")"
|
|
220
|
+
|
|
221
|
+
[ -n "$id" ] || err "$file" "id" "missing required field"
|
|
222
|
+
[ -n "$name" ] || err "$file" "name" "missing required field"
|
|
223
|
+
[ -n "$schedule" ] || err "$file" "schedule" "missing required field"
|
|
224
|
+
[ -n "$runtime" ] || err "$file" "runtime" "missing required field"
|
|
225
|
+
[ -n "$timeout" ] || err "$file" "timeout_seconds" "missing required field"
|
|
226
|
+
[ -n "$notify" ] || err "$file" "notify" "missing required field"
|
|
227
|
+
[ -n "$enabled" ] || err "$file" "enabled" "missing required field"
|
|
228
|
+
[ -n "$owner" ] || err "$file" "owner" "missing required field"
|
|
229
|
+
[ -n "$created_at" ] || err "$file" "created_at" "missing required field"
|
|
230
|
+
|
|
231
|
+
if [ -n "$id" ]; then
|
|
232
|
+
if [[ ! "$id" =~ ^[a-z][a-z0-9-]{1,62}$ ]]; then
|
|
233
|
+
err "$file" "id" "must match [a-z][a-z0-9-]{1,62}, got '$id'"
|
|
234
|
+
fi
|
|
235
|
+
local seen
|
|
236
|
+
for seen in "${SEEN_IDS[@]+"${SEEN_IDS[@]}"}"; do
|
|
237
|
+
if [ "$seen" = "$id" ]; then
|
|
238
|
+
err "$file" "id" "duplicate id '$id'"
|
|
239
|
+
break
|
|
240
|
+
fi
|
|
241
|
+
done
|
|
242
|
+
SEEN_IDS+=("$id")
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Reject natural-language leftovers — canonical form is 5 cron fields only.
|
|
246
|
+
if [ -n "$schedule" ]; then
|
|
247
|
+
local -a sf
|
|
248
|
+
set -f
|
|
249
|
+
# shellcheck disable=SC2206
|
|
250
|
+
sf=($schedule)
|
|
251
|
+
set +f
|
|
252
|
+
if [ "${#sf[@]}" -ne 5 ]; then
|
|
253
|
+
if [[ "$schedule" =~ [[:alpha:]] ]]; then
|
|
254
|
+
err "$file" "schedule" "natural-language schedule is not allowed — store canonical cron only"
|
|
255
|
+
else
|
|
256
|
+
err "$file" "schedule" "invalid cron — expected 5 fields (minute hour dom month dow), got ${#sf[@]}"
|
|
257
|
+
fi
|
|
258
|
+
else
|
|
259
|
+
validate_cron "$file" "$schedule"
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
[ -n "$timezone" ] && validate_timezone "$file" "$timezone"
|
|
264
|
+
|
|
265
|
+
case "$runtime" in
|
|
266
|
+
'' ) ;;
|
|
267
|
+
claude|codex) ;;
|
|
268
|
+
*) err "$file" "runtime" "unknown runtime '$runtime' (allowed: claude|codex)" ;;
|
|
269
|
+
esac
|
|
270
|
+
|
|
271
|
+
# exec
|
|
272
|
+
local exec_type prompt skill
|
|
273
|
+
exec_type="$(json_type '.exec' "$json")"
|
|
274
|
+
if [ "$exec_type" = "null" ]; then
|
|
275
|
+
err "$file" "exec" "missing required field"
|
|
276
|
+
elif [ "$exec_type" != "object" ]; then
|
|
277
|
+
err "$file" "exec" "must be an object with prompt or skill"
|
|
278
|
+
else
|
|
279
|
+
prompt="$(jq -r '.exec.prompt // empty' <<<"$json")"
|
|
280
|
+
skill="$(jq -r '.exec.skill // empty' <<<"$json")"
|
|
281
|
+
if [ -z "$prompt" ] && [ -z "$skill" ]; then
|
|
282
|
+
err "$file" "exec" "must set prompt or skill"
|
|
283
|
+
elif [ -n "$prompt" ] && [ -n "$skill" ]; then
|
|
284
|
+
err "$file" "exec" "set exactly one of prompt or skill, not both"
|
|
285
|
+
fi
|
|
286
|
+
[ -n "$prompt" ] && scan_string_for_secrets "$file" "exec.prompt" "$prompt"
|
|
287
|
+
[ -n "$skill" ] && scan_string_for_secrets "$file" "exec.skill" "$skill"
|
|
288
|
+
# scan args values
|
|
289
|
+
if jq -e '.exec.args != null' <<<"$json" >/dev/null 2>&1; then
|
|
290
|
+
if [ "$(json_type '.exec.args' "$json")" != "object" ]; then
|
|
291
|
+
err "$file" "exec.args" "must be a mapping when present"
|
|
292
|
+
else
|
|
293
|
+
while IFS= read -r aval; do
|
|
294
|
+
[ -z "$aval" ] && continue
|
|
295
|
+
scan_string_for_secrets "$file" "exec.args" "$aval"
|
|
296
|
+
done < <(jq -r '.exec.args | .. | strings' <<<"$json" 2>/dev/null)
|
|
297
|
+
fi
|
|
298
|
+
fi
|
|
299
|
+
surface="$(jq -r '.exec.surface // empty' <<<"$json")"
|
|
300
|
+
case "$surface" in
|
|
301
|
+
''|headless|remote) ;;
|
|
302
|
+
*) err "$file" "exec.surface" "unknown surface '$surface' (allowed: headless|remote)" ;;
|
|
303
|
+
esac
|
|
304
|
+
# reject unknown exec keys
|
|
305
|
+
while IFS= read -r ekey; do
|
|
306
|
+
[ -z "$ekey" ] && continue
|
|
307
|
+
case "$ekey" in
|
|
308
|
+
prompt|skill|args|surface) ;;
|
|
309
|
+
*) err "$file" "exec.$ekey" "unknown exec key (allowed: prompt, skill, args, surface)" ;;
|
|
310
|
+
esac
|
|
311
|
+
done < <(jq -r '.exec | keys[]' <<<"$json")
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
# timeout
|
|
315
|
+
if [ -n "$timeout" ]; then
|
|
316
|
+
if [[ ! "$timeout" =~ ^[0-9]+$ ]]; then
|
|
317
|
+
err "$file" "timeout_seconds" "must be an integer, got '$timeout'"
|
|
318
|
+
else
|
|
319
|
+
if ((timeout < 60 || timeout > 14400)); then
|
|
320
|
+
err "$file" "timeout_seconds" "must be between 60 and 14400, got $timeout"
|
|
321
|
+
fi
|
|
322
|
+
fi
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
case "$notify" in
|
|
326
|
+
'' ) ;;
|
|
327
|
+
dm|none|profile) ;;
|
|
328
|
+
*) err "$file" "notify" "unknown notify '$notify' (allowed: dm|none|profile)" ;;
|
|
329
|
+
esac
|
|
330
|
+
|
|
331
|
+
case "$enabled" in
|
|
332
|
+
'' ) ;;
|
|
333
|
+
true|false) ;;
|
|
334
|
+
*)
|
|
335
|
+
# yq may emit boolean; jq -r on bool gives true/false — if someone used string "yes"
|
|
336
|
+
err "$file" "enabled" "must be boolean true|false, got '$enabled'"
|
|
337
|
+
;;
|
|
338
|
+
esac
|
|
339
|
+
|
|
340
|
+
if [ -n "$created_at" ]; then
|
|
341
|
+
if [[ ! "$created_at" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})$ ]]; then
|
|
342
|
+
err "$file" "created_at" "must be ISO-8601 datetime, got '$created_at'"
|
|
343
|
+
fi
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
# requirements — fixed vocabulary
|
|
347
|
+
if jq -e '.requirements != null' <<<"$json" >/dev/null 2>&1; then
|
|
348
|
+
if [ "$(json_type '.requirements' "$json")" != "object" ]; then
|
|
349
|
+
err "$file" "requirements" "must be a mapping"
|
|
350
|
+
else
|
|
351
|
+
local key
|
|
352
|
+
while IFS= read -r key; do
|
|
353
|
+
[ -z "$key" ] && continue
|
|
354
|
+
case "$key" in
|
|
355
|
+
runtime|secrets|company|cwd|tools) ;;
|
|
356
|
+
readiness|last_probe|last_run|failure_class|pending_probe)
|
|
357
|
+
# status-shaped keys under requirements are unknown requirements keys
|
|
358
|
+
err "$file" "requirements.$key" "unknown requirements key (status belongs in hq-pro, not requirements)"
|
|
359
|
+
;;
|
|
360
|
+
*) err "$file" "requirements.$key" "unknown requirements key (allowed: runtime, secrets, company, cwd, tools)" ;;
|
|
361
|
+
esac
|
|
362
|
+
done < <(jq -r '.requirements | keys[]' <<<"$json")
|
|
363
|
+
|
|
364
|
+
local req_runtime
|
|
365
|
+
req_runtime="$(jq -r '.requirements.runtime // empty' <<<"$json")"
|
|
366
|
+
if [ -n "$req_runtime" ]; then
|
|
367
|
+
case "$req_runtime" in
|
|
368
|
+
claude|codex) ;;
|
|
369
|
+
*) err "$file" "requirements.runtime" "unknown runtime '$req_runtime' (allowed: claude|codex)" ;;
|
|
370
|
+
esac
|
|
371
|
+
if [ -n "$runtime" ] && [ "$req_runtime" != "$runtime" ]; then
|
|
372
|
+
err "$file" "requirements.runtime" "mismatches top-level runtime ('$req_runtime' vs '$runtime')"
|
|
373
|
+
fi
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
if jq -e '.requirements.secrets != null' <<<"$json" >/dev/null 2>&1; then
|
|
377
|
+
if [ "$(json_type '.requirements.secrets' "$json")" != "array" ]; then
|
|
378
|
+
err "$file" "requirements.secrets" "must be an array of vault key names"
|
|
379
|
+
else
|
|
380
|
+
local secret
|
|
381
|
+
while IFS= read -r secret; do
|
|
382
|
+
[ -z "$secret" ] && continue
|
|
383
|
+
scan_string_for_secrets "$file" "requirements.secrets" "$secret"
|
|
384
|
+
if ! looks_like_secret_name_ok "$secret"; then
|
|
385
|
+
# If it already matched a secret pattern, we still want the name-shape error for odd values
|
|
386
|
+
err "$file" "requirements.secrets" "invalid vault key name '$secret' (use names only)"
|
|
387
|
+
fi
|
|
388
|
+
# Heuristic: KEY=value or PEM-ish or long high-entropy blob
|
|
389
|
+
if [[ "$secret" == *"="* ]]; then
|
|
390
|
+
err "$file" "requirements.secrets" "looks like KEY=value inline credential — names only"
|
|
391
|
+
fi
|
|
392
|
+
done < <(jq -r '.requirements.secrets[]?' <<<"$json")
|
|
393
|
+
fi
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
if jq -e '.requirements.tools != null' <<<"$json" >/dev/null 2>&1; then
|
|
397
|
+
if [ "$(json_type '.requirements.tools' "$json")" != "array" ]; then
|
|
398
|
+
err "$file" "requirements.tools" "must be an array of strings"
|
|
399
|
+
fi
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
local req_cwd
|
|
403
|
+
req_cwd="$(jq -r '.requirements.cwd // empty' <<<"$json")"
|
|
404
|
+
if [ -n "$req_cwd" ]; then
|
|
405
|
+
assert_cwd_safe "$file" "requirements.cwd" "$req_cwd"
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
local req_company
|
|
409
|
+
req_company="$(jq -r '.requirements.company // empty' <<<"$json")"
|
|
410
|
+
if [ -n "$req_company" ]; then
|
|
411
|
+
if [[ ! "$req_company" =~ ^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$ ]]; then
|
|
412
|
+
err "$file" "requirements.company" "invalid company slug '$req_company' (lowercase kebab/underscore)"
|
|
413
|
+
fi
|
|
414
|
+
fi
|
|
415
|
+
fi
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
# Top-level cwd (legacy) — same containment rules
|
|
419
|
+
local top_cwd
|
|
420
|
+
top_cwd="$(jq -r '.cwd // empty' <<<"$json")"
|
|
421
|
+
if [ -n "$top_cwd" ]; then
|
|
422
|
+
assert_cwd_safe "$file" "cwd" "$top_cwd"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
# Top-level readiness is informational only — ignore (no error). Documented SoT is hq-pro.
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# Expand args → file list
|
|
429
|
+
FILES=()
|
|
430
|
+
for arg in "$@"; do
|
|
431
|
+
while IFS= read -r f; do
|
|
432
|
+
[ -z "$f" ] && continue
|
|
433
|
+
FILES+=("$f")
|
|
434
|
+
done < <(collect_files "$arg")
|
|
435
|
+
done
|
|
436
|
+
|
|
437
|
+
if [ "${#FILES[@]}" -eq 0 ]; then
|
|
438
|
+
echo "jobs-validate: no job YAML files found" >&2
|
|
439
|
+
exit 1
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
for f in "${FILES[@]}"; do
|
|
443
|
+
validate_job_file "$f"
|
|
444
|
+
done
|
|
445
|
+
|
|
446
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
447
|
+
echo "jobs-validate: $ERRORS error(s)" >&2
|
|
448
|
+
exit 1
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
exit 0
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
# 3. CLAUDE_SESSION_ID — Claude Code (alternate/legacy)
|
|
34
34
|
# 4. CODEX_SESSION_ID — Codex
|
|
35
35
|
# 5. CODEX_THREAD_ID — Codex, thread-scoped runs
|
|
36
|
-
# 6.
|
|
36
|
+
# 6. GROK_SESSION_ID — Grok Build (Desktop / CLI)
|
|
37
|
+
# 7. workspace/sessions/.current — no session env; last-writer-wins fallback
|
|
37
38
|
#
|
|
38
39
|
# The env precedence list matches .claude/skills/_shared/journal.sh so every
|
|
39
40
|
# per-session artifact (journal, session meta, scope capability) keys off the
|
|
@@ -59,7 +60,7 @@ session_id_is_valid() {
|
|
|
59
60
|
session_id_from_env() {
|
|
60
61
|
local var val
|
|
61
62
|
for var in HQ_SESSION_ID CLAUDE_CODE_SESSION_ID CLAUDE_SESSION_ID \
|
|
62
|
-
CODEX_SESSION_ID CODEX_THREAD_ID; do
|
|
63
|
+
CODEX_SESSION_ID CODEX_THREAD_ID GROK_SESSION_ID; do
|
|
63
64
|
val="${!var:-}"
|
|
64
65
|
val="$(printf '%s' "$val" | tr -d '[:space:]')"
|
|
65
66
|
if session_id_is_valid "$val"; then
|
|
@@ -100,7 +100,8 @@ work_mesh_ulid() {
|
|
|
100
100
|
bytes=$(dd if=/dev/urandom bs=10 count=1 2>/dev/null | od -An -tu1 | tr -s ' ' '\n' | grep -E '^[0-9]+$' | head -n 10 | tr '\n' ' ')
|
|
101
101
|
# shellcheck disable=SC2206
|
|
102
102
|
local -a bb=($bytes)
|
|
103
|
-
|
|
103
|
+
# Bash 3.2 + set -u: empty bb[@] is unbound; keep the copy portable.
|
|
104
|
+
b=(${bb[@]+"${bb[@]}"})
|
|
104
105
|
while [ "${#b[@]}" -lt 10 ]; do
|
|
105
106
|
b+=($((RANDOM % 256)))
|
|
106
107
|
done
|
|
@@ -5514,6 +5514,41 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5514
5514
|
readonly options: readonly [];
|
|
5515
5515
|
readonly subcommands: readonly [];
|
|
5516
5516
|
}];
|
|
5517
|
+
}, {
|
|
5518
|
+
readonly name: "jobs";
|
|
5519
|
+
readonly description: "Scheduled jobs helpers";
|
|
5520
|
+
readonly aliases: readonly [];
|
|
5521
|
+
readonly hidden: false;
|
|
5522
|
+
readonly usage: "[options] [command]";
|
|
5523
|
+
readonly arguments: readonly [];
|
|
5524
|
+
readonly options: readonly [];
|
|
5525
|
+
readonly subcommands: readonly [{
|
|
5526
|
+
readonly name: "schedule-parse";
|
|
5527
|
+
readonly description: "Resolve natural-language or raw cron schedules";
|
|
5528
|
+
readonly aliases: readonly [];
|
|
5529
|
+
readonly hidden: false;
|
|
5530
|
+
readonly usage: "[args...]";
|
|
5531
|
+
readonly arguments: readonly [{
|
|
5532
|
+
readonly name: "args";
|
|
5533
|
+
readonly required: false;
|
|
5534
|
+
readonly variadic: true;
|
|
5535
|
+
}];
|
|
5536
|
+
readonly options: readonly [];
|
|
5537
|
+
readonly subcommands: readonly [];
|
|
5538
|
+
}, {
|
|
5539
|
+
readonly name: "validate";
|
|
5540
|
+
readonly description: "Validate scheduled-job YAML files";
|
|
5541
|
+
readonly aliases: readonly [];
|
|
5542
|
+
readonly hidden: false;
|
|
5543
|
+
readonly usage: "[args...]";
|
|
5544
|
+
readonly arguments: readonly [{
|
|
5545
|
+
readonly name: "args";
|
|
5546
|
+
readonly required: false;
|
|
5547
|
+
readonly variadic: true;
|
|
5548
|
+
}];
|
|
5549
|
+
readonly options: readonly [];
|
|
5550
|
+
readonly subcommands: readonly [];
|
|
5551
|
+
}];
|
|
5517
5552
|
}, {
|
|
5518
5553
|
readonly name: "worktree-gc";
|
|
5519
5554
|
readonly description: "Safely collect old, preserved HQ worktrees";
|
|
@@ -8849,6 +8884,30 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8849
8884
|
readonly description: "JSON on stdout (always)";
|
|
8850
8885
|
}];
|
|
8851
8886
|
readonly subcommands: readonly [];
|
|
8887
|
+
}, {
|
|
8888
|
+
readonly name: "autopilot";
|
|
8889
|
+
readonly description: "Check registered team leads and wake them for ready work";
|
|
8890
|
+
readonly aliases: readonly [];
|
|
8891
|
+
readonly hidden: false;
|
|
8892
|
+
readonly usage: "[options] [command]";
|
|
8893
|
+
readonly arguments: readonly [];
|
|
8894
|
+
readonly options: readonly [];
|
|
8895
|
+
readonly subcommands: readonly [{
|
|
8896
|
+
readonly name: "run";
|
|
8897
|
+
readonly description: "Run one lead autopilot check pass";
|
|
8898
|
+
readonly aliases: readonly [];
|
|
8899
|
+
readonly hidden: false;
|
|
8900
|
+
readonly usage: "[options]";
|
|
8901
|
+
readonly arguments: readonly [];
|
|
8902
|
+
readonly options: readonly [{
|
|
8903
|
+
readonly flags: "--dry-run";
|
|
8904
|
+
readonly description: "Report proposed wakes without writing them";
|
|
8905
|
+
}, {
|
|
8906
|
+
readonly flags: "--json";
|
|
8907
|
+
readonly description: "Emit JSON on stdout (default)";
|
|
8908
|
+
}];
|
|
8909
|
+
readonly subcommands: readonly [];
|
|
8910
|
+
}];
|
|
8852
8911
|
}, {
|
|
8853
8912
|
readonly name: "reconcile";
|
|
8854
8913
|
readonly description: "Run one machine-owned reconcile pass and exit";
|
|
@@ -7114,6 +7114,49 @@ export const COMMAND_CATALOG = [
|
|
|
7114
7114
|
}
|
|
7115
7115
|
]
|
|
7116
7116
|
},
|
|
7117
|
+
{
|
|
7118
|
+
"name": "jobs",
|
|
7119
|
+
"description": "Scheduled jobs helpers",
|
|
7120
|
+
"aliases": [],
|
|
7121
|
+
"hidden": false,
|
|
7122
|
+
"usage": "[options] [command]",
|
|
7123
|
+
"arguments": [],
|
|
7124
|
+
"options": [],
|
|
7125
|
+
"subcommands": [
|
|
7126
|
+
{
|
|
7127
|
+
"name": "schedule-parse",
|
|
7128
|
+
"description": "Resolve natural-language or raw cron schedules",
|
|
7129
|
+
"aliases": [],
|
|
7130
|
+
"hidden": false,
|
|
7131
|
+
"usage": "[args...]",
|
|
7132
|
+
"arguments": [
|
|
7133
|
+
{
|
|
7134
|
+
"name": "args",
|
|
7135
|
+
"required": false,
|
|
7136
|
+
"variadic": true
|
|
7137
|
+
}
|
|
7138
|
+
],
|
|
7139
|
+
"options": [],
|
|
7140
|
+
"subcommands": []
|
|
7141
|
+
},
|
|
7142
|
+
{
|
|
7143
|
+
"name": "validate",
|
|
7144
|
+
"description": "Validate scheduled-job YAML files",
|
|
7145
|
+
"aliases": [],
|
|
7146
|
+
"hidden": false,
|
|
7147
|
+
"usage": "[args...]",
|
|
7148
|
+
"arguments": [
|
|
7149
|
+
{
|
|
7150
|
+
"name": "args",
|
|
7151
|
+
"required": false,
|
|
7152
|
+
"variadic": true
|
|
7153
|
+
}
|
|
7154
|
+
],
|
|
7155
|
+
"options": [],
|
|
7156
|
+
"subcommands": []
|
|
7157
|
+
}
|
|
7158
|
+
]
|
|
7159
|
+
},
|
|
7117
7160
|
{
|
|
7118
7161
|
"name": "worktree-gc",
|
|
7119
7162
|
"description": "Safely collect old, preserved HQ worktrees",
|
|
@@ -11430,6 +11473,36 @@ export const COMMAND_CATALOG = [
|
|
|
11430
11473
|
],
|
|
11431
11474
|
"subcommands": []
|
|
11432
11475
|
},
|
|
11476
|
+
{
|
|
11477
|
+
"name": "autopilot",
|
|
11478
|
+
"description": "Check registered team leads and wake them for ready work",
|
|
11479
|
+
"aliases": [],
|
|
11480
|
+
"hidden": false,
|
|
11481
|
+
"usage": "[options] [command]",
|
|
11482
|
+
"arguments": [],
|
|
11483
|
+
"options": [],
|
|
11484
|
+
"subcommands": [
|
|
11485
|
+
{
|
|
11486
|
+
"name": "run",
|
|
11487
|
+
"description": "Run one lead autopilot check pass",
|
|
11488
|
+
"aliases": [],
|
|
11489
|
+
"hidden": false,
|
|
11490
|
+
"usage": "[options]",
|
|
11491
|
+
"arguments": [],
|
|
11492
|
+
"options": [
|
|
11493
|
+
{
|
|
11494
|
+
"flags": "--dry-run",
|
|
11495
|
+
"description": "Report proposed wakes without writing them"
|
|
11496
|
+
},
|
|
11497
|
+
{
|
|
11498
|
+
"flags": "--json",
|
|
11499
|
+
"description": "Emit JSON on stdout (default)"
|
|
11500
|
+
}
|
|
11501
|
+
],
|
|
11502
|
+
"subcommands": []
|
|
11503
|
+
}
|
|
11504
|
+
]
|
|
11505
|
+
},
|
|
11433
11506
|
{
|
|
11434
11507
|
"name": "reconcile",
|
|
11435
11508
|
"description": "Run one machine-owned reconcile pass and exit",
|