@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/assets/scaffold/core/scripts/session-journal.sh +116 -24
  3. package/dist/command-catalog.generated.d.ts +222 -9
  4. package/dist/command-catalog.generated.js +273 -9
  5. package/dist/commands/access.d.ts +6 -0
  6. package/dist/commands/access.js +18 -1
  7. package/dist/commands/acl-lock.d.ts +41 -0
  8. package/dist/commands/acl-lock.js +28 -0
  9. package/dist/commands/agent-enroll.d.ts +2 -0
  10. package/dist/commands/agent-enroll.js +9 -4
  11. package/dist/commands/agents.js +5 -11
  12. package/dist/commands/auth.js +6 -5
  13. package/dist/commands/billing-sponsor.d.ts +4 -4
  14. package/dist/commands/billing-sponsor.js +11 -11
  15. package/dist/commands/billing-usage.d.ts +22 -8
  16. package/dist/commands/billing-usage.js +57 -22
  17. package/dist/commands/billing.d.ts +7 -7
  18. package/dist/commands/billing.js +13 -13
  19. package/dist/commands/cloud-provision.js +2 -2
  20. package/dist/commands/cloud.js +7 -7
  21. package/dist/commands/core-session-helper-runtime.d.ts +1 -0
  22. package/dist/commands/core-session-journal-native.d.ts +1 -0
  23. package/dist/commands/core-session-journal-native.js +158 -34
  24. package/dist/commands/db-remote-deps.js +12 -1
  25. package/dist/commands/feedback.d.ts +12 -0
  26. package/dist/commands/feedback.js +52 -0
  27. package/dist/commands/files-browse.d.ts +8 -0
  28. package/dist/commands/files-browse.js +83 -1
  29. package/dist/commands/files.js +19 -7
  30. package/dist/commands/install-global.d.ts +2 -0
  31. package/dist/commands/install-global.js +24 -1
  32. package/dist/commands/integrations-connect.js +1 -0
  33. package/dist/commands/integrations-manage.d.ts +2 -0
  34. package/dist/commands/integrations-manage.js +6 -4
  35. package/dist/commands/lanes-link.js +5 -3
  36. package/dist/commands/lanes-session-start.d.ts +12 -0
  37. package/dist/commands/lanes-session-start.js +93 -12
  38. package/dist/commands/meetings.d.ts +15 -4
  39. package/dist/commands/meetings.js +58 -19
  40. package/dist/commands/skill.js +6 -6
  41. package/dist/commands/whoami.js +1 -1
  42. package/dist/hook-engine-path.js +13 -2
  43. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3236 -1988
  44. package/dist/hq-cloud-sync-runner/package.json +1 -1
  45. package/dist/index.js +21 -0
  46. package/dist/lanes-session-start-argv.d.ts +7 -0
  47. package/dist/lanes-session-start-argv.js +9 -0
  48. package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
  49. package/dist/lib/billing/plan-limit-refusals.js +26 -3
  50. package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
  51. package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
  52. package/dist/lib/billing/plan-lock.d.ts +22 -6
  53. package/dist/lib/billing/plan-lock.js +58 -30
  54. package/dist/lib/billing/usage-cost.d.ts +24 -2
  55. package/dist/lib/billing/usage-cost.js +103 -3
  56. package/dist/lib/billing/usage-only.d.ts +37 -1
  57. package/dist/lib/billing/usage-only.js +89 -4
  58. package/dist/lib/billing/usage-pause.d.ts +27 -0
  59. package/dist/lib/billing/usage-pause.js +50 -0
  60. package/dist/lib/bot/log.d.ts +3 -2
  61. package/dist/lib/bot/progress.d.ts +23 -1
  62. package/dist/lib/bot/progress.js +43 -5
  63. package/dist/lib/bot/run.js +2 -0
  64. package/dist/lib/bot/runtime/claude.d.ts +12 -0
  65. package/dist/lib/bot/runtime/claude.js +20 -0
  66. package/dist/lib/bot/runtime/index.d.ts +10 -1
  67. package/dist/lib/bot/runtime/index.js +14 -2
  68. package/dist/lib/bot/runtime/types.d.ts +8 -0
  69. package/dist/lib/db/control-plane.d.ts +5 -0
  70. package/dist/lib/db/control-plane.js +10 -0
  71. package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
  72. package/dist/lib/doctor/checks/ps1-shims.js +82 -0
  73. package/dist/lib/doctor/fix/apply.d.ts +13 -1
  74. package/dist/lib/doctor/fix/apply.js +38 -3
  75. package/dist/lib/doctor/fix/remediation.d.ts +9 -1
  76. package/dist/lib/doctor/fix/remediation.js +22 -0
  77. package/dist/lib/doctor/registry.js +2 -0
  78. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  79. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  80. package/dist/lib/hook-engine/resolve.d.ts +14 -0
  81. package/dist/lib/hook-engine/resolve.js +55 -1
  82. package/dist/lib/lanes/links.d.ts +2 -1
  83. package/dist/lib/lanes/links.js +235 -17
  84. package/dist/lib/lanes/registry-flag.d.ts +24 -0
  85. package/dist/lib/lanes/registry-flag.js +72 -6
  86. package/dist/lib/lanes/store.d.ts +4 -2
  87. package/dist/lib/lanes/store.js +8 -8
  88. package/dist/lib/plan-limit-nag.d.ts +0 -16
  89. package/dist/lib/plan-limit-nag.js +17 -18
  90. package/dist/lib/windows/ps1-shims.d.ts +60 -0
  91. package/dist/lib/windows/ps1-shims.js +152 -0
  92. package/dist/main.d.ts +0 -2
  93. package/dist/main.js +19 -57
  94. package/dist/utils/plan-gate-error.d.ts +30 -14
  95. package/dist/utils/plan-gate-error.js +60 -46
  96. package/dist/utils/self-update.d.ts +2 -0
  97. package/dist/utils/self-update.js +8 -0
  98. package/dist/utils/staged-npm-install.js +13 -2
  99. package/dist/utils/team-upgrade.d.ts +19 -6
  100. package/dist/utils/team-upgrade.js +37 -39
  101. package/dist/utils/token-keychain.d.ts +11 -8
  102. package/dist/utils/token-keychain.js +36 -15
  103. package/package.json +8 -8
  104. 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)" || return 1
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
- if mkdir "$lockdir" 2>/dev/null; then
226
- acquired=1
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]*) ;; *) printf '%s\n' "$windows_pid" > "$lockdir/winpid" || { rm -rf "$lockdir" 2>/dev/null || true; return 0; } ;; esac
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 ! printf '%s\n' "$$" > "$lockdir/pid" 2>/dev/null; then
233
- rm -rf "$lockdir" 2>/dev/null || true
234
- return 0
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
- break
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
- if [ "$acquired" -ne 1 ]; then
243
- warn "tool-counter: lock remained busy; skipped update"
244
- return 0
245
- fi
323
+ warn "tool-counter: lock remained busy; skipped update"
324
+ return 0
325
+ }
246
326
 
247
- "$@" || rc=$?
248
- if [ "$(cat "$lockdir/pid" 2>/dev/null || true)" = "$$" ]; then
249
- rm -rf "$lockdir" 2>/dev/null || true
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
- printf -- '- `%s` %s — %s\n' "$seq" "$(now_hhmm_z)" "$title" >> "$idx" \
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() { echo 0 > "$counter" 2>/dev/null || true; }
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)" ;;