@indigoai-us/hq-cli 5.242.0 → 5.244.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,32 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.244.0] — 2026-09-27
6
+
7
+ ### Changed
8
+
9
+ - The setup bot no longer posts "This one takes a minute. I'll reply here as
10
+ soon as it's done." when a step runs long. Other bots keep their notice.
11
+ - After the bundled setup bot connects its first app, it says "Great, <Tool> is
12
+ connected" with a link to the company's integrations page on hq.computer.
13
+ - The bundled setup bot invites teammates through the new-hire flow when HQ
14
+ has it, and tells the person to have the invitee check their spam folder.
15
+
16
+ ## [5.243.0] — 2026-09-27
17
+ ### Added
18
+
19
+ - `hq auth login` remembers the last provider used on this computer and shows the Cognito provider choice page on first login. Only the provider name is stored locally.
20
+
21
+ ### Changed
22
+
23
+ - Internal: moved the accounts, sessions and questions subcommands out of src/commands/lanes.ts; no behaviour change
24
+
25
+ ### Fixed
26
+
27
+ - Removed the unused `cli.network.force-ipv4` hq-flags lookup and forced IPv4
28
+ startup path. The existing Node family-attempt timeout remains unchanged.
29
+ - `hq auth login` continues when it cannot read or save the remembered provider. It prints a dim warning with the state path and error code; a save failure does not turn a successful sign-in into a login failure.
30
+
5
31
  ## [5.242.0] — 2026-09-27
6
32
 
7
33
  ### Changed
@@ -14,6 +40,10 @@
14
40
  company selection when `--company` is omitted; explicit `--company` values
15
41
  remain validated.
16
42
 
43
+ ### Fixed
44
+
45
+ - The `cli.install-path-compatibility` hq-flags key enables pnpm v11+ global-path recognition. On Windows, it also passes drive-path tarballs to tar by filename from the archive directory. The flag defaults to off.
46
+
17
47
  ## [5.241.0] — 2026-09-27
18
48
 
19
49
  ### Fixed
@@ -1611,6 +1641,18 @@
1611
1641
 
1612
1642
  ## [Unreleased]
1613
1643
 
1644
+ ### Fixed
1645
+
1646
+ - A bot whose agent home is on a network share can now mint a token. The
1647
+ refresh lock was a POSIX hard-link lock, and CIFS/SMB shares — Azure Files
1648
+ again — do not implement `link()` at all, so every mint failed with
1649
+ ENOTSUP and took the whole probe down with it: no heartbeat ever posted
1650
+ and the inbox poller retried and failed every 15 seconds. The lock now
1651
+ falls back to an exclusive create, which gives the same guarantee and
1652
+ works there. Contention still surfaces as EEXIST on both paths, so
1653
+ stale-owner reclamation is unchanged, and a link that fails for any other
1654
+ reason is still a real error.
1655
+
1614
1656
  - `hq mesh daemon install` and `hq mesh daemon run` unload and remove the
1615
1657
  legacy `ai.getindigo.hq-work-mesh-listen` unit (LaunchAgent, systemd user
1616
1658
  unit, or Scheduled Task) so only the daemon subscribes. The daemon writes
@@ -114,16 +114,29 @@ console, and they sign in in the browser.
114
114
 
115
115
  ## Inviting a teammate
116
116
 
117
+ Every invite runs through HQ's new-hire flow. Read
118
+ `.claude/skills/new-hire/SKILL.md` in the HQ folder before the first invite and
119
+ follow its steps: check `hq members list` first, set the role, invite, then
120
+ the access that person needs (groups, shared apps and keys) and the
121
+ acceptance follow-through. It is your playbook, not something to show them:
122
+ ask its questions in plain words, one at a time, skip what setup has not
123
+ created yet (groups, an onboarding packet), and never name the skill or its
124
+ command. If that file is not in this HQ, the steps below are enough.
125
+
117
126
  1. Ask for one email and whether they should be a member or an admin
118
- (member is the default; admins can only invite members).
127
+ (member is the default; admins can only invite members). If they already
128
+ said who, do not ask again.
119
129
  2. Confirm in one line that an invitation email will go to that address, and
120
130
  wait for yes.
121
- 3. Run `hq members invite <email> --company <slug> --role member`.
131
+ 3. Send the invite the way the new-hire flow says
132
+ (`hq members invite <email> --company <slug> --role member`).
122
133
  4. Tell them what happens next: the teammate opens the email, signs in with
123
134
  Google or Microsoft, and gets the company in their own HQ, so their Claude
124
135
  Code or Codex knows the business from their first session. Say what they
125
- will see that matters to this person (the connected calls, the client
126
- homes).
136
+ will see that matters to this person (the client homes, and any app you
137
+ have shared with them; a connected app stays private to the person who
138
+ connected it until it is shared). Always add: "Tell them to check their
139
+ spam folder if the email doesn't show up in a few minutes."
127
140
  5. Check with `hq members list` (pending until they accept). If they never
128
141
  got it, `--resend` sends it again.
129
142
 
@@ -142,6 +142,17 @@ the way a good engineer would, and then do it:
142
142
  what it means for their team (see "What HQ is" in your instructions). A
143
143
  connection is not done until this read works. Never say "connected",
144
144
  "done" or "everything is set up" before it does.
145
+ 6. **After the first connection only, say where connections live.** Once
146
+ the read works for the first app they connect, open your reply with the
147
+ confirmation and the link, in these words:
148
+ "Great, <Tool> is connected. You can see everything that's connected at
149
+ https://hq.computer/companies/<company-slug>/integrations, or ask me or
150
+ any other AI in HQ to connect another." Use the company's slug from the
151
+ Owner context (for a personal connection, the link is
152
+ https://hq.computer/personal/integrations). Then the real data from step
153
+ 5 and the next question. Say this once in the whole setup. For every
154
+ later connection, skip it: show the real data from step 5 and move on.
155
+ Give the link again only if they ask where connections live.
145
156
 
146
157
  If a step needs something only they can do (an admin approval, a paid plan),
147
158
  say exactly what, record it in the progress note as waiting, and move on to
@@ -16,31 +16,70 @@
16
16
  import chalk from "chalk";
17
17
  import { browserLogin, clearCachedTokens, loadCachedTokens, isExpiring, loadMachineCreds, CognitoAuthError, } from "@indigoai-us/hq-cloud";
18
18
  import { DEFAULT_COGNITO, refreshCachedSession, resolveCognitoTokenSource, } from "../utils/cognito-session.js";
19
- import { cognitoConfigForLoginProvider } from "../utils/login-provider.js";
19
+ import { resolveLoginProviderSelection, } from "../utils/login-provider.js";
20
+ import { getLastLoginProviderStatePath, loadLastLoginProvider, storeLastLoginProvider, } from "../lib/auth-login-provider-state.js";
20
21
  import { callbackPortBusyGuidance, DEFAULT_CALLBACK_PORT, isCallbackPortBusy, } from "../utils/callback-port-busy.js";
21
- /**
22
- * Decode the (unverified) ID token payload for display purposes only.
23
- * The token was just returned by Cognito's token endpoint, so its contents
24
- * are trusted enough to print — we never use these claims for authorization.
25
- */
26
- function peekIdToken(idToken) {
22
+ /** Decode token claims for local display and provider-memory purposes only. */
23
+ function decodeIdTokenClaims(idToken) {
27
24
  try {
28
25
  const payload = idToken.split(".")[1];
29
26
  if (!payload)
30
- return {};
27
+ return undefined;
31
28
  const pad = payload.length % 4 === 0 ? "" : "=".repeat(4 - (payload.length % 4));
32
29
  const normalized = payload.replace(/-/g, "+").replace(/_/g, "/") + pad;
33
30
  const decoded = JSON.parse(Buffer.from(normalized, "base64").toString("utf-8"));
34
- return {
35
- email: decoded.email,
36
- sub: decoded.sub,
37
- entityType: decoded["custom:entityType"],
38
- entityUid: decoded["custom:entityUid"],
39
- };
31
+ return decoded && typeof decoded === "object" && !Array.isArray(decoded)
32
+ ? decoded
33
+ : undefined;
40
34
  }
41
35
  catch {
42
- return {};
36
+ return undefined;
37
+ }
38
+ }
39
+ function peekIdToken(idToken) {
40
+ const claims = decodeIdTokenClaims(idToken);
41
+ return {
42
+ email: typeof claims?.email === "string" ? claims.email : undefined,
43
+ sub: typeof claims?.sub === "string" ? claims.sub : undefined,
44
+ entityType: typeof claims?.["custom:entityType"] === "string"
45
+ ? claims["custom:entityType"]
46
+ : undefined,
47
+ entityUid: typeof claims?.["custom:entityUid"] === "string"
48
+ ? claims["custom:entityUid"]
49
+ : undefined,
50
+ };
51
+ }
52
+ function errorCode(error) {
53
+ if (error
54
+ && typeof error === "object"
55
+ && "code" in error
56
+ && typeof error.code === "string") {
57
+ return error.code;
43
58
  }
59
+ return "UNKNOWN";
60
+ }
61
+ function providerFromIdToken(idToken) {
62
+ const identities = decodeIdTokenClaims(idToken)?.identities;
63
+ if (!Array.isArray(identities))
64
+ return undefined;
65
+ const providers = new Set();
66
+ for (const identity of identities) {
67
+ if (!identity || typeof identity !== "object" || Array.isArray(identity))
68
+ continue;
69
+ const item = identity;
70
+ const providerName = typeof item.providerName === "string"
71
+ ? item.providerName
72
+ : item.providerType;
73
+ if (typeof providerName !== "string")
74
+ continue;
75
+ const normalized = providerName.trim().toLowerCase();
76
+ if (normalized === "google")
77
+ providers.add("google");
78
+ if (normalized === "microsoft" || normalized === "microsoftpersonal") {
79
+ providers.add("microsoft");
80
+ }
81
+ }
82
+ return providers.size === 1 ? providers.values().next().value : undefined;
44
83
  }
45
84
  /** Display label for the local machine identity (company agents). */
46
85
  function machineIdentityLabel() {
@@ -67,7 +106,25 @@ export function registerAuthCommands(program) {
67
106
  return;
68
107
  }
69
108
  try {
70
- const tokens = await browserLogin(cognitoConfigForLoginProvider(options.provider));
109
+ const canUseRememberedProvider = options.provider === undefined
110
+ && process.env.HQ_COGNITO_IDENTITY_PROVIDER === undefined;
111
+ let rememberedProvider;
112
+ if (canUseRememberedProvider) {
113
+ try {
114
+ rememberedProvider = loadLastLoginProvider();
115
+ }
116
+ catch (error) {
117
+ console.error(chalk.dim(`Could not read provider state at ${getLastLoginProviderStatePath()} (${errorCode(error)}). Continuing to the provider choice page.`));
118
+ }
119
+ }
120
+ const selection = resolveLoginProviderSelection(options.provider, process.env, rememberedProvider);
121
+ const tokens = await browserLogin(selection.config);
122
+ try {
123
+ storeLastLoginProvider(providerFromIdToken(tokens.idToken) ?? selection.providerToRemember);
124
+ }
125
+ catch (error) {
126
+ console.error(chalk.dim(`Could not save provider state at ${getLastLoginProviderStatePath()} (${errorCode(error)}). The provider choice was not saved.`));
127
+ }
71
128
  const who = peekIdToken(tokens.idToken).email ?? "HQ";
72
129
  console.log(chalk.green(`Signed in as ${who}`));
73
130
  console.log(chalk.dim(` Token cached at ~/.hq/cognito-tokens.json (expires ${tokens.expiresAt})`));
@@ -0,0 +1,25 @@
1
+ import type { Ctx, LaneAccountsHandlerDeps } from "./lanes-shared.js";
2
+ export declare function createLaneAccountsHandlers(deps: LaneAccountsHandlerDeps): {
3
+ runAccountsList: (ctx: Ctx, opts: {
4
+ provider?: string;
5
+ }) => Promise<void>;
6
+ runAccountsBalance: (ctx: Ctx, opts: {
7
+ provider?: string;
8
+ apply?: boolean;
9
+ json?: boolean;
10
+ }) => Promise<void>;
11
+ runAccountsLogin: (ctx: Ctx, providerRaw: string, accountId: string, opts: {
12
+ home?: string;
13
+ deviceAuth?: boolean;
14
+ company?: string;
15
+ }) => Promise<void>;
16
+ runAccountsSetHome: (ctx: Ctx, providerRaw: string, accountIdRaw: string, homeRaw: string, opts: {
17
+ host?: string;
18
+ company?: string;
19
+ }) => Promise<void>;
20
+ runAccountsEnable: (ctx: Ctx, providerRaw: string, accountId: string) => Promise<void>;
21
+ runAccountsDisable: (ctx: Ctx, providerRaw: string, accountId: string, opts: {
22
+ reason?: string;
23
+ }) => Promise<void>;
24
+ };
25
+ //# sourceMappingURL=lanes-accounts.d.ts.map