@indigoai-us/hq-cli 5.6.0 → 5.6.1

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.
@@ -55,10 +55,12 @@ export interface ProvisionResult {
55
55
  manifest_patched: boolean;
56
56
  config_written: boolean;
57
57
  initial_sync: {
58
- ok: boolean;
58
+ ok?: boolean;
59
59
  files_uploaded?: number;
60
60
  bytes_uploaded?: number;
61
61
  error?: string;
62
+ /** True if the caller passed --skip-initial-sync; ok/files/bytes will be absent. */
63
+ skipped?: boolean;
62
64
  };
63
65
  }
64
66
  /** Options for the high-level `provisionCompany` orchestrator. */
@@ -68,6 +70,15 @@ export interface ProvisionCompanyOptions {
68
70
  ownerUid?: string;
69
71
  hqRoot: string;
70
72
  vaultApiUrl: string;
73
+ /**
74
+ * Skip the initial-sync step. The vault entity, manifest patch, and
75
+ * `.hq/config.json` write still happen; the post-provision `share()` call
76
+ * is no-op'd. Use this when the caller has its own upload pipeline (e.g.
77
+ * AppBar HQ Sync's `first_push_company` with STS-vended credentials and
78
+ * Tauri progress events) and would otherwise double-upload the same files.
79
+ * When true, `initial_sync` in the result is `{ skipped: true }`.
80
+ */
81
+ skipInitialSync?: boolean;
71
82
  /** Injected vault HTTP client (override for tests). */
72
83
  vaultClient?: VaultClient;
73
84
  /** Injected access-token resolver (override for tests). */
@@ -25,7 +25,7 @@
25
25
  * `initial_sync.ok=false`). Manifest + config may have been written.
26
26
  */
27
27
 
28
- !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]="301e6769-89c3-53e0-9b81-42840d6d59f7")}catch(e){}}();
28
+ !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]="325d7bbf-c44d-5bba-a156-072302ab1536")}catch(e){}}();
29
29
  import chalk from "chalk";
30
30
  import * as fs from "node:fs";
31
31
  import * as path from "node:path";
@@ -317,39 +317,47 @@ export async function provisionCompany(options) {
317
317
  vaultApiUrl: options.vaultApiUrl,
318
318
  });
319
319
  log(`wrote companies/${options.slug}/.hq/config.json`);
320
- // Step 8: trigger initial sync (failure ⇒ exit 3 with cloud_uid populated)
321
- const runner = options.runInitialSync ?? defaultRunInitialSync;
320
+ // Step 8: trigger initial sync (failure ⇒ exit 3 with cloud_uid populated).
321
+ // Skipped when caller passed --skip-initial-sync (e.g. AppBar HQ Sync, which
322
+ // owns its own STS-credentialed upload pipeline + Tauri progress events).
322
323
  let initialSync;
323
- try {
324
- log(`triggering initial sync via share()`);
325
- const sync = await runner({
326
- slug: options.slug,
327
- hqRoot: options.hqRoot,
328
- accessToken,
329
- vaultApiUrl: options.vaultApiUrl,
330
- });
331
- initialSync = {
332
- ok: true,
333
- files_uploaded: sync.filesUploaded,
334
- bytes_uploaded: sync.bytesUploaded,
335
- };
336
- log(`initial sync complete — files=${sync.filesUploaded} bytes=${sync.bytesUploaded}`);
324
+ if (options.skipInitialSync) {
325
+ log(`skipping initial sync (--skip-initial-sync)`);
326
+ initialSync = { skipped: true };
337
327
  }
338
- catch (err) {
339
- const msg = err instanceof Error ? err.message : String(err);
340
- log(`initial sync failed: ${msg}`);
341
- throw new ProvisionError(3, `Initial sync failed: ${msg}`, {
342
- ok: false,
343
- company_slug: options.slug,
344
- cloud_uid: cloudUid,
345
- bucket_name: bucketName,
346
- vault_api_url: options.vaultApiUrl,
347
- kms_key_id: kmsKeyId,
348
- created_entity: createdEntity,
349
- manifest_patched: true,
350
- config_written: true,
351
- initial_sync: { ok: false, error: msg },
352
- });
328
+ else {
329
+ const runner = options.runInitialSync ?? defaultRunInitialSync;
330
+ try {
331
+ log(`triggering initial sync via share()`);
332
+ const sync = await runner({
333
+ slug: options.slug,
334
+ hqRoot: options.hqRoot,
335
+ accessToken,
336
+ vaultApiUrl: options.vaultApiUrl,
337
+ });
338
+ initialSync = {
339
+ ok: true,
340
+ files_uploaded: sync.filesUploaded,
341
+ bytes_uploaded: sync.bytesUploaded,
342
+ };
343
+ log(`initial sync complete — files=${sync.filesUploaded} bytes=${sync.bytesUploaded}`);
344
+ }
345
+ catch (err) {
346
+ const msg = err instanceof Error ? err.message : String(err);
347
+ log(`initial sync failed: ${msg}`);
348
+ throw new ProvisionError(3, `Initial sync failed: ${msg}`, {
349
+ ok: false,
350
+ company_slug: options.slug,
351
+ cloud_uid: cloudUid,
352
+ bucket_name: bucketName,
353
+ vault_api_url: options.vaultApiUrl,
354
+ kms_key_id: kmsKeyId,
355
+ created_entity: createdEntity,
356
+ manifest_patched: true,
357
+ config_written: true,
358
+ initial_sync: { ok: false, error: msg },
359
+ });
360
+ }
353
361
  }
354
362
  return {
355
363
  ok: true,
@@ -385,6 +393,9 @@ export function registerCloudProvisionCommands(program) {
385
393
  .option("--owner <uid>", "Owner person UID (default: current Cognito user sub)")
386
394
  .option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
387
395
  .option("--vault-api-url <url>", `Vault API URL (default: ${DEFAULT_VAULT_API_URL})`, DEFAULT_VAULT_API_URL)
396
+ .option("--skip-initial-sync", "Skip the post-provision share() initial sync. Use when the caller " +
397
+ "(e.g. AppBar HQ Sync) has its own upload pipeline. Result includes " +
398
+ "{ initial_sync: { skipped: true } } when set.")
388
399
  .action(async (slug, options) => {
389
400
  try {
390
401
  const result = await provisionCompany({
@@ -393,6 +404,7 @@ export function registerCloudProvisionCommands(program) {
393
404
  ownerUid: options.owner,
394
405
  hqRoot: options.hqRoot,
395
406
  vaultApiUrl: options.vaultApiUrl,
407
+ skipInitialSync: options.skipInitialSync,
396
408
  });
397
409
  // Final stdout line — single JSON document for downstream consumers
398
410
  process.stdout.write(JSON.stringify(result) + "\n");
@@ -414,4 +426,4 @@ export function registerCloudProvisionCommands(program) {
414
426
  });
415
427
  }
416
428
  //# sourceMappingURL=cloud-provision.js.map
417
- //# debugId=301e6769-89c3-53e0-9b81-42840d6d59f7
429
+ //# debugId=325d7bbf-c44d-5bba-a156-072302ab1536
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.6.0",
3
+ "version": "5.6.1",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -649,4 +649,75 @@ describe("provisionCompany", () => {
649
649
  ownerUid: undefined,
650
650
  });
651
651
  });
652
+
653
+ it("skipInitialSync=true — runner is NOT called; result has initial_sync.skipped", async () => {
654
+ setupValid();
655
+ const entity: VaultEntity = {
656
+ uid: "cmp_01H",
657
+ type: "company",
658
+ slug: "indigo",
659
+ name: "Indigo",
660
+ bucketName: "hq-vault-cmp-01H",
661
+ };
662
+ const vaultClient = makeVaultClient({
663
+ findCompanyBySlug: vi.fn().mockResolvedValue(null),
664
+ createCompanyEntity: vi.fn().mockResolvedValue(entity),
665
+ });
666
+ const runInitialSync = vi.fn();
667
+
668
+ const result = await provisionCompany({
669
+ slug: "indigo",
670
+ hqRoot: tmpRoot,
671
+ vaultApiUrl,
672
+ vaultClient,
673
+ resolveAccessToken: async () => accessToken,
674
+ runInitialSync,
675
+ skipInitialSync: true,
676
+ log: () => {},
677
+ });
678
+
679
+ expect(runInitialSync).not.toHaveBeenCalled();
680
+ expect(result.initial_sync).toEqual({ skipped: true });
681
+ expect(result.ok).toBe(true);
682
+ expect(result.cloud_uid).toBe("cmp_01H");
683
+ expect(result.manifest_patched).toBe(true);
684
+ expect(result.config_written).toBe(true);
685
+ });
686
+
687
+ it("skipInitialSync=true — manifest + .hq/config.json are STILL written", async () => {
688
+ setupValid();
689
+ const entity: VaultEntity = {
690
+ uid: "cmp_SKIP",
691
+ type: "company",
692
+ slug: "indigo",
693
+ name: "Indigo",
694
+ bucketName: "hq-vault-cmp-SKIP",
695
+ };
696
+ const vaultClient = makeVaultClient({
697
+ findCompanyBySlug: vi.fn().mockResolvedValue(null),
698
+ createCompanyEntity: vi.fn().mockResolvedValue(entity),
699
+ });
700
+ await provisionCompany({
701
+ slug: "indigo",
702
+ hqRoot: tmpRoot,
703
+ vaultApiUrl,
704
+ vaultClient,
705
+ resolveAccessToken: async () => accessToken,
706
+ runInitialSync: vi.fn(),
707
+ skipInitialSync: true,
708
+ log: () => {},
709
+ });
710
+ // Manifest patched on disk
711
+ const m = yaml.load(fs.readFileSync(manifestPath(tmpRoot), "utf-8")) as {
712
+ companies: Record<string, Record<string, unknown>>;
713
+ };
714
+ expect(m.companies.indigo.cloud_uid).toBe("cmp_SKIP");
715
+ expect(m.companies.indigo.bucket_name).toBe("hq-vault-cmp-SKIP");
716
+ // .hq/config.json written
717
+ const c = JSON.parse(
718
+ fs.readFileSync(companyConfigPath(tmpRoot, "indigo"), "utf-8"),
719
+ );
720
+ expect(c.companyUid).toBe("cmp_SKIP");
721
+ expect(c.bucketName).toBe("hq-vault-cmp-SKIP");
722
+ });
652
723
  });
@@ -74,10 +74,12 @@ export interface ProvisionResult {
74
74
  manifest_patched: boolean;
75
75
  config_written: boolean;
76
76
  initial_sync: {
77
- ok: boolean;
77
+ ok?: boolean;
78
78
  files_uploaded?: number;
79
79
  bytes_uploaded?: number;
80
80
  error?: string;
81
+ /** True if the caller passed --skip-initial-sync; ok/files/bytes will be absent. */
82
+ skipped?: boolean;
81
83
  };
82
84
  }
83
85
 
@@ -88,6 +90,15 @@ export interface ProvisionCompanyOptions {
88
90
  ownerUid?: string;
89
91
  hqRoot: string;
90
92
  vaultApiUrl: string;
93
+ /**
94
+ * Skip the initial-sync step. The vault entity, manifest patch, and
95
+ * `.hq/config.json` write still happen; the post-provision `share()` call
96
+ * is no-op'd. Use this when the caller has its own upload pipeline (e.g.
97
+ * AppBar HQ Sync's `first_push_company` with STS-vended credentials and
98
+ * Tauri progress events) and would otherwise double-upload the same files.
99
+ * When true, `initial_sync` in the result is `{ skipped: true }`.
100
+ */
101
+ skipInitialSync?: boolean;
91
102
  /** Injected vault HTTP client (override for tests). */
92
103
  vaultClient?: VaultClient;
93
104
  /** Injected access-token resolver (override for tests). */
@@ -510,40 +521,47 @@ export async function provisionCompany(
510
521
  });
511
522
  log(`wrote companies/${options.slug}/.hq/config.json`);
512
523
 
513
- // Step 8: trigger initial sync (failure ⇒ exit 3 with cloud_uid populated)
514
- const runner = options.runInitialSync ?? defaultRunInitialSync;
524
+ // Step 8: trigger initial sync (failure ⇒ exit 3 with cloud_uid populated).
525
+ // Skipped when caller passed --skip-initial-sync (e.g. AppBar HQ Sync, which
526
+ // owns its own STS-credentialed upload pipeline + Tauri progress events).
515
527
  let initialSync: ProvisionResult["initial_sync"];
516
- try {
517
- log(`triggering initial sync via share()`);
518
- const sync = await runner({
519
- slug: options.slug,
520
- hqRoot: options.hqRoot,
521
- accessToken,
522
- vaultApiUrl: options.vaultApiUrl,
523
- });
524
- initialSync = {
525
- ok: true,
526
- files_uploaded: sync.filesUploaded,
527
- bytes_uploaded: sync.bytesUploaded,
528
- };
529
- log(
530
- `initial sync complete — files=${sync.filesUploaded} bytes=${sync.bytesUploaded}`,
531
- );
532
- } catch (err) {
533
- const msg = err instanceof Error ? err.message : String(err);
534
- log(`initial sync failed: ${msg}`);
535
- throw new ProvisionError(3, `Initial sync failed: ${msg}`, {
536
- ok: false,
537
- company_slug: options.slug,
538
- cloud_uid: cloudUid,
539
- bucket_name: bucketName,
540
- vault_api_url: options.vaultApiUrl,
541
- kms_key_id: kmsKeyId,
542
- created_entity: createdEntity,
543
- manifest_patched: true,
544
- config_written: true,
545
- initial_sync: { ok: false, error: msg },
546
- });
528
+ if (options.skipInitialSync) {
529
+ log(`skipping initial sync (--skip-initial-sync)`);
530
+ initialSync = { skipped: true };
531
+ } else {
532
+ const runner = options.runInitialSync ?? defaultRunInitialSync;
533
+ try {
534
+ log(`triggering initial sync via share()`);
535
+ const sync = await runner({
536
+ slug: options.slug,
537
+ hqRoot: options.hqRoot,
538
+ accessToken,
539
+ vaultApiUrl: options.vaultApiUrl,
540
+ });
541
+ initialSync = {
542
+ ok: true,
543
+ files_uploaded: sync.filesUploaded,
544
+ bytes_uploaded: sync.bytesUploaded,
545
+ };
546
+ log(
547
+ `initial sync complete files=${sync.filesUploaded} bytes=${sync.bytesUploaded}`,
548
+ );
549
+ } catch (err) {
550
+ const msg = err instanceof Error ? err.message : String(err);
551
+ log(`initial sync failed: ${msg}`);
552
+ throw new ProvisionError(3, `Initial sync failed: ${msg}`, {
553
+ ok: false,
554
+ company_slug: options.slug,
555
+ cloud_uid: cloudUid,
556
+ bucket_name: bucketName,
557
+ vault_api_url: options.vaultApiUrl,
558
+ kms_key_id: kmsKeyId,
559
+ created_entity: createdEntity,
560
+ manifest_patched: true,
561
+ config_written: true,
562
+ initial_sync: { ok: false, error: msg },
563
+ });
564
+ }
547
565
  }
548
566
 
549
567
  return {
@@ -593,6 +611,12 @@ export function registerCloudProvisionCommands(program: Command): void {
593
611
  `Vault API URL (default: ${DEFAULT_VAULT_API_URL})`,
594
612
  DEFAULT_VAULT_API_URL,
595
613
  )
614
+ .option(
615
+ "--skip-initial-sync",
616
+ "Skip the post-provision share() initial sync. Use when the caller " +
617
+ "(e.g. AppBar HQ Sync) has its own upload pipeline. Result includes " +
618
+ "{ initial_sync: { skipped: true } } when set.",
619
+ )
596
620
  .action(
597
621
  async (
598
622
  slug: string,
@@ -601,6 +625,7 @@ export function registerCloudProvisionCommands(program: Command): void {
601
625
  owner?: string;
602
626
  hqRoot: string;
603
627
  vaultApiUrl: string;
628
+ skipInitialSync?: boolean;
604
629
  },
605
630
  ) => {
606
631
  try {
@@ -610,6 +635,7 @@ export function registerCloudProvisionCommands(program: Command): void {
610
635
  ownerUid: options.owner,
611
636
  hqRoot: options.hqRoot,
612
637
  vaultApiUrl: options.vaultApiUrl,
638
+ skipInitialSync: options.skipInitialSync,
613
639
  });
614
640
  // Final stdout line — single JSON document for downstream consumers
615
641
  process.stdout.write(JSON.stringify(result) + "\n");