@indigoai-us/hq-cli 5.266.0 → 5.268.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 CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.268.0] — 2026-09-28
6
+
7
+ ### Fixed
8
+
9
+ - `hq core ontology duplicates` now proposes the original entity file as the
10
+ survivor when it ties with its numbered copies (`hq-core 2.md`). Before, the
11
+ path tiebreak picked the copy, which it did for 129 of 530 Indigo groups.
12
+
13
+ ## [5.267.0] — 2026-09-28
14
+
15
+ - Company billing links opened by CLI plan-limit flows now carry cli_limit attribution into checkout telemetry.
16
+
17
+ ### Fixed
18
+
19
+ - Local bots now read the idle inbox backoff flag with a 2 s budget. The
20
+ 150 ms budget timed out from home networks, so bots kept polling every 2 s
21
+ even when the flag was on for them.
22
+
5
23
  ## [5.266.0] — 2026-09-28
6
24
 
7
25
  ### Added
@@ -6,8 +6,9 @@ export interface PlanLimitUpgradeUrlChoices {
6
6
  legacyUrl: string;
7
7
  }
8
8
  export declare function isPlanLimitUpgradeFallbackEnabled(): boolean;
9
- /** Resolve a server URL without changing legacy behavior while the flag is off. */
9
+ /** Resolve and attribute the final selected company billing destination. */
10
10
  export declare function selectPlanLimitUpgradeUrl(companySlug: string | undefined, choices: PlanLimitUpgradeUrlChoices): string;
11
- /** Keep a caller's previous URL until the hq-flags rollout is enabled. */
11
+ export declare function withCliLimitEntrySurface(value: string): string;
12
+ /** Keep the existing destination and flag behavior while labeling company limit links. */
12
13
  export declare function planLimitUpgradeFallbackUrl(companySlug: string | undefined, previousFallbackUrl: string): string;
13
14
  //# sourceMappingURL=plan-limit-upgrade-url.d.ts.map
@@ -13,23 +13,62 @@ function companyBillingUrl(companySlug) {
13
13
  return null;
14
14
  const slug = companySlug?.trim();
15
15
  const route = slug
16
- ? `/companies/${encodeURIComponent(slug)}/billing?upgrade=1`
16
+ ? `/companies/${encodeURIComponent(slug)}/billing?upgrade=1&entrySurface=cli_limit`
17
17
  : "/billing";
18
18
  return `${origin}${route}`;
19
19
  }
20
- /** Resolve a server URL without changing legacy behavior while the flag is off. */
20
+ /** Resolve and attribute the final selected company billing destination. */
21
21
  export function selectPlanLimitUpgradeUrl(companySlug, choices) {
22
- if (!isPlanLimitUpgradeFallbackEnabled())
23
- return choices.legacyUrl;
24
- const topLevelUrl = choices.topLevelUrl?.trim() || undefined;
25
- const nestedUrl = choices.nestedUrl?.trim() || undefined;
26
- const generatedUrl = companyBillingUrl(companySlug);
27
- if (companySlug?.trim()) {
28
- return generatedUrl ?? topLevelUrl ?? nestedUrl ?? choices.legacyUrl;
22
+ let selected;
23
+ if (!isPlanLimitUpgradeFallbackEnabled()) {
24
+ selected = choices.legacyUrl;
29
25
  }
30
- return topLevelUrl ?? nestedUrl ?? generatedUrl ?? choices.legacyUrl;
26
+ else {
27
+ const topLevelUrl = choices.topLevelUrl?.trim() || undefined;
28
+ const nestedUrl = choices.nestedUrl?.trim() || undefined;
29
+ const generatedUrl = companyBillingUrl(companySlug);
30
+ if (companySlug?.trim()) {
31
+ selected = generatedUrl ?? topLevelUrl ?? nestedUrl ?? choices.legacyUrl;
32
+ }
33
+ else {
34
+ selected = topLevelUrl ?? nestedUrl ?? generatedUrl ?? choices.legacyUrl;
35
+ }
36
+ }
37
+ return withCliLimitEntrySurface(selected);
38
+ }
39
+ export function withCliLimitEntrySurface(value) {
40
+ try {
41
+ const url = new URL(value);
42
+ if (url.protocol !== "https:" || url.username || url.password)
43
+ return value;
44
+ const callbackUrl = url.searchParams.get("callbackUrl");
45
+ if (callbackUrl) {
46
+ const callback = new URL(callbackUrl, url.origin);
47
+ const parts = callback.pathname.split("/").filter(Boolean);
48
+ if (parts.length === 5 && parts[0] === "api" && parts[1] === "companies" &&
49
+ /^cmp_[A-Za-z0-9_-]+$/.test(parts[2]) && parts[3] === "billing" && parts[4] === "upgrade") {
50
+ callback.searchParams.set("entrySurface", "cli_limit");
51
+ const serializedCallback = /^[a-z][a-z0-9+.-]*:/i.test(callbackUrl)
52
+ ? callback.toString()
53
+ : callbackUrl.startsWith("//")
54
+ ? `//${callback.host}${callback.pathname}${callback.search}${callback.hash}`
55
+ : `${callback.pathname}${callback.search}${callback.hash}`;
56
+ url.searchParams.set("callbackUrl", serializedCallback);
57
+ return url.toString();
58
+ }
59
+ }
60
+ const parts = url.pathname.split("/").filter(Boolean);
61
+ if (parts.length === 3 && parts[0] === "companies" && parts[2] === "billing") {
62
+ url.searchParams.set("entrySurface", "cli_limit");
63
+ return url.toString();
64
+ }
65
+ }
66
+ catch {
67
+ return value;
68
+ }
69
+ return value;
31
70
  }
32
- /** Keep a caller's previous URL until the hq-flags rollout is enabled. */
71
+ /** Keep the existing destination and flag behavior while labeling company limit links. */
33
72
  export function planLimitUpgradeFallbackUrl(companySlug, previousFallbackUrl) {
34
73
  return selectPlanLimitUpgradeUrl(companySlug, {
35
74
  legacyUrl: previousFallbackUrl,
@@ -2,7 +2,7 @@ import { type FlagClient, type FlagClientOptions } from "@indigoai-us/hq-flags-c
2
2
  import { type BotConfig } from "./config.js";
3
3
  export declare const INBOX_POLL_BACKOFF_FLAG = "agents.inbox-poll-backoff-local-cli";
4
4
  export declare const INBOX_POLL_BACKOFF_FLAG_FALLBACK = false;
5
- export declare const INBOX_POLL_BACKOFF_FLAG_TIMEOUT_MS = 150;
5
+ export declare const INBOX_POLL_BACKOFF_FLAG_TIMEOUT_MS = 2000;
6
6
  export declare const INBOX_POLL_BACKOFF_FLAG_CACHE_MS: number;
7
7
  type InboxPollBackoffFlagClient = Pick<FlagClient, "ready" | "isEnabled" | "close">;
8
8
  export interface InboxPollBackoffFlagReaderDependencies {
@@ -5,7 +5,10 @@ import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
5
5
  import { effectiveBotCompanies, effectiveBotKind } from "./config.js";
6
6
  export const INBOX_POLL_BACKOFF_FLAG = "agents.inbox-poll-backoff-local-cli";
7
7
  export const INBOX_POLL_BACKOFF_FLAG_FALLBACK = false;
8
- export const INBOX_POLL_BACKOFF_FLAG_TIMEOUT_MS = 150;
8
+ // One read every three minutes on a fresh connection. From a home network the
9
+ // TCP and TLS handshakes plus the resolve call take about 250-400 ms, so a
10
+ // 150 ms budget timed out on every read and kept the gate off.
11
+ export const INBOX_POLL_BACKOFF_FLAG_TIMEOUT_MS = 2_000;
9
12
  export const INBOX_POLL_BACKOFF_FLAG_CACHE_MS = 3 * 60_000;
10
13
  const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
11
14
  export function safeErrorClass(error) {
@@ -77,7 +77,7 @@ export interface DuplicateGroup {
77
77
  /** `same_name`: equal canonical names, or one's canonical name is the other's unshared alias. `similar_name`: canonical names match once spaces are removed. */
78
78
  reason: "same_name" | "similar_name";
79
79
  paths: string[];
80
- /** Highest `signal_count`, then earliest `first_seen`, then path. */
80
+ /** Highest `signal_count`, then earliest `first_seen`, then a file that is not a numbered copy (`<slug> 2.md`), then path. */
81
81
  survivor: string;
82
82
  }
83
83
  export interface DuplicateReport {
@@ -285,6 +285,8 @@ export function mergeBackConflicts(hqRoot, company, now) {
285
285
  }
286
286
  return run;
287
287
  }
288
+ /** A copy saved next to the original file, e.g. `hq-core 2.md`. */
289
+ const NUMBERED_COPY = / \d+\.md$/;
288
290
  /** Groups of writable entities of one type whose names collide. Read-only. */
289
291
  export function findDuplicateGroups(index) {
290
292
  const parent = new Map();
@@ -349,6 +351,7 @@ export function findDuplicateGroups(index) {
349
351
  const fb = Date.parse(eb.first_seen ?? "");
350
352
  return (eb.signal_count - ea.signal_count ||
351
353
  (Number.isFinite(fa) ? fa : Infinity) - (Number.isFinite(fb) ? fb : Infinity) ||
354
+ Number(NUMBERED_COPY.test(a)) - Number(NUMBERED_COPY.test(b)) ||
352
355
  a.localeCompare(b));
353
356
  });
354
357
  out.push({
@@ -4,6 +4,7 @@
4
4
  * message instead of every command parsing and printing a server response.
5
5
  */
6
6
  import { renderPersonalPlanFeatureNotice } from "../lib/plan-limit-nag.js";
7
+ import { withCliLimitEntrySurface } from "../lib/billing/plan-limit-upgrade-url.js";
7
8
  function planGateReason(code, details) {
8
9
  if (details.reason?.trim())
9
10
  return details.reason.trim();
@@ -27,11 +28,12 @@ function planGateReason(code, details) {
27
28
  /** One renderer for server-selected upgrade links across CLI plan refusals. */
28
29
  export function formatUpgradeRefusal(reason, upgradeUrl, options = {}) {
29
30
  const safeUrl = safeUpgradeUrl(upgradeUrl);
30
- if (options.urlOnly && safeUrl)
31
- return safeUrl;
32
- if (!safeUrl)
31
+ const cliLimitUrl = safeUrl ? withCliLimitEntrySurface(safeUrl) : undefined;
32
+ if (options.urlOnly && cliLimitUrl)
33
+ return cliLimitUrl;
34
+ if (!cliLimitUrl)
33
35
  return reason;
34
- return `${reason}\nUpgrade: ${safeUrl}`;
36
+ return `${reason}\nUpgrade: ${cliLimitUrl}`;
35
37
  }
36
38
  function safeUpgradeUrl(value) {
37
39
  if (typeof value !== "string" ||
@@ -94,6 +96,7 @@ export async function offerUpgradeLink(url, deps = {}) {
94
96
  const safeUrl = safeUpgradeUrl(url);
95
97
  if (!safeUrl)
96
98
  return false;
99
+ const cliLimitUrl = withCliLimitEntrySurface(safeUrl);
97
100
  if (!(deps.interactive ?? canOfferTeamUpgrade(deps.argv)))
98
101
  return false;
99
102
  let answer;
@@ -113,11 +116,11 @@ export async function offerUpgradeLink(url, deps = {}) {
113
116
  if (!/^y(?:es)?$/i.test(answer.trim()))
114
117
  return false;
115
118
  if (deps.openUrl) {
116
- await deps.openUrl(safeUrl);
119
+ await deps.openUrl(cliLimitUrl);
117
120
  }
118
121
  else {
119
122
  const { default: open } = await import("open");
120
- await open(safeUrl);
123
+ await open(cliLimitUrl);
121
124
  }
122
125
  return true;
123
126
  }
@@ -26,6 +26,7 @@ export async function upgradeToTeam(opts = {}) {
26
26
  method: "POST",
27
27
  body: {
28
28
  companyUid,
29
+ entrySurface: "cli_limit",
29
30
  successUrl: CHECKOUT_SUCCESS_URL,
30
31
  cancelUrl: CHECKOUT_CANCEL_URL,
31
32
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.266.0",
3
+ "version": "5.268.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {