@indigoai-us/hq-cli 5.30.0 → 5.31.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.
@@ -34,7 +34,7 @@
34
34
  * methods and `grantPathToPrefix` from hq-cloud.
35
35
  */
36
36
 
37
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="73529786-7369-5356-a6a8-4d55bd553147")}catch(e){}}();
37
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cad165f9-6445-5434-969b-e0705274aa56")}catch(e){}}();
38
38
  import chalk from "chalk";
39
39
  import * as fs from "node:fs";
40
40
  import * as path from "node:path";
@@ -559,9 +559,12 @@ export function registerFilesBrowseCommands(filesCmd) {
559
559
  "bucket-relative (omit it to list the vault root). Mutually exclusive " +
560
560
  "with --company.")
561
561
  .option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
562
- .action(async (pathArg, options) => {
562
+ .action(async (pathArg, options, command) => {
563
563
  try {
564
- if (options.personal && options.company) {
564
+ // `--company` is declared on the parent `files` group, so commander
565
+ // binds it there — read merged opts to see it from the subcommand.
566
+ const company = command.optsWithGlobals().company;
567
+ if (options.personal && company) {
565
568
  throw new Error("--personal and --company are mutually exclusive. Pick one.");
566
569
  }
567
570
  const accessToken = await ensureCognitoToken();
@@ -594,12 +597,12 @@ export function registerFilesBrowseCommands(filesCmd) {
594
597
  "browse your personal vault.");
595
598
  }
596
599
  // Resolve slug — CLI flag wins, otherwise parse from path arg.
597
- const slug = options.company ?? parseCompanySlugFromPath(pathArg);
600
+ const slug = company ?? parseCompanySlugFromPath(pathArg);
598
601
  // If the user passed `--company` AND the path doesn't begin with
599
602
  // companies/<that-slug>/, refuse — we'd otherwise vend creds for
600
603
  // one company and list keys from another tree, which never makes
601
604
  // sense (defense in depth against operator typos).
602
- if (options.company !== undefined) {
605
+ if (company !== undefined) {
603
606
  const fromPath = (() => {
604
607
  try {
605
608
  return parseCompanySlugFromPath(pathArg);
@@ -608,8 +611,8 @@ export function registerFilesBrowseCommands(filesCmd) {
608
611
  return undefined;
609
612
  }
610
613
  })();
611
- if (fromPath && fromPath !== options.company) {
612
- throw new Error(`--company '${options.company}' disagrees with path slug '${fromPath}'.`);
614
+ if (fromPath && fromPath !== company) {
615
+ throw new Error(`--company '${company}' disagrees with path slug '${fromPath}'.`);
613
616
  }
614
617
  }
615
618
  // Confirm the slug resolves to a known membership — same pattern
@@ -644,9 +647,11 @@ export function registerFilesBrowseCommands(filesCmd) {
644
647
  .option("--personal", "Read from the caller's canonical personal vault. <path> is treated as " +
645
648
  "bucket-relative. Mutually exclusive with --company.")
646
649
  .option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
647
- .action(async (keyArg, options) => {
650
+ .action(async (keyArg, options, command) => {
648
651
  try {
649
- if (options.personal && options.company) {
652
+ // `--company` is bound on the parent `files` group — read merged opts.
653
+ const company = command.optsWithGlobals().company;
654
+ if (options.personal && company) {
650
655
  throw new Error("--personal and --company are mutually exclusive. Pick one.");
651
656
  }
652
657
  const accessToken = await ensureCognitoToken();
@@ -673,8 +678,8 @@ export function registerFilesBrowseCommands(filesCmd) {
673
678
  }
674
679
  return;
675
680
  }
676
- const slug = options.company ?? parseCompanySlugFromPath(keyArg);
677
- if (options.company !== undefined) {
681
+ const slug = company ?? parseCompanySlugFromPath(keyArg);
682
+ if (company !== undefined) {
678
683
  const fromPath = (() => {
679
684
  try {
680
685
  return parseCompanySlugFromPath(keyArg);
@@ -683,8 +688,8 @@ export function registerFilesBrowseCommands(filesCmd) {
683
688
  return undefined;
684
689
  }
685
690
  })();
686
- if (fromPath && fromPath !== options.company) {
687
- throw new Error(`--company '${options.company}' disagrees with path slug '${fromPath}'.`);
691
+ if (fromPath && fromPath !== company) {
692
+ throw new Error(`--company '${company}' disagrees with path slug '${fromPath}'.`);
688
693
  }
689
694
  }
690
695
  await getCompanyUid(accessToken, slug);
@@ -710,21 +715,23 @@ export function registerFilesBrowseCommands(filesCmd) {
710
715
  .command("shared-with-me")
711
716
  .description("List the files/prefixes explicitly shared with you. Omit --company to roll up across every company you're a member of. Pure read — no download, no credentials vended. Owner/admin role-bypass access is NOT listed (only explicit grants).")
712
717
  .option("--company <slug>", "Scope to a single company (defaults to a cross-company roll-up).")
713
- .action(async (options) => {
718
+ .action(async (options, command) => {
714
719
  try {
720
+ // `--company` is bound on the parent `files` group — read merged opts.
721
+ const company = command.optsWithGlobals().company;
715
722
  const accessToken = await ensureCognitoToken();
716
723
  const vaultConfig = buildVaultConfig(accessToken);
717
724
  const client = new VaultClient(vaultConfig);
718
725
  let companyUid;
719
- if (options.company) {
726
+ if (company) {
720
727
  // Confirm membership + resolve UID, same early-failure pattern as
721
728
  // browse/cat. Roll-up mode skips this and fans out internally.
722
- companyUid = await getCompanyUid(accessToken, options.company);
729
+ companyUid = await getCompanyUid(accessToken, company);
723
730
  }
724
731
  const rows = await runSharedWithMe({
725
732
  vaultClient: client,
726
733
  companyUid,
727
- companySlug: options.company,
734
+ companySlug: company,
728
735
  });
729
736
  console.log(formatSharedWithMeTable(rows));
730
737
  }
@@ -738,9 +745,12 @@ export function registerFilesBrowseCommands(filesCmd) {
738
745
  .description("Search vault object keys (case-insensitive path/name match) under a company without downloading. Requires --company (or --personal). Content search is not supported in v1.")
739
746
  .option("--company <slug>", "Company slug to search.")
740
747
  .option("--personal", "Search the caller's canonical personal vault. Mutually exclusive with --company.")
741
- .action(async (query, options) => {
748
+ .action(async (query, options, command) => {
742
749
  try {
743
- if (options.personal && options.company) {
750
+ // `--company` is declared on the parent `files` group too, so commander
751
+ // binds it there; read the merged (global+local) opts to see it.
752
+ const company = command.optsWithGlobals().company;
753
+ if (options.personal && company) {
744
754
  throw new Error("--personal and --company are mutually exclusive. Pick one.");
745
755
  }
746
756
  const accessToken = await ensureCognitoToken();
@@ -763,13 +773,13 @@ export function registerFilesBrowseCommands(filesCmd) {
763
773
  console.log(formatBrowseTable(rows));
764
774
  return;
765
775
  }
766
- if (!options.company) {
776
+ if (!company) {
767
777
  throw new Error("search: --company <slug> is required (or --personal to search your personal vault).");
768
778
  }
769
- await getCompanyUid(accessToken, options.company);
779
+ await getCompanyUid(accessToken, company);
770
780
  const rows = await runSearch({
771
781
  query,
772
- companySlug: options.company,
782
+ companySlug: company,
773
783
  vaultClient: client,
774
784
  s3Factory: defaultS3Factory,
775
785
  region: DEFAULT_COGNITO.region,
@@ -787,12 +797,14 @@ export function registerFilesBrowseCommands(filesCmd) {
787
797
  .option("--into <dir>", "Write into this directory instead of the in-place companies/<slug>/ location. No pin is registered.")
788
798
  .option("--company <slug>", "Company slug (defaults to the slug parsed from <path>).")
789
799
  .option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
790
- .action(async (pathArg, options) => {
800
+ .action(async (pathArg, options, command) => {
791
801
  try {
792
802
  const accessToken = await ensureCognitoToken();
793
803
  const client = new VaultClient(buildVaultConfig(accessToken));
794
- const slug = options.company ?? parseCompanySlugFromPath(pathArg);
795
- if (options.company !== undefined) {
804
+ // `--company` is bound on the parent `files` group — read merged opts.
805
+ const companyOpt = command.optsWithGlobals().company;
806
+ const slug = companyOpt ?? parseCompanySlugFromPath(pathArg);
807
+ if (companyOpt !== undefined) {
796
808
  const fromPath = (() => {
797
809
  try {
798
810
  return parseCompanySlugFromPath(pathArg);
@@ -801,8 +813,8 @@ export function registerFilesBrowseCommands(filesCmd) {
801
813
  return undefined;
802
814
  }
803
815
  })();
804
- if (fromPath && fromPath !== options.company) {
805
- throw new Error(`--company '${options.company}' disagrees with path slug '${fromPath}'.`);
816
+ if (fromPath && fromPath !== companyOpt) {
817
+ throw new Error(`--company '${companyOpt}' disagrees with path slug '${fromPath}'.`);
806
818
  }
807
819
  }
808
820
  await getCompanyUid(accessToken, slug);
@@ -827,4 +839,4 @@ export function registerFilesBrowseCommands(filesCmd) {
827
839
  });
828
840
  }
829
841
  //# sourceMappingURL=files-browse.js.map
830
- //# debugId=73529786-7369-5356-a6a8-4d55bd553147
842
+ //# debugId=cad165f9-6445-5434-969b-e0705274aa56
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.30.0",
3
+ "version": "5.31.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
+ import { Command } from "commander";
23
24
  import * as fs from "node:fs";
24
25
  import * as os from "node:os";
25
26
  import * as path from "node:path";
@@ -1034,3 +1035,51 @@ describe("runGet", () => {
1034
1035
  ).rejects.toThrow(/No objects under/);
1035
1036
  });
1036
1037
  });
1038
+
1039
+ // ── --company parent-binding (regression) ───────────────────────────────────
1040
+ //
1041
+ // The `files` parent group declares `--company`, so commander binds the flag
1042
+ // to the PARENT command, not the subcommand — `subcommand.opts().company` is
1043
+ // empty. The browse/cat/search/get/shared-with-me actions therefore resolve
1044
+ // it via `command.optsWithGlobals().company`. This guards that contract so a
1045
+ // future refactor doesn't silently regress `hq files <sub> ... --company X`
1046
+ // back to the swallowed-flag bug.
1047
+
1048
+ describe("--company parent binding (optsWithGlobals)", () => {
1049
+ it("a subcommand sees --company declared on the parent group", () => {
1050
+ const program = new Command();
1051
+ const files = program
1052
+ .command("files")
1053
+ .option("--company <slug>", "Company slug");
1054
+ let seen: string | undefined = "UNSET";
1055
+ files
1056
+ .command("search <query>")
1057
+ .option("--personal")
1058
+ .action((_query: string, _options: unknown, command: Command) => {
1059
+ seen = command.optsWithGlobals().company as string | undefined;
1060
+ });
1061
+ program.parse(
1062
+ ["files", "search", "README", "--company", "indigo"],
1063
+ { from: "user" } as never,
1064
+ );
1065
+ expect(seen).toBe("indigo");
1066
+ });
1067
+
1068
+ it("documents the bug: subcommand-local opts.company is empty for a parent-bound flag", () => {
1069
+ const program = new Command();
1070
+ const files = program
1071
+ .command("files")
1072
+ .option("--company <slug>", "Company slug");
1073
+ let local: string | undefined = "UNSET";
1074
+ files
1075
+ .command("search <query>")
1076
+ .action((_query: string, options: { company?: string }) => {
1077
+ local = options.company;
1078
+ });
1079
+ program.parse(
1080
+ ["files", "search", "README", "--company", "indigo"],
1081
+ { from: "user" } as never,
1082
+ );
1083
+ expect(local).toBeUndefined();
1084
+ });
1085
+ });
@@ -936,9 +936,12 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
936
936
  `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`,
937
937
  DEFAULT_HQ_ROOT,
938
938
  )
939
- .action(async (pathArg: string | undefined, options: FilesBrowseCliOptions) => {
939
+ .action(async (pathArg: string | undefined, options: FilesBrowseCliOptions, command: Command) => {
940
940
  try {
941
- if (options.personal && options.company) {
941
+ // `--company` is declared on the parent `files` group, so commander
942
+ // binds it there — read merged opts to see it from the subcommand.
943
+ const company = command.optsWithGlobals().company as string | undefined;
944
+ if (options.personal && company) {
942
945
  throw new Error(
943
946
  "--personal and --company are mutually exclusive. Pick one.",
944
947
  );
@@ -981,13 +984,13 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
981
984
  }
982
985
 
983
986
  // Resolve slug — CLI flag wins, otherwise parse from path arg.
984
- const slug = options.company ?? parseCompanySlugFromPath(pathArg);
987
+ const slug = company ?? parseCompanySlugFromPath(pathArg);
985
988
 
986
989
  // If the user passed `--company` AND the path doesn't begin with
987
990
  // companies/<that-slug>/, refuse — we'd otherwise vend creds for
988
991
  // one company and list keys from another tree, which never makes
989
992
  // sense (defense in depth against operator typos).
990
- if (options.company !== undefined) {
993
+ if (company !== undefined) {
991
994
  const fromPath = (() => {
992
995
  try {
993
996
  return parseCompanySlugFromPath(pathArg);
@@ -995,9 +998,9 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
995
998
  return undefined;
996
999
  }
997
1000
  })();
998
- if (fromPath && fromPath !== options.company) {
1001
+ if (fromPath && fromPath !== company) {
999
1002
  throw new Error(
1000
- `--company '${options.company}' disagrees with path slug '${fromPath}'.`,
1003
+ `--company '${company}' disagrees with path slug '${fromPath}'.`,
1001
1004
  );
1002
1005
  }
1003
1006
  }
@@ -1060,9 +1063,11 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1060
1063
  `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`,
1061
1064
  DEFAULT_HQ_ROOT,
1062
1065
  )
1063
- .action(async (keyArg: string, options: FilesCatCliOptions) => {
1066
+ .action(async (keyArg: string, options: FilesCatCliOptions, command: Command) => {
1064
1067
  try {
1065
- if (options.personal && options.company) {
1068
+ // `--company` is bound on the parent `files` group — read merged opts.
1069
+ const company = command.optsWithGlobals().company as string | undefined;
1070
+ if (options.personal && company) {
1066
1071
  throw new Error(
1067
1072
  "--personal and --company are mutually exclusive. Pick one.",
1068
1073
  );
@@ -1099,8 +1104,8 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1099
1104
  return;
1100
1105
  }
1101
1106
 
1102
- const slug = options.company ?? parseCompanySlugFromPath(keyArg);
1103
- if (options.company !== undefined) {
1107
+ const slug = company ?? parseCompanySlugFromPath(keyArg);
1108
+ if (company !== undefined) {
1104
1109
  const fromPath = (() => {
1105
1110
  try {
1106
1111
  return parseCompanySlugFromPath(keyArg);
@@ -1108,9 +1113,9 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1108
1113
  return undefined;
1109
1114
  }
1110
1115
  })();
1111
- if (fromPath && fromPath !== options.company) {
1116
+ if (fromPath && fromPath !== company) {
1112
1117
  throw new Error(
1113
- `--company '${options.company}' disagrees with path slug '${fromPath}'.`,
1118
+ `--company '${company}' disagrees with path slug '${fromPath}'.`,
1114
1119
  );
1115
1120
  }
1116
1121
  }
@@ -1150,23 +1155,25 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1150
1155
  "--company <slug>",
1151
1156
  "Scope to a single company (defaults to a cross-company roll-up).",
1152
1157
  )
1153
- .action(async (options: { company?: string }) => {
1158
+ .action(async (options: { company?: string }, command: Command) => {
1154
1159
  try {
1160
+ // `--company` is bound on the parent `files` group — read merged opts.
1161
+ const company = command.optsWithGlobals().company as string | undefined;
1155
1162
  const accessToken = await ensureCognitoToken();
1156
1163
  const vaultConfig = buildVaultConfig(accessToken);
1157
1164
  const client = new VaultClient(vaultConfig);
1158
1165
 
1159
1166
  let companyUid: string | undefined;
1160
- if (options.company) {
1167
+ if (company) {
1161
1168
  // Confirm membership + resolve UID, same early-failure pattern as
1162
1169
  // browse/cat. Roll-up mode skips this and fans out internally.
1163
- companyUid = await getCompanyUid(accessToken, options.company);
1170
+ companyUid = await getCompanyUid(accessToken, company);
1164
1171
  }
1165
1172
 
1166
1173
  const rows = await runSharedWithMe({
1167
1174
  vaultClient: client,
1168
1175
  companyUid,
1169
- companySlug: options.company,
1176
+ companySlug: company,
1170
1177
  });
1171
1178
 
1172
1179
  console.log(formatSharedWithMeTable(rows));
@@ -1189,9 +1196,12 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1189
1196
  "--personal",
1190
1197
  "Search the caller's canonical personal vault. Mutually exclusive with --company.",
1191
1198
  )
1192
- .action(async (query: string, options: FilesSearchCliOptions) => {
1199
+ .action(async (query: string, options: FilesSearchCliOptions, command: Command) => {
1193
1200
  try {
1194
- if (options.personal && options.company) {
1201
+ // `--company` is declared on the parent `files` group too, so commander
1202
+ // binds it there; read the merged (global+local) opts to see it.
1203
+ const company = command.optsWithGlobals().company as string | undefined;
1204
+ if (options.personal && company) {
1195
1205
  throw new Error(
1196
1206
  "--personal and --company are mutually exclusive. Pick one.",
1197
1207
  );
@@ -1219,16 +1229,16 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1219
1229
  return;
1220
1230
  }
1221
1231
 
1222
- if (!options.company) {
1232
+ if (!company) {
1223
1233
  throw new Error(
1224
1234
  "search: --company <slug> is required (or --personal to search your personal vault).",
1225
1235
  );
1226
1236
  }
1227
- await getCompanyUid(accessToken, options.company);
1237
+ await getCompanyUid(accessToken, company);
1228
1238
 
1229
1239
  const rows = await runSearch({
1230
1240
  query,
1231
- companySlug: options.company,
1241
+ companySlug: company,
1232
1242
  vaultClient: client,
1233
1243
  s3Factory: defaultS3Factory,
1234
1244
  region: DEFAULT_COGNITO.region,
@@ -1261,13 +1271,15 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1261
1271
  `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`,
1262
1272
  DEFAULT_HQ_ROOT,
1263
1273
  )
1264
- .action(async (pathArg: string, options: FilesGetCliOptions) => {
1274
+ .action(async (pathArg: string, options: FilesGetCliOptions, command: Command) => {
1265
1275
  try {
1266
1276
  const accessToken = await ensureCognitoToken();
1267
1277
  const client = new VaultClient(buildVaultConfig(accessToken));
1268
1278
 
1269
- const slug = options.company ?? parseCompanySlugFromPath(pathArg);
1270
- if (options.company !== undefined) {
1279
+ // `--company` is bound on the parent `files` group — read merged opts.
1280
+ const companyOpt = command.optsWithGlobals().company as string | undefined;
1281
+ const slug = companyOpt ?? parseCompanySlugFromPath(pathArg);
1282
+ if (companyOpt !== undefined) {
1271
1283
  const fromPath = (() => {
1272
1284
  try {
1273
1285
  return parseCompanySlugFromPath(pathArg);
@@ -1275,9 +1287,9 @@ export function registerFilesBrowseCommands(filesCmd: Command): void {
1275
1287
  return undefined;
1276
1288
  }
1277
1289
  })();
1278
- if (fromPath && fromPath !== options.company) {
1290
+ if (fromPath && fromPath !== companyOpt) {
1279
1291
  throw new Error(
1280
- `--company '${options.company}' disagrees with path slug '${fromPath}'.`,
1292
+ `--company '${companyOpt}' disagrees with path slug '${fromPath}'.`,
1281
1293
  );
1282
1294
  }
1283
1295
  }