@indigoai-us/hq-cli 5.207.0 → 5.208.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,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.208.0] — 2026-09-26
6
+
7
+ ### Fixed
8
+
9
+ - `hq lanes accounts set-home` and `login` accept `--company <slug|uid>` and
10
+ scope the host-account-home flag read to that company.
11
+ - The `hq daemon` updater now installs new hq-cloud patch releases. It looks up the newest release in the bundled major version and installs that exact version. Before, `npm install` with a version range kept an installed copy that already matched the range, so the updater stayed on 6.18.1 after 6.18.2 was published. A failed lookup is reported as a failed update and installs nothing.
12
+
5
13
  ## [5.207.0] — 2026-09-26
6
14
 
7
15
  ### Added
@@ -8172,6 +8172,9 @@ export declare const COMMAND_CATALOG: readonly [{
8172
8172
  readonly options: readonly [{
8173
8173
  readonly flags: "--host <hostname>";
8174
8174
  readonly description: "Host key (defaults to this host's hostname)";
8175
+ }, {
8176
+ readonly flags: "--company <slug|uid>";
8177
+ readonly description: "Company scope for the host-account-home flag";
8175
8178
  }, {
8176
8179
  readonly flags: "--json";
8177
8180
  readonly description: "JSON on stdout (always)";
@@ -8195,6 +8198,9 @@ export declare const COMMAND_CATALOG: readonly [{
8195
8198
  readonly options: readonly [{
8196
8199
  readonly flags: "--home <path>";
8197
8200
  readonly description: "Account home directory";
8201
+ }, {
8202
+ readonly flags: "--company <slug|uid>";
8203
+ readonly description: "Company scope for the host-account-home flag";
8198
8204
  }, {
8199
8205
  readonly flags: "--device-auth";
8200
8206
  readonly description: "Use Codex device authentication instead of localhost callback";
@@ -10571,6 +10571,10 @@ export const COMMAND_CATALOG = [
10571
10571
  "flags": "--host <hostname>",
10572
10572
  "description": "Host key (defaults to this host's hostname)"
10573
10573
  },
10574
+ {
10575
+ "flags": "--company <slug|uid>",
10576
+ "description": "Company scope for the host-account-home flag"
10577
+ },
10574
10578
  {
10575
10579
  "flags": "--json",
10576
10580
  "description": "JSON on stdout (always)"
@@ -10601,6 +10605,10 @@ export const COMMAND_CATALOG = [
10601
10605
  "flags": "--home <path>",
10602
10606
  "description": "Account home directory"
10603
10607
  },
10608
+ {
10609
+ "flags": "--company <slug|uid>",
10610
+ "description": "Company scope for the host-account-home flag"
10611
+ },
10604
10612
  {
10605
10613
  "flags": "--device-auth",
10606
10614
  "description": "Use Codex device authentication instead of localhost callback"
@@ -35,6 +35,8 @@ export interface LanesCommandDeps {
35
35
  now?: () => Date;
36
36
  hostname?: () => string;
37
37
  resolveCompany?: (company?: string) => Promise<LaneCompanyRef>;
38
+ /** Membership-backed explicit company selection for account-home writers. */
39
+ resolveActiveCompany?: (company: string) => Promise<LaneCompanyRef | null>;
38
40
  listOutpostIds?: () => Promise<string[]>;
39
41
  readOutpostIdentity?: () => OutpostIdentity;
40
42
  spawnLogin?: (opts: {
@@ -30,7 +30,9 @@ import { BALANCE_MOVABLE_STATES, CLAUDE_ACCOUNT_BALANCE_FLAG, accountLoadsForRep
30
30
  import { parseUserYaml } from "../utils/user-yaml-error.js";
31
31
  import { backfillOutpostIdentityMarker as backfillOutpostIdentityMarkerImpl, createOutpostIdentityMarkerIfMissing, validateOutpostIdentityId, } from "../lib/lanes/outpost-identity.js";
32
32
  import { vaultApiFetch } from "../utils/vault-api.js";
33
- import { openMeshTransport } from "../lib/mesh/client.js";
33
+ import { resolveActiveMembershipCompany } from "../lib/mesh/api.js";
34
+ import { openMeshTransport, requireToken } from "../lib/mesh/client.js";
35
+ import { CompanySelectionError } from "../utils/company-selection-error.js";
34
36
  import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshEventIdentity, } from "../lib/lanes/mesh-events.js";
35
37
  import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
36
38
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
@@ -88,11 +90,18 @@ function defaultLaneOutpostQuestionVisibilityEnabled(company, env) {
88
90
  function defaultLaneHostScopedAccountHomesEnabled(company, env) {
89
91
  return readLanesRegistryFlag(LANE_HOST_SCOPED_ACCOUNT_HOMES_FLAG, { company, env });
90
92
  }
91
- async function hostScopedAccountHomesEnabled(ctx) {
93
+ async function hostScopedAccountHomesEnabled(ctx, company) {
92
94
  const localOverride = readLocalOverride(LANE_HOST_SCOPED_ACCOUNT_HOMES_FLAG, ctx.env);
93
95
  if (localOverride !== null)
94
96
  return localOverride;
95
- return ctx.laneHostScopedAccountHomesEnabled(await ctx.resolveCompany());
97
+ return ctx.laneHostScopedAccountHomesEnabled(company ?? await ctx.resolveCompany());
98
+ }
99
+ async function resolveSelectedAccountHomeCompany(ctx, selector) {
100
+ const company = await ctx.resolveActiveCompany(selector);
101
+ if (!company) {
102
+ throw new CompanySelectionError(`No unique active company membership for ${selector}. Pass an active company UID with --company.`);
103
+ }
104
+ return company;
96
105
  }
97
106
  function withoutProviderHome(value) {
98
107
  if (Array.isArray(value))
@@ -158,6 +167,12 @@ async function defaultResolveCompany(company) {
158
167
  const { company: mesh } = await openMeshTransport({ company });
159
168
  return { slug: mesh.companySlug, uid: mesh.companyUid };
160
169
  }
170
+ async function defaultResolveActiveCompany(company) {
171
+ const membership = await resolveActiveMembershipCompany(await requireToken(), company);
172
+ return membership
173
+ ? { slug: membership.companySlug, uid: membership.companyUid }
174
+ : null;
175
+ }
161
176
  function defaultOutpostsClient() {
162
177
  return createOutpostsClient({
163
178
  transport: vaultApiFetch,
@@ -243,6 +258,7 @@ function resolveDeps(deps = {}) {
243
258
  now: deps.now ?? (() => new Date()),
244
259
  hostname: deps.hostname ?? (() => os.hostname()),
245
260
  resolveCompany: deps.resolveCompany ?? defaultResolveCompany,
261
+ resolveActiveCompany: deps.resolveActiveCompany ?? defaultResolveActiveCompany,
246
262
  listOutpostIds: deps.listOutpostIds ?? defaultListOutpostIds,
247
263
  readOutpostIdentity: deps.readOutpostIdentity ?? (() => readOutpostIdentity()),
248
264
  spawnLogin: deps.spawnLogin ?? defaultSpawnLogin,
@@ -7922,8 +7938,11 @@ async function runAccountsLogin(ctx, providerRaw, accountId, opts) {
7922
7938
  if (opts.home !== undefined && !requestedHome) {
7923
7939
  throw new LanesError("invalid_account_home", "--home must be a non-empty path", { provider, account: id });
7924
7940
  }
7941
+ const company = opts.company !== undefined
7942
+ ? await resolveSelectedAccountHomeCompany(ctx, opts.company)
7943
+ : undefined;
7925
7944
  const hostScopedWriterEnabled = registeredAccount !== undefined && requestedHome !== undefined
7926
- ? await hostScopedAccountHomesEnabled(ctx)
7945
+ ? await hostScopedAccountHomesEnabled(ctx, company)
7927
7946
  : false;
7928
7947
  const homeRaw = requestedHome ??
7929
7948
  (registeredAccount !== undefined
@@ -8017,7 +8036,10 @@ async function runAccountsLogin(ctx, providerRaw, accountId, opts) {
8017
8036
  printJson({ ok: true, action: "accounts.login", account: status });
8018
8037
  }
8019
8038
  async function runAccountsSetHome(ctx, providerRaw, accountIdRaw, homeRaw, opts) {
8020
- if (!(await hostScopedAccountHomesEnabled(ctx))) {
8039
+ const company = opts.company !== undefined
8040
+ ? await resolveSelectedAccountHomeCompany(ctx, opts.company)
8041
+ : undefined;
8042
+ if (!(await hostScopedAccountHomesEnabled(ctx, company))) {
8021
8043
  throw new LanesError("host_scoped_account_homes_disabled", "Setting a host-specific account home requires the lanes.host-scoped-account-homes flag", { flag: LANE_HOST_SCOPED_ACCOUNT_HOMES_FLAG });
8022
8044
  }
8023
8045
  const callerCwd = ctx.callerCwd();
@@ -8689,6 +8711,7 @@ export function registerLanesCommand(parent, deps = {}) {
8689
8711
  .argument("<account-id>", "Existing account id")
8690
8712
  .argument("<path>", "Home directory for this host")
8691
8713
  .option("--host <hostname>", "Host key (defaults to this host's hostname)")
8714
+ .option("--company <slug|uid>", "Company scope for the host-account-home flag")
8692
8715
  .option("--json", "JSON on stdout (always)")
8693
8716
  .action(wrap(async (provider, accountId, home, opts) => {
8694
8717
  await runAccountsSetHome(ctx, provider, accountId, home, opts);
@@ -8699,6 +8722,7 @@ export function registerLanesCommand(parent, deps = {}) {
8699
8722
  .argument("<provider>", "claude | codex | grok")
8700
8723
  .argument("<account-id>", "Open-ended account id")
8701
8724
  .option("--home <path>", "Account home directory")
8725
+ .option("--company <slug|uid>", "Company scope for the host-account-home flag")
8702
8726
  .option("--device-auth", "Use Codex device authentication instead of localhost callback")
8703
8727
  .action(wrap(async (provider, accountId, opts) => {
8704
8728
  await runAccountsLogin(ctx, provider, accountId, opts);
@@ -31,8 +31,21 @@ export interface HqCloudRunners {
31
31
  sessionHost: string | null;
32
32
  }
33
33
  export declare function hqCloudRunners(root: string, exists?: (p: string) => boolean): HqCloudRunners;
34
- /** Install the newest release of the bundled major version, never older than the bundled copy. */
35
- export declare function hqCloudInstallCommand(prefix: string, bundledVersion: string, platform?: NodeJS.Platform, pinnedVersion?: string): {
34
+ /**
35
+ * Ask npm for every release of the bundled major version, no older than the
36
+ * bundled copy. The updater installs the newest one exactly: `npm install` with
37
+ * a range keeps an installed copy that already satisfies it, so a range alone
38
+ * never moves 6.18.1 to 6.18.2.
39
+ */
40
+ export declare function hqCloudLatestCommand(bundledVersion: string, platform?: NodeJS.Platform): {
41
+ file: string;
42
+ args: string[];
43
+ shell: boolean;
44
+ };
45
+ /** The newest release in `npm view --json` output with the bundled major version, never older than it. */
46
+ export declare function latestSameMajor(npmViewJson: string, bundledVersion: string): string | null;
47
+ /** Install one exact hq-cloud version into the updater's folder. */
48
+ export declare function hqCloudInstallCommand(prefix: string, version: string, platform?: NodeJS.Platform): {
36
49
  file: string;
37
50
  args: string[];
38
51
  shell: boolean;
@@ -48,6 +61,17 @@ export interface HqCloudUpdateOutcome {
48
61
  to: string | null;
49
62
  detail?: string;
50
63
  }
64
+ export interface NpmRunResult {
65
+ status: number | null;
66
+ stdout?: string | null;
67
+ stderr?: string | null;
68
+ error?: Error;
69
+ }
70
+ export interface UpdateHqCloudDeps {
71
+ run?: (file: string, args: string[], shell: boolean, env: NodeJS.ProcessEnv) => NpmRunResult;
72
+ bundled?: HqCloudCopy | null;
73
+ platform?: NodeJS.Platform;
74
+ }
51
75
  /** Install or update the updater's copy, as this (the login) user. */
52
- export declare function updateManagedHqCloud(daemonRoot: string, env: NodeJS.ProcessEnv): HqCloudUpdateOutcome;
76
+ export declare function updateManagedHqCloud(daemonRoot: string, env: NodeJS.ProcessEnv, deps?: UpdateHqCloudDeps): HqCloudUpdateOutcome;
53
77
  //# sourceMappingURL=hq-cloud.d.ts.map
@@ -61,18 +61,45 @@ export function hqCloudRunners(root, exists = fs.existsSync) {
61
61
  sessionHost: bin("outpost-session-host.js"),
62
62
  };
63
63
  }
64
- /** Install the newest release of the bundled major version, never older than the bundled copy. */
65
- export function hqCloudInstallCommand(prefix, bundledVersion, platform = process.platform, pinnedVersion) {
64
+ /**
65
+ * Ask npm for every release of the bundled major version, no older than the
66
+ * bundled copy. The updater installs the newest one exactly: `npm install` with
67
+ * a range keeps an installed copy that already satisfies it, so a range alone
68
+ * never moves 6.18.1 to 6.18.2.
69
+ */
70
+ export function hqCloudLatestCommand(bundledVersion, platform = process.platform) {
71
+ const plan = buildSpawnPlan("npm", ["view", `${HQ_CLOUD_PACKAGE}@^${bundledVersion}`, "version", "--json"], platform);
72
+ return { file: plan.cmd, args: plan.args, shell: plan.shell };
73
+ }
74
+ /** The newest release in `npm view --json` output with the bundled major version, never older than it. */
75
+ export function latestSameMajor(npmViewJson, bundledVersion) {
76
+ const b = parseVersion(bundledVersion);
77
+ if (!b)
78
+ return null;
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(npmViewJson);
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ const versions = typeof parsed === "string" ? [parsed] : Array.isArray(parsed) ? parsed : [];
87
+ let best = null;
88
+ for (const text of versions) {
89
+ if (typeof text !== "string" || !/^\d+\.\d+\.\d+$/.test(text))
90
+ continue;
91
+ const v = parseVersion(text);
92
+ if (v[0] !== b[0] || newer(b, v))
93
+ continue;
94
+ if (!best || newer(v, best.v))
95
+ best = { text, v };
96
+ }
97
+ return best?.text ?? null;
98
+ }
99
+ /** Install one exact hq-cloud version into the updater's folder. */
100
+ export function hqCloudInstallCommand(prefix, version, platform = process.platform) {
66
101
  // npm is a .cmd on Windows and needs the shell, so every argument is quoted for it.
67
- const plan = buildSpawnPlan("npm", [
68
- "install",
69
- "--prefix",
70
- prefix,
71
- "--no-audit",
72
- "--no-fund",
73
- "--omit=dev",
74
- `${HQ_CLOUD_PACKAGE}@${pinnedVersion ?? `^${bundledVersion}`}`,
75
- ], platform);
102
+ const plan = buildSpawnPlan("npm", ["install", "--prefix", prefix, "--no-audit", "--no-fund", "--omit=dev", `${HQ_CLOUD_PACKAGE}@${version}`], platform);
76
103
  return { file: plan.cmd, args: plan.args, shell: plan.shell };
77
104
  }
78
105
  export function readHqCloudCopy(root) {
@@ -99,10 +126,26 @@ export function currentHqCloud(daemonRoot) {
99
126
  const pinnedVersion = readDaemonConfig(path.join(daemonRoot, "config.json")).hqCloudVersion;
100
127
  return pickHqCloud(bundledHqCloud(), readHqCloudCopy(managedHqCloudRoot(daemonRoot)), (copy) => hqCloudRunners(copy.root).syncRunner !== null, pinnedVersion);
101
128
  }
129
+ function runNpm(file, args, shell, env) {
130
+ return spawnSync(file, args, {
131
+ encoding: "utf8",
132
+ env,
133
+ timeout: 10 * 60_000,
134
+ stdio: ["ignore", "pipe", "pipe"],
135
+ windowsHide: true,
136
+ shell,
137
+ });
138
+ }
139
+ function npmFailure(r) {
140
+ const tail = `${r.stderr ?? ""}\n${r.stdout ?? ""}`.trim().split("\n").slice(-3).join(" ").slice(0, 400);
141
+ return tail || (r.error ? r.error.message : `npm exited ${r.status ?? "by signal"}`);
142
+ }
102
143
  /** Install or update the updater's copy, as this (the login) user. */
103
- export function updateManagedHqCloud(daemonRoot, env) {
144
+ export function updateManagedHqCloud(daemonRoot, env, deps = {}) {
145
+ const run = deps.run ?? runNpm;
146
+ const platform = deps.platform ?? process.platform;
104
147
  const pinnedVersion = readDaemonConfig(path.join(daemonRoot, "config.json")).hqCloudVersion;
105
- const bundled = bundledHqCloud();
148
+ const bundled = deps.bundled !== undefined ? deps.bundled : bundledHqCloud();
106
149
  const from = currentHqCloud(daemonRoot)?.version ?? null;
107
150
  if (!bundled)
108
151
  return { ok: false, from, to: null, detail: "this CLI install has no bundled hq-cloud to match" };
@@ -113,19 +156,22 @@ export function updateManagedHqCloud(daemonRoot, env) {
113
156
  catch (err) {
114
157
  return { ok: false, from, to: null, detail: `could not create ${prefix}: ${err instanceof Error ? err.message : String(err)}` };
115
158
  }
116
- const cmd = hqCloudInstallCommand(prefix, bundled.version, process.platform, pinnedVersion);
117
- const r = spawnSync(cmd.file, cmd.args, {
118
- encoding: "utf8",
119
- env,
120
- timeout: 10 * 60_000,
121
- stdio: ["ignore", "pipe", "pipe"],
122
- windowsHide: true,
123
- shell: cmd.shell,
124
- });
125
- if (r.status !== 0) {
126
- const tail = `${r.stderr ?? ""}\n${r.stdout ?? ""}`.trim().split("\n").slice(-3).join(" ").slice(0, 400);
127
- return { ok: false, from, to: null, detail: tail || (r.error ? r.error.message : `npm exited ${r.status ?? "by signal"}`) };
159
+ let version = pinnedVersion;
160
+ if (!version) {
161
+ const view = hqCloudLatestCommand(bundled.version, platform);
162
+ const r = run(view.file, view.args, view.shell, env);
163
+ if (r.status !== 0)
164
+ return { ok: false, from, to: null, detail: `could not look up the newest hq-cloud: ${npmFailure(r)}` };
165
+ const latest = latestSameMajor(r.stdout ?? "", bundled.version);
166
+ if (!latest) {
167
+ return { ok: false, from, to: null, detail: `npm listed no hq-cloud release at or above ${bundled.version} in the same major version` };
168
+ }
169
+ version = latest;
128
170
  }
171
+ const cmd = hqCloudInstallCommand(prefix, version, platform);
172
+ const r = run(cmd.file, cmd.args, cmd.shell, env);
173
+ if (r.status !== 0)
174
+ return { ok: false, from, to: null, detail: npmFailure(r) };
129
175
  return { ok: true, from, to: currentHqCloud(daemonRoot)?.version ?? null };
130
176
  }
131
177
  //# sourceMappingURL=hq-cloud.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.207.0",
3
+ "version": "5.208.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {