@indigoai-us/hq-cli 5.300.0 → 5.302.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 +42 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
- package/assets/scaffold/core/scripts/token-usage-report.sh +9 -0
- package/dist/command-catalog.generated.d.ts +32 -3
- package/dist/command-catalog.generated.js +39 -3
- package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
- package/dist/commands/__fixtures__/access-vault.js +1 -1
- package/dist/commands/access.d.ts +2 -1
- package/dist/commands/access.js +58 -49
- package/dist/commands/bot.js +1 -0
- package/dist/commands/cloud-provision.js +51 -2
- package/dist/commands/core-checkpoint.js +1 -0
- package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
- package/dist/commands/core-company-index-scope-flag.js +6 -0
- package/dist/commands/core-handoff-fast.js +1 -0
- package/dist/commands/core-handoff.js +2 -0
- package/dist/commands/core-job-native-utils.d.ts +6 -0
- package/dist/commands/core-job-native-utils.js +80 -0
- package/dist/commands/core-job-native.d.ts +2 -4
- package/dist/commands/core-job-native.js +6 -10
- package/dist/commands/core-job-notify-native.d.ts +36 -0
- package/dist/commands/core-job-notify-native.js +359 -0
- package/dist/commands/core-job-remediate-native.d.ts +26 -0
- package/dist/commands/core-job-remediate-native.js +374 -0
- package/dist/commands/core.js +26 -8
- package/dist/commands/deploy-comments.js +5 -2
- package/dist/commands/files.js +31 -14
- package/dist/commands/integrations-api.d.ts +6 -6
- package/dist/commands/integrations-api.js +4 -13
- package/dist/commands/integrations-connect.d.ts +3 -0
- package/dist/commands/integrations-connect.js +21 -2
- package/dist/commands/integrations-manage.js +1 -4
- package/dist/commands/lanes-message.js +43 -5
- package/dist/commands/lanes-questions.d.ts +4 -2
- package/dist/commands/lanes-questions.js +123 -2
- package/dist/commands/lanes-resume.js +25 -4
- package/dist/commands/lanes-shared.d.ts +15 -1
- package/dist/commands/lanes-watch.js +5 -1
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +92 -9
- package/dist/commands/meetings.js +35 -16
- package/dist/commands/monitor.js +17 -2
- package/dist/commands/report.js +5 -2
- package/dist/commands/rescue.d.ts +4 -0
- package/dist/commands/rescue.js +27 -17
- package/dist/commands/safe-extract.d.ts +4 -4
- package/dist/commands/safe-extract.js +6 -7
- package/dist/lib/agent-kit/fallback.d.ts +1 -0
- package/dist/lib/agent-kit/fallback.js +1 -0
- package/dist/lib/bot/daemon.d.ts +5 -1
- package/dist/lib/bot/daemon.js +4 -4
- package/dist/lib/core-utils/soft-timeout.js +1 -1
- package/dist/lib/core-utils/token-usage-report.js +17 -3
- package/dist/lib/deploy-apps.d.ts +13 -0
- package/dist/lib/deploy-apps.js +24 -0
- package/dist/lib/index-render/companies.d.ts +4 -1
- package/dist/lib/index-render/companies.js +66 -3
- package/dist/lib/index-render/index.d.ts +1 -0
- package/dist/lib/index-render/index.js +2 -1
- package/dist/lib/index-render/projects.js +82 -5
- package/dist/lib/index-render/shared.d.ts +1 -1
- package/dist/lib/index-render/shared.js +4 -2
- package/dist/lib/lanes/delivery.d.ts +7 -3
- package/dist/lib/lanes/delivery.js +18 -8
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +13 -3
- package/dist/lib/lanes/keep-alive.js +29 -5
- package/dist/lib/lanes/outpost-dispatch.d.ts +5 -0
- package/dist/lib/lanes/outpost-dispatch.js +12 -0
- package/dist/lib/lanes/reconcile.d.ts +1 -0
- package/dist/lib/lanes/reconcile.js +358 -38
- package/dist/lib/lanes/spawn.d.ts +1 -0
- package/dist/lib/lanes/spawn.js +20 -10
- package/dist/lib/lanes/store.js +24 -0
- package/dist/lib/lanes/story-ledger.d.ts +2 -0
- package/dist/lib/lanes/story-ledger.js +8 -0
- package/dist/lib/lanes/types.d.ts +26 -4
- package/dist/lib/lanes/types.js +33 -1
- package/dist/lib/lanes/watch.d.ts +1 -0
- package/dist/lib/lanes/watch.js +19 -6
- package/dist/lib/lanes/watcher.js +139 -9
- package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +2 -2
- package/dist/lib/mesh/live/daemon/run.js +36 -2
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +2 -2
- package/dist/lib/search-index/background.d.ts +1 -0
- package/dist/lib/search-index/background.js +1 -0
- package/dist/utils/self-update.js +51 -4
- package/dist/utils/staged-npm-install.d.ts +16 -0
- package/dist/utils/staged-npm-install.js +264 -0
- package/dist/utils/update-command-supervisor.cjs +54 -1
- package/dist/utils/version-gate.d.ts +8 -0
- package/dist/utils/version-gate.js +83 -16
- package/package.json +2 -2
- package/dist/lib/install-path-compatibility-flag.d.ts +0 -9
- package/dist/lib/install-path-compatibility-flag.js +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.302.0] — 2026-10-01
|
|
6
|
+
|
|
7
|
+
- Generate company project indexes while preserving hand-written content unless `--accept` is used.
|
|
8
|
+
|
|
9
|
+
- Follow all hq-deploy app-list pages when resolving apps for comments and report publishing.
|
|
10
|
+
- Let a senior message or resume an `awaiting_input` lane when its pending question is gone or cannot be reached from the senior's host. Pending questions remain answer-first, including across registered Outpost routes.
|
|
11
|
+
- A senior can resume or send a fallback message to an `awaiting_input` lane when its pending questions are addressed to another audience. Once the resume is accepted, those questions are cancelled on their owner; unreachable owners are reported while the lane continues.
|
|
12
|
+
- When a blocker escalation targets a session without a registered monitor inbox, record the reason and deliver it through the lane's existing inbox instead of retrying the failed escalation every watcher pass.
|
|
13
|
+
- `hq integrations disconnect` can now remove company integrations that were created outside the app catalog.
|
|
14
|
+
- Lead envelopes can set `next_check_at` to defer automatic keep-alive resumes
|
|
15
|
+
until a scheduled check, with pending work continuing to resume immediately.
|
|
16
|
+
- Reconcile records deduplicated keep-alive skip reasons in each lane's watcher event log.
|
|
17
|
+
- Add a company-scoped reconcile gate that closes local interrupted worker lanes after 24 hours when all carried project stories are done or dropped and no pending work remains.
|
|
18
|
+
- Skip refreshing failed-dispatch lane mirrors that never reached their Outpost owner.
|
|
19
|
+
- Keep lane keep-alive, retry-context, and monitor history readers moving past torn JSONL records.
|
|
20
|
+
- Refresh primary mirror heartbeats from fresh running Outpost reports before dead-host reconciliation.
|
|
21
|
+
- Report answerable awaiting-input questions as the `hq lanes show` delivery path, and retry blocker-freshness fallback messages after a failed append.
|
|
22
|
+
- Let `hq integrations connect` send an API key in a custom HTTP header.
|
|
23
|
+
- Bound `hq rescue` GitHub release and tag lookups to 30 seconds with a clear timeout error.
|
|
24
|
+
- hq-cli now logs live-board refresh failures and recovery while continuing to retry.
|
|
25
|
+
- On Windows, background jobs started by `/handoff` and search indexing no longer open console windows.
|
|
26
|
+
|
|
27
|
+
## [5.301.0] — 2026-10-01
|
|
28
|
+
- `hq meetings transcript` and `hq meetings notes` stop a stalled document download after two minutes and say so, instead of waiting forever.
|
|
29
|
+
|
|
30
|
+
- Keep `hq access` from reporting a denied vault path as missing or exposing matching paths.
|
|
31
|
+
- Fix self-updates for npm installs whose global prefix contains spaces.
|
|
32
|
+
- `hq files acl companies/<slug>` and `hq files unshare companies/<slug> --with ...` now handle ACL rows at that exact company path instead of treating it as the vault root.
|
|
33
|
+
- Add native hq core job notify and remediate commands behind core-native-jobs, retaining bundled shell companions.
|
|
34
|
+
- Allow owners to disconnect custom Factory connections without installation records, and send the company UID required by the revoked-connection purge endpoint.
|
|
35
|
+
- Fail company provisioning when its initial sync reports per-file upload failures, while retaining the successful upload count in failure details.
|
|
36
|
+
- Treat plan-limited files as initial-sync failures and keep provisioning progress snapshots updated.
|
|
37
|
+
- Preserve story attribution after a message is durably queued by lane admission or an account move.
|
|
38
|
+
- The token usage report accepts `--project-dir <path>` to read one Claude activity folder instead of the default folders.
|
|
39
|
+
|
|
5
40
|
## [5.300.0] — 2026-09-30
|
|
41
|
+
- `hq files unshare --with agt_...` removes a grant made to an agent. Before, agent uids were rejected as invalid principals.
|
|
6
42
|
- With the default-off hq-flags gate `files.share-empty-prefix-warning`, `hq files share` warns when a granted prefix has no files. A failed or timed-out list check does not block the grant.
|
|
43
|
+
- Recognize pnpm v11+ global installs and handle Windows drive-path tar archives without waiting for the asynchronous feature-flag snapshot.
|
|
44
|
+
- Self-update failure reports now include the npm error code and a sanitized reason.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- `hq core rebuild-index companies --company <slug>` scopes the generated write to that company (behind the default-off `cli.rebuild-index-company-scope` flag), and `--help` exits without reading or writing company indexes. The unscoped run also ignores orchestrator state tied to another company's PRD.
|
|
7
49
|
|
|
8
50
|
- Lane messages and resumes that switch project or story attribution now tell the provider which story is active. Reused completion artifacts no longer close the new story.
|
|
9
51
|
- Make signed-out `hq sync` commands fail fast with `hq login` guidance when stdin or stdout is not a terminal, while keeping browser login in a terminal.
|
|
@@ -11,12 +11,11 @@ set -euo pipefail
|
|
|
11
11
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
12
12
|
cd "$HQ_ROOT"
|
|
13
13
|
|
|
14
|
+
COMPANIES_DIR="companies"
|
|
14
15
|
PROJECTS_DIR="personal/projects"
|
|
15
16
|
OUT="${PROJECTS_DIR}/INDEX.md"
|
|
16
17
|
DATE=$(date -u +%Y-%m-%d)
|
|
17
18
|
|
|
18
|
-
[[ -d "$PROJECTS_DIR" ]] || { echo "rebuild-projects-index: ${PROJECTS_DIR}/ missing, skipping" >&2; exit 0; }
|
|
19
|
-
|
|
20
19
|
sanitize_cell() {
|
|
21
20
|
printf '%s' "$1" | tr '\n' ' ' | sed -e 's/|/\\|/g' -e 's/ */ /g' -e 's/^ *//' -e 's/ *$//'
|
|
22
21
|
}
|
|
@@ -29,17 +28,23 @@ trunc() {
|
|
|
29
28
|
project_status() {
|
|
30
29
|
local project="$1"
|
|
31
30
|
local prd="$2"
|
|
31
|
+
local fallback="${3:-—}"
|
|
32
|
+
local expected_prd="${4:-}"
|
|
32
33
|
local state="workspace/orchestrator/${project}/state.json"
|
|
33
34
|
if [[ -f "$state" ]]; then
|
|
34
|
-
local
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
local state_prd
|
|
36
|
+
state_prd=$(jq -r '.prd_path // ""' "$state" 2>/dev/null || echo "")
|
|
37
|
+
if [[ -z "$expected_prd" || "$state_prd" == "$expected_prd" ]]; then
|
|
38
|
+
local raw
|
|
39
|
+
raw=$(jq -r '.status // "active"' "$state" 2>/dev/null || echo "active")
|
|
40
|
+
case "$raw" in
|
|
41
|
+
completed|complete) echo "complete" ;;
|
|
42
|
+
in_progress|active|started) echo "active" ;;
|
|
43
|
+
archived) echo "archived" ;;
|
|
44
|
+
*) echo "$raw" ;;
|
|
45
|
+
esac
|
|
46
|
+
return
|
|
47
|
+
fi
|
|
43
48
|
fi
|
|
44
49
|
if [[ -f "$prd" ]]; then
|
|
45
50
|
local total passes
|
|
@@ -50,9 +55,91 @@ project_status() {
|
|
|
50
55
|
return
|
|
51
56
|
fi
|
|
52
57
|
fi
|
|
53
|
-
echo "
|
|
58
|
+
echo "$fallback"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
sha256_file() {
|
|
62
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
63
|
+
sha256sum "$1" | awk '{print $1}'
|
|
64
|
+
else
|
|
65
|
+
shasum -a 256 "$1" | awk '{print $1}'
|
|
66
|
+
fi
|
|
54
67
|
}
|
|
55
68
|
|
|
69
|
+
stamp_generated() {
|
|
70
|
+
local input="$1" output="$2" normalized hash
|
|
71
|
+
normalized=$(mktemp)
|
|
72
|
+
sed 's/^> Auto-generated. Updated: [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/> Auto-generated. Updated: <date>/' "$input" > "$normalized"
|
|
73
|
+
hash=$(sha256_file "$normalized")
|
|
74
|
+
rm -f "$normalized"
|
|
75
|
+
awk -v h="$hash" '{print} /^> Auto-generated. Updated: / {print "<!-- hq-generated-sha256: " h " -->"}' "$input" > "$output"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
company_title() {
|
|
79
|
+
printf '%s\n' "$1" | awk -F'-' '{ for (i=1; i<=NF; i++) { $i = toupper(substr($i,1,1)) substr($i,2) } print }' OFS=' '
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
write_company_projects_index() {
|
|
83
|
+
local company="$1" company_root project_dir output tmp name prd description status
|
|
84
|
+
company_root="${COMPANIES_DIR}/${company}"
|
|
85
|
+
project_dir="${company_root}/projects"
|
|
86
|
+
output="${project_dir}/INDEX.md"
|
|
87
|
+
tmp=$(mktemp)
|
|
88
|
+
{
|
|
89
|
+
printf '# %s Projects\n\n' "$(company_title "$company")"
|
|
90
|
+
printf '> Auto-generated. Updated: %s\n\n' "$DATE"
|
|
91
|
+
echo '| Project | Status | Description |'
|
|
92
|
+
echo '|---------|--------|-------------|'
|
|
93
|
+
while IFS= read -r project; do
|
|
94
|
+
name=$(basename "$project")
|
|
95
|
+
[[ "$name" == _* ]] && continue
|
|
96
|
+
[[ "$name" == .* ]] && continue
|
|
97
|
+
prd="${project}/prd.json"
|
|
98
|
+
description=""
|
|
99
|
+
if [[ -f "$prd" ]]; then description=$(jq -r '.description // ""' "$prd" 2>/dev/null || echo ""); fi
|
|
100
|
+
status=$(project_status "$name" "$prd" "active" "$prd")
|
|
101
|
+
description=$(sanitize_cell "$description")
|
|
102
|
+
description=$(trunc "$description" 110)
|
|
103
|
+
[[ -z "$description" ]] && description="—"
|
|
104
|
+
printf '| `%s` | %s | %s |\n' "$name" "$status" "$description"
|
|
105
|
+
done < <(find "$project_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
|
|
106
|
+
} > "$tmp"
|
|
107
|
+
local existing=0 stored actual normalized
|
|
108
|
+
[[ -e "$output" ]] && existing=1
|
|
109
|
+
if [[ "$existing" == 1 ]]; then
|
|
110
|
+
if grep -q '<!-- hq:keep -->' "$output" || ! grep -q '^> Auto-generated. Updated: ' "$output"; then
|
|
111
|
+
echo "rebuild-projects-index: refused to overwrite ${output} (hand-written or kept)" >&2
|
|
112
|
+
rm -f "$tmp"
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
stored=$(sed -n 's/^<!-- hq-generated-sha256: \([a-f0-9]*\) -->$/\1/p' "$output" | head -n 1)
|
|
116
|
+
if [[ -n "$stored" ]]; then
|
|
117
|
+
normalized=$(mktemp)
|
|
118
|
+
sed -e '/^<!-- hq-generated-sha256: [a-f0-9]* -->$/d' -e 's/^> Auto-generated. Updated: [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/> Auto-generated. Updated: <date>/' "$output" > "$normalized"
|
|
119
|
+
actual=$(sha256_file "$normalized")
|
|
120
|
+
rm -f "$normalized"
|
|
121
|
+
if [[ "$stored" != "$actual" ]]; then
|
|
122
|
+
echo "rebuild-projects-index: refused to overwrite ${output} (generated content was edited)" >&2
|
|
123
|
+
rm -f "$tmp"
|
|
124
|
+
return 0
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
stamp_generated "$tmp" "$output"
|
|
129
|
+
rm -f "$tmp"
|
|
130
|
+
echo "rebuild-projects-index: wrote ${output}" >&2
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
while IFS= read -r dir; do
|
|
134
|
+
co=$(basename "$dir")
|
|
135
|
+
[[ "$co" == _* ]] && continue
|
|
136
|
+
[[ "$co" == .* ]] && continue
|
|
137
|
+
project_dir="${COMPANIES_DIR}/${co}/projects"
|
|
138
|
+
[[ -d "$project_dir" ]] && write_company_projects_index "$co"
|
|
139
|
+
done < <(find "$COMPANIES_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
|
|
140
|
+
|
|
141
|
+
[[ -d "$PROJECTS_DIR" ]] || { echo "rebuild-projects-index: ${PROJECTS_DIR}/ missing, skipping" >&2; exit 0; }
|
|
142
|
+
|
|
56
143
|
{
|
|
57
144
|
echo "# Projects"
|
|
58
145
|
echo ""
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# scripts/token-usage-report.sh --last 14 # last 14 days
|
|
6
6
|
# scripts/token-usage-report.sh --since 2026-05-01
|
|
7
7
|
# scripts/token-usage-report.sh --json # machine-readable
|
|
8
|
+
# scripts/token-usage-report.sh --project-dir <path> # read one activity folder
|
|
8
9
|
#
|
|
9
10
|
# Effective tokens weighting (matches weekly rate-limit volume):
|
|
10
11
|
# input + 5×output + 1.25×cache_creation + 0.1×cache_read
|
|
@@ -24,6 +25,14 @@ while [ $# -gt 0 ]; do
|
|
|
24
25
|
--last) LAST_DAYS="$2"; shift 2 ;;
|
|
25
26
|
--since) SINCE="$2"; shift 2 ;;
|
|
26
27
|
--json) JSON_MODE=1; shift ;;
|
|
28
|
+
--project-dir)
|
|
29
|
+
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
|
30
|
+
echo "token-usage-report: --project-dir requires a path" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
PROJECT_DIR="$2"
|
|
34
|
+
shift 2
|
|
35
|
+
;;
|
|
27
36
|
--compare-windows) shift ;; # flag, presence triggers compare mode via --before/--after
|
|
28
37
|
--before) COMPARE_BEFORE="$2"; shift 2 ;;
|
|
29
38
|
--after) COMPARE_AFTER="$2"; shift 2 ;;
|
|
@@ -1713,7 +1713,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
1713
1713
|
}];
|
|
1714
1714
|
readonly options: readonly [{
|
|
1715
1715
|
readonly flags: "--with <principal>";
|
|
1716
|
-
readonly description: "Person uid, email address, group id, or '@all' to remove the grant";
|
|
1716
|
+
readonly description: "Person uid, agent uid, email address, group id, or '@all' to remove the grant";
|
|
1717
1717
|
}];
|
|
1718
1718
|
readonly subcommands: readonly [];
|
|
1719
1719
|
}, {
|
|
@@ -3122,6 +3122,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3122
3122
|
}, {
|
|
3123
3123
|
readonly flags: "--token-stdin";
|
|
3124
3124
|
readonly description: "Read the API key from stdin";
|
|
3125
|
+
}, {
|
|
3126
|
+
readonly flags: "--auth-header <name>";
|
|
3127
|
+
readonly description: "Send the API key in this HTTP header (implies --auth key)";
|
|
3125
3128
|
}, {
|
|
3126
3129
|
readonly flags: "--auth <mode>";
|
|
3127
3130
|
readonly description: "Force the auth mode: none, key, or oauth (default: detect)";
|
|
@@ -5620,13 +5623,26 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5620
5623
|
}];
|
|
5621
5624
|
}, {
|
|
5622
5625
|
readonly name: "job";
|
|
5623
|
-
readonly description: "Run or
|
|
5626
|
+
readonly description: "Run, inspect, notify, or remediate one scheduled job";
|
|
5624
5627
|
readonly aliases: readonly [];
|
|
5625
5628
|
readonly hidden: false;
|
|
5626
5629
|
readonly usage: "[options] [command]";
|
|
5627
5630
|
readonly arguments: readonly [];
|
|
5628
5631
|
readonly options: readonly [];
|
|
5629
5632
|
readonly subcommands: readonly [{
|
|
5633
|
+
readonly name: "notify";
|
|
5634
|
+
readonly description: "Send a scheduled-job status alert to its owner";
|
|
5635
|
+
readonly aliases: readonly [];
|
|
5636
|
+
readonly hidden: false;
|
|
5637
|
+
readonly usage: "[args...]";
|
|
5638
|
+
readonly arguments: readonly [{
|
|
5639
|
+
readonly name: "args";
|
|
5640
|
+
readonly required: false;
|
|
5641
|
+
readonly variadic: true;
|
|
5642
|
+
}];
|
|
5643
|
+
readonly options: readonly [];
|
|
5644
|
+
readonly subcommands: readonly [];
|
|
5645
|
+
}, {
|
|
5630
5646
|
readonly name: "probe";
|
|
5631
5647
|
readonly description: "Check scheduled-job requirements and report readiness";
|
|
5632
5648
|
readonly aliases: readonly [];
|
|
@@ -5639,6 +5655,19 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5639
5655
|
}];
|
|
5640
5656
|
readonly options: readonly [];
|
|
5641
5657
|
readonly subcommands: readonly [];
|
|
5658
|
+
}, {
|
|
5659
|
+
readonly name: "remediate";
|
|
5660
|
+
readonly description: "Run bounded remediation for a scheduled-job failure";
|
|
5661
|
+
readonly aliases: readonly [];
|
|
5662
|
+
readonly hidden: false;
|
|
5663
|
+
readonly usage: "[args...]";
|
|
5664
|
+
readonly arguments: readonly [{
|
|
5665
|
+
readonly name: "args";
|
|
5666
|
+
readonly required: false;
|
|
5667
|
+
readonly variadic: true;
|
|
5668
|
+
}];
|
|
5669
|
+
readonly options: readonly [];
|
|
5670
|
+
readonly subcommands: readonly [];
|
|
5642
5671
|
}, {
|
|
5643
5672
|
readonly name: "run";
|
|
5644
5673
|
readonly description: "Run a scheduled job";
|
|
@@ -8582,7 +8611,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8582
8611
|
readonly subcommands: readonly [];
|
|
8583
8612
|
}, {
|
|
8584
8613
|
readonly name: "resume";
|
|
8585
|
-
readonly description: "Resume an interrupted, done or blocked lane
|
|
8614
|
+
readonly description: "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with the senior-only --from-failed option";
|
|
8586
8615
|
readonly aliases: readonly [];
|
|
8587
8616
|
readonly hidden: false;
|
|
8588
8617
|
readonly usage: "[options] <lane-id>";
|
|
@@ -2185,7 +2185,7 @@ export const COMMAND_CATALOG = [
|
|
|
2185
2185
|
"options": [
|
|
2186
2186
|
{
|
|
2187
2187
|
"flags": "--with <principal>",
|
|
2188
|
-
"description": "Person uid, email address, group id, or '@all' to remove the grant"
|
|
2188
|
+
"description": "Person uid, agent uid, email address, group id, or '@all' to remove the grant"
|
|
2189
2189
|
}
|
|
2190
2190
|
],
|
|
2191
2191
|
"subcommands": []
|
|
@@ -4013,6 +4013,10 @@ export const COMMAND_CATALOG = [
|
|
|
4013
4013
|
"flags": "--token-stdin",
|
|
4014
4014
|
"description": "Read the API key from stdin"
|
|
4015
4015
|
},
|
|
4016
|
+
{
|
|
4017
|
+
"flags": "--auth-header <name>",
|
|
4018
|
+
"description": "Send the API key in this HTTP header (implies --auth key)"
|
|
4019
|
+
},
|
|
4016
4020
|
{
|
|
4017
4021
|
"flags": "--auth <mode>",
|
|
4018
4022
|
"description": "Force the auth mode: none, key, or oauth (default: detect)"
|
|
@@ -7246,13 +7250,29 @@ export const COMMAND_CATALOG = [
|
|
|
7246
7250
|
},
|
|
7247
7251
|
{
|
|
7248
7252
|
"name": "job",
|
|
7249
|
-
"description": "Run or
|
|
7253
|
+
"description": "Run, inspect, notify, or remediate one scheduled job",
|
|
7250
7254
|
"aliases": [],
|
|
7251
7255
|
"hidden": false,
|
|
7252
7256
|
"usage": "[options] [command]",
|
|
7253
7257
|
"arguments": [],
|
|
7254
7258
|
"options": [],
|
|
7255
7259
|
"subcommands": [
|
|
7260
|
+
{
|
|
7261
|
+
"name": "notify",
|
|
7262
|
+
"description": "Send a scheduled-job status alert to its owner",
|
|
7263
|
+
"aliases": [],
|
|
7264
|
+
"hidden": false,
|
|
7265
|
+
"usage": "[args...]",
|
|
7266
|
+
"arguments": [
|
|
7267
|
+
{
|
|
7268
|
+
"name": "args",
|
|
7269
|
+
"required": false,
|
|
7270
|
+
"variadic": true
|
|
7271
|
+
}
|
|
7272
|
+
],
|
|
7273
|
+
"options": [],
|
|
7274
|
+
"subcommands": []
|
|
7275
|
+
},
|
|
7256
7276
|
{
|
|
7257
7277
|
"name": "probe",
|
|
7258
7278
|
"description": "Check scheduled-job requirements and report readiness",
|
|
@@ -7269,6 +7289,22 @@ export const COMMAND_CATALOG = [
|
|
|
7269
7289
|
"options": [],
|
|
7270
7290
|
"subcommands": []
|
|
7271
7291
|
},
|
|
7292
|
+
{
|
|
7293
|
+
"name": "remediate",
|
|
7294
|
+
"description": "Run bounded remediation for a scheduled-job failure",
|
|
7295
|
+
"aliases": [],
|
|
7296
|
+
"hidden": false,
|
|
7297
|
+
"usage": "[args...]",
|
|
7298
|
+
"arguments": [
|
|
7299
|
+
{
|
|
7300
|
+
"name": "args",
|
|
7301
|
+
"required": false,
|
|
7302
|
+
"variadic": true
|
|
7303
|
+
}
|
|
7304
|
+
],
|
|
7305
|
+
"options": [],
|
|
7306
|
+
"subcommands": []
|
|
7307
|
+
},
|
|
7272
7308
|
{
|
|
7273
7309
|
"name": "run",
|
|
7274
7310
|
"description": "Run a scheduled job",
|
|
@@ -11068,7 +11104,7 @@ export const COMMAND_CATALOG = [
|
|
|
11068
11104
|
},
|
|
11069
11105
|
{
|
|
11070
11106
|
"name": "resume",
|
|
11071
|
-
"description": "Resume an interrupted, done or blocked lane
|
|
11107
|
+
"description": "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with the senior-only --from-failed option",
|
|
11072
11108
|
"aliases": [],
|
|
11073
11109
|
"hidden": false,
|
|
11074
11110
|
"usage": "[options] <lane-id>",
|
|
@@ -82,6 +82,8 @@ export interface MakeAccessFixtureOptions {
|
|
|
82
82
|
* without read on the prefix — the server refuses to enumerate.
|
|
83
83
|
*/
|
|
84
84
|
listDenied?: boolean;
|
|
85
|
+
/** present-denied only: let a test isolate list denial from vend denial. */
|
|
86
|
+
vendDenied?: boolean;
|
|
85
87
|
/** Deny only the ROOT (prefix "") list — the caller can read the prefix but not enumerate the bucket. */
|
|
86
88
|
rootListDenied?: boolean;
|
|
87
89
|
}
|
|
@@ -93,7 +93,7 @@ export function makeAccessFixture(opts) {
|
|
|
93
93
|
if (opts.state === "missing") {
|
|
94
94
|
throw attachStatus(new Error("not found (404)"), 404);
|
|
95
95
|
}
|
|
96
|
-
if (opts.state === "present-denied") {
|
|
96
|
+
if (opts.state === "present-denied" && opts.vendDenied !== false) {
|
|
97
97
|
throw attachStatus(new Error("caller lacks read on this prefix (403)"), 403);
|
|
98
98
|
}
|
|
99
99
|
return FAKE_VEND;
|
|
@@ -122,7 +122,8 @@ export interface RunAccessResult {
|
|
|
122
122
|
outcome: AccessOutcome;
|
|
123
123
|
path: string;
|
|
124
124
|
company: string;
|
|
125
|
-
|
|
125
|
+
/** Omitted when an authorization denial leaves vault existence unknown. */
|
|
126
|
+
exists?: boolean;
|
|
126
127
|
grantor?: AccessGrantor;
|
|
127
128
|
candidates?: string[];
|
|
128
129
|
steps: string[];
|
package/dist/commands/access.js
CHANGED
|
@@ -52,7 +52,7 @@ export function formatAccessOutcome(result) {
|
|
|
52
52
|
lines.push(result.path);
|
|
53
53
|
break;
|
|
54
54
|
case "no-access":
|
|
55
|
-
lines.push(`
|
|
55
|
+
lines.push(`You do not have read access to this path.`);
|
|
56
56
|
lines.push(result.path);
|
|
57
57
|
break;
|
|
58
58
|
case "pending-confirmation":
|
|
@@ -236,72 +236,81 @@ export async function runAccess(input) {
|
|
|
236
236
|
const bucket = entity?.bucketName ?? `hq-vault-cmp-${slug}`;
|
|
237
237
|
const s3 = deps.companyClient({ companyUid: deps.companyUid });
|
|
238
238
|
let listDenied = false;
|
|
239
|
-
const prefixList = await listKeys(s3, bucket, relative);
|
|
240
|
-
const exact = prefixList.keys.find((k) => k === relative);
|
|
241
239
|
let key;
|
|
242
|
-
if (
|
|
243
|
-
// The
|
|
244
|
-
//
|
|
245
|
-
// it for the grantor.
|
|
240
|
+
if (vendDenied) {
|
|
241
|
+
// The caller is denied at the company boundary. Do not use list results to
|
|
242
|
+
// resolve or disclose matching keys, even if a separate listing succeeds.
|
|
246
243
|
listDenied = true;
|
|
247
244
|
key = relative;
|
|
248
|
-
steps.push("list: 403 (no-access)");
|
|
249
|
-
}
|
|
250
|
-
else if (exact) {
|
|
251
|
-
key = exact;
|
|
252
|
-
steps.push("resolve: exact key");
|
|
253
|
-
steps.push("list: 1 hit");
|
|
245
|
+
steps.push("list: skipped after vend 403 (no-access)");
|
|
254
246
|
}
|
|
255
247
|
else {
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const fuzzy = pool.filter((k) => k.toLowerCase().includes(q));
|
|
263
|
-
if (allList.denied && fuzzy.length === 0) {
|
|
248
|
+
const prefixList = await listKeys(s3, bucket, relative);
|
|
249
|
+
const exact = prefixList.keys.find((k) => k === relative);
|
|
250
|
+
if (prefixList.denied) {
|
|
251
|
+
// The server refused to enumerate — the caller has no read on this
|
|
252
|
+
// prefix. Keep the target as the key so the request rung can still name
|
|
253
|
+
// it for the grantor.
|
|
264
254
|
listDenied = true;
|
|
265
255
|
key = relative;
|
|
266
256
|
steps.push("list: 403 (no-access)");
|
|
267
257
|
}
|
|
268
|
-
else if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
path: anchored(relative) || input.target,
|
|
272
|
-
company: slug,
|
|
273
|
-
exists: false,
|
|
274
|
-
steps,
|
|
275
|
-
}, finishCtx);
|
|
276
|
-
emit(input, result, log);
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
else if (fuzzy.length === 1) {
|
|
280
|
-
key = fuzzy[0];
|
|
281
|
-
steps.push("resolve: fuzzy (1 hit)");
|
|
258
|
+
else if (exact) {
|
|
259
|
+
key = exact;
|
|
260
|
+
steps.push("resolve: exact key");
|
|
282
261
|
steps.push("list: 1 hit");
|
|
283
262
|
}
|
|
284
263
|
else {
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
264
|
+
const allList = await listKeys(s3, bucket, "");
|
|
265
|
+
const q = relative.toLowerCase();
|
|
266
|
+
// A root-list 403 only says the caller cannot enumerate the WHOLE bucket.
|
|
267
|
+
// The prefix list above succeeded, so fuzzy-match within it first; the
|
|
268
|
+
// root denial is the verdict only when the readable prefix has no match.
|
|
269
|
+
const pool = allList.denied ? prefixList.keys : allList.keys;
|
|
270
|
+
const fuzzy = pool.filter((k) => k.toLowerCase().includes(q));
|
|
271
|
+
if (allList.denied && fuzzy.length === 0) {
|
|
272
|
+
listDenied = true;
|
|
273
|
+
key = relative;
|
|
274
|
+
steps.push("list: 403 (no-access)");
|
|
290
275
|
}
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
276
|
+
else if (fuzzy.length === 0) {
|
|
277
|
+
steps.push("list: 0 hits");
|
|
278
|
+
const result = finish("never-existed", {
|
|
279
|
+
path: anchored(relative) || input.target,
|
|
294
280
|
company: slug,
|
|
295
281
|
exists: false,
|
|
296
|
-
candidates,
|
|
297
282
|
steps,
|
|
298
283
|
}, finishCtx);
|
|
299
284
|
emit(input, result, log);
|
|
300
285
|
return result;
|
|
301
286
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
287
|
+
else if (fuzzy.length === 1) {
|
|
288
|
+
key = fuzzy[0];
|
|
289
|
+
steps.push("resolve: fuzzy (1 hit)");
|
|
290
|
+
steps.push("list: 1 hit");
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const candidates = fuzzy.map(anchored);
|
|
294
|
+
steps.push(`list: ${fuzzy.length} hits`);
|
|
295
|
+
let picked = null;
|
|
296
|
+
if (deps.pick) {
|
|
297
|
+
picked = await deps.pick(candidates);
|
|
298
|
+
}
|
|
299
|
+
if (!picked) {
|
|
300
|
+
const result = finish("ambiguous", {
|
|
301
|
+
path: input.target,
|
|
302
|
+
company: slug,
|
|
303
|
+
exists: false,
|
|
304
|
+
candidates,
|
|
305
|
+
steps,
|
|
306
|
+
}, finishCtx);
|
|
307
|
+
emit(input, result, log);
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
const stripped = normalizeTarget(picked, slug);
|
|
311
|
+
key = stripped;
|
|
312
|
+
steps.push("resolve: pick");
|
|
313
|
+
}
|
|
305
314
|
}
|
|
306
315
|
}
|
|
307
316
|
const resolvedKey = key;
|
|
@@ -524,7 +533,7 @@ export async function runAccess(input) {
|
|
|
524
533
|
const result = finish(outcome, {
|
|
525
534
|
path: resolvedPath,
|
|
526
535
|
company: slug,
|
|
527
|
-
exists: true,
|
|
536
|
+
...(vendDenied || listDenied ? {} : { exists: true }),
|
|
528
537
|
steps,
|
|
529
538
|
...(fixed ? { fixed, localPath } : {}),
|
|
530
539
|
...(grantor ? { grantor } : {}),
|
|
@@ -541,7 +550,7 @@ function emit(input, result, log) {
|
|
|
541
550
|
outcome: result.outcome,
|
|
542
551
|
path: result.path,
|
|
543
552
|
company: result.company,
|
|
544
|
-
exists: result.exists,
|
|
553
|
+
...(result.exists !== undefined ? { exists: result.exists } : {}),
|
|
545
554
|
steps: result.steps,
|
|
546
555
|
};
|
|
547
556
|
if (result.grantor)
|
package/dist/commands/bot.js
CHANGED
|
@@ -228,6 +228,7 @@ export function spawnDetachedRun(name, dir) {
|
|
|
228
228
|
const fd = fs.openSync(botLogPath(dir), "a");
|
|
229
229
|
const child = spawn(process.execPath, [entry, "bot", "run", name], {
|
|
230
230
|
detached: true,
|
|
231
|
+
windowsHide: true,
|
|
231
232
|
stdio: ["ignore", fd, fd],
|
|
232
233
|
env: { ...process.env, HQ_NO_UPDATE_CHECK: "1" },
|
|
233
234
|
});
|
|
@@ -32,7 +32,7 @@ import * as fs from "node:fs";
|
|
|
32
32
|
import * as path from "node:path";
|
|
33
33
|
import * as yaml from "js-yaml";
|
|
34
34
|
import { parseUserYaml } from "../utils/user-yaml-error.js";
|
|
35
|
-
import { share } from "@indigoai-us/hq-cloud";
|
|
35
|
+
import { createSyncProgressRecorder, share, } from "@indigoai-us/hq-cloud";
|
|
36
36
|
import { DEFAULT_HQ_ROOT, DEFAULT_VAULT_API_URL, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
37
37
|
/** Custom error class so the CLI runner can map to exit codes. */
|
|
38
38
|
export class ProvisionError extends Error {
|
|
@@ -421,7 +421,32 @@ async function safeBody(res) {
|
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
423
|
// ── Default initial-sync runner (wraps share()) ──────────────────────────────
|
|
424
|
+
const INITIAL_SYNC_ERROR_FIELD_LIMIT = 240;
|
|
425
|
+
function truncateInitialSyncErrorField(value) {
|
|
426
|
+
return value.length <= INITIAL_SYNC_ERROR_FIELD_LIMIT
|
|
427
|
+
? value
|
|
428
|
+
: `${value.slice(0, INITIAL_SYNC_ERROR_FIELD_LIMIT - 1)}…`;
|
|
429
|
+
}
|
|
430
|
+
class InitialSyncUploadFailureError extends Error {
|
|
431
|
+
filesUploaded;
|
|
432
|
+
constructor(filesUploaded, failureCount, firstFailure) {
|
|
433
|
+
const detail = `${truncateInitialSyncErrorField(firstFailure.path)}: ` +
|
|
434
|
+
truncateInitialSyncErrorField(firstFailure.message);
|
|
435
|
+
const failureLabel = failureCount === 1
|
|
436
|
+
? "upload failure"
|
|
437
|
+
: "upload failures";
|
|
438
|
+
super(`${failureCount} ${failureLabel}; first failure: ${detail}`);
|
|
439
|
+
this.filesUploaded = filesUploaded;
|
|
440
|
+
this.name = "InitialSyncUploadFailureError";
|
|
441
|
+
}
|
|
442
|
+
}
|
|
424
443
|
async function defaultRunInitialSync(args) {
|
|
444
|
+
let failureCount = 0;
|
|
445
|
+
let firstFailure;
|
|
446
|
+
const recordProgress = createSyncProgressRecorder({
|
|
447
|
+
company: args.slug,
|
|
448
|
+
phase: "push",
|
|
449
|
+
});
|
|
425
450
|
const result = await share({
|
|
426
451
|
paths: [companyDirPath(args.hqRoot, args.slug)],
|
|
427
452
|
company: args.slug,
|
|
@@ -429,7 +454,25 @@ async function defaultRunInitialSync(args) {
|
|
|
429
454
|
onConflict: "keep",
|
|
430
455
|
vaultConfig: buildVaultConfig(args.accessToken),
|
|
431
456
|
hqRoot: args.hqRoot,
|
|
457
|
+
progressRecorder: (event) => {
|
|
458
|
+
recordProgress(event);
|
|
459
|
+
if (event.type === "plan-limit") {
|
|
460
|
+
failureCount += Math.max(1, event.count ?? 0, event.paths?.length ?? 0);
|
|
461
|
+
firstFailure ??= {
|
|
462
|
+
path: event.paths?.[0] ?? "plan limit",
|
|
463
|
+
message: event.message ?? "plan limit refused upload",
|
|
464
|
+
};
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (event.type !== "error" || event.diagnostic === true)
|
|
468
|
+
return;
|
|
469
|
+
failureCount++;
|
|
470
|
+
firstFailure ??= { path: event.path, message: event.message };
|
|
471
|
+
},
|
|
432
472
|
});
|
|
473
|
+
if (failureCount > 0 && firstFailure) {
|
|
474
|
+
throw new InitialSyncUploadFailureError(result.filesUploaded, failureCount, firstFailure);
|
|
475
|
+
}
|
|
433
476
|
return {
|
|
434
477
|
filesUploaded: result.filesUploaded,
|
|
435
478
|
bytesUploaded: result.bytesUploaded,
|
|
@@ -654,7 +697,13 @@ export async function provisionCompany(options) {
|
|
|
654
697
|
created_entity: createdEntity,
|
|
655
698
|
manifest_patched: true,
|
|
656
699
|
config_written: true,
|
|
657
|
-
initial_sync: {
|
|
700
|
+
initial_sync: {
|
|
701
|
+
ok: false,
|
|
702
|
+
error: msg,
|
|
703
|
+
...(err instanceof InitialSyncUploadFailureError
|
|
704
|
+
? { files_uploaded: err.filesUploaded }
|
|
705
|
+
: {}),
|
|
706
|
+
},
|
|
658
707
|
});
|
|
659
708
|
}
|
|
660
709
|
}
|