@indigoai-us/hq-cli 5.311.2 → 5.311.3

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,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.311.3] — 2026-10-01
6
+ - Match company file access checks against the company-anchored paths printed by `hq files browse`.
7
+ - Record a placement resolved to the current host as local so the lane starts without dispatching back through Outpost pickup.
8
+
5
9
  ## [5.311.2] — 2026-10-01
6
10
  - Reduce fast-path startup work by loading network compatibility only on routes that need it.
7
11
  - Make envelope `next_check_at` an unconditional keep-alive hold and remove its completed rollout flag.
@@ -60,13 +60,31 @@ STATUS="$(jq -r '.status // empty' "$MANIFEST")"
60
60
 
61
61
  # File-ACL principals must be an email, grp_<id>, or @all — an agentUid is
62
62
  # NOT accepted by `hq files share`. For agent recipients, grants flow through
63
- # a deterministic per-agent delegation group (the /new-agent pattern):
64
- # grp_dlg-<uid tail>, containing exactly that agent.
63
+ # a deterministic per-agent delegation group keyed by the full UID digest:
64
+ # grp_dlg<first 32 SHA-256 hex characters>, containing exactly that agent.
65
+ agent_group_id() {
66
+ local principal="$1" digest
67
+ if command -v sha256sum >/dev/null 2>&1; then
68
+ digest="$(printf '%s' "$principal" | sha256sum)"
69
+ elif command -v shasum >/dev/null 2>&1; then
70
+ digest="$(printf '%s' "$principal" | shasum -a 256)"
71
+ else
72
+ die "sha256 utility is required for agent delegation groups"
73
+ fi
74
+ printf 'grp_dlg%.32s' "$digest"
75
+ }
76
+
65
77
  GRANT_PRINCIPAL="$PRINCIPAL"
66
78
  case "$PRINCIPAL" in
67
79
  agt_*)
68
80
  TAIL="$(printf '%s' "$PRINCIPAL" | tail -c 9 | tr '[:upper:]' '[:lower:]')"
69
- GRANT_PRINCIPAL="grp_dlg-$TAIL"
81
+ LEGACY_GROUP="grp_dlg-$TAIL"
82
+ NEXT_GROUP="$(agent_group_id "$PRINCIPAL")"
83
+ RECORDED_GROUP="$(jq -r '.grantPrincipal // empty' "$MANIFEST")"
84
+ case "$RECORDED_GROUP" in
85
+ "$LEGACY_GROUP"|"$NEXT_GROUP") GRANT_PRINCIPAL="$RECORDED_GROUP" ;;
86
+ *) GRANT_PRINCIPAL="$NEXT_GROUP" ;;
87
+ esac
70
88
  ;;
71
89
  esac
72
90
 
@@ -133,12 +151,20 @@ done
133
151
  # --- 1b. agent recipients: ensure the delegation group exists + contains them
134
152
 
135
153
  if [ "$GRANT_PRINCIPAL" != "$PRINCIPAL" ]; then
136
- # Both calls are idempotent-tolerated: an existing group / existing member
137
- # is fine — the ACL read-back below is the arbiter of success.
154
+ # Group creation and add are idempotent-tolerated; confirm that the complete
155
+ # member list is exactly this agent before any file ACL is shared.
138
156
  hq groups create "$GRANT_PRINCIPAL" --name "Delegation: $DISPLAY" --company "$COMPANY" \
139
157
  || echo "hq-delegate-grant: group create reported an error (may already exist) — continuing" >&2
140
158
  hq groups add "$GRANT_PRINCIPAL" "$PRINCIPAL" --company "$COMPANY" \
141
159
  || echo "hq-delegate-grant: group add reported an error (may already be a member) — continuing" >&2
160
+ GROUP_MEMBERS="$(NO_COLOR=1 FORCE_COLOR=0 hq groups members "$GRANT_PRINCIPAL" --company "$COMPANY")" \
161
+ || die "could not verify delegation group membership"
162
+ MEMBER_IDS="$(printf '%s\n' "$GROUP_MEMBERS" | awk '
163
+ { gsub(/\033\[[0-9;]*m/, "", $0) }
164
+ /^PERSON_UID([[:space:]]|$)/ { collecting = 1; next }
165
+ collecting && NF { print $1 }
166
+ ')"
167
+ [ "$MEMBER_IDS" = "$PRINCIPAL" ] || die "delegation group membership does not exactly match recipient"
142
168
  fi
143
169
 
144
170
  # --- 2+3. exact structured read-back; never infer identity from a new row ---
@@ -93,7 +93,7 @@ if [ -f "$PRD_ABS" ] && jq -e . "$PRD_ABS" >/dev/null 2>&1; then
93
93
  fi
94
94
 
95
95
  GET_LINES="$(jq -r --arg co "$COMPANY" \
96
- '.vaultPrefixes[] | "hq files get \(.prefix) --company \($co)"' "$MANIFEST")"
96
+ 'def bashq: if test("^[A-Za-z0-9_./:@+-]+$") then . else @sh end; .vaultPrefixes[] | "hq files get \(.prefix | bashq) --company \($co | bashq)"' "$MANIFEST")"
97
97
 
98
98
  REPO_IS_NULL="$(jq -r 'if .repo == null then "yes" else "no" end' "$MANIFEST")"
99
99
  REPO_SECTION=""
@@ -135,7 +135,7 @@ while [ "$i" -lt "$PREFIX_COUNT" ]; do
135
135
  if [ "$BROWSE_RC" -ne 0 ]; then
136
136
  echo " FAIL $PFX — browse errored (likely cause: the grant did not land, or the caller's session expired — re-run /hq-login and hq-delegate-grant.sh)"
137
137
  FAILED=1
138
- elif [ -z "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" ]; then
138
+ elif [ -z "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" ] || [ "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" = "Noobjectsunderthatprefix." ]; then
139
139
  echo " FAIL $PFX — reachable but EMPTY (likely cause: the dossier was never pushed to the vault — hq sync push companies/$COMPANY/... and re-run)"
140
140
  FAILED=1
141
141
  else
@@ -156,6 +156,31 @@ done
156
156
  # Browse each distinct parent at most once, and retry an empty result a few
157
157
  # times before trusting it. (Cache dir cleaned on exit.)
158
158
  REF_BROWSE_CACHE="$(mktemp -d)"
159
+ listing_has_exact_key() {
160
+ local listing="$1" expected="$2"
161
+ printf '%s\n' "$listing" | awk -v expected="$expected" '
162
+ function exact_key(row, expected, modified, size) {
163
+ gsub(/\033\[[0-9;]*m/, "", row)
164
+ sub(/[[:space:]]+$/, "", row)
165
+ if (row !~ / +(shared-with-you|creator access — creator-only|role-bypass|personal-vault|private folder — children are creator-only)$/) return 0
166
+ sub(/ +(shared-with-you|creator access — creator-only|role-bypass|personal-vault|private folder — children are creator-only)$/, "", row)
167
+ modified = row
168
+ sub(/^.* +/, "", modified)
169
+ if (modified != "—" && modified !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9][0-9][0-9]Z$/) return 0
170
+ sub(/ +[^[:space:]]+$/, "", row)
171
+ sub(/[[:space:]]+$/, "", row)
172
+ size = row
173
+ sub(/^.* +/, "", size)
174
+ if (size !~ /^[0-9]+$/) return 0
175
+ sub(/ +[0-9]+$/, "", row)
176
+ sub(/[[:space:]]+$/, "", row)
177
+ return row == expected
178
+ }
179
+ NR > 2 && exact_key($0, expected) { found = 1 }
180
+ END { exit found ? 0 : 1 }
181
+ '
182
+ }
183
+
159
184
  browse_parent() { # parent -> listing (memoized per run, retried on empty)
160
185
  local parent="$1" key out attempt
161
186
  key="$REF_BROWSE_CACHE/$(printf '%s' "$parent" | tr '/@:' '___')"
@@ -187,7 +212,7 @@ if [ "$REF_COUNT" -gt 0 ]; then
187
212
  # early match, which under `set -o pipefail` surfaces printf's SIGPIPE as a
188
213
  # non-zero pipeline and FALSE-FAILS a file that is genuinely present (it bit
189
214
  # exactly the referenced files that sort early in a large parent listing).
190
- if [[ "$LISTING" == *"$BASE"* ]]; then
215
+ if listing_has_exact_key "$LISTING" "$kpath"; then
191
216
  echo " pass $REL"
192
217
  else
193
218
  echo " FAIL $REL — granted prefix is reachable but this referenced file is NOT in the vault (likely cause: it was never pushed — hq sync push $kpath --company $COMPANY and re-run)"
@@ -8,6 +8,7 @@ import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
8
8
  import { describeCoreCommand } from "./core-command-catalog.js";
9
9
  import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
10
10
  import { resolveLiveRoot } from "../utils/hq-roots.js";
11
+ import { ACL_SOURCE_DISPLAY_LABELS } from "../utils/files-browse-acl.js";
11
12
  import { runBundledScript } from "../utils/run-bundled-script.js";
12
13
  const FLAG = "core-native-delegate";
13
14
  const ASSETS = {
@@ -113,6 +114,42 @@ function quoteBash(value) {
113
114
  return value;
114
115
  return `'${value.replaceAll("'", `'\\''`)}'`;
115
116
  }
117
+ function delegationGroupId(principal) {
118
+ return "grp_dlg" + createHash("sha256").update(principal).digest("hex").slice(0, 32);
119
+ }
120
+ function stripAnsiSequences(value) {
121
+ const escape = String.fromCharCode(27);
122
+ return value.replace(new RegExp(`${escape}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
123
+ }
124
+ function groupMemberIds(output) {
125
+ const plainOutput = stripAnsiSequences(output);
126
+ const lines = plainOutput.split(/\r?\n/);
127
+ const header = lines.findIndex((line) => /^PERSON_UID(?:\s|$)/.test(line.trimStart()));
128
+ if (header < 0)
129
+ return null;
130
+ return lines.slice(header + 1).filter((line) => line.trim() !== "")
131
+ .map((line) => line.trim().split(/\s{2,}/, 1)[0] ?? "");
132
+ }
133
+ function browseContainsExactKey(listing, expected) {
134
+ const lines = listing.split(/\r?\n/);
135
+ const aclLabels = Object.values(ACL_SOURCE_DISPLAY_LABELS);
136
+ return lines.slice(2).some((line) => {
137
+ const row = stripAnsiSequences(line).trimEnd();
138
+ const acl = aclLabels.find((label) => row.endsWith(label));
139
+ if (!acl)
140
+ return false;
141
+ const beforeAcl = row.slice(0, -acl.length).trimEnd();
142
+ const modifiedMatch = /^(.*) +(\S+)$/.exec(beforeAcl);
143
+ if (!modifiedMatch)
144
+ return false;
145
+ const modified = modifiedMatch[2] ?? "";
146
+ if (modified !== "—" && !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(modified))
147
+ return false;
148
+ const beforeModified = (modifiedMatch[1] ?? "").trimEnd();
149
+ const sizeMatch = /^(.*) +(\d+)$/.exec(beforeModified);
150
+ return (sizeMatch?.[1] ?? "").trimEnd() === expected;
151
+ });
152
+ }
116
153
  function parseFlags(args, allowed) {
117
154
  const values = {};
118
155
  for (let i = 0; i < args.length;) {
@@ -379,8 +416,12 @@ function runGrant(args, context) {
379
416
  if (!principal)
380
417
  return fail("grant", "manifest has no to.principal");
381
418
  let grantPrincipal = principal;
382
- if (principal.startsWith("agt_"))
383
- grantPrincipal = `grp_dlg-${principal.slice(-9).toLowerCase()}`;
419
+ if (principal.startsWith("agt_")) {
420
+ const legacyGroup = "grp_dlg-" + principal.slice(-9).toLowerCase();
421
+ const nextGroup = delegationGroupId(principal);
422
+ const recordedGroup = text(getPath(manifest, "grantPrincipal"));
423
+ grantPrincipal = recordedGroup === legacyGroup || recordedGroup === nextGroup ? recordedGroup : nextGroup;
424
+ }
384
425
  if (status !== "building" && status !== "granted")
385
426
  return fail("grant", `manifest status is '${status}' — grants run from 'building' (or re-run from 'granted'), not from there`);
386
427
  const prefixes = list(getPath(manifest, "vaultPrefixes"));
@@ -431,6 +472,13 @@ function runGrant(args, context) {
431
472
  result = runHq(["groups", "add", grantPrincipal, principal, "--company", company], context, "inherit");
432
473
  if (result.code !== 0)
433
474
  err("hq-delegate-grant: group add reported an error (may already be a member) — continuing\n");
475
+ const members = subprocess("hq", ["groups", "members", grantPrincipal, "--company", company], context.cwd, { env: { NO_COLOR: "1", FORCE_COLOR: "0" } });
476
+ if (members.code !== 0)
477
+ return fail("grant", "could not verify delegation group membership");
478
+ const memberIds = groupMemberIds(members.stdout ?? "");
479
+ if (memberIds === null || memberIds.length !== 1 || memberIds[0] !== principal) {
480
+ return fail("grant", "delegation group membership does not exactly match recipient");
481
+ }
434
482
  }
435
483
  let expectedType = "";
436
484
  let expectedId = "";
@@ -794,7 +842,7 @@ function runSend(args, context) {
794
842
  .map((entry, index) => `${index + 1}. **${text(getPath(entry, "id"))}: ${text(getPath(entry, "title"))}** — ${text(getPath(entry, "description"))}`)
795
843
  .join("\n");
796
844
  }
797
- const getLines = list(getPath(manifest, "vaultPrefixes", [])).map((entry) => `hq files get ${jqRawPath(entry, "prefix")} --company ${company}`).join("\n");
845
+ const getLines = list(getPath(manifest, "vaultPrefixes", [])).map((entry) => "hq files get " + quoteBash(jqRawPath(entry, "prefix")) + " --company " + quoteBash(company)).join("\n");
798
846
  let repoSection = "";
799
847
  const repoValue = getPath(manifest, "repo");
800
848
  if (repoValue !== null && repoValue !== undefined) {
@@ -824,7 +872,7 @@ function runSend(args, context) {
824
872
  body += `## The goal\n\n${goal}\n\n`;
825
873
  if (stateLine)
826
874
  body += `## Where things stand\n\n${stateLine}\n\n`;
827
- body += `## Pull the files (on demand — no sync)\n\nRun these from your HQ root; each materializes exactly that path:\n\n\`\`\`bash\n${getLines}\n\`\`\`\n\nStart with the project dossier: \`companies/${company}/projects/${project}/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal.\n\n`;
875
+ body += `## Pull the files (on demand — no sync)\n\nRun these from your HQ root; each materializes exactly that path locally:\n\n\`\`\`bash\n${getLines}\n\`\`\`\n\nStart with the project dossier: \`companies/${company}/projects/${project}/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal.\n\n`;
828
876
  if (repoSection)
829
877
  body += `${repoSection}\n\n`;
830
878
  if (secretsSection)
@@ -1024,7 +1072,7 @@ function runVerify(args, context) {
1024
1072
  out(` FAIL ${prefix} — browse errored (likely cause: the grant did not land, or the caller's session expired — re-run /hq-login and hq-delegate-grant.sh)\n`);
1025
1073
  failed = true;
1026
1074
  }
1027
- else if (!combined.replace(/\s/g, "")) {
1075
+ else if (!combined.replace(/\s/g, "") || combined.replace(/\s/g, "") === "Noobjectsunderthatprefix.") {
1028
1076
  out(` FAIL ${prefix} — reachable but EMPTY (likely cause: the dossier was never pushed to the vault — hq sync push companies/${company}/... and re-run)\n`);
1029
1077
  failed = true;
1030
1078
  }
@@ -1059,9 +1107,8 @@ function runVerify(args, context) {
1059
1107
  const relative = reference.slice(`companies/${company}/`.length);
1060
1108
  const separator = relative.lastIndexOf("/");
1061
1109
  const parent = `${separator < 0 ? "." : relative.slice(0, separator)}/`;
1062
- const base = separator < 0 ? relative : relative.slice(separator + 1);
1063
1110
  const listing = browseParent(parent);
1064
- if (listing.includes(base))
1111
+ if (browseContainsExactKey(listing, reference))
1065
1112
  out(` pass ${relative}\n`);
1066
1113
  else {
1067
1114
  out(` FAIL ${relative} — granted prefix is reachable but this referenced file is NOT in the vault (likely cause: it was never pushed — hq sync push ${reference} --company ${company} and re-run)\n`);
@@ -23,6 +23,7 @@ export declare function subprocess(command: string, args: string[], cwd: string,
23
23
  stdinFd?: number;
24
24
  stderr?: "inherit" | "ignore" | "pipe";
25
25
  stdout?: "capture" | "inherit" | "ignore";
26
+ env?: NodeJS.ProcessEnv;
26
27
  maxBuffer?: number;
27
28
  }): CommandResult;
28
29
  type JsonRecord = Record<string, unknown>;
@@ -89,7 +89,7 @@ export function commandExists(name, cwd = process.cwd(), options = {}) {
89
89
  export function subprocess(command, args, cwd, options = {}) {
90
90
  const result = spawnSync(command, args, {
91
91
  cwd,
92
- env: process.env,
92
+ env: { ...process.env, ...options.env },
93
93
  input: options.input,
94
94
  maxBuffer: options.maxBuffer,
95
95
  encoding: "buffer",
@@ -36,6 +36,9 @@
36
36
  import { Command } from "commander";
37
37
  import { ListObjectsV2Command, GetObjectCommand, type ListObjectsV2CommandOutput, type GetObjectCommandOutput } from "@aws-sdk/client-s3";
38
38
  import { VaultClient, type ExplicitGrant } from "@indigoai-us/hq-cloud";
39
+ import { type AclSource } from "../utils/files-browse-acl.js";
40
+ export { ACL_SOURCE_DISPLAY_LABELS, displayAclSource } from "../utils/files-browse-acl.js";
41
+ export type { AclSource } from "../utils/files-browse-acl.js";
39
42
  /** STS-vended credential set the browse/cat path consumes. */
40
43
  export interface BrowseCredentials {
41
44
  accessKeyId: string;
@@ -121,7 +124,6 @@ export type CompanyBrowseClientFactory = (input: {
121
124
  * - `private-folder`: this is the private folder row itself. Its children
122
125
  * are creator-only, so it never covers a child key.
123
126
  */
124
- export type AclSource = "shared-with-you" | "creator" | "role-bypass" | "personal-vault" | "private-folder";
125
127
  export interface BrowseRow {
126
128
  key: string;
127
129
  size: number;
@@ -505,5 +507,4 @@ export declare function resolveBrowseSession(): Promise<{
505
507
  export declare function registerFilesBrowseCommands(filesCmd: Command, deps?: {
506
508
  personalS3Factory?: S3ClientFactory;
507
509
  }): void;
508
- export {};
509
510
  //# sourceMappingURL=files-browse.d.ts.map
@@ -42,6 +42,8 @@ import { pipeline } from "node:stream/promises";
42
42
  import { S3Client, ListObjectsV2Command, GetObjectCommand, } from "@aws-sdk/client-s3";
43
43
  import { VaultClient, } from "@indigoai-us/hq-cloud";
44
44
  import { isPrivateFolderPattern } from "./_patterns.js";
45
+ import { displayAclSource, } from "../utils/files-browse-acl.js";
46
+ export { ACL_SOURCE_DISPLAY_LABELS, displayAclSource } from "../utils/files-browse-acl.js";
45
47
  import { DEFAULT_HQ_ROOT, DEFAULT_COGNITO, buildVaultConfig, } from "../utils/cognito-session.js";
46
48
  import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
47
49
  import { maxLength } from "../utils/table-width.js";
@@ -142,13 +144,6 @@ export function toBrowseGrantPattern(grantPath, slug) {
142
144
  const companyRelative = toBucketRelative(grantPath, slug);
143
145
  return companyRelative === "*" ? "" : companyRelative;
144
146
  }
145
- function displayAclSource(source) {
146
- return source === "private-folder"
147
- ? "private folder — children are creator-only"
148
- : source === "creator"
149
- ? "creator access — creator-only"
150
- : source;
151
- }
152
147
  /**
153
148
  * Bright-line guard for `--out`: refuse to write any byte beneath
154
149
  * `<hqRoot>/companies/`. We do NOT enumerate `companies/manifest.yaml`
@@ -2962,6 +2962,7 @@ async function runCreate(ctx, opts) {
2962
2962
  const config = loadLanesConfig(hqRoot);
2963
2963
  let outpostId = opts.outpost?.trim() || undefined;
2964
2964
  let placementHostOverride;
2965
+ let placementBalanceHostOverride;
2965
2966
  let autoPlacementChoice;
2966
2967
  let autoPlacementSkippedHosts = [];
2967
2968
  let placementMachineIdentity;
@@ -3013,12 +3014,14 @@ async function runCreate(ctx, opts) {
3013
3014
  if (placementToApply !== undefined) {
3014
3015
  const currentHost = laneHostFromCtx(ctx);
3015
3016
  if (placementToApply.kind === "local") {
3016
- placementHostOverride = currentHost;
3017
+ placementBalanceHostOverride = currentHost;
3018
+ placementHostOverride = { kind: "local", id: ctx.hostname() };
3017
3019
  outpostId = undefined;
3018
3020
  }
3019
3021
  else if (placementToApply.kind === "outpost") {
3020
3022
  if (currentHost.kind === "outpost" && currentHost.id === placementToApply.id) {
3021
- placementHostOverride = currentHost;
3023
+ placementBalanceHostOverride = currentHost;
3024
+ placementHostOverride = { kind: "local", id: ctx.hostname() };
3022
3025
  outpostId = undefined;
3023
3026
  }
3024
3027
  else {
@@ -3275,7 +3278,7 @@ async function runCreate(ctx, opts) {
3275
3278
  const allowedAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
3276
3279
  let accountSelection;
3277
3280
  if (provider === "claude") {
3278
- const targetHost = placementHostOverride ?? (outpostId !== undefined
3281
+ const targetHost = placementBalanceHostOverride ?? placementHostOverride ?? (outpostId !== undefined
3279
3282
  ? { kind: "outpost", id: outpostId }
3280
3283
  : laneHostFromCtx(ctx));
3281
3284
  balanceTargetHost = targetHost;
@@ -0,0 +1,5 @@
1
+ /** ACL source values and their stable labels in the hq files browse table. */
2
+ export type AclSource = "shared-with-you" | "creator" | "role-bypass" | "personal-vault" | "private-folder";
3
+ export declare const ACL_SOURCE_DISPLAY_LABELS: Readonly<Record<AclSource, string>>;
4
+ export declare function displayAclSource(source: AclSource): string;
5
+ //# sourceMappingURL=files-browse-acl.d.ts.map
@@ -0,0 +1,11 @@
1
+ export const ACL_SOURCE_DISPLAY_LABELS = {
2
+ "shared-with-you": "shared-with-you",
3
+ creator: "creator access — creator-only",
4
+ "role-bypass": "role-bypass",
5
+ "personal-vault": "personal-vault",
6
+ "private-folder": "private folder — children are creator-only",
7
+ };
8
+ export function displayAclSource(source) {
9
+ return ACL_SOURCE_DISPLAY_LABELS[source];
10
+ }
11
+ //# sourceMappingURL=files-browse-acl.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.311.2",
3
+ "version": "5.311.3",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {