@indigoai-us/hq-cli 5.276.0 → 5.277.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 +76 -0
- package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
- package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
- package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
- package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
- package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
- package/dist/command-catalog.generated.d.ts +239 -1
- package/dist/command-catalog.generated.js +302 -1
- package/dist/commands/core-delegate.d.ts +6 -0
- package/dist/commands/core-delegate.js +987 -0
- package/dist/commands/core.js +10 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.js +173 -1
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +11 -4
- package/dist/commands/lanes-accounts.js +44 -2
- package/dist/commands/lanes-message.js +69 -12
- package/dist/commands/lanes-resume.js +2 -0
- package/dist/commands/lanes-shared.d.ts +3 -0
- package/dist/commands/lanes-shared.js +12 -2
- package/dist/commands/lanes.d.ts +5 -1
- package/dist/commands/lanes.js +399 -47
- package/dist/commands/secrets.d.ts +16 -1
- package/dist/commands/secrets.js +77 -48
- package/dist/commands/sync-mode.d.ts +11 -0
- package/dist/commands/sync-mode.js +30 -0
- package/dist/fast-scaffold.d.ts +17 -0
- package/dist/fast-scaffold.js +91 -0
- package/dist/index.js +17 -1
- package/dist/lanes-drain-hook.js +154 -7
- package/dist/lib/agent-kit/run/inbox.js +3 -2
- package/dist/lib/checkpoint-ontology/apply.d.ts +6 -0
- package/dist/lib/checkpoint-ontology/apply.js +60 -3
- package/dist/lib/checkpoint-ontology/plan.d.ts +13 -0
- package/dist/lib/checkpoint-ontology/plan.js +13 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
- package/dist/lib/checkpoint-ontology/tree.js +34 -0
- package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
- package/dist/lib/core-utils/jq-json-serializer.js +10 -0
- package/dist/lib/core-utils/secret-patterns.d.ts +41 -0
- package/dist/lib/core-utils/secret-patterns.js +33 -0
- package/dist/lib/daemon/catalog.d.ts +18 -2
- package/dist/lib/daemon/catalog.js +18 -2
- package/dist/lib/daemon/config.d.ts +6 -0
- package/dist/lib/daemon/config.js +15 -2
- package/dist/lib/daemon/requests.d.ts +4 -0
- package/dist/lib/daemon/requests.js +2 -0
- package/dist/lib/daemon/run.js +94 -8
- package/dist/lib/daemon/supervisor.d.ts +8 -0
- package/dist/lib/daemon/supervisor.js +101 -1
- package/dist/lib/daemon/sync-runner.d.ts +58 -0
- package/dist/lib/daemon/sync-runner.js +277 -0
- package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +52 -0
- 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/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +46 -8
- package/dist/lib/lanes/config.js +15 -0
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -0
- package/dist/lib/lanes/hire-grant-dispatch.js +41 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
- package/dist/lib/lanes/outpost-dispatch.js +11 -2
- package/dist/lib/lanes/spawn.js +17 -10
- package/dist/lib/lanes/store.js +45 -1
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -2
- package/dist/secrets-env-fast-path.d.ts +5 -0
- package/dist/secrets-env-fast-path.js +16 -0
- package/dist/secrets-env-fast-route.d.ts +18 -0
- package/dist/secrets-env-fast-route.js +54 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,82 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.277.0] — 2026-09-29
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq lanes accounts list` and `accounts balance` now report a Claude account
|
|
10
|
+
whose home is missing on the current host and show the `set-home` repair
|
|
11
|
+
command. Balance exits non-zero for an enabled account in that state. On
|
|
12
|
+
Linux, account login stores a host-specific home and requires `--home` when
|
|
13
|
+
an existing account only has a shared home.
|
|
14
|
+
- Claude account balancing can use the default-off
|
|
15
|
+
`lanes.claude-account-balance-signals` hq-flags key to break equal live-lane
|
|
16
|
+
counts by the age of each account's latest completed lane run when provider
|
|
17
|
+
percentage data is unavailable.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Add a default-off `cli.secrets-env-fast-path` hq-flags route for `hq secrets
|
|
22
|
+
env`. It skips the full CLI graph while reusing the existing credential,
|
|
23
|
+
usage-audit, server authorization, and offline-cache behavior.
|
|
24
|
+
- `hq lanes list --json` accepts repeatable `--lane` filters, `--active`, and
|
|
25
|
+
owner-only hire-hierarchy snapshots. Remote lane lookup now requests the
|
|
26
|
+
needed hierarchy once per Outpost invocation and falls back to an unfiltered
|
|
27
|
+
list when an older owner CLI rejects a new filter. Internal snapshot switches
|
|
28
|
+
stay out of normal help and the generated command catalog.
|
|
29
|
+
- Add the default-off `checkpoint.ontology.dominant-sibling-link` hq-flags key.
|
|
30
|
+
When it is on, checkpoint ontology apply moves a knowledge link from an entity
|
|
31
|
+
whose name another type also holds to the same-name entity that other entity
|
|
32
|
+
files link to most. The move needs at least three times the inbound links and
|
|
33
|
+
at least five. An example is project Build Your Own AGI moving to the concept,
|
|
34
|
+
or the numbered copy `hq-core 2` moving to `hq-core`. Moves are reported
|
|
35
|
+
under `dominant_sibling_redirects`, and no entity file is written.
|
|
36
|
+
- Add the default-off `lanes.running-superseded-message-delivery` hq-flags key
|
|
37
|
+
so messages can reach otherwise messageable superseded lanes outside the done
|
|
38
|
+
and interrupted states. Accepted messages include a warning; done and
|
|
39
|
+
interrupted lanes keep the existing senior and successor checks.
|
|
40
|
+
- `hq core delegate resolve`, `pickup`, `repo`, `secrets`, and `transfer` are
|
|
41
|
+
available through native implementations when the default-off
|
|
42
|
+
`core-native-delegate` flag is enabled; the bundled scripts remain as the
|
|
43
|
+
rollback path.
|
|
44
|
+
|
|
45
|
+
- `hq lanes set <lane> --model <model> --effort <level> [--reason <text>]`
|
|
46
|
+
changes the model or effort a lane uses from its next resume, including the
|
|
47
|
+
keep-alive resume of a done lead. Before, keep-alive always resumed on the
|
|
48
|
+
recorded values, so a lead's model could not change without racing it. The
|
|
49
|
+
lane itself or its senior may run it; each change is kept in
|
|
50
|
+
`settings_history` and shown by `hq lanes show`.
|
|
51
|
+
- `hq daemon sync` reports runner ownership and health, starts an immediate sync pass, pauses or resumes sync, and sets per-company sync mode. The daemon waits for an existing desktop runner to release its lease before starting its own.
|
|
52
|
+
- `hq lanes interrupt <lane-id>` stops a running lane's turn and leaves it
|
|
53
|
+
`interrupted` (reason `operator_interrupt`) with its provider session, so
|
|
54
|
+
`hq lanes resume` continues it. Keep-alive does not resume it. The command
|
|
55
|
+
records the session id from the interrupted turn's output, holds the
|
|
56
|
+
provider's admission lock until the worker has stopped, and reports
|
|
57
|
+
`resumable: false` with a stderr warning when the turn produced no session id.
|
|
58
|
+
- An operator `hq lanes resume` clears keep-alive's `terminal_state` block, so
|
|
59
|
+
keep-alive can continue the lane after that turn.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- `hq lanes stop` on a running lane warns on stderr that the lane is killed and
|
|
64
|
+
cannot be resumed, names `hq lanes interrupt`, and adds `terminal: true` and
|
|
65
|
+
`resumable: false` to its JSON output, including for Outpost lanes stopped
|
|
66
|
+
from the primary.
|
|
67
|
+
- The lane drain hook avoids loading the lanes module graph when no lane is
|
|
68
|
+
resolved or its drop box is empty, while continuing to record tool activity
|
|
69
|
+
and using the full drain path whenever delivery or replay may be needed.
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- Doctor command tests use temporary home and state directories and skip the
|
|
74
|
+
live integrations inventory, so their results do not depend on a developer's
|
|
75
|
+
local HQ state.
|
|
76
|
+
- Outpost resume account moves forward the primary's hire-grant context through
|
|
77
|
+
the owner-side session move. The owner checks the current senior and hire
|
|
78
|
+
capacity before starting the worker.
|
|
79
|
+
- Security: `hq dm inbox --json` and agent-side raw message output omit the delegated `actingContext` object.
|
|
80
|
+
|
|
5
81
|
## [5.276.0] — 2026-09-28
|
|
6
82
|
|
|
7
83
|
### Added
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# hq-delegate-pickup.sh — close the delegation loop with a receipt or a FAILED
|
|
4
|
+
# state (2026-09-07).
|
|
5
|
+
#
|
|
6
|
+
# Why: the delegate pipeline ended at "sent", which is a send receipt, not a
|
|
7
|
+
# delivery receipt. Seven delegations sat at sent/building with no way to tell
|
|
8
|
+
# whether the recipient could act. This script adds the two missing states:
|
|
9
|
+
#
|
|
10
|
+
# sent --(evidence)--> picked-up recipient demonstrably has the work
|
|
11
|
+
# sent --(silence past window)--> failed nobody picked it up; sender is told why
|
|
12
|
+
#
|
|
13
|
+
# Evidence, checked in order (any one suffices):
|
|
14
|
+
# 1. --ack "<text>" an explicit acknowledgement (DM reply, chat) —
|
|
15
|
+
# recorded verbatim with a timestamp
|
|
16
|
+
# 2. repo branch has a commit by the recipient after sentAt
|
|
17
|
+
# (manifest.repo.path + repo.branch, author email == to.principal)
|
|
18
|
+
# 3. --evidence "<free text>" any other proof the operator observed
|
|
19
|
+
#
|
|
20
|
+
# Usage:
|
|
21
|
+
# hq-delegate-pickup.sh --manifest <path> --ack "<text>" # record receipt
|
|
22
|
+
# hq-delegate-pickup.sh --manifest <path> --evidence "<text>" # record receipt
|
|
23
|
+
# hq-delegate-pickup.sh --manifest <path> --check [--window-hours N] # probe repo; fail if silent past window (default 72)
|
|
24
|
+
# hq-delegate-pickup.sh --manifest <path> --status # print state line
|
|
25
|
+
#
|
|
26
|
+
# Exit 0: picked-up (or already). Exit 5: failed (window elapsed, no evidence).
|
|
27
|
+
# Exit 6: still waiting (inside window, no evidence). Exit 2: usage/precondition.
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
MANIFEST=""; ACK=""; EVIDENCE=""; CHECK=0; STATUS_ONLY=0; WINDOW="${HQ_DELEGATE_PICKUP_WINDOW_HOURS:-72}"
|
|
30
|
+
while [ $# -gt 0 ]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--manifest) MANIFEST="$2"; shift 2 ;;
|
|
33
|
+
--ack) ACK="$2"; shift 2 ;;
|
|
34
|
+
--evidence) EVIDENCE="$2"; shift 2 ;;
|
|
35
|
+
--check) CHECK=1; shift ;;
|
|
36
|
+
--window-hours) WINDOW="$2"; shift 2 ;;
|
|
37
|
+
--status) STATUS_ONLY=1; shift ;;
|
|
38
|
+
-h|--help) sed -n 2,28p "$0"; exit 0 ;;
|
|
39
|
+
*) echo "hq-delegate-pickup: unknown arg $1" >&2; exit 2 ;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
[ -n "$MANIFEST" ] && [ -r "$MANIFEST" ] || { echo "usage: --manifest <path> (--ack|--evidence|--check|--status)" >&2; exit 2; }
|
|
43
|
+
command -v jq >/dev/null 2>&1 || { echo "hq-delegate-pickup: jq required" >&2; exit 2; }
|
|
44
|
+
NOW="${HQ_DELEGATE_NOW:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
|
|
45
|
+
STATUS="$(jq -r '.status // empty' "$MANIFEST")"
|
|
46
|
+
SENT_AT="$(jq -r '.sentAt // empty' "$MANIFEST")"
|
|
47
|
+
TO="$(jq -r '.to.principal // empty' "$MANIFEST")"
|
|
48
|
+
NAME="$(jq -r '.to.displayName // .to.principal // "the recipient"' "$MANIFEST")"
|
|
49
|
+
|
|
50
|
+
if [ "$STATUS_ONLY" = 1 ]; then
|
|
51
|
+
jq -r '"\(.delegationId) \(.status)" + (if .pickedUpAt then " picked-up " + .pickedUpAt + " via " + (.pickup.kind // "?") else "" end) + (if .failedAt then " failed " + .failedAt + ": " + (.failure.reason // "") else "" end)' "$MANIFEST"
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
54
|
+
case "$STATUS" in
|
|
55
|
+
picked-up) echo "hq-delegate-pickup: already picked up ($(jq -r '.pickedUpAt' "$MANIFEST"))"; exit 0 ;;
|
|
56
|
+
failed) echo "hq-delegate-pickup: already failed ($(jq -r '.failure.reason // ""' "$MANIFEST"))"; exit 5 ;;
|
|
57
|
+
sent) ;;
|
|
58
|
+
*) echo "hq-delegate-pickup: manifest status is '$STATUS' — only a 'sent' delegation can be picked up (send it first)" >&2; exit 2 ;;
|
|
59
|
+
esac
|
|
60
|
+
|
|
61
|
+
record_pickup() { # <kind> <detail>
|
|
62
|
+
jq --arg now "$NOW" --arg k "$1" --arg d "$2" \
|
|
63
|
+
'.status = "picked-up" | .pickedUpAt = $now | .pickup = {kind: $k, detail: $d, recordedAt: $now}' \
|
|
64
|
+
"$MANIFEST" > "$MANIFEST.tmp" && mv "$MANIFEST.tmp" "$MANIFEST"
|
|
65
|
+
echo "hq-delegate-pickup: $NAME picked up the delegation ($1) — status advanced to 'picked-up'"
|
|
66
|
+
exit 0
|
|
67
|
+
}
|
|
68
|
+
[ -n "$ACK" ] && record_pickup ack "$ACK"
|
|
69
|
+
[ -n "$EVIDENCE" ] && record_pickup evidence "$EVIDENCE"
|
|
70
|
+
|
|
71
|
+
[ "$CHECK" = 1 ] || { echo "hq-delegate-pickup: nothing to do (pass --ack, --evidence, --check, or --status)" >&2; exit 2; }
|
|
72
|
+
|
|
73
|
+
# Evidence 2: a commit by the recipient on the handed-over branch after sentAt.
|
|
74
|
+
REPO="$(jq -r '.repo.path // empty' "$MANIFEST")"
|
|
75
|
+
BRANCH="$(jq -r '.repo.branch // empty' "$MANIFEST")"
|
|
76
|
+
if [ -n "$REPO" ] && [ -d "$REPO" ] && [ -n "$BRANCH" ] && [ -n "$TO" ]; then
|
|
77
|
+
git -C "$REPO" fetch -q origin "$BRANCH" 2>/dev/null || true
|
|
78
|
+
ref="origin/$BRANCH"; git -C "$REPO" show-ref --verify --quiet "refs/remotes/$ref" || ref="$BRANCH"
|
|
79
|
+
c="$(git -C "$REPO" log "$ref" --author="$TO" ${SENT_AT:+--since="$SENT_AT"} --format='%h %ad %s' --date=short -1 2>/dev/null || true)"
|
|
80
|
+
[ -n "$c" ] && record_pickup commit "$c on $BRANCH"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# No evidence. Inside the window: waiting. Past it: FAILED, with the reason.
|
|
84
|
+
if [ -n "$SENT_AT" ]; then
|
|
85
|
+
sent_epoch="$(date -u -d "$SENT_AT" +%s 2>/dev/null || date -u -j -f %Y-%m-%dT%H:%M:%SZ "$SENT_AT" +%s 2>/dev/null || echo 0)"
|
|
86
|
+
now_epoch="$(date -u -d "$NOW" +%s 2>/dev/null || date -u -j -f %Y-%m-%dT%H:%M:%SZ "$NOW" +%s 2>/dev/null || date +%s)"
|
|
87
|
+
age_h=$(( (now_epoch - sent_epoch) / 3600 ))
|
|
88
|
+
else
|
|
89
|
+
age_h=0
|
|
90
|
+
fi
|
|
91
|
+
if [ "$age_h" -lt "$WINDOW" ]; then
|
|
92
|
+
echo "hq-delegate-pickup: waiting — no pickup evidence from $NAME yet (${age_h}h of a ${WINDOW}h window)"
|
|
93
|
+
exit 6
|
|
94
|
+
fi
|
|
95
|
+
reason="no pickup evidence from $NAME within ${WINDOW}h of send (${SENT_AT:-unknown}): no acknowledgement, no commit by $TO on ${BRANCH:-the branch}"
|
|
96
|
+
jq --arg now "$NOW" --arg r "$reason" '.status = "failed" | .failedAt = $now | .failure = {reason: $r, recordedAt: $now}' \
|
|
97
|
+
"$MANIFEST" > "$MANIFEST.tmp" && mv "$MANIFEST.tmp" "$MANIFEST"
|
|
98
|
+
echo "hq-delegate-pickup: FAILED — $reason. Re-send with a direct ask, or hand it to someone else." >&2
|
|
99
|
+
exit 5
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# hq-delegate-repo.sh — code/branch handover for a delegation: make sure the
|
|
4
|
+
# project's branch exists on the remote, record uncommitted work as NOT
|
|
5
|
+
# transferred, and verify the recipient's repository access read-only.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# core/scripts/hq-delegate-repo.sh --manifest <path> [--yes] [--github-user <login>]
|
|
9
|
+
#
|
|
10
|
+
# Behavior (driven by the manifest's repo{} block; no-op when repo is null):
|
|
11
|
+
# - records remote URL and the branch's headSha into the manifest
|
|
12
|
+
# - a branch that exists only locally requires confirmation: without --yes
|
|
13
|
+
# the helper prints the plan and exits 2; with --yes it pushes via an
|
|
14
|
+
# explicitly anchored `git -C <abs path> push -u origin <branch>`
|
|
15
|
+
# - dirty/untracked files are captured into repo.dirtyFiles[] and appended
|
|
16
|
+
# to BRIEF.md as work NOT transferred — never silently dropped
|
|
17
|
+
# - recipient access is checked read-only via gh when --github-user is
|
|
18
|
+
# given; otherwise (or on a gh failure) repo.accessVerified stays false
|
|
19
|
+
# and the brief says so plainly — the helper never claims unverified access
|
|
20
|
+
# - never touches vaultPrefixes[] and never pushes repo content to the vault
|
|
21
|
+
#
|
|
22
|
+
# Env: HQ_ROOT — HQ root override (default: resolved from script location)
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
28
|
+
|
|
29
|
+
usage() { sed -n '3,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
|
|
30
|
+
die() { echo "hq-delegate-repo: $*" >&2; exit 1; }
|
|
31
|
+
|
|
32
|
+
command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
|
|
33
|
+
|
|
34
|
+
MANIFEST="" YES=0 GH_USER=""
|
|
35
|
+
while [ $# -gt 0 ]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
--manifest) MANIFEST="${2:-}"; shift 2 ;;
|
|
38
|
+
--yes) YES=1; shift ;;
|
|
39
|
+
--github-user) GH_USER="${2:-}"; shift 2 ;;
|
|
40
|
+
-h|--help) usage; exit 0 ;;
|
|
41
|
+
*) die "unknown argument: $1" ;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
[ -n "$MANIFEST" ] || die "--manifest is required"
|
|
46
|
+
[ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST"
|
|
47
|
+
jq -e . "$MANIFEST" >/dev/null 2>&1 || die "manifest is not valid JSON: $MANIFEST"
|
|
48
|
+
|
|
49
|
+
BUNDLE_DIR="$(cd "$(dirname "$MANIFEST")" && pwd)"
|
|
50
|
+
BRIEF="$BUNDLE_DIR/BRIEF.md"
|
|
51
|
+
|
|
52
|
+
# --- no-repo projects skip cleanly -------------------------------------------
|
|
53
|
+
|
|
54
|
+
if jq -e '.repo == null' "$MANIFEST" >/dev/null; then
|
|
55
|
+
echo "hq-delegate-repo: project has no repoPath — nothing to hand over"
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
REPO_REL="$(jq -r '.repo.path // empty' "$MANIFEST")"
|
|
60
|
+
BRANCH="$(jq -r '.repo.branch // empty' "$MANIFEST")"
|
|
61
|
+
[ -n "$REPO_REL" ] || die "manifest repo block has no path"
|
|
62
|
+
|
|
63
|
+
REPO_DIR="$HQ_ROOT/$REPO_REL"
|
|
64
|
+
[ -d "$REPO_DIR/.git" ] || git -C "$REPO_DIR" rev-parse --git-dir >/dev/null 2>&1 \
|
|
65
|
+
|| die "not a git repository: $REPO_DIR"
|
|
66
|
+
|
|
67
|
+
REMOTE_URL="$(git -C "$REPO_DIR" remote get-url origin 2>/dev/null || true)"
|
|
68
|
+
|
|
69
|
+
# --- dirty / untracked work is recorded, never dropped -----------------------
|
|
70
|
+
|
|
71
|
+
DIRTY_JSON="$(git -C "$REPO_DIR" status --porcelain | sed 's/^...//' | jq -R . | jq -cs .)"
|
|
72
|
+
|
|
73
|
+
# --- branch state -------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
BRANCH_PUSHED=false HEAD_SHA=""
|
|
76
|
+
if [ -n "$BRANCH" ]; then
|
|
77
|
+
git check-ref-format --branch "$BRANCH" >/dev/null 2>&1 || die "invalid branch name in PRD: $BRANCH"
|
|
78
|
+
LOCAL_SHA="$(git -C "$REPO_DIR" rev-parse --verify --quiet "refs/heads/$BRANCH" || true)"
|
|
79
|
+
REMOTE_SHA="$(git -C "$REPO_DIR" rev-parse --verify --quiet "refs/remotes/origin/$BRANCH" || true)"
|
|
80
|
+
|
|
81
|
+
if [ -n "$REMOTE_SHA" ]; then
|
|
82
|
+
BRANCH_PUSHED=true
|
|
83
|
+
HEAD_SHA="$REMOTE_SHA"
|
|
84
|
+
elif [ -n "$LOCAL_SHA" ]; then
|
|
85
|
+
echo "Branch handover plan — repo '$REPO_REL':"
|
|
86
|
+
echo " branch '$BRANCH' exists only locally ($LOCAL_SHA)"
|
|
87
|
+
echo " action: git -C $REPO_DIR push -u origin $BRANCH"
|
|
88
|
+
if [ "$YES" -ne 1 ]; then
|
|
89
|
+
echo
|
|
90
|
+
echo "hq-delegate-repo: confirmation required — re-run with --yes to push the branch" >&2
|
|
91
|
+
exit 2
|
|
92
|
+
fi
|
|
93
|
+
git -C "$REPO_DIR" push -u origin "$BRANCH" \
|
|
94
|
+
|| die "failed to push branch '$BRANCH' to origin"
|
|
95
|
+
BRANCH_PUSHED=true
|
|
96
|
+
HEAD_SHA="$LOCAL_SHA"
|
|
97
|
+
else
|
|
98
|
+
echo "hq-delegate-repo: branch '$BRANCH' does not exist yet (declared in the PRD, not started) — recording as unpushed"
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# --- read-only recipient access check ----------------------------------------
|
|
103
|
+
|
|
104
|
+
ACCESS_VERIFIED=false ACCESS_NOTE=""
|
|
105
|
+
if [ -n "$GH_USER" ] && [ -n "$REMOTE_URL" ] && command -v gh >/dev/null 2>&1; then
|
|
106
|
+
# owner/repo from ssh or https remote forms
|
|
107
|
+
OWNER_REPO="$(printf '%s' "$REMOTE_URL" | sed -E 's#^(git@[^:]+:|https?://[^/]+/)##; s#\.git$##')"
|
|
108
|
+
if gh api "repos/$OWNER_REPO/collaborators/$GH_USER" >/dev/null 2>&1; then
|
|
109
|
+
ACCESS_VERIFIED=true
|
|
110
|
+
ACCESS_NOTE="verified: $GH_USER is a collaborator on $OWNER_REPO"
|
|
111
|
+
else
|
|
112
|
+
ACCESS_NOTE="UNVERIFIED: could not confirm $GH_USER has access to $OWNER_REPO — the recipient may need to be invited before they can pull the branch"
|
|
113
|
+
fi
|
|
114
|
+
else
|
|
115
|
+
ACCESS_NOTE="UNVERIFIED: recipient repository access was not checked (no GitHub login supplied) — confirm they can reach the repo before assuming so"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# --- manifest update (repo block only — vaultPrefixes are never touched) -----
|
|
119
|
+
|
|
120
|
+
TMP_MANIFEST="$(mktemp)"
|
|
121
|
+
jq \
|
|
122
|
+
--arg remote "$REMOTE_URL" \
|
|
123
|
+
--arg headSha "$HEAD_SHA" \
|
|
124
|
+
--argjson dirty "$DIRTY_JSON" \
|
|
125
|
+
--argjson pushed "$BRANCH_PUSHED" \
|
|
126
|
+
--argjson access "$ACCESS_VERIFIED" \
|
|
127
|
+
--arg accessNote "$ACCESS_NOTE" \
|
|
128
|
+
'.repo.remote = (if $remote == "" then null else $remote end)
|
|
129
|
+
| .repo.headSha = (if $headSha == "" then null else $headSha end)
|
|
130
|
+
| .repo.dirtyFiles = $dirty
|
|
131
|
+
| .repo.branchPushed = $pushed
|
|
132
|
+
| .repo.accessVerified = $access
|
|
133
|
+
| .repo.accessNote = $accessNote' \
|
|
134
|
+
"$MANIFEST" > "$TMP_MANIFEST" && mv "$TMP_MANIFEST" "$MANIFEST"
|
|
135
|
+
|
|
136
|
+
# --- brief: call out untransferred work and unverified access ----------------
|
|
137
|
+
|
|
138
|
+
if [ -f "$BRIEF" ]; then
|
|
139
|
+
DIRTY_COUNT="$(printf '%s' "$DIRTY_JSON" | jq 'length')"
|
|
140
|
+
if [ "$DIRTY_COUNT" -gt 0 ] || [ "$ACCESS_VERIFIED" != "true" ]; then
|
|
141
|
+
{
|
|
142
|
+
echo
|
|
143
|
+
echo "## Code handover notes"
|
|
144
|
+
echo
|
|
145
|
+
if [ "$DIRTY_COUNT" -gt 0 ]; then
|
|
146
|
+
echo "The delegator's working tree had uncommitted changes that are **not transferred** with this delegation. If something seems missing, these files are why — ask the delegator to commit and push them:"
|
|
147
|
+
echo
|
|
148
|
+
printf '%s' "$DIRTY_JSON" | jq -r '.[] | "- `\(.)`"'
|
|
149
|
+
echo
|
|
150
|
+
fi
|
|
151
|
+
if [ "$ACCESS_VERIFIED" != "true" ]; then
|
|
152
|
+
echo "$ACCESS_NOTE"
|
|
153
|
+
fi
|
|
154
|
+
} >> "$BRIEF"
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
echo "hq-delegate-repo: recorded branch state (pushed=$BRANCH_PUSHED, dirty=$(printf '%s' "$DIRTY_JSON" | jq 'length') files, accessVerified=$ACCESS_VERIFIED)"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# hq-delegate-resolve.sh — resolve a delegation recipient to a confirmed
|
|
4
|
+
# principal (person email / prs_ uid, or fleet-agent agt_ uid). Read-only.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# core/scripts/hq-delegate-resolve.sh --company <slug> --to <token>
|
|
8
|
+
#
|
|
9
|
+
# Output (stdout, JSON): {kind, principal, displayName, source}
|
|
10
|
+
# kind — "person" | "agent"
|
|
11
|
+
# principal — confirmed email, prs_… or agt_… uid
|
|
12
|
+
# source — "verbatim" | "people-resolve" | "agents-list"
|
|
13
|
+
#
|
|
14
|
+
# Exit codes:
|
|
15
|
+
# 0 — resolved
|
|
16
|
+
# 1 — usage error (missing/invalid arguments)
|
|
17
|
+
# 3 — ambiguous: candidate list printed to stdout as JSON
|
|
18
|
+
# {status:"ambiguous", matches:[…]} — caller must present a picker
|
|
19
|
+
# 4 — not found (or found with no email): plain message on stderr
|
|
20
|
+
#
|
|
21
|
+
# Tenancy: every lookup is scoped with an explicit --company. This helper
|
|
22
|
+
# never enumerates or falls back across companies — single-company by
|
|
23
|
+
# construction, matching the /dm skill's resolution contract.
|
|
24
|
+
#
|
|
25
|
+
# Fast path: a token containing "@" or starting with prs_ / agt_ passes
|
|
26
|
+
# through verbatim with NO lookup, preserving existing invocations exactly.
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
sed -n '3,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
die_usage() {
|
|
35
|
+
echo "hq-delegate-resolve: $*" >&2
|
|
36
|
+
usage >&2
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
command -v jq >/dev/null 2>&1 || { echo "hq-delegate-resolve: jq is required" >&2; exit 1; }
|
|
41
|
+
|
|
42
|
+
COMPANY="" TOKEN=""
|
|
43
|
+
while [ $# -gt 0 ]; do
|
|
44
|
+
case "$1" in
|
|
45
|
+
--company) COMPANY="${2:-}"; shift 2 ;;
|
|
46
|
+
--to) TOKEN="${2:-}"; shift 2 ;;
|
|
47
|
+
-h|--help) usage; exit 0 ;;
|
|
48
|
+
*) die_usage "unknown argument: $1" ;;
|
|
49
|
+
esac
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
[ -n "$COMPANY" ] || die_usage "--company is required (single-company lookups only; never a multi-company scan)"
|
|
53
|
+
[ -n "$TOKEN" ] || die_usage "--to is required"
|
|
54
|
+
|
|
55
|
+
emit() { # kind principal displayName source
|
|
56
|
+
jq -cn --arg kind "$1" --arg principal "$2" --arg name "$3" --arg source "$4" \
|
|
57
|
+
'{kind: $kind, principal: $principal,
|
|
58
|
+
displayName: (if $name == "" then null else $name end), source: $source}'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Strip CLI noise (version warnings, session-refresh notices) before the JSON
|
|
62
|
+
# payload: keep everything from the first line that starts a JSON value.
|
|
63
|
+
json_only() {
|
|
64
|
+
awk 'started {print; next} /^[[:space:]]*[{[]/ {started=1; print}'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# --- fast path: already a principal — pass through verbatim, no lookup -------
|
|
68
|
+
|
|
69
|
+
case "$TOKEN" in
|
|
70
|
+
agt_*)
|
|
71
|
+
emit agent "$TOKEN" "" verbatim
|
|
72
|
+
exit 0
|
|
73
|
+
;;
|
|
74
|
+
prs_*|*@*)
|
|
75
|
+
emit person "$TOKEN" "" verbatim
|
|
76
|
+
exit 0
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
|
|
80
|
+
# --- name → people roster (single-company, read-only) ------------------------
|
|
81
|
+
|
|
82
|
+
PEOPLE_RAW="$(hq people resolve "$TOKEN" --json --company "$COMPANY" 2>/dev/null | json_only || true)"
|
|
83
|
+
PEOPLE_STATUS=""
|
|
84
|
+
if [ -n "$PEOPLE_RAW" ] && printf '%s' "$PEOPLE_RAW" | jq -e . >/dev/null 2>&1; then
|
|
85
|
+
PEOPLE_STATUS="$(printf '%s' "$PEOPLE_RAW" | jq -r '.status // empty')"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
case "$PEOPLE_STATUS" in
|
|
89
|
+
found)
|
|
90
|
+
EMAIL="$(printf '%s' "$PEOPLE_RAW" | jq -r '.email // empty')"
|
|
91
|
+
NAME="$(printf '%s' "$PEOPLE_RAW" | jq -r '.name // .displayName // empty')"
|
|
92
|
+
[ -n "$EMAIL" ] || { echo "hq-delegate-resolve: resolver returned found but no email for '$TOKEN'" >&2; exit 4; }
|
|
93
|
+
emit person "$EMAIL" "$NAME" people-resolve
|
|
94
|
+
exit 0
|
|
95
|
+
;;
|
|
96
|
+
ambiguous)
|
|
97
|
+
printf '%s' "$PEOPLE_RAW" | jq -c '{status: "ambiguous", matches: (.matches // [])}'
|
|
98
|
+
exit 3
|
|
99
|
+
;;
|
|
100
|
+
no_email)
|
|
101
|
+
# A person entry without an email is often a fleet agent's roster row —
|
|
102
|
+
# fall through to the agent roster before treating this as terminal.
|
|
103
|
+
PERSON_NO_EMAIL=1
|
|
104
|
+
;;
|
|
105
|
+
not_found|"")
|
|
106
|
+
: # fall through to the fleet-agent roster
|
|
107
|
+
;;
|
|
108
|
+
*)
|
|
109
|
+
echo "hq-delegate-resolve: unexpected resolver status '$PEOPLE_STATUS' for '$TOKEN'" >&2
|
|
110
|
+
exit 4
|
|
111
|
+
;;
|
|
112
|
+
esac
|
|
113
|
+
|
|
114
|
+
# --- name → fleet-agent roster (same company, read-only) ---------------------
|
|
115
|
+
|
|
116
|
+
AGENTS_RAW="$(hq agents list --company "$COMPANY" --json 2>/dev/null | json_only || true)"
|
|
117
|
+
if [ -z "$AGENTS_RAW" ] || ! printf '%s' "$AGENTS_RAW" | jq -e . >/dev/null 2>&1; then
|
|
118
|
+
AGENTS_RAW="[]"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
MATCHES="$(printf '%s' "$AGENTS_RAW" | jq -c --arg t "$TOKEN" '
|
|
122
|
+
[ .[]
|
|
123
|
+
| select((.name // .displayName // "") | ascii_downcase == ($t | ascii_downcase))
|
|
124
|
+
| {agentUid: (.agentUid // .uid // empty), name: (.name // .displayName // "")}
|
|
125
|
+
| select(.agentUid != "")
|
|
126
|
+
]')"
|
|
127
|
+
MATCH_COUNT="$(printf '%s' "$MATCHES" | jq 'length')"
|
|
128
|
+
|
|
129
|
+
if [ "$MATCH_COUNT" -eq 1 ]; then
|
|
130
|
+
emit agent \
|
|
131
|
+
"$(printf '%s' "$MATCHES" | jq -r '.[0].agentUid')" \
|
|
132
|
+
"$(printf '%s' "$MATCHES" | jq -r '.[0].name')" \
|
|
133
|
+
agents-list
|
|
134
|
+
exit 0
|
|
135
|
+
elif [ "$MATCH_COUNT" -gt 1 ]; then
|
|
136
|
+
printf '%s' "$MATCHES" | jq -c '{status: "ambiguous", matches: .}'
|
|
137
|
+
exit 3
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
if [ "${PERSON_NO_EMAIL:-0}" -eq 1 ]; then
|
|
141
|
+
echo "hq-delegate-resolve: '$TOKEN' was found in company '$COMPANY' but has no email on record and matches no fleet agent — pass their exact email, personUid (prs_…), or agentUid (agt_…) instead" >&2
|
|
142
|
+
else
|
|
143
|
+
echo "hq-delegate-resolve: no teammate or fleet agent named '$TOKEN' found in company '$COMPANY' — pass the exact email, personUid (prs_…), or agentUid (agt_…) instead" >&2
|
|
144
|
+
fi
|
|
145
|
+
exit 4
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# hq-delegate-secrets.sh — grant a delegation recipient access to the secrets
|
|
4
|
+
# a project needs, BY NAME ONLY. No command in this helper ever reads,
|
|
5
|
+
# prints, logs, or interpolates a secret value.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# core/scripts/hq-delegate-secrets.sh --manifest <path> [--yes] [--no-secrets]
|
|
9
|
+
# [--env-schema <path>]
|
|
10
|
+
#
|
|
11
|
+
# Behavior:
|
|
12
|
+
# - derives required secret NAMES from the project's / repo's .env.schema
|
|
13
|
+
# (--env-schema overrides discovery); no schema -> records secrets: []
|
|
14
|
+
# and says so plainly rather than guessing at credentials
|
|
15
|
+
# - without --yes: prints a full-prose confirmation listing every name and
|
|
16
|
+
# the principal, then exits 2 granting nothing — this step is a privilege
|
|
17
|
+
# handover and gets its own confirmation, separate from the file grants
|
|
18
|
+
# - with --yes: hq secrets share <NAME> --with <principal> --permission read
|
|
19
|
+
# per name, then records the names (and only the names) in the manifest
|
|
20
|
+
# - --no-secrets: skips everything, records {secrets: [], secretsSkipped: true}
|
|
21
|
+
#
|
|
22
|
+
# Consumption on the recipient's side is via `hq run` / `hq secrets exec` —
|
|
23
|
+
# documented in the brief; values never appear in any delegation artifact
|
|
24
|
+
# (policy hq-delegate-never-inlines-secrets-or-share-urls).
|
|
25
|
+
#
|
|
26
|
+
# Env: HQ_ROOT — HQ root override (default: resolved from script location)
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
31
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
32
|
+
|
|
33
|
+
usage() { sed -n '3,24p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
|
|
34
|
+
die() { echo "hq-delegate-secrets: $*" >&2; exit 1; }
|
|
35
|
+
|
|
36
|
+
command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
|
|
37
|
+
|
|
38
|
+
MANIFEST="" YES=0 NO_SECRETS=0 ENV_SCHEMA=""
|
|
39
|
+
while [ $# -gt 0 ]; do
|
|
40
|
+
case "$1" in
|
|
41
|
+
--manifest) MANIFEST="${2:-}"; shift 2 ;;
|
|
42
|
+
--yes) YES=1; shift ;;
|
|
43
|
+
--no-secrets) NO_SECRETS=1; shift ;;
|
|
44
|
+
--env-schema) ENV_SCHEMA="${2:-}"; shift 2 ;;
|
|
45
|
+
-h|--help) usage; exit 0 ;;
|
|
46
|
+
*) die "unknown argument: $1" ;;
|
|
47
|
+
esac
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
[ -n "$MANIFEST" ] || die "--manifest is required"
|
|
51
|
+
[ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST"
|
|
52
|
+
jq -e . "$MANIFEST" >/dev/null 2>&1 || die "manifest is not valid JSON: $MANIFEST"
|
|
53
|
+
|
|
54
|
+
COMPANY="$(jq -r '.company // empty' "$MANIFEST")"
|
|
55
|
+
PRINCIPAL="$(jq -r '.to.principal // empty' "$MANIFEST")"
|
|
56
|
+
PROJECT="$(jq -r '.project.name // empty' "$MANIFEST")"
|
|
57
|
+
[ -n "$COMPANY" ] || die "manifest has no company"
|
|
58
|
+
[ -n "$PRINCIPAL" ] || die "manifest has no to.principal"
|
|
59
|
+
|
|
60
|
+
update_manifest() { # jq-filter
|
|
61
|
+
local tmp
|
|
62
|
+
tmp="$(mktemp)"
|
|
63
|
+
jq "$1" "$MANIFEST" > "$tmp" && mv "$tmp" "$MANIFEST"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# --- explicit skip ------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
if [ "$NO_SECRETS" -eq 1 ]; then
|
|
69
|
+
update_manifest '.secrets = [] | .secretsSkipped = true'
|
|
70
|
+
echo "hq-delegate-secrets: skipped (--no-secrets) — no credential access granted"
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# --- discover the env schema --------------------------------------------------
|
|
75
|
+
|
|
76
|
+
if [ -z "$ENV_SCHEMA" ]; then
|
|
77
|
+
REPO_REL="$(jq -r '.repo.path // empty' "$MANIFEST")"
|
|
78
|
+
for candidate in \
|
|
79
|
+
"$HQ_ROOT/companies/$COMPANY/projects/$PROJECT/.env.schema" \
|
|
80
|
+
"${REPO_REL:+$HQ_ROOT/$REPO_REL/.env.schema}"; do
|
|
81
|
+
[ -n "$candidate" ] && [ -f "$candidate" ] && { ENV_SCHEMA="$candidate"; break; }
|
|
82
|
+
done
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if [ -z "$ENV_SCHEMA" ] || [ ! -f "$ENV_SCHEMA" ]; then
|
|
86
|
+
update_manifest '.secrets = [] | .secretsSkipped = false'
|
|
87
|
+
echo "hq-delegate-secrets: no .env.schema found for this project or its repo — no secret names to grant. If the project needs credentials, add an .env.schema and re-run, or grant them manually with 'hq secrets share'."
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# --- parse names (KEY= lines; comments and blanks ignored) --------------------
|
|
92
|
+
|
|
93
|
+
NAMES="$(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$ENV_SCHEMA" | cut -d= -f1 | sort -u)"
|
|
94
|
+
if [ -z "$NAMES" ]; then
|
|
95
|
+
update_manifest '.secrets = [] | .secretsSkipped = false'
|
|
96
|
+
echo "hq-delegate-secrets: $ENV_SCHEMA declares no secret names — nothing to grant"
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
NAME_COUNT="$(printf '%s\n' "$NAMES" | wc -l | tr -d ' ')"
|
|
100
|
+
|
|
101
|
+
# --- confirmation gate (full prose; separate from the file-grant confirm) ----
|
|
102
|
+
|
|
103
|
+
echo "Secret access handover — this grants the recipient the ability to use these credentials."
|
|
104
|
+
echo
|
|
105
|
+
echo "The following $NAME_COUNT secret name(s), declared by $(basename "$ENV_SCHEMA") for project '$PROJECT', will be shared with read permission to '$PRINCIPAL' in company '$COMPANY':"
|
|
106
|
+
echo
|
|
107
|
+
printf '%s\n' "$NAMES" | sed 's/^/ - /'
|
|
108
|
+
echo
|
|
109
|
+
echo "Only the names are granted and recorded; no secret value is read, printed, or transmitted by this step. The recipient consumes them through 'hq run' / 'hq secrets exec'."
|
|
110
|
+
|
|
111
|
+
if [ "$YES" -ne 1 ]; then
|
|
112
|
+
echo
|
|
113
|
+
echo "hq-delegate-secrets: confirmation required — re-run with --yes after the user approves (or --no-secrets to skip)" >&2
|
|
114
|
+
exit 2
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# --- grant each name (names only — never a value read) ------------------------
|
|
118
|
+
|
|
119
|
+
printf '%s\n' "$NAMES" | while IFS= read -r name; do
|
|
120
|
+
hq secrets share "$name" --with "$PRINCIPAL" --permission read --company "$COMPANY" \
|
|
121
|
+
|| die "failed to grant '$name' to '$PRINCIPAL' — aborting; manifest not updated"
|
|
122
|
+
echo "hq-delegate-secrets: granted read on '$name' to '$PRINCIPAL'"
|
|
123
|
+
done
|
|
124
|
+
|
|
125
|
+
NAMES_JSON="$(printf '%s\n' "$NAMES" | jq -R . | jq -cs .)"
|
|
126
|
+
NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
127
|
+
update_manifest ".secrets = $NAMES_JSON | .secretsSkipped = false | .secretsGrantedAt = \"$NOW\""
|
|
128
|
+
|
|
129
|
+
echo "hq-delegate-secrets: $NAME_COUNT secret name(s) granted and recorded (names only)"
|