@indigoai-us/hq-cli 5.345.159 → 5.345.161
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 +48 -0
- package/assets/scaffold/core/scripts/session-journal.sh +116 -24
- package/dist/command-catalog.generated.d.ts +222 -9
- package/dist/command-catalog.generated.js +273 -9
- package/dist/commands/access.d.ts +6 -0
- package/dist/commands/access.js +18 -1
- package/dist/commands/acl-lock.d.ts +41 -0
- package/dist/commands/acl-lock.js +28 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +9 -4
- package/dist/commands/agents.js +5 -11
- package/dist/commands/auth.js +6 -5
- package/dist/commands/billing-sponsor.d.ts +4 -4
- package/dist/commands/billing-sponsor.js +11 -11
- package/dist/commands/billing-usage.d.ts +22 -8
- package/dist/commands/billing-usage.js +57 -22
- package/dist/commands/billing.d.ts +7 -7
- package/dist/commands/billing.js +13 -13
- package/dist/commands/cloud-provision.js +2 -2
- package/dist/commands/cloud.js +7 -7
- package/dist/commands/core-session-helper-runtime.d.ts +1 -0
- package/dist/commands/core-session-journal-native.d.ts +1 -0
- package/dist/commands/core-session-journal-native.js +158 -34
- package/dist/commands/db-remote-deps.js +12 -1
- package/dist/commands/feedback.d.ts +12 -0
- package/dist/commands/feedback.js +52 -0
- package/dist/commands/files-browse.d.ts +8 -0
- package/dist/commands/files-browse.js +83 -1
- package/dist/commands/files.js +19 -7
- package/dist/commands/install-global.d.ts +2 -0
- package/dist/commands/install-global.js +24 -1
- package/dist/commands/integrations-connect.js +1 -0
- package/dist/commands/integrations-manage.d.ts +2 -0
- package/dist/commands/integrations-manage.js +6 -4
- package/dist/commands/lanes-link.js +5 -3
- package/dist/commands/lanes-session-start.d.ts +12 -0
- package/dist/commands/lanes-session-start.js +93 -12
- package/dist/commands/meetings.d.ts +15 -4
- package/dist/commands/meetings.js +58 -19
- package/dist/commands/skill.js +6 -6
- package/dist/commands/whoami.js +1 -1
- package/dist/hook-engine-path.js +13 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3236 -1988
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +21 -0
- package/dist/lanes-session-start-argv.d.ts +7 -0
- package/dist/lanes-session-start-argv.js +9 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
- package/dist/lib/billing/plan-limit-refusals.js +26 -3
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
- package/dist/lib/billing/plan-lock.d.ts +22 -6
- package/dist/lib/billing/plan-lock.js +58 -30
- package/dist/lib/billing/usage-cost.d.ts +24 -2
- package/dist/lib/billing/usage-cost.js +103 -3
- package/dist/lib/billing/usage-only.d.ts +37 -1
- package/dist/lib/billing/usage-only.js +89 -4
- package/dist/lib/billing/usage-pause.d.ts +27 -0
- package/dist/lib/billing/usage-pause.js +50 -0
- package/dist/lib/bot/log.d.ts +3 -2
- package/dist/lib/bot/progress.d.ts +23 -1
- package/dist/lib/bot/progress.js +43 -5
- package/dist/lib/bot/run.js +2 -0
- package/dist/lib/bot/runtime/claude.d.ts +12 -0
- package/dist/lib/bot/runtime/claude.js +20 -0
- package/dist/lib/bot/runtime/index.d.ts +10 -1
- package/dist/lib/bot/runtime/index.js +14 -2
- package/dist/lib/bot/runtime/types.d.ts +8 -0
- package/dist/lib/db/control-plane.d.ts +5 -0
- package/dist/lib/db/control-plane.js +10 -0
- package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
- package/dist/lib/doctor/checks/ps1-shims.js +82 -0
- package/dist/lib/doctor/fix/apply.d.ts +13 -1
- package/dist/lib/doctor/fix/apply.js +38 -3
- package/dist/lib/doctor/fix/remediation.d.ts +9 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/doctor/registry.js +2 -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/hook-engine/resolve.d.ts +14 -0
- package/dist/lib/hook-engine/resolve.js +55 -1
- package/dist/lib/lanes/links.d.ts +2 -1
- package/dist/lib/lanes/links.js +235 -17
- package/dist/lib/lanes/registry-flag.d.ts +24 -0
- package/dist/lib/lanes/registry-flag.js +72 -6
- package/dist/lib/lanes/store.d.ts +4 -2
- package/dist/lib/lanes/store.js +8 -8
- package/dist/lib/plan-limit-nag.d.ts +0 -16
- package/dist/lib/plan-limit-nag.js +17 -18
- package/dist/lib/windows/ps1-shims.d.ts +60 -0
- package/dist/lib/windows/ps1-shims.js +152 -0
- package/dist/main.d.ts +0 -2
- package/dist/main.js +19 -57
- package/dist/utils/plan-gate-error.d.ts +30 -14
- package/dist/utils/plan-gate-error.js +60 -46
- package/dist/utils/self-update.d.ts +2 -0
- package/dist/utils/self-update.js +8 -0
- package/dist/utils/staged-npm-install.js +13 -2
- package/dist/utils/team-upgrade.d.ts +19 -6
- package/dist/utils/team-upgrade.js +37 -39
- package/dist/utils/token-keychain.d.ts +11 -8
- package/dist/utils/token-keychain.js +36 -15
- package/package.json +8 -8
- package/scripts/install-hook-engine.mjs +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.161] — 2026-10-23
|
|
6
|
+
- The bundled sync engine now uses hq-cloud 6.18.99, the current npm `latest`.
|
|
7
|
+
HQ Sync follows the server's telemetry decision for free-plan accounts,
|
|
8
|
+
failed company and personal sync legs report a short error summary with no
|
|
9
|
+
file names or paths, and the shell-script line-ending fix never rewrites a
|
|
10
|
+
file whose signature marks it as binary (hq-cloud #1039, #1043, #1044).
|
|
11
|
+
- Fence session-journal counter owners with atomically published PID and owner-token records. Reclaim dead or aged PID-less publisher claims and aged orphan temp directories; holders that lose ownership retry before their callback or counter write.
|
|
12
|
+
- `hq files cat` / `get` and the agent file tools now name the storage host when a signed vault download cannot connect (connection refused or reset, DNS failure, timeout, or a TLS failure from an HTTPS-inspecting proxy) and tell the operator to allow outbound HTTPS (TCP 443) to that host. Before this, the raw `fetch failed` error surfaced with no host.
|
|
13
|
+
- New `hq feedback assign <id> --to <email|prs_uid>` reassigns a feedback ticket with your own login, including a ticket another person claimed. Agent and operator tokens still get `TICKET_CLAIMED_BY_HUMAN` for those tickets, and the command says to rerun it from your own login.
|
|
14
|
+
- `hq integrations share`, `unshare`, `grant` and `ungrant` help now says the principal can be a vault group (`grp_…`), which the commands already accepted. Group membership is resolved when a tool is called, and `hq integrations access` shows the group's name once the server returns it.
|
|
15
|
+
- Bot turns on the Claude runtime now bound each Bash tool call to 10 minutes (`BASH_DEFAULT_TIMEOUT_MS` / `BASH_MAX_TIMEOUT_MS`, kept when already set), and the runtime process is started with stdin closed, so a bot-authored command that waits on stdin (`cat > /dev/null`) can no longer hold a turn "working" indefinitely.
|
|
16
|
+
- A bot no longer posts a short assistant aside (under 40 characters, no `hq-block`) that a tool call follows in the same assistant message ("Bind first, separately."); it is logged at debug level instead. Full-sentence progress notes still post.
|
|
17
|
+
- `hq integrations reconnect` now accepts `--scopes <list>` like `connect` (space- or comma-separated). Use it when a provider such as Meta refuses a sign-in request that names no scopes.
|
|
18
|
+
- Keep explicitly delivered conductor instructions replayable until the receiving
|
|
19
|
+
session acknowledges their stable receipt. Timing out or discarding hook output
|
|
20
|
+
no longer counts as instruction acceptance when the paired hook is installed.
|
|
21
|
+
|
|
22
|
+
- Malformed rows now retain a private audit without preventing valid later
|
|
23
|
+
instructions from being accepted. Active Stop retries do not block repeatedly.
|
|
24
|
+
- Keep temporary macOS Keychain read failures retryable instead of recording them as denied access, explain saved-sign-in fallback, and give explicit retries time to answer the prompt.
|
|
25
|
+
- `hq lanes session-start` (the SessionStart auto-conduct hook) no longer reads the lanes registry flags over the network on every new session. It reads them from the local flag cache and person flag snapshot, fails open when none is known, and refreshes old or missing values in the background. It also skips loading the full command graph. On a warm cache it now returns in about 0.3 s instead of about 3 s, so the hook stays under its 2 s cap and the auto-conduct context is no longer dropped. Feedback 4db60cce.
|
|
26
|
+
- `hq meetings list`, `get`, `transcript`, `search` and the other read subcommands now default to the bound company when the caller explicitly names its HQ session in the environment and omits `--company`. Before this they sent no company at all, so a bound session saw only the caller's personal recordings and missed the company's. Plain terminals and cron calls keep the server default; they never borrow the shared last-session pointer. `--company` still overrides, and `set-company` still requires an explicit target. Feedback 04771691-fcbc-4847-899a-6c966c561b52.
|
|
27
|
+
- On Windows, remove npm's `hq.ps1` and `hq-auth-refresh.ps1` launchers after install, update and `hq install --global`, so PowerShell runs `hq.cmd` instead of failing with UnauthorizedAccess under the default Restricted execution policy. `hq doctor` reports a blocked launcher that is still present and `hq doctor --fix` removes it. The execution policy is only read, never changed.
|
|
28
|
+
|
|
29
|
+
## [5.345.160] — 2026-10-22
|
|
30
|
+
- The bundled sync engine now uses hq-cloud 6.18.98. When HQ pauses an account because of usage, `hq sync` now shows the server's usage-pause message and a Billing link instead of a generic failure (hq-cloud #1036).
|
|
31
|
+
- `hq billing usage` now shows the amounts on HQ's usage statement, which is what the invoice bills. Before this it re-priced the meters itself, so it could show a different figure than the statement, for example charging the vault fee during the trial when the statement waived it.
|
|
32
|
+
- When HQ pauses an account because of usage (spend limit reached, card needed, or trial cap hit), `hq` now shows the server's own message and a Billing link, with no plan-limit or Upgrade wording and no old plan names (Starter, Workforce, Individual). This covers sync, skills, agent enroll, meetings, members, secrets, integrations and remote databases. `hq billing upgrade` now opens the usage-pricing switch page instead of a Workforce checkout. `hq billing usage` now shows the company's usage view, and tells a member to use `--personal` for their own usage. Plan and trial reads use the server's shared plan status when it is available, and older servers keep working as before. (US-024)
|
|
33
|
+
|
|
34
|
+
- `hq files acl` and `hq access` show a path whose most-specific ACL row has no grants and is not open as locked by that row (`lockedBy` in `--json`), instead of as inheriting a broader `*` grant. An open empty row is not a lock: broader grants still apply. An empty ancestor row, whose open flag the server does not report, reads as locked unless that row is open.
|
|
35
|
+
- `hq billing upgrade` no longer dead-ends on the closed HQ Workforce checkout. When hq-pro answers that Workforce is closed to the company (`workforce_closed_to_new_signups`), the command explains that the company stays on Starter unless it switches to usage pricing, says to add a card first with `hq billing checkout`, and opens the console's usage-switch page for the company (`--no-browser` prints it; `--json` returns `{ url, legacyPlansClosed: true, message }`). The open-checkout line no longer quotes a plan price. The over-limit notice (`hq whoami`) and the plan-limit refusal printed by `hq secrets`, `hq members` and sync now read hq-pro's `legacyPlansClosed` field: while Workforce is closed they offer "Switch to usage pricing" with the switch link and never the free Workforce month or an "Upgrade to HQ Workforce" line. Older servers keep today's wording.
|
|
36
|
+
- `hq-hook` gains the US-021 bridge used by master-hook.sh: `hq-hook bridge-plan`
|
|
37
|
+
lists the registry hooks whose `hook-engine-port-<id>` flag is `shadow` or
|
|
38
|
+
`on`, `hq-hook bridge <id>` runs one hook natively (shadow compares with bash
|
|
39
|
+
and returns the bash result; on returns the native result; a missing port or
|
|
40
|
+
a declined payload falls back to bash), and `hq-hook --self-check` reports the
|
|
41
|
+
engine version and bridge schema. The local kill switch `HQ_HOOK_ENGINE=off`
|
|
42
|
+
or the file `~/.hq/hook-engine.off` forces bash before any flag is read.
|
|
43
|
+
Shadow comparisons are appended to `~/.hq/logs/hook-engine.jsonl` with hook
|
|
44
|
+
ids, hashes, exit codes and timings only.
|
|
45
|
+
- `hq-hook run derive-trigger-facts` and `hq-hook run eval-trigger` are native
|
|
46
|
+
ports of the two policy-trigger helpers (US-008, first slice), byte-identical
|
|
47
|
+
to the bundled shell on the parity corpus, with git facts read in-process.
|
|
48
|
+
- `hq core hook-engine-path` now runs the engine's `--self-check` before
|
|
49
|
+
printing its path, and writes `~/.hq/bin/hq-hook/current` so hooks can find
|
|
50
|
+
the pinned engine without starting Node. A binary that fails its self-check
|
|
51
|
+
exits non-zero and removes that pointer, keeping bash hooks active.
|
|
52
|
+
|
|
5
53
|
## [5.345.159] — 2026-10-22
|
|
6
54
|
- `hq lanes link read` and `wait` now require the selected conductor session to own the requested link, preventing accidental cross-link report consumption while preserving explicit session resume/delegation and child inbox access.
|
|
7
55
|
- `hq lanes resume` now uses the canonical lane ID after resolving a short ID. When low disk space queues a resume, it keeps the requested instruction for later admission and reports the lane as queued rather than started.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Spec: core/knowledge/public/hq-core/session-journal-spec.md
|
|
4
4
|
#
|
|
5
5
|
# Usage:
|
|
6
|
-
# session-journal.sh write "<title>" [--files f1,f2,...] [--body-file PATH] [--session KEY]
|
|
6
|
+
# session-journal.sh write "<title>" [--files f1,f2,...] [--body-file PATH] [--session KEY] [--company SLUG]
|
|
7
7
|
# session-journal.sh list [--date YYYY-MM-DD]
|
|
8
8
|
# session-journal.sh read <NNN> [--date YYYY-MM-DD]
|
|
9
9
|
# session-journal.sh index-path [--date YYYY-MM-DD] # prints the INDEX.md path
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# "unscoped". A date-global counter let concurrent Claude and Codex tasks
|
|
19
19
|
# trigger and reset each other's journal reminders.
|
|
20
20
|
#
|
|
21
|
+
# HQ_JOURNAL_COUNTER_NOW overrides epoch seconds only in the counter-lock age check, for deterministic tests.
|
|
21
22
|
# All commands fail-soft: warn to stderr, exit 0 — never block the caller.
|
|
22
23
|
|
|
23
24
|
set -uo pipefail
|
|
@@ -131,7 +132,8 @@ resolve_session_key() {
|
|
|
131
132
|
counter_lock_is_aged() {
|
|
132
133
|
local lockdir="$1" lock_mtime now
|
|
133
134
|
lock_mtime="$(stat -c %Y "$lockdir" 2>/dev/null)" || lock_mtime="$(stat -f %m "$lockdir" 2>/dev/null)" || return 1
|
|
134
|
-
now="$(date -u +%s 2>/dev/null)"
|
|
135
|
+
now="${HQ_JOURNAL_COUNTER_NOW:-$(date -u +%s 2>/dev/null)}"
|
|
136
|
+
[ -n "$now" ] || return 1
|
|
135
137
|
case "$lock_mtime:$now" in *[!0-9:]*|:*|*:) return 1 ;; esac
|
|
136
138
|
[ "$now" -ge "$lock_mtime" ] && [ "$((now - lock_mtime))" -ge 60 ]
|
|
137
139
|
}
|
|
@@ -158,8 +160,9 @@ counter_lock_owner_is_alive() {
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
reclaim_stale_counter_lock() {
|
|
161
|
-
local lockdir="$1" owner_pid current_owner current_windows_pid windows_pid claim stale_dir suffix=0 pidless=0
|
|
163
|
+
local lockdir="$1" owner_pid current_owner current_windows_pid windows_pid owner_token current_token claim stale_dir suffix=0 pidless=0
|
|
162
164
|
owner_pid="$(cat "$lockdir/pid" 2>/dev/null || true)"
|
|
165
|
+
owner_token="$(cat "$lockdir/owner-token" 2>/dev/null || true)"
|
|
163
166
|
case "$owner_pid" in
|
|
164
167
|
'')
|
|
165
168
|
windows_pid="$(cat "$lockdir/winpid" 2>/dev/null || true)"
|
|
@@ -189,6 +192,7 @@ reclaim_stale_counter_lock() {
|
|
|
189
192
|
|
|
190
193
|
current_owner="$(cat "$lockdir/pid" 2>/dev/null || true)"
|
|
191
194
|
current_windows_pid="$(cat "$lockdir/winpid" 2>/dev/null || true)"
|
|
195
|
+
current_token="$(cat "$lockdir/owner-token" 2>/dev/null || true)"
|
|
192
196
|
if [ "$pidless" -eq 1 ]; then
|
|
193
197
|
# Recheck both identity and age after winning the claim, before renaming.
|
|
194
198
|
if [ -z "$current_owner" ] && [ "$current_windows_pid" = "$windows_pid" ] \
|
|
@@ -200,6 +204,7 @@ reclaim_stale_counter_lock() {
|
|
|
200
204
|
fi
|
|
201
205
|
fi
|
|
202
206
|
if [ "$current_owner" = "$owner_pid" ] && [ "$current_windows_pid" = "${windows_pid:-$(cat "$lockdir/winpid" 2>/dev/null || true)}" ] \
|
|
207
|
+
&& [ "$current_token" = "$owner_token" ] \
|
|
203
208
|
&& { [ "$pidless" -eq 1 ] || ! counter_lock_owner_is_alive "$lockdir" "$owner_pid"; }; then
|
|
204
209
|
stale_dir="${lockdir}.stale.$$.$RANDOM"
|
|
205
210
|
while [ -e "$stale_dir" ]; do
|
|
@@ -217,38 +222,111 @@ reclaim_stale_counter_lock() {
|
|
|
217
222
|
return 1
|
|
218
223
|
}
|
|
219
224
|
|
|
225
|
+
reclaim_stale_counter_tempdirs() {
|
|
226
|
+
local lockdir="$1" candidate owner_pid windows_pid owner_token current_pid current_windows current_token stale_dir suffix=0
|
|
227
|
+
# Publishers create <lock>.tmp.XXXXXX. Older native hq-cli builds used
|
|
228
|
+
# <lock>.tmp-XXXXXX, so reclaim aged orphans with either prefix.
|
|
229
|
+
for candidate in "${lockdir}.tmp."* "${lockdir}.tmp-"*; do
|
|
230
|
+
[ -d "$candidate" ] || continue
|
|
231
|
+
counter_lock_is_aged "$candidate" || continue
|
|
232
|
+
owner_pid="$(cat "$candidate/pid" 2>/dev/null || true)"
|
|
233
|
+
windows_pid="$(cat "$candidate/winpid" 2>/dev/null || true)"
|
|
234
|
+
owner_token="$(cat "$candidate/owner-token" 2>/dev/null || true)"
|
|
235
|
+
case "$owner_pid" in
|
|
236
|
+
'')
|
|
237
|
+
if [ -n "$windows_pid" ] && counter_windows_pid_is_alive "$windows_pid"; then continue; fi
|
|
238
|
+
;;
|
|
239
|
+
*[!0-9]*) continue ;;
|
|
240
|
+
*)
|
|
241
|
+
[ "$owner_pid" -gt 1 ] 2>/dev/null || continue
|
|
242
|
+
counter_lock_owner_is_alive "$candidate" "$owner_pid" && continue
|
|
243
|
+
;;
|
|
244
|
+
esac
|
|
245
|
+
current_pid="$(cat "$candidate/pid" 2>/dev/null || true)"
|
|
246
|
+
current_windows="$(cat "$candidate/winpid" 2>/dev/null || true)"
|
|
247
|
+
current_token="$(cat "$candidate/owner-token" 2>/dev/null || true)"
|
|
248
|
+
[ "$current_pid" = "$owner_pid" ] && [ "$current_windows" = "$windows_pid" ] \
|
|
249
|
+
&& [ "$current_token" = "$owner_token" ] && counter_lock_is_aged "$candidate" || continue
|
|
250
|
+
[ -z "$current_pid" ] || ! counter_lock_owner_is_alive "$candidate" "$current_pid" || continue
|
|
251
|
+
[ -z "$current_windows" ] || ! counter_windows_pid_is_alive "$current_windows" || continue
|
|
252
|
+
stale_dir="${candidate}.stale.$$.$RANDOM"
|
|
253
|
+
while [ -e "$stale_dir" ]; do suffix=$((suffix + 1)); stale_dir="${candidate}.stale.$$.$RANDOM.$suffix"; done
|
|
254
|
+
if mv "$candidate" "$stale_dir" 2>/dev/null; then rm -rf "$stale_dir" 2>/dev/null || true; fi
|
|
255
|
+
done
|
|
256
|
+
}
|
|
257
|
+
|
|
220
258
|
with_counter_lock() {
|
|
221
259
|
local lockdir="$1"; shift
|
|
222
|
-
local i=0 acquired=0 rc=0 tries="${HQ_JOURNAL_COUNTER_LOCK_TRIES:-250}"
|
|
260
|
+
local i=0 acquired=0 rc=0 tries="${HQ_JOURNAL_COUNTER_LOCK_TRIES:-250}" owner_token tempdir windows_pid token_file publish_claim
|
|
223
261
|
case "$tries" in ''|*[!0-9]*) tries=250 ;; esac
|
|
224
262
|
while [ "$i" -lt "$tries" ]; do
|
|
225
|
-
|
|
226
|
-
|
|
263
|
+
reclaim_stale_counter_tempdirs "$lockdir"
|
|
264
|
+
# Publish a complete owner record in one rename. Legacy clients may still
|
|
265
|
+
# leave PID-less lock directories, which the age-based reclaim handles.
|
|
266
|
+
tempdir="$(mktemp -d "${lockdir}.tmp.XXXXXX" 2>/dev/null)" || tempdir=""
|
|
267
|
+
if [ -n "$tempdir" ]; then
|
|
268
|
+
owner_token="$$.$RANDOM.$RANDOM.$(date -u +%s 2>/dev/null)"
|
|
227
269
|
if [[ "${OSTYPE:-}" == msys* ]] || [[ "${OSTYPE:-}" == cygwin* ]]; then
|
|
228
|
-
local windows_pid
|
|
229
270
|
windows_pid="$(cat "/proc/$$/winpid" 2>/dev/null || true)"
|
|
230
|
-
case "$windows_pid" in ''|*[!0-9]*)
|
|
271
|
+
case "$windows_pid" in ''|*[!0-9]*) windows_pid="" ;; esac
|
|
272
|
+
if [ -n "$windows_pid" ] && ! printf '%s\n' "$windows_pid" > "$tempdir/winpid"; then rm -rf "$tempdir"; tempdir=""; fi
|
|
231
273
|
fi
|
|
232
|
-
if
|
|
233
|
-
|
|
234
|
-
|
|
274
|
+
if [ -n "$tempdir" ] && printf '%s\n' "$$" > "$tempdir/pid" \
|
|
275
|
+
&& printf '%s\n' "$owner_token" > "$tempdir/owner-token"; then
|
|
276
|
+
# Serialize publishers so mv cannot interpret a concurrently-created
|
|
277
|
+
# lock directory as a destination directory and nest the temp folder.
|
|
278
|
+
publish_claim="${lockdir}.publish"
|
|
279
|
+
if mkdir "$publish_claim" 2>/dev/null; then
|
|
280
|
+
# Exclusive token creation fences a publisher resumed after a stale
|
|
281
|
+
# PID-less claim was reclaimed and reused by another process.
|
|
282
|
+
if (set -C; printf '%s\n' "$owner_token" > "$publish_claim/owner-token") 2>/dev/null \
|
|
283
|
+
&& [ "$(cat "$publish_claim/owner-token" 2>/dev/null || true)" = "$owner_token" ] \
|
|
284
|
+
&& (set -C; printf '%s\n' "$$" > "$publish_claim/pid") 2>/dev/null \
|
|
285
|
+
&& [ "$(cat "$publish_claim/owner-token" 2>/dev/null || true)" = "$owner_token" ] \
|
|
286
|
+
&& [ ! -e "$lockdir" ] && mv "$tempdir" "$lockdir" 2>/dev/null; then :; fi
|
|
287
|
+
if [ "$(cat "$publish_claim/owner-token" 2>/dev/null || true)" = "$owner_token" ]; then
|
|
288
|
+
rm -rf "$publish_claim" 2>/dev/null || true
|
|
289
|
+
fi
|
|
290
|
+
else
|
|
291
|
+
rm -rf "$tempdir" 2>/dev/null || true
|
|
292
|
+
reclaim_stale_counter_lock "$publish_claim" && continue
|
|
293
|
+
fi
|
|
294
|
+
if [ ! -e "$tempdir" ] && [ "$(cat "$lockdir/owner-token" 2>/dev/null || true)" = "$owner_token" ]; then
|
|
295
|
+
acquired=1
|
|
296
|
+
rmdir "$tempdir" 2>/dev/null || true
|
|
297
|
+
token_file="$lockdir/owner-token"
|
|
298
|
+
if [ "$(cat "$token_file" 2>/dev/null || true)" = "$owner_token" ]; then
|
|
299
|
+
"$@" "$owner_token"
|
|
300
|
+
rc=$?
|
|
301
|
+
if [ "$rc" -eq 75 ]; then
|
|
302
|
+
acquired=0
|
|
303
|
+
i=$((i + 1))
|
|
304
|
+
continue
|
|
305
|
+
fi
|
|
306
|
+
if [ "$(cat "$token_file" 2>/dev/null || true)" = "$owner_token" ]; then
|
|
307
|
+
rm -rf "$lockdir" 2>/dev/null || true
|
|
308
|
+
fi
|
|
309
|
+
return "$rc"
|
|
310
|
+
fi
|
|
311
|
+
acquired=0
|
|
312
|
+
i=$((i + 1))
|
|
313
|
+
continue
|
|
314
|
+
fi
|
|
315
|
+
if [ ! -e "$tempdir" ]; then rm -rf "$lockdir/$(basename "$tempdir")" 2>/dev/null || true; fi
|
|
235
316
|
fi
|
|
236
|
-
|
|
317
|
+
if [ -n "$tempdir" ]; then rm -rf "$tempdir" 2>/dev/null || true; fi
|
|
237
318
|
fi
|
|
238
319
|
reclaim_stale_counter_lock "$lockdir" && continue
|
|
239
320
|
i=$((i + 1))
|
|
240
321
|
[ "$i" -ge "$tries" ] || sleep 0.02 2>/dev/null || sleep 1
|
|
241
322
|
done
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
fi
|
|
323
|
+
warn "tool-counter: lock remained busy; skipped update"
|
|
324
|
+
return 0
|
|
325
|
+
}
|
|
246
326
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
fi
|
|
251
|
-
return "$rc"
|
|
327
|
+
counter_lock_token_is_current() {
|
|
328
|
+
local lockdir="$1" owner_token="$2"
|
|
329
|
+
[ -n "$owner_token" ] && [ "$(cat "$lockdir/owner-token" 2>/dev/null || true)" = "$owner_token" ]
|
|
252
330
|
}
|
|
253
331
|
|
|
254
332
|
counter_read_raw() {
|
|
@@ -303,8 +381,10 @@ case "$cmd" in
|
|
|
303
381
|
files_csv=""
|
|
304
382
|
body_file=""
|
|
305
383
|
session_arg=""
|
|
384
|
+
company_arg="${HQ_ACTIVE_COMPANY:-}"
|
|
306
385
|
while [ $# -gt 0 ]; do
|
|
307
386
|
case "$1" in
|
|
387
|
+
--company) company_arg="${2:-}"; shift 2 || break ;;
|
|
308
388
|
--files) files_csv="${2:-}"; shift 2 || break ;;
|
|
309
389
|
--body-file) body_file="${2:-}"; shift 2 || break ;;
|
|
310
390
|
--session) session_arg="${2:-}"; shift 2 || break ;;
|
|
@@ -313,6 +393,9 @@ case "$cmd" in
|
|
|
313
393
|
done
|
|
314
394
|
|
|
315
395
|
[ -z "$title" ] && { warn "write: title required"; exit 0; }
|
|
396
|
+
# The bound company tags the entry so the SessionStart index loader can
|
|
397
|
+
# surface only that company's entries. Anything not slug-shaped is dropped.
|
|
398
|
+
[[ "$company_arg" =~ ^[A-Za-z0-9_-]+$ ]] || company_arg=""
|
|
316
399
|
|
|
317
400
|
d="$(today)"
|
|
318
401
|
dir="$(journal_dir_for "$d")"
|
|
@@ -346,6 +429,7 @@ case "$cmd" in
|
|
|
346
429
|
printf 'ts: %s\n' "$(now_iso_z)"
|
|
347
430
|
printf 'title: %s\n' "$yaml_title"
|
|
348
431
|
printf 'slug: %s\n' "$yaml_slug"
|
|
432
|
+
[ -z "$company_arg" ] || printf 'company: %s\n' "$company_arg"
|
|
349
433
|
if [ -n "$files_csv" ]; then
|
|
350
434
|
printf 'files:\n'
|
|
351
435
|
old_ifs="$IFS"
|
|
@@ -384,7 +468,9 @@ case "$cmd" in
|
|
|
384
468
|
if [ ! -f "$idx" ]; then
|
|
385
469
|
printf '# Session journal — %s\n\n' "$d" > "$idx"
|
|
386
470
|
fi
|
|
387
|
-
|
|
471
|
+
company_tag=""
|
|
472
|
+
[ -z "$company_arg" ] || company_tag=" [company: $company_arg]"
|
|
473
|
+
printf -- '- `%s` %s — %s%s\n' "$seq" "$(now_hhmm_z)" "$title" "$company_tag" >> "$idx" \
|
|
388
474
|
|| warn "write: INDEX update failed"
|
|
389
475
|
|
|
390
476
|
# Reset the tool counter for THIS session only — journaling one task's
|
|
@@ -452,8 +538,10 @@ case "$cmd" in
|
|
|
452
538
|
case "$sub" in
|
|
453
539
|
increment)
|
|
454
540
|
do_increment() {
|
|
455
|
-
local n
|
|
541
|
+
local owner_token="${1:-}" n
|
|
542
|
+
counter_lock_token_is_current "$lockdir" "$owner_token" || return 75
|
|
456
543
|
n=$(counter_read_raw "$counter")
|
|
544
|
+
counter_lock_token_is_current "$lockdir" "$owner_token" || return 75
|
|
457
545
|
echo $((n + 1)) > "$counter" 2>/dev/null || true
|
|
458
546
|
}
|
|
459
547
|
with_counter_lock "$lockdir" do_increment
|
|
@@ -462,7 +550,11 @@ case "$cmd" in
|
|
|
462
550
|
printf '%d\n' "$(counter_read_raw "$counter")"
|
|
463
551
|
;;
|
|
464
552
|
reset)
|
|
465
|
-
do_reset() {
|
|
553
|
+
do_reset() {
|
|
554
|
+
local owner_token="${1:-}"
|
|
555
|
+
counter_lock_token_is_current "$lockdir" "$owner_token" || return 75
|
|
556
|
+
echo 0 > "$counter" 2>/dev/null || true
|
|
557
|
+
}
|
|
466
558
|
with_counter_lock "$lockdir" do_reset
|
|
467
559
|
;;
|
|
468
560
|
*) warn "tool-counter: subcommand required (increment|read|reset)" ;;
|