@indigoai-us/hq-cli 5.306.0 → 5.308.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/assets/scaffold/core/scripts/audit-log.sh +384 -0
  3. package/assets/scaffold/core/scripts/git-pack-extension-check.sh +164 -0
  4. package/assets/scaffold/core/scripts/hq-pin.sh +118 -0
  5. package/assets/scaffold/core/scripts/lib/portable.sh +211 -0
  6. package/assets/scaffold/core/scripts/refresh-vault-access.sh +188 -0
  7. package/assets/scaffold/core/scripts/resolve-company.sh +199 -0
  8. package/dist/command-catalog.generated.d.ts +65 -0
  9. package/dist/command-catalog.generated.js +80 -0
  10. package/dist/commands/core-audit-log-native.d.ts +9 -0
  11. package/dist/commands/core-audit-log-native.js +257 -0
  12. package/dist/commands/core-git-pack-extension-check-native.d.ts +22 -0
  13. package/dist/commands/core-git-pack-extension-check-native.js +190 -0
  14. package/dist/commands/core-native-utilities-flag.d.ts +4 -0
  15. package/dist/commands/core-native-utilities-flag.js +7 -0
  16. package/dist/commands/core-native-utilities.d.ts +28 -0
  17. package/dist/commands/core-native-utilities.js +52 -0
  18. package/dist/commands/core-pin-native.d.ts +14 -0
  19. package/dist/commands/core-pin-native.js +236 -0
  20. package/dist/commands/core-refresh-vault-access-native.d.ts +20 -0
  21. package/dist/commands/core-refresh-vault-access-native.js +229 -0
  22. package/dist/commands/core-resolve-company-command.d.ts +4 -0
  23. package/dist/commands/core-resolve-company-command.js +42 -0
  24. package/dist/commands/core-resolve-company-native.d.ts +28 -0
  25. package/dist/commands/core-resolve-company-native.js +408 -0
  26. package/dist/commands/core.js +18 -0
  27. package/dist/commands/dm.js +6 -1
  28. package/dist/commands/lanes.js +43 -1
  29. package/dist/fast-scaffold.js +12 -0
  30. package/dist/lib/agent-kit/fallback.d.ts +0 -7
  31. package/dist/lib/agent-kit/fallback.js +4 -4
  32. package/dist/lib/agent-kit/inbox-state.d.ts +0 -2
  33. package/dist/lib/agent-kit/inbox-state.js +2 -2
  34. package/dist/lib/agent-kit/kit-config.d.ts +0 -5
  35. package/dist/lib/agent-kit/kit-config.js +5 -5
  36. package/dist/lib/agent-kit/log.d.ts +0 -1
  37. package/dist/lib/agent-kit/log.js +1 -1
  38. package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +3 -12
  39. package/dist/lib/agent-kit/mcp/jsonrpc.js +4 -4
  40. package/dist/lib/agent-kit/mcp/tools.d.ts +0 -2
  41. package/dist/lib/agent-kit/mcp/tools.js +2 -2
  42. package/dist/lib/agent-kit/services.d.ts +0 -1
  43. package/dist/lib/agent-kit/services.js +1 -1
  44. package/dist/lib/checkpoint-ontology/apply.d.ts +0 -49
  45. package/dist/lib/checkpoint-ontology/apply.js +5 -5
  46. package/dist/lib/checkpoint-ontology/identity.d.ts +2 -1
  47. package/dist/lib/checkpoint-ontology/identity.js +1 -1
  48. package/dist/lib/checkpoint-ontology/multi-writer.d.ts +4 -9
  49. package/dist/lib/checkpoint-ontology/multi-writer.js +6 -6
  50. package/dist/lib/checkpoint-ontology/plan.d.ts +4 -17
  51. package/dist/lib/checkpoint-ontology/plan.js +5 -5
  52. package/dist/lib/checkpoint-ontology/queued-touches.d.ts +0 -3
  53. package/dist/lib/checkpoint-ontology/queued-touches.js +3 -3
  54. package/dist/lib/db/control-plane.js +6 -0
  55. package/dist/lib/deploy-apps.js +5 -0
  56. package/package.json +1 -1
@@ -0,0 +1,211 @@
1
+ # shellcheck shell=bash
2
+ # portable.sh — shared OS-portability helpers for hq-core shell scripts.
3
+ #
4
+ # SOURCED, never executed: . "$ROOT/core/scripts/lib/portable.sh"
5
+ # bash 3.2 safe; works under Linux, macOS, and Windows Git Bash with set -u.
6
+ #
7
+ # Canonicalizes good in-repo dual-implementation patterns:
8
+ # - work-mesh-lib.sh wm_file_mtime (numeric-probe dual stat)
9
+ # - archive-old-threads.sh dual stat / date -d | date -v
10
+ # - compute-checksums.sh Git Bash awareness
11
+ #
12
+ # JSON engines are NOT here — source core/scripts/hook-lib.sh (hq_json_get /
13
+ # hq_json_encode: jq first, then node). This file owns OS primitives + hard-fail
14
+ # jq install messaging only.
15
+
16
+ # portable_stat_mtime <path>
17
+ # Print mtime as epoch seconds. Uses BSD-form stat -f first, accepts only a
18
+ # strictly numeric result (GNU stat -f is filesystem mode and may print text),
19
+ # then falls through to GNU stat -c. Mirrors work-mesh-lib.sh:215-230.
20
+ # Returns 1 when both forms fail.
21
+ portable_stat_mtime() {
22
+ local f="${1:-}" value
23
+ [ -n "$f" ] || return 1
24
+ value="$(stat -f %m "$f" 2>/dev/null)" || value=""
25
+ case "$value" in
26
+ ''|*[!0-9]*) ;;
27
+ *) printf '%s' "$value"; return 0 ;;
28
+ esac
29
+ value="$(stat -c %Y "$f" 2>/dev/null)" || value=""
30
+ case "$value" in
31
+ ''|*[!0-9]*) return 1 ;;
32
+ *) printf '%s' "$value"; return 0 ;;
33
+ esac
34
+ }
35
+
36
+ # portable_sed_inplace <sed-script> <file> [extra sed args...]
37
+ # In-place sed that works on both GNU sed (sed -i) and BSD sed (sed -i '').
38
+ portable_sed_inplace() {
39
+ local script="${1:-}"
40
+ local file="${2:-}"
41
+ shift 2 2>/dev/null || true
42
+ [ -n "$script" ] && [ -n "$file" ] || return 1
43
+ [ -f "$file" ] || return 1
44
+ # Prefer GNU form; fall back to BSD empty-suffix form.
45
+ if sed --version >/dev/null 2>&1; then
46
+ sed -i "$@" -e "$script" "$file"
47
+ else
48
+ sed -i '' "$@" -e "$script" "$file"
49
+ fi
50
+ }
51
+
52
+ # portable_tmpdir
53
+ # Print a writable temp directory (no trailing slash). Prefer TMPDIR, else /tmp.
54
+ portable_tmpdir() {
55
+ local d="${TMPDIR:-/tmp}"
56
+ # Strip trailing slash except for root.
57
+ case "$d" in
58
+ /) printf '%s' "$d" ;;
59
+ */) printf '%s' "${d%/}" ;;
60
+ *) printf '%s' "$d" ;;
61
+ esac
62
+ }
63
+
64
+ # portable_date_epoch_to_iso <epoch_seconds>
65
+ # Convert epoch seconds to UTC ISO-8601 (YYYY-MM-DDTHH:MM:SSZ).
66
+ # Dual: GNU date -d @N / BSD date -r N.
67
+ portable_date_epoch_to_iso() {
68
+ local epoch="${1:-}"
69
+ case "$epoch" in
70
+ ''|*[!0-9]*) return 1 ;;
71
+ esac
72
+ date -u -d "@${epoch}" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
73
+ || date -u -r "$epoch" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
74
+ || return 1
75
+ }
76
+
77
+ # portable_user
78
+ # Print a filename-safe user key from USER or USERNAME (Git Bash often lacks USER).
79
+ portable_user() {
80
+ local u="${USER:-${USERNAME:-unknown}}"
81
+ # shellcheck disable=SC2001
82
+ u="$(printf '%s' "$u" | sed 's/[^[:alnum:]_.-]/_/g')"
83
+ [ -n "$u" ] || u="unknown"
84
+ printf '%s' "$u"
85
+ }
86
+
87
+ # portable_jq_install_hint
88
+ # Print multi-line per-OS jq install guidance to stdout.
89
+ portable_jq_install_hint() {
90
+ # Single multi-OS line so portability lint does not flag brew-only messages.
91
+ printf '%s\n' "Install jq: Windows: winget install jqlang.jq | choco install jq | scoop install jq; Linux: sudo apt install jq | sudo dnf install jq; macOS: brew install jq"
92
+ }
93
+
94
+ # require_jq
95
+ # Return 0 if jq is on PATH. Otherwise print install guidance to stderr and
96
+ # return 1. Caller decides whether to die. Does not implement JSON parse.
97
+ require_jq() {
98
+ if command -v jq >/dev/null 2>&1; then
99
+ return 0
100
+ fi
101
+ printf 'jq is required but not installed.\n' >&2
102
+ portable_jq_install_hint >&2
103
+ return 1
104
+ }
105
+
106
+ # portable_native_path <path>
107
+ # Print a path a native (non-MSYS) Windows binary can open. Git Bash mktemp
108
+ # and $TMPDIR often yield /tmp/... which Node's hq.exe cannot read, so
109
+ # --body-file looks empty. cygpath -m produces C:/... mixed paths. On
110
+ # POSIX hosts this is identity. Empty input returns 1.
111
+ portable_native_path() {
112
+ local p="${1:-}"
113
+ [ -n "$p" ] || return 1
114
+ if command -v cygpath >/dev/null 2>&1; then
115
+ cygpath -m "$p" 2>/dev/null || printf '%s' "$p"
116
+ return 0
117
+ fi
118
+ printf '%s' "$p"
119
+ }
120
+
121
+ # portable_qmd_models_dir
122
+ # Print qmd's GGUF cache directory (no trailing slash). Override with
123
+ # QMD_MODELS_DIR. Otherwise XDG_CACHE_HOME/qmd/models, then
124
+ # HOME/.cache/qmd/models, then USERPROFILE/.cache/qmd/models (Windows).
125
+ portable_qmd_models_dir() {
126
+ local d
127
+ if [ -n "${QMD_MODELS_DIR:-}" ]; then
128
+ d="$QMD_MODELS_DIR"
129
+ elif [ -n "${XDG_CACHE_HOME:-}" ]; then
130
+ d="$XDG_CACHE_HOME/qmd/models"
131
+ elif [ -n "${HOME:-}" ]; then
132
+ d="$HOME/.cache/qmd/models"
133
+ elif [ -n "${USERPROFILE:-}" ]; then
134
+ d="$USERPROFILE/.cache/qmd/models"
135
+ if command -v cygpath >/dev/null 2>&1; then
136
+ d="$(cygpath -u "$d" 2>/dev/null || printf '%s' "$d")"
137
+ fi
138
+ else
139
+ return 1
140
+ fi
141
+ case "$d" in
142
+ */) d="${d%/}" ;;
143
+ esac
144
+ printf '%s' "$d"
145
+ }
146
+
147
+ # portable_qmd_embed_model_ready
148
+ # Return 0 when a usable GGUF already sits in the qmd models dir so
149
+ # vsearch/query/embed will not start a multi-hundred-MB download. A file
150
+ # named by QMD_EMBED_MODEL (basename after the last /) counts; otherwise
151
+ # any *.gguf of at least 1 MiB counts. Missing dir or tiny placeholders
152
+ # return 1.
153
+ portable_qmd_embed_model_ready() {
154
+ local dir want f sz
155
+ dir="$(portable_qmd_models_dir)" || return 1
156
+ [ -d "$dir" ] || return 1
157
+ if [ -n "${QMD_EMBED_MODEL:-}" ]; then
158
+ want="${QMD_EMBED_MODEL##*/}"
159
+ if [ -n "$want" ] && [ -f "$dir/$want" ]; then
160
+ sz="$(wc -c < "$dir/$want" | tr -d '[:space:]')"
161
+ case "$sz" in
162
+ ''|*[!0-9]*) ;;
163
+ *) [ "$sz" -ge 1048576 ] && return 0 ;;
164
+ esac
165
+ fi
166
+ fi
167
+ for f in "$dir"/*.gguf; do
168
+ [ -f "$f" ] || continue
169
+ sz="$(wc -c < "$f" | tr -d '[:space:]')"
170
+ case "$sz" in
171
+ ''|*[!0-9]*) continue ;;
172
+ esac
173
+ [ "$sz" -ge 1048576 ] && return 0
174
+ done
175
+ return 1
176
+ }
177
+
178
+ # portable_qmd_cmd_would_download_model <command>
179
+ # Return 0 when a shell command would invoke qmd vsearch, query, embed, or
180
+ # pull — the subcommands that auto-download GGUF models on first use.
181
+ # First non-flag argument after a qmd token is the subcommand, so
182
+ # `qmd search "how to vsearch"` is NOT a match.
183
+ portable_qmd_cmd_would_download_model() {
184
+ local cmd="${1:-}"
185
+ [ -n "$cmd" ] || return 1
186
+ case "$cmd" in
187
+ *qmd*) ;;
188
+ *) return 1 ;;
189
+ esac
190
+ printf '%s\n' "$cmd" | awk '
191
+ BEGIN { found = 0 }
192
+ {
193
+ n = split($0, raw, /[[:space:];|&]+/)
194
+ for (i = 1; i <= n; i++) {
195
+ tok = raw[i]
196
+ if (tok == "") continue
197
+ base = tok
198
+ sub(/^.*\//, "", base)
199
+ if (base == "qmd" || base == "qmd.exe") {
200
+ j = i + 1
201
+ while (j <= n && raw[j] ~ /^-/) j++
202
+ if (j <= n && raw[j] ~ /^(vsearch|query|embed|pull)$/) {
203
+ found = 1
204
+ exit
205
+ }
206
+ }
207
+ }
208
+ }
209
+ END { exit found ? 0 : 1 }
210
+ '
211
+ }
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env bash
2
+ # hq-core: public
3
+ # refresh-vault-access.sh — build/refresh the local vault-access manifest that
4
+ # enforce-vault-write-access.sh (PreToolUse hook) reads.
5
+ #
6
+ # Output: <hqRoot>/.hq/vault-access.json
7
+ #
8
+ # {
9
+ # "version": 1,
10
+ # "generatedAt": "2026-01-01T00:00:00Z",
11
+ # "companies": {
12
+ # "<slug>": {
13
+ # "role": "owner|admin|member|guest|unknown",
14
+ # "grants": [ { "path": "reports/*", "permission": "read|write" } ]
15
+ # }
16
+ # }
17
+ # }
18
+ #
19
+ # Sources (best-effort, all via the hq CLI):
20
+ # - grants: `hq files shared-with-me` (explicit per-prefix grants)
21
+ # - role: `hq members --company <slug> list` matched against the caller's
22
+ # email (from `hq whoami`, override with HQ_VAULT_ACCESS_EMAIL)
23
+ #
24
+ # Fail-open by design: anything this script cannot determine is recorded as
25
+ # role "unknown" (the hook does not enforce unknown roles), and a company it
26
+ # cannot query at all is left out of the manifest entirely (the hook allows
27
+ # companies absent from the manifest). The server-side STS/ACL layer remains
28
+ # the authoritative security boundary — this manifest only powers the local
29
+ # early-warning block for read-only shares.
30
+ #
31
+ # Usage:
32
+ # bash core/scripts/refresh-vault-access.sh [--company <slug>]... [--root <hqRoot>]
33
+ #
34
+ # With no --company, refreshes every company that appears in the
35
+ # `hq files shared-with-me` cross-company roll-up.
36
+
37
+ set -uo pipefail
38
+
39
+ log() { printf 'refresh-vault-access: %s\n' "$*" >&2; }
40
+
41
+ ROOT=""
42
+ SLUGS=()
43
+ while [ $# -gt 0 ]; do
44
+ case "$1" in
45
+ --root)
46
+ ROOT="${2:-}"
47
+ shift 2 || { log "--root requires a value"; exit 1; }
48
+ ;;
49
+ --company)
50
+ SLUGS[${#SLUGS[@]}]="${2:-}"
51
+ shift 2 || { log "--company requires a value"; exit 1; }
52
+ ;;
53
+ -h|--help)
54
+ sed -n '2,33p' "$0" | sed 's/^# \{0,1\}//'
55
+ exit 0
56
+ ;;
57
+ *)
58
+ log "unknown argument: $1"
59
+ exit 1
60
+ ;;
61
+ esac
62
+ done
63
+
64
+ is_hq_root() { [ -n "${1:-}" ] && [ -d "${1%/}/core" ] && [ -d "${1%/}/.claude" ]; }
65
+
66
+ if [ -z "$ROOT" ]; then
67
+ if is_hq_root "${CLAUDE_PROJECT_DIR:-}"; then
68
+ ROOT="$CLAUDE_PROJECT_DIR"
69
+ else
70
+ dir="$PWD"
71
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
72
+ if is_hq_root "$dir"; then ROOT="$dir"; break; fi
73
+ dir="$(dirname "$dir")"
74
+ done
75
+ fi
76
+ fi
77
+ if ! is_hq_root "$ROOT"; then
78
+ log "could not resolve the HQ root (pass --root <hqRoot>)"
79
+ exit 1
80
+ fi
81
+
82
+ command -v jq >/dev/null 2>&1 || { log "jq is required"; exit 1; }
83
+ if ! command -v hq >/dev/null 2>&1; then
84
+ log "hq CLI not found — nothing refreshed (the write-access hook stays fail-open without a manifest)"
85
+ exit 0
86
+ fi
87
+
88
+ strip_ansi() { sed -e 's/\x1b\[[0-9;]*m//g'; }
89
+
90
+ # ── caller email (for role lookup) ─────────────────────────────────────────
91
+ EMAIL="${HQ_VAULT_ACCESS_EMAIL:-}"
92
+ if [ -z "$EMAIL" ]; then
93
+ EMAIL="$(hq whoami 2>/dev/null | strip_ansi \
94
+ | grep -Eo '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' \
95
+ | grep -v '@outposts\.' | head -1 || true)"
96
+ fi
97
+ [ -n "$EMAIL" ] || log "could not determine caller email — roles will be 'unknown' (hook will not enforce)"
98
+
99
+ # ── explicit grants via shared-with-me ─────────────────────────────────────
100
+ # Table columns: COMPANY PATH PERMISSION SOURCE (header + ─── separator).
101
+ fetch_grants_tsv() {
102
+ # $1 = optional slug filter; prints "slug<TAB>path<TAB>permission" rows.
103
+ local slug="${1:-}" out
104
+ if [ -n "$slug" ]; then
105
+ out="$(hq files shared-with-me --company "$slug" 2>/dev/null | strip_ansi)" || return 1
106
+ else
107
+ out="$(hq files shared-with-me 2>/dev/null | strip_ansi)" || return 1
108
+ fi
109
+ # hq-cli >= 5.142 renders a private-folder row as "foo/ (private folder)";
110
+ # drop the label so the PATH column stays the verbatim `foo/` pattern.
111
+ printf '%s\n' "$out" | awk '
112
+ { sub(/ \(private folder\)/, "") }
113
+ /^COMPANY[[:space:]]/ { next }
114
+ /^─/ { next }
115
+ /^[[:space:]]*$/ { next }
116
+ /^Nothing is explicitly shared/ { next }
117
+ NF >= 3 {
118
+ perm = ""
119
+ if ($3 == "read" || $3 == "write" || $3 == "admin") perm = $3
120
+ else if (NF >= 4 && ($4 == "read" || $4 == "write" || $4 == "admin")) perm = $4
121
+ if (perm != "") printf "%s\t%s\t%s\n", $1, $2, perm
122
+ }'
123
+ }
124
+
125
+ # ── role lookup ────────────────────────────────────────────────────────────
126
+ fetch_role() {
127
+ # $1 = slug; prints role or "unknown". Never fails.
128
+ local slug="$1" out role=""
129
+ if [ -n "$EMAIL" ]; then
130
+ # --company is declared on the parent `members` group, so it goes first.
131
+ out="$(hq members --company "$slug" list 2>/dev/null | strip_ansi)" || out=""
132
+ if [ -n "$out" ]; then
133
+ role="$(printf '%s\n' "$out" | awk -v email="$EMAIL" '$1 == email { print $2; exit }')"
134
+ fi
135
+ fi
136
+ case "$role" in
137
+ owner|admin|member|guest) printf '%s' "$role" ;;
138
+ *) printf 'unknown' ;;
139
+ esac
140
+ }
141
+
142
+ GRANTS_TSV=""
143
+ if [ "${#SLUGS[@]}" -gt 0 ]; then
144
+ for slug in "${SLUGS[@]}"; do
145
+ rows="$(fetch_grants_tsv "$slug")" || { log "skipping $slug — shared-with-me query failed"; continue; }
146
+ GRANTS_TSV="${GRANTS_TSV}${rows}
147
+ "
148
+ done
149
+ else
150
+ GRANTS_TSV="$(fetch_grants_tsv)" || { log "shared-with-me roll-up failed — nothing refreshed"; exit 0; }
151
+ fi
152
+
153
+ # Companies to include = every slug present in the grant rows (plus any
154
+ # explicitly requested slugs, so a company with zero explicit grants still
155
+ # gets a role entry when asked for by name).
156
+ ALL_SLUGS="$(printf '%s\n' "$GRANTS_TSV" | awk -F'\t' 'NF { print $1 }' | sort -u)"
157
+ if [ "${#SLUGS[@]}" -gt 0 ]; then
158
+ ALL_SLUGS="$(printf '%s\n%s\n' "$ALL_SLUGS" "$(printf '%s\n' "${SLUGS[@]}")" | awk 'NF' | sort -u)"
159
+ fi
160
+
161
+ if [ -z "$ALL_SLUGS" ]; then
162
+ log "no explicit grants found — writing an empty manifest (hook stays fail-open everywhere)"
163
+ fi
164
+
165
+ COMPANIES_JSON="{}"
166
+ while IFS= read -r slug; do
167
+ [ -n "$slug" ] || continue
168
+ role="$(fetch_role "$slug")"
169
+ grants_json="$(printf '%s\n' "$GRANTS_TSV" \
170
+ | awk -F'\t' -v s="$slug" '$1 == s { printf "%s\t%s\n", $2, $3 }' \
171
+ | jq -Rn '[inputs | split("\t") | select(length == 2) | {path: .[0], permission: .[1]}]')"
172
+ COMPANIES_JSON="$(printf '%s' "$COMPANIES_JSON" \
173
+ | jq --arg slug "$slug" --arg role "$role" --argjson grants "$grants_json" \
174
+ '. + {($slug): {role: $role, grants: $grants}}')"
175
+ done <<EOF
176
+ $ALL_SLUGS
177
+ EOF
178
+
179
+ mkdir -p "$ROOT/.hq"
180
+ TMP_OUT="$(mktemp "$ROOT/.hq/vault-access.json.XXXXXX")"
181
+ jq -n --argjson companies "$COMPANIES_JSON" \
182
+ --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
183
+ '{version: 1, generatedAt: $ts, companies: $companies}' > "$TMP_OUT"
184
+ mv "$TMP_OUT" "$ROOT/.hq/vault-access.json"
185
+
186
+ N_COMPANIES="$(printf '%s\n' "$ALL_SLUGS" | awk 'NF' | wc -l | tr -d ' ')"
187
+ log "wrote .hq/vault-access.json ($N_COMPANIES companies, identity: ${EMAIL:-unknown})"
188
+ exit 0
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env bash
2
+ # hq-core: public
3
+ # resolve-company.sh — resolve the active company for skills and routing.
4
+ #
5
+ # Resolution order (first hit wins): explicit manifest slug in the prompt,
6
+ # session company_slug, enabled device default, then none. Prompt matching is
7
+ # whole-token only; free text never guesses a company.
8
+ #
9
+ # Output: {"company":"<slug>","source":"prompt|session|device_default|none"}
10
+ # Always exits 0 and never writes state.
11
+
12
+ set -uo pipefail
13
+
14
+ ROOT=""
15
+ PROMPT=""
16
+ HAVE_PROMPT=0
17
+
18
+ while [ $# -gt 0 ]; do
19
+ case "$1" in
20
+ --root) ROOT="${2:-}"; shift 2 || shift ;;
21
+ --prompt) PROMPT="${2:-}"; HAVE_PROMPT=1; shift 2 || shift ;;
22
+ --help|-h)
23
+ sed -n '2,14p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
24
+ exit 0
25
+ ;;
26
+ *) shift ;;
27
+ esac
28
+ done
29
+
30
+ if [ "$HAVE_PROMPT" -eq 0 ] && [ ! -t 0 ]; then
31
+ PROMPT="$(cat 2>/dev/null || true)"
32
+ fi
33
+ : "${PROMPT:=}"
34
+
35
+ emit() {
36
+ printf '{"company":"%s","source":"%s"}\n' "$1" "$2"
37
+ exit 0
38
+ }
39
+
40
+ if [ -z "$ROOT" ]; then
41
+ ROOT="${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && cd .. && pwd)}}"
42
+ fi
43
+
44
+ MANIFEST="$ROOT/companies/manifest.yaml"
45
+ [ -f "$MANIFEST" ] || emit "" "none"
46
+
47
+ SLUGS="$(
48
+ awk '
49
+ function keep(slug) {
50
+ return slug != "" && slug != "_template" && slug != "companies" && slug != "unaffiliated_repos"
51
+ }
52
+ /^companies:[[:space:]]*$/ { wrapped = 1; next }
53
+ wrapped && /^[^[:space:]][^:]*:[[:space:]]*$/ { wrapped = 0 }
54
+ wrapped && /^ [A-Za-z][A-Za-z0-9_-]*:/ {
55
+ line = $0; sub(/^[[:space:]]+/, "", line); sub(/:.*/, "", line)
56
+ if (keep(line)) print line
57
+ next
58
+ }
59
+ !wrapped && /^[A-Za-z][A-Za-z0-9_-]*:/ {
60
+ line = $0; sub(/:.*/, "", line)
61
+ if (keep(line)) print line
62
+ }
63
+ ' "$MANIFEST" | sort -u
64
+ )"
65
+ [ -n "$SLUGS" ] || emit "" "none"
66
+
67
+ is_known_slug() {
68
+ printf '%s\n' "$SLUGS" | grep -Fxq "$1"
69
+ }
70
+
71
+ prompt_slug() {
72
+ local normalized="" match=""
73
+ [ -n "$PROMPT" ] || return 0
74
+ normalized="$(printf '%s' "$PROMPT" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9_-' ' ')"
75
+ match="$(
76
+ # Prompt normalization is case-insensitive, but company IDs are not.
77
+ # Keep a folded match only when exactly one registered ID has that key.
78
+ # If IDs differ only by case, do not guess which tenant the prompt means.
79
+ printf '%s\n' "$SLUGS" | awk -v line="$normalized" '
80
+ BEGIN {
81
+ n = split(line, tok, /[ \t]+/)
82
+ for (i = 1; i <= n; i++) if (tok[i] != "" && !(tok[i] in pos)) pos[tok[i]] = i
83
+ bestSlug = ""; bestLen = 0; bestPos = 0
84
+ }
85
+ {
86
+ slug = $0
87
+ key = tolower(slug)
88
+ if (key in pos) {
89
+ matches[key]++
90
+ candidate[key] = slug
91
+ }
92
+ }
93
+ END {
94
+ for (key in matches) {
95
+ if (matches[key] != 1) continue
96
+ slug = candidate[key]
97
+ len = length(slug); p = pos[key]
98
+ if (len > bestLen || (len == bestLen && p < bestPos)) {
99
+ bestSlug = slug; bestLen = len; bestPos = p
100
+ }
101
+ }
102
+ if (bestSlug != "") print bestSlug
103
+ }
104
+ '
105
+ )"
106
+ printf '%s' "$match"
107
+ }
108
+
109
+ MATCH="$(prompt_slug)"
110
+ [ -n "$MATCH" ] && emit "$MATCH" "prompt"
111
+
112
+ SESSION_CO=""
113
+ if [ -x "$ROOT/core/scripts/hq-session.sh" ]; then
114
+ SESSION_CO="$(bash "$ROOT/core/scripts/hq-session.sh" get company_slug 2>/dev/null || true)"
115
+ SESSION_CO="$(printf '%s' "$SESSION_CO" | tr -d '[:space:]\"')"
116
+ fi
117
+ if [ -n "$SESSION_CO" ] && is_known_slug "$SESSION_CO"; then
118
+ emit "$SESSION_CO" "session"
119
+ fi
120
+
121
+ is_fleet_identity() {
122
+ [ -n "${HQ_AGENT_WORKDIR:-}" ] && return 0
123
+ [ -n "${HQ_AGENT_COMPANY_DIR:-}" ] && return 0
124
+ [ -n "${HQ_AGENT_IDENTITY_FILE:-}" ] && return 0
125
+ [ -f /etc/hq-agent/identity.json ] && return 0
126
+ [ -f /etc/hq-agent/machine-creds.json ] && return 0
127
+ agent_root="${HQ_AGENT_ROOT_PREFIX:-}"
128
+ if [ -n "$agent_root" ]; then
129
+ [ -f "${agent_root%/}/var/lib/hq-agent/identity.json" ] && return 0
130
+ else
131
+ [ -f /var/lib/hq-agent/identity.json ] && return 0
132
+ fi
133
+ [ -f /var/lib/hq-agent/machine-creds.json ] && return 0
134
+ return 1
135
+ }
136
+
137
+ run_with_timeout() {
138
+ command -v node >/dev/null 2>&1 || return 127
139
+ node -e '
140
+ const { spawn } = require("child_process");
141
+ const command = process.argv[1];
142
+ if (!command) process.exit(127);
143
+ const child = spawn(command, process.argv.slice(2), { detached: true, stdio: ["ignore", "pipe", "ignore"] });
144
+ let finished = false;
145
+ const finish = (status) => {
146
+ if (finished) return;
147
+ finished = true;
148
+ clearTimeout(timer);
149
+ try { process.kill(-child.pid, "SIGTERM"); } catch (_) {}
150
+ process.exit(status);
151
+ };
152
+ child.stdout.on("data", (chunk) => process.stdout.write(chunk));
153
+ child.once("error", () => finish(127));
154
+ child.once("exit", (code) => finish(typeof code === "number" ? code : 1));
155
+ const timer = setTimeout(() => finish(124), 2000);
156
+ ' "$@"
157
+ }
158
+
159
+ if ! is_fleet_identity && command -v hq >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
160
+ DEFAULT_JSON="$(run_with_timeout hq mesh context default get --json 2>/dev/null || true)"
161
+ DEFAULT_SLUG="$(printf '%s' "$DEFAULT_JSON" | jq -er '(.defaultCompany // .) as $default | if $default.enabled == true and ($default.source // "") != "disabled" and ($default.needsChoice // false) == false and ($default.slug | type == "string") then $default.slug else empty end' 2>/dev/null || true)"
162
+ DEFAULT_SLUG="$(printf '%s' "$DEFAULT_SLUG" | tr -d '[:space:]\"')"
163
+ if [ -n "$DEFAULT_SLUG" ] && is_known_slug "$DEFAULT_SLUG"; then
164
+ # A device default is only a local preference. Reconcile performs the
165
+ # token-backed active-membership check; never promote this fallback when
166
+ # that preflight is unavailable, times out, or returns a noncanonical row.
167
+ PREFLIGHT_SESSION_ID="resolve-default-$$-$RANDOM"
168
+ PREFLIGHT_OPERATION_ID="resolve-default-op-$$-$RANDOM"
169
+ PREFLIGHT_OBSERVATION="$(jq -cn \
170
+ --arg session_id "$PREFLIGHT_SESSION_ID" \
171
+ --arg operation_id "$PREFLIGHT_OPERATION_ID" \
172
+ --arg root "$ROOT" \
173
+ '{contractVersion:1,clientOperationId:$operation_id,identity:{sessionId:$session_id,harness:"hq-core",adapterVersion:"1"},cwd:$root,hqRoot:$root}')"
174
+ PREFLIGHT_STATUS=0
175
+ PREFLIGHT_RESULT="$(run_with_timeout hq mesh context reconcile --observation-json "$PREFLIGHT_OBSERVATION" --machine --offline 2>/dev/null)" || PREFLIGHT_STATUS=$?
176
+ VALIDATED_SLUG=""
177
+ if [ "$PREFLIGHT_STATUS" -eq 0 ]; then
178
+ VALIDATED_SLUG="$(printf '%s' "$PREFLIGHT_RESULT" | jq -er \
179
+ --arg slug "$DEFAULT_SLUG" \
180
+ --arg session_id "$PREFLIGHT_SESSION_ID" \
181
+ --arg operation_id "$PREFLIGHT_OPERATION_ID" '
182
+ if type == "object"
183
+ and .contractVersion == 1
184
+ and (.sessionId | type == "string") and .sessionId == $session_id
185
+ and (.clientOperationId | type == "string") and .clientOperationId == $operation_id
186
+ and (.kind == "queued" or .kind == "needs_project" or .kind == "needs_task" or .kind == "bound")
187
+ and (.classification == "needs_project" or .classification == "needs_task" or .classification == "bound")
188
+ and (.delivery == "queued" or .delivery == "acked")
189
+ and .lifecycle == "open"
190
+ and .companySlug == $slug
191
+ and (.companyUid | type == "string") and (.companyUid | length > 0)
192
+ then .companySlug else empty end
193
+ ' 2>/dev/null || true)"
194
+ fi
195
+ [ "$VALIDATED_SLUG" = "$DEFAULT_SLUG" ] && emit "$DEFAULT_SLUG" "device_default"
196
+ fi
197
+ fi
198
+
199
+ emit "" "none"
@@ -5730,6 +5730,71 @@ export declare const COMMAND_CATALOG: readonly [{
5730
5730
  readonly options: readonly [];
5731
5731
  readonly subcommands: readonly [];
5732
5732
  }];
5733
+ }, {
5734
+ readonly name: "resolve-company";
5735
+ readonly description: "Resolve the active company for skills and routing";
5736
+ readonly aliases: readonly [];
5737
+ readonly hidden: false;
5738
+ readonly usage: "[args...]";
5739
+ readonly arguments: readonly [{
5740
+ readonly name: "args";
5741
+ readonly required: false;
5742
+ readonly variadic: true;
5743
+ }];
5744
+ readonly options: readonly [];
5745
+ readonly subcommands: readonly [];
5746
+ }, {
5747
+ readonly name: "audit-log";
5748
+ readonly description: "Append, query, or summarize the HQ audit log";
5749
+ readonly aliases: readonly [];
5750
+ readonly hidden: false;
5751
+ readonly usage: "[args...]";
5752
+ readonly arguments: readonly [{
5753
+ readonly name: "args";
5754
+ readonly required: false;
5755
+ readonly variadic: true;
5756
+ }];
5757
+ readonly options: readonly [];
5758
+ readonly subcommands: readonly [];
5759
+ }, {
5760
+ readonly name: "git-pack-extension-check";
5761
+ readonly description: "Find or repair extensionless Git pack files";
5762
+ readonly aliases: readonly [];
5763
+ readonly hidden: false;
5764
+ readonly usage: "[args...]";
5765
+ readonly arguments: readonly [{
5766
+ readonly name: "args";
5767
+ readonly required: false;
5768
+ readonly variadic: true;
5769
+ }];
5770
+ readonly options: readonly [];
5771
+ readonly subcommands: readonly [];
5772
+ }, {
5773
+ readonly name: "pin";
5774
+ readonly description: "Pin the current session to one goal";
5775
+ readonly aliases: readonly [];
5776
+ readonly hidden: false;
5777
+ readonly usage: "[args...]";
5778
+ readonly arguments: readonly [{
5779
+ readonly name: "args";
5780
+ readonly required: false;
5781
+ readonly variadic: true;
5782
+ }];
5783
+ readonly options: readonly [];
5784
+ readonly subcommands: readonly [];
5785
+ }, {
5786
+ readonly name: "refresh-vault-access";
5787
+ readonly description: "Refresh the local vault access manifest";
5788
+ readonly aliases: readonly [];
5789
+ readonly hidden: false;
5790
+ readonly usage: "[args...]";
5791
+ readonly arguments: readonly [{
5792
+ readonly name: "args";
5793
+ readonly required: false;
5794
+ readonly variadic: true;
5795
+ }];
5796
+ readonly options: readonly [];
5797
+ readonly subcommands: readonly [];
5733
5798
  }, {
5734
5799
  readonly name: "worktree-gc";
5735
5800
  readonly description: "Safely collect old, preserved HQ worktrees";