@indigoai-us/hq-cli 5.19.0 → 5.20.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,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.20.0] — 2026-05-21
6
+
7
+ ### Added
8
+
9
+ - **`hq sync {push,pull,now} --all --no-personal`** — skip the
10
+ canonical-person leg of the `--all` fanout (companies-only sync).
11
+ Mirrors the upstream `hq-sync-runner --skip-personal` flag added in
12
+ `@indigoai-us/hq-cloud@5.25.0`. Wired so the AppBar HQ Sync menubar
13
+ can drop personal sync via a toggle, and so CLI users can opt out
14
+ one-off. Plumbed through `pullAll` / `pushAll` / `runNowAll` via a
15
+ new `skipPersonal` option; outside `--all` mode the flag is a no-op.
16
+ - Bumps `@indigoai-us/hq-cloud` to `~5.25.0` (currency-gated personal-
17
+ vault default exclusions + skip-personal CLI/env surface).
18
+
5
19
  ## [5.18.3] — 2026-05-21
6
20
 
7
21
  ### Fixed
@@ -77,6 +77,17 @@ export interface PullAllOptions {
77
77
  * legacy behavior for this run via `--mode-all`.
78
78
  */
79
79
  modeAllOverride?: boolean;
80
+ /**
81
+ * When `true`, skip the canonical-person-entity leg entirely — the
82
+ * fanout only visits the caller's company memberships. Mirrors the
83
+ * `--skip-personal` flag and `HQ_SYNC_SKIP_PERSONAL` env var that
84
+ * `@indigoai-us/hq-cloud`'s `sync-runner` exposes in `--companies`
85
+ * mode (see hq-cloud 5.25.0 `resolveSkipPersonal`). Surfaced on the
86
+ * CLI as `hq sync pull --all --no-personal` so the AppBar HQ Sync
87
+ * menubar toggle (and CLI users opting out one-off) can drop the
88
+ * personal vault from the run without touching the rest of the plan.
89
+ */
90
+ skipPersonal?: boolean;
80
91
  }
81
92
  export interface PullAllRow {
82
93
  slug: string;
@@ -110,6 +121,13 @@ export interface PushAllOptions {
110
121
  hqRoot: string;
111
122
  onConflict?: ConflictStrategy;
112
123
  message?: string;
124
+ /**
125
+ * When `true`, skip the canonical-person-entity leg entirely — the
126
+ * fanout only visits the caller's company memberships. Symmetric with
127
+ * `PullAllOptions.skipPersonal`; surfaced on the CLI as
128
+ * `hq sync push --all --no-personal`.
129
+ */
130
+ skipPersonal?: boolean;
113
131
  }
114
132
  export interface PushAllRow {
115
133
  slug: string;
@@ -13,7 +13,7 @@
13
13
  * hq sync status — show local journal summary
14
14
  */
15
15
 
16
- !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]="f6dfc1de-3284-575a-b790-3dc10db2707c")}catch(e){}}();
16
+ !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]="bdaf154f-b17d-5482-9e4d-feaebe606a1b")}catch(e){}}();
17
17
  import chalk from "chalk";
18
18
  import * as fs from "fs";
19
19
  import * as path from "path";
@@ -60,7 +60,7 @@ export async function pullAll(options, deps) {
60
60
  },
61
61
  });
62
62
  }
63
- const personal = pickCanonicalPerson(persons);
63
+ const personal = options.skipPersonal ? null : pickCanonicalPerson(persons);
64
64
  if (personal) {
65
65
  plan.push({
66
66
  slug: "personal",
@@ -176,7 +176,7 @@ export async function pushAll(options, deps) {
176
176
  },
177
177
  });
178
178
  }
179
- const personal = pickCanonicalPerson(persons);
179
+ const personal = options.skipPersonal ? null : pickCanonicalPerson(persons);
180
180
  if (personal) {
181
181
  plan.push({
182
182
  slug: "personal",
@@ -354,6 +354,12 @@ export function registerCloudCommands(program) {
354
354
  "every top-level entry under <hq-root> minus the excluded set " +
355
355
  "(.git, companies, repos, workspace) — same scope as `--all`'s " +
356
356
  "personal slot. Mutually exclusive with --company and --all.")
357
+ .option("--no-personal", "In `--all` mode, skip the canonical-person leg of the fanout — " +
358
+ "only push to the caller's company memberships. Mirrors the " +
359
+ "upstream `hq-sync-runner --skip-personal` flag (hq-cloud 5.25.0); " +
360
+ "wired so the AppBar HQ Sync menubar can drop personal sync via " +
361
+ "a toggle, and so CLI users can opt out one-off. Ignored outside " +
362
+ "`--all`.")
357
363
  .action(async (paths, options) => {
358
364
  try {
359
365
  assertSingleSelector(options, "push");
@@ -376,7 +382,13 @@ export function registerCloudCommands(program) {
376
382
  "target instead.");
377
383
  process.exit(1);
378
384
  }
379
- await runPushAll(options.hqRoot, options.message, options.onConflict);
385
+ // `options.personal === false` happens when the user passed
386
+ // `--no-personal` (Commander's auto-negation of the `--personal`
387
+ // selector). In `--all` mode that means "skip the personal leg of
388
+ // the fanout" — wired through to `pushAll.skipPersonal`. Outside
389
+ // `--all` the flag has no effect (logged above as part of the
390
+ // option's help text).
391
+ await runPushAll(options.hqRoot, options.message, options.onConflict, options.personal === false);
380
392
  return;
381
393
  }
382
394
  const jsonMode = options.json === true;
@@ -531,6 +543,10 @@ export function registerCloudCommands(program) {
531
543
  "(no companies/<slug>/ prefix). Resolves the person UID automatically " +
532
544
  "from the cached Cognito session. Mutually exclusive with --company " +
533
545
  "and --all.")
546
+ .option("--no-personal", "In `--all` mode, skip the canonical-person leg of the fanout — " +
547
+ "only pull the caller's company memberships. Mirrors the upstream " +
548
+ "`hq-sync-runner --skip-personal` flag (hq-cloud 5.25.0). Ignored " +
549
+ "outside `--all`.")
534
550
  .option("--mode-all", "US-011: opt out of the strict narrow-hint refusal for this run. " +
535
551
  "Has no effect today (default narrow-hint level is 'hint'); " +
536
552
  "wired so future hq-core-staging releases can flip the default to " +
@@ -544,7 +560,10 @@ export function registerCloudCommands(program) {
544
560
  process.exit(1);
545
561
  }
546
562
  if (options.all) {
547
- await runPullAll(options.hqRoot, options.onConflict, options.modeAll === true);
563
+ // `options.personal === false` is Commander's auto-negation of
564
+ // `--personal`; in `--all` mode that means "drop the personal
565
+ // leg from the fanout" (see `--no-personal` option above).
566
+ await runPullAll(options.hqRoot, options.onConflict, options.modeAll === true, options.personal === false);
548
567
  return;
549
568
  }
550
569
  if (options.personal) {
@@ -667,6 +686,10 @@ export function registerCloudCommands(program) {
667
686
  "--personal.")
668
687
  .option("--personal", "Sync the caller's canonical personal vault bidirectionally. " +
669
688
  "Mutually exclusive with --company and --all.")
689
+ .option("--no-personal", "In `--all` mode, skip the canonical-person leg of both the push " +
690
+ "and pull fanouts — only sync the caller's company memberships. " +
691
+ "Mirrors the upstream `hq-sync-runner --skip-personal` flag " +
692
+ "(hq-cloud 5.25.0). Ignored outside `--all`.")
670
693
  .option("--mode-all", "US-011: opt out of the strict narrow-hint refusal for this run. " +
671
694
  "No-op today; wired so future hq-core-staging releases can flip " +
672
695
  "the default narrow-hint level to 'strict'.")
@@ -674,7 +697,11 @@ export function registerCloudCommands(program) {
674
697
  try {
675
698
  assertSingleSelector(options, "now");
676
699
  if (options.all) {
677
- await runNowAll(options.hqRoot, options.message, options.onConflict, options.modeAll === true);
700
+ // `options.personal === false` is Commander's auto-negation
701
+ // of `--personal`; in `--all` mode that means "drop the
702
+ // personal leg from both legs of the bidirectional fanout"
703
+ // (see `--no-personal` option above).
704
+ await runNowAll(options.hqRoot, options.message, options.onConflict, options.modeAll === true, options.personal === false);
678
705
  return;
679
706
  }
680
707
  await runNowSingle(options.hqRoot, options.company, options.personal === true, options.message, options.onConflict, options.modeAll === true);
@@ -685,10 +712,14 @@ export function registerCloudCommands(program) {
685
712
  }
686
713
  });
687
714
  }
688
- async function runPullAll(hqRoot, onConflict, modeAllOverride) {
715
+ async function runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal) {
689
716
  console.log(chalk.bold("\nHQ Sync — Pull (all)"));
690
717
  console.log(` HQ root: ${hqRoot}`);
691
- console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
718
+ console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
719
+ if (skipPersonal) {
720
+ console.log(` Personal: skipped (--no-personal)`);
721
+ }
722
+ console.log("");
692
723
  let result;
693
724
  try {
694
725
  const accessToken = await ensureCognitoToken();
@@ -712,6 +743,7 @@ async function runPullAll(hqRoot, onConflict, modeAllOverride) {
712
743
  ...(onConflict ? { onConflict } : {}),
713
744
  narrowHintLevel: resolveBannerLevel(),
714
745
  ...(modeAllOverride ? { modeAllOverride: true } : {}),
746
+ ...(skipPersonal ? { skipPersonal: true } : {}),
715
747
  }, {
716
748
  vaultClient: adapter,
717
749
  sync: (opts) => sync({
@@ -785,10 +817,14 @@ async function runPullPersonal(hqRoot, onConflict) {
785
817
  process.exit(1);
786
818
  }
787
819
  }
788
- async function runPushAll(hqRoot, message, onConflict) {
820
+ async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
789
821
  console.log(chalk.bold("\nHQ Sync — Push (all)"));
790
822
  console.log(` HQ root: ${hqRoot}`);
791
- console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
823
+ console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
824
+ if (skipPersonal) {
825
+ console.log(` Personal: skipped (--no-personal)`);
826
+ }
827
+ console.log("");
792
828
  let result;
793
829
  try {
794
830
  const accessToken = await ensureCognitoToken();
@@ -811,6 +847,7 @@ async function runPushAll(hqRoot, message, onConflict) {
811
847
  hqRoot,
812
848
  ...(onConflict ? { onConflict } : {}),
813
849
  ...(message ? { message } : {}),
850
+ ...(skipPersonal ? { skipPersonal: true } : {}),
814
851
  }, {
815
852
  vaultClient: adapter,
816
853
  share: (opts) => share({
@@ -1002,20 +1039,24 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
1002
1039
  process.exit(1);
1003
1040
  }
1004
1041
  }
1005
- async function runNowAll(hqRoot, message, onConflict, modeAllOverride) {
1042
+ async function runNowAll(hqRoot, message, onConflict, modeAllOverride, skipPersonal) {
1006
1043
  console.log(chalk.bold("\nHQ Sync — Now (all)"));
1007
1044
  console.log(` HQ root: ${hqRoot}`);
1008
- console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
1045
+ console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
1046
+ if (skipPersonal) {
1047
+ console.log(` Personal: skipped (--no-personal)`);
1048
+ }
1049
+ console.log("");
1009
1050
  // Push first (matches runner), then pull. Re-uses the per-leg orchestrators
1010
1051
  // so the per-target rendering, error isolation, and exit codes are
1011
1052
  // identical to running `push --all` then `pull --all` back-to-back.
1012
1053
  console.log(chalk.dim("→ push --all"));
1013
- await runPushAll(hqRoot, message, onConflict);
1054
+ await runPushAll(hqRoot, message, onConflict, skipPersonal);
1014
1055
  console.log(chalk.dim("\n→ pull --all"));
1015
1056
  // US-011: forward --mode-all so the strict refusal applies to the
1016
1057
  // pull leg (push doesn't need a narrow-hint — the narrow ritual is
1017
1058
  // pull-side).
1018
- await runPullAll(hqRoot, onConflict, modeAllOverride);
1059
+ await runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal);
1019
1060
  }
1020
1061
  /**
1021
1062
  * Best-effort read of the active company slug from `<hqRoot>/.hq/config.json`.
@@ -1089,4 +1130,4 @@ function resolveUploadAuthorFromCache() {
1089
1130
  }
1090
1131
  }
1091
1132
  //# sourceMappingURL=cloud.js.map
1092
- //# debugId=f6dfc1de-3284-575a-b790-3dc10db2707c
1133
+ //# debugId=bdaf154f-b17d-5482-9e4d-feaebe606a1b
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.19.0",
3
+ "version": "5.20.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "clean": "rm -rf dist"
16
16
  },
17
17
  "dependencies": {
18
- "@indigoai-us/hq-cloud": "^5.23.0",
18
+ "@indigoai-us/hq-cloud": "~5.25.0",
19
19
  "@indigoai-us/hq-onboarding": "^0.1.0",
20
20
  "@sentry/node": "^10.49.0",
21
21
  "chalk": "^5.3.0",
@@ -493,4 +493,102 @@ describe("pullAll", () => {
493
493
  expect(call.hqRoot).toBe("/Users/me/scratch/hq");
494
494
  }
495
495
  });
496
+
497
+ // ── 11. skipPersonal — fanout omits the canonical-person leg ──────────────
498
+ //
499
+ // Mirrors the upstream `hq-sync-runner --skip-personal` flag (hq-cloud
500
+ // 5.25.0) plumbed through to the CLI as `hq sync pull --all --no-personal`.
501
+ // When the option is true, `pullAll` must (a) skip the canonical-person
502
+ // bucket even when one exists, and (b) leave companies untouched.
503
+
504
+ it("skipPersonal=true omits the personal leg even when a person entity exists", async () => {
505
+ const vaultClient = makeVaultClient({
506
+ memberships: [
507
+ { companyUid: "cmp_acme" },
508
+ { companyUid: "cmp_globex" },
509
+ ],
510
+ persons: [
511
+ {
512
+ uid: "psn_alice",
513
+ type: "person",
514
+ slug: "alice",
515
+ bucketName: "hq-vault-psn-alice",
516
+ createdAt: "2026-01-01T00:00:00Z",
517
+ },
518
+ ],
519
+ entitiesBySlug: {
520
+ cmp_acme: { slug: "acme" },
521
+ cmp_globex: { slug: "globex" },
522
+ },
523
+ });
524
+ const sync = makeSyncSpy();
525
+
526
+ const result = await pullAll(
527
+ { hqRoot: "/tmp/hq", skipPersonal: true },
528
+ { vaultClient, sync: sync.fn },
529
+ );
530
+
531
+ expect(sync.calls.map((c) => c.company)).toEqual([
532
+ "cmp_acme",
533
+ "cmp_globex",
534
+ ]);
535
+ for (const call of sync.calls) {
536
+ expect(call.personalMode).toBeUndefined();
537
+ expect(call.journalSlug).toBeUndefined();
538
+ }
539
+ expect(result.attempted).toBe(2);
540
+ expect(result.perCompany.map((r) => r.slug)).toEqual(["acme", "globex"]);
541
+ });
542
+
543
+ it("skipPersonal omitted/false keeps the personal leg in the fanout (baseline)", async () => {
544
+ const vaultClient = makeVaultClient({
545
+ memberships: [{ companyUid: "cmp_acme" }],
546
+ persons: [
547
+ {
548
+ uid: "psn_alice",
549
+ type: "person",
550
+ slug: "alice",
551
+ createdAt: "2026-01-01T00:00:00Z",
552
+ },
553
+ ],
554
+ entitiesBySlug: { cmp_acme: { slug: "acme" } },
555
+ });
556
+ const sync = makeSyncSpy();
557
+
558
+ await pullAll(
559
+ { hqRoot: "/tmp/hq" },
560
+ { vaultClient, sync: sync.fn },
561
+ );
562
+
563
+ expect(sync.calls.map((c) => c.company)).toEqual([
564
+ "cmp_acme",
565
+ "psn_alice",
566
+ ]);
567
+ expect(sync.calls[1].personalMode).toBe(true);
568
+ expect(sync.calls[1].journalSlug).toBe("personal");
569
+ });
570
+
571
+ it("skipPersonal=true with empty memberships produces zero sync calls", async () => {
572
+ const vaultClient = makeVaultClient({
573
+ memberships: [],
574
+ persons: [
575
+ {
576
+ uid: "psn_alice",
577
+ type: "person",
578
+ slug: "alice",
579
+ createdAt: "2026-01-01T00:00:00Z",
580
+ },
581
+ ],
582
+ });
583
+ const sync = makeSyncSpy();
584
+
585
+ const result = await pullAll(
586
+ { hqRoot: "/tmp/hq", skipPersonal: true },
587
+ { vaultClient, sync: sync.fn },
588
+ );
589
+
590
+ expect(sync.calls).toEqual([]);
591
+ expect(result.attempted).toBe(0);
592
+ expect(result.perCompany).toEqual([]);
593
+ });
496
594
  });
@@ -378,4 +378,79 @@ describe("pushAll", () => {
378
378
  expect(result.filesUploaded).toBe(3);
379
379
  expect(result.filesDeleted).toBe(4);
380
380
  });
381
+
382
+ // ── skipPersonal — fanout omits the canonical-person leg ──────────────────
383
+ //
384
+ // Symmetric with the `pullAll` skipPersonal coverage in
385
+ // `cloud.pull-all.test.ts`. Wired through from `hq sync push --all
386
+ // --no-personal` (Commander's `--no-personal` is the auto-negation of the
387
+ // `--personal` single-target selector). Verifies (a) the personal leg is
388
+ // dropped even when a canonical person exists, and (b) the baseline
389
+ // behavior — no flag → personal stays in the plan.
390
+
391
+ it("skipPersonal=true omits the personal leg even when a person entity exists", async () => {
392
+ const hqRoot = makeHqRoot([".git", "companies", "repos", "workspace"]);
393
+ const vaultClient = makeVaultClient({
394
+ memberships: [
395
+ { companyUid: "cmp_acme" },
396
+ { companyUid: "cmp_globex" },
397
+ ],
398
+ persons: [
399
+ {
400
+ uid: "psn_alice",
401
+ type: "person",
402
+ slug: "alice",
403
+ bucketName: "hq-vault-psn-alice",
404
+ createdAt: "2026-01-01T00:00:00Z",
405
+ },
406
+ ],
407
+ entitiesBySlug: {
408
+ cmp_acme: { slug: "acme" },
409
+ cmp_globex: { slug: "globex" },
410
+ },
411
+ });
412
+ const share = makeShareSpy();
413
+
414
+ const result = await pushAll(
415
+ { hqRoot, skipPersonal: true },
416
+ { vaultClient, share: share.fn },
417
+ );
418
+
419
+ expect(share.calls.map((c) => c.company)).toEqual([
420
+ "cmp_acme",
421
+ "cmp_globex",
422
+ ]);
423
+ for (const call of share.calls) {
424
+ expect(call.personalMode).toBeUndefined();
425
+ expect(call.journalSlug).toBeUndefined();
426
+ }
427
+ expect(result.attempted).toBe(2);
428
+ expect(result.perCompany.map((r) => r.slug)).toEqual(["acme", "globex"]);
429
+ });
430
+
431
+ it("skipPersonal omitted keeps the personal leg in the plan (baseline)", async () => {
432
+ const hqRoot = makeHqRoot([".git", "companies", "repos", "workspace"]);
433
+ const vaultClient = makeVaultClient({
434
+ memberships: [{ companyUid: "cmp_acme" }],
435
+ persons: [
436
+ {
437
+ uid: "psn_alice",
438
+ type: "person",
439
+ slug: "alice",
440
+ createdAt: "2026-01-01T00:00:00Z",
441
+ },
442
+ ],
443
+ entitiesBySlug: { cmp_acme: { slug: "acme" } },
444
+ });
445
+ const share = makeShareSpy();
446
+
447
+ await pushAll({ hqRoot }, { vaultClient, share: share.fn });
448
+
449
+ expect(share.calls.map((c) => c.company)).toEqual([
450
+ "cmp_acme",
451
+ "psn_alice",
452
+ ]);
453
+ expect(share.calls[1].personalMode).toBe(true);
454
+ expect(share.calls[1].journalSlug).toBe("personal");
455
+ });
381
456
  });
@@ -125,6 +125,17 @@ export interface PullAllOptions {
125
125
  * legacy behavior for this run via `--mode-all`.
126
126
  */
127
127
  modeAllOverride?: boolean;
128
+ /**
129
+ * When `true`, skip the canonical-person-entity leg entirely — the
130
+ * fanout only visits the caller's company memberships. Mirrors the
131
+ * `--skip-personal` flag and `HQ_SYNC_SKIP_PERSONAL` env var that
132
+ * `@indigoai-us/hq-cloud`'s `sync-runner` exposes in `--companies`
133
+ * mode (see hq-cloud 5.25.0 `resolveSkipPersonal`). Surfaced on the
134
+ * CLI as `hq sync pull --all --no-personal` so the AppBar HQ Sync
135
+ * menubar toggle (and CLI users opting out one-off) can drop the
136
+ * personal vault from the run without touching the rest of the plan.
137
+ */
138
+ skipPersonal?: boolean;
128
139
  }
129
140
 
130
141
  export interface PullAllRow {
@@ -173,6 +184,13 @@ export interface PushAllOptions {
173
184
  hqRoot: string;
174
185
  onConflict?: ConflictStrategy;
175
186
  message?: string;
187
+ /**
188
+ * When `true`, skip the canonical-person-entity leg entirely — the
189
+ * fanout only visits the caller's company memberships. Symmetric with
190
+ * `PullAllOptions.skipPersonal`; surfaced on the CLI as
191
+ * `hq sync push --all --no-personal`.
192
+ */
193
+ skipPersonal?: boolean;
176
194
  }
177
195
 
178
196
  export interface PushAllRow {
@@ -252,7 +270,7 @@ export async function pullAll(
252
270
  });
253
271
  }
254
272
 
255
- const personal = pickCanonicalPerson(persons);
273
+ const personal = options.skipPersonal ? null : pickCanonicalPerson(persons);
256
274
  if (personal) {
257
275
  plan.push({
258
276
  slug: "personal",
@@ -385,7 +403,7 @@ export async function pushAll(
385
403
  });
386
404
  }
387
405
 
388
- const personal = pickCanonicalPerson(persons);
406
+ const personal = options.skipPersonal ? null : pickCanonicalPerson(persons);
389
407
  if (personal) {
390
408
  plan.push({
391
409
  slug: "personal",
@@ -637,6 +655,15 @@ export function registerCloudCommands(program: Command): void {
637
655
  "(.git, companies, repos, workspace) — same scope as `--all`'s " +
638
656
  "personal slot. Mutually exclusive with --company and --all.",
639
657
  )
658
+ .option(
659
+ "--no-personal",
660
+ "In `--all` mode, skip the canonical-person leg of the fanout — " +
661
+ "only push to the caller's company memberships. Mirrors the " +
662
+ "upstream `hq-sync-runner --skip-personal` flag (hq-cloud 5.25.0); " +
663
+ "wired so the AppBar HQ Sync menubar can drop personal sync via " +
664
+ "a toggle, and so CLI users can opt out one-off. Ignored outside " +
665
+ "`--all`.",
666
+ )
640
667
  .action(
641
668
  async (
642
669
  paths: string[],
@@ -678,10 +705,17 @@ export function registerCloudCommands(program: Command): void {
678
705
  );
679
706
  process.exit(1);
680
707
  }
708
+ // `options.personal === false` happens when the user passed
709
+ // `--no-personal` (Commander's auto-negation of the `--personal`
710
+ // selector). In `--all` mode that means "skip the personal leg of
711
+ // the fanout" — wired through to `pushAll.skipPersonal`. Outside
712
+ // `--all` the flag has no effect (logged above as part of the
713
+ // option's help text).
681
714
  await runPushAll(
682
715
  options.hqRoot,
683
716
  options.message,
684
717
  options.onConflict,
718
+ options.personal === false,
685
719
  );
686
720
  return;
687
721
  }
@@ -885,6 +919,13 @@ export function registerCloudCommands(program: Command): void {
885
919
  "from the cached Cognito session. Mutually exclusive with --company " +
886
920
  "and --all.",
887
921
  )
922
+ .option(
923
+ "--no-personal",
924
+ "In `--all` mode, skip the canonical-person leg of the fanout — " +
925
+ "only pull the caller's company memberships. Mirrors the upstream " +
926
+ "`hq-sync-runner --skip-personal` flag (hq-cloud 5.25.0). Ignored " +
927
+ "outside `--all`.",
928
+ )
888
929
  .option(
889
930
  "--mode-all",
890
931
  "US-011: opt out of the strict narrow-hint refusal for this run. " +
@@ -911,10 +952,14 @@ export function registerCloudCommands(program: Command): void {
911
952
  process.exit(1);
912
953
  }
913
954
  if (options.all) {
955
+ // `options.personal === false` is Commander's auto-negation of
956
+ // `--personal`; in `--all` mode that means "drop the personal
957
+ // leg from the fanout" (see `--no-personal` option above).
914
958
  await runPullAll(
915
959
  options.hqRoot,
916
960
  options.onConflict,
917
961
  options.modeAll === true,
962
+ options.personal === false,
918
963
  );
919
964
  return;
920
965
  }
@@ -1100,6 +1145,13 @@ export function registerCloudCommands(program: Command): void {
1100
1145
  "Sync the caller's canonical personal vault bidirectionally. " +
1101
1146
  "Mutually exclusive with --company and --all.",
1102
1147
  )
1148
+ .option(
1149
+ "--no-personal",
1150
+ "In `--all` mode, skip the canonical-person leg of both the push " +
1151
+ "and pull fanouts — only sync the caller's company memberships. " +
1152
+ "Mirrors the upstream `hq-sync-runner --skip-personal` flag " +
1153
+ "(hq-cloud 5.25.0). Ignored outside `--all`.",
1154
+ )
1103
1155
  .option(
1104
1156
  "--mode-all",
1105
1157
  "US-011: opt out of the strict narrow-hint refusal for this run. " +
@@ -1119,11 +1171,16 @@ export function registerCloudCommands(program: Command): void {
1119
1171
  try {
1120
1172
  assertSingleSelector(options, "now");
1121
1173
  if (options.all) {
1174
+ // `options.personal === false` is Commander's auto-negation
1175
+ // of `--personal`; in `--all` mode that means "drop the
1176
+ // personal leg from both legs of the bidirectional fanout"
1177
+ // (see `--no-personal` option above).
1122
1178
  await runNowAll(
1123
1179
  options.hqRoot,
1124
1180
  options.message,
1125
1181
  options.onConflict,
1126
1182
  options.modeAll === true,
1183
+ options.personal === false,
1127
1184
  );
1128
1185
  return;
1129
1186
  }
@@ -1150,10 +1207,15 @@ async function runPullAll(
1150
1207
  hqRoot: string,
1151
1208
  onConflict?: ConflictStrategy,
1152
1209
  modeAllOverride?: boolean,
1210
+ skipPersonal?: boolean,
1153
1211
  ): Promise<void> {
1154
1212
  console.log(chalk.bold("\nHQ Sync — Pull (all)"));
1155
1213
  console.log(` HQ root: ${hqRoot}`);
1156
- console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
1214
+ console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
1215
+ if (skipPersonal) {
1216
+ console.log(` Personal: skipped (--no-personal)`);
1217
+ }
1218
+ console.log("");
1157
1219
 
1158
1220
  let result: PullAllResult;
1159
1221
  try {
@@ -1181,6 +1243,7 @@ async function runPullAll(
1181
1243
  ...(onConflict ? { onConflict } : {}),
1182
1244
  narrowHintLevel: resolveBannerLevel(),
1183
1245
  ...(modeAllOverride ? { modeAllOverride: true } : {}),
1246
+ ...(skipPersonal ? { skipPersonal: true } : {}),
1184
1247
  },
1185
1248
  {
1186
1249
  vaultClient: adapter,
@@ -1285,10 +1348,15 @@ async function runPushAll(
1285
1348
  hqRoot: string,
1286
1349
  message?: string,
1287
1350
  onConflict?: ConflictStrategy,
1351
+ skipPersonal?: boolean,
1288
1352
  ): Promise<void> {
1289
1353
  console.log(chalk.bold("\nHQ Sync — Push (all)"));
1290
1354
  console.log(` HQ root: ${hqRoot}`);
1291
- console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
1355
+ console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
1356
+ if (skipPersonal) {
1357
+ console.log(` Personal: skipped (--no-personal)`);
1358
+ }
1359
+ console.log("");
1292
1360
 
1293
1361
  let result: PushAllResult;
1294
1362
  try {
@@ -1314,6 +1382,7 @@ async function runPushAll(
1314
1382
  hqRoot,
1315
1383
  ...(onConflict ? { onConflict } : {}),
1316
1384
  ...(message ? { message } : {}),
1385
+ ...(skipPersonal ? { skipPersonal: true } : {}),
1317
1386
  },
1318
1387
  {
1319
1388
  vaultClient: adapter,
@@ -1552,21 +1621,26 @@ async function runNowAll(
1552
1621
  message?: string,
1553
1622
  onConflict?: ConflictStrategy,
1554
1623
  modeAllOverride?: boolean,
1624
+ skipPersonal?: boolean,
1555
1625
  ): Promise<void> {
1556
1626
  console.log(chalk.bold("\nHQ Sync — Now (all)"));
1557
1627
  console.log(` HQ root: ${hqRoot}`);
1558
- console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
1628
+ console.log(` Strategy: ${onConflict ?? "(interactive)"}`);
1629
+ if (skipPersonal) {
1630
+ console.log(` Personal: skipped (--no-personal)`);
1631
+ }
1632
+ console.log("");
1559
1633
 
1560
1634
  // Push first (matches runner), then pull. Re-uses the per-leg orchestrators
1561
1635
  // so the per-target rendering, error isolation, and exit codes are
1562
1636
  // identical to running `push --all` then `pull --all` back-to-back.
1563
1637
  console.log(chalk.dim("→ push --all"));
1564
- await runPushAll(hqRoot, message, onConflict);
1638
+ await runPushAll(hqRoot, message, onConflict, skipPersonal);
1565
1639
  console.log(chalk.dim("\n→ pull --all"));
1566
1640
  // US-011: forward --mode-all so the strict refusal applies to the
1567
1641
  // pull leg (push doesn't need a narrow-hint — the narrow ritual is
1568
1642
  // pull-side).
1569
- await runPullAll(hqRoot, onConflict, modeAllOverride);
1643
+ await runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal);
1570
1644
  }
1571
1645
 
1572
1646
  /**